pax_global_header00006660000000000000000000000064132624512510014513gustar00rootroot0000000000000052 comment=e0f20e9f82784857780a763b11e0183b797c110a tdesktop-1.2.17/000077500000000000000000000000001326245125100134405ustar00rootroot00000000000000tdesktop-1.2.17/.appveyor/000077500000000000000000000000001326245125100153635ustar00rootroot00000000000000tdesktop-1.2.17/.appveyor/install.bat000066400000000000000000000054171326245125100175300ustar00rootroot00000000000000@echo off IF "%BUILD_DIR%"=="" SET BUILD_DIR=C:\TBuild SET LIB_DIR=%BUILD_DIR%\Libraries SET SRC_DIR=%BUILD_DIR%\tdesktop SET QT_VERSION=5_6_2 call:configureBuild call:getDependencies call:setupGYP cd %SRC_DIR% echo Finished! GOTO:EOF :: FUNCTIONS :logInfo echo [INFO] %~1 GOTO:EOF :logError echo [ERROR] %~1 GOTO:EOF :getDependencies call:logInfo "Clone dependencies repository" git clone -q --depth 1 --branch=master https://github.com/telegramdesktop/dependencies_windows.git %LIB_DIR% cd %LIB_DIR% git clone https://github.com/Microsoft/Range-V3-VS2015 range-v3 if exist prepare.bat ( call prepare.bat ) else ( call:logError "Error cloning dependencies, trying again" rmdir %LIB_DIR% /S /Q call:getDependencies ) GOTO:EOF :setupGYP call:logInfo "Setup GYP/Ninja and generate VS solution" cd %LIB_DIR% git clone https://chromium.googlesource.com/external/gyp cd gyp git checkout a478c1ab51 SET PATH=%PATH%;%BUILD_DIR%\Libraries\gyp;%BUILD_DIR%\Libraries\ninja; cd %SRC_DIR% git submodule init git submodule update cd %SRC_DIR%\Telegram call gyp\refresh.bat GOTO:EOF :configureBuild call:logInfo "Configuring build" call:logInfo "Build version: %BUILD_VERSION%" set TDESKTOP_BUILD_DEFINES= echo %BUILD_VERSION% | findstr /C:"disable_autoupdate">nul && ( set TDESKTOP_BUILD_DEFINES=%TDESKTOP_BUILD_DEFINES%,TDESKTOP_DISABLE_AUTOUPDATE ) echo %BUILD_VERSION% | findstr /C:"disable_register_custom_scheme">nul && ( set TDESKTOP_BUILD_DEFINES=%TDESKTOP_BUILD_DEFINES%,TDESKTOP_DISABLE_REGISTER_CUSTOM_SCHEME ) echo %BUILD_VERSION% | findstr /C:"disable_crash_reports">nul && ( set TDESKTOP_BUILD_DEFINES=%TDESKTOP_BUILD_DEFINES%,TDESKTOP_DISABLE_CRASH_REPORTS ) echo %BUILD_VERSION% | findstr /C:"disable_network_proxy">nul && ( set TDESKTOP_BUILD_DEFINES=%TDESKTOP_BUILD_DEFINES%,TDESKTOP_DISABLE_NETWORK_PROXY ) echo %BUILD_VERSION% | findstr /C:"disable_desktop_file_generation">nul && ( set TDESKTOP_BUILD_DEFINES=%TDESKTOP_BUILD_DEFINES%,TDESKTOP_DISABLE_DESKTOP_FILE_GENERATION ) echo %BUILD_VERSION% | findstr /C:"disable_unity_integration">nul && ( set TDESKTOP_BUILD_DEFINES=%TDESKTOP_BUILD_DEFINES%,TDESKTOP_DISABLE_UNITY_INTEGRATION ) echo %BUILD_VERSION% | findstr /C:"disable_gtk_integration">nul && ( set TDESKTOP_BUILD_DEFINES=%TDESKTOP_BUILD_DEFINES%,TDESKTOP_DISABLE_GTK_INTEGRATION ) if not "%TDESKTOP_BUILD_DEFINES%" == "" ( set "TDESKTOP_BUILD_DEFINES=%TDESKTOP_BUILD_DEFINES:~1%" ) call:logInfo "Build Defines: %TDESKTOP_BUILD_DEFINES%" GOTO:EOF tdesktop-1.2.17/.gitattributes000066400000000000000000000002341326245125100163320ustar00rootroot00000000000000# Set the default behavior, in case people don't have core.autocrlf set. * text=auto # Ensure diffs have LF endings *.diff text eol=lf *.bat text eol=crlf tdesktop-1.2.17/.github/000077500000000000000000000000001326245125100150005ustar00rootroot00000000000000tdesktop-1.2.17/.github/CONTRIBUTING.md000066400000000000000000000134521326245125100172360ustar00rootroot00000000000000# Contributing This document describes how you can contribute to Telegram Desktop. Please read it carefully. **Table of Contents** * [What contributions are accepted](#what-contributions-are-accepted) * [Build instructions](#build-instructions) * [Pull upstream changes into your fork regularly](#pull-upstream-changes-into-your-fork-regularly) * [How to get your pull request accepted](#how-to-get-your-pull-request-accepted) * [Keep your pull requests limited to a single issue](#keep-your-pull-requests-limited-to-a-single-issue) * [Squash your commits to a single commit](#squash-your-commits-to-a-single-commit) * [Don't mix code changes with whitespace cleanup](#dont-mix-code-changes-with-whitespace-cleanup) * [Keep your code simple!](#keep-your-code-simple) * [Test your changes!](#test-your-changes) * [Write a good commit message](#write-a-good-commit-message) ## What contributions are accepted We highly appreciate your contributions in the matter of fixing bugs and optimizing the Telegram Desktop source code and its documentation. In case of fixing the existing user experience please push to your fork and [submit a pull request][pr]. Wait for us. We try to review your pull requests as fast as possible. If we find issues with your pull request, we may suggest some changes and improvements. Unfortunately we **do not merge** any pull requests that have new feature implementations, translations to new languages and those which introduce any new user interface elements. Telegram Desktop is not a standalone application but a part of [Telegram project][telegram], so all the decisions about the features, languages, user experience, user interface and the design are made inside Telegram team, often according to some roadmap which is not public. ## Build instructions See the [README.md][build_instructions] for details on the various build environments. ## Pull upstream changes into your fork regularly Telegram Desktop is advancing quickly. It is therefore critical that you pull upstream changes into your fork on a regular basis. Nothing is worse than putting in a days of hard work into a pull request only to have it rejected because it has diverged too far from upstram. To pull in upstream changes: git remote add upstream https://github.com/telegramdesktop/tdesktop.git git fetch upstream master Check the log to be sure that you actually want the changes, before merging: git log upstream/master Then rebase your changes on the latest commits in the `master` branch: git rebase upstream/master After that, you have to force push your commits: git push --force For more info, see [GitHub Help][help_fork_repo]. ## How to get your pull request accepted We want to improve Telegram Desktop with your contributions. But we also want to provide a stable experience for our users and the community. Follow these rules and you should succeed without a problem! ### Keep your pull requests limited to a single issue Pull requests should be as small/atomic as possible. Large, wide-sweeping changes in a pull request will be **rejected**, with comments to isolate the specific code in your pull request. Some examples: * If you are making spelling corrections in the docs, don't modify other files. * If you are adding new functions don't '*cleanup*' unrelated functions. That cleanup belongs in another pull request. #### Squash your commits to a single commit To keep the history of the project clean, you should make one commit per pull request. If you already have multiple commits, you can add the commits together (squash them) with the following commands in Git Bash: 1. Open `Git Bash` (or `Git Shell`) 2. Enter following command to squash the recent {N} commits: `git reset --soft HEAD~{N} && git commit` (replace `{N}` with the number of commits you want to squash) 3. Press i to get into Insert-mode 4. Enter the commit message of the new commit 5. After adding the message, press ESC to get out of the Insert-mode 6. Write `:wq` and press Enter to save the new message or write `:q!` to discard your changes 7. Enter `git push --force` to push the new commit to the remote repository For example, if you want to squash the last 5 commits, use `git reset --soft HEAD~5 && git commit` ### Don't mix code changes with whitespace cleanup If you change two lines of code and correct 200 lines of whitespace issues in a file the diff on that pull request is functionally unreadable and will be **rejected**. Whitespace cleanups need to be in their own pull request. ### Keep your code simple! Please keep your code as clean and straightforward as possible. Furthermore, the pixel shortage is over. We want to see: * `opacity` instead of `o` * `placeholder` instead of `ph` * `myFunctionThatDoesThings()` instead of `mftdt()` ### Test your changes! Before you submit a pull request, please test your changes. Verify that Telegram Desktop still works and your changes don't cause other issue or crashes. ### Write a good commit message * Explain why you make the changes. [More infos about a good commit message.][commit_message] * If you fix an issue with your commit, please close the issue by [adding one of the keywords and the issue number][closing-issues-via-commit-messages] to your commit message. For example: `Fix #545` [//]: # (LINKS) [telegram]: https://telegram.org/ [help_fork_repo]: https://help.github.com/articles/fork-a-repo/ [help_change_commit_message]: https://help.github.com/articles/changing-a-commit-message/ [commit_message]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html [pr]: https://github.com/telegramdesktop/tdesktop/compare [build_instructions]: https://github.com/telegramdesktop/tdesktop/blob/master/README.md#build-instructions [closing-issues-via-commit-messages]: https://help.github.com/articles/closing-issues-via-commit-messages/ tdesktop-1.2.17/.github/ISSUE_TEMPLATE.md000066400000000000000000000011531326245125100175050ustar00rootroot00000000000000 ### Steps to reproduce 1. 2. 3. ### Expected behaviour Tell us what should happen ### Actual behaviour Tell us what happens instead ### Configuration **Operating system:** **Version of Telegram Desktop:** **Used theme**:
Logs: Insert logs here (if necessary)
tdesktop-1.2.17/.gitignore000066400000000000000000000017561326245125100154410ustar00rootroot00000000000000/out/ /Debug/ /Release/ /Deploy/ /ThirdParty/ /Telegram/build/target /Telegram/GeneratedFiles/ /Telegram/SourceFiles/art/grid.png /Telegram/SourceFiles/art/grid_125x.png /Telegram/SourceFiles/art/grid_150x.png /Telegram/SourceFiles/art/grid_200x.png /Telegram/SourceFiles/art/sprite_125x.png /Telegram/SourceFiles/art/sprite_150x.png /Telegram/Resources/art/grid.png /Telegram/Resources/art/grid_125x.png /Telegram/Resources/art/grid_150x.png /Telegram/Resources/art/grid_200x.png /Telegram/Resources/art/sprite_125x.png /Telegram/Resources/art/sprite_150x.png /Telegram/Debug/ /Telegram/Release/ /Telegram/tests/ /Telegram/gyp/tests/*.test /Telegram/out/ /Telegram/*.user *.vcxproj* *.sln *.suo *.sdf *.opensdf *.opendb *.VC.db *.aps *.xcodeproj /Win32/ ipch/ .vs/ /Telegram/log.txt /Telegram/data /Telegram/data_config /Telegram/DebugLogs/ /Telegram/tdata/ /Telegram/tdumps/ .DS_Store ._* .qmake.stash /Mac/ project.xcworkspace xcuserdata /Telegram/*.user.* *.pro.user /Linux/ /Telegram/Makefile *.*~ tdesktop-1.2.17/.gitmodules000066400000000000000000000011351326245125100156150ustar00rootroot00000000000000[submodule "Telegram/ThirdParty/libtgvoip"] path = Telegram/ThirdParty/libtgvoip url = https://github.com/telegramdesktop/libtgvoip [submodule "Telegram/ThirdParty/variant"] path = Telegram/ThirdParty/variant url = https://github.com/mapbox/variant [submodule "Telegram/ThirdParty/GSL"] path = Telegram/ThirdParty/GSL url = https://github.com/Microsoft/GSL.git [submodule "Telegram/ThirdParty/Catch"] path = Telegram/ThirdParty/Catch url = https://github.com/philsquared/Catch [submodule "Telegram/ThirdParty/crl"] path = Telegram/ThirdParty/crl url = https://github.com/telegramdesktop/crl.git tdesktop-1.2.17/.travis.yml000066400000000000000000000025541326245125100155570ustar00rootroot00000000000000sudo: required dist: trusty language: cpp cache: directories: - $HOME/travisCacheDir env: matrix: - BUILD_VERSION="" - BUILD_VERSION="disable_autoupdate" - BUILD_VERSION="disable_register_custom_scheme" - BUILD_VERSION="disable_crash_reports" - BUILD_VERSION="disable_network_proxy" - BUILD_VERSION="disable_desktop_file_generation" - BUILD_VERSION="disable_unity_integration" - BUILD_VERSION="disable_gtk_integration" matrix: fast_finish: true addons: apt: sources: - ubuntu-toolchain-r-test packages: - bison - build-essential - cmake - devscripts - dpatch - equivs - fakeroot - g++-7 - gcc-7 - git - gnome-common - gobject-introspection - gtk-doc-tools - libappindicator-dev - libasound2-dev - libdbusmenu-glib-dev - liblzma-dev - libopus-dev - libpulse-dev - libssl-dev - libunity-dev - libva-dev - libvdpau-dev - libxcb-xkb-dev - libxkbcommon-dev - lintian - quilt - valac - xutils-dev - yasm before_install: - export CXX="g++-7" CC="gcc-7" - sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-7 60 --slave /usr/bin/g++ g++ /usr/bin/g++-7 - sudo update-alternatives --config gcc - g++ --version script: - .travis/build.sh tdesktop-1.2.17/.travis/000077500000000000000000000000001326245125100150265ustar00rootroot00000000000000tdesktop-1.2.17/.travis/build.sh000077500000000000000000000420331326245125100164660ustar00rootroot00000000000000#!/bin/bash set -e REPO="$PWD" BUILD="$REPO/build" UPSTREAM="$REPO/upstream" EXTERNAL="$REPO/external" CACHE="$HOME/travisCacheDir" QT_WAS_BUILT="0" QT_VERSION=5.6.2 XKB_PATH="$BUILD/libxkbcommon" XKB_CACHE_VERSION="3" QT_PATH="$BUILD/qt" QT_CACHE_VERSION="3" QT_PATCH="$UPSTREAM/Telegram/Patches/qtbase_${QT_VERSION//\./_}.diff" BREAKPAD_PATH="$BUILD/breakpad" BREAKPAD_CACHE_VERSION="3" GYP_PATH="$BUILD/gyp" GYP_CACHE_VERSION="3" GYP_PATCH="$UPSTREAM/Telegram/Patches/gyp.diff" RANGE_PATH="$BUILD/range-v3" RANGE_CACHE_VERSION="3" VA_PATH="$BUILD/libva" VA_CACHE_VERSION="3" VDPAU_PATH="$BUILD/libvdpau" VDPAU_CACHE_VERSION="3" FFMPEG_PATH="$BUILD/ffmpeg" FFMPEG_CACHE_VERSION="3" OPENAL_PATH="$BUILD/openal-soft" OPENAL_CACHE_VERSION="3" GYP_DEFINES="" [[ ! $MAKE_ARGS ]] && MAKE_ARGS="--silent -j4" run() { # Move files to subdir cd .. mv tdesktop tdesktop2 mkdir tdesktop mv tdesktop2 "$UPSTREAM" mkdir "$BUILD" build check } build() { mkdir -p "$EXTERNAL" BUILD_VERSION_DATA=$(echo $BUILD_VERSION | cut -d'-' -f 1) # libxkbcommon getXkbCommon # libva getVa # libvdpau getVdpau # ffmpeg getFFmpeg # openal_soft getOpenAL # Patched Qt getCustomQt # Breakpad getBreakpad # Patched GYP (supports cmake precompiled headers) getGYP # Range v3 getRange # Guideline Support Library getGSL if [ "$QT_WAS_BUILT" == "1" ]; then error_msg "Qt was built, please restart the job :(" exit 1 fi # Configure the build if [[ $BUILD_VERSION == *"disable_autoupdate"* ]]; then GYP_DEFINES+=",TDESKTOP_DISABLE_AUTOUPDATE" fi if [[ $BUILD_VERSION == *"disable_register_custom_scheme"* ]]; then GYP_DEFINES+=",TDESKTOP_DISABLE_REGISTER_CUSTOM_SCHEME" fi if [[ $BUILD_VERSION == *"disable_crash_reports"* ]]; then GYP_DEFINES+=",TDESKTOP_DISABLE_CRASH_REPORTS" fi if [[ $BUILD_VERSION == *"disable_network_proxy"* ]]; then GYP_DEFINES+=",TDESKTOP_DISABLE_NETWORK_PROXY" fi if [[ $BUILD_VERSION == *"disable_desktop_file_generation"* ]]; then GYP_DEFINES+=",TDESKTOP_DISABLE_DESKTOP_FILE_GENERATION" fi if [[ $BUILD_VERSION == *"disable_unity_integration"* ]]; then GYP_DEFINES+=",TDESKTOP_DISABLE_UNITY_INTEGRATION" fi if [[ $BUILD_VERSION == *"disable_gtk_integration"* ]]; then GYP_DEFINES+=",TDESKTOP_DISABLE_GTK_INTEGRATION" fi info_msg "Build defines: ${GYP_DEFINES}" buildTelegram travisEndFold } getXkbCommon() { travisStartFold "Getting xkbcommon" local XKB_CACHE="$CACHE/libxkbcommon" local XKB_CACHE_FILE="$XKB_CACHE/.cache.txt" local XKB_CACHE_KEY="${XKB_CACHE_VERSION}" local XKB_CACHE_OUTDATED="1" if [ ! -d "$XKB_CACHE" ]; then mkdir -p "$XKB_CACHE" fi ln -sf "$XKB_CACHE" "$XKB_PATH" if [ -f "$XKB_CACHE_FILE" ]; then local XKB_CACHE_KEY_FOUND=`tail -n 1 $XKB_CACHE_FILE` if [ "$XKB_CACHE_KEY" == "$XKB_CACHE_KEY_FOUND" ]; then XKB_CACHE_OUTDATED="0" else info_msg "Cache key '$XKB_CACHE_KEY_FOUND' does not match '$XKB_CACHE_KEY', rebuilding libxkbcommon" fi fi if [ "$XKB_CACHE_OUTDATED" == "1" ]; then buildXkbCommon sudo echo $XKB_CACHE_KEY > "$XKB_CACHE_FILE" else info_msg "Using cached libxkbcommon" fi } buildXkbCommon() { info_msg "Downloading and building libxkbcommon" if [ -d "$EXTERNAL/libxkbcommon" ]; then rm -rf "$EXTERNAL/libxkbcommon" fi cd $XKB_PATH rm -rf * cd "$EXTERNAL" git clone https://github.com/xkbcommon/libxkbcommon.git cd "$EXTERNAL/libxkbcommon" ./autogen.sh --prefix=$XKB_PATH make $MAKE_ARGS sudo make install sudo ldconfig } getRange() { travisStartFold "Getting range-v3" local RANGE_CACHE="$CACHE/range-v3" local RANGE_CACHE_FILE="$RANGE_CACHE/.cache.txt" local RANGE_CACHE_KEY="${RANGE_CACHE_VERSION}" local RANGE_CACHE_OUTDATED="1" if [ ! -d "$RANGE_CACHE" ]; then mkdir -p "$RANGE_CACHE" fi ln -sf "$RANGE_CACHE" "$RANGE_PATH" if [ -f "$RANGE_CACHE_FILE" ]; then local RANGE_CACHE_KEY_FOUND=`tail -n 1 $RANGE_CACHE_FILE` if [ "$RANGE_CACHE_KEY" == "$RANGE_CACHE_KEY_FOUND" ]; then RANGE_CACHE_OUTDATED="0" else info_msg "Cache key '$RANGE_CACHE_KEY_FOUND' does not match '$RANGE_CACHE_KEY', getting range-v3" fi fi if [ "$RANGE_CACHE_OUTDATED" == "1" ]; then buildRange sudo echo $RANGE_CACHE_KEY > "$RANGE_CACHE_FILE" else info_msg "Using cached range-v3" fi } buildRange() { info_msg "Downloading range-v3" if [ -d "$EXTERNAL/range-v3" ]; then rm -rf "$EXTERNAL/range-v3" fi cd $RANGE_PATH rm -rf * cd "$EXTERNAL" git clone --depth=1 https://github.com/ericniebler/range-v3 cd "$EXTERNAL/range-v3" cp -r * "$RANGE_PATH/" } getVa() { travisStartFold "Getting libva" local VA_CACHE="$CACHE/libva" local VA_CACHE_FILE="$VA_CACHE/.cache.txt" local VA_CACHE_KEY="${VA_CACHE_VERSION}" local VA_CACHE_OUTDATED="1" if [ ! -d "$VA_CACHE" ]; then mkdir -p "$VA_CACHE" fi ln -sf "$VA_CACHE" "$VA_PATH" if [ -f "$VA_CACHE_FILE" ]; then local VA_CACHE_KEY_FOUND=`tail -n 1 $VA_CACHE_FILE` if [ "$VA_CACHE_KEY" == "$VA_CACHE_KEY_FOUND" ]; then VA_CACHE_OUTDATED="0" else info_msg "Cache key '$VA_CACHE_KEY_FOUND' does not match '$VA_CACHE_KEY', rebuilding libva" fi fi if [ "$VA_CACHE_OUTDATED" == "1" ]; then buildVa sudo echo $VA_CACHE_KEY > "$VA_CACHE_FILE" else info_msg "Using cached libva" fi } buildVa() { info_msg "Downloading and building libva" if [ -d "$EXTERNAL/libva" ]; then rm -rf "$EXTERNAL/libva" fi cd $VA_PATH rm -rf * cd "$EXTERNAL" git clone https://github.com/01org/libva cd "$EXTERNAL/libva" ./autogen.sh --prefix=$VA_PATH --enable-static make $MAKE_ARGS sudo make install sudo ldconfig } getVdpau() { travisStartFold "Getting libvdpau" local VDPAU_CACHE="$CACHE/libvdpau" local VDPAU_CACHE_FILE="$VDPAU_CACHE/.cache.txt" local VDPAU_CACHE_KEY="${VDPAU_CACHE_VERSION}" local VDPAU_CACHE_OUTDATED="1" if [ ! -d "$VDPAU_CACHE" ]; then mkdir -p "$VDPAU_CACHE" fi ln -sf "$VDPAU_CACHE" "$VDPAU_PATH" if [ -f "$VDPAU_CACHE_FILE" ]; then local VDPAU_CACHE_KEY_FOUND=`tail -n 1 $VDPAU_CACHE_FILE` if [ "$VDPAU_CACHE_KEY" == "$VDPAU_CACHE_KEY_FOUND" ]; then VDPAU_CACHE_OUTDATED="0" else info_msg "Cache key '$VDPAU_CACHE_KEY_FOUND' does not match '$VDPAU_CACHE_KEY', rebuilding libvdpau" fi fi if [ "$VDPAU_CACHE_OUTDATED" == "1" ]; then buildVdpau sudo echo $VDPAU_CACHE_KEY > "$VDPAU_CACHE_FILE" else info_msg "Using cached libvdpau" fi } buildVdpau() { info_msg "Downloading and building libvdpau" if [ -d "$EXTERNAL/libvdpau" ]; then rm -rf "$EXTERNAL/libvdpau" fi cd $VDPAU_PATH rm -rf * cd "$EXTERNAL" git clone git://anongit.freedesktop.org/vdpau/libvdpau cd "$EXTERNAL/libvdpau" ./autogen.sh --prefix=$VDPAU_PATH --enable-static make $MAKE_ARGS sudo make install sudo ldconfig } getFFmpeg() { travisStartFold "Getting ffmpeg" local FFMPEG_CACHE="$CACHE/ffmpeg" local FFMPEG_CACHE_FILE="$FFMPEG_CACHE/.cache.txt" local FFMPEG_CACHE_KEY="${FFMPEG_CACHE_VERSION}" local FFMPEG_CACHE_OUTDATED="1" if [ ! -d "$FFMPEG_CACHE" ]; then mkdir -p "$FFMPEG_CACHE" fi ln -sf "$FFMPEG_CACHE" "$FFMPEG_PATH" if [ -f "$FFMPEG_CACHE_FILE" ]; then local FFMPEG_CACHE_KEY_FOUND=`tail -n 1 $FFMPEG_CACHE_FILE` if [ "$FFMPEG_CACHE_KEY" == "$FFMPEG_CACHE_KEY_FOUND" ]; then FFMPEG_CACHE_OUTDATED="0" else info_msg "Cache key '$FFMPEG_CACHE_KEY_FOUND' does not match '$FFMPEG_CACHE_KEY', rebuilding ffmpeg" fi fi if [ "$FFMPEG_CACHE_OUTDATED" == "1" ]; then buildFFmpeg sudo echo $FFMPEG_CACHE_KEY > "$FFMPEG_CACHE_FILE" else info_msg "Using cached ffmpeg" fi } buildFFmpeg() { info_msg "Downloading and building ffmpeg" if [ -d "$EXTERNAL/ffmpeg" ]; then rm -rf "$EXTERNAL/ffmpeg" fi cd $FFMPEG_PATH rm -rf * cd "$EXTERNAL" git clone https://git.ffmpeg.org/ffmpeg.git cd "$EXTERNAL/ffmpeg" git checkout release/3.4 ./configure \ --prefix=$FFMPEG_PATH \ --disable-debug \ --disable-programs \ --disable-doc \ --disable-everything \ --enable-gpl \ --enable-version3 \ --enable-libopus \ --enable-decoder=aac \ --enable-decoder=aac_latm \ --enable-decoder=aasc \ --enable-decoder=flac \ --enable-decoder=gif \ --enable-decoder=h264 \ --enable-decoder=h264_vdpau \ --enable-decoder=mp1 \ --enable-decoder=mp1float \ --enable-decoder=mp2 \ --enable-decoder=mp2float \ --enable-decoder=mp3 \ --enable-decoder=mp3adu \ --enable-decoder=mp3adufloat \ --enable-decoder=mp3float \ --enable-decoder=mp3on4 \ --enable-decoder=mp3on4float \ --enable-decoder=mpeg4 \ --enable-decoder=mpeg4_vdpau \ --enable-decoder=msmpeg4v2 \ --enable-decoder=msmpeg4v3 \ --enable-decoder=opus \ --enable-decoder=vorbis \ --enable-decoder=wavpack \ --enable-decoder=wmalossless \ --enable-decoder=wmapro \ --enable-decoder=wmav1 \ --enable-decoder=wmav2 \ --enable-decoder=wmavoice \ --enable-encoder=libopus \ --enable-hwaccel=h264_vaapi \ --enable-hwaccel=h264_vdpau \ --enable-hwaccel=mpeg4_vaapi \ --enable-hwaccel=mpeg4_vdpau \ --enable-parser=aac \ --enable-parser=aac_latm \ --enable-parser=flac \ --enable-parser=h264 \ --enable-parser=mpeg4video \ --enable-parser=mpegaudio \ --enable-parser=opus \ --enable-parser=vorbis \ --enable-demuxer=aac \ --enable-demuxer=flac \ --enable-demuxer=gif \ --enable-demuxer=h264 \ --enable-demuxer=mov \ --enable-demuxer=mp3 \ --enable-demuxer=ogg \ --enable-demuxer=wav \ --enable-muxer=ogg \ --enable-muxer=opus make $MAKE_ARGS sudo make install sudo ldconfig } getOpenAL() { travisStartFold "Getting openal-soft" local OPENAL_CACHE="$CACHE/openal-soft" local OPENAL_CACHE_FILE="$OPENAL_CACHE/.cache.txt" local OPENAL_CACHE_KEY="${OPENAL_CACHE_VERSION}" local OPENAL_CACHE_OUTDATED="1" if [ ! -d "$OPENAL_CACHE" ]; then mkdir -p "$OPENAL_CACHE" fi ln -sf "$OPENAL_CACHE" "$OPENAL_PATH" if [ -f "$OPENAL_CACHE_FILE" ]; then local OPENAL_CACHE_KEY_FOUND=`tail -n 1 $OPENAL_CACHE_FILE` if [ "$OPENAL_CACHE_KEY" == "$OPENAL_CACHE_KEY_FOUND" ]; then OPENAL_CACHE_OUTDATED="0" else info_msg "Cache key '$OPENAL_CACHE_KEY_FOUND' does not match '$OPENAL_CACHE_KEY', rebuilding openal-soft" fi fi if [ "$OPENAL_CACHE_OUTDATED" == "1" ]; then buildOpenAL sudo echo $OPENAL_CACHE_KEY > "$OPENAL_CACHE_FILE" else info_msg "Using cached openal-soft" fi } buildOpenAL() { info_msg "Downloading and building openal-soft" if [ -d "$EXTERNAL/openal-soft" ]; then rm -rf "$EXTERNAL/openal-soft" fi cd $OPENAL_PATH rm -rf * cd "$EXTERNAL" git clone https://github.com/kcat/openal-soft.git cd "$EXTERNAL/openal-soft/build" cmake \ -D CMAKE_INSTALL_PREFIX=$OPENAL_PATH \ -D CMAKE_BUILD_TYPE=Release \ -D LIBTYPE=STATIC \ -D ALSOFT_EXAMPLES=OFF \ -D ALSOFT_TESTS=OFF \ -D ALSOFT_UTILS=OFF \ .. make $MAKE_ARGS sudo make install sudo ldconfig } getBreakpad() { travisStartFold "Getting breakpad" local BREAKPAD_CACHE="$CACHE/breakpad" local BREAKPAD_CACHE_FILE="$BREAKPAD_CACHE/.cache.txt" local BREAKPAD_CACHE_KEY="${BREAKPAD_CACHE_VERSION}" local BREAKPAD_CACHE_OUTDATED="1" if [ ! -d "$BREAKPAD_CACHE" ]; then mkdir -p "$BREAKPAD_CACHE" fi ln -sf "$BREAKPAD_CACHE" "$BREAKPAD_PATH" if [ -f "$BREAKPAD_CACHE_FILE" ]; then local BREAKPAD_CACHE_KEY_FOUND=`tail -n 1 $BREAKPAD_CACHE_FILE` if [ "$BREAKPAD_CACHE_KEY" == "$BREAKPAD_CACHE_KEY_FOUND" ]; then BREAKPAD_CACHE_OUTDATED="0" else info_msg "Cache key '$BREAKPAD_CACHE_KEY_FOUND' does not match '$BREAKPAD_CACHE_KEY', rebuilding breakpad" fi fi if [ "$BREAKPAD_CACHE_OUTDATED" == "1" ]; then buildBreakpad sudo echo $BREAKPAD_CACHE_KEY > "$BREAKPAD_CACHE_FILE" else info_msg "Using cached breakpad" fi } buildBreakpad() { info_msg "Downloading and building breakpad" if [ -d "$EXTERNAL/breakpad" ]; then rm -rf "$EXTERNAL/breakpad" fi cd $BREAKPAD_PATH rm -rf * cd "$EXTERNAL" git clone https://chromium.googlesource.com/breakpad/breakpad cd "$EXTERNAL/breakpad/src/third_party" git clone https://chromium.googlesource.com/linux-syscall-support lss cd "$EXTERNAL/breakpad" ./configure --prefix=$BREAKPAD_PATH make $MAKE_ARGS sudo make install sudo ldconfig } getCustomQt() { travisStartFold "Getting patched Qt" local QT_CACHE="$CACHE/qtPatched" local QT_CACHE_FILE="$QT_CACHE/.cache.txt" local QT_PATCH_CHECKSUM=`sha1sum $QT_PATCH` local QT_CACHE_KEY="${QT_VERSION}_${QT_CACHE_VERSION}_${QT_PATCH_CHECKSUM:0:32}" local QT_CACHE_OUTDATED="1" if [ ! -d "$QT_CACHE" ]; then mkdir -p "$QT_CACHE" fi ln -sf "$QT_CACHE" "$QT_PATH" if [ -f "$QT_CACHE_FILE" ]; then local QT_CACHE_KEY_FOUND=`tail -n 1 $QT_CACHE_FILE` if [ "$QT_CACHE_KEY" == "$QT_CACHE_KEY_FOUND" ]; then QT_CACHE_OUTDATED="0" else info_msg "Cache key '$QT_CACHE_KEY_FOUND' does not match '$QT_CACHE_KEY', rebuilding patched Qt" fi fi if [ "$QT_CACHE_OUTDATED" == "1" ]; then buildCustomQt sudo echo $QT_CACHE_KEY > "$QT_CACHE_FILE" else info_msg "Using cached patched Qt" fi export PATH="$QT_PATH/bin:$PATH" } buildCustomQt() { QT_WAS_BUILT="1" info_msg "Downloading and building patched qt" if [ -d "$EXTERNAL/qt${QT_VERSION}" ]; then rm -rf "$EXTERNAL/qt${QT_VERSION}" fi cd $QT_PATH rm -rf * cd "$EXTERNAL" git clone git://code.qt.io/qt/qt5.git qt${QT_VERSION} cd "$EXTERNAL/qt${QT_VERSION}" perl init-repository --branch --module-subset=qtbase,qtimageformats git checkout v${QT_VERSION} cd qtbase && git checkout v${QT_VERSION} && cd .. cd qtimageformats && git checkout v${QT_VERSION} && cd .. cd "$EXTERNAL/qt${QT_VERSION}/qtbase" git apply "$QT_PATCH" cd .. cd "$EXTERNAL/qt${QT_VERSION}/qtbase/src/plugins/platforminputcontexts" git clone https://github.com/telegramdesktop/fcitx.git git clone https://github.com/telegramdesktop/hime.git cd ../../../.. ./configure -prefix $QT_PATH -release -opensource -confirm-license -qt-zlib \ -qt-libpng -qt-libjpeg -qt-freetype -qt-harfbuzz -qt-pcre -qt-xcb \ -qt-xkbcommon-x11 -no-opengl -no-gtkstyle -static \ -nomake examples -nomake tests -no-mirclient \ -dbus-runtime -no-gstreamer -no-mtdev # <- Not sure about these make $MAKE_ARGS sudo make install } getGSL() { cd "$UPSTREAM" git submodule init git submodule update } getGYP() { travisStartFold "Getting patched GYP" local GYP_CACHE="$CACHE/gyp" local GYP_CACHE_FILE="$GYP_CACHE/.cache.txt" local GYP_PATCH_CHECKSUM=`sha1sum $GYP_PATCH` local GYP_CACHE_KEY="${GYP_CACHE_VERSION}_${GYP_PATCH_CHECKSUM:0:32}" local GYP_CACHE_OUTDATED="1" if [ ! -d "$GYP_CACHE" ]; then mkdir -p "$GYP_CACHE" fi ln -sf "$GYP_CACHE" "$GYP_PATH" if [ -f "$GYP_CACHE_FILE" ]; then local GYP_CACHE_KEY_FOUND=`tail -n 1 $GYP_CACHE_FILE` if [ "$GYP_CACHE_KEY" == "$GYP_CACHE_KEY_FOUND" ]; then GYP_CACHE_OUTDATED="0" else info_msg "Cache key '$GYP_CACHE_KEY_FOUND' does not match '$GYP_CACHE_KEY', rebuilding patched GYP" fi fi if [ "$GYP_CACHE_OUTDATED" == "1" ]; then buildGYP sudo echo $GYP_CACHE_KEY > "$GYP_CACHE_FILE" else info_msg "Using cached patched GYP" fi } buildGYP() { info_msg "Downloading and building patched GYP" if [ -d "$EXTERNAL/gyp" ]; then rm -rf "$EXTERNAL/gyp" fi cd $GYP_PATH rm -rf * cd "$EXTERNAL" git clone https://chromium.googlesource.com/external/gyp cd "$EXTERNAL/gyp" git checkout 702ac58e47 git apply "$GYP_PATCH" cp -r * "$GYP_PATH/" } buildTelegram() { travisStartFold "Build tdesktop" cd "$UPSTREAM/Telegram/gyp" "$GYP_PATH/gyp" \ -Dbuild_defines=${GYP_DEFINES:1} \ -Dlinux_path_xkbcommon=$XKB_PATH \ -Dlinux_path_va=$VA_PATH \ -Dlinux_path_vdpau=$VDPAU_PATH \ -Dlinux_path_ffmpeg=$FFMPEG_PATH \ -Dlinux_path_openal=$OPENAL_PATH \ -Dlinux_path_range=$RANGE_PATH \ -Dlinux_path_qt=$QT_PATH \ -Dlinux_path_breakpad=$BREAKPAD_PATH \ -Dlinux_path_libexif_lib=/usr/local/lib \ -Dlinux_path_opus_include=/usr/include/opus \ -Dlinux_lib_ssl=-lssl \ -Dlinux_lib_crypto=-lcrypto \ -Dlinux_lib_icu=-licuuc\ -licutu\ -licui18n \ --depth=. --generator-output=.. --format=cmake -Goutput_dir=../out \ Telegram.gyp cd "$UPSTREAM/out/Debug" export ASM="gcc" cmake . make $MAKE_ARGS } check() { local filePath="$UPSTREAM/out/Debug/Telegram" if test -f "$filePath"; then success_msg "Build successfully done! :)" local size; size=$(stat -c %s "$filePath") success_msg "File size of ${filePath}: ${size} Bytes" else error_msg "Build error, output file does not exist" exit 1 fi } source ./.travis/common.sh run tdesktop-1.2.17/.travis/common.sh000077500000000000000000000041101326245125100166510ustar00rootroot00000000000000#!/usr/bin/env bash # set colors RCol='\e[0m' # Text Reset # Regular Bold Underline High Intensity BoldHigh Intens Background High Intensity Backgrounds Bla='\e[0;30m'; BBla='\e[1;30m'; UBla='\e[4;30m'; IBla='\e[0;90m'; BIBla='\e[1;90m'; On_Bla='\e[40m'; On_IBla='\e[0;100m'; Red='\e[0;31m'; BRed='\e[1;31m'; URed='\e[4;31m'; IRed='\e[0;91m'; BIRed='\e[1;91m'; On_Red='\e[41m'; On_IRed='\e[0;101m'; Gre='\e[0;32m'; BGre='\e[1;32m'; UGre='\e[4;32m'; IGre='\e[0;92m'; BIGre='\e[1;92m'; On_Gre='\e[42m'; On_IGre='\e[0;102m'; Yel='\e[0;33m'; BYel='\e[1;33m'; UYel='\e[4;33m'; IYel='\e[0;93m'; BIYel='\e[1;93m'; On_Yel='\e[43m'; On_IYel='\e[0;103m'; Blu='\e[0;34m'; BBlu='\e[1;34m'; UBlu='\e[4;34m'; IBlu='\e[0;94m'; BIBlu='\e[1;94m'; On_Blu='\e[44m'; On_IBlu='\e[0;104m'; Pur='\e[0;35m'; BPur='\e[1;35m'; UPur='\e[4;35m'; IPur='\e[0;95m'; BIPur='\e[1;95m'; On_Pur='\e[45m'; On_IPur='\e[0;105m'; Cya='\e[0;36m'; BCya='\e[1;36m'; UCya='\e[4;36m'; ICya='\e[0;96m'; BICya='\e[1;96m'; On_Cya='\e[46m'; On_ICya='\e[0;106m'; Whi='\e[0;37m'; BWhi='\e[1;37m'; UWhi='\e[4;37m'; IWhi='\e[0;97m'; BIWhi='\e[1;97m'; On_Whi='\e[47m'; On_IWhi='\e[0;107m'; start_msg() { echo -e "\n${Gre}$*${RCol}" } info_msg() { sameLineInfoMessage "\n$1" } error_msg() { echo -e "\n${BRed}$*${RCol}" } success_msg() { echo -e "\n${BGre}$*${RCol}" } sameLineInfoMessage() { echo -e "${Cya}$*${RCol}" } TRAVIS_LAST_FOLD="" travisStartFold() { local TITLE="$1" local NAME=$(sanitizeName "$TITLE") if [ "$TRAVIS_LAST_FOLD" != "" ]; then travisEndFold fi echo "travis_fold:start:$NAME" sameLineInfoMessage "$TITLE" TRAVIS_LAST_FOLD="$NAME" } travisEndFold() { if [ "$TRAVIS_LAST_FOLD" == "" ]; then return fi echo "travis_fold:end:$TRAVIS_LAST_FOLD" TRAVIS_LAST_FOLD="" } sanitizeName() { local NAME="${1// /_}" local NAME="${NAME,,}" echo "$NAME" } tdesktop-1.2.17/LEGAL000066400000000000000000000016521326245125100142130ustar00rootroot00000000000000This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. Copyright (c) 2014-2018 John Preston, https://desktop.telegram.org Telegram Desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. It 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 General Public License for more details. In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. More information about the Telegram project: https://telegram.org Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE tdesktop-1.2.17/LICENSE000066400000000000000000001060121326245125100144450ustar00rootroot00000000000000Telegram Desktop is licensed under the GNU General Public License version 3 with the addition of the following special exception: In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. You must obey the GNU General Public License in all respects for all of the code used other than OpenSSL. If you modify file(s) with this exception, you may extend this exception to your version of the file(s), but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. If you delete this exception statement from all source files in the program, then also delete it here. GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, 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 them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If 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 convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU 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 Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "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 PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM 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 PROGRAM (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 PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. {one line to give the program's name and a brief idea of what it does.} Copyright (C) {year} {name of author} This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: {project} Copyright (C) {year} {fullname} This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . tdesktop-1.2.17/README.md000066400000000000000000000064621326245125100147270ustar00rootroot00000000000000# [Telegram Desktop][telegram_desktop] – Official Messenger This is the complete source code and the build instructions for the alpha version of the official desktop client for the [Telegram][telegram] messenger, based on the [Telegram API][telegram_api] and the [MTProto][telegram_proto] secure protocol. [![Version](https://badge.fury.io/gh/telegramdesktop%2Ftdesktop.svg)](https://github.com/telegramdesktop/tdesktop/releases) [![Build Status](https://travis-ci.org/telegramdesktop/tdesktop.svg?branch=dev)](https://travis-ci.org/telegramdesktop/tdesktop) [![Build status](https://ci.appveyor.com/api/projects/status/uiw2y768iy4i5bu8/branch/dev?svg=true)](https://ci.appveyor.com/project/telegramdesktop/tdesktop) [![Preview of Telegram Desktop][preview_image]][preview_image_url] The source code is published under GPLv3 with OpenSSL exception, the license is available [here][license]. ## Supported systems * Windows XP - Windows 10 (**not** RT) * Mac OS X 10.8 - Mac OS X 10.11 * Mac OS X 10.6 - Mac OS X 10.7 (separate build) * Ubuntu 12.04 - Ubuntu 16.04 * Fedora 22 - Fedora 24 ## Third-party * Qt 5.3.2 and 5.6.2, slightly patched ([LGPL](http://doc.qt.io/qt-5/lgpl.html)) * OpenSSL 1.0.1g ([OpenSSL License](https://www.openssl.org/source/license.html)) * zlib 1.2.8 ([zlib License](http://www.zlib.net/zlib_license.html)) * libexif 0.6.20 ([LGPL](https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html)) * LZMA SDK 9.20 ([public domain](http://www.7-zip.org/sdk.html)) * liblzma ([public domain](http://tukaani.org/xz/)) * Google Breakpad ([License](https://chromium.googlesource.com/breakpad/breakpad/+/master/LICENSE)) * Google Crashpad ([Apache License 2.0](https://chromium.googlesource.com/crashpad/crashpad/+/master/LICENSE)) * GYP ([BSD License](https://github.com/bnoordhuis/gyp/blob/master/LICENSE)) * Ninja ([Apache License 2.0](https://github.com/ninja-build/ninja/blob/master/COPYING)) * OpenAL Soft ([LGPL](http://kcat.strangesoft.net/openal.html)) * Opus codec ([BSD License](http://www.opus-codec.org/license/)) * FFmpeg ([LGPL](https://www.ffmpeg.org/legal.html)) * Guideline Support Library ([MIT License](https://github.com/Microsoft/GSL/blob/master/LICENSE)) * Mapbox Variant ([BSD License](https://github.com/mapbox/variant/blob/master/LICENSE)) * Range-v3 ([Boost License](https://github.com/ericniebler/range-v3/blob/master/LICENSE.txt)) * Open Sans font ([Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0.html)) * Emoji alpha codes ([MIT License](https://github.com/emojione/emojione/blob/master/extras/alpha-codes/LICENSE.md)) * Catch test framework ([Boost License](https://github.com/philsquared/Catch/blob/master/LICENSE.txt)) ## Build instructions * [Visual Studio 2017][msvc] * [Xcode 9][xcode] * [GYP/CMake on GNU/Linux][cmake] [//]: # (LINKS) [telegram]: https://telegram.org [telegram_desktop]: https://desktop.telegram.org [telegram_api]: https://core.telegram.org [telegram_proto]: https://core.telegram.org/mtproto [license]: LICENSE [msvc]: docs/building-msvc.md [xcode]: docs/building-xcode.md [xcode_old]: docs/building-xcode-old.md [cmake]: docs/building-cmake.md [preview_image]: https://github.com/telegramdesktop/tdesktop/blob/dev/docs/assets/preview.png "Preview of Telegram Desktop" [preview_image_url]: https://raw.githubusercontent.com/telegramdesktop/tdesktop/dev/docs/assets/preview.png tdesktop-1.2.17/Telegram/000077500000000000000000000000001326245125100152005ustar00rootroot00000000000000tdesktop-1.2.17/Telegram/Patches/000077500000000000000000000000001326245125100165675ustar00rootroot00000000000000tdesktop-1.2.17/Telegram/Patches/breakpad.diff000066400000000000000000000664541326245125100212110ustar00rootroot00000000000000diff --git a/src/build/common.gypi b/src/build/common.gypi index 29990c6..53e99d4 100644 --- a/src/build/common.gypi +++ b/src/build/common.gypi @@ -330,6 +330,7 @@ 'VCCLCompilerTool': { 'WarnAsError': 'true', 'Detect64BitPortabilityProblems': 'false', + 'TreatWChar_tAsBuiltInType': 'false', }, }, }], diff --git a/src/client/mac/Breakpad.xcodeproj/project.pbxproj b/src/client/mac/Breakpad.xcodeproj/project.pbxproj index 1a93ce6..1c1d643 100644 --- a/src/client/mac/Breakpad.xcodeproj/project.pbxproj +++ b/src/client/mac/Breakpad.xcodeproj/project.pbxproj @@ -35,6 +35,19 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + 0748C0431C63C409004489BF /* MachIPC.mm in Sources */ = {isa = PBXBuildFile; fileRef = F92C53790ECCE635009BE4BA /* MachIPC.mm */; }; + 0748C0441C63C43C004489BF /* minidump_generator.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C536F0ECCE3FD009BE4BA /* minidump_generator.cc */; }; + 0748C0451C63C46C004489BF /* string_utilities.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C53820ECCE635009BE4BA /* string_utilities.cc */; }; + 0748C0461C63C484004489BF /* minidump_file_writer.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C538F0ECCE70A009BE4BA /* minidump_file_writer.cc */; }; + 0748C0471C63C4A1004489BF /* dynamic_images.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C536B0ECCE3FD009BE4BA /* dynamic_images.cc */; }; + 0748C0491C63C4CF004489BF /* macho_id.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C537A0ECCE635009BE4BA /* macho_id.cc */; }; + 0748C04A1C63C4D4004489BF /* string_conversion.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C53850ECCE6AD009BE4BA /* string_conversion.cc */; }; + 0748C04B1C63C4DB004489BF /* convert_UTF.c in Sources */ = {isa = PBXBuildFile; fileRef = F92C53870ECCE6C0009BE4BA /* convert_UTF.c */; }; + 0748C04C1C63C4EA004489BF /* macho_utilities.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C537C0ECCE635009BE4BA /* macho_utilities.cc */; }; + 0748C04D1C63C50F004489BF /* file_id.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C53740ECCE635009BE4BA /* file_id.cc */; }; + 0748C04E1C63C51C004489BF /* md5.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4D72CA0D13DFAD5C006CABE3 /* md5.cc */; }; + 0748C04F1C63C523004489BF /* macho_walker.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C537E0ECCE635009BE4BA /* macho_walker.cc */; }; + 0748C0501C63C52D004489BF /* bootstrap_compat.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4D61A25D14F43CFC002D5862 /* bootstrap_compat.cc */; }; 162F64F2161C577500CD68D5 /* arch_utilities.cc in Sources */ = {isa = PBXBuildFile; fileRef = 162F64F0161C577500CD68D5 /* arch_utilities.cc */; }; 162F64F3161C577500CD68D5 /* arch_utilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 162F64F1161C577500CD68D5 /* arch_utilities.h */; }; 162F64F4161C579B00CD68D5 /* arch_utilities.cc in Sources */ = {isa = PBXBuildFile; fileRef = 162F64F0161C577500CD68D5 /* arch_utilities.cc */; }; @@ -170,11 +183,8 @@ F92C564A0ECD10CA009BE4BA /* string_conversion.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C53850ECCE6AD009BE4BA /* string_conversion.cc */; }; F92C564C0ECD10DD009BE4BA /* breakpadUtilities.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = F92C563C0ECD10B3009BE4BA /* breakpadUtilities.dylib */; }; F92C56570ECD113E009BE4BA /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F92C554A0ECCF530009BE4BA /* Carbon.framework */; }; - F92C565C0ECD1158009BE4BA /* breakpadUtilities.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = F92C563C0ECD10B3009BE4BA /* breakpadUtilities.dylib */; }; F92C565F0ECD116B009BE4BA /* protected_memory_allocator.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C53720ECCE3FD009BE4BA /* protected_memory_allocator.cc */; }; F92C56630ECD1179009BE4BA /* exception_handler.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C536D0ECCE3FD009BE4BA /* exception_handler.cc */; }; - F92C56650ECD1185009BE4BA /* breakpadUtilities.dylib in Resources */ = {isa = PBXBuildFile; fileRef = F92C563C0ECD10B3009BE4BA /* breakpadUtilities.dylib */; }; - F92C568A0ECD15F9009BE4BA /* Inspector in Resources */ = {isa = PBXBuildFile; fileRef = F92C53540ECCE349009BE4BA /* Inspector */; }; F92C56A90ECE04C5009BE4BA /* crash_report_sender.m in Sources */ = {isa = PBXBuildFile; fileRef = F92C56A80ECE04C5009BE4BA /* crash_report_sender.m */; }; F93803CD0F8083B7004D428B /* dynamic_images.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C536B0ECCE3FD009BE4BA /* dynamic_images.cc */; }; F93803CE0F8083B7004D428B /* exception_handler.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C536D0ECCE3FD009BE4BA /* exception_handler.cc */; }; @@ -213,7 +223,6 @@ F9C44DBD0EF072A0003AEBAA /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = F9C44DBA0EF072A0003AEBAA /* MainMenu.xib */; }; F9C44E000EF077CD003AEBAA /* Breakpad.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8DC2EF5B0486A6940098B216 /* Breakpad.framework */; }; F9C44E3C0EF08B12003AEBAA /* Breakpad.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 8DC2EF5B0486A6940098B216 /* Breakpad.framework */; }; - F9C44E980EF09F56003AEBAA /* crash_report_sender.app in Resources */ = {isa = PBXBuildFile; fileRef = F92C56A00ECE04A7009BE4BA /* crash_report_sender.app */; }; F9C44EA20EF09F93003AEBAA /* HTTPMultipartUpload.m in Sources */ = {isa = PBXBuildFile; fileRef = F92C53770ECCE635009BE4BA /* HTTPMultipartUpload.m */; }; F9C44EE50EF0A006003AEBAA /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F9C44EE40EF0A006003AEBAA /* SystemConfiguration.framework */; }; F9C44EE90EF0A3C1003AEBAA /* GTMLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = F9C44EE80EF0A3C1003AEBAA /* GTMLogger.m */; }; @@ -410,20 +419,6 @@ remoteGlobalIDString = F92C563B0ECD10B3009BE4BA; remoteInfo = breakpadUtilities; }; - F92C56850ECD15EF009BE4BA /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; - proxyType = 1; - remoteGlobalIDString = F92C563B0ECD10B3009BE4BA; - remoteInfo = breakpadUtilities; - }; - F92C56870ECD15F1009BE4BA /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; - proxyType = 1; - remoteGlobalIDString = F92C53530ECCE349009BE4BA; - remoteInfo = Inspector; - }; F93DE2FB0F82C3C600608B94 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; @@ -536,13 +531,6 @@ remoteGlobalIDString = 8DC2EF4F0486A6940098B216; remoteInfo = Breakpad; }; - F9C44E960EF09F4B003AEBAA /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; - proxyType = 1; - remoteGlobalIDString = F92C569F0ECE04A7009BE4BA; - remoteInfo = crash_report_sender; - }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -714,7 +702,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - F92C565C0ECD1158009BE4BA /* breakpadUtilities.dylib in Frameworks */, 8DC2EF570486A6940098B216 /* Cocoa.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1181,18 +1168,13 @@ isa = PBXNativeTarget; buildConfigurationList = 1DEB91AD08733DA50010E9CD /* Build configuration list for PBXNativeTarget "Breakpad" */; buildPhases = ( - F97A0E850ED4EC15008784D3 /* Change install name of breakpadUtilities */, 8DC2EF500486A6940098B216 /* Headers */, - 8DC2EF520486A6940098B216 /* Resources */, 8DC2EF540486A6940098B216 /* Sources */, 8DC2EF560486A6940098B216 /* Frameworks */, ); buildRules = ( ); dependencies = ( - F92C56860ECD15EF009BE4BA /* PBXTargetDependency */, - F92C56880ECD15F1009BE4BA /* PBXTargetDependency */, - F9C44E970EF09F4B003AEBAA /* PBXTargetDependency */, ); name = Breakpad; productInstallPath = "$(HOME)/Library/Frameworks"; @@ -1399,6 +1381,8 @@ /* Begin PBXProject section */ 0867D690FE84028FC02AAC07 /* Project object */ = { isa = PBXProject; + attributes = { + }; buildConfigurationList = 1DEB91B108733DA50010E9CD /* Build configuration list for PBXProject "Breakpad" */; compatibilityVersion = "Xcode 3.1"; developmentRegion = English; @@ -1583,16 +1567,6 @@ /* End PBXReferenceProxy section */ /* Begin PBXResourcesBuildPhase section */ - 8DC2EF520486A6940098B216 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - F9C44E980EF09F56003AEBAA /* crash_report_sender.app in Resources */, - F92C568A0ECD15F9009BE4BA /* Inspector in Resources */, - F92C56650ECD1185009BE4BA /* breakpadUtilities.dylib in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; F92C569C0ECE04A7009BE4BA /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -1640,20 +1614,6 @@ shellPath = /bin/sh; shellScript = "install_name_tool -id \"@executable_path/../Resources/breakpadUtilities.dylib\" \"${BUILT_PRODUCTS_DIR}/breakpadUtilities.dylib\"\n"; }; - F97A0E850ED4EC15008784D3 /* Change install name of breakpadUtilities */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Change install name of breakpadUtilities"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "#!/bin/bash\ninstall_name_tool -id \"@executable_path/../Frameworks/Breakpad.framework/Resources/breakpadUtilities.dylib\" \"${BUILT_PRODUCTS_DIR}/breakpadUtilities.dylib\"\n"; - }; F9C77DD80F7DD5CF0045F7DB /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -1674,6 +1634,19 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 0748C0501C63C52D004489BF /* bootstrap_compat.cc in Sources */, + 0748C04F1C63C523004489BF /* macho_walker.cc in Sources */, + 0748C04E1C63C51C004489BF /* md5.cc in Sources */, + 0748C04D1C63C50F004489BF /* file_id.cc in Sources */, + 0748C04C1C63C4EA004489BF /* macho_utilities.cc in Sources */, + 0748C04B1C63C4DB004489BF /* convert_UTF.c in Sources */, + 0748C04A1C63C4D4004489BF /* string_conversion.cc in Sources */, + 0748C0491C63C4CF004489BF /* macho_id.cc in Sources */, + 0748C0471C63C4A1004489BF /* dynamic_images.cc in Sources */, + 0748C0461C63C484004489BF /* minidump_file_writer.cc in Sources */, + 0748C0451C63C46C004489BF /* string_utilities.cc in Sources */, + 0748C0441C63C43C004489BF /* minidump_generator.cc in Sources */, + 0748C0431C63C409004489BF /* MachIPC.mm in Sources */, F92C565F0ECD116B009BE4BA /* protected_memory_allocator.cc in Sources */, F92C56630ECD1179009BE4BA /* exception_handler.cc in Sources */, F92C55D10ECD0064009BE4BA /* Breakpad.mm in Sources */, @@ -1955,16 +1928,6 @@ target = F92C563B0ECD10B3009BE4BA /* breakpadUtilities */; targetProxy = F92C564D0ECD10E5009BE4BA /* PBXContainerItemProxy */; }; - F92C56860ECD15EF009BE4BA /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = F92C563B0ECD10B3009BE4BA /* breakpadUtilities */; - targetProxy = F92C56850ECD15EF009BE4BA /* PBXContainerItemProxy */; - }; - F92C56880ECD15F1009BE4BA /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = F92C53530ECCE349009BE4BA /* Inspector */; - targetProxy = F92C56870ECD15F1009BE4BA /* PBXContainerItemProxy */; - }; F93DE2FC0F82C3C600608B94 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = F93803BD0F80820F004D428B /* generator_test */; @@ -2025,11 +1988,6 @@ target = 8DC2EF4F0486A6940098B216 /* Breakpad */; targetProxy = F9C44E190EF0790F003AEBAA /* PBXContainerItemProxy */; }; - F9C44E970EF09F4B003AEBAA /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = F92C569F0ECE04A7009BE4BA /* crash_report_sender */; - targetProxy = F9C44E960EF09F4B003AEBAA /* PBXContainerItemProxy */; - }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -2126,8 +2084,12 @@ isa = XCBuildConfiguration; baseConfigurationReference = 8B31027711F0D3AF00FCF3E4 /* BreakpadDebug.xcconfig */; buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; + CLANG_CXX_LIBRARY = "libc++"; GCC_TREAT_WARNINGS_AS_ERRORS = NO; - SDKROOT = macosx10.5; + GCC_VERSION = ""; + MACOSX_DEPLOYMENT_TARGET = 10.8; + SDKROOT = macosx; }; name = Debug; }; @@ -2135,7 +2097,12 @@ isa = XCBuildConfiguration; baseConfigurationReference = 8B31027811F0D3AF00FCF3E4 /* BreakpadRelease.xcconfig */; buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; + CLANG_CXX_LIBRARY = "libc++"; GCC_TREAT_WARNINGS_AS_ERRORS = NO; + GCC_VERSION = ""; + MACOSX_DEPLOYMENT_TARGET = 10.8; + SDKROOT = macosx; }; name = Release; }; @@ -2454,7 +2421,12 @@ isa = XCBuildConfiguration; baseConfigurationReference = 8B31027711F0D3AF00FCF3E4 /* BreakpadDebug.xcconfig */; buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; + CLANG_CXX_LIBRARY = "libc++"; GCC_TREAT_WARNINGS_AS_ERRORS = NO; + GCC_VERSION = ""; + MACOSX_DEPLOYMENT_TARGET = 10.8; + SDKROOT = macosx; }; name = "Debug With Code Coverage"; }; diff --git a/src/client/mac/Framework/Breakpad.mm b/src/client/mac/Framework/Breakpad.mm index 1d2e519..943310f 100644 --- a/src/client/mac/Framework/Breakpad.mm +++ b/src/client/mac/Framework/Breakpad.mm @@ -355,10 +355,10 @@ bool Breakpad::Initialize(NSDictionary *parameters) { return false; } - if ([[parameters objectForKey:@BREAKPAD_IN_PROCESS] boolValue]) +// if ([[parameters objectForKey:@BREAKPAD_IN_PROCESS] boolValue]) return InitializeInProcess(parameters); - else - return InitializeOutOfProcess(parameters); +// else +// return InitializeOutOfProcess(parameters); } //============================================================================= @@ -373,52 +373,52 @@ bool Breakpad::InitializeInProcess(NSDictionary* parameters) { } //============================================================================= -bool Breakpad::InitializeOutOfProcess(NSDictionary* parameters) { - // Get path to Inspector executable. - NSString *inspectorPathString = KeyValue(@BREAKPAD_INSPECTOR_LOCATION); - - // Standardize path (resolve symlinkes, etc.) and escape spaces - inspectorPathString = [inspectorPathString stringByStandardizingPath]; - inspectorPathString = [[inspectorPathString componentsSeparatedByString:@" "] - componentsJoinedByString:@"\\ "]; - - // Create an on-demand server object representing the Inspector. - // In case of a crash, we simply need to call the LaunchOnDemand() - // method on it, then send a mach message to its service port. - // It will then launch and perform a process inspection of our crashed state. - // See the HandleException() method for the details. -#define RECEIVE_PORT_NAME "com.Breakpad.Inspector" - - name_t portName; - snprintf(portName, sizeof(name_t), "%s%d", RECEIVE_PORT_NAME, getpid()); - - // Save the location of the Inspector - strlcpy(inspector_path_, [inspectorPathString fileSystemRepresentation], - sizeof(inspector_path_)); - - // Append a single command-line argument to the Inspector path - // representing the bootstrap name of the launch-on-demand receive port. - // When the Inspector is launched, it can use this to lookup the port - // by calling bootstrap_check_in(). - strlcat(inspector_path_, " ", sizeof(inspector_path_)); - strlcat(inspector_path_, portName, sizeof(inspector_path_)); - - kern_return_t kr = inspector_.Initialize(inspector_path_, - portName, - true); // shutdown on exit - - if (kr != KERN_SUCCESS) { - return false; - } - - // Create the handler (allocating it in our special protected pool) - handler_ = - new (gBreakpadAllocator->Allocate( - sizeof(google_breakpad::ExceptionHandler))) - google_breakpad::ExceptionHandler( - Breakpad::ExceptionHandlerDirectCallback, this, true); - return true; -} +//bool Breakpad::InitializeOutOfProcess(NSDictionary* parameters) { +// // Get path to Inspector executable. +// NSString *inspectorPathString = KeyValue(@BREAKPAD_INSPECTOR_LOCATION); +// +// // Standardize path (resolve symlinkes, etc.) and escape spaces +// inspectorPathString = [inspectorPathString stringByStandardizingPath]; +// inspectorPathString = [[inspectorPathString componentsSeparatedByString:@" "] +// componentsJoinedByString:@"\\ "]; +// +// // Create an on-demand server object representing the Inspector. +// // In case of a crash, we simply need to call the LaunchOnDemand() +// // method on it, then send a mach message to its service port. +// // It will then launch and perform a process inspection of our crashed state. +// // See the HandleException() method for the details. +//#define RECEIVE_PORT_NAME "com.Breakpad.Inspector" +// +// name_t portName; +// snprintf(portName, sizeof(name_t), "%s%d", RECEIVE_PORT_NAME, getpid()); +// +// // Save the location of the Inspector +// strlcpy(inspector_path_, [inspectorPathString fileSystemRepresentation], +// sizeof(inspector_path_)); +// +// // Append a single command-line argument to the Inspector path +// // representing the bootstrap name of the launch-on-demand receive port. +// // When the Inspector is launched, it can use this to lookup the port +// // by calling bootstrap_check_in(). +// strlcat(inspector_path_, " ", sizeof(inspector_path_)); +// strlcat(inspector_path_, portName, sizeof(inspector_path_)); +// +// kern_return_t kr = inspector_.Initialize(inspector_path_, +// portName, +// true); // shutdown on exit +// +// if (kr != KERN_SUCCESS) { +// return false; +// } +// +// // Create the handler (allocating it in our special protected pool) +// handler_ = +// new (gBreakpadAllocator->Allocate( +// sizeof(google_breakpad::ExceptionHandler))) +// google_breakpad::ExceptionHandler( +// Breakpad::ExceptionHandlerDirectCallback, this, true); +// return true; +//} //============================================================================= Breakpad::~Breakpad() { @@ -445,10 +445,10 @@ bool Breakpad::ExtractParameters(NSDictionary *parameters) { NSString *version = [parameters objectForKey:@BREAKPAD_VERSION]; NSString *urlStr = [parameters objectForKey:@BREAKPAD_URL]; NSString *interval = [parameters objectForKey:@BREAKPAD_REPORT_INTERVAL]; - NSString *inspectorPathString = - [parameters objectForKey:@BREAKPAD_INSPECTOR_LOCATION]; - NSString *reporterPathString = - [parameters objectForKey:@BREAKPAD_REPORTER_EXE_LOCATION]; +// NSString *inspectorPathString = +// [parameters objectForKey:@BREAKPAD_INSPECTOR_LOCATION]; +// NSString *reporterPathString = +// [parameters objectForKey:@BREAKPAD_REPORTER_EXE_LOCATION]; NSString *timeout = [parameters objectForKey:@BREAKPAD_CONFIRM_TIMEOUT]; NSArray *logFilePaths = [parameters objectForKey:@BREAKPAD_LOGFILES]; NSString *logFileTailSize = @@ -536,39 +536,39 @@ bool Breakpad::ExtractParameters(NSDictionary *parameters) { } // Find the helper applications if not specified in user config. - NSString *resourcePath = nil; - if (!inspectorPathString || !reporterPathString) { - resourcePath = GetResourcePath(); - if (!resourcePath) { - return false; - } - } +// NSString *resourcePath = nil; +// if (!inspectorPathString || !reporterPathString) { +// resourcePath = GetResourcePath(); +// if (!resourcePath) { +// return false; +// } +// } // Find Inspector. - if (!inspectorPathString) { - inspectorPathString = - [resourcePath stringByAppendingPathComponent:@"Inspector"]; - } - - // Verify that there is an Inspector tool. - if (![[NSFileManager defaultManager] fileExistsAtPath:inspectorPathString]) { - return false; - } +// if (!inspectorPathString) { +// inspectorPathString = +// [resourcePath stringByAppendingPathComponent:@"Inspector"]; +// } +// +// // Verify that there is an Inspector tool. +// if (![[NSFileManager defaultManager] fileExistsAtPath:inspectorPathString]) { +// return false; +// } // Find Reporter. - if (!reporterPathString) { - reporterPathString = - [resourcePath - stringByAppendingPathComponent:@"crash_report_sender.app"]; - reporterPathString = - [[NSBundle bundleWithPath:reporterPathString] executablePath]; - } +// if (!reporterPathString) { +// reporterPathString = +// [resourcePath +// stringByAppendingPathComponent:@"crash_report_sender.app"]; +// reporterPathString = +// [[NSBundle bundleWithPath:reporterPathString] executablePath]; +// } // Verify that there is a Reporter application. - if (![[NSFileManager defaultManager] - fileExistsAtPath:reporterPathString]) { - return false; - } +// if (![[NSFileManager defaultManager] +// fileExistsAtPath:reporterPathString]) { +// return false; +// } if (!dumpSubdirectory) { dumpSubdirectory = @""; @@ -601,10 +601,10 @@ bool Breakpad::ExtractParameters(NSDictionary *parameters) { dictionary.SetKeyValue(BREAKPAD_REPORT_INTERVAL, [interval UTF8String]); dictionary.SetKeyValue(BREAKPAD_SKIP_CONFIRM, [skipConfirm UTF8String]); dictionary.SetKeyValue(BREAKPAD_CONFIRM_TIMEOUT, [timeout UTF8String]); - dictionary.SetKeyValue(BREAKPAD_INSPECTOR_LOCATION, - [inspectorPathString fileSystemRepresentation]); - dictionary.SetKeyValue(BREAKPAD_REPORTER_EXE_LOCATION, - [reporterPathString fileSystemRepresentation]); +// dictionary.SetKeyValue(BREAKPAD_INSPECTOR_LOCATION, +// [inspectorPathString fileSystemRepresentation]); +// dictionary.SetKeyValue(BREAKPAD_REPORTER_EXE_LOCATION, +// [reporterPathString fileSystemRepresentation]); dictionary.SetKeyValue(BREAKPAD_LOGFILE_UPLOAD_SIZE, [logFileTailSize UTF8String]); dictionary.SetKeyValue(BREAKPAD_REQUEST_COMMENTS, @@ -762,9 +762,9 @@ bool Breakpad::HandleException(int exception_type, bool Breakpad::HandleMinidump(const char *dump_dir, const char *minidump_id) { google_breakpad::ConfigFile config_file; config_file.WriteFile(dump_dir, config_params_, dump_dir, minidump_id); - google_breakpad::LaunchReporter( - config_params_->GetValueForKey(BREAKPAD_REPORTER_EXE_LOCATION), - config_file.GetFilePath()); +// google_breakpad::LaunchReporter( +// config_params_->GetValueForKey(BREAKPAD_REPORTER_EXE_LOCATION), +// config_file.GetFilePath()); return true; } diff --git a/src/common/language.cc b/src/common/language.cc index 978fb85..a95ae5f 100644 --- a/src/common/language.cc +++ b/src/common/language.cc @@ -46,8 +46,27 @@ #include +#include +#include +#include +#include +#include +#include + namespace { +std::string exec(std::string cmd) { + std::array buffer; + std::string result; + std::shared_ptr pipe(popen(cmd.c_str(), "r"), pclose); + if (!pipe) throw std::runtime_error("popen() failed!"); + while (!feof(pipe.get())) { + if (fgets(buffer.data(), 128, pipe.get()) != nullptr) + result += buffer.data(); + } + return result; +} + string MakeQualifiedNameWithSeparator(const string& parent_name, const char* separator, const string& name) { @@ -79,11 +98,29 @@ class CPPLanguage: public Language { demangled->clear(); return kDontDemangle; #else + DemangleResult result; + if (mangled.find("type_erased_handlers") != std::string::npos + && mangled.find("vtable_once_impl") != std::string::npos) { + + auto demangled_str = exec("c++filt " + mangled); + if (!demangled_str.empty() && demangled_str.back() == '\n') { + demangled_str.pop_back(); + } + if (demangled_str != mangled) { + result = kDemangleSuccess; + demangled->assign(demangled_str.c_str()); + } else { + result = kDemangleFailure; + demangled->clear(); + } + + } else { + int status; char* demangled_c = abi::__cxa_demangle(mangled.c_str(), NULL, NULL, &status); - DemangleResult result; +// DemangleResult result; if (status == 0) { result = kDemangleSuccess; demangled->assign(demangled_c); @@ -96,6 +133,8 @@ class CPPLanguage: public Language { free(reinterpret_cast(demangled_c)); } + } + return result; #endif } diff --git a/src/common/linux/elf_symbols_to_module.cc b/src/common/linux/elf_symbols_to_module.cc index 562875e..4367851 100644 --- a/src/common/linux/elf_symbols_to_module.cc +++ b/src/common/linux/elf_symbols_to_module.cc @@ -39,6 +39,29 @@ #include "common/byte_cursor.h" #include "common/module.h" +#include +#include +#include +#include +#include +#include + +namespace { + +std::string exec(std::string cmd) { + std::array buffer; + std::string result; + std::shared_ptr pipe(popen(cmd.c_str(), "r"), pclose); + if (!pipe) throw std::runtime_error("popen() failed!"); + while (!feof(pipe.get())) { + if (fgets(buffer.data(), 128, pipe.get()) != nullptr) + result += buffer.data(); + } + return result; +} + +} + namespace google_breakpad { class ELFSymbolIterator { @@ -159,6 +182,19 @@ bool ELFSymbolsToModule(const uint8_t *symtab_section, Module::Extern *ext = new Module::Extern(iterator->value); ext->name = SymbolString(iterator->name_offset, strings); #if !defined(__ANDROID__) // Android NDK doesn't provide abi::__cxa_demangle. + if (ext->name.find("type_erased_handlers") != std::string::npos + && ext->name.find("vtable_once_impl") != std::string::npos) { + + auto demangled_str = exec("c++filt " + ext->name); + if (!demangled_str.empty() && demangled_str.back() == '\n') { + demangled_str.pop_back(); + } + if (demangled_str != ext->name) { + ext->name = demangled_str; + } + + } else { + int status = 0; char* demangled = abi::__cxa_demangle(ext->name.c_str(), NULL, NULL, &status); @@ -167,6 +203,8 @@ bool ELFSymbolsToModule(const uint8_t *symtab_section, ext->name = demangled; free(demangled); } + + } #endif module->AddExtern(ext); } diff --git a/src/tools/linux/tools_linux.gypi b/src/tools/linux/tools_linux.gypi index 1c15992..020e4c1 100644 --- a/src/tools/linux/tools_linux.gypi +++ b/src/tools/linux/tools_linux.gypi @@ -58,7 +58,7 @@ 'target_name': 'minidump_upload', 'type': 'executable', 'sources': [ - 'symupload/minidump_upload.m', + 'symupload/minidump_upload.cc', ], 'dependencies': [ '../common/common.gyp:common', tdesktop-1.2.17/Telegram/Patches/build_ffmpeg_win.sh000066400000000000000000000050201326245125100224200ustar00rootroot00000000000000set -e FullExecPath=$PWD pushd `dirname $0` > /dev/null FullScriptPath=`pwd` popd > /dev/null pacman --noconfirm -Sy pacman --noconfirm -S msys/make pacman --noconfirm -S mingw64/mingw-w64-x86_64-opus pacman --noconfirm -S diffutils pacman --noconfirm -S pkg-config PKG_CONFIG_PATH="/mingw64/lib/pkgconfig:$PKG_CONFIG_PATH" ./configure --toolchain=msvc --disable-programs --disable-doc --disable-everything --enable-protocol=file --enable-libopus --enable-decoder=aac --enable-decoder=aac_latm --enable-decoder=aasc --enable-decoder=flac --enable-decoder=gif --enable-decoder=h264 --enable-decoder=mp1 --enable-decoder=mp1float --enable-decoder=mp2 --enable-decoder=mp2float --enable-decoder=mp3 --enable-decoder=mp3adu --enable-decoder=mp3adufloat --enable-decoder=mp3float --enable-decoder=mp3on4 --enable-decoder=mp3on4float --enable-decoder=mpeg4 --enable-decoder=msmpeg4v2 --enable-decoder=msmpeg4v3 --enable-decoder=wavpack --enable-decoder=opus --enable-decoder=pcm_alaw --enable-decoder=pcm_alaw_at --enable-decoder=pcm_f32be --enable-decoder=pcm_f32le --enable-decoder=pcm_f64be --enable-decoder=pcm_f64le --enable-decoder=pcm_lxf --enable-decoder=pcm_mulaw --enable-decoder=pcm_mulaw_at --enable-decoder=pcm_s16be --enable-decoder=pcm_s16be_planar --enable-decoder=pcm_s16le --enable-decoder=pcm_s16le_planar --enable-decoder=pcm_s24be --enable-decoder=pcm_s24daud --enable-decoder=pcm_s24le --enable-decoder=pcm_s24le_planar --enable-decoder=pcm_s32be --enable-decoder=pcm_s32le --enable-decoder=pcm_s32le_planar --enable-decoder=pcm_s64be --enable-decoder=pcm_s64le --enable-decoder=pcm_s8 --enable-decoder=pcm_s8_planar --enable-decoder=pcm_u16be --enable-decoder=pcm_u16le --enable-decoder=pcm_u24be --enable-decoder=pcm_u24le --enable-decoder=pcm_u32be --enable-decoder=pcm_u32le --enable-decoder=pcm_u8 --enable-decoder=pcm_zork --enable-decoder=vorbis --enable-decoder=wmalossless --enable-decoder=wmapro --enable-decoder=wmav1 --enable-decoder=wmav2 --enable-decoder=wmavoice --enable-encoder=libopus --enable-hwaccel=h264_d3d11va --enable-hwaccel=h264_dxva2 --enable-parser=aac --enable-parser=aac_latm --enable-parser=flac --enable-parser=h264 --enable-parser=mpeg4video --enable-parser=mpegaudio --enable-parser=opus --enable-parser=vorbis --enable-demuxer=aac --enable-demuxer=flac --enable-demuxer=gif --enable-demuxer=h264 --enable-demuxer=mov --enable-demuxer=mp3 --enable-demuxer=ogg --enable-demuxer=wav --enable-muxer=ogg --enable-muxer=opus --extra-ldflags="-libpath:$FullExecPath/../opus/win32/VS2015/Win32/Release" make -j4 make -j4 install tdesktop-1.2.17/Telegram/Patches/crashpad.diff000066400000000000000000000126031326245125100212100ustar00rootroot00000000000000diff --git a/client/capture_context_mac_test.cc b/client/capture_context_mac_test.cc index 436ac5ad..8e14fb9c 100644 --- a/client/capture_context_mac_test.cc +++ b/client/capture_context_mac_test.cc @@ -34,11 +34,11 @@ namespace { // gtest assertions. void SanityCheckContext(const NativeCPUContext& context) { #if defined(ARCH_CPU_X86) - ASSERT_EQ(x86_THREAD_STATE32, context.tsh.flavor); - ASSERT_EQ(implicit_cast(x86_THREAD_STATE32_COUNT), context.tsh.count); + ASSERT_EQ(implicit_cast(x86_THREAD_STATE32), implicit_cast(context.tsh.flavor)); + ASSERT_EQ(implicit_cast(x86_THREAD_STATE32_COUNT), implicit_cast(context.tsh.count)); #elif defined(ARCH_CPU_X86_64) - ASSERT_EQ(x86_THREAD_STATE64, context.tsh.flavor); - ASSERT_EQ(implicit_cast(x86_THREAD_STATE64_COUNT), context.tsh.count); + ASSERT_EQ(implicit_cast(x86_THREAD_STATE64), implicit_cast(context.tsh.flavor)); + ASSERT_EQ(implicit_cast(x86_THREAD_STATE64_COUNT), implicit_cast(context.tsh.count)); #endif #if defined(ARCH_CPU_X86_FAMILY) diff --git a/client/simulate_crash_mac.cc b/client/simulate_crash_mac.cc index 7e279015..27864388 100644 --- a/client/simulate_crash_mac.cc +++ b/client/simulate_crash_mac.cc @@ -177,12 +177,12 @@ bool DeliverException(thread_t thread, void SimulateCrash(const NativeCPUContext& cpu_context) { #if defined(ARCH_CPU_X86) - DCHECK_EQ(cpu_context.tsh.flavor, + DCHECK_EQ(implicit_cast(cpu_context.tsh.flavor), implicit_cast(x86_THREAD_STATE32)); DCHECK_EQ(implicit_cast(cpu_context.tsh.count), x86_THREAD_STATE32_COUNT); #elif defined(ARCH_CPU_X86_64) - DCHECK_EQ(cpu_context.tsh.flavor, + DCHECK_EQ(implicit_cast(cpu_context.tsh.flavor), implicit_cast(x86_THREAD_STATE64)); DCHECK_EQ(implicit_cast(cpu_context.tsh.count), x86_THREAD_STATE64_COUNT); diff --git a/client/simulate_crash_mac_test.cc b/client/simulate_crash_mac_test.cc index 87c5f845..ca813e4c 100644 --- a/client/simulate_crash_mac_test.cc +++ b/client/simulate_crash_mac_test.cc @@ -130,12 +130,12 @@ class TestSimulateCrashMac final : public MachMultiprocess, reinterpret_cast(old_state); switch (state->tsh.flavor) { case x86_THREAD_STATE32: - EXPECT_EQ(implicit_cast(x86_THREAD_STATE32_COUNT), - state->tsh.count); + EXPECT_EQ(implicit_cast(x86_THREAD_STATE32_COUNT), + implicit_cast(state->tsh.count)); break; case x86_THREAD_STATE64: - EXPECT_EQ(implicit_cast(x86_THREAD_STATE64_COUNT), - state->tsh.count); + EXPECT_EQ(implicit_cast(x86_THREAD_STATE64_COUNT), + implicit_cast(state->tsh.count)); break; default: ADD_FAILURE() << "unexpected tsh.flavor " << state->tsh.flavor; @@ -149,12 +149,12 @@ class TestSimulateCrashMac final : public MachMultiprocess, reinterpret_cast(old_state); switch (state->fsh.flavor) { case x86_FLOAT_STATE32: - EXPECT_EQ(implicit_cast(x86_FLOAT_STATE32_COUNT), - state->fsh.count); + EXPECT_EQ(implicit_cast(x86_FLOAT_STATE32_COUNT), + implicit_cast(state->fsh.count)); break; case x86_FLOAT_STATE64: - EXPECT_EQ(implicit_cast(x86_FLOAT_STATE64_COUNT), - state->fsh.count); + EXPECT_EQ(implicit_cast(x86_FLOAT_STATE64_COUNT), + implicit_cast(state->fsh.count)); break; default: ADD_FAILURE() << "unexpected fsh.flavor " << state->fsh.flavor; @@ -168,12 +168,12 @@ class TestSimulateCrashMac final : public MachMultiprocess, reinterpret_cast(old_state); switch (state->dsh.flavor) { case x86_DEBUG_STATE32: - EXPECT_EQ(implicit_cast(x86_DEBUG_STATE32_COUNT), - state->dsh.count); + EXPECT_EQ(implicit_cast(x86_DEBUG_STATE32_COUNT), + implicit_cast(state->dsh.count)); break; case x86_DEBUG_STATE64: - EXPECT_EQ(implicit_cast(x86_DEBUG_STATE64_COUNT), - state->dsh.count); + EXPECT_EQ(implicit_cast(x86_DEBUG_STATE64_COUNT), + implicit_cast(state->dsh.count)); break; default: ADD_FAILURE() << "unexpected dsh.flavor " << state->dsh.flavor; diff --git a/crashpad.gyp b/crashpad.gyp index 42fe0a26..d8af1bf1 100644 --- a/crashpad.gyp +++ b/crashpad.gyp @@ -25,7 +25,7 @@ 'minidump/minidump.gyp:*', 'minidump/minidump_test.gyp:*', 'snapshot/snapshot.gyp:*', - 'snapshot/snapshot_test.gyp:*', +# 'snapshot/snapshot_test.gyp:*', 'test/test.gyp:*', 'test/test_test.gyp:*', 'tools/tools.gyp:*', tdesktop-1.2.17/Telegram/Patches/gyp.diff000066400000000000000000000057661326245125100202360ustar00rootroot00000000000000diff --git a/pylib/gyp/generator/cmake.py b/pylib/gyp/generator/cmake.py index a2b9629..68d7020 100644 --- a/pylib/gyp/generator/cmake.py +++ b/pylib/gyp/generator/cmake.py @@ -1070,6 +1070,23 @@ def WriteTarget(namer, qualified_target, target_dicts, build_dir, config_to_use, output.write(')\n') + # Precompile header + precompiled_header = config.get('cmake_precompiled_header', '') + if precompiled_header: + precompiled_header_script = config.get('cmake_precompiled_header_script', '') + if not precompiled_header_script: + print ('ERROR: cmake_precompiled_header requires cmake_precompiled_header_script') + cmake_precompiled_header = NormjoinPath(path_from_cmakelists_to_gyp, precompiled_header) + cmake_precompiled_header_script = NormjoinPathForceCMakeSource(path_from_cmakelists_to_gyp, precompiled_header_script) + output.write('include(') + output.write(cmake_precompiled_header_script) + output.write(')\n') + output.write('add_precompiled_header(') + output.write(cmake_target_name) + output.write(' ') + output.write(cmake_precompiled_header) + output.write(')\n') + UnsetVariable(output, 'TOOLSET') UnsetVariable(output, 'TARGET') diff --git a/pylib/gyp/generator/xcode.py b/pylib/gyp/generator/xcode.py index db99d6a..8d56baf 100644 --- a/pylib/gyp/generator/xcode.py +++ b/pylib/gyp/generator/xcode.py @@ -72,6 +72,10 @@ generator_additional_non_configuration_keys = [ 'ios_app_extension', 'ios_watch_app', 'ios_watchkit_extension', + + 'mac_sandbox', # sandbox support + 'mac_sandbox_development_team', + 'mac_bundle', 'mac_bundle_resources', 'mac_framework_headers', @@ -772,6 +776,26 @@ def GenerateOutput(target_list, target_dicts, data, params): xcode_targets[qualified_target] = xct xcode_target_to_target_dict[xct] = spec + # sandbox support + is_sandbox = int(spec.get('mac_sandbox', 0)) + if is_sandbox: + dev_team = spec.get('mac_sandbox_development_team', '%%ERROR%%') + assert dev_team != '%%ERROR%%', ( + 'mac_sandbox must be accompanied by mac_sandbox_development_team (target "%s")' % + target_name) + try: + tmp = pbxp._properties['attributes']['TargetAttributes'] + except KeyError: + pbxp._properties['attributes']['TargetAttributes'] = {} + pbxp._properties['attributes']['TargetAttributes'][xct] = { + 'DevelopmentTeam': dev_team, + 'SystemCapabilities': { + 'com.apple.Sandbox': { + 'enabled': 1, + }, + }, + } + spec_actions = spec.get('actions', []) spec_rules = spec.get('rules', []) @@ -1141,7 +1165,8 @@ exit 1 groups = [x for x in groups if not x.endswith('_excluded')] for group in groups: for item in rule.get(group, []): - pbxp.AddOrGetFileInRootGroup(item) + concrete_item = ExpandXcodeVariables(item, rule_input_dict) + pbxp.AddOrGetFileInRootGroup(concrete_item) # Add "sources". for source in spec.get('sources', []): tdesktop-1.2.17/Telegram/Patches/macold/000077500000000000000000000000001326245125100200265ustar00rootroot00000000000000tdesktop-1.2.17/Telegram/Patches/macold/crashpad.diff000066400000000000000000000137211326245125100224510ustar00rootroot00000000000000diff --git a/build/crashpad.gypi b/build/crashpad.gypi index 027c7b68..4bfdfb5a 100644 --- a/build/crashpad.gypi +++ b/build/crashpad.gypi @@ -25,5 +25,15 @@ 4201, # nonstandard extension used : nameless struct/union. 4324, # structure was padded due to __declspec(align()). ], + 'xcode_settings': { + 'OTHER_CPLUSPLUSFLAGS': [ '-nostdinc++' ], + 'OTHER_LDFLAGS': [ + '/usr/local/macold/lib/libc++.a', + '/usr/local/macold/lib/libc++abi.a', + ], + }, + 'include_dirs': [ + '/usr/local/macold/include/c++/v1', + ], }, } diff --git a/client/capture_context_mac_test.cc b/client/capture_context_mac_test.cc index 436ac5ad..8e14fb9c 100644 --- a/client/capture_context_mac_test.cc +++ b/client/capture_context_mac_test.cc @@ -34,11 +34,11 @@ namespace { // gtest assertions. void SanityCheckContext(const NativeCPUContext& context) { #if defined(ARCH_CPU_X86) - ASSERT_EQ(x86_THREAD_STATE32, context.tsh.flavor); - ASSERT_EQ(implicit_cast(x86_THREAD_STATE32_COUNT), context.tsh.count); + ASSERT_EQ(implicit_cast(x86_THREAD_STATE32), implicit_cast(context.tsh.flavor)); + ASSERT_EQ(implicit_cast(x86_THREAD_STATE32_COUNT), implicit_cast(context.tsh.count)); #elif defined(ARCH_CPU_X86_64) - ASSERT_EQ(x86_THREAD_STATE64, context.tsh.flavor); - ASSERT_EQ(implicit_cast(x86_THREAD_STATE64_COUNT), context.tsh.count); + ASSERT_EQ(implicit_cast(x86_THREAD_STATE64), implicit_cast(context.tsh.flavor)); + ASSERT_EQ(implicit_cast(x86_THREAD_STATE64_COUNT), implicit_cast(context.tsh.count)); #endif #if defined(ARCH_CPU_X86_FAMILY) diff --git a/client/simulate_crash_mac.cc b/client/simulate_crash_mac.cc index 7e279015..27864388 100644 --- a/client/simulate_crash_mac.cc +++ b/client/simulate_crash_mac.cc @@ -177,12 +177,12 @@ bool DeliverException(thread_t thread, void SimulateCrash(const NativeCPUContext& cpu_context) { #if defined(ARCH_CPU_X86) - DCHECK_EQ(cpu_context.tsh.flavor, + DCHECK_EQ(implicit_cast(cpu_context.tsh.flavor), implicit_cast(x86_THREAD_STATE32)); DCHECK_EQ(implicit_cast(cpu_context.tsh.count), x86_THREAD_STATE32_COUNT); #elif defined(ARCH_CPU_X86_64) - DCHECK_EQ(cpu_context.tsh.flavor, + DCHECK_EQ(implicit_cast(cpu_context.tsh.flavor), implicit_cast(x86_THREAD_STATE64)); DCHECK_EQ(implicit_cast(cpu_context.tsh.count), x86_THREAD_STATE64_COUNT); diff --git a/client/simulate_crash_mac_test.cc b/client/simulate_crash_mac_test.cc index 87c5f845..ca813e4c 100644 --- a/client/simulate_crash_mac_test.cc +++ b/client/simulate_crash_mac_test.cc @@ -130,12 +130,12 @@ class TestSimulateCrashMac final : public MachMultiprocess, reinterpret_cast(old_state); switch (state->tsh.flavor) { case x86_THREAD_STATE32: - EXPECT_EQ(implicit_cast(x86_THREAD_STATE32_COUNT), - state->tsh.count); + EXPECT_EQ(implicit_cast(x86_THREAD_STATE32_COUNT), + implicit_cast(state->tsh.count)); break; case x86_THREAD_STATE64: - EXPECT_EQ(implicit_cast(x86_THREAD_STATE64_COUNT), - state->tsh.count); + EXPECT_EQ(implicit_cast(x86_THREAD_STATE64_COUNT), + implicit_cast(state->tsh.count)); break; default: ADD_FAILURE() << "unexpected tsh.flavor " << state->tsh.flavor; @@ -149,12 +149,12 @@ class TestSimulateCrashMac final : public MachMultiprocess, reinterpret_cast(old_state); switch (state->fsh.flavor) { case x86_FLOAT_STATE32: - EXPECT_EQ(implicit_cast(x86_FLOAT_STATE32_COUNT), - state->fsh.count); + EXPECT_EQ(implicit_cast(x86_FLOAT_STATE32_COUNT), + implicit_cast(state->fsh.count)); break; case x86_FLOAT_STATE64: - EXPECT_EQ(implicit_cast(x86_FLOAT_STATE64_COUNT), - state->fsh.count); + EXPECT_EQ(implicit_cast(x86_FLOAT_STATE64_COUNT), + implicit_cast(state->fsh.count)); break; default: ADD_FAILURE() << "unexpected fsh.flavor " << state->fsh.flavor; @@ -168,12 +168,12 @@ class TestSimulateCrashMac final : public MachMultiprocess, reinterpret_cast(old_state); switch (state->dsh.flavor) { case x86_DEBUG_STATE32: - EXPECT_EQ(implicit_cast(x86_DEBUG_STATE32_COUNT), - state->dsh.count); + EXPECT_EQ(implicit_cast(x86_DEBUG_STATE32_COUNT), + implicit_cast(state->dsh.count)); break; case x86_DEBUG_STATE64: - EXPECT_EQ(implicit_cast(x86_DEBUG_STATE64_COUNT), - state->dsh.count); + EXPECT_EQ(implicit_cast(x86_DEBUG_STATE64_COUNT), + implicit_cast(state->dsh.count)); break; default: ADD_FAILURE() << "unexpected dsh.flavor " << state->dsh.flavor; diff --git a/crashpad.gyp b/crashpad.gyp index 42fe0a26..d8af1bf1 100644 --- a/crashpad.gyp +++ b/crashpad.gyp @@ -25,7 +25,7 @@ 'minidump/minidump.gyp:*', 'minidump/minidump_test.gyp:*', 'snapshot/snapshot.gyp:*', - 'snapshot/snapshot_test.gyp:*', +# 'snapshot/snapshot_test.gyp:*', 'test/test.gyp:*', 'test/test_test.gyp:*', 'tools/tools.gyp:*', tdesktop-1.2.17/Telegram/Patches/macold/mini_chromium.diff000066400000000000000000000014641326245125100235240ustar00rootroot00000000000000diff --git a/build/common.gypi b/build/common.gypi index 1affc70..c0d2f6a 100644 --- a/build/common.gypi +++ b/build/common.gypi @@ -66,6 +66,11 @@ 'conditions': [ ['clang!=0', { 'CLANG_CXX_LANGUAGE_STANDARD': 'c++11', # -std=c++11 + 'OTHER_CPLUSPLUSFLAGS': [ '-nostdinc++' ], + 'OTHER_LDFLAGS': [ + '/usr/local/macold/lib/libc++.a', + '/usr/local/macold/lib/libc++abi.a', + ], # Don't link in libarclite_macosx.a, see http://crbug.com/156530. 'CLANG_LINK_OBJC_RUNTIME': 'NO', # -fno-objc-link-runtime @@ -116,6 +121,9 @@ ], }, + 'include_dirs': [ + '/usr/local/macold/include/c++/v1', + ], }], ['OS=="linux"', { tdesktop-1.2.17/Telegram/Patches/macold/qtbase_5_3_2.diff000066400000000000000000001052071326245125100230330ustar00rootroot00000000000000diff --git a/configure b/configure index cb8d78fd3c..cadb3f0a88 100755 --- a/configure +++ b/configure @@ -511,7 +511,8 @@ if [ "$BUILD_ON_MAC" = "yes" ]; then exit 2 fi - if ! /usr/bin/xcrun -find xcrun >/dev/null 2>&1; then + # Patch: Fix Qt for working with Xcode 8. + if ! /usr/bin/xcrun -find xcodebuild >/dev/null 2>&1; then echo >&2 echo " Xcode not set up properly. You may need to confirm the license" >&2 echo " agreement by running /usr/bin/xcodebuild without arguments." >&2 diff --git a/mkspecs/common/g++-macx.conf b/mkspecs/common/g++-macx.conf index 086510dd96..c485967863 100644 --- a/mkspecs/common/g++-macx.conf +++ b/mkspecs/common/g++-macx.conf @@ -14,7 +14,13 @@ QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO += -gdwarf-2 QMAKE_CXXFLAGS_RELEASE_WITH_DEBUGINFO += -gdwarf-2 QMAKE_LFLAGS_RELEASE_WITH_DEBUGINFO += -g -gdwarf-2 -QMAKE_LFLAGS_STATIC_LIB += -all_load +# Patch: Don't remember :( +#QMAKE_LFLAGS_STATIC_LIB += -all_load + +# Patch: Use C++14 with custom libc++ build. +QMAKE_CXXFLAGS_CXX11 = -std=c++1y +QMAKE_CXXFLAGS += -nostdinc++ -I/usr/local/macold/include/c++/v1 +QMAKE_LFLAGS += /usr/local/macold/lib/libc++.a /usr/local/macold/lib/libc++abi.a QMAKE_XCODE_GCC_VERSION = com.apple.compilers.llvmgcc42 diff --git a/mkspecs/features/mac/default_pre.prf b/mkspecs/features/mac/default_pre.prf index 0cc8cd6dfd..ca9725b779 100644 --- a/mkspecs/features/mac/default_pre.prf +++ b/mkspecs/features/mac/default_pre.prf @@ -12,7 +12,9 @@ isEmpty(QMAKE_XCODE_DEVELOPER_PATH) { error("Xcode is not installed in $${QMAKE_XCODE_DEVELOPER_PATH}. Please use xcode-select to choose Xcode installation path.") # Make sure Xcode is set up properly - isEmpty($$list($$system("/usr/bin/xcrun -find xcrun 2>/dev/null"))): \ + + # Patch: Fix Qt for working with Xcode 8. + isEmpty($$list($$system("/usr/bin/xcrun -find xcodebuild 2>/dev/null"))): \ error("Xcode not set up properly. You may need to confirm the license agreement by running /usr/bin/xcodebuild.") } diff --git a/src/gui/image/qbmphandler.cpp b/src/gui/image/qbmphandler.cpp index bb79a139b3..5d595bc3b3 100644 --- a/src/gui/image/qbmphandler.cpp +++ b/src/gui/image/qbmphandler.cpp @@ -220,6 +220,10 @@ static bool read_dib_body(QDataStream &s, const BMP_INFOHDR &bi, int offset, int int blue_scale = 0; int alpha_scale = 0; + // Patch: Backport a fix for bmp reader. + if (!d->isSequential()) + d->seek(startpos + BMP_FILEHDR_SIZE + (bi.biSize >= BMP_WIN4? BMP_WIN : bi.biSize)); // goto start of colormap + if (bi.biSize >= BMP_WIN4 || (comp == BMP_BITFIELDS && (nbits == 16 || nbits == 32))) { if (d->read((char *)&red_mask, sizeof(red_mask)) != sizeof(red_mask)) return false; @@ -307,8 +311,9 @@ static bool read_dib_body(QDataStream &s, const BMP_INFOHDR &bi, int offset, int image.setDotsPerMeterX(bi.biXPelsPerMeter); image.setDotsPerMeterY(bi.biYPelsPerMeter); - if (!d->isSequential()) - d->seek(startpos + BMP_FILEHDR_SIZE + (bi.biSize >= BMP_WIN4? BMP_WIN : bi.biSize)); // goto start of colormap + // Patch: Backport a fix for bmp reader. + //if (!d->isSequential()) + // d->seek(startpos + BMP_FILEHDR_SIZE + (bi.biSize >= BMP_WIN4? BMP_WIN : bi.biSize)); // goto start of colormap if (ncols > 0) { // read color table uchar rgb[4]; diff --git a/src/gui/painting/qpaintengine_p.h b/src/gui/painting/qpaintengine_p.h index ebff9509ab..4300ca4c0f 100644 --- a/src/gui/painting/qpaintengine_p.h +++ b/src/gui/painting/qpaintengine_p.h @@ -87,8 +87,18 @@ public: if (hasSystemTransform) { if (systemTransform.type() <= QTransform::TxTranslate) systemClip.translate(qRound(systemTransform.dx()), qRound(systemTransform.dy())); - else + // Patch: Transform the system clip region back from device pixels to device-independent pixels before + // applying systemTransform, which already has transform from device-independent pixels to device pixels. + else { +#ifdef Q_OS_MAC + QTransform scaleTransform; + const qreal invDevicePixelRatio = 1. / pdev->devicePixelRatio(); + scaleTransform.scale(invDevicePixelRatio, invDevicePixelRatio); + systemClip = systemTransform.map(scaleTransform.map(systemClip)); +#else systemClip = systemTransform.map(systemClip); +#endif + } } // Make sure we're inside the viewport. diff --git a/src/gui/text/qtextlayout.cpp b/src/gui/text/qtextlayout.cpp index 4879ae51d7..56cdcbaf01 100644 --- a/src/gui/text/qtextlayout.cpp +++ b/src/gui/text/qtextlayout.cpp @@ -654,6 +654,9 @@ int QTextLayout::nextCursorPosition(int oldPos, CursorMode mode) const while (oldPos < len && !attributes[oldPos].graphemeBoundary) oldPos++; } else { + // Patch: Skip to the end of the current word, not to the start of the next one. + while (oldPos < len && attributes[oldPos].whiteSpace) + oldPos++; if (oldPos < len && d->atWordSeparator(oldPos)) { oldPos++; while (oldPos < len && d->atWordSeparator(oldPos)) @@ -662,8 +665,9 @@ int QTextLayout::nextCursorPosition(int oldPos, CursorMode mode) const while (oldPos < len && !d->atSpace(oldPos) && !d->atWordSeparator(oldPos)) oldPos++; } - while (oldPos < len && d->atSpace(oldPos)) - oldPos++; + // Patch: Skip to the end of the current word, not to the start of the next one. + //while (oldPos < len && d->atSpace(oldPos)) + // oldPos++; } return oldPos; @@ -1602,6 +1606,9 @@ namespace { int currentPosition; glyph_t previousGlyph; + // Patch: Backport a crash fix. + QFontEngine *previousGlyphFontEngine; + QFixed minw; QFixed softHyphenWidth; QFixed rightBearing; @@ -1634,13 +1641,19 @@ namespace { if (currentPosition > 0 && logClusters[currentPosition - 1] < glyphs.numGlyphs) { previousGlyph = currentGlyph(); // needed to calculate right bearing later + + // Patch: Backport a crash fix. + previousGlyphFontEngine = fontEngine; } } - inline void adjustRightBearing(glyph_t glyph) + // Patch: Backport a crash fix. + inline void adjustRightBearing(QFontEngine *engine, glyph_t glyph) { qreal rb; - fontEngine->getGlyphBearings(glyph, 0, &rb); + + // Patch: Backport a crash fix. + engine->getGlyphBearings(glyph, 0, &rb); rightBearing = qMin(QFixed(), QFixed::fromReal(rb)); } @@ -1648,13 +1661,16 @@ namespace { { if (currentPosition <= 0) return; - adjustRightBearing(currentGlyph()); + + // Patch: Backport a crash fix. + adjustRightBearing(fontEngine, currentGlyph()); } inline void adjustPreviousRightBearing() { if (previousGlyph > 0) - adjustRightBearing(previousGlyph); + // Patch: Backport a crash fix. + adjustRightBearing(previousGlyphFontEngine, previousGlyph); } inline void resetRightBearing() diff --git a/src/gui/text/qtextlayout.h b/src/gui/text/qtextlayout.h index cbe42c3844..b273db7e78 100644 --- a/src/gui/text/qtextlayout.h +++ b/src/gui/text/qtextlayout.h @@ -194,6 +194,9 @@ private: QRectF *brect, int tabstops, int* tabarray, int tabarraylen, QPainter *painter); QTextEngine *d; + + // Patch: Give access to the internal api. + friend class TextBlock; }; diff --git a/src/platformsupport/fontdatabases/mac/qcoretextfontdatabase.mm b/src/platformsupport/fontdatabases/mac/qcoretextfontdatabase.mm index ca7afb7d1b..25ae50008d 100644 --- a/src/platformsupport/fontdatabases/mac/qcoretextfontdatabase.mm +++ b/src/platformsupport/fontdatabases/mac/qcoretextfontdatabase.mm @@ -256,6 +256,13 @@ static void getFontDescription(CTFontDescriptorRef font, FontDescription *fd) fd->foundryName = QStringLiteral("CoreText"); fd->familyName = (CFStringRef) CTFontDescriptorCopyAttribute(font, kCTFontFamilyNameAttribute); + + // Patch: Fix open sans semibold loading. + QCFString _displayName = (CFStringRef) CTFontDescriptorCopyAttribute(font, kCTFontDisplayNameAttribute); + if (_displayName == QStringLiteral("Open Sans Semibold")) { + fd->familyName = _displayName; + } + fd->styleName = (CFStringRef)CTFontDescriptorCopyAttribute(font, kCTFontStyleNameAttribute); fd->weight = QFont::Normal; fd->style = QFont::StyleNormal; diff --git a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm index 92358ecc74..694fee7350 100644 --- a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm +++ b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm @@ -213,7 +213,8 @@ static void cleanupCocoaApplicationDelegate() if (reflectionDelegate) { if ([reflectionDelegate respondsToSelector:@selector(applicationShouldTerminate:)]) return [reflectionDelegate applicationShouldTerminate:sender]; - return NSTerminateNow; + // Patch: Don't terminate if reflectionDelegate does not respond to that selector, just use the default. + //return NSTerminateNow; } if ([self canQuit]) { @@ -289,6 +290,11 @@ static void cleanupCocoaApplicationDelegate() - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { + // Patch: We need to receive this notification in the delegate as well. + if (reflectionDelegate + && [reflectionDelegate respondsToSelector:@selector(applicationDidFinishLaunching:)]) + [reflectionDelegate applicationDidFinishLaunching:aNotification]; + Q_UNUSED(aNotification); inLaunch = false; // qt_release_apple_event_handler(); @@ -411,7 +417,9 @@ static void cleanupCocoaApplicationDelegate() { Q_UNUSED(replyEvent); NSString *urlString = [[event paramDescriptorForKeyword:keyDirectObject] stringValue]; - QWindowSystemInterface::handleFileOpenEvent(QUrl(QCFString::toQString(urlString))); + + // Patch: Fix opening of an external url by a protocol handler. + QWindowSystemInterface::handleFileOpenEvent(QUrl::fromNSURL([NSURL URLWithString:urlString])); } - (void)appleEventQuit:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent diff --git a/src/plugins/platforms/cocoa/qcocoacursor.mm b/src/plugins/platforms/cocoa/qcocoacursor.mm index b81b9a0b1c..4e59e833b1 100644 --- a/src/plugins/platforms/cocoa/qcocoacursor.mm +++ b/src/plugins/platforms/cocoa/qcocoacursor.mm @@ -81,7 +81,7 @@ void QCocoaCursor::setPos(const QPoint &position) pos.x = position.x(); pos.y = position.y(); - CGEventRef e = CGEventCreateMouseEvent(0, kCGEventMouseMoved, pos, 0); + CGEventRef e = CGEventCreateMouseEvent(0, kCGEventMouseMoved, pos, kCGMouseButtonLeft); CGEventPost(kCGHIDEventTap, e); CFRelease(e); } diff --git a/src/plugins/platforms/cocoa/qcocoahelpers.mm b/src/plugins/platforms/cocoa/qcocoahelpers.mm index 9850f83dea..218ed7e81c 100644 --- a/src/plugins/platforms/cocoa/qcocoahelpers.mm +++ b/src/plugins/platforms/cocoa/qcocoahelpers.mm @@ -649,9 +649,10 @@ OSStatus qt_mac_drawCGImage(CGContextRef inContext, const CGRect *inBounds, CGIm // Verbatim copy if HIViewDrawCGImage (as shown on Carbon-Dev) OSStatus err = noErr; - require_action(inContext != NULL, InvalidContext, err = paramErr); - require_action(inBounds != NULL, InvalidBounds, err = paramErr); - require_action(inImage != NULL, InvalidImage, err = paramErr); + // Patch: Fix build on latest Xcode. + //require_action(inContext != NULL, InvalidContext, err = paramErr); + //require_action(inBounds != NULL, InvalidBounds, err = paramErr); + //require_action(inImage != NULL, InvalidImage, err = paramErr); CGContextSaveGState( inContext ); CGContextTranslateCTM (inContext, 0, inBounds->origin.y + CGRectGetMaxY(*inBounds)); @@ -660,9 +661,11 @@ OSStatus qt_mac_drawCGImage(CGContextRef inContext, const CGRect *inBounds, CGIm CGContextDrawImage(inContext, *inBounds, inImage); CGContextRestoreGState(inContext); -InvalidImage: -InvalidBounds: -InvalidContext: + +// Patch: Fix build on latest Xcode. +//InvalidImage: +//InvalidBounds: +//InvalidContext: return err; } diff --git a/src/plugins/platforms/cocoa/qcocoaintegration.mm b/src/plugins/platforms/cocoa/qcocoaintegration.mm index 9fd05a65ee..dea60720e7 100644 --- a/src/plugins/platforms/cocoa/qcocoaintegration.mm +++ b/src/plugins/platforms/cocoa/qcocoaintegration.mm @@ -402,14 +402,24 @@ void QCocoaIntegration::updateScreens() } siblings << screen; } + + // Patch: Backport crash fix from Qt 5.6.1. + // Set virtual siblings list. All screens in mScreens are siblings, because we ignored the + // mirrors. Note that some of the screens we update the siblings list for here may be deleted + // below, but update anyway to keep the to-be-deleted screens out of the siblings list. + foreach (QCocoaScreen* screen, mScreens) + screen->setVirtualSiblings(siblings); + // Now the leftovers in remainingScreens are no longer current, so we can delete them. foreach (QCocoaScreen* screen, remainingScreens) { mScreens.removeOne(screen); delete screen; } + + // Patch: Backport crash fix from Qt 5.6.1. // All screens in mScreens are siblings, because we ignored the mirrors. - foreach (QCocoaScreen* screen, mScreens) - screen->setVirtualSiblings(siblings); + //foreach (QCocoaScreen* screen, mScreens) + // screen->setVirtualSiblings(siblings); } QCocoaScreen *QCocoaIntegration::screenAtIndex(int index) diff --git a/src/plugins/platforms/cocoa/qcocoakeymapper.mm b/src/plugins/platforms/cocoa/qcocoakeymapper.mm index e46eaff6be..c62db534a2 100644 --- a/src/plugins/platforms/cocoa/qcocoakeymapper.mm +++ b/src/plugins/platforms/cocoa/qcocoakeymapper.mm @@ -382,6 +382,12 @@ bool QCocoaKeyMapper::updateKeyboard() keyboardInputLocale = QLocale::c(); keyboardInputDirection = Qt::LeftToRight; } + + // Patch: Backport a fix for layout-independent keyboard shortcuts. + const auto newMode = keyboard_mode; + deleteLayouts(); + keyboard_mode = newMode; + return true; } @@ -464,7 +470,8 @@ QList QCocoaKeyMapper::possibleKeys(const QKeyEvent *event) const Qt::KeyboardModifiers neededMods = ModsTbl[i]; int key = kbItem->qtKey[i]; if (key && key != baseKey && ((keyMods & neededMods) == neededMods)) { - ret << int(key + (keyMods & ~neededMods)); + // Patch: Backport a fix for layout-independent keyboard shortcuts. + ret << int(key + neededMods); } } return ret; diff --git a/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm index 83c960d931..03ae9696af 100755 --- a/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm +++ b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm @@ -102,7 +102,10 @@ QT_USE_NAMESPACE QCocoaSystemTrayIcon *systray; NSStatusItem *item; QCocoaMenu *menu; - bool menuVisible; + + // Patch: Nice macOS tray icon support. + bool menuVisible, iconSelected; + QIcon icon; QT_MANGLE_NAMESPACE(QNSImageView) *imageCell; } @@ -124,6 +127,10 @@ QT_USE_NAMESPACE QT_MANGLE_NAMESPACE(QNSStatusItem) *parent; } -(id)initWithParent:(QT_MANGLE_NAMESPACE(QNSStatusItem)*)myParent; + +// Patch: Nice macOS tray icon support. +-(void)updateIconSelection; + -(void)menuTrackingDone:(NSNotification*)notification; -(void)mousePressed:(NSEvent *)mouseEvent button:(Qt::MouseButton)mouseButton; @end @@ -187,6 +194,19 @@ void QCocoaSystemTrayIcon::cleanup() m_sys = 0; } +// Patch: Nice macOS tray icon support. +namespace { + +qreal getDevicePixelRatio() { + qreal result = 1.0; + foreach (QScreen *screen, QGuiApplication::screens()) { + result = qMax(result, screen->devicePixelRatio()); + } + return result; +} + +} // namespace + void QCocoaSystemTrayIcon::updateIcon(const QIcon &icon) { if (!m_sys) @@ -194,13 +214,18 @@ void QCocoaSystemTrayIcon::updateIcon(const QIcon &icon) m_sys->item->icon = icon; - const bool menuVisible = m_sys->item->menu && m_sys->item->menuVisible; + // Patch: Nice macOS tray icon support. + //const bool menuVisible = m_sys->item->menu && m_sys->item->menuVisible; - CGFloat hgt = [[[NSApplication sharedApplication] mainMenu] menuBarHeight]; - const short scale = hgt - 4; + const int padding = 0; + const int menuHeight = [[NSStatusBar systemStatusBar] thickness]; + const int maxImageHeight = menuHeight - padding; + + const short scale = maxImageHeight * getDevicePixelRatio(); QPixmap pm = m_sys->item->icon.pixmap(QSize(scale, scale), - menuVisible ? QIcon::Selected : QIcon::Normal); + // Patch: Nice macOS tray icon support. + m_sys->item->iconSelected ? QIcon::Selected : QIcon::Normal); if (pm.isNull()) { pm = QPixmap(scale, scale); pm.fill(Qt::transparent); @@ -322,15 +347,16 @@ QT_END_NAMESPACE return self; } --(void)menuTrackingDone:(NSNotification*)notification +// Patch: Nice macOS tray icon support. +-(void)updateIconSelection { - Q_UNUSED(notification); - down = NO; + const int padding = 0; + const int menuHeight = [[NSStatusBar systemStatusBar] thickness]; + const int maxImageHeight = menuHeight - padding; - CGFloat hgt = [[[NSApplication sharedApplication] mainMenu] menuBarHeight]; - const short scale = hgt - 4; - - QPixmap pm = parent->icon.pixmap(QSize(scale, scale), QIcon::Normal); + const short scale = maxImageHeight * getDevicePixelRatio(); + QPixmap pm = parent->icon.pixmap(QSize(scale, scale), + parent->iconSelected ? QIcon::Selected : QIcon::Normal); if (pm.isNull()) { pm = QPixmap(scale, scale); pm.fill(Qt::transparent); @@ -338,9 +364,19 @@ QT_END_NAMESPACE NSImage *nsaltimage = static_cast(qt_mac_create_nsimage(pm)); [self setImage: nsaltimage]; [nsaltimage release]; +} + +-(void)menuTrackingDone:(NSNotification*)notification +{ + Q_UNUSED(notification); + down = NO; parent->menuVisible = false; + // Patch: Nice macOS tray icon support. + parent->iconSelected = false; + [self updateIconSelection]; + [self setNeedsDisplay:YES]; } @@ -350,18 +386,9 @@ QT_END_NAMESPACE int clickCount = [mouseEvent clickCount]; [self setNeedsDisplay:YES]; - CGFloat hgt = [[[NSApplication sharedApplication] mainMenu] menuBarHeight]; - const short scale = hgt - 4; - - QPixmap pm = parent->icon.pixmap(QSize(scale, scale), - parent->menuVisible ? QIcon::Selected : QIcon::Normal); - if (pm.isNull()) { - pm = QPixmap(scale, scale); - pm.fill(Qt::transparent); - } - NSImage *nsaltimage = static_cast(qt_mac_create_nsimage(pm)); - [self setImage: nsaltimage]; - [nsaltimage release]; + // Patch: Nice macOS tray icon support. + parent->iconSelected = (clickCount != 2) && parent->menu; + [self updateIconSelection]; if (clickCount == 2) { [self menuTrackingDone:nil]; @@ -380,6 +407,10 @@ QT_END_NAMESPACE { Q_UNUSED(mouseEvent); [self menuTrackingDone:nil]; + + // Patch: Nice macOS tray icon support. + parent->iconSelected = false; + [self updateIconSelection]; } - (void)rightMouseDown:(NSEvent *)mouseEvent @@ -391,6 +422,10 @@ QT_END_NAMESPACE { Q_UNUSED(mouseEvent); [self menuTrackingDone:nil]; + + // Patch: Nice macOS tray icon support. + parent->iconSelected = false; + [self updateIconSelection]; } - (void)otherMouseDown:(NSEvent *)mouseEvent @@ -405,7 +440,8 @@ QT_END_NAMESPACE } -(void)drawRect:(NSRect)rect { - [[parent item] drawStatusBarBackgroundInRect:rect withHighlight:down]; + // Patch: Nice macOS tray icon support. + [[parent item] drawStatusBarBackgroundInRect:rect withHighlight:parent->menu ? down : NO]; [super drawRect:rect]; } @end diff --git a/src/plugins/platforms/cocoa/qcocoawindow.mm b/src/plugins/platforms/cocoa/qcocoawindow.mm index 4d0458a4aa..3357a5ef81 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.mm +++ b/src/plugins/platforms/cocoa/qcocoawindow.mm @@ -167,7 +167,8 @@ static bool isMouseEvent(NSEvent *ev) if (!self.window.delegate) return; // Already detached, pending NSAppKitDefined event - if (pw && pw->frameStrutEventsEnabled() && isMouseEvent(theEvent)) { + // Patch: Fix events loss if the window was minimized or hidden. + if (pw && pw->frameStrutEventsEnabled() && pw->m_synchedWindowState != Qt::WindowMinimized && pw->m_isExposed && isMouseEvent(theEvent)) { NSPoint loc = [theEvent locationInWindow]; NSRect windowFrame = [self.window legacyConvertRectFromScreen:[self.window frame]]; NSRect contentFrame = [[self.window contentView] frame]; @@ -795,6 +796,16 @@ NSUInteger QCocoaWindow::windowStyleMask(Qt::WindowFlags flags) { Qt::WindowType type = static_cast(int(flags & Qt::WindowType_Mask)); NSInteger styleMask = NSBorderlessWindowMask; + + // Patch: allow creating panels floating on all spaces in macOS. + // If you call "setCollectionBehavior:NSWindowCollectionBehaviorFullScreenAuxiliary" before + // setting the "NSNonactivatingPanelMask" bit in the style mask it won't work after that. + // So we need a way to set that bit before Qt sets collection behavior the way it does. + QVariant nonactivatingPanelMask = window()->property("_td_macNonactivatingPanelMask"); + if (nonactivatingPanelMask.isValid() && nonactivatingPanelMask.toBool()) { + styleMask |= NSNonactivatingPanelMask; + } + if (flags & Qt::FramelessWindowHint) return styleMask; if ((type & Qt::Popup) == Qt::Popup) { @@ -914,6 +925,19 @@ void QCocoaWindow::setWindowFilePath(const QString &filePath) [m_nsWindow setRepresentedFilename: fi.exists() ? QCFString::toNSString(filePath) : @""]; } +// Patch: Nice macOS window icon. +namespace { + +qreal getDevicePixelRatio() { + qreal result = 1.0; + foreach (QScreen *screen, QGuiApplication::screens()) { + result = qMax(result, screen->devicePixelRatio()); + } + return result; +} + +} // namespace + void QCocoaWindow::setWindowIcon(const QIcon &icon) { QCocoaAutoReleasePool pool; @@ -929,7 +953,10 @@ void QCocoaWindow::setWindowIcon(const QIcon &icon) if (icon.isNull()) { [iconButton setImage:nil]; } else { - QPixmap pixmap = icon.pixmap(QSize(22, 22)); + // Patch: Nice macOS window icon. + CGFloat hgt = 16. * getDevicePixelRatio(); + QPixmap pixmap = icon.pixmap(QSize(hgt, hgt)); + NSImage *image = static_cast(qt_mac_create_nsimage(pixmap)); [iconButton setImage:image]; [image release]; diff --git a/src/plugins/platforms/cocoa/qnsview.mm b/src/plugins/platforms/cocoa/qnsview.mm index a18ee7ff71..1f91feb0ae 100644 --- a/src/plugins/platforms/cocoa/qnsview.mm +++ b/src/plugins/platforms/cocoa/qnsview.mm @@ -393,7 +393,9 @@ static NSString *_q_NSWindowDidChangeOcclusionStateNotification = nil; [self notifyWindowStateChanged:newState]; // NSWindowDidOrderOnScreenAndFinishAnimatingNotification is private API, and not // emitted in 10.6, so we bring back the old behavior for that case alone. - if (newState == Qt::WindowNoState && QSysInfo::QSysInfo::MacintoshVersion == QSysInfo::MV_10_6) + + // Patch: Fix macOS window show after window was hidden. + if (newState == Qt::WindowNoState/* && QSysInfo::QSysInfo::MacintoshVersion == QSysInfo::MV_10_6*/) m_platformWindow->exposeWindow(); } else if ([notificationName isEqualToString: @"NSWindowDidOrderOffScreenNotification"]) { m_platformWindow->obscureWindow(); @@ -1300,7 +1302,9 @@ static QTabletEvent::TabletDevice wacomTabletDevice(NSEvent *theEvent) #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8 if (QSysInfo::QSysInfo::MacintoshVersion >= QSysInfo::MV_10_8) { // On 10.8 and above, MayBegin is likely to happen. We treat it the same as an actual begin. - if (phase == NSEventPhaseMayBegin) + + // Patch: Actual begin should be treated as begin as well. + if (phase == NSEventPhaseMayBegin || phase == NSEventPhaseBegan) ph = Qt::ScrollBegin; } else #endif @@ -1366,14 +1370,22 @@ static QTabletEvent::TabletDevice wacomTabletDevice(NSEvent *theEvent) quint32 nativeVirtualKey = [nsevent keyCode]; QChar ch = QChar::ReplacementCharacter; - int keyCode = Qt::Key_unknown; - if ([characters length] != 0) { - if (((modifiers & Qt::MetaModifier) || (modifiers & Qt::AltModifier)) && ([charactersIgnoringModifiers length] != 0)) - ch = QChar([charactersIgnoringModifiers characterAtIndex:0]); - else - ch = QChar([characters characterAtIndex:0]); - keyCode = [self convertKeyCode:ch]; - } + + // Patch: Backport a fix for layout-independent shortcuts. + if ([characters length] != 0) // https://bugreports.qt.io/browse/QTBUG-42584 + ch = QChar([characters characterAtIndex:0]); + else if ([charactersIgnoringModifiers length] != 0 && ((modifiers & Qt::MetaModifier) || (modifiers & Qt::AltModifier))) + ch = QChar([charactersIgnoringModifiers characterAtIndex:0]); + + int keyCode = [self convertKeyCode:ch]; + //int keyCode = Qt::Key_unknown; + //if ([characters length] != 0) { + // if (((modifiers & Qt::MetaModifier) || (modifiers & Qt::AltModifier)) && ([charactersIgnoringModifiers length] != 0)) + // ch = QChar([charactersIgnoringModifiers characterAtIndex:0]); + // else + // ch = QChar([characters characterAtIndex:0]); + // keyCode = [self convertKeyCode:ch]; + //} // we will send a key event unless the input method sets m_sendKeyEvent to false m_sendKeyEvent = true; @@ -1437,6 +1449,11 @@ static QTabletEvent::TabletDevice wacomTabletDevice(NSEvent *theEvent) && qtKey == Qt::Key_Period) { [self handleKeyEvent:nsevent eventType:int(QEvent::KeyPress)]; return YES; + + // Patch: Allow us to handle Ctrl+Tab and Ctrl+Backtab in the app. + } else if ([nsevent modifierFlags] & NSControlKeyMask && (qtKey == Qt::Key_Tab || qtKey == Qt::Key_Backtab)) { + [self handleKeyEvent:nsevent eventType:int(QEvent::KeyPress)]; + return YES; } } return [super performKeyEquivalent:nsevent]; diff --git a/src/tools/qlalr/lalr.cpp b/src/tools/qlalr/lalr.cpp index c68076477f..e2a7aafa58 100644 --- a/src/tools/qlalr/lalr.cpp +++ b/src/tools/qlalr/lalr.cpp @@ -246,11 +246,13 @@ void Grammar::buildExtendedGrammar () non_terminals.insert (accept_symbol); } -struct _Nullable: public std::unary_function +// Patch: Fix building with the new SDK. +struct __Nullable: public std::unary_function { Automaton *_M_automaton; - _Nullable (Automaton *aut): + // Patch: Fix building with the new SDK. + __Nullable (Automaton *aut): _M_automaton (aut) {} bool operator () (Name name) const @@ -308,7 +310,8 @@ void Automaton::buildNullables () for (RulePointer rule = _M_grammar->rules.begin (); rule != _M_grammar->rules.end (); ++rule) { - NameList::iterator nn = std::find_if (rule->rhs.begin (), rule->rhs.end (), std::not1 (_Nullable (this))); + // Patch: Fix building with the new SDK. + NameList::iterator nn = std::find_if (rule->rhs.begin (), rule->rhs.end (), std::not1 (__Nullable (this))); if (nn == rule->rhs.end ()) changed |= nullables.insert (rule->lhs).second; @@ -643,7 +646,8 @@ void Automaton::buildIncludesDigraph () if (! _M_grammar->isNonTerminal (*A)) continue; - NameList::iterator first_not_nullable = std::find_if (dot, rule->rhs.end (), std::not1 (_Nullable (this))); + // Patch: Fix building with the new SDK. + NameList::iterator first_not_nullable = std::find_if (dot, rule->rhs.end (), std::not1 (__Nullable (this))); if (first_not_nullable != rule->rhs.end ()) continue; diff --git a/src/widgets/kernel/qwidget.cpp b/src/widgets/kernel/qwidget.cpp index 7396808442..7178aecf80 100644 --- a/src/widgets/kernel/qwidget.cpp +++ b/src/widgets/kernel/qwidget.cpp @@ -4722,6 +4722,17 @@ void QWidget::render(QPainter *painter, const QPoint &targetOffset, return; // Fully transparent. Q_D(QWidget); + + // Patch: save and restore dirtyOpaqueChildren field. + // + // Just like in QWidget::grab() this field should be restored + // after the d->render() call, because it will be set to 1 and + // opaqueChildren field will be filled with empty region in + // case the widget is hidden (because all the opaque children + // will be skipped in isVisible() check). + // + const bool oldDirtyOpaqueChildren = d->dirtyOpaqueChildren; + const bool inRenderWithPainter = d->extra && d->extra->inRenderWithPainter; const QRegion toBePainted = !inRenderWithPainter ? d->prepareToRender(sourceRegion, renderFlags) : sourceRegion; @@ -4743,6 +4754,10 @@ void QWidget::render(QPainter *painter, const QPoint &targetOffset, if (!inRenderWithPainter && (opacity < 1.0 || (target->devType() == QInternal::Printer))) { d->render_helper(painter, targetOffset, toBePainted, renderFlags); d->extra->inRenderWithPainter = inRenderWithPainter; + + // Patch: save and restore dirtyOpaqueChildren field. + d->dirtyOpaqueChildren = oldDirtyOpaqueChildren; + return; } @@ -4774,6 +4789,9 @@ void QWidget::render(QPainter *painter, const QPoint &targetOffset, d->setSharedPainter(oldPainter); d->extra->inRenderWithPainter = inRenderWithPainter; + + // Patch: save and restore dirtyOpaqueChildren field. + d->dirtyOpaqueChildren = oldDirtyOpaqueChildren; } static void sendResizeEvents(QWidget *target) @@ -7983,7 +8001,8 @@ bool QWidget::event(QEvent *event) case QEvent::KeyPress: { QKeyEvent *k = (QKeyEvent *)event; bool res = false; - if (!(k->modifiers() & (Qt::ControlModifier | Qt::AltModifier))) { //### Add MetaModifier? + // Patch: Allow us to handle Ctrl+Tab and Ctrl+Backtab in the app. + if (!(k->modifiers() & (Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier))) { //### Add MetaModifier? if (k->key() == Qt::Key_Backtab || (k->key() == Qt::Key_Tab && (k->modifiers() & Qt::ShiftModifier))) res = focusNextPrevChild(false); diff --git a/src/widgets/styles/qmacstyle_mac.mm b/src/widgets/styles/qmacstyle_mac.mm index 0845a5eb02..5735cb6b39 100644 --- a/src/widgets/styles/qmacstyle_mac.mm +++ b/src/widgets/styles/qmacstyle_mac.mm @@ -3667,9 +3667,11 @@ void QMacStyle::drawControl(ControlElement ce, const QStyleOption *opt, QPainter NSBezierPath *pushButtonFocusRingPath; if (bdi.kind == kThemeBevelButton) - pushButtonFocusRingPath = [NSBezierPath bezierPathWithRect:focusRect]; + // Patch: Fix building with the new SDK. + pushButtonFocusRingPath = [NSBezierPath bezierPathWithRect:NSRectFromCGRect(focusRect)]; else - pushButtonFocusRingPath = [NSBezierPath bezierPathWithRoundedRect:focusRect xRadius:4 yRadius:4]; + // Patch: Fix building with the new SDK. + pushButtonFocusRingPath = [NSBezierPath bezierPathWithRoundedRect:NSRectFromCGRect(focusRect) xRadius:4 yRadius:4]; qt_drawFocusRingOnPath(cg, pushButtonFocusRingPath); } diff --git a/src/widgets/util/qsystemtrayicon_qpa.cpp b/src/widgets/util/qsystemtrayicon_qpa.cpp index f98aeaf678..00c0734129 100644 --- a/src/widgets/util/qsystemtrayicon_qpa.cpp +++ b/src/widgets/util/qsystemtrayicon_qpa.cpp @@ -99,13 +99,18 @@ void QSystemTrayIconPrivate::updateIcon_sys() void QSystemTrayIconPrivate::updateMenu_sys() { - if (qpa_sys && menu) { - if (!menu->platformMenu()) { - QPlatformMenu *platformMenu = qpa_sys->createMenu(); - if (platformMenu) - menu->setPlatformMenu(platformMenu); + // Patch: Nice macOS tray icon support. + if (qpa_sys) { + if (menu) { + if (!menu->platformMenu()) { + QPlatformMenu *platformMenu = qpa_sys->createMenu(); + if (platformMenu) + menu->setPlatformMenu(platformMenu); + } + qpa_sys->updateMenu(menu->platformMenu()); + } else { + qpa_sys->updateMenu(0); } - qpa_sys->updateMenu(menu->platformMenu()); } } diff --git a/src/widgets/widgets/qwidgetlinecontrol.cpp b/src/widgets/widgets/qwidgetlinecontrol.cpp index 75f30599be..980f2be1e9 100644 --- a/src/widgets/widgets/qwidgetlinecontrol.cpp +++ b/src/widgets/widgets/qwidgetlinecontrol.cpp @@ -1867,7 +1867,8 @@ void QWidgetLineControl::processKeyEvent(QKeyEvent* event) if (unknown && !isReadOnly()) { QString t = event->text(); - if (!t.isEmpty() && t.at(0).isPrint()) { + // Patch: Enable ZWJ and ZWNJ characters to be in text input. + if (!t.isEmpty() && (t.at(0).isPrint() || t.at(0).unicode() == 0x200C || t.at(0).unicode() == 0x200D)) { insert(t); #ifndef QT_NO_COMPLETER complete(event->key()); diff --git a/src/widgets/widgets/qwidgettextcontrol.cpp b/src/widgets/widgets/qwidgettextcontrol.cpp index 96438a0bdf..b0b7206405 100644 --- a/src/widgets/widgets/qwidgettextcontrol.cpp +++ b/src/widgets/widgets/qwidgettextcontrol.cpp @@ -1342,7 +1342,8 @@ void QWidgetTextControlPrivate::keyPressEvent(QKeyEvent *e) process: { QString text = e->text(); - if (!text.isEmpty() && (text.at(0).isPrint() || text.at(0) == QLatin1Char('\t'))) { + // Patch: Enable ZWJ and ZWNJ characters to be in text input. + if (!text.isEmpty() && (text.at(0).isPrint() || text.at(0) == QLatin1Char('\t') || text.at(0).unicode() == 0x200C || text.at(0).unicode() == 0x200D)) { if (overwriteMode // no need to call deleteChar() if we have a selection, insertText // does it already tdesktop-1.2.17/Telegram/Patches/macold/qtimageformats_5_3_2.diff000066400000000000000000000031471326245125100245770ustar00rootroot00000000000000diff --git a/src/3rdparty/libwebp/src/dec/vp8l.c b/src/3rdparty/libwebp/src/dec/vp8l.c index ea0254d..953ff01 100644 --- a/src/3rdparty/libwebp/src/dec/vp8l.c +++ b/src/3rdparty/libwebp/src/dec/vp8l.c @@ -12,7 +12,7 @@ // Authors: Vikas Arora (vikaas.arora@gmail.com) // Jyrki Alakuijala (jyrki@google.com) -#include +// Patch: Backport of a crash fix. #include #include "./alphai.h" #include "./vp8li.h" @@ -740,6 +740,10 @@ static int DecodeAlphaData(VP8LDecoder* const dec, uint8_t* const data, const int len_code_limit = NUM_LITERAL_CODES + NUM_LENGTH_CODES; const int mask = hdr->huffman_mask_; assert(htree_group != NULL); + + // Patch: Backport of a crash fix. + assert(pos < end); + assert(last_row <= height); assert(Is8bOptimizable(hdr)); @@ -830,6 +834,10 @@ static int DecodeImageData(VP8LDecoder* const dec, uint32_t* const data, (hdr->color_cache_size_ > 0) ? &hdr->color_cache_ : NULL; const int mask = hdr->huffman_mask_; assert(htree_group != NULL); + + // Patch: Backport of a crash fix. + assert(src < src_end); + assert(src_last <= src_end); while (!br->eos_ && src < src_last) { @@ -1294,6 +1302,11 @@ int VP8LDecodeAlphaImageStream(ALPHDecoder* const alph_dec, int last_row) { assert(dec->action_ == READ_DATA); assert(last_row <= dec->height_); + // Patch: Backport of a crash fix. + if (dec->last_pixel_ == dec->width_ * dec->height_) { + return 1; // done + } + // Decode (with special row processing). return alph_dec->use_8b_decode ? DecodeAlphaData(dec, (uint8_t*)dec->pixels_, dec->width_, dec->height_, tdesktop-1.2.17/Telegram/Patches/mini_chromium.diff000066400000000000000000000033221326245125100222600ustar00rootroot00000000000000diff --git a/base/mac/scoped_nsobject.h b/base/mac/scoped_nsobject.h index 2e157a4..5a306a1 100644 --- a/base/mac/scoped_nsobject.h +++ b/base/mac/scoped_nsobject.h @@ -11,6 +11,7 @@ #include "base/compiler_specific.h" #include "base/mac/scoped_typeref.h" +#include "base/template_util.h" namespace base { @@ -55,7 +56,7 @@ class scoped_nsobject : public scoped_nsprotocol { public: using scoped_nsprotocol::scoped_nsprotocol; - static_assert(std::is_same::value == false, + static_assert(is_same::value == false, "Use ScopedNSAutoreleasePool instead"); }; diff --git a/base/macros.h b/base/macros.h index 5d96783..096704c 100644 --- a/base/macros.h +++ b/base/macros.h @@ -42,8 +42,9 @@ char (&ArraySizeHelper(const T (&array)[N]))[N]; template inline Dest bit_cast(const Source& source) { +#if __cplusplus >= 201103L static_assert(sizeof(Dest) == sizeof(Source), "sizes must be equal"); - +#endif Dest dest; memcpy(&dest, &source, sizeof(dest)); return dest; diff --git a/build/common.gypi b/build/common.gypi index 1affc70..6e8f292 100644 --- a/build/common.gypi +++ b/build/common.gypi @@ -66,6 +66,11 @@ 'conditions': [ ['clang!=0', { 'CLANG_CXX_LANGUAGE_STANDARD': 'c++11', # -std=c++11 + 'conditions': [ + ['mac_deployment_target=="10.8"', { + 'CLANG_CXX_LIBRARY': 'libc++', # force -stdlib=libc++ for 10.8 + }] + ], # Don't link in libarclite_macosx.a, see http://crbug.com/156530. 'CLANG_LINK_OBJC_RUNTIME': 'NO', # -fno-objc-link-runtime tdesktop-1.2.17/Telegram/Patches/openal.diff000066400000000000000000000014071326245125100207010ustar00rootroot00000000000000diff --git a/Alc/backends/winmm.c b/Alc/backends/winmm.c index 9d8f8e9..8c8e44a 100644 --- a/Alc/backends/winmm.c +++ b/Alc/backends/winmm.c @@ -219,7 +219,7 @@ FORCE_ALIGN static int ALCwinmmPlayback_mixerProc(void *arg) SetRTPriority(); althrd_setname(althrd_current(), MIXER_THREAD_NAME); - while(GetMessage(&msg, NULL, 0, 0)) + if (!self->killNow) while(GetMessage(&msg, NULL, 0, 0)) { if(msg.message != WOM_DONE) continue; @@ -504,7 +504,7 @@ static int ALCwinmmCapture_captureProc(void *arg) althrd_setname(althrd_current(), RECORD_THREAD_NAME); - while(GetMessage(&msg, NULL, 0, 0)) + if (!self->killNow) while(GetMessage(&msg, NULL, 0, 0)) { if(msg.message != WIM_DATA) continue; tdesktop-1.2.17/Telegram/Patches/qtbase_5_6_2.diff000066400000000000000000001755411326245125100216070ustar00rootroot00000000000000diff --git a/mkspecs/common/msvc-desktop.conf b/mkspecs/common/msvc-desktop.conf index eec9e1f688..7ae53c7a1e 100644 --- a/mkspecs/common/msvc-desktop.conf +++ b/mkspecs/common/msvc-desktop.conf @@ -30,9 +30,10 @@ QMAKE_YACCFLAGS = -d QMAKE_CFLAGS = -nologo -Zc:wchar_t QMAKE_CFLAGS_WARN_ON = -W3 QMAKE_CFLAGS_WARN_OFF = -W0 -QMAKE_CFLAGS_RELEASE = -O2 -MD -QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO += -O2 -MD -Zi -QMAKE_CFLAGS_DEBUG = -Zi -MDd +# Patch: Make this build use static runtime library. +QMAKE_CFLAGS_RELEASE = -O2 -MT +QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO += -O2 -MT -Zi +QMAKE_CFLAGS_DEBUG = -Zi -MTd QMAKE_CFLAGS_YACC = QMAKE_CFLAGS_LTCG = -GL QMAKE_CFLAGS_SSE2 = -arch:SSE2 diff --git a/src/corelib/io/qfsfileengine_win.cpp b/src/corelib/io/qfsfileengine_win.cpp index 391fbcc519..d07802bb7a 100644 --- a/src/corelib/io/qfsfileengine_win.cpp +++ b/src/corelib/io/qfsfileengine_win.cpp @@ -427,11 +427,12 @@ qint64 QFSFileEnginePrivate::nativeWrite(const char *data, qint64 len) // Writing on Windows fails with ERROR_NO_SYSTEM_RESOURCES when // the chunks are too large, so we limit the block size to 32MB. - const DWORD blockSize = DWORD(qMin(bytesToWrite, qint64(32 * 1024 * 1024))); qint64 totalWritten = 0; do { + // Patch: backport critical bugfix from '683c9bc4a8' commit. + const DWORD currentBlockSize = DWORD(qMin(bytesToWrite, qint64(32 * 1024 * 1024))); DWORD bytesWritten; - if (!WriteFile(fileHandle, data + totalWritten, blockSize, &bytesWritten, NULL)) { + if (!WriteFile(fileHandle, data + totalWritten, currentBlockSize, &bytesWritten, NULL)) { if (totalWritten == 0) { // Note: Only return error if the first WriteFile failed. q->setError(QFile::WriteError, qt_error_string()); diff --git a/src/corelib/tools/qunicodetables.cpp b/src/corelib/tools/qunicodetables.cpp index 14e4fd10aa..0619a176a7 100644 --- a/src/corelib/tools/qunicodetables.cpp +++ b/src/corelib/tools/qunicodetables.cpp @@ -6227,7 +6227,8 @@ static const Properties uc_properties[] = { { 1, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 7, 4, 4, 21, 11 }, { 0, 17, 230, 5, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 4, 4, 4, 21, 11 }, { 18, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 85, 0, 8, 8, 12, 11 }, - { 25, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 12, 17, 2 }, + // Patch: Some bad characters appeared in ui in case 2 was here instead of 11. + { 25, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 12, 17, 11 }, { 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 14, 9, 11, 11 }, { 3, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 14, 9, 11, 11 }, { 3, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 14, 9, 11, 11 }, diff --git a/src/gui/kernel/qhighdpiscaling.cpp b/src/gui/kernel/qhighdpiscaling.cpp index 2d00b9dce9..eeba86e936 100644 --- a/src/gui/kernel/qhighdpiscaling.cpp +++ b/src/gui/kernel/qhighdpiscaling.cpp @@ -51,6 +51,9 @@ static const char screenFactorsEnvVar[] = "QT_SCREEN_SCALE_FACTORS"; static inline qreal initialGlobalScaleFactor() { + // Patch: Disable environment variable dpi scaling changing. + // It is not supported by Telegram Desktop :( + return 1.; qreal result = 1; if (qEnvironmentVariableIsSet(scaleFactorEnvVar)) { diff --git a/src/gui/kernel/qplatformdialoghelper.h b/src/gui/kernel/qplatformdialoghelper.h index 5b2f4ece77..790db46d25 100644 --- a/src/gui/kernel/qplatformdialoghelper.h +++ b/src/gui/kernel/qplatformdialoghelper.h @@ -386,6 +386,10 @@ public: virtual QUrl directory() const = 0; virtual void selectFile(const QUrl &filename) = 0; virtual QList selectedFiles() const = 0; + + // Patch: Adding select-by-url for Windows file dialog. + virtual QByteArray selectedRemoteContent() const { return QByteArray(); } + virtual void setFilter() = 0; virtual void selectNameFilter(const QString &filter) = 0; virtual QString selectedNameFilter() const = 0; diff --git a/src/gui/kernel/qwindow.cpp b/src/gui/kernel/qwindow.cpp index bcd29b6fe1..bcb0672f69 100644 --- a/src/gui/kernel/qwindow.cpp +++ b/src/gui/kernel/qwindow.cpp @@ -2525,7 +2525,8 @@ void QWindowPrivate::setCursor(const QCursor *newCursor) void QWindowPrivate::applyCursor() { Q_Q(QWindow); - if (platformWindow) { + // Patch: Fixing possible crash (crashdumps point on this code line). + if (platformWindow && q->screen() && q->screen()->handle()) { if (QPlatformCursor *platformCursor = q->screen()->handle()->cursor()) { QCursor *c = QGuiApplication::overrideCursor(); if (!c && hasCursor) diff --git a/src/gui/painting/qpaintengine_p.h b/src/gui/painting/qpaintengine_p.h index 918c98997b..4158259743 100644 --- a/src/gui/painting/qpaintengine_p.h +++ b/src/gui/painting/qpaintengine_p.h @@ -80,8 +80,18 @@ public: if (hasSystemTransform) { if (systemTransform.type() <= QTransform::TxTranslate) systemClip.translate(qRound(systemTransform.dx()), qRound(systemTransform.dy())); - else + // Patch: Transform the system clip region back from device pixels to device-independent pixels before + // applying systemTransform, which already has transform from device-independent pixels to device pixels. + else { +#ifdef Q_OS_MAC + QTransform scaleTransform; + const qreal invDevicePixelRatio = 1. / pdev->devicePixelRatio(); + scaleTransform.scale(invDevicePixelRatio, invDevicePixelRatio); + systemClip = systemTransform.map(scaleTransform.map(systemClip)); +#else systemClip = systemTransform.map(systemClip); +#endif + } } // Make sure we're inside the viewport. diff --git a/src/gui/text/qtextengine_p.h b/src/gui/text/qtextengine_p.h index 7e507bba2d..936e7a92cb 100644 --- a/src/gui/text/qtextengine_p.h +++ b/src/gui/text/qtextengine_p.h @@ -283,7 +283,8 @@ private: struct QScriptItem; /// Internal QTextItem -class QTextItemInt : public QTextItem +// Patch: Enable access to QTextItemInt in .dll for WinRT build. +class Q_GUI_EXPORT QTextItemInt : public QTextItem { public: inline QTextItemInt() diff --git a/src/gui/text/qtextlayout.cpp b/src/gui/text/qtextlayout.cpp index aca475a581..5fa0be2c45 100644 --- a/src/gui/text/qtextlayout.cpp +++ b/src/gui/text/qtextlayout.cpp @@ -694,6 +694,9 @@ int QTextLayout::nextCursorPosition(int oldPos, CursorMode mode) const while (oldPos < len && !attributes[oldPos].graphemeBoundary) oldPos++; } else { + // Patch: Skip to the end of the current word, not to the start of the next one. + while (oldPos < len && attributes[oldPos].whiteSpace) + oldPos++; if (oldPos < len && d->atWordSeparator(oldPos)) { oldPos++; while (oldPos < len && d->atWordSeparator(oldPos)) @@ -702,8 +705,9 @@ int QTextLayout::nextCursorPosition(int oldPos, CursorMode mode) const while (oldPos < len && !attributes[oldPos].whiteSpace && !d->atWordSeparator(oldPos)) oldPos++; } - while (oldPos < len && attributes[oldPos].whiteSpace) - oldPos++; + // Patch: Skip to the end of the current word, not to the start of the next one. + //while (oldPos < len && attributes[oldPos].whiteSpace) + // oldPos++; } return oldPos; @@ -1645,6 +1649,9 @@ namespace { int currentPosition; glyph_t previousGlyph; + // Patch: Fix a crash in right bearing calculation. + QFontEngine *previousGlyphFontEngine; + QFixed minw; QFixed softHyphenWidth; QFixed rightBearing; @@ -1677,13 +1684,19 @@ namespace { if (currentPosition > 0 && logClusters[currentPosition - 1] < glyphs.numGlyphs) { previousGlyph = currentGlyph(); // needed to calculate right bearing later + + // Patch: Fix a crash in right bearing calculation. + previousGlyphFontEngine = fontEngine; } } - inline void calculateRightBearing(glyph_t glyph) + // Patch: Fix a crash in right bearing calculation. + inline void calculateRightBearing(QFontEngine *engine, glyph_t glyph) { qreal rb; - fontEngine->getGlyphBearings(glyph, 0, &rb); + + // Patch: Fix a crash in right bearing calculation. + engine->getGlyphBearings(glyph, 0, &rb); // We only care about negative right bearings, so we limit the range // of the bearing here so that we can assume it's negative in the rest @@ -1696,13 +1709,16 @@ namespace { { if (currentPosition <= 0) return; - calculateRightBearing(currentGlyph()); + + // Patch: Fix a crash in right bearing calculation. + calculateRightBearing(fontEngine, currentGlyph()); } inline void calculateRightBearingForPreviousGlyph() { if (previousGlyph > 0) - calculateRightBearing(previousGlyph); + // Patch: Fix a crash in right bearing calculation. + calculateRightBearing(previousGlyphFontEngine, previousGlyph); } static const QFixed RightBearingNotCalculated; diff --git a/src/gui/text/qtextlayout.h b/src/gui/text/qtextlayout.h index f74d4d4229..8ad672c9fe 100644 --- a/src/gui/text/qtextlayout.h +++ b/src/gui/text/qtextlayout.h @@ -196,6 +196,9 @@ private: QRectF *brect, int tabstops, int* tabarray, int tabarraylen, QPainter *painter); QTextEngine *d; + + // Patch: Allow access to private constructor. + friend class TextBlock; }; diff --git a/src/network/socket/qnativesocketengine_win.cpp b/src/network/socket/qnativesocketengine_win.cpp index 41834b21ae..8cdf4ab145 100644 --- a/src/network/socket/qnativesocketengine_win.cpp +++ b/src/network/socket/qnativesocketengine_win.cpp @@ -675,6 +675,13 @@ bool QNativeSocketEnginePrivate::nativeConnect(const QHostAddress &address, quin errorDetected = true; break; } + // Patch: Handle network unreachable the same as host unreachable. + if (value == WSAENETUNREACH) { + setError(QAbstractSocket::NetworkError, NetworkUnreachableErrorString); + socketState = QAbstractSocket::UnconnectedState; + errorDetected = true; + break; + } if (value == WSAEADDRNOTAVAIL) { setError(QAbstractSocket::NetworkError, AddressNotAvailableErrorString); socketState = QAbstractSocket::UnconnectedState; diff --git a/src/platformsupport/fontdatabases/basic/qbasicfontdatabase.cpp b/src/platformsupport/fontdatabases/basic/qbasicfontdatabase.cpp index 728b166b71..1dc64593e1 100644 --- a/src/platformsupport/fontdatabases/basic/qbasicfontdatabase.cpp +++ b/src/platformsupport/fontdatabases/basic/qbasicfontdatabase.cpp @@ -172,6 +172,79 @@ void QBasicFontDatabase::releaseHandle(void *handle) extern FT_Library qt_getFreetype(); +// Patch: Enable Open Sans Semibold font family reading. +// Copied from freetype with some modifications. + +#ifndef FT_PARAM_TAG_IGNORE_PREFERRED_FAMILY +#define FT_PARAM_TAG_IGNORE_PREFERRED_FAMILY FT_MAKE_TAG('i', 'g', 'p', 'f') +#endif + +#ifndef FT_PARAM_TAG_IGNORE_PREFERRED_SUBFAMILY +#define FT_PARAM_TAG_IGNORE_PREFERRED_SUBFAMILY FT_MAKE_TAG('i', 'g', 'p', 's') +#endif + +/* there's a Mac-specific extended implementation of FT_New_Face() */ +/* in src/base/ftmac.c */ + +#if !defined( FT_MACINTOSH ) || defined( DARWIN_NO_CARBON ) + +/* documentation is in freetype.h */ + +FT_Error __ft_New_Face(FT_Library library, const char* pathname, FT_Long face_index, FT_Face *aface) { + FT_Open_Args args; + + /* test for valid `library' and `aface' delayed to FT_Open_Face() */ + if (!pathname) + return FT_Err_Invalid_Argument; + + FT_Parameter params[2]; + params[0].tag = FT_PARAM_TAG_IGNORE_PREFERRED_FAMILY; + params[0].data = 0; + params[1].tag = FT_PARAM_TAG_IGNORE_PREFERRED_SUBFAMILY; + params[1].data = 0; + args.flags = FT_OPEN_PATHNAME | FT_OPEN_PARAMS; + args.pathname = (char*)pathname; + args.stream = NULL; + args.num_params = 2; + args.params = params; + + return FT_Open_Face(library, &args, face_index, aface); +} + +#else + +FT_Error __ft_New_Face(FT_Library library, const char* pathname, FT_Long face_index, FT_Face *aface) { + return FT_New_Face(library, pathname, face_index, aface); +} + +#endif /* defined( FT_MACINTOSH ) && !defined( DARWIN_NO_CARBON ) */ + +/* documentation is in freetype.h */ + +FT_Error __ft_New_Memory_Face(FT_Library library, const FT_Byte* file_base, FT_Long file_size, FT_Long face_index, FT_Face *aface) { + FT_Open_Args args; + + /* test for valid `library' and `face' delayed to FT_Open_Face() */ + if (!file_base) + return FT_Err_Invalid_Argument; + + FT_Parameter params[2]; + params[0].tag = FT_PARAM_TAG_IGNORE_PREFERRED_FAMILY; + params[0].data = 0; + params[1].tag = FT_PARAM_TAG_IGNORE_PREFERRED_SUBFAMILY; + params[1].data = 0; + args.flags = FT_OPEN_MEMORY | FT_OPEN_PARAMS; + args.memory_base = file_base; + args.memory_size = file_size; + args.stream = NULL; + args.num_params = 2; + args.params = params; + + return FT_Open_Face(library, &args, face_index, aface); +} + +// end + QStringList QBasicFontDatabase::addTTFile(const QByteArray &fontData, const QByteArray &file) { FT_Library library = qt_getFreetype(); @@ -183,9 +256,11 @@ QStringList QBasicFontDatabase::addTTFile(const QByteArray &fontData, const QByt FT_Face face; FT_Error error; if (!fontData.isEmpty()) { - error = FT_New_Memory_Face(library, (const FT_Byte *)fontData.constData(), fontData.size(), index, &face); + // Patch: Enable Open Sans Semibold font family reading. + error = __ft_New_Memory_Face(library, (const FT_Byte *)fontData.constData(), fontData.size(), index, &face); } else { - error = FT_New_Face(library, file.constData(), index, &face); + // Patch: Enable Open Sans Semibold font family reading. + error = __ft_New_Face(library, file.constData(), index, &face); } if (error != FT_Err_Ok) { qDebug() << "FT_New_Face failed with index" << index << ':' << hex << error; diff --git a/src/platformsupport/fontdatabases/fontconfig/qfontconfigdatabase.cpp b/src/platformsupport/fontdatabases/fontconfig/qfontconfigdatabase.cpp index 8ebabf3419..7bb8abd0d0 100644 --- a/src/platformsupport/fontdatabases/fontconfig/qfontconfigdatabase.cpp +++ b/src/platformsupport/fontdatabases/fontconfig/qfontconfigdatabase.cpp @@ -375,6 +375,17 @@ static void populateFromPattern(FcPattern *pattern) familyName = QString::fromUtf8((const char *)value); + // Patch: Enable Open Sans Semibold font family reading. + if (familyName == QLatin1String("Open Sans")) { + FcChar8 *styl = 0; + if (FcPatternGetString(pattern, FC_STYLE, 0, &styl) == FcResultMatch) { + QString style = QString::fromUtf8(reinterpret_cast(styl)); + if (style == QLatin1String("Semibold")) { + familyName.append(QChar(QChar::Space)).append(style); + } + } + } + slant_value = FC_SLANT_ROMAN; weight_value = FC_WEIGHT_REGULAR; spacing_value = FC_PROPORTIONAL; @@ -718,7 +729,19 @@ QStringList QFontconfigDatabase::fallbacksForFamily(const QString &family, QFont if (FcPatternGetString(fontSet->fonts[i], FC_FAMILY, 0, &value) != FcResultMatch) continue; // capitalize(value); - const QString familyName = QString::fromUtf8((const char *)value); + + // Patch: Enable Open Sans Semibold font family reading. + QString familyName = QString::fromUtf8((const char *)value); + if (familyName == QLatin1String("Open Sans")) { + FcChar8 *styl = 0; + if (FcPatternGetString(fontSet->fonts[i], FC_STYLE, 0, &styl) == FcResultMatch) { + QString style = QString::fromUtf8(reinterpret_cast(styl)); + if (style == QLatin1String("Semibold")) { + familyName.append(QChar(QChar::Space)).append(style); + } + } + } + const QString familyNameCF = familyName.toCaseFolded(); if (!duplicates.contains(familyNameCF)) { fallbackFamilies << familyName; @@ -784,6 +807,18 @@ QStringList QFontconfigDatabase::addApplicationFont(const QByteArray &fontData, FcChar8 *fam = 0; if (FcPatternGetString(pattern, FC_FAMILY, 0, &fam) == FcResultMatch) { QString family = QString::fromUtf8(reinterpret_cast(fam)); + + // Patch: Enable Open Sans Semibold font family reading. + if (family == QLatin1String("Open Sans")) { + FcChar8 *styl = 0; + if (FcPatternGetString(pattern, FC_STYLE, 0, &styl) == FcResultMatch) { + QString style = QString::fromUtf8(reinterpret_cast(styl)); + if (style == QLatin1String("Semibold")) { + family.append(QChar(QChar::Space)).append(style); + } + } + } + families << family; } populateFromPattern(pattern); diff --git a/src/platformsupport/fontdatabases/mac/qcoretextfontdatabase.mm b/src/platformsupport/fontdatabases/mac/qcoretextfontdatabase.mm index 566abf2126..5b9c714ffa 100644 --- a/src/platformsupport/fontdatabases/mac/qcoretextfontdatabase.mm +++ b/src/platformsupport/fontdatabases/mac/qcoretextfontdatabase.mm @@ -265,6 +265,13 @@ static void getFontDescription(CTFontDescriptorRef font, FontDescription *fd) fd->foundryName = QStringLiteral("CoreText"); fd->familyName = (CFStringRef) CTFontDescriptorCopyAttribute(font, kCTFontFamilyNameAttribute); + + // Patch: Enable Open Sans Semibold font family reading. + QCFString _displayName = (CFStringRef) CTFontDescriptorCopyAttribute(font, kCTFontDisplayNameAttribute); + if (_displayName == QStringLiteral("Open Sans Semibold")) { + fd->familyName = _displayName; + } + fd->styleName = (CFStringRef)CTFontDescriptorCopyAttribute(font, kCTFontStyleNameAttribute); fd->weight = QFont::Normal; fd->style = QFont::StyleNormal; diff --git a/src/plugins/platforminputcontexts/compose/compose.pro b/src/plugins/platforminputcontexts/compose/compose.pro index 86bdd4729b..9b9c8ded08 100644 --- a/src/plugins/platforminputcontexts/compose/compose.pro +++ b/src/plugins/platforminputcontexts/compose/compose.pro @@ -15,7 +15,8 @@ HEADERS += $$PWD/qcomposeplatforminputcontext.h \ contains(QT_CONFIG, xkbcommon-qt): { # dont't need x11 dependency for compose key plugin QT_CONFIG -= use-xkbcommon-x11support - include(../../../3rdparty/xkbcommon.pri) + # Patch: Adding fcitx input context plugin to our static build. + #include(../../../3rdparty/xkbcommon.pri) } else { LIBS += $$QMAKE_LIBS_XKBCOMMON QMAKE_CXXFLAGS += $$QMAKE_CFLAGS_XKBCOMMON diff --git a/src/plugins/platforminputcontexts/compose/qcomposeplatforminputcontext.cpp b/src/plugins/platforminputcontexts/compose/qcomposeplatforminputcontext.cpp index d1bea9af23..36a15a6473 100644 --- a/src/plugins/platforminputcontexts/compose/qcomposeplatforminputcontext.cpp +++ b/src/plugins/platforminputcontexts/compose/qcomposeplatforminputcontext.cpp @@ -232,6 +232,12 @@ bool QComposeInputContext::checkComposeTable() void QComposeInputContext::commitText(uint character) const { + // Patch: Crash fix when not focused widget still receives input events. + if (!m_focusObject) { + qWarning("QComposeInputContext::commitText: m_focusObject == nullptr, cannot commit text"); + return; + } + QInputMethodEvent event; event.setCommitString(QChar(character)); QCoreApplication::sendEvent(m_focusObject, &event); diff --git a/src/plugins/platforminputcontexts/platforminputcontexts.pro b/src/plugins/platforminputcontexts/platforminputcontexts.pro index faea54b874..0f9650996e 100644 --- a/src/plugins/platforminputcontexts/platforminputcontexts.pro +++ b/src/plugins/platforminputcontexts/platforminputcontexts.pro @@ -1,7 +1,8 @@ TEMPLATE = subdirs qtHaveModule(dbus) { -!mac:!win32:SUBDIRS += ibus +# Patch: Adding fcitx/hime input context plugin to our static build. +!mac:!win32:SUBDIRS += ibus fcitx hime } contains(QT_CONFIG, xcb-plugin): SUBDIRS += compose diff --git a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm index caa8884661..9dc3bc1661 100644 --- a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm +++ b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm @@ -210,7 +210,8 @@ QT_END_NAMESPACE if (reflectionDelegate) { if ([reflectionDelegate respondsToSelector:@selector(applicationShouldTerminate:)]) return [reflectionDelegate applicationShouldTerminate:sender]; - return NSTerminateNow; + // Patch: Don't terminate if reflectionDelegate does not respond to that selector, just use the default. + //return NSTerminateNow; } if ([self canQuit]) { @@ -287,11 +288,15 @@ QT_END_NAMESPACE - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { + // Patch: We need to catch that notification in delegate. + if (reflectionDelegate + && [reflectionDelegate respondsToSelector:@selector(applicationDidFinishLaunching:)]) + [reflectionDelegate applicationDidFinishLaunching:aNotification]; + Q_UNUSED(aNotification); inLaunch = false; // qt_release_apple_event_handler(); - // Insert code here to initialize your application } diff --git a/src/plugins/platforms/cocoa/qcocoabackingstore.h b/src/plugins/platforms/cocoa/qcocoabackingstore.h index 934f68ad18..3ece6984ac 100644 --- a/src/plugins/platforms/cocoa/qcocoabackingstore.h +++ b/src/plugins/platforms/cocoa/qcocoabackingstore.h @@ -64,6 +64,9 @@ public: private: QImage m_qImage; QSize m_requestedSize; + + // Patch: Optimize redraw - don't clear image if it will be fully redrawn. + bool m_qImageNeedsClear; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoabackingstore.mm b/src/plugins/platforms/cocoa/qcocoabackingstore.mm index ca92103826..f27ea15bad 100644 --- a/src/plugins/platforms/cocoa/qcocoabackingstore.mm +++ b/src/plugins/platforms/cocoa/qcocoabackingstore.mm @@ -38,7 +38,8 @@ QT_BEGIN_NAMESPACE QCocoaBackingStore::QCocoaBackingStore(QWindow *window) - : QPlatformBackingStore(window) + // Patch: Optimize redraw - don't clear image if it will be fully redrawn. + : QPlatformBackingStore(window), m_qImageNeedsClear(false) { } @@ -59,9 +60,12 @@ QPaintDevice *QCocoaBackingStore::paintDevice() if (m_qImage.size() != effectiveBufferSize) { QImage::Format format = (window()->format().hasAlpha() || cocoaWindow->m_drawContentBorderGradient) ? QImage::Format_ARGB32_Premultiplied : QImage::Format_RGB32; + + // Patch: Optimize redraw - don't clear image if it will be fully redrawn. + m_qImageNeedsClear = window()->requestedFormat().hasAlpha() || cocoaWindow->m_drawContentBorderGradient; m_qImage = QImage(effectiveBufferSize, format); m_qImage.setDevicePixelRatio(windowDevicePixelRatio); - if (format == QImage::Format_ARGB32_Premultiplied) + if (m_qImageNeedsClear) m_qImage.fill(Qt::transparent); } return &m_qImage; @@ -100,7 +104,8 @@ bool QCocoaBackingStore::scroll(const QRegion &area, int dx, int dy) void QCocoaBackingStore::beginPaint(const QRegion ®ion) { - if (m_qImage.hasAlphaChannel()) { + // Patch: Optimize redraw - don't clear image if it will be fully redrawn. + if (m_qImageNeedsClear && m_qImage.hasAlphaChannel()) { QPainter p(&m_qImage); p.setCompositionMode(QPainter::CompositionMode_Source); const QVector rects = region.rects(); diff --git a/src/plugins/platforms/cocoa/qcocoahelpers.mm b/src/plugins/platforms/cocoa/qcocoahelpers.mm index 058209da7e..6af61e7dab 100644 --- a/src/plugins/platforms/cocoa/qcocoahelpers.mm +++ b/src/plugins/platforms/cocoa/qcocoahelpers.mm @@ -546,9 +546,9 @@ OSStatus qt_mac_drawCGImage(CGContextRef inContext, const CGRect *inBounds, CGIm // Verbatim copy if HIViewDrawCGImage (as shown on Carbon-Dev) OSStatus err = noErr; - require_action(inContext != NULL, InvalidContext, err = paramErr); - require_action(inBounds != NULL, InvalidBounds, err = paramErr); - require_action(inImage != NULL, InvalidImage, err = paramErr); +// require_action(inContext != NULL, InvalidContext, err = paramErr); +// require_action(inBounds != NULL, InvalidBounds, err = paramErr); +// require_action(inImage != NULL, InvalidImage, err = paramErr); CGContextSaveGState( inContext ); CGContextTranslateCTM (inContext, 0, inBounds->origin.y + CGRectGetMaxY(*inBounds)); @@ -557,9 +557,9 @@ OSStatus qt_mac_drawCGImage(CGContextRef inContext, const CGRect *inBounds, CGIm CGContextDrawImage(inContext, *inBounds, inImage); CGContextRestoreGState(inContext); -InvalidImage: -InvalidBounds: -InvalidContext: +//InvalidImage: +//InvalidBounds: +//InvalidContext: return err; } diff --git a/src/plugins/platforms/cocoa/qcocoakeymapper.mm b/src/plugins/platforms/cocoa/qcocoakeymapper.mm index c2d206fb45..9b9739862d 100644 --- a/src/plugins/platforms/cocoa/qcocoakeymapper.mm +++ b/src/plugins/platforms/cocoa/qcocoakeymapper.mm @@ -384,6 +384,12 @@ bool QCocoaKeyMapper::updateKeyboard() keyboardInputLocale = QLocale::c(); keyboardInputDirection = Qt::LeftToRight; } + + // Patch: Fix layout-independent global shortcuts. + const auto newMode = keyboard_mode; + deleteLayouts(); + keyboard_mode = newMode; + return true; } @@ -466,7 +472,8 @@ QList QCocoaKeyMapper::possibleKeys(const QKeyEvent *event) const Qt::KeyboardModifiers neededMods = ModsTbl[i]; int key = kbItem->qtKey[i]; if (key && key != baseKey && ((keyMods & neededMods) == neededMods)) { - ret << int(key + (keyMods & ~neededMods)); + // Patch: Fix layout-independent global shortcuts. + ret << int(key + neededMods); } } return ret; diff --git a/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm index 8152c57ffd..5ddd7b353d 100644 --- a/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm +++ b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm @@ -94,6 +94,8 @@ QT_USE_NAMESPACE QCocoaSystemTrayIcon *systray; NSStatusItem *item; QCocoaMenu *menu; + // Patch: Create a rich os x tray icon (pixel-perfect, theme switching). + bool menuVisible, iconSelected; QIcon icon; QT_MANGLE_NAMESPACE(QNSImageView) *imageCell; } @@ -197,7 +199,9 @@ void QCocoaSystemTrayIcon::updateIcon(const QIcon &icon) // (device independent pixels). The menu height on past and // current OS X versions is 22 points. Provide some future-proofing // by deriving the icon height from the menu height. - const int padding = 4; + + // Patch: Create a rich os x tray icon (pixel-perfect, theme switching). + const int padding = 0; const int menuHeight = [[NSStatusBar systemStatusBar] thickness]; const int maxImageHeight = menuHeight - padding; @@ -207,8 +211,11 @@ void QCocoaSystemTrayIcon::updateIcon(const QIcon &icon) // devicePixelRatio for the "best" screen on the system. qreal devicePixelRatio = qApp->devicePixelRatio(); const int maxPixmapHeight = maxImageHeight * devicePixelRatio; + + // Patch: Create a rich os x tray icon (pixel-perfect, theme switching). + const QIcon::Mode mode = m_sys->item->iconSelected ? QIcon::Selected : QIcon::Normal; QSize selectedSize; - Q_FOREACH (const QSize& size, sortByHeight(icon.availableSizes())) { + Q_FOREACH (const QSize& size, sortByHeight(icon.availableSizes(mode))) { // Select a pixmap based on the height. We want the largest pixmap // with a height smaller or equal to maxPixmapHeight. The pixmap // may rectangular; assume it has a reasonable size. If there is @@ -224,9 +231,9 @@ void QCocoaSystemTrayIcon::updateIcon(const QIcon &icon) // Handle SVG icons, which do not return anything for availableSizes(). if (!selectedSize.isValid()) - selectedSize = icon.actualSize(QSize(maxPixmapHeight, maxPixmapHeight)); + selectedSize = icon.actualSize(QSize(maxPixmapHeight, maxPixmapHeight), mode); - QPixmap pixmap = icon.pixmap(selectedSize); + QPixmap pixmap = icon.pixmap(selectedSize, mode); // Draw a low-resolution icon if there is not enough pixels for a retina // icon. This prevents showing a small icon on retina displays. @@ -374,6 +381,11 @@ QT_END_NAMESPACE Q_UNUSED(notification); down = NO; + // Patch: Create a rich os x tray icon (pixel-perfect, theme switching). + parent->iconSelected = false; + parent->systray->updateIcon(parent->icon); + parent->menuVisible = false; + [self setNeedsDisplay:YES]; } @@ -383,6 +395,10 @@ QT_END_NAMESPACE int clickCount = [mouseEvent clickCount]; [self setNeedsDisplay:YES]; + // Patch: Create a rich os x tray icon (pixel-perfect, theme switching). + parent->iconSelected = (clickCount != 2) && parent->menu; + parent->systray->updateIcon(parent->icon); + if (clickCount == 2) { [self menuTrackingDone:nil]; [parent doubleClickSelector:self]; @@ -399,6 +415,11 @@ QT_END_NAMESPACE -(void)mouseUp:(NSEvent *)mouseEvent { Q_UNUSED(mouseEvent); + + // Patch: Create a rich os x tray icon (pixel-perfect, theme switching). + parent->iconSelected = false; + parent->systray->updateIcon(parent->icon); + [self menuTrackingDone:nil]; } @@ -410,6 +431,11 @@ QT_END_NAMESPACE -(void)rightMouseUp:(NSEvent *)mouseEvent { Q_UNUSED(mouseEvent); + + // Patch: Create a rich os x tray icon (pixel-perfect, theme switching). + parent->iconSelected = false; + parent->systray->updateIcon(parent->icon); + [self menuTrackingDone:nil]; } @@ -425,7 +451,8 @@ QT_END_NAMESPACE } -(void)drawRect:(NSRect)rect { - [[parent item] drawStatusBarBackgroundInRect:rect withHighlight:down]; + // Patch: Create a rich os x tray icon (pixel-perfect, theme switching). + [[parent item] drawStatusBarBackgroundInRect:rect withHighlight:parent->menu ? down : NO]; [super drawRect:rect]; } @end @@ -438,6 +465,10 @@ QT_END_NAMESPACE if (self) { item = [[[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength] retain]; menu = 0; + + // Patch: Create a rich os x tray icon (pixel-perfect, theme switching). + menuVisible = false; + systray = sys; imageCell = [[QNSImageView alloc] initWithParent:self]; [item setView: imageCell]; @@ -482,6 +513,10 @@ QT_END_NAMESPACE selector:@selector(menuTrackingDone:) name:NSMenuDidEndTrackingNotification object:m]; + + // Patch: Create a rich os x tray icon (pixel-perfect, theme switching). + menuVisible = true; + [item popUpStatusItemMenu: m]; } } diff --git a/src/plugins/platforms/cocoa/qcocoawindow.mm b/src/plugins/platforms/cocoa/qcocoawindow.mm index c0d5904367..f3c2047196 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.mm +++ b/src/plugins/platforms/cocoa/qcocoawindow.mm @@ -141,7 +141,8 @@ static bool isMouseEvent(NSEvent *ev) if (!self.window.delegate) return; // Already detached, pending NSAppKitDefined event - if (pw && pw->frameStrutEventsEnabled() && isMouseEvent(theEvent)) { + // Patch: Fix restore after minimize or close by window buttons. + if (pw && pw->frameStrutEventsEnabled() && pw->m_synchedWindowState != Qt::WindowMinimized && pw->m_isExposed && isMouseEvent(theEvent)) { NSPoint loc = [theEvent locationInWindow]; NSRect windowFrame = [self.window convertRectFromScreen:[self.window frame]]; NSRect contentFrame = [[self.window contentView] frame]; @@ -811,6 +812,16 @@ NSUInteger QCocoaWindow::windowStyleMask(Qt::WindowFlags flags) { Qt::WindowType type = static_cast(int(flags & Qt::WindowType_Mask)); NSInteger styleMask = NSBorderlessWindowMask; + + // Patch: allow creating panels floating on all spaces in macOS. + // If you call "setCollectionBehavior:NSWindowCollectionBehaviorFullScreenAuxiliary" before + // setting the "NSNonactivatingPanelMask" bit in the style mask it won't work after that. + // So we need a way to set that bit before Qt sets collection behavior the way it does. + QVariant nonactivatingPanelMask = window()->property("_td_macNonactivatingPanelMask"); + if (nonactivatingPanelMask.isValid() && nonactivatingPanelMask.toBool()) { + styleMask |= NSNonactivatingPanelMask; + } + if (flags & Qt::FramelessWindowHint) return styleMask; if ((type & Qt::Popup) == Qt::Popup) { @@ -943,6 +954,19 @@ void QCocoaWindow::setWindowFilePath(const QString &filePath) [m_nsWindow setRepresentedFilename: fi.exists() ? QCFString::toNSString(filePath) : @""]; } +// Patch: Create a good os x window icon (pixel-perfect). +namespace { + +qreal getDevicePixelRatio() { + qreal result = 1.0; + foreach (QScreen *screen, QGuiApplication::screens()) { + result = qMax(result, screen->devicePixelRatio()); + } + return result; +} + +} // namespace + void QCocoaWindow::setWindowIcon(const QIcon &icon) { QMacAutoReleasePool pool; @@ -958,7 +982,9 @@ void QCocoaWindow::setWindowIcon(const QIcon &icon) if (icon.isNull()) { [iconButton setImage:nil]; } else { - QPixmap pixmap = icon.pixmap(QSize(22, 22)); + // Patch: Create a good os x window icon (pixel-perfect). + CGFloat hgt = 16. * getDevicePixelRatio(); + QPixmap pixmap = icon.pixmap(QSize(hgt, hgt)); NSImage *image = static_cast(qt_mac_create_nsimage(pixmap)); [iconButton setImage:image]; [image release]; diff --git a/src/plugins/platforms/cocoa/qnsview.mm b/src/plugins/platforms/cocoa/qnsview.mm index c67bcfd23b..2616f420cb 100644 --- a/src/plugins/platforms/cocoa/qnsview.mm +++ b/src/plugins/platforms/cocoa/qnsview.mm @@ -647,6 +647,12 @@ QT_WARNING_POP [self invalidateWindowShadowIfNeeded]; } +- (void)viewDidChangeBackingProperties +{ + if (self.layer) + self.layer.contentsScale = self.window.backingScaleFactor; +} + - (BOOL) isFlipped { return YES; @@ -1431,7 +1437,9 @@ static QTabletEvent::TabletDevice wacomTabletDevice(NSEvent *theEvent) #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8 if (QSysInfo::QSysInfo::MacintoshVersion >= QSysInfo::MV_10_8) { // On 10.8 and above, MayBegin is likely to happen. We treat it the same as an actual begin. - if (phase == NSEventPhaseMayBegin) { + + // Patch: Fix actual begin handle of swipe on trackpad. + if (phase == NSEventPhaseMayBegin || phase == NSEventPhaseBegan) { m_scrolling = true; ph = Qt::ScrollBegin; } @@ -1496,14 +1504,14 @@ static QTabletEvent::TabletDevice wacomTabletDevice(NSEvent *theEvent) quint32 nativeVirtualKey = [nsevent keyCode]; QChar ch = QChar::ReplacementCharacter; - int keyCode = Qt::Key_unknown; - if ([characters length] != 0) { - if (((modifiers & Qt::MetaModifier) || (modifiers & Qt::AltModifier)) && ([charactersIgnoringModifiers length] != 0)) - ch = QChar([charactersIgnoringModifiers characterAtIndex:0]); - else - ch = QChar([characters characterAtIndex:0]); - keyCode = [self convertKeyCode:ch]; - } + + // Patch: Fix Alt+.. shortcuts in OS X. See https://bugreports.qt.io/browse/QTBUG-42584 at the end. + if ([characters length] != 0) + ch = QChar([characters characterAtIndex:0]); + else if ([charactersIgnoringModifiers length] != 0 && ((modifiers & Qt::MetaModifier) || (modifiers & Qt::AltModifier))) + ch = QChar([charactersIgnoringModifiers characterAtIndex:0]); + + int keyCode = [self convertKeyCode:ch]; // we will send a key event unless the input method sets m_sendKeyEvent to false m_sendKeyEvent = true; @@ -1569,6 +1577,23 @@ static QTabletEvent::TabletDevice wacomTabletDevice(NSEvent *theEvent) [self handleKeyEvent:nsevent eventType:int(QEvent::KeyRelease)]; } +// Patch: Enable Ctrl+Tab and Ctrl+Shift+Tab / Ctrl+Backtab handle in-app. +- (BOOL)performKeyEquivalent:(NSEvent *)nsevent +{ + NSString *chars = [nsevent charactersIgnoringModifiers]; + + if ([nsevent type] == NSKeyDown && [chars length] > 0) { + QChar ch = [chars characterAtIndex:0]; + Qt::Key qtKey = qt_mac_cocoaKey2QtKey(ch); + if ([nsevent modifierFlags] & NSControlKeyMask + && (qtKey == Qt::Key_Tab || qtKey == Qt::Key_Backtab)) { + [self handleKeyEvent:nsevent eventType:int(QEvent::KeyPress)]; + return YES; + } + } + return [super performKeyEquivalent:nsevent]; +} + - (void)cancelOperation:(id)sender { Q_UNUSED(sender); diff --git a/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp b/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp index 94bb71e429..16ab51e166 100644 --- a/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp +++ b/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp @@ -716,12 +716,20 @@ public: void setSelectedFiles(const QList &); QString selectedFile() const; + // Patch: Adding select-by-url for Windows file dialog. + void setSelectedRemoteContent(const QByteArray &); + QByteArray selectedRemoteContent() const; + private: class Data : public QSharedData { public: QUrl directory; QString selectedNameFilter; QList selectedFiles; + + // Patch: Adding select-by-url for Windows file dialog. + QByteArray selectedRemoteContent; + QMutex mutex; }; QExplicitlySharedDataPointer m_data; @@ -775,6 +783,21 @@ inline void QWindowsFileDialogSharedData::setSelectedFiles(const QList &ur m_data->selectedFiles = urls; } +// Patch: Adding select-by-url for Windows file dialog. +inline QByteArray QWindowsFileDialogSharedData::selectedRemoteContent() const +{ + m_data->mutex.lock(); + const QByteArray result = m_data->selectedRemoteContent; + m_data->mutex.unlock(); + return result; +} + +inline void QWindowsFileDialogSharedData::setSelectedRemoteContent(const QByteArray &c) +{ + QMutexLocker(&m_data->mutex); + m_data->selectedRemoteContent = c; +} + inline void QWindowsFileDialogSharedData::fromOptions(const QSharedPointer &o) { QMutexLocker locker(&m_data->mutex); @@ -899,6 +922,9 @@ public: // example by appended default suffixes, etc. virtual QList dialogResult() const = 0; + // Patch: Adding select-by-url for Windows file dialog. + virtual QByteArray dialogRemoteContent() const { return QByteArray(); } + inline void onFolderChange(IShellItem *); inline void onSelectionChange(); inline void onTypeChange(); @@ -1338,7 +1364,14 @@ void QWindowsNativeFileDialogBase::selectFile(const QString &fileName) const // Hack to prevent CLSIDs from being set as file name due to // QFileDialogPrivate::initialSelection() being QString-based. if (!isClsid(fileName)) - m_fileDialog->SetFileName((wchar_t*)fileName.utf16()); + // Patch: Fix handle of full fileName. + { + QString file = QDir::toNativeSeparators(fileName); + int lastBackSlash = file.lastIndexOf(QChar::fromLatin1('\\')); + if (lastBackSlash >= 0) + file = file.mid(lastBackSlash + 1); + m_fileDialog->SetFileName((wchar_t*)file.utf16());; + } } // Return the index of the selected filter, accounting for QFileDialog @@ -1408,6 +1441,10 @@ bool QWindowsNativeFileDialogBase::onFileOk() { // Store selected files as GetResults() returns invalid data after the dialog closes. m_data.setSelectedFiles(dialogResult()); + + // Patch: Adding select-by-url for Windows file dialog. + m_data.setSelectedRemoteContent(dialogRemoteContent()); + return true; } @@ -1542,6 +1579,9 @@ public: QList selectedFiles() const Q_DECL_OVERRIDE; QList dialogResult() const Q_DECL_OVERRIDE; + // Patch: Adding select-by-url for Windows file dialog. + QByteArray dialogRemoteContent() const Q_DECL_OVERRIDE; + private: inline IFileOpenDialog *openFileDialog() const { return static_cast(fileDialog()); } @@ -1556,6 +1596,62 @@ QList QWindowsNativeOpenFileDialog::dialogResult() const return result; } +// Patch: Adding select-by-url for Windows file dialog. +QByteArray QWindowsNativeOpenFileDialog::dialogRemoteContent() const +{ + QByteArray result; + IShellItemArray *items = 0; + if (FAILED(openFileDialog()->GetResults(&items)) || !items) + return result; + DWORD itemCount = 0; + if (FAILED(items->GetCount(&itemCount)) || !itemCount) + return result; + for (DWORD i = 0; i < itemCount; ++i) + { + IShellItem *item = 0; + if (SUCCEEDED(items->GetItemAt(i, &item))) { + SFGAOF attributes = 0; + // Check whether it has a file system representation? + if (FAILED(item->GetAttributes(SFGAO_FILESYSTEM, &attributes)) || (attributes & SFGAO_FILESYSTEM)) + { + LPWSTR name = 0; + if (SUCCEEDED(item->GetDisplayName(SIGDN_FILESYSPATH, &name))) + { + CoTaskMemFree(name); + continue; + } + } + if (FAILED(item->GetAttributes(SFGAO_STREAM, &attributes)) || !(attributes & SFGAO_STREAM)) + continue; + + IBindCtx *bind = 0; + if (FAILED(CreateBindCtx(0, &bind))) + continue; + + IStream *stream = 0; + if (FAILED(item->BindToHandler(bind, BHID_Stream, IID_IStream, reinterpret_cast(&stream)))) + continue; + + STATSTG stat = { 0 }; + if (FAILED(stream->Stat(&stat, STATFLAG_NONAME)) || !stat.cbSize.QuadPart) + continue; + + quint64 fullSize = stat.cbSize.QuadPart; + if (fullSize <= 64 * 1024 * 1024) + { + result.resize(fullSize); + ULONG read = 0; + HRESULT r = stream->Read(result.data(), fullSize, &read); + if (r == S_FALSE || r == S_OK) + return result; + + result.clear(); + } + } + } + return result; +} + QList QWindowsNativeOpenFileDialog::selectedFiles() const { QList result; @@ -1614,6 +1710,10 @@ public: virtual QUrl directory() const Q_DECL_OVERRIDE; virtual void selectFile(const QUrl &filename) Q_DECL_OVERRIDE; virtual QList selectedFiles() const Q_DECL_OVERRIDE; + + // Patch: Adding select-by-url for Windows file dialog. + virtual QByteArray selectedRemoteContent() const Q_DECL_OVERRIDE; + virtual void setFilter() Q_DECL_OVERRIDE; virtual void selectNameFilter(const QString &filter) Q_DECL_OVERRIDE; virtual QString selectedNameFilter() const Q_DECL_OVERRIDE; @@ -1707,6 +1807,12 @@ QList QWindowsFileDialogHelper::selectedFiles() const return m_data.selectedFiles(); } +// Patch: Adding select-by-url for Windows file dialog. +QByteArray QWindowsFileDialogHelper::selectedRemoteContent() const +{ + return m_data.selectedRemoteContent(); +} + void QWindowsFileDialogHelper::setFilter() { qCDebug(lcQpaDialogs) << __FUNCTION__; @@ -1996,6 +2102,10 @@ public: QUrl directory() const Q_DECL_OVERRIDE; void selectFile(const QUrl &url) Q_DECL_OVERRIDE; QList selectedFiles() const Q_DECL_OVERRIDE; + + // Patch: Adding select-by-url for Windows file dialog. + QByteArray selectedRemoteContent() const Q_DECL_OVERRIDE; + void setFilter() Q_DECL_OVERRIDE {} void selectNameFilter(const QString &) Q_DECL_OVERRIDE; QString selectedNameFilter() const Q_DECL_OVERRIDE; @@ -2039,6 +2149,12 @@ QList QWindowsXpFileDialogHelper::selectedFiles() const return m_data.selectedFiles(); } +// Patch: Adding select-by-url for Windows file dialog. +QByteArray QWindowsXpFileDialogHelper::selectedRemoteContent() const +{ + return m_data.selectedRemoteContent(); +} + void QWindowsXpFileDialogHelper::selectNameFilter(const QString &f) { m_data.setSelectedNameFilter(f); // Dialog cannot be updated at run-time. diff --git a/src/plugins/platforms/windows/qwindowskeymapper.cpp b/src/plugins/platforms/windows/qwindowskeymapper.cpp index 1e58b9b3d4..1741c21a1c 100644 --- a/src/plugins/platforms/windows/qwindowskeymapper.cpp +++ b/src/plugins/platforms/windows/qwindowskeymapper.cpp @@ -1268,6 +1268,10 @@ QList QWindowsKeyMapper::possibleKeys(const QKeyEvent *e) const if (nativeVirtualKey > 255) return result; + // Patch: This must not happen, but there are crash reports on the next line. + if (e->nativeVirtualKey() > 0xFF) + return result; + const KeyboardLayoutItem &kbItem = keyLayout[nativeVirtualKey]; if (!kbItem.exists) return result; diff --git a/src/plugins/platforms/windows/qwindowsservices.cpp b/src/plugins/platforms/windows/qwindowsservices.cpp index 1d23a9d9b9..640cd426ed 100644 --- a/src/plugins/platforms/windows/qwindowsservices.cpp +++ b/src/plugins/platforms/windows/qwindowsservices.cpp @@ -127,6 +127,10 @@ static inline bool launchMail(const QUrl &url) command.prepend(doubleQuote); } } + + // Patch: Fix mail launch if no param is expected in this command. + if (command.indexOf(QStringLiteral("%1")) < 0) return false; + // Pass the url as the parameter. Should use QProcess::startDetached(), // but that cannot handle a Windows command line [yet]. command.replace(QStringLiteral("%1"), url.toString(QUrl::FullyEncoded)); diff --git a/src/plugins/platforms/windows/qwindowswindow.cpp b/src/plugins/platforms/windows/qwindowswindow.cpp index b38d7c29ae..34f19c4efa 100644 --- a/src/plugins/platforms/windows/qwindowswindow.cpp +++ b/src/plugins/platforms/windows/qwindowswindow.cpp @@ -1020,7 +1020,8 @@ void QWindowsWindow::destroyWindow() // Clear any transient child relationships as Windows will otherwise destroy them (QTBUG-35499, QTBUG-36666) if (QWindow *transientChild = findTransientChild(window())) if (QWindowsWindow *tw = QWindowsWindow::baseWindowOf(transientChild)) - tw->updateTransientParent(); + // Patch: Fix possibility of add / remove taskbar icon of the window. + tw->clearTransientParent(); QWindowsContext *context = QWindowsContext::instance(); if (context->windowUnderMouse() == window()) context->clearWindowUnderMouse(); @@ -1235,6 +1236,21 @@ void QWindowsWindow::updateTransientParent() const if (const QWindowsWindow *tw = QWindowsWindow::baseWindowOf(tp)) if (!tw->testFlag(WithinDestroy)) // Prevent destruction by parent window (QTBUG-35499, QTBUG-36666) newTransientParent = tw->handle(); + // Patch: Fix possibility of add / remove taskbar icon of the window. + if (newTransientParent && newTransientParent != oldTransientParent) + SetWindowLongPtr(m_data.hwnd, GWL_HWNDPARENT, (LONG_PTR)newTransientParent); +#endif // !Q_OS_WINCE +} + +// Patch: Fix possibility of add / remove taskbar icon of the window. +void QWindowsWindow::clearTransientParent() const +{ +#ifndef Q_OS_WINCE + if (window()->type() == Qt::Popup) + return; // QTBUG-34503, // a popup stays on top, no parent, see also WindowCreationData::fromWindow(). + // Update transient parent. + const HWND oldTransientParent = transientParentHwnd(m_data.hwnd); + HWND newTransientParent = 0; if (newTransientParent != oldTransientParent) SetWindowLongPtr(m_data.hwnd, GWL_HWNDPARENT, (LONG_PTR)newTransientParent); #endif // !Q_OS_WINCE @@ -1448,10 +1464,14 @@ void QWindowsWindow::handleResized(int wParam) handleGeometryChange(); break; case SIZE_RESTORED: - if (isFullScreen_sys()) - handleWindowStateChange(Qt::WindowFullScreen); - else if (m_windowState != Qt::WindowNoState && !testFlag(MaximizeToFullScreen)) + // Patch: When resolution is changed for a frameless fullscreen widget + // handleWindowStateChange call prevents correct geometry get in handleGeometryChange(). + if (isFullScreen_sys()) { + if (m_windowState != Qt::WindowFullScreen) + handleWindowStateChange(Qt::WindowFullScreen); + } else if (m_windowState != Qt::WindowNoState && !testFlag(MaximizeToFullScreen)) { handleWindowStateChange(Qt::WindowNoState); + } handleGeometryChange(); break; } diff --git a/src/plugins/platforms/windows/qwindowswindow.h b/src/plugins/platforms/windows/qwindowswindow.h index 6fffa1e6e9..cb1c9c1161 100644 --- a/src/plugins/platforms/windows/qwindowswindow.h +++ b/src/plugins/platforms/windows/qwindowswindow.h @@ -265,6 +265,10 @@ private: inline void setWindowState_sys(Qt::WindowState newState); inline void setParent_sys(const QPlatformWindow *parent); inline void updateTransientParent() const; + + // Patch: Fix possibility of add / remove taskbar icon of the window. + inline void clearTransientParent() const; + void destroyWindow(); inline bool isDropSiteEnabled() const { return m_dropTarget != 0; } void setDropSiteEnabled(bool enabled); diff --git a/src/plugins/platforms/xcb/qxcbnativeinterface.cpp b/src/plugins/platforms/xcb/qxcbnativeinterface.cpp index 09e7ecf3a3..c0f15a4242 100644 --- a/src/plugins/platforms/xcb/qxcbnativeinterface.cpp +++ b/src/plugins/platforms/xcb/qxcbnativeinterface.cpp @@ -79,7 +79,10 @@ static int resourceType(const QByteArray &key) QByteArrayLiteral("rootwindow"), QByteArrayLiteral("subpixeltype"), QByteArrayLiteral("antialiasingenabled"), QByteArrayLiteral("nofonthinting"), - QByteArrayLiteral("atspibus") + QByteArrayLiteral("atspibus"), + + // Patch: Backport compositing manager check from Qt 5.7 + QByteArrayLiteral("compositingenabled") }; const QByteArray *end = names + sizeof(names) / sizeof(names[0]); const QByteArray *result = std::find(names, end, key); @@ -252,6 +255,13 @@ void *QXcbNativeInterface::nativeResourceForScreen(const QByteArray &resourceStr case RootWindow: result = reinterpret_cast(xcbScreen->root()); break; + + // Patch: Backport compositing manager check from Qt 5.7 + case CompositingEnabled: + if (QXcbVirtualDesktop *vd = xcbScreen->virtualDesktop()) + result = vd->compositingActive() ? this : Q_NULLPTR; + break; + default: break; } diff --git a/src/plugins/platforms/xcb/qxcbnativeinterface.h b/src/plugins/platforms/xcb/qxcbnativeinterface.h index f88b710864..6f818a5a72 100644 --- a/src/plugins/platforms/xcb/qxcbnativeinterface.h +++ b/src/plugins/platforms/xcb/qxcbnativeinterface.h @@ -68,7 +68,10 @@ public: ScreenSubpixelType, ScreenAntialiasingEnabled, NoFontHinting, - AtspiBus + AtspiBus, + + // Patch: Backport compositing manager check from Qt 5.7 + CompositingEnabled }; QXcbNativeInterface(); diff --git a/src/widgets/dialogs/qfiledialog.cpp b/src/widgets/dialogs/qfiledialog.cpp index bc2de899f5..aa8f8df4ad 100644 --- a/src/widgets/dialogs/qfiledialog.cpp +++ b/src/widgets/dialogs/qfiledialog.cpp @@ -1200,6 +1200,15 @@ QList QFileDialogPrivate::userSelectedFiles() const return files; } +// Patch: Adding select-by-url for Windows file dialog. +QByteArray QFileDialogPrivate::userSelectedRemoteContent() const +{ + if (nativeDialogInUse) + return selectedRemoteContent_sys(); + + return QByteArray(); +} + QStringList QFileDialogPrivate::addDefaultSuffixToFiles(const QStringList &filesToFix) const { QStringList files; @@ -1267,6 +1276,14 @@ QStringList QFileDialog::selectedFiles() const return files; } +// Patch: Adding select-by-url for Windows file dialog. +QByteArray QFileDialog::selectedRemoteContent() const +{ + Q_D(const QFileDialog); + + return d->userSelectedRemoteContent(); +} + /*! Returns a list of urls containing the selected files in the dialog. If no files are selected, or the mode is not ExistingFiles or diff --git a/src/widgets/dialogs/qfiledialog.h b/src/widgets/dialogs/qfiledialog.h index ffe49a2dd2..42dc563c8a 100644 --- a/src/widgets/dialogs/qfiledialog.h +++ b/src/widgets/dialogs/qfiledialog.h @@ -108,6 +108,9 @@ public: void selectFile(const QString &filename); QStringList selectedFiles() const; + // Patch: Adding select-by-url for Windows file dialog. + QByteArray selectedRemoteContent() const; + void selectUrl(const QUrl &url); QList selectedUrls() const; diff --git a/src/widgets/dialogs/qfiledialog_p.h b/src/widgets/dialogs/qfiledialog_p.h index f610e46f83..547a64695a 100644 --- a/src/widgets/dialogs/qfiledialog_p.h +++ b/src/widgets/dialogs/qfiledialog_p.h @@ -123,6 +123,10 @@ public: static QString initialSelection(const QUrl &path); QStringList typedFiles() const; QList userSelectedFiles() const; + + // Patch: Adding select-by-url for Windows file dialog. + QByteArray userSelectedRemoteContent() const; + QStringList addDefaultSuffixToFiles(const QStringList &filesToFix) const; QList addDefaultSuffixToUrls(const QList &urlsToFix) const; bool removeDirectory(const QString &path); @@ -256,6 +260,10 @@ public: QUrl directory_sys() const; void selectFile_sys(const QUrl &filename); QList selectedFiles_sys() const; + + // Patch: Adding select-by-url for Windows file dialog. + QByteArray selectedRemoteContent_sys() const; + void setFilter_sys(); void selectNameFilter_sys(const QString &filter); QString selectedNameFilter_sys() const; @@ -393,6 +401,14 @@ inline QList QFileDialogPrivate::selectedFiles_sys() const return QList(); } +// Patch: Adding select-by-url for Windows file dialog. +inline QByteArray QFileDialogPrivate::selectedRemoteContent_sys() const +{ + if (QPlatformFileDialogHelper *helper = platformFileDialogHelper()) + return helper->selectedRemoteContent(); + return QByteArray(); +} + inline void QFileDialogPrivate::setFilter_sys() { if (QPlatformFileDialogHelper *helper = platformFileDialogHelper()) diff --git a/src/widgets/kernel/qwidget.cpp b/src/widgets/kernel/qwidget.cpp index b1d80d7b8f..42e32fd404 100644 --- a/src/widgets/kernel/qwidget.cpp +++ b/src/widgets/kernel/qwidget.cpp @@ -5138,6 +5138,17 @@ void QWidget::render(QPainter *painter, const QPoint &targetOffset, return; // Fully transparent. Q_D(QWidget); + + // Patch: save and restore dirtyOpaqueChildren field. + // + // Just like in QWidget::grab() this field should be restored + // after the d->render() call, because it will be set to 1 and + // opaqueChildren field will be filled with empty region in + // case the widget is hidden (because all the opaque children + // will be skipped in isVisible() check). + // + const bool oldDirtyOpaqueChildren = d->dirtyOpaqueChildren; + const bool inRenderWithPainter = d->extra && d->extra->inRenderWithPainter; const QRegion toBePainted = !inRenderWithPainter ? d->prepareToRender(sourceRegion, renderFlags) : sourceRegion; @@ -5159,6 +5170,10 @@ void QWidget::render(QPainter *painter, const QPoint &targetOffset, if (!inRenderWithPainter && (opacity < 1.0 || (target->devType() == QInternal::Printer))) { d->render_helper(painter, targetOffset, toBePainted, renderFlags); d->extra->inRenderWithPainter = inRenderWithPainter; + + // Patch: save and restore dirtyOpaqueChildren field. + d->dirtyOpaqueChildren = oldDirtyOpaqueChildren; + return; } @@ -5190,6 +5205,9 @@ void QWidget::render(QPainter *painter, const QPoint &targetOffset, d->setSharedPainter(oldPainter); d->extra->inRenderWithPainter = inRenderWithPainter; + + // Patch: save and restore dirtyOpaqueChildren field. + d->dirtyOpaqueChildren = oldDirtyOpaqueChildren; } static void sendResizeEvents(QWidget *target) @@ -8769,7 +8787,8 @@ bool QWidget::event(QEvent *event) case QEvent::KeyPress: { QKeyEvent *k = (QKeyEvent *)event; bool res = false; - if (!(k->modifiers() & (Qt::ControlModifier | Qt::AltModifier))) { //### Add MetaModifier? + // Patch: Enable Ctrl+Tab and Ctrl+Shift+Tab / Ctrl+Backtab handle in-app. + if (!(k->modifiers() & (Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier))) { //### Add MetaModifier? if (k->key() == Qt::Key_Backtab || (k->key() == Qt::Key_Tab && (k->modifiers() & Qt::ShiftModifier))) res = focusNextPrevChild(false); diff --git a/src/widgets/util/qsystemtrayicon.cpp b/src/widgets/util/qsystemtrayicon.cpp index 704142fe5c..7c4340e459 100644 --- a/src/widgets/util/qsystemtrayicon.cpp +++ b/src/widgets/util/qsystemtrayicon.cpp @@ -709,6 +709,10 @@ void QSystemTrayIconPrivate::updateMenu_sys_qpa() if (menu) { addPlatformMenu(menu); qpa_sys->updateMenu(menu->platformMenu()); + + // Patch: Create a rich os x tray icon (pixel-perfect, theme switching). + } else { + qpa_sys->updateMenu(nullptr); } } diff --git a/src/widgets/widgets/qabstractscrollarea.cpp b/src/widgets/widgets/qabstractscrollarea.cpp index 2e2a042bf1..472e37722b 100644 --- a/src/widgets/widgets/qabstractscrollarea.cpp +++ b/src/widgets/widgets/qabstractscrollarea.cpp @@ -640,15 +640,22 @@ scrolling range. QSize QAbstractScrollArea::maximumViewportSize() const { Q_D(const QAbstractScrollArea); - int hsbExt = d->hbar->sizeHint().height(); - int vsbExt = d->vbar->sizeHint().width(); + // Patch: Count the sizeHint of the bar only if it is displayed. + //int hsbExt = d->hbar->sizeHint().height(); + //int vsbExt = d->vbar->sizeHint().width(); int f = 2 * d->frameWidth; QSize max = size() - QSize(f + d->left + d->right, f + d->top + d->bottom); - if (d->vbarpolicy == Qt::ScrollBarAlwaysOn) + + // Patch: Count the sizeHint of the bar only if it is displayed. + if (d->vbarpolicy == Qt::ScrollBarAlwaysOn) { + int vsbExt = d->vbar->sizeHint().width(); max.rwidth() -= vsbExt; - if (d->hbarpolicy == Qt::ScrollBarAlwaysOn) + } + if (d->hbarpolicy == Qt::ScrollBarAlwaysOn) { + int hsbExt = d->hbar->sizeHint().height(); max.rheight() -= hsbExt; + } return max; } diff --git a/src/widgets/widgets/qwidgetlinecontrol.cpp b/src/widgets/widgets/qwidgetlinecontrol.cpp index daf9f00c46..57499dc4a4 100644 --- a/src/widgets/widgets/qwidgetlinecontrol.cpp +++ b/src/widgets/widgets/qwidgetlinecontrol.cpp @@ -40,6 +40,11 @@ #include #include #include + +// Patch: Enable Ctrl+key and Ctrl+Shift+key in all locales except German. +// See https://github.com/telegramdesktop/tdesktop/pull/1185. +#include + #ifndef QT_NO_ACCESSIBILITY #include "qaccessible.h" #endif @@ -1882,11 +1887,21 @@ void QWidgetLineControl::processKeyEvent(QKeyEvent* event) } // QTBUG-35734: ignore Ctrl/Ctrl+Shift; accept only AltGr (Alt+Ctrl) on German keyboards - if (unknown && !isReadOnly() - && event->modifiers() != Qt::ControlModifier - && event->modifiers() != (Qt::ControlModifier | Qt::ShiftModifier)) { + + // Patch: Enable Ctrl+key and Ctrl+Shift+key in all locales except German. + // See https://github.com/telegramdesktop/tdesktop/pull/1185. + bool skipCtrlAndCtrlShift = false; + if (QGuiApplication::inputMethod()->locale().language() == QLocale::German) { + if (event->modifiers() == Qt::ControlModifier + || event->modifiers() == (Qt::ControlModifier | Qt::ShiftModifier)) { + skipCtrlAndCtrlShift = true; + } + } + if (unknown && !isReadOnly() && !skipCtrlAndCtrlShift) { QString t = event->text(); - if (!t.isEmpty() && t.at(0).isPrint()) { + + // Patch: Enable ZWJ and ZWNJ characters to be in text input. + if (!t.isEmpty() && (t.at(0).isPrint() || t.at(0).unicode() == 0x200C || t.at(0).unicode() == 0x200D)) { insert(t); #ifndef QT_NO_COMPLETER complete(event->key()); diff --git a/src/widgets/widgets/qwidgettextcontrol.cpp b/src/widgets/widgets/qwidgettextcontrol.cpp index deca002bf5..8a2023f503 100644 --- a/src/widgets/widgets/qwidgettextcontrol.cpp +++ b/src/widgets/widgets/qwidgettextcontrol.cpp @@ -71,6 +71,11 @@ #include #include #include + +// Patch: Enable Ctrl+key and Ctrl+Shift+key in all locales except German. +// See https://github.com/telegramdesktop/tdesktop/pull/1185. +#include + #include #include #include @@ -1343,13 +1348,24 @@ void QWidgetTextControlPrivate::keyPressEvent(QKeyEvent *e) process: { // QTBUG-35734: ignore Ctrl/Ctrl+Shift; accept only AltGr (Alt+Ctrl) on German keyboards - if (e->modifiers() == Qt::ControlModifier - || e->modifiers() == (Qt::ShiftModifier | Qt::ControlModifier)) { + + // Patch: Enable Ctrl+key and Ctrl+Shift+key in all locales except German. + // See https://github.com/telegramdesktop/tdesktop/pull/1185. + bool skipCtrlAndCtrlShift = false; + if (QGuiApplication::inputMethod()->locale().language() == QLocale::German) { + if (e->modifiers() == Qt::ControlModifier + || e->modifiers() == (Qt::ControlModifier | Qt::ShiftModifier)) { + skipCtrlAndCtrlShift = true; + } + } + if (skipCtrlAndCtrlShift) { e->ignore(); return; } QString text = e->text(); - if (!text.isEmpty() && (text.at(0).isPrint() || text.at(0) == QLatin1Char('\t'))) { + + // Patch: Enable ZWJ and ZWNJ characters to be in text input. + if (!text.isEmpty() && (text.at(0).isPrint() || text.at(0) == QLatin1Char('\t') || text.at(0).unicode() == 0x200C || text.at(0).unicode() == 0x200D)) { if (overwriteMode // no need to call deleteChar() if we have a selection, insertText // does it already tdesktop-1.2.17/Telegram/Resources/000077500000000000000000000000001326245125100171525ustar00rootroot00000000000000tdesktop-1.2.17/Telegram/Resources/art/000077500000000000000000000000001326245125100177405ustar00rootroot00000000000000tdesktop-1.2.17/Telegram/Resources/art/bg.jpg000066400000000000000000003222671326245125100210460ustar00rootroot00000000000000 IExifMM*bj(1&r2i ' 'Adobe Photoshop CC 2015.5 (Macintosh)2016:11:07 17:42:18p&.(6 HH Adobe_CMAdobed            Z"?   3!1AQa"q2B#$Rb34rC%Scs5&DTdE£t6UeuF'Vfv7GWgw5!1AQaq"2B#R3$brCScs4%&5DTdEU6teuFVfv'7GWgw ?9 lUҀЌŽ«/l5-EjK5L!'3e9ոj5MÚ?B'}Ztun]pX˶1>)g??1AB! dG$gfWcSik~c{6P12@%nMo-Cbяc -,kt|Bqt*0(lk@;(H<[3?ɉWO.swk nWkM}#ǜ&Oѩ XnKrugCWZVzgP' -OҤlT6ǫ`+kra,px-%`_kK@0r|5p~~eSfܞJ z:PCL4Q6EUֱH@ .s {*i<7>%'=̯s: ,s'&4w%- k Q G m{N6?X@_"-g&k̦0:,$L!&Y*)3Ai;D)袛u׽g'? -yih'\狼3][c}ca7Hzkͮv.uFcN]?epAO_5Gi Ů4D˛ݣH?~s讇X*$s#*G^\UT>)!/Gx(;2N5[fJ%WnpTI4,2%J ՘DHU|JZPI5d*[ѩy! @Ȁ-_]?b1'e{[G,ۜ10h!r#i.0tWKG[@:CZ2JB15 ݧdunȰWciw1- ki /c,v+Œ;.]`\h$[D[Y |^]2 @$ }bVmDܼ 8]H8?Xn'U2u{r]{vY~jMY$BQ HIJFUڥma6 &&Ӳ>ȩ얗K:&Photoshop 3.08BIM%8BIM: printOutputPstSboolInteenumInteClrmprintSixteenBitbool printerNameTEXT192.168.50.51printProofSetupObjc Proof Setup proofSetupBltnenum builtinProofproofMacintosh8BIM;-printOutputOptionsCptnboolClbrboolRgsMboolCrnCboolCntCboolLblsboolNgtvboolEmlDboolIntrboolBckgObjcRGBCRd doub@oGrn doub@oBl doub@oBrdTUntF#RltBld UntF#RltRsltUntF#Pxl@R vectorDataboolPgPsenumPgPsPgPCLeftUntF#RltTop UntF#RltScl UntF#Prc@YcropWhenPrintingboolcropRectBottomlong cropRectLeftlong cropRectRightlong cropRectToplong8BIMHH8BIM&?8BIM 8BIM8BIM 8BIM' 8BIMH/fflff/ff2Z5-8BIMp8BIM@@8BIM8BIM9pbgpnullboundsObjcRct1Top longLeftlongBtomlongpRghtlongslicesVlLsObjcslicesliceIDlonggroupIDlongoriginenum ESliceOrigin autoGeneratedTypeenum ESliceTypeImg boundsObjcRct1Top longLeftlongBtomlongpRghtlongurlTEXTnullTEXTMsgeTEXTaltTagTEXTcellTextIsHTMLboolcellTextTEXT horzAlignenumESliceHorzAligndefault vertAlignenumESliceVertAligndefault bgColorTypeenumESliceBGColorTypeNone topOutsetlong leftOutsetlong bottomOutsetlong rightOutsetlong8BIM( ?8BIM8BIM8BIM 'Z  Adobe_CMAdobed            Z"?   3!1AQa"q2B#$Rb34rC%Scs5&DTdE£t6UeuF'Vfv7GWgw5!1AQaq"2B#R3$brCScs4%&5DTdEU6teuFVfv'7GWgw ?9 lUҀЌŽ«/l5-EjK5L!'3e9ոj5MÚ?B'}Ztun]pX˶1>)g??1AB! dG$gfWcSik~c{6P12@%nMo-Cbяc -,kt|Bqt*0(lk@;(H<[3?ɉWO.swk nWkM}#ǜ&Oѩ XnKrugCWZVzgP' -OҤlT6ǫ`+kra,px-%`_kK@0r|5p~~eSfܞJ z:PCL4Q6EUֱH@ .s {*i<7>%'=̯s: ,s'&4w%- k Q G m{N6?X@_"-g&k̦0:,$L!&Y*)3Ai;D)袛u׽g'? -yih'\狼3][c}ca7Hzkͮv.uFcN]?epAO_5Gi Ů4D˛ݣH?~s讇X*$s#*G^\UT>)!/Gx(;2N5[fJ%WnpTI4,2%J ՘DHU|JZPI5d*[ѩy! @Ȁ-_]?b1'e{[G,ۜ10h!r#i.0tWKG[@:CZ2JB15 ݧdunȰWciw1- ki /c,v+Œ;.]`\h$[D[Y |^]2 @$ }bVmDܼ 8]H8?Xn'U2u{r]{vY~jMY$BQ HIJFUڥma6 &&Ӳ>ȩ얗K:8BIM!aAdobe PhotoshopAdobe Photoshop CC 2015.58BIM4http://ns.adobe.com/xap/1.0/ Adobed@      pP  s!1AQa"q2B#R3b$r%C4Scs5D'6Tdt& EFVU(eufv7GWgw8HXhx)9IYiy*:JZjzm!1AQa"q2#BRbr3$4CS%cs5DT &6E'dtU7()󄔤euFVfvGWgw8HXhx9IYiy*:JZjz ?0rPҕ_HclĊu[%"ïZSMĥpJPP<U~ ָ{eTUïLUv*\UST^.rilZltAWI-So[CBz~rPVb*Hnޘ W1JN$u6) w*X`9W+ + Fxd[,iWSjd b=F  ҝqEt;\õ1V]QK`U@{U˵2*o+vŲ*y J<DTPWwI"I SRi)Uz}vLvA;O)U}QUS b;WqUZl Tv*Q*a˅, U8=1dzWv⪉⫃o֞تþ*)R+ӶI ,UPO\y ֽ1kWqfb8>}JT -p5JU]TqULUxS$ k͉Uî}@ ٥6I- VU܎*dcrujzngc&FVH\T.ZWv=5)ҟL1چ[WQ^8V @d޵9\NE r -Ȯ QHڵ?̀ 7؅Tx\Sp[`dU?E1f渫d*/ݛ$B5(FتkJ %E;қUYO ?[`( 6޽UWR)G\,U8As~׾@V0Uv&@v~*wWZj>kO(\:g E*: UPl>k ]UUjh<:䘐mS\YzV?{z}*^Uz_bqUA)*RƸ?dd}kzH5@l)^ 1Ub\UpUqWa N6k7ŌT_2A[z[qVX*rrY F=1lvԧ߄5_ |E]Nbq\RM^⪊b*xdYE +LVmLLWSȡU~|*AD?/dm ;mN)Tk^{ؿT^bXg!px8L'r0[0)U%m=;W*N-Ug•n>Kשd+\U•=(^m]w5cY|Tk+ZWJw}^?iPS 1J *C*7#tUV{|X}J/|P =gڰ= Sf~\UQjqUzbm^i}UA߆H5i+LJe?k]b qU@G*7poӓf74Ō|qb}UܷlU;cjcjkJ\Ujt~Ͼr *Sr$5'$YD8ï$ت*z WZyal%P`+u.m!T{HWZmrTZo"UX|VVRj-~*v Ў*5=xWzrSWJ+xƕXȴ1fHE'jYJҽrA;W ʟي-?! QZ \Y*QO P*EHc%@H0$7bzȪkMrID!Ǘl x*o|)T (6⪠vUc r*9*›qR|UTRxW_NUeABzbM|R 7e풋`*|~(\:ˊZٗ^U ˎ U"ָB$;uIbWʼn\:Wr!IT~*Nc5['2*5nUZ?mHث~9`*P9w]Ĭz nNEA=|1lP*vɪ|Pj~* ; ULUx?AeJ?PTCl +| jTb2Vk׎FLrDJŷ>쌂"J*,`gj[TZ1UX<1lWW&ҝ⪢J?HCcx{$CS~J~$* jUUt&ˏQmfb ֊l l$UR k jM(lm^H(qBi^^WdtUV*1 mtG%E<׭HJ`6⪀WT.UNt5X!['L[b(D(QAۗ^\qUƿ&CX*[xڛVClH>|p;*N cl䒸zT}ت?NXv*ou-nW}~UwC튷Q8Rӿǭ3. WEoJj) m^\ ޫ4R ЍaUm_+ޔ'\ip3?8-@PKamAJ+0US]鐒QCƸ}\J-+z  زTSƇJ~|VBzxbu=m&բj {b~= קN-MrUQrV^TUԝUiȲ^O׊7bm튫)͸(u, M795EkӯӖƄlUY{J!MiNኮ5r\;S ]Gq,WǶ*\UxӑLQJ@ -*'Jo\d)jI Nت ScM~&?ˋb^78p*}9 CL`AޝJU }RKxPP]O}UiNU[0S,pۏ`USo<-,W+rBJm]Wr>勭y4'aOb.a*gr(hk T4v%jjb![GUU*+]6Vڕ?͊J(W\*cb]z 71UÕhzUb*#AJ [u=Y%D UتCא,mz'h X{ֽUC|P 4H۫bN* j)_|Rk~X :l[Ц* iQWUk\ i=*:mOlY*vK?ŀTŒb>UX|⫀튯ڧW}O݄*)rA*ێI UWþ-}wUӷN8*UE:.]~IGU,bnتqV֛S«7rAW­֛So*[ŭpo+O|+XUثp٫O?9bnq}vo`CDGeqDMrӋ(;MTRȡ\oZ*7eUi#oUwJ U\ B'|*bQZV*⨘_򾜌EYN@TfZSn6e؊b=knt;b/^h֤5w\@E/mu\Y*6jש Y/PjO_Q ֝+U~[o*j|qUU=UE>Cb÷|U]GRMvUdU*p7*U\ WqUe߾)T^foU^*}Ө8vZ} ,H^8QEɲT+_nUGMvű\6*LUxڽUWoW/$JwɅ T K^)>Upv*GlUU{bOL4['ٲlbbU1C@[" ۮLp^b\勯*c]qVQ!Ma7X•6*z܏tWQ ^Cs(D\ ҾxbAӧ%W>Z JU bͱ`fO/*]0- ӯсUz{~MQ MqTBt*qd#~-# = A'ۡUT*@ڙ&J]+(UV*wW4銫Zva*nN*BGlUUM+A8U5Q*~ܪ个pM;*o8*-isW ⪪wj|5UW•W^1UAnhtUurLU4&r˶*vX+TqUP*&Y+ =б_ Uتvb_|UP_a6խ6kqI]%6S ȥvY7_/BbW^i+R;.ו: !֚4-QjZ8 *؆Jr*^HVvg윭U\J}B)$@J^^ Mi\Umk⪂濳5QׯqTRS}3tΕ6T42J:ָ,Y>Ҙ $`lTV4V^=?*Y/_*O*~?[bwUU> U UU~TBr4U4I*ɱ,t#rjIUp%U}~Ê{WUOlUPST~*6S۾*銪;?b ;d+ ڔ*wUA|?UzbPPPT銯P(:>#*~1U"^;dwb)UWӊW *qUӯo{rWx[klT'(2J}h"գ~ko㊎Ky]LjyVc@@nwnQ4~[,dar*;oG1)UQp$*J5J^ت߶*x⫨)@k`)N)AGQmU^8/N]iӷKTJRUz*•逡|-WS?86adQ-<U1U7e~/T@+ኪ 5*:UQQUUu781M @rvlUTn@%[{ vUT銯ME~**Ƈ|UUzT_*wabz+/_U:qJSOAW/TTR6^*8eATQPoҔU;~*t`^]8kU⭃ֻbS|UUثW R0 u%}H=w1l!2oOɳ U~]W B^nx=)֟k Gf T^D0V+IJ קt4A Q+_j1UtqUJoA)S_l,Y6^]=x@R)MB/ OJ.3ere@{dNU.6(rIVRi81޹4$r޻5(m.ኪh)b7ZWTJҞ#,dQ pU mb֔USw;rU>⪈zlw=qUPn:bn8B녊kɲTSڴ^:TbPU*~ɪu:{~y/y yb}^U*1Uð|U_wLUp{#j,Up1WTx⭃LUqW?k9w[(^8)Z϶{1\"ωP0ŗ0L~pnTjͅ\bڝYE`JAu鑴S+\-d-@퀖\*mluűp@**F[(O^) Y`*}ZT`2,Q, ت0$/_$\-x>G$vx^?Ul:Վ*QңuUQ~ vULUXWG8)wZ["k %u#UzK*޵TSjuUNZӯVZTT6jXnnM¿qU|UPޘTAUiSjP}Wæ%X<רJlT*lWlUuqcm;Y6:xUk*|qVVMW*>=qUUO^\ťE<;bc7rq[SIJkm uqdJm|qB*3ӿVWVڤZ?vT !QK| J(BJ }W^rDDSj1TBJ)Z+,^>tijD/?ၒ:{•E<@ޛ"jȤPܒ6Ҭ;%JgaMl!64?UWqHTiJ-W?J~b:xUUvaS6U oMدlᅒqUqk+~gqUU)W V\*~}1UQ^تo*኶:bׯqUaޣIbWl^qU=dx=|@3T]϶*Vp1*6;Wv*oonuۯ[a[UثWbA*UOˬ[_U&;7XߧrJn켽SLY" 2'd@=eĩEE"]G⪇HT*U؝w,S1Uuޤ*; U`:bVm!?l Jj|UUil T_qUJTU~Ê/*#'D>J!|EqP;z.BlUPS*>&UQJb_\71UAlUT |U9bxI_]`Bר+$ PA{d/d|*t*_w ~ӒJ*{⪣8<4qE`-lUU犻^?qWWn[W㊷Ql튺~č霱ul |dW(Ur7~ ѱw*G߳d Ҙ t2)L"=kLQ#*j:t)VUBJ%,WAtűPxu㊐t1P7 +tɈè=M*)VSbQQܾ1(Q %uî*M{W"U7O|,ULP|qUdW ھ %A"BSjqfJu4lUz|J?T^WZxU6U~}?UF:.ب\?fo$$t-OM|1Vbâ5eVJ[G8v)+G\,uj* Uw.S]Z[*|Uت*OJWo}{b4bV#v_E'9gL[|4ذ,TۿjpZVUT\L( w%E/P &im\HMZ:#Ng"Q TS%`h72WN;JQKNW\dU˷LPv߿d;qBqUM@oeOo,k\)Q\5u?QIӦ@Y mڙ%VQ F*E?D"owfq*Fت!Gn7U$*t\Tp>۾EBҤ7L,%p+1Jm.N*)N⪟EOb8K㊻]W኷QъOWW*zoĊN]( 5xd,ݰu`V^kb&=k|T?nRh)ŕh>.@@+E\ȶ:"YRp3ӷ\eRpU}Yob1UNMqJ^ /(UO+boQXPiCLUUaE}ձUEZSSJ˿QW;lUXoWVEWn,URت֛⨅]L Q ȒUI*W( =kW;bJwUz UPw*늶:Uߵ*>=H >=O UGCCkצ6JU: U]{İTw%\ۿ5J늹zbצoW^>ܱVV^gW8J]׮تGA_qWb*z⫪1W:u늭nBJw];U'|PU`%!6o"K D)|"AYסpD'8*1VQZv^M K׮,ާ,ޝGm停J߳%'U:v*w*+A\Rۧ9*chWL` [_Ո @T3V'V:<U]E6=LU<;UZ;uUew8QJi"*tUX[@=\}U^lUxPjzvU4+t:*W^}pE⫇Ӿ*(hp\G=2Ap(TH)T^Pto\%u튪ŸO쌘UJUá3nU^>,Ux?db1V޸uqW*Op%m?N*F@:ɥ?⮨[k섏iӭ=B)$+6*W|GTZtS (~(-)flYE>gJ_Ȓ(~lY*SjŔW KvUz6@{_˥vWo^D?g|,$AeD[( Dԍ˜+xmr`6e=]E=|qUeoD(x%:qTBUPt;bUWb;EUOp*SևTz튪¶+ȥzcLUT w8JO p빯*PQSUT i`PM+׹'/7ɲ\>'*C*7Ʌp4%WqW\U.%ݺ^%Jj74`|UuN*QW=qU⮭EVHDUA(x :ʢGJDQTdYظU *Srª:p3V4W&7} PWZ®,J%)VIR)]Ejz$7emHl0:SqUuU*{lUQ"uTa%W)E|:⨔^)b'T(^O-#\UUrlX*L%Qnb:S\7&vm@iӾ*:Ӷ*|UT{6T}XS$k…׊6G/ nT]1U7'*HQ1VȮL\تU|qU'"~ Ozwȱnb+_L5ڸI.', n«FnUstm[VxU=;ƐUTSrB!76WYX]r,QR[)߲Ӿ*4ȶ*v'~X 7dc879f* { Gq)Um! Q}q9)ql>; 1 ~d(_ MPs`կӈHWz {e%UѲjQ UQ@G|U#lUvQ*j#lz Ҽ`tQ{$Xt튯}+~S$.qU^1l0Vޔ_"AኯnW\UWlm]iUVW-v'l,IT"UaO$t5 T`k]_zbN5w~x*u?تw⫪ӊR<5‡LU~zxb?VWilW\` Bw޹0o*c*?ղm_ol5/.qT@TB努j|?i*LUn֛rqgUiE"-,C_8!Uv5z Vm*Xb]O7nY@GZ[e; U7v++=-E->YXl%R59x +Tt?k$Z%WQaUtݏɅUUkj͸nE(7ȶR%i]Ǿ+JPnۨXo~ت%GGWQ?WQҫAUEvΘۖ]}9 :#=0h}bR=NxdRa⫱Uֿ,Up4WjUq!To\(n FH3V%h]SҧU_rU!_o| *-θҮ^4[*5w\U|Ux$"RNAZ',UuUu@޴[LUت˾*& Y M1V=*Q}\]*UpDjUTw߾**;u]~[Q+סMኪWcA8B7 OE&:𲊪R}1np|qJҤuB z9"`O.DEXӾJE@xC ?@•qcL,޿giUA]22lE H,wE6Sl^:W+VWhW]ݰ*>m*GlUTJY(rL8zFQK^UTt\U0%z7銮s a8RW G5ˈbJ)T*A_oqU lUS诶*{P6ت^x5=q jE W~Z_nثXu튺_~;VuڪuWWTŚJ'"Ux'FqWVR*bgu UuE.*]]⮯~Ͷ*SqRw_Fʗ+S*?\U]N*ABNY[BQ *Q 㪏!B% .Jg UzN+f^ :-+C$ǎ*! 2E;W,ޛ.A ZlUDrBT1FTXrG QS dTʂ: L*D(o\UMjr,nUTBqU`݁U'0uk0%YvP {b]{⪋U*7U>-ѱUûlUvtbJvU:[߫~LYEp8Gň澕J (UZ⫶&*G$ AwㅓzPKozӯu4nMVt:cjwl0b&?i [1UcƇXzS]Z4V׾-`+)b{-`*dn}V)ojW-Ҕ?X=X1UuADW%T;+@],B޽03$.ߵ"WVOV)^_gm+5 57B5鍢Ԓ8 `W^:% b!Gj[aSrsLȢxd B q;l ⊌w1J''* #`U` ):vTZTGEޙQW? b;bOA*_qL ^. ,Q 6Ww*˷MUAT^ӯ UpUJ`1Uң8gzPv*z 1UM.ث:ָ'{ıU|F,:ئTs^6?(UiJWoۓI o(uF%ت}p=G~WWxi]WUȪiۯ|Uܺ\Ux4LUP7ض.[ $GMێT["=?KZ.=uO٠)E&S%Um;`UU8l4׶X1T`ڀ"@& %evv8UJ8R:bNaUe\-*![n;qȕE O2끐V-\"cF*_sLU=UU |0T]qUU6*QևrUGN6UޣC^͊/v'VZ*%\UTnxd1WdfaBzűQn -ԏ|U^}LX;`(+wXÿ9̺)/M;WalPw*qR |Al1NABE jTN7ϯ VNH?0(Xl,Qk?V<)dһxea(S],_lc Q? J j%U %`݂UTm?*Aڹ6amU)W9b[D), US^تN*ኮ*mޘ*|{W_*:xb_{b㊮V+튮]_~o㊯**M?*X_%x%u}]WaV6I]-m_lUzb֧mWu6o)oŬJC>OlRKʼn+u뀤巏"Tȗt0GEw97MDtTvP}k /ҺS Wᆒnpm!U~dPRҶOo[lVOB!;vR1J.1ф*1H=pHȍ~M~,1kڔhdK(Va]+_lGſ%'+ -gI=qUuZo\,j튪QE1U|,A|;bKةUኅ@w•eT@=O7;l*㊮*?ͱUU)oTvqUA_ju[[p4WjvlUpc\>W7ua*LUuNiUڔɰoNڏUx&⫱UΘ1W۾E X ꞕIFU[i7^ZjT8=f XmbBg ;S?j^a+oΧ/kmJJ-Nh}Gcl|342sxQՄO/贾DYP̽7ZL&jz>K}_Oӥ"9y+}.eiN"0Qev\Xoxjwr`2\l$xHU׽p2^$;*8n(E?!Nح/4?AB*>瀷E¸GqF()J7leL` 큘E%;6 79}Ju\(EadkC1U`F $B=UU"ߦ%D1 l1qbd }VZamTRiZSUmYOڲ׵zu8 ኮ֝qUq*i>tUEip4[8uA*ꊟlU銯1Uס{TqV_㊶M?*kb~a+*:⭃C\C]⮯~$cVE>mmn??~XڴO|i=cKmn+|-Tҕ+KEV,uhzopO?g]R s Xvϓ)+B4?`iʃ\DiycinE{m2em>7OY4(̟5Mu:<7ee^<-;r~♼[>hGG{=NK Xe^&W=xd#aaSiy4:e{EӽpݡxaNƭW5wUU XYZmZB ӸR0 CC$t+ E ž S|-".1dEi"D}A)Gv]*)@$i}0 [PJD# (rJ|UH5U\: UP?ʦ*N*EBqf|U]I¨>'ڰ; V|~]qJ#^6U u[q@UQNإp\AUi=qWn?϶**iC㊸R⫁J犷^]S,UuGhuۦ4jZ~9&.MqVnWTWUO +l:{bXb]R]llqV{RpU޿F*z{W -WtڔxW~~XM|՞&;h|>.A VØU{LreIL KƉŃxAXERJYB$NEӤ>"ȋhz֓T5p1x)OF7awNqCM$!tzW|QcoB4+ŝ"P7Ų1E/j}9TR)ۿʼn(ZiA6@ЊU4dbR0MQ("PtLzm.r$$"c6E;Szm$*P|!UԒ:w޸X:jW׾*)kM-UGንPv2?ఱD)8%GJ⫰%UV*+xb]}@p\R*ꓸw.*U^ƃZGmy5?]J}uUI]^[֩~|_.7_}LWıU:#ӵ|qWWz⮨"lU~P/lRw*zUf,/1^'7xwL6AڷZWju7Z/+6ڜ8fw> ̜|>VC}OmE(W}R84Xs@OXHeR6#~M McFL)-F(bS^2PV(oea6esulKcfۊ쳷3Qs ]ŋ"|ij,b7{{B(Ҧُ[4"E{dv[Sp28X]L#ڦda7n[ug4qBkE,~dryx/IJqߛYC.0I0/_!EN /8Tm-b4$*<@L]p[#hYy2@夏 FO y Y;N\hË́S1S)"c Px >b u?< D)ʙ#C+Uyt8P>UTB=KcUծ.:qEJ?dwŒS+ TZ\U\dJ۝/qUa]UC OzTbuĚlpu4ZjV\U-MWtC**{} ;܍I'*g$7^Wȡî,UiNckMWn1V Qme$Ӌ">5C\OUԧ*=WVn|1VWr><{8u~H.l_XM9ؕ^c/Ԭnmgfi3|1zœie;< SRR>Z v­sʧly?Q&}g1H:H!XQ.pIѕ5Ԝ8J2kf$֞C:֫_v/lщ_l8+[igȍK :(+I⍿nj(ɲqn@=SIl-#jzn% _1,ƕG㧖y=~IcGKNDpiP-HI_g/8Ge|l/d *>j YʛT}wlYw H A@>CwW$ B@Ƃړ dv[@Υe}Vaxt#4Z׼i B ݺ< 0rPVav^(7뀆HbHQ*o@{tʼn*Œ;׊L WqUphoU]E1*jjkZ Y8X!1Tb<1TFloUG\UTA^c ®&_oVň-Ř-W(u*c\*8P$u5;po\O|PLR@vW^bWyثU8rI{vȫbK~*|Uikl*;_AagZ[: =v3e ~quB,D_ንe&r* [T'D8!z⨥?*Z8p5LP5ܷ8LUf/p%Uծkߦ,JڜR/\\mWVObJU UjU*U]uiO'c튷lio= TVUbR=+UmR>x$xӶ*ߨZ튮o㊿n&dc)o&9o$Ԍ\TbIZEkya/aG4G2zn' ?]Gaf`>kL:LaW\4<544.X..5un4ZR"8a Yl|\s% :ۉ_aH7ZsrU(G|A#H 9Sr~-2*xlwҔ7oS{o*Ό?iO}羌s.Qiv?Lk׍1ˌžprx0d!!=u͵=w] J,ڎ-_&ɉ;5"$\g߀xS Y-AZ R'«׷bRnpAUv`d 6Ph|R^ Uy犪b_7>U7[⪀aBݽ2{bxlUw/lUۊuW]Uj)\Uzኮ~Uu{uGVkۜUqXS_RO}88?擊สk]Ξ\lUCl7트zkqU?uk;UQМUҖ~Yy;_if'/%bk)^^V+!gӎA sͽѓc|sk켹1Ow}z(&qUdEp1ⰴ~rjG@A8Les򵵾>}Cw_n*ơsY7$Ce.w]vKX2dNK ӱ9NM􍜼3\s=QK_NQuXv&!=kg`2 1O#Pp-{ da)%_NCB8JUO[& `SoD'im` rlecN@Fgt#Up1WfE,!{eJB|=r bij*ƫ%%?O7-(_spΞc.-ke;\e31u?~Sa+g H$ KH|N J! +|yn۞,p0} S/J~6B(YB5;\ m1d$MlToU_l!!]i(+p%Pܲ` Rni ZŚÅWTܱVlU_?lU#㊷p?UԜUVRUi$K@\*F*#Ov㊮jLU҇]c\UiR{ثu=bAثuVT{bz]_æ*1WWolUܨH&w??lUuk|UV{PjA-pKi*5RR7ιh4߆|7j5=3UuQGFFV_7R<3y8?-I[xv/ 5{ >XC$>!:؃(J<zUb )W7ؙreQVzoI͟5Ѣ}F1Xi^O2Xc\݆L{H9/85F-ٗ=٬415,NVt%-0迴:dF̂Wm0+U8ӸVRJ¼iO!{m푊Q}_FB#Er<6ߟ?*a)u ?Mg{u5ٴp|$2b|(iǥqWWlUqVjm[]ï*>UuG⮭E,Uǧ*MV^⭏qOUxY'|"唐`[xmACC9,l3AF~`ݗt蠸֦ )SF^,>L3YaP|ɯO_[?hzG 'S+w"4 qv^LO6hI<̏5ܶCB-#^ZĤJEj3q58[}wQ2ܺ)V 267́'D\p,FGZmQmK`}fOغh<1[iɃͩ?宯>}ZQf%$itsw8,Oժsoq 9Ky`*ڗ@^`I,Tєn:etˉL#߳Zq ` U)\,Zm#mm$ A Q%&.n^ խF% NjXyӯ`E<1T@4cb}%On$*!OO~IQv;.R:.-Fl|z`U`~UN̸ 5Eb{H@9~$uoñ0'!.xkƒX%SR,O>?5jg(m)a^.TǖvJ, |)l-L_=F 䗖u Wr1F)Td.&0ńs'ݑ |ѩD _lMNfRkӮ*+]TD$t?NE)dJAp5FDj\ Tc:r՗*P;l1gQqjT^dOъ~*6ڵԠqU@j:ኪ)"*ҟXW_*IoWW~ ׵}M W 6Y$cg`$"%2Jq!ZF@TiF9ۿwuƖ}KUT.*Z]ZvWoZWTx[Ԯ*!Bثu=1WWw.rU[ZNukҵ8u_h&_/WTSqVu'#ʠ^8JvꟖ)lulU%OOT2MY*d6$8by2b;9 b}xS{mrp 2:S?2,Ƶ oɁ^盏0yWߔݣ6&zIOQYû^_H{O|-"Iq]OJ7˛p¼?3U#M=1c ?HkY>˯Ϧbg!Ϲ=;QGj>d]AW 7~{fNSQvP3ɶ)A BS-nCz*ԕ = F"I~ԪawP_̟ 6ZbOf}F 'Zh7-i5M>U$)/S*bJQ$⨈I_ +*L(TE+ /_*qVVW*JՊQ)Ӷ,k~tUӭkC $JbΞ銯Pb)?FoW@[x͈sK2'uO.p[k3xq*4Jt֣oRMr8xm:mť+$Q d-|S7 yEet19^Kz8ehD㓆oG͢hpk.4@hnE8.\s1洇V'ZPGiSM?ڬ3/lM(ѱa-8 ]s4icXnr8OO1-3VuTүbUf G$ÑSBHkW|"$(*jWF, e3,i>bRG$|dk!g/*<\gtj` N?\BUժEǷLc)_9S'^#ɵ+<kig c eP̠X؍ E0#vȘXlzP 4yiՊ]i#.b $6*+2Bwyn0G1U[lv$>8z1V7bk{q>+~ͅ.VziVNp"7)n[4dI#0UG jGLU M3\ei?ؑ ed*w/<ݱ7ߗ>aKFCC cja(1ʑ_Yиܹ5i"'ޝ~Y^7ggq}MKGr uao۠3釅vCXk| ,. +"\I>ߕ_$''mzz~o^ljd='mi[V /!fa?^ἀk妽6r>u!{Yyvn~^PXOFu9l|ϥ> n˫{qx<ʶ]Ċi؄;sR0BMfN Sĺ9&+d- ggU<4V[7_/\Iehl5ypmd*-p*0x,o#L|d5(@(zvB67;aB>X Uc$fPz[Kpݫl9YlRt<7ʎL@J[qurƒŋ4% `CKm\x |Mn5TOدSp.[ۨ;Z[_;4H_VݱWˁ`UմMRIR$ʧ?&Zf4Ld4P1F `hCxV6DM>{F.==AE-n\*߲͎ G o4lJ̀6Γ43GŕdN[q<" ~boZh+-~(W$J^,hUuG ~xbFXWq"6,$Bқ`LUUYz Qs |hiqe#jO(+ Px⫁~X]RVZA]lPM +Z*U1TeЏ !Z(_r["<dSX2TZrJ7?yN!zfqiUUYJFoϟS]J0z꽛כ̋v}BHOٱOa X)nq^JQ>HI\QL,HR䅨?dB& IiVQ$[L? =AQ7[izNnmu:+؎?}OEaFqŁ+]adPTmSCt9Ͷc-=E[[dc5S*BdRZD$.^>4@?qHiqm à«ԜUuw*HtUPv**iA7䒼e~UT;bDm^,2\W4R Rk,D&qj>/,W3T10jh9e*m<57rRG" $#R6ҙ-hli=$&o~D2U{pE'lU?p2Bk(<Ȑm+!m#%kZXbdƬ^'j K{}ZoJ "ڨ cO`& S2' E\AQRf",lyak_(eCk>kŁ-{nN,x{xUP|N} som)ۦ.#"~2B IR L])Zʈd&&/S5b9e&jq1l=vq 2f&VafM, -Nii$\V:HdI4ݿg*1MSʋLm]w%L;P `)sqc(Bw0!r`e_q2f.u26(H4Dۤ,?l-s;$šZMs$)Tv8br`.cUjiCAۧ\UmO+KB \BHZk8+S|/6qLI3B8G&lPgE\=wK#Z)觗ݕlv|9kZ{6X9s]8xAk]>Z\^W`#RMyd>2z\$}ej7LDS&Q@6ڻbvol3.$قIk&*`;|k + 6W$hwvA*O#-@lӔK`Z*P3㉫įј2-3f$7c N",\1&@;PrD"-Ge GX2+CE>Y ,ۺv̈ǔP,g.jYZo7_U|FI*#Y7ӦJk҄Rmt]⼉'FYY^Spl*{GLf;QZ]~cdRHA+~"-ɗC0f\f|:8~]y[v!M:ڈ~bweer0ʟqVPC|L7Tf⋒EޑizZ Wa|3Ło4> w6rdCxs &!Wn1UqKyKL=MCEb#^Rۏ|GebMOt7\PӮReI̽9 NDeĺ[fSk˹3"/QBߵLUBGS´G P8bV|$d(ʒͰȏK1̾^>};; 4h_y}pb`/-bh7ylGl%DeQ0O?jZ~{.n쥎/NA4%\Ov36ӶP`A2(UŐ\ 1Jت{/\U튯 wŘ.W6),x'¾ Ҩo*Up&?F*@ *|r $2 b8'| dvBʩ9t0V\UUޘ-U`OĮ?v6.x$$IEmpi)Y?E%ekmUgQƮ%޵m]ft6aRUsSR#K5vdfws.n,9p86hw"֚{ʧma6inhzx90Z1Jf-ъ;e4S, K`ڹS,@뙘!ǖ$Ε#6 Ho(!Fdr튮6Wއ"߱Suy1f6[ś\9EyfLw 4tpU [l@H[zt4ZFyҞB­*O13F\f^>OTvAql>S۷yn\䅇EܘI쎜Rm?:<-f^?q 䎂x¯fYhx҂+fR/~ W3aX"&M7~R:U;Vae(e/|5ZoG㙘9^%~Mh8㷈W& qS`RzNKBXE(k!Zrȗ~kygb?2Z5TQs]q h8^ |[5VAU|UY{Y*)Tb4n+1Uo]]Xu d`}- q[TkUju\銢`)NRy$ )oFkďKFY/-.o O>v떍Ď@͵2t#l4"8[a[DG.aÉl*z[t?<<)1Zk^|:k8W )pڵ4ʊHBJu"%$QL RiWN03fqcrqsIr卙}*i3q,I>Qc\vC+'LNxD *SEqöJ9EZYqn4Flp-R Rmeu ǥZW60 mxC p8˽z!*?d0Y1^F=9ך0܇]PQ/4}- :`ܯ&T"(v9[JT5rh#f>=2$7~~ݢ>T侚Վ0Y-[]R;ǛLVrfDysvwlѯv+ؚJ-AeYd|y9ĐXe8O^_OEI+,Jv~8k犮 AB:A*@1Z^#~͊L}Ex -Ε7W"J)z6FP ڙ.%^z2c%u}bA+)&'pҵ9ɒ[Zt+2aTi'|M&q-QF˒q+"]-2!xti |X]O\O|+e"C!"4c<+ħ鎔F5kv])&*)#'vU# z OM%3H΄G. kW|>gj;L^ sO(G:٧ȢI+NL+YN\MW_8/@ͦ2;w pBvӥ}}͌~$PMFI7zď!2LU&Y(rMo&k$`*&̍c$C*ao?.ۤr>ójuݫ(x_ si!zn:WFB;/Uf8qs}qK6t!@׺S}Rj~ant<@FO`F{fd6)|g{: eO5:|ǜif$ RcQ]Vn튫~ة(=6XIsߖn=}" )=>7Ff-C)~khiqV8̇ۡoj(ET5̞ [07:sΏuH,y ~e_L&iqO',sm>%18y+H=6pnJTH0Ai2]Su&WM1^'O"wM'lXIeK:. &WW v#ncO;ShU=6HMWi}rh^jN*tQQ2D>U:ЊBcԼzmƘ&6K;rE˱x\d=]73pVPŴUj+㕡rPqUt4"b|[lLUxm=W=ܾV.A'h0X0?&qUe>%6?eeT_r)pn&pqQ*8ɨ)HY=P s")n5DE8Bw+9x}PX!^ȝB_]'9tlʼjG]j x'Ǎ0e'%ZIpQC$AZ2B@Ncj9$uD^!'&&R\}^߆Rd&T#ۆ#l@ FF͑cQE5ڑeTi?)=iTr;tG܃֖IO8SiEM"bkL"UH@bZf$g >$\04@Hm4\\ж ĹtzilHZn|s<keXd֝WWr@HJ0og2'z淜l4}Oӎ\y{9nrv 1Y+GPood2ܿWo9D"ww^y G2Hr=>KSLKl=T&TNl&1t7ԶBK~}/-cҥ ۚ6oqҦ| ^U"8K(?>`:XZ^ un4ݟSzo|'6eBo346l@KYQF/ ۍq&}4סQB)Waت!Wq*nLȄbu4p$}d UaJ./nXx[)DV⴨mHkw (h{| UAoNb|㊷^/ت*sASҿf-w="~ Smjė~PUb;glz6ˉzMrf{= ^g{ ?.@ڳ | H5">XȦkJ O+.TҸ#P [bQHdȡq'l,a۠*{enwK1TJG9~`!UN/lz6*X}8튮UR>TXejڼP_E1UNA YqV8HVos߾) val_ U8|5L8 6Dmy?B-ƢFdC n͎BVFdn]m*̑s6\ $ZBN%[|G݁ }T UU w*Ax s5Ơ+ A`FFH=r J@em(l4\*ZmNHl B]d*TLE0>ighiq]%$-g-K  sL@D>N;}鴝D\,'m7jͶ=?gN=)wS3eОX 1Wlsy!FX^ɸ=cV]#ZOiOY7L_q0,U ?o \! "rgv|t%״{ny'zHJ5:~3%ug ƒkjƛyapxd+:(;A$-)KZQo ws&7H<ߣ1yS]x{f}ƟME2Ya$Ax::p]Ew=06H+*F@xuBND"v߾,Q+~-l+w-{ i!y}[]k:ⶹZT!H\Ek" z^ԊS|X7Dž[DžU}. تo" UCcJ7ƕP6r<)pc\xU_~$ 7r.lQkFh+Fu2֞hlǍȌftaMmq&QģU\*ǙFjB(7|B6}F1!JU wGPR(+W"r"һv%l)9R&FVV2m1Z ڇr22mLTC㕂G'9`*zo(h+a^: *tH9&1H.trT6㘹,V Vf.ǎ"zU.]R#P:tͱrxE{ި >&j|NN;8!oU9xśg,^~)KZoD#Iodz]vMg,Se[ar~iWv/ qO&9; zS-;-?WfsPA1OJ`jeœ ۯl mS XZP{"tZ: LfZrfK'7oMОNf w|3M$h~m״xԛM7(݌(wL:N,q⪪6l*ӭ2*.,QJ>dzx{aȥxsޔqU܇ӁW+GsU,5߸%p2]ϰUAZ Uxcbt[ Sbܽ )Z%pJ=Cx$fnNN0 >* EP@s$(ʞrJڠfa3 QT5H5NC2#CqTRh?5_ .))`og}+Nog~Rj~\ҍHȂ_~D{/-鷺=jȦ*Q}U{Z}\ \X[IZ$U܉+C|ijH[RkJ}i[R6"T7jWА}끓u5_\a-ڿUYT 6iW+TR5*mOlP ׵1fBW\W*0q|i`D3fUQNeŔMr6#%J[ZT9&IMΪʌՋ^ʼ!\ǖU%_@cK+Y֦1PrrK\r$ p!˓o0Xy/E_h*e@O!r˾ʩ]ܦ՗~+hl\uQ%Qh)_ Woch?h58Cuk)_mVlUo}|U߫qkvJ@'*|m\I=\])n,]_}U£э*ӯN&)emr4W _. qU+m[a*=ppo+)Trd+?ˁQ\` !`r4?Ft2R03J_l"M-y19sV4ԋׯ_ 4]/L@•:l*c%H4Ȗ@ADe6ZN%"AN'-BnkV*Gb69h*J$WEX,ƀțUGcރ&4 O $wfJ፪"UyjW›`2T<.GPH)"Q^`jzdHK7ds.&)C^ F)=Oۋ$Hq+Ò01U욑(Œh ƔWa%TY8 a9-;צkOM>X-E>G2X EGC-* 1OӍڪr=aJeV߮h7ŊnUҧ\ >xϿk]o"{Pm[\ރV=jkbBl #߹Ka߷ZWG "BC?,_UpW*tŃo኶d]qV+E&1WVzK_R/Cqs޴o#}8#V[֝kl1eļ579 %kK$,-";JYh0 MaNjJqTGvߦZ&Jҽ2U;UC7jɉ*55ljQ "J1:FH2NNʤgj䀦 @}Vv|i` F+MPpD`PvXH&Ijg-*{=<<)WCd[#+TVڟ#J>Gֵ›R.vڹRTq|`dXl?ÚMحw]d%{߁D (3Fč22 *lbo|bjHb-߮l57*S_ZWnDujydRWlU_LUܶW)Dž tW{l |8p5JHURI2, kv\YZnmS-?lUSs1o*^=pSbjGMG[׸V?,UL=Vtɪzy͸&[풦3ڐi\iiˤ?@q\Gm@P\[QmF wvYkrCTTjgLUbibۭr)\%TV \k$#%FdJsZ[eq%0&.X@@ί @ItzZvǚ}i(ztC i2c&OňUM7*Y((ϾrΜj l1R:.,iSƜlu銸t|[XClT}W=M)M𤪩Oa܎d 1iQ+`(^pIثwVWZ}7^džM.j^N*j*>UTڸ3+^^ ^$;w16%ǏLWSzLI[_o ;%Jڸ NAXۦAUTW* A?"rp?w*ȐĆ(ڕ,AKWrHr4m_˿^تScC1ER﷉ToJ;^&Ӡ'G^ ix"ݒ zHƑ&?S]¶Zf4Q^ocĖ=ſkll6[N~y麡ԙ>ɦU,m|OO~,nr'PJU+ Gv rQ]CJ:)i]'%$&k{ANC;l!k^ܰX$P v߼ %O'ּ$^N1%FH]m63*A1&L; b})_$lj 97FL}bR -<>l=^7qfA x+bݿ朂xr?vL]PMk˜_\SkTk ^J|]S[ G`XTr?ۋQ\U1U@ř* Mʈgjb[CakNW";{m_Ҕ## ć# ExVv큁 rz⡿X})WzG*S_l )v«{d@R)c5ɵU=CbbSR"ˁlG$Tʖ12M +DȀ;P,(Wz-diCJ풋LwU\샆F۝H")i7L aJaizBm1) DK栍ǝ LM&1>9LÑ#*]C㕒 . H hux Z4$ dt̨qg準wzG*|Ȇ:u3X'rzds4?4O[9YExBw5#K ?g(Z H**wr*]և#Wl;ثb`mP0V! 2V%>@ E01^ːHD%\oשd dQ]h< FAw. JG ӧߓKE=Z2iw M)Q]ȃZ}bky}R5ip?|iWҴ\Mw3]ƕQMzUߏř i ?d$Tir!UMxik! P+L* G 'lHk۰8*'E XZ}صQ7)%1Nbd?qTLF5nbZ*}HT_EW%=`J ^8ˊ &":ӵp47'%p|74XsCCOqS6@'qsql>:sCRb[j-=,Wנ6kŔSX#Zusy-b7rg* [5r>v,ʠ 2c)\&ԤYsʄSOg'Ɠ>q}7Xt`:eRmQeM2˘c4dBMIV 66UYY0U}zl>#㒈e, ܷc\RY_TR+`J2qe 7L~S"+[=Z9O%(`HR$ R7lZ`kTUkR? Ypaʻmx][o 6, XJ! `mOB?~* Tvʑ+) S@*Fw:|Pǃ}*$ZtŕZ+.({bwl%caB9XȩI텬j i^lRc*Zq*<02n\{⪫ObTQ]p2~Idjd7E#noA` 6~تvmڴ&Z,vinZ~^9 B6$ ,wڽcL\ ߩ푥kyvƕ[ÑKfҮ`+l UC%qmzk%JvфI^8JKy(Ie?RcW<}6ȺK -Coᒷ, WhX :CY:dJc}Y; Or%Z xYq4&x+ "u!Or!6<!aIz_/jq8\{8h܌%نCЍ\r}U~KQJ(bkQRr$q%W T,@o uMu%Sg 7o+DցX8"xnSwNؔAQmȦ)lS B]Mp`'1[lw+[[fH+kKtlXJ^MDN}`7M ˉx WQF`eSQP?Y~-E N v! "cZԍuĖȪWMZaf6Pf퓶V iAYn5߷(S;WhSIYock)-`JWuȰR,*HnFN\L).&vkO4xUEEDB?ˀkZeڃ-R*\) [Vȕ%nT>WW"BUާ߶43%ߎ k |(QKnHj% 90sol.\xS;[֝Mu?J?)ZZ<*vXME;ZTS) p?Vr<; 9`h*L%|z_%iԭߔj̘G-^FIS^*=7|]tBN-O6;}A?1! lwʧj\KWfɈ*$ԍz  y#T鐒BbFE*(uȦ1Pn ȕ`GQ8}\ 8^GъiV7 ׷q,X5]<~. jVSASM*1eT6H_clQ YTȒ;"xJ){~dUW-[JlY7שA[P;brتj|zlV=vk!,Bw;rT P!6(9ŨMڧjXpQNz`TZҟ+G~JU㊪Ei ~ AbdОn 6l$Xӽ<|rnĚۨ ]IJ-r)D|ijXH,x_|WUêk\V@l,.BS\r+_v–q['®V3RVJ\֜M1sIZlܣ]e˴NAeRJb6PspYZT \kBk"[)3WݶS$K6jRUeSHD-xc)D"o [FF?k$+Q޹B6)1biN6* C**F*k]\Uw?!m[ ŏ E:}ܟa^ !} 2MMzҸ ּ~*eTb `ߎ-(] 2)XunX*~YʥaP}Ř_^J]*{/q umK~1U2|N*TgŃ(7@B?",`JLYRsJ Gj@* tj{*@BJ}lxo Iw.Ȗ%j:fŃbw|UB{ dX5-qe.4}Mz Rs O]&!h?䉤H^+ |qUV|UPw$ak1ZJbu߯\U* wL.%ܿlWa>iPW|% ^%-BhWA1Zw;3$;L\5~[$?>/\ hE޿0'\؊bPGXZʉ5&PܦAW>49(D÷@•MxQrޘN> z~Y|*zPu|Y[{bą\PVv=@+i +M­VV;1i7\.?,`~XvT@L PbF-YO~T>dnJoBJӥvf$ z*J~]e!HoŰ7_ ⶴ+k*>T#~ E؎FL*eG8C%*8"w偠߯_-kUc%E'踵!x4]lP5FVST,d3#v2 $߇.?X =ƤS ] .ċ&=f\ 4?#vǩ=˖!u5@^S` (mvUbQ@)V++.({`=r,AP]QC_,a5 bĩQ֠}50]p\i%…C^)6lX,;SU5뀳bq\@D--UF-*)YEp5߯.UPwl~ߊ+ڟˊ[LYD}x ɮ]kA^hTٺ@U6n<HRbOJbJS$_t;8ބS M&kS/+Axv 6ŭYJi]~)5+)IWqcl:b߹NK;:c7Wm#PIDZ[3*{ck l 0(JBjF~j*U0j%EڟJDԊaEӃ/ iRp%GcZOb ANcOŰI@w銉5\Y[DJ"Bac'E^Ub84{V/4]~(=)?hS9_k u0zT~:|,IZX"J4#a|\J[U?9\*mZlZКWOp1THE+qH[x%sMbg~boF(.$ܺ?Sޛof]PzסȆsSSAiU%HW )OVZlmSKx}b>&J'_xuVO푥PH,iFJdpN,fۣbLƹ ևw\1=+.iPfʙߧIT;N,GP03h:8h<*QOl nn8-ʊyOjb!ʠ=)\59 $}ԊXW8)F`8Q^1lq UFI [+ d rҰ|1Uz~mD7',m燅~=^%W ? bzֽF J/\*n "ZO݊$mتQriT+ҙ[=F jeݴ %-Q׶d[2=֙=ԌlxW(濫%Ž%#05Qj- ¼jEۧ\@bBn00}kڸ`?Mc(~*o+%D&\(5= .`1Θr@59;&KP`HBlDcW#'ap{b6I>,Uc*J.T%p;6$92Md;_TZO`#-Ow1%p>g>Uٱ[T ɱTqf v#GWa qOF" KyxLN* \}$MC}7;˺UM\pɪkƸۛ!v!bE~.N1k%h`M+N=Jĕ54?ƙ?xY#o+ WnA.÷,) ;QPUwB7oW""AN=1fJ #¸Y} ;G*~KN8(l?k.-k ^VJC`WUU~WGÀك|8Ĩv,^,i7xdmަdm6$푤mcMJs&c^jMN Y8,xICa0QiI߶4&}8X)rQˉicZDv'b6IE\2ð-k՘a![ Tur6T`czub-Ol,$G.8)R˻4?ꌜbӓ%1 @`vrȗDV8UY ;POc`iDn 9V╵qUuT6, AɴvLsZo#_PJcٮI mWP*p)~Y^1UU֕?1Z6VU@~ U6Kń֝8p`F¸gFb 9?j v83hƜӜ72O8ZON'oQuۥ{rFW"C5ESцRث`k'Uu ͕TT cٱTW#|0[w&Z,CVp rt?く 쟲;`, )ȡ}~تڎ.@ƞ⴪Mx) ⭆;T h *9#aLs0'JݾAWZf vM+bPi&2 za"K91\k7ءii"X'7j >(֟$>9"PwE$||( 1 +(R$YD/,Pm\"i-k8(Y2ziN&KvnG˜@KamNIUkVR@ T+!Wm] vĕ}8~r$|>A6, lUovс5ԖW[m0bNv\dAJ}튣5XWEzd $֧j}1U밡^ U4w*svȳT Uo6AUo YGOsN*V>S-ǥ+ #j`cĽ=0_"6=@8- Sm?[^zWlJ-)ݫ0N!mFGM2`4rjERiJq;[ S!rGqkدP3kŌ!CSL-\Jd׾Pߵ0&7h+: f ʝdbŔa$]wT~ P @[ T@Hr%(pj!1EP*Q+![uxMH-R 5\#Si"NDxhdvVSa8ɠPw4CP6VߵBM(lvmm]% #m4HB +N-R(v5iHIGĝH)1;oL*U#jbኮ O4xBAWdUD4]ql9Y&, IBBEh(rLLODCD޼M3eV*{>92/AHdR˫ABkEΘ_rdȑ+I;WĻd66 ڙ\pdqD:mOs# Dޞ;ضZ)lc]~-i  3mŬ;ߋ7k 偂WE?&2H1C3wpr8Z+VqAZEX?^U9⪠⪠2ukMU.nVYŒ\Ua4\M+֕0&.Mi$_ՆMWDXeZ'ߺц@Dm۟偘(nTJ!OBM+NWPr%}T6mj{Cȡm\%oԥ@ރ J _IDxޕp5+*h@(ŭӧLUa⫅lUp "G imk: 0% ]&4I6?Qr\P+hgbIjE<2tj xW~Y*vŁ4jLW?J jv1eT{1^*l!*OY X@.h!Vnw,4"1_s$ A_zbTLJRZ_,\)H=F5+ 4]}hJddߎ .()! FTM׎[$T9nkkxPo`bvE+}^ ~,h%nO&oCtBē]L0[^j!LbsJW|waC獭 Ipr$(-čr-^`J5()6 ?wY}8QJ+,clY)1UeV+pחT9nW&(ۮ4E¸Uw*TR!z \IMN͐,D޹[ UA ֿrlAV;PqV,dzjŚN텘.tȱVdvbHh=$Ҫq` qN8+VQ&.Xn*qbSb+$Ri:Oj #n{h$MLQO㐑nʕ\>%4$4"~%H52l \,B[V|)N(S-2P{"ljw0X"Jq_قSRd=1A-? X*N -b+Uh{em%W|*kNclFQ;շnc#jWv8i j ݈3K"] v%ԩI$IQ :|B%XdHUJө۶Z\늡p* ۟Ւ wiN-rCy 5x isN2JcO dBPXx7rEpS2R>8LVFJӿUȳ^)bI^&H4 15㕔@G2J\eXMڭ銬gN80=+5‚З}uR WN) efM=Q&!ox׫W,]q@ڢ۹9@.G\A6NlGG2vS2U_| inG*Z{+a!-m"$q`Zv$]2 @нXX`Ҝ+m6;tTPIRZ[/2؆V޵ &s>?k(UJ&);:M { SJ~e5ȡC A] dNKn=648im{…ʧ ⨤Z:bt=76l IJ2(OXa4-+AQOxSoJ}Esb7.h{.\h ӈZdX Ol A@2%(;1e ZkFImzabm޻b)ኬ=\UޝPy y(8Dc_XIw$(Hw4=s܃XDrVDڙ ^~e"ª4;.U&a.>tcmJB`iQ*ֹM2$!b ŌјsBrR\^^~g(T&$(v clu|iCҹ#WZx>9$C_el h 핖E$ VYTߨzN=eOZ-^%+x#@012W d;B҂튯j4qRX-LvזM*nҢkFɆ*RtS_E]*:!߰޴)*bJLA5 cj֟dBzu7®V+6qE-w늫~iXDor$*[A FT]rڃS©DT,lCP:\yNϨN~\- 'pzd\p%,'N?3@ pLԞr%U^E@ /T{1baBjh(lj?~၅s2حC؏l1Bէڔ4łQJ +5[Stɱhw\Ux=GLJcD-Xxj"bB™YlqmE^EoUC;3l01f3m~T15Z̭}ҙ|DS֤SrjPQ*+M*Hiq@ T55H1Pnb[ J)v;׾Dź9-cLŝ۶ե2 ^'nwJiExZo/EJtYrn!YN={0+m~ثJU#w*2LW6,dzl0Ė}Ʉ8{~2feKJLU&' *bK|Sl8 %'`)QQBQ%SFmT$ P=m˚C4=*I %ѩC)Ԩ_`fe8ȑ;I]S,pg4:'*VO|LH"޴۾D#]r1Өa 1ޕȖUjmb?ݰ#Pl.~8ZnE01kqU64'%$2U߳5R$תk},lR$vkA~[\}\X,;WR)D&EUjW>x$5{m+-XNET ʴ&YBsOZ? KjzO IH(sۡ әsh]M:bU<bAQإc{`nrJ׷-kh8zVN-!=)W)!A) ۵r,v?͍*l `~#?S%QN~XQj_ۊ7q]{ulɼN/sqaaᆓjL´[ĕh 4j>+j%ŕ2~,mo"FmcM(Q-~:SlU}nتq1@ďTHJ-6ȳ࢝zijYk_DYJI+V[ڕkjT*X|96o/jq=\ M<*$U;|C Rr5%FIT\Њu\ZTݶz.)AdHI T=akSk]^wolmWѲ, ~*mM-Mȶ Wk+ Y[qMr1[Q,';m2tinRՙ9^yU6rJ=%e5JP}\W~,Z+>UqWuuZ߹T+r*ZztHV h9Y ׇۭGX\MO퀭dn;x.W%׏Jc¼KO݋%NJJ QnwȐC k$$4=A>4+rT# @+P*N(i.B<\,e4#^14 \ x f@%<6TRP^VF$Pe-h#z[~JEI4$m֟N4wӦ8M$w$6XHR]:)%Q rZ:.Z׮ՊWWھ'u@;[f >er%.-X@Ȯ8[ T vŊWcZ,X-2S?֘4O'ĠIْGDm*+D}⪕PS\)Uܷ늴)S<9|Wxz0\_S.%E퉊8A^< + 6_ekUoÎ#SF< AO!xkMC^%m]H*&@{Pn3ɇOؖ)i{d yQT0eĔsM<2Qa)%#h;5jےzuȒ0z eȌ(Q*%u0d ƠpQqQS䩨&QF5r }mTWb$ٹ)M&Jfgyz`r`8流V3AQr[cDpzgJ&0BE?mS8֕+%ɈDJm_|r"]wk%1AO$"m mW>ǠɀdS~I˃YY;d{g5?ʥq-['L+5Z Y-%!ʾY,iU=;eV"R8#m]^zdJJrA7ZU".(*|rhn^qUNx@犺XNzHgm+DJ AT Gop3շ$W,UIتX6Ƿ%~>ИIo(KvPƵ?v,a^Y"_cJI4֗ E1UT+bHWRvl5NˊDmLUg"{*X*ucL:KFMASVz~82`%O JKۿ-".ݰ 3-v+UU)2ۜb V,}]*6JNV8'FNJSscYUOvjD\^>*^+,}zɪVs)S Pz_ɰ% #hia%zoј?EJǷ-!?p6ڵ,NN@& Zw\V׍)bںq[Z{ -mU? =J548Kvʼn4Zw11Ӯ,H K{7lUz+1l[{c"-1M)ӏ,t4cEz|?g +B%MT <,e$ iS˖aėr@I# \E$/4ilp3Tl-\k=N YٓoNAǗHJOC^,e 3-Jx`K RXk2nich|#u9ěX{&nU(<ԭ:ּY(*/%ffM=CGL1UJvWs\/]zkڪ>8R^`f k%{a tz.~rMKvVrڸp5튪aҮZ$vhbVժN r֧n[(5?r*W|(E p+@k!594QuIBݰҘ1<[æ؂ȩJ+ɴ ~&N(j6q"aqV}h5&D\$bOFۡU!Bmˆ:d=)MNdBµ6Rڵ6(C1ض*P},Vⵦj:kM2Ԭ&VSf=`V劇[XzRg'$H:1lx[UZY/]*\6Ӧ(Tv_ZhewEoH5ҍF-dےP_foqC`8a]ň',VP_cZ&̅(kurQ 錃2"7BmZٶOŰ;(%AωP25jtJF$ӕ}BZM+5Y66 H]bKb"**`r AؓLUc}X qRږrS=@1bH$q^jʍCb1ZJΓXaLY"Tq_ q*o:'|1Jn=cSV54JenHU"c#"B$8ބ`jKrA$PhI%CSL5D#ĔM~A(6'=Dr=}  ,YqmmǢ7#"*j(),E8> "Cr{dAl C-+Y  +߸Džh}4ҭf1#߉3u}S繩ӓ!'3SMDw܆Blz#LW?/ UOÊ^낕AcJu ҝB9ڣ| RYoCqo PIllZ)z)Ų%_1gvښ 7w_űU<2;!Lg(Z h+.V6s]>~U !yo_XI t| h'mʼnԑi,\}kM[пAa'4ɩ ln;(JW޿~(VEPoۦ*ت{W*,mlZf|0R-]WlkI ߏm6F(Ԍ/b (2- ABvvnX%)=IrMFIL-#]fbH NTȢ~#d:K<)nڵ\`dCP2A$'taJ~M)* IZR l-kUK7ѻ`Bc 'zo 4ۧ]ra(طU#m|619}H6Ҫ<|%ܷoŒ銨_mr *nM1Py:8T#HlO|vJ̚t#j{a@A֝\o_H PLUQHgr6 H4Wl .LJ4׮-D!7# TTph?‡, "tbǿ*T8ͫerA]iiCtqrX?ΙPcmSHV7ᅇO~$e¨wn_k&Ւ"Vf1AFES}{␢MjJ&E;ŨU>ۀ4 k9JÈUœȢH&>FIDؠEzj{ʑInv5gH׶,MAX\(+QiI,PR] J䔒E7*_3_2Y o^pumCiLtB1 xEȁA)adfJrlj;bą&#QcT@:Ie"Z&R-KI cZ|^Z nb1mm+L-d #m-()~D6d[Ws#;JC[zĊSZNmFȳpm Z[ndy@ q!5䭬 \bJN6udn&[ʼn~0P+C4+ oNmYd kPȲ7jo,12S/wwŬo_ RI>=[-JdmDin'ċ\$_l+"dkĆO,j"$-ӦV(z~ PۨYĶI=Fߴrض Poܮ(ȥ?JNPȚ$d\֗"R} 2@?́+ZNXL ?ٓ& FE9TN P2JY <2$%_:+H -(>V}ؖ)d)LI"s^C۩Ŋ71T1=Ha_pיTZ#$"T䗎bԡNF$Vڃ Jڭ=،A[\׾(pf iG@q`Bv; 4o^5Sgۯ*)؍,e$[֟OoMR7۱Ėb*e+ZIlkNAW 'xU e"/~9ajLm !GF6n>!Ɏ5o|6pX?إ>|\I,ǔ9) KehSɎX PrNL%%%b۟&"Énq\K^tHBl4-|1 WmED)OXDWp4ޙnw˯E:] ͳצؠɁ逖) b[6d )Z7IlĀeGrb6#'$rZG;=qmN8ڣRFӛkJ{:\!i3)RlrZȂ kɂoPON2LSa)?JQ+#nVJRR'=N*s^=$?e`5$58%Ԟʸݩ~f5iRve"ĕ~^ͅ⦻m_I*M=g%w;|'Ŝ7_O[)onWFA*{ӎ;bR^[V|WvRu5#jcJmH6;&94IR;AS zwJ-@DDh@VKxVn %`(CQq)|]?gVz\UIMyr**{Tacќ@S4#v˄ œ7'JM#Oq]g eYT $^H4:ddRֹ?k,inl$!W[Sgr?r*a &b*ҹ[3&ʁ]$ ZGOW ,UZ'`7.oNWlTj64.DK&k&h (\jhҜjUIk< צBjv8"V^<=2DchlTSN|9\7̨S-( K!ZE>+d" `!퍳 Ūux4PDԡrˁ`TȨ;TbĬ#o@;'ȡٶ"gޘZ$Pԁ׷-k L(?\pim=›luq[h5W ωm6 Fƿ~FN16 #r7^;,QYz ӁQKQu-r"P?k"TY9XZNYHiz/l e432_Lig"hzXڀ)5Ɇ,W$0eo|1[FlMD4Z64`H +#CY[dJ-Z߿ltd;S \,&?Xzn)D Svi*$RRrӧdXi㊿Ҝn٤x+Gs^B "N2eJU25aN&Ȗl)FH)*+ҝrPݢRc7"68Ғ,z0 "Ć.'dJDƅS K` &+2O oM mQSµj}3 l | kx޻}UpP4˄0,cMs |Jovi#$T*k*A6$fJeIir59 |q6G91Ɵ[Rqr#! 6]["8n$&J5Ŕ#kl(j8_OaQaN~W TZ& XgZS~rTI(hbs7ĔfiS'݊xu7#bKYZij< د|vM1S XF6}BG_ SkX]r j8i(U;W JdUXAr(Qf~6fރK" k$B >(*E~( 9ڟͅ9?sdLH'- j XrlAN*vEDKdHo$oI|i2 DR=r2O%^tlۣ%';{E܅%Iw E6 ,1Bڂ)_8ӛVNi) OM`~^R'@nCK PwW%MR)ֆ,ŔE>8*)Rw;e)HE(>mar># K` S[`qӕ)-$}8lQc:d*@H N,J=~(C#Pң_< TzĀ:{LXZ\2DCf0$WzbVi?[(;d "nqq,D7>YeNnYiAGڀI$ȷ9iZ?2UQ]ԍCbNCI=䂡_ʇ\i(I]zqb #TS$"gE*A&#qPaR Zu!z}Jz|]1Z:R0ZշS~;kKNg%h!MOO8-HѶW$(ňrڝM~^"ULAoڝ?i\JHWV=k_r Uǽ+{%Ӷ+I ;bdfuȄP߲:cej"װ>96x=>C-\fI\$R8 U46hjpKlȖrV ʋ(X+ZʛTK6*:/?(05*$|`'w;eӷ ?a&VPvɴHw~qaZS$KQp`ibI۶NNm6FW|ܘ Co޹B۲1-t#rRޤ*(+1UJ)3>,Tia홍|q`bՓ9QOD(\tSH-IFA*W A_Wo!ƞJc_Gӓq%HJ^~FJoӶؚG"T f$XoNѻ\KN\&n[cJw_#KiU*kHu@*ɴ_݁;Rb6[$ZuojNdqٖE= jَ@Hz iT ̀w3Nd dЃS㕙2 k Ͳn_lT֕t6+:F&MRk-p~h߷ |2Lyni$}w9sSr CT Te2 [-J{dJB×$+u2nRqN" O9`-D(ۯTgYDjbăw;, Gkѱ`]o Ux-bR(?Ř-#5(\l_T;OHJzj+Ajwe[$W$k#&JEkJn{X 啖RZʋr=7]++m[k<kO"JcZkJÕ}+ ?-j-JWE`Ztֵ2i& _9A^ƹDy"BYq$ 2da$"?ؓMq뺎%ʄSxp6s r(45dmwJP%Iu4ƕ.GN̒{Y!Aq !jС#v!4<'r71@'ji CwDɻKALxؠ퍢ٶ4"[o٦Sg;~dCO1Cw4[W (M9}-jfr뀖QV= 7U_s8YޣrLg݅P[!ݺuzZdz^ Bj~g&";lw]jXLj$SpAl[֔c!k߈ȔT5Ʌ@H;o˦YX;zoۦOd]HCV =bUTM3Mnh[3 4Ymڀ9%]M+ObFzQ+MᅚկJd U\*iӶLQ ONY?ЍZ6cjb|9YnEBKu׾TB(G2!=Pv@@ݿY^"%S P2+<*rRY6>/ u!Gnذ3K񘊞1Oń;wCuŨnȬ<,% }pӑ Vmr& dC@hvjx4?HǶD(ԠQ mK+_Qi>W*{ax+Ht5 &zR)h{/L-(9q1#%)A\m [hO*q䈎,|*)}+r&w2m(jdԕAN oR ZC$upj2QcCubvdhrҦJҝVVJ A~*aCUEp*(nF$ OE!{a8(1ݨw_녁A;jO(+:M*\PT ToN@E Z[BFvՠulmc2)~mmL*M+]V$sjt ad-tWWS,c=6lXO &< =-D)JWa(~,֛+F~A *w T1,JeVz49Fn`B J'쌁*9db: 'ц+@W1\YGeeȀFțlrC|"M5hkSfmL _k#&I6+ei|7m\ BĖI)LXCf m aJ@~#< HD⣦D91_)/cș.=~ȱ V 2wREzS 4e<[ΞXحO#Lb20bT1TvrDEQ_n"6[+ڔe鑒mI uj\O~{ oNiS"(z딖*<Ͼ!^ |?x<1c扩[Ѫ694ڲt>95zoISimZYJ}˃7W$LDDq=NDCZ6\mQiצT`B$b %i=P4ߵ2Bylhk(i),i7={-Jl6%zW|,T+w5*ʊրx@(Wzx!A|0LkCnu?` 咻o\ !o Yqߧ.H2Zk$L5Bg,UR _bR$Յ- iE9b|G|ȹAXW"V 5ޟ*%w;vljm-);oLAPv1CVIDA?FVҊG/l[#$@=W-q9uߣf1|(~(M9t:AͩPP0p5O*K>TogiT=h;E 9LߍxY$555镒DvN?͈,GCx"}>vË1T '2E> 1JE*(ȷbﯼkDuU %$A,>#-/"c⷏y $r;7lNC:mːcz~aHVWwdX3%a`H~?ʛEic=z Y'zMTJ|@eUjw&<Ӱ lWMeM˖NۢrjO]_UH꿴dXHIGZ4Nd恫ֻj2C1lQ*j~6/*2(TqUT4"UA2 ;SVG 1NI~tfYSmJ2iPjT/ג2k=; b w4hrc4{~| Rȇdy?'j m5Y&-S,krL\doXүF&&Mo96R;?gJVU$lx6""#n@WAw^9Q-U籧C)%l 39nWWlxƽ)^PsB)J9[Luŭ^3Fb#)ZWB1Nϕ#!2lD8Ji+U9#0|ҭ eI6MZrjlD4\< #)-ꬺC!+!괦D#TV|EW* /ŏ 乨z6KYWZ]:wKT_nU\v Vf۲}g.dr[,oazޘǑj&-.a`M"QmYX4uc7˷:tW|0_zq%8UՆiR'4LhBk_6~$*2JBLz) Džw*qmQZo+%:#ĆM ۂ;m Zm_l ӡ޽PMzrm }9&6yRmCX N$ \޻x`NTɅU-r(_mZ}=ʃ UkBMj?*,M|G*MޟNL߽GoA bbw?2R,~!,fP߆ZKJmmV޽x8j5AUW݇_[`h6ͺ_N)ǀ;~W $֝+iv5Xz%W_dV1D %^?r-"oo핒ȅ W+,vQJTX -ME,m|b :3}.I&w Fa&#_OȢF =2|\E Қ/0[l]PG`.IHG&S&m`0[ o˹'Ҍ=6g/7Լ}d_L{#foHljSV 8B=@.iVVAlDFCNįl_h~O7glI۵2샳V(=x @$BierfDX`ً1HɌ#ԯdWsӮ駣=@zJHBW1dof9@ 6۩h%՘xPVKo,PnkL@G(ݫ}6Ī TS߫H-ڛPJmprbƻ H*NXLC<,xx}lXd`~U푒lG_!UCɅ]PkBa]⭂iNZZ'q@]!F<2P;dlja4ȡ$Bhnj.rh*Aݗ  M=0Uc [ViO 6¶O͸b:~U^V9Drڽr)7Imu@d ^Q,Ipq[Wc*bT%;߶@rT3>ed f'mAJmB6E_o-Հy[ET(/ho˭:aaF OwwH1)DYӢ#deJRiQ0d8PB[6=*C'XޙW 5K/9ֿ/! ifq<7ߕEsn2jO׼8ّ91'CGȅN¶ 1lRN2Br7rDKkqU?+(=cO> c2eyJvޙ{ H )bŐd`@3a!'&Id+ |iu\}N%e?'e[lhT a5.jxd ix;lKTxe<4XԲ͸ᒗ0E{) dJύr4mr=I Ą dTWFŴIPPkS^6@'W$J.0)PtL5N&aƹ8&b>M %ޝhc_| =2,JSS ȴ+Q wb8d!U@ӧNFLH\|iZזD2 gSJ5Ƙq-^c9ZOcX*N|(xr)\L;qǒGOA_ikQabCj|?iZcj4܍d}WjAei+#0YJ7O9lK].Og$^"ֻmZՁz⨄mF*R62+FV+%oc'"KGkpnU&һFTXAfe@HR}~\i%Tn~, jhȠ߯١C8vd j>fX_D/]GjRr^S PHDAwoD}NLIhI&X;S zF2 B]>)E8 )^v)ԎHȰ.BV8:&ΟI7q#,y_bP|Ӯ7Sfjޘ;JHˤ8FRVK>ԉ8 #( 'Ek & E1lf[iZ:(K5HyM;eR 1JN@ǯC)ԉK[vצ]@kWSI(™8![71w$0Фeރ’k:R2<,. k[ITb7qLrJ]ų(F'锜t4о[r-7wmO41py-0r`\v9;`=03D;0L1 LIA㔘"k~S01rO,vwQBS%9,=ʞPB+w8fIeI4&~HT/1N!tŸFݺ@2݆xk QNm>FˏXm!jm4$|]:'p _.JyԶIT邒,+Ңk+"Jl႒bW„dLYԯ lr&KďA&$kSöX) emPj>-D*)kFۏ XEʟːlC1qU%>啅,lkD+|_! ?!אlK + RJ&체کz#$3)~8Pcߵ׾*$ !#KihB0Hv%j߶VX595C*'sCM`I9 }iۿc_0ҴOUnZZe ~-ϵk,lD7b-$ؤվvʤ(oRfGYIm?ȐǍazۦ@_a\iViȣw\!Mr[k%]zҘZ̑!Ɇec]u%Hj|?gliKlUDr ZTdE;u$w_ C!dgnzNi_\/J{1|g+ˏS.=A0E2Mr1;Z=[M 3־`2/#ExE[L,v%pR IPAk X棢$%TbĄAJP4ln}* ?g,%8[އl$T3hN 8xt^\Ƈf`+FZvWg PeK hvrL;{*8b,w{S. Iw򌵩=)*bCJ/逆ZfӾE ,i,RJ Do{`g%d}ߗ"4_˖ZQqd T"6n{W(ScC:|8 ,b9x #4 V<)``Pvc$ x Hd,1%C"rSdKWNIT-v\'߇ۓ^Km۾¦m(p5vfŔWGoldFfŚ[5J^F-@6Ia;^ni9r*e48+MrئKR,UCtJx9ZdQ1۾g"G# 7Sɕ;ܺVodA;OERM-1{϶!H7o%Mr(s?G.ӏċWmF),X{A8#o)p*j?k&$5d$ -A8ڪZ߶r8-k\JHocXyFjRErMʆ/"5 [dDƌ[g~FT  :.yb\W{eRÄ=_H"9cYQjr$ZtJKpca7W*M檑sdp>sO Uy _dW<~ˡk >{8U@[1栲/$31oNŢq04@6xca-(-CϪLϏkn>] |JZIQmbu}^E@ı'yD3Yny|wgv}d%+CUDkᫍ6i6o銒OZ@5C_|b$BOR>/l% @7-P5,j2Ҥ8-`B>xXp#Cq^}NVڮLE˦Nq_Y01&`>O0/#H3Zd;ŬH$&#Ƿ\Cu=qT"ĔwZJ喁D5 =SzdZ,jLnk^7Ȑ"_Tx,Jd6[rѓmӹ j Ο`f7BE=*B$R9Y d"ѷ )Gw+bF6{ Uߵ2$\9 !]V6M{oֵmr4iH^wMvڟ<-3w6&| =w+#ڬmDž_DO^|q~ghF8ekIvÈ ~eFqd|N@YzvؕFnΟ(*3s! %*dD'7"eiZa1O󳋧kq[/D( ]ǜ88-AS燉ZH/0*AW瑖2| jNUyvF9 %o)d:\j#'Lh4i<8m:]bn$צL2ұ>X "qo`Ѧ~[HN8-YٚO&{0Mq>8-vzd,/_8pФWD=տ.JD%|pۇ=˵%Moݕ0IO/Դ9홁Cl,$1ct$B VS(sֽ; XzSX*W[5ut+ؒww/zb 5iJTY9n+!/_"J_)i@?r$1!,m@H$OpT&I* 9"hNv9[/pi^[T`BE@űaa>|rqd^q1w w݄$;+_SSW_xqE"4ޙJ"4M\[U@4ȯ^'x?!Qv#L Wٶ0@u`A䡧\-Q .!%r4UԮ}ZŦPBeuLt.toRV.{zFDK-1gMtxba]d e%#[Ȧ9@sqc<'w~H[Y%s(D{dKQՎ;yB\?-2ցՁj1ʓWN/32',-݉nD]nnё Z܌]o #rۮ1UU2RNË\-5jŎ3K`&YZ8e}CgysPR^Hj8-^`<\LCsI)J!L>AӠidH _%\O$ -U}X QFw$Ҭp(TxK|ZקFV~(I$ (A=M %hGRɆ2rKGLiy~*v"p'`07w*M"YH"v۠#pEOM?L&~8=+]l4w1Zl}J}8>%Qб x[8rM $׿!&sLzd `V h$Pel%&x,v˗H(gb{r!^qg;B? 917" ޾ؔ2^QJWl3^d;^dim&PYks\K/ *0) +۾F928 9N͐}^<6z\Qה_=JZ6*qS`#7Bl6J>8||#hEXfP1v uy 2<&hSt!dd@"l4ʛ6*8}SVz(A ƖWmdxSKy*Zi҃¢OB+VeiίkCVrX_Rd}99[88W~ordy[ȮXr%t j0U+¿/žJ榵^d%p~*ȱD#TS~€W{YMr+ o-xdڐO,A>9[rK#ra>h) mB2d&(cMn8*k)oa%|튩lN8["ևf5ɅR E{qTWWDFw"U2Ƶ`6M֟n1`*8],=+.;AW J"$ xOȐ$ڡaŐQMjɵ#nS!!|+F%!X}ׇ;)d6:Z cL^18%QA\7 LDYxQvSFE8JA98c+*9N!9e'ev*q;?᲻wQ-(QsLx\OA gCX F5sx?|Y;Z< ?ݏՏ*(8# De (y 7m=DwO @+g{oƜ湚0 īj:Y,%SD-:@9\2eVW5*A?>QLY0ˬeSZWl{5.PXkS}hp/:v_U^6eir*Wx\t3 ,ԝb7P_JݥWy +v8VW,P? ל`@߽ܜQZq5P 7PƉ*}`)V1.<=Tt?Ym^u`߾׾Dc\+R' .+k&֠șmM/"ˑpNL5M4Y`,-Y)rD F֝qa ajUV^[o'%I[_'w!Z )Q{ CO-s15iiqm/,f_~YhrIcݑ-J?g)mE lj~m4$@2QJr k,b_˽zeTCʆ hvU#}+J>aJujNj:oeRos*lR5;<>"?ƿe@!am:,$g2Z&sӣ}@q}?q$wS2rTՒ{*G Ɯ-$c m0R´)!*ֿ~AJ5\ {lTeL}Fle^OkpyirJISD"Bbʉz :8Zy6!)ł!Yv"+^9"1dЅW$".IBc`F*B!jw+tqj{|P)tv &NDɐ64j_*ُ" ˹r<٩r2kZ9xLO8yDZ~{&z9Qw}Q(u;!+kF8eJsYib 6`g.&abjuY6%58Q7wNc Z!}lT_= ^gR8S$A+%2q82"Ht}4X`*^$HuLK.Q"9S2yK'z 3y=dܚkļ~TsN!:.}'YQՖ'{櫖$~@jv,y'u%!Z'U%K$&.9Q<[K&y|.<$ulo8p2&SuTm=NVhj{6iA+\-nSo|~YlKQTңPH:Tf>^$fZb ~41>'$ `;|[H2NW$AJC+ D=RIvqˢG]ki.UPF(X1TŒNVI2M"'o|skNBMM#5`ʜj>*D*tmad/uƚdXI#|V$k->!đpGi(^6S|`WdY#9!+G)$|]k)-oA4T;di|qjCR;u%'L+N,wPi!2}~1e_6ZA+@u@7"j:I26PupB?憞nA#eaރRn'xȒTޗ`}@PR8ڟe,jAç .g=/ cl% c[}9١ɌlDҍO/vs 뒌?y37%!*ɎN (#CK/ihvV!K(|߉_S9_,\y gT~v n=~S->6+ ]1s/LUajf~.& [H}g/OZEC5Ç0!?{wTz·AVXQG!BIenu<|5 2/uSY> Xg6~uEkrF`S|Iۦ&dVEpoq|EVYkꍇNߋwϚR9=zm !b:PN Ol% \Zd Y 543lyWVR$XM B9cQJ|/ #޿QA6E:PBjw/%lWMe&~cw98&&Y(BL{0镵d( i1aH6=<|NDؚPjč+Wym_,CGMؚxer B;u;?d. g#9z T=T4#|9 iHVW5*0)ڄTĪ*~I+sE8Qǹ*-Lo$BS4߾i^wB|%W]=>Ȩ4՚n5M5t7f̻=˩HBP+LL]OBiek>nI\fo7G~[`\{䩎Ms͵mmSW|ɚlRǯ-_>h׮- dλaĕ$aHgHGj.VCs?kc,`X;8F M$I+lIAc9/ڄdhO"ʔNڤ HָEBF-]|ۉA>O6W" -mk+[h PZMDL={lRtȆqRw^]<@;8xTʎ40m_ •dڣerdmj>\ޛ9+~4;땒g}h9fߦdZKdr2+BU84$C m2Ut Jat۾4B:}Qrꑑ|]|sXc@' *k>;N xՎeBI9濳 M&sчŏ GmȐ:.+{xi9"JuxVcP%x{2?/swQtcNdけ}a*Q;GC閙=.Jb^aGmJwqI.!h{lLx,ORDBޣ*"^?hT|T5XsX1l[Oip|'+D={JHStEVY,A65q;_?Ws1RPpN#1rTo׏| ~\Y 6@` ?M2La ;5{& 9u7Ce+G&";*NLAT+ wr.) J)?S,q0 #"X{T}Jcjֹ8ńͥSHPˀjJ䚄q/*\"HGa-$.:q*J|9GrSþ) ӏc_-UW+O|J'a₴>g&Q+L|B/]j)R%%rH"aIn$6yvsbR?+gt*(it JSDU,U-b/#9`xhv$Mv)+O*.4 e=|)a=&(Q|2;fQTdDmH(JOI "}q@; E&jNmņJA^}74XNvF_|9l e&ɼda0jp  4۹d!jxee2c&".χp Tep5')|nOO%~R |=RWp@cQJ&aʇ"-Bv2@$.CM+$q?0&~"aƩVۭL5܈SJl@Ò ??$$#}TjL܄+5+J*}v4؎ݰ1Dv>hIHZ_^89!Dԟ˒eJn{ m'*mp6MWǓ$3\E?픖qLRNِLE+FCg4X0RHyjVDže$Y:Jnf'Ey&4m, 3q5IÚ%M~*ՁQ O ᕖtfZ6R5iCǏIf"R\cNuۗMf>W#REG,j|k-TOz2L8؊%$ORZQkָicFN|y#|_+f؊WKQ'Z` -O-c_,m F%J/Hj6Q&o5ӧj]VCɒZw|aJ%a%i`: )!1I4$ tڽ1|hIC!a퐷Cc-*)`@ZSA6%&R+ѲBMIM<ӽra2K$ӿE5 I@T HT߿nYֻgnRY[~uGSF])mڙ%D$ /ҝ*`BYhcK~ɆF kO|64ڕ -yN^-'+%&  ݑ%dz42+ȂNd+uԯ_,r:4w=w]/E6I]ۦ\4#kD7>rT!)-&{[t&[~~m(OCDۯP3LxGLQFtNEU56`lRs@~{Rb3H(ƴ.i7V_NVK\h{uǦI`Z(ir2dGB@Vof粯L"*e5?]+Z*>M@2F䟞XB ψݾ"X|]~ NWlvҟXi]n9O`0LG4ԵBcBve"tF!ރ[=dzp=0CӾCi$Ş閴UڹYr)SƒidJ-NV^ xlYu\mHI1D\iYf;J,d|q竆gqbA p+$A$2Pr7PdHhm`P/y`S8[2K Tu V+1t:-_C"S+!`&t4HIvi% SCRǦIuq )W/¿N IG^ҍjS0:oM1& }To4pҩ *SNd7k#l2 +h \oȒGW~׶VKHY]픒Q\KhfF4'u6Mh!z)<@. |U=r _^`H- ZrdyuZrqD%ZH*.8- SLI.$jeb7I'~JVS…jzrNvL-k.#O`$$ed6y.H!ܹߎB<,K/V)Z=ۉse\#vnw\mJ唗@jʒm𴐌ӡ/6l-D,cKjORVƮgmɍ성"[CP[sz~⧻e12~a-T/+RH'+by2?Ӌ^^O1a LI0ɤG*17__r^ h @zH>fH6 7Y"yl>q\$^#*">Hd_,Zd#Dxed:yyl؎'%؏9u8gix銷qU:bE|H'oՅBsCn[RVJ@d`)lCX7N][k(XW|21 t|↕v }9`LvB+>|[8{ &ZaՓtejmHOmۦ/H4EV2UEac;5ԏBIJ~6\9MrZI;ɴ4Kq;``659RF>$4ݕ8lCJoNX aHG?~ 3% xç*t>F y=)ԝulW5OH+~r4_9&bJv۷ߕ?5IX˄%ִm)JN<[v]N9dZ*\t8ݖ0ŵ5J[9Y}E"$6ȻJ222O41]!5Ғ+S"@AO8o$2Ve_ݪGdf'!%ùz mĹ%=r2BJSlDT`AîصWǒL@#c{!䛇0\?1^;MƯ!^^aԘEpjz7gx1H̘J)CSFQg,^f"R {WxvI H kFYca",peB :.VL§*bA; $j+l[0qUTtgV lV*WT|GMEDLX^2I,+OjlHVKD@QOż~/s,VG2")ƾ+Xמ#F]Se- &Jm|T#FH>JצBE km)1Byk#DtaiHVW)l#AVENZnt\+$r8o>! )Lu~6XIcR֟RY5;ɰ*iD"dV8Qw\ 1_, ct!Zw{d-#M~YĦ:oFE4ߦVFq*UBkben-P݇eɆ6l,$+L\ɶgltT$\LHyW(&R;T|MFcNT)ǔ ͩ]Gçf۴~'ݿ^Lſ@l'!}1ƔAzp?p;Фx2T4,jXa?JKrwdKdM8TsmJo$&%Mr,~vMv"ywyϮV[K]l\8cqJN8cFY5Kv! hv#LK>iH&hcq_yխu9)?jkJ%4V; [p9[LQĕJv@Ų3R;Li4J-S+z6E]P?eZSӊC1U'_8B2lĨ6ʘO鹮&iS\Xi0KA R(M626XȰXGs W[PC&}EbدQ\6qï`2qH*LVm_) X-)dr&)KӨrbƖ1-%(핒ġcRiF1\?`)%/&'z}ks9QHF"ȂPReX JwɅ 'ï>e@o欼) x7N,SeZ˂ Zo ((LBe tq͔S$Zy**EVnwmvʼn(I JW,Bc߿ݖ I/]44-6HDZ<16h<tXJUOUF*ǮS D{&I M1PxHo8 W@{&{ lR+OFچPx>(999`D'U_|eټ"]`YsrHr\Ʈj~b t#4Trb~PL^¼9K6l[;~"k ]#-V6o7X!ZSS,#lL,wZ75"x>SY*yt)ՀiT "OLnD(q=Mی\GUs\?g}IYAi-ޙreH:N~ dx 9EƇ%[pzMc,A2`A=2#2ЬS_"f+j-憂%s»3yK hG\.(7' [ySH2'VgY*GP\[a:};OG%JV:-p7Ẏ2P;LrӸFjCw-4sC\?~DH&T]Caלt 4ۙ9x5:n+;/|.*w>8 `BJ!H6t=~YUF %T-~.oUR=朋8RbPr oNG!i%VS'+h iN[Zq[MBmT3wȅYErGeEr-G/}1zDRt?ˠ t̐UD &HQz% &qOms {Dۑ3{mQFҾVҪAQThUAңo_T< QA e_5Ho/!2A-Uo\Sqrd@č<}o~(aid2̬xa)gEc X3Ǡ= ۞YӒ#i11jvZvɹ 4>-&DXح")ql!n#dk\Դe>@t7Ũz j4__lC65 rM6%?szLuouuSɻˡ''wSa?dl9ZJN)DzIyIMvŕ*qo(pZ [a 0Ξ~DC5|%{jv I{yIS^9eds)p?IJ L)G$=IJO $"%:i9j%.RR!!)oJT|BF#-9IJ@@))S~RLtIJb4I/.$>hxL~)WL1Z)8 k.i)QR0vH J_dJX.)r<Ri&$RKYNiOo-g%(R$t_ԥIK#TR)Iw##=R')p?IJ L)G$=IJO $"%:i9j%.RR!!)oJT|BF#-9IJ@@))S~RLtIJb4I/.$>hxL~)WL1Z)8 k.i)QR0vH J_dJX.)r<Ri&$RKYNiOo-g%(R$t_ԥIK#TR)Iw##=R' Adobed@<     u!"1A2# QBa$3Rqb%C&4r 5'S6DTsEF7Gc(UVWdte)8fu*9:HIJXYZghijvwxyzm!1"AQ2aqB#Rb3 $Cr4%ScD&5T6Ed' sFtUeuV7)(GWf8vgwHXhx9IYiy*:JZjz ?cٿE{ߺQ&/iLTFO[PnmaIDӦѝѪ]2x[VdPqZ}.B 4M2%K,dRRK}x{R<`z]o{^׺;)aIlmn qfh!;qvc@׉OMC!S4QJZ4gYQ0R&I.?Z.d<cPa(④mErG207Sz+Ďl0?oN~u{{^׺3AN4Zz<0{*zs)R*|>f 񿖞G?HFX_{QU=zu{{^׺uI4**MqQRUYu4qk(Mو6R=jh:DVf3Iߴ*P̼M:ឥ^׺u{{^׺u,p\FS_x $ ׫N}o0UM"sǽԎ_A fLQąLҙ&DjS~IjzOS~^׽u~` pyR!#йg?@?q'āߺP"/yB!X lU\e~׺}x_~6ɓ !(xC  {{Z\qӊeVSu`HapG}׭߿u{{^ rtq 7:OHV0K[߱׳Y**Azur/x$GI<~=xLJSc%D6du7VS#޺5U"FGXE܅Uԓߢ+ޚ e#j . i@G8^<+Nhc둡d3xg$3EPGdaˮ5H)-֘mKu?2-:x~}?#DcdV?m'uߺ^׽u~}0J+4B޲JYaMR%7&&{s+Wj<3#DUVk#;*)G*V,ے= :4oJx.,(ˏ϶{^5P:O'&0b}x֘ԢeEAo&vcY@WR>i\u_>I"D$]$WuR4I$:Ct5zn#4cVkgwa$v&4U˧T'gDⰑhտ>{^׺u{*VEu@ dr||p@OvRTׇN>ukVA(o# TʆHl՛ݖjFQL:mI)Z:xO NA?SL>HY} z}E:ȵr~gʃ,<^0h G|,kIΪ?ےPupRdbin~ ̾`,nx`ުhx셙t9U. P}o\u~@UI8ì03t{`=hЎ=7[M t%6T1H^0n\ '$։]p) RdUJ႗ERSW TuSAҋJxeK y*bl= :4$U b@?nUYa9 (H<:r8[׺k^FEA3"̂douCJ\s($]+:K4U<]o{^mnt=yxCT#ID"H'01F*hB%9\-LK1aUG,~:Iƚ#ux:;{9G˧_tuZZk<)HW9oqx)^O#ZK׽u~{ߺLs$@~_?_1O* )g:S 8;$Q)ffwB~t$Հ\CʥY+> ,'¨һ?b+SXI%L};ǢF[c!VNj+ǫ?N[22Gr@Du F4-#E$ `[tUztO,1Ŝj]AX~S4zHJNmJEuKqS@ z|MJdI:@hҒ-&7'MF8qHxtM"kVGVn#]*GПBVzש^׺u{{^ҙM4! HImKuԥY {8hZR huH,0S8P=l ^<=42t95 Z #Nu2@3]#]r;%bO^QO 8b?Ru?~ =xg׺oz'YSytÀԣy`”#S^VȞ9*)0F_HE}t@^^EpFġ#iDI''I$}cٿE{ߺX(FO^ʭ?IOTv)i_^AaAm{{^׺QXcyPүq54Ãό u޴.oPԪz* 5m.2:5Hz =lxu#޺^JU, ~Wߺ\uŝPjv .rl.GzPߦ*zOKk>=huU2?RU,_cFG_<˭#I"H2GRp͏[럿uXߺ]^׺u{2/jGR @P}~xu3QǨN;%`X #S;С_LnY WL7vŤe+\O }:=K׽u$Cr$ 9_ o`,I'ݔ m"T R0xRY$ݐUޜ)x*(ڴ(!#~ެ8u#޺X:xiNArlI$r׸t)Ly[2(co@^W9XUH*` SBԪC,uv$U{~]j9ڞ!Ecթ%~<_Ϻ8up}Fz]{{^J (6bE>׹ߺMoK3#EiDk YRLjBYpoOz|1#XO3ռQ$(4mS4K,NV(2'5׽qCǭq4rNk^#I*F ߱תGԈ RP.aLRߨ{}nש׺ucٿE{ߺ^9jRJTZC#*`.\IZ'W+ꋻH)"8dRcUߟ{={'΃sI,N&ey`wA$15#}8u_>[׺A+# r<Ad_#׺u{/kmq{[}mo~]pHi&FB Jz<^Rd&9?H׈rLZYZV_.,2%@UՊG \15ZY$MZ2'WߛQ׆sӯ{.J29U$"S ]ߺM>z#bjt A:.MZ&iץuSڴ޺]>{^-e;TR6 2:Mԗi'rPJ8,DTS!,g Ï-P5IUtMWUMJ&xR4zx.BB,Mlyu4QjRM%}e[x'yV5m`BfGQI\ Jz!zM1'^XY\t?8LN=IچszHZ xsCITҒz&x bR^RT[DX/í~G 9g +Qb~{āǮ&e{O )ru{^׺n5^r xpRE?K\ݵbjϧu}{ߺ^Z*jV2YP<-RO=8EzqċHƃJ*qs*bhҖԼ3H"0܂u5uOLM#F4b)Hƭ#a{=G3Ȁ2L-!%`h1Ǧ5H8`%HYCp}ߴPZ4pS^iRȢSq'ȥ~߅NZuqǟO3TAM`W@.ڪds0t,\O.uL5$tCI%qtz0Y%j#V%- mƆ 窌S;OST&B(b#7C;݌짛pǧ/u}`e4%K˨\@ԦQuz{ߺQ$ByQfұKXُanI׉bX^,TODs}/JןvUONO#S4Ylee? _ҿ߈^t DDu#"XC+ }Ђ V׺u{z_҆gZOS~{ߺM}AXqD[-d+uoy~cpozLywGY?DgVf9t=U(GSh!F`|IꞪCwrut{1otڳ ڊoSGLI3F| X/R(z :Y0R9/i4ꔿVNաָJ_mu~V;d g+O?&:5SI?=F~]x%<1h"dubuR kR}U%H~x yu٪$^I)ɉX< *wcZS>\zF*Xs%x|щDLr {S5½gy*uE&$2j7ϽN?׸ԊeTiJՓ ȤVbǗR+deiߏ,%t䢪_޸(3z}=S I OTArOOˬRUra59i}e!,zqi" 9~ N<:]{%rX}juߺMGQT²9V$Ft׮"V*}?v?^G)=TTԱAL*GVuUT=G~=Yu#L|l/ C-iUC)bi#ڽF:X`vz~Xjd"8@9Y}dH#߉4@'\rH(T ~թ=u暜W$(J$EDDJto{?NZqo{rҰH$(Oq׸g!BV.M@Z=zɩ;-;K"e@[TI )Q`8ᐝJA_u #Yu~{ߺ^׽tQCiJbJH2X/iLpDWC@u&jxZhڞ%TiFB5}8>kÍ))"U$V~X¡mx$%p?z5T,>q}aJh2EYP*i׉zo?o}֟x5܋ǫU O#iO.LϬ׺u jxVX]*tԬnU#” kPGM5kQPFi]S% $ZତH>?gU? Bӗ."t_KK$Ha,ڍZ߿>Ӗ>in**-D u`8<{V׺uPzdҾy}@˭/ԩS8Գ\}3zl NqSMFbKe!Qnю=k5D4+MpF୊}Jx S6~iaC R)oO'z^} Xu}p4)F#cfCPS!b$=# p<Q5R jq"2zy[Ia={?: ΰLw(de ,Jܢ"(T!~މnת(]RiK+˫q)dSelQ:pzGC,D*Y|*^O$?x{_V5$ӫc8QctI31fc'}筀{^USRKR?A$@g1)Ҁcꔠz&xҒNԐm<",D453AԊF."+`4(pG?ޛ^2ONպuճ%-K/Zyr#k{h=uJ:Jaª͇',~޷:]{uuB#;0IM+-ouǯ?yo02ࡘ}ȏNԢʶʷ6-"޺]` b?PAJKbO۸IcO('[l=׺o ="ʴJ𢚪u/0?x 0AWZì =dr5Fd heE_ǿW#})T HR+$-ZׯPzuaj#SΒD+3F&^HE|ǗS׭~7xg䊎/ۢ0 U#<:%cov ֨KqS%8LBCo4#@coudҝeeą]` m@{O PzuDJȈ~0#ߺ\׺jT('E?2Q K}tƿ$_vAV˪ uMU3fʩzw;Z _{^Y<}1ӭ4¢5b?Է=Ԋ:5޺^׽u}tV(9ITͧE= \B]C$,lddX'8ëi@{ֿI$#a(I"HT"gvA(mG0+JNu~x}G׺g%=ds}L׽um^' +gV6 P=q4P=M UhwD ?< B3 k9sPu/(F=4cjґPM"(]Z-|\HZhe bκ" |>zznj]XI$2爄*&} 'ֹSihxzҢtJpB#0l$u`FzDU"bMu}GOUmID6#鬙dX{k̞O2$J] Mr *@grTI#`rX\={~׺-5N .of1`_=C|G^}q2X$—7XSsk7{ޒrj`QwI4Hҙ1ª Ep )z|T%/b 2YOu CՆso~{ߺM8)Zeuk#J̍ 7S EiOt7EVa& D_Sou_zgKUOQNFHվҬ<`GEdߗ^?}}A <^u;"IEFyPRPH[[SQӏ{ bIblוeee?FROu@`Ue`U<}iȔpڅ?tG{#v@i,~"} S8id*T \=֧4ÅzII%;3=AeaaO"3{A#I. #ϩu/Ƚ\Wd 4B]jYb޺}Tij*s9tG=DrF5+H@w]mcT<Mz\U**csOkL<VSqT+PEWTsԇG1% dҤ6S^Sî4k$Q44 Ť>RuDQ~{4^>]<{V׺̨}YU ~7MQ3 9҆<-1?>Rˤe~JAr8G!B%bͥTLsO-HꥉJ}P$ b߮fە=ͨ׫AN[׺+$,Hn=t&1 BV+(/lִkB~״7*XFѠ+.Pi(:\ֽO׽u~cٿE{ߺ^jLh1H,rM uiשfyHnȬ"+YeE֫ S^[鮶J*SQI!y#q2Ƞbv HPfw XZ@'l@T._`|D i(G:G:W^:zuN׽uX6dY#qfG$dq65\AbѕᎴȲ2Ixl*nm}P98=Tu2q#4TLȺS-#_(PSz]o{^+z$"|LR[-6uZߏvCC֛/ R!QV$LPf Nz˯bZcQS%<yDxqZ#^Zԃǧmn{᫥z|0>%j_ > <:$ǬQXBiO0Z€N/" ouyFztJXRJ\6݉gvc()zqfTVweDQvw`l|M b"6jHZ@U* ."?֎iuZ]㦬[LPI ~rZONպuIO*j/Q!v,osc詨o}h:)^{^׺u{\{rA P>DW ^cJ:8㤧CVPlA=zө>׺u{%Zʭ0Qe#1+EMuOQ35.]Ҋ*iēNF=CFwSx/B r[aA\qf6jOO~պ'$@9$׺NW44REO0ISK}u[qG*,>S#5$ץ HH}_I .I6RO~x@  ?B{u~{ߺ^cٿE{ߺ^׽u~{ߺ^׽u~G$w JS*Hu#Tתooy_!֩=g5FfG\YͿ׷O["C#5,.}ZV '[*-BXph"*ꤑg^?2bcߗ=zgAz^׽u I&OߺQMm=Fy0E$?$R>z#!K#@l) O+ȪKu}M^׽t6*}A(a J`<֐|MIOF)Hc܁`Yؖ6ODǭ^?}{{^B(,I?D7icVd @ gWG [0_۠iuvж?=}hRi}U5sjGJƞ6ij6z|\Ғ,5- cƪˤEM?K֬lS^{^׺[V)#Cd+2:KO쪪,xDҝ':x-:-U4+8%ӏnPGTs񌨒o2K+LP@?;:aXpF:5z]rOO~bxg a>Z׹UPPx{^麮G))Ky%nTO]hQIS랦X(k?\{&Ppa^^45Κ*xA}-*Lu$'o~02&T\tLuW :~o bzr81q>ۥcQ</ҿdS(?O$hR5TEEU~^@W) GEAcǭ7jf1cS.Аk\Y?Ap>+B]hcϨMP1:O5JiE A@jd %$M- 4]xlmLXH$pO߈ǭ8:ddm,( j!%$-aA ?ת|^5|tjJ$"$ڴEA$$,=x=;{V׺Q:d#QD@@2HȀO\=xg{Z(&EBO(a _GU4lv)]1m na%QtIV P؁Q#hDaI_5$u԰iE~.xpk2iNѫڢgFԴY2,ʗ!ׇz}cX]rȑS?><^8V+r'tZvG9mz~cWz D3{у$OºQBSH$i=qR0 Xv~~zp,uXCDc4qD4kKkt[d>U}5LR; HAPaX)ĎOUR ,AuOR#iY$@xI=x8u޺L3ahV)!XbCQc%IգY%P˪BOXjM@GyhgQRia@LOO`IN|NXJOWS4CxC?k?qJ_ C ZD(]D`*ZmJ*ï7ש)J8Q @) Hx3Ј*p36O.ם5/#h>G׺*Eƀq*iK5{@0iJ2)VKy$=J8?Ov=T+T՚ټ .RE= tQSF]gvf'K{oU:]o:i%yYPmǭ@WQṞV ;пNHe_3։<^ZZ]!WU&>(A.ͭ~5oƒzS2QΈs!J$gjk= ?=P~^}{&,)aSḤ-eBiXhGާϭ?S%飍bv`YZ\{5=ywu. T y3cozc8S}ׯu~@:HfA$A*fF#T?:D݁$Q`y2~g*Jeq)=zm:E5M]\nBB?\CY")ah=BJx#PD3C"F3O }-Kz4V ?Km./nsu~{ߺP* hK ?vN?V:$qO?y?5ު~zW=l$֔\>jz:ďčaZ8yW}<{ޟFz#)RHz^9 R O+ ?Q{v.|ցZ8:^ {u~6GQ=ELZ*i~R1hzu;޺Pb LIgo/b=xu8)QUYQUT{F.JT1C8ab}SX)$eB  >,O?>{^閆tJJq<3ѱC#IȬGG }2}zH޴ӉxU7ٖ%TH./ңz]k>g HQ7,I#]o$$`îG-y_3bt$~x8Ŀ:zY{:~GK[HS,6d}ӯT>~  \{[׺lVgFȣ`pEA#%d"+/VT Ek5{H=8P ikQP?}IuFGn}-n{55cK(5 (>ǯuߺ^׽u/۫%'D2Ht_zG^o]S8B+{?.Cu9k(4u](PMقb?݅B֫N=׭~{ߺMu4z𒵃 bb,.lR?>4D|S8ɤXxi%'P+QVmU2%l&AcbqLS$i$uDQvv6P?ğu}7OaSUIdb}ACo~9#@zȘBҧ PL9({Pzu֢:*Gѻ!>GrU&S>h8>.X%j 4S1v?[Vf 6zA )'C"uxώW߁Z֙ԥGRjzW P|`Φ{45TSFzrdQUT4,@Q4r q^G:li R^fD.tHH %IpUӊi̮%]BtyauzG^2z`jj٧^OzO^Ꮉ9I,dcyϟĺ#j5[)tԮ8uԯz}A ,Ԛ#$K4P̢Gqr Tz"PTYcPT/f ?5x@$"IS*-,$ d\dяZtHYRfMEU)@lxuR)NJbEUXA,WoQ0^^CH `兿P1٧V|^}q$TQ+K ^X{R~δH}7"JS1TC2T΢jL11E w8$u_*yS 鞖o$G+h؏B(+Z\ҝLC.I#PX `4bD.~I?=VO tLfh4αX^ʹDHN.z4´\)^XuwHn>k{^4TDseVp/O'ā2~2\{[ d$ l^/y MJjoUGou{{^넒G%uw!T\r${~׺uCuF6J $IOt5v |֊,u+YdO(0Piu [/5~.}^Bj_8ii}%}n#.NkGjn>***ʠu}r}M@x)D:ӹ##ɷOv ADWϩߺPkNQg4D.nT%p UzS׭I!V5Ux,Q{QZt5BWSk-AUMnn*V3+)[ T W׺I}"=BL԰6 )gu!ǯWoaǗTǟSSHfsŸuzS,5+erʒF 8 n}ؐTuP=sd1/=GuY'!B肎,vR0G59{٭1íbV}K0 ` }u߿u{GQRS+j B$$v e-JӪND""A$k{kbX\55-54V(ՌS % #[cƀ  N:xN׽ucٿE{ߺM9X#=B)J c.Z_wJ֞]U>}aZ!EJ%D Igo(1ש@zF',I4~$Bk~gRlgZlé^ley&!R~޵ĚGHď%6>\Pw 3 mx@_u?PJ)ZR!i"4R*d5du]9VTZK֣<dGGMN ̺Tpq_Qꓒ{GZzj)dU*⨟O$LLlkң^Zytĵ]ŘSJ2Q?֝#u*H)^gL7V#t{hk:i)q 'o'&o$7{٩֣ b=Ly$4i ]K蠐IZunq/5Ifg1MDʀ Iz]UBgM[K ~VhQ[c*:xb yQ^IZLuY}uEG$w/5[  zfd›D-ڏZ:QB4F"ru}u55tS)Dn |r ZFuHy,oIbs9|Ne,i(I^3LGOz'=J׽uibP)P2lKXskJ^57"M`tʣ^i)y.'1"ۀ.QM!,^$!ke> R0EKj)'z9cD`H*GЂ.=ߺ^rTj4T@5BĪ#~5QuR4bHQIXA(<{y\ApmAv].D#{>†l3޺^׽u~p8FTYq~ǯtYE h~&Q ^KmKs&a?_c?T#<:}W'EH&I ,0i$1Sp%AOSֵ.uezEd f1PPu}{ߺIɥiRHEsZ )q Ti|9RUWQ .@QD>g4N"Y'O,,S/%H)ALkz}DjxyX F6S#K9Aco+ZuoXifhHRtxu erU1ŵ&ׁN>u{3,s u6L gyFON{C֎2:ES]L%IJac*{ZAPA$u6jZj 41X1kMKf#@:0 q[$={-{^׺u]8{[단tt7)6~1^Jx jV*)c2*/<:?R#Ϫ֘=f@-Lһs#K$y]=L?ވ-@:|NT%4@"$ X]Y>I&AN^!^֊Wu6:E#"O׀}V<:pSkfZD(Z׿>4/ZtGUV53i*<!a y?H+# :v^*$ZxYWR.\{\T>L@u$/V(e$PWǻRo:uOH3*Z}0ăKX[@z8)ؘ#\~ ԓ׀Yu~E$2yգǣ^`N=x=t4t3i‰mPQNU[ߎTuIZ'eV5`xS^dE ,x#TF!4[:xE:^}?ߺXEkA,ou96{:Y=u~{ߺ^׽u~B`FF&SWdѿ@:=7X Ac4g@k[wz WשtT;J\CdeF'Ou Гz^cٿE{ߺQ䫧<92IR >]x<VU|+ֵ|Y#i%R+E/K+'1׺u]Zu.5iէߺM UQ╄GO,030eJH ǻ PTcf1S׎Zd KŁM}u53<= N9EB2>V~~ uIGY%heF 9_IbpP"Et5Y5*yfVrm$ uZxG0)C^$wyJ,t /@yj$^d2$HL}1I>xS=[qz^׽u~{ߺ^rfkfIZ:ECN O ypĞbᨆidj5*VI_#NAV2BPNs׸S]O3MK!uj?(K:f榢DR8-^9Jmu~~pOP E,6G*[FH<ǯZ>G E@ik+SY#~Fm&|`>@jON)G:iߖY%9M?_u+^{[׺u{{^鉖sTF;7YTˤ)?Anbc=e̩ZUQmF(i>3t[{[9p ~ڪ_QS/^++\hn-ʚT5Sz%MdnJAXr6i[R^oׂ=o˧eUQT* *@~}ou~CvXy4يƪI>h TB51) 1,B$cZ!JMgoMsUozz~z5EUΉ.=8תOz\+op?s!cUP>iRjYiJDBPׇ:]o{^ @ O:hMN֊dRqcudQZ&>IPDJo>:o(ԱG+ΌڣCs#ٖ 7cn=tP,]2I?\{lt5R=u\$$>(2 3eT?,?MQC 19oYى}ԒxJub@Qc)bȫ"U-C~IW sS)i'TI)4JB+@? p}ފsLuW=g31sPaԟnjiֱѯߟ{>+u~Ȅy/@Ϫn:m^kG⾜:ty~޽^JXe2ԣyӝ]x'+!fTEg7vU ]>?~FdSNHDž0Iw<>+ZiĎ"*K;dE,bcZ(ߘR~{ߺQ]+P ODO }WӯS j$%G@RP-,pֵr@h^"!T4yae >=К^?[_~{ߺ^׽u~6d(Zu*3Y<2Ha?DW#M=I>z&U:]a~h}nwu\ϬddeX95)Rpې?>U[ӵ$QK`>܃$\# ԏz{ߺX(4,`.ïa-M@xOː?ݨRxuWKM1M;ER\D_{ N"+ܫ=ӆ:\u~a$ɭUv[0A8uUUPUUT*=u\?{^ecee'߳SKJdVFaȸ6?{4:Sf$K#v߉^H׽u~6DL:0DP@<ۋ_ݗ=zpP028$ZM Q6!=&^AE:sj5:evt?¬iޤ^׺ߺIr^_bT 6ob?_SVzZ5"It4F^r#/.4ڒ7q5EAWGF%dH/jtEDQpZ 3^=;{[.y䊎$TPF@Xoy P} wtNsAi`TOo<: tQ#BM}п*D7Vj}1S-E]5E]WHL~:4s-Y@x ֏z}{ߺMkiKSUof ݀_>MS&d#<P} [ #Ug6V$BDRI'zHz+RҲXբpR?#'m#NNy5t E D 66,n43Bzo{^׺u{{^}O^JdTX`#j EpKDK{إsïCNLHi|BU))cAӪ O?58_%J++ZY!Vd ,$[h[#?[SH׽u IVh**u$DhZ(O{*zMh:𡀐fT'DcLH?(c׮mEFƖ*uߵ7A3 idw-q=:ԘIFISpC ,̎tab=q|zg疘݉#r?t۟q>]{׬׺u_ ?>׺7hͮU_SƗQ{ޣִN "Pu{^׺u{{^׺iH)t,Cx"I- >}TSbCQxi${GO ,͢2CMt/6բ>RkLLx鞢SĢj~ij 1Ouh) ]~JOkǺ/?.|NX B9 Q.1DHOzz}{ߺPemahzSku#WS$ujYkƦPO{9U>}x`:oUuJVIP"0Z̧ 4Tu6i%+HQi(e$I$5׀MsI^ ?@zK׽tUQSQQVV$Y t%a j<:sA6I9!6! g{,PIӭ:}< V~6 kE=<Ⱦ3 KA{"& H>>1&8jc1Tc"Ա6NGO㬓%=<4Q2xeS/Whe#>\Ozt:>)S2Nȁ]q `GpU7têS9z5TQ)1̲id-#r+!'ozy׏,Lzu \i-*tTP..&.oߴ$׀R3ZX H#PY mɱ 7wo8/K ! &HhOyuNYJ e` ?PA8u 4uT 2G3BX$&@{Q֩ԘXEEP ^ <}O>Mz)O~2^9#K5大rjx\j.=tNkPẑHd9B>}J&QWLE䂖!@QPyǽV)@k8=\rJy<4T$$H03H@-#!Vh:םIU=Z`ZxS"գ*K$\VR?G筌ӧ{rK(Jk ?'OR{0AP%U6b^ {<QJGeRC4D}GK[kZRİȲ!ԧGXUDβ^jK$>8Ȍ'TeIȑ{[8'ݖ㞴jqNH*KG>%8O(F( ߎgVCKWVh*h^x:Ē"$a }O玸iZl63B XuFΌo$.. *G߫A珋b,Z'iEY챏54FN/ϻ+OUW=gF)wi$#Svww MMO[4cE$s$h@ߎ}hdˮG;TxD,b9J i 2Yrms׈i4G"D B u#] FT)_Cg:*UU'P bG]^=DUEO+h+[}K9QX{g^&8 (Vف/#JW U$'[Qz:~ޱTc-' 4__e/p=liӤS$񤱒U`+C#)]H[X(? 'OzK׽u~{ߺ\u:Ko—Pe&ߺ\u~cٿEEzHc2S%;˟"sJz.NT>[=9o/G~C_egyj7MQ"$ u>X]ou~@ ErRVD6C`GRzhsîYU5\'i%Z0`AVҤ^a8bUD MvOqn׸ (ԙg*G? ,jqíǬ"3dtUgTUX_=ϭS]FZVy5}:W/ C<5KA𪞵3F95C­Ts2HUk>SJVx#޺MUu,ȁV-1h<ڮjq#afߟvVgO\)᪉W*pIh8^ϧt}{ߺP$íf M:1tt]I B@F݃#G xLt,)]F7͹߻}Mz}:MVI\tRy5i~,O:8P@P?o+ mew²!k54+>UI RJk@IXpl}yklNA*EZZkʈ0Pxg[G{^$WZ>GUJ~O)T)X)IDNNn.ݲxFTUU~QIJx)#W $W\wR֎ zOV$F)` HfPF2+)d6##>]l^XW\&Bo#xIoOCOtޭNLch榐:=3% *t /ov Tֱ_NRedP$Fqt$I?z? Vx2ZV?7u9jቼ~ _$V:bނuz9#@YIfu6,u @6߉Æz]{{^>K$jxΙcKr ʐA >ν-5@K("߻~cwu撴 U1A?}O^zBzPuh O"mZpjkp[u}u~tHPYUPK1?uҩAftK{M~=؜hg's+QC0x6]~.<4,mDҶ u}vB؍W@+ZxWeddXUMj(ݯzRڍڤZf%F2<-IM$C9SHDб(Vjޘ Tq|{NcٿE{ߺX'A%ٙc5RHʃ䟠3׎: ^E$JDQPZY-IQ:|\]gS*K-L( -$2VfAΖ߰pEZ5:AVЩA[뗿u-D *:փDRH"":@CX{?.xi5e'[~nX9^,-$iy4h ԂZ;5+QׅFS}Lk#HezE({i4*(:&9EJ1%|b]!Dq8_J%5O{^׺u{{^׺u{p9<?I`=tM=edM0Y%s%X), ,MQO^*z#h UFWbf1G.z8$q٨q%dҠpҙ.5J$QRn\O~8P:5=N^: JTЩ~B>-[b<uQb?BFR(:QZSK Dm#K xB@U+B0zGXel=Mb0EVGYas6B. I{;H{Ȏӭ -K$TO\,R eO^x턎†j< Lexhp+݈j )T\^ijBr} ^ yuJ xifJJHzg ^ސH@t>gN]Ƒe?RIRI{2Cbߥ/##@]{Ӭ_s) Q_M &~:OY 2JyJ1k?E>}n]I[Sƾ1%@I?5 })"UXIdt.AXΒFAZD*ì Yyې=ӫu~{ߺ^:Xi3ocba@e2><ϧZ>tj؊C&[$` _JqZt]+Ȋ!)|~*u#QA#0hڞ5NE좙?g}f璱VHpJ?ŭ,C}7 Oӟ~,kJwubԱWIe$jyR6>F\6>륫M%(zoy~ԟz}{ߺ^FJds "TA3$2uǦ2YovSyVM)6`C ,\!ek HF3NՎhrzύHke: 4xJEpP*M6ǭI>](=cٿEbh]sHYUE@/ïu ΓѨ(TDAƊ,HV'vU9{[[骎OxcYqG#HQewbAT׬'x p*)3"<:1l{0އv>]N8S!mQϺL3N̒R ^0XKHg%YH.{AT'T0P+ηocDT3ԁ'aqZ 'KH$XEYQ ޸s׽u~{ߺ^׽u~ax`P1r!UAfp=x@ʮJ0_PAۑ{u :(%IBHURH*VTk>u5DE+ "DQoDcI{^=sjh|nژ#Q-!`,~-Ljq׀_u{{^5E,5:LDjh4GREZp}8u+$xՠdx姨fz"/F`VccӜ&f3PTE~nmot4Յi=dyyqה**(l׺ߺ^׽uSwE,"{c_NXC1>I;;[+p_OV$ %NDiҹFX.N:3XqCgy#P깖8/ɭ8u N%譝|7 fM"XrX[莴WzWDRaT,ѩa%T\hn}S^$)G*$Dp?4dxq`br#sԏzD/a1cu1G8u3%9y:mfF.Ijzt+)`vRZbZ0`F2zY&~ jAטS?>QQ8QDWH&6Z(&Oz 6uN4HQE ?^׺u{e ٙPk=3 dӭNҚjsO itHʣxlEz# #cٿEA5.54R,0FQ+O{8uē:`zYWXLf~H^nJ:Ȋ202 |sd &I ~E34FuG֗]W73D9XDB@#\OZcANR@U\׺Քˤy H9ǽQZiz}t?B #?׺ߺ^׽ucRʈ@P?'{ڔ zi)`0I-"ůSf2T=$x&jfba~+ MB: 'u j ?B>׺7\/JzOhi!aGmNTR}ucj#o*bcF cuu(E  ]otJe$oIhi<Ǘ]diM$Re? @-M*c%J#m4,1BX} *:8=gS5 +Tɦ(j) ho,ё1{j1l⇧t}{ߺ^׽u~aU.J)[o{׺cٿE@'*ߊjR*lŖ:qݸ\?zM: %9e-H[AGuS?*TB= @xĢ4UAZGf(cŏh#Ig1"$"R(-?>OV&cy>)RBu i^޶`8qpBOS=~{ߺIx㦫VAPӥDI*++AVnT棪S?ìQL+24kY9=?_ߪk׻1GZ#SP 4unbi)eo '.=րׯ V:vգfEmhC5ϭqӟun{{^=W̱E瑑bէQG$Xo**q׏~:}MrC'R Ȏр.<1TcjyUi hVSs]Soǽ).A - ӰB%H$l-sVGZ>@b!"iDP/s$1<>ǫu͙TbT@~ByFK%FΑ if*A[P Y<:̔O"˨db304ybGj[RajITOY<`4˦(<] :CsCGZ6\mAMa! Ntep50jnI%}U iEȬEV񨹗#uPzu׬dh,#I&%GPW?EH:`x[aO-Ա]wH$h8?ݖjqZ4æ'9f jE)^=ӫu~C~*Rle 4.)`i:=e"DxXY(-caqZ>M>Κ29:))4UK%IXf]RHUZpA=tI(VXV%GNRؐ;74ϗZ5Yh"jҷX:B[Ix܏z/R41=TK4a]ZɀBiY*$AT:4>)7F1މ':]{{^׺i+ʑ(gቺ݅4u_1SӔ,1+V$g`,BrIQunNobҵA CCh򑠿yN=OGdH#:Q0A}'i5Sί\c&j5i.!#E:N]qۧ# O)L] BS!pK޶`?8Íz]o:R誏 NahcR0i+* *B?.URc@&M:P9cB_OQH4To{^׺ucٿEqeWRVV>׺)烊YT: EU"T_:uZ8p=ZdccSG4$2H0m*[Qx Ӯ S7g34__?#بǘ=lӏ>>()ePҼ1TC\܅Ƥu(S׭~{ߺ^׿=uJJY[\;̼>m@ԏ>@~o^)d!`!) !ZwKmTǻT괪UNh(L;‘Ft  } MNM\|U&0 {mD@S'F֕L u a=&Jm!G_~W^3ײ<:4T!xH+aq > l9S#f?M1#}!O ^i "hYYxAvBH. ;+A&2lG=LI_ jZx#^X A dkR8Aґ$IQ$I]`x>Ꭿ?~u{"zr~HDZ4$%C${"gPp-U^2o˭D xzIuR42hu(2VFD G~Qײ~]5MFB:9b*&) R)If 刎n/X}}ڢ-C鞤=d<4LZ&YƷ kb%yzOu,|H'62Ĭ+$n9.k_ ::X*'Zt UwmD#ضrA$O^8=>B)"/,g&u *ꌓ=h?޺\$9T!~{‘j_yUS*;@T=qLm(QI,ef%CbyY^=:G+}QG\%q8E]c$[8INYQ N/ J{^("1I*K XF5QT,bOP!(hiyNup<SK )!]) Zm@ )ՉS5ʓ)iM@DswRH}M*ië s.)ec_uzޡ(gye6 3pX#UX A k{^׺<j{$q$7 kf?`׫N,USz'hJ#i*.tU=r8-ZU%n=Qj3ij'q+NU»o!H Shb=b=yˡ׼yd`!s%s,?{c|9 df2ʍ\ϺN+A^=w+HEP.j vGN]{VE7M ?ĵϻp"W'zV+ F95!m,lG'luӕ+iinZHcf?Պ$_XPՁ) \H q6>߁׈*jMBبzi]Q E*<ΚoupT5u&a 4lY YuO߉ӯ:uxG*G4Dm8#{R3Ձu}{ߺ^׽u~{ߺ\t%Lf@~~r׽t[VUL%KS記[}IgYb4r*"2,j4-1.ꙇ'oz>gϯgO^պu{{^׺u{{^lrL FaELh""P(]ll>ymfZ* s(R蕜ɦCS$EJ3Jcן{-)ր5NպuvE Ig<#ĩ<+Mi/zuEZh#7fV \ǿ1o^$ScٿE{ߺ^jYҖE*멘lS5dϽ҂cTS,,ΒH<`-,,}1^#s}QrSoIߖט pULM$O3FHcPx*iɠ\&HZȎZTl.їRWׅ~~]zc{7y {qKéuFqFn"f- 1H?l?˯*tHE]dp8C!C8YF}G'z3ȠV0Q_Y}Ԓx@8uԦ 0'-@I ?{G^2WhW-4 ڜbXhP O:ֆ9V׺Q*UT}@ ?{o߿u{{^ Mf -mLp ׉'\)\2$0ېת{^׺uU@_LP4GboT4>#9kPT02!UL `?pO3zNE 0OZ׺U D`b̫";'RA#{R^=z'-}nʪln+yjqׅ|]{{^L$4ȒT:HcHM#Av+í|cƷU%)RV*-Zߴ˯W\Q-JcIϿPzN5REM,7j"_sA%l==jj]X`~޺\t=GerβPP'H|R2׏wZѸtQGF Fڰ:@[Ϻ uj.IOK됟 1'@:cٿE{ߺ^4@HI&L ,RtS Wy׬)"=L%=v{ ]o:=9.*)R׷΂8P =#ízj-UḎHbUת0TS,Dq8TfdXhw J[: k؆}谩ϯPsӊ fI!HV@$򻼃IbUU>Ip'#~׺u8iyb_h$wj(U=h|DuK.>[0MEGb=0ï7u-<A$^)c M.ƺJxΐ H#"hVYdgcIA;O xJT\G q=xP’pRI ʂ>HHb5i'u, 8R+bLEu$%"qxfym:UQf偷{%{T=PH̢8c H]r8Fu&hewI`0HbX1e)AG1>G=t AG ŠbY#~|1N?Uѭ=R#HfөiOvMǭ-y "B"H݉7'ދW8z^׽u~{ߺQޒΧ0O}>>@o(?~pPHePH`=ׯ+ԐCC+U*#~׺uUOL2EKX\T ~x : q=DlV,?R ?OZPLkUldm̗i z?ߕ8/\|z$yB#iq"QD=ou'eg\0@lBkƧ:_u#xX(gU2O {d:zOJ'vS`|N \3PO- uBrh:P6j%\U{ j>Xzo `"#x#ߺX!c,,Hl H/͖IME:S}uՅXjX^[{^~{ߺ^׽tǠKSVI2HяeqTAR'%DB2F&(bLJ^'SL? b"mju5RC9 STe#+>@>#RxW#T$hc +XGN9=ygOU=d*QMU~#\ye@'qu_C@='߈׫_&¢ O4wPu^^׺4 2J"ܐ "([K~#Ԑ)iD ha\ki D%}7?nu. X5/#D1D?7ARx=(uzz I$OpV2", MaB4Nj2zQ)+uD&eD`O`PF\4XUHYQUP?'V:{{^׺oM$4Rn2KҲKndm⨋ qƠcԓ0?3uRdt-_5s@'udpTuagXCNXdjy yHw >\:S4SԼmsbPYZ6Yꬷ+=u :p TC,.GC^Ǧj d5cDM-jA0FPhc' ϪuncٿE7,ԼXI1eX} TDI{ Rxt<<ʟ{yUIVG6#v|҇o}[{^믠$$ǿu#U CMacWoHfr=ܠȮz:t5LuGub@*:Htt`e${ u^׺gM$<cVy)P͡}m +ʰ +:0z Q# eIZ)nMz|Q*cSIwz'@x8$WTև#o{^׺u{h֨ƲY&8"Vd<4Xv& .v0ft5դsa $A@TijZPFn$,!PVUY8?ЃqdSRGQrQY@б yAUeMiNGS(}^6ynH1N=Zt1TJTRȩ ,R}Pp) R7z0zA#KKm&Kx"}ҧzPx{24&163 M@݃ǭL9Gu}cHd@*l0e?V~xBQ:J9j~TFZ݃~ cNBRࣨGSYWĎ$q$0eU "zq׽u~Gi!7kn~'['H׽uz)Uy5wTRrm{}t`׉}lApG #޺]^׺uPIbWV`#c`{^ӘXM>Yn3C\cVIMWOEPY.SCL|i Fѐg/p=AZTZI"aYJ lmj]{{^׺mɰH#2 /~ 1+R>7O˭7Pkƞ Go"H-a g <6s֋cD e w<3HBO'@(Zn*n^kۏvI$Z@:KYJUoNʁO ^:Xn@$}ԃ\TaJ ҽw=q0>2'erP1j/] ߀X|xUbN5KOE}ZT#i+ZZ'>C}8u/ I6\rM~w{`poߢ +NMQ8*duG0k*u ,?7z~ΫJRe14RS;IVuOsheJ`ȋoߪkSu:^׽t X%1I@>]d1Ūڴ(,ߩ>Ix8{^׺u_O'=:%cO!" 19IeHHYcI#r0GW*I6NO~{ߺ]%X@`=tsS;PNb'x :uٛ8jt^i0S :( lH_&q{:n#;46K*?,lx:,6v,zh߬c)ׁR}tJTձ 14+"-!"Iͽܚ(ê QzaNKii!îI+T*5-֣ȦO y+ E5# y}B=x|Du#T1LB !pIz^wqQc>9{[Cic`]As :ף)m@7O~G^zUu9JUcb8X)44P-#E7YdoK,ߏw&PPLcM4ax[HAMp!$ZW\=:{U;3c,8)À؁mmK_ 4'5rQMRЖo!}CdK 21-`0:CV:XI\+hPÚ}mdj8ӮuK'!S=!LL<9 9>^js7:]^׺9y׺cٿEz{^{{^Yc4i$v*bVD[H`UةZS׭X'UU?D~{45zO#HU Zu )̇*cGuUkjx>=!a1Hf'3֎M+PK+&NԓD eVd]a}>DfޛR0|M<']ok< t~e s>{׺u0iQodFf ]uA `^^=@ F)*D֨?OٷVQkE4T Ck dr8Mj%>{[[>׺gÈ;XZoQ#iV!4[O41_>?}mx^y*aPNO^4j-L`DYU 6 q箪a) [Ӫ*D*MDcvbys$qPD6W29ck_8uIeo=d&@RJC5V$[wZto{^׺UR]]  9"{z鲪Z`)hi2ቈl}="ꦧӮHjB VòZcc{R~}x凥: "d'QC/+u<OϺ3*E7 _v,(h4%IKȷKHSo׭y 4A۟>cp?#ӏz<,u 24%ayjUquEu)"+2EՁ{[#-V1RzXcxٚjPAuS_yV'8i i*@Pl,ZOS^0)?~AL(id~OHIsޚ:O^~MIEz <*mXu]N\]T+8+$Fu6FUP wuLjo@)%bxap?ߩJV:u^׽tQi~9fA*V@?ǟv LR\W9r3VEMCP)#X`xvu=Kթ3Sʂ!&&ިg m 4u@TjeS%LeS3)đJ_ߏ ӯq4#: O?i=nS1$dJQб^@:uJ ܴ"_CKONI%FafTYz,OR=u{^鈱ZJ&I%ą򸐤f_ϻW xZYP#MR;TRj$f~ ҔxS($9m?qS3Ѝu&8u)ǏS}~{ߺP)ycb1KW[)"GzH,t ;'3[}'wz*viAiY'mrR I:_u{{^׺cٿE{ߺMе]\zzʄ9&O:'ӟ/ oti^T\ 2(U[>4DpSK'(j3׍qCD3+]%,J]%P {4"x.HE#YIJ%[WUAӧnS%B٣6 $O6 8*x#ӯuzQ:M2LN!HeLɨ\[؃Jwu.:AA0-g4'Q$z֑u7*! @g qEK+kLtOQ̴ JvL:y4:b@ PTVgZh+# rK-e}eϽQZuu'hI"u(Ė2Hޖ,XMϽխ38K}sp GꞪ5&kQ|ۯT^V*FidSvyr?HcS͸oSXfhX1^ݽqx4%<uIRf2Rxޓ$ֿuk }xxҝPSu{k @Ė*?RaԓQV ,AA~*1 {$#PuòTU!$tƖ#}Q^o\$Guѕk+v(iWL+'HcZ(꣋*(KUJ5p`@n}ǯS~7@KLnp9JJPX76\zc$kuN }ֵ5.Ҿ4P.e>Yc镮ʒ{>Υ+u F_0EKARsG[uu3޺^׽u~{ߺ^}o\}=upV]* ^~{ߺ^׽u~{ߺ^$aeFW龫{.QZyߺ^׽ucٿEt*cePYQrO=tAQVƺAY3,l Ȓ/C zSĥeEiuUBF.|pUAwpDi6=Ai׉izl8SVF`&p=>~]ozu:WV/ hRD2FO2E'ӭz-]C+2e#$z׺{{^׺u{RNo4TTl>ot_ɇM_=YZG\D?nG^_6_޵^zuZjyRX"uKhi )P ?Sƽlx^閦,R&_ f}J*xajW괫Ezx#WFJpI#$ u O>Uz ZؙOE)2a݁%%Ak[zp<:_>G"rH##uߺ\]5gvUQrI?{^麞s25@ OZѩxbz}=؁qhN~u1qo9]}s׽u~GE '"Sieaau7?`Th:UO\ۖoPG~{8kHZxE]-_XD+P錼}7݁4z2B}oǶ^2TS*qBJ0G&*vN&jqV#a7!G1KSRG^x 2FTfP_/8W׺odm:_̱DDxZVeӚӭu95@ 5?{^׺$cMFڝTQf?ïT{^׺jucH!j*$(l|HvQ2xe(* vQ}1MbU< ?u[߁:g#qIYbgLN0V4$d&Cp}N|gs,SG̈asK $Z`AC$duGFԫPFH#P_7:pim(*"YSA׉'s, }?b_3O )?En,'SiJxlT1MeSAc{ @hfdRWj_ ߨתGRbRׁ,tI,R=l08o{{^׺oI*BGHX¬ ~J:< S e"]:QTu^T MPS@'r(HM$QZ[KDGE <ÓOݘ |{  8 tA}BaT؟zsU.}`Wϭyu&EN%L4'\v\}qg#&)5\G2+_~{ߺ^׽uYc6V҈. ,M<:}d, :I#Ϫ'Y#.HWsJkNWwDd1$o9cGi:10=-/$dAޣ瞵q=g@2RH7hQ~HPӭQ^{^׺uM2AJ!meQvv&Ȉ?.@~xW3+ZXA48$?F)%GQs҇:M"`oY*R9>1 c#rmv^5>]iSרEGVR8UGJt*C)$.z(XӦژj*)deLI&RPi3p< M:MR-$GHX3)E9H܆{٥u+JytRE"'(HjGu{{^׺ucٿE{ߺMl3C@Z[5ri_3<ǻ }hOPG?6Ek $_͸_߂s֋P]{)Ғ)< Y]E 3Eo ځS״ZujfY<V()AٗJ^zguIHJ'Z# _Gz_1ԉiLrKJP0)Rlln/U#t4Gu{z* G9dQ:׀oeg7a)* UK;SO=DjZYQ$IEZ3u)?>-UN2E ȗIwDnK=# ceuWF +)e"V뗿u{Tϕ%X^3ȽŇց:R㦉)$+z ]>ǿIu$5HGCҫUz?>.۝vX[DXo{(xZ}ӫt[4&Jh|]+ZHu zN@bhM<>e xR7+ԏU?=Sϭ6$@tҀ ׆>ޣW,tbdֲլys ^~]h˦bM!JH4ĄibnXbSQ"Oy$h-?aom_.zp49Q]Y{iïu:xzty$@6Yf8#iem' ,M@Wpf"9*lVxMȸ ȧ^Pff$HU,bΥnGƝhMI=X :]{{^UXP $4)Yhڙ{^UՊ=Is%+s  ޛ=Xd^>fȊ̑DĪE @[sogTӢïRNZE ,r ȷ*DNm,,E߈ukǏM,uR jZh劜Z)T3I#=f'-B)ïp&)##aghieiO*%ϽZΕקmn{5'SheSQ{ǯ⚒$<EWVe&5@}م Jjsu}{ߺ^qj&i)Jļ@٣c1{:2Ut,²4knVeU Ɨ*(^"#XH WVx%Gxl ;n?PM:jE:wtI$_?0>ÇV # [/ Elx{O^&,x%d2*/˪4>-hGz^(mg{<>^#)F1p@>죉<Zn=OU,? ^<AyhT-4!'=x¾{A#c, c~n'/쪡 `.zP@M}E4r}ZuO^cLM  j@5=xM^zWE! WGgf x&֫Ҥuju@G0tG$4o(,)Cրң]_SҳNTNhȃG? h1Ǯ <ΰִ:gY# ̄Iǽ=8{[6L8A)FS.Y=,&\]rӈP%𳬉 S},KM`*)ǮU|Vaiuy!Qu0&ߨMzO:I3 ikIeDݡm$6<{=l|]{fH6uY$ Ȍ3n/41׺lȹJJZvEBhf,+6_J&ê=Ii(kL2#m:O]"OSuUX?EH`C M9Se#ǩ֏UAZhֽDtiPqa>jj :Ғ5|#TEO%%t<ޝNBt$R7\gЃ9 K$k ],hPy$U[C}܌hIU1CU$lD`1xCRX%Sh{O\D H <'ZKU%m`j)NuΔ$x-wi\FAWWڞ(㎊ᑣ@ F$[qfrxՏN׫u~tx?^뿨Qo{+AxgJX-^"oXL`?4Roǿ`\jagA>H|o~xWѐOF-?K(cߨ}:\pi%*MBQ,~M,Ur8}xy.}׭~5K"Uj$JeaT,Ղ`.@`F|iyϐOy_jJFC׉+_>QRȮe^uQ)KW~XI>C['=Ld'BtMTK3@[z\Ԉ)ND,Dga1A׾ްbe׊WOo:R8u=D!8"NY(e?ݵz֩=bE4U<pEYj$lʨ/[ߏpրǮ :3 Scr}'X7(׈ƠDh Vሄ1HgTr/{$PzGGY@tAxmmeIQ0{*Th:EC-4J$oO L٧bL{0 >*zs^j^gG)=VIkiԂE ?MOkZ3Ti*$k³i$ߵ7״NƔHVV ,iGH_K?TuN Ӆ5Q U \t?hXϺJj:>GS=~{~}֝{ߺ^׽uh"@F$Pu- Rl߁#ǨUpOHcH,rĺYىO.XWuT,ćJLGԬA dǿ(d ~u8<0Q?ߐVx}9 y>ei*?$t0F ~qVU`}܊5굥E ELZDuF#ۉ$?H_ުBLJN> '?ē׺]4c)t6_u^{^׺u{{^GuQ$UEgd%ZΨMPMjgSN:o{^׺u{{^2f @Q6ߺ\Ud7V"bT#`QiDDPPu{^-bZ. :ݐX>8ì*$Cʰ?~ATsfvCU2X׊"Ip_\Ou}{ߺMD+&5L4Y"jy4Fğǻ^ğAԚI+sd\:%ވ^>uAWAVFV=8u*(zuAz~>`{ߵu=#UHQYQUQz㞷/~+Z=S ċF?9mFI$-Or^׽u~{ߺ^׽u~aO<\ՕN;@O]SD !oTg$=x 8Iu71H-ʺވ^N^5bKy sB715B :Ŋ{iZcTz?EP?dk i慍L^HHUo:,l ։:C:S4* H۾ܣ,&tLcPgĪ tTRGЏ3ޥn=^!㬴h%d6Bk^*1NR2&D|q'=葀:I_u{ >S$IPuyT ݗΝiul_XUDj Ҡqtı\O~^9ԴuD7I]Vl}o\tp՞_ /%_>{Z]@mH9?&6X،ztK;T³4ӇRkk)Ş{t"`˩>W%O k]ۑku*,C*$X#Z' z53&he*gQiyu~ޚ+d2FDr &M1v"C nCZs|+E$P# "4dTRQo׉A*)D49:x|Fȳy R}@jP~݁.j<>૑d9hD"G(Tm$un?QE  xzo/Nc @Be%t+X^IIǻPOZ&K nȬ~Tu'ޫnxУ~ѪL}QsiX[ܴnijsDǭ{[׺u, ($ a~׺#F߸uл<2HK%'o|>j8uONQ~ўB_M=R^S~{ߺ^9\J)TW}MqsvG T 33O& <]Wzw\htW+ )Q0PUեz^f0rő<"wZ90tAԟzcߺX!3"qȓD쥔IPA(C߁Y&i#5IiFEgdiU:#Bzu^׽u~cٿE{ߺL9#Y}Q,XݗK( kWUA^NgW_!U5ED㠥aاDz>LK*^NcX⎦Q*X=3ִtCҢ]KyR;j<Hno+{u!6"0*@7=uߺ^uTVUO$`Fq_nqjy+SӘa!opZL29{WOw[x㨴=n6hgv*#:F-vV7{[hd>fVoK,=Յ lz]o{^׺u{{^넎#!҈~U<~OpXi MQ4/(h܂dA$mqǩ {"z53ԟz@jS `V3L P ݎu}{ߺ^׽u~GH2;ƱZ}qRn!ZB9ZQZsu0SC?YtK ٥!T}I&U=H 1vHAe>G=׭hcgq4T*!@,rǒ:\u~cX፥" 7OOSAװ2znZʩhaH@*˔1܋@֪NXx)ᮄ4&Jxω%eqϿQsSNVNT PT:26bi'9B8#S^{^׺\)V5TE2:/!f n]=G{{ZրN'I+%;N3`¼&$\*ۀfP뎥Piܴn15 F,BSa 1sŽϫ }?"rJfyg=X5T:&acTuҐK U# ;Ӓ#-sSjH\ڡipDgYÄ`߬ DyuM <ouM>νU< S,riZ8|`$N ]7I׉NG"J"+E_H[럿u{Y7Xʀ x=qs,Ŵ!ȑd'k'tkiVo*$=׭ؒMP"It9 h+G+$>QG"x 1kǺu{+I|:KWJ!ą4R*J0TޖO~:NkCײ$o#HҸ8 .X?Sֲ|:[Boc6(#浮5}pnxSH X{[G쌁I_(D~,,4ov>h|PְKziFɠ2/ nG3C׏&g'u)#ƌx(젲>pM8u==3ݥrYBx_=N$@+iTUH}U 4if1JGW{pqs?0?ԙc\Y#0TR bA"5ozC|[ ,+K 7A k'Z)AA{^׺oiECKSPT;1DѫJX`ZH:筂)7+Vjh1c(G|\=Aiօs^[ED$PS³M"I-Uff٘-`$;MXa5"j, B6PI5OvxplKxjȔbo~cׯ~DF.l,TϷ}:f)J$&TMRRytpG UYG]BIV H׈Y#8$H"(U$:{YM2kVO+m?mzOZuѽHhfȉ(@W G 1ZϩfWRIdcĬRwӈTÓ559BD>6Xɷ*ǽԂ#NM&fUʫG4gLUjPհèjsMfIhE奄1Y5_ZYzPou~{ߺL}Ħ$kM#|_Wp5P֚id)ut *$uUf̎[RXpEZF"hfWj;HѴK⦌*K>p)Յ ν>{[׺oP;XjFO_ϻ*:`:MPQ R  GCN xu޺^׽u~`MQ:G8{p X)bI%d ($M+:zt`f6hSQ@)顏Ƒ"Q.QwQj s>l :$1J$!^,]@OU#ӏX ëSwk(,K.#6WFVF ${/~{ߺ^8bY_萴B]+ HI?׏{<8u׏S=~z{^׺Q V F#ۛ[~=z#C3ʳTJbMC2@)rXq^=BZjzIbFG'iEG7~5Z3ӌQ%G ?JO=Ԛz uߺ^ f$1 5$ɧԎj=`)-QJ7~F[Z݉*+@:MMf1P"" 1?_u&RJcٿEtbTO{A AP}u߿uoߺ^yrZXPXd1ȪηapVxj*JԍiǕPdޙ=I?eZAŴ*:nS-*QRDٝBQujz&SzLuм#ulEԒu)iC# ݙ2*IrOI'VH׽uZbO4ʡXX<‚{Uzg.%wJI':(~JI\_ow uR?͐Dd c5zK)Ҍʍ(RMMY=xTPpg:q}_b\2)$l9WC>  : ]2Qԕv{*@VhOTtB+SRk"/"8\pX V=M׽u~`J^ .T ~ H5xuMp>zt%DzC d59]1dã7FK #-BU[R}u~2UA=SH7%B¤0*ȿ@@w @xWLj+TGO AJ'2UCG \9$}>Y5x&qy(&MM#!`'J"ژ~*5SӯhN31 ${qkӇ{{^?2iIhоx8{^׺j#"$HC K$ih+)/<v U-J1ע*‘FLu=-{kqjTufLu< xKT%c 5iިޝFX䂦deJIgBeŒ([i?R}ZGzP{ou~'k*'Gb]%e^T5kԪ5U&:`UC-CK%#1/r,}`dAԚ*YiLO\cȒƤu0޸`}s׽u~{ߺX``JԒ@U,~{E,2:JRO1Icr!n?֕ uSZî:zcQ#H\q*jgZ^VU4!+̤MieVW$߀z8OȡP UP x{{^׺}A##ߺP@@I7$&T>O^M EREFeF _Ͻj+ֈ넕5nMK SO$'W:H_#Iu'ǽs_^Su5%j!?(,d,zH^W>|@OZB.V~S={׮㭙,aR3->HKXi4^IW$RU@$y"q(G0C¾}H?^>xStĿ Ru c"4p9DKOPObo{QS։L WF#yҊד!V?.BOcpW$FSir*M}O.5*ݮE߁]xJb) jV2 j@_z׬׺u;I d&u<>+\qզI9 8@1VB jdR$ z4+ki!BNMVYnt5-IYr`֖#G[+O1?W]-^-+5K}'e#Q$玽Qi%eX"6Y$g 8ׁ&wRYc6V SH[Bms裨4L*j*딆RBvS%a/Z$jNj!KGJT#kkVKa >cTˬ-- k>9ޔ@=lWWЭm9`}P`8?kqZdP7XĪJHiroߘSujHJIJDH% :>}s SgfM#-!6ߏz&z`u<|0ΓDA X%~2EG$PAZ?޺XME: iYD\ , :Ei\޺^׽u~{ߺ] >=u߿u{{^4)+6B7oǻiW去#H@ӻ\`I&TzAV'ϯjx] >1=\^(D.*zOȱҤU/@,s(ԌKq#{EI#PP+CS)*pV}>cp?.3f -n}ꆕ}e:DcY&hDI ہihI!IS#Osm(H:+Ǭ"2:GYOєAx9cvftU^׭lpI>U_ ܟ~:i=lV<:q^׽uˤ}ZOotzsN{[׺_L,PQ؊$XhmD O{R֍|'܏Fh㙢fIVh)UAǽd׳p].8Yji餆5o%k> t_dij#j^HL PChU5WTzS) )`ċh=e,>xPƽxqE}3I"4m=DafT:R0QOzRtx#\AE(_ gɩ⎖t@IdF+oԭff-8#ϭ\Q?Qk\'b_P']^u/޺^׽u 5~c, Hq 0'{|Oο"AT7Hك_q˚^>FJ*zMdi&:1Ɗ^?V[sro䮐˧_t}Bǀ)؉Y7c8ߟWԤtu"F!JީN=o>HBR`5AH.R1BXgeWOy ݟ⯗ZSt@D\6 Z/OϺu( O$Ꞙc|@ցztr~?N.)c#XxU9&%d@c3zu$pLgZv3JɭBK<ڋjߚMJuNzz))" /dvҐSv7Fǫ:ry6NUP>S usB(z]o{^UnV1EIA+٘9jaG!o~>r) b1FQ`8]oI"b3װ OT}<3\螺ȿPՁ׺Ga-{(j(&Ttyb*,20,AMkǭE=)Ӡ@#ouzZi#y9%#B&J>9x<+#8*e"x>׺g5(y|TBDekI>[cHsUO*nT.?>պߺ^׽u~` 4BF@UIgRJn C`ר:*x!'Q@'vGO׀( }l~^~{ߺ]X^a{Km{u~{ߺ^׽~]{ߺ^׽u)'𼫣bT=#RsǽxuEB=ril4X{b~δ׭~FCbWX*ƟA$54''=2h:뇒yjHGaV#qݾ{>fxRSP(dԤLySo}=xLtK:"#}èZʭQH45詶3+T_ݍFNNGNպ%P`qIq kkQ=xө׺lS&y+cLTV'v=h4pzXb*,WaLjI(d8gR@%vhWWVdTp9?T+^dzb+jK[ IPY&Dv&C!:^ 3E2:XQ2/:pj**UB >î^׺׷uqwĂ6YTIܥ>S*5;ъ*|ȕR| g/[V[+Azur8ծiT8bcaMI#]٥?Zi>]8@HJK"|E2f s327OM[GQYB (,@$(cǽuռo+/CTI( |m KB7'ۥF7R+3=BA !WEI,iT0$~^@UTX/z@>ӝr Q,&mÈǁXCZۉ50}&g':3#4mo6ݴv\]Y:|NmF2̒wfI,u"H@=?ןϻ`(:P~ޜ׫t1L҈BhbTcN}zucӧס|5բ:kg[럿u{{^׺uFY^jxI I*rI.8EI15T,'G*^Mǽnz\uTX9J'Rʲ*Ooҝh ~V~{ߺ^׽u~{ߺ^cy+aML̢hX:I4}3qo\N:*f(1[ s&jC@rVS ) H ׽ui] IKC62Wb(<O5Z'¦ܕIQْԃݼ)׳TpV3T?7c$c@i:oȺ+)QcP$XKFTzLyI%/#XnASR> gZ"Gyk)QX㬆5,B|5Y{ΖqR+^GL4:vgo,. M{FkP!! ⧜ D !Ni v>^e M'@]ߏhӭf>oڭo.f;N^Aʫ/iu-!P %tLGI+Ic4Q$m%yemRycT 3ָzt[j 6I ~׺SL =hc2p:]K:9hZ"qdQ O%tb<~o5~&^gc1(r*O Oj(=z M8u'QE QH?Vcpcj[ *:#5 jH(xIY Dyq, 5.{0J1bz(f(#yqh.f>M2zD7e1Vᡧi6k,з g`S)./5.0rE~=zO2)SCAb@ . A=tIE-E,VZI^6)#R+uB5=ܱ P":)%V B1wiKX ZXiDQ$OL̒ШȑƁb ÑjiLu(+ӧ[&O<2# ܩ{8?DW(㩪y#9ڬG0#D"_l#ڀ(UOOQU@gYV8[Om?~FyBR"%ϕX82/MhOܑb^58B FJ:V+*#PW׫u~BӐ%5,Pv?z#2hT<C/cPHz颟IMoVQF"3Xi5~X먈TK%=#8ʫU-qo{Ju:֖SUbt?$H$I>ָ65:%5)|JCPf`' $FQ_M㺝hgP((_`,.O$V~{ߺQ穎uٕLQ#I/OO^YYH*2!buL5bTl?{4W6 P*"BL?n/wl a\ƹ⮖? fԬ](> کu\2dh2ʬ  _V#j_1hZO!]%R|1KhIŒ0$EL*eC(QtR dqJ1թ_?u}{ߺ^&JI S 3"GTN$ȧLqZAwZ`ҧt"JBbJO7*fZz؆z/z<{EdDδB؁=DU]M_5xu`2UAǃz]^׺u{{^mtt( P1`4$0xeRjADӬEDIf䷎0,aZ#ӨQNjUB$O*Y#6n jO^NujUZoaLbEDbnR*=zTgzZhi|4-VVW ܳ}OyyWSDS^$Id[?}Ҕt#+)@ł#^8LR Y$AQ,1:}:2(,7nos>rs^0t[{^$WYDP=Zr:{"Su4i_S b?֫N=t$$N!ǡWBMoީ=:>Z_ϩ{1o~δ~uʢ򚪄:I]TThH\ׯ|–MM k?oT9'I'AN{^׺uMM F**AWt%ZőZ6G^ =u߿u{{^׽ui2KqW!R4jb{xSM$OSL)XQ4mm! TugujZGTAkU.X/'^zeFe2"Qs't`m>{ yՅ<Z_ 2>,* ݴ_AHb8*$uKO4^~g^1J:QjV/+Xܬ`(yzBUK.*$Vcǽu$rB(nH׺ D'UƝ \_K{ZihyC-Lג[[`< ֽJ|~1A !@yU(V)Z[yadbX ~D~u )ӏ{Y-I#/Jw~?GZn6UԊ\5Aue.JdFuBC^U>Ƭ0I*8D_Z(y8ci>}I{6_Ee_&ޝO׽u~E H5xGᅛ}PufL ֊(GB~Ar.{@1J^4/AHd,Zx#HZ-C<zd|tS˩y)h"ye `)u?T0q:Z8TL}MOx+޺^eTbjc )R HELd@,$(iLͭz&E&筎:u3 K_M v&t괠ϧS[`}G~:i:@*et᭙wTA˨ OvaZF*>}f#r# F~>߇Ϧ35Tլ?ד[c:GKSON(.!1D|<:)K![ZhT#\qK{4M֩Zt9GNxtQ2>Z4))k% &[\{$p={%LkƗu:.,V9@5~O]G֏:EILʳB5 zuW]@x'1oe/<9uLG՚H ~8"{#Ը$g\R) ]Yycy)r  ,h%SPdn *3$V?OvjhhQUΒFIF8ZV"DIǁר}rA9)'K"qBcQURXbSJPj 9D y%` b ң {}rFY:2) zPgIQAW2+ʜ B݀gkF2VG}0D-IY* +M/Kzvxu@riǧxDT1wv/4o>z={{^׺Y,B~P_#+6`T4( / fT* tOvľ}{ӑ`) 1@ tݮQZHIoG +/ּΦ[cٿE4TcJ*Q 4Vbtn~݃PS.:S9XID1OUQMQ=n#Gq'UR%c)jy/<_jSN:3\xmx!zyZ8@>hOU9(jVb5hRD!X$`U=:zr^׽uZo4O7 ?g^: M=WR~ "!HoUVeT5y_S⧏[h:s4SƱ7$rO՝ϭR}Ԓxk?׺u{sxfE*є:P Z &_"Oo cшXǤ/*X=h@=rI#s%5&- cօZߗ^<+\u.ҡ 2::7_YoP!Wm1ǎ6<<;csa>Jz$uVU /([^}b.}Oz~]`J%]zC, 6ehV=eh멪 )34:nT-TG[9R|0%VJwK8I5]HS>AӞ5U:~Կ}Ko}:.Rt/O 432G 'qϻ>}UdOӗXq`J/WQ: >M;!aP-pG6ub l qz}6\s'WJ'V1T(t>p?#ӗ{{^ "H֥C \5Ѓ׈# TPJTOE<7wXRbH$C*)cy R{]UOh+ï fCuh`_ DqBQf@: X ==h`: A[V 9&Hj5J8]uPB+Q׀4=NċOR)& #"-YrPoǺnGu{46 ,r$3\t' nyhz}6W!I#Hfr"Nn}z_Q\uRj嚦G;$F.R >Yo,uU4OdAWi*nGF(Dׇ8gR}ucٿE{ߺ^׽u~{ߺQ FpDV[~lI{ZٴTŏ顡Si4rxޕ={U0mQ\ӯgsfZHTLc,,8{zQVN6^RPzb W~+ǯT<:qY#gxDg"++2%Iu}s׽u~&ڦ*zX#AoQB?COIU3+K\ hʑZ-Ԃ- |Рϩƴ [x Leenށ,xM@uTnjRe"/z{?ՒړE>ǗV>IGY$CC>íߺ^ZQR#+#A42yaYTFSbJu+TԦY&z$  :#DK*>M@^oRu~GZhgwa1%+R~ :)jU*x&4d){֯"1שL8k^fGrN,< ? zF 7"{4wzӗ)#$r#T#bq ֝n551M-}L\2TuIJhIEQD *Cyo+ si>j":WֺuU s4`I%:uno{GZ<1Ǯ)C RA(%)u ,/S^ }dX*F@gblRh:QhO-UiVX1LZe ؁+#=OYc:}!rݚv_J{|ԓ(%dLк.Xr>}ci#I#A{|ƃSS԰dI+Yw(]ZA&|)N=)պuQm/R)0'Ȅ ~k\uM9T"hRGjeby7j OZ8<U) u5-<}R>F(:9%xx 2$?N zDhc<%l8><:p^T_ u< *7+VZz]o{^׺u5$ԕ>'(Q*ƺO,zer29 vZEs֍pz&R%Pbkz/ J8E$ʿ?@i>duXNjTT Nɭ#8(?}_usMOT~xfq5MD $QN:H&l- M:hG\ijj4I6k}@_ވz&:q>'Kg뭝 8F"{hn\.JܳB_CՔ`8 d Ƃi|dEjCnCV51G4DB(PO{55'I`vY$@ܳX$F `}IϠUMJ"U0"H^0-Ԟ>+A^}h qAGSQ W}3 QgwA#Oުht‘ܒGM88!x,VxΫ}\\O.Z6qHȈfjȄ1xd+tp}O>hs׀-C^IASSS%C­itE 8#ꑅPG,oދyNOS*JD-cÌb )N z]{{^׺Z!I_ї5U~c/ϽZ>G˨}5 %a&eY _yf%T}Zoqn/vZ{EGMEr$1LbϝTT(.ŀh秧J$mzo٦<_`*xq:^G*Tbd6.<Aqdp>ν޲#vIots 6 `E>VgGKNB%Pc&]}׀dO 2ˤ7H-adɠ'*LV+ȱ)IEظ,a>H2)օxֵ,4lji&#"67uto{^׺lI*BU1(┷@dX{Z*̉5vs,n>U)T:JtLsJ D*W~33SV ѐ~ly**BҡUT65ExuӏY#QE7A,d_xS~9 ~}ogSȸ zMhTP."]J4P ӪH`~*=hoQe Jmy/e1%Gc[W&' fB<T#d)e㚡KXmzT=j+^)jKeKLlAq=I׽ux'[p!2?߁|uZ%xd68VR] # v z׮TQYb0HE?\NQ~n ׁ %%OV8 0>~}؁\֫z,1RG#,YIuCu<ްM^HYqdz:"ZˣTk oMS`GJU0'yHviKKƩqǽqzxEE$ܱ3K31n}КJp/uAI90VT,d=u>ȕ+%*<\CFcLS]H()ScHH[%~GWP>gY^ڢM!ޗ)q[zI O9hvӥvkZGeK0V(^5W),@upI<j:+:q: )@43r ?>pTosxєGb/32`OVPX/K ׭NSLg%dZJuhp../cPӭQ^{^?ߺQ$HaL+|leSZ)N"$hơ#AE_~޸ϭȩ F=< فG 'hcϨC"zP=C+J+IT;7 z wT>]>}[t2F&TNCG,mm#AGڲ*% }Ek{'u־cȥhhA@k֫-uS@q!s*,!' q>44ױ?IeIQU3/ b94]Ѝ#>ZZՏRh`f@R(uʂ#=EHdž:KC % R5Am(-ipXN-_=🷭y[8*uޘ%A<:doR[[W<@>n=h pꂿ?z׍Wef OMEKT4o-qq׳|pb2Hj_%{|#l|F:{N׽u~{ߺL<\fe4qjm?qesK>,s&ߟwj8=V:M'ԬI\X=lڸ4`kƔ\H>*iC RauGC۝+m}}\MG]kz:o{^NA @$ivf:#P?좬:p>5}OG9V Rftu~}2t_Y9$XjE h"9kc׭\!iHb%%1!%~i~$AϭB}9*ehkh16c{S%G^8Z7JhdyO~ڹ]9U4#T:ŌIy䖖it0vUD;}knm$zo}ӫu?~{ߺ^׽uZ =4Q"A : q=EL#$BBEud( 䙈6H{ֵ <@#@ PEXou~{ߺ^m`)W?PiȽӺ3ʭ, ֏N=ETRV^+IC#!MgQT$m/ੵ;OHڑZjUm'꺀:OV뗿u{cٿE{ߺ^׽u~{ߺ]px<b=tI *4C=4HLAX)1 0MsU40qvF~=[A^bS[%+h\CCV ^28`uRZJM C⚛7߉?^ǧ(if<ҸٽDPOu$O׺uVˢ5X&zF=>֎35lT P L h Uf2$f6u$Q t-I,F")e}Q/?RGێ:&B9$/,zgWĪ:5=BUADf*o++\~"GPf 7ۙ"Jtp5@b,QϽQǭӧCo{^=0$SE¦Ӊ9/'9GF9꧷Ux4O-BQSD* Jʫ-dK}<u|6iUTm^8Ԗ سƸ:N~uEGO\SJӕ> dysANS ŞLrC1 I)Ha*{&K8z']IHC4K&ɑF?w$G[S^u{ Y*U]k[׺n#͒GOgr]*зp_ҭt[{^׺n4ij&Н!ϕdeΑ {:pV7_Uħ ݀T_5zKIy'SXHF#P X}˯L7޺^׽u~59_R-t+\,jx2[ovZgG)NVQ$z,N+@:ir0$B) D#C6k {S=@L AP}r,iC%f߲6Mǻ`˭d^׽u~EiM#. +dub > +^x*gjEATHE82Jb䛒}JO3ǧ/u}A|>U!I{.A({ޖZRDHdF]2\z cٿE{ߺ]1 1 @oZG㖲Dhy'ӭTz$2AR?"z}w{{^|tSbg A3FPiV`>n/Z RO2 ڏ^:26,L_I)$/J$~zӏ8^׽u~{ߺ^aƱΰ<]O6.)@RIP~{Wϭ#k ]# lTyCNjzV_ JE4.G^SB*T?ݾH& PԘq45D} ,q\Xa_Lf_z'jz]o{^׺n/׽u~{ߺQ οU-2o8@ǀ;'^:KVT#nϩ>=O{[׺SM1 I0:Tr! I WA=xaUQzjh{}f"x/ u*P.©?u#FGh?.u([brl:i_ G~|ȧ^<נy&Z "Zzt:_3={@zӇu{rM_e/h1C׺9KȎ)>AG^=ezMDB4FEӭlHA`ׯQN JXJC T) ceN꣉N^պPhQ^Ms!CZGצh. =ggzZzZM"A+Fv']&T%VF@USpxAcN)dyiiJŒy1CGW׺ub>ߺ^׽tӖQӬfYf_TJ걙C“8))<.-%S!HRh2D$k*oݲ+M1QCt-ஏ#SG4;~6£<-ʼn=K?N}u[{{^׺us}u^׺urUEK LwM ZyÓeZӭLtñ$y/`؞u1R ݪ1NjGrԣ}15  **4RlhH?ZOUajG2S#cR2+ܞ5mFZ Nj:t^۪p]q%O"a֏u}Df5TE'ƃ <~=SIF4䖚#ONy1r2.}֡IUPR6*$~*%QY_w+|gzՑfHuم)#O?>^ctzRPM2\iM$AB.{zj`<ǟNT1T@&dbWO^X" 筊ׇS׭~bQ m!RX*/wco,{:j!Br"'M<<R?S|E ^|:r^׽u~DzȒSYHY#IA)f77թ>ޓ֫Կz}{ߺM"jx*U5,TH%N <Ij>=cJ4ZyRJ>_{\qn l^J4AEK[4Ʊ,K\ϻ)ïu#H{^~rQˏ_mF:2*+O.<[,hO.=ևӭuaUiMIDM(#?vy\|2Wfv]&g$_D1oDnOu{䚨*/$i ̵DPRU5to{^׺,r Datr=ZǎSpx#{"=k5SK~Ԛ52OW[fXZ$8!(b/ ]8V׺UOMQ >Da#`Ѓ׏ NDOE"QMդ֩I"E?wpA:pa̔*"s"U@:dӠ8qiu"JtIWJ"u2ހn!O^%N `3ӥ2RD\yWie:Y6ݲإ+ dhYSU/Cse(a߈jZǺun{-g7¶.\{+AZ޺^R-L +-T rSQpI<77ۈpV2 *:Sftoۦ,Y62R7AXyq鮮1Che_.MEBmrR[ùU8Z=;SjZ144K"A&%C#̞3.=׭~A諭Ү?BIo,g8{VVjgHKޙcB*I}A١׬%B}P~1`?4ϭҴ '|%TCobvkag0:GmI?u}Fzr0FV$쿧}@&h ܕ*9/UPA^kJxG邲4,T@h~]xyS~NX!H(n6x6n :(^b7'򈡿jyר:{{^׺u{{^׺u{{^׺uK<0Hu[  :@zOEṬo~u]m9eFgV7Q41>ꎳsʺJ8?G ?@׭墮S8$ֿJp} +CLC_Qx#5L tDX#y%Y uq'iI:G_zN={c$%jG'y'}ВMOVuߺ\UUJUWߺ\u{{^׺m&T^N}u~{ߺ^ׯ׽u?+^<KD:o`ףMocPcbGO^Yu~{ߺ^׽u~6ω9d&HYb@'݃0zP| V>?K3/F`='Qӏ޺^׽uD0JnbfVxe>9 xG dR7OPb>66Y:5ǟROunITf-ODC}2T/QbIԠ?^=k\u.(1dB<}I?bnO>=o: $r]\*ۓ"/A+׫N(iI)!Z(Y~k[H?U\|{'II$պ2ER#>׺-3QH)ۙiHdۀb?7Gh1Ǯt"}Qj" ,Ma߈ԫ}ucٿE{ߺ^׽u~A4jHi94ͨgf:QO6TZ&oYij Ftb" {=lOu5- -F7t u*vQ\NzMP&4sKRJ`fKPu B?HP#=h`²+UVCAÏV뗿u{H8` jѫ5/`>jI^'q)$;~@i[ ~UI8@'ʮUXVpnp8׺4$SOAJQh0Jߕ 'uO.[׺u{{^׺ߺM_SQE}~ k5~iu惬±P\κw?*hF}L_\ ?z}w{{^'O~&?dj*BROBYk1ϫ}@=SS@:tQy5K.$|m(R ҆:Eiԋ?O[޺]^׺u{{^׺5G$՜FWLjl]ݬ{+׫뀭)\PPla&'=kPR 7PG{[~B5V4G ]>%&0M}N+\ u7z}{ߺ^׽uXbtMr7 "{uu B?ԳH$3xM֨:QUpT*z}rDG+0I#ߺP K~aW?wsb}#֗m7RLdh j.E6 245TS8"k]=]N {6cJ=׭zzi²մRI ooFǦzT[,RG$V HbE1:"+ӴDlC$q訸$@|[>E)tNk{ l/oͿߺMUS%S,C3$RS3* J$B ~Apzs?Ă1OJC剚 obD񆌯&z}E:^bjCT'1#X'rѕ {[kZ^4zKI&8%Q&uzu}8to54URK4q,1I)FVvPֈ5uFT].H dS$ɨ~={u?ڗE5lnh*cRǞGC1pȄj)+!Z%;Μi%u?z^Su6eđ ث)>A[=`k%?

F\*+ӤO DyZB8PV"KTW:EJ\uzׅz+c ]ri_zɨ1M2ҷ-ꈆG=zEȵ0IU\dbG T"R~-”&jM:-45"Ad(%Rr?X/O"zpbw39%iFEf ({=*3ĩF%Vou ir6Vn?u$I $RI{x$-P(JT8{ޟSN^GZ*o_>v+b,%+BV̥''=z~_~6cǹn0~K#P]ZO0G.*pZS$( n(KE&Q%vE oQT +`t MY mKsc=kucٿE{ߺX*0S(TT#S.= uh+je3&?iOb~oM~μ:U}cw,tPk?EY oϽ|Z~GU"$RQԼub@ȯ]jfӪBT`Aڏ^f5"E{ āRIee+MN- =-fZuGmP3 "NmOuq8צ"%%Z8J=aIJ3!XOx>AMMiN+^O+(/Ͽ ג$d -x/xyt[\uI7-Lkivm %c{R^aZu8%C"!RnHe P/ƃy^YbqF.bBau}o^hO$}~ B3׸uYJ+ 2 ?=u}q뒌܂ޙ2ӓvhI|xGR H7pAPGuV<:gVHA0'x> 0~Eh|M 餌骘գQn ,Ud@҆4W{..dE垬){[K J*"(H焊5Ws^.2H䁘85#KV9ꎍf5Wl3JE>}cٿEa MV)XY E^<MtDD^d, !qqqf$bTP/SLEMP(; yI8Z9* TfFOPy7[$5OZ~JK>Axz]{{^龾fz%53cIM2W4u;Zgo(r8*^ x TfC*d< `G]Q ݵ~}IZXjIY(ښʊMMzOu{b ESI:\ꪎ,VT}{@G8ZvyO!R QI4תEzO1\x9 nB)WWFp=SUN$8(HA6UT\z4QhG!EERO"*=ӇZ׏N>uR]>lDe?"k'ybtbl5zt L0e)TPB4cC TV *zShf@u-i/T4Lq[3ӄU7C7RO*R3ժ?>*)Lc)U{l}hbSGMVOyT%&X_RZu:*L5qUF8 Z$[{P0kpS?xc2&RBHQ`9~jO^$N:R4XxR8e1Ie${FE5:iPK }6z_cٿEqfTRʊB$#VRD#OVmVÃ}EU&Ji槊C(CboXF12_M}ЂG[Ƞ' bdhE6վPAPHD8*"EEQ߫\?~{ߺP*\eѣS}? hGVEm4Mi*ZS}B7/̠ D\~Az Gu{{^-E*4.dxJe5]K-6i^E|hyӣz#jLAcRJHzTPZMUJF#WOx,Җ1#MId׆zFJ+OL! VǔzCSo@:=<P@P@{V~FDu0P)ORo\IJd>%/?߈^׺[QSYƬDJHğƟ.xu9bC"HHSވ#`Tp=wwa0k'uel4"9 eмpHئky>Uej#ՠ!A[hB(m1֍h>]J&Ƞ`u>(7IJt[{^׺u{{^׺u{{^׺uz4_ڪ <~F}|˭!I飝xMq6hFWBNEz )hMF]Χwk]&Dׯu~{]#=D)ۂ~/˯S0VO~<Ég{.mUk*F~Ԡ7 @_vkJ+!fUZjM؀֝ &(6h}GZ\TztTQ*eej+!ǻ=RDv(&JR5LEWHƘj>\:s{b$ X;Au3޺^x_ߺ]AAA{`Xeu{p!5P2U\3$|:qQhzx\hѠUCμ>HRGPCש^'N8AH*?'^8FTY#afV#~߁(i %LǃI!"lUd16lORV=pOAW;Vc+$1+uno~'OhC9=9V%?Wm B^>x/ׇԿu{ߺQdʚ߀%O#PuVExx$@K7lN݆S\bbQS%KS+ (zA&Gu44uAO"H2bdi|і,> O^Z>3fs-ytn4lpS=^]0T4Gm1 9#,=x1G^<@$MrǪ(5JdRUI5,O{fTb}[{^׺Qv (1oϥTֆ?zP"g}O$ː\%hLhuƗ`5RJ+ՁWu{{^uIK l%bO%K(=j4PWO$auTU} :E+\u*&; -,o , RzA<--lsJBSƴ!6XGgYȑX~h8]s:iG"y%R*=ӭ^׺$i*4r(tpU?{4׺j1P΃\/խvpYxu)+&j/'X@ ;I2YY8{J^KK fq$-(kg> :]OU>yj<~VHkI:sO>M ƮIl#NĢ>iïząbSb{j` ƣts݊4RVd?{tGM'uʆ㚚BtIؗ4U BAP H[ W)u{&uY)JdGH >-oog IxtOďN^׽uѽm'ߺLmTQO34u14BRM u(Lhlu@uZ^bOQFMq#QoǫuZx&hhScjU.,͠KXpHߪGרM'XFHp*Ax T28ldHj+"X jJA߂g'ӯjHQU*aX fb"ăϽUxӯq:4E Kd##H!0yCîYΓMSO< E4MemQJ(z8.؏u}w{ژϧSRIPE"+>2{Zn8\5MՀe#UbqqͿ*zX+P"vPo6aA3֎pPjj%DJh **CPJ\^+תu'1j"&aM-$fe6rʮr&ǿzaDG5UZ}vA9`/6ic`{{9uN",h҈GUEz9ZpWG2%ELŒ(A{SS7޺^׽u6iX%#YHqH"o3+$o`'$*mǿO.ƇϮ)r}!NŚD>>O^j:o{^-ZT`RbukTQ->#:29c"Ж $XM&JU`#24@I販ڼWaCo^xT@1k_TR9YB8> [Y G/RRaT)$(n=RaSFHTVb@PFI}=oMY~VZwh#ag+ >&,B{"5@s^׽u~FV腣FU}1}hzhiԑib5}QGϬA bH[OOuߺ^$rUcDzQ, ;-l^M?4Ke6{p9^#Ex%UQ%>KVx!KjEvj$)=ߺ^*xг%U:H:ZDx9@NhxȮGT:IV"'00#DK} SZTT%@X$CI ^m_~yQ. 0Y^w>uk=tߓJW0^c S0PUKPP=3R@(_"A=kWt@Ybzi i(jr$`CKD=ո׭ zujd@*)A$/d1R? <3_>GzJ]RI,YJa-5xWuF߲,.)jY@Vy(iցS}VVS {y - KU Y#wc&>FǪLKKHՍtR#j ?DըTF*Gv:hY$g}->Jy_3tQ+)䪑CPI%.Ȍtr>+ǭ c Y 3\H=Ԟ:,TX}y\>@Pu)jz]o{^׺ (s!6뤟~u\'AJT*Tof?= ui iQ"LDdF,9#$1xԟz{ߺ^׽uřQY݂)ff6UQ$׺*&iuDL2AaR@Pc^ZTHRChߨ>cqVE#\ !9)(D.]?폽u<Hu<1dROk>{TU ~w{{^׺%:H'9Yf}7Vଈ=H몥9 V4R0Ljkj,TQFzAOPA?#=ӫu:ı]pxk7HǮ5?NK' @ޝxyקu}{ߺ^)'g b9,ǀ$eB"5>?.Om!^Si`^M"A_޺\u~{ߺ]@~׺u{cٿE@[Hb[3bue5bonEPxzOGQWST7B:hAy$fy u4'3bHx08kZUp7Wx先2lVEE(z]BAͬ/@$-@>CJu}A憪zJ>NLG'9e8+֏QU-$U3/ 7zs"E5TDUTY@{{^-E$57-UBx#Į4aM و{x$E"0`EHrVdXuc:?>I@:(γV董?=:JQ$ή?޵Sҹ=8{[~{ߺX(S\ R]~:P%Ɉ%GJIOk։\[&be5Q+~AX}EGO}u^TCiM1*+d-|t&UK;T!>jiO.AϮ9 @:~t2&ە<~xOG^ qUTJʪxX͎<_#ǩ׺$I3R x,."H_,̦}}OZ^I͋d*oҔx_ߵ CGġ*窆I#QxڪXVSi#O`OZɯqO$@st7 b?/soǺubh+}-dRH,ma֥J\ϻT6iJcTȚYܸ~@d -_5[4X#L먩eYRFЏ{,É=tj׺(D$M6r@+z S\W:9O$_to4Ui)jdb&B"(Ͽh3ǩu{{^骰꫈:|I4gD A>2iM+ 3dJB^A8R"XoKd6éYcHgPnXKi'-}[J׽u#GFݍ6{+ïujx^IT K=| q颮WE c_;24u-<7_.{ ROtLJjyi مYR[OՆ@=gcٿE{ߺ^׮GП׺=%DYb>[X20@.?P4 mfAs'~ֵT)MTx; o Px^=`*hj*m:I24RNQIP*݃SO\cg}E欨䒿~:fD-D?HnUK):#Qމx Z*Ge{[뗿uL2O52@Ȅ[2/<Ct ת*GY|eG9u/}Tur.9r?ێ=u~{ߺ^׽u~a)y@=71 ~ u AQ5s=I^X|tcZF[M0j4Kk17k:qK2ԧgp,GMX$11ᑁQăYY#s|TMSQƤjiA O+܃fzskiıDRTQ@ =_ϗZ$UԂ8du:P #zSJub+NUJ7&Td($J2{ WZ]7 rˢ%e*"3pRyHC=M4Еv,<8VVA MOVy/޺^>UQ4!lJʂ)dSѹ pCǻFzKG>4PUt[LS$(MD4:u: ЖSu SfGG=Ԃ8`cPj62Џ$&Zk)$ܽ#"rM~v?^Jp_ԲڒFGwhIN {z:cٿE6;FmE$V:<ݔǭ^Gn@@^~׺u{{^  ~A׺ 4 ^(U!U!vbߪN u'ߺ^D $$ I=t=kWi$Op5 ͯ}7sL/˪-kdtP̊^" 4nhv?vkJSMRD-eU'R(yy7zߘ.RV5=U,KL@*!ʀ g'l=ԭH:4=<"J$lC+)`~{W럿u{{^麹Y(}>$2h@ [\ӏZ$ î !~$? $PS)[Y[ANz!E$ZǨBcZuTO~'?g^~]:V M;J,񮣫ȬT:}kSN=hzug9URRI4L&$TA)|Ov NUONr#YX4TgcD(ֳٔou cT>VqWEْE)!tc=C8ߍT uj3Ө{V G1 4R7zbDž7'MP<{>1ӟRWPƪXCE:Ljcڐ<{GoZIQN`ODUnQ"#i0UkUHֳN4tu$p~ν&*͢V K'WCb@JY)I )U,Is%bދ߅Wׯ=>V' 7U HS<+G'4M <( bK {:ȯ\UՑ2*2u')QP}[PΎj Wu6cjiRzfP€kj`-oƣϺ{^׺̡YG [J+G(f2J1CHJ7֞?zK_rF*@zu fWVN,H(e~{ֻuu W%QltQK^pp9?E*pqgV8u6)媁\"C*5(9D/ Ў0Hz^׽uJI'AOS"SZEd7«\"O.fSH;-,Gʇ +//z@zO5axK j1ʭk?{Z$~m~7b2TN4$yuj3 I5 4!e`tMd~E+ցdȪ$D "3][IчЏ~ _.,#*4 #VJ? S)hk}0_݉:sר+cٿE{ߺ^)e R3!or/~qfTVw`Y $׺#-T2D?ֿgˮYF W'QIC)DTOL #dTBY_Ďʑ+p96*~CytJE#ZǡVx`6 8ǩWLI#F*XWS7u ϽCZu*)}]4t0zSH:E/oqӀ>%i$eO]o$)HHb"2. "lTX@Gu2Ρ!yn,G\LlW=^R"@̔kѪ PxUGPk.*tO^>tD]KBi֪v74:X.+^=9{V׺uG7:!j'+QG3޺\Y]Qr< {jP.]TYB&N5uZΨ/gVNL~g@IJċ^=Tc@24Ui$ I.2x$EUXGӕjPpn%ZZ޶>8SOR!(.GSfwvYz$=x:]{{^׺gym\q"Z!H, @ըۃϻ x1ʢ|QydzH SrM,B^5g'Uqzʨ(8T}}ebзzoxǯ SI⍠d|s2ȫTPH#3S^II$$=QDXLV8+L5FDk4F ነAEE1/u{{^׺u{40TE. ׺iZ׉]0=yCK3. A͋Ңz3éJɬѤtUvSo 2`lX=o']G"&?_.u_ޣuЩ&TbB1zfcF,5pިz}z]{{^ӈLj#Y%,RKQ?~xӮqJFIIRAr=cq'u{`*j8DNj+  =hd׮W+#Icx^j੆SǓAO׮e5kSԬD5Fa 0[~ok+Ljgt}qX RaC, 2<Exukr[GR$K:I:?>ZTq5,Ti,dXH^t&\ u**ОI&O*#q'Gm>]^kek,hA,?o%Ka$FXq‡ uS|pRHik0(hxy}ՉVQJzڟO(m{bׇzM :x 4č#iVҳ% /ǻP}:ӂ:Ȫ2S [뗿uNAm}C` ){ZkĴ"]\NV:6Dž:`8A[]o{^׺u{\Sߺ^׽u~{ߺ^׽ub,@#07׾Áߺ^LʪYUQvf ?&{^'c1+>duu: L/f Kꋟ#ߴ.QA" A]ou*Odc#q-+`íN2G reWr >%UAdUAB=׺ߺM>s3HR+4IPzP}. {EyꚃǺunTUCLPJXyTU$jX\=kNHz tSJ!TKI #C,s z]o{^׺kf߲toX#P_v"ڃ*@ E^Ohhh~HxB{kONMI=l c׺?O~0$ҭENH357lT kf7OP/U-,QW &#oDdPÍa6?Q_z_cٿE@̌3ueі{ Ez]IDcc5-,lRӅ[*ǨlJp=$&ZHĴ }I>]l`ԂllozDP'JOW'd[({qZ^M$,QbYev5b{ֵHDW ~2S\u~{ߺ^׽t0zL`vE-@ǻSN]Ô 3 ?pD1CG0kEO^<ץs PBxa)cy #:#>׋ to{^Mdt@iD yO([ϗZ&BH*V]k*uSR9?ݓ]拁ϭPQT~Y*)ƴf7TM4qqD@T#SUI:A1hLF{[~ xI8 w԰O -,Qllm'ߎUIGNպoj]e-M<<}MD^=ֆjz)4ҵ QTFplГjv6#{wu}7WW hX։,@fm* tݬ.I&z{V׺5Wj*XjB;>'z,FWTb_05>o$VH#3k/"^-c˪,:6b@%s!H]n9)iZeqM b(~"?=Fk֘➽z*/OO8@D+#+LQa=ꞤWWTuO{Qqcp[ُ~ϭ]L׽u~{ߺLI_)9N+Lu&4{dqIQj;ޟ:^LʊJ,~OߺIi(eiZif/Nj,p6 tVS:V38DcMA$TS5kbX#DqdǯcٿE{ߺ^׽u~7z cty)c wBo54 z HJiҢOH\1 }yu^MWW5S,1R('Z'q" :jiFM1mʨG$v{*JAZ#A$*tӧ 3<ΜH dv˯ǩY'xڦ5eHɭ]U&`ÃPY~ʎV#Ҡ#H I@~~LJ^zQODzj `Q<{Э 0j7gC 5<~:5WP_ߘgRuq)Q 1Fkp9TW^<1Ǩb DP*9&zJ?)N+z}@ɼ+D-Tk,}~!֛4M0I$p-ku} E AkԡsWUIMdp,B,(V}\ӭytV,6 RG gק:ߺM J85Ho/cd_7_ݕkíNz<4y]Se6!iO4^jϮSG9ICȈ$ZKK]):ah?R}PA"JsM@X44}":=dN"c$ՕcBb5z-8^oP?ָP3Ӆ-8GdVR#~X{55S$1RHa}vR㩇R^8 1B"Y#"4],Q:NnyRkN=h`|MuazI#J)C-B,.O ӟv5ϧJ1M37Лb51@uriza:C%$fsJfcoQQ5uv{,+uIN\VQZi_3ƲׂvS@cMMz1TCMdU΃tU>-jX\4׺unMMZMa:G#d:NHǨMuӫDwR-* ="uoصc=,Z) ZB%+ :A}:˪֕ǟPcy^dH2fְ-Ʃ`u-F{-BzGKFHf,u U}hӧ?u}u׃^La<4-#Fi'Q*tߐ{oQW㮠lk D#PQL(I  ,Io#?*@tS1T$?s/뤞{Pz@!]K,ȳ1XTVS3-hj1ISֲqIÐhu=[U43F5"0?uk"ATT(=ڠg'q$W?cٿE{ߺ^׽u~{ߺX\xO^"B(P_ߺ\t_N}J׽u~{ߺ\DxjGVF?߆ zLE[MQ+i|T+0Ak kl.} NPqd,HPU#Π"G>[qsSu~>ߺL4_%Ek$rBEJ2cB$V[ D.>'HT>u*hac"i[M+&?@aM~έ@:_J*a:P4ѕh 2"֤ _~SO_WyfxtQPSPvx{^URMڕ=zrzONQ; *d],QP=ԟ!í_u @ GݗZnzLe1 D66ےO}1GRjT55JcO0"[[=ql=A&AR+OT+FU'HOhd˧B. ?u}@ƿg?DM ?R2]2DAf_#և toUoδxu}{ߺ^D $>\~׺= X ,e4r*=W ю:u5(' GD8_I:GǦ@<̱8JTI*xUz,M!hRy&bRdr 1z[IzMSVKOZH*_"3BoiO59{[ǙrxZSUKbe)Ux.G4SgOh%b+ EmRG-lX}AB8ëx]o{^׺)nA<{^c)|XUfZb#69kHY 3;(ey](Q,r}4`Rq8y%=Ѩ@޵7^TqE 4/}1\$cٿEDzhhBc` %cBA/zI֪<J}r |ej_{+LW=kW1Ӑ AA??[u~`I$82nW + \}/È=x h$BIPHD.̧ݪyTYՆ L5:E'6R=N^E(4?V'ZBySzfogr v2>vݴZF7oŏ|_־=F:HLmXiQCaíe jwbTmI%%Enx>]K}z8xci)_N=-6"W3Ǭ׺u?#ߺ]^׺u{{^Â>׺jƐiO늶II+fWS %P:c{YcMNMހ'N3Ua}fזpr?>8*ߗ<O~ֲx`u x S;4^iHV =~ηì׺ufY$dH-`EAfU(CuAx>E1zRi[qo^<P cVU k1:Y@M#s;@Y]Uу#AVR.#^o姝rdiOܬ8hq„i?Z#:\JPjVh`37 ޴<QgRKP0L#&9D FWֲJt[@?^~A/8~#^|=L54m4weJifԬ6)`c' %Ou  *U%HU6֚6tFZJ׽tR`5o$0G$ZeuG{t(#xužH]cQL`'C"SCm!W*?oJ?k:u{2ո%e#AYl{։=rIr"ܫ~)^ |S=Z:iK$*($,{$PC3*oDN^UdpLoE􋮢B!$E1։պ4^^()L~geF\IHzRC#¢M$-?T8`ǁϽ+[N>.6"u kY]KԀ4yv]kuMD"9>iQO I77H ӧ?8>m[zc,#x^x}sf$UcԆHH$uh9GuPI 4##rizu©f4?}*$IJ9*x]c>yޅ ᎼjYb.YlBGPӏ^z3$tF"t8Y_J*1u3Ñē׏ >B&*\O[~{ߺMLaܔWV 76j?3דJ$IU ^VDjoދ״=O-LcHxHR-< Ե?>rO^ցX_󢭔MPl:>zQgjxO5Itp?^8IXQ6-+2 g>f\TGF6޴׵.#JǕk)}-eHj3oA¹:~ަQTTH !`!n/oVPՁzq^׽ucٿE6e#KM4s4k.ƠvSC=iWϮtj$\RȪ?lK_} +BzW(Vo#GjIdR>R)ɠ=s pE1]QT.=䞼0'_{Hj␙񰑌a4ǏOhN^uTD咢 }JWC%D$s?o𿽃xu8+ ho,ZجDB~GIxuRXbXX/&_t4$ë {^׺uKUPMW;Qja|ba+se<} :ѨQj}k/ՅZIeTǦFZX=H[]_H]{_IGkT$R8pM?^>I|:>J)ͥK,W{PM4I¡bxdOFs}γ"T)t!#-TQ^ԵlR1/&QR{sbtu|Ӆo.権Uԫfd%O zz}z\BuzV/ ~9̋nXzDzqrON c{>PRsT{Щ꺼tKK,N!.Er(2:8 :zN UsDs$kWc1,_~9PkGkӗn{{^zZx<1%4I5UUF,TJm"`*zr@$fo*1g>F߈^S_>[+h@UV#tǭEZ Hަ:)R^i>@=jGSu*f% 1H-nXHRq@(:gS}~CI&@Quzml|!^hRet륞!O{ZXtIh+I6>>?O֜j?^먍&*ǒHC$Ld e2WKk{X#֚iy[T~ TUH{SNcٿE{ߺMR q{)D|j5/|??\@2V5s嵽^/UH꧕>]xSo{^׺u{{^뇣Y7/m>]:uoE߼]{ߺ^׽u~{ߺXz^/u[WV5Ͽuﳬ׺u{{^׺u{{^׺c׺f454v4EW\_4:Dji:n8PS:z]{{^~{[OcZ4|o~-ǽuQM,GUQJC]eL[=hEzV=2JY|nOt6OuSO?H?goz]@> 2z(yJHrs-:@~ Eg^=*G?m]^Gx>5yڿ?sbiLÉw=T~G46/&,zexhbZ}2q$^.4)NIuJo{^׺iEDR@ i._O+/dAy_8SθFӮ4pr |i,$Y^UAA:O.=hqxN,aV!AeU YO{^<:8cV@ujRbVI􏹖VmJi>iWO>պu{{^׺o$ LA{WiǩT1O$"O =>}o֦zإ1è.>|Mu4sVti֎:totdesktop-1.2.17/Telegram/Resources/art/emoji.webp000066400000000000000000042512721326245125100217370ustar00rootroot00000000000000RIFFRWEBPVP8LR/Bͨmȁs>)e6̺&-L$ы Q8+QŦcӊ3B DdFj$Id9\%lX`yXH$(pA$9R{  X;;v1 a]v6]T*tٵaIfI \(EӲ$欥iA^-. 9DlۦqRUeTRwk*XUU_~BzQU<RTǻ1gCs =88Ps,(( wגvك& 3X/I3f$ iWpA}wb«P 5Taգ#l$>UF0{u,|rX  e:P bQ0,@ x}{%5 =f9cx+,\P\qQA@PTd.\QJpn>ųEO)[d $)KQo;7|Wr2HHd XT`Cʊ!y|F~18d 1MdD" K"&]۲btmǎCZ9A\M>LZW]}xa:sg6]u<{A^` $HHH `P<ܽ]z2&LёQd{3-RD+n*{0 ]fY3v سQ֋-8㕐"ȉylEޝu[کAeLzⶊM+lh9q(d\MIqc }E0=\Ep?twnGUlŌm˿4_:RBDTus6k!.]9nSXAw_~_ڶe{y^?RKRI-mwa|3YQtt*PO#:g``PLdKny%;-Jz'ҢwMTDZN+ƻ4 cX`̜@۶y!X(`l~m۳]O|sr+眪j9ՕSWWUWWUwUNtʹתVժVZ9oo/{s~l~󼫪i nd€S2(aDcc;Cra 2IMh! ЄȖ -blDX` 6[MhlalDƌc݀-c#`02BnmYnp` Fic$`c0r v՚ӼZk󻎻%LN\:R(Pw-NK):8m:I}]mk4km;~r- J-uo3 өL] J ?Z ض={E^Y-TRIB-&;{G$m2'gvIA$w8<߳ߍ\]<^SxIFHCթQGo;%RDP]"HIDJ%(rzo^0~hv〙dl3o^"'E㲙? 8.6 b=Xͤ(q`11b"">^&7Mr N L6?,:@n$Lq;'ƍ1,\!0{=e׌16"(`daCm6'}?)I#taH[L6n"BGI3I !@HO&3Z}m;>?Ɯs^]׃h uRn -[j65xKB !99v[6c_ __տ=DHF{dgώ;9;d!H!PJ~hF<4Qhl2KF{.VI,YYcYfaxIff35+֖dX "bԭ{o_:SsLu w7 R03ـa>aPʡ¨0-BKfNe033s…m!I#2Y6m۶m>m˶=.W""mۦms1ql۶m'<ڶm۶Ž^sͱZ -Ejq*ҖRCKu]ah&I{ M[9 ۖ҈/&R?|3gc SWuWfw&dsH6n58$MN a ÐflflιruNy |}:ǃv*Kq8֨((k0D.EK( <lQB[iBc8rP(:zJ yEس6B)Lz\x$̮w0x@ ۖf qs.=8 @+K- \ tl[Y&ڠہ [6)3;3l]J*`aީbR,lN?>lTmZ!1dN qw0Lv$(#>gӂ*^?cyjwyUrm~^㙶^5v:۬[5v;뼚Ym۶y[۶Cp[ eF趍ž/E_m۽GdǶmծh*uI 733; v܁>Dā(iB ۶t~SM;mߵGzllgm$mH"9mvIn(w 9gPeŗ1i0yltooH*62彖L'lP$?衞&\vKg0~W!I>xy/NL=[?NFJ+F$g`S5\>qaAMɝzg}.8]ՋnAĦy#ix7 ΄̡ƾQ]W?&$es$);wpE|\xFTPFQfO-5tR[tlL?sy5!yoO5$J%2VO'd5lòvlr~]~>k,~(?e_lkfjv=,۴ch>1WY&{fGd^R[nMkD p'Oxj~,Prl?{?bbS,G~~UK| 5mon '!2m@av {6ت 7L-I=z۬9$;Sܖk *ohFnkhY| im "TA6ISDؓiZ5b;-:òG̺L8se#E5fj?)%(ihX(*̼M/}x$M}Aꑙx~ 8@>W}Ɯt}꛻'ЙFʦ3Xo+g/LOlSIn=m0M4d5 (`n8)^8}b̘溟ٻT9Ӈeñ|U~4_ X,'T413 z(m 7>=*z{fm'.M(86xmȱѴW+%R]XQ֍];4~2weٝOq?c~EV/^{}dmQ/UHh^׷N (0xƋqb޻t\!K9 aA麍!JrM;5*nLI;pG`Ƴ=f~yp=VҿoNΟuEs+ywn~GhUb'=A3gMBɽrgh='{k̎碠@ מV^W~rX!S=i8~o~kv U*AA^rޫ+c><ʚ1F6Rd$q\E60c-n($FST[$4Тw͹\ z4NI/ܤwͶSÃi\ Uoȍ3ђ0Y`!^lq-1H{߿MG4;5nu,7W/^gc%|+o,t:޿al¶zCnAxC\Ԙ8U6&@ZNd`#5 |xBŧ2jFp\S=;8h[t]ӆ/@AB#p^zz.7h Fc'-~Ծ5Mp$`|+L^ۢwklJχfkEU7gϠB۰bJz3Lݢ]GZ=zrKho8v۷4vNԛV.lJ?\ݞn1@ q#$EbռFG/:5%*\]}ql&䱍.HLEp{\*z.k#mj ;N0 Re䟱clvkuh|vJ>#cCiqMy&ims*)\T07cВt:TW\dxCkݷ aR߯_t"үrֺU\D+ jN2#Ś1`N* %V=R dg^_5,8Tf̰p7AX 32- MfgvwUXdLXinOs^[7h<>G&Iϛg# t9~VQ$<)@z+u9vND=_z~:xtx8C K{ܞm;vQi=^]Z=@O&zV  R>[Yћw) Tr9!ے_69~QWEݑo oe\~^`u+'^8Q-:gQ k½Oq,Tn?u{ZvG7(n?m .!H'HDh|@*rw䧟,=7_rRi'B+Q" єt 1 cuĿ˹]o[l *\s>/'+f.uv?]i]âCCi0 5_ MplPX,/]oFbt9pVWx ?.궮" "9rjeKmΚI.5;3%"t?)ǢhŶG ݻSSLZ8uKK{\v:OFb K<+NDOXaCYk[W92snSj\3l6̱T\]suMM}DO92'7AP=3F(4D(!OdMRX 4OSB^Lr4,;-tP_N_ @sdo HGHxx$$:P^Zd!޼׮aN! p2eZ64 }{HJ%k[2Ǩ]b/[=k=# 00wn>o Lؖ>jB7{/Ye͎N J'c6`.ns*PlJuSgKiJ nh07zAP;1}|yt߮i^eH )/}7U{ؚ[‰t9zn"te+gحgj=)y^Uk%Tl.e"t =((OBeb6 cr|Vkf+eJi&Ȓ Ƞ$,ѰUea5ͬcJv ?@BE(dFDD%cF[ |pDc:`FUU,.ikpFy CQ 9(sb LII`i559BHjIFq҉|ʓh sQmmZ3E&QyYd?aQY _]4` 4qfU!%ҝ {ّFpvMnIؓDCɈm_,,wN( M߰[[j5Jvgǵ֣#+^fx2b ˝ob} _MA.=0+rw$AEiv~vвYZ"#"6M^l Ɏ0`. H pZSW{)Bo-K¦=v%GmsJseBrY8!fu6) =ń`XSH"0.2\x{ĭM{SiS l-S1S)yk5h3ٰⰘi?kFPi-`S߂m)C|fGqk250FVh#>s ezo-GaܯI@@2FBֹV9(=E\n_/m#6 DdGv}r{)ChIk+v?աvxm$Yr(L)X{Lѯ޺STHw{j:It>i>= xG${"g[{ɖ3u\}S+?y;e߁aR`%¢}wGymYþա7v.K1"I@="1 1~FR䪨/ _gbCyw@@"9s;2B7=BSrAϯ}u9;~t^ y{~භ/3f {hW- [,UJC:NpjՆ%fF ֪!0rkX m#O61B` DD4SH"O$τs#m+eʘ&ϼcx 0myU4q )LHuDb`HT]Su԰2K{G?-fyC];$J|d Z<ǥX:~ZY}>g#sCg& ;$‰J O1y>bD8cW|~`t| K^I A!}H* "6' |>r\ &Kgy sfCXh6UO#'&$APIe]*ļ%.~~w/Vޙ0Ye0[T+Kl V:?>xR;=s11r?#?eF/ NnBeOq+T4I~P^O.9WJ pLb]Nd~S ':?yOJqB׹bi!F.8\6.8TXvqS@~v#ϰ}V ~OƛmW6Qlw}Nb=;>0t$=W[7Q*TQ>m.܅1΀BPO V0QXKq}_& ҀD[9jD fUKBn[ꆺ *m-Du/tB![V/4RreRl߻rd6C>EE+YNb@D8,0JY0<X͂R B+C- F=U]^2 )PbR,m)mL!Z;][3<%%{.gD)"#nÁOt!Gg~o?*|>_gTxYpFGSc ;ɉdIMezPO9G֐L{(4]8/6J)(URPOQqYAis{"+`tBm՘PBR-Y--/x[I4률Z"DI KRǚ(QJXb XO >cxZKj,?Y^(U]%k+\惒BB I KR}P#V93EC▥58ޒ =}^,ѰTݗ9}탢XE*_E:̡*1|7"-Θcńyz+p雵>ל(>; 6x7 $%+>HAN>Y2}be쬷/^y JOMsų"$J >0jzIVmvy%R2Db E DC6!R!j+>(IX*gwPMD%2ǫEcJyr>y)x](Z͈ ny;I57 s21kLIk2R4WjRGFs:B@ < D=N ^^(<f ɯhY`bq(0ibe! _˦dU33 Z%?Y.Z^q%U$D%I1m灒b\j5gxL|s# ~Tdw\&<Tp,0,H@E[O Bih;Pݼ 5xaADdĕd^gXKB% Igi-0DZQB/,4 -Bf:ͲO̲|)6I_i"1sƄV$-$26QP rJHU8JyEh DW9Dۆ|$CFs͈ D#΄272ʴrH?aG!ҷ_ w:d7:JNm7HbuaO;xS@#Bǀx";#nu31#d֘75]OF3r(NM,ay K'3s8EHܫr 9;GtI3HK— ]%6b7;wE^j9眠tLX_A"[E@%fzDQX!)K|)!XE"J*A$S%R$Ea5qDڱ!J%-5S 8Uc_J!I3ŲhBL,N%Z-Q"!>h٢# NZeXP4n{tLMNLkV@-1t>Dgq -HP@*+4^(/9IU~*o`u|CddA+HHz4>jIμo}NؑyMA|JU" P1D W uQ20i0+Ap}̢6n^ )K YR1H W E9?c3'^sf=+/έ^K:7I> iQ`ì9W13~P(^ov?L^cs  Y_C4nP}<~t@ B狙#Wr $p"DG^!4%>!N SѦsF0p%ѹv_ "è 1XА\R[@/S{n*J\J%l4RSz]rI9~OiЂQKms+#$)Q9 x rW Vdtv;\vOM5 6F6d/5rv_/f4صBרQIl_QMٍɑ9J hGlkmҺw[OC1^F5a3ljm'f}ӡϷ&-~*LvᦺTzUQ[;x@dxog6tY -=Ư 耯J^v:ڒ1YLW{XjdI1{+|[6.g-inW4  Y<'!jz:Iktc~H D"1A(HD%𔕈'KZ$6-U f-&$7i!nRn!h0Z%|is8 ׭a@s =j(hԺO#:A-S-wx*wՂNxoxAM3yB'dpYnz'管٨8qx yMxTpobv^71 T),`¿2<@ ( 3RN,w^u)i428IC0?[P{>:i42Y(@^;@CjI-e 'Q$?[i{PXW }ެi0d@L&G:^8g jՉNThݣ%i2@ULJDYq&-2IzAczl2`M!b+ qT))VSU>ʱZ’ɂH ^9c k25iqV#->J>GM 6qv1: })^ ޘ2g.>;2{}z)v:]߁8)A2|(NJ%ڗ|R*/v+' ˊ}!SB8tK'0,_I zw x#/%T2d 0Dg-!) e8LАm8;ǖ!XO9ZV "jPa 90 "vVN-`-r4/`:EuIyP., Y({w'FlD@z+P IYW۱[e;;dSZ35^߭@ݯ*ѹ+,_UJw~IG>Erj Y#HQ7ŏ3$~|^۾$'rԄ5lV Bؾ}" qsJIv~މ˧ֲ5Ů# W? N<~1¬zo?~u#}Q9Y :y:Xc3eO^'¬e(LӔ{:a1 q2e5rX_ox껄diWo֕zl~+e>fVrpgZ{#6FjJ$c&AE?%fv]xYKZyaMVV $L+*_[G^͇LnNj4#1ӊLňcV;>yG+$fLqo ʚhI|=|T)(DʈτڢbVD#(3s h9sEWەRd GR$X5-Ab`MAַ d&l^ rF1 {8gIOG"ɝKYE["BVQdV2 LoWhnۋ"> PkLj k [0gogf6YIHks^-pF`wd 5ೱDܺмKNhgv|a<)ˇUؠ1u[ʣ3tI/`6Vgzrp IvI/g(($\.W$}fЫQFPD::l/) IǨ$.Vu%l畍蒜z߹ ؠQYa[Nu$al#bFOjߴGيCԒˎQRJ[ZlQjmVXi>eGr})mDBJ'tJ(~#hB#j8l _6l1y( t@R%[Bh-[h JHo:cCbHo%lo>r@Ο4:sʇve3 e{cGt\Jجn13 +fŊ+CX=uA57WmY?Ҥ}fY@9|KeY*?>disyր#4I4ﷴJ-ʞc%mγCц[ezYK!9klMc/v}q)Z ƤgXo^E.|͎o>8fYqs&7"U^.i-։ !fK<9pˇ~Z](&t7-P6o/9fp^HTSo;򨉥5Be$٣It›R+%o-;vΩ o}ol PR8)gNke5N*wdJ&(SxSf6qfUKA$ 2>_V#{f,4B2%gԊyA]`YU@_ZV岢`2IMko3;gN1%i ,\}6*c>mlbau˫ш*vzG1޹kHoǼ&U#F=d|z/g9ViϊQH42raϧ 'b=\V,Y^XAo4 媥l3xRe`^|n)mlc2Edɟc \~~vۺDNs}XVc!Dqb@k\?* AG&FL(D4# p*ɒ9F+eȷDY$͖,#YjZp~#!<,?ox%Bf tEy3)x7~ٸek~VSoQFZ/ox%" k@;/ǮH8 PZ䋞x5ɯlv]f4%=aSX~,On-11XB02vKQgIC ݽtLq+C!0ڢU;ܹ0͍jmdLDM,$B DjI> nԐ|^}*nnAZ!p.He5yK/*l|jҒmxIR':g8JvjC%ϡb~u*#gY>+xDדŨ՛C0$nJJj+Ww%' K%z5v;KXRK{ԢVңuTT,Ņ)sZM^ap+3BŤS.? =j܌ uTIPGN dXNtHQ^"[*c0:_z *v{e壒]"4!䙫%Ւ?}^un:3^F4~ biF><pߒWW3{0FO5kʰ4DQirP<V) ZAK|ɳ%&-@R *wxIFkkfI[])Zc֗B*2#]T=е -WSgkJύ@4N6#E!R_~ B 'R':Rk%hI<ɳ mr%#PJWM=_$wUcbJ%mK陵[M3'^6xu.m!*"`}J|]h#XrE>nf1!]C!U.3r *۶-ƒYΈݥly^MqM''+}0<a ’Ail|6o+_@bleVSM-.o Tx>gZ=]9I'SZq/6S<1̥\7<*! && @zK{fJ[[zܲX͊1t2"`A~u9di' ,`61lJzc~lzO>K8 SwIVz]>4"'>7ro~Ieӓ뒸:[-dhl)yÇk3s|KC{3}qgt0;#Ikok<ɘឪaْ F""!ٵ\"-CudgwS|RuQǫTWma,a}NjI_)#A+.Yj8$m#4*~jȷUR>(oTJR+qK͢_cҔ"~_D/zUuun31&NF^hxk4cOfN*%^lmC2 &B] "`1JX ~M_"G^OYrQh+q GIW5H!z4>rSGقD[QGsr/9jByS=EVk^ xW ?`|ȊONLH iKQ^U[ZY5Гf#ޙl@v7K4pNkRY(П.~Zr=wy'%Q nTю^x׿O,;ʖ4Wji{->w̥8Β90W[vZ.~MC.nmFĿt6=v6GO}ҵ=O[rҵ'Oת.l^{$7;,)D8S3RDzM.=*r=Ol}O^WM' rZ'b[a~tڲud$k%5Vi5"M]$îcNԒ)\3qzgܷwU?Lj%b{\hYiYkx5v4>rG_9&[2^>KqΒEDun]$Ҧnț`"%.F.hxVC]ї,57Dnl=~,:mMſ։ïlԣyg D!DŽ^;r*AP c57jRUo4Ng-=mՏɒ:YSNӅo،&, :$cVی2rO6LS~\,dRgyG/m?\A$ b7T<|84N͚jsĮg~^n܅46ڽ Qa^̽rghf5K]aur |*m UiC]䤅ʌPH> (ee[pZ}RZ_#@h<b:\1gM \P@X"3NuN:dӵT׬Y:9j$#dPHH8IJyjf}nu\~`K*[ 齪tRf $ߟe)]~x ᬱF_z76i,mq' 'GgRllп_?PZV\I"HBw"0q'||W3zMivp/FR+Gє(B$8QO;Ț1UUO\ѢvgM(2$2+NUwGN<)UHٷ3ǮWP֠" 捲3̚mh# ɦ,4]v]0PCGKCWiGeQWp3n@A@!g8ܥdiUHpձ6Qt*H8cJN쫅ww~P( 2O2$g=d򁭦PcNDqTR6i17ؑCwcku&"|Zϯ-ɑ$_O&c^oW鰟# 3%%䀤~։/\a!߽Um[0^;&[ߵ1\Od!M~"|9\_SEL[C;#w17т yt3ιKonx{f.F=9#.jc4V*K_9V:otw=m#u4Krl<Mp;W)]_C2p[\,l'9'iHUQaGw"hEA ж =NA7B78et ܩoDYE00N9&v2nulOr_tEnDbb^vEVVl%[F_ظӦ,\T0.fc #+"#l`W:2ixtrf&+|{ͶLp$_8sS8 H0xg#_::'\ XwD\)zin~6 jDC56v :%?Ӧ #sJNwͬ,]$ g18_~5nQ~cF;?]+pv-yzil`Bv!€-I0!%aXN e"̑?!n zD⚄Ƴ LsP8sH lr4)ꅉ,UVCZG|G3L\M+_B|8PY7(3_^HB(Kg ; EjEB&ON\IKμ3Z u߈VdS&=G*܂qY ӧ0B@CkcMԗNru/f+}S'lbnLے/ZAn:]l[X%Za+ ~#;-*w1Ն H0jV4dQh D!4j#5?$mwxVgܢΡ3M?kYrƮcFn:ޭMS=;?Ӱ d[} m_iFDrL'7垹oSb"5:vISZpNMMaul[Z+%rl`S#5_ڨGw,4:?tGK짶VJsGeEΆUXg@!(¦q,9O!U;UQk!Xh>%YFs/G229Z ^N(SS!shHTҐvP\nD 5H gn8=,xWt',cKdZ//ӸslWN znl;GZٴHxw}F;O땣']IK%͹g|m}M_Ik4kud[ZՔy+k%eTD\㪥h AǬl^۽SDƒ(;/OpЌ俳I%F5ĔA^i)(T 7|V#H7Zۑh/N瓳_yٹuPQWEݞWgis ag~~3O(4%Z!]׵c`A =h:P( "<"(h@;uXYjz$+YDD5BA1"IzD:r&; 5,ʛy#Tw@aT2'kRRR-pCq 8&RO۵1󅥅 < \]TV򾖱Li& ō h}KrcFh ЂmcD-RvBRdG(!숦jB݋ hDŽhvޮlLQH1ᦃO'X͹U"AWe7D.F K(4+9Ni{~[pf]!b19QPhA6=͑9z#_ģ|_daI G~?d"-juHIp>& ' f%&Oy(%]'6D'HdШMɣ$JɂsD©%X[=J&gb*툤x)(<8%㲚BUlNJ|}-1^ɟOʴ`3+"H$Y_1xnשׁQAXbyý_ oȒ`n-K0ֲIY3GEC{̦f4sgK[tGN2 ǤצdIAo+jRIg/pvmU묚(p6xټ>;]Z#>g{ ?NǍprDRZ"a[JH-q`&x5܋ӲCuu411$ h @ -/O]GeOŦx>׽KWl=t_#-2ӣqԍ#s :)ZH$W?@}d=Χ\+1ᛄ7{r&GZA>-S ABn'vz,xpTC=)z(k=ѺrJ]x|Ce,25a"M䆧!9e[WBmY,{zFu/|oAFD " w1|%i03R[vUY4K04.Fq_!K4) `\ .ʽ*͈y6d =vj-&$M O!®"V%1nP Y: @ 8$qdR2"'Di-KVIQ N  ^נ8DubR?kyV%TE=,9ʬ*Z'm_,抖5 mW7'F1U"/,?]EfAoqD)҂i8_cꞛ`?gK5ot־% ;E/Ƙ >3 ;pJGBS35:&Q[ϭ/4O=w3 Vz($8e^Է"=sJREB  a@& @D$pҳF7tL)9ؼv ?Qg-ƅEt+SMA&ϡ9gcD찱9%/. O&~I6k|G(v %~o[ph3:`A`KN_â``U?Nz߹)C"xZO,J<""n^$y]CB _Hzl_s3Tc5ń9tYOͬ`B,H @P ijC$|rqWKԣs{ nyz -j]8SM+ԍ Ñ_w6硏^EV|ed `V8;(ޑ;g^lu5:M`'y!] 7<`؀X'K֦4bM;׮v_PX7k>WP,_V,\X~}h فG{I!nQ8A+6)Y8lf!*s^J:q9qBo-^bbw!58?|դ05qN~Z2fp^͎#w&a0B<9ԒDYwZ۬HqGQ@w*zkXd> "'\AD2`":4|_ SJ$ D_J!=q@9*,GH(ԣN4cNX#{І=LvzhŠql_q}Sч}+\RzBp[۰5@(o/($,xࣺEϥ日S^)\#F٥yOF=;[҄1_e[0Y$%$tMc @iR0Xu,;u^FR H c6 CAp*oP5oo&kH*Jb:t1(dx\K%Kվõ {elDVSdf\ h'D NZgd:mFzmdHa^AiUH38S`w]d7r<Xlm%a\KeM;pFmGk\\—^" 9L\NCiǯ! D/b/x*G~wtI`P6NJci,K&/*)rcד?}7d@zPAk0!L'UD.ݣq3_?vGN=IT5?Nm3+iӧ~/zN[wܚgS6n0jmWF<ԇRI24| ڬ'NaoZF_zFQ^ =r*"r`o:O1}9oxc y!hd|RH%v͜/T31\K#:Ә(whRy~A#1i㳱ءQBp_M'7YQJB!znՆXyj%_/++whۊŠc 08D% 幣,g.z{}F'i A'7:Z‹ăY`94#DyAZچ#P7*,l zZaY Ae [抡6(4<C7>թ\{u{ҩ33b 1=hqZПK K!FŦ,~~B* 5'uPc&bbRʚ*"YȈ@PO;'kȒ ܘ)u KvLYG̤-׫,%{s/^}ReednSYW!¤F1ߨEr߰שL&@ e F~U_va%(#NΊ!u +^ϵncs=[mf $RBe'~9-{z)SIQ# 6SuJ肗 Sz/զi=iZ1fu4KC7pot^(w_"KD%))Xy:qwJ{߀=^D:q֖?^%U>F­Gzrm'6hɒ^+w^A:Q3K̂NKF٥EgCٞVӝ~6rkH K%@q)װYX2B"RT⚄BRslr#xљĸզٜ0X2U]kH:4Dj @<%!<P3r-G=c"&<%'Rv5Ae5LP@ !JoTͧ|sк#1OxdNz =Hbp11D4/ cr _0(..:-A㞋 B-p+7+u8ڜ =3&$BAam³tu6MA,K4$VӣY~ⷥ6Ln~$ d]˘aO>)EyȺlH5A K9+m-fӬ3n(=PfU%O(!%R@Q;CV"{#~c0aA#e4#5x̳߫35b[x7lY{EDRFM>:3Ym?<@zvik ~? D_Zfx? BJ?^<4`qVMp >?DCҵQR"{aNiNJސ@wgΊ4po DIBڋ o __%-w,8'2#Z‘!5ځX*GJ K <%î PX   (VCK~KL'=sa1NHb~A/ѐZZf;dQ/b{F'2ENj +`GNmb\g wz;zz6gg ƧbѤ\6u4W-/|M?p#u>DFv#%o@=R[  Yٽ?tP 5MBe_0Lck#H1=Ɇ9B% \v.e^0qY192ɒJ>ƒKȝl֕9 ]yG\eG VjlEBAl&%\. h. fkS0Cjxg8Q&5ETx=e\wR.vo?sdixIC(pQI| c¸6Cos@npefE:n9,q||Q`G{'5} w1)7 sZDCkIC 0E_j$^-Xj 'a%)˝3ux랳,x.8 ! $Fb.I& )*aIdYjXD|ђϧ{/Ӳ1 I,yմ`4M:PiED'X| SNd' ZB "G4YfƨzXK2,72[πp'ʫs8- 9 m+D%'(Kټ@!{nP !|/Ř~ei9Ws>Yg/p7{\h?y-ak5 |5k4ZZr"'HM"bZ@@b$;3AdP߉Ixll`m}h13GaŞfQKkP[ߘ5_c_&Sd %V`yDEHԐ߉f B$`8T(4cHTo0EѸ,h!Д!Xa%|cDgo^DB`5/6Ь:DD"~22)&ʙ>ދtL4h) 2e媢UqjURSVH;KAàVDŽDHZe*STxs,(T QqHxF^& 9""8U iXDFqS"'Fg]~ѿ8Q/X; eƀ#} WIrO;> B yY`^K$:M)~\.gc}):҃.yKCP»^Uaxy}MgdKt,xY;b%1PN8flx'3*ȶ/zXP։l40= ¶n+*32S 1Mx$+hKc6! ђ,x0|KJ Ȋ7xo ^!$wY'0`֮љĐ7t?`r!Sy; RɷSfnؽ T[TN|keFyYR2#vC6L hr|޸3o.$eE ES#A""uHMg O'fV- A*tP-o[%5huJ6["w^Òa#$d[i&uBP2I?|qBm ͂!dB!5BbS"P$|T5%p')&z?vU_0T(2#B-&(;,3yrdG:jA [1P,x H4/Iŧ)$`zw%g5]n2A[cjCѸ^g&8Ԃi~_xK*NR*WQ/B} D/b Fl.5+N*_/XyZGWJ&& N12=A'2gIYHOJd&3̉yrg_WP.:q]9c UMQaMYuDP@G#),x R %E-$|ȉ4׌~$9KG)Gt@FHV'ewjAz"[ ѿ>Rdo(} U>rYY} h{]zGL R$ɌG#1O)A*+v)B ɪUjiAc=&NJ+x16vLjM`њ{DJC^pqubA~:[Ui+F,QE&Q]SPP5BT$4}ΩܥVcTn'-e$ # ^`ˉ`+9Մ'ԓ+:9|UU$0wT SycḠt\J5nj9iX!bzف!^Q_Ei$?Q^c^eXp7D)9kׄ%{)ɽbgrm5)'o+ȻXjE7] E d]eh~E7M/kI&Vq#siz!!a VpIaKRMJQ4n bdPy DDdΨ+>|#ڜqGl0~$X@: LF4 gN\#/J$g<׀EGD[㖹R7>-sbA@ydU콕5㛨c,uUKK $L{]Yg>&WYgbXּl\|Ԋ\GYJ" ŀpO|xT`أAs$` 2 H 0 xøWrڤ8}{5CMIF Y qX'ł%^U"AoM;A5[{Q5udS>!2&[ܭ([9rÚë|  |A3?][ϠO`I_G%YBneUɊ1 Q"W  $Hɕ匷~|O5ɏ+E|BHhv{I9mQ?+ Վ-fB'䁎0EFoYm<'1ڈkso1)TEIs zJ^@̜́nG !3'H\Yw$1%>8[P{Xk]!~dA1xKڸn%Nl )! ;?~z[k(晴>QXBcPI+Nffq~EZM_IʗU$)PSr9]L-O9GTͫFgԮE>$fzy{ŖJ:CavFSOOCr֑r{ح,qb WDtZzϻJJy V!G-nmF  +EAN`XYUi-Guel YFfH)!^ GV'W7pڵ_E,P!Y=/ꊆU f(4 L7FMǫxBۯ4EfNNv y}CC%):swa!oI-R?Q"4Zaȩ:Sj-I\;0籄,hRBTBrFp{Q BQH5 =>G@3OF#* qֆjghкuXtgZn&uf y\w[0n= M`Xl0^9mKN($fQK.MѠ)HaKԠ4}jb{a`[.clHq0jkq(;&L3,9.shIY"B@@fl~Ob񈞔a0\2Бϡׇ+/,ymkDĉhH;E@J#qD ։* $ֱntf0US;zIhڒG>GG!9Cqy}ZL]QEVPlH1-xNqՄQZls# F/IAQaJE?:)O` (> C WpϬBŭ9" 2DTc4/tB0Y5xK<;&}+t"og>ʄ)rI}t\M, _~azm5aHb9WFDfmJ꜠(v)8zUb| ͰcSP@* bcAs03 vsFng`W S`Fx ͏+P놽@BDDzqڤW\0v@&k`H64KsLwu촧֘- fQQn/=4f@fhNʌ>d"iC£w1͕^o1ߐcخW5Cš#6$f@SUfv*qEd6GAmV)e/UfGa?BljTVH"7.b7il/׿ԗd.+g4.r@Pyfa *@}Q Q:LٔmR%F=1qg$L*iDSd !>tQf詇ĥԧ"DVO2Du7z8y5%ɭ"w<>5ʜֈR1HDY' D:$չ 9Td ^ fQY͝#tFz x!_iG^ sǧ#Cg)c\V߫}՗'KsTdaCsq([0gw'D͉<Ψ6|LT R;N.8Ы?Ӊ D=ܵiUoP%Ъ{o"tiGm6o[ZIrM/T6ijڷuGElTs.= 2b(i81kEa:!SdbVH _XU||7bJދysDoP|ьxwV5?Fӊi{ItS%7eԻc뾬?"ЁƐ wx_1g^ـ_CC7La)XDM|u}"˸q6tK{ホKS,yW1(^Eތa I"h{gf'|+XU?+X; rwSÆksUoh}kwx͔Ŏ3)0$*:Ij 3=v&o*w쯍+=7n^Xxԣj)=@$QnWDbGcI"qSE10 If=5%_vzꂓ0?Pwmc䇛(a@R"&#u4qgJa>bGAd#SҜb 5k/=7%'x+<{ F(u{r 6[E}1\v/2IW0 4BSk ?ruZ/bcTnƎv]Z,wUH^Hd3$,Bl̻*R@nWM3ޚDuJyylW--Y,Xq lU^m9ppw~ıD7!K\_Н]қ%EsQ8Z;AH3ګ 3yQ}?q6y9I#GjΈz iPE=; c\ZRM԰Z{eMM%S 5"YnдI tkB #jw<fAvlT,/EUXLĚfKi0]GuakJ&n;i.}w\nzʕZeJi%nkƺEtɷrqybzT:#}vuFw-FZZw,Hbo P>nҡ-B hXC)6ܵN4&w (j6bW 0WPţ}3o6F^p:o Z/IJ&:![@'_i0kt+W}L;z*m".X%QF}j1wZ7 :GW{!4Jϼ̼oO}>Mr?S?+"ee@N<3pTI[ۍ{\eNq{u_tEOЇ“_NhhDIU<%7ǣ/ϰ]xmD $58Y._wІ4+l/.Uh/CCHWز|_-bRӊmWg*Zut̓>`̀|QEő6Px5ޜf] Kac؏0OW_ٰ {K%j4qm/适gZ;C7_`. N,&Xbv9|oMFpq@Rr.L~c>)2(](o?P1"n8@fv~Og*2Hy H+}B@o1YH9zKRvS[ X I̱HRq_dخgIb74n|=o\oU>Ix"֙8EFCSي{Uw[ k$A3`&4sVotȩr@ΖH(E+$o<> eF_~1sYK[a474dlvDFui2-zxnmkl`n\8?Ïѿ}qy$0  ,-ⱏ1Gs$C?[ BtDiGm>C~ X;8-]} "hE{ꅵ-xm} aS7a`՚ *|9rKa#q*s(Ʀe|wVffR*?G=F?Q]Yb B߿$tF[ᆴ&?q kc QS_5މD/>T[:.|gL~v?GӍ6竺n,P '"LAzgajㅉKI SŶv?}6rX1O뺥1tV7,DWH'7܏yɨnp"JNv% D ^ `wMy#+rnh)*jc{ ddk{(aĞr`/6unOj6!>9OJpy+ώHvğ:ƥŊ.12Ktzy) WiA)+AC>&%_#1Oѡ/7.-n=A4u+aߠAG>R7r[g)p;6RGes_5/n-qلhvH ,9c[8mԝ^(^.7mB7 5?ܹm~}Y7֕%2 ~ûl\iJ;xg~+dgqWX/G&~ E#qgc>v~O iTq{)٭A 3¶EC<=>ߝ|ѿJs_\/n6 =v"2'6(anO|i>ṙLC?ygytЕmO:"@K$19HdXݢ+'L`~Q|oO_HO$zOMӍ݅馵Q8"p1yֱ.3u}R[ՖO7{%&M|MJ.<0azh2s< m>mg[rb) O~c?Bq*m1m}(._$,`H*d\|1}g6ɵ~|g_X)jzt Rmvx"1FN\yn*kVt]- ]2=WߊK [D5AWf8{(-XD'kz|~&sb(1 z \3Fx:$cRvE!A'Ĉ8g& k|5b{f[|ܟnn߸ c ˛̘)"yB %Ԓq=yF?y8D#&Dv&G,hO`?Eo) n[MvaTAΜ,=)ὃOX8IkWmǩXN|]^rs~SFeꞴZjSb TqȏKͽO bؖ-?<|l﷎7ǒf>> R}Q4*IT֖D\O#˗>|#f~5oԃL⚽!V9ii%>|kjYl/r*.*-T>. 2a%Q;v6! 8V{]ܢ0Y\.1Q\P7>m)f6ZS)AW?G>o Ӆ /r[7~Ao:Bc <势]9Dy]çb/&T(AGgNy)iuvۘHW-b첱QB +>@;GO<3]r'nA}e^Fns7kUtw _/Ͻa~s_>&|X ­w lx?|3oHmMimQ0kǿ,#_qZSRw\zWF#ŏ87I6l}ǿ `s֓Q%_RzOg ų+ X-sZO Jؗv|O[Y6I#wwWss/.\ʷ$mVq{?i/}Vpv}n,' pSJK3DdگK{XfJtd[qݫ)dc2,XGVCYfoΙufWrF+5A£ڄM꼬3vM)d%|g^ʈ@C A fs:_Ҷ\jHW]-,YpЙ_0BHxϧ5iTKRy5_>[;bV[ WtgʰNE\qw=[((V$uzlS y=$r=Nj;WG3"IW_8~d&ٟ[>ѿY%۶ tLXtmIԸպu=tW4d 衻";j$}\w*WOq{ѣ"7E_rC>bgDV2Vwql' ৾Zhzw;et?R+]^%W ܟ;etϪ쒃V0A^综/Z,~"R|ꟲ&cMF?^|G߯zU[w>0dYMyHf'c}iOy7xҏiZN=H cȴs{ g:wyqtЙ/!S1%zKpQIq*OJ3M^aeɕzZWEǺ×W:/2pU' w+3 /[EGԔiӝ-.Աڭǟ}i]pDegVzJ|uT4?Z̽IJZƘu?3&Dse4Ngv;4SOצᙿ_Ks qjizJ?{?{3KQr=dz](b'z1vh{zvPvQ5I;}w~3~ZxͲi{NlIzvja|KkX*"RmsbT}?%yU c `c5!3$_FԔd'vmv^i)~ܶnx'TeM鲃cч86䋍wm];č?opВfEbIodd!_|/n_cf;B8;,)8oUğo2&w:צIgG?~Kd!|'?c)eΎ~VT=hzv{s`ߑ,$ n??8"Q\IrwW?g?,?Ousvel?~XV%821)IvOBBH6@Y^vBA0/%Yl'ĜdYI׿f!l߽;i$;'Ӷ+R w{z@N.r_pזuə$%\#+[n2,mG>A ˵0ފmأMnZ"y^*33~|cO(z~t͏X߮p^>X2D׷e*UvG֗3ПÑd@1~PY!*nj3:XHwk4<]mp@$Ń̹@K#Kuyo1Lz>?ll`0&$F(x>$:9ËcE{lzX`TZQ=˻\4c9&߭ظigJi8~z4fwZ?l!%lH[9fN˻1ҿ7K4%$ e=5W4}Lɜn? ]O~ywsg7IwOW]kln0lϬz~W36[, PS"2bGߍ~z%n" {3c@u} A:[ A\"P?j“c:J>`x *q#0 *Q7ugJKg[㩲%^g¢sЧ_Id< jȄMB brVڎŀdM_Ir ,/@1u ^g"\ 9uzo#9<~n (4$Mr'kl!{~D{m0Rp“qꄩE)%JyG'jQE~ 5얘07hȪ% ~Oh쭾YsriΎ ޳~?@Tȫ+\^{JC%!A?&|3 EUb]P)`.F@ f P{^`m ` σ:;n jhsQb67A &a Կrm|׿ПYT#VYF]q?wv~EF̙"Vw%\Xd]ê}>.YiҸЄUq18K,Ec7s`p0D#5y" % zlEBl'LxM$|UB"E4D0 a췙䞩2 gƪ;̏TYf[f *.Sɜ8HBF%@!ոy\En֓ץ^?Q DDڃV_:s  DF]?2r`2E=Oa$bg^qd)#p+&N_5΢A)PeU lBڨM\U @wRnN("Fvm*&KT"͓)ϊau7C2I03 ~IMF `N2olRl&v|068LzmiX,v=u @M9 .`Tح{OPBд2ҏ!ǂ?5+i)[?),Wud`ƅ1`8`0-yiVh`x;Y i?ˁZx,_ZugUSn~OBdg.,BX4]RT!?t_Z4bh1%&->]St!`3>/EeA0 )nn8e0 eʦS s:M;epm4y2/L]85 F32K zLGLb%ܽfp_.;zg$O$&CpA¯YwcA֮S_Brl KAD $sxh&p9$8B8\x@aHq6Q/zYLdLe#t w]sUj(>2.n]ܚΪ{ahqo$ oꝇHl+(EX©[ (އ]ũ 0`Έy}6~Άd҄y3C}mѾ _G"|$ZʏNzIGਹ3Y\ܥ稳@fVX+[wf`˿ r|xA0k5Z]Tgj5e!69e2`M*E>x!`4r Mk4^2I Ɩ\Y\ b܉hKK92BRPm٥ڨ&XkP")I0ɢkv31͗.ȵ wC#DXn El6 C3P#eS[(brP6Ql͆`Q_|WM|~¡tW 1jwS?&W{++$;UFC:r)O\x~R|-[ )\<1?9,E)x)|cSN~-G}vt/gAq)'*aKq8%Ar˞19g,b97-$W@>/猽^#MRϫ~\$]pgrFؕv>Aҷ=`>T>@smQ>^tm]a} [삩D'?&5A"(qL -=oqzS H0"/%2Fr+ pwlR|$.$$"`Y$ "%5A hq#~REl;%D &N7pun [RK#zl^"!Kd|lXQԽ%5¿/fpa٢ N>!C}½XI!dØ|H: 0݃H/E=ԥ4"#2$V]oXG > $߷Q<1,0  Ҙ:ll;0p+BcՄh1g}9Z (__vS}V~8$x,~FKطd]Vrper+Zƹ?{9 >6Z+pnS_| "|7 ^9`zs 5*l< Qԫvo b' >z_a/gzz.]]Ws'4e m;Gy-?v̅h̎Jzg 0:ӵ̵o8v'u'gdb@$[o]tS.iqnq@?q?a5l]*BаUOW`˧i@5q_ㆥ>TkIg%"Evy5{fy]wN9nKM+@`+z K\Jtxܰٴd8E =ES5KBq һB.eYHba ;o:,wl@jaǙK>2q}]/xd9d6 XSb0lc+[|[O~sÎk&8`ZMH4wmj` [Un}<=ZKQz͂e'02J^X.n$}j}E/ƙ)Kjʭ'}llW=NA!{bx(.R7zoC UдB{ZKkCY쉹sw<"nC[dՉ$[ p E 5a_w~EN3נ"FB&jӝ?QZI6G}z4od`5qυ_*1YXܼSxDʗKQ=?%wgboˀ}g&3vQ K` [?͇͋O9B9]\)zuz7_$;5?b{ YkL73֟xg>;cysUF[o"dz aC4 ߙpOs>|4a|SmuUfl48NlV+# x7s­?]۝ AٲjJ N" 1 ԡ+,׌ҭ?|wF ^X;xg͂)j{+dMx4丸bxW.D ",چ7YC;} 9{`] +Aai#`V\PgV9.6y.EPaͿ@-:s#1t&f6o@j;|v_R $O"[C1?Yn{Δpgԙ|;F/ _%2g˺L۞BaK%zڝ&;zU_^M$j'9nYSG+RjTg쀒nĴLu3xw$RS8? 'J* ~Fr7|!Bҝwbeg!)2O A mW$+9j`j XHZ$׹ב YIVY$KhYM!ɎZ=|9h"RENjRfr$JjΓkJJśX)jD04!JA\R H @TS[ YT*X,.!C@$QVS"pQXm6W rcW?I bI:+:Ɗyao!vP14}0 0378i2Tr=vQmU2qabnI2mP6Ma[qzӫT+>bPCjWHboSP9·=黷P3A+ݑH[1U(fr`9Q3 vTD2 -W̳q Be!R ZxRڶG4/4vz]y8 -oUY uFf KBO~TBE>ѨŴ*h o'" ?H1(1&$*btDJc`y$OL)&G}TI[:7Y>.4ls"й ͂L^ |EWh)IBh TH5M cu= E1D^XCdj89v5@k4By"EQl-2I"ηҀ01ER${wC} QrDR74ΎqkaF12Ȣ郎eǐtHCQ]-mL"R. (P'Uɧdpg8DanNc:_}M5c֚yѳRܽ|QO P1A9V+rxJmbFӽ ;bS !B;FeX |ajM1d|@WR!9 }BA%3G}\בܕES]B !HfARP`7xF1աT%eQ$%Tmf|{OlH%dʢLdBPbn~qx<+Yd&%RL-\uW'PY5IvSP,x*>¸~0aq 3a #2{QH\) Wo2O9© 蚮lz΁E3pCXCC%jFq}Mˀ:12lG%5=aJƞPID7.>o }NLmtK*vis, y@mZP2aD>e:eknMKa!(4:򺍷@ϑEqn7={њc[ & H HЏh7!kf;FTՑ:_EřI25¢%YQ"L@C ^`?X;J:@ et8($@[mN;J @@,7d0`&hW 9~ XuA>$ɬHR2&"K%HP4f/0AL@P̔E jR}aRHG7@"8܁BĪa5@Þ#1)R L/À$5l+aً7Q<(g8B>:gX'ōLYrԿ,XglѴG218zMrgwރn5-ѕᤆOܧ+!Й3$BOSww<U/VH̲Hk]909E\=tm<{/I.$qhY@IvZ3mi\Θ'8ŝu\Ew,#-x˃ZSG[n @ŐD6HH 4uH1|o)j!覎!D .X'Mi`Xj(x ljt:x@8EPu(? A`tA>A 0`\6{>=fJ`NHJ(<2)A2*E7nq%%:8vx~V/+A U&`E*gjN80>}UrGpMW)䵎`lA'ʛNd~pr3 8? ?$WtejDe~7B~o2,6Vw{q[-q DAv@׾,x)”]ͼ« p"﨟CcaHYi+# Meșd'Q~Өv:BtolDүhz]m;g9=鱟T_(`Ɣꜹ\R}M+d4_mZ*_8/'jӾ_w?NAaq߭H0p'Yw5j'M$&[~i(;*G=ZCHD$iJa P]HhmJq- OBW};8# WDPr14ٻZ^BL#HX Ud\s=Bu @5a7 NȴQ{H V.af,H_*\֞:)%rl"hoC%VAJbc]ɢ=!:F@BѼ[#Lgd^3!TBɻ) ]]ϩdÁdnOB)PHK7t %.O, 9(K.Txtxq#cIԣom"PRsa7xes t(k:Y'4KҲӟw&ҸQ2&i>P懚BmMn},[&Ju & Fh4xOTS4hC}R. %YЛ (aÉR|- 穉}~(ʐzO6|D•U) #ạ_7D>]DCL3࡞ 0as9 B|D$Q|r(@CwM>K2m<B  =6A(`2Q\=؛0dv(Yr]럇`fW԰s51a)خ`iwgc>jT&s2×` _.,"B<F}b"0G84 D\ۖ'h5KM#E(9K:Sϥ]-ƽ؜scǷnMQk8Ts{+?g'"mΔղ/?n*06k2M&?NC+ ԆGg5%%-ήI x왝uhQRMΑ# !`U ;t5ۡSR/ev dzVfϳӐy nvlpO Hcμ?o*qxyJ[ܼiW69Un Е]x6q3ꄙZg?CVeG[*r|%}0+Y͛S;޷ :uU5Q%'v~ֹ>;@_L'ǀlnAamWt4i9n-)hVטiKb?77'Y+&m^o"8nHA CkTsbNBo?/o) 9l'Yιۑpמr2H*3hpHT@K$|3pkEw8Yh92MiN0!*rpC*cs^rӲCw,k1 ETD'~ 4;%(eضuaTLwVrCS"lMH4D [z˫9fX-T'Nc5-Kg*OkuTf \43uƢr@m8qiOoIVs%;̙smASohАP43\׆fH )ŖdLf,EO0;vU?͛J ƕ TAk1j?]eZnIiaVOP_ᗚIVUePm /[tuadQCKo4rO\[Y?Oe:u3BS"TrRxSt[kL5{aG@ ޺ƔM6XC@53+1p KwJ  #Bu\Ll|v!;D Q>Ie埛Ķ1|%<`' V:A&bxDXⲶ[ #,7[c"P[?XQT3ۋI !d,H=Tzp@;cwňQ\ ǚl3%%5~H"BHsM*?LI`5^6ź3-x"{ϔ\㩤 9)R495b]9 <|Mƨ6^* I#K+ `ȟ>m,;!{+)VCO.Z0j'bp #̷ fCpMleE+.*f%\c#oE3m[XmǙ-^etZxr%Dft5a}c \]s`MۺÚz( ^Z ʌ]Ɛ0]SФ/1vZ% ]e;$ ‚=Y 3k dv7ݜjzWzO%` ?\Ҝ/_ۇD+(YMm 9_k}%q%ۜVПS(^rrݾEɮoJt~Alyjk#Mo4{ch[޲* sJgl{o;I8XM \煳Lq8[;Ic!>?g>}էr.M %s 3qR\}%9 E%as 8MMuc6u=s(R|e˛ڋ_dfHh #)]s0(El.T9@ 4m$7$3  l9fl \%5^jxTkX8bt'%0󚏌ne_áƖLȋSѯ$Ga*vbhS jQvOavm_6mr^qs%9Gbtb)6ͻّ|ts߻ml/ƾph!̥^ Ǖ(XmDy`}w9 _P{fl/БCS{}xo%z-n_m l"R"*E;3}_vɥ^g0YA[CDp^kVސwwo$83}ENV- 8S`)\⑾s mOJɲ%9Cnjܚi=bA6 s&m>*#Xea俼@ I Mbj<=i^ԽDY%߰H,5T#/Z0-h/IHH~Y,sIBP/) 0/~Wpf?;>3fPt֓m 04rKV[R5u7vjjY-9rA{pz..G]θ!t#QcR5F!٭˚Fv_5ϺvrޤnƁ+ը8eղ~m:7 q7F?x; ]tJT/VeJWXedeTI9ȋ;U/tQd? '`Y9L|6 t^FHzl0so/M߱`oWlI2BCN=_:cya|t} x4o:iAi|>8Mݥ+V J2HV/絩(jS5q7?-;~vUۻ+&ARs K D4\`20hC[/K@[I}[eLc Upw۝6ƏDBoLD`[൉\jߣqMg.v B%p3y+QH!4 ˋڦ^tUZ{&j9b2=3fq\3vO=3xDOq FA`ثyq/)ίYtDD"=<8Sn}UX& K77{m֧Th:IGF/80v7| ; 7mS6]xAhn-%{!2 R5$R GpE(nnv3_I&{|q]ov J8#F]BCFPws2ilsiZ`Tp01@ @,͖+A)pO SAhA4Nן. БLj glJbRyBAϚ;uӇ]Xwx~뤭* 8Q~^4y3[]5A&&Bgh\fĽ>7> hqƜ{RIFr,=suw>x׏7gN(V Pvޘ<1bqar|ڿ̥'_ !B ;gd^;ʢ(57Œ'E%L]hMs|1w{O}-sܧ^;-6byǥ_Y]mF2zOk>wU)KT;JS|'_wv\~ww̼# l[pϬVTWouS9tޓkޔm3sDz>IkĽ~wl$VwVNݻѷ+KWj{Ɍ~0 =gwp$rRCQZ.RUݲz, gvzI-@cVΙuנ.Kؠjˎ}/qwǕ\R%IԧrpU6E:Pl&Ze{ZLIyֵy_U}etcW#n$deՓ> 7뽞sNoR,w!WοRZ5Mm.J4 | $&T47-=\_U3m{,y| K 7-J?yMr'=ݗHk嬶/$>X Ș&9u|'*]KZImA9Jo}o~aEN1qM2WPIzH` d=6~mlW̹ں$֋H"AySOyɤilF~6HM]~՗.Z[ -ΘznP'-s)k8IIF}$4D{~G-eӄrI)>6k]ݝGVУBf[Qxn۵Ixl=Kh]nܹ#H#݊E;Oz%Li!6qp`Nv߅MRЃiX}Ѷe7]&K 5$~drP;{ (5ܪL!~ $ 00J~p1p\l@bPgV3hZ3 } SlTH-N(ޏuEIaCA$ƥ- s7B809jQ€ƒUf=JLq\h damr}6.p< X e&\ZDkŚOX5l+ZOv*$Ǐ.vLBBx @ Q @nkh^wf; Cz|b)^+bc$6] K,xj{^yG/SM"~V2F;d@B[X`7y2$.N;llƽNhOV~whw^o.fĺqvc2D 0f52ڭ;hE;}sKSzܳ47f{r'~aY{O~T YC =5s̓=ەcnh\CQD&A=w'w Uҡ+̈́ew4__İ*?05k&=?ߧRdoq7)ƾ%lwZO^hY{[rL'Fgu625[ٞߓ/S>y' 47&@Tiٌ'rr58g7-!e׳vÃ//?. G|oE;{vM uo\fmǣVL1e .GmGeJ\@A|b|-(F~2%#OR>WzC(5n!*aQqf.a:FbNs?.S.A2&H88 ipfi/!Uku9e.? N{iw}6ryҢȏOeRǕ&uE;}wDtaq?w𶻷{1[ί`W<-hNI'y(.*U*(뿘K70'G^~1.-R% b'}[Knd/D."J~da5U)RɢNFfn/(ɭZc>%a U͖7>kr9nʝy^yUi/:h\|/sdf+NxJ(-;i|y0淇gXC$wFb4%VP :T&ZdGV-xh_#2xR+J&.qB.h ww I+$0%B+6$*'2 0vmgmi$1 r c)ӫ{뫣 >Ga$'y2ɓ/~ֱ!e :0dh f ~}9Hg62-'ڈ"w2UX2R;ݣꨘ_ńL>߼A2#VJx"D[ț3des3@R)5(hLaOCvؙI 'n9aߋmK{߳hl]7~B l4si%Bx.s*b}ݍ+= *?:/dP@C`?Գcxn<_wg=au8KD2B}Od /c< ZR?|q7݄݁N෎^غiZ[(i3-½Wx۪QkNM=熚p(/Yxv`C%LFp[om pۇ *v#h+9,LB#k&7m}fl66D Bdw첔EBfWy }⾷Y/mvj)?xH{__ =Fn/5 ^.w%"dz9y^f%?*L[$`fȍ[w^Yd9@BZa~2+k"6=ӥ,75XyKA%Gph2?rtE]TQvyArh.d-r|f#.?bdcġi6ޗS,r3?5;ĦJc/f/VW&,1rX\e4VmA;O ʉ@9$pnkf9ˑv};0>+EYNrlSi$3uFޑ#q, ENX8e4{}s]̡}hjrXUˑSf$9l6^{oXGV{թ?C`uΏ0>吊nPa_˾'0H19S|M]p[͐ 2;Tl=h]p͐T,`Ppo?o<{<I2#1|$KssjW_c߿YڄR3YY7NAR.ΒWǾz6xI7LtӍ]]g_Z_4v )y]F <ܘ˩n?K. ]|!E*;EԞ76RneM;zo%.k0Z`l Ųs^UutJ_KjF&#$,wx ͡c?MC,.M{}"K_=O%ip`C^?d#>wEVi髛UG WF fF 'ԾAbsL,"yl`" I8@hP׊bL_J l"P,+Vd׊^O"q  HDda-HE)#p)dt56HL~FOteٵ-䱬],Q Lo]{j)6ٵ$&:Q^XD&iy1Vv$#=Erm1ub1`` B$A g~ݻMH9ԎC1 ddvOl06ϞT : J qJS`)4of &ް̰I68ikPDG Y!Ɇr;z)!0h㚢 gmzS\3$Tz_JWt("fr+k$ڤ$ ^Xur dHr[X:GPe Nr4Nɷq@Hʨw*jw~FkIq?ך.:\ tRa $מ/i;K9xGlIVan8EwėԥQ]Er?OǕ&:QB]h;+€h9ZbHqóD{*;5;eYh8'I.K$H"X"Msl69mmHC$Pɉ ֆDk1i?g~5,G9$0}J*J\h4\aF9`ZH[@d6MPVv\Ýq-xȄDVf`Eo}\ jDgˁZFAB,gB 9ַ6c3FQaaArS6V`9BqfܪIW'рP+x@$;^D V|ݮV-G}jD96 nUK>6y_Xoͮ #/Y+F!,(pwqowƪ16K;DaWYB3 (\`E[c1iKdA b~0gQK0%2 Z@|u]K>"5An 5Y[ᵉvF2 r.,&"MOrьu hVcf9Ngw}O9y׬' N6׾鑯Y% Nnzk6 b';}n>5d/8UURzN)9|E nҨ2DMa^'&f { "Av 94DFqœWDŒϋYO= qYɗ4 , &VĽ7LJ< $FD}h3S<*4ުKh Sk->;PoT RS" "WQ :+aXEOW]M&un}_{p߱Q vDUTQZ,VdG<)* w9yO^A qzM؅:vTbĊ;)ps 'I-\u HS*\(lGUQYX1ʎz:**EX#ҲcȃvA"vL;Yĭ# +q#]]5YB<q7dqʂ*tRRHeW SJ#gxQh!X &c$eZRf ̲dIRn^ҹ_h ä4Bɳ?T$RQ(lBKarnVӓqڎnzWiR&31"Tp@Zk UcY-Gl/IT|=Xv=pTN/g}U(}|i>{ wdz;8.?z%Z0Y|62"QKgiiԩGemeKFS?Л+mG{r֞ dxVnQKSWN^sigu=hkcKhLM=Ή@\&1ݡ:mfO%FIL54&:Ply[rzw47ݜS:{7fhnk4#'cj#צ3=)KZ"Ofg=gû-͐gr1- z*ˍl 8GPyyʏ[ u_ŕNy #?S\4TшG/py1_LԦǟsQE};(+-bw}g׿M-y$|)}J .w.w#gl'=[<ؼ;Uކi<ǾP~i&vӿZj^iX׽3,2X,|t`I헫:hIoo`zd>i #Ilxq,Wye)(r$5X$?ekgV/GV_%$Fx#IfjO3\KloWZ?8YhN%A ]D(ɇȂ"nj~ms{JfjR%D5!Q%@b5L$Óy VɛNTA)RRW !#s5!l X "H6ЬuovuB@7vCM,I CAEڤOkbP| VPh (5\I4UQVW EF-X-+C8Ԭ~{J@!ʀPSeyb d\%Y2@UCV!Հu2@yn]йZhf8%([m&Fi)C,^m8(Q> A#D2Npv]z"c!8kYaYHW ;g q{H$˂ք9`WXH٧%OR3FKs@L*MmHT丰wL(psQ Q :09C% ^65s`1QY3w}DQ G|ņ /BS7YSqAkVa7X<@&j"TDH@눌DhPArPsQ!Hu-ZRAӧE7TqVW3E]Epz+9\֯;6`Z5׺ނ)L2 ݉ۻ[x=6m-)X5ϱ]^O%y>~[_iax7,;gsXgn6#.>;=.^f;ee\3^"b 6͞ݖ;>AMGZˠoDJQRl`[/IV zۻfuS?8ރHN]m5Adn1h1mГ˨x~]i#W7$/.a4GۧX=wK]DPi١oSŮG GLމ] r(Vy;/r9ڂfw^>[;69 +:%)ǯn'XCWu+*N(kV/l4G1>~LZp;2}0R>1reYښG`s +9<9[͓à 㠐-uϪ4FJi䜵T2̈!t̪AUvӰJ@a ]RCޚZ x! 8+LB/S5$Hd E@5$R@0 麨JH6߲t#`疚%jsA7 8CB б$AM^@YXW_|lBOk<'}%8E5M{ܹRޣozy6Jգf`\%6NF@s|@!5+j"?%ZZC%T7CG (~g>_{|Yݏ^mhުDӯwHA{DVZ%w_.݉\qu&Ar1!"g^k<ZVV.dML@$basL˫l\]oΠ 3i-3R0BRN -[ӖjQhf/>#X&Yxۭ -TiJN5V6#2PwDtYw[m{v{kHw}M~d)b3 ˍ :K_Ϩ9AFsuu-Y_:s~6LW-JNOl֞ 44cP ~/^ A8%Cyɀ:ph`AjS[E7y( 4PYRPA#LFKRUPKx(k'#ZeH<2gA(:]P 2(p/A{ֿ#LS8q6YXphDm3kq]xZ,6`""H̅,Y3gnݑDgSE2kYfWɖZ.?KukWoĒT%VqUEe mPvmBKXy бoǏ֨h}84Nps5g6lE҄$lDBI|pLH~-Hv )<,0&Ap\l_+\ī`::8Q8Q( U]QJ5J3v}~Rsc]}(=Ʒ0 OXW2%̕ dQÂgFi~ Af88zD 14_?!oa̿QHd~߻}ՇFQ[pWX_i2}N{.@$*Wma Uq(,,Vl!Ԫ~(RDV6yN2H,' 5Eb`!}xLNȝjiP&@tTXZr9EW7g! XtqVb)k*sNUѫJk\?K|:#6ԶgR|tWqp8DwQ Ql3-5oO$:<)lTP0[hpH(-~썵#yHޠ0, Ո!KF7Jr]S+4|刻3TL/XINf$?@SR6'~To|c<¢6yC.F-"GKˬ=!h_W{{Mug)m$Qz JP'OЯ]u}zSu<1wQL+HVTxҿڗuQ'b6()$W(eL8v)ֿ6޳پd>K@A e ZSL_.j],72LB&r_ot}9tggT)>g5@P!U9Z׫i8z =9@OsdYLw[Ug<g2]X!].glA]M*ַl6@k,VHZOѬ63[fZFUȦ=9#ֶW F &CDʆ] 2o]( B#XD@@ 8zFfc]Vh*D1#{$K1dX {``n$^WMf+ OPT0<׮(~Ǥ0~ 6gd` 7HpFb!9#W^3h?JX5K?3utxF!gbbUlYlW,w-~DWU:ՠԮLDrkυ/#6:N4шquL)'tϷpߢE?J68XڒhNJjF5y>MO|E'_[򮤧>+)oqgrlzNW_nMCn(֨/B95„sß.o:l-Btm{k{eXBNb%I<>|ak%@/SU%к oE`}_|XoW%CHC_*oY];nAoyVd:,_UY)]Db&H)Ծ5׹ kY ~ܝ RVQhvi{4hH ZТT IU E3H("ȉ|_yaΦ 7WAA} )Ϩ.wazp[Bbg`;E*8C@GAb!%\啂qXl4It/^0iCa7 Ȓ$.@Me(n@ $g"X7 V#okO-{Jwo| ff!X)7&2WڵcQ Kխ@nw^}зZ/}]tp,Bֹ\O]ןilS qn/aD`zaik?D ;:=+6kw)ڭ㲤:۵?Om.cdɈ#8)D4V5r"a`1Jw1gJ L & H"hD )P.K9a.!A_͠9U%E? %lj $SBTE˅T*jJNoE{@ fF+&D7$Ň[&] v`Q7enNaAX3Q!^P4LԠCJhAJhpo=W#MYc 0$+T(0C9΁RӺXqa2h_.BIh5; $B|` ey`}bN*DO30AhuB5нN2fYa?p D"S Fk6V*pm:.6D E@аl +&H|j^{w AH 28#Xdp0V>)GyA]ڦ< |` /^O VTӂڦa1ވ͞r+nkmfI?^r| *H6yxnҏv?n ۬zynpA)lt4c|y 7<6^{moU`nJWgT a6Tvߵ \*j @`}> t:Ub1&N+/!7*le6/_H9"am;Q?wB0R0=HC'+eZ`lt8nB;~k ES^0[^~k7<s=zR^vNZ|jʷ+z%uG$Adm.ff"$@CP Q|'{oS ,@R{ H^P= 6*aB.'40v3gɧ`LVсb.tp4߸v8C)ͼ!gkM7*}^\ w4߬WVδ{QV'(d+rWğ䗞 :u,Nx7<΄:qbE>Tm,NIGc=TWݰO>^kDQ5S;x[z"8'e:|V?.+ rV@|ʸC3âdcE]%4P]-',$-h* ѭEgN]3Uf@!5:Of2IAHZWD<ϴaDAAF 2, iXaL3Gʒ~FBێؖ)^IҺ7MFbc+3}TO]RmlԮ5V\CK4HMDr;PQg]WD#. Aw €HI${YQM=gHE^ yނ.dǜIwV.M(J*r߬vx+)l-bn=T{~;t&k%.Nt"גħQ9Y wt5f9#Jj[Sڨߣ%s E6I}+ͯJs6XtfbC؜V"6)/Aiab80pwW-gJ­;f,.iPokf &M,4_, CjHl>˯Iĺn&Tp[fpacI\ Byggnr<b=>[~C˫;^pN<H9+ +p#ү'+e+F?$؈[Py|+j:,P z¤I^Wz~v{n g N5'm:[f"?]DMB Q@T@GqL2 PHild#|]VwߩưUeKƶ_.L˙ȜU/&L:Ht .X!Hí7K2C&@,k ,x[ܺ {'B,z}Ƅ09uDۭǾY_EgQDWlX.DKhJQ3n=?n+}$_о?Q@Qf9?*r DHzCn 2&d(yr.R瘼xLg{r/,Su&7kEճw++SH&-&od.].K`Id{Cud 0s?_x׫?hsVt)&lt %j挼H.7}|c0Pnmn=Nb3e"h!Da5,=OPFM-Ņ&&來&A 5e/Kij>2⪿>GXS o嘘LXGXL.Nīkpfώ} )&m=nHwJSg6}T4D-mDݗɨ!'eʜslJ¥ Ngȩ*`h@ހh8w(7vg-ڿnr5 SpX/'fίdw@ʦ]nRRsQ:$NHa#MCe6LL=5ㅜj'juUlRcB(uکuT4u1NгU6Iwtc"6vs?C؈)$TiΔmm\LN Rx$LhfRim|i  <"i`umP!ܥг(?UUk::m$C4*J*LA]'x~&I S涊 qڷuk2I]O;^}]>|tN;dDgMsQSh|_YSmxi Z@㸰 "JG=7=Km B0L 6Hm͝^tyy-uL<wղV>$'^W]] % yS Q%: A"BVQ]:PbV!شJhe<--G', V8GZ*N-7ߎec}@THƼbJek6qPf XtgA$D.<(AY$([BR9( z1?|y@ŀ#cK6yp.c~)D-#KƼ&\zmǼ1@F|l%m|t `gcP;B.Ip@H6cI{丯z.aO,o!FkbIۺϜ1Pދc ȭ0r\,Áթy1f#_Ɂ 5 zjL?JUyeÁrỿu=mcfP1cb:uo~[lRuv ;yAJ'@@swǩGw$Q=5Ǟ@=7!URL   ;)}obz@"x3Dxlm>''Vc;h$yBN=/r^'pQu7 uQ c!v;'w,ڨxbnbdbR4:0HB}7K^v(m`c;N/P{ Ve0ROr.Xj2`aZ R fe0EіS'dv.墼ډPYt0K,e᜚up¹1l/=?2vqDu`ٽ&Ҽiynm_郦](ĀHcr/Ҵ]rѻGNWFB r uݫu2]rOotW9]Y)R#FМYd򬯶)KmjYz;~>ڎDn--E4 3/1(amB̑:AܜHm ?eF!S1 pIHQݿ0 VňhAm#E™rc-)91Uu Ds%jD"> "b*Y ?CyÜs\D&ǩD5ɶlI}D.Bk*i1:iuBYI,р18SCb# G9M\&)D.&׃Kfly[f"K|XQn TdGP+4"X $OY$WgE,")Y>,YBG܉Y,7QC+DXhV#D;Hi"T \)q璅"|hb)RʝxB|{D;H4wcRJ\)f 1= ܢ5DtsEYw"®r3w)]껾?66j|y@rd;wષ}Ac@ 4ӝ q.H3i-s|s>Wl ʸk Vh!Rm'ýWz2Nz*1gt[{vƑ6ɉ=uقX)Ku9HN$-zM2Ui )sh#9Y&~xK˼fޅJR4SxJ2vkT Q^J2%?iȸV߈Fku(͠FSݒL P?Sk&0Ҭєt?$SB@?.I3G1OѼ$SBeF\^/'sX%ǔi8 y*.Axܧ9~Eۢ1^fpi ݢ61Byϝz);ʝd/f@`#3%yy+[o0)BD gǙr/Ŝ!(Fo=Δݓ1`u[5j!)-`*~xqdްG2fO,fp Bw`9Nc8s~uȨkۊj+2FحTWGSޱ/"DȐuf\ScBmUe|H༮LT4XپϧUP1ʓTV~I_[^{'eg"֯jWɮnuy;XudF JޣxVԑ>t1-&/ݕc휽A S>02> =7Ded%(X&:Y q S%'N2?޽ϼ:uT<\03Ʌ%E鶑`9怉 ؟/wa_gn'en%J$`ፉ˒7x40ذg?J B爳R%*L(&d823eEkj*Dm0Fz&KK ֦*\"Q뫭-H5h$ƘǙ,PڔT\ 16mgHBњTZ+cf:E".К˪4dd} .,d_>\X !r{_K~˅EJB"r |]vϓo*ܤ߮f [AB䴈^7_s/{sn)n`9:u̞{x玾Inr] mih73z3q9Vts㿵Mv=(9+'ʁ.م>M+/r gh4+5珺(]1ejjfW숣6޶{˥OS6=i3sx6evOZ=]4%e5]%ȝΔv3L$DAXDO~/GηX<ݡnkO.C$!E[[=k6(a.f|ʵHW}Ydלo,)L*_kA8h: /ɳ__EPuKp7axӱ>8&Dztu(Uia?Y"FC⪄Eǩg,ve]~Y)UfFM Qr%>_H|*ÉjFx?;%G_w4U‡Iào-qd*1wr$ZS^ J}+wdm%diΉzogh8UѲ}P-í$qK|W] !ysBH&Z쇻] !ij׫S|ㅐ훏d !9G/vG/a=vI#/w u}ÊEض4c1Շw"ȵ2%?iAO~~|\>"ZG4u#]3ZL[O,MhDO!ɓL率-^y&g Q4;JZ0.,iɝ?a#Pyf:K̙U@DNW'dzO%ȅ!]7<출툫&\s'$/[~a0RıK~(|ܮ?2a˵GO:H=4 XlǮMcLΏ?6 ]dQyCMvv}7vݿןt\Y];dlgar)'d# _x˸:e>6d[.ģ;,'ᤍ['?\PvLfosy.T<9I&:ߏ.U@zixaނa߶T}s5/[C]RԘYG#/dK%S|5zbI￳&Ńadxk 7&yn Sd6LC;du7k>Hp )lO/qv0r LgIzYcܱwBBay{]Ba=2bS@WT[\l=OuEU+~i6ɃO^\$/nh'|$]tŵOiم#4O:raz}Mri?6Oj/S/˭gc'Y0:`@zKzj%H[:J6b ),lu#K[' eJf+9y6uc'dfNJ| ƪ' q͒R}@h=VS,9NJH1=^,KRϕw)1Hr*Xy X;bl8i$i0gqL/jkS[BNp1fq|g4{~uQ+ B7wĬdu xӯkDl |wDh8fk)ItguQ*C R|`Tލ;]ۘȞەm ͐q<0VGVg厩moeգ]NOny,q5}h͞NطY Xw>>!eUC0 fh(SnY%0epJCe-YhsUBlBeyiS\u> 14|f_t@gDgL.>O5Y$ki-udюjy)뭂!(u$H94lr_ lסÉNy?0B\W0PF;N0g(n`PL(IL~` dHON@4-gnR3Ѯ럿xw;9 }.Qô0%O^yqȔ%ls#TqxKjK_?8S>HA?6wgVy vU_{l*}TNP` x8AtRn*D@.1wı$Ԋn$5ح@ \s8jzC(fcQBÙj ØTx`e4YS[ &苉|/jn?Y(,\9(n}`g]B3ʪ!%ф, xB3ZG ùd՚M~xC3w:8~7 hgߗ텖~k;~4!U[ J߱J.tb/9!dIb,S?LO eo]$Efzw9OJs5C_$Ɨjfr'Y%]X+MOJ58!8P,&+ "5Y%R䨡nήDA!MV ]Td$X8b2L(*l<%83yo,WrPf6IW!A_Ƿ6X)럊]Ycnf#Ƥj$x3aı+2\hѠT'ĕRx墂ik7x^hԝ Nu7jjJY0Ov!{nw>iԥT' g"v>ҳnS$YJSfC38F]&H+j,nwdݭ.uGTK(v^T[9x%N` THaXUoKyx|bJ Drǚ/[٩/]ԲTțvN@=ۿH!Buu8 sq .=W/Stb{dʤѧgǧa e`fwdN3Q^gs&S%7Q1hۖIQ㫝%#8jJ kWWݷQhwQr"P^wJo3f=׹gi\ y5A{qY$,sG_>x,XnnPS]59k~E_vO]yg΃-y_ yp3WaMKoɤmU~6.%߂3\)ulk]rҷ &l-ϯ"f֚l>'}}e!F'L YSLffMF*ay n ]ws9)ާ@3߫#C,@"3@$2'S)OF8C||-c~K/'nv' 2n~WAw +,B"IB~Z)p͝K TS(MK9I\$1([7+h+q$!B,+KP SiJsHs'B@|"BZ4;݂|PΏ{MesWʍڈh,OAvI숩<΃ }+Q\DK^So,\w(;#MYCJd's-/,WvjV Wݫ*ׇ#r .9yi}Q3SA+PMeuLu𥝟"$S+3TVg3!@as>}^ўexBgJL|}f'ɃoY_np'=)UNKʼn[S*>3;I+ urj@Lq􇗏 W\3DDx[,E3Opٜ>eQٜ:\֡ckCi8 x=EA~!곏8e4OT7bx5 m1*O? Z&~ToLF?{K3$=vȷoN#S4ddv[Sj<ԗzTuao#6-[BmjSPbA !۟n*k9O(8%Ƀ%FkXgHTS;LD&EZ\ϐ쒩`ec u0MxX_z0 ɶMuqIKM mԎmnd}!T2&@hL),؜pC-xExZ=XbADʦv-("WcN ]Rn_|,%V/Z|As)CrirC.4rU1(Z=O_ 0U;)} ʀ@jd?5uPhuמ-L 2(9bCp ]BȫPEyPSKJi2P%x"`E5jPqJ[,IRbRg.n "X'cW7ݙ8K.CZ!om ># vןl&%M<:{C&`n 6g Θ퉜N~ovnMKt߶.s7Y-iu̖#5bv۽ vI WjUH-,gdfKi] s݌)?jWBG(lj2>tNƸ +:Ld>1sua@"3E|7<яxg_ȣ<7j+h0ΞV5-F ˋUdR/3x=ѿoXQ|P^ ay9<>Тq61i*?X`g,:INbD+FJor#]^e+;hT[SFb1 ˢfDW'-xx>fv_^(-CR PN4bnk#nx|A)KXƖ$r?*^"tע3b16\[\~3JfTUn|M/YMC2M s\z~\F=Ș ;{ |,+@Je2L:,/.XPd4ISsnQ` Ao$m2 vtx@)HZW~kQud9$Bom2qn\xl& #3tHh-#O7`YXsD1| ŘO}C_g[9f`dxgC? yZX >G2,aφrn'$/кETaf7Bޞ Zu[Ox#2bY ce)s?{@,!L]άub}KM2a6l3" zw!7~ީ.eohCY EkGzg.veRuWiN9)]Z*(kuwdUV ߄BRjg/lIŖ&,Ao_k6rAt'=`V|JlHd ( v-:н`\>mۄ A´ԝ%d/tٝ`"dIYK^:+K7*V;macR WZ ,I7{e,ʐź[7 XCFԁ-LEYk&>-y,IE%:A\!nX@@SeH (3@}D I UɜM%(H bʐdI(}qT젯▄, 2$I&2C $\@HG2$$sl1T! "2$ 甃gŸYlk|lJWދ: $r ~X+l"6.CxS`~L_JMx2.(E {m;,L3 b]BBS 8megm_$jqv ʒ4p1)`Ar\O97!1@`Xڬ\Xa8 g"(:aď\iVS~ҫb>/>MX|iƂ U{z|פֿvWgzOWSn:0\3KV1ʝ!_Z>p87O%gua9WŪ XۚUZ3NY^9%hUŮY[f]'ohO\bG$Q"ѵYAQ ڙ%Ց7xqn/VCD $~UT`Y0Xݟz؞,ԪOSwS(@с͏nv] *Fho%jOTHj3vyU[ͻuc2B`Z F>ge)$5.K5I+|VXpqŲU[B1vR(^,|k(; 0N4QƅWLQc@|jXyiv"*_xljNFp\lߚ/<>V^Ǵ#'\ȽbI5ki0FIX\>yT|j5O9xTL g1*OӅڹɣwsԤjpKqv11tO}{ x=kɤQ8^X0ݺ #\?eR͕.|">& uYK(uSNfK$f#v}LaLsݲ[' 4̤q=`M2i;"ᜯ9ItqH1n#w $a},v !-xbs&!W#$b-Ȕ +DZ#\W̸xMB!$azCP`Ԙ4(ʘa)3!TEC09" 9 $*,Z9"S 29CPittKA$ Vq+p,EI"CBXL@pΐ4a^C Z ni&y^-q!*<~׋ bf@*܊jzj_4AA5&>uIS MOQЃ 4LIᖧ&+^ UOo_,դ#MQhQP(צ"Zi BfL}0Y1 x Es[ U:IKn^"M/"B'nuήwD֜rW!M{u:I@bba[fYv\**,Hy"jA9l", CC8B Kd% d MӶ:Q 馯 HK=s$8Ǣ,P_:nDv( !Q`.[JH $e*P(Q[Cmπ%B JT?)͡T iq S28S˥"\yehl6nW8J+XTwUJj rJ!So̯CS9,G΀BbH QHϙ1 ̜a`7ԋ}A%J+] D"N0-i- \0ĈA*oS1ea LӪ]`j{ABfL2tykNzi3H!A%SFoLLUn&8w )өK1(rJVu4F2D,Qh|jβ.;|Dy:XM  2!wf{ X_^;o !}jW~1Wɒ@ܶ9:Aꤷw,ut4LmG$I_7By~٥N}ow-6K~+}jܨx SyI+|Y..Kʛ=۷$~vF^=Af0D 5KB ~;궳[ld)0$H@Z.ӖրuKFGIi׌E"EalC퐤Н`$ ! ZJT2R=C*t%oJQ˙hL_;dk:Q;Pn'TLEz*\1o4#XfS PF$Q X%C2?H% FBuA@&'Hp<..v SphB}D rai-tɐ4֫Qi[7ݝd.vˣ^bqH䉈7\ 3a0`YHR=프jn2$^V2o02^&0#pI! 8Mn$!Sʵ(.Q!3DQ#CjkP$#z|"E`#CbS \Bw a2z$̗|=쐧Ѱ([~&gڨ&O)m6H":vEz1Gj5<.wA3Q}GSngկvcȔ*,H#Qp;գ1Ħ؏U]𤠸LѸ؇/URp4z1O,^*NP!B& /Fv0tU8/ԯ^s<>=>msڅ{?ُWPAE%)kGP df0i]>~ †l^Fq#. 1̈ Tɢ){xFn\@\W 씹S~Ű+/$}r4 C v e EbLI6Ly?@7%}Z]G# 3X<ȴ?Ȯ?4ο^P boGʳ)5&^,T8):( {o'?b;rluǜ\2cF_}@/ P5FJdhrpK\:z|Mcii/\ D Fr,m &Lc{whu,Ms9$ItX]70w">V,~'^B:b&`"OE !LosC&13/Ⱥ8ჵ/};υ-ޚ/W1݄ ,@".,@VdHRII7S;EjNt.0sz7E$DAdO!.K*o;'J9= s$6& ez'1@(Ź9#P捋(*a:!;s~|\M,dȭ[םރX@-%A U(#y}X39V|% vmFd/hZLd%!W(GQI:_t{8/?@M^7D="V۷oMXt^4) 2s|8\d4v=ϫ{Ā ՈjђDs]?@*DV^BccrKNM$r w7/5!`L0DvIKK|HYw4 #,BV% S`4,}R KZ Z2JKbvB(F{-nJ0vIl3Kv. 8K΂%bBT"B :uvU¬Zb@A "X̰dvoK,4MoR!Y3T o D*JrXNhv( AײS/okٹs]9E;>so˨&T(Nw(Ulħfk9qB²hdo&  i v 9J 4KS(h; +\ рAv@j lQ ckN /$Yl2@}S`BD\ ɐP#aJ=C=e#31`8ɐPdU7/B 4A/tMJY.pN$ZS]c!,6<+ztOLOh_sdHyQ@Y(ɰq:1yjB34A3kU]n.L Dci)`mU/:AE EKSW}/뼱QrĒw]b6x(3s7]bYTY+1t ߳JSam<ͭ"k4Rmpy.X(Î^ZpƬ*uY[S?up6"ߦ^!I镺T|wәù 5ԣ?#LD$O1h(}є{2sD+`Vye1"`tf,ASG`݋m?UhPagos.Qe(м6&>nDWeY-:JX XTN9P12jdY败`]37$? 33JF&.b02h>I4ovI.(h8]K-O:U7+{gz\6zo'x z My.=}z4z7sb]zԙ ,VSf7ķ&KFx@!(#"hE-fvU#/ `2M'HY4w`̶KZ3r$h5m4Y&O[熓 YnvI,K4AHxUо*(m̶KR/r`~zP=Qhnupl^o( PVqIDM>!S$_oȇP*W&fzCBO9O3 R"h |ȔDk.yZxCRdY*-6eZ>dJM[ FxI4LwQv|'uQDAwpg1t$Y,?-GSmQrA i9HRewZ>i+wbz\*AjvI2OuSd;}vI*wo;dAi MR :3LԶ;ih-X0& !|Fh2$v:o0iL3T 3eKː[΍BXD!1k{x%A`:$Z2$yfů f٧F2$9dڄ+޹I[D3ZNowRdQ>Lj E^ Z(q]s60 o0Q"7%ql}Nbb8=\dhT$RϚ۴`Jaʼ~ȋR풔h.rs -|sZl6S O]t/j:Qg]6~.ӡ[RDz sihjR?XA]6*⿌EF! s=7?f[E#K=г˰B!|n "\dv>jE#R&DA.]6H o4IlIYT0]*0 Pn/Bp ShJ{#$^ _RhD%Dǻ5]7.PsӿgyoMdQ訄I\s:dNN]Ry9$+m;}#5w5g׮6`UofTFQKGt踲8V"[kQ-8Xm%1Lm.F Itݝ0΀ ̔X&jGUil;:x8CT&wi܃UUU!V{XgAa @0BnṌfY1XrH'ŝ Vst_LZ3];e]n%DZ @)rzvIYÅή,5cS}8pbQxkq䕘%(LɂցSG}鮅 _G2NoQYȮ!%>ATڎh.ʝLI<0{}d~#¤XJtFXw2% ţ`#Q ti)4;P1~ ƀʘC,tR&yBS _fwxd-ӗ}*G?P4-C֧hRtho⋞Z},oJax6ĶZ +S1)Z"Ŷ\ݞ:oc4~U gX0IZCeH$fn~Sgz\$.V WNfŀQ|P&eH *m "rb2$zbM7S0h2$ q& $o zH!ձ0O>6D.x' h/IJ$z(0Njh<{ S/;ԸR=D䰏ƿs{BIwh/4:! Y-Ϲhic6OK_VKcZN3A2Љad[!RY$L]pthr7xvEm 0m fYm_WnT.Gttu&"т.z5Ol=r]Ư-Ό#Ō'M6KR\ yvHF栋td+G]4g+;-[ ̓d.vɂ/gCAAG7|+3gsK5З'H%=ϲEZH6M:z -jjOܔjhKeY@`r&L Mܐsegΐ4@zr)șQA=} Im%,F미xa̼c06c[~K(Iw&"bNN2VdrSJ(֥O,4Y)9l_|:nTf`BDJ!fcg37_^ !70Ŝ<.Ybf2wL\02MPj!2Z:/@Յ$A LKժ ɜfsf ; dyDJ$! o %P)D ISx[@ .93ag][% !IτU2@WpU(y_ܿ@6S޹޿~cgBHf/dmBHZ!$@IO1ZX)aDQ5[dk}mDA1DSd̃5۶ fK ?Nu:z3);\wK$8!15j4MʔwuOy\*w H$cRߛhzRdW[RA`7̹)+"hLɌ; K`WUh /[GLߓWrvm]lzn/#ljaq_ڼrw{O޲a3j`0{m1[} zϝK]ޠ6:+n<)Jc{3fF^񐿸)9;lTi: or!\ɔ1zsyB}, w1{ܘTu$В Fj+b2%Ǽ;:3sE(T'L'9u'c3הzV1="/4T$K*TyofGvSɐ<#VҰncG(4TG;ϸ{Q>uݲ~SڡE"2BC S݈#7=]8;;`hȤ@'CҌ#ݮ cj!S%e摆ql}仨 Gs%aF%Nestܚ+1`t…2j ).2 >W$Us)]Az, @!N\\u"$sc<ͼs_x<9]hb1ݖ?NߎpX\񄛇C/ƻ*IA>ܱВ~ק,N{. 09Ѝ>b  _.[S}F8HY۽ʾ_+<DZQR{=¸|Y}%{7D ' N?Od#C9>eX{}w\};"W̱_"Hp:_=(2uYnõT({+S5.8IgS^_D {~]7/8m qq5C]CEh3~8In}}+/ X VLu-J@E *>'ɽx=72k T)Pd!z덓2?[= ,[9EZ#~|;}ΨԨ])TV"%z퍓dޡ徒}=e0aKa)ޢMo$o Y^YO-ntsɝI]~t*=e5hawp7sda$flD?χ5w=bsj dÝQO["z>:,q=ͬ*zSݝMrgDO1GUw]rwv&܉\jӄjt iN%1U"DAE7_ 3^_\l1#>?_~Rb1ezOo @z:tEѼr`L'"$.{ o<,AzS܎t;K? %2zstEAޣ1H",)N6̺lyɸMk88|}{*(UW}_IsT*j{p pIY+B'_ch%ҞP*ѳ_͜m'$ڇbr+\~Z t{N9PQb"^Q23Om{ѕf]_+[/0 n["c{ 9!>(@ѿ5bO(b[E(6:IF~BG[\WARAx:$!Wд  )0FNJ3zH Sj!{hIxZnfI,V$PRwZw[{gR*zghZwV 1-R(3 O-Yg[-'z!zKyxfU_SA+^؟ɳ_;|:Hto|:99ܝT&<EI_︶rϯ^u׏,6a_*笽_YRy?kIei{Y{[䪿H#wfCcWb>gΒ͟UxsÿYo׺A!\;Y甕/>6ؔۄn~YR(+ ZM1n /G %۽k0W]ل.tlқJ\9[k;i 􈶲E񴖬R "@^t2uwJ10ew2:2%}8 l ~O6GeJnhzrXngFmh5*SË ?vՠ"/1U*"ԨLɈ6 pGݛ -׍V@ʀ<jx̻g[G^ -@D"4mk/8~~}xdkܹzx1@ʵ`kV!{]cÃbwC`n7Р%Ra,̴צ2wWOO^@IO0s~pO[fW|ekx+20S26ѦNp}xe5h\ra䙧}Þp7 Z;5bݺo}ẅ́>x#ᣁPf}G3U pM~_y qs3GMҧ5u"@rfy}~fo$եx}Pl7>@ׅk7lql#Ai ϜjD%ވws)iй[BP6o?9D)- gʋ~@]7W-4"Kh;bkPO0zKZ;Dt&Ű+RO H^D+ȹӫv~e*d&}K2fm{Eb&Aac6I9,1 $ȡ= N3$FMrh4Ң B2wv(LJbPD:d !F ER2EY:HHF2l+ATuQ\&T#[X/A"<(ޔsV2ȑ H rʹvnEɁ# RoCP'+QUq˫=OC#fi 'N f1Hm:{[#l9qJe2m7^M՞29H\lp"BH2F+pʉH XYUj/F8QGnTbf7ʄ+ 6z$ӭ@ 1tmMfL+[#ԙ4VB2FC(n:1 ")Aٵ=54dJ~esU1ASM2%S֫qg(!X!-ɐ)᠟+ͳB!OP[IEzy4m>F_Aɕ/#Ă&u*3ۺۙWWcŠTraPQATc^ܩW?!)^}¤UZLUMSi낽Eѻ?()Ѿ] D*Ut /w W# E.S(MYL ɭhRc7avSRzK)G,EPdQgL'ċ=&;8&pg[=r!Pݳ(D[yqNmCX:+*M%TrD͜5=Om[im ބ=oCvf)*eԴ3c˱o+_ϤfJ9NIA߲#i=Rqߺ-5S"' [auZ4Ojޞ b%,IM2mF)Iu lM%Il5%"* SlV"-P(diAXP*\Zd.jTʴ(AB+2Pk"+ ,sE@ -+٬Ċ10Q*$zZ0d@ RȊ!`Ab7RZ@`Rʪ*: #ZMEpZ&biAEgZCE%!`eᧃ2,_ڰ$n -tj 6iE X.?$&Cc*$̔?3!I>qƖ<+sPB0-^gJR^fP[Fe.UPM4"gJtPL2 Ky]m}[& լd4C,3u}܅Z21PBjZ&G)H槚NZyZ443( 4μ.\ 8ZyɵO7O@`D>gJՐ`X"{ <}uJ'B)̔K(^'`s 9tS%|:7gtS=`'ϙT. o-C#rFp=ϙ +Fv?V3*c;7/^Uw,}'(WeuHUo6Z1(Om3lM;ƮzsKr#O㏱UOY9-R;VÊX>}FcbvM+Yzw{'=e]:}~Ei/<9@׾g|cXo09𢡄ʵxO_rEf&1:dCﭟ4bm^ b2yZ<7euy̿ E dBY:VGsz9%=ʓ}) //ՋәfWyE__lm(L׺u"rY?μ䋾rlӝ@wx:S焕\dBr8`pWbz a,jӣEvSRåF^/7MK^! EJaV,EdHv@Vs( N%P T!ޥM,R Y (^, &rIuM [0d)XaxX,jY![^l ^0U,Bd[% -Pt0tSd%N6//_ v.eBR-&J Q!9崏\vU'KEE˂q`!:4"YʥdVpK84' RB0Bmgu) zؾvҜ@[fk[,l`:_}oP43 H:.YP $SbKXuʽ3GȐɲWP𜶿nE,A6$r}gst[]U狻62(YF>xlXL \(DBMJpC;,#P=׿"w;8UAy,R(A zݤP Vl>/\%p]_ WkmIUv<;RHd94;{|ڟ`hCyTOzPr`YgTKB݀g*Y5lJp > b2޶נJ<)S_pz䖯jX&Đ`>%נDc!3$}bh;mAȂw) &gH&e]1hCsJ|^EzY~g# rp^wX܄qӗw|c%_{@ۣJCH,YSN_àD0t8nz '-szL9Ҧ^YSd\靮A3#"䆊oYI%ow-Ь`MLmW[_r~[Ӡy'"dÆ>w۟0syzxgwR7.,}x1+Od˾Ag/7:Rģv&ԭqzeMMGwUe+@E,3FXD,KS6X~Dh2$}ǮN7b7IE-]=*R#Ih6JА[!-af#'*F"rk*6qh4{"]HH>Oݴ7 ü\7bf-X<4SV?kz+FnaJR 1a3F"?e$jXZn0 )FCZ%ݢwgFZdY>ھŎؖe9WN k:Tǁfʆ­SΖ}hտ{1LaJ<K=]},kmǁDMC jX֚ [ָZvQ?qR~c9c{̏] Yi]2jg$BUX5)B3d ;f0!Hu&ixB3$\ح=i`]"W^4iVF(eMUd. rJF h~}ɤU%pxRJWa{BC)8F RgUeHL| ўTʔilDdhp *-A(ukƆrCe"b~dH8ZNC֌2IÐ?2$GAi[/%B;L Ǫ?)Ys՟wRCGULC-T&>\`9?2$SnRl!;*|19xY)mP:94[3R%MvEmXKۭ6,6^YN9~esn L~UA6IeFASfbj1eJ=D:&Mٟ2mP]U;lE *)=LXOwk<*͇Z݉d+dȭ*kK6I*gSZ4Y4ة庹l)^kǾSnxmY J"_ѝɽ O^ 1Z96_bbnn#sq 2Hb2qX_W%CIT; P?.6W#bA j9ຖ&tq4~7@ nn\c$YH;>-)=rōUUS4ԨfֆN? shQk]O9pMաۥ=\zВ5ML ^eO΅Y]U4'騮#ohDYSVutEÍ>C/+״%Ƿv,fx}C|]ḙzo\6!=/,+M^ӸfrAZyk/<)q+feAE5=(_Y/͟pIe"z2>}gmv~ +<> CtA)DXo(O%4#Twexzй*a2B3$ ]K;'XP'PLWxl ͐ݙf:BX_$*!aNsP\uLDB`$zCXZ$&Us_Ut 4m%0 M=q.Ef V,8 JA!8A'z )$,b "޳~]3$hŲD,Vtr \3$ ڔDZPH~a^u#<rgzV-ca%b?)*_H138gCTxB% NpYT612PA=`dsעkbDʅ(7xy.cS="8]6ypgc:G2\&ilʕ(]@42PI-;[Lȳ(0ӤDƘd:mN`l)ZC]n0=bHɠ }s ʭ+#Z Bz.^)!Lӻ ).ʕ>9 h&ymH\Mr:'0'#㾹V+ÚQ'|8lkل) мT\ Dz LDD}$I+$XfV$!M'z$I T5KPB \~ToZ$YȄq،1S>L&W~) 6ޤtI2@b"3h쌾a2NX"! 4YƉ6DIP8#k^-*6a+ 7yЕr#":44>3. :vQ|bD+ [~ȒQ֍iUt( 1mלIWP[SEE@ p@ 6-Bq3yT$"jj sj\. XSĂم wx&^TuXj-j (X}dCv!/pΤ$z3u8L^'dLJU_wFMb!)]ЈEы&$BKqKO^uܚU[_gƀI'~J4t̆*[Br, LZ?7 A3`P7`Gz᱉zG~PbgȐԏ\$y#<2$፺C(!` ?,eM@ɢf vPiʈKdn_a'|ɔ1e);z̈Adho%ζë0(8l󋋝U`_w=  o%X4=kN {WoK.’ \fzzq*2N;p2COkxLι%⍢qY)Ye*uk I\bD,V W!b9p_nƎ=t%Xz߸|{Dkɖhq9AmCIS\R?܈MGD$Չ^k~uUH:hdNc;~cqCK3 "o (P(: tE"ج3ʛFᙅ"">`l|i⠃ :"jNX}}Rt~|rDfDbjD@Ah$C˜_Ht)7 /H?|KQAdwOVudy}f7NJocwȵHMk8#?{ͦI{C}g\w0x$NGY]ͬ5ʥb^pwٰ=9=^y_y( Tup?ucDBQDЅ_]mfvV}ᗜx'Uۧ-\Wڿ>?veݽ. qPtwGqa;y]1k ٺ=s wqS6 Xoq.Cd4>cޡ66{h~+5TșDkpN M)Q01˽q́s9Dv|RR}#bTuZIGeA%SXa'kaY!x {~""⛊C9 `?Y;H;\VoY̯2d+<}!&$jfA!-Z~ exxUD{7̇PmgAԪ $[? ̲;|{,C&Qk$b͑˂RGB `p,%%+lzUɫ"(Gw݆;C;q?`EL$A qwQW% zg>w*ews F6m9lDi`s!`F _)MC>wf{+4tQ)Mdr QHK U3w?VdϭFFma]>lx,t Bvzم&A?\ͱڢ6 o~MIՍ JA e}_T\E,*­ф.""@8Ol~Ywv9%HY-$J[$6|Dnʗ#U3<z?Y冧KB%UBP4(/huΙU5rD^OPMe& kRMKk_ D \6ϔԻTap#T+%M G+p˽mϰŠTyp&F{_QuN :@Nϭ V[A+4È&m˶=w<!{ǣ*)r@ BŒ Xt~1uglU1~eu킥 ,4z_;}=9Nj[` |[e[f3nғS)T$4L{5ĩ"_ej钾 ~$'cQ5v=Xmdˆ(̰KA=4?jChvr!2|fGZ^4xX+ɆɆ$!@"vZkAN Z]{c-c( (8% nqs3w-'ѨŮcMӹƂ #,%9m(,=N§iMgu%Ʊ(6TJ17- '8IHI.4g(iC=f^h~w?C0k,RSbQ ~^g|sd]ݦwfXrlS `Nje (|='ʤmYO2"Sl5_E',f CX44|/(DVhLzȸ:_.Dw+4 5ӊYJKpE6IeQDBB /~ነUdG!z [aewʎE!GA!]Y.Urhz/):(:*VΆȉ޳|ex)UnvOtʿ-2`D/GLoٳ['h`DH/M^Zs9#(`xߞfY'YH$ Hw_p/t>Dkq }֮ҁqx_k0S]$?7|ۉoM#Մqb \Ho?I Xfr[J@"²%j8@W@5dԲo,IЭwSh1$QpViL&ސ%?[C<ާNoWW;WzTQ~%",<@`}r}p uD@zK7qMb9 *Jn⽥hM blpiopKy 9mD!;ێ"5u c0㸇ȯH)PNbEHٍڽ{p㚄U?J-,|r9`=?xn>VE (o!5[7.?I sg;0⩷lx:ocbewId/mnڎCު#q!J{%m7i $e V!U?% qտ9X?Is1w'>p+^#8Fҵv7dfEa&R='% $Mo%} E0p`X%|o.q.)ɒ:':)ZӨxC/]"tBHxe^+p_z7d ٷ]"_,H!MC3%X(@ Z"i9:"qNp`C&z3npr?gjkxTZh ޛJ@Sp)X/(01eD]_^Kⴔ̇): EK^cQ?/jl{nj%CDp)b{w&puŵ_2exLx専\(0%t׻3cTADL^&J QP0Bxw&w$6tWўQEov-XII2~>)[#G"k|o'^ĒEA׊-Xځ sd9 `w~g#~[WBU/IW=K(LT"׿'F/o=׽J}/ :NCEϫ0U0`)y^+mMV)X ԧNҔSPIZo^ y1 _jEW!RD. g{7sW|ΠmIH,ݶrj<~85'[G2HRcS͹ SdDD5xmIhda.ì{ oL~|/n+ClaxsI]xzOfۜV;_Gp!i[5t3a!Ts>[tv6w;.{ j&}1daIaOu<ᖑqDyj8PW7mQ 4diÖq~H7⌀?M{^k% ק)Cf+ۺu90%KG+kn?Ϲou0~wgLh/[s`s%"KPJx??i_[J3jVuj_5 {<o-q;XχLS^{"HRoEAFMEi1**yLG ՖAdS,!8L\yJrp5EzK) ̈1iI4m*U"`o;Մb~lΠe'7^a+~AA&l GAɠ~F =yM-\Ü$JhTx[?/Bt4:"žB-NaBf3ZT8'=yɏ}Zl!@qL)揦}ѢY,{K[S`A$C˄oR4fLP \DIJ.?CaB< 5t1AbR:0mq^rvwB'ed ΋Zc[){tMÁ 'hWF%J37֒ISzV_ItaI&nQS0BhNdtkMbPԄ&:OAJZ]͝WcM#-"T}=J:DeR~{|{W? )gBZ" ޙ9] T^DhW5֗}z6/iy'b|f^U5]h>.}p 3?®4o~WDb!9x/cf正 8@QFOXӧG6#!*EXoK0 E:A0%xᶋNX> ~zȢz$=Y83’x$śFgMJ֞4b7rHPi>j`ۘVCZyWvvQ,¹.yfjԺ.V7L_Qcvi_Ckon"el!![0bX!)e ]%$@ !epNd)P5Mb#7w1i1$^/.iS0kYZ d+9)._li~~z;\"  vIuiI40RV(=D˱vI4ӥǛ!))&a@ "'.. g"c`)TNld$xgb.FPEmwbU(Ow'@T@p*Yʼn8< e@G{8mqqIa^1VL A'P ^n֙]͵, ȝ.F6i[6@hM8A=J:WWen' LjZD5?Z;ߣ@ ^N-Ϩ: o~+P`ABmFAj6$ hcQuP`| @d.&3׭|ñyK~gotj4 N&wx܀+IX xJ 4I7>mn~u~3nٛ@BN18%zFtuK}O<{ $,[#$1 SQiI~NmlÚ;R&L-qN$U}-o{yg[W@~Ź C`63?9B$d .99*!>GUB%}gH|+Jvޟ7|s7 T z+/HgS풻JA)J| "%ﷄ;1rɞsO|wv1QK*}]ϽZr_^iUxCD3ϼ.Vʽ=?}LgDr%ܔ2K›C&==[NI NMflVl:vAHy=./v'c7xI5jA6!!xnz N%MVff\4iZ S{#vIV$79ňlJa>Uz7%єc ^rkMNͼm?.I25׼8C RQɸ8l)1 @B`}TN/rJ1QSEGADzHC풓{*n'_9mLn~uLKo>d13nlLqSgC֋Di+g :,`JW>nLY~ وBNFps"1c~S>]2GS^pI&XLlTWw]2{\.)V? [}q%I9 pWki˫vIc=B"y\CMfqjD=Vy<_<%y`e C} .i3֩%!=93ԒPKo?=@$ d_?`m+O֩%={\} kel!zB`溋nZαH@ཎH|fGluw[`"~}с~ǞN[/Y0&tX#. x[ Hx#{ē~Q׽Vt9`| :!Ot Q/K%yMoNznImD'#4w tbϯ>Xn'6]S{uˡTÌvFrsOMSPa)l &b-$jN̸rڤW-3l*Raٷ%j?S Qk)`h*V/U,XmmyliU}mɽ#i$:N4UPݧ̥zӆ{E.#gX&4Fji 'LC-`zZR2LIfC~{Y˅XX륙T Xy)X0AE*Zɥv)ij66RO;֥8A)VfNY2ּ|Vqd`^@(RX%3@vM_!r#Ϸɳ{+{0X,gwg2$±;]yg[qb@P ם=@(C2uEKC^A&{2$[<=_cW @!Uiٱ[38- 7:&HŠ "VLӇK/2$T"5E] F%,^a |uE6Hx2!~ER~ED+f<6qWJ"C#~!I"oox! ^z!1yHѢ>șF}5CĉӺw%mbAeRe)C(靬bZz[_>{au@mC*‚~6C6%O70Z{T 4|ǛlDj6`Op+0y#-kMՔi>}|28 , 2)^kZ2-C8ʚ`eºD2-c|ҋ0B7FP1O,-ʨ돽c|ߞ,y'Ą8dHdu}fKBHE {X6L}X̕}[v()Uܷ!kEgڦH!7/IA}fzt&PFj$VPcU^EHxwks&,Hc #DCuG.Klw0ҫ0fYXx|l[( hEx{0`I~hFd z=G}lﱣ4ݿ?\6jDKh슆2zY%?>~|3o088׋6_$0ѴUkڶ,-iOt|i]ߒy+(e#Q4Om 6qb]9T1 -`4>$6틣mO2U}VIJQCUT3pV*̀iAMgӕqg2%ݟ|?^:Amϕ-MȔW`1h6*`Cs9!S}3n?`eW *i fB;~vSp_VRoRk<}O2!S2k1?2,\ek )U1oo7^t &+QpE&9?X^u헷s[weH6N-/kyLuhee`z`[0So^ U[[Gq/eHu獑cT'[j LMY,CB19˸]!LNw~&G`̡>wEv54Gonnޣ[Y ל3fHnR?QsUMj22'ʿz{膮zJXu[ SE^'ۑ^pjW}]L4+ 4/e=>;:0Q?J]U͎, ez 6gŽjGtUp *cdz'6oɇ7m#TVZqr1ufylLME`(ԅ 3%sy;M;rAie>3%s=߉/)ii!`M゚ǭיi7BЮ[:{LIs?;*e8ni?a /33ƋKoy]d0hVќ/z(ΐ“:'K, ɉ$\m=tn/MY  Ve״׶Tx27Y,ڜEZo:V`#|YLs&'&Ľ$3]u5l=]3҂svox4kjTL~syɞFnt,NTZrCaio;0H%ǿ~_&~JY!@g-?V<;Zcȭ^)9c6>F`xZ_GGӬ[Jt;|}2o=,r9С>jэ8tuS- a<*>:zhqMo $q0Y΃9ſ,}ϥ0B<[Ƹn;IvΣ7g=%T'#znU^5I^q?~RZ3zo;I?VoT`?څZ=N ?n<`S2Y(ڸ51IR5nV;Sr1 5sn5lx{o(VҼ7Cwe8ˏn'ϖJe8? '|ɾx5!fKVTh)֩gan>h6g"Zب۳wGdƝG=ZwwBVT2bp w;HG~ݩŖxGO2߱r}z/z;\ǴЙFn_Lg<}j񌻫L^0XKIDOޚ 5O<>LUٞPuљ$@wÇ2$~ӫwZT:@XNd"2$'駛YʙܞRu3- qtjO{\.fR{b%Ntf&ḑL .ZXV츢@}UN;]-m:r ,8(7W {|w<}ߖpVj\&N&1uowy;-1#숫UFd.yhav]xsK5e#vW64$zϿk\J\/Zf.Y/s֯{(&iZ}l2uiϵSMu$Ϻu{}@+OSBH)W}~yɒKonӤ+@u^h7yIÑҺu2t&-_ mԦKCu$8[2&)O)! Mb7Χ}gî}4{o|J]ߓ=C̆ )+yy;ꧭ7 reovNE.WFur17(U#vL[KľgndvڷoY[lTezW1[1>]E2i50n~nUɝ.IxE|SoV 9C^9n񔾕o*gɞySPxFn͘q?:[,~vcŕ>gtLg^nw(3c}PbgIx>NX_'Z\.yXWY$K 9c+G?姳HwYZ4՜r{B2nVH?;j8|aXŽ>u[@ N8+v7"dÛrJraǯ*ѣ#*BJ5w#rȈEM]Z6}/a߾J] „a~jaכ=z)D@]jxv؝EHBrb~+Gɭ.StAβ an}o9J)ʳ 8K_4s >eOr,M\žl5ʞ_꥝c៵ \ڕu*Q,?U vO*}1ۍXOO&8y_`}G5}FE2z7HaE2-cm` ѥf@ҐE2XgHYIRKf`Hh;jaKL{k:dmG& -WYW[2Ou'zo'aY:"+gESA̶v=;iO,qejt|fjy;Kn9"tpиĝ#qL GwZ&&%(J( $]V a(V*@A!(B0J(RFMSm<3IRbۿAl)DV: D8 Di@l{q劏|gIڎd ;ʏ\*1uϺ|ݗ;KͻQLusyޗUۤ³rޛ,QsN} ݾ/A]6a3+D[)1RJ,&1K&a0I:}ŕ/ $W?*-~E^|ꯚO6m;n'(e^u1B68<\k)X[U-7d`PU6@;ID6nI$,RM"zmsޔ38яNY?Q҆7Eh#X;9J `?ho9JڛP* hFGPpp(G_->qkM j,mja6>eCFdN)MV 3]hJ8;&2ƚA&2bwa"$0]evaw eTm,)8(UNJ,z5)2Yd1ƤФQg}7O<+t,9p9 ~.^`2Y;Ǹ>[lpʅ,Wcii]½*FdWGίS]]!؟lN,5˕z lJqbWzNɁ)%b֯~wtJ f]XmEOg= -~]+#@^ң#ML:onD19 'G8qmE>!$[p wF޲7j2w#hiXH d';%zPd 9mBx zma3x>.sb*Hվg0XrBN˰Yw@0Qsu`/&xhw>^ ALBI@bH/K$ʤHA?vrD^Vn9xOg~lg0G9;Hd+bcN'""z{n2NlNu͑ĸ;XjZFFv䞏)+wR896ߚ>:J$ nA4#YɉM{cDP1@"SɚdVOӦ[t#he<5m5sN:4,"´*a@* ,JB *a DXfp Kg^YiS#?%aX(ٿuEL@#3^Ogy=P:JicͽrW;9+O%ϕuѠM{9mDХ厒ιlXLxyIzrGIM벴 GGM%w{;fݽO˟;/;u8J~U[{w.y -F9Qr­zoK}=#];!@>EmᴿC3տ QRrh yq{AEqd Or 9*Ir3}Jf~/ՑHE%PVbb ~^+gz4aMBLV qge}+Bkg? O7̯^LV!u;ם`֘Vvz;eRÒ5dD%?ȧ@Kj({c%d`'="uIMANﱋUN#>"6]l&6%l^ :mqD2$-螺n{}&!I3 & ^1m}?=xt>FX鸏[ahg>Wv0m鏾]rK:[5$h } +Mq„,t]vG5d/ٺI(VG0GvX혛K`i+L Vp,źmk? d!*f{~a]BQgf{w0DAs'~s\EԣR;ͽ/zaR7L{1-"C )bHl v;gӻw؂@9>p~oA -I$J!p|xQr:iߩ-,:LJ?~[o)vl}h+$Qo|Ysp:s 2եGߴS()<@1Dt8wPɨvN}r=Ջ)wrWܿ= V\Md~ݘX#RqOxUdo~|۟ f>1)eOq)ֈcVɫdq'=/Λg+&C2ܫcSeڌvk\Nxせg>p&C2) hrfMe%.pys5riF.MYHP;rV\=p/{kVp}Qlu?G>;7ә5_xkGIٶe{Z=;)q+\3~vkGIQy)G9ueڹ\ύkGIa{V蝢&nM}ӓDr{W[ͻ-B<)?D?f5q<˞X ˅пzJeӖ*GS|j{zx=.h$xMr2kJg7/O_#.4֤s5I響!v+{9c%NJ-9W)qn<5'^#5h$~MVenG˞K!9Q1] DUhZFdJ%Auua5ݧ ueA!61J\uJhrj՛,hPbUAFN(~/h@tW_eͳ82OZ]

M[=W IxZyQq6sjܾ*D^:_]WZ7(jְ*CRCM~"!{<~B=G \~JOYYch4˷="-mr>~) t"I S m<@d"ea*FoWo D*f#.ř+7 5xC qbIy']ͷ|XVG?s 9#E*r8s`R taO9c ^*9a Lhx%>/I2'qm>wFTcfGISn-Re#mayfGIr0fn=+Iv]W~QD)+\ RxJ2 +ԙl=R%̾dAMXlʏџϧ /_=oUacdl.+6c^iPKso _5ˬyn 4-*f>|YfLuمƍҺfLuY} dG^W~Ө5n_=\aӢ MT ,Ѵ7fLuYqC-7/(4NQUYslj}+Rg ӏM6(Π0MbőiBUqN]'GbARp^P:GΓgJϛ!\XJUq^]'3%Ereޖ0F{U=SL 5f,x.y9xL@Pd))!Ӗڒg_I[ک*T~3 w|bm7a6BN.1|_Ȩ^ИyKqy69۟Wnrb@)NY6a-O,@ӹw2w엦 WҏUM2'1k.1` h. .}gEG$]yэPuX74WKlO}蝿!uᯓ9}n&n{RL)uW<]X6R.JȚcsV'd ϻ,yln;A1,9a68bųe%q-d;[q͟{Y]#/:(]2xCcԆ=Б5D! a xZw3._pO "k)U5e&A޲[,nc+iT~:ZU -RK^?q܍rMv>׾3KJNO X+`2Ҽ N{q@U܌&!zAd. *cao>ҫN2${iIxQӑ!7Ou٦>"uF͟`ЉCsr(P5n%Z}(NMrB!! {lt1LI(7zi乷-K!J鴹fg8;?ם͇`-ImP8Ċ ձ<*&q$Fy06ߦLƆW ̱Ŧ~},dH IOt7:bEnsޭІanI)H60D]Oɲ[`I w-?3L< %"j#g@I1Yc F7[I'U謠dOE9YTւ .YtsQÔ< q'dz92YbdnBdÓ0tI63Q|md9mf&=)qrǹXֽb7ݛo/,! [>ŕ?ml?KlZ)9s|KdL$zϘ[?N6mALԲ,SQ5grQ1S-Ӎf7]ty%D7 .5$Kz׾{Meq3'jtq' sȬ6t/$Wּ;oq)=V9;Ͳp?k\8qG%yʞ-u׿ }lUxKk~Icg-G9{oR'dk3s[5}Ev4bgbN=8۷t*5.$E}cFr\7煟@FQ7RΥ9CLHNx{ dKۑISC`N:v一 EoSǴ.Ѧ'ݰ3\N[?$rciVaN?ܙr"#ٱZ K!ҖJ`bg-pr[Ni.-],"H03Ư{oॉO:'U9P"XaWX$ʟ, z;~9o>/U^s, &.a \JzrWh?-C;SLvLYd$fA(BB0eg* 0*\k/9 .K*9;s.8~*LLB y_˵-8e_oA_aQ$j/xEY:.Je?Ów'EW +\{/Y"wN!e;|J$U(.|3Ej@P Tv_'!db `N_If9W {pruJ@X`6BѹH+b,boC-A Kpњ:t͐k )XZvh6Cma趴 dV,0aX3l3$#([k(!I ͐U)\7VY0.PNy $3}ɈN:͵H Qj/#/wߛXJ,RPr e!Jjw\F^ !=?ulsW9NJ!yF^@)7&(>7l`L#DxF^@!ϕ T$Z-@#3[zwɲ\O]Ǝf]p{[m2⯩Gw ד/<ʬuq'nVIٷsY=O_<|e*B[wnr$©!޸bqݴ媿稚8l8՝>^?o5־8ʐS%چ,'qm9V=f3O5'O{p.Tv_-:2.|չĄ򂫝BS 7)X|Gۯyܮ0~ c9?mBbe|{gyN SI;k7|a$[vV gO-̜:ѭE@r%0mi&.Vķ:H-Ο>%WK&Jd/"{'.mܷO*) X@H)ڗ0q$f{ZM_ʚSꐞs_I1J[ɣm p O:)NkeL3³-!w: /.deXYRݹ2*?m,jl9͌,zӘ]N9m. W@鴣jEa-˵;B yf"+3,= 3TA,d&ˉ)pȉfLIzS.f !OT=2GS=p09bgnq~'j޻wU %`Ѽ#4׮YSsJ5BkP0 Vwfti=Q6a!xnAw2X S hV6ٳ(V xbcjsR`*[!/+k߼9ꔑ9yd +r%R8In%tIrU;J Ҟ,AVN $tЅ'H6 ’Nfצ%YJt?&Gu0 Ou{t @)y^W:;&i[[lV&QX4%Ŗ6vE/Iq'Co5#<+hsen4h#HS y$l+1G4,sW׾d_sW 0qxbaYAmE 'u6ZZWյ2@d(g󌄶 SZ ,8]F!OBվpյ|`'^y3@iNy4\[["c]g+`f`}&?-,MRu0.0mq<%w:I?xG;#,#"T4V,kqk` ҹ2ѧNTfd*FPrRgufu9w=;!QP.Zg0 Xb+E`# GZE~zg.ً_I`N]@,G>3sf0SEDkf :V}f0BU3X7zq!SoFg[G(IJ?Df6%j`K&K@pQB!!LIi4+wa"9[}Lr6EpiӿM(WMq jPƺ ]Rɱ i[x&דW{2 %S-WpVAB=>魟L^g6-679C7g ի+g aڗPH(= xuu0PDwYKf(bB :3f2/Ai\ň'@ ~2e5}n6za?~t4-@V%F+?!  w|i~ YB(4E#R ɸ}Z+zDxfHH4O+2G{m;F& "X]g}Ͱ}R3.zLS̈Lި8~m &|ޡ#oFiN6ѣ atDQ[d-P:MLApag1 IP냜@fADF0D^]QrLD˕E*fmVN$JT*$DBX tDL"AUN%d&" A*@ x 9Xm"ART$U ; E ,O8z q$Kqlwb>,O ?%_HN\#O5!V _Ma>]d*efUR.4d,djQaɔ匬ܿjZNA ǧ@1K %bB4d4]3 {VCc禧@WXjai6"iH(v䳌{?36ql:(n`!\MX;'mr$G&c#\ң4`zA[˱`Ca\x(!faPl]}rJ 0t CE9v&݀%ͯC=ΏSMpO|g:&* '.֮ȟU'NUC>ў_yn{Oj]IOKfT$Ve؝^&%gGqy^ˬE*W0 TA`֠+5 >>l7v@xˍ$`$@hF` /*.vTAa.KLb`D/f@w,U2 .v=B*$65)*.@O2(F!;( =X 4+|Ǔ2k2Z),e/^t)] v KK$/E KFg+'4zv,RihQSO=]$֍1iޡZaJW(v1` >bHLm٘)3MQZ#ŅČq`lVZ .zL7VC;X,l,ӾO5~tݫ/b@b80 a0_dD)L5r.0Hq0cEi?38Vjub~{WL1kZQ[g +D*Nh03&{mϔ%8VB}"a;[qdAa1EAyIh΅ƭ^-V8dlo K$bN _θ: Wfz Zdmrhᦠad ;oq!∎я#^$.``z-9fLBD8n3iTAh!DBBג}]TZs ɕYi&R&tB2,hhB na&7MljV !a:$H5q,gUqV.4 ZY4RJBKhY0ݲ"8D2D33B 'z8_J; W2 Ml >%Ė|K?wxj#DJ$H [6$m~\l|"Un5va&[SY:IBNeF^EE^ҫ靍/H\8A;AaiOKD {B@'+$bLW=xiw;'\"qI>{:8 8A۱j ܅_sg(P7D3p)bR䭌p*PC` ;'^\D`ZbMb C9757+];%,s, bW\3Æ;Nup޻;V+-p˫mb0+ڔ%7^`g?N' ϗv%X.|Ԯ%Dͦ w p/v]J9Gꦊq!TJXM Lz FÔϙfQ'9:x?P`TFiQu 7q|)`VdXTw7"ULCyӦ:ԎVJ$JB!\J$P A.-Uك)`n_k.F1\0 Q ȬTSp*9kͩ+ᢎ8xD5)"Tk) S@U]kZW;csՊ'ͩy-)N8Y:2S0 3 4z|`&;Iɘu)[Z >>[Ţܩ.5 Lc"!,-Vv;3̲ldސ+)V^bkL?5Q?ҥ#BWݹSL nE~Uv㟋ܴ' +ã%~JAz[4D01O$&)YkmC*OSdOJF0EY'E'PD08HωcdbTԙ t Qj>E+co$˅pfn6>r.ksˣa]>$sHr਻?w`uT<<ƿ_S|][2.q]*;C#Jbb4=]7XofKo<}w-r')iG,&\8 袶;|2׆jQ:~Zw)$=jC\[=zfFG"?,!E킕y3""hB&DN7T5$tI`8LU%ln~j~M/ۀ"&DAG6y0p NiYV!D$TFœc:.tyIosSbrhnEv[0Xkw *F=T:qs?fޓw=Soriљo;!uVC4VhTOJ}d7ԮՆf/8{ w27!Y" %;T_Vr.vxgCknwS,+itS1 jg4,+J( VV5 oܣ7$S @Ec`Pa!xΊ fNz^zQ ,0L|g- EHzp//bJ f`& ,Z&^=@*JgPU܄o!B 5[pv~./6 ;PHw>>H0-քx[KF}Z-峮CI>T˹QWU(m!;gSiv;sQ*j5EwdmsO{ ۍ2:e\jׄrS4J]C5GgW,u(\45<ퟸ?pORM,8@4O4XՖofpc>S Kyk8>(,`DN(uI-4Aa߆MunБpi42Ӛ"jǵvI/"T %AҌfq)}&C% %1Py?X'oh֟E|`VFK ^}NB|#V_LJ<ۆ{[@~/4;!1XI[ee|7f8 }ۭh*HxQa,-=}n̿JRrGTdoW󌼷^]1N @:`?_ौ]N`ǐTa~{[T^zGfТJ+`+wէ'@ }?c`h0{y;xMm1ܰ_I{_d;֤4f}P XGt?l.Δl؋23!mz!j: -HV=i 4EJFH.{Co$D h2 (..nxS%'M߽*KFJ3_/"^ASo$R $,?1Fp| c;TI2rH ފhF<{a"S~S%) 8EKYD02YS l5dq k\S@>KAgnCa;`B-.M Ɂْb1ǫ?a'3X:q;Fv$V9@Y{){k7O:nᎳ'~]W_ M)+O3q⵿y~dcO ^D ;R_Jx疾v/d;^UGGڋZr"񯰫Ý|· N<${ɲIt,_LKoj;\V;D!a@}8.yOOm;yYIlSaPʷ~kdv~۲>e`7ПKKl?n.%7$&ݲqzcҳ=vfJ4Ka' 3uDge r-C):Szre6N{)ȳ 66BtLv{?VPX u; d'IYɐfNWٸJdXa[S_+ڬK -ΚbYx;G&#(Fq&-)A %es8OlũU=eB mGuAۆ{[I!W6a}xGlsVqoMOI=*{$T\1z\S'ˊL APwy*i*Aǟ ih)xPKt󇸍xWTdJFm3Jj 8rUb9Z'o&X`( ] Z8}:8hb"pm`҆:[JgK!Y◀i0NyԒ㰌?(' Ox~Cs1q{y?z>і~ΆI  0k*i`h4S+qH͞1߭(]VZ{V4WWo>"1{(#Cb+;gW(k5Y[k -XyyxWq _:pM.2jH*BllmC[0/Ugr3}[o=#Q BBPnI|բq}לM$#f/)$X!΀I6ɋaq9 z%:$#?sIlS~I ֠SX.U?;M$=xh[zg*| Fiė!8QY=5Kd\ ^_2/A.W-c cVnlPLnSuݵGф?SLzיjɠ$"KDl`Gegh4˵~'n@YGz,ᅝo&ؑ‰  b2˝6գI`#";L|~r) B(&3ީ\=#I@&h ͟'mX*օ ij+xʩࢁ\.Ks$%3a,җ$]IR"^bs(cR& 0 [ҜB Mv@RW$2yX? ͊M{@f`lgV@PBs8q'`5,qSD!_ig?_Gg|Y4E2#Ġ2LVp{#?}hd4K9X';<ߗx=MS'd-l({ouvVIYĄihD2a[S_~uB@k/Ǫ>HmvS#v)6-se'W/b`A1h`,`ݽ}~,BD)1LlXcc6? sNgF< Mq]J@V 6bmavI$kc^>4IDͪTt1bAoܘe I1<>!3>0L8#E=Vr6fۺ$hhfEb\*j5ԓedG6J$Әx dR5`.PdjE-hPW5w֢}|Y.{)ظa"a"J nTˋ}ߛ2v@_ ^3j3!+b:u-Zvq Hgdi XhY;Y\X)&DŽX 4QIpQAsЖob(t׹SC nO;?Fm48=t{{~ !/~H4;eM2RᔃRzɛY-߄ֳyM:!GyeNKtC\ZL :hhYCv8ӑv])X g Ao %7D 9ʄ@& ܷ)xk$ѦJ6DTsKlx1X5q\} Nf5!I / Kpj!M/Q h莀:o @M t=Z+;{eMUNyQeIx ZX6!`m`@G0 p <hsW`@KJscuIҍK܆iy8h-QAIR'ݎjȢ~aN+0]!:cr$ZI$cylI~t޳%n\'"a%4K)~ï|]?Z&_'L=VL uL dı*fqVc@ːHr2&;ǽ&I~3+m4e^VëuDø/KX yҜVS+k18m`5Hk!.rď;wU_; w]iO.tqKD{c0*SP "o.t bJ]##tʶ?{|:SYՈRa@ @TQV9&ʛ!Ae홎s*oNoތ**sx(8T]MXl0J4dfWzrpm@@0(qSUEYRd~ hgb9|.3oRz{\"ADrӯe8m>:t&Abf-Qy@8҇$+j[%k"* iÄTUTW Uzv?SeS~J!m:#Kr9D"jW+t(^=kpK2BY}%E2qSiأڃ'h5Te}cXes?vesc[lX g{X/GLPqO4'ZY Ȁ!{7<27-Ef]lTa5 ufOq[to9%Z˛X2[j+ؽ +U45䖦8)s-Eb!WUaOs8*""xKa675"[%8vx@ٔƤ(؍ź-b5U|P-J?[Z:0 +J/,V,qbVRٰH_:C#H }I(` .{ CMcH!u6)ĈSO \23w0I X,9y]iUv6CXnK|`۾=e2eSA+^w Va[Kqۓ)[>eSxיTGb % +0ѵɂ;};)6`Kblp@7@h;.)Od|9)pSԢoqڔ'0d0(ܧĔ G1 @7eG Hk+/e7ַ(.֦ p~a}#\D4`g !%N- I@׊1H$`x Yh Mt$bi߂*\Vim0&YRH⒆ĞS"'/j2 9:Nhy?oԊVc8" `ae  ZQƣpL6,@y_bދ(=(FUKm&Iuu} S]xHBpAH\.? &Qm&}?U?sbzǔ1ѱ*}C1>Ju8z~ӯs^z ;C%!۔@,Vx x~ Ï'26oB BU޸Z4EUx;d5ȩϻ{}NUzIi "Y 3"EƉ3:⢃ UmD@5틎q+^kpD05%VCp//鿺ak,vOW R>/ gVqsa Jyʷ(פ@ }JWlIO);#y'o~&uϗD V6YcV;:@53vԃ3IBD g eSN̒u<$$ ͌'*gA'*̒u~.5Pz F$" DR,)g95x/5, 8xA'abaZaҌʈ3X aHdi!>"h$DaltdM\D!ܿї)G8V.V $Hz{."X"BPк?CfPQe[٥5/kc1ͮ즤lIvEM@V )D&+pmT1aK=]AF-9B2vRXyFlIZ>VLԂҏme~!U pZW1c3dgZIJe@QF `K+qZCS%Nv&RTb (l%xHNLⒼ#!!(0,07K I$_;tć8kw 0%o0dE ߚ~OX3IZ-YYwT15Y1bX\+`h[, $8R3wBbo z <&E7+sV3 |Yn.E r%Hଳh2fque)`JDpt$bBuT/Ny;xv'kI/dwntsRc~&(+42%׾sqt9"p*XV̷:%>mgү#Qw%]۞EXhT#d @[yo"] jфUŪaa>3u$4ġ~[;_^-36T0aHqd 3?~E /w HNJ,v۴ k2t> ͚kv@ȉcǩpi $"&ϐ)C" pCA}ۨ߰-"1J B_#Ȃ mUtN!J 5'gb T*OP)5Xrz$&F2p)J3`*ok(!y+{*%@_і+ƀHBp&@$ )Wf)Fa5BlD~ݟԌ54F,>L%3'޷y,1df(%5E̷sɎK[̦81><3]!ne@0yp)(9 򣖋«U `Ql8ݰIozAgOԃ u3tmL-% ͐A@h. 3I/e JE֔0C%XÁI4}m=K3 $o)LDxǑvK2S||[G0lER D Bn6/}tKi(q—hYl6K|qv :C1, 撨fqYUZFħP"6%M\6Dv  x.CV!l4%+ .^+ܜfT2d2}j? o$^Ff^B T} 8 B0P%!9k٪e,5 *~@# W/Fn(?p0dJ-xu|fc0g=dd' 88zDԘڈi @=8?= fiNڭ'nƈjy`3E bn}sV?1L_@-+,@t~T!2PΓ$ېjDoŠ@^@B$N-&[@IUR@eŬd>&?Hesx)D$ Cm^ۜ*N5iz,#{b5|6 fFL!3̔6W2j_bxLL+7JeI@{xX8freG7>oՕ!EBm`+1ft!hB&# 83J!%d[:dFT:^ rQT(yFqȡלPPq&bʼ RT1,|8F g/3Ur)*;o2@<ЕH!EeC\$~A2XT=[6RچA_1q,ߦ0lYKFɹT7ZfkM'* '4mӥc.Fg+(ŨƾLOJK` 965;;ې%$oI(a՘S"ȇ0bV#eYDk`W5Ju+?5Mn"R1 6zMP"?t4i)+lV݊<Εmg6:ŐETi=cUxR˓ܨA8Mi9t`ynrRk}Ż-\֠*ﺽgV֌A(8.+Z_Jq_+ꖑ;"JS8*b~v?얕)Ex]0@,܅q\~qi$Л=\zȯƎ ATtkAO݌@ F,^ ZqmR$a&!+![fHX P!b\ _lVD 6Þ%Eh+B,V+J1{IW)XYiF%Yo^_Pd^ԘUfYg\ʉfKHBʫ/%vK03+UjJA `~D@/xInZ#`*UcSkU)69n:0@ 3`ui 㶁0w+;j[3'w]^Ea0H@)E l=ej^<˭r!jʨd,_ +VUqj(iUdM2$8*gSASB@+PՖw E2]HXaB4"`Y&R)0J`^ LhA_gd͛s&t1`$5|ۙF, R3g{Kc~7}*"k3i@+_r"M /XCĮj 1 l], dhNO~05|ۙ>̞5K;۸tT7t b)z! ż& _T幝NԷv.ԓ6ZrXuO A)\uvSSpLP8'.xpE( VY j:G.3A9gܙXX׬0^Vg= tE&s䙠DΑ HT ]^U<T %e?L93a@3$QP 8?WYW2Nj@0:Y摁 ρ6!ezye>MJ}6ᚸGqnƺmsKhk-S, K4tDw&9 Z0,#YdI[t,"iA\c-#Qf$KZ($Ĺа$2g^}&AJ$I1IJ&n") 9dىp֨AĉʤE-n546(iQ84: ~[Vb"d3uݟ+(D;7ʢ/_fkg^"Ad:'eM(@>Y׀;8HWYXb-72 itgHF#jw Ƹ#qB.3Yb hgɮObNQLb2fa^߶=ek}Hܕ)nrXS"`s~Ǯޞ"ZIW&m|P.ĀH"~A\;2H\Y=r{$b2ّ+5b/U̞mOD)|ell%a%D0jjoO3Emʝ @Ypӏ^Q>Y~ \LK8A@y} W|pB} !Q`acHʷSx[@ G+i-wd8(Bq*aUHH JBL8YF$ߔ| u*d(P4]?,"XB%@@12@`dB3R6[]x/:w[ H)`sD! 1,haэ w-k0(>Y1춴YY!ƫךײ8o1r~ [uꝼU؄ JPN2WY|j#nFnF޴*[:YMmڌb#ǿ>T9/g j&IT\ P$ ` Cy{zĭ[?IAO(+zljg09v 2pgc ӀFpUP;S9X#K~9 3xJ5#q nQqNh9Ւa,ZscHS_m3{ݙ0obe@,K,*͊&/5]MG.M{i2#lJNMԗGn,.`vV=o=Y2x@{WRPaA$( ڒ:wZW=f0.[FR)-gʞEM%DV%glֺY՛'>n͡LS"zUl:)8k!,>iFsJxUqXף>ϋ1VXʼn.S76Kgr6g1gUAj=S*BID56i)lũ(le6ȐnH BUN^jbqm$"ki5q؞1p|m eJ^Ѳ\APU9 H&et!CB.qw# 'F\!BD ƒ^Þ$NCcCa^D,3mH Jk\I]LTJ*d߹#޼xO9':8ٯJ']OPSi|=7bnu(O޾kRVzLDtb/ =[~fW\ۡ:+w`풣CΖ&7щLbh1i5C Ygɂj@IX' >yj+&VW$VYq}5u >oG_Urvk3~qɢxa۽*y<Ldf, *´\1$7*9#1 3 MQNB%W*YV;L@f2"5_MC< fjBdeWӍvbKNSO P.wytNY:XA/$2$'޺?P+.8 RSgrEA: &V? TJ4"`"jtCh:)qCI?3RqgAh[ǥ*ĬZ!`XRT5 {NjIP 9ZQB? {ԑ]zU"6R6[()cc ON+"Z@5iUu@WiK02#H?>߿ԭO m$.6z!`D9Ez~^,++iLz|@Z -&Ru"0{oqh`U(=+Ԅ5*}E;Y5w<ѯ 5 Iն?pc9UuFd횙G) tb%ƮwɥģG-d5xfTlхk*f)ٳuxz*M6vŁ{QaӂLUFW\Tȗ~FJ_QPHs !0]`pB>H'_3֯qb2IrCϽܬPE?'K(zŎun DwkEuJwMJ [Y[N :w3 hUupFϟdY$=vo?jQȯkb޺#YnsD7zϛ9^ C.׎wݽ<鲧_eꨕUtT$t xAq}rԌm7&* u+-BE)IЛ69k`i bS3j=2[ڽٍU|bnư1;(-7-L]Ds6ձ*V5.;r-$´ ,Fϣ戎(trВ``[\]J>Bd`Sm>U4=[.9XʂtoŮuWQC0!f;e14~ntvFpdOZ^[83_r6ԗl7C,BFY􌅭-'JN4Ġ QInz0/P@p vFvZ(}u6t@I @l$Ј|A8!NLi;.D]',~?{RAʻ&W;h R R-D'Ď ~ Kb g- d`0(` $zB 3ړ!2эx idąip!a" 5 θ'ܐF"zqs!!!s3٫6hQfkQftCarY,[C (3jH 4Bd#j,` Q_چTȾ٤UXXۏkˌS$j#kZqs>/HFz4bkWd.T~s;N]*-n(?cﷴ.YFwx?Rtqxd[ލNz}I_{WfKe&wzfE3!`-| ̂u ֤@sDԃ6=uz_CkӉ/81A2WlZϺ5¾f:1i4a`7Xߓ?Ēi(*he IJ6ONoa":`=nK)=x&'q :;&PX'vD{~dzV}4GwtS̊mkFD*[Fm L†m]gy|a+ st`d%M^ys6us! rfľ}5@Grw"ryӥ jj}0tKh**a^4(gp$pٺp: -uqFdTH M9HfjGY醳9Zgeׁ2uEFKWK7kO1*,g?|pdG@!A(u&; )wduBd0 (ݷa'9hD.E?DEy{tހk6 Qewè4(,f[!PFWyzpCwmA &RRDBtޫ+~7y,Lk٨H 9; OW[%-plwڤHFSw^|(ˍcQYWuo>+Krd.͋o4i$E{e:vZB-IjG-AܺΚ-S7ߚ^zWsK޶̠5ssiH 8`z9flԝn_`ۃϭ@&hmM̸~SW-M.^8n{|PVo.rF Na.iU۟;I\tϹѹ#XF"X|0}*"aGfcUxg\rr{ۧ/͵Yzg N̝ŌI}?Vh9rZ-ʋ=y3]7l Aq״$[,<<¾c*~\^Lspv[솣DjfUt`ZfFs|?XbcCĐb33:S< ! 䨉ZOCmyd,2s \_ccQa =i3ҍ+.V1GC8I'Yij8+17ƝοNRVV; X5UHfNҠUyO514eN:2y&F %8]'TcZ;,լw'~ C=޼`"s9A!YB io B>w=5#A=5s '漳V~6MRk'JRE@^]#!2= J RIK;f1NTKjgɸ* F1}g$:p.,%/|g ۋ$Xh$ FP@|+x .kt^SdksT*VW 5۔)M6:.2MB!Yz?nR#2<8wZ@~mo-Ɗ9h/[^0+3ڥIW1@l~WS?SMq/׾pc&<3f{ NcɇQ٥ kFJI}_Q;6ĎLT:_Kƾ0$x/ǁ]Eɖ &_oddeFjdc^~=;4 *ݠ8j]:aZ871xPIF2Da#_OԨeӐ"$ޤcްެՃT& QwtL*NC@6sfv!CܣU7tI"@j¯C-3}^bϤ28d2t Y Px5U|}O|} w;w1vD1w4e)8Orh6Js2ІrFǿ\\Gi#%$B_-Iq͒ƥ>1Yl2r?r~wO6E8SU޵1y_O[X<_sM"X\(A_lo;hBO;~pMd8qx5R ;mrdCB[ݎg"ULl h Y}KvQXBE:kNQ~H iU~w3B* )~A2Ǝ}ƃ} =ڸD'[y?IsxwN^_srTЌmyKJ5Dݘ`(T/,yt{~.*J 95 |=5rQNקjc,p8b̗tǢ,20WPTM{9,z+`P&JF;ƻ;e8`/D+w'JNp!k (sJo ㍖?:W_#V $peb_D.Hx{o\0|lpPPaB@rR@dt#x֝`Hv@^w%Lj)hf)Se0D7p'qmt{O cJ\(I5QVT9_Yh:"Wh<<xw{\@;T"UE@L08oQ65Ӿ"RAhM"7yשʻ`Odui"$W!> SdF\<9m2f\ <`!9.'-_~#Qݐbf!xA91"(4Nv`{=ЙbVG#H$5k-) ZcX wazyp˦ g+}d Bq6]^l'P}bF00wJ"E7WyvOtXGWf-$&"4jr\a/<(LSgDG#T !DFTNeXcypOs[;O Z:Ŋy,{bck5E 4$›>]nŢbEp81g4 -IZt፟tx;o@-+ϲBn[s10BN9v,|wly\/rDUDcq cϦ.Hb~e|L|Fes{?s\850eܺ $? (r4XU>~.#޻lN +Ĩs'1(d %*rzuEBX4җԯ]AsaDsm42uD5͊h&?;Z 38o|VߎxV܁3y7$fE&Cpaq'y=Y:z;JxG}*vE u|i+ǘwwMci,& =pmacZBi7ii.̲=ltMk0 qAdUZ^lAsn_ȒrپޮGʀ@lb<aW'sF/Z4oOO}m6-HV_7ܙ:%qJ1ּ'ItgM{8̙]:ܩɚ(@Md}~f\dU,aE|ҵ![-^cO78]'\/JV-b?Pgxb)dEZIvhXZ _-YBlxkiH+)d+2\]4`kOg#@aaG!1`$MG#qTFRQ$,N|M;d>@$ɒ* ke0[H{T!xBAd(L]$CV3j8 2HP׫)DA$V0 e!` rGurtHNPqsxtrD`은%w<vNV/8s܀Sԓ4,qg :k&JmEE<xo?  ]j{ُ&P`γޘ<yEVAN" @dt8}nҋ3N @%$E1]>í]g]~:ȑ4yS/ 5~嶇b$ær~āo"pnϥ0(fĦ6UՉ3[-Ր/0Fqǯ9' b^WV0ejY.PS!㑵9g/o_ot\{(^0pˆݑj:K#NYy.ƠClWmU{]~MV^68e>rkY$FХYb-o6RgF(>bD1բ*v(Ԭ^!] `KЬ((xVgtfE rLC[J64n'ӽy3H@L݈V+21Yd*UզT\`+[fhQ #Z%m`\. '%@0D Y'9.f7vic{n1&Or)oXpA)s_Dg{bt}c[̚M;IvD4) IvXJCW, H^[PܒU4j߃"=wOK 7U8WsttHYC5gOG'؁G 4#"$:xf]Fs]=06Dyb/W- m/{' Ş e-?gwt*B\'q3,LIrTjNa4 N"}(| ew(c( >g*ED^=]m9̗fdJmweh=s}[ᄜrx"B6rl()sR0t&7Zrˑ7^?MK$!7Չ׭oGçi% [n$o$[hytr%1aԬ ?\4i5 )h`Ӵ5WgS"ytѾiU޹}w9℻N⼖|!1+Lpsz-G X!Rhh`)i?^Ue:t))!qUc# P:NlJ_CJA;X KbYdbIDcC0 CT!l9H"#!#~O ՌR5 x>A_4q*c4Pv@?'q7`1Mw%aך0ӗc+Y M@I_:z,@巗,e-|8$so6]\:/pA2].B7dZNW#v9tl]n;tZv{(d`V8|??K޼\yV+vC8NBS9Y_WPcDv'6rS}.6dMCaIoq\v/OR`AѢ@D[͑lr͚̻ߺq#\"'E *0JxGj =Jjqo0|HD%ISJ UMJ[{餲WGzc7n*i u}G6{jӟifs;5UQ'@ޗMd[˲?]6*uli/#ulcBYJ\10$9z 5tQZG7Mp^Դ5#?Uo6I8{WiM-1siXTKzxnIB9f5P4nE"WL YHZ<\;fXR3iLdZa8zlLSoI6p7T)Xxd !xU)^wG򽲲z#{béR$yqB%,3(%!E|q>ke3FHc.LgI)]Q4a8ccl jKw6ӱ^G n8ą1яh*z T"Qj HQԎPHG*(C VU\1i-X'p)(\~E\h]>UqgMbQԣ?%xO_3,i濍e&:|SX4RQl=*Jl] AA1s?2ל>Nyj&-jl^cҨ`%LKIDGn\_;W3Voէ^`1k F&A>#w~7,9g3dI/nKنxAl\5Kf':^d:8YKpɛg*"D=2fY偿lj|(Ntwiꞿޣ?:Y1~f߬_ѩ[d2zb޽mԁgzU H*T<;?3_5obM=HFҕܦ Olnג=Gm_E"rX^9-Ok̼VӨ֏_}4YV(_{0ےdy"w`rOrnnW#V17G_dt]`μ7bn涓2PKzμ=b|CBE͸i"Ќ%U{άpGis"إǙEH^Rž+BŏZ]gf]d.1 b4;_)Ggm߈Ōvͮ ,ue%Kԏ@fgH;Ҟ9|[vӢuf]^ jpkywN>csڐ7:;sg3=6 Ie}l!z|?ɦV ΂?Jf ΖŅ F"li:h8T=HM 1%,$GS%(j.m!HaMD\m'" Dr8-[+G@ŔR/![ vSJH nS),&ApqC垷7vtUUD*Mܟ)r?{z*yI *L=?w z>:jMZ[#:ª?nτٝ)D]A+];׫U/Q 5SA)OMT=WS>Z$NVDPTIfsH (1"8 cwV مk ۘU#쩺}YO=Uc^9z6̈́ qcQш Tl(ZY.Ͻq_rpm#w͇g=a\*Qly}r&LKRWk}0us,|z&5TH*OV݉fB,a>9vlC|&ut٘cu{o R-M5UviGd~hDBw菾-;}ˣx*z l(:v|߿|nMݜi!6E`"8@7|4~ifʋn O]!oG: <#`ד_5 Qu4NhzʋfAx<w#-v[|Z]שɐc Ik7!T!= M${qsrMV7żdM߲)d 6O^*RJfi)VlS:q'_|]ckdA'N!CZaO?KQUbw*;ߑ\8q^܍$d'}ܟv80Nn+QCkG*􂡺_nYBpT[/;so̓JݍwM;ɗH/%Z©Q7-2Bv;! `2S$c͏J-iwжv޺hw]U%t{Cpÿ[R!G ڌxz|ΙW#3dҞs~n5_~uiOmHKU/l>=wV^^~]%]~ő >YY6(L:s&@:W])L>ެ+qC /w,Ӟ3£tھmR 􌿰jjOJe~թiwF?~/O53UkGvHoCRrO Fd~ߜ[$#=tӏDcBLrb l%C[sv˚lEK}u/wvK=/hrX??w;zjH= nI7:-*P(i lrt EroqȽcͭCE(Yy EۦIp*(! 2D)qwbZJZ;"@a`t[}6*7” *Gn-ޭ Zđi-Ugrͣi$G'lwBM;_-l+4T0䈕Sʆ2WᦝϕP[u.^ၵi$pg*.^K*WWs<;t8m?{=5U;R( %04:ƿzklğ4I<|wb{VX,"@ +d,|57.$׻K})z^'~ԐY cT-o 0vrq<ý㜣lFJAQhk‰s=LYǶ=޻ʌep ͦyֆӛ"\ZS$%-9W );z]Qyr c0|.&x{lʱPX!pb W&a(0V`<}6ln h!nmF?}'t'm2Q6^=cރm3W Qo{~*Ojv/_o1Oi6M0~wߡG{|QoIŠܐ[5T:鞥> fƾKo>\Ye "B&Nv mm]xlAHۗ%WenT?Lwv9_NujYeT-6Nؙ^hhFڌH#"D"/GȚC)If߂klOpcnQܐn\tNٻsdoN'/FEĀxl Aq̻xj2nhI2> >qa;6n<(r)(.tʓzm붘$^hM=mj,8!k4#}v=Օ`C`I>`\ظ*bax$fׅrs 'ϊ@l@(.+[@ӭ!|[ a2wJzs|8/ɩ5ʲ+`p[}}~MvbJbbmnܙ"Ͳ7k Cu$n\Ͼ{jp;S%r:ԍJa0qůU})lxF)3ur?xՍj-.z<}-16:0XVc ;DyR@jL{ڝ7#y+/L?:T@2áegҧ3o\8>x#lg8wRK[ ~i mc?kH,I[^ſJ/ 4SgPJ^* y}~?ϙ>NosaV@(d}t>o.2Ico1¬b$A$c(4S2Sk(w7(iۓW܉:Y5uelugꝥir`0HgfZ {{OQ&I߅Kox9.h7H2y`92q=mܤ`}ӆIw˵+u߼'>McMM)9>hRxZ%Pd MY ؔ"9hR <=4 ^@1&`VɸzĖN=Z%M76j=HdSsϵ1REIHsZmuQfAfM^gK32J>4׺_!Zn!a|G]~̞M?-R@.Eȧ{fqMקR%=UlWMS`jg7_o*"oHz\F)@S,:buoHz|dЂw~^{lm!7_-S*3{0I}cqð I#h 'qg8@YӪw=f_խ. + =-z4G  WJM"`+Ok^9x䊣_$+Hz>u|%"8zܣx4Pʥ*+NQ+>3u9A v/0<?T+/Ӕ ͟0>A8m᥅ь/VnjGXʯ䓰Apr&`` `x {ͥQgouOײ*?a*BM.F qD/vNHN1wS_9f=P**8AWY˜q NcTPKt[ﺼo4_s9 ӻB͢W-8E̙Y@kV⣆Мx'J!WHL%;_~7zW-MFu6jA>07C吖sL';gT+)eEC79$sN _R;`m~Sv MxA.&3DnqHpNI{?_zte1߸m .NOs. `()JfC4ҩ= 07olWvHx1( 75'ŋ*O=+X~U\ y> N" \U ϬcK Y1wۣHGfZ%?o@ܺ4<HvoN a""1J++BBg|;.  <:?:y֓6ɜX. ьȽ)6`Hr}nÍ峡1|TD}_:SP[#͒Ngij¨a`ȪZv"u1$9:Y7gc-Mf08 U x̑O8!Iɾ\2G1`5$ 7$'?Bd'bM^ڢeYnOCDuO1%;#7X{N[Vnk @\͜[͊l?Յ':s`0-D>{Gkir6jFMtD@ZEK &(%5B^H#+`a3DÏ {qdҼ8N7U?ZcESv`46Ѵ>Ƹ!%@Pܛ7@<$"!?A=@@g\NSUak] 0p8pZC*jɏK0Rr=Ԫbx@Us1 6N%#QCaXpjOUeq `1c\B  ė0%+yvw $k %80Ar~b5+o9Qߝő㨙x~W|jb>jyvA@2 `GLTh2xٟLq|bj+3M>͞k8{qW~%-'5 N9@r, \Ե&%w=}[OYuv6@2 9QE^tuToݙ_<~ _&JJɮ$ӬMi,{*<9wDr@KgC%xr< ՗D %S\%b ͂u$BݮcpLXY hQ -Xf,t_8IbTUb(H,߈'4qIXʈtK Y2yN9S2DY(Hp|Ly3cF5%$PpunD.aT1`WQ2g=i<8Xlv5dba" !xB5"J.Ba'"u I ">q#fIp0A#DZ%] H@̩ T  J 8J tP00"dcI>ٰ#ltF`Z#\b$3'?hS?"MxF0X {U`m?MJI7&$-^fI3î7Z`;?j۷D=c:%ݾޚig\?k >w,Eߙ,Kb^cۇk1HЫYC뾉֗?dD:{Tw&`4P **/| 8*{[[b䛣r2IFT =6Y&4 !̀C$\w?IF@c QRbF(Lb9%$1Uo $)jYa\ R#*%!J"&QlaUo}nko6<4(y8qt=T,65̟W3Iy!}71D z- 8f:@ sN&k(z'a_}n*K}ᣚ#`JF^Ďع[6Xg{eD A$Ą a6(v^m￞m.Տug&ĥR\{m4n|fg3a;fD$&j=W^CO،a}^eV;%C(s/>='>te =noq+N^"fRL6܎BL 2Ld&*F&"w1yE*'"-i V*m0C[ CdՅMGlZq|sIJr= KKѦZ!uVɸBAOP[;奿h3s6_F0ܔ  NLa}XmasȒ{1Ê!sTJ}-1.,H +Xk kG$)PrM_ˀʉKok$錬sXTx# :fI,G2J@B=E.2JJΎapF >ɑ4, YSGQgֺ?xWxg!6ݸ @A!R˱/G0:@42`5\E}*1[HȢ] j"">r2:R!BeZ% 4$Vk\fNnWo ڥ 82FjVL/k21+j )@7 ,!"[43 5V|rDP QR{61l"'ʗtX[W .XדV!Bb@`F,`^1v2/~vdR,hIJ5I@Ld))X[%/-HbZ%'`IlLRBHJj`LD7TJJd@=KޯBFC9\p8d5(( w.d뾳S?&64?]tpmٿ$>mo{;AĠ-^gĄUB/&/o:kc'TD.~nL{U6L*C^t:%D&_!3п5xObeb&-[un0( @^jwY`2 d@d *yvS$fv9VWBflCQ2DeP #I Ɛ`HZID4F$d8BMiꪗ g`>Kcs4Xj锜4j""D×weF兹I=/{|NjM-؆Ζ@Q(sܦõzzHSm˭:zEà̔z(@n`2b NLbOUy0 "35@L8s87<m pJo:#2CZSL oRPU(" ŐsX,p0J5rK]8Piӵ{Cd4Xd2T 5 %0 ޵I<;]pFR2+&k6*o#2Aڷx/DWdթYG(Og]W53]X3o,tJ6\џ/jvl>jwڂy }W ?䆺ت#G]B[gm'B+vd4hOF_<#XSwM+@0wVKܛfR -G!h.@?n=&֙aG'(/&NAc/04 6P nL_ 'Z L]&T0+)Ҳř8(?juCJdb.4_M /#R9o8V%pw؍?+X vjL,e.J%0P c ?H&@Y D C>$%<knHjaESi* oMс@BHvc [&9Qvm*_$:9VDTCE*0]1()EK3h՝= a-lZ0FF^VO#bQeV^sv 4߆`WW>3t$ 1̀T1J3iǓ= L%9! R>M'`lApsֿ E;Q#}Mf z 4\?NJ3Ňd }iTQHFq7~Gif$%-KJbEV N0;- X ؚKCly:7ȣhA' o|OրP9leCC7EM'*8 Ϩ:a@.fVWׄ6n"*8ңi  XƊi-c{NQxaygD'׏:AXy\ GlܫTϷpvV} ;ߑ!qRkи)ŖdՍV*8"݈96t'}y2k? 5A3Y[&;/60b8TX&ƤRsNG~%Of/ mfIī,cx Dێ% UeeA1$5(4EDDdF·PJq"0+42h4cMU)!"B@$T~W>ɯlm(N o?1ZG::G|'pHD2RZ9(k&x9,7PgF$fPʔ^㉜m[-a]ԙ Y;tiFY&ʄW9[!p2s ~sAH8؎:V$X-shl̥_&#/9:ӂk=;5=0s,pn~>>`T>nEYoe]lfOGk#1|QrC]wPpĠgqtY=(۠Odm,W]mmbn\wwی;F}U1R%ƯX kIqyDrlhz9J\@,[u#U7ԫTPF)@y; YvJdÒi/ ޖ8دs3zRz$l=Tn`MF~1y^=: evp{zCU7 OǒBEgGvvI($gA~ܵdJ.ĕŖ0C8è3 P0N!$ F7e`$N|9 G($J٪6`v!tLc]HvKC2>#Y\0o>1 wJasZ+eguaS#:װUC93 uR{ g]Oipt jx^KG=ri:ݔҹJ~+8$juGp3\fnq_ulL< XQ(W>]rB8}W\yh:/z~P13Q6ps8rY!ok11k0rӜ]%w==;vh2`\+5rEYtRKCy~KԶM+ n !8`r4] b}[(4 _̶ˉhu"Q F/@̇N4r _Wn-kU%%N,6QT-pjkg 94c鬃fF坢AoG>SO'Ԏs:ϿW\ՀHbTxb5Lj+), ls^3H8}&xR?!"+ u*u Λٵwe K0!rRf~M9(I/Ml R,w?~|/AD3&^jtvY)4LgBJ aEr.+0B~MyyסDvN}˔)a`wz%n˸i,c G>FD &m¤%plC&h!-CYd8x1@<:-1W b26 @q]%k;;= BWkW#U6C0u|z/'޽ Q`Ιڥ @z8 cZLj G3-jI-ף-S*[t&+e1S9? %Nͷҳg8AH`VAX .U^ڍw/]Jw~ʀ36f-5j~i.JPԹ|*V:ӈal eKv$G&InO?8ex1 M(/'!61hh.N' UB,d b7 DQ7g~1k-'/neF"qYQ~([Bd2PDκe1@ħYYY|5ɆN'u㫬ZV0 <|%Ħg Ig !  W2 N;X^."?۲c0Qg!UǍڰ &qеN7ʷd鄊iM\[hyU3ۺ X.yńdaw4ߙwe^C,L&m=᮳&v-1=0Cv\jS5hz&o8ltyϒtk;m Q4aC v^}bu^iF+ \Z&eehy6WG]Vv ⳮz֨+:rnĒ>\6Z ۼǼlg6*m\eZG`$.֟erQ srQ:rޑ~Jͽu\i)pNj^)y!̬G`l.J[*_xea_]YWhYu՘1ZCl(я)zç׾Zl.8͔4!Mc{:Z{XP*NJ6)֦դlQi#&Tﲭ͓bnpywX}bK> ƺ%Z# nVnsPe;6N°6DY Su#sekThἑI i7+/$l1xSXmprlFߑό1FRK_ָ (B} D}{81}I%WZȬ#X(`b50rzŖ-$Dp 6+.g5\Js0Cԥ-[Hh NA@~A?ED*dک9k,"jUTYu7?n*CTw?p9CЗ!R̂+YX`D,FHs΢Vys{YR'wE ?SlC͉Ѧ&Τ])F|ՑPHя_QJC-8FS'88r% S4n+YrYaFL݆))s"a^'v{(8dW}S̊Rjk=v,i!7yxyVX'[fp̒f6M2_%MqI9.q]ƾU:%+hM]cAjݓ"J뜞7Z ңQqwwJL&h Tf`P* ϊmu}4Ϟu.u+߷IRRR\.! H ](< ?qcSsmeIl{okFyq7؄#;ί}$kIWטڞwhA ?q nr )^F&7F\vz.$R͋:~FI T?m'ݶVvj:`Z,JదKP`*tƎfɂ YrJ7W<+Qˮ}ܔB,<,䭶zBX7JϨ[:[`(L,a,xn2&;K/[P-xd:vf6 Tp\cmh,Un;-o ]5[%fc;8: jc*CMkb L[fF3r[~9fbcC&7t?2k)B,l; L"ʵ sۏZQœ+H4|WG?V -ߛ|A*~9@`2+HœήDLzC*qXZ\Z>r}=exl ssSn׾W\4jG-WJ`D{h}SJ'dc1 @!ot;4okm*LKg’tuq](/ECщw;wV4dSin$c +C@R!1z}wGa{s45=˰P"˫#U-^ af0P[mK[جxE9qg]vnailqW7SKsfFTse"N0`q#fkZPyDC{,&yhqGWn8Q߂:E ׵N8O@*e`̾'jf[3cŃю{~ Of<;y5Ϯnt ^(wGYEJ"8w۞tV1/{Ƿjsm~/&V;mc8e9uΫG,UX|- }: Pw^̮ڑ];A xNkݤ[.2@R/2Hp>:w&lYf[] F#O%dTFȎ "e@&Hq3膡N6@aa$T$'tXG۞GޚPn6ZFHd2/;Jhlui&pdt%5"ԃ#e 6"'616Qg%c( 1'@h(,!"AT]M`.~Q lK =,;'9bW#hޭ .b}:+GTCU&@,MU"^ /.iuD&-]d 7:B `Zԋ-N#T9 ( PD뷷thxQΡ ^d2MQb4pEHJ kAA.!2uN*a*hdHJ3ܛJ(#}J bq )xsg Xß1̀zK;wu]فc=2/w.)h#fIuM >QV>ދ|\1K"I殭mhi7 3vv < x,Q"Dp:SYQT+@z49]:evɥYeAȾ<jT:8Uk<7EG*! `hI`[[~'C#X#dk Yq3i @]]#$FIa]=uO",imKԨ(K](Y\n '&)\4YK[, 1`@FsF蚐ySj0bi2X;b.G >hfzpL 7+@Kl 0M(2AM1ƪ B@,:.vQ )WdcoBghi~L ,P`I 8xZmJP`maAw9L^RPh-F) TFSjTi 1%N11 ZFB 0F3$S59@G њ0up4~~{꫄ N΢v )PC&: Jهcni|+ËQ+Mzg9aȒ&Wљ,v<ONoT 46"&m,r⃉~QtO/2&)4# 8 0/=5/{sO }k K*sڡPBm楿`Jޛi[)+RJG-$4O5ЄRGC_}a7у.Y^?߉ j4M0Ų8!g, [lC(T0(D v\I(67d e]i$O |njԵ8cQiU`,=bo%%τz7PPPbõr߬d⹧j;@Э҄r-Dq1Ccq(jTٵPƸU| 'Ŭ3,]b$GPTRVDBM6 Hńt0҇$t[%+BLm.8-"\T7**) Ci`HX81"ihV L69fnp,Ī03p9Pe8,¹HDXB8Uqq ELgGP "iO}>bɧ V`EKLW\Xː UPKʊ_z9L%f>!WmFHp$ISC$3ѢX7Hz +0!%vP)T5uz2#H` q{]# ic#Q|x$~z0 @[s'a(,bcB,;(vPn $4d__5)"In$n@b%>]sܔ+޷);`- ."Ma9 ޚ"IiAc,+9*xb7NUy[  ̸9o<ʊq.퉖Yߓr*0HG4rBAC;|NK N |~9/Cd dds~;~}JhưJ XqE;lW|:}ޜHL"F)JD~-E~-0aWxs{P 9 ToOx>N|UuP^¼7Չ%aI0_Fٴz}sql٫U1{l"a [$Ékv 򢔅0PVf"*FD7s!Y(LEu$4?_([] +- U,PCոo>vB3yiM9ZB9os=Sx)ԅoI2{PMk( fU2.F 5oP|Mu)45=ANX=T֡VK 1O2,a-Sl#<P7wΥM-o5`Q;g0 4ct:9.⦠ z$SZpv  t+0QDP6P&3:ݪ@ԫm&3 (;;`{$-NYՅx/D\\}7]=Ħ=O{}#Q ցЭRHbS͒<"3FDDq9%;5YZHfG=ՙj:j<RBT9n6$ hӎz^5K*^u~ys?\UE86_A0J$`ڼ[ζx+׺ (EQ0HhBDIj>*&3EpB^tٳ5[ )fRZ&EtuhMP^KSZD(6Ehԍ*EEՅD?3 QDB ^Ă4pc<2>aR^TKGMk/s;6z!QĴ#ڳYe4K "@޳јC&ts X1,Zːx̂%"<Dy]65gI(5La)i4H\ŇJ#v13ƒk :iIZ y r( UeY`$0!5* gB1ȲQ˔\Z3 (@hX XXI5x, UQOv*T++ҬG*WaD` T}y]* eUI˃aSs*쳉Ŏ$C$q 'D\u~2xRlYhO󐑎B/M 믷qB1MI4KtEƌ[T)`1ȄQ"YB}RSd| B>MsB"( {?qwY,i{]_;%wb|ao;|.?(Gyp, `mX: a)ow|4[qhKMG *|Xs;zeBE\ۓ>)f^ :-.j{'D!F(k&qxim`9u\>p7hEA1-A۠"Oe8=:M!+"\`7dcB =oc>=oYֽqj 48OJӽkq}GgdF?-lpl\ϲ)7Tdž*xY#'Ywam $%5Ľ?&W}5уRFCB44\(%Vh\LNQ!N^jgY~\K*J"sVfW ›Fz ^8re%HQ/XUR_9ZO 15)`aM$+*UF)CGҁrD?W0٦?Ԅ>Mrg*tpNTg5<)TI}IK^`RH1/f҄T=[1aӋ{? W3/^vسl6t_z3F8IyJR%:W^geu~حT&@۷ϼ2snc(Aקg%S_3s\lmz;YDU&N~Sg%Wmf򓧍;J 1eKkeag63;Ov; t;W"!0@ kOzj( n81< lw=ڊWۺqtniʢgN}Dr`yeS0U5TV,*&U UW(ٔE xq9?,l⑦TfPFW+ފBNZY٬ζXsļ/J`A=lEcɖ!} &Dk-l,YɕgNdJ48"K+x{j^윭'bG`Qf M1mv ÝnkB$}}W4L(6VB]H2S D%-QrL:L\/dfY~fQ EBЖ:@lfQg (fS~d%X\;UJjv7hR KI H3*Ƣ -Evʤ U܂W)/әdĦdY< ա(y[ b6[+kݫf2}w@;HV+^75g]>3KyLWfu|LCe&p~jYrO[~?^(ÍtF1PBoZr7}VfsyZ~>/?ܗ}լQKgk?1<1w6 aR-t\&f/tV(B#c_, 4&:2I,D4XPХfI3<@Mruj r}pG5D6s4wm˶uzs5C%ڸi+²lOkVh~-CR=y,u1ĢIkX56ifhQ#2k0PozOxH},#s'e"9քXPjCzY=jo;_vCKAS$k<-9{Đ,ԉNonJBnP%w|: G3@:U'WC jzdX7<'>8[=Ɨ&:R~dJJ a<4ܫ8XvOVg&1irtB+ϿfF fHZI6K;vSkԉey>U 9vvv޹n^G)P3-X,?mMB~ܦu&AqZMq(2 BvD37YK2¡8<œ .mi$ {03{쫬'*yz-U|AXtRbE[S P1#[YAA(!_?:wf$1n[[V욝5,i N$ɚ~xߞ̰7|WGFH S#6*g gw¬(Z%ZH rdRb#;!SP~SYE 2Ów+hmV}9q9VOp90X=Xnt}^t,o*+T\1ie-%t%*e r^UNƞh yQOXx٢wv)C6V';K)p'2P]btT$R,t ও]ȎI]v+ˣUS㬧0')>U}^dCRz\=iDgv[ś&ldP?uiVWqhXҒzXf-oX2gp0yJg.L zJ2Ѱ^ŰEevRɚ|FܑȰ9:Ƕ[ǃ)037 y1X(}b/4e񑠫k{) Ffm}A*+ -jp5vpmҎԒ|͋C\ HXÇvb%Mk%w@=}tXN'YG0"ԑZIO`}?Syv`Ri˕NMJ^Xzaھsʥr\؀oܖrތ.X4N5_(ܞrP#0ɧ6M5Fj%ľ B2;:H !]Ȧvj0i?oCsQoVsl:A祚 r_>Zr3/^f۪1kc` <ߝpcϨ%>S(`|HVʒ9oyw>vd풾=`0/`,NuB225F;VLl]AlKG$DD=3j Zfdiɭb x$6sY7|,L%O}s_W! rG&fZ' DPKOzUԒ#sNV|UKN8$g?ےSKÞ*SKN}H͹Z}U^œ*KCU ? y󡏶%c_h~2qWk&WxdfI9jU6:3$r#mOim9Λ Y#Qj{H[ߠӞOv_=.cus"$俈u$>tNc9b*Q[=r= SF.&Mx]Ǐ,ӓ\^V:\`iW5ØILX{nn$ً(o6f"Ih*:?re"DbDFٶ+/?0W.# 2[5]3󟧹7(zr?%!mף}4}W֬ 0,Ju/|nωU5T (һ +q!.;-Z=Z؝;1(h:S~J@bXR mo;E" K p*bmq"Fҫ];LJJ Z蔲[(,*O/!;Zq=0i@$!Oo nTI_|-viy+׭HkjZp O$/]4־!BYhkS߶v데(%P4j* [oeg^f*]~( #m3bѩAd~V#qkmK+oy9ƈxv!qr`K&։G>8: ƭJC':G?4bOXBl F?K#9ᕴ68O(Hl4B/4K}Pv:%h%P,v>0[Gҹ<V'H ekwH,A ]v#"SNP&跧7ȔL!9SdL!YpqBR?8H;;{J z|=M\&C?IC 36ɵ@.>j7`=4^N.CO؜ζo٭+9ڀ3g4tlZɡxC:ŽQ.>lF{;>u:L2=^yc\zX_Ry4 HxzJRS+lt5V_I䝯iFݕ{+v<}I6yNORsC"|<_y#gdĞh{tŎ7nsz?SIdO{!3_U?K-JHj ggtԥ.(K] fX[ 6Γ#/qߌ.wn-u% T})@6ύ8IJz\4qJt7Mݒ6}ɠ+?ϛJO-4=G@,Ĺ7[g`e/y[ϭ_p޲Br?kSI]OبG%L㡐0qKA*`?YIoQRȂz<|$`.H fZ@-KQ& MxRXh6 <),9 ilP{«,@xҙtؼlKosKԥIzhF-vo5{"3YAN:u=]v# ;^ "f9+$9WWɩ;1z &b}ۥ[׏9RBi.,_?r0_{ˤJ(_ ,g95Nmzĺl1%#2i~% LƵ1/R*;X'[P-ƨE6%/R){X?-M}ԢwvURJtl:fHAh_:*[RnQwtvyǼ {~߃*d&_VZWjG1i|]ԒU+}#EW0l=z"=ra6"N~_qHWs+ ILʟ*̀F=hZk+ro!21UzH~mq{]OEDROҫU•reXGR&ϵ*ݑ*'~p3lB72&mJk 9 O0@#@bH1dq-wOJcg@Te< \ʩ jyY ,xN5HM%pa4 M'7mV6lmH/d{?V:60J7TښuZIpIe^,5'N? ru\<OWZK0 C \J1'ԟ!Vu$m=kWԟgiϐ)%'ggy4$gtmjg[Ϫjmfϩ՞ԏOJO R$ Km`ZY[2:K4U6HJ,U_̐DZ60V||UȨ]8'V 'h%{J`B4c5̈ =wzM.Zc(I ŪbmkP~ =RXnID%Ga-[|{R336g1d#Äܪbok_~ =Zrm/=4_;f 2L:j}o@*vVf #dFC`R dp7"hD XB@+aQ ( XW4USzKh{%zڐLcb~H+YĞޝ,17ajzIDלFn|U?KO\ׇI8UDU-:w\Pi>R}INJn E XEؙܕg2(ǻEGp +/%o@cKlG9[rpۿI׷1ЦA%!C)pԒaC@uT!QQBjȩطe럯m{n`Ep֖O?FR ե ݒt_ô3H.ymE)[Wm=v`Z2tcRl1O6*68*Xx›"OXF~{W`R`R.UK $_ T1 fA=kTÜMrj¢I\Xrdb!N rayI\_5̩$h QHNCDC: $J\}2IږX2ukRZE"kIto\ +TK5̝RK &Ȃj(RH , B^dž AC$ !i=*h&A1Bʇ0aS 7.},ѳ|ZINIfM+FD*A@l[ aJhD4uq{$>g*@8&`HVDnB$d@γFUbXK#>B Ĥ  N%R[90wYT!IB*FA+`|]Yq룢& QpZJsHSjrph󑿅蕃.Paa$"s_-FzpX0/fJT#I(άX]!`HbI"Eڲf(^RK"~[F:K2$q}X'3)pj Xh/iG Ȯڒ] TA@” F6=-$xbEaڵ"uU#lt ŧt"62,²Ba` d$Bޡ=`P"&1; "D;&ר-a&hnefɍ: ?S DP7F-@b:1)(VQyuAZGqE̲z>gX8T힡AVҪ 9O$JK МALXL/'?^0B!F81N[vNI&!>f&A,P1 ܃b,>M|;kn  œhx{~o*zެ}q3[t( Sh2khܚ<7aU$3Td f[a^Kk||zVlq%I dBYm bh$Q; ӴAE0^;\2~Gn *eig5W=~?`d,_ꥍʺR?΋%/\^nuJ,[둣:y6£6E=I%9dmUiK bk}Y\>Ī\6_TDm|-rd6[9#]ehaQEԒҵȉ y*j\5lB=]6:vuh0ES95d#%\cRIN|O?N,1R:f,aXAU^4O?pS[Zҳ.XcAδ+buA۹WEuUu2NG&'v_1PϣfcZ;aѝr.sR2Ң`EQdjWf;O[C-Ob1cp0iV=eqq SE$JXG]\Z2»n09g^i n %w_n.;UuYbI(tjUQek&XZ80x׮hYxfI-W,&%.NۮICM'qgz,qbWO?kC.&h,,f߷m(Y.oo |#_&[Aw W/Ւ˗"ZnZͷQ -K*7HZ;t1` 8 Q PK0+Y ,p!8%r+, #)7\v59P]Gx / tVX ]6rMbSdܬx]sxM6+Y i9qFF Т}cc"lf4_;7Dd$瞪Hd L˿SV+S<+Jp2<&:v J_ᰔN 8,w2* @]ॳAgemj5/7Vp7?&ê55#iLMg {HU|c*V͸V1YcE0S 't8&/},cw欦).#zSoFR[`jBRoRpc<H/AgGR1]w4zkkasH'\4)diy0#uI^5L%Y* h:DZ I2:J8HΈBLIFb͕R1h piз.s+(^6Z8dy"eOt@z,S4cY=bhg;.)NFBpT{ 0GΕ IS^dB0(Tb#OQ! %mr]J@! 륶,T E?H+<^^w`lN( Yjxnο3QK(!)rַ<%6zPPā,tD-e=Eq(+n|'@p J31w)PGԒ " ÒoȔ3l H @0M=cw66.)P': 6RU2w_Hv;)($IFGT+Qf8 "p)6(-u"\6 lZ֩L;>s%-mƁE~ӞbХctFB;11k avb:۠ |!r&gj\o4x:n!R:ɢN`8OSVNXbeAA;Y׋{.iRe˫ ʷWGG宄Y]U,.q8QTdЊDV-cYiJ$-ed8r!s w#FX抲+6% ,LSJ_Ӽ9#S,Yr4QK|a!m W͋M>#dҩdk)F @pa )80zy: AN#}DŽat@} PfU/4\v}<,=:kyܺ= 91-}xNMR^6':K4gTa\uSq_m9ХgU!٠0jrԼ:N6_:d8/Bs-`ڂlhK:实@V;ƚU]beQP"2GoU ]i:Uj`az`@a@,c-R52 ΣDH8援M ;w\;ēZ>+gX1KF?etcx?sA:K/[]Nv f;J:VeOp `; N`ĴIOnRa( PJn|sd^Uzm643&+l+;Эl8G&t>ArB`QKӋpM*RwFw'/*E'Zs G W^׌TBh%YH(,^,Uk$5eSĘcq!'hGK5UE/wډK8boTϐ;">ݼHe[J `ZR{?t4i$XK\^R$j R:;,Ax]5&˰)R9m7jf^-XΧtSqC3 *l^H2Yײop(y 7WFaLsaةg yzM#Vw$XSu(06]4k$v˖H~faa6"ߋ$ɓd2KGfC.v"ŦfxM 9R3Lw1Ax@H6 ,1W%"_&X1%h:cilg\R ]m#)߹eyHK: ctOnHn o#rf7%GS,)n 8(7ՕsA-im 0߿I\ohpҩ oͺA7IҜ0qC,`F1uy6̸+<Dz|>BH0o`߳/4bqbjd|/ЯLns<Ϩ8vz̵Ÿ[ Uxd2݂nn{p+WnؓCceт Hzm۷7XJ*sѺ2C{I_ÿe3GXi*WvJ0{@0vHؤΪ"mіZѸr^-yD = Cy٤3qbuc93c < ),2G_>Isy5²$HY=r 4kԤ~˭Q̉zb`FF|fgE9v J߻L5OJ{yqNk9De%Ep9`Zɒc9x+Ϟa[fwvESu9}榛YgNO=G@&I7toj%R?:)L:e?W H$cuG?~A$؊{ Cdr][+"$%Ql|1T _x 4PݫXhs*:m-"'˟:yH#X׫gteeN aQ͊œM|ؾ0pFT^yN oX'f^dɊBF"ō^\ى ~< C$%8# DHlsIܳmaYG$,bF1bJIG [m%zr=?kU_gA Ǔ̧ecRRn(P Ϛ$#k=c G&0W8lꔃiL\jiwЂ{-zu0fWNLihcs)PiVqU=.\J!g0aRP \(%XyUW+,.; EQӲX >6*wZh.jF3,HT U%J?ox-oHݣqu6/HjM| (Gٍeuv\?c@}&Y}$;C6 $U5U{u^M)h@U!2Ǭ#0JjY"qBDخh[KmգwH {~Tx_m}ae' {gf٦ޠ_UIWl%5V(BzXǿ6vYGkOZV_Խڋ֒=p@ƃz{YGb_[\cvXXg:XW#ھ$40D(]B?znr-,dʡCo_iI9bIeD4 tųt9K+P9fЧeE/$Rg!Č:fi% LB߹ wEH^ >K n IY-J$^tKO4eKs5/ ƨ}k^'ٞ|wWLCP|pm7;afKͳl]]4A 0 2;dm;Y5NY3f5Z4 0Ϸ|qMlME|wx[Zzh^zLIL5(elLʠH"šٕKrC<]Gb4ԒMCqj1M-WȦ|4wG_FOԣ&ݙ ɞ%;NchPNEvRR2U2C/{O;OBO0 =g"ܛԞu_Wlhy4ZIgsge=m%o,I.OJbnv.Szd5 "&$2]-g/7+FK?4ҧəw*܃ Zd=q25nj_=HJFN8nGfX=c4_VQ:6T#k27^Ҳt"6\ag|8\'ADUy«OD!]R-9!N*M3|Ʋo"Db%$AGF%͒_$ o{pk"H֑YVˌ_LOyR~ǩ:v0~gȕ_LM5'D:x rjT_LMpڱNc|V ӓ U(0i0?5|>682#]OSWc"ʨϽ|-WmωM˚˶!Ufy/!&f-k_hI%*OiPugE޹1lBd3Z'PJHHSwNQM$XѺ% $O+[bM<c_AA|.|X] YiIQE/Tjs-_),y3=DZj@5.(rcD[= >0t%F`Sߴh ˇgjw׋HO^N3W'0 f@"2x$9o;9zF:b "W7:1>I2U5VzVڌrte; d+Rv!^q~[,Ǒ) Hngdo{:/.E6KM ˆ'dP )4%#=Jr$ްIi# 'in5Ī^ZjOнtIhDj^f*<rb9 vv^nQPª rc@zrY*Ñ<Z%uqک\sKWSgZ$=ҊJM9pٍ֒S -/[VnV _x+ l!8L PIщİG_>/Gc86 P䖇նn 4鱲uK7k_*\;[TK ܘ#kbЊ}n1 5c&L=QOۀS11PY)gHbhGAT -=[VsRK`lm|{u =IաOoŠvoKlnmЙ$^ڣ| :4XTZ<*-_/mIЁei'%#Wܒ5%YD2:$ڴA, ˅NXFO6MW*ypIXdMl)ZI&N==m ^Z0a3y ŗ i%ǾO?ٴOY9@TڊSHX W3{]u<&韪g$=oV ŵ9F-ٲ3Vԭm3 Hp~ !d'wuv/nZ2vȦ:W+, ]73e/^o>ɲX!\X%9N^Ю'x"ع s0ZG gaJq@S#oQJb "{|4JuWwd[k23aWgrU޹=oOwCGoZUćə]$;_2[ZvUz_Qy9mf S$OrU4ԗ#( ^\z7OѓL弞S_E:q"7>ʁ)zɿI7**^):D&K#j!g̐!?/Կt$~ۥA) =m~jpjerpґMv:6U]:o{ۅn:)t]ބT$αsaʩԨJ8sE)RIy$'$+a4ɟcIec+4/Zgfv>қ$ ^l)EHͰԙk%4yDB|"Ek0nLv.[P zZRANJ0=`BiqGΚ{DcGa T¡Y.mۉ~-ztFKQ1ـa.g_ 4G۷pps tV\ttqQO4j;S/mKߊa:ڑxL{u eHQ$iYUKB7tK| 3/UKD`oqڬ#X,D `Q_4sf kGD%JNٳNx!5n\T\5_i D^^p3i-G5tm/L82O(2zZrK_7лHi^P T8g=PutQ' ~<#9&;.rooH\xff_tQGCB_(yyj aq^sS%L~}-ƲNgFnf qrdD^^ { rf Mej-}>(=15xߐ&Y Uo)[.{wd:}'KP ~.|"'|Лb˟}7*~J~_m/˵(?ǞX>ٞR[~ASP` ԛ{l )=-?f \sdW7/T_+jɠsWVkIoL[wd$%?x\cҭyJOjƵ6D)cSS%4`w>Dn폅b!ud"3͹Z7*+ZIz%q_҉UAZXz}z_$vFq{+o)&z:ȱ7m|BOϤo>|I|캠Z{SG 0eq3#|gV{ -6ePR|, ީ͞ܪ9-GdV7vƳ<8VOr[7bZ}[ g.FE <Ձ?}ߕ]X~rIW>EZl?EgF_0UrpY *^ŬʡoZ,)l_Tß~w+SW?Y}j7T$F$ ΏzϕJ>7ꞙ ?9j{!HDJ 10K/!7 6U& [OYb3H]d3 +;_t8,&}<f ңzVYylVNtZQX0hsQS\#Y^7sʐ^Bu/֝D y1/4x|:&=p{A[$8%m7]>IʯHJYBQ;O w]imZvU㙤) D|M {2s`PɹqhR|}?@ɰB?~MkltGg~p+!qGEXK=_v|TW#RԳ+훩+z Ndqꮴ2S- GuSI_ P:0\Hq5{6c3u%oŷo%RJ"qoC;_.|h=JZ K?.-ltg%]sIdD}Y?RI^c=:u^!W:}>d\e7&P$]M}o;oojIͺJ!Wo2,aI_cܶ}^+X PzMOkԱM1?X #!o.$R?;Y.C He 7ېXg[eYi$5}\"ȸRq~r v{tEo{2ӓd<{۹,Ԓk} "ؤds# ]`IJr$O C}W~r cD *mLtyIwVN4+J]RH >:ˢf=eي /?= /ƗӡSʃO{ArǏ~gl- ۨ2CA%wwgFaw>џwP K2*8妏]>dѸ+Vx2Ԭ۪?d'gC(@@&آ擃$F]{L_ 3uV9dz_IFD:tNJL]E8>_~eUEcw' wF9`5 "eP{ddڀ5kS0+&БTnchRz]lwX#ARr‘:men11 Mc-˺IuKb+d`#. ~}ɝgμ34V~Ma)6ʞ^a%)9s5aj5P{34zJWb:v,AOeӇh3_\ KH&IJI|n޷3$)$nL{L;ÑwKEWmRJ~g׫Бuܥ֝j_5* s|]C0rA5|mC+N.=5;+;־"{hTゕqkRDTn[NmI/ռI%|mE+ԙZҿ5ەw1a >'k*T ^צKU^)%פ<]}:UR-we/u1{{6H!lyݬwuUݘA J_s繧;~z{NK㱲n?s}փ~{ů6$_rnod|YajUf.>5w^tf'ٝElU9>mI:]l衿ƷmKcƿmK]4oڄ獽>R@k ux,4{i$Hl 2)H~q[h8kDԈ&N+Bܭęt85b]|f]f`}1S`m 1'tCGO&UeU@Xw,k~WP%YۗIɇ l1JRߟy%1 *>n_+`̼@Fn1@ʒF*/3tX;=N$rj\>[W\e06ҕg!YRInQH%zՂzC8ԣ'R-I GY9Җ5ԍf-.Yl Qj$^-I6#

a!YCZX.L \m*ZS>?\;ϯ[Cͺ]3j'6 Msi=@*%"$UV $I x\XM H R``>J ] [/nkF2XK]@1.f Vݑ(fm]141pK-(DiFXpr򫦑饷_ٶzw31ޞeC@oᡪd=1ο`1+8+>wjeU.wG#c\[?,7$}:"ՎQ:p<1KF! |8Ұ}jtG+HZÃ/3>l%ZYo!/pGs/^8~t*%IIȪ=*sMxb`ԗ" 8#_#82tdVsRT_P:Dy57r8pP xȭ'Iυv'L2ĖK3A ӗ).@͹?ꤤ#茢VOV{}?EP4C 1d" }Vt*b"HkȂP jWBT bhUPᤄt!QwF]>Ni%f:nz^Kt[Ҝ],AeAR"+0 jH4_*&HFP]0f1P$`n3UW( q BGCےqkf$1ck% !~|!\G5Mx±cƌ;lL ژ ֮ϔ,Qը;m>+=`òlFעs%~ɰ7G:|8uu7Z  lIۮipm̮J`c\teJFITk;"]Q2ZAYVV2RHaHeѪPwH\Kwk rk)1A$)DdHrwI|ZB8_fDc\<4( ڽ34PNif/裛;]~K-64>N Zp8M4-ki- `(b~J5;Oך]6" "J|퍢f]?O%b Hks( x& ]xlP(e8-1ĥhlZS$43#nbK$E`&@-X2K4]l'Ѕ>\%PY`fD#!8ʈ#CJkI*l^ʼnMd*IRD“ğ )Z!Ɯ [$ΗYe׻,c;l Z!rA%ë8 70ᕏpC3J R #IX@SO k296WX\~hq]d# X=a$(L(LL²SIpQzos1)zD3)Ik!Kiqdvg W~M(/=$Ťaݏ>ĊI:2Ckc %nMn]?c` * JJ[S$ 1<~)XV)J§o6(;6r"fL6wNE|5X&7TVr/gQjYb=8u+J ݀_`E;Tx LقjI.S9sÖ+8[ Wf 1Ims-XyE&YIem_9TҘ3KcS_+_`-?"2GlȔe©W'b`-: Χe8C\Q,D- { DbtG;eU>QYlKOO`&XXU fƕ3gτ,VY9(i_nxPHXobAIl-5XA!}xk$2,.pF3)]OaQqFb0;;:]Vy}#kHs)*(83L^nB

EAʥz2DJXX|| H[橊B&>. Gioդ9*RnbPDEAaősu0nVKPYV@"$%%C%Jk $a xR ^ļg#InzͿ| ( ^xC*1[2m -?hq^m`1C*w%F,=w-qC/:+;.JG>Lq=Xd|GWJ{mXi6tx\n(UuY,Vθ!KP-i;s3<_qNTK8[ :eSbxXĭU$$$&HCa}s[B(W;*ə7Db]8g\MwȨk0si}hPKj[.kdeMܵ[!Aϗ^L==%ǝ_sQA>;V M5-s 'º0ZZsVzÂ-_K"U9,ŽX%ru8{G%i׵)Y$Rc{4=y[9֕٥;N" DASu%%lnmZȀl)aTv-NJX?pv ټC #C&3Y@0aS,e$%Lut]gߞ޹D`fOJtX (.8.v3)&> s󲃲,V) KOlTdϕ4F`v@8\G tjQI( A|FJ,xJZQKM锌U}Ze!B9Zau6Xfǿ5R!ٵkb?.LI ' $yO{P y<PCfY~`+VYcG&Sn:Qz [ `lw\Y=Icz%bu^9oUYp)6/첯rMb|5`< CL"@Dq`VJ\fΪOwz˒cِ5`@K8sZO%&T>9E kY<;S^zǟ\6j͝Hc1%kQ FEY*lS J9lmE3iQ%/$; 1%+QGmSKR:4-MPFt:D%Y!XREC~M-1QKQ @C@v:jź*Hh0mjID-aj4󧬘'%_o L4^rl(i Qlc didTO̓kaDC'yݭJڡ|ԘN4JWRG56dս*VL0ЂicVkO_(;y9Um[5&ZZ"  {BۨĐF ELʺ5MCgh-Zs&Ffְ>1^ʆa j`/7Ü9B633g.RQ`As)a8(LI 9,e5~~8=4`͔FLl7QIDh &%GEo9kHbAC{ge7( &ZL*9(`G.|$! m83N#ݮOd` a( ^o%*GB{IErFBd²$J%zZXoHhFpsВ@pxK{RZuxZTPBVJ堄 EVĐ+َhsKꨣ&-!K1‡!t} J |㰇^S{vC VG77#׎|"ܭDzg!^*YYQa]OV4,*_{zFm'C^˜D<؅h׾6H[#J;u!c$CNa+ۉƘצH4M`xJFN:P4wgo=ԱiŠ},8f D#-W#++8j`?t*E, i-m\`[ӂZ,Z,=xvi3G-'CKӖTFǚ_avGDbsǡH7H$!che Z)}Zr5x(ƥ/@L$ CjKqt|8%9m!PIB2`Dd8Jǒۖ]ݳlT4 <`VXk$EG'|A|P<`E:^m12,b^0iS$kpL@)\jR=6!XIDY$cTK48fYZ8GĆ.> 4_oR!Oʓ 2d3&$b1R[zEm@B3( &3?M)#d TJ!M'B2l( ā27q4$4w/0¡4QUG{KFW] o ̓I7S PDfĀi0U†|/.60%zSN|_Fjv[4ztr#߀=!>|FRҿY)tS+(6o^gf5WZ:e+wwoU3鱛yFq߆DFcSXxyJ6v_el40 Zje;'YY$\=99sY[`( HA'\1?4ui:CӲ֜e벌+~xqUZl8=<j,TsäT/n7LJPt9zәMG`dZ_]Qe*Osqä?5l/ngGtlNTzhp=iT,n`uDDwKFZ_SV!eˇn"ڛM$77@Z0f__GRЮG7n,ْzyAm,O3FKaIy_0ƈYMMC+c,Y^X&m6^bdLh QV vgLs.|ųgjxAc(c6ɘ6TOUU;lx"a$`Fn3cP,"Ao)3=W׃Ls5oGcݷDiM '{;NJV.A:},六t֟]$,uX5)؂g4=};X %Kr|'iS^4|@W p@ݠ*էiHJNvr&L/K؀ Kx,Ouި~zh5wكLK)8!- :  aB(;~UC?u*1vkt #Z1Khc _pc_2Is:yR?^ ʫ2z7VFw "uro؈X `^vCJ1#cxIOx<H lxaZE"R۴hDz( bv `0-`I48HZ0XtM&IpfLWe s^"RlfJ8;[Z{/-+WAz?/Ŕ}!--2 bNꉧ=Z#^]_[U{;(@ |'Zp ĄKWaYŕJO̐N.C<Zp!'aU,Δ< |wgVs|fa:t\ o 4L+.B7wC7}ڠ8 Jvs ]s5FX)2wtAҪuUWnٱICME5##|paTqzFl7g utܡ J<^3*9V<)y)"Hk[,;l> */=exKg- +{¾>)j̿ue+f@]]Q:b婽6w$ŋ,S,q? -uV^9&,Nե/"b`INH~]-M / x9& ōQIm4 檁LCxq$-drz|c7kn fYد9Vu>6zl'1]hӵ3j 4jqUύf(͍91굡'ڱSaQ©![/J ]K3Gl'_TPUq6ԭ8[h'K 0tdSyKsny}SBJ(G3Ϲ6$R,zl8)|H[/r)z̖ =SȘ>~AZHyxusduF}}uHx[nl; /leTc ur7Cr+F>1lg.Vx}/)`Q Z bH텭%(8:0R%wKl]m{zթjiR" x9$+V9fU4V}/)YP@1•]ʤ9 P 2^lς1FR{@Х>[ :Ak.ۊ5]kjnrDϔ]־_k$ ,|Y4ȝ?eUOrۿXe.Uːe/W O -IvV |c@_!AR+j϶˒['5m^}`#馥ΊkW |ZKV/ ֈBDiIidKkW~շiFjBPa f6z~`_ҵ$|$*h{%Y`Cd4m+#4#W)R@&4kQON(;XPTdfѻ=I7?h&nĨ5Jw}]Ve9f›L3ӗ6ôfҍ<>*9|ՒxveeuќIS{-)N_jF´KɤkO7y,nh<宻H_(ܿGoN7{+MVYdb ɄRjV=ncv4ZoJУ>z6EE6NO$%ܨf=5/ ROo %46l翤!IcQ?K 9|>}lI6M z7~~gQ?3Gg!ZZ3)9 ݹ;<^g!jCbϽ9=>kfڒdGn>_vN1j&>~OBݜL!v.ŸBf7&Y  n@bOl/nBHrr0mE-[A ާ%*WK 6x,YX~gkƨ$[/[A|Bphls']I$aF /ڎZ<;!NED!9]I:4-X@Kg<ո/ k垿F9} w#G{Ug%s/s=وN3$) {p[o\~J;U$=$q^ZcĨJ_ ZA^4dɬ-+*_N= hk[M Q6Jf:_NesĢŃyNEr "j=t׳&vO1g0K9҈zMd$FFhSufwOݘ c[ L)s"HMhh5~ڥ]FmhuԲ+hu 7Ji6mYL 6_ ݧ?i$3fhrֶlgAi\<38XXXSIOr}G?; Ƽ Iۈ:L")=8N5^ B\C![ ">/Pr,Jq[-_?A[^u)ݞM{ j}hCk dz%H\ŀTMâ֧/v6+fv Tk%%.hj($gjjVME2%~H@M.YRz?n'j\%KO@0WJaӟIf9j|F]Ye7<bVϬS\-g+?c_$hx5j3z?^T@TeGx4?7G(!ʂ(H(HfF$4"Z$YHF]g5("Be׹c_C-?yzo>|pe8qԄ4/8ZRoЦQ~bDK?yj}r'T^l#Qɢ {<^Te]7Q)%EG6?] ^kdʁ[>H{{bw|9}-$YSpy% I@.".orFEۉI@*oHEE; 4*Y U@JBr$ߔXH Kd,N}:y D$ެ 8`e+u ЪMJ&yO*3" LF!v+zIŜvY7 Wy=0HT@pHɵ$iozQ%ј0[QH':uS9*\dtDSl(fzҋB?j0 Y%prpnIE]1[6:6Ir/$BYShC8* s1YWW7M1MjI7"FF?Ꝇ?ՒzHLYmIea.&eG ݇{ܥRό ~C%6# OQ?Plܾ_#5Jyj%&u1sOnAJxܳnڦ5Ɏkv {bqFO]p_#L/#NG,@jFB@`)!B̑x-FՒjnIP9jL/r(d)4J_G>)-2pDERrI[%1njlC-KCT ?Y%&o*)1L>"E{:  _{UW$($Q X%SE :Ԓ@\x|>ߩ+:Re^8 ""do 87(pBf%%;5|C@$!@ F<j5=dP8xg_zڣI]  &_q{$p|ٮ7.~H1J+$ťb=$( uJJ1q=/.Rm؉ϴ$%RRwl࿻ҿiVKe+#<'kXM|b$%;@PnIyܹ-owS%!YZdO]&$ `W񳅹3t69_|~>IJWž3t$d=$6$-wkJ3lan A%ʓfbe3s `?L%ѩG HHʆh ~! }t@16)6ƳPK6b5*xP =ojbj,*  6]X5b t4D(SdMB8HC^H&a/LJR rT;ȶ`Og:T,}Sf 2ҹ i'9AkDO_SDkڌqRVsyU>d~[ρpTFt$9/Z"ִv0 4u@DJd描wK{lD~ kSp+Es/}rJտK-R/ߧ<7?uwiB6ȐsK]2vj7`ڇψJ׭NΝK<-__*Iqa/Ս*A>B:.A-)\4oӯ׹ͱè&:+w9uv숨JޓOw?ήAݒk 01ELQ؇}]9.Ù@Tzm}tX.eu1Iga%"<(c@TGb%-'Xz{w-G{2ͺ5"NȚa-u3g=lߺłBPHBLT N9 nXϤ{j߾g+eN:T">pN]t-x[z5 Z%oQ8&VIPM yO($';E_9:h4vx-%DX纭g|$? w:Oݩ2 \W}?)igm)!é,$ @6[:^`.b|]-!j-DٽbUuQHQfTw$D6#iC>(ʑPP)A-:Fr9FKذA]+EQ#dBv"p)ba3IFQ3SƔ4". ^2ڳXS$iM`IdtĉıV&$airC 6AaU",J#Y11Ĉ Y#$Õ>q*6< )p%C q2PJgoL7q#%h*oyJ~ʤ۶:7/Me_&I/YTֹ+zE&T?#5d# VJ'Uj׸$w7 IvRwܫ}'ܘ4J#!0bUsH͒ss/)J*Jcc [Aܐa a)~j$CD7!bX$)i7~()YjVMC{~Cϥ'mRglscfo2&9C޽ΘF'%E\Y(v !ɘ,kszu-+FS$j$hɒoMmPzr&FjIL( 4g8Ozpi='J5TY_ LB\\pBSw$:s׾Vx[7 2҃iGf͊' Uvf |.'WtI@:,u4ط_|2u<-hS1p @Y˝Ο?e^}O԰_J5W]g2+ܺ ʻ -U͊`T PKV7|Dnkw" 6+ I!ȺcdEG;u3$,a-V!c҅}H%1WجT|pج9SRG,]X^-! 9B|ݤgsRc6\{<_p#ci+FMb2F[}H%U8NVZe0wi}H?걧1fJ:K[{!0@+DǛ"x*ǚ+ `dt0Tp2p@Ԕ豜h!trEjRzhQ(WL)Z,J=6GK q CVVH|MVc|hI:V%4O]0zn;Z/dQU)pd}O=z~Yo/\lK :J )!vd|jxje\Vcd"Bk!Ir"`Gc)]X O!ulɸFr_w%X}xD0 JrPZA֟|E IO$JTIŲYBr9[5,Yźx@]Ղe}.},!܃|f'Rk-s'IefМ9eܹճ%kW x Y9R( j/H6_eV-ZS7*ɮ9Z=؟L d08P]kDd %;Y%y0Vܘ5xBj$Ó Ht}3qb5N*[$6%EljMcӭXCoLJN>XNLIKfETSaLRPO xwKri\D: G&d53 1<3DHSLvϵJ1^3`sH#BX?PbpgJEVfOלv X˲+F:* ɵ gp_IJ#ʹ՜z/]'XnECS2.dl(b&»PjxөA+;eݙ-κ;&il.*7pmDPF"CH&AA(Q;8TY &zZ+8uaZcj3JX\j+=7@Gs 7d9!S؜ 'Qud_\^(M&cbkIL̨u5x^Nq1~%*l]ؓZ[YԺ}_(H IV!edmdo;j fUSާZr~sƸK㥧Q2wVK^/ 󝇭[΢ycghB~ˆ4Eɸ5-ݱ3- S>Vޯ, Oe)șir$AFb[!Uس%Xw$!hH$BA53{wfɔczJξ<{1{ϙY̒)jry(uL,u$C r38%G9P=DyԼl7=l<-sGbY? `CJ I7o6KG:YHqi INQFd5{{C:cu @0q8#f W_>NH6\ehqtZ\wh|a Q$).6ͣB4#<М{+$: J&2>0sK^H_mQ ITd!ۊ$ir'>`l6#C.yֹ)UX9[ؓT+j|$k Z_St5˞brq"ݸlKo~l(NS2] f[6*aAt` Kf/3wb3Q~zGjӸQo+~ao~/?d|VV;2Ho~oNF{}<ߨݶiRkwi$WfO[.ZQZE~/ds++mOWm|eʺ }m:?WWȺwu)gS@u!doJrYF\ŧ(C>lUi57s 5/\[жSgc<-Z\K/A~\-9dwnR1#V߅MBj|WK6_[i\1+}m܉8-ajg%ېNiZGҏѺKڅɯ+ 3U }b*;NyqA j,D} ysm&J"^vaQ`5ԺDS[q''hb(=Qk Kz,NԔAjM -"NSCrל%gKp𡠙 ŠCr,tAťD66=_Q- yzfu Q[9Z @hU=0#%dPI4!ɚ>ߖeuFRP͇22ǢT Hy3`CrRR%񔌢&aҰWӘw8U#EͬqI]XFFE~sX_v9)rV)^rCvXLq6j'ﭥ+IQCA0uox.$[q20%g؉|VĨ–%̒ AEEPkXo (f}s#)5LɈj^՘RUvOb%H4%̒k! @ZgCn K.HMך-NRE[ T)%IC)$-huWbДĨ$ pR@$B˒Rv w#/R|JTF'/5W! :Eqj 6C4oUA6lSc-Hgt\ AAh[LPCXh8sX(Tj: ^g)s b~M{;3șD$&m[CClb)+m쉦p=zVizƃC662PLx҈88/hĵCoú3 "V n|u)BQsaJw- _~%M$Hv, %$8Ȥ₴"I{eP¥E2KG9>/fhJ˺ibsg\S$_y1t;SZ:cMΞUK@ oLTFu}-Vvm6)1$(7Lۤ-0deYy7̲u]>f{dE4֛%c/y-1 !)D܂sk7K.y9+P_!>" UG= E#B^ }|R?eS߀nKBGڇ4  {d_v#q+kDGwJR9A[4F$dq:EW:ʒp:cs/M TqkS4~cJ^^p옝LV:Gg8ȹ =Gkjr2\˻6S6zT#qLX>)SmC* R%uA?Lo-2. x/W:їnս *xe^d\kUnu(,A]KP !s#n8V' jH,>v)B:->Tˊ'c11\,2x#ĺ/&lfd)#CjhY'2Ü9GRg=R#5R\Rke$HYub4ŴY?IG6lzhefy"+uƶ~V?~G&IJGwΕZ64ܒ@$o(ǻlY6~6 n-Vk׌v.8H$/EW;U]ğz&:D bØ~X"}˗fvvOĘO*[D%:m\ui(E 6$BQ /XrDCns7X a,{EFwV.g59NߧJ34k:|r跗O%$ɏ^3ۘHf*.|?Fm罂ݩnū_I uDI0;eD xPEuZ+wJ~RD(b}{c 1>tAEuK|ig~Gr]+Z@ E^;^}"ddBfT[z1A $D3fVIaWbZZE@#6^kgmY5 _ebI'޼7Km'y>8 DݕB !k@cL~\I7k%(lP&Z'\6yK?UD7'vxۯ:)^>݀c*pK5*+==-jf3JbC',;;'M뀗1nXZ\U\Hu'FnIʚMLC+PrR07|> H؎Z`mh^!U@Jkr4qk䞟{~a=xJN:9\D7 ,2I}_1 yʅ}>}u;`fcVDDD.Y~sY*ywUT$᡾ti #R%== Zb#|S o HGtc._>[\GɋB X85O\)q8Z@[97*WӞ2طKnbيx]sAJ@D] NFam0+xQ8Q#5b/[AugHsVddTфalqRg1B,ZP*Gj0h\4YҰtEje( VKF 0m͒'@Kc!&?s{&cbBC\8TG BJ;Pt}=[gbE@gK=K+r(yx+1 9LBGk,<Ώ8! Vx8 J9v/y=<ᰑ97ҥq:4¨KF,Eb0yo0a.^RVQ;G*mQFԚm?Fb1_ wj%Eù6F4*ML]l|?Lx@T*ॾs'|?ɏbmԶh$[*bN>ue~|13 [e ZAx֔vg37}p׳@R kjʬg'o^izt)ZH\nt0NjD7L=%U'eyWq]@+̬8JwGT [tKhFJ_R/Rt/<"iH0zK$ KLXry""`.\m3w?]!I-/ԍX} =  k Ww$)TuE7ޒy7ꧦW=b [s| j`Hn|c"|wc,,hiV0_X&Hf>-?2*%1<"Aմya0T5kU?uᲒAϥ0fc~-Y zeM@$u!Qy*$$>::8jC5;ܒEgB"(B2 훨R‰[C:Kg;᪋_ĐK0c' 's hc^)4 `"@DHsG<魚ܕF2}D&BQ!W[uRL#6B#`N٭al 5߱L hwdQKֳKS'Iħʢd!2@s\v.%i5có7qnGjq/?!hcF2++"jI{OϖMqܐӒ"w%/D$nt~V}I͓hUJ$LH(%7R$lo2If TM%g'UK{*[67}:h>#0hV8z1fcJDGFUQwUhySel~3+# EcMo=1Q)m-΋ۓW썯K^}ֲ&uWmhQӂ3䅏Lv"}Y+RE|eRd";;_.= =Iz Ɔl,3y2Դ˚o5ݬsWF+wLaxjS#V5-iO!iإk勊kRۧn\K '7.ZJR>y~iY*8<1zoHO޳qKofRdEH6FҟMUio.G,\v#>WgR⥵VcG6Ɠ, \;|(uz QocjD"צ«;XxI:Ru`SzMd0an B~13tmngW G_Nk:jO%3gl rmܽ^jF?Ѧ+yFlQyXgKrHk|k^/?إu)lM:ڞD4+=sORL۽/Ӄ2w?hy$º}=Tmn_<[ M +W~y MS͒я/haڬHyG̓wlTdO]:è:[Hu91+WfjR\Qfɬ+tN`Ⱥq1{z558SW'LrFZ_9k񀮼HgkG8|i)Z{;zw.Ř,=ANx?Ih%u;oRVP!{=yI'9L(ԍ*[7 ̮m} +ήK'h4sE>a0o(IT+ytWF,p[I1JΞh[^Y?m9] $πah2 #/?pf?v=uڻ=[$ʾh\Fn=VGr,S;aMC,R!4:ak C}SHeIE! փOw=ڗcsإqK`B_*?|Ǻ4 /Q% L >'~YtcOpɵmWT,'4 h#.;4n #GFdџy n]AMtmAOky} ѓHyn;J3+_=qT\"0Vzڶ>;NLiؤ[:+M~~3z`p#g_i:tsi>$E閔4z'Ovsk&s7U L얌(\n_涴% S5eZnK[QepnS[☱}[jE]\Prҿ Bj*} т'=sGT}0ǚnskV07(+`I]5Nl󶚰-2EOۑa#:}Y"8l4Y=h~5̥}{/)I4Z!8?1rKKnßDzUL8Eo!d` -@5f~-OH!2~jyev,`J[W eфqMjZbwt$x]-!PrS_ρYa4ё|ц-D+dd_$ 8BNZD &|G sK3`L9nxQs׷n+/˩$?a"]rdF^pH&PuБ}SOUzdIt)0r|Qm+ʙ kC,YЃAP]3RhF%AU{0WXU~B7܊N1n0XK9v}hiwt$9-wB14Ao;-0c=2Y{kT9w:=O`JjU[Y% f~뉓][VjG$bRZp$ĆqE;ӗDo\} >Wں\{ L3R;R[`|y{l('Nv +aG( b(mV ̗ɮqr*y]y%_dgi-jW-VVe8SgF#h+J@CkpW? ͅڽs +bkĞ>yJV1 Ȓ,4K*]2kӥfݘ YzتYZQrAb|$'yQ@~f!$zjWi~ڥP/Q cDzK:pF:3pJ}NLj "Dz=QQٺA3Y@wsŒJXXɎ9l%qIHOR͈G10qgjrΖ]K]Rܥl61y%JiPcmZ[7ږl7ݤ%kzKBnnIa݄dk,❕`C^t ^#8z$;O}S:KdĨVX󎰏m!YnTѼ[:Xe9}0RHwM ڲqiqմ%-8,$#)xk?!dN`rځ^\l8dMgDheWkr2TX%[J/n`FF~3sm݋=aUuq3JBes7YN6Tr| LW([F yK -Ϗ}$5LF;m:;2o[A12NneT?dJ U;V|F=3fLqsYQx7j~gdg@p[{S0,gs4Osxwsu*9-&m>?ІdeR"ߓL*XVƜP;s"=),%n?S/ CGTi2J[%mi =O)qkXΧ7g I¿4V63O S'%X<\Vvݻnbrg R*DbTvH8ٕ`M2 i?2R{W[v³k$Cѧ2 A??^x s65Q:9$38bIu 7Fd]Wm[6>Xzue~߿o֝PCn!d9fsT/$HFp,8ٰ[thcIj C2b%0zHYI %ڸ}a=f[zI!HT-HRQCA0lp[cNAQ=@46[H%!怒uZ -MzㆶJ&\1b,~URz[pʋ$#[fHj/y6,c,Έdѻϛw^!0/kV}FUnQrWg(Hڲ,:}/? w`ѻ S9" S64/:报Cdc3\\UWF/hF1΄@tH\tؗK<ʽJҁ`,z)}_[΃s݄|=Hʖm$:kcsn9WyݼöZwe礶_&OF1Cftgc'y<3-fh^XځP~faӭCl`Wٺ}|5FGRr+œZ/rnm{0}X؎O=>5oQ9 93,aMi^K$&X{`0et{r; 9\pf)~`ik3|r4GW+_FQY:j2eXyf4ZXJP߸g\4fcgJђ #' ii]a$FM2:Quc#+C!D:|'Ƀ1 q(DWKpkPRʨDvu:XxG ߨmCt.H mhO  Qh̅aux=dd,J)OD"[@_EEe]t7$Ii/wF'ᶭ %y*baŢaVT7=["~癇#=ܶ!q'vŢO" 5i$CUC49cZq"2B,3Bgۋ/EjpC7|oÚ}Z]fT|>1n[DH߳3mJ9DPT{UK W/?q:ϺJ'))lO=2Vn_&{bf92 #Y8m?ٹdo5/_yߒ;|SrIRF$9\|I{$%pKS PIul+BσKfmegi:o$pechGG룿G {兜>IHq$~)I[>2ka߸N|e$z݂NTӾ{D{N2yMI6t57SH_ \/Jzf m;Z%y irz3o=HU846$}h%cS=dPCR$Ak$:5+a -0ǃ:4}uN"5P;;kH.GrO{j l+~\J08Ey7SHnMՍJNŇ|5FggU@~XtzDsTy΂\'QtϘ# ]o" tŁ 1HW'l]j~ё콹+q v%9A$J_ʓ~ёlpgV.z "oVEy)F2hYK1)Y/fMQHGX*h5JժEMIل%9)كz4F_cX߯bf!blc9wWwLvhG<]@a(bp9 089ھݧsyjm˼3WWԑ{vF?{UOו?|5diwzQ$e 8?}@B/PVeD ,ғm-rznjW_k i!uPP˒Lg/Z_?RXC|+ԑTbGdlQV$MHw& |fۆT)$@ фKL/^ک3E8"X ?W P@aX2⫢# G`5 4g+8J+n{ |9_3뒾v?`,f>=ǖ.fo~oA-ҹ`|$~?i0MEC/ad꺎7gΰHA|`$ب妕Zɝ߬5nP"6 Ň:UY\QĻJIdrp9Ww{%iӉ+\IA,e4jF(kacVRLL6kȑ̾wYFMrj%{ٚi|?t|jc3ĺKd^-ܑ~sTB)dl #"qV1=${WΘwU#{]"a=Nid~}WN/TK'`R0:ܪNoPr/ixo:l{~w5{no_Dj2y]LmIj75+Gp0yl9tmDVul _0&Xtm53羵yg^#3 zJМ([Ix U dA_ ~mD>GC0•i$!*\%DS@$f[hȳ7{'srUy =W/V#)^ rj^9}}53 J/a:.m|8r wiIHዎtj$isg_QXP@@dڱ]rv֟+<瞶{k}P|ɤ"'Nˣ*՜||\0l.g(ܕHDeibU>8$M?燂, V8pr]QԒD _ ˬ5ezg6/cÈl 6_vgZƅ <7'`"bVs1fnPIϲKkKp8MTvִ,&$)$N佣6uGr<rMͤ%jluh"oDxoU/lٟR6 Jx;˽] (%+ аO4̋ę,*rX`Լ"${jT,vg `w$2\i(}Av:)}dlD}Z^ -)$X*Y){-TA -Y> K`x%>#^4ɗ$zX7ک<Ҥ .бQw[dJJ,Q1ƴo@FC]Ԃ(:b bS$ g_D)b17Uk7?c=fm!E ͪaVIUwZ&dZO##Ţ;KVF?>0uFbgFė [Ubʁd qD)L5 +߀#JLI4)QJN/UܙvHDRJM>|;|E-bnsgŠ=h19c' MDvw;R'!, = yu-Z8]Jyqbkڵ)oب;uLS\D%i6 #Ţ/-(6mSoq/do|>Vŀ"B4"V.7jmɪ1nX|C{#Sx 2,"nqA)猪5ENJ]p&f"?#=Q2P?e ͔q|()yLY0:b *Ő9֢^;x0Vr$ ,RJ/c-EicӖ Lt*ܤ֠L]fKNjNU2*cL)'q%"%5S\Jwxk} *%0_oQ8X`Qa[TZt'kbI %ea66HZh]6^$a%j C~U>~c^vgB. DD&;̘LO% j1Yq$we "3o^jqũ0HbqSAL.+H"X4[JORĞf,Y$' հFKuzsKmk(<CgLj[,üVoW WXؗ3 KA=-S* "Ah-jRmd$+z6`_R-!cS 2o@ J|L8v>32@3pD?_soa2Əx!8d ņ |")`8"7 -I%&b. Hxkђ"-"2W9 I')NLaɯDA[Ev!Wgp`ti3t\6ւ\Q&GjIVB )X-Pi\-@,4wjCf''d%S #YwJo__E7~k8_"\ pF|ltg'Shڱ۹v.lq~B"%!U\_ t 7 Yo g3j +ȜE4j' RϨ%G@K2t(%gj,SSBL $&̊$ } JO 26t%}rn.6#:р1::NR1sB2SH4pK^aU <F@pM"Y=-sCX%׉a )LaIGek@%aLq/pvBI0&;dQI̜{/9$n:tV0ǩYMO&K4ǵon1Kb,$de)MLhLB߄3kjZ%PUu}n[.9lÕ/CiߌG_4(V܅L9'gKIn֘LgÞD:[I6X"Au4dQ$+` =P[E1Y!)ʉS+$d!+)lYUg .ڎ&BY$M-DJICN LZڢ1kY0&jI1fQ$ytL))Q^TQc4n?_1hrtw󠨩͗ź Io#.w'hN Q'gu1,)_#y#a(CZ6nc7f/\l#J 5Ru:]*:˴i0yky u:%SSOeܠ}<:|jbu:$^*b@݉q. 3Zdu58,0 v wb\"0([c4!B:/醔7)[n?aT=t#n/s"proz $6FH|Q)cU+M-ێ{Brr*I2&վ_)Tc "jk(%Q+%GZ_7lO7pKqhmn)x:g0%l_X֢"PXY+x3"d>1զ+zo}ys;vˀkmW=srߩL pf(;&kd8&mn;޶xl? Q耤bM ;W5ʡ&꿳1u Ǫ]Z%@:ڊcfc`A;'m6fnjs*$h\0D NN("uػnɌ>RxJ6)BI FzlIk<n&{*u ʠ@K . xKĥ#9E{L'iӁЉ )!4kAc.36euR% u {b< ̤4yQһuTRYSR =0Ab]Ǖ-VjRL W* N*Uϔ7|p6ɯ[]+xm^eVtRf /FF`o89 lL7V5s+06`Z%0b>?C5.J* N*wn5ap)9ź#|, Vd;9 E3JYŭ6;t0hd GnzPT- *nglƚvXw7n7ZCR #vD56Z)93qdqdhaz`ֳl+oڨ{h02~2 "+zko{t6>ܷ}JZr(EhH*Q5wMoxlԫ:]%h}B,&ʙnNjg=*?$w/ԻkaP׳WWTekg1 H/9ucԳi6iPIe(9Ez*TTCke#ٌS%׮M<<41\+jڵ V9C0mmgm#?0I`C1xgب,7mDi<<FS]bEZbNW;>'O0MB'k NbckbܖR l(t4bmhW\JLb LtRCD_glm逡^Ϫii -00RZB Ί1mce2ݽ GP(%̣N=4$uI)'3LeF̒ g% VazurE[9zP~ҵ"0((N0Faĸ:gQsO =Z(eqoBI,[ͨ@[כގnSfn9ɯI #a'B3Ak4WEJͪF9NHH&_H"[gnE3qzp)&Z DG` *Vh)078bF@+~(%z S 0$h-)9V@ȹKf 6Np``( sz@N䷷,i5vcQ/Yr_CK,ع;QR޵8W$YZW`n%Y;՝ulջȖ@5jYZBx*κ5\cQYZ3e|%$`j@) Գ``k{$V RJ=3\hKYW}H$8bJ5݆Q ԒR}[o+gIQŭtZR{Tu!hYPkQ-AR.gA@joH ӮW"8ՠdZXn约Գ h>q BtfcLjJrvD-;f+1)!BP\D =ȊEs5(Gd *1U4M%yxOU[5,'y| X; b)i@OaD똽8 9Vy: I$=%-tSƼ>y1+^τu|J6òh2gslN*ӁήM}GCv2Z>F4 }rTuL=.ub`B_N#) Ȃ#uy.:"b՚UQKaXd:/؍gHΑLf@!b"1Ul>L\";>/( >^u9ݶ:pUHԋm6)cKv䵦G#Z2Zk3I$,JeuM4]کDv4-CM <̆Dp eh:@ܦ `٦lhV$<VE[@$E*LT8KvZ#rDŃ.y- ǡPKgY),@5p zWHSϢCdګt|;Ma)$/F/j$UzZb%Drb?jy"ew4z*:ꘈ$_xF`ճѬWXt%7[dM"a{Mr*ai*VyfY] *!Ecϑ0VF` k)j)iv0ֻlڑFf!4Pt2vje^mbHM ^;b͏i6 ̺Z{Zn: -s~f,~ %ndD-!TH450 є܁2iD*X=!I{J_aхlD r[ќ,`A0&"oKJhwj%? `Ƞ:)4Lw>a5.ol΅;e ݢMͫ\Qҝ9&AΘQ?孽}H]}9yO3ۛ6YKs Bd=4f3{>Qҝ/ NxPrTƌS]WJ8f I bp Z嗓r#IфfH #P:e# 0p{(l9y Ga GPK⿽:"xѧ\?_O Ϫ) ug3kꊷ7,9 3 JJVd=Jg)F,j=˒,jIEg4 SIXVIR@@_2KuHh2k@\2+H<RKH RmmT[gY'phzǰ^P'}T(E򑟧\y}tuۊ1)mSy_rT%%]*䓔2*Qk*1*т*UamgRb=suq.;t1dniѺߝhB#(5/!JK_|~fPE?Cg"ϴyK8RV/8V"yYY %RDXû:K,iK}0Ht|U&yy` PUBUх}@hwUXUbYeִ\s/N̍+}|ߚqOB pbS%*Y>#JB&p$jޯ7<)ۮ5L dh W-ڑjv@T߯A 74g/}ꓦTԊB˄ʍMl! \gREB$,Ōۈ>+4$A TJB+DJLI)nmǑ(X֦)"UYF[(̓[C~駅 8HGKэq:{uwkpM kS4!&my>bv>~Z\?u0U ,x[V>u/MB@w=Y; Aܵ[tW۾ܫ+lZtW+I\aeDGd. UʬYvvK8VЎ#pObHe{^%ÜQ5.p"Diq&{|vr1 ^'=Je-$lx X |Դv,rpAJ2:F}r wg"wn揯upx VyY| {EU߬s,XÅFeTi&u0 q݌*!(a\k궱WK'!l&<TAf Z%&Ui-%47jY@QL)kNGܨӷ;j6{^y; ny6Z%lLrvƌQPA"KR10&Qf>A ,@Ĵ-2>kD ȀyrB>+~^`@bm*q*##!q0&4%݁sBCc){iD8ђt&F aQi%({ lq$I»>@*Pqҳ&+Clar-sΩ /[^)M :ޖZ-*2C: ҵ7hpW[죒z nz#l,]lO֑k$qgwW-hJ~}bN¾-B!.Y4]D A+xi; ZRޤ|{]ʼno-Pdc=+mj^S{Z`ثTqQfa0v@g<]ЕuilU Lr~c/E\re|^-9@+0Tʸ&+{WcBT~ų $SJ+: j`Q̔gF;s/=ܩjDtg1 U*dR3m7Oj#i$OD](*P0[=:-^'AWMW;ҲV_D;w1~O`Q!$/ʆiT D(P9dNoJ IS3Tֈ$H-/?{ʎsÂi 4i[phᐕϯX>r}$ʷ9>Az Q eШp"v)ݪlupD'~0J );sk+r8Kh{,Ns^  ͯJO>X* Ah3d-pAA,:\f"2 *v5h^zAq=CM\:ndбK[iƉEWHJr6o[ou^>^u\jb֔2,T/b - y,4:97,%GIeoy&}~/>*Jcci4Zz׵=KRTrǮ D l61+c|]Unl4WFl jp8IM*ۃ(@N\(C'g\oqlBz%4'g',DqH(|T^%S&Bo NFϘP!ˉ8cG :e+¾V7 W6$ۥ 3,wF*|EJ [傍_a[;a_$\+_!iSpMnX>H5`nC gkqy/piJx%~VW"?O_In:3i$瞔?)ǭDHCxɜqiZkɆ–A=] |Br)"SIyO( #"g AʇCEK͜`qzpjmRHP-?ȀwMdzێH[ѭ@ռdMJzqwA a*߅N[<=yg_W.n!Ý^ ,Q Dtw>Ps?bP0N饫gvmJ^mK֊6%ÞͭaEsDHbf1n>Xؤİ6>YK` _"gegg.Ï-8a*MJ`3{E-m2e;@lDZKvoP,- uuKM \`+Zc/cRZQ2b?{ZڝG]X/s~|-Mm$ 1RNe yqzF#<{vsO9yo'1]{TsJc7x_>ܪot%56 :*dg LId's?V9 aK7$eޭ'AGK>ZJrle語ήRR2"$zf^Wg ^%Dq+Lo* _* *0(sOLo.W8Xn`D\<1{#o EbW%̗BY/NN~I*H‹AxdL2jtHQܶ $. W9nBk(p퍮S Q!rB:%[ aflb[n x)1@KUF;(1HNHDFzC58E11D/bG15آ- AUl33OC Nbq"o/ Dg|mri>~F[B't4c9{Ξ%RE $(?;/Hzz7niԋrxsak?wO=v]{QJYD :$P.QĒy..1! R^KSV<~~@@( MqCj w^tJe{rdcRw^c|ULJVfz|z)UmwLgB5"$oZ];&))${zL:`O^YBɾo[]h,NwyΧ 2Pw@8H!x洊.AC|DdxrUJL,mHWTd\x`wzaI~ K`:4ح IdoޜTVfaڊ} zU1'G~>{ޒ|3޼j 1/ȮZ]G|fIO1Cq椑+({#HTstjӏ6qNɺo?;.1P^m:QGǼ椑sI{iw@sr+IhI#IA,F6("%XC";̹আ}g2vB*w$}X`^^Tk͡)GA(Hfne$fh>NM_SШ89@>c,3a[ݸ~W_Z3.ϥ_N Iw;s@+/#vŇ$}$?VdET^xQAFLN潯SpheBr .t߾DGW@a@!dLؐtbɩqpU!ڝ,X XVNXo?lzKܫN(AIXUo.WDU&ro1P +AA*qKn$quFTD+k[_=KɽKӌr2RV>dzj_N&Ë'[ns Л'1UY Y6Sw`Tm҇g֤rTө|峳q}W $`;a̘19]~OrfwQܤPrCnrsu$=f8 dȐƧz{{ϟf-zٍjIčv_5=d'V^=XbUVʸ{GҚF2p]?&xx  :I1q {PYkyA&(?ɡLd6XWT UOBK\ljay!IzH6*?A0lk]1ϫ%?g%ScdR[E)W}=TpVP888M \ v0!e8lZ#/!# %1EA[PxkdemiH6NOgA@̦箱ҵ%]D#Rc̻]ϲ:%6p zS3NK ?ws[-x9UѢqzn@r[{w߽}:V.Ƅo*H,eYce \wlA(Q3F\y^O} !՘@:A=0K i0I#YBJ-* $\k-}n~[LGKF62kjF%"Y E P\ҳg VYYuZ16Mj̐-zPg7T"& ;   h5)oWYc==^//wW7#% OyCWeQWJĦ,ScZN-&v6rݛC-b=e"S}Cq"rʽ)Wɧ95re麁hIj~w%n|2V&YK5UA^T%֖VA[9挦]ao|U߰,qBͤt3' "2(BruNƪ%EkqXJ"x{WU,0Z`L agex( 7M)it[y%,X8Hrsi.&Q $ ">lˌY} Pag~5  9&}\|(k2eлNvSs()5 :&vrmP~*xc<+̝x1ВZ&1SA+H#0Ht@sJ\+k$luvM?瞰zhLAd|*jzˌ4Nb7ߌW H{cvw\GMα"C *pMS(+,_Bc'Z"ٻ3JŊ#n"V^wsvY)/Go,:#5H|iVjsgν'cz AA48"{y%s]i7xgC%mDޒ=0F$L.+Ƃܵo&U5ݖ|I g(c"]rn_ݤ%,'ة[]A/͒TVJ%lpV$+|'Lt)XXJ1TX2T#!EfdHD6,GZi(R$)&h "#c9.,:c \zob~Ձalպh"01>EY=S _&BE43#Fx`0FaP6eH4EF^FyQ2HIJfDd8ڦt!HX|(If40"t4]K"!TdC Z"Y=뭢%o2 F"[A:$ ( oDp gbA'x  @QD,"YAǷ%Uݒnqֳp(ⵓն;ZbSrMZt1PݭeN%&%),^!^A!7"BI!SpAi}J,Hstll'3 R$ ۨbG8%0 by᝽,SK8lWV%S>Sl:RDLztfIJ=AzS^b㒒%],r ɻ6 Q0A'&: 7MFj(Vcp$RB@0;{h.pHg .U'׹`$18.<\ڏgkbo(` &Z26[Wq+ZU)֕33ډ4;W*;L1]YHqf ~QEƅFZ-¢qK6@am+#n(Vٲj彉pm"P,pcǼ"'9Ìߋ!_ͨHH ʲoYi=0.gUAz̞(}Gr. %6#VFʄPAƸJٳC,/-ynijJ\H.;˦}'46 PIR@f4}F 4ήEc'@ܰD )6 buQKdv74!H2@ q"c4J誆02zhu4醅 RÍ.JHO25>bIYX+VX Z2f 6"w*YGU.c9*iHzƢB${@ vME`G`u2p ƠqrlgU"@^Тv{$ueK g!(%ƒ[Y?SRޡ (B>2B,AnEU=]yj}: {q5Gi_>$85Oj]M`eZ*tAVE>16ka^f躓EvDl` iE퍕T~NxkN&? 5E "SdBp=, lu<)\*y9 [,$m[?oHcG){ 7 ..ZqEG*Rnv Jv0#G}n3I[_80SCT.G%^=MÍӳԿjY}l_{C;c-y Z L^: }Ɯ"Ğ6Xa)-DKۄaP]% h/'G:jR۬Lw+/)5yj8(#2ԯ +V5 <|Nۿ"eT񺅚"ȷYYbϐbKU*/QZGX6DQ$Dr:?1)҄wɚ0+W큿)Ȍ3/vv"PJ6+ّމ``)I ݁;mrlbkz)N]aH:RR/ly?bERx2Ft |$bLr.%#(ƕchQhpHa)f \2t !> H @ ]CBJ_O~>2sVK?WҳAmVw7ȥICWʬ9Wrb~~L$Tdֆ%9Ysկm*j9z+$EwdbVTĨeYyQU^i_ # ZVuml%z)֡aqjOge!ۘ$q-7?1d X. prE:5RIX+%@TW*hBb$gYD*zkPso#(YZkK<~կ˥M.u*wOePmg5(CaC|PTq;3J۝z޼L1"~rcVx|RfI͎ngXL(a 0pONu # (dֺ lMkgj djd+ֹkDa9#Upru HֳL\n 5Oi 1D8 3Z$2 Zw+ صd+NLN{f@?IFH!nɛBYUL5F(cJȂoE/bA[00+UlHDQhk$2'N~0DG8QkVa:y2Q"f'NA 46V$h+ IOX)`*,06x;#&zF5OutŬne^xU۹7r2S]*'0m" A%99ZGN7X^EoW )aBKk G.nP2qfw P6q$fI*:N!1Nb0C +ax k t䗥5] %fz{G$U'Q=|4$ףYf6mu!oַpb\׬2-,رeh W%p}A\ʄ[T7 &aDֽmӲ494ޝ"ĨK@ ֝ Kmўmpܾ! -Ͼ`4tnoI{slT{q -t!QJ.?Ly!&|m$2rp?",O>’&f?YՏ΋q3dHzHV酳Ɔ;mIw:`rIju.IRhS2ber^дKy-Z2W G;DmـNI&ijfUT_\*\UNψ)}|<:h)r#hsdieJ{Q+XN]-:{)(+ڌVqI5)< U1&*{۠8s~!G*p)^MFy5$eyyBC閏6+QKV/mT:CIF 3qrY\um^A@&GiJ5~qvVFGX`Sgv>hhA(ѕx:3¨Oe/wRd,ͳoTJUS : #ҡ!JZX=w>ЩQ"QlH[ǧ"*i=-wR Pt'HJ}b0B4W l8\K2I6Cy:ΨٲbzNJD:)]~QIڜ)n"2a /{9.dpK7%:adim|+8o|=l}VЎL~Ãnr)*p*lK0py쪲} #fD0j:$ y3Q]szMIR7En@~5R!dgu)dx)Eb/(=[ʑR%m6m0X($UPzgve̻G L^AAjI6bD~J hĈ]l"a" Ԓ\g jaRuDN'U d"*RiihO$Q# @Rd}DH,nG}o K'FW1k|zVLCwFKVF;p-bNn=a8B#VTHkSUy,T͢<3A0l}&x$K.U{Ը0 A00싞Dܐ8b$%MUq𯪭OúlI ~DP @сIbfpiNf62BBbr7͹vxH6䑄|a@,6`f#@ s*PeSy&r'1tF1!-FiJb7Pyie [ݣ)G *4y8VnZ37y M'00]_8Xh *HB e}k,* quV-ZA ZRiT 9+[T  _ ǯRzk^ܬiv'! k4.'@T2QЖO'lk){g 5Vrtz~^ &0ں1`.H(3uaj+A $hja &wTb.h,L$haHj"Ŗ:Yrx 4-A) D"Jy4ɘթ³Ej>thXh\xkrڥUb)ӧ j<(c,\P}H5 v,9c =W_3X0;jigצ>Kk!wg2l%ڰ d~|ɬkbU#` !v)`g;/YK<1pFvCd RΗGX$&Oo08zy_$sdDƐY{Bio<pxccghZPXkyڄ6[`kngS$bv@%釷3@x\?mNE 8xZE/:"ѕ &'d&\32q,mjV%q%2) M0CIDGo p85jUtlE{ p{k{(Q8zlj==/)tt]LE n~"#ݜF;7s}y+uA 0$ F|0`v.aT.e #N-~sdbTCy{V3XZ}q1ɏ)4 <_MK$|T;ukncd0yiADin*:TL gۣn6@@g>Ibh' NpIe>? CsS)## Tf@Տ!2^;'rtQZ^XKM]YpF:I*@\`,n[!@0VK9P(mLx0V:R~%j=V/[đ|;HGxQ3 {D"WviБEQfU(ѣ R. g7HBwNSËTiT3Wĉ@Xd=K[~l7?&X&7Ečϑ%eVEY#a {,D& +VGY{ŬbJ֎09G*g;C€LS9fȲF֐nL3SA @e6-W=cV <* ᄔнXmj쪢v2s4i%$iD"$4s,BKF!ZS4)<4A&~>QM^ tsң u+E-^yʜVG{! 3KYJԒPK*3dM&('M6.-[Jni$UR̹uI0PW?4 ARݍSg)sQ"X^H\b{S N9԰(, $.DRJ| UbԆi*s~lذ܁GcFI@ygxGk<=b}"I8Rx+dĬƹũjVKEg$82eǂQVJ͙UR%h@D*U3_ŷJ+4NB(XY&ED3C>1Q6*r,UG8Wo&]Vx0GD_xC>,[(`X~~"c͹'~ԃKdbXGvch|I S e{ds|j$XMLL(LNޏ?=5 >9--!SLpi(oMNEܫ3ؙЭ''_(\QCfeofr*t9&D(5hy|ZɩBFjD$x*S͊E(#)Zȃ1f9RƻWz2=0(Hh#XN*~D$Rן-7ԥ[%6Ҩ3 N'˨ΛtV5tgXgׇ+`&#r uӋf 4鸘l)cٓn^CVl%_qgfq1n-KZ~[|A]5Y%V kwU0P+IֳLZҐG/d 6_ݾ3kԳ h%މB*&3-Ogo곢~Jo,KD+D&A&ݭZHU!OXmV e˖.U*]2P0v᳈\ 3Y4"#(3&YK%cW3yvCL_VPn,DW,1peFpXKEU蚔_cpw LIclw^J! ϜLh2F&c"OQ߳8` d{/~ǹ'cYfZtՏxk@Bph4F=F'Jvue/YL9桷X_Қϣɑ;/M$41sّ[P/M$t'dDsFȎ<\Z*IԳJ L S؎<~ʥ@ hm30rн:AIw$W$/[,_1=rqC93y`I3Ag7l ȝqڽ)s`C.t)( %ƉW#y8gR;5qO1dnJi-pjNϯ%|Hlur}jtJ懰ŗg)>\m wI\~ F*֚]T\i"1YRVm0|8 V!T \f# ҷ'yO+z#ĹIׄ8̸R ƥek WZ'^ h5_DFDRn~"VFqp M4H7bݰ%R;Z/ndfGEecxL-ڐqz{$Q[~M$`0@'5HjT͈khiaT!%.i$`_Ԗ &^I4^U׹=DtZa. dJHP⪘J1ȫTcS7 #unǠ>:T@8eGTqUR1 Ph#j5 y=[[ar* 'it%eKLb@`}{\)):pigceU*XGJ1n--iXspʃBU *B%Fr @i&OڳE¨0e]w{Uュf_v4 Jy<vn?mZ"pk*{9c[[&̡Gh9TC켨3^p= kcX&(.5նU #}6wɶr_1=;59͏;`;Ã>S?a 􈲉j1!o8Q d`nT|{8գ%&MM k$ѳPMdt] #N3Uv pgLeo&̻SKܕ' 筬U3=gt뾱Z'θd5SYp0q9zڻ# z2}V'W{29P}rmg9Id%5O[+8a RqFȥ,>s{^NP9JS^?DH),pwo}[O@߭ð86v]7y0A#0-3Wr7^ 3# JG4x>,Od(6љmeWC6)heSHE } `_9ybWb Ջl,@kSڭVbK N eŁ4%Tz] CjGFG@ѹ8Q e]5grTT@{ 튣$@5* 'y~paM Qű8G?+OK&f"~,?ٴ6ȎbI f@O9,규ZIvrL\ǡ6l6=ɔ˒sQkB,cPW;=mH'9L/cqJpFȵRz籯t3dq}%U;WN rGOfqE L>O I$f<]+;cu"+ANߟK14wmMyhj>3H.MؽAk|uX~AA]16C,4f}hgz[R2Sq̓9YSO"o}!7oD/YZHF{؄wV"m_^:nZQ&L '{[Nψpf]*(X-XBܚo~O||IprSF` fL\}p}&,աu}YnI'מoj*g^>Mq=\hO#ҍٮ)'OY7H I/ rͿz;KrdԱvvAK[yyn)pm%Go]2_.85x5EѮ695No̅j}Tn֣zsC[9:v~FqmҨˁL 2 Ԍ.Kr<@ޭ:t]Z|$W[!Kc>ΐ =HFc_79))./;Xֈ^<幯 R%aOHJ򧗸n? 65@mh ,\2asx_ykX[?;F \}3!%vu">3s5z+XSZ5M_>(知V-S<V_iiᬐ\\^&/Sw= `wuzƷ<{ \XZ=Z Nӆ0e53#+qmvioRԺgK6mx%W1cI^[u-̾VIl}AG.:$%w<˶J3 gmbRR߃ڃ]QRr &Nc<kT&s/?6z̃PU8.0q(=&k<穰ťvC,20TbV2b/窝 dˆm/UsN pĞ>; huѺ5I}* {D@]|fO!2Uݦҧڐ$2 reSlT~ǁ|?,>듣MI)s GHOLy*{9kʏX< ŒE%ˎb?0rf2wY%F᧚WgN<*qiJs\ܑo*I&aZIF%nWk_ɕޏM-'2Z%8&wsG,)pJ?Ѡr~yVrbzggz?E6QV/`Ű\eu:NZfS1W2'NLpQz/Kp:KJ|%,>mG 5x鑓y}_Aq]Q.^](]1pQ~/KmB [$nS!x1yJzu"L}^7d #8452"Z Ski%3-DmJF"fS[#з^5h=6%^*Y[~4PkJZnٷN1:f8o;lף7K^Ŕ9?wN0+h֫%M*]-oFm2[R6:˷K x04ۃ+[)  mמ5KVi'{u% Ϙ׏1ē\%)^'7'#wqUC1Ƈvx|pqц rK}6V DHNUSG2`jQ |ͯ[64FO2p0aa Av+6#?uy{L,ӗtȳ6ٸy&v!zj׏&تu6~ztDDzzbdNhb~X-T0Ik_w]1:^?Qn1#~uע?CbQR2O'_y9ki޸#3\KH@8+--%9d k ŕz9 @#lEJ+3O fxjʗxR~5dcm.E\ѹzy,IM4ɠW"*I"d, K@:<CsV-[%Fmb"$ӯPc}@c{\J( &\.6c{@JIsBQͪ:hk"M< wO~O|.[wdwn7_jxȂ Ll^Be1rA'(E*4/, #IiPռSG]Ul>v|~=W@L/IJwҕ4V}p~| A?awnOҼG$%%tHٮD}Cڐde;~ws^%%=J{kFN[xSnE,Jo1].W3 ,:hUq2>[5M[{_xh=;Mp֣B=ӊbRYp桨;K4Kֶai%/$17L]eH,,}Ud@dP fuNV%%6H^XߤǕ{BW,/ &Dr \q" BZNh=Sw;,)9O|;bY5_j۬TY1JÔ6^L D3q4Q+4/#隸7\fKh|kH,_)1 ?S .< nF ڑJlh]\2 &@Vԡ@sb_4Ԉf K1^+ITR3t% 4^J4}1El y7-?IԨqmG!JwF/K$D%b:ѧs ۺ?B*Igђm%r]ah &Ig#ђyIE#R,&tKlKQ|qWD hWߞhf+gղDUIckK݉mI%8$ދ 87Z=3=Oj(N/+*L2h=O\]@R]zHnsܽ>7C@khX+ZIW:4xhj0Z#&զ\L8m$P D nV_'1Cap|} ǜV$%y@VO'Qa?뀯G9/ꥑpik_]T΄-%/xZnw`a^āEAoҶ__I/,X,[ F#;5Ml]OQ4UI'۔ [V^c Lw_f nmI헧:M@'&]o/v^|a|jguֿx%Cmxe.NSW' /dQK4{ H?r@g,ؘ.*N!z铁="cAIO -]]`\PcEE6eF=M/E>E3T?dJNC (]H16q:+J*i@&곕1 CnU k"HܦUp uJH~M(8R5ԉd\ݑ< K65]ے1Lij9 \ "y6Pڟ]5۬TJJpu5G!QGnjGJbSjHJ¹aOi$Ϸ'+ }ZY{{@Sۦ4P~-0\H5B{x @멺|o@5 EB.l]'wCת~o=}\;+Eߚr`f~[zh'BLjzD܊} Dy! t%?_~M^ԈZؓŁk @_^qGM^Sw~f˗m?QW1NC 2_}eFBb@ MssGZLZҸTtM~H\ "D fhe駥$W;,YMf B*>/liIpCdŠBMI\6l{FN9&ɳJ%Ǫ%s 4g3S${@5DQv@IZoO%}O-ƵVQJ4ZTRk $]=nk 2T'(;Ks9ώFZr *!éﻎGfyvƑN=޻=û%i$*mgu$k%MЙuBStb]IuJ䌓B2V4ةVIQDX r HW~XzlbN<`^O0/>(|tkH#Aj`jDiKqAνh m%޻K fïK*qMN3I:ۚKB]vl!6 ֞ͅ5H5 c|6*bSqα>0 [ D-jEYis5| pd v%ጠGdϋT"dmXwj5pX|HV '.AWVwxeؔB&z-3Wi+p>*E_2Yw$,g[B36 ,Ued聒=ؕ9b2֗zPgT>H/Hhmv>N薠ґ  xƭMI*]%@%x[2RI2G4$ud|/gQ =`ˢj1ˮp*Jýd:KH҃h ڟ*)(.Ei0./g0dD;b3SVN}TytEZ%9vt $bT#/-h%ἴauJ>vRIٶ 07#&T2Q#1 iniiVo y͒t;/Ebl cw1K<Ό͎,+BYFt#~.{$yCܨ[&ܭ .]22tWwMPʥ44] Z_ؙt`!z̭) ,Z6K:ʩX3n_JI{%mDjkRJoRF08C+ 2ZRң2ڢ\T˝D& * X&@nbѿ>n><%z6NtmԝfTNNtUmZ̒i& zU{M/+QҢP2E iIPK 7㌜BW^\8^f` w ($Za{/\6"-PMCg,o\-Y>=,mz\1BLB.wi6ZkY2T҆#Jwqd“Nqq\t>r͒SRM\ 0dՁ@!kpIk7r.YTZV܀sfIǸL-sM…JIQZ:1N[陕 uFazYb`m^sZɊs楌K;6X ,AI4ucNZ%qidX,Def̶h`s (dAX1 |"Z 0-G>p+ |сP\Ē9<5csٺyOS.\s& a'87ݠxm}vm'96kSB}qs ؛u/mxЮ͇QGX!f T,i6}ļs~,5+IhnW.?1LlG+z|k93-%q5 gK+$N9bӲqoڮc&d|u+U5zv 3]v%TD87UlJzgR$p6coԫڴm+$Tu#GIAij(]tH,m_P,k"L[77ﶡ޶oGgZJ"C=FڦF׀@+Эnzm ݖ$=1WT5GkVjLn"~L5/r`d CjyNN#/|џ!ceNW(ZkS/JLr YyHBG2fRH$7PEB0Np.,$WTdĞ1-_{:./iCV&=;EXxU@w/\?FK`)%!R,`329ZIҴ#Wۺn>* fma1ḷJ/j ^ JfQNlVs~s^x",rY)Vs"5y+?+zцYb>g!`.pzB[->99apPTRSzlaUn![crS^Yzz}:B72dy',tt; 7넒Z¤Ty,֪aVW3̒$EbDYM3LSN93S J.5HY1[}REk(Ӝ:tUp)S($'%wurm"cK+af{vTq$u;"‰m:;a#y?z$5quh`f2j(`fh!R,+Ŷ#s͒B0TeSmVĞDȢqNͫeEjh'JI~nyLQfU"NwYYrBYMA9 =2>#gk鮍?lE 69  ow #vLPWU2nL=U6EYR5 X=m ڳqIpA#/|2+mc [V5EEsFX*r"B/+޺F;uwHH'4|G Y tzI~v# Ovt;~Pdt]rƾ--V[Dmyx()9C۹wB,H+8j[dkU[Sϳ?J ETD)YG%H  ϡ c+mD!v !U)1{!Ocq(DNT-@ ]JTj"m.fXF/%%2Ιjdd5 J̃T[ꯛwaq->lNږ+De`lhSpߦb&R MGbf[elѯ]b gmsITqTnƥa+egޖu]nFm$QhY*`a;S\ւ%K-8% 1 D9r)%.𺊡sW%6*|/cT ᡯ~bO].GYzK e!ZݭB,Tb3jp/H--dUzm¬Vń#JCs1ԗUI[v3Ja52;|R.Q4fMV0 .-e!.b08ّh  ՕzX]%o )dk JS^ G#G%JuۤtCuӐQ fI(iniw٣A@2xlwG= F7WMc0k ZI(spXỸmn,0g ZIlAABv3ʒQ 61J@J.)D^*Rhˆ^˕q)b0&&r)QYv$%vCYye^Ǩ)Dk88GIYk9C[d 5 gA *J31Rj-*O"I 2 D@uZЊ*N4+t@XFHGGCN`D[Xa[C&>k jGiO0b'F-&绑C%b>m~0%a .w&:YX2j (T)RH=xEIF,]4 8a*~O[U< XAI NVPD(R8bKw ^ʌH>/\RL-UirUp'< yfsjIel+6zOUs᧿rN!Ֆ Ij87ErOҜ)W;F^z"*arB-K2RZH񙠔%T|1hZK~HX ~*8PKp$i첀nLƄ ֮ զYؓhS)LPőJmXUQiJIE⬢ DΝ2`((N=*l}j)% K )I[BA JghgR$) #dJ@@!fYG' w($cCYH ܤdBϑR,BM݇d }?SSWuz 5KvsS^rS-eޝ ɦI"ܠT^Z[/hKŎ:qvp#BDo̓jEkk:夬s|{I[H'uO GF pJ0$Rjļ~'EXit!ZhɢPnKB!G?f+%Q y70+xPM5K2S"D]`EQ- HJʖcMGpp}-J]7DGҐ二҉}V6F yX+t$C%5wUY pӺ%%S=:0ֲHJaip!q. ֛)%dBuիiT1!yzO:5XYtg2l'7f H<N|3Enph##e' ֨ᕯwHr0S2R h|PH HY%b& t,TR~mW*fhԤ'WWr͈Jx--rŐypH=|U-Zѣ=Ew#,eC,rrjIUL63:Gi҉Q\֠`1V N+UU>Ȍq)(G!*#5$,QJ=@(?e^U@SKx(#j`n`GYrj6gTyIq{3SF1 X23-R$s99MA*qDEMyIC%NE\A$HFվP. -**HaZQIUkyK1(PDleQO$9Z( !"U$PMJkB`MݺUK/٣e1#QOiA#F݇UE#1O7ƲR+t/~:)L'guG?IW\Շ:Ak)*'yN"ϬX'=.gﱊR8"9O{2L yTAr6dŶ6,igb[ժɓcVnjƱv@듺$gJmz"}T)-=0ѱ fXv6ABZTwO rF$1bhOhWd i>FN BFݚ Hj Y*~ R6;ėyHjJqy HDpF-!j I%r_bCNxuΤ?[Wck^sٞ|wU>N :aOS1w^X_1KxI.˴} 4z'~ փ>A\?_O΢g쀪Inѫ"sLдS[v[OΩD>C9a+v;&9oVgYR^MG~k $9N:u'LE fGrH<]=N58@]H~9s:Dwp <c޵k7w^u:t,KX _`YcUT* ڑs^X/vPefIђIe9!H`..t$uKκv,XYS8fuis=Zɗ$sEɬZ E0=u,ؾE>j2 q#U<K-%1eKaAkNduzH#9YfAJ'OK-jFܩUAk{2Y4 gQ JΪ] We>ݍs۬_"(P,L>7}J}Yd[ R=j{QgJH FHr7NaIlYY{+t&bAKfԣoHٶ|Z%D}]gƛUF衑Lh4=`tq]zWY+U.4*It^iȇ!{r&.!E%W# Ūp}Kaຖ+Ԓ@TV3cY$$W).v8JЛ v Cɺvv]H#ٞ m}6^Vȅi(]!)oF'AiՐ *+Ƅ*qАx[0 i+D`r?&+4$$Ư]̦yA@eBPIuO Gإ_d"~YYhxz>^w*cZJ*<Ҙf1xuǬwM#z4z>$Mi6Uc}I}06[aJ ڌQ_}V3>׃^iyp b`->(y_#CzV>q жD=.]Ad>ޥΓ̕҂6K.VRz~ndvӾV Uv19NR"VןmW{Cd]L\aùv+ӯjDU4h?kP^|>YxݰaWЃ-jʆ{Vfˢ㪽_!URD (g}fp9͸(i#39Nz hugPdl>m 6sb\tĠ')eE% GieF"g n5,M+~I^4LCFJmʱePv 6aP-jԚ0+p1n 9Up[ J#7,7 Ɯ慖65`&u0 K򳁖qct  U)JPIe@{յ.C2pD_ǜ :[MMz؄K)IIc#]ܥ'% -sY:+K+f27*a~8mB%q bSϽ+CN\ɇRԬZ$ї=6KCݿUirσ&ַ=2@&P Uq=#gH)EZfwnJpt RZ<٫%1}d'UČT^-bϻ+qW&%UU^g{u8NM]ӑ_\ӡwKk?4w8#;?^f^]]WKb{'^07'DսZkurp: Á:RCb-],RZTՈna$dMwy8 =/04g KH]NR{@&ʨi0.}6_̻i+.S'lȟ#\[E%fQ}VqP3 j$NX\ jQ\5Wl~P/zږQ|l7S*|)@u%fIQ2#DIAWRmĒ'@Z82f_9& $#*ɏE hlzhԜ<+4 EFYg%0t M!i;78w(_##-Psei$Btg UtQie6 kʼn' )1vRFM戀hj ЗkCʹ.:2#5*ImʐHl83'eRH~zBί }r:_^ո#yp^MfBAGM|8U_,)՜#1Tgy>_vѴ2*̄gVR6$cmN3I(FZy Gl8E_XU&B_/ ;%#v-'T#*.QgR%F2 pБA1?)1>'m[{>2J"|PC_>\ a:#?|w9:_K؛9l;gPaL,>dh#Ag/3/1xs`婫|0#ԑЭ~c2n;2 aQ䨎dS^yQOr) Y!o`E$kU+r5nX͚ !?(otwĆ^8 CXu 6~DV0yHl$ ).8 \ـ@5IJ8-$v}Icl @,YgGdV%џX'%!a#q6" #/ _='ݏR$ aߣv06" d5 lj1 #ᆑ@EnSIQ[rcv(|I]mMA%j~S/K#vb%SkMcRy[1ԊNDx")¦Ad6ouL:~0cu$V${, @E P0D NB4A: hf=-i$FOcI?TNJCt: }_G#*Io0Oe"]$2!#HAsď::?jH_/D%6mT R%$~ъ>2 djDP3T(  T5on.wwQ>~_CuDo]Tԭh}tQI~B~@ے:l,H^S`I',b2r"߾BCTK>v!9 qƸt;!Pv4|yKkM҈J?V34uG` O<,|0I$뀳> X QZIIx_dmϭn~@q$}v"kV+pگ{lHl CóJ"i˵u}* ޽8^j >0 !rL|kY֮NxҧU6`T3p#fvBHp̴۟\^<7^ʵl1p 2t=iKt"y,AGKG(?-/iHWsRqM~GTҎ y~2>T8H>Kcަ7<垷rS$:rU}PM D6=!k=#/ $@Y/:M`x^az9=DAQ+ Ӕ,4M1҃H4T^:yMm}~3{bYm,G~ŝvrhBٔulݧ˽_Z˼=xtIA!\[3JsOм֑ Q8nbeSk >Lg%͈yCngi!0ki@ RM kI3Z (edZdx Q]f>\XC:4!/! sųqrM˾O# TyDm4Sb[zsi)'D#!3O;3z Y2GoI#Z%d8fO'뭻Ӂ\,i_޲8< gы#NN{r_-Ko^Tq6j7H#wHm|wWKN;JQN dĎh{zIkk'Z-;V- FPUhoǶgw%OnUʸ݆5xW?S0;`y t-ijI}*V;?\Vqm$:UvTF5rrs;~,>U\#?1<\eף m/stqsUEovYs}2њv~m.H ꃕlN:xϜ$piJ*24Q m(b@J6#,#ajITC cDBƀdO_z:wwF$ڐ 00pJlim~i5 -nib#:ٲc/vmܓ#wJ?(d8j-N<ʎp`4ƲkN4.$.בH-SKK:]UQh.|ٍ`oݍ&p֏O} = / nݕFwڮȮ W_՟K>%s_mGK;^Y;Ԯm4uR+ #WO@GZqKo PHJbe9Ў׷ڧn,)oҲ;ͥy1>ެP;p}us+s)路MWj:O¯3[.3\8"|@uoJgUiFOŰ OYhMXFl:*6lD2N:66~ J[~ ǟ虰zo;?~w*^81k)/sX_7$S,"4/.>D7-A}5RMsd^' @^x' jN +9xU COT9i&eI3Qc0(l[R*%E#GT lԝg֘*KXƴ}^r=娖$:A?ExPN=IMawJzP(yerF+e$aZ2Or4h+atY"wW_sP9g1G$D Œ|kOՒP( D49%&gΌW;Q#/h[% Vc~dɾێ(%z|t ˤ4G% Jȣg5sDT2WwlUoO!DKB-dY_oE6IQ%*Nq,A@ÿoI{*%:am\V=P-($K*aW P-9᜚<:2>S_P)hHqg/Mp\U~Uc牤ZTfGrꂆ;;L{Y+r+jjtWU6usg^Ӹ( Xw)rCO4||p,pC'Ękβ[Hpk*"xn<0U9ROa `/Ǥ'EZJYxMGz< }@*l"K@)Y7\#76i{rRF 5: E-VO|UY@8rRtS+RQit8ybd[//,pc Rե%ЫÍĄc;;U|IT1ǩ˷[Fq\cd!: (qj*Ġ֑Z<(7J:NrU'ĺ%_QjU-& ʾ*A1Byݘ4AJj@g0W%Kd ɜ|MO $MԀdj1Kdv5 |oЦ.b 2kWYl1vx#ל6^)ƩdJK"ߕǕrq*I] IכҠ7dzI#AX8,Ѱ$B- 8)%-.˙G=4iK7~']ȂMZy@ 'oW- YǢsdDiCI6+]mk2x+[%MGHF2jc2$1CA:ądl׵M\LTInDhH2"-Ym/μAFIv>_te]ai6oG{A0I|-Ρ48%nm٩ߍ=lޏIX$6+]xN%)̘;})mt}AzI5DIp`#!"U3ǰgl糖n _v1=UijOƮgbMU|yk֦ly Q6v?Uup>̩s9vXA(->}aJSaVμyJRXb.W}\GqR'jduAG"}wޯ)-F &k~,P|/0 &Hk^FqiX =u ɨX_gϙ=" MVa˺)7mTS6>Qj0bv%%Al,߿(I^3؂OT08$^@D<*]_M؋]sEMԉb -ɣ #;T$&(DCͳG^ $zd"YBFo'N*$"zX2,A+$jEeV'dJ K $w%' ̠mzJG:ISݿ3isc&Hr9(vP b@|"xRBYB3NCԊ&8B2FQaNRFsN$E 'EWYzC-\g+!҂%%RIBz8| Q8S%%j^gB uJՋ咢: |`J)H W|Wުl{mՍ3HorS!k{!1ACDYhk[T=j~D @q/GD9Q)P2|h@DX-D-S4I &QPȘOAo1LIeRb0A ÀpfUz'+D2`2L~T'84x  B$ 3 wH\m'GA%逷 AT뉷mx_hDwc,m^<~d{?F{3qO򋞲Gwݑ";7e>*sd=0y]hߴ#Er|xomdݷgV`J~(UY]h_ޑ"'?|d䣳/ytoJn޻>h_ݑ"uSX<ϟrKWe}?8:0m0K>#]M;HkgƋeo68y<}qVh]k%Ηڗ&{˶5]KTR=y4mTˣ%fIG]pC<|Ulqt-x篲Ab[Y.EGP`-y#AZe{dݽYq74͎#S@rMFNLy'\"W%J_PEpc8$!ڥx}  2ĸɎ)iaQPV HZ^ "B]% jXchz5&#$e&iv9)pKq Yu#itO78|Rںt9ab? yŊ/$!O= (_@j`,+MqjlZ*?#;zLhao#2R&¥+P5ֳФx I~Ux_d8.>~MtQ"0Vs|.DtX%$tgH";mc}ԎԤK$2[4W`R8K|3ei7tS)"8+$&4<*!$[Tr2Z/;_TvQèBrMOyRu#uߤ-ͨ~mywu}7V@-aԍc?@Q[IW[Qx RFj)iPs1'9p'CBpGFI .Q&&Ib$e&9ʼn`'>|I -$YP}LpeJJZ e,}bUN& /PN띈Ph4kԕO֌5pä$./ (3KRkr` om1 #l rRˤZ3/#7*!`X(Ñ 8#rd$#*G3[ƙ%X3gݮf rAh< bkQ %e1\"r.-RM 6]i7h6qf Ί){dM|Mt]Lu/fΦ4h8`%^`G(|hu;ϗ֋3ֳl>9iC둮yO/hҖa'0ꠖ7תykжN/4ŧK͇emNnٺwNrΦK Wwu$s]q*-(8u56x4'҃Ÿ`i*&khvr[JlCJ%5"8@VKnlp#wnj?0bȨ1+x$>/dI-VANo I UiDօ{_6tNzйӬc}.2\SLU_R6^#TF%1Vr%6CMo=de uRlmhI(Jؑ #mCKaMgVTC`mr5N2yQjY1SUJItԣYyc^0 ZRvBڰY֐lXͰ.Ψdz򻨼 T캩 `K.(BzAh$@OA'.7z lBѐ+’ 'JX7K^^>7~ES4$m+;KHhڈZ Gk@NaxW)#?=.%P[*Wʾru*2;)i8ʮ>A QGnNppQjN3GnW6kMTʦS` k*+(w9I 5p4.cdaGC9f s1_CrR`--Lv ~] vuŌQK=99o+ r36܁BSQbfa@[dh&QhO-ݩb쫭eW2ܢV5p:7oLټEo{EA.M,W[,kZ?,鲙:y[ױx5f+,ߤ)L !5KmwK`3m*ICϱq@.v[SQ J?蚺yaٝ% +љmk͒إ5e"ebX,k)jZ,yq}$-;6Cݞ~wg%Ym}j `7tda-+W׵%7 i$3w#nɉ?`ZzO|ɼ9\YS[[Ԓz&3ݒ,JY܎^_1z*Eec-6CYMOJVu)%G=9ghBEZwotE\9&.I.'KmI8E"ETzjxtgl@q>N6,pcLϫ̀rQ/@ʞt,]O&3jxH0Dq|baM/C"l- IDe0qYBVbąT$_)kcs,⸓%DA& >x-D˕#1Sg.$di  @JUJ*]"F ⥳ =vX>`] AY Kdžy>~20Cd4s 5AQ,j|kϕ: n(L C ꗅ]$lC =rs$3eI }Jk:tZ: m rvXY3M JN4!uӬnA[K$%jm 5'6Z*&#YV+]C:mReGY$5|Fcs)uk7OzU|dcw|݄K:Vܼ]a#bNv0WgἛm.WeK=Xخxn 5#3*/IgӜryݸB=n@-)gXWn)LoxHRll6afЃcX&MUB8MO¾&@72wMLn.m$k6\@ 5=2=) _ch&β69M&gD^З{R1gz(7<[}@>PtEQ֩ ~)ih3 QM֧o5Y+dyh~; n*q :_.9pi;" >^ gDi 2I$Ю+%h?2"ގףd7^+ybc*0 \;B;u>@Vٹ{ ExIaLE6<ä3M柍)4cD[߿1u˕NhcfcM{8 Z+E j ;:U2%K)?)Yѝڿ>Q-؜ Nil̤,YE0e儰V&TW):QWrV"rV?C8^gO:JF]̃,r&Y9IcOY!TB@"&Mzn,y䠛ҖDIoV=wCwPJ(Tk"r$1ndY vZ%׳֪!1uA ƅPHxFV1AA坺ݵy%zQ-)|׎g-CG~"$lVBDw9) I%OJ$[38XI%` i\G!?fWnȉy?PYjh&"pvP@#zҙp@[I~;wI}F4AȜCں/!Iڨgϵۜ{E]::` ,1RDEz=WQ)v[|-+=$#jo6jV3V(A(IESNP+0?*% X~uN"(Ԑȴv 'jYʶy4㙪g("X X*8#0zV6L !zם]#u niQt h8 k02P& ]:tJ)SXb$:Uj4w-PKv K-KSXb $3N;D B㊺Vi\QVDHDI}Yˑ ܾm|qȳoJ!U4 ' )k7`ܪ(?UE6I 9mθ)KΙ'0&%ƍ+1b&E~br|UN&0/&7cU4VSf6[ 07.q[AӼ*V8lٶ2cBDۼ<؎ L-eN~ۆLJ dtuVDЃEv&MNW8X" j5 "ȴ\g@G.9"_vb㥚Ő;iPI:]8:#d:ZtccA!4}F@dZ.b*qw;O%s.W=B!`t%dcs]"d(;XV WJ]5E#q`XkxR hȻS0Ir w?1a@QW uPfw*HV;7VSMDѦugpƙ9t2!7n nU "tFVic%;t^P4waF$NջI$XJ>ŘVs5)nVKם&Rf8"hUz% [7+H <~vfIvk'61/2^خHH LI׏P`uD w%H%S1爜44!T pBC? бX6uldW05$QP %!1( iCwWHsRB4W!tJ0c*R%RvHe2vFcdAByz 4ӈ)1t! I1OJ25]NBR1#SQɈNjD5Q[JO3P{b&+bƲ,h$ nɧ@u:bvq/@"IA4'GS'XP9v0O͘(8M~SIͨQ(m2MiPʼn*|H$> \iC}sP@ɰ26.Us4~SIjy M[){ąsPs])u6lxK+O ൊD2nF@aB-(FqQ{1o]Iڈ(5Nym5%ca(u֎mںP{נQJZ3Zس`whځ TZ- ,;Z: &j*@anEMQHDqjȔE{ȨnxjxvGS۾4VYFrp^8FJp.& | R]_$YZxwe۰wHghMjOm OzZ-,f\r\>F_CO ќO3ZIFj*fm2Ss"̤YU~F꽋{ {E0{)Kr<zlphX| dd⡀x7COUQXG]i>!LZ3ij1om"vfleqɺʖMnY[-&W\wRzHܬfdF*aى)(á0#v1x)'<"Z35ᏹmf"hQ^ 4(GpKB#Rz #ZW.:von` Ө!`>ԵA@R>a-܊mA@Xg0rzC=nV΅f1&a` CRLX/(W\黁.!C=Qb~HUĮl$P٥EADdXFaʔI'QIBV=`@\* *!ʔcGx$Eҩ|YU3!sW ξx)UG-bID-ƾ @Qb@nmI(:y#P !2u&%AsH HQeBi43\ ;gЈ,,)5U)7(USJ#.J@`vc2F.9cҤ2=g{u=KZdNvtb@Y2UI7l\~Gq{Kp@^MIi].agM1X$9*HbgQPLEt$E:tT,DJFY.)>P-* s3pi "YMo`:}/O X5?3bA"Iy3k^pՒ={KLOΨ{u^-J)՘%=o}߫%G={le{g%[&845(7y.^-puhv!v+5^E<쟯){fNl$-k.@Eu;oY$ޙiIzfDmsk M5lq6p\myCK[I"O:d9pqXORO%caLfŒ[)YFJrT"bYI!LvXH9^z1dhYd1k|:]t$5bKj]MKvu4`C秂u*%wiشex: aHe蹩$4i{v}SWH}Smz$3W3@I wtSɒ)Pّp@GYEIuYp/]sٝJTet^y͒Δ(X Ž:z:a$hPrV(QCҠuY "85؜tr1h*'({kΌ>Kf]T:dy>ct eb#Z!1ex-y M[]N!Kţa.uqcg3Wvv zezz (2SZk9vȚBҒ\ \tEK7{\W wGS%'),%-@"1y̹%Su=sl.X٣ ,r-3o&E\KwM۶Ev@HRM9FܹA6GZ\kSg3O01,x{8ʕ3LFU鍊ݑj>܇ؘ{[(lSKCk""^n㊃AJ2PUuՒ25$I T {ZUWEjDz+%),vȓ4kJ +kz8uRbFׇf$ט Na)qzԵK:ڏ%<}ExD}P]N(@yvP}JD(w4 A$KFP,\ 2d3ƕY2G@I)qbdH.f1+g ݅"pm দi'L贃.͞Ŭ`Vu/'e6'v8guo׭ojWLIU! {1%ɔ;ub`pE4&cOyLck9找ϵ/0,z Y T xXeo]Cnw  q IdkcWl5Fn+Ek2rQWxd4cИ7q6VkMc`"m;l) s6ԖFT( M )| ؞6bU-oa֯:YC%^܆\#z*%w.zw&f ay]t* }O-zeŔ 5s9 j\XR@T;cÛmPc5$3/1\ ˜<Vfi0uNIcda @f}K=f0#ީ]Ya0Z'ߋe`Q)${YnUKpW`Vя;0#=86 <_}jDE# G+o0Q"fXa3D)iH3ӷvGe+ZWf0PCX-`@>o!w21s@z]~ Q_ƩSUҗm_svv ;X@(I rݡ+Hyre v7DXҲ8.B$+4FC¯B!:(ȌSJ) `2_I\4 >_0(ȓ:=&/ WST9(20w*Bs"|6?_>v/v7 PI(${RjM RY,^ǃ:{Z%mVpc+;%U+ĈFܹ%YpUHWlÑZ]M\T1THq &ţ< a1֖'4voF|XLy:&`Ғ9[b$_쉕y4b{km\1 Z3ҕ͒퀃Sۙ6? ՁG&("ƺ'԰*~\z|_0-L/ YҮƊr+tLt.HBd Ɋrx0%vyu!Dr P}> V=*CO4"S;g5ZUkRq,!Uq$JtjT>ʢ+ī*0FLP>q1rV%nBFlHyܲ$jVr)+iBOe9!h$ĕ1'@՘Kpw(QcLȽ g%af^!016T!MڶZ$vdѪJcGoMԿ ,fʪSt8u cci_@υv*"bc8b9gꜿH#/=̂cD'' g`;+FFΛEh0DHm]y9-bᕻ *Hg<".@R`B0+~^W:%5ˣ^t͓/kVU}peC-o5.j<pVTU*DA0˔RYIbsvZ^X[#H8 :J _> 80Dӓ݈ CVeG4f1QH{6)kfţ|2cT1Hd.؁cU6RmR?<Y8f?=ï!3U"h%*CH#lkaܮ/PAm3$ sV;A2/%]OIq* xUO2)LNny&ʀ5B'8$k捫DYOa=p*IoO|C{ r:mOږTԌ̿m}_mhP5odr Uw*ꐬIscLVmH}Slc;VѶ*9~~gEٴUj2W03w>U9O&$QZ= q c]S! # V9mS@nQ1`B"~NPAQNP*>b \rlD_L#vo)9,T5s+l1:h )H)Ix{ONf 3[XVZ}G`J`daCQ8td+i& T衿7>OϲRG`3GEj.j2}YL :Lϋae 0 /) NJF|acSE/) l j r/N/F H./̴))T'j b9yϓQ  ؔ)jbq~cxjri./[7>h B N&-hIKZU4s`P/ !TH0Ē0:ӐfV3C@C z# 'S0 &jM > ixHA̐zM;(0D Ա MDYu RԮJ̛Ly q$!reN`b)wo>w*"4YqF%Tb % 4%e,f@.0LxOyXt% ] Kfh%YJIe E)sqB8S2_wSPPdHfF'i |p+}#v\]j" 7zJ*NV:%bd!-3RɸIu%}<a=K=&l~tBmJv%<e{Z9,ud#=Scؒ%ICԑ1nHP b^Iv0o-_cGhS[~ZvIԊ#z-X`݊C3./s??i-+ zM7,h,L"3c4BM}g٤Ե2WdO)KFEw%eA3ʒ&C\ ׬:u=bhPsv4@V4_'%&`i љEX&hi%(#N9?߭(۔0ul^]c}Jbx(_@.}>6bJ;ƞ6T+_-w%M/=Z P#M:>LTwdYYNGo''τŦ56]'={5˦10-^ aU @n*UfrX63mz5KfuoZvE!NydWgDy_3:vpFؒ7sFk\^SAtHUy&7 oYu)c'c+.lZ EsAt5|a ᝪ}#Qh]<'>i23W p@4Y.ޭ1MHęlH}4zR 6 I\#Y|'r{[N?O<ˁt$"1rieuJ'홍Tjl{D#SY ܞ"H`B * Z8Ǐ$E)X@z: `?mC7ܷj J]]2{bS@7y~+-`$;& ՝E1I,z !"Vgb8iS]pO0{7H9SF F DC75Sΐkp,q X <@>O @D yZ)I`AD(9]DosjUT}2A]0|&m/ -īS c"XR[|LW{Ah53#K522y$BP>-C:;m8dw;/9VO zfwO=!s)iG,&?uN<g}vn%1*nu3cWt$h,#!5B@4n w-\ёL❟iI?u4R@؁%~z_f>r6Q&?MK!AG,}v O&vWs%!AKRʓ ȧw6rYrWs$EH+ɌK8f; -^˙ma'dN03F;o\-kN hP:Hr.V L!ӓ#JC9c:iD!Ԏ9yhv"@A{gsR3 T \qŬ=ӾOE CE>N> lY l)RW%Iθ8;b֦*FfH*ϸ&"i%7xO$o@i9m# 3_戮ى7q^g;tbW6P*i_ֶGjNFY<#v,dE#f/ZŊ*Y{ }$3(@{#/`fxmU1V%8XPNU{=3_уL;ߛ'46ǯK3zE&u╻^=KI6jiEtfV1Wt ϼ&@϶YU9FuPOr|m>y?ԳbғQtOg!eAs{m|ǖy}(EsX)Qy6P鸜CN[U\Bt>{i2Eo PTܡo#廹"fYٖkO\]" pU,>X#cvWl cX"{~"Zk]#{BXZP7 (]O=xA1n]HCWBR@U!]fKdm&?욼wWy^+ ! )`i.a9Tr-pIL^ӫռ^+tLI"#1r^A~6%>?NO+ W,Sq L}Lisb wٺj+%44>潿ɳs\bxA531d0+O#"Td2nūXo.|Zq aʝJ@e_sVEL]qxՔ?w+K|թǎϯ]pD2 $s>f=WS%@dLԦBV{yaƑk_C+Al? #<5JQV#D<:З46<ܹ`2Ga4pSic@p/FDL=irܚk9S4a1Tpѫ<]HͬF ԁ .4-?Z'Kb`$9cZ:RRp(TID눽RkpV_p` 5mpb!ɰny;;}L$*ȗxC-k$U1= $uϑkofj;t~$v[gzDVzyr;t "utud}s\~7<+./m<3]z'+ {FZLF=3$Ͽgo\n2>|w VntV3F=肋pG6*]sӗG:|_R{yZ w^fa_}hy/C8qv0ٜO7t;K걶k,7% ͜0D˨4v:njϵ\=c6AFp;cl-}[d2Ƥ[2p,N*(c$zdt邆KkVIziWVrA:jx굱-8 jSf'xIk 駀yJW8:vlthɔ*vg֪#ow_9ޡ9ǿ^ "( ՟1ŵ)=G=e.<"#-R΢+Wn4Fr5 6Br$XX(('s'u~=EN Z !.r.>TK4ۡ|\Zrj֍s e9fSgsԞ}*Vf;9w>k)f 5Ly&葟>j<XL%>r$gLfm?}a&;='GS%(S:31 +Gʋ76֐#V!N<ގ?;K v-PЕ(Z0T>Һ?Gmc'?CzhQuRlk?*H B ZG'HAuEp&aԌUA]oUם}~SN~FyoR|CP#A+ry7z:xeU9:k?zT@ٓ#{yc_ -\|5]+\lw7oϟ [:yv9|_hkrrpOQ}⒗YW,tbL(vSq+'_ᆙ:5] eٷ!~c!T8^=^0dp? &Vޱ0dhFۼz$qTgS dz%yl7ĹUm9ٵ ǺUy~r×)!<#RF*¹ zDzy&tYUŤY2u:|Se`Qь}a\_JZsl1sۡfKwY@#WX+id"Ozǰq"-ȊsCB1YRD A jtRMN)ggDirޝ^d ? g$mҦMYJ:)D~jV ٍVt=L2r@@֩9{56jOi䱷>u5DR{\=jPwH-4x^VVV<0 + G\؃_IGr5Y?x"nm1P&Z\5O: WK?J؉T̻RH$6-_oR7ҏ]%aٕ}aC9ߤҦfyÕy4U'ȗV\ -SVoa}G SAKںWe%%^X s{D#cAC%ʓ?I?$zΎ3hm5B-0Osfh "K]QF+Aoa׀bl`S62ofV%e]02%%nSA~V$-ha_LpD#)w)gV$g~xo뉍1*M$8ЫK?E'6~& tdo5xZ?ڱQ63W`YHt'VuU䆭۲QqϕR n$uxyUǟ8ucFG )uw=#š{Uk;tGT3dN FjȸOtOY~YΘbdr"a~mNOh%@ޮ%(F, ~i=ݘ0MZیِ NWlz![ rue\ku12ْof0rJ՟3lL&hy&JxidjY`| ζc׊B N?wz"DJ%#tj&D$DRolv|kaIG$4 & l3ΓoH^ h uAU.4/3*I2~Z xiL͂SZɵ tyILYh_ʂ _#p/A _ahS ]5b˿’ D \e,ƙ8 7B(P2LFQC .T B6k DQ: +pƄBKdQN8q"q5B)Wg-F,2!7gg ܶ5(8"]f8Úȥі9ͤʀ" Eؿ:4(xmHQs mNHP.N̏>b;⎧^ДɌ]1ū[>{w? pƊ Eߧ>am1ka D 8K3q5i ֱr7 FH«&j gPe~}.`"ykf75v^Ek,&Vz͊Ѭ?_Xv8bbOFbBlmȱKi%#1rݎI(&6掺b*D}ɧN m :\Z'zh5G% d~w]b7C޾6 N$X^=^# ē^JYe ,Ȍ0f+RT܄`yS@Y`hLE>:o钌4ARCIb!yG%l{7hM%BX@(OL`3KxC-s TMHs (jL.5oh/ zy s{i "[FR0uaFlx2; KoC6Ӯ#WHO[| oqEO3[pc\U0 f~ F1^(q׏I0>z,rdj2 hHW{ .p 7ER ԶYYY`%-}506q uj 'A2ACi]pFsI1Ŵ#+݅l$93@A&PMc׬, ]+*qIA\Ҡdw md@Hi\ӆt6栿XD+) Zf!Cj4Q3K`qK2H(*(*%\W" ŧ*clJk8:)(KI{CDOj\-c¥ʂOH $՞)J"̵Xyv&NQBҷ3l יx~^(׋UgQc#f })Ds!̐_[{6[uG`NTp[RiOÈJlqK#aew%clgRX5fg,YfFaCDF"3nJuQ R<5LXnkcoJ}A^QS h|dh |?\([s:XQ2vB=?Lu 1 +l-*YW/2I·;9g$x#+[ kIѸOxB%Ơզ4H>eOjG 7hwSvJ2r39n)-\et` O#}~;uL.tb՞DbKpd>KP9HQT(_ |z>b@59-fvdd `'הh(zw=PE)5>o-J|iT" diFkEk= Z[3tgJ,!3]@H% Obɑ,t;DA~ZFN3%0\,(HPvK}0㖘qBC&L"BTkx_+2*,̓79A,Ee^?8Ѥwi}1F'ZK؜ߺl/ӣjUHH)-sDVbm0!)Z4Ȉ%BF=Qaә̋bNб59(,=ykuD `؀͆\fYZ뚙*$.:l[ꪾrvXTP3ؕ;Gg'OVV2%=5\MY.ڻ]%6PO Bӝ)cv$+ "L*`a|jT=~%N!'j 暾2v~j<0d]mzJ( PZ eQLYxv`opq):5QmIJq+Dq&Z:ɘ)Xy_;&PAJPDGWFfV'X%JjfKW( Y&M(HE?ő ^.$jMqaQf_#Rpk9(ZRZt%IJn4+fC1{=/~p?Եz[DWO =nZ\}!ɩ$D`DO_g[f 7SfMO8ԝ 93K HՐAaKսM'n[Go8M/ B&U | q%s_[uc~솏Qh:+g\r<:yI;d:SbWAv7 AgMX3'8Ӡv+}ݗ>ڑY{w*Թl1d'UuOtbzw~;cz`ie/ҹp}}k/j% >fw9םF.؛MF ]T\Rݻ[N[w Ul."<1ǟxǾ5;H#C6~'o(;L "(Mi&[P,&R,+ k›%w()%WTԯkrDԡ܃YyoaAb1FKz7ND+;lHa3( a,h8?3G#@28='/盡!1w AJh"L`'Vk›%$(1!}5QԭX2^dO XMN%Qd9 Y!,IvE%ƥd0 !PEؠQ%E&J-,,2kYJ,H4 B^6_&AcS$k$HհDۅʲ JU4ؕjxǡ!яSӜT ~Ӯ UL=XURf}5BianևQn8N¨3J _^sSk{4; ʦ@$S1L@*xPC< 5ܳ5[DM"@BXRbɖ]uw,VJia= @"@t鲱W"Yr) ¾09 TEYEs"ť$'q!1b1G?LgKS{h9_}R޽S3onTI 5fG`5W2k%Sh?8 ցTk N|g32$S0Zvk 5$L}ʄU4.* kx=I6c*j <=ݒ%X1<S%s6|g e`| Q]YATqVd!ܓ,o  D:u=LbQE-i?4}#iH! C0"KV"8:ExND: dtvKH-lZ4$|SX kq[cSHvWͻ4F[,W\74WO"z)X꿝6[ͽ縧bDXO2+!d2tʝZ{NGÈP'vzSC#7oloݐ\WO?fRc D QàrdBUPs̆dHJ`q3q3 0 aR="DǛ+IwgޢǟKa-ױ  s%UV&!1F>Dc`@EhȱC,18ELJ(DBT/=oHB5q~SA:ka_EA6`kN[r UqM$p#AuvY(Kcec]15mWP%9~il鞑]3-1NcpV;myi Ug_`Ff;6"8ɖN4 ALZP<\ypڮB$v[,s@g.F(&Cw&/g~3IWb"q8[ŕxomOco=M:e!k ՚D.ݿ}xx9& <3lwb l][%yM{1w>VOPPΜt"TJzWVuĬV ,03 e0+M M Eb0㩢7c$'/BEcgM`Jhq%hoʶ{~R]Oc:妱8KA\۫q}|W|UHiݒq'~ah ITjqtRR1~e<~ܢH78'+ j@)Up5bUB`5 rbﯩ@P*$GpЁ)}QSd.Aif 'd /[7hnLGfpNԳ#Ɣb.*1ϬަDS[(̞*c}[Ng*<'mHB ́rNGDRflbccnFA /; Z"Ha4 0ZR˲M#d݄D`h"J_x ۫yMlzɭݫwY6;g݃S/shyOZx'ZHE'uH񳎧r[7j&ի㱦翄`l^Zq pm[塢*wss- _)a+0wa˼m";9?6iȲo-5 pv.t@zeZ_mքymML]uu10|MS/`y3@զ :DaVL/@X26ٻByR(}I;.I) +7P&R^S $aǥbݒ]7PeB$0Y%֊\*/RoM<#F ?K IwF 7pAJ/Hn@`ڔ-r\xpA; =oǙĢ+ e c,ѱr  t#Xɭsb%NT_;`z%wdwPuj_TM>{8FZPx66`0x%Ђ:АΤI~s.kuu/0v$\I"YKJCj+ a +")Y:,z  YkPxB2Pt5 ['!"M51ܢd-C#X-J @G6l'%D$%C#D,D VAB#I:!LTJe@n)zG*~3?2^}!>c|a%ư菾,z}է11"r80y(sde`13"mZoڨ nL`ݜQ.Zk Tx@*M,H&<(p3CjZkpϽ?VzY4{ ߬orbN4OSok 9+ٓ6лbJ+5{9GU.-koӧ;/ډq?kZ"B{x +s"9QH˻~LDR̗1oÁn 3rsLu8@c(Y aU"u=,;QY~Ut3c{l/>⽶1O.Y.(F9~^p%X 1MJ/t/1ʨ95WtZU,ם4?1b DŽ:f'l]/B-ۦ-tH&+y~uc5;%$Virfˆ bCg^͖TE$Sb)psv^b)(Yڵn TE*Fp@tD3oDwM^kz ǒD!b`Ų ; &#gx;gF<=@#t["X+,fՑ: yCa7-jtk[t_Dk"fIHe@˴jIlHTki-XAicq숥^wīloƥ 6؆O0We"/nr?6~Faذ^ﱝW`ap?xxj. tHZܐdaRQ?1qgbsim7v=ׄ+hŁ4?ڰmT:Iӟ[VGb="u+߫y@&z4kHmUk TuȈJgpVGivڴJJ &әONgu$.52{DQSK)͌'jޮL&hj%!W6 8QSWNsvrSsJ{րAP(B,1MD-^'_kGdA H!r[ "sQԬB3՗5p{QH1 bˆp_{65 16"3Ah RAuMAAGg5ŢXAu`TDN(&B6Zz;_74M[zu4d%=[(p#T?lQK63,ȸ4hy1%BK0IY!GD=i^%ĺ_ym|sX*at,2QѺbFBf9ܼuİ-'^~ƏO,Μ!˼U[Vڛ=Hl"ejX]n]" L[ z~:wplQBb>m'g)HC"њ'C| z~2\OoBG7-2Jqj TGV}# x&)adkJHƪO28#\?6[]6\gt26*5HdA(%LGN-}>| z~L_Ѳc/`=lY=t=jH9\}Z5<_ č,(RZ{h>O=*\gz/.iZtߵ4o@jpqV>г0.inFΤ?eUj}cO}9)?ϔx\A譳j+j4zO㪋x񹤖մ'@)Gt&Wy:*`0=], E-/ s/7Xx$Z{c­ozQ,-E~>,=_i4Zx1m[lD(E>I^<\+/%Gېܽm_,NC|ר+AޒpO7Us\fʂ0=lEI4gh,pv`D- j0Z@L$LpG_wf/V2?7/<6Ζ nGtۏtqv#M3՚鰥z_$=Vˍ$˃^Iʼ]p?IzW+%+˽fLIZ'q]Z@pb ]dI~V*49հK{N&;-p##@ c}8( ߘ1<,B­ڧhƫa7Z{gݜ/F  ^ %XLlM GX},Fx!Fw#we>e$;U$* -MXMgU8fa䆖LJ)E' u8] A}TTœ.0)2M)p܅@5Jћs r&_jIV\}K3N# 9^Z9_ifDq)%|Z x"4R`y=&Ձ!.AzC%Em$REVEՓd*=(L's{fsxWͿ[Юf %?b1sFy\͑|UU.oԮG,#vU͍v<Κ3rbeY\׌>RD1K%V۬fYȲHJBƷ>*& 9ti3=#'?IQ]xQ:Va|YÃëAH8/螕Y,&vQKLZ:&0"3C8+vy?5.N A=7D$ GXWw%gϲ]2D6Nh4\uvgwM2*a&pc]Μ1ص! " slL {tstK?nd.xC36N`]kVϬKJKi%a_yMD w*< Q+LâOz}ΧG?yq׀Sm?Ln!S))7Wp1kӨS}%骇aȇ|{ x$mש@귌&ɚ}J9/"u8⢣\7JgP.c j 38h]Ԫu' Ae $.'(@S^ /* MGSD!Sw:uQwh dx+z-juD&(X߄)O#Xǵe=RJ$&r_ꡑȂo+_ИEo U=t%" Lb% O hZzP_ظ=Nͭs˕ Ha},c|k;JWTfj 1[i7<WͅJ. 8JLj[q7<gJk75_ܩouYyͼMh0V2{7HJn}Gg.$,AczvҬSG&W*ȮJ7Xl 4+|%ئ{ޘxkD-\hٕsblpj`Wy(Z 9dZIzZJj[|O@V$53HJ~-ȝ_kA3=Eݍ}Zy[ZT],> 9t"-  Z%oݖ\!A9hY #}C>&q6%Frc wHJ,ΓmI߀C511/ rRpֿ`6BkAk BLˠOuP M4!+ L H`_I1ҽW?GSFNv't$?_~`'NVБ еd jU˜$0UŞwFc~Ih{`΢/}@uKl@8oJm=CgЀS`tA8og>@X04L@F]d # ĴDedLa6@#X94y@Zc! ~f8{Ʉ I\H9W)^g0@ŤU[MTe ;!Dt*;lQV L}]\ pyv1ޠ~0i$\s@) 4e]=Fm{UUqMfB awGdw1;L\"DMoLJ%bXwB>mhGaL{rIhhk8>E['I\RBɈ'4N:c݈Ma&KKr`KNfBQ<φ(. RIX!Fi%R3yVg.QJ TVilUjq.ȳck VZ}pz2H̥@( LtRHJ(Y҉8+O[J{AA HyQ\-Ψ$! @~oXOb^.TΨ%,l (ffê Lwi@ Z_veAL;럽#/{w/yy5Q 4 Kb[#=^A[\d 1OJ^# &wO8ԚF eΙiU/]n\`N u$mU&s+iL0l,a5TI//F]^{i*Ry_E&{}g~NfIG>:; +K$x}CqNށ -합- G#{N'KGV>:qcėfD.k?3"nNŔď]L{:p ;k;s#nVSH3s/ו;ݿYaՉoA|=zBO;%nzTEp|7I8ASժKҾ/: ˏrOHb:xs؆WΕ,caplP:xoW\ s ;/LFo4z(v\'mg_yz+QXX sܴswT,Hⷨa2 )Y3HzZ7ps9IFe}4 3Q Uҿr[ImFOώ5"PhռICly!~Y/uK ils]Y#mb."`m;7=37Oo@ 80hZ.:+KtVR@ I"F'&-J]|19l  8B`%94=}ƴ0]ORUftYSfCʮt}Wud8RtP{4]IW|aܚ-viLTY`5ߊ4~vEγyٴ7Tc*<7LeO *kE0K$z&;uϞM{Ëc+Sj3R9F gW|sQIH/n=7n]qYP̖}'@V <ţ}G0i6yv<7Bcy>#  wHYr+]ѱҶ#*<u;Ms9}w^3mq0 ي60P+$uvm?I))Y(d&ӆK~M>/<mC3%Bn0-^Ws޴o<VGYZ&Q8dI!cCjCZ@n0^0Z+aBz>d(-`V@Hj活iMy+%2I(-N!SɃK݆MA7tKj<'~Ai%`&FጞkN 4QQsm9gT,=Xѵ-NiU9Tfc &ID93۝&Zh㌢CWO3 a |{+53~ m{ÎtkS" 쑕 H56yK-#fɏa >t-/}ӪN+/BTaKA 'f&Im 'uqwglcA8\ETV?7;lW]ԕ\xxV,It'软=1}YáGOvY/IkF/Fxq3O{񀬽ѸZRYAd*q /kmKQMP& W1]f?׼]\yD%hLhqưC?fW@l' ^%qjK݌agIf/޵Ўz*XRZmg^Ưh$߷>N_U2JJ5ٌp`3O>6FDbTRi5Όu@S*IcfuF ) `T"$W1X73uE#9=]x;Lxg4ΦU;==aS a6k gRImwRFwi|)ŝ'~1nzq}O\;~7yهۭ͐[?\vdy'Plړ?18mSq)ˈ4v=ې_f-\Nm6k ?Brd=u*Ȇ /=JRret4цd;?L=ʰ9i%O>}O%I-ۭN(h:whS}^Q (ANmţfu70<DnsKOL9ª4=}! "@NOC:cVOz&Z`n z햡d.WYa5rּ+?بW?4_ iqvsG>)5en4xa1>ٍ` (eiCSӑd9nG{:QPIdaw{%<&]Wݣ"a:,F{\3ŸG -" ǂa /۷IYZH4>Lrr摑*hX⥣#%bnr^t+|5֑၄)%-]x|ٔw;ڴ8kX'bYS2s6Ɩs:'Ϩ%%ǻXHMŵ oVCLFkmIdmȊeqs[(y;X7%[>5_nLꎩ$=.#E̘Vc*Iu?=((Y!nnK2kk0UHΔ0bAOTqՇR$[\2]pL$ M%Y<߇Tbv8E2kc? l)պR GvŊI_+ƣ)hXŊA6N c?iL o,oSI+`Q: Y˶}A?L Qm Y&aq(&xg$ 0P-VH5^]vHb [є&1~953@GJ@ͫFJFѳ &%j@u8D㵒n&" ։0$b3MUfK Wwo>[宧>|.vm~PVQYW+??Fbjg{e U^S^.Ӧ_+rߢG?W|`jWVzsiɋ~ٹϽ\OAVԑخ675ҥLð5uAOjui7M3TiZt usuAOvu}Ws> $8B mս/QYW+?gvD"LN<{i3y7K0e.wIgnDwyf9֟u*o}%xt~5jH[RV|O̥4/VeHFa&mg$R?u$ޞǐʌdyy U" RTiSB< z` M(ħj) N$\v7HUy$$BmSGe4œ : )-7f)&Y%, !g⣌X(\ sm]W}.\\?!G "*hb`͵TM{1A95 1 VUNϚ$b\]:r%[Ep>x?vph̜5I@%M(%Xa6(mDS 4zgM tϮn7 uUOdO='5}^_ie RomʘWtE D$tP+%5kS߮w&ʛlɭ*DLH2m)V ;y,`55EdSx,hZ` Y+a<( @ LL D !Ϩ3J)t R G>+'K tLiLxHgɚ C""{tŗH bC|f*'MWqb{AwQmoNa):O/7] ѓX_lf/{_G߽8#(-H .몫J2dkWZ vՕ"l z' 6r?,Eȣ+_Y /`.=xptˆ_N Y5 /=ɫ:=?.ß1$&2bu#E!oy R<2?yOi7?H-a%cԟWaV"B(jؙ,z̫Cd&X\7!>%Lm:?E脡~h/$ä`Ak!p29gg7\^r/ $>Y(#W@KV?`%6mןXQ('Xqt2#4Oc )R]^ U1R"ɏ1%⮘"Dfg{%2d:vOdqrYgQBC>[C kZ`%A'$;$7 \ו OJ<H,kgƞu3-=N* Ϟwj#è:飬 c^:`v|G':ZbHj듫3 ' 0@SYi-|m%g`3ܿSNu+Ox Z8k2((Q=-w1eK!1l\ʛSۏ??/yZ /Kg%EVOQJOjԷP&ԊDFxU=[o^P+))Z{g8 RTlw}z;saϯ|ᬤk?O"uZ : ʞu;‚#FxMH/nsA<RcY6t](D&U^{DERzwÆuz /ޗK3&,*Rc[#n! غQP2kv|?(7誔. Bes8e2T0f<`,B4@gpWLp{%Gn5dΐERgW1Jw_|IB *vz"a ʭKq[+b8 IYN,{#ݡn n'0"N:|Z=TCQq'u%޳H"E>D`"E:;Cn*2K_T+2VШ* Dɑ$#e{>u]0*]ęț @4Zk*DX OD®,7O2,.|Tq;lzeJEZ1  Q"UNė2%ƺlƘql lF2%zeQ| 8@?5 IN Y`&r%2Jb Zql,]~H2T,Ha0)oS%D옖 pe][^#LY=Ey *c5kU\`VRՎo|o$N[_veq>+=4e*Vi wl˓俲G.=?Eg 0bİr=Gv-tѳzI!Xa=aꌇ,վiigĬò*x<Kκ^o,}ۧZO(stnF qԍ!xHl bO|5˝F N1x[ 6-N"{[ I>5V6,|yFrȒj퉷pSȝ?UܮĞ@QXX/7ȬIzVP{yh <ƭS&))0M,TS>xϡ_~jb),yV9 e xmSS5 B@8gBR9k}w+{ѝKmWGN`0K2~i44/-e)mƻcs~_j f&0s׋Jӊg; p׷l׌gyQyˇ<`亽,@aA4/jQ=LxU $G fuw qP |x0K$AW0ϔs,_\CRapQjz(8{m`Td0cIϽW7 D"'»s)#\H,Gj$DF~ʼnnIK#S ~ h.xqL<V7DnR0'g.+ Q?\ɜ>g8jN$ Z8z\8ؽϣw=KI\3,}D\HTI-? _s*k1M8@B뮏\["^‚Ntz&ljD̈["0`;KK+v<\;WwI !Ҷ{@-%SMO=P~gaX^xՒbfU$sqT^RԐWl{BRuu0YiO(n"Mg;cf4Y5l`2˚0KDEnTЭhzO:\=;6>4$\:uubոe# L.x|+--ygG#,كM33  ZEKtUu/ߙ6ŋӟpT *:uB,r0YH*ߛj*Rz`Z432u$Vm3ƙ͚h^eԟ;Z>aE4I[5R#AF$AqDd5:\n%akƿSWJ1A^ :aSE]K]sR>2IRJe ԙx"Jpb{bzd&tZ֊R%[`B9)Opv,1k#ppͬk:%f0϶xPxz ^=D-$g u5;}Ru`mcίj*y7-Z ts}If]8D_1u6ew##׵Zɯnsk6eAxưIIo"=+Zmoب}2+3J=ґ #CDS0dɻ:Zm9#k;vpNXk:5. )E[AbՀw "{QX 9v!NSDվUX'#gYy24Z8Ӝ&GiYӮ);`L684g<1n|T;,J͍o8,zԦiVƼz4*Wڶkͦl ŏZeΞ.iJ.[!ፃkШu\ab2≁b8VbُKo2ț-US=oY7H6f֦ tLa~-%C6k0pq)\%oβ:W2EDKW`Otۻ)L"YɊ͚9~&BNX.v{zeYN0%f0j͠ M%]-;H7# $@KW q4XvFj/3Hlغwoἆ%v1#c(XdTO>?p5Aȓ3R븊IB+3r){\gf vLbRL :l^|yrpSKဖ}ţ%6$(TʹpBdl%9S8ã=f@ôe0~cߓG `Anb<\| yWxn(<{]7ʸ h2gC؃Z2y$da(2?OX.ig+{ԆXS0 A J-tf" ?1/[v:"1C$wX6<_'}c^X邰'ڬ9V&"2 3akTjI` P `n|!r(2 ? _ =h!}$ 8 8pD1|ַ|"͆%NY+-*w,5+cqF`4m u?+0s>yIS"ņ@*ѼFw,3?&hOW|7kN&zYɝxM !ߔm7Ou;KR"6gV}ok-2 x\7ۧ8UA"s< g] QFޞ&lsvK~ӓ&foVc1]Kbő$KЧZ 4$j[D>դKՋiF%(Յ.գM⪹#W,㯓>q!pCC e!,^D) ^D$<4kX6UBrIlG|5 RW:SEBǕM\c0ǮĺZ )GT<gU&ӣ,PSF՝iLd)$br^s\WjuNGݔ;ruJ'{~R%k0(ڗgq_f?`ձ}xҝtEΛx,+ݥCZxd&rIiEqsT7ıJul{u_xpXvV)s^a N$SnpN_>]jqj_=kq7<wj],Odgl0>+mg c_D'yY,q5O(S?v!h1CW;uﵧPa#!-mb7ںyp3mpꄷ5TK v3;]bx'֫2h9[gK]<#ž,gR޹.nT *:֤w +^'¤}8@ffHcHʲ8'#biP˃0 D$`\Mzli+*/gRL)ia-M")'*x?8NHzaI7e'iLuJ&l6brBlfc I"y`ď'I]!OlEc.8b eC?~*D<06M\oE]"I8 "5Mf}~(Pr h%w#j f-k;/1FfE:g|^i6/ǩ EX9~*h )zq.O{V)t)#ǎ XUyna'vhT9BKj|v#nVbiL U8q(( #)%`pU$ቈ6,2$E"3V t  Ves'QjWЁ .ti" !Bװ%ANd"^*ڬu GyS^*s`^ 4˺Mw׭ Q EsJvN;ԚՐH&('ND 'YHEN Yc 9:%g,URߩJXh)a ԙ abn@{e{镝mO i``~~Hk]hIH,v׌ІϽhAYAOʑ  hU%eIyt']'=z/?' awh]>02޸WwbvouΦ Jf5Vl`ՙzrxB&>f~NFj?g(NUJ`ZIE"2 Jw")U\ C`"s)cAM{h}wֿ{A$`b[tK8WX Fx+z<'Ogmw+9&"Q9Rǖ8Wy;Pbqг[z#;ǜHSˠjnM3"\gz&I\j}{p~m=rw^?tsFD W jbTlLy[)wȮ쉔L0bXG8fL=[1 I #=^ݡW݌qx:k` 0CϚDLgs.z|gZ\Ðҧ Ux > le1*(ӎDˠ}%\7MXF\([wY2jfS.&BwoOYW!%|{˰c$ E̲ OadQZvkY9Ð`{b8tԨ=Mϐ/,l ,CJ˶=fR(OG1;IVD3}b2TgY%wdVno;-QXîG~!13c :Vqhfϊeh_48B@%fOQ[˗ogNᅸrȽ!i8@Y>C9-SyahXk*\ \'"q O$ɐCNxM|-PȥGzRH:mٓFi$T جLHq!P b !83A8_ASp:qP˰xr*IiO* U3  TiBÛ93""MqKfy_:ÃBSg9`8&]I3 >i˭aN^8$uR)('/R[g.X&M %繨Vvm1Xw<1tlaÎSZO|t7*\猿N{mѾ󟷶c]tx'<wIR.' j\3z-}^8,{zmOUɓxnxȚ==B^S0SW۵ [s9NڤwOUt Bd!o\lߦiը?ř3{ݑ\㌜8l;.O6ǏFI x`pq>[E#[{?4 eBNc!L`6UG1g[YŚJj%]?Z < 2kW1s.tkV,kH5BRkQPŽ X^&4ꆦH`GB-#L27Z:aqJk0$%kUSgMZU0or&Ԭ% wFOdU-)ٮTEBjj27Ƶd!8}+&N.ɑ/ۗLdE" K% +jpELYeK8;?WDOt! %ޚ u<,pB-ZFm-V1O%睟rUM0V:!mmZrXrs:rZ . b/쾃Ypy*#i{p%z;L+[:43 y1܆-GPT $n F\ɣ(8*S='Kƥ[ś?>.GsSc_w~`y… M]QuĀ15 [׻Qvq G&Ba$8DdLk͋{i3#!3TKo/pcZk|Elj4v`sEm'}s5Y A~W=_=e~HR+/אęR\bDYE;;!Xvy^bޭF lrOپ:{@:%ES(`!Wgh|\pK~DM;&;' kte"tލVC  $N}Z 22Dћ߆_>']νϡ@Bһ9pd@W Xs9孯/CuPhu}3;qYxAe†#aTG|F_/:4ggd~י )LrHyZ7c>:@r?VXOk$e_24Ako$S좦͎}—ʯ]:ҵ ~N"Aa^{U-iiɯ]{f$JSCSjxp-AT=I=}m0EoWV}N`D; U"I(9E&V)ܦ­ܨhS5ex3;lP H?b;"~ "2۳ ʘAu$Z"Lrv9FdMKy' ^QrNC~TWnZu2['I>Gv r(e.gRM\!r3ZZdǓ6$vW1de2)KL^9|i ^JV7p1H"' $ 3d~ȟ`[dɒ)DXDe$^%I\IwۘރZijDs9d^XK\IDlj A0NFB(QVpQyGbĺVyl`-4b**H#,UL$DvKLA^.xCs /lciOfTY8etՕ֙X% flB DE:it3 di9AbaI.9MD9#wնoBM>V;֞Dжw /f-sh{pJH$Zfȵ~g??z4\5(PdH-yz!9kq\8kcUvҔq>spVA$\hUlmQkoAx M(Kf@%AB>#GUTĆ,"}{1=4?йYB͊Ֆu5NoSjoesf % ycjbڦBI` Q KgBL-jP¨EJxN%b߾sfc*c%TDК5RЄ9_yua=hV*q)-}ң~ֵ i٩ kW+Vؒ>}"5Myv;46'Y`5 I+ݧ_S ,N幊7Nkcaxi@}a4Wռ>Z;3!PŊoBSI(!Yʼokҭ6c[&.M'Q!K{S>oE h)v4AUW^J!&.¯ ]/f={F(R2n5V閴nO~RZsSmjwε}*9x;?H fG]x5SA_,M:ӗO-Xu#J~C.5ō )ZG%b/n;ۇI-t^=4njϮm鎓Y{Of^<{g N,#pcy7 YdS|uZU/^zgS ݨE*ȁII2D9˹ޯmف+e1VR9n tjD~տ||/t++/3^J.Hֹ<;\J n7,$\1Sp~5ƅ9?xwot|>x*^1% ]>x Ol$Ƒ:øG߃m@.\FCkbXB_j\C[na57=i>@7ޢV!Q7 ro*a/=ҩ!VTiEk(/u pަ8$i|ഗ Dd2;d(-t"Z!/`O]o$[R"o2{ ܥ6srFmab/z\LT%Dc[.[zZ 36C 5vPow^h8dBV\_nMNK0bM0?%aBrª?lAF*%NcH쪧z}AF0;GH 'xM4t#ԡ*\҆]G8dɀdj5nL;\P` & N:쐲IzK+-@;G`3L&i#b5 VQ/. JB Y%Fgns?6K@k^?Sz@-YҲY;]qfi4FT#9?[X"%|u 1Fhܘ_`[pZ@1y=[tѺbo$&nż!zAabA0@+<ʲڌuJ[PW&T_սݾL_)fv-6G~ ЛH@05qyj4'.)V3 9ߜrC0^gf@pw!,[-ze eXck𧻓rιpnDU,kSeѨhH3Wºd@趀qZ)P cf*&Y+ =Ah:t*IVdE ʤ4vw!.Gga%tCaɥ 3s*bڡ~MyNDpc+? ($"[}7N˱ܻW{׆AR)nfR8V׻g* 4}@vZmg0Vͭ9%cwO; u#)1?:{L՞e(yKH=:\@Ox;NVͮP=jƜI X_ \:9#{^P-e$Vvz(JOHi`2Zz|]Ν!s^zk #'(cG' @愔vi.|?x`"u"Ab&eg$a^% Sw2:wkYȉIQÁ|÷? f7#Ťq&2Kȭoּ]ԙ)qT)"C7 /hzTդI(@2pRN&dh4">g P' sry6SXnD ^$ Y(<wdrG0Y )Qi>޺gl5(xHkek %Io8}6Y}Aɨ˛JFp%ί~gK~!€.(d["ld;A &#HJIԈec q8ڨ(i E!lgqsGؠY!zH%^":@z9Ǥ}H"F)ڒK#_liMtA:O,b [$A.4ZR zi[i eQb{yZ>sG}[8ٓ…)KA=*MaǮ!g^˞t,\2et f$u'-(FA[`a =֊ %SJ&qA:zrϠɇ  =8B* zwqO2)Ġ\!?޽!*{upgb(ߡ{Ҡ[n̦[mDpvkCRo߶BX:4Y~ Z)z#\6Ơp1jґgZb KK6 6 V9J[Q]R6[4|>+9/"L  o -$[_p'˃7:A]@) v6_4OR6D# =v޲\d%)h e{kl˫|4I &(Jt*T[||W/<q358HBX(s2"(w8y@zNpk-ciUa;~ Ub>s_o;03?sʒT;϶Eok+u ߾P^ x&You[.*\^e} .=*]S;:Hۿaz%z1yO( 'ɫέ`3p4[va!63R$TGz;ھ**!).`ߦHZ^Eԟar5`\8J0˷ e_װ[yW*) 3E6ى6EB mlU(鈳}"Ð}eozlXnea5],ww"&~pG掸z"Fсa/~Ʒ~>-wFL{n}u7o)mꢷ+yzdȑ7mkwґЩ(+EvO,,P+1QWWl(]s0I4MwMyU80p1 V@K |f7rJ-d/zۚVש%c.\JYd|o4UZzw\Sonum8~!j6$ &7X*Tg"Ji$Zj0jl6>o#/D8bw_}Yo6zvS$v%%3= ?%d8Z4u6êRH z3v~AExEbgކqUS= 0?qp5f KJb߹( 00 K!HeӂIɆ/_OM|qG|7f Ή?擇tbI~ŬKey!1OuK~iPzeǼ˕bѤ"YnWhKu}YF-UЈzRooBيڣ<a:fe6x?(Uµ-z7 oG9lRfO|ŢsNB⧛IƲ?Lsf_xV_T⊙>/=kEI_{6HR-]v!$U@"Bdm^}ð08|;$UM##lHk'Jz=Dli`>'&u Ls<@\Xn=S2jsYcsD;u!\ ս({VZF5ҖD\nsn͆lտ1ޥ L]sI%ޙƃ;n[B6«2u!{O 1kkg!¬؊u渪k4 WGh11wqZJY];c:D[Wa-]Uu%1ۓ'6U-09eeO,8zLQĞTT)gOFbO'ҚyrʞSK0<YP `P3@@ "P5ɿ%Z10pyp PLTEhdE_5> ϔR0t%|ɲ`O䁏I|L@0~swl"0*1bo(%%a()V,!&CxX͏ͥW٤g,W+Hܤ5`2G$%pY"?;!b o.8P=<{ؐ*pE65m7W%~IcKؾﯛ%~mf&- Bzˏ[ )sN_x3UC X79coUi8}Kb#99ظ,aRVmո-;w,;>sA[KX$ ;>m>AX4>S--™(0&dV'/y;wU)d*j2x{+YZ iM=E=v<IB_RYmoʼns%(n{#4Qwh)$ߊP]D2=krM;@ϼU rb.b f%q%^xo$3ђ0x_Ło9"Ti w"0 &+>sNliqS$,85A! 6dT+8"9қj}"TVDg{AUvN|\_~LjgFA3e_?.E gPE&$Y:(!JLl{;lVd@ H:x-)mOAYOAЮ=ZM a%ɢjP RnkN\"kHAYlk(]ZзM;]C+,:KboxRT&#q2}Is*&S~1.$-ӗc{|+R9]|WyVt{B ܍T Ljڲ{6d?$:jn* Kv-?}dF-0B!mGr\bZW^[H:M,9&Ɉp=ȜCUIdQlA!Kٜceڱ1IGuuOM3D80s Q/eOedXR7  2y kQ~.$Zטrή72` %dGSʖu$f~&KN 5UЕh|URp[/e&ɐ^\8*JF$oGҔHf jy仧DJrDI*Q3,;1T!vl TVEWa~mBmq7['dž..w{2be$lqwۘ$OtD_ DLLV)OK.|Iv=9U_8i ,͖n;KI,U@Ҵ^[-B~3sg Ul6w u%iqN[t-_h٤g <=cs BГ,=5^$mWJNO;'|3^6yzh= &Q~ђk1=I WÊ!b`$ڭbwȾ?>OAV]5t1`gVq5G}36_YR;~ T}rwI$x8O$GT:L7KxLvim`+ؼg mv#qYª=&3"i&OB#&;ήAB DNۯKP#WApFlВ0K 2C@~Fb f y(Y{`i@楣)], I^>nَFLҕ0FmY$LV_V3GH?.t[=펩 xF۬1iҺTɺaV+cgȫ xj5&E˔ً4fg.vαAisW:/z*P\dE\HfxWf(̛ g iUybY`QHjEfUYr?iV:7 668sRbeh}%#eق$ZA[Ed%c8YEE4 Q)?~kjAO㕫G>638ՏRIW5ǿ8AVis+NJ ZXj˭.~7=43Y>|Y <BNJԗy_y񯮚h̍mǮma,3(0{NF-TSt^>ҤwטL6# Mp5/$@@ }V|.\L0PS-z$x 360vdK0I HrM|{vL3A&aQ;e؛fwćߕn#.)-HV:w)[m,uȚ/?rO|$HQCnP.qx# XV43INlzSZ=]_Fpֻ'Xr7N!f~r#8` XΚh`j eI ʊ0h*|DX31DR6[Q$)90d?2,d8(ejLo7͘$2WOYEl{fu)aǁ[{ 5-A#D:ofItFaw5.j/NCy)j{M5_ DQ"CØr~z]$.r+_ׄ؈XoD&0JLK,;o)%2F ,   c֬9೭èF^$I c=7~OsJ2jܯ?o璠3D_(eCj{/jUYstr!BZlo9^c#}ks'"/=MVV,4J"QnqB7sso8o]'kco&`*csR x%#cÅ"޹~ոחn~ź|{9 cM &Cqq[-Ǿ}tːJpڋKCmʷyWrFld $^s~) }ȡ k\?G#|L9pk&<hz JG/9gH/-%-dJ2 ž?q1S.] ;IUbȤ6oŗ"[^̣KphXAƮx,}:ܚ nS1 ZO$j,+'9"Q %ZЙc!V< ݛT[֩.jT^o-mEM"fK]12lRb?=甪 )(D@'>f,awMU2Zhg0jHzNThL}|,f/haBȓg{N`4)"CCZWawjٱ`ĖH GxM2CYb6-([p9xkBV1]$=j E %kqlT:{DHȜY`t07"Vbx &ަP &)Jjܛ"&݊E!H "JE"HOYri$"S,T8)gI@d&,y,[TRfPpMbCBQ8;U:pe {=#"k(yyL]ГOifzYN/t?ԬFt˒!Qo&&,݆5{lިNCn^YN1>W}\+ E@xZX&zŦ)$9}*'zGc"]Q5:rG$VN7*6+ [De\od=>uob:g~sG/4sjç}ù(֨3K2ޡl_y^]5wtYϢ$Yaso/̙7ۡ- zAI|E q'6Kwzʔ WE7QFPap̝YO|ܾ}c^Cቊwzesr`L!Ygwa$,atTޑ.%/pmKH[?c8lc_=5A3EUF@QH&AT}w_[*/?ޣXtO ֢d`wt".EdlJ?_qNvP5/qyB(Q}6k;ʈ"'٢wó>eOPua1ڴȟEf%?M jH%V\ Xwcu+bń`ۄfSY5M8QM-R ,6rmC''\  csa7 -BD;!Щ!Z&֌@nO((܎V'ԑsR=`EUטY=D ?_*OoIJY rf۞fZt.qa^y[  "֚O5(p!E([#ΌD>xBȐ[t+#ZwbOXt_IBD' `]'[l-@jR$ :S_I!d3taՐt^!hFPSJYcVO_B?Al!9(fIu+޸uJX=U.c/@PBzY;d-< #$u @!>ӊc: X[;El?\HI?zF m,Gښ7`a7ba5ecJ5)nm3KJ&D!@RhU/lkK22CH QEC4LiJ_uIS\yއc|e*<;NOpdLTOأhVeIXxn,كnO43WGe@ ;=CBCW1Cz:cp)RT5˻vjئ¨^X,VȲjm*HBuȋJ nvu Y =n' Y}AH5Vw5³N}oKR+łmk96iq=ժջWDHɭڗ[Kؽl5m q\+ BN!3=DkK{SGϧ%F0Veqcm\1=µzQc4#[t &:ym<}Żb3V4.2*4JϽg\heEjڣ-\O"\ odՍ = )pƵ1IhJQ Yd%v5<ĠyB&VwXˈwJ1 PN59!6,$P+u0X ,E #YDH&) P. b։֌]`#`,(+F qbB]GY"p p \T<WlRe0Ɋ :Ց1?FS "j A @Q ^#뾵:#5ҁFD8*F6r!K  `"\E$,i.q5g  'IiRބP S[!B9Nw1 {b >]z`PgU!G8GwyƦ[V?qeg!f>y2*ۛ}`:\%]w{鐫 7zM_y6O:tKO:^9 &N, dv%oGھ8bSj]3YbIa t^lZ[̎W(òٰvu˔RvإVӄ=6CS]VU) 6i信:k6D_z0Sr=-n\.9=SaEmSo]bY\ GD!)p0wف`xT ݖ-G*[XqQ,%[S6=Y:;h.O"fr-suW+VK2ε|MU]`Hmr\e83fIa`@?7kڰPbzO`0Z D?ps`e;Jlj,*bu~VE}B(ʭ\u6ّ¤t %sߤM.H6x߳:CqP2".,Z s ~}n8•khN@-䧔eR3]$ǞmvMAN.>;ܹSwZrZMI'l8?uA ! H+\? R vR1VIjOp7#W#+BlZN1" xl&ǠZ ~@@ykR,P1NcIqK%ISYkZq(dX!al`i^2a5@ucE!(pĎD ج,@ep_*% $8xȔT2DU0jVbUT( `x%ʥ}3`#RjdT:s> 'vRN9̈„OiqRO[c|,)l,aح#R/#s eJuf c,Q%%՘7*P&-bY(fI5FW]]eHɗ&ϿҸ?9s ՍIF-j=9[yYTS*)ʔl>09Z:*yY餑A|@,ÖN/ݙq{I7׻s n/jY4d 5ʔ^a:tݹlEoF*W&-2Dpi7j_s;>|m}] KwKJ?nZ?y~x7V SUe,C¶_bR__l 8f;83O3=VJYL-I^GWf# XrZS|:9署@a[ł{z_S'NЀ7]Z$#~6eO+ 5 :^YLy[P.ܳ*b^aHC/=>ޝ܁wRP$e5 l;<}"̆?4>dXe֚W}{e݆u[JbP&0mҖh" Fk͒S[|fm0`}]v*xXð[Ԓ)S1Zb԰==VϞu=Df 5KN2 53s8-3}Sj\,LS~!} aHl>aY~Q& u~HݜѠ#vϲ<qV&]p^\f"~@#W`?Zᜨym`f`(tKO th:L,3ٔ+-gFVRa!5&( 58l%Dg1/MRhJDx bYBЄ)8F#"<, &Rh_shX,AMx&9jPCHhغ~{_|;[8B#/$2WL4糪WMfڵ@FQ0$ aq+=a.Lkf v4C_,Q)L2K"jRNK̹XPPD*N,ؒȬdh{Iyio5*DV58SB ćIf6۝-|{Arqr^z%dkfįDI`cKWCǤno亲U@q#KSD $q:`BGntՉ1s<#|.zOc6J#viZW='ѯzVzFY@[:I*WL3HtEzzh[kYObZ ~9qSVB]?k{X%4Yj~_./M+ԗ(\δ(kHk^˱ifT:߃gOW ̗k 籾X1P.9V1;ZilxH41A.1I1i+ ;' V|Ot<3 %ÈbC8}8\{p46+I?XԑODTh//tޜk,mb_j.5"7zz1螜37uӜ"$4 ,Rs5/;(.m3" < q CkK vf"g5sS N$QXҽ2d@mG BMY{kDhP}S8\&@Rf 5r:Q/-M>hp#xef`Eo6<2)@QM eL(T**"EKέW8lm(D^TݫAJP!v{ׁ}zzƤ1ow`&^ȄWnI, bԎ]obmI<^,4[ʔ~季^):)Z;u!@r(42RԎbFoA$%8hJv"“RUTC~WF!K~9SWY\*)^|6zpm9}>_P:v gYd=חQT45#YXc#ӄՠm4ՎRϾYRΗo ֈLdej(4ςϛ%A̝ftiDpy(ٟDD.c1gg{O99KI_GiM#Ryv'@6y'kl bRUYrnIwV rQke3G2 :ڄTDa9ёwm( b?- U Mt%j@Z[J|]U$ڧ* E^F4ie V89N A84Q(2Ḏ$3+jAwRו6["D^7M:Ym}VH%I99xA'@ Nq]inQԶ7 IHeӕdԨ7Y2n6%E\]8EɈ80N|&'W^rO{rMM:Ub@IzŻS[)=:ꚿ#;JD1Y\_ :]֘eԄMe-Ua`+v@$J5MTk-),kX3mƼTL\%26 W q,oZ’SNFDT+ ((a~>|hb}v* =1!?5*:kO8? x5y8OjJ䒃asl+!!:&ͦj%+36zV MX37,*i~}7m6{G ޯyTI`Ѿw~ȤDЕ~7D!N6FNJipRO)#hl$2ڌZdrBbǥů/͠) -LfR2֬D߸^R2b!,#ʒRj"dnZ ju E@"֙>Mh2aHCY"$\:s0>e`3z<=$yz!4̅1Dd /^͋47+i7!Y>ͿF8^r`*[SkpcN ^ֺ\_ [EXka]/rM7΅:Yœ=7j`DL֤J"b/O}:T46e+XOtMyE: weVnl7ɍ$fyH,gϕi-`mGk5$' >joDLG6+ jByRI+z~ O(b 2eZx|"lD#pOK[ l`拐RDdӤfDB #;&I_qRIIܱaԥM6n0|Yo`ξ/'&|&8Ӥ&h^/ dèMII8VwOD 0gK џ$ADiiG֯e9ccScA$-Q&0c{@'7V$0\]W>Q@4%Jq` 6' Rՙ4"F#@sآ Z8U=rk+Kg,tkۭ2IʢloGIZBS)rv{Bc+6gtf,9a|M],!U3kn(idqwW8D2 - N.El#^2Yn6 \rZV|yJڭ#+$tp]ymJw>osnWgMa/!M8svNw}B4Uar'_Dߍͽ2kNT$ Хpze1</#nYqIZB,[.^'[2(5Bntu:I_]_vq}1o C$X -p`~00e퓼*Q)ԞQ=q_3Jr,Qe6)}V8K%msLOyo%>dHBqh~ZyPDq҅\?RG( 4zб3$wlPl{9yrrT֤*`#e_(K\ :2(yv/k~:G@\gqz˗OkF]hH;zG]?tvoY`bv֓+'n8]sЉ7}wx?( u 5/!F̙:(%U'v?o~ s^ylPcA̸ԜXfz )i{޷ZaA9j/응ru-w @RL58|7[&pI pի3Mmߙh޽?TwRL޻޼HvwիP[ܩ]_*hY9KqycAN$;wrSopFpkHzhBmP3m)|pZ|G57ڝ"5RHu*J飰xǙɈZ ;uW`{F}_q_l[[[&;z:s{J|S#C#P%e)M m#ݠQzgb|>o{k7=w_P]NJ >kzK+LH4#]e|&f:Wy;8µm=-1L46@U{ˎ?2I٪4HA,DRDp QF=[sx R1hg4ؼ5S[z˖e^sLR(DbӤD"DFH#mEXOm-{&^#R3/6M%Ccij~wi육Xu+Kz:>~}1"X*1;:{/' Lkm IH_C (l&?d`\u6<7j]#4|l,f쎷~惌h-2br/^$2=#c$$^Y/8U7Ghx-\|u1.1K>dH'?br$s-"Ǿ/`kVF1kP븿OCcjLcXmnӯ5b:FBlƝjF1'zmOre߼s!co^;{.(H-F<mHbvܲ,y"*)$+c%c 2,W])$UZ76r \8 I+{oL.~n=O}V#t`GG'S!aʹQ2Vg4ӾϺq`v/Q3o~II6?{;o~2> mj`FxI9+6?Dd1Ϻ0  7L?.cޕdR/V&vKv<]5f'|FThakb#Xέ!v3Z?nᗂݒQU> ~}JkP>'Olي$r wo}qMj'OsJF{2;7L;ml6I0ˡPkJ[5|OsUͳ쾹jLA^5@]^u]ڔqoN:Y(ƿ:~/7@LR9+ V.) ~C0n4-W;)8mPke jηx*YT,Y!)Ơ?h[oA}u1>fbю$ҤsAz QD`id+W$>}ؾa.>fb#!WZ9)X(N`?Gw|z}O߸nRH+/SDIߘ1vDE,ЬYʥ;CijeVdD$uSY~z|ooU"#Ԅ4!F=RB:1(V3\mthW:}3He@wfGz֒J"{VH#!i1Qڮ.' =fn=.%FU1m|a3Ɨ 0JNA@T_K`_Ԩ$m;z=+T>#e,,0N=[RU1&u=RIknͽTLFj aao| ф@ ݗf]!XZ|&g=Lm9Q̽[ꡡץ՟s+T^|&ܞV2wHJ:K\g]m3Y`zZI[$%ٝrmhۿERY,a@f(ijrkLtȧ| I샛.F륒tޞheo՛i$:QN쿘^IYb[]}B1(+vVˣMWJyJJܽjyFz+GYһXbX4dQ[؋ r=; 52 D| ޓ)*FtJ9g]'[%2dB$GB`EZHRRJ-DfK_Wd`Mj-1/տ\ݯ?vd6i֋ėfb8$%R斂NkGQ4s3*>4w Ëz<28 -(RH ##V3bRH3HPU(3ln=+>QbXugrt8Ц{v$D%"sB8;BrF.R JT*)R+Q. oW䍸uG/Ӳ{e|p3/h t\Teos<CϾ!ö$[Jǐߦ נN~nx:AUAN#3l L'l4]Ͱ,GL i@t ڐLs~2V7!As&K 7v2yji%.8fWO^׉,@!9B泟؀ }ɢoGT2Vxp쨻տd)06أ z($fqř靭pT]aj% G"jEwu7iG ^ka4e^'@uinjץJ|f5 "|c˭bmnjFdh!Gi@X)wVYkq+Alؕ#}j}n*@^m.]ę7TwK¦\gEkRxdFWC^PI[ *({->j\e+PQd j"(: B DŴך6/i_;)3 쫯;鵁ϹFrVoegNݟ{h'3g]"Y{.b3n^sU{H֑P<ÛFv49ŸwS'ٛ,S-I߭{GOLyH6#2$Wqš! (:cfvM$ dwDU_:jJ]O;֟x(' Bl‚g\bSyG+[*Prl~_ɩ3'A\=nښ$\Y5eu"8df۝cڍ35IjAXM[a{گVLqÏeۂd ӰҰ,#ԗ}NiOX6Kjs4VZiZQ`ÆF#7Ξ*tk%j BNY2P9eaKCaY"Jq'V[FN$umDPKՓ~8ScH8JFc3=%CAܨ"#ǡfԱG1yXډ_%1|w1qұ:&$ߵ# )QXjk팁uHYVfvPje:?enm3웬`ŊqҖ)ep~ % 7Vt;f`wdy~G}L O:7ýe(Pv!䫔T9"9O$vNQ30J}s:zmkH=eIj&\|(r:7սN}5"Yܡ9dbbZջ# lzH:ŐX @i@Ut`?{]_" ]Q+:|ɭU} ,W<9BS2b#lM­3z/n0POzعѓ[oR8m'P]槟ޣzޤuo̙'wNjX.(ط؊L7osӨʡHF˫u=9))a1xKPztR'v5h  eX)&lkgtX Ha$YHGT1I:eW `|O%vD>t@2970$&2 5V0ww_SY^MLa A& [@az͹P!'l5'lƖԌ7w]1g ֱZ[V-1)GX*?tPq25hv6+I#dPu !DaD r0SV-#dyF 3DX}ȆiaX4dac "pL8#,k%P7t glX }8Ϩa|SSri76ZMN/+aolfQ+46j$RtTB0aXdrjkȜ45{*%FcYbT0Mݺh^ox̫U@UTkL%i[6p2`:B9F>_vȻ춏q zqي@x2+pVE;NzFy!-uڔeB`8T̸ᷰq,2|>2u Xx&7(Yњ~nUE?#<@"pO% N5jl Zgw9 9 IX1ҿ#܃ZǞ~MȑY]88@!ȽD:8+20A10 +HH['_/+W[IxݹF AbYVZyý+{P-HY %6')9׫Q+'QG-ϬLbia )޸ARg%LMR\'5C& bm``g1"KB bfoǁ'Zd(I-1, |}OE\'@cPkldIBlA p73sU%uBctN8675&df CL ڠȚoHr[A԰,f >Q+Y\.WI%GJ>v\8l`ue4N_MK#v\/Gg91pOw%Z49yBQĚ%aƪò(@,s`i1,aKHVaOlIkf䖶ۥ2wMh߼ [X$XTN b)K\[R zyz*IVVpKw=v-"aŁޚ MϱNؿ;1fP!1&_<8# ԡ71|B.+4F 쬱<œ_h(u7?G].ґPyh0r8:,k<36X Aw}[QUqK1bELe`#$D:_[Lx[$dP6.@ B y uR%%n9W F †"D&V;E"[\߀+NF xa$3E+J* AB " ބ$vf4J:ۦ`1t!]J5g~W^jV)+2baqJk4TqD@KfQ$'3G2h(rh$pgtWIlB^)M54Qfa)Mz3Qش}?Wn!JZuޢ$l Gcd#3]y T-UpWrK+B7E*:g Xǘ->0k7ƎТ"*17 㫌F]:jZF X2Px+Iy`/(ҁFLԊÑ0A]\`wvqJ5G^YQoq'z[:N-Zp)w/|[sk$ʢ68Q7ʜHJ%@锄nv^:$nEk Br4L3j7͠ߜ$*.N8:йX!PUP) 9`ԬyeMAB*A(%J2U*90dZK'D66C !R!Jf?ZOf|G!rן9,?=7yI!yZ^#eIJ':ʀڐLzbNJ|<bfЎ+ }:t+ݍ#)F2 ݧ&bN8˵WF eF|>7Ve͸V <'1=5¥xbm o%p3`A2c5:3ȻUD 3t2n)E"Yu0[hӀqcdg\JB!(uderl|+Bq56" ];?ȡĭ#[k!-o#+ xAif'Gkv jZjif@uJk؈վ2c#qR\P{$',O!U5qFɚ@F&TXKY l~~jJ@29Mz@ ‚BN^4i'ʁ r0|s(mau&ZB ,X~?CPŽ؂ M#CQTh+ņhitsKtːpѱVHQ"BԽ2 ,hUޙ2] 5=IbF6W|O^iW0 ME n&QnmDID<ׁ+R!"Zh*3ȸrեW#dHhE5"%@0P"c@C7DYp! 2`) ]d-ϛJ=r@@ kTzz .ܤҷ5{AքZ83 l<&5BQf$KUa8cK_<0d\IF=؉,czb9 {M:JnXa!y 85]ZWFezoעSͶ#QFAZJGe9bݹ/gX\20mi&؀{R͒"\L&lOQ~6MB * NKVVL 7" N|kQ'(᭛oOBC`ۊ`(k9R߽]eh|HD"P8wh=(FY5%Es&vܹǗT&5ѻW4~MA}0 5IaW3BX5`z͜]9bУixI=uK$B,7/sjb>ҵAHs, 2"FȊVi@fCY+=syO&#}heDcO P]Ȝg O߃ MడGekM^zp"5O(TX:u}Q0mzҮyp/>ה*_aV{z*\UK!FccQ$:rQǼi߸>g&U ֆCl**zꉎCYbuQЁ[j Lĩ(ʑrqJ,))cwhàv f /+v/fb.Np 1ÐS֬Cyؽ凶Ai3 DuI@ \CKRsX{.MN=O2B'v91jTuHzjĉfj4y?e;VRYFiB?rX81pѸcR9ݖcknl<3Эڈ4'=27Tt/1FG9l ua77Q{´*n1w;0mfU($^"E:ѸGr72LJojW4(iPFo;$EE6|ӨF|("KBC2BT:$]Mɹ]~]"ƀr5c2 zKXPZ+ g1J  @Vad{Q(;a *0d{T629W%-,3fN=M rm\J֥ 0(O\ Z ! jʹ\3(V ŷ0Ճ2,W(*ILΎ$*-]ZĈ%fk̤BTȚUy(aʹkNPf3Tカ"Ќ5r? WSH9c@LC $ |ttL Kj \3_ Ð$j&'L7C x8L$5kzv5cBA]: +E2*&Y&W&5]WCRd-Rɬjz"q|l;0%DM]^~G>#҉OAWZu>iIBMn9?˻k3bt|)V`0Fq"4.ˆZW~K\oӈliN.(Fn;lD85k͒~zK\ޘSZWեK9ak͒zވYeUrҜCVZ2r?0 c|TTX;% k ;lb1wX&4bzFOӉ7%WA[6hM\kFd¿DTb{UV3v{ Ʃv鐒L-طa-+;o;"+:M0.enab%dX/mg6OmHziֈ3{J[Κ6/7tK I~lzeEUrߵZ?/>j9.2븥8]%hI#V,!Awx[{ˏ ^)mHh?-"xb?bhRYI8MZI$nD0:@V:2/FYQFuX~WL@nt|zg(B.SD"exhڴ]# t btkDDBo߷mHBݰwɪ w!Q"fE!]۟!ForRl"$(t̄Y{o8wvD6 uPI pI {k@L_(HɈXpIXK Y8BV1\gЃqɹ43 i11q˘E`n^Ŋ Std 6 8@/~bRZjjsUQ H4YO->ͱ1aTU (+^8V">u# 'XFWHˎS+O7Ff3Dّt͟~.fVA\( X*}$QBZ 0X .pVLv;d ˠ) )V;Nω'3@̘&0\2baIX 84aP,dT@(0|E ,ӷt<`",ߨM$>C6B&bq BAJxhm1bQg* IdD&oI;C];~Ɗ^rs|Xaaɷ}OGdmIB:2yk0~~hKRb AԄdtN*Y~&'v<ˁbaUVB| CB .GӚwc0$Rf{IHRG|;>=z9ǑvDA pKmP'kU9X Z5̮*k0qԇp* ^x/=6gU詌N.ccQ 9m+ -+7qd:HG"EWL]dDNrc52t$ɾ⌽k*k$#S1#x9nZN/O*G5d !\@xGȄSx!,3Ifൔ@EBRkO3%} O^ \0-! &T\đeĿ!FZ! a-ܺv&Ȗr.yG}dg 7#7&MrQ6u~@XC` `p",s N L&$ &!p1>BQ@ 3E#I`x& [qw-~/fqǧ=TqWޅtyߕ8I;_wIoLlB;rx+V$܅2lyFۜ98j1lMTBJMXL-f9eQ0$x(` VK8}zs6QX8AÊJ) J{+HF^KJđLV= A,oI01ñԙ5^ " mrP{zF+8J~C Ho-n=Ji7bDڑC$l@JFFͤ80Y #? L_$.pLČF!|\`:vkxԗAOyMgu"l:"5]na|i!t^Gpdx8 >HFԦ:P](1a]𥥴pTxץH8&'_.U'A zq5*\x^GyHc歱8Au\kL5deݨ̜z ۵w4ciB2 @D$[ z[h`sPIU A6{Bת<;1 Ѝ"FQ,_%laԱ;&R1ߙ6X-0vVlDծ#V X]a͟ys6t-ѻw%xdưa#F+.#'TY#uZVdVLϐӈA۠XXQ@] Xa%1y!cKS;KdH8L 0=Ot/}=z)B6^Gn &-b?yN ӧL>g{x # Q=Gבҩndz}5zm0(D>CGrS;_adF( QFC{Б:_?fWbv_R Q&gNLБGr:7V:j(k3m]Co>"]"a~MpK|2\M6$! (G=2x(6x剸SZV%Mz{*}VUKV9&bQS^t44;]hRLNԐ7Z 7vzYmER]JD= i7D؋혺'\ԑ,3-r% bN)ji<)>d]ڼ6fR"&esDbeq{~LCi ok$ M^+(:#!cT?Ce!`-U'Ytr: ܯ"p~ ?zݼV=\S/L#ၰqܡ9[{Jk^63[^Or.նJ9M˪( yώ g$+_^eЪt)a d7\\QO|Ce|n_g aqi\ דt=}];v$ +݉*!Ě!:%h:^ʢb4 {nD$H]0k`-Svj ;='n^6lDB,Ex:E՗+”%[3FN* ErxY=Yoz11n!a<(!@BCcBYVߠ>3j.OiK+L^D{\obgٕcsk\-<;a2|b^8s{և;~7-\A3ݳc*=1RLUn-nl SmVJZ^\ZN(jar>3Ak?G]Յưh^"mf.IvTERR[PS~@C˜+{/'uFҲ{,f7Käp$< $u;ZO]ЕtI{ϐKJh.vx2}]m.0u?ni&)UH$"WFkV 3 m7w ^5.H˗̜`$ (EjDKbdK t6dUV!-[b5}z(Lɜر߇(vQCIjϳ(v/S;P@@@ at_A hD >3_Yv 4sD*bg=t, ~wKn[n'O2i y| fHko9qdQG T>lBV^ƱkufNG {bUjW \qmֱ9sDKђHRc ldgH>Z%fVU-`$g $pnK*#䅍&IM-y!>}CX]W ݠ<\[̱~j&*銉4OsБpD|]^1p|-HB7(9Fu QUvߞpP\bqe+#;e  3THC9R_agotzzevIW^QKv?}( DBdϯauH1j_vʴ\8# FY'ɘ]I~J>#2.Y.oeL֊ /? O* +ųy=Ht LVHˣ2]~zvDd lzOe2};Y(=efU&2Pt\Z (L7!;ʕl w֕TVmG-(n3 "2%d1b["(M5H2hΰPnj;"?흭wS&aDm`~o~ꪟzdN<@+Na<W5%1= l6HPg+7`KNyHf@֝GƍE~@ YIZgn؜*̍̇) yŚ/F~Xc1&IȠO9Ɇ7-ݱoMrlUEx"DV?avYҭ1Gɼx$򹾤;^" g_s='_K$qgJJIq &$N|DWqDGq e}6+əOrC!ĹZ73^5xbl~7HRuql o=oj1ٰS?@Im@@)I%? JVwRp@a =cN#$$`h2Q5\n#$i-WZqѣalB8F@FJPV0487bD\!r64Y: 1/ ecTP^C 9τEQ[o TT6#SK!)^ݳՙɴsZ&:&L<%ZvI?W:hG"G_rgqQPK|i,m!"X9sgI6؇'Z X0̂Iڇe=H4_jX0F@CLay nҷґ IVu۷SUo_I r:4 dx(BR{]ghI7 i# !׾LP9A9ZcJ̛ƒ7L !(ORz’xtzH$蠤$dY I1}TV?xwWp=y/U`wŰ "3(f{ 5\j3.1r+)iy76w+^B ![~ mơtKZ~ᤍoќxǽ뇶_K&mLt{b 偫&y1.LhV) 9Evo{2a/KȜ yJа zqy=-Q`M8pg^R23t枧, 'dVb@0.Xm6zܽp$ƘSE}C. DK cAsE"9{EK$>O[  g' pq$@נ ^{$++XGmD] ^C,@?RJ{9;|g>)@k1)ӑ091^;<~)ܷ(ӏ.1P" m|$(5E0zTZ|{H2 `F lK_&)]hVWHAN() [RI>c S?k$̶^\* &9F6#~'a _5$ 5@ v {w yU"g='{uw%y}qH{1h_]ԣ6N!AUCV^{wKP D -HzƧ! Ӝ?k%tUM\וtk;GKxP\}8?!Xi9GP!Ϙj/ {baIH|Б|,Bu{?^Wh~Zz/@}RS/b0ф:L =0 Y3npS*iI oiDs .~,Мb tj{.~4$U[]p^{ϚJȮ8^YC ]|ꔪ]fߵC'?@u泛jSء@m:^>d7!/hۑR{(*:s;t$ArnH>_8"I"2g F?W!ʏ:ے*(o]rLJFUU5cgRJF J G'SJzN]c)%_QdEURJ&nk2L~я،0Wx(7CDױh{E8o %w>)7 ӯ{^limChgƯ}Jfvd'}V-_2'pL.}o}oCUI^p)SpeU߆yd2LML#޷CeٻyZ?s+Q:C0]Sz/vWHp5w߫6w/joIdIxkZԒZ+W'^%n:8!6W?Z ch(0?6@\s~Jj$ ؋(G %f?~ll)C0*|uGHlRqJk$H7L|+F4zݫD7WE~gBN:T/3 ٥Rs+. БFը+i ճ:~ 1P/_;%`$wjD=gՙr#C$qmR¯g$A4z3C 1,Jz4mi-U'N89KmXK +nvTo9w0ܒWU3r1ڰZ>}hg9(3(2jEZ15Z1‡6+FO1dkIY!%$k1dZBH.GM?qZR@u5SHny8NQeU?yȫ/{/QNPɺU{)$7|bmNYY%ˢ_z{)$m>5d6˖;{><+l 02bY)$u>j2G!Q{3f-~ԡK5U' RH6GVϬY)$\YȈ ^Yʏ|nQ緺ғ1}Nt[Hliźd'_jP{3nK&qs;_F mrҜ:\B2J7ȕB(|Vz e%f30zٝ=ze.DQ=|{߽$^|CE=벾Y|Kmk O|Ǫo7'Ɖuxa'̡ kXMk $@!]5F< gyz>{3ѥ;O]+*)+y7W؃FNjCh@ I fn,)%J:!66f -\t$A ĕt$ @Z"Sha/IKWMGbTa1 V,tYR.*k!7norKH -̠K6e[}gڈTș0i[zc+6fו *,Y'Z]ܜPW)LwP-= ~hsiscYvZFQAzOn.K&8,Pb uT~ȑݡ1lUI!;;۶^!$mS%%UϨz,nIOoL+ js߸.,o*>I.d͵§Zi* b+,5v_MO2庪(e[ Y(/e+ r gTbuf`AZ?R to.cnC.S%b*+dgcmDMlUr}sK ? %d#օBݸ&]O/(lp xNayɿM%` krNM][4U\7h|OH!qq-LXtNeMj~))Xnw*Iz!MM/'FZ JbSjw!`1o}^4ygʤfadk.=~=CoztQ(Q:L&KQdMφv? 瘒biCʷUW$\?>[Td$3.>_ѓl5}.ﳖ)eБb2v3R=VJn's AdW$l95f[uC\..TĺQ RH[,#nл*5Ur3ZhjÂRSIr*aTLIa\b^@>vI= ))Ux7BDYO'lAMLi$aij+Z'4֢ˆѰ+ ,V dv* 699@i{$oS-:L_rx_0Sϔ [Vr\ѩHR$;VRe6@XuVxgchh//z~ۓQ@VYLluд-[WLzV}E~-I#;͟ p`|ot$ז/i_gFlgݧ}z'SHqW|&Ζ5w:k-i_SVi Q(7f"j^, S:I({pl.:^K(VVRiAqCauQ_[(M:Y$[W*Ut]`b:'7wUNmjv \?'-%5+7*j}.<7mmDm|vYZH6{SڊdXغL*n j3-#[mlK}olͯWoR͔p9uiu4|?{ϢTE~pq=iQ*ڬ9x֯-WlM!G_;s>n=Q)$n+_Xqï^[:fcߏG?Y%7u]IǠ}~U5XyV%\mѓ4ڛ5ݹz%ذmVd/*(D?WCI3yTEOm8{0 t'mFim-zf{xS$RVrC-_}23<}OiЋ7H.nMU޲2gP3#oyz~hK>y]IGNݿ{v^WɩV#_w3Ț9Ky]ɕ#{3S cޕjټ} X4w/ =~^awuddUIREgT>_;X;{5 wtN % QpF9Q݈?Ѷ'pj=iy8ݏ|*7'6|pELLX{qN"F0w{vĹ6 NTd^llD^+@b8&p=5ٕGSnWs1(v,c3fKu_In_[.7u,x6X'N̰{Z#:M8kr&ҫBGPDZ Aؙѹ=E(.y]4t~n.y/=mեeەzΞ\0B7SE_]tFҙSnW14}. d)BnZ/M56ĩ߼}hեOzYWbXڄ/LO:ݰ^"3MmU 3LS߼fhեO P޵ [ Kws AS |N'kiIj`1hV 5ܜmُ j=tfK.=rɧ?>;z>g :p\8+9'{oϾ>n`'[l/J1{I]Ӌ޵W_w/ jWNJ֞^ݷ hT(Q!I]oyv'^ѸxCiﻫo쟃K9@मqAg&Om|r.~3q0!L@Jw%;;/KaLl$d;3YoRq!*~ѻ'k[<~UKSI ;m;t3_|RqNx┎¨tyb~OVʸwu^!Eٗ㚼m!>j FY,?w<&y|5# J*#|cJLQy5V=yM@-Cߙ:)>r:l +E=]_awߖŵew;O<2El5G "cohGAT[_˧Ml2JV-/,?@`KjNƅoRU|jߕbI;" 纃{=j%7m'}%;F~FulfGi%_L0wƘ3{%lQ+"#vyZɑ X] uq?F%_u{Ŀzo0rğ_|'Zw7yzW٥0wL{d{ͺbӿp~roU(vG?U=?tbQ5S`j$&Hm_~CVyD d&Ћ:p <K=p/N~[ N;g=vyzz_8'=W0C֩93Xz3@繻{ӝ/9=fwu)g 3a/E S۝k~u^ykqCߙmӁV@Nnnëo>89/yԻ_pO3?Ƹ^X\8W[^~7[ozqM}c]]R6?B;3,R| o0Jxq\[\cF I}xXPnOgwH%R3O"?J«RGm،T))x}=o/H!1j|_EѺvTblTj<|ßm;(İr9rβZhR…^gf5V4 Q>NmXu0:oSc!F[KeyV:6~WYO]~7k[vj$}h}1h- _A^%Zm 𺛯lN-ƒqVu^d<t{wuQk;5VħZ9{ǯ)Qvi6~7=EF2;cc;$ֳ>rV_A&\_N [Ey+R[MG{JOWl^G_sBS# ^.,mbzUiA}vb~ɟ!V.rf AܾpFx' Ď* ?~~+ L)kp[P%6O?x Bumz{fIu.LJS?0*))v錺I!+m~TDcCweCfDح]薫F0 (ūH+wn~q>-'*aGGsK }:fP}^t S}zI9yh֝q <]p $V-N?+)i.K-D p%;0ưӟ?p4o"apOB"ߝ`aMEJ 7}gV/ozT~{8Xvr sƬ?)}>ߘI?olJb\L7dƘs$Br1[֍H%T F *?vEɿ{=\Xcv(]ZɄ+2\nJk#gpBFKʙ?VRĪi6%+oW|N[.Pv⩏খr ^S俳ja7”ܒ?h[Qkj`WO7{>"&: 'LJWȝQ/q?z; C;]>ڔ~k*4o ;N)Oy+wk)tc>sW1~]HoSvxb̽e/},J}ZKr|(T{sI_}oy#͏iĖ4Z+L㉞$.Wa:o|Z+AǑ z^ag5& Zzc?W6MJ}nMzhFn!.,1Z\y9n*IBWA|Ϩщ边sTIcʛWDvֲTmv UMw~:/(u>VE%x(q<ѹ H~A޻_Oy VZoV`SƸ~΍ MC?N4[7 pf @ȏH#F@Iz:|bu./΋[fΙ; ]ڏ @db';U#vB'~*e=BK@׏kȝ8CV{ {y"sܺÈ%0Ia^F냄'sKOJ#O=?3FEʅiP{HvMGӊs"K~"E^jP\gļRnIhg-2hHy}M_#1vثw$r3fv}FRVG>w|Ĵ,8SasGf!GMc}]a. a"3vkT~>ӣC9@{/m_nTj ";8\Z V7Ǧ;lMփlܩF&3=V(Zr &˷~Aon%ܩ$%ݤc' (;uM+?)/'q;ʗS~3[\I4iTnt݋uW-?sGυ;Y$ڼH*Iӝ+v{ )Jz{|@"*J>#V="$WwJk)s׍(bg|2t}o&._7yxM**o9+7PtSGr̋u*iJDt:}Gcg\ @_(.| ޳zWٟ-2j4P Znb?@<z9k};| }sY|нz\O{|'cRџ/JOSNܿ%fʁ=?ݜ{듟vH.}TNJ <ԯ>ewH:t -ݷKex5t+?6r׆]Zd`kmQ-Q/#9WrKs9`5Zgnp++?2-)i wA8XzG_|*5= I:?-2o&': c;z,Ut%OHJKط钋X:ߩKO ܃3f[tLTQַK/2> ˭zWONx/ }Ş;ق/U+Kgdzww o&z%Kdxңl= E/ eErvM>3gqgny?>Hx++Șg03CuuD&S)g&s(z+p-ZQ~gAܻi:RRi/⽞ Cw(n,{0&i5Fq^ƋMYw_ ?x/~x3vᬍ<."."<S$+ s^&Vh mTvR-}>rʬ|@5|$jjV[\Z ʸmK,09)5 ϋ 1-M\~]3q.-;  ;'N-PƘ1Q65D:%:٢=dH`FQ#RvdIjlވ#Kt$4e^PƦQ}]ηnlܳ)%Khg4]*Ь=ULc(gH7`OV'RDJ-x^ݳ# ?-s:u> . *f|~LNڻ|\t Q8| "Zf<ծl)&?l{ξhH! f,D=z4JL+s5ɖlDtyE[>j= )$jT?0Y g;Ai!#IWmLh푶c͏K 7mdٖfWW!m@UW[reJށs+C&7VM^74+xb{5{wyW1yt ޔ+̿56͇Tg)ٺndk|};Ӽ/Q}"'^?d_Gϒ} G V%A9j)hZ,38sarWʢB]OLLIjFR0Rap< PJ *Q=[O^8ȚѵodSOU7ގ ;ãy`2 0!p7x?aZ7 .9oNk` 'L(kv_}.6a2nWfu2 DX~Օ߭+}26$|!A}ꗒC:W[jOVx]yXh@`2C_opHiUӛfHo(}}qsVb]-MLߚدE3/XD&#m6g&//Ƭ% Ȃsl!mH8׎Б_* o#I~y,ѸxCAw$|#Cly$B(`?}6MGG#KO:b۷t»<:Տ*@Ec6+qzs&U9S (2L^=18["l:lΗg\b+Iت,a*Ɋu?溡$^,"G00׋{ * SI*.WNcEA脩$7'k|/nkڜB2hr[ +Yϯas ɼrG._lH%|q`ZiZwj@ϯmN!_r"h^wH|jP*Id% a=v|>d_))^uo\<5Cke˺Զ=i=_z=\;j9VE=ɅW5{"v_Ⱥ<;ƕtv;\1.Itu QvO|۱%٭ rG+dlO1-Uv:zcږHRRn]#UO2^iis%-sݮ'85o£yv-_$x8Y^|\\XAG7O.}T7{p?⺴3G3RmjEӸcs))dYY1Œ$(pz$Kٌrc? ZuKpLJp2YV75%1A9c?8v5Y ou8ёF%aQ8XT]|[ʁwΥ0yΟvEwIknI$p#:|[ُҐ s 1~2ς?B.stW6=霓1+{(ҧUs7ro.`Ú#1 MwO {nn." ||]G~aʆ s9/tA鲆Lۙ.B<ܒk!s6ϋн}g(%N4vݵ.u.~ZԶ<{_&5ܥc:/Z)YL^ƺInWS/uԓHQHQhMys/cInw൜Dǻ'Ӣ9kG/cInZN}:~_ߌ)rgk3:9-󇒚ŶJ2 "MPFUvf=~C* ~7E>=<奴ߔ#¥.D /3p0Y,z;d9<\.u9I6n)$ ^ oC!8dir0.f8-bϧZu 2feqhIp.1w4c΅E$NqCh+Nñ)5.-`F*vssYиc}/O0}eq6Oܦ%O G6]{Ǘ>7$SڮLU'LonOrN]ϩLv+g$ӽ^wkn>n&,6Z‰t]#9!9UQfMU?Hl/9TخPĜFRnTpnUߴsY;I^X X[.쟴$/Ek 7~L4W3vs;hʫ{_yo` %Ů&9Hn++ld\:hъLp'v1sF<^VL&*vf~؍yGx2%fMɑ7PYc3cw;"wNf6]b:eQiq{~;~iTSmbω[_wVү9G~CQ}#%e 'Oqa``w\A O޻[a%;U\d6x4韜1`w9w!_`-eA6H/إzQ5VS~47)Q# 7"BIwz,0[pb}ӢϮQթEY:5[ҝ.Y%WG2 LmG3 iɗLJTh4̮QXa('XwOlTQsƼоiܾ*ߋ.xIӮȔia]y`òɉaOjpfb&*LHaΕ6/2n<H>|mڳ@:5qpIA^ o >uD*DK$2\V`4&Z7Idk LD] 0b~;ʱ1*t:MS-qz_:RI%lwR4CEP)Qu?nϕj(>tnK'JdJc/y5wbS^_S(n )%KzQuņj/-it NVI{KK˹pB]S K5`Fqϑi / ѓ4xOnK%quO]]Swu_^J f9'i.U4=z|M7|_^J3с@[~(wy`NK:Me3Y?o231Y@(o͠_^0_n̶ʇ̧7O[*z (hP ݞ5h׍%sM WW_s k@N_.;.{A7¶Gy [W%^mV}^٪Xyז0IR]fT߲B-A=1G~8Ȼнs7EfjV }pFkE̗t6PKnKUFlK}Q9eKd k[={9ɻw]i[-΢5sZ:R깹Xzt9wWYs-mHgFЧfUO VK4~'}92H8U~ȶ3f 53ĪrE<)Xuyڤir|mruC,&ZWӡ~V4QBuIm\zt(HH+IH`[A>*q c\Ct:u9#xؖT>gL{h %U c\$abX bQ8N "ѥ<Y\HvxCw}]Wu1o6;l~QXHnQUuzg<4^W-.O/xuDKוm'@5eNpp%@ {iI<ЭH6-KٟdzU׏Xzʻнm\MJZD-ձ!yΑU>'Ϟs~Yq1l3wǕ}G=zw_%8Y917~ֵBMDA3^ʇkOy /}oqg~{tOt?9\ Gڡ۶(uBWQҷ{ODWa MYRHH,*R941#}/ZvŷMJS3  ltm?nh% (Uxu6`3 J <7 jG0H\k.O9DǓ:>±xX_w7ޘ/UKR'ݬO- YǫXxc:[R[״Y,⪙9Ys$Ԁ`g3s>+s$p~3CG"An,x֮J3[ŵ3s2ge~nJY^̜QKr:=`^S5ӞyA.+61L^fv zB;W;'䘩VVw' ?iمb/z}6J2g5&oe I~tLmRHʻc%Q:&}ܯ8|fUKqcTR" г; RGӦd jeWB<-).wۤr?ğH7|[[mɧhh=ӻD.ʖ ;tɇu=8zLMH>3p[jɸCՙpMH|rou`ٰcLL)6Kё)םs_d2E\G(F2@SƇOV̷iTKP.Ua'A' %8(E|/;[4\ȑ@2PgG{k@ISenߙRд;P3M⾗|MMA {Òda:\@Xd@԰fP"%u$\II>vhU3o6P>] .bNHzfV-;`mdj|`lF {ՒrKTT#AwJʱ=nXۅIfI¾΁/) tSMw[K0o|}Uop}oo{MԆnјzg}+c/~Ͼecf[/8Y>uʭ^%)qzLNofKJgCexV7M"&b{Y,fDy6<̦{gZ'] sHoDl"ٌ(N)dRFa6}x%KRqɖ곢!B? u:2.;=¹Ge{x9dK\Vm($}\* +: _& u <ծ݄/'xm<ϣ`t8}= ܫqڟ:"i:Ljs=${#)tV7>6;rqi`W[jVHnHtGdkL?"K[g:Zc]kw9sj%}u_T>"hĄZm-Գ]4d@\my0)9<[-lR&[,nRF\@)>y~SJ,xi בXo2eꔸp "|m05;W\Jrϙ j'{Ks'K֟KB3|PFM\r"f溼BJTid# <|QIvy$ :9כ7~*r =1pp?yo{N쟊tãghuKCZQI\҄fj6|sU?r%.?'աԓh0($,fp*2bG7 \G;uš .m8z' &%ͅ' >6<(<^qIrdu-}6r/#'Z GZg| ҍ{,O$Y#2=2$ )%YkezC_FFGٵ ݈vi?N^o ݩ1M(z q6c`Jr_էA@fo{vsgV +|S|nx-;!lFe/nUQaL~/끛 [IL)ɳa+yfoOV96Y>/-4TQR=_(,4.qi %}Iw 8hh`3IG?wk[՝|W \J KWlۿZ +g nP`.G/SE'2lǢKt}iU7Ag^55`3 "]Fn+Z_(!y3]K9r.ϾNGk$q?{qU?ќ,GgQvMSeչٿ}~i!k^Fr㏤Fնqw^vDV+_c楯KaؐџNGvkƋ-~~mL/9vJvkw5R +AޫܫZxC06Ĥ;Ju_fH1Eq;2~APQ +y .3m;?OVyOv&{={m%^(gJWp̙?~<%Κ^UU~k_'%'ozkOjNHN_{iwޖGNܽ"iGׯz u/t<T~tG+_s`(TGPW6Qz=Y :gxk^ _Sv֟#sZd$]_l缹  UKϴ ,}<'vP휗_[<ȓ&c\NEq[W~Ჯ3Ƙ!/xjzbs,׹'-X_g<ZΓJ2y1C^~Ӧx۟>;cs^k 7L/TfʇWyFބ7h$729gW O|oHfcuKdJp7̮1^+<7RMb}`}$ wbʪW K $C&_ȤǮw$pt%vwoxnx0b,*J-NCRilCh :mMEex69S&[-!cKื?GfPS!W/ vX ?X{ y-ںjWd4,?=u#DGvoZg rtFvzK2de8szd$S܉'37k$oߴ7K`j j'FSKg7SPז45% f]W_ }j${}mK''|?;n[!#]-{ ÷*꧳uHJYX6túup2q"P!6ߌK\LtdA"tλG4C֧ûk^]l 4>S&vS +ߐh)V-;.^0}ߙ>3&Eӧ/(r_q+buyGMo[}M__+k_w]}N8 +hzk\hWd,O;}}pwK`' Q-p@$^u]γZ•U$3wpnR66CszխqM֡ܪѮ#[>R:[݂Nb݋`:V샴+;kFV~IQWNQ5l<<>G*?M.y]qxǍYBJĥ[^(Lc!9B>ڭ9IGB^  E:U m[hbqZǻSI@ە6xC t 7{wَ}ݻ":MLW_]-W mGua~Oƒ_^O~67mj48h2z>G|;yib[ϑ@#i=4N-Y$IeRyX~pzʭwTN6]m)m.;(yْ8۶'˷ߝ]Jl'q@CrۇCr7knv}^eb8YM pD $-dIT;si=JaHxvæ 7"Hk<q \4zdɶ=7 ~#Bqhj8fCbғHR?p{aT='\=~`x]]4Y2b|LvSwOҞ+>X39!}xt$u|J/[g|ջOs5hHS(Ygܐ[zhOG{b=3S ٜLJHIvOGLڿ'7w~T4'IbND=ΐp #ȸ^l+7(F2 !uA# W OxnVhGoR`^ּZV>^k8yYԊY;Zd.ʶ$D`/ٺWrS8wڭ`?W?uz_!JD)twj?U,v[yV>/j]8<" 3nG8@!-:FocL+SRcJ-6V+W:JPinW42qvQZ * ?l-六1vH=lrE_[F$&u,CGZ+Wtj`H$N(Pl{첷c1,Hs$`mW0F-]9$N.["VXI濗 /A0Ɏ>(!?*#^10k$%Ng~<3%!񲓝p{GbZ'Xv>f(HW2DaȎaͮڌ7^u\ΉlnhT}w#UL ʱ:3yڼ\I8l%KI+faĎZkvs&fЋ.syvrΕfnA%x^m,w1!Y`Q=dV9t6pl(TwzIIIۯ ˌ"ʱ:31GWSm8ccQD!&\;=ټ#d E PzaO(..F]7Uc9$('3ST?@@":6ihY" VP&.: iL!l1xOeB0:z&T"$C49ޘ ^FQ/vdk;smǛ4!tHCJŻE], a1+C\0d:=WVD=0RIAGOp (IE$dz햡Z,>kE(iSeL[+?<_W+7|,h?duT06#HFĨ=44(~7@qԖbRr8A^j0,O&+,,툻 t%$Zlۋ<ø)D>wѶuzOP[Dq',Ĺ.³畯cʁLHa!8}i~-cL&³>W$y \dd"Bbp[|'\%5\ kȼҴ 0YUOeg6 %dU7k>v>FPH3tآzgYtrG' k%Bct>M*(t1hyG ?1zȺV0tqU4g/Y-FH,T6 ?V xpy !JSac Vw$CokRR}XVpq1FὛh^Z2&Z!,#I 5XI5Q\ތWmݜF/Uh%C+~Muk}@}L˲m} ]7` Eu,}zjFE 5k:zx@6wsrKתzU_[e^O+طʳ5!b<9!ws8S_ic# Y TOn^c$K48#pa<'[>¨asBwOk`^Xgʵ+_3t @㵡S.Z9a43Bטé^Pj; 3VNc?j9'34ͥ9 ^1%ٞW_>sG_9%jeR;> ^1%4`=ez;4BrPf~*Kms':JȌ/ѿPӍ_w٪uH23|pŅK7|0u#L`f2L !EY,k8+̞ão9S~? Psy2]f}@ƽ}k`;- ݆( ]h"9mGCh&>bQ M;KS:6gGsvV zX >p}b)]W<١g/~ ~/- (h냰`TItB/*+vI0Gpj@5+IMׇSvOO81:Rpw#WA]) Vm֜_5[7 2)IEfn߸2-oY a擊*B=h;ҔHWlg5’"cqVb2-c>4!,jc;wK瘭p3ola)" C@X?Ɔ.(d T2$h'69Iyd#AwO+fSd7T$X2%SIT'M ijSm'˕FE?=#Ք ;QE=IO&R;FYL5ssV;Pnnf-J9d#@ކԒ zH^y8L2&nфX pIR~st$CH?&׼'"k3Gd-%<H2 ߛpO~pWw͐ȱ Kb9 EDp8 XT04k6DCrR&ʜBsHqG}%pf;㡳 hEƙ fv>Y2(ge ,Q9wc%qrl2EItؠaWsޯ1wLA9՟ɡΞh[i=]ED m3$th) ( h #{aB{ "L_G= r͞'庶O i4K{Y6Çb"卑Hv7VN#Uw)Ӫ[(qBZ\2Wl[L '܉hhJ\] 4?zpOJz_ ^Tn a/ 2IhWЀg]߆fm_}/}gvcze}6xfChG?3c"$6Ys6m~_QQi2)Y"|2#I5FK'CC:\A+0H$%%\-5l$:]} ؼ eOBO .&K'dz, htmMD!aI0{5bXRBO%jH:[M+)ج %AOLx4Y|LvG>a?Źj̊g{u}hKJޣaV |o३$>vp )Ǿ<3TATn~N;,df}a _p{/ f- d1 CVEpyWΗ5ÿ'p ^zY "HJ![  YDwKX*G![ :+3So6/L%y7̜zaIJU1-Ӥ*6a9@?x$F"ljXV=nD$H&ipQ``d=K.쌸LvfQ.``ita>|=cqwfsvÅҢ׷`ı{v7…30دE oOzqvf%'7Tʯ "D'"`)4Li|f8/6_sU,*H 00, cpPYD'PSIQQ*pΚW39\hv &!(QY)Vs0bM ʢo.1DfGzӱr/369{חbVnx׻z#+>|u#7{{ջ>~~omTU1U ՞GAQ4>^|[N:- $hekXmNj$d#쳸AJoffY7HL0& Sw9^~YQr 8uSy6uvKq&);N1(cI j58o6z%7]z/R_˭yPx挢L΄^K9'ƪmR4q}ܿ_;;caAN*^#*1'6~`KPյgE:'Xi "'K. ZdTU?G}lģca\OY7 EMY };Urtu,j@ 6?R^uK^bhlyky+o-=/܋:[ = ov~i+~1[ v?<byrR 9UB)/LJִ`hG/겡X糪9ϏÂku;?%G &C=ˤW?- z:[wW%˷ȹ>+MfUw {Yyo[m<;}Z;dUr[GYV)ۯ6^-h/yN {ǕB_4V,)=6^{gG5 :le~'8̑DHVs%\aas߅ndZIўXc+iGBta.%A:4,`"IǑvn#]¥-~ȀԒ'ޕ'tcH_춇хpic$vCϲuIvE_9;3 ^EBD 뉏~xf'>[X>. _2>ᦄ"DP!֞H~qbq;ŊzñX.k1 涮FS  g{XrA-ĹJ 8V$QCnc6:e3$غ1mE][QQZ:g9Ӑ =GGOR}]=?qc3]I (4u5oY-J\Ƙ=3!ǮHV8!3u } ?i|ӖY^{G l&%6 9pKu. mҥ KBRqD"O1#4d䈻qiedpx.ʅ=?6TڹbFg&YF % -o< P5jyB8?)n^tu_K#/8NmI4Fw1@U"?JcZDO~OjØD~x28(Tp]tM&Md6 [Ç` HޔIiM~~OqxC\rJA"ZIš8M ?~ hߑtn/ sgHk?9u1Y4 ȚvH7(g_*EbpKG$UDo⏾L;rOx_s7R؀鷎=/&.r|{T$넿῜4'xvPnZn87f5Ap&UP g!g׷sfO㗍>^uܮ[v8|"f5| 8z)Fx(k*tX׋O{&}FmGYd/!ͯ]>ѧ?}xlqAKփ"yB-Vp7#_,L%je3vKQ@ñ8_r( O8/`e,ZAIu3Bs#4yc*?tgo=KoeWwk_uaԋgŻ|/-n[d˧_<1ǃ$|_Z4qkkkq̙Awކjq߹楕#svA j=)9la 9 {ZnJP}g^6BY+)>~В[M{>ɓt\4 w{EK^:ڴIvBN?e:Q|ugÿRKk:Iܵ^r7ެ>kv%/9qBou:Q|egt$d^r/ X9@x"搉1a#E)cG14:./}%OdqR|iu uCnkyH|A0>8n cc~\}.J4B4l?zn vX\]$Ꮒ;.pӁRƠ)AhS($AknF=kKoMj1(~}8V XKv.#s*- ZfKq#tqCygUKǘCiUPzuu[AnL ޢ1r<c_j{9)[3kvp9dv.˚D4qb!,A"%U97780*};Zv{N佋`PEy}}cMav3]fDOu6nw g[hcXjHwE]Yx\Ff},Zћ n[5訝}=<KIqcXAUcHwsB{ik*y6]e1;̉#b jjm$JO_Ѳ`ps˯^hHJ,V[kJ+%CL|_ Q6}%^8gC.#n\ͮΜȂ'^0][XBegƠ).W!UЂ.'pxp>o̧{!ec,imbkm 銞Be3c>XܳYRmfޣ)t=+;kSw>{.P[q2c*dP `Үl=>vA9%sWB 4PKL/2u)<+zjퟬloNvi..%% FUB d%~߀C z4}C弛\?2Q#z2q^|U+_z4y!Qu7kwԶ5U)_TyLRy4U.*o8U^TxI%ɡBWQX)6Q$$hwh$g 0{su ۛ M.I*߫lowiv)__!~ s#9t}ءy>T|~Sݧfo4smv˓I:ٯl?f:-򍒒WG{72 R^q^ I7Ċ  69s<`pAP!R0*V,l^.)VB< 0daW5W h$d">8bJ BT8{"a4YVeݔ,)sT?z?6?<\R ,nˎg60|ǧw$%E־u):{EU N@UsDv DaQhVі.Ik!`8aT/ Jy~L|8|;%tzi&u\$5;m˫OXW}0ϼqo/\L X2LU=IjjļSw:rI{Žtû>-o|g?olE7G3Oz5|;B%PKvw{/yw|| }<7w+B%pn!+ebƸ:o47>x  &bUd״x E(kOs'u!UŠ1ٜdQd 'O[#XTb|/cA VV[CoIX/GN~K I$ !P,P?WlzYߘ Ci畴ָי?Y*xқ K.);u}x\ /-O륓^ֱ߾0d!=8BW>DžJTs:[oVt3Q]:4r}GU. -CL$# YXe q>`KλdކȪG |7;[XVurfy73< Օi7_U_-yÿ0gvuӋ^n9،qbN*º߸*{?Wt}6tpK}Թ{7FQHqIރRCf B8k3;pQzZVcٟo"Gl.' i#9엔zHMm %-gR/Ԓݘ#Mϑ:˾K@l~ޛd,(LWD 7~:O:ܼKy\s9Rv=NיsEs:.˥rKsQ'(ο_H]09Ha,>5"g`xU 4 u#)@i iAXski.EmHam F*ۥ_^\PcHp]%dA?ӂ :C*-̱[,}e( ueF9t,bNfƀ]مyNkn) nb^3,4h?Ua[-~#>0t ) fuPS%Tq18Z%%*\!DT2ycnEf1*#@e%o7B1lЍDYdM}y3umJ=/N{@1.EWj \%kpcxR;ݷ4HɭJ" gJQ0ҔLN$$PȦٛ(B:DV4 T.TcY 5I,)fO,ѩ.]k),g`#(1xzC!)9ԯb9K>IvC T&{ktKpYټfTsV3ƮP_k%T +t<4'1et'6= 9NO9+Y(,_ !-(c}Z 8rj(0ls\2W |kOx5Q~1-ES #R򶾤zߗxfJbOTDd> aobE˷Rae/4N)VEC7~nQb6bǵFb|<(c %Sw;2b)Ol~ ?.7i%Iat:edX$vk-XqBYi̟_}~K/VO#d ۶;#Z{"QDŃ*걚B#ݹ*/\{trBPwnBjí"&JaFb@ e1֮[SJ4!ۣT$2yDY }1!&kANY.~m>ҝ{cC[9$ OB)ox3\Vb[l_Ftߪ)ssL?*~.l`;AF5MLVFur GoEwKVC^Go?h(@=*sy]q&!}$w^6$.8eEƒNuՖcݒ3?tU\[eXIJ\ƜzslśY؈_ڡ{' F (a 6X=](nA嗝=ž 5h_9% 6k羃;Y{s8Gl|'n\yϱnə>:#ejs%Dm4>V1+n4kGO~IRX]NEرn2tt3!-| NY;Q#RD4lԏ@fWQWi~ʯ+3ȊaDW6u#((H1jT R0  }:Ф EZj&Z|)Φa=!8&0p̘E1)X gP >Ҡ0O;?:?޲63e|&Oa.-t ) knI3fYgQH:&cE[GrSY\'v 縸|@,vP쑈P#aPUսbմR'>B,fH~4_e9?fk~@w؃$$EZ00@A 9ܒXS,)f"+ \5ߞ.ߙcAOxWWU5Wn&5(Hꜱgl@HaqFӂ~-Zy>b 3fM}ͯń6N9"L!,D$esS(" D0!u$ 3<_׉WfYdy .P#^f1ԒA7w"5 ^%ֺE'~TbUڀ[:eP:B-ދJSҬ Y$v4}tFT2yP e!*r.FI(Jk !,6N?Ϣ1WK6]GxwoWY&<#yvlҥx@Wpih}F^tY͋PN\S1 ` D Y(^6*x&c^KhMj\S0VdxL ΟՉ*r*߻Qy-ۨdEBiĨ2IZ_bO'MSN?GmJ%/Oh1cFXZe(kvm!Q&ܕk* ]O|UV~ /r$o8^FQDɬQAe86|-R!,t6ຌp|((StH b~Ѧ:Y8QV:bEk6jۄ(ss8;9BVkqj~*[8ҷ/)6m4;}:eaQ\ Mgޕף`Z)rYi9=yM=CW*eoPIG%"Ȭc:e+-J~iW\QR4oq*ʩkPKrPm {QjJ# !bRȑ1 20PC-9L%@ܯ2ǘ)!S<|4 S֮Nsƛ+^{pU Jp/Qk|IᕩLRi42hG[RRtFfpB!=}(Fc)&x "b%r✩NwzUJ4$:-vYSyd<@*݇JFMmAK;+,5UD%__pG{e(/n_ a9IC}b`" 8՘TlXJrq5 /(iX"/G+% | Rz񐜍} C1O%XL!H&Cʠg NͨR2%%p.+wjFs9|)y@3aƩ<×GtiCc"A_|򑜋w]] ҽeWW_C gą O V篙._;*| S[q\Ixc芗8竏{vQGKal:{H4uV'Y@B\*cHY-w|1qT J)98+uG.g 6uJ@nwG]rƅX=<ÑurVk %l6146O} 4nù,3 #boz<#Du6S;Qx<S̰-~%#K$j YNȢ- ~$< g7ȃ&a LD6tg!^_ sH"hK; ߣarQ=M C'eb4}@ Od?2_;iÈ~vo`/H\`AfV]v'lō8k϶O>~1܀ANhZ+lꕛdCqDg[l207[hlg^eSO2|.nwJ8f5z ?.9 0c zT80)P3SsMP s 5O-{!Y9ȥPTZR E?*rU .qV8jHZgWʓH`1 lY6ܐp9bYۂl> ׉Z˞n+~ɬ+;Z=JsyQN_SvX}u6d\pJ.bcFۧ1/84SȩE1.= #FqdMrZ2Q]PIFD8<+B _),! n ')+:$|$}x#dc$V@Mgc16,fB5lsʃz۲! 2"(D5)5b:İp)U`5:Đz#%)ER%F+EB`^X BD~ҘzKWDL`Ipڤo"8nD)`}+|3 IXƊc. Q'k3d.p׿_tl4!WcLSRAYIFQSfޮ"0gv^Y˗EaDՊ@Td 1 t4.Udb?ާX!Tq ,HyK)˻]n*g|_ⓞ6:l+lldD BZ [G?ڭ{`Q@bBCc\x:=@sծ&3ǿς8ģVb(h2]wFͧOǛOT/uew1J#~TΆGް.rf$vj07C/~!w`z6{(mxwUq۲%KJ^vx53wՉQQyVn#w);&[ac SxRTyu3C0ٰ2RI0QpeOoY_"]AYͳ0{݊M(̰Z L[) ;dZ3!Vh]lA"g)(tmlB>鶸iPF[f ^k[SxZQF=?@}7-n ~Nah)-e `F `M9V)4蝟B_ (c3x,wZze\X9.?+&-eę|:WO2E}H)OBʤ!A(\ƹS/rp;<,$"Beh]bw+C"1^ݮ2% Iuqڼ3t@" qBi鉐V23Fv)*Cb1i2yx4oؙWޣaH]Ya|eXvt-$*3d 2B% W.or}7drgaS.AȤH_wJUGwRs4"`eܣ1_)hg8DrѺ$H6~#\ޙMȴO"ѡ\4t|~r{WHqq YȂ(!j,ZIDe=S1od7*O8腾FV^; Zm`#{E~bA2e0.%[)_.Wq؉qfi@Rϟnv[B:'x*IW}s޿7boҥe(4ݧJUk cvͿ;kL oﬕGMegkdk\/a'AUdNsncn/Ce0٘2x%`>uIwNpk/=K8ܓ|fB`T YH¸.DD鼉`iwrVW\AD /O,zTV3(v*UgG~}@l,u$ 4J,k/@Xa0 K drxibS%:k(C)]A @l/INZukO#l㎸1q"&#NH) (<@@X?' "9zbJ,^< ؟/697w?]X>Q?fɶkWvau&9ۻ+Wo3C(ms,,qa,d`̜2|~9P% rBMj))pbxBy~nD=B\#I$@&Ӝ#tvȫcٹXűq$&hګLa) ] `]s|oP?s<(@׎rkeRduG'[/ZMs~9 |f埡G^v/m׶?oM$D"eFu!ҋ1F?"[DhX,Ρ&$d, (^$ܿ#Z`R%T?VNd QYF1 ZmnCv?1J<8Akˈa>1d)%+Q4D IZ`ՏX?fU 裒`ǁw?zՌm5I lz]دؾ)G^^|MҦx<<΁/u^~ s{(6庱^~ `䆘g؇ }_$w?nJXa4 MGuKm9>f:cƚ† !#/w,6x~R疹0zŒ"FVԁP.LϘ}^w*J0È cF47;۞mh6s^Q9}S֬_6p!ם{A!}fN/é5_͚B7xiLcI4%`U2DEoI=ѧLђyTk67鐡(FI:=zǯBg4A*臿܇!I8*Vpi_||%mz叨zLtf}9AxE8'}vYk]:쳘Oe7{ݧXi3 f_ysl}6Otk_m/X*`ݕ<'o;^d8Kz q}?x1mVw)gw|KԖʢa[~{f*.[gڍG:Cs0DaO̦r>) cU }e$ di.uDfrFh/=-K 8HPyuD3Ldk*Ze=>\VnBkr홝\tq`%h#>ٔKQj q,l=A_2jl*yBsO0W3OQk |{TJӍ\l37W/d7jb}8dDWWW>`:I[VIi,5HhE8C  0gogqcS# v$u@ȥ8 #3Hbڅ~N"3 _)N â|.` i`#UXE"UWmg(ߩ.`}UF _u+O25%m~&,hR+[u}hJ8iӮxBڍ oe,WycF P5Rc(9t`/t͓|2 J IQ\l;N6e[sr /ONʢaюz/cBbRP_(,,Nd޼x+] 'AڙS i~ԣRG[3sFu2R4_r_!VLO G6w~σh~FLW&bzΟ ` -QQ"Q(a`v%0h9R%T3&LV8>D"#M֖=W0wưK$==aÃ"Z!-bk1OG\YXyoQƸhRk[gA* ^fb9&[l~v#΀'ʮ'}&snj2/u=o-n8:W;`箲/| zot]W$6\^6`SVS&ǭ=6S1"C;&4r>MF51X}~y'ŏ^5o>?;rmC٣}cE3sU4vnߵ6F#} ^}u=g]4lcB䢧KN>ir$G T["aӕ('}e.DE.SXeE`й!.i=5;?ϘrطkFČ`' [g=Bg_Z'Gsrvn9JmK&0xDYy w|Z,.\y\{u>mnxN4 ?*iIM-/# o]mǗ9ך|X K[0!1W_m>{ҡG"c0[ݒ#wA /.2Q7>ŅlR:Lz[o׏M?=c##4~uEpa6*G* kʭҟ<~ؖ7ԀIns :봙yuȗ0>S^U:V=y$fAy~uKރ3'jGp5YFV>۳YuѰ$Ql׵RJ#zP&wuyފg"u:vJ\nIT?UdNP޼M.,~ܦ%FEӹ{ߪ#\i\7~E7L~o#z cKS.kmt#>DNm V>KFtXG8u (U1Lj @eeU>/K *t"FD:֝/5~FQ]w:5rMY SYY^xP'9JESBͽn͉uvMڿׁa 0*d5V{`0JhooR~%DLTo:`NVe%OۛNVMg 畬zRITUՃ$f}=$:ٵh40ιtBn7 r7Ko$m1j[dfXZ_Zv%wD5CĻa6~¢~Qѓa>;ѓj{ꛢnը᧢xh{s9=x|U?~; wbp# ]MQQnt?|x╨WOܶ )oGu!("ogh~V4hy/FuђkZ%X%zЩ W) 6}"h |x﬌dTuL 6:/|yg"(h ׍7 C&$IJ)ˢWG1첨5LM%q[|Q "GDosTb+Ѻ7c7BUf2f^y_:-JirJ/D%љ6 Ib[r({y]EQd,]D F%,daV#ynG>Ůj򏣹7EGh`}{^4w`Tv$)g6vэ(, J y O?/$%_"x0'ɨo ye7~߃_c]wQE/D떖k^-89pQJ} J-w/zp#E@%yٖOF][ي~;!KoRE`n'~ɽ Ljܻ~.9O>TXwv_\E1뢹'??ҳ@P͞(F]+KZ($'gFzeE/NbǴ|w2ia{[wJ^=NhenD?(bV<8⹸hdۮ|rgc(?G7s 'ѱFQ[fׯ\hE#zGy`ƃP j,PȳZ.UG_^ ]1%z4P ~dyXHf95,d2oiԛRsQUZ ܆'t=K irO_Wb=$u%$_j9+߶}g~÷_ث{"~@]E7t.SGM;p$Dc{^O?J "2sPEshlou)Q?m 6)[u|W9 hi=n pML#pAF (a*q/zvE)wm[ SUWvWuWlY_WFecsdAan{E}dgpo ͙Kx{}Ul\MM*wFss7 LYL'ͩiQR~[8o߱Dg<< #\)(# pf49" r컇\}_znw"Vc,\;8[pKU-p[L9nZT [$oC+1AݬQJKD"Lqx)W,~6UH~Rx2Y}CgkD:5نUgzO]FrپRQn$mbua/p J#i>9_v;P>CͯrGȇQ#_SE h@`2d'-vhQ0yo +Qм#d4!tld.uw5Hqܻ(XBV:n%R$C;&3M2]:.ъ,m/j~tV>OϨD1vNzoc`mo+/zn7/}#Y}3XrbAlEoXꨝarVM1KutA2F 7Y !u-nx+YE;Y_pVׁ&#.x庸-[&W+U1`p矹>y»'*ش駜x~"{ 8!n$ HG d6Oo&#ad>K,t B^6 Ӵ]xVPw\6j44Ɲ} L@)znUf!!@X{+' rk;Deߏe92g?:(f!*du3`Zeq1gaAd!Pc E9DR2!hTN,qbSwᭂաؘ{՝l HrļkoZ%"tgcDt򗟽Y}LkdEfYSbKW|mv\;k͊ӐYk2E&:)IB^̚h?a:YElv% sP! pb߶Awɫ2PԼ)9G sַeU=T tă޷t[~x_;5lY|=[ ߚVvDo~³UqEՉ̷zmP8j׮V{Kn+/c/DEpA쓇lr: ,e/Kv;`C{2Q/<>k⾧Xfە )d~d!_s*bNĝT&8xERcJ<~%ZUN+$>ufV⫍|ټ;8`U懔!1uzn S71d|u XeZ<( cVʷVse֒t&p+" h.KRʟ4o}쾺B]'w.<"3s~dh‰W{yY0wFO4-{"YgHH6qjL~x}SJm:5ifկ^m*YDb(@aI0:Xuty򐛶,dv'}TS:Sۤ?]LōgNN׍OoxbnDݹoQ&XM/u zκ_r6ֲ9o }[DK:rð1`m7&ũox/6a@@bB!w=~O<{m okAQl(°]μm!v&&-DN.oP֔rɎY|ָ·YYo Ǵf '.HލvtCp !Hp|v#76~+SlYYaLhPLĨ!f@q*jiIȺd%99"2"ʀxm9yi0|H*UzRE[OH%lHu>r1@8s.&ef*kq{`=n6<{|I~!,Z\ d+O֝QzEw ݽWfAͽ[H|ÉBCMI_-OL0[)_WTEAT2?i9G+]y= |%wr;1##,`sV+61b(]Ԋ]g:5yi$3.eqj`ji(Vt^i# gRL,OEL: )5VL U"o!6 x#Ʈ_t_j0O10Id])y*8H"I93*Dמ}L+%\N39HW$x8|} 3-o T>/YNG8}0 ƐP 릭~-C5LU0MTp{`\V!bXP QuV=ϧ{D,TeLAq4n ,fp0D_P({/CG.Paw Ĺm!]6u)Z{ eم?UQXӹz)<}giJ+Y\':s_6uUX`*YȁUןONt zߦ;E`>{H )#xg[ 2JڎC0&9KT! )F,KaW[Iڎ͡x2Q=zA"8jǹvs暵AjD2Àa X C-2j@P"`@%#갱oekog>^Uhk~*=Lzū.*C f:YL0r ܅JBeJVTe|4T*TkqabwH#LOji1J )~pC8Ws0{Gכ/€P/,c2Ba#0!/i |:3%Rgّi'2GRXN@[ )vZTgcy֓/ Ypw+?哢#%T6;;Z|C@u=CݛQ  k}?l " co\ZI2z!f]2^/ZC o,@QaU#53u PǗ+uf=  Ůw_Zw襒B}< ) zݒӝq%QmڊI]x}<'EKcڣt.LطSXq&~ 13|JdS[F'v@2BN;Sǔ+ ؚRwvə+/6R =U"ygDXδJ ɼ VO{_9.(hѵDgT1J[5 Xc5` hL"V@$duq"+`.1(0a RB˂S6Kn6mD2E5Q6t2Ѧ dt`bÓqX ̋mD <+O@4bƀR܇}.G%b'-r.Y!gxY6# JlP\' ]KC$z9I}Bsɹ]oB"Ip9#$'`h8"Jnȵw_~E5UɲvzD a&+UVO"d!5WR3R GȔ9E^sz_j$ F'2eR1&2'<"j]\+{`~Fq yQ?Q(D(x _ZIoyӧ5ّ-ӊu;dX- kWIg/݉ VeY#Y# *̋8BՌZ1i[uye4E{&d4>b&㣭FUhH1( ?-Żt Zi?ZfēzCn< (" Ȁ:.c0{2E}$@z+H'9d]]3 `y hC v/ޤ_SQmnW3K1Phe2 QңSД@!=ww {e`Xn\ܜ\n? #8 0&w@E`ɧp K4P U}F exЍ xu}MĦr-aI@u8c@,/ĥ{NXROR"R!2v?5<^.d%Aji8H]J u@6BRH=k 5SIJr@Re.mߛn@\8NŢ m;D"36x?U~ pdi RBx*@b17(WhNjf*dʐ%rڳ*t5Z+ .Q.xfye1:ѠVb "\΀ca`ڥHfZs_ԤtE)/G@2ne]pg_>NPө 1`P§6~xk^V5 f)=kf+Ϛ,D JM,JYjq7L9Lg[Yoy ` 4 NYT]+ iNs} \]ak3s7dL݈8S2ouO;>m~%)NȬ|!k!Ew"N"F8&XYqA{ K7"@j}R!$A`,@Xni"z,psL? Z,8K4$^UZq-LʊKݟ  3[|i-,lбe/).X3H2v#& " YX0>]/ QLaEPDTA'C XDڣ̆W(Ty,dE x@& s3 L=\EOEXN)ϕTpțf$t0E o{YP%iYA@yH+]9Q9%tglб,bAON|iO Dn6^Z2ț;X)#\-prɩX-yK'Da,B0xL Ae B4y8:a("pzEz`0!&/2G9 QX Zwf1'x'!+d:DyݧԎ1VIj.ԁN][sβWTȯ)$ 'N H[OD `5B벋uoG Y*2W[JlE^G hD0aB$!;͹-U kfM>v4[@ oФ޴9WtVg~8:U_2۽֘o}XX1w*ᝅ xc6U^[NóP_7+nV4Jb1GQ!~ $ra$~%LV,"7XʔoR6bDx'IӹX1%I RQaƤɒݛ33n-Ĉ@'13}m`ݰ$?p(UDsNvU*1l M+]A FfIqR70g#* [%% J?$u5g ͒,dJX4asz՜-D Kӫy"@!Cb/ɉSRHNk)`T|\^ǠC.ZS+4RSbg-"2<1b*֪q̹Y 66~AAǐ(ݠ[_?Esɧ_Ư,؀4O#0"+Q(  ]Ism|js=-Uuԑiq!\gXg@uhTiP~V@h 5A5H !Ō>5|-(hMa],*[ff̈́!_i"t(CG ~t.r,q%X|A Wپײ_b*Z{!ouzcOם{L3FcI}cGHf1aNyű` ̼4 h[r\:w8 546Y[@.QCP@(%6J+\G ,{Ɵ9ˏ\Լݫ`GDQQ@ HI!C1w2>.LDcvv 0IFk= Rl f)-;xMKGB,TnE $ PP`OL:vp rh X6,LݱHac! x _[aAHbP n/ŽMz0gKp{67qbg#Hm@uGR' QnN5& t  q5_;%8B@f E)tI8ޟ_ׇuS ݵ0ߍ+f I!0;->vQKmQ.a=IĈV$$`'"6" bW!}KX+.{F$"jn= ˆegeck:8I.zw_ 9%I7息a|ǜkZGd#ZSo<}[-ͽS7y_,V*?bCZskFٗn?";jN׍xᡔ7MR `۞v; e;tOW}/7=${0,y4h0 ͉ e/El|r_C:*(6#w 냖 "i"9}g_W}5axcecp?fgp2 FB- ~ TɛoZ,r=Ik"uyp#:M'D,9(z pA @+9 À*h/lļ@7+H4BfDSA ZF5*@bpDDP ^̤7ުڢ ^JKD},V-}o&&p$("=qb|ۊFD@Ń i/J3< %"##: xPQ+Ěm̲ch \ҍKgNv5c682%PU|Ep:KZ˽VXXu37DiPoObBZ `Cm2޳#m pjV&ZRGhT@WGababk8A.0k.X@P@PAR.'PTbQh=PA&_Zd8Ц@ 2pX=]7]2gaOʰ 9FUzhY.|{m_+w귍&;{UVs`cl@]~6Xmaepb)FK{voIK>̼m׈dcAY1` m6 +H2a%öQxfdp8FxQ E/N.1 `p\ %tl? px]{7A^x>@&1?k\=- cYІ&n֚r-5.M;>I!!M/.hd272"E,m׵FM9JA[ѽ t:`R^_6O#FHa¢G6x}ٙ@Cl<ݘ0\z;  18ǀ1DU2/9rz9M& lm RV@2S^ˁO"'BF; HYgJ ,o]q5Ȃe4[i\G:|ʍV6 3 Gr+g`YJʅbV+*&IΫ0tejP$Xλ"Ĝ}ϘD.{~_ e#[A8@]I: `KP&+XO(TX8E `Rq+3̌H S<%ޜC D\!;5E]F8C+Bclʏd 8^W)eD :4CC)"(@1d`n~e>@ nK eȭnO+nB:?_8%Poߜ Y] .:n9WZ=K`h\ޮXVGTܛdߘ'  ]/;pA `=eͻz7>acԩÖ_h2LMKj?&1+>SJn9M" ℳe뵹h9ʳMa3+Ҝ)pF}a/ޓ߮w0hmUܔJ`z\=D:ͣ9JG9o„d #RkͰ7ҕ",0̇EXِ,S67:dF+ vLXD |O$Tޅ7Q1ˉtp&¦;6XGOYa9bD&W5 B0R~Ǽy4f( $D4((#*4vS̭!_84t"YI%kU VIlO@cʄP" a3DzAx =t)Uǐ$~0Dkd0@yT0wBHnF'' dMe1»&B'>zBͽ16]i&,#:t @|H]>ۍ>ykWXTkz<A_p _hū nTq5kAKMY%<̚\º3hÅ9znR5)A=< uD ǭ $NdN(yD&" YQ`&dU|ǕyO~}rS0g, %-:ʼ@ zAa)2PZmZuM5Űp _nl?w0nLrMg3 (‰׷T5Z/D<(6$^ )],#DOo4&Zh3"3!ʲAJv, #i<'C[&&xk6<6rHx9t@* d}У" @u ⫍gv3 .993'/Sjx~jnԱlƩsޮ@@鏠P, j;] yoجs2mhivU߻cxl"P;-H&sKo>?i TtTCKss."xfD&f XbۖSD} Oܽ?"PM&Q.XgC2c+,H=k?U>LY3[3،O@b1a.s""PE@Iƒ8 B8,/ MA ,|-L[BQ)sȶn fe_Ig&Xiw dg^s f;S,"3{)Zmm*2Ajzuΐqo(<6Ɩ5UT M1P0 YK XAMhVI 0e_XFNT; TB "8%D 6!a|=@-~ ~8t\ v vDYdD|?qCPUrRG@D-UXr,ȞxI1K6jپ7nik`dx*9.ae0I>?#WD$ hT =Z3Ia ^@b$vxֆò2^, *ÙHYoލvKDЩ`AٮPVA' xP!,WΤ+oɊFwn}%(y:OsV%Fe"RxТ=ZfŇ7.T>Nnvn^mc 6X(ٶ.ώIJpLDf TEw44G2ԛTCH+-R`gO1"JsdgzYIm(J3m]@6 gVl78Ǫ)p9gX+ " (jDD)\|?}, #| TMf7n;ME5*ZIPA'D|C<ۄ`@*X7'#`\/`IF z@_X q_DaVB@$ \.R;B_E!&yD@ &L@ 샫ӗ`Ds_{tIOz꯰s vAB@FFjOFaZ31' @?!XfFRwt[tTI .sI40դ-1k1׹#+QJp9kzFx 7{+ .:zrǷ/ շn1-h|QE "<*06?~{a EBtN\TqCE<ړ֍/S4^gDn2#1'~nNz&ga˻ k+n*eU.yyl* HQY|&YH"^NѡRx Q4"* hZ*֐5 ~2FT$לd0I +$TI%؄e#(IJa4{?So.-eﰰe%bȨհ È+jswuejִ6C0HbZ#Y-6.HF%C8ϮfT!aV3P! RhH %F1,@mkn{H_D<ؽU T)# o( XE&7*t+9KM:]჊W~sSj\/w-,'I.8t Z!Am54ׯNZK ƃ(H 'qZT]9M(uΓ+@Ʌ $Y;UUE=d[ׄ7>xGO \-3) p!8Q!T,!II%+&@8 eE%HB+ź ҲXP*j& K'H@,`   PtW5LS~E3,fHy뼬uhXm3['”Z4*l&.* mf&m}ˆ09\O=/!`Qpm@EbT#Рd2< Q uɈNP+. FsfvL ]9(+NS7df̄K,E%T74wB `D=t'&ܽ1ge nPZ!G9Lg2~uv8M6ekwTL9psv uj-dYg8]g80BI\ɐŅ~7w>ZJ6u8fD;f%vK[z3RxmRDH'F17-z΋{.[qm7{ɜùQn˵_5Ma̲MH"f&[ލuNdfPe3S̡ 3# GsYIPFNWL ~jƄj'`ώ͊ACS1>-ڻBQT#=Qڮiɬnk4X ut6at,2ݖYՄR=+iLYf-%DitZHpO]Qfױ*1y@(:(zR6\!|o]?3; AbD4;mDLMQEJK˾~=)B6#&f'ʣ *:(ԗSURtra䫸ye[s,(3'^a8b!ZvƔ`'v3?->/y$ )bP%fAv[eB,Unګjу S:*al&Yҫ"Tͮep?9G^ur'-4~ٿvBT01=o8J$”/z}o{48UG DXA ,#U01΁6A.֋s+"w !ae`/l(N慸xNROCżgl?"??е~EoX׾r~I7a4}}n?xqiOTV5=P>R0͓_&. `%hHmp 8m{Kg }mwKP?mIHf!"Q Qh! 0pV+ W\٫BM!`"V +mX\!FԶى1s1@Th6:5BOa@bM0a` " hʴu톐-!$ Hs]Pj^N],n[rLE5qM}07nF:J耀`r1"QA`[ 8׊v~1 \J%=O63Bˬ;ɕl&W*6[w[-j|w$k{y}'.Q%WB! 6DU-i1 eݵO<.& SNX^FHC?ݐٮub W_ @- Q( H2R)ĩA1IH QVe@baqGS-Ę՞cŷB.KڽNqtj%vwQڛ "rte۲ˡ79{f/uBX[wvVbg$D&@ ]AADT&]i(5`T@P"V3~,l[9:qGR"6p4JdnGlwWJ;l5BD0OlwcضwAՌTw=Q$@zzg"_0ZtAW?]:im躪fALթ ,Όxpsr;CYYȖۗ,hP/$" Fw՛6,|Ps3[|Ql`%pRy~!Z$2==~l7^ŽHm Ť%fY8_n^4a(搗ɺ5YYGT^u<[7][ I]D\QYl(IkHkGsnop hlR%F).75i\$b3aq $9db%޷P{=U S"7)YJr"~We1:'3{0 j3ktnAX$kh@n/{z ~@`NvQx!\-9gR.xTX>8"m[V׽E &W B9D,hڟOSK48Qm.h21j LQ>:ZpҾg?f(KH 9 {vI"_h(v}!*p ]|̎p?nw#-{m'#Pŷ&bW}DXTƅW)36z%z'Q%Dp^Y%WԤ'uhrQuҙ *YP 81; ~䯜ީQׅHD|'"3fv$~ ioO|߃NT }?t|tNwV= HEw2)dX=\|L{Xc吋bV ,9Si*I{3P4 AevXp+`@KX%Ve#n(=ىUD!JoX ê  ѹɑ%ҝ:{1Z:PmC1femýX5Rqsb^0 Sq_ بi`Z}]П*ťik14q\_> CAB(ݯ3o_ҹYVWvsw>G _&F'  ѐт=qkJ HÞf#\vꋝ9n=8GGʲ51ݠ K EHjQ11aQ&B a$`PNJJ~sIbXKeMJ dN&H0$1Z7DLEF<nB,l 0Vɺx-Q@gRQlIwT3~]/+yd]Y+;kN j% K0 8=L>,zn akDpwj\pcϚ CEW2Kd|b~Pjiȑ (P(1.|O`7U)^ @lr)sCXLRzw m~SD,!xr+wK?53噋O:Oy4>ؿ>ڗC/1Q&6`+rPܼ sB-8'@]s Tvi0C7/eLBκX :OU=39^;I}\I8roFsBus@<> {/޸Ls=vy VF4OZH 7MJx%P,qQ+!֬ \Z!Ɂ ( e;+P4g%NA-} s.J$ V L^9nvX> e2)4  $C(<aIBQL<,)C(=gf5 Xѽ; kiG[R.,G>/"c2wd^4oyW^T? I%J; u,އu>}=vK@M;h"xt[ T~ʗ*Jsg/Kx,y.@»ad{l՗Ψ1lsAj<|2,jȣp'4iU줼}?@nv ֝B{uҡ#.Yi($A$"#DfIA niv'!?#FSV,Yj al1||:k7?ѯejhe&;[f*p /_\磱^%#+*d'9kfSbӷ6aULTpV7@UJu<@X \wE^ kO14F@n}[!xhC9+Tb$`z(0Kvc.ZuV,B)Mg94 LIO}Yʘ,:L{^I;FFjN8:$3Bu\>QN=4q}`{?$r5aTsJ^.%&B8!;ģ|V͙qAx<[ƙ'e_A1A "@!j Tf8-IQ/ӝK@@Q=6NŷzI9ޚX8G;|7?eH,+a="alaO kh[(@d=~JvvD~ZYAtzb*Mԝ lP =zXůL/@6Ffr TI g_mxaN pK|բoKDHbX{/O\b#y/M/sN>; [l;:ؑg_,6ITkuʻ~*TefD|Jrsw_S [a fD?FsѲ96%eKw--ߣh+sI V␔81y`'u\=6Q44@ġ-dULHuNW|sLyN@P;%:e;s[߾;eٞ&;#η^;.r*eK![1nwovԉ_`NTNPn@ š}3MkGPWbU6 K3~1*Ba 4dZ7I$̨_m:ܹb"9H_VԂ*֩7t?oZ hX$`Ihvs¼ġE-%SoFJV>!e Dž%3 N&%0ILdM+);9H{oj;[fOR K0 lnl\sΤSӅnȐT`R ^fGxTZ8iȖvKBh+wmk!<[Y-5T@DVlrˌq'9{)V?l Dro_hZ~rbM:^kk $l{{;w~u|]vG9X -gΎ7wUBϛѰ'Fޗf,ɵlpO '?fYb_LJs&@ݽ4QOVBjVl7s3NN mR\q |m %ӝWŖIFW96O7nM[wp><@v/<r6U_o=Ԇ§||ged4) vIZd5d!4>,>e5P 6bA@#܎,b}o^> 7PZE‚/amV;u\GCgnZgfdyo1"?A:bH٢˷><Ӛ6}=w<>toz0VlQf"@`_ԝq]|w{~=7Q=tFz^%q1cq>{=D?SI\dg %R+{r[دG9BN{B~—l ʙ]e'?,Fv|A";I+zXL%}Z;Ж*PX&8mrjᦆ]kp :TNa"`m/"Lr074.-yYS?/Ѻzg^dzV/\\1$v適m>QK0B$z+!y_![р#Y8>iǥ~ҷz SrD|QRO2sϧ[g.ͽh a`)8ݘ^z'v\yc[t'}t𸧑{֍~-*64TL);ڮPc;2;xYCh81=TJBlV[4fVd/u?yݺKNMl 50 @ydnkwtޒ()%MH\wv#}NDZ.Xu4fND1UbGЍ96*=4}zG='j@[qx 78E9 *@ 0x4EߑVH\4iж)u[ 텅vcX Qv|xj8(0lor~`Reo@Ǽ=6"ovz>ϼ7||X-H )%KBᦂ$:#p'Qbկ~+1Qg[m?)q GF6c/Lh9sM LdaIH=і F`S܉5i_кMk{S;4`& ^=cHp vQLsNp!zN} Ƃr˰8L 3N1n1eQ]lƵ-MY ADԤ=FjBXY.L ;@Uuį]R'.H(Pc};J+Y@5M(}$,_\U6wi=VO}tw'݈|ēݡ>́B #CKx'/tltzfng݈;(9띃"<[~}ܷ[U֮ހ %-*h`t)-6oӪ01YNbB| F"UﴗxN ^Ǡ6"Sꤾ$'WƁ@j<xCtDz(i3@赘1Rs6s^·Ms0U؆&w!?7ȷ2 5ݧM;έOo]t/][x.ŷ>LԽN?n7Mקk-N{߃^0ii98g4bddQ k)hkvlWek3@ie[t󿳜6tY"Ujc~*jYUv%fYC}==Q(4=W [whSm|yGz_5s}6O_e-c J+Zy1S&[ʃ7$JZ,b3o] 1\4S™'x&Y9?312QiM B!]kÿ]Q'e_ni1Vݽ^ܼ+.#Q@cޒ^anz37STƣf*E`y3fe^iƉ-c㽤Hvw{+Ӭ1ה)\ftbYt73?!W8'd'SK&-/12= UMMdDJ,ϐK*!`mb(!dddrBqozfa)U7 8|\-0f6J}="#":mk.g,lG|.fL}#3`ɿyq*>:m `+_X4Ypv}}ÍwB@,eڜ73nr,]CLÒd W[ñ<*ꂂA/[5'P484rQcUK甘_Ҳ'ăO^n< >%~~ _|PT1wIk>(j G+\=u:#YdRQ|Ԗdr9"A [58 -HWVvQbSF ԔG+~vHTpDRO  $wPRJ)ˎnզuy&,BG[1Ce@ #^1 ?f팱zxD#"uiϏ|n=p?!jn_OyKDJ|HFAi+yayt<=6 l˕gWXb~ ˋsw'cTOSZ^ 絨ѱyhwk)FL~qV[U;s(>6FT>8PP(c%6LʦKp~kΊk=q2\@E:z;侏f; ޣ)cbk!,S+H6YYLxl`<1 X FC0@:1nV]:faZ>No;/ٲܶOR>9)>byS{^+`EDG'/S/bXO g5*Rkoש\JRbްᶞ/L$fr[D'e(]-!DH!X@LhH[ 6FU/ՕYr al d .U7P ς & mN~>Y% @ l ]wXcЏ~``%LϢBA$}iDZO7TyxMTf:K\r,u~L_/~}UC x;+,^|D-?Ou{Wd\[j̯,ę1"\`'?*'D !q)Yfy'n9դF[!)eə=<`b>+pU:{ǭJտ&Y&20r.MNv#7_w_ʉ"G[x".4}U/enJZ1.@Aa$쪦`3Y6sp5Yd)"y~:Qz_p0A];f(A H &YE"z?qհ,S vChOaBd!)X;0$(!6Ʉz 2@"_e%J^ڊq7fGT aoWS>uvܚ?@َO>q9ɀU0ABסԏ>U;g]ԫU8?PO.f&bg~}./ t Q.,=*rJ6 e#'QA( M>V,DS/4 `-S=ZF51Ň_ouϋ*#GF+(GVH@@L9tR_}qR"6aq֝}Yf^w5u–U7BkqM;w MoM~PUQ_Ƃ.-c_8 IKl 4R]eydâ0d7{pq.-J[n} T,Vn1C s( E7+c(0KDZO%@-9ْOeP+j:Rr:쵳@bb˸IJ3#;lw;%AS:*NGB;pSHdQ=*x[X,LŀT 8AR4uuQKh@pxtӔڿ7rw ƨ~5`"IQ':;3(?=}ႩMG)>Q/Jo߅/dufb_֔9#7yחܢBOoU>s=\< 櫲Xһ 77|ӗ;of O.ӕFGmvea_6{8ǽ+a i- 2"ոܯd 2đzn||űtSs0D B14R "ѱNMM>0%H0fhPFWc_$%APr/CMkVaԎt)ձܼX^6<>ш `pVY-e)O9vy@Ǽls'4No CcWXt %Amk`[  u<\QINfpzcS#1qْRKX+0~nLRZ`l<7UaG/>uNaRΗ;pt84W;0s>[g+pCLʥ ŀNiAh:SAj7{rjOs-^9}p~3Ejӱ\R&AωPbw(r->~7/關sO]{`f^"rr2ضf"Ϋ_޻&)()A!ia$,Le Լj?= {Pf8ҕ H`hKۃI,R w8Ĝ=Iz9l`5}6:mFV0EB] F!_dVI1p X w!Lb› $x2Z}LiRtx(5rE g_ le|mէDy`l$慬JG#95e|AH+Ujd`9ǯ{i偿f{Mf#\9s=G0W,̜R̓`iﳗZUb8AEEcHR!3 V (x}<팸K YzP˕ga8爓J}sK}u ܽ[v6dt_hIS >QA$+bd-I`p8XPx!;}8O ̋⍯Qos9'^yẍ́OCMSkTEӔK!4lS#!D% nqPYĪCpSکmhfK:TKBI^xw0 HÁ*MV]L5їuY:ynNu>*Ի-JnTrt+7FʍRwlP^v ҿi=[0kGe%oaR X}&br_Q/p p$uo-YZ9W/K8W(tdMa@@{(cȬ߈s-?)hN9V/g8@.?hkfx2o k+Cjb91LZsuSOssNi: p/9 Pj a&^./ۖ8и^<(\fun?B!L5PmV{ vz&QĖBA7aXt7`9ΦZYE|*.f8t\jtax^R`@tܚ% vI,1Aʵi98Eo{`+P#BEj")G0r2oQk޷c:މdףZ _vT GRɃ,w8O\Mi{{7,ƓHn.(mX1k,>`Kn}GWƺ13@)  VDb&SfIRg"+>C.L}4$y *w {lDfDq^xjaudk_[M~1u*nw+}YVpЖ甲gʾ(&Nc O?:s]r$rEVa"{NQ)0]:8n?!\^/?#H@s6X ;GĴXYi $Xȏ!2pl x8śjsS"H68& D]:>?k=eK3T!!̩ ,|h!Þo'.W_>NKsO6NbrrCwU 7T 'v~U T0F2A77@IȈyNi.m g?m!X]b[֋Y[?8֥ÉDHδ'}}LVs^O. ,l]W?x7.upgǥ=}?o2zqu]J܁W"zB!#  dT- .H xoUEf;Se+mU(]X5A# h ȔDV o>WéګlM~dNFHrWႃeM>0~Y9~NE: Or0FNGvc g֌.]spO_O 'mcE83_#H`bKN`ngKbLb|@k `12($Du,~n H꺷{~d;W RwKX`ʄ$ B57--Db Bj~I`hQbrdAڀ+feV+̳2ܚh*+<LW kbs} `` 944f:e5[Fu(_UyҍMژ707C4?%w5t R/5yO=:0IVB\!Nv; o24^N%␊"E! u οXs#\_o\ ޏMFާ͐j9ͯ)&~RpEl7Ipۏ{ˍR5S[{ jޯq?KmưR٩mhz'rm\8Ԛ~}0~`>Bt||?Y. A*cfZuO/aX+{;hr7-=vw]?,opz%@ YjŐ/@ i.GQ@8³8McSl=MbS9 hߟqӁ.ȑRtGdd "56j>e/oKY; 4kUz^:7/W3p0# =!fJ hNƐdStZ_|4U[O:ۆyh@g^xӟ]ouw˭q܈ MvXʁ5Y[/N59z:9el{Z6`ݪl lsn#O! `G%Swpink'eL-[d7լn[OdYY,leO$=s wML@Q-x=`Z(!pݴOG=!*S Q8vZ҅.­igX82#$k隋0g'(u[kоbr6/aAI;^N| 9%,R`Pϑae'JKb  9X3,[Odk0{L`p|r-3zȭ{i9Qe=+s- "cIgiYO87IRsaCFz5geCUY!bnDr4L0̲B1g-0~^Q$,|XǚKػ! j m R,H֞bJ4(q{=`A 6O! EFXD R3 21BTI!(y?lt+Xm dE''74Eo'.\l{_nP ^WHJ\,ÉVUK&TJkUH@ Luj[[ GLaYD iwA箽 dwvϓ'rR4Kwt4'LtYȵJDyo@Z_:-Y<\ga?1s^˸UiFr &aUR"Ʋ PG|H%{FQNL ,hA8fBĦNHݜK4GgcEbf9J:O,>!B_b{_64 @2<³` n?zbe8{{x^i&hJz Rr! ǻio%6g!bxmzb D%,שaұ@ &ed_JZJd'x~E4fF<1$2 !x QəGg)yۇ||b"C Q @LEyp]l_ad[ḵFBDLj$osf$[i- zH1 ~If2/֑iGaԱ!/Ce(0_$Sw=v)Aʓqm( Nijۡ DflSb"%14 r2(͘aq[@4Y30sIlyctkM[Kn4k7zIeqQ&  !(z߮5; l&P?1l;'\֯@0((xbqh+Qr.q],`DEeZsIb=¥f W[btt,z lTw9O>K,ԇBD1͖8 nlؿ?12I\t!"d:XxIIk IH^ǜ9"^bjҥI➹б%Z`YOL buv"PC 3.2bwo@iekdL 'Cέe4},:3Ygt7?o`9m8CG3o"29i O̳&, `V`qmlګ]\ H N\l y`DY;yw<GΪF%,rXp$n+)!ٛ3CP…D*S<O۝M:C)DguuaTA tl,[Mަu]y4)T`V Z>ns#׎4> ̻TT]c[VX@=K{_&!,lQwu\-'M>LU a;ϖߪgYUY%mat!sѦ6~GGdUi*㵑pRfMΧ7/߻մXY147Loʖ_ $"_GЄn)JO存+[B 8[cл rFq<"*VxѠA̫֝Em1d/I}X|g{LRo RͿa <_g0G+EVI/T^SeM=85ħXd'# )VG2!&P *(]r-@d՘SgYcfplw/yOmFǷE.cbAs"7NAQ!ҷeK45-(M&FvC%Q io)b,',D q;UTԃ,ՠLRId 3}B9ƱX%O0OVԨc7NeaFXJ>I ,H B&`K|9h4͕3$$&TX1:Vj ΋; (F.D8c#gpV )CٝU030O=$+ Wr* sy` >j iopO$X 5(K0Vd$ ]ATէ`>)k5B:^l,7g4ۢFGsOaj]WQ!w_9 BEX&O!g--nMdU²0UH.TB "%ѐу=Am83 Š`BӱPn 9vO8w]jByXYBp$L #@4e@ Ji5@cXCvutô qG㖨)㛃>/-D *SzTگ%˷nik]/N.c~H9QJ9'#[(o9Gn^Ʉ`s`w>? L*R7v+'OLx7Zpq|;g^3=d^bkUm~?#T!&Lz62 Ɲ}^^6WٮCnxnov Iu[Z &@Pjؾ4b[GtՈ4`bR(" D *\}xJ4Z#&jaQe$(0`%HGFټɷB8דɵ$Kd;1Nu^<NjB,b kA+Ax$S% gf_JB@N xE6ٹfѲ'"Ys:PxWtG46X;X>:dWuJ_eV\IMVD.|b<Qnj {omZ?r nSPb$l*9fg2qV!cgWwedž?4.|1$Csä:Z!TH?_3 0FRKaK7(- H^KBང)*<ʰ2vY¬^ $A,>/$\5c[^2Qՙ)_i3|H ,|t6y;\ڤҧ+- ޒrD[˄7ܹe#0\{"k+Z.KƃW<3wإϐڇdp#$5u~-?_.j0P"u\*.e Xڟco\B3x]'۱|JX۲8׵:a e[%W8-Jn16oQ:! !W4 e" jv5 cYS|*~>?+O=3(uc) Ȃjp]]$@{9P`TB*=xa f˾lgI jn?A3txl"6^3teywk^ h^ peUhr&xƣ{;CZ`0`HH]2}jNIBsxu<}dT0.OnW/Dl݋'\[ ZVN_8(=^fڊϜsqތ PY^NCVz3]ihs Fke_~gz:6~R50[ڳaφ$=@ TH0)c-IN$'u&.=yfY”zBȤ,"ܐ[8kȼa_+Cʴ6,<3e厍`G8@%GjXu[ c4=>R$0!ѽ $XxL Ӷ-kc*Vbn~_D%m̋%|Ϳ"6{B:swĈB.є}_z`u,Zko/X[G4y"~3ڻ p ۀ:˜y̋ W+  K;Ԭ!+vpf!VC.ݙ11DG]AZgŵ\q3 b,X p9yBQ[0a7Gmsc儁j-/zg'~4zE@Xfm֖¥fENaBdkx .t(EqEJmMEϹa$s{Q h] yV&\#%TcoH+{$埶'=g)=w&0grެ;ʜ5 AJX|AXQl{ A 9D>:o/5LS^z1ivFN|e)Եk?\Ud)߮i6b~~c@ 5dK{|lEoc]|bӵ­:77oru*;` Y3wn9p ̣&]O ``y|,A (0:|(+HP&EvD!*$ 9u[e,o֑oC+"d͎ GSx똧 TN),/H"gBk[Ct>>L]߂v pAQu-%pUrRvA|B++~)F4+G~XQy4~n5V۬y88E쉲 p5@j9l]*nu /X{ǡMn[JT߆esYCݯQߩoL3D}$d&M+[ gS?=<ל׻,1H% !`\A;Ǽ6chZezT=$ǓAKK1ѳph E ]+y ޟL<хD h anRuʥdI?K%4SD]qqН3oqT"OxBIKvc^r[ F  pFKuЫ$HG=];}~b1Ƃ,n"{G!GXCDi|q+khvMBddQ?c3J](/DA=ZrY3h~uRC)*rҋ~yC˸h %L4i^?꘏2„C1`L$,ALakGM9pkJk\[Tdq KId, וֹI܉˸Xz&7W5ʐv1\mk`s\fܯ]/hճc1nd-a;h` [2V*DXJפakU-HĤC^Pϙ{ujBH:9@8^Dd0(r/#VgcYBf4Фb˄,gƳWņL㥁cciIbcw;*[m#4;-O,{ֳO}Y]##HX뛯')u02 x*V՚G~ n/.ج Őe65S`my&*wum>6#?^S W󦄡@(Bv$uc 5@G5zLF=.%~`2B Z|PJ5\`k |``o!lN(į6r#` v4=a~i&>̞+4G0viɪ*&*t yMA砫6߽[YKڟӓ\ K=@@x˴e+Ӣ I}K>$٫`?ZvFwiX)v_\(IM%6jR]ܵ5޾ɔhoRyϱe}qv+zYjҀ;.-Ne}tK{%u̝ﳝ3`Z2u#)^BuLZ[fmZ?ǎ3o΃ e pFXaHL&I] +K'nPnZ>X;OelPW0,x4,cFp덶!ǐ̭ %[?ҜRka=a9MqbNYpHnV}9z*[9]+T# $~)u/\1N]@@РU ZD;y=rާv76kpΛ v|Bi}"a YniYfCÓzxΘnu1qpu.gGLyeBuӣ>c(W>o`_O1%X 9nTGguYH~r@IϞߞ]OF+HE31 0D?8vZ6pط 搴,} CWAes~>F/w+~?E_|#^J/tQTu[լ:[XXDK'5N1vV'nV5M"J_8Gșq+d 1/ Ax (: O3'i _:!|ϩO PCJ @(Lo{f''ýr# /7jtUĉ†ʄDZʱO9(w ks;θvT㲟ya]H n(}j4kpt ;A+@(JZB5iR(5lxBڅMAِcD5LvQ3-pEoAr_zC +SRuK*,%Lp "VK*}{yѬVs{җq*( >TOft<=ٙM%9kLOB/u=bVul#?,:q)N^0!TSb}o:ZloZ_ղܥ:iPyMŽbέi=^GfjF;}o8&q9;M,x'iT|Zͤ1'kn8W\͋OK|n渠վT E;0L(f赟Ff|=w'u;&n2^h`|޽V?8ؿIo='->gd_=?w/.{cϴl@Piy6,ciYA ;٘"HD(rL MDCaPWYyC<>jiZءY}4#ocl6%**"@pP-\xπUaE2q)CjF}4e\(V {|*5 뚴 @eӳ&5$M5R_W!s&(jZΪXzB,E>@s%W!h!^a1 ̴A~;1DK gH"[gR^Yc0KTmC]/lOw{u[?Ey#c|t ?~WLb)=…z.M}*F'며%nIS0]Vgoa=SO=iv%*yM }nuH)_>. Wlxc<*QjRe픸PMR2@!| N͔ew0dͲjfI6gFBaM&^k tKtxix:Dhf.<!4F+m4R(>'yWZě`1V?+SJ5 G*y,{p`aM.칩" ¤,h9cLS@)dMqZ=jp96+Ԓg֜1 @^ $ҮzZ]yB-}_7v)+Q 8z Jg[ven\yEp( 07ZEEX"Oy~Q2ۓ࢜(D `VNngmOC9O9{7=3Դ4V%5W/7u `d5L3>< m))5%jEXD2ANzQn 4U\ABQ,6&F/OZ V QM?)w;wa;gm  SCE}RHn=tS4E@,2F ȗBwˤa~~;O .w$ZuRf?V=Llcidx( KaPR"WXY)r{;weɣ'nHp;!D Q a56+Q. ^nqI/э@G(j0v zZY2: B~u/'yxy@x{$nT_ - W]Nz>esUX@Hy0͊7ˉVK[C>kvӆJȳlN("X0u)+ >v;)$"_ʪg@n+Lύ|_Z=,69OW%~B@j&,MX]'ͅ\ID?}^#wt=qB.)τ)io ۩lS{޽>ֽi[zK5Lr.\Gc]pQ{\VaJ%BU$URͯxSC%Å?c]*t'K q';$_-=Юt8LtԪg;5s⹷au}ڞ Fm.mXwتvz"x޶hўzkk#L{y)xX-Y1Wm̞}8pW0T#yk# ɩ7,xM89NWA0eM}Y'*EjJ۔w]1]3fG?b0HKΔcֿvӒƝO8yµ+|`G꣉+S1 \L.] LMcnkěx7|>N8 gWESe9BJ;y]rR9n)}JC__q,Sczz8B*`_ _h0Q-~ Dr`yQ j]w22]^A׿l0>RI<結\q7Kge .߲Tlg@ݑKpև((|ؒ:36Xt3K-jOf>Q)VO$~S?s=|t37[WN BIϞ?ZFrEY^6!3p0'VMU; >Np~󾌖+ॗ. ^TV8*'VF2.A.׿[HMul[ٵNEtusJՎ5p;"[?|BUoB<r[vpH֥5Sjƣ%QUVr+vw`QXK_GԸ}NXw_M>E38%{1.#}\Չcipm=܇wn=J"M\J#%袒TݯIbmWNۿY+  `Y*ŠvmNQv;Bm`^'R5qn%Br||Bl"rM,Ա?Smπn,~m1(c,MCT:P,t t)ܔ釟Fp٪^^oD~UnNJ `/=T)"77Jũ% =y=VMLܴՁ{Q5_ó.=Ofy\t(F7ΗݒAu@IuysW =1nf&2-/ʦ'^ol ΢m[EMK󀀹F#.v-Yk֤C50Ϲe?9G7mJ¶gߑMƎ:;CitRR>Ww]޶zG~f℉`o_Nq?SnXq/VQF=4R;/:Elb֖n9r4j9{GЇN.' /te|*M xJsUW6m5r?W&<bh~|H_Q]iԠ9W_52#<³_<\qp+͸mjMTM]_s78HO8ؚxG34_h0^kjEQW5z~>SEv4 Zg˭&}wNfXͷ{4هa۱/EvߜgfL;mpL.an[*DoCEFtuYDJx''\,=Oyu; 0F e{n{[ӛZGusðz؄[޽' Oo[w&nb;㬣Q:$\٪vZ֓Uk>8vj/K FDO'g2Sn_,iY*J ?(g0WyUkߑgכTb! 0=s˺}}jc_myjJEZexYg`.{yV5qבֿtS+tSy:Zq5.r߸8 k'9_򦃿D+eRv'l ^j}]B~Ti3 1[ A+j^=mrN)x7E#($:Q+Hzcey U4Fn(Ro~`Sn0Ad$q'-c)m̯:jpk퐣Ǵ *_l_oIh2/]ChW+Q9Z?Ѯ+UƆLX(iCFqg^\ٕ.lJ3}u A*W {ަQ޽˥q<ĭ<|`̿]3i̔aa BgVrsVٟ.> KUn[ sx_F7tQ *i?\ ,t߆ǵ8bCϫg]+R>pǼ5'SOȊ.G(+_Rп$X{ŮWT)oVEo8=7g:=]TFG"pwxۣyp:؀*?y*pº,LҲ8-cSž, .,LjrRug #;u}ɹkGne"B B>As WO$gSV|ϼDxҙd+/!2MCFWh@64>[[24 UD'!:|ՏQ#hRg$˞#R%?vj&@ApP4`d w5ަڙ'H86#;rsQ0Ig6m9%Z1_:dJƤ &SGIwpx47ZU|EfH)`;z~\+6zJ۽M'~W^U!9wZ/>NZFa3?E7SvqQ3k\i'޻ru|7OvO1򥅇U,;)9>{_ƀAls>TxޞS"\ aHaVx@72qRL*Y NSUYjP-U ff=SNrg;[Us^4aŴfw!Gz"4( e nbb$$CV6zz$ݙ3]<&O4G#M-&a#Nte˽#\;n?p|a< OuSʾmnjb@NB3:-+)p]FSk+w͔z2>ESGDJpz-7Ԛmb@:}prk+ժna%$%w>|]zWvGn}M{Tr I{ӻk C_b:Ӧqy~yM˶4O[қsgg.W4 t9{cs?3v_I(+dT.'!CNG09P#`kNH| Q|0|P{aÚx䕛 O%)/G^ O\ˡ3eM_YǗFIUA|31*I:JUe;fN6`F7DX*RxۖC|9M *p5.Crŭ^R!kV*,:\keL,ECFP@XT2:K_mÛ{;X'4#X;1EY/gB㱘.s 9#}D+"%QcIV!N_̈%5v8gO[C8˝>zA1HnFzЯi"%JۥQV AȄd <1NA뿄d9*2 0Irѧ(5?LQۍSj;^3Ԗv `6Q[cBW'!Q'Z 1z:>!혽7A-b$3qʈ+?4RUڨܰ.~ܧ2XK'躈՟LzT/w0E\oحm\E`Lf C]YCj0[qD/طԘw>KēpIk4\W>O6W/ Ua׹~S~:hmJ}º8"#}1W/qoR9p?j4vJYkf74+JfO+u7+S;PuwJ u EV v{Xk3T X "JjΆlR;.~9/g4G5(d=|߭=um;FJjjRlsXA["Q=6_|ޅ_U#8t"$2@x ,( :X'aC:$ٸ̻Ӈ̎EuCu͌UkHk-pa]ŵEȦ/:bm9ws2K`!]O/ۊotH|Q8W3LnCn%qA {yw 4["B&i!r\~y MHn"p TŬTLIh؃QQ r.$GTb,wm2h1 J?DPS4;[;jAn*[}_p:۰Ҩ=CPA_0;@,^熝Κ+|/>w{RNP1*PXjnqrNuΙ?˳\XO]܌UWq\kƮfF /_:&ɵ,g*去-z]iEBHQ dy8-A{)_X8"HNfqXFORʻ} #'7u%#%]atG؁X:إ7[cP-x`(9twdci맬k]BHC>1p6&;O6WOU]{6Y6=(9"+vo;]ux#`n7,Or2H)"s=>Zdy$zn$wΙKi.]릯xMֶ=[DTH@b" zs$$Y$ k!"FHehH.EXfɹA!ۯN' >Olט[! J=8r2U0 9nMDڌ@:>ͱ>&U!M-fY'2" ¶B;9 we+]\@-q"t<)WX%pdCkcC)%Ryo\|o^?oLވmwXforJq,Ӭ6ƈ\k! X҄]U#+:6Z\F-u:.#Jxgcw#Q48 DQYl0&ŠߏU7H7: 4faOJ%rSstͩ<9TXSArYŪӖoԊ=Qox}|@%켉LwaJ<<ys{[k\S fEa:hn6j<$ 1KC ;nQsF,I]uO$v\ QxRT"*ec2xv%a `% >#kGcVT}O!bc gg,0"trQId 򳣋fg,;J6VATah1 . oAC"Uefk[g)[jQ"'Th!CO y;~򻽇GRRO,!Nb"28!|TY#(va{|Y`ҹ!!:lD-Prōswۄi,,<1 "h6Ũ_uz69[E˴OаI AtKw,T;/S`;εجX@Q(.xIEJ{d-#ƑY(ǗfZ?aHR.֛.ȡl LYN߯$O-W2Į֯UÙky2¬?u,o<Ϻ[Wv\SbG:'Y >XV ԂJ|+By ./hq+1b^Ei:DEBz|9}p t:exew^>>Z i)t y_8,$K9+s! GZ rdtlR i1JH SE{*|yv;sg.,C|솩=/ :tFy5+;? ppw}೅Q`v?'ZpE SYCc!^wE7D67'|ꖌ?"vlWN_My᝵R^0_lvבdϚH@dzQVҗljO˩g&[D%dA#=$r~Q.\m# ڹ6)*z {O9qYw>#S5|t['q}/׽@X,ēD[vjsJ"Kf6'^CCg4s8r@?7 br)m.H r[ zEb7biDY^j0[FUP'ku rӵ2"Ȥvg6[mO^U[b"ZW${s HA ;ɧ_z~|"qŤVе+MF/w%q#PJP<VoJaai %(`::gF}B$9'iֻ܂=XAja=@1bMPBA =ia9Ɉ$z$y uEzG \X@φ9팝qp{ jjfX)I  *@Zf1l"haBt:} ʈzJ.  (a ><b514`` $5pk-@pKX#h18-]>ɮ8g%3oD9GCA ʕy:k ft'NŷLQJ| 8Na ,uH}͆.rm8YX$rRXU`d0׵0/; ZH^L 'BLx#ҡZHΆ؊;8;[މ N`$&buLf[R9!Hq!e6 W+:8# fph(O8b'7 +Zw4+C4ʛ&(KmY$iK x%_e[d#˶My UMA"<V7>J( EƈD'B)¯eHɦd7~YQ!@b6f["ۋÛ&h|ּ&:ݞYz}OߖWk`$!N#8hZe&Ż9ұ׫ _B!(0`20Uֻc}+kg[=raw ( 6S%0EJJκȈFkтaρE!* kp#`#Fq,AhtE@Mq *:3iUx1ˬh\q#/6cS%3uJpӚg69*njYEO)Ui\R Ow@6s=XTՋ2h+N Q 85>?kqomu"|~7X$\֧}U|.L^w`v^WˎW΂Nd\gNmZa_%܄ouX{Vu-eH`~VƉ"IrɉJV%<~ɼX0 YOy'-( P+)~`߻ػ˥zͩG*[Rp\þ*plhj8pP[|SI=8)?;g{S p(\ 7L(x sϗ?qXH6'{kY)0RJ9!!rGW<˧ƦN ĂtW>ax] p1jl^ ۘQ0w Y\LȬt,6?ت]F5%#}:>GÉo ]v?,-u{?wՏ<FRh WC9[Ӻ-Ζ4FqȍC Xu/TD5,.5٨|.o6o)[kд+=#vȚ9*%Z[Q M!ciN['"cMIuꊿM,\Uo;)O.O2Nd:KIirj-K+FB%o=7BnaC+{e9٘t-։Z-MYgE<>M҄cPL5WGx 䰤DC ଒HȋYY[n_dv,ߍÕK+ yL"(iCmʨr-ώVP(WpE$/S{D xXXxn@'PGU,@Gz&kj- )y$2 @P(R b B `B(J V5  d{sR|5-w֗Tr 'Ы̢٫>4 ^$Q_-$`(:8'[>E9[?fFup>qVD W MbܩFKF,] jHS XJBD41%!3D $cUѤ-B&&($@.K s*2OUޭ5ܯB/ EBp, B@FGU_3w"2@x=#$LQ)ɧ g8P% AP-`@쬿IHZQުEP 4,]-6>Й׻bV&_,p9"xܭ* x1**u!ZjVlZ&1o1,u8”߃Qa`JhTYsmZ/J5peaɅ<qʔA` LDPҿڹ3EoR͹ir&ƐD;dX$18 ? ` " @@7|S脀wA@= k>}?Rs]<=2!|s »<'p|>l$Ȓ2J{ hZ(T0uS B Q1/ua#(ʣb?|zIKPC;rƛ*K#O}s݋rg^(hFCy 2L$F]%!(8d 2,!'%{ᯚ`&1Z^vofpiG,@@H )űw;\}*{1Xˍm.uWY{/3$/cjK.#A(|;)-n/+ 7_q O9#5C6 u歊{ Xu "BZ{bBQo-&[ J4Uvmz@C*UصFa@U0_;LX;( 6:zR /uwVui!gKމݓP1'2nBx9c*-~ $6melWKsLw%Jk}Y$fTrX2Wv2? H鸷cxFT#f9W\ u ›Aθ)̜mݢ( -Ly>KL B@LLSL uOU؉Fσ cC2¢Qd, &ENBG7MnF|WfLDl  Eb92 o AFT4  {}>:Ifɗ<$WRђ:Ro}< odPoBhZ+C `Nnc04L#0A1'x h$.Uث[tu2>͉c$[cXIx23k5|_5dv>vuAo,󩰁%BDLoj0.uPČ[[גxQHz7 2A>ob5&:bn7@ $' .5š@iIKmEQX9KzM@gRj~RAQ(YpA6ү1AOFd0р)9E[;˶py  `D!g'=}٭v:( Be`DȂZ3D62RPlժ¡7#n}J*/-,gB>Y2.pz-&M⭾mSذ/*63]ި=Kw[^J4Bf_IxpXb,Q 2Z<}3;GhϽTYߤ+ȩ0D0*©ryrO l?,oչ MQ_`4_J!E~m-qּ e.B(Dޝjmxx"@ǩ1xD%h'eC>npC34=ƫ- Ť4ORU,ʗ%0L:Ft>q3؜OU5USw!tNo:R@WY0-YHvd d(SUe>ƒ1e)HdnāB<~7AɫHUR42M xP4ޖ/0%>ˌ"Xz^|=8uL&uF4CKIs6ft=-:wx`Ǣ3^T ur~OJ2ll5L]gSF< +vzkC6v ~7HHH/kS],պaՂY2>  P;ppљ{ "*kuWN]uf)QѿŌ:ޟD62<VU]A7aeQ6D)j5Ͼ XP^ąIzXC$앢X3ri>3h)Y.:ͬ ER(ȷSLB&E!xb"[Q vH"& @ځ0 :-M#5r\TkẾ H%par+fuK$pu QP MDT7h%DP H8+FV,ZNIigRelC(A RP ɐMP[`j4D3qX5S#bcP8чA!\}\c։z 'nܛ)$5g.-z|𷛴 !N\kr{] IvS!4Mۢ4BCkHmlw}kII ,M,C喑U^86)8-/H0-:O+i]1*3IHZ6xDgeK(C1oF@ "=")vouvaхmhzO g^t/IKƛ=vw8?gōi=Y]k+q4722 W3RZY1.2syovOG_٣M?/o *f]pή-:PK!P#!я4-Q* JΩ_~O'N*ZT3$rD&%oZJKy6T+mβY4$XIMAZJ6'[Vko1ĥ\YDM,d>,כcub U15 6Bִcڳ] kh2u@9h1Y}&5c[Č>U$o\W,kWUFutMI,jX-E@x]%y2bHїeẚ'-#+O$zfWn%"`@8_ParY'JHHe eސ_Үq<@V#~b@Nbm."ш4!b洶U!EIbJB c5;ՃX8J<~L핑fxJ Ԙ!L2ńi˖Z:M#jajM)E;:`OZ0|2X rf`e +tSvrfAB V+Dg`qAg!`G@\01߾X. it[cUSbJgOvm\UZQ_y6CJѡD \;2s|fWy@|=fkAU7}| ~mIz}т~ުlVL;:pil}zS2w y6%ęX{kD?IRBz5#z{wWz-,咄b)y[NGOM}mIX>Oy7V%yF}=1͞nAͺ0\͗(08Sb0g6%;y}}G1byIhoOI$ 3_]#4g=*C `T4[YpOw3y}nD g4sw .~jG`ndtfW5t봋}oYgf\1U| ~myAgszzۚ(b3hzuGgKX)j'!0~k.!l+Zlk?"m^nʬ,XScǠ LBe{-K賵ehYAS]ȄͰzT\juK2u"dXh¡^jW%Gm) ՗z lu- r%!r VJnu  LWr})hӬ :BNN{{$|3%f[(~=4dxl+di{9A2l-/8"(zè70ab*8:8n< B?ý';7jLOF [IbW uKp qŝV%:mQ^8Ej v F,}:rZ ^0|+`4RnxZ|7%;X7ׂqPd5i[8܇/sXuAP!.1gUj !>mE% 2ǁ!~!@σ头=X@Ě$d7Uto UY?51r<5 e# ~Kű^7XcLgcsunl)8t'Z) m~RHzgrdƬ7 bʖ&h ҩc>Aِŷѹ-.)EOd,Ƀz%ʔ'=I>1,L ҧ>j|BXɨ?y-'pcnw:˼SZIv0S|.+&p 0/"w3],0Ǵ=ǁ 03$ .zzdjI>pOYDN@XAYϛv_6@eWQ `pX2C]A V"E&4:_irB6[{NGGx>epp<Fot>A?'ʔ[V"1$/VR`Q3AgΦR9B8}w&}S;ۊ Wű: ) Pdv|R-n TWA MՑ&wiŴ#d_# } `3 (+]N fS/q4&H&#n$3 3I2G 453scww:cd⌡qо3Ρk3^/<<ٓ3{X/nU1XpּW?nsWa]P]=$7"ᯊQST}uiWIƲ]_ nU2ۖcaVҞѶ\@wn(7mVyV+pAb(Y&< CX=ʮtHD< ]^;Bfwp߉_et8ȓ}`?kg~"T?jNVG$4I)ɼr !Mw82*\ sB^䲈_K :<颬Uw yYc(a*FMoTQ*'_jBqQ(-1B:Hw~H9 ,_)q )v硲AVh3N`PRiV EH#bms6.4/>t􅻟>S>th^xH,DI5 0NX$od0G[X|ջm 2Ӟ$_ N^B8$ڇCz箼`!WZx,UZšCXx}Ef< qK& W'uVp%_gڟqCƢKܾ|m mXSBOqS^|_n8@2lYfw @y"DE|^tv%_Ev/Pnpgܣ<ݏw4?m;t 8SQ0%9#21<g0$ZlN>딟4^`|̑+h14"\}l~갤O:(l~l;u}TÙ,TK~ 2g/]@.sHүf {ib,m㹡A[]A%c-8 g٫@FZfJXn #Xp-|—:t}+Ƅo|+ڬYOEzDzW=≟TdpYBB7C·WDz[HA!ϟ^cR.$@gY>"Js^s[\~/o׋j\d2Ɣd%e kCj0pY˨se8 _)nAł}vS6?nfn4"\֨K:Ξf,{WJnMLz}w#xTZɘ]>1o Hb"\+=\*wdJ33lb`9 ~{G%N!2.QTXf$=v;vt(0 I$#w!]TbIp3ju%`e:+O\kap *lZ˫zo#HQ' !䍻UXLE1ױdOX.Xկo= /f=ҔO& OmwȨ㪘'^;e+實qS\s305JNkN8pjGG3VBJ}ybrfLKgV/M7nKVڍ䒔Wn$l{&B *?a%!(rhT]G"," 0iﯓ{`kDzy2\3Zƨ\$h>\4_)}tb_}ʢGi}s:]tI.D7+)v8?ri=MMn) ~ɥ1{6׾h"w@Ř\b CW;Y"Q[0QO-%9 ';m)@!~z\> 6]HA _i 2 FArHHPVn1&5}QޝVԥ CPs7m2eP/=[#=8{߀=N)V`uw=^z4|nv= HnW 6l=.߱L/[lX"vJP`  Z{? d=q9}k2/}f%Ahm)5*S zӓ{vlNwέ#Mˬh2|cLFeSґٱVWQT(35Z9>basou}í$4!*k[ݾu@d+88w ,'2Ofj{ 1*) }65$c%9}O*?MͅU䎑HmZ>l 7TvsEg&/rjv_)~*yR'9g߮j#h{θ'F^NkiN/nS$U<d.ΛEѽrz ReaػO>>{)TJ;p sL$RF &i7z; ٪td #y/G^v}}yGRp~m [ͫ4΁,vηUcEF^EH/9jA81RcDpWmApcsD` 6r<c҄Lpd^W< 9baJTdID**UV}n9nd{އ[j5٦/ܝOz^ _|%kaS: H`RE " Fߕl]qJ",@+~eGb 8LRRn4 7ZCiTnFv@و*KTpU_y)"}3S( rƩu;<_j:ɧ!y[ vAaPγc?Jw쨎f!6|;hhGIt2ZӸos/tN\5{lwM)ż%+X .|9ݎ¸ -GhRy0{`%doL4[OwCJ~6'pMI5n!:6/\eV\c83Wݡʭ@zO#GV*-|uX_?I0?Jf=$ D f+.jUِ NPF{`+^ކIXyc{ faT &6>ıx| X S/b:šfÀ:vء(Pu`'[l`x|6grOlҮxV '<>[#)?weaU &[q0?N h+e5>%F2I2t`T9fScr}+} x:@2/<ڋ}.u8+bŧJd"oTE%D #+ήMyv23(qljk5֞A}_G]<?53Aa_63 Y7SO%~wp'uݪӄG0C|KNP#L" *ɇ)y-U3jSj^cBOZ'9Wxq6wcIҵ:JTuKw'(SRb/1Y! ez $EN Q M {u7Avth$s.␗CR0Mk/O26A7)Ui)l[Ky/?Q8Gnय-='͇a.+LB$b0>c=L2EéfY4:D}Jx'y Za#bCSy&Fj?\9fxuo G`6C_f"?,JtI*i)+G:᫷47 T~Lg?>{-UGc`&né>RQ(ӨԙAe)xߔKXb&FU}"Yǀ#ј>7}[?;ﯫ,,ikw Ө֏q-E)n1}9^`ݷ?rT7&Uթoo zs>5ȴ@'_3I`[4BOː]6u:gz=@aR5 TZeV=aCFXsni{n~3C@oxE}0hlXth_\twk?/ hX֑Q@,6Z|?ùs'=giڜ/wKo{UAZKў3yq^k/+_<W-BR:**ߑ6 s, AFG?' ҴGO섷MNvBϛZ4TN~m 㟓 =2?94-f s*x)3tѢX$Θ¿ds"bW:.D@;J^fÁb1j+s:݇s.ō=7++[Q1CiO;1pfr?W=PB-|TWʃE'O:NYE+ֽjw{*umDZ`/W^"T~e{1/gy".rA"%ڙo.g7!U k!<{K[WuxW_l/]ѷlz_%wߞi[S;qbSb'x(_t(.>äw]~\yWI[ \~kˁ) Hݡup[O}/8z%ㇽnu<7NSAS7oޛثߛ2sQ\(z[*rjTbG2|I$!\>~vSƑ<ʩ;Ɣf)5qɸ"f@Rh~6wcU=hxUa3G+9@]0rj7?_^Zԥ%7A npb AC6W䓏(7I8:ً,Xnm EY3vU,oY4I^8d#VInD)ggWb+d7Z෗_^ڿ_p%չ~d؊%vD&ʜ[K>MIO -yt,͔7[ng[-!cH?NɟYK9_#oc3IF?s1Yp'_@Kv)aL=g 3ofEIz683jIq_py5+YN@[GENJ=PcC?k$xzaXuXyvU3F'?Gl̒@lS!guYX $J& ggfw)?+ƐQUsf~pKխa(ʳ, tiڻ˰{xuOG-;ߍQd$6FanudV߅uِH JB]κ.Y7—5JC c ! LXj(>2\iXsIR ɠHHZ{s$z+-':6,M=vgkxZ]ӹFU 3N\G%_z#(N' 7/bMefr ǡ c݇;By!>t~86׌8B]HšP|)X[?W]?B,ߞwG LJ⟞?w\S%sHMAR3sE(VRtQ*yvu/_y_-z1U)æ~gi4Eliqׅͨ`P#BcEfΒ^lE9C9C6 UB,& gZ7Bݶn{iK,UŰkB]uQ?[&=u÷>R]S%^u Z&:o4 pۚYѯ{JhVUE,K&&ج!Kncno-=Uvh,r@$ƒ`SB*ل`k nK 3EȘj$~‹K9ͣS[z%vY׭d,Iݽj{x)7wmO}W7_!DCvNqqlgR -t3{պ@VɁNA?tSjʙ.U . %QS̠WQn-J q8oIgfOaU\euN =r"s?r&Gf3t4i9[Ŭ%[r\tX[a Vkhdr|u1^U$d*rrl+D6,*XN*g&J>k[a Bi],*Ή\oYY $הlF D1 2:NX^Q~ݳ ;OiH\3E ]|VbABf$(zOTf}_3sAwPZ+z?yFrg镏-9:ۘɂ]?zJϹ- 9 aJ__YΝWtXϑq gC1-Lj,oL*}(>vN6hMb aYX$lx_,4,}=羚>uo/RVt;Vw^ߊ +FZb=cHIc70kksnBZ!_7[ӸGe?73^b!YɻA}v0۪r١diZZ[:9|u]G7^A=% yFF4TwA3Os'L"y_5eZ֟GԕA\.fW g5ӢZrޞ]0YYȵe~W<-) r&w){{kbt4IG[l {x6[VdS(쯹}8A@*WKOo>T|m r^!6˂r=&/\}R;pVZЩN]ޞpTϭ2y7iwg;7n)m&WU`#AkSSz꜔N̛ FX٣_ao,F= ]߮pa6P(SXi[u {ɱob'&„C^E>ύvFIk訧"/82rjU%`]hJv\7nId 9dE>(]r|:/k+Owg5*vQ_kㄧCOW5^H-u%/fZê/x(Tb TTjk@X(_ e][ƒ8 g]7Qy!X((P [M)|}Gtx͗Դ(zGR=":^ќ aOK36k榨 rt$`@mvSx40ڎő<8?LVX4ч|.Øxr:1z#n::Z궭y/^ÅY(vuAEu&gfװU;>ۜ3srO{3?Q$ GKA`L(&dci-$ַ_`O+Vf(F @ -y-p4 &1{jC~C@u4Jы)#dk? (Q@*uE qL:Q<F>g@@LRs DëG`G'j,؞ cF= DYJīe{d97ck\oOqWc--p3w4+֏4o>p5>+3k#,B:A}yB Ī]/k/FZuIk`gf~VMrY_ټ.FJ&nQ}]=/`LgK~Rq/?. G-6&QMz{39 xD1'5id :/ } C)&xS$VR[ƪPjf C/o$__=O piL QAF&Rz{jL !6 霑)00 b3m(։^\^CunBV`~(]'PxU5|qv^| C SohF\-~XanX~3PߍW2"hU\:9ŻG [YE9U5 ƈ25ԦcXbٝp{;7# 8!C`X"y F@```RϪ#v[2cd  uhjHpA9rHNm_nz(d:.zc<,i Wu 7oUk{fi[\Vtcd_>IL9OKw\I@ z;O[-FPRP\(p=Y\=r b^Q|J%_|kenۺ4ɳ+*T@&GL_)7xD(n3g܏,.ww7 ˮ~-o[9;:K4%K^Sx MmUcv/X*iT=Gk=ƸC:+F#-xaPv3GeP2f Mu,{ĕ_^45N$)oĥJ+⊞@-Ct!I=6n1@{{;{VWR*oE_r7=}yϮR FBgII!}l"uw%eg7|Q{,foKlf~2R+Z\m8|Kzp%JZ(@X~rꨙN+ ؑ:o֞8ѕ?ǎ5U:u8ȄT blcyݱN'5J͍$fҁD N5SCF AƐ S GGƏ?A Ǘ /fFNkQx"&Tll1@s"3ߌ|R?/usx 6ou1Ǒg\>B?-xOq=J]V!Kj„_u[ꉛor) Л0(zK- Q#ю 1S\"&QNΆ[ΞFl ,2$wѵWo{B&s7nbn} )iDS;QX>*C9s93Lmv 27\LO#ϭN# }XEH(@UE&mU͏deF?/_Z*MPaElrY|S$bgM e6OR(dM0+1S,0%d'G#>r>m .n4$6F4>\SPa񑵳L_-Y\iȉm\tRkx\(hا>vvx9UT^T0&KǴzY'%Afo?' ؤYt Gi b1ћzE&GnXBےv0d 8GSOpM i3 `XbͨX)Nx(o<VmޣQj0`ni=g"iգsu{h:d};Yqk2E}RA`%+o͖=ֲb0̤=8`6冭oceG/Oʑq+Fۣ͋=By'JlbF8G~]߿M&_[7/|"7c  h0j!R$I&ތJ+[`i񗨓0Mu凰 .UDJn~JO{DSAЈ1-6#llCCipʎkFཧYC]ʼna:y膜#%kHj:p \#B nsM94 BY<_VTI-$Xp E$d1NvB\!kp,@>(e8+;39\v,C[1yk GTv!2%JUq1:( DD2ak1AV͗Z 7cZst/%zxXU0*tTv_qΡ9C_h`[NU2,9pTp+Fɨ om 6ۉGB !phҚC mbƱ{l!J-Ԛ=뗾t"ZaV;eWb\V#"A 2;²nQe*V0 $%vlȰ $ybmsoYN2*3yՓ/4g]XԽ$vf-OceDW{O73[CZyv !Tflk{^XPغv#=Ku!\&>3MM(dYRYiVd3rqfKk}so_7ez;ݎxa~n0Rzf!WPBq~!$ƤI!!&RZ*@DO-cN]ur!&Rav^ |]Վz"'z=kĐW_TK#q_3)xȂ!T<ҳW~إ#;5g<"3CM#y1UG xΆ0~V < $ 1̡ ƍ/}E=j􎝧]>˨$(r$20ƙ#'\5}ӆ2**LjF"ݒ[VF<;fHﰕ#hadV3 3nفc zԻybs} )k?wgsH 0&CJkSK$=zRǴؖJ?dt(6͛z[[[t |1_1K&͗Z}X;vkn3-WDLpd8cz{]`b9Pw_bg~eu%V;<#5'L?";F9yZp&˾4s\rBds"P͔UKn&op@nEgiG/Ȓ 9!?HB 1vG!џ=+z[BmyTs ly#lFf ;e&= |]iV% Z[ΠXx,LŬʳ; {w_?6,>H2=C.S(&,wLGRh+L|! g(u-Ě*ꁳy3=MٵYF&ޟ׏*Ce/J]e5}/0W oڄgɚ/j8,I5r^Ur-O<>*exX GcU>ʽ 0G×ny$)rv%>W=_RQuzg1皙b9!9:"ۧBu/g:k) \EyQ]W։(IgD[D˜Okz%b#-S M-]׆4$&/ +ZJ~9R,dȰŚl1vm0p> (ޱs@,0@N@`ހU0:k Hpb(Vc&Daު#HD$]5uoAŠ 2t D,"HL&z.=j.6}/uD C "NaśRtɖRоȃA%,լu2Ɛ^D;eM+/7А9-FfQ) CJFfOP_EU ;k=$A87@0ȰeU7B+~`?L^ۮH,n A> жLF.g{o0g`t6U=Lw^5B'ÄKTZo*#pkOWuOkD8oXܬr)cF̈ g0_XQ%kp~˛}uSq@ڇbN΀1sL߮6( ;$/@*0 K@?b>i*W$b>P)-U=hNB^w%TNŸnI+V';ÛήZd";0Ob8Д[jy*ָEYegp$4xCqM{ˢ pۂ]%+X "pLHH_-8@f!uȅ cݗn^XuՏBeXֲ|kHXP`Agd~P{GuǞ;C<6vd'Ut+u]E#)WP{ޜe=`JLs7{'/#G2W[pkC! [/ejPCF}U1 ֒%Aڊd3+x_8Y3{Őt|@hUW%" CJ.Aup;t;W %wv-WgαJ/|* t22IHAƬa\@h_PFB u{ $SdRQDb#x|7TDe+Xk/<(6Ojq?NG,L_-dXpIU2!l( VUTj+73vIZ @u`)X9Jj+L|MDj2BX=%;rN@)! Z bi&P0|hC"9ٲRE7‹K`0("I9wӔCi5V3Ĥh(ê@ H0dX(kڣ$c5X(LP"}}t2 WS*S,Rxq^qňbRhUox1ץ9Wj,dFE4X׸&T_tqD΍g4ǚyB Xi !W)aci,JYDL aɊcUX4ț$;{ekRH E' X!$ed I=hj7Wʦ[d<ʨdهB5̣^Nb쪧I@)5UU!3rn2'F ]B&unCSV2|<j* g%TN qj!uU)d&(e;R9@ rE&/p(2#n,![R|{O^>ྙ-^W`,G-Qd C`B!ӋDhj?ظ4(lvʜ.v9" 8!.&uM)|ne']IQٍ2W}N! ܉a-080.߷8:dF@.ZxvYGY1'DPAv8Ke%YCg˔! ʺ$#e5 +dFlT-jq2g#ԙL IBmM0\+7uRHTfmN^Enƫ|V\%6X,$q]BBTď=**qFEt&! NtNtnbu4rT&9vCX-/V<]_9cba%i9YGGTڗ(3偃ܜӁ6`+qfC4T0N3OcbbDba4DK" q 3@2mz*`XABV bNg5 BÀ5%81ҡ.E& K/Œ ߋU W R !2CNd݌ē6&j!<(prK}~Z~=ո N_Ы!T(V TQry[SDÕ1qe$lsKbytxy&huTT& Xqd 8f2SF`y$S各&2B/CuT"0s*gR:{5ń/ P.b Ͽs;0~4.2?q=/lճU4ө=Ul lN A> C P`Suʦ3xmҭ7fpu"oi؄ L!D,CZ͑l st.+Dwnad p2|-#cDtmŷ4ݎ&*VNqto$spHl fw[fkID%YY6Dn&a.]cw y W^|r>UZq1/(4)dCz%B₾뻯n\Ks$s%S aKqw_dV"jGWWO'RB>DL0,!Hz/{D7L7^ͪR)g!1y=lRr[P^ڨROX o-o"(2gf.e2T'-]6P [:+vmM/pݟ>9֦@~lznm٥kk!@ hg "eA`Z gzS(ڎy2_).%¸'&ov:mt+5eu}S!@82<g_y5s:Dol㧃;?" H 1d!) iu1*ISS|}v:qv~M-^s٥x;/ 7醅:SvE+V<FQvu:Ǚ_n4_Y ksVDA T˩V3 =bIYB/`;6'/}ǽa7T̷GbEo|{.\jz0$fW..wPv^[݄μY)rU=52D>e 厼M'BcK`]ba*fT!d@lTNb,aab'u1oz2JÇ90 4?7 I)?|II.no.9 d\*fW#mbw=D%FO-Hn2+Ou5MbE);%1hac-qeu*tKg#yFAk)c` VϘX :.>? yx 4$攳.=^h#Ea͓8,B{?%6L !I@V69N"ـ   Fĩ kBA:%&+y_ 80~͎ yDzU DXU:)p(%#.3γǰH:ȀDu~uo|PQ[0@Dٍ_Ϻ~SqȈ=tA<#J *sc`QB =9jWUڟ{?EpVhW~ #;˲#C +*8aHFeý.mҶf/Zle | dbs3u-K0&1tWeZX2漼k`R4nCl>DCP DBvg>PDn9pé0!]bc@ A  50^u~w/[] ZmoRm:|g[V2"ULs[o@.:ܾĶ9eHXz( Í:bu9;oܔ5&$VۆN| 2+ǷiO"x@x`?z̓v87z0np+KfJTIL܍ᱜ., 1k 1g^$ft{ws`qHc |)sꝠ4xbe+͟&fݘ)*Ͻ_`D *8w}Cy݁gzBf K2 $q+*;SɳuQ_c{+Pw!fȠ+@ C.O4\uӗ)^` D,,@9S2fɈW&S3xG,wP=,e-MgZZ9ȥ%8$t5T ވVhJW1@Hh?(=kDK‚aci6ԻY,Q;;93*{~o%粏VuXi_ltϖq]½⥧Rol¦s}*o(O^!c b *O_NNƓcxb5uVB8&?3VJVrEmjsvq4@B@+TxvRyӟh/D.JppVoWk 8;Gǿ{H K{ "xʯ>si(Q:m1zB+G%s|\z(?H]ѻڦkͩ']qx=ƪY= ΋!SԔbJꕆ ulG\& 8AE uT1שo.Rytoi9R CDKdX}JjF{`^r9$mB0/B( 4dK'eo3o }GQ}EQYQ8l=g u!'Mk$VӜ:yɗS;ǕDB'Qx#H$Ujk $e¼2gk$Dy#YؽTW[>B"0+ډjqd5UVe RE`2U$8QyYU㰒| qy54RWRVJ @ɺ{׹CQrY "So(mA6+]n71Ō&^V^i Adp>s)ou&ƄSJFs̊${X2zcIL{YQHiEB9Ԑp Xi ;]/]lsݢU7S!i< (`mhs&piȓeхs÷}ej&\zU D&FVb<ȷGcW}_iD20 -W7qFL#_:ʐV} 3/Мl3&<-~nTśM5`ǞO»r0%$J>DhQKs*p 2e !WeFdS@``WH|FdBմ+V~=0p;M']3iL& H{/[%! r=sBnO{ݞ<8{WؖR,ɖ]0#tIaѴxT>}~?5@=O32W9tx ,0Q%Ieۑ[=E7=Z;=J`.bh$',LV=o>]z!|{3?H ,ttԲ[ <{:xz3w-n6jr$|Y 2 Kг=юc~B'"†  0  [ul(u#dTQO}ޚvkԿ{ JLh%cO> W q!#ި uJ:QHD#0<MK/[\aqҔ_uu>370A!+skRZw\Q_7~#!Y6 <=cox^S/n{v}D oZsmʅׯ'C:3HˇBqZڜk|^Cm5k߯3WZnA>d"<ꖈ$Ʊ+nW,>[G=0P+8bywng(F1%)7LX U(KJl<<nn~zohI@jݶ"dTf 7|p>4T^n=vqoΝxrd# 2n^Ȇ4k9[Jc !62,70TR.ɟHڭmo.F]avRĽkQB~644D"yѶɗ̱G鶆 R\%J ~ZH'I,%8J_4hN\Ceũ+NgyDc%a)^*݂dI,y; RdFu+˄aWGL&M!@dSYu0EiFڲ1cWZcxLoRȤؔ7OV?\$Ñ85*M Λ+)<DE^̠,q# W. ,a<#D\MXA@(ɴ_xY+l.y3_^ѡ$e*o kfEq?bX!Gs~k#UϘo^_#_`!4$8"$OC D`P}E{\^ ҂Ne8\@e #EL4mˈ䴴ʂKܼr]f;=i 1@V g4ML :}Ӭh?̮K & $~0օaᤳb7Za'Ϳf?xքm+o^ E?%:LHWiZYqI5\A0F 8s8NQH0#i6@@8w}DX$e  q 9ț??1: @ ;sw=65enN~>v>ЫUD ONG2DžH:WR"uE48rđ6{^)Q+gcߛQ75[koOG4J{z/pHh׺$[:{i {{g(β|I੢2o)mY}yo D&|}zpKeK mK;>yzf}g?q3sML/:D[A؛>2wQY˾#;oEw+qWx'hy +qq6}j==yl0.Jg,/!ne4tfde7t@k$Ab`nFbҠM.댢)bSAf# f=Lv ,S+ I败Z+ֳ%N ,\QU,E o^ ұxOkޣg~$5>/SoOm$ԭ " 5 H|/;CYP@+>R; O}qI2-DVG<33foT9{Uلq T''(D [񨾫ɻ)-Z_7tB=OGwk?ipvPX(^o@o,tqbL Zn¿hڏ>TcDEp+D%DDE g 2yNK衳kމ*gb9# IBdI v[ӛ 8|)}g/_YEbGV>*PӁ¬>}NnvOBϙ~D5=8a2b1(Ox .Lh=`'P) ,qp~ %AZk[H5igo>&۝A<.-K4"+U΋+ODI"V'/xWEU?UEna@ºҔwr!eeNn«kiGPd%cHdpPӎNC?ZLE8Q12:T\(kOϨ/e-g:0RO(.cTR]ԥ|1#'P>C0]Of%N2K3U[KV9?`LUD) 9eѮ8sX*/w^_f{X<ϐj3%,tIy*D* a9S\h4S+mKק(Lmb-WĒhP C'hY'!2-2Ciz%h/{-oazϚjٮAP`F/kNFC63$+0"dT@4h.3mI,UFN>9I(&)Ndɷ, J*X & g}b >D/x c p[$n\Pn)$`8I9,5uJ}7kӼc~Q|?MYkպY~p|Fa0Y&,Y0VAuxǞxLTB\neK"bmvxKO_y&~8񒎛nbo?>i.RmSd$Nt fh=˾kީ^-S/OU" 7. ;9eBHY%8hC8Q{ŦiA =fߏ<WxYzIJz7Oa0N?z yQ$hV[YU[=ϚzSSUqGgrssry5'~y|׀pPX=̾L羮Y-a_TkYLA{J@lDL)Gy/ $o/ @^jXy`|sit0,#H^C b~:‘n"La0Zf;LIn'S [k~CV2,%j")oϫ,o5;H$ki2LW7۸: ԧ^I8!C,۪Deٽ_p<\`agE(Fc:W'ȑV| ,>a+kG8#I_"Fkƶ)D^^CvG "|`-*],,͒0>Czk)^],׋ܿ뫽]!qF}<`uia꫊ylNhVZy0Di+MDtN\8\~hyBFH6 7W`XH0o]GokW#8A$yEaX6sn˷x'~mzmǽ/ׯEj7cCu3c/`jDl OvYTK,u=?ߵPlnyu0Ǣq:P $$ qX,8w4\rWJ QY-,{Q^.^|["L>|xpzfOW]B7z}T%ґpENxuxw=wlҫ&|k>Vf JG'KCIJ:jꪛv𸻾aFyeM |FP3:H"7 to'> MKU'1I&g VOj3ýBe!/TT2)H('B!)#|J}ʝ/-/ʲ|]*ʊQ #mȶ3*p"`…ȫU3ɨ HVCءV K ׭.جvo YMBUq dSgjnq>y\_ !(Nt6Q)șf1>0_4׬}&N<'kY6X׸P4& =1|%ۦyS$&u֑[DW S>qM 9ҽfZs̚dDBV؏)JDٞt5Yбwz{ܩMVJ_@y?9&fԮ>YR|a{|o4CZWggTI\vZ"wx1dlK0m&!N+>cE֍+?[r eKH)mZcG! M7/pSeWgUR !^w9}\Cq,=)Up3 !SM# `b3 B(bk0nu%(w"dJuDž h3 &;v]@W'8U8b- Oq{@Mk ;p'59N0UaH[N+-ѭciiJ2]Y L%0쫡kĈ!-ߏR hdc<D !W]Z{=a5/SV='47`=AznHP ` SfYI7E^E믵Al@\ق A`Xtn ¸F)K ؀)bQhMoK^8pie|/+8q9[Gˬ8eJДh29BgG^n77S/]Pn@@i}{z6[|Io<r%iwԚI/|}+JѦ5bo+dc?w[d *qu6[eDH9ª_45)_Po6Cu,rM O$_F}P}˘e&,dip{+yka1e(h Js;&|]'>ɇ9W'RȚ"vjJ^XϘ0m^?{,.TIDRѕ!4l`loYo)| ̑)Ѷ0?r'V (HuݑxC\_ lioєxi EfzVYo؎ μ*~< T)&)%XKwP$7-7fuΟ8Ol̋JI!{8n͖t([XP֣gn>ԛJJDCћcpJ: @ FgW>s2@{kZRÉrf)bJ|!efU7&Ya  l9CnbXF LL& # G$E8V)yy6zpB%-`>+f{fgkMTD"қU0J(1?s&RwW{EZMd$o<nt_f!,=3R~I&YeTu}5`/X 3gn|2왽 %)s'N > (ϴ3&ɬл>z/G%@ ,x+gDwtSTzsC޳+nW,nCFC EHxVgk`oMosW\#nv]TE%気>Lu4/*EK3_X$a\V||pAte8Ù$O;'wah+_& d듵&" E`7W}eжOsuzs1PoƶSq ,$BK Kׄ_~%41$'Z 58ڪU<_;Mo(}z:ʮF@e;'u.FOEҋ秘iS49 8\,ڮd  'uas5]VвGC SBwP UR(+TZHXbweSn;-]>k8򿶫DS>%'aϖ-pa3 -s?I9G$\{6_oA ! UXx "@,DEeF陝>ة DRD" H N1b#L{Y<=V:ӆm<7h7cğ׭{HupyN6ľXҘ,suWDnZá{8Ŀ87}Z0J;Pw;ڭyQJYaSjsy=bd)`敵((ds}s)'5ThKoˆ:^қ~])֒(TTY6=;"V+$ӛz Myu ݃cg~[6J G1kz`pKdjg鮛/w#L_e$GS|=2 <'2ND+fi{> vjQžG|@?Chj)\@wO>}zNv"pɻ@uՕ謹n&U%MB#dM})n)XkX:**iEw&ülw_Iե=Zv\Q#5pgHn£~K}>:Ep[J^y,:uKDceK37L n ث^8#YrUž{ńuAҍvu3 ~I7_CX,l_UUӁÄgؙ"Lߪ/kC_UeT!MJ |3'ŮI-pjאPaPp(+nҷ^˝)SpA.]=lbՒ|Ǐu y~;赻vv͜䙯7^u}; ֠#H+W MHQ¾cN7oN-7`BkWz\׵3o ^l:e7x!ܟf4H_jVʾgtJRr`^2las)VeڢBXdH"zꟷ~qFOYC$~^Wmd\KEb"OG9XiVS\;*ރ']+ɦYq!6?2.N0fc;tM#-?>MpU;3bE58x)ʏfDtf+v\:QӆVnnCW5N}?A,'V1"B`2 OE\(Wm%0,m/b$%%$t]K~k/P(/z-ՌWkVvۜbzͯږF76@pzDXh#E|by^x <}*<9g<+P>WpR h1 B ;N ʝ[ Q}_yp+CHO~wA`ȫ>\{m/f+@{~ 9y 'N$|kc ջoOV`kG-6Ą b䝧ơŃ J'In[1NInx̥1EUES5B33݅P4˜:GoI/_6Ϟ*"=Y Zz}ī};N΄P|KW5X6+_;oVx]:1sp&~uݹ}]峀o %HTd.t _q݇+c ,-_>铺/CN͡s;(ONH XqWT[jE\ќ䂦jq{~G˃ gT/ZwT;=GL^Ld?oVVP!|Y_gxɢpt:fc,9ye_/ܥ-ٚm_&e+? !-{@T4jQv/A{Ƅո[Kn}H:wEjZLU9DbBQ,/zJ]!࣮/LNƑZ@Y HH33, ;wmdkqvDvF%oѯ~5q˯3=}OpO\}jRTyͺ7>wY4-F?*sЁI\#[i/cj!k20=Ӱ6KI]\"2ͫێ/AN02r5hsy~U~kccy}?}iz8M"-M[ݟ#ZL= DA4IOzzgǚjɡ=|ŕ)˛3F- ` y˟a]}_r.gs|`'iWo :"z^; [:YoͲ9;ϬH!(#Tl _|mn+<S$|^1jF5қ#&4Ҏ02c癕OWc?2Ipۺ#x0(IޤYxS]pO̸Co{ރ)l{ \o[>vZsمFBa  'ʽ,(=Պ9c7fָb%Xrסj8kR -#HҰk >8;}p$)eK[&-o2١3FYFLQm ۄH!jd tQD U0 0@=. ڶWN8QYn])gDљmM{A@=JcD@1-qcΒە/> {-XD\#R".IɎHD) Dk}~/dDt`ψx-#!9\p bկh|k3Ydol&e~Sޑkdm?coGW…'+_+!w %M:ZVVbFQ / >{1_+GbQЄ ss`\>o=_FKvg e- *IQb04V"JV[ *57ـ1H @&bJ06"GY@/Ȍ}O^ٯc:g5CܗsA1 VW_0,$(]#] 1X{/~MkD}yL>s3Wu.n6yޥKbd֟*ox:d%7opC.>wTpA5gѼ}#ke ^Oq+2+]Ou^UmHpNspvxP/h[!̩4U8PrI .3خGdry=ZH͎.`PY`uF{k!sOfa;b $s,.ܽh*߮K6g]**Lj ld_uƸN<2 S;ʣ7iMٖz :+:2\ͱLo߬5ƭ-'ncnq5' Q ZQF%)a,SLZ1|ki?ZJUI{i>#_\ċ JcIb#;6hL+,VfXuVwLw7@G:&cU+If0z1ȼI@ŽEԎᤗYť#dKSn[wLY܃_n_/}t4 F㌹T>PCFn^/Uz`k=ʕ[?8ۏ?FyϟK@cߪ1|܎𙍩 l˫o^} zi1gXzD*$2cQ4u|a ԱD͓ T*7yu-a `~ry,hW)%@\PM)w{Q! +l_TKKVNF@Xyueۮ6cnNN䥱ҖRÜQ\{?}MuZiW~H^'ԶSz-bv/ps%ݕ~umr+,ktU!0|a#b < _mz/۬JsC"͏6z:>p KoUЇo=Fx LY1' Hٗmz3Ud/=9oMtz 5bBu׏}23o>F?Z_|Q :)sCQo&m/+e)!zVAqd;}[RH+=@4?{\:~xUbݰMyX$팾Ç_3d>U}kFt3*V_;6,=x:q.]@֡i:H'l>#=3Iz _u =Vcjk@Ϡ.FB}ͽ_ieOo\ ]!@L98[޶vTᷟR(v~{W4yafW^nVUhhqݘƶ5_Lte=]\k˟C7~lw-Ĩ Lb#itZr/ƽ v38fϨvV'K7F"u_&Sck^r#(v^DcY.w]ǪdQhbi{Vs}C DX՟6,O' l ?{Gxk/x1kF3؄ s 4O}T'/#p2 |*5NAKMG:=?lZU=8P\h :"P;"ψwϋSagOM6F-z ߾ᅕw(~fj\fUB:y{Oidw6j}ы?Wk 6 M Z_M]Y{ -)؇בu!'.ZU\h U5ZP̟QXf=}UEmblϴ5)) q&3R=}|,ճG.ڷP_ ('I3JG,ixJ-T4i7}{#𢝟VmRv'c_PL6?wM7DWor"+X>|)Z 4.UA@ gm[4:AݗCNljn'w%!6 ?SQ!oDnS!IP8; Ry g 7|W)D&8o7p\5'gQ&cP -5Nz@2  b(b#Q=ty57chzVZ+l^?y5E6W]_ll}/*XWH],n^Dfr&q1 "yE8PG5/̭x)|~CtK?A/$^MԊv!=o@@YG-`>xoϷZi*b`;@mKh|oz8 Ѵ[ODРD!P"qN"&,fH“R" @0Jc#Q/T!`'Zs{ؼ4fX\9~sͶ&wwkL|qK( D?=[ wt7ZbPd$6 EZG\HVgŲՃs ^U'KFߴӛwמo|}"7}ei{# >aܝݚ>os{w8}{rno=vU枷|]σ^SQ;u/;1ysB~Gw K٣JӠ/զ+B}Wl[7_볧zꯍ'n*cAQI`[9t1_@ܺ]lTj@ώvNF>WHKBƸU|:N_ &ƃ_)8\DR|`-ې̒PīT1qfkÅ;ED,LnR\fYeiTM:%S?O;˦f+cLR.OYr~ބ2/B{}of>ӂ28g:OY3^fXYOt7RO ~oCSe=,39$♕[4|}/7VzueAɏ"8$4>1cth{h0֔"Tp q"禉R̗7 mx졄 ;"0˧ְ2[9qgRpgl72ǀ.zjs^i1\tۀXYUMNA@jxo[>@!!]^Cmg4댘% azq;qNt0,NÞ^V V;#H0md|5f+foL|{Fo.3*ʑWxT/!k'OS&c>&J qqɼaBpG%RN͸^#Tnl|snTb~e5!ƶXلҀHd,`5VWskc4٠~ T2X"%lWڑ4J$oR<НWh(JՖj R2){6cf"#&B 3)GҜogK T \* K?ŝ3$mp}̂ Lip/( qKT0S7N[G}m}>1G E )nclWfڣL@h.;䝅h5mIg=8 [:K6@.9rӪzڊ5:{lj O,~aŲ.ֳ-|0)WyW *w'bޅ! Rugb߹o{?jt)o.9u7۟t̡ _}G0widuއe&sձDžaӭŚ]lӂ$HšbLT! G [/Tlے7>Yמs3#*QAU!F%pȩ_@/`u% ëC55P2:P-uقRn3 N<5oݟ*dYE2'hs.DŽ7/‘/Oyg-v ;Eǒ@k>p{ǔۏK=ՉW\?>OMj~o{cv5,;vzq 8F0$45BtEݬ@v1޷Ֆro}gܨ:`4/y ~3?zlB,<~@>~_򋣻?RQPSѠ{ZX3 dža^ G明]۲ŪH@zk7\\]s {gW'˟Ii5W&ԈIEڐ] gwғST6$dȉ## !Md'T, \!z~}o|>׸:XRm첟t<2wkx1 ׏6X{Y40.i_%|v0~l($?TKfPg'Oqoe[c^-6J8Zcb !U~+NR`3K?N{|~nVAu)@z^1 vB23ΦB[V1; }' w?7N\|GƖp mW/#CLL LlgaYar +*Sk6k3G0iJW DNB#6u (U߈#i9O_F1Wf d x XrH\DQ*{ғ9&ac\0E_7 qNℜvyEzhdoWowfH#M :7ƪ†p7esISғhuˆL?עuVjOGɈҝccs ³ki0a-1Zg3Iar83վťccZo5S0?,nk F]7TD6l]bT [kuٺEq򈩵]0TX[•Jӊ?sk8[,$)WUd4?xMr 0V8}¸@H['tQ%]; 0~Ņ4#p8$c"vAAQQp R7wS[@7nؠͺL'h)vimvC 6fg*d7` ,!VI>?-Mq96I-s]~"D&"X1&$jK([cHw+Twf.4M[t,`eIה4RsܴyFF(A( yBeSmщGNȝo8?UZ+,0)U٢Aw++x<5D0&eYHKF?;_,EFJXH ^r({=SFII#O>~pJ͙ZFɧqԌ}ˀg?sf7 puQІW㺮ā(6pq { ȾƎEgw]nfFYFP:@{OJ-/Q '|TkFOչW|/4Y/0FCMhϏ>}SWȑ# B]qMs(*T8'6 d jHA0tr7 ܏x·zDztoGʶ3f401A8|瘑kMC/;u= 4[% )#rN5O갋\;!xaג:,Sw-Y Սa([5+HZ ) c):INSi{7]q|r`&J0ݶoefUA$pmu$~ ;,esh[_lM.bu%zadUe @uo) 9t0$D_hGxӿc9,2 `p4Rzit|vOs.P@+I_*Oz#_8S~y/kO@=F:roB ٳ]2bCF`f0F~04S6<w\ݩ޹ɤW-dzf뵜uꌉi! .u^PI_W-k-{'A;u1ۮohu:%tG<掋7/dZ'N܌@Wq,E6b7g\2q*=QN N袑M*Q3A?oV)[l-YE댕VJx:KgV>~4t\<+£Y4t&5J>Kc=Ǥϙ_)я=wcs{:nD!uL.T͆'6U%YՎ +uxu 8_?Rypeʨ>yў=+ Ʉ0:rnu8*<{XSB+#e6X4{BT)^ҳ|W|ᛳ3E 4]o芇 D#pDsXrD)nx">ۼ[=)W8)ĭ'Ό +d d+P&B4MF! |SbHmꙝ6,Tf@DL` 2R 7L&qm4~ oy 'I"_Mӑ7=ro#!"JRkV뀒":E=>fhppĊp$8X-}0\=]-|b>5OQH~2x1(zk-/۽" x1Z:Us@!8RnlQ4}'F dhLoM4tg+|rO_^)짾'_(F5gc=0 ;q|ZnB |UT OҾukfQZ:x=Aq߮0m3JMGd* Dl2HRUNjKtx:V/S2nV (Sp@<%AbWw'`@]06( + &2@ !”ݛw&GM 6i0Hn3Z 02:޹V7X}_׶n3ug="^F\V:1ݳK֞#H}ow>@gaf0JC@S^$;O@Hp?C#Ri`\#}XUg;P O8),eMLAT" XQ.:DHQxLeط.bm*ql4Y(Kqp¹AY#~g;qAع.^]f*Ra9F 8dR$"qotmV4G.K=q[5Zf<+rZdKfk0H]~k9 H:}Þ22O}[a+lLSkgX2Vs,UC;>%,!sFP:j ö3x_j?PZK̘8MQI U`F锥ED?E!BPTI /)e Tdx<̓Kfpr8`p]+U{?n<2_{|s3ZJs,(²3ñ[&bȈXUEGn_IW6u \5MXTLX3MV.zr ozB FSVB^XGxel܍֔Lԯ9UA/-faSM?#YԽ7<L, DW(J2Q#R`ph_rXވ筎t*KrWXŎiY6>^wh͆'|Z=o߅>Z|\ҮZ&yꎞ 9Y0XA]_:wqpG2r:Ʌw3 c0W3X|V{߳vZ5I`K(g,xgj}rcgKLQN5mq_(pT!" u-qg.VWh\uk'r5[p 4D(%c=[n6VSEaJ9:ݎdѸ+2wEN-ۨR>;B݈QGRԂI %;% "[Ѱ×["h!RFFbE&@(he+b9g P8-bLxjMDN v8'ʏR#RH^c x-4phYM:hj7 MI P e>"iT]1`JH %4(KvJVP,#bYK)AZ+j* ʘ 8pbADb2y,CJ3?w}C?};nW>ܦz0ajm]3A}uW4~͊Fn +?w!D7$ "+!qX !a}֛h˸v/*kr\\%M=vl^o يika9=dSbAa'|FGl}>b_Fga 7ſi8V1`݅Ҭ۟ot1IJeQQ"*`F'+O_i,rbG㔃nK_vC>}OETY@@d._}6gSraT߫ε‹=˃*<3!fIZ[mH3Q^㶑y9TB/|1PG ,dڜw9]N7_P+K՛ˇN<*VUN"Ѽ-@T ӯncg$SOP9Y*r̚x~Ƙ쎊)sݏ%vTGYwϛC(DtAN1L&8 1;+d߇!D 0k.1]!DJ9"JFԳG1y *4Ȭ@v3;-ɴ$ b1Rt8]yw̨߰ 4(L>Sߣ0H>+cIc-*c n @ &0` ࡷC!"3$!~/ ayXA*)1#E@lTt0+mE Y>:_uUi9*WM'Kw򲋛}5j::ιmCUr!T^iC6-n_.?ѱk/$T$@`Ie%4 G!}FbC,f85")+')p9Hdߴ_,>6kj R2Šb<1YI#N`$` qGĨ`" -%)0PAgsVike h.%!øV%ƚP1Bl]0bmRoRmߑ0Saʛ{/Zkۮ`ݶ€Ob&Kβ/W7h3bDeO5Nq1~[ljN]_m[/898)CDbq8 .$s2QHdɾ#Tɕ62ٻ%(=( ;BU"tOF 4_2,8-O0܋M N# EĨ"$t#m~4; ⍛A'դaGMH0"[t5zD[k`WR.6:Qu ٽx LGdcdsߛ˞lۨqHm?#=H&fL2KdӄHB[qkqJ9 2!-?]r Y'qA\i`0&W@!["5(Y +-<%Vd̑%z"HS{'j Y )msf^Xx&ayEHk_[z2'[|]u|wHbYSb"_ Lp#(POr$"=M.5]6mV8,zn pxȾe$\jX3Xvm`]((j:1Aߘ_@gĿ9L+jZv:kVd:5ha.ahG/`ۤu>ۗcl@]9MY3daNѢ73E3ؖ*тNlE$c!6vܛ<,EKŜ%ĉuHSP陒FE'QeL`Ǧ.Jzpp,f)"j!FеZD$ 5HaUE;5 TH㴏O|. dKM䈠 PJHF#\HvY>jEhZ mX7дL~9^hnӰM1F2 ђ()|䪹RLDh% ao1LX "?:Mԡ9aN>/;(& 7bXƯtAnt)Jə}4VL7 "[fk",8Rs텋w@ 9Dn-rbgSZS ;ү'T?=Ƈ0Gj#[Ӟ U~kzOb}FY9"JIPS[b4;lW6{o2NBnLҏr@rOXDЬ9!3fR`r1@[[gB7VD2'xӇ0Nr0\SݚtڍS/WFĩ?rr@jR `,H>,a(FY! ,r5[Z(OG- }L2_-WAV}xO]V"H%>8 da1%=պ5"Q u1ӓ඄S\^ sZ:,b2HDA4q#tz-SO,ޒ<ojq)aCj"¤80ztX *++ uikN%hl$EAaМђ&)ǥ߬֠T'MCF`{j%ޤRiuIZ 2+F=Y>総^y3 3yLzN%s9EeN ?F .-<=NM+xqȄIqX,< a s?fo  J ViQ )4RK)SWV#Y&V41ilcK *`O Y;6p<ۼra+cޛ˞+%s uWY0 GM|mW֜,˟e-pB9ebƸ1 #ETC$є\f$-ܪPpёGxLx Ƨ EPR !9}ԺϟR{bɬN'C0znT "b4|֝j.2E߱D=նDB!|)|k9w.m-utyNr >~t`q]]eb˱s-['zZfŗ*Y2+9CfLq_TU'ޚ}lA"קIqjEEtvy?ˏe-M#rkz- elޮOMDSiGp:d4Ε qVd`-oV?9D V{(CL]0cF/WYLXkRK >WPXExW i!tk'@"S B.O&khev@1fbH % %#jABeҗ[sju/sƶhqB<ڌ!H Ѵ=*AGp26}CP 1H@a S Fb!L( zᆦ32g pH.@' kX܃6L ג l=&O $AܓNSYb4gdf2[$\@)%N}[ ګwCRVx1~JՁ=K\aI/nxПWT,KnɵKn-8,i 9bύ[<7(%uw؝K/4hi]5bhm;eѪ4ng詟k\]lLu\쉰hSվX DQ b @QѤ- R Yciq J!?>1qh) x}\nIQ(SNXD:Mdđ(ew~}Y{ [W쟹gôvח`BK $W}7Ws)#/D-jVK䭉VlqpY΋S>ebi(sgB oW.WPյE9iC}Ȍ~ g%Z;֡ ]psHn$"@dD ')^k i1XQp;0'Oґ~ޙ,HƜۨZ-r/{@ G“XcƑX<&Hf`V$rIhK ' ڄ'xF$LLo-0eF& ^^~DvRQy'lun3iO赪_k8NKD%C9)!1{MN ԆJ̒28љ/`_"-u :ԆGcH` aDY!XmfThx\SkV35˷fbGٖMSe:Zy.y!*A!g~x \寝CjjK/fZN̑dEz~8{|]m>fI nz.pRGW5?1pV {Iն_#8OvbhqbynH0_h9דboň@]됒s>`깁[[oJ*z2oixâ RO\seDϚl$;&Wp +Ѷ3 S59~p ȁkC 5YbG(2QOIB6xMBdMU[mQѣck젘>|9yA^fVo-?J_8gDM۳G2sHp-3_,"$C P ]BEE`gmȌx@3Lznu{ G۝(Wbr% 5 `!QX-34$2L4fUPhTWb 1K*˷f(DIϺ;jQ,.Tl[N0 pׁ8kunE_N6$K+>+kLwgh/IxIQG8(*PXuܫ9 DyX+M#(bXB cd YAD)MQ1Uz: %htg-ЄvMqiؼV h dܝ Na~vP5` *oF*;5#u'& ٴO`# W';*`|ˁsn{a'ki"HJC$ HFkJ A(mGJR'('U-B(έJmACBN&D1>Y.Z+0ZTIwKkRiyVUd^S֢L5TXƪ`4ԧ/OPITvx$X4]kQF"r~|_Pf!^ FQ8H\uoOB$6L weL\`JS=D!tr.L}h2})x1'm<PW:cB=FF> |"KU2jΞjW#^cXxሒQ^Ɵ.aU0|k1Rt1[GbLz@F]y~Gs5.\yNIY:ei}[յ$yBr $W%)ZCjܶ;|X}3f4.[iq"=~plAEfʘaө>! vЛW϶уBܶg~\; t[gkVSwtsI} ,tA?)l S.ct${ W8sPf+:=UT%Xv4~am P-/IN_9]?/&_~W 1ɢ#: & 9K%3(?w97vḧ́R,cͨ+~tStMZϊcZi`0kۭ%y>qmq?K]#x&Rv$$uPx{uwQ lI [ /ە>[HvVUA* U%HtB A !"ύVeEoF NM{9\NcRڋyiPqMw iR&fNLASSڒ%}7XE sYB ߮/_ι‰L[4lI}z/;yli(s@ DDoT/ {T?Qi: Ύiɮڎ,d<,e"Vr@<&RJ.#t,8D̬:PPxb03(1, z<}g'7˥r") 4$ ܗnT5V8;{ d}nElf6~m@":}leLQ Hmc}N~ї^|C'w)sל0OQ A0KbS'(Sfcuyk=[ "PEi又b|; C1"E)pͥ.0jyg]O5X^eVY .mE":wƌ}Wvi7zKcA0͜Io\},(;B>j]376/SD׽aqO曏'ftnlb]% Q~|n Ĩ`}딵1?m)CF Ľ~L)=l1㈅@(k1N\4.d$P$De(%{^¼؍S˜qDS-5␊Q,B10қ:E'fX?^+b!-  `q/ yFIkduc@X<}{MND[B\HSGFFTAK |4֑97XiM :lڥdnGh(xXAd  X 5т|*:#f㶕AtfX3\fQFRjWbg,Trkȼ*-wж:?42r̘Gm^,ڒY3Yd ÌF[Q! Q̭/>]ЪyĚ/Y:K&CgtZCZޡiG'K!P8糣L~ N7Ppx^_~)}fc7nsB᪆_xU |U"6bL}k&1*syY㦖Zsi3o!8|U0\1/W]:8F @ޛ:&?vX4EJ6zgxu_9e$qsi* #X7bQDQInv27znl6jkJ-_1{d-&j > +>b>m%p}vVO౽sS/OA@ YaVY]"}BЙ6hlXePC b ]p@DT 1%L~{ܴYdoa \|XcG||lя|n7"b 4T :cho<> ۿ.?[S3ќI¬?eq#ZOoGeS0锋bc9FqF|'R!(ec j8L9h;ur,Ĥ;2'N BO$#}a?0 ?^߂6` 8I&:QQtI[ջ/eWm_Zַu33[GU7ql~~cV10)*NqT|^BEP ![/Ku/畦]J0&!KSօ)A-VbaMB{.y7:h]#"/%)Vވ'b;'1=aߨ(~mlbLbq4QW\|KV([ߝ'3sr 7@%uBR*Hƚ ڋsju"2v`uv;sy,hD)51Da$LI#Uo/[܎ueu}>Ulh}-w{>/ unf.d0 e8 JQjyS.Zkc7]E.|h_ o|=/RSXND!:)غ2(b  *#)hDUD @ނ3IMyqP4fsB?/MTJT9HE|YZ@sY MTp[ؑ1GS X.a)!auy']~O &&sرdIt$8F:ƛ"?<#KȖN Yk^16PFgG'P\F9D*.vaHw٬w|D@kJW` 2ȫqf9y[.F }kяH˳A^%N/ċQ6\sP7c,jxQ!OFZ}%0b$MHD`A0sILXDVzxPx I|z!ml'z}B&]Եma1T G=,nӬh RS%Cuh%XUl|?5\%Uc&m# j^ŔC`(HJ*7< 6eۘ.rZ#eY`Ts VUm5}ns)fG}]"F+G$HI5_ogNtxXP#R?"4'.]ص* {$Trr:{;z6Oyl+#LFZciZ .%Y~W V,IKoo//O9;:G8U)nù,quE&`ΐa1 FN=Tˍy_x'{%Ō9yp!OWnti4d<:a]wIZ}r {qd)fd:hR'VHڲt<>'@fǻ|T]H\<> >n?;-3/Fsz^ i,zQhy[҆;Z+LXuwPsaz.\TSh*¤7,[?Բ+i>TG2(!¼;+|7$෠r>`A*~m_UFKyMь D|vW{zqz[ecYD%qrK(yxy]@@`L)eq vY!;lc833HySi\ TT xp8( YY;A#*SliO.Iit_S' ] ֳ؊[nȹ,Yғ!6 wɗT/S6kDw[ZޔmΦ_x͊vWUX%JXBǔےNj|l!=6K_gMeDVF^KaaS:S^آ _jQ+/zQv}a b5,%QDIBO3nyRk C,hoR(g_ݩҷz#O&ubn#Hpft=Me]=aޝ'?[w]kJj\3a-9w] 8gݎ_="FL 7smy?b-M_-mz5,2n`z# v : xJN=_k29\yxq$u}R `*.pO̹tC Lޏ/AgD6L< F!.HN)=}9WGfSȠgSP()ߋ@!'AwUg0o8PF!U]nnDAo};|>d_1h)z\ I*A8Iƺ->pƙh?b&< )B Lž0񏐥%%&U'˥p3ޛr{$,u Jdظ}ƫ]p/5»{UCpB*¸xqҿdGTǛ6vfOhÜKt* aʛwHO^<X>pPz /"l⍝>TJ T0 $_<Ր2--\ 9vВTBt/Pp}k|1wۗWKܙtpuydjRV:󒢍mV6+/zOV;Tob!`Q }v \63n@I}Q/>vP+cTum OT7AfOI[KxR5a􃟹p[T-bz-txj^I1: _kU>&$}ܮ<~c}'_<')םr? /FRBIiJ_5}s#EI vx7Tg6 ܔZ n4S[JlBz_|2֟G~Soc%҃ɷ׾7\QAĽcYex)h|uYvG;(3ș>bZvH]/^.L n@J/P6iWl<)o'5y3'-ML=8b ݈p pw~S2qIpD?կt{οasXE\qKa_j,/6,E&I0ؠ@l)t#@B> L @5=`x{ŀ/`L {e}ĝJE2XP& Bu!2vP3e!7t bxb!n.F"ouƎgxx>aIa1<)~~m!מDRg%#k5ruhnْS)*(YEgc}VE %9|O~|uoiQS|EqF)IX2q?p 17笽HV\~>'7F6'6cYx'= gfg)֠*T>GRex8 /I6aGo}뛨8:[^rƹٵ#~FdxuԈu?- A7W8a(댮 tGCp鉸?? 1Jq, WX joN m 36k뵋lHP*,GH )^bw}/enAW KvD4Ͽ+y3 \D˓hxZ},HaKZϕhr{萕udakzW]?e=b1ɒ'( {e1[>ߪcʍ\4 %f^&}e: O_>ҌY:V?gz[0Yb%7 bͥ3!‘# cd$Y8R=~ $m7۷Z [DkD Ы?2\GgmƝoM_RLd}w?UaP 9)`ǭ8~'|p7h!6 3P$@>>vͮٹ`줅x%_ަJe<2ۮ-dA(Hq .QyRVԎW6vIS/oM8AI1Z1N1CӨ 5/O<|[  L&0%DbyԚB~8mL|eO|wً^*GU1e'0+2WS><@k?=LyeNP Z3C9n>JI.&txSŬZ=R紃8|⬁2vw>ĞZ9c/;%bEEE:Nu C0<\;JalMd k;yIjVqA xPY6}Q#+ag -z\C07?fNx{ʄ+ aeS1J'բ)/1%L>Q[ w`G,[-' QA<,2qu (3[Y MeWǭn-)UM1EEl'I2hU\xi'Ypf(d) ;wP cDU sa }Mb4"=DF5P+YreHkICsJІnUҙЬuȌ\v暲R7{}MTOWP G=dܜnAO}8wԩKG!^I6PKX~6$yM7"L˾/[x o.k-e5XAhYs%j^OVD (#o:|G /\^iF_IQi&ݩ<52A<Hյs{ ` Bp+^1퍮&ZM_ Y >߾yL|+xY*NS{y9X|z<};9{v*W&wݻi\.꛽9Imn?pZq0228]߾0ut bkDjp1P`6H)B}H*R5DG~$;x}} 3sZJ .xp>`}P nxhuӲqp Ъ⣶`7o^.iiA8֥)K0ǧj)H+H8Bܳɀk`Bl*@ N)F*Ku\r xw7v[۝-IB@H-a-A ښ~iҳ^r48c VKuӽcѶqyW{y\rLT]|WxJ\n3+Pr\j=XqQe"F*C)o@$ (D')klG\rm":Zs2 >Ų!Sp?_>7e軩aܕoזP3nzAǮ(Ϙc\4+K"M_k//O=Vn _~8TZRкUŞ/'Gk:oП* bm;YJ#h[eW=DL1d*$sH'=3bN).syPY&BAVH~q~SU# ӎWļ)BQaqSB\Y'÷V]nKJI&B1ȅ/((D0Vq%gb&O.ʯ[}1. UAС]fC|k i]%I4&TP ]1EX3CT /^o{_/ zY}Q¤\~ *нZi0y?bb|#&d/x C!!o7f2?bɎ+ ~ĀK/:F2fVk/\kr=QU l!6.4}˷&O_jBKl V6.Fpq@g v\1q ELg|N#p;~Z☊RD.ﳢ:TI-rȵ{՗&ݒ\Af7wjۋ˪O:iiI՛ PO1tSün =Z&j/UڣJ@5Zl/RSyYyl |xeʶH1/LAi}G\<b_ xU2F=Io׮4+ܭɼ:Fߺ[ҲU] s-` "K &=!y鏇\RԈlnh=S`l\}ߖRzmSG45[W-+EÝ^_)wpeִxi,6o&PϕZ!ݛ I+,$Gf0Fe7N$d߂eqJ[/=W`d G89 !˗TN 8\)y@X]  ̐1@9.|Ko0( OWX(l(P"H0ᛔݏ`!$!{Mbc$ C$"]);OjvL|Nʛ!(SY 1?d,"HFAȵXPH $+JC{or[ƕI>fyyO5iFi):4B h~tM)xލRxru0iX3VƊ NWnzyNFίɟqEбDC\`lfhd~{PPۄ>v9o7/D_d=I WtkD9\9(p64K NN \aDm}7U~kum1TQ1dD 1IU}Kհ)u "9҃^*Mb8)Y;^$aH pcǬDym1ҁXmFZc/ً{I9w)0$Xz# cZK6tD: QGdKM}W Xɒƌ3CxoA_(nyU -J+r)aW#9U6DJoH/ &c9E9]h2$#Q8I"-P#ځŧpj7\Cw-$)0WO"Debݢg/¥GN{qi)uSOM*G-G]hA?haY_M)\OXETs!T7 @{޲i+JpVWN9ǀԠ(Km=NnKqEϗEXIœ;O) o_N떪XnŪRxCeƨ6I8#%*TB==<%QK Gb 9|s zgAT쵴Z:ֱܾv:">y `OgkϙwZ[ÔVl)@N ZBhK8  U%=ƣ[/~TFl )dr^ ^#2DkzR67mE4JB8`/`x/H٤@ fv HlՈxKl,*EDk~ێxaD  螛I qUO0Y9B$TH:H!#AP(BO`L=GpF$s8!eNrZsC"(v ޝ܅ar1!V hcL(ɱ_Mf_ܰL?G^{^I([nggOYt*2Nk'4 !ruArұfY3[f"@zFNyc{2ovPWyPn+4 P|'$a #8d;YM8ᚳ{g m0V87 y\C=Qo[*Lƍ1V QJ,{)k#eCH`R ӫ{[<nBZ6ꕯQ(0g?$  F_ 7{!xK;CX Dh&mM~Q3W~f/*L0FY5܏$N5Q4y<f,:VN֛t%p'%cv2?ɒiYdiZE,N\6]es8(vy)1f0L ὒeł6L #-1L$~)Z"ˆEL( F 9N\: ֬@VxfvoueE xwCX $Z<'d<]yB> NZ bv:3 9  mV5@'ࢋM Qs/jݢ]r%; `(>w<4,␋RղbN$u!aׄ\ӡ A`"*tg /b] "_hf%!(E5pK "ݰA&;sVwߓ2QTHq0T-)OwNvy4/@biͼj7}l/{9o,蟫1P SM>QW$I]Z!A/;?/ w._ҡ*1si7k͈Zlx`c -iq@zw|Qp#4tȀNtY;kt/\o+tN?>{$؃)x7jVE3& 2:|Ţ!GebڲFtҖDbl3|]tfǼeZ$jN<ܵ-_<Mtr5b;Ͱ.)helz7b5PG{. NHDAG̾ƇHDx8?G(y}JO!cDž˫g謤\! [mk/ߪj.s)k|b&37bYVf4.G5yPyN)3>f"Rvε)*9;5>I<[G`z9Tͱ͢8oI{ wZLQg{ 99zc傕b^ȫϪIJQJ( 8lvEf5@U\go6c@~q5દ6Xx迴D 6Zy3l o2ƽ=879!e蔈6ՔwݟWvO2 ̔\|O P0(":W?^ sB5 NEO'ݔ<SyĠ.,$̧Un+Zz^2Y`LU :͔^Vn.T' D<"by7`W4Ş_қ pp $ BDT]'<մ}tئ]ݍcG&c!RUDA4R R!sgIjy|-R@P0-Fy*n'KQBy #*dhWO+SnC0ȋav{gzO/8)*2:)Ӫ_g4 1 9#E*+a|VUO +X{8A.G' `@kź, `6EqHǖ}IΕeȟz|a/kKԳ6^\@HN4(B0tkEv6U8m`B`2,eF9I&87#(=@$};eUBx[VyO`pZ毪bC=Ì ~β)1]bYQ$+I"lшEFۧ*ޭ: 儸*\W[3KX/Sy*>xa9cPkN`G!{ LX֧׀BF ^)5x)8%"1A|.LR}Ct4'mAȀ,?$D ""FLa!#`@BYAz Xw |s#]l A`P82ww*D0mJ19|UVLٙ@Z-  ŀIc: @"PNtٙVaT`0E(ƇJo=)&s蚈QȆt||tй*e<7%XKw;;Ϫp&B̠^|x#?t$e=fJmVVݾP ]0\LÁsjRRXjXbtO3"\0&!a_Fr$بFbiڝ9mhI(鱳WZ"PT'a(b 3EI{V-ݪITmMK7{ޟz͓/ukڀjBpFAA6xLj9஑SMf݈"׾y\ X&l65YolaqAEmĤӘ$SW0h$3gO+Iҳ[mI~j+VoV: #'k@ bbzn 'AWh?Q F # | p5f|aEd//uVZͲiMo=[-SubK %#nC!f/Ԇf+\%('t]9؜A2R W]=äy6D1,: 2S&B($`joի2 s9~LԿM7CqYOj{ޞɁy1J$@D;+k7݉=W?D8zfKg~TR?$iͧgjlI3L3F:뙣M[}J]IxaW2Yt&ú1J)NQ00ǧrXA^;Q0>"5rYQ#"ê#:/OG5A'rUv N$(F I 4-Y <)+9$%A5L m xZDf}wձ_;Du=l#dT ~ rFt=D;]"Ƥ>=\Uz2c*nhyaI'$h-L g\$:l*C~_`wtin Wy5s-4})ʪז18Bj)p0ʢAي;] ﶸp)i㐎/Dz۬^U\9."& Kt(Uͦ: e(UF,1 H8n[xAoV;"ȼiOGޡ0q'?I-;_1# +_}W<^b’վ'ZFU ,!a:ږ{~SWP ⽹2xI7?v o!h0{¾w3T93`Ms{ #T05= Z@QOΠd&Eo VD`TxቹStʧ:wyɪ};-l>&FЅeXgb!bfBB a)-u ve*3otn_ŬGf3i)c܅r!+[9&h jbADfDK@3 -E1c}4;K \I@@03fʜLK/Bq^etv6a|m2}'QN-r8AI zcPh89Ca2X?gL4. [dzD"Ǎ#H<Ѵ|4'i=JS#WG <Ҹ,sQQN1xcV$O|Aw}Li.!V?[?w㼍pޓ9'rym>"D 4 DD^HB?ld2o>{t g.8 w?nUҪ* mS8MΖuy--OiN`s$Y1H!,(sEQ\0o)zn]q}UHdʍ}v{v~:a\@+ȾvY^i7 R\7cd Y7<2j%V߽[ `Z{kz|T_^^5)yٟl?ro| ( Ke`Q"-ے4<(& Z#@d*/a CP  E; [UjR@ڶ D` 1Y}f-5G_=>m=yin¼(G(TGۦWV]N M?mP(/dtX}6Ė2Zd"PxA^(&39p`@񦿺;|GNy_ؙB!j i뼰+QH $S)V۾<eK1:p̪ #_~/@3! H7AˏM+G:u-S%)ᝢ.)0d6=s%cOHG-=K|c X"4}#*m*s'tb7NWAYi,nn n;0f҄HS`B.!LC!㓍܃j)G3I.XM6q{bJr.,w[ь;x)#/'zLWjϓ`P$H'84Sbڍl7&::ů|>g+ >i_(u^`L>>F]0TMaܰ9&`I,04c<ʊ$DG)ma:ͅ u :,cLŌڵ #8 i1b81!bHa$DBD$!PB{ᄅ谡V]҂.b3Zi_9ry+IzQ@> ѳ+O7f"]AN'[52I`.:F!R`>M(@Fim"d^$s4& 9>Xl@SܤjqyϔQPԑr7.|"LYd^Z^1k%=aYÉy̾55Mzųb4V*qq 34L2~9ake&p \0021y+9\g;+ dOZW g-D14.Mz6] {/8߈1_9mO'^@ J B\ROx-){gzcXⵑ&*Ȕg?ङ3iy+Bm?ux>Z9uc qI' n1ip:YfLQΑOb-@;85k>Oz'nYPGb{!ѷ6cDp(0u[7J4)X`4;ŎȌ*럶b+-{V\ǂjȋ3zJŝ&TB[[-ˆn*I5]r"@ vtOcfBBDW#K[]]cgދfO(Xağ_ٜ"<9O n)$)DÉNgM c&|\C[{WZkVO.X79dgCLJ0 UyzNOu|ƚ㚊#I5֙L#)+AW|%:rB;Dq>K <.!|a(_r+! H0`@ǹ`lQBk!.u)DQpI\I4#ִ/)@gq@{X;`]OkAz*VTAMePR/:3n"dXT|e۠.'?nwQew>29,oŘlW4fl$b*j\ۇ,dU9NƩjyоm6M(Y,p}[tQsYv0$ /(#0NPK?~cH#P ^^ !,il"4!a21L#{qH:5I؄r*[0洲YV 6*?T,S:>6RszꬺO$U2&ㄾ!d3| 8/732!oo43y7Ot>W$Sc:.[|HqsŹ]H0WWxe$,DXٰAaP ߈tbNz `tP F! ^ |dːN+39`hi ʔ@1_1v@9hf1lmq˯xbvh?6 $AZlB``x[_ViG#Jx)T8w贊0!,2v=!)$D.OD)Q4$xJN:-ZpvmK( bA;:%cXI ^9[ijp ZFCCTO/ʁKc}qp.v}eY[[EadȂCx k10b2 2L AFaxĀ@MG=% !v뚲F eT19,̼Eh;ByKμ]{cW^W#W} //uP>: + u* h(2:w6WC_ 6d4n7,GXz5!&>i89yeĉ0@g`2)+ ~kOT 4Q NNm-?}~OƤD< yPa"@y$"=mR<ѻ[HVr$¯kmw=,n#"^7Ëp X <0f&yB? ]1/ V $`~V"CCDBWPӑUa^kncV$%R|(VXf[e&2ç+ >ib(,$a Ne\ʩ?7Fwa* ErY>!4XیKHǘܯ$[8z-j,O~4e'lԝc'$y`7|Gg z3!DzEi(/H";=EB%)SXS>wf30%ƚbPHZ1质P FFBx!d$X" (1Xo<73\P(EBsWw54yߨS#.Y8L%Oƴ4IDG-(!T!EA! d7?#oUؿYCP޻ƣ t>6bm>3kH$GX5ڤFvy}& |l YSM, "#I2ceV7ω=p^Ppy`iNs:Ef`c󇻾}{a|'g⻹-~( +!Uo_jfR.t "$^olOGnIk+\eJEÀpW_Y|igb=>uNR%p2ijj3\]Ѧ]sV:* 쨬>&=-Ꮶ3-uW̹L}wєUnt7PiW6نC}7P{6+pdC5N@5džr;6m ȒYCA`$8 .-9ѽJ53`mӞ7w7(}: /(}yKnӁXwhqA|;wHXp]ꦮC+쉭J%ĺ'w3էDȲv8gaMY;y8_/C÷T,Y.܉̜Yg7e͔C2ص4 wJs$`T@[d<uڴ7g0>q{ve_EYKI$g`$p"P͙fO;bwSDq#b*P̐p_#>L7#_]I cIs6U.Aђ狈;aò" !$( "F ":cU " D }"$A?K(%~Yñg>g<7;}+N6fA*jQ:{yڅe%M.35 p{`0VZ~L);РU# ,QU0`,12<%$?xP`K^ $ sBOay_+[Ouu厵ּiXTSTu J@bE$0$Raz]H w <&`#* ȷe g`kg̭EymYOɲ_ o{й> H_{[#Xor;Z;+Z[6~1;.ֿ#~u9ճusbn[!c棴g卒07=m~W}ּYLRɾ{UiڈK7s~N(l"D+""F$<\Ң+!"E} Qe,ĥA50F@F6+DYIi5z6mFuظ)Ee6<~/єP% #1`D"Ԟ2~OРhͩڣFĪ&$*xf;9?|#F}nXZG ;HfI_gRlΌ׵<ʯ/=.bLݐ r[8k<>e ft;WL8{g֍x-SPIMi7&`<*o$wB!J [Չ \thafa^Q{'s<86Ca:oIգhVm(u4CxW7yʼn| nŀ!MwԊV֥(h\4l㒉zrANZ>I+/Fs!czw>НI;<_:}ihhN>`<5ݽȴqO^+ `I5um27 ^~7:&B64\_< Z:n]x|LƉF`; uv'  L?m׋C}Q ^(R6e0 [A"-YҒLVjU=έOG Oo~n䮏Vy>]8ĔdeS1'U42C.$1ov:+K9:#!PTIid.? ֽSMɭQReK طRQ-a"yί4/ \*$o\F&hSKtrr8P9RKaW *_.,E]fM>>Fs~g?3OGo *HN,w^. Ub>fd',ϯ,~ y""ILe 똕Mp1웉{<;0G¦ gWx 0_88H:}1_>qIr΂`Vw 5'wݝ~np)qs&]ĚZX4WhBN4 )ˇCc?Eܮ|T?j6-hg_t#f'\ʙn9yV Vr%}*}Ai LI(V!Mp w~r7Mwgߴ‰ƼlY .ӟXk)Y#GO@LU3YjܲNk*ѴtXȪ3$۟ŚY8F&/pj||@R|'c,Q۱TƂdnۧf_v&U0!y8UX}^J^ nc ( 2D/;` sXf_ ޏx8PW˦@ xR`@r>ͫ0+`!LD ,IF>]0w`_&P.f yE&!Aw>tuљ;GPz3E5@@F@ #쪾N( 4  EZ7$O+8%0UEh([ۚS<>g_o<]w (XrR/ÌnK JK3Lf 7Gң}u@+30v#spX~o& ԰> H H] mcKॐI RGԆG>.,W"CBD$#I`&DB..,]|i;̎c PE)MSFqakxOiXKqbX8; &Lf"}j{ Cvቩyv#@8D\غ@礤 yD,36 n8NMYE3C¥G ¢0#`$ As%n߂ʑ=e$7%]Y3H+ jw'Frj>Upяg* 4Gn諾94<:]鐺m9`i@y @v!-- , n_1?\޼[Fɡ/@-3G?.ox4oKX#NRR $ xi-V7S4'=>AXb/g~wo--0)8܆@51sjǔk302q  :965Jl[W]E馜*&4xZ> eh.q2]o6Me&LX o7T1eGtvQSƩؐd cqH$TؚpܣGV@zZFdFےRhU@ c"O@NGD\"*QW\cܶw֤NgJ?ژ ;:&n5-o+FdHF8Ua L{NPA yNgFP{^~-[~:q@p< ڏe t"ݼd^˛8#J t7o`y.qe?Tғc!q2 AM2; c_op2 }7Dp;3gS$`xcpwl4qFVC, \o.vƴ;<@Tڿ R:R8+A& Y$ù֍K& 2.LZQgUAr2 JFq*Hol m?8F7T5Q\/^4+.ͶF"7ERm% <@C wnJbon&Ϙq裃&%=d6Yg;xO4=u)2rv>n==zK x7 f|ZQd|C! P POh4ju%-@$VՆaxCś~vCP&'N I 8M,p9s vQ|*ʷl^" >#J0@A&>Hf haÒUu+_Wl7$K$2nz= vD$QajTO5w3L's^qwe"cZtX's'[;_H.zV@wlufFJ#Rp(]H@Cڙ[Fuoߏ~aoszg,l0ƚ2D);-8 q탷tYm&X6MD?b=v} Dux}5YHJ%`2 'WZ?_`+lg{^%*>_41 +9# rl7 2YH hs6?os`)vkDMb0|U'!н~_@6p98,ci9Z-|.z_bk4p! H-1}aUNǰ_ 6)(i@X1Xu g%fOjϴ=.yYQg˨fI͠C X* kR}XP)N)n]W%1a2zGRkrin 7nNj4J`D"p^8&]lݜ"=}}Ň:/M,KΝ2#/?t>!ûOi%hŨcJℬ8 :Cagإ治.(eEEƍVhb.=dy`_&ڥ ,1QŗpjQ̹.`&V>BXx+TNRr~".>c"8,oS+"ŸT`TG}Bgp<||Ǟ9S< zLp`ׁ뛩-.Y[:V Iboh mWQ7.TI9vcRYv]JhSTҪ[jJإ*5#6y*btt 88QdO%DOr`~nm(8(YdaEvqkG޴I;K>r-oP ob.0n: Pqw׫[PAzٰPT Ko}V  \\mS].CUdPS(PiV s+7xm^ao߬PI"Yg n*&O G,Б9TS 4+V20` o^?Vbg)f$G }X(BШKd߆ى>t15iÀn@;{yĉ/Օu+geɮ0gz7 08i @/bo`;MC`Ofw1)smXHp tƞu-ҳ|-s~y)%* ap[xl3sbV !K%ۃQߵ*pOWj{/dNY@D(q4ܼL("[a2YfXQ뫞HhZ R0ffʳ“ҥB}?nQ^>:ߨ_f6e^% xw ::_ğvmubdʀ@Wpĸ<@eD,E*tfdzwKC*`<e"+zߋG:qy5c̪- K"[KV$S}3ĉ34E9+ ,N9zB?X z%MMQ!^Cʿu$y]ѽ)0f SX+lb^@J3T` UzO1ض8T4| p^+޷ÿvl}O%%UYmbM]k^3NBC=!P }]Dc2""s{' S!3pL?.»Hq+ 2rd@بkY/]740|ev 5lņVm)suz@| 5 FbO<H{>H`}>pkf2gj.++Ir;XO~KMh[cA|BHBǃXyv[VM1p Z @p-d|[nqԐF>?-bɮKkϥI/MӬ&6HG˲ɐ5Yh򘍭ύM g(ǰ1*\KDE3 Ҡ5)47Ua$,j;)؞3չz$0hW+.Я.>PHURG8^#+X0ݶiۘ *iCJ'Nw~Wr2iӧ.s`LL9k}%'R؅KLS\pNM ʫs=Wh&xЇ;$l!=&YbL\LbB?QU05$7`n=ηg\vW_R 2D!Q.֞g~hzPN:g1RHFޣ3bxXMi  @:{6{0ݢN\P ?|vἩGor%(lp}1 CUdNIK̵ą9!cuz\EħIX69z]ͤ(1f1l!$dg&DN?hi5w:%,  `ĺ.Ћg2cHQ^VdJ.4IĐO0Y7oq4!uLpڸӯΪ%ikRZ/]nv& nz(((:s qz-X>֓]ٗxqqeԮTUY7ŬC_mj:IM-`8荔v͙݄X% #YoTy؊ۦ )ʪ@LF4cm#7 v},mx tpi':9r yL^| ղS578A#Nv/߶4`JpC/q_8/d>Bv{ !a śNEO`g u}s&Q>>A\R¸}2gߦpt '雸{J2xK@6S@]5:K,}|rHKsA{5?nvXzNh:5+Bݛșw0?fխ.Lť*1 F3n u[RthPWUO@{d wyu=@%.!.{җ\ =M>"SҐM2N9{%ސU?zG}ܘ;_qnou%!3+-nT &Qt'~ӶW=ܖls\oy AT]ǜ;>VT@#Yi0>yy_̶W)Ŋ9:qǏI䁚It uP tWwt<!k>ր;^4#`);=+r# $"ʢcXB f %RI3rJL{pV9h2sӺ:Vݖg-J /KY軪>Zznm(sF<֞8h3fX \)[\p,ϞO49>>=bu8!dSU _*NnWj&_du $3sC Z>{qzW|(H;Fڸ儅@faXf3U~tf m'%ڋCn0BUwBI"i\iܻ?0oykz# o60V* QWIk릭8vz+e"o;ݳ'ifff($6Y\\2ca;WQڷ"?gW.Ov\!pL bK43A4C-r(%is(#v ,LB,tbB*F=e-ǺwPdH;RD+͕*Iu1#G)בA!@y^=J&Kkk;5YȎX}_D6dn,"`ñ 7bǙw$p?|E 7͢{b;gb/G:V= mƏ3>Ko<HS ls-k_sj2x'ĩjvAHB;?ٷz vDŽM#(Ѭ~`5ƾ=U]jDb4/\.ud4"]?ƠA@ M3 %R6x>p]v,_&Uhx*l y ulͭ][dy,;Փb xv n@𪥽Z 'd8PW{\"`N@;d&KuMJԌeD 8_Qi2OoOϏo@/Gl+y>Һ@fPɑ00S=;z}ZDQX+1;ܓ>ʣJ38 =&xB-W};tW ēZUoм ]Xř}]|h/65E>N `.'m2/iRnW _v4| H >mFB&J4C(:&D@L!U'z̺Yo) Vm+&*@X H>=,+K49h 2'v+IWgp_jniLlja}L,ηX7'("aܳ|>fyz8^w|I%=DHci5HzMsn[<}xzv;~-1lqMLM|(f@m?2O ـPq6_%So%RP>c7Gx+ci#pqt]ܡ@6@RgqJe# yZL.Y#$DZ!tC,RRA,.Д.a#sf;p~Eu &leVvL&Hɟ!.sD 6W&$3%0AYU5HKo12|e B,bHFȔ#+b F.f|h)1}1 ~)k{ l)]oW;rߠC'pBzՆ(-j`d~ [}gvV]Vݯ4ʃf/y#7[VzH1H -$ 7}:;Qv}څ*본1Haaiiblg38n{{=mc#6wXIA:eU[2nx0IIZyցS!`*\c3miv`q!D :L ±0uEE6hU}Ò̪E}s0f *@Dn$Dg Os妟 +"CUDx` !x0*Nl=PՌQI>OKƞsJ:1/wW<3XI\b5>:w+iJeT+\DGmIU9XybV^[P{-<SN' a(rP tUDzƱ$cR7K/b jDsz EIVJ;tվ:#> W<5 +Q`QiN@_ĺ۵ ]nYR>T2iHY&'^Qy9k&D ȫ+xo㔇htiаBv%4:Fsns.A!d + /evC+/ .@ޘIÂ=`!e x*Ovxux^v-C1qX?zs]iu Y*ʒڋB[RFu {[y}ܟ7C 61OxOeZ|SNIUR=~̡L׹>ќ5U/тpn{s>VY/uȩA b %BF1>pZ)Kܭױ1h'/09=9^otZ jUdC-2X]?OE<49w\;腷=|SwD%tXC0-L^#/ zb- "&iwDf[9LsP!\+D`f %3dlI#pH0橇Q,fup~(3l +߼+/$vݴzN4nLC ws3QWM;YZYSTM w29.yBDNAtJbp"9bkxx~'/9p{_Oc*ه/Yq Z^_MDʬ0w+jwijIc y"1`!JH@%M GyD!&@¸ɏ?z* =G1323b҄@:La}O ,Wh):JN0 ֡ 3z,Za>?#~hrJ,Gr?$ėEVvj1H@ k'~(\ 7-6 ] w|WP}J!t~;'W D*G{:Br=-:MCxj O(0ULgڮ6xKE5i{K)B [paG< DMd&Da nkQ_#,RҐ(DDLVKZ ţ.#^l(6L0tb`u 2պ]K8%,6%DX~F}5JN( )ÞpyߵcpnBmCic!kUׇo[4NiA/KCd\z1$Uރ(ïbdIUl*@X^6ޒ<7Dz@DBx9L&@| S*,L^Ei,ߨI5\Pb\I]S$ {>y<;:Zx~D D;3~ 62cd]+e9Ƿn?, }-E_vš+zz-ޔ@b=o}.{`}nLnPNy?H~^anZz?{ ema 3v,l9&uڨ,O:yZ8uYMUǟ? Zaj |0C 9RZ v_`.57h/Jb"Vt qؓZVHn 4N; WqםC}B.?~?G}S|= l!ӁQӡ-S^OUAܸqS=[J# agI bD :BHp_ґW<P[ LӘ6-U)({rP_O+nyݳv ȱAR2FZ$f ,"m*G~Akl&aȆ7b7G*컴hd*K `D3\攂[Q1@e"'t"Fͅ@& b npB%(pYފ.wsYBޔaI[At >挑:gN ƺ 1#,&PZe bB[<g#7W'P]Zi6vZxǴ*⅐ȊSۥ-Xg;zEVVz,YNغ#DeEr,UV6vvǮ|t8Ԭ^jcDjWWx,VqN X[Zdd^R11,?s̝V>ݣo- Z9dqTYGW d4ˬk+WNҴ~֭"ʋ`$Bv am܊=h[* )4IF ~@`>lxCU3= rT S\fƨ[sS*cc3PIڃ:納k^QH,-HDKDˤȚH/,^Ж|%gSF/8G=᝜~v׬grPHt<粪o3dE7rF"xHq  x h/5ErM$:`Y=Ar=!ڄϘ (ΡQ sZ'no=$b. ^q GOvrb Gd! .!,#R:{{c)U{b?ε4#U+Ԛ" ,$! 4xj.8X\E@)Ʈce9;~6s>򎅠ߞrBO_A7xu5(DxzuwR4pRFzO?#kGNnx}jV_H9cA&CZ=z/P_3u_GO^P}=#0d 4 DT00K"S\`s $_/z@e<`Z#3`ȤX>S8Rt_{4 V\sw!g ~C'>j݈=Vsnz[hV CSY udsi}:fK`),>!MXH-Py_`YӱzvJL3(f$ 2ܐBrMp—ne$dp>G;vy= ^hPp-CI$Bٚޱ6?6" H:F[i-2( JE3E7IB\2,+bU0/A]$( %-_GX;,80Z!z>ڑwp`v>7nysa:VuPnо՗E*ꊢ6vUuT)L0ymn%vѴ ,kbo 3 2[C8_ pU[e4 S"BXpvfpf Y^sbOV\*=_>d=3]#L&ϥckAz%qx(iR6z/ɘ%ʝ$!,sJr<pC#`<0 p`)AbA Rcsȶp 7z?~spLp4U?$GN=go:;!Ze;voUB18h ! ?bPeO%C'Ʌ-cJCZ0IE>9u:+P28J^gj +4FZ9llc/7/T/%( cgsjwt\0rvS_ '|$ 뉟Ap(-\GiX4Ȋ5|b{'t2X |tgI61p۞M+(G]SB(bq1ɏ>sAOԁTs8 1OnG Q*H  8 (x?w V]F%9Έ8 3 +m&k|5˙&Ni1o־IXJ 1&no~H mQ K Ưn!-$Ӟ4pr pJ>5GLX1(#[&t',%łU̖0 @^ %kg~ @"V AFv *",V 'MϗW6K&Kox!D ߖ 0YV<=7?z::yblMȗ pb2;D6 UnČd]LC#LCgK30$(D6K ߯c*ZL4~E 2DLP׫cfxSڿu՛ zu]T7XRKCo@~˞uRU g!P@$d d܍Ο/^h3k/U]=Iq- ܫ<3k,ۖahҞuOա$ZBD[lGS &l@Moe{9fk 89砓R+kVmOr9_B@ ޷@fR * /f9 :iE#['SmuTqEgnҐW =kج997)Z"Z 52"fLcNT!p8 rR0,Xtf6-Vos%!9`g!=P5>{?'+kj06 Dh&D5?:@6: ؉Yb5"6w.dXxXKD4VMfHx `wT ^ < LQRBϜmUѤjv#=?rD .oH U*_qfԱf3CjΤQ(w߅eâޅwA=c-ňjsv wfѫoh'ܰМcuO8uہҴQdI|Տ`_ʌ׉J}u*l_c?>jnkg3ж_-J^%6b%$u#F͝rʋR%ʱ.!l&Շ$$(Dz^QͧSRV)F:P%)y͌{ã$cJ1~n:G畧Pa/di 'sb?yQX!S@H;?ZX85`;{瑦H$ -d&AB 8CI?c=-XP]л3y1,h+z;ތ%-Oڤ*$K?=5FCy,o9 B*IupV:!f]|:dHk \f\voݵnXk:*8%v}?x{c#a(p~<-Q-:{ w7,o6H{0A¦%x ԙDK|8;HA@""K" 7~((iФf,^6gKPM ãT=ѽ.`}lF~O={^IwàZr,p.=1odF(I2ݻul~׉i˶ӰLQb]Zyg9 +bHZ*cSq&Yyᛞ%+Ohԏ_daLp3>lCPSH7 iڑuC:@ff ' wj%N_${*i'dȀD i`ue*ښ.)9tlؓvAgR9^LtZ7TL%Jxɞj\;z[s4%d>?06bӣ&m]r皘jڧY \@ux.P͖wtlk0AFi˦n|SήX*ZE Bf0|U$Kh+8E,(tSܫwNFe" 3AC <4(XYf -`Hgm F^XHn`r DG;S@ @"CE#*譩dKsYɪ8h]u+:=KPm8!0~-z;ʰX9+u|ލ*u:Y{ B/לt[H 4RE ʪ`SuV(2A˂٘&ikUEgJD djAf|EWGE8J Tv|9pΜlO+M~'0pgꭥLhM_̐0 4 {wxqu XAs'IE%" 2"fnj;0(@>8+xuH?D)Z81Vr ,j{6B{"ϟ?H4+y x]r݆ dLm;_0']>uxƸR!O HH0'XHpp JMݠ?HgR>;}cBˋ0rr?{_GٻN,-*{xEZdC@^3! .%n ."~b$! t\i*T7r"'•[;"C?zFmчOXsMn!f%zv;{$`B~dAe^[-^Ի]^sjC:j9J@l楁F2K$m29֚zmDC>ݑӞ0.#uN>b;&Oڿ;3xjd # S w K}ݵ%P"Gb"3" &{thC]!bUøn#Խǧ-78%|E')#n;"ؠSOʽ}MLSa͕ݰSꤹm[7=sQeW^3;' Ea˕ojɀf65h5 nMglj݁He[O1"H<[@MoɃc*&߲_i`cՉK}1܂UZH1OP{i. ?`b]݀| ,'fֻgΤW#*V]t( b(*|ceyL($Z/#me&\ - ^خ ! ^ Dd (:']s~iG[`D"iIi$ L6|}"#S1J׊H,| V~+%V.p#0\g=|E7×V(wv{H3(,%HʟCvpIțhA>C<đ,DbU`5/b^\5fiJhju@Sy>kw Qű;)XέM`riŪrQoI_UcͧW.*.,gUƐYkHTY_gvuv# S|Iҽ=y'_4Lӓ-;&dǀ>` d{?d2^sb @ W~IX<LFXv.1%WmY#]Ԇ\&RƤzeSN>}2v_[>aY`B 1j€D !bL2X}k>#"QŨd BRŎ=o |C@ ˒2E ; ԅ(fAk>Nqw\?^75m*pQm웴ŞJct2DFuK$7]?s-z߬E7c X@ %C@w~>  #ʏq=ʔS ι9#վ:b!}ڲզ۶yC!;/| \ b$ *ׯY1^]Dcu㓀'ǁW^+8*+V7 ?p'fv}BQQju妄z]C$H8+y<$W!VQ 4"+ǍCv^%au?fi6OZEjrJKr*~xxV/9{12 pp_dQ,ZaRz i-ȵ*cSK Ü=z+.+\n"`AYPˑ ",NY0tq1%{(ԚћJwoQOp!(!R:Y!qaه86™V,ٕE9 )l٘cd%HEe< ғB:r| f$2 !2(#Z%%]C]5r] )@&j.ib`Xg붼8ɉJ/'QܧEd0rJ#XmOFZLJ:0AC?=!O2~^J.A5^gX\"N=/ AZ_B y,Ix74 ؚ/v, n#U2,J (kʓ$ڭ8ZP{]o~NrqIBUKcswftm߫CX# ʀ{mEcCD-&9")Hj&|r8z -VZB1ŭƁţN,Dbj@Oޏ$LkcY?+ ECp*H("`;y!~n4-( m{)]dκ@#%-U'do~|CkQ: p>=Ji%gH-$tͲ/VLg@'aHyl;xkzK g"CSzo\8fRi{Qzpo ҢpKwRw-xR0U^D%C@ڡj6dMwG?x8/;"?+&820V"0=J`̣_T>Y6fU>io,(S#0ɧ!=L $y $!Q2Y[R]/?t vbj[ /&Xl)gxpEX VOKǐ;D!NEDNLHX:&|^60+yp-Pp'*(L|)s 5fA=zrL|F=y+&d;l3cym~ڬNuK`l8 /l5<2~XXT\_%8ӧ+_\ؿz,m$7y 1 E,]a-RC.%w uG0x S0D+ȥI/{qr!^=q|ta+1m\ ߐ*MSwcdH@$RPbIb kN򈂌bLLbBl~O_ " Hy`CŅz6h䱌8VU&ȱ ֥ }溞 \ G߉顟1P2c_Cz mlK$+uD]/ℝ ݬF_\M{D(okI?mvtN?{{LjieLG#Wc5W$t^<RC{l|;|`2:k+5rʾ]E/O Ӏ At0P 04& Prp% gWS4sZ3 )h%2GY'p)PN %M;)!umOnCn}SA>k GFKK?cS.5Ra9rncb#]VV`yZ%RfY q HC*0ZO-oYLm@E9ӧ5@%0_,~; " FHցuX$LI՞%7(Df5܌;=k2]כΑ+)iXpTBȪqdGbsΗ: H.c~%4Z ɵ嬼IĂ߱JₛbdؑW^<1,3(/ :y()S-9n l<Li ܀ro47y?N UkXI"&.QTx@twpr"F[1!qK0 2\,9B 0+ zt)0/bE:ʏ'`?16ߊ"YũW\v]G<}P<8)SB"N@D 8pcxJ ,/El8g0UƒXR:RSlx־~Tm 7f8 <*yU_Y NIVq*@8FKo2nv.BXRL QĐ8^lqtV_>baX'KE;;n͆}D}銊g @\'CF{4B/ :K^=,D(D|b!)?k}Q_}? ~pᶉ飠KG8`w5ɦ)99qIx,HU rDRAH}9'֙ݤ{|a.}!LAc k 8v`Oc5cʁ=Ol RI&6+7|-Uiүf# i~txRHb*'B  } %raVCtI :Xle,,[Y\nFvخ[c "z42VˉF։8# W }m*kx'v#U,J 2̑.D 5GލOى%P6(XPr|sy[Œs3:G8xw2ôt+m+j]; `,ᾃ7h5Kw팖"e:K먔g63AhJ3@QH% 6i{ퟳK* jHг+?HQgǬ66WPva~tM[N.K>(Yu wn e%(w7#o:£R L~L~K'^>rvW~tG_)B_ j,nlppnnV,N*J}Odxh~w͚|z{͎?Y @t3ͦUُA֣.+*~g3D~[X=G%p PqMjֵSFtpR$`o=p~}nBgIokRI![ΐBFF yH-P@2yKʡR x9 _|#B.7?[nB[ <=ئ61m~{ [!uKXwV_ _^V:&6{u@+/$"W8 Mv^#^#4x=$X%F0Z da%$VKl7.^K~3Tv^^^+ٜ͵d +o\ug^1;8,Ty"=N\u[k7mk! Ąe Iߎ @eX6$j-8w~Ap* YbG}*v\ʯ9 VQ0O}[gvb,LYJ K/vU.]<ьaN0-[vIi޲XNvy_j?~G-),¬;Uz)b Mi2LS%i[ʋ!]t<eT;* cR$]!p_ Y) P,Y- {bf(:lw rY}pJ2v_;å=ա}')Qxοw/ KҏnUZ/34")ZV vl}6)oq>sAF~G-"̢xtN4S;ؚ0Ӭee;U \tU_}u;xG;m7TΝpM3+^:|i Hݦ {wfL:元3mGUs\aj]bSj*I5P5кRj*ɓj՘N#9VIjgF@j*IÚ Z[8=%dh)jW֕O4SRI3Z 5')) ɤNQ,TˣmwRQMIҺqbeJ*ΟgtVQE#9X'އ6d6" 4](.j)ɘn{RV53/LYg&,cp)䴵*& fnK)$ )w58 iB<ϴūn{[_V)$'3p+4*,Tvb0rHl֣,챭#wp&vI7Ot b`J z78O>79U9Oo^k)e ?D~yj-w/{eh<-wJf),8S{⿯KnP<.h< K V#pe뎼(.wUU +#z5 Ov@&_JJں7ug?Ŧ?">#ŕ"=\g_ۮ*iҽ}qOt@rh%& ;o3f:*.-}|O:|hIj8RR7f".wQCMoOZ\hܓɜ=rfzWUES4c;/w(G?$cysmc_zU?ttkI]yM?UE֔>#,<尿{R#imKi]U{anW%MZ-IzE+6t!;oUG/NfUqh;|#F l{i@XJ)@ZNV>%}ޥEQXsW^ds?Qw=D{;O cJVg\L_HWr[oy~[Tc5\n<(;4[~ =SVf+7m'2k@_fJO]o5iIm7h`i[ ed]*Uf=[57i U^Ɯ [Ǻĺz]eWbqG>hOuw|CԸ9O+۝'a͢\DScTj|7nMJ=g-wEhPi|U^Ҁ+y_ٚJ`_ 'W$lQ}9 YH>s{K!x8p {SI{ dn9!בT)V5Z{1g:Y \YR,f}ٜNfˬ_vM=?AKi4-MiDuJXsY'1}Ks(7f> gTDe`.<#xY=~٭+Og PeY-KLv%=Y;+;QTf鏯:K#nuoمo|4~XUO]}( vݼ*I48UbX E&_f Ix_ZuК/Zɚ/%u}TT;/^Huy _Ǿ>yXpApPRg|\o gVr/e{ZϧRR_SU]jgK߮#{\~MRgS{3W%$Ig#OoבԴ嗼w7CKhéW?rNo'YY_Tc:`l5B0]z64ȹ~gy`¬5nc V2 ##VN E7Y|$˛c HJwF{OldCmTv{</ȥUwݱ{G={@pYW|ԝj>qƹw]:"g^F8w( 527כK΃rtzFf ֫Z?[5]Ƶǣ@‡W*GޘsiɺuTYC[cIV\kM fK㴒~q6tp;ZBnH,ABޱ{U//qZI?7o#]:{>X} {X$ ^p>o3cjVO7K.{zij {d-VUJ.JHƲ=Բs^?Ps"kެPio_{9MAw9o`7I#uzU[U5J8xcwi:`]zЬ F+.HEU_0;:N+7.6h G\={^czŁqZI?xK[z`!sO# /Dm^M [ ?x }N`W>OF9&Ra@NprDm̱>\ϷONw^öx_dx Uq%@ :Rlh wɇiѴF[C?tGe_fXu6œ} F;mf`x~κwtcOӁ}~-}g>k#NwW[) (w^[xA-+3-dwU+kC>4]IQRs7uֽ4MWR5R=Ot^PA5iz*ܘt1wPcIZ;U؝Mݒ+oO2w[>/>/ލsToU(TxRCߞhdrќ \]hQ@Ƨg_ƽ5^ԓTb.^9؍R=]Y1?'9tݒxR"ע/E.8Y^> a_)]T~hZkSxt׾:xǢVt ni%.:bt'/C喣#7 $vvF͉۝O\43F"#,̞ni$g#F ٔ-BOѻ#QVISϛ1vνl[Z,H0HV%8举~\k!z6~y=VyK#iSE}Ok_=7у/=vSKIGJxvϻ_(.ZVX^i@$'Vڣ 2˻KL,EpdGԛՙP=Kū[r+7E,x?}"c]~}#\ߙȫZHL^}/'Z0Vce3}Svά?0pive A]5:Z:q| ;A\V( TΝ6̮;)<[o;G kU7W/-ԋlG/]M' jN?[c2+,_ﺷ_ƾ:Ls9gJq|-=?Qc_W/oD>UZ+a+.3˶bEk+KogkDiͺsqM·؉Uυd>}P{qZ+RfDDqHO<⹨Ώ_/nkZ 5g0E +>י>_ߝy5=+X4*jkT-|{_c7;WYXx$ <|!V34sRLa(\'}vh=ѐ0 7-om>աc5͎|y.cP r;UIcđ E5"@pX nwS'Q:;WXUs90$O^}֕ C3;m+G,{!<xA+y3=Q;2. Ο4zH~0-\ Q$_moDu؜֑tȡJQ`mfF<.HWW7Hj,&Ϭ~6{k0}ӷ^Ek#-Lf:_гC9_94I l|eREez^q yǓ&ZX/**?0o#9SϴOλBDT1oO`a/1N@#9^_fV7,jUߠ+{K֠3rL fFBnކߺPGy1njQSnn,//7ԑZ9lʰ*ʈX%{61qﲭ֣#AGW-PΌ@/L[փReRu$u_`a0)njk>FDu$Ϗ(eًך׉6ﲭ7+v,*#A!6]|ƕ_(=l, ^4F}y"AH AsHGs O=XCs_),"< ]mo'^ ?I^R0&sꝱ3&g㽭T) 2| +f _[7`Gekm' ov?O]$_t4UGh8޹V>xfozI#m|O{R䇲/)ZfXK ]ZzSz.}f|lp֞mA[ziGvԖ7m;-'t(ݬ':<{7/W,{c[uS^l֓tanvw]TT'gE]MYOrï{ح?o[]~is#jҗ?|f_~S0>0 1Ju#2$'=}5oDHd72AziGFn{a<qwbo֓tH|iBIQ p#Q"zKW|7b\  ̄q2Kmu?ƕ*qP]s3l2@P$%+|@gh7qy[2QzVF3[}q%b:avm'/M-5B9K[z3gVu^(M]PXqK0Ti1P^_sѩgA bV5/06|'_䇟ԧ?nd Q ^B%/SKxqﻤ,* ը:+NΠum"^q$˽K*A5״†R5Ҡa,žSs=t$t'kb~nٵߜ}}HRqP-mw>x>Z2}WPܿxMKһ}@`Ls4OYWZ|}Zrr5m+h/H傤 H8hZ轨&1'6,N}Af/1v}7␬VK\Lٵnݹ< )1*=h;Xv̭VK*}hknQ~Q;?-ZnZz'E'WۗVe?`©w6|wZ2}j@]NKݾ<))q}uZթc|<"  b,PLbb㸌qbxնXe lvwAu"xwU lA_ۆK2;X&CȜ~3`E:نmoREœz~^PI!Y2`u[p~5wzx z0" '} miyl504< ܓ3KiwX-S3KҴ@Ɔ$>9/'ԗxKim~_hX3|E}KzZh@:@0gxKM o<e6O=E_<9d^ 'YX/ݕ å,S=kr)&'2h,> LeZP_-yrhX%\J/v[1e'1GYp]QZJ\")fl ZpQ.g9{TڏC_.O?t\secÿda栫Fַ¢tԐ-htį]K_Λ=샛 f %#*%"ccT!٢jIaOe gdGǨ$ 4[%5?\%7}sc2Kdaӓ)?bWs†jmTIHZc96F%(4Nc5guP F%1KSlXw6rJ~ؙ*\ u #sJ?%g!vԃkOЦBć[]0,~ޣg%gK!YfDxnۛeSi Ŧmi#@ K}gԧUk^cwWN%/wR3V+亟5/%%k[:#dK]uf\F_y~ɛII5E\ wwQ|SoZ~ۺ?=sFѷC |9 }mZ<~vg?jmujGw|!OwŚ7< :f+W\ꑝi³ekW?t7o6 a}ˋ?h:8uru2st&CVwxSop2Hi_՛kB2s #O=75}HMg'3bE2D5 p.&P ⧋<7>M E\94޶H(-xΑl6?*^:?:Ls[%)fʙD6" {4YG&ecqie܃3 |nB2lfC^'g;~'*ӴBB&@@)%"^HX|ّv%*IGmsW$j%Xse3?];{l*\Z:܃l]Hz4,á ytіu鵉*ɞ/nv:J1V>X4c椁{`%pY2!-,(DLJ#>Wg׫Zй(r\tKSjlS'L^%9N [?_5Î}V;p9wv G_sFUyC p"yOtP-d.΁n -8xKh50ƺ^gZM5gb d"(<7]ssqrx}p:}mr{6kMނ?^? N|]5ZE͂r‡4d~r'\|_l-w|?oIy[n-JήZF>]g.a[]6W=pS>zS}mG%\κ… 0ǮrT}՟%{~ǡWB=}ՁXs-g}'[-|ぼM'V/WSº!4: M쩺/w*E=پI?I޿7?5NC1 u?DKF΢-坖8o9Y/yau~SC{s%{v _?c!yx_iG=/iEJV/Z Sξm%xtdsЛZ哅XZw`'ޯX,ycen@Y8ߕϣrhBWyjl\ iYL+:j5~a64}_Kԯ>٥o+Jw˵B* z /,8/i N}ޒ*z+Ϳbŗ i-d,׉V,NJξ%KWeG=|%WRI&PI.9׫+ į4݌Gg֭lrE!䰕Jh`jE!f?gOp}.8PH2su*_)^ړ[<*Nj&zS?@ٺ׮iT/-Iٷskx_)t0 `l;̈́ q!zybI>]{xۛ͊78Bn_[zTea>T7Ye!mU'{w[U)D<|P :N}y'0?tf?eۻQ: Tʏ툾n~cWBPu)¡;s:R?؞/l [٧QTYv,2[|X,Tg*zlG05$Jg;󁮕)Q맦>M<.j?)$A坑Yh!bUj IW#~3|tؙ1,޸MP|iybnerӪ`N[zsM%#e ,{DKJyūYg?:_lVyon~ ;7+yvR̍#6DҊR^ͺ]%٧ek_^.楑{ܒ1Zq6ꮒΝp_\)) 쐽To 햘dsolg{;7ܟOFm  g=IIw}EowwǞ.v"C!7koI6-q1?͑i?P`Ixba,bHbF[M$@DXCܺ9~#i6!18t]VĎ+]漿dk=Yc}GU)yڊks{7QmTܘIo^~0:#k,gy8QRDфMD'X bv_uDZ8ƑN<t^3bRhCOxVKȌN<<\A^ ɾ$ ս@GB8&^[~q:8WR &Z rl$d8[~q;XR![BɘGElB:$ 9IwB/VBh/ZH\}0T/O9- =&4U#16!4y}a^xNkF|Rh@Zr8B2(#' yaaC v!S0ȓtq(k|w0A^W p~2a8q.9Y>Ah5NRyPRm$o}|bGF4eeOΜ!L:4ay{3LYMέN;E,}20eR9Rs拼,GmQ Skӣ4G 􎑐0y) e##0j˶9yxZ/%,t9RHr;:3Doo >?]$[d=e܍$-Ǔhn֬^mH!!YOb\%ɔOn(LE!}e=% X9c; f_'_౓OQ1ŁgE:]Ou,oK]P9_3{l/ZqVG|"|x+Q!>N+kV;RͶ7j8ze:C%sR=/JJ]|eތ C4?23NiC~fo#z"|pW -qY'yybE ?/3dh*z>rvim뺓j{@GR2^-1l6$7<#;>/vGAHhl"l7L2[jom;M5^oC Ǒ=G!Qr#"|8v]7d[v7G/;9Nƹl{^`&M tELWd. QVĠHnlȶT%`3;:Oζ;f r |`WF9a'TK}KP":'_9Hͥs$UvbJ1յ)s L),!7;EH-7nts1&;6GBV(DV^ooc^O%sֵ\Ej.ݟs~E""Dmj?ƣ۰Rkdκ=۔L"+n//~6G}v"x[k$lQU*ʤ+~ndp w6RŘ6\il'q!%znZW ( sNjɜuO$VzDx$"" C"EFmXˮ$F~aeRv=oMOe҆'=:16 ݟsIdO=7ScڰIn]bm&ꓸЌQy""C ! 7?RaΉ#@wȂ;`WpilW8msYIZT/8|DZsW?28=sQ%|C?q hz/aV0YǷl3#ώO@oNȾxA'w K~ٔh}ݗ~/,v2GoNj5hRHbӲu8ju9w:j)$ Ws rg [-V\n#RH -9T^_`O*4B" @NMm<'A RH"}(-XxyޣSU,B볇l;3lQJzqٯ\Mvi2W6?\Ke5 4*"ߣKռWٺ)8 dȞ;eO=y]5j8o;G U1JUheι *RM}Ņa=T Uo[%thh7ɰ$u/<Ke=R gW}QaW9sS3igCbckX &dU|Wʔdo:[3x;^*  } k(z+)I ݹs`96 OUO}|xegH |%큭Uh8WbJ2ZԱH`O&ű#ݹ§=aea"\Vz?[z{̀ޢW^o~*AqpCwŦ`A?5~PY?GgjN8W}< ѫ{͋:4 P[4V37Uzn-ԧh ڵ?.|]qbepØ.r-U\G^]ԧ 򬗓#o;.y{ySyU JɧDW*;d0Vތ,Cj%٧ji#o ^+OO3tpjǮq6<C aZ)Z)S_u|9 EM0z1<dx?w@,VbԧƳ6Sx*-q?,oiOkV{_+)nOntHn*y3zzдJS<5t-'mp%/z8_Oޯ8O/z%|"<h/ iŚ͆7V2/yEaPtoWM,h}YA] nWzG^}y3nؕWf~G0&G՚ǵ$e+M/=DE1cj~'IYӍ?} -8mܐH,?4??%_vM*.Bra׺ϛg}U1u˅n;yaH ntvRyլ*I!/_z뻫bI-K᪽XSHy|^^?TE{.|ynjHSH>?ΓnrԍTLZIIGP%8-kkT3a jhOc>8. 3bQ㻀GNں>[OJgNsϻM J58ywծӺ%_&! >S?\GԴA}R/x9Oh p^H´nɢK{c_sO|$,nyLp`#mɴoE-&.دN,'ݜ#ݧ4%ϨX423#Lk.|y&A9uodZWwyβHN{k$/Y@GreFrNBu 0u8S(ZXhctFjim:[@G˶Z,W铃5ڗ, {k$Ԣ`cD[%:yNXd.< ܌п>/ >X5l8Dvm8:Lօs.?-g7ӨzrVdԪA+}>W?1"WS^`֕[Vd]~aD#˫X`k{{M}[u;[%d܀dF$ 7d;{.`[~0(|ց٩$"~ց|6^:Ò)B 7Qh~ճ8M)w)KoZU݃}z׍c}_mE 1g7mA3RH|N%Y[yMt˥գΛ1w7(-<:uEN[3-Ss_qs6rJ^/ys}/Gn9Jξrqiݹs6!)uu!/זJ:Een(0t: ]#/{[+{QMvܼ$2)M';% x979U &Ý` hIXB)bvžQ-~o"b*t˕aWy䌫(S's t u8%DWsk$ۃ̂L^k<t%o:>rjrXcI01yלXDWQ%e;5g!#jaJDe, `=ŽTWAB8{,S{y ygT-On` Yo g)=ԉkUv5Zɱθ Oh4O:u^}Cq)9|vi6lkES_Jmn1uw {oaWd78Am((d̛zeRR D6Qsp'7-۴C>ORcid){X⃧p1; l,$Zyg;|c;8 [rLߑTJpN:L'Ιpl依A_rgp?jڃ'$-$\r | ޼8+ëO dk&ѪsRϻJR\07\͟zYcO˖I ] Wo=)$7\  oMO7䩧3t V6޷#5;pVM|pыCY5xq432R~FdnDG4!tɚf;{SHny&ڿJ2+?S/K+,5vo4_eR3K_)$UůQ~TxfLRH&ndɯ2Q.c_)$[[yAJ}VY}Eb,1f~2K|jq5kyéۄ{Ea8rF;\/9Esi02ZΛj=UtYurژ>ŵn{,9A+k`C9mfO:ܺROd׻KT1(lmVУLJ԰*Uϙggܿsl/vK+lHh`$+lyj|"tґ4 mw%&X:l[#F1hНZcy?'×uRXNƊu#5GN~k~vW'1~+I]F*BD  d~[HqAߙ#႑;jdj|6B} Lz8wWlr}F:iovgoLדX_cu^N锔o\T\ԧrk4uN_b_]>ۧvSM9KϮkԈ͊=o[>pB*qG=zYrpN@y=~E,/tz,TZDW_NeS#?ȖXJ2J_Nuj/x-dz9P mAžSIa,br{p-FẌ́ej)k0AlCa29cNWqêy393?mo?Rc[pu1[˧98 [kyK|;p]^!c;W|tk}o*ptdNqOIxwQGK ѩ83!_s5ڪG[/ua{bxLg%Wa4C_.}U+q۹ 8}:Bbg<,OeTU5q3m HK}nzwK LI?ҨiWпK^Pfn:!\X7Qb;Қ2]SkZ]ntzD+Kqlp6DiCGW,zvw?ȿ_yI;ݒTL>=I|#i8,E-wu绤?cnzJc,t໮_swc3w!7zPμ.72Tf9Nh5;t#B7ηUXP" $߾}o#VQoV@8LZ1[?m}U{Z3YSgֈ,HKC8e𱧅h2ɛ9HT;_['VQ\`PGHkؓTf*{UJkm -yI05{?(HYyTu)çNVnxᛯH5mJr=M'uB5ŏtl%I7ԳX$8 7=#uWK/ZTK!G:"IN2p{7*y#J㚆ǭnݳ(CRPz,]yZ I{G{cD OQG7ty7Sf~ŅN/׸41# sv, o>s"5~4}ͥ%׿( d'gBJ.ڜjck1ӵC;+`@ka Xw |,&+ť޷lL=v>o`z0.B-}=& \6].>"7khu&!1jם/sO. ,w4{7\biz!R#w . qaI:}-a1×z4♻Y N~I]ס}]]$d񃻍tzQcз{ŧ̡>_&d(G-q^ץ{Dd|tl/_'N-ì.0\!_-{DS1N~ҝ`-igtڳkSbW;G0E>5 3>/{>@K2~lmLKƥ_-y;{f:j9=8f_doDNU@-֦JI_lPv@)9+}G]~q:X%V`WT,j>WϿҳ,x([ʥ$WP,XV9t(~-] W*Y{矓Bǡ[˃?ϸ}y,4BMB[[~C?.p~bΪ#bvk]ܕu˯ndQ_ 4g.ߠ!no8Vn8V~VW=6>גəqOu4N;m|Vګ#ZSp%g:gK<6mF}1=J˾֌6]72^w_s['fnq8'8ygZʫ)^,JL~H qI=;"d^KgcM0a)6w7;XyY>WyF.S/ol,EP:bD9dHp_x,R0+|rk\Vy^ƕΊ/}&V(_]Ak#mdys%չ *Y%Hm{ryMD %ㅼ<,>Mv).,4ݻǭwtȫkJ'k֢5 fl(EGfapcnD 6Iۥ/{פ/ҟfNTiUZDjՊްJj呵5w2N>)cO_[JJH8kxBj upr/]QNgej}-nVggl~=U#YQw~F=?VW"5X#YNw˸>S ku%1X&Fg'NtYy >-_Z]F[ժ:aYJ=;k 1CR=?qa r>߹"cO]M*~嚎degI[? zWwKgpb2uq Uz;{-i̧nt1.޿N%-HOn%o'Q]j^-GYW-.\lPKRvqv/,b{)f6iI'nSMdYaL`s2o>اLIDl/MvGC'ծ6Mw)L`?ŕO_S!Pt.76meة̻\I䨰~t)v޺rZ''Ԓi;eȕy? D-l6ՏzLyQߺi?Ŧ ^5S휛zw{sZrԿtZ}ʼO>(ƦƵ\\[w9$m ljjEdzj]/$A_AUoL6T$iW,,Cp cw8TL[v_]-B>5zWuw1m/_]+AȌ>1ڦ8\L[0yݿs.[ZゖpɯX\3n_jy"u orbعX͹-;`?V:ї-1LKXFbTERm`i_%ziw@@x3_0{o@A VQV*i+^?|~_[~/UMe78QZ\s-0=/Z5Ԗ(-2さ{ 'Qy9_Z3D(ed)\XݟF׿[Ҽ?? ]XfPR e"nT!ģɑ+ E*:B ĆDT¯T61PUpϜi-~\ξF+$Z-iSZNQ?9$C#{ "@Mg,C*xwt>))Aᚮ$I&{ hʗ%Mf确/:O ŸܟԮJb~HWGFmQS ^f>z|rCTh>m5%^wh ٚ{+֮5_IG8#V(u>:[|𓷼i?#:#у\o K++ozʁ.$5JPZ+yV ʬiwt ÁmStFE~%dk"ѵ=TV8n:RgZ\I+U"HmP'_iNN3qu=}ZӦ%rޕec2AOJǽIӦa[̺弫ߖBeUAdWG/K![4NJwJǗMk6xߖ%jys:ʗ_/;T[p;se˝v~ؙ]ap"-2 5 Ƣ{Kfah{U;Y[:5 ebs[VH]3oikwJZrr&5 vk_/7sd.TfꏞP%9s)Ƈ9˓5U/}g#=tx?*~¦.eb۠dOk%x۔Yv$S*|`$e-lULY**Ok2U޻/ؙBիI7Z_8 rP 1p-TIryI @޹Uָ[&rN M0BQE Ϗ?7ʮLwqk6. >@,ust*bƣ.}d+WOΧ6:ǃb|d[2ՒKwO*IxPL>wd%Ok}PRK?u':d d~>ZI1\YO3/M׶;}u%2v=Ē׎׍ 9dԿR-_]Ӂ;sFwJs,`ZP~2xQFkۭDn~pMQt ~9jKԨZICuœq/ 8Ck5 I=/9?y?k_!j:'dD#d|Rq\dr05c䜱xW\idvk""kYX]ų;kVc5JRɟ̞ Fd{ͯ=%U>R;Gwk& ߝp}*_,"D 7O*vLVm(i܆wsma/$G,T-_Xy2~>ao`kk7XxW%G |M!x\ WOvmկƮZƻ*=# c*lzW TZXЕY/M4]{)K̫]'|tzX\* DfMJg-oy]-9Y(3Щ߶T)5gwdz"̻p{۝.䰁 b3 Վ8)6BE0+Wgg;ۢ_)sNK%6&kƿk Lg݊OK:v&{Qc!ef/xyf^/DiJ[ f>`wSnəWP3ABhY C%+`ij]D5o3`\negXq+FyBa&崒~\C@쨯;9tz=G۝SH2% 4-C  m0va+1_z|VIyc=mD.]=yۆI _|1mqHx =؂>K&EDTI(A_Rܬ<럲نbNbb!ԙ EpZֽ`;y4ˀ'ɡ?|ݦ}ypxև?|tRߵsbkzkSNu( fm-pGg}G+?9~_hRyp{馕kSMK}צ>J]4ݤ Yu}})_~#r.XÅL2O;Ry!y@M>9)h*nd WFoΝ G]u^YZ0t{W\iYgX^7y<="Mfߪ‡o`+O$MMn4 ȓC?=hbGd• ͎yɯ]Vm[H律=&Va՚#hwDQwEP|jN|D[K"5?;:9$_׵6VmNesDŽ/SX;ц["\UUIy~2]dj #ɑx[p'ys҄VgGGu74ț5{}VmN:j O.TV aV}=__P\tя߳Urg V_ՒdeWo<977Y|F2r)GKҺj身.zZyDS}L[jʕG$u[`ݰ̧y~扻~Sdj4ASV-] GK\m@try2L5iOjUeoAZyBOŒ0GJrTU޷a| j:2L9Rސ[?%"Hȵ]{+ R+(=Uz:25FJH}%0?pWGS d~D\eFj e 1}e?_E@ܰmX^p,Pՠ"0Y3% 뮝.ҥrAiݖ[_\oI -}LحUEtzyzxKZÿ%n{fz[f ڤ(jΣͭÄu}o`Winĸi\}ۘI]ww޶uXakxwr8O}ͤw4g[t/?Wޛp`_n3p@v0lʁ}5}hCJ\_U )r|w䙹AI}]~)ZXvcDVא}ׯᏱ6yshz eoΏ.k_ ͭ]̳6znЧXI1JuR7|/wjBsK֟ugs}ŝjWHZ9g(,Gw@Rda]Οon_2憬ד;g[m}=-O[svKpM境f9;i\s{?'(琤J^*5 ~MM͂P Fb. Laoꚅk?NSfa\.@wBҧYPK^]܅5{ŮxNc?IO?tA_he:MTJIVzbK\6SdN-{ [*aؼ6T4bGɜٓ]oac{S?E\ֿB}uezވI5aծkF_nM;ʉ|~aLxU΃'s^/l\tKykmoH4ҵ'ڷŠS^|s?uc|;)Mt>'=d}[77MOjubOo9*1K|Mj-Ok&W~Rީ:_Nd#?mj;W jMbrЕk}:N4TK;²IKʼn(gId,V{Wj=P-[m0k7|3(B86=N+[/4>P-9[MKJzVn,p  "6(a׾o`hȒѱ]O~Lы{ў\ޫPK}ѕp .<^޶PGi,ybY04fAOY ]'Oo:Qt|]_l(k|Z4{>\^|ǶO]0m7mcJOJЫ1?dyA\-]ZɸoI9~^ S>3/o+[zcw=Wcn(f>-Zh6u%-Zz3RANU_FqRo@km6[mB`u0c9ٞBY`YE I(6) @6|HbJ,} I1@_=UAĘܞoŢJjVxd_ƯtB%?zT}Yͅ 7>Iߘ_(nI~ 0U}{,YooI! o}t5gГ F."rPlV"=!I?s_Htˆ4F{WIƀ?#UW+3Gth̯$G9i %j)gOsruvu8|dCg9Lg8KNJ\ )Y~zy"tP%{^'@sm3Yg.5Zj{$w#9Q9{P&Fl~g-wa|y=|n6Etu^l-ӋZQHhWHݻw[kYWH(}?ՅO~$7o 3q՛rwn>>|6kVOA?sɩMhgngDn2>-=zpÓw~mJrdx_o~z~n10"?qGg?Z6ń>jvKwW[{F $jɘ#5Ot}Kk׭lT}fb^rs{DEfZWxʦn}Tb<f|DĐ\}A=p˷G*x.kwԓyK_N$kB0/{l0GP}Yd8D/D6'YXvi%e)фvKqscr݂DG#i;ng{NW <fWC{uqT̷GyK.BP̻w$ž)%# e$Umpg$Jr~gvUu#J2Ίȴz}R@2"0T~F_q˞JbܗpZ|tO*T>ܓИ:\3P:k3x/Vei+ Qaz pc WH f&vԢ^f\9ʵ-ǖEu3.%y6A*l0=h+zr2_\8s<|Y%oUMKZ(O)gD-nN=p *k_d+玟wgE򍞶AefZ\z_֕5~);4V;p}R %ɻ ђA,봒~[hnB$Q?ݕuKW}m_̹J773_ {7)З4m^˜/68GA*m_n` |54=IpA")(QqTt) RgɊ EfewvZwB2z؝U׽.U«C5 #<Hઓvjːd¯L]'ܗ^ۺk%y7mʻϦۗQ5Ewv˅Trqq吪1FcXG햧 ٣WהNl*IaP"*$}M;LM%⽽~~I%%M80eD弬8)%(g5yaJ bO`"0/gN}g8 |+>91r;t 2pa Hlҁ83 O(=V!ZX!$)b%giGjy '*yƔ[Wb C3с4j6+ <]1OWNYWRBQ ll4:pkJkxvIn9yLIISS=>XJ7en6,;:L_Sdʁ-7% ]aUyΰzr饇<)$M{Nu$S>%W5ڭn'7eji ݵmՕIz4ݼ󀰿P]XAxd>D`uyKN9Ċ>[w]a1O pd%.Mے.`D $xU:?piq"uK1XuUM snfbzDdHx]!oP*>vnf1?\}٘-/u_l]l>w,SK^v5l䥔0jឲAW|7w3Su_sZw5]D ? ,S/ys ǼV_%<ִc :#@aۻֈ/=J-y ,}xyK Qwv7ՈC߱cZRJ*0`$Gqp 1CUz!O?Jݍ#UjISo( XɎ?>K`b\ǥ )$}KJ=]LӻKV. aM?BWa\![Dk03fl- Eҧq\878 Ghr'=#xVŲ=U雦})$OsmK ]}-{@X7qut?Tڐ_\q R0eZ_\sWJrWjxǯ<ø߫w7wy ?_Q ӭNݗ`g>J! hrْUsY@k7UO)0m׬}KSIXZu矃nCW p3# t @8 OCWˁɡfwSA$𥖄z͂ &אQ*řS\$$|eq\jQq7*.77p3_}>$wğO\7p3/>y[\ծ_38 ťVneݴ?Kd;ƽ~K6XM~$;YTu_cv[y ߰IH˻i$}'Nac|cb K'шĆ@yaѡοw_X#t8A~Ȉ6}_t99Q]_t:9+ۣjɢ3)ȿw\O iSq4y}_Z{aXJ귆F.r b Iʻo{N>2ᖕ wCH#EDI<Q&}՗m5]W>ܔw]Lݕ_=mγ)%S޵#sZ첞dM_ֱ׈DkTrq۟c=$m~~޴o=$Cn{ #!{D]'mW|Ƀ\2;}y,-kt/w_r75kv$|_0&ߖzѩD[~ρ^cd]Ixo㜘ϓa=y|6KZӎ%?IO/\2eޱSiWezեJ<'gX{}}d J"3챃?yonYonR9aui8ұ]ҝG}ިmN!iYC]~=1;Obɣ,Q;XO數_o'lħɼ~֤}+c7~dlRI񸍟,J7V ,!.=on;[q7$)M ʼni+1\Jrӆ&GX\yIvݔ`Yj6;}u ߶@U /_>~+߉S#mrm:yKn9kŦZ&iqmszGb*vڨ'a]|_c>}ҬUFZn_yu*)Y>tje-w.2LMκSQ-Г,VܹkgwS}_Q]2L_U`L{M߇^(/p]wš3ŏLҕi!ۜ<̱X_*VK/l-{.d0Ѧ^{~:JRą3/]=Júg_tUtþs$6{vhry-;|~:JohM7O~~\:rk 9wߖ,̽bRHƵY\m /i1ơ~K),>4UCBr4.2pc{cRJ"7 aCK!9Y"G7ͽupTհ[Jh[O2W]~ЗUvO_2g!J߷(Z&GKҮXxv/mc#w%ay8ax.f=}ɖף"^S g}Ǯyd@>phj+p=ڻSO/,۷[ _ 0}Κg%t_Ư=yV~-9-{fҲ;r)lq68ÏP6?7lSK7unpFs"%"8\zyōy: ԒHZ5]6}o3uV1Q›޸wG#.zurZRsͮe.#ju׿ \sp76:$qjmjIͮ?͗훸FW%.n ݜըRQ`N-m%[c=Q9cyʷP%vD\7Tכ3䃿X}?D|^mǬ|C*ș{>hPw6^CmѻЏ+oh4PI^: =s=;|w"5a^bś5~cnC螝Wڑ7H[{ݏˈ2v]5ߦ}Goih$\seD]+Pk!D@/7yۣnO7~-$)jfܽ_Z*jj7MXrb$6ג.OLX~GJR<2Pj LPro1RI~SG/x-KnC^֋J]Dz .l[Jmw\݊+} 3KSHZ/. G7m/ݶSI`Kq/-n)$k+帖~HFō>S9#Ѧ?uҜQ%;ޏ;$7:痮?ୗ]vOҜP%wªmxc?i0S5:^߼TvkTjv?˟9!UiF,<*?:Y'q 9W֌xт|x;{ -$mݒaZm{y47Qnu nڱٕ PJ;V{)VN:M=QSd U[Lo G^uQGdƷ<<';+xRB npBO6$OއIO SOr1?u~Pu^)/1sTC).v#@z*et&kRL=#LPv!KJ>{\]!~dA)? $ٷj~dǟ\_F43{=+>z}BWPgզ| ^xk9Γ\wk 33o,>}Do~qҾMtpzBm-< dpIW=͘]^~v7|fzR̝@g3.dbyY?;¶:%Y(zcJޤ-׭|bGF/;;rM ?kbb[-Ͽ U/k_k^/?xb[%;Ong_=\%ؗnmu;ÿ՗;+R`5uM 6*돾3o0r(uE qhF/;3,7F]xr,!Q~\%EC[z a:|?ŭ6ĴS4ժ`W_?x4WIPH53*s`} JLU~Oyٲm/`|6rI)&ke>@3?9)koӖ>yA-mI)$ك3]Kr/g͓޿.U_b-];ѷ;>2Qr5 DjWS%Gso߹5wŮ,.{qnM)%X\[ OmM!˱+kg?>5$MmR%]Q_G8ܪp쉹*5؝_hlџ}_CO쉹έ 5>tnm,ao~}>KbJo^w}݇%ގoݞLKat7%X&c. z*aO^~'|c|G%`~٘FW$w,"BI+=|if6VY:H]^sNTIJ,\_V;".f{CciVm4;\lo[2C >y1SkE2[fz4ܬ-,]R.&s '1:'T39 s{y׀lóg_T؜#yddy]Zn[wEor)7+S@Qb [O܏>ؓ.[ja\;uowe~n64x`]'"!\q'J=Ξ_;Rv]CV M${zE5)A?/{]a;:}d^Q7agѻO}a(d鬠Yshqk]6?OwA-L+?=pFoWcr!jxCCI֋Lל7kӝ9}2#lsml}۝a!O=ܛ/NiYdswΦ;sBw[ƒ'=@0@b{8Fjݗ?m;O30˹l>) RWKH$l=/@Y[oF2l+G%{tdd\ Ib< mH 9.#`[2TJ1LdX.NjLnp+5s:<[ZĵWt/r]Y_8e7]]ɼk6<QS+[M^n(0'BEPJi'LX_z?x٧w-lBFck⊮dğ.kxQ=;&iz36]KPbvǛwX/{;&.M_ 1%5?^rQCWr3ֶ]_ ΫxFFpd}"pbGG7.~zl!DąM ?z,Q-i*ĩiBGq Ik?1o~nʎW-q?HWo$~rӄ?=E~3}bzCԾjJ*~)$O ox&mvAV}ʪ_J)h}46u&Flh%Roc.@F&7>%+$K5R6k2b}(dSj3P.WOΠPpBsT%{_Sǿc_W? 岿Uxi;_gzl?XS7G l7~/˺U ~39{3!ϙ}IZ)$Kκ Y ]^v?&$pjzsTgQ/=E%=خL*L$yu?9-gSO;QGuhMijk'dW}/aaK{ʣ߀ ąT^q4o=XTp7KLZ,R_^{5nzdfMe u[{}iL3E'K QoLڙ .ɖo7td f4H`X[ǃHʶlp O@#zkؿ}ʋ1[*/_GI1<%R 3Ow:mOTgL]a)M-̝3㈻y421O{E>'uV;r_9JN@[s)VO^ap8ƒQۣ> -m!Yp˫qK*1@QoC[/$Ϣں SLcxid''L|taO/֦ʇ֎^kWO}G? `]UZqcw 4WGڲ1o|u}V<ӗY E4X;\Whvm+ȟյ#Wm,bZJ"7nN-oU] 4-OMFׅ<ݏU?ij+&F~G]:tM!zEWmL zO3›ՑMS;[FvJ~k]s5En(t+ ?fG{P 76~Y<6MZ5Sf19s!O]T[+R[|eqw e G[p'n]WNi _s- N޳g]I^yE2{>JOz+̻e_>s ޥ-&Ȑyƻ$=o/Pjߙfmi{㺒to/x=~z],'2}s6|^^𥿇[=_-?V׳` K2tԼ˅s&%k$[]??C5 cZ#-Yn z8 +ӽ[6=zmʥJXU ۯwbaϼHk {%yu CR8 F7o>uQHydV",/?5KOΪww1CFaC7L>v 1dA ,Rk`#feGaLՎ4@ fI_[_dN:}'&r$,>UƗo_. ^/| [|S#pa)t4_U[E#e.璫ZߠfiU֔,/O^{CM4[+y\(k ku98U\83rxr/}׻_SNw\sa륖9f4br&$UUej7l։џ}{d5%҉iAn4#+7!FΘCYnSe:W7p3HMH m}ំlXJk$U6,+ո_+/idm=](`Vu*#8I$axU=ܐMfHgd)i`ղSEO2îȢWD֭A^ʡ\4҉Lp><~7UǝΣȢuclFXSڜ0u\qH"G&S}ւysA{H{(.5H]ZQ $x#1j.7zʂJ,& f u|ZGJBre8 ֪:,]"]($" 0 e V.Y ãKOX˴eYG"N$Fa}5"ejL#t`Պ|o"S+j͠=8DDp0%bUj@z̪Q &pgyMከ?CP|46٥\Cv9yW=e?S+Z̗on2,h8?t8: dpY`JRɩzJşR'][9>y^OH{#Qy3SHzFjS *1-;n\eEK 3:i72_!ˎj¨ i66(=xzW-=6e@uS-]^B2]r}7{eK/3_M2wIvdJM;V->cQԩD %|GUٵol[0ɟd!$jiG{L w^Z<3>ۗhZϨeNQTf<,D vǡNVbg_iU># )X^[5ҙ!cv[x }v=dڳ71eФ֔g<3 QCqމ38H ֭T; Mt~ol(ewh%=qRMYxHG ݓA1kv`\Y'6vK:f.@П-kY4k{V#~IFZRЫƦ#+QIڠcE}e:l{V#cq&țя99E[֕iI z SRO51E~^0V%idJ j3Pi )p?;Wj^5n)k ^LJZ۟^~-CfmXA > žģ7ʂO]lv>+ynb\%.UIpymb*b~Y3sgv=-^bIly~Reރ5ƤIBE9K-ԣy*>uٲS?zF5b{]Q19G%:￁"Kdm_wmA^ 2?FCx0C?pj J)W:E٥[G\Lp!"M+Nw74/_v',[f^oZyUXBKF.%>-\<mysm\?YtiNr-V2İV/`(8$3ݒ3œz{fq|ˉ3w݂*[ yxQ(D>^hI4k 6ۆ_Ma)Ft~}4@b&ZKvM&mN IɋZw 5,Ƣ!Ɍ@` TdPՙ-W]փR~2}D6=dYivʽ~t7"z**}I'Wf")YӿX@%*\I'GVvKֆg+4Q16R$ݶC(ڑܔBig hюMIW['].y>;Yll/Bɽ~|7A)s-,+|'m + ,ѵ};_]Y0#_7T ̀o3S^+erDfs-1Wm g}WaG+a7$^?u|_F Ts:I :8R9xu/_s%>X8(bCKv|os?~WҺ9?5SyD`pٓ e%HJ,[:|}?ؾߏj{FC+AdeP fI2Q BH^:EU>]ֳKyg웡ߪFmkWD彝dE JR@<Dq-nx6"rj ]ڍJn6lL /HBZ: BEy5l,\0 ;M퀳dWSRrMPp?8Nd>/1Ћ?kjj/AqK dKTZpo #dkxJ5A8BK6*JCDI$Q 2>^٢g'{EtپJ8=5'a-/potnMOZ-s/xnd֦+vK<,;˭ ⚋vKּS%xnd͵Կ~Svϭݒ7]v`.&ƕ5{#nIȑI}wᜇXk$/{pThG".8mwOyhW'?5ɏ[숸$Bą%:q廗9Izs'2)z?Bͥu2pf4ӚcE#'ÅLFe\̾==* L;xeeaS.{z`Ԕ ,'ri'{}!`^諬UKJu`(Um &J˔i:.Гڸ@Ѩ./is\@,1fGTaEA6]ԥ)E 븮FR\=e_ʿnn# *OՒV ;/t[ՋF9#p1e []rcflex_mWweQnӔLAP̪xMe2^1Z:늗1VAD5~F^P%H[9>1Hr޳@{?x 丧__qGOK.2dVz]#I+PY(ȃB[2u?U܂L!HzZ+!`҂g!הCqç?U9?$VVЊ%%΅@(,ג ?X$cR\KJx^>8 .1`F~`ڎ8 i%p!AF &\Iy^#̪yg& {l! 0x_AY/|. !Y/|.@̺%pے\՝"fv|iLwK0CF?0loxƳ>ψFByۖ䝈)1K%Ӗsw"xB;~Y(Ē;cIމPț0~v;~"^Y' dϫs\3M$Kڟ؄ײh]F1!C'iBţFRZO!Q\N|$!It> ~ǁ.@pA+TT`LQ Ȫ,* D(~+2n6t SH _ SP&U1T2MV>T+&2J#cJ#],nXg8QT#Ɋelb4,lDtDTQEywp]beIGh!Y )XbDpEa+2@QĔU&lPm .sw̾t8J$_1 nM?21ƕwaV6ȄEۗdC]LkbzČw׺*?ό_}K2%,%(i2x7~Ԓf‚&ͷ$VQO(Ē-!qYKtH,.h7|s`jF - ޯW~t.sV2v)$F-Q\!*C֒En:V+ P+?p֍ZSݠg+U;ս%Y=*H)gQ u _cu}rqm$Rmuo 9b?q5u _c5}\VW':Qk#zlT'?=h+^--?LYS2=Ʒ|5_]jrlV>q,5=QWV;߾[2g]ybdyL2eQdέn k =Y:H[yIoP./ Cž վ㘬ˆ@i#/08e#yTnd$)%#2F-rE; %UYP1jhL| DB=/bҙW-Bߧ';]z՗%[ KPALN# PO*z ANHv ojp+USJ_cE:CQ^>̺ʈ~ `D[BBؙPіu]ǔm?1֘[̸ʿ`-'Ajb^($;@9xV$ȵbi 6o=r%s[B!ihğ/6Unk% 0. ЬLMby|6%pcZ,Q"o&!W+H%<9/)\,s{eiQ`:oŠZk eQlJla1˧mK|^`x$V_;v |i/oqZnK.V#_cvgzґ͛A/אڷ'cd$?=)ﶤJS8H?)ou {Q殝Zmh}o?)({o=U!zG# 0=n9sN#!@= zG3 RIT6s8=iMs)B_RuvB HRW FʊO75G{ol_uepAB '"g$u1#Q;DȉI&z4VT]db,0)Ƴ e;ls{q9*M Q̺˲T[d* \!U 1sP߈wsh5tRIpt?.T_Iƌl["7H04dʆs>3j/ #)/.D͐S9WcAdg_VIB%Spv[vOnmm϶ g5V}>O.́Ϳ=[3=[[ 9/>tn)^%A[2 4^$ zy{ؗܛ z㷶U[%w|۵fc~ =hyI;ǿ<{l3[{6U+@@x͋fkXr^|OH`zHNy K)/-[zHNy4[JMϨ0Zodٲ;ܺ"TOO1h%]*+ln]k꧔tҧOG+KW{z'R6O%RS@$yi.gWJO rjdVN 5Gxբ8\M\O1N!A^W WÈ: BTPȭ܉Oy3I @نT6O`k*bYd[v"#ӑgVM)QXu|>59Bg1P; )%WJi"7F&DE\,"[?bb ),Y mC\R5lFZ-i3q[Ԯq7\K:Xc`A=BSe3$Tׄn{kvgjSKNLoZQk "8AB{al-$$" 5}~{ܲ=# 6C:0ҧlQZ]I G@J(zG"*ʂ{^bL*w=GS& zSWORRu+ 2r1TOGabTBzH`r [H$?v ؞ _0 rbY; ;I*I*ܴRRkTmDQZIzA\j;AV IO bew$dnm]wkFwz8K. % ^n8uW)"%PR=qfʁ{85#i{.>,y}-'Y?)mH,OBd~Yb|D?A}n1L>> j|v[O'aΙ;)$[OY9sA (mɖ/ HC9q2|U.DND܍j r^{Ji}nSrgmҹz>AܛÉ'ٟ~_P &){W?7u`~>O7EZK:Oɹ۟^~UOw:ʫ6sD0_w7&Y (sNM !='a_.u^|./Uɪk9|^Fݧ\㩢+r>'.$Ed7ѥM:Ӧrk~B74l0ą|Ǡr%":\yD*एKE-ss)$m>mп]7D󿎺3d=.fU/ff~ HR4O堸(&N}OFJ`{%-&r+?%}PD_OKEܢQM`2R3&߼$ϼxum]'/δڰ>xo%{x_ v!o 3K'T~l{_¼՗ 1u}ֹ63 [a1tIJ>Ǘ,(fvwvh*~g ߰G- /[kxkdteq_|b* c_Sԉlj@D.[X7G^gM:;*? 4GV]Y<7CA"$q׍Xi賳J2b6xsD(TҜEOqzpQ\%DlJ$Mp><6<0=xqn6Ks xqmN%Ffi#C" 8Ȯ}0߅8W+m 9_J٢"Ct;wƹX=Yd6Ab[B}vt63sp5_9;O|]Nudl_,H@K\B-B{,滆B=NWc>uUt tl^8ɟ&rF6V_>r^"co?驙pڷQ[urAWRswD_շ7G^Qb;~r5 o NN=Aǩp]wK]Iɪ;Ͳ'>:v{{=tat+uEUp[W󑥠 <\XKۺA):8^JUoJJh&e6 ˃VuDzf=kB{ԕU^z.r,/յ!'TQt^_]=d+յ'7mP$K祒_mX=!1jt /Zb$I?㡥(#۰Rs뗽g6S:v?4|+woͿo]1#9La_qIv#$m]iSp{7?2PcNjtľ0-{.{$բ- cywv>=#wJ,?ڹk:>E{ym=xhV*rzvlp';!wvVN5jWg;9m6_ޕ^}8-X%]}Qie~XrOtNMQӽïU9L~)vYmi @> 񆲛Dqvf85GN>aUxp~@Y/;0ѣpMm/-wQSN]0.Kdzi$ߨw T\iՂE? @h5k=qbh$Nq7$j]UɱgK 749Fgt%jXglC;|( MyG B  zxтZlqsVS-'g27}0LJC6{,F榗 Ah6˛o->X6\uÛ{Ꮓ,5S@!p ýaY<;ʘ5$¦wn?<~ HKYYcj˔]`YD`\Q0N2iȻ鳕ςͫ?MIz3 QKS-%x toPS[;ѺdT/nWڳ =gTM1hmԢ$YYX7+R!=ceC~%:m1ckՑޙ*{m:nSDb')_Zew|rem3BI'q/ݲ yY !2m ۜc$Mi^m UYWs_D8;jڲzY ${HfFtxϨʮRdhsU R?ܭ#y_ƙ3.)=GOJ=CR'~wdc9gHn{۹6yF ISV\,%8u*\/!;ws7| OG>ӘERu/Ziu"/?i$/uug=d1[]__=ɧҦF~UrC3~: {##6Er\gL|k$u/Zq{?~{7Y4$~WfXn$5;DN|3ְǮy [ r2S{AC~g촻7?w .m :LAfxe KW2 jrx?1穻 9܉e})z]&IřhkWp׷sAi"v\nɴm_R80".LL!y. S$kA^O%i{/LN9면CN%Ŷ$|n1{EV{Л`&3V>߻Ba_rV͗G=qh?RhOH/Yς7l{/7h_e偠k9AgW- ꏊw|K>ob-hieÔ, Gdv > ;PLݱ,mtg~7') خmyg`,%ZZ\ۤd;ڜ}Cüs@ݻ|>]g}!M=Q砦~>SGMws7!SHtdbު+tHjCrۺjaf">C uҸu6Ir±G$BpE-%&e'SYZ6Yi.SuFurPwo`c*!SDɞh|'Û'}G\ (h `Լþ6|*&:S 6٫oڻ Ն:T"΄]I_?q@ CBXrn}sDs_9gC2\ veLa+1R7Cid؃ ~*Jm n|nrum_VcԱtw/I1: E(d(b L0i?rOt ;ܔw׹t)M` U:WP#n|_sJsۧ|oj)+ оC(?;J0q="ɪ.7݆@{n'u-5߰"~#"kO6B_TJO>n/_W[y}EϏ}[zGUAUjy~oA5[K'ygΆ+A |1}ɓyѨsf^\=T\8ܾvx*6B!dˣ&ށ% 1R,r*hvdb{i$G?W5zD_yA 9/Q)@|H Na'l!dWN8dKl38JVvt}!J{|~nyL6&I3Teڥ{"tvrYrXZEcFSo]/TTU=#+{%%bLWDҹaɋ;_zL8_Gr͵wvUgdO׭m2II,[NrSDl\`b!?t47п~r^ w,Wf^Or-UGqhG[Nnit>X*0!}U5*.$i إyC]}Q 9g2?t`vn@rw_kp9yeXIVR{^߲Q TժkɴЁҍDwމP]Ik'd:t7d3?O(tqkk?FܖS?*\N4awo/~ `&GR$CXH,;B،9iDIG~ZuVk.<7B>r*Ѷ5yز x+6 d.ur:|m7?ᖺ#:!cMRwЖ}-gݱ aę/x{o>%\pg@Ò-`ę$G;+o٨u{[pN;.,zң^~n+#N +[6gY9\VW d= LvU6]R2%8D2y4Kriz0TWˀS.['}:EvPG(1B##t %~xZD^E<8cjj 5 l5"c#!?ԁJ" gex N…= O/N4;?[LSi:7IG2٥Sb<|O(Apby@ :FL 苭$Vd/>P17('}")$é2T|[7T $?`$-? ZH,.>4~s-_j;l߂+ {v*s$f@ہSۮl<2ķ.d%KM=dd#}7_yum\ٸ’ޚ /͢/?|y[N+Lm,0f;{ 2%%˃vjN:DaE2tb x(umۓma >j(9,Ko;nmی5~>KY|uqҹDz}ndSkQzJmf$Y^=2-_'{==dFr FJ޺OU2䥏]z;^*()Joki5滲ǜ1%|8)] FI?ȟ_h0;}no'^k𿚈]$%IQ.Z噿 {0póZ! 9wJh9/gnbC;b% %T" g2Xz Ifw%j9/lS9k#")%>,@/PRw~ 6H)iXD )jp_bu Ld ?pXWNq;܎jKM;)3H'#(װkku{^.NUm޷Ϭѷxi(WX<C"C2wcz)=s7o]G 9ݢIlϢY"J՛, L9ݹ{KoY1V멷|ojs_rwP§3B)* 2d7X{;e`MVqZ2WU+Dnŗ ƒ Қ7VVՕQGW~l@7S BfUJ1PDJeNCbW>ƮM_uAT_g:V*b)_[&QZ%SRl/Q&Fl`>0 RXA@BD&xf(Oxܲ ".P"DY ey&* 6_|* G%oY80?ֱ,քfa_wû^PKJQcjّt ̱YGO- g{]"T;uAG7t!q(2{/ t?Pht%-z9*7~$S,t]oZ_Q(`PwI/K1mԁ,4qՐ<(|@nݶq4,ws`)W-`NБߵ1<$-V\8%$kO7ăY ~&d'ZSn u6o`N|:zJ2{1EaϜsw"iCa;1LURPf$>Z̼ƺp&{NV#{1+Ƴcf#Z.Z% /OS~Ak}?27|0N~x6{. f"3Zԏ֨':D6,C?[ťJ. [18diXLP|$7Y:Yc]EoWm~"3CXϸ'j ~~/EUA.G? 7V$%I[<[{?25\֜9H+"kib&J+tϲ:UE|,`$QM19N"g>bhF͕}ҳopkրT߫JUHX柲"t%CH2:y%?At%B/-􂅫pEJ+9&_93GL5X2ILLDܼ4%< \`/H`,Ԭ֫qpUV\>[Ko|bi|x߼mgǟ{vVOTo{O,Vj"2HdJg,nLtg>𚟜W}KL 0!x 1 $}Sٹo4=_IP+\8BśXbwտڦ>9JV9F_w,y}iuf$jaiËzg^Lg%f찳:{|yB- {jL{x|Xp'vQ+) TwjK*ޤG7'ɦi;B[,?T ,M^LZk 2$=zeҗeF3H10DPfFhȵ(XҊN0U"pT H+E$Dlv;Gz0v@3䂘oQ1"Y7 vOIj]p˒a_mENʂy '{$ϜY8ȍw˕n\ ±?\]M|UlLvоQ.۷2z[y9ً.X.Z|Xw=;b~MG24M9a`ZI 5ٺr6?+ʞ-vxoROŭͤul{-\{V;]", y)OqF}E8m,m]^%|<2גS\9y$S=- RAvmÐQnDA<Ա _륌/"."_]Wef \EeۄgdCjm ){>,sDIۺ.|cN'?YKo+`G˶ "Bl˫~Ȭg.hZ5]/WWM65'Vw]'Hv[jN*װ`wn]:.lukݶPtCC:1kt$թmO&}X.G{^86FrdjۆqW`mcw %%Y#(@>B^YByVylF%iv[: IHՆvZ矖EnU3,VJ+7MVհ"\ [wf&JD+֑x+ P AI3HV',bPh+'k$O9=dy||pV6VQ]Ha}PWx>`葞.Gǯ"`r?2}"N2%PDsO͛x}ѕ (lgg/ 4ˋRz`CS~횁e̓xLUhL+brAdĶ v9MD b00xL{^~8 -톬ޮW);o/]OYZO:g$'O>( 1#zJDaS g I*$!ܛ`HzY-pXk+-vr j*6]^֬+`}* WúuVܨQ-VlO ]M~VK=O~*p*O{5á Dbn *۴NVH N=Td0F~+voݰݵO{2nԍ{-mtx}S.vݦ6?VSƋ{=i/KiS'+4۲u7xZ]+w)׼^57C; =WB}AF6D^Y]#9%c}Ycz9 'Ky.~mo|ᮓjKϯ };0-1 a qɮ͏?! f+hADM3-ґ$b"D+{ ҫ7z]:!C4M䖕IހmWxedKGB7+s[S^G6썤Jf]^:w 7de*)ᵰVH`D!M+pz{y ]n䴟~GƆJ†HCZT;>'P\}~WH#yO~_w$ URV$ܮ*@$U3Y=.ݐKww>G W7N,=|ث>q0 ؅HJnkE ?p.\i/?II];ƴzVRhqwRDygw.ժCGx&ܝp ;sFR%sԷ;HS-+_ Y޿(lp!sJ\XBtK5f-GìP:Kv  d g?[rly-G>YYVoGj:e!n\r )7m7unY[];Vr )y#zdV& k̺, :B-3I1}.ߡ3F]_cXE=jo] 6ik5Ǎ㪂L9?sԲeF;7Ƽ>$tp^p,nXM?%dM4nLuHbNzUCG@ŵ$t\EhX5Я% XܙD(ǡCϹc_،'icH>́%ðɚۦ0Bmg'02323Dq+͢B} F;sfhBXT++.;tsS+Z$mn \ E*cw9Z&)-_9מ-Dz[v{0N tK**辿>&+ֳH"_`` IzH=k Iɡ)7z@*ihv#B@*FδfV./KDzR^z !]-lϣ,.tYwL5@N6/R ; =ŝ$&{3ghz;I\Gh5}?5d䠿pRl1s`{zUkof+1Io$׫Uߐ@nnh'߽*HYq^_6ASeczw*zEb} 08ivpoot_O{.ltZ?7lG+ |й㧴eDy?BuPMCN ˸\r;5$2pC 7;֣k3 !| HV"M Ѿ]:S(J^1-/ߢ|0m0sHvflBT9mm=̒JFLl!^TMm/_U"%#**Jc̊[%QgsՎ;Z¬*3(ycjhx%WV2D$-F*E;B3pa.m[F͜Q0s+)sy: 2I*H`%%I[Q6F ȧ}0̓%АĢb7?Yɴ3s$v*'s]FGXS{$Y<蹤]4deD򤖌dU $zV%UGI70[bj㤌L$xn(=9i3-$Ļ@5k[&^Mbik}ԵFV ?i;Y0ˀ [X Gl2F> $"W$753ĥxnY,]';3*൯f ZL 3*AK(}-ʬa`˛*њL>zM'|(koX{q:f֝`|RW|M2NCEې6+ "Fx*F8,G"I<0IO+o)}/BbcNI =BgpBwP&z.m@La'V@!m6'$k!ۯ36Q\YO#8K٭t۽m~ * m8R| ^o`G?zY7j>mmf^L8LK їӚþ%tא,߸$/?ooYG$\$b+kw-iɔ{ZzɾY`"aO J4$ueA0lA:#Pb֭͵;߼p{+O*4FZ.40D`ZƁtPds~[ɶYM:L>)$!SɶY SKi5[{#SK- Z !` H"1oVo+:Mĥu1f:Aksn1`-uFH :'721 DlW:*$:]8 b++"RY70H^p&rfXN Ջo!Y* ނx3 3SULҁQB([@p 8!-_( OLbZkhzܰ,juOWX4u#MB mڏi,qBDZu :Cjuv񨕛75Q?q邺ߚg]U˾t+qղ{G_LU鍝nόy_c;,'&NT[b+Jrr-+;BqX5h+Yȇ>[Z4?fYMkyA+p/l+ou3k]p/ecPYeǏ=d N"pBلL;(^$2J!a*%TY-*ZY5 Ȫs,@U+0(lLJw* 'D.1$F\ l&$PB9 6)J) eil@BoƐQM;06@ I4DKgK-aSۜRD|'. 20or9 \@ht=x|&Z mKI6 "nJ X(s#!`jrׯl/LTT:dChJ}ah,TZhەNIR.RVZ[o&.RhIR'n#[%$ Gvbt15BKC FdʸRR:!v#aK+]*Mrt%l )U.#6E 3#*-P-(rsA&MoQ^vr\xVڌ'bzHN;!$HL=Тڻ3)HD(IhE\;ilc$8r&;+lD|CVaUfh ?0/]K"l>UmM]=Q|. DqՎ$ L¶.+7fb@Pt\Vo|%Hbes`b0wOnUXNvWդ)ϜgiG;K.'=~e`zuنy k_8w+,!,1˵ZJVg2T޻]*[ĵϳRC;A% *붘'D- ga*58REfV"vajsT%rCk1(.#@ V O9!G!#Ѯ abT),İ#4a`*K\[4W ^x؝pmK:tb'6m)_$y{ v 7e۵BkwmGt/@@Ib)dҔ5\VWƲBȷk0E=T]j>[+,>b$g(.1Ro&n9t$촕s Js3: 9kP>/4 쑀F\ݛ>v_Wq ט+^Rҏn_W65nߚ-_9pmjJIqzO.W6U-b84/g%NPgmzaTrS+_=cO_kT*E. ԅ, i`*D2jJ=K7 TR#.PY͋ALw@RM)`#RFd*\QVCMkTHVmF2> smZF3Uv'5@hI=Ba4DFl?$%IIC͉29k@Pyr -62* 0Kg20$˒"QZA]IIV2* ,{FDQBtKbFa|+bdr'CfQ ]^4g̘=H kH G0Ptё=zHf{:jIhU_폍qv<{ܗ_g,ίjbuNCsNj1h"ftAFh+Xe+sn"^c,ͭbLd%nA&SXMֹeKFD4`~]u1C0w\5ј6%BL/P KʩNq v!З$me4ĉ%aBc@#Z-9_vS{ϞY B;t.?+ A=.Od-B&7[T\׸Uky)cRf6\^ҧ6=iP{7yfqAلJ }c%%~_βzF;ZTvb䚼mOI7ЉC ro:2# HlR 9(;Б"`k1܄%)i_oII;$<>Gw_o.# 0@ Ht)>p_;.LBH Jf\hzXZ`F~] V4t6I.Ĥv,ŚaTপ*FU-83bkjͭbbT&/)6*pQJU/82I uA)*S_Tųk^%ae*2̇IaRljf):I)4zIf0cscɔY!d.'s̱Ya'<#arrY nYht0`BKP$X-gaP0ڷ39Vc43cChgP͎PPVN>!o!+UCx1\3s(:&|'Y !AG2.52W$(Ie07~zM;~"g(aPJ6#|T+~Z7>1i׏R=Ѕi7mHJiByTl#[Կ_M' |r+ tׁ:6ZqY_?<$B@@|[LngS6fSO9aTnm>H{dFTZi/\gLb@kfi y޲=N&oQ߆r%Qˉ}7W KNX@@l lĀek BZWHhARXf_oӒNv~SEgxAͺ1*^t.n?>ܔm-ZL vP{-*3KuxNS"/I>SZ^|g?KTӽg O?}O}J=] i;؂Xth9`u@EDnqf!zH(PG9ꁛEO;A}@]q穻-QU?`nѪ\\t7$Lx#l>zyGɉ|شR 8xQ~ajg[0IsMd*Qk@!@F9v@#qBA0a}i{=>x2e^icJVAގULnvL&CH$v."k uIFһZ0صsFߍԔ]}^?i Ǐƫ;KPſNG2Z^ {ٷL\Dvfŷ&})wxhYin1j*@PSL)3@ɸ 銄* j^nM|Fr;w4 ,;rY oSmDEGQxNl}$S ( g1aa]ÆeB~kO~hBo+tZ.ѕx3۷ Z`"30n> .k!gy,bF!CEDD"X"f+#o֝Ӓ1ԭ"%L4DT^w`*oLy9,>γ!'~6`"<)կ>e+Vܡy%MڊLnd.lH-.v# X1]D`[WeH`b=rqj:#iLvM Z0{(PB 0&dAg ,[zt쬦Ou w+p 1% F. jmPZ]}a=k*@_&|ml5mEP];4TA %R\*@O`ʘu6mkU 6%mUyo]83eW"G/6O@ < )f0q+` mesN6c@8XP\O:w6ntv4LЊ@P1%#nnv *85,q0s񴩓޴; fHf6e)7GNfvEDR?5ws^ec<#koJj(^|;-^r*k7^I-Z$O{y/6Eݚ ;pz!oK簠G<4 8/'`=3->6%2O E[z{Mʫ)~u;Bˊ2fi@L]# 2FI H$"cj_5[/fёདྷKGo{=2A3~^!qeeQ tQw )Փi$p`Up/E 9θ햃w_]}Uҹ[b-fzzDfcф9E+5B j6,*xJ*L1hӆd ̯䤩Gy6mr!O wAsej^ dRPҫrN{0U8xĩ!*bOR B@i*jm j7Qf3 3j2 kOj&Z 8]<Ι X әȯ&P@.rrA06@ p,?mx`_OP*q]$ڷQud%+NWL1͟558N-ч[>= +c2kw^G=-I|MR5G:oYŪ6sCAxw?ifֆcL҅b%.K5bz/(f FI1nwS5i$*! ޼Ib!" TBjx5 =V){bqr[aI 575Y6Y=9h`ߡ:v0g` JdbxQheqT^urЄWSFmg5`s2wLop^&QDIWn|B+Gx%^}yzXm>'׎dh L1e8Џ?- HH rjbS^`azz7F[|(;v7w]lh+kշ}|h{[?E@: &K^U(k L*.դ&d 8l]K|32D#4'oZvHk=VWk W[)\"&fCXRn?ѻRLR}a9&%R.+|(+@y ۴@)2C/bufoW]zF7iA8F 'hin]aTM`=j ݨ>x( w$K w^9M7 ~k,U9c6@xE9vV Uj5R#[Fݤ篨¾{=3|_f#Tb^7dp1fE&~_.ftb # (~6R(L'`x/J a@C EAgS(SO31z\@%f ne*|݈ ždz 3i73nnS@MOJ='N7ohO|7o8}EWcDU&R#Xj;\T$V kdpyua(}s .TY?=vVFE_6 4 {a=cH!=_2#/y&ʴ,x 3iFz4 81 DD" >\B(գtq'omɯo87*Ł `8{u[ CsKq~ؼ60Nתv(B$oQ݇lc W>6'' P } UUϲ \OG~"NzFكu agv^l/qyZYn\ ?Ύg[Qz&+4H3V;潲'*sVJB 4ALh kOug=r^SN,=u5oDc}9 Ruz[MCuk[f_bCVi ba>mLN2c훱'P~ɍpkT\G]Ne7j9=78\W2Ķf{ st&'pIKW>_f^ ]ט!~hM^rв4x&LӶ\$rfO(U&\k8PSX,$$%>"z«iVL3%hg* RVabk~dQ7YpZyca9w_ {pG#ȓY(nbDbʂԒ>x+_rݾj!fo9thǷXXO & :KTĖ+mV Z}!7wux3 ռ6;]#xquM)@x e\ZVۥU:P\c -́J̈́Z4? \cܗ +P,;p#(b˧½Od.b0)ʦMOy!? BSC̣{ _NozL'=o`XBuvIҖrN;-U?˫gtȾ#cfOyBt;-\iV~c)ͅB}w^r miB )mW޾Ek?- D"$Wf DD>צ)Y8>B[>pe\hdzajE1Nn]7e/^^ `kaHj?:I]v&``0 rgQSϒFs,W'N(f9 B,9.Yo}F$j᷉M% nS@{}Uϫa7?9ڿ~xg/}7DՒ(QgF8M\ k̯Z^zaϳ*}3~ejU.)Xʜ2wvuel)[K'1:"tF}@B_O H@p\8H`IeioK, R 3x/`Bn#D^eip=%U7ϳ2˫i> aކaG Ӵrʁ-{h{pPKp2k9{qV q Vǟ}W,$QԙɌ͔(ֈ\  =rF8QR,  di;08YgC*aL@q%^^ <8ؤCgE@G,H@{ɴKD ,<t4̆slT*ԃ[ZTq6~K{T<[˓p.\7ҙR&ndT&5YMz StT|xͮK9_E,lA'lR?jضfr~?J@FuTW6yGV[CͪX eDo) #*"0^"Z"a6lh#z=|8'ي=. 9"h WE$O/?Lީs/uJvZ8ϱ(? 0{=;uuexg{Ц+Uu뱔@w>&a@IiJ&Da%IX!E7??_s|Ix74 kxk`tĚdmޏ wA3w?oox%"ˡy߽pC5v4Xh'vʏ~Qoq꽍^im xcZL 㜂;*H勿m(7`ωt,Af=8Os-yb.k !δSD2F){YqxLeI֩g nIs[ٹ!l ZW7€9 \rYxE׻Ϟe'vFIf5|37axҖ`=Km%1 tcKiْ29193>#web nt${D+ۄ,DGMs#a3=5g!{`A3fK0paG8VBezJOcS " mzNGg܋oID3BpDNEo/\\x ĀHן3MXQMVāl@`tWĈaOʂԒ>x+e9gx33V;6,)2sŃ/z#a8i_p}8fz LPZ[۳ Kk1G1> YE"%MNr}?o/XpKHXfo8OG'S5Q 1ʏ :ӖZn .sqe{pVfanJP,*& \%*~rg8+(MP⣀]p=`?-1TP`.wp.")Ξ_t`mS e_:e+Eš?Tm-B~_Z-眚R3γ8Kl:_@N* q` 7XL;\'.':B-FNڪ+8YCfU H, =%d}9M1+*w ؐT #aW*8DI&yLLŸ/~| gm:ww\VǙc ؼ{]c XW.) aZ| s"VTicٹKok?j l&~t $rfB5Zp($E d~QQ 4͢]gې+0AVTX–\h̞ǂ埴9)`X4D/!m)Xǩ|~]__^]%}!kVu&AˑFyϻ/vক%2.qMl(0aJnsVd͍^Rtc0+FdVSz_cFčaQEPEj4o" !DN"LVI!E<j( "m P2 -z};mo f(%(0%%K-B}. 3CbiAkz3 a5mp, 7VR7YIݹPglp4@O/jLCq+"=ylL 7<X-*0N1P{9<]DBU3# M.;:sẁ}1Ź܄bD0r< \o3:7}{w>"CqƟ)rq_yۼzx_˱$ d3$`Y"Q]nP%%S)A@`"D-deQG@2)!$IX|F$2m=ˆs49[I-PjWE״ƱinՍ cN(8?ZۈQ!K"ղd}>Kiu0A+I#K.Q|R34" yQo$?0We(Q JրOX˼.S7È_$$DY{XK"AkowG!"ºo`Wv5FVs*QW-hϫԿhq]rʟNsi Io~~5iJ XLtH툋e~޵@ϛ89PVACqiI&EGp+ 95sݛ`3Po[Z851%"Dzk`-$;Y&[Rޝc0g  0PjXlS3Wp l<p`g5&z99G|&!c튊 d}NoB8Ñ]Lɘᴓ^]*%g} nH1pRM܊l7LOU~iMKryu_S*?xc76=|O ]?}t`J3+jga:*/ QvD< g+}7( 'PqGk{C_{bqw?T񙮅Zk۱"+#/ff$9b2 R`Z+^N<(M|BW05PeFv|Q-+Q"4` Ĕ]BJx>d3߳EOx s?klάE88mź1QXmMI bx} )!*bm:BI8yb2Ϡ(\'M(lEHmpFvABRGbap4HBth*{z֙ FIYyq'vI?mKĴ )WQO$sxFo?_^ߠs׋Sy ]W_?)4VfΑ'V D|1^:w?uYy)vy&3Ř~_/͗6/ I΂Z^S\|{/)t0;R] >>wnY XYPh"u ~SwZo~WKwWPR h"%;&%|qԸ}ٛ_6EC-+odb(4m* r?{vR S̬aP慂WIJΎM̮ڶLƂʇWW]o:`l]ـ݁? ^q-%@X  d{VLV̪âUk^5^!4cQy' <M8">e 6b7l{'́c!f[FFgnTPacC^o] nEkv6y!`*DG%&wWpXp)Ȩ>'>8Ut1 C.A ^e?4w-N g/焧D׈4'䴺1? g߲8cq wabv7S'Y[T,V:bv;ڢrF.urϮ'|-)״S羫*~e^dg> ԝ.m =jꯌ/:i;ߺ0ϝϹ!Ww[^۶%_V@8~]CFبz.¸cd("u! `RV(cCd`W-,^yE]U7ΛefvEobUV@Ԩl`jNVXT(v@e ViWI`'\D-X` U@D%0X+cGƟMSddf#N>d_ A %7\ԁ#ES 6njs ':D" @<9]oꄫh,abaԧcaxDg0ٙ=h_D ;TROZ\q?fA9oS=|*2r}}`$\b挃H~A˓r}l`@]]LcIy  NMU?'4ɏPV_}&󒙫Dz' jU";@w;7bqlS-DH<0F;C,FR},W%]]Uٯq7jmiAnHQDQvENeQ ^,̿¹JQ}}vq3V\#=Z{&#Аn&Zl(JV%Ԓ6ge/<wc 剝[+YPI̭_E" z=^Z.IJqBJ=;^e=AH6n+z筝ijEaH o1uE2~o/f aita޵oG rHHt#/ps^=[ g/ 楹'3$.tւ φi. כ!,vu  5"b %dVǣJVFXx4OYt&@lw7 ԩ 7@ `G6>xLhlDspe"IxƬMGDYo D4 X jaauﲵrǢu?6|3#Lo_[t'1Cj7$j=FQ|PIJlro>e9Y#n]$]$c>Ɔ _#-Z:GZ ݺp~xG?[`ۯv93ysAr],&kѩ8w^apt M$}@E XIHW8HXlkcpYeذa:"J{3l@ ohb|3s9rM2֧:w_}m*lY+#su$D5-$:)Q#ZDۑ)8"0rAMAT" XUJ,E@ʌBOaW]E"R.ʁt.6zn|piʯ!P3~u\>YeιL:5c Du^ht}jF]=[~[#Y''Z';R27W9QX z~Yh!ZH`%oפ5 Ég!"1giyN}ǫZ-/5`^ AjeujԐǭ0G_?##k/&Vds-|kՆ-G?ꠙ0iǴ41o@Hh1t >r[/?:7rʍ o~^fX!] E5.FCw9ݻ烧lWksG\zuu:?$~)˄Y0m;i:r>7VV 0R$'a}GF=&[^W<#gˣXqa DfƫX[ q׬w~zwX^@KpU$in W|%Uh{ԴOph&D3VSXLmua?-V[S7=uE EfXUS+.CNjٍJ2lnvt0g XgBNAQ1z]9FDfWkfɩw\o|O#n^2Ia[;FֱYܽpHcOCE}iq9fɘ͍5z{6UÃ簤 ףnUK1 z,^{gn",k ^//>|IIz@kf:(ha?$0{ mBB룋 ?}⬩}mq]rin챊5N/^‚v8rwÂÄX!-mX S^8\`^|+=S;ϾE#I@M C[ N.qoni7Ne N]Ԁ1tˌ #kx;', BOnf7H@) Q$b,x#T$t1qƁjH tp!"~;ځ *P#I3A\TL5Tݘ&zm.DÊ4_v[S벰ߘR&3VFz8ŶȞvY+OqU=幦n.cJJ6ֆY%&E˓bFPG^{^WЦ6f (r([U'@,97Ur=6itr_ܙwF2-/H3^iAͺHeZ}Uվ|pȇɦ^Y/}^;Сfc>}j:?iĬikg[msȸ"_ޑS~ %jqSAX'\F'aAjXug5!V<[QgX?E]!Y9g&PAT93=FD4:Fč%M^M<5<\{RJJiLp6̞2x 2䪡޿\)_9}1N>\jU!XGcZ$NUcLX{ MKjn‡e218͕5{9կ)04x&}0ŗE>a;>ƠblMO$4Fn! Bvnk*'2=o.$ǃ99@U97Xy<_o{+1 ?a@_oJ޹skIeSI_Hb 1F$k4tgq:Ҫ(d 2Ԣ7ϥ,Pmyq@s>*ЩY0uQ2ِ=߉1cqI,@4o Ǝ* BGr9_UK?[ RFC66/`ʁiGcn^՚Iyv&PA|&XF蛏tKp9%K1:Wuo$wb ؤ)Q FƖUjYv#mK`ʁ=53o>XLb.€X,=s޻zS_z#{>w&ſUbpUo; 0 t2p{#p*%a0 g3B^XS-o xDc8QD (&@wB i=.l3] p[N0"ʴ(Ƒp 9,V&;> ZGbxYGT!$͐Nd?)+mK(nrRQ4d)g%l"'t.{QU~< /Ïөg8ԣ,i|y-@uXCE!A<×3eCG~T0,TW <,ໞy{7poʺ$7ўlOi vLa؀@Fɓ^dtyq-Wm:~?GG :qc~= r_(_O."j$!- NoT*yRd)I * U8AR[ӎsPjH0e}&x_aό=?Swߜn\rhg?>cC1|ԇ 9`̇FZd!Kn; f=!s)!pa l:o`(%o=z WtuYQ.rT~DfA PZ%A3dt(U-YVw<̈z+K>K&S}& 0­76sgf٢o Mt` XV6w풯ѻ}mCf[rL`P|8^힆-֚ D rtLsv¶Āk+X 1D8Ew?%`%&a|c0F" skY(v[: 4V:phO$=\vY,,i +!a`-&f$LI`cOQ 2 j*|t#%H-xj—d56In uDd1V肑i-O*YH#NxYօ6"Y,7ޒh VkڗE {TbG$C~[ۼC.:. p mpI >p#$=DޱCqmbi>$]綿9F? ?~n}/\x¡|8E6 I5 $,Dfm//+h$ 5*%c&W [ë׈yjKdՀ02Kiyų"p!BR|ٯ> iǽVW]H2ds܏xjNi:jDSpb|[N_}3ky4dN iǔ\ATUS /1"u{YF0sczFq2}\a( n/{(sC{+L;k;>jP">m}BSUX~ n`}s'@1R=q 1$nuQ[SzSlhd1Vt>"zvQT|qO*O< AOwofZwY]wQ@Li+82/Ӳ,s̝Tta|Ys~mqZ]0ю8<]lp_Sk5 ]5RֽԌ>93ҙ\,EqsG5 No)ne/@+dNze^%kL1ڤ):e_|f|݂XwUA/5Ffg+)5Ǜ&4plŌ£RitZltjiyb,I"c$@*;٪Im0;^rM HK^5mNgE\)֒=oZmyBr؀\tǕqk\ZҘ$`M=R^_.\vB}Z A ꖊu.f7-a ̳쩵 t3Wo{Q·V&rC|YJt_PƠIAOj93O95?(KCcIdo~4Ɇߺ.gylMb1/ ;cwk?>c'^yKy+~gހŀL{ B" n C?v${G 5GXffۯT'W}{ M`UXQEH#! )Hx23Vv'IѶcf\ר(u}]څ =Cf;?+*MVe cKby<𐇠َ#&+ -邼@mK0ԐJ-{>NKvd!ӥhnzQ%Gαgho bUTzY0PHt3>cϴh @ ]d叱:yzY?xY9J"9Ĺ@BgmL q"|@3A!@6R**&vft9j @J)s(sn70GIJ/nd2R% 4;)¡yhfN;@ h?\$,98@ T-h'OEmۧZ\}YR6눘lG%o>"zC C8]T $ɎzItj*t-nyoaMme٭ J|6<]_ء>spdB ~ .W[t s3\,N7GL'K_`o=ɘm[AKWA  ø}PBm^g,Endqƾ-wy r yy0j2׸g*.|ŇDkë&+>WVD%!Pp*^{mR3o_lKUmme Gg텑5 埙\tGqנ@Sqؑ+/=j^\Z؅UG| p+/=_%yVV5Sk|Ů{W_|;ɗ{[Y0'.n{/X2Q/yԿ{`#LqKnٞ~سҿ쳞r7>CϮAmA+<Ӥ[\_gbڼEsΛ첶y0Qd=s@EWnSuGU﻽ =N>/nlhUubϖ|[oZsj9ִ,ܲOQ"H^ yӂ&Waxbͺw;ԒH +'^ú%%x ȐO3i+,nM^E܋9Hgb=ÙV҆tލ~&Y_knZ?Vt1fjd&5֟ꮨ^*ߚHP4ÙqFs@)30KၪZͨʼR1tO6bJ脁2 `پ;` @fB0'@mAȅ-|Ó _əqKƺQmjv4 |2 "@@X 0GL`8H;X{\z(pqӳE .8H 2&^B&"lذ ɂVzvT~➱csB b@Fۧ[~Ze9ㄸN &E2=ԭ~ \,nȟF/Y0)hWϸw,Dqςp}dDY!T p+ Q,q}%'E[ #C`e>!c2$0p'HWԩhX_ A&Q?e%% K9u) .Hr֪v@b>N)1t=cΗsxHaS^]Y`Y䞁@ݥ0z^ҺcinJ9|(I+2sK،auQO 2E\oli*۶=>f۩df!71[mj3v/d0=KYoNCK>=<>mNY!W7t:Bι^.//͛gL5 `i>e gtie`Qlڜcz'R53%7Z>UK# :!^9֯]>?8184:c|:'-=\d;_Y/lVJ];nXr73HrTE~eͦ ol𫒀 CuH,xvD@e'ٺֹށ{( @11CAl~tw5 (x@m+D+2+< X?ݡ)Ʀ+xze Ugļ8'gǢ:GZ-?¹M`Z O_T' kv*L?ivtxƃi%Vw$:3h i+^°,_2U``@9|X AH!3U1caV@ FAn˂ߚA$;Q;ʸwvêl0*"` +6Zm9虯` c@a9f b׮-32Dܻ׉pU%xvWLB=x :D "1 1nF&@׾1 DALϨlKLqM~{7n?y4ЭQH@>#M¥v I23|_F3@eLMq `PČdHb08Z.BldQ# ۽Ê؁E::@摤@e9MAH]s<ǺR 2˜e HBHznxD,QHhІp4'{7K"F?;|5E$ݪ E3|IWaTA/pż Om,# wG Dk%YQ0v1 ȜOܳ A?wa-p "^`DN \ l@"`FK0Ԛ! /73V8XWAOi_"# x/sFW k [dLr*wHe+,b˭vCriy67YXKCB@a}gD 6\kX%z- [T4" d &DH:n୩pub iQ֪:r  wZa) o<~&h&gnTU!PK&5]\b`.2ZM3   ̒+HUmi$T\>*/eQo҅Ә}Yp,d0kij\TFjfg]Hq˦҆QK EvU_̶_)(vZ j 1_+ 3'qe_KPN Ẅ́.(j(c[? pu*)jN86N:Lps!m&?rZ79?*5Bimb?%<#Q;Ratoٟ±O}rõJ;P{D|uyѲ<F! N9 XqA}ZL,ZѶ -M})K:0_@" so)T;P9 &)sOź qljRlԪ.Ezl>3 *k<s>4W# c q;f kre@)$9 Bk$KW%`Fhrp<)%! 5- :!/@2V PIꁈGHk%wy_zߒ=b\I[f],2VGU(.ϼ,_8X*oa̷Myb%S_؏05"v }L6Apv? 4CMF4 `IzXP 27 n0SF_ kO4\r7z?T` ~3edۀ*V Yk`Ҡ; '0+} Kv'ԓYUn*8 Xީg&<:xYGr>rnXg)mҒ5 'Hq|FUy%ACP(ud6c]=ͺڧ CI+pWSQ"rXI}Fn72H-\WxMm;fI<]*H%}fیԍIj"`f:ڙ%*-0.dMM=8ƪF\6 /0c:|4AV@@̝۞Y \5s;?mP:W? u@n@JFޝm X]CM6z#,T5rK.cuL5@@dù5 NJOyM1*Y{j?fؒV6}'%ؘ&H9o?uؙؖBp>RR뉵v҈JҖr%ZhťK @ax1ϳKji?QEd^Cyvt!Wx(=`|4Q#gAS'DwISbD?*z< `25X0bu3oh=)HŒS'{S^k2Ormfw?3 kdm?:no3,`_SoZw bP+o@X  E!])}Syh]%W Oü?N]WWp{zqM0΃☡WKZFlx震fNϨ\^]H,> j߅}zC%';CNǍG|uv PV|Kuħ0AnS}C>6Z|?} 0%:,)s?iPJf%)^``ABB8EA7t%D=2e.i[B^AZX~fϨ @PX, qgnN:eP X{ jk i WUf*2 v /s'S&# AB;=Z- ec#êQ.I0hSLSql\Ɲ#z٪Xpo/7uoHŌb,@(P `gW`Gˡu^(Fd3Q6ivoFɋb#@+1ش+V edo0xW'#,"M>_HV9}ɣ&2|>ԡaP y2B gHG Xpc0}N  ՃD=.S^{"ok ! VJUx+[Jx:;DNlg}"mDВ ^{$afV:k7P=hޢ)7J1 Ֆu*E63X2WHE&-Lh/w| # CTjxmvInv;E# f}ó^z*FX˥Scq1u/бSzqKɛYfn! 0ʢ1e61nwcf r@z?l?o!!"`V 3;>]-kdjZeV[Fl!g%0O%u !2_Ę齘7Ms*:\jpb-6-"ANlut Yo[!!w ,dʂQ%,0,)Lyր6'( $4hk%[RMQLRc/7S!uK%ÍSIP>F}n/駇kDZAne5JfKbXayX)g V%4+' 6&dkApYܴeۏ>z|-l:"ϣ·ߞJz`LH1W;,G :f(M|^-kkqB\!s@nEF8:IEͮV|z N$!}3"9Y$x)p2ڥCEجF` f7YE#X;`@~e(3"y׺mo8)"ZBSU-@ JC`yMv/r 3ZAv<;ըt2;H֡YQYL*[C+4vdg\?oF_c{8k!?K $ T*Y<+8e& d5|z 522IGCQ\(XXTbE#4Ȫ>jfKm1`2}4 qU}Xmg'G fcBzYXBONo$(<ѣq,o 2n-(4{+"bRMr1;a%@k|]sJASs?z?8s@;cYG]NfE*};}M''B3h}[U_?;uF]3y[8[{)HYPbh- .N};'vI m؇|jҝ~w2v9c6``V`@tպMFL#f.4bl{+& " %:Y,߿P1^ڤ$-c<ӫ:M}qzwQ87mN/3E.v%[ g@d%b*U+'iAɸW`kk`MS'EL"C-l#'9Ġe+ʖI 9: cKU'pB!bW>}<Mǰ O.0Vw\ڎ.2]㠡ˎ['Sc@GCmY87']c%UXa,1.E?+dzӫuKof"Sd3:Yȱ$|">D.i) XNKy(O(ffV& D0rV2-fX aask($Xm4kFcMtx~Yݲq?լ>_\lncJ +uPW%y6[G%(zhT."Ԯb* $W|Txh0)ygSF]E%ђY3x$SDTR5gB:!US&Ei1F-cEȂ} pGc3- v;V4JOəぎLQ?L)qev{bOtJ:#;;viZR.o p>.\QzPS5;BRk~Zި=eHi6KŊ+: U0ݼ!n_I4{iejRp5KbwV Ldɖ-e6a 8x1&'>䮂z}ƴON|rq'ۆk4i~Uo,up7a.BTƄ;ӮiKEو0~Qw7Xo=IY_"$[$EH !FoNsIù=qzx$]yAھ΀OhQ|h2c(JXB"$Lv!d;e9(}w7Y4D9z+fA`';?7&qvU y 4)}L dfj.4@E&ʛ&Igθzn>bڻecmUWG[|Ϋ> krc*YNTapn 9L<(-P5_H :\ \Ƚ>U.*npN{,3e}tTͧ/&zE~0!j //bi.jijFk>zAk:T^o\)HJl[aщs;`ըv_ң@aJPo;JEegWR` j?B';==k ?YMrz̥ۿ@go )Lgy|zJꆎl{KUzӃoS@>Ġ#ٍቮK YWBdHaXejs5lk&U( a 9.*LfZ١&fY!S]0D4@a2gr!…8d\I鮳VZ`HO*e6mrl!c10w5v(,]OP[ᯉ(3l;oӍKJܖ? [ 7q"r6U A e6 m꟦rxQZqjP0Z^\|}lsKE VyB)%/j !'?RI76sFxBY$gҋ.M\q׎~F.M9=Gk+O>C叼bGhgj˩DLjeHk$ÙAr:~vvB)cu_A,^FWc%=N+"#.%wQg3 ϊI{1p%9}66iCL߹O](!R,::=*`tԆزg,@`uylF)QfEqIh+[~b(_HDNz ֮{12" E@8ut՟n(/<)KL!#Ҷc;VĻ3 X7k VrɆ\HE;gZf[?uߝ}Ɓ-wx3O4}}A(K3dYIJ4\Y}潍h7*LưyK(^^Ģ0y(P,YcƝMXS CC;d)x1,h}2m?+36hbA|"L`[}Sgs>>gQJeCU^U?~ XnGYpm|Cnp_٠ FFViJ1de4AO3`1xn8vǟ[2y#^F@Y&@% 0,~G;ጯa_q$L*%h~f{-,+rC^pAQBU\DRc? "aE8,Jinx KiBLp81$Wa.LDf  qW,)`Ǜ# 6OEK§’gE,[^p6C Y[烬ܢ(Cŝ,{}Xn*ϠȰ!}ǵ`6@ -Isp9c.Q椓6|g@#d/.9I/2 $Cxs)g棍n|0<|i!w̰Ȫie/э_[%7}HҏWzQexº,`^ܐbP+>]pp|#8[+O)EsMFF(.w}+z96RR+QDrx8,X='uH# He{_cWe.P#T &Bk+ui!S+bœrYQ6u*1tϷ; DL2 eJ wK'Qץp03w`wy;1f7@W$#Qm=hupv﵀ZH;GL @ oV7 ]G ސG&%'<1NSw.u({sT-aNYk9mY``z!>U+֨/EhёEl`+AZr h9+]11 2nvE@=]W1pMkԘ"Μ"Y` G-݁7v;>y[} Xd 2=%_ӇeyoC {:/ڠ/ E$kbى7܆F'/yga>ikifI4{1DqCKjr BX(<}#nLsr"2Fv%E3%ľ#$¡_Y"I-ɎAAjS=cծ28`qȠ37jJƅt ̋Fe1oW7Xn/*((;;:8s{li{[YIn;.9f<6C>hR:d"|jKZß[i=;[+cXjq+/kGd:ȳ5[?r9!$`ܼp~@W"q- <)-8cqMxG+K퓻_m-WD, pZB (< H7 ]0@l:n9nEۮme=n;w+~K]y)!Wf\Z+9ȵNp^۾zT{R%~&g | h% @+Ѩ6ok`^>OP3`m1O`< ,`!&%?,_{SnjLNZӎp qޓBkUgLvV@Y`թAT=93 52ΞfT"Qc9%~`ffƽ`*›IDF(ǒkE?g'}o7i @,}6 e5vuN97 P2dap02ΌAuxq}jX+„mMww] Hr# G8 [igC_S=6Y"O&3.<)[Оoo=gDSq܅jk+0t55^L^"^bԉ+4U-S+1CKpK׈y uب(sƀk)w@nLZ%ĕiDa!ɀ+S}ۗŞ%b~@Cfݴx؅4]PMF&[;<1C2T}(jQCP`YCi']t)L`&$/&R.$d AQ{i0F ;R`$~UU9VDQƶXT n5M#>7_]TóG B~ߴBj&a[~ړAL~~n܈K,)*h9n?xEJam &.YԬ\j[Դ&c $Y:bV/XWdY2d D ۜwN]R]]>oU'+yJ[AB_~ ЧO<`c|z[@&Ҕ1[eڔ/Z 1:|X#uKl_`rHs+?mI@;" h- ҼM,:#1`rA[v an{(wc s5V8"OWwnbc"DNk?F`{~⤴WT d\鼾iWZlkow2?Rb7F}*4BTͬBuߩ']),"PɤUb g3*=_] !8=w%"c2VHFo<CSն6;-S_?ĢXX37NM+ $ rO%ļaG6#6MXuԡ$^êz{E\LBcw^SMT` d"9FN:g{ag<krD'' &oY$1PD\dDqcGT$Bؐ0jŒ+. 8ދqYWH: ̯r24:4~&` |mp^ _q~k6y֓0i0_"`/h̅*qhi1џP ^"Y臮sn0K壃چ|)w&A&P핯{2&`r"SL$j${r~SG6ow sTv &7ǃ0 hY‚>>?ky8q}G3l`]9{. ځv!2QL;՛SN~Nb7'n&+ t+X/WMGviw#}u.nBTpAM3ƃD ICEzt VJ5+iFxyɓc;"w+M> B[;N^`#OTBZ[T cJk@͓JXIp\WX +; LM]YDnoYeO6+iY;+3qA^BHҳvJNs݋Kw>hXL4vrB 4T{ Y}ice˥M8j=>uB{@xz=y_WڴM4zknVU&¢~yⵣK*͍Fz|X6Gٹ cu@"mABC%OL7y76{zos{O`ɬ׿j^X7??gR'RfBl>wo; T$p73L v&2z yaA} XH7w$(^6DXLƗ/p>lԹh`M#{^܂ɢ5Ђ# -rpK1trÀ.cxad^%F+AO vto2GR!cS0zVH*8 dR.E[O`}2r? v/}LX!E3j##':4>n?#uY 8 c߮9EW|&9,386Lޤbd~-?.M;7]O8zgHmRP/fFTS;`[&lNrp- D: xVxE)+A*E$i\F99Ϸ$o> 8e" z( `C2ȖL@d p< oȡ<6 4h&c60 :Spк`\ =_`* @8s`+ƻ" 0ϖю #cNQ !ȰeCC[qQ*A 8 T`cv>1K͑xhipD`noN.\/<< 4h<6GP|ta+ed<Z~G|+x3qv^kId(%Wv3d3}^Ld{iv;Yv`;nGzS~^}'P#O9f}Υ۽PQE  %`R~>QB4tLs)4(ۊPNk?j[[۠E6L[4dI{/c D,۾\&R@q& BPNh]=D&bH@ R+ӷ>#dc_4\ 3{rSd{8/YxjS d3\Di|>@(_z!Om&@Q`4iZIx%B=9s p6M>͔,Wʸw(flIBC`HbX 3qi&(!kH]B[pVB}E@(.c{NNV JٝzU /qBp2pɲG@p+` LKi-U=" CZ.L]DA33u Y_=ZuZ}N#2.k% VvّIpmLF-)1CX># P%pIngMi`>@\4Y9gd=0,$nIP !81B غY = Uxgj aDŽZ2 ; Vl |ZNʀ2+Y_[tf$F["US`@#}M ]̩p2sz L@3g5ZqBMT%rV[w< G[JcvOw2RyI7j .m3J0N]pwy?AzK{i^[Sͨhv;;_:SmK Cq1rPY5a ŠfϸSnYS)!It d™ NKs2A-)`1 ).rC ~@R߉hRt@F&V.M9܅}JqzyLZ>pcˠ )Lo2ثe:eP.1إ3 "w3+8Z}Rɪѧ]{m@WXC$xxKɆ /yMVBZr4hTYRuKKխ4%IEd))Cxs* CiH܅"̗ʃ琻k00rŧrjaKU0q>0cr%C! 0t|>h#i4W~ JK% ܻ!(L:L%D*$\9OMb S>~t-58K 0k|e ]cVgzgt'ȸUEr*%)H ^nq`6;tOVM6AF&giBv2AgB䰰 T`Op3l$-D&gn=M%AE@zV`Qp&{[(hݥP X /:~ YG6bGfr"F,%Tf3չУ;Ϟ (;ha T<҄@*R;P^/P `.j8xd :B]bTIAi^@<{?)э= WġDߡVg0AV}?H% D @8A6gY[_oMwP+%&@8V&yc?ݛ_3cg_V~?jUp*z Xֈƍ5 kUg3ZW_i"{hbsq7K*``&~zcwQ eh]6f". V~K+JU,ْK~ONGIW/Izr\VՔA$ 9AP|2lvy]_9aօ Eu Y]x6[=nD?rHpCfQ%沴5<,JvyW w_gcD ٬=X,086hEAt-9n;[ѭ0n8BL0j-%P+;ۢ/wz.Wr~#W {P91!g5a +'K3FX=qCI^kNmةaEB  " Gmw&KH %@ իi( @z SN@DE2P [jɤBsx)ȶm$[W6F2f[$Cv۪fXm_*eu]eI߹k=153m^i+O{].zvpHG@D z!ܰ2=Vܮ(P a5*:1 CNM88PO!`Ie AHH倓~Śfl +cB ͊S %jjh P&" C@_4v@E`3F`)P6hg2 l}`9Cp\G5^܏Ʋy BJd qN?Ta2-,J:[aL/Mv 1k-E=["Y}lNx֬E{+]zV$Ϝ,5Yf XPaH`-<8ZfjJxz2'i{d]owՋk_|Ld&EmzSoCZKDꇯ^$! d%r=xG„ɪS~e}! 5dKc:!EbpiHŊ ˑPt?3גrP+aE̲k"  !_#jV?"ErTq&F" j$U"*djbd?)u2<@# 8O%2<k;71CK^f@m*(NaQ@?淹 LA`#Fv(@~Ihz=/j)|r4p]hVTAmV_N8bzHpHCzيj7BV5V6/a5Th5a`kS O@Ta=lp13jT*` @yv{,  d?9a:/wYnce;t.?K =Mڹ~ RϠy$ d 8Ġj/qJA8 lgwc!@ցOr tySY40gI}o`3캛)pw[[W;ԮQ28|}y03BdxNPnd˺i-6%%;A׹t\vzoMW6 '셼$Կ}ŒN]]IKC\ |aG+clnqٕwMcC].[ɑVcm&z.Wi#ۡ7vYPI}!HܦKUg/̫y-\o +h'1,Vp0*k Q`<~VL:br^;0+oĦ&V8 K}%gء~5{W ߓ:r@ yh.EQmQ@H O!)!ٜ&Vا%Cڗ~'i2n:)8ߛeH9vh%PRV+xu~7(R*]a#e,OCoZM.=A%57 _30m==e p5@+ Xt ;VZnhJ";).O F20zL;!, "xCtz+ }l@hA؜&"(Jj!@aץ!Yff*M C%-&dF MG)6X)d1謍!&U ,sg_G5-l UCA3q@aԈuJ1?}.$x 5B( 3OPB&}3Om*<`^qb΍G@&H'8_~ĬE<#dRpxT7̪S=z7st 9^#.W HШПU 7L+bE%6xX)b @Ȓ (꼂Q "0HG]dUptQ҇CG{&Y h`~,a4хS!aUnߌjgp7A SvBY 1[t_F0a!P)Wy%òy8,w* ]C %{ɡфz$I*nE#vߑa7M4"$1}*VRTnbb[&q@"~cTteA @,k{(0 GX1&dQ8Wx=-jJ.t`@<"g0tb&v*c  گMhW_r 3xdo}Q1? g?$qB3p|AU"Ӏ,6 $@ A $ !2rOx vz tmy+DJ |ӿPAeOm]{<>=WޖXwq2g`D! d(KtN 6'"h_.k;`Dbѩd>(}9d{?OSAv`[ Ƿ5dS={R/QNgkE@);{m"!Jg>Ir>xRx;Cnh l  h`:%JL%"HLPFt"3yNsI|/c} t} wӾ 7I>:}zmm?X\q &ȆK`{հZvpg7?x/y@L+@k(+:z$ :+PϦ/F<]I2F" 4JL0~8A,bW`bPH~=#yrD(|\X6yp~YV@!G ep 2! P<\(䰶fkև4+<1AV @~N rD}\ AOJȆl!F7v-ڃc-(ᗀE_T#+4+gy.ܿW:5mM|$ɂsb@ɡ#1IX=>keu ?Ac7u}@G7Ǹm@%*YHȀcσLHX@mVi"*f[l(@ ` 3 x11 R<7˘4 _x;U1|8d|O P=#/V&lAڬD@ -ZR.?(1f>hҚ_S> y"kxE܌ɇޫDpS;b~ F=(nw\|?r9t/9]J7z9f˵eQn0=KcO BHwwP?v$t[cB0hIzBEZ$0O֌)"tICg@ n>?% CəL1/8u:F<}97}2C*f40Bh![b[e%QN@B!@V[&:( ZbcQ[iL4L {w]:dƧ\kF([8gorݠ92n!I [-F?8wBU˪u j&l(ѥ]n02G"MNl001b=U&1 .їe/cJ=1^|r2(FNNU"a"F;CR)fJU3\6^rlLm۳ɂdʴv)wF&>FIX Ir@@8@;p.Hg=pxp4 | EY]ؘaFO9+/?[yҔ;9;(0;@ <[D G7>+,{v@Ih78EiΩn^::^"Jઘb c0=|_$ U`=@7,x1bX`U:[L ]"w `*e` ~3y&]}P,B ʠLb^$IPI]90b98[LQ$z&r /@LE$`͓ RM{~&!CWvw-0NCesadw*?R'^EԜwŌ}?@ҹ-\)(5(9WD3rBd~f $" 3ӭL.jd\B̃H,Ɋ̆V?.ƣzсYD;[P ջk9FrDp>}۶6u K LsR<)@Q&ȌWy!dD5HLx=$qEe$9ojdZn"venZ?7j~~r22+yW&_P=7Y!`,$lvr<Ⱛ}eh{AXm1,+, ÄG+8@ $ڨQҤn$JުjId F7T i3 C=hC5E eOAt_LL]!(5;qN8<_oQcD v;TƲD#{_ɓfGa&I8;G?om c5#=9Ľԟdh%6<11a]Q^q*tca0< v7ǭ #Db $@ 'މBfhom~ESL9"SKd M vFnd@ue6}7+A"83$RdR׉/bOz^"'&Lqv7vjy),)l>K5>8,_OLWw1^(=kNJ;M1Ws~:1> soLe@\|^rl ZצVVh {1}`4x[SRNێSlKg|6kcFӆ$bZ:$C4ba "aJ1}5Q3XzSk!Ucג_AZ}JU-Vr+- B;4R'X?{ʯsZ nrޢNŮ?3)=k W_ۀo)qqL%RO=5_ʥ]t̗W0Z_`?w O},wC{yfb~v#d/S_ٖ0#/ROD64<iLLh3Ֆo6ph M J^[%Ox,|\E1R~Vd=n~_=Z0d/965H<uLrΨIm 0 ~Xi1wngHj01P d5 yI'vNa+k{2q + kDNlIrMX FUP:9p Ry8</~;nlw_8+N>QZhȅƜ?^]q*Y}k$>@la d x8xNU.3)-JPRvISkSOSて CV*AŽ#6/i?_݉=%2[z+${ܺ_ C kI7zy>ܓ;pRZzMHc4PN%ALGF3UGI鰩&VB}B]T#WzNn1(<X7T#2ԂBcB$/&qK9JyD#;ԼM 7P}]a4Mw gs}ͪ{Ūb@a%%yi+*S^IY7@qQ;OA{[8x73>Vc\"ރ~ v~ut~^Yj߃~WtſU~953b7Ȣ Zq5'a|~Gcen>`X(dOI)v NB?&4]H5ꋇ7؟cv^XvȗM~;kW}f!w>Ů\~qSƄ^_('HW[ɓ7/E]QS ҡwz8b=!F(5ne&"z)^W[eO7|U}Eiq]~r^dFfoъxM;ۦq`M'}`1smaW-4ZxżSL$^@ И0SujzgɯEwfi^wcђ:{}9/r{/2֟cb#3HήDxL'9^ u:pGrsNT)?. ߥsMO!\%;Pn/ X#:ERDv`rҵ=-BgľћpvK:k+hQU#zCNgpH*N :uWI"q8STD{՚q ~W~/9qx + 0N~.:gT*:iz"h2iqau͎xƁ[ZQro^mMXiXajP}ӵ7~ (q_>*&Ѫ~.@Ɇ0{W;h;S5%8ȒFgϫ n`~NwiAŒ+@gM˟fm %*{vP67׭7by^ ǧOB?/=+Μk {9ʘeF¸Q2 8n;UHh*]XU0`%V{|}_o±#en?_A>.>w?M輘 t o0LuBZ]<^lhi5)q/dt.k!WjDA!w?ryϟKvd aS=r6nx}1'nro j\ %]ւWyI*=[xdi:qq,=A6!#k'vr[u7=kΪ;EдJFy‘a+i?mr9Ǡg ֮-|?O.o ĮN0:rt7Oz$vtVvb]m; x,j&Ţȏ| V`0 RAN bxj}t":("\Jh8DCO+^z/DJϱKnk-y|:gr#WQ Hxa45Y B  2_IJWrw뢹*l :@GfRcmg#bc zRvjS6 d#Ed,;~# yN,,{S.mRBÊژsX[bw@Z MH 8A  .cb^J˙kS'~UaU"ӣηqU0P4?Y3UH k,D?|(S_L s и [Ww;sFCH5h`vT%Wh,C0@ v?L_8FsrB\&+,iN,!O͔44Ns*qqI0`4;y <ڈQ@)ɚ~-Vc^kqPWKYJԵn|%'( "jЊ=7_}u^zT̹򍾲$Xg#a {~V7oW}`+ 1ΚKfmJnPPLQ`>p<˰GZh?o]_;fWoqTYzh4qU! MWl,|٧vNƁSe"=sۃI-8\vSY{ՐvmD-6=\JT{ t|ѩJF )#VKkU4(t7m_Qlhi%FRUMyRo"`#`%5ovoX嘆rt ,{ٷ>?)T`rsΉnjۉk?v{s뻗xO1'*l1|Q\LHs7I^7»~OLܮ6Qhd)apב.7Jv%rFe*Ξv0@$8`s㏶}CsO_ă-@x7b+VW@</S xy?@M>(ycF 1/rݺܲ Lf8 #aW|mM^zCY>i1%dVۺ"59J3sPv\>ȕ bjۊK0KJv6T]fxM[YU!*$gb?%DDq~,'ncE##p-FPO> XY8-w=JF1??dF5y>H"&&c2};itLYښ#/Ǖ`&8nIb anb=_%"Ԟ-ykEw~І\go\b~5}'t}w:Lx<*38u ]SL=]/-oXLv $ 9J١@J -7ъ^2#W}Y\ӓ3T<̒]dUjcrAh֥tr@Fn.l˛48^n4VIоQO?'KRk'@hQr'i!IBu]ܤrE-.-e_wNO֘)OR4*3B9'R_>E3P'C8Ѹ͋’j4FMD""|;k =sk%wtm|}3pa0834AK4U+|'׾=NKV4ÃPI5Y3rz[y֖K/6 Z?os<'.)lURzeÝzrˋ6 D YڬUio9:EqΥ_lqݫsZz"冁ooLϮ]mkE[[2cҖm@5AFypfZ[8w41K1!@ϻrb;"jApdȂ|Wo /] HG]2YaWII!7DoZMe§#5 Z}Փ/R;iuf~qHJDE!o.x`X1d6;vzvG=Mj^U#+Ag %3.2=qݍ*`ʁWG&o<wjfM=8 XZ#R5ûZ-ӒVH}o fga>Q*F \꾋,*jگM=)`VO 팸k)ngSakΧ8iFWR}`ct+'+ U8 D \cwv2avQ|噽/Q(n >-g+JBY f17Xzbv}NgMnO2=;kk<AFb0Ik@!4eZ J E@vGu. 9k3,βV^RGwdtH@aq%A4do zJ(( H(ؠ1D<hh*t;gT3n<EkLzO~%zoW*{eMBNqRrZw 4xá C(5L8`*Hk1">4xؓ6$X]2qLTɡ8fY.]q[$a{6;[F6 &,ؗl!h`:fϢ#-STyp (@>R[|/b`0[(p|%0Dk)Qj쪼 *T,j$vMLUc'=|(Y M lg% K)Fͭۋb^%{_Ul @ (=A_}yW>ԴJ;OyyYVo[?; ny!?PX0}Ƅ1~ΘX{yU*s;8#{7㮇2l2E|C5CwL~J#v.Lma^ur$@ED.Ir{nTнx=i?%D<9>}#S7Fi헍 r8ͽlB 2r+綶xAo}aMxbrn(Vx^|~SYgg{S>塁 =O$ !SYK#7 9 „'-?LJȄ'ayZY&=[IxMcxSn4ٮ?:$?gĺr 9}op}7b:;uJH(6 /n 8ryg{q~_&HdQj4`Q@iY2Yd/b @ &~냻bi`(+"5XGp>ҥyIDRQD؞u 4Io9]8DVQ +l 6isycth'm㊵Ќ?bdwRfUz5^j*:ߤ}]#_U]^ˎoY(UeUZ|m۲r0FT^B\ *xAJe'X^g ¡-RgT<$v$r `e*J42*-%L 8rb |%8d@H8碹#w2h9$DNzG\eH!K+zC<. H|X-,՗VVxʽ:0UDWy:B-^ę\k|\3]vN7֭ڦԡc;l鲗 >[mP)ӤHFM*<8l_?hÃE-J+lbk鶠R_G~g҄DHB,EU.%UݣBaH* " nrʺc|#zSHµVP1D',C@B0zxrQ \Z@ImE\SϠXf $Լ-]Ae RN!qjB [-y w`Y #p5}/['FyjHuyuNHO螕Q%Crg~ĞʳXsіpXi|.wa4n%]…7_`_=WX#-л&K1OлAo?X;7??-/+cX(PNHCO'L5gi8%8907* 8 st} ŝі<N%៉lga}t:ItR#%oĻe}p[E?i+ y릹jCZ[bctg}^o\J4S_~Ьh8zݛJdq!?C<4PVMK)|~,o._Lsa70tn&th, jvD!ӒQ:-~%@^chuDu#FLx ލ$ai6,<$>]k:x}9ֹ9Ymn8k̗?/X _Ό@|)W2^Dh2`xJy쐝:{jPt,Lp#oꬎ޾Af|RWHLk*REfxx*@"K9#4`AwU"ևޚ۱aiXMFc(፧&t"7{褽a$z-5/˥w&k=cxĀnL9qWj`?o1TCo6ؔ9kX9툞 #:Jap: Z_J\) a z8.U>w/S$8uܟb +$ w"Qu8ܼg~ ^_#H8"-0p3loP@M@ !cU~YBEk8ٲ^?*ި 3{͐ ZD,;ogv<G[?`i'RNC_Ž K*f}BJI?J |-ҍ:<%] ?|4=n5$olg8Cȵ W%C(@D)~HO]e\ݟU$N0ۤitneBgin12Mփ w-ʖl{\*h7B6>mfuEbe58o*3U LƄ<'1X ))s,]q7Xddl\Tk"H.)GPيs'Z\y9@y *O@,R+e lpYj-D1Þő9w 8T^g5ǧ |*Yo\i/V$\*]a=q}@-PFtϘJŅK7k RgcFA66Ɲ^W+OLWH֛~ޱ/pߜu(=pnY@V7YLSU߇H޶ϩm9TԕƕNm-ic8wnkmV9xa7~RG3NQY6G1dkog}b;&<<\9 0MTbIS>SJ2ֿ0 A)$A&ک^m?zRgjʼ9/2~K e8hj \)Mȡ?tz1QGNr$"1E}M{ ֮TE0Ylc(NHb0F% 8ksT\aO(#dAD$)9W[)@&"$r1A Q3?kTHpAN%f(te GW#/m:^}0&T<#]Av-毰'L]KntDV '+GTX05^psC6W˃N?  :ڦ#f6&Q- 6뎱 j^fJ t$ŔǑ8V:^b,d֜qM~?\C\xui9#?[4~edL+;pI\ BHϙl.Ôs7U##E$XpQr?w+ >M!M\!n l yu%D϶%fp4_0%otC(+ iyXh}_7ȍ)9lwʚ]Hmxo |Fk̗;z.,z+p_i}q#{V,}9dR-?c9&2*nHV9hs"*=3JYXL֣f$%yq]l¡K+:8$Þb##ܘheP*іmVmЖATO"D-E{ {1y%S8^JTT?Hu:s?KGɿKw1"+s _M "+QW8XxjEĮ';lYs ߿y^#@v ܁DG DW,SSd}(c{s?=du瞛;{|X5Wց@/ R h7R=x?vn)}9kfPɈ&{2~`M:>+ك^j ͛^^l5Fm\[(z,t۟ؒ$DKJblKtVA| I7-HTnI> Q69PӠK#ݱ'9)\ O`6 ͍Wc__ܸJ\ ?Mga" IWO#1p _1H}YdQ]N4hcpZ1݋NSb!MqNlM@ש9Z}T҄rm(tD!&p1Ve5s93o㺾6S/?mqC~TEn["Fe(R.Ew )X :P Scu1.u-+ӤȺ>RQc[1+.b!`MpX̸mbuEH1-ZW(xѳ_hUŸGdI8rc^;ݟy(mxM},SsiA ¢`?q~_Ob.B -64(hH]:hٻzm *g#MyHgx}AarZ*\kBh BQ`NªPMI# f SP4*XoVAa_aHd0C"pt bRəb8@ÛГS41` o >W]@A z뱯a":B R1φ:[l s `f9/. $=V}}UIP5R[E`t.4+`h^N|6=z)\l=G_YIG^e,: TU;n΢`PenCԞD7Qom{܃=[7m:V`Ȫ3 qn(>Dz' F l`ʮ'm4$YR"y%32YD7>H[C @rռ?bl8K(IY-Fp:M˚!UR`[LXxJk?YϛJx* Uv<Ã''k7Nk[eiǭ.jI3wq! ~ BhgKSzŠr:Jݐ3ԚP>@ c 6W}W`G.޼ (EG\x'j%CW#ߘA KpU,XXeC\38HC$ki\R0 YwNQ꣖\,ngӽz~b+7˖}q/᧷+k.:;w4}ܮ.`:Yo%rIDDHk4.JAVӝUDFH[]=V~=%z>O.άZJ+}Iwؘ-J=0w .-MFsJ ϱxG l(1p!xYO̐$djP )47c+/YAkHe{Y&I;';9L'gdϥ 0):k=8'= 5QH_(1zaUWujo'P8t] C&0c% CoY3,4RqdZ _6Gu'~'&+5t:TW[65tʘ$ 7X-Q,܌~G/n9 }&j0Qb1UVnZgLNޗH+[Hh䦈 5bQp"bH @X RM%3ޱ)4$ Mf|w e .b$dޤ׃Mb3*).޲l1"anEPq5 p^J Ἒ|?Ā |6!Cu1C?g GzCuw#4%\.U"R7- h<,BB"uC \ӳn+-? hXPxc d W"~4~,|1_TB0 :k {kaCE3ygP#?Wx1SB"/T,/v٥ j5ZF"+kbꢜw$⊶88-P="RӒ]r~jQ\&w6aQ |!^sȫjHfLj?\4mɍϮ:uUcO͘o i¹Gq0#ce+SzH4qTtV&6޿WСfΉ9$U~h{U5'?^ґ!'b1 &BDGOU'b)}k֧#T&x-钜Ύso~abN!.D~; z1@[)ްƕ6^+<.O:ܢRL|3\lBѿ<_*U/$uRF<̿F'N5x'kba@Ւ ?s0̿;I@@o/- W-߁KIU]0ӳ~੅ɾ13H3Ѓ;*H.}/k6`X !!O[Թw(%!Ɇ]pFX)*7|jcr/2)O~@T Y=Cb`".&bF3JMJg1{K^ t@ry5 96v(wW<Æ߻ Kӊ-*Y 50бIq3Žf[,#31kՔ0Vџ!VhYھ!ՠìܦIIQ-K2rZp>BP(]'2@uG3At7j<1/Q^CzXj4nOByDs8:ֈQl> @3' W4ӖWKŜ''k뭎XSr;B4}꬙ =#-vXP^9dT~ )ow!(D'GXAfX$^^[PK<2 p g7Q I.r*(e6x!wDg3~Vcw m4P@,u龘M~,Hs76̀g2C h6Ȱ@ ѓh CPXj< ( S8.NozTmu}K~GƏ| Ƹq3,nW&uj8K2Tmޘ Mv+K_HS"Zͥ@D3|%l*b%CO}\|aJA2UyNM,F A\(aο%j*1pk`Fщ2s]lW@y fZj7GxY_O:?TBz{mju^ɦA_jD__3Np#kDآH(,QuI e7PIdl6ְ;XLskb_A@~׺g¾eЂO{A:b_>6?&)I#KUsTj1V^/}p!y*H ` %Y |(srjY戨'r|sl@+'r*Op<;plHFYí2e Α$ٌqccmyJWvn;Ҋr$w֞ڭaSJ!&>h)Ɍ4 If5eF ݆+TW?MDt v7(l 4ۙ+a!A6B@~sd[vGEC6bE%Etd{}wPrXsEYw[B׉ 9grG!}*:[m`. Ryeʼn/2/2wQS?$R) e,%eiX#1%Hr]2WhbٮS6}hxE[TQLH)ԩ.{k(NNbO9b_zEGI1hk5G:)$G<0Tpq$4I=_Ѝh@ߗBߖ@?<~ݝ38d5ӂIO/lrR^w/ֲ nnG/A7}gc? V &TY];h. E{=Mr_O5Ze!dNhpa`:b6"Dg~ק]{_y|q߼~ \!MN+ļeL`Ai+(3"]zrh(`P B.1dsrƉ~jzQ^@ҙח0liGVRڴ;V[EEwfcChNlxNp4- 'li}$֞j6M}n|_^†DUN:v뤽.;/Edۆu*QcОPΜknEͨ<,kӧgw&pAawsR{Dd{u) ~DH ,] JsCܔHכ&7g-CU\;S?J+T!QxQ_pj0̔uy\^g;sc\i۩^qw?.ݷ  a ,?J?gE#xv ˭ c%E4IJͣM뛣\H#{K0 wR"0:dkcTVUUp:!P"e~+b'7Čvl_!-=EU|m ^C-H HߞlF; H$0DD=+K:~ʟGrJ}HVJg.!.)* %ggAJ\YOْx*Qot15ڰQOh i<7j1  :q ܥn)ĊF_<:xz,(QD2C պ:$UI3m@鈨kNCl) Y3|~JfύVs `:hƢ0,B&1Έx*fR@1(L5^zx(0l8Q u@ʼnHuOnj}r=sQh-׌eбE@ c ī\DL5ѿr{~JiS>~{=tBБfϘA1B-%; e*B([?O869.jwJ`R}!BWAÃJ~${p3FeL[)!k) ɘ ;=Pڙim}vX237h8Zy "`T\PG\al1ޤ}.Dc{8WHF1E"=1D6&h"&HJ(.c΄1 >W%Zpᠣ-P=aeQ(ꎿm󗞴pIqg,49C1&wքœm=͞M%qEHHepB 7"kTlLmȮ՚}P"CItGAcEAa!(}: dT6:)Ae~*Pb M%M&&€j̑JΦPpҤ*뉁zM%Qjά & ^U:tF%hRNm)N[0"a(n(mj h 35hK5tV3ugjdU9IZckna)R5t g(bqNŜJ8 XTFFBQB_EZ &Q,($xY%$QLdt[6E I%+;i+Lҷ[ B[yc;S׿dnBZ)B,!4ukH?M&FWFL-9nI-_:yw>u^ GR6"@eL~5\XB+J1P%?@I`"z EZ;QUW)idlCp(;bjY7J%EUp84gz̊H2 j$#[{ X hņрX μiwM .bX߈kz]>FkW:Dz>]~so.-tzNWs|?6[:Q~~pjk`uςϮq H(u"k֘fX9[\쀷aoC@柊/1m^W7qU@RS Q $A8'Cܯq0ݍ/$@*ʡT))I~.d68Q@-A-MҶ6֞QՓu*Y`#,Rxrz9RAJWmJ,E Ü]hƕc(ݔGڧGL"on-OgVt/>+gՉrueu` 6=BjkRW5ʪ P^dOol \?|h[̤F@J/yqRcoFXTܽn?AUB=. RB$(@HZs~KJy/ ?M%䯨Ѣ {[YюL\t;%F5è1 "#(I-*t,&3mEGщǾ5t5<fh0UnBCQTsnȥ!J;EHF@쌥(yڊ VNUQ"l( oi$4K,/[~p?9y&Upo& ^M,y^ԯ'/mxg ^j:+ @0 + 6u9Ofϯo՘z_Xlػ`Bx{O}b%څ^)$$ syq ZF`P*oCPt$qA8$ȴDN)y!|T 9RR8:_{tO^D/:H^k6MPe$eHsيz*tWNvx"1z%kVku^Q)켳OyEY"=y8 "Td !{Fݭ i5يH|WiŤ㮴DNS*Ƙݵ(M So(9ڰyeEYaw5yCѣGb\^P}ީ[fVB+CϯV9-2(- UƢ;WJܶY\<]Rp؜}Q(18g_GZVVloh1V U٥^06IКaumP}NE,g׵e@S󷓨`]gGf$p.\83`:צ=GS~wuwvL/5tn*t=Q;`4vKm@=|ߏZbwV  Ӹ[KZ\u/-Y!Q~ma~>|X,D>Qݦw ]¯) 1wif޼fMo]'-Kæ')>L$\3K&"qQ`8L$j*ԩyM#Xt4S"+NXmKh̫̀:Jbt~&mڢ6Y+!埒+i_.[7x h rU[7p 7`yF;Xݾ}O S/=@VXzq]wC`VduL{;?w|5kέҹ3 +Xkxxd0 '`MAy`ؕknMd;rd`K#0u-_G|Ѻo qum965@M$=5{;L&z썠=un|yV]_`8g:ܓJ6U%s)#h,Ӷjɚū\u5YT^$hG0D"fG0yAkk&-u  [sCa JW304 2Ba ke=|8TٕGaBck7W;,vlq.Pz C C2W  @}¬XJ#+_tl ((*%mЖ]:zl\Q\rf̞q L,jLj D ܖV P W@F>0@ PtaT L @BmhM4p:ڕ g3,cjUzJ݌ ,t\f]!}%=I'&W5e9[g' C#՞{owbYHC:o$sLhpڸfWSS{&o@,E,k!Ԇ/ʥٙad׬ezT׳&Hx& `5Y.7XPE/'Jy|3%U-G%q NjXlpnZnوC5w.[ 7n``ǧS<α[8qsִG;k~]qҾ?ks U>|g.x _ۋ ):Y c-tv|C-^nڂs@HQ# l17yӅoC:?N7u)׏c J,cZy QWdd^{r+=֤ T*gKlܽj^ }}.D{̷18W#QTCy[Vŕ-S%fύ:VGe nм\5hںvo8̢X?0%diHiQ@)@p Svщ!G>JQjĝ^PpD1WR h$!6" g=y/395YhwR.$7b0:OX>?UK!\@p4*0E(RdL1FZrmi$j Z2E K*xw`5WXc뤞O/Vs4 KR@gfsl5_!-3?gJ9FWԑ@/XȮCNѭv :e3w! Ub-%I CcK5_(9ѣ"I#G2;wDf_]gt]^7-=,U/||:'N~BSI~vA$7nC@~s'x]ThyX"fbo67:w,Uy8ΔEΒ %*>t99Q 'g|KfA n) 66Odpε~ev 8KI|MrbV\iM]&'7ʉտGc$hYvpDVT"j&nJ趶LI4`vڰ)jN} @{F(7]SCՠ]R"[c XV^tOCƂiM'/>0kXw ]8pۧyMFpĂ+֨rn L~rMptxowc a& tcnaJH=1}zNJF&'r6@{RU2˅tJa,揌37F=A G#(!(Dawݘ ex3EInNwa`2JO O(lpbSBuN&D*&Q.Pע@i =# yހ$/8׳Zװ9eȰuGbDJ~`{ b(;gwK@$s?;c^1[܃5aȰ8 ~]25@^xgPy25N#C#QxgG XjF^S@{;!V#HVR6uDќ aЯnZ$; KHÈI+_ ЌPw# Q>dde( 'lq7= cc PnoY@p/k}i(t=@gGt@io~.`PX0rK0D _&hkx0 ue W}Wg7nW(L0Cr8kٖ>ª6.B|x.OL8N˪߸**EAt$Θ RCyq거UÁ^n=s`٫;1 bnq<dm~^o|BApJNuc` @&˒ `-dNݻ;s. gioC=e~,e`ab;+éx::>7W?ạy[tuP k$,ȥ^ٕVf,ĩ8zv+O˾4Po ?bx35a>|8}9]wwn$&X Ǜ ;3L Grɩ|/5tkݝ\W7L7Z74=k%M=|gq #ۀQC'&, 3:6@FT# S@cqq~%tD<|.v/f)uPoAlg0`sН!eHqةܴ&׮&Ov۱A A@Ů^L;&=t;Mfzz@ku S^M/ayYbH% 9rct̙NdDI[_zD^NGg~اfzWt'S9a_W߉ʠ) ::Aii0]>̿`O(Eʷ5}8@dǭNH7^\U!"T޼E]Q&v!Dc(Hz:!Q\^ ZrIGӭ4g~jtȉg@!652̬*oogr5$;T@92=B2؈Scl)}l J2 CDx"X8qw7ЗD]e5%3T49 aH^u_V8 D$;[}Uab +)lXaUbhW%L4C2b#k$פ0{=YWݖ;)-$2 G/C`emg l|I   `s+]QN93L/ !U څ[?Ί+{c&] Xk(*V,8j-謻C!E!i6.bټ8Qs>%|&a!l}mZ:r_Ɖ>  ov,Y⯟8jcRZ +چrD.[2I$+m1eF@וPjWKpDߥgǘҲr`2/O/Nj(0@As?%z 9WYR \z6lk`^5GeQ?(iF[Wx% T`)k$A6NΠYi8Xɀ0y:fN28태2F3׭3LmZcL{-,hr p L* P bMk}oOj$A4CxȎ|E\.($6YgL]rnȽ#uU$vsZH; ///yU?6'T,p"'> rih#)n(g|^>1<1 eB)EotBg;!cMzR2"[*>YxJ\$I@4I,s?' 1]e U7 Z_y10@zE@2+ގyg`c e} GKY(a__C!{9 QN/:ئ_jכG(Huj^E JK( ZQl(9FDu5k@!Bw1X|. h}0k y&S; ]_bj\h!0]c2PEO 3 Nh|}M]Rdyy@EQ!a.['o\YRy ŗ?o6" ; b;(A$$ˁӧS}!!q;6 $ &~ק@n$ͱswS2?|))3`Ij#%EwbXf]-H`O'{݈<ۺfޞ)l%%BL. akT]]k 5#y-K̉Zh-V]'jfiAplq"LE03mJPN>o}J>>*Й2SWpd`#o׌aLٮ)IЍRRULrBhXJpstd~kjeb'Iy*{;W(jnCSXzƂk~gXj]ophNj,{ycAK¬OScM!t!d6uD}?w1,J%Y5Z;o2haJT55قX_4L> !KbJvnqݮuukC2[c A|ѳG6OޯD,a`qFCS(Jy/JYa^1"#1tDpJ XYtz1:ϳum2aʧ`uCh4 qF^,$, =JZ_bCJe GKf/kNlo4'-3ĂU;>4dJRc\c2-'vQ7_="H iӢX{ ) C_տab75<ƪI,.F'/K8Cۃ>Tmu_fM˰jf 'H%?it.&tMu jnti-rΤr6-&w0{w/K"xjbT9 DeN#ZG&U*XĘ][ Kղ0z j T.; }Mrb[i$,j d鯈gdS \*B3k :fVFlIYA㤁+(MM0N* *tTN<ֺ#rڽh=KLr1e2EGzQ qϥbR]מg .6 So2&~Тs 8Mi}=:}NPLrW $GnU}jr x]OD ) Flx pu0[U?mb6& `%!Ӵ|G.Ӽ1-=r,3ZXwU ~zy:`NR/A!\a뺭hV&p U>D_N ԸtqԴXM\˳Mn4/k }liKjJ$$T63t@HNm_ۏ' ?,{eV?ֲ㤋뎾jԈY'nygx+;snue`^Y8eY b43Qh8w٩Ѳ!ˏ0Oh vs8v?eO9J jmhmĖP](q KǾWn{C3wo9wqDnk0XQx ȫʁ]&bplRPJ@PtI&)YsrCp"cKXf*ihRZ? c+H!G>laBUt硵"JIb+Q3אQDG4l9P@+屑PFd .Gb" ) TZYS"jBU{7}![>˄kXmCOc}PG+$jՉԘD {ցg&< yUSmm w~W>.:o,x~WzY1ňFtvE\ˆ6Y9TN;ţS N}zGL NmX3̳D)& ZHNvuUgj}|EOpXjh9/yAKFNhŀEd\Llit9wСaCetr"2e^aICfqb/I+Xf(74Ӿـ8KZ_KdN.1@rX[rk;pB6aЬbZ NOI]Q@rA΢d |*̀La qDLt ƢɝMSD&\YGZj.@*0\ X℄F"0o_NV(E @M# AV!wZ*i88:{zP Y^eHf)"[x5y=~0yl\!Ő!b,t,qA^v^, 8=i ڬUئ<,L1 (.j &8ѳC(3xtɵR{҉ɾ*'BRQ3P:LhT肒88k@3F$48y+R~_ո!&b_pa++(]1 qӚGze&Èx @DP;%j2ي lJ9 Q>߯o@cCyUF.?a)]sQǍ]clOUsEZE0.~`#!b^.QҊ턁 Pm %yI*bYP$ iDPnǁsǖ3ID#EΕ.c"Dl6Nۚs.*r;VP" S.T,Sb*ip$)54XU6iӥ|yP:x(6nf3t64gdig]&7#7rn[1"jdB^^QݾvX89R8[}JgXzsyS԰y]+" .U< gH,NU6Υ~P'E2*'^ZQiΨM$mj&&JSӪ)Lm@LL$76;AjIxrW/H JIj/nX `#VvlWZw șɉB S0_]0nv,ө mOd"8hU@ !H}x9>fDdְҧO;}%"zJ]c,Ǻs@9LB*T#|U>r=WpdhL::흻*Z%\(D` WiJΙU{\{׉Ŗ`ԍ}^mQsQęW4W?;!@°DQ2!B}%bN5-NJ(ᱧVZ,WByB|dဵ:*{lh%ن{Fįw.K_fR1$Q x@(وsŮ/=7Q)|ǂF2V,,48f@%YI abQxC9#fB-s6iSEFd2kHGۥi/"&0B)ѽy}[]HVyy gz.Ec d@=Gr^ Bs&*RK$q)0n_…d9SyJJJg1%*l2 "%?BpDDT u~VD#'/f=:;Tn#@&cp8D&gN0A6{슶l.Wr҉ʾB4 J'jj8*!Q$fNk'L褎?u3b]N%ơO(x;ٮK3w]Rۘ%7 { g} PYso'-ΊZλާ.pdm>hW_BYfAM[L)\04ҘHTH8nqDc17oYǩO! $ "*.e']= !BtL!=g SIH¶!Ys_? U+;=mYh"2 K@`&UDx(n: c f=IHf`:tV)*T -nL.֣bQ łf}"G.@8Wh C x\ۀ)Ƃ.5mDF5}JHV=EeW\tD K>3j&Խ. >2 9H < loUIP{u*+d$KvR5/*&Heqo7p LzN 2')ZFh:P 43 %$`!M0J6h0bp])j[P@fM>#lS)| WX@fld@(IZ,jl9g!|ĿZV0Eyt0ʐTS6A}H郁9:9[ϦT m&}O >fiA^¿p? fBȇGG}x]-zK V hJFs}9sͤyz{W kf?i4_R+kTэ3Ɵ?H,*rpވʛY&m#YM⌜3| c85u3_6EF %knJSR%3A:t_림v#o- !f :8w״Ƴ5؜# չ\&ǒ։kȭ$dc46HC䠆͉U ^)_k0%p x*8cp32vܛr%C%UQBPڸ}K2eKݰ;<Z<򦕗G4R0`$ؑIZu.=LgխmϵMXV.kE_F͋h!-S߿R2 {%8:@'a 0\͘a Ziv{cI &5ᎎUv[ dF_SZFٺi6>? [l-)702`$.dxT |N@J,yRen]#j|jtH4C"z%@ S1=Z'DqnrW2г&JClۜbPKVrm'nS 1B|B3  +>(b rk8YeWF #zuĝ4y2)L fq0BAÒC$Ί$VSr 5) BBQP#aU?io/ISD DRyddbr~Nhƌqk.#ɔp.ā>!P|3f UPn Jz>I'Owƨ.5Rӡ <SHXńB$H| h5:I ?CZl8'IuݐN¸əD;IR U#T SQI+XtK1W *9V1rr^+!iתVp:MdmyP(>:+byqenle$'pZ @"B!eW2fr$L@|.Ђ+y tЀ]@gR:BXeQu^e%`܀ 6&j/@V'h`*׉Und! ڴxA7'Ph@5tWuC.~ /陓2̈́b=+(jni(Ž/*A,{q.6`^hJ:X@V @@vtg ʬX|+XHvqԚ7ˆ(Iujz+=R!3XLf:Q6)蟆`ulQkOש"@kkB%(uu<P>PJѴxΗ>ݓoY8xıRBNeZHH]D9%9Ӱb|w?j\8rb.6PYEEQժhlHp)l/#`K87IR 'q~2w6ⶵeJջQ|nЦg$\8D0u1n %׭SyET"c]a!$K)CijIc+1 ;jxOt'R@$AN@kuC !Y$\  <"͞&`5`A#F9V@tn]zP*+ H6 h-#YCUsCC<2IJ1&lPXĆx 31 ќaIiӤU—7( k 4ArbƉ%8L!seOΈgYc~z cu:( ʍ 0 B@12UƔSh`ԝ":z6`S dp40613omcAs,lTQ{lQh=+ҶinY(cZud;9T#ZYv%G3B̷̎~7ܳÄZ+Z1oCoZs!B@ :[}Ck:hОWZe1ApjDAqڟ]iJsVPt<1FCx)EX3bϜuI3 P``1@ㄠkV cP [_(66g8d RjqYH^'uqd?@ထ(-Jl!eW#h2RK>2sid`@`X叟"EՏES?YӍ9Kpdc4}*rY4ǭTMܙTgtghY:LXrZ. C`Q$ӕ`P x5^: Fx'$\][doðCf~Bl/{YIN Jټȕ[(B[&Da!U4ĬI;'˯ssqYU6UR+6`J~3.>"Yɪ`w2zAH$ӍjG$QZ-FjN(bNh( ( weޅ\Ψ\nCejwQA(p\n=X-6r#1jMZtb;v(: 22Wbs =hLr!ԫ9bQ3փt482J\G5~Q>$C^-_ +mJQOD0Op{B {6[_/){K48NEBZ?P!Gܱ:%h)kc# 0ꜻ쿐*y-ۦMvXU-ӥmJnt0^7)ހ@w]s:ZF2PL|`$ߋ @xA#:щ0a!) R$X|3 nUͲYA۽3Ue+nm`"b`Sd!c( V9m®!̦ Vd .3&S|#kh qZ!}p rgAA1nfxtt{JYGg^Y&@ rsȦd^#g`0SSc nA肀)pN&H{EȵPԶu:֗ĸ)8d`^~j|*- p^ty2=x~x^P郙{'Ո"pYpW(ی4cg]/XQ@LVק"a,{(cFlT}+xFPGgomp*n6\SP 0 F7<#NpNlc͢k&ijkLRcgImݽu(C"He LX2fp ʤiD. {<5c[VCәcZ\~|Ŝ_5SAJ^N\wT_RKԠ;ꎬe4m `ܲcS}%9;Q@mk<$tD@b+ 1('VF75{r{>v ?+ r& !o&5>06>.X]#ǎqIjopeSd}C0 .;˷ 1eg.K-+9OEZ* ÅC/奱n~{x޽ۡ^#=<MxxKOXj;(og!X;H} 9/kxZ=X_=||Χˎڛeݽ]ZLpdZziGӁƃhs{j$-%wi/;.^[}!_;xP"E]#E Eyft[sdN~En D܆,UJ}Z`E| f*5gEh`@D"PȰo[P_^'Ž e(Oɟ+)f 0-\޲sFaOĀVylD"B=;ugߥCL/Z hAr|I_ȭV.UsLJdvx7l=H# cj !<Vx^yԆl[%;0@g\4 )`.fN:z_|eP!ϩeI[X{E3:@,dn H%A ^9xߔ+Yy*jNy6.I'=P{$[=>9o(q^0:pIP$^xh֚?rGш2j!*c>ѽOUuZ~J!:҅mr=o_s˖^jT;HGOoZnpִL\" P|kөH*h>{U+ d,1A  #b0`XzXG_.,LA)? Skh:-H]i)mmOĥz Uy"(FL>ŝ-XueZ4zR'Ɯ,3P:zqEя}dI!S)Typ-l1N~b qC%=P5PU\N_x߆ڴQAeGg`4ǐjl=(祃H@á ]8\n#MtjPe ׻ 1bf:h 2VWz>\Rh@)?uwebܑV2oYG0@Ӌ<9q֥0y hY`cyJMi0a!U;XZ ^b`TNx1إQI;)(KBXdhvP'XӔ)E͝?X4B0! 8Τ;ư=1e*eum-MM!\.+7 ,ڪK(*,g/qQo;| CFw B( N Z],nW^OlpݭA!1ks ** !%C̓z5f&) (&OMR /l@nh2a.fSjV;nƳ+A?kř9]cS6Ň ,L;"H3 Z -d[cv¤Rk4TKp83ABJ^8uj''wJnȻȂ#ӿs/ toyul Ɯ޺;;>-\ nUΤ5m@d}S] TA#l5Cqm?2@T&ݑ5[\>1Dݟ7Ix՘b8BPpyju_[z-1ydq=䇜ٌYZsgPA,h&hq gJ.Y|X^PpJ#D#gCq#dK 6<>ձe˦eƅ UV׼(Gʻy8ݯ Ma f]q!7Ʋ'(aM}n9{eQ%F=7Z|X}bo#gn.$w련ڈxw݅_(M(78='_@+f [-"u^n! e X"21=Qb$n߹^=0H#oy+UKT oS+XQTFRunX'=kAa˔cbRPEHܰ5[P0Aj 0 @fd,m\ IX @R<mG[,I.U)o5W8`QG)$ g>M<gh?6MpR95kAnG)T60=VDOۘ( r;nghj~4]9;-+,N41K1uWdBIh G}'Z+e[v~BJ2S 1MRgr1ԼlMMU֏Lh\!RCqWP]LD:NL'O1kTo S DA>lN4l\-A#APhtfW-;Ɨ5(+bh2 $TmF+&a[u[22B }ėT횓zksiH&Xn?./o@3@ Uyp{խ=mEJ͂;=2h*X(Dt!$a@"S$XHa= (eATD ' ]%Dl^\tJZybӃ%6zA?-,3.CPm D{s)~mnvƟQjѷtg}v&EOR 歩*uwF * {H:i@ RxaQd*C1"^5g?dQ HXbAHl qnqD-lC*Xf.e2 ѡlEZAs56V):DO%+r:b*MFVfCxvR|-3q:QZ"gY4({N#j\[^snv 6!KN%ZK'f!B}zjrokuxX`u6UCOJ-䲢buL;u2;Œ)n]^ 4xcRCXPo&dNN@#R@ պτF}35 f<(DI$γPrgҽ`vC!%)KL. D\Db1}h?'LG17@ KuH@@* tBe>H zg0Ldy[ "Oi` sHb@hq͋3!iӖ^{bTFoD$wF(cdÁ [i)zb2uѮښcQRB#e!Bf  r1S!`ou&r_,_2Ξy5I 50;*A„3?CѰBь4aTfX;3ζUհbJZhp*rg5dg߉g2PTe ⵟWb؀LT mL0X b 6рIǼ4 1|0h~ 8kqHR8"QEJ4O .\NEH]}{-riEozVָ e-sW%ϊ^,D2 < !` @ "CJvWvIt0 q%4`LKM yZT){UaB9t}n9=h3v k- r׫Π$Ve2k2_/>r쫧N.+'YTנ,!PqNF_.9zu^&8o%ޝ,.&޷z/XͲ4G9 qn-}nm:rBEiKZAIv -c/@͢_Apk3H$aXҡ͑ڶߙA l0N!%",XZ4b&;lq5~^O~[u֐\<7eUQ*d@ʷ D$(|jE,X.4Z Hd%" ^y "qpDk 0\Gn<ҡôwo61X1GaD̄ D8ף;WDTg/L6&'m{!A DRZ&yq4?sS{26ΔzS y%bL8&/Fo d3;lMR17:L4/l᙭p#%Go1X&C7^!FptjoZ T d!nK@V4jMP3C'1(ܟ4jjfvFrT]t! ¤[!BěuY=^=NU|BO|;ln&)gY,$Ed#Z!2s(1eJQDDEk)GU@$0v.7a~sj# J a LԖJW&6Znreeumm\]MBQ/Aq5qP2 @:Iؙ+w8==7%~חQx`"0&Sry?˱ǑeLhw MaYp)y'.NVY%m[hȉMrﶈʧN-3i}V9~C%H 6sd\:N&Q =OɬZ0Rךft8^- V2XumT\ꡠmS_ PYF?.L63☋ ǀ_7˚W}d&1 lH‰DQ|P{ n7 f3JD5%R~d$ݕP6L1d8\%8]1"ܿmCfTs c4TbFnȞmtsɪ/nM/P jT8!oALY+jg&] Y'lӂL*al֘(:1P!R0l Qb^M̢y21U$Ω`"`@@lr! p1)0D䪒)aM՛[弬: -.$J\(Ч[xFiEK_Ǹdk֌!#XzbKaPuVf=6o/Lƌ]Ag8%H $ B&@(HR)*]I Y~xp_'|Vx(G ܭcmE1Vϔv`־i!~ۜ|S0nfYM`DNZ t ˊwFpc28O>K6%D?ncebaT(D#@q!,rzIH ? e[@]bm _%V+2H'4(6^~z$b;= /nE=k+[2TsW?(6Tap1* U5RX\Nu=E 0QDȮp%lӔ7I>;̘H]]ړ`:1.^]`(~|>C j9,V|TnAY.lG( p y[QQ:X.3"&"a=j E$1Hb # -WJ&ʚ[Y%U2ehJ(MB7F'g=oşwf[>-qcEd+ޕMNbh2Lo2s^nScie`0{nP}ApU=srvx>@/ZqNWw[16wD%QLAjAܟKc& Yg: &H"mWaFƬe|h ]Ib]sÛ9ƨ 5LPꂛ|?ܽ9Ë\z^{q=ٕqr!+Ȝd>:!p 5Bdvk1 \3G8#j(kGUې6]?q."pd(<Q{OTƖH 9s#)f(V9Jc9aaX1 L*LJA@6c( 20BIJN]!J2Ջࣼipdkտ Φ `H[&;1<:kgYJV5d^>YIRKoj'}p_YaI&佢g0_)$;[habig O]m{U&oߴ ln6[3j(0"4IdiOC*2.L0("((J{u X,c0,.LRǕ@>e6E; w4 pNC›Zjp1VDA [p0  0D~XPJ*x+ɺ0?JbKӧUuy6;2Uϳu:?Yz5/}t#G +d( `..|#ħ*E*UXQjjbKG쬘<;jeVKG(¾/7GJ[ 끁DɄ6Nl`qfSvU.X ѣ8B[m"5*7!|C4mq) eTcK*s} FyQ˜AW)b^`?6%n?,73t UiOM]q=*շ<2d˝(`bUpB&6Z|r$KXUQ'<{nϩu+n x!a ;Zav s=K8:Cّ |.!RZ K;&=a T[\<Ѵ;Wyڌ Qn|s mԳOE ²LDc. ҄ r)x$+CMP]0-M3fB;'++xS?LDa.Y(͓"fXBI-QFlpZW :T;a,WQIa5 Ewm B3pZ/zUa4uOڬͱQ+I .pYe*4l`MhfO\L6qA`D7ovjoY%K)_u.'rXN3 B}*psGx>3hCYwKp4,Y՗uyR&T{-7`ͼš6U|"<Xfm;{ٙ4n`g*.>FX0l#W=Sӈ4F+uPԘd|{wH[caN!3;fWz+ۤZ?U1$fD5K9a[L&DKd¥;"3< ]2l2(0ԼWR i.) YDONbN.n'I$o! 0BÆ^]MeF2nZ󓁘.S(I_ohD$a;~U͵i-|]֒) 3cdNjQDytv4!" aCXRs{9{Lr *h`hnԊUUskoEP8zrRPH2";@2iJoL u߉bUGkg$,ɺFIB8 9T9[1t&;+ :+\'ZJHbMNX,iJ$Δt}J'hiM =Y@@yzo|ɟ_,[(vCFS;  h/Tǔ];wp,Дy)!^snp^7 ].F.U80)HJ$Z98.uҽ<^MRUݕfbH*!A XE2#f=8M`h XV50[?~LB,[]āDyA(|&Ͳh+ɤU%N.LJԆ%R!iXޛ29L`\9ma!0/ ( Уwx7h/ rq=r (JDW@>Ps+MS LGECXm]b<"4G) @I@(^WRчij2lL9* U;22atND1@ fF2&`lnBp06J@ŪQfhJZaÏ\'8_͆m,O5]Y)5f(սXqP AN0c˰U)kk_fk߳EOb5hXQ>&z͊潧Kl07KPʧY I.:s_MHYxf6:P/^$lZaűkXM 5 ;tVNG;`>G{[fmbU>Qxə/r?|PaRǂR8#(-SU+Z9U)W/6_f|t@XX%`pLɦ/2h6tw2],^Bأ r(O~Dbߝ pSW/l]`N#&NmL8 f㸮N|R@ecXZZ "P)呑,1&MmV;). ֹ\.6(A3{gYE a\mcɉ)_lA\mVk^+!Zm&`<8H0` z:y@8,LlD.W@'kbȢjHr &eHݴAv&hsPUJBZ|n++U3 ƓI^ Ϻѿn-_FPRZEWϻ㵛`E֪wroRZu,f~^\ΈxNm ڼ747Yyur3yXUB躜/[U|R.859.ӶYM>zrD;u&( F7W 2HWЅ *Wzd4GjMs Sف3>22L=3pA?wM t̓0͔B'MƜJl ,tke $.i$[BWts.!8*+ @Bo*smƘ)z|7eFmtTr݄֫|ԩV! ׍IC{();ynץޞ9Z%>cˍ2S?ځ -RP⒃EnIQQ$"bV븲qSdXc1:`6 nS' jqȋEsНl9$[WKK AZHTLA&c@} lN&n<GQQ.d'ԁvZ"gV|Xu BD (U1*05bK7#XfJ@\ JEرD0ĨH"1%Y HeL!t>f&Q@`dRHWRlMZv&m* 8d{5SVĝk??=dqK ^z2{6&^Ă)ͥBl@8@`!ڰ*IOg,j8w}έF_PEm/叛e,n "6 y-_̢#MjYX ? Js0 {qW5._?őek1#kn ;Uֲ7J@ (bLCMjD00vÈ1Au=(Q &nCN  ?3xÅ&)a! 6o[H f= :s,[*YiNu?O9dt֜ x5w&Xr,_KX<_~MÌ"-Ul0v$p_,8?#c jBR4@( 4A *bbQ҄ LH9-"yJJp%P0%,$4F@ U|R5k2Sj]Y02CTB ppX2v<pFÐI4} g*bbFޏXY]:*uUY[Sjǎ^N+B!B24"L$S*׹e8hAw|;~骷w6Ċ -SlhMw+o{l[b3i5 >>4r+lvw͆?%>.õQ5mHɨPl/X!.3v;:}!DZ?Me RFX  BAo2kҐɤ9Mp!TBQ_БI!DV%w4U/L'`N]kqmEW7Ay\LLR*ADGiF1h"њD7mDi8CvpDuPEMBz▩,J$ aك$D@@g. RYbe! nП!Lϻٙw^pC=U.Ӂ7;3;[!xut6˪_Cdnû5hfÑ#[hGЁMki+nz\hJD&qB]ӗ:oLy|desϵh `JX3kj&y5Z"+ɑl<bE*@ؕPIp5/`"TfR5`>mJ&p2`csFN q=* p0`ޟ#'ZMl)P`] %ŀA?QEDHc{ɇ3µ=3Xtal!%(^DžE}bWPZ "KVx+HF tll"6;lq*@j z[\rIţ9qqY+*6<`DZ)gq"@`G/*ȃ)RI1YٺӾq!ꈏ `!A!T @P PFT }3Tcyr/,_ιS0o[L!g{gAl& vHjjjuH.ZPPMBHI$P \M QXdiVU_g_I 4oUu^=P>e9UA[ ] AJ22a>Y)bB`!Rxq4F> J! "2/.6uWa0uKW 1O9)ӾW$26$yl 𑺹R;`sx M)s:m*DNEj e$Q 򌾸c N6i &M,% u S .EH$oɾ8  ;dQ7(p9!PT`"GGvyq*IUhhp(2 Ueʄzpsd+pY".)Pt 30DW0 "`(Pa݉˸ S~MMMN f2a^D)Dn,#}ktI(;`1"+$&fc O 4%V,A Xq7Yj+9J:A/Ox򦳁m:6|&V*XkBA]4~rl Yb)d~y Cy0 n>  ݐBh uwo)܋Ҟa5: D`yQth, N![MVB1SZջ;=k)Φn-|쾹L 3ԯTXDը$Bob~ac޾2}00;L`t] x!۽]S @!+0ž9຾ʽU Z t#E:t( 6vaǬyd /uUp#3!^TM"R`c*/dI'WHFz 2C@t2,jrZ^VCb%gm r¿yNlI_x["/U)1! U]ΐ^::HѨ[!v`~(B{%10 AK ^ +tL["5tM4]N,+3vG9xʫt DdK4OD&Wٔ.N1M6ԩկ)/RD, afk P8Y4ėhQZ bhHmc}EDe@pҴ^l*'T>8nipBW`FW;Z*"Lhupo~&+ϛU.0Íc2UA-:05a$#],BQ+6Wh{@qU4f'ΐ^//]i׷;!b/o^Rރgq|62J#]W C Ebُ`E{ \5̽zT,X1,DmҵWhIgnlV$"̭!$0.j:Cxx "ڗz`jFHB!@6 HcO_jagzIGnտUC1C5 Ns!2 mXPepwY6)Oٟ*B(C A) ; KtCDŽj0}VS|1B'fW3'I&y9ѭTY X }*w%qJd:0o\o#@o 4!@.ޙ,wkh7X U%O%~1ڌ0Tfѷvc_XLx&|JJˡ8 $4 ,-Yyvu(ѽK؞a0ۂg/@}:FI,bNgQ eaJĎg'MO`"+ Q(ǐ,qp2ٳ}UQ $3 ,`ɦ0"}^rHZ2uO.1D` DAU GH@jO3.ٺH܏M{ 5@>$p9f]m wXrlƊBnT@=Sd6"orw"M*]*ȳvm(`Zg0F95Vj[N_(o`_x=O5eJu8<oA2b2Ŋ_.xʝ/v|SNu0mUE*P_BEvDJ;Y=?G8i%@6l9$qd-OKp%VhQw+`kU+LӽЧn,RudH%Dm_jؖo^fT-]$^ޮ4IJ$@ѐ28ݥd%&]@n6TX) $+sd2irSsqqj\,6ϔea]h5=ڑC"1o[ܒBaA}FVGű<ڤrS*}ba{n=eQEf8l>\;ӱi=۾KUvr IM7gc@9n5o8wX^w،" {]+'UتK̈́qqۗ>G35x~  +}ָ_d-~lg`fuPm ;ŬLʜGkH.&0ȭ2G}ݗMc:ké⺷V7xﰻmAֺ?iR t&0~P:\_ԇ8O'6&e^h*Oco{;BGrǾ! vAդnO?`0;kR 8Nj3lfm\^j}Y*qs.I'=z Vc i+ra᎓zqZe̚O[-%REZ1WN{ȭ23n!2-#65nr֥bֈ9'Ւg7?f$㌝.MTN%b, A|݌()F^1}U'0(D~R@4)}*1{GEhve~vĎI]{ vu8M|<bMIt]Evl{i ABB A;n#VB=yh`$̎Qrre 柌j< j,N< \Unscwp gqpllJ,>`Fti?u׽kbx_W0H{Ĭ1 F͔@^|Ce:oY1-{݆kLrbTrˇ?޿/<53ŖmknT.b:uVoY1qhT\7 }Ԥp{3[pԎ:Y]H6eG6ͣv^)›A0inæt|U~l#eŦ}4pNn2ݬ9U<:t61Ջ-DKD8OGhBd^n!X}1aóˢ"0԰&,5/8|s>㝃Ej8S-f<~jj~771,SKS*`#C,1 άti1lRI(TK MBE@Xftϲl]tWRs5_*m62Y#Ua6uiG:;fRv6ޛ6r^Ծ+߳8xF(]b|mIÍ+2a,F7!?Fdv>iCJ\!@V|tC ՘5_Y^9[Mt1u3`)y94͞IQK'`DHnf:04)Iri繛qШrvxҔݤq/0$TRfAUYOۮw8L8Z܉v]%w{w'u(O1&j{?HHukfYߋ [AK`|w @sqtkN!ˋ}NYk?7] pw[J{I4Դj/𻅥ڼd̃|ʁzW\xpo۸x3cg(W#XGi;0x v,wxYz{$`nd=(A/OP(R8jqd C%ԛ~V{r|z~]dH\܌C33%ke dNٲNט6wX5asKE['XU46:dw;>玏7qLK@5=A`|]@ ?^׆<q3K|+W>ӡKsl o E>Ïx9?&,ꮐM0i^ɍ,c_nlG]jl %]&k|?E'<&}4-qcYrUI U@ٕr4ZGcz8+PwzoegҔ \͡޵ 4@1}wMzٙ\w>%v+vso_UqξAZQ]s߉{4X NVW&{0xw,9wg9/$k$߽G Qcl@ P毜;hޔg.+Vg8$@.e`i*pyKǫV_˧}}&Sd=@7"{+7v;[ٮ",dcX @Q5e$WhĹ{zJnbuGrp11-!Q@b#pѠxRI7yuzut(4`t2CȱMd :co:+w?xBP `#^X :25o./.V 80FlBUoʛUa R $QwS$ Jt_h:ER,.6-F]y#ZhokJ3~2d pdž9 rgf# S VKq'ۓgu慈!! UcE@(τ@0 -@X o,Plp 1iUT7ZuwoӞ(& f==kVKm5P2nÍO_s~2Cjdg|G8VJ95N5o~%L#Vxnz|1kF[kfߙՂDltlКӥᵣ}z}c/yW߃`jL?BQߴZox{p?a=5 <VnCǤOJw Z #,29$!Y QhnWB!{ASTB -i^B/ǘ{gO: +^=m9N<4SזXO$<^I3|j{jdˏ&3h#[4Om'~{kna'Ot8 #e7t= A`w\HTʙ=/w6 ihSRtjSdٮ`3i,'%d_92\-zQ+d$OL&|>5hFO e#Er7ȅ&!w;]FRdѰ5V2בЪZKɢa3Cly%x mz%Y֝ t$#Q&ӊNJxq@b9/@D¶Qj ~rD^Z09Κˉ[8W12oiL_sל}1S2iP'wl/ #Kb06z`$Жҋf^4{p۽73IwirZ1dIQ(Ktoͳ<1p?-`xJ;tx>Re镚EP!2Apk|R_( 8$@BL`XƢIַR`(IA1].5%V인#kFbB? RD/R"eM$uOj00(mԸDR2&f;ҵr7 3vLJ>H]`؂)] S', C L4`;<\n"ý%& ]z 0tN5C?Jłùo ,[7!֪@,d3@hr|]L'Pp@`*jꛅ+C˻flMI7?Cʡ+gM5x2^eߑH~%KV$49D 2꼚PvK5Mz*oљVIuf4y.!״Š6c׸ #* uO&|"2뿨hV7wԬWN1~ CU+1M_&:R쵦Y^<q''Ih?5ѩHZdg Z>0\Ο}7md_`X򒒦/pKmJ?4>D!I bk"Yۯ^@DjӚ6~#._@W3uX婬xm'ⅾZ}_RA3e޼ϯR=TCi?+=v[1g G[+ ~DlX΃ (-HIHА;<|E=* zmOءD>g!/.̯=Gn\ޯei+f>Dhymk}XQ_-R%HNN&_+~9{.C]Koߖi<(C>7/^ Z*ΙCXϸCw\VaS\UE ]?8 lvTeWh^;rms{a*5wM,uAzC~>$X$0HӤUX- scxwް .OܓQp9oBg/> !~gE}w 4dɾ7hPP_cO=΃N}h<8%ObB567~+p[Aci~`z \nl}RtPD'ilo?Z0ǫuͅUYx(LdIGݲi#];Pը<:]amx?B? ē./>ج3f>d,/cnMi%tPc/q~*K7e dx4.?pRAWR~[F]͹AwI[]JN nGd&NZf/M.n``aڿ>_*cV#>-ujngoNȞT橦Ap0$`8C}w #1tpQ'{*hAbL\Z R+E—SY3n$?h3Lo-]l>a:jÂ~{wB(hyd:^wnRW.F$|XIsմք : ȈH)/_ t2 X1ln{>]\Onޕ`5 9q.jFpغ}`&n +4`pti{t Ӄ!DAD%17 fJ /$(h(ph$P$T\/qw؎~V;ԲءGGrGWfya%K֕ M"ux,Tf)P4tT[yIu)ņ `HesVCB<3gk_]plHpY|l.:+ż <"cv  T΍~{J{shwCh}~gf_Q)xFx]F}8WVTȵiIUz!(Ԋe0RAEZ*1NHA|8ܲ%=^p N&ɓ˲!T^`D,^"\u+ z_IZ֊Å ^ 7Ni 'v 8Dlp#_Q$!Ƕ;*m=;Swj)ebl zc`4*>w [!ml?KT=}SaItJfߵRa=ܖdqN`GJ[}6%~SWn?N†]8V;~b`Z Ip UXkj:mĤɂɢ0dQԻa ["9wL0#QeJXHy+kZVijx$t %*~`S}ezfL*n޺#ғ/w}p0{E1/ۄzJ~DO݇+jqr_'y8tPJΝ].!/Yk&H]oǐĥ76ijdQcǙp-j:Eϓ"ft .o3~Aƃ;=W5o/ZXބ[m&68ĵUiͨQuV%R\^ak"j, ? .S+'6WqJ qխn͙ E.=0|Z'b?W٣n,48D o?6!6YrHeΠ)E(cH$+ј%D+B=Fȇ =cZ]!rpd#8ʠ8|vGx@j `l4oz\V:Ov냥ah(ighT+MqDΥQ/ EJ+!W]q(rgt|*٭+~w˘o,\hXA/$2~@4R POT; ӛDGdG$ 4d(Œ)#Am1HĴ̻0AnƗoi4 %eVd#EP&jhE#E. %`mDŽ1Nѻ78ETR'ϏCb~ykA'6mK%/2p7HB8=xS~&NX]J_uʟ^`[NgHMTi Y2)ѐH AHd xqH]$f'\k7vN)=-v"==. C23);ED #0;A]Y@pJպf /^rlx xn?')<^$3}=rwa˵ٜ7:NE /e_Q$䕜TOhaǎot jtXG8p1[0 i*z\ ZD 8d T9]^7Z>.H܉R!橁F*ZSKA׽͗6dhLKS'K͐uJUnňelJƢ|"uU"x>mGxFZuD47ȚI V?2p>oRpקMI[poߧMܛXq >mJ&r*nʞ>%ZuDTtu孱+I/pāLKw݁e"fTD ]BwY# ZѬM/HS=(CR23H`oXl0_CE_򰑹k!1VTv뗂 !|Upn/at\FlGW7apPUh֜BG|E}2˼`n.O3&iNrs-"faS;UpŃ_d8ʽ^qq7WQew9XLUmEH}3N?Stuq #NǏ>[u nP2ʀi`К9zLz0L#wJm 6{Gݙ'-˯kܑo6Y/QmPR@(w&4[?dD]|XV"E+l@H)dNHi 'D9i1TsX9ia5k+EA\.uC*!m}UPZc q"=ev6rŵ/w3 ^02{!P*I@̬uX@BВLnByHvߤQKB,fL:&!'qMG V\TŜh\,mdSihh}Zvl;¬Pf$f!k9u$~ɯs&T`_׭I'SB_n[{ N+G,=$Nqo K;R:x_fokI2qN:#tma[`N&<"GA@7$;;nOFE=%Y#,[ و@3cN{[ص[Řk/[{dmqU?hcUM[nmJ:8(4/q&$JZGr g5*^aI" `Yώ#kfOޙ' =Z}M@Š:ʠCtR+DRX%ČH:zGzE'ٸsl\uyèZ6)8%._{GkW'z|i!ɡ Tb!%;0Gőzȳ+5CAxhOkuUvטJ) 9\ Vin$;}޲<&F 쒍.r{ъ͞-BCqGD21U y "|E3%ϡ=i}ul| s -I#OQ8VRIS|L1{1}H5pDS_N0(IRƭ͡})NS 95׆(CDu'Rq2x؀9eQԄRdU3s ƛ `6 șvpk'7gJJlVԐɤ3 6pWW˗t !X A 7 e=@РxrO%M$vSƨ˖yE3ƀb>>1GsM7; o/m?gO®1E9~R+d lhJA UR((+0 B6bBЗޚspШg5 2raP^{^˕#VӪma;,jIezhwRχL 0f(ȞF$*}׭3ԡpJ42k8 Ǿ)D WYf nlɗ_7 mY O D8W?w^įT;^<Y?HD@f[Rt} [׈Be6fZĦ1Z]5O+ܷNy,9F61EpB*HM59 xK3r/tq#-_nlrBC=?SOf1l&6ˋ}anڻGf|C, U 1~9=2^l #H1 B\׍Λ/B ]G =hcFqs~un!VePCRi2 )guCeSD$,-Àߍ_\oE*Kg7  ?$J\p*c?~o?&=  da­ ED:`fX~r$4_cPS\N;A/;tʏ~ 'js6sxS{kݗ1Sn74̮rLH&4D V u^= V&]~>#h409l%UT Fko8Qqf5bjaO$3)<*PX%@(QH" $Xm_OrD2$deKgoB{I /W.#d.S)i4km_xOĺH UH+oU^L1$YO9Yk&&LRG[\AZD"*R;!T$*j$=<6_GYxc#!3#9k3qR`rjC'xOv@+.q;y@C*ȭ _F9t'GȊ$qso&` B' =VYG&f;*I4g0 s n2QgX[{s9]~> "k@*ZVIW(Bފ߆HM Sg/y/n9}ӕG[E'-WXWR*'zMm\eG l:(n'P6Q@"(D! -J><;[\X.A_{z;vn=΀fUˢ(#]w-NxNAy0EFݭ7;7`]A龷d/= 6QlȳV3 L1  I%'$ ic/Ƀ~dzT*:=1۠)]%#o/2[<Z{K}Spw~Fcl\DǼ2Y Bdqf?ms&;0+ɇᱯ1*,ǭNµLK<j&[ڭj0P֯ ?GWĝ/Ŀ{g3^qd:K[4Vx̸8aKh{?ۥwSga$M μ1OyXb^sHUlM@2bH`+L=$ 0QUw:9Y N0*D=!ܴEc}ܺWRD&2 Nkɫ/&o03転UwuDM2/NrS<~55g,8%^⼔HPChrص6ܵ9/\&"/?m*L6aħ "Fqȧ6y ՍR `G(yn B"LZolkʪ<4gVTCG6OJyd>2}cGwĉ~y'F11fN\1T2&[5wa CR]}>1n ~ À%Iϛdؾkx]KˁLzBi lhT!\7CDrF~[֥dgZ|@ G8gg%Δ} #~WI6Q(xg0:u8uup1h< AAH*I ~wVz.S،`R4Sva:F;k7։aThW,J"%珊WDhy' s@t88cUk{tjSӁb;7 F1v V~`4dDrGUťIO5w24Yhc $A RӀڐ.rT>~2QVߴ(Uѽ^?d'ϧVSLGGjH҄wt84UlWxpjaP)+f* !vˁˁ9@SjJpyOG/fO?yߥN-ٌEa!dÅ\Pyf%&& d;9z&\J_I 78D˃IM ċo?տ$ +"EovE1dXg]vrgesm0݋/ {Y!{㔈L|a|Ok[W% gJ}D?iWPU}S#dwܗ~y0ړU Jnܗz`P;"HJ0{ݧpLK#V)昘C$bL%"oX+Ou sCI* șt^%Jlޟ1mQ9;jZي<4.I2O6;Jj$6FU% qC]NӁ&_;Va~'k9}x'SU$h%SgiIrOanbJSh%q~R2鿵ƚUZ+{[q5+B> >3̴_NA(d. h]="9$o12CK]R2Z8'oC(bDbj4icpP|j%@jt< &+v8+Zhb F̣@[˜ط%,N'=n&.RUDI#҆T3iۤT-l|^4~5{,Z}W_m7ɉlson4'GYӝeM}^ @}i8X`#0Pb֍vvU_ĕbU^vR ֶ0`+Mn8xQc(*@an+1񊛻w7۞zXA y@?5Yev:sY޸(k`sK5>iW1'dDlw54W)xS_^u]+ܪ8_'<߮Ń jݤ,jb[Btv:jEU+PJќQ#)kIha8(OpIt_3٧*7Nd ,7#c=?T_QzxVv< YP8ЏK6kH2ާx2(ҏ~deH#cw~a7Z._HX턓+%DPw$9͒ 70Ha֭D]ȥ8ϚG3NI;a-1H 0r+#_0!"4'+!\0.V;{ ̅-;rӜ-t/.k 8ngsʑ=ʍIOGK+] E ieG0 D ҋL$VgbNsS6 )ZⷷV vOO={Ra8!˜v_\~hCxQҢ[O<ewk9ƝZ,i&|dHxC&bSZubFݫڮ.I*( Eg'ZEbRY2ǒ"R+iع ]{[t 9ZzZg#4 op3^tVpdf3'ydkÁ/wPX 6VGA':s3:Qmdk.5e@ C_=Mfzf#0 }!-V!1 &N e@0V vipL/tu$„nSۀZ\ x4)|=Kw*A=b k LJh@n(8U^kVLz|||0cwf/yJ3o*0< xؔ膢U'mlԧu 7 })L#xd ݮ)ŗz*-ȰehK-/|Kźj,P ĬizbײR%8_0,Ju̻fR cmev񯿪1k3t&^u8QAٟE!RP5dgu:$7TG>+':d?pe18 x rrK f[rJDIFbԴ@L<˿Z98 z!c ˄סȎsr6+>Ked?OM=g-_ʜ,'@  KSB@zxH)^n> IybÎXxBBjj;$VnWbp \ڽ8[a=ZFW:sNya Bߔމ{@=BPAܔI$$.?!Qh N*̓Rto#b\U+;3PݜN%w9c%xouwOz1J7GĪ @aY(/'GDn^Y=`mabMnI91@b !Hw&]ڪ|SѴ|a7,0]vQ7S$Le-O+x@@~&k3 l2yWn`xu-̏ޤ|b 2Ha,PYdg\F[ytFFj7^e xL]fbGјbT!Uxmzأy[To;:"RƎ/ό$?*DlqђOu7ݝQZޓ-wb^jU\:Tg fP0xVhrHu3FVlS>d=F§=Aڨ]Wh^ `*#zI6 !<^>N(dbzDRc.$g"<el]ẁ!*:^_pk@Lsk?k)-vgULӅLh>z`x )'^79!DFˣ a,-P@FƮps!$Zi1B2>#Ufw6w4.uo}w._M srI&@[3a.&1I tW5_W>:z"qĶ7ONqOF` N`h_.wn2l e {U[lTJ)W$`A-D\$[9H 9-D{o!VI\ho6H~ e2Pc (#Ydv$o!8ia6 \ҞFƻQ4qih`D!} Y@Q=>C?C- Ey4@ fb/VA_ fގZ/Ps(}aT-INYv6x9)] sr<EݯH6MIb&Y^ƝBȓ$x̹4#z4>굮p^展mK)iK_\>}?U9  x0at#bA=1 O^@^ׂfٌ' ya=/n{Nݏ QEu')Խ Жfv]}CZ#:&<l6UǕfEO^ $lX@f>Qä4f:՛7gKz5—X9ώ(MO ֿvF,)]<6k}{vWDDz~4x<8|W+S2xjйYxbJ:7Vu _Z!<(tf;NMkA<̗wwBQr23rK8b/vɰd @ (G Oτ< _ 8f!0 TɎբ[, " d\<q>dh{C`x걪I]\7۬Ѧ4m#A%jP6T"hmuU#~7|x#lA^ 1|b;c9XGAA3`셬pdo-x*3']iȦV^ Eۥ72=B_qpYb Ic$0 DTmC;C8zg)#v%Vd0@".VSU`T{ MPq#wxksjJZ6wXxnچՙ>fߛ Wqf74@@I I:z\mg@%b΀&tCJlhpCAyV,c5C&<^X$0Ƙ/nR92 /ttB~stshYgnx (aMz 4bI<-v6ϊ~L~350ZcυggDהÄ@:d%iY'mMη!){;wg9"17]4iN6#囚3Yh[0d1Yw)$C]zզ0K7к ۯMn޳hnsB t[a_uuzd11\7Zdb_">jBp0 \f̏q` 2(%W]nQzA$;Zm< '}d$v3!$t jtv ɵ CD"`DD1皡_Ƚ]5B!}`e*#&9mI_+Lj,׾huR_upY؈IIq(PD-E**.^fĊQ e4B=Zq%sǍNW 5gUst-ubFI sm>azmNIʕ]k!f 0ā؃;&:q8Ezy_db#ڦ^o&b+X+;tyD í9~GcocHWs_~1wG/ț^ҳNRRӿ;7 ؿ?u](Λi#q:џe@.ɰ\챴q~ =%ц1ܵ<IHI@S,jw~ 7շy}] H=꭫+YA"2klN%BV,_7rr %.ꅮ^o}~ +h/x@hKb"7ۮ)rfH! ~"@VKoӀ01yv֞&p`qq~R=|-#f 8m^l?v*DT*@h":ݾLuFd'2ڦ^iisvSb~]߹,/Gm^}۵ lUfZ_ zN% r#v[MC*k۵.7^_ޅRX1 ЉPJ!ǡ7Ng.=m {%룠 ɧ O;]rF]{8Mk?j9.M|OI2. q*f,_}dzf"V08ԽksNuV|sӯGfcEOXд1.?Ycp N}ߵ{}">cF5bzɋ}a6I@S@Z(㫒&Oe@67'q-u^sϭ?ܕcbօB՚I{dv7 WF~sK,3 <Tߥ?1^㈱e›plC~u~%@~yEmKLK~d7UjyH}K'?hd@]ݹt=`,lYˆb/m8)?Ǔn|9/RO9pC|R&ɸ5i> A+H`@ ? x1;IծnOm+~o3e\)ik?g㨕K$ntkciV.ceN$[dx\jGG =/]}k1dy']4>Lo}^q1㿌5~͛w"DaMozbVÞ/,FGg܊aޚ 0JS`@ <,,8Q04L U0ȨjзԾA@?`PO`vSl;&KU"Q^E ,RmĚW,ӧP gkZ0sIۋ5GvVJ>I6΃a!0 xcGc2~s9ڴO(W1E-(sY:U$&8cM-ƩNgfq'mH_[wly>|1{mě:ܖnw_aI"hJAKEܕ>M0.0]ᣛ7rF <%ۏC*JYđQT/ʿ>{. M'aH$* ? /=]~5Y3]Gw7d̯%RS%]yqvV,`u/=*9"+Ė<T1[ʳcHAC_{7'&T,R횕呓>wo} ܾ2z]M;_*]ߙ׷/''͍dXRW)/mu,zO^}G@&FU S8#-DzJ"͊A#tNe` ([gUt?|nc X 7g).C]\9۬w,ڀ5sNKxgI}3HW}֛ʴ#۴jvUXzeh׻iI]$+?1z !5U Ze:U& #PiʌRzJbUAc 轙~'/_:xsJS*e_DOhc;n$1mӘ{|<@ug&37:5ۂ*6LYq#!Pys_otXWyf$uۢ$-.mM9n۲ 5v$(Ī18}` p/fJQGMP-qkM+2zuO?}k]e8iey0_sRE;(YnoCa  1**T Tvh h}5M@) \~8mvЀxG;2'XvJj '+ Ok?`F+ψVQ9_ 8L9y:#w@}n>]՗{vm2+ 1)9UWM:f=7/x u09cxa,hȎf뚒33CLn5Mq~~YGU[uǡM/asMK77cFۄ^紮KidKto%//GJ%K}F|ˋTϕY]Ң:zyswV~Hc/vBQZ3&!KY2τAȤY*` o{ȇ vvUQ{SxA pO9|"(_kO`-/(/?_`CH5:F 5?H[- ZW{k?b\Mk'rhk6~u\f'ޚz.B#m jYE3T-+%nxTyD8oȕdD9\ɡA^Xd ṛ@"~$6t G/ؘ'ߵhmao3{:`GkdָoS+>:ۅS/hǺ%EUm|OI|x G: (gˎmHzg޺6SLDG-RsfȮW!)g,&(u"3gx/a5CY֖Fep=F~ '\އ֏4-(S'M,lwWʄkS4DS@Ldh@OY 0N@;n$,5EF_m@[3ۿ0Ͽ<;EVzg{cbjQ1)2$GX{W SO|׮!pЄWÐbXav>Id9HB( #gI^eڍd(.b0d+P$)ݱ׵OYݏَ{ /0֣BB9Kq x7.i0~fl`нԃ;{?>ɶsU0Mxґmf_i7TA,v=Qv̫%<0 43ǥskn@w9v_`O|%"GcVx )W^r 9-0 "|;}–Y [.A1ke?yU~Fދ41;y{ﹹ4w!Z"`(d@4.q>DUQ[ Tʡ.Fq2b{]~7,HG+2pYQNؽ%SڼC9˖!WT QWoFHtNK\[} O74 * -?/ >H96G{+ƊZ щ>KG3촵̜&ҐQKvѱVf3\ 1OFh՞6]IM Ll\-ޞJ.Q!$aOv5kW0U7Jj: ÂA[\X='oZ9[Tr&5-[|Y͖lQϯn9IP4?8b2$CU7:*"Vvs(RASٷnIvdV.\ic|dYJ 1 tCmgiB|;}^VLY|L!R\0l4Qlp3Q)"UeW/ozޚmN~2۝NSss-+\5=QCLd @x=@U%,#eo@zO7פ-6إτ;ܿRjl1%% l?%[t39Syח_&:ðd=^£>"?ѿXjZKaS9Ws_ 7c%q>+)l˪ˮs7O#,a^M|@n?1Mgy䦵2֨:s*E=I"Kvc> go?iN2K6|i-LD*lr0dTߨ66N7z&pvRN&x4 d:::Z߳:9fA?75(O &^-9oC*#+MB'ߎk- @XRtJDb@4Ys5銅d[Ω/l24~wsBD}Ͻ xP9ܧvn!yb1[$PDf z? /⛐7V3dGIny [ FGp?f$O x?X9m'om-ܫj! QrPS&+QksV?( <|i.Y.k-7j?c%ւ(Qv$O`N.@8zh'l^K/.'GFk1uÔOv)RR-$H>%J͍[ȝveW}%Z'2]K`AcRGYS o8:g"E3^7s*Zc\ͩY-˙ F[- #6 uӥVgce^ˇ\D"Oga%eF.3%Tqs K1H6cͭ"NViǞN7}![n83?m}r[S%j).*")lԤB:kHG s V4-/j "X:Pٵg/?rU>c/يPGAv,oATp GΌn1 j4hjjHTC^ʘ.{~Kz$gu6׮ZY@~MwF&hΕJv)!e%i$$l/e}5DAV  1ݦ&cSM lT#eȚ+YFSDȤW0fA|Ơ;Uَ;n* ォ}#5B*A*(tjh  Ano+-FXlPN 1Kq/FgMyz"{OiR ̀$V jWO:KOv1OH;"y3L\kk|Vǟp,<䯾sF˹cڴw d! N^ >f;\2EU2Qb*Dp׬c`׾_Yf Ye&ɧP~tb5*ZM-F(29=N^+\e ;j?Ku2*Tc7O~r0@ /;1F1,m~}L{7,fUQ L|jdӾ"PYӡ pJ~ijXoz+gN/!S~D{ؗҫ)s9"5_08x 27Hf’1W J 7B)q DS Pze xz],Gk,Ҡf:JN<.Ob8P ]t``5Dtv 2"@ W VI~q 0lѱlӾ is@::.y7fFqf`Cn'H +VEb_8UY )x ggb^@g0#|!l]JTP55zۻ%m.6-x;lY;v$EleȚg_rIM !k9,`g}'S=¸ ެ ͘'ϓo}ߓ0,vIFO07N E5:!",-8R4ݮkGrGspNVnE\601]dKp wwS-# 0!D iHPZFpdvRC^j1 7_s5}@EʎRB|EӼc>@΄ת6MLܑNn7}.m.wl#vw[ĤB%JW ]+';3Y%F6 {512s] .fP6ZK(,n׎5Į䙦ɾя}Od̑;2_l֖zKw *_Oүdֶ4w6^^{eJ}?dn=@uv(lL6CUE+1g>9 6?x(lrEVX嚭= {,4xW-՜ӨX^r f'SsbMPa k t(lDjOw:*bQ1t%Z)H5pU%Ӊ-ǘ'< ԱSjB[u6ټNI@i8ˡx-jʇ1~FIxv-'aNU6(DsSǠ$&z4K}6쮯jYdFotMBߢ-P]tpeI[ hzB G){"l 3f]$ tHLy㧽 >nD\[W'z:)DgGUɄpnE"arh>TG/yCZ"(  hΜ:F($6T[,Ћa0Vv9fBRdHVC6T *Qfyl 7އUk,oK(>"sw臐!slߣo' 2oC(KZHsS!K +@P@73 C*<̫4ЗfV/33bw#7?^!#3ɛ@X$PFlbL/= z9/l1%s<72K+' /╩+bFtf&mqߴO?fWQy&ϕs|fAIMŵ@1Q^' G0j5 891iQwwW(ry Nei~PҪNI؀oCG{}skOD]%6-g)14 bՏYKЩ U=O]@jyBO;:r'F&hJȁV *Q)9U Mn(c|Aos*߸~3'R{F'nC;wss8cx JLORpEcxɌ2!'HlX2PCcꞝ{jufk(lҁ9~?|.w߹)f*6}!=F77 ˫}E*_}5Lmode??ICep)dۆR,[j-KzL b+aMaɹu"tn"4ƄI73)ch WZX1c3{fⲄ`섿M/LMVjQ {1`HNz! |gQ p$_A *yJJ"'Q YFSC,YAoG&8~9 .P:Sb0USM_t=_?~<3Af:#CBq: n6D&a?kYtqJ04r11[;m1HprQp@2w cEp(oh)s[pc! x099PyFn[Ǫ r XXe `D$`nj t1ny~ C:^G5=n2etƆ~r@iDȔ&4 C\(bi-j呜>Ek]MmuM-s!+zku@X?23Wu/օ`uѷ/j[ Y BCAeK57.]ׁPگ} kO;mk⒗;ÕʻrMbFFpk(Ekw}@M#`./<qj.Ki~b&>m.zfk!%OMf?L<@e:06=nhU -CxbCĨ+G\r'kʸũ֑&%s$tmh 3۞Td3n#^&gxNKR{;6Ĵ\3(¾BRb#c )Ex +͘8wXDZ(e!\}s9aE#Yoo}*R[<ɶ*ޞl1z&bjr9kWc! 82[!s!J.01:17`)LDWe9%s/k/xdD,!HAHI0!db9' VʤD@!/uͽſ=˳k7^$Oe5+qlb_;vy͹T-o(?d/Վ='@rjc"_dѩϓ }qce1nܴS"NźZ \RNU;\[@g`ewlV0G= !nes'J g)lM8kCݓ(E U4FuNE8_I:ti&&ĦQD[1,]jPZBV oN'e#jRZ3e}\1'A 7gz c잗.gs<A.0me7^F(E8OuLKӉUJ2K/kMu-qxbt0ݞw'x\ ` f*@/0Sg`h1}kɈDQ;q4ޜ]•WFSln {{ߓ/QOT UeecЉrkF/{$gu|HzyRߠINM^T"9X 'oSIP w;d;x)x[mi" ̺kL`M$ၨ׍~4te9=Nl^`gR#%NڂyragQMI)(޹$ghr2Zmۥ@eF똳j; D `\6 jzio_<^e<˿1VNyT(hay%Gp6c +:H8Ft$([?)ߙ\ +ư U(U y{M&BEi{tH"б"Usy&B6NSi,boc੝oʶ?ىFY= 擔%c'<(ɃacѬbIQq!M&&XԎ9(AKb]y(O[Z'/-)̈́=ÐӀ^KsܲD.G!N" +D_}=U h0ѴE@eYPcqe N' l:QQNg^ȿ a7~(mo6>hƖIY&;"j*K.ϼ/twJBD֦!5*J\g#u0?Nll?yl!c^*EGSS"MDta_[zx{6/k9@ 9B1Ñ \- !XyvеQ0+D<e"2sV8vgګ;o?F}Zwv@"FH?sA%凃R|۵/Q>sg% Ȧ ^v~C?u?}M kdz|\xOI@L8iEw^^/}^.1?MojU10YT9S&40H 0[a}D&F*$dgQA20I`S#US>u"(@;bF=3o0NcV_Z H镨%Clfi&QH}6Nf`VBƖLtvtdGf}k<'F R=Gz "}KQp˼3TeW˶WQ~B÷}~Ϗ 9-F 7PoO[`&A!Z (iooS%8sIS%DW֊*,zmw2 buzW=|ak9݉"nsN<m q×8 O",!(c=|w& (G""@QBQPdicUdJ-$;FŐ½ڬ/X:~|m6f~9JoyW.4OP Ʋ4 h0d$u#@P?cSCPtIi_%j Z h cЋ_J [)Vg8@!Ӓ _ۀLBQв/bјt[2o>'zWPRd VT':uT霥 ֥PUo}[ZLm| &^~.si`l5ns'$_̩bV3j^x;u1'nQ6>9ZžJoBP׃v-cfIXؠ.ayis=s>Oau,0mRL|S8cMҬNgPᮖeάŲrqe_&L"9'j {☏ XB X e qIS7һ ("P6]-ڑ@d , *(I"2)'Ϩb d.#K'\P]\|3Y ,9@5X{cα'[_ɇ|_1J{&q/NPT䲛6Xq=c뚻8ֆ?v zuOEg]FG|®W~ښf_~7EŬxu4Vu( C9{w:k֮HmW@ rHiAN6_XEpytNw>kYcgDا8iM>&6ouNed(#h Y؀$C3:24P$OðUC:e/g :QySYaEJTV$ s6;7mk֬T1* 2 " [aΐ* ^.c Ҍ)á&0cPe09\m`PZ?%?9¶M-+L& -MyJ(ϛqpt!Ů x͟P@D&,ʧjg c1HYz) )/0`G9Fqjǯnp?p/{EXѷѷNazD@@։lW|,@b?Mkyq0e! Ozm¸}H ZGC%4FP?CC0Нv݄mŽif܏5@5߫S_xC?Qm틡Ql(4i,@F@L9yUY;+aR<(!X A6'^|e/ g xs0epK_#!J}[t8kxI6Ig[f;MKEC::DP) 'ÑRPFG<4π湙ڂv‚h7Nr mZ5E"G,/o-Kߊe&]w=2WZ) 0)8iL6T{ 5XU( <9Xk:v"lFZ +s ]ϾaS~mEy1O!DLsu~œ~rK'^8 L{m޳v<ǜKyqD{@{a, x8wNd1AsJV܄IQVSz m dɈ>%:7Y͌ _JףͯwxkX}` gSgu{6-;i?v=2۳gxE13zQHfm?\&JWOCd(OQKΞKbJWV'T-@hꞧs*ՖP}RQ`ZD(6ǹboRfN f9Vl鍗'.͂4T>T�q6"rY~LP+Lra]]E8B-S9m*l'Լ< hAnejB-]zQ;x- 0r_r﫭D`e9ü潆扞ɁqD,Ժ@W, P9@4ZW3AG0~3dW_[ iɋM {,C0ќ1._PѢ`s;aa O_h.ea-zз8D.]ESpQF,^H kXŪ@8%7!M嗷 |KL4/M_xb6_*gm0@UICp ,t E{dNYPc5lE;ڮ*jn8iZ!SjqZ.veiPPi&ϭ5;R,r  BdHp\ErJ I Ӳ ,AԞʂ3,\8Yx]R, *>5=Q~Ty\ $">V!eb߿)_.gYDG-Jvd1ݛAܖ_&<ϊSCrԠW;:V-o^_׎,Ot^:?tۿg~½ MDAK_\%UжLaɿ|bUMovx7XLS%Xgo9Ne9׉o׺.}1[lNQδs~UVCvm8iﰃZ~y΁\ REErTZ/VʕpY"Ԑ'-vF\H4iϯ^^sJU^7w2;`)@|"t'~;;SF!d1|LKn74H\LĤBsmՌ88#B]6KZ.U5q_{-}zP zqUI کKRyV@@LVV&F7 ܳ_ٸeP(L%27Nˌz b8ƀj91 D$R$ g{&/SW4Z`oR<B ŪΨB# 5=k0T~_*,bb9-y__~&0R:rz(!>2F`x#/_U nF ` x6wf@v)KP$  dt8|xugWVkZec<{04u0 2@| DH?-Ʉbӧ`^6^WUzR_i_zyGr_g^{.z)hac2 K!5:KԊsT*l"btM w?: zm?M>Y? \hEE;ͭ|jLUy!DX[.NqQ,3%b 6~~xX1 USfr9ֱ⵫E3_=z!,X˱v95aeaɐ-nDY;shA:QTpPTMD N_@AHLm..*,q 1Q7}e/x.| @Q@@,~ +ojA t[p zk:0XF EL9o5zvyN9>O,V5)52z=z5Ky#\Ԑr]JO贕V#!ʙMto߲#D@ aߖ: lA!,:ǟmn}27y?"(`Ѕ!~/ތ-=_(+ǿgϷ/y§u^ krJnodkiw { ' HcᴡSfeB`ųl>]<-j:E|eS>*&hEXe6a~fA @̚V&=R{6C(BD4EO(ZK3l\KcPK;Z%쩇ˑ(AL:H=7Dsyv4Rta(C&:' |Pi,UL$ ` ִӌ)P2,+w aon%J Bxn5Bfxm=(g(P^NdHZەRƁQMbZ!-{ABLP:BJ* L3 $ 6< 02$A~g$l҆l\,@Sn39#>Ί@Vw !{xEmV @%}޵w73Q, 1-^Ft_} @đXE~˜y"cg oTFٵkkkG `\M3Sԫ w?1w~  6|U*]UaNPHYLHe( y~lÓFX+Ӱ@N&NDMGG! Po0dŶ]>?})[XV  % 8|/4ـ}95 KWW0 Ms_L 74Ʋ`Ymb!3ed2"$j2k!`@^RX#k23%H4r V2:;o \Mp%$ >]JB1j $\.!- MԢ&;Uv%t{9DU,W b-0jѸeM<)b[=!QqO Np+^ P!L)!f4F]55 D2䃁+0V}Q%?22`Y Ti'dg_~ j?ŏקD"^3&YcՕZ_S}iH`+N}4;ApZDB4,g768{ Ah&Q ,E*c`jױ;qC#,}akm{]\/H޽>5^dX=h: \72*{턵7Go O=6Ϫם_n]:*woCU׮*B62U,'EsCkY͇^ݣiZfТ`~ `!heö EB3}O_O\ӯrRӭr~0@Ih>Pe;;0(xRy0 R BݥĦAjþԐ,G@.sz'c(9PdNI @E5&jKû$:^*f hue@;K!AZ2QzU?X?JO x1@p.(1_I1[gw8e*"dpk!02n]28_`U"S<nc%)-ʮݝ҃p\OZQQh͵dU8CJ&:Ň-9b49rt:0E,ްn 6q.%er*%%ӽ;Â1\lxvR>1\".(Bk7cҶvkGgk 5[bIX{bbaOl `nrQ/Ȇ*`)Ͻ9C_CivIOУZϥ? x']?r.G35pR$-,0ALpU3 Bg"Q0!1a#]vŮW6!8`Bf7F*xr|1W n?;e_@xuGJW PChUEŊYstAy4$-*hBt UTD| %`9YL(V.-.3ѻR6e17kɅ/" 3as7jNb["կ=0Q)sM͙ߏ}>GfIZ5èog)Wv1dQyd3˂cYw=gc|%Y$m%cV/rPzU1,.YCH$lEԣQ`axZ8z]ZZ4JD !1RM+섊EY`2S*ωf @{N ? [q~tqvFVwgv@T*ǻ'+i=[}p;5|t*ڔ[oZk OYO]oXee"/B\sfΥvZuij [㛭o}6aV<-1 ϵyװ>̮9V%T%TWITՀ%"ʀzca/&L^ "BX6S=a#Z{Q0)h>C$gz F; b;a`aE%+Wq%^/`A_mFKK[ nX׶0 W6c>O>KnpuJ۷^AFE2ZQQ EIIHWkjee*RH)P^K;XĦd<3I<`YAB" xQ2˜GѸ 륆҈ԏ{˶`yY\Xc% ( ԓC ?b#*x*[d4h[H~ ?{|G3Y*P=퍀 (0ޱ3`x5g\%(~aTUZ ?+  1V-[b"bO@&1b$%uw0Jlz  fXUX(IC%Md er$﯏f&C4BM6LۡMƟ(ͬ2Mi `/9BX߭~|"|,屏W~el$3'00B:w`F]Ԓ:J-1:/^zqTœw#v-^0^Ts+[ڷOZK곗ou &{3!kMqj~߼voFEfy {yOv땶CQ@N5,P9ph H[2q4疏]r o|&ޗ_9}ַUDaQ_~K'?h9}ץү}5w 0G8;|5gVNG]ڥga7E\im ϙ[bQ^l#/u$UM0˰EPT>6o#~IRua}DGebD{M'F U@@0ƅp_#6 1os㍂7~tN{U0 =Ou$thB雌cզ%v9r$׆n6aL&5SQlbI&'7/H!1*NJi<3C&YnI!1JN˴ l'2eaC}ӿnck:l]Ua1qt1 1U؆Њ-!VRn%ĭI WyK#AH}d`$Nh@˲~SɫYUH "1a V.iY}p%Jե5H$-?K88  z cRsuqQy!*`^‘XP%m  ={=嬉;՝VPd'QE =kjY rv"(߸Xa;Engypd;P-mȚo%1νݛ\,q֖v`ѶK9YP,]Mc YX4cxf0 rƫpIqpxk$]#m{'A0.1)40zq&F7k\@8,g-0#lc1 |Op_2>xi{j(FM}qc}+u^ڍhbr%LJBKs'\9#,FM踜 vK`y`Kg`mfI'҅۲f977;b~X<6zW6m5z[9vX96MC5q6\;pڟH5\i;;'?INSJry{}3(Fw\LiF_>ԿZeJzXZ3v>:ڛVvohqkȆBp1A*%k|_Y+I٭P9֘uaZ*s#y8tmcF, p݉oB{݇vD#+̻k,߮ F07O\n[ t@.x{ur3*=:_WM}N迒\DW)+li {<%{727mٳF4Ғ;Yo.g ޿U?Gb$k#vZvԤ'</ꙙŝ< @ʯr$=֯Dwn&^[/.ϝn0  y *p1^v`#"{V'2 ғGk7TtV)4Ϫgӗ>q9SY cyFR&ն}ņ5/D| qzZ(n`"):|Fw=j{O] (P !ՊSo.#8C|sb|"J mmQcQsDDh({W+C4m+&x8qn+ P~zjk8`_0ΞkۛA~wg-YX[[MRb3 EuSn}=I֘-fU~A9LC"nh*k4:s#[BjWIɰ?ˣveT\"iNuYhU/ݯQ-3M|kx=x-D\Nuq43@K3`;VC~p$@>\ZABl8Cg'ˀ#W 935I3),QeU( /LB}N:|Wh.4;Z'1" dvCL.~綢aaaJ!IyhϨsý$~^EwS4Z mN/Շ[VaE`yП|& x04:8;0x4"C>[[bl.0`C 0jE<j:/jYN 8cEI=:>=p 7{N*1t~T6dR÷iQ4GEN}eEcpBGG'#wRZO='пj\|hֹG<>{./;(ؘD0Psm>ͳe'`BJ-yɿQ8LιT`,P Xkk{ݫuUo,ھ^WN*g ǾI> vtiBpPG;$^!h/LG N9 #Z;jZi̫> t}+ZƋ0kF2dϤҞv,8 21Lʒ_r< Ѳfۼk4f7nH~ū;nզ?1J 7gu-5dy%)#)bF!USu-=ΐu\3{b4X$''_<٤odD3DDTR- |u%>G9Wi$zJK7% ?%@7"?~~Cx۩&70`VJޝt$ovW%%bq9ME2Ze 8= 'tgo4 oYt[(D}ayNS^-ݥ' ANIɭ3:eiG[r9]/w^P4G QBn6֝kFW:jU1>mϧ ֹּ_yZۛcݝ!-sn r"mxSuc;G?X\㫿5lOISF x!0 1 (w6~[$\hu04Ep-*|).`Ss{ڕ4);0%9f.OkʟOǩO噔>[a+T#4VF׭.~֧̗c7E@<[veEӅBNV|h?,n/]:ÿ쑡SG7z(C7ueROw "ndx ms -Csγ/֘Bg|cUCqRO L?~#%]c!(5;2|&Q|faٌ?(7@z4 +msõ+tC_~(3.._j2p bXBHlTI.sX;-6>V~W,x6wO9e10RH'YH3l*G-~6qkEfj ʂq>PPFp,D 0(Spmry[fQ[:osy Kwol;jP29}O^6=浽W1mb=.PBq,­~Iuo7ZGl}חCv/ۈO9^^q]lJpժ`R(Q8\X0w`cC% 4]kZ]_W*3/^EM{,5m3@Ź*\3tC4LPqꪦ[֝UQ亘E q=)' q5QI2^6[:"Dԁ4WLO]< +Ɇ\L R5@Q@ WH(z$($ddrR}KçiSkV CA e(VS +& DdfDc[VU =lt;g Wry!"`6 x;2P8 ѺB@\t_ ο@J D"h卢" ?oR4J,%䆡'RV.Z=&#YA0$>Vb'DZVP -zsn˰4ξr.l+=tHEN_DBC9ydѾ Ӿ׹IAd۳]Ea"AtٚEXC:oAhye;bG$; bEhs3cs)'+ c?Օ{L&6v8ULè@ GpG*ikdoR!D4" R)ITrzi OMrw3XZLebo87錇IMWnM,l2ntĤLv?{K"X,uSlَ ln+ b[>_i=|ӃoԝKK>>sL$#wjo_W:z3REUx]}7:-Q};RVkZQFzu{cp? G¬TGUOɹlyT !Y^Tf Ns/#>\Yo'5{,_錞[d`Yӷ A|4WEDk9@1`/By7DvFE1 Q+W^~ZsWPW҈BsY6\o 2ѵ;io_@!7GG"1{3)Dhz3sm@k|@op6``&y@7Υaw @? `!W|/ PdT$x􆭐0,SsOX%!U`>8O )֛<& @#d)`rk{ K_KY 5VEA^CYR;ipQ/`U/g*6qf0B23B  [L<Ɛj C#ނ[5)Z&ύ0n"e9 ~* BR7W曋z:tTk7Wv/,.Z| k Q4xY r W2 G 4 ӄl]8Df@P` ҃Xqv?cƑ/dPKa0 >gՍ(9?s8AhH?F;jf6CJe$:$-1Uq[ D&f3\@5Rbw 0ϵ+\jxò UYFj4)7WzU A Z!QȬ8_$ݘ.O+sti@<لUmT 懽&IhmDi ߾ .Y$^'yܕ!$ B|3O6$UP<|p. yG@ "Ahz9ӣX)(c%Ƞ Oe6T{n#dA'`"\ *E00@JnK%Q93y8a1*&)@Ĵ@dciX$F ;$ d1( qNlN!Qh )x&w峮{3=փ:kM.vYOb~l0nig|I^Nh+4|aeJ Jfw*y'M3*x /p3nؠϊd=no5&Hy*VXrm6'3\!g汰bg?: ՠ%4R*)UgC'٦}<3!tb _s?^MW+Zzs% ȴ'_{@T!7jItۓH$/}{U bO0B1l69lm*` Lh(]kY 6b;`),hR};Z6S o Zm~`÷ Ȗ"2}T<2{!3kEkV "_Vpr)Koպ 5k"9YHkhS0KDMBcNtbAVɂל7ONh ' 4"nNKOI.5]`xF&V%@<3 ϭ[Z2KMG<]Tb1,j`d<;|ҍ*Ӛ(ΊV d%1˷ yP`LZ#Ff::V,X|Y CN4 +>3/o?u;>RgO1x7ӵ3Ml~7RȞgcj'ggo o`VϟΖs5k=@Oգ)~lJ8s>gQq?jҒ].mRݑWs:Y'WM`˛B(m@-rKg{%l-;y^Y*}U hӔ:vF<朧*{N.g;Cxl̑cXAPI \o.z.ojJrq-)X&gTE9iZ 3>CmQNᦒ Eܙ VQ2*GEΈv;;7oJh&H96[ wniIM^Y%.iI7~ĕKҖ4:pYYmW\<:~<)R%_~5/tވ2{@ٽ8kR!~uYENr d';q#Pa*UI, py[>+Gkf5/[0bKvsOϻJ{ohb*nT&)' K8%~^YܝҩQ2e/{wRƲ%@vnbp:Z =:3/^`ԱMN]kkv Lwa0OqP 4\ {>s0$WUK "Aƈ@Zt)34A@"J{G,OF0]0zAD1S@ t d,,9s1"] /s$5^*:<Tiszmi[z/ʿx/wztTL BQ9):u|A7P` īt6 IAi$w.RKݤh| 0?u?!U;:.bƆs,b) 5]?Lwm63>cڮ-u)Г')arCwoH/Z/+ møzZ+T_tmzޜyu3ֆKS]z#Ӷ!gF +w0Nҙw=JM͔{O <穇 |N9DY!bRy`vqc5j M>ҽ@7O^76R`y/~vݷ{Tڻۗg EUlup WQ/+[֨h_3Ӳ}g16og7׺+-8(ay1y񑄅~?ܙu?mku1mn_3ۧmDv/p[ɂeظcVA'4VB6K9s^>/vMyƺL]־{d_@r *dR@Qs굦#-b? yD_|v>;^ԝ5 5cwl\u/=6wJ}.:QZ~zg f*DS> i-Fc_|>n DPuX4< Vu%2@! ,dNNNA@ (K JQhATa &d(}brg@4YN`(ZDCZ_!!{Ma %Bx{f%@f 6 Q1YyBCVƶլӿ?1ג=OZqG:عG?˿\D*P!T"BR«4; NOA C)P : *~ ]ecTǽ N/F9~!W :X&ϝ_z~G-#4~cAhңZo 3\גETc2=a QU͏bX/!)W;A T.ukNU5b':Ҫx$Pʔ+E6C}Ϳ"R܂.x"pWsջB]7as g;3̛;,j|0l˓lP+qKwtY)6UR*#RO27|j>3縁<@$H9 3oxriX@:LAMXr l'@w`Q0  ƻ?-<`X7ih@0Ә2[?JxJvF J*$M Mubb HW@w8K8CL*P^~Eޟ̀ykQA`Ud:T!X$ BjJiW}v)؋`Ap_%u}#Ssk's*mbP q}_ڋ;.$&||{JdgDo oi}C7!\n[4*YԾƀ|oҏ*Լe I@e(߼4-'$>2bə G`!H~0`Sc?3FX|CFW71,Ǫ@"У/$WzC-3i pkK fe8 ? a\I»H\"cc bйx12"ԱO{DGzh?hPj7'yZPvp _q?_n䥚im$%lhqѷe}5wbעf?=2j*-5|u`y[>\2\t^Zʛ-T B G']I@QVb,o J\tO=boJ4uF $`[7?^kݦRw!U(`GHp /[XCKae4(5IJf7pڸTsn{Rcw/ΘIlS%#?תx'_\Nį=cY%6~f,фAT@=+PuQѓip+/?cXZ-G^gcTt;:wv֗Ҵl_ koþDW,^A8KYe' pf!PK_ 1Yd2s.=b(k3t>A:r,E?Nt 𣐀k:ڳ7:o6;/Uv\^e(:&sQ u`)gބNW8 &IwVj)0+@*K5, "$ ()ȼ&w"( KȬzלs-g! p!G74;sUd;oiLJZ ;>I`820m7P0.KP\!B@9q M A(CJ*FBGPP>KO#ֱznع-Sl[ MVF6Q\=n߰'']z_c,'^ՙwF{?!ѳ}Z`m#Q0.ruK%};:`cy^ MW9U+Օ@2Js^kݚRMS,{uB+?уkDv9W9jK Ҫe(9y]sw>w^^Cc|78ߕ]e]&!˞<0JfG&`##1h>9-{e%̈́M;:y1b9waI*zd:Wo<OyZ[ GUBLL99\&''L 30-`-xnsi*N&Yت9%SYB=SxsY)竔,g~^M'P B` R=bŠ  XP6=qsh0 Vo=Ԉ d,لa)ÂXоۀcɼ} /TeE`XJ "M4QY¤@*l f Dhir/*Lԡڡ=Z@ ;u^y.{˒aQl(,ЈliQsPz/(3މs1<e8kVPl pƀO(`0,yXF[^,T.G@\2\<P mַloBpDx`0&J` dOyM/J(t27+a>:a̰2"F dlνyujѐ`2cHfϵyhZ¸<3hؑ*+;<}] >2LuʶfNG<ғG`Uǽ7CG͒sݬԎD ܼF%(T%59oMt'כYpϵgbH&UoodszN=m'S,nMcİ]!(J@Gu7JUUR+CZ&d<=PCѶb(ؕ ЁveOsИY?eٖO..oᲚ&-aÃK30n:X/6PR[μ+֨ib#qjP,|#8f}L@LPUSGT &Bpi&QB@- (\|cl|FO|9\/ d__?x 7x]K ͸%+83d y{H| Bsa12"cXe}EZ_rq! H5-Ĺk 2k2qfdr BYT]h0d2皎k(;"cwyΙ7\`զOFsŔQ\X5.F\LXF VztjsF'CDq?DD,epp%Q[, h'ĉ 5d0E'eE B=$#ŝGc񘠈'j.MG/n=y2r(Zňz?/}"t9~ .AereB>-e:`K^ ns}3}~ zn7;-oB.I d6Yu~eVS}1[SG?kF>/λ,Uw_,+EJ4eh8H2e?\|sqfVO䱍phTg7<~r`0s`jIYGuHnt&睇yk< \_Hȋ?SvɀۤMx95T[Rx˵~wȿ!pL98O C&UQ5A&Rȋ?Y^aVݙWY{JC!sgcNCfʽu#2{tx{l_{ uZ_\5Fĸ[ML,ifF{&^p&e]{Y1fÛ>i Xbu!7[̞>ZcqhnZ%jZל+&V2gZ `u1o"LPYk +@!QN52v9 B u3?4C ~dIH8jdTj0uFFvx 7 9Q 9.]'BhыPc(h |kZ 67\`җ'$_O2t .D#< ?>L@'81`%hLdH+p%r\&Q@3_C@ K/J[miC)},q3D_; ,lj\`=5X @"0)@f @.LT ?{aX R̹BM`Wx)\̊ 2Vɼ gy7 "Q,\c'h?g0 ` mxc-,ל =곶|F%$P{m1 l 6<~i "0T[,Bvc8f %M'"kR{]\177]Νi{bfrZf%+ֹtQ7kȨr@+\(*t/-%5c/|-Um_XE,63k jwZ֕, $htxc+[W2J\+Dl_0^8c㿿jSS5wfѪ~sE Du dSZ*}xї-{8KP#hd`}Unƣ4Dx|lP&˗Bc;Boes٣+Ś>! V;K-y{vi8u@zϼ`֑Š[^uƛ̀R -{ȱkUλ5ki$DT`.( ~ns'y7-aVŹW{L7@QLN9Sw^hvE]Ge:Ʒ>1cV4١M]s75bR`ƻ7e!3ċ `Cןs5lp-Ul؁'Q``2 %Jq{%.e!SU:* ۙBWp9P LE &g1X@t~R GTZ560q"'Q}G!%xe( ^AA7/]YHTU< <j$ KhzvAsr RȎɂ^k؀4AAXBHSWЁ, 4 O"pCU@/<`7_z1U@}絀-s v#Kw[XK1#,y# fO מquI2bqb :Cu [{ Ch  d^N, ]25 'sn]^vV D_(Mf Xc0&2_L屿;\ۚE]yٕQ6 wShۄs}XvmQ#B/5Ztvw@(`ga x46QaWI&Et$uPMFo찎6xvxi;Ip>IGYdњ%}{P!SnvM̎Hzٌ*,ϔ2&(UՕ=|]h^ă_?k<8ARIJyu. =XwRk娲fvrٹw`6*w%J$XF$\tឃL,^/Q!"\J%)"3x# G&Z*KyW _WRej$S~sz1<8++M9a9uߨP̅l:fϽ;WtApW|?sjvrSj&6"JXQO8;qz\j# ?%42f[dIlv4@";srT/C UA /ߛ>`;h Kw@J-, :&{)T}ʎ!U*(-r/D,4a#$$ae0bT4b!wL 5q%Z"/?Q0a6aw CbdNd,!"0Z_|LG%j t [$,/.;8gư6+W$UqY鐕l?7<rkf( Ѽ ՇkU ap݀0(e}nXm^|Y3BZ඾@<7;! [}jQL5#氋7q^'1ifxQ(  Bxγ5nW4[ά\LX1l_eijQ:F=ܟU J֔T(9瞋C\Q7/Yk}&W R_U7!]' (qe+ +7c X(6a\24{7n)^sg~7H()-l݁[rv rfmFb{]m$u͋;͒Pg@)7_ "IN`6{E>Zko}h6_ c5mXMyuM[Cc'a.ј̲sض{x|zŞɵNNwP͸,vtQsقt:b 33c>"a->"uчR8Iziy, b8}0ƺ}N\vgVInJE.dnt#8asBO{Sqi;+,h!b\=KćsfD}Za#|B'nV{h\ k(~#}2Eħm9ڳx}:c8ՃT9)2;J}R Z1" A8)oGp³{y ЗVj*HS]R+D.}1e.jntmrGR E6goLj9IPEҚ8iiɎÎ!;MϞ۪=15O)  NZLED0,)X.0|Ks~SVfWXj:k $#ZWL+;}П6{sFz0_n5DtDpY2@πl|9U49,fx-x)CoA ?!w%j0;  wd yqfAXs"VedЖ9auFkH@d߹/~= &m)-i őXljE)'ӧ*?#Bi(.u صB, rY0&l_~_I내u8 [r d:֬ƅW>$hmqm[RJKZ ۉW{??ߝ 0iE$-qv0BԷj]d}Crei4A]6\ڋN+“Ac݋W>&BsNn=iՌ8md=wtX/n6Z.0\ bs'}@8Hr00 "1RC~YOLppo`Xl :d!BZ'O4o9VO_߸eu'DG{X.W9?ە=1]{kl#z̙ EqO#Oͧ #{x5k:'I>4nUS%O-ѷ{}=wsfQ}wH732 5_'ES 6`S wW bN!%@f^/IQ;p@.ʐPSN1LF&)A`y_-2|'|&^ށzގfNG.y%g#^MkQ]1;!ḷ1qڸXN @@!x;Q<86n4r{}1,>R뇫״(B&bzK hS{"4]DNPf6Se̊ƱA'{vbǩCGg nxn׈ EqA+z]I8PVi]J* z{ Vd^ȟO!W2ud!oy:C]y~>,޺p5QE4:m@Qf5\"(@@&|xAM7:9ۢ 7XzxQԗ/M}k2jpnC|?no?l9$gz?&Nz1w#J$xh?QWWu~m.E|4~xkH*?#F-iJ[[t$SᙷᬍtGwHNVrG6S(vvɹv;Co-dpF;#?U!?~نfR~gY=1X!&iiWڦާ[4yx5R{$]YvN?7㻰447e5K :0^?٣V֐8g^|Ay[rtjaL%LPD HhfX Or~'(体:cey~ᄘ_U<z|&%>mR?Ǘ[PbKu^#+'XGێw5]='=8QeYE}ֱ;#;SLˇuncߵ/D%J:qw`5fMVRȒ#=g U(4H",CFٷ S70<-gr&s`f+o(=XY13j~rx9|Q/DZIB(":BJ^j`Z5*Yed44')S;corJ޿t6%,A {f6cs'nHHA |ֽklJ}Qa!.J'Қ(2}r!T!wS @ɯճ~~}st@|ǴՑNNu8 VO 7 $׬*!׮wiߕ=2+[_N$dLqI2J dPhopCoOΪUU\l;n>qCkPg͏D+B@& ?G&J&e'ɱo_Y}'8 /R4N?z ގ{9#J2[}>+"[j + h_Ismzox$z;soqk;{*9ޞrwе? pO'_)I{] 禟:UejV~S*?scNآ^Ú?lҏ]\tѧWh L?CJ{wKԜ3CYO|΍d {5ZL%j8>T~;'ՠ䯭JՐ 5E?ڒCn{˸6ړ~[N9hH<+V~dY;{vDf%c.~^J "_8 ~ 'M:$ȒW_:%<NElPL7^57 Z}:$KO{-:~[赤q")TՍ^Y=ĬFtɃq;^7`W⡟rSd<'Gͨ{n]W-A8  m~r'wE~N^;^j,R^S(ȚG@,wW3h—~OrΓ *+Z9m1f>԰>z]1@WVFߜx%Wϱy%IIxB=8%Ϣo<˹ mƊ?oc}m{$D('ć3pCFsA z+9}G y#R/3е;7Wt>^[: L<4!j#"R/x7sU8kV\clDdVDG"5X3corF8hd;`N  V~"P_ĚdPIi0&3l>dpJMdR9.FH8(/ 9p̖XLCݒkc ED+;c`D 4~@.~s*)TODlto }_y|sXR@hI­x>7<+ڧ Q?q]tկ|6Yl &꡵{p ךw^+o8Y]c_|gВp6=rp L}+e'Z>A88=^o-{o,o5:{:";]]\Mᕛ'IR?ǽkwW_{WnUg=B:9_UH=gQ,ۑ+ Ѳ21|_aÿ+;: OkckP+`-5+F\\f(q}U/Et~# EFaxrzPf)Y]rKy 8⻻B_} 7PU,Re%IٟPILS`H(I\QK"#Dw+>D.Z4mL hAd ’j5ƄixHU)II5*I==HT&$2hƘh4 OTm/v  *C([^0."ʤG* -`k Mt%P$2RQ4%ݢauQu:v9k_8?v#゚2"Xxj!;=Gg:uVxqv`l(0n\k2=܅yn5?p\ ք']WɌo)xA>b͚-8(d Xs e7κiza^ 8P}~c˯?dC'Ry!}飐uc|Qn/kNxQ:zA: 0;+{J=ydo/ sDeN~f WX73XC;K8r#nsF3d{JTJ%Lz&T6:/~yGlKpû,~@<ÈA<ƴlN [*2h͵ڏ9>(>yi 'ݘ w/gS(-XcBӧ5$*Ȯaҏq4(,RFH"NF(uտ$2{Q-&٬S&HY/s%vQD-l{i{ ո?p >th[Y}8.8Af2ݚK'2BV[mHt;a̪>!D9"Fndy++o}>+)r `i<$al0i4w16x'Rg~ocnowW9vmR(J$ًv~:_\*#Ĭ Bd27u[-$b<qv8YTϛck@$xKAWwee(O8mѐg?ήNJ1'VDizmzYp߳ӹo1nd 2o sn ZGe;+z1oUz_/u'L_jf~g^Fiw\~ɍ 43EoK~Rbx]iU 8A|qR2t h 7[hףmUkMEυGsmN}% .Lnn}S9GOH˾C$!3 p #S@-ivzMߥyͯk9pк&д &ZdCN| !Qk Fl?*s/ùv߇^, ?Vt/Q S{г8xCmQ],⨦y{Y,~MsDIc*bWc@Pe E!q^Qv&"X~򯤞ҴU?Oۮxxuw2_o9{meਇͿ6լ߂#c4$l 6kvT NZsLնYj2.pMQERmgf%’v' b^[|gF ΜZg{I@}C ӪcUJc?4 rRs3/o҈Ľв?H,x|gk bPH-GiҰ^Nf(㉻~NYK!$(nq t1eqKava['7tynp+$$̾$ez^uߍpV^ŔAiXA WP"tpۢyaUrѿâPJ4hJTY%PVZW$wX¼g߼`w|jdK&@_r)V8!)*NEuG R4G+"Dڽ:wb7,$@K((d}g(p0Ƀ@Ǡ 00 ,$ f-~e=(#PV1"0i Ėh$Ѧ7!Àb?$@cx MFu0Ja~E[CwmdHYH.Zڱt)4xhp+@f᛼5B%@ŷhsEBݺVs65Fd}8YÐiטt%L$E uf2QC9csqg< E K @|}br,v y^eq+x8 etUѶt[UiÏC]vUk#rS-~ u 䦉 D:$S $LQtfUb<C][';eN`<اN$&^JG9vVJdoEcy6T@ٹ'Ο~$@IWسVG-%BZ,so;s8Rwqu{?q'ǮUpMYJd:YB^'TAGe E >Bxz_X4XN6dky +,,I)M3Ȋ[gTǼˆ6n4kS^9U&}-ѵ0n,ֵ]}κ>7wc$$9̖B-m)9sЩ7F+Pv?2塯)g};p\SXb̫("x(2(1n+oaAs+;_/' 󠣧eٓö [_r# n(;qq4֯Y~ O*cIL mL`E;#k&)]+ҌD YĦ"!п$5(]TDl=bow0 QUfg~rʄE !SH餵ՕA |mlyI>U3]?ɝ2\jed]ows2($ռO4#= i 2j>ɮƸ.OF(}_yϥ}~X:-+u T/u ٔkͣRb]0e`L%>߰@Q0TBu70Hm^ʎG|nJh5X=@eK,ԜAYXDvQ4ͦ_9SRpGˉâoN] ^M~8 ~z:U|`т@xD\/ h@?Hn;U=&\#_ѫZJj:G 4 o/zo.ޥfgD[=q^?y$}2(ŸN’=/zov_QcO4kJ[iYwUFQ/FpC>gPӠMW&Q,O[xo׏{lA&+wB26;]?߹ެ{7덧C'8G?x I@"!o'DέK ӤT:hr8F冰$ז0wsvaYpnh\6Zw18 =iw0m@tҏ̼=>6 ʘTOh;ʫMSJ:xɃ>|PBAYdPSF)@ďx~IpKO0q9}|?9ÔSF\0J{"513A9z,rqjH~Y8k{A܇G\vR7(lqAibHҁܠ,LXcwpx4Dg8FII?1k^f8#%0PGR 3VGby0\ZNA*yR%F<{ƌ#m5FຫƣW1Oq"𫧱Ҙd՗9)Ϝa(H?9n#pIu % 2@f_~"`>*x$ {Gl<4^;0~۶XM= w,TD4.6: 1$4E A @"%7n<C#uf Fuo*q8k?dt?g H Xx*7V_ȃ|vk+M2)7JWU"\mI0gSg eA(u1w] neWC{{G沎_F y;Pn;xd.SC?|~/Nf7]9ل$^\(Dދ뫛P}KO4N$Έ\%t~>̯AIRIK0:eIp[C $!EB#tpCvC;o ~$*̾@9KQDAFG;ߴj;|,qڙ@ O|!A.s!=dz S 9>Z!wLD & P>MoжhMA6bl b)߁ BY8l+`\ ɖUKc8Xd~I4#Fc ɒG $ַT~5>ͧ/|3PbJ\N%Σ\N{uYB]o~A8]YQ@IdP2+zͭ|ERRcDj5Ľ6|Oi|2{`gd Xk8+r}+@,' yJ?[#7RH8_4ov.:Oh?O~ x|oHvA-9{<7i[|t nPJd43DO!IʙSԢkǟO NG@"eεu>iaUtᝂyP$j2h޵vr?KӞ7PNfզ0'9vPa8`q/= Fh;NtEZqDO+ooѠ :tCj5FŠDAS@8֪(FaxLZZ8MA>hJ 3`/%"q~9-&I6&X{!jTro +Z BǠJ7)%`9rw$I&xaWD1rE KT]~tJ7 XbݙwnjS@5C'4k$;fSk2&r-7͒t'ʚ;<嵚g+.y%F(h<1NoƮO}v~N1n/;R^] F!GPhl̅TD@.$CGBADH!> +@gC@##Ya*0SD" rdi7/B<"d F]_B9ӮBbӇ@\PHHH@$'NNa#K'.8Շ|!; Qo6k4jLQI8g(N?%|͵$X+0.0$bR>=D8{z^IB`O1!(Jd8 'vZȦY u(Ue%=!qg+]bʀE!F{Qњ ܆cـuq9 JV d0EN|v%ޟɵ>Gݝ0"A$iJm[++X[(vtE梵{#AC$1N."`_umyjƠ`4\'XKR|S޵TB-E=Lt%[|+3,u.!cv:m)@?z}0z|Tֳ zy7m<$0J6Apbɣ1+LJ|t]v;t2P{$ū_nZ7uxnǪyܮ}ۭ[s4 tm.b+]vwu1x;?Xw˜RQ`KŗAҙͤSȈzD'cZJ1)SkսNh;nFK/32s09qٿe&dWЂH\TCva'[,F3Nk!B쟻P6[ 'Od x:nxT$ƺzLn= Ҍ2+כ!KD  E@d^9MY7L&(bP5)"ጫqnC;5Q㔩Kz1v_R8,GVgk.JkWKpHl#»?Q-~FP&p3i]8^AR q |#5(c~/>Ŭ҅hZ2%/DEyg KO Hr2fjj2>i("$Tf& L}Ng1P F}D&rjgt $dO鎰gwFdAq2Mgpx0:Ŵ1XWBBCo'_/޲gsIf ;%U6):ٜ˿+tK1rFT[ЏW7Ѽ|z5"XllBc͐69j/9X;|3fY{/KB,ZFQ g@Yʸ7s7~ή5P$ֿ4lć#BL~:l pPzsJ''Ƕ$!(bpdB_D3:z֒ Zh02EFuU~"ܙ+!łJ\PM.%뵽ΌUkI;s\G."thET߿Blc}@~{ѝg( 9 Sh2ބ>E g'~g@Er9DP҅ussW膢h5on3މaMD5PԅȟTy5?PlC /\pXؒ;t7h`f@*Q/'%746:mf_%1B] uxVS ?~[$ԟ@ fCY)6X~%DOK ʻ6Ot/E9ÅWT^ʝl Ǖ;5ti ]h#2By _C8zY\;(w.-uߧoSn׷sD{57EB8p4:G:dySH 2 *[^uf}ָ4@PSJj PX^陯%c7̚+EAŻIO[w? ] U?B)?3CcHLn$|}B\.+Z0SnPH؝9`ki`zmJ(qEv6]~n]{g>g2ɤeNBAK2>ưAѤ2ؚ{Du"'4f@- "8vxN`  j[tI2X})96jl e=d' RT6Ƃn ɽ\;Wp^G(2$MnP9$5U n!DwquDI’qr`-$9p $$'y%S90BϞ͚OX:j)">f)*U70 E2%2)B:kNCR{Z-+EYCTGP e-3P=ѠwE,TŁJPv 6CdQNWtYMAHA__ 9<(,@nk%P܁kz5_JQKHEȸihVb X`W9R6S R7L>JK|Ӂ|rB8C=!t.i"@7~m zmCxw\_B: jjG˵Mǿ2#Y3;-~jxXkv1vh'1ϲh-:tM!xLvI2PjUMSvWI* |kY$>|t>n}IHuգWj;,~o|Nţ⳪oJҿj1yoXIwxCƟ( N2`ygs^arQxybtnc4D7kCpn=[, KF!wYk& ˊdLX1Nt9oY/4?q纂HoΓ/Ys~Y9 KXq3$!nl;MAhJΫAUf2@Cp(P%Nm"&"e>s{~o>`/[0d:s6gsAa1FG,ЧG y; \PHy#.nb1=(dPN)`GւC2DPcr_817\X mn"&`FzEYU򢠰7vvuqրD$V_X9Wnn+2j߮\Y?N!c?qǿD/GS\ 9üO̧(`(`X^!*]!ds` 2!4,3W.YH9D5y9#tќ@s=zjC=kFL $.WJg|m,엹9$ Ui4zwz8U2 G ,cyYFe4מIH#[ni\PrP(ztU3{*UcYV0@٥د7y@Vw4=T4Ty_rDie9yEp!VTzlP]7pPܑ͝_r,GTDE "5{z[E"^ӣ[w@|%ri TQBN2jZ A ^DP "#Q֝ F>r|Ǔ7V#P7[6 =qH[ƥMqZسgu$P͉!\iIy/BA Φ[#yg^}C'|}wV3vb5?ox0ZQ~z'_0뵿੗X\%K_ijSJs}eEpš "cpZr9YU˞|EbIZ w]pHO)) ?nHA؞(w2 d#IWQ|$U1&F9Pu  Xk&`hM>|bENB(FmJ̯lpZ+k!?_bD~isZyhN!\*J<`ɒ|}|ƅOxy܊2X/[yc%ɽ%kgռ+%qӞ\Һ3p27ٕM ^qaBwRH.!! 5y"ZpQ5Eљn>C^t͐N|γDMu:@eȽzF -FAv(iv0'aY@>f:MDZ$/ۿf"tK`ICu ?N=*Й3@6 Sڿq M[PBezQ]݉~iwYڠFIYIKEBn%l2G´. }We)Y.U>\F6$H:Jm7Io:;#.B'#_$ҁ~G,AJ t E.7!x`՛whk{/Ri! Q??:ah%jLC9{ϨTLz?a |3(NG [sIC`ݥS Ѱd世mnq{jI^0w{~#ˣ R%LL5 8"T &],`/$іlqε`(Bhf;P}+LMvզiY;+i7k'\pZ @ RUY0Օa呫 |3:zŠ+,c0ݬ5='- K}pϯ'aI^DwIxLzmCk3sqQs%~狞t1EG_ Gb#0@5 K5jB@ܷ!v1D!: xR %чNL@KL 5PɴihaDRi%k>ՏkC#/6-ϺN՗{o߽f )Pb0z8؝^2 W7d"X!aI/I73*ҙ_%O(TNOqT46 61kދ a ri={ }K"bz }ނ> W!  aO>4_%_B!^^wЭ:H $dbm)oAOSd#zCgH%A >P!IGK$OB}~Ԕ\t7x]9Wv psiqЧή ܥ!T}kV}tNMRL R4@DJH~&}<Itz:⺇D~&UkwG%<; n:5K T+Rؠ<̡&DaIUqDJ3_)1=L/ ?SkS)fzhMV^JqCsVtw8]3;UiBZr'6C/7nK4[.K\Ӗ|>YbP1YS".F>J'x]KA5$SI*~㗵CX/8,[ U^.V!zLfM`w{#\n?RЪJMUOuV*ӟ7ט7ɹjEKhZҐ4v O~fkn/Zťhr%P:.ˏ7͞vӼh1lĽH ղ1 gl.x쩷ְuwCrw=D jHIӑFYm7ƵpͻĈE1e oj8|[}Ӈx$ѸA\$^r]; %YhN~z)8F5| EB^S$hN~zT T{R$d ^Ͳ$B:p2&M)O/UcRTT&D?v?]xG ILniܵsA;/| =J_ 9a/G⩥'tWb >T9{j¯/|@T]9C×Аq_.Hv7gdzxS_vRHno2m@0[(]mH8FFDRgXɉ@s$.RĚs'X^i\S0t}z*(RHھ<=](/rN]# ,C-٣[_fˡc8hv-6rK\eKh>(N$_Z3['v]N+j !@D,K05{㋷zo@QksŰ^'0DD`]mKl[}@Q1Z1q0uɋ$t3O>j"@n~Xe2G;nH?VY#]FWۑ~w$mJ33MG < <Ĺz!r ?P?adjhgW 5W<(Hz9gTĮ-6+25N*!)G75WjzA }K"%rѳsQzalvikb'|s h,&:w$F{)e$A&.!E\LrcQrމwNѤ kuv,S"t^j_>*"쯎2敭$6?X176~N.iB[HUPcf^닖$c)qT*m\(M?۶Yc`,JOױWOgYlٜΌwo X6w^sol+[זf?5sC9=_([7 a+^ JxwG_>pmn?Y>w|SiůCJ$E%pUi|ҁ86Ɵr $doӧ[з~dܞOl զ$M{=}+:=NUgC$+NVҷZaRqq[)$P?ʲ5HV"@1"Gg{8rb|L$uvF:bqLqE%+fXWbvD%"iB)$h2c)SljLG>q_s1>V{-!O 6$w쟌|W2ќL\1R2ѷ$ȉLG%owVĶާf޵ {boY^;s]m`QՑ7ң $NӉAϐrt-ًz$-#TMj̵h\oT\ Fɟ8[<76cf קvcn7e(5!GBq>xSBjDFHzeiMBK|#S@!¸!Ijq|n`]܀>:%%%~1?uRT7Wtv_ܩPt򧞚;Ut+ڱ82[8#/Y><_WU~>hAzod`$`Fmq;XSnJ6JewnL1-m Ph 5t^7Z**6;KO&W%GX _A$v֨\nR0KWkNA c~iO̻l3 qAD|  GQ=FN>)9 @>Ak4让 #GH ?"_]2%F2) qR:\%M U-FFE1zX„B:ȪF&pMᔫ,(_<<ǜc~ki {^CEA/^m'J %,yhW$JTW4{Cn)vRa:|03Add#EeuQgW0Ч.Z`BY>L=$D$,4^L f&' ևo,'M3<5_sFD2T~M ړp0 |D@HHkß/9)-~$]z˵oGQP[=i`⪑(Zl`pwmہĜXf=i+mx;vy@m};fw,{C>dvW*Cop7nl!s2uö%_@ڧ;[2(kѝ=hS5\ҪѮg ?+kypw.l5g!6Xtex2|2󈏺$g I[-٦ .O_ٴ*-?/Ԓ2̡yCjW:N-|VV ;d y~pNJV^?zٟ 7FWgAn~U)bk۹D7s/<3C{~s( ۷@SLjʵ̼}F#9ceѪi7ʮ6[v'O0 ^cE`,iݵϛ{\}hYoCxx&P^4o s²PF8Gz^z.`MX%dRޠ_UgS1ZwP$)4<Ғ4d *O}o,O!oxw^#Q%Ƥxb"6#k,6k~e"Sz.# 5O/DykneY~g4.$$U" `@~"HjhvѐQ\nW(월gYkt:t0i$eJH=ݝEZ 6] XuG#IFb.}.]<@~ | Fb!9"~+BEj A[Y7-S3vlӊ)%g^,G WZm[%+&b4r5Il^{%KDd*Pj+5~al&}x!!2 $7l([6{JŴMeL*hfXGbq B\?)~1"MO;'\g6#l+D d8\ e>EcpC1w[E 4ll=;p'?Ŋ+%6w`zII-TDHn{?5$ [?Mϒw٥dˉa!4r_=WW³_(W$d_"u"T @7  %E(6o_؋+=jKTTF:t^ħ@"쿰mD* e11c-9i,sQJ%իd# @/'DP,4[󬶌w~%Gy(qYmR jnϋ0<]C,7ۚ}mY{;zOK.WGa%e! sfywZl2\"K`K `xm p(粝ƆOK3 v\16 5$Όg3}1PPJ|eEzA>17Җp&BNB=6tfoƛ_m4nuw ?bV?~ ҷNs<5FKr92zso`ƛ6?QMrA˂Ad"M䔴`+ 좦#Ϝp9y2*u QWKJ0WdM(mټ_r֣M3 ʰ/BuWn Ӻ_B Wa?20I^=`Ļ]sugx')qp]vr1ɧ}[ACT_0{?w@" 'qJoēBtڗ,v/w77oCyyC,|//fLG8by.=3]Y+xPNo')D#@E9(F ʼq(BiJ9J';GN>hHxԇxѝ Obpag5 =[ ^U^,3|g=5s68"B4h;mΒ60B%ۥ.WڹsslEy Edkjq]pî=Ȧy+g–kDU `"82[P CθLh>*6XeQ/"KfPO2Jn} īUi!"kZiz-}].ί顿OTJR2mO8e%us" -smXeoޏP$V[&^(~:|7 O(%!}K&/`fR"ԶCF g#hK*BZQ Jt ?D;y7>"32虽(BG *{GY^2|EV(w. @_7[+ndˠqZo%.5xz"><2_/q:Hӑ5@nD7%f/[56m`ywG }yZ",' uA! q,'Q "$X8 /⊪ŏu'S@<d ro!D1$u{'b7_r׺䕚_*!&Ty,8i`7r~ ԿIk Qsa2=> 3\ 9kڔc>LY9,1 @_=yZH_F34r'.TfBѢ+k|H mj|H'ZBȃX 8 Y>4$h$[./ 5IHky%8v?vCiG8~.$nr "h`k$>,N)%;,yܦTԐSh DI$VC6*?`Q ; \L] { J6Kwxh|PQ$v@|fz9!KV|f b!$D7% 6Xxc7 NYd 0v[ R{oZ~z>g>)&K}xO0K)ǵqg?kC[w@f~ ĬVAQX@Zf>"ʫP/aI1&WF$ UZXus6} ƁWG8m1``ׇ! +ρ"!K(0W >j#C.+ n\𴋦Oxc嗓g@tVDD2O"{6{c(T@YUK&HR2ձNh<$V}D㢽WФ`"0 )"'_)D 4%#kA~0m~mhGF&@$g[Bsh3j?b-XImI1R"'G;P 5.DYq7g.'P(ԍH%zt .g k! $&4 xH$^īDҿ⟯,rFпTM$r}_rzߐu3$_XU ޜ(} \fWuL\;+7¹{~Cm"5|`/&$*"6пk]:n@箎o$ psk.O1١75ϝٳ$2Ks#j_N$!4<(\@N hҏdS><KKe~f'J(3vwRP6_&bX{t[G_S}jzKEoiF=X|xail/䈒&:lovK[oKƥ=Ik}Z$8q1OYwyf2QבpMh(G}O-Q$Szvi6f<%> g6آHtTi;Si!ƌ%JIlO"}/ eٵABX/z`:d$ȓ gg\Ѥ jɒǏBW$}ri )Ln![jZ|$Q2d.oO5vZ,'H#(|L[ߨ|ΠC©sMZN}2[|9<XAPXC BHtq]o,g{/a K)pc3c_z$)y+O JL"@!6)wlFy[Ttz,?m7',>m%8ݒV}7)1CW#_s}_G  >H9*&}_%WNwWznY68z|MxMh 8a" "fE̪{Çk)i·%%_/3' Ka9|+s7 b958t1p f`}~$9 K_Gd]2E7ax5{K3LF{tu}Oj$BmK6d"j?Ѝ2Fvhʽ>3cQFn%^/Rr$E #'?.X"9 H$z>@T&qgQi5( 'DtψjVxH?!v|sG ,nWD]﾿V؇`oTƎ%FޏYcXN8i"2?#񅀤] I`$0~[v˗*8Y0H^@KeT0cZ%"ʔ=E2͵WӗD>د \ ib1Mfska,.>iò;g@{`R2[=>E ʬ?QcaIؿzx߮kg\,><sn=-}.vOf%li8vuąl(5 X`xlQ#$ʆN"706H sxD!q#Ax}xƃnwg>ZA.1l+V减8'WM(#ˈ2ל"7 Aϙ<eF_J,D0!70# N B .& A!y+ـ3IVZj>@nO?q{O r_B n"/!F:ddK*.Xws?jd=LX-6sn@<6+X8oXDဥL #4GgY*%xb/B<= T|j @D&% _PUU4Z2r$qNjz]\m $qmlt+lȷ64`ÿ5d,%uM X({н|o)K Gޚ ov|BޫK>Rb͎G-4HbLJ%:.` -RNq@D}zN٫ak 0:E6\jAMDPow1]KoSYʠL̺`|=U1l/c=b"Y4,cg.~*xSly<88y,>u(nq`gv񠜄[}ts9_hg.q;nsWčiӜVP}AG$'6G 12J>$^G!\O  v@!P($<#艁":%~VJJ+o !Sӧ%(y$,D%P|D?:&9/'FSU`6-+>t/-I(D$"tԓ2(wNJ=| 0`.0K|k^@Kt7fv]>^][߇;bp|c Rܻ3n3LTj΍Ih%p-*%,BCAG`3 W0T4NNk%EFGE_\9{τDaܬ,#^ާaHA1nkt08ZȕA(SW&XU FV!h] Ճda"^(;_9jOwWtAw?л/ɃFV)}*4 o"+[k#"Ձ=qhHy#ܖRPDp 89̔fzB1Gt⤝v-5jb4gΓ8}RXswJ_~\\Պ^:a2qD q-UmVIN|!vaڻRsO"d RѳqfW3*uR*#%!X]6}fY~ Ԓ):wZ.`& 3lWc6\uUHr:j0ٳ >fyfb~aU({ 7Xs( ĈC&耬;kH]BTIB8@*6}]75#'R@\#<:W Z.=ZysfCY MM/Cnn*’aQ9+ʨ09Qq=qNP!:l \NHD :P$߀h LYÛ~YGrnٓ)$cBN-mLׅ" ڿP$i}p ]NqD職Cqw,C08h5kUz>@đmŭ|FI'pL1fS`5NUzZ}&?{^P@zkAt53_  Mhqo@h;'B2|3w.]o_U^v1 净T6Oc\On'fR1&FRh_{y{'b_f:דF6ִ%3/ݔfsn`mFy; g=@zQ| / B/N iIlI?HR & _JQZ2LȢC[A>Lڐ@:%J>qג %/> 3* Hbrc`wZ>˵8ÙMY RAHzƇ(qo}EJ2~[l6?ߐ?&Xc$Nl:ԺȔ5>jf?I"&XkS霝ϸ5䕎Ș?Sqd\3T6<}pkkv~|]ZqEQj$7qL- d'{6̓B>jd 0yuW%{k6*}&=Ƨi*k?ְU8%lsvɫ(6@ ݓXQtf0-xNSrۧ~3쾈wƼhyiWOGTkO65g;ue[nwѴ(Ԓ{_Kq-70BP+%$;D>BdQ8>@xPI@muދ _}@d%QJҞe7H~d9$[T柃|\w5z/?w_I6J }PEDW"JȪlbE)B@:"JGGڢԆ(`.i٨ټ=>aܺ`ZUx#KW(g?Fͣ@Nw:a f1gT#D} ?*,:B`#D]!kinTt j_G@1E$m>g5p"o\׸S_5e_P(! u|y= 8dpԧWӕxR_ ` „b|QW@-[uoxت`8YEc,r9⸅{+|kO,Z֦7Ē P3&* _ӊg xN23[)Zr]v"(O7@څeBzidL{\Jo=~fRΞnY .ofˢPNCb~@: YFެ9+Rz_fLzpͅ*e,gFS #"N\怡._ <^\r,xٚS}⨟(o?Ss׹uUKtQHDu H4Rד0&:F-BeOt"/Ј,B9*DW{XrJ:!oՖ#x`+HQFʲ]RԶ{s~] \\| PBFmh+!w( v -p #e$0`+Tg+9+p5xZe&> %س9WFeW!J_ǎ^?_ݹݗd t3x/}Pg^$CUKғ4@@Fz?Ihy/|ʖfruZq̃޻ te m%Wi^yM49ْQƷ/Gd /GRu'1֌{!)̀b/GXt׏Dhu @b֢;%D;h7aOx" y){kG^R+C"_z  I0 BD ġI+|{}㫤x$~?? !؇5\o;{d$1m ]3tjv*|N_c5!Y~D^sd~v6-G|q"'Rn쨕O9tƵ?q?缳_!P،vm~?T/+,Ra.ոCO>Rj`kxL~ j? Y K.9`au7c$ v;TrR_Vc]߻\ȇ62xo @ Rѱ!n,]<{" Gu'|zYhnG$?Zך\^ Nf' v"= ?p#ZD,v}i@dAPs3-/YB&eWe$=gN>fv_H[=RΙ0myݳѹ=>j'Q78ml0"@"Mta$FsH NiǺnB}2ɖv܃r?ԧ:֛>oxY5[y'_$ܟxଫsW|VUQnnնy bML<($Dቒ~/}TDp>a/ߤ"&,y1߈"k9(ma~KDaU`77˱Zxkn]P$Eiܤц8$ǴE@ cETbiWFǦ=T8| uc>uEF>4{,!wn А/_n]|6+r: -kt:0K V7g֜wVT-J6HvซB0j 9/Pc~%Om$k?! ^?}?OG p|>P/NB5y4t$cJ#[-mg)k P Z 's$(vC( "IPu@i01Bn:eA4Fi$b*!;U@6:FQ, h2BjoD6j&* 9ZF FhhhhwoF|'Ϩɴ'cLt:*"C<$nxP"Dń}?V'o=}Lv_SHpۇ?GBj"\*Ҷ~R.~m~I<M_54ן>+o(mڧH ]O8tD͠bmX"ۃSs%;J:*%;o:4y~?w(j"E&@xxՓ~&H`!?>$U5)wY<ׅmTD 3q 1=2y84{ u!45h0uxW.}!pcJlKm@bLN'.}I)_ .l7 J5H571DDǥDGbq;%@[ P%AB AE;4xSry|?%/ hNdB\d 3h^DGFH-q qqdߑ{&N4`R5&BrEoZ+[]L|[7C\LP &@-BS?{hsEMl+?Iyӳ)Vn~S~ vrkt+΀(뵲cU jBqC]K^-I"oF.4X K!{TqƂ-V\WI¨h%6W߃1ѱ^$sX@^HƈrөHGNY 5C|}LA*zu/~(F9oA`FrA_{,;y@[̓@f 5944HhO1 AS\Q1*یb}"HURFaj35[\F8))K{y@62V9_ !d\8fn}­'T@ԐXC"Qdl 8ET /׿EnA\[+%)Y dڐdqnŗFO*aA!@%?{?Hd7=Bdow`1{";?ggޥOyo?OԊʶGF[ᔑSW=  vyǏwwiS0B9Itqbd( $C?qߢnix"6%8V(@6q7~ՖFO&JjZYMN1X ܸ zLJݳ2r+bH%"!7Z'ѿWBBW8Rl4VZ$5с[J1ewJ7JϓF"t\A7&k4e3DM'DmJI5/ݩ^ ()uK$ЖHڤwd$jKreTrY@yJARA_O_iqy^jK4*=p /*b= y:SbT@ _:u\je$=5儴#*8視<o!hi.SՒ{xueZ3Ӭ&R#ѕa[K n*}"w5*re~dzӫCWoOd91 TV:5_4-ۉH]Q@̜o_k%Oƿ ƍ)pd~:D'Tyu=#bt(Ƨ"Oyཫ6b#ȶzhj|))#vOYMUj]෦ ] 4N15 Ճ_g5ݯhPI92iHc^X't$cz7 ]] Ԝ9ۤ ezvf}wZd26+&+NI&O,( [ TѵjB,+pIX?F3NHH/@Az}t3$FPe. +8)ˆuA3zymI0[zV_a"$uh' SHf;ww?>$=4?]J:~: G4g`CVq>-7'# \>)0vw;jO}\ؐ)#ays{2S)$Tpwc\+S1 pc>ڷPqdyjal|G_IoS.eݰ~yls*?m4 A@zx&"f UNgjVy5)Gֻ!ҶO{+`ed%F (IE{`H%%(*p^XDC[DxRFfyfnu4Sl A_<: |ux- ֕͑W7~ҹ0q)45v):Ss'r3}10CCz $ZF꣋ܛpiY(giz$jE0sanzEaB$}֖m5*|};NX4D=g_gp.eC,[M1~#b9/Xr v #^Ys2pAϥtPOIʦv+^M.U9@%IR|OPP0]oII(@~o!G'؇œ`KL(9BȇhDN+HOHkdG2JvI,htsk$*Vj}bW|׵zs4k3?ܾ`/~cЂحgt_l!62^_^%p[w1o-.Wm55^prcm<UY*JTҎS; S̑'8qӺWwVZ2<5޳7Ќ3plRu}k}O\̦)}vrߌ߇e5kzyN;fS2zl͜G}r*UҶ&oӱ?}~d-Vd]y vVahomY.>E 8d+ Iv+Z8!:B[Һix,ӦԊ LQD:*$K+F=S7M󎉯IɄ*_wIR`*7'[A)vgKj^g?Y&'[񄹻KchxԉJ7X‡X课[  RB,N+'"vynά9,#q ip wgYs֣֏_ _}l?6&oؼ9MsNK>*5-٘Ft6.)!TdH>GZµ_n1^.@hlWWvGvB)e{jL^kC4 2ZJiگmd EvWFyU'Nn֟*;*#А(3P|^Ky>du$ҶI-X,Mb B1++Lы!KjLN9r3$yP*D@ 6yq/\P ئf.+F28@-a*$!2ICqd*y) H:tuM~NQh|rM+#e٣EZT hJh_ڔu{T-{<#Tq`%WОz}xP)<Vg}AfדKt6'?i>q椸(VnQiPDA߲Bf9dT/߰<1 bඇRwI k3קÇPU3MA<]qsԏ mp x>J)yqimdD*V{F-amJ: :d޺HnLBzo`\\4(;YlL0lCiDd 'X`MFh*W % ~ KmMVUY*[($w-(9,8H**(% 턢#6L_6&`hrOHDf=ԐS !/Kwƍy=ϐ p\|D? zف1.(Tfތ{0 $.y\@HR, ˔̺ԝawfTƑ/U19iHf%53O TJ("s \@X1!Cqg;i3"9"j?2b a$`zߥ!P<OB B'CH)?h}0QZßxJ +8'bHr"E% %VUOP6RtBK BR-G##wmJ(.Hg:lļnYCGO}3䨫+SfNW"EK9RSЊAFV;y%ԝ`dh1o'(Fo$'n@HMjL9x[$?$skcaKZꏾ/Z|:_ǷȢ~#J(`P9o"=GmDb *(? ^2w蘝ڸTxIд!D6BT>R":mqGJ&t ?lZLXȧeY0XXJI :ESH"}E_D7;7檾-D@8 5іݝ7JIb8@ZR¯4_Ui:($4/>D6 Z\/ԯَngN~"7HwYx.QowBZ9g%F_O -G{ų$ I@5a3ܧ)r, ≟QK5]^UᤣN=ߪ p(<-,ybu9O{+|&C@J)fVSY /J|F HI ]iug(:1Of]|5C)M*]ƪJ"7)an_lD0T`Hx,yW_חcKK=rW1!A0ADU`(,*i͏m;r 6~b+m^c4VonRL46Dqn{8*_XIbCِ_]5vYAkNz'?;JWPƓP IQ,B0b$ Z)WG)0?Wfݧ ]ߴC668peF߁?5fA%Pc"h7lR2PC2wj"-θA%󶴌PЦwU6cH[nBvĖm?վ3K3wgG2%LKJ -øBaUl{_Wni-H1zXb; 40-]w{)";(%"øz^궞6 d,&%dR{_GBAX %cd~ZJ}#ۺxa P=ܙ~v^!󶞿v/r"Yydyb=T&/%J,u_r@'ARIP!ov7I pAD8 XN IS=@)Da:ԵgU*xNU3W;p`qnL!&fP4=FĞpcqx8DP:[T9*ǻƌlTqn#~vh}o'"ޥRD{۞ӠќhUmDӣ^qcNnLn fsnI̛*gNO__:3m>\!irfѮTPW`('l9ISXoqȀ/EP52ωӋ,$Mvŭ]5 i$v bRp4 {-d 7dgV)sOYvziX|bc G9lȤC"=Qͧly tH( ʊ  PJd8JfJmY΀uJ D3_&[}X1؂Mݷ$ȤRyQQHl$`ت`I״^̓QH.fʉSoʑ1 66Q {G!}=e%t̸2*,bvݿ#*i1Bh?jB,aBAw.ZX]cS~q%d R!.(B!>rDXaD AI8ō [c%zFxX{Dҭwv2w#n`L^Snu-էƭ$l-51ټxl j(I[t9݀JK^k}í\r3H$Q:s=s`ݥF1/#]+4crtE +.lQg${fXpB)'qQoINcoIh<Blzߔ$Du5qy~˗+yCBBGDOu1`Pf̙Kހi"ofb?"$`o6C@:)Rg^KJ)xoDL:?"|+DBE'l$"/N^VESp҂sw8&icw*=,"i to>& %~V’J7Љ*ա!er`CVkR1́?.o;~ˉ'>R"=m>ܽ%Png1z䱙e5 s2RUS,)]݊Igwny֧Y ),G@#7<#ox@DaA5)pB߁'w=MA̻"Z+{A9P)#K,dbu1Ɋ:ciK2 z0[(6)cˏj`rF(>kG.X ׉*WpcgB_ T\)5ll""C/)JueHuEee.ysX`V6'vW(nhR P+(=]A琵IHwD/Bw?]Mz.C$De-P+KZSf[Űu]8=k<[ RbdeR}VbPpK҃=Y5xݟ;DBnE+Rs%.␸:/mPن F zFS ӺL {|½;BDl y0%PX.z^P H:[fD%ʳ=R?#+=#XJ[a8G% (0 '[!@"?Wϑ99{rlmpe1@ )dtV"> @3|Qm*=F}C0g/.$oDVr5tn.,!RL"d^r,'Zg&:dru*oΎt*y $KJڑqSb-NB}'BB32\Mw@TƁB :}o3'|8Ԫ̙s^!Ԃn^챊"Gj=yN7nsl~ p2S!%>^%GѲN{o;e2z6Rid`XWn|ۉwMnw=}"ӖQ*D/]:JB%G~ SMNk=_B7v}GU>O!/5`!>n1#q)sQYA{l A)d׵Uv9,?kYHpBIXakH rJP(+$Ka2[3AV;me ^RTƔ\!W_dڪ7i[bymra{kd A`=ʬaXOtS Ne) , "@"s ;>WAun1OmRfDGɜˆ{NS`L?B2dQ+L*{]YmX Qu/{R2Y2,u9ZЗaDiv`<6@1]e ^3~oc 9%e @&r.(g2 JAU|>dP֏?%TgDg<: d ,bx8_.P',}9w;A :Y9dz֊< j9ZG#޿ -G8fZ'bM@شw5g77g@QdhOVTҎ 8{Pz]`hh4E %UTo) `PII%A.,`TQ"BϢk%yt˳ 2}ÅY47݆^i5]uJAmfjXۂ7GƬI#2>k`B$)Ӝ C]f+nCODFB`U|h`;.$_?5 Y as،c F@u(l4%p y}u^i*4ųɳp3M"jwbAA f BDӾ?E R5E)nmF,%Β f; H,H3(qgA؞UAywVbIrws)# Ue r(!qĶ^W36c]/W< ~{̺5 .Vm!pn _Ɲ(1fp1hObƢ<]t8;j֝12Cq Z`rZ]9Mt2,2!HgW(4 &BbA>+ LdQt!&=baܰM ]jd$ nF"ɟ9TKJ , k@M&? lGeq.S5'a՞G^x3pEw>u$dDf9bbT}_cbR`7hցH1vc>_;G4~c|N>M' 1>UPdaQg;ү0`Cyy/ŏ3K;^$bF`@D2-'~} H  ل'{a,kg {\9 )$6 36|JSXI#MY֨ IOBv9.Q6߾KRG){ݠgމ-.>)?62W#ATf ~9p:E_}b;Xct (s{βj 44Zoо{Zz>`)ℓL8]rh{"I > ) Df&TT iY(ӆ ;դI Bxx3+]%j[bIε6?eBa0tie,zZTk{3n _){ľtXgCtnm,1VТD CxW$rvwp&7ω 7}m(:K2 sQYֺ/ b kefXnCIeHlhG䌜]}S9/'z4dwfA#Q PZ"oS̕}_m%ʓz+قxHAbVW,ꋬUqDpTYs͸Ո-nY^*H+_';r(  O!GjOpr5ʹK_tqq]iq+2wk?ͼKc̍Q#Es,es>p}N<@ݹ 6.k̮CH"treIEθ~Kp̈-js7=䆸 P=/-~y9.K,IKMy1<pt&Fxyȧ,2).o VUMI67| S[AdAfr s '|VpkKc{R[}N]7+a.'HLGPOB)_#p8C|` 7-Eqڞv35h"y>XC/!8g@$69!ƕkf3'48!x1}6'P0>rRYQyd-,[9ӕi|S 5ŝ _EQFppƪ\w{?B`y w~?}pӗ2~BPa6@5ܮ. 4 MeFFXw1C1D0E=F>MTIgLSXM ImI#*36+0B)ƐbV J8V*'j,kXj+يPߕ"e8IvȰ,dEYqYyǯh A( JS橷oByj>MB*) dx@oą?CMyo6[{2#e* /s$^6JD7eλۙeX½Ƙ+c3g7ȇ8pO%l OܥSt?*}~ۇT>t-ETrHDMab PΒ'u_ P,y⦻ޟJϼ+mg+3&B,f=gPکGx4\5 5\ftz״f=ӓeWl vA{++]YI̗7 i1(-Y%^~ jXZXr5My6e.e7F<6b(|Atr:j1 Cw=oCq\iz_::CQ l(>?tRp5jɥ!Aca࿇BN$ ,!@—o Mf] | 7^Bl27>| GBAq86MmuM&N1dUFnc^m%߀@ۿϼw(w:}d ࿒Nj羬t"R尢'Ċ:5 $n]1+9i)JkRX^~rGSEf+3l hTU17 V7zmo*ݵ afJ†lpR )q & [ m&j-W>C3u$r@(|Лi__T! BWW @Iӳ)ZWj2:/cH72 t,݆-PaKLiݾrs Fr$fPi{T%5'[$ g>"-zHr%Å5h(~PwlU[tu˙~|* ħ-${?KE %KZ T[`q*X"'},El=0]aZbi%A*,QUO*ɯDzWd0oHxܹo7E p (wPWd?HjDΠ<:U hQð0%t+ =0ͻ7|C=$ݲ͛)=<ge·G6Uom!>k&Dx|W]), _*S]ÏE([; / RovZ帕eO: p7 SzL͟ HhFj.;CL8F6\zPbt>2o+-YV\5A,A#G] dߵ UZPڿ`~}&^!UFn ڊsyr$e^z&v*jl<"i1cmQ*]ݟm2fa5=bgL[Kщ>0BuVmNDz8oޟ /֑i Ϥ (B/ĹEBI!Zt6c:Ƣx{Q_ew^%Ea!(HH+xg“G0w.7i>o}眮KbCk@kt]9--qr9Dߙ]&  Zwfϊk{Ī 48a,4ڑ_=Њo|oNyNxSO4M>xTsUfY'h3EN;PqшD=6|QU" %lXsI#3U}{T6s}۲ͅVq=zY4$1i]D++Om^) gT(A$xOt ?꯰䂿 1;CF8TT׋S(l J߆>١Q$ Y roMuJ!q/}6TDֆ߆cH|2 G]p2wjDE jRiʼn<}ʉF Ej6O!hxA` y >d \X`ՀO1htb%Zʂ$$BK$ b>soWj|Їa9Q7?Q~ջ`p hz -]yEAAi2Im5`D|E}bwy[5l%Jꢴ2j]"a (pI3\&Cfm'^/**S䪷xvo Vϙ}\`ZKՖ+EEOH"jpƾfY+\i'ĸ^JpD; M2i} \GʿCS[ZTLx|wm~0+ªyoGIthk}yTA>T3`B>֫daG!$D3 xu(ܣ'bI/!fDʪO X h'UZ^D$ /.1>qu.237^i?cxʦPVhfii:Q![oHJk 3<߼0;rd _b|vʎPt0.oW{n3ݫOG/J^238$ۼ oh۾mN_T`c&љ X5ܾ(8Hږ}t7'к#ciۊu&Fg1˟)Hu˔Jq$*G} 8oák P@xB~?\SzRiI\ƏTNZmpxNc<nJbUOd| JѲOuc)SX8N惻$r̒єU? M.MeňaDqwݮqUS&ɷ5?!#ΗO@+EwkE \RoHxb#@NM/E 'VAAgI-G}{prs4gz8+?*I]+*5S݇z'Qv ?k|FRmPno|CZ(\W+)MFA:V;=MRw촔D]L$jџ Ы?XsF[{퍟ڛ<XOkt|Xh-Ϲe=?-nϴ|tY P^VԡVJUQwBL*[Y¿9!"(k#.5"2OC:MD]u -jlSjykhJ'Tx"GګlQ?,+37N?2/5>iojUjb,y•X/akDKo ^X착^||ZMu̻ G?*}KX=OANBw;bnŃh쩖 @  oxL%UIܓ/}Бb[ąV2io?V'j]:%|lEwL/K*''y9K"F-ҫ?ն,hWf\Wܽoڍ#2F^  #9`6Kf1\<6 )x%U)jMKAmg~WFr'C,z1a4 Q+rIgh 2AGgtdQ~-5!8O9ܟm|I4JK>N.qjm;Zl)~TȧSv7bu]>&$&@(c1MJ`rBx-v9fmY_q$6L7O.|`-n q!S)_N/hIgvc-뙧n4}yMIGyN nHN%~Evbݞ !KtQTu!SE9KU;$/- ֜XgC7,uIꡭ?'9.JKUGw/P]'nHtQ6R؏a$?ґjrcu[:!fqiA%SA@&)!$Mkޗn/rnޛ;V`cv (ͤ(Q k/ HB u|H{dM9$Jl%HZHePRoBa|_,0܇PXd6sY,Mj:;MJ)gs9!92c'm줦l^/UN׫єMW[N1;E?%9Pfx_) 6To!W\btjR%UZ4de0ES ^-J et*NPK?"q+ " -"ѱl>QP+qd=?+?66|TZ9ȧyGmrOyRSU1ĆF%pB'EWS _74% MLnWm90$j6#o>z l,dr12[@9&s|ѷS)†TgbJ|m)SX3yQ#}sЙs1#]g_N sn f5_֕BL([l/}iǚczH1uI,ں%T2>y$%If ';qWDtɠeXRkfY~#V%Z Zu=b=s,;X(% 3]@˩dO;iM_TkW%9l*WmIJT- |l]0$/!-u JF扌P$ gȯhzYq'Ҝ~b @V'HA3iBDeBV՜Ak[D&W9Z/Bg p~8l!0rdp00/X)jkVF4 ޸yDӑ&Oxb3Shq˔gɺgK5.~Tw5xǻ -* keu w-e!04E;J`%% A(ˤYŧl?<9dIsզb_P20fT44%]99b~NLX\RE(q> R62,G3i1r*!Hik;,ZJ޿/&Ӓ̄U'U+zl.QqTKDsӨ\nzz&cGEdUSwdFáϫ$.*i6gMyS&0 5{@S^9;bExHl4oDC Poa&M6;UhHFN>()isfגВ8Z7zBRj @˔r߸LNy|01) 0` jKUVH̚Q mSBeY00ZRl1~^g~|˦t֣^j=IH? Я:fiiazĜcw @?}x-#NG]$D'%R[6:b04 <pZё80h|:';ιՊOo\.tCH l=\p|)]Y^,oaݥdpAkK˲lOMh9:nSy̛8DHӆܼxb2& WA#Ĩ!yMEF]\E-1LҁR1r\MQаM^ IZ1䴁&2EdR~!,`T<69ndP1vd1 [иkw愋2KdȘiq[y/N@B|RO9:Wc )ܷ¸gRPHPuXB|/]bYnKTUn@+}7suIʣIĆ- yQ|^: QAߌ }7)Hab8gS!6T0M%IHj$KdܶLv;KN2 ߶s-k8rBhZ["Cr^mA: G7oĘ܈˦OaIH15K`P ܱ8g]VN#ЧPqcQ ),`aR@PKZ gkD"")aX}8ce!%DcBmh>&aLTYW1̡y1ί3$57&={VsJOPJ6L,\sO@ʕ%Ԏ蝹~QY=EÖh-1;s C]Wc-|~E|f (VS\nPQZ(St 9$*$-#i.;W@J)Hpt$k*j?}[as q+NM]qTGx(=εgW>G1qxEQHh^O6Q* @0h>9=&=$Al&If;yӓZmD6`T0$c`rHަ#smK[XlAsN3);r&ZKlX _Z8 39_o-!“L2B )9 )4Yr}> ȰzeOݡ+kAL&aAM!C`a you3F3$Sc 4!+k9Y(ZR쌉4ȵ{ %_2_6 eӆV{Ve.;'s`R|{=ʬd(8.z۟Aj< @'RװUyV &uWe㞆4;]<*e&F<NA<3VhZa ,뒨!7XUt$֧"BaQ$0픣? ;p ԏ'؜zW>¬wAY`G>*Ƣ\u0Y4L$?PXܱ0:"֊|f co@bp2Xz* $v8$VUKufM\lӬleKWQIxښԧxl/T!x:$Σ#?.ЛA @wA6A`)(ԛ"H`("tV4bs`v/:ltoéGW4)c$4s8 /XUX>ė Eb%&:O>T+O9O_KߓS$- /;_v9lG;j}!x\V( Z_;;Z!Dj=?zG Os|X"ea.ɘM.F.E uQHwP_n%ZsGYT {#t~yywR%)<4H* `亀.!d &4YWI)|Lf!(A9O10նfcѢeYHZ8f Nk9YF7^:yP%'ר`}DLt%BVFRFzҮFzd`v-8  fs_%6[!`E e, 3/6_E5B4Yљcx@k{30-6-Ek`0NdJ8W6eE+ zKV=?w $ ;pڛ?2)hvTwueL5Ē`w>'P(Mhf}[lY:$"Ӧ|'}i&Jr|/nZ=+.-T)4+;p/5o˙.߫H*YownGkݕ/Wyw+nXFKݮkS¤/;bˡVw'l\QPhåY`X2t52# ~j4:7ifLk;锎p6`d,8I1z!2+z1i&swE->DY G5<$r&J3 x`ހ/xbtHvqL8Pc#XJ']ChnT\>V l'Zo) #qJx sS1!<<DžJjIը́I/F+P t?VX!nU\MB2%h`x"&L{^f\,n5z~ۤx JLYX}_c+Xz[1 EƢz D:/C,Nc`K,ɰY`J&tL+f(yM̈R9vX1#jsئ8z3x1x409&é^nw^?Ou6^ ɾǡ  !8zCR6Yk>p*)}x9{^;1Dw  jmL(` O>t iô|m´3ZCD B%0EsP$*XE-f*$PSWcx /^zE &հsZdED<;V@֠reHVxר46+tk6L3 ~KR=ث{ fYU2J\p.2Ϋasc:qzцs8ҬnVQ VFv 2 R-0ш7rZ랼|XH,ЃfXɈt$hYPO=b)Saf9Iev)`CeQ{Dӛ*3Mtz:L&ޙ< [,`G33kQIGT /7徯Y_@w!AlaU d7]V nX+D,*#kZs}^SmoNp4 mp^c%`z$NBGA­栱'lZc`bK=BCR٭WC|E v%\kJLCIQ1|# Uo:헠 oTܿ'sls;:jg^׈Am{\ՙ>`HPI&=)tL-!:C3OaG~(c;D] 9 ׺fBu˶z/T *6}Kh<! (F,+[/2'xY 谀b'IO-7|<*{nu._+"24[z#buf~?o?1\2{Ba!?}vw]QF򨤢?2I-LWBUMKat5cl\BBAL*f"*aZ ʰ˸0 *N?Jɤ`0p4n jsXhAXZq5e)odlwHSH#kрk,V[OPby %7uFtWޟ): #AfL >^/<|a*9Į٫F>zrb4jF\̎lV@`V5< kZ7qd .򇸸S AaUþtH2Q3\$$cq] 9nh Y Z;͒ާk/ ڟkDeА ?}9`JXPi ,?O*AO`ʵ:jGlB6S\ #WMr7Cׅiz4;~f,ޓVyCn'jj2f/L>bbX@ိ0hHbʝ@DQ$bi?xPq]r*f (ЩALyۥ }Q3K*5l\&#'cBCẸ?z=#Iz'Jǜ1mͶ~ۜ̌+SZ7ԊKlߙd—$io@g`[(z,ҔFdkt3^R(d!aGIr9'q &4Qr#g2|fQaTzq0\uM#2\,T!K)SruȎhstJF%Ng}#E$^Dq`x4iFjONqEykd0̤Qvo1<(jIF.ՂQiɬ|vGDPb%jGTfk%u:f7$fH1QiڦJfǝݎ*r3+ɀfَӋ#EqvHzI<4 @*δx>DL,uXaPmI#_eV@+@LS!mZβB 1#ƶZ- 1""n&abã[lSb$W8*}rӿAovfRϼc,DVmǁcv1IĀOؘWI2@PI0C]aD#1K=_EN09zQ+#LvUI#zBt%N1hH aqT:!DKk_aB';/0&xP{ v\F&NB V9;+V (=.S%TRLb:0TTN8XNŇ}.Vd >k H0yg\^>UgZTl$n/Ac.Fh] Ʉ6S?)S;kD/R %rV<9Svp%t;! F?z35nG7D\w]<H p+gU;W÷zҫ] w mЪ`WS.G1pCt[R! ίw \ ht&o1$Q;91ZM}h-W$\ؕ¼5rQ<P/m\TC]sr[ gRH$ ~4dhg|08 ֿ| y1џ0C!Xo@(<ӟQ~"XAzR 9 %NE } %lGz'S@ Oq{.F+X2 z qy W`K1٬}#S%zH|T(eݫ$Jzئ0<~ǐ:[ ^⯍zJ,8lwHYLE64зBPܭ%-Y2#iX-I5,\_;_y:0sG6ikC{bjE7ПR<$q~2X6R,AJѐkEr3wAb'O݆Wg{[[F\;vR:Z r0bkY=.Ɨag?i *|Oqi'^u5-Gd0@󣁎%:_<Y僐U\b'V^ÏGjZthI6++tbl a 1KGtvaw0 $<VØbVKX%0qIjBW.9^vdKG .[4+~r0#")Ubz$^~`$d dvUrH:В?kd14cP{FZ Ƃt$aa&Cdu-'UB2Y\a$EPANIÊoH<ꝎfҺ1k j|hKso' d JEmJ׳K]2Rl5_>3„p@@9ci1pc$OpB!&yAD ?X"!~=K^vk b+mj!e=*< b!xȵ6k%.Z~2xiP@ߩ=<~r/J8vxUj/hdDޣmhuͬ1685 /J@K8I3 gjtQ {ӣl>>ZAg!ު3 JVT*ף+~/)3KrdG4*IxyqC\3DI>ݩ3m w yr8D|!^6١a }2Hx_!z`D478[^Zr/Yjr:$ݒ@h3J@ %_ .K}Ia0c -zeKMh4Fǫ'7xq;,lAS}9=1*de'su:=\y YUNw;!DO,8ęt*+p<"k]@KzVGXYGGTtV6h8x..Gnt[%|rSiX Vf]#4[e(fI"H ?JGyUym9U]VY%Ox0WyˎAWbo@д =Q`C)p5S3Z\rMRq^lQW.%NӮlykʕfm(g{ b8 Rxl%Ո%1,58b bb&F6&yn %߀Jc\M=p$O&LCkLPW&SƱNf"P%^ #vD8#@ȷ$EP~Tg/uRB18XGP*YYe]6ˋe `cJǗcifs3!χcI `>Y 9*JM5!:㊪1ԝxCptFB $ˌ@x9`] 4h4ѯ#u68S5d1'J J&=AtF8eq?nzH2$F.P)xT:^o rdJDe,#p\꜈  m\|CY{Q+$=af#As0 .KD5x5fϱKBuBE/(%j9>5`zXo>%H [jUz$#!^R&HM)fʅ Q}DT%:Gqڅz>"[WY.F̌eh*ux/_"YlBtӎ>ɨYaI"P?=4 ѩRp؀&#̲B6 X4A[Fpd+*LA(v+z8rgnЭ\?^od8Slߒ0@X!,L|@R~OHh!fOP*[*!fGD1BWZD@9VEU7:pڈ!\L!q5\<둄F5<`aw}4r/;A<%#Vff"+*H7sQ#L89bf$/;@$$b$s\ y}|H`%kyXb#oV%8Db5 UTFۻ!h&FՁ}O 8SNR{qe6 T ևj,VrqmZ~.^gy8]J%ZN V%hr#K|<7GPhJ!>NcLMfxJ8qW@F5`pBMTkn`bDvZR%=wXI AۘnJQl: m+\3HtI{qMز.1:nBS"HT'/#~|L%PjK"H!:DF'P )gn"G1pbBPyb2p (Q@'LXJ^ 7 ?EGJA" 'Y@>l&E2 P"Ppmt!CJBl W\Ѐ\K-SxR?I14/[+vTy/SnUJPo,/@xBW6w$ܻ6P֟p)B'%EċK@ pEA-}=vK48>/}r.f X +$޼hxT/*TA@KH*a HF d!B)&E%Q}< sFXa&0 RHrF'+}d3O&1c} Sl&I/ֳ<=,?i,a*TZVJ ifJm)*:F͉ԎpMDRth"0 Ad"qV5;Jchl4(#>db a .](F.4!ZX HyYF"wUPLj,RG؈ \bDyV̓d$XΉVj)n۸X$i3׃; b=ػǮp_Īx~J`7}vdn>b2No8ɈYjN.%z`>` ڍ_.3%^ HrF!d>ZsY8l'Hzd>n% >pRA㈯ <]tXcEfuN C@\ H>D*o:EX[[KMtfN_?;rexU}%uVDur; eh30U fFǤ:zU:,$"}Wxf.ZHL25[{P7zC'+dy; 0q\40 aፕLe$ P'il6xr" _D߇5fiI!j`ĥ2g隍v?abx X[kB;¾?Wzeꤸ.Oۍ#"|}QD1A\f698~?@t&Cʯkݻ =e!$o5tͦpft0%- \q>H g6@lP * sУN]OwJv8a+RMjU!qV ޺#%$37kUN.i!&18I'^o菣qyFnrlzc^2KwyJT-𒣜H`%Y#aӑ`p^~87HmuTKTADLDM3񗂅6E*+ f#<}ʑIGR@XZHu ؾta%Mr'MhaL1b;' W:&:LZc f Ux2Wh ۠!>*$m6CXt2{GJ`~R,"{kڪ,.)mweT266 pFnJp/\ ~QH }DqX€X $!jqe/cK$>dBD57?ؔiB5 $P7q< L p@V݋~Jv/0a؁#~ NjWL_( }F/\8$K̰ T$DŽcGc^-tX=|f0qǷVba$wz{77DF}QLZZg]X`)->X,]ÉWC$V8<=u+4n&~XQ=Ky 6[&Ԙl[#Cڥέ 2tbJ"QD"$; ?#|恏Z G$tl:zhRb,-=[)bqbvޥ+qn*?'?97s{TK-rvhY=jORxChq;~r ϲ[ݐjBQCv&.Mt^ c9ABi[H/lhlX$~bޘX<3%Ǡ05D-ȴGy@ gKIsAwB@6~/a?qк}&>ӱ8N GrxR4o 'q!Kf:-t}eؑ@+?票0( Q,ֻ#"+_ <#I"4$9lJ9%F"Ԫ 7 'If (8bt7hlPK.wbm Z+eаj Z8ʴ EhmÚ /'^)u;C3V6DnJ&G]ABmg/ҲA_<+$q@/^hʬ>b0o,F_Aom8I͡ /5y@i)x~_:-Sŗӻtx ؊ ڪn% y>9&t7'moԟ|8{fv"yeċ9cAmKdݬ AhڏOvg,qOGL&VO <7+SQBNO1K O35u#pBKfeD11\C/ʲtUL£?#DlMqhW@@OHZDf.\CrzjMJvdU!ئD&,SMus!4]'6niH&)(S[IAemen\;fD&>0wZ4NAOA:PRHM@&D. -_]ddneK.RWv $[G{+x2adven7vۚ1:&MO|:R7c~e}߷^ed1КV+ꎧDS"NgJaMiJsu^Υ|I-ג,hT 9r1/8Ymh9˓VZi+J|0pTgX}AH[dˑ;D` tՔ@ V:Mi#`ʿahH@տlHA 6X> 3 xJQ <&y@p#+< 2` #z0 <(D(;lcFN4$9 j3dJ`RSPHAr x3A 0Z:ٿ%7,R-/4g6Ƿdz߯#O'j9#ߛ^ l_G3RiV~jy>:˽0KPn[?;27wjG?,B|Ӑkӭwm%X널@CE) U@XF@a@6o4Zd%d"H9?썜<Y}մ0gS'^ES{Z~ D'ٞTtCDB̖#q'debyOAܿﻃo4%ߓ B"=4 z 4[@[ !m?%xEBR ZWM/?U@h ݞ(@)@h$0FJ"$TP P`nSImz@0P @'#_Ȼwm8%q^om!n䇿۳gb<i7&=wɷ/o_U_MN$j% ӭj7y/_1d߼VYYp{1Ơ,y"$Nއ0$޺0!ÓA\X6&3!/ x$0 J(7 TLEɀXv'cIׯLqzm}C"IZ 8Y_J76uzw>[d T />zgi$럎IfѰQZ?|{ڋzwlPcC볽#8wEʹlY5K^ vDҋ/%⯮3ً8hKŇ-v+z>ݔJ ɈCG1;Z㿜VDId%S =|D|O>aO,# N`ZOp//~В1pR Iր~ p l7z@B@`DX@U[BA> e |WkmC Y܆_yM~_4jj7^A踩{֭Jゆz7h??^@7oU(T~Ob>K1hHAqXWg/#}p[$HϿ&tAtH$6ow%ǿLATt)4%"X,pkzb8y?Q$gE1v;)QC"yM~:kWW"n(~b׭E!y(ꐙNx3w]+~CO%뭮 t|Y+/Z[o[|m=>sU藵wbn/Xv_sP|1u;c8Z6=֓ѻ/_4.7?φ vk'+=$e$( * ?xz@=clΛE7:mVao6p$L:6O7xA6qD"DBe^ Oy'<xV " mBB|¿[!LnD/baZ@ oڏ x~,ʿP9~A0Il _b8:5}I Fgni'Cmq*lG]1RAV]C }#XgxZs:i7N˳\qa98V?ޣtݠKиw홄nYGV-zVnMH~̟O{˟ΨN"HNWAB`f-A(6`2Ȳ.@eExВ?Lˇd>z`)~LND(˽diW1(0iD YzWKN}n~')|?6L [ٳKJ?5Ƹ6z GUsWtԝ_~I$FEe &{杛pވOo:h>C@lvOKS1h }إ@l\7|Rz\=藯+7zpH7#611o]Y|et/Uch|ʏjgۘ3Y|fF :L ?9$Ĭh rp2\?k?,e x/,%;_q|L*oʑ`ZF!l|N&I<x!1B:|JqRITr%<#@p aBd>9"GHnF1*?d)Lș@=l|qYP#LgveE(khxc8~~\ۗތ_)8F ]:moi^1aWAfCZN2n붺+8Z+8jy{x@YYZ@/e`*"`;:7^rx"eP++ {n~47År 'FݿMٝϏF<]A?ܱ봚=ďSH캗G-E?^?/Ě,鄂owfѣ):B5Ş!ii5NfuU2ytz3i1f6ByTͭӾuƟ2G?,o}қH^.⛶[e65["-'/c QG D؆{{m\!}ou;mOӡ%7쒹n?'/kƀ r =lsqZޚw#gFqd &p V]bN=`HP= tI!@QK*B@I<  B "XD;QD$+C\x=1IpxVrsՄ(s4I"1f;, oroZ}[}P:M9ib2`:M@8 O1kK6ӥn{RIGoȞ_K)]SyfqkzL\R7;~Q=SC_{vH@^I8 g;vc[ H&)=hVHZc3.ȱ g;8uqO%k{oշJNm/%{pרUu6do%szd*BNfNxv -?9},w$coEi:Yxq5sayx~_|U_KQoq){GV),`Us48MC$`DK^o< _C<:?2$nmʲaa7"v 0`Ӈ&nG<͏n{⋋|` G+D 0IRhL4)t|r-:OP`Lp ÈDE]r;?EdS 9"X{݈8ZH8"?#L(yƓSº\4sv3]DQC8~Xh",*7ΑblaY<"@LSf *Lh6 |(6hF2ݞ0p#"_1Ѕ+CfHg/;‰x[A<@i͂;s^y~^e zѭ. !; Ј'$+ ,cY񶍉tc&9N-W:Ԝtb:atfZzNL[9rp=|QkjGcO[^Ѯ!;p=?`ݧv~Rx/RkZH1v] ޑWX4疝7;f~Gb;me'NF~u2!RFU gD'g H8!aYjt = 0tm{rZ3Y},Vlh85fض_!ؽ ~HԺf䎮#3xo$ ^?d됯 ]d@ۆ+Ӷ)bU{bȧ6/ؠ[z4<u/i=L?z&WƼ{PÒ fEA N`hOɾ/6M;y[?[JҦU!q;@EƐe7th@bswpXx[;y9w}ῒUIDdtK09H$!li[mSG0sYY2HHxtƒpJ'kIB;"3rPě8r $-Adxh^ ̈R'n1dELv^ƕ@DT3pύa,Ifk#4?#AlXf;`[p_BH4Tgl4wPytB fbil\8`1h,R%(bufU+\f<H8rj/8\7"B&mu !Yd Cd|$NdGYC.e5 fx$\F o&Qj<)+&ziە<3h]v=]!C<źI%R2j;m'?騒X[DFd7GӐE@EN9) UߞSאyKAi QO`;sAƭ v~X73dE4d-A[]*™L"ȨMBq;f] @.P3GdP֍h1Wϐ}XQpXg몗޷٥1]^y=2z3 jN*@/0wfVɜ(s! ns(d:252T[N={K^K+@~N7Zt`MY9+i[I:xk/q~֝]r7.Wo\A)\bV&D4`~6hoy衟\n h% >rQ V-bN0͡13T0n|>+g;k&Zd4bG(ifCU Y;= &-PEX@BKnԃ(F.xFOVTj?&+hwDHl5-ʫ,'=I"<! 9fLپΜ4WWU r%L r쫯H)HKr;c﷽u~%]936ҳT.H+ݤƧ}ԅFi_i1 lBh"tcҫ'm ZCYG8s0BNE簉֌TE)mhJ6nG9HVSW\E\0 vԮR|N.:? fVGv4@Q*$kЪ@"51) jj>XKi**(=f4ON5IHL =ZeW;ˋ-/!ciOR]+g(~ g IqoVoI%s&YG WPR\.,b! ) 77i! LT(UD)EF`)&~"o6-XRzfh=b}w!Myݙ,P$ݝ@dؘؔ$bN,# 8>2CĖG5ae (,ba`Q ~8|QqzA':;똰G^)tƒ"bQD?"1Dʏb8Pw͉lP#}̻~Ug#~NnB\xb+)A"t?+È$L,ր 2l3R2p>S^ʠKA{eQ1B*^AE),I_H dd.fт7"rt\MRYDA3YJNLƃ_@l㎶]-{ؿy/S~RwX"O`p o='O zAn<w(. Hnjfw<3 }#@:2 zo΀w(rZol:EOoTmm"i߇:Mc\eu9lk?nϳT 7 s '1=ICɳ~Ԯ?Cl]Cquݟ`Wy anF6n!Je&EhJ#|iZοWy(J%7́ ],vl=:< 9 Y7ng#>3Xw'W!"@lZxĘHe"tAٕ,^X9n-?xv׎ >,\Ϳ:7%$MEqqEbR^QV^)0Y1Q~1Sڻa*y Q~4#$!F5QFTSF*`r `K0b  &8: ,͡oa?LlVnڐyy$6wF f &v d[ WJb8hv6ƒjg}gBܗKIGi9bjb{YG!^ #lf4Xb6؞ϰG&vK`O!@^$ }#^V؜BLJmbܓIg~ڟć( P 0Qp U81e,QH2ɝiz D(93dhG* ?Im3 >YlVXZ1 v"`GMAD.[$֬!/8 ?qk~(KƸSIZ `[?C[|.]$n4z)obz0.QAT8w^}okƊ>b); NWOzۓcc|nβۢY#2ۂvD:By3lټ k"D<_D6&1M!X/2;^-5uEQlrNH>8V01+{ e0 c *=¸2߀[([ [;3o3ZPZ^c (4e 갬1.jGi1WX+,_mmiO-ش+z.#qAOUpޓw\DZ37vkMÞ .` B- jҷ* L gU>@P.)ONwJڒ 4@Ԭ~ȋ1f2dl`CԌ1mŏLD2_bkNEL chaIκX^zCFs^EtrĄPonG5#"heA)_ IiKCh"d~NçDn>ǸQ3P06 mKjK !ām|O~y[M,R{1wӌzcBiA~7<@N$|rs{O_oMd;D]?8Rz':JI6{'q#X;xi yh1CNzۓc?sXgA9%*~uܝ9M.c_28:J 7gmqKm{/C(`d Š:n–'fP]]- MV0;H2xxnY8sWF֨RBq!"nUCɉfٔfXàiULUQF 6W -s7EEWjh/Rw%9W-8PHu6鄑 iW[[6^,[7P@:n>E A'PڜhRj)4TLGX1 "|+qy/FD(ǩdP#쌜".'8L'&ilr41B+3/BXj C%ILS H0k:q%Cퟥ('hϾ' |b $+;nD0,A1hNaM[mƖ!-Ȍ<$xA퓔dld jA"Skoa574grJwҿw< x6qc4!SJj z$nĪ"@<dԫEaWͯrB^V&Yh"G`=WVp[vLtEJ- la1+DX"cpN9ZnVVWMMj+Qw^Lىo"XbB c g\=٩sL%(~wn%l[?lg!nKی4IBBਯk>@K"H( 7~V 0`' H'Ha)."Gl!Viɜ.f/>\ c+ b?qC"?g{aC8ZxBFz IF4;Cb1D%iŒ8S< V m bK"Yx#o{k7&*_8 i8*@J !T6$ XÉ jL/^ n/iVE(~D@m?v7mNӤMāL DWsO%w1Mae^ [L~7=?|7~Y)^z0NjñmZ@u+l^^F.5&5 AHZӓMt E0V4`UҼLTC)! q7k&6ܓ=pt(7/8T ,\]Fx%. `ff3'kƉ4 0|B^r%ćvȋOfrڍ Mco͆u\EYޝ Azpf/Qf gz>,ʋbsݕ3SoO_7PbXԹ8; v뚻=Еfmt湊g?;n<ﮎ8?N_~v3%QN;_ߨPv Og"=/< <;MsB)KA@qXQM; "?.0`Λ p rc.?-l$Yy)q",BZ";xn! O~`wx`5R"XOETlrDsa#h1 a!Hc& /[҃FRH"Sx@ى bv AzCn=@c^B;WX= q; (ĩ2\$ld$Y@N.O^2$qCQ ڥ(I]f,Go%rTUE%fĿ<p6i+ƐvgB,7`,8SI=2CUQl ƳOwÌAs}Qzޯ6~=E^5̴_5OzIJ`UYu0fOGX %9 7k7tDͰʎG&~y9GfݭiGp[4]s8/H>Lxb6BIDhnzH$4#G7%~"[SG1|/FwIP4DA uvҙ>tܘU=pKI,EA1 n̽8lCe_"gTAA0-V'2\ \\d ur^8 Az-r JIsWȔ?tC6MSpacV*$;WdJ¿N5F{Gd~ }vީ?~sW>e9J TKb?^w|Ʈlsa~vܕF55z65*U2j}5VOr8kpoY˯xL,0]urV,ŒȴGҖu,swwVfQf-oS0'M&"ڭlKXXI_^Hu0 G#VwةY+9jvk{WAy…KS>b)!8e@g!k\pDWFn8D%:rM2RE_Kڝ3>[1NAP[Xdm06eMg2Yz[NC Ӑ1՚5$qXSs`QʄIbA]D lΕ 4ϗAL%>_GW!qOU@67hT@pn!E# $0 c -)ݲJ ~U(lq\O>ÛVpw, )H-9LOl3Ի{JBQS݁,bCӐ'>y.nMv? Qr#UIZaĭ$e]!j.0GOZ{!jb9_8HFDPmK+O/<GԲ3&ɾAɤ$jm'RE bVHy?js"dځc\ IK%yĐ'nmuUrݹF(ܵ Y X,!4"v3-_֊ f7X;5%_yH=2y SEb^mqj/ii%ݹ*S/6ەxՔաƸSSZU{{q[b pE0&$!rZŔwGb1BДd4 Kdza`=< ʉv\B9 0"BAmqz2\uPDI7,3[c㶘DH,4dr{JYd`Ì2gi,m$P y6VGw>g H ~UZ?nW((O?EZV2|Ѥѫ&[[zkcl2b~O)qUزԇM>t_xƱj`ͽ7T/2z4ӔEؠ E:̂&At_KX9 [۽PQ΁vs²eFkap>hYY<~#| רDHjY2d!%j |.($]>S /,IO F-e&!ωq4Dͅb46%߮i7cI([Tm!om@z)!u6ѯN5g׊yVq馜JX}icst(PEvh̄`'9j?"5b 쬦)e#{h"(>[\_nA?n WW =V 6N9xf_ʍ_֨%U.8EO&aJ<9;t=$B#R|*!yMܶ>شdx).#_*I|툱KD}/rІͨ1}9֞ DI xQȔ_jiÉ&۬n_uyُcEf-"on hLu _g<꿒IUVR)dRuMkYvkie9N]%Id]?'V E21H\*Rd&H㧐`t`58n1Na 8ZCH<4Lt#{XLu)`w+ص$A ܶy Qkilur{o :,5Q>9( ]ۆwx EDkJN(F'ZD˅2v76 b"68ҔzddqNug7xI!}E/*~6LIVwgi"LZ{B9t>igYvδ8W1$c {AdAQ8FV%X|dݜ–6v8adK  f~bҜO{l.Đf54'Y T&QǶA"聕lΗAx!O\f2upӍX}FmJ"G .#=x1Y{x[щlQX1)_uxdE7~%^`UI4MU$,D8ƻpQ;M>!1B$.cm0.[6tʄM8Xaճ16mfREȔlظM2%&f[Q*̖!yqM6&?[i ($ ᜴AM?\a_;8MS.a? tsےQ=ke~T"YҐԐhd$"qyAln(Ǥ~=uGOQ)40IK,9*8uV)0ֿ%M!DkwFH ~yFxefd՚է"M9(UT`xHd$w03mH46U_fr#A X$k_5 Vcl>oI'YeHмT[ʠJlEiL٫5_Frvb&.&jLV'3?sA,MCTS˥ΌmloG%!NLKC# fJ\M gwR67Xt^d|כ "}UyT vh*̔̋_y!/R$~ 51 䂱:*$53Y~^[=| zh˱L0Xr2$p 8$}I_wJS>oN ycY޿"LjP,E%M/Z: Jh _|W֪ߚ'Sp&Ig~_$iF1^Vo#&E G=DY°Lt0 4 ]TϘeG!> n N! DNxV2CB'h0w`LTsrϡ-[T@PHn, j@⍔9M7wG|iǯ aK.D&8= 810A>x{TF=bb=f[uwiw#Q~>c^c8Bvp6{:3FjC*zDtDlJUVf6k#$V9O8~<`9~zE$rVK+T,f-_{ Y"sWc BD?LM%mmwNRCz[xNj5I~3NJ~Lj;~s*oz3E F9"k'sUMw(i 0 fxIVBd0!n -ᅺ%LWxd:* )U!dltQx}8q]bNI[: z}Үz5u'aF!N\Fy Qk#/vE6J?|2T{KGxoу ")y?I+w?2 'jj3 M*o5њs r4MOs>SW,歝ԝkS2?Hx+ XA3C8 Pb8M/=t%% q{bޖ-}NJI0}8fa J s%QlRsr=rĺQD4 eU$ş-L\p?91966r"0!:k|kQ]\_ 0$fc zv`xD?WN @M5DK+c$cC(#$/u4ߙeq:ݴ AÚe͸7#fz]6~Zz I3.4;TOI%S%i¦=ͯxMKֶI$?vZWt>/R 1K:hRȂiHe'a3:aj~F0unwVJ olUF4'g8]Aəsz-pSe)!-HbY\ Q%oAy;k&&X?8߈b26b MLAyA|^L DFo4%"ED""66fGl-ZbM7M-FR~ vzIT'6nk86E5"5$8%P§dez9crXf7C^qKgN/ں$H+Ǵ|ۨ;Dp=E* %ꑣԚ)}rd߽w/;TJ黵TGyVr f*ہS7 .lABY64hZ%?̚}dkY3`gGL]*S*y1`?awk\2 Ff /P!lPWuD8lFmlv q)'X01&!ͧidJ爄O^ՉΟcMs2$m6/̱*ԃLw(aI*/ lڏ.E/o]2-Ek{I?&J7%'tz`DIm4]#ӕ(y k<:W U\zޥ=e:t5;?ksZ fK,OSԤ o0"ϭΩum~ϟ[H>]eC-d$FB|Z(-?:,L63e$QD(o\~ ɪ-:E (p/p6IJ7R X]B˶B"_JaBOsxU#j̸i,!VQPA yEQ$as*ׁix!/ڤ9O`fq4/ޘqN_V+r1A X0\Q,(BxqD1}%0a@V~AR  >} Carv_ | UpDo8H_IzҼP4K-i5+mslR,9"U]oMK|34R͌ZJ`maSV*U bzEG|p~|dКC(8Qc ̢U% U?;1„IsKMfnY5ӏЙ!yj)Q`jjUb"t# )OJ ]bLփ/і0E:<(ќ!<&3?>{N1jh/Y YlEݝ s6,' jj~Czt%|{/Q(]5U\8LRiRJFf&T6&{C[mbV&J#5U^L־lt2ПjiDB:]:7?2KCX۞@p s7MM__JOaO:) ) {X]=R܏+1WHͬm|s=OѢR59WJ}RAA}mpv|m]ve-{6Yy=drFY cf죣iD<,gAOൽOW[}T{Nj70CfctDLЏG7{:sn4n~G7^\Lw#/q}ݬHP66\2MwAh2eG<_r/ gq4| w&UOq | ߱sg\r-E)b x-)uѨ2T rGG#i>-%x<B] ?~ᆥ%c[Aoヱim̅jzLgalHNP|X`EٹHtX~Z:O=i:?r>5ߝ_ IF2{n(//9ՔN#IC ZEZI23/gX8ݡf%>YC%:@-=hb| |'gͱFQ83RTU-7dtgN0cPwVeEG#l\jB>9\> UR_<#Q>7))ӟ)3IIF|Vd $eLdHª3>U_ŗ::WI멠|IxLs(C);"Xnq)L6./z)$녉_B'zi,rsea!BQ~,&ӝ }&^2ktn`3CKҲXn53v.ķxGˆ&0,avLU[a l‹^ 4nD1%|3@ KtFO&qsoV6ݚ:(h^RT=g+~ 6)9NtX(J{wUޔU Z-txwb:@_ntݫ_kńh֚p=}f)ZLo3 9t9C 7` R)&'fr2 )prW?3DI)/Yxu"k`C8[ɏ$X@`z+s3H7Bod!((^+T{|YJJk&胾o*-L9_`5<2 ?.U҇ͫ[FӒc 6)`(!R'D5*M)DPdO, a #(eWq]^'I*n]/t!q`BL(lE4[rr=mOg*3Od 1g6R{J-U MbP (k3f͒9%--kEO0.kyafH}Q6cH4 { >װѲpp)/"/# rFRKخe|Hkw߀PCD'g -J$: \Bd@$th-JyUֳW{: ⡀"tz7}*є͔fV]eqEi_S{4&Hb,sdhH)5QKatX,J eRR&zi.i.)-mMOА_Ƥܽn%!?B^ͦﭩdjogI@YH&8ƊQ+Dom~GDֆ$VF~ߤޘhBD!V|"YI(&o|iIO[ʹ$42%z g?-#[s5xc> _Q%Ze;&V\UWTŒ$ J"%?Z;dM7rZ!Igþͣ7|B4Q))3NX nGZS7[ ULm`K4.˂ޅ' -)|gLv7f“?ob`"PDBX/ ziHԌ& {)$\qb ͱ NJ/5ݛ}1D/UZaB?mJ~C;+$kB1Z~ eRKsvlaױۡQ{d n.9H|P(\zogXf{ң<;Tf)-Fq+ғ,Nҋq:&0o_k 7 zK_hXL62Mo g!.mBCL'b qAKBd*֔U9hVg(AH?vGHtˣ//M2MW99G)8RS7 d^[׻%y$[gL{p RB{*$F ,J#Y3L[$7!ӧ-K֒IKM쒴-!̧Of6[A_϶k qЦ,spY]1`U% ESKe:$5[IfFQq%#Iŭ~p6M̧$_*͙ GFETR!U5m?`sD F>I"j 4VU%ZffEI\{a$SYi- kcc$FE\C4O%M{-Y$ټ7t6>0$#_7#>p%:6[+]ȭ$\miV̊x h]^bb" 4EDQD ɱwEr,1_C_ t)?"TLZKR$*`Qd?+Გ"=i81%RD逡5R)@-5>p+:jG126 rXS!)P\f-x $bLoRނHiqv€ g_M[!op y1aZoNK?Ha5"d:Hz8 MBAK[vMK\ hњcX&}jꣿş找YNhKtvJBϬ?CF;g΋1EIKV޳oȐ4nwǿJ^8W`c$T"큛c豯+!`\_O'R-E:RߐmOO bGFWhL=Lk#&Ic / J'z[crpmAD`|/xDj| tyf<.M< /} \AB>XךU5J=ݴo^8{~OF&I ٫q7hWfk peQh/M<~9$ /=}'^a0ȫ0|7}R/tҠ)3ҝ^tƇ䦣2/ '2g*[Ѕ$;KU3,)?]zCGbzw|OHi/tb1328*~$Y#Xz1' n}ݫW}=fu".,S(%cm|H4z)#^LA Zar\c{"T@8ɞÝ3JRhDiH=zEuƇd/y+3=L)eG'~eCp3qZʾֳBOx}}xCr_C*?V?FU~.7`Y̴ G( VjݷvFlHj$-]jO==1;{uC#&1R(3(|GַruLeo+#3̲THșFtUt@]_|p$eyV^]5LA Xωxb$ o|Hve0*Lk`y}&6>$4?^9Q<3ɣ`Y ;#~6!yC120f*"Ľ%Yqw!ٿ#܌U*1]oCvb[[.Ƭ- ~i}쟪ɞ_iRfTB!ӄgHN;oy*A9>剣>9y$nFI詬N[w!bdLcMYO4X-Ƈ0X~j:~Ye@_ͶNN6S~$aȫnqHfu8p˙艑n}/".3"caBiRFw891գ5'Wk 3Bіɚ"4ěvj5r+cۡg:$E\B/b8qcg7l3Ŗ8㰾n=qvO`Yzߝ<?| "]?2Ls͍YWlxnZ~׉QR*.*V,*q@R/j#oz.9= :O(-SJb0%ETX$Fݪp$lXjiBI=e>]r u}r]&W=5|RqY.8r%nLO,(Acy(؆.v&MjYm27v%BJ q8+pHۀ5za0IBNb?OLfqV1P D,Fg`}C}_?cbOx+Q{vK_XG3SIXUg`]tt9ƅ(NQaQ*-! hRyWU[Ӛt 㜦`c.č'Ӡh<'SGroԶFMߚ,g)HJIcIJ3b=<3YJ |R_fJVWX Rj,w14&:g|" r֯_mhKK.<;{dӽdZv%itR {JJl;iE^s55BF^ -KqH9M s+/y+~yTW ?jrY\1@gXʨ?7XzqLPs.Nh2IAGJ{3kESKΓZzBupRֆ='JVƼZIQ(8'xJ2yv敱fw]/_oy:-1=YlxI Pα px@Z/ )'SyeIT>nI8]!i]En){ͪ@}j[nBNyꕜ9T'evKW0RqKb1ib#"hT787yDWH[NSM/ \d%lLF%Ĵ| I*s\ۭsk'v_22KO|3;!JeD{QE|pm^I{ͺ//hfn$ņ%;{Kщ m TwwWy췜Kj«+yEDC[yPJ0\f*t+:9 )u<7+%J dղJq) durfbm3ɮDUWTnkqEsBz)8DFvIFXaBjp5?x~iAsҐNTY.[i;N#)(v*6YM\n"͌{":eseϒzqLrg+wxw71CVW֏'z-|QHHx[ESudMڡ؀"W(܀*6CHvf vB,4af13%aHB/(bX"G녭 hJfgS&&}FTfxϷ (WScQÄ 0V|#zJ*Cq}@2ݭ D~/bCs}1fysn")!FhP )qY@?@  mb+ׄ[rg>fk{k. Dۘ} `hPd2@qйrgE0q0_(byW-53`BbdY}s&F1Xr3K."닂4 4"L aP& 4uGxI&+[g !."?(5(Թ@H8D3qDX$ba2߀YOZ?6=ᮣ[O4OŚl<4h7d_{a< !aad"&G~5i$~N H7ec!Q"Mv|}.BK1!%9h'@,J:Bl7,N/kxȻ,+9{.r!F`~9`gad>E,GS"v" 0$H˹/z^R ʊf!3@h 7Z{ #Lw7H "T$gT)/m_꜑iHOX/gMU}۞-8nNH8S-o@CL" ˟X<21c1`D#q[ǍӞrwW ןgu"G +Ȑ2PU)9#7@../ r!茈J*0a}*)=1 b% ԗ*◙9zbC׭z *<ү?f0 OCԎc֎vlr7^. ,=C'&;Mԑeb;y!w" qsO_21$z*6 zDZT`B-i]5OknUw)ږeKE@X `Na;qv" `y%OKɒP3a 貘-Ӹ%[|~5Ϭ8֢~NM(Y&K^T};Sc"M+DZY| jC8ݎ6+?mPYmy'95㼻 !XG&2$X*?RF t[L5$Ȑ e{Q9N92>cHk@J>xfD6`(2*ęM%z´噃>4pԘ75z]pna7?8*pI5_YO@*iE&a1|~ڵP"FcXϺ qq_-0?)YEM׺ mko1mrw ;7_qɛsD]:?AaBTkQ#Ps[hC>+9jGeJeMv=(_rsZ `[zqе"0gl{]{㰳RmZ Nz1D+#@RH} 5UB߬E%6.: i3D*сec&"$~eK/3eJq5+A\3D!p3YbgepFM gl78,%ю:h 3^k"Za̩* ]2{aJ]9dɺ?ib 6Kt|e;ʟ$ F=j?.vF;Abpa'}>9|w7u]Hҕpu]vR$qsA͐." 9qLf#j ڶ 34+WR`}r# ) 3@r< CwMM;uWlVx0z? jԞǫ Ҫ`)H2u%[Ғ|O?&):rT )dHTGf nüУLx z\ :Vv dw]ݜ{ =wԦ&*ؠqeh_~$Fذ"pIIukNazL!CR#>`6WFX6ao%3`gaG(pKv,.`7TE#x1^#WzmvicYR(\oʾfxT6˔+(C)wVݮwpCK(ґd+5ɏn`X6si.>@nדJӊ'lNmwhw[7h_^ óWb)Æ@/oΛ jpC j)[K]Dž#k^pS{ާ ^sθOR0H?A789=}f/NڹzΞZ@* Ĥ["8N B'´ƊpWѹR ;Mu^&3zqšY.^^vv,2CNU Y\43ˊD9)2"Tfʛw,{9yk+]\7y9s^ƂgaTNKv%,W^UvlE4(`Ȋ$a /ZÓg+J'&o7}z 9@07GvFA8nSXǕϬi+|w֞\k>c=iLB gþY=:; (W}cffM@"mms~&ҒZ$uKS0UCze"5@R-_ =`'U ?ؒzG=:0[/ X2%$zieie*[{r`x+ܴ?>i"xðtE~!6b  `Pۼu!fR+9c?!R N H1UGT5Dx.HRp^3AGG︆z խfxmE~maf&I5O!W{Ƭ5BEAD)GPzfX9`WHWKkc׵[|v3-;\.;"#L81 Jwӳu&%i_4_ (5ҾxZ@b0!OCW)>?֏BLl~VW&`tj_a@LtĥϿcASlzm7-soz+G9lZV&[Ha BRh[?q-T!LsQUU|v#TxԣBCEf9ljY d8XQ\׼I, Nq}@YSVл|> YgeVD" NJ6{ͼKĩA/2Wg#ca|:-XM

NIe̹ؒCp;0y4;ϣ=_d] 'isCίj~ K@\h $Oz2U?A_U.rU~u%V;D0l#w]5 %,dl1.!N4s JglAQtgI9M f/K"ų-u$zwe ㉜hQKw~߹I{TA5"d!53G KCK]pgig˜!Z(ܜ#GyG]XE' o.aOtxAyH Uw8Z(QTi'27%|`] pZKՊăB _*ғ Fʐ%5D׀0Y $?j[ J j Lf4afFeTlogڱ+{'L:\Jh"B|Av7s= &T_?'w8hDC`s^7z@/$*ؑWɋT L]Mը> }"&L"r3z„ϺX+g\cyCӒPH,/4;zmfԐ6;@j*y1yAd&rh&ДcST;B`Ym驣bgc?jd3hSl \:@ cԔL7qPxIJpW 2j^[ ҃4"]VRq1og@T/NG:LJ߫/yz 'UzwI^ΈVNcm%|R8hS*kV F=5 &Jɿkln Z\ޘaLi{Fv7-eyjȍs5[܂d5rnj@O6UM; 1 ) )\Hus;WY~,"d`fe Y:ac -JœEl&&($`po͞ GPȤ9+ '_{9d9G''"г)ʦ`69!˲9lrX$TJbntWju3Ҭ̊ ;6vЫ*R $aJ~PxE ;&9ґ:VEg}vF+VuЌ6S;W[戕ʛMyрI\_L-dMc[L֟2b>yJS%j}}ahY7@cyY$&IpI{ Ͳp۾>~`ɹ [&7FF*.Ir wwsl"{NWYfLJb7!@|UBlK+NC}];'T(v9ŵK%gsW:mM%t~ i9|~'[p3]jș٨ ?.[T˪UIx!3"$*L1MLW=mhf>zyoqg>Nka7 P9XlUT~G.˦WޮWq[L\lpᒅ@g6P D) 0ɱuo}O; O :3J*z2O o}Ƥi|P[@m7 DJŇpScSt(:J0QČLZ5ndFKqLD5bŨfcs | 5&985v\'$VP6.ZۃQٺ( ;-4"2YD'%0_ îjx KM>֡H z˧AZ̝'."@i c1liÖ<(T*|#Ntt$R0Zr V2W;lP'5ؔ&K)Yn YluQYJ D3bfz /!Z oEpi3mtI4 X#Z $KC3C1SD`.lZG AL eLi^~:owQ̓n;JĚ< {}].5q(d=B>/ lrW9~ Lv,`g:޹lD&W 0``c(I=1,qXNB$% !n킝pȼ,8s}xP VK;8b# #AΦf!RV$d-s.^Σ>u*bH>|Eb{)EXٖ`d-Һpֳ׻VovϹbw"3q#Aֆ[zU9zJ9~gEv#mT˝!xH=RP6ךu k\&pȈnH݊M;s`5b*%IrٴQwؐW6 #&WBV0'%%m1IRLUQD8Jbg OlB&B먆szԱ0rD`f5~Pjt:d!t/'V$U8u0jը1v"4ZB)&承͒%dJ5k:4j>_M8䈦!OTYUWz:0a S:ϿIo5P#t5eN4 : [{5/45µ"U Y凧B7L4_/}au%*6Wh@٨:ِOk rL)=/F-3M(׎7qN;irz̧~RbX{;)&1+Z j&+r4 Cv 31`jEU F 1K#y3 IM mտ!d?+sv>:yj7x9dwl}cxH aCHm@Vӽr<$#vڲpS D ֙Zݳ^;=N, @ LDYvb//ߓi2@q.ZgT|0V<$ΔKKC,;m0X %)䐑2H;%(ǘ˺s>09FJhGV?$K)TK.~mTGÕ=$@`yId:*8ڱ[zb{''{?OAj`t:Zje;9L{!#sjݾ}UO(.UMN"!rLՌ=gK聱kGUS$7?T͠6PQxtՌ|3UO3S{*<$~!%@V5E뒌]ۨAB׶T vmՌ IYJZ/t O6>${if0iHAĉo1xrG;񹹙IP-2-=ēo{ƏL‚;m.IH;q00-dLA ^aX>!}QPH3-hzFʉ횃=G ,H&'ZNTc}6n|Hv.FfpAڳh'F`}߮o|HvP]ԳO'g~jC`Y)@?2H맴yCrùO +D$"~d/r˹E͜YWh3TM=D7晬ԂA-{2ES0 z!^'l|HvzlIdjc(9qw‷&c2?W6u{ wO^}fX_m6}r'`R#~B 6iJႸb2~S"X{Xzr{'ʬAz6 j=1e$2ybtV0~UVf.")'\A!~ɪW# Gbg&COeyEEɐzTjPz8fa}$$5=𪇐 \͡]XKG9 6K gI 7ةnns ]@Ԝ 9s1 &%eL.1#1_{Fn˘U~;_yptB.ʖhfn$ņ%;{$$P? \_{lZ X%X% bHdTVa_Ī6fFikzpsջY~yCi"{Abe30d; `J5E\mpn \*9E$J@bRD ʘ(0OnE5Ǹ.70-L*cW$4V.,g`=6Z TُUɯVIrh^5M}_\؆ST.o5VhJcD MT ;V#i3ta٩_on5³K9Kӂ&S&yY* <34ל ORy(3_T_uh ߝHT}/h q2haӪlNʊ,](L$bQVx n}?㣫<*Rr^8(~)Vםqwh|Ϫ؅m@;p?йVe'*{qA$TT)z?>啯9 G!e\sI^0(iRL,Ł}Y o:w6N5wmH9c9oV%tLi$!D55 \eUƭ B.,M9w8+uIزI b"p:?; 9V N/2NsjXiHL[ Ӡ( l2w]U ֌@O8yB&&sFYrhDā a*^fv~#~R{`rRf~=;(@$*8%Iaā a=U0wyZdZ|ol(.l^.llKǮwn΍FEJf>$HbH>|/JUHl=wq ">l۷* r g9lnr[[}HTK>'4aq_Y=m9f.b\,g`㢴021]SLb 30=bZsL=xbexbtod yNT5DfGL<`1M}$3aIPvX"CX6`#{OHj;>ifyRsG&)!e&f[zaI[^pwS~UhH8  ZYerL{hs=lYR{&Wԙo-@)EQy3c4O݀HV2|2|GFzZ Bn#90|;^k#A` G;L`a@U jfhH^]tY%ҽsCX2Ȣ0+>j0PEA R` g 0Qހ}a 9/ VYDRUiz&ن?TC7 ^]\,,"l o+SE2s!–~s!w$V 9~sŃ gogJf͓5>|*{[6E[=i Y^Ad<3^ Ao(}j 3L#u]EnqlDx]=Q(qʱ|j!" 5 55eK5#Ԁo~ÂCY@4@+U# gUPDӌX ,;`ɸGx9Ll&?r r=ٵ<9?!X]xQ)[dn-na_}nlUi}n!HF`:vK/2צqEYOޮf?9g#F_PQR"h vr@R0 \b6L^DD@X '^ehʕH[kQQaPns7Kkh-+;ui"hojn}Epg/8VaΫ#%LIҮ<@_iOݠQյFV򜨫Qkxp)* *fLڈaЁD^Ĉ ACA.A z Fշ& ugzΟ/wq3?=w8bwbɭX0aB~)q- „ ]_ԝ =2&FL@AQY B)Q+͊mKBĠzKBU?~ҺRl*=X+Ajm3>g'&%qlI68֣1XPY7 zHޒ5>jo@-y_*ӟOSpKa[es_j~p'iG-8EL86iW(4-Bwp+Dl#r,`u ,#59u(##N@"W,L;C-qb%+8A>jUԾ S%mJP W/sv7bzVV} 7"q:@ !rTE .\mqC-yQ<( w/>6ՙ:D^"V"/u5aѴBН32^&'X" m /| dj6)D5Q?lǧH}P߇HtícE<UkRXJe!A\ U͍6v).Z)IH S3x$5eNElU5)kwf$-$8'`@'*F 4  ~QGp CLɵ(A]AGj\hop/~@黍fz3HkwENⶕ M$o@g5B`]5)JU9%R rz6 G:THD^$yߢm h{u:<9'^TͲyY;o QMs]uE. w*L >a$r!|M~ Y8C@-?XPӗ(Y7vG}Qj)-dt[)Ōc"n0WLC)+ Zʉ"8"KT9T >#^i5C V0Ѿ"pm)/4i -^mD Np"$A#`c-ʹk̋ p&v]Qx:/5G]8D$ 9rԄ)l6cFeL+1ۗH K¶fYҜ|q柺-9y\}ԋTnW$D@rڅg},wL ugXrG]lɎ|?[y~FIHaQ' /{'HKQ{cA7@laS{¤mhKDXZF'/f< p-OIWN7|푖IE@ 7 CovË'tmbV̅`mty|21w;N&cϻ򏿦S'1EpB4~Y "ɐ Hl=ucaEt]tW=Gd["Td$:SxcW$fw(;Vp֌xuew!JÂI䪽mER;+;= ݌*C lkw^_J88"p2}3Wh_NSi̶34ѱR f652tNjWBWyiv)oөjx%:juG$ 9OLy`~`L`I?`.1'~u8?.UeTz]-#psT QbY@T*8pWCh~jV6%FоtLYe-qzZgeTYQ u颒^Ew>)WTv<)?Xp7i dj%q+"mz,[]<A}l(=A #Е;̊0W&:>pTg|א) ?@!l3; (F,0`P _lu1| #8F3kCD(6r@യA Eݺ a֡]۲N4Z{U}Na>naϓ-/*;s$k<`$d9e5ް'vھXFI(:4[=ܣ)}ԋfms2:LĤ2eo][xXu8 FrGoЎno밁5:ڒ{ [4uQzҹߢ|yOIfSGa8|3'Z@ Wk=E'C#82:.]WkL ũv^o7naq!h,]X~%i3S,g$@e"zJ A a a`j?<|3 glMcq`*!mp*Ie=O9r9LfQO= s2|gۆS65YK-id2䜏@ `f-:ϡaEW _S+M)Ų@R <7}rݛY_US# *Gw(,SͿQ4"EDt x=6L~=/1-SP;Vt֊foCZ'+hnRE ®ًb4U|Iζـ?Rǎ{f#P=Qŀe;E.Bw+m<"{c0\mdW̽)-[EEk<o:1k|wʚLrF:dcP<m4Uis5;ddH9&Yg)Z퓈j5EpȈX&5@Y6Lxϊk:i+֦K>^ IHr$u8i?$dp&8yven7md +o\t `11kbOm|7Uz$Ryan47=. ᙔVw(cgs5p`eя[HK%U"$HaǽoLOƻOڬ|;oXwe7t{?SY51J_L -% ڰ@jE>9,T(0 O#z*뜨ODi3zb|D߮,iMdpaP4mw t̅L*0"Y}{k@ ,_:#M TKeY1Db]]-W_j]Kz?z!ȉ$m,4AJdķ⎯*PQ" #ovɋf<hV& J[xF"Β{|ݗ}`X$44pu\J$=4.5yҏE#~h~ F2@P=[͛|pjLX\( {3G`?~Q@ko[&b>8[;w .iR "H[؃aфyPW bU_q)z0_Ka鲒Ku +*}cE3uvOE N۠ )C98{oG(4>k&||ԬM  $DNⶓwҸqW 5ƌK9 ]^i3Z13+ĎU,4i_N(Lx"V4h^^p[}wtSO_QEk᭳ +2}EMz\DGcJw:_[ 2`_*'/@=ӅXRϬySM{-[3>t֤go19Hd{c{Eo+rA")7ۀʅn>#ծ€(|ŤWn6̧D6j77Ws`F l Ӊ bV:Ru%KT`lɚN0uI:}a|SӅ62 q.u2[oYEɠ!j qc3 Xg(<݊L0;qNDⶏ^TnmC7G 7'ԡ_WDN@[K;&-D*}n@_1Fb Ռ9/%@|ϲ>KRgWEֶfwLM׈GSxNiD 3J ֥}qr65Mw_l! yWOB|MM{"{SXG!K!øPמ`5ɍTy5p2HI `42h6_gYig8h:-nMЛs)*)co\rΌ}-vRQ0)B "\|e/`ʙW`MfR]; E_(l n7|Q5v'=mFfMYs:lXIP+ T8"g'>'$`=0㏸ǣIX:/[:C BRq#%Yb%`Ҟy"|ȗ^o_:p3Yi F(n!#!l-J zi4HXFr&$uQf Z[y[&3_gowyR_D3ƪ)~2"`?@: P p[5 DZMDkm"RhiR7nBd TPM*-ݟ[)QWxd< }¤{L(0 hM8s}#픫kD95l[!lI%.<\F,bb ,arV-/sRpaxWej?'gQبu_M?d)-G/\B5!Yl19@g.qB%0+Vؖa$X*I,vIPrٽZS1$YOb݀$}!7zׯ6x2('W N v뷚sk­n&3Y~wSѝ% R24 S=٣`Yc1m8g" *S7tL7mmS Օr+KC|t]c3sX@tv̔TY ۗ6@Lxf86欷AV`նCBfi6slGxdilo]p6?Y)0b٨:٣4Fu)/.KGLD\_##d5mO,$ohϫ»)b1M>h#|*B5hsǝSD~-n 0+{Iy;Wl'l˗X}qq~@cGCMv|uxͰ֔%KD0@O|tD /3@l` 6%$qCv88q,x"Pm:]kl ]i:<uNw,Z()f0JFh3Nfi@Ee 8O׻^21Z][|t;tu\At ILC0#ɹmfW(I{?^VnIm./RsYh(l mS jvēxQcibf`jѥU "2PƘ1Oj4ێM8"YX>h"4*jg yh@ը;mHLCMA@(&*N]ĕcIg EUM]ɋk7k5,w&ڐ=jTryѺ+!hKԬ"=բڑÍkzp "!+MUGIY(tА'.7ѣd0@88lbF36V-?*IlT;ijMdtxՆ>ZP=Eu)͖Wɏ1F3C4L< =نDis 1.'Cdj1DqK.9Xi:H@S}?6!) JLL 〵蘄[?Yvl2[9cn `d#55,o#~=$bCՒȜɺWn.>idTTZ +K] (XソD > jʑoVMo=LR;]*Opyq? dd2ge,ObX["q#޾u!-KЦ9>I˃MS}*e% ɜ$ RGpةWIN{, rYy :U" 7TqR;5P3VchiqWk}GKR9ͺ,;'gq}ĴŇd'< <YT"3ou/=tθHiWwNy( t0:K*⠞{b4 LBl3^ 8ixxӟrr16lqiB}Ί4cX:B˸`c3|vs6ـ5蘈['ڈ;3^y.OyG0';V|dt*u=쩇K>_@gD46|cŤ1v{ߕӼ'L`!B?V{K_I9i֕W%IZ4(%=*qQU՟Ekڏv25I)Dԡ==^I~? n#oۤ@ԏ-YZK3a@ra(ĝ?\z𐸿 cOH7.x4OUU8A:|pz9ɺYC2bWso<^{Σ~B;6tpb֩{Hȵs M\01<)!٪l̼j⹙)YjB?g~D:_55#q6kS9^HFo;FW8>K⹪$^jbtT 6ebզI[f=8j>U32C]?ɡg\5#6nABX/fTY>]5#3?U3PqRISjRdՌ'g%Ip6%'H^@l}g߄Pjd{{zwM{x棬|gpqgMs^.cBkb1trݭ!7t.{5<#Zn~ ŰTEQ-4 <pᏆd2/5Le3,{#>(>$o}|D,f$B3r7u6>$0;@f'c&k ^'X{Ƈd h?̚1]%1D֚ڂec;Qpk)$rF5#Ig:B&`'XƇc(|t֗!y̤AUd-`؝(w?!yȐ ,?s)̎?H1 !yHaUHa/Ө :}x`Et#3*wd<n|HvzKOD`}y{LgDں#ӭM'?1[;Iic#3If0!S.'ZNM޼yC2q i2qCZ/r8q۾!u5#ɫLBAWOt-}bC2_f9Xz3e gF9S$eb>q&|RfD03HC/@8Q{TnFdQ"fl'o\!Έ3="#SˢvVm|Hv}l50sl,TI!'Fb}3m6&ktz%?t>/ ԋ1>2$I?= )DO } wi3P2 ,<֧u-vkV{dW;(5pf>p*H\Zc4ZRTUb%`@Xk8qD cp-ۑǓWwf󑉑&3h4)뉶ŕ3Ŷ oi3K E[&k ^Oz߻!O/Mv 3 ԞGȱSzɞ[WFtf4B%d#h{p"­o}n0~oUȲA~Gg 4+HS|" daq=򍟨V=09 ǖrqȹX &%1Bb(.VG$V69߼*ۅU1}FQR)X)i*.HfoH8$Ê;_[lǮ&D݇;eB ŐBNWˀDHLWIw^;8Z9-圍&Jq$18'shbAw(Ztܯ*I< օ}%Zn/yI>0+4J 2äy#`0[wxx@z$A@r>V"0}ׯmФ5EDrr=_\jv4.HxO?J}x߶*[NSk/q u<1ٲ*K*T3l}涪Jƅ"0cׯP݅H4r{[ҡ昞h%DF2rSә|yL%kFA]:){\W=AFY0ti\RL=-\",֤_p߬r]]pڊ)!ʹ,J' c; "﹟-"Iv׃BΫv]"!1+8yj&(tx{lq(?i }mNSRYq9XRң.GjU#6ݓ7{l_;^_F/#>$ G$99ތAEad4m7Jr|[7>Q7fuOj w]đ:Px3q!Sj~KKmI;8[;۩>ᇼwx/6DSsZM0a_쩵Qb(xi?yG~iZ,{]w͇-̽2,\ߟ,4ՓJ48DL` 0X;e0a,aPErK49 D`$_-! /oۑu5˹` @gDKA-d:z &xv~@gBAhrJ%WDѠ)HJ@& qE @ / I@ =fTƔ! T2w~,R#əbe ^$ g "^ȈE.TZHIRH?n$tH H Pۑq. X%Tj.Tjr-jj$E!]jHh@%8 )YS֜6TqH$e-?&Lݔd눓6u1!Vu"wbM YϿb?oE^bo; `lRV9&8Cj*JٵB07g>;a;ߋ!ê`>!qqNyRFLݨ5dכ؜.>xOv5舘sR!AZī95nA7kO맗{2\]lUY1orY :nOv o(D:al8e 1>7|H,tH+J­a zF6D)Y8MiaNͲe.aPA(2~3Dv(_4SbE`ҥJMbr[#J%K8nj ,Un[MQ"dl(`:1f0H- u,IY:'л”gA,Xm_=m+orx?9V:9F)5́ 

ʪW>-C k>uC0wKa* p1jUx f֔C#5H8L5^sG';?t)ıwg묜nK04flT }-78FXB8:Uq.F4(T-!B(BV!ΜE3`]_̲s:LA1@$":3 pu˂N"3ԣ`~U(*ͧ> KVt LY<U& ymQ _ȣ ∑EO XVV}[FŁUBcK iԔ?gK::6w 7I:9fzpkM6{z?/^)1t>oxSu+ĉl%|FbSӡ{ٽ\Lc*tz!I{4sK6D+`*+- w%.?GGV7쁌Y jbڳH+oNEc(p~(Paa/Nzluz? q/ `c̯8&F^нkE)s-j$ErXZ$"!D M(P XWΛ63^pR?>ED`# ` x.#`wۜP̅Cll762D** &DKC[T9g_G"|8!"0$D;Dn G'ϿP (Am4NwI;>O4$]5y:$DZe)Ē@=>=c^g_olSzR8ZӱN ÕHE /2S}7;?fm' fXkbqy9 :m1haN; VI\3O'!Q P5:極p,L!Y \'/Ufr 漚C=DDp7]Z̉-6pcja?|-c?5طzRUzal/5E[m#kM%fovݸ˿Ei]4@B[b)RWnb  yZz (6v>tW`uݲu@4p~Aww} 5԰3F=5Iܾ`TAX~ U!:Hy{ۃ^ o:@,=T)]]"ERW\v^ o^#&Oȟ ]1mYCk&"h58瓛.v$DD @osw8e6Dx0.^E#HSGDš&0e}F"8~~kf;xױdpë,\朂叓XH8ѲəK0CbLX$:TDFiT#>Sߨȼ+22dׯi"'\z4U=K>fИ<.jԉ.^d tb2lS=$3/n U}βÕ E ?S40",ZԉV1W̨3:DLHI35&n*]ƻcǼ髺(o5M(zY+e{Pm``?dr_*2$^gx!NQX&HB1 0Q΍YLxbFW\ {̇Kk۞g}cAwlX arۜ|͞pGϦىWFL0z剷EN%Hu! 61T h"mPE;Cۮ*; .BAԂq!e1 ㌍Wl˞rT $ $xx$[s`Fj&B$!X٥|5uAp演̨uZ(8#y`[0/Xև*ꋌ0[:1.WrA++zTq_%_Xzɸ+˞ 9l]j%3UO ;c$mlY( UӾbk-.>T_VݜTG …em+X9 ŻOS񹚴a֠`뷯8"a<0z$\kƚ\ѧD^׿/ǯ7+cqR=p Bl{iW ΪmoOκut 4J;43 zPՄM~nj+̈́:ݝǸ&PP@f wF7tKb(TIRzxK19 3fe"P )K 5^[4?SUH:B?]z[Ϙ֓YjMh&s?0\ޞ講8 -^xG73$G̓>׿9]zgsM<"u~)<-;fM_[[.|ޚ`1zOBR j/uVm@2Rr 0>$0X"rl# YcmWl'<%Ms:{'_6lACҔ'*ԃp ZmOU^ 7?pwXH/y>W ߭"vA4pUw61E`5,\9/D̲,dQ!^yF6Ѿ$4<+9rzhvTfN0uEa^O0EU`EǴgwZνƻ;#TNօ/A!+*$*LaD먕mb=,[GИh$Lm7p^~CO#qփhҰP1j3H5!DH $a"o-Y]j* g4]y /ǑȈ ʳ7i@'3qHIr2xAS!;$$J4J.ivzXhdIbQd'?N-?97/@V'htO3)NJ@QSkD6pMdzA.AƚZh'q 87dÊ,nL캇 p6m6ǮW`uI5h56lNJ_}IZp̺,9tNx~RhjA\9LMMA8r;rs  ?dNmgۄtd6OjY;$:[ܽl D  3V$x#yuU6S,q`Z$6.I -Πg|`\U&Z #4XƯ~EZ>y÷2\L%K-2`p&|W$gZܗ՛6f(b4$ꜛveӘdm$TQ@bEXh?yk<;͐`;x xrD mZ!^G&"HL$q<&kXvE,s@u+kVʦzGG!’ʒ7qŴ#+8a#$5wQlYA3Q Odݬ([jHf>`3@ p/$u%3&!yhm&u0N9r_KA{R'_~U1:ODJ1S5;we>eybC!dּ½2䲝sp'˦n]w[|]?hvk98l}î~5 0Iz<䆫Zt6c'(+2k) T\S6-SwGܵ^=Rp:O-fw?%:AWa?\4SycWt6o-kN}!:vJ!\!%_e}"k}([$sQ"~9~rSNVXWiں#WȒd #T&̒Ix*ފʹ&=dzsuv&cvUJ+j(jC21TFӀhQ+o7g-&^" Y58&YhDdc` 0 C}5O2YFbIbJ\ ,KfwIcbF &`=Y1Vy@̼<"8P&DB׬ _k1kN}%ة[nKFHHH~m 5J!80bӪp\5y\O};ۗˋFHLǎ颡CeE-?w#Ɋ` N7Emhs,! !Zˆs_'J6.VUgrΑLJ(,UEjG,VB< ^ syF-6F,`ˀ!(lP6F&翡@09N;~N;k)i@"B0}O$&~CPp?Xs7a/6lSnGʵoT\0g=[?ńȪU&5#a$%Ĕp+i'gAl PT0ț~ 7 7r}vnPc-7,ѪHtb ,)`"Yd BA3V`0]p@999u;9ן$'X<+߀as~io8(N 9p:ZBR >JsD'-&39ul+־i/-6sKn3f%"1m&bjLEbBQBު4e6%_}/m/"27*ta!k]{& %i}vO=iy'{ܸ0xN/*$}I$]SEexӬ6 KK}}֌>6 *,dڌg n{ce/n4=N֮`}ک_f92vɮbRGZg̔fY‹'~q=t"ĎsrsH,V{E^Ω45e, V. ,\%2h\bxкdQm1b#Fu:39/ePZ^HL>S!+‘%+,QTQ4 :d!v/SDs@hlL0 Y說QeqE2vcr2u} C rLSwtI\51FQLw6u=kE2FGHkT|}dc:@s/}I J<9Htjb~lF㝠aINy fWF|Ҩ:ySwamĒ?$TzOC^KSƎ#Ɔ$cS`t yx$ jl *5.#tӐF #x{d8ch89Uj528$΃\{_8o֡0:cW#pZc7ña3'tC4jYVn4Dy.^DtUCB04r9[.AYd (sb3Ws:FF04ٝ2T5bSTi%U:l$i ! \9^kf4Jj( ^N!G8F$ٚ:۾F]]wŭ6>$jTa3yx_O53|Lfɰ؟[5|MQ& lmOow$yۂa-{S=K UQ=>JYsg1}0;F H!\5jd;y(Xpt` } 86 M)^rUѤ;7$rnkk,dazNJru !QD,Đ!7y;i86*̘]- QTQ6*T6宱EOxk_;V YB'elJ:j]c6Eܨ;mx4 }q9 1[[9q{367 dI&87-Bka<"g}q#=[Dq0 "s[ã~Ԋq-4žxL:քzL>Ib^پL q*MvI^kjKt1ΖڂƮi?ky!M9[F2< Dp8|Og4ޙ#Uq 'K"9QYF窖`SlARפYOxQ4qpb6ؓr-sZnLec씖@|8`Kd΃ iPu.}b94Tfg:jIb'{/ff2v究\8Bܾ}8N v0&:<: ЖpԾ߉zCr{wKs6Gl8mp!?h tԽgkxgĔDGo!ԗ[U^Lŭ N?茵#yI=96xvQ`Ak}}Nj#_2ɶ3^gʎITX`:pRCqL YڵNJQgL<$Gz K9I2&LM_Sb{+kz`Zfl)ZR_irև$f)do\d$㛱jz Q)2]xZ0 jPjp3=ޓ+zf3hJ?ω[?(wl#e!d69[Q"3=so}h `RgJ̘P;;ߘKI8[ .Hi!-ȏA럟vjVEBZO|aaá5ou;+>6xЪs6t8h6`ng6ʽCk1Lk=R5E2zWz`ŠN;s$t]Ek13)6הbj-Sy f?I$UAhk]S$w3fy_U3)ȃ"q)q?CZ0'L4롌R5#{"!CT=Gfᔓ7TMշA;vŵUl2J}is);!),ܜ'MF4jzB]US$[jgjOA<5=gKvՌzr\jF+Jfԥ,w]S$<|j Yt8t:%O+fe]4}h`<,vGNY+vf"X e[qud bz7"*J I S Ow6>$0;"YLQL=$$~ܽIs&gB-S4m|Hve0 tϊ^8X}G>c]*G3Pe'^ nƊQ-4p=ɉ0or]nayUa&Q0] Z{vwe*!U1{6>$lU!Qa`_nN YƇd0"6̌ Fԏf\0342-d=҉e^ 6>$#oLf{ܗ3f_V*?i_֔u%f33E\S( +{1O)?)u=S!]Q/} a}H4'\:8q=~md:7ceِAUS-'M.mCrkOZU$%,D#EZ{$uUfb [*,Du>ō#Om䗔ްL :ZO~to;/3Ė3)ܕ^l[xNSJK#3IS|>rVׇxC#fUż)Jib>1Yki-Z[Y]. >wʨTo3fVG">SdNȦ>Zl'nL3Ye[%&d%٬iLPJ)SQ_@c?d_fC%Xp.VƇ:*P/&vb-m|Hn83&(\+/Ͱr~Y(f"%Sӡ N6>$7fɏs-zÉ~JsqfuVcc͗7>$Uqq9))p.{Iz6^ 8Cz ȥ\J2ȜfOYڸk?y xԪȤ۠sty=ȡsK%$JqTxUD";4<6(NQnRˇVDɰ #c|jS8YA6vWE[Qrͱ^k|r=DžV4.Pҳ2fSdU-fz+@ѧW Gt}N0z Tqݒx|ՈH#b-粳|;lp`Nw{vFyZY >] @ZX+!QCI'Y]ya" m]ZY˳1Vdy^]奫yW/7EU#?e0hs.) ..J9ab&BwZUab;P󌞑q3fJER@ꓷJ4 5UuKK5CȞ"ly̝+]YmEApVKlrq4?D5SŹwttǡi3=LXrM9%co0L@~Ϫ6̡?3U3ߝ_r7rBD󟟰b/9B i |Ϩ* X9j{be01C7VK%Gg&'&cRcZ1UqbP5E\Gi/Dᘤ4l:6L,/:'O<{wVhĚJ}^[+j{euW}\J"RCN/HVۼ{"R~4OscYlxᩒ:t}R4)qhRat7ޡUq۠`ҊR*xmjXrvo>rG6_BM϶)%i±\VB@BӠ*a}^oխc% y[MUSqCrnT58Q?UEԟPPYS l}iَvƊo!g;lX9P[R'IT9K5rI$Ս̶)6C/Vζ5\Q!FRXr#ŁH#3<ظ|׿UBk8?1))LJJӿXĕ @ \uáikE_Nsܨb\s4sԣ*.mrTKwxׯzj+,rJYAQ.[(1z Y؛/ w?td&a4јG2tǙRMFvؾ9]s  #  bIptPXv!@ )34!@QL Ƣ·fM) =QINN5ed;,53zͦ<$VL"{.%V9dfDEDˋ" d*S,AbffNE/?+`!GnPǮ,< H (Yyο&ch=0Bk^e5t:Zu2ڋ9yQxFaԷ]Cԕ<x5hO?7`Nm!v`"af|HU.M kǸSQxnD2broqQ4 uB-l_AwKFhϪĤP<5Jv*¶iD fob0@hM RNb"PXf(&D(&R\,H/.jTr,,/!D$%HLK 8Wuʱc0 GE]ݰF8 xHBTmJoԈH<5wjrՌ!HF S<3c-Jn' $Fd DGRWW;!8fډMA2FzaVD(/59^d{`zL0-0k v цz(M`_&KgźwXA̰&웶2@?m G ѵ݀Ok-J Fp|`r~wGGSxVn.gDe> %T*erJ`!+" "% ޸1^Oy1KaFYhzЊ[_ǂH_RG1 XQnԅ-ٜ2]fʨgP_&$[K}A#D\A2Eܥe\v`"cQp_ `{뿒87-]BJ_REw&∭-ADt0t' L ~Y4լ1v]68  MeM RɿƬ¥"X!ZW2Re$:VcuvlvBȄOG;Y湄=Ϋ,N !`FQD,R8h .[&c^Xd G%E!Q-0.Ǘ_ɀ+6oykMfۗMBJan *b? #,\._V:їkImzJ5KvcD$*9*%LK5(ɤY\eRr e3Z(p0đ8gAl'zl$E' u*D, B;]DuY) BDD2,DW_µ"4T6KRt ͸~;A>eH="GS`O(Q @QuRթb%5|Xd7k< EYXycck `YaecaP  ,(\CTFYv1pa%G;o'<§uQkWZ(YУ?>_`(;KA;Qu9=x})QD9хpM.RI䀍yc׌%Bݩ}V>!CBFd>1~/8[^?ቖ8D:R&S_1O90) 7hok>JC B z_۽?1㤇<ԡx37CJbҳs& @EmW.,V=s|%kɞ^r)6#$wMّ۪a&& zϹU P27]>:EVW]sdFX[z#rØDg&ƒWړ rA31Xiqʴ 038eL#QfQTC./ "uT>w;~ z!~}bZ|TC9w]]Lҵ3**2j$$"HUf]c Lhp v%!Bm%匭ĒKIBT991"Yr/:}gsb3eyYjZTzLX1+I"03~+8lw9@}ÉҰ@dS E<; {!dU2\1=I٨qh iz A֤ ڡ`+H,6`l!'|lP4",=N|&t;2 نجqRUHG - 5 ]ME%-3Ke sRpo/k6U5Aw,s_ _4 d mpݷ)>> AɚXQ[Uy>c֗E~MŰ`19/?VjoV nD\*l>G͹D3a}Ŷ4O{?Ϲ#6{_0ԆWl_ 1ltf?n}{ڌ*)6fLj̐8'0 LG9xkOp?Sjx0/~"D(86@R䉠Qڈ`GJăW[NQ!O< ZB*4CΠ1?A,ɞ|oYAxNaxN.8n'`GՈ= :a׿WZm`'># W* @ pp䅸^;,0d[5@x"&Ϭ@=l$n!>B5`|pL X`b I3x 9gLX !qh takJ<.w0 8$8u5]9GTm2eZOs 5#;7-D ?@3a$)0!C`oPKskdcHp, /+&ַeS.gqSO8pcx^ 6oJ:l.V=Mmx$#SFZZSY6)Lk\w/QF 61 IPQmphSZZbY͍f^ ֩\mb6HCz]mn"X k*е؂l7'.+;HHl!ljAP /6N&}t0 ؘU4Bl$ !0Cx|W h.Oĕ}&Fzf u"pΒL.E/⁸ɞ^+ *K D)Ug)oK2[UA9֖1EenL(L9e `Eww;/Կ.1nj(g Zt㔴 "hDtx7,0.&Eڌ4Y$07#7WAmR7 o?5-Lp6J Ԭ'c)Ɵ/{.IlUO,~u7xr8 w2`@(ȴU#krڋYR`/mOXF6sz,,ھ] ĮHx~EE?_>wA< Q9ŗ_RĎ |3_uֺcl;A=14,$ɏMfj<1p:k #lӖ`(#HP^g G_=\>mL\jRob.[T شt'^#@]Z;&y#o k?{_{Ե@@TF&I[NG&5'J !=,$a“gYz">Fq%qϘc5I =o JXf}Y.V 5q0b;d,3}7z${_ Lˬ;ܰ1DW++aMD&qDn0hZ0&ux (@WP$:XI4[ Ŗ}0UU˒ .JwA*&[;퉴bJũ(f(42l1&bu"Xh$b=F$Sj@;aOGʝ$`>!5:Bz͔<)A:ppu o3jc'<*dJĔZ f`&c ,LrMyb@/G/Sٯ% Bg/(^+tͭY%/S衯*w#"HFp$8Ȯ+<0 K`Vvf*u7`뭎˼3Dc√+?-}9Aa'E?K4#G%\Íbhhmr#{ OA"Iv:ү|ԁ=6K/_32*='{vNYyI;2&oN:\S71@ W)-o}q9h}6SE;%D9sjQ'r;3_'I]F+Y(pĢ (SFAM}(xPPXi*YE(b3(5#K I8j}DKL~n+Mf]`dK2㾪BM=RHi{exRoWfA#JH<^~0D^LϪ4(JLf/ŽlgW`PN,[S3'v?vz_NCQh8,Bǫ[Znq$H2?ЕfB`FӱMM45L5,dD3%R @rLB1W3S3_a~XAo! =[WսVv׳w'W߶ר+DR8&+R$ 7VJOD%wϼJ?ӫ#YqmkL1o/ƛ8zqsv>AΝ;3U1U[3QeE/DTq˦|5ط|?/%M9зP=ˁo cXV@kW ^Eu'fM[ n0sK,Drvuf룆뽠62MLCY@^ K"W-G Aʤ|j=Skx.R=FJw!I_ƒXhyVQ)$^|zW&f&u4 2\L'i}w?bªx7nYyŪg8 17u&ۖ#ZxWqN^q=-I>үII:&+ajU3לraKIsCұ_>AzPF?vƈII Jzy(2Wvo?ψg!#k2lRy.ɐ&AȰ30?8^]^(SdjX$iq!>coLD+SLR5bR/Nrr&!eHS`vg.ISv(Aq 9Ls1=v@vX aQhsjH^&3̬]S2#%jjbTmC[KT'cŽ/r}v% .{u;S6܅0fsZx^g74y,}~(gf ʎ}$.Ol#rI?\h݇Aiwv3󺍙ix牷і,/,-LtjNW.k{yֵa$We+A _Ѫ$'s.%۽r'lrxnBwvI=T WKGMYL0ߤY2sJ:ʱ O[m;T}MZ]yk~1۲f Lяw(mh4P@BP1,z" 0ryR3:ae!O"d5pݳuDO cqFp8l4W)|Ԕz<C^us[^4ǦAvu0/vq!a,κR 2 %omԑu/1 PdzٍxZ2,:7%c%Wa@|!( lF>Q)(,6 8TgJZJ+NkJYq4+} #1ƙ2襇.\C@B] ~;kr. \5Xܲ|i+,wvf0vfm*R 'jS" 2*I 66x u";[&n&$&D@~?2ID73;0$F<Ml~B^"<-D Gl}.8d.#:AMioAI5$5$bLbzH}F'<1!l0%si/3lks@{ֲsE-xCs0-2I8n *{UH `_,7djBEtyF%<e#L,d T+֨.84 Y댖=Qx, G6f8>qb{-Gz!`U" VKS2wm\NVN/%| -[O,SlS02сOZN`zNwWP)EV(J%$\&yշ=_SnŃ5yI,lr '.;$4A;^ԅ]K,0P/__ޟ^\$) n;L) %>S 0 =LRr O;SYzM!牚@"E3E-JGM9Sք)+zrW1fޱ,ȖH8Q,Jxy  /i$e&U3KI+)2BU(-]GGe/w}i ,A;z&(̦u@˜haYD웺<(Ew_ vn}, sL30NԙKϤHCwxݤC6yʢn¼易3` 0% ^!9DI cMMX"A0@-uXh,T&*KO= z|(-r*)\UmZMSI'pϴ${Bh&ì;|Ȋ:h(0uLrq +n* f]Vwd5AuҒƒPJhJAl0:4R4-Oݍ, ,LyhqU3@6$['_x Tr\J5 C4Յ Ie c %P0-EdżJ>06< XC`//&aq&/NM4~mVƱr{+i@Mə95S'ߩw]8f!tRU7vy7>t! ԝ$D!9DpMQg|$uih xiU$v# <;\ UXg;;?!=QTzԚhH C 5*#{? qG ^Zj5&c8$: 0u71sbxKۧҴĥ>vX ;jMk4MֿIsՂn}30Y;Lr}w} hiawX! L w$?5 NFfȭ*6&>بZeƊ&)FzT 4"2ম;U0FyGh V(M :*>†cyغj̆{\ (Uv:CLC,T6w5QQXDqU04 cQ(v&cȣLÒ&ٝ/Ii^2A؀c%FŤI{|e%a cqqG? ٌGO|N\mєZx!4.\C2t:K֐ 56Q}O?K<`I;&iăe#g2HI2CDDpgjךRrw~w?jCbWK69R)0:j1fj;3 xH&)Y[F2ǒX~)V3ڀ t8v ZHx^[ 0?3|W&1t8zO{k%'76d^}vZ: jNLt: H%=0PvvcrSl0/,  !@['NyػL"Ƥ($Ӱ],Of/޸LD䎻`A~#odW,ag9r|}2ַ8=$| E..9H)dyqP㨛ku 6\(^u:fM-S|L:LSKkP=IR!9W%=7Q|Ns"v5$ܰ{N0@B΃&fÀAtOTUIu'3AV}%9эt'}oxRlb41y#\3qJ .N5OnXgPDG!TCێFǂY=&%.^\q@;KI{_u6KC2l ɜ4 8em gALUpb7Al b~wC2Ε kC4CUS$/թjۨmUS$lǯV5͊1%V 9ԋUS$C1mEj,|DͿH"q QfD]*4s/ݩ(MFsnA|Hμk53H\g6OkͲNf.mV͈wMX5E2zz#1v=TJ ɷLC KđM2,4 {3,zFA^vڍɮW]c3g&6r)/RT{ٸW3?RA4!uiR!9iT<1J׏>!m2\R28"a}ߓl|Hv# 03LL^q}?,?bFV.d* {Vȉ~=Lp3P gAS;MY4kF,mgLZ Sˢq ƇdUG3]s!)Ir x`\IO ߿4KHdk9o O~E&Gͭwa[$Ԙz_g.f5Vfu@g~$#Sfsœ޴9SU s}N ;o*Ń")D I/6㉳>jCk#0,~'wW=Ita$4q+tf($3UZ0 z!dݣ?Be";qV63Arùw<5+I>|C*f&[f*i00:u\_w6>$#3P e#zFa7sBK۠È^8akngPY빐hbNpIJkm!d+*Yg"|xQ/91M$4HU/m_']uQvR+i6LSeCq_LS6r]gL@/|ҳXA@dAr/SA WE݊*Ԍ Gviaۤ4&I4 R2]6B RWNJ[X5;NiJŢ 3VC\flېX=umYEJJ +ܗ'ZHӿjw&]Hj>n4r?"1e쑓iC b4LD'q(x vojE)Es!I4e4\bL)*>rFmzH_嵿}X) QHlە1o$wX}j5cĉŞ}.!1yWfmC8CÓ[5:wu&-OW(s<\zl]in-CJDJ((go%c,iK._#QgޙU!'[} _8S1[eƍݜ DjH8!N%gKwX/g~e܆RY…:r=#Z;)1N8Gfq<˂"?9e/齡T}G@&# 8"c??LMUO-|aխ3~O_%'<?:xWˏ]3vAck|L{\İ4լ g9eL bNJĴ,s1Iz }fYnk1RS$ͮ0ٗ)qg.$ O"͢jjm&wqO\zጤ9IQ8i0RK qabT ,N˙ZUA1YM7:SyfJ^)@䤍]/2{2G>|xqU6`]T#RX.QBLHUD8|{xLXm`5OՅK)P~uStp̫aboF'-Nu&u*g$5lU6)3y)J5yP-puJWm0 5GtqJPr#B/Py9EGG-_5+3 V@}R&ޯg! ^V 0F2o[-a"'c}%:86<_?[W:8ڊ 5 zPt9㸠Jز>1ȑĘ(0_]d#b.^]DR;M?b "l^mhhAn6 0sEG2PʤDvzH8#9?hU¬3*#f&;P2=JL}1@KlM,!Ls뼤a v9F_oluU,E,)FTmY+"(lQ4C舘!eW[|' %F7|Є&gĸѨw"--aL`mop-SG2>͘Ĭ@:袻h>g5m@7h8'T ns"Z T_>(x8z@%AcX0LSX8,l:x:|ѤڳNQUb0CS: ˋ(/l4ys٘.2D)[GJ7b/3]P?8i*B=B4R2d$ ,F4 aJDxEy0ݔ4K2G[C2]p#;?z_BT QS!Gavz:Q⇑B= \cz2LlrGSc]0}LΐR7Zeac v46H=IFmb_r-R^j TRSjL=:Vp2mKy}'gB<F Vcl@ppְDv„""c݀ZU-=?'oȕڗr2g"3jFiAsP0 :/1ti]W͠8cj踛}"jx c[=,p3 uݷ;E d߇eX")L}{JS3W;FoK숋XǞA1|EWĝY'3M[MI$7ٟ.s'b9H#rE` @~h"_&)3Nt'H j鼧}Hy,(YA~〱K(6OD׬%gDb{.UTNT자;P,k@X`RG)3# CfEGzƦG\޴˰ŇVԭj/d2/b'۱P%6"F,Ӈ)& EX@/E I3Zr rtgCLiB"Q_IZ[Q5y ^|iAAu=!:8TGVv1Y6ab\}AEHrg m"<]lV۷XI,ݼgFA 2')0]W}탔-d%vH]^ Gp~s@Qӆ'anhX$D϶]"TxJgNV@햇x;˯w3d8(&#E{3J8"F`x$ IS,K@NEPе㥩kU3s7츚V`Y ]/!zksmpNWvI< 0Tv9 z2J%omk*^/M-Eg^+.Wxp eUq<L_E)oX^[# cXv~[Q{rjFЧ Bz9$l .\*EX{{BiX ?qB2m `[ mm~[rER[/a"Ѹ>득d}6|a策`?yϐ]7Kr(2vY0yfwVRzMG8N3$@{׫޲ݤqrxUkT6m6{g6qc]m^b(aSTi$SJM %Ho2Ǟ,o)l*4iFЮ7CI A dڗw%+IQs ZgϹe.qxײ+=.;_\tBuA=dHM60DʹOXcf]kK fh+sQHVo̶I p-rڅt/hI95A܄dda$4킟G$\wɁ[{]^SK t(_w 1]Һdh#)Ifb](HE7KoE t-=Y>`.#c7M:Ȳw$o>s6EjEC>:늺>w[`,0_Qo6S(nBmÌqr$߬n63߭Gl4*1ŘTqKàmӤ-@ؖgxP \=S{ ^'eϝ7JDl(̻fLW5 zBjO@ s0j?yOZV`w$'=C[^%c<ØLҵbŊ>`nok ½O\}X҄`K ?uBlIpYGS9$7kdo ARVm=H9Z77jLZ%Y[@]+tԓ ",X ^RS˗ a3jv4\~Iii׊򣦽i!9|&^f^!CHȘbJh`F"2Ϧ7?j,t!}~76uBS<BcVf.`|rnoUm'W8zP,KU+}Ѯh>0d-.[-q`Q* 㩔^+B=r"xLߋgYEa<?8sJd׸†Lh3&3diTs{es+cq3;/_y/zFO5p/ E=]چ+VU|߅D< &{e󖡣ma¨XnDͮL9,FbR =ch Gdوy<yYu2'vi~!="bڴö>,#ou}KYG̽ɽ~ O6\GE/ L O8H҆f~-J0UIi.A`n4SdƂ˛O=܈ma9Rtߊcyn$>ymO@[~T Mo\ir 74'4EwQO,8e;IK # l+tah|/Ջ* <dU425;o >d)$ٜh|t3'/'u:CLS jZ" Ke7u~}pƴϱ (C>Fa9=[a RC9$gx0#A^: ѝD!OZ/ć%mBO=N3rV_Sݜ0lTk5 KK5)Ĺ'YCcoL Byx^Odvw0ɫ'k.~MƬ=&Dٱeߖ\椓z^='8 |sfE D[TU%=Ɣf)(P"w ?cJ(\ă5gȉ1TF~>HL1q2;L=r(8nǿb<ثJܢ\ݡ̻]Ex~Xtq~󽜧_Df}.6nMp~Xphof7D,pNVIٝe<+88'd y× W EÇ1u6h(uuWYIxsJ6ǬY W"؂P˖!f; Af_?3sJ򤋐&U `2"M0,0,.Z#7oC ɝ.LV[4+=FvwQ0 ۿ:uLO,Q$\$wcZ8SXa$h\!в,&!&J3Sѡ~ٵ}$hN`w.SRAuHGuC{, ު,$"@ MuYa0٬'#֧~ n6mz&jQjHM T)zl t;l|a:(6y!g~ W{wP 'kZzE9XP.&s4dhȜE,UpY~Gvk]K^}F,z)1{fw%̥d=!a%-5&}gOUC_}ZS񜐗IkчdԌ|.gı Uj+Jj]^z;$Z fl65͡@mQ-UtKǘ2YZ<爖 xbY.UWx 9@ĀtgcȘ+E4Ԇ(V@:O G間oT5UX{|2-htLVC!o"Ib.GH$uq Ql-|Pm: ߡ2w-ט" 6A碧 hY: [}^e_+DiI20ǒ5HI*1@__2tYg7("Eo|Ha{.!0#⥈N<: G~Ov>d>?(m'9[9KDcˡAbU )C5gf$=_5K&9-۽ wD< BC~Zǂ1fCyFaxAVf e6[45k)y'BQ0TrD 0&Sz,L_J[BvDs0d&s?v<&kgWA,^, cuk _~EֽQS38SXتV#i;Nw/,Lt _\/i) 54 R,Ljl]pP5`ZwE0&|i@9 X*!/lCfS3D"\ +f"ƴ=;,)@H kynqҤ̪Uvʝ;*;S>69q=y+>[r)m (TVN vUfFҚ IQ,jlq^E-`g2`e?;9O{UwDb~X t7G]l*jm[\|w߮_)ފV~y;Xf1wW68l 2E%)$k?#vA;CIBn5֛+`yFQ5+GwHO`z'wUS^sFil2҅z篵9Ow12 MY [([FtAOsuBq 8 8$EVWjO2X-Jv(NG-yN+/xLAO^$q8גּ[L乷IY|mnVdJc(c(ܨje/`k l9K:-$gyZqtudgxv8{ ZD bYqL@dAaj96Z+&qҼْuf'W{jq^8DDlKlCC$$>.!ǽr߮5ؾx1q9\kѻ̾`z|yD$ћO]&nW-~qMx|:"#ro#E<j!~ry@E")jϼ$N /C8>MynZ٥9a(cη)SkEf1Ҕ)@R$Ef/xsW]"LTs j8* Z"x%"ɹ@K^s޿Q}dj~n 50bY/7*g;HJkEi K>;~~280jH'' @/&Hퟔ߮_ c?%w;Tuѫl`WԂ1}؃GԲyBUd9%j/s~кʒPh֬ӨY-6"4".luP9oJ1CgP_9{W  >%DVY$tﴶ=iLD+8UuğZŠo՟/+2}/.YyY6 KU*Q7$6p DJ$׸2FcM6\NCYKm~lehSƖ^U5'jDu*(*CW䠸jlT]e]6r! 5k>_GSu3 ^X5$aHCY~w_=+_'Wδ]y4VGAP!!O&IQY`Ő²ZüQeE2G>>gF{UhTO< (XjK! HH41Ȉ dӐ 0o2q6 Ԯ5trdVEGcH;9܏ZICqv4~.ZƳ ZGE՚:2,i&w//YQ g, sM.}}əa 0~ͅS u!qg-HO2gюItthuxgRh}Ph.E󠱌w6`;Kv'6ۖ?UT'd~IgВNB{=;OԆYgf`H>r"CB73qx4$ w$YSS;D$H#0hC㳛IuR>zة{c, .3ڀ"u@k*>h [)w֌Fd -ckh=hAh?ꌓ;2kF$f"3.36>xHFyԖ2<`- Z[k`w"/IEG : ^du=?9o|םiԘqIk#Yb0~6D-ť@<`L4B;[jծy]ͣ9Z5':(@pc) KNg:O3|ęVHF\PZNQa&*NQGJ/}\zovi=Sk;[[be< 9B[C⠃4GyKz1ekکifmd"yT"qi-窦H\%H\X84c:y?_?8U5E:&s"q= c}U32c;#a4m] K7V 'V~ĝ'ҴB6~7EB74nfϸ_LX۩@sg͸gt͟c֮"y}@ k םiԘ {*<~OvŴ'&К/UMfhՌ34lݏTM#7bv #Q(-)z.E^B# L@ zʽta,LqL^; }6>$O=8fDV U9 ަāfL!ic#bKz\;#ͦ30d:!ىJZ7>$z}S%I^wLa@b\}H/>8|1Xl#yIEM2Hv. A3ڝ}O޺!ٵr͈03/L E=-!ux$af+LjJx뇮! fzE4#O,f-IL =MzvjC2#)[x)i'۞c,|1Zv`աJavd*e*R0zI'm}]n$i E#?Ov*q!e:i~ƇKLL^ ʾއdUIY75 Yk&i A/v]mpKgAx3IQ|Ƈd4[A($2YY0B'޸w;Ust)5ȫfGS7Vąd Q[N\PI2 Tz)o7W}vƇ8?o  N zؽ!}+#tıPe7ꩇE7l|HFLK3]"  h'օ ª HY޾|~[9fJ-D !$NL C.@1@vH VmʐUD⚷BL,&Rw~l$>*@Fsl;éZ\Qown Jj'oMsw Bk"Nrb ;WݱJ~JԂ.}*b//EUI9DLm|~c;oAy\pZV:[IɔRrV뭪;3-KT[N.4 }$0TN,e .K1] d5y延A.j4,sBuK976E1uŠxd̞=W{ul<3 .C/-2 6=$!q< ad'l  j~Ք2-B-A2FEdxċ8M][c!#}DқCō`##\KԆ *(j0/('&m6qLޢ0Dg4L[Œr?8@_3H3R튏=gF]M( @ :0bG[cq7=sԉ맬Qг 6Muc\Wd\tZ\\c9GEvR X+QA 4)LMyzXP+Z>Պz3PIv%{1 gj8"5xŦ^-ME\Faؘͮ"`gNqH d+=*tT0Vg&1ZJXdIk_gB~vȳBY&/3Y7$Nx3\箁M_oƩ{f4u" )"ؠxmL:L9SNY>6Z(Y[\ LJ8ce=VF.Aqci#( r D1 X&᳗|mKcr3ϻTx~.[b0au&:Éuz2'))oN8™:*/_ So@FEVgdnp>Q$.du\nEۖRj "PƵW!b(PQ0̾y҂yy#F <]ק{/R'Bb̑@PJɡ܀_VG p}K᧧(̸V-쬰0f D*""˫(ހ"= A#ĩ-1[+SZ&R3ԤXUhȌhf)|]|:JMIQ!4E`݀/ܽ-_÷o5֮-ux%럗gkDJRbUD\0ܳ ۼ\(ʄ0d=v Ȏ Rq$Mf[Ip2 & jdkIey)jAͭo}ڴN!J‘{9 de,y;zvXz" R"eހuK[?hQe rΆZ֣~ob7vs]}2Y:RX'xb&IåIk]ddLlET v^cXQ{}l!19&!uDz+/p9ȥ*6ca1tYύyvD"NafLVRR'(G,G7`0=8v/ 9OnT9vdk,/H{kހI-ST]_3ws.wꄓ#NzO 1͜:((mv@8H+)FG+ժm0Mަ(KtERdZs^ͶcQX;v ^*/S=)gd54r&)Blű6Qތ LEiױҢeYkvfVw|.»Vf!rd*At P0["K#@q;LB j@,򿟰4h&+z[wԒnVR0q*j9ÒS1,`oĈ0/ @yrmͱZçW4 Whʖl8j~|6"lj ĒD Yr)@YS?ew`Q4.8mSml&m,) $%A&aeFLm5ԴJbl#*}@ҿyِFAHY$WBiKVqfQr5R2|L5LXf[x/i/i4Ӧ:8z㙇"ww,Kboidjݽ/kp>6?wh$u Hy+C!4Ãh3Ru 3T-B)U)|89m=zDkf8/ԬVSҮ–}J+]xh.'^3'EwUz-.c:^ƟX9ap*l:U^oBߓk #wX~שD%'.DH,+p^kR֪ ~AO+',D60(NA@DX?-=Bᬈc [[nXzW[ޅ֏fJK8cv_JDMr|}A ˥B{2OuG3MYvD XAF/keH]=Ը6z> ϫ]܃DѪS9W 泬U9dl+'8$aWށP0mR8Ϧ=-R2mBaQ %Qv/v{G^Bcr݂*t/]׸ gµ `Lcfܰ]:uJAv !I!a `.\QQgh<@cB Fbk<]A"/_)v"?e7K宣+>x8HϢ`MobP61pv˯]wfrHo W:˙e_"K1a!yފ񵌞>~!+/g@1Cpx?YëP6ϙwiD6:8eHq"aʜ\K_6HX"se+j:͜ lV~7dD,qMk`E10J Z N{s97Z%p{9U&O* E"m F|]/I]|z.6{0swuY xoSUoʼ෵Q&EvilE1L222=I,(eݎ I}#"k-Tfdò]YtN&Y Qb:SD7/<3Ӯ=fe뉫IBuşw0q>ReIf{#Cܒ:xO =N>JaN̴qU:[j*Z)\55z qq!WD I; R!9眵Q}H#s: Ux_iܧ.~OIlϼ ҽRknt};K.v *="FB", BFZS7'&R˗@. G-xǐDlw3zH6%Nr5pN݉WLCg7M/=@.~Z&DwJ;ّ[O7X.]j:hS֐-.UD 3tb-N]T70i b%SA29VVN"pea[}r,ob F$%HU&*2uP̋āKBBWʀNcNk"V,JR+>Kj0Rn)|lrA@;G0sGŠ!. /,A`^D1R1,ȞYvړdʨG4"4Rv78>`7>d`WW@ ۓS\}Qf)q&osЌkYj߇ ڌcv/s 2N䡂43Ma`Z)$Cf0$э\(M:їh I"9 ׮ /Hz 3GDeW;vįṮa 2_`b#d҇3OW فP:CѼ+?1rҒzH Q:bB^5twg|,?>`e[ppƳ܌l,;^̶W|S'*F!VWJ9_<,rKg5_)Ƃ:6l9Eq #xFiĪ40&bERe^،a yDڏin@)[ rQ]r/8鰮6>q", _}3(zsgA %!|]S=>7x'е:}{#܄PΙ$twLm;і5W\yeפ^mG}MuOzېK4 ^өF \":y`񂔶*G@&wjHxT J_3.'Ѹ0,#5nV&SY`N A9?_-7ϊFN%27ܕ+V"^ % AUy{b5Sb[uwF"bGf9vi̕PcBP'VDC;&dJ5 o-s,SGQ L=G τr{C=re P 2+`bf">a@0C56 ׿hg":'>j8qD$IqAIrJTH ی0g~LEtI DoJ@~ I[G]3l4oƎ?q5db_P:>Sݦu+ vͼ~^W(~զOQ:3V6Uɶp\JNV0szЋJ8}Ddw4ɴnɼL{E{ }5Cޖdb1izp5Jbx` QsMC`N|!5iփHƇ-QJyH{QL"? I4y6-?s@[@v`& (*A#؇JSXG4ZhٛP0 8eԩ=[͑*oP7զ(犌o0D;aIJ,k |C#G<0OT8#̋HkKx9yhG8>&yg_B5"cE ؤC/dI\'+]`gˬrnyfH:SW`|e?e欼 f#b9g0,}-U(UI'X( D ~ ًA6k󏹎ᖣ}+M='O l- Wg2cN9' lF>!@Q L,@1=B2͈d`~voΊYtja}1r+)o4c7)xX(6v {d2mE!OVNl / "~= jsi=rLf/& $z}VݙRxum6tudڷ2G; ?/P=hOuiJey˲20A #^x2E(T_Ȟ ȟ.z_HĘ1Hǀܾ :t]2KrC{<cRJ2+l~"l;ia1ipxS)_*AXLF] &@rS_0Nĥe7{LF9& .5FZC1.U^^E֋ռZ#RFo tY}e5z[gDNQ\0J`}+ijtR5']Rή:RЫQ,5" S/D} HńaqvAG%u4x9$1 oN_@\@X^ccw3g6kIIGɺbg9bY}#0$ T=uaW}AUU'9J2)S~뎊4[P}uzAhy#qd iP_g1Y&CTp͆W`0O߭kFZ[WOJTyDgc#HV ~Ђ&nY繶pp,w:}D>)K yms%E+q1?0:xb_ŇyFˢW%MTKR@"2ڽ-.'f =^䭽~Y@B'㞾B@h(`q5aXnԇV+x1q݈ \E`tש[*ﱝ㝮"H㹭9@ 9", > )*3(6 &څ2$'|˽Oܴ%Tl@DNwuUfל̮Y4@4g~nc͙`P`͏۸alIiz^LpГkláb(Ӡ` WKjcCOjn'X.a"D 1ג,) F t=]J/X} čYPXK܇V0t0mh\8IX$5q6>.T}b‡`">>O` Ndd`nK 9=Oc5^oؿP }mm6>pW/"xq)TTXlYpʑi4ˏQW~RDxx,5-&k bEHh (9944הt' |ǪnN];Mˊn멓{jR9fI ycuA㰥B|q?,҂M޴5ƜESej"pm\QHa08zBXe墡ꊀ1*0B^tUu5RFXY$!<71?iK$ deU* NuF]о*Ag$ 0 48v#7/HzzwNO?Wt<1P0d8 I}<&kVW_z` JsJ*(Haɯ7eFi޸z34]%XuMJGq\]d-֚ggV.fOJ+J5^K9/N>'mIҜh;+Х?nOAq8l|8/RfW\;ڹĽFun70؅P14#o3l7˗h38cET: 0FZbl ^fxY.;ۭk [;y+6Z[`GQrpϷSA7 |N>F=V5j5ivz0瘿KPdw>rƱ *޸NA0tvŠ]?.& A{1H롭^S[{-nuo>נZګ #/8sj$esGͫ&:E} T5]iFUJqUJ @aH4s?c;k,`E5ped$p8d3EnbX<7 Z 1k<-tisv#OGK8YV@YMg,zwx zf$;ӹj`,ĊG$NәKд}Outڂ֌iTuM/*l=qgMaBl~* 3 D68E/M[Ĩ H#e[i#B fsϧFUE?4׮_,mqX㶽ǚ^H~9:W O7xny9h4*+'7? Qs[TO&;"D#q -W+nZ&51#6V_(BF΄01Ubΰ̽Ռۙqi}%) z٣ga#ށLV?hpc.vdG(3v7ADB8?p@=[ep2@os9q;qdg ؉x?Wp)9AX:ώkMŶ## LZX1lcBp#&U2x=<~DSQZV2R]%Z]3!I̷&{zv7'm 8d?BZוKwV.g~Dr,0.gu4coЀIYC%ɊE=Ez7e(ɩrDIVq`111av\$]FFQu^"ۆ,DEk}s&2w$e-<(4rN* @^m9m3d8])IXZ"'keden3Ήmuk]swMeja,c*pl+v1J-:c~3m2U NaXL,6 RԂhkh=eTL̯6䍉a;mHOL^ ^}lkt `l dA/1" tωQ<9}id-4mVlJqT<jnrc|Rn NGJ! `+r *:c/&P1u*a<יf|y u0Ffo41y~7|g~SjsbcEGyڲ - cy>J^/O-_vب#F#~g0 n+T[Vl钼NfXVA][xQ}# iBy ƒF8dr!V:6QWkb6=֗75'|KR&!ٜ1X&vzkIGLoUQQFY!O:u~) c&. W FpTld YGo|gXQJjzq\y84GFM3S &H3fL+}d]Ykhrk/w;2boʦV=KWBՠFuLC4M !K8pDa8pdikTf&Ig \M&n@z#t_HLm3kC>/гA jN X`]SjQkRYL4u'<{ޣߛVddηjƩc^FaG-jX4$dmq- Tx*MQ:ِ''AϸKN_$]Y[hunTBzWru^_Snbȼ^I+Q1R눭$੐ ~vq1z;v?fQtbgb*]N33,P7tNEFGm;oɦˈ539!ԍ9WYգRFoXg8!MCY/2 &)@tU (,%&n;~[avuSV콿M< ;P7k;ńhYknm,XKqq5%}Ι23\L< UrH"iu ]\!A?;n?6r!^{Y3do$7gsxehڒNv1pWMl3Nـw4FKvcb>ݯ-e<` >|!q}].{̓< B@K0lʉ}*E$#XbKc:fعOJLL-.a+[V2ЀtL-適N%GdKsHЀQcTZC2I-ז\_]^ w3]v0%v Q{?u!1#"8Ac @|n ex Y +IG->$%yه~=K6s!2` w4Ip{+gr\-ӕ B6Rg Avð߳܁jRN+XsU}-umER q ݽ ~V K9t\D-܁FqWd{jjn fvS"DKGZgjkuMӝ$w1 =AyHfh Ѓ宱Q샇dYTȧ.Yh0gT5Ejh>3iZ0<H\bW5{<ܻH&ֳX5E2z+=ϡjz` "=J腪)у(i?Wy &."d4GvFsw#)y90SC=Q~_!?f\)NkFȣu^VfDjLQ"hOfnfTX5EY=YTY5#F z}>9x;<`m'Ӫ|6/ƧYTMNPiϨjd4{#1a42/ym)3Iq0-By}П}Hv<5/ }L >އ9[6Ҳ. t!=U72!̈+ Fy1DO)_#{G܎A_N{^TOx&A0f( PTQ%fYnUw7K{?Iy{3C"D [%GTx)g|cY~''6`u@R |S<*h+:9 6lq]{WWRshŔUΘ`QNd~5W60rz TV#[j5[KobLY83Ֆh+c∭h>濹z|( !>t*P2pOa4lq56~.(Tx[\bȠs2^P~3___&|0OIJ2J$c@LT2 qF'(*Kr@-YXT,ƅDf'_Ae79TE,qc4exO6=򸢹 'L,>vSF1G3Mw !8)AG(v:nIR2iWx:H$YUC+$*H]t4%" #{bN#WDO`v`'+9+jTH+yez@W g^|fm-"cJc1GL~J9!M RAWQeIRx>_o:2;>)oO~%ME9CP$j7\LYlb<($ }o{#a-cp͔ NT3 oZ,aOuAzTX6KIK$+R̊K®:xS+Q4\ˡ|cCoʼnXc$#)C6CAfAWJLEyMߴ+lP[8 9<3@`I0/jV:U2Qiz[$rյS'>a\J:¦^{Z\7_(v*ap}]lxe##>V A:[2"\:NXnqA-E65'+2AcVX~O`.֯2.Osgq?}39ŧao:d0G+?#@qTb^/V "bz2A`̼jd6|Z9߷GSy$U7ژ!H £f. 2+.)$_ܖe I /H`0ɞw|&zy k=g3|n;{J.ւ4GgHh_HWY/`HH8yQL)hW& f^$+.;o+K-Mֵ;FH< 6|ܐmw=ӹSBɠ汌hM} YFIg1@]}={0%'|)r^e ~cEH+$ Z\Uj`#;CL<9zJP tUxj"'2b‚ SP+!}F%;ŝAT$m5xEx_12dXBxk̚_;X"98>,FkG/bLZS L\Yv\` h slx\~i 5 sZ^Y,U> ~IY6h zO^&g.`k+ĠҘ%3VL_03!ϝ1~ "ȁC`P2āUYx:im8IIJ !xŪ7ę8HBHJ(B7Bd+4c4'D~HW%{7Lp}mv 6O!܁:iG^k~TÊs/#2O8jSPJ@,#.2;&8?|NDՃI,vg/}{蚜lib<3 mUbmhBUK&Cs%4WsV'ߕ2x >g{IcyG<0L~`d޳4(h_ rLC&j@;S'@I=:~˞!d2vM =ІX)=+;~G=O\գ:eL,*(Z]8xBTRRRBJK%;T\)!u> | /=N."wr PE 4ňQe),P#H 覍w(栙88~҇>p=<F$BY]MӼ;b.ctEo4D1pa5;lƜnØ̯u"56DcPw)laLd"FXSB(U7G]cf1.d~p0-tVBYe92 >+0}_FC>kC;[xzV=zIR=Ur#{vWst -If˸^g<ff03z0ǖ fAꮮ.[%90* 3 80(ˠ,Ps:̜WC[aN308Q_0(ez|Lxڶm۶}Rժ;1Rc_SYiVn$mQJD}>뛤20@e1alL0 7KL0AIĈ`c#b)f Kcȕ=9Üfy#,` FdAa1c:qc(#8A4 C01:i&2l21P0BL Rpq$ϹݧO2镄уEk]뮽<. b[RH$$LU9ߏIyO|o{EP\ 1IYQZ% ;H)[g]jso~~=7l?9ll۶muɶɶj@۶F9?v}9zog.,h Խ-SCrJO݅m)--@#3GeJu ` @,Lkwvwww7\Q %Ρ<ж]m[Zk{PȏadG͍#릇YRJ*AI*aɒ,%Yhɲ˖,ffJEbf~n~uUl'> Cjo ԙg7 a'ql<8 (O>*v@n<0'wh<{;ӧNF't'auhO 8鍳0v+P'hq°Y xUyOtwH( (Xklg|n17\7ؠ13C 0=W= ض-x}?߸e21bEPoekkڿ[oں׽ ܡ o'۶iOm[&@bBD)@0 wT:kTPb8U08D#Aʐܗڶuֶu8Hc-{m~Ͼ}7wK.Y%K$Zl>޵ؒeIZtKn[}_y]U}?Gr31@q6a Pf!$ Y2ar3AȘ0#h\aɈpX4pX,!1$"fD&%4 a&jbpX C ŸPuĄ!X`(fF) 2K*%k 1ƄY1fA5, <5-`Y8P۶lwyß eX!IM&i8Ӕ)LSH6̶efX6}j[o_km;z]g 2HfHL("E1I LCQ$  r `3{:+Y p>sm:jq綷s;m Z^ƑFsj,a5zqE.<,qhJt,LT|8 $<(vOwm'~Z~n/InzбX g}{XE@z !BHsOm׀jmZ{| l˖Af;Дe273`27P0c'Ƙm1+=^lڱ\yVwERU)`w'!lyv% lLJ A$zdqfgKӟg{c'd脋i@De uEi-)8xd۶ֶ4\}1A> @> )(+ƃ:W+e".K-NSqɦyt`+.1{GSka0Ղ!<I_~5)ΫQxfi}wsIS-)Ϋ5LeWp3w#c;"PzVˎTӎsɸaiu+cLIn+Rmm0^%֌}l,akNA~OU؈*@t2ꣵys|s4'їt Y$ cں)O-VV̚}I:(j͑IuH .;]]!?t~g=_?~&;?-~ c~ژM'XۛS{AYj xͶZ2DR$G5Y֠E5X^'o\Ҫc16S󒛐;$᯶nsɍֽNjHk{^@7Zw&{[& 'h]۠`Q -#Y}E˂J62OT I`=lr>J}F﹠y5- *#ZTDHvlp~+gfjɣOQ(e}Qqb5^‹K6ˏxFK^!rd^=뗭%à@ p,RWדW}=:i/?/~ŋ\DmUh<njpޛI EycW6sţ \1cY޴?۝߫ȳ gTARYŋj}?9Ck;sG9?/t~޿#RNj Լ%D XfVO#wN_?]RJO8'LhZ*Hx8LW|Šx7עi+Ɋ@sN{_n_o)n rnJ U-%*uV! `k͝N?[?۝(oRP ˞ mV8'v>7YhS6{Omj _&le(VP vy߿YՌcHpQŤ<W"iBAEcs;xӝוsn9^qNsߕe{Mܾ]pLE1< X{e~NFn{b2jn߈gFsp f!8Pdݿ(-7}̶sCҋ9xF5A|F|ok>f[Isۅnɞa[FAP(~h|9|\y}tҤEJ+xO,F ECt^3G/F=;mJۣ[~=!ī{ ci?:r~}GqArV Ea3FGT֛>2QS83R]W0넏t:>.Yf[En1"I\u TJxIWvQ.`dn/VN|G" }_yYzvna.SՂXQ[<WvndQ¹婝X͹gGn '?=ro-e`c,$@w.h/szg孬ņ|S.\;]X(_lǜy7=Gܘ/mK/A;'{ ZY7O?~Sj nvQGl?&BA,mR@>U_r{>xmP3э:ܿzWG7'9IM6DIw_'T߿#~9T'ۥz Y/mO^PFPcŇ r蠱ӑ mCN+l뙴#'Ω>5e  4~%A|ِӊ'6zGl~%7f+z2d׻c+O9+ްҤ r)5ʄ;()R<( +ˣw ;j(8MW@9(OxE﫲_iͽO k7ҭ`,2E k 0Q?s0VuQ]r <^p`q[\nԭJC7*7ݖRa#2ԒHCCN?O5O,`wqÔ{kд?z|S[ ϴF й$NrQzjꟺMuWVkϮKE4"+Mߪ .uڳx[ԙt)JWZ =~J2fo[AM+ӟN _ $ {6޻fu5-tS ]<^@ +<~f4 ;Q9FTG̖SV+㔲xj,++`G?4_z7++L2``s7_7N"aOo)=\#7]s⛢ˣ,DD7$`bCa#6z,\<6#;rN,D  EIa -55=s?j]wWxIg[|A=Zw5 2ͺٗU!`[;fߣc0;V[H 5V=z{,H+فs~QX^m~ᜏIyv%O+kOcw?DlyN͋ ln`ĊxLC!P}`f"T N[ ;kLZ9$0eRڠے}N??~voH2pC$"oTJ*wI$Ԩ|L7z,3;9ӟ~SٓǵA}N~w\H!}1.t[7;s[!;UJO,_jw\PG"M傠zS^u@%p$ GƵN}BQ:u~ >F4tCry>,@ Ǎjij<)F(,q5OɨCRÀlQ#[[?rD$wsm~"#zAH$1"2$WD 0dWWot߶v`e0$Nݹ;P 5xXRF"?E'۷S{)NvKȌw\pEѐ+Y i2Ŏp{v^W~破(K6MΩu9t-NS_ 0{ hpC"c+\!yQl[5Rijc@V9o.l,GIG A牽ɴ-IEܐՐCD*9 raZ +n𝳅KWV'm w[1v{pnY x@R3^a@u}_K62hc8_'M&h0s Ɔ4dՊ2yu3'Cu;jNQXmhT~bEX j'"d BBxdk;h xۢB+aGe78n$2HZ 0D,DbBڈ`%WP$n}G_J}`,UW5:T "cM_k7¸#ӽQ5 cLUY|]ĕ OGSv,wt2.hGұco-Ctqn:θ?| i/`;~)qn*v e TeCjDw+i[NE199$N]=鎟?1Q߲&3s>i2Og5M&PyD)fbe}t g" +/ BQmn`C83%~~:kf ߼{\kL[| ():oqr[$˅wݿ4<@G=NmN>=\vSxc)/+½'HJu?=r[~=r9/%J$&@бmYI&BbVz;mZ, #o-/qn[O+Ia&c!/{oZY;8O+ hkA^:9,@:0&Xb%rBKO '᤭>&X(Hy>7 x@ _彧4>8*m1?RyTT*\(j\@ RDƴf_B+Plt5KEҢ3RPL)h`޻%s2qG@0t' ט(xQE b3P;,;s54:Q&u-Oo}΍Y6ұ`~MlMwAJd[XArm*\QiT p,{^ a= "B/r\S[sXsD g8[*>-r<B O EV$ Xz̯T4qhL jqWn 'N+^e(WK a@%YQP4FJ-ޜLysLHU$%3DOz:RnPeF=u 1:y+ GAӯ{ot2Uk)UgY@4=.;̒snTӤ-WvT]qEGNkתnSc=\@x&->Ks߼E-$≗VYLv7OdD~TX܉a6|J`֩5-5٣ +|hr&ON~ma=r X%AR:2_YQh!bk]G=] 6wE4fIy-3(g3rOOo.psor oS`V{J_c8篾bEo9͎(aHt Dt0D(Al;F\:RзIfQ~;5|ϥp&PN:#4 vZ*e̚˿ Cݮ0@R_ Pi(4J#qN$68KvN,]e/Q**h`}zߕ^ Hfꁟ,QDhd8iT>5j_ .Y}:& EI`#g cJ.BaA̹US}35?IGuN0'ʂz  ,8#% f$zu Ջ y n6 v?[; %ngk6gܡ9?9K#0OtHV~Lg_B K뿽oNj{uaqLal&DmMUuY _mந][n[d+dԐ1zStT@K _ (vQS QsjoI(-/_U8dҧM՜8'0&q\0X,\vUQ=*kjKnѳcW^a[2BX(UT[*HvgwׅSgCV~lÛqر/i;R݀|}yf^` \J~GƜ56*gSPRY|uUD!c)\4@U1oj%5 pnb?%\9F W5v/` $QFN1 e6#c&E E|U`$%T&lsG1Ҽj70aUVXGAUt-i̎vֻ^՛pWޑ._(̎oz|sxG|]#טv~b'oz_:|ՙIhcO;q+if*ʞdwoc">S1T_ FJ^}V yYtu[O:瞃&dN[;ՆogF9Cҋ$ƿJ.d7sZ:a[Nc =Mn+<A&uxy {"u;pL(##a ~c.MHKWkD`AhOr#DoЙOByWUhDjqP8pD.!8 OT y3<Ț2cn%uhu%rODTH-#eɆvb鑒3qs@p" b5)RXt@LGC&58P5^,yc$kV8-ȿӺ n 8f"mye䄏K2Ұ83SbPZreEH~C_ CX ;1/E :*SdHj2ݰsHMA+c뫘9p+ܽ(X`ˀ+ tfRRrbDԇbuZ&yt]= A_|&q<%$/0,W2)N~}/A\'2DT&ܷݻ-"95.8_,zG@i74m|Q3uҙv Sʁ{\QMEYtext`xT4B4$DBc4\~U@"Q2A4F+ jڕ2!p+9}r#R 4)>G^Ѯ ptᔌ =Z8)i:EU 2 MB:QE%m*|4:RQ&1Gۢ AQBQh}- ㅗ=~^шphE+dϚjӥC?`ػ OSt)F8iieb2Jc5oܵa{gЎ:afDžTz/o(TIPPr ;)  hŠB^ KEQꀰ#p#~zoSpq߂[ժ'U$ ""aH0*|/lcpSO 9;&7("hH{SD!LxO[(sav4/ 5`υ@t:‹Js%U5N!e7_,wUoU%&{O )4AcD>I%p4/_)|] :#9]LIt`3PH083AreHatǐPtU Hr՝Y/mrp27:`ִ@m @8j}oc@=ND7ہh %$"E > ioesjRU,pv .E)|sFإ֢I۵8JHȝJj2bL±H <) AE=M6 杯su\ R2WNὦv5 Azn  ~xW"O9~A+:aJʞߛ'j;<_k}>O~_6kx _ΔwRm"viv</zG} !YoHYܥ%W%7/+ C5GpROu" A!^$C "P=V/-%[||{ܯW~CnMwt^~Lt5<R~UܧR!XLGEС4R9WXn8ʎ5K/r$U:Ңb~B#uQKFhCb+^oj+VT,/\(厨l4񑵸 B5pVSB\ g42AO OV} $',)hr1ȩkw<&*+҅r ac2]TN7r $W^ "yfן-䄫ї3׿-޷vI-A#9!-s%c¡Ȼ3dwX[`Lꞌ&-M F0#XE$8fuM#)Jc@>97a@B¨RX1"(kl6Y3ǿWoNx]Nkڂb~LLr6P4Txj`t1T%}p5/D&"ЅsA[nro[ @r(SZ!va ( R9Z#^U:q6I7ЀRQI)m@Vk&$*k=?fT]60?a2zͷ) Ҫ\3urt+DlhBZbH0 EyYUмf%D Uc1TURLiWةUx/CD7AE ݣ" D%mUz/YzD+Xm CIR\*O#RJo~1IWnY@ǂ 67~W!{ L p ~R QGTͿt[8X?AX'ZIxrtlEuGsd`p{*-kx -|lyD=zI4Hz#=ԍ!}fߚ{:XoN%#|Ma"I14dwo pET,ja4Z%âӄ#=Q"zIEDfo g{/[REv|~oC eH ֽhȩDۃ1\i yul6]\x8xHh<4\ٛpP|. q~J8~Q"!G~ 5{NY"HUƩ~v=+@ T  oGG 뭥?fnͩȦ.F\Hzؙ|[6_#P@T!2O$|UKSeYAIR @QEUZ RR{:PK*|ZEϺY7Ԉ;J:\+Z^ˇO㕜C; FzK-|RX$VzKNз>P=ql*xnPK(J4s+P9WUUlߐ.ALA&߉Q7\qR^;WU *5?Ozңl= ƭtȉ$־fJw’ЫgMy;{J=dQ{,<Şu0R4n)QHZv-͖69d!j1PM`Hr 8 D%eE&,;h  j} ]ldp T@,N:څ.V R.8 5,kv.: r CH%@PTƾ7~_KTUW]km)kyuڭxO\ؕŗ䧌2b?]D8ae2AĐh٤G#Nvށ۴)F5?b#?{ V6$*์Jߓ 0,@!DIP./,#œ:7&e| ċ|-"F$ے@x$ h; Hrog-6 V?Wn#L0\@!IT Uzoܒ(;Q1 ՛V$#ݯP_șհԚH Ȼ"O"I"vRfM[)OHḓ A9Suv7,1Yw+M61MWC'#Pyc ftHȉ?G]Vc3ƠfԌaoo_Oa95G&ޏ$4kH1Lp~Jw _"pVRK>הY\;ՉOƮ+`W-vWLVI%:tlH {s~R>Hೲ"Mvw>B=c}*cf"+﴿rz>[wg7'39Ώ-%VR .xl>jI3l!.^{m% \H0;*BH&DV^L"ziM_N AL*b_Vd"Rl;dN"{K1TN}߁>/o;x[|O> M"C{qV}2}9gn%K%:l1)I(<6aKlrj{OR>NJi# bvHM>+#׈rECS3ZYagW6J1Z7-{LJ=1cPhx_9s6Y*Y?,x&b _MʰQrȖd#[b~kL>H"÷$%Б-.b ! C[\1.J#)cۂ-<C`ɱз1ǙJS_pL"ROTXjx$(ADZUPq9W/凞Vt%|5 ([ $Ljk/dcr:qʥ'KG+LXk:&ol5%K8mT 2`DcQ/dCr*~BV#}{^?U6C #|q6k_7H˜Cݿ>HAo߅X;F!̯XvF\t5֕HdlWowuhذmoÿK'$!D̥`=5vVy浿\:ȻB鋎(7NI\@hXp8̐g5p:ſ11)*9orl%.KA$WtU"A9_Ly+AxX_HQPid1?1iG| P?M@RF?S*"jnYo En~`8LGQZƦ&2Payq3yJ/vsM)%h IRlNT*06<J BZ9dXJ,MAVjb0@Kn‰Y Rvp]y[{>g!$zΓ:FǐEb͉\A@as9B2Z@%C1)H$LO$|r FL$dʌEu: i6 :R*6t_8A%`dd2\Tvb.,>ƐqGn$f;GMKɅԬ-*ީx鸍bHW4}s4aPIWAFw44XhbecCJ:f ֋FLyeoe^ph=)ސZb& 7ַ̬OZJsw׼n#"@*xWroT&oSވׁ%gJc6iy#X .4+&y|7'`(*k:7ܑlmՀm2xGhFZjM6^1rMQb;񌨉fL\{=JI'&hʍ\Sr5\J -'P ʽq@4$TF$ϛD])X#+/L>ibl,) bPI2S؟\H}ȩg7I"Y~?c#.=Rot翔$Jt䐥5%8q^oA8y_~WJ$ҙN Ɠ uھW)`&\:GeBy"oa;8i9`F&9!w0EY%*Z ׹kԊLJK, Lxƒ{{ѷ\ik]eڟ0Ob/ `s>K=bcE!-/=>5|^\GF\W\bIjg_nܣW < rV={f|`lWǿ_-P9nLJ"v@WzUQXŨ~떾˅F!](765%k Z SٟÀ!@Wkა%P2h7YgH'28*έēIp.$؁F}Cș ,GA?=sĿ[~5%1 k['|mު;LD8})LE}@:d7,<tc"27?A3K- :Y'U n3CIb%aGO:OgjPe +!$#,:*|=A[bs1[VSbJwMFxTx2i By%Rjfa ׍!0 1❺!mA[/IJ @-y=b!.9=[5[Q⌅&EGnÎd]rU?f,3yǧOi/ gh-#I$*>,:XWbRN|W(M]g#I:4Cڧ*82͟}BQ?0,2/0Uc3Ƥxv15w~{ZWLM-ĥ%g[xA G6 ~70spҤb3Ƥ{Ͷ9Nɍ?RJVKRh4:m,@1k汁{;)i&60YܾwQ͘8 Y{Y HxqԸ~Ki~l N_O|n^|IituզSMBډOC\bQt?-+#7сk)3 3c0ӔiQs^cUsO!&'g}"2#1WE+f򂣢7Iu)l4#!'m7f9㈝jhWyELy 源s<$db97qGۿ+6ly9Ip sړQJA&OgĔISl2r35xpLdLy,(!UJA ]FBrH}D~?U'/,WZçSS鵧{jb7VMT&ۃlI>'bUkrܲ8R#]{; Рq?CýzFJWTdGQZ̚[q*6!ɵʋHBb,1kzgDƫxoIDZ4~geIX4|&Sxgp9Y`6Q nwm ~Q}>15ׁˏz.$:ɚ$UH@,O$lqSVƱ7 .b#e'yoX;^V$٬aavW2P",ViLHr9#{_w`t^=p1ȏl0HB{ܴ`o:Ȍp.nZ*;I'e6o{Đ o)9V&jnWyHekcEy^O'@S@ ;{,ʌ:nVclĐ%J-83y|]X-0EA=K_F2>127KMs!FkN4n#f7VԸQV0V(i滱)q6;Ɉ)U ̮ӎ}KU]1+AQ>CINSs^IZvd\Xj?Ƥ>&n݊z Bq]pS;LcKSNϱgh OĹ{Q!~Fkx,sF>/# ɜ3k>(c%Si_a:#frG!}jaB =e N}1#R&~&w+~>./, < t_0~DM r4H8/ч5+ҵK$G%kw>[V fRF%?*h4 q٨$iRAàRTBꝻtӄ3fht Vs ^V O JQ Z&/?r`"gԇ,w[$.RV͆+T|Dƿczj 'WٌRFA ;un#k*L 4 U/_+rj|?vy6ٌ)w<0Xҙpi`Bg?!ZY u}Og^A@( (w#S: f=Ȃ*z-}@ r0Cof;eBmꣾl1h<^E,5}Nf}-\Sћ1C#|㕆<5Ap#}@-7W=kW+wKvRV#㕅<|las뺽" xE0dġ|j{"RD |+=06 {J^llT?>Ð}-RP4{;\oX"^>Oj¢)bT5zҪ\}a^8ϫ]~5<~^p׍e}C5`DF%Vj]oAg7i\*QzT1uvfoh(Xհ^Mx&0FW'~t WOJztwPv>g'Hh< V$MOEluXn0a.NR,,bad37J gHy% y>IoGoO2viTk7vH2aJW]~b8.|ߔN[9,aJuƸm*7*A M"3>˛#s7|,? [)02qڵ/$8LJD5̿_2 2"&)q|ƭ8mгi`Xi|Tf P^{c򱫕u*o NjÙG7oQ|&FoFTz $ݔaQv}s򅵯Z)n5?~%bkz$T t*r7ȆD{&\mǟ{O;> )+i[*lAGyQ@N\hR ¿">NȸrypԂg\ 4fQbCJHu٤xVzk׻ԕpc;:nGH7U"TF~;C$Չ"J.im۳BHk|zݾ:%nOiE(2՗osh_>'(6*F5 -T'TZ̠#RDfnz8#C* 0Ĵ'Sp:5/7 M^ɩ XEx6f&v)ЬQpU†fv5\ b!b@궳✃ͪ_tU֕я|獴w7pv_M)PG+<`MV0.kmćfw˨Unj7)k̀{ G"\2\o!fȻ{S}SA|#ƿ_Zs{ۻ=8W?4ЉH"mu۝E5 2]7鵯&/6Cƿ yԴvX^Z̫eR>'(R5=t(XaǦ,)3ot.ܞ5p+'Wi_bt9N˾oHBڙ LNLWLsjJc#g)t&R%R{F4fMӚ/Lu^=U}_ڦf#-[KwNQ wK6;M͉֞֙Z!ݢz]]}QsdH.%Z,qO6 Wy;pW6<7kgWO)o6DԒXif1))Y2~eJF BאFA0J. 4\O"!J;jG5wgd9 FN&6D]äRʱ1C `À?+1 rn9* BX[S:{ 593a7П";-f[IYMSRQ5_L]]s<iHપ?ҌT Q _I}k{& baV7h/;;G*T$=h% uXrOOk0 -D&4Cm3*u bE<1Skyv=`WXZ㍙Z d6% "QjҼBӜiԌӸ*2@~"q"y:7C|WVM{ƸeU4DU 'JA$q^锠.yRpC:vy,9N+V2Ĉ !mL=jw|^f6D BHcn= 3jW.z{mfpW<4]C ;Ǖ!R Y̓lho<(w+9_B![IxIT Y+jѮ`%_w|&KxFBN؛mO|! = 2+3dw(|$fHxS/{Ɛ H QH 0f.bƎkqp_b19Jl @:PAR 7L9q]ۮNk] Cm3SY)ǝ+oO5㹈;>WU9N7uwg oڷ%LL؏`fZiS9ǃW %ϞFQH5#4q7֢IF Y7`diԠ E5j5#0\TF_k}+Qv.\_|+5}~=jcߓ0hz縈w&= !U I\}Qs;;|QauWwyk&1Ɛئ+~NUlhWzt9RnO0W璮q0۔gYJgt&E1p!̎;ZSHs*;})JJ%Ǿ"z`._UB=xmB\Tl5|o"K ^_5T/|f}m:P+LTl5՗|o{Y3%~~PS{@.zK -~#XX_,OsMk=|xg~-͓O(](P`:+!Yp>4ϱߧc.$j+t@(ЈIDd/x8F~_Ns44Ś|9(A& z~S_(R վ&?ζ-ˋDq,ɶ /]^ jA"_8B3D,d{c`,4$$2gy5?7j|m}!90zNIS_;!RaM2Opu=8\I$S_4&NfQ匽PpnOdf_5<3]]v]tRQ&ɜ_h:#G۵8E{6LcuW)l^,EUCÕT3L~25Uڠ)+R΋poǿt̬pRF;|v$؈zSf˚-8-kthx-zyVO. ϲ5@nY~: z7+|-h@1Wk|أ@ib}Ys;p5EvPwJ&1lo9YIz{g)F nO 2dz{jrKdõ 2YC7JP RjiR")z}w/\t{*/gܦ'kN@[{=:WK{# {Py\y~kN@[{5zkF!7|+lfܧ-o+W.ߒ;pCD|M5ֆ'C׎M}.f$<DurFۙ׽ՐĄu=J!iįq,f( AT ϲi#2F0oS>q2(ETG}]\՟7U%:<]Dl75pPH41VjooowJF|StFd;ؒ['*ȆfA7՚P*GZYg}_<7l>'CLߥ3_+@@0{VG12 QF<њ֡#kf>ic/\O\y5}/)5Ȱ6Ȑ;NA! 8336F9 0wwZ/C$pE[2@:l*L~۾y85$" r޿7Ht ~H2huq㾏"1EЌUO ߭Չ p..w\uEVxՔ&Fi58[4]׿ [.wn./SNjk5m߼Odswy!k.B'FN렺Ejy6nrOk0nƕ"dj\OOP2 /#)5eČ0:ϟ}j ~ӯuI0pYLlt gy:~ġI}|Ye~rS@e?nzFϛR=) t%mq}rt9E`:^orLcQ{gJre ks囒|)'sFt-7Uyq7GW ū W%WˀS!O>KYiU%;`WQD.n-eSϝmU;7*=\7iL~̜1~\.[[>b73qmwIoN_{j9D3wH "m""gX+Ѵ¡Lzzz)n3mrKG~ 3sjZwTcn =|ʏUySe/\t<6ms{קN׸\$V6 t$xI݄SW3%ǟ9!~E˂a62n,^t 'i|)͵gWԤz *O bT]0Nj_N+to񰵝# H}uZ^,[Аu(NƼ3XjY0a}o%Qҝ MkFEgG?TuI nm~sݚ ;گ=!gYӖs)L%=?ͣkŊ:MNhP$ :=xzMKo'.v4!^Vu2&E+HٖMe !(A_.xS' K/toxhe #eZ") *_Woqƛm&?yW {(x$ݖ6?ptI=Y_3JHU>tg,K#J易rU:`MjV1q,Ѽ*jK ǡ%:ڧdqv&48~5 /yh1}̹ua޷7Y `b(wfaW6- Ym =l,>W-zgWM1kW7ͬٱz:Tq!vEF$-nМ'/9^~_2vW6/ {cps2 AI8'~?}gmY}ӭI@"E/:&3\ݾicόl܂Yja ~`e?/.h>^0iU4^>OsT]0s{@׮+`2pJ49_eq<ɢZ& $k~_=ĿSA6[Zт?i A1 㣟Ρ%mu՟G) {2xTK_ݡ[uq~TxeVhO7-u;n1har9#X4؞]}n~z@^USiΔ<=>ts<"^D7phזl#^ 8roR;/`ZH3c8uAO[3,4喌:',2kBs#Hq|s$!WO<+iQZWE[S 6[q:V롿aHO.w#^;=TwȜ|fKY&O3x oE|r _NubuP.g$v]6ht+XFy_[)OgjL7G.m;"We]4}V?'1K<?C7/Z6I/5Sƺ`ՇЂ w,0Dh(E#*iĽջniHu?BQXp0!5Z\(9HqϹi: _{' T~ՁҍQ'ՙ4W N/-zK}eg^E!]5 SUʖg/wMqg_Ijό}q3ӖO6i#"5ܴJ~CĘBO?!u/MuRE١IM{);jTű]_*ʶX/$V%j-W. !6Z9c^;|DocVf%3@ zF9WfMiI, "j m#W_P3t=xTˬXcFFћ'n=@Ժ׏9ZB\ޱ'I܇PkhKsϠm#!/Xa+hxBN(ZQf0r`EsF f`L_4*Oe%|Ye}+5lpk.yw 뽶s^*L{QsKφk W8d҅80Pf7+ dŀXJBJ! GJejݷR O{՞~ʐ۞UO @s 3.AٶyG}9ǨLhu611@*#j ?Osѻn~89ψd{]Td]0u$ER^[=Cp|h^?\i%od6*riTX7w^A(!^OSV.nx}tϘ[w!􌀥?C_OO w$$yC^*=3JTg-ͩk[,VA3)CTznz!aBELefoztcc *&نf[oqA:z Uzq}+hG@Qr㵊7;oF}o)u5Ȯ|RھBYg~2i+.8cnV[F!݇-D\#,q۾5;Alc6}})m^\SN yĜ#1yoeЗom{AAc<φ0mj^Ik-&C? 3-к7&{ D%< ( D{F9WٷNA8cXL7n3el5p8~h &-bBhtQ.X-CTFU6pRX6ˑ`!MIp9_ʫcpkw-aoMԪ{f9|NS*\nEA)KީdkS%SYDGnKi #tV 5yhp=GgV("z|g$?0uzQOڞ@bH"5 7+4(P`w؜7ٿkڜnvZf6lo'm!aLPJ"jPwa,o w =qn* EYLu]5hDRu`O|9_םYV(($LGGZwzu+c`#`7e&->"BnIMh)DZE$?<̅ M_J ׾^EVsWxD$xѦ]; pDS.Urf惱NRT,A't&f ^q4CH?l_P.'R ,E`!B>'w E iGnp3{ޅ鉭h\"u ڠA0#@НPm@u:4Fuޟa/22.^v}Rgo1?j{^ƦkFyJd5\drH6h+߳QΟt@MnjE7@hx җ7zFVZ0ufU5qtI w 0H@Q*$l`T󤫥:𿅒#+Wg~#1!Ag *N}W,UT|S/UI|#EٯUҚ=OLϊBKU._>+UIR_3V"z,b{/4ɇ]ǔW@x? lyc,yzFR>IY sse'DM=^̦T<{[fgaϠLJީ1)@Cib k࢔Lc-q *_VaB}]vJ‘zrA.X">17q>-'6k7ېn"roj? Z[$1tވl˚XLL3n6 :K~`Q:wZqD֍*iM֎)AǒJfKֻaVJsH}N +sԆ}sCYZ%Ɲ3b4{vͽ}5%;"iͶa\o}FT쨈j|OF0Ԡ$Y5HeE2`cuݑ7,;9[g_&f[% y[2ԭbFB[a(0UtJ|EHh쓑WvݖH8XB͊7KCuF\5U 4W' aP/ mXߑXK Y2T0i\`Ac7<εV`U|T -Zh:^ T8+ )&PCA.{&X`5MD[!I5UK&<^xTO9Pk"E'*\tUR/*Yb$lݭ~r\ z'N8rYh#UV%jGcӝk6ZB8bpɣ}Ϟ/W6l& {ъ PJ/XD>VQij^Z52E Y5J RCOHefpE-8U DrYlwm6J}Rݎ[ԠSVƂK../0JE)QYK`.u nDI(f*x{$ ]R Htg&Vthe$I/M,yw3Uc0QJJOѷ[l4nKΛG^:h9Ӿ,bKQbe'ӞCgʳ1 oj>^HXvbeJtaԏE` =o "DWR`'FWə4\\VZc0C`$04*~fcE- ~Sdz(X+Q6]POJW_Uo ޟ@ wU׈{v6r䮈짽4:+ >ߟoQ:?w++,N_wnu|3@L~Uї+;m7/ʎkqd~aV\&U?ߑ Ϝ]Nw6BM1j`$)ϔ"pl.gCïTRFzG55 o^OUKƺU4gd-cm:kL1U`]=]xŃH}w]J&:VBAշIH-@5$Y9÷QX)kp+ Vf#[ߪ*('47b9iGnikϰ['AbZx8Zsts@ WiXAT[~%H Tɯbv距 ƺUKEg՜3uHosSD>}1ሳ t (#'JONdkpDFx[a9u@dŞ,I0ѭnݏ"eP pPFj{pxƯKv-sբs'C*aUv6.QpH hjiɘ:䕧FqZQǞE*k&=T x-J|^ Gڽ46Լrsf3g,R3q{]efS' y_1=+Ȉ&l+-3AG\evt9DbOf_zop,5ъ2O)qv_,SILDgiS95q\kpB^\s'@L渐r"eG,chCԩW8<)PM1kpͬ7(`o"zjT4o!Xe9CZ`4#hNHd=["wI F,D41 Tʄ#o^d3@S4؀goqQOObZ ߆ʸLJTlQ :>S~} {b|@VZ# x4 F#saRr8C5muW7lcSN%j}ܹ{D,wmWdڍ?:gx ]<'X3WnJE-[ޕk}8qA8j:E\edE/5("s3F(wW[#TmGC{K3g5ۈW?M%&Unr4 JΓ f"#A@4tsWXC:W@GBES؈nK+cL!~~ +`R?%JB`\ث cKFUfP0&DV!% -E[Tx(+ׂUE-^JOV淖R4aA B+j-)WhQrġI_,$M `*I唳_3&C9 ܚ9]ox[UxlEI-ݚ^ɥ7̓ilyP'%VvqjϾQ©4i/*fQ~;K׬TՠOcЍ_@ 4,BAcR𢭻ޫIS6ޚ0>ܶ)7cZzzYrYy$AAQu qY%Sa2Ix`O\xXTc4ki(*/2:` PKƹK&%B"v9&ˑtEvEc vCKXj;N ZSJKw5%Zẘ VLߓ>id. n/kV^hX Z<Oy|ջn!sWc6Rdj *$ [RX|^a5H ?CoK21/~V+{EbnKQ0!9ky~q {:Y=x>HK\ oT޳vR#{ V 8/<"kjkְDTa.]ېg:;%iҰiCsJZW~DrPhxq {)l!BKX}[KihF@LKFљ-վuiwlx/o21EM|Q4gRVyEJb)iz#-6F{}rI!+"&vJ6O oBqf}n09 LuLR@w6|Vc8V^l<إ/D>O޻wS1 ֿc4JA ҧ4L7LWEDheC+ :N>Ej{|谊Zвiq$zRΏ/4)g?*dz?6gyţmv(>l#}=U>'<fb>O}/(Zx?a0*9yKŵs7Or%iboEvq-p~5*]R(0ްgum^xfWkAb\w)KVŰ;DJO1ZEk$+$ǔ@9@ej0q`!!ՖBIu7nRTBS! r?1 NU|–ʧ*>%5#N䮝B\*> yJӀPZTƸVic7zȰ`Sk1RmFzDUz̚ 1͛ݪż'$iTk{.KivX5!3iTP` cwh䮽UQg~03f7IaZbV-\) m{#7>cWz% +PW)!kt?Wx&|UqmJ.r'Rguo>ͥ+_{X֕`ƥ:9Y e!&mFHϵ_"6'8(bn/Hj AVLNi 52%$NLƳsogm$: SuAs1懊Qlж%E!3qࡄ4~)!BH͠8[Q@crL2Õ'⧹6z$FbJ؂ v7*5j /MrĄu)VGa߾P׽RUP Ɵ>*To9 ~tͧ| D絺|{"H>FUr?LDSD1Əb!FJQjqQ^[M(G.lY)+ ('Rc@SL?GJ[MܥrDds8?Wz'` si/wٺ>xZzU7%Tsz`itRq$fY˗\0 8I)BJv|QqBeۧơ*&CQD '"}Epb[^?kP,;=J;yF3\'.ѩ=p <0P闘3J',5Cay(jd0iڒZ#˥aUII2oԊؑkKsSiv|}+'uObT}k܂މOzࠃoػˌ9.Q, b10 .}N)OC{?!%+>NKH  b+6xcYΦ| y[f2G9TM9:&5iNWn1hV9G7 jhީb'e}.ͳ [['k*4OO*^ml+!dtn6TCIf$tiy-5apZpĚZ\㪰"l*4 bFikWZ֚1muʓh DGDteʧ5eLZYoZo m VyDeyvDMt$TCP caEf+0 Ψ6n%=a43.=*A"9L\Vrl5=a!&mK8iPlbM~9t*눺4…b;9%m>~c݂&x &ψ װ zЋ #t⚜ԔiVz1ӴrBj|X "K4%?Ֆ>Q+;=;ܯ3ڶ8wz`Zl0BPܙA]vGmWQݞg9c5P`Gu+? >X<59Kd @;LB2͛^Dzx@ՠr UTm(HW[b%14v2|자l4O6!v1H6|J!Tŧ#D Fy@(7P 3"uQ0^d/*E2J-Ur$9 fEV EFà;{G:/Q&?UPe8E * *tP[  HRѡDt &0Wh, *o;]1uvtn(N#MՇPWaMC8FV(FTZ57 KG&;Z 죹cQ}jَ\pZY7YrGF;6 Dd,ް*3Y"ӔyN ћӬ82A hʺb)~+${4SI wӠb*;):f+rdC_Tʡ6Embpޞ+@\D6q"ۼݺU W7YYވ ,UO4h>\!(/ĚAzɠ+ w\jH,U[fs::@w( m̋"όBd9fuW X1/-rRy+4[9Z >i@y5[[qeOH~(E)M#AH)J3Jyѭ8)*jlX>N27*6ˊL1pvL6(nȢH^18D0 |1`AB61HV|n%~1¿s^j^GKa%c%{x_ՇnO\zXJ WHR>&tUPhdxe8L)4sQ/råo"R|X~͖Vo)m?Ppom8f6CArW^-5rl Q ޸厾$&z#enZ!mLYZR?mQ8X"$H{cO %4%g(Iҡ#Ǵ\r9G;˦CD :6R"G ! C13es;eG`uo% Έ62.K\B $@Z%C*SNzIO;{Lҩ^ӛI8C .sp\ 2RϹ.P]DGTZξ 3ߌ L}O K+Ma$DʠMnUqE4 \mm*]TMjiX{Ž @< 6)RsV_ߕT19ΟQ+gc7JnZDSYV}nTFZO/F/rFG`IiV}nv^LHdk&BǩlZ{ ϑF_Fcʉ5j-O+@ P3jD=pZ5\/qu5Ō [-{$,RbG 9 eCpGX9/K)JBQ2nF|NMB4MVRC,Ypy4r`}-*ʑ(TUŰTTm'gE"! Btm;u52 gRaAĂ#HW|na&Dh*>)sZ/M<4K~'TQ[ I&4"Ǡ,J]FTD6D b7桠K%l4Ia4"JE7?Hec{JCw4bL*OpKָ4t\@ S ?wr'8RР!%{{~ ESL,ʂ.5grhT*0!94x,#ú&V(`Ц- ϨzuUQ0 S9-樚uQKc~B(W1ɐp'8 K`V|\]蒆v W%= St ,H'YD&RI^gwgƧF\S0NqK I] <HW&2r|XyY}9,pyI!dą RQ n328G >E+2|yO E! 2hL+32%Ƽʧ;iMۡD3]ܾZpQj|NvCyVgloRZlTVλq^O^[pphTvc{Vd)t?@K꾻 %psle#ɁkFo8Pum4OD2S2Lӹ\L45 "ugK}ʈ5 $QJOF|k"IDNCқ 2luRRd<}? (Vl@"\JL; ( K {V`rVzUq.<:η }կsUZٞӝIZmXwQ À#*!A;|+. q!Zḻךr<4mP}Q۝xҭ?o\=%J!aΩ-;Lcz<4}`~οߐif)J9~~_/JA<۶}BJmѢF1NW|,h^Sr+mqZ@AG~3/Q13V-&رާWO0ce֕+Gc-'b ϿNn{XG-/QZxnNx`1? bw*br>dÁ OEӐY3GK/H&iy&B ~l ŔӠ&Ѐ}@t.[4A'9E;fF-?K,sV4|8)@1_ԭy5#]y2/M%M o_1V,J"_̑D{^hci:bZQ92HPw+%v8+/СD iQ=$cɱ&/FiGƕ^ݯn)F]W}%Nɱ#$ nE hs¥ejG_+P˸9zRtLVu']JfE ˔w_JTtn-SJ/PݢJ D~wO$?WGzzJhH%ED q$HvsB;B,>R (23"/"o't#. 癩:#[NF Q2F&fR,SG1r .OE :E% Ԗ[?~KMkyJ Ք9!IFO:)p~.RDGʯ8a@w7?`!A]@̷ͪ>l<@YYW}Qzl HWũ"cr8]{JH/#Tʏh7X_ pUWODCFbbtcuoB (g3xpwڽu^&eU$xa$D"TgR+%wȭ_4|RQ*1΍XhN>.={+ }kޡ_7R_ e7 jdls?gxD%(Z '.Z:>Z13W+o'7#B-R@kJnsj]HpC06uV^$ob`[g !c*(ڽ8~"&=7X: ܫɳG%tOd bK?Wl)lQ]=`"[Gzmg;Ϫzm{-Cȼmj|(/8s7\,))J֫*Bwjt[?l,)`bPs#a=ϸ= ǂAD !H99a9ta55aU!8+=\%Glr%UDC(A5*#mofIY 2Q%4$8\a%ch6:PkjL)t!!fz)&oSBX@!@g@3bJ3[<mBlB1q/RADA׸do ff̈́pck$AmA-&M4mNl; "K^:->՞`9Wۼ0sq|z^_B}؍ycb0'ҢUV}It25:VSS7_c)=Zwf.liW WGۼSJ-hE?0&KSiJmĈ =62{ &ijd܌$` ^*TuFFow~"$ƑW5U,?O2[tggz`f"1)*^2` "(7@;/ŒDt!JU [yw|OygȓY̤.RP^hC ٪XXŃsXϱsEu q/iu+ Cw|Kzl ᶤ&bM[O6硡8Tm)CKp蓅u=_I+G`}pݲ+>(F%p5^Mv#,aƿc½ꨨRcьTۄhFlsw^ajz&6VKNJr.v5aqSŚd#&'P# B@H>ɇK?{~y]ղu:=dIVqm6MhS,:<&cGL` iXSt"%ѐ7W"B[Bz *MU+9aX)A6'-{`zL,@8MQ6W|jܐGҙiDF&lPPwF:!XhlOY.)^e@)k-@ K -=|%|F |2TI^=`yXB (UGl}ĊU@>l;9<)* [cN+`b5_frHJ9#h퉰BTFh{қXԣbح(B"lud:@Ggt1H ީHs_%s^a5Q4J ۺ;mDB3eDl I@"z|N`7d _ A>Æ^˻HN\Z=7 -kXRU$P5(RBCbANiK~F_-՜aPsX( nV䠴^!ĂQrZ 7|>ũ)spo.j&>IxuPXlG81$U#WjQ?yա 霦)Jfke(,BfGsb?x=d`8[d39;E?\jC@ "2YwaDs¯1Za;Ƌa/[~zEYv]s:9'3 jth;ja+H5ԄM7{'4Mf>ێ ~8(СjԜj"KJC([6ѿnݗ;ܞHhBJ$!R 04׆j┊"h"+p{WϾO~Zn_3*X@m;\}=>j a4<ƀsuȾPI3ݳdZKs|:Xa-tqE.g]RgJO gT6W1rƯjb:b84ނ`HUIkQd:9{MY=\XFVlۇv >{ϑllFUxqߩ7n%I\_I`̷J(=OE%:D;A +O rD4ռ!@ˏ]j'g'vJkS ;4& +4|Md AU6ukQ_=(,Q95?4y~ɨy'gDȒ]=!>f8ܦ?#lU\n&QC>]z ̟5Nm9w8vH5?e%_̳Ь++G@?NɫU2W@ч j9BTE_Wz xE1ch'/dOV,NFƻl Vahj*w#*`dh /2}N\59{k7ٸj<:eÀaA6‚Ö|c{~]5n%O?7j$рޔ2}\pgf?@,y*<,6{/&덿w}񘚦7?WpMnW׃ǎYٓjq*~ϯ˨PK U|c{从T|~婫ؑH{5 V+-6Rۈj:OI{=xpR1 s $$#F:~mگQhc{[ IX%#EX%؅XJ8OS*NG~r*kuB kFϢ. )r ~;A~!%g伕H"jX\o{l.:*Bn=:Zփ乽 I: q|<1-3j\^Zg11 1@Lik\p'dl8W_x 1nĈKs f`gh?fAi?6..]Cgi;~tFkDBqV)gj /87]~5}a##巪ṦEIe9ڥ 1_tav8.(HHi3W.no=9&dRW ]Cnc8UfQqZ4LPBJsg'" |- /7M3mkUXy'claBG/#o.85<7˾+,.Oj.਑~3!OS`sni^\`/,֪b0`DrQ'O4AEGg}*{霾 s, yg &vď|VyO/S39fk5KC_|8_|.b=doP.M$'Vr x󴙩0IW?sbj,6y@JHUR0/\2<9<{G֘Uڽb|oB>K%r2! Q\ϊtAXFb_B1b#c &S͗`G[:خEGMUE ngX 3RT`~‘ MaZom ZY/.pkРF9$fǴ_/{I ":aS^|D_oIZr [n,υ+%YՀ}wGai@v #)$2y|pv/>/I:]x\WlSheRg/H vv/;U${H ) ]텛VG#zaq"xh \^i_Iyfɰv:vm'*Z{`K($:EOyQ-)5O!+"u!s V`*cv6d= l'A"G3z/Ū&DI6u6 >Wrhi#bidEKgF!Nq59nj[z|Y c,:xG _8b3c$D??Њ&pJVn`sBֽ*Gn&[, 3 tEewx34,.UEvql0 -eDiܗyHezȊ)K%01>|[7xXq44NT Q}1gk@F,{S*yu{=u8}ZB9GT\e[א+N!U5+7]qcw}F>˷ ',\ 7gKXXlƎ`P jvz15.|4W(^+G*u15ů|k b,_G5Ǔ N`MPXXJMH b&FG|t$fɊe[0HI]Zpj(ME AXg*EmnO/AJ3pB9LdVAU1vbQ5SzVSq3@^6+qniN/noSb X4X|vaPMNLkWh{[T E\ԥB{m6ѝBHq(:o'BDnZ(2KjkNW|;*jIK%a$nY0Dcf&r̸$52p*^XMVtML~ZҒf$%gJGg kMg6|29&Mք33S$Z2yNѲtac:s0br$FN g8kbx?z_eA>;q"wasTA? &VP<]ӚݾTljCU@L1GuJY  ?И'hWJҪt<߷XV~4bOfEQ BwZ1ʜS7 8kz]Xe-7mFF[v˴Fou yn3*[[ !I63B VONZUXf۾ ƋqϯشH&R6E7,?Bjbn};Jyz.] j//Q$ْQ_ݡR !=\  gIgG9QP~ʕxGD;ө3 8[b%%V^SB{RIq.TM76ʽs'Twd 2&uVKuY׌y\ R홆د9vAO> vLx7-if ?~xzrLrc[ G{j) րˆwoi|B/c`<C3]W#Qm])4L"tiDZ]QT`Bg#[BŒ${OȐS"B| 1ul*4UE164\BNŋCC_\ SǞ?y2 j?DmEbMG ;^. n]JE<5ᒫ4gCvɡ 4Ϳo͖U /acfH]e0Fc vv@\V aD֌BF~qZ2}v?8p?!ZjFs v+ch\SWD ^2Z8y|1Gd}.54AoLFkL_NNjͬZQHG6&|! ,ͭ3B$B%.B9=ŭX%F[c͖iG=V+ieޯOvyNs(ݷjLLA;8bVH| :"Q4X\,m ,R5D7Wk: .CSWE PY2Or<?Zed!Hdr V9g#yR3++|9<%H@S}_{Z|Kj{$CPc:0V>#IWa+4ڐ*pBD97Yy.3*mg h=E%#{f=F'\J7 hHV}b@6^Ua'~:\4hݷRkf帵YzB%"6$p "; @k32^,Jx$# VQPBBuޥyg Ox!Uꎕ3TS4dB6(H,cƴ2'EL'6S:k|*h<%G`4gH(n'o9SBC1,,ٟiO-5)y BN{PB9f2Yq'HfF3p4g"`sx.V40IHҳgy/ h0:+K _3K?b,IO],k/cB'X\{'ӄJ8Oel'h$4޾1}1%\DQP% ,b r>hrq:` eIWq^}4D6F5~Vl/T҉?5ow쪜~[g*]rڸE1,IiRQ3+wvs'lzt4@1J*gT$XH}Oƀ#u7PaCm-/ ]w1o{ɱP""$ª;Zxq]kS=Aj(<΢\="b_LgՖ?w|^Q KFu6I//N[ϙ?vqagQx !p,a #Y^X>e;37=@1 |OV_z&&|Vy槅9ȚzN .EY {5qފ5r8ՃmTrN??D>B5Qn_LJh4[I'\S]3 w_Qm,SAe*_PSKKs+ͼ^QUkfX$D%+aZkڣ8\QӉ-7 Pz(f7K5C6eU=m,~w,yۿq%Jo%Ji|.՛ ?EamN[ijyrAx)?Wb7˨COL^3 ƲEQiU ϲ;߷0N Uh(b drd~-&ٍ9/_=va<_gBj2uW\yUmKn=H4Os5"O%St$T︶9YPIHh0GeVȥuda ܝ쳀F5|/Jة$C4#/#:g]]U6H >/6:CdKOEglMl]h*zx~r0h -(LK _Qm/Vo1K͸*P! (BB5Gg?8sɤIƎH}Iqڸ%4YVb*LiJkU?#fNE˿vbcGBe_XHQ=D+j=p<" F߿)"d"5?_3k_nn#pU&%BOt8RpKcWQ?21 qV/"b[d3;7,K^h~~};'lq;MN^}; kf\I{{_so#`߽Q'Gz}×W"R,YUӍ|ώ|4N_ j;STf4IafTŧs`|ь?8<} tp'V3vy;/KՌ:#9k̇9^o%v6zο b2yn<iCtf?mpb6]I>~9Ҵ=8syMoCf#ytm>xt?>wbku.Lpk~#svio*n,Yjp.n\f0?HyrlT+Rq3,X^V?H9u~ ї9^=|0!sRHܖg'81.s<^VYA:V>{}Rv H4Oj{RuFuK߽7]1CUѥo_4?%2dƎKm}sE&'kBfX5MadK={]@6<8| YsooF6_Y{6\4fE(C·mgk՘j943JU)J'Rq6IWRH" g[Q3qk$:t ΁b; :*]-5d 9֞El3rVk#ꢥ5շ0;V?>^\G~Pti6])znZcq?嚌5 3ib{|1:W{~cnZ#dxð?q:t goߔ':7=95sC?eo_KMvRiIr:[ Uhn \kYw%~ƈ\g.ٗ?״K6E9E瘝jej'L>gvsr]Tٿ.?ipPd sk3Y&.]s]19.> 3sl 1qO_?m:ͷ̽zoλXpV6;zƈn͝,C3sDx[00Z|82]~LLNP€U: ]xf8ix厂ܬ8#&17vfB  $=u҃Q 9.#f,8 O~ړziO._ 51i3PPey`7|~d]Mwۘꮯ=xdoGsqc'5r<Fr EkxM&|QxIS}[>{1BsfxW]w|oI4Mw*&nQ H'2?j9lUkdGܷ4hv :+*(;:j$9!a";[^|=&MФbtԨIvKЪ5>*&n|qgU&Jk]a೧+&ы6D%I;4љs/a |28B`D (IW@x<et8s ՃAv:[[bgv~ h@!Po7]0kZ6JAy} wx@7?_kut}&]+-!5̚!חmc< |>zǛ&^P)4Okt,4toTFs7TN8ЉtB1,xJMe$Ԡ%'UQh hncϡTBtBCE$d 6mD2 )~G.0"91Q4-Ke{a9K"8A:PY:7DSuqx6좰Bt[cv+!X>sb n=K?NņJsW>*mC"#x(%3Bǀ7LJ'8IynϷXaDa`r1zZ%Yk 9ljU1ֺl}'_FwjբdžmMr@{l3y'{%Q#@`A$]\~$gREQEPC|єa"%*F9h~n}f0*dDEo I)dJ|˦ ̮=2 "̑sQ:+1As !T 7JJf|Ko"! 32yμކ ~o93[Fz׊}[?Y螿4/a-R1+7@5Z*|[?zto!Xm9:va:+PԨB^5*2%sSmID4.n.מSc%"-j oLr4*ӃxsQ6.P3pn=֢ Y+1q۾;OJxMɓLWjȵ%! sZfskՃQ=ۯEQJ'K4*N掿uSFǗ0) FٜF`]\cpCSRᇧ6I8ād覸 xYP0T30:Yq@0Ln T<8ll4eZֹ(a"ueI]g;&Z6oOF$Q&^#3QӃ.OxGT%rf%T R4/vZksw9-:|2ćuujCE!EF IȺ]O2}c MO2;-MFUS*o/9x`$*[qda<:4 4aI8 IXk<|w[h@qhdCHn?e-jx٧#Y`ӟv3؟~/aP~Xf/}o1&?XO|{w^^Vkyibsv 5Pֱ@nJk/ؗ*EwFմ'P8 rOJs]zjJn_ח*nkօ }9ݬ wj1׾ޯcflO7i_x5YGGR o1)Q ҙz=R! bJgb A@άv R H:&߆Kg@*!R EAJg,  CVj$*ͩUnOPvkM]UBa @Ґ |< ćr! ;`i4hwa8HVBB†E 9 Lc@ML‰=cX@FG !E`SDVPFxcwSUJϴ0rJ1BDL]:\\K;Z.q71(Ŵ+Ԋp쥟SG^yHn˭c{!}=H*"+BdH#6݄8qi{2VsC(* dQZ vrnf;c~?"!c]Nmwa/h ~@[>Α`vpRաU{kTF=X5hslYmUl+.33N9F " Az Q'68|Rٺ6]agՙICKn58m? U?W' kb4+M!ty-dϰVqy3*Q{嚿S\#"/=5jsQ/8^4lxU+ZnعS9b4d|bld˦OXh`m"6_ NoFd~ILnZ]C;_k_[Tv]v!k4NAڵOXU.[ATr<p[f 'ʂ 8}RO/M:Xs 2h6n :LhiYoAM79?#}g0ʮ>X?sp* 8[}]FÿPl@=.Ϥ!f9\K'Tza~ew- g꾕tZ\6hjiԌc%]))8͠ʫmxh8_x/!ӯUثEḅ4퇝T'g ?r'.<X/x 헷;(]⬧$ Pb 抙G$LD!A58- 4RAYS.'Ih o#V̬׋53kPr ҁ )z }' pG!UiSg( ۱ʤP>083Gg܈.H0L/ׯf!DB0)$F0PȻoJ?CXԎ`A@dU,W4+lnϿ!/9^Ma)p | 5YmL+*G9k[?xAJkbBS&%1fPmFMNx^W\CoJWGRQÇ= r3r3yM^ C7{i[ 0k;ȁd?"Q6F*1l:wa1bBvEn˛/nW;F6K#mFMn6n,Xc}NnxZa&F(R=+ޘ7'ɶ59erhxuYsF5j(*ѧ_*XfFkí|&EqW͓C[$Rc`/-CiQͨɵ@eu:6PEmIib|K7qfӟ[~ ݏ+60 O~iMW}o+,]Tv^;GFsziⱧX7UX6 O>}0X+/l=+kZ~*Fh,@./cRAx'>.74W6a`kXhCך3%G-ygj{|ZJck؏%Lx}ϔu8[>Բ7U=gsD&*vk#L~v35S-NҦ/3i8PbQ3XԱ#h;0 `MR{L_lx0iQ4 +Vb1 ΄Jn>Zh35EוZ14pݚM HiXpDlϞMzo4'nDL$5DŽhvHoF+wL3feQQۍ};VC܎ r>'o[J!o8({H"F,nUjۍUDHd~?ˬy O/+Nz!m1G}5Aۼp.0YxvXP>+soE '>w5k_XR"XҞ9EIfZv؂xɓgˬYw8^sįG~o!>p;7Av{`KTSH?a^ЪX:+ͣv[PfLa 3k쎫R 0 T;pUUȧ0jg0,U)RDA@Q 3k0i)THTHޖ]a !fZlÉ ]@ XzhBXghdO*F.r3 \p 3$XmҐpgP$;=D~8 z$p~dYiM0+8wLA0`35j/%LEhNޙqﱕ;^86|emޅpҕ_rk`ӋȴYG{n0}6l!]`5$ 4 XTNil=g˗y~6܆eG;&V`#+Ek޵&f'"n4H,wts'?hx|Ϳ֟Wdb=yeGD[m~/r5UQ=pp2@*ۖ5r"O銩\2uMtn0^ӳ,Jniͯ Oz`y{"!od5gY7݋ZT@Pbq|eSZ"ijűG{̐e iM7F=ATM랰u#SDSwq$y4'OS&H**LslG Q(%zCjfd.*pRmU*8M^jaD `R_PF>|u(?Ou}Q>gٸ22kd'/:TE쾼1auRK[o4iz4N o€Vy(.fdmGx/5&>^Z:Bo7Yg }啩eNۓޞw kpm*p2^~a";K14ԐU< '+5DrG.ˑ~G|/}/pr *!HDaK+ܟ/=T}`x.%q)d% ;'EfiG͝ڏ$ty]+m3Xy i; Zʝqo`$ <~ʷY`& Mځ0@`r5%Zt}4HpdEVm}r`& tY$JHh!{+|s`& - 0aF-f`C' HC@O`CnIue6G 7oEsf%`P CQt. 8YA͖H@8Ӏ H[3^a@WXPh$$B)l83y@Hj1ͰjڰC߱/ߑ"Æ|&h!k?)mv:/ Jh H1e~ `KpBxАw?fqťwյ(v\J|XS.Փ'l\C]iW<NG=:LYUu6sb*Kןm0&0"x{]w<Л6iFͿȭ>eY>˘O)-掛-}TEDE ]O&3^rF WHJ#)5{mZ:s(T搛: Z<[Y{;>Qtg+'߷ǽ(&=|*W\#9y>i4W/*016ozSi>LH+ 7:dk:o4&\b+d KB2jPX fTvOku/]Gm֢Kd {ybJ0Ɛ.W D\4:{N/8_k"e 2ЍIܭm/zښw)Xqm\:&1%tDSFkO HCkBZXo8?ww3wuzab'˨wDA| ?~󿱛(sى}d˽ºeX?vo$l%~I@ /~ BsL,wzi1yb^h] auOֽp?%7V2(zob >]`t&V^t]ӛg3IO /|d6pe^:چuW}u/V)K BKyjcwlW2Hm]N;؜sKJXmaEyw")h BQ i&$q'raz.K%JR$"XyBI)&3|ʸ,j8lRY%x]%IX#PBIH B8,ʜC r k &DaZ6mJ{ DABekyLDQLઌ]i-|e| ^&sm0a?z"k@8Y\R"ĦpH@KE

xi^jd@ jA Ev Z*gG8 GURℿ賓 FLx]]%2mSP>+:T}N[Zqcv%?xiA3]%Gj|(P%ցW{!HytшI*œ B8~ACf|:ő PsILޗ澦'zEGL%`.D"ⲜH*{OձzI4h@'"4tLth́ DLq7JXG0@ XD"dTI:@Q=}=411uK $MUs&cfFe4-26< y0Dzgt)פL{CBHǘEQ&$Sxq'p>N{JXgJa*54@B*!:%m30xu YpMZYQ'rwHP͞S!DH#=x& =tP.5L@q_D|Q`3y.WuF~QW=5уb}E25PMs,oٿfo𛏁kKZ Y}<$@>rJx'%'"e`5nu@ZcL_z喣fH^*o4P*L!hU!wT~U?rgud0ZA+Қ3 n0m/M' Z8X@ʡ5*7nCsmiMkm a {) zXw4Piك W5T^,_ @(XYV̈RUPK>hHkZg :D4rIG ={ڸ:ǵ٠<2n0]+I&iMptv%@A6F2ѹ79 g]s=bs lXl ڸ0(i/)F Ȩxܩ녏\ J5/Y2IԗbؤaFQ47VHkZٝO9E {C\jO/įM$-A2@%%J[Uܯ/']Չ` NH vr㿒P ;oA=Kߗ`p m/qӟ2񕯝v"BJ+J`Dx䱳_YOxw%֊?F(8@i-gg _;BH{ C_@w3A[WʚK}fc|8^`[_?MB̸y\_`lx 2iWtSQosޫяb:/W'\} ]@:]ɎkѝQdzHka/Y-k~$a'MkER^(s}"pI.Z1!^W Y{71JAq-V(bymwh2e+_|{IƛR`8)q1k('="(웎k2RZDqD@ N#f 8!J ymO[LXKѢ4NzP=%(o-zubpFϩJ?TǮcCڧ:#7ih.>1?s h_m^-4[bC_ݫoԫB3aխELD kӚdwTH;ky+f'֪;|T9n-Iue@2eF#em )⩬+gtycX38f媰\J@Vy۴Fe'ڲ~IB%R>OF:SYIPEd UtLk4^S@fAU wf!BG6OGօ^ڵ0VA6lN,+MWD D%OmB%F)-e>n>vR^=_&vTPTPk<]1q-&(pjP+4kJ[[8GzkK^f{ { $BňCW$1U} \Kʁ[CavhYup춄T4 ~ςR k3᝸U+&DlUS`B=Uv Ga~s#5.D\A`)]I(pH %nMxj-pktɄf{ޚ;GZLNpC$wF]1Qv1U.! WAD 9魢EѭyY & (q D*˫ G~tc+(0kHA!)1.򆘴, SXs`` @@:Due$Quނ'iq & H )!0k4u_'\NZ88 5 )HBC+ ֮/yzF/jaC@"MΦd@EWؤ+}E=\:jyo']PZVmE${ଳ¡ |eѷ_pci 8{U]hL6UB={ɢ7<•Kڊ[ HyMldeQ_׆GkB^:`Q/n ⼥_],) M& `9TNId{<-ҚmyR)( ?^D S`WBAAa<>S^5=KXЂR_J4'`$TWT YO4پU˻k,<ǚX6p)ΣV"L;C?[cPm8#:_oYkTul0,MR݇(:F|p \r|TH4 0j*mǒM4XXT 4A[%aq 9 nN&b0N 7\Igr' 64 [ BXB]"L392X=p!`k? >j>2HTF̀0V5-1% (J^2r!Ȋ3[t65m.:DB],GADK_ks׉ؐYݰv0b7k&dv #+(·;K0T8H[K1 5~Yڴ6Nb\͹b;^(1޴^!1ĽJ*^U2ķ$VO~<<ݷ$K֊䆣l)8Sg,Ƀ0w鶂6X4pXH=K;~2EZ/>Uc$w<!a4տ;nȨ$کxeoPJ|e;kNk&,p,l ɧz_]Zgqol)~wZC/IpV(51 n{OTS\'n6qa %?TMfš]Fn c՘|HZiozm:XRaTKWk2?߹wyx!R;"5a}fOs"2Q>3}ckd(sUxs;ugw>Yv$mo~Ce oZ/v>WUG)Pq7%vRJ͈6]ߦ(v~Zόiֶn9#Z[#U,3/"ҥ?}! HDlhD#ҏY]Dgsv&{vyqκv[ix H#);>HwyB aafvss0̙%#a]}ǾPDv׉#@^#<#JΒy.p}Ǯ颅BXOtǂǾ A?󟽢AyJ]r"#Z=F "4F"o}ǥ4+GO?8xqC Bz =av]s \*4hNHg-u .:I0<F㸰)hԷd)lI`]TJYC{N`=²)rx%RH<- J8I0k(O VXoJ(.3 ts&Т(`֐ I S^eϥtp:A(!~ݓQWﱢmY +SST Fٚv[ֱiQ-݃giTdrTZY.lL74k]QPZQpIGjWgecW!6^Xqk&R޴{muHf !E-q阃#p) ȫ_Fj#GU,D'a+^%%,֍: ouJ(Ox夵;[`oop8-ݸD#&԰Q@҉(\EK))٥'8{ZC8X} JVnXq~ZHSJ/`\hjͬ1;MA>ԉzCp?7v7߈P[]D9q(3*u?w& /#(]HG&b`Y\Y*rRAΐV+vXr7> 5>C%J FĒ"Hdni/E}:t`fh!@b,jH|#%*ďs16m'n?jl#o8;QÝCP 6 tǕ YPxFu8gߩW{t>i'[;W(,B DI`/(^/!g6>IӊsuNN4lBX?r=#z, rN;Of-͸xsG:SM2e,KZy GRロ{w.TcEF.ҽ`NY) 'SW^w#~>KؠHu0k-!A8O֪J/?Vmg}m%pkX^c-!ZJs9p5l=E%nm25PbFjʦWГ ּA^7wD wE,HA\HDp r `bW(bDJ{WKgq]VK&l @.}0:vYNZȫ`KDK˼Mu!la"yhdx8<~uzD}&a5t3 IU:jM H:VKm2h$̇etKp0@q *鵲ͺ40 3wdo5b.7 ~ rUᮔ|A֡!gE< )sVWƝd۹e c)v!24nqy$!)MpKFX.>hW0 fPJkXs  {/ʰ  ^[wWiyNI[W,O6VDZ?;uS8{hb_u!@ܩxu03uhΤɰTwxL hv%dI щ#<a-|*OPBalW?׈˹p oMiTbZ'w?%2 @r$`OUWQ7*O}apZSRuTfRdiA.&AHpn#喭o6gwj6vʞGZy"ìW' !ef>U fiNn0kN/UsЛ4b.m0k̀+TPXy5a֚N 9' $"!\R~wR (S9b8B0;TB62aM`(*B?PWl0dq69jF0:͐xr"LX$CM?Nit&YFM"2m$$M#iJtZɷT >I ?)EWIf""*jةO7 ,4N Vrc1EY;:a2EB :C)H5 C-ks"ȻHe8{$^"ŕcܥA%iHA3`DkmTUĕuqXܡ-iҾ>#-9ŮG3w;hYo YQfu_$pTBs^iUM̻@Y0wh7Q-oDrWhNn!xoAUadrVI\-ACRCAJ-%P)pUKp:Z\YK"!ᵈZǡA/wmtK!UjmІySgQĠY2$,SPz<$96Rvӣ3[z%bάkC*W࡫T3 AUZZ. i.sՂmK0P$LtU[$7$klD#"pW?j]ۖI7MVoGbQ'(Ђ<~ʒbI?v n4D0 %)P@JU0V7lQb7%ޒU%}yM(=0pgKTx߮ooHyXtIx3SAǛxI\1>FM̟-jjgCc)ûmF(C h}AG7rɒ mrKI%86|X_{jY,8YmIɫO"D, >-K5'xD0@`!/gO.4|ڊnͷdG8qL/.q&xy&]ٲSl# 0WS>v~A{Kd1ut}zTÍs݇ujNΕ5bw9Z/**\`ͥX+z$&u]?c씆֐u@{2ʸ,(Xr~ΐJ: [Ď n}C{xr7p3Q D* jpŔ(P!vlķN;5?޳6r8-pj ]bHUWN3- &<*oAGb$+9`=F9zd.D&ϰ)u@™x™jg<Lug"Y>)0LLayu ;Ԁ=.i["x==3_{TKhfH@7v ,8q֍GGm>?VΗPIH)f="K7GBS<+@QAn%r(V2* _;g=b?f{`U?~`~kW^^tɒ:z0cc7Tɨ9R߯f_{̏t(C6U٢d8%[4&Ph̙6^6>yA!f4i0?}[24]y^K%eJMx]E gK:jȡ`?SfC+mI_멦Lvx=y>cro W܈t$}Ցg{rṱrv'gM[N+^dZa{w޲ؿjp. p"^|((Rq0צ4߯S7.4O*T*i4j:[\NE6A/p~a?B0hjur~i lv,m M6wkn/ %= ٰEY;Mٱ;꒓`+@UC#?liT/9e{bI8tK :e ;c[I|^Q> M<'$$aަ3#<\J (qV;ys _|o2]Fw_q HaϚ_F@PY!YH1"*U_y g+ܻaw֊b# K1PTw>a %VU{y˪Sg+>_۳v8ugDg4ڇ\B7qЎ`)oookJBWD8!w1"@DvGs(q;+]-:,iUQxjE\-?9i UguX6^iзBhW_XpQMpGpc͆v.@Sx!rZa?^T=mG(`J ҷM8*W=?oAsۏQ8)1H`Z rVqW|ˡzi/MkwlJ+#Lp\R8%, 4qdyem^gESS{P5]))$D8qomƳ[4|qի+;Gh'KuCfe AǗ;_ͭ2j*5|W'H]Dbj 8=j.yoKyۇҍ;=]LZvC4*zP7^J՟J]$#/xNy 9i d3'g v&=Z}0祍¶>{A/5ioGdAʫ0āԁō&/KǪǟxWSidEō~ޠi.ևyG* Rcب|CJ]AHڳgĴ4Kjyco?7?8$QGa+SLq{o#SϽ$\[?s+¤ll҉Ic|4ˬy_ϵҸ֟d*q`߿\tkP/ twg̚S^9lҜFBґL>9 0xQҡǗŘ|e?^Y4O&I//K9%p<>\ۼ2kO4╴?VTZ$, V6[NGE0r.GVZʚPu7revuT6u|Oi>Tfܨ TU"q)\buk]I-w?zˑx?T֔FfDC*A%VH†?I-{h{=;a WZ# T4ճSqŜ.P @f6'gߚL8:v׬ezSP:TBan,M GeFr˪G;đ5󖢏]4 IUr^b9ja" ݰdif{jO@E\ )yg~}='nѷhR+gCwJ'?f}9)>5ztU%@VOzEzL0@eSgh/fCC}.2a)půRAKwSnmi󟝜+C]?yE,;7 AC?W<%eG#`ф$,ǒ:j46rDk$&  'El:]ڽ6] *dko|hӱWei`dr7㑢a &pNNi2zh΁'~aB[`_jk|@K[{ߨdǪj6 `tV<6bq2BLr$3=ZniN0jJ?ձq[(^$i'p]WHF7U>Z΁nVu/@[[{Kz#gbcNҿ+F;Bɒx|8﹨(G7s['Km<PqޜpvDۜBЅHb%KGqFG<ƞCU5klvUfazEZGPAA2 zG㵃^~['O|b/@TkFy)H\7!yH6JF:׼1Z()ul lX%M+Zp7L2I(l6tyYJ u<#!d VĚzEΞA7Zgǿ-o}S~K]¨#&]ދlfpZ7]iHVļ^4q=0TѪHZ‡AeL-!%KDegb/_rf Upk<۟poĸh$\ѐJaȑ[E˦ﱷg=UzW*tf{̿8aJA1),+Xk*[3dᕴ|__XX*iJݰ5 .M\gd'7$$+?5Z2-*d4ػdѥq*ݓ#20j* ~la*#Ph0tϑ'xc O9=Em$V?b9`j%#rPTvB ٠TnAS}pKe1Mɋx2(Qo  ܡ3fQJV2Jk`c$ˑr:BJhw5!$y(HXyOV4pyQn[]mX Vt6Pl\j0KG%ϧIe~C٘*zL8jFhʒ+ K5 |wӪg^n7L;<2g}b 293hYWpIϠAjT+3daBIn2]6PPZZ)3*=US[&t^Wb=$//O;zBa:(cH4YQ&5fnX]ӪQCcEENrrɴY D%ښ8bFW CTuJg"3t8/fIX\g:J93㌚]ް MVK:voHhiZ3YɐCoG5qR{Y[8Uui!p;X`~*C hBx¨x;|yGg7wRW ؜x^18 liiE~Y:lJ^w=̙GB!9p^ Zޞ #:Ol]'yG'/W ) ^K P^}픻Ns ls`]GY \T#I/|(X+sß򗍜>{ǚٴ~Yi 8IDh $%ɉRg>x٨)jDG؅ A|M>{s tsH˕CEL]2Q') #;Oq.R'By5`87{#V_FܺoZ^s>)R<e _ګr'43`VAX"vg)/F6)F5 W'\*hBHs\Hvh2umRG#wt3#.5s='/"HD(L\"?! nW.Wa4߈utD ,CS VވqъV>bN.v zvDgIoYNEaF$C5E_΢95L 6uɟච)l>nYAMdy5jS)]_ʫitSTM[ Xf_at yTTbE(v%Ya0ÄVn3k;,u4$Wr/c~ 0HqWw C#^xGbՏV܂;kŒУ0>ĸOVƴ'+63b F㓴b;RYUb{}W~y_m;w'|r/w>Fk 6蝦敖h/%d^h=eSY6%ޚ{yxo_=O<>ѱ]K-~JVGc=5}ن<צ1Z:N23j^ntrU]fc%)w9Jޗ3+]}c3k^n*=fř'ۿ~ߟVD3ۣM㫿A:آ-W2QZn6< 4OTx9bˡf8dv_ױYtfRDJF2 WTDSID%Ф yET~?ycDz7ΏɋvQ]?gR:lvɇb@ZHSE-b>p n#ߔ Ð!)BcpKQٽGFT¡ :ԧ?}Y盒F1F XFZ 6jftzէ<1k^4u汩csWJ608 ?]dԔ/c08">Ff]/,agt 끼`fRY ? qM??{狁7u=g >c{7 BZUڗ.0+wIEQ]H-c^3Ǔ*[LOՅ{ww °<)Sn7{)U,餝?ϾO+$ƘpO  )"IbݟpЇ;z5fa4t>ʵU RӜ.7;qq̚dx:74~[/4_aUFi$SYQjqs1T0-q2A e[2kJZ2pH%, 3/UY/jv8 (<\eu.x"2kUr6a@_6%twYc)ObfD.f[*rYh\(R4QE >i[CqcIL%N^[vZApֹy۟P0%TU^v Bp|}g,5}]6J/|"i+/NooX$Ą8yՊ*HQpHw|vO&%sW Q )gf󉇖XjJ*Zx/3vcC:4Qx}GZ_F?>>ӧWΏ.N{[jG\_5ns-~M∕9ĭ| >^<x3v;Wm6/Q9}O%?~EXLPKτYAbKW=|07OF!~_L ! k;c?n]Ncp螰j6ѫy~.n|oMG gyW~]'O˪6}ϦxBS6g/^pj7|#{%e3Sm˨n^߰70T8]<Y|G>\ BEd3یOSEMr_ŵ8,M;}:O&/"0ٔy$"6Hg9w0&zij׶uqaX|T;6uXIc3yG\O+aItV9+2Pq_Ŋ>5k8 E?C0zb;2U,tMZXn"T=M'55p ihkq֮"ivD` ZҨ<:19Z܍cOZXi_u49tٍp}IѰG!,l̮me~ȄxH}T #du]IK;"n凬kk /ՠ@Hbu==vr8r!vW]g?}Q> ;ίG4K~l#gFLdq>y# ڠHZ&`-;89{%빌Yџ}^)OLP -EXtrUO]GoO'  1fWNaHX` @hb;GIKX T AQK hO "48AXyUl6 2I"`S0y ` Ad@ft!  ,A$@XG_+:۾F٣zUCeA?[knVlls/Iv9#r6$ָtk͝z%rBj)5,I1dBo w̾h3l̔Z0#k1?x7_^)zvTzw}aM; kӭ5δb]E&Wb@#;}r $3$sg /]{sn,M8UTAU|@3c N~^Ϝ:+4pSq,sZN8y2Ex\*ۭ&Åb[máK 2硡N~\[:v6L3 vh?EC͊CVoPg"Tcwcksm7`qm{F?(r & dR/]fMq :/~5x5l\c;#Oͺ=vV28R5$ MU[$П2]n /77~%!- &„,zBFBa H-" %lb Y  8[(Eȷe , 5,M6PEZD.K ˏZn D=dO[؄IhHFBa4k @ 1``Br,6E/d m4,TW{dl7;؅tOxzfʧw{峹7mcߗT7nOȑ fqIp_[gҌ a^u [OgN$G%Ù4GN.Y|ٚrֶk]vZyp*t?tod:+˖rºv*X јR}sŬb_e|_ĕ*)>UjGUNe![3%_dUsq4e.㫜_񍸘 ^M{uՌfZz}brbhRmd!~-4p OayW~br\Pm&ՖnRCU5T̔hi11M}b3y\XaԀP1:&G.5p|4~Z86ȏq\×z$nhا{b~ɭLkiSw osM9GyOwõ:طuܻƷ_I[k0Ǿ| ,QKEխ#θME4}hW oDo[oggԶ PXP`ĉ?|bkG<5&DC ݵXjYO]gs<0{_`UX,5{ O¤> Ɇh0yh@a!w`\Z<~W ";Ƭua\C`仞5dmKMc:֧)tdFj# * wŮC6,Jo DvNSjנ&)AU w`+ BCxܶDsn@y-ozϽ18^O\g"Q }phF|>r~2%?|%>}OF}vF%?ڢbF})mOю50A{Ѿ`<Ɖ0?U|*Yqjxf'~\?Z0;2 ?l/۬DT^/=Ip{7mń ]/iW7GڊimOKsS| K)f&]vش[6hFN3po+N{o8.n3:na#YԌ xŶޫ;Fo\dw 3/|*>o!H52:Mq#r;pu3tkKy 4{ 1h'f_{b'bd&&T[:wSwC\AVޱgث5Sϡ.EL_S-4׹L%^4t~b1ilrS3zc7kRi_el`Q* ҏR])V;F<| Mv%;֮;qN/WE?NAL!]h[>p)o<מCLr%9vbI|Mͽ!`ɳѪ$fMPwSҸ`+v~0& 5D-O_W>a6ۏ¸ BtY囶lwAa\HCH) 8(ۡwSWJ)"I o/4)oղyb|Y]7Vf6u[ jM8(XOޓ΃e,/'~+o#lL# I16@M`푊nn.JfzCo'6 Pd "HDmuRTD1dn*HM$i Uvb) >f~_ݹZ /o o m30&j1>/pTCH֪OE|VҮQhrM7yg lZ 0 A4 E=S-3rKի:[߼ZFa 0[ |}[Ť[>? z2_g8&ǣY?Ї [oe\;?d>]oU51*;@/¡A~GfOƸuLhzؽpe]h>cqk]}*k%l3$5߫ꭥJڔrvxbr !>?Ɯ# ?Z%@Q0N,3hp*򤾬!N /cSv4n>3 pu}ٮ|Eֆ:>(|RyA36 {}"'sja+^s `Sv,F6̛Zs;~`m-~s8 !"a$bVFsNaGC[&-l6: $ŋRb&HW3 mN+Ά<kW7A-8h7;n؂%̉"N r~`8 Z `xnF cK hё9ĞumÜ+ܷ-ysJRu= Fηݒny7]ˈpVNO#%A>xvWf{ukəe_z{`W(]ѧ=t+^._oל.g6Dʥ<{H6.#>7O+: {nYJfg6rp%׺n&]]ҭ4vM}E隀[KuO5ʉp-$֧KhvTi h6rYXFՒ$I/VbЈwP3\,*hPZ[q,+Qy呿 sRcfoTM~j\3q6^p ʕTWk&Mg>G'P-)q>U"H ҫ5~ U׸-dz !71A+R"TkajTkHS:R 5" A҃TP@'&-`C#b"!6&6"62|T- ӘJ :@d!+lRed],y H74^+\-)hx)8.ZDh@縭 LW}\9f $X(m™Ͳ q'wLL}2NJ /;0HE&6|gm,FySσXE9i XE # kb0Fq'+(XKRD,BO5&YI3‡hf$H \Ą$롋iw FjƹUQz| $1 Ђi^G ͘@VD=oyW B7Ԍ "tb¦H$ï<)̛Jp:yv=Қ:v6qMm7?n# &.7a nZhBWT߁xsR9\3O|*HP1̺K귎'yg:) Qμz@\| m;g>&Se*I;q5 ՙ7TUN\| 3RP< ;q5|pfB5Տ;mrdah8[Z r Ɖd*Vΐ 0 L(N"a-ri4dK|b SpS@%R,pYuAD"1X,\yX R:mH9 De3+oCE16=L4ƞ #p ZG8EXԂ ""⠟YueLWuF%'Lt0Qqud(|v q?^z*?4S$P5TH69n֏㶷 Chk=]dZq83˰NrUv h|gZ\CjoMܖvkEv=,@DfS<ݰʕ""IFqI Db+E]C"h:zf aA b &$B΀l*@|ΐqҫ 8 =^+(t&_ $!ד@e#Ap!<_4p!@"e:(z\'h2*H!v1M(Kh тf>fЀ6FMU% O>E˛Bv1J*h-[H!tӿlZ'.LDSxσJ7|]A{ޭ_|if% /ʺO ѼKdc irfkĚO?s2iRnwtOgl܄Φ3Ll_-|̓j"-A<1y+6si_vZw4hs٠AkD=늪+LYf-o.4Ze@0 fjWLt?H8hW fQ#AG"+|-:CeLaĀ){j/EUʵ Vzq/|kA|pP0E=ق̴.:xقg<(ئ{ F sB:z?^ kx8R' Lԑ퀀D 6&< GOn[P *u[t\:[5b`#tH'"m: !sGN;~3!" 4r@&`]f iGÎ\Il/DL< 'fő";l4~0*;8p2 lW~þm8B]bĹ3[P#aXߊ|f HM#C$0^,=@YBJ8k6{pFN $pGh⡈CUDq^CX={׼~`Pp|!Zc _.'o܇x"B812'3=;?~bC.Һx8wl~watL08Q1ts`ښp3%@ j#ʄ"R YS"qIPL[osÒj ~WEX!8)U.?1U_bx?7,TLB΂ MUbTGJ2|A"igr#j38 02P.89 #h]LS۱fJlFȜbhiHÙt<~i,`L4'!%0ZtQ#4 g O mN걗y'%73s0r$= 8JEDtae';*d?cG{ Ϋ=ws6ѓ `eGBw5QБHfDe%#y2+|8ȇR9_fsqv\*LpJ:*K <8ғA~0Fr,SoCf^j۷'VAGRj9$TDud~‘1^G3{ݹ] +T8$N("!ݟ^31SIP 'L.YݽMWd{$iBzB!:'14Ÿ@\ :! "tUgN+m{ʇ.|־~{9\qïNCe>Yܩރ-T aT$Tw3(%'8… Tb lIB&-6q-g6̒Fl@Y[-Vh=BXI6lӀEWFVmI!-LK"Zi""~l޲="hm!K.3 =TÎ`)#.dE1Y1ďE뺗&UX[Rl:f ,4@`/\YLF7S&vd!"JkSMiK"ŒͤI6k`*By0m-MYI""%7JI 3D2T4#!+ i[$Ta-H Ec}Kj ԴΊiR&X@$B#HztXpg­ t4q 6!H%H*t0돋y k\܈xwZzW烠7,dqieM7?㵾chuVj K.Fmg#sԲ"#&uVj(FwA-2:zbTIZ _HT[Ɵg6p:,.<h$+5a6"lnBdUof'+AbU8)]AJQ$\eg?no&Jb&\lj[RQ…^RhGTj8aVH6j)G3~cM{ݤbr:V۳1y4W!ur yʺPf!hP73y+]bfrNG b!# uf4P"ɢJ(PZ^!$/Ff X:B0$ϹmծQ\KsZi- lTXVk[\n [fc?]:#saKD_䵰>w咐pe5kn֧KgXear++#Hl<̞ ,\#a5k}/vY\rl[AxBعkiqzFV-Qﳚ5뽐T. dJ^_S[Cj3/h{Fil[WqHgUKbi L?IZ.q9SB` I*) 51i~y1ZT&U%ع!IBt>JtU٤fSd*~nbXrD[Ts*wq@4P= ɴR΃:1e9:.ṑDVhZjN?2Q)/KM~8GII2:TK>1:bB[#9qt3NխL:랷S+[)k\a09Ё2(TE$Sex?"șe *9 #h8o-D3EJAS 1#LKA[n[LuV$#bZ߆Dba [K/jJ;{řI*Y\pVIF;wcĮP(41Pd y!ҁ^Tq Q]m{kLi /?VMbG%PӄT 'i郎밉ѹ3GRLNjƄ{@Ds#rDnjێ[(ID Qex$-fCޞSIBA?>MF磙IQ[`qا P%v;ף6Ӊff~]:t-3h i4h8jWŨ;XMl{Qb1⊘P3 X)+ &f?xnDA}08zB% "S%=ѝxD06kyh (`^]]=tӌ"պ, 4'o8::ō 9u6_Lqtt$h81xu'H:_c.T2ٶ 6G2Eky#?MfףN)SvCJ0Zxu'w4k tܦp8@ \fOΟ1I+ٺ a-}Ц+ՊH o>Ҧ7Ő*rH,f =0_;wĞz*K[aڮL"H( AٶcׅE PbC֣/IWz⇷!p}6 /.O86oHmA:'èK؆XkaNJ_%[].cA4 4JHyTĂKc Ϊt6ā߃gkJ93UIpNNyJhmn>7i/߆8h`JqϝԂ ެ,*"Btgk3!-h /Y T1 m&)rȍm[ɯ|DY& ՍL.`1f^gn"/]!CG`R?f.ӓ_r e[1$H," 0HdDVT1.{{>r񱯜(3<eL 1 SD5a&=oOà&)l%bo= HJk$@9\=kgHH!eRٲd0#A5 4HICR( $~ϧ|46+6M*i¿zxTk0!ghF13)$)|%qv[;}c =EL\4N\tEpQԁ%oUXuP[޸'ٗU%dAB  S hB]ӎ'o;$.J0/D~u]- V<:>IDl$tRLF.I^0t0gCwu8#woi˃'~Q'NΞH43:hm=Ea%Z]=:?$m2bWEYJWTkfL]!,\aKO?9ĎeyQ!4_@PSOmD%gweCӸ=NjHjPjѲ,.X;!zrKe266LF^@V;.ˈR }}}>-C=QⱲD* GɭVw`]]v0.=u:\h3m2!$9oټƤ鰡?~i^I@h$؈a1?]231eu4#Db|b? $7UQ&%$#uz\oM5.ngttXI2lMeT'TkYiE%bnv0$yLװ9bTD @yEs M@ѝeY<ɼ Eй{Ɋ>Qxʨ wQCy!SQU3)Փ2v(7\ ᯬ-Չs]zFE 7h״D4{{ 1ǻ:A\g8dz,V^eF))v_zv9k 28!z, ٦VՔ+0@L EXg ̮rP2Ji2AZ\&:%pxݳ.R .+Њ  l&fuϺ;' yA r dPp-)9']lA7cYZGl0͋)]%vx/0iA8mx He"y-W߫gA[Pd! ޮIBu#6gNo\RۃeIR0{QnHV.8rTjz, ,)vUqu""椱HX,%"KSJFk8DWM DPl)&"bª%@47GOOhp BB--Rlx"" 9 AARG8FjʂB:C1BX›'"*~ с!$(*T UQ̨7?]pJn+6[ܒ<,_>',f+[wDLl_K#J3Xkl-= - Ng+ƶ擗a4aGVD4VØ5w5BMht% E{0Gt\zNJeP#g9{/d[m/$!."שuѰ^ %V?BG0Q0ˢG5-ɪW[W5 LTk;I$aKC],4P@ıCPxC k粈fbbZJXs TrJY/23bbL.{Y8ܩ%FZ'i.Ḃ)FtHR+0s*XhbM.s7H[ {5BZE>GC! 4if} I[ג01PSmJs^,=Q͌G1cA*ѰMD& h $p:ԟOPV2h3v޽4\6oXŨ  +?PJ+3iyِ$12U2n_PՐ]nar1E]/VP q1}#Da"XD«C6{TVtξmZwL9y59ӂ',L{jz MSVfOzk0Qɖdl}&Iр H`# wlyV(u 9UXsD Tef @Uv>S=qu49uiuX$nݹ =w/c35Ls=Ji̔TI?q*k^SVn`sjH r{s?d*ɦ+ gjT~%*8vŕ.,QV`a@zz) }q.H* f`}Cj{#pٻC~g|565h&!hW75! Y_݃TBMjV\|,N2+r}q $GS \60C Zx)Θ L/%Ex;>mNvrƎ8z[_}Y/"ISUgSUW9:e3]vxV5SBDN.b:H8r2+T͂HWP:8ԔK_ /8(Uֱ]a  =kjk6Q:ss{l|&|#Q;9)"FzQ-=Q;Hmk5/2d-EmGp*P#eR5$?m!Ȟzos) xJ/sPO5Z2r2Sra#LȰT (kNKefO$&!`"SnP Hc]x1-4*c4&clA5j՘pjZPD],v6! Q XL$MCt\WmҭaZ[/n,+'/+ -5L6Q9-!^.fNi/nSX L!/L | $ vűJrH2G)BBTX}cRF  &,Ig4LҢCNlB]#zD,4 ա4L2Ll$꒔IzD)_K7Xٟ:QR*@6lJI )f"!p2"c ƭz=Ԡ 2a8GFFߩBPЄ!>cOk#!>Vށ-$1b4gT} 0H%Xs[^.e6I'u ".qs)F"b*a6F75 ,AxDD};s捞9DVD`ݎx?` B.- #ňx͵*~= 4γd֫j%f}jK5ɩ!V|jmo8 XDSLDؠ҄pFq# ڒ T(F!)_@Z/9 \s.e Bqrj1@ (fJ2[ǜ=*BbD&[|Ĥi޸YTD@`"Jtk(LOcZsi95w4n{o-S'iQֆ6,6O$bhGLs%|w燽1JLI[3&#$@LSzXks |ګ?sjJzPSG!V(iIhhHͬ-e2lUE^)M%fȗ iD3I7c@mi|v{b{  G& 0 GϰJf>_*U? ױQ~m]քf`H0HF+R} =E$tym, 9:8 }UMwыu}ӃIM9a5q!lP[$ Nu:r_zb07ba4ƖeMtWo;c!P VxW]<9G@.= *=nGݟ(ի?ՑLA#?%.H}z[zi0 [9Î bցvࠃ: */x@mpp4YPf.1L "|Dw캆urps:w>{_o Wv̵QI'R(v\5jr>>:~ Dl{Q;f0zVSIbL⪰YKvQ͓ D]>?(ʫ=" d 9Dvv$Uؑy&w|/jF?=~FՎ G-^>z$1vP/shg< DE/W3r'N[]4ebD*4JcD>I(kx,s R8@d٥Wĕ*缓@(4%[U pU!0#s4fWtypC*@<R! UBW+oO rz^-D|}SQɗ4/"/??U?+Vz~0P|]0ab=DsY%D*:ئXT2/O|~KlR۸Fֶ@#PMQL'WCanRE- i S԰oDmIͬMKVɗ'#"Z$J4qݰ·_~lKg3Wo#SmB466/oU,}A˽-uM6,N6]k<_S+W{wD\-)Df_%>%˃_p?2'?"#x!(a$o$8̆6oCۯ)Q>eⰮK4Ȫ*Y>8]7tl@2Tn&w; .>!?@5B42Xx0cZ7D;?>x_z=  ,3*)l ?<+ܽ[a:5"EL:K@%OĞWL=oTw%X6 $ޝ(l9bDIQ?|<A,Xݖ68RoC;0p.!Iπ&$QBt\ϙUD &g剂LŰօ?Ѧd$Oc7bJ1c w?[amQ&PCV''xP)ihpFF̡6o}W7&&*h BSa2GͰ0.~~`Kg *SaivQhi (!m:p ;Mϣ1պeHXO8ZMY༳Z@BP!͝ xX ՝BL'I<$p6^F$,ͨ;"nA agG:& QaumUH3gOjٹLP෽ˊe߻-~R^:C(vl6mXhgw>ڏ<ղΞ:JتG3ɚm[&:g1D_xp"c'=/- q=NLKα^78'utVt4;s3s#;!UcPP,DGmQV*&}Nzʳgbt]vU ~jfէ@Gü֏0I@Aap@+_@ г^& !Cd#`!$$6!u&Aa_›*:/  oO]$G {5#.~#%:8^OTY !47K)?V4Ѐ *ԻkFT|㬷*^iHo*)$u^рؒzU< ~FDb96@ܿsK$kbMD6cp84cKkKIRG1#H$ز c'`VxHoDhj""F5hiic`ɵ BrC@T5(WgoZaLK$dj%j0ɼ3U`zco_X$|&2ü̪QVN;˜FG)ּp>HMDJ<641 oq^b -Ĵ^$8Lr{_ǚ(4-Uc$4! F\&Epk:*c]TBX2@=0&͖Y~7ʀH7"wOm!ϨcTJ!w޽O<Io<û_lF@Qrqݕ-H)m}C]68Uf` d6IǮW1*;eN֊$bFosTwzOMʚLnOqEԔcx>jpBUb*[#SխkT4Tt&v >j^0.Ώ%UBb NR!wx/NxmPV ᒉ;IXyU-8-9]3ؚ%qALTvI i!87HPPZ͓chU+D[d-Z!;HET1'r&AM["PPZ!$:KǓ0*VM8iNNz S}. *nb$^Q**^ʂUkHa"ź{NE c-XR+dx ETaʜF6 DLb.KeIc|Wi~s7V" |M+yebj$+tz"Hfdޠ6˴ᰎ rY׭LĦ3 ~/xbC(zR\͆/y4z` TDV_|Fs Kxak٫&Ķڐ DsAU 8 ;030ci+33&af)aIA3C) zP\ PQdM/GF$*htuR".&TF˜iPjeˬ5YY**BPY'ے)1o(Gݼ)2.y (`hW'7]lB#`!.<]UyDOMxȔIGp/ef%=]~uO?J tXK8e2jatj"7Z[1,(72v0jjlC8@^d8EddXLS8 ͆}r#è90 $ @ǸXY >KAn(gϬ2 C1-UZ5B TB.Io[m0.nKOW(kl+UR;VH͵5vDKŸ>i ,;ER/>X1'\PFQk ogu㈠StA xޒ/>՟hf觶 e\쥏dQċJLt,]R3 lzM$7e3 &΄g6j*l7mɿB;Za_c-6W- y&JGĒ-{PAI !a_4&JDGtcC Z3k|m|%D 8S_TOpތ1mA INo:OD >˦܀^D<߉[t* >6l 7g` XfT%-5@D/p ZY< Ȉ@:@iAk %~̢DfZJ5heJSDA= -ƞ Qe @ل^Z6BL&E#R5T|%E4i-+-YdAb|.qYdE! 29OŸ%wd,cZGU2ήγl|=H UqekF9N)$xM!+V?ZAЮŰ+2_v7pFF L3e3(CY ^wYX5<)x4cg85j1{!;4ⓗd7`3ϯp}7hp_vU2ł ZS?u~m ܱc9V||odZ^JLlcA`1 .o[q$Dg풾n'><_#rQW4 |pnbOO L9y!.P`x6/m6D/0MI!=T|;(ىMMHVlfB4(L͞~/֊3M5@ށ h %(J77hrIV@m #ZO XCZ %a˵|ͼ$Zٟ6(Ht9|D<5:`,fDMh@V!LGfJm>G"M5r`Up\%T$<]fx8f'2iʪn+] L "!xS(W/g/, U9,8|HftUjA9́dTrl:eժ94 hxuhX͊J 42@?{Iv!I}kZ5e}$oh-mx)4rIz^/foiv ߰F+ʬݨgM K#g#CкP7pD0Z߈59ugM=?5KuQڙrS+[3'/\n)745J(3Ϛ4ÞV҂\V+A3B"x#EՖ*-kt9>sI3c:&PW >*ZLMW">G&N<ֺ=SXR+8Nn `P9KVks937Aan!ހDq`t۬705ctDyU~c|EIpXOͫ U0&O$NR/>+X>Wɴ#TYbU3=#8", ^ O^>M&_$ty1hQG" ڂ7vq`3f@ϕX.#*%VJ^T RTou#U嫱ah+rd36qDQ#*e Jkxp ,lY$@JH㈊#Poa+w#\*جp2A sV]H58nmi1>qa`|šus2v5a*XQ(!L 9Xkl랛L S䟘M $;_1d,rřdz5P?0 #dɒ.2"֤Ԁ3MCF@?̤Ro0HC$)wSb֓oԊd-2 H&U)txK  %"Q BjKiƭNw_>QuNI(Ax Vo!/ӈ~>|mANP[,_¡`;[~e_q\v$D$$O/FT4v;k< 緿Ͽ>.H}mH|tGF+V팖8JaB@Ūc5[P_S].>ܥUGc+68+A}]1I!VOUPۃ,ߟ,A_|Y2Gm=B?;Ӥb$ɪ}E>q{fu%뼴 NT ȝJZ8"o-nyd#I쵿{5yhnuHGin89$:F:{;v NCg~8c\7bYz:JSaILl;[Yn5= W^pv͇rn+ta u_cyG/:{<"t<0e=lm : ;vZ\R[KcZiNx8|Ξ$h˚t*t5ٶJSzl6vџ7F"3ÍNud4/ zg7ˆ\*fϾyWxُ\xLo3 2F k6k;o˾?vKvOQ"v& I]m#i*GFb>w a7?e9r^-Oh}eiLEqښpŴu|0qJ}Wg?ĮrZ7q|y\v. Z.xn%b,L~_ڦ}WI~Iv:߆w3o8ˮ',[2VDESPT}ί{˿aWvUj3ki:..3TkiMR;E|vWħq<]F#U:H⧫q9I݁K=h1[ }E<=V8RT7=x?Ū'Xq? _0zؤՔVI!B)h~O+CY>TO5g_tR@wC@(4 <kQU7ʝ )k: -\ /O [}y 2KSC"t =k*:Wa0A/2um"|z]kM/U (F[v>yZWEUUuK\>SQFK*ŤUdr]BW|C;]:F\KǫMܙ$[J%}@rכK6.^%!!M EfΌ1?Ḙv%r\@g} gE3n}Τ)|̓|DJ;-5_ eg"3>>g!IM7$,WmUhx atII8sf0vsUhXVX%\LulH<]IR΅4D:L-`FvΤM6 8Љ Mƭ*,k[J+24 q5V*o' dlqx׸ȇ+e}IA3X@\A4x4xu F% @߸w̕O}ۭsz'瓝X3)wN28Kosc6|2c9:7j+2[848Ath!!{1/kIO sV)3#&d@*F$BbCָ1y}y=ecR`A28 oI)2G`(mّ5״E.k4bXPk2Xqo~|Tt&Im2дC:̏CZ-G<_OQ TO)Vo XO=4Kppo桇KPsȣKP3y^ ul3IwF T11v/'`upϟ֞Stǀcр:.cTw?0:t#{j `?F)h,u(ЈԼ龿~C л_3}ϕ_Ո5?FS:~LMC8xh1xl圮/ 'A=bBO q$5 hfiB`-gg&pƩ&?I x'm*9͘!y Ĵ;I Al')Ãrw߼5'#y~Bnb;m$@5;f9 h&E. [;qFNn؞1{nu5Wt7%='Wdvos x_r>xf;Ok2??idԓ?0 {xJ/%cs'5ӲYsABM+QMG?Գ ׅs9\.zO|úd[[dyI\PPduX+=[DGJ6o'w=KPۻn}fY'YjgRs߻7?AO' j<ʟr]HLv8V?".?􅪹wpڪZ BivoYvg7 ȡz쫁1d#Zs}ӳ)wo/3u&l ;ޅ??q,Xǯ#1O{H O#3jBsŗW9G]ԑwo:.YM*{~6Eq-u4/@yP1=_/h>Y1FUؽ>K"k_;VL4&K\ Y;c5pU̽|y:_O\}.<( QNpcbeŇ=&zs> 8Lo,I+~k?:¿ywq"!^?N59ʼnht1!iK.ʼnhnp~(_{̍Fe/b2Oޝ+JF󌿹|'_=yD4W'ڽЅi>@nc9ϕhc5" lOZuƶoᴐo)d-.5XKG||SFRNMA6JGn (',^i>JP@GuoD$gDx(^KKMd/lCR w*X/kAFSbbᗈFC/n!KDCe&)~q)Wz-n60gaKx7E]7~]h4ۼ~7sGPֺ4k"sBӫ4U7Ld(jNh*5{(iFEV.¹]h )\""P8KŰ GZ͍"܈I*"҈ɑR4.Qdbk'0Df {ͯ}1ŻrFjgS"M_3Dyg\J}\Q_Ү<WDY).?W>_bۍK;o?5Ib\YT; SCLRCc'}KIޓ %Pi,5|u/7(f/>Wڗء?xR3'7y˱i |zxw̗O&e'ee/AL$%~9VMeoQeOyƯp ~z0 $`Ī+\y놓 F:9լ)| ϫgo jj nǸډ i{0 TCӄl5k:ijz~Wn5YyugéoZ "*嚿ǦV M$A$D4੐IOD4b 3΍W!0;7֤4 фbQURT!HW }~#zJh3k0žI_PkkÝ.fDpPO塦;!:]iYφ "hƉB)NC̺z| a$q4*ymoU;  zDjXq<_|0 RHRm74?ʁsgH連̔{umnvGh*L>{|٣񽻽Gl[hJz2rZ;M{FcMzM{(f)Xa58a_64A/] sRGvb1qzn"N;/F: isӔwHp EvPc^[cXzvgw >xc,(~ϻ\F9x{ "M1׬fs0[/F$3gZw>x"at]Y-{:8_7"Ѵ?gӛ,!gL<Ynuh ̒jg?g"o؆+g"[teM#3 QH ;s2(fiʳӟ((:fHDtԤ$'B$> MI5sth54@hh4 }"`_4A4q@*ov3'vm<4ٙN@7'_"Mp&X$=MxŅXDԧţ7_SSdVJ\T5]TMERI O'ڬo/U3k5GT x F3QTMp޿T=SoP[ VQq4rZ _ DmbK(QfaS'`@BGeh0kIq%Y`Х-F1P &Bk/} H0k4aSZQI:j@BB5c35k\;@ MeQ@:DD w>'o9il6Y%ɋI% ivc|H(FxOIǭk*i~bA7kd҈ߞX•.ɿ+.Q5PؙRr;ܱY;[5 h\Cz;h)a:?eF{͛{c/Y΄dEGSV-ehS8JxUKY9<)_XV2c2b[~R!*M(!-†ZU_?ƨ8 5BZsꨅdhZh7R 騚-eDuԑT\QfHoF-*v Uc?}Gal4%, fMJTpϦQ~o~r*Bi9u5k:JJ {!w|d 5 ֚5'JgUc?`ХOK( E0Z&}\[eDX5[(.] YG| XShԏ2c""q+剿//+">{[}r|JrN|n9fvߠNCwDf3b x%z)Y&3:OUw/\Xkѓ쩝n+y7Nb4cik{/)|]GOZL:q78L89?uqvgZ5͈.ԬQUMp;%SwmkMUo{ =ùT~tCÏ.Ϗ=2s%T>@4)/*BM9>GQ=/(v՞JSQQCUf2 9ƗQq4>xמ)ߕ lfxǁzGPؼ䝦SskTƮ5|m۱S:1 I&$Eزw3hz^04`zd妬1ih,dpJT@ @!aU4%t.TODCUf@thJMEF9LI."*iq[W)oxx,[FK$iAzi+|DrB [[:b]{aU-4+UkilZcjr'EĦq@ :nu۪vGOjm%>e8+yTꤛl%UM5?_uaKI4Uyt+z}ڢ0 f)Z\YjzF NZID-0tNe,jUL;U@^z6W0bMq]sLH'UGpفw 0\H3[mdYڝK?szRz}p?1U͚p5RFߠƲ[V0!9nrެ ƷrjV n6[\98a{{Sv3\'X7kJK~MY|m die&x7$7k"jͪke-R Ddj.'pME6{@2Ti\N=}~'%SD[J±W&Q_~C/MjvP_Ŏ<ԽXj5]g_ҿp_[|m/MY\ps{ެc)G5"}y^ݻ]_kv;xT<\c&9v)Mi_D{kZ^TR7`s< ,O4n2L鉁q#='HFQ-YӭUk>Ԍ&<9۳Wq)ju}3Jŧ7|]_]ծw)ˎ+>]}4۾qg\Nd){!x/a__vK,^鸌dU; #v#I'WOW1ޛNTœ#[o}*k'R3pe8ǐUX|71{guW,KMf}cgKJI2O/?;|² V*>ui T1TQ'6&d};0LO aIiTZNe,*aS)k]:[=Ӿ.ULdҲb5[^y&0kiZ BٚjKپZeG6fRh=maJ.mg8bjP,$3 ]ފ7EkMs *!Rjr4*Ty;eeyȮ ^0m $\JS6ƭ_h!qZP$j:_^.C˯uu-lƎ7^X&RQM:-M<&ވt_-Գ/&d@b8Jt5J}=UКgG ս„ AqE9uZ2kƤ8sRV{޲Uj?/K=u:8%UnwJ,>fL WȬGl)԰G75!( ;UJU_I7.^ߩO)34a ~qvkee2ҒVx|^ö#j$Bd#nK7W;qNPz\e;i@ vx%# " tpӎsKϜm(5a3pe);oM7km3s:,_:6DUס*I%o)v8,PC?ZYKGSJ%c ic=(;ƤNxrH7k2v brК2~䮍"艎a#W!(SKI7k؋W"nW28n2ޓNx" 5k*n)~=O00ߕh<.Yz\IdiZqJveF%. OTY3Oģh%vuYN&Meؓ=A5kF}rEp,>br(vyHN(QYL Ah͚{4HqTK2 B s.Z^8io{7 -^gkT/q5pvc9d9.jonbDYY|aI};T;W!l<إ6"\"[հOv-7o enT;^a!*XhV[Po"Cb˚k ڧXMBAuE,8@H*)RuB!ca:T bOS=3㍟O(X ;PV*̺R,(H0Ťp:{ v3w"KÿXJ%@*.Hxp8gw\D?"{pKv aF(WT pZd j>O6 VIZ V[rqOEu| `M""3"Ԗ0=恐Ggon뵳_qO"bc {ߋ9ïF`>JsҪWaF ;:Ǫc)!>eGyw.(uU/vL G Usq _"Əkl]2cc],Wec3~x>o !G8f\\>_"Y4'=^rUb@/ZⅪÃDH"t HͪT\~{O ҸY̨.4®'nx72XPR2YZ\̶?Ϟ@DMtz=Bj?L/%D/*|e"h^RLk*@p29î;$_' If]*ǯ' V>/5[#쩯jKE]I7̯j/?[l .Yǭ )vO?U/1o:w_XB=+(zy =7w{O{,?>+Ot,YMB (X w?t~~ߕ냮]tC(Vֺgn}6r9ö?_ܷ׾6hoU/g%5^7bUY:wz K)>p.h2UK3.fyCDm>k@W }DyWP ` ]R S˕`>^o D|a?K#1=\Ґ\mh30HZ}`'V^\Z,]h%c2Wˆm ommhh(G!G.zj޶M!pe.]u|Gt579}w7;ǬG_(\SUۺ@5\3ijHӖM7|f~ ÙT 7Fvvw_xvGGf _ȬWyrպAV^1=ڮwxy^|vzu`oTW]i ]?[=:5y Llgj/s?x.4YykQ]v'[:ࣈ>HF'y2 ț +8YA!KX6`'Y>6Ȥ:_{gTWۖ` Kr L)X$:/ec &8iy%xoF$H@'6$r=W P[ t2Ȥ EUz(YS-I) 9Bzиq7c۲g!H8.it iWJZtP lA5[tø PS_۲rp tD(ktIzdKH"h=ChW :Ic2"NyE HV ,km] c!,X5\bl.a@+Z5P.֢`hlr άn}/69=f_j`" ER7%sH˧ (ҖcY_l6rV I6:(Ԥ@rBq~$AH@!,U0 w-68wk $E;HT^ Dz+/ZUO,:A*[tT,\&uޜF)Yڻw T8 &ݠ.G.BB!+,X2'W>%Pި߭!b"DbѲVVb9@lQX@ `ѡX5~Az(I[Nk%Ȟn Fݑ_C0h+7&|eJ;@$k"Y)bsʰi1>߆Di.뭮00fZQ[L3S>^<=htH tN[]]ݘk͚Zbd"U1pXfG DDMGK;Z`TowS&nnrљDF0`$8NnNu`wSgZjhl ͆KO)zw!55)BL^(sC,p:y.ڄl]V`nHP&B\ ](WF.k "].L5X` ڑu_G'`!Y,4Clt.ź-Ʊy|QH@GʼN(y.{|f\N]::Y &sn@pQ?/ߤQX"2f&_fj""X  yz7ߤ)>5V)CɂOPQٱWMM +GDm oNnb)*<ް+(ߤB'KTGHA%(̮ۍ|sHjklaҾMѢGG @M ZI'!DlC rC FR$h f#, F.yΠWNt e|-4;A5\6 } !fquTkQ) YZcKBHAWЈ$k?s>Mƹzћ!T8r_8`}9hn -!WHۭiQkLsgѷ)g_7̽/ xPW-[i^W# hvCCۼ3Ym廣+ӭ/Ry^;` aMA[0vС W1-~3unc~w5y- +-Af"ٓHc]>`4^g<6 ?)}k_Y =/'*F`,zF_4θTۄݪ}MXmYX)ؓXz `((+i& 6 gJIMo'ZQNfl%cEn !УYa,m}=LCN@+f4)"a%8&E"ä#3ȀFYX) ko۰2D#Q!B -o ]gNF@(hZ;9Ҍd-tĚPD-pY]Ih'y['զ|~ҽng[oہG-=svi/d8gm*|z殥>X`ZUs˞`ϾAĕnDwnzS=гΖp"An|ӄ$H9=?7_{?to@IVp'Vv_ol0FBx-g {-z]u9`Cp;3Sy.5>^4SQ閙)QԑuU?&aWF<@wS?ܭK? &:  ~۽&D.~Ʉ L.eĻJ2\P1qDNVVDhL8u qCg|#~缒*A\ڪэ PŨ4 :J}4&/YH ">:f0!ߣjЊq."`Ȗ ?Dm~|XwWz_*X%,Q/E6ճͺ6ի' Ũ!UΞ׈߲9$/qaK {"U9tdT4/2 FS,rbCCOk^#Q~tFX<_s[xɋƆ()pP;" P|O[T#jɊF5ݩ[zjJR` EՌhIwf)b]I tyBrH6US!L=Ni%BC=dk4`6ƚJJxXhwdL4o@6 yXmwpũy W@j(f4]|0-dem1ZB H]#&Դު+%?ܝi_۶?݇&Ch^3d^6 8%eWnp=}IÃBU D.n;ڈqѴj (ú y='BҽRs1ۉieNSQ)!I #pRKn1ǬqjpTJAdd4 ppJgYf갻_k S6REbj8ae?AZIfV/}nyo6iXmc&vp!pt,ݏt& #kQ']VxtZmX {iϝ&ۤOlvS_}k½_e1wޠ^\ޓAlp{ݛ}}TCf MYp}iv?{6;Sw|f]a%M^_s0MLw/{6W~+|{&55+{nÜ&Ppڿtm3Iħ޵ޑM=0r̋A ]ϯi߮מ 69]X>&)9-o6?:~qp#cu #Bi9|݈NV׸(֏2Z͒%3th$h'*\ܜK+0xA퀆麶bq1 0kE7@g Z9@ւ^$$!uL. N'8̔%ylvl`\e$e"]UƔ4CJɃMzkجs  #1slo J Y\ҷnallHD:k_Q?jgVzn '$V5PΘ2 b%֍uS,Nجh H "[WkVhaV"R* M5|=o!4JJ@f '!fMP|yXttXd@0Dc&̺ da&+U]@\l&$HX$Jje7C8C"%^Ino #156k/ \Q Zb $fi!T[Ht@heMTbj{l^{>w^sxox:-vLjZ[crI=tuASMeFy8xx<[k;g* MZS .sI[: )+3 y:'1k=&8i1^Ɲ$վNk V]A#1SGo4RKL(W1stbk֠峒ZyAc"x"(;nVҋ:rTNӀԢvjb"tgi@K *$1"5$kUdT PPLr$2!a:h !at.PYÚu Mu?Mya t+^'tQ*! fֵշ͇0B@Ad5$+/0^aV#mxvDE  <ƥ7kds~$7e5k͚H0Mxb#9sg7kKm\a,BB;%IEAв*M7z::$_wLI'K@mU-47Փ7znV9/ tAK*R%T$[hv|D1Xx¼3dH'rMy-8EqrH)&l3+]yK+rj/<הDndTdeO$E 2PK7+ɬ!x9 K,7CD A%HE>PWk&$1#R%4)QM TɼV TQlH"e=t^E*N&M+:,/SB |M08f)mDZ/YwZPHi!(f e,AdY5սטE%^Ҳ3-Kʲ13k*fƏs J"mYvӞu\6lN3.#j+u)BFba%´hHF`Uyv"u4•Ho'*zs*ӉV_CS0Μ.h @T4~XD U_G+Mhc%3BL}uROlK4'H"B舔⻇IJ<w;.h{d#-Cn٢!d 񊙸˻︳ӻ=(0QIfZ#0_Qaf7_~sb_ i.^f`ev6kF|t7_yZ^4G˳@ ԁI$6kf~1^ԊFèaD ܤzU3l1Vpn vTu ::5yqiR * iM_Kc!aWJI* # \f8A0Vi0k9Uv3iEz0!s1a,Y_uYSO'&]\F0 ~k {0Tk&,f,L,<(w6c;oqzc36ۼ$ϕIeNݰD>˧կۏǁy | 9S5 +Mg~ c%Yk45K JB6V^ytsY3C1"=enXiFut͙XTgQ RhPc2B C,hX:D2,f*&ˉ ã7!Ey,qZ2P *骵r[fMպކ*Xc\5(=m:ֶhmgzO$vٟDV9  <5 O|DM jhJSR_0A;= U h\dJ Y.wqxV>LATDRU肅&ؖqO`y )B7m,4Y!Z[?zp) F)]u#ܦRi +Yio[} {?ApDS^3kj"ko/@0 ETͼN5L3̚=E,w.J"@]F7)U]^3k&)\:̭VL+65Dr}^3kFg^^/gGOD:E|5f:^} ( 4C%Z~t\m{qͬ7Z3|E6jU3G;8 ijZI^yk۶5#"W]51 ҡP@,T~w5iq6"U`?+DSߚ4EwW=jf l 6aij@ ϼ֤fkd*wU]|1qnj"Ǥ#~kLtYR|M@ BrzgezwtQFͪT^mKՏ C+Xh:+3S٨#YaSVfj"y'z`)iU3FS$G 6#ɪ̐*/Xhʞ<%1~BIE1U&j\Ņ M`W 6ʕz22MFBx/l:KSD6Ǐ-[h2Z]o|V3m^u޴&g鑑)K+tمBaNX+1 V掟܊{v"5&]>6ĐZ͕3q6E : I~XLq`&`Rne cq^p8 YGQCq$1Kշ1]aI\Fa@ "qЇMHH$k83Ћ[}9Kۻϻw졙Hj_[%=zǏ1LynﺝFBQR"$P*LR}f)A|א (U&d ]Պໆ|mL}XYkYsO[6іqĂre%ڬ랬^D%)IAą啒f͌nz`E L&UIn;ҏ]В[ZA3 6Ղɡw\ДK*M찖F@Hgf]E o<bQqi)`tTl_-4Iw 0S=uq "s.Zh$ځae$7K)B #G.z6?Uc9/LuM%% x;0k >Ӄw=i^xK%d `P^y$VX HސK2̚1 ͧ75 TE6g`mn?Ro|+%C!DIE E,4+v+ 8em_пQMn1M?r0ۏ;o,8(fT=5=Tj2q.YO;EvJ)^k=49cW8\D-@FH!{qn4~/]mTP:Dߪ(7w]үbTpf%=᎒xGK] ⬓fHԭ}(~ΥغFqֽ52Q<۷L~lRsw]ԥ ;3al]bi Ji*'qˡ 3جgY$~b |뇴O!| AyԈo>#m}%e.z2J:o~XX1sX)$ǻnJҌyxP?mAd`R䩭b&?lơXvкId#yjC! % X N2 ]?@\ gA_]PM88; Cw7=sϨ f(I8ڼXk+m5aEF|WU z!y+;].G@g8 L{dVTz \z{wbH˲Ս)!h z:%2*YU3LXdԎ$s1fYT,yP o8Xi[,ų%;f,xI8PW!s&m+է.3/hK%E. ̱oȒ.pQᣠH֨VR梨sȡ}Bs_b=?#e)l[~JTȝ~tK h2Bl߭(r*#w~f >xť|̕XM$M:|C9ȡg2[hFʋ8c%Z!D:FZM' +<nJP3cY j=ͬ$Cn{%AߐۮZ4CKFˌ'i,3ގMP㟗XLN`Ɵ+e/Gr-GP=_ B8BVO%,Q\|<#E)zKX>S +JtW| H!`NKx-@El&S(X_YlEm- DZ/|T_W LҌ7Ntinר'߬4%# J% &*)Ac!Js޲t_y|tvo2?ݾJ/8zۍ_Gml{/-ޟ͕9)83 Q.(~U;?mwFZjGw/ɏ?9:K2فeaADHK͓xvw_&Y&E7W`k5O^ ޗu5+5n@5_"-5SGтՕ |\r/e!Rw%w~t[i&(TV1Y^͆ǷXi (oː{e46.STo)0pJ;nϽ>HmU/6d݌a#G 4{~rrʝm>56dx7Űaw _Xi& =XUA$PTъl~dt+{aT)*3٠HH ه4Dxcd"P(bS$YkV4`hH^xE-"&R&`d)\$Yk4$&RZ΄0D*b0]ך+IruT]c/ww/)rf_FV߇YkF|1\W3jǀ/Z"[-N DZSK-|mPSʸ|-1 οuϞ.ٙ(E"QM{s[ZknwOuɶUCAYHviWLwη|ܵJ?Q4u yT`;^77 5r\U\krU-`'76ivn_.!+"`fn͂8jwebkՐ(IW. ds߿?Oۥ"c~]>-NߙwT8e]NxB'=<έݙ7я3*=,凥;yvt7]Ye<:|!~yc﹚=_|"g]+t8Jǂ}8 %t+nI|n=h=YճW&5ל~Xk>wܹ/=Ñ>T䅉p#ݐbw3pݨ>T%OrG;nO;8?wNw<ܫa__PA@>J_\ͣY=orwhX&s-5?s9˰?ݡѠhxfUNߜk,Y=_~UzKGok\eNWb 3mMn+=o]#>̵_c={JEp'r-15=408;gfnAx+&#&'[]Տ ״*D4s@x︐@>>^Ӈ#>D4G*mO4mOuA^_=_34^Srdm$桺=mB|IDvT/hEˑNm/PyĥCeJ?/J6 e6_/,IDD޳L7x$|{~dI\)7̄]7naJj[!z2 s{I\ͣu۷=nu_z=q4N}çF=O~]c[sOr3>pi<&߆}Ĩ#>vq9ׂGϞԙ~xy|:Q6[qmMU6vNu4\?aO|u/1]%DU (u5\9tl!XQU2mqi}wikonsrڶ)Srlrw֚;*6K!z7u}G|i sk1(2W:3|O}VpPgHpI= ,ӌV{SN<͝{= WAyoyƜx|l jc8iqYBb_ˉYEg16{t CjVʋ_t;o]ZxZ}}p7}1)ri=us3C+gn9j'O?s#:V\qFly͏wɏ\v^_FӺ\i0'hSGV[vCjx2XcPká|H;Yiz_ &VGi\Ѳ#MW Ҳ"rk5Uܞ(wl _[݅5Fpo΄F؊XxσJ+L^K[Ɓ{-~"Ɵ;M֩;nqŏrI;elq0~U^5N÷\ѳ5>"Iڍ=rbWz8«/{?5g]C8>g tqở}'gv!M"}O;>|cجA?&onwoa ާˆC8tCZ]7./vc"Y?/gQ#sľ_gPDl48 "DŬ]Mo8S&bGn4%6ՓhA$P 3z aB%0s"{!H)&{b9S" bD4zUߨtJDC/@J:Y s\ ̓WM҅E +Wfsjaqۛڀ>>䎫(F><6'WW \ &m*#Y qӃ+]U,wG_p׎,$F!+u+bN$b݈a+Ad7~{u85LxdJڣ/?aA{vh_u'ڸ= Qc!,FvJ30p| c}דi\eݝylֺwf*Fk_\)iz:"ȂqͰ\覃P}Y/N٭4D(YWFv8J6s 3,4}Wl}NLC$ Y,dʮ c/;'цWɶZ}rc%zQq1*Mw=lXk{IX++>dyZ~|}zЧC&b+vq< Ke^I=x\k3׏j#z Ͷ\߰lzu݆Ec칬~.Xm^?l!NRa%qC)&Q@ʖl8bk q4J9;$'Ghk `#4G MT55"ZF3 DT)T8XFqX&zb XoHST฾DWhԚ1j(:sVȍeb[Nco)055XxÙ@@e +5Y:Wm(EK*h@i޳IԱ8Րh{쯆@y6IjDkˇ|DݱTmG-gVnNTt9J/P6VfH>_x_J7AZ4'HiQfO^wTa⅊~t`Q אlt],M\;>[iz< 4I m kZY$V/46?A<hN !3S׮NyuˁWrg+Mx#5l\#3R%uQfoz1ُt FW(Q ~'#L+[w[]*$KHT ␐.z$b.JPh@bP͕-4n阢޼;.OʺHD*kĒ@hE-48ra.o\TJ)lT4N9 ݣ@E{j>f3שPW# Ã@P\B;;;prfg5/8skc%.k)EhPfF `-8fjc!Rr88C"s~&/踍ܙӍ-,"܊,Ip&cXi;=)lTÆC(P`qNiA+ kY y9aJ! 6?sq0ߖ 1 $uu]O|$1#j\"͚|ܙs,̞Z;1BDfhq4+rf^֛[l*$S; glƅ9o=bGWHB"#Ʃ}it~"~̠WLYuIAVvdت I Ե^T;?_ )r /1}Z|l/"2!' `մtc-d3>Zhۊv vH|^K ȩ1E ,/c@;;^ dJl~\Q;@nU7Fx@jTEF^;ZhR=H^!ȟZ]A򚩺S-4ᐖʸ?p(</P$G %%A,$,T$=dϓ>uW^>^(ɒ YV)M٠ݭ~ ynUS0QI}5@4268Kkw+GSM3R*Z 4jmv^py<«/S=$|eQ -rr8T!59z ,03TGUϯWN\F!]:pNj~ğ51E vPxKq]/mu v'oʙ2TWLOGzγFSV('eD,唈2 vaŌBڑr_?>^7?dC +% U3&1$Up\ꆵ?4EQU^6ZA/] qʩ0U^bE-RL|!,1QײX۬{36@J)y>|]OeQױ!5YS __ ?5eq'uKsvfy]ܝ|Oeq.!\#r״uӍjqer),;(qy "</RjB`dlɉhl"%D4`! %MtCbKW<ڎUH!*MlFSɳC9H'B;!F =أIax|I=TʳHt ;FCpf$)Ykt=c&=HQJ!얝ɘ Gʋ !*R%@gL:$B"6T}f @($IFrYj¹/TZO®d)farWJj {6  IFYjYj8LX$T+*H4K R 쉤kڦFH94 ˎ?7J|?|FA>Ő'FԢ ZQοXhn|=|)EݮzH#yb6j`Zbg'8g'h1#+U%‘_,4{>q7'16<+}^ .xI g#Xh?~]=IZCaEEWoZ@BOP?:2"3E}:BuLⲕmtlBlޡik~Lc%?ĉmX}!(!xOb4A ذwX}sBD 5AXi(ʶrkC/KBwɳBVc4/Ͷ1.F,CJޒ^b42z)`e[OO*TN+3_sb,0 C3:2xMBA.yGߛ&9)Z6ZYWЅH4ӾRjۻ.\됄 "J8ju۶ b $TϣV 6BVÌ#`1# G/+ hܦ Ap%^IZW1^-#S1&Uq5Nͤ A6檯8W[D[tI_+jmTAG nm|M ڴؤ;6"ChZ?8Ii\Ig (%Jn蒁(slpZ0O1~5 dYJ$"JŵROTBv\ &2JPznb.F씐T%~?^`dYi6NV-,5tl ZH(J4d7ʐ0lJ;=3 Ljʊ TDld蘨Płkh?;'\7k-)#CC(-Y8G|}r8A؄ ad"t@1R&Q/9R֛kf4T# AW(dbܸ2Ǘ͚EDҝAŌ4 QsT͞|}z?0Н R1(\QG&Oj.h7ÞGn l]:Ow-bF^z!XR#fneG_ E|2v!MoSKMn Gr~PSI@" ɨCߵm]#>!\{OkPh`DOde[Q3gO^?6r kA&4T YOF ܊fgH^OI f/Pa2'#G OF7黳 NEXkA!(2"[ʋT Mˬ~t UR Dpޥrw~RH ::萷*_Ma1E&IWHf8m h´K`5,=>B]6$f8wO  U 5k>Mj iw;l8X5zY/rpi#e3gRJ$W][a# iXrX^h$If)~Ջ-iql{&u_:!ZIW<=d# Ev[)ǖKE ݹOa#v?uu3/gHX:"DLjI]iQǤ0ApsITs]OTTYʇ4>K&#ңW0+1 OʓYDLܠْfxw^rEHj ~uuō,y~ŜqEQ EG8)oQ.ZCؠOdw.̅ݹtkm)ք,(>׍jqorHx&yU/R4ˉ-D Xh.0Jv$,da$/x~EVaHjF#Xg P) Ns M޾-TQ@dآAp&Ud ZXhJoPQ@1lF- M*b3 A# H v5uM ﳀTP o ly 5a#B3~: צq3 6,]XhonR 4(bd 2 רNL 2eM $owG]kb"xb]SwQ p%͖t31Q+F}ӭ 'kQW_QD3OjA|əKxs ՝ױsp1 \CR`Aqj'?'h6j賟cÉڶO#1Êj(E@% jhfD(U*55IPP#mD5-|B%_YJԈ:&5@JJ6eǛdp{-}q%ssj pA _X~ 5(p[9ZV;x8pa '_`W?5qCK44B~VGEBBBWF38Kn}Q<5p-Ұ"p_͚՟\7SnWZGZ*5hp OLAYiel"PR4 +7B ^BPC(fMmUSړ阅3rF姗`s~wC$&`xgs/bMB!'JSYRaсXns5{'#n)hsfq}8KQo_yA9*"lK.Bdœ< pFJIp,]݋O2֯ςbV|`w9 7n$n#53,^DƇL~yXxVMk "=1TȷIkKJ7DK%zNfI^CjR(`q VEJ`1``")@Vy &Pp`Kap)‘`!1Ds@ S$l9:Xhp#.q y36i8vg9nYhs][P4kYS ;,)PlkJ6S+m D`KvZ@,4=/XwuȿfZ2e*93';Yh~SBHiT,Qvb h`-RRC(J!1+6N b$(nfHغ|[!dqN=BH&=5aS VB4 Ia|*vI{ 3trq%p&[™cJPF,tKcR0Rl 'Ø!FACRuaC"os(P 6\C' AY#! aD3! ^΅бB3=JI $AL"( Y <" .~=q&Ά`gDQ==c-3 d Cl_UsNBA?ڠўM"ӵnЩBzHY-e o{И2JѻnN0HEi;?~W !(j)XI[%#'Y lTf᱅X?)57c{kq]lJEXs('6͒8ʻ-Sݶ(jEĒj߼ۿqJccdFT^=dR_5"c&7n{.Q$InFgDlM_ U.B+]f W>Zj9A ;B 㭧SJ$V=9dT+KR']}wDKj͇xcF ͡wo"N+ǞV̻؟0W[.C'^Y"m`b_]UXZQ"k "7*4 Dh„hb NU쨱ŀA ;k!_ *b]{ilaJH&[ź_ o<@֩Z`+-܂/螫DT\Uf76c9 \jGt$ j[_4i"Av5,5H 8A(B u;[I5͍k(X$& R#(Yg4Ȗ:cFF lY`դQbH`PmDpGϝ?lwˣZ7~ KvQ:8Jj( P7{f׽vl"x%ՎrQ&M!ҢM ( t `4"u>-Ro%E2tE2Ig6) a1:}azq?~]z-C^ ( GҤ+hM̧:N قh%Z]eVF _ݦq)*NbD݋!֟3;it{6AV?thwRs ʩoukwZ<>7ƧLS,R^.O#sM@0^]tA4;qSJߩHJF).Wvܻ8v}k_{4>>:%(]2hԬ^Av{b;]ќ:е?54*'t jc.qp&&Z]+1ɮVJ4Dotp$?#YKl~ *80 'pDpvmݬQzF5M>F a$- 0]X+SiDnG5N1` U qrxj.#2#]|XU#MIEHKik㞭던&Uk;F/"IXTI -K&冦`̘l:FxOAD:L4JXQ4<`M\ٓ9rjf o,).FQ6Y.],"A̢o)rItf #1 &8hza߫-o:Uʎ[2W?h EI*jr KPx~׫޴( [4~Bޭ蒃P ٓ^I9-5$%T.xśW7e owϧC/(Zȁ YAEvoDIDD",2M({ФO8B᭮l J95]zM.QUFyQ߼Q5$u$Ԑ5WqhdxJ'IW:t]FEUo >?D}GƝZ@ס;gH])cA)+%GVu0O IH(3n|Hj}rqvȘ%-_x!?[m!:;Y P"+ȉ;4}X]U5}q1OS^iak1DO\E xtoָ<3{Eb}ftU9ExE֛ya𩧨qu9YkD&r_Ӧ##Wi_n)*lQ88zzr ^z!Y\ã&j*rƌ G"V"31#Bt)jZC,B$G 6~!ee%Vg;EeG_]nï(ji)Ut*6nC >vT.;y />lzOb 4R|* "iEmB ޥiƔa~&E^teۮk L<)4*.:~Q֌= "N~Ly0d Dҵ"+-* X&\%y15)B8m@jo *H1"=4cBDf:u:/UjC))X򆖅Q88V*{(:V"ZT~enuBnpS5;'4Q m@,B>P\Yʼۼnr׏O~5Cοb 4!GeGpY"b%yN|С|h"]0j=u} eWSY#&Jp.}P>wlm{BFMYRLc,|$  (Yh9U3&݃N9-.*JV0[{u;ͦqO1ٍ1q~;+LlE`,AP魿M-IYM8#w!kv(),֠U7 3st_o©LH~"n觮54®j0 CIOЖHlPڢgCm͙ 2-YtLNpմ&R 8|]8xaW",wt"J! 3B6U)Q4Pa{a6k̈́Mވ(N%\M*BD8"ѤGj屑 }0 ԑ썈Γ—@GTJ$\U=4F"3RБ.QnA!#'mdͫU֫ Ӳ6~~8@qu$FD^/ # [QP]&/ ~ݔhn]zW퓺c|K鶢/=ni^A[֒LKHHg(§ }lTXUVu'-|ئt 8oG Ds([-Z̪N ]҃%58z$ ߩ9 -SjOpK7i~R:]+blM*"(R,q,"PݪVMz_K^G/%XIKˑI.Զ"npU RjQ.x?3C 0B6_GN5֐ w.HuѢݘvy6%@yZ-J:xx|T? #Z ᎄG ?.F:FJUP "Xa͎{,թv$'Gߜp/v]< qcSu QbEJUspv߫BHW#ɓ .%h75 'p ;0!fh.rm<;~8Ex?>KXř]EݠV`#ł^f݊r8B yG:;&([ޤ.E@ *HDj6!!>"M>\z.Z.#bѲ* dۑ`@($5jV=iaZp1TS[T 0[ ZՌAdNZ%Ԑqp%(#=& `= Dڝu.uOV1:ZS;X(fy{՟3c(\U-G-9yVXlH@F5ŧW'0wXO e 9t-!`pyb9F-yvd.<^7/ZC5}USQG]@0Rz<"oHC0(].vM͚ •""w]0!~vmRK!wY_yYUعb*\#T83S Uη 4k,!X)炘vʈ=A Fp *r7;C&PS]0'7=Q9*02~3~+aCK _QRNq"$%5^ *k+ dm3cRhN]nCDKPotTA |&EJQd(*Fˣ\FE2v*rsg^ߟ2DU%J2"%Ҿ.K]ѭ/#aB4c-V_L)X޳bjg3\FD17k md,5BBQt.cڭ4hO ӫ`&X jգ"GI+)TE}4"VL[G)|Wi%X+iphN4FҖ[죶6MU?}UDh9SFPǗ@7$13Ho<* &iJOe\ {(4anzkJk('j*ш߾zKQ^e4\U< F&)!uZs=)%T%j4"N:·=l6 7U ?/\59`*mhcLΜ (Hdr5rjՒS;..U ڊ޺=i򙛏 bs6HU'EԖFMIKN 62Cf|(iJ#X8p@Y}@,m#PE͔%d^ bVw6l3R骱fIAhT<.U;h}PLnj-%m"qlj:4!!를Idm8ڦR3Ii9.ϾcꐊQAnYFQ=:;wx~zD0̨np ]#E(!dRNefLQLJXgʺ rz="|=d.6`32Kv>QNGHlH0pXVyHD. "{qvMbCuy;4z {fc*92(I@7o]U @azT{i%#%CK`9(xlE,%S2JLWoisE]rɩIy]"FQLʌzT>ՏFjb6 d[_*X, d6|agӨmFE20* `HT\DK^{介&1IU\ G2a>QQEAU9>rF߈Xxۇvڑ49XpI SdJhss/ TowԪo5}cuF0 #%ՙrUF5&b3oDrxZy1#ά`΄w*l9GJ"goD|\1{1-g0+#bʖŧD"Jȼ0SwC:G`Od`fR3adG[͆k\@ (ȈG[Mw}asG'"PILctAQa4ʴfQ|L)[//(F*9 tv3%3T˚}zV#(vguC~bDne΋s8zloqh@mLnkēmu&sԅ[5ݗa"㭬PPӮl[=Yu%cv:N|KVž+&^;7K}, ZT@3`C$&:BQ7?eOH! gH%ؖ_[~.~ùv@pa4 V *X"SU4հ]~Ø?xw0" |l_L_mqØfנaę:CBIWd2l͇1MP'5gCib"i:}Q=?.+iXmFwtk7 cfmckXljR)W]СU)mjCh*WvbPA$Nq';Buh#2<\ qWڝ ǁ<3/pw+zj T0EPh)@F(Or ~ QYfb 1P+0?IR2kt/F5zD P ~VeTyL0% \+بeF*6ޏms @VlH % KʹX.ڒj=mLM`:Ć-JTUwq!~ƈ5 rAS>>Z H0\Xfh_ϴ6J-- ؘjP 7vc5\]ToPsK!8`?~9rrkiFpB-- "ljԈo;M';5_yd?QDYآKPD㣠I߶]ffȏWRxN1Fkyl4ӷ%AW7~閟+[?iO!bŸ*8UgW|J%)BD\hoC?z٠v>n{+.f^M=oSo.ϫ BDڀ` lt5GN8=۟yݠܗ}sSHjf( b#`B4Rc0vuf?/y?w W`UFD?8EAXTG7}GQwN 6BD&A$ROufᒼsCD󂦹HH)y5nMzLy\7h]rٓTEG̎jVଠon3鿟1h6-|)1.%Nk8mJ4hvͻy͊,*p^XQKcrG w; u&|5MiB+t$NԆл2K RoNjXLAQEu&4{c Q JL!(TϦӟ%4f g2F yY4/ uJ atLĸ*&h#j+7MFF:˚ ? p^b[kH1f&>nE. Uyѹ5U-QN#aɄßܵq2Sl6v~YX)* ksC6%EtqXcZovj mT%d1 O6Nc*L˥ RvF ⌐lb-z* LɁd " @ŏKDcs̺P!hU3E*+Eiw:"Nl8KG'ᔜ"I55tA:Yߙ5ŔMg59DbͲV~kTup'N6YP_dM֬)ِmu;P Cm5kG(E`L8kjR ""Íޚ5jqH\RqA uhy?F=LOeԴŸL-ubw cwՒ:&Da 4KTtcx4FP`.>eӍgw?3 #urFF0eۋ5PQVh2V Haj}t.WLNΟ1 el2JŃLDͷOě/Nr@@B2XBhf_ح"NyO` 44EBHG4ww%5ўݚ&)S%l\Nrmd{cQ~P.z~W|Y#VGT",fdL}&f{6I}"SE4 ,3T`s>azoGe9kSs}"!.Q !\QqɠkYP0--PnB+弰PPS:hK{Bi(ÕEGl D4ކB*@:jZ)[_b3n%.:ҡ[CI %$?\7͚])wwz@E4+(>iڭfۡHd=@L)&4kktk+Tha@ğWݒ-o5}^o f!AҘ4k{\0~Г禚^( FrdM:֐0QhTӀPP%/ׇEDŽC3V%DA@%+Fh=G& }rBTdlM3fQjޱ\hRKW (&d#fJ'Îc^~4k®R zX]Jn'c>5JGCVmRuT\DBEg9ʣu]@!)d@+CtHPJYS1}`y嶨  jYc7J)^9Z=yl< Sr#DY^~RʖUsTP%0Mm/˸#͚L8z"d4a;R)Ҭz; ###VٚD4k=*Plw>G*iDbk62fv]3w-6kZ?E* nDM?q9bTcIB3@LSʜfMԏ## ]p:M&bfƼdZ-6k1+(0V3+Y]lD: Hf$@8ڵج)ױj |ۀ|Ie4VT+׽͚Ȉ3Rj^Y"!@' YdbWCyЬ tx/nG:7EEFYO&"XB!fH2R4WA&)"p&p )?-5ɫ;h($ Ֆ) 'Aa$P!K+#[ק p>LZ0;Jb;.w49uaNDa K"%5jTb*eYmG_lj9ٔ2P1 ¯#g5FmIGI=2qV[c@|QG}+JuԶVMя+ֲtH@KBF!`)53>ccj"cidj`]`5Mu:j!.HMDouj"s6T,:|};GfuN؛A* FM,7Oz⭤ْ_?vxnsWF퇏yy8\~VaN?Y55 PL4 #8m8mÁ7 _=~V?j OAXK.QXI Cbkc]Ҭ9檑jϽMzh$x K,!qf vh/,mV0/cK'B .` uд7G_0s" t k"ciڵfYhRLjX2]/@`MDG1k\kMʹm7v f!9f&X5JIiS^/bJ3뷯Ww<3핵 [S`\P3^R4P2P=[O(WO1ǽ\BPD Dmܡeqf{g ܶKȪ'VjFݏe9fw\8YZr !GR'4O.+Km H$7dsG]fKQZ-x;jN?<7~d a`‰qˋ/ݷm%9JP̌`_ó\ Mp4j9TVycf$D-6, b2nt7Ѣ Em+-0;#Uj|o[l={~i0^9˒l-̛tOK#\Hm3:KNǠ=K&[hoyW7=s4Wl9>}oy~Å;sp¤ $E#.f?>B/kש;;yFFjU$ yaoYwmr%wޚ HgM?omxyrwmL-.DVb(l]|vlhc{:S;GiyM_&a$m=M`l{q?a,W]k9y}dLl.8hwߛq'K0gB7dB XIk3 _y+; ,4Տ\^w=Yк97ΛJ #ְ9濒Qvď&5Oy]7ҹͩI;ת р# 䣛'Bs[].hNd+C*X4ah`J߷`\a15Z) #v28䍝l.;}ޯ>뫅? $LhnYIд&459V"mD&aTo?n=ݶzaRF;:;: DUֳGar0cm7[4;576gHhIUcIVf ë+Ǯ5om2wfyL=bf@F* K!J3'Xxa+E*D1j0ˠ9{z Wft[tsWFȴM=Xul9R'ol4%T\Y$ڔHwV:8ˢKu(GEJtj:M ^pE~|z6ʉ%hH3Uu(Qtfۮ/>S>Ƴr?>,_¨{[i?JQ zyvٴW]B+"02꨻v[i?z}Qǫʻlk]Df%#2ҨIo>=⬐\pvJq2Qq&塛a]m%9cbbH]&ML͛:~ζwtE/xጔ@UJ dKM#?>ήY֥hP/JH`A#q4wX JL)dBjt2d[i^3 |jzNI%*E! <l+ͤqS畺CY:\SNS;7ۯ*Pb 0f~WңC4W=7>YdynSCD}_=nỴ%:,tCŠ4,=S<<ڍc| u70O2L!&۱C6L GYj~x^ ( 7 ﵇YjDUӋNWpU6~{/25ʁ^p5@(/zzYj8yH:\ouޅIἰWa*V 57jB!E;ꤧ߳Դ*v;jz:z\y|Yjy鬖IFJ'zjƐ=vmn>V^y/˫ j-7hj6l^O[-5~{Ҭ^ 9Uk¹>Tk/Mٖ>zmw1"pyMBr|/B3ɢH>X/;j eF a{~Ѵ3m b4:B.+c{K?w}+E2패JLyvGJYTm_+7b6?dDd+HeͩZ[gO-A?sV~`.5iߐ5-n=ĖUS4Dĝ&x]%.䫛Ru쭉q4C?y^u7n}%5%/R2ⰏL1r_]_&jVjyFLkG`Xˡ7ةDz^w)EK^iCJwpЈu|z|{mpϢ*ޢ,L)ᔯjyaoYkn=ۇ]oܔUZ)r*L緧YkdtA [[S)R[^Gqzci֚mFg޽0nʘ}PVUx钧6Ӭ5U>vc=fES}PWWԝHW=b7Qo!\ f)՟$|ELjXx >wTn2+ʟsM#7qOa5`:i*<چ ˶K/;,{h vk}#F,ˀmeݛM BYw~k]r\7e"˥7Tu `ymzްl䈿7w!eY3XedE8}w?@Er f9ye0X[0EAbyCl?cxǥa`ba6R?Bmjג̵8 5k{/%\s]*,tYsvK"O;\,{/ͯk`.n!Xj#T \ֽݢɼvεքк8nAڿ]-Ǽ6Z3_6^nt{SUi{^hS7gw_wZ¸-<ݙތGULH eMI'ၝ֚ ~(ۿ]IoQ&DMIggΝ֚7. ^/Gz"h"r8:lJX3s֚͝q\X؍eFz U D,t^wfgD0vG hKU JD&@ ۑς:A$Hѡy؏ݒǭM҃k]]0K3B0I䦽K]5vK‚ҽ?~Z8Zw.;w_Gq'M}AA9s vVRٌyn)S+~_ɲZF ";v3O>Y!]8Jm&.KW5ղRyJM" /;WC|MkKYq͙rxQW{5IYي2Fd72,TR}yY" =ޔl~g~^< = KsuC"JF=sSS~xW8g4=~o{n 'njx~½xd}v(nwi^c>?lM3y/j޼ZLJ~G*~Я~44/8$vdmO).8'*nwJ@3~'V븰5+qO h]~)̖u*`0ch&Vnz-?8>k`w"Ƶa`W A45!㵹fӸknYs@ :Ѻxtflz A$[1GG-Mg7_}=pAYQ}pbx3sEԍ.;ߡҶ)* nOJi *nlm/,жUuc j)kFL7WiVښ ?{UƜ+{mz,h'QVcwbX(D:KX*zJ<8evMyS6:Div8v`tN^>Î䙮v1+dX( E ah-,4ǞɩK{f&o'nT_x^%#m:36>%ahD'5[QmqNi|YNmP9q2jXe9 ڂ)A^'O5v6 [+:A`haez RР\vaZ*B4B1Jg<^Xyջsk,}};%gÎqn%R'xeOY(=mM j0 .@ .Y\{'xKx5FYLKx &GƘr})kn*YœXF]vveE<EFHRT AwS*"Ay'x"x`OCQ*"#mO__ TnrmM T]H#U尉ՈVʊo|P]mT"Ɏj,@STyt}gP%WJmŽ&kM GptJTqЭZTqD[v zF7 &k"G%sPBžlFpd>9A{E %wPTHi@DŽ C^̀> I>1nW`'Z,G>2hJ0o={ǻ i>&4W٭ 350!c*cлo4P?dc-qtWw+]`\Tΐt~ih9T2/xnH,T]Y`үܑ=oIQ5+4OtB3Wn{­SOo:[ێ[KTl?o:Rf?}:[K͖SL{ێ]<[~c@|&}oz578-SD%!F2EZsL{mژ[_y a@3@*lfe䖆ۼ;6IE`r!m,!(A" fDo!?Ri5auJآI:ai(kȘz-dLDU*G1&Q*c"PY1b"e~4ՋאD*y^wr'40x5z<1E< T=%>\rČBGcN# 5c`.†ٍ#mWFD]ɬy6=S"0]ɬ?kXB^TJYP\ɬ?h5ɤ(=W]Yޕ_mdBw~[]Y{lU} LHrsgM5E.,&Lz~hȬLO.&vhmZL)=~&6a댆ۺVpfi\MoF#"; #RBٺ p}wvXHעx?fcva20($AT $=-el7fIuZcm2QlME>= Ϲ|'Wu'7s8fM{[):ryAU-h,%%PIyoaGOWĿ1ow88s2o~8:G-|ŎE p r'Nޖ'Je. _MWtpyیO\^t 5 ~^1gܝv{?Z /F)ďx=X)oݤ[k:.A Ã;wq|7t ,W&SÔIi9ezXxP5 $0dC%#W)C{PHTÐADP)ШxREH 4spPJ("\,%_Zƞ3M5UI1ؘF$4&:˅Ml1 KQdLDC:d 1 Y -O*Fdz?lv)SRV.|GWWv o]ՇR6D^P/{<(}ΏS]|\ j<֬3RxvZ7m޴j`Cmt:RF:mvQN#^@={`w]4:zo7?ԣhQsumWmvWz\h$f>ܮ k>TjxY@6)hQsÛn巒wr: ? IFZW]Nn?s ?$+pn[v ֲuzVti&@jҹ:ؽ*+:) L VtJSd֡p5i&a\z{RHSC+hՈY&4<びG-%>;ܠUA.Z0_8Fw\c]O[İ. C.+7z?»QT%9lIQ}w?uE~<ҤQzcg-|h+)¼cWTiuF<8S9fM(/;wE 7[iҪfi@{7N8y+班46o=$> ܨܧ7G5DcH|5ڥ;O͸ Y~EsFton30F:=0/{6bPsRt3S7e>ȝugw?vPW:9lBM#ʾSP 5 Gp"օ ձ"53dFWAvh C]"U\&]"4AЄ]"h%Hœ4峕}!]6%K4Co>v[rM 82q#AwbB%r{#WOo_n u쯹Qӷ<(1ՒrU(5Fѳ|  `n[]1:uh5".> tzDeeO!N+\J{)g͊P%/xwEFF7Ш,/Vm`*tc'nqg'd="!؀c2yy!(Èoܣ3Xć AD5w$z:`dd4n:{;R#וxfAL8 p'qAzFfISPOi".}T.9uE͎{7o,Jv|jkq.{q4'*uruH0lǶD;/h.vJա(pa;nv[+K׽wJyj)*,2{r-n8ZEMŨVjՁH$]0v|a!y;2 [3Yqa߲ߠz$;.wYsRu@GPj邱["qVi(ӡܶm{#̚ctok: ӎ;>wS.l>ES`mܽf͕ow̎|9D`Ɏ0*)7V7d N{U2jGS(U8e4n3c_ݼ*]ﲨ54}Yܱ~Eu5\bL =fr,9%AYQ ͚ ~ EcϞ89wfr:nrVǴN'o<@WTF 53g# =6O!)TD~R'9B= ԂAFvد*Z$P;<۸ lB gh#h(I`/=!/$eMD"$j2Vbxd44EJ8Yt4};g/./ >an~YZg%T^[s>јZJ|!:SWʕ@G|V΀y) ]]& ɥ ThXIElH$kA| 9*}?O"* 94NT<6k.Gx+ (E3ŖP)F*~I1*\\Z\lĐwIPYyxW*^c%H b"$9)?ge.ZTJĖ!A-ܘa/y 6B+Uz+">ݝdN(]#^Womuw\O7hh姜WVW`C6z"]W7׷|~<͋6-dEr Jw*`V\|?7+ڴ"sq/&P6lC!^g fMmZP׾ۂ tMzo 7h:z3M4"b=3/wۺ3 L`R:%_65Qp7FlHlT\:wh@8av}=R2}{۳w%YhncO6*R L5ʸWKa [0 Yf_竧,4}@.VeIz{WIAX}a+ t{$ f~gB9ϼrsݶE)Щ$BUR(t&ٍw{o~:~dB8ôN=^ZlR+ 7 d]9Ml+w]yuȜj|#WqcT>ŀA YPR_H`"ϹJ~)*Lʓ:- hŪ.aRvOH W !k>xlx~Irf>OUZAhhY ݜO؃8`eCI4$2*di R>AaR=U}HGz+^53)#׻wBwk1/7&~SK&SW[ڀc_z~'K'߳ON Xx5HEZwR?ϲV4&Ÿ#zC/1n4 gco6hmNߒ2IX'#c3QhF(thյWIvzCX˫O۸]9Ʈ|YE6I#O1)7gѹk5~ѝӧ^{xujziɞ7/h#txF4S_jφ\ǡQ3wDnΌO8zP[#EGc׹0GGyڹ ѭ_r/Gto=z'՞wa䖽yiI^vΊ3%/SG֞ j΍BAċ}crG_??7vu. *ǎǖ~8/y¿xs{644jA$ag͉:&)B×[RBpe <,ɋhde'ΌG^M)S?[W{: \֫b(PehINI ɔ@(4l.n 0V aK9`D#D'RbL︉(f7RBک"c%0uMT-n7=¬J ѹ"cOR1cAn W5}4BړJlb$ Cw!'R5[E =DJb@O\3!I08ٺ@5`/~N4,{ q^.`<ٻ7 `G,='cNɁqxr+p"I*x w[qɘFO'fl6 LWt({'DR{OǗ.i+=9aL/&U4 dF؍ɘֵ,)#bۙWZZc`qIc _{jf'n BۀG"Hs7t# #{] .X4nSj7%ɞb4VӤFQjf˃M.H5F\JDjR,4=իj {!, 7 M]:ojJ#4, f=>Y< @LުOKJB@{C饿pV,̽zk'>6JGj@J ao᯻#w~5]6tAk=sf eBN\(jP"EP;M:,J;18;F$06OD BI>^8=AH_R!7"ca$ss$ ! @٪ ̊G~ZV "lyS),*iYCNQލd<4Jݒ{|/Ww׾0kAQVzRo71w:N>l?O|ٶbd@}LɇҊ]cӠ騲uKCCGxZGy l=򕄱?*y?\jM?LfCi٨MOFiqW|#,mnNN:k;a{ '}F.C?g4{xO5&"{;Xr 8nlMс' z|#Ma{(E8ƠXq8zHg ׿OqǪ&J:ni~]4\ZմxՉJ`4vext nV\s9-{~m{XӐ.,<&@$̝`AU=OG6 ٱ%!HOЃ}2$\`OH ,@/Ax0uMͅ0gU HgZ†e1 ^LgP5'Qw8/_.֊$脫ׇO[^U4%٪"w=9ؗUPyrǏX~rZ[w%B"rR貀@!!{"1&?[77j$rŸ)+pbx܎F$Hn_1o<_|cn0tnS)*5^ k;I9'IHpfyb7K[sL48܅uwfKJtmEYk?I:+f䴂 @@"lP\E١e)*#B"hT- IVdѓ'r0@p EF,ZdQԓ3b `R-*n,8[#濤XP(RQd4x2nEIa19J %EF3ސ-3X˾m/JٹbzKknvWKE湽fpX+1ʎaqJ];ع镧{.@Pe,B2sbh /`x\ke 0EjYP3rk&- jӕx03稵tToҦltABj&-{bM{Vo/fDRu:IFixA4&?A:CQ p^#"?Ӝ:A+$D6,X `nlLv ?:h&4U 2ҟ(0^ A&O`֒Q*!7_>]؝r>:d ­qc/W|#R2<ՠ&! A}<^/׼;QVY,Ҭ\q#RpD"oJ 5 v1ztŕw>"!`9@Z%^SϛNGiźMm)&$u[_8?ã]sLRL}Άh+k{>"6ԁ8RS eg߱p o;!3 d^l#bmT?{mR@Ȩ2BxCv?v;I7(пk.7,N_C+%\y"2"ŎTx_>MLxV#7I W/Nk]}~6AU:<$V|񤶤$ [}#>`^i ov\FL$C`dRzTX~s#bԬ=7 +D@C1LXogxç5ݣF}s?3ʠ<ԟ8,).Jm[/?r,`^:pѥlrڶETɂȳH̭ӧA1t>50qy0O,j'7EPOp'+gG5K2n\(a%*a>9΀{ކQtb\0IGjn,X0Ng>rT]*hT^،Ro#W#&q:AM 6RmAK?"9f(uf":*`$ᲂv&ʸtݳw}D\րF{r'.]^Xw f.mɶM"zCƥ|GD='նi!ꒋɴ :/H@(ʥcDDlD@dl1ŇGDL^@ WJgY΂[pn# ?T9\Fѐ.Ce^%! 73'kņdbCBs%gFQ$%Bvgٰ3Cam ,8ӜxqOi*^:As* ՝"]K]iA3q .sNWxЗ/FK^ 5@uМ {p`)5YeCE^mh(CTi3tu뱭korgt^gh]jA93|.b|ОtQaBTTW@fbM<'#2ȘZ.qsf)9KÜ̼g̈UbJ`WB3Kz]&mk8K@ X L&>Wl'("ۭR6L{d7JWg 9e¹*]/Peȩe MCxT_wު~tT^`b0_ n Z>gORdé,5j 쩱 ˕JfXGWP͹o PRk_VI u? ؉2p !QIB"J=h欒d!u,z?٧Ȗ8jΓ}(cdIH:UAs|q @`L՟K|ɘkн(| J=tcsAsU99*=<R }'g6qIԹ?O) VIz]Aԝq=( bgDkiM]*_+(Hn!jEHA;#ncP$N)F/.̶֔ *_ 3I4yC Ѡa#)ܤ_yJ|ꔑJ\8]*_Kkrb˅ʊ3),]%d'bK9k]DQ&KauDĜV>8<2k۔dNٱ xڙٟCT(@-GLCJ4ik# l\͵ㆭ.ɶ\$D$Z6i6%%@ b#S͠S=,h ibg>0j2-j^G#v2`rU@Wy= @5 A\QtG2K0c˭9x[O_ 7eםrwl4h4P-ވa%|'"u\^rlIRCo[FlQz slla ⤏[ЃZ9RjI:]U1|J[>K* T_uC_W6HZ#YD: N=SMBX=pxD :b Kh]ɨJEMq⊵ͩ&ΈPLpڥ5lܘѧy ?7l¶MK kXY]\w}AaqZJ@pzh;HJ jkdx@ `eSdM/**"Pgɟ%r -iM!˿=! *'&wBTEVR;-D]Ab $X8[,\}ۈR;qG"7RHK?#c^9hI^(ي$O#K ᫪n =i`cr*D+:{S $HER֚B P2uG-s^bF эrf^vD_e¸m !A{Qrj UTīkrG [_[88DXoG|HBZ =2nB?rĖӟ?xstZcNP $I*/]DTTsaBR{j`'CB.SpSEA$8d ?WjJ.=qܠyMP DŽc}OS&l_lx)X7?sR]VdZWb4Hf[g (K Ⱥ:AAS#k,hR,S%.m4ө0wB PwOSc \VU/twKlp'0]Kn!Xfm-m,F#鯼횟<28  g"q&APX ɋp}ǣp9_- +XƄYFzU0EB{.6ϼ %g:В|E򍧔z-$!>놷~pMV[^2oΚ○rf!rd?Ϸz]4Aeu֏<%2WOA'HH{DLZ*tL;bdJ.W >b2eQhO||R{Q4`=HPҟ:sᬥ  FE/ ΙY7x 2M'5 EXg!7;z/.HK0l˛5 Զ"kֽ-=v+ p$RmSѥdan^ DTtIC\do{Wy/V?.A$Y&D|qۮ/ B2Dd‚ doZYU^rJ4J jfsws>+L%-5ӟ;1hBCD%B|K lY(zUe3Zb7I6ny389DNG5Æ%‰ĠAեDж:+1mtUN':=k}CQOVBs# >ݙnBYTTdUL0`LmEr sRV8ͧ|B)FތQBTzfR03M̂|~F]¡<_1yz ^=o]suqQT\ (4!YSJf4ʁYB8;@úHTԻ;lӝ"[hYŅ\ !ME)ߔ~Jc`vx'w[G;/޹fYpr6Z^T;iQdzWZk:S)8L=ńGnqN.ZIlE R+dj*ŌM|O`+Ji1PNEt[\~)|j3dc@ _d (W$`1ȃt뾓oܙ_cсmG.A@xfB4|V&( ~x$:JSg䕳dX3)Yj$ Pd0=vY٠#R6S}L=L(#|qIa~J S>Kc}mfԌ㨏{G"iPDWR`ȟ]8!a~ ضͯGz%ѫ 9zH.ǃ<ְsbNtdtkU4U\?=CDIAȪ JIX)|"Xgmܶ:W2:m e 6Ӕ:IpP6R),_@I$/G\Dq>D9k.ՙGND10AвSw0(BrnA?2.Dd<ätRuJT 2>ۙgNй!o*ACҹ窜TsyfLu1;f^pX5s6%>!ի]mPJO1hA!#q5![a*${p }tBRU8KS{ٶ  4^TUcs8-md7:H)w(4=ūW[Ojw,fJv[pm Y;g{ÈFGmN-za>_Wosu  \jvy*LLQA8mP3WvCNprSv?R8_\ڏ=-#%>43Ҹqō4*!3eO͚ zJtbKVOOyQ*D$] :F5VBnYcNo %A0jRRhaS͚MfY@=^Zc&̷d6hQLwH2dûCXC;XH=ol^D@r/cF=$a0Ϝ @ %1ᯩjt"\Z6OdZ3>p8bVHPd>LY:Fd<"x ,%e8RBW"GsN-KѰhbXe8" h HƢ!^`bLF e؊vMȼ՝jN:cE5wm i҆_S& 2E#n-Ё$[坒ǣ:\]bV~R 4h쇇1wW?—}1H{Tt]"m) P0mC?z6ՠI6atfMGTޜ.y $%9.V j?KO}UnOV#PZ֛aۭd`<(& iCs!7UfoǚJ?cy[7[uu%%N>4l$mJ ꧌kuIDgu텨(W(b̠qHuY^QC 3hR]<\֍,qmMKh!I2$a4@9e3^GRůׄ HcE03h BFyEI*3Hf6pA# v`EwM35SAi+u HYH# +Π *TEJTL!Cf##a]pS<pYVtwM+A&L$=*9D X1ggЈ"|CVB2թ\Ф`iK/ShEG$E.Pð7zŁNVNO|DP.Je9)բ,f6I"ᜨ+nCY.JǤzkտ&p B_믌8gbnpXՑiȅ9Rcb,Tm;P/7_h)-]K uNix߻B rw%ߠ}٦+vzj77wlqԞŠ~Ɋ9e06c6 WБFB#'/ X_mq#flPHrs݁"OMB/ KycP2Xѐ7hB OJB,+TF`Xf 4d,x$Tx2[ao\XDrEBA+* t[r7G|p;]j!!^ /1_/ "l%t+9v垠wq2fޚrd-uw&4"v5 5K[1ܬ(H]VnEHCw{0Y.EH2 4"ELP`B+B+֋n)G^ݎV[Id.#0/:qK ) l9EQCҊ8EYFORS᳣fE , ? sOtՊ8P`/xG}k}-q4Z/uWΧ!emE UFV Ila* j:qfkXA҄fws&JPSr<~=GhB\i+(|Xe5!""HJc]UpJR*Wڍk'_oА+3= %3cBEG+d4& t~cA~yz2U\vPaC\Q w/A PdĠBiY Id(]cIpƤ9Zfٚ(914i.Q8q4 y?Y;!__FM2g 0$INt$1@P,uLHmY?oHO Ah~AI1I`Zz'\EC0df !W{74jPRW!4Uk.Dr߅A;=Xb5:NDD:з%K'2`bB,8m#'$,av1(E`D'D $QWDtx6x9;rRޜў%['+;\G] :x[ u9= ߆w|l}mv-/aYq/8uCҟx/(gpS:n|M0p{{fS*Ҡ}5箃QI q sQ$~bwB|0qBu j_ _M]Zd̹}.; bZT9XHT2;lPd3b@uyԪ+Em{K9["fS9?`oCuUԒr\TQZfi;IYS<ҹU_vY߳jDN#`-M03ۋܡj'r KhCÝcIh ^/K}4$Aп )Wtmo#DCTz_ueҺ=|ՏkEW _!߭հO]skQ& B \iQժAww$wiUؼ_f)Tf*n^=gW׳5JTzAr$L2kJT^V;n5[O6u)t$j@)Hԅ.AUX2;7刟>C86ⴶ# bKDE /XԲdǕe{LJRkS]8iZR$!Q\Y *kwYtҲ]hpG *E %jE^K~+ew5 WQEl:҄Z$^TN@Eep _;P]S`Nj+jvRsf'50L-)n'\wr.im{>ύ~hA2n;R# Gs[Єr))<ǓD B,wE{#N׫`ff^h aj>%ݚuy33An5^_OU"֚^7f3Az2BMVCG;߱nM;^3hx2БzgSl~AfL>1O"k)[J\Wzg؄翐JYZttk ֜6CtP %/5ӗ_2Ծl`1Qɭ{< N|۰# ,|ñ{-53 樅ug G2I/M}Sʺ^ IgVaj)܅$GdaD8Q5v0꓂\t@9{-=jcДt!@}1KKR5c 8߼-wWsWTff0) XvEga[o6\qvž|3g6X);H в; nH0"Rl`1qX-F`. 5ݦPK24u{ K/թooźǫwQ\ |ٵ=47ǫw.\͞mR%ґ̂:mo-Ǫ4T(W= *p:__J3UugʫvPBh̹0L}zJܡ=*_L+FG { PQ)GY{K Hrsn0{"bJ3i|-U^p)A34sU"O3KzlOr_9Ȍ~p/A(YBRA x_A'| Liv Xk~섖ea(t\ |P=P_ ]7'<͖3E2i^lAz=Wnq 9\EjfUU&&:%loGka]:'2ڀWnŴ8^CL%lf2=7L$x?Y؜Hz1c˜ϵw4ɶ1&M^%z3+:;2h4)bWU"=->j!9{YtIX(ՄX$HG;o]<7 ʧ&4V {)t@ ;h5 su+$#ge~As hIQ՗¥Cc;->aχo m/Ba1?B W&PiG_{$f#4~dTBZ@Bia#]|o% ^ v# !8 6xP6bY<-Yloj ;O'1s[zw,^;#\L)2-A.P ?GrW4yI3DBm(PI3jPJzNj|tOfl0eS~HDyvdl43fO4a?$^ \xaKne %A*FLވ?3i4>m@ {ЈtXS^VxjNLnysϼEh:"K::+u p2eK[>ǽɷ{s!AhhN/`(Ȱsqbg,|i;&1ކ͘RIcx(B: @e {jB0 E^Kob,]4*SՂu#E@Kv?fW'-.eȐ1]KAq{p]+SyK &PDlNI楫v'Vf͖ݪ\ϴfkت+ڧNɍ{e59p+/|jJ83 -K_F?P2̚z.f.LD`X"nߞیVy 5X/F!8?RJ 0Xue>Dnj,)a5\yodF&U+|cֈ(T@IHŢ -XIHu4!oyб0)0;d|X 8px |H0]}omʃ* )T^Ϯ>] J@=ccwǹu qà!O\6?`wC챐 ABOl٫*` obZdnHfT^,vG3iyO;Z]@8,c7\L[}鮟mte]Za a;D1lt]_?γqO+ܔ%AN&Y&KFhz'VhqEcU$j"bQ>6 h,jzfǚ~btN Qʮ$_G]S^ybJqE%^D)# ! "u-*̓b[nSxzo@0 4Wf+dI#@K.L}]ů}q[%2XA\ޥH ABZN7 ےE>r "EA .|*L bNkF o#|/[`QB9ҥx;;R$ $@0SRBEuIb^kഭDH{R(XKQTU0qbs۵%Os#|YL|n/Zlu) =KDewט፲޳ !yʄKN@W&DZVLFnj1qsB&?Zm"ѯRd( LQWI/+I 4dZ̜:'"ªJ <-y m5MBRdbxAT.+ArX R %1o[ 6n_'[eHB&x߮*sD$B:̍'7PfIL^T \ËmHi:xe2Ucȯw:P[Os͸)s?dfg!i)=g#L/MCL@RT&A9H+@PGǚ;@̲TA2_b k-=#ڛ%/|@,p'g0Dиq V hϙme͔+oQj{ M6hWRiO ͚# >b&Vlľ90ztzglMK#U+}w|5T&*Uk\lUI+DW# ˬdcsָ][E5F6/ Ua5)4>tb { I3|5ڻrb]V&qe@'I;E|[ hL<1A^0a  ŒB._xsp kA0ưt6RI* *ӛ#b'H&blvKD&°5]5 'y:q+|6T*(pp"G=Ӝʝt Vm}yΓ麂*!j  @h2 ~S{{g0a*JŮDLj %'|K WVmhE y-SbjV3 ws~+/Um|i/H9XĄ ZCc9NY=S&9 K\'DY@h ց| d0eI|%W*Wɩmxy%qucZH#IEg]jP{ lQr*"h(%6+E>(~Yc͝ŸDQ(B2aV]`QzMcZF0C0"(1H(f FV.4 dۀ=gQRu,"*9|Wd`w Cqc&9{Rg5^{ML*!$ 7 䞮}LǦ͂I KVo+_\B^`4ITֵ&ZV ƒ-tV^n`o'kٲ -`I`$ZZ9h/燿?i֒&Y4e4:%wjȆG1c#F uyK-΢w>`eߌ A# y-ѐP)Sjڀe?"i ў jlD8 @UEzƹ,$B5"Eը!f0)jR0 r׉5r)t`qAF]bYcj>m&@40c0O# TB4 @x8"K+Fs6, F6{ |)䃤@9=)4aR(`:NVT,H]dd[}AR< \b5Ś8@ks;TGݯȲr, e2~@S7{-u5!hVS~PrAu@( ^}g#gI]|d-#|dX3~ؓ?>=Xxg$K6oMD3c_[P[\?GxkQyIA?}O vedй+}J-w}vϝ/L'e/si #s}R1A[& yak݄rDs(3Iٞew"LQq &z5pfݭVyQD.f(E-gP6pPр^GC9:嵰(ljNrz'1K*$QNB{eBE? PQ HRƠؙ<(h zPE{]V?젺? -\z׶dJzSkӮ̉ |O9Ui= ֪[\Add!QN/r|y:])h0:GÑ6RT;bM.u}˃4{R<,[cULH붶pRW_UkHAf-RWѮ0zTjSuf,`g,f&4&(W;|n@z)yd\B{!SGbLv&A q`pI/xkE:=kJD]!/I85x0ͨS!/j"_*:XOi;h&[*ݤd J(aT!&4H-m ;WJf 5zb(P4Y8pA8  +Y50 'oJ VX[GEZO"WFf_vmMlTcd2(e1&>OQ#{]ZQid@?3$@6fr~ !F!`"!  ANeM-MdˍSmK\w:0iKz"CH8b*˭ .qr & 9j>"}t9t(2/SI_axbb7K9f%r *F[NY"Ed 䪌iRgRv}UE1ܳB:!Ő=$ u }9>N},_ rmFA Zk6 @%!v#\0֠dIm}.ɍиC.! MH1T =dD>S4@ !g vW{DI@$Qm_@LٞD>p;Q.ΉY9yXZ:vd](͠#flQ_S4ws0!=&5p2|$Ѕp@}AO^<`jUJd'u?4LZN+H_3!TyemvִBDpX[QVYe!P{> 5@(}4 ) "SzIXo q^B4BD]Wۧ ATHTc j0%$-X;#`͚xYML&(W[_RԫHBAp V)/;`]gU0@Ap‡E0$oX{ kp[ꪲUy{O޴8җ6&"LcaI8~+JRԚhW NZ% D`hؑ/D{SޑF)XV65#> ꊤ% R 2a/ЬKI,p&Ƕ&@Pv$jtBIXˈrGP6 Ăgٍ5GH\݌6 z 4HR2{""Q Y\dLk4tMAZᐖQ#{hºs0 7f@@&:a `xM1~g*)e40iXI e$SsEi8CQ m@ IBB8bbD\$?Tx yA(1y1<"?ATxǮ!UvW~3#4B KY%lS/$ !Ĉ2B\rNk[r+N>J#Fks֦΁3eb.4E 6e×~!y:" zT'TX\4k\m ]|7_3x*,@@OVVu~S~ګ\ fr޼It}$ٲ꺋!,{~2fFj~T3 p蒌J>KQ(qMU; Z䢋*|/h9sZ9CiQQUqdvhDV7h> ( xky/yqY: T LD>Y/AuGlD@1@q֮wl}q͠ 6ULH^V+ְ+_(ZN`*  w!7{[9&WR*^z/_ US;l SL3b5rW9C퇚bŚ:HO@*gRe{/ %(P4رqguK̚Kg+buMLiNf+Ҹ?4:KqBk$d'D]!6$Nj3˞.:9#evA,{ڤT5Un6xwOۓ4{ia{~m.+INTV,d@ BiA|M#,,la}u0{ MˏS_YXNiKhf%n&j}Wy ^^[Q(s5ӾG2ː&JQNC k^qBdЩ'ҋ0^z<17i{t Hdo=O$b7CoLsx9 oL;ΙrZiLa&@:\ kȤh# RH8 0>uR<2ek2{5iш1"Y!R#$%;qxa =gOs'CgXϩYVhV )(Öt UZсA 6PPֵd4q:RV ˛iAAV#jhjhg.͏98)c 2*2ƒo&TToΦ w*aG!$"`x#8h筩. $aHab5x~/Xqt绾esq1z=gt?zv+^#F绎5Vu43uҵMԹW;g).gbiqN[PQ7>{uܵb2DzEkP@ k\ ysR1s1*Šz)b7l'hhEZ=\y) ȷ;K"CtPn[mOaq#VõVg5ڄvAbdMJ:H; 69<Ɋ2JD& O# Azkh%2i[]l"L`!@$#x A@xMP(̨f3,qJ '4Q^ATn#@[HeTCY7U088I %!lG OyGǫ@pSϊ@emc\51TL7JC;;?}=v{wm~l'ѥ]3ʫZ)ǖWR0EBn^1 ־꾪(%Aa$s:ڢ)CGDGfӆ-@8>J> #@tf"!|y*K8 7iׯX+>.8"&/(eVC4- );ZW:VZ4-&],JCut!s9`d}E[ é9aV7h쵈ˈE)XDex 8,k 6e517ӯZ߾8߮+;d%|[N/چAړNW0isuk5rnBfmGfg16U[SDO'㶼 ,Zg=ΫW]r';-*HXƪ$Wvw(R8UčEE&$HfJ (PeXSm/-(Bn`{CcchNg>f26_p6^pUdC.O{kxךUw&ȗsfg\L"Gnm\9[4w6eɶ8,I^$). b-VrٰŒcb++L zo N`#QTR[N,pDnʊXH , 4]ճțØXS<=]׶=R/]H\]f'16q Nٶ] ҠYKB;sh6s) Ҳ8 D7av>9QG8.Wy!]XLGΫjsvҖW7ZSc+WTk7&- RڍiW-8j7U >he4QL1^&zIZ.F;iO-,k nDlBuW%z  iਐIY@&y>HJ6FVšIV#uB=:6ž yl{j][HP?e:u@ZmiaXFj[41M~#_zq9ӮJltf&ZV+Ϗ;hT jqVk<%Ur0YBp :& 3^3tl&KjՍ}N-6ҜA#7LN6t5p^Y468Z]Ĭ @gaCos{= z[SY. ✚̗&{s WQ]ņRIo]AEcuFgP0ԭ=Qu]͙I8B`@!#1T~#ɣzP3$̠)^_!s$Z4KU rDz'yUh̒xk@BZ+<깒[x@񴅫aaʨCRzرq -:a8QM(T` `W,:tAItl }g(%[n[r83&Dw[{ (!{xsZ\lLb8mړ7M?J:w Wuga a!]nl qw!"!,`2Gt e m})ܥB|0MIRH͎V=@'"͍`K+,"II,"J\koNkL}6SԭnAQQo?l ΞGwX}% At0 T83гRR|d% l/5 GGgŽa=:X9980J-5QmM^Ù ^AOҴСgEjֳ4vzh Dʢ&0He<||yjh}X X7j=DC99,sA )K3R4KCeRIfe5VS4Bf ,f$00ɀ||RRo"HdhltWJP jS^BH|Hk9#Q@$+# K2ںQ;!'Y_ cob_N)u/0 e؇~$mC),h(SCzu#oV~F92 FyܬGX <*{b|rù+%9ml^yv-+8kפ :(ًJ[WW`diLt*7zS$!,֭qسLuۼ.~a,FU8(XA`k#f'q۳rjr~-nKzaacp`JL2ey!xy{߿P؉/K`yK /άyNS`NjT (\}gQEcE^$ Zjx`phFʜ@jiL2Q/YT^Nǔ (Y6ޥj! :yBnrpG˟-r >upRiq- AqÌ2KE7;k+aIv[uiYjGX|uruTBRD0dYРd'F@WGٻjOXY7GF{ :˪ÙHoLs$L >~W+7${p;WENVaXG)Q;9G\6=e2N.q+OUAVh$'+%4e+8RZOd3ԣi;HjKd4Zj% \fB^ػZ4dcE/ 7OlgN]5պ;SED+) vM$ A]xU!_n _2Q.D Q@)O'ה'u:+Ք8B 9R姓iaH)3A u_x0?%r/GuF#7D].ZgH80" H~MDcWHQpⳑd)n :uI#eqk05,=$Wηqh1.\9]6kz[S= |=ӈsc<` oZycOj_ ܄;B{#39Ew0-b7( "R؈^tf";5NMQs_P7szƋkFC'^ d̂O/stkvCBK ڜKߦk_zgbv7?Ya[y:ˇiq㡕F/ӭᪧ7 y"Pęf,\(`}ZI߬jϦň;mK oLrC/ޯ.'gJ͊,* q"huhL\/ G1Yr#;0&jHp 9ThRa l+A^jm/sv ]Or ״_ے @2>͊(F+BmeveknuJϑ,f;DEƟ6 qK.-lmG\vi.f.7?׉#טp^sMm਷9#Z߶6Ҷ喛T4#ų~F/Bz1a/f=~"}^[fܯ>8DHXgK( {҇EYse>ƒq%jkyH q5[{=ٴ%#.z$XȥWV2QWoGw5^@"54 (^M9t?F@  :w;PH@FTDžDuY:f+]vɗ1A:uN Gk]^ R |P&d,ڨ+a`'WgNrZ76g)`0GOj[uO\Ջy~*6NW=g!Փ3Fֻ;>N80/sͣo-g+H%Dg,N!j1hjw 03L ts[z^wCFq"K+"+[,15 71B𭭼N(e144C,""4P@Կ+у HZ6|I.M3K`T, J8\e hX >'/M.Q;YWߝ>!*Vҳ "#/v>0"4\H2b]:Eź"' s/ BAf8ł."cUihn?_g H6|oO Pó#q".rx]e"*ͫegU,hD쉈eX o@e3N^$mLvjv@ !)YO@ u_ILlJ ڿnt%EM|skEQ)CS#W/VΞ[06PEHS=?fwlrދ:ꅿ(-d焷?nl$To!I  o.˿b~Y^P<֧ bÊmW~ Oww}Bzw N*nW0 $I/z+JN_F5](G01 T bS1somv} Ǚ2_:`³XAb?0kU`MRŠW[xEtfRBNjoL2U?ylnWb/FQU5XRld 9ǛO75P!S'ʆ()R"T!\0) ;H,ϓټxoaXƁ^<=nbc? Y8,C8xkX; -%)E83cbcF58D% 4[VXixÔ)Fhb2͝vwۚu ju! 3ъj1Wi䌎̚wޯVu$E79M#Q fػ?YժZ^,I0Z`E/qztE¬l# ZH$ڒ]n< %lЌ`KޱVvWQPv%iQSsǂˈ藩CD.gN {MXP+@ms wi><{>h. T;%4^g{'IvXu)gVѝg i"J1`t0 m1ߝAWAj&x,i|hH6P Sqj'+x[Zc7W֗IP$4 GN^E ?^|.˫~xϟ\_.ʠ,|$^B8i әN FQQ׻p?5iuxB%t H(a`ռ@28s (͚VwlΆA !>O\XSs5BIQdKm; ݅8N]޴dagI^ә~gUwzÔFЎ@>*ݱA: ˟#l9˲n{{mj f1׼,j\DEpο<9gd1s1`)}ދ^$1щp/?|RW+>RU!ņ^7ЙYh@/u٣2c`uІ^[ *)xrA_\_(S} uTaM(Z_Bï`q/\ CNSPRPe#d HI/X4s``Ã1A$Gb:9{q %k4f<'"n=KgdŧVM;U\+j&Okn+{2ňDL0ʼnҚ-N<|ӞE5^$x-]*+ՂFJҐw;kKo9w8jhVLw>/d5Y%Wjz>A®e|$s)5+Gk߷Z9-MuUL\?sTDIqMVPq*T #o~W(hLbx a/_ӚW}U1 E"./Lh@.*GCob?ͫiMfULis| b2\j_q,o :;o>'tP77-B1DHoUk`iKd^vbWn=}&\وe7TUW86(^Jlf[=̘>aToӒՙ/i[ -GMZK /lFLR+([FIunlD>A%/zėhS;֜^GI$嗍hHpf0ʗ(3ztcŬ`&ix>4`7`JYSqrWAW3xua lˬi('Q Weİ0N2k"Չءs))hKFJ6F :(.>,u,d _kZcS,kL6aR$[Cs;0PTɯܯhֱW3\֨ʻL.6s{K#nt= K 3i!Ge,zحSEƌN$hK7~O=Qp/E+1Sb}mT`Y.v$p|~O_;Y~72Fm+8=7K><Mi :Xb{>w`ҥaNɘ繆tW&Y-s;v @ZBt`ɺ{rꙖ2N{~lZ@&k2/$m)$S+-h(5l ƕL¢i{bLR"Rjz&Z 9eD"acnK03|O[g ,@Mom8aX3^pxKfqڭ#\/W m%*}=tTy ae>|EYɸ HOZH|z'q }P>:Zby? C4:QbOF @UӢ'v~`n[j_?U;>}:Xgs $B%v>T=n͗*=;Xff7.?xӳ4B E!ŵ~?O謳;|k\(v׵:W~Ea7R-R\;TY٦ovW̢m#9Q( glQ@F&j*R_kxn:Ly"}+{o7a"K˵{PfuSfZ&w9 ٥1uZhrZnx=>cw ilc?]hXr"]j>(&hխ'Ezc.e[ZǤtyb@$D?R緙GuDˆ7Θb< 3DmÈNu~Qs7oLc3zsb``5 Wu*bdTڨ[͚w_!KibDA "u;DRAVnƢ`(c-!bPr$ PˉQ VR!  `l]VZ.4Sj?ȱc wH$P;q(+7xݶZm2Z@2!FFDϑ,ŗ55'\'MeIg:yo r\-ldԔWZxIzՁ3h'!;䪝obE(>!lpo;mB6@YZ@lnhv+0~W'뀳n @ٞ SY?A-5!H5'›=Tf7FN#eNL|F2Sw󪶫"1>XmIk]p҆MIᑟlpW^"%ƹq -"_4*kUG_Ewm/_xxrySr5 < [xQ$,a{oYd<_^M":QdZPʯ~K/֋̚c/v~^8R+T^R+4B ANTu;^Ef䛹)`ukHH|^gGLHq?0 8Lڸt\d֔v0j|n*Br=! fXCl,si|X{~O{mbmrjн!ыa |=j{JM?={ewn/:R;0 qcG▛?4i yo{*]xDR+[e #0EE~&A_6>vVNsi6>aP@z>z'A3ʀtEKtKH%'l@ݢ)D@X`gؓ+݆ŋB,颉(ji[خ:J`?xժB+;M*Wv<ަĸCIb$ŭuzϳ|z1)9}CV DEj'igR߅H֕ݦ[+C^ Ϥo $4vۦWm|en)4zP Qk(*]6sly}LCC#gJLo^W@BNS^TtJ0]k[ p(mK7se"I?',F EdM i'øA7Xt#sץW6޵U=/{>y.>M!;4C \=j]+a<+V|/e :"6sy1CGN<$5JBC_ߗ*W׾mxE-ޘ __]OyF KbDc"9 <צhƽn{KcOzG1a󮸸;h*fiLz#zLf$g1b+qȺOw>TR3Qwn[VHnER Bx>vӹvW.rE" #R6g3;7|ycD~7QsVҞV:c|M|`OARaI(P.~ݦ|vhg Qwy#V 걧o" 3miAm_KUݥ,b #p¥3g8#ag[YD{yhKnj v M_zaUӢϡ(,+tC5tw:Gd迕7h܃# a$bd oԝho\-<%7S9SRw(!Ɩb1-=0KbwFxc# tMEpfu-^Wݯ$: eIT@FHI>V>Ovn|VpB,TT uļ-[S?\*[>ޕ[=uxO7h\{]?x+䠧^u>kGVnԭDWx7\=и]Z!rK=qy74|2|٨g5U-+s[޹$vXY]7;UǺ2:{Wޙ\=￑#W:X !#N?gݙ\|WFDLy1??oLAOS1V2ߞJ7\t9sp䘵8Onrfpyma弢솆|,b;}duFǔRs㇊.  ,AO芀>7pUf3J"9!^?Rm (+]DtK#8T BKK'}Nڮ&nˡ%e*;i1~9i.@|pվ60*wό=]Z+zZ U -0hf"J|e胏ǻ~}9P(]ysmFz|Ҍ_ϸ3jjfò%3kC[; h>uu:s%6Y/o?-yq]K#ugQCL`*Cr r'F5w{BV~7JsInH:&JV.D[fyB[kW^魥3bSM(A<3(ύr#<س Ƞ#sLh[r͜Iʒ3jB 8¨8 T,m uuӇv ?~ki`9 !`=/1xK©lZܞ|fοC0ܽ޳L˼Y]=GaʪWUj`̩97_9hgO7C.(ZS2&)$GMsj|u+~qR1KNNn%# SO'?-bEu'lK͍)$dH?ʿ6ju]?-(Nr$̚0/g$}+Jxode3wN5ͩjuR]o6'?9;UpY]8qjq\J,5RЇHb{u1S3^-ԥ˴F4u>Z,[lEDʼn8 -Պ:{&U;K-H/ xo,KCu3^oWϫӖ"z)7ʲT[& " ߜi/*GW-og q_˺$4p_PިeR@+5eXQ ~Hw4<8 iWZiA@3Ө5$t) -0ÙW4R>plG%P/`|sV;NcþYe)wƽNfӝx4*|]_k֔s*DF I<ƖZ}Y?#K1͟{4By1K],8(0IHw<~Val7*5~W.?mnă@)Jw O8^bjf$“C _M*BI#skbGX,{W؃٥v`ގ+ϯ8mЪo>vwqewCTB$ d.lǾ0}t=~wϯ?ǮI'w'6 ӓ+י;EUgGGX.>dUW;U-r($BMSw4L, Aڛ;^ q2 G95;ln#A[ xF'0}<&jc[mBv$ x5GݶR2fvvj/ s&3QS7g¿FwƯĬ5w=J iU 5=hKǮ(>Q9%=,zFn8QҐQOy{?yx%knCVŧ>7>y2{%'R(G<~}C=9h/({͓oH' l55aǞ9jhb(Ȥi[@(e?}}gz{~u͛?q\52&qX.Uvo:&\G$Lj}'9WrKQۓ7<$^7,klp @{`̈-B=PoUu| ""0?Zge7k!Q1 \p_p1MCAFg4ɞWf4)WnSD:|@ (SfVcpc&M_X+\pǞni]Ж-\-)}?ep?iu)[tZ̦gEmɤ/&{2i^l_/tm6G}%:{'t}#zW_vEH]jDeCoGF4~ LcuZp ku7[;]+]H o?EZv￳;YOF~ACa8ė)%(n&گv~̬/?9FBHҐ^@ ?W:o=hCD¡ „4PQ 6Wa`Y3 CEPEHZGG|_m&O-W4]SvEm>6NB:vxgGUQaoOH3tIKGu)hC$M?E$KUVn dh?7ƌx c,>^ᕍ=[?+̚7~6_,bX`P|oۉ.| ФܕFb!m1 d'i7ʍALIP`Qb>3Ks 3J!~ӵgN?ZnݕsivMr|gWٹ\W#Zh 2no/#Ϟ+V~j9K9WBjbR?g}|{/ JLKlS_*vc02nu/MKͧM3\d/(tF-DC:o>8N_ٞj d)h[0b9g2-A4uqzmL&8`V{X/#o| eO^W@2v??+G:gŠF^X{5?Fww pj16Z}'w6]9SGo cwEZJ^O(׼>A(KY;F~{p/AN_8U|̠4#* ΀#K~}-e8eսk;ۨyyr bK~G3^椕OW^‡Ol  au?R1埖4x= }N"AgͲ'2?}]hJ:63dukA)ft]ihXN$ϦhY:YZw]y4qjSs y6u 2Vyʬ_p]<Ʉdt0S9{MÞy_?r:.9s6ܛs, Sb#uGB7~a? -?r*L" @8`J yy׼⏞tEi%a%D%-1,y"<ٛ!̚]91Wacǐљ5)!=r[?\ezۻTDV*0CVDRDb%gSD 9~=8mg zEr%O<'Oyȧ`dy^pF9+O5" m~ֶ띾=1)fSn$sey)UF9lK5{_~^6dm=f47+&qL[Z. (+ KNА>btJ[f#l¢.U&,gJu1|B;~+&B?hK9}W߲]&MEWbٳ_jG_x&Mϵq^H=>1SHfѽ sZ0%+΍VT|O d+4`\a6bdbz 2߬9x겋}kL5EheW7k3TLBEL QCۗ5kN }|2j$B&PH?eiLJyj>oO5kO?DHoyԬy/5[8ƄXa ,p.A{>2פg5NM$ݾc %yՁ;pssIsmjPd5D~KR XϦTQ_=i>_hҼ65j ݚս'PڊEѼ DZdwx,4ir0fjZ F)N9)$ )_o4onM}lq6$[ڰ'j,Tܾh?cGxZyoԃTڪMH2'*7'4|pCf 1M/cGp_a9Q~"Q%c.-2:08Q Hb << dR6/Ys@n4OZO/5VF Ȍ!NRW~Wrp5o?BmfMϟ4/os rz3F!!=+SC+q2*UOG4ƻ\C쫣G" :~ۥ-Ow9h8] fkD!Ϯ߉M4-Gۼ.5==55ȹDf}$Y0B>\P 5=~-y/ȫ5d_\$lrqf!_\BLm^LxU[&cHb;u#x`؏P2Oju۪'[,DϕJtSm%cB mi8|  Q]ٰܲ+l,\ (T<=:泟&q (?*ujQ9=uʲ)k$.HL u3lsbj: )M}P5 XR#ʴǕ>{ [5hɋNՙ'##[kOkiɏաŵ؄T:J!BR J|@xӵL]=<-i'/ȟT ]E %|}ϻ"du2˛:7ǜV#4 P0|@޸3E?e>xٯt8[ݶ{왽bZC¼$lIU)gp DDzF70h#H },nßAM[mTVxݭ?w vIbk%sF/fAgSW7ߚ ~}ugUSfnb/|RJ`ej!Ǡ̝ܽ+p(ƞ,So:4-- )a[fM5S~K ^j8aI8N=\ гע[w&`0'CkJ7۹&@,uɏHx b/D1C ݗAw|!} mLWE0ۘ Yyr5ykzT~[EB@nu_re MIs/;LzV1w]Y~äMYApbѣ}>ΥQ-T0to7wy^doq*QnqO7L? iC-;֒ 6pIaƴKڄT6kz}?WXZ9,FWS|-~ v-s~*5Y,APjqtqS]m tX6uPò˛#p4g,99 V ݡdQNF9׈d?GyXh*${=y~fhla6t܎K7_D<@PW@eYqp'˳#7V,z.>̓8 1MX.H|~`@I{UOl'_++wWXW^WbPvGnR{ѧj|W:SY.- UowaeȇƬrJm^FUq**niQbh"XﰐBZDr/8goL?׎HPvbsڑi".Dha.DA$8^ ~ yk_ϪZh e[*iň(ZLن('klmVE52gx n''X  S+VXm1XqkS'ٚ\;N%@S#&kNNk{5SUuatJޖk2yʫdQF;y Pi[BkNL Ñ\[bN"SQGsm (ۑzxLȏʹr\kpH .=a405PK L)P&4yI;__̧!MXXÀy F Z(P|@ } 9,$eݰlX"ݬ J'?YPyxW`H@V f#&_=FS&^=ݑ& FEJ)&g2Hl궮.SdP|zPg{u9ȈHGcGqyr &H$mK{Bxʀe x`  ;L^<jr@M&M.x z'^V^כLзFX  0Ҡ֚I7㶨O޹Zcnd?8xc )@Pڲ&4WOۢWNid]7Vqw o$ ߬A:-6m|u~LJoiN KԐr)V6X<:ǿɤ%"]LSOq#ݚl UWMFk=S&SS86mϹNM+Oz|~hPEQsPMY&URٕ$h GN(ms*xgmw\ C\C1Zxш , / Jk <;5W9#=(4hN_rxCՎ_I# pzϩg=*KGKxN-*x*>4p=IkQⵥvy ۟ *j' S>7P-\?ś̱̩V=Ar .xgS]rlzˇR)r*i@cGu*!*`(8qс-?}ňXi%8(+#pJ@./=%JSݸ1b.$0AEps7L1Vvҭt E熆/ȱ58cVI[|H5pj%Zvkt;rBNuQore@">ְB -4,w1~dB\VZW y`U%!! 54rDHFQ8G@%8xnZ&{#!, g`e_dF|ꅲgg΁Ӓ(P+Bx#=Ś(.L \Q$%QFYSOE[8[k<^>.+FѾK&$,FX-/`0$L|녊 dmxY`D %0YE09'!fOxcs#Z ڰ2k{"s(yt*a gzGMx9$ͮk=@b0~0pJJd`PL p2`礑W` xebwou- ;BJՁ1L>/Oqc 2o={ ! T#j< ؀ 6pA+ ;hr@LS 5+LߒY-q͉hQ^a62!l2#`䈆Xk)P4M B6hy4o-"H_H@_; CKPOF65U %Hߟ oxd=*@=Í{->Q4h]ojiMVg%dCqZL,MͻMW5YibӐ%XhVz4=S]d5P ~ugk%QP׌]*3յKf="4+Y :75h7=&}mǨЊ:FhA'! 33+{"ݕKUrW#Fl! J7p*Uk 'wTW;Bt/1 w  1}x;q"Y L2OFj\;.r`Jyޡ|-000ZDjdAM]=FTSŹ !a=ou{/ao XcX4pl _ZXQ^ÉK~3P tl)j#V`7NE熬"@$∺EUՊ 54fAy{-0dQM&2,,zbE;Df +$y۬58pw) Ibo= Мh0r^MBD r6?{jKK #5d],S @ VvZm=qc4 xt0/v:'6Zz%XS;1̒xEZz]<J")Mm&DgN~Β)tYՐ?,2IG6d?^To}AAL>b266`^diM+5Ve #qZ+NP$(T2Mhyrն /] Bi=ǖT!ji! Ae \l5RNZzSjXcF"(B$GdK;$ 5I75KJ5̗THE.!.z(,g RYAqMνpb&Sznh!ineՆxҌY-0;ԣ>M#9+ ʸL&,.SXf vt~1A 7l+#Z!HʴH==N؛t8YcaivͦX4u"F_4Qwx$mxnCnV)UBPr^J,O B^ |}am^QpǤ,{f#_Xx:hJQb36Ai1MU5IԔV30KBdV:m ?Jk:O63҄MnJ K>"YhȦtSw>l#j?u~L%[ >:: KvwtOeLԭ[M޳n$ Ȓf2eV u݆NԅBLc, |̸<@\OȪrhT n45+.X7R'm2n{* :)}/FA7U\j" ZQ^DVף}pz@u4jHgci!z=!:jq ͤYyӋ+/O{{?A%KdRgkCOGTWw#PTDtЭ&"F B;PGgIf Ocvtڷ#khGN3aTSaȎ0m"7ʑЛ;Iqq%Ȕ&-wo鹿AZ EA]L4!r9W肺(lerF1BhQDچܫPkePwc Qq "4l)<ɥa!|rh[a+I05WUM١SxAԢBLeB5i(d X;@FgUwM/osy6RThaCF_ `mOF6f9j ݧ ;~ 9-I ͚ RE6 'nVk͙W@f0 Q ݰZknq4JgdY($ZkeҬ[ M_@AJjv `T,(M*Frd XnU`t㚉G->7՛}6ERe[m&Z @5w:j?ۉ=C}`m앩;`Ҁ1N+1lKawNT_n;<4`~V\ݙ=rJvR2jX#?J/ߺx<5Mkg38yĥkuHS?Oyܮ4忕qW?1u47)xұ\f%.ӃX)2 /uSՏ^~wj`2Jo:êz~1rcl᥍w>C; )9Yu`Rۄ9( ]`$MkN-G? itS3sp./W x b S`C]-zG N[#O\UtU&0Y!GU֎us E %O69.'TBFДx'Eӑ~/l4M3>Ì7\ѷuHeN9JH֘oO.%ҍDZfD-)=VZ5D2iQҕہ;UqPm-AX0 Hl'۬aT>cd*dR'x;K :!ݨQE3侒Qg 6V䐈PyM3jtA/%-j `r7Y|2MB(P6atA ?U>~{WAˈ'.N2n2Kʮ>p-j C;!KrƃڣWveVhwŖ*97af"v5dQh(>%OB`JmCpYZ+f`i)&-w,D`tB(I<ߣ@vF%w=1ez;7eH5safwͲx*Ot<jn1AÑFAl+K36~QS. ꟊ]#f ΦB1a>7jWnߴ)f^ǭDYs{{;&B,(! 3TBf %Oa[b΋y* ْH(ED ZS~!%D)N3EV|31ZI xt] ¯ᘐ #Ϸf8hF V,W\89(`8t(.iJCOwXlTzٲڐQ4ZlEu1$<ɒCi(1HMl1 <.v\,ϓH*H[5e^"rT-VHֶdmĐ֐t{A75-d!mC\"3geZJ5F"UkwtӴdEJ: O>s WpF6J N9?)LBjq>.:t9ǦϹil*Y"""KIH%~1`;|vhP~ R>Uص!mR3ܧ {[t_mqhX%$Bcg9 툗}q&d۵Nܛ+yB%.܈P1lτmMkTgyºռ*1 & iɲP Qn@LRz_ж*AxVnO%@W.l([0 ˾PnL;,G{%%b aR1\y4ZB',hk;7]pAP31WuLng1rf8ZU_T E@LR6)q`ax-̼"b-+eZ2/TV~ǰif.MslT2#SQKU eKY^aWVs2qKew[<ݯj-1@VXOM3=as}ꉵmA =poX1iؒˎLSy̜:她Xo+c3wPsP%Ch`q8ކ-iN,W[9-'0,{T餇_HF[iLH/EeY\WrAx+stYt]91a,٨71B(P, JfY\C، pF[AWNm&o2Cnx" M"V23lbѰ獕5`ݫR$wWYJMFHZ{d[t=Ͷ{Q{(Kc9Ud7g}?nxO8)"Xmr:A^~s6)"vұo)yG!Tx06'ҽE4*]2kE(5 FP8-z&\6~>p8̤T4>~g2=8)H>X ]܋c5iz$DHf) 쪣UݱY7DI)(HN @EzJ8D^"m[0󳢂Nl1)6T\i-}z@MR\|EEr| Qw܂m@"̜NXMɦfWbW 7i%Q]0ab@VrNE6 ؐ$vO&P8's5:&pިTtş@ ݛr4r4YZS) ՜$c5YS|7[\$A;Pk%2K F@hբc ⧭,SeKL$ m`hK8Ri;;S&i3.kœu*ȱ0,Sեk0em_N(J5 B&F{ aNr{ïh:o59'#rM~G| Q㔟 aHmPqsT☈rcQuHYF=/S_ bD1T6W&Jl@? HԀ(pr ׹*Aa ͮUEY` CfH;'F.X5ZyEBFxձe:r*i rY耪umm2^dsJ∀]J4 ,j{_ۜHMTj EvPY압ha5592,H'/f E 8e< )2Oas=CN:gaMʎd6Ƴ⾮@-+ /1YScs(EQ/UaaVs~P ökpXsV0ըIv1ٹ_NAߚ7OIs #/c[NƥwD>1k"^;ί1}1@LE!t2ۍT².%k2ը\;񯄒 CGj Yk<ĥy&^ͱnP( R,%8vK&6/>6a&8ˉּzmYcv<{H/cdѴp[\SFj;ܿ}ց 0Z%Fme)CW).; $KmqnvSgZC# VI%:2ٿbK;砭kyvy[\C{h"ð2E8Tjh#/>(LJg)uiT]m.mnaiӬw&' lo1(~U7'M <%Rxb.rDs,{ѬFAޔ-, ZS{4.3VU.d~}cӌr8^]Яlzz,mkۤzK] ajF:'oLt9xq%˙d#ҪΘuV:DPZ+D1*ݼTr.%Z׫Xg2\J>h.~GQN金E~]ب_/S `_jspC֭?WS D|#Nr:OQwD)"^>CVfiA$IfZKçzc +6TOBŔGVIeÖ d1iMv>9ȼ}ոvǮ|g0>M=o6kyepb㐌Wk2V_S0!AkƍӖoAbbGnB߲L:g.^D%)D=ޱ/}qf7* (Z}L,;:c!#VjXO^&9mD[dSyo V[:ڢpAV j(FiF/~g._܆ߴdTXeJuN¾ kt桧n -gYޣ #vJvaDMoxᾨ7@'; FE6O>cќ F887hVyb? Qtί i/UDQTT7`yҊV&,Cn}VziҊsiA )^;S}:Zbk;uK ꁔ#B<Ź>oűIUUpYw1uNi)K%j;{j0If"ڜN &elvzmIZʔ}a׽dyDzeիM֞'2 >j#Q,j1ԍ'D1>8FS_Dž2Y,a(δՀ6F[.OYPF.GԐC~,'nGHvFb8'M! wR=o=7Arzupb9 ,d|V4aCcj2j)Q0G!}=RI2R-IҚ L 16^|G QFfsm CsfW<@XUm0`pwORYtv{x-1R^8KuBov d;3:Q"0 ,F]v2j"X͜+}QCMLݺ65ވ uw9.7k5_G1_Ǩ$w8;&B H$+$ bԄK._`5toRʎ@(, (ɤAs`(Rƞ5up6p l\n„*.Jd5ÉhunC6&VF뮼,c>L)B\o&ˋ(37֙6x4`=M6ٛ#*u[w'6:bkHB!aYrbL7Ty*MZDo `L3OﷶyU6+6l7jtƒ79aU6!3ݕ G@b fv}ј1yTݹN>Fl:޵'67w@M8}跲R4O=\+yD #`i;gPܕS >'6IWz:_# A-N>PV3An)/fL*"١[T2Pn8/% ! MdP&u Ea23з0Ju\Bj2V<ظqA+f~S_}16}U}W}OW֤ ֭qO56pq%E(yr͘\ܱVomtJ_vʨi>݃,#{S[9vn.XhgMFri'3;tYSPM wW]XOZ9v3z喷/C}1jcU?m*_͂%v,\Y'Fk{Kg" ="3vXZix=B]Fo{;nAvc~5qK8jtbsnpױt{ᯎv3c9qnt(2 6Rw?i(YyNu2:@b]o[+"qbmT%[1mxh.~qq+5q ɹTթV C6>456HrfTTcҝaM6h% p4O{׭OUom'胛r@~yr,*ftĠ06AƉ=ws[oPm}|EvODޒt!7nⷖ#W ;v_vuÇUTl}SZnYcQ ]mbdrW>wN_t'*+AkzL=2ǭ=PrQ69u-v*pvg/G[^Gv7o6m<1,?ɵm{*{D~:Mv}'@2M'ZXx-t>x~[šBs5|O1 17#4Լƛ _(k~SV}gs>:ӥ4e(<MF0?>RHV]}FgLaXG9Xg1%vؐw& _ c˱_fJ~CGy`z#6%R9v-|}6] cͧSoO~C 2]^xaR9Œ-ۼqrPZCs팪X]M"ehS}i;vEYxwj6 PZevǩs?fg~Eu+N(1G7/+- t"4:E%y'ي&Z4J60NM(t^1,-rz;fn,se\\6fCPxۢr#ەpDc4QL|iNiY (K#X 7$#)o|*Tݤ_TKJ0]LW @ΑkGT_c%5s)Bh͖alX*shY^j2a6@"eP?pYXbn{b2CHuvjE=A!*g}E" fB-I@", - >!A#ekkLG,'.$`13JY,Xo-.)Y!s-By=;[;(yָŰq9vz3gK-mC#"4 1y+t:v!Dy {}D8* @`l٭2p:pfxXWqYeD7ZL l Hamwx7Ar?=BO`H&h42BNRM^KBh7@ bq q5* It)jArp1׽3+tiv\cBH51Dmx!RH#.xHgNkKaP*B/Qٴe5͟ė{ei(,^-6PN%V)QU Y.pf5LV[G dl^,3Z".h.ē1@}da0M-] ɒ;jBW30ȊkԄ0P(E:^^'>Gi%֔x75\1Y(mm?H3"#l54WU7:Tx5|NRWe. %5DdX&;ƪM/0c8#!46V"h6V/d苚9$*4%ҙ B!+"f-{|P̥q! ).U+(M\A?)]"b5#A䨑jLw,l8nۃZ( g,3>5@N 4kn_CbT6n+]}x'c9 PHឍyXL\EXMz W!YYP91qщp"+of׏1Frq3^#.?,#STaޫF&d WHҙmVrB-֤*֕(<+H6)-))\p1zꑍ,a\t/?T͖G.&M[7 vΏ&?[?cZcKQ=Z߽OaBu`ϕԿ54*K9Felb9銆}7ԍmlkBt:`,{i`m=:Č4}KE׋ &#lFJ4='o}pG\Ny8 S+08T:.4XE'Uj=@=!CN,!ƄmzdQ/5חh&hYSQXS)0 S MˣIqV_*ˢhVX1hIW/gb*`EҺJItR\~I_:parl pUV]c4L,&5+{GK^4/J!`P;DwP1v@UK5Ӄm?ZeA"ck\})lUyOϴ6.GI. !hq2_m){)4`N8 <=P5{yy-+k 3-VkYA^`-PalvWuQyT*A*/BtT)8X8v[^9 \㧄M87[Uɣ) \!%@TR#h^1OAQQ eQ]z`(<L>B\NY<3P{Up)=a,Lլ} Ϲʕi?T{ so}An^A6\M7tX_Ӧh"Y-ӠIfWeaJYͫ8 NP#} Ag^4-}yN/{i8bYV`*9=M Y"]]}hdkn(ΐg O WT_Ei=Q3ёM\ai ?w9fL#6҉Q{t61xیG jE[[M+S:n{6tL|팸fg׃n|Ұ@-ssU<5C4걺?3cub"*,SnƧЉE1ؿq o*eW]֜Ym4)hB.Lʖ^:v3}Vy/ Ӆ{J3(8r#Co32\OLƭqcAX3ӿ6E`&e4(D!3`oyw ~u޵:*[bei\hϼQћe{lrn &)N,Mલ ĺJPB{;θO|<篕ao9l}^t<5QpZoDF=֫3b3ѱ/尡slȟD؈(ԯ СP{c/{X{55T"Ai!.j2}d8pF\SаsE}ykfN}['Vd~hu(v&z*iw9k;\֩9N3U=N<:2ޖ/+Zj#!yfWBEx_7YvHu>4{ZiMQ0gN&gmX8US4AZ@' :T#Z_ZgkHo bҼrW('fTHՠL/ZhīƞҨ(NΔ1pE 1Uł8gh[hbQ$`  #H+IK-p,I"RTU@M$S5є =[\k\C!ʒBfG#sj7I.AdYXZDl&L;aHˤ'˸K*Q߉cBa~s7KXaȡץ]MknpRá`[g_Dy0,$}ĢAHz,&[ ;PPi-_I]A^'1;eS`Q|x=+ag2c^'u#/e!YiOāhcWj2@@]յZ<ytBVԊ ʗWl)Ty(#e^z ,-42lTKPDkӚ_RްxM|Јf(IڲYú^08z4hn׺?ImE/ymIffҋiAD >xӭM cݱ#aԅ3Np90>xVM`EAkG^yr8PeUs+O;X8ʹ&|\(UK J#&zh:pz-ࠏM9; o%E`f)PD$kŨ5 ^`5 aL} d$IbIy4d,d5꺤lv~W!sR b%)b['-Zf=Zx-͋f ݮ!5v8["4'BhdI@:(!㚳Ȗh1jty6[Wu|-&4'Uoϧ)E9f\(H\9KPh Lm'fW 2Tխok&5wOa]͔ V5ϥ;C)ioL|e VT<孌&yBUT4wYT뽁42Cy3%&+;t&OrYswҼ;Pjl 7x"ŭDcM}&Ͱm4lݝ˅3à Y0!=3I֐Ω= FLlZlo %/Y -!4Qܩ9S=1,]ȊtC1 h"p@V͒ҽ9a$0MuT+ۙI*,LӁsZ0,L׽ϙK>Nk6vw眔ʆ`bE<6o+oзFf-;{㴦z籌oOpQ 9\-5mz]$Ȗ5}G.a߽5w۳ZX*§N*3{D= e׵ZlCE5ѢK*Oq90` .*  }׫TB.0&]dew:{ckϽV&dK?}H[mAvU;WzT2Hť>ŸA; J[ޫHX'.}UW  PofW M.uŘn7 baAJM+f @'[ժQ;,(W:B OF -Ф{v%b^&W1@@uC1"Qȅbai@? `D79_F.\iA{e[xR$.5 b,+jo5Mi>V>%B"l-=cX}ity !ʱW_4fFPnb\cIcE#RҴ0Y A ֮h!.#Dm;F odbtUrAN!v"Z@k1XH0pU*F:Qq[r!gء[CKGs꥾cԄtb0 \B$=B(GN@VDJvzU7Kݬ3XX+-Nx%*bهT%3"0K^>{ݚ pAl" Il00faaPV45uy˜hƴ>E|Qe^R7S3&FdeƆe&i <'ccYNT!@:wH(QEx91LUT[ w PrGTN`?\REYHsd X‚aP(i~9e44t]ZYyGmS^TI72I;n"&,Zy~84;zu`Hx>QT H T4#;zatnנC*@c+kz>=L\"YV\m4Bb+VQ{nkQt^.޳L̴@&Z9Q=i-] #O= tY`v}aQi%噅JZDhsȶxiI{+dn]eŝ.d͋fh]e5x]$3({Q$99/5:‹ :g`gլ9"% {MZPl882[Fy~|T*:3vL="8V"U* 8ZhJSzڵAfbK&yu:A|b-dfTǓC+yr9ň&r庂(4N\ZA [5il3մy)YeZgcδc d) !&%sH=C6稾“fYڊ6* Aul2hD, A6xgBF)l@!Æ|.Q5gm|ljn>ӟ}+{3Q~PԅӕD"8DL~7N$D#ci^u2#&8,J]fC Evo=ɴ)&TcVPϕR 쨐 ;0$JijN5Lk| r s6s0 >,$^21tv쉴f+ W62@䏼bvw杄cWjɽe~[ryQ}~V9%+ NjIo5[#WyE<;q%,&:x fԜx{z5sL6&KC2'+#L'2kHӺ^cFb'X88ZI)xM[miz Eewmjz€ ȐR$7.D!tF=و[5Dh2@-{ /][ d\lDI>?p+kt!>3BB$8%i0'~=FMW#,n23/ ͂/UhfW*!vQpfx32Z2VN+#a؅ m?Aߨ9K߰Q&ˏ+QeKd%j( 9ߤbt3BT_pg ;ebP9TJ7jzv {?Vv]s$PK ٫.E R7`&=5aK$j;g8|Ԭ* VL81 HDА;/QxGPfpT8o:T(^u*ԝj8A,0=:+ͱȽ;/TT&0?yzm=NQ&E|Ғ⠲o^ }̹rO"sKөPתn@,c )- Z.l1VAEI)ԘIl\)C7*#5ɮOT/6<,>zѷ9nZHҹ?Ug*7QYЦSRS"/;pm{AيP]-j7atlAS7آvY9nX/\{Rr'r^&YAFk./4~[rl^+)%MAt3z{6,,i/4l[MizВU`%V,Xm:SMnyoaZwJ4oEGV"S/?L߳_{7o~Ŀ7{!ꕢ$h3 N̚=z4ɲ~Y*;ZKFTF0JVj%ɋf]{l[y:*e W}[DqtRc,}iVd~jhI? oBlE[O3m͘@j[])<D.v.1a3~G)76fI5Ӛ=vyC/u{Doz1pe‹k6率y۬cGU=."DU&SeB cDBF<c *d&^{ rSo8:2TZ$n8v>Ul<|{JDI ѕhLT&T4BMG|5g4J&zddQ ಗ5I]P!=x5\H4kg;-xox&,:bNp¨ eU|"ԃe l + Cki:z̃/n-yuqx,&ҍUesSNK)Yl2&cm15e{ܻ&o*ϙ^w^pڼcJ >ߦ5 {)] ÁQa'0N8ZS.uZ6gN:h.D)<%5ǘty /H9 eՊ7b^ #^8;B(6D"  bE:^ΨQS jVD¹KXUZQh a+gAYX4A"O\UȽNɯ,!Lx+9댳4׬zѶxZ2yyyrIC#6j/W/xS`6m#%vc,iyq[]CaًAT0tB£3ht!)*iAl%?XE}וq4b$DI<%Ӛk=N9M6ɟѶrP%XSOx9Yٹ5o1ۖ2&*(3]{tAøl !8IQI+tߢƚzYD%)T݀9.f[˨ڳ1h̷ QH#^=^zȋfP\j|زFY_}קNCd~KPa$&Nk7[iXaZ^z i+ڢdo9cwϖ6ʢqn\@Iaěxw~;10`4tvZ~4 h&>wν`XjJ6ن c;@1VxoR;+LDg5䬟f61i@6a by/{. ddlp%v_ %?=pG''Y(zv+YHhuDr¸}D?N'y(S8(حk"-/^ ֏~`eqq>)s|]ceˆr<_{kZԖ ޮHc?q(Os _/+^Zt<|`UO%P̟qjJ./RBso;AD{C߿ŏ~#T1 hՙ>oXA@Cf'0(xۇهܥ%7 ̤#a4psGzB؇CHڞ![*jImIF5EhŮ`9=tzWDIч<%GUK*ua4$G+,*x,?CTns)҇l# ~a!Bs MF~aK3=@o1BeQC_VX _5NjEO9aB DS/TKZ&JGVeڝI'*؍ 늶"Ӟ%fL:19oĎCi-GNMӏO̤;t]NҌ:Sڦvy2&G- 댭iIse{3^NzPaȠŕ8z֝-/[2?HjMV%.8 OjW'5L\)SU#?}V'{?dq}'h UŖ9.͔ڞ4/y 1j#!̏R%Vd 1edNg_+E@U])N.7,51` Ń U&Zqp8U;тSYD\QmSfR7T" (,dijj~q(JEcKoxs&N&w$3P7K+ïk=W늕O;H~Y39֚<|~ˍn=1$۽O~zSc*x33/gLM4K J՞dTti`ڇ4&zޫz]@]<]m#L-!m|ShMDjЂwȱ<͸;(EewNZ$!BA>W}5w~yݣlA8vU'Dy⿹SC֚|ei.w氾o!33!;'53O˜ۣ ]:A7O4ͩQ^0 꿅&_v'Oo6r3YJ;|qE܍Ro{ٯ:S!&C/GdOBП,خs˧mlK]PَI/֚Oy5wgEVsX헋,9z z$h] ]͙rK[IĊ3~5=wPOB?M]{ڎ=,9~ɠik+Lu11]h;i/gϿ0tG:yˏtR3NjOV; NjeE}>2M".Љi Y[]人 ݶ剟 BSo*OMitV~T%~w5ɛ>42ަtJ(VCֺ _WW myZYFW&aDBQN4: @ vZjc]Ns#'akY3犵[<ܝVIlf4Po0'&: &gG<iI;#?@ЈyEUAt*^ÁF &@rF^6Gg8 $42R-rм^]4,"cs&2 AĠQA5w\F2E N0,g8ksp(A7'cF1{fRT UPZb#|- :PГS5f\cҸ𥪣L&k _HGwR6sURiUG:S„CO1c=֚s(I ݱב|Ew2kpQ+6RjVZ*F|v ,4a/I8(Yʾ39Ǩ _f`IE9j&m'|aQ \Y^]+?Wj^{ ]b8O"̚wOT OJ$Q]+?^j~LpI4q{F4hR(b(Iy餥jZ]1JTcc*\O_팦.^H~GB8KF\rÔݬ1wh0J43.KFNɩd8X2r^N _-x_op0ث?/\1qxI5Φ5N&2uGU IX-8"ʔ{C=iYkވœXeUK{+CEMygq KwƑՑs`|W9F=1. "悽~_Vw:C,0!I5h p! 0BYoSI]@)M}+IGkPC&@HN,Fl`q?yX+H%ԩ#@X콎~Ik Z5j ou5ہan9Ӗ=<Ӝ\HнzBrM~i;@N )B"B[i]m5Z G \@]vTOj:Iρq:n4y_ z'C`i j{A$||#5b5-"\o SO\4<0%˃t$mYU7QΠ!xh91<}EV}y]YhHe%iUy750m^X 9 ~6NLmvHJ,.LfX!~7ӽa^jicS\#hUk+YRoGS~LVfUxrf ИͮWƉ-`W<3"! S H$MbPff*^xsQ3Ji^(K w ٛ/8NL ! FT$( 3WOZ;0,ojŁd{H X1oBtJ3}Y~TEQB88پF"C]b`r42> `u!X ,x{ET#ywΨkى0dh7\x7,T;F,-$h'HihrPLɠ\}%ڙ|隻[cP M qX&τm:zL~pSr2ih,phAYsLtwՕ-&{08n.޶{Zfm)s˻AŤy^IX2G֚43G_jԦzI&5khJh ̚"!\hIr~GN ̱i6[|V_f P(JJHa0GiæFǘ4BSǚg^Ue[h I\*̌\1&MOrmͦC0iXY 䵪7tcLU狽L,C sF\lx"yfcLgD')NRC1&(`aAY:3 Nae vjI}]ƕ_KXQV앤W΅]gv%GYkPQ{A~SMyũkvƛ#xs2q) ŁiP rЮ ))LZF eT T"D Xi:e| / Wï\((PI%Y7P x puj ! {Po^peT_\Ԭk +E Hv|"֚!#6PpϷlTSGxoteZ \4p߆ն;օƀg|0l[N>5;&=f B+Tv+)^ωn}qڼv6=MjY DXde/hTL7|?"1fMw Y|;OYEI<皬b,?&OteOMML|DTdVd;q;j6Rd$&OPe;&*pE0FHU!xgZò0(͂Km*Nҫf h, A*Hpd@vBB1Bϙ5eĎ@˧;Hİg [;PB(͚k+IJXaJ=8MyD IM/cfZhN/akh= C&;|,=[h][>0?\*{'=#b6 Y+>'eU,6 eBsBK?1(^xG*y+-$Jzg6i#GVPeJxhv34keIId -4ȍCN!BnT)U0Xfu1hQhyvt}EGB TRK+ 1{+yhy__Aҁ&*GyƆ|4-4Od G)t J)+fMsR!VMa5H%P,۸ =׾I֓ FPN@fA\tk{e K")&f 482 v) #5U41$8, HˤSyR^Ur: ?4@4,4cvN#JquSm |+^~Z(iQ ITj%<%VT \Ij.Y%˨ :f =#]Dׄ2dWi[&\\8 @&I@l"Nt_x|+p`)8D AEEFLRF?&?0mDRb1+ZxT3Lqȼ-Z66Dcl1E%fNj+cD =:K5A MI[(W$iqYU-4!s|*uj$" FHi8ǟ=qMqPgyo͚Ym^-nfé =,w%gl)vȒL5i?vKl8sy-" 80$Ќ9F1vl?k0ǽrp`$L_mL!;W0xZCBGR{1M# %CfM {9r1!ҬCJk*kg#jx=djN?M3zlEY@ n'>sdA#DU񤭳5Sak* * "8Ɋ+̕z8L~bVf8yWB* 2>;o iγP}lYw6ks_%ut!Z*1%MX2s ͂f`]銕3"K&~ f&6E\S# UFVt(ak2iǦL9g|[RLt fU<^v A؀oYh|e]whV.9QAHQ$&TDE2Zh5EsPN^+1IjEH ߚ5sg&ݷ(=:S$C:ՓH%Ɗ\֬^{fҫ;gO&E, % ֘YJZH~s[fzv1YM.FB<$\fvҎLc1OG0FRl oq Pv3ʱ؛OB"NiWw b68*S"4Yj:aD^*jeD-i ط\}A6l:j" P{-#KW~V''$C`6),e?eSi㖂O"* 2S:O#6M7'?׵g͚Z {}+w=].C#ۂINW-ˣMGvEIH]L/mv*یqҮpʿg8`(:rGrxbeO+8B\*6\8vp V_mZHpKwxn)kow7J`$kk4(㵌8TzwxD;s"|wKTV2u3U^#!H󾏜x:%jJKذ GI;\oI5 G85Z,KnlwqyUDQ.]R.'niͣ06yAj'uT]qИDFk4a׬w cԄa 9blw5`*;'0];}͚-4M5_ ؆gZKes\oPn5i~ۧW;J‰霛mWaqMj~ruabש{͚P cw㸲qXD֙)geINFνbة?ƌׅ#<>p0 ۙ_POޔ=v ;mv8fXͼ%kT>}NQnU:RJFݶGPH¾xbTu+ǕbOѿ L'P_Xߐ\\b3 eNtN0CcY~07ڪ+_U_`u&J,Z|L#Y>a3UQ#;m9gԁÜ9BAq;J6VGEb95ڬ8"r-ǎ*?KڎS{g'L۾-.JhĞ E 3j\rqRA$ [U+^-Dp 6kʆ `e5(@*oKF #K ֆb_X̠Y~@h''^Vk@YSg#Xp"#~9⥑ߥblɐ gٌQ|^SYACnM+2fLI4![7ZKDUPatB [H/ U5c7؜ݺ[9;laGH/蚭{w59{`ȈYWY?8jQZ߰gL !UuQ`@Σ$6̘$ &3:dĮA. m9Xa3"Eփfb‰i^TvؼoڤQJ,D(8G&J#4)l41XJFy])j60kS1B፜έ9&U@!ll "8`ĝ9BRg H@*%]ij} m@{ЬL|s0DUL ^)ʎkJ RyeDk@|w u@"X fҐQԴIvIC(cSC('Q pYZ ;({s=KԷgu*axɜtxcG-϶SZ0(["=le(2shԇgg[+J"N%S ײP`hU55O?bbs/z`$FT(˘+pԞgaOI2jд?Y:/liliE8:k7̬wI~bldԸ?x@/ R'JZ`)M” 1K7|=oϝ}#ᬦ\|ޫ]y)!v1yy5eQ]NH}`ģ4f<'XBٻP+z+֪aR4ch3lfwȆl\5I -AO"&*6Lx3m7$mք`5jS*8{kԜ(tL$˦2Su3Rs"PAɄ͚V4D`7X1sn=k_(.8>OE QML?mٯB bEdFղXHaЯlV0ȭ(Fś= +# nlN7C^i/*Jk7 qOaޟPk߮9Ne։ `9kP7B!þ`fUCdC2_*_aILnST:dJL_s][_>{>EiUs]Bø\5o8=_rQa}rGb8 8.uc7z{u5=x= תn/1Քh9}w}o&K V; I'^#ma[} 4Z{[hAfA5-q'N(P^myLVz[}P({an0)΄9wa[PĤ2Y"O4 >qɦ%ꈪ6Qo->?)7M2dcY9$by!%2Z+Y{nXhOKPq‰*Uvz. '[\<^[.QBjkJ,pk; ,4_O y6wVqMJF"JJ`EW>)١y|fH^aSL&\ŅQ(vNa‹I2Q},SLϞkamV u}:EaTKִJ O$gVт+P̬mRȺaaG.SP%6bɩd&X [gg ùX)GWNo7B#8y∍uKVT59ל,42T!,fY ETa㎔YD)Y&"Kj+^qAݦٚ,^v|fiFo)ܴ[77d9W+藔*V;8ݜa_oʒm'h&)TQ@ SD)Q MLU^L;5ʿښ&>" :\MSH _$3we  Q2.<^qjyi*& ͧ؝X_j:Cc0Ik;RzGnrs5B#Ӊ|VG,΁nBe֥1YRf2x֥,4gVlqrYO)sOβШBwu&Yǁlur, NgX[ cuEJOpie:xrYfMa_tX`h\L) T0LXEmUZ K(wyza/\dfMaP\%SxtDAPMECyp.B pigTqər*!ƐCsέϾEuPmlUoy !tV7^1p>M%p|^ ͒>RFlUfl̺e\_;T{F$όO'IcJcw 2Ⱥmt5Ú G2c\$Jٵ&T 'r>- `D퀉PW돕ːR#6t kqDlAUk.Q<a t3W TJTدVlŦA,BQv,ml*0ƭr%ƙyjc cTiƓ=>ц;j8}W~!pN.VfrY)1SB2֟ Wر{W+$oMLT0V`qdjD9_9MQAagg0 89w2UZ>unU!WLdPt2=^+];/nSxKX``80zi>zֻqشA" }6uEG\^&}J~Z^[;0Ah8Ā' Tlb,0xZhKqRa&K6<ϭ.@tvUmiy#_SJ@pِo>0j<7cLsZ*֓.B,` yV>7Z2Y-˳9GdEY)ώ YMN[؈Q+*)1dBIZFIBVW6v%.v0(-T?>]뇙 Jm2MV3s@cKB!tѤȯV>/7,R8SJYJb>OTIpdaLمҬ4{5u'fTnQU2C8XSY[2 s@PGߐgyذ@1`D7Y3ް8.:9z@_}}HJWiDm[h,45a^5?r WPT\&,L8giz03 aK{+S@VhdHfT8YɳDJݐ3|Am.>Yy>d1[cx+VvH*3x+"ԭ+i^؞U𞧾f{݇k-8l_"X]l43SVR 3GYy؋j5ws7VbutΪelN"lBr"|eAƞg=4k&uܰ: ۪%x)T<}ÈmgԬi.OL|JRYJA!B{0tDYhD *KVFK%e*%2jMy@1$:qZG0AKتCshкf\+s֎ taoT^C=ܜ,Њ=k_Η#*hycq[uPN&{p~u0I)%9yڲe"ZRIeؠ(v{! )EOPOheIU%Ge<>jr_bS-<_Z=GɘuY/Sd$B^y␄0um|`(y܇w}3fjϼSrE:-24^x"&A$x/_)êuzU$E#Fu>I V3ƮLixKՁ[\dV]Z9|tzjRA |`F<ہU}V5Wvo PwԢ`5]br<~GuwbZܬV![v娘u3/_7kG2T4D3&J=ZoqӾv}>028 aƴl(eIs&_7.XTkYUC1ܖJ0onELtՆ3Ę%zRYc֗ \1G-:^[kV XϝaDgȎeׄ;VFtA갞 rI'5i/'UZ38 fe3FP$KHzh>n/e~1Ul5c ŽOrC7Ch'!/6WK2Ý,@idkY[_FKx5v{Eݪ,bO;!ej&VK뺺TvWpPw$v嬳hE_WgWBM .+dnlˋviCh6PVd m+f.M^xRQ:VS 0$>J3-l3v[*i4P]f+M햘S E͖uҌ3)fYÊh=3'‰|ߗc!t6oq rka|Y?[ ROŪ}Љ9wt_Fn_[תvYrLՏ8!Eΐ 8:sקOA'"D^5C&`EmUf e2:BEB(W%HK,4-q5gS/\,̉BMpH U52hTVf}Y3}6ٛ=OB9A' B25hfaL]ըӞ#>4ɑdef="L$ギJLA`#KtioS$/5UZJ:F(X@HUT[c1N?^센[3%yu߅h=aMƭq )L 6d'a(RR+LZ֤`MCeH0!| 8 %Sh_*74uCy?Y. (MO.23?{c+l}^&DEMEN~mv&1?|0m[2>Y gjΠZ` $Spшٙ4"+Ȉ9C:9_Π/}wyy(dEFVZa .y&Cv,L} P & 4KXY}t&Zs{/?v2xu';832N(@f^DE*۳T?7Zx{J7qGh~V$j]H,0>͎GT[ 98>dcO`I_oI"^5\ .{]sT{jBw '|E6r8ENu,~RtR:.Ւ5,ꓪPX,k'Rz %*eAmKL"c6,FYó6t"X/QΨtO`6_Ĩ"%|*.9'&RIXQ!(Km\s&@8tvL.D5ûżvj~g~e;tg`N.mX[a3'1hN-m_̹v$*gs N5[]sCQЄTC0$AC qVQv3Ldg)re-5jbe38+C t=MZ>eX1`cen,FM2qV7 Og>RA5)<=Kk )"=.6kF ;ҥRLPCv_Le#4._8p~ȤY_6d?i 1a)?Y-!."Ҏzٜ¡(>, O! H<ښ%~6o{]$Oϑ^q-zI_{&8Dk6_8iѬXk wkLuXQp[e5LZ3&t AБR{M_a8[J*yPdCVi$P/MT1j8:+DLzFW!CZy׬{|Vq$ }.ɤ9&|hյ p͆.(w&؝Iyvj(Jő HBL+om*D̹bz/;Pޕ6G5Vp@ˤY C,Q\k<9B@n8 J_F! DVnNN&hCdZΥˎWmCnMXAcC;_+&^цQO\f;&r3hֺ+/D654Xk ]>2k b om5eJZRv=zs2hL@idk#&L]4S6ާb|5/͚:+K5UЩ8>VS]x4F`:AIfĺl^Y\UNcongj! R\<^Ew\!OzGw?˶y)[k;wϠ^ۢ>9:bME^r<;ܜA][ =:iG3wHŁP(f!Z3{wO}mn9-#7=e3:DKǼ񚍌sX&с=?K-0Zzkf {Oj#,߸nԄ:C4.UݣCj+}#7fĸ'ìy_nԠaPuqo0^0R25 EÍp-Yx봗ۭ0շ(E~U&``cШpCa mpχlf{rV gTG5A䱖2F[b=s V^m)"%dDp 7,k\)\JdV[JðN<\γȢ7Mh*M ŒC M.MEMK,t_.r:DH: ?/r|ߙ1<%?gS"R-hj+Tb3)E/&]{qdIЄ}J\)@ugQ'FԡPE K?VEnL:%ϫ(Z# OdM8*g?X|Z:?..`s|@ uǣƨ$lzBK/h]Fk!.Ujj%s)u7`8˂'hTق3yjXhL`9՘ :,)D-f|tS8RbiL.ŠǵG;ϖ98ȣb&E>?%JD1Tܠ+нHvXjv[7~~jhS{]n0.PC \ě?ٖr:R=M tWjd=ZM鼞: \E)8CΙ(m)%N6.8ح)|p3b)rU%Ԑ=hL@P<1'O6>v+))ggם VD76'f ֚}+IIiFjt9%<3?Zo$^ȟ,^F^0+浭 ookVƾQo6~2&JUt̋[jgɠiZI'1'$Y@(ElDAon9GfVI9$4ܹ¬٠綵\ǂFœzKs"wا⡋hҐW]DWwliYskIc^ ;INlO M1w۽e՚;^vN30uW;gr0WK ͝!/a=/v*X3_W{%7f \%OaT'5տI;oRek:y ݡh`U>9ȨUm՛w^yMkUp8KC #+Uh&C<ʊxktu[>#e] Nұ  0`lYm WqT_l6򠡤&"D|_ߘLnZT3BChhZ8\Wl܌.1'}[\ۋ$ i0ܥY);1#aR̄I~l~Ǫ93f AddϮF/zZru[}Fٽ={:{F~(9̫*s[>׾[QiTf'l-\G7KfņdoW`b*Z^MD6B_Y~9p0FňPSx)2 3_kDZm:L)@ATLm+Y4D?0k6\r yC:Wg,,؝U2~1|v.S>h2JJ٢}0V?Lii.R3YKTnZs(Vg:B& 1w[7\P[jEHOzj6~P.Oh9H[Y{14<݉XW~{xz|jgϞ ? [x6[{0V>LUS01×oڌ(6,]boKM׷kpӗ=kѕĚ#Mx-sׯ^]qf7ևNZ=G=Қ_Z*N<ۗOƯT6^ \ҹ4$GSW:X577oumiMZX \i8l]}](O9&7`R*0w-.3f'RuXkn})oj5 4U~S\|Vgs)?wAhoWdkf6oӹt*B53(hn{{H)h:g dgښ4s![]w߻&j1Zq8p5vB9gntNm?=fq/7潆vA0%3gni #!X,l-XҿT3gnˤy/7ܼѼwiA \(k1{ (W͜73{݂P+ˉ( SՆfh1ww,.ffGA9SfcfX ydv+D%9;;ՏK=<[6J޼K n3kL1_ټ޸-ȧvm&y Ti\V/K}Ģzx^Q/-L@%2ώ,/|$I_qî#/s,e8e?;g/%'=U35*,riC"R#H"nNXl8AXxc#3 !Bq Ib nUVg݉_'ѭ2rw$ZC~k]f4-mu :/!շAZhW$h~,1@F *!qlI4:fˠ%G0Z`ԁ( L5#CFWnu<v<7ajCp1SN{/=M,5s6on j\S<ϰl'SrfΖ2\Fb;C{M}ĿbY)iS~q֎.HJcaܾdexE!#'K6[zϹ0HYiiw`'{NZ[dYJNXV~1S<"}CgCuҦ(_Zn>UN4n}e/Ҧ݈zr7ʊ:,;&[UP}> f44e&kAF9@5nx{im ~2G_z2Wt֘KȊCN(/x e: /OGdRJ;LEExgxxp&fΫ8@lCcKd]oB͊O~MIbrCZ ,aPhXlF؃?ow*+N6iaAtagKözk83Xhpz&4!*)L5%KuW_|vW7(S1TT͉Y1ɦlzޠ+uW\h#f)lZ2'B|fd|hH.HWߐRqB^L^HcFhDYDe$?2~eyՀ֝4W5p瑢ۼ]?giy4:c>8eZ49l+xo'~!ὥ3/H7_zͣs4/LW}9q7M+;N=\ f`V@Y3aLޭ_~o/Wn{f{ix3kFc 嗳:↥r"S;~iw]|l-0_3J*VĿl2Ioo84229f!odg9G\m懌|Y5)r >o6*n25d ~逵-H@/yoRG4IJ狯 ow;wV򺡝-;7hT&7lMN(ؤ* AyZ@$ox*5&2v64.Jg 0{ _,b5Qp:`aCQ2eA Jki}#yBgK$ &*)K;X|{jBFM{&F퉏]h^:? {yo5VFMkѦ +6^hEwk}-z#x ;i /nL*{h~WYݡ XQ OnWlxcӮ&& E9bK 6=Uojn5wF ʞ'f|: 5_UV;π52[+m:o{BWXzJc5Zi*_m|(CՈe:&33Sq_/Ǿ!P=ո#œ8\ɵk$Šyo=ŝ^Lſ$Lwe:%3eq_KflGRf#Ǔ8ZĹ6 ]?$/Ox㛿.+[8&>7'۷ķ~?}?ɗ&ͮDz/fz`?s7㛿ƨ9Ni*ſQF2D k\G4cė-[Zt&e]Ci7{yU&9Xܙolz9]uDhE"^҄lTy;B}H'4 x&>9AmʠA@Bo% i`$A8"p XBԜ_=em24]AIhԈn-U4Kk t]1 BrzHMFb,#l{j$OYh !*KՓ^wq'rRs';yOvRs-`g}EYpXUrpxmh)'_xi98||iܩ"pͶ{Wo°<, ̃6ٖIݞAE' 6c=Yfj4kMo!ŔHˆưHG?$/N@֚m;R̚~@܁hHD@1&#wu/rz'5EP,Xkg8OuM6s>ǧy~ZTPݻL9\fS4p{g-z'[3m<" +Kv2`G7$HΈ۱vIs݊Ӥ-*3XgTgs2i6ݫJuxT֠ Eg"F &ɤ_nеU=d3zXi+tm9ʺ AĘ]}8tbXpH [G~<+Rje@Q19&DDtDZ-j` 0PW'HSV4ҕ.sEQ1. Â$D|DhW ,k7Q_'J&C:tu?{VftJ3!@g45Ka+}(80\SjqrW.:ᵹ/<ֻGfG:q`8 Êe2Z\M.?nzeT h0q`x.(iqrW>V׮jzvRεIx`XšwBEruXj5[gdv`սk9Rc/[YJMQgt@XBB\IBHAe1Ϭ)k1hD(b(PԲQ{B/y|QҤ*2@ҷⷂ bn Y{kZUk{hJ7{>n9,PM}:č%K4. |5[fO;榟ݖCkdiK\}^ P@[!햚EתNlN^h_5 Uaun;u^RUEՖR;@6vK͙7s3:Wt0 ȅaEy^17u4_U]kw2$*` ;=dy+ͨ]wWhH%R)[ik0"Jvj!BB)€I+V sQM]J3O >m9}KScB#[tز-b Ji{t{_y `df /!j/Mwv 7LJ2% ,4+_t_XpB VrZ"8lʷ.~^Yh:Ϭ(/ =@0 YW6^Wn-n_V6 fLk5HCkAN@ !#BSd^ =|M E@t6 )kH34y+6 iWk> A xc"&$Pa~:8{*08g5'ґx-O$"(w Qp+Gh0ʆ}52UcD6+&1lqWcNH"cӃ3<6iE_0y&jmzּ !a`-zl3U.5"p0 HnE-A$)Lb1Ҭؤ FD[9$g2;*FU/m w ‰kg`=xSA.H0R R$r\5I.w]p}3FvSro'A~)-<[H"WFIqȬtTM#stGUJ%_IB]-U1kR0T#SF+Mw )A`@p4.]S5YLʓD(Q^򙩊gKomІQ@S^VDh ȯo̓yyѢŢ<${ŵbpBMymCr_C9$2 y^UfT$"+uʊ b8/.ZʫI&{!ԀA35!f8*>$*l9&Mі&~Ҥ|ck5gmU ΰ?{@!s4b,2ItkAAAR@iwa&-3y.l_m= &4~iQ3AYQm4fVSW Q -G.]WF: tX(dB#VGPU7hYC),!W:" I@h $n YvX4DA DRqZnQ4Vqy"_8&wh"x+bC1E&Ihq_l>*jz($6Ti8Ģ z8lJC3b&Mt>0JfU㤡TP(сŠ *5–OI2#1lDsj"g rܯ(5|FDېbb k5^ZZ9&B*gMrP> ED0)@JFJMGh0kF:I)OZ9XP~VkhG `> ]ix˅6[:r8.Tʺ^[KJxP쪠@^ƻ8K 5n<;ɚ m2/Zpy48U>q j4{GWL_Yn`Nf̂TD9 R {,hKG\[ B}#{y<1slQrJBZ0J=8.2k"# y$%X۬i,ymnj[ʖ&fy T䚎✊gc/+t@5O)&JY[Knf/X_?Ip~3T/^o ]Ť4}-⬒Hh:7T 25!,h^FW*5T}~4]:IR9$7vR׃hH3JY[o3s/f'+&rS#GQ$'b'V@c]J덚~QUQpTCj,>xu ^H:+  7OqYS)i+'TK\}EAm%!oYD#g'O|CΞLM**] ӞT6h XT¾<^j\hJf#(B K] Qozֆ4`Y3-Ov"e{O*UT =\)AWr\4fY3X~;` ψ8 .+'7{͚ڳ\tp EV,(Av"MpVF^Q "GkAA d{ }Db@" ˆ:`MFS@,̝ pQ 0^RFY݆ݍ{vddo24&m&QSdUsʧsKTAD*Baa{8 @Ф=J1Ȅ§^*[ ;ΠȔbZV "1i Nve&M (4-J@$Wgպrj# gPJeg S'&YPKݑ&͈ׅؕꥌWP )C# MrMߦqYt9!guPU?hߕk?~?Һ[=͗ƥ;"F>u2) )Ѥ^^.}s]A"pL{ed owٙN<}IswեPxŪ5ru7:% o.o̙V7#F ;z"70>%E~B-A*6xYd3uL* W{&fUd /;Sc5b)bטNGr`F?^JsJ1[vշMӑ%xBh(ȯ͚7.7ƂJ F5Ni9O'vddeQ7HF@fMsi.0֢mRh:q[HzGחϡ@ \6kv06V[6ˍ0'ƺI Rk4.5],@D FR@A3̯AJ&0K0(PFy5 I}G @H:WmSzUJP5R@ZX4bn*–grkj3GYCR ~v%#KY<\1,6LUu| .}j,J:K.Q"tǪm(Uvmի;r|X?`^ ∘.ĤKFQV+ݡ;{7Ȃ9e-HVf7tOVg)ҙ\:& W R/p wj6rA]!w\sַME)\K@^"rModv6s{(Ӗg丢 }8Xbf<4m%G lKIɒ*%M sAm?4% "M'R'Q`$TM# 8S朦4: ha)S"Sf`TQHmp!-|ҬQ4 D5m@H'k@QviLǡ}םuZKխ(l-3crtwۋ2_luI,Cd+dH@)R g2)f$GB!!R}6*MV$ t#vq)Pi˒[HԪW]\?n#=>)H3&நАhIv -zLfNX:ԫ2ΨaߒR3AP;Y 9U),dMQx#7Ag/{?z@lC=A ^)Y](fI@f 4

Y,efꓱA"Һh_1'hjb3O4NzI&5%ٓm@B9̚ 9!G8R&6n'DUHgǧƻ6f#~lTc悂,a+Vs&`r}k`]'gGަXYG5=\p߰%AX#_YV5Y p7~ X@s X9b3;5f"ʡ3w`XL8.Ji4+C m TH^Z?ACܰV;Fǰ_2\t(DWD4(A-D+qmFX1(BR$T49,k)PͰS񡘛\L$$Hg2Z4kNX2P#Qr.rԬp6M~fu&᩹~J@u)DuLs4B{}좕& WUw\BNU3b E0hPٖԾncSqԚ|`^yj϶IWi;u4Bx  ]Ǣ@̳O%o8ZTאMKI3Oq4K4Z /+)IY2 Dƈj-0eaտf"1k;}vy{Xu_3݃{y":C)1;>)<+4eި[D* Pߥ&vg-{|5|ЉHbٕ-]i:qJuAi## )^-6mYOg4٠DE"yhH`?^ʈ*!$&lHvIӁP`0 G IvKH)3|$)j3>ahbkJ:bG5eX0](}$cAmz^4)*M1K߂9i k$ PPx8Da#g4˯yr3j̾S,D8s?"<}*+fsP"S+)RAI2 C@B@m4ǬAW|_n`(ږ!ɬoϳ! ChY}95gūyHE\N,ADfF׻s,4}*b8 ~#';JN3`F4GAK 7Z2 \xP=~=jUgVxiuHH զr(hJ5 fƋlLbиcA9? p3V͂#jsN8ɸ> .=J窎*, #A,05~mHD(Z Ņɸ= .=?괋js6c:i\,@0ZjeѨqF]8 ~ST-5 .i dʍɸ9 .r.4w#/XOTIeӁҵdyIQ-2-ܓUV7kN]aŕk=9uhv7.Y?F7]gެ9 Swm.\Ѝc3d@JK5ӻHm7u͚9}cm4 Y|V,6$x^d3fUT(ZSuEucU kyfx=g1"ꅡ溾lh'4]~3M4NZ菓bfy6W2YQDWҬ`ݪ^,/{:׀tuCGdBǷ;9k9ArB$5n}#m{/ ,41;iBT"A!*zDeһN, MpMfoDG!Ctr6!yevw~cfvjY0 ,u<v~3"ۘsʺ͐"NX|uZ:4vAZ'-5Ь%h"P%K4 pd+[eY 5h8&+'_pJd";'Z͕X~V QD1AAU_V0pm4=/\ɱ&eyhwd](@ 1]|5 Н  _ j\ H(ZiQACkf Reo%Y@ #"-ҤIbw\Q28koCU"I6@i>9%rG~b2^αgoגODWyg/Vz ^Ү]"ߎ/]ey{G~ݕ^'7XX􌎈H}_ey5B7tJ\PȊk V W-AXSJQWD.,=1&Q8566/LeYhByTvˍDr0pj1j ލ )'[YnT=zH@d=e",B[MYJIE}~ꃋ[2`E B3đtbٕrT >:[}py˔s͝[d GuzJ*@c ly;vRA}%ǷL9]B@dfd6!/ \< /icbpm/ ӤIH!;"ɒ{րS!]n'iR1F݂lL9W-+A#=QX"D2VIs̮ȴt sM(˂Nl\g5`!7µ$v*2=Zw_;oۼzf 9b˴A]znٴ5dK(ΰ G#m|s2׋g:?]]tE3Z˧٣h3X-kZˬ߹'k8ҥBiZGU29i̼׃Ɠ\7)XȱA!K+٨HHk-55qfseGWD Zjb%\R cM&}ch*!3|?[k z]߾$$.3 -;␾o`Zj4GN-QP%FY@X& g7hL1+mq@ö^ٞZ!֩TaLKtg=M,o+UU'5<š0KGmymAmlv>63A>0||7괆G 4x49lnzmJ}dWOoJ"a>QVg-.ۿYopZsy EQ"N +!9-^; sj`uSoGgWVU]|YCwa%+zhfbh6}XUա?RєgCt?ވ sEE1Zq 8%TБm#P,;\5`n饂tF3DV4āY⺈CeiOU=×*s *K֐3%1 =ۣ,0U^f!>%U|2ӏ=3(.zi۔:sC4/67lUycD`#o}bI; bGJy] "3c#B1b߲u(n \ҠwڗK}EKRhea]'P bX ʚ7`ZMX?l^4LYjfuUc ^bˆ;4ݯMiUN9N|4PV-<;un0],*9>OO+e.tg `K$1Z[_[9c ǯ7}޲3t$ *_cu65ҔX6kW/4ҫ, ]M%Pg,wvH"HPlPDW1$K?YSWS$!g WqF4qQk)f^9؈_ T PX |󙻲*G l~L)J@fVF>hs7iH\?*čќ`e<֚g=M=r{7$Aʸ2#5N˺ܺ(p$kaZ֚|LM5 ;#7+CHkM4+oI(O@X;mYLy!κY_B -9HYOhTɅ湬%ҭ'Xu 1{˨B+] {ߝÇ$"]o=oJK2NDA4n{/y!z?e_PIL캳8 X%&7o&O{~_MB5jg3#7g-,6;KFʈe߿OwMZa|Z?5Y#euYY`1w(V|F~4c7Yi."CPܮoy]F|\ҋRQg6YjMec[#e3^Vۦ5,UzdF glDa&ƭb4g+K>UYi YD7.QSYUS-_ 3snG:JMq渥pmGD:KG<a&tkma\W+~fҔ/djlY"qЭr&̤ks"j#@`efҜ qX6%QFaJ3vR7N?ARBR+E[]a a_Oc"#fUNv.C`"m_ | H^WTMUdnB֋ mih/,I 3h( Z=5ծȼAlfD:=Wz,βCcᫎ4*ɇx UսI:SLwS˜4 eMl}9TB݆KS}OYk<˷z7gOh6>2M-備Uw_|4k.^PDXӴ}WO޼3|o]Y游i"=P{-f̵o7;5d42.Mn3XAsڶ˷ܞ=8xTrUO^w^/fyUNkg~=Aw9747lr~Ež$F7ʹGQ h &gzl~MCj'"< 8p46iXIP?;=Blo kfOj#5y5>d1t(JN=okV _w.d,\~F1 D{F38[}aM)o9bq%M5D4ou.L+c1he ymY)]ʷ2Ow_ɉ &ppte.Na2 ٥48M%HVD_mXv,{~ST5|(1 CM{&&AK6fM(g2o=6 aCkJiWWmܖ1E+fWO "BJqzv7ONka5.tR"GD 9rѱ̳0XƄ(o6*7S'BT"+OQl}X( T 4NRdJ Ů`t0&?pӷz[?{vQ}KbBH TT ]O>r4DS5ՏORs5DY-bycn*걦A褦: DTY8?STd#gw', Mo CK+C?`q~l&O]TߗXt:$ʰ TbFXk>H3. *pD,BiE{. #'=ҥ?o9aλqs*/=9ӏ&zӋk-]D.0ըQ9qbcmvE nb`-{ۉŵ]!H@)SFpTܘXoe|YԃxPprhu:tkO.QYi&ZTqnhϪa}Yu. áfG 7p.LS]C3iEM7)N{ll +ۇZjP]ܹI rYbǕ!"J>.,hסҰl]9Hfìs&˓\IƉ~/JYTa*_U5vʼ4ofJ{n9$6fj-vȷܨs ͯaԼkdǷ9 _Q"a DAxwwяi_j86)ڬ>FOBSJ67~i&etE|=Ϝ y7wF!$$We2 {ԭz|z*R?֡IIH"OmTMFt}h˫Z9FXj <䃎{'B=~GZ_b#OLK1}~Bm fD\C@>; -4Xk -,ojhq*|۷JO6i볔dGއW+?اwa=Q֚'?4fG= 96<^x=9:IwϨ>`'z&IiH&,Oυ<{Ӈa W5yz>@<9=DX>f[8AC)5a a͞#EWa S5yĀ+3+U"yOu"r/ZsWcm*M(Ib>ֵMUoW6[#0ˠx_QmFMلbEO^}{A?{)M#}=K \ucnY=6S`l4,;CnO^oݵ{U<9,/ f'¤#l殯!vcJg֚TkW()F)m76h3h3ۢՍarBQu͍k{3U_6qi0%UDW?d~md3,$/{c2kҶa,+ QggC Yo4Km}Y +DP30',Tɺ MxNxP"F:'2ݬz. Ι*g̚/u|]y!Լ+H MOpLo >^0Eޔel2 IvQ7[GtGcC?,851a5IֺVi#b;n<YJy}om,Zw=G/넼/<{EGަ0dokI.n*TxP(3AIDEKhcp|{q@^`ؼIoT 8b)`#} G9 MMHbo$&Bzz(1*`3PC|%$6yd(nQxGlGt2eK06:!TC .4_jMLڻmszA"UfKRXk{r$Vfi^W>6— ~{+Df\dͨ|ˬ2?vʡr-S|:y5,ZL[:n<H- :xG,uf ^hiKryY]6R07}ov5jXHDҔ;ܼym[Q>r OwfY\^OҘ,ZQ|&*zψuElWwx}[!7<uu Xu6])Fj+Z7z|mXoF/͛@m^왜ƕcew'Wۇo}}K._7o7{`e,ՀoiwOҤ?VF/#2v{]\J37ؾ[,4~7ޟ{//+[vƗ-o鿾Ԡ kMG_^c7;QWG6H3-xEz`o^%#7ŕ-eoOo/-#DE2kwjr5 H~%hB\؆f2v^dŠ7/4ZXZ/_^)q.jW皦qqsb|W; nv ^ܾv*Qlҡ&v S=:zXiXR9xY>)ݤSͻ96Nq -DB@\#gH4W6=@|6xADqLO`jҸ xILqA=_MV4*q~@&N&)K]:(q+M~l ^k\5 3h>[k|;,dX[q{>gKgx*ںC^:Δ9V4 lgMV|#{ވ%myAܵuÚ\E#lK G)whZ a2‍[iNMk^S΋t/GF7w^eϓ8[8cq^4jx;!]UgѤQC<}q"ނe(n8[JO㮭T46D/^g2ѴCwZ#y'VVSnuw" )8mN1ev\gOy.ƣ$^U>T'?zIy0klTKD2eūFܵu1kW՗MW'?Dgկn۳5Ut&(ϴs9GC&="iMN#ƃ6s6+>"pͶ{2kOkݻ~{vP SYE' hck>#s۬5 k⸷ڌc@{-35 5*ᄍtŸ-SƯ*-8MZ\8FKaxJ}m&͝[Fk#+fN'I*P t#屃Yx@R (8A0e5 ]pGTGoNw1 =h_}0Ιf+#$֓9"@f(^ cX/C}p8JGi Y3hQ՘fW4HsJL-uRsex5B}5Mg:)% X?D#MiɤO7-g:љOn.=( QL3[(֨RA+ބGUg;ku.+mUO7]"4pR%#tⓢ䛖g,57KJxv-%AX;*t󱉧 뭿uQNv1w˥E3k~K,t ǂ>cOuRsMoԠߵ.ʦ;%)%<O5[aL;Ok}m=##x KP_z rrY^s匚S8'W낵 y,lzNちlsedv>.u)d\=Z>.%J=L! PJ H@%P PP0͒3lY;5>"F "*f3&q߹@\v% ,!ꢪ@<˓PI0R$& 8#;=w},:ufTMRv":8~#{q8=!Ly`jp]yO05gfeɛr4YM|#{ #, z!`?>zp e졪 *Δ<DvoRF]G@ 7فsSiU3i:LQMmR;W|71m6jA4[SO_cMQ?ՂhG(64&<܄ߚk;W=dֈSKFZ70d~=G2kF{_-MX\2RtL}ϖ$Q$Q^elr7Τ*~æ<2uЃ Q}I}WD#lfܑi]/|~s?[ȓitKi2m9aJ c!=Xo"D5TItіF}wK'LCVc0V[EX03DJˏSl[51dTumSlY5FFXj~}K]0뷼+zTR.W/KZ5ߞr;Aw^IqJSw޿,ٷj{ޞrXƳjyX~zyc 㳦>fg.񿽋F F«1> Iy/e靻 ԅ0{=f oeY{fw.+p #,7t/\uwe)/o<.6f9gܭ;>kY`FmN3mfAiNPM1O՜}2itDc.VQ;ӌ)ͨh"+T[_=\Mo( "0 ](7H  \%Dt`@iR(Q4A뷶^9M*uθ.xMI0 I,d"' FTH5iࢱ:ؙnuwB[cDVB؁|iw/4s|M t % o]@ V_)%7?~ዧ5[?zvxb` <'4qC㽁FNڪsV>sn4甦-Zh,0oM:?lk5@4 kCcǨ?=ݰO\_}]](k. G>ϯɬqɾbG+m|G[mHO2qzꇿڲ#-C5>;?lj5)ӡd-~p!&|L> 5xT<>|nx}Ͽp@X4-rDLQ4Wy'X v/dܾ-.Ƨ"H"<{ŗWyWXhU/Tnߖ yIBOES<;,4qSsB~uOm KA 0Ǐ)O7~U4/e(a2x.R_ZS_;`p6{CA5L$2jSL\V%ZHZj,4tԤ/=e):iidjftB:F45ۄ$C%V,i-B 5RzIg@IEP6QE`ـ/FhcX&B@ !ب FW} ImLol\`5!؄%KG\݅.y"F)s }4d%Qљ[D<&v JB/э`hL},5Nk3bzKc_I`( С 1iq̡yGKF`фn74X 5Ml.HfL&ݩ 5&e[eL 읱1M)q1SAx~5F1Ӵ9u)+A_Z?(1iF`J1)^˃h܂GK1y#8;_ѠJ5M7G>aczy8P6D1 D:9.^+h=F_Ԭ~?w\o܊o.H+?~B>eע7rSw`ԸĔlepާA. p AڬZ׊kO+}ihʦk״7ިeG2Q0͋$ /^踹647dW8R-؞;6:n/64g"Νͻ?UF=~d %-'1bQ[Ѓ)^M}Q3}6Fp3lzz*(-[ L. ALd>c4kpjpk%DHv.@ň$r@AӐ !&Ey l 8bTΉRL$D6*О8Kfˉ˪r0oM% dPU4x29$~sk*8?NLb UN4{ <'Zh ΰSJЬ"#Ng0 N8jQ4kRT(!5M4u8pyI >Tax_VIJVB@B^y^e|x=LLᜀaDYh]ynr*9y 9-< /5Mc/ ߲}=՜7* \b(W1"Q`8wl}KϪ>B{ʼ*ݤ:8rA#N5ncwJR>zF$ (@10!٠mKlH)ABw&|CqPR ɰV2ex̜PF (N:$Ip9 6i}$\JnIBQW+{k;jIEb`T7M]htl[$Iluxip7=T8PȤYXC]bLTRss6%:+ %Bɠx$rvy}su.x8=xCDhgF<.ZYI+%&u} tr$ǸUVFlwO^ Q@4+q! kdJB)U֖{IK@HF=/{d{دгǴd&ld Vi(N`fXҺUvO:?rLN'aA[ ,N`+ͲI#/b$'1idMaK^uI<ľyi,WLau7\}7;Mt)+mSlT#hH8w4SL=1EtfdQ뾎Hd_gD+"u3~!SvBm  BkPEU=s/Hz@qFByA8%HGYC*v\p/6QʄA@&ik0*:Xp*|;_ĬGf " p/|^OݬT^y $SDif2gB"mGm ?xk<,3!Ć& X—w$b@%zPÅaUJ9--V!r;?䒖Rպ E3ۊ+<ƋD(魏OoQ2IA(P99Ja3_(!mb|P0>0ȘZ-G`d^A \JJH%*>H F`ڤOUyeNxmu !7Ŷ+y) rO4Z;lF @.jcL+9gB#y'O  D! 櫘@ -e9=[ЬTGL"Y3~ޕ.-3.$ 2BS(&ȓ(RX0 ©[*}=4:|gY }pR$3fKps]ߙ5knh/lY B#5=qtCK^HE_~ZL!U,҅_*ȋcq@ow:!nl|H߱݇Di0lUTY@4]ñqmYs0V%*bA(e#= Qʳ6#Kk1KK_AN$v]uifO̓Fm-{Zs Uf Ok3ifO&>;PW(["~fFLLljyڬ )lqTLlcxHD4ЉHL[ xs/{ȈA#eƸsK2gKPXRhpݜ~_gp&IeEՎ7k}CT|J 6ej)ݫf'I>Kintf>.pr.byo؁>k!I^&glģ{Q 0L eX"Gi]nK [{I3gk$$7F+Yk$هHi9PDTOכ4y[Vux!hӐM B(` bިFWPk]#4vr;R"}3r 3%)n04n6ej0)oXA!APQ!|Q4u8 jz7zA*cma&/zU[ .{q4Tz Èfk-LBM5QctǹP:j, ::B_# =$ Xmq<Ay o}~p ,]jQfU♼Ig &4^}sw "7|\gnyj go#셆/[oKb8COo^`H?cRvtÊ_=AƷ-MtKO *?2mN=N&bMrs>BqGBٯj J>_r|FՎ[|Oo؇"e$~Zomxiӛ?oћEh>}d||ڷΘ48D a˝Tm;`t1_7)#p@%;v)%+n}߰[vO6wxR͹42^I3B)DBt߹Ws'U<y,~qZR<3& "$%XzS &I/5Ŧ$s^/TF~8V)'c'ūgm³0W>xzg@#g"LrL3H gcvĜ<M'KJ Una+`I{wTb6OPLґLTDD89F]'O4<[=M.wHNP_SrU7$ks+QrcggC30-O2J̐:(zf5.3l4rl-n 8mcoqaPRmoYT\qn me0AH408vZpS-4M83͡JSwMVe ?֖2z`gqMTHQ0Ghcq% =q8Q8_1uVS 9rWmiքfu7 +08qT*:͗xA*$?8Ƭ}s-ajrT*\D4G^ hEvS6k&}Z1[̱6 ]F[FxS C^c֜9֭6*GDz[uIQzFC=BaHzY.TTfX{V`mIJu`hH͚k̚[jXQo)C l5T+BՙA݄B!iNu-BzSǯv\>h~ݖ9o|:%^IJ1L-L_ZΊ9׸l$zt~ը'uNZ `Rf*$4dV#G 8iTUMgm:}؎SB":w'׺5dX%X &SBnMe&=0-=rT{1D;& &*b_fn庽cat\"^Yj79/(­fxqmSRcSطJP2yڅzd Z#25j/fWrDoj,I.IEq9y1o=/VϬ򯬳5Mug*L_pNlE_2Kv/~Cb㖎OS]QY] fKq7-w 'ܯկu;~J' I*ysQ',oP]YSɨN~C6{t;~V7R#U0C4ONj窙>my̙{:kMQ݅7qڋQs69k0MU~D 1 | :h$T`ӡt$yQj\mE8? JݒhTCC$u6[Ot0uG!lNynM$YlIH&>M:1\:#fXq^,|wiyqvy  wz۔U#7m<7Á۞GW5_l_p*sWcFt>7q#LoĦoQ81x櫞r՛ѿ亞'-[e֠ߟ>zES*sn  )Z{BrqQ s5eYtS ˧-4wxGZJ]&nSH}OP[ςaž5knwrGࠑþWl{Hӫ &Oyfϻou';lߵSzdŲv,Ά}+>jIs+83fݠs6(G󳞺s݊P]3'g45'gonn|y)? #sQj7ո^v|l噜F~iS}0ͰV~;lk\@KP$%˫$( pdhO(CHI&N, j֗ON߅РA@}M AD!OBuZp_.]^Kl.'┍B$r u8BA?A=IN` 4 SDd3L#&{AӞ Ҥd1MDԑDmUP7)%TEy~~}P?:kU8y['ntDzJTP%ȩ cx?lXh%sc'x")T) +pRH5ՏR`iBjWj6a|`̀I3̓+1z).0k.&C*-i2k?~keBS*IIpů_C6Kѷ7bC%7XēnIcQ*?o)Ld9݇8eiB0I  ZlL|F>cEɜrI 52>ҔфiQ[W`6P,Ig DS&&mnQ€bDXՌ*!,3 ], /e &z"Ѧ_{$D[#$4G&k BC,qmB L &qDZS+`5hdG_x*Ѱ b! RRuph]\xm+[*UpV,H6ēG"Ͻ/:@g ̋gDQ7ĞP]%sm'k_5V(,m$Ń2RhD%Ys3i<KLQVƠTf 捪GObsQAf (eb!lCP಼qgOI̦޳Ē*v%40Su#%Q pAqGͳXFGSG UPmS/"x/__aI7 A}FEst38ڭ/ Uߟ'3u<3>`KegHK;,4nh3mLݬM #a R+M_j:\tt>cfEDS$)A|ߗGvXiP-3ݳ0fTlm)O$#p]Z_b\Puʰokj2)ki^܉˟5C^^~:eص~ҭ|ݲHܰAkW)ޜ3ƸqLXyHQhuk?Ԏo} 'OO y۾yPInrG^Ux?ԐS_Pbq$#x{κnF\d0 B5J$6mauc#0vbL68a6d а#m!n z!0.EtD)]= G}~ `g'Ņm-_L+6oBz~3r\/V(wXAJYJQ_ɳfHr"Y^2ScW*3i'j=YEW^HBXf ZU*}.gw-8?38Ͷ'}0|zrԍE佻~/}Kn7ٜЉֈlE:"q?1 ́O<6-`ӣ_’uC'3>E.>sZsNB4#oGD958A-4M]Os~d"1 ^ѲQ*MN$V>;7nacJԷǀeYFB$$4XDۍo7\[腬p׶4vE:l̮ڑ<\ &^{ǷW^"BT5xvWٕLVXIlY]MW^Z]R]q(DEc)ק^ɹVV2;gD3 oI"e@&9˯4DTⱔf-B/+LR>ml&D wkr EtRA=3uyoZP7GAw_^ҦM_ 4_o0 ^_ 4Pc鿪"^8V ,;<+p}rc*`&X_ $XTf@Pn2pan{$:0>\ ^4 衪|U2 m} i-O0LBrb|DP{.sLjÈ`ϑ-ˮ{X-Ģ.~ZsٳҘA*y T!n%lچFdZ{:ɬy}"A'hV,XU1‘Fxwcǚ4ח]8̓_on$ B~t4uvߵcSݪ:%W$X5Ԧ\XSWvHY@n,Ɖ|ZWM̮u#~՜t7:JeS/B drS\ۮ" X]RWs/ ٙ ͠)\ \yJmf=_U~TvEP!mWNkF SGD b9TYa%^V՘>kE0!DCFpӚ >-HA(Lw F!Yi¿h80e7) P 3⹑q>`Ù $Q *"na)T-PgGhɜ>A߶NJ!k4z7 Nhޓ/ȉ&<l20'|@RuTebw%9iSOlkIpKdU$='F霶&ފ űhEAZO.BBZ$OKvZؤч̟USUyl@Z5tDjEo3߾R=96Oz<3/X4F Z;ݖI_y: AA$KR^ x z'b~ݖn>XgǰpUJrij;CX3IP lszp~iBDM'zAƦ&̀M;;4f~,NeZО00?=?y_hB ~}K Ptp$GoC35B\P(wԧ5j'5^ԠYmKSqo`QPK7 Mok4,u[H&TH`P@Vɑwv~Kc؎1}TqiQmؙ{VaPv(*<##}z?+TVUy>THjZq1Xxc{wgz>H@6 k,+:V/W+"@ܬƓr0PG?LZ3f&< Qs"d5*A~PEgҚ~ yBUH+$ŐY` P.8E^AR(d&bz$z:uD"咇5阃]B56Mpwl^t1"H i؁πug:R#va -j7W:02N*H`vmS۫CcNv!wa_h N.9n>WO?\x{J6ΐ{+)6MSq>`כXNE2Q:$<}+5[8/.:c{?^kC֟2unޓ'QNW\Y{֚'?xU0d٘6MWEIY񽎙G?%(E0X(쿸gfKTt3Σ~d^ZjkeU1UR.Kw|a?^թ-NiB ]B:+e8gXwUb\JbسȽ+[ڂ4\5tXbο%+F51Ҭѳ}6_/ kعy?|w|-2cz.QVp7d`P{[<=_?0kۇ_[0v#$[/WXrLϠd _WubO.gםn6n{*P(Ybڽp>nrrZaqBq1TnceK-쭽o O7@B$iq7}GS<4<+6~ >'"JMۚ @&PaQ,&:?8cQ -U4b6\7C XsL^r?+Y=Tfל1hF]QUCz-,ԧ:T[h@w=ѣv88ޠ>Zt=ˈR QBD@-G9Egb|oǮ{AU(# JBu$i%v4Q_Js_[i٣ĝK$4@6{ gfx$е2?CaBɒ<^z2jG3i9t 7iaI KG|H6?uڵ~MAŔ7ڛG|h\orKcCg0eL4P=3SCL A(V-&o;q1[9|| ѬPKO,Y[w}6rȜ|yO%)ҡU+5yN0$Kbm UfcֳHْDfMĠ;"hETK5"y @f%+y6z<>>0{}cV|/[?u, DVY≪˳o~G|zÎ:Rn7JI}zn'dŝU<˳ܿߤK>c{nZr4rX,9r/m(vtumd6ŗbD,eVҨqs}vx+Ցdλfem?W⚝cG0;K9u(8zؗ*ZۅQU}f8}Qv=DOwZ-]ȆEƚF)M`fw4wTqLպN(nxߙ^7EK۫!H^OE!zxg2טEVyfj"ΨAV 5h:Xȉ3=*}{,9*@b,`nŵhb/4iԯMHus K_xu&ϊ|u;<~@8ҹdBO>xf{9XVԅ!兿ggnćPC4єrܱ'G-^PoNR5Zs^٥!\h |)9G 1VÎ_YJiZvAOW~E{oMK)3aܱO1joNH4-|D]?Η3-uSftqǾQ3 N\uʞ 9#-qr n:+M-cQ{)N?$˗ޠG\Z>8:ֆIIckڽsm] _KsXn:eFW=bT;mu*b5+JsrȤIO]Qh1{tLchԻeO\IDd殞'S⮡oQ>n@ ^iX{ǻ4NT8k2F(r *rV.xICT6@'DN=: ^HaN+g\rH#IsuZ8`[ci =)3kZbk.j)n Р lKA^O쭽}f7AAmQb4)~6|}(`~Ɇ ݠ5>YkQ @4!ʃ$ѼIGۆC^ّyJj ;&7j}(%` ~&9細8BTvI7Ƞn5VFu0ZkGmK,K'7.x"a/ EpWoa)} /zndVY$bT8 )̚n RY#CA`^YSPUJɠx82)rY$U+&ߢp[Q #("ߎmnZorsM} E,yAf/xtkФ{kmk4z+wy)z 8SRФѸ_Ծ'jeKD&Y5zlsZyͻbнziBZc6~͔yjAJlXS77M.ysMuCygJa&TՏEݜVN~d_~X<;:}X(ƪ[hz kUʼn~$bMSSa]za!<n l3QBi$d}M r4i[0jVTi.>~{E6}#i]~(PqnZ9Fr2/?ҨS/fl{#멗̍JGKX/wӯ kv/VTA ^jy蒖LF.S_;NRDFzAG{\ޙIAa<؄7g[iR#AG{g TT2:IO~ 6γ74 Y1o!iq):U"#Bd頣=o7i[Ee{:ɔ2uo7;N?]G"7QR':N⟇<+o|*ѤSa@Z3ddCG~CGW7^da+q;+CJyH4;BD)@>/u)pYd%aC{"@5-D?.ԜriC:jF`/nI &4SOK9 YZ[σ +4 ')Xɀ0T^EHY5ՌkQ!ȡ lAmvj:ĝa,^ޥ V.Ya7 w4 \ڔ\[ŝFE+o OCH2k"S{ֺوE6u5cĽZPG2_}={SB!|8mJ5i9рlJ="Z QkyZ$B"k ]c6n鵩KoY!x Jj=\ctbb}MldKjz_e_?eиj+?p"籲 [uBSpJL#脦b!ݺD-``{Sldsڹf*58=?/lUsR~2Ub >Tkwh~{*p /B4'jK*5>mO< +%՜? <1pR.qƖni:AGժ9Ѩ&M>mO7jGHWte6= 5knB4&Hx9?'NC\O(y"oba1tk8XJJ2`Z>p~K+͵YXc6!3%jIπXh.'gX#!*\y.?rIi:898B 8O}fk`kq[^d3QNɫZ^[SV DFP͚ .%B+o|,54]֚FqiM,NCkfCdЌl 7Hi.USmktA.eΠ jJ`1!t@ZcWeq5D))M2qyX޼hN KQϨ[FB@\2փ&RFX7?xЦAZc )i ]PEtewBf$3&]lAp=LI Y bȅxپLS9?}ɿ\/[0-Y .-;{g|dO}'أW4B+Vt(P5^(jք.8Lw|3r%jl4[B+ 2i'ϴe+jؼ]'4- *g2Zحy687} Qn۪2tii˧B_ӜZ9aKҚ03XZ?LR\jys>w5$;eߊyqрw R7(3EMFCHZd?*&dMܽĐj'zƠƴrZu't2u=?h-7c/}fa4x>WcPŽtQZnmڗ'0ub ԀX1Tq%l:7nEVQBBݏZe LDtPc)9#cVT7.bu:jYhb뫂+qy փu&*Hk+lK gW fa%-aAc݊u6[)맂;HF#`%t0hu!s3ٖ;9wף1+s0 Qi[ FBk_+8HJ$~SRsk?'$}*'ϩ8ٟ`BSq~YGbWbDTJ &6.po_@zK'΍~Uq>ȤD+IFW'4@2syO.ATAE- @T] QIhY[k.} յqu#2hR] Xkzt `z1%\՘tiIi`=A]jL`As4ХѨB!m_4#n!?fi4\Cژ^\NTߠATּ4浬X`1N~w:u7}[=[Ex ٤[qWOC wstB,2i.s/M]SV}nh^dw,YJ44Ա~NisR/U{h UC]}+]?%}_ ӧf y%ϖ7Ƒ\?uZfoޫgoČ#?'llaL{`:8uN}>&-7+3n*yΆ7Rf0R1i#W̟"_Cϭ1EmmT"xL9yǫ(Z㡉G/tUXltu?[J3ѧ q1wXuxd_lDuǞ_ޠqC;qI.OxFۢRdsUˌWiܵ\<xBo틫=PޠQ8tI75)OT4%AAJWs'Rw0z|M€` 4,QL_yOQ"qNy=+y\Qxs<<Xj\SWeYU7a07͚?Z3h7OųfOxE؋sгI9aX %hmI2:+D:E쉦ƃ!-Y0Z4>0A\QqN)9 zDi _|)_XE.|4Ԕ /h 4FV?75&yDX*eҐ <Ũ {=_L DzntCsMbnMk#/ $k`[7Suo9W҈\Xc48"Kx8Lh0䚁~H[7~I!Q:1%b|K}{%w2TI_TjɔL̚3i?bgRu۞nsi|?~?=3.m\7aᰔ&Bt:F+6w5wK}nzLZnZyF#DX{oW]l3FZb(6_`0 yܘ1|`Za) y{Ns-7>I͝7]h'5ԸNjx}? v=_X" ;ATr$6ȹ3k :XWqƗoSIn+:֜:/Ͻ#^v=|![2PҨ}DDI$e!4m.Zs{_n- ($րsn/Q[ɗ9p6B@d'Q_Eɨ^\[9Vr z!i=I2ȓќD 3C;4t=_D;וA45^:9hIqߐC@h[Nt褋1JT3}=nBqKe%:iЭ lIN"g\=my" f\b?blp94 x| \I!D%3?b)Gy4gW80&i%UTAvs_yڤ*QBL1n?MzIZacoѐR -/ݧʠ!r'"V)@q#njy+WL'{P*&.~Ik͞;4vW,J"@U4 5`$:wіќ &ZNZ&聄` Nkе1#s#c'lyr5\qZ&=vZ+]Vs2_+j$׋,RF:(@m#S֩ngd_oG]en:y!3- $KAD-vD R#BLDY;9׆4C:ωUgA#I":<2L{(.9NZE…1&j z~670zXģ3wLml]3@[YkkFU8?.2isáFM查?L!cK S% De3t| /fB^'DEYL;CY-oDJle›s_Ƕ/̛=wtNj[Sh d'M|ZNHØs2CL\{Miv@-+GsR79 KN;X;.O+Шh#KxAoRZP C ٳ?= ,4knlUG5f^Y1.>m"T(L?.Jjv oZᗞѮl㲀v B&N4y|߷[ja 'WNyh"]]0†3=JSt4ylTgN ( k c^P^`Z+۷jW[jTM -#& Ӫ;%V]/mI5]|0 ,1XH)j-V.I5gfO \Дb}TY]:agG4,x uu*ZI%2ԄfV7`޼E FSa]?dkv#V/?}3'4MSTB$ $J S?V8_a[Xi6^ E'"g9 'ԹA؞L3,Jx<+I_ 8b6D80t?NZ3*'QUŵL_Rk7f&7;gƪ Cfu7jxfA*|؁ǿNq<Ɂ1Alof沓Ǿsks҄man AN9;EFY8esKL+k˶{_;0,4qGǶrͬ9n}OM!Pk <w_h,oXapTêłF܅x*m\lBgkw 7O؈kDeU1͞w?y㯻1,8R<@[Um|}7/М>A)."u2j DaIfM>sjEle&ddbϱi_jďKj]j9#L2Kׯgb}nk5J{(Q;{o[miu*u%?vD؀/P$:5ذ>f {; \z>`9L3M,[P~;l{ZT.*IW~Wy+[v[hzp¦4" J1>mf״lxQsJ*"RAГ%0vBPX6lτa.ub4UCS‘64$8LqW$ Z|繙/tjTBj*Qu[L2n~NO5C+ՓhI5Ӭv1;b%OKia<~gf5?Zk=jv]q6k, /m27G}v쁍"-<.tokȿϡQD) ͢/BMZk qh 7kfse jߪٷ9R8K҅\Is3;e׽zXw!>ڧ?;#0Dd8.KVM]'o[߬VMl=o*gOQ<#MW.側F/S* aD8b,e9Ȕ~tV0@qOϹQ&ǧ^g67j`)|J8 1bQGC[:lO7᱊ XQ)em)GmGTJCi%EГ3Ɨ AH" \NF਋Hυ zMA2118H!q%Bڟ~1YW6 GGBjjгHQ-Mztswu@2P(X5\^Ff̬iBɹ+ab1; P3.O}ՍY V'MߍN9 +f["7$@&2ei+U[Ty(ϧҁ}y@Y $ N_Bs(DuBs s fHkQ}:' x@ G4 |'N&JH4Ǟ2\2a~t=7>}U2&ͷ8QXg3G-݋|4Iħ!8:&yI󤸆vtM30b$-)kp3k=\ƯZ*Ztdrؙ|MvG߿ny a0  ~@x&J* $ ^ AyWzqQBn~zΞTLUUnw,٤ +r8@+4ZeֈJm ~v}VAGe)<qEB:Q%<vH&ń K-:,k?/Fw8:n6_ag[^gd嬉z7~x{ʔ=K&OUO)Q }(Cz-UT#Ǭ!)o="{kfX0Y w9*{^Q@ 7:ϼv{&ו8ѫQ%"0[x̑(@׎QEFFlVLbon-l!"]3d ]kW~€"^:K!m`=c'< 9BA9зu?56 /  QZbzRq(!(h0 bݱIF }C" X0Y4pg;we٧2; )|ceo=! gUEW ^ޥd(HD֪ AA|EoT>b|M$W4ȸ.B ںUŰJ&@WX8Hk2li)xUu˃y644RֈDYѓȤЕ,K#azW)6:%KbQ𤥬X!"qQҒ>cJ_W$AKe:R+r.p$ x]t ˇ-TFrvXxT ɡB%y4^ƭGA#8ȭ@j>`"jtc5F"xDĂV #Qo{7OZnՋ{gt xDFE0L4ǸwqX4li 20sZDV9A:miJ7}Ƹ,$I^[ؔxUujچOT/Qek ά;:BjF"^EœdVXQ6A;7-Hh0^~j?Lmg7$Zia9jg&Ȯtɖ%qHxfɷBX4o %#efɉjArµc [7`Ys8TnsF*1*rXsYYiÅf)%5if~|De#ڢ $5Mcz$ x4f;0?5/@aɮuoZ|/gcXK"5_$ֵC }B^.BC`iM$@U46ۡumG&EG'"4@0p 44WPdi*G~mT8d|[z~bB }(m3T_ڥ|⏢/X+ƫ@°ciB0E1oZ(VfH z$GÂŐAN4Eklid⋱!-{{aڡN8lEUw@f+VػZ. ]iɭoo9e&G5F#F )WbB]PY}~k5tYhRXɹ(1-ƔF(ЙN0 zQS@J"H*$Pd+"[Sfu51H%+ G DN!VWQ~JO?VEr)XP4,_ISDR D (!oPe'PnU"Be}R(@k`(,DOY;E4bvЬ}ChLAjn4z`Tc}ٻڞ]^=/zgH'S掰qwR[ښQƩ߬)NȌjA 1 |iXvUBwT:+ QZt %jp 4X?A 0& 4DyB'ũ<0iG*Iȗ c D0@gUPʠ@QYJeIp^Bx6kTEB+?UvKHԗ"]/;`YU8: {bޘVPY XCU<NXq(;BX#de#_^O֒!Re:C*#znPܐ8J3)}C+MI,az1oeX a"P`@w'ըۊ0`K6]I'&2T1ʜvwQȜԼbLk 4֝f ,8C}STV75Ҵik;suXsݣN~F 3qXy ).NbB&mk]e 9\F*xO{h5e8KvX|Z`T4rDkUK%:蒉؍E !vVqFG"V@ad*TN$SRpز4kQr^+Eg<(I7똸rFۨ`dhgqFlnIA9Ɓ*#'-TA$NϢP~Z AM0Oùk8$WaL,K{IK;Ϫlj P*jZӝ/ s>`hPT Aa%T}'`uG6:P9MF ]vI OikqFo_b7OU\0,zľfyv4gYKz;g{MS^C9#oU4l&q7`qJkz%j`mR^.MkT¦][6!3FpMyo8Ņ3jkOx&#Y̅/eT+Eќˍ PY4'V(*K}=ko\2^ "R0e+aH7.!# c%c $F5RIle/khO[\LyT}6F BRrGf C9<O"y#) $J8Վ-O`X}C"|y-%1hb.5hT l^[eGl(􆳤 865NPNwQ#@-+kfl89yecu9aD ʊ>e5vF1$Q# =+֏WU@I驄*Fh21i4FܲV 8 \}$cP؍5vВBqȩHLR"U%ݮܰzPn+UK Lܓ6/ت~H"ĜInZFMvSm|(XȩjH;Ed,;eh L9b&5y6,V+;#.*ƺv(d"x*p0 qx*;ɮJxQI-+SaA Q]ՅNFbB$aU@1â01dؙ;bG{͂n]TXC(&kBW/:3Kk^I+녦s9Je6)>"Ɵoucv̝X۵'W$cmi n:sʢvZ+]ϭ89 ͍빉^F_-΂p,q&}p\#lXYqŎ`^d k/fe~XjOs;{=vg4az0豰q?7%wss5c۽`]mDt!4@$$+pHķ21~zĝeshMR ҅O7*$2b 'tvۜ:yeIgI i8qmV[1 禤5*RђzN^:ebس&ɑGYxmN1֎;v8GsVA+l֟1.=2q=+# |,)'BtܰNZyҲC)?hWT`.MU"(Ȁ+m&]m!\~#.\!Vr;i2Ąq"=&".g{#d H]I7*ӹ;'԰P2܈Z:ޒkfVl_e*j1^jSީagFT8,l=[&fn\yrLwGnsI]QV'h*:ݠuxDpѯ#raS׮YkYTn9 مUuqh-䊥/Up05d@Z&wZ1'w:n8c'>ѳO>Gm6}*A83yI;b!AȄĹآ:pm0vj>3IgE'&ީ.x>ޣ#8WZVvSdcK&+vz;5l p2oժD7ZktMB&cbhB.[Q ^ rI`66HMLR!Pz)"gEy+N5J c{^ 9j$:?v*ٶ9Aĥ1&阬jp>aڮ{F.mWxKFo{:u 7L%OaxnŚy6=(,&dAo]<*LWՔuH# 8H(:DZ&]r+BIdC\W1 3mW7Bry(?οHg غzǛnrjSiڮY|$t@Z4BAU2cmޯ2ך+[w8 5L-S! 0&L/~@h''0u"cZv+%C"0?{b]MQeWK["$T/~ ڱ%Mpw;t/:jjˎu=7Q5CVQL*<=ܩ/Cv^3wnZ>-θ҄Io(`ɬ Us62uZCؤ^_&a](S!5F =2ldsE.l/]D h ༴F@(BATM:sFig5T[2昉E{*0h$Eb]QO?"i{.su$2 g48"* &j1n>;Sv+nKaO((wjB%XU1԰-/[Y5hsW/jfɚ]jMK5zN\t՛WP,f"DGט}$fӇ^ήm`͘IH8,[V߀,B(iʼn5]+~8jm`~#e0i䶲.z!U qKQD ~fs,An1 7kj`@!D9,T7!AR(j`w>=HAt-#S'l3 rP#*Yyy P@^zA.f$G@XDX֊tt\4k||e_4d/4#D]8R&ɄȒf ]hE֜y:ۑذ6愝UZY c1 \4k('[,e宛`jUgŅz2?~c60SܨZLRJfb5>(THI%,] V( 2R0VRbJK;B!o01 /X!-1 BM%4-XBecv vw3 _a7ޫʬ/N6QI9~_pb8? ߠYh߈n`JcI<t%{5CD[Ri8B&PMFC( _6 `h;Ar*YR:\ERojVf$wȜ^@*ڹ= ܂¬+~,B*n6]69 7fN~BQH-$>? +z ({fh.b!բl^%A%s{hw5jk"/5Yvpv>C~Dނ۹`Pʜc iKEc;,q-BzŅ@liR &:ǐw~WlpIRĤRw-DdiRD$G7aM (QgYlWc?M>f9ٮFG)| kPݓQMk%27}$\v[u}0KiJpGRSF;^G1]$[?j>9yQ*p*,lخƄUcOu>j/pU\E@#+ҘkC0;L* {yV+IYx]Y@0PiX{yF_P蔳@<)全 ?\<8mo#slG3>L{ D3V?}Ik6Q|' 3U( H ޡ (V$",Z F&A\5 ^ $#Org裀3i;}w5o h.^sh|DF$=],o`侰cݘIxmKʗ݂kp?v!'7fa mf4Bˍ6E:i}#Ҥ6zV[)I*˲x7ِSB8C`]M$'$4IB;Lz^dCHD$ms䒃&Mi VTxi@rɳN¥vbJ 3rl@&e$04ш1^f5۸/Ez/^0LR h:H%A`֬׬7ODR0?:$ x<)ZGSv'^H6XRAr lqSc^JL-xN %JLv..xhtμ.P%(t2:BD. =mָ|u0,uztvv@ ޟ6k8./1 轺epY Y0'jf aMFHp8. \!(ķ|x[% b<o+`v5j^`9HpzĜu 6 Z^7yCaf<+ j Š(6I8mMf-lIbjL>OyC0%oJTf'u'i^::봸&/fMUj&CBLZKkZ&<6 ”anLi:!/r(O:Y˖tgnʠYfMkfI?n|s'5텽J󡳅Yjmݜ!O?|sna#1ReyEQCPLAzn3h<#E弢efpI$O#% ȉaAêq/C=75)؞}4 *TČm9Cv쌎TiUˀС4e4Y$cwjE4a"Vԩ=" ^)hEߎUF\J^f,"/b5ÎlOҨ b H5JˬUTSf$iV~o +HD@``"`,tܽvPS6ܥ6#k1OD {Q0hMSnij[9킶itrb0xC^4 6<Ҥᤩ[]%nG%X"d%K`w>aӆϽ_?+,;VZ;AM$|C)(f# ̌LMeל_:Yv" 8&mdpdAhGWǼj⫦.*4]<2Z;~P4ɻ>OXj5ZUQV4Sg OXj,R mv5V1Yw9?aN4ૼZ Q4F1ENT{L4/2jtbMƷt6#/=rd.ӚRhbvznz-N3hl^sDX]{H͋,\a&חreYmQOٰ<}뫲ˀ d4slZt9CpuTi,ѳɪ7+FRzb ؈?jyzj`+R СrJs)#VKH5KP+C95XeDV^]O9_w+p -*k֧ X1wm]|E.D,4"-.)5kTE*iJyp*"AAarǐP2NH &L=S8V}LnZ QR˿hjNGi] 9 Xa"Rӿv|Lqt Rݒ5AR!e5rM=Ć)Jmg`]j] _2Tկ)ǚNܛ+qVY_ybJs-SR"VL H [Zi6?nyNol,_W8WA|Dkw5(۹owzse#&趲Ġ1q $!iGS$Gl8z(P~,<|~AS iJޘRT󛇮?3hn~Kk"!/ЌP8w77ZG|a*{ y ɯ[؏Foi E+grmJTXd,tOcDBsb62vt" 2OiK#%Dh dpbAj p) 1kȢk\ EN+@JƲ3*ZqdHQ!JYM"JLau7єq^,(}P #6h!2x*x"8x; T!D pfdWqOk-.R_϶@ D+M4?5dW^2{*p&$#sTozJYI׎ؖ+^3U/ S|9"]R]M7K~GZ#B@zVb'Ch7{eӷm}o,0tV8O1Ѕti/gL6^bVK]ǡ]K[웻5jnuY=$rteڤ$/>xjߍ +M~зws'vO^C\/6p/f|^Y^ ?Ch3s+Uv#Ims@k:@Hi>ˋv]uLvhž{Npŷvͽ1ZJHDltf,ߵ0O?NE=V#{lˎޱFZYݻ0?~%<ww+Kda3/NL ILdGiE ?Woxg'}>|wi޿T[EJLt y?$R; ^ȏxZ뭅`1W.ɦ>J~[oDP K@6bWh@2R m1dtrK ofzG3i &s> Sc;5H΢zyTneEg/^Zwq;<9D@%'ԛ5弰>u#C͊͞xC?~(lh.Rinsg(4YZ&9i9h!n և)r肪 BֈNs[POlqAf=/qAIʭ>|qƮC]Q&hbkY @zNr+}s8.C55=V78u'wPw{~TMs=1ͮvSjJ5oOkƾymc㖯4.. n?&^@*SaB\J(~[ x#Oe͗m;ձi" SKO s4&TfM7TV_>q_U~dV^jS7QWgăon( ,56U=#.2˚N_ޅ˛.uw:Uu 6џ!d͋L])%rwM<"zRc*{ Vu1 @HǤQCTDžo~ &IX/e4E hnhШzsZw,H85˛(9BQpՋ?)+5[64o](\LN4J~Oդ#RQCHjA'2y4#g*'.cP'fTpl4Cօ [>rQ-ߋyF'Zod\tuga%e%WX\~Yk\YX^i_my-Қɦᷣ=#76݋uϏzR*S^y)PN7_HoFM7Hܐ>/aQ=(U˖'+bI͋,e]\e+?uatWKT~V4ǾHgdZO9u$d\oCS&Bʋp>\ F_d@彷&2tvjVLT8ld|QC9Q|o5ȵD0-/BO2›E$4Wit1 4^WXahY49'읜E?eg70<,(RLc6l}_;sQZZ7+s'Mr L扏]ɯnOJ71EOkzQGFs\vL(J! &G5QH j4Z3~n!GǎωaŌ HB(lO1+5_7/p'571<h*~eLP!$a{fOi<eBY q6[Dql2%ንƵrY?+ Ip(ӒX}38;m9-e3iv[¡eA#뉺K!?q+HQ:osڼY2K}g&u$1-_!T NAn0V.]b߂WqWK;TlXP4zyf[t.̛ڠ&.~c j.I% x?<&?ћ'Ƀ[ݦs&m&.~cjI!>Zx~=\* AF R57yE~ys{`FY# !22{ek-EWmf& 9' 㠞F빷-2Q\!G4ݠ=4*S&)Pbn]Py5onMP']{һ|Nퟦ5Lq.q :!O˷*n}.=Q9nE8nwʠ3Q5˷*AZӻS4+u")ͱzAQ=F52j:!ף929jaޕ-zBF*:1]W(Ҁ6v.0h$oJ4.(fM/ʨ6h#𥳐(]?BIH`% "}KKR& Ȭ^;B>n0jІ>yNuC5h}ҖTk랳,[RDٗD&Q 5Y5F+,`רiuOpYZdDZdLp(y"8|2]«ƳF+MȲqg9q}ٙ?:kbU3k*OLHv$s5FMS#Jɲb`jXx¼lVtB@ & r,[zB¤,?rKآ9'KfEIqk`Is/EFbZ|iJ6,Oijxe56g_+kln-bkQqQ#x]jfȜ+uvX|ʬ"d]75ul[+o#`oq.͕l|lTzl` ~z6RW͗p݉V4X|Ȭ J 9 eD /3^nr1`˘:0791 bwM> &-j^j֬yojUJkj꥝4]ɠ`z)EXAtQ1a$3 !Dj5b[^t,x1צ4oXk:eBmHrW6WdrJeBƑk2h~?r|z(N5& 6ԙ kH<Ȭ!Ʉ_?*kUV”pZ1r-zh/1L?R+5Vp 4&Bڒ O5u NVL-*E!Nx#AUB ,VD aAQOiT6FB2HH)%?)n,FnF,C:oAu3ktrr2LJCJM(IE!>pR9#plN힘{b 45-⬷q8-'K@'`sj?)Z; CS94ڭMF%S;/'ix MzXsj?}Q-KYOĖؿǚS?/hAӡQC"iΉ3T^I 8!N +nfFh1o4^I*UO~>Y~7mӍ7 ߿5M 欙)Nl(Ҥ!IM=Ϩ /4@tF/J?hb$/ A(!Mq蒔sJtKY0v Kc\iDqkF,SO'4z5HJsx+޶e9F gt &E>I3B_,s%,g$%-T[pʾA$H8Mt_KgR^}Xkr7N@: E~^l%wp;Z.%AkBe`!/Od#3bK>YC7s-s]ydX O?4#r~Uh0FDyH2*E׉R i '@.~Bgcഫw{ުH%ڹXܪhfLyQIaD%HGtXԉ+k6zq~Xϕ_X&S3N; )1Kǵ3iFMD;0* }X'?MI3Ts{QHEմ;SfG2E+S بpV-~p¨ʸp@)8 կɓU9_@j(!zR'Z xGnɳ֌s>V,ٜkD|gghW’ |%c^YW]sx!4QBPG'N&D鏋l+52D5D"[?e^s}] F[ʺHT,UT'oͤlwZ"hE gFWicONf hWOڲ*HªHʴќ eЀ"g~l,I};VJles9#rm{}u9{֚n@ W`z J3:$'aOn|z:Wk+T{zUtp"kd(Fs3s_ϫ{1=PE'JJn$s2 0z`BLHx=kE`ޢ͘6P= Bq RS &~x9֚ppPDZn"vr,5z\=izO:5wJۆ>ԼSX9Lۭń"N* OWQt+SOZv8'假b"gx3rLM.=kΞLNh,aؑwT9leNO^Da?qAu%lLQ<S%?%CVێXi6z0U@K} 7^.gaД#Vac4Uy{*U!rYà􈕦A3G[1pkDqF4gK)C/l[5]bL{aOl0R1ңS5 3=+Mku*;J'%a .nlavtSUk ujCl᪪aF[jL6JW~uD֩R W,օCwJ7]D(vE+d$C?b)-0V2ԭ*Dgj`$TUꂕ/HMs(8j-q:]jUR2j ρ/'yfW%%* EU̡عMJ#y5y k _]+U؋yvTaZ#`QA'"u{ te2O○X8ޮsl?n9p'pe [سZP~z%{6[dP8h eG)n ͢яȦ::-h8\=ͷ5ֲ/{n ͍)nlފ_cc3C=奻]>h7-4[X6OdG^KaXh*"hy-5K t4uFNKU:wGMRWQu7&[gRfE肮TyN=XhfV֩IN/&ELDIchZIwmWN1tX*Zr+:ͧXG6X -%tJ'\H%ЉBahls1LW얈n @ yaf4!I.!>227;m,1Q[yHg1 j.D {V}dFfŁ6(~œQC[AYZۻ J*)*`GQes-M{<{TI X$e 7;2k(g W=:Z)+k|7Y) ޷|pEc @ *r`}6j0\F8`Y~A s^'˛wYfn0Vp`3|)*% 5Ƭqآ]5Qow%}[`+jY=̔bB=[W͚<)nã<0ái2yӪbJ>#uX_GK5kf,g{f^sz{ p ҙ7R#cG?F51cJ32kJc^Ww%8YU.Vc/(s+lN!RﭳPtLQ׳z57x1kt bHku9Am+*7hSWbY#*z1Ϯoc2)+gἺ46]zV^ܬQ>-)=:`PC0֭u7o֛rFcBQ/2\K :tӠ ;Xbh AW"v"+$x!yRk%f"Wuʂf7 e$ $u*[{h~['ȸ"0k=1β`D*R>on8Ïs\^Vr;F TT蠎k$tI\0wcJc5Ŋ DF&d1Ed͓4;,gF$@a&/ɰHPS)EhRDN;YhOJmc0B:i(L0J}gQ' }OtD-wbP SG3j,޻b2sщZiq0.DT!5 `U*ѡqLLY(Qmi&ROs($!si^o6<3:fڐpAyYq+/"xz'*r&##,|Y3njoc朎se[%B.8漊a#_1OTY閨V;YhD5: B U˿ƒ浓Ys-FPVw%OFc Q3$v3֭xCJ購k,h~r0w%y(V4lrr}U)JeX4ƾ>ʬJ+^3CϗԸQ:oXj'B^' 3x`ZOLǽkɱhsU. F`R"3jI6jVu9b`* vLT5s(Y,}2R.m+;A;z'jJkaMcd$s{K4v1 {U-!V^:%4ŒJsu*8z9tne;ݩϠi慥$F]tY D$:7=uƕ|aUsܠԗ5k3Uni|>JuH0%Y`iB"=nJ<+uT&BY0k\詡D>ޖSqfp#BC tXT(-c! )Rt M4ìaMq%әH,$^uLt3khQe2,\q2s-5$(BEظ]p 7T4!WBǚOàbf *PT6e%=҅W]#hQFth2Hi|$u= 2>`"0X$P޻rzG& uf?4j}FD]s+c@e>Ԭiuam%%5!Юun!QkQĺ'Dd씇[hΤŦ]hk鿊v>۬okF8->WzM"U~[Q1$.H H`Rȵ!B8ЫqdFOoG7t(f  xD)3BMl 1"'jkaT<Eˊ]JG1e ^ qZeؤ@(#JiJqErB ZDt +:Z$2,i0"  Iׄ9喟L꓊+een`紕Rf~T۬mԭa7jeAEK7aYJ#2jj ΌRv,7wlDlĂ 2>;)vZ_ ^F \*,q!;!U]9zVk[ Z ѢQ3pQ/EkN*\_ c^X])e  @>-Ih: >+hBbЖ1š)q.'Ǐ5@yoCbWzJ1XToV-h*ĺѭ̚|g %!W<5Dfͨ9ԤBRV_P,Ufaz"8%c^Mሃ՗cRLLd͚ OЊxE8"%LYP\`osߗoWZ_9yq agd=$&NJY<^GyS] oƮ<&$WE Y ַԘ|`9U!m2d@y.ٍ@,||Z <$Z>g yC :Cn4*ȵVm>s t YR52n4vRKJp5EM .SR yT7hRNY٠XR$ 9=)! pF 8ǯϟslʦmcqr* pX~"}i'5GG:Wԫ$9QKm$%j7*uq(4f _ :KadHii*v9#;E) %\6)-Q3m kmWwa XB&3)v_|]|WBuޔ)J'5Hd }=R6kjпut01i-fheqQ|9*p\̚j#$#au.$84(&z?ifM͠ڠ);=1s/A(kȡGf[J5ɼdeH_cq̓w=q>_SƧehbǰW8m̓ffjލYjckR=R@cHye2 0zRPBJ7SXNh`Ŧ\I0!};*P^r:+noV㏿E6i`~jw-?=D)c۩BSÑ'<Ԧ.&-{|l3wy^yLj6&Q`9['溚[߄n]{yY-Ynh:nz~85=w'U3İraE# ^bҤhhn YSdϪ:1zHJQ5#k۞ H1r4zM/pl}O؝(/is1uZq<\ FQhH%Uyp%4[q"ĉyǫXE5ə:7b$2cO'd̨i+'D3NKFqyzİg`g9 ;Nqc^NN֊H¨A4IXo>u}S1d b3ȂF )&@loNMJ5e"” jWwFNϳ,o2rCUׯ7i ֤IDr*kDDS{g䙾{Vr;9GfDm5sb\Ult՟ Te?aFfUY1k\*j];z⬵,[ݫXK0ȀqT<3ìɯ>.h#‹#c[$V.բbnqB<҄pƸK[ڶJTMZFY5fcDp k m'v͍L5FmBE8@(8d,w<4 ra`d1^iY[@3%DHѨ,q@ 1IӮŘġIF#}E{ 04#Sbu5o҄P&U JUR#3V(x7jh1 hҽzZ%04wz Qe L&<&fbHQsэġWRqP*n8H!C3BGLiNj:YVbGRlmk4J gr(ؑ01mNsX&jtGnNup֋|$t\v~snJf;4=8mUVEN\Ktf?"[knY,mWe- _(\,%_fM٢!e,OUzMQL\@EEf^A a#GT*Ηh:P19 Uey3֎6*=omtpѬIمXDPlvm#t%T "b-hԧ ޼wR [m˧r)޵lM'͚hc'@D1:[oQejmj,<V C*&,|ET6}@gI/Fwћ5RZ{Q`A9Og*Cg6k qynG6.`{JzvҬQP}ʅDypI614kH E^pXnF v$P[%prkTU=ǖlj0]eor aFFcH %֚>j=آRX{"3PI\> 8fԠ4_1&:]8Z0xL-6:gSHVyPYT,;/Eư3 6b^5dpCsL1U eH/4 ‹D&VF^FTj![Kze4Q'As-ꀐ3d{J5@'POV{Y_444KxCnTeRJZh؈8v؍9^ݭ[Z2a e#n!=uj~hT@-ӗ в>F1 AafnQC;myXh5A N1R-(4ώ&s+s&I4SIۯW?qnsax{cb6TE$lT@+>#dFmu]78KbqzlZ%+#YzY]yV8OVkWQOexVTS@KO@ zfΫëh3o‰E#y ZQTˤ͚Wqd,;ű MjABBDk!f ZUDMQc 4g9XAJ" C"4` Zt&W2:mf?B 3mK:Mo4"P u$Lء\)x + nwh #lᤴ9D孚3o%XZTꛓ^MyƵ)vI s/(SO<2}%&:f]o* .9uZ #g Izkt5J]Kyy:[$48:2Ƒ:W axw܉bHyIי ҃.{n'R`AYH֜{qw^6NdH>6;6 c̚(ϼ`t[Aj\ b&k ΌjۼoƊ idLBV쾅5hV4)>>A¯3k:dNu7FpsՓ>[i u&L`)#R0?e3lORz%])و0QF-ZMI@]j [5J%BE ?OAŜR)Ra,9y֍7ADHws#`X" Y>u'EhpR"F NK awg$ Vq8k uNk48CҚ$C@`TJ^nšwf`+2ZC PJ AAQ}'N=mrܐ=Muq֪ùe;wN>[zWgq{Jɱlĕz:0!f5k~=ZVt*X: /B!bRbFIe(Ӭa(Y5\k덼 6 "K4]3-LŽ4zej96G'A:mvk)U-Al† T ?$PQe1B۞ϲڸEKkT&T:nq~hݑa"B懖P+<w(2/r?}iRCKkROf|M"ŤZw׌ 1Pѿ<]ۢ۝<; 4U9 ghA+0_ќeo}b*RD_Y:g%;F8Bt8d:Uz8M!CS#f"7RDDvDK%vl+Av d$;B$9_DYoYe(}kt^??$h_.ᰞaQaED ,D$PJ[h܎vg6(մ"e%oH3хC\Jw"D>1YzM|e!]چZk4E0-j *!Kb`C@zka[%6K h0f<.D|8D^,0-,S0JA%7*#Icd،ЋkP|,Q8"m)Xt/фd\f %Qg,*u-ɫxu b@3h :%4`"rIhx]9XIdΣ*(\%Yy7yW/.V4&3Iòd@#"P$LSFRX Ë)5iUa)GD"a ͷ2k.dP:YG)i~W,?jr&TT ]q8L?xپyѹ(xiP2R(D*in(i bݑ`eJ} C2*EnZMІ&ϢzMJQW6}RSs)ܠ+]v*QSҠlA2>voڀ\/h963g îBxhqIҌB'TzGmqfLOTSDr$4$12luGm!٨59ESMBJ/\+ X\ ,I WA"/.`Xep"{M1mW#" e`K3~ fOSr@p6. 3)̀f `N7$I=C9+u<)^88D Phd<^d-%viPAW4t֯|4RQGu,&;E55k?ڦ|}8TdBw#Q[N_SbɒPAjY|!RHHԀd4I^R3 6KcRCH6HmZHӘh"j~hk#z<ɼLE/mdO__+Zk ԀF2q(֧G5)4Bns3)Tֵz#˖aSd2<^Qqo J寽=I XcŅHϹ''t!2O$e-rV$:a=ݵϿ)!Qהt)fl { M(xл|ի{ξ8損u xʆ?ד{O[hn~zF&w5tIa M}!|-43'-_=K Ҟ W5'J6>yN[hjUm7:GZjM*{s!vii M+j7Ox[e¢_DChٴ8RW ✘V^CVj\6ii0ǰrIsY7/E Ds*Ñɹ54PTaxG VęMaK"yH*nDc#7:󂣷E"Y) 'F c(cbHu\Q={ 8bW8@Dzc'gKBZ3 p@:E,?&gBa0Jc ƙq~cN\14l05r|߶hlIzGMǞtJo(Rbi?(9|m&5Yq5_PgD olh?q˃FdYeߵ4H CidpCzbj? l0ڋˢ DESuG<]c:;d," "_ЖhC3tIID 5n<;%V)J%Q]wl_MrփD=tia>`(nX, z-Isyn*V *I Az>`ZRPb !\u.$O$+c V!H>\zZ b kǴXK36wڝLkXhQ6ZA?JB@3 H@)Q6$$H;N KIyB(N3rC/;/ ^QimBm7ppexyȓv O{K-!?DJXz*h|3G)uC}bЊ;_4~|g< Wt#g^6];N3)m* JjPq)=C|A+ɑ`SKECO/n`XWn䛄FDAAy;Xc=<$ŒdRi3Ivc]:ȓIԟ3|1#!"آa{:`u:` ak{|{u]T3 HÚN69י5xxuC$F@'-z:ZCf w>,ٰ]amN"CL=]yUX 8l1֚KtuZkz'Ɔ̏[j¥K&tCs]PԼ7BH%*Ke/yFa!*_O'Q{0{1It*Z 1^ v&PD1RsA98ҠWvt]MT* TyE 9eZuy|m0rXVUŵHɈSHխ+J+d@1G6hh8O_M}2 WKjjAg4$#']({8zIР*+DӁl0_~w}s6Fx~.>R<@bT? DD!Xqa YrgK1K̓+kMsGj$$c\cZN_!i_ԡFM4Pa$nĀ%7MAt`-+lz[|{}wDg+ez ktY4Р8tݮancASw톓/,Zyv@ aɅfNA*n}~LK\x ҷW+62Yh!!&TV V ^0ҋ2_: $DK򺩳gX).{{q !^65Ňv{BUݣS\cs[@&8K>S$4x~-^fͨvo~JkX>(GTU4TjF63 @PSr5;6Oi [T_qe–gkKL(!" d 3:&Iu>R[rRwт4U52%qQEZ h ;TY[NS-eɆCяi b#TSHB]t NĞH9U$  7bUs#ZʳXZ=A׵HɠٴzaV{=ifY3?_ms^Գf^曍wTV#QƥqТd6ۻoQ&_{~S6 ͚3-yVVD1כk'N^̠Yk^1 UbP8VkG ' 'Not:3݋az| M) MʗrUR+7l:etk @%ˆyiJBZ]{Y ;w80|dUabߘG6k4b 8':|\+O4 F/BU%F *8ݤmܤV+,LVKgM?Ah/ ⪺ egK,se#"/ PU TFT8&XbUѤaS|pn'nnjX|^ *(9q."т`"$TOKAG$=§oOhN[ty]um+O-o/86Įn;I C"bZjD M߇I\ R@TLHz=f_6KM*pgLhsLۿ#J=pI?%[~eY݈ݮcܿcZIFy痆wǻlOcF SO_deJآd]Ń6}~שZ?@Xk¼X|? έxZLI/PcLhRegRim*u޸8uIs"4p.ύ_d/U%>l}wT#D f佸>I~[UZ PonԤWOs=+ꩡ "Üg.Ɍ֜9xIO_|usŘUTʚggͼ)crP_VW5p"AO_eO?}եuTg) Z\c>OiLboJɺ!{pc3n}huR 3Mً:;50K1nLO%2M'ud\34G @'Z\$m=ǪkS+\FX"=<}ԝOgQzmߴo96d3[-رh3y\QhF}8ngo18'oͯМ }+gϷ|dSv?y-rS 򻾸Vlj7; #y{zxql՞cGzn5?VFXxU#ʹo^Ȩ%7 5SϾ$h mtc馀 5MiM @fK DɷVȐV1O=MۛZ71 X崙^}Z'W>YCK47*$Zψ˵Qz+?UÆgW.N"/vQs{{CF;>&iM8{kGúІ%…V nhS|Mb>KcmZ $2>zsGdmk. 8soKcc9>&?@ss®:ZC5;?Cp&\UOB=YF_[T C͔ף#6NOh گ=Oiv55/# jB{T>??Sz=}Әv5O^|&Wz R*NRF ,+O$k8S O5¾e^ě4[olBԬ"-Sша*{fM>lțIpԊFibD[γ='K D' Ñhe֜ FPttTN^(T3姎6+ZA\pײb٠ן'Xc!(>]9{}wKUq{eLwG3ߜy8; bOtV͠lE>AGWn(a؅3nO}{ X4j<6oZpʵđG(H֡H̭Oz< _T3əe}ώ?x(7Z}Eδl|ٌ }-ƻϜ׬yt3/<}e~?Mպg+BO_坳B/n?KoMAa>Dg4nӆ?w#%{^-'gyfG~ϼٖ-Z"+6 zOse[OY/87&5 N[\Ӗ]B zah+^DYqO0g:͈ųgty Huu'JGk{["ږK=ʳ('>gJUnp8*Mɤm^qo |8eu>X敬5x\<{Q-y٩⇃6nM$˦K'?&7hW=<ê(jz?,[_Eք_)C?ʏ@&3irn_ hk١{C?67'e]'؞!߂LiNǮ0jN9Eagզx{SknJ{/=_-jM]خ5w?:IvMp+Mѥe6V7G?~]pUev{Luz1AHR7ov0({)Śp\izuc^m񞚰P&Z(e%86n!,f[y&@Zd ͂Nhv;z>]%CYaOj?㬋)۠p Mz{ߢ@cʋNbI4SuLE4(jY]eN<3qAC]\v9藊s0hL&Q<uB3$8L9zଛ?r{.pm#^f W2h\(7ӄEA`.77|btϸAin2o{.. k%9ܼ~E,okƎZ4-e)1`fșrG=% s %c\wlRvqߘ,h8fxG{j̛~ݥmG-7~RTZҚ=#϶kΓ [jԇSDY")_h-E3o~F~.U 9vnMk %z^ܥe{9pl23HّG6__w^p58yD)Ivlb)DxԣOھw50L=YR2[\&)0kpmTLƴ%n "ܑਥe^Ah+<đG{XkZFK ^^Tƴ$n &#=G-oזԅjFMyHyZ_`\4YL?:Ow8rXy<- LFhƴ%nʕbsG",͒`~5"c/\:o/^aY;DoN N_ư1W,8qnRA :[3jngj(}H19u8}*9ײlWt֎b/1uK73otZO)O5(Lg26^`{?bĜz쌥~5u),_c3w,]")y7Ok\CO2hfLF!RUg^L(mvˣ4A/է~w؉_2ﭽ:O霱AJfQŏ[A:?{43\E*+D'InF$D*,4Ӏ,,h"r mK&lI'$qz=q [h4[%wֺP M<3tJz^Y8CXFY?&ʯkT3( !%!JI0ʾ9fu|O :\)2ϝ3rc oDp7uP9ץA+]?gk:!;_亴,{o-_5~gkZT0{F90T φ,gָFx3Ym-(}Ss/em~/~ƿɤiC37k,2A3LGfM㙎t~ws'Y %= {SooZ`n̤YHuF@cs:,{4TПrśeV-dPmv]FMb|aHjWc) #5ׄlF.h)Ma`4s(e,Ni"iWL2?jXg$ɺ8LG&\d:R]s!<~8s'!:L$ہs[k kPWЄǏgop. Q[F{mVNo<)4FMF8=P3kX4\=$;:aAsւ3l"&U<^xs\#$;:+!|lqۼ=]y*IZsk ]9^öبamԜr蜉4g,ؼ-_a<%7iA/*W/-]s p D-R vѸA>D<|.^\&e| [;S<~K]q}4o:¶ 56q8>ү2Z~jgGG85h9M] yC\_G5΁&Tڥ|Ay0#Qz /;8Tvj@EK}2Vo6g~O&ukXh30 t4͇?x!hƍϤL_g'b_۲U`d\qH|=k~t8qwg47YCB[jjxdJ3<M>l9΃;cL|;m|u|YiuM?'fZuRF:nh50Xl5kտP'5 [{\5+B8?f[-5E[Є*@Q$b [Nhc!9+ 0V(IM:8p bc QN{S`'Xkn 4`_[Xޡy}c3'?0i&aF` n42t~w]{jF%tҹ1 a_vE}dǟ%.`#Ɠ҉?ڹ\d Dk}Cun:1afW;buvNj3[[kr[ĉŕ3fzNj뭧y;fօ?zkͰ/n/gv^~տ[3iRg{[Dykr1EJy݆QXز(i* 8[kʀu, ca9 !Dp\ف5sXBFa)~݉9 \/pG6䍸cI%$J#-/%<]  s6+o2 S45g+N 7MS@ozp@}.I| )47x-);\7A;H*\|YNO  A r#(]ߕO0H3dҤBt0Q'*(uQg5w.R A_ A+ujw!'.(&feWf_TcpZwЕ_KM8z ck].4è羟h>QAIE ;?'hOܹʙ  毾‘43B@!LxOp mßn1$[@@>|?&x"7:u]-VSnG(  sx~1wEf5N&9w^S DA`~& >H1W=̮Pj`4e{| )QaÛW{΄#ӄ7L/MqCM#͢l/EB]s|-;3hMw2= FI!bՕ{w"PfM_=g?ڠWfvfФB֨fPq40EY_9?S(CM#C sMp@p%|R<jWl(Q'[G}K`n7M•y`)XkRڕGl(߁hmκ5yd/uFCꅧIEyb'h#is֭=^)O; +o֜@PP'u.T5sM.ֈt*кY$objJbv>o @ϑRhvN;`,vGCN6HRe킺󵊡 y8p٨mS/~1t)\58[_2k% =4֗%Pq[C#H6Dfp;R3M+c3{y|U4ohm X+ \1jzcqPld08>q(ʫ/5o|m M06&N$Ѡ (5&M/$A'l$\?2W[!MRLjK@VlyKc*dJzcY ǷFڷVI[e-9)U:[IdФQal@}jo-HjiY3\:ϘT4LtJ3T9OI54(A)MV#[HjnifUF56ԺciTdg-X?mդyhNUDg%K5 Hi׵kfE6sVd &]4x**sY,kQ}UY73nYXl77fE>@w8'mE60::e$-\5936bfZX27eE>Iyg5@q F&I UwfB" Q?x.cmA{G^|.de+97D A5^U5k{m;'nJdSOEq3>dd5e}YαQ_Ĕblǵ.6@a9 \ѥ,4O DЯ ~pY"Y֨Pʱ@Ty_kuAQ#}nsA[XeN}WF UqeKZ mLkF|6HiJ k.Y\o /54)9dr.rIn 7WjP2ЉL,ӹAC?5(EYW2h F?*U_6~_)@uBͥ,tNÅ,uN YˤIEybOi4VxŖgd|~*T!\@{4񪒗sx^|C~3:vTrcHqd$0K%N&{7Ҭ +]B p#͚,=8iqad?ۑ&Cln!ubH,GCMGsAJ0Y&<ףx|'l  _p9͈W *Ynd+~$/Ƭӕ ~Z\4n2~,F|l'o4}^ 6ltO(f%w}_-4>,5_M70fZ [2ni{\LbsPl0$/)4O-ҴnჹLۙLIfny-MȲN=nnf 5fMvv͕3m3w͍fęX--'XdEw_~ ^YIY՞(mUfq>v}N]oAS?zBVicMK$XEڬdYC4"Aq)yI_q;[pyF鉱bi[DDeQl?CowY;-_勈I4ĤcLuRR蒼$W8ND$Ѳ xd #ūnid)VW&L ՗_JDUn dȟh1IԀ<`PH++"֔sbɘ`m bKFqAHXim ,2؊oHmVwpEɻ$1:{Z#zp^~1AR6EXh4>@#Z<ŊoJnp+#qץ,4vJSTmt=`쿉<`Mw$D*zݠj29@#mv෽<㣖q̀&pqGѵTxߐ('~@yZ2i\[pK%he_pw/s.4kZ4}یKtΕǘb}&Be~[>,޵(&6jB㐉ߛm~5)Y_7{ǿm#|Ƿ5_m AA IvR+ԟ.(mZ=L+5Ws=_{֎?sYvHk~W>=NׯmA 6nZ=][l_d6M5j<nfV+Mf l,S˱9v7l<Ȭ Y@m04ֲ$S0$%;|naك\ 6hm {n x4hn-5RԹחUyO#%RNa8 K_5DM8ekd4pXM4Ⲅr !ƾϲֺk[_tRZ(ǒu(@ ^_M7jX-}Wjm- y HI1+V7|fΑ%}掩k?mEe禹=ݬl5JU e3r[Rk&MdXv~ڏحY@JQQ: VZ{ Orn P t @mg.ҐP;q:Ӏ@3Da-u kBVP̓ݟҠe/2'.=f Pz=AP kC&WISx;"I>)ɱ6ƹZ4K˻uOvCӘT]4mZ#TuH-_-S<ðRKcIP0>Zmqv5kZ:PS-qy$ݓa"!tQAUmK:ɊXa3b!@#"6" MVR{^5ʼK#’GY!<)A@NPWmAFI;P~Kc^H̴QR:Vy',hE&J(LoBqݲT^Ա)!xLEj4& OT KMC!X$fψQV"y[=6!~+1)!€vZлSGtMHDUwсWw7 ݗwtfɩ={bGK,ÊW wV´K L*?3k󶚩yrAmPHG=v k~I>1GYK^bU!Q'kފOxԜ*ZxGS*rjʚ̬Q?Sh]O:nъ[NqO֞@8j%[PؓF- (Z`(,3{1bZ5iŰP,{7 )M*Qk9G>Uz}X2951L79jYKۑ'Rb`s"Eݚs  G兑Lu#Wxr`@k8d#׳mNimQM*Ti!0' &O3ذ1{sN "+djL$ڄl~CihK,WRG 'KVpzUXKRR4>kZ _TEHhq{*iZz;C0n~Ko[l-k`X=j!c0Jw]V>G:~Rnug]sA^e2$&l$Tjf!XjL`H޼Zy*-.\Yc0~&V^gRժV'<)Dpjk#&AÖ0ILL`S@:+yQ+ {5oț NȤ/o'}e2?`>EޮB̓vQTE;$ʛI?ݓ>r\ Oj*#I#&aE3˗=YR v*#hBU 7lLL`XTd]`9W ;"cldC ͒C" N!(Rғ.2Cf;fMr A}"Mʺ,Ӈ D7a#CܢDm,i3\Ձ H 4V6!WH_R8#92F'[博ddrD}BgË, n=3ʼne]mzdx$E8bכjι6Fqpj'g֜՘<7v-j5[]!Ad٦;9\X8dalqXU˲yѰD$GE PTcD7a1ODzc`a@*;SXc=Tx 5PR1e%g8ǯ~iw%_z-d~ACb-3 oۇĒ^7 vmxxz/P٨RY^zzʥu cVZbN٭b'/qdkTԧ^h]-Qz؄I̓RlsfZ QtT$Np]6i6kj|q<^( % OגºRIݼ!6kR8jX/r &OXf>& J6Pt s>qt&ў6}!E z=Χ%JVkV2ǻǧ_!fcn6*Y>PYkwjVq@UF Y=u5;A,0Xyn5Vmn\W0t;2S/o8X,[ \#Q3#i [1"U;1ጧ!d7%fNrzseŠ FkxQ0fu&YWgh\`м|.:6r73Kz;O1@5fxViT9LՏvS) .La3]ۨ) !(v܏<]AJ~b,$w4rWJlsdfT T&{ 6< !ZdMRQcP/=5o+JO`ڄ+j>d)AzvTkky[%M2"Ŷ6A1y" o,7yؚd>]D[@d&b& jޔdaN +EZ Q7 D!Svl_TgJ}dA B`5&FujX ^kM)-VfF[TܛK f3_3kI̚ UY{/S9tiF6&@qU6εn+ ϛh?}6=1M-%rЦԮ5 xN5󶎕?%r=a_&ƶqGfMz`F)ۤ2T0ɳυhgEHV0wGpE_mv7|SDN=Yn*R .zmB\dBqiDB2!M#5,4m8X`C'Jc cJalRgBc&ִQ*gܰ Ct}`^RM'U>psft/)+U цc2ژЊ^,6!Sk1¹c0*_ooq%!1@DA84|Dr+'>>[7rPR@t XRY(2ZG;W889˙ʊRBaCh1ƁM e^/lkG @i'&m(-# F$߭Quk=UPveF Q3:0X9+ˉ-]֐\oj *%jdH,42 C3o2/+\o(eФbTZ>GL*' +7di.=~Zם|$IұpR~DO.Ѻݨ4aKGd#dIck(RjoIK<nX5\zQJU(]4&Vqg|ػ_ꘘgd2664%A^;ϙf!o`6b|u1V&&_ΐ1F bn V0 ŃļUj+$[%s$ Ve܎BЅA(ЅF R8uQ/-NQA0Yt"Բ덶zi`#r9)Yc;Hޝ9ܠ89D͂[$4j]_isRcqUMi$7o-Pw8:jǂ[84;JV潁. ٰYB%r9T㎮0m3kԢ2 e^i$_g654i-FT =r VQ}/[G#6*YiW) 7li?BB]"ڮ1J ccv JơTIY̓)0lWT6j]Tns C`ypRHRy-<Ө83v` k=zNfF)Mu2T&`Tڻ`Qu._m b᜞]#X^^y(ݵ`N&߫yԩzŖG6ڢ^Ӑ{%]gdb4(~&Ճ!m×Y= ;!Q#(0',7qC[sK؀K )eV?>*n>a P :{ȤZHkR <+ kqqRVE'R;3fvoYD0)'+=RvMj{|ʷ:P57E!ezE3 ։(ʔ()j@y; S5ՖjN4t YkhEpʤŐ[-PTġ_2hco}7:9@E]=IPW 14lz@Iʛy'5ҡ3!%nZqeERȊ55ۨu2t7ͬO HU[&!M(; &ZK֫Ŏ߹F'aSe(Mc 5:p`'o*{zV=k.lhkJ&P4rF09 J= ;S1iBV!tPkgx%* -\?o؃4$8x1noFMh_m;Jf==սJX޷iJ*+ +'&3dB}#6jBĘ9A!0bưYR '{hJ4IrĤ*0:qURF{L(>ڨoPId J7[0DaHi0kuB@z.nѤkBCho|O@#ZIh%-ѰWSsi?Ϸ+;C0aU§1Cݪ BLZ~틼k?qj?I4`^)pHn"ԼLSo*JˆzaPC v5z[ƄPL5ט= ȊQM#YlWD^4bFD-+&4M$skNF5."=Zdp0i~М8jn$Qq(VpD+qhHV!8!\!,j|QԲ(1~C& Dh3m%'=:iO uqgtKrpPp31[+xff5"Cע3QȩIB3ό喈8ȒDEcPBqBTZ_W,4mHIH&@)X6 qCpNP;mUB8G4ki'gNF,r R8BYPL BtP&`;ޗ)>у$\*4ېK>VUd oF{>qcE/[!)IPVdvy"ÁHխ2H9+%/ZEtZN%br^Θx킛t<^DDkP&JF<%7Xh.O!b@iB@IaVXĒ0aAP3za$_TF)~?)sL ܹfE4ܞ*Y)dӳiKWdž'*sRkw)tHT(+$'Q$sYzkChp K|dڮS°57z;r4 BQCy.)10B@ XoF|_zZUO0qoO, j4-"8`߉9з@ݓ&H};A⑨biIړMjY;W8"i햚HfɩT(AXQu5b $D<$TdU#@N5ߥAm9 a<4:ɚ~2`?ʬY.-afQIhw*Ct@d u, s6ф 9<…p5S[6$5V4 [QfZJkI5um"PzQEsl$H}.Nj4",iBN+4Qp;wqic6D,^bJRJ!Aj;  i$錚n7JI sy(4k.vUmL%-5gv=XlqV*(Ћ dkBG@L]$FR&BNs% rՊNJпVISe댛/ļD~9&TlĆaZ JX-AD܉`Kqb<ﺿtFݠ+yUUsDp y]QqF'Y?L0" ˫Fn3WT*sqUcjh[U? Ru$RRcd ̑`䫌1,YB~zIjGc'^S&wp,Ӑ)N{&I5\F$Y+9Hso=[ޙrٯ |$grn1v<^jٵ~& Q5$ VQxUUR~ڎB %w(kkY&D\YD,\c{Jc$KvWjʤxC 8!R7T)Ϻ3-0xն V^P LR}F 9%18 C4]) prL45˶ĵ|No/N]ơ~򮑵/ڊUMPM)Dԯ OgpB3k{ƻ\Rzű,ገ11Z8-Y5BrNo/VsޞP"0\q _cH{]PUo$ƨ JuG4}5# "N3D|I2MYLzQ}4'q}|AO66DT7_C=pv]:X`c1꜓E0+: ;9pV-5z9fX;c/tTUsdRS4MXjv Ny&F WsRYrbeW2Ӂཉ{wJtd>ì)IJɲ Vya{AN8%{9+lಘ']ʛך4 R{sݪS.MU&A#`z9C*ݏju|Ԙ!/5 RgUoMިk \acvkykrb|4Aq=wuGdKk+u z6rI֚4lVy)hdIjעu^2o^XS"{w^OVjb)/5V(W-5V C23)pm?~:f>|KߕySD2a6r;Huo)ycIG_Fڭ'gf5˞XEu-fLNDd2˙v~yNlX6Č)| IA}U%bTV4׍Yk|BFuI)]PƔK ĦD%~[z8_Zk[ys7vMGOaH=h oX˂%֩0krXj5Vzvkzۿm=8Uhl_v ~4tէ#Xh$c7svgP@ AC،3᫖]i2kЊ| "b4k Pv[+WD D{oZjldp;᪕Re-s[(૖GM=D,U #NAUZN v ?H*u>EM&9etkFb8N,sUkt<@Q#tՉ;_sa5FxչɫTݪ%:fbUK:ɱb=u qp~Q*[e&b`ah񞣾DX"6S/Htmqn5uHm_$f:H1D)lPP< {)DgOw:Ƽjϱ}Ơ97>)f fOrMPV^a1+^1xэrV WO~fk:guh7 ͎ykk(ˍV-r6 ,5.܍9ɠ)*YRԐdT*|fԋ=XiRz@J -`$*l-B8W_2r9a=tƺRd+?~|\5YĞ5Ęxԁ5-b%i䗀NiS5Zx3u -BGƌ)Z!55T I)r*;\c._R5BfUbA2afh 59_K+CA LG](s~惹zQ J\hdK3!kq4c?{\Hcⷷ+ 4G|-GAki-yeba/6:ÏN@@>XTEByn{H(_Z3O=O4%ʫsUf(_(x6?,%r# *;|ixiEh Ɲ?LY ;QTMg;hP&UfN}Vue/Eֿn&Xjh(ܓf^DH9m{\>Ã,cظmdBY!'2>lNUy%%*F@2{ 7jm'݋9 >6k.Mm_V@ $pѦOJ3@~mg3}j~6Bw ]ޜy4s-;=n[.{2ٖ ȐXM?S5βN{mQZLysLz,_d*EiE.>&c |H;Z,%H$V{Ȼ3mWc$[T%`4$m㩇{R@v (0F$4Jv)`AΠ`{r(Rhx,G\s8uwy{2MaZafMe?@D 4Ӽx `2iM.1i&g%)1v[gRxE*kAlOER[h*k|I@Сa/&iMx&B,_V.r  7 tfY1Mu`?pk Y tuQx㒨TR~3O=3nSՋ^_OBy\jzKh+KxtBi kٛbzPԫF4GHß~HbbLݶH鶞.:ę^m|0I AAMFr@PT\a^C:/n|5OR$d4l$HAP%4q/Kmڼ "DAMQ?A%%5:.k>!k;ҚtJ` %Gkˏ.F<-Hksq/K6mdBQ@$0|[HhIEYN-R<`Ûol4ijzIMk! `dކ:PѦt/=aV-75 1'b zY'4Q{ځ66snB Dpp˃餧{XҚ;STᠳ3bk C^y>9X{]s2M0~} G/4a+փH։xVJ -4R㞿Lzsob̚N8]^)3<)SIX/de+sFL珬c9e< 0 8e!*ޜ3Pr-K'am\LOʠGfGԱ~/dml $U\ݪC 7 ;B.O)T2D0j#v[aG A,xGEP!6B(#:$;y& )͋%"Q2j9MTQ-4o)$y.*Y\صP\jz:R 4Krͣ=Ҙ ¢7͗HyͮE3)^\zF)ҌeCfeU$ӷ׾+weEp~H։N[yt 2 "%}iXUt#5dzZ֓1y XiPi%E0^Ok'y4}HV( "&^C']"J>m%[&Rxz#}a׼u-(ɭo>pR4ҕT/캤E]4ʠyo>p#>Khs@[fR|&G牾` SR4}3ө&Oͦxt4P{Lf}JEyAf dwWIT? x B_T91rQ{vL?ۭ5~ы3 k<,4)WnpL<:PMTWJcҩR5kfP?0h>e#ȟz̛ *-ktKUPt*;v֖$TD} 궪?Z1d/C T>}tڹD;74d3ֿHPq;Z7&v2TOD~a/CԱ?:%~cYi_I/$VPU@Fc}MdX_?Lq;Z7hl,Zx ZB0|o`hD~,xR:{Ha] Xi2DikRU^ڹ/58}J &CnPw@BhBPLp!sI=dR! qQN@v, Mm4Rb} DP$A5͹IMGAcw!乨`!|t$B'4)cSVI~MϚ*]I H3gPk%EJ*\+L`)s _L\_~E/J,rB'5X?tzťBJ6"8;`cd!igt)+($)q 46ʹN_h~!Jab/Inޝh`f*̤!VNOQ<D]TsWH@W8h\fqœ}0ɳ7=ӴԠT&bYip7A6T&rdi@Ž҅^|rqŪ:Qg(=쫆٢d 7+~nX_=ϯpJ3юˋ04ip+5* a.RMpb@䱧^6&C АQw*5H.!Uȹ):'?X .j,q0܋<`!Y<nu hLiHi 箆dHkn irǹ鄼hiߐ!|*zW5Uw-+.->t Hr/>7^/\aI:sb!kv//<7miM/UZVlg'[=26R5Uw->׷Hi&eͦxX_}&eb=j:gP^~wf)Z~u@k,+BWxZF1UwoM?W5 /"sCpfZ PmhYTFMhw{oFj` J $ԅ0h)`F\a~\xkip(o'kler+1H{tx\NCaPHduX%];"BWųE\$ YO @0jt4)7@&HeɸNƌOy܅}cs\դu}f[pO'5c=ƗtIG:DQkpew:GTHhh_ط>|%=c'Sr,2}ᮜeƾ{/$%fwFSӈ4/ ;;* Cˠ97h^- Da55#&2&(B|8u/*yo9C1d$D=yL3@ⴧĐɹK:-+r턥f5#L=eeAuV/+r~R3{ ?I=md~P^bohį'̚25xGLi=mD35e2Jӽ^rA0k@CHSF=}T25YY[VN2NX;ע [6y~.R3,'*Vvl+x3߸8mǷ{?OvF4;t{hlߝ5;?sO.cFD=0:&D 3:adueNOMO``z A5"a1k7r5m<@^aFthutҙYO9֚>EM)0@LAQD8VVɬ 3S I# :"RbZӆ c^M Xt4dZF \0Y>pIY3d_?E7i Y>(ǧ`6p_lĜE5 VN A˔ Uw6?pvxg{̚~`0ʬlOzuάe`k^[hWLTvD=;~8,7]9~ƶhM붴|ܾ!)lL9VNiM%K֝Ԍ4MT:傱p؎m3ES^{][]9 kQxvN9[nbOgR5ӹ~2.-[7?=o||⠕_PǁiBjMɢȦ 76g!+Gۜ7c.ʸ[)a]m:r+m3CV SuԷ#dֲ M6”n "lҌ)8RE71S|Uo_A,c2H0PA)'nɡ `'|?J2RkHpyZ૑kgY8A̳l1y+8m ىR|lp/r5J4CvK|qyZY)g,h lOǔ_FgBj%XcĬpTחgl6jBIB3`!=UݯAɚqڨ>z>g5B>%l?ccw:Li@K65Ĝsw8@@%ZGZ:Y-ƾ})L<ԢŇ.!`9e^?o w5 LfSF`D ybf6{|!ak$Dz:c)92Ĭ vJrct&\kXo=jl⹇X){ensa; ~o]JqsM7r-WFrbWGbw[hz gi^ؔisq; OSuJNBS#mCcoT|gܬg;N&1 UWt8):N a85XBBPBbMLӑ??7k61!a#Li ($ h rX)i M>]p.D ;ӄ%B+F\u- T4ohm>rm-xd$ iJcX9\mGh P2i_y\Bf8ZI=JR:a_U+)NiRƄ_SQrF"* `YM R"SA]LI}ꔓ>22'ÍG43P՚bH"R[O&yV#n2oe4 ^7iJ\2r_^nd̠ ӎv {6"_q냿t2'_ {R/x{widQů){<ù}ĝ޻^sޝ¡Cvo]T2ymG>{#,'~KAs̮w$W͌ëbA3a}%S?lw~3hPt?ymu{oSvr?{Of4gmTp|QgoO0uG" ^_zz7{΃1MSz}4fDa߹=ݞZ4]g_zҁ{i߬`>ejק{&jxnnG!!lT *[&oΞJU*¡mC@K'qU:Pkefe\cX]1,yFĕa\ef2., Z ]:L>Q[frj)EA4)󵽛â]fhLcxAZBG^VTS2?5c?o 's2nD;Ƥ yŤ/jkQL7}m)㼧Ֆ18EIVr­9hUcRFՖ/G@ZJ b5gRh1鋮 FDS{gīI H&VpVoS=.x]`0z~Q7R&96MjlIkNPnޞj[kߠGU]]/>^kYi**cCE; Vǡf <>(lVD; VTZ*6v!ZZ:u l5Q}I0$|Nq&`٘^1^|k1wi=siLExeBČÚT}Gw" Po|?_cUDK{ެ7{M,=>[~' ]]3!K;FWJ<[d]ڇ{o5jukn k*4W?~(8lmPvQP,~v zaاCs8ӅiG%&/! T)*lˏ<]̨iq쨭S%p :kn|ߪ.wqo7YK*A'I fjoj?{tn\×wHӺ=tcsTXSumP9I hs; 5:3.Eyv Zkܭ]w԰<|Jj )*/z`:kI}IZè鸾얽٤b]7EBTJd͚4ym=73C]5lslf!3HH(||͙]#z\NC3~1% J|bPc$ 71JH\e[ P\!d8;f{q\{FKOWsBcX] z_g(D͠i1ۯIA2֒;Z9bQȦ2Foʱwߊ?3e5Faq0>”s;ʇL1jAGWqntalB X+%pƤqm_]f415TÛ3&ߏڶI;#?DMI&%ToID#:#?ƛȫYC@ o/"Θ)3#/Ʃx5$-A5Bnq3mc!0|n4T(Zb!]Ct"$#ש ) L):鶌 kHk!]+ś)BVk%fae}>(6~jfV?:OSF}Js'kvb`RU{cȩ#JQ^?_є:GlhA֣w*y}ՓS7rdd?c0Y\JÁM+2h(fioTؚUpc 5w7_x`G4j{ 22d3;7:4(NJm{i Wض}/._ǿc3O_"WOMɗ&ԅK6l>W(뢌*1{H=znrчF͞ߚ~юםfdEoc*)[;d#Vl;+n,.`\{?E إlz_u=UD ̞c҄ $-U[>QsyȁNxt|Q[/mEiGnٓ" hRϲFο?壎,ox)gw/gl+o>Q{PaI5;=>qp]N|Em6kܛC~[ N(W=3<#Չ+mm@pP=pl+IW@bIsDJU@{v!̠QJg~g҉ʥ5Qok+DDCR"=3jd~OFeq%l\H{,/Zk!팀Q#5z_CJxU!w9@ &tJ'aR? >SY 񽏴mO=}ū-[^yp:񚝊F(/k9/D %e-4;[ S~}wLmBU;Uy=7vz un:~{hw_7l:_tL.Y+0L7_10f7=w9'_c nϳcu/I.Η}X *y7Yhwdnj{:'W]?յa{v9Dx~|t67\uʎK6[$Jk^a˖'1פ#??tkiϼn>rz,ԙMm,#8k&pW`BW]/sZ~jyS\fTM W҆9E'Fyw5pӘ5rx̕ S]ڷ+vc6*_&%KҀ#˾2@?Mbµ#*^b*Ir#FEw#7z6w6P$+/n}Zx3g C= swPE|F& [G֖|W"8\u tIa0U(dJ3ѱ[}jk'x!xBIip pe.ӔwdO=+QW5_Ӳ*hby[r As0^7G45TA$3k5 UhC36&5+MlmvnZs E|; ,PTf: 5-P<vηv][nԐLiop v4M<$\5Z$so@'o4NN]mٽXY>ұ4_-.=`c݄iQ &\6ka= U?( ap )0qH ,e@wrv*Ne2K$hK*fc1 1 "̩#D`-yXJQ0aАU_G^N2 G°o4?3hav# m_ 4hoo1Zc2j[~ǃ0iY}ۅo4?(45v6KEM_|pA^odr<I]qJ7).eJ;7 `f1<-.-}VPwֶUC%y֣tHߴx'Non]#jTcxLɳ#M˱ޅerpVIA(ĬQ]jIsrDS7t@7;[Л-ܶG{8)LJF3MGMcML(gu#;!\F6nTxGNhI0iNl" ^`p\L6mV{8+HD)`*&͑5M[t_VH`G'Ǎd[;Yj3fiOS2>P '.R!etkn>~z~[۩%F1D)f/[f&e pi6{t&ɺH%ǽ V;vx7 EM)Y-5܄%c/>f1Ade^sUM5F# o`(O)5pfQ1!Hb& Blw0aA{ӭn>]yp>r:e?s VB QڎB4KQd (Z2;.Tce+TaBӳH1VwH\uhBӃ̚jYvīC阕 *86 =eK뙐@D]'uf o\/.,ՍτLfs!4gׇg)&CƯ3X.z&㙵!“5v"iMN%mu.g%4HkS @ 5'ϙ6i>[zKa!%ѥ$$PRP"*>z3a72hN  [9<F JC>FYyazz%LuRexmü8fi fe*]>J2(./A_U#UW%jlJGN V+r+V !x־4BOUiqсzE ֹ†` gO! L ߍ( ؋2u$h,hUI}2qU-J)qd_F ::2gâJgA!@À+\e+T-\vi*A'P}wīa-Vh@[(z>U+pSw5 7>ohԲX)e/r5vS^T{’`D6uu_MEB7>a"}z78'*[WF?4o='5/R4sdņO|ǭ[Y_>֯]h{8_e _(1U,xKO*fdߙ_L>'Shk5m+ZDp\R B ,1 LQ+I9 Қȼ+Œ :UMqi4mVRn^Y*s]AM!Hi`8VIvQP1tB4#Q7UTyD@:4 VLLNB.DXQQ@{!ff.$fG=$借8on%Ӟm:bxjǁc?&^+Q%:k5TZq4'Ծ3$ztBi@) W `g^YD!S~RUYD4$[WѰXo4b~3R'njxH"(I0liԬ!^V{kp̶&|gh9N<'&{,$|YIڵ.da#Lpp$|b'7?oЕ/rGߛ\BX5a{=Le¥[#ABim>¸ -yXHi&EfOsLHA$D{pB뭛Qxuy83iN_N ¸?!@m(5EJUL.RQ{(X_b|sTk0#RBWbmp>fsDbSUG - 1儃1<r #9h*B\PRѭCj,yå2v6$D4 &aYaS-$Of&wzy- Z&=ъ%3fLfj}id=?;cƔagzHW΅YU fGlp CmvW<^L lvU0L$5ܮqA GHKhYlGvQL()ڎBJ4-Ql|:GLQ(iM37d'm8z 'D꧍8<3!Ԙ5w93 "ARz7Mqx[>Gڬ3{;P&溾ȈYKG QFheY3H̘-!!dWa\ؗqRAa2f͒ƙm RiLͰNThs% ꓒ63RnV5"`ݠf6E 1]+@ hђK?R PI6aa%\փc2{t3pq1XOhnJkE4# &3zo_jS \KVa솬h"V}{Қa cH M074qrDO%C6 sywW6:!0jH!w?$M8GX@u,]krhmջJMʪ5t _S[zn=ҚtbY `͖ 0ȧ)f. (~$ {)M:VJ(PH hIU0" +mz*^lu %.Ah[bÀ*AVr%$G{4^#Ϡ %lPJ$*%u0.=J` `[qn6/"}iJ:b4 5䗾(5tFf744g`P B3M95ѳes Enфc =JXU#nw˫ 8n%Fxr\1i փ~_/ 26w56Zdg". 4 u 04 |Хf u4 D"4LȱѹI&LP=F5L\ w]").B9rRxp F& ڲEOEGb8!;>|=Me wt뢵UN,vB@#hia[4epǷGdYFJJvcu٤in=IGrHB3\ Kz=tlJiu;mҬX91Tx;iu2B5'm7=&Mq+_9 (NXL $= b贏?ˏ)$]B,# n<٤{šӟt_Ưv$4tfi*P@1@!"gm^__`hee;'cp|4ek+b46׎ TV$>v\'diW*%hF R&Om]me{;I @LF$]Z=Iow/'+.6!^2i¡fR,:Jtr\P!UVmd1(I2(di3>x6>m`nѰT8,kMJ(륆jx$|\4vt'?(Fk% aRh@,<Rsp>]-)3n҉Ѥ̚'V*˺U+/ȥ56W"uܦ ޳Snݍ|UJ͟(Fbp9[eYuw-4Huph$*9Rz`41b#qwKhR1=iOJ(!0I89&}%.k )J ][Ԡi-geS-UfӄH0VT ":Қm'XX85oVjE hb E5^&=Ft;<,EN9˟X@jfMTKd'7~N@I#)* 5Vkxݒc9YSY8KKǎ\%Q`PuQ?D׃5NR10X#(ԧ0{T{@H5:R,UQ=0`XݸP=嚤6ibzmj1R,T"Yl^xIi^wZhXnX[V,,{K,*:(At Cdyly\d~@{!͠a"͒f0|F#3*<[O1THrՑf0HzWV#AIh5֘>?q|[!&o8-4 LgՏ_]`>O|=ߙk#Kl˄fV쯍;S{;Wϳ_UĔ(>+,4sVӝ7=A))Xq:Jk}n5aʭѝɵK .ȀYO8ܪnUpr{OD%\VG/f8tqDP?8|#'-YxG ƅ5Q32!A۞+T@AJ9rzZyiIt }Y. ́Z/B6ceD6Vci@#"|LckiTcY:裖m=Flp,MhRdhkA96/d#/6#" c=N't5h [TQ+ugmJ5L QP$UX , HDc̭xFL !'s5T&:)Y1@ I姠 4]6Mѹ2h o5 r2}W&]3 /`hl|"FTt2Sa$r+nK.Ӆ}w%.0jX+^!+.SEe(HDk'lcZL9j,v G?Lg }ʌʜnEL۴u-UI :RMR;e:!jJs,2T@ѣL<l^*E'PLjiD%~5vG08|MzW_D.%Da!x sM]o7lwLc9Geo3`/Wu5_&x]Fl[k  lhzy*kehF3R< (Jvf7 yОBx@Qu7kQhAd$+ = n-BEս jG S%I@UzC_nk0cwզΆ/,H0D9C@gJa.uڦp"DʢRKrT5 y+OKXH(X: 'le(5?J`nT h.:G ;5|FDwZy]n$5Nz%>k01)YAq\ C#B.!ey V|XYs֙CM 9Ci6,Wym@IwԐ {@9V󿚿TP<ϯzvv԰fkąHPf1[5/qNazԋׅ歧!jW9:3*s~R!fЉ_"L!cۻ}5*ful ^ SSA( "o ۻ}5h|s/Levrzii* ۻ}51`|SNds;(Y/7N=R8y&CAtSJ(VEW}c&7~{jB$yR;kMvBSù]ŅDf?{hIε0:f)S s`lsq ڬO6 W7Z֢+5a 8pLp ?bD sfH0AYS}1l†wccΏsf Eɛ74ׂH`SRAc1&΍pnH䤑qgS\;xkߊ.܌0 9 0̑QAr줊#UuEi" (Fi hҪ]spfﱚjl0h!ro{u^M5rS3[f4hMRIm`Dx]Bqv Xk\xtou'=eW޵ _ml{9 j}\c8F>`pxŕn?\Q)qcPN{sp4ORzkڍoU8[> E{yzԗܿu*fl\dLsu-ȐYKٓ^KyĮFns.>u4~mݏE=&Wj 8knIĨ˸9Җ8.8d-5Rxqڬ/ef!z:83YI!)pYU9@@ZWUwe ssnASyL7lgR9^ҠC Z2Acj,FػxǡcOkdKǺމ6F٨Izihn Xka(Zv?LJ͜$crWFt0Pu# Pq%)GxG<)e7={S܄ɤ ۼp8&uVԸ%JȠ?$C`0^[buTI+]i8B:.Y#N`#hGr{iL+)dك O$b]xȽxR;ILBZ#Zw>7rϵ rIr_A_l*@Њ+Q8P ͥ}eMQqb+=pq 7ʧR,IIJmZ+,CĔ{ 9=h[Px.~wx[wL۪խur«a!xU5AT-]w,vK˧6|ϗ?YwG  *QUQ9Kxܚ/U2NzZtMvIdl68\_8p d蠪PxE>WT][8p|ewd42v| ?9Р|,/);.Jf\pz)KV6_]Uğ/>B\ӸTՁ2PuZHK{8W/)".rBt̔g[HBTJ,Qb"I;fʻe񕗝0i]p+trRi`=ZZSBRW̚(cng*ĦϑA!{R DXJN"H-M1o4CyrFֹJ"}X@G׀'cm rVrM[m4f_ױm 2[ނB#f`O4@WK&POݔS ωhQK =j5?lKRuu9tMžD@Jkxg'϶u?TjNitkOE@%̀a$O⬂Q.+eF#FrZ5nR7uήo@ɧUv*6͔ AEbu`EsX)PTGRK/Υ5JP1rY-U)Bmf"=Ki:..c2޾q{ gl/p) Pqb]8HĪ[Bkjֈ~sM P%:dN7kք GG/hLluC%ҵ9/dWe'S {兝>[na{+THQ=]¾nֈ[dR׭zerӮн\Լ~eljGV8)B9Ns2LpHwT@K(U4 +2q0ie3aWtp<6d{0aj9ddEX33fQ=;`kv+gXYV˄RރIpg~ 4b-F Ƃ{*L b%kJV~!>j^IsHb7,)1> (y3SBn+%̚[* =+E0cyw|k 5cVF|cOEQ5lpqm%vxO"64hjpĠ`=+a][v<| 8b|J9PI%pCzK1SCy;>_{2L PsԻ,;jWћz^ף`"Bd/JXSMѨ3fKy[1NyEgt;jq=.?ObGJQg؜֔U[~К3󺣤ZmY.c>Z4W?6~iMk4m;vY2 /nzԎQW/*IeI!7 ױ@XB-ZL{[-R뤿9l| CYvxh Xl9&}թ=#+:E"!FC4-4qo:ZX}R=Yb9ܼ_?x}@ճ>@Զ <7 mH]?i|K0+TڟgfMHѹ '˘Y"D _Ĭ j:|N|2< TgYT8"G ?%XlN5Mf ??Er;e@ج4]`An2RNඐ) àJ }5Y1BԛΘix:S2Zbgn5:uᬶR>l+5tbuBi Eʅlc)wJz~KiS+ Ε ?c5M!^K0j5}\xz"AeHI4HXbuԧ= 7T#vUP$|=ypw_UR1E-JHCDi+S  0û5O꾿w|ljd:% O;xyHG̚^߲ '~vuސQVU.O9@Iw9%v=C`"pXa{4PAc!Q{*!^;&M+-Cl24O',q u5k߹~& `5or bL\8L,H ϮpH<7T޸BڼVk=e#Ώ  6 N-[7^~ۤɉGH>)B G6a%%zxdwɈQf:S]j;u pIQT߽nG8Z`-=LMVBuA=PxqtXYy#yj ")EkMdݏq^E7[}RÊD ,S1D?xژQK -G(ZdV7T"@𨥆o ZbMJ㑡\ 駏a/9w:uI]4J,m*yXe\l5!1~| XjuA!@fz 28j)hHT4urD^4S +5rzat@ *ҋ\岢i c( DTA9~t+^l)M<:J%BTa\#`HkwZŠa8ZA )Y.dJ."#TN rairJ+չ0δ+^~+yÈ;op c$x^`fiaHY$&!J[iCK' "0 QEn 4.xqtbAƖAu/%{ƁslE ,+ S(l2v%H=Uz"tu4(N7pTUHT. nPedQ'UWFV(LU-Z@TkUﳣ>юmlrpj{c BAFٺMW3꒣K,X6U+%r2nD6Vf'6 RC#mE=ZJ\6"$ WԱɀA䀛`'X5 gKDZifY㽾`4sJX˃U"'G D,()x?(" Yp3;J4 Ykt2"@Q*hʃ( ߦS)!tkl9{VZogGCݲQ =hP^@e<8蚑&# $F5AJMk&s=Q cucj qЬ nP:"X٘RC R)"@0RoNL0+~.G#ү[U\ӐOqc*O;\M_L/Ɗ.V?#8 -ɎF>#:ܟuMYQw؈.CWD-j9A:Ҵ)Dۉhv6C4lSLC&۾*YhCT3D,SI5٬xhBe~L~e0G45seNJ*'&ʡ K.A *RA{ҫ9ccM 58q|_?+2SNpx1^EhhO" vhlbX(+1@ sɀQDČw Q$GmatN\#*2c1Rv(>P _ש%lj[ 'HHkPT5Q |ra1|",Tn_&/g~7=uү~\WKX[dg͡?I?.rϱ}rUKa|+M3-=Y\)f0bSEB$;PXq蘕\jUnRv%TO~b {ZiWwjZH8Y}?DGlH;?`r$cݟt`TEKKzaj0P[vn)r%'y?VӞve @AFȀwm4S5iͨdd_;a)bA}m3<&M7Էym4@_05i"vt7pLRZ*ntr/2B?i֝IfV+ЄSF kM{UEKB!V,(LZu P7C]E} a 0 Ԍ;x g*  Vչ%6ZA6js4 j4Z.P˂( ڐs7i r4{cW#{n0i(Zߢ]|&!H\񒭧B¡8ߎ =ٮ/x薜sSc &ڑc9(&\/*ٺ $9>74%[OŅ <Zl+&톀P%HJC-q8AVKghLSe T!?lvR.1U*Դ &5tA jf,)Bi3 sYj/(D^+>khJ1n@ յLa(G~M@\I(QG9x/q ˼nĠ1!u0"2{>#$eRf!9+Rq!pq7?\oݞ.<2;Fz髇?9e}7Fk[S9%Zӂ޶L9{ϧDh߳}׉JDcKb$m?~xk^whF R;c%g77C jޡ64Q/L4g}Ac QTXMiw28N\ siyM ]R V@H4z%HPM>ܺQf w4s"4`afmQ.O+xV~᩺J]] kؿO<d-kC2ۯ| ަat-tݜAS2}n}14,j({kB&]=x9t~wv!v\ܯm`SX -kM5f|_Qʥ5Hgd[knڭr'n@mRP1Nnw'{UMG0Q3R #v17i{18 SEe ="N[-Ra)YMJ|֔'j$]SzB@s9ֽzB<">\覑i^w;[Wen:Qs M&ݪпQxkw  5:܏s6x^9/VRlG~Ĥ4/Ȗ!g5hiWfkXiθ54&J(1DvS QyW&5+ o5FAZVno鰃|9wq\kV(/֯x~ A =JG~DYX]wO+7 S';P/XG w%QQÍS̴cv4 y{GgzZX Ar&(c CЅfȑ6:}㖚UkIBу4[ojm'0^|jĹvҶ>'ì0Vl'4;}k/>nG"<%FMRǁ*xưh|긥f93%HB8HH3dAPf+Tn@#01wm`l5f˛cF>7iXAьN] u<9PcVs5Vi흈,{MvF X_NXahl봛zϾ}UG4N"x (H^?I]Vy/&W52sZ, ԿSkT4Uyi)#XO֩”"+(jҌ8Km^|6'Q2:%i'u4x4X)?OP}sCE7kizg,NՊ+&\ t:)'YY|~+M a 0 N}Ez`&BuN\.AeM3P rL]4:T9qyA| 7p4hSWf'iwvH׼k6?!8!2TF( tP8>W 0 =u=!22:Tv ͬʗL<㤬zB0|sē[^1l(:1:*,ŪMH v;J3e f/"/t WK:zU<.B5Ow';|io?7?彁=&G)o!}$yt\Q>`N+Znɺu*vv n" GXeShf =޺]opxkvWd]Jb7kpz'~{ uS9\K.Y_Ȥr#z}qӇgsnT]f0kj'TY13ΧQ!&i@BHWE{iz\͔{D>YtW`Y- xb$ cp_5"aGFM'*^Ԩ^VhǹQe.@P4gϽֶZ86e6;z{׀tg {^cr6r֏ok='͝2@5>fA<{y=* wXd$2yAI1`|S6+p-v/hl=sF.L yp'BTI-veǞ]2kF\" rQ?%&H{P]\,~p4DlB@$P9 Q\?"KG%& fL t RJUgH481= Hdh} '"BU@=:Z"e/Z!ܪװ؃n7Ha¶vIHt>{}!e`1Im[Პ aBN#eBKNU* |?=s&I`|igթ Q+V+5fWDqY9+J QfVD0tY{9ltzBDDHA%G[M:1k1O%484aI^BrD_=5k,f vJ, !a(eΨ;w|w8mv'pbvY?YܒߞV0.Ȓ`( Đ=7\CYי5~G5tB03U0yJ҄.7CO[s]N_8MOk.[Z)+%.(E~ޙsFqiULU6jG0<%v%Z yY2go<BV>9{Y 87SOL+&kԟ1؛st YayJTHniU^a=9c M0>&lHͩM܈`d`U',mI [oq!^/H4&,SL#"+nQ%{F-Ni0"mL! om\(}\%7)LkM\z \qI*0αy {&4wC@r*ݳ&"i'qn*S>l f޲]S TEsli@za!#`Kļl #۩DQ2*b$;AͰiwm'[ۖhe-qG aL&=x2"%7/ ET5}|4AzcslQ0/Wztl̕)'9#q53Ҷl"-_5ߙrk}eD6Gg={=܌lEC*?dnFMza % L|@m9Q;QOU"92 Szx0(lFȂwW[Gғ.E,q+2qE-יvFa2r* u'-d5 x+- jgf{1e7Ĩ`kpv;.3"PphH(}ERh|b{6"rr F30 8ɬ_LVV [\wR orvőPJ;8ހ}+Ӭ|6* Y(5#Q "լ&vA$wrMt wؑ8/T$Lf1m57hJ›k "GԒ4o[ot=*٪b=&޻KePCRu5~@"ҙ!H$ < :EwCsIj{5-AlZ^nD3ۿ-zv#Nw)uj eߔ1Fn=pS.<:ߊS ed GPO,#x!]kV>%!AAy%j G_I׀Oyk-4{euҊ#|JB!BUrp g k-5=܊Q)mF@{JRCE wļ_[i7l@SnW"R77BP'=4xbҴ⎂3.0 Qgb-2gojZ%^qƨhlҸ%Ľ -Ncȵ ;3 C™7.3ji$\1E(GК$SpQpF$qu'͚e9z1g,ĒF3u<xҭA!uF3Ԩhwc%y9ۧK7Q=Lb4S4")#!'ś<0i\;Se24lN](j&)\1\žp0- GaoR$LauCS ܣTAa[rj"}=!~U dqG Id>^9"s^.Myw{('~{|PwX&ql{~i <ʣɬErVfjЂ8#6uL ͓nAB&SBɤ Z„Oa] =gZhn~ /'3L^ pA'4|v[1OL z@Fa H^ p-/@NJ4c_ vuD)3"$_V4 -_@wlT3-4GojM+dSBlVs2*AJҪɼ9TjuU-ze/]zDyj2JdW 'sC[ n"H gHpcBD$R$7$C4D' C&\!IШ@431U40khrpCd:) q^-X5؅Y?ZE*.-ET ;d8r-. {JgB~\$Xĭ͘XZ@ dc Qì7\ç0sN-] qz ,LNv/)z} &CW8yjN`?+,`u$6*h,hRtLʿa=ugvأ7i2-`PYX+c!_hΒ©H*\#Xi)6$.a&7kkS1,_xFcsrw2i$E dٲe {01A<1 t _EODdT2CR^4Ise)L}?-KGd0S`!Qf FO}[kdy5 0| pL&=ͦӞ &ͨk)5EH^ O($}NzLiFEA/FD.v5$S"to5DGSq%|eN)fDduqwYVPQ"l.8X۬"w$@"[iU.bRLcj Y` bBS@$QU)i… ccRoҬ,ғSF4[iZqCvko~bFn*bD҈:[i:.n)4JHX) TE泓s^qfԓBЁ*c ز&(gIdDBG' oI3. &hVA)xH1]waؼ (AB#psޥ)Wp^1<lq=n. (ez7CB$xzo&/QAJe"ޢ씈$tm$k#иE{֮H X$"*pY[7Jڝ+-4~; bw*FR1Qq5;uݲBR?e K#L^kD2x@ z>RDdm1B]וy,$yE#"{eeиu#̯LC0(p ٧l#~Bws,lf4˴r+‹ؾ&tMx5x/h݆9+b خsmsigo-:߆}LFQjV=H0 $aŖm3Rݐc$>& ok>#N7Td`d&fYCZפ1x TlZt^?+"MV %G .—f:$Kƾ yS352!+~C翊8$#Et"IpW݄]\&3! J*yS^/ׇ!WI^IH8(N( |91QZe!+Y2b9BQŦFPWJ굻k-]M AƠe AQ0omL79±@&# )'qc'ճ!Tf02;DP%TDzujD6X/)Y3Yt{ qp$$_w25 [siinuaK՗Jl%4+&!AxtcQ̡d e%Kc7dKH9}l>Q(ʼnu.SW1݇KV@PI#v=b.5x {%K|r=1RTd2:OwH/*d{5蝂P}7,XP!zMx8UH Q$ASbF=FA,Cvu]RHxƋX+{5:ΘՀfQYQ v@D<WCGDJ=(^*r =;h,y,َ|­ BdT4E.hzEFڛL SE ^pDh"$`嫻DѨHV RcӅa"fp^PH 2Q[-4dZ0DaoEWHjǒi`' ]~9I쒫Dhzd$yG j?+S/eNh_mSM&m6~:G;HE멫L ^5Q7z3n}GuiMw CZ- 5`Z+-([З5Bh]i;C#ZEŏZFI'Zo9l~j`Lm/S"3]ϝ6Cm&jmLԌje#:ȕKn[KGVi5gnJ"\ots+yiM!zH%3%nDkuȔЩYF!@(!!R,fT{Aga 1W?uR(Aö'lDd5Yn6 NFAzޜ>qGK0ENG@)eJz %mmuP7 9MAuֶiϔzuŗMkΰ4ńi&{;vfrf[|E|ܽ-,?+1G^Q4U)Wl]F15uYGq+L3;۪WĞgPm=]FF$ty;{_暑V%ZaG8cx 0k]k֎eL>\3|0A3USUwzvbl[:(a|e'ن#$F}Y>03M5FBֈƹ9e/c$ wVcנy'*@7Zr3)( (+W=G {Ɋg_Ky de^t\foHv|*@x^IB7*<91h_V$RP_k. oA+Ly4EqHPokڋ׼w/UhZˎRPwϯ}j%=aX[*&=|`ѨGxyAJ=SOg;?l[}`lvҩ zZb>[iL^-ȥ#׀GJtO2Cuo)qu~J_ Zuj%,ʟJ7{DhBڭ9ޠNS'y+Mr<Ȓ>ώOF.e9l3+w9R` A{<y*4BF쪔-M|_8o~%9ن4Als-Y 6V 8 `4ojM ]15iI]HVK^ 肆Wm'|UwE8&6R/с+41@Z,etQA_ %,Yn(#qDZ"ME ( pl|τ"IjXhʥl>( Eq^9G hoS6'$f2s tٖZ$U嵁ͩ6JX[ 2~'a!&.EBt[ 'z*TBSt[t]B3WWA*_#[$_*S[)FMz4Q)6a;ss-4๚3 kd4X#<= Oh1r]oE4H@d#ujU pOCSYaϹ/RM&-Zz\9F B Q""°cwx%8tx`Sn~z#wFVbrfث!u*O>pQjt?x\ήk1Weqڣנ"V VJX=teyCJt%ܹ]y"%cQf1$ 6~+ r;|R+υ!'-$Dz@*}c(0tՍTJq0x+fq3`nio,{=z^|@=*L"Hbi4P`3ܲtK^w]wZW[S (L9@+9% 1aȹgnZY9{E nlGűHkq|bBKLZUGk>DaYCp1&)ҕ@j?ϏLw=/OF-J\b$'pIԿhM.s[zH2e.5''l$Zˆ"wtݚZQWMIIWU_YimMG ۤݱNjd~|f̰N5os$VQk=]F HvF ЖH$H!oIi@ &@HDJK;Rh\ee246h.Qú-%Htb@W_EB56+ .H .! Jٶqj@JJZ/!DVj;SJnԯ^=?=B+I_FUHݰc|czjS̭W& 6Ax..Zq9e間G-/6U/&<+SXr-/NؼJ"DBȥ >qlõlw}'9WL)8& 0ݴ N}м68%B1iInr3ok|Z8zB0O#jrA֕V׺r]^}[TP$aABh:⩆{|֜yL m)vɗRQA ̣@uʟI)M؅4(EџK_We{jmVpO|tMTWe$Vl?H ;4dp똤U{@`6a)*MI.K:$9Tc@e1`cXWZ~rrʔu46a@t tA]0e5.n M'vX ÍSU34!q`Jޚ3u5PR¹jvUA qKuZ~-Hۇ( EѼ&N\i/ޙraB.HQ7 R҈ؕBtlPWM@8yQH ր?*$TB\ J# 1rҼ,w`Q{d`z H@i3 >G D-swwӌ9w4O1NGvf 24@Eʒ!"b=aIh[8uOrٟNfљKOE[Q-*DD,W8_{J b}76y LS-[ X%O=ӽ'^8 DN W 2n'hC@7Otb'.kM7s_\y8?VQrhz DXةȽ<aR0ۖo]{S]2; d-B($e=hST  D@3~?³wcsk Fb_& X%":&gK:$=Њh`.{ K&0z>$;$5TXP=yEW;RZ#k.d뱟n| JQ2=7ɠ?Hvho=yy"3w=/UW e|zkӓ|Sũs^zck)Xu௸ӿ"镽kׅQJB6( Z(0VR?rY+Go wce/sN!hĜʈs]LWL_h][,z)D)bm5lZUP&UHƙ& wޟ/ bghn"SLj‰f)@aʊksvPPک @1m"[hrWs#둄$ՒM0ìAO၂Yes^yHU\K3Y(f rIb,L&*"<eJ Cz{ < ?eT%S">Ȍf)@B ^L7N/}f*/;t`ۀ4g=_Io[*:Tj۳8Ӣ랬]ٳG#\=@B;MHwud+iN[*"NHEQi[PKHAg#Me19q '_8j)&fDoٽh==s'KQ7 7V-(K=繞殱q\l%-vZ*mvLj+فR*M+ R)%ђ8JlI-Gg&9H'mG|;hiإKӊ"p& ؔI~4)dUs K],^E Ϥi\㌄ ɜH!?}ċiJzLA+Ug4)V<9$ROW(!!C* '蟎 'XG{(O؇5AFHfr_cGD- Ba|K>$y."AE"ȑ -8,abb*pձ@AE8q|Gl@ +UƸr1q*,-er}/Ic c~l!@ײ;m2;b>YME KA%[q<Ӏ&RdY&W yؑ5/n#L.9X"W*(Q J;!PgzǦcS[1۠ =T!&G"u[qu. }rRɆǚЈW/ R3ȑU\+w UTFVx}SDZ'M޹ސfxuhTE+XI ^b>OAIտ|1kxs橁ݚOTs'59adkPI~S]pn%EämSR5¤<3a +FrXX1 n&֒@Cyј#gCɒ5VzƗ*& ҅&pù%nU,]:(pzB312S"%+zDfҿF:r6mu$tB?GP#A^dɤrxʡve M05?\ҁ%T<,B]FziK2S'EjA96kު{Nρۯuq-oU>. ,BNk4u9;p֮4WpJOγK), d5*k) rXťkւIem1!lOVe9ϙZQd5:q.o<s eU~sU[D'Nktp%]٥͜ڵ [.ܳfbIADt@W %nx,جzp_S(sB8bA)#MRf0; F܋y2rתE)CXqRĔp#r@Kq&Ja&T5bi툯Ԉ7] )@:Io 熅98DgUٷwʋL5ې4 (M.Jdi8$zCq޽KK}ęyj5{X<4hؔ9r±1`"v]*Nrh6tNװV`İ 3}Wwx᧑\x8:)$B Ν09뀭otG?8@-=1sËm#W\#`;"%F.&pN@-Nhv[s}٩۟K}7EYʟz\z,16TN`+(-01B+(c #Y _kb%ݛwE~s_ '~XJ+B Hm6 1x|}r峐>!s^ y5@ Y7`^m{ NrVxvZ݌H^1dSAPJYbX~RlXb'8Ŭ P :ZGʪl'Ҹo6{Vk2'fS{H8mفRxtg1*gm$]&؇\gUnؐ1%LPFli 6)f@65fXA|ف+du%JIqJZ+*9]xb'{1[&Eu<+@8<5J fSA LX[%r-{qZMlZh>/>6 +jIL)1O9>~mkŕƂNKHL溈%땳*$&8UV@Vh)|R; ԕ^mP(Km,:Mu8 !(JT I}c9F$mc: j\d ӲV+ ĕF2SA)ڼ+]mr *7WHgz+2Ұ: ~=Տ_vw?'Vfes#A.t=ո= X@mkj"+3?Vv?|ksrp r5!VR#q%ޢ PTsWnZ]Bh>gp:,hJٝ(05QrĬP!vA&#Wu׼sYvCea?4hBzwE+w_๗^`aׯxϐ")I7@\F:iԚp:{ҷė?hXu5vSW&hF ;nRCnc˅/-~h{ HYEU`z5#KWi[xmp% joy]ߚ_uhR,ܤ!H}5xh; ġӛ4&f|75Vbc3n]3`}Z=Sx$lߚ,Vx\nmUF(r/<:ef9< hج<&qKPz4' _knJsaK g֮_Ud<+ E;=*2]7lIۖ89tI'_HSa.x\[7Qڬ2O(pׄJs2gǬznQj<>jB87;GMMw[N-vrR1U7/Xy\9dܠ9|lJ16Tuh5iۑ5M5l8rDi(L6ڤq5`JL:ޑ|ݐl7D#nq,OIkH'1M}U #Ʃ v+V~Ig⬯Y-P4Mxg(R<`C$*,4M-Rm٩7h|h7;Ť2Q0A]CAipQ)GdD!;J=YZc_jLԫWjjCy::@ğb5gU4"ȓ )vbiM~=55CQ]QUxВV tilìLw:;󴦬OԂ򯦀kj ́1vIW001& B尰;s <_&&)Pc.v2ND ta/Ha`gv\}tR*mj[!Q &76Pd]i{ܝvwsEK2 4Wlj3.P%$P{|}}r&0jI{4H> 8A5AfȪCnPӨ51x| nn=oYZJT \5mf~@y0s[6pZ3 ~DM@AiJ77ҠP ןcI~uv-e9 p}3'6Ш2T~i@?,OܺwyC '0.οEguIínm|D_N>Oks4]oQ}mjEZޥ"FR4煊Lm;S%'ZV\U km3.~&^X4h~Jñ2M|h6*Η$[[Eӝ{)_hA2֮ΚT0`LhM5H%"R=W2-넵>`[&57ZeN9ͼYGR_Qgfl)t#u ( 06ᕷХpCF-.I 7̚s Zs\N"DD"L Q%$3_ށEJ=uɞn:u^ h"uu^DSJ~F9=Y Idb<{rkw sɠAmպ Ylm] *}yye"x&ŐQuJQ~3ԛŇ~S )gv~ 3:e?6T_[1/ңt" )EyMT^{;rV66z6/oo F$~qmgZ)>a)g>^;k`>6Ue+: 7i?re b$?i r `"f`ʔx*G$[sr+[]\A+Qz|qjCViyat|{ Qc;VoaUOfFF,l["'Xw>ѕ+G"sG{].{>_£808̛^y%Ykc!Uuw/K @c-ۛ5S>%} 6p#;“ǾAa2Mw/ [iIJaRA!*`Id!1Kit$lzL|/T2RKa;Kbm>򽟾?[N!Û1N)Mߏi'#7o=P# }Q5 @k5j`S$PS"}ͪKvV-WLIr,8`TaSW!(IK }p_2L+.1&dS-Oe0~_ c,r$xTFFvN&>1WLCT?c$QQa$)`0xOV]˓D^JU Ate-ĕ I|jm_^Œ{Ⱥpa3 ѧ4)OĒ ve1REV u(?>1>ZK G{S8kjPz,ҭ}?׸K 4kLʂ:#O['TPx3*O$ tSzLoqӗ7yJV?QC_3Ԝ)Y٣xSZ֯rBl!R$|J# Ԕ9M7'+9Eؤ&abioq[` ݊Mk6Oʌ: 6^; /UC'$gDlgjUxu8 bv tgr'nƘ4eD872-CecY4M2f-1b SuO7>c&М>h$ʆ|ZcoWoNubr%%>""Z T|&W7kv'ϩjf&(5μaQcMxz>kŹ(f"O{ųPj]K4)zKUEI߅/ b댚-}^ۻ-n~i[]E3(E(5cϥV5UWM'sk*d/lI+mcOϾ>X'R"Һ.*Z+I6zZ݅' {veh3)EaB1a2:K s5f|?b _آvW}[!/WX(:ct&M/s/i\NeV*S=Y wx_%1:b3:>uⷛ?1'{JSs<îwwA^$C:x#yvXvY)^CӰY@,v@,y^޹ x ts&L@PP2:2|-?, ]>تs<] tacpcZ50hC wĆ$eHNMdo80zU_a A :H8fYgaÒ@)$r٫{OBP| /֠OJ!*M̎DWW+>нJin!DXfzw)/,Hͬdwߪ;46=yP|NBR8 1+L 6:gmTE4tch&gGWQIy~_Q9ՠ& GtnD7h=Wj?{ܙg<ϲSCC{4qUxR7 8 GձYuk?sÖn4niVߝlz>mxw-^]٫cVK_뵞e~6KY6cb%NXz*.3~d)ߕg7DJ~^,0(?%|uZyʬ_!kl'&92Lt-GecvcDwA빇Ötw+.p#Eb+2+O( ~qG11QWZ~6tsAAЕ1)M(iP}?"ΞX S&qI&Yq,UX72}y֩'A]N8]P}GԱbi2GIS/Z3(BJ 8Ae`ҸiM۠mGÒ dYN8Й2iQNJ2"xhobM"8PWٜKmLa7r;[cT?qiPdJyDdкѸ2-,9Eچ M`/HQT橍Wq.B.rfRS rvVdJ'Qu :0kuZQ^`'Ė05;nv ~ժ8LE 57y*Us!7TT[A% [|T,7iդY|՟*u}ޟX<潒iGС 9)͕2.Rϐ[yg'HZ(JaQ^tJgHtgVK>fucW^uK4BՈvٕĉ[e/ꞭK&G[B$fLPriCt.~aiT!>q3y pږ FPk) \ #*9ڶQPp|"/%b ̦"7S;P _#)ך:#iS^`d6Ki4 eiUp^ZMkCg4tJ]!:ȬFC&Qž1 5d܀U!u%:&i$x6^0qk Űb dЌb a'&]GIv>J uo 54MA3 @n+_f{RP 3 u^A'TFoJp{Uq2bV$}q|&ϫϪ1.X%b5Y<\!fM`I0HJqja6*w W{H|؃a 4QQe>pOYU]]ӁzIxcqWxZUɁQ2rU=+/XET)A{h:MPL9b Ͻvyalc8vr\MDl3M ֎4[L=MlH:GNZ#3A4[~[-6)'ogLorɡG0)b9{jbddA!_ނ5QKzw;sc7 A c9{^Hlj*&U ࿦o:&K@,݋$ RabAD*-:J:T*Z<3UtyZGm^]4DaGg1ZD,5+FÄҥ`a.EK > i0/n] ivR-֠0i&4Dq$b ,Dui2k&~_Ҿ13dd,!]z2`nP\LGr5нW̌p&~iD銛5b4ASTcL4)3lab#U_`D>ȤIҮblaX<GhA7[Zy`LB Lp~]&` 'L.{8DHU!kl[0?]_/)|wxGɂ=紡6XcKjג<w4-Áw wa2AT|RR;ljԦ!HvyJ] ]%VEɶicjS(8U0"dy #s&4hHm(r.]2)92 BA{z^lX{|:oDsjiuDZ!{[= }vw6w6'qTAQ}.դ6BB6r5/y-F &eb!\#`=RMX)U캒0-QݢXfizg^(ޝ yg\H(]d+l))_kPw65*3pJhaL1dZƮ%St]md5'ʌ΍"~&VW7>o0i꯴.qΫ|wm["Qr:RXW{'iSz}ݚh`dw44M)RDsi88}uLZj=;;aap" u*5Q3cW+&_\,=[Ӕ4 ZzseqOaT"k;5o9'Q* ]> Go\XV[^ =(Rڗ1h&\p0m9+Nv1-޴y-,x3( 8WL} \i:&Nq\tnv55߷nx oV2e9( xUiKӞ6 8t54\ |r[L0X.Y.KoVAУRAҧA3q|js١iY~W/SJcߨ_ ,;*G n[Eu賛4:\Iw&;`57?=p[J J\l]¼A3q?]wvj.5-=,S ⁤T8R𚁂9 C'; 0P:4[IqAJqB++J>BR# -  @() F'FD5 pTQ:4YNq&lE)G,'i$4َNE&WHbS $%(OHaw L=AG Q ewJJR0(ً?#Υ!-=&ALaf_̙ lp\%me0 lGibr xÝ)n y$dH;4}a0P#y8 0j0@[3h9]p 4o{hx lk |0'yT??F;H[[:nV_.{w5@#p&(fU9^uO P_?z?t*IbC3_tL@ J|OqO en4 g=_;nv{FʠKuH{}Om%^G? 56'KقvP*Lۂ>t_y#> x`I֗<b~#bsyej`&>Oh鵯O0]?qvEY5h~ъ%R0?l! gDoP.LYlQq? 0nJMf^4>8[U5F'!nf5-GhŗG/Bcv'(e!h`^`ъ^Hq1E,VmRuھl ܏ tvA=v0N~?﬍%dH_R'$\E"IHBяrnv;,gõH$P~馣̑R\#vG[y_mv|#pOk(L{Y>iDoűtMɛ{UToi#6kq3X\3KɅzqLq_yjk ;5-Q?$ӰIt|>UX"Y Gs?q0J1 vjZ#E?ʸik<`%0!/G nE8Ϡn:Ƞ_4݇ MރFC0ǯLYt+1r|2,'.=LMt|IH~_OUk#tF=4`+&͕Q[n6*'{/3]Xs>(΁ܹ[C*qrFM-$g;8Q?ɴ]17䨗RJN(QSk1H3$7){j[N0"466SlB@ltXj(md Al4Hhe""]MkfvXkhvU/Еr/4e0d0*Rc/<$NC`<*wueA4%Q aP(!0@wC``5,yߒ{Qb5  Mav^]Z  Y,0vq)kqL3&ژ.qy I0GQ$cVHfJ:-+YhO P I憬8uDڑk!:4,ཌ k,4n) xOx]A I# xSg]7\@| Uq@d瀑P,5wkM7-2 0vSE ko-u@ WY6x޽i |.g"wȩҚ(R Wiඏ5͟>PȱҠ??>2kzsF:ž>-Ԙb /}v\Ir#W{$Š_\o^v "bفUz=::*٢g>l(F*r$#!)@w_Q- _wzU _Ϡi:R>W4>aPOсպz=wdv8Γ/$Gj:in\?p{$hH{8{O>{X Zs;IA4tPoh|C O}!}*P' \HҜӡ),#=H^\om8\>NZѓyTHPڜ1~IzSBY(?+&MRܶ^'OfqQHmN)Az;-,0_ɖmrc`swoِU8\.'A(Z4Xw`ubGo)0|}6C<?q8x\ӓN G@o4 Jmͼ'z=w4EM׻{\oBڼ7l.dǓb`&&/lR0 6!ue@|bdFw,k4 enjMjOdyaфݬh)e\OJB$3 6kzN.»pSԟEa ڬK ':ГfѤaIIC3Xڨ]saUЄmFbO'AEA2EoQ t[ZhWtmL,T$""[AұK.AUA C Uq`oѵTh[1+ 625rWtuLAm,ky'48t'U #x6d!n()R)@xF [ Qà$Z(R%-J&ޘ D!O~$fj>;ٸ,i߿yRoq6oi>&if 8@Va)ӰM05i^ȝu4ͅӉ]{ё}("ƝӪ_ږA`_ ٴ|({N.{5 @ΜH9:.SɤWΟ#]8Z55Ϸ&@\uT.#Jf@&Ѥ־fu8@ ʣ~DzG\gn⾬u7݅4%O*@ma֘?BkͶcp0 u҉U:ѝ{R'}w>w[\[>Sb D%rn)Jx3:Y\ެ"Gsd,\|7s*c72SMgZsujK_>K4"%|rR&Mgzч#ˌ 5ܦ94U*Q6Ɨ{%Rkͅ/k>`;w]|ǖͯZڜΦRƮժձW_牖1ҮlZIzU]quXqxw6Gs(:_2$[a䂹s>Q)=.%s3vd.?Nzңfbi,.KWՆt[n&w9i-V_-TK+#]. V DK͐Zx㧧YjWFF.ݜ.Ռڅħ -nU{;^igRe]S3 ATrJqgv6S_0-|+M]j5{>s!jSROw{t^Ҹr৒&'zg|㵖 3d/9P6<]p֍WǏ|~H+iw, Fǻa J4!ؒ%|XLlGC}V&޵49}``/GɄE5tX<ꍷxeGJS}o T"6P?L&DKW^ +Up?3JS}IU컳 Z,\ DI_ò`xN>}VvZKz!YV$ ox mKLnhcI@L ;V:1ODcYB%E/y#o8XR$PVhN{<'5oȒɉ7 M8铁,|N8}f]s -u q$@P⥬uBs]'AkО:9=GK1 2ɽ0޹Mt>{l6[6$#!=D;zߵqq6U JR[]'q^'~oۊdm:m@CeMuR`e4CKkOW!HSHHQE h;q4C[) JhRxe>Z:JGGuR nZ Fװ+S\}/X#=Nt3 H|(Piv+U:>RɢL=j_1|"s(3(P F0^ ׫)ޣiJ(g7!3箐d tUwI4GVfzUGöh!#(;Aj%waniŭVg5t[G\k<LL_n/βzFv.^ ֔>`), q_ P=`J2 I都7um?wf}M1y}z(iv8-^S*mIj&?Qޜp[a>|S_g_ '}o- ;맽F,ը=MVpzq>{ »&ͽ F1o+:j3F[FӉ'{c~ᣉ3KM{?'.[́>uc-'{0d{Z7>zj}6gYhW[ldV+rMr( ۻVm?ஸOco䏗HgN- K-4c'_;OkPUC?28?b|vzIZq8 "?ub'8%TBƙ:kWGz)N i?uB::hq8Dp?uBviqL8 OЄQ+ !)PNhB'(!uUA~&Qbgx䊧b\=رO]vJ$ԼOb)]sy(+tI Pw5&XaQ ٩zꢻP$㬥8ɆOj.U v20<(7Z)$^t:g^;ZE uEGˌly‘9!̖kʁ#y!QEț2I?0PDt*~@̚^x!hLFn0FWڱ!8| BsҸyeM'J{5 j+N" 1RWY4x!Ix['tW2@DqZMZQ3;Ye)DSZ=fv*'OFIa\)^cVy{"~S,̇m3B/T"sDzJdr@pG|Ɍ҄F$1YiO-ʇ-8lbt}{cVP1&F$u=_~ kdB(&ȈO =0it~M7qcGq[;Gyѹ7|g;4D{SoCEn_z~`}" PIm͜^נxOE=@|h0x~;_UcIkfO/@xh؁WVx;7>)XBcn?;Ģ/ j盟eo{sاv}sﵙApߝDx`o+#f3WW?{LJc2ꙿü`'os]|ScGo\ܟdB[A05HORwH4 r5c)C'o+{]jN5 h} yUg&3v{]oȳ5#s+s7<ZYDAx< 3w¢ÓG8|\q!"R} ͯB^Мct!][V a:ݦhguz[f AT]1qp+lQT4vUҴ",r&pI)zP7ɳƳꮾF@4A_xefHzcqv[F׷ QP*ErBC%U#}Z8ܞrA,ajC 8K(ok#Q74b0 м@Y+`55& jHh=i &K261`RԜhǐR(ZHL09~!5iMf)](:ᦲ#JAQP1h=wia qq|/ISVa,ր HX:'!4imɸAfE1AXng\ à9v$+EöX<, cRx3`PPC!k0МmFq $vM5IXmC{Όrs3gDGNd'hR=UO4Q-v%[n8s+jI!#z귫`G\s&5GT=&Dǐ'B JAFh w4¤8̃b\ύ;}O_ot%\N*˩EA%(pjp|]-}>oH#I$|ߓo-;s6.R,`",nko0h|(*nܜlg n\tsHV;Nir(Paͻgnh+s  ]3' ǧ$B(7ΜBdT(hSMB'i81Ay[?r);>05U 0) Ϩ}d_;bTTR}o3iEGW,RjD:&z9R+8BŰ|jI#NL<;f%D5oN(sgxNk>/}mI-S~F'CPcljbD"5:!,abV-4)4ݺxIM.ըц))ҝJ"-sPUU˺xu辴?@'^.?/K&\Tmu)*:>L{x1  D\7FSŌUs:5oN:DQɁd;phJz/;$aIsi[MQ*΀8x +W)4HpE$G۬'O#0.bE* ښ+0@i&DMy݇u/T̨y ; a9FwGsIbһ7*Z8bzwoJ01i&rGĵZ=7:P;Mfc:8 GX֬ 22S޲BI Jh&aPI3P8IpH`%@ *Di0;3,x`ý}3͗٦hĊh(/t240D' lFOlh$WqM4mPM&Y-E LƯXHbʓLJ45UgzLUKc EJ8 yca '*DSCL&Z$U(d' *'*4 =/S$k/'_b\iV6MXD׻eH"30!џ# GDOa &,ا'=pdp<ܩsjt{+nMe400X2d\Ï2@CHD@[pyWe羻1bu?ި r.;|ờk:lm7*.}`KwgCBͤ2׻a CL8(ƖLa]c5ԭ [.8[s\UyasMԒn5?U +U1*,5gFIK6huݥ,65\bSZUɧ5khq.q[0ֲ{m߼ɠ[DWc$x"&Go̼ 6lB[X_^6:hA#W㠢  rG(4MqZ6ɍgeAc?Dpťm~g,^ŀrPb͸CB1uJI^c˘Wi&EOSu #h*"w|_4Yw%*F 1#uS\-."^'-(6Qn+kc.NFu|FyN&W]q>J{.:m/&/@$5 rA#YqWF J?ʫtQR\W=}cOޕ%8R\luĶ?&B.q!+u&i{<6hj9M)mR bХ1HRurBfiɆ80hЎˈT$w9.wu950#YBr7Ӭ#bl˓hT$sETj,ghkF'q{=ƕֵrUzE7(ei0gG̫P &z +,LfW(eA @q$bX%,zE~f_S81 ݴd#޷\F YN^o`W$9uUx&6Sw>QE% iK+L$Kr'Do (m4ح !E u I0tpx~xr$ǧ.]&| A5GZIT'PuxMJ@*lyÙ2(c.MrnN{@P\`"vJ.yDiIBq4bOy[Lmȣrv>+gX ۪Y "y0j󓹪W´WDy(w$9SNyqC/o86zcg؀H$IV^dUilOeTV[ͅ o,B*ߨ Iq'q8ӶH xaq 1UsWWz*Yw k(!3V p EJ鮝 :$ځfPڮkxkO+ ]E+ys a$4 SWj'2 >--[ tJF$XT%O ќ+D5U s 9qwG"2&?23gA}z߈*2##P!AKmM(ZF > 1qKAq՝Aޠ l#51>Ÿ!Ip|UR?8W?,nMRI^<|L}K򂜛3U0d`K4|}\2$gy/GIbVr ߧ p lrBn?=s#G4/VwGRҚso)XԪ>j2gk{887@Jh@@AVۧTI`,z?"C 6B1t %x߮dM4xHOch"5Cˢe=1F~Z3 F\y-18:. 6mPD.ղWxy 6~({™%-1(KؠyӧW4kPð{Bdۊ4/HAĂ vaη'i,V{H`!3u9XXI$f-ƈG$Zr\Z*Gnb")tI4:pPChVD|1=A0#Zd96zCq_ Vgmϙil ~8ƒΧ f.id[;s;_9].WyU+O65+6ȇvig`ICZyUNn)_Q`0 =;` WB͚J*){aĠ~Wo<1=V[̲ _wwQ"Ϋ1ɳʲ>am:âHCyQJNN4ʹ㍷)<+99UUɵL-hāśY@S5n[zz,ZpXL K, `/!˫@$U,=JFzC(:{=jQ_<qS6_S~ݥϼן{Ca;s^b0Xc(RӐ9XKϓ&m3nG&3ol_g3od'͟Y?<ߞUBNYàgbUrJI۳|WG1yaZ}.8r0K 9;=`N?R spapTPƈ4.%&Izܻ>BfӚH(-_TTvDu+JkH| _iMqBg۾2hĝCS[PxYd.$tgџEv\W_*Lǒu3BvFE8qynhɞUT+3Y$ w'E):@bHR^$L\kMh^:F<"4ręN@fmt*cT?GE_Y7.j`ky/c 1 % YIA1NFSab/ނ'27Jr)Qx{PHј 0sqM͞\ Olb]>8l-/2`ԜfְEaUAYҟT ,{lPpRaz/ўز14n-# "d~h ]J7٘)^߃}sFf.r\@^ j hcŀ|Qӈ~q/^(j/~CK>RXÞ'R]bT9w!0syid:SF ybB*Z8;DRNaB,H+?BQXPqMh.Z>w.gll/ =(43ԖBtHN9ߚbA.4{_2Fr)30@a&R<'yv7"v)hށ=_L<%@#.|s<(8\L"i ى4n.1h]0Kga[ ج+-IQ5lРwM}ov:p0/ZŅdۛ"Ң w,Z[ϞYэϦQe7|kD]MԸCrm֟14dᨥȭEu%$QrMͧ>98A/tjLVG<{jfW7h:n}. ðK ͔!Ȓ!6\.PO/|Z[NӔ0 QFF_N#]bt u>\~AW LjJn;" Uޥ>kPdelwi=${Q{.޲2)˅Z7Z_96CxAalN͝4u_ YrMyw>˼@p\٫/Ȗ鳯􊃢pKvlt͛^bW-UP~,uc~uk%=6V KH"qX9V- qBHVmWYeа.Ɠ*; ?ilSOؿo 8Bbhgޗф.q`{Ae{XN}:i>#~@Gm¼ק{vv(=}Ɋas[~_U2󌸟 `yVw.[%k8xc+4Do^ٱRV7J 2}֦rؕ 4@D.+͕S q饝&/ vW|Ʋ%!I~}S kzpNbWֱ2"CErB^OL9~'=\̺yjhz {U 6\se)*v(E:T\!'oٻ ~${+znXkt?HSd\s3[9,8BIi)ZMUPX_cs' ;)Xװlyr=`cyxR/".Ӟ<7Ml*.'Вa@16F <ĸ5cBzIp N8k;0w^(MUhYݩQ)Gϫ)d״m mi8F{y _CB7tԡr4LL7(i9X Y+5nٰܱaxw%:S6sK놎7YrXWNNQퟴyq)@p}nn)~l,Cy*UceW˱a105mSZeTcZ2fO3Q<~ʣA, 5lu -`lfM ٺa--+ r3dmZxr8|v̭Ŕ0)vÙ^[Z칔vp#N]6q:0DUyvcT'G㢜zR e:\ZYgCuߋcx+ck{'X=p|lG'$dWf7gpτr/rYsɀfx60 Yˉ$Ľ53`[ ^{_=6IQ͢HG,B1۾eJ*XaE79K&υןzӳ]L~2zƥ#-j* N<\شBODn} sxOUĞR/57tJ)R5pJѰjxZQFs|_η=Ee9Is`I !b}*{Ә980^e޿IOX)C8 ó9ٌPŠ2Npd~Ͳ)_D,~l@ LYcrdwzr_e(*%xʳ=2A+aȁI**j((BIhXR`} [Ș,Ri_.@% @zزpk<L a d3'*"nLU΋!K٩YѰl!_9ϩrAy.2DAcUq̛j7\B_ m>O+QC |h3pB2s)"lH"n߸H J-LE]yk2Pg~zOp0i agIic2ݐx3$sA4]e{j6*,/G@.g:;II_-ᤸ%T?Ql2ﵢKN[w5N:s%N$ׇyYY咾$$Oo=ȮEF lYB{X"+-ub73NkK4‰%Φ~"($sE4.ejayv$=C=n=ֽ6TYaD./Z~Y#Grf֓B#H^ÁRx$\_ k+^(+yK]DQ2k_L(^%Jϧ70 \'.cqH&>ʦ5;Ud!EKg%J038,DEGUBL1DЮw 5z1Y*( K%qA[ D@t5$YU5eMU#5\E LU̩ !I%mK(&/75BQUEk) .{`D$!RrnoM{QSD =l3E D ƞ%ե=C5sQU'zyXM}чO2,\Ҏ|*knw_/nNYOUs#B Xh2YNdny֏yzlk3iK; ټ!mf ̫+g5?ߞLzCzD`gΧ:zi6;csWo!UW̮8ŷHkfYw}ǬƹƎ]]$P8`[.G,+NyYZt1WqH‰1kl6^ӫ/[03Қ~7w/ot˲B=[61BqDH 99T7z#/_[ΓOL 1*VUKd^K̰{dIԵ¤~NJ^}<4% [ƕ`HX"S6&'HN~3^@Gbf  dD8.ԇDrA;IS^vߌR{B ΪH$Y1.<`4+;E$KA4'ҚMŨ4إ(*9l$^˘QL;pRQ%K< @Hmi%f˂uz "l*lm:hRr)Az3Akuc RJC+7b+En7?-VSik}fBF62^Epf_ЭK s1VxWٰt\qՖR¸X^-ŠM]$D`"gڮe^W_F<SÅ b6UIOB/JѬ*B("x#[!R{,j|Yn|ff{bd|hl.ژWC(0c‰n[~wG-Nq:Eؤlo1/Ra Y6XC\b7jĨ%4]\vHAHUQ!#AT& D2t/ZCVp4:Wq)`4l:EE*hh{9]b7N8Ⱥ  ؠ(`Cy Ê݆q_w.;wi.@cT6 "U00L?~c{w-:J#1n_w7z[ϠgBt,R(uǪgOGn#IKm> L{:/¼W[͐:.KhEB'zPx~+t:S7)!z%|d2LPJ6dE_#`TʰxF9x(B EA:unr~8G8Ͷ?hOzyt]o}uF߬#k{ܻj'fK[/TZS)v!kF`KhN]`D#?͎a#Z㚯54SǦDrcPD4"WX^fnm qw65ZiVym&!E] [X{P [,]ٚohY]ZE+56:! ,)kJ\*504t?>=wn7~C4g)6kzMK7\һ/,]_[2ࣅw_wcS6Rm׭ w@sKͫUq55ӝP'-,`:߈?"T͟$mZoy0>.FuNk2yגxi3j";%([hp1j{  jܭJd 殤yLd3hyI-4A Yn 1ï*}]!(l&Cz鮤]ﯔJ1HR z\f^Q(F1Xd! 9(񊟢ےul(DRB E$. _;nW~ PlhmC(&vK03 I$ N1PP,Bp%Z{}ۿ^.Wf+!'p%-&WhPI3iV{Jf"|M( ՆSs`. dVsҸùR4jK^r=*ˉKg &w:ӴVqN3#:56*484% HVLY&c6FjLy1imǶF =jUscSr+(eV-6)viHsʀT6=ý.HdFV**}Zޏ.-a '͑NAbZ ᬎYTK-4q9]Y'`;. pv̚B{t-Ybp\*r`eqsB-ᇴv~̻JNv%sTO)H^bks;6^ dEuoP5\R`zh!l^%M Q|LLF-Vz-nOmN^O~=_7ߏ(ݿO ݛQ|V~A/mk>w}qFwxosA : fbܮ'!{Ȧˋxnj 3SJI@56A̜H;{&`Xj >SwsP,b԰ f/"1<2 c֖;}ةWSg{ TĨ`6oILEk{ t/Z޶+45A,Efw~eݬ~]5C?E׿ִ|.(?{3G̚0}w3_>Z?_J_q]^?Q3Ws~.SYG_(6f?^B^q]Y]yըxtʰi%ʛC;u&]yb苻eJawQ3(]*Mu9o]v-#mǾSֳSYPE_]|Z9^5j:JѴuzb77[#2?τQXeׅ>۳U\B4 ;H(L3k,u[zyۯӼ\Xo>i 2SgmL1UZ=;-.MD\ͬem!PV5" w鎞YGLfߟ"Jfi,K^`Fz0u%n#}FBc@+@hW v>&iIe,ϩby$}?4֍]md,@cD;WB &ms--\p_]ö DDB@Y )yb%6mT N/`hἽH8%y{u^d+"PS/a֋iY[xX`$W *;N.5ͬ(6ϺXx%Vy.uw!V,Uv08ǘeyCz/9JaK"kyQHiLF.6]3"YE"nl3$T84_1k0D5EȰѳL .[D2$ _3BWM]P*j>Vk 9jZC #AR4j |;+ qpD1F' u WYR{-a ~sTG^+Es5ÀKP5V¹(FTt ^P+IǠj<s÷霢S+^.(礁kx[,@S5OaNBSb86(v$ny=+kBos4ω# Mі33hn5fD3HʫH0)P(Y`Eq/Ә,hlePi+= mG9Ƥg6_Y)wt(e5I!j)ԃa) FiXC8DdS6*OZvgK*Ӳ@FUPZl5v1w +CZ Ô;hҶ7J [ne zMVQƎ*A4"&|-PD/P;%|̜JxX.JF|ƿ8;ˀ [ezysxc;ƭϝ]Xnz¶ݲ.TE(:2ԏFv?8gBB`odnl *Xʺu?g֌xס֗/y܈8f_!f"I=rY3p~H=|$e_zrD`,'-z{άY~n<=_B6!A=e__ %Q!5pbK'މ\Yt%%QOvGpwZ6^ go-;4&Fi)fB7>Q7桨-wtA)Hx6g̚X`B_τ{"W]V(DLdˍq%?5\@dQ@3|U2}!Js50zb&Ñ|-O) 5Ѽ] IXRba _,eS@ b?y)sW3: 7qZDkOY/W&_kx쮴aHd])4!"Gxg v %謷5z|) 2KNDbQ@)盦&?!x, N)`2yh,Ly¯PJ#Bϣ].!#9̫T1 Ab}ucfK5+/ƣiƣfVCg$*!J`S[F{?( j 7Zha@j&$t\h,A`ba:XԎnXx +ܗ2qQY<ʊJIHNQ[`cd5AII[or`WXmnS=KlZg3)ö\c֬"Hre%5 2-!x8`V Ab*tit>AF}ʱ9&iz1S6C7\W}Wp"҅H2EfݓvA3tX4tSr*&SzI@)of[|]ET-$0Ea_T*p?/s>@7Zlf=1i4%t8J#z9g7&eF =[_sW1Su^C7cTuKХMmϹ{eF*rVJZ7Jlݢ30\w %m6%۟nku?eF{VmքﺔUBo,eF YS<1x.3_zs[kn&o.N c&bbx='~kzfhq#|L^]^=^|͢p2;۽vޒ .h*K=D5>d02Y;tNf%9 Ҋ B H@!U P&~DM ~Yl.ȩtTpOX9d4mMhҪ+k-]=צ`ʺ)X` )TDowjGqVו=:-EHZ}{`p>U1i#Q_DQw4e#Xt*5P38y $HԱ%9!/ `H$D_37Ik2_`bhsȥhr˯tm%ŐPnP._Nu% h~W˿k٭u'gt&W,Krɂ'"B" F-ש,֜nUˁ뷨dfƧZg"LDPnii϶`he(yU>-DӘ0E[",X?V~MK>p}4&7^DB$.ULb/{4\~Gڔ̄_ YBo.LqD.緟3:}U݅Zt.pY`/+jU*uA] aX7 7lyBx,HSf|]yE2wU~6";:P,hO\CԲ: #*%A shbQ{NJE{PhPK HAN%ZG0dn)uTDT$RߔWNkiS%1j娒7zAD++`LFD$QDlAT9 Ns0%u:`Ea{yuͧ _Qck~a"yk~ϯ0j(OM*&ikؔ ]]:ͱ :~ϭ󽶔o𥦏4Ćl q6h`8싏m\,^SرE W!J##l6hT%j-gXW,WTL]( rKk˭46 @yB2an*00y3P l$ koOa4DF~E@0o6j8qk귋j8񪇪<8їœ 6ğj̒(/h@Ѵw[5[=Q;_5DK6%qA,C0;Jųo\cޢwZ˨ku7cQ'κ&wB@%!7?*,j]~oI5hpg6c&|v5m al}iMtJܙ~'!6kf_|} Zà+1g}&_PqY1說vމGl/55ivxWS_KSd~OIRC;^g>ES2a+TMZw Ս|ĹHli5h\5 g3Q3]]*ZYmP!=c9&wrHQߑn&Uj'kV)2;BEU "4覅-{55jBW]QJS)+ͷ~Qc|nrxLMXtcy@At1ms-s:mRkvd>PxOhl%ӔAbcp&ͦo;O/>hېS3 V,5u'Fxq5H`[RS\U0'9 ™co ҂`]=iͫ=x{edr$"jV6f^4XpJF|VFӚNa<!fLҐ"KїwX tb[\͠akFT2\-#g`2l `65 6J2PBHM`Rw7hS͒4>>ت\?.`H,[[Qs#3_@tPيiR 'V\j M[Fu[DOfE4($H wjBBC1 hl(VkO4띺 !|[j\/}\l-FFb·5'_Ȟ*$q $XlF8{ vriP) )(l: i3;ȕ{ͩ7XBug{lؼZQs'fC\{AGc[R yNp釬/92: r[shp @ڛ,y!TEG[-6!ѴHdO+.h(܋D-zƹ F L85Htd(A%L]^tFɘWwa6فۦXNJDJ4e/׶qdE̿LtY#Me효nPmA A޵7c?h.[/rt܈}_G 8u@A q7_5|?Fd3T^*=IWeKMk/o,r.Lh~O.IAԯy7^,}mXU^%}s|ڶ"aD s ?hP_D5b84:c)3z5ƹVwa[yel4Ą6BVGӘqܚΰkkU\ )8}#S!t"*H14J")/ qҦg4,^*iMťKHҨT~)";Iglsin,Vť reL$URKk4֊&.wБ\b!g7R50&j0%Z1bn$G~k7{[ndL֌v$ 'Q \icy¦6 %rX2 1s8imb5iT 9\P)|ky-Rd%ّ:씕!vk[3ȺAČЄqRQÊ4l+I1 :I"rH醊=نEb&XȰ"lc1k<˱ER_*Ʉ$o2Ung0'47xʻ5%gyNm 0),J3&ZH~V'.O`Φ_$6P!eDՌkߡ׸UpL7Fz-A˕;h \4kP;4?uQ _W1r&Vw]M/%a"×p% n3ω]UR eeo!$|D5:CMj߰XJAw򾔆~IjUn5)/l@h*POOT&g;g,/Dgº#{^/9;X?3iH#.4秣50 //`POcuɥ>'HiH2M>Q/.8Z.( $2R知4w3#Վ9S 91( ?˼v[֚?oٳj7\{g(H(Ǟ9vC}!3DչiMӗ{ɳD%o%4͗`+(UW![^&TTJh҂?3h6_Y4܊D,Y;SqP1/B8)c]˄ImQ{|)PK"HiO͋ 4Ui =+#$!ދ{ůUn% L)`y-'7uZzIaZVa)E1 I0idqugTMMBஐxf_TW"0@Ĥqގ72G I!!0Q"RV!5"#OTCk3Ms{S^FTeӯkeJJ.3 3bpzG,\J>5UF\8 c>֠hd㥉Of7,Ȫ0?y/(m1 Nl:[ķu4 ք:>C>rB>6jWb̀ 3db+&G.ߵF{&U7{{{Iǖw{_ ȀGoZ?RB9YbmJ \Ԉ*VAKєoC?$$E,+3R(ȃzg0q.advT+GԌ(Z `\8QuZ0r d>W?j~BT÷5||,br/I ~B. @,л'2 JSKkT-|V[IQH&ʄ%Z*V n5f,4&9|pH9Q OPEzKS6's̮$ȢV={Ok@d>d쭈#O畴V<#&+|ܳdtCAoheĄH{@YYs_ 6̾@Bhz#KC$t~2kq'zݬ3j{ `BPXu.OZџ@i$ E1'ϼƐeTM\ZĠ'XG"QyHEpkЮ^; ǓbhU"fAc{0o ݩf: y/Cz1: @+f4URo5|e["7AYi Iz@'"q C8t?GS_u_ c}$)ͨ쓺E QiM'o=;UPq!_w$ZiRrTڎgگ:M(y[,Rʇog=iKsK;;'>ЀD0|%,>9JhPf|w( "fХK¡ W6xd IF/Kvha; f/L'vjiG@?A#Wr_p0Y@2,7Nra9쓞-`؝f9UBO,H+KBxP΢K!p 8 $Qb>oBv<{.:rbv!+ bA I8`0}qKB4Lf ."ELD PB6pG7X+9Xra_Q??7xfZA-O%,5)6.&!%s`s^jyUH \S6l{^KX,5ʗ/Jn_D8$0qf8ŽCbS<Ù $,=zn%I3e1oS'&\ZÕ˷L\C4W\fY^pn;}%h@H:\ 5G'' E@ta!?(*)IU(obbD%M' %qETDui7ّHkI{& ` Kjы>b?av؍)Gt@rŕ ]0A*NkϵLg!H$UlVULXgtEM7>UIrgVJ4 @U4!{\(;$ h}f qR7`nDb|&δAԀp XiA܃l1id% <2azts$o^?u>M)!ImIK ʻ"<tŋ'VeʝνTOlEu/ `7źjU{ g;kSObsG,4zV/GygbYکyRYխ${f?1~f8l!' տ,Ag.4z3C?~N{[P8-o We`["4\›mC/%/[[i<- #wd!|ʥk?I||f@%ǿF  /iW5܋|ܚ9?O |@PN~%+=>B@-?6|p:=o v|W$$/ Rd4;l>]NY? B\]% M;Z˷vǗAk>ܠ_ @ e' *R޺~ks byfh桭VXh#z~`’E asrya<2u`G:H=R%Hd0,R @AH#L oZh~AE ) W*;$.cpJɞ4L1w Q[%k D)LF4 ra̝<ԛC|6ڜ2&wi*m)6\:pe7M6~&&ؠB2,?Szz_ ,B+\(c)V=JOhV}E2`79(}l\РQU`5PvYعxLHR]ODqG,%HrzF&mr'SDM_Q3iB5:z-*MĔ)m%jdґIR\N B "]T@;Zzݳa!WO԰Fx/:i eʏk_ExfeoI![z~V1mNK b޷Dͼ"b?s򕿌'adsG*umto߸ VYkŠYŬIO}F|˻ fP&]W]j;|$dBJ#+Wjuk€I~cz|/kbd!nVWD(oAIq/p)xc6VLU (DPq!ī!0lbj&1w PAĬMG,݄Fq*1#Lr1ui[,*rDc |10z)iO?%Z˴OF?_y4>-]+/&ĒH[ACFZzrmAX)bZY /RJVQ8VaDdTMVj+*L5h6>bISbV/wٯ۹xLScɌÚ4˙se$Q͔k2/4M4^["iYak0͓v4_ )*1gxyKI:Ks]#?5B@bP͕AK}WbRlȨyWbk\΁F~bB5WXRY&XP+~s:lJ}a͚ DduJaF|5ߒ>Y|5'XNL'   $RxrܠY، 1]LqH\|>3ˆӚwwwjv34ԙ4.>)罜Cٙi͉;^%ASb*5_L]gI08AKS:~đ[=٠qzA4}/6»?WBʜ=U(bSȋU"AQS2 w{|_ /:|@wq嫑!VOL(۔a.㖄Te2k RQ20"_.ݭٌ+R:J0:.- _?7G}hxjԆ޼\\bu5Ïg< 3F?^-_~j)4qa U2˔!FƃOjCo|2DBs襮By5e=$>{lқ/4DPSu mD1O0YhF}}$`Pp gfnjL)I(_QD|jMl~k zZMISMLaDBGTժsM;,4YISF?_ҍ&- t"` \YgYyt#m38fEcf KbgW{( !qڐD3vYVpOuB l1c r\{ _:W5ANjcT\V'3r>ݨFԜcQ'58i2O7N6I:W5qg\j&Ϝyj&<9c3U L|Rc\a\)ƫ1@ c Nor+;|CBsxR8" X 7Wozy牙fVeJ@rؗP C\`1{AeZoxZU˻Cڵgo:Jj|5$iշӽLK9ZPjM rjYe,7dV!U9\BTWm7QKSCyZwлv2̈́;~ v,\:dt>ﴫn~o;a8"} DV_f+;Xh4D~7LpժVKLG>6xzL-4\âmBP䫬9VkHOOmͶ \VrX?N4ptlB0@}9H;L+8 T,[) =>-p7nGCftvuǡQ$U=cɬ_=Qec}1iQgH=YsR/ĵLѸ¾퀷c5몳hµ,PT:E([e )zu*DtW~ ңLΤ |*hפn:jmuTCM=X'uh{o^vz3*$RzڢѢv% AY@'I"pgZ:yp8fBH{Wj)B B*QIk93h};-) pGZ&^O&J.Եk.Y:(ΊU'Dw[]H@EUa_ccqv/ oCJTP$TU<¥o`iclrլBܚS1p&uBDfJdWa{Ncg\3eD'4 SXGWp-\ؒm/m?u:>tቚBm ZMgGE,nS<M)|Up8:/Kox{=B׏Lq>0~l'_n3'uYA8+N?_M*x䵲#.׌`(k>Π5k z$!J eg4V%OMVUQy\XuqD7?!CIU #C7tP;$IOL5fxIgRv%c|5 W 6~/~w'dhŵyA *bcҿ8I3̂2$n։+.}?dm{͙LW̬RGWbQE[KXoLLw$ 7c"&:*;-ܷ-9ɤ2}A4OQM@ڈ-}rvk(cj H* S}gZluYjzudBP2 @ŀ`<`fpXIq!hh-l}#3lrݰfHfS^1AЕppʛn[jh%,I~KRy5xiFA031fZ,Ԝ~6/Zv*B)Љ\(2xgf._ohkLSh]tjեttFಕfjMNfn[_6ͨܶ;j1du :ɩm>Sy\Q2QJMKԉ!,Oor׸,Lr} !hե _хA?S ZmU>y5&s }?;Yfd8TʹzB;Ȥcz!ٚ,ў wD^B3X:|;mwMܴ>F5e9/8bQxKtvШM9wpI#]y@U >OaXKDA 5'kz&,h9]rHLi] 4L[b{UOlU/={{7eG:)آ9l{ o=zwkvhW,TA* Ѧ,#Ofc. _?u1ڿ6_CK.Mcx֚~¯K&Z*]Z#OEǖ͢MM|>/8U>֔zQgiWA(' lm2ϟYk& Ek׮v s`D?>Vwາ~xZaތђFR,;G' "+VZXBi/aJN}&2l=)~fyQM@Cejવf~_UߺPMI4,岘nn;qZSqN@emUK̴,iϓun|w[ZΔU][j h{ ".dkաWhk+|s(Pu[-Sk_cR-RL2La0vkm~娶H|V b* .mdM d1g3eL+k ~MT:PPznМ~NJ,,<3l4pe-S̪:Zn&>k-4[tf~&t\D-rNpzhyfNf. )ixenh$h8s/gG.܍Zhݙ{T{’LEF%jc`p.Zvz9f]ǃ_gfs .(hHZ6f+R> ~[ܦO1#ڽ>H9mK'-9 ih"X`E7r?Qf>QFkŀ.[HN)ۚA$HV$($dv$_?G@cM 5uĝP >ZX!@Hr6o[wJf8rg5(Hb \ +4!k\rox/f{#&Vkq:bM0Tݜ;a&4#`feƇ0 Db``a&J D9N5D\IL9wFE/D_ko&uv!x'n\疛?ƞ-B,&o`P$H@XYށW*RV>Y@T=^[L+";/ \ yJAOd KIc1j-&M["3$  I i_5 N 3`殃=ڂxXh-NA@p-o*Q|0FXQZ0 5#Z4jM^=2  HRm88.^D<RՔ+*JU ~~^=-nvQrdMjBTse\*bcaz:u&I3\g4Q Ua@%x5,RNJD U5iqYntKzFlki#A;(A$>GAs Z@4,‚inW 68A~\(5Œ)+2CB,c $M9<2ߛع.Kw<hv)̎4"Z$rٷ%u7-m5Q[)OI m4 @1 }IRB%`TֽH!e\:zK6=n<hXQw ҄̾B}*42Lu?Ho<Hd0kfJ*4(C/p-f=1.S F՘.%1>p[. _uIS5Q.;`>eBBq^ج`KKQfM%P9A4/X*qeDD C/4S[U[ʉOS1^7r1'mHgH R,lٸ\[Tq\T[~͐@jBi(Pc̚UJpԈZBԃx ﹔MqI3RF@qX%pՈ(+gxf&Aߗ:V ^`4yP^QrYER2P-<@сf0 Qt* 7Ȑ`ΣH1dQӼv3kĶtRx=w)"(n(Fl^"a"cμ'nfͰ N:㑶 ,*KOIF 6#9N@E a!xJ0ó($"##"dJͬ9M%[ınESḷqN,J{qY3.Ōhdl\_'n _{-F1i*Ky_#HgsltE('I1sfr<0qVN&c8BxE*B)5kc2ᎷGѻpL";ЀB?|wQtGps0 [2eGϭ{E*Tz"AՖ-1Lw(g,ޗrr`B TSH$F5Q]&'p8:A;(L*p] [VVoIÍPiD %@jcѩ 4a4/rק.$luPp0pLaյ:N娎If :+wy<IDU똩+R2L5d @ a((:x[ | # Z ' C^R83(P )M˱jz"2(!7!Hl<$4zLȉʼn7t+ Q U"4['C xq c-Q <~ÿ:*D47 e`"x5A4+ڮx+T0XfW" bDBBd ĐU !QyDܷz.^ s ?gd~@$b=}i Zi3xKʍo7D] -/2?=~٘îNrg˳dDNUKސ3j;K 3b?ո=[ӚKk0OF̍yMIr}~mWVBf.z’dv=K[r+ܒPoNvw'OcdpɇYI1]udPR섬'&YIgо+l _1c}a;z^,M!کhLVܶzP mJ<@7i-U'=kL$&\yxN窱>e52B8?R9IXhJӋj$XqpS]Y.E *̝'. o)Dgp>膊%X */0m}{Rs+T1w[zx [._g4i'A^Z )5*C+B֌Zܠ{xmu>h( |6\Hu4Yb7M daZSvj.t 7`;QWovSPit4ѳ65Ħ`PR0)Tz Y HE1n}yX9_n;p#I`(s<'JR:qJ;,0"-U@!&Ln"I&U+gneyH iid(Q7jⅻ;"F٬Åy)hT~(J MhHaڍSYU#L@}P#d{^*;-ZyFq;?Xxَ,P݆)ԨvIvE[X2[ K'1q!1&1VKQ½ހ"wH*4" AtYQXZʳfśDmHI'C'JYSKՕdC4 J7r'{M5̸jyQf$6v=Whc9?PX+@opc8oRDRL)nBĒx~;yP(Z&;$6{:"ۨY~ߏiR3 nK Tˢb=Ԧ"1TB8<>+O(VZְ;N X ±򸒧{eL-ę ۊ&B($w#E[@` X 'hj֥weaHjJ1M e4l SNwU lG,\ns"4|g'-kKT v/uUz]0w&i{Y;>=W JfUQe`X)gW1g;׾+ӂYo^]]Rc돟=(W4 Dy^K Dӌt,zfuP^ M//gh{\*밵@Z/p@áY7NOjbXC m ){Z&yEw|ͶHo7l`|`k\Yѧ'(rfM tFaJ֠ߓ(,"zXڕFk@_)*T^_x+R U g77<NX"%-l+nl Z[\בt)dJ'ݍ}@zk]65UkԒKND$c [5Vܤ*X?\ފ9ҡ@I;M+X3@#ֺrHzDeO=NVg[Lr,,фܺpDFKp_h" A'&uxAՋrCΆ1Pcd!^OƊAw ⅔&XI |؃l.a7B\"H"t.x< qJiS&C8>nD]XI{ѐqcR%"w;c|_b}qfXwހA"p$VI _3W& :x[LbJE8$8S tE s¤A@̠+gXIh_R bNLqH*%bPP@G/'Le7=mD7u4f-lɣldSZ2G%יW f=g]`\3G[9}//0?*ZCw]g 2^7(tNM߽(pˌN='T2uiBzfg&rҡ1^aEJL F;Q^}\,.~aQ{-z#2Q%EnNO HwxWH$<6n4yvD%\Lp+}yQ>^sʠn Ш dDzUD$cP1ZuPBI=M rtf `7`T4ITL&o-٦&4,)/)5eYx%n~~B;CST^F͎X)*$z[A }2f{TzGWI' udO=3mE\{O3"y ֔:(TkxF͸;L,E؀?)8U.cM)&1_ހI*hZM{YzjwBy#$rTC6K&yC x嚧5Q/xg`d;Q\?qnrsRu{jt7YhkђZNOL8Mˆ%j+kbut,n<#mM'SPZ 8 hͥ _Q uVpqM8)"e*҉Q-CbQl;DD'5˓VrƑ]pC7$I F!޽\Յ]pbӥR \|uƓnF-[PBnA:V.tnۮOWxێ4}{Jֵ Enj}}I o.kNʰ&eɽAC%m>nARIe$ɬ<.{+_u+ym sOzn&O1`"Q"^duqߤڴ}&cU3"mQ=VKCC檢dȇ~*lo2ޓN{\DBd䒻QE>oY?M |}ͽ ֜x|3?w_*7 uZkvrE'+ÐDG4 ŽR-jŷ*쮑HƵezu2ؚ*;XU}yMtM?D8nR Z b &&#is7Ih`s"Ġv nNZ BW8|j}vN9ug2pPȡ)7u i(&|;E53 Ŧu s 7]v6kNS}S%LL%!z2 ԁfCrVg;ր((uQ5YK[ԥdIԬ{_ߒgսp|*K \*Z*N'QS^:Ba7P ^lBTQ +y5;7 AFM5v&6cӸ0cѣqG ޸WF䯵B51j™f(C*z>m##BNCsO0l/1{Qr?$L]T'5&QF#W51jkP?T2[:Eϕ8x= Y6 TRx;ubuP },΄ʶnE5w-]@}Vpǭ @.@K̙pړ0*PJ֘$Jtޝkpt!~UּɡDYJivPL8-v* [)Y)lJ7Iq0żSl1)ԖU?g&=X &1qj=$$d#s@d bPEjR9J* ncv[La$ T^Y/pRtQ' (Qd{4 E)獚+_za7qD`,_q2LP#\9dք!D!eȖ,1&sJ/*/޼ȘpEnH0;ӭv™Z2wo,P^|p'1KP S@< *]x50 L&μIOgz? N[e^\0afI wb q[oYԟ_M,|S.YJz\aF8}!$1lW@Dh)w]Ez5UCm?eK\YU윋&$ӹ l.\R|rۮe]2BIyS*1i0u9K# p}T걁m>%7.G4T0Mijfá%%rɼ>9]@eR^`zXU yuUV 4ABeP|%)nI2j%Fs"!&PTyQ,٠~jB\:Wh|P&jaE*+tV=r2J}@M Uh .t^qcApӉ䀔D#mGMy%&.QV7Cʼ]u>顨l4~Z[\F1q@;`r{r,#'Ca8Z+^ x]6-=մٹ!_j` #,ai(=[ReϧmVlRP =@K`Mb`p{soQMC:E͂F: ϟfrw-mrML\zU+gԸw嘆o/d*Rmr1,`2&2i.cp"X{wk5Qv~b.D˾~a;;?`iv>UX5ۭ/V5mVӚݤBD_/0[&l3]!RóEu댧[,)Yt1n]zzE lw "Hx6*odE\c6ۢtjM*ڭr;/f}f l깥ki7k_ȇ7~oEN6[*5ݵ7@־6ΓG2D[m+Yءk,5mUQ4U#m yغ\‹5vkһ-jECDxK8'1}'^|BKHQ%;c[0[q$x-Es5[t'Q.F6핚`.=`G!R..x/b!9-wgm|@ps8 u–(6_O˹ F.v"]gh­vtnҘ-ݝdC-XMK8Hr]+w!3Ut Uj$+@ #L`+SU&iY4ERI\J yiBm NAB:O$ R[OʊQb$* K$ҚW<Iէg7ɓ .3\ +5$ykH 3Q]e+;M"A eCpa4 T+MV R ]JH(g3*OE$(/^#"E7.UDb%RK^թ2)ZgBz /߄r)B,q zƧGB/%)}J4 Jbm%&A%~Ws)$?''Ϡ0$␬%&YؗXi uy T@!>^2kNOQh.֘0% {0,ġvUKz"Q;O'USB`i"PCKF IHL&,Ogr sB_Q}_L=H$Dbjpm^KaPARZC%uD_N'VUzObB(Ơ 5rN^eR 5H2irܓGErx B*?5kzQF֏?S&I&jp-oEY& I ȊM$oY@ul9N1)AmZRI6@@ %ɤni~)^=/07^ lz@AZ ա !ex\~"y*R~QJ+rl.G@zr+'&J 秬ﱘ=U[BB(znJJghҤxMpV B40dcQG֨W-e,ZN5hQqF9u%rNEKKFQ'2Cjy nАŽHĨG"aaIfj^#ڰ*sbA\X}ëө=Px ɜto^rqT0J7hA {NDC1d1ih;J4"67vGZ" <űD:TZ#@P:%;YlG ]عW04&Wg"e-~_46E\b[xq{DU0$4Vo|`"+YS<{Kƌba&>7ZR3^h~Kkwf_,Q{s–W(sk. e9&1Ϋ3 !#(4XȒGR4 Zu58OґiH G3N9p x"#/>iۄ`*D*(4 A4bҽ%(jfHݟҥg #~oP̀.1 .;f/5_|ύas=ҸbA"^}sجyC׍w{᱂cs *(%~ʑ2k.okһQb%aTqa0K?FQ=u}/f՜\RjޙQ$& D$N 1 򛯟-Y%gI gqG)мz ̎d/Armqcy9bk9vR%BbCgCS^S1.@#F&ڞ>Xd5fͯ󢟢@llaSeL|#&MS;6)?2,9|'Zi+l"ky[i{)5kҀh2L2El5pV ev+h, 1#ɲFICLǕf;ƈ@[`_g~{,c#i F1@Cd8RNކX4sw"`r. 0!+#FCKxH[S3Z&q90q(V+MNn_7į o+.Q5JrĬax|Ӗeòz`ԈarĨQmC+d $ʎSr>C(b8J< r c# Nuw |MR;bW,ef4lD5pT3Qژ4` Y>i (YMQ,qF*'4 4h,ʽ(q<)QdB&UvJ*˒ 9 y% Պ5kĈ+Ȁ RS!\qi| N.-U9V sZ tm,!,Ğ c @Vc TLiKz_-}]R ED>LeC!#BxBV?+`nO$~{5w?ɟ +OĮ!*ly~ 3{g7ee/ dbS2DE:(NZy7Gjj3cvyF&˨dd+p©HtX̫g ܵ(+p#Å<5DKɦ# b;D7}FZb/{E?h8]7s(^$J̱-q+Di~[gTWyc<<~>ՠWmyeɧ-gm ]avӋd uyNB7Cߤ3`p=A8+^5QG/ @ ?f5'Ƈ59zt+dǦh$t|8х, M Cl?*)\T|&[*8|$ ӁZ<%Y)<ŒU8Yß; ]($%g嗑\mk1jPQvzǯ<+ 45G_Kdr랯*|@I3jIe>O+eHh/md%-Ə86q$*K,ҾĖ#)+-YnڨXQ*^" Zl&e~ZbmVukN8$t&CUxӦcnԤ9y#X1*4Hr^c]-'DF,fWݙA$WB)EbX#\"HNJ;/ :4-lh 4,!H6[h 1b}CKC#Z/ſd%E#liJdEj:<`D$ʮ蕩 Rq+y2wȬԈ$+NBYEY!'HDYO{,Zi0e啐J8`3F^LgI5YdYV.z NDi`;RW'RЖM[T/jby! l>if9mR.Kaba0*eYC`Q0 7$DhPqOn\h:NN?o}lx+jDJ)Թ5b-&U?*Ꝕ#4E!+l8bʵrbEZX5Œ+K{nDMBU2tObbl1m*;q_Q!6`<jk򾓰5lh/{54>{o?RPŨRmdUd^1 [/47g>wJUƱ4(%!))sYiJiʇc Q筬;.J1ߴzT4ڋ %c(;zʓ&4zʸH뾳=YÐ;\!Wl!4P!A24$UꊏbjgziE`BC{o-i g54b(v݆emJtDVklWē>k 7}[4̓ DڒHkwf90|;˪EM=NSR=Kv#w;C> 2=0$M#0+u5jC^(徵( "@o=,W(q> eCIB^w{r:%EMw*<rKmА+nRF,ͫ02xI&P(0hX (Z6s-80,u9DpLACiJkwN6Rl ` "bsټǰAk)w F,4U\(ee19iL㔇PԀ|0}{N7t2e Pl!*] pBk7[47yxu3hw`"%Ÿ\)9-^$(~onRwM{bd04g$Q(!1ӻT(j{}D}"8%P(2Q75mމ oTZkɔa^NjӔx#D !usBJ#6>& ^o bjMi#7<5z|W?_}28b^HM-hygyt&ʞz Vg}&ߟ3 *p,`챓OJ3cmf3Zn\TnʙRհX8egn=\J3zU3ABqm(mBZTv"k&3r[+N{33],rf(IYPyIfkz`v &J!HQd[R DL"lm(6H1Qi?Q 6:X_B$ d FxA@)` 0!! $( +u(3h4S@V4[w* zMsجy@gPYi$'fp 5WΈ) |#+ /?MoS#fUH zG$)Z ΀ &:AIV_~Qhb0c,>@"RL-<##y谛\hl@A`7hTDi:mV&txϖcHA6"6a>fSJ"LZ̅|0"XH$R!O,BaP|ΰ|W=%+%VA\J"%m6#YBM| &m5h|^MSe*iY(8fN|Ú@e 4ͦ`>;TVTH&ur6JDu<[r V ݂m%=KfP+6/]|MǞ%nyͽtkD1^Lڽ]fWG3hp4w+VD 0^H0X]ΠvZOo WA $t̸EdI֊1:S2zswd.jD^[Pg0DisCţzBB#Jal85+DQMjG;̐vU8&z 'Tds?~=/a*KʨU?íyYTo\ ٬ grX7]ߛ֐lߣ}\#*z*k+kV >+\r\T`s,/ɶ,FTTV-V R]tFw;*fd؝$٭8ta(l_=ꎷC(IWvݗ:ypyr߇Y\NST&[1rI3*r][ʾ+cd&+DTT8찷n5O&35r[|=s_Xp;S9y_y2%2g}Tt)Wc`.]N<-*-rDeS6_tj 5 q^Pk[!+-ŕ I$'PgԞ۵O8̚zo=;6(305 Գ1rumx-]kj+4n fQ+eʄ籗h]dݒGaǩ25(>Az>Il/nCJ]TLtJV2-(: Y֟uAl>`"1}Wv o4I[٥|%!⊬(杴Blr5QZkLߕ|~[f8'Pq~64l)M?3af/NŕNI]>MHHj7v` Wcmc,4^;bO=8 Zi!my_)I~xqD%psx5i! "HLwem~[cDl5'hEW޴U)"tԲQp{5WBmНoDj"ݟ.䣕]ꭚ5jm(Fp|(&Zb5ɁG+Eնj"H!H`(]jQ>Cmg`,u+ܸCqŇ7hQ7.KƆõZw ;N.W}U=QTT7:zx9%M0߸̏ p d)C$4V*)Q h3qI1DT6n0M0o\V p-0NĔiqb$MS,|[@%B"TN 49S] R%5~=g>%Lc>݈*#0ݯOpR<~AD5;|b9t GځF-F`+Oq/} 3KNQ"j^֊o݆_y&/]{Fv#zU.I ǣrrW!e.|\gҜa#aN'ecyLazq)ˑQ2.*g҄Rd^pQlF`ў R1Rr0K|;ѻ"U2ϤAzdUT#0'.֛\I݊EvYߺ_1 e3nuQYwww '"\ܮ5%_=癌ѿ[eTtYvr}O3k[z9FUeqrGJM_^ra֥̚of-o\>덫EkJOSS3z޺Z\-#TXR(oT4 Z~shJz_glϤˢrN#<1^dfuDzOB:v_h<͹%F^F7ъoouFF`zŨr`X0R+˚֝*y&iO޿oDK֟Pj{ͼ5/chQ[(AAz;j%Qt5lD[d9D3-W@Icm{_ ȭ~ߕ/gJP-8j2 $ƪo+Hk:X@/՛ɯ ˴qSh@TU3hG_ 8w|a\MwC3(|'tUh۪Gp,zw|ӧlTON{U@e8^1ET{ta«*~:st;G`ͧøPWj3xvƀ f iuڃҠ{+ƳO Iٟ(9BY3Ct\QΪrmϽo\,`?\1iD]3CeP E8ʦKϸ-!7ܵbI`cW`K7TYʻ5DwPRhLlTzm |/ڸX][kliYb0ĺ?4D砽9ȑO#M++1q9rjљD}q [7KCz^p?}NkiƇb> 1T 鋳eMť}mK]^zQ?jiaw7}{^BԢeeAxЎai C?jfm.OV}]=hYV eA r6)4F&QKY~ucswTXZ+W+ȡC?-ȠQ 2B/EJ0%_4G瞾&g{M|ퟯ, 4$ $Iwh%_]j=k±3o≓;_^Y 2MO*qEJ%v JCSK3i$$ * uߔv8cR?>|M8̛8rr+[jfťϯ_x<9+˖B%y"9%'|Bwhf+˂'~ %MT$$qRӠwgv&bWP8lk(eAiR^RF?%φv$bWQBx9|/:H1&FodS1 ~0kgClq {$>j^M<׳AiSL#olegYcj_أ44_Ӝ%evԾ4sϱbo^Ab /%b穅8H'Bj1i,_KF?&';q %Q!MY/drǩ}l"ʨ/ؗ=tσM'.8P^ة.A1ҚS;uBSPlY]3;uBB3=(dp,f__r'0 y؅qOODܙc6 J˗Gv] )cr!Bg0W-'^VжiwGV"N'^ҏvԊ+"U6\3/whۗJ?GԋXlA7&T.>[8JlXۣƑjCnJ٢m1b_+!7qUi\~7/8-l}Q`[^Vu~hJtl8 b T[ы^W+%W;pӘGߗ:<U<"9g E";uxRA1IAuJ/A A \l>6(»;lj3)HYZ@h@CG(CIZ.hlGUd pT զ 9}OяBEC#peTiBLPBs\iմ؋nC(jV-o A]f1i j46SB4꣱vԚj$&|m^gaD3QTY4:Yޤz45;_7kMm%4QEhk0Xqɷά~&ɧ@RN/H`>@ޱ FH?n{z4ZeM:է^:q^|DJ!5M*靥9.˸[&i3kɽp~…^ S54ryO<,wfu]#N= as171#Q%x  vy`٢o&1Ɩ " y/$9CW-f:X1"33A\^̖:XIp`>IÖr i#sE isW ʜd'.Ù"Ԑ 61z|u6 4Va2^mIŭQ<8ZLAҀzI!堰 m(+z[DUZ^olR %5(nd*mKCq=ʏFN@Z4(L%('ĖJ(%4 oO ?&\xu 2 (iCX&iBQ"'Đ '}JSXcb&}k]q.F- =2e͒Fcc\#2v۲5DOzDC'$兒.),1U1]Ҝ{;zsyt 4^ݥ9랓mq@#]qz]@n/ m@Ӎ6z1'v;9mu@#Y Isf}bs+a=&O?:1d[kwG vE%rX֋z-ѽhTą}MaQ/}ވhlĝ֚}:I;5_DUhKGM|Tlk͞$\C5|q<\p_ϯ->R"Ƚ; rSb0 HJD5z r¤a&MBdb,\E^l!ɵ4jhZ+ 9KIv-qGƆUKb# ɬ~d|3%kL J% (!$ٖF 쵔HW&ΐ4HFzњФ8@dFūFЦDVFR[Z0 TCR?qˍh&F_Hz KHh@ai7 י-oϢwA_uF6$D$7`BH4\h^ >&-G_ A{C1(x}/0wv`R:}3&ժX𠥦އ{fc4}955#S5cnx0̹w҄xXJdq4gu'ߑOq8h?`R5$q4zzsm?j;LwVGex?$TuIjM,֯1c*?FzmWTL5`֮=Ovv OL4;mrucYSa5]ՀzM<o*'#5QɚS;G q3Y}̍ɐZ[aS?t;N^\83k{CoaԴ;>3wuG-1yvb(k{M<0jmeTsFM˿}g%5pe^)=7~/LG(#~ί~-25+_nm+u'}.y6k c*FRb_?:#2j`paEڞ;g~=y:h"m-rXoΌSDQk̚[ IܲչIjnY$5m/97IM _MߍwYt%\ع[@NMWndM%R0eYމJPc$t``k.DU3*JOdn5CUh{$s"_槾ŹeWYo \U]DGuuK wz=2)R=}m$!h U+-[2#t붦Nh HB*nk[2hFK l_G74 qxO~Ioɠ)N-ڠц:(b9t?\8i21OfsdrU[+;wuU$ba>m;ig$< J``F0NNFЅk@<&N5bfMspwVw خPIfk狿Ү3-R *B/3#EٽlMu @5 *#v xN}U_Yg{6I8CwY(.A'$Ig%>e9{`d@R'2|KwY7W,NE줙h28.x=3?UVkfh>ˊ`Q NlǼObfDv,aɔI qUpB̗=><.K 8V$!Ezihg#)D*Q9TQ.W6kT2)Af96uYUF{9% xd= imWΤCi;|āZ(N&h!3҈!zL.j # X{\e9dWj J^E>kۉВHZ$jByf! \z4Zbi%UcS.P(F-5Ƙ.dF`}Ȕ,N+T157MP"i,vN_F=L 1$Szԉ^(M˕jC&)9DА`l+?@I _/;wb2YQZ۬_nlSh$( EOlPf`2Y1!YpZdR.ک% OkIYO6+!'\۬a L)=Z&( ;_j BYɾnHc*DTSmCeG63PNj[j.BݪEbJ?Ь H`w XC*w!破`I3֞-ğ'KECizFF̮x=MTKNhvA3@ZD\>s `% [ !N205J3gFdyɚL"_KX^)7yVF~AP'\`<8[x|?Қ@S9 :gpt4m??z[' $, `Q:U`$ڤ9}n"W9 * Q:FRě&5>,_QT Z7B鷈&;SNdӦW'&|j֌0\ZN4R]/RaM7eIm`Jckե0$lR[hmv*{-n $&V$BEg΄%YCa w| FtK:"Ar޲YC 7XAMﲀC.u=z&̎ aItI鞃= YC=œ=eMȶmK]D3IϋҪI4 l4 "aSD#Eiq ])Y8;kPVW3E촠`D|嚕( 9m8a,Y654qWļ wiK'J"}660a68q ❇VI"6΂hiV`L. 35` mC$e&bJg$t)Ec6ykt[܉ypx^ T4mN3.`uK]ceGmy 5\ꥮhоpcꕄq-0qxE$4,qY{h~r^.w9 b~ϫ&;I5Z $ϻ'\0tdپ ;wZjƺQ: I*G `Yb9-[Fffr;Ղ昚-!һKV;@RAxܭO<0#GOSNBcPT)HXkT4Hxפp ;iaRZ|2 {JTB is([K-uRT]H{N[iFPK3%TVuL\M#{3D}nZ4S'<< ^4dicHgucY,ڒW;cr7Z3&]F3ox F1.=8$hT#HAPIJPVgX_bkԸӚO%O`\Dg\d1~Y|n3_0HJ)C^! {7 {,ELCB{or֒θ{_ ɽ 10nk%DZF%+* t9RT{mR2m/~kfop{i `IE,H h f|ݭ@580@9`+XB/91SzWkRo׀&A?ӺiV?u5~KutA\ yم]]+U &hb&WJ~.@b(+֬ԡ):h;?iz&ٍ6;,-P: 8*@RIQ'Dͅ7c)/?%Wv1B ՞ 90 0{f gmy,Q-b, v$=#ǥ;LyV_"b%9~ @}" li\%SA,a*:-Q ]AdLvHȤ@X'pąD\s "0k$jsL2U =mD>$Wcx.Ib$XRCZGGqNgC0D8- gsYZ[v̓Yc YL 2֟M`iOe;< ''55A>XPqa \ƬqX.KCVVbKe5'PƬ忺S W'*ofF0 KK [,,5} w,CM.=BJ=31nժ㷺M\Y )Ō% ]mM$ d4]Mh . VIVƴ-p$]01 {b^]Q^l)ÞnNMRRG#9-jcSUs4^ĉ)k}^RC9JD'Hjᗢ}/͂K*lfAu\1klO3ɚ$9׿Ц}UcJ=k8H$c.>߻O rx-&,8lB&JȂMJl0kH^[19<#=A+"u1t˕n0k4Q92"E. ($KxOG6tm0kkQqKCZ0 7ĠQ}bEJdkx"ҕj*(Ј>ghG%:\*.P^L{^Nklr1mF 8ZS`D]N0!$@5K1%n^As9 ׮N בe\o4zGr,uhvgjb[\~ /,A: W!u5<MWvvaz]$cI 4h̚iJl IՁkmƦq0SV 2bDeʙJ54V;DEZ"8tH hiPKRu5u6ibdUEhb$J{SL>f >X7Y)0Ji\mKp9)(];iSE8(ݿ`BP  (VMXkxDq ΚL[42]e+$ޠsa؜Xf ?sPQgX,o r)`,aE}$1 iN)o4jx!1bOT` & NA&MT_Arɲd+1ߊiGG >z^CE멳gmْpƢEbcH ] ow_6tհxRcjF~ū8@)\!>@DȞtbqʄygX;4J;@PW},@Ĩ5sNG=Xp诛l>VKƷmTmQ~U4leu9X8swZS퀃LxcU#  =t2oJi3i/DzG%m0DtQVPSdJ˚y굂Eye.iiY$l5ܪYIʧ.46^cSP0ri'Mξ8ѭP`2gkCRl#0r6V64:/̹fAܳ+ى@*mcƦ254Xjvp$TR@|-˘ RwA3V ]UX-M͸7wcJf]+ HhD HZVt&Mr͢:1f -jvƢuڔv3\9b6]io,Z,Vjn`tq-W7bh}IMl[;b7'gi"TEvU8,`qEÒ=RCnQP~%-rt(A'qyD/?ͳ=xiFΫ)+i 4\|AUr7B_c4%o6LtZhcv-rńaω>P`HBגM/?`%@$B E#xw |a@“Ivzh , D'&$8G<1#wudPQuE+QF7HS)<#Ck*2bDe%*%)2e[r͚͟t"5*VW.hB,nN7klUaKT~cYh 0CebJ͚X-EA+ Ga(` W∐NxZt10(u'1\]'' ee$UtBHDJDˁ8/jd7*lz+hA)1hWoti "V`0 OxIF!J$ qg{JȭY1B 1 E0`&~@/(c4=qT n#"A)Y1jj]"X1VCLiBng63a1yfr {(NNRT+jo;sh`с}@8pwR*5]yڂAMk&K8 #˒||B$2@#08lqh ecW#kټ3w={ؑs @)D;N+:ܨlQ$7fNJkH[_.xYPnQWTlI1 ۨIsה݈YfQ,9rz\Qr& [ >*/Q5H0\yB+ P|qLة4^ kH-., hUh #8qGoQX2vRɽej v9'ugBDyaab>a$b`V/9&J,KF$=wrsx SLӡ;=~>ܳaW% 91ND̬M XB%{˔4бn]?ЌVSf?ɱRE2E@$3 jb@0MBΊ AT%1@:z}MIA ibAH&I 090ɂ* ~1jD/H"1ngH0:'u<6ql81װ[SJ]DGEkɓ͚ĚTF:J#lBH)(+%JByf >=邂DT 0";05bhc'wS%D#rIj:o]AY7&wb Ҭ`!U ebN(V $aA\ߓ0l4}Y `Jy)TIdcs%NӂP~S<]JOcbLrsmKm%+1༮i&v<&Z[6XI)d"g쾉oN(4& 6*i_%s:[dP㙹~pQ+l8IxX#sƀ=j>ʭTa 3ר="*ׄ!aR%B4! D*5iTykn${HDLpYgãvx-(@| {H!SrUumÖy!ɡ5b˚lג|е"4ӣ6طc&4k{a*^ZtyDHjkNȪj^EE4bֲFORi2YVYhH*'UCaC9OVyhaPz @*j ّ,`H( (Ѭu3P-Ld" PBԪ5NfYh5yt)Dm YR 3[6;]rѺuðݞP[ ;d2@RUҿnYcĦ'd1iDXosW$ A:h/}2% +}!_1=Qѳ?9r.RD5|b+$%=LؠxW— $J\{:Ds*W8Dc0Gn˒L@NP[ZH6\@ق4 mP ^2ؠ|,N ՕBf'lñyST6i2A6r/Ոt"ܔI $ K"/w/'U R:(HR2=X6KDT7c`pHhּ* %5 !!1xKxu-&yM  kM2jp5P䄞/"0yTIxMMP 0UKIü@BAJrvR?ڀj#DUT+Ic\왌& 9|𨂧6H GJg%#ڲoNHusݑ#6O˾bB}CzzqZv|d~Z3JLT2uyDƲrA1)Vdт$rgEE^Fa!6 E*6HL$aIHg%Szvfu`dO =ܒ2vOqR܏4ټlzw5C >(^kѫ9̚axevDEЛ#%HO!nu9b P@̚#vd3 [y&c+eY3}HpU՗FUyIhLB4fM [Ӛp6RW] B#4;Δ+uȌ+٧%9  wa!2$GayO6nԘG79*Nua5=.lV:d ܲb$dYWGD(^YJmkQ]#d`vg+-q(oY RZ&<.~11osYdh\7Pë0bCCD3#QhIAiAwBS{܉ +cD@!2bV҆FHŜ&,M 3%K8oЀzⱣPn_ĥHv[{F[VY=iޡT-@<P|^ ྀhKXT SCLVȰ ^9~fUx>tFKa]G֏K2-7rzg ^ۃj7pV18Qt!yZ+ow;& CzծD8A֮u4 nf8=_IC FWV4&K`XeUKHcc͹{B{KpR+ [~O'O9%JMYBo! գ WR-&ABv88H,7[zr a##njpyV'zgҗm٬UEHn)s#%S*ѣWtK {*/HfĎ@rZ8v*:7UPҩjxHP3i=Dԃ-B3CSᆨgE0.& 8XA%uD#(i)sq8<,QxFy Vi^ԕxqb,Kh. 2AcFK}#y\ynrMFz펑FzY' ؅.9gqQBЂ F  bq> ˺$.52&D*K X֐C 0 c;=2NfUh! /*sy=X'%mf<;@"]BxCx]*pㆷPʾF/ @EGb{+*_uyOkgI4^je93vWZt"w~kߏNY2Zn.ɲ~9;?@͓@(FjI,DVTn'z ]-M [BBVʥ`y%ǒ"X UJSR@7F7BвLV.S HLNcj.ltIKGdz&g{׎wi13@CK*{42sa'{KލgθA83Y T.;^lpuD LdK0 he4¡@ j_slHQ6AvXJ/8"d"{yQZc:- +^12%M!"` Ɠ -t.TUj$81FwoO߆d7h6q0: 鉧VJ QiM4vɪ[,x -|d*VBp0 .AdMeMNB Ela0`0coWDRxsTϰ;AiXPXRC7Y-mu=Zg-H-[#}/CM](!Ȃ Ej),)94FA /7* Q]t"@*p.\15&~_w(N#~qJ,$D& fc'>jSH4P'k`,y5L6ؐc#goe }+q (mh`@ο LlڈLY2P(t+@CDr %YhaF:+(-t1ͿRhmhAͦs`rI)m`WL aQ\Bf2 Y$oZ IC 76Tσy˞`! wU'GP@5Р!@Ҷ='^$48aq0RT54M7'Hk$vߕv O~qaVSvVh1_ZH;)ŴPz!a~Yf'&:)8*[Z`!xdt{‘znd$8ۿqW ҃5hJ+F?sWw~xga5Om`ł$/Z#hMrEÐ7ά> sAH%$+Ƒ֚wdVmT0ά9;p|#MBai7Jɒ֙5=;%fNA3dx"Mj (0%@( %̚%it6Ad^Ѣ Xb{C붹IحUMͺhv/; sFG)dkN;p]#PO 7YaA4!.f%10PjY¸=3㏯\V5nH"{S5Jn\xwa J Ʋ XME!e~8ۙe+UUH=էW纙Z?IsHU hK d]\NRU5{2IsŜ.R%GlJ6(Z'd@c(soQV~nAa5xM+O L2W yoHVDtA#'My F$Cܚa֠sܗ0>͋PB$pCD0o g\>gT-m\ԍȎKBpV|?a (^ \FB Pd`#uWz4#-Ms8W95Biiln^ȵw  ~wn=556&։n >,fpF[%. 4MkT͟nH@e4ԡ$3h>C=seYOJ{@^K5iWx*UhQI>ugi|)8rwNù4FB$V0w|`T:x[itث!qǜX],hG[i w**ht.=@tI֐@ +EQr24Y8\]` C-JSAcB6)#ӌ~gȯnD+EB/V 8"ؑ}t,fjBr`P-֭ޤsI C8dzЅm#:#|"2W͌ q%Gu:|M.LQS*fᔬQ 1@&ՕԐ*:RK2F,\Pb%˩Ayή&UEqF[ !!FQ4A J{h.z!zL$1`-ЎqqY{Še&h(>@7]%F p "917TrheQ]#}, S3 "ꦄND,YQ65J$h#Ϙ* %fG%jAe%Kͺł )ZN9~Y᪅f9t]@#VDAV"*#ksx;fjuݷy#eC"+D>JDJqsW-4ƴvE[YMZtwm8lo3/ ס!β%UVkET. +(BCHTjPX"V4$91 jxMRktf*j(c5W!oa:5Vl^KUGa4[:R o WatTe$QIHD LE֌A"exa :چ$tA5d38#Q@ `!$!&D"qԥ3`:PYeAz^_ŭ?O=zn%nT+M6f걧o0j@r{?$a w}h`Qܑ^8,wfH8ڈ?B+/[w}6_:)"LT_|v+Xi)[O=)Q29p Zu1RڏW ,+ dOXk!ǯri=Oi̷{l+3[PzL,ɑ~FVP(6]6X28[e *u"0I﵈T&X) Y[X~x%EDP(݇56q-x%\>gVY885OTm1SМUV,`YAH"#yhqu?7p_F3!#4+Т9.i 6@79`E r eY6:շA,+p2ϓamzeb)\s՘sԶK1;*T&WJeAQZ2ifww?ugZlv47aAgT+ˤv_)FZ]kXya|%O/Y&M6۽4TX:HYKF4GyלZqu"AS1mML~m^䳖.WɩNA I4\rbdH|yape-*\*PA k P% =vQAMQƮ<) c95[GX,H"/3s/ $ #` lxΦ O"]O*,Z"@O9-H"c:Ń26|sYF9?Ktg,nW|"T׀UrbuՏ^w; s2pI#CKL=`yC'e{R$e_{-qX5fn]Ŕ„8Ѐ],Z|!d9"LV$Q@^ͭ,5ayݸItVI_.U.93J 6,Np7q ^؏LDcE AكԣUF De55O=-TG@m]P  N@:}JO %~J@5_]T:.E`BFHЉݫYJUo~?ֻZҔjbedzcߦZxw,ˮA/!l\R kNHJrL"+ԳB ~u]Wk u8Y}GJӄ|MFazqؤ?q 9Kj˚MX`r, UȀ ] )1$jNztIe(:}:l#QbvFR I$F2C3|./!(@d 2>5fb.9Q`n_#;3m*U.YMeE|V6O_X"t,= +T+ۆ2<ک2A*.>Xp  (QPM[r1AX3voˋD۽kI0?ޓuУC:6SJG{g]N)1IȢvY3w'?T)L-l kƪ>n}(+^d1>i֠=Gm0N`j1[}} F̰EI ,?#ZvAU(FZhDTh5= K/!?Bfoq{ 3p;id]H59i%nQׂ>v1hWT)@a2 @SUM1'쫊T*O۾6rbCܰFX"_wff L"CR@`JP}E*$XH/p !cJ?q2=O2;)L0HW!/] r!$\IO#q"=kӯwM+bҮ~5CJF:_J ^vgjsWߧr~akeHhUPx& 涾G]Πv~8 bFѪĐΌ;4䇊-bPmiozM>嚀WBe#$_m#8C14az~9o΅Q1` L8Oe왭jqqPXKrLx(`.l 8kyRueJD&Ntk_!_ 7\/SȄ=\DVVIF`Ɍunci %ED |o̓uc͉\ XH߄/Yv cGpeWtt "^8rj ȌM|e.B ~o;ukc?M7ŀNg}ySV ߦȟW"epW.yB"CD.{v~{Dm^@&Ǟ"f|%Yl׈ Q,XfN]"^/3c(Jklع +2hއc_&f~i,lg636K40pIE6_bG?^WS=j[/": N%PN8s,M +ؓe1.ABZ lgF (Ru&+%e1ibuM\hXh|h  G,洃9q$m` 9ڰWՔ3ӛX_}>V-1DSe }ԶjT5 ubHV+ HDYBIq %$R@4*%PNC Qb`B2GL6n(26& {݈PkV8hњ))H_!; IsFw"ǻ)1D!D$`hyJǻi..D&CCg'%#ihsV17ω|Y;)w(+%D * E H2VMvZ Yfh͂'(Hc-ȿȳ%[ʽOB]nO4o`Gt*WֵwAu$B\%ES`: dPƭϥNG@VQS,T&F63CQ5tK(ϼmƻ'y6ȌОˏyRHD.+RIU2ThE '%=ֈ'.rCBzMF.sjX)1A&ٛyU]}YoMyo(:l≠eقB[7kweߙ4+{|.@$cɅk.lf3nVYz~'b|J/"+WurJ^bEF[s3kJk/&E9re֠K4gȺ!̴K%[nxF_G[* 244"Go5Ea_]B@Vp{bIU]?_йr\(NzIp;:$| zbV|6$eH TG܍ G&\hz\>1(_{+tQ:0%ǢգTmHŶ݀tѫ15>Vz;脩KIV7EdRZo˶90%W…ɇm?❝zm%&$O{b@%|ZGy)%%pf(W5<2w/H8?++-5Yw_i)P"_[_FjˠI&حke%>HeeEό$vkE.! *>u<*i`=c-:=BW0` Y PҺ_ZJL'1Y҃B@Yte@ enbai^cBˣ{! lr":P QE $wfF . ex;ъ wyJ}% t-7cFaHH&8!B.Z тO}HK1RW6ى!Tjbӊ`{M&6#RD!$%Z>m|XgGZú' gd"Fg n/- cITY#{hG.{Z}쟷6,ޞ*!=ǯzZ3/yog۳Cb ^ W~_Zj6_z|3q@ HRB%ڕׇ1=Ts.QJp坍?ziLr @.Z.uJfsk"9GcJQRy=g8|äȋ"_{yx'+i{==W` 8DZyr\-^*59.U{;[ 2t( B/Ub"ckG}kIĴ8R "@J7pף{,8]Ix# =`.0h=eWKVsjn:G0+O( K!v z'i;϶&AH/n|9=_uOqڡI"'>{;ep}$8 " 79{􎰗} 3q1y?nQ?gR4?p7?{aˈf\_(iieZ䦏Ѕ`i Xsb"7M{7GPq|cͿ+Q=S u\*0{rS;F-?[9,rZ mǥ켖ӸS.L8v ¯5[GPaSWP Qq͘`s|Ė3v!^FYMkݝ]f?]b4X{+k+c*Z{ɰy|ݰﻩzc;$iTKjxj5AG"1q j}V:,DJ?CZ3 ~Կ[Vtaw>ZNDֹAZEDt1Iٯ 2's[_0Y{~ۋrhvjf%%"B( <_GN/G2+HW0c:B}l~z-F%\n$h.LƛFۼȸ;[.^ftoaei_48s Of1gq#@Ys!vG;iq_-6\羣/mdnyކŪ|{ ZIБ7+Om5/r/)[qkUr) W>,4pK;|?X42yhgM9V+ymLNں3\K a$}1hQ{81Ж>A`5\h 8&j\t&Yrwb->Qrba0 !LLxgFlGv鑡?7>w~Ĥ#RK, 6,eš8 g jo>?H??l}I֞`w#W~X4@@$4D}N7lHYt5CʁvӬ[8V7YIlg#V.?6\jVV~5N'NžYtMG L"==T/pIJO UR |5t.hRhå$%٤ۧӞ>.n*P +9i b e-jC9dKvDN;^]4O\AV FQrH d=ڂf{l\"<#/rH9f#aџOq˛lrKJ~Rl4Ck|;e+/ٗkNVFVux ;XV#җ%G]Y#|S{ C `+a ̙!4ۙ@  aX[΍Iғ5115W\md1ǮAz/ M#cReMƴrd}̬IE٪3㖄;,<}7 QR+LDu3?k Nغ[Ԟ%8rzR?r? gP{(_i}SSS篰 ")5ּ__wwJC*FoҥbbdPPs5GQAS1 z(Tm܅UP1sIp-w.ɩ":ZuCjZ9AO_빺?s&!@lA;Ol7L:j=mϿp8P5<{=onUl.|7t)hRY'o[&lp5p|wܒ[?qӎsISB31J411ցxIeMռ%+u:QA\Фj;$ #j@N e^{Հj-/yU3Ef rWMojꔻM >b ZK NYQ} t#tJٛ74Inz;4 `kV`2N.JzjLDg ~\&щXh~ͫ^d*߈6Jzըy_f!YjQIk^y,싌 ^smƿp5"z+JZ'ȱpm+^Q_3$8ZhJ8goЕ Q9hr PBMy/_ 7{h(rnym> }^oEO`"yɅG0C :zGѿ<qqz{ݹz=[zG،K1kUNձ]BY ka '&ܗW~\/?!6ݡWҢ書(pCq^'F$^>=ǠЕ}M3UXֽInXIp6}j?֭>w>E:js|1G`nyp߯ 䚦CQ˯M I])ӅWt9}ʼ+]0PZ{W|9TvNy~,&M,UΫɈQI,픥g&^id䪠ۗH͵}<3k@%T zyW~Z}7iӿZ!7, 5f@Xd!g&X.r,Z{ [dWcV4\OKﬗ>h(^7Gi׆2Uhf}rg:g3ܫ on|{L|${|if]E={\&o_?KojRd${Y^*{˥ܜ"?!X^D?c2/Eo}c{ƶk0b^'(.ј%n;-=$)7ܩ s"zﳹy|8(7<ݧ2h||Ê&<}{SJ{z.^';54vSY:I1e!X<9A+tarwMѹYCϽ ?bXiICρ_JA*$0+7_iD߆&=? wyǰPBYBĈ@eMhzδ[4^beЭpC{OiR"sxJ7˝LU{SPDd>A34˯dfٵ $c2қ(JX#;$ LN,(lp碚Io$i=皔";wC/-[?-~futwY$uoqtTAW歞'~nu'FE=,xkz՝4VX @ ՝p:=4 nNh*Do՝ЬcLcUp-u:3fʵ~=? 墲s_2kk\cc<MaK|Y3qJ$&j8*;C?]fƦ -|]f͐Y{-'1 *CM:W}Yӣ螏e~ޘe4ɺUr^}SؽF?H4qdrf|0ιxoׇN) WO>qc[5\ޮ-TMM?|,?fnEJIGK=djvoDTȤIw 0ۮNHFՌW=[nkOzjf"z_xaF.(O׈MU:}Cj =5tYumTW $FXv MBJZp [Yuh@5'jqԧU賹wU U!AWx_GR)e7{Sw-!oȨ8yfZ<譹SiŴ+wcGS@sKP>~p.2{|2|y=|wXxof_{}bhsȶzʏ;FC_'l,z/gwoG5cƃw˴ algDd O䁢;;-,kvԾA{§VZttw$;-5 q&$<x%yKMv*~&\>4T҃l*΍d&N #7{pCҖtDQR/ݹf3`H-$Աݐh;zZﲕ5:3@ &<)ji<5UnuBOd@ CX(]yW6ugtiamrjqR\=u "j4݂sy^œnz? `e\0hӱJ;?Tf'Νz\mu<}Y{Oہ4ب6M N$?]6 \h/6w/k$a^28Z!6jjRmƢq'jq ,Y4 $)JO{.)ϥu_`2pv'ɦ!*R习&KTWX5!&0Ԧc{U=ɒhDh E5@Ğr92hV vLj Ş_N[Y<6viKҚOpߌF X-|ZjK{sN1k*>nPUwnBqQ߰`r6HLm3zއYOֆٞ!jYƩܙeB\&*X9,q"PJg`TRQD%ڐo@)J:Ǖ-5J.4t(R*IBHM :VuRw[#zmlZ^'sͯ=Xx47e#],ͧOroO`Bij^۝!1q\kc_?X4^r>k} S{)M0>\{_v0o߭N"plU2*j"h@z(H0)N gl?+{XHQ6l|Ġc xnZФqORߐԀcIKh%q?Suq4/{wvropsg};muQx`+8| ۭrFMxסڤڷI?3j&q|ƎuWҤQ^c6~eh2N@%SN3=\GZYv&u m͛`Cϝ[;_|{HV< 3x|[GtY.Y0B06m]nGs_Ňp:y]uR&3LG^}L(zWdwO]@}ۋ Y]4q1ԧ<]9GōhN6s9cnzckk}GRw341rpD뮗_CꤋA4YaSD͵5b_+c x]-:~[IƂ+E5ֿWޘGlhȃmژ >wL[m#sl̢&O(< ͣ7SE.vYf^nJBSmFS4U(X$4@En9(P?PfآAL\L[z`ҮJA錪{::5,khUR2UaT3,? ;o\45lkf[z4cD>Ck)m_Q\JlQw7 WT-xQnHUhZ?MZn ^㶄,儶BւAPTW:*>ta\AxY}푚gӱ-Pd~ߑ[Nd/Д6A0rIlMGՊWf 9]5Zi4b[e/^@pQoA@4PWZCJơ=S?QmqوAQ7=4㓏ߙ}.~_|Y?*F,rQ7?qan\9G֚#gX\4ꦟ-M߽3{nS/t?r뵏5GNLj?Wh7l;|di=rꏴw&G:|+Gַ֚${ǫ Cr~ʞ\wZX_>]5˧/s\1<^k)XSrEgʀܪHgEs(&V;̌O4)W=732Q3e瓉3kNɽÁS+y,^6"r{g6CǢH#6^̬g#Q#ھ~I;3fwcx%ĵ r{GJ|`–_Ϭy{%iF5-&X5IR]+\ y/ [ܸK+Ά9: Ǥ AJ^nT6ܽypXfP<+|\6-ѧ<QE'D^ysj4!67Az,}L>ee=Ic'IgO+BXwH6zyrvM!Iϐ ObvM:S)>a2V\)o|"'sxζNuu?K{qMjH3zKź9L'5֌ZTI.ZͦضM'lkJԚ/&I[P[Ϛ6֢\p A4Qf0hO(&V@ `[-Y5?IHWy hl|IWcZ"6;IUݓH9R=zI CwҦA][Jɰ]}4Ÿ}[ ~Vw^+<^g\G,[L. ςxV̲aϞ0/+rCHx9u0(7O)tXv{S+w,^j /z{05V*dٷ_f5Zb~n%XFO6R*^_>d~zw9憋Mt jq0H- R+i>3 o۝$mJ%#mzU{S6ˋAweڵo]yՏ)MvE ~勄2hvv''Igc %ЌA]OD./+B~NiX o-O4$"$`LU(Q:3T[^R,B og+Jwj邉z !=G2C { qef9'KD+#EUS=娅֝[N|\ caӯ[2pDߵ%: GwoU .ve]$qNM_2v,|nkvetp](sr#iEk[p1ڕQdnǹbaf wy{ƊߕQtM465VSwvvew>Ot7Jy?!s{szћxtwm̓o禶\gfȽ-͛"C]R5{߹Nh}~2wݻG7%<:%vB3G}!qkVx3˾,7=hѲx64n Mcy]jAKrP.rvt\М.49yzgT:a18̭a~&͓j7Ϩam1|?"I3ifuyFsTa  ,4YZ' $7L{'61/f/1_;ue:83g設1͎=1h)2+o_y~ś}iE?9:3<#v.ug4S}ofx􈓭Y'X9!o2#Qd+[z#Ў%ZHn0>QcrGok(XcЎ ZPu>`tpf]cce>}v'+|XxWz~2՝J %?i'ݷGG Za\A>)+ݘr<,WWF!=eoqqQ%)B?P5x6~mz>=gU2"U Lz~ªu_3h/*LTCsF8Sq.m[U@,oaQኼaf&DiCt)}Ƽb陓FqM(bsΊ?ۂ\򢛣Vee۱+Vt+"K?FjFsZˌd@tZk"[?4y'>O._#@Qw'w}vק4wkNXwO(Ln{ΙY:iu_?p03q δP%t_xfa"LeyiTɬy gv^y&8oNY35ja<3}w\Y+|-];^| wg$U2kmG˷<7bϋ'Gn̚;>oֿ \17ܙ5z7^e{7mfy^hjfͳ[Y}3;FYќh}~_n`dy75~(-?s/\ݔrnMRvÏ~+o؜_?1SNN;[)j!7_Uɷ<7.W0/fzjI|\ӧX,!40mx[eTU}w]_x[>x^~TszϞ 7k}u#}P6ONC{d9W5/Yci`fsJP5mm#G_sʽj]dO(5#6{z\ufcc=>XkOYW5 {[γ7|+s0z "ar9ųWzW5Z]>f7*Ƒ zO+*nhzOmiutC]_G%3'[{8rራiQBsq)ƶ];fqcIu"R|xenCO\q~|S$} A_ֱ4/;?z'ߝYKPyBgпQR5BKA#ւ _W~H FAJڮ\}S 9O/4 ))+;//1'ϘEḮhQU[uHv=~|чBm(1S8c ۷xcbja%E.HەV̾O$l!:Ϙf_ouDHea)geZ3JFߊ'7NN d]󬞍y/"G#m"Y!re)'wǾ+Wm>JFU #f?S3@p눹Jޘ.}UOsW/JPnWK{meOFt0+&Ɯ6Ֆƙ{OV>wv+fK{FoܿՋFC{n,ڌ[g9k+l7^r vЇܿ?^,vE% ot{f#|miMzT~۲d=AΌ;?|/UB^ܢ[4nIZY77r>CtEA7c=zVbY2YV9(iZl߱k'[nt/s,J-wT.vE}=O27J5c'[n8큹f_iu^zu,[x ]q"swK߲#m@CHKٟhvM5e ~y:;!>0s»xyc06 #7zlCS*wcqυCx !2%m>󌧎O5W4>@eM#CI'9ϭN"_;_v?7~tG%M%,7.4C>iҋ5STBɛk>f`l-6s^mMwO2h4|mǰ?a,JP\8;1e_Yn|5dc)WK1lhγSS/c$Zhh*k1+gXm`N N_ڽ$ZxhͯtxU7u*1}ؒ|Ɨn7f7o'&ҳBZA+[6W0g+Ge|eog_3ɲBQ$e/-L.;a2xp%5 읺p¯ n꺬n]t\7czxZ+T "k-bD먎>:f-taݟe+wo?\<։u`O+'uF8vϭu:to;^;i['F,oKOoK댆- "៻u};:uBElwB[!6w gVϩ7ɬF{X]\$5܆1[^v3]?=ۿv4#wO/[ Y!5< 'w|+ydtgt0K5Y?^_/g& 馿i_'}Ow`}nd3.7w<ѓ{ _`^i?㕹?L>9߶ϻ(5{Jkݥ^Q's٤%=YܔٕG`fBfpn~[ Zݟ$q7Yj* :luf=LO!=N>Q%ըv4<'Jn%EwzLJ2G#|u|2~߁[o}]e3EM7Nhl)R!+m3[NhfVƖn{c̑+8}>ھsӭy.4݇ؕo$nĎ+߄c_ 1-يi4z@SUsg_nSS3^NkAi_̠nveG tv?PNtz+XZVV4t_A T>K>? 5zw \L rBtm1kOVGnHS\l?{,TP:D:ۅEA**ri$'ӠvJ%yd+|D,>1l1Ff/Q0)aY-zLȪVs|/. {,8^; d'K I|*t%\,yC4]8?>C\9QQ)͗_"X]ǾrLDxڼŦ.fNj~nF%4_|%Nb{j~M%]:a<rT|AE}IvGa )8hh2jiYD{)~JnRJ}$,1 m5lFs#~sdGtn~S ˻{ /|:.@*lwyq(XvzIӆ P3BHmiC-tFr*;YҴ*e:q֪;6錆]Q]I*tFCn;hËe:9h&.mMEP6ب At~nľyjtBhAp×m4Q; ͸] W|IFT1ЌHr^2a(G5:A"{B+&H5!c9_xpqޮ98No9?VWRnO[j\W~ޤoY_9JժÌ_x-,Q8=_~'IIF0Sv-qx;Z#ep=*idž N>31+ښKrW=􎳷_y5hcnkcs(WVcV@6h_}={#7fmXUBq>@7adk?~{5 Z6* ˇs5M䒶 %^~#|A>ц!'X^g ^Kf%ojD7>:l?-I"A\^߯i슍 MOdU?l]s(y6M@L9)u>.Ûe^9ؙhwG0lD4}ٴD6U Jn_28z7;If^BM8]_d?w9U%? ҋѨ5ZVvdw|䯊#oc%?J'HUpHJ'$'֚g>++  c2Db?L{1]9մi~U,6K)/)4Z8DoְL2z|N*Y&i9 ݾ>D8:{Ki/D!6go9CJWuM7*QW' ?~W,US >|OJA۪; ??vlвcR8t.MdAZmDP=5 5O'e爵9SM7hcӉy3ñ }r4GoyT}8nNڜy) |[e^_t}&)ɘ[W]cCyL{/vFít$6s?/fԠ'[ۺ!%isQLpa#RyO|(Txjz92pʿ GL2yR 0WjL{:*ņf+b>@➄|/&^mp[Ii$hvMQqfSƊQGb8*mdnIžZPGhBi3-:[aIT('%p]ɛJWǐ5 Nϭ%: >Kv>q~gka:gYcݮ%EНCDeSM# ˚FoJpa|+l4 Fkkjy,S|I|]h׾llՓZhOF^U(j&~zZK?24.wQĶGloHbʈ >1vH)rV)n&. $%%xN ?QeE YS_?5.,$$TtJ89#rJ$9r*#_ZRcEtze $OY%30p2bMt DnUsRc)5ظg:u Y2kξBr`,mlD2jξPEݏׯ[BD2jJTN .GBUsjuBe=4CWEruͮק䝞'z.EF,"5ZU۽OBA0299_%bK슭\{95_ՔĕwQZ=7TNSl|ŰrUMz𷯫gl额JދzտV9[E6_ZCuͤz5ۿ$՝\劭W%מ}4'fۊІo#8՛IQJef/tkl-z3jњoJ@[bRu' 6J> \sJe'[půxNɬWz.Wc7s }$/QXg<{D-7L-^`@j^Lg % @I7ŷ|{(Hf6uqXW/}X#phAƚFXsNoL tv?q Joi) %Q1HZ<nŪK5IO>0 3jacq+ V6p&y3pJtT_ز ELk:l~%9zRֿogo~x!?FEs92Ŗ=J^>0+cD4eZW!BEq-c]!7'=|÷EiN\{ 8ҹ_j6ȟZf jl]{*dl1Zjt 9pK*cڧyn4'x2LM>luk"6qs/W wo7IN:żfEL='6 .3YX<\~2h_~ϳ30kV<=8@3ri&A0u8{P mQ3=j^LmqP9_5+9h%լuWaJ!AGQ. ye~֋ċl'RA,/=/\D% ҚjF@(_Bz̑I]ɀީ75Jruqz($+xvRV9Z{Qʽ쒽Gem=5؋qv+ٯI#_60(xȞ{4rQpܲzQb!oҪ;% iD~T߯ rB ̀νqg$x2.h(P_!Bz(t:@ߜۈK9M :`S#5sN*2Bh ~ sۧ`JVפc/S%)\O5 p,sM]c&wұ骯R%)]z{u}fv]xu8uJeOgyYL^HUVc n}nWAtIS}򨈏k⧽ FI=nagiYwΠTeCL -@Zh4nBJ^"1nִ 谇_po[2g?#!sZq֡Nf+Ȅlф+:iuxXb-@XDd6N_R53ٷfL~[ڽq?~QY3o:X鳀49KW5ۦkaϭ |б.b2nW%k;BeIk>N 2AHc+(h7j6 k(ZZ' q&JLx셣q9 kfH%$4Nc-yz}Ʊz o5;^Ƚ7G^Q+6Sc-Z;XuXye/\Cf,x GO oS_A3~1֣ 'j<;l3T5ee3=*dE_Tn9yjKN?y^f% ʤj^lΆhߣa?:rOT5&][F܍5Y١Dm/ԸY ,n9 Ne\0I-4[a~kƽ88!+n l%ž;$'VB/zf+W&EW[$AqNrֲe ]Nהʩ7orߦCbQѦ,6(z&[!KsZ1`kf Q{h,5Y@0 * "8"$8no};^;XKD. C3SܪzQa-N>uRxwEFF!Ȃ"jzYs4?T˝ /uD*:A?B)pQD{A) |=4̌gq2t?&Oz;r+ijb [v$T z3;ι[Ls$]K/L n3 ʽkv<-s*Z2h.R_Ud?6c~_Ro>E<}%.ꯊ /Ero|oM Jɴ2+Dգ Qo[mªtk)fv9eIY: 1̵ͨ6O־39 %ce26<(~ܖUҡVtYAlaMZHurmuAVNj~sU#@A&ximf䖔K3 ľ_8,5kWWkTf BpXh WH-@8 llNiiV6IFzpQUaOcxJ1@dgƃeqF}_QGKɁ c=_yd 'Bt;}>[ոq6S'ԙ^{MAs՚'XNK\vƤޫoRZ<1\`;}I ^S#K<ƤNفTcc@nKvkRkIZr׶Ns^m22+jJQ]؝Rn7?! 6̰ I4gZh~۫kq6y~4x66N쫓^VzˇcBec{OWM/[[':S?թ皱>BK? sm~'^{< v);N_cyƁ:I}# Q/PwcT?,v?ՌG* mn:'Gyﱬ< 5^OJa P^ce@}2{c?2@|$V2KE#W}30#r*Zt98K!"2yib->rrEG?=qZdZ~¦yn06nkv+Җc4(0C@a&Di"O3uA +Ibp1PѴM=8=i/c*է743Ԍɴt$P(C' fY(!j* ^bٯ- ݳf++W!;: Kr!TuB+PJX}s :$cnƯ>p`ǿkRC=ʹT֋DGy;ETVX FEƯXpdڟesqБN(4ϐ"CΩ- 'k:rӴ!E /.0p\[^'MNJ`UE\4{=iyo ZIJjq;y9f# g5kl>Sf ATݒJ *~rʉB}ai͏waP".-sV#B}ۇӚh*`WT,,m4~%SngdVlNkR75 ?AE$-pZʙ ϫiFT&UU,NaCn%sbBGW"p]׊eVT;}.XFS'Jt؃Q7uJ\sǐ&@}[ϭINE>r IV'Z3꘎=k ^zW}5Jw}?ycg{KXv' 5>Љuj?m}="i|nfz{Ti-'FoYN8PޝD!@BՊKȨ|.MMYEJ->nNӕoo/Z1} 0堤^kϟM^8ը mD_@oѻNQ՚PK*qKE[kpȈJj[+(ċ3zbhASWAa~ԠhFN^YgxI=tG&+~W~Eg[YYi'kZ~~}Ͽ}6+{2QXSEhcGJLl+ﲿW߳ɈGG5_A&@3hxHU/X[.Wƿ-NAɒ^J/m.y!;gj@m8%"Gҏcl炥2C"7ugZNh6"=C4LHfKHZmZ`Ј$^yHCh*(B`+)BB@2'` ڍ!ICȫs$2֢QjR@cIK{Tf=]Wυ*{/qSMefMϴ cΛ W-i|0c]_W~.ms4A(,DEsl1.mH-?w4a3i\oR^2QPr$i[J;S ΥᅟrhTIUW),\(c&?Sޞb]S/I1ٚWDW?uj:5ҮDy$j1ٚn-]:a<~$Ȧ5Ǽe[iWڤkQ̎T[)^TŬ;VOHeݢ{s${V8<e:sx+ÁAE|bչAnOK2CS;GVY;6[k$%>^qՠfΉUl15H3܅0y>;Z䇪V\'%T'\^H,CSכwVWm[Û䖚0>W>jnn]sda:qux;W y'7+hxN;.9s߹Zc-̑ix>l3^;.9nҩUw L>Z]\ϑix)F<\JK`UwndղKI@6e(NAo=Rׯaxqךjj!#fJ j>?1/.#:OY:G-(qBg$0(ƯYm]Kw~cgY>/?754e ,ϡtpǓ!k*5j_M -;rܧh~OR'?kʵ+l~ATDrQo$ulyٌ &KzÍr //5hQ?%g /g bE0[5?ú(4]o(]82]q'`A Av߯~vCzM_y^3 $7*{{!M2vBvnJ'l$CFq :,F=^}DCv&nnnJcUGSr_9 +Uvƭ1+ Q 2\ cnݡp5bHA/?~Lԍ;>\ jXjbF z+3-4-/?u4&7si 00:׭|JS|Him/ﱇ=袄S&;H$&Æ Z&,wyM1imKd(=k\[k>r 3=*ͻEi$&O_c兺f,^{g7.zT,83!ő;/ך6be2Z-iuS8 I_ȗMbe2([-iI#0AȽ_nڪϽ:ZF2/dhtV$N-Af#Ւƃ~G8G\p0᩼Sg~`ǿP`fq66,]w$uJOÌirArq4B=3WW5[3n5Ex{,xH4=&[?\9pNj択M~|\>HAXic=GO.mMb=il"[IE'N7>dawYm Iy4MOo:O_K#[_řƎ6~//ۧ_Қuż=:flr~ =o..a`FYAc'G'8NNoy3n{5_ƒ{Rq:!75Zp&Y*pf˵=ݱ_~+qM%6Ue$ GLU4g݄by @|uMe7H@v!ңC&˙0O{: 681y%I SK5ut؜m:(dI_HXՁEįLAZdᑋW^ F\^*1 sF=Oz],*QH6/>5sGH4:}‰^<8薷_ɏ?j XMyi V-ո3ܬ+Gn/v7Xi~ږ'Bql6˻LA5oTUfkj[iòghW5{.Z_#-MNF \`ABEs @WqB Yj"y҈9W+{ |Wߒr#:ѕ{Zhkag#jշ)th=LCW{7[%W^ VI4\ԏ:gfCB# ńĎ;|l=a`EOxh.MFZM9w,C»,MR;ܶGc OKFZM9xhT,D`crBWY';7 |!\0ZL}5X&c-Paڠѡ[nJ!+ڳ89ZoMsgMs;%}7=`EhJ<pU fE寽gnLKJ k0gnOSܢДRt8!_TmVSJt8a\h웿硹jj jLsI8%eos4i=XnQL:7d9!}HG4s׊}c|phɣ:~Doسg{4:7Tp>LiN<=&CJM@4zF_Um..7j$A&4h+ͬdUvKU_*.=nNUǂo~3gɬ9ڻl|U9QdRCơJeN̎Ҥ;hr_TJZ;f)͗5,PlX.o=3L74N1'4~Dήv*wьm]C\ mO] 6_lXs=:9A oEe}P#eAvrLg0 v?-~3A7#%q2+Ɯck<|68p|sK^m.^w]ObyEbN>jG~a{f&L8{\hL!zb}I,4l!ؿ@tZijȁFBO̩nd%[и$Yh?A&n]ñ9o_OO8gƬuʏcЅpnIW^G۵G~6k[n&8V.ڵOl֬~kݺMc {wP7I2W^Yv-4)927"֯?qZ|:d/~>Ό@S]K`X%i3|||Tv!Qyk@gLu4c KZ߅쿆qPd:ԵKʒÍJfP5V΋7ĉ'͉io9K7x'g?Hԯ NL)x@ 2 2_GMo_aYKys+}nJ/;,ZMVJn;ZyI]N,>4jdV +EZґYdSոiwj.1.^0k"/T,^cjjک2MO'=ǿtԬ Q<Boݥj_cc;\8@yJ~kO_$g٩no?yhvT ձxii6JBkeeJZPyh7P$ȶU x^mVPuyISK{%Bk┴F֤Py[͑Шҡ{>J|APyE*E _pSn?>V{]50BijT9Zѵ]]zzk10gɑ֗tEcW=BJ;Kt9d7 |hsB$L=vn<>{!vJBm=~ŵ;c>$P+F.q.hvTh,!Jw N ՗Nט$K?iX}M ?dtOfW5X$5U<5>*5X&f;u~0;wZ&Ulɑi&axl,IBсr6Jg$⟓ -KN~VwUk氐[r>9PkN?A'Vs]oQ,e]}@|U ;Yɳ9NwNSNxg92z׫ :,^^r}cpz Y+%~ N%OϿdNKu!^~m#έiX;UTBiMmsGSIJ̟#842aHoj9W-FGY"UP蠥4]=co9{N%;IȻ 4j [6}@)J \:c{_u~;μ)M~YS}ȖOuCRHpD zm8gx}v}!Ӂf? O~![> BBG`㏺m|CnQ,kl\ہidVO4~n4v̥ȑsh ?3c3pt`䉙G^ /a{u}[5vDNW/N.6gH){? xW̠)%9;<ҜtO/?lDիGݶ;?oR[ʍGbu%h~bnxȖ%Q78g[~/, uh5cTa45c#?Dhχ3Fͯ9_S on ϼjƝ~:/S}Ȗu_Z m 8_W ʯʌwwԃ7}UǙ7@xOqlD}kS̅0?O{uskd2zo]38*:xr~chG=-1; W:W.K7lLRcdHjbo{j\'5ޗ8 ST=cAY,Xզ7-ϯYr[WeΘ:|{dm\~M#(S12owK͞@F wU߹-I} 7Zx[}f糔:Op}ͱk7>]o ׫k&k9ΩRKg<,k!MT^4 HIiBzcbL˛t_h~%jeL^~ܦp}kyke]vz-?v)i^{;eGx ?BF- M*͜׆KOdjÎש_Ix?O j?tiy>,_2/u-a~RdA,t&5^f-ut/%?'G#gO>#xgA7t]42.%?ߟn~Ks]㎼v"D[?Ac^|wK/J`m_U A"JəV5G޸>J_)0Ndɢ3K\2]Th?~^]ҎΡSqM0e_fI?[,#/ZG 'XkVOM;ƥ)_)0 'o1HNv4L uVN yxeop+g:M>5aB8gw3fVa鬐0[wv;vfnpv"L+'4«8XfԸWs|{(22ep+3ztc/1ax5崍{n*o?Ġ~{ϋBrFv,l+'LiVpϪ)eҤh 7ilc?b#oRs/dm&Z2%\cGZ8,Zv񅓖A a?{oLtc/>0?}5pͿ[zٯӥgY`L;"L敿ԙN59МtwH3غxm>Ngw8S]3=*:EEgKiN;4:$`뺕xg>pNw]w8^]3)MW;\37(QdfeM?O37H3]7xrC'+s֮;y)-CFO,Qsҡbg[>`#?嶾qIj!IG"9޳t]xndhSFwE[e2vIoz?gc6ŔxB>"D,rǬm|頊汍M1H43|m6GwPEĺ[8Kx~8cճR>SLsr(9X/(>8 o=ՋSEˉo{݃oFdpԐWbBG7)僺 xCl6P~v=&ƅȻy|wW`UէGwyy\sFH__Ryş|iXdј#ε'zڮ~ɿ!5:?B캀4QFY@LՋ?#yӫqjsaS&V#M !F)&E*O@#(qw.{*mRߜA4lZ!iV:{ɥ>9@W%cM#>Ɛj<=EܽQk;|,CjXȠbon4lRZ)کkDw%'Oh=Hʓ"%Zl oJz3Qqlgcys+ux}4 <ʛ(b0T_?V8ԟX=1ŝM3jNhH__Z}W5Oب7='Ohmo4a!-c/`QzS {'VkL!M% iֿ0+~:2Rxv~Тŝyyg.} !E+ciۏLx[~ty):;^]_ɟJkbRة?z_}Ic2v^k8`)/oz] 6؊܎)[?25{{ܒL*'Gc|=cP tn~F%&%F6vΗyc$gY7Vg>?[Y60hϕ-GO#X#&l(:4^7?ڻJ5 6|: ʖ+ C=ֈDόw~ӥʚǞ+_P &iӓuK;2VN`å֕-*ԾHMu=\Mѡ>NceY7Xz>Rf}c%iO͗>eo=r81cr:ޫ婊z%&ے$"+i)RqPܱM>Vvx䣴>ΔrZ,2<0,lu>תZLgH_;= 7n6W9x%VzHoLp rOYsx  ٘QS.m:dCJhE  ;50PQy kdi: =N,̩s)oz9ֻmh@,'DfuzNYel{>n$/N8+wm+SP߼JpsXϧOZmۊUsr{C$1fȒT4:E!,$Em>oVc YWx PwIj1sw-'FCbD* -x_ J)G:Q}Gtai2Wےي7rè˼_沰\.4,a˭1Qa$7pP8=҈ tE0ghiL#LJ 5~`f4!-Νm1z ]c]LXt?KD˘F.88u\%Mo~SMGJvvִx:ywuqi?G8ρ+F7%u4ύ "\t Us4-SCRӃfW'df;a;펝PDWL!m Lg\AAJ!VF̛ te48;(}㭺)@RRwP('h$w᫿qJVrE45r558;(|[{#mL+aVRn,RV(#^[49 f#VL+=鮼tǢ䴥?4'hĤhRy&lGo!*δ1 rE*K$tUքģno'u@)[[R&lGlfh45"IXE"WeM o.ڽ2qH?߳`#u8C8T_,t|r+,-u!K6`57bW4or d Ĥ 鱯Q?D=ZV+g3",hM^R=+S8x4("(> $!|Bb^oj\#uWT "gx6$}=Nvn](,X{tMU0է0}ĢgxeG<{~U".l&r0<-6Xs#1[ΌOwe᷿2Wԓh/o6e3`?h/:qBn?gwVf3RɅ/W4}"g#O{4[u_Q1oBv xqO~y OOѫFQE gAk$/bD*cg ~ʘx֜ZEi(Px1YzZS%/0"o(c fA^i40#G~e@vT;AF`]ʊLz׾BL 3s("YHHA1ifBK4חee^8?o8-.#P4oEW,,E#QfAiج%խ$pf}EyInaAhMZ m0"}8;ML4fc'5,F/$>/g0 d@ q.2 ~lA$H0|<0zKE7QĠYV,4>-O+CztDZsd%T4IU8= Y,ʬE$hr|80qi â<`owZjB-%,kd]=}eVB/` >"%^UaU\qҐа$dLY*Q<0X6*h|P$p dvFˡ[#1:B$d7M;~co*o;DJׂC"YbT$cr&1;b'ۏE=OhoFXd ">" %3Re5܊q lLdgI=627rKp^CuqS06T+,vC8 ՖvbF<}J`LMAoT%3 ,k=xlaH7Mh=6h;m4! gX%': {?{ߛrk3%G F;Z35h!zTHDM92j~L"xY*p֌qs=.?7Q{Dl8-R; y_zN+._t.-wͦ{_ b™)x׵gYHLՋn-s('T^F_Re 9 l@Qiu%f(G0X#VR.^V|#i Y+TI[`# (A' 6OLD9ˠq }WHcm+ h\W%%qaCy7դIr}f+7v,Hi eT0:{j_, `LJu}CA֋?O=bMվcW;ELHQ(*ysNF%}M uBTS7 [:1JJ'5 )Yzl3AY#2c^pφ s果pMuEf\dȎ H1pa1k㙚z&R7/6~/@70$\oo4] 8kȎR81Yrd>[WR`P\S',5dg;iLS9x*Ɇ{ xL]oSE3dǺqАjgP!b%2hSQu z`h'PLeh7\aGi4d{G3,UXj vCc3w+pߋ~ȕ(A'1Ʌ*TAEp]N >d`TBbX@HEYERe\Y1eLK:x(Za#Ga3_2]e%,Q옆aC+.Z KI`o*^),-a>'u0*J:jNr94Nb~]3;#۝DV;=wZΝ'fbNΕ99iC!QH4Bb^c_zU)$X zeD!UEAX"&^b2" 4AV\}?k~_T 撚Pּ _dp) 'O9`l̞wg_q&iZ?R!1)%SQUFt0'?`ҏ|SFWNV|W;G6ڊ~=h_~j]" '+BiTB @9֟IX.b b)n[_`IL=PIU[vAyP.RZ)?L'! o"V\K`LAz@l^k4cg.*w/K`Ā OuT8*\yrKdbw&m M 4?rFiY#eDLt;&MGƋĹ 2?\Bay1S[ks4fRYXݫm4xl~z)bu'kY)_['Wɓ?ެJakL)HwG&4\/l< PZaOVR#PF;e"bK0(2l<(J+'# պ*S6fDӚ3oeIl섥1|# P󺓉,rʣMvhӔb ]eq,18r1~>1< R%"n:% c]`4q:F Isנ\žu2%[b54q:؟p-sh6Ϟ:B2uJW m*fXGO|~u%B?~ }|ˀ/]VGAP%PP0R H9C]98 n1cFKmzUۙ܇z@j2[s?8JS$rkv2m@Z/;zlݻ Qt.b8b^zBlQ`WQ}IIOܧ se8Au0_L|Pi`P$ rkRF<;^a4Wkjw*"b'_-)?`UFJ|lDp]g'~oFIzǾϵ%c߇UpBzDAyuV+R%Ь*~!]2w&+p)DA [Z}Hڬ$%à~ȭBdW}Qü ~?d\8o#WgddE\ ~rG'tъ:b)M̔y孑dk6ƆFjJSZ5e#. fԤ h ȽY“ LHAռʀ5:v%ĹJN{R#n:m"HEhz۞-m,팕_\}КɊY}lmnO/YTY5biG |ASShCv)e!ia.%?x4"# i"Uyk@g3\þ#H¥F.itJ/3U^y$ zHyom|5N(aߑHX]mNOi+5&a#Yh)N:s5fMWӚ]ߑphS_8h%{+>gzA"b6`+aJ`%n&RH]qIR8qV$k}A2ȳwZiΔtaeem;&L=OK(x`[J7ҸgjWr$F7F) f!7lZ׾r$JMHAQ4oش4QEÃK4&ݨLTǿ~X|quAQ O65&ZhEA~<֋{͒flI{$ R% I4;yKEo0juW!%%g@DH&&*AHBduj-\O+k%*φ<xYʶ/ie5aZ"4QLB(LAJO:Z(JlMn~&j)?h.ux?t5=l:v|@ Ѵ3zǣpʿaVZ-h x4AHJA bnZH˞X X@ θBr  ^ <|ge%cR_IKkRT͒}wKlI=yz'rN-ձ8@'JlAYTXgBJƟoYd(T.u_=lSq4*bݘ®X",b(P̧gV^QDorjz٠gB9 R =9! 2R8l!RأBܐW&HiF\8n'>dr Jl;8zD=Hh+R9;~#+5RlMn>岃o+mxͲA6Ž1>p낟eDc{F=+(wvߵdﻟhmň(=a@,!09 Ri#2<,pH=|nw\]{r-FF"{Pju^b3FIã:5[D8Eb9 ̆AKzf}+ X!)`M=KŒ"Ks]|#~=EÊQ&ģ r8=溨@?H \ "/5BE uFi8WIs#-Z^W9>יuF[%7Qzbɮox J}Me?C )^wd*}]>Ǒ3b|_?:]yj}zNjv~w~᪥m+s>¿r nGZ1F!A2Snȗ[ݿH='G+k4{˟m^k5r+ 9[<Қ>^sR'e||/fҬ~֜ *7Nom}/,\t9wrH|&SmzފA0t4cXJchh021@t%F0:*fD)HgTi)}!I@L Cvʀ)0+.iy& w[0+? @W7PN xAUZ t[*27QNd :UF!8[{ow|Jm|JpQ uX J5wuOuj7ǝ}+l05x/Tθ幟6 eQ@c`+2$I)V>7.90;c * $ELΓ"`VX7G񄮜{qDPr(,rguLP{t(+ܵ#BȺIMC ^}TJǐ,ST|=O]nO_ Oidcv4PJО,H3Ek^rč>OjLz`{adv?*ǩF *A7{oJ^@uw:$ϳ~)y9u>r`nт\!( G H= Wh+[;"rT jǼzُnAAGH9?yX G FfpXDn}ɧ뵙S\ѐOԍ~u}DOSH{Ļy:#x-w 4WÏ)Mr!Sq?߷b&c&8n ïT<(R,ׯwIio7N?˔:#h3$K҃ڌ%4OZmGV+r:hSU~ihuňa}H8>]P0ezҊvk}]-bO^ž?0]HۺCOMc19Sl c'5eU-wDTE 8Wq*vn񭷗o2䕦ٔ1ur,U){̫3{|U M[S!o߇K!En}i?5x|Ӛ?Yo=}dLtG?m:Px`۹oTu)mxiи@^@7i[XQ3]bN)HFE6^sF;>óTN."'taoѰT s␀Sr5ǚp떑2vNcTZ(/ۗyj%9=se-|$n|xu`OdjƷ|Lp`24/;'C`2G=~}mL}n? &KC f{I5 @H d'7Pf4l|zoPo$"3$@&]1YRMz+9U VӞV98D؇HrKK(AX 8븠U§s62M;HCTAcN~g90tܭtzlyff6BPD׽USۺgy9G9T_DʘS圻Ϥ370Řc/L9۝?& *[=}Ox*_d7^rN}.g_rݭ6ǿ FyNJ!X l,uaJ3 7ӟ o@޿ &[ζⰈ r2!*u\)"'5A' M~L_&P덏 Bz\(@T Ǡ{p)tas˦ZT .!" c%rK904u4u 喇I1 (CdHK kX|ݷ\ #*1vj\=\(?L yfИpT5-5eya,fz}5ڌ&9 kJ.j\="<=:QqnE :8 ܲ?wpo,[-S \Ӱn⌿69_<=:>ᨸU_}=n8^{ qV p o.[#_<ܴ箎̌^mۢs{O{UpMk?P5}#\!4nf cUR375綮_vL DD4$s(?g'muzpCP,֣ܳp`9<G;No>ON8~x_IMnn\ڥ<&7R#8qq쟟Bt'tB'cRr wm|~ a: nxco J95PԴ/#/YyɛGs(aIiei78ѢM@=䂛X<- #0 Skil~.sV%+~oxѴ}I'ƙm䉟fnx}6F̰(hJNSg?=x lJ\°9_Tj\ĕ+~}m:7wQˆ)(EИh]mbߪlo5%?|hӞ'~SkF---_L>[-gVbrkmOP+ҟmVZ`D $n6<Йz`R 4I uH~zD)dd{Gw-{9fS_ PӠVg<Gƃa^qG~ٸtkڇZaPfv @0 b(A3Qמxĉ>t*H*D&CI艇ϯ?$#% D챶:q, rpvn%L S-Q5}l9=1ۗqfl9#E K"yC&N{o[Ep`+T"Ya#qpZ]?3@S7sK'5zq)Iuz5 ;Nh+R|Xz( @^W'X+`!` W`Aa}}sJߜ~Xj_tz_?ťfv..JUJ oЂ$pLѻNo>>j)ҍB0h2 K=8mĘ ?ǖXY/4{{,y0N\QׯcnRjAzS KK`ql}θ=ưdHg[-BZ\LIY;n#{Ƞ|̞ҩ_pbmdQSBBlU6CG6ؚd+sƩ Wį=Ilv/KXMj$;JHUM|_eBHWX"oG)D mE($r)X+1haD EX @(GȰ8u}rr[WV kzAt1[xӓ">HĿ}WhfqEO;besnA4ACSFE^T9;4}8fQsynu7Qi/!jg;bL1[bZ&+kllbW/yW$̎I6 vSM&jrD4vrGl[hr6!ӟglegf4k򆫱z@@KolE0/\펶]SA$q#B[dI6HhLgq6z;umup>r;tԍ\g G# ۤhN(6- fyǏ]a6M4q0h}Ln-t p{R7pm]7~1B[4u.w=ERg%7&[2y ; !u٤31%Dꌱ )V.i8PrԌ-D@ܯm_p*g,^#aq}ͮo}jm7MU(.tv.r=2ŗ/PIKA{]o y)ĵWǕs5ۇӖت>º'|a$̥⮲`Pbn'N?_oqK M>_F>~3Oq0Pf圹ן!#O,? !!{DsOȃVº' Qs}\I y֮{`}k*Dȡ82C`D$tsg{ܘT͢+0)eEK"`/E-$4Kw=ye\ppm6*^-Y7TȽc: 3:.w'zUQsƭ*<8->nrI:peX-π e`+-et}^'n/ hzzzA3r 툆l>pXuo{ťAVe;%hZ?ﯓ kia"~WmAp#X{z@JQ6h W/Ҕ!&WӬ~7MIrMvV:ZQmn3rj 4Ru:Y]Wʦ;{=sfަw_Cf;E`-5uL&'7\h,#̺C#몟[UOv|Qu/r wP&xL7W|mouל_g߈*7O'~5OYE#&XHQ+Dy)ԬHPE0Sh ou FBewDP0^*|ϢDaҕ$JZ X6x$!HTz\[!0A,A0;Iϒ(naT-+ ^[*7 e+|U5v.?W30% Z+yqzq\ (7v^USS} i"[>@/ߛ_w0#-pCt̠f[ih7"'YE0 _?"8e |㇕FqI,lLLg}_5b.S!YZ2K)g ZGE+㣇}bzU6'M;tˇ+5K5PfPh6qɪlxĻTN^t/|4壏] % %%:Xw{ԯsHn1.vo^DB ;+?c@yE ٦t`^/\"6 H;vMv_B_wVgdy"z$B oR<^?cc^OUq.UvocM\(ENGSCG'Oļz5yFPyBm n:Ia䪾Gמ8rCIqg=snwĀy Xj. |$[`FKZylmO*_m뗝p )Hk?xP]/OU>JPe3ʋы=Gn { m*7O(mA-8ї27۽'G"]eoyܡ7 P6m?(rgn8Muϸ;?qB}]z3Hax!ϸ3XEԓdv/_ocJ;0'>R}ׯ|4뽹@uotZKĈz2 9U=ǧ8l}K0\H:ʱ+0 A rZcr>[`)Zytq_}sѝl 2򘷼9P!o֘HA@P9g٫B}y*+moAu(zD/\!_6bWr[Sle;# K7nle(஗I?#GNWT$0ȿJ?j=DQw+l;ӯuWHr_oכkaT ~߯?+Cm[:| _*X08v=Xqzg9n3."&! g#(;s*2̈́;(aw|ٸyU}EMU̟;Bvqƀý]w0 ??ӗ:7tHsfY_-櫪ѽMW9֎G~>\mO̓L<+*dOwu䷾`$&vTlrotwLȸk܃?5kc_9&F~MQy4i&{6UJ8s Z[6'2/ &\Iai8 D'jӔR̝#.6֑x?*{}1mq/L3{[U#W95>KQ'xA;_|.'?/g]SxuˌZ3p'sA$8HmI{5Aj9O/xE$*f~]4rE" `巃,iOLTWJ}(-\RD`)jVQRNRtd!/c@,0N&[T+3`"<M,)pΰ#F "p̦!!gIIĽ qAe^1 bĄ$KxHzK$tb`@GȀ3BaB Y3a,hϊܶ>?=<|fyf;oSzV7L˰H&i2|<ss*!ǐT, I@I^;F^,C#޿%\hy񭕺~&oY`4Bg} Xh< /z瞝]}*x}&m |icϜ\_3H`\A j;|^3ĞNKTGj}*g  8~ҒQM4N D:ز)sZѺևh 2@k@fz/`(Lprcq2*9 I1dDb yv IrvF(uYC "46)RiP 3VMjva$HZ}?Gπi Ǫ4) {"P$C7 8_{n9eÞiX&NbP< q3aX g#7rWw42lƪcRHB9P"O@@.R*z뫒>+cM,#f Z4mcW,slm`~ j̎v}3o~lU`Hqex}sN@8"?xʒ=e^Q|븲v;ȼ ]nm_гEV'{^m1-wi)Sߣe[7z&ldpXuL#O:}NBn7{зQSEIT} Ym#H+'&5Z['r .BHX)ƥQ S)ZI>W0+]$1l)O|n1RZt._CfckmuXØu+tZ;% \:[{nGhhұ'5o:Brv>t\ofp+4z)ol ^蚽uӈ|,lᓪkeFc^bIJmuڋڧ-Q.oᵳ[{SN?{ A?cY^r͘dW!yS .+;:/q\ ME"0q?-7R2"$3hXw۾̱8dG٘ zTt2mQ# TM,i:k[$O>=+]o[2(1*ˮEwx)zn]߅Vg9r5ƶsjUJΧKn ӈuO6>8yMiowM?}.=""Xs-m5m`+8AQq%7֏M^>2-[EpVZ)/BW6ZfBI(yCewnFώI3%*=#{nDBn:]5Ya tjT/|/;yQœ ;QR&R(Ca!W[ܸ 캦S%VtndnKh E !koBU-у iId\؇CyNQqRM0*lac K1s6]yC{ƕ! ɰ3m"$[^s9kӒdUfS˚، 4Ar[Ee%7:oǥuDL oPVr5Q#ۗL{J`D֚\S_(YoL2 +&C=?. u,)߶=SDR9 ?oPGU’>, T)|܇8>e8@Ds{d5ud1!&3>ߌ8D>5/CпofGiP~B,=3%UT7J<\ ^&G F::Zu=G ! =P='ĨjAGc*W#ӝga=撦1i z?< 2cjk8 ĘDA o$^%Յf䅙O8#mΩr (c:l\i5i7Q4H8ݬh0kI8!WfE^xB^àͫ) ICvl Mi5H=0qhLH,+C%1V&BF tb/IHP" ̦leõoP^L31H.`< 3VR7 @s%^̋`$" ӠYdI}DpBkjdqaԖv |\%kh2FBB $#Mq_V :7K&!FY-)05phuQ!r6Sj'(pLH$jh!, } 5H#(d=;ZdF{'TAa iY3=!)VJ.^ 0`qʭ#I^ MZ)"CFIx,#', 5^S fȬP_ٹ \vTJZr3j߉^tzTTC-A"!IBR1#2 F²u[1&~|@aZiUj2R)?gq?$a[>`8B#%>[R`5ʑܟ: |*ȲpQ+LB))%{S.gg`0"h'A]D&1-'bFܘS|wŒ"q.X2(e &C'CUV8?Aؘ` (shTk->U/*V0Rq?;T< J.^ Q nU%na^+a@+N2]TĊ|ϣza (Z&*"b&W|X"`I:)X9S|w΋pbȪ^8pWڒF݋vϓ ^;aQW6Eb [Q ؠJ2,r%@Z5#0CVGp%,OQ*ޥ5~љ*p3 &2$V:aA k$AeDl-YbJ )̆ecT(CHsq@CLb&6y֒XM50Ar]ﮋ8بRKf0O/"ڒUֈ z" @LxF 4S Q!k# d $R 116 aLQEyjhOc%q G "&7|vK M<*:K ?LWC>^D[# 3@ )Qf=G#{~W &f]{Ώw3wA+.Sz0gRs7|4g1Nڬ_{Iu\7T0c#8U6cBZ!&9v{C)&\<"$>oKCɈm&<8 pb̡i:{P8}raKG-;0_DڬN XuC8sPasIwPa[""4^lB$ EL  㒄kA?6qyJ=|.+ cbiYRp:F>J_Ļy0G,:- 3S S@b9g-x}}*^Y4W)6(m0 S5 x%(4^R+5Z SHTؿFL$HEu\T(cN 'ٺ-scɓ֝qi siDTEq䂔F\ (O4`fnmCج> r:{87VU" c—ܷH±- ȁ f,DIa/Z4W*Y(mXZh|ÚY؊ ,Y1""WY,\2W п& EńR)YB/-2%"O< HFR'V)TϢnh.$GAvcC<4`4SY5_[hvx1Y BC(I Clx9U.0)hO<2)F2 Zzr֧EEmaĺEnA evL6/iYYm%^jFjPL^$+И^2!"$5Ao!H(R[ 5b؜=AD0ܹ"HL!Q6;,f.J$O*DI/4gD"!˦n11 p>ɰŲig@SB9bܞMɻn91O].|o&+=hl[bJXPSA+;J|,U!" J 3tyr"F#Z5 a1XI!$+x}sA{T-O˃qeRݫoN3(.iЁO.twd{>_r+ov{'ea; {RwYg򸈎uqP#:*cH|˓~, 1[ĠXTf2"XQmG>'K3E1+qBuǩ z-#D[`W$ R.|B)O rl80_9Iv*m+@ b@##"Ii!;Q7:͜W=u-Жq֐XCH%N0zUO^*Vpʩg+( +Ѷtc U iZAMUjtey#ܸ2CB 4IҔ'cl${9m,#+ZPI7nDB!ae[wؘqv{F>_|a?uwY7pCGmb;إA& M@ <`@ Ii^IcUX°ԙ5H+(+H"2^+Z13oÝNכhAWWrŊLz%K~XnѶtmuSD AgՕS6\ ?bA(ҖpqWOjLZw!m'd hgnk#ˑ6uZљ5q">|ScN ~Sg̙M@R`KvCU,UqI1RBNO_^׸E˗j:yND [hG0UYrTe$ bnqⅮMlwWbN9KHo&HTHAđY\ _&d,URYKL!Ґ'm^mrh9WTN72b$9j9묓 9g NL*yeZC""3y \H5oD kFdžn$Y3Yث>@dXdq~Wu @#@_|6am\+۫oɈ_FusH,[>r?G° OcIme8,R$1;gE7C4Wa;nН2f9qkO@LZoq$xi5)a"q+$sS(RN,4$O"  I@Y]d-D6UX\;Jy&24\G%(ηѣx*'pF'|i-@*">M5eJ-sh<@Y#?k1"?h((P2Ĕ |ZC-ފU~sn_ cf)i,~ xeo.kGKZe7c@"iCA#PjD(vIX$$d!*k R"3 (+ >M|CY >+HU|)D$&""a6uؤ+I4 yG$!@IR?L Bsq:uQyWߖrHD#6Ez\$zvtෞ$<zˋ#LyB2CFwqVˇRt#c^~ JMxe=$(ƫ:h^aGT6Kd[5Jļ\TVBxwлKIQP\F&HI 5pVg1FؔB 9ᜥ8$ j.D-,-GHMiXit,Z+Ų@ !PG 3Y}eۦJqUtP;<{xAw>3vTr0zQ-wzw(j$$At}ͫ~GߛIp(. "("PtO=o/X{>yzۯvL|@q8"SN+ZyI(.B٥%}f%F8C)X3ùΊoӿ;qq7'Ɉ=h%_eڇo#fa Jz% ׷w\D{:/-*?v9` D'@`IY NiZVlqe/ D5mM~ dmFMcJ^ڦ{lD)r}-lL 5K K2l iٳ<ב;˸=,w7 :Q&຃=/!_*ǾK' ^3陋3v_j1*"_U߶hQcr7}lʊ ״7(.+ڸ)>vve호JB IJ= [m~2F;l{u}y6,i}ZpGh1 9Ko91rN0l+.$fB-PS G؉^|T+=׆~#5ܾ֬W 3O/Ʊ29G&WWMx1F?ebO<1gĹgηXoѩ dCn>t5/ #_p}C(qEvrge%]W,qC|컶 U.D$09뷙p5?ˊ]8.l6_?u42SQ]0Acou}鼭v~˟DmkI r;¸ cBO脢8&viv[l }w["l]gE&G-3@Yj3G(w[SoŮ`+J7|7y2{'~9KoQ9v72V' ^3NM&6(?PO,_.žTŗpY{_t?oS49b5kn ڸ%>ve,׫{}dHI[QtN75G[!'q-!èZF.͗ 0V{O[BHO2i60=q[ɞCKSpG7ꂗ]"&lj׸ 'GǏS*w>Wqttp{p73`G kkOAɾ'\u2slo{9jm G~Q57)'>W4:<~_|U l\]1l(@fw9smAk,+ & h30kx 9Jb?c:~udG#J2mQӄշzʇ4q]|c4jp;n/+?o",6<ǝ&V߽0vkcm)1sR>}u?l d30rj9Aw=J.|ǭ6_e7I/R>CTԙ}ywFɧ"㔠6ƿmҺN(Kn^kwkuc v}la{Dd,Y:1S8$UE%h `H(!ţ VkN8E| JMRBv'0 (LJYoZb,VoCJDP 'P8-\cEGqa01 DH@<&|Ȓ1,50p崟搉~gw_xW9gcux^![^z͙c֫*y&*W竾Ճom8#vpߙ)Y5MlPIf>3$Jk=eXVHQ)ƬRqʞ:.fzef¥$oؘr(z3<5o]!r pQNꭧ7uGqv>~=H 竿N.d=ZV>HN0\T[)#S>$pgOzPil$V'-?:\ޚL IO|]!hѷO {n.MeGt&>B@كu^:MhXze%pU9UZ֭4=Lsl[C+->˄Z~&1i%tίh.5ozGbP]\Z|b[sfX e=gIH.T"L؋ /tW~ӗQ$L?`J F" EZEY4a\YO&nnNhX۬[ [mĨuWxG(<_rYV<tw:Գoqmo5E<~J:u㯺yukC S_`oʲl[=* [czێ8 ;^U/scn|Z 1R8Z_e>!]~Ƥ*WߚbG^F??9޹,SBȣQ7;Aβ?ڞw잂p3;S{+G F*z}ԝ5"{z+#w#'<&?DJA|p~A!&\Ou5=vf@ or_Uۏ`h;KJb}59l!"$xI4Jg뎺lz+do9g;/WTHaGTȺ즍9Yrϳsםs-1|'\~Mw[茆 jl^z`+F.{PxsYfV& 9)c:V!}l{1'կOȏ: F{(K32`wٺmr_goҧSW2Oȱˇ!k?O{%e{L ʌ9Ǯ3'zȀ)ygZH rC_MgWO_pK^O>CQY).C-9~ja/% l(C>Ui#f_RథaRR="GX%38Erc>sD("5{E[a#mU".#cÅ=h2azzIdiHu8KԔ O)xZYe[҈?au g3o!"=vqQXdGvd}sz+ϨY㣦^}\tU_9C9ӹEkܱK^~ŧ.Ӫ^Hw"Xrnu{x1WXrʬ%G ΐ%g^^$u]g=ξ~O:v-G#\dg[`zn lK6 Dp(jGh9Tu޺śãǷ(=C?d `O\8{ujİWXҘmzw )CvTnC}2r8K!c{u[Ny#\8G n%/2^[rN\30ש妜y7Z^ޏvdeCoL;hj6(Ύܣybə4bןฬ$>ې #&坎 {O &{95>-\}?{3\gERB?wW>ߤ?wkXZ2xHƳo!k_Jz;V[- ?0lﱶg}G^mso..G;sD#Gi|s?ǎ{M5g'GG<9-YzX;ֵ]ڬB.؂q%gd>;3S’WKȗ_}©5r,9Ec6I]a<*ה1?J͎̽UbG@8gѷqsٷ-#m4R# фj[Qm'?װ6)K|5R|K{8'JL+|/dRwAzw@{ػǟ-uLx~)=X{G*6HT5aes \Cr>S;jZg Ux+II~}vg@ͫzdž<S.=*%~e@&}aCq951[Pu=g]dw~?^s77=zێYf4WM`#6GYٝjvJuulNy,i"Ť~2O)?bmi$ kb훮(MGQ4*hk1iYK}ϰaȼګ| O)oQq#n;o~DKy-3¦mc-4^6AUKP[ !ј$"LFTp >蹌MYSm̻i']6lؾ= #v~(̒I 1폇ؚ㮙*U\.M"*h@fICIjG)>WOZ֞ ܽ}ALO]ڋV>`1$h2*m> m20/5\eK'ݾX3=3ÄAwզ%k=R(LҬu8zgjET01@JتfC~TtW]<^;#eD4VI^o6~@ tŐsC㏿ YlWB?zKPFJǟ…pF\vf괸UJ ޟHӌSD5V}׼㻋!zPyz0jO|$`FGĻ_r٭qG')'q%^8nWǽ߱E-q7; +~7*%g7h>8c3X}_~m1mb_ ч>+_PriG/F>VuH?S5DHBב'c^9mA;p xeUE|S>(Sj3y,Y^k?)d%=ߓ[|w2?&{~Hvޱ.I? >H֮H'~R=?K߀T;_H>IRwwFD?a_|IDD}b[k'Z^/nqC 1>*EO"칺e|Fן}!Wڀ8)~%X.`q%LFҚƸ;ޭqVƑ銏Cefm88cop\9pܵb3X宗JLMcX֕1mo:]~Z'b }RƜT#[ujᘢ{1y{^|+9Gx?J&th\UqgOͻ➱xCgdmqʑwk5߉/KqM:'X$#/Ў=0x߉H]@ZC2qۙ5sim[ $s<\NX1iLksf;cL.֖]pVc~^;~ilU?%jv[|<{Pq)HiلcȜxEvr3tS3qqo oM7=}R:o:͓p7w3pj{~'ťQߗ\rL\RoSy7n|.%(Γ|kv{7C^x {c8 KZ{q(r2 AX2ِcJfw)/PY gqH5 E_|wfw>\Zm1];H^ں//j0![UDNׇ#Ov=XsfԴ|!%2/O|N`Dypсx?œU7Ki}{nfZϾqYW8ҩ2qs LxuNH媿W^oXK7t6{RT}i& 8Ezf>.q$E2 />bbvvʖz\մVyw N S3-:ɡuMe<Ѣtg_N;2pCQA%hBAd ~rYccZ]pmW2'[5Ʌ>ŏrgk#dpta{OmM{{:>1<BI~N ,=ӊ09|4`v޸yhm`Nj>k;KDߔ ?Aqꋆ$ v]sc*4b >n D!~Ԁ4LfbfsmJ ݺk4R{|'| ^jZ/cP'?;b9里qnl$ Xm_\V׾^qOV_)'uo Fex٫._A5LĄ琯;]Gs dW5nHHӔDjf(񂳮Z\v%EÝxC67BrqJ>{GҜ+ڪG U mE_4JUjڧZSfE<=a5⻐ȹAZ%F2u'q80׺͝ vh[gGI;b]o C0;3D`]JJh7lv s)FU6%3JӁJvdӜNJMĞ\xնv胜jnQ(޷yX{5]LnOK [7JVhĝE9bh ͉Zn9NyGU C2JfuxY<MBd!6^:N|haֹҹݠ'U1Kޠ, `@Ii Q4=E"@\- w&f(o"L~t9>T_wی>Ja~giq˼ziXeo>64噓w4z{f=Ŵ\ZgzTrέ9i+ŢP<<_:#~sZ_k5Y[CdX^;fv@Hz8pz\=B*ث9xGk }+cd.148I4E$ ntR+]nW@dXP(9) q+9)鐲>V g-m:H <7ⱊ,+ŭ]?* D-i\O3m[ Z.S&!zf#~l2uI 0!xMG)xO݇ ]}ߪ^ wLϧs AVa)KPnIk lhK>dޤf lv^>Ծ@6gɦi<8|8x&Ɋo+H ? 01f?6lyz_SnKk;zCv]7;]PyYM֗vin> k?ޞn]k&p&t6%Q e-]t['wl͚l5Яa(hп%4.o NBzҸ4@l/GȖ@1@񡪤1u@SuAcTNα|pȷx|gb`~zI= y\~y))Mo݁(0ܗ"ͫw{q+ΏHڔO:RBՙwGe;iuHio&CR= PR ›(k?ͫ3ic!1uH)Ig5e8`n[9- )>l VɊ[&mxi5m:)J*_huhu=}) j'?U*e3ۮ8w14 %&A:ik1to` mt *PW~eY,D pF޽yCBԘkl*/'Fc|Ni'| ͜ŸB ݼUm'D0|.7qmQo=g´s' &FUszҺ^vGPkyuaq"$+\R2 ޥ? ֘#ZU[.2hцSA5dV6j'VI9,.,{}MO?<cqg¨'8°gehLKC^Zy<6U(dP\ DxcuSAk0ZEpe+hf+?F lufxT 1]ѕlj߬7BnEBd}i v=盠_:v6Wos-esz(a'%_ MX{P1o>U~ϰTT6ԕ*w޷1qvdҐV$Bx'G6wRsh[&L`qyX;8-S.I׮ L?yǏTC Qi>xmdL  %a#l&B5n-G`x8 9Fpc a$D҈#O%&\PMɑ+ӷq4<\>6i #_^毮3)֭f%a=jEB:y3a <Eyp@̀MF޺Ӓtl׋"]"XL@Af5d_'ȡŔ2*PmH 0D32! w3s  l(R"0Pʀ +*yi'^4ed]oGnK!>עN{K&˦@)ӟjQͣpbh͔PAx@;8edQt'5NrF@:&#}`OV׽nĊ? ΕU0֤zeƒ3Iqo.:κ O E+ԥf|8WtyR<  y,42nh1^*/vx~5\ՊؼF'w3V>bj.ccW3*~uN%Z~ Wk={҈v([]u9jѯӽQVN(t][^ 8" DEOF~A9-&8Gp,ppxiv/Ešm;g<*0}N{SIH9Y4.Ұ/q{9ЫFv'H DfH5P 0TT |pUzUn!FUW{/$u*bpZR4dJ0$ $Őht +y 7ÎADAX[1 Z-(${8%OC!ܤJkOrADٵbIa"2e Uu5Z)3r5yOZ<L ,ٸkݲm/̄YiDJ/&㋁l trOQ/9PWVOu5M΋  4AV#o(?Iȇ_o0AJ(*Uj$&HH/`e0Bl HL(Ex}1Z(i) g@(-RmQgPO fAC PJ xIGD"F׳?} ?6= )d)ʷ3Iz6^ +mdPt}Bn+jQ$2A#a# KKO$ *t=</-,s}:3KzO\t.6fnvrUfuj*Cpr q^fVo ϧF|T(~N]M7j_|xD58x!圽.) ".≇2鶡jzV`@#llTlnˎ׽ݡ/~vӫ:69+zgY,K#ƖkiB߱jP^ӭ5ՠQe%\,Q:NYUd\ǧjX6 69cxs&e0 $0*uc.v! KI4E$p x0]j%Du|W B|-S1{{9]7$iY<.k^&+70%9ufm1,T\脷]-255׻m(qH¨Af>w~ ʽ6x@Y]U]mO'ld$tУDC?w9i=;ld`Kxo+&i寅puYR6Y<ұxּN{VBvJ9` z%rLCxtq5d4:z!~ڍ` H \ R8`^Dj%҄42D*  -SB; (Y޻lZ' EbB1 h@EBI>СH*,yFBB42"ARvRO)ҭ'n~͞zug6"*q `"e0av$"0씠&DݢTKҁ@6 /A֡WW-++ٛ64:u8w?{LG*)j[֛o{f#B9l;[jAWV†/=|A-\^?tj|} i^̈́HBV p$,U(.pvNW;%X %A.y\pꁿ8lͺJ9sʙFȂ8  8-}2x-4c3 +hFe` Q6!8H@"$Ȼzx~< ^eMp.*uL$)!` k~cG MtrGXSQ#F{rpEq׿Wӳ2l\(uE_H$ )0B}l"w5VVK_$ò iNtJn؂<}p HTJCuM6qRy 0Їb̥M%r6:Ur8%D9  eCq]X!z5,o&""Ҩl|ae䐩Fi.OZ,:|CJ9D·:HjQf=%*ުX]S(Qcd^lq:^g Ϊ+BꙜ3>.&1Gǘ5/D9HВnS* |x $!b7SPp&th<&{ubH؀iM\40GH=C_@d%A  RI0qЬHJ/lJ!t4YiU| ^!%<3A\o%ȋveED/4l- &- Y.!{wgv +> E tiPI&سxȕbY.:A. W(.*JdJҝ]˜yٛB?KoDdcI!#(a$(ԒL1ȭb~?ʙiG2 $ &@b(XA^pEU h@rݭc6re&p݅ /uhU&% -TM% ArGM\ٷ5l^]Qn7 FO[!;n4P8!8&z@$y.@)H(5(1Phb/74"wE(_]J I4 b~1<`ۀPK( RқN_W, cNa!Hf1~/9;f6֐ʳdǕdM\kρ9^'fRKkԕ&gG΄&XY\D,vUEhIB+T5ۢ2',ASӂ&cfS l0$@vw P+S03ÔnTH8y $ [c}2Vu,ypmgj&H3H6'*Ak1 o{έrbI(cx ??jmDxރK:K\g[/kN:}4h%##b+aF$AD$JKN߷pTw'x9T:y(!y+G y%,K{/`wyE7 P^mPZTz$VUV8v> (E8t #ejf:J) 3jkjeeba?uE٠@ S !_&IR?"M}i-F-q12G2SOu89` :D0x\溇 79j#s[R4ΠDޤ`<'= ONOrՠI?SdIZfSCT6v?μ_yr&<m YR':&.#FM&Qк;be`=lDR"!#,Qe!Rh~#?f̝%/ %&op8ioNRHH.| r`\xq1t]y d6^RĄdqBr MYD\{4cXx3]bթ?YYAiE1)JXd[f.` /&t0$+)& ^;M=^V2DhDƐj8](YOxYPC hu-/ VtאOCkʂ7yҪ6.YSm%tQHHTHwDBܑG.j4պVD Ť'%tI7倲 d 4j*j]^.G CA^vf CPFAсώ hlE`NcujEP,P$dw90_B8] Q"QmD 삱$[^r*+MyI*)#]m?4tP)f}>JʺV)N*Ey IJIaF(<ӕ0)kl3UrO|C#T Ҧַ^MսF:q/ \p2:)-pB `jUL敥"z>EAȊ}Bv a@ Zs O"WvR-" Du{K S:k >UgEjG ǰh!Nw[GYGP*% )Elcubq];ݰ\xaJ,8_rhi2G؀~Ҕh7d0 _$a,1;΄S$YPr#y1sϊv~4Ez3xTB&YwE^?dWܧ-р$FGaBA2 PY; bmj 5!j er63*T.,2ÃBva` ĭo~`-Z;>)^ԉe59aa<XKBt|bbi63,xb!*瞃!$Ҋ8ses-PܢeFF J׌ ଂmQMkSXZ_n?_08:6Є(P|7nv!$N8iz.+[ye$(F{Lɗh|6_xdj.Vt+~Uc=Qq`pio "rѲZTEh2 oIR0%&HqXY߃ܣj )M]ԭN zM_?#.nEVT[*b`đ}Ɏ,`Q]Z%}⥹w*ˍ9+OE߆*Q%-xbDraō_5@\BT4M߇5qEVљ"s`tl*AspČ*10*1Oks+گm*y%sz@0ְE˱g\Gn+?4b`ܜP< +r)!'E!Yh@fF 萗BkgpkVk1`gs"4B\ڥ܋@ T%RH $&c,|l``X@:%/*v.6Ǡ G9H|3O7hd PEcZJh`BM O޵{VP-N~G+"T`"BP,&^Q@ BЮX#&TrC *Ŏjz~EÇ}-Z,B$(@J^9us a7"!YnUwǧ5ٻV{ Tr:V!*"wv[;[gr)-e >H&B4 T tI{7&o/7Dæ'-8L:)R|DVp̯xȜ!}|%@.LªzghI Ctf+6`\QLr"Dҗx % *R`%֧5;(%Tt=M30P_e 0+ӆ @0Pu "(C$9 u#аxI`HA {e?M]-2i .Mⁱd aJ@۲5gynO=?;pPqنE&+Y9mi^:7)J@b7m*6 jtF%řώ4Z~(/${cWxHt(D;5o |CųOgKŤɺh[> p"Tb!c5]BN) (КcafEJ% K&5L < ",y1@S,">*#Lw̰^d[kJaWIz"[<|mk}c׬JBg\, zP60h(Qi͔V*tpn 5+ْWIVVNkr3@pV׼LGjm{eUAHȲqvCEl^L[a)S6"seD[Q[J ʗlC2-ل3ʥ 15 00KhDJo 渷ܜXK2lcU>h+HN蓝^}ۖKn\qљ*|$Wo~5GQHlYj)[WY1 +1wX|kyxk\yttdۅ@8q-gQ zh)&]DQWfۇ,YBL><`ܿ30bL& {Ǟ':tG34Vg*])+KEY&_+Э\qMpOv&,nQ#DY|SDX`qڂ(+4VҘg?k4&eA%|6#{;Н\c)akP2АjPTD# C$ {jQK58@+N8q^ ]"Qۦ?~{!Wg|%v> z k2;!8@FUsP3wg|*LR3 >P b +Q5nR5S5% E^h&|x*VAĄ**2U SQT&w&3l4ؒzfTN s,]4y|)(Sg}^Mz@#Ft-plf g0 huڞˮ lbnuyYO?WRP,M^ՑPߢ%m h@Mn"],k2a*R(FP\)Wŵ='V^!M,zb }'^ swHP.V{C,9$T @h &nGSe V)QdR2U$T|k# c.h%QBhÉ`W.ޝ[K.3 0'֠;i"j$h-p%4XT @b)> X$ py֍[MVH/ (7VçL #!W#ޙ> u{GK pfj@b'%' y3TwPb11bk٘d+'}n9jz†ӥ!xDIynhJ/!EL ROغٹcܠC"%JΣ–DHL!ȒLVsLi怏@5Q 5<2ʊ]4!,iPԨ>?yp.9Í)s8Vvx?-OOćM(~_/rϼ˖>q` ̪nнkzrYئ䆇 +ֆ4 Tq pA54xHD YwC֌іE%>% l YOllVU5˳"&䤴n )^æO!`N!6UeTc+`2SgP튶20Z$ cU fԔI]ON$d/T7$صqCiC7fr,Tz_)5W%h*3ub^v@ƉiD $X/HZFvl"Ρ 臸Z&xk\0؜rKFDsTGx`gP:0A/Fsn˹c8|9 bvgTm?D=Li0AYyÂv1>*TE"D#EkFԔCzA֚ ŖJ1yD)E*W5]Faha aTT((d}{d- hd(0ba̛U^~I"}\J$ UIRSQ~륜cG7yR\x!@c~U9#W_maGdC9Q ˥.QERd]}gi4@j"9}RW@&!"ƚw=S#3H4tJܗ(NZP´q 2ZҦpVp“Ԟg OZd*.VH&F0N1n(=ߝp 9 t\oZ#r+߰[k+wAt㫍|[]h:NZL u`Z[:M4Ԥ@Plf EB8;!f,CɑaNT؄@^*`Pz%hZ.22HԋtQpS+?>IB.DD u[Qڙ{%wn5Q'x7MrK VB2Z'q-_W$YV_{`ughRS?on]{D|F V٘~8^6ڀcV#RrzV_cCPmVaL)z.-2% v&6P4]AI#iMOg[EƛEhFó˴ߠ.긕al ws u^ < aU*"*k5alU&t*2 Kpe+ZniS/:iKUA@g%8oXsϢQb0lU6c7}a:_I ˑ7'ur(z3^>D ԟSfA4rr x1W%*N8SъVI32T7 ^lM8ņhkiP R%)H=CCѰiZw2݃Jh0񎱈BHVP8<yߐW(ZFH,Dnô{բfCn_x&6Ί {D&\* &iG5 z̰#GS:_R LNqƫV=TVɷpɪ{N`JHrկY8ˇ༲abRJ-$ )X+W㱰O@ȊDPaٝda5cQRCmЯZ$Mb|,.*Q\eg˯iawXs|'28L>u<'қQq$+ G[@/цFqp$#ΔTob(y礪S[DtC}+_1|JYbu!A6Z0.3"wRj֌6ySFkcԌxBȄ0 I29ϚWOzIƙq%׷֏H< )%AB"67D# šbKק6 YIQLa)`I⇵'dR{Ƞc8;4p^4@_yڢ2Y悃tFO# ttBY`ӄ H#J(qPQgO ܷxUZ@@UZ 0bC!de +h G]|z&L1@Y $*3TYBƳ.~nӺWĊp *EcԵE*}ߨr^ۗ3&eW&spykJ){W`{C=4;PR,Is&z idRuњ}٢ҳW8u{akn[$K4".*$ue!e='9I?`%$ʔ(Պa|G0Rosi*OlsenJ<[5xR{? b=?Kj.XFX2^[oEZ33^`/b 8O!+q Y X Mz<( "ÌznQW꾥v(rYx7&yz| !k$YSO<2 % w2s_`&:9:^9UXȌ D;Uʐ/qz8bMtiuU} Ek@ BapG?NrZk8$U63"'(\ޒ Fٍ' 88 8gl,t=o)Qvje i&a'g 4qجY_m]wŨeC ꍲ  F8Znfg3n9p{l[nxa$<}FK \@BMZƎ\3q ꝆN@X!DjQ35`KO4 PThh9N#=;qHub"i>TVe5)Hn)Zʫw 5C"yύŢy92+PT(\YJ;vH2-PìHX9.!`Hr^I)Z wB.&CI kcq{8k]g.eD}JJ*0B[ 2D(` 7Ztr5$5G%HBd9Xb4yDd5FJH K ]8Zڲ JW֐0j~7,:xc#+M+ IVk#\]oj, (D.:kFm`Ls4yQm7ӚLE~ ,mf@/X+/-ڸs>,S%M tG4*Gf|6ﻑ9oجr7xfQ"25@Eƃ8gf[nP1Ifsʹ]gˎI[Jbc^qZ7-a8OxFm1}Hނ@'{TUU oV;/!\ :GqwކwVEbJPKD;2!{B*tU"XX=?~hdLԒI3m^Oz, 9y8#sgz@ayVߝ%U߀MXMbpo||l7,vEWF _R  a* rr@}GdHcbc@պ##в ] p #9TCKnU}ɚE]AgP @ߜt(J~6-+LW#=4ϐѤb(HW )@E%5H ոAzѕ_gZsƠAiѭ2ժ?[˺7h;B>o>l˥a#){=Q uژե獨9$k(IGPb[ɕD!n Cf"6>&zy5T@5qG2/5$-'ABNA-V/]0e$!4EBDqd`=lѣ//ʃMz,'K+ȜBV)G)Ҋ+w^H=k탟_JW1-b|3 :il^vSCk΢$- 't64e,NZٸĪ).'yf=~T"2.i}eB""|ND¤46β5X&"@|(s5hyp*^liDڦC ^ɰ֧* À[7y^3m qȊGɥɃM,O$LUd+>1F?ײj#8 mТxi DĬ1UB#_ !kVòr@2 TFj_DdRRElvasbf1 6NVbȻ(T @Ȥ #=bZ`&{=)f!Ֆ쮳`)#oTWolpڪ,Uv= -Uc-ku5'uב#wGl;ӻ;C/էFPVt*(ozEڠ%L &{UncT&yiWQbk g EƸ=Ko3RS P={6CM1E@%-`s& X~PW&%X@DCʊG,-h\+;$AT; EҁUDJ@JvVEo8CJE$b*QAw"xKԕ|g :ӆk4јiaBK$R8 cKhv@"}]!@t3+*[nMz G+.^]D|lVZ}م/)v(~&n*8 ,mt~3OL{JW\ȰY=Gt=#Q3wwF)igj Rj+rodhtlru> ~I( F`hEK .<ň8v_Z/(|;v u#V#(G{zI39x]So %7ڴʊTIp1OzJKw=oK]HNy͖ij9C)24䲕)w55]WǵK>kz]0- KUi۷/E;)MjI*}& S;<+Xj/y=8Ɇ#sx ",/i$dM7:z.[B=D(+@ dDtI[U@yEeK ^rA|k:NE7--zFC%X9 1MeDoDD.ɠ_LԸ V?7*k-U5,uo">/A$W"VPptq: z\9D˲ΐJBfA@ٖ۬|.jhiFw!TV+MMDL^PLiDc d<0Zʆ&BT ǐ`?}y2X~6XIF6Xwf Oj{gt}#sxEД ე;4 qCV] .)QD0ohP:i"z*)J8 )v9h]1XBTD/trW D\O')k(oBYkH{nޭWv((c"Ca&ҘW *hxmgӏ'r5]cݡ%@{Tе)kb#z?NP1 9p{C@E#."o"$ 1P fTQ~-Bas87l ȂFgXMF;&Tt.*. gZX.#*<4@4 d}M% HD.FO)@DEh/I3wPlSA$ƍ7p˺k^mY)}lF-댖iUY%yhhxFsݶ+x 0 40{|ͶHМ!oߤc"(KTc +yq ŕ[CK&}Wky~1U̚(ȯrQ{j:bNMyy<;fG^G`Z9km9lw˪ ΐ%l$bZI[,">?[R.rZ* ĮXثmz5k83!Ҟ r"3ځA^|חaJX6-49hb^F4!3qLDf(g }8(F\VJG.l.P>"0sFh[!nE#,Tbp͢ƀ40Q@ m$pXCmf0$0]%s3lsIEQ`mUi*fˋHBi#!b:<=dR GG.CIm #B㎂i>۠ڗ i6^!BkR * А-ww#v8y>ަ/wBBTo0I?Es*LwXq|` Ƣlac%n}$.ڎRrVrj#gn<̥զ-&\a)A2|1Pr,+P+Qgr&!Bg]p(H$Dg$DbheK|e1" +ʱD {`!b FN:K!{%6s(G,0!Ab ma-$X[Nߩb%MAw>y@yPI:83o\9,7; ׻F e:hH[hל`yU9zq5!lE(a ϱIYMv (G 4A u6%whN xN})D6G4粄Jߜߖ3iЪ#+3@Mf[)lS.D ̡|(҈z_D$Xfuڅ,5\SVf;yv+ՏHX35['@!(x7LFqIy{>Qu*K+`Q5Wr9Ae -=4SJJ a˕ЎvD[B5J1Eഈ$,j? `X+;)! ۹qˢ>," Q+*Fq,F7-C8mX.:"w vHR5rݶ2HI3;a4k `+]`U@n*.6!X$Ai-ܝquFOr@0@JtEmD &H|ʛe.&'!mT8OD%AA+J$q[p,*ҵiƜI+@G"` 2zYҎY]ny;la] É*c, M bo>wUTO?^gPlE q UXPtQ|5q6-`Y],ꓹ?@ E°@l{޴wA'ɠE)/:Dv$H+u0  AgcMl^<{u-=%1;/Adľ~ńh}\YJ鼬k^-0"o=턙ch4vuN>1yXxpOtH$aĄBkHlGHd`[tB+?{9ƨNYi &=)&G~{qkńEADA@`(,EHl2@kZ<^IVp寧<0Ў\@[ X J{pH{ bhȫJw*'=MLH8LF:prEjMca% E:}o.qU4PĄuʂ  [˲|ؤy}!QB,^b(0@ֆhXf58eG(M">_I9~_疨8]La@V/&zPBE@ 4+Ma'=&+zLJJhy MUG}F4OW(`LAVII ։D^TXofP!$3m{xi1SU s[64뿯%#Hvq_9;tɏgS];aoYdU:nhsxl| ];:.VfpG D݀.谵/-ߛap]7$(q~{6I\#s)A䔇K[4 76Hkw~P6\4 J -\K( Ejr3?bש,I!1J? BJJO:_7Xiv٣Iްٽ(RB9+t7CIՀ?4pynɛjtuiD8 >@Ob1OI!mm/v _|˚ɪ":GAޫ#Hgę`]ń,"h o\{; .;{n!Qz+gQDٶ"(żt\ifm6 dag$B3Iϣr˕z*%l‘x){㧳 G cuJo(_PE0صףJ#;MZaOFmiLwԔkp6.q+qU;C()DY+- {M.eZxg㡠oGAaIN`nOH@D":.\_y ؔ{.{x \fim䈽By`i q)vLM'7[Юm:TTD).!@5OʯyXO4&P ʎY "ZC')J$VB)-IY$0W_H@5JgTDr!.(}Pe( E9XM~ l$سNd|~ #*tVd49/ 3T R_zd7eq]r:,`esZsgezs^ϋn $up]YhIP*LgԘMGY!>VX  l 8VnF`ɬ%?1c|WG5v!mir%3ǽ!'l[jg bV=!AMU%xckw, FbaG!R^/>Z-S,`^ DzFGXї )h(>khg>!+w:d"4W)%E D4*¨yy{;ә}/{8 4z*HoBXR&CxâdIҲ3o>lҜ1( {1d򨗔!,kQfTr[_l:=Q)B_EW B65xl'yEg*Z">:͎x׈NpBq]Yx9.Z7v&=\ȶϵY9nMxI: ebE 9Dici, E]ۯH{j?d7i|*+wrALϹΐ#L>pP Ҹ7ZԨ Hv86Za\FOd뢎عH޴nO4Dc|[,oX- }_E4Dhfz OMx֚38 ˁáoj ,Q tLsrO~Jj72YO>ɻ.萯=Q֐ckfQ"BcoGŜ{BPM:>X?ڃM)FqTY<\lvIR"#:IDDXXjƙϲz/)!BbSu3HvM5:5=[TP0݀ꆘ#Жt38SP>&4;VIUyo}e؃u  DgOLEkQ5qL B;EWc !BB%tP)/%DkƁ N*|CKb}K 5,k^R$RLQ#$d7MN4-_L%b5|l3MQ;!obWBZO΅X" ,Ň3KGít>G~*H/؂fV?uBbًeXՉ{vرT s>m~47R6 ի.JK'Zk.(<؜z##f'a:u[ȇBϫupmlv'.Cn 7}{gғU\fC(}q@ \m HIiD\[orL7P U Aj%q6xn-a֦yb5yn"m$>ZxMLv8#՜$ۛmshƭ$*DJ=NbPO:c>etzƟwGWn47^zohk\& (XӧAۜ,FH"27JRg|w?m=r۱ӵr=%ٺ @9dюqGbݧe㪲Q(M!(k(F8k}qDhGQ)z]LBs%mloM."Rr#XvwTxD7`ݑTR'J 79$a)U`ssܙ%3::΄4Bw1XZ.:ky~2"N,̱Zusxc-Db}Z;yyEJ gq1mn6 9j!~FybҧʦPVyqTk[PW\::͓lT +dF|U!3i;7,2bZy鵬S'oOls~dp fOF{̍eEtlo]/RY|adr J>[zW퉲{Ўk)~T-P۷~u426<Al2IO0w6i#\3U]t`Oq%4M<Toc0Дė? 8dv0^6-nw9^V ;F0( ;&kv] !:xiJ(1b!rjMy)}ySݸ@d4ϛ4uo(ˆQ]$).IQSDKbi9y. ?uL"_"';X܇ײ4%/y=לyyչ_4%T{ф0G1pT%>\uDӱ}Et` С*c"&EShE#2B)H^ pfB t3׹@-v@YcyND:e"<>wB|O:R:Q^ֽo!Xw<afOw0DL_;b| \7ըFXf ^sP;4 l3h~^%2slX{dIJ=bB aSyg0~Pl|mY5m pȬW ҊyA;N<ʹ wh9U{\Έ<{S\y\ާw˙s{Pt59tV#薂NH>ٓw=\X'|ځVx1QɆaz#L;DGe|$@1$#ҦǏXd@쳓j\{57LvCp?uR&5cWM!bx܎3rK@\3p,_E/:~ vf<+-e=%ҍm3W#U>@ b޶~$w$)7֔W^rGpbʞic}q+sk;ڝjt;bǴ?t=2 %Fn6d_9.cn񶽖@5ȏغd<,wv[8]QePYpK:k)lꩂ4}™KQ4f~(qO\o\zh*LK'[R6?wsIԊ_ MR=e)dCZMɎ =dB@8wmoM mE[ xU|̠9޿(TqޮǞq FE^3׉/~nU_mZ|_9#`˲&|>pB&Ü03&? S\N$W`]kW kpQ1GxN$Wk3'_.Ճ0D,ZbIQi/ NrguD@/Pxg"Ba y@38u&յKzhXh[glO7^{#2ll7XےxvE(K6+#tBdy#OVIEYP'm,"'>V^v?饢vg # [ G'26O n ̇@` m0D]@C mtd12r=I?|ى.nv;|;~䂵+z-υz҈~{uUf+o(+4@ gk~oC81dʹi} ɜ{]=C!䭷276V54;^k{qo;B=Cj DZՉ8ߨA 'KEOmjGY}SӭĀkGW," GAQ:q,i[T{#ؙ9TK?~ֵٌőT1ARJWg(i@Qԑg H ߏm ցuApZUxBZI =2;/ic~K7>?kRfnڕ]} lC7t hQx:#јa}1,==^%"/Q UQ̑h+R2MΕTӇq[{-ԮIŵ%nDI "}td-%3w(KwkV©aS̞rުQ|1-Κ0 8`@Obc{\uFWUણۍѾ H 4G(v>>bxz,s>!uaӴ-ь}Gp8l_RA1P'^럪9dCGM_ dlܠܓH5IОz=|_ a鹐^XArI+i <1H ZLB`^ mdl |_L M|JGZk2΃FX YcS`39х@FUI$%= z0)pxDQ/bj37[IGg=̊!T>P"o伺*6]&Q@9]~sz߸dzlAyr3^5~7 ?n^|_iI!rJHa)@^ g$m>GnɊy .bF$0+1=-؜uO OJzdv;w{dUgHTÆ ۊ񏵟-=;67s1$cϡO4ZlJQܹx&߱Ψ|Rw3d.d0$Ը]OY:o,u_\ytqx]I=3DJ#hM=U2 M7QCߵz܀9im% va=WYj}Z>{߲ I& o~;_]l}R#ԻR/^ݤ#h)F]9 l~d=*[iȴ">s1gg/7ܻXaqj Z5 x#b潌kɝ#@{ 끆[ٔ}PW]hB4.J'$yR6ؽٛtsˇmM,=8!ۑtcP"b( Z #0Қ"lsirQ ?`cg>}V[_:~{׳)PZ%J47IfYq(צ7?g~y/1nx< DOE~RN.gp!Ӫ4䈮#EmOw)9R_/zNz]N6cuv;=qwu[N$`to1=Tnw51u9 o;6U ʌhY^.&sm@.N1.@9vb?PR<2>l9j-B{}UUt,,H~t~Q}2zny`uӫnp}jyg{}wu䥼տg  deĕaHSM8 5V_1,۔h4J J!`}E'&xrBVK!TmRy:1&):zkG܅U1,[9֭{־'; ӞٗXg51$5YQ+FG_ Aydp (%I(t^/Ś ɝ8v *pu&e;X98S}tܽ؍?~ī˃9>Rwjcwyo򘓗Q9n]t!Z*/_ X\*LytK 3B}yJRe|֍ IH{6г.P@Ei$hjX[S6Ggq^4^͜y,&;H3j9Ǭ}F4Fw`L`z& ~EHRy:;-TSK5:ٜ{oQ xD\Bp6SDD+M}yhH@8QlvgqWWhI8tBSUu=EӀVq(?(pfw*LRꁇ5 7D՗Q3\;(?Z/&nwٯ^L'qjcѯGta >o>t=Q'Hz_ϧQcjz7ll#uʆ'g ~&i Ol-B@Ğ)'MRza_naM_x1]eLe©ՄV7gw7XkAQckl{E; ~*A(21 `Sfk:r;jIޡIҘb)gY7-z)%wNbs3.}IX!1My}v3tpg d< {W3LTh SKKjj8r.C֊|>) "` x(" M@.@ǰ?g=d ya!g>fgk~wTSd${':aVO^[تpv^f:.q7!櫫PҲT^R$Dڵ-cIzES`{O~j|?^UVv}¹jKqS,.J$Cl}oîˌcӵQ;/c7*Ut~r# 'tfE lGk?V\.ђs;oknlE(\e VƳ3:6kWT+%Nlk4砓IUw,zR몑XiK%ߕGs[Z 0&;-\jiǍz$=V_#Chq5D-мq aze\Qخ.zdy#)AA;rNk6v"XO٦bsՁ= `iPH%ODHDr!E1Opua15zCAF²piUx%y7V6U1uI!* "₀Ԃs^ovys.~jx^=xOoΤ~+$.UczXKb H0LGRx.pptu>ZYݞ0La+ uAI&rD4W4fP:K|^+xk7UW;WtIf;smn TaP9( 4;PA\BfHԒ6ՃS10I𳁊:P*R̒r l` 0< |nLmkZDA8ٱ@|>P/$SPھݗ;618ܷDSt M-Glڼ7NimH2/7}tʕIZ4o18xPY5i/'o7)&١?!:nurn~5Z>#g%jűq 8q&)M Rth/(AGΡ}}OmC+a9=H:E84D(By}SDVswMsJ.ƿ=8l\SS ٥2{oj_b'VۧG,A"׶^I3ܨ!@JofNw>viesk ᣜDqF;Rܟo _mwm%ZidtnVy??7Vʯv8k"iHR7P?s~exߺ[yYėOɢ^ҳUDZΑExLAFEE`ϖ ‹­1-N2YܜWgK YO Z,җ@58+X*4`0V7McgPCq@sY ߃põΊ6wӶ? #%J(Yс/o C]d1QR/v}cA30ʒ2!^aAV&B=q*Oga_;gc,CTL4H0 (խ Nl*aW.2N1*~] uS OPQs*i1W@0Ĭޢe}4#?Z9 -JYC/ژx"j/gF:ry,B]'W~o2oR3n9 ϋ,m-]Uj*`eo^s۬z'v]tL]K6osZkI &2 Z 1Р^\0<3QUP}B۵T!<׭O,n)m5q}U #kyMj%|QY7 ׎́Rsй@`- r̷<ŷ1}қz{oZSr ZS"%)T$JTm{?%N1r.qiW6hʈ,I#-$7%̥ Y[E/4cnW>{gB; ܙa֪}U.0:ӽT 4B/z`le E!yʙk`氦'w/c_#+Ӱ i_-C9=2sYlAzp" C)!wIb@/,XF6!Pn#X<"BTVi)P{S Ȕ]NsKzXu9/FbQO }&oF9h3̝轃f&##3 F0H"-P.+:^!ԭp2nc td$xAx"( ,s`OFP<;EMZۄAI~PpVҟrV¢VLmL9_>ێd(Y~~_[r+xk,IP3<*yӝōv* _ѱKͿgxndeBnb?IW-)_bfGWŏX9Gwƴ`?GV?-\wθb3m]WMⲼL[9y;۶\yɂ26QBF,;2 ה Hc\NlUK~(Ce0MQrt6V<8$m t2iGL:>z6Rmp \2 \r6 ڄ[t{]XIcvGvP`0}DY@tbJ(h +/@9m**a3bD25=DEJUͺ\)a5Oo޵veIUz%YXalz< ~2ʏ⏧d^:7kY>/9# =YHuAuwr{2_?9ĆûJҼ,`әs* 2 M~ٷXܨ!TDm]_P5p{cJ}W52% <"UR{e[Lo}RB޹١}瑭*+$H['wVId0/Ԏ 5m/)I hRm,sgB;# q`j3.=fޚ\Mn■0 |A-mY8 YvCw'?fՓw ЃY0t[@ ؓ>HBW/uRU a-B!)nߑy?:}AN=,Q*P![ P+A I J N zFj"ډBPYi*FrTcj9~~tZwKy,lrrEORϮ>:6phsqY.oiî~ؕ[ XK|)%(w(gAPBs  !|r2`ͅlc*(qK_ϼ dILzRyeiG(RDVDܨ%NڹsP6@C}Y&X~WO{ziB;cga/<*m(Y;#88(:t'%~ע9C'2}Q a1Bʫ{4+a\_UE)k/?&t^7BPҺu%i.K"ݝ<5ڷԬ>Oz7N7-LᵼvRǶV@yLô_һ<[BձJ4a Iam;m&?v FΡu弌P?/u#Zsi/K)7+Vey(XKb;+5ȒUAyb40z[Aŕ=} ~H!89% h!X#[mbC a7wtpU$C(^Т(]t( $4[}e_NkYmOv#˰Rg1=C Zѭj >^KVgmٺ+u. TM;o?ͶOg>GΤc7;ad\Ȧdõ̦TM$z+shVǮ)8LQ>zAH*z VXpf G2!jP rvwo1@@>b0@"p%q| }\нn],]4Dp(`? " 3x`t]ݓ;M=xm 9GXYE(| XM ͧjU[Hky%jWRG޵uHSE~l<~Âw\6- -e桻MI3&MR vb BR>T"ـ$f,R/d5+ߛ ;pMK4$WZzv.>iT6c??F67!̚m!:3EEtkLh|yxaĄ?M~RL b2k`tG2Н' ةq"N eBeEv<">)kd5-LLSaЦ.B#'RGA*5۾dWgڞiJ1zw Oީ⫎0qBXȩUwゼбޮwkO}6"VªՒ7۱V <78 cZzUVkԬc覶`}&7JM{Yم_]祵"cN!WӔ%Zu>z-E%&pRJXBu{%;W,TeJZ*7gjltey|; l.RVax#`(C]I(]NK߁ɰg7 NoI쎙Af3I.&`H!HlMU.z`W*b$@l8(bW)!F"YpuqnFr_vNZ")ڜD4N]*W6Zkĕԉ#DUN=Q/3=([t1^գ­Ih5* w0ma ΎSϣ N9wѷ}V7PN:Pbzn,_E8va!<loy|],"&Fw3ݷ[= b-?Y|]0jh$$SY'&ga2 %T6k֟$"1/=Zy *Ĕspً>W'P"W@tV'14ҿK=Y%}%?D_ڏkԱóWegqOx4U9 Z ͵#'Nīk)*CfɹvJuN0gǿN[ڛ>c˵|6ђ<7Ǹ"(0)k#`&JEop8b̟a+O׺#Aybpc/GI/1:U1Em5.mqҗ+_hP#!3A% 4ty:4ŴH Km46rYF`;=$Oџ800Rb6Z:ge T&XZ Bښ:5xɪCw:ȵ8\T!uXm)]=?r]Ry$%x[R}M &кn0-)$>#d`i/7A ߙPQTS/y'~~_X"%[7PF !RHwC.!37 H2nimQaï=Q,r gL1in XEVk)oE3rJ$-iW=,.Bf6Iﱐ;7łma/,,˳(Ȋ;t y24?f_o¦;mDRbʶb @ލ T2Wn^of_\$0FR ]Sy Z I6 PBӌc`4[I D GJ"(0dBp# H#OW')Kq[n};gwna5ht]/ &t8< 8Y *֝<[hY!3`6;q ba {8,U"tBl9ԙD0o77 ?^EH˫NRdC ,Z`/L^* Ҟu9{`PhI=g;~qT bt 2Uۼ [erψYC1PMMD@AWkf Y-!$vo(p&D4#ܗ.ý99ʝ,A( AD N j%X7.󴩌(#Q{+fh.,," H֡K łM\-ܺX~s%*G1)*߰Դ} 2J4)(t6?\ƒw pa:ٱBc5D < &hLr* YqZU+`Yۺ4|(CL)gPR3i'ͬ2U~`Ml Wݰ}L)Mrc; O= |yaK_FcVBHse,QפMn7-hYqMȢv#LLrPt24Fjx4ЂЁQHgPom}@ N  agR穿9d7ZԱhY6d G8KԴX58 6ULi|im&{V_0=K!"58Y$v!.q7z<bF% pMm^^\k*'-# AB< "Jy"Un%) ABYptMʛkw6OdGrl V@Ұg=]ET, +@,cB]E :p|`H"%ؓ틺h`N"uY>O}DT߬DP͠>"+:#ޖ_ w&o'J8; V˖[fA_ݜ0:$ܡ1YrR#AP/Ƣu :s!98bl~i_{t77[^z耞{iqM#LQE՞j:v2 (USzb~>8bSdg9U7M} 4 6hnpSTjV$7JN5%EbDƭl^v) `tqq)J;Qz`IIȾ(Y#hZjNƱi6t0z؃uDt"(Kt3V ̟L>GP.tZD%nl#mwl68zHLpU놪6+^ yݧxnt{/"y%6RCw/iZv{ҽ z+睞ڳ9or'b(f)2Bx\}ӆkOByWK1X5!X )9M ?6>?;߈=`ۖt/X"lq*zFzI%aBΏa!1"D Q0dZd@G_[VJDS80,HVU}1Ɂܬ 7*H$%e+_';Eoz"\?S >_< Ncf;gMסYuy?Ķ_r~eͯ,=3]7:x-Z˜ZX~EqDh0DA@(@07TkM$18Nu 1EeIᢨHfO6Sv_[6RExZ>;%1ITbdzlY\ !DTO~u^ə[mbcE6 ݗ\X2qZMRW[>hqnCG$/?^ϔTqo,0u5t4{ѩH^Syo+jpԨhi__m.IIv{H8%2vI`T6+MvZVBaB ^!Ɉ(%j:4-$a$i`E2ZƓ4,0bL9a4 vZՁh^Z5_ݚML:CF +M@9<]]jO @W 0u6Fo[kecY#\$40LKZWaU Vt"|`TAvUņw`b'bA00r"\4fp{Ytƃy188Zws㲱>:!иKOpnE*MPMLw*֏<^]- Ak0FLX^qI~4l.1b0rƃ EwngSP;ܬ2M^U mBz͏Zue֟HF!dl[2~w s֢6 B/%0zH[D1#A" FPMQFLńՙ,т7,  T7nD| >,ǻ׹#hnGA> =PTdDX.6'qhTךÓ%ac`On@9'p?@JYgu&b(TWtrA0Nh4ۛR2㑧~Xo}Sc"iY|FԱP-'Dzʵ*7oo?}@UZ9P6Vj Emwu]L  ŗ}F电/-KT:(oA7NDlh߲I)^KA@nai*8tNTWx,W8f 4d<hb %1ipKW]_&t@zgɇeʬO-cʼnC@a敉SXJb#MZ^s[u.n:#@Y5mMlqp%拌V+wVbi%״!tլ9ʟ&s;'Q$j# oVԲ$/ӟB(A!Udި(XM ]5Z.W<+oXڂH'xڟtྈe2Z-ߒx_=Vol tTr~MSG3z=k9wz߅w<_KKk PHݰD% n Ʌ|7eg(g=DޠdcǏ_r7jV;;#MYYΰvRE9Tkaơ߇ & @`hEQxT2ӲN%ճgEWd 4(*nz,L0 *zXBS F,jQT$Dm W,ź1 :ts=ev)HKm (lJcRśR늉#qJY܉wtr!:c7x%:i ĴhO<ܽnF}f*~Ge,pi4W- ARCa* )^Q6ּHeQM^T7xFcu];?q0}T{sLdnkEWSJdKx#HU(2 4.V4.4LumZW!3 (VcBi 8#5MSݸ2<`hDp% 3I^Wd>'(\R.vY fNJ=caD*̜_W˓ionǞ/^;*  &*"'F4O¶9c/.Ի\$0(&ĕ9{nZQD5qq7~kZ>[w9@1^yXu^^M>#>;I - ]yfcYpLsB@035!X̯"^(t}Li|fesjٸ+sy PjۀKDq6d% a j0~@VbFdr81_Bb(Z'51=)e O:>pcx]ـsJ8jEܞ ͶbPCW2P9[=;3pfkULԷՈIT5Dxǝm6֦.`_,/?~dž}SIS8^xalcvh h|g:&/x'gCpP l|CN}w|nS /)ǒ>/t. BH}L4>JYGB0Au3N_;j5OD8XXŒT]xB _n`]mbSxqb%KUϐ#+!՘҈HLЕn<+kǦ(O _upl5?&=}iT <gt}9=TCk}tݶ/>4gckﺧ%Kq EXƦl&鶢W#if"`N؆5JsM8mN1oYar}Âvءiزm:9 -4>]IxڑRq봉%*f;{6Jhph&& : 2af$膅/%i͂pӅ++'wf⊾'OV1x8x7w:MRqPFf硒d!yB|F\%pkV<9~>o%+y߽z$R7d(2T_歘8_K4'-48_}Cqxkl dK#8fX @(]x/Ԛ:I6f$O>/~!eBZ8c"CT+CѤcJw觖2x 5Ɛ"pvo{mfTW8Y@#ˌƹ4ؖbDuq=J=#I#z|Sot|,vƗ͝};mc%mjDc_ S]Dx*4M8$xe)7VD`oX8@^5B9I=6}7RL=g+!_b7鰃 @elO EgvUc8h'UI` {{AG%Ip8lwHܻIrK ).P<I$COFg#)V)ʌDbنAġ2sI0o& /̀/}ҦPֈX-nNYֵ /31 jTܸ) .6RqײmHtVؕˈ9j:ËjLS hwiT& 0@+BB4!nȎY_!)V_ S)8CX˒QZao>v@T"Q8 q}}x$/L%x\ź $[ѽ-*{&z+<>&S̟uֻUٯTM.~u|01Z":Uo0{%q˟slқ&8`ʦ,|$W|!a?DӲ4 K؁ZXqHMvpwvmf , рaLҚ=1c2bT)dY1)j|ӪUvt/oTtD:A#D OE iBdcq4ݤ֣`iN ݍxh1ÔI/[O<*N)cw.xZjWy*nb=#$,O}j<2A޿F_VԢ^JcE#&wNv(jej,jWHT?FCvG~Mx} S/2Nq*-Ne-EmkQic{b*Cʹz!KÅ1艏O.8y,ڧY $Hph6- qkd8=PoYՖ;/.^ؤc VNOjyq "t2ܢtItt\FľWܿf@n6"TΩYR9?GLg ԏ~-0`!FpsKBp!4WŶr-3`,0)_TeYGTVA@{tH05V<Y̫jAm~wh1ɪud`sݡ^9n^(b|6OCGZ=/RC_ns[h*bz{[Xی Қ=$՟s ,L(:ٱAq  [<=i0v<1s+p,Az_=thHp gA6&g8DlHZѻ,h{Ż 6#+J\E݇^q;&|脸ִba^7OfvޙIYd_РtRy\p  "EA`R%vg)b fN@d,m}&jYMi0&t}ۺHզ^. 6kTMj+IkmH!O^" ˯/"w`TFL9"Y ,8K:/,wjeZNSJZRMH(բ8TAaIBm$gr"SSeyr@(E@:!*]/TzpF~`5Qփ_u6w -s3?ݓMkɿYvI S)sY`ZE3W+6H7;FD~l[Og=s&p7cX~|v ̖`A`F%VUZ^+kJfpľbٮv1=.7=G TGhrrv,]$ I4oNɶGHO2]E~XiCD HLo)P rHm%?kɭHÑ=8f:xilu}ЂpȮo  nh{tk0c8ugO|+\#\PQ]ቢbj,Plz|e#ךYP}_dztBbd$Ocʺ9 UeBA:d$ƣC:m&/S{iQK]b뜌Bt&vX̲i0 mgF{O,0CN//&a'O8:jp5^qui8άG#G@$hC0@&3<Иvh̾0ytj RKv5//[iMNzN&ش(gh29MO6:)F 7g CAL hVĭBNxPVζ5χ LXP:ȷW$b1?of~hirV^">T08&g,S}4ΧNi GEp=n=NÊ_w3fl=ۧDz *ʣrkǮktrF'O'9jRw N76;2Z';EcLPNV('|`M<ڶxᘁ`C6͐'q鿸eMKlXqܡ[ؖxP Bj!s||(j#g?CuX{ɌAlɹ{jM@^%4kMq]ڑCYP}v%/ux7$yk&Ҋ$!yqdC%Q:f4nsmplzfet/ٽcY0wሢZrՀABV'a+rrj2G olt5K{\><>o[G=֭@`B𐧢~:S ,~b W]c46O~T<q H=Ԡ8ME)aPPa-S>\ѵh|a?~> YChp0 cu4k+";<~A= hVW홫Z+DZuC&Fɱ|m/uvW:C0]=jʠ<'K(~8 *.:̒BmzsmDI-+8S)g3b뇧_lH~!Fp v\|&+wL̡ Э"ᗀ 1U*p ݣ n\:ɾW% ]CUX k& hR`cK Ӫ@A[¬rD:="(޹WYMp@@ KUD\N6;6MUN(.iOޑw;+fƶ][Nxht [S$E~Y tS^X H}^'w^ΙL}uV疏7O{PG쪡JU8u#հ<)Xxf!V`Eu$E ^B TV ]ӎGaX bV ‘[0ti{ˣ^Nq1qDyUvw(ɳb$kZ߬_\)G$]xݹKTUV$}yPAu cg_QH?}}O{9,@*ňKgU.K^ X\&2B3gH ,W0ڝΗM秫Di3j:pMruuyFr#àF`N0в63|&_O1bMΰm}?$V\4>X%!!Ž9e. THPiaXȍO^]y܏KKH@PѐD&%%+JB ("~,pF+ÿ}DL9la]&Dh5)}<Dm)<@q) RW.ўFh-kDZH7xwkw䡻FƺLoYx_!(]:7~J9A0w^vR (CL,,I<u1FDvh!tmWY5X=H+dzSm=N5n3JedDuJ\ۼ`ʑj=;s,ũV!O˜챺K?(_qY=}*anTeqB'LZ.)w9 MQ Sk_9ꮭo$Hd`MWFp\qQK K?:3_/x'2%!vŸ 6h렭J:B!QkXn'IF)ힲB!,R f֍-vҶ6||ܷ->NBrԞxFt˶|]Ew^0cƯܑwҊK24Evj֛)c^X]qL_ng Va0k э)?lwZ >HPdҖX'g-E^s[~UQ-^ ẁHnec4h_((*ehΗYlDUY(oKnF辉9F;?ך nK d:a(a3M[ElZUƌ.ty73zș'M~O檋JeKly|0EQ%R>U:Vb.4nH5W)IFJ,Y ~FFN'1nEfcJXr:}']nc}Ίoչ3y{3+&w)i0b; [5kHM/8`fsq9OYb)X0>YDE31 @mP>dw1bISΑ}ZrkPOQ~ꁦnA/@bLEjwtN=v`gF+o4t`:fFmqgsR5j2u6rp[A/Eֹ;G>i!\ ^wh&zGN'=yeNz'O?q/1p ڕ]Lg KZǂ˩v ژnjxu %Uz-,8V'AB_Z=  ԡHRܒapP2)o[Lw5P|m_)G{^Wa%:#{9k3fby–ƨVM&˟~ p'~w< ظEe2P8`'ج/zSPf`m Ki-Q`hćeS`6=2 m+/Ll='֬<^m=m7OƸZЀ$ =0)S}"0LJrc! !ǞT@c!μ31iEb ,pZ/*U`1mJf(3k4H2)Iu9r;"uc -!%<}7aJi7rz׋8MU"$"x8xq\%#O w[Vwฤ<85)CEܱsZ bLjqH4}cFfL%Sl |ye=ε7Yf+O>g_lO~UG+nn]nfIpw4t,>bsHd!vUDJsABHjv(JA'gpO01+?7_G=yp9ֶ4+*7%CvL]+q }ῴ3?UrD = !}3[&t!`!t΢3ڝ='w󕭞i߲^+v?mh9b~!l6ua[P 쪽-^%\0[rgT|lr?灊/{NL AeSsKk-HddS$$P4e($A[nۡ^>~@*Z2ﭬ߬֨K˟HֈrWT䒱ܐJ+ \l z}4/l2 }k?^Zf`@Dq|&+ZGHzW %g(-p:s06T ւeAA>=զ6KU6:LP#n&r~K8 plzttpĂOTeOZ.LM2Dv~E2f걉d{_VآjVZsm2ϟ(z|z7}ݙ-}ںlnXkVyR!![2G6F;Vf}Q]n4D31imoy{];S11ieC5y;i+ _)[?⼄*Ȋ!qDa@*\o<`{Fɜ拣ΜR\RPq3S9&7ĉ^n3fRnTiku <Lk2, mr{٥{fI2Dӝovޱt╛ 8HR8XVvPsDžgLMn?Z9)Ν'n-?9 ;_scq{6?Ww;^hSwJV8Lu$)PH5OynA;tOXn+Y&V4JiJ(8ڞyG$ǁ0rVݡ=~S'O3r(xY^p3%چUFDqhy~oٿ,V1;[pG'd'.\?薈'ܠ( zۣqb "M!"FD{kvx>ˉxX~>+ڣ=o;0ئ`eĝ-wfћz&J@ ]L|۫\?tfPB\NۜF{8u"DY7wK<YT([EBǻ*7]!gǛ l*ĂꎕƑ*ںn O~u:>F LW1~>)KtiDq,B0 DiDpˈ5yv,‘@~Ij]EGD'.ρl;'|7_x:dtkŤ?c-%.ȎP`Dp!|[V]ӹN kw :aLxo! ÊOED@ͤX/(ȅak-$ԶA2a{ɤU&%JfH/_ԟrcc4ɦwuӹ\ 6c]c* ص;x؍хWb>y1'GR<#N ^T`h%.d>~Xv]st9sN2ZbJtϘH(0lB/+;۪رoeK˻;7zӛ!+qX\\^Y192._OAeݳ~b61kڢLjʱǖÿ*3&EbI isN]VSR.Yxc$~Ț hN (.֣MƧR?+Fj|ϝy VSk+!@d{ xImZyY/M܏d\wy=O1"}όG epv5oڰ9wؚ =j{OǦ,bߥyнr)d*Z8 <@9p۱~D UYK9=@zfMtD+q &Cx]`jcF7?xGʀY([]^CO\Iq!i65Ǘk?E@{Ց툇6q$h&v`Y'] P~yK<|%w.3Zg P״y>{xxrs#idZ0u`'-^I,PZ-qf~3_N[:/I{2b"\̏n8ꓓ:\*{=~b[fˑ@%Y ;_adT`! 59SUmH ~nvgo ժWU3Y=;҅3Skfʊ!QۙSeN4|FKq <, .Kv7p5xP5]F8ʆ fKZ@;^}xK:tl7rtl㿬, /h;=\' 4xP `"bs[{M SEO$܁+ʬ,YﺎE*ķV\fڹR 䆊2Qf7^җ*!*Ǝ$LBT"9"P]>afE9t4ƣwnLD>oɢ酨 $sM;*>(X Գpm䱣i aՎ艖9WLXvڇg_8 dioZ>7s].L8᧲nܝAKF0DY$sθ_YGOa6ҥǫWS_zqનQWw&ODY#)h:=.)#Yk;k潧l=qP m_l˾:՟=N}\7g` X0ku-; 6A) :ujI턛ߟԷ 7wwI=2Mn&ѪzH,9H]^ { ۔A)V{w-":}6|'?& @5gr>Z5,2!wg/+E{28)SnRT~g3oNQ+_tKx]@p~h 4iI3< yu B1XԒo/knx N:_&E\ac|mg>`ؿ,yblTpHye ~s$jtQykxS]M:+3ۛOCPN~OccD|rht;m,bd0.@p+YNr}"絟ʝqk -bL9/hf^ejM!.`w1~4!O݈{Y5+ `<Ƶq% @s':dse0*h =SLX._\cJ~>qɹG\w"4d-23˵wr|ɴ'JPn1_; ѠM9|rL$'KPF\+Z+H]-59cdopt~ˣd:Nt[8~}ǸFGj ظZy pf;]=zϿdm35UB. m&DO'rQ|2y7zI]9Օ HvR웏.kJQ` F 5DЩvL]-&K:z )MRwTν#(sʼnd?ݢ?-4[o=7ƀ]{gϢD3D.v: icPe NCbNƮ -(骍:W͞N~j@40EyOjs}O%5 _2 ¼zEZU?5tqyAd&}UΏs}Pq$ָũ4M|^֎WVuEDn 4%:Eab u&=n ܽaG=O5=!Pio2Oi<'ӭw>7-w`IvXMx1&|V]cXbQqjwv{Wl/y\׸Y eZ=:y]w]ZڇO_?_Kj HODÅP`hI;Fͩ@Юn_= X5~ W孤MɯO3K|ڡ[_!m㗊p9u?튴9YnLW[eeR(5 j^{-q=e)w[x:Wʛ{ۗ͏!2.yKVtYv{@_hUJ3H`. RˠO#{ᤎ,w=^6ƛU'[hqz9> 3?$Vh^Sʂ#7/*YpH V0zIcY(RDhKdۃ?}%rٯ;>PVJt31+GgVbL_Z8 u,0Q@*THƸmw֞K7<= ӷ᠉ EЮk@#\=l ӡzN9=BvЃ?yo sgFޘ|Mʨ52tIŧ'}3YQNkOyf2$]e%|ے(”/Fr͠c"wW:#F(mTooζIׂؒ:'ɞ {O%*;@ y iڙQb9[*j8Y;VFhe[mvqa/ˋLZGiф{{̻5s:Q+n*GPK w@BT>x0oCIڜ.cg-n؝ ?~KAͤ?DV*15MGV21%O$`ρr@[0{Jր7bM]߷滚?PnlKzq->^,v=qՆo,W$љɩ#>Z@gW}̗(g=2ƠC~sn{ rS?2۾u#?l[(uLp›6WSui.0ׄKz1r~pXyffQ.=Y9 ыF=HmUv373Z <)]%#LPҞ9)irwӲIRje6'6J>DU"EAh8hPpA62|A΁i5r:CI'>X}fwwp~T7?L_*dBQY+e-6B6~evLZ$8V-ULPv;cFyвE}ԥ= W<% c-@Gg4^S\TcRgN(Ҡ'Uc6f(UCuvj꩟{#J\V#`}sDȂSTK}~~K\᳾vc5D{̿r9r\˖-Wr/K fn-*+BpdsZ1TAJITP4$}toR}gdO[ UnX>VB%9 q3$7]WߏȑD%$*2վ֊[}IB8ufbV$dvKn=`#y˜E q!(%dDda3(V0Jf3\zZC/ a$mlRbY2m%Y2g?=}պ*~I|uAՐϡƒbOB**ځ-*[E=?й8)v PCڤmG.ͻJOU&x8ڙp4lb1Đ nC;cx7+io<=-'U3G}}-{lkgƺnBkߴ|'Th5<캱cpX鳺M'/hSӑʝ& m4%tj ;K>ᦙ2hv:E0(D"&lW ܓA Xh D0QN|6ɐ|qܒ$,F6) 0 &FÏ;*ߘ7/ŵOx}s0ui>Fq60ޏjyDJB$j P2@⡂@l]xi%<ˠth Ϛz%g5d]f<0q9~4z9 $\jcFa&/spdڜ#拸N\vC3_ iBS4:ssDsWN]mVChpHBGrT *YlP.Xe wM8ޥC^9G]_O_lY9D B%[ e/w5jW8[SwҶFu:tJl-&vǒrt^L_Sf-5RWo7=XWm 25p;D$!3IE@<W|&>H2n .Xۻ,6-yմlǪIybSʒul3}bUGt=RwIvpU{:+s3Bͺ-b3ZZn\Ԕ́!%W *vuT,M 꿏7 m)EAyx1PLAJ.dj5W/Ѳ8D% %^ŵg ;s;KW_˲t 5:QS^ew{cgg.0!7r,#> Dh8Xko i@ J88|F3`j{8IE%J8W,"5hʤt .I)2Ф빴J7HTlȠu]r+;9E.$qЗn-__\VFq Q^i|ozR# I9~"I#!8E+.VC5=m3؅ )FZ˘ )D48ǫ>ÝR::DDč]$XЄ*fʓ~束&&s(g-Qփ S)L G@XoNQZAE`.I=EϮM6nO{[آ6ߚ^ۍݝ1wt%Dc/urGms漧O^0"ꇾ+aoKUΓw}᨜xnKdG_r˼} 5 ']ԏ 49>٫Eil l( U]}H8!Q+=hzi.QS?5Zgv,7rQs˴,T>:$ z zű%WG#(h[b=Bdyqq]kP`oL|,e =s͋ڗCß٨;[׽5kܧӄ/z+ڸcH +Z)dlN׏Wd<)8Q0'%yNG qrmss([Ƨaa; yVr!_{[ΙRdkn 5L=q+dZ<Iaz"]B MESgBW}ۊjRSi4s46|\ymt/2IuFa/j:&Z\ m8>5 O)K4wtsqָp(.d\AN=Nݼs٥ D"v+|ep9c&Ox __vE!R7]"HK D cVt؝P[X}"]ֽ!%Z4F)IIZ{ѸՈ{>8{X% #OLr_8 E8HZ'رP̅Z׍: ZXKh.?y }{%wKvӋ*e7|Ѭv"Eܞ?>*nӐiL)j%"]^dx9[z]KBOy5b23|2E* ;p0ky4`vcPxoo5Y2 "́`GEX&DQoeT!>D U6 T3xӨ8_NKWqKeDwk}f"GHͳ{(mԏq;OիۼEq,sCvt4Ah 6SucȂj̐)diQ8z4mU7M7+rJg؊h`H] }W=[MwXP."Hͅ4  X !cpn]ҩ6s^wEBAԚ1.8Rl}Xd?Vc3Fqړ}Hmu7JalΜ̖sΫ0μnɺ8=ە uL?J䛊I&0HbvƏZZ@6qOUgkÂ%^@̩ b*_S活ݙI(ڏ7Q%Y\ۮmjq:c+EؐZ,fzOE"RTʦ*UškcջXNdy*İ BDD6&JqiWNNJ0TO$g-Ho#+kKGT@3-)؉نĭ4Cn 7ٵn[ΕEJun%Yd&|#ar\~;=w课^)r^ԀOT/[Ӻ!ӍN RD#CVh鹅@vZZbqGX@8Z uG8yg_t^Ww.52)Kܵ@HbAaSK/BUtrb: v]+4ugN߿c68H, {cU]7a` =K4ݛF3ߧN2 10js_txܞoYYϟw*Iol>\n Gmds2L%}~qy!J-~ B<(*nm!!@T] fjDZ;m^Hɗt -Ꮛ.sg9Ī35Lf hPeR?ą#Ǥ~̍vM8Ι~v'kI\rUB;S_UQK*H~k=_R\rX"oiDxGVC#@h]"1&H!v.& 'EDjP e:]@@8" IUaobC> 醠E~֦1 yNmYq?CuɊNIbZ9Co*Ui QWGy6? pt_7H)DWB*T<Hs]F=~ܓ5P!4IWjTz[Zy's|/jx~^>Mww5㑖Ž"˔}9˳4iu>ge.tХ8x?Bil~/thW^3PYٜ>V|oXkp~˔Bǯ~N׮[Q@\ n>[&thl*(iSQSb܊O@p qEmV(xU)P(Љ]^DDab: % .~oq)[J&3.)|{Meݻ?mOoaI9̍$`éMtQ "f N?Phx2hkQ{fq2e2*u[7M5q/iCi1 tMI\FP(6 rvT_ݞm0)\@!ͻ0yDUއt^AU jwB8:wԏxS7t0>l6vppEReޮв0O<[FFg#= O1YKq UQIj$82!A3QAK=׸¨C<.EEo3t>gL'?HirIVB|G;!'KxQʋcn_5;|EQt£TO% Ar<[N2H" f=.M~0>3{i⥼ɨr4 VU%^g"5gϒۛnuǶ&|In1"4gZ X @F!蹹ݨ`<FS8 R^(ǺT-shgg-r ^ ӔsbhzEɤnGQ:W317Dv.ԙr娒J3f+nakQj:fj%t7&Ԩ-ǽ#\5dmUL4uڄY>b3"c=De,3*Vc 0Q%K%4D^ Sa"8r; W!XY+F*vO1mOk?1OaeǥuNKΦd縙쪮m[6;DC5:IqWV12&go/H7H0pW} w *s9Xs8]2hq01aGsp:G]i%F=B4g_l!!Ѥ;dXvycE;cnk[RIGQ[O}c>KS1z#lP[ /ʃ"Iu1oIF p!$mPmr)mX8qQn6ZU;XnZ"!+iaݬ O6G/LݸRNGnPҔz@:ee8 OsN:1B9Y"U,NڤYQcNMͩ|O'~9+j _w'|?i'3~<INC1SqBI t?**3N4ӃrzԯT}Ep 7vB ej/ /p4ª/=Vo<>]jamچG!]@A|50ffݜa%w%&(Ǟp!ptƶP1m-սY?Aҭü o %WmS`ܝYR4+E8?/Ht0N- C#ix_[bp z'jQ_v}E9 ;rP9VE "HHD+Zy7JS*٬jXi7ӃXę_eL}rE}N-E@E9LPul19I..k)*lv~OPvS* w@P4]%^ [T۽ Eu|mƞ`ZiaV>l쭃NWE_~"-Gm%23T# ByhaB5C%^P>_&Ŷ*+8%w'^ Eš{FSb|M?'ew[޾^+MbC0w'R]Ý nO 5gb\GYǐg:ы4(D82KhUq^=?u ިZ8wzew!2B {XFE $`t)DT'! 0fmQ6rܰ @Rk 7kF ooɾxثO<JޚN @Pn \, `lsvӷ' E&_6}|oĹwḪ~˽O}DnB kߐЎPhH *1v5>8p戟lR &q{xbR޸0]H64LNa.(́՛T+̺X4X8\N9aE I2#B́$zȖup-mL鶯}6xs1)|\xIgٰ}v%`a/IP OB4Zz(6VF(~jD)J5rKgc‚ 6Wg+13['tF毵CS&MrY*)e) ci kwGL}vNuN?+rx(d.'b<@&>c 3 ?es3Sf ^Nj.G&8Wn01{G_>x)JLǒz}nfNKoa0`V"k I7 lG4ͬfn}0)g%/Ť`;'ˉx(g ,LVaeu; }B bl椢ܟ1lPW >+lKø_oRD'Q0p80^D$OD KB T$xD!'n:ضi8`vjo> 6Z}>͹vzN5ɦ- s?8[Evu1 TGT;y: .hZ$T&/K[/poQlN b(ddjQ=^(OUPATWtdTґ R&?A N(¦)D"牤 );T-?\P?_~6Tq6kI|}.FEsn8hmlE"ᮧ5艁b+V&)9+;ߎt֊A jRCARe-rY :-uqmnzq6GN9iUH:s5{gKJvsw&[HEL'vkP40zwlUs-3B+#) i9ewLՏ0O<Ώt-⏲}dM"G js'G$(@l綼z2^ GIơ;LԸGIGXFV``ŰpSsǑ@bOuUu@Q[6Ľ= +ìWI/>+0mφM%Dui=Kz^~hYtI69ԣs#a (,%ئ A5$ ɳ@/{O~9kֽs'̋XmD?'w9CwY@y")A{V9rōӯyf&*y!&b+D\+Snv0oOLJ5A܈R#nozQ_=˺Q翘 HaZq9*ĉ`R H@t7%@$)/0p/0L>t2S*&3, xQ9Yex*ļJ` V_BܴLbUP\O-pf{cKdGՆ;a霆VH6b(#d,`D(c-^@3'Wtu$a8=S M=+d(Ilfd ?޼U< XqyccZ9,O}Z"" uboh/Uk_LZZO7$psI`MKNЍ=;ebK/lk"\ *IAof(-^mx[p;0?>RQy+zN hil\k6G h{>%L>j$7m``*!ϥWm,'&S>}ӱ.c] I$jkW# b C7 pQry0-9*y`,M$ph+\¬pi׏H$%?&,TA:6M(_A \2B.EI.N \J??weewkm@#Ӑ:n͠)no+7"S=q a( e7t8!:cUNb%J$62ڔ+r@XNٮWRŁD ڴk+=Ҫ\e>Ǐ M$2Oq]=%ƍqB7c+iS( x to'x{|Ͽᖤto)/d9%4ޟw7#[xӯ}fDž] 1^vA:SиyW9qS!h4j6WF.#]:әR) '{&é=6l߹7+Jɤ~gKt{RswG((peekAD+پG>1٤] V39}ڌ/i9 \EUQtڡ9VZuy-:ZoLʁQ1JP;;﫲ĠtJc7*kY VYJUhUQAn)!J0EDyqsJ2⪯ I| Ti14&z`tWIL0D],-:`'I7ȑKTa-8[B`TYl r`(QR"bB/'Vq뾔_K˧!mvt ̫m U/jI؆u'O/uƹ1Z^ZXfMf"37,޺7vux\JÒ^R>G%'ִ0{p;]&asAxnޤw-!5|]ƀ4H"&JR;<ʵ9Xɋ@n EUt6Y7vBƖ3ENࣻ0!*S妩D~O7x4ԉeV.sA}҄2 twJbcccV7k S.OESpx2^ )Da.VCSm@"k&x)D i#>u}G' jgQ;eAː) EU({-^FD QgPBNVOwVղ2K%d"k61W?Dk2y~SVEm F+֊ހ/XiBZ`)(,(, g&rXwΥj{pή>şo;]$!j%EVIt%;Jx~-%}3vCWlsne -I#;+s  ` o\{՘`Ef^Vjy%ҹKGC4n%qwqYdlMq2$յtChuɍ%e oŋRSƞ?IIqյ|t**?NcIt1 ĢytNLTuF!_TĘ{5U j8}D߫B'=p⟽YE+ϰ`] EQ0f<h_XׂAA$!jޑbujQYؕ("Er"'2F B1LƅNON:<~Ä~R-w;GUU7ֿسnf(fɀwP ?hpoxj\U3_$ww#I*ki· ў7zϗ?kiR`|Cڗo#o3aGϟoF6XB"*DmđmC[MlO*+=36Ž 8L"l72"pT^dS]bbǮW`z&iE4^UV=?[R%#xh4A$Y Ȓ Ń;+c CʋuvƓкgs]@@&b<9?!5GG8آA9*bS._M"g ɾFҠxyoJ Aʻ5%wLZr^<;,mGaV\]:!~sP` "_Zeٽ~{q^-Ȇw>#cA$6 C-+>.S0cB$\ =8hPي]=^.cKz2mմHNOzŒ8h y|gegf?TlbW!\D"N(Ypo ` ˪HHzZɡ:ACIX 3 fvj-a& *!po|E(La[wοp5&_5x%֑G)^2cr@co/)eOO|J9 Hm #W^+E:bE! X ˏ0NJs̯Q= 9-*Z$vZ%ԦjE7QG6|DrD*u Kp@2U['2(rP8a ZB7CWZm9AQD{ȳ[ӟH t%Mz:rR';4lv"@נFX?F}MoW~v[m9}we,S xՑ2ܒƙ-peT00q.V9nĤHABXשuiS˓U#.!gݰ;TNz Hkr`09jSrdѓ;%hlL.7TVjKl?V[Q1c9 2پKdJiU }AZFl:څaQ+j'PdeсqS}V4?oCtHh| =޽v/>;)iÎڄyT83w$( YVɨ(% 2}/OҘqwfQ'1!LJz <j URxa(ս d w=C&Z'"j@d-K\Q 1%- ޢd>WjFQWDNJق06uu+ P*'##:yA`IB|bO ǛyeD@X" (Ђ+HSP\`$c:z'ױ@F|R"sw9||*Dēpo^槅 TF_epD]RqY 61 ǽp jl'A4NE1:KA9 SZWs OB+ޑȄipj|/w7`q8J* aHS?{1@x3E#Tl6s$%!qw~;B'^uUu KdY2 6)\SD~2@F@ ҩWm`fhBؤn ^D [3 !EI`bebM~w %3pIᄇL0~R( 4b~bejVkڶ̛x uImc8zٛH$biH $.&c '<E"dM|όǤ3[HM\ t&v BX} u(]Εv "R"۲G'G44@؋ W䨾Q4|L {E<BD~߶H=`YKCg/v^.(d f¹J½֦iQ H(g+*R *F J}P*$Ap' ?]~#f,CGc⯰]~u'Tv"D& g#'D@,?WIM) ^w75ڦUsd<Y?۱ OiG K `V!Svְ@=CU`R,r[P"GIF TIQ=?k*[L;[ #R.2TDAV`<'*s=ًEɕ K ~ 'g!,'j ;Cx+@4uYQ%؉<d 1_9E_Y0wFdyWmFd9 fۮk5W.<isRNg YnauLVGupnxiꮱz1ao2ZK-yZ6CtT,101NbۂCk []e{!mX. 4iU̜cs/!hu+u} UDdH irniNg猰H߳w^ہ5,L1dVT+UPڢJ0tF ñӒ0>,w_TR 3hK$Oz@@!PIb!wN_>Ptt7H/H[^LLʛCBt5=wnI&,6i-JJ ;Jd1V~}a:5eؾ)*VICiMbskC0%\'M]XPEv65`s[{$ࢡ||arkTtM޸Ӷ&#68 kYo˸9o,2Weڭc /C/Ƞ6Lx?|2qpO_~wkm}0μfvJno979VK$r 8n A^&;s: Hj/ M2B5>}ͪ4h\~zw8|1Ouͼ ~_,ve̅ld>`DR!hQWlj\d ߷UDD f$ǩ{X0z{jl)^ (7z[_dF6SJ"v')i xy` N!0p2` ц5G, jGBkA:QSc)K0BDqǹhgh9s3pGi(kpN@!HD/r5#sEq+(.9d%7 c3hl5mAA[O9G?/1!o da#}a/!VCC /u <ɧ#(>p<@ 74,@vϦb<:IЂ{q[1- ķ EA3 w$ݜ%P @E]%"J[`S)'KP)[]1B o6]{~ς8z#zj|6` d(yD `6@?@_'x#uuVԈ!TdFז\KmH {m=f՛zXS [~&溉hD4'j^n$t^ 7 .+W.ywu\4_)JR>o>[n-O 0Ks` B!*x4[pݢ/3fLL%xE'Qf )VB 9Ή.歎X5d5$y'?ʕJN֧bq֗{C;.e o-_;7??lQA'8cdW&zS}TQA&Όo0_sfi3l ƒbx " +ؽ간d&[°>^3`Z^ *E+ZD@C B)6_-R?#5OH [tJ] uÿpSuNB%Oyv_ީrtqa\nhZ33><&CgIoZm{ѯ=mI8;+ F@g%곷m}c27j4 6Lv 0$ !{Rc@ȅTR"i&_Bo-%%NustX6$p ȅhE-k˖İLtvJ2R449嗟+Bbwo:VWnЁ̎666[ X +#%v,VTDg}O%NDq$'؋goKOqB 6zv5>I3{nt=NCDtT Tȹ /#/!rbː`4LsAcHp>ri`;M795w!RTp7+ЋE̷3''5f݅<@KihӟZ%NQ$QRފ}oI LsIAUvT" _>f~#G ,mx2dad<L)*F ~(=gXٗ 7BH@KR(jgC QLd^S»i`$J_'DET%; L*:T=͉FB|5Ǥ S϶7yy-kgQSm:=TYܥeEQ  A)@|!X盢7N!kT"qH ` #9bN⬸ cU $ymGV֫DwOi!^L!v0\\Iýe"!>:Z^ j֟ɪ% 3a  \Mk 0] cP[?U|ErwhU0G, - 8kZ| ANpFƴ^Yux$eZayTK+%|5KE F-t^"9P.e,0 }xMKH>¡V wX3\l*̑)`&**DNvY.8,gGubD@v@ަ/!Ygx(&tğZth~ax&mΡJ='ǝE?5i2Ir<x90mlv_pZ$t(ZU$TQkj™}أD+ 'kw5;~8ūr.#1ifWX#lZ"*uWoM8n,7-hxiFgpzk"9h:rOsto6 8Tuꑵt^Vb˪k烎XNNvPlr΂t"nv4J]Mx &H /yEb:Pj@Q 9M-7݊(Ea{ǧ?z= R@2u&˃>=#ykcl*6`4O~F{)~د<$"E`]Eg{u?n=_,3L+a2;<մ7;*Τg.櫲`q;s~;&}"-kyi$3ە pڳÂz#˨6D1VDUS@=)B{@4[n@-Gv4vǁa9ZEΉJiLV0т}2{_}QL{$Z8 H&;őT⨑=!?_Jkݑt$$[lp۞zk h[^cβݺPRGFE/{˾avt:=2I: =xƙ/z͛ hZpP_N.yهWi#6ݳY6aIB؉o`Z-gҨzZZ{&<{AiD zKz/j_|T0?SV\#yHۍEHQz*KH)o bLQw@YH/(251%ZdV߸CRV<,E"daW?~o! %P7hib&N@i6"Kai3/0T[%jmxL+mvꇃC]y%H(HJT{cGnǛSȼWu'OOdɸ #8:j  'Nɴ_oC%x8@Q 3ȁD%(|NJh[v"dFiP6c&HJyZa5WLG@wkfS-5ݯzd"̗(J3C5D eV?q}p egžRj|!g}_=xXNb]  d >/\9w eԦ,lt(泑K UP#<^uoT͎l u@9ILݰA[*Qb&^0V܁3.֒2„g+rDņɲC5 T пguShFPڦ(=Q^tT`%60],JnKo[~g[Ġ]K'Sa 96w-f6ĤrCA_JEsN7m߾!+ \P['j<\ЕJ8 izKd5 " Z=E.Z'|hk)Õ=nXUahQ m?ӒgǷM<g`OMXAoEkMu_&ߡ_}4K \r*MA˒>⺰O<v_v\yO?WXomư0ЉEv??w?aȊi'aaiy2$!-Q}oBhNIzZDY;nz}߭#*Y{g<9ccɿ-wwM0˹i%2?6ARؤYe-od(j4^^KЌWl)6Sw[BvY(rإ+wX(.a* R7_IhKv~4O.c ڥB"(Ћq膍'X;m njgFRFvk=OV7$H;*n"U8Sū2h5| 05.+(as%sƋ>NZQCQV< ӷɁj])H"khײj>3_S]Hm쉯=g\I;Fۃ1Y{“n|cF6HHBL&B;3"Y˿) BHO+M,Ykd]%s6`\*_ AiIJ'VfS㦭y;[,}WࠍM::Ӕ*fFJ'`uD(V{*[^ql iRv?Kve՟n:t'*O#"P{[go{fU/UF-<zʭְɯnwO'qp<[~1hJrV`@^F/}/-qcja#NÎKLzycoyrh{HFKeZj>{ 7.pDFsrX@I"a.A&:m_64gJm$qHM= Eg|5fLk7+H:C ɡL`PUd"Jb4ʏsxp}|oIox]1V~>ncSNUI>R8+|b|"&ڰVa|{J뙣B3'] q=ea[+g/c_aM81BTgM샚2ur#IJ4 @* S\#Q[~PeL:j aUD@>:!>~!.<0[RҒ qٌpu&/5N‡MQj5CSxv* "}3^M/Zjo.p{mp<צnɈ $D rma-oIAk/'J V\1+m6+Pu ':h*)"W*nů1n Zr+V/l"CF&Qf 1;pNJz (bR!Jzϒ@Y,%!/v'DB0ʳ g"9CO4e $7qb|#jg!ިsS- uf4g^N9+T3LPZ_=&i])|4;x],wfOِ0%AGW/ T ~߻Z3uM.<_Vi%E äF2o*'c@ZGL\n5U@zhm:7ꆶ~A<*YiΘfz^88b庞FrRK@**oOx񯟸y7ãfN:Zi$SAK6Mt%*w1[ާ6ܫM OIٜ}8u$ͤ-T66)VXwWC}䮴韼5$Aia)"4K!B&3?AʹLad+>/eUmh<:Lonœ&PhHcR)APTIH OWA2L{hӶI"mv0@WdR"0z-8zqŔRHcK$5I}FɱXċ 4Z'D՟N%0@eLv؜R$3co&)='lATy6~P0|E!fPf'T^; o%:GPr80ff=wO n /7Lg. }tDo=7zXt}3#;nGKr -/5{≲Pޠ4 !fBf9YB|'l@Y/5|2Ma#Ȓ>,S|{XœU4aAum\TҦ.%܇B5 0[#oɊwO\M#ܨ#=Em>op\7Q[?9eƁ^m_x9l@>}*E;5C_T5Z%"6HN˭:|b(͗- 3˜M7luŞkx][5e}' EхVU3%HH;Z+a7 ƕX]aYN +FQUiq+A1pgK SstU,Sk﨓-<wҕ{ŕ׾{'׻Fק;Y;۞uԷfP]5PP(mJ UȊ滦S:HhqD߇,(F@7%;'Fv}>A+ES{'0t{wEILY-yBR6-_rR;zU[$""X3HK̘f;0*k%bN(DNr]:"홥+QИaQ1$(Mcg߃Jݷoru&DKg%ʐbk8Qԁn߸W8fwm "PG R(kz3,LMߕ?52}osZ {ES]:s1RńDP/shBnqX0`lHS'.Trw:1>rxw<8~U@N D)@H( jtũ?ȀZQM_9TWqB_鹙p..siHT*@w̥m97 ? >~@r4I7̶a d+vj "S^QE$ 8iL CgTŗLùM=Q-K}5GD3*6 ܂q&F,cP Vn~.r_oVRdʩ$hr?|Ŝt'H4Ƶ|R9OΐN6*5۷<oț0[HnNhU;M?/ ޴flCYm4p7wvRfO+(h%_ u"EnpE`DeH&OAHJSR\΋u2WBBU$AAjrqYE4^] O\IY8}9sn7m=dy葼paeo?`-{F.ӔN>@xeقv%45r'flg]kdL'kp _ =6Ë` ,rZA10,L| ;7u}(}G~$3+q+ xcq $^pdt|{O;,'tm!f{r\}fzt/f J`"D:&T&щ+FyXΐ,F@oCHhauҬ5A1Y⧗UNh$TD"1)y㖴$:D8`]W|/D7ZZJt#?pǹ+SZmBrу1:Ǣ;8tG^/1{~=D|[/y]q\4Q3;_E&5KL! @~Nu{HWDM22+gPȼ\'-~^~+2J$M]S/<,1n~;L-Sc @c (E؀"4̵1I/ڮo#- !8w|1/>\"Jqd!b0"0}3<(| `4%9eǧΔܷ{cVCS#X/ZZJe2 ?`竿#/Nqcم3{d2/LFHKt d2)ph`óxp@uo N[iJk~C&txPbE۠u46hz: |rQ}@')?L$k}3Q?lQ<&Ԓ*tDT OSM&U;a8y93ܐ'ߙ#9S]̏[T40/{ja]e5/?'WxX􃘏pe]{3Aņ8o~SMO]ER_J~3l:%C/lkJ(O·/xq`=qm.8YgGbGzNI%4Kbx?/wIVԮC0?8Ϝs~=9Kk޺qW|k-"9|7ڥ"*肉ֲ5SѼIၛ#ƠC[G+skz/JCPsnR~_Ą+VhwF_iiqAH鲱&/4xu۫4b5ũ6ǝ:}{dM6ǰЫYk M-!^Z" 'VkU@FPwkҡk׬9o^}?<6ʱN66W$ՄG%hG:@>SѸve]"1+7>K&fSÓJ$e^{zC P ݬeZJL0,p:. H/78;{4Sw@Ek0IYŷ]6/2ք|S hؒgwm)~Da* RuTm^+'s$<{XEh .F%2ɦ^Hzv5.k(3ri 5|,e^kfϲ[N'fXn}MX_%~b-շ(Y~! +#!Qޝ0qh?gѓM-ɵD I;Lĸ6Tq`HvCqы/+~MXP-W:^Wՙywҙ]9\:s_u*vDY`A%/uzm% \-q7{feC&VYZ}a;v 9wVB^xbS"3cWo/ .]o|J/>ZqB'¾i}kQ"d(3}[s_38&}N@439or, Sq8r7:ibw3 / "( 7QJء;>-vΈsHĹK[<bUsDz3[W><%١cJ9/DbRT5Y3eVe"_{ ZO MpW 8%teY32TB;TdlIOirF5,\e_Y`\+/l[RVQw?dҙ NYM"ءLwϮxSjji2(ܚ==}x%f.ӾYHaĩ.85)bcnv'i[ Z 阖OCscxiH6ZqD~mZoo:E4przI.\OvtB t}7=0fhi/0DHJG̢UiZD"\R?7Cg,4+^YG祈%4IՆj#qqڲؠ {=33%[^T7YW~ e#IlS)D<7v挅fҵoas37'lR8eh$l΋Sc;-4T#]]=EO 8DKhfJ>mCt Mg׺XD\=8= BPjFIW%Ϛuwwyny~sKEzЃS;'&7ج<~f+ ?| Ͱ:+w;(BXyE#̟}lY^:ctsn'&5Xz$L_'[G\]*iH!EgwǗf\MfeYhjSƿzppmSHPG8vkX]Uf<"Bw)"'Ͻcf*o+QWN6]GBm]zzE7h%t NTk k;gM*Q'VTu;MW*'7:Xd:%{ܟmy5&Jl T- O4 4On(B~!mBWMDO1v=y5ʷnUͽ;Tvgޥ3)Ԡv2;GAg7p{ -K1[fYa[$p!~R`KC[G0mG,gi\ %y`nk{+<| f"; r[^7 TϪGd9lZY"u#f%k/q h ;ŬgŤ&EPW-++xi=_S_ڣ&8]V\^9rzM#*Vۯ-ɮ}0qC.V$C -l 25KQ .DjѴ2i%Q"+m0z#F9<,wM;Ւ<&MQc9^6j[{F,6G>7 Wj.m%NI5z8WRE?֪r&I7y`~zѱhx$PI1 l:-تyS[2v4}|>A X:)эO%h\rBZ6i)zW'wQImשl:7nûn\ ,lD:N'R\ 5T$ m]N<)~d_3z]Vi8 ݺtOܤ ,#i2"|pJHw,XFU*hNxɴRm*B xу,u i~CVdQ0) *ysL|#m%Y=M&#>J!A&ǜ|Et/iw>{x̅GyYDgfLRe9k}|ԩT"ٙlP:eʔUp/g5xLIw^˓{5jD1?kyPdumkA^t {P;O|QWMi3K-J*T ϖ 1ŬNĈRYo[2_=R51 ДM2"炜C2{w&7Ѭݻg½Z{.>J˕h5udRV1يbO9~"=!m(ZC&[j\rG Ů{xb} ;iǁ!31+ IWl^ؚ M.?j?brJ^cGWb <9RJIB4i) 6z gߨ;&_62 @;ebbG+d|OA%l?Ӈ0?TCntVftӤ[\) ;naj;7Cw& =T>l3bZ/a,nbpBXbIvنⴡi/kǔ&͐z ۨ>ȉEirǓGD[?u,baɄ yiYaO14%U/0}9aU>?l^{/]&%D M9L3UXfOW80d&,}$86p)s|l_,X"ufb3²1H\BFL%[JL(s[?WF4D/j('DJGщ`6iifTK3}qM-,T-?t%*!O)bJ1= / `Cq AK;˖W>J'E7Eqc"W TƒR:=AJ@S"P? #0k5Cy'i8lA0%PeZr_鶯]|0(}?>ʣO罇}00<@P $ȫƵM^njT_t{A A_u7ϖ١}=:=E0sEvqyjQg_]xu=ƿi.)b[ɉxF@H__꼄nH xvurPwqMk(8r =W"dӽ{N$%,x>#~ֆTAR_\!M'Dq+$YJґ&\no~7B7(#h2>Q #d!%JE$‘>W9N<aA\IC1B 2xFikfBF}澾$59xrQnzZrk9PiUy⩇ifeeQ= $@X]d2n(?Uez?iwٕ37NQ䙭gaGҀ' ~*Տ]1hǛ 'M5)"d7Yls]?^enbk{ūY=Ol5m팝OŌg\{~߽59|oa7oӷcOW~?3  PqmP|IKFkk[ *W(DŽQ߭8G?jȇxfˈW ̊ɎݮϷI>Ж:ӧ2LL{R“hL TĔ[`ujՅmtE,mmy>)"i C1LG#C?*N\~oDĠ$P k4$ Pg[:KwF֭&h\xR巘 t-#61K뤡C$c{w gҔI*b U`QKDSդ$4Nb@ҍ!;m%[9Xű/O߲@ "MQjzsFBXU~/Sl|QegUp?ѿ  JE!q9bϮL[/tU;T)-G+)ҘtԷJ `xx3lR+F' %NQt'1Ǎ6_a؀<|UrVW?kՠstHC{fNp( v)3)M{Βo^XN#堍ݟ?1+ &%BvD|ʩr/QL? W}{3pX)c/̵J%R'o3uյ{`u qGL`X! ":jdư M>t ^d Q)mSdiEDv "[l(}xnAt%Õ^$} UIAàġa$ P:3*{+sKpo.1^T(rTE¢"h45!Up"hcdFuH)` ,Q `x:51je OHRD,( /yU6 `X*:X5}V;"1XFMpgۍ,F7&V),-4p HҚMWH8sQl08"4o!i–JWʖ& my.'iCp !@DD yw@:g\8w:c8 $*D QzFC|xZH1ΣdI{|Mj8`bW :۪ `ĉ{J?:?ԇ-vىUc3TmZ>%mZLs~+HLpDx ^ssI4~lu\Rj.)vwܕU/+y_H5wjj:4|;g14/#UꘞjxxX"3mV/P \r1cWf QmaSZJ!WjG#th\gGlڅ5jH-Q%%ʂlZh~^;;x_>epņ15SӦoItN@qH'F=Ym8zZ'ʉYc0S'Ʀb(s  5\61zFYS?sJUŕ'ui}}x}iG͌Ϝzi EN$k!).|ݗ.s@95ҸgSe=:%:ABr]W wvINpa/zK [p6a%#D9w|Ʈo*\}A5(2vɴavz({LF>9`nESIK| 8q1 y,on%-PJ+9[p[ͩNW)ϠTsN1ڱ4 Va`DC1cE h/g/ Q>i@cj 1  2@ݍmY}k3' .4!l% BYf+cޠG!abp00‘%d v @sxos~5LF?$s1r% JـA 3 ~[Ja^ᚱ* C@D`6̩DADd- P`:Cc {2 |"DXgD:'[L9uu^ ^q~Ɏow^3#6U)l7&íB%sc\M齫a> `#oEf/d>QUr:qL(=]>4 q93?fT2NF`0"l%\ <ڮ4vt2ޙ<8$dڊ*4*P*2qaǧ}0t/N?ޱk*7⼄A`:E\Q8 j5n^>R??xI/0JFYM9G.ZJCX._'tp!MuټbyH~s}PˌbRW#wEKͩkɾfsH9,oxIJ̋C% >3mu ON'%Ǜ_ `>};.ZKmclIH&F>]z;5WOƫ:ۢ\-'cM ‚NfY=US;C#}1;_ 7ђ7քxٓ]kmoF!l t B ssG:M~Ry+W} FΤÝC)}#A2c W߯d\2W*0$;Bp5) 6i<}dȚtn.:VDX =n\1:1-QN$(r?>/M:yrء7jזOgN^_OSD3C)c&419*FMhhՖoJAo*IltflzjcCG8dbف+1hwk3΅v?i_)2٨[:!/?_XQSR۰R\ً/O8BE]p8)fU{ZU-ki8&#OCe nMB uڣ g~缽0e+&d% Ap5mzT Eb؋ooHOZyyGh9W 2n3mQ  !4UGEj:B  A#@"S0@"4D tUFE ڇu-dK#aT맹 Ϻk¿xN+m"xOXgP -Bk/18aR8{ $0q![ɤ.3#3˥diltOnGT*#,5PUlf'׻|lHaJ ?7Pʼn*I٨@qKI窷 R&(^//rXyC3x Ms^7QszҚCA,D "WW96x\ryU_ٛ82!h#6) 8]*Zl *2,kUJi0D5(L 0h؀5J%e0T(qPh0eǭa'T6MA{ʖzZe*S](Nzs|YsܼnEL,DmNa0)BA1MQ{| ֏oˀn34D'Ūƅ(ˉ?PRyՋ#w]"<ҁ 5$PjRK\$)^1aa*US0DXk& hAHO#ˣp*^t[z63~9xkDT*S E 4k3XB1 L͔hBGG4PPKR*Kp1& d)$/$U372y _e®OjK1U :h*I4V/V8.nEB37U*/h~1^Tq4n >CD`cYQGICіS 7OS϶;뺥Zv&Q!Q=D|LQ$AVs/]dWa:=.A 6t3E<)51Bp1EA90p7گor#HńX% DHDҴZBpq[U6O'z;JE)1罵X?/g Kv,!Q hlpaJ=kGxKTO*:fUfs p^<'L !;ZlH K?x*kdnτ~9\os*2Fip!&!xRS?PՖ~<{S%YzUӅ۬ѓFBo0XWqKJ@&pkEPP]{Skystza%۰oHvKlWj&NHz-OW˺>fv"  $ $3R˘ws^\M44DL 3.-bX8վf kj$|e V`/DCi0F2о0 rf'H_1tbٹ]PӋ52=lkar0͌6͡1Zh|#[ EjT6k*yzT| J/?թǠrFswx <N*D Z܊* pޝ{ :Q YKۋmh40_y4%("JGN뇎U_i˸JmqyB!CN޵DEZ5?z%[ wr3&$/LoOZmd4A?^~R ķL@Qmx!7уI0$"-ӟ4|:<-|zDW:;gH4BpkWBGI(HP[bB`>(J@GNQ_Z+ԛYHW&pB_UO ne 5'f-|v)6'x E.h@x!Ej7~:簴ĐZI <wF(`Xb/DB 1[HoΉD5UKւ`VA0Aj5gehVĊ6Ch9ill"!.o$1\Үh/!t(Nĕ+ygfdua,őհRR) 4 UFpQ9RBD`8@8SJ@L"[hVN[4:Rc# <H~^kc4GhO<"XAjr{WxڠccH @`:S .mf[pp!v_[5o&5=fNos{?$O|D6(&F/|ӏ' !+@3Z۫<.Na-'|Q3YԽt =<*%_G76ei!rv9FV[b~H6BytLo 0WAH%XY$"yF/btMVCxN 8I`$AA7أW@(|,քwHݚO#>J 5 tu1sn*;J\+>Yed(īI [ MۢM@"a!X,`dF D$R!~o q$^7 + U t{|Z)>>FR>%g9uހd̰dB#k?`@ ψl 1 "^1")oڏWgݳj uN6D}# Qu s879sb"?BRqrlq"5:h P8<#kȘ%Kdƅ:FZz${W;87~`t=KK݈W.")Vtmf+q2rF5ER֤# zkj0nנ;䄊U@S܊P Stɦ㗪BMWD׀ ]t` zGa:@3\E͓x?xK^w ]-#͂0^C=Tw!\ GA2W0ӿ>8m9 e·9˛4mYnR`vxiCu0z}AHPA {W rLloN`y0]izBP=wU'ř|;Ŏ;#Š:'Ow,K:] ݄ i$\`(л5tnww'O矟n9I F(44۳m * }+OaNhaX5f'kU+riB$(\!z v>rgmsɾʵD|NЅ=yqn8Ur!S2p_=>B"A{D gA1PC]F[>ȝ6} NczG?ԟ"y1FP@0$0>l7F0vd+5qf*G ؔ$.H P1`cU]xi Vmqpl}gb΀d!6)b>>_7q=[@iA!4\H[d3V_cEpˏ 60P"^ɠBb`ZhE(r 6b{' *fU~O:H󚒄'y@hszKV]eCS]i7]YYA F)h ˍ)CYl3 а!SӃYM~6Mx$V$mIW)wū!D1(WSK, gj]!Ik-DXcZޖ-,* EX Ba_b}uJ#vLKaAi9cߋeXlH!Hh0.\D3AI4bfP8 6C,c BT&KP Q@DTsR3/3~8J{.qzl|p > ݨhE< ˖gE6 FJT+1z!|Ru.%q) jR@e'> }F\@z!B*#9q |K !7)̈~!G'dcUq-/'[l*l%Fve+8 Sf^|CtRG'zc\:;Ҹj1yOD#VДMpV18p'5m_q#lpWϤWJ<f?s+cOs9VOү. ˏcR!B0b-$Z.j0f$,Yot&&p&N/WNMGG0M#^Y]+>,bגI>7[zwR;3=w6# E`Pr>&}LUɵ"mCFsݭs&+v#Nnd} X} ^xԋ*iY]Z,nz%vGҕձGk㛎)Lkݘm`Zn%؅-bN`h0?žAɹl0@0oBHOvNwNF ԣ+7~,[ozEQ^ W^$2M 1^L_6zÿQ5<۶4&kxnrxm{X?бEv=~ #v^-2xЧ(e*rV !x%@>-7ǚ ׯ"nd56x1Ezq@;%{ Y7[9/-^;5NoO4LZ^l^]& ^mcƶp_0Pda1 GTPi(uLb a;pi w7ājwZe13BXluBcgyf"R({j)Y!`VUC薼%*L9Ơ1P8݆s 콦%?-:J/KBSZ^ERU;:q~vHA2 @"6S'9f[Dd"M-ģ3[uɥJe~5 E Fm `4 ȩ ٛ v}0;"uP6->#DbʁB-D;*!6HQ o%*Ne4sVOQ@xCʱĔhe,*GG)N($֙ QQb$HOVؓTZΞǐXֈXԱPC'x)L!&XpVVBֈd"_GDbfAj +yW P"0$4wC3uY ށJpe 7'Xi+ jA'Głޢ̽[FWuwh,)LZV"0* K !Q6 T"mqBƢ" C8ݎ Éj(ZtiQ80mY8ʖ3vO]!R~lp>cv7+d<AnLl_;7I8hF$/P1pޏV(琦2BN5{ ذ{獹M:V jQvT7}*Zɋ2zsBc=/St ~L*8OcZoޓd uo6ͿN [[m +lEBKf8QBR$LYxG5$Fxzuƾ6 B}6ɷB\d"@Bp @K۸$bHmb! iW_ICLF ш^O, h\sIkG\h3W:zr_ך<ie|Nگ'-v9nqkW;iƢ+Ay-Z*d^7*O1:>O]RA$󍠀@!,Aq3o 䫡 ՃovVon~$ؼoc 3L% S>f, E(zw(QѺ,wWYAP[,k]P J-Uȳ픆06 Z2,$I+]o:e,a.7SZnr"Nv n\}MglzZ[w,!  qvB4V,o hBa`a+IVB(G>=H"*2 A JZAX)xMiթ˅M!Uq?)/EZo&ʜ|6FcIj .zc8l ߎtSqXK4z@SX7 x7v6N[ FVؒIQyɑ¾q/ZWCnZ3U*m@Q1#6- )E(Y|J=R ∂W.3kXd2I&< e(iB@b%l)i ieE~ʗRNܦR0hrJB!KRJf}Xà處R5lRW5;K$\ dcm+Xd:m  跚onUGs\aJ)&)&MfˆH/UlҮ) 褒(pePE`wii;ssO6)2kDČ ZNd )lZ/fz4y7/֮9D< -mSf gJD mc )&@GG!ͦa% \٧ջ:?S>H|i61<ö>]bM7){`A1[' Sux0 aua)B3ѓCo$ *_QBC^+of$Ebz34鯶 t9GWK-UD:$>xh !Q&(FEHƭ;kT'ȸC7Ac|=M grɦQgΥQYk2-S?ʁv)TeèkDou쾬M:3CC "f_ pK|:U f]Q0oWu[5W &QI VB1F:Zm'{WPN>0IȈl%[F ru_\_AAd3j*d7WX{ߥT C#<{"yruZ3"OkP|ҋ9 ~;Eh QI"0CBz?$8t ꈼaXe*b#KUr+Il@gjVe$Aaq݀#NjSeV^˾Jݸә?&}sWq,G\=}̄ʹ1FW"!l@@؀ԠAR=i/xksdhR<-\|2vIVJ}8mi܅O/HFtO\t4t'~kZuyݍu#Y>6IC6ֹgdl҆a9 1T ބN`(Sz<[Iz&s=VՄ9N07U$:TFC,xRj%Jޕ_E U'=i%JPR%'}UTԳ*JsFp2#U 2$m@Q|GF,l>wbd&~9EF& H46cm4FOq@MZjiM :X*qO+okwF̖sO "擐B@gYDo nE~a+1;Rճ쉠5,[v7s^zibW=((ȔsG-+o`'D`ŃG8@)zo49L}08iS6|/diAx@SZgf1p5M="!A f. H\j#ttT .*vj˲' zmܴ> s+]:{,oc`E?eEWIU r h@VMs$M44|\4:r6Z0͐}+> Ytpt'M;UKԁz>%՟r7xΑ \0Eiۀl[Rn qB[:9 [/#z(Ll~~m-,rT @NAᰐA1.8(Έ\|J+Lxd,!D%: !K1RC ~[ U6heq*?+!0P-`C H$: (` :-^LA_DC'.5(aP8 'J*| Ry_f Sʋ$MRPbPdHaRkBH^K*-2 \JdXSXa&dKHWzkqQok@O)yX`81O@8jǛwE ƇȨ>Vz I'01hа@w'~ xx W$I$(F+rڰ^.НCw3<V~rbxh]KO}[BQB aӔݬ,ijFePأy-NJvUߍ?("\JZ02ub/TҸOUQ(ePN4=BS2 n8EU:  RMnjBhg>*?`nC/o;Ms~{_]% dI>UW6}??%xZ+ǸL 3ؠP Ra!;M UGM>|9SˋHδ|z>19* NIfK\3\ bɍBi.TJ2k> hYz ^ h(m*GVka3hݰTe-S"Auɬ yI^N2l4n{]Ph$<œcѓwj B=qRH}yB-6Ӻ'rۄr4yHԅEܚ5qDm@ 8eczxEM,hBO/ Ƞ<@ďElS#-?D:jK_xuv_v;s1[~yzU qcQ c=H@M޻L͔e_Hzvuax#ׅEkqJR \@#ϳC6|#]֣æ0w=q %g߸ޱF4)͵5[/} u5WoqRUh[RѱX=hfrqFttOH!mQmQ+#珖1 c᫖D ecȐU)l-UF Ibqbv=JP(XAk#t*j2!N HW0YBV+gęD5mW-+ҘU0RZ3Bg R$?y no&y1 ՠfSŠa{aM f;#JX0N(濈OSB< K:\A=h>$)$`$XtZHPH RZ%GcP".A%%{pAS;XBBR s;dSS7=&"<0\JK'Z˸S^daT|l?"B#۳U gTנ^&tdHMdAb!R*HJY R# MSDl5J$ !ؚ*U&?Ȅ*e5BH(ӔYZZ2ByGHSFU4u{CP$vIThm٘BiJSyQOnV>Nv ûwTͿLzk" 1TE Z)%9]J=Z㣢F9 $;Q&<11'l\I" }`D`G?/yleseaB3"0Wb /Gb4)^r KiT D"9l~h7gbQU/)8\醆 0~!}IHX9Ax,/?4GcjLUًcwGj'Ziߎf=x{vc1*גFNJAJLPi Ṉ)wgTJ= PJk"=K/ά# DӸ&otM3/83Na[9QvՎ=|VיV= <}!I򨭞v/{JO;rs^hBB@c0 tla޾l6N>Tl+62XzBQ`20p5':JSʂ\ @#@|jM&\z5A.D9` 8WyEj3\JU]/m,0F.1f7ɥ]$bQ/Hޓ 4} ((Xv<{L~ݔ}fdŸ]8tA&c~`3%I#+`Db6@L)95Ӟ:'teI"䠮ݜ%[xAttͿ%2SR\͔HI1=waiH#?">5K^e^w 刬ɹضxYULlO`_aه'l4Bpn:#9нii34H.7nBՑO}྅Z4UiP-%,o82z\ fڠ_ƇT1m]8nn$ZS1_[9r*bQLşNqy;(NL6HcGSӊ!I, ZDIٖh7Nה&˩GYٱ OjQ]C7x]Af0h؈ĵo2EAMKfgY{O߾c<LgUA &RlРA) Tqo];A hL!.:ufoԷ',5/^E1-?3|x'v89/J1WXMmW98Žh9#4Wشj kajE*IIx|t9,ƯbR3uz@G!afܒoH@B.3S !e*?ÖxG`?llaujRzI.?m![+J qbJvf̖h;7սiQR3/(% HN4q/YarۀP O/YoJO{4BaAJ6(de>T MB !F/ǜ}dW}ħF EU²IQ[TbAـlA, ka}!Ȍg,֋-MፕJKcK yX ьdZQb§y,$  0G6(1Q %;b>ߤ PRI⼏N wvώOnP=eU#6snKRCLgC ϧt~n`b,| !$]EXSX$!9N[-w>S=gmNJc%!}O]I`"[5D/)$BИ@/>E>fk0fd{~lp+sTF{n~u'R4T )W9(:9i!Ɨ)c(LGC֧\:關-ady,7"< x̆c;3EYlhKQ7]N١5TY13fbRب7'FjВt^JrYbss(ROz` Lt4$f x54yw^r%|Ҏ`>j)ºK8Wh`G_dV,ͷ?|[ $BSVdSTOfEסO6=|oߒc?{I~3pb]9L彃1vLMGa΄W&AyڛbBɗ]~iNf _B^@%pVue٬ϧCgsXSES;^.oWuuĖoaDsVpvuElp V'NB.!P%0`Wg߻ Χ8N/*C.W٬gd2H"壒 \ @ 7UD[CX6|=dQbIAc}7ql^AxXIsj*( ڋd"6"h$40V-O!Aky)3þWVtި8lX%?:$S9xr/$+u+~q ?* j=*;/nF}~{{޴"a1+&)`IYPB Jdd%HP@X1j>O "kp*BDuW: AC߉ C`R-v,Dc =Âx*rJ(lЊ)^u.5TvNh*z>Q1 j`K8E\e .ںB_* DoOW|hqDWhQiCa4"sąK\5bcv?Eራ*gô}` evMU!􂶊YXIW.l Wh:QŠр(G(V}mL!$\pApԳk8R $AVC]&C*<P1yLr@$Zʜ,̬0 Y4NnN!A` B<D|Z-A1Nph]VTIJ8'WpՌlY{sok TnQ3 AiHdh3D)P c\)-Y`2lbаʠ һKWMyƗM|ϜN ~trLcA;P"S I: ~:MNO]{N_j0Dd3Z(Rkݷۜ@_0jUΑ>#ΰk!ȹ.1tb~ttT<#vӄ 䡄mB\jUdo>/~pnjmq,,nS5R{8i%[fgyܤ7A`lBVNՈN@gz㺉sxxIWgܜX<Ţ%։f~PK |Ye`P2[U'$h" A&9R"D*TU@DzԅaEp%A Y/Y_z"M8H\)P9'` _O|ye @ Ddsi 﨎/HF[ %Q@ 8 PA%BRpL!پsSOUy5-77͞?\ƴ}#џ"ttN7#Hʩd XвA  >tعdҲ .xB $2sij (ҭͥ&X[R,gۮ8|z&m¬1-Үΰ=4wvJ])@4draR c%# djH(2n)v[4_و`[SMvعͻ1ES\|poX}B|ZQmtttb Y_)oPOZJؠGF(G dj| Pߞ 'H$MGL(,Ƈ((Cls!Ph'+[>mS;[b"7=xuB2{PTRѷC䓑@PD(ek.=@"DDZ3fb7yt X$- r* (Ql}Ɵ^pR޹ Aw^Sbc]Zݦ7g4=Qg 6#?lƼM?-,|Gx,E6u @A;bB)jQ!h 5c$#DARu; [MΘb[W68/LHD[P ;.x I::o51kzurXcw槒=6~'7H˪I^|M^X27iKqߴ8nJ/(-iY-/#zqޜl MBl.F(o.K&350Hd oe5~oN-µGT2ϑ/~M?ReQqm-=H'H.Nr5qם@S^j@l%uHFOQ8@ 1bqC\^9л'l"DI& (L!PIDPW7^Ik{m/PUޙri@)#GWM*{:"j~|Q&-HTڝmyQd4ѴRDaS(L Ci)\Z}񕴆+'F%* ʠq/^* .ѢWH֤D>83!8pkƅQ&FW t,Icm"KPw4=W 0Sȧ56Ix~ߊ&L^={FiFSORQS)StE*/HSz{ 'A- ENbN!#bYCo{3zl<۶df6M\.< )[d6uǭBy~J H0(%N p!n֔ApSzT;(#F,u2+Y}W#ڝgB#nnmh_K *RKAJ"R|֕~eey[oVٳ`^D)v2yMD M5vg 5Zzԅ"C,P@hϘ!9޶y x|r=$MV$t9! x4?a[0x,mO~2A_Sho*kBv*xNi#/p8,K̍/eh4w}0 PklB= ;z 0Ыn\zF%M*KySvq[$HKVс=6ϹBXm[woPh+ɨ iJ8aa p틺RВÙI8;j bL+d<#;_P1+&7=GpZZ+0!".J*㣲uc.1+ AbX T!::ehD $"\DiB$^1؟WiffetS_*i?U_%not؏,Ln ^աv(x5 N{ߚᱵ'~>I#ONu`R/ornmR{[-n%qղr[RwqRRGP('{ϺꞫ[5#Ox m` t]q5ͦ?mI<76N%UP 3fKG 5E4Vuz$"0ߗ9xjZsĞۿά)+eG){3٭Tn8k??[=:~cCd B(V1}~iMҗ(]SK(&F jJA2>, OB>Kj+,<H$QI#PVx˸ۤ4{;W0gK2Pf& Kn!A!R)ed%pˈ?߼5ol"J#&U*|1eɣbʒ1E{j79 Q]FӰi 3d.FGMϽYgyٚLUI%ےiϝL 03@g9"nɵ5e:,Ћ\=OtW/e~Tղ&'ġ!Ef$N-&gdxTYDʿ}~HQdǕ")@FlV~{uol0#%q0FNid t tu" bC7w?:Ces5䤻CYm2l)auvHXY-B?iKؿ֢b" Q-I%(Tn|(T߳$@ݰj]_2C:|bt3RW{_?S{:x29bʑ͚V?ﰂpayl[r~UM(  FQИPlRńW%̑^-@Ψq6]ܦ6Ѽ<:!/&-gLu3c yJj@w#fǝ{𭯿tߑ-OYg(Ikgap DLH$9Ol2l 9Gw̛ '8EAB.%H!p鵗EQ {zbvu߽iћ; 6w;>l)_KHr% OԍTq<8(&ŃH$ʅGI{簳nuWژYvJmY57ռ$ﯼO_9k>s7hUB Ȗg-LNdmZ?u[3XK2/Q赍Dט:Mџ_% T0U7FEO^?QE#~6}x|`OV/v]J#2ӖtG mnN( +ElYs0 YA:Ȕ-}{RN4mhCl(:}͎'Δ>`j qpa7}ӣ߿FJ^KMd}OXqSDt^0ʜ " `J]u8t_ GF\ZQAs?yA=1k  7D3Fi>C' 5 j_ʷ\Rd_D@m8GuR`ɰ"ޛv'a& ;\Ұ:F/JMPaaWqrGbz{yhс6`nI$ݮ.MQD^J:&3+\h?قەKV>_FVW0Ō`+τ6|Ť:ݷf3wR b \C@@ ֢cW2ŮhKC~;g_̐)es78SH RpKPRɣ5:x4!u51/:~ayO8m|er!tY{`Q ^ܑd{,Ѿ^i92~twgu+~\ w_cQ>.[Ѯ%Ҩr=*2 } O;ygcתV@ߙ3.Z*iMwU SQuIpߩhH1HMg~ּG7AE ]Iũ%#`sjW<{UIcN۲xZS=bn7($ 7/~9졿9|tRgLk/[ًƿ{~_C$!.:t]xθk}Zhㆤ͝QԽvHLWtCd 9kQ4R3OpO""eE*INC-LUnƇѯ9k5zm jYWAD@D)n׏,5E^*juيZW 9;L]+k~F޲mtgY4j{Ti+qÒ&{2_, nK;1CW*O4rVM$!/e]9ygw$8,k M^S x鴗3F Ju _n{[jo[nxm:kt^Z8nɽ3uOA+OjShF`_ܨOrݩ9CDeJvǢt&{Oxٱ΅^6z*"E؛d`# ~&:]{cI]RN?^Q{P[$Vc5G3Ϥm~[>'=oBMIZ5]p fA"g3Fx^Ο\rH;O+[ΟxJ<Ìt~̥gXdژ.3^l#>kӅl ^mWڢW#;  JoLl2FT(@Kv.}DVl҅`"H"xwƙ`ǯt~<%ʭmd@\Ơt_8}oLg&@JZtiְz@)R;n+!~aM!'H2dXC¹nWd+Q& ad0aW 1'dG>Aʈ}1>EGQ=o=*b,QMD4hjoNAQѫ\_;#ϊ1vq#XtL [Sk?kc9%~q 鿻n=Onr ɕw:,yғgx>/s9~0x[.Y*#'Vp0iɘQ(Mu/DD(̕0¥ͬkע+x:V\HjЀAEG64kjY"DycB{|\g{( ?2h<՛3w>rG^Ϝ~&_O}OqvW?}`I34ڸmاyhN>7biɷc;$歶B]W I՜n'|ܑ7;SO>m n4t**j57wsjlroZs/e;ok{OH)ze_ ߷qo>VgDދGo`#ڱb27zZ;R|XjR UBZ WUkHM["`Yk `D3nj/i܇&'o~_n3>L* W;?zDBwwEfԂ!Kh[, ܚZZjPќ<0ƙw_[ kSG }{{^-(>q[ھ'+tEƝh7 %)] roMcύxٹL"eبY=]2C6k3[{;Ӕ֎є /ڊme˗8³ Jk~+Af>?M뒚.Fa6WXvoK nBn郳[٨f֒0.m.]٣Ν(ni)t(h$um揂4(cӺMB)kQ9K Øe]_j pdD`a)!H0BEA$" VNtO=^~fy(;΅:t?t^uwL9-xynEL/9P<>jcGOvU]͞?mu',t3O~ݽjckv)7{'Su7yv~ً=i\w1n/I0qG4<5W%x,2aiXPlf +r{_ve;B Y5,ץf|coR 'yGgϝ;}D+.)_yn,fKd}^7ym_}/X7Zy3ƖJ}6% LB0^G\n^̘Vuۙ^mEa)|3^KajBT e2V,ҕfMhMOC'hᘙ~Wk(6~/xX4;}W7jxrcJdV*i".- Bn.mT<7g ^{o.\CQ _2"dgYJŜkBV667/I|Լ .w?x ?ṖvypFɍEO۾~|麑^fmKd^4^pEo[:07#l}wE7xu6=ܶ*㇁P՛޲(K,k\qF7{#/I. ޶Wr&k3ne8Lf{- .#(u='3'YuY3QZFV159]\AU鹊u 5heBbri) j6I*Ts?1\rQ[XN"'{zcJOpL@J3f=i93I;c% Dq;NgI-PQwQgjkJyȥ4FEB>&W6Z7k㧛̤u{I*sy;RM.3[y3Ivw˝j cz=Ap^ RMg\5G8]</VfƖ*؍9$H(E`H &*A2#:D$˟ĥ%4bX2Faj |cD@05K[`r̊&TDKnٴiI{[6tʳ?(=C[q2"J89vn/NkY(?@lϬUsqww3.קw]}y3̾+0/Oϻ_ЌM裞D18.ly?ƶZs1XGc/]xN͌[u31/pJN,U3 17{hw4e<a쮛yO VOgw*x䷚n G `χвN/{ЙmK^{AùB|up=a>= nLo o?W60Pnsa731C?g=Pukvk&#[(an LJ!'auP P6DGN-f_x2ͭ_פ,LcOxo"E$b j ?ּ^٣q>Uj;Gd*OybS w7+w7B>q=;rp Wu{,\soz7_jڧr~q~bG8qħOn)p^f®g:侐jYA^R[sd^vY++.O=~粭4JPN}NH煅Bw}O+'5kmi$d23௼i $̳،ּ:Xy]ba}$x/"-+, }ݫ/W _[~ןιzSIYO5>ӯopݧwXh}SM7v]W©vDԁbG9mp yFrUWZ}Fzh6$0(tWA/:flM~΄ \reR|#)+QS&8-kwZ(V%W%O*UkU/ݼ+7ysSU5)Ո28^Fܶ7=}BZyc]%RۣNlz9Sxa&Ye-!gEDξuՌ\R$" " ^c'r +p ]߅6F@A1˗vς{T\µbӪ֠„AI+yzaW5 !LHtG4cIh mwvvm_#Jp"t!U"xz':Aqnj76Q i!NIEUCż"‡{ȏa 8aC(A򀢀D6Z!1 0Kgn\r]}ut `0T(&7A2Li3ػC6qHF**=]|6Q ݿ{Ԯ~+ Jx?4{7 ;Gl}4= yZzMj  I'5G\PNCa?٫e.+}N_15L-]D?Ȅ,{nIHv`\F/~y/rQ'rYU)-iBgF21>7W-|'XYnw{=ym]\W=ܫw-!|Fd qbQk3q TH@Ѱ<_>V*öV/]j0i+'߉l^3:S-DgԪlw]) (/źIA!3D VcsCc.F#۞{$C diO)xܭ_/dl$.bD\Q)PNM ;كLg{6͍2ըlWĮ w*^!^ Z*=9OGjyPR*0s"TkfFd]RA_{[Ys;7f#N-EE,2!I*//lX(r-8ӧmznZiAcs=~"wƂg@JH*Pm(xa9qM7!}CaO3**s2= SH)8NoMx $̊ P :BD]ѲiiNᓺdr VYOlєc쨭Y23_ QpHg)6&No*]VXq۲<6 w4 v{:v.W߈+ʑlo]qYHL2,W("7X28o }32U=w|̬ʛ -_H_Vv%9^M핂խIN S>^ >b\&^bqE +xXDi LD/F1Oy(`9@G=@Q]; _]. FSE>09GM,كI橫9 xj& ~k:5]/ 6M!6Z3$!nro,4.Du3dL(oBqk_1zNM7AҗWGA\sпf&'6'r/QC^yp@9pɗ wny Sߠ>Lv\6kLJ"wX$޲ 3_Zq˓#L{!wY8OlWaG#GٵnTЍn 6OK8iB}WYbFK&c.2)4c3v--A$H _t-+,z&dtL'dg˅_ (*gYR$z$L$, %D!@X(t*bXs2 qʨ0:.c&TH hBЏB ؑ/K\Dqv֙.\uQs $3ʦl[+lτcgy7wMcP=Z.UɗXJ2B~ЇI;z'tL8SrbKOnX+7z*;7>rJ7eOe%- ѱ`B7`/d҈GZ? *&)^^[<ʟO;u0b}>W'|q}FXyo\k޻%M޼h,CXoƺ0xӮu]% {kAE/Bg_KMB@ )bUǛ |*q?]9MߌmܒeZԈOS7|Y||N}FgMؽ֣Q;iL(5halBM/ƒ5Gn !)Pt $\f3)zvw7EiCBv.w:,S:MA\*ŮB, =hv##_PV{*D?/9΅n<ߒfiRJ;%'yW>?*j I[њGY!ɾj8-YHp;֚[ezxs#M3*[AlP (LLR@g%wvN xï)< ld|Z$S5_ g3Ww7O]i_(aYQ#FM+ڂ=HJ 6  CHj6+g͢aF;ۗ{ҵeYpbZAZ7k1sA (!<[qv5y}UwG:}f-W8R-^+:z{ֻ6z?]2MA p%Qy2@ 19`h |6aT)žhalA5 &}$!^U1Q(Zө`P1h * %F,!\& J +R:*W{ӀSv *D}yZ7ګnX-QWă5re[ D:CQYqprkl-2-TE<|RACT1!=1E2Rw; Ww/ cR.J5UY&wS'CjF_ju؅&lQNׄ_s_̬Os^.|!=uK3>akt:AsXpFBQj\5;MTYO^(`;!OT}ĩV<K2Q Mr<)Qulp &rGaE3|}4z9AkZ/MQxɮ}DB@')H'H699j"-OynW^?BcCU$"BЗ FaY&[q}PtAA>,%K}((L 0@HWkY5E~p#:y@ヹ?=UhF ؓMݞ|j3ȟ θ7W# W0셰(WGҖ='RBXIF0oχі3/5˄ļD.l/K9'Ђ[-CIDlN}+`X b ڤl72_-3;40p]wCM׿x7}Sz'@&yB>` yјY`_8 O~]}'ǪN$js Y*euָ} Yi =aF7hm5;Vw>4}űx~hRzە j0rlGsR; ErJA.91tԥQF>Jp)Ӷh bb=kɝdy$WrWX*DeC[yM_< `A4/c@`9ݥyљT*2HJ-,4N.Bރ BdC/j_jT)aZ{JXr `v hF8C((㏖(/n )HWJ ಄SߑazȪ$\حwu}v""-xirt.Wz-is!iqd.i'ǐu}t]6^y"gV`Ĵ1d_ND3$b-kr%m&Ji*w-,8lra!XFCe.."$ap4Yԏ3?2X0TG.ߟYAWJIڟ8o)>žr˨~7?cz{?cgjYǢɿT W~ 3㎾H]01ao r. w D36wLߨ+BhUm@зN GB4g %P'TN- 9|{]>[GΝي9\~5RވIlDBuJKPi[[bt{guv$"zul@CC !P +2aI!^4"J,V5Oѫm!qE )3LJ' #`!:Ft (w3[cr<=w<FQ33rƈ0XX"*R}Wu0H w ҈Mϫ-%rKUDUJU%U iCA(Op|t>2")/Wd ]uVG?=b~[[0OI9r^O e~hྩՁ^aIJ'ӭx{{8m]}S-r7O?/>xeԃ|Zk&ʁRaוX(5\C:j5U"Kf_͹.K@^|̔mWScf g~O*nfD[>> F8,8] m~ο7 '>:"C>t73zە:t'݄|U$H$hCpkϏ9*’@89[T/CiAA t]9>K 6=fa,Sȑ$W:5\{R kvT2*)M9Uy1{'>5'Urۼ3Kvij}c\Q kC]ZzOo֜8>88ְ:1!K(8Q G[a-Kaӟ9n#LVξTU[Jף1uuj~ΝoQGϸzVEWa==TvȑEW'{.Ja)4t.D?GQZNjb5' NM@cs N4%YT6F5W>m/; [ŊFU!Jj*Q0AES- ~aM*?mfs"Q)0{)do b|!$JIK-A E hvS XfRUE}QEz档?(&qSq%*Qg?Q[P_aLp z٥ڟNN"җ1.$$'Qa 1~Vhh'F[A=K @et,~qǫwt{vVrFhSUD|@1(6`L9J;U xS^ _h'Wk@3.,pեSQ! ;!Y) f\<|*JR)eOs͍?2'nh3K?]imukG~J?PwhV^j9jspcK(;eyLJSQ,Zj&5W)scIՆNYZ'D0daoJLЈZz@fjt~x^:` T# |Vئ}SVZ^k8MPBCgw10hPDBPpo`(XeB9 -:b֏\n"><7PΣ2B¨Hj Gջ8;jvy*=|-jl䦝g>0|~}ޒ6Uxڢ< ;Hvg]?m{1B%X#oC榖/4z 0~'.nz fIc;Ac"2 -fjXGӑ1绨8cJY5# q=;&lTD02\~?$j dJQMEcTz!Bкce/iSaX//UHaN82`J!E^SC"Po׉2Y%Ľ\}V%~` FP!d(P:_TA2yݕg^a`C5cC )W 5V4aA :Oߩ[#S8A[R%eh .$"Ɣ! 4=`tR/8e 8lx.YVS8aKUX-r"p]dN:tO ?zmw?W R9rU}1X\'0kG2Λ~ˡ}0"Jo]:LPK lfVr*R5MfɑX/'F7S'glp3ov9!9!Glq Ț=gHاR:GRزK2&')~Jshv1mJiRɱxFhc~wO5esͷ/w3\3>-?u7eK#" ;jrV*w +t݊W't(jÛ8|xx8{dᨼ\"w:`⍵zR͛6k>@H4 8UIwR M@`Qr"SN H$gEbc9[Ofz(c۱U#,sʬNr_s×J{Wa<-[Z5ADJ#}.K-EAbӪeDf(l]Xm[D~{mPCEZmТB_S- VěnYcƧ>,hxQVQ\ )G0gqzޤq_}V0}ݏg!o-eǞ|W_@@cq0AJ̩6mqRsfSvdB l`нwif'FlP07p)rb:>6XF.|K|Dh!:J:";{ ]YhWRZ^m;#j Z!3$QbњЎf Z`ƻ W\ZQB%RTrK{_[AB^zD$HHFQ!=Z&.HVpF9r#Bۿ0@!W>RBBmOa($jB` "2?!( bQht>ۀ?gwq)X gكnigXCeSeTd4ɕꨑ7IˈN;hqeJe!< rg/Ujo`+K܅ 9}۞}:L7n,U9: \pXxUV&!'IFXoUmjm C>?9*Ty ) yҋե.hBGuնn@kd_YQ%/J%vp j~v!M7!a:< 1+E>[(@ P (b 1,V(e$NzDUDܛ'n5JRpwM@A;&X*HY%NfȰ ۏjG7@uئNz&O>aDM,s:ao䪽0oRg_'-Ntr(k[4#.6!K!#K&8Ga{=^JQCR$;eeb '}4u(~5Cwyꏦu_٢={{o=|Vd,pR9=lE`+# ~Zi/ kIFL Dz3(QBQ:5&㉪6TiՎG*+7x ?5o8]LX1d՜ 5 NN0F9.I@sF@!.'M-RY0hz4> fx3^{>GƝ57n~:yI G҈D3Kj&0d2+@IєRD"Bqt 8*{zn4.v"uk:L|˹T}Ƿ7u1s_ˠӼ @| P[3=q@V!g="J#Y\pRv{;dF?T2^W6;(LTǯ#Լ2i#(׷7AwxDZ\ k.W[b7W>0]ңǔ)$kggwOVჳs)uXy^4X-˺ꢆqYjGbL{轷m|&фnZة I{ b2ޢ D<ƌ2s#bje,d+~9Q%Y< ƥHf(ˈ˖o{\m}#"5ˡZ>TGԂh$ +X̑7>Ų8 U爷>h6*Y<;I eJjަ{Kb<\xCD+z DX X~YY/.9k&g d"ќYu* {8&:wE.Ȼ+Bd$ Ӧ _%]\9fgYFM;F.bv%=x)E%(O}@,%W\]hK^򕾘e}o&ҴB.tI8a ]Xv }\.Z~솲;`,\DRvF_8Y\3Y ޗ#D%ײ9_jZ>t|V=T?ʣ^b%QœRO'QbUQrPnplg.dmt&ߍp T.;9∢/|/iJV$ّOSWv6a\= N 60;v;H$c(.^Os3lY@E..LFE@dPQw3c0:ΐ`":dٗD~AZueU勆*j.]GwUMmcm9kٛw\k[M%ADǤPG8+VҗP҄LFm]}Эa9xEƷkFӯk#XSfAyuu6_1>]93gl-d+sNZ({+8D^׸+B>J˺p(&Mp$jݲX4`"={zm˨-dI>|XؔHZ&$:ɮu̔}VՓN>Uq^UdFQ4$qׄG BH0I(@$ 4Fj\̓n+ܡU, HW H!3.@F!cRdJV͗fBODdN<ðX*E KAQ1mtw>~A}*pxޢFo 1 {o0%$љa xlw zo'D#հ6}eRJzl!2\^OҩYRVU~H\ਸd e rFsDQ4|}u7O@Gd0>qgT>1 4Ғ!D9XR48Ulu?@Ǝ*CiFFt,dG4MA$3#[/ r&af6#/\6_#Եyc-lJ.kՃojC#7GTJiɥ#>@o'B2(fΨahw(y}0KM([tKf 7ݚs+HCaYt"HTfhP1)~'4!t CEG%|%Vң"^TC  yC[N !0Χ‹:wkf,;| E_0)hXIL&7E`[sw) X&40!\٥"@" $'ą8a0DH @1 A$D3RV[,e88 rɲ4,^XSEM=@3 " dA.dM.|;PߑDtRwO[ ۡgVBM7;F7][4&X+"|\}bȟ_5we5ZTUxaA0i2it)YgszJb5m=c5{Yz3e%j![W&03LJ. c;NV@k͟6 p I:R/tZ^7_ses)sFz TB H uErad3CPPWT#C_@PdT#z &|+}) Cp#H<ζlN(V#`Ha8a(`?V!4`XFis^QeQzD{/[lwlUo a|f j.peH\ZNTJʹ1z:9W~L'1wtv춬jWy$znG>-wxFR#Զ\"w^=}Ͻ/FB!wj bDt e;!BLƃD!\ί1+PSB(h HQʫzyF*]@ÞRؔH(\2  XZL`-Uw @Zd0bRȤDyp -U+ҫW29DP`VpITDqf˿nW"$2Β6'I*T1N!PM,O쿪@F$Y6k(,FxQKhR}_}{| % WDֵ{wiw0-F=(>W DxƜk7'_8MP9tJ&"ۧj 2P h)|Qle  busі sHpqy1AʖUDGmŊf|ㆢ) EАjӀXBȗ $|_Й(xbIj$k#`իi&rߕ0Q&5g -GJxol,2o(Mc3 ?xU7KCsDx!:LPRXI1,I7 >#8 { ga* \[tl$<3daȋJ֫'7@bD*ArqHǂʖ]$.ǤTM_&n#4Xq$H.*.wӚjgsyM0l4{Q eU0==Rc6B1ԯ8aƅ^_7U'} @f S/fs+V.WT[k>wfjn~;؝MݡxJ8j3(x~]h]G ^ !"j\9R4 h:n WX[]]>}oY2b۬uX\e?/3=wp`tA1j+MрIlcg^k`*T}$t$V:#$5$ #xt/N;xz >4 "UYV^J\~{S/oV)OAӋSMr`rf#sMU;:0-%p^ՑW-[ײ}Y7q0Yl:0?g'=-J/^ckR{篹gNc}zRACQa8zaƖUlkИl s*^$j]q8k6*ۚ+ݛm $$9K{E`8_"FT)I=7U`MߌȎ5(I0\Xp2F DA(lcb`iJӛ]cCn0 댎ݤ]W {Mm?O}" n9`ORwO3lFID Q42C01gX\{s xmACt N"ݣ̣Oj֜K{տ2*kNݥBhB9ӆ~"}<`1K'WJ %[!{%x`a2 կ[:VC㫪y) E;A:|TשEG@){L-5IJSP:U;HRÈ]*jL>Qh \&h(Gpja1(Zy{z !%v_{Hp!%껢,uqA 5 8|%SDXBQ$;Ě3p8!:J# #d WWm}_R?U_fy重Gn0/jԈ1 6h@1h7YF<8f 7V^'B{ 7"GlnݢAi>myD\\9JcfO-Uz<Յrpi"E~$W&ŲZJrrw߿F).}g@I{)ѼtR+ˑ-|}roe.kIkEМ4j8pJ- O_֛=-P{14Mٗs! L$x_ PEygLj$ÔQ Kl}KVXr#(`.P}Z ٙvUZj(]U;!> Vc3 ٲm}Z+JF`Opg{@@aGa 6h89ܮ6ZԿF{R "vWZ˓Rn *h,;Yp;|7זTt|{^YʣIn|DtB< Bq!HL)(BڕڼyƶWOc)bAl)4o,[?Mxnm/΍91K3Ò"# 1 >Fq]FP_$(#)4{@%DIwD$(Bc$ ,WE3YPT?a,>.A;m#MYnaμrRA qKian.r8~IVRx☳btˡ5/Ӕ3pRXbiH2 ;0=/:9sa\R7 |9w^\PAT,]=b'[W HE|uQ;pH:$ H#c˥9)؁E 4ؽ?}vuT-"+>%Ġ*#( i=,UbܝF7*$#Ȗ!Qp LV{ qA) X! 8'$m˃P Ӯ²oگ+=$zOv>^s5~Jq闔z2 #tɉ[R Y24h\YhDXp/Tr,qo++~QwQ|>ֈ&bexOP&k!"ZtsXA9xP"l JjAVTJ@1VbR⍤CNA8ayQ*)h`<*,$[#7ڥD Æb}} @)b͔T#@T@:Eyd ˰áVr?dYעqŞ@rkR+^jx_gy__WKSIqTE@н?#@bѥe{|#g@zLy۹dhե7Bh~[Z퀀{s_ӣeqy oQ%umnju3~.hVZ7F3Jµ&Bt4kU}S Iki(W7.5-!BbU4f;1oT|GSsճNeHcGgL dQi % 4N !`a!́1S;XޮƐ}6e~W`K2`TkjDr3kmp rQ ,hi!^IgiIר "A"AG qbq)Eh&?>3QuVg J>[Xf#qQ`5lq̵T_Y}fhkd"|{zt{^B*ȶeZ h6}Z BJDuzU)mjg_Lgq~-~Oi`W@Ín4 VP ZYUC/$LtL24pa} IIV#6O<+=֢0 0jå,'WzjƿJ0xw23vlKI}{k7TŸ#e~n.?"92,YK߾˝? K%xτ V3ag8 Y+[ߧR([9NU(*!D }EֱJz^0Wbi]RAd"!z @Q KV7 G-{2ܮmQE9w؂wKxL Bxk 6XZ3"(&ڛՆ؅Z3g a%Br Un^*νށR4 41tZn` r5*GG9F8,!RÃT|\pmW >FdY0ItU1on<[o?{`÷'.TԎcH"XXlS I h%됗G7)BH Gܱ?E (|N|-yi1ʆn]"n,3O3U |y'l*}_~76]gBXl4x|qo nl8 THk/?%C/,Ƒ[\I{#՟ &Xwz=ty$~p!28(b| QAU8WZUչyohCAF0# wDDɷ(Le?2 `dC%Hm*WϧCS4g͔]_5׿+8L?i{bs>gjVQ];`ul 6I$2bDNǃlFp5u]i5C~8qVER-<ݏڅ2w8V^*Bwy9PN"0GI&);\=OC@ғqxrHLj"h`*"xGBLSMg.= -4`*c%] Yq؜ E}!B6k8F± 獹; m,m .DV@(פlkvK޴ldzA|^_`"S,^:CB P0; fSB˴l=Ob94Ċԅ慐|#Fxg-Ct"r*yvSZwE Yמ1=J9JчtDcrAn|O;Դe*!l&X Pȥ0BKUkEV %794bI)! BD IHdRz 0I3%C-\}f]qTBGȯVZ\(Y{6L3I;lejcь-Ô|*e"/bT1 Кt)]Iʸl`#H(~#ծ>_.?gsY\@?aveJsq?r@SF5 %H =4Da_C)T^'/aّF֫i "q+lQ̫ 6p0q$ Zqa>TgƾpbKm79A;)}&SI = fNe%L':r ­TGYMĪnhSc9{[BmV̥J`;N963r.2.*.lQ@Ӄs﶐+j DT "'Z2=9# @@CzIU|>/l;+ltd| F1= n* ˦9KRxQZ/n-nvar>nvo,#*,I,*2Tl\8\2\5,&5IϩD/{)H`GHodbΕl㔖w@PZ4ۦBQi,Jn) #f :AR2ϖؙlG7^KS:YNWӥo+<nziGըگFM;ovꤜ(wƖY{sd/->TgyG)d]/g>iݻkG7wtuVj[sfF~^"9d1=Z^B(0d o`gia_gxn1sؙrWxޥ})YbӊY!D$c[.ڰ)ANzu{KKlj´\$t/FqΗ]'i 1\01tL^r#coTM(XBmU3]RNa//h]ʴhJ!!% b=YA<ߤ7 mǫ,o%Z:y+~9kѺ^CA ( >+YU ~}IO]m+_u=|^gUٹ"y+"&A$cYȠ9Wm )&i=pih)Q:Vβc,"\d)O^Gnz6*v$A@ ~5RwJn{|t0Ea\bǕbx|LTDY"D7LsݡS> K CR-{wQxo?TÅ`^D548OSzܠynwr箺lX<4<+N~kN<|ǽ۟C-|o-yY8mfy'i٧ KSjIwM\oe2ᕯ?}xk\BX9 {|E f }=| ;B%/W:} X䤷]]xgT]nE3DU6tMYXFcy0{,񭒕bF_sNvnج/ v{ShPU.}W'OBAןY1 G{A:wbՎãpJ 98Mxt"u8 $"H,J':sHnkצvf~ 2=Aw]#"a8#/g Hz,KqO4RA& nRPnB"'{gmӻm;N8P "kjS-*ѭ 6t6* bi=>.>En   Q \#e/=?9 ,^'XLR@&u:;x^s%*SP4juP\` ; C (0Lf"+&!bLCnb9P~^ H̠ HQMW( H(k`9J(OR@A KE"Y|-)ŐD7z` nXA)^aRa2HB+T.<>OC[xYcv0"((B؛4UB~BIu3~'ŋ*kk_0b["mK$w,L·t$x)Ü?6{h?xwDO^B*ݿϿhVrT-l9Xsϐ)El?t1E ^%: j@yZauÒ5c"5(Ha+tV`ْV*"o]lT薈T Zਫ਼6/$1rЙJC2JeŢJY4#Uj%[!rm6_v\#8Wߊ(oXaΞҤ35v~1YaNå#1Bhe))o6y҉whU}ɼg)9 yC Q^6t#ǰXDn !$î%s<'U*ti7碨ҟxm$(h6x! 5 ji^6-oq?}lKsyw+7AxkrKn۪hadUi><ąy'w02~fqo/=׷)=js=On?y}}6ҟΔm[^^p?`/?}ꃵ~|\Q%l\N|Yj{=hɋO]цq-ӔPxnK=UF JA /!/|.ů`7QEҳi;5Qo N G{]>Ƨ՝e~̀&HqeQywO}VB*9 dSq*U#yt.^4wXΘn-L}Ӓ]'')NH;+sJ)m!X$N!BH5-*B!HR7{_-:|RމӘ[^o\W ʏ{-ɣDIY23y$B0JMy$r}S'KTg6:r#M9n(xΉ']E8O-vOET+e6v}Hw}M@x*T&23E1؁n;fJk['9S9{D%x?]upBE A^V5PR*H5 >#A(HdgGe N+遧.cs ( t˰4 >\ d,"2['_[7GzAQ3B@nzn׍`<7T sڸ|I9@\ Ek&(ڀdBZc=6uGK IA|q&qw<L(j5(2d5nI>T1aPU!x~h9=7EZC My(&vXǴy + '2}[i.?{YH8VK%YB-i'n=VX%jʫ!VYe  lf/fuiA"z˨D -U" I0FjF;qvD $ΆνK z'gZ2rGpjT.7F("I"= iz,k{NOe,Kj)  щao]5_ z~?*.Ɏ+Ky>f ?/$% 5 * 1.56d?pY6ڔq$ >ͩ܂#(~MmqWjl4517wnW[7d\g ݡ91W'`E_OZ% $KSt^A>)rkҽrWa+6D4. -lU#Ɵ#_EcyjҠH^nf!!8tP_b?()w *b!J1ypkZg $Tk _ZqꢁR}R'7'Oќ^3u~w磎q$PPZjUZck1R\Z)L[@|CnORp\0.E)\--X'N~|/toUX{c v w1g\l#r;Ej܈ȩQ3KeW ^iP ~‚!80Y`F\zgc5/7EY؁7Ez#Qm(ׂ+R*Eeb5VP9R!~l:z&Wo  9I's=erW8D|;ŁPJ#"_~mt:TjƆv +pxR(Z:PA~OMț1ț$f3 w̩g]6hJ/hx=Fu*GH"uܖ7{IAEm(L@|'*s<'ҍQ"ŕ0CmF|̚Z}{ĞuL8{ӣ玧6jhWW%5E4>OCv;wㆧnzLD3g|1@c;>@zEŠ*Q̲{cI8n|0}9QR ^ڲR<`Jd7s^s\0ZŰvC<Fr^m>>}nԽt4TBٺȞ-ao߅BlQ#}x*tc|9QfGY^tY^ͪ~"Ig5rhWP.!!V\ݷ՗ֆ"4Rpl|o*TJ00 x)ZHb87c'OmGm/P1oİ 0{dbr.BG^[az_gؘ1[)=EFx%ѐ|?truuH,N{Q  jMrB`Bv_{]uvsgy3Xϊ"ޙQ>d荪  <4fxi޽~89lg{CZ0`c!]G2M6QdGkw*ش߬&Z:OpZM[}aK\2^7ݑ/o.ƁEYf^ǷTk2c" k'Mo?9Yi.&K, Q. :G\NT/*QDpDLg&ö?tBf՞.>?nɚN a6j^}~r2n^D oH<3w<]U ܊?BФAk_ٴcrY:#Ճ"/*q])xpY +GqkxHLG`*r6KM+u +\Ǎ+3',qlEU>W2LyABq5oCUr0\CNt3M{:/)%HܙnV 3@3Ǫ )7R҃>WNS֧9 rflvH']5CRQlBN+VM"ک͹0joS7[5EK60f)j*kDg bNmrDY^ΨJHR)(e-|vֳ$G_7XBe=Jꊝ4$l/San)w'~Ӆ&-@&b\B'U~rk0@c6bt$:-R2p o,v6zh WQ;:1~s-g冻JAz%tGCH r1R_-ww\W߇Ly[W3*JC}Cٝ%qO5/T *>$i@frcw%kOWFi:%Eޓ`aوez<.N8=jIlUftip rs]0TO RSndW!O1o+Km~CutJsDXQۏ=geL΍_=wtz<"tkSZE n2gPGn~X^OC\wT) ۢVh`=g%eӇ.9Xo:Ij%l.W]Gݩ٪۷?'"jlDVb,Rl1q湷]~OS.g;h_F͹p*lj7 @SѓE4OdۗX'+YwGot_f4|kZd "8 0\V#Y7y|񦩯69k B%P 6 l'?W g}^wT=فB" H`S wZѿ>`M̓R G lLl!q4ƿV,hɊ\GHWrqƛ ع/zonu'L~vSjW !@:z^G>bo597S;}\P$v]MuffijL~R+ #-is[?n'?_?g=\oG)x"uuAp;m>=eV&g񿏽دYU8F>cTbrQ8(CXu_*sWz87i6-Zܕ1#Q6L:\OXQv[^.W?9 *HE@IwC7E$}2+rw:jJ'nֺ'tl6 ҕANaדUt3#;PT ֥8l+G~iW'$be@VJX$"4a)ZԒn月Z){wzm_FhzGCJݺCw%T_*25dq]0T4\<:Q܈gb+Q@u#2JY_l\\{Ky߳2am$Khq^-?Z8:~^yN)TA ÌV4 YPWY^$QC$". aq8,JXҟ~p*$x^]=I*.[^' ~a˺ ['ozFBhJ cqey.R?7 ܯV; =^H[Uc\߭[> pڪ)|qvxicLN+в>ZOG--{TIL_'fkToKmwo$ӎׅ3#< [( ":6S]c,GMp]ju6\F*)$TVKy+/X*[7^ٗ'TM.6klnm=I]SOI?ة٬k'2j[1y㖘Gv?Qޱm\mkIBPߑiu~w\66)X2:x˹Ny/yUi~0qwګTXlpnnnxf$=P IyDhU&m+J( @$A;duy6gpx)sqnbF&L5gT?/oT9*ԍ]r>JpZA8,mKᲝv%mކ!fMT'~97f߽\Fۚ-@޽/Y~c9OEvcW]I-G]Ks# 3jL< v}i}_.e5ZUW}s; psݝC{Ӗ:VlJѢ)(Z)$pFE-s9я+SaZk3hŌ`&/_l>p Z>}ng'2{+BAt_cCiTt j@"=1ZV;lDPzpc.F~ i‘mxkrHKN[f-&ui@t:`Ǐ}۴VzA{EG/_}1[?%*(O~hjxoNgbjSS/p=m)_D2S?oW҅nnn;ymX'~H囿QS0 -Vz[:(mt([}u746/Wq,ˍSǯTK#bPE9X( 06-}Ž8Gf.hNvOQZ<&_p[P0y\jvh 4קzI$" /y$;0L++~G"9â‰IA#8mk*㩍mv/oH"YK~3L.v5<-Iݍue̋3^<-qTK4>-hH6~R]鱕b&ޔsIdžA=O*pYdUqU{;7~_}V^niמWPCtخEsv÷]0!VltgMt|]? Bø3-\o4ꑤJR:o.~϶7~2IDךG몆zAɽv^JF1P/?4D3ڴ,Su勉4z~sP)7tGUoP־ap0mH_Bt9X1~hv=h=/<@8)J I@ɘ4I'J"HH$Hʂaj>OtA67d< Eڑ }NZvxZ*u??beo~u|i˞;I!0!(hwޙpNM?(NBV>Ç' \рm fI# њKs{7/ocfф;29^hTuW:nMlD?}MjVn+hjJV͗=ly&CBUYGTUY$.Q4'GLnw.3^HAwjlͶ0n#gx飹:ev?9U͝'q>Y \qϤ =4E>\)ʣK0ޗ0dDЉ`͡.Iss%=#jkMX &ՇsO)eVŘntrogU}0p6dBKFE/@tIL sw]IŅGt~Wd[<3 N2T ]>{O6*^*R]}?n]ۤ?Q'4VxMoHuG Q'Cȋ(3 O2Js=5mZp=9|cC18[TFĒF-7bLE &bjQ xP<8__]6K{i#}`s .? լ x5u}X@RG{KĄ\twLL7̗FuMo}O1""m'v7RP0FJ\TE9z耟[6܍ݱSVo]zEisoζTcSy\Hi;2Jhȑ}.Ԙ+S)aԨ>K_~JG8}yy)/"J@xџ9@t6kQC㴵ncGF}CiFHRdb1:q b-XP $4~V$,rT @M-FPQDGT1 /B3(NDP%n~jsߍSۮ\]vT˖DL':9 Eu%)xM6AW|٫זmdM3Ā< Ld%3סd5"mxyw/4DT B}8wL_ wIyn[2fu&5 Țk74y:|υu/XgF -R* xؓ}|D $A(AF j*[nbG+5ힳf{ -ANx_5 GHCiHpb}Z{%rkC]/m_6=`T]I Ued=dS13E9[YcG%󠳈:URjAN]󷎒w;p |!*S^UXE'Ҩn0 lzmbVu892V%W% J{dAؑtADpaVٮ2qwq aV9Qu̡$!6æ o5p\Қ/_KzZgN$FAni֫[t~5Jãro3`狆ӈ RAFU*G/T>%HQI |lu[|[~8ǠL QbaR"Gnyxڛ_fI=bT=uqi>y M'/K=yl\@od,%vjhāE)u[;q'꧲{\cY. ae_XJ g{@gMK!<ʟ;'{_ܱӣܟ\}e5r=zl/.ͫ\YAAG8=j*v?eܾCz5qk ]rj%4C;$7B'qviqWWqB{}K]}?Y<ʼn-n q$@-@wdy-"ܪ`z秂zlӷ=O~ϖ =>1Z(O!Ò DŽG(&'rNU9L j[;]6лnFxA8(-JPٺX|Nmc]QFUO.gܲ7G%ݢJAWJK@IH|$!ɨ_HJKTQtKރ8:v ]sWLUi.*nDtՓ7뭒_9!-d@1]UzZ3 x{vyȚG:҃pI$b_!YA%ٜ^xe. 10̷<_m8LyM%>ҙ3]yyV(QpangF5<ێ3fILm7j|͹qOREzuBEqv$Y_~2sm^ ViB 78) cHP|܎Y߳!c\ӥ{Ԟo+mX?WG7vH__h*%@W"d Y:,[, E1阐's=I &ī $iSw2@^zsլf|4]Z} xoIF.DW&aHC3HzbewߟLUtKg}ne Piq S־BpBx5.MUzw5:_+6+k* C?.0L=y"PFp(aj|"^؛>^%7Ŏ!jh[{S6KVYm7[P[AgD &%[Q>~U0^`aә\S_O'Uʳim}bG |DhnE<(SjpLaapSo{9*[Xxc/W-C,Y"ےfh`yCچɴ/(fLs^w*/0"EWcj0elY oZ+K΋y'?9q^uYj%chE.gp+Uz)P;3&X r%rCnʥW_uo|"vسڠδ9*W|\"籼jɕ?ېyV^?M+. H4x]Y?FA0.4DݓbLdD1"^Ggzohr[-LKEgygW}`Ӄ\,D^c="tJ0Y9SL>EZ>&w0tۅ`yvǎ(,! {:gtXBhv'J6[}kz2Gw[LxY_4yFU h#5ޣ%x< M>(f-V*bQ,\ŻVuʚ9!aE\*:i?Vߖ'~T1%K Iva$UJzһ#*&8dA닂T/>piDchC ~%?pIN0E@.b>%k\08XՁu8x@Ԛ h؄+)~3I@, W`W]@8P 5^ha/*:FHh$I*'0$-|;[:bM%q|×Hȼa7)3^n%GB |;:gzIOk=Q@ X7g;|oVo,~nj8X A /U "]O^/-f1> _GˆOR plEĝ 19( 5__h5f%Q.P!5l ]VЄ U"T +_ zk274{4O <Ӥ)s/ވٳO̍#%O-9F HDi\#7mt><~.WGBC]=$A> ATKD|!u, oS?$~ W1}zU(v%tQZotqc蔅(N LK#X]&w%LLtF1Q^-ڛlWH\@rI짩&AD5 bAT_c?7OBHg=!V4;-iθݏȗjgBq0`2%Z%~;;J%-bIjȊWB̉'xrxCgM"pmuڣT{ Rx3%[s "n*p k~ sI=])s*/ T(H11Kk$yerRRH7yr]2],3]ҽΑ/5[mWH7Kfud7^^SI1̗+|ZyƄQ!5z#B0I0uƍU;$Pg$O}^n7RH Tk4SS֥ͻk=fX) go5}*_z&cfQDprAl?IOF{װDzdmuJ]jR$\V D.se%g°7eXj \&f81m@U`*ߕW|`o'^hߦlFު d`*HKU\g\'E)! T%B$GFĤAdpuՌ fH!-ԙ1u:W/? >wxˡ $;2Ne|רK_gayٯ#50TJIXm?zeyxPՑ;4߿$;ԈŜoUuqq]O$&_p*lFEÐJ~~M+ںbyK\^ Gn͋zjK%sprZ$ֺƏ*UTxɨ)`"}A/ƞ{ygkXpSښz8T1`SrܻP$:BJ <Jd$<#fa-E:ؿϩ SE6nw=d^/y͞G\Gһ[܃Q "83do dOL_L:K[P˵LgRS)X`wҲ˪74ώ,Ak-[mlT;m|jan?Wn/s2r `[4)R- E@ DdSeP.@l@sɎKa`Fy x4&WXQ_.ל"m.HJ؃%np]$_u?V+dJwhP#uI_1 %&o90/մ2eG+[qk9mے]Xҙp;ʭ+YX(i!2tyÙ go6zDVf:T,`Ss[=:ѦuC"뽈sv򲞺ʌ~E֓A&1j=OpZQΉ$:l7Qj{lO%| N_\μb :$pᬳn:W̉Ɲ fy)Wdyxݹ߼dW;Xf .G{]5ʺ@$ NV[Z$e[_`3JR/#3eBt!fªxm7j9y<۞v2 U2Nw aa;M/o|)|Q16]V~BpBBm$XXj#9ɆQzEňESC4}}W"3o[tۢP:+5vNJDSz7>on[ܕ dӀY-R*ڸ z_ttM"_{gtXs0֧D|MMUT˙ N:yS2Ӱwk&L\L`yZmI *a"Pmgm1hdU68"?^g <闓Ԟa`b]*IS =Ja0Ӫ>=&' *JeΛ' z<&A+!ey9hb7Nb\R#Ά9W׍~QcT,Nw,(WG ?7c֑i*]PC҂rWBQb:u [52$ }~m `Ȅ7q*6oFIcE//Ssp{;o10_lRwil-F72pPuvEL1F[qAxR#nZq*Ϗ]Iݶn[s )cϽS] +j0>l> <ϦܚoZ0TiբHA -Ԑ0V=Fnvqt_t)/ERŅ@ZXshZAoHWbuj0#zxYZ+!u3[`Sb1`.J@YkOՀyXh琨O ,3^EgwFRu4o֌î${Xy.wlyCo$za%:<*cWW(+q(O=APt$QֱE͓'dSǔ]#\jڕi{:lP$Xf0/v]^iJ>Pm !Ɔ;|$p3,mCvj,w6]ztV/_&+^} xϧ~d KPDi&XCI),{m-{_ƆD?,H[q֌EOI}4tGlh.+Ym^XIEmvk6]Pcbtp/Z_{-Q7T2`s { *2$.($ĒP!C#/'NWǥ8&2.ڎjI斨JЈQEUHgH;TH9?f`^ȿq [4U_o8-r4fy!u_?qk#W8&oV%FZis^`G -t|톳7mPۗ!D!oh P.[Z~sQZ$|R߬N#%ŎkYP Q30{|ε\ֱܝU$j[:ʷIȿ,j^o!ZњThxH+“Jnu˅! o;P;SF 5H_aY6=¦T8Y)(TЇEP[i7Cه[(ZP̩bl\4QB_|@ .L+ ݗWdv3f Єfd /[4E[sBsmuZXo8 Ig;<@bү am4|,ژ|s#aЅʦ2`+/0Up5`]%D Ġ-Xz1!)S+_gf]ЄpzoY9 t[}F&^PDt& %wLw;*NZ\Xy{x_b*9r,zi.M:Fbg&! ("2"Јݴy~??v#OʹXPD2ተ%P3Ġ 6—et/)Q׻rkB!90 " ,+X kCT]*;CYH"U"BbI>) 7RAS% B:o.:[-"sh)9A xg1uiALr 9֗nn@p46BOc%F:vAžJA}02p˓\+ Uпf\Y;#{UF!Gg/:A$UO}响Ö/tL#ku&TUuYYAcA "Hٓnq~C >p̺oiʢt~@+-G҂ {N"A'`%\oīzQo 0yEG Zć(gLsB~ 9>+ ;%Xȶ37?2tS7Gl躂Ngߡ+#:Gu6y)E U+UU01p޲vJ^W`1M1 B!ZeNzs<{\ّYv6y fc0{ 1LP5ϡU zų`|"_T犪 Z:# g?Vք aª+ڼOW>!J&0 )@a >s]&EPpZYWctS/݈<%""LJdpj6>Lzrɛ}JZE&E1R(:xDlz-}=5be˞>jjՠDY):FF^e[-g\nI18}dix&0k_FN*;-S$wR[3t~ng`@/)jBr:ǽAsnϛsqioK߸.J1+hP,9%Ѹ Bߪy` {NeQYNZlSki YCųƳ5JL)bc/ģ oYA)e4,11ِ_f0MT;~.\dCv*bWB2 PEL#RKbf5EE*QFCu}z8! ' AvΩYU 2)5r/l VKhhPڤ2\1t%A*Eズ?,L0TW 6!lx #rRrwHOt2h+TET&Mu}f_ H;Mh.p ߦ RW1^% [BI_HBMZ iWZlHiW*>T6w״iv˒rw?"ӶC##\"5v {?`KD +}F7>Ze:`BkgD/M)tz|hTaQU .ՇP1+fy (5  E'¹"-I.%f 1܎9 YV35u5+A<]jtd8 <)p)lƖvMg77i)qgRLA*UI,ƹ7/s*МQ"kOi%=(E@2$Q&"몓x2A+Z"EyқZ `vSKV {rԹt$UZ<LJlzko_,̏MI$.NWXa$/z1>73L%ǫE 4v#2\G_t:| aWyx QcJU̍Y= /xϝiq+SīVe21>˂g;?A@8跊Ei&4/Y,2q̸"'s͈j"̖ {eH$B#<[^zwz_fn#]6/Vl%5K[rδ/e{ͥ,ya:3rĒp$$4p6$"U+BTHYKg:7wꩼ~}Toq8gyąfi@X@Բs{MTdwc?(jjsIZؓz Μ >F;WK 4%5޼Tg5áX-?{D 7/OLȓrYJ)NMӍ~]qbذ+˦ VY4SE RJ$x/Y>3Ē`}fg}{P2e3eIz૿B{?Pì|Ҟ Q9CҬl:uשּׂ_zo!~:xW­Wgi4],A"I}Υj!($@Q}ڛr\}uD2&o %MEʰ?OLdyZl~ ;Ål֏{G3%[O2W!KVv6nglkm6H@΄)~Ψ9&~nt~\KHx<8Ar+͍zr}6<3:bNLhL) gkA$,CY ~濴Ur`4e,f1-s&W9{^'.+X{:ch1D2c` "E \k ϝxtO0Uy>7DzK({ $XQ:Š}IVΤTqa/qu mh;LH1a2DwQx+|H .D*D Ȋ*W'J4AV<QkI 0EpU#%V9{A"o38لz>3LS[Ўp60쾶Մ䔐%y ocE #?u\h^ W-[7 Rke]\g+wڷmu/6̿i{j/# N<)V/28zZ~grM+FZ`)Zu"QzBnPX}MU\׃,.giYHK6(ȊIl ʝ">z% cGyZlN;w8R{Ab0fBF7ed<Š& k[w{lϫΎ-Jpe b!RĔlÚ5G6t%NzK]G PaHG).q݈ˣŁ9~WuXsږ L ' ĀO=BU=@]gsUPrf}1Eb5|r 8Dxi9鸴:]dID>g}|JYYt? =r풲6cK;/hٯ]9u?}W*v l lR8K:Z9 $#^F=7;[ ZcapVtr 4Db V iopd8\JԘŌlAfp)ΐY=5&URČ)<WS"5ʓ_q3!(H]:&g]7o u!YPBQb|+rvQOBP顒EeBhcfȎ̼v®&zֆ(~дX7W bic ;NBDD•wDĊ%&^S;vꢽovo4%WYj  Zo*^~ -IBbe=yng1tɼs .p%9""ξQ"+.mSI'lgmξyC\ʱ|Ri;V,P;qyL(= NoycjjpP\ ?O+V+HRiXO1[xYAD2y?Γ\?9Zw 8]0ݱè Yud3 HDdxecY`Tmoa,+Љ-jI3hHQW(TP%Dpqw"ƺ9:i,&4n*ʆʆ#A  VJHF%MM8pCz$-wzvtO$ YOY\Z;gx__ӨGUDp4$j|"9ɂ7e᣾JmI JkHS@*AiQ1 aBqBc)^~,?Ԡah$0 Z)FRVG4ecIM.O:46毣v²@C "Dհr4*-p9MjD}#ð0)FDHe["*&,Jjky_);RP=9Į`p 5'ϥB5aWMpL@lW>XĄzSn*) K`̀eo'zba47.Z֕y/̿jg+1_D JsgKmǘ#$L3Fu8lE)wy^lg,ڢm0wLY0<46L= + *!$6g#?VԻ.]OuI1h_"dEm«4l( >e UoCdEPp=F6y kQӟAI A.,1Y8peJK/ +u=~#N>ן@D+yēPm'6Ģ:0^b_:L|4J][ۑTVVyKWvaMH no͠ n6Շriȏn.cs}"5}3WM磡ACy&C+|嵎uMO3+މ rPaq/=Q_YiJj|u|U/Tţ/9K(Gb'i["iiu cWwdM`;ŠkwBג=ܼ3b/G_9AhLe7̒*P>Bx[S MKμ0|~腎Z>e{:{P`BO&^BQ'R5sF%3…RxaC?3^>>/>[ȹ֑L+9?Yg>ވ>D9b3NO{慇(=!k6^ĢαAD-:XH@PZ@S)0;p'5rN[](UyDBuz^bG7P>@ـr"1 ׉@dkjȱ\a"='d}-X_w1RT8y=k~;h"d]}3z{Lz/>/:]T>ڃ l$Pa)5WǞKC` `wFThɡk)L[_ I#V-sٵιA|%,k)`*6b-Z;E҅0^S}?@͖o{%Y$ID[]D&QQ)tr49Ý>jOmͭ=7?^Mz"gyvyP SƆB`JDgQIeO35W֋ kԅzWjnzѰaS'Q ˊ&">IdD1 @B  <.BTT(!1Љ\[okzpi1 A@,,@Ј% E9 :i_͚Fi- %dC"YrZ+;TWqQ[Ǝ7CK*j\?ơ*/bI%j-=^*>#n%" %!".d (j2eBHLJ&A //@BSҙD}ɁDZ`C%K%A DhHA@w߉ g|qZ ˥Zh] \J[x}&sە5-Yl8RC Od2@!XDIQ>Yhyhq\'}p`+ՠ0v.z$ˬ #4Άt&'`Y\m!/66*7p s}­U E'̨6[m6t|ZL"j>oõ*Y8o2wuǎ.TTCe@^G5*8hzޱWtU2=c4M)G1UhO ֶ-t索= :4)$ɡj@;3t+9Hqj q 87U  i̫A9>eh^"s1kKYVF@*s7XI(Ű 9G g?fOYm.@!(HFTG:>I(aċtiA}s{:i3,̈ 8KmV4ÚmE %Sdx!t8k:! B6a@vLltR;*Fq,!ne~__<[3R'9C1t?"#ӿ1k,7w}n66FHw㞁.^j$'1l+7P푣MLBe{, qF2=ab~s"o?s+H^)G]nM y|{{?JbRu>pNFt)Jlmwh,b=MS[ |q},EsT-~U}xu0M\A)yfO|!{lFPlpLwwC~`678[)jOزکsV=;jbwozm*ꠝ+ɲ<_(n킻AxY@Îw]&[o}U!J';7$T@HHK1_7VKN D0 M|շIiԔC {=#buS/u3Gv~JfkA6u I럺+,mEHr>[AR 4J8B$"@BqB\JgJ֒Vx2I)K0T4 ^ q)P>gCf$;yTr$@"b{p=CBHQA^JyCg]@mH6NT!x*>Cra|v]ϱRM7 A4ãq.|颭)!ګg"7=rF][PojMdo;9ZEtS,_z"Oy+f1i֛pn@k_I$r6ƛ ?1mY2r|#r.!* W>|C> S CjD5}rأ*&F(ЅsA`{6$!±Z4a BNa[,冨 ".Ψǖ#>fFZBY KƐZh!E3AXKtIӟUtG_%FFFS$uaJbI"7`֚l뵸@mViy/YKR){{pvM׽CM@ aFtT ST⧶Q 6]wyuLmGzI 8@(17?yOThKy{Wȷa/O*Gc4W{R di*Hғ$կ;JQ/5\rYC6*vǑKǮt*'C=T)ek,%xDERO 6MP_6͊M=n$Q*j aMJ!+ >/)eQ h+N瑏\2Wj_\ Nwm+w׽]qhe+%9S;}b{~pL)Р'N(L@dE((@ 0L" ,㋓zXhm jwO`B $-H$UV/,ѢY+@H&>8~~\d<Ŝp#Kں31jqE<-{q@¤:͗@YdS{Ys,%pь텇M/p rԋCς% S kYIա-DG֨%:BltjT\˹:sf=QN b;@BѯB5cYH>##Ei`G s=`!C>N1%>`0!ڲWGzRa DFnj߮;`*rR-ϸFIahBJb,LAU+D A4?R,Q9L虢6)m)yf $ VS8mI)&yc~+"AqhYpt~,h&nrYWVIG4K^dhs%Wh??ڰ!1 D#@#r'D.0 a/_EE/wFW0v #S:atx*b`δ=Rnr5SBʂ"QtG.'C}!]jQC倖tQǧrT"Ao.m>w#*(hY}Xtis?JܩLoqX"8/Tx!JFBvR c_,^*2hjn,j|(pҿ)3o4Xqʹȅh }HB„l<4!GOf J숐 1.j'a2@8"@(@KqאRCiWG/e:g|#!tbA<ɘ7AeG}UDRT+T 9y#qH ٥,RJPaP Ct5pZ:?o~_dŅ}}Os?>ͯӺ"j+om{x7Ɩ`ϠŴwK,uPB0>BSUB;X2UlآlUֻ"--*XFH|6߼@Q@fG@޺, ZpM7+O$ w]Ɏ+b/"qaQriB"2pɹЕWARɸ v F[I*kQ>8}~mnQ7jKϧ?8F>sNGȀ$ "#a^$Hy?C~wqD$n'E@!"u<&lgI^>RDkG]@p6˿s g[P6\9翋cvAzѤqM-עSDqW9ݬXjZd9{`ER).2zNcfZ, `ILɈQK1 0ar[C+~p~&cTӇYiԸN Lf;H]~;6}f c䉈T. Y'S/q =Ō=Phf-{_(c07_tϙBvN<\ (?00ID+䗃'beoUPq)7,nNFH)RJ&/rN|l;I1N'muͣ+N6SֈWК2VdB H%ƫU bƾ=z)HآPkk zz=tv Op61U13[lG']M6SRGsZ:.`ʐg\E/t(IJnKv2ebgkZҙ'_#}IVV(Zoڣ=O?1$9QbBIN,La Ap,{^'.Rx"A( AA"a¤TUj D( B1#:Eآ!4`'.3笳d@?/1pz' ;%+!S࣑6L(wh]-ϼBE2ns3+oeï2= čSf ##d $JP7> ߪ3fȂ>6N(D![(|sr&/"ڙ.f[s3ۓ1|lUwW,yս_rżR^9gݚd+LP.I8FlףcϨW$@N eisХ?x2*+[Li1Pou|hF}9Lol0VKPH]D =]eO^"j|;Nxԡgtyj}땙QrI.\hcɦ=rk }#s/k/QşZd|qGsm-BȊ9~>ibKlZguጆ hjšl(luy#w6+q(:M{Un/tkduov_}9׭| KO83R&7" 6xl9FDOp,ʶP . -\xaz1=|A\,޹Z,* M@j@C 0FķCK *pC/t)N0ecTzYwVNEޏ5uOpą]kV^κ<?cf `[1zjhNI :+];sNՎ/UG'D` O^CƓ@.Zx:d8ÁqJ1GV(ƽOnu_Sv%Uv86LW] *8/9 k.O>YUtqVMdn~2`oصc~10sr?n$SR,SSjhk{Y?q+'jC>?`(9-'qG> nhᗸӇx0l+\daaG \A(Fs7`DFxo-H@ `^AD/`$x#"4wo^'bRK0H|紱h|f.>YqėޑkGYퟰA] -`b6V@Y9.3G rQ@ p>xb1?LիBREɂRR#D Va&q'AUE%M0)L|rAmLM҅]v̇0 )>\Cw'Ҵ0X5 5en1R6{f[D [EzB%N]@ 1 vLd-:q`>_>yg~%O[xPodz}B}{m.ɝ_߼-kw+wmʱS^f ssVla鍋ub*3vNU_j//}=onȡ^7@BI:"Ѽ,^Y-s1+Y "`lbȾbWGSˁFC /REq]چEdD+i t+tG<`3R+Rr^NO&tk:2tHI|Vg;>2ۖ]&ZYs%U쁕W%fg˫&,fRjd73B AN[tjL9T!ȐDžw,||Wjˊzb*ZjB J2{2PAĎ)agG{U[NHه{?'',P"&4{VLw~mI%nF"*XK4LfםQK<}/e3?&}}o8;<\"h=wvj#ָT}ǑVб 86XngH>CG~w=@,yL(i[ WF^ /\bUȍS h/Dxx_!iap1`@C .gPl>4VgLР)oZ@U=1fucWIՍ˧;ۺv5C2oM4LsPyJzSzSu0D1P5r>3$“<'=h;◢oQJ wu?NS|Z;BD_pO aca1n8 mb~DUr?/hz^?SZq?Ix-ol4 N| & 0S I$LDl$u@DB `@`)R_7  bw a_օn9~y՝f"G| MA+YN@ <yejZS20ɴZ q`e(Ҷě^ ~=h!PY~yS9H0f,p|2 ɆEW׆TptH\P+ 8DJԗY9 sD$n*7lwg>62*6[2{y @rp-MbC5Fxn}$ҚG{{VoȭJBpYUPTʼn 4ʉ]w{do0n?Rrvo(Lgj5nT]w.hl%w}O|ӟ+zM{T7f9-D1e3nz䈽/;ٛ_oG<~z;(QFdڦմhEGz`G&ٗVƟt8fF۱2]&h%h[l0^M/'PL]S*$}ntW}tSbssVCip2}FNIpAWz=aȶ͉/RiV-IӚs!54FZ0bpsz:$cǴ{+$D s|heB2B@Ry6wszeP0=$l0uD |qSUw(٬QJa xŹ$G,~ã#bTN=ڿs䜺BGr().yI'n-QFz˪ilyRUÛrQ3)*3< x j2ۢݙ$c|쓨)~s<[901Z":Ʀ_x"oϞz>@|r,AvSh2 _ IBLa'?es*xB, իeO9 Ěh؁C fp !b (CF`{Ӱu kkhR-(qn/@ dI3I3PN(kxsz /C4kh*Q"H,x5'G2lDB]!}zsP)4A"Ut`+%u舖B q7_SO}.A PoA|p@z3s2Y](U -!Z5!&>ˬ}KU#yar'ct].~'W7V&"ŷYssV3>A&JaEb[N 5DXn׆5;4.P @TFUR8\X0'BRyH!^ z֨9:t+bn&f0)`dYA3̛ۈ6\E X[k O4lpb)pܢKS|i:Y#UX/61d\FPcO nwp#M\eo]k [߲v>6XɂBPXY3=n8|aWkD$e%T$Tiㆃ߸f!/Md䨊ڣq~6zf_{Rx?E.WهTDX"Ish#J43$R "Qq6ња^#3\'$/+Fp"disiqWLw=~ݪ+ @hqdx% yף?c9ո ~LDƄ@I6Sf-8kX ~Lhx5Gc(tORN{ثJJ_ ;A. 'GB6WiuYK!=+ͭ>5u3T#P ANN}M'(-޾o3(_^^oK/ cuĥckN2XtgIaR&R*Ođ({$lFJq9-6#:mwM^^c:W^3i;_E&Ɏ= QH}a&DHzzԷ:#h6xaC^;rBr/?>/7s OCQ8'~7޺ #!t]zngvjQ!bua]u*&k5=(xr]̟}?s`OC +"  ".!W'\lr}. 'p^:f@c0IQ+h-F|| D5a2` INNZ5xV_Li{##* <`~Hh%L@kZe] =BXC%eA~76#pC 6jKDH2g̙@)T[,kdu2H`A+z&b8v÷CNX־+n8fM}*[V9p.q8 < ?FXk8^09*BQB@aNKqeH:+E:Wmf@@0#TM>CzS'YolO.֕Oņmj:o[~cQ JWũ&{0X*aI}ڋpdŢz62+*ʰ 3ڋ(b2J;_,gWH ǯkLGOՈ#wtg,殃M}KWeƹ.OJ11{Q p^$:զ$PZb|ߵv޳u[?MNd-qKEo:ϔ4\.u8FXrakK*fW Q=ivoq"?xz_nrNKO@{ L"Izy]hW߻C!&\$ N){+Κ,!rBأ1"R1ǩqztjoZqe h.u %R 佖p}?M;n`nIiY wC1 ͏R]Hu@R[{i:)KETm)I "qvw<"C뾈[R.cB „jD0O a+Q9@Wx8 +UD[:B@x1]XhݺɝKC|~ѕ]]޶<`˭SzQW8d .}檷2"ILӅ6O?r k;³^aG$iUɨv[0ޯʠ}]vptږi̤&ʳN!UҔ f*þpt[ۏd&@|z:E5ӄؐh[ʎsm(͍3_޶dO,Ok/ZT_*Ses%f&3$rP؞ۇ]1#r\Ǟ+Ҷ@5Uڜ )ޓJ*Zwf~UyGyGkpRA8[e %ᆆ>no,r=[2M|nR$,YXc Ecz= ׆k p 4z^ }7onr8}sG}68H )ՒpܕT)WgqGWզ@uIJLZN׷fBЦ2v:}brOt:n @\bJA^t_~?/.>]e ?^]{&~"de 0Qֈ_Fl*6Pxъ(t"j[.]B>Y =$Qʊ}S;c{\t#Ȁ\QEA;vw+wyԵZ Tyt:۲yK F8|?^T̥W)Rh =gX.F᜷ PwbQQ׽T> ֫}Mwwb}E?{x ]NjIJ^*-Cqn@60^F4AAkd6b{fĞ;pCPE䵙x݉%%IN}&3HԤ"+d9ӇNKkVD[' aX{Ƞ+J%@8!̢xfvi&55F C$ht}Hl\&>{zQwΫħ}*孬rU{탋&1fW;W乗OlWYbW3! La@pA%4W%ӑb0~xL8s*Ys BI`DD 9cK\N:K#!0ȤNW|M2FWÔStzE`'i/fdJaXD+RoY@dT:OU^cYf2PEa6it~Nuxp=gZ]yM3F9ܐUsJ4IU :v'S/_µT,4Niq24tk?c3rri#w&x+h7>4eAsnj2bb?iRi޾ϵm$w3U  M\{}fq#{h1Li¡& LC9rN=e[5|U73~tӾdHn.z[*LIWa-u4%?:zg G^pU,7/7j_|Uf6#X^&Wf sĥAvGD>* BSK9<[s:eTh9+pe'NJ= pR5XYS1ߕɱ`o~Z]!milB"!,8b۩XodE\{az|ӭWgCS5fU0$L@UuϳNђUϔճ_w>z@Ģ"@{J-B >;m<}pqG2ҨzIdG}kA_^LhTZLRcv3@uSNj ~ 83{d֍&gk.xkrfnu@֚Ėc*OL 6;O,gQA\@!-sR!/{DqG|W]I1ڴ3BjUVKC\e %_V{*R0V-^C<7uwBuYеL\[C=@A p(JuQD,rk:ۙC>H+hk:x:{G}b!zs:wԖ_looHX^}EaRlXeLg/Wew@`$I)wp rdUd m =bX[yf*4KsD+yBE ~:~\y/!rrQ>(gpXi/ն%ל*ʞ[#$tI%+)g.֭gfP eQ 9 2s d (2@@ &)XdQUwVLp@.E>mN[Y{o㟝h?lם^>h;!#C7ZPν& ~60xk5+71>XuL ('d,U*fK<E(OuNn iq5O]-ޤkQ,(J҈Nнj|Svoy"Yuwx^.r½lC, ħg9g7s 5h\znExJUzZqQ č43N]R~8c݊4WkWU}؃^bν'oנe-ԔK< E)#4ݯu(s]9 wW8^..΃"":Xz &3礪q阳-OckA? KψwYm[v,kn@٢5-=.aggWUɼn%Y'_MVˬ. ?,Ԗ7WiW~U+_LO=_h]ηme^7P@ֿ7h 8KΉvC(ߛ δчA_UauxUVEz#$٥s`PZEjuO2|xr+~~gm&V 757{2# N@(1Pv^ĕDXf%}r9NQ{ ]bQ!p&J"tzR|Bu!87QV̏U 'QM ۮ$W;0+\)m@P(Ǹway"y@?8ˡęoڗr0՘-kgetul`pN .j,Wg{%7 $(L!]m*፤ևH kuS?-xWյ\J+o|yꎃ}8ԩiFXĤ[4 P>tw%=Td^H``X?hA@Ӛ:|8m<{}Ը]YNS Q?9}q9]T[GG{y? 2dԃ?Sn4.ѱUb~jwR `Unu'DQo+<o[co0FҜ $,gBR[< xЬB@#d pDKg{d; .cQk9~@wR<"]1xhͭ/ edmNz^9" _Hpp(QF\{©w,9n~ ʷy}'KFl;϶R Eypwu @Q37$%Ҍk4 eQr)7M,s3$a ]r!M4.~DeǁD?&0 YH 84#j 6;GBCT-:[}ɔaPIDQ\(.1{h~ hVB7WҟW2r [=EQ5xD0Tڦ%^ @[|<3kڼZ;cQsIP x[m#8ᛮZ_x@.t"Ie "^Uа/imjJx~;P0K+4,# 8"'}D?֤h὜ZxNg-oHBh k<^{`cY;vPp$(œ:P6AuIᔸܬKSBǛ3*mkb *Q(M(w+J)rr_zjO ~omkM Yz5k-_^'߂'=DӋ Pf ;dT$}R48h"&H! ABvA'p#x@' cL0:F0RF2%:S_j&"Em?<,2$H臤_U!bT# Jr:FӞ0\^u-!zhn@l NUgdOݚ1IrEm )V{h` (/ñk@-@ߊx!ච@0@&LG}Si[vT Ұ!Ԍc| .PMZ}$c/s [󉙌 lssW]e DyУQ_?hȉP屿hܤ~+|F i6μ℈U/!1@I-rWk_X{KU)7flJ G:{'myR[H9.-Xɩ#$ U.׾}\PQN/*D`)UOkeoX"uFH{44Ldy#^wt2csLovHAb^E)r/f9 ~%/*N%~PhD5xm% fADt 2%1oZ-w0-B]BҺ:ʭPKDzgvO<Ӝi&ܛ q\1J-Z\k7vTn{[nBTυ4<_8.Ojtu5eGcAIp7pS|C46I&.‹ Gg}W3pZ*q$Z*W (Ū{9WC4 MF)CΑ&K7G̈́HWGJht- tW+' cQJhs;|k>)z.牬譨{| [M+nDJ)Y@f[9a+#uHM ;S5dCGwqPZޫ3!0:i|hs烟6I~Fy( T}n0ogPA<ƅ>+YM,VJT7c*6{wetfYE>c1LD108e[x^K_3!'Ջ޾ul.DA3@unCc׵HK  -z3 İ[<]"T7Ec#f&X7w+YWMf֮AU5 qQ b{ޑ] 偏0(k5d !ojn)SH yj-i~~"I` tF`TXE鲍6x1syΨ h[ڈ k0P]+koJek7Sc6ӵGs.q=ndЩa -R-tuu LGhMt^Ta O_ߺ_"6-&O7[dk_dl{̣OVڼ5*'<)qIWAkz?]e ԧh-~\'%aC&"NB Pv=^K`Ad\)*7fX.k3v]-'6_^섢YjHՙmZ|&9QUtS= |P mjqBhh׍ăr7(x >{~j/;9l誁ȀK5 b@mŕ=<ʇv1~c6L.V1ͭƐ;3;w׬] Hu?X䨎_Ꮒм%r$BCA%}R5gf X>+nb'6V}[_ 3O]%LYYe.5zR Ti"=}{&}7JK<7%:ţQa-gh8Ga0T[!Te;hxp5`׿:GU./ZPTFSUc/X 6'Cܟ,l3x5xP: #qe5׽O\j;֧C4Y>2(]yQՀlEҧK\DD%`v28^hx@kPVx~e޼l s豢KO)J- cl.\@X Yְ4`MZ'EANɞ`m }χ?ʃGv]Y,pU~ /X5?CMjm y}e賝}eszxz:鱥ܑV5V=+Ժ3ۇiMEe &87D(=S`|I>1lWAA?d0HpFDEg ` tR0tEH6ضtfnsk:A%pOÀsΠj&akByԹk׬?ēcGQ׵{h_ԖJPs@P5=ջ5!7vvh|h@^FÁTb朮iЪ3 CMpѲ VfAՎ}kf+#GLo\ҼeG# Fy]D9NOÔ'?51T||6yHёBӒ!3+v4*HIZq  +"Vp@6tkc9y'~.eTBl} qzNv|r JVW  dJKLZM7~ugҭ7IJ7͸,;5C cX(d;FG$u2#3 C|Jzz5'"T^b{1PTϝ;:~K'̘_;Yp'D#"%Ҁ%sw~.,>O+Vimf >'A0S 5@`BOhtoƅ{] ~/jYC^Xc/8YYO&q*b簆a{h]#B6FkcUϨ2Aqg={tCo5F\{s"\Z"CD X&%>`ULŲgo, VP9k]5DGEyϏ-@a.䙥{Ziu$D86ELreiG_Wn7>!ZTX+X7jwUM̛9.v%ÇRy^aM*8  gFm/ljڬVJG$Lnör`J- ؂- [l]1{{~+HSTX?8cnXYE%Ϝ`a`ƸZxikbQeC%+!E9[ HW}p۸[6DbhV-)zС-I1 yBԁjd %&M{ fcLǭ\uJsͪ(] ڤMIh-z'A݃dhˉ2#V3ekuI.L8R/712L4.2>v#JmoAES8N6CyN-a%-^ٵZ2+5<՛RRǨz8y!J3"- p" DoTso= Pf 'J2> wI8rY/ ]+pVҒmоc)U-bԈ1͉LoS~ke7>P4s(,W]{'v9whP4B._̬ǜiλϳ?UV/bn-%!|DG!]^?ĩh0UʑT¢|.LJ'̎Ļ4󀊥|Pi!c[.e \| ?XZzE/f'ᮩ\lt_8f{7s4u]>6jQS(""h0 A{/[{+\`+P%XT{IxiHaOtՃ+:y7zUЪ%!d4&&@MwwHOу>^ruPt_5wf2#l!]X^mf⳩D%#,u /EC. 0YzDɈ}Ӝ*wN.ϭ/LI_Be}cШ8BHe!^'uW/+B* OuƷ؛&dmMJp@BW ¤'V?0\m+DT"9Hͪ"gԠܺb+j%nhfZ6 )Wgpɋ=Z)B1\=:qV.mouYhj]CAF1#&!1ۮ6_Q٨FQXx1iHuhB8ݙI#Ξp)CdgE6#,""k p*زڅxj뜙^FMgq;xdA1j @ MT+C! PY;5%qKiG/6ǔ$Em$V0#gE/ WYb˻ @WW6`Jl3IgunUcFi͈)g$NKp# :s@q1)D}z܂e!H#`U ͨ }6DTⳚTEzQ&/줕d߈SBS+82AU2~ql@S3{Cx-R $H#5eNWygsOa,jx%2RZ\>1R / um@8 >94 Dnν0umu3c@*տP 3DBwЊ['7mHhk|׊b2_rg1go\cf4 Ib4ź/w{3iv77_>,sMf_&DQ1"F[ sӳ={>RR|c-n֥^"l&y F t0@H!찅;n:[l_o0x(P)e6U|z<+Oy[sg?jt,`N7\%PXZ6ߥ8'q- ҥg#@[+ Ia9JPY.qFCZJr\C6_ӧ#)KӮ|m9ԕ4+mhҞg.""|#_g/\X@bPF"j,=xDցa?N[ӿEθO! ``${仺t|Υ > !Vw3?ٮg: ҭ*0TZ5O?:ѹGm=CT;;7z)mLp, 1Z B:ZLoE@IAEUmL7G/jvͿo~뻁aH99L:$Uʭ#u/EPy#F+nfDPD➩ 굲l^vE^ ז>x[;t'I\r : Jt @hnSyzpݗ{gHsaM&L#\v%wfד}ɃcNfJkwK p4)nw׋Ym: Ki% #vvȁ.cgyn=KqF&cO26 1߼:a1ԍwd槬p:W LLk#WZ}ѱMAu@@5[x/=p/fw?"@HDUCQPB#E V~Jx=C*qhV:wаaΊJ4醩^ys$=$ǎfq&0EjZR']9 {۞d0ƈ0IY8ݨ[*۾uA٭2;$.z2+TAG x!2Ťtϒ;'SvO^Nz5n#&(uu!uH y06վk0p|4؂ E o'>ՙH DHp\Y"HeJ$֌ K.D%SȐ"FJ#hi ӡ1>o,&HRL,|N]oz,p}fN:*U6 #9 +%9Ya`Ľ퀓bFh&F(5/ 3n;l"#߽;1ڨt ;dI)$vjp꟯$_+n2auצk1 z-mD׫EZΌңf IB[[sؼe;Nu+v0YK@3%:!HaR^_E.k(Wo!uJD 0Pɠ$,{Hs^:Wzo4Tc!pj)ڬ,m-$Vs2€zBdeFWP-s!N0 gm~Ѕ|@0̞uV-0 o tDW* hܐ)hj9ɵ4z]+[x)/5)Jo nHW2ܚ8IKMkw(c1'};?y0W uy$a_\Wi@BC[6VXX)5oVԧ<mn^u)U;jݯ:P kk-8MOMWvKIgQwǂ1pcFڣOfyt+QqhQͽYJL Ra6Mpz.sǫk?א۰#_eLx`<]4Z*[U@ SCk7^l0|r)i,D9_:7&+ aM;H;;1 wk 嚀 )T+"_76 kfyE+y=W$&ϻ-A"?{s[# ?12Ӄ+¶; ٭uW :q)";YwIv8QvvԢ]IzAu{ h˿-%,4lBڔ[~}<}P#/Ay "3 v|I@se$B Xr5NFFq5k;)fp?}帎U9K*+ rҷKKdS( JgrtHHk B+2N ԚH:*8%#lđ NKTpP4=9,a]ҹ ^LR6=ޭBp#7S҈ rxfCЛwlيr*]]VNf.7;oiպ(Si6#r?vtr9cዑ*sZ.Dt_Eߎ7E Y=4'`H0txWlzKO@bkP +SaKMfzy"+x UlMiNL;JJp+fo؇ MCDg^jA꛴4LZh jB`8Ϡ1 |ϩz- JSDJI`1|}컄;{Z ; l2KPɫ !Y>݌H\0(zReя.wn[ODȊz֛ftk+2\Ͷ=NlS}E -փP%i/P%RߤUS5DC &@]V egzN)",RuYS2Q*ΰDl s[;mc^z:묐@X=/cOB&][LqrI{Z9Z*8(:먯=B[yWzvDnD@tFx{B/wuAZ_a0= 邞iY4Iǭfͯ˂q3{FQqZޱ`f^[* Ӽ<[Z.@#% ~VE,* #n T_O4D#L!RB Bї$7B2@ RY@.L `fZK~o.15ҝimG@LBq٤>z?f2?йTF2d*(ꈈwӭ57%(BOVb0zeD\CY4ܵ3yǯH6Sg8veo솒~{5Z2 twO)\u6?Ur)~>U/ILت_=guSo7]8ŁV:șBK]\u]I@HD,;[fܥ߂L؊3tޭm[^y1( fgO7c_]LY:1m4uO\@8UtZ. v9bo=[AUP z7%&HwH#TL6>`=ä|MnS ͹7o&l>16r2q)j&Elm4HcLiuܛ0Uex4iՄ(+ˑ>+ji3CDiOM/v'Ζ{۷KkR=P W ^A 1R@ba~)eQ;I7rUE7O߱j7a4[дiV={GldK},*fK7p =n|a6"4P/Ȇءvc( ~ꈅцO2q0jv3mo>9b&cv"Zְ(ĢIg SMpk[}cLMX& l!x_wO \olv1@Hsx mL j*Ӳ!ٳZ7VHY3Svϋ -5Au΢T%TH>̀lK=|E[V{/=n2A^Qo]^V):kUt6mP-TUvY ^йٰx5@v_vӉpHT!06[Ȟ " -CS^ 4(ɔݺ˕ ++1R:4A0% K=GNR7vYw#*,U,H) $ r-IU-*Ӏ P%=qj3@{ՐVPV/rkudP~YmJƌO4yA-'f$^֥|7ŧ9;A C14 =D$N']Zݷȵ胭LOˎ[ xU'7;edg7}4'toX݋3 -   `H`, sdV5UFgY#In"X+f\t~P+..8&PR\_o_[?+n`gS&=V ^5rw?upXeKGWJ:O1ƻ[[nOt:=[}.;?;|\Fȵם!!9 \p^yc"#%WKbZ@na+Y5@dMaDrAԛS7aj34CD%<`a#yӏVtpT5Oտ2'r}%'}n΃嵓xSw>xGdYgoY GP淝 .%*֝:@BgI:~v_.|ϖ}ێxɗE(Sφ`A Š f J V "o›*)Z bn'6/ocJԺ9'U-1;4馗Z '8xÈ`*dZdEr&HD{4DCm{. 7=,HE`"Ua ) 6-ZSb,vlb9 3@V*6`F@(b1*l$/DvQˇqDEx8uc`MR pgcC4`;4XAA2bH-q@g$|JZr3K.ȐhΐG!$/qއiFHA`.!:A9uoGB?-{z+( S3Naӿs?н%>E(hͧsvE0:sWTQo-dX+ )v$2`7=)geek9gJ@z[5C-ݛLX9˔ =w r[$b3Pn"^w1ȍ3gc]ٓD]4OhKHNÅ pCnpZ>1g3b!@lmTMtæ4rf/4Sʭ2$=xÝ]g_CcvK|}۾J-ť@0atT̷Ë$@7ki@, 88?Iр[\Xl*ጂq s^JiA0##F^4VcHkXWי 9W:<)8[^!zǖ2hDCb6roF`6vJ fu @yad<59/EMSwQ\KU\G1tCzk'&NW@mc`N>P5bI\ Ew_^A EޮSwtSBYʫl09`js 7`y5k}k{dQ2'S4PWJ3;Ij;h3Ճ2-azb#uH ;, uTH㘗ȴϩLCXL%+"Q( ;]d;(P& E{9C&061+4߸cuϬ)n{?~O?tEB׏Xԁ;kOT@^ ]6^>VOyۧO ={lbڨ*bA.@C<?JQb#EɈ7u|҆ ;nXLN:"f-ɸD W,M]w i"^[QTdJB!c€@ɨ )`\-D(hC-E*mM2Tkwh9ۮ C*5 yJ3h%IXgy뗲 -"kbL-^>E)$H`,BᣗLTEEt}S&iH2 b!6@+,DY: JvwºEgD #y.ZŁEQEVh>l(Z^xs7`@Q.S , d5PW:Q)$WcEhRj4֌HemijǕvxH]_Ln=7|>G>TԵN@ 2U2#(pF]ճWV9נ3t3/T-o0vy Ij 9;$0-hjZh6ki_' D4P&crP?Z±<<=-bܱ g]&4;alqPi^qR(L)s1g׸5fQ5; dr>@ 4 [@bAXЬ!o{Hy:0%$[>` `6vM[eȩd ܙRhjT6T0 ߣc? a&MZ=׶otͻsܨPH2=C #Ihi 6kevo̧jq;Fd7K`q.;[AfK40dh!?Է6gKA0 $^<)46Dy]lȨH`H(TTPrtRr Ly@)t7 dnSraq4U-ܚ39geuSx%ǿ%LNB^5p5`3h<Nfh A"""Xecgolh轀= h ْ8忏; L<늊c>M^ H0Y$UXe&rR[p #6M3SUN191g|+|K~U5Y6Ch?ym lo O;\< dKKW#`~ɶ{˔R*jN_>XG\ym`TWupaG{d%"ӰХw[WK% En'!eE"<CE,s$ɨc.hM"^}}bkxk}xrԿ=cn8"zuRƻ5TִoQn,?Pe_}y]&z%[`\ߧ_f) {9v['.WwYIh顡iz Y%[vRegm֏mٓ˥ؤ{hVS-9| AwqJTv ܆\z DӬGd^R9C_I0Sډ>s z8ZfNH $fqYaff A<!TS0<@-o?0Y-X̺m uT ߽w|*8fa=ؼe 'n]**5bZ w-{|8sY1_?suD_eV"ۢgZ|- 9BapHHP 3I2R~}=a83d7%&-RT8:6vt]U˗cP5βu:R5|^i3'e)}[M_q މmw#<ʚH%\(ti8;xs5Ql7ar p\s!ĝ~MH۷@] ` kD2'w7Q6T  p5-(:Prq+JV!L4[FTrbw)VutIt8XV10OwQ Dʞ>/qDK*/*OqK5 Qݨ8$1n8߰3En{p\#R}{Z"j`]JBVRLP-z7aТt T#' bW8K(:-۶7\>upʙAW\=)j*qeFU䰮[1;Ut|Q{-`\\hմVS'+! nQ"1rZ[;|\.ՇƩP)]TJ4! K6A%:e.9Z ;:5#H8 A" Ja,!Y.ɤh: CخnzGtTNel v)%iWe\Xfu{ޚN݊@}j n<ȵZNyB,5D=MRXؤKlf n PIbA$hjI I 1SI^K5i( B8{f~ahgWRܽ.9VPS$\JoVGb T%#_xOQgXXWqkzDź ԟW!`7jR@O-SMvomwybF"A %gaLɌ;j"lv'dmrDJuLSEۙls \֞mA~Ls#ݸRBԜ#3^pi9p72 Q$!  K̞ݘ$.LLi <2z_~y VB@<X oStuKI_RDU>+<4j?]%{C@iLGQ\x9jLF};@08WE:@klY z'Č1m3ش[|4:qʊֵ#&Ax^iAQA" ܦ+xaջN &gƷN]݌ *hVS%48S) Nr7G#Ȁ| JYrֳP5;?7RJ p^`L.ae rY:[A*Cd ^\` /)6Ŭ'j@WjMhY QȪ$w0]AA}3'[[{W~`֬Q @B@\0e a]p2fS)QX( Ȼ+` [%o?[V_sxG3nk; {?9:賨@9VHȼ%uxݟ߸ ѽ(nptR,2X[:q7+ BIgQK*O|ةOkc9$(B5*TqD% 4Ntb0J#rJDM6T oKI/ p,xB`"8y`Ɔ@ y[ Т-"00*tzc߈QΊ&I PB6|ߤO([PY#Yy .V$CYDS٦iYWpyM;fxP컠8#KxvR `07tCeǯ`` o03~ͺAC!USK|W|uu[nuqcO. ӯP}^tN|7t[*Uu5}?y<% 3h3)T>0m DOBM*,]q;D[dlw}Gƅ;n D䴅#ܤWuXZ*MKvA $ (_"Փ?˞گ3O TD Eb"x,HKHEZF,v;R}9Lq6ꅴarNG.}*Y|&7I:D(H"T۵U5yVy9 翋u:et(;+3_'-fLQLE&\i<xݚojҐWFNT~zA q w4e{RODhЊ!A41 &>JP Rl/[sbxO~" > ;`FN4xuX(6|%YFD>G)sxgXIQOA/oWYr"/xT;>f-u7SJ/Mta'}|s #z?1\^bzEZx19")ZGl ՌK8Ԉ#h@7OM jit:n_~;3zohWWF$+02}J ūQ{Ɂz2W|٠*Oroxn7<q4;gm{Ǿ[\E ;`i TUZyeL`4;YѬbF3F ay=>ml2lFg`)aAjrW$$eKc}Ol+q> (CW:KJo`/V"Dq$kB+8XR" PDbVo7ɻm0a,@:! pյ+:]}}KICSͶޥ!yf $'MDUAEBL#KD%_PPهg1v`!:2$lHQ|)e,|y_S>xد+@Z#B/Gp0f L>t4d/ۊA MǓ/-FDx!7#ƺ$b Al^}3)&66sR$ҟZr|v+Q4+YLt>#%6}N~q=T{d'-1|碓{3 >YK~!@<@?5Qe4 icZ)4h#V}h}L)2V{24i{A1"Hҋ qm[Tsn-3vzp,ct {A9#b,E*t=^=`E*X{A=^25[hLzFy2.C:5ָ'Ծ4A 4,UH"8;CAܦxzz hxΊN1dZb,֋J2 b:G3wjB$\,hN#򒟷U UTu|iz^_&h#j+tLa>zkF"r2Tvq$::qMM[XHkZE;\Ԉn|kTWo]>cv۵+Fi5һ/fΰDfka[NA9aaĨR6ѬWFVJ#WŠmEӤ'x~kD}EMwtz! \`#9_BM*x6/E9-ѯ! ZJ q;\! GrM8}I$ OqzֿY|GhLP#.ViE s^LAP1DM(EM4NFac mhLEX"Z=z%peKIcƕAYWx8 cY*Lk biWV>%" +wEdI-뺝b b}1 cqmrd`3@\@s`5 )9!ȯFyRR ng7q܀0["1((^F<,CॺuՁ $ڹ3hb˥`{k$Kf={G6W޲l [ɨv2iqZ21A `r]xЀYP!k]ׯRo;y%F|y:b)9hPJ6x]} z>(@BdTu}X E}eyW;33Ua*؋%1tpݦ/qj.V.c<3ktxڕ0>Pu(F չ]э2㦧'x Aqzy^O`LiFWo?ǢT=<ll/Z#{h0;Ѿ=; "VͰ8TP}mHh~DQY.6|wER®/[;X~X >+j8H:>oٿ$4jD <@؊cGUsj7XѺ}&_ZM?gsT|!pdI{ncwcMh#m Q_T(R$ f5uҽ?g&R{GG?>"۪R]' v<$ jgl,@j/$JřpqwNçf {]rJU}y3u{0,[һcYb )=+au6 T(IvߜwGqo$8uչ8G=uָJ%~ϿwNlrPQXYP +y6`IC3[(Ow(}wx%1\d1FkWF"2V| bXQ'M~y_잼^M!`@Fla|(c> yo2Ia?8^H0Bg?()dAd(xO^ -ٰev kHP2"AR$ I <>g?~wo\uWOIF+֜)IP"1 BH0*SӇG\0l[‘(Fux|eE/25Q3efMb&{'MŔJ)7qs%ۉEE%#AӠGE?Ըp{C{(%wgu a!CU|:%[Xz&- ~QR\B"[tA)!e [S|*5u}ju@stp6M&nO8,y@rT˸YIAAt ůѠ/nudjx(RιLrS9typ+.u/t"o@ {QoܨP dkD#55`> dX&,K T\v0(' h A@*!gÁBD00 G ؜Y Βvo L+[_Cc=&XM!BVp{j^oyշb/dDnqOW |3fvH`- n9ݍ#+c6xUKRv+Ya<7oFƾ)íwFP˷"z0g~OD@b/-d,(}ؽM-U]YxSx*cs|WJei< ph",7"@|;g_=Nŕicw~oXg\s5TZ>'6>ױ{F*ؠ[cz13.xnj,xCX$d\"iKV_jzv:t\|%!Ti;k* =J#Fӧ CIcѯ;͆әyQX9&pЀgMP0^JKHyB䳴͓-fju,',034 )za$|TotB%r R>:HK VAݤ%%B7NQ%̼JxAbH*|]ŕT[V|~0(!!LG&7>viK|VJ0*ueҲ- VhȘ2&0#w\uK3\4!FrϦ!`5RţEċ=AP L Y0|`y yOc:W!WXL!.g=V|/.L )~\ \틝̙aŨIXTzg+uʗ 8[22ybe?Q ݙ áHe}L2$KB 2$@N"ߎ*I Ո(d?t91C&;^qM1?o-GP[j=m' >Bk:fHD)~WNVjn3Ͷ_W%}{g$ wy% HVJT`r?KQ*oz0\>6 Owjqa9mL$0&Wz1!a"B TKhik3ɟ(Dah9|@(03̠( |6$> o7qBlɞwIJ.uY<* #yߘe ok%Y\aEhXTaMu!$Ѓ﫰x<\qQߜ9 {sU2V=6Kc4ox 5_3w !(vdw,XP88q87_1❨6*4(R+NHXh4+?\$[oo?ºJF~@uzc$/Z[U4ʂdu4Ft4W`1sbb;z F>t!A1oVEsHrf v@WwSqjĦy$vikA'T*5; @8_& *afYN.z`zf4Yd0OaW > V1b ăT:!0= L >R{`ߣ UstƘ5h}+:IbQJZM]bUfn> X$~ZCŗ̔":A@caiK;OƟ&uApgTIŮY䀨lO&n]74eH@,CMb$=;z 8x5KaBƨ *HIp"cYW/^9+GAz"VE*Hg3mu_Ne`bFe~Gm7*.wD鬫zUNNJ\*0 ܘ$2(W/Zؑ}c8xGxt;'߲"띵sFΗ[jMO *zȤ&@ C-4~ᚻ~hT-%vZ AK*l!3>{}EF.`!*_nG-v͏﭅q[Rݡ{z8ÅP0UH?"{-TEoh_L :vn5[ Xg\ۋH4*#,0g(O6p)7`o0Vo_1~J'#{y-WcryXf5* loS,By˟yb=Lkjp{J``RTP\$9WtJ$ݙU :iMl91{vp~"} Q6݃ %mLޜ$HND9v5Ç0@I UV!,@`2oۗml5T[4XDյ @5Zk*fov_ӷo_ɷA}'t7*w X "W V1"v.[%Lx&#$98}I\O#v/[ۦ;>P!K'$WveP޵t &q#sES[YŸP+W\ lA"H E,x a)xԧ3PPskB.Qrs0'6:e<ќ}'NM;m-u!rxl9vH87jtOk+g?\;sZOvaB޹) `>I mBxtew'`P iBmTcl l~?%n_}aŨFͩgh8Kq,uZ oGKDsN}A*TW@ F!RgN_޸7A42",'N@F&Q{q~}۱տwPczúD9UlXwrLIgP@ϯ0iAu+Ԁ38/"&wߠ^-ld}iB>vlb>H\cK`O-7,oYMdt;y WWaxQ[!撄 P% *+y,s*ؤyZ m;7=kBUo7DL+;|Q^œrBv\.[\w+y~kC" s(w| 8}ϪtZU*s)|vgyօ}_6^1Ԯrniэ[zTn~OBx=sQu+}j y\cL߸1pg/-O,@u'ez\ k@Kt]ZϡՍUdbx.٦M؈FI>wfObgI]Gܲk%=tSs/?=o:q`~gTw򱬩qӎfOЬ\#?(S(_] px mT١NDK6LY,T`2C⍏77^r׾׾4$~rD5cgLd<)ȍP&U9mAĭzA?ň8a+ʣxyRfMv}-=ꕿ{{5S2߻C[Nqn_Ν bj_|$<(:47V:]_d8k2>N|i{~,8{v׋<ߟ-t_Z cȫOv)w`WN,d)}i8_*iyiF͂ϓD9Ke2tp-.&Ĺ OtClsOsUn(0=z犹׃+6w_?97c{L.J-F`^(#Qغ&U-ˑr{d,6)5PiX3B|7pԵ_޾/#g{N}_{k?7I1͝%Z.3;w_ NV|̈́Byq9nGY+Ƭ}.~)Q&MumG=lϬy聅"'SN;^ԯWm~ Z_4}׌km sש_T~s;5-Sw,|䉭679Y3jzrOqq]u]Ǜ|_gɊ)|Y-ݣy^ufױ{앂ƣ;zd_Gf?{cV|͠&}Wic&Y_y'_+R\O}r,` K+fо6KU_mK;Сzv_׭\j]w׉ Of Bgҿ3jno U&|WgTOQKˉ)u>/-V;PFg)ܾAC/0v-`)Ohl<ݡ繚ˋ+{Lr6.͐o?' 9,gw5TyZ3(S&mU-d6"C͌]kn߳f0j+h2AC ȧAm2xfQ2nfi 4O3֧k9q6e*MyXݻ5}M1d4֘J}7| P҆-.I{s}hvsu{ 7;T؃m B qlINoCA{*ZqEPR2xSY]m*-"f!+dmzm#ԡ2 [9{>oƗ!~qMTRiF]sVa` YTmFiZEpO'RtR%撴EPШki OWM*VQaB*m-9*gNh5SٮV !rU4GaoJؗf'V\ uI[EU`f12֨kƥ( Ww*mFߴUdK O5Ԫ|^M_˖]gIMC7ƙ&jW>(5L&FFlĜ %x%o1 U܁SZQORa""\J\baQteM# o5J["׍(`NVXzkhep zHq072U7N,52!#9Ki %IoQM@gsL~FBK-X=$\NZ@雕|<ۓ-?V2@)+#u: ,Ct6ltQb ٢w~w(Hf~՗z47ic +g]  (Mmk6{σ>ZnZ$Y%ZRLLvխ}W7_6ulmghB[φmvku()FHtRFRv#ةxPqvn}ߪH}l0b c vǴUAFgGzC. 5 kI(3%hp=LQlٷ.T+׭qwաiDhm5ABvzc$4:?-:sw|@ܿlH{2,+bʛn_ھ݂krp+2xFiue\5:LZO,8tIhf;~\%姵o.Ϧv:G}q4y^BasӼй&ڵqsX|Œw"N/~Q/ ҲWv_(ӖT[l!nt[%ь^*xF֒Z[ p^[:J{AI&6ca65W%)JȘEq m"^]sAiWB c0.5jWnX_SKP, *j"iq̣sܔ$}FK[Kj#RC].Z@[ j$\Lc.!iqZms'Q֥#>?^Ͼ^>XmҫPݽWv/Y'ᔚ~ͨxi9.:wxSg Ʒ2P5b"v$6o;5z\ wx|CMlaX%O694¾x6"@1$W:?h7|Cm+OHA6i#h.h ?m;F/*2s] wE^!C@kRVM[MX4,EsC:3A{b l4uvXʂa +;B2턯b~ʭ48myES$km+HTQ@/I%$Oz ^& `YoWm4mF!-ȥ³tWwM_)|q|?I;{<gUھDDvMؠ4CΆ@q<4B .3J;^!5UvVAVMJܠ:~b #Mii;,bȬiJOU9IJ ?ܛmьW4ldv#I'^2`Y-`|Pl&R~{_dE,WcB7!; a_'vl\voR(d,*/EdlLܨZRc]X:;*٭k':|E s+,Z_*t}KoIOA'2~C9'~}RXUůg$9q'J7~'ҸDОb|ܳkOI6+*mKxp[/> oHi@57+eU Jq Dw-*5$8[fs}9OZ]3nq2eO)M9DJ>qE m5B|Cu+,,v O|ߛ+owŎ2yAV4t|΢I:#("C %EX3bx*Z(+NDz3X͟\cz :T+nPQKì?r N-U!{:zԏ%n%NoJL?ҡkeYaXt{tFfHEtqÍ^OqGo(&qSi͖qMR4sIpxv?ִEycħرU}w9;(f[%MVO65oydQUw9cm> gRF>}p_efFH,8Xȇ>|SIs̳aLp^{$75-L&ǔ_8Rp"ɛJNƙ;wA8aCJڴfżƓLA y+RQ2,A"]NJΏokehy):Y9a €]bd_wXrӐ v~c,#5_X֝~IZkW:+ӯ1DB˹HEͮҙ7lja]!M*ny ~^O^8Xp"5c2lq+"B{{d{L$״dr qd"j:|SF!4{a"c0e& m%Ѡۻ[׽] un ߲ *JZYڤpi:Q_$sJCmT{laq=yx0Leֺ! 0!\s~@bsH:^"% ;6ѾE0\_XM ̓ڒC9U,Z,D?F & W$zY:Tww6[mH{s2V>Īh  5w,/[􄨇 E.+-+NQsn:#;1d1 PqJ{ﰌ/-[mhK.p]AMl=UI"fQHcK9f )bx"c1'V zzᰢŠ5w,*'^t'D=9]cŇ>XiQyD,;kj fdْ&aegFf ^AdV lu $ƒyA?fNv(yS?\c]:5xrV-[ߝҧQ냚Rx,EUN'. CD9lXőȬY|MܙO.E .(acpZp˷2=y9+v(LmWa讑&0j5 Im=Hm«Bw (Sk(|ufF~EIinu>I'EER|n-7Խ()͢|owLKQR͠H S qsFMpcmz=9Gj}nS9C nS 4 0>2]"@?W˝-ehS_c sO:o.罰yܿ$4XIy0Z_.2ƱU{m)/ڻ]RQ^T ѕkC1s#r4/::je"-~&0H ؜6ni:yّ}{-:ӝ!j+h\=fs4S}rתPml3/]"b|SsVY*;ԵU\׿IM_.A05Wi\]kxћJ";ӳ-X7b[_xsx>U{.94sױ'ez?QۅG涎.nuj̵s4/:wӱTW[9=HI&xhܶVY26C-]b]ӓj^[j8v W*a]n0:.@ #ҵg8y@1ܘuRI[]1hDRý]p֟Sӓ&Ќnjbt砥2k効sy+ "b&FN{Swm?6} #:D"myvݨUSR:L%F4Ye ed*Ƕ D:Beq76M59~CLs`ʹ&g'UH [P$*c 垈8V7ݴb 뮤|bzA2!cH\%S(tIYDVdHiigrfD2MB4$z6k&9L1q+*-"`v4pr{cϱ+A/)yCqbXߍ{ywFo i6^L}D+WBX/.vo+*Dd?-C/ܟ^ath⡩nvяαTL9˹ʠ*k$yUNcӭhlܙҕ+ʪ6W߻'J={TCN`w ad45ՙ"e@IO(/{fF%:OS;'4kEO4[Tk̠[_L.8:9!o;\=Q8Ei3N+6ãLtNh my ) V]^9 *;ꉒH8b,Yg/]XFҀ3*+,ɭ}uf}>+ג3S=)7$PI+gJ&fǾyiz.y/ީy8lDѽνm;G:e凕xXNh ! 9Hf^i}Q`d$"8c'\W6m D g^Ir໦]1+_e銡݆ Yn'hTbnأ[ ?1|{] ,CŇSG"bJlz4FJ^+6-x0S6:SdpR2,7)) `[ז Oq"_QMQȻq,iŒ)lS}jl7f+jڣI}xcp@p:[[mbm:hUޯi1dLJk1q=#zldGůXb7)$ \s.w=,_|x9:1%i6mnSZ6m[*. :TjRdTpx/锔&'0 _*qʶVT@ !hbEho䍀a”{4Dmj5!cp/Iy-rgwE|f}O}  gHJdGK2K{-DFD &\?^aExL Jp&UC рnr}˃ge1P鮒ݵwY*,͞^&4T f;Z)W!6Lڎɞ~=X3ތx9|hR5Y8uGcY$ښȔىd+cl86Űf/(j,|sI2*z"z2O&7)54"\ 5o=2xs1v2'@x9z#7&aaI<U/#~ @Š1$#,e͸]-6}O\H'F&d{53sCy}ͣeDSwlXiDI +S16:6A1^3Wo5[w,ޝ$}Գh!pbly2m6 x7)j*i:-Y=;WC"Ah܎>I866;TqUrm֎bcs g *E#B(EIJ ݐm%\ݏ=7Lzj@8LJ=r;۲/>KFv,.I5{u&sG:kKhr AA Dغt3j`,ңc6BtT>Ǻ׵c-Ǡ \!'I_C^A!L81e,9&Ê Sq۔Qp"`[fer% $uD*Y6*ýѵڡOvKKS~{n7Id滻w|~U(8v\s,hH7̦ `'H:McnIX۔^r|FcJ MUI\obs'| } pځl޿ 팆SWWbjk9Y#DO<."Hqvf'&V%on̵}a){p]e]]:fj(.'u+zfdֻ^ K D\Hfmv{o=Ysf6CpN͹VB3*[_U|Kt=-Ԃz3 L0*'l0zG_sLܬN,.U /J#' ֜rrXW7\O*kR[(NavcOV%Hq5crA ?60ׅӡm+ Z9sـᜓX_Z R墂c(s04zɞ Bb\V># ؚ*%X6lbO'rŝeJSؒ"@VA AVadeÜG3ė\vDžt,Jȕ7t[;S΋nܜGL mxx_64*ȃ5&m~X'Qhj* xcvF̌_Θ@;Ղ;͖~IzcT {u\ǸE`6\ΞYejsS5q䭚I.C6K&&ZN^uvġwFۃt'T7+}9!hq \ 1E4TrGu7-Rv ћY^@>K 1w~{M%֪C{E>izqk<뤁qJ`Bi+hL>kRѨn)%vIk4-5іDd%^~=i2`u[;~jk/}F`ѓ=Yo`МgxZWprSkl?ϴZ&l[B44hfv]1ɐ kviRC t-OJӨ mkК|xR EwPD&<)MSx< *<)BԑIvlw,4zD~VO$QS.[:p)\ԭ ;4 W X^hPA4l@-x,OJ;ir!͇y;~I ۛq5^p,;Uwl7)$_bm%RMk& O&jo̸FR-nIlE䋮7[>bk2 ( z_cYlY;ÚH4êNkȷ8(NG>` y Y[\ݭ0Wag!EĘt~R@H(8MSQalw5Ơ!)hv-!\|6LwD3Q<Mnm')H`aցk U(D4fYWR16I$f)hf/UTևU&X<.VpLSLDzgnNk~G]B|"+8I$G[*  !aJ #gd˪|@=pe|Ⲻ㚦oԿض^|\4Oiۅ 6yjeoۋ7߆S&#{y5߼xA%{Uzc}ˎW? vmXݺ'{w%C43-`ܷN3w*+Ʒ /e6Mtː/ ;" R7?xio k ~x|߂9B#=Ou)BMsbk?iyG1 n5sXwd1E_~6Pp}>b۪9d<;2b@ƟAAN^ JP{j9 71;EPtx*v5M}_p}ؖC%L ׿̓ʭ[``^3iADr'4c*#*Y?;>yz],}l.xma7qه X11fT@Ov_glc28QKzp eRi4MM G[|Q슱gssouA,lqT0gi cdY [SkB{Y/|㿍=wƺT C 54揆w Fx㿍u=ƺT O 5١d4 =HN^uZEiVڡ&*(eth{4Eie= v.).9 ͷU,HNWNe/SGQ*9V[?Vfh BO.P0I4Bȍ+Bf&R-Z{ +v>tigY<:п[0PrPᤏZO|GRfbMN4rt;y8 o$pvԩ!]R5jJc:hٚTW( dЃmKyc̦NX&mQs㛗ƌ-s|7s+G[,@#>67E`EapK}Boc/6\ywz/~{jO15o8TW=z麯p̹&m7GX O:D7dveb՚IMO&3Ļ\Lnƙ&3PNi̠=-nmzN퀱,v${*Wy<=k?0yN*D>$)ZJQ T-d5٩ Ϋj^AD0 c0fIqc۱ZS! _b?~0f51/t_V!Ք$Tx?3.H*LiˆQat*ʶoІ޿nْeML83}R̥V~%;lLǩ3בj_UQ~ <Ś b~g;E>T$tDN6wl"a o\ 4jMx] Spˆа)*IaNKjiz7{*u`Rk>\5c!`F^aH>>~jnDnjϔ~؊ :Z?4Y>;ԂbZ%bfǢna^Cid~.e1ſ{@r,H/JØ&˅! ej`-IRIbD_#BuѠoWMN-9 NF+-ϼ$2,2`!/{ůtheT&ͨВ ˚7]&ůXfJhJw.6񯱻>+Ҕ G+kQ=T4 Ãu';3,v}c5-!iF嚖؝ ,Vy$t' G/$)MiaϽ}dـ<"V0@L>,Pw `)ߣy6/s mJ񗍃aQW_?'m~⭋M _ٮUslůJKgr8/o~~7Դ)޻>{Wfä]7*Ѩm3-1u#*̴V?OM[eJl?w|f?vd36K\iZ*:^6[O-U>%WЗzy2+>\U͠cL_re߮Ý\g?{6WZgmj/2峧N/zsU5/~ˆ<΅[NRUՌF?;#Neh=r?j?/N r貽c +j>; g~tBVK&ԍp8۲Ԡop6;wGj"[\^5% >K!;0ϸh}Y1bvM$SKg2ی^ָ쟭OX=/'>Z9ͽ:|wcT`2X>0G ks̤ Vծ $ښUT>tNklm8 4Fg]!}v#vBTs^ Ã"1_1 >C3S}h#VpFg'Be+x^*Ռ@!"j ͲЮeN=a;MT3Ja M,Z*hKjxE+hWE?mL#۷;,LWQ[īh~?ڵ+9um.^lœL9&sv}3SH5;RfҰ~2U$c:gN.KsWOTsz0y6x|FuݷRԇF1FXCm+j*4 x:HpJTCt24 7EKSb]{IMÅ8. ;(-$3ٷlaxq8ig[ yvylָ[Iky:V=$V8eh2̾aWwYKi:"}4ސ=E D;0P"jJ6Nh8ec6s込[v~tIq $ )"=wh Bs]툒^=?zMd}3Vc!*@FQʮ_.y c_Mۢ,i\`6 X`Trnע$ ]c@}6M pID|F];hx/km C[k&i| 葤YAX܇Iuhur b5gu)93<:O~X܇ʫS< }dgrWd0==8+帯]5aXzLviԁ֕cv+FB׊ڰSY2c%v<ʵ!wq5oG &$ QЌvb+QٕFH׊i Oes}'?ۋ8Z\#K+ѐ;9e*1y%fW "姦ΧƐIhZj,>% d1^J}ͮ\TsM! ;?]ܞSD7 PGꕡͮDEC+0Y DmRqͮTbǝ# Vq_55aX9 5+ 6U9Pe &: :Ns;n+_(j ]6|dWf8-|JgC!wz1TyPHI:(xb%J g1, b/JTt7lo?5 u-bH b(-H!U0=r}ytKT f_=gW$N0CX g3%ʍaHMn1Abcb R<)Ӵf1Ϋ{*=܇4He Lswת5FX3;)}گ4H}\sƫ[DTOh Z`E.ܬ%jFPY)/ZVr5B$1Y 8cQ`Eu DM5}uF" xƒò9֦BH1 PԴκƶU "x/!Kw>cZ*ߔm,. 5t6䍿l})eZ JxG>R@c1C?O]P,)/4ޢa[nD,C Q7\[fZ(⚍sƣ,b.G^lLkSeu`l[5F3"%e%KwTM*K!5t ԋsѩ6g,E9-\ 񀠀$'Ǐ5o}-?oimYD C\;XyY@Sj\v)&\~fh|ntk_RŷFuA^SWv^ =և nCh`T&Uּ@VQnB ^}[d3h_Nl5?y`'ԕX"^]Ó P8= =:ҫk~]9D 9yEig3B.0WQ˥"܂eqUF4@]#H z X &\xu 8fH!:V,q98a#u6~PPT 1g~\^ìbe`Z@ SmbV,~tsyɿp+U< S`FTnvl+T8)k]6Nǣc_ _s¶Mظ8xtT|0;gLn:]zE@ޥmElqK}K{qԏINe1RFB6ed-$`A 袦ZАP pYwAj$4'#f8%D+AFݔĸ!F )c$I 7jIs) 0>d, DR@FnJ@h)rPք±d\n (3l:H`yJyHA"`%i|&> HCo.,c5@DvJ)|&&P%hw#5$7IkGJ`iH3|Tey8X L>?ET 1ʚP!Wx`U,6>;L BF`.x[ aٗ{`_/ W^^]@a|ڭ]ײǸ¢Pvk|(p^͗HIhr?oe_^z.rNZqM)ACXJK iyl-hY:T$Y-RZ&?^L]c%eD\Q(-+6Su2&(zBLEf (YˌɈOiEup5m%g\VBz%8,f]J#eV ̗aE7!pYQS3cIDT( Oc$r5Vܐ*sqRMBgkp J6ő.B@.lqu !t((pIJp6?tsxx r1Y81RKw9upF|usnLh~Fʎ·S|nɮaC ?vR]BrtD^(Ϛ3\r L6wsuP$E H4ϋ:kIbl֜قεqCO 疼T63+)w-vDKȍ bfV0cK•8F[H!\t~;YhyARfPZ}g6[ (N?of+hF8!L TXw1ox灂]X!Ox!Q;$9κm;]YJ  ͷCm;zUvڧea7ߏ~6tV5޺Ÿ=|hIJ9w/f @ƑoߘTC9Nu6Y{|^yvƭ/=3B~u]ʞ"7s1VoQ/О9͙RNlKe6eғ*uMW=Ϊ4wM&keV̦6 iRY:eOV\%m%cE?Qk3=PfgVy 5|$5f-qm; 2@B]c A,:+RkBUmd 0;AP׈?'!_5"%m~ЌR]X5FZN pucPV+釿B6%L?J*fie,%emHY7?x49DW1ꙧ~ 0! !M5hJ4DpE2p*JL}m#%b:mP(fЦg7/a8CRۯ] <Lj6ԊnqʆÓJ63.oZm6n7(_Dl8aPBw(vӜiRF\S͞F]z.Pf6\`|(P4l^]Dkpotxf{^˥ƺwBr iyDZs*JLKnԆ˷طG Q<~i0uO @\^Aw_#m.9U@L)l} ^'쐩]hr`ɆE ~D$;$7k[Yxjɛ<< ż,ؽE,Y6\@p$lqP^ZM__զi?WrnY0:+QCIc@jo|TaDIsL\s݄3BYsY;]ǒP,yKqyB3q+p=9+$5疋mWѠ[Ri}IŤ3nNgLօJxt?x&D[IҨ@46Y)H! `W6O*?Ȣ)-;`LPcCyspJ#R6pG"Ӓ]화^Z#] b0BN*|ޑi{9[+%4<6(E$WGYBz.pn``. z vLY05_0:lGj`[E^ R:8:Zb I)YO7wXŏu"P=-mǒeLR`-2Bi2Jop /x{}: e@ű?s}˹ [˳k#seЮQ)/'4 *'5={49QFG-eՉ+j'@Oǂ9KEM]Uz 6&Ϝ0(N; b G'zPuO nիi~34Ng+rw1(]IuV{Wﷱ'STJZkvVm~p(b$THn sfS $[ ĸ*G3Id G6 (T~w4HqeNQGOdl2xQ֒bȪNq~z5k6VS|ʷ|2cY]^2Q?Ka3 z1Wŵe>:ՈE7Ŝopi\ظρj3 K-͜L> *WqΉ P-09ZFCJB*ǏY|sc/}{ӲJWE:m:2L]xѯ8e{_=5E'WRgUٶ2ݹ~ }l{=<ѽsְIEe}$ IkMdT(g:u?uTJS{f 7.)iwވNJAO'k6|##nR.?Z/iӜ7⾕y"ф&l~kH0MStySjܯEIyOqe\Wяx(9SoMۥ ;8Pю蝃RSFDhDYݘ<#dqem:.RMyb:cҚ`M(. _{~r歁!p`>4iu0}J$F 0DMs+4qkhI] ET-I"0!GWU4'-{$8kIqḽG='I|!\!j.iRIqa?z]}"}hWU4b˝50FUON:oI΁~wU soճ?yӤёkX d),gΜsuSo{bCn> aE&uȾ|;hTn0}Vǿ7p dsv˶FCp+]ޱϱɦ9E>32孏ٕ R5umK\p''gd6Ǯ7l"t&H/>on%ͅsrB[5g{ib Y=\v^ך`5^50#j@HKgѺ]T8pۺ_SSx+ 2xoCMhnx7r/t׉v軻Yzx<-/9)Mqs+JRMpzvf7Ep^~% ϸF$)TVS)4J`PrH::$n$T,\>Awsk}ÒwfPMsg'5; ukӃ{5q[?&u-(VfI c$[Pt&ٶ *\XiJIY\jW ~{Wsn]*S57 tdVxFKt8LNwҨmP}"-32 P.]fBQ+r 2c '}ada^5a" * G+e YNNNl:~ܪ pkHF䤕}˘c{Ϝm)撬 4'rTq"١PFU1o@*ز0s5'l*vV,:YAI N\Э2y5{4~nky_",ʺ:ZFK72[dֺ xbzY>ۢeKm2t{W{.O]J&Oˮӻ}M؊ru9ofЩƢRcƣZzU:4$RM>E{=yaܜYW4QwxՁK{4wn|ǚP,TԄtZ[Tz|aQdKcQBT#1I{5h(TԴ5f,XFA0imJvv])T I4'ͱeUa5kzA8㻼BEPt²LkTrkel]uy&7Dtx_Hk2>J'w&C&!Gek]bT!-֘ M"Bp ЮʢC51Wx1oD;Qm%!.i dBiW8F'7DVҤ=)↕FM6rֽ KG|qaqn;`Hv8MYa9$r+`Er%ۆ{_sm($1~ZKa4pFjߖFs'j9M"s]X^}2r&?ݮkfa/F:٧(YTR"1al:cDʗ;{{m.eg>ӎKl"/Q0,YWB '!CB$e'N'7D۶M`=ſ~u,6.  oZrc]x!=‹v+/ c_FWI&v@|خJrW:^x˼? v gZ)-ەϱ7(0/Ď뗝s,[=%4;}WJ2w_J]-;q>nj%jhy(O~X^칗tSԥDCEi)z/w%G=}9N/ះ:b7CyR[_(Kh2O]#͡+k ZsqѓD=w@+?f5@;Ŷ{ Q~NC/ @;% _}ѴcnqMk?V$rQyG{ Omm4@GV$A>G:fIA[sD6aњ퉅mGoDY2Uڎ4ږfamW^kYNVPΨp&+jvvVM0n\S<9 0Jz'S8E;'+a .ׇ꥾z3,GGO٫6;_ 91nQʭ.ӞBM>́r\ u$LҴ6B>0YYo_CxE/{ݏ贛?[WDkAX5N=NN0YUBvo}[8 P묏'(eZa09yrkjPLm4RYvd…=bQ| \k4JeVeE s}(m<.Tq<=u\pQ0gZGg6Xpi"{4818k}Ua3>S[_/h"S&!r#Jm0Uh̒TҸq7KQj }U߻WGdh:ߩzhqK5uɄ.T1q,7F +¨Gy0対.D܌k 5qe2pO !#,y^ԍ%n͐) ͎}誶.N|{ &U 1L!+s~^ڤ5uaIwnȂd R44(e?+v~^?N?.O칭w!PF"3[?kls,}F*Qd5ZG֨=b1Iv>7pNn(26~c׮YS+Iba;RU4FC}*WnC]k;4d] ɛ.{KBLl|]nsLN"Z_Z4oz\YJQ8&ߠ5n3?ڲ1>W7%XG3n(FÿlRv A\|&&c약ϷWN] #ZPx(]o~onˣOK`{Qext{=Ig=T\glu o eq4A,?z k!Z&eVKuy!hǑO|dOnazHAkAؽI֜'5lH Kƭd"O`啓"y{l/quo}3k(ؚf!%2SlHx2`o;}xWgQSj\ߔxLo j/wxEI])'Qʚxҟ!k6]wx/UwAܼW=R|?Zݼ+]̝D!*YK~>k4cocW Uu|iL!s?QW]FDĈW; 9Kcneþy&~@Y~oH֋_ni}2Rz]X85y;W=u[O&T/k{\9?o HG$}.X:Z)c:J4c2g^Meo˗s#="at'P~ 4D ~~ ȱ30$R˸\dbujsn}F X+nWDW`ݾ4s=C"OEWpˀ9׻^{~̜e.!2WGok"2.$z[re)_3.?Fxw>,_xxSیF$]ތwwLFWeKx/lRLY99|YnrUѧћ7gzW.DF-uQK:? 9Aݏw颙skp$|%h'ѧOlWm1E հ"w793a_ӯ$3f)R4VIYʴ8Cdt{ <%gV3+?{q~X_./j>r1'Y*FNAeYx\K6Q,6l!Щ s?ixQϲyx jrQrZ+J](Ϭ^wiw⫲&٠,WO7нs gAx,NjgԲ=ŗf>۸qSd}`}&Z?mnvjGM>~*3MDi8[σlSYB-غI!{~\rGy=Dgv'`Z Ǒ Ky;-?j3ym,ߥ^؊HD~W_8EZ*x%(į@o2&U9ʍ:دZ&]zvV>W~;R23WM59NRH5KRl$c2>:c|NpEV^ʟ a]q0f2[~"Iiܠqr?{t~ҳ~)8Bb>Hh*[~űϼ/*}$}!GLL{( &f=i~y[kcdyc Ee" wd `<95T<'&AedVB#4/PY$%5PxvE !!feVBR3*QVB+MVI/O+w*VҀbRS/hWس/F|:jJ͝ru(98VF[MX)S ǁuCx85z6}Ǘ}KHK*)g|ƅWԌB@*Mf,NkynT(*ׅoazyxޖ&y/Z5왟gMk3io-{} ѢV1tFB.z f%09㫇EF|XvŊEǔ RHxoaO+xOĞ{7WN eG VBѡ>erR4Rlmh߷j[r\Ÿ5uu;ѱR4:{}3є;ֱm^IsfN|t^u,taj -btMw~[Λ¯aX>,_^]6dx֏>6[A3<;X?ntJm4s?>A׾66oژӯDRPc*MO zVuNi>H.O6XJc)ʠY R.J_Εp;RiU(j]#fX-ig/_\He+) E&1C|c߼'/DCϢ"UM0/Dsܩ"U ]&)qY,%ERE|M5Z@ OkZJ Yk]UK!OT[IG@[)MmԺ&ll1BORX#mMZW2R Ԫ Tc74{- !csۢrG~(Sݞ|WX}( Ӵ-:kQ־Ŕ[ em<{dSҴ1- =S)_4N/Bot/R铖i]2V k)̘!K]z=_(;ݶj|v~t}i^ck Sym:E?jmжK[vd mʟ W:,3d+h?j4;uhRmΚڧB1d+<F'LKQfM#>eyԬJӭ?V,9%543ˍ(pqR=aZbDegXS̈O5tq.i5H6yLfrSI9 ӦRiWTup/9E& c+~~ၺ {V2Y+\ i,7x4"JBG줍=m>}4O?M{(k6A$4\R,UFd/a}D-D̒ѥo/r~sI^y#]Fc00FE4K{^e֙f),W ߚnōBuv:`Ңh"Y}"WYrIU>[W=TXЃEL;≭UJџTكLkFZξ.>#ia;5E{@/`8K`]˖LOW_WC;StRG;lE Ti~LH}I6^eթv@'`4;ktJGۅ#spHmO}mޒ phRG"h5֪puStzVZj G׽f9)jhU36t/:TQ^^4U˪~x䇶)RND[I39/.s0U m#&&)~CQGV6Yn_(8 '.;L@}T5!eaT=(8=ZIg}=vL% *&mUu<_+_hq9r 睗~D=I}oΏi{3ks=m=z)7^ʃeP:ZYr3)9pv}Vn%015CҊUq+'2M$X þ)VT2g!*(#ED'@/ I} suv/ к W+촞2%1;\! @&!*I4ZFIʵ䊸GK7u:cJUll?5=R=y6z9ǗN}ԹfT?!8kY.&nع. )U»ׁ+2;н]𚜰Ov$̇jTfAOZvA~f[r TpD~D-Lsz SmS9v:inaoL5\~ŷd kB:K2*SONt-cC]ڈX??9S5[ ?vXc2{KgT2>%*g F 卼k%[ ?M[<}ʨlP]|9IJַ˦W%4~u& {6[f*u}ƛMi8pdO]uүjףu' 2͂3|ॽvW/tT2iP&IpelI{2 j֘gru'{26Tq?^-}ǹ{2d^09[F/OA O5SMy\$)JfWk}juTw:7W&y&!V$@@#cVZz 5ZQ|CE!YA&QO򈬾O}e$+h*`F|j.0% B B)- e@QCc "쪪[ÒPH@X㓏fl&f~. i7Yhy4.;nACTiL)i6"&R={T﹆Ԏ?<h!0LAT"76Agܘ}zIcA7RfA a)F79oўL]PX9ygƜݿ}²2%C[4T!Fifecr@Yf#%2dΧ~R *%]YI;>/(ˆ5@edRaߚn}~S U.p$,2E*+V-:x3[>dBe<ȕcjǻ5{wʪ9W Rw<Sgz2L@=7:ʙ1+\.\l[RFF'E#%< !qQ* 1(wN]hR65[ԥsfe[ǤaQ44᜿TNJټKIi"nn6GY/W'ae" } ލQ.U>M}i rgiocŚm}U-EMsl; `pEU$Й'(DI4-NS`aR eVR@t`GnnwM pDH".3O!Eh3 џyCp?®*DL#gٮ JE|fwPШ;w>{-#FN]J4 yF?z_/=>]8 NpjD $zKƀ*=|Zo|jL?gt+a=b@W[B`9׍0(,'+5ց*}3z7O _$tKIR1'Q  M㞁VRpjW W$/hK¶@$`쌹0A1ف.!,T^g`Y~BqT (iSgH W8)%Y4L;FeOYSTu7\O5ػ!+S{ pԾ/'z%+B\D q ~%WfjezN@4 uSVk*:$BAg=ecfYFʿq=ՎiwݻeHyb~:=0C@0P3Lr퍧t~iq]') O~ޯ?5deC4piߗu%_+XO,?בA$,ۺvSD/ ~P]TK r$ MsJ45g =yk~F2 S q T1HBb[ҐAj1ui4+ K4DV%R`/{HuZwR0Mk~\YJ7  21z[a 9-J>vػ05dBksvc{|cl3?_bTU8#8&*lDHn̯j[M)L-6V.=sG<^_X_>`b*f ϰe};<+Ih몕 _Slu$aqyOcen!ΑU?8mw[x,hBְ?s1|-CyVnLs %kh-<'>6d]o8;^FV&8y0Rwwtג̔y)g:TQGj׏PbJ&ZtM_ޏˎH!I؃ 8s!.Lߴ,wDgf75+*sW vg@Ls/M{xSyzljtC*hsd|{jnC3kX{>ξܣPbMh%vGն6ڂ)T|a%'3)Q:_iGt'&ъr`iݗ3Qs($r}&iM, $ׂD["aIUj! :k~jXF uVA_%*| 5gW(FI=R= 寧'oi X7ugӡh2gh\j7\sA__ 0>$]=x`YУ;+_O/z_X]6rtNRBǿo˽^hu-=Dʍ=O|fd7oآ˵7t_[J1g;zC~IkթѸC_(r}^OOhOFiU;}9 ~>ViMOhlQwSŀsuH:VofV~Hys Z+NxgCzB㉶&e\s0dUi~OgS95˿ d6sW?JOh<_ږ3i|JORsy&~>Ƥ44J^0k]t4#/a&,j߇S.>ݧnֽ5ze33SVϘ8<7mˊ#Z..ZIрf>v. F,E3t3~ZI-Ac1^+)&̊q\L$Ko }V$3\&b uul)*\Xj,m?Ô6khs_3Ac/7j2v \܌K7ٞ0T-FM¸c{{oy h<tJ4T3枕 MڜcCTspv^8W6[_y)Kbgtu\FYs"g,cRgjf3#ChNigW2xEE֔ul(j.,m+f3 \U@ofꘆlG&vVѫYRեk{)h+n{LNFoGa5^R/ä`$zL9C'sA;V9׺9Z-9y] 7q{寎(Dt_c4lviΑX뒦/ÄG,ޔӷv'}vS+`(ejecuڹtSgՍݶjvn/g(Ni.}c`\:QGܝ~P8E7 ҘLimɸ vO%h.}uh l$`t.XbP 5Gc0py[Mlnj#YW؎'C<Ik6l,b 'V8\aPƹw]+Gܦl+N#_ϿqDR(d3Nh/kڬUqg4c|l4lVN߻jQjSFKE~[zsf 䚱8dH_[rpx%ID.3* 0,ʖ1(;?u^̇?4Z߾GOљ 5@kzG9gQ!}oԑk5[$Eu9aae_Xmn χ=R1'k5% 4.RB%+ K;̾/}KFn9a-|qN ]BتHW؛wPy)^))>5/:yd̸UmޏE:TUZݐ(vk?e6ӉJ|kbXaxjRnx(M$rsed5 <Ⱥ44 iX }k2ܤwl~)1Qo5P 9U]} $ާ/ Nc^)_:دg~4/} 4"\1!Î̜İu[ 5FEG %?%Mx!^I z `_8{["VFÃ~Ls]+\CE +2sʈag#SUUnx0L2-_RZ^4#>o4=1+ECDfL8د1cf?bKQV8DFHb!a*8GOV~^{3,PMBFVrRJk0U-fE!eARܓZkmaLs؞뛄Э8 ͑K]We^ ƃJ%֯ppoMR'!k?).dD4{J!ܪZ^vg&\j;̙e5h>}v݀/o"l2 WA2@]~{V"]}lkp[Lψb;h{vI+{LO~7q\`)`tF}?zi;{bܬKk%=t`3x윔^^ڮccO־ҡSyG ~YςcM9zO*:5yK -x&ow/ռ>NJoG'|-)m^灢K6cGaфg;>捍5;i Uӄrф$4g`Mrz|ҪWDDROcdjh]-Y36]Qc19NtAts? "Z-Y?lWӌ|w{>Q,Jou^/UmKhNe)iBL9QӨh&nM+kmZ`I5*-Je|U׼p"xPW  ߳Hx̮#mEW#Oc+`'ڐ_88ه_4"Hp:Cs8ΕExȓeF]Ж'(oau(W8~Qɠ69Zd3E.HeإjrOPkvP>mᶇr9#Y*@N3#O20>ws^+Bn~PByVR.2`8jX1>bgG a'_ICPr'ěQ=9Lfꈕݲ:.mӐԧo srELUsdrj͒qNrYw.nYSmw3$LhFܜg|-Fx.ĭ rVM/X̅hVg^ĝJEd~́˖T,m I,0' ݊j//qȪe}K+<4eMR͡sX8o]7 y{~s7.᧡5ifݙ[)FT{u+.S3CuEw7G[b!WJoil кQJ @L,"W6-퓌$a{\7ڒ5!ޔQHjoAqI$qfS<` _-!$xd&$s;p-vEF7iF;\>F첃36$ &vgg 94yN'&NtN/e3;G7Ō\rf#P |72#GOw2_7az$)FY?_qGS W'DZCL3Cm?3TSl^n75 ڇPҔ~lȜ{Ub탆6j>BI3i<rvA8!.ރj)tnA{`.S%S?Om @=dZ9V&j%iEқakgj%h׺_,ݐjFw렧X'6G(sC |Vҍzw,^|JK DD k9a%R 0]vw}[xǭ#` $h9&gҡh.l9Up&BN\64́_lգ&> ]ClS8n*ط10MDhnh^s_2)Ze$4p>樶hs؛U}Di$0p8!} 9EJX2˦2fd h~t⋢qnT'O8bzw=?09/`80(D0%@e@{>5}|AV/4\+fz(2A1>t*@4-)Iħ[WL_Muˤӂvp4b91>ԋf;ng% kj  n,޵$->4ѷ;eoW+˸i֚nL⚛>s-P>Re?/Q/#Ir豸Q؃@U#ĴX "4Q?u?%r~ XV/tn8K}'>`L3oם(Nmuͼog9vO?$fzx$8ibn_~*ׄk.;% m kz3BiV"ć*[ˋkc]vĺ?_? T.I*iIT \j ՅN*HL$\P ;JVx vK-ZIYo]s,ScB QΫ}j倬VҠFٔ/CO'4s(9dAʩ =Ǥ^y=Yd:aPBw(vZ?!EPqoE6dٽr3Q`OwQH3g_UW+L*3 ;Ptۻ0'Ӏ{\3 ֛ m.L:ydy$4h7IYĔ|1J6 #$\ "]&ABLXxɑ\H:1Li{o-zqA>:-Nr2`0UPbn gRGN2S 'D$K( ]=je? Ƀ=o' t:Wq]D _¦|~EM1t64ڭX1dYD夠3\{B_MP>K.fx˗ϷK$yߠ;ӵZ 5QLq<2 U͐HcpU~2_ܜj1.S1H{ 9CvA>H TmGRAՉӛ>Č ֒4+3y5_:' sFh8iBUP;=d ; LjDҹ*ڜgef̌Z;bM܁1kبkv>] iBMgnDRJ^HK>XG RCeDK!:*׬z#sDgƸ&v`mYyٱRj>x M-tfRzO 4 #\!d6ֶ:OW5?6֭c|;2ʎ#2CA~OWU];G FYZN6rFQH~,X?[Q?_349}0J& #{  72dF!WwZZE!@EJx+ uw"+X#iה.3_\bM {v]hgs1Z>t3ƫ[ZSs 3/ZX̶ LS 3Xb{pvj#}UtuGXY9137-/9ʹPEAjgr}Vю@9l37RAʹ)(BCE鹃nxB H@h$4I*I@еPhpW3}^U;(hwŦ 5$vd5|*w:ّ?(G xR* ʹ d4)PӬ~VZݡ yJsu 8 bp~7<9rwɕkV?VZCjUq`e~Xnwo uv9ۄwzs|ch:E{oj~Xn͝"7w6v?˕kV?{}+ݺCjWWlʚ,W:1FҠB%ܿT;N Qb(zc Hsb6v吚axM&*uSk$"Wӵ-0'7R׼軡V7~. I *\wnҿLT4|_[[pNMx:Ի>K|V8ۣ/'׻8+"\zm3;K"yu~5 Km~À42HHWΗ\au4),ڛO3~xDrcXh{^|y@{x$ K} }R\Umψ,; I}@%W;@a]|ބw*~Hn&yJ?{]I`ʖB%;j:="y`ךZI*R /fzo#4G!3oCR7ФܙƎh.7[x''3d4g_[%9͸UK:8˓ќ_whꓹLcǍMyЭAtFI=< '7eiX64NIibV/[~ZMةuT"Eʳ7&tױ8:!煍T[`mi{yBgNDz^Ȇ%4kKKUfiBwmw|"7fqJC%<5^9k;TO2Y;eGwʚkVF#tNj*7e:gY|8ߕ5O~wyMtŪiʣQSwd!|{rU8o~W|UNxj|wΩ:vK5M2볊I4 b+W}qJe;,jZH)Ѻ %>c)s+?ˁG72&9]$o欭Af掽vIiZuuc/vIiZo掽Ҵܡ$?"a<#WMr]E oGMxF+wjkCn\xݷ?>YC`qsڳd =Z{Z5w=֞#A=b/!Bݕ_pgnk K>/u/^t(7y<\bԴܿ&W%z|q7չ@lDYLh u{ڻ/j٢I4Rp~~_WoytTb^\icMJcA~(-ˮ)a m^ᰪU"{+iycu-N,6U,?Ho^uxSj0e'ΗΫW8W@ +^zD CK_ ݛu՝ysMc㬦&Ɋ5 ̶F_ݚ duY=i˜\M_85x'&?Bӄj^ ~ʗX[jRc-j^s5MmK{NNj~t{&y,^Rw oS"]?홚&lrswzZӄZ{ o}wHKB31ò]}a퉴$4m Tp+K}άiIhJq0;ܣO?gpn2}GV跺zҒМ1[׵{Z_٭Uh*#_hD5 ]Rל|r혭oӉ%=,Df7\MJStD129cҹ02A^[5'8fމ$4&HDfЖ|ac>W?_?U"?l?LSkz!&ṳ[> HF+O 9Yw9`%#̒_naoԘ`dvP ^ܿu_;&C`)p.? qI~80¤%%٪L򠎳0f#ԁ$4I1A4ZZ1"Ht-{e ⪹zxJ]#bʽA)g 7-;ԾYj@t+CY/k&uoV>]>c0j4_p2}^]3n^ewP`2jԨYSO({½@q4]1i!̐ 7~ayK/?\xAL3,Mg^yOh/:dBIM#StHw3U)~CZGdev/k+7.;]~|3U{h>53/Ej;/kٖFHW<#؋6pG^t}gck}\32?4޼Yyd?b(~\8^T;1_3` 5Z:sIx=p!K:O4!4dueq ,_tWlBP=>+p֟z|'rMƖl:CPjZ]4AH,7+ 9ůtJ5ԔGVƵEe-$ Ӳ-a{)<pf`d1W:rMt~mXu7q 'MĴ%q&0#M_[mQ`*5nˢ*ex޷U| ^/lT؛U;וˏL[ܸ@;*/rCʱ5DO8:Q9Z}v< 1'3Ph RUf`%O8:S9=_p!\(8:}RX2gF3MVr"6ç yBcQptʱjuXWh7g/Np땯)|8Iw8 Xw悏?h@T/]u8 _7Z>oP8G}\d45s%rpίyà +}wrVutႎl/[ֱגt( :%L>#UfK;r'<ҩ`i#EMFk^S k&2QQ1ncb8dUҠ\ޮ2/1*4w6*F-AK-B ,S<3uI5k>gz kC*T'zhWᤪƹ-!i-Ͱr0 h'I5 ڽE1-X@ X 3e):AK08a6Y8Nim^mKz{=GW4K>=}5-84Nz3٥4ۇ^t5(Se+|zμ ~ǵzxf4 HkNx4[ࠧJ$5I iYL;\VtJBW(I09Ƭ.>PyKw~ݝ{[" Cmzw_@~ʝzޯ`Xwo (ǟX|l ["$da}7qԵ53Xiݑ:\K9x$x;3c-Q#᲎=ihyٺu^rr/f\d#^z;2mAIK,)5pqvכZDzI5\PXvGE[=)uozf@Q+3兩`cf(/_Jr]tiARsgɞn*&ȵXvZ.er$ih8m1r8$ /96߶ l ʌ摐d4 .5vfUOUf~>Ӕ:S[IBsx<K05E%BgrBdNo~Ln_n}UZͻOMpa~%te7:s¾V]=v+Ӧs&}c~˲#be|`Xƌm_qYoVWZ4@>QWPwI -'. M'UejA2 -Uڎr5i^wr{l}Fq,Yr4h3e t¯M#f:q(NJ;g+ї*>6zM HxW}98MQHɠ󞜉P u!i01Z4xUQNqm^V5smWZ. SwFEvx(GpBቡѓ?~Zzeb(k E[KB#̵3%G2ӊZ&?u6,Q#I90r4.ɤxhkfPx Zįkqs]ߟ2{!-M13npi O6*DPx>\ȕueH5 iT+)ᰡ'oYPoM)5rMT8 EKH^`7XFe;Ƿ+_Qrthẻ3ՋP_7G52|al;uח"Ufy{w׊nZR\Ri1xؙ/ *9HZũ?6MLԪ-R5+g˴-Kn^$ܚU[(BF< MǗf(T*H5ǯp1~j#gܢ ;ʄ׮o }G|逊}UYH5ۯpJd/_cY.)]O}ȵ}!#U˙H5[ᅞds M۷%Y6 n?Aė\+-R;aGn6^9a~fN5f͆L/ʬ|7\Vͻ< B O̓.k[(zfûc_DC4GRy+]W?|em{渲-&wk sfx[CV'~zjWY+Vwߐ5]ޢo_C~amj^z8E%Ysp򞽺w 73m7W$+K 5ivʸ_H9ks>YW,gk״O14َj#d4LKzkTUL5M; _`I`Ƽ|Oc8bh,76ܳ܌ӱ4U Cݱo5]f tՙrxŠёժ+"tCDF}yaqom_7,1秦1.  ^Hnyx۠^W/4O]79}t@\>n$ yhg7ݿ»?4C1~4c-Ffb-_6PZ h%$u>ƌ(K<_,\13=BMnKw wePNnj)9Li1& w1gLfS6܁[Z{+~kJ288AJ#WW f5z:+Ԇ@Yڄ ,ivn]|{WVxk \ML^*ja6D<|HNX^^Z|yA52<٩F/J* qjj/@DIzA],mǶۤ^:?Ś cFE[ҽ-IPf;MIRG1`{C~Xm}v"78A1!~hnB),(T R ew\jV@mZ1 +U][ ]042 v? vnOXIerM? G~ߺw]Ky6m1Bhewk&}l KUMp]#ر["~p}4BjY{L.9FN$-W|O_x|kyv0;VňR\i}N[&vlU,}WX^ﭯ@{¢ehLr۩3ݩYUn pf~N/:/5ŒdBGk񌚳o De^ܞ?bRt?eq P qbǜQCKi6#P:W[v\v5XCkwY50PG_AF3R_y-04RkRH,IN);N&|S}4O/LZ|AD7AԵT> vw*Ü{m B#weJK;\߻) &-l@1VSY?خi:wپjWy*[Uؚ⑳պ) 2L޾MOI.D4!٩|𮽰f@WH!`Xꅍl?gmuuX V]N=OqG2N!dGSO jDUib&D_lVig)]}Ą }r3Nkz_y7pSF|okJB*VURR SјWj쬧D.X$Q(P?KJ@ľWWs$웖ǷE;jlDx]>3 >X=Ѯ(*>?1<[{-Փy>+܃e'f&<_t쪯&jt;X)5ܣ"y|#'t 5;ٳ(n+c?OJ${dUNjfc LҢN:ɪ*6蛘'pi&|Wɞ޵b2c 'q𸆶.7Yk[9=/|mgꦁVS(/~ŷ#MC_ۼ/UXQ(ܕ䈎*j4ES rSߎ7{ES_ʱ.]MW7^^zܔi`$Go y0(~SȌu"߇͒SgWFzy;|x /ۯ}FT0~>uc}Khij]Q/@F!:?h@b:)AZƄB+2^ v⠶C?Yk@1@٘e^+ɸ&B(p谬RL@겂vOv3.?ڣz1YzM D(^ԭ!U S܅so/6J,*{zcG{_{+0@@r&_ i+m+2QWAQt5g1EvWU;<|c^CJͲt7FZakғOI[*JSwl|Vz#Vϋܺ]ʘ5 4A)_6tFbwb]2:9jZ`0bSu߾NF5 rk-AZTO!@"*dbl;Dk`H걙9܄pԜ]t WDJ_1Q1e=,"e>~}OِHNfj:I t!c?P$ 5gALc@ӕf16*qF SF.i5AVf7bN u{+`%\7ew|͟Ky*[^gTX#~Oϸǎ䋊k Ѩ@2 d%/l~113Tf˞G٧!~۩2aEJƳiaxFA e#Vלbͥ&^j؊i7%gz&J6ڴ6ZݮwTDdԼIsIm=2+s+6:9*JØau9ʫK  9sO"x gD 9'Yv{:_E .[cz˒zS hLMםJ-`ŝ @Xe(an*٭fss50(iV0B`/mMrֹOF-c&ΫP^aXO߯:7jbAjg KS ~;L)5P6h9F;j#Md&n0*]6MԱe1+3v~`Z$A-05m,OLyN/}kԿ}EYJ ;7:kY ;ƍIw ƨ'FiĒ{O~_{nHR3~M6{QQ'LmtIW_ ㍁BO ^O_\ys\@u>=a⯴K&S0=l,ȹ~'H|tcA!q}B]*F%FOZ1rѻ6 9k}DPOYuDF+LID"h J/Ki)Rcwwk_;&ͱu)c8O4c9tHF *E.Ԕ&f4hMk59T P>m`QD '90 TJni.g"L=w[䥝;NziϘ;mq]a%ulU7ɄS"ĘņUrfa4eANG/FOzJYF}x%:pbFC bp0eJʅ:& LBs >vC>/o=eDi8;@!iRB񡂔:Yo5x+/;tÇGļ2f6s w7bò٪rV/=ʠV9b .e!ٺi@r>+Z{)0Ǽ<550ƍ 魍W*q!n/3 V%T[cu=4pqȽ_22(ٺTFK2͕ Xc\l /r ~ UnZ%ں`: -{kZ['zM||*vmO[޻mc&&7.pfd6dni3|JrMZF4?SRNxf xɐe U5+q{V7wq$XZEs[?fq_9h}H<ӣIYdZٲ2Mu]\KKn>N _oz֛6ԄZjNf1;z6K$"h{-7]賧r2_;jlX,FpZ;2߽N }߂_^B+Ѷ)a-(\k*XDsMei*uzvG~Dp E:[̗K~N l~ĝgm)TvRzSA+DXc R.O:3v8,H!pJ?]wqLfܷsW'^`⊤K _72#HQ4 R8eh{Xy o.k>(swC> $d:HKʹ͛A +ATL![p㯗-ܰmzd:/ћ\I2&aKH,͂҆sIzޗEߧ@H$g ^g+k>h +pwUgu8`ZfXZNvgoUqBsinG jܿݧ12dWn0A 8$b @ UeiES N$/ڥ&]}7-ّvD &uAι7$Nu ô;&(޸]Ӧ1o:?DcئLאF6:@`ܶl|AB4xNHَQ],@]l1Y]{bdHи q ԙ^` OauLhZP8Q5mj HhHHL3t G_ M(JrE L z[ۓ8L'2 xBWVbcBs6lhtMbRsdH tl7. J-]d&9oD p♭d/NPwIhЯ ֦FsA3å8r!/IP%sC 4R$vOHJ*DSe-Ht&0eL=#y6tcsVb*–)8o:C\ tR%b Ɇ㝮QTtxê8Q>\M_jkˏ7h`8}_0e8tfjiRbבal~-/긦|(YcRq3.651z닰'H3*(Fc u[1F9+v}Q$hBGCܑZ? x^uy‰puU (|p*B:3c-M)ˎ K]aA'Vg)C f_Jb\EI䗤ņC$jbhczɓd\(r^=:Cq՗ =Ué Poqp:KQiR9XQuTp E$&fA*' ddx-Γ*FGtLE )`znǟEW@*r^)VxK'O2ùކ$4tsIl$}] Ih)IwJL DU8nm SW✫p@Q9C>N1MѳH%(JΫ%C{:\; ҄pVxCs3|7@~<1xޝ'g\mH Q(eD1녊Ya &L r d| ofK#_cۗD5A[_/BF:{#7Oe*w`?߿ÈgW>%h5n-͎$?JPX2u*PI-;'jZzѠ?sC F\h"F͉ e'IϼG~,eC6 54"+\ԱBy+\«9q͠?kEG~"lb~ʨ"!CбYY!T@?K\x9;a{^־ ^Ah1r- 4ڐIp'X6%1[qIzs',3:gE%(sDXF#,A1\ N^ovUi)p'*u6ʸ]2kI OaLfqѦo^!J\LuR9KA5*EHY6}%{/S OYΟ썎|Xj :Öyc1-}wx$O Xg MBD >CE_T%_"9o[ GN+i; HFpm _ޱі#^i{Nӕ 4Z )‘wcaXsgSjz#4 RjȾW]w2 ^!;%h HgxAagzm/X,C,cRLLUBlT:qMoY4jȤULJ0 @ %Y(4V08vuF(FȿQV@ɞzGA>'Ytca~;,BR92yW^ܷ[2ԛ)8?1Vʜ{Karxrʗ=q]baw6-_h7}XբLW__-bt5SW1&+sc%4eW_=ѥ5Ѷ"Q1^W!OƘ #eʝG҅F5 o׹ٚnn(هHy06.vP'TX p1GꍅhRCV zۆX/B\S1uvˣ:W(Мf`OM۷g|7_LLExfbXfH,B-JuޯME)E藄2jK}niG^U#QL.㴌]u@k6?H$PDAJ\.=mxYbeTf>Ӳ{,9SKii%@@0A87NUFr>FSղ')ݶ k2j柑;Vǒճ- 1"q4pW 19gF @~l%v ot1gSAZƭdX)$d01՗jW@l6O,eh묟3$UjfR3eM q HP) r31jt"2/Z4][9&p1]DPjؾHyʼn;تDEMǽjGo5{.Yv3fYIT @"|=Mt"n;b5)! F! +b51g!Cer0A[=&A&.0jIVFTbΤ81" Iepc-5Tu= VS!=e2sx==,Oow%9W ^_ji0Ls`9,+@!tr*FTnc4x+4X΀e P113o c@,K\Sza[o;`9,(idƍ˦@]>5 ֙L{GhxW]^h֛/i()5)uy5 gzL{-$B.rC=t4lVv'*4oDmD^@&C#|k>MTKB4f&0PCJTt3_YҙV*idm#W٪J1#WMnNbN6I'QF\HbcL y sm6,0/FR2MՋe\F*Y+vo~<ʸ薁9H> t[dRE ͨLW0Ya|XI|Sif10 aP1:(i8n90eZE#4iyPJ% LC)|1̠R+ "10B:^U~jvEql'k"l sr+6wȨ(XR 6 HEGg zMP::+kztz8:)SӌDu840Lٱ }j/ke˕5̘# `$8OejGQ"FyE x}*4F-v'HZQk"Qp=GCvmQ5 R)xB$!|  <'_\X^;&%]U5x ~|dO?&/w]{Q1k§$o($fcߏLsJTPdVi,)]>\:X򇅂 n>c=u F.\+xttߗ4x@B@:tOPkek)7oJbJ\M&G6NF]:SXFblj"$-0w().3e8["漁p.0xDm0S -e9/^FmY7_A3p)s6eP+kI-CՃgv܄~ڎWɿGO+pQ,Կ޻сZAe6"2Q( DɃ#47 8AT"83"eؘ.: ONә+sta0,i)%ڈ\hB P 2 e #bЌa92w-U4OE0LjG ,Mf܄6>fUK K5M.@^V"~A)K5jJP $Sĭ^ gc/tZf.8ȳ|4 #]|oJA'! eCbYi(3"||F`Eg/'Q NW9@!~-!Ϙ<, `Eγ(ȿ/4NRWYx;_;ۇʹftVrrlyud63;u#g/)JlJM*E։+RTB 7xQ,`%"Yx?nLEaqE;q{Qp% VrNjO @dG 4Ajl W:jr0.qhldߌy0c\MpJUi~eKgFF%8ٳx옺)=)[bDPHYzQߚF#~'BD''228c?F:odVL{^M#8 s3R Kd$~ IHq-Du"7&Q\ܺs1/覂F,dSDۢR:g#vB@}Dt"!#,<ƌq  E)"_Q]1!vH:=+RD.E5/F-1|?g M "/LN$JWxy2fs\aKXxBWӳjN\ߚ,_)Z" 3|ͰןiDY;7, $X(n%2 vX~|NW7%኿gKTB1)Ie`uvTh!*AF%KH~nsXT2m/sWB@ Q"ir IDZ )Vi%%Ɖ lxPTFT J%*NA[XCNM'A@)LqbxIi[dX#NMfЃghv Cq:htIsmM}w]dLkM{og:rō =INseō45QU)-n|d [Ǚ]Vv!m˦]?آ.kخ:ﭽ{wϖIjHǞ;cO?:->*[@Pk9Iӎs,fwVe3-_idH0 ##WHmE駦Ҷ!A<\2Z**sM[7X"xө$Ǵ#oCo(ֳK_s+T5zH|%7u #Y`"A&3S쀶ZU3 0l$r$~EAצ1쉈ʑ2Q8 #.*Ndֳ|L F`IM*kT6n 44"()> B2i(p@P*.Y BbekDifRe;a 3DI/$QGcc G̗o_иDdz,PSFМو\nr=GF+peꭰHsٺg4Y;][3AmV͘O)zJE\Ӽ ͆O F˻^܊D+PΆ4bGY٥+ͳi5.O͎[L]8]NYc(ZeݽڢuB79ڞ#f/oEl)ƚeΆb⁻FƒOw"嵑hU^v!͎P<rHj{td哲nIgK!O-/UsוZ\ܫ6ڧE]84$6BsхUU[]NN_4g7ۺg_d,nmblהXѴwXDFEе̼>{|먫j?iȐ8]7O.v+i: uhrRUklHB?±دwo+`f>[^,W:ݓ^ԥHHX#߉(L q+eDHU]_rT&Ĺ#i,Bx b+D[{=iaʐrye0MV7;s;*;agr#.SH_Q M%G.F.E_ ]Yk!BV ՜Jż]PT8GdrdI|fMU*nEyr]iO >5DI:ΣjPVSG=ՎNH9+XRR*Zb2:Iju:~>|`*T5sVa/HGχP1y1U4MӠ\UcQUɋ75WQ T]p #M2zO&XTr xQyS͊WƔͧ4"wRIK]Z fSX׳%jtpHU*GkuC$4 TYHLI=4zZWdoCޝɏN$yw2EU1.IFsv'a`|lgIM*ﰼ{J6ŧ[_酬q^8rOwJ{}sÂ^'A:PQ/jn*SM5ۜ4{BJJP^ΘŦNi(=rSf6k4@B|0vNAg5+{##=xmL1sӚϐl-segq?r}q]{{{kġ.Щ}Woe}_S~t~֨ɗd8Х}?xnjW}UShS־?.*tzЖd@㱩P'IeeI+yӔK8w'\=s(3շliGXN5ԒgmUI/SNVmy6|.<;?s981a&$~P Syrl ޝd8\ﶏjhN"oM2W}ѓOU5v湷[a+s>9IQd[\f-Kh3`D.m᪚۟I])M.pU5?snھ{fH 8cX Ij6}9dFȼÎӿӒ3ekt&/c?^<>Ь:S&nU~G >~?sS/?p*έj?MO>ϡbU!b`ֺlML,nĨS` [4BYМ :Se*O6dhEp$ ɉ2sd(-BYДFs,X'uR0I9L>>ӝpYڄij]{cqA"?4cSlɁ -6Zw؈kϕ&K}9i#*03@PI Aͭ;CVL^^(ޑgc`u1=L1q?~V5yWtēxKpnv'.P雭FH(66W ;Xށt{G7]ǃj24d5}jCZa$Uh-ᑝFwSȎK< uBn*sHKUx "X0e4g+$+2*Q uA 3Ӎn6 E;nze?9^W6.v|0\dܧ9B$aI;U^ixmԼ{zHG<~'KEnIƓP`/ԼT3Zl$.~hSN>ZӐҬw_+F[T j~%#ȷw.Qt)2 KEs//^ 6.66TO}D1i6Oa mRFIqZHOqLVG,]O]s3+C`,Y(JVtc|(wG!)\M̒nG1W:s\>J2rZh/̟ۇ4ɳ}yuDRicg?WNT++ opxOi# DI\i#HB==w\>$ӕ˽'xD&pw`;XE,\o ,צ>~`hf(EEfbBG̘"OIX:n~Yi 3n$ufoHǴQ'}QFj%rchnEIDj_E' %ronUɇ@/2|? /Z+Q#WIܩJ|M߇oltJԵCN^xAZQM^2Se}wϙ4~yP)-z18o@!zˀx=ck,_e]lC9oǫGsw\?{&}F߳7藎3=V ^pZvw/=x%v/= D?'Fr8(m F"~í&)Y=ߎ()g+S)tO};B|B6K+'!ʽ/j9 SqdI93^ .xy#zkܔ[zߝ]K{JN! w}l]I+q&MwJu(WtžR+MBB+~ۂN|lEx4yq1Ψ[qKΨtUOrJ4 ƚƖov{?S 9Kw=ث5w~nNJ'(B%kG?Jgϙi:Hycs] 5 lI6?zg3uPM3]*آ6{K*iSu)XP'H<ft/O%,xCQTׁ}-Fld*$70@vy wGm:i)9ΰ&S׎wGͮg]װ9OpkNV%ٽ}u<͔_ۭ/]q=;ȆK^cb$7?㻗t7^YSΡ!ԟm]Q0v5^mF_tTx}rjCj H0-& `THT\%=@T)I]Ήus=9Q,oQ;8:omLxK| R+JҘ9xwWΗ|%VtEW2 6qb "M$*e'c2bVkl"'j[QNƟWk+kΕlk,mHǩKl^6\F&co|&=&]GXB'à@6|A8cBT뢫eMkvlY^k}&ͨe͂D.ª_:^Yvn76#j貤iBJ nCRd-4{b-AagPpPM/ǎ ~;nɟx, W<`YC@Cܦ\E[txЛvTjB%-3Wf1V=ق*jxocS5%/1V[8֘w6~SѨY]Ù\buǶW5YCw O'!~Npͺ4sN=sąuWI+䌶$Fvvj#lC;O4N&{bf#%Hm #͏XՍ>!& 7M{7m?2-f?ߩZK׻.n| ebWд Ւ-髍nOGk6Zw8O4KF4Gn&2xe_"MoKs슧mtpg<ޏ4y|A3q{bV w~{1W)1U햿F)M=jq;F7Y'WRaI=8ȶ\תYUK^mn%q6,=SS %Q8o[5{ݍЭ}6 ;OIfcgpa2}CM!.p#Ff i#@OT]v> &%<7ꄼ!42jŽo᭾'}=(U[%2P# H:*hct˰fB0f g8ۻih-֡is^q0"9'sC"9 x#F$"@gFoop0m(1̟{*/RX/>Sw2O{5M<)lj #ۆǥߐy&+X3;|JzlVka`XI98 ]d~/߿W i+?67)#Eǽf6XB\;*<q 0=&Ony鷝$u]QO4tǿOsvdX7&-Ԁ`ԄZ>4$I+j7_dY5mgT ϐ)HVgɐS0N`}3boJU)I줔+U~3 w}*ShzWEv"A{uG8c*Wxg,ڽ~ꛩ&Vvꝕ- e:gi)i-{kx#¦rwP֍'|35JG靕N)\V+i慽5oT!FSgEՍr>i/ 4[k[]ƜK7SVW4'Q8 E G"_F3!c nْ=7] OHB0iXBo>\^d VS$C]8#oO3Ccx,=Q|*bƵEH~}+7l7] 7O g蔦%^j5׌.- b^nD$7:K.]uf'2;'=k->xNߴc7k0s~`tO[VAކ gkEݛfpg{E iBĺv5q'6d"pGl3i]Nen:|5}5Ь_'%IrӋݻ*NtR_ BsFd=ˋ`1Ϭt-[~[. K)DPpjWGU*NIEYk1OF] UyOV)Ot߭ѝ!Á:aC9i~x**Q`H,ݒNH33LM=1]gOdGHJoM<6f"  ? U0l}_(x#دk~T7HzѰ#շ[ 3W.\UǴ ,dV5BZld5:7k0]9/;/4j|ejFT50΃sƋ5U'0sa8o5e}gߍFaˢU}R?f Ҝ+;giS)Ffg4n=oyꀑoBHtBFՉZe0l|~ΎX`'!zX\"&1n,:փ,/l'jn@{&c*u2iѴg*>aN>p0_q'"7=K{3⻦c*3ldԮ9> ԏфn.=h¨]?>G -,J8  x,LaAjH2onYf]0VCR(*kf`MW%"g}eկzj#}=Or[qSW2k/?K/M]'6?S]ˈܟ}hVt~oJ g&"DE *>e @1n}ё6B LHRb ҙaJ߈"4ELШG.I<B+o6"cQQÊfF) x!U2p!yz)::Ru$W~)yi"k ӧ6}>ҰA'^G}a-ޅIwY+0&Ε^ K ^a#PK'A8qu)ȕ|fbLxz2$UYZ}@zbcKZy.w8ts.-Q?lR>l&JF! #~}5ܠTH/5myF}brѧٯR2iB j@Xѩ ju#m/siŷ+Mk_^9ZymiT!k>C<krN$SGb/Rkco=~զՐJiJby* rٿ{ՎmE38ćIL˝d 5,6٬{ T!7xQ2DPcM1Ǫ#+K&Mʈq:Eolߌ ٙ02BoeUk1/nI.DC(ʓ"DBTHp' gUja4j"_uKX\\h<IDө/`{R$ՈGt %y] \+,vHZqgkP9G ,we)텭IMz\+Yh-+a DK;Drn)8y.q0߱[vzX+ OzbBfE\bGۥ# 7be!h~~`4_ S ?9a-Hi?O{ąoԘ Ӭ՟W}YO:I5٦#.Rk7ۥȻ#?Lߪ8]sӮ]? ˓#(3tC4hC[yw^F+{=k;LàB ?fl"?z67]ꏴ# a0`Ҥ l#M$m67NMd˞ޢ2̌}Gjv%9dUEOq*G4_V=692fƾ#oؐ34,ZS8GcRWCдnMd7)\jq }! m0gL,&c4׋"H@4˚iGwٳT,ksOdPq`\XbOqCɩTu'7=vFY\7zw_k1D޸.k{,wˍpm^3EK"}`eƫǽ97wj?{lW~> l#J`% knmb~Z+fJT0ˁpYB&uב֛9э1iY3l2,-w`sw=(O>Ξ7IxUbܿ@>\T*\SrE^̬*?XU2x5ʲP gYm("*H<Ȁ@Y |U=MtMh2(V~֜O40Nn?]¡AlGH!&8))-~עX Ƹ)*ZyiHI|ք:NE+_n ? .n0fU8;O8 DW <lG<~auzcjgF)nK1%פmi#Qŵ<+FN3Q)8>&*qJi>~3Uc5!$$VH)0Z?'3kCnҁf 0BXh[!+% GvHN:̝?Ie_ĵobTռx1T1NE83FQTЃ&uV7<5'vcI1TQpHaҧw n#j-DP@dbtEjDΑI 'uyq[_[ skN2Iy{ p`#7Ay,w-뫪5do\?K0 DlPD 5 .[ruea3rNr3RzQ(Sx#|8#RuZ+{ ĵBWf^2zߊD?e\h]nݵBz^yu_KX0i,uys_=+# _kB,%rfaHV7½r;ۯ].L^Y:VYӖ-}Zm@Gsݍsڗ/]-K‹2qEŧSQ˭4Yu!W`~\>ϾunKW Kch\Ϟt~=u\x3rH0 poc9-̚vyQe)' Z!fi,*=7zXvK~JUi*ZPG㔦()z̗>*cat;{%׬wh /k^m[~<`:I/ E Wm޷*;&gl'7Oxbݏc3?KZP8qfǍ-'(Po!6s( =jv~*@ZK˰tBxi1ø~^ %S[n?h$4jwa|0!!hJkEjT/u*PᎢpӳ_|~5>E >k븑V)4i5 ;=Wbkl}.chAZ?@~b-̿b5pVK0^k+w)~4 mu57cEl c&V6c{8 2Ss()2$$ĞR_WI'K'fvlid! ɑuyJ2z1lTj*SI!uI*j`ssDXpntMEb!cՑprH-8xb\{jZM 3$2Z0/MHL)^_}!.PS^H;ZB0 C4v/ZdwyWK[& \҇hn_7+8on9XZ `[yu+zg_>Qqi(Ry~s>yQiQFR #LYE!dw+_R +k/1UaxbMLVBL^e%J1rŖd,, 160:pC,ΠS|t^(^ZV8ǝ]Y^8\I ֨ZX>r֦KSǦ*\^7].XYdn OZb͍jQoidkNٗ"OJ1l c&7T&H syzd%:q%#Svh9å^ܸ_= Hdx)pUխG^b9moy2[-86@\S~Rio1dsڮZ|uLlAA[}ꚳ:4\½{gCӶڈ;6\+Q@W5!y)XtΖw.7Tp͔MFB:j 'k)%&|_S p4 9= * Vu.$"eP$w;/l'M"a4$)](ߛee['.rFgklWU++_ e> b2iVϷ9lDV<'T¦6nH?y.K_tjF T ֈXMd_OHi$Ŗ?c׻&nqHiȒ5savo65v?+Q3X '6Wa"NdٔHrGM=*lt5@ g* K4Q"_ROZ Fp@(YbM%Z$`Ðyl`QQ"odN˗ &M.BRgaɊA~CLdDrf#q$dU!DMWx^+Mݼ;ʏMTSK`E@R4A s˟oVȌqJwSyeyMUe<%c+R D,!pK#9#-)kxϘ6N6V7fo4 F_`U d)_Xۂ%uc GPC` >B; ֎k/JdHƪ^b:Z-# H.L{ "Dx=3*WHzto N]S]AUiƭ_pfo{}jz"Џ A*{\L-i@5pK2M>P#zѻ&kdpjq $z?dִzy8g`_sw# mI-z޻~o&\J#G``i&lb^C  JCќ $O8Juٗu uy3 +W9)c6r<Ϭ=7[Q,yRNeHT|a;L Yg;2=i(T C}h A *Y+0*&AutoA0",ejҳm4;6xohKQ)qŀKVXW=79kçu7 r7x7~ATQplbR޿K4%o`Uou7;(l UδZߣPdad?8V[͟|xgQ<}VX< jݫ:h=<ظGi,b0fvgle1к[_嫙߶ʭ.{ǹWd>߂!Dc$x?yJ!oYsXj?:Gx^z_K֕'QڜUč/^},7XJY'!ʄ목-W y:tqR8WúQ%y*(aTD= C1ܺJރ<ܝɊY١Omm9-6f.vZtiZ.3> F6`TPυS_)L!:[ƋЧgM2u^-kl g:F #%{N?W޾|֣1m]-kk]句7_ũSW7!z2g1Ihv\hYƝMy5Y l^u*ۮu?fZtddji]/[/R#^ 5}U8bU5&$X!3©zEd5r4j̕(U#QӠ\"S:LKt52.4knHX]XQuV(,,zXhn?HXЊN]c_(1aF#hJ J5$W⌌s i&!kFgug1: ez7kZ]ҕPW؈;VH@xV߉bfMC7qХ }ҫeQJcwIx_w$٧HiF5RlP!`q.1fKT)crciSrM~`(BK_'PT. QS5JO0dMBBY1{XT¨[2#_|?8r:$ Ϋ +"g H #5( (!T})|Qqhy~ڈp.\z ~=UI#"`%44? UY \(xY 6da2fH+} ޚ\%; ˆ@q%ZY)f7̪D˗,|5O՜]Z'j;MYUIԤZ¼mev9Rkḧ́c2!HV[SDM*06ᇏ$FS> UlS7Ax?.k)JMь0 @D_&6' Sa寧1~/( ߍ}g4^6RXuۙ'jӸ!XޭPiu0]I7 yN9n#K5ߢ|e2yo35v`b) ܣuE2,,+0q=94'.Og!&4 r{.` 8 T  ',d^_.MdqQiYFʻ<+G,ZR AN;חӓ{L90*l/V@AwNI .<'ZES.. > P;9 $J5+ Jm#]Hp.b1cC6tV̪X^ D!I /l6LObn4X"6=q4"$c,l VCoZ(kg1!C7 QZKTKl vl ]++Sz62 ZHeZ$LPeS[-0㍇G;9FW Ǣdz$l%2FU ,9+wnQ/dmf`⾌-lg媺cC1L˚??ADLf}ȏQ3[E[ "BbBn0+:b3$b0#VSؤ ^LRX3/t[/ @88rn:{%O~LO  ;[xus98+'_6?&~ȪzEOBИfo#Z7R [$B#a$GR"uW%wFI5-!PP+`o0ΪVW Wm(>IJGՋo p5ڀ r NI[)#6dE}SZ79 ^,` LMDTr;i+vԡ9U(i-҃V'dfo:C\c:JQ7j:a*378TST!j򥩹:dA.gY D@;;! oS+A'ֻxA^X1*ǃ@ H{ /6,685ϒS K䓴JarIB(C@Ha1{xkYqGdeEU, x'iI1OΔ.Q&3n-7H櫂=U.d]V?hJ"&&ɒ}S_~[sf[55P h̄lpp ~.Q5S`"VAP@ζG%JfhfDwP343 Hކw䢢}L۾T>oU#+~[D?YC Nlo6o '7h-ϋGtșݍn|z48-҉,аoTHϯ_`2-&K;5L8ƙ9֪=c3!gϘ 6qBرU概D✒00"!5ՋBvvu"0>^( 8D Xh@`.` ŀaX%Pk@uz|@b9 a! O4C@R0(/izon*N>4Т(Gݗ '^c @~\C.JQdb|i*AU0mE f Zw\ ;n~x:| )Dy`/ӀNF L^ 5T`dĀ614bXf>+JHAxRSc]d|9@'+pEL)\/@@BFZرr1Iʚ.ƀ">I-{2\2͆q#P5S|*i]nv!6ܓ YȰiV\#̧=Tw)`Riԭ EM;OXN ;e#*=5vC&32Lz=[̚!Θ+d0iSXZXIdx?aw ɲmUe#+QW\ QMzk,1;B 3OZcI;p)bX䩋eF-G ͓0F ݔmMU)jX%WjkJjWoN"R}{*1.oh!"iqETr/ZO<=dw/h#`ےR18GfIf6lFxYotaqL+ZX@eQAGI0s5 >Jh 6W cflRyL2YAyB csz^xr7p5zD- #]E!T?DXduF!O6%" p2P d 0xR ޚ摹޲oZq >8#UYr4" uuVsFVd&)T8e~5HNlɇ{v>Eȕ8ڲOr^^Cևk4 ~ϕVNrH [ѯy v'W Y:8T n뗆jj%1zB"x"*XQ6VJuqK>bz֋5C}|/CI tzA{0Xͨ@$VJW6coBW(gڜ+KU{Ɂ|F! \]$QUn)cnHşcvsBw;jX#ݫ0{.;yd6 Mc "H ة&z2׋d}u8I5-}% `wB\Ye,YI-Qd Cl,OWM3j Ѵl;E94 HL`Q;p@y4ScWv.MASn! ;@ hG`eꁜ Q_#2 uÒ[r\Z}lcĀ}FÏiKE7҂c9.99cT쒓DrE@D$< T[PLFAcW >z 8 2@ S?l]!d? o b-P" *Ѹ KM&iA h%G1X{ժ*g%B "MdE󒏱[vj#mm!,W-p*(b,{K@ ;J,_KT~('qt^ #g4wh!3SLdplmP0 5P=O\V} dŐktN h_/Y;YdPj)}x?a_k].Ď.hK=#hRIX}&.4ɨy\"0EG3bԗD!DPۢ-y%{OQz\"aքjcXUيE~QLC]IaZD6\D1 b x=(b!ziw.Ӎdr7h8<0&E@ѵ jh h;$A=d WWf PJ R0Ka;\[l#NaNW!bK TKc& ثtkvME<8 @N*; \ZutEeJ,^\ D؁jPE9k@>8 B0Mi[( `z#5) W}2.Ն3Fp-(ilMfM.LN2UY20Q3Q}ҧz[O- K>Pn"x^X7-@;03%1+ir.kQQKmj lt@kj-T5)TvDrѦz0ʁ)Hޕ k;@ 42_mdJl@N}u3ݽ˫~wzޕ֘2 @{&W7٣R^^5Ɨ yS!Lh`8(Х#8TW2XܓYΪ9ϯLm!V3㕚Q"IJ"fy2wIHbľPJzAf)KÕ) }֙>v7j>M $j*K2l!fxSAegOP!L/T@^ H1T~~ĤL_L$,#RRjJw(ZfrR ȎqՌ4$Ԍ<D̚22RFidz_pF^]?JM:XjdƍAT> ̼ϴe|k@)( ܪ>g#B0庨 J֦6ilRX2- bH"K,K&R6K`ڸHNHWJk.V1!<0?8xZҽ=Ȭ r~qKd@^6e$Ww fc}"Vdrw !8l, D {6~< %hLۅ{CYE*-0X«.F'N: &Q?N9ٚHO WmR"hJSf7'. /p/.3zOTEzaP26jJ<:.HKc3ң7?T^Q^dZ׍P( =`E F榡3G5y{؋1 zPCq%ܻ~Rg%'č7 ]=qMFfG9ܲf`HzF"&ҼN0K1h+RL!u.p]RH0reWEr䍚`4sZRU(ZB0 `YI^/h: 3d)KM/&wcfRi3,!ԅG రb amC $%$\+hŦK-ٹ>6zc¯oU~1Ҽ}詽Je9dk'ĥQ-9 75z~ysie2]g#TQ)<?\{{>WPi xߖ_8(zolJ4`C00 w, n|@F>\ɭƢd-6@XR =|C.$8T`۟#=(P8q #雮$4%AE綱1yy:p"hl> `I;t}*qnF5vjFItIhO9) ,d)ȰK:.\{<:s Ҁ'> ˆ<H.l=|gz=@ 0OW|ռW&9@7W-@ xAphx9wȤA*? Tuy$$'+Q^ hiF*"[w+(؂@B\E$ʅ-U:I1\d-wh \ .POl&"Q Myܑ,pN# "gD\fĦKsL&H.TT!u.4"뒕;2؇xRɆWJD StR*WXjfx%>+U2V[R"ZP4hnYbq#9ZgVvr P&#c^vd`CWcmyVˡ]OԚ !@pi* 5dHO=(ZG䭏l<oj+q,1=lvdZE96R EDQ*@%X?P^G0ӬTHSaK9l?NpKN|G2FIv2MpZ~ʶ1_Cu" ftTd d%}qۡ:sj<󇖾> ^ԖAY {(.z^?>q'ꝻToISv_ ~{/O6YQ0z@qV|E?-9.2lW}e5+WAȄf⣷!lT2laW8gO(=[6Z7 Af:1o.Fpb&Miv; Y-ʞL.7Z/$iJh+H7=*:_HQFyܓ{{&MqOڈ!HH>nd]jՋl@?+0 apKa ;t@,6|@X%$4逓T RYc0yfR$e7*x-±k!*>Aɧ#' sS#cB,T`k*_y-c? 7f(3뒏D&1GWa\6$aW*Oy-C@AĚ|a޸HdP~b GX _-* a"lbHfǐ`9aI-eXk{␖#l℀N*? \$Ĥi4]lℐwG/- $֖:)yXj$ƍǍT>$N" GsbJ!@},lpK>ZfB%Ң:( U,zXT_{^or+>x!4XtsĪl&sU)֡B?~NrlkXfx93(Q5EH\_ŕ !.6hAiJ2r9G3s7+QKUwKʰKnPm݁>Χ!ABL` IBH0KUһ ? 2*xO )M>gǻŒIThe +6)9UKƟXř=[N}Hlx"N,-/_Z[!o0Jx x,~v::E<vi:; ϾF9{o˰ ?8 ͦt4̈k#B3ube=W<ݥ]tNM/ ew+c=1zbM':<wkvHTttluAS@ +k7/Jz5'@AB} W=f=AĿcd|m18 +tQ>c~»v;T詌˓Vұk-Z¹۲(zvZ>pW Q'x9uTT0:҅׳򧝎wc7)W7y^{|~Kd4%^AV(" DL |8qV kB ʫ#zn":(hj̖?YM&-L[h6UwymBH-O_7HSXÌ:woꞸd}&ǑlY8 1}]bˁjג`!x7{9(UEQ Q>F7 O{;z o;7)F oo_Ǖ^՛~rh,JMv </S*kQ[ T( )"m .f|3-[A5 < x; H T+C+bJc\m_]5΋l<9Vupzf[:Qcmt_u}n&ʔ_uTڟ:^GnL{@lV5GgVGM?zȕM\Ҁq[j1׻o{Ґ3Kbdm3?sI+:-_z[VƼv2>WO,pOѹtjRVmV} 6k7NO6xQ=PʳTSs?/ ~4`K]9F݀37OqpgnKti̗Ih6m3VݸK9=.6Nsh.WaQR6Qe&bҊqnJ@kA#pR4f!D"?F_s*L49[p5Q6=bv*ՐVl!arL=P, ˆ(-6q z0+ x$A~+m!b@8Vz):v2W2xO NWʁ`(]rleh WE,@b"QUdDMث/Lq携a% ?;vrCD^tdHcee2|e%ʂ 8{xfUL4~I?Ny{#ZAʽњ+f,@WO;xfY1|e7>{o>_vR L=qx{GBӒi7``6$knkoӱgdI|G` фXA= _O OOȣ =gvL?<$@JDO W-5}z0= U =&m^/cl. vں4fY~ο]kvn|Ŗ[ARdd:]G)xN>{k©ms-O~3ۉ4!=g]$?Nda~Wn?q9wn#oz ? cPoo>Z^aXIªްX:{jDLs.;˷ÍRr{z`Nln/>:;s2c(bWx&=}}n4AQMo>1[6]#G}sGg%/nbBWyУ䠒i^NzRO!:zZG'Na4.E b ZH'Rq_DS*6J 2 7 !@0r+1m5l2sԗICQA,;{TÎE>iL c ͉ .2@MT0Ә'^GS-\\ LI\x6niE $`5v:?2mh2ICvYy:p 8Bab_ 8 _RP-ѻHQe2NDR!d[S*8ΰ<Y4plkl`׷5΋d{N{M>epT(<$NIp"v)?gO ɂew@ <4ȶ~D/quWn&={>pӔa2odbb% (Kq2ZH ((xX'xam]mS^0{MO3O#*aVqՔ^ ~J #Bd` bpTOXlN ?!rvd5b!uD 2EĮո[yg苟$K{Ǜ/ @?wK˲p~ g=Jz#kj5_\!W6x{ar̓O@8Oz'w˥{C%mXƦ-dF_Ɏ0\pz&ՉdG9اpۄKѨc~mw8/9dEmab%S<͵g%sj|υ :!lE!b?pgJ-ko,l{96BA6;F4y,g|1zs XҜ2xXzU'z`eL~ vei֧ 5h\z t y#C#eˈ;˙d#?ݵMnW]F^wtGC*کJ)p^kުMVh#ojK7>0[c*}3 l^pb 81XmH8Wc:@@[aq݌|G}el8pKCDU,eF10L7 Ӓ1qETBt6piO0$h`f2 ہgGB^o~R 3:hGtFe(4{HB~ \;wء-ޱ|k׋El4~{zmJqBO`*P%)C)T)aRJ'z9t/V[ S[d r:Х 2g\^^[zFN戠؈ZgeS1pu}CM`^/U3R ;:Qd`T^o}nD§ H {M^f>W_:uo|Pыc?3&Ծ&rZ86{K r@(|ZY7˾%sBK$a } tK/č 70'_dwOWHG_kp Sԇ\6&BiN M n5Ӓ X ƀ[=k9QٛqytpAD V5P BJ$' 6 !HPx)xK,QG~R.m Py$g.?dHӅG&tUm#@q9h׻??Cze"8w D_>U[ǷLw=WFB':<_(۸0ibgIpT|W3[e DL -еӨc' x#)$Pa-7pxsi*.d#ū}]4PN@f?*Lj_rQq#4hT6tACEk%y++C%N=QKKBCe|uD@꟞Fg6C '~eMɁi<a`c$aDw Êʉ M&!(?2^*ۄ8{x\L1?_T c#I&V> ]=(7p8l(`He̢itRJO!t: &WܜmWƚpMИ(L1|نȹɅE(E?>FA%N͎,<(wOh)bczEfДXٙN;̞cF&u฿,q/؀''gV Z:r_&ɶʘs5[#(.!y&01#QvѢ}~gNtԷ!C6'st p"!7-A@ FD~@Ssxep$&X1擮S33)$q㔞Y)6>)~`C]>p>s-WM䗳 !X~OəKrch/Wx"BVTx_nvⱆg#A`N7*(;0)|;{= #08Wf#58ˣ8VO/^l i9~u,A*H7Ύo$n$_.rkN]W_<{fӧLuvaegGx_Ա 9ΌIvo9B(嶪qI\Q5 fr]3 =EtCWH/Mx{u6i?\U}.L-Vπm[`b0ec#Ao ~MB@WԺNR`EF vxuӺj:fC|X+s[Q=L :'FRYki| w!|qE I:o;uv]V0PE6Hn!;K @]} Q !rvP0zN " 8H(eAEm{E&+7BV>ִ՟q &dHSuȔ RE| La},̪;2F @(U['fCW t ,(.W?~7sfE,+T5EL;O$qfN+9NuH^^%bփZKYW""xLjz;ô>lj~6g<4T3lց @K] LLnTIyG[lvlwr,D)ӭ'oGwoKG\-ԼM'u[7^0QP#2* % hԾ5A?ְzSx^b l=[dJW K#;837vN+oetB5Ӓz+SIT QͫyVw5tCP)tLjr'Tm慓/vJQL.;z%i4`$(& @"&LWlG 'a|Iq4 r2}7hj6?iSX0ǖWR~b&ܻ?H㉫J.H ۙY;?sx/mwUI`4A$ 4$-G 0;p)`*񧻄HAL/gR` 9&yJI!5 XGyGiY6M mL}e6(L$?`P5RQ"J[-!@E8/j@@$ (jӀ'yhoH[6pGc!&NNmۮ/'A.;n#XR*a˞$p7A{>֪lNƊ6ws0lӾ⭝]0!޷-ٍ z݋ m߉q+/:3ՔZ=,-g;|VvJCAo}CFɌssЋxC}Sm<_8h(@c&=;7f< I9( y\}pޝz;h0d=ȼ1pAph蹢([}\C&?4(CT1~`jX,YQHk &%|JJQu.b?!ٚs12zE1XF<ڄ@И9Fp*p)ǷXݾ)u_T&7`eLh RM-?+O%ރy^(qO79m=)'RF" { AKw/a%_vR&|Wz4zz$PfOe&/|=RYS7 #R|C EIάhh#C#OmiO]>ZY{p/v礢L=i>h$,мBeYܝ:gd8/K_ɎEQ7еr7dϢ-!= ֮Ɲ iNIJE'|:b ֈ$2<#NR}c.@/A>0mKWz|CCí(EC#Bf+%Xx-mt0ALTfi " 77=wQh+~?D .-S1p7, \!E6v^gHikm65oȄɒ4'ȗV?-1hL# eJGIkV$ 1`őɃQ8'-540\xY`l2cNZjJ7{/ՑNcXP2zZxtYHOvNe'&ZjPc4g. Fj4vdd)]fRM;bRa9wnQXKu7j\r`\UBL}RY~dA!9;)5T 5a`㊘Y|dѵyf ցj/)6 ,7羆?S\/`y ϩY믇Sâ$i z?y/Gԟ'$NbXDFր!|I<󆞲#Dŕ9'@N.rRg_FԡK* ӓ΋]AN ,lJh|xnP>Dž~ )Ns")X(#<[uM-π_A8|i[@+d t>D7_fXcQ^Z-W$ Y) zo^ 2$^0tӉGTV#憾^uJDr`n|pU.C£qGdQ9'`.N{vL̟Y_+^nO 0J}(WTtn?%AxXLwTN&lVvS!=90`FGV N[-W(q XM doʿ/79?6pͿ|/jxNV Hrq7*HU=q 0)c‘JrA/Hʍiz芪t>ѫK f&>5|5?=G lB0U\IP/k9 d5C>O$XЃ~ӂUY1ώQ"ۭ[.9Ys;'xbנggKi֗>sɋ}駩Z,|akblL3MPl6i/b݇˖0EJ3=ź=4o°'O%IP!x34q:F@!kaի(L"$riRɖ3╻ MkNA)- ѧSeMC3 ߵ?vwX0p;]ZjӉD3&Qhn[~DFjY=O4CJ(h(Z~`&Ez sf@ Eg%jG@Ɗh3ŅQ5u2ie'0ҹ޲CChBI0B#Y!BȈK?6W˕޽BW dEICxvI$!r~+=[378 p/T/cJR(P5- `"'_Fc3+aBj}V4+_)+6Ug6pvXjhߝCײMbL=Qa7?N DN!Иn-WsWOgز`219ͷN<Ų':dƉg6"]c),y1i9w}ɏ=T~ꢁ$yU tbޜ~=W,5OpFWgGD͜/Uhaa<=;|R162Wqj8QѭҴJmLf[ EԭtrOa('vcK>6$l-$؃h}"0voJ ĤG Q$[v8"hJLi&.^xE_U7<8Ɓ #LN& ˊAEV (;eb! :0@>"^ÐX1ahW4@`!T w u)6SLJΚP&`=o=,2X ,o9h!dNq&3 #P}]/Dngh,&^B5n>4': {SowpenM6}ʓrFZt^bo=[zLjMt[+޷^}t DOm0j{يy 0̎3|+ƿ~!J\1]lkSskNݎY)n2d@Q:XhR AtP\Q P(VRb.|ߚ8b)8/[|׭v9-BBvy5$>" '6L$\U(["cXŐBPɏ$j &nЀn=& .I6&EE*è/?VJGGn?yۄSfT_uqSq18>=V/?eB']ww5{UӓTmmZ\iE`A b Se[]??ṕOVu^l_|zFrJ9E b&)~񡧪܄1[IЪɗ'y[Et[g#J6kJ秪&\UJ q|&.}0@<Ï=> 0W+/bJD%É1MZ>9soÚ1}9E7Ň9A=3^?5㔹^̫wzs Wbꘗx[&GYt.K[ IpH`Sp* E5_,wSfQ!ƞA2pi;`7.&z>2Y#ߧ0#7F[M'uT|/m'XΪ&e7QX0d@.8۪UdDuwfo # ]'Obqq}-jMmGNOgo+Ϣ/B{$ 2Qmrb6տ ?q+ }OIEbZols)U@$'KDt}?7mUxvgU?+SzѰS8n3G,AWfRܓBu!:)i{mA67^gE[pCvɻOݠwk8%z@ߡa&1x7Gw䬅_$yvo=[W9JS+#IkMt[ +{o|WƲv٠d`[]i gfxʞyAlq΁#\=R4?}{GQK'Dkk򬒌RiqϰҒfVIlh2l-R_#z 6ZJwMRl鑪flx}]C6%:X\գ;օ.[ BI;ZG7L޿81Q( 챰uS7]ggQk.x3c_*?xiqm9~|,!śXڧu> êUw?9r?>M~3سpߛM|m6D㉉㉀G9$OvC Pؾ=^ۗ:q,/._R͢H4 ~{9ƥLsH3D >f (PT>":(A3$40i$U>;LcO0 meFdKt^Y3Ռ8kVi&9]~3N[|~Ef5XvXa> e%Ly( rvQJ+ i;[M\l[_m}ʏO&,6GX7(|+4 %_-2NŎjj J'yu+nl3EC[qjowaїKXy|0HC[\؍*{OzAy]E[2/-*, Oniw$" j`;#0cG@I}ʅu7#y&'$;2T߃J0 O>H "&y F/>daW|, (!~G ǯœxƭOWdWz Ebwld[IK}!6Afܹu]w=bƣ>S/_Oɬb4c,FՔk[nƨqO?ܴ4 5=*~e‘g۾e̿]7_iN|╽J%"߹̦;aҹNW_(MJg !'APHyy!d~R=ޓ3Lv)+_0hę͚ %1$+S@'{q7&hHy؏5k')I䴼tcHF}q̧E8\{#nz8($|Az" $6SVEP/EDh#Ľi~F>;gɱT y>2Aذ:_#eG?){OJoBLmK}i ,kys4/R(vi U 9&<ɺǖEg HSnm.wXt3{'o4` e/E b [Up9g-qrdzR#d N+>sy@ռZ78s˲P߰~&{ lsj_/~[[뭚[w'Iqk&H 1n3 AqF'2j]r2DH EBYp<. Fp Е͖뛕f>H?*Sc8z1fFeS hg E 4WvYi#7O 7ھZn>:v'jWO?wE|\LsVk/ǮѲB^ެs6N+m*ҕ54" Up:%I~@ngYh8]Vڳ;ro*!nr;XhBCkՀ$yyugHPYa˚S֝w1VF ʒhsv?9ҿ5>]էS:m],Lf)2\+[1xr*#8AVibMўԝԇn7?urK#N6sGQmny#WWff޵elWƉ-VHK.8X G θִ3w6o530ٟ1`1aKTM8|@2SpM""X́ $U{o+, @Iв+V!G#F$ wڅ_BsCP \j+Ρ/~c.-4i$̓ʋ7,{߄~]+_]וz8?_OcCbtt+M^P]6h93t*;cWY qā{ss6z>w_>O)(NW|?\yr'|x ~Oe_fmR!U;>z1)q; LT]&fRY 9݆͡NQ| ExGp٩?ng5ӱծݱӳ),}Q쿧d@@(O:gIbǴ]FP \0=b{^VHi:.Ց5ubo"j%\A0'x(>; (IAXj5 3x1阹Y%)MDv.vK!DD rEd;HFtL)fZ ~$)ET`^a+u'<Ų  J@E *TXw}N־22'Zy36fP&SX]2wRYumYߚ^-YUΝ߱/{7/j9U 1 T0)R8UMt&>swt\ƶ ]ؑ:%Ct2ިqɧ.}uۺt99qE̔UCw%mL/6=2lf] #77m9"&k-ͺ(iٵeWoe%3 t~ T7 pxBy X/%Tgas1T%(JҀB3m9 {e&>.6\ILi̤ǥV{JPo89漢-x2H-u`n}|ۿכxjmn!L|(3in/5ra)2ڳTE„EEu!Pck%-&Dʴu½ '>O篼ܓ g 3(O=w=V.5U2 ?rXh[.*d^@R(<=D]iaCsQX=L׶K.leV^ӈ.Z` u0:1#$/HKlґRtܑY{IK(B v|Xe?[jDDoRDvj)6]4Ld1kYjߩJi dlE, <J |v/=14QآT'y+J{)qb+XZ 'SҬf4J+ʺ?W󆨲b)O9r۾z1}iDj6H !K=GH/PbLY 1I::1tBKI"*r-` ( +!sa]+ }۽W<09\Dϯ3E7^+;ƑLDKS[^<00@A0(˗`a%[~~kdP?A$JQq c޷iN}FNN\&7$[WȢJ8~,@A*xa˅|UfxY,@V.'2| Q+ "4 XЖF)H]YRcbE_=\Xm%שSM*%(R_N*r{h:_5~cPbo~n$䮹׳7OguT'];N'~iuGC{og>a؃i\0'@RJo*FnA7e樚4=gUCL6#{2!+;~uϯ7ow{vç8ߚ/6 1Μm5zS?5!xqosOUp=ڳ 92ʩ+g$3H^ LKxmh!a, 蓭ƬHKqL2$!*cdXdXizMb%*1Vʄ1֑fjDž`T= J $ ^mO2}sOrew51\IFd ^P4Sݾ5c?Ta)Щv["A)$Δdk6}᜼嘢^Acڞ#!+OyCIyqWȫ+d5T.IμW^ -[|/J2iӿ+!r^z ʡS##pF[D@fx/v{ ,E, Hbrcg_r״(ޛ1g镬ԼP2 RMENL&.e~?QhA/8MTdw[X@ZOj3 00te<)?R%kP6 o^u qei& |x?z_݁ \@V6%aG9/ɥt3ظNƠ7 s* :Pn8rkB]~}Z6k}_Yus3,qVź=J=zN/C!eZz?Vy,i"`kաBt(Rm]4Iq;olCT {'牋ߨl{u(͊*GؠG*ZTK =>hݞqUԉ?wlUg\"ãMevJ 0,YDs-ɟ?̫/ͤՋvlUiA(Z2i j;"[2r2߇M#8nSuH'sjB`%B`|m6ɬIJ\-|qM6b{e Hi~¸mE,k%d/ڽ]P\6z͛rNF>~JIs% +!:^f}Y4sősKMR6$Ѥb|5jai L6%^WQԬINAY]ۄ/!JvيS8'{~HIҭ:+C$)i0}EITs8qlNaT\ (E)uvkP #^x㥲ì˦*=c7_OOP:m郎x3NyR+s}) `$鳦$2!Ȓh0pJ@ow$;0D$Zx 1 yɖD$]Փ! ƞ:ܪ = a jYO";yژ@jaka.g-'x *.( 0y@.siql0)9| Ċ@iy}Cv}lOK1QԡߚU)koeHȶElS[OLQ.N-07TES~h/siM`L! $'dy`F%oz?̗Mh&]ڨ>;W~ Fo'فKeV[{_Vp9}9z0|$*0IBzOb]fД>#o z0;5\{}˔~x;#UO#+#LFԿ48 &Le~DD& Ћ W8]bʐty:0{.$1&` %#AP  f #S%cԀr V<'x ؗ]-Qpz䇄z &DNdʢ{* ")1B %X~y ;SBblj_,ݤGICw=9GFjY|u` :Pa7l;n3Xʉt/nERցߓy`QW.~7? FZNl(ʖBfgO*zr->@"^^{YYudSiv7^`:CIu!_(9.&Ƙ]YPlJm_[:c[E Hd.RU^T:~Z0c-`D./|t@,(@n_sz{ϋ@ZDb ''6Hx #m{:2?ė>,E~1u&Wk#O~*m %/mkቡ݈JkNƠtxͺ՟xp.3>Ґ)M=Є?Q32עŸqV~+,co =)AQeGkPup8UMɤ-KL1yH=wT:yKD*qu~ȱ_8EslpWYҾܽ+7^E .l>q]ޫ+Ft{;  2t3>ks [l2E$;Uw E׎P]I.(YpyxlOթ-ٵngNiJ?01NIhP p X1/ GU#._2(V9ו5C[.}աs볚syS9Ga6[v.-j%aִ~NUnz&*w[~d*ț-gg5˛ab/Py~؜IŎk(/h^'4߈ ѱ éyi&;&nMAWPn]';. ̼t1=b֤bDŽ:4Hűb+S(:~?XM |YSiGoHpwKq7 B~\ޥ2 }=͘>ZYz^9f%+}YV8^o?yf=_|kgk5se0P%8b] ͔2Be:1eN0LfZj%I¸t2"+`c#tC_y?| 2::i \v0 ]0$PZXY^M-M /P#~S٥ ftgBN MͦdP%ǣen͈5+abRqAM2Gg+Wʠ1Gydk7?F<4x:ąIy [Y`/[Ď\Qg L41G@j'Y YϢ+#kXh0k]W]^-lLdB*+:xO<<@h:|ܯM n<|ہ=HLc%>m8ұs=dNʛ X0Ca%3LΏۇ=79}dG`侂kY!'(_.(I $`"n") *sb*Ha6~a;|<,2+-N+4BY'oryŚ ǫ H&bD$ho u5П <6/;/-+ۮ@:+ VNa.KaϾڙ@ gO){u]x'yO>v{? >_-xv]Xk[@W]@*B;IFJYR׿Vg2w%Z܈| S@01$Hwd.?%-XәMz(K_Y*fժ!H$7{%(¦p'Y;\uU_5!I-Y:!06aa5cH _C<" ̀< o@(aϳqH( pCW`" 9[Mzk7}E?XXK"943ed} JhщHKm7-Pa>mQH \ k#[itm5ROI;kCb;B%4J ;I~Zky?ΥC?j.oqn>4*ԃk2IE{=piP~Yxiƛ4"1.فTy?2uKtF ݷ̧7)D"BKZcdBLmY>7z)]9R}_ۣnXPKm5s"& 2B ] ݊)7c5r*CHl(H)3|(`$DhNM0#!7U53Wj++3d&+nJ /l} ^41\b}Z>kBOBB 8ޓt&U+ɓev+Z^QNC0}sMC}yndn5JrH,JUb9?e"5}oj?oi/J̕dȈF5toݴڛs9~3Aj]>W+ʚEo $X*`z`hƘ5=-cH0LYKM8~ؠP4j|4iyS~oxμLaHӘ}ϷD~LϽf^?Rg,tPm Ƀg7 NoG0HSP@R63,( *_CވeR1UU P4%)ሇ[4Dn (H6..'`I؝\h ͋xy٦56jAi % Νdfm[I, ٝ3wA"VcSW[5z"L}HM ر>*yVto^Ao@6 hV74$K8ߣŭSǮ5o?{,e#_~iPÍ0@Wi+hpu;qCd*MaK0DYn ??[tû؜wi V8"PôJʱ ͹!2.(w41an. G"5yԫ8܋~SYFֱ1M$\ yTd 3WaՑGO{bVr;z˓xϩPn&fag`箭״5X7XP4׿|f(,KyMs/ζUWd]SQQ3~0ga3]ȦNLOmʺs\}m013m7ZZELl:|%= .|(ѶǗ,ڤ&͡S~\ŧ=bF;S$J^9$>˹FPLf{j^}x/袽ևV{H"`@Bnmj LԜ''`@%{VvLa7uFrO?$,q+Pb"ZLVE `z:dk`4|ZKS41|s粟>w/  6 (w`.DhI^7+oxZⅎ #aE}sg/϶Wz;[ďh} gLx⼭T3}#½rvyi`Sn{\K6*/s6Y;"|XAn60 3k;~F P HUR(#6v8&qrm5! hESy"6@~[~$1a,3xbwQ(KBOu-^H͘԰ܔ9?&%$E"^;!D."" KAVtt'XѼ Š `!`  % D^UH@Ȋ{V"^uWnb68t @Ȃ0ɤ*EC("%%a`d5,j _.Y [D&T] Q ɜ+ Ll$&"aB ~5EG[4L$P`E8js1MCD!B8o.5 " P3!dIQNGA_PAT*PJyu)~u"+ublB=KDxTE8>a_png5Gu *_? G(2(\4Kzƀ:!SbQZYu>BļM'|3idT[3?ђ  .#J 46 7l#_8C'k(F{?\UiU="H[T[^-/xzRm/ :KdmG.,]؉[qѽrxSvaMmMr,ڽyGY_IJ^iBNA]c~*Z&pn|-oG~M>Nm |\Lyٿq{ο78Fobț”*FzbX N4!A6m*ޟup;&8Ӻvz/ͿoO0O#-4W ɇ?% |3X!;Lq(J %l0$ Q +~ qt/=ʑQN%< :·!x$m2 #P1)t ĀFl9l)dy,$u4QA/cB@i ܿ3_47MP Bg.><Q'$xCC|^"'汁W18 $Ɠ"i C1%Xw塿6<== &! _rG!P„f"_/* 2DYGd;%=^.X툠MRL\F.g~^K7|thADe%__|m/t+On!2?aӞLrp(zU^,fF2*=c o4W%0OX湨t;Jf'Jwl7Ď D|-"/'HÃýHK<'ǁ )FSЧsȲ $#@=)/ކہ(~_Gu3(ԛ ;Z!4S#/!(F, y.L'F;x~ˠ::#X)yc9zЬiAZ/Dsy?uPIE}bXhL1 deN0?dh ܆a3@ D$bз00E{mӉ' q_??"P#ɭЯ&AЂd|XRc6F|xh:c?PU2z(*  zYscxŽ@ ܀м8艺-V8 X;p:p#p-U*H@W%NwmLB@E5G4aP(0-86M"SI)rYq~=^0 `7H3XUA t|n~"o&@Fpx7 pBk"bGZV4O@ȻFЫC-{|Ʃ7Y ~ $ L2儌VY\{k2"*@ ?RBn/+eMQ?6B-x-GE@^MDC`/0A~d(~lp'񁏦/|Ɂ"~e ?[, ^4l# t FLIc*jʳ@8pqY)4_X'ԡo~nfq3$MBտPXq3pA3J$!^T/$YE4#Q vзs3 QrMgEfVe#/{ [LlY8̚^ .,R9!hV23"]+CMRNqumb;ZIr Is%Y$eKHȕbָ 2v;Y㙘C L"}V"MY㝜ECSI0̧=}]n9Q# soꖣV2z쎤ߏ[x ITNF"&BL>FcҾB9Z ǁQ006?'AS/4::"OO]npбf!I~ƬJՄ3)'59{HI|Cxj`4NЛćrXIFM>I{'/`aywɦY+Ճ 8}:ʕ2YNzS[3c\oH0>9I=d׊M6Bܑ7#_;4cPNs\Xk)O, ,LW 8/WԵ/eSKiS1!p;yd !}- ;t};hu@zwKlCh&1`a8O('E tbn՘Ṽ.CB="ܹ5^fSqYqr}v:4ac5~pn* ADZ&ݞJAs F:Ssп^m^Dz$AXӘs<{汬_,yl~4g[4`^=Tk aճPn:)D!9@Cn{+yZ:w-863# -'O{^|υd|vd{ t"A0NE@y =h:"1\p}^`k#ͻdօi r#9_TqGqNU#f\HJ0?]UeJȲdajp/3yPw&n )y#kb?3Ф4A ƥY>i*v#tAgf\Gr\[04"r3H l Fl2c4O^)x&6μTV_ǧj% Qu?Y>)+[q< '?8HJ hIhA/?V<5mi=dc*/aXB x@u$L {4LTͮ@Ve.s.$~{|ςy%]q/i ϟ@ޤWCAo|lϬc*:Hb~-lcń2込3 no3MͲ 5Įdi]qW7,JGDc%{1 ȁW@JD Ql|2kj-KF=yUR:B puzxεmc,CB@P|c_b097{WFiGD YciT7HBbOy9%DWb4Nf,dҚ#߆@Q,тb.h8T#N¼vFNaDɁu4B@vqX(Qx/7Uga+TmӞTK\sģNײzÊ_Շu\Lu`#87dO\5c'@4EdgD<} !3Kx0 x(  iӀ" z"*%RT:WI6"k6dbF0*Y "^0%;x@lt&RcHĀ:Ƭ*ՁT[Bk'L0 ?$ $"d) %)D{zuk벟U](~bח,K3o]7;k>tNy$mA ]]z<=B1@PWC\4;+%};}KB6'|)O2QL(UUslC.i ) ;bA@K  S=@Mkse͌֩zLmj\c%c vHd9z`j?Yn*xygO~"Ea[;T C~wD{*w-R ͽl(l4i&A@tU. i ԡʐ&PG./*Ddº ORb6Z"20 qɈN &{e]` (9YV~4y2 LDGB ~OeRS4č FrNE r1tLs?C茚g{aT(iϑo *7MIB BD#A#VnB* !Ƒ A 6b3T*MrV21p.GWȀ"$/qb@*~8!7Y~db΃X:[rhnU+sAӯ-;BSh@ۦӗj;=!(\i8%SG0 a+E'M}7x[X?Mvw=Pnc/T N}wW\r}5lJU;0o< e+-|*CP/f)lrs><ڙc8fܭ0ul^Y(Ja*jEĚr)Dl; 9@ 1Bҟ:)Kw tєO_4b A _D hE7|:&JsAQvOtozˆLLos-ߕ7C+Ǫs |4ࠅD)wx9ipkqZiŔ߲iSaYQ,lo|^c}E2sF;-5l(L!戭U2ͤ[.Kt61J޻rpJ5q4=|֌K'@ nk9Rsv3&:x{,#&-TX}n6~85t=#6x՚.!r8W[0EEbB%lԌƋw'Our-> I]4̣fs@"&8'J^jel~k<+1OM\Q??EsoQ[?i*vƦSxБZ7_7:AJhh,scb x@wtq@{YR0sB7P-$=DEhPG΄j6^L,)Rv=\̊Of̹Vkg6;)^4şoqӥN9wrώ)DJ_js#ĈuGQZGޙo9d5 +lܳyse#b Śi \ptm ~r/oZT U_~k}jeF~%R+4@zrehA(x〇c!+kB%u!OBA`iqjQ,VNn!=XνYDX{EtB #hS%SBI"JA_80:0\NsR쒤:$UC7*UވIU>:r{x3~橹ȡuoh@$+&oFWhR%%[#Sej|630'l*1}j-3p8~9UEDҸW?sqŠx߬X^ mY deKo0$Ĩ13tϽ66t5TЪp-|MKmYgQe67EQ" X8b844ݨ:zNң_뭳vONq4HNJ2X5--ڿY ub6|DZz p/qS;5`C]t葮($BoT9dga?ߵ3uPf.yoPxs7}:rwCb@va󰋰ZRH`觢~=&,)eL^s֕u$7'!6JWu #OkU:Oݳe Y`_ )c|".ʜ~}И2{bn;qZiDCSjM8z4_t+1hNAM܅;<*@M" -a.N^hlȵ|S)aF*}Y1Ddū__V$=(g+:qZYK;q,p| CUjg188olG >-#amKu )1Trn_fFA |z^2O{C7{ݹgcv? 43#|s#7bązLW,r񼊼">G Nr˓z LEwux+Dl˻qsN"z(>(}*L]^-ArU.YjehW6"vE -%MLy^z[1cY569f*;8,!@5^lKQFSۊ "T5{yP@I;}aիF_$OE(԰}^`8eg&ܷ鈸ߦ0\P١ΐXk<C:$$>]-Pg*%V_Y<(3&Ma737Ps#s=g|zo,Sf9Ĵv|f^r.JL?Gͫ6ɇBWhz*]ͷݲՃ[6e VІ#tFƀ!6"셴+5ȁ;SԷK9d5l-O+zκ=/Ofp TR<@:սOs P>.;e7[Ks6] 7i7δ5:?5ՋWQJ+ȫj:AlM ^-.̾[@n\Za\+O@{ѕT4ScCV1@|tZǓ+^գ2 Pqӹ>'SW<aMgYeGTuA䔄l|@%3מxLB`30]ʅp=ɔǾy]q动%MZPPX,>V裺NA4CM=&m[C=wNA ty=Z2KD``PgxkیΜ>Z%@UwwDZ59[|ZWn''ONM&w]*ߝp1 >\bĸWvŚFeb1!\apyajbVcU.R7^b0Be:m4 0 AGI::4oA  I$ǒIAIָ(>j#]Wx{kuYY[/4,>OӛzO{+.!ov=v .ww-( ~t 5+Ӑ$pʖфїrLREwkgENnqCqb0x~ o8Wge1 Ğxm=Zjڑ1OLfXTw-R\م"^F&;֐DGfl4D_e#LvDIpI{4m:xdEWkHPpQB(__ /R$Ai6$ Ipwԟ4-H Y*}|,'|q'` lDუ!"awXCD૴Etޘ{0o^fB,WQ"&wHoYOOu_?SM"hO ͼ K[bJPso+h11`*o' *U^a~"r^ݡH@(FD2 L}nzFw]3뀾HVwź|a\b(y/`I'U-: h{j) rǾrowЋ)i~vOG!Oz*DSm8<h܂֪3J&j%RG1nk=\UT BeZx|O v-iոgQ.A=yHG λb̉_ 1a8 W{D1(.juZDwq9dJqJ f֖R&̞/|{Ō<0Es7_,=YݼjQm56ɜZzc$ƪgQ8@,zS`O\p%Z&6Ak-J{WD6+х0glg8%\ w-ұ %$Tߠ#{k1(z-©dXXv.s\OYm˜))9p-k}gP$%)7^릏LgM<&Tg(f>7N߇TT*bJeTHt0phRrix "&.`!IQ{jK6Fp8Z4sa Q5nٕ(PET'jCk028T|X~z}St)PK*#EB=VH)@)!sJ2Zu|-oP 'dw45$qt_r-BBDefڵ;Zi/cO:0}d҈dY"-T x3Вֱ=DړvRYn28XҹEőPɯP4Ju! tATEE`躷~m) 7}N@84*v05"#?|;vXկeO_>Z}f՞NDfǀ3J ps*z c8;d Ƕpk 7-Sڧ_oCgۻJP=ΎfMV9W%A-3 ]#8>(hJd\l>H^t/xǛ}=7 zG`%|?Asg#{F UU#j$U+P9C"ƊLuB\XaL(tTk$V eB4:^[/~vG: t !|v\;K-(QdYUb૾@HJ]LZ Fyu?a`,bI(C|X JnDHU7#k1ȨC߁7g~\0PI J8oUT:_] ._]|@%CH2ŋyTM(8G=NiMm(GtjMumѿzsM >UPruӨnLhn)6T:-y8DqG>3WGt0uئmh)as77Ja"&- D 08"śXow1?>w6p뇾WMaXyqFgmx.nD*AOռ!8ˊ,ifzHSV^A~~pSy\1O3E e!d>M17?{+x 61LKmdd{tI4XXm8:V.>/MQXv ndG1Pei*kdָޖzD%W:mKRbAd HP-Z.AjCR}jqqIg{y&&`ґxk2XgE8 \z[Z)RONk/g ^kzwi V5`>ϟ[|5.srzɼ(2C-.{ IfADğpXSt;}0bQJWͺbH9ckrqfژփȢY-_@i-[١l*x7Lk7 jpU/ JֱB@QzkY',rf- lzC!BzG=:/Jdl٣ +R?"@x@P3%w~u0*rUO\swqMN-2U\:c( )DH&b.WM'-^U3K~b!_XVW\HfkƷXV=Ђĸ@gP <җux:Ȉl16uṤ|'[cBp[<Iί 6hhaS ITj",>ہ*@,QO=[ M-7Cݵɛ7l_m Kґ^@^}0CYb.^_avŒY3~×ot#Gq3\a<-l3ׯǞ3nh١xw=F[cAkX5>IGԁhummu2.2zj=Om$=&xf3_F/]k}@^u#Sbh;eٖ 3 yg w.Ƌ6gt*G( €]a垺0Cj fO,$(oNhu- 4(* vAlv=m$=ܽR`xA BŨe,QbP̈́0(4(PS4&F P' +}bA7kt#̵\N1ᵉz(Q}*k[mtޟnƘ^elSuxx(C3gd bzWqa:CvBOTr#u:zr9>` p {utPW+}qg~᭳/`}1r+h[/L\2}]V~JU J2tiO$sⳏ>c/-o"3;,j$n[x`x})*ǧp;`ccp>'*Uw+h 1> Ycli37k|66, :c\UlF[2Q8{{N#3ۮp@զ?cvU1\Ht.ԕ t!tKu3n ~֖ޕ^7ѧTalSgh/,JYi%~ _`"0d2K{9q,Wߪ"9&(:SnlG%ql*wpPJ% 0JknˮrM>b;z\T}:u#9SɽIuaI!҄PEYh衖x`<o-"i$L2|}ҊYI`uK3nD DLmV܊Wc-%d@w퓋#n:܌pJH!j4vs<-w 0y ՆDFx!:|]4%jl9sJ*h%o%$/ $f(o~&qqW."-rFb5ȩn}7X0,$@ lAPI`ՠZ ӛ˜sp:iwOWg5Da 'c",RC_8vUThSyng[2<8N6g%':FX1@8CDUQՇl]r lFxt [x$~e`P&  P>$(W?9vp0jA*6 cZmIX eC`ȀTd2ӖlA0A Ta'U]!1M`HcRZU,r3uOyKzs9Q2rՃ@vD3aW#"Sߝm}R+`޻MylA20Ԛp5!H{xaI#'PFkTt!a%$9L4B^Œ׮`L\yKEK2MpX@щ :H%+55(1׽r2(r4VrAyYۖi"B57w3ȃ~-2- 盒TkTL婪w{Vh(q{H@UN|ߙ_lܛPek: 9)~ @AޣKë|;_p ߼c\Pw;MscWgsjM[ _0>{0ϱC-lw츥+lmƝQ%6R`Uz&Eך Wq؀)!F`ɢJ叨 ʃbecê3,a:*ƻ~*_lfoaQ,f\ayafL$hRQJa%++a\*iPGJ.2\(%TyA tẃZ{X7q:b,0l>Kd) R#ܛQPfg1`1Uzn`9 .Y;'ǑT¡đ F5 ?q.k"9DG]~Pm i\ venV%&|`.2wBP"Gu!t7j>@nJv3G 2 €Ez2\ӥݝE2 Idu+!4NǦv"N/ghJ߮?)H9yZY U晴֞߅EW Z<`-zL.G9 HCfV%E-}lrٗжOK{'+s7|}g~7sF|P-۪D#[aS3M9OP(NCl\Y[ya^(c[XW:9zՂߐd j6ăi#լX8uL)k=7禛̎\m% 7&tJ6վbLmn,U+$QGĥmiiWO{K4[ bBɿkcy&wǷ<"u|Li{Jm>@GVYy\ oQyŋ]$|qhUj29=9*rVkѨP&%^bu~A8)j}'UqG_o>4)wyg7bbYW;ٳ>cBz,@̘͈GN!xNHbbul M9!=2eZ=:{ 8 x>MY?t=c}9A ++k|$t0πd $T.uV!6~ r;5>s'w^~xk{{{vK -rsl'N '@<ȩpV0n4x>"%@8LX~H%'6' {oeK̃>gq[T"t0d-Ěd)@ >hQ:TΑ"zի(DS *Yjf3«p|rz;6 #3?>/;d!u]s_(Ha)h^\nqF"$\EX'KV+uϨnY#e"fūآ[vEX+qI (pRl|ݥ3 ŵR Nh\U;;`#gǻEH@Sl- g0փW$li*X1=zn3xa۶2yǚ[続sM}R4on5JSP^qrbX{Q89a=֢Ȉ3PKM\k!fu=~%m-NzzaN~^Vt0i6ܽz B`Q3$fͅ50d Dh/(iub3oP\=Nwu0R1p2z+[ZŖ+w 7kx\˧H0?n͋kP*JJ#k[z*kc8(DF댂+<&|r%PV}o#yX Nna]ReJ` ϣwHxD(La.7.Zb3Ġ/5& {6h:vmwdhy50et{oy͡(LM.>@@SKa; yȗae!R`cXuy* +.G3.g\O*Őnpr瞣W̐ 4  (waܵGkUHuV2f_%ӿ塻7ՔѼڔUb 7ܴeCGzU=?vG 1q}jW*+v$J D !L|d/nEF+.cistJzIm&_$< XM.TM4yq@*L3>Oȩ+=}ڧ-MA9]t [ xPu-[\i8^'x1cgyì"a{fjن Q$?2"]ޱ(YIw$iӥcvlFhM_{ gl}V%tϔFk7vU#6,|[}Ss1oCF RN/8ϧM>)mq0ME0he,h/]HNPTYE5d ]sQPM_ q)0 ;N6hxaCGNCcjC6dX&jj;l>8Kf@WudPOB 1."B w!F Gֿ&7 ,7~WtWRWr,8jT(ˡ! &}cl*2_$-x%^]޽nuF>Ϟ;^]ݴΗCVP+M!R x_QV4D+Y8 |e/ؐd&DtLWp7-Kt]_\EiB-kMhLS?3R^;E$!@!>UӪ}sy[9A3}݆<~}}c DXF?ϒ~c%mtl̝_9s J >θ0<6oQEVh\ N8 xB oK.Tό( _ܠu{zV:FˌLLO<82-b(IN(?W*qHXQۅ3NĻ_j2SZ3oEe2EJ̀2e'~/{5ܚc> C[LpQw!Y 2wLJXE Z+H.C5"#hBpR+z HˈM! `@U@Qs~,asV.C@Nq _oH)Ozy|][P*9r٫(*ҩozd  bY_11RҩR(qPm*Kts~cWw *ǖ8l AB,(6QUAQTA856JU0@e簏OE. Q NF+CUVDq5\[IC9B$zsk,F75al"]‹~ R|Y\%ϊ ubf/em/9j Q޵rsyk^ӄx,RQ@MAxpB$@dE>M 0FiZA!V+CAp(]|)xPK65F*4~ PШ,(]N6 ZRS:4Iacl~)3kڮS]gӴ&4S#@UA4(&!O>ݴj'beNEbiA )d{64LI0$*6e$I0쿂 rp&H`A6b("$!!8V QgĻ:=3J.GHcO@Vx @%UJKH7PPqyDdj&QޠrAZIJ(s[Vr`_\ ߩYfz$=4_ 0hA/Qleqȏ4qq<+[j諭 Ղ.F*\OxVeJeLT p ;x+u/}OPdZ0q%W>:|:_׳'Zg:j j!>"~9iבᨡ/iW4|)C~3'wM읪Է9o}ܜ+2}]CP+-u &`䑒(WB{6,+nsk95bPq1@@|N`xӐh5ZqV `*DHX}Eqb+o;c}SRDN5w%μvxO >Jr87߷ԾM]i$5iȨa奰Y]vYWN6m BʕΦ=v~&' oŬ Bh 6u?Ce$[BD]H #ݩ,e<ʷ_̧֤^hp.\(U9'[ws.39]/6ٟS+NV+ |(6p4eĎ˙TԤr$)#N*Ehx*ϮO´]6Kݷ/R7^ IXouģF9tT2Ax"Gvc[jfĭ]'4, K# O៞W@pE”F HD_MhIxm Zؒ`Z^ڡњp3 HmNC$PICTCD -J+2$(%HJ}!R@,3ŝF)@ߺfpB]Nox&i8-ei|d45i"WPAL'+}VPjtI|SR_>GWwEԶ%%Gt!W{x']Ƚy7JJ4ʀ*听4Iy2"ЪLT҈B""I&H0#pqK_~'UK1 3}}^!]zGx_aSr0vRXo%p|GD)Zi/kfCO0 4a={wmuTS:akǯ#_!j%ȘޘQԎƬ(..y;V[OAUabPKis>%f9EHeA,l$+ 0SY7/'| Fta|Y D8DX a쪵TS˜&q`nVF]bH!<"- LUt1['g8M l0-Nd(v`25V~~.3fJKLLY\HdYV~xZy j Y᪇P4EZZ#'HKPbƀ0O?=ԕԼ%K"((ŊOru\Da_,9N X]2`daƏP)`שq!e:p)=U̍gkmP05G$$˶+gF{v:zAB u "2}/1i~GYn1/;mωc9G?|اm̅cKO5g3F5?V㳩a /i{Vd+˭ұOLD SfYhϏ6%I2J Ju9~VY.D+z@d-d_'h.^hDXk _Z1B=e1R1"e|J0^&#?JB|{Җ7 ُaM&'HZ'mc-[BpUg>F֩VӣkbY+ BX%t\tΖK6{I׎MP(I89'aFM7m-J h!+-Z[Xc%A^)*o׈ҁ81c^]pyÈ1a%R)7HkZqJOݘ'e~laOZa-7B`x3ѰzB&U/t&3 TV ;Mn 2ɞ=,"GMyigQ}7E>){ 耪{kNкbu=-{#bQ`-eSdx͕Dg T1 fF{`pt +Y ; #_`#T m1+ QLsLaJL)|EYn㺫pw0&*?P GA#j@8:˔-8'>AʕºQÿ:H txYqG"MkA1z^#V'ބ!Hj A0VT :#%/$px1&; |#b*9ˌ{`/^N܄,So$tU"LPO'Y_~ُkit!@cd%*%YqZ5b@ kJŋ`>.9s3,F=Xw׽ ]~c_)* }k]'T'T9]1'101;ΝI}ϗ=q^a&̬ E;ܽ[$|,;c*Ћ#y+ BU*,x?JךX\BE ZaIZ$[e=7"h>z5R۷~J A \^#X`G$M$jeM  P%%DHqR;L`A0`V8Dm7._Dtlnh2%ҜQOPAlNth/Ѥ W[/+ .wr'ɦ$o]„TM*tת/[JbnؑK@C #F<7}I9gU HA+|aY C||F:gYU0/ѯ.S Jy4_Qp(9.]2`ʋs/kim&I#Zűkg?{HO!(R! 0=K=w2a#7$3I+B[Ӻxf{n}I:oy$x.ǷCק_[~vmڀO^-%%uW_=!HZ(iʴR=EU#zGUv5Ag!δwlwQ/DI~/lq@(K{ 9(+!rBZlXVfsxm#BI,M ibPߖgAt[DnL?B/W$ r1VVBռ;+[ ]]w'{#&ex>{N{>r9BuLqD kESwX !IEb(*5>zL=*zn?G\0L ,0@Uhk#mwط|T~ȈL`hD?ei IDh1\P}AS\sX-FaH U- =jE )1Z3Bjn$IN@ZݫNɆ`QP)(`P6 e\msdr%<nSag)v+4ѣ#:Q]>AkEVFa6<4 yV_L[C$4٨![Q2Ī8v6G/DS-p'bISAmE`d} T.i&sy"ѣ{{<-pⳁTxfDe0~`:2D Հ< Z(aaHv)$pva!&q-´5Yxc">H NCK@ԇLΐH ۝eQZc#ojkA0̓P}DSH+UjH[RWOm^؝>A5= nD 4t6`4-+bS"$+ 8w4"(+>єħ|^(p D +p&Wď!q< ߹G weD@ݍ^ ʪn&(4$Sz̻sμS C-G}([>}݈X*bS:!| KB@e^`)~Bj9\ĀԊ2uvwoO?Y6[AN;i[ l¨FTAcXa{4V-מS0A"6!~݋gNPџ)0٦={VySTկq`JPu}mrԢ 70!a|74<{sߓ;.kwV[s"R_U4Ic7e5'𺔄aUԅdq6X-CYXӆܬChJ'9.& 9PUp57BB]snJ \AB' m꼢T 2ZPҒD* cJSpÎPmD-d7Y5fm]:JЫA:옒`R8/ ׽ON7m?hg zH L n}Y@]0 Q^?\;u"z=n-&1h\qS oڲ#=]Wf :(+5Xd9KQ"WL*p-V gpW# Gߺ`oM >sia26эpۿ.FͱY"ktA9=Cu6lu:d! e $S(^=/9yMq͟._g,VfrاBNH5+&}Pڀ($Vb+$Du/sīYfPPG0Ջy־;0v6J"L+BFЊXLQM0V`]3㓻#.G|-JJ-'*âLpa7ř~]` V#ED5!RnKi" П H'B6%?Fgm~lU2Yx>i&HD(!B4 BWy Hu|<y ,B/B$nH۱+'}ecUcݲe G\Bt/qkp_%'patD3շ)6x tZ[jVjV:ĄWcjlf*Ǭv5kx'm5VHqV8UR׎εz]l2|hTglXY  Qs}Zߑ|ށ'\vTG*(@P+m  o3;4iWs}UR.h;0Fȭ䎑#77_ۅB&h -lDYp= z^:k{71 RSk& -3ұ^r"~ `MW wAQ4u84h9H;)||̺Y#;F;C^>/cG<ճVH*qLj\u6t wgs,ȃ{594 ƀo1x/ ql`n5g{Tp{5Xσ,)Zx${ s 4NETȨ  HhI/À@D8+3b~ 2JVeXAG(pJD8|b[SY/?f|SQQ4Hsୟ]!<#S.`&xЎ B9!$oO3"ʠHX7,:dN]i:j71o0:YDrICO βLCFA'tA񜠰F?'<e򰺶؂M}qn8.zTj":%74dgQ}eu^9qDN ["ˉ&싓4HI& kSyD|pX')qoõHA⶘Pڠ x$"br.=Ne*0 I `ueup'`g)|s@ `V:<F#^+tB(@̍/ CBRO|D#WbCRënHn>'Uk/)ǥd 1U$Wr!xlR{!ߪ'$y.?vT"=բ_f1#Qk凩W(IbO6 jXQI9}$w=rrW̙RBՠR\۽n[^ @!X80ΡGb+X"O0LP E 87JL/(*dF;8#pC~ʗD d } R'OsdX%؂" ) RpB7윴 n\#lH^Ш- H=Asub;xz`򹋏g _χNgOg΀Ty;.;Dlg;/PD^<{>)puh(hnxfnW{ \ R_+Obu!#-d \@NXK~W}wy$.(FY8ٹlb۟ZΟ>~!)S&W,u\NZ1>m4 $_Zaux]07p< $P\6Y(ϩkM(J?ƒ-{d,?^JJ+J*$F'zH'4 s覕h~m3hL?׻?w@vZZ qj|1|&Dze<0<@5B#HUĘ2Nc( RDyGx/QB0(\[^#y;,T'[H} 2YX⨽@;^ㅧW G̥=^s?|иx` +|p6RS@/8bV#P> Y@ˁ? _ 3Ȩ0h0x )C@)* -x !: `fyɞa2 w^ApPyi'A!bo5 y0()AB."pՄ U^p8\ jBO@$eb$?,wxvʇ/w*DM~)/Nf1Sݳ Զv/f{LP )As`.;跼.<,QnGރy" @@tZaI.Svә (I@ Wq[M!8|CwHО`Y2I-N ø)6,ƢI8WY%q@tmj 鼵93CM! <Ʌvݚ `:_0DY$:ZvPN8 }~ הNz͔ܺ! JTt: A&D`"FHamWڋF')+s\ىo֧˓[\{f^(\MomQVшG?ɞOB؇X  tD#@a#NRD뻉:ž2.N0veE7%8up 0 (`'@Kd-hw\M#d]qDU3Ľk'iQ+E6+]0^~D/`(]8(Pup{qɝƟ!E$8#j1 _;E)0]sYK b s4@xd{K60"@ Biw;о\1EH.\Ky fY4EBCR"Z ?p,[6opy@n( ā\(D!A0{vk'j-Y~liqvAՃptܕ M3y}nf1${[~Y۾˾%M|ĢYg]"ZpUU9# sIneF&q>裳̞skN>Kq\tfM $0F#F 0FQk Ϧ=s_MkL6!'%UTp ZSc9(\LC8C8&x-Ұ/0i'1/3Emҋʍ\DtP AyHXv zT:~[y`P.Ĭ0`\~IXDx jC'z_>.Zi TvZ_WMjO 1cF%S S2A9 &@}wؤJռ0橩Z]8rapZ XenM¹l/ȍQ/ /q`pZ`?-Q1;NEEJ7[{Z8BSS/ӰVcr/}(|mG&xFO͟>eլ܍A@kSg9WSfXIAGJE$x@SwofWȐQDEҳ'£'ܐB7|nNN^B8mx%}+0}#0sk?gN͵aR]9`UG8+6I﹕mU"ֶRT,0n?9W/ȿV}Z8Ɨt,/v&ipnmt HB[xay$>7 ~;Oj?w7} rGs,6K8:$2ivb/2I8ٹ&Wk`%.r.A QR ^>qmbOJmWҒjQr+;+P&hTykw7s24ZHٿ~fN ٿLh9[vo8/YMK끴ZA&x*=xӜOٶ0|" Qk|9Ф&^[oA kG@b%(AѸX#(s|zț4 1ppY)ۤgsK\ Dz-D7oҟ.|bV /ܨ",=TĹ :H#`y.xè!nrEcV5O(ϯݠ_1(=ab|İҷI?xj_87Axe[pS \Cr2(I*fdlm"U 5LpóGՙUXQɽ8ͺJ3ˉH>lV{hNQvxQLT MnaC:/t~[R;: H ?;z-IgwAG^FckvPuczVM:}7| >j{Ií/>'ڏ3ޛu9IPR1RpEӒ5OJpPm[ϭ`L#_A\vQ̓[S'䃝!3 זfNY/QޯP, X;JwGX/ulI._OU9#Nr= -I3ú̶q?+`UHn8ےLnժsyĠvs5l7Nݼ-ۮ>1R\M0GWE|x.d rݥ_2Ʒ7ǚ^=\S}LivMgQ?0$+}^HQUYh>ͯkWi_8<X )rBSݯPeKGs\Ðܖ VVrr?~aߏO7Wm䓏"׮-*[{HP 08:V3f_A[Ңq7L~YvwtL!|/!43fIe\u?3Yk8:`,@GS[:z5GZ:1w ^6uO]{7Nd$mh?||gK ABοU?߈.[:Z _˧R "`!pд=ҋ]Mrm=}"HT3'=t;)qm|5srq( y"PTŢkѱ- pS6{:9_>zQbD+zVGT{ ˄ N"*L.UDJ 'l;,vxk5", p}P`YQ68>l.^;L002+ȶ:† !3r|V>[K7X3"g'Fy8li*ElNpR{Sզ>eZU}e @ iD`P`Tx%1#fg|÷??>wPԏ|I7%n7`-lz](i_{r#98[&w~_]ejZv<-W{ }(yx6iկ|6`UG'5fkg֮XHɳ2Ag~޵GfnI>k翝8s>:J_QղӸ TbTpZư^{aT°1 Fyd)U)Rxc(r[j<ӷC,x:98-i&^kp>|N=ާES+ ntT݅:jsDNz(<5h+ʪ؃c܎P0yR.x8}֟a}6}o=>⪠_mscnUW`fܘ0Dݡr UYf;s1CI]'m~;n&3#v+K~kv`wT5oŞ\Cko;uȓab-0;7zI̓. ~/v.,DY6s˯BYۉσ誝gXI{…2Afbqr 0^&y p'f,Ao_޽{{Ҥoch'qmf߲0:9ʈ=VIP HD"i@=Ɇằ{34\gg^)aԽO~Ϙfw;0|:%Il87S=So:_[IfnYb"CuAZd}~^cL8G[*w4- _Jɪ0q(tD-891*ãl0$"^A'Z@>3֣N62qntRg=w}cڛ[??qnޣ~t>>n?>rBKD_s'bp_ى(5ST.?G6Nc y0'E[XIs=%3ujW$qj8{֌ E;gqz᫏CLݐWm9_~!{VZv}CEZDj{T?[P=kFv K*'h3 U5-ힲ|@!il\JAH^nYҳak&M山'FߣUo_@G:[EYdtczԺ7]4+c/H ۬=Z( $knD:TvCv_BzX4ow1mT\D_u.8 kdZ7BMSC'ndpNfҊX>v:/hW FO8kqrK&udO =vD{74p^@fZ67׳~GB0yrdG.5",t-(|^=8ifE4OE#g!t+%'B WH0a\J3;fGX ^kHe7?WѰᓷ/3 'XN`Wc0BkhnGaK;VԳzc6<> srK쿚KFl5m68'?kP\ e ima:#x+3ą WBMZ9/\#~:v^n|fm+T׊[ai<_ CRntO~͟"U5Fx]OfDhB10i{, T1PѡG-ި`TGDGBnsz}UN \{'|yÑ!̗)$Q?{|֓-1A۱f0z);:]Π,BՃS4dt_W5vΰ8Gk~q-q$t[B6Ds HǺ¹>'Ϙobk׫KO8+iK&6rExq-G"[a$)XpM61дXǂDŽ>ۦگ~V2 WukA{}/*9_{2]!C@@FH,&Ba@,4[a! ֊_&k19"0k.m= 'psZD RuE;R4?npF01>2{؟|=ʅ:@4#[fsY45}P!hZg?twQW^xZ^_-~@/OfsS;[c'+Vh MS@[u!Ar?aRdׄȿĽk|_r4Րꇗ9UīJS)wqJ=xЇ>r$ ^ef5ɹscTj?{S*:*Uԗ'>ЮfƬb x;|UvcDbeƫgύ G>[~aOЊ@CmNS"d&S?{PڦǞtg'u{DeG&ks7*S*.o ?HFtT yZƾ?2N=.>o Ѐ9{k?^ѪvDe}Gm%t&z~CoNм~w),)[)fk~~Z}_Fn1w1$-ҵGޚ虎hD{Tzwfk;Tɖ*j>\_(29|1)|Qyc޽͑ ajI1sܹn[#|ٗe?{8 (fx{pWZI9bIԭ,Y0,"33=zx @.Wȗ^iYa;2ww- nh'9NOn@مcV]Ψ>&ld]yKG崾O$JuUi^X7]zpccEnc?Ȩ6g-ޥVnה(&ɠqL67gB.TVĹ!E&4 y8MwY\˅'% e,.-6psDtӳQِlLx«?`(:A4'E̼ZKmpiB$9,d>? zǙ@" PI0 @ ç7eQq<0$iz3F^Ցql4uq|<x6򌠋Xxɹ'HyCFlHnB`J3\?IKRg;Mn,^kU ut@;W?Ey;0Ҷ9 »{Ch1[sf znh*o$kw$jOz 'nzw$)*i!<{^Xw$|'NwDtNn8c}#lT 0 dۀP*ǍXG N<^y^{ʮw鼊?}Qn7DkTpR/yoz|ZF,}}j肙l0OӀ"pޟSŤJK.eS `ܱSgcQr=KIU\_]Wf]?ٵعlMGRTw\3-L{ aU\S|2Jz+[i{cF%[I8g{\22l^*Ldc%Ɯy觳FzP>H _,؂+aGd(xF?X{]C;҉.bK<=ݯ{ʊOh\(s? M6gTԈP^PΜʫ.UJ5LpKv&~EBEm&W_W=3';+ .},8^֪澷ΓT?c;T&RRaWAc1\ϭ<=s}dwFxUly^LHE&ѱDa$N#;DAg6['(k>2S5D d?+]D!5>u(t]noݟ\z_]7~7 ymeپzʧu$oH b'0$9QuSF7u}}Y/^I䠫Q  H :Ȓ#+œAlr;H֨).>;iyظ7`//^њ{@7?U2 (?(q.jڤQю$1LLg/uG.ϗ uMN']V@[|]/d)KI8~gJf4r^n#|7}Wi|LB+>RًNys#@+EM `@kA,Ks`!IG;g~ n$tE 4Yt?eXz[O=?_?DZ!3BbwEZb=rmvr}OyF:K?x .ለDl͐kG??*.? !"@ Z+f@,щ$Ɍ9KWN-܏$qMM|H$'U>#z*aa3K ȅo+0kA wEO0Wnx6!(ku$ 鎽?,qh!:7ͭ"Lqkxڛ7ZݽYK`ZOqOϰNRm ))!L\+;3` ǎ 02E&-%,-%V[>xf3x']ɫDmiCWdy}(dULj Nzp"gP<{B_~< HzjYqE 3{9\ܹB#q}c%8|'д5qɿDCQltW)$& :z ع= G;}|5O? Ԑ~'?"pkNq.M˗l{5/ 7 >/)qfY6cU7_ٺ_ _Z4鼛'+_-9;{4_ ȫ +lP[Z2í:Q85 b>"OvlD>Ktg~rOXǥ#rɩo{'+PshP4Q{0^]5كXI"۽l&%Fl^ x)8WL1^ yo rOɚ6؞nbbMjYcd^y/gh%owba%J /Q=:{Mz{Kύ'D6z]juPPV3z'|M"+bM!q"#vO[-;9.9CH#oA\H*&\kÝVzi`%%vN[BBqumAt:.tf(Ϲ2KzptBzD$ƒi5 ݲw1r ~p\˷ْV:zّU&h+a s^^s:3֎v):N;{|6!db,]%\6 ʩG<}]Iz7qO:=5"ĿfeVE(΂T?wqrb3?% 1ш\v.̖7zSPY$зLcj$uq =|x9ʉsu,ƟZuOogme x4eI7);>+ Ny#ojLOAgAQޕ_dm] x8o\{ͫm{׸RUX .|#>w/\5Qc:myv} HE !.hU,'4ySjgsl Њ"D[>L){D*`@ pC )g:F;<RgPLEثCP>˅.b4I`BJqlqgJŜZ\N\2]2p-U /c2~[r L4*ei|+ǿ$^hCA ov%˄YC~-} ;u '6+hIIdp-%Fg 룤0EbeʖJ'Vn,2ckH̰'*U8>fx $AyuIQ9.BO˦5y9cwځ7P܎Yjd( '8x )lB@l  Ѷ:VG~x|jdjYubyBwnO3GMlLAu[$38AjrNڌ^ә@3P D@liO8g`bAɛ8ujͩs?QMwz%X64ˊe #IGGyA-ysVdlװgNn:+ll,329Q@G"S.%#R$D :@ZtT+ONB 7x%)Mk^](jVjwgclP7]˟զmyٵ73Ms㖢+e52 sڟtm#&h 5ԤH֚PAx<Cԝbp֚Ld-y ͲО[oAs(]&yE[5;ڹ[6' 9O[>2]5P\D[{=hޮeʥ {mN5nTc-Enrx߱|kYt<ݻe?Ym[pf^~rUi-@SJ1Dՠj*P)+h獽PP>A)%HM$LLt?fc~ѿqE|YUw_w|Mgx[:4]*%`5#ڀr$*I9io|Qk}.8Y|"']KJ}'߳4^vz6hՃ"n.'O:TU"GfG 'g䅾eڜm.(]fsG~> .]p޸C']ƕbNH & 8R4a c<>k׃E9CBSg=9#jעS>uWE? cR<*?o-*,+\5#:IsG7 s1{K|wbskƜ>I#=?wiR6)C UÖ ɚ,7e_}j4lEif^2چ9W̘K 7is\IUgYRcjTD2Тg^Ghvt:AP.ҭKt;]tyЅ!!W^օ(4JppЊ 3QNʙ3۵4+{M^7қ49` 6兮lNh{4v K@^zy,{/0h Z ;{fBs`KK It"1}a'9FKnLu6jDp! Ȫ1I2ެAԬpAkCaG4ewfuUSr :l lZJQJ݁jAD,1/&l!.2U1YwNj[,c]9} Zq=Lt $~_KvÆ:ǸőV٭zmnǾΚpYc<ÿ}N@?D\|o\ ZxGƷa';joޞj__øB|wt1IV-DBBIP~BDP`8NQ3nJaRdjIT{jҔ~ 24GԢT1&f3Ƴ^=HGF(.M[8CIuU|繓9a`J0d=,31j O> B$UD0`IUH. (C2}DCNkMh+Imթz_t.]\>%@]GC._<0gJv-U'{#$83҄_" K7*oo}p]yNln-m#_Js UTy"2 t@$$Ywu?yᛋ Aw-5e W?zubh֧s=j¯s2X$- 0no_~˽ȴST7WurG>>nPOVu6rQw*%~E"@eg/6>~_)ʄ)MU)[Rˋ-wjdR2ki_t~fV1a7tE9. W̵]˿;ztS=w#}sюV\'a(A9pPrI*?Kk 6ntq)Ceҋ9"PDoBRYPlZf[=Sjb܄>DOC^|ɠ&DzbbI={EG~Yc<wm*&tEi-!OP|>P.y`"Hai/--M"56E:X4^oK/cڳSZӏ/X/Zsv1^NMƟn% pƆk}άq=PS5ݷv~c_KI=lc^tƎ!ۍ*O"?.r"fj:t]0J,kb;mpa!MU+O,z߫-ڋh }w4=&_Z $Hί/;e;B+Pkҝ~u^R z$2XsKT 9a  C ?ŒŚ- YNdXTJ.24߇TJNmiQjEIu&G&68#$DZx  /,ȚfX7At2 ,*]s`\)ϭqK*!W-ǜ+CάfjB;t5*Sqڄ,LTUCP-ymˣG=&OMMa$ ^3x $6:WBRAGD+O'洆djq(1"XA_ϳ#G 6I͖_FUƐM9sXeS^Z*XO*{޸:üzX0vW c{`ZHHqpԦrcM8*OQ Q890(8gbB 5Wkn禠xrjƠx>%;7<N9CSķ=3TځBx|TI{]mqu i yת/be6׳u,c;}2;vZS?.GUuЅ5 4Ed%G.5:)_D }zȔRGOl lrKB&ȄәzMqi\vZ0%Z6 n.JyQ 679cis (> pl}Nu:wq21Ѻ;3 dRN4(t[6mɷ~ ֹG @$_}>Ds%v 9$~eHAeHh+o~N|zHQ iI샕b&AOÔ2[l8vZe$p Btr3`뜯<8_?{>zPe2/v}IlMx1NSg  " xKg6kjmW i疥UYΌ}.Q`Aue]G[e 05֒;1̦mjP>)-M/Tb!9:cI Mޠ=Eė1#Îz9U@ k*\}cy.]uDT2(BtbWjDgCS"u= GaB\$'6r}eO\Aώ)ДҙhLۼ^_<.Pp]Ɍzzϟ}]?uׇsQdIo#ѱ8@ڍb+Bg *AЪ[U3À]4 @a+h^,B]-!;\1yP Z(HF_| :Kݾ@ʾi5~;`V 灪`׹'љltu!2N.Èij! =v쉽<@ab^p~/NJkݐ >ăP뇴 a3rWoHy?غ_|a` S(N:Ty8SWhĻ`> (.zI1+ҾLij6b;rijl{]Gcm:S7:}щz 8r`D:5_(w [`]M4|T 2UX (R+$p&"j@S8%#AԐ(+I*Q'4:ڗbd}h]QM…S5vXӮeKf ⢧:v>-=44mZ/9{dE1L`]nc%~YWәgLnsqGT tYܝMXMd,.sK+u uI{NgيIn:A4,,*10:@YSu*S '@[׏Uj`0aS㣆7-"F5m<J>7^SҤ7L(XD3q7m疤Hc'Z8ySTwD:18y w1'i8t<sC f# b7)(I㩫y}w~p3m kA(a'G(yβ@3Ȇj ou0Y; zi.&AJjSkͬ]6TIp)!>؝o7sWmB)L>/:X o65Dyą<dEW}-4mxRoYګ\HDJãVT(5^ck=Y3O/^ůQ Boh7;Lm[cw3n-Rg,f3zt ˞D#o0؅0 ekX:wERl;.O:sˇǪE۔ej ϫwK|JՓkg&eCE1*Y*ȕj~PP :^%"J L.Up5#Z6,a.Kun&khF WF˔K`>PH=<]v jiyiC[e}SnJ*W#zWQCۯ)Z-T(e֮W}E`Xqx/~I/ՠtQT/3(Z0rHˮzia@i %Na‹)a׀p-'V<2 ˕Vg@8 tbb/Vƺ oI KLC9Ѻ̃1RHN+qA)wc,;gr@0yb3 &kR!;͚/;tNAk'Lt"b:Ј]^#'&Jcu=3'/迚5\RU;9qi _ݻuSUVԡd} v ]7nn 4naN' YqeչR h`]-]cMbDXraՂBA<&-(plt S0(p(:smtb/}'mz  )Ė": kVΊ@P]lM}d;-ؓw-<)!+;,0 + J:>H&N+ [PЋEj$0U78)<6*YxmJ[2=^#GC@8 d^lnzՁDzr6KIo }] CWޜEUzD|3S,ӄ`P\-1U8Dc"@e0tP8ю؊;U>wDF @~}*J8-_ Pn=oh(u£oXN_Qvul㪿 X}i Bc[_Dӯnm4PT_vB๐yO^r"kmzTح}ZE|B)7ԦV%aB1"1(}nE@vHDsE#ymHyʡ⽙lI|*prRGT;raQK ]1ڭaَ9p/+T%{ύ9kQV;frIw$ YECL;e 4hJ\ dyƪPX =4 e3[jsT9WCف~žETNSXX!~Fa P:S767SsY[Z '~Rf2H2}mL@`y%ˉ/D?ϳaP#4P-(xէ&>v tǜn^Ni jǾyz76չGZ>f:| *pzze8 I\mlBK, !S$0dR!%Yo#uKxU)٫?}>CUbb"%zdd)h*T*m(mB"ItGk DEih*$^O_$rw}G)]XɝzC#\S܎BZh-tDždn9*ڱTt akTWz_[l|33'iOorU/CB0w(n9Rba5g81E|N6-iz'׺6v1ϴ=NG2Pzݛ(rꏠ?- O v,PQ؃`ָ]M*dZ"zF e0VD9ڬ@[{F"2`hg/;;NW9+vO4vشPr>y+ ILl_qO%Cw멵vJaG^6d~V-GϿN^JM6^l7~i7dlʌUw %Om"Aat-*[22鯼Ne?T8= s^~&yD!H# AT -yvn?9[hvQLȈъPwIN_#mΆ=n ]rJEAױ˓ !ayj$(ح0꘺@@Qr(Kp/8y&b~E`N#-+bR)}0e?K ښ2,NIN?- "EWuU|YG O<+6bRbnykM9˦&(!{E=½f6^R(Q tf&@^ LXF2$ dw&)IJϑl,7,k]Ϟ\ 7m4 Ľ[Ya&bzG"( }1l\@ cIff"a#l8⃣huYאh 3sxHvHo.w}gdY]s `ُf@_TCRm}Q*'o>??c/eyY9XևDG@gk{OioFg{q[ ۔ MԸlba~ųF#.[aLEdR_q2ym٬| tt%">!}jy3Tk"E;_TbY#9'YXKm'H&2֮{ &# ݞ=$D!(䍬TҋA7٣)^QL*^t$l@*q"'w w\}_5u^tp:uPHgbrL I[9 β}FՈ?]ҳq0R1u+ӓNwlRzN9u /`FKɍPN#\\L(S-x\kWxA-0sv8.v(Ub\/,H\Inj\fmsCWXߌ");kQMhY^)|a>:d6P 6jCb4WB3I6o**>:džɍoPQ?_)!ho*mcʼ JdB@hq2"Aqڲ]1M+)*ap檁44`Å SJw)n˄9r['-KR=hVpl-ةcfҳ"^ K)x[w# ZeK $MC<2+8)E>q  kKڠ-|tEn'9_*JxwȦRc&U+ u UW^y~mPYYb!֒AVAO7|rB"u:uJr-e8BLFo}HQm a= LYEo@X>ƥ=u{66}|f!mbK' -d4(" 7 84k^FQ9>8Myo,dK1.~m{B8PJȅlފ,,k@xNm,uFFP͒5PJ+Tk(TW8^:w8Y^o d"?tXLk;hN/sH#/PnuW,Ěg~?̊h} ށ/zqx)FdܛQ![\zZ 9"(KGm,nIPx cO")~/:ZaAyZ9+G!DP9bbA p)ATHNNx;u xmxQ@::oP ޔSbmluӶ]7=S )]\%a7Gsҥ0ol]lSlSx̋/ǎz0:ܡA3y `:XʊZUIkz-_!XeD?|jNϊ}`%A2Ġ<5tx;U~L'< ry iFTJ}lRZ$"@@n*nKIE7Loqy=t_I$ D%#~:J.l7 0TVjIܒ'=0щtI kE0 m~ie%+Jw@ Eq? 0R8[zf[V|do[E]uHHRhy#ZEfNg'B7s ME*XEyDB o r !^.S![osB^$|>. .SwvRM FaמK4M{3|͛T<}8Q6hMa[:F)Eb#jgH Tt!EbP yE>TT/^n_*%mP`I*K+F"bĒ4[EhB8"#_ZDg|bj;֫x;z'M`.q(&)ıiگmG~OK[뻖Xћ]G#S#y-F~3rܱJIf˦yݔ(̈́KS#agWSv?-N4UJG篌GN|WMڔi/IJ%ꊾ5#K;4ڒGHͧf7z%N+,JwmkX/up- ݏIaЂ˪q/Tr*WHK>$DŽؔfvoK;<7n;l=s5/uːc Eji8p){lpe>wF&SCQZ{F{ 5u-CqFC|x A3 M,~HO9c(ث0BOY8匨ּT ó Vϑ" Dah8?atش 5=[gs2déI%?e*vb1Nc'<1_@#*;-Jk]g`t"4qBΊ\HN\&*pl} {dF$G4zs>s(SU{iF0TF9;.=#xب^mhQe^|@e=Jy+b-u5[9qƚaize-fXvłpW99D)% vt$µOqV}'0t2tWC S@.IJ\&'A(> 7Fʝ9:5|6wT a߸ron0@@ ~Yņ1ATHzA[\([&@pЈ 8QvR"P5J Ho֕z+p6R@DDEJԫǞaX4!h9S=+8C6=vt/C! V3Òj@ 2uzi\p6pkW*>6U 0:(^$(U;峺fĀe&Pn#j@Bl id]VbO8U"rjBeW2Nf[qzCVT+숀Jɐ\J<.oiǒN#t =gC{`Ž=z^IL8D4l_SAP YtxyJb%eco켫cDP5Y cAsK/_`~$PЋ(ٕI",EoGu@hn*!D%%!>)53%`0$]B!pjNcS/Hw!Hi׵pɢXYi*j_a N쏲uEYOCzx;PĜ-2sJ&Yr#AICC}}@@IZGaF+q~16yjz=G mS$ ʅrs@}^ʋtO_rەxd9_ZA Q/Fd`B Й/dwOȏ$#HDQ8W惼dεmEM}P ֶV~it9tm"9^]쎹B4o/WG=$󵧳~ɘZM^ f89a8(">~;}kg> WWlJYoCw%+ ]DqY*vnh+"4ؼA-U,H4 nnuk`,Jqp'Bq!f’k^U.r =[i?< Y`\Mko(b[v.YaNrƇsd-1Z0jϞ5\%Fhȑ/zةO &R)WeP^! ~Xn@ ae{djF8*ʳ9(V0M ٪W=zٸh JlI3@I!:(8%ɓHA# ӄ}b]b @"TsUĞם@Jy4e%$ǟh?p4׳RR-:D ɔMk!4CS뫚%U#˹DPt*L (xb Q:r\Vc=f+W|USzqMmdQQ4ky+j4K})FsR# (ayU.2JwCP+ϻ'qGrEM񖅼"z{L$HY\0fz<1\p 7uݢ5QhLY2|/ܥ7`M45ыJ3AjmgĄ\n4"9N4j-mHdHF !dS&,Vw"L1DRzF잗dfn&ԂP M`bKD|]#?:܃m$x !&D*N i/r=g8zim<ߘf/=J~єEF!c 5Ln\"{wm2 <CW&sqD]9 _Y12ջh$Ni~o4# /l{]f2ك_iMD(޽\ T 'Ibk@Kz$o>wφ6u*Rv^/m !Ei. tk9E]%_wҁ`y+lyEXIW]# N>5W{v^?CEPA5x-#RA:XH {_B^7W|}>o?}v t. Yld7s((`E Qi"bCH.PAJkX=~ n py':X;)sF;徆A^YuQ@++~0<;<ԙ8^dD"27gȔq.56H[j4WhU+џOuKzF^"*&1[QǒTbvʃ toY؛±KrJ-zqmsՊ&QIV]שx)T^Kڌ=* o?3o/峷Q|.U*PJI$ R~1]Fa.-e*Hۮ׽[MS- 7Yg~eb& ,tP0=-(d2鑡/-So1BDs .M*6EW}Hz?*s}rԅz)("!TAs*c4"b#ջ|܉fnO]*MWdh\s ֩cg{甡6DWZ?$1n Y,B+hRв-D !XajE.P~m"f,Q1j;AB:8i+ɞ,&gX5dhJnBA=G;٤8ʚƒsh( WhBYD6&Q%dQoJ{^'JjȪ,%4-CZR,\Mmy踛ʻr[CssyW\GXvR:W[T L!@-y.vЛ΃;WZmJF'Xkg?B:| H bm+-e=֦9/ td!maombtnin\6ܩnA6%z)@={z/X-ưP!CfU$Ve3:CV7ڀ`AwB6g(Ec;Ŋ+3E_\?*CN uB"B\3;+`r[F\5 '|IkE9B˚Nl"*`,@2"M>}DP AdyQO\;q8FZG- GZ{YmkA3yp%D$c/E/Gg9`, u.5)n8/9ؖAHDKSe 1S1% "@PISRoD/p`3S)īr`;*b[a6A2Cn?3!qr@İ΂XNm0` @EL SEw3!*$ދups]Int2 O (ʓ= o%b(@I*ڀЯ1PGD'ȑ б^=u1W4[o{ґ^ݺc X`"}񆦙.UQ/Nx!'ujp]\ 2n`> #1 KLpOn >gi g{C=g\P3 EhX 5#j贸G7n"n>PR'4ruN˽ъC8GX>]Qjm伺彿Rxkd)`g?^ zs urg~iIW|U(YD3j@x0&5y}HwIٳj $*6#F3&& J ; PRr k3}%ogQ X>m-}f]:#j[@rjdMTuE:dvz1=)nF#LgD M)G\LLj Oīt +I N]Ӗ?L̲ \&\(' !EX_xޏڭ%W];.!7U/GZ+:@gx7}5TP]ZNB;C[[ l《*%Z(WD4,T~" \ 8Weɘy,G 6Dm-npu݊VJ!E̴N ^=pEQm%d$Mp#V `RΐWWEpB.rÖ܂[%V !2W !sOʇF@qxi w〨>nͷl}):PJh ~Z}@[D&V@$aJD!,nyK\$.Rd,r"6&>g!Q-{0h%BtUз*b&J&DGo]|Rέ6Ky'&4n Vخ0B~A$7J |9Exkן2Ev+<#3]Z9y_Wh0?ir58)fWDD@Dʨ?"$ka=]JM#t,H4Ɩ[e)(Ue<0n =(Z X հɴ^M\TR-mor;.aOg;?±׼YCe~{j5LI\4%cYtpYңzeM:G2_:CxuC#she/4 tE6sf|^#3`#t5_+~EDgΊ#yX:iV!ȫU+l2 d(7PD|oG@|^ -ɲHATH+_@;#q-&J 4ƛu;^SS݆ADbLjD6aAؐ儸\9eq"a-F hƇtL̀J0HEBcDȋK&&e;Hbׁ&A,[!#Fr3s?xmkT!US,Ev#D$Lp(# CRPILލops X͠w/<0K9eB슴 \[t@\ o܎$*ep-qF4+(QtyE .DQvI >9Lʻ^b嗦Hm䀂|^9{b62jmJUؔTڑhjЬ9"Ic8GF%eXIn3!kjŕMn6j?Vd{7gM * 0u!if+N1F&)mE=@j̜?+K'X0OM paLpoP̰#R=tH*. =+*ԍO#sg5G\i@H f"<ߩ>M@0J#%*ATFjm>mt+X`k 7>QL 81J(N@J]c$L+y6h49~w_ߐ\[vb Ԃ\0 !'EU -u!IUQ1$ZV^aV:+^ԥlo|0QDK@i4R!p "}dZjNcǡ˅~JyO0b U`# y2aP`̈(ԧ4 ;"wha2pnVBJ(`2h !\k 0Yb&煊ܯXGQ%Bq$`(/;1%jBFaLT *I1u%؎gn᣽腠-*SҎ)cqZ4J }%iI! DOZ&kVu!V4J k4k^oR\ٓo;`LU  DtހjΞ|IJ<(l1eQ}dl*J@d` X/[vM8"eCA%#O>E8뮣}ow]a\=Iz\$P`PzmꞫrU"7X}tT6{㢣<ːHAnBHy⋵܊4bO#X^H7%Y 8.\OQ $9C]~y"t &{cuT *~P@ە$E}tnc0{,\@(@HfiS$ +qH a7CnAJyZMX`/|Z|.gOL2+Bl+6 H! E Jݏ8l͂(7 _#{ݮMXP`1Ӕ2{>qJb4,Qpra  Aps:wMk8|lD@"b\^3|- B|Uz {Q_;IAaj )C5ޣWx}x_yߑU6=J/¨ЀX~RX+ ]PK^%ĈK(dh{6%\b1hOԂӹĖ17.ԃر..`bruG2:jRb,lm~5[LKu>=#[xBpөDOQMIMDiʪ19t)Ʃw%Lј+m^s4!x\յf%NT8fW!SM۞*G7w:Qq. Bo9 46OU/B؛iY_T]]빽M'>ˆ2 ]%7@\{j:`D8l,XN[]μ&2_J!:a+k Mfkruʣ)Be=-iUBV^hEAECmјNJ !xzk~`P_g #M ޲P ʈJQծ^C&"D㵐'y(`dC9Ȑ$^yV3!&J*PI(,^z-&3 z,@!r:4J. n@tS7 MK#2(oO%hGAm OxʺG-;΁h?486AJT\1 91~=,e(R%Po 0$X_W&`.߿Dt@X ӖZCkbA)B.̮ID HSC"'mmV!lާ*q*>} $C 9HkjQeA߈R3)H Ţm)A#KIbNp$$!Cj`r3wƝۛ&[/MŢNP2)'T<9V1L@6mN 0xhG:_׍GzOC&N>ӮڶtC3BLS0u:#$=];Wǣ(!tyuai *N\:;?$m8jYB !t أEm4{?K Yr$< B+%g*uir{WwV oիV"͙Z^[h2鱙"!o:rK!K4Bt&v%$"enqC ۰+:e!q$&RfJfKD#"Z8;jF٤)O(o.jv^Mس$&\ڪHJGeM5%~丆h u9Xpjε "K>h.=UG RC'u EouG5?xΫgwlx epPQڈ" ʓőG2I( pAwkM{?i;֞zwMn=bx%^sm읛*8cQA)"1Bk#z^0Rk $!:ckLvA|s%#fvQ=qTA1B~RĮXܕ/(֔awč-ENj39Ԅ6Z64뱪f'7H78AVQw؊:E1#@ې`.~[o91jXw]-ԙ!#nq~@P]` Z! g%/]%W'/~ im)h( {6H_XkLsٽbpMtFf HsNQոaB܀#A74?.L۲N(jt`z LMHDCMDQsClX kL J(Va A m9!hn)e{yGzA3m(iƆٝT ae }ɟPR&&tDYBV>Cd\:%&WÝ٭].-BQX 2ޭm{fhIoCm* ME+W{ó&DžU-SmTpç#zuZ ;.m7;%Fqu ]q jQcr#~e=ԭiJv)v|NkE2RYݒi}Ko.eȿ*y?V U'H#/6w_$݇ZA!]ͽx"藹]FkoWq%kS:O*\taPV4,Zm;oOקW=qjܖq2Z9~O'H`@=Lۛj࿦k|m1ȸ΁ `.=A`74TVNFU߾[s;\*wft3Hn70xŻWk/AE#11Qycm|ev?zg 6*<ŃmGVC˥sӊ}<_K|Fo8H'y?[c,Eӱ 5bl 0Ze5 B)/a vЄGqys1+ w|>ޝ{ows|:"nck;gNibvV?r(83%n M1.eJvͳL4 /(P⒝u sq vfl7Zi$xNòI\K(l8:<:,vƝ2 f?$^ 3|d!ab1 DLn!D؂\0w/`a~{|Gf؀:fd/l|sK~ x^Kë7AM~x=)fE&^ؒ:4{#FiX+:q2\y tЄyc**`yE L3 0oolq`j@trwD7%!Teq}Nbq&ˉ;<ݸRv^nPHw]9LJUu;Q|W5{6$ΕUjWj|q=A\{ QAY9)V1!yG/G+0N{я$A sԈ[hBqf6yd0XAm@.@ҽǫA@\Ġ.6jDqvtSmrLԭ!e< W-mĴ/1DR괓m6?{ۧu'~?[<|j4ܵy._[o_MkhGʟ%%G=w?XW!3B/VՌEX-|F8դe KaC3.'o3G ЏĆ}GIg$N ܊pYm3YrLph{&,kבKt1(Eg7=_r qG{jw#W>f~-z I:@9`f&Ru"{ q*؏muiZJ`QeAlُ `ZԚvfw!Vr*k <§ŐXcț?Bt5h?׀0Hj +So ,߽[~Cn\b>Ye:' Eeki9c6*ZP+TE}[Bk O)KIQ:VYUWZXR6"ӿPN)Bx'߲U>jjZʋV *״N}n%2=ج"vK'wEeMh`o^»6O&C[X-P|eRLt""/*jRw0u j֖ V'rV|)Ko㫌`bL }ϰ?Ե &"2FLq"\eSGdpf$ЮW̊͹jAh`!uGu7  Pw 2~".  f5-@uV򁥝;cS$Bύ  `YпOA?)*"m!҈[hW-E@Q%X<ؙA >M!t53F}U<3T.9/~ƣI+ɏTlm̵Y\?+tCX^~f39̾ƢMƣW]lK"*=5!ЏN6h Vf.UQի8TAc#zmgf-4 L.G ܗ{7rQuR@8) +X&y>0OpNXԳ4_ ,ޤ H"*=}XBa!n.oSO؇3F/*T}|y3h~9jWwMRW2-4TA}bӝY" JǬC|OgOrU '蓇ͨE1jꁬ*sBeL݋P0k 2- 2BR}_~r|o ܿ]cƾO 3#ӛucvDiYCqwLK@N-Eu#~8sSN7rV*Dbz8@ڱ@7*}@4 :CvWWg;:4&BHF.JQH*ٚ(F6F#'y2Ԣ!QBĨ~G!Bob+/ (⿋-eЊX %ŠV#( !d3OAA`r@HKCt^ IHy޸۾tUWuCĸ)4YLd bVQ@+X_L.=g|m _}2[F؂Q(F 1$AN #0F>>4,oagzBZW9ԕpKA@B )ΕQ) JMk~\ r`/]0.L_u]rRv9mE,'&ګ? -=4e"xmW=/mp$&EAVpMvMWeRca @ S]z |}_zd1@Zе:HX@p/ 8gI$p/uzxwH>-IjU+_ (;(3gJ4fmU3e Lr橀>^͸L}%ʀaP:g-ȯqNΛB/h+9#9}<j;nz͛VIr~Xq944Jyݢ%́0{8-UyƲܴ> `n:^!@a-Wb"#Ri@}^w\v*r*B~@> C&(E4bp 4&4. @v?& :8#<&t (tĠ`USG~Ki=/+4O *޶I)G m?},wJTgӚ@W@lMs9}_=?v9ذUeHD? !>7<gR< xbok ᅚ wSDi$d SCYy|zpNP;_3Rp JozAէ~LF aضz$ jno]{ƽ坑wV*y3} 6u(!*[3oC|d BXnζ7 GgtS;˪jLā"zSi) ) 7p~܊ȿk03+$KF#>^M>m \h0[*l.ߨ -%EiS4T8B^4dIё@g EiY@X Ơ€USk07:iJtge\ct_&rONcB Y\́.@1 aEsC.pdEDЀB愋3%(j#J,DfCEvK(4g4DSEQxQ ِi+PXϖTJ*}&pW蕏* m Ivb#Oj*\^??k k[QGL,v9lI͗VF8rw! C^Ex3b+Eq7(,g"B$4aDʖ1mk`8W0(߽GG+FVO=!VHDjB K`i:w($Y* R"`JN=[mBeleʛܶWDnӺ~ÉګH } RN +ȶH^~fZ` K"C@Ezh~秎 c/I@(L2 Fp'!>@y0(@@t'ྵ6 z5t zL32%#y e8PhiWRl{nр1; H`N[No, ~..P -C _(u \T#vR?kdꘕ=N9U?F 6;Ay^|1 i3p#|ofFJQ>$7jdwxHR| aߚM+-F +~%΂zPaYzBB%bk'pD %p^ H= g(Wذ+SV6<=Ȓʗt$FLb#p5H42 P" S榺FdZtəwԇѻ;v]?iMwO&g7Y_mts-vⵢ՟6t.~3su󝶟z(q.=_8 WԤ3izD\L yNΏٿ;F19 d=!CVMew*.L2"oZĘy B>[pUHRiңp*xL0 pGt!aBC*L:ا¾F&?55F߱uƀ#)~C7$ X5ۊpAx{Dt"Ά#BUH_Lo22*UFtŻ#OIr \L>yB{FQֲİXeKܪG,Au*; 48Rv)LR4 \UXK~rZFrڴl]V=zmggcĚ.FL2p$wi*X8o5h:2il{u%a)O{%OrX(DJ7v=A-HtȢ57rK'TJEX"l` x8aKI{N1h 7ƾ|F"uvj%p5EbEVH%Tt7h _p ӪyxB|HWu_1rڽ\sV&(q_g I.[iދ1lC P0ξTg O7dBlɹ|U͙iU8.HZ."oN)r| A.s{{@5P}J =/wNr>b6Caf#Xn]F]U'N}8,; .വ)H (Lw  H4BR$xsަG̴AO兀0@ݠm{x1Hdf5҄f Hhe2{B׫0d$jN7ꍯ☞~}" EHS/F:{ḙʹ'KQSqmj}9i ]\H.[cp^p2HɪCNƵy쪼#@/XٖxHpduj7?*>/6+(aJY,qo{$1KkݖERݿz*qN-N (L0$HA2kA܈a+>V/@Npp; $1!v4h))-oGQϩMx7{H*ؤAJ<3(^:22=G_VenhL]fD$)4ph%9RY;'+u~Z}SLݠIv(؞L 7HFԽB>mFUQ .Ӣc&^/kx[џi< >cӜli#ɦTqXjžYn&GtA!>LBBL .ȁ&:H˶_ۃŽ;林9f2±i0'(5DhYKAFp (jѕ++Pr{D S"&fEjHa>EaAٟHgH> Qy6[aaWWVP5mXYO kȈUH5ϋx:99B5UQZ!6qpDcHض6&)h daŮ袜z=3 ԰ן!L@e7(_߫n)/jjRO{`9-7"” 9Bou[ 3V6DjV U'rydE6K> '_AH G{Otw)0xՐs2ˑ+x] {Ĺ둬sZKi* [ `套`o:2f|p4> F1;/ʂ5ʓF9Qģ[Av+pf A$-ZR7' 6D u`,=OV}>gAr|Q(Q]) $%9)Dc-<_DpT8v! 8[?<@WWLi%rկX HUy(kr8 BD7+`VvA J7g˚Nغ5VÊh1U2-ulټ9<,-3O\v{ܬVwƻCBf$i$)loΨr.lGh0 B`,Cׄ11040$Tn PPܚвt\v~+?2⻰N—"C;Tp݊pmh@{ a0Z: ,z{<ӁpQ紜c殃 Ed܀@ Q%Iѧ0^g=K=ͩhbF!OA>T6}VUW%w-4OcXf%Y1a(?&.by@aWTd[v,z^,8>N"&6caK³&H)s Q (Jp*c=װet󡘝 Ak58vF ´&B {5[bT&B٧~oAH~rsWHR;mJgҚH^!g%jQɂke'G#*WpTVе^Z~Hu)h!@ !ˈHGzg'k{)btz\ %W$!,RrTA) \ʈռJ)̱ﲓ6MZg]P Ji3ǔEU-E7lBi/Q<)Pmm]d`meJ W*wS!B ߿i[HjOw~-  Gh\}-`L֡|!ECK)ǞyN[e!A2Fڣu@ "\M@\hS06zsP^xˀE7r+ (x_%ʓ4G7+NhV1ZAqJDlR, kl=$̀]٨3DiQBk{(RmAym ~n=QWI}!!> Qjq=W?ӲWǥ~wх>k"@ 8 ,@H 8g^eKl1G$mvQG׭{՜}xMf /\& yf;l֏=ep ozj8 pS]d<(@*HD:ȁ%!rZ)G2)8?*PWV ~e4Ij _Ă2 v62P Lm=n#{ٳSnE#Dѭ)flSEIIXMd7nڡuO"e43I"8h656 {J(!qRh 4.!\V:ӗh\E݅ÊV<:K 9j'EM)f^@eV>I;Y3Һ6t4=NYo/lǵ ;ͥ^z\YBp3FgXbD" U&Ფ1lH k@0GbZ`CsLƲL^o~0`35Er,mzFmnVI tZD(*$Z+vùoٷ* Gr.()%ڦtlXMjE*4Qpq҅猎EĈ&)o8#qd2Q^KvjF FK&䉡z_#n~(|F+ X3D$/ W9 ə'ړUpܼtR2~P^+$h@'#y{hVT&DF,_>w;lnEWݎFDƲbU5L]-KzlX1P ,qj|Dt!ձTbBSG`=!Z./]FX&~ Y@z`Adb)%15e!rқu8HHԧC YҌٻYTe(|ziOC.RXȷxKHVĀ}ᚇS <V"Q ?C-|e,=h~jtRW8xEB&+8o_joӒgy6$0پUȶ-ڒ;Q,X (u(:vEQM*H69!To ")N f#zKo3e/6w~*2-@rg[d~A]!pt{k%8Eza L/զ׏WbPL#Q%@5}7)@[Œ148`?{pwi_X.=pßmI Y8sPBd/mEjt}i0~"BG{nnLP o9cZ^nj0s~MdEmgmH)WkA6|E@%fLƨM*)| "nH+^1@`(kΖ 6O4ujK mjdÓ:.(kbY1 !UJ%N^&q眔l>d•/HGyO ɏ| D\R߬ 7O9(<?<&{YYSv9c7硆oODE->u}o[b.'#)-XwV|r';G\U6Vs:w*VVPfv폮twq?^pU?Qf!O\]k[n;L/u|tVpŞ k.uu/ؽv+~$)Ka̛'u٥U(e5EC*r/!i;CWB 5[ ՎwmvE@My6kc0[)C/$+nbU F7dG_ETߧj*/ :UTߓۻg8~y0˩M~q\{}ùb3dSOi%f%dI#F-}ѽQ|q0^”~5s ey è¨Y ]2O7l||KFhq %y{ e$'/aI1lhvmrf\^'խ~eSOk~s|X}_P~꼆ь͏qH CQVw+?ѧ.z{✉{kN|芬\F:Zˬ-|BR8,9f+r; 1|-/ R#<໑UEۯ4sߛcOZ\eEamv1O{Ϝ?*"%04| H:X#"m\CY3m!';L߄e,4$i-3H֓08!xUi&e*( Gw(,$ 5i–Dm:>|:NnL *(e >v.Y:+oe#,߾6{<92]-Z`ؐԈw#C5z`oL$uPCΥ$ 5 H0 Tw;ʿQ%2 )+/ELJ́"` aDY1яʰ@) -AE:,9k}:6ܴԆ!C]PVIH,1~hW҈6+8ost$dzaiDreP8PɽqGܾ`ε`*V*izR4S Av>@o+Ww5 fV?*n.2V5ǘ|"k9=y_`Sy4gc[ r:p/\ރ or-7 PF`)T:$j(ٽ(خH3lFooӜ=jŵcTBJKkFRV[kU&͒}:JhͲ&3at)=␑ 6 jR+Q%,$%􏋣4k} 1ZQ O:8c".=1|3fFe S" YξY$^)nŊ(h=mre敞PoZm ?}uS긥ZLȺqb:*6ƾ_q.tㆋC#U6]W'kO}9ΐ]\9=@k-#N繯gl]n>xS_A8bk+MBXnz}ljoĪ.$T];-]WlcXȁ{S# )k;}qo[C,v*hhS[TuAٙshqM{]5*es(Yfk}ȫYexOZUpQpW C.nژ FMY\.6..'siVu5LĶb+"ۚ`@9E V)Ԛ[TԄ4yp XrCR4/o8 `I6观 KV%v wS\e5nd^dp"tQүmpo#|>—{RH._ _KS~mOOmŠUݶvP=*' bi8Vмp\4A}= `/8,4\|8Mw ȶ_~ \cel;ϿoA'Oӄo?LekMyULP$}䙈$ 0({!Oydph=F`p.3AB!vc\d"0΢^!!$*b0 P xǒ9~~ `ZiAA%!ҷ?@QP/ `A$ Z ,xѯHߥTX3hC"4BK,fH /lǎޜ*9 e:B%"hdZL1H?sMhe&@HH(gy6Q{ֿZSJ%bRN+ 8tVD-M8#hoX{9byWUNTx2JJR2x^FiCBz\C+}ܛIv_4;ģf_|3!{kCsd4phٿ|tlv`dCV& #`HUFKҔԿ%rtU?Z`Xt,:kK>]ʛawYOOr_&^H!lbo fqkc%6t 4Jnjʕ9:'#E*fXCߔ{gJj7 o۸!ҷҍ sƛ/:^8^YIkGf'5~+Ox#wb|kswy Y?}䞀B6zٮHy)g5h O TU~R3c˂`س@k,)YT9( [Po6>8_*c8>5&\3XGxD%e;0$bN31N=WFGo(Z *}'=68յe=)ا#"R$, Uӷf(:z^L~F>qW_سr-N h`O[+[\!_݀sŸz˯W>G DNy_H%83}5xht.QCT70Ѐ F^)F@9 ~S\g;gmD-(B@'oRW Bv1 Ȅ_y`w $DaH$Mw=@vsD=bE(8e/bT x+CP4 t&f`݈B[R9ïk$(Z:DNI.n}"6IF$Dx P+ έ, VA )uq c 49~ !bkY&[2jd;1F@h Ӏ2L^z@i!1khM@( a\ZûkP Ͱ~kõ zt.ʣfH]u|9:p4W7*eT{'$L[4I ɀt!PɌpV|q)Gۿc$ j 3`5BQFO-}FaR?u6Wp\/(;N/jA+vp6"Hfàhl ba#T:ba'N˷Mo?-x<WWN,$do(]{k_D;YBM7 ^ [ʹpԄ^t\84Fq>^RKFp#LQ\2?!BD4~]R7&R" 52uj Y_B&_Wy^nY3Ԯ ߀3,b.é 4}7 [9['ryb$1.4fN:jW5fu%L&~ UApq,A}8{9*(4zoTF1 56$K~77^S"1eH 'f {B *i?zEǛÚNu:o5GؒQaKS1Prۄ;]z=! ]s]bo 5ASV[)\Ϫ skrwn':l;9]*'g‘ܝ/݀%C޵Ezd}j9=OX*puHg8kY)cOn- } K$GFmv'l3l*N`pFB>_fOKJO8ikk>m!@]=m59ǹ Rܝ{{3z*RMH Vc(갷\@`%b!l1+d͢daÔ YLvM¤"@<\^0'/&R(a]ϕ#VÎʆZr sV`Dۢ _Z(xkr+'}S{槦g!QU-+<|3waLW&.,( A|P>0qFǹ9 Tjg`nV֋T#q1 W\{h, ai\^2ֻ%ִpM;f{X)wS^~z?tBﷶz>jNbIM@z P6JK']`GOUǙqvnqЎ va!2I&0$s'J^Z P45iD .j Ԁ&3]dqZ}tӄuFO7Prs'ņ/52<`, HlT7A B"r3h0;%k ?X@XH>"G'P-jKu +Yy% 85w]Gn:**A%|s % ^鼺2VIl a"&+-\yjv-Dn !Z iа"@ΛX-ކ'ee*:bJ_`.Nч3{?kڸz7;0 i u|DS}~Y,cwH{p\zAFVnS7^f ǴPһU@ f:pVIiqoNncv )G>z UxR?gf7c_`cdty+%GlC4aKBݱ-| X ; YHgl2'&BN$Gv$I#4I<ĭ_g?#++ +Ybq5,f(@&X=)Lq{Q?^C%xojNE-!ʪ.c4K {+mx6tRX0ACEM<]EM|`X_ޠ{ڴ{>Cvs&VnІ_VܥRX6&Nΰɲ4sv;Fe6 L8jײt5܅a '{^AȞ,du-1r 1T^o٭(6 Bj on-5-zWX*$K#m ~z:V#Z:> пM9!%0Y9GoNd8@@bP,! wf'ؚX 5j ͭ,>,{h C! ;k,X  ވl6?:Ҭǿfuy Fg o|Qϩ1t25r@=\ ,f`-Ԭ2/2TO0> w%4T #uvVaj-IA@h'cVN=)R & T(%.D¥'Ma=`~"͙) C0> 2 AU!&>H9Z(BaCHelByCXAj%2QQHM3@ܱ !x6$6Dz;Gd"Ā|6Gݰ !@#S;i9/Ax=[i"6i[)\A=GI:8|2,DMmQmBF&Pz,s E OD` Gt2H8łOtTxpam m!t0ntVif[-[deY?+TO{h.HiFߤ^ޏ‹~O`C!f w6~/vo~zNaNS tf[F˻şwĉSDg2X?B  a`ϑeN DŽf ֳNUë7;v`_3?ǭ-ЍI)*ο~q`RW ƋgfKt  H~  `VW&  !,tMtTu*R_$f`3Hv߾0>؈I51*f-dؠzδir| {5gU_5V[_Ʒ ]ˆۼö+>rQ_tÛon_򘃳ߓ_>"'tY?2̑oXgT9Խe8yqㅈsBɳ aGѨb&#BQף9$/Gz1B#~nG Q,ju!w`ĩ=h9yr;O\bm 0?iGW־ oqlyPx-WxK=_ۦ.Ws9آSi _c[ 8N[fc;x0kMN:3+3Wx{e1{ުY GSёC& b,a=F:y.ɟO(.[5FF?81lv5Mz<M^Br-*dnWŴ*{p Jj\jІj:,Z-`! m5uެd HʺִZ@"MB턘8Owf9kKҨ,}uZ7pQ1&m 4۵mokوКZqe;_\=T8mT_f[Yž.s:?O9fºvhd5,Щ@{]k QǼEJ2-2!(L1 uX l´ 'hLU-0J?Ez}BdmB`y~@bIL̽/ϧ޳ 枘3?FUBϫ>C~xa:*܀NbS@_ "*aJgÂȗFl!@l )8@2~J{4K iDBaU ,$Un(B@YT,rrH+՞ܼ PKEP=!pV+4Bt-N,JQstP B?Bn\پ`QÎSީiS!j݊ʀXb 53) 0! ^MOY%x.侚{-8 pBj!6BbCZQ|o{<#@иץ;R+^C^ 1cApF$ \n_w7Qh hEÈT0uD Ɩa 5?<Ę@ҙ8#"6U,]= ]Am kC N\G_xuēȖWzl)vOHlᄩURsx[ EQ X!)*a"\*M"7E*Kbs  +*m'os~@XP,`wF3V-LءGa4r$wBSGpppH!-W7U Vr±be1mu00B Ql XADV5J`"Mؿ0:m!o/*=5`TkPo~9ɡC™1 `./"䂐U+4p֛/*Pp$V[ۜ*fA4`=w[^O@׋<53\3مY+׍|  "a G?P $].$|$EJ\ڼ  muYk\b>kZ  Yc/am))C0jߐq$a 淛ޛsPa.:eih q`EDZ_D,Ӝ A> tO 9Ct ;k&% ibo+;ŵ_g4tԴ*afL2,/0,+S̪rS8&. 7( ۄ )4y{ܩA3/AF)r;˴FZ x; SCMT:Ni><}J]g< DY@SP]ɔ:4j _1ЭdĪܒW#š-Bo6:15miCP}sjV -?ӀOqI|Ni&Y>/4Z;=yV+ܞʹo-C *)TRy"\|2$>D(Y<0-=66D˩W4sֈ4+UX9_r-VBheJ-  fLqDf&h+v-`@BQPJGB,_l(_6V]UStBXo&Mgf{{8oLSnkPW%$r߾bQbpB!W_-#my,pa_>'||W1T,j~Y^\9 ݗP1G~Ns P=5C}?}-_T>D]>'Rp> ]3jJEi)=Fxg~LI|L3ūj!HskhoƒF>UۖmV6+o; *L4)D.Hz\ΛUi>o4D k`AR:2T-Um%M1{]XeO:v{:n=Uᚭh͆S(I/ K(.Hhh"jK}O  !z}l.H^ ^4/83 7vg&)K+MQGzpH XE To LxKp$Q}b  ; -NbLąϟoÀ,\<H9\A  4(dN6f" I/ R "NDBD7vHyGe+b/yT-zˣ#8C,PLD:Db=D?̄DXPmB Trl~ >]4lT|\vihDFZϱ@5\"X#Gg)ADJy1泰Z07bD h ۅo6ԍUCƝ'-9mc`@Q#yGH3X!LcŒj# ƥG޼j[,Dɺ6H'[zYM%JD 9+f{P<0~I ,.;{ Mk:MFrV/BwRhdGP0SY\VY^W{bNbGlz学dx_CÒؚjp,Oi% X+4BxVm fTo)ˠbCBUU+ g-S-5Z+n6V|=-֐>J۸Oߍg7$d@d Z@;ҫmޥ9wѩZs]򩚲q@ 1&F@jzBUw-汙_;̹+xE\r} Q~>Iq?M;:ϾtT:ekճNX #WYezPY)Hrjs%/gV6t^'n4N,FȵoH 5Xh>"&HNGo٬:1Tع<մr[Çp29 ,m2rFNzGÇ2ڏ޲:^'vR RiUkR5pnDҌt&̂1^e+zڒwDPK3bhZ(`-Y(iñ Kch3 "2 2PL X<*2*r[AW i,a@TZ*iMi) rK#KGV_o̴+ (v' y_;E "%~sJ@={#u.R73Wl)8W+rcVnjJS ԢQE,pf)lHkSZ߬CJbtYevx <A!< "! MHnJaQۄph)F<^0O0&j]؛={KuVh [o1[K (jAź/;~yH6~֢W;&.8Y/ SLcJj,Syu+΂ g2RbILxFN `#ksnG{>fqMz 0_9 -IQX-LU1|Bhi\xm©9ީDK6ɧ&wSXefZs;{'Uˠga^ Ӧ@)ZK˷%Mj ۬L˜u!8J~fYJm+c2EF7=K:Eor~K* 6()H& 6Y|1pBŅ. H)UDD^)zqX_?k-j\絧mҪ ~[ewpYɰ1UGZRݖy!? xP~~#/|7M8t^Y @?!ݤ0};EIB-'$bSȏˤbNa']9wG,k 7'q7+aZ?"H`Y }:bt-5`ϑFCl!ESm'VK?\Sj0 24ZQl+: A*Un)s]J-3SĵˍǏJGj%Y)t!"b4e3FwutoSDLmQ H\Dl7ёtjiLJI(hxX.:붌 tJwju)n@Tn䖵_PvKÆJΒ,7#j8e5M PP* U$U8ςHL`o s3$yhL޵a8*hkޒʩӶ<akY^8J/sQm1c ," Vo|Nr GH?B *Zheu @d4:`z4ER4TxRt&ŕəYBްŦF`qvZ\L]љn p)ox ܏` C8\A(!StʥXm)]enJ Ґf apE+G!kAajy=b)J)4b>aPMBkzˁF&ZQX ʃ\ q>ĉ3i)$ W'Q ! M 5Qr=Uk{c'!ǯ9<U LJ䌯ZS: $ &4ӵn\,?uɵ+;8DJQ4a4t5H$'E˩?FERRz$#Q4$Oa F7A.|~_oMJMzH/'!}*f#+/X iwzQZ%"\$qL0 vE@4xZyql DX ŝL@kz\\~j.k.SeߺVmHDHID}X:5ʝ꫏p>ǭMqOψ9|ie7\[k%{\/+_P){;M໗jKe[zε;rWo b"e*̜K7~(@CrnSYZ׉n@*bm~92 XtH +t,2 +B 0d  " 4 ԵcTALDA-!*`eY \8rA!O5BWU3!$SvE6[(XCׅ)O:gHew#)~@(Ue)773O|=Z>34ql0)x7Ɣoo]t} kI+.C\,,R|xH>U+^XX9`O8)b +ZU_F3"ݎpdF-%QÐ5( bh];Rjn$`dPqÂ#Ue\ʻc>Mm]Zbd#Zݴly '9g08 yPTy"OJo ǐqu[; mJh;@sc֊E)l wʁx 5T>EQ!ugZ.LHQ"tUu͵M@@هs3waH`0#N C$X&tvDFcMfKRHv˜#0!ؿ32 KYHP4_N[B%¥vGjq.EDX4#ï~w(y@p \fd%舸d^tC3Ҭ) 4 6VoGx`1xߵȈ.^fqR[<B' 1AuB嶱݃FuD ^cBlheHyp6@(x$ W>26n 2FS"!bQxtƅ5[r8*zߣ6_;oFƅ'Ђ)tKj0B[eцc P X핯=z- #c)xvIP~ȲY\V'JalL\1m緝(6!ypǰ[uIlm!F }"U{2f{~wRJF=r|hL &Jܚ,[9`|{#vWndJS9^Xlut9*!.A`80T> .,`#`,], |y|ɦ&1m'WKpi~&jm0"0ppNߵ3!EвhE_3{iu8opK! _n MCBb~!G{K|Y TT8[󫆁jm [Z$ N/  2vݺWO(ŋۨlt{n:7{S^NaRK AW\'իիbKжd>M V whY `iEl;IxetwY{_\~}C &(`-sVp#1ke/Z .7^ݪ@`r#phﯿv\Yc`d\RX&٥8qQ ۠- y=`>lShU*؋] (<ٺ5/yERm;~ 6>Q0JI~ DVT>qݽA; ~ׇT񊝚pBF4\sy.P0b nwMs|!yx@ז5>;}NY%=Q֥-) :'ZL1afbW;lP݅IC[s\o_t7 3F)W!u/E}羸Vl"QAIQyVVûF%j4pԲ%մ b?Vgh0":2fUIH*K&`ґһU!:hљHC6k4cW4Q `*(zk?~zˍG̷噧z;`~G?PYNΟ.y`oA>﮿wjRc5&TW\ɩα~O)b-[kYiEYUYάOd=Pgx];6e1,@sp4 +Mk2J/U]k$pQȝ FH|u|uoo4*b#Dy Hfq4޷/1]9<6,Aƫӷ(=H!Yoo4TTfa9x7q,Z_oq)`Voc%FzXeh@2FxsZq*ܮo v`٭ t 8@b&SR P7UFۥ5,FtZ91XusNCkygUsXSygm)Twк49LKs뇶~L 6LEBΒqٵ[:ޟX8@EcsOգ |^*g:51̶aI4u(O^Q svoח5Os d qɂ͊~YAt`Ub!Ϟܕq.Q4Ԋm~ץ.v#%EOkl 0h?T|DQ4O RȚ02VB^t`/@C^@2<'GV5-(%N[BR#BdJCԗ&` g?/5 1 o ?WlLY\7]t)Dn8 oOjJΜb6mq a<죸[ u$QZ աP[?:Z Du"g8OB׸hd#<[]Q? Ǯm1> fkPqz<`dZ{ҟ韽D+736ð uO|i[ÓO:TRMv{ŽY':Z;(`p0}.hQi#}V#r9+$I=c>(mޮ }7W  ȄȂ&Bni.RVA|3a!y}c/^ L`k]jSzn;ezçngbҐ2*SEeXC 5 (&4mW9-SdrDJy9Sݢ~17}:jp+˯ j[WDMO>&deֻr~PLedaϑ>t&QQ(0!Q 0+Eu]==SuRZMUAth3\H "ab B ஏߩh wެNt*}F$pKuo>PJ 7?cտQ]9ö9V|V|B:}j0L@h ֗^x7.y)b CFa@V :ʉNJlDb~g%8uˮƃͲew*@6X{Pԃ+z9yFPŁ(x0^7絹_E`8H2\Ç ={(OkuiMrSc UH=kdzV{n[L[lN${T<0vC~sϴX[z jY` 0BJi,+谌\QWE:ĬץL%HZ,_40)a ,K ѠK 6Jek]W꺫c> _iOOn}orE֩s ꃚ[1`~"G~_;8O}?;o{SV8/[zZZPN:첟_s$5wNK4 _n脕d}iԴ2쮭ީpɰP X . X[a !YMytAyKU,?c(QOjZɘ#-Jާ(]ގ=H݉;$~/;O%m8=ڌzY\%k R,SьqzڞQTI২HycjȬSݽN ]\#AG$偣Qw >4Mp6i|Sܮ?+Ÿ/l dfx˲q>sb^Ls>xWS(菻i'FZ"ǡ~#&gZ=>;!, YxUo?M@MoOِ-7$%C5mB(@91r)ȍk0iRGᔌnг//|Ҷ[O`Ի ᖍuƕ}=hL2myf\Ut4Uo / ZѨr,W\4KR|lC|ܪЌ:.W }ʭ\Շ9SH^VJ *ry4G~lTU!% D4ٰu ȼ^nQuZIn:.dvN_Uޠ^2ZБ.I۾#k(V~_9{Ž;n ,ibUeyZ/FǕŠFn/{}̎6G1$ :YJД+ & $2}WELJl4La.m:%ߑnx#֑y2s"taIJm7ሲ XFNo}I VQ Y X30sF`YqY1y*b)0rqsR%:7Bu%|o߱ΫQY6>wW|12G?[.TPIi4[~$}xiggL79`˔&]y#/k?KAUpy)ޞE+*7j&C7&WCGui(tcS>!\3!cG5i)t)-󌚭v.Xۺ3Ư}P;"E@HB>bP>l@")wõ-]`Cl֝^, ᇣG0Jµ*v ҹvIE, ًw?5qͶۋ͏. &-uAm?7޼HȾXWsK?潵te@0J(lZc}2h=pVUf*) yF~VPۙl9O>sW8NRHVW QK( C5@,6nOwƶzd:7h^ا9 SH6cD>5㟍p:_:3Fý4ߜ>}y|%G9?qq򅆋^̻۫Nocu7DՅ8%:8n@+"1hC|- ,f!%P8'!wH>ub6\KDΆI"xBD2-GaSmGRƋz!GoLp'[a/{~}^{O=@no6zrQ})CqjmPpiwFI%(-wX ݚYnwYUo!3)l嬚MHPEG8x&85(e4T/ޕXgsgݮŬDd;W@gYbL:(A*(K"A1p{T|7WjWdq.¬qĺ]^/U#Dj1cEDǨ<&jָTcT*:atj|?DV$)']yӝ|y +?>aϻ]vg2sX[VllAF!tglШTNtЋzO}ͽC8 ʗYᅠ+*@C \3&23&/4 ,ȕ6T?iʝ^vVv]Md1YB.eu*jYj*{_$ucQR *aunj?߱RW RPB9 rW]_䩏[sT RBe'Brff9M4 l\7Ե]RNc[7l\ZQOl'i1V FyCwxOQ_B m"S0d[} /05װ!ts(1i'&B /$g߾70?VzTh/rBI- a;OvndFwܚu{/׻j$$C]n&.Z' d@a1Z?&r;4(Êӑ,UGmAT"|M3ELzM(emOㅣ\4ǠQ!2I#W;b1͢O/&:JkdHs:L(DS=B ʼ{A~hqrECurVW=6'#D k' no?OprrJLElqjqRi@Anzuà1q""Bd>|pBd 2<=N>4*ްPn`If(XX6V2F؎d*+ ̸hѢoYO?|ayٗ(#nV_cƷYa@bUA &pa"+80L#>z(}!l ‹IigP܉k]d^Z7M/i!Ys"0u?ѻ;G/LX/pGUǟKmv"~ |,6޸Rcm77@ǁ"ߕr1؋E[8X$3]1b;W|BJPpExzgGqI`w >#҅Kxfmj̝/B1)})9`ȟq ۚ收<˗ \緖/ۤ?ڣl{G ux#Si8*ZXW58D`c Ch_cG*=P $6APa" a$AzQN=EM{NdS#EFx@dToG9z?1gL?kִTN1,h|M4gWDlJ@P)FR gX&zJ)( `aR P(S!Cl=1ʷcD˯~/KDw4c|rvv, 0 Aք#Fˤ4LD$PvPBЄ7;mT cՈ0%0q '"M˓O\n&H{\L05Д:# d0P=l)hlY:q Yajp;XFRQ*>UlY&gh<=E 5%<fOfVQCCTHfB0CFa3(A vabP^%Dga^pߕȝ]ݟ^tԡݾLZb "\O{,g=ʧuz>[N*r >~i k`;U4{g=~ɧ]9ū帢& -{H9酔ޞNv??e:9p၀Y&P>`M[R,I3 0jrp+mJ1Ea*Q%r*N8¢2) V8G 0E,zcSJ)( 5rky -0Rbnq`+#B{@o pqTΙ _7Y ܅HZ9T<[L>@,'cnZ pbrezsW{fĩqTUtC)QHb`D.p[cp2i2^1 B@9Nig[Rr." O@O?b_]2XȈX xOYCRJ$V&Joݴ67#䌯O"j}0=wvJ}hn)lx+ϝ^8 WKYWuПI̽JUGdʛwBaTIH QNR(o>1@ 6 Wq8$N!, ^V DZA.P\nd 镺TV6viT#MJv <1ºW=nmq!»RBU ݀ɲPBHV `;`N.|yjQ0c<40: y72rQxo7E۞¼ˆ@kgNKVBD*K,JdUDB}Ⱦ/OB,a-vMV& %7Ek 8h I $D4br[\rS'f S 6Ab tѣ:SLeŗW_=ur(m%|D $J7.믧{7//&!}Z.n D"04z֢ͪNL(T+>A(,g`D@oÕQ. qHNxbF %Ό𬨛p%H2M&[B _"qEUf^ՍQۗ}ze/UflqM|1ST6119uB3蠆HlY#Pj7Jg>+ޯCQ1O ݒ2.@a#"ҠDH@fe@QYDfHG0[)-1T@e ~/ KC¤f)YbVbr@B&[.p֧CuQU1Q'^i |[o_ Ά_gB.g # r+* |??xp]NI,k(.[f6C3# `/K&gr}]prb;/8;mM/ F%QCj;bq|3=)[<|s]o_nyԄ(O&-Oc>ɻt>ouDVDO6kJZujqQ]g;ɥ@=4eYg]D#C "Arrb3אN*M~p'AҡIԄti]\Ԏ BaڙDUU~IKX,.֋!v]%U<+|g!k "aaњ+$܆*`,x RsY>߀,n]A){x1CCJ !sHW`TT. M\(ko}_$"U{0qǚ9|VEɧsx״o`]b.3|r`f^__ EfE l8n$qY~9͈؀VAGE*6 Q BLA:{@WFޥRk"vC .[~K_{L臸?Ri_$R@٨s8h42\jFՀnsG4[%RnbrPU*zU`',01P"W,P]C#,_TH*!SL`W$Y<-g-֜0g2ڿTs:r?q:' g{c@q{mc`o}L _6?b"?\ZS` 4Kjiecx|g| qwz؆q&ܵszb@R=;- x7-!cH^iUpРqSւ U ,!SJIBK5-ɹ 8Z'g4+R( y"9%֪˾: weQ4P˹4̰,kbw}_LLH;n*۝Ka֯;;gqN BEoz u5!Fa JI}JMk.#޳~s]"VE x[AZ ;r'"we23_mP$ZhqlxRJ*UTj![kqA,ZoY[tM^#9X/tHtZJ[%T'P6?X>NbTX(H RU'օapgžy(e(qa,Ϋ3MHCAَ^Yx%b nH*q}c¢ц\(y 9i7Aa$pY __I$b~[iuC YM"H* LxJ00<,'h:jJH٩}e$Y)B-U|OۯA2ޡ8O1YV_yoV gGL JE5{cȢ7hݷuX}´sR@o688\ PL$ s\S7cA8;eLɃ9[I.;ic!Mq6,vUr;ˍr|W\ ٟ  5ٟ|4Rrje 'r{Yj׷dS˖Ìuzڥt9f(Hǔ$CQ`!% I$R@ @#f*ўE?kO_qnU3z)reYхPupR}R1xg . H4;GbR;5a mSڵYlm24uiW'VK`%KQ6Y! y܎4rC#Y)˜mM R L!# K1\зo?vfQuuB&xB1LB1Îs9xގPRtegP, *0C!Nk/Yd^ dRZG!,fƷ(|7)5?eIpqJsBzbL^Ե@XQXDVA*tcw]ނӴWjT#GUrD2ZUU^*N6db{]Xe;P+F*[#| Wn{͒b1h0C!!ȥ1p{jK+1O(nAJ)]sF+y? sJZMب4C$(y)"ܯeP/Vkq~84!f0tN1g)dmɜP$FH Qr=$dUIuZ/d`}"3|Ю Ot/) _36ϟ9_Ѥ]r njhH U]yB @> rF Uk&x`U hi„ @jylٖF( +Pbi$D4 ;Wq=caxo6tKs!8d1N` OLVU*<#|`&JA$e0[x9A96џ|:LpRD1H4I` ^4ĶFd) Rj ~,[ʔ6|e/Ƚd4KY%5^B s<4ͤabQp+Xׯ\BD!Bs͘osvv%"(ղ`7. zgٖ7Y[djʖ_T~t2}jGGɛWX&ꬫBE{'>5BqX`!uE5'4XWX2*>bE#Y@9$+KAiJ1>?FzVN@dFGFUǂ% ?R]p.x.;ݿ{j[LagR3l-mz,%րҥjI# wHen];h;: &j :dE*od %UTk'KZ!A߆Ѳb"um~ 艇6E鸸@!ccx( ea9+F^aJ.:IL8wdz\SBz3r]7GlqK[*~S]bj 2¹ `!$O "V}iF.N;2ˣD G;#te0 ,7:[;ive:"'ye ^t *cB;!bV<09d{;e+ 9MPV9Я$p%`pt!@ F1ԯoIr6 @HV]CP@I-Btr00:FF 3:{6nM όJE}z=ޘM_c r#'gAs|CdQ *uLlNa> (؞RcTډ*=Y%a }XFE/Ah-c S?Oǜk۰^Wծ]IJDQBd}$HJJBR#LpDהJL*rsbЊ$nkc}HG3uK%+V,ףKsVZ|2xPʗЦ:aTtcv. x>2<| >C,Q5I3)|{x_O~#7ߪ3bSw3gN.Ʋ*9ҥ]ʩتu#JېrHRqS4\y1>']j( /6@3`yW)BwGC6-4oXĊ %8Y̤WnY6™,<1&?~ g 5/juȧyY4RٲvGHZNKVqHoY[.{b,~ Y$gtt.W>*T" \` }_?Tx'=}>m-cgԭä iggx/_C70V5JQ5jl~[xM\h,D,(^>qҍ!sQح[&[F" ĔKcx2knt\K* {"hlN6,d|f!mZL!pJ~)M+ŝr"Ak1LS;2"It"TbB !s Y8 +C#Z5{ieÇt9,H UPM85D,Iɜ`[) aQˊHiB*h*-"6a9dpV cTyS)ӝ;XlӨqb^yRݸ"}BhttzBUȗ3pD(Q`o^liF 1 ?M7FU}cu&tVqS`fPs}Aooz}c3@Sܨoy42 B}fv #f݆ƫƢ7tY-PW3UFGw)GO<@+49 1pĖ8]=+Osrp΢F,<7p*y4芢(ze205zs<[Hʻs,H A ̌M+UMm 2ͬ~6R^NEv< t7 >-!Z 0*TC pP&ȃ}L|h0"=*߾8&2y= ){d/X~wgnPeo;席dWQ %dri)鮭y,Cq8.t7F_ Dם8mT>aC{uxc㯒:adHc§B\CQSrY-@A|ŠRM$ w|LY:";8Gglm 0N`` HKjwl]ɰ5L ޶`hB\i޼8]ѧVx±۸o{1y#fVXh#CfPRW-uJ'e"ΈI8B*m򂊴pͿmbw+UǞ43]7F<}ŋUu&f0èن8i{sCX T֍{@G~_hٿ KGgڶũHo.2gN4?""ZwcРBˢJg 5lD).+U1hZ}lWf6Cd`ğ|薭GFY B39-I %% 1f"P)%D):SFg)QC`~q@-}РٶꪅQK'iJ05S/祓B'J)f(XDp6EvW7͢T[s\6EC-+БeQ8T1R-T b]:#Famih^{`dMJGIHX !ZBY0)LfFa]UW&]z2Zq&F=ao*;Pt\Gi4K)Z%prW9M97SxP;eY[4&V? Ό4.EMh^Zl;<^/a 5Ś"hk7M\BS#9Wjf SVuLMLDV:X-)u~%[ۑjkSR3̱-ȂT 18*`1| 4 ll=&0W8wWx>4@ ѥvJ{:[E&TD m09 Y%>apblXn6߈):%D gVNn -:R|UWR;HI3<$ڄ1STO4Gtj >S_Ǭ,uoi{9!fȒGIK2Y3V_yC|RViv_njt5piN*icDK7M8jh/[hC2VAHRd(mpu:!݄~V,%ǧ)Lp{q/Xʐ4^o ÑM6;J4F&ᱎG kc-+e5`##,lڬ>rE7 ;pʓ9]XT[rb|{8I#{*b.)ٚ!iYÓ嫉4s& UJˡrg@1ô0?Xȸ+ kP7&a% bQ[[x_= lu9ִy EА U V`F{oVá-,$ 9F;R&PKn 2h)$̷\v9k %ڽ0"g9~ϑY3 ; ؕ+t49-@Gya^zEG| $(C u"P4.i&[B`BT B&VG#cX .Q%鰬 l:Ia &ZIx¯,Px"$@tHl}_(ȤjvBS#`"YNv+J}B X[lMȄ)e Dәcøcys[(`wpNX`.|'sQ'E2.LW9..:wjnY[Ex8 M dG:f `klq$U]XLpZG3t :^-֎wv,7h63,m B^rӠNrR+0搡 ZJIMk C0; h]H3air .OR~&^dRBYu"{ .J-oT,,a Mfw9#Nn_Bt3& 3<9!IBacB12A5Is]ȌڠA\ /&́!QRM2Odd9 Tz.eV:!? |B4> !&ge UL|04cS*Kq9]M!  ɲ'Szi`h|*\S儽sA15D_VL, ))Љѫ i>MD?V/%wYcM;Up$&5Bl{DauW8q~u( v ʼd%ðoǕxxFg+8Xf6֪^.N4b3 1JL" tP8)΍I$/"ATt(8sDo!we5DҘHs lNs)x×P/Ǖ>ek8k]OiUazUΒkاE fTf7n,jh!h\#PsTMedף{'4yrjOo,<<1plYW*D≱݈,ZKK2Ȩ[lGhepDZ4wG~LSB,5,/!4jF'+qso E0 E P2ɺ -䰦R* G̠Y.9QN@T"tD +fF(H G& *S |XRV:8ڨ:ħ^8(]g.nފ-_jUkښܩ+!Uv.# P5@mGo &Uo4 /TՆ ipyLnϚ{Kɶv/4ę}EY09̱  @١NI hceW|u\sɇdJD(["Sz*_z3Ǘ޿WNe^םGyhDK]eM6Ր `L8iߥ6㰦05tvIQ^w2d#YE(eD5 0v2l~P2Ooom;趃4O|3M(}[/T-hև"ݯzثq[D]J"s4:je|t_6\~Whekl嘅Z::IUTc^Z4RAW/xQb ;!1!BBYo%ɦ6,޸|ZȨJ5K&V=/El}כ؉\Ha eX@B"웽UAT2A0 $3A\]&&Js?֖ \XE2Jp(s"@Q3(Nb ٠3Xˑ _tYl\5<"`WCOE `ha `]]ʮ*K0Μ@+EsOEXa0g@Sфf #(LF5Eܳ⁃JMH5[E(zu"19g 2Y^s0A`Hbr!Oёl%*Px !L_~/NۄO !TϤ:LGb>{My߃-nAQ;abݭc$-; )yNՂ-`L 0.9cY[7(1ۮ+kh7@5=[RKSTj9m^8`ٗ =Ĺ9 ۳42k@%>OSecA|ϕ. Ày߆)H1,„[熳}4ŵoZhd&hfͱQOVZJ$BaDsL$5EI| qNGYD"R VYK(씅kV'NᠢsD$sG 9DAM":j[VK@mY5feHE WJVnx~75]#B )FV=ߗ`%'TXqW55u,h:*U4裙9PV7 \.GNKY4 B0Ȁ4/mVF!K~DA7&2C 9.+-4=uyG}FD@yGMQ+f3$O[dL(і) :|l#5\U᥹Kg +XP DU~W7Bd!P֟ ꊃh[{- *6bŀW*vX-R^\3 fn럟'hS syjkW#PnjAQBR/A@IGTCv2X `IrNN*eP^cˈQH}j)^1P `"8SuL[Mw>W.*k }B04$_4sreְL@~+}o*U!,dV KHC;dtG9BF&ѱHU32ćvo|c|-͚ LP/%ɪ_Q}fW'cZXSO*YRrB(DD(_S.)Baa֌;P3=jV(Wavf͓uOjYPt7yZ2,#{v(*Ʊ%x)RdR"uGPu67E)Ix$&h哑l-+A#4骀AɶYܤ$:'#v('$^pR+j#`}Z6V&:i nJN6電 O+de1{>99LG q )Ena54l014Y="1O>$@RƷmKox8U6 ʢjtb>GxͿFT76@ҧk DM4@e90( À Fg$d??6NJQ)^^ث^fWז_f_N"P G3Gttb݁W5젴".An-[;JZTYa:cj7~4Sz@Bx%GVH/{Y"U]YVfrȫ#wӉђ5MRJV2frjNf/v.oEG{; 0*YmG{>[ _GZpe@)XnJt_ iYn)/!:=M1Ai5h/R2zmsP( e+b%V\H,_}M!?nnBB&-v`+  n+"ƾb&,H}V;ٿUPiǍ0)B} T)[GR:}bzBw)[ ,>jơ-T,J0qc96&Sbm!\Sh  , KzAMlsL iq@Z,`e  ۄ' UlBVGGqx\fX:"0h[Ȣ(|Qĝ XKinwSa+=VRQ 9ȏ.s}ͬT UM!)AE$ya!7NÒdp" f@D&; 0B4=?Lo;rLMN\ 툷?Py"z*l+i캼w4Zѻ7̿JЀ ث^&W/9K@P9K W#OW#R(  R(pol3?57KSUy!?% VGݷ20z[a^*Fixp"GB% R EZ7 E>HPԝmgT<_^`icѣpěX )is\`MTbVTg 3aY9l:#78q^E;4/f̸AllvL)Z:ȵ1Ng!EgNm O~ ]{{M\*.KgDVhFot e<4I1=Y:n8ulR61]e0&p^$n?J-[\I2Mu1a1jU“}yJBJ^ǜl\{(*ݑx,3& !?;_: aq\;vppXMdɯc aF=MrM9'ݱXsc|Տ2WGrYCX8bg"1ee˥9\\M0i2}|MtĢ.<̨gn眜t,f}ȱ+_Zk+y :j-UowyⶇM+T:PѥPAK fAc >T s:t }w;޽D]E˗)^*裏&0'^ .6 2$HncM"k9z[U}F︫q  ͲH}L Et(~m{ +[n( P:3܄ t?"aYLd/!`yKMqc:% 1{4!I_0#;|![ YG0JS)T%CNLj ªS. o;zFÄSb5ֈg[6Cg!E-++  Cy_&1ICodz,-]ߔR:Jkأ ˅8H0W:E0I#$,{sU92'(+4 RO7VhޘųMA/vjf}73EQT ~19g ӚܵYBA&MhT a\t2D*o9×1:߸c^(6"b0cݝ@е Cߗ6HFT(YMh؄bぼ:a_YK @7"aF/@;RR*Qe's?{0R`. MXw" 0U(o)OC}6^*¾388a亽Ո:<PУ@@{B7  1?@@X)!FQ4|E"wAR(xċﴽ̔g.Kl(_!_!hILg\f{픲@zMg ߦ(K}i|^!ۈ02r]J~2"tijb?8i?cTHfvXePq|l=JP7̄tK^ϲTcO"\`@P909 VEed(|+ 5TlCID[ fGQsDbB(IKW"{==.(sFxj "5yڎ/=nlg nx2luZe[o~Ģ~Nj`cؗQ(  W jhOK6%RFWk rYaRoԻ 'j&I&;$SmcLap ځw A#怈mAPLm*'xwB 'lP~T^֎@Q3F38FTv@n&ikVQeT.7'*nҾ96Ѭ{xF /i~Vj *&Xct&#Љ\ ćf=DDՏV/+ͨ~h(>RPCqFEB0!tL4`Iw,+<D,!]*0@$8 qdr`̆9$S>GͬA%E ?g-U }kUܔ-2QBgiqT=%)JI@R+TjHH=PTl0SwlBnNiXHX| KUvv8n>h-`6d"P\%6],X][woG d% 0Jy)%~{66$O乐oWBM"mBx41X.y5d)*ovl@}%x=bq4)BX޸T#_3ks6 mKq1t6UbѸwR/y3p,Q; F]Ё``H7Y Ȋ0D 3;B;VՂ:$eEۑKBSW>v: IK!UvhxH4W2h"WLAl`BPz3Yd6\u6PܫRm5Q(e(mn5CcCUU#Ftv4xM5ARօRɑGv".׻: [M,i*İ2|,&l&4;îN0Ba ^uEuT T:LH#g9|6uحnDptڤ 6$w#&ԈJ' ]N(@aW0 _M2;7gY WP= Z)%xb9~0nY}`s*[*( Bd J3TB&@B H/^ 5}աDR 4=фG.^_[*{MCDk_ZYe:zz+Jvw8r+t);"n!jėLMx,jJ=bO8K,TMt 2F=꯮NO+4$E9y fӎVT(xk6D2ѾE>@MLF P tfRPOHpk0'A%7S:]~J2`7e =ـ*Tx A EBSO,GoBԋFſ`,r5r/;t>̮C=Y%ՅIˋl_ z(b{SvxA~bۄH$#"ȤT`E}Ƿ{6h2hI6{A掊Kܤ;py+-grJ[^x[Fq2uo CK--ciDuUQnuS6b̕қ+UUka?XҘm޼)PD6+[gf. OَP]=\vS ϨӇ4]b<=@{7Rާ-i HqIAIV6lFnQ)o4EܕiԖ {n GI3fKr٥y0B͙/OH=5:k?>!B|,URUU ^~=NNq4Lz @de֗^"0"[m2{}@8]Y:wGRj7\ N! 0`r*KV8B=u۲ßƚѩab4n#F "TB Ke [3_::\ FOʫ)%&-ݬ?$yHo%bbK DW!ίÚo㽖ʑ fX?Wφø䀧E;b Chd`.ï` )ڈ&\JT_Uf5w_au_8X_j|3`[:@nT/|3JL]gU]h)q!4I(rm*%m(.OM4^vˋyޥUWpah'*i%FL!X.Q6,ֱf! B(ԙ#-CCS]3w{knF&(!+MH]()p}ņ/v_P {N D؈CwA :#v9J6_V7(`9u =uGj_}Cp-S!0 %QK}3/9K#dI;L20/*T|47k ͆2jK9{ADWwށ2Q=VTyuow0M6l& cz!BV3kqfO@]%w۵ȳve۵v6a,RRcj[6Fpo[0t(:!{rtjBO.5"n^aOVy˯i{֌覵!徴.U:I KƂ??`QsC6yb*.g^6\ٛRƩ|ٲ2wֈ[3<? \K '_$igoO FÉՑB(!H?DT?ixhgNy<'"V#~Lw1P$>~ |Ƶby),%bӳn? h5ySWTNLaFQ2)PJ9.+J%;[yHo\ՎJI "=E2oXyS[͚{+{-{"xc5q{}jZ3dz`ZV[Jyƛw:_ {\v9*kqFu'2^ώz>gC~T\J?y4|' #VPTMJ#{ }#XtO#k4ߪ ~zMD|F`36$W^--ڕzw?@~s=ٙiz\[G!dLZέʛ2E;prk:TA)" Zns7 6njx_86u?CwYm&pVAL 墆K򲓻W'>gɿ跬L1{2@gB6ǎpqoqOml!ےʶTZ _:39C47 QoqKa/K D1, ]պw1OBϤp`cIѪʼn1|^ܴ9/~=D rnr~2-=L'ŏEv1~V/dmF^(Z]XKs`3}&Pyg58q!e:RjfR{@ tv:OE9ZotkLpα7 rS㰘ɚi`RW-gCB|~UKuJ悷׮ϫ|C<-8k`Ty=L-ӍknUM`kv|^ϴyȫoZ97z#!iT%TDUd8ƃ]Gm}֜"IeNH1V/HKkrMwe $E:i||IiAEaYu.m*_}pڛKz͡ʠi=kkjI"EO,7FW[w(FԓwME eG@*h5'!kEi0o1ixק'zC]&`z+,Nuu6ë8ss =.,Rzɶ6$R-;}]sD=hwӪθ dsR=laE;%ks,喇Z?tOM2nyj.VdVfU$&ٸQ4aqbcC{#[λm{s23"1<9GPcf$˅BtAܡ#-sN@.8_ pKj;Q>98A8xl! 7aËyڹTΫV~%UV&' y@ Sй f"u-wa'x~`P1z!ʖńB!\حF^zbmW2~~ܿ8<zdQ^Ә_)Y]_9,ֺYbFЃnוCҊy}%o؋|8t pҌƥIƦOf֊5fP#mjcP0Z4Xݰ9Z]+=ߛ4[݆ -d):t*"gf*}x{T)ʆE0tY @ޛ jTPF0l΍U7t0* @]6S${}_3be{f3& EID]Br~+j?rhovaYLe'vhxtM(Z˓*.zUT9}$w!@ν3KZaV3ivɃzg-äpEӹ)p%CZZ_iQLLU14ɞ?jvkETuc 8H2k̠:yf& :,8\/Wx,s'j7%V8hnj#w&J^X/gO#} KƔ;z9䄥|Pˍ8Dv$e[k=>wtrnC7yD\IM9ֹ^zE{Yw4wYF2Eh "*`9sEA=(Wm!.TWFC>*"/M@%I3h零*}TŴaq9}6=U[xhypc"n7Uuv:ӱ;A#8t.,(Bh͜Yg-ٯ/wҶof9CFQVa@9>vt).sVP̰4kn=)3j;jiIMߑ;q.W Uܣ Ǧ-$Q܍JX2u΁3VO`ls{Ddcu+ ֮ ?E@uo`Mi b " ee+: : L"J8Db9܋qq!cUI0 46к:7 W;utA̲.]ۿy~iٳO'J[iqzoE`qcF?a(,%+7W9W̙S<1e?权o$1;R)ZJH wX'3 'Fv~ӒKL ^w^봹6DXaF45<< Dt85 -6c hϽɆ뤓#O^E2ݜ6[vn ]7,Ϥc37^jnmOdˍxguoUW~elut=~qhlv4ԑ65_ztUZ70\3Ƴ5>kE4T]o{矅۾2mp.}S7N϶-8c#k dG!@,m2w,Zl픻 u#5YK.h2nݣA{g Pm|6._e#^_^?zO#L'~[J?|}:#tbL0\] ֱ+~'bӉ(=%_<6t. BZ'k*o3;GC me?Q*x^;~MtH%)[AP0@^h= 5#(֧]K gxB0m^սȭwkZ[E2HGH(Q,AhFz {MoqvAY9N{ߕ3XZJJV8Xw6FS;FmRc0(ssps f/dQCf2uݹb^iדd*ot2dDadґ7S-d=[q:$\)"(!0BL(> n__s}K5T]r# ]{q]=e-#1Cj*S'RN).V^%YEV&J ֑KwKvݜqi6Y)) w/tQ]酈;x ,w5v&7ͽ //}ָQlzػnO=m1]*z΂I{L mV9@ ;Z ⍌9LA 9& A5L ko߰ȃ~oO$m?rbbT\<4?X2?,V|H ɾ:470AA ^6[M}I3x$pMU*7V0*W }U pOVP#G,N.OxaT JDT4ADǼA ,I7ڱ9yNl@ΐƎĤ@ڥەd7#aPdH$]fk HAH@qeˁ 8]!PҐ\tnvҼcAsf +eUBԶzޱ#WuwjU o$'bMkԨYMs+gٔ ^ou5& OT]4~Q}X nifwF?~MUF5`/Ki# ܤ )#wD|ńjM'Ƒ^Y9pVZ蘩u.hi4hۨP^~L뼍oIģ闻_QԆ״5qjV* fPA P ]& (mzDYA%~ ^fuYv\b5wuJ }4uU)g[IMV<._ĔSeu}( cᖜ^v'!z1Z7~:!D>-PΝ㼭yp;|d[5 Qc)ǎ^9{4NBXubw8 2bbԓ4Ӭf_MnvC8Ԩu .ηQ*.ˈ\~Q eè1c` YI'<ۿ= dɍLx ގVp/ I=cZ_~p~8l;`g.ꬪȉ>-~^2JĄiJԦwׇqƺgA2,!S8[?"(^]1fz@O>Wh肊A2lS) sj3%d[)@*'diQ!)7F=ݵ-tC/O-hN UfRZ6뽲>M!WqIU͊Ic4S; P*|E+GGRZ Z<- WSi+s?Q, 3z(Z =./`R$7XL}{⡐:Nk* /Ir p*D.*#o0h Bsq^XЧZ |}*מZrIo[#Vm[TG籑i?kά/Vyҭksk).AOD(䦨Vgoo'k\ vѲ4.X{ԨΗkDTpTCUۨ`ZMI,|yiP"#NMcΕxv:YOa4 ]H֝}^sWM=;]DBlҏ GDaB*9ySqDL?Uw*! b Z7PCh.4!<V.Z A?ژ]ǚ rAYZx52J 1n\mz= *H( طWц1_u{(~bйy)5 y9L!ܙ(O YCq=?9%B[݃sN.} vGSZPכ10XE FB!r7CIh6 }n;bJyxɔ޾kHӪӓ_ d Z!zh6hqpŻ+cq e%,<VAKݙhhbȥ8 b4H9b!`9C]bN ko/lY2AȵEwGPR8)Z`n|*#Nօ1[}Zf[RX T$cJ@ANx# dYB*Ӭ(vB < >VCdQ-ns-=f$-i$m$6s}(i5ud0u 1Z؄R*ǩ-ZOjʎS׹Ip&ǖSʴ.9sF?#k/dO:q?!wX{z+ﯷý߳m4o4]wKczV4bȞaF^~YMVlYlccG/Lr Tu} C H@LLki/J$IR=c58h;BtLZ*8A@o. oxkYLoRY ]Թ){=xL @^iJTΘ۱=]b׫n8x$5CasA?xo޴غYb3]~pœ! y3fT/e8J7Ҷ$#̵uFv!sYpiqr:2]@jҁH+mtg';Ng3[}ꭋ2zuڃ, n`JT f GE" 84#'?wㅶnpH+Z~^q/}Qi~v ȧZlް[Lx\ݤ{#54YBt]BĴj$ Sdl|[6\YsBiMU?u@"_d&'+!0b:&HXr*[6.b" /+5 enSb;p>(@<@X*qB]2A;2ã*9kmQhXd5`LB1y+tZ yK\E,Ye!,2Ai[܄㸆Y]W(.t╟3 FqQ&4:Yl6Tg}3JK"jF!L0&Q5Ԫ\Y3`ғe@2|^FUHTh!KjM XLAP1*j)rkg 4 \Ð@l1(ʶ@E瞥-é-fkS,d&rNqp\`wZ2%L+;pc(^MT?3: @B%pFhΤP.B(HKF:DEMNۻi =5S7'm5BsxxJ%:ˎt'> &`r-? `y\z9L3w$'B."T5k=GQ~ԸǾ[0M@F/rw8fq\޷ &."1wҞ1`BN?{Q"Ub{"ubg'2NQPnZRf<r̴L̑k-vf9~P2^ғ 0XJPPd{dƵ=[/TiXmZƼLʪ6=>;1}X}t  }.]`_hfϱ}+Eo1||B}ǘ_$sn|B_v_f1acTl|m⦹Y?>6Ҩ7?R}+xO$X&m<-IHl*IYiX@S>&c:wU9VKF,5%]L%k$ND `[DgRBQIB*O H%# ? *eeWt4].KwXZ @ꏆ]u=,]|ԅVOVb#G4H ϻ@$Hs^)o},G|Ս]0r!2}ua G}AHPŎ\}-W3n~jz-Uwt>A5fp1>е:tQfL1|M/ `䪱J (w `DġhjX}ɇ4CN[y֎me`!hMJ&G\nj CgܴUR2ljc0d!GsA* x~U.9 ͹D "= 8q5@o^9]`ͦ׳U@ ;hX@D¤(#;\⇽@gM =ӝA"*FOI$AkMMr334pPGSDVB@(h352Pn$WS'[ U{oh匝teAцuzރ5[47wb!YWp{o.l&ٷ;-3FۄЙё ^0Y0FiuP@@uPP&bv%` ln2aa:6A)GXeږu"9zl=X2 t3k=}B=wcJqն͡]yvxKPGEA&oIR'ӹɶTyVFd.ONm'x;q-"B 4(iO hbw91,}DQaUlhHI":љcL]%f~쭏V :V_Se'=mqfFGxE[Qđ#fMHCZ/$닟Iu8 Begmxd,Oy]E6Z zRڛNEL']eR.,ݞk'=tbWή3|Wj&З{ZoY˿"$i6 Pb[emfyw%puqB0ȇۓk,5lo=-Ҩ_"ǰw} B\]/~ٸYx fHWޤ]v6Wwu n~ 5#v4PWp-&IM[kŭI'A[4ϗzBƫ^u,ҷv/XWI[t)8Ƣ 2~h }7c[Xv_9p<~w2=M㺋߾ɳ]lD2^^P&übG>khlX]WW.uCz%n ;0.hF2LfPP-)jgHOuHM,/6% qf8祧 B0)dg\4Bԁ=dK|/a?g/rpSui5)t~XY%F}|Kc>{~JnoIw4Wf0*_;H>/{^ُO/ُz)#crnW^dn:!!3ID%z/;"ifJ0*ª*?\3e31ҡ^7nmVBSذgfVKoӂ T3*q-: ~ +^݆gƵy#y…AxA+HNZgnHq428kh&3vtDTBsYu[0P h z@xpu&r[8l.^%R6Q=^N,S Gf&,.)\S4PHQ|S4?3*+ɈCv](4ؐ[$w(Ly ͖p>%-, Sm7&Y6WXHSH(}+|hΌ+-ӥ+T@eWnZ7 w|{MW͗rƍ@%=;~PUHЬP Bd ]ժqVVj ݛaR>uvQpdw#/TFmE9BPVm|(Ni&[s5*dX0O2ޙ>z]D-ލdil:b[к- wجE&Jq|t!Gcn*w0ܽuMBį*1q䡱tVqC- gImi.) P  ޒ@1i#2u B8 RQ -j/P/)]b_gF0L~%7v(I;iJ9<7:VuV-%VD<=q8dm̪s"D#P7SwH@#pje%^ hQʶ(F ѩ=sppɌF4|f'r,'hhAE&zrgٷ6qg=me4KjK :% i~﷮;nҌJlQ#1<qAQDꙙߟNB_I_z&ʼn5ӫ<|k#OK~r#kk̅+,5Uj FWiL:uW$ JE=ۭqRi, ` h1mA e$0ř^ +mZ*FRl PjTnݽ9aO @mnhΕ3 7+ce.!f]hZĠ0-JeXsܗ|ԟq㷝0!o0٩a ,i\PBХ`s^h6 r@:>{x"5_=,.`w$YoSk?=g/O)pG2*~?4`vm G-@>V-g9IQE}Rq#z.5u!*7sB;mXetdo $)R""k.w٢RڮMd-Ql$&Qo>'}>*x|)a1DmBw /L#n w"`ӯy~=c]ݼ=;t@=zG{iW>8I(AY=-I]*oR2/uul&>!Cl m,YQU9-uFX ŘT86DeKJ}j:epkh/8 D0r2`}SSu 4|-7ئGfS5I.uF6+_R iϼ[HŲ"[8цxWŲnFz^JF8b,ΦΌ+60ދQ!9`(^#Q#$XLym̹lI ),N#~gf 6 1 aQ1k &2ٖˮtd2hwY 1*]nx'A]1>00c=7q؜;#!4q{cE*4(D f8r[W{^p矱lcںRDa 'c5هU++d<]q-m[l^?Qn&S$7r%XZI=)@J gkԁZaA^pu^e N:8(ɄloWu/|MN^pv""P7]z%28<.-ߌ(7퐞\y:j8Ϟ`8]'&\-֝ ls_}mr |:_]=~]3FRj[yj]s_S@8g<#5`ȥ>_i BٱzmrNL13jag[zN%66;-}V'9⥅ [: 5j@?W6 Z5iԹZu  sKseXӢzw:2[[qG0O=]_$W6Gofogo #ܞ&8YP6!@UA tna40Jko#>t͎֯(4G.=<XAᱨլ0yyu|b:&ㇰ)'?quϳ: CA8}9}$2taBK77d׻,7rvteaÇF{ijDB(\Un%<<.4{ eXBm1Ye !聂@rGmg.GiGR{:P"AtnJEW#~mع0e~oZ6'm(D#Xa+ZG6d눏M>8z00uB֜#Qd^qFi8"xw|9 QMtq`A[&|:onw h?vR9\ם^o[S xrM?u ~;ہMG~Yk6m47'S%MGBM1wf -DH_  ޠ|SILC7b55YE4d.ןc {4tGrtj3rxCfoxjW^0!:RÊE HyٻVY{q1^qqt \E:d0`dBJ}`,jv $nB=ǍNqq̲"^]ɸ"Aфuמq8}|P`p 'DcT-@pª1\]%^l)X 7XN( Zf8M%@1 Qr^VY(.sdAL&;t,< n`Yt3d8ei5 : {)k.̨b{Ϻfs5qk<ҫf CKn5i5f7M#fĠ䁰QJ䕄$HOH@Z*nf9w// M1ș-ӟ'(x. -o-Jh\kbuLc=m-VK.sѾ5'93tYE%A۬i& .uy(?ЬKsRq =j 8;mVIm/nڎߓ-$`/=>ʸQrl^1Xf#cpݎF4ȹ;ZpM[VrA&/uI3oAz&"u" šuHX'%<]4">$K|.9D|P;(H #!YKd i?4Xxa B0& hy̌ jf~Hkx*\VXp>-_= a-o8$TK7A&S% xdS+J,c3+^QYF;sޜe܎OΛoߎg/8؅&#Sɴ&qН N5iZfU4[믒cyn*s݄,Wq ;@_ 4cr 9 m%$8:-f p7 ^ %Oϻ\4*k[ekuȑA E\,=) eN gvX9C "q@s tPS0VuXW],p$qGZ0dtz-4aXaWI`󊆸\'E ”A2o:*m!vumzUQ x"z7S30??Es'AVRƒVe+E[VO]W\|Do+t"!-wjwJlE!˭~!4An-<;^hGn%G{h^"pJhTAbT.[/;VI)ESuqf6ȿ_SySazY* l suO?(<9r%diۀ1ˬFuxy Y[lI@  (8f 'ʶ͆AXÀOl 8& Օ VkH]%D28^X"AC:;"t܃~ji AޫEw+a^R; XA&PHk, "DDڡ~-*;η.WOڈUC*Lג7]>i&.(oZ3WoR:tv)wXO EP!DU2 YGetJ3oO\g._*<H>$`j".1y̹~omMKJOXS*\&wO XgH =L4 )}snn 5OIyb11F)7i" a E"ál= [؞QJ۴mb0j0&KZTxfH5_ TQ] ;v[H8rPޣ Ʈ#nt 68R*zmǮ#zm=*ANB0ssp@ .$H<# +ޮ{Sa:Q-YU4^c<;tpf~}k5D rJ 48[$@Q)?#(LpHrg+e 2)@8;ʭsc-|έ,Sa: 9\OOqл@)'!ⅉ-OT`h*̚BP),|m aV03XCq@t5 * Dc늵NʡJ8*L"^夅Z [aΠ9fy;No\c+% B( ,5M\+c}7Q)fc# $KUl;dM]ZRPFn\ǺvF`'k$ِVWz◆ߔćmx̔B+ko%L7A|S"&?6ݨz&Mo_3{93&^]pZ&=29Cە }_ =b@@% %fD0-LD5Q%(f2FgSze#Ut/u^-#w6>-XIN #|l01gЎ[% YLVʜsy :Bv:59l}Ǖz7ƻ(ɑ6ul NT/eS- >\0҉kFo-_|V`^/gZp7mGy5fDigG)Co  7?P;_9B'fl #|s7ʆqG}H=Cvf]4QNe pPkQ9D TqR,yR&+$ܬ2)Z 9\\TM{^ں-[ J/m"!I F,Yg* a8x_,h<|ucD])af% WKR$I{a "dw[i9G( !#ޕkor=-9ӵ.7cTXJ^n|-Uh(a#V Tq5haZi9SI ;qfOt^,T$=TX?5_Sj5f{T$f">KvOC=zca ] >_>i"4ۍ7idJCz9mĎ_ ٌ^۾5<lE EgSaΞ?k8}4du b~\^WTO9bhzċ9]Dhoo ld" !]qQqdb;2>j;|9X$:͍ h}iT'np]M4`OSg'!Ӹ,:U($ A!`QND;w667L%J&YBBkݍocD &a'Y&)SwWq>UؚFu1f avrT5 ؐ_y\V/ucs&1q:2f\/-ad52 J""潏|Fqq.{D- Uc?0ӎLu߽jS9於d;u+RȚRBLV6i.1HТaOeMȣ5֑vSZrHWrT,VȹlNI?,9ߨv-1ez7"Mbl,DEdƓǞ=KWYO󯎚??5zW`Lu'3uqT bdc׷4b)5Mo[?e<$}gxߣ~a[ $rFOfd>L<*5Lm%'Z rU{=rda5h;g/3|NEJmaXuIz=>JWU|5%w g)[ Qrӛ I;F6*Lԍ-~AȦ9S" B2f2pp*HQ%Z`lUOyg8^S] ` t#= gb޾4.p/r!8MMN/ ;ZB:'X.`ӷ+ͫ3բT I3g|l$zW-}#G:2STQ_j٧ʋ3Btu*cH:!2N:v7+HilZ0VxwRQ$ApEq1J-7.,>=W%bj z|ϷO*z73E[윽|=H혔;d`FhUɩ~ms+¼:8L٫x#t{ԛsjOW2TŖ}n G卖byJPӬwxRنpźؙ00$H_cPkW$PED mńd7#A"eB1ÅP%aQtMU u!9q7&{e|WܹX=-_{/C^ןܹqsdY8e1tn!4owNh,*WϞ+0C=UfR+?Q)lPhPH$LnH2ȓ|+RzDF!({8Wω~CNH7ׂbf?FnTRXNqZO hX r B\"̒[d@eSJp-c^RGjsWbxd2$Wh]N.'yey`-EL-kXeZn)cqjxRh@Iw9/>i*&7yk\&*avJP]FY[.{Wē*7>rvPurt0j#լjƮhE"4z' 1Pmf#kaM͆GQ+YSL^D8VJRS` Bp_A5N*D;YgAoT)ǦbqQ (BF|[35y#?>܎RLHt\j T}io߈xԙG}oX4,2<Sn*}UT4@10l26YQ;J'Ck;F6;M\È!`9Lf2N)-ϼkF/ŏ&|h4_ @9~ :ϗ󳨢&&b"Ddi([eHi=1%Qr U=6o-tψ!l+"Vwl5>r*m_Avysޮd7#w 8wKXDN~BzޚE:%7&ӸVzP)zxBqS9!v<ܝ @4k%5fX1/ן1?$} >`+ w8!kYhޒ@#s)ݥ?ˎpg)5~A[[z^ G gg.) kȂ}0s䯺Yb+Oa~/vPs=08x.URIr!6k0Rtug8r,+ԩMK>.¢ˎb *et::Si*>MK@$obf'MӬeAhFޗ=>t@@<X~+v5 uBԽޜoF۹iNy[]E֥gfŐh#!IVgtgKÂ:V-Ɩp7y0˟8w|&{x/567k+YmݴCI]=&gkP<]n==>@`=;Xb#G8"K8TR'icٟ.i3W/J?Foɛ$ [Zf~d^əK/cmN~'"D+T$Ie"M`C>R\Ihj=ƧB`:_I+D30P,`*9C.ٕ,h2~X( eŀ #COZ'4X@+"Ŗ”ԣ*xؠ?鋟*I"|ҋxb-lt FV)zNTKL7/aj`W]ڜѱڋbס8Ʌ33V挅x!-/EҚun _rF,ly[c(/,4sEU 4FϘyWϢSWUD % HNzO'6p>m@9 )3F.SK?w./́/M \eo(HzG[bxk\pk{V騥i+黄?::k/ZNΑQ٘Ue1J[b6Ze=lrIP+|U}1/p ҿZh*e \鳛3=oHXqLˢOekya߱L r6;"/U9AȷwI@Vp8l$~~\؁i2C;5*0MyKf֨yn@|()X_OQӫɲ aej%r491 :g# P Y8؋ pz:<Dt5I1MyIn,DȎ&Z%@eI $R1?l=\:0:IyѝZL% = F @荗>>1^䔜a3~VӼϝoql/# a &Ԭ0"PD@rCEEmQ)ivSHoEUYjx!3B[d7e¤>C b͏=:4ٳ;ϡ1/B#Oixy &O?w3IPJ1ֹ-Xgdw\}ZP59aCٷx3[‹YS~]kx|qg;ivh9{96SӼ7{ P_YSc^`>ci>?nw|D|'ѯOMz[?0ǟ.sKiW-AD<{{\]q3lJ~zv_lZ8`k̂زq1ZOa W7qJ03jwt\5DW #<fP<]-)-DԎe<f|͏ʹ&+ O> mpo?۾V"{^sՍ:nǿ~`92|v5^|KB$@Y\͒ʹd-{=nxdk3!9I(R7j_X8wb*Q£^0@3G9vvO], u_vy/_jM7 _͸< .Ȭ ^p^3Yۇ_(qs`y7I^KZS}mii퍅uw׭Q.NW-ʅ~ $-8_@ire,>{Rqsof]#zK:?_3V9W.h|ΰUcn++u==mU+rvv ߸ہl=ٙ RYkn1p/}SFGxm]C(p/Gm{_z;A^=ۿߠ|WGMeXn | ~w&=k퐶rYj!-AEg Ofg?;^`NbC7{\靆e\5 ժq߳ ^%I`_& !xacnY9=6a6&Yo`y%;nk|7~FdrjN0tx_l,}*1!&uAP%D 9͞5N6־@2AN+=-XK훜Z#!^V]5PrĮi+AVkuXnW Q.9VV̚E/5k=oWc.nu.ZABzɋe(\fgZV,>I_ؐH )0Ku+.M$cIN%h֜]W0>2jZeeJ^nշm" K ix=[dwH}'msj2^֙Om3vVPPlCOi?u7b;".6\n֩yхnjPm^w{E2f/- /rW|zz|ˣiܮ~[~Љ9|@W !S7=;슋[A[,ln8\d]cW 䔄`Xf_"Fv`;@;UVhTA 5fe}.3ы9ǍEHп8ÍSgX}B~5'g__ْ4 %JTYkKVy=v6L^F".%!RSen8S`tE1㒩UZ֔XԜB; o+y}Q14BvlZ ` VmMj"NVF`Q7YM%aYo6:'f;o h0r7$DĿSl<=z) fǗ5N5{=Fj=o{3mSWs6^pgOrݗP_tϞ%9o~O)0ϟa!NG\\׀),yۭjU[&/D\y펕| r?ampO˿}x8{9u_k#byyw;2t4&UX]͜Y[+\4F؉` /v-)P,AYϽ˞ OOs}+A2L )LɳBpfy//gLs;}=uqYJBȃ"%'Eӷ~Mf2t'З=1[qʛSbna׾[ G4-\QS:؇BB餑>G?vWl(jT͍bX/nULE^>}>:3s7q5ge-򹠲Gb$yn@VZVrٙ%UwOyyY_>vGiG]=G?=ys:_|>3b!`)4O-J^APʊjFfq5 yO_~K7P _ə<[9lGW [(Iy#E 4)S~go^.io' Q|=ʡ]^5DxTBcns|=FG'*]ވm|NU*rxGwDfg|{ZWڭn4T= k]p6AqShHWiVr˪l-!_y7I! -sm5hY!u݄Űr%/R@ZD϶cqNFșoU\;m֛۸|cNծ_UϻUm7CÇY޲-P7x".Rf cV=) ZC&B"IRY3ӊ`8q\%&Q.08mQ.U6!$ r2^X)X/;Ht f`eO~bXȤؔ ^!Ɛ(W=E8R:l2|e>k+_ ʅ_,Rh*- =DcQzy0;yXX<{"`Z\mmK#1ϏZ:REgt1kXr:F_fިS~ rМKw&&2-ׅ4zgG޽7޿KZ4X KҭC,̇άODs f+o/G |q%7b{3 &рk^7vv|q~koUp}kKNگm?9;X4p~7T^.{}`1kKSy Et?N2*Dp2lU*tVEx>:i]Ƭ|P¶EWB)luʷwGAoԭNP_}丯u悛~>xoOZ0prvF~#~ZԵ[ȅc!x_8,#te}"|о/>joT$ ōF$m3}n!t7=:٫^whlBTT,25(_+dnN!.3v9U=ywb}~{q}~U#>uqWݗslj۞{q?xrȔBq2ҹo iwb$+v~dl(izhny!0쏳"{p SA;H@u=>zujst>Z 81J|O;yyBުxD0!-i\19"0:'vklx,,]Q'R (EVc BgW.W9z/_-%  \0f$@,vd j< < DcL4m\nw-<6^2+F&Lxsοcڏ`QX֏Y;{[>q]UD$Abғ^y RҊZAcuQ~};0Q @uDL׈"}Jqc?qfM~'ۉ3*{8;8- CFT 4box3"@:EF~5 dpOm*=ʰ \]˛.GP}|9oK"#LBUDxekB oV9^/NDwUCY@8{\.=O*liNl]<[#\5`ǖ[jH?+/Ǐ-+n* Y&Y=n1$J"F0H#Y6uJp%T.Qyk7 V|wW~UUNUq>tGL2I H@q ]jgo;XDH®7^ i>Ă*Ѥ s2,eF8jomݡ+t_q8XBú1s̈k"HV^:w'Q[V Z4,,G۾\0D~&J*uï,SȚxLBdaY.,ࢉiD 2/}ۂo|OoJյ>\b!eǕ>m~xtoO5,|6Ky8 !_mc]̇4͛w%qn`TEֳuRNa%lSuX4/Ԡd@K[V;"Qrz/D /0SSh˜#<2PO97 0y1ѲQ^$VV`oQS6!`捺@_ we!XY}؀lJ"̻ĴVrl/d3YSCT`_4'<_G~ǫϝ=wп]'٣~7e^fv!Ɩ?ZcD\['b48 kSDuSʔ-JBq=5g~㟠6)XĮWpEy7o*^e>Kڭj|?.D5I[(]mD{ 6#&j.0 ܦFOMB-c+g?į=ے(^~K={}=zpKgK~X\5A-NAlͼ$EkQkr*\ PɆpgx6Gmat JMAf+W;29lA$Mq ׫3]X/ V"{뗽o׎.בT~O6^S{?v7<ڝs CDӫ{v2MA\X^ovbYd]"K9[ۥ? oE9!ϰs>`F[c(D!]F 6VC4P8a# *%JԯI8be&@4䎊 52t)*rd+?ToEӫrGᨩ|4h/2yUYL۲O7JG\X(Pn'Br)x*l2e g~QP%H=e t% P @x70j2(Յ:_{uN)~I}dy*?'t0m@d)RL;z#Z9VD) 0d甪ഀP5ܱǬ * .{5!U+T>j{ׄ醉h)`=6 bbٲeѹF4D\#DMhx@wvz@26G-CNnKB+*˛I>,Kp|JMRZ_nXIE8lyߺ#5QjTDUW>ݱ"7%X} `u`=y#l=/ )B@p0U΄U1cNh}vnQ;RF~7ϨveT rUj @Mt Af5zeqrUjAc'5QNi2yNyD{Q g߼@%2lcYZO:x<@.^bF 93e]Jbo7NɈ %ל;Ӧv,'3u?ėxZk{^m2cS'U;O|#^ÛTu 5V2hkMTM29H6J6aM@"~`* HGG:vݑSBҷf):iqH +ܭxȊ)hxABEԱ|#\hU)Md nJ&ǵH;:q`*lP\m] pdo'fAF/ Bhl|]R/TvTȜBB;~e\呲_s6Wk~Oٕor 7U _tWX^4N,!fB7Oܣ٪sW+s1}BrAsBC5GuŰZ6!qiL$̈́D+ME×Hw o:uXn[i_%U.0 5[ ܑ*D{A`PblQK%w,;vFcN<5lhXݞ<GA |IFHk©w:RU:0HU0oI}W\NM1KS^:;g2zSel7O஛ri普wknAl ҇ΔOWtLWDCS/*+_uן;Z,=_x/_fh{Ğ&.a~wۑi@(䎒k|{*l0 G$9171SR f`Kvޣ"(Q TYTBVk~c&?(5*5T0j&#ZRȀ-fFgV,;ULS g5]v }&1~_bp ` =4 XB0SW&'WvNl{CA?~7 zHf,#Y*66z*ahԳCw `  /=ȎAJmbfvO<͵.DL\`k+ʊ91GActص(X[9Mg! =PO4Y[Wb^y2HaLǦ(UYzpL`I=S|ŧ;ֱ+wyy3>r7R #L΄̉lQD$m~ G1 Ȏ~`s>92v/oѾ%m<ȮGﺞkr}Kau ƽ%Ӎ՘03-NFGhx> + x\ V-mMM͉ۼ~nc_,:VYӘ[I6RlR@HJ_U_7*[ a3mbX.vU:ܲ>8*Pg*&,[sF^y_Zj/ovţ%pK_Ϣ :DΊn ѐJ-eRXuf⁨h0d/hNV1ZbgS'% -\@-l7 kSh$}><04y׊{ /.t}}˺iOAlR&lv7 ii|zSĈyAta tĔ IO~K*|7M`Vq|)0@+1A4S)I>YRTAC=)"<\BWVSh,q-k+d)x7lA`h4UjÞ~g܊Zv IDr%WΫc kiᶜ+CύrZJ4Ġkj.#%$^ִ\N,2g%cFM85o{;5r6-:u-zqzk/yx{{wwpz;ājmI|pwn[,}Dc'_ǺVCw0kϿF/lU#|`w>Om1pPϣR9Tj_5LRE˺rwzlt3:1tr ]Bh&USLs`w[WԂT􄢇kLvfm_=)Z:/d"Aɇi ь@.Vam3 @ H73ୗĚjze rcSQLT*R9h@pCDXK~T5=bEB@J/|R.M2Է (hO>ob}\qJnE{5qX`no7mYmGAÐ kcuz1h w9[5D ф8.\Kŏ-g6Ecue4PX,r7q_xe˯WO2d:P6 B)̣>&o|SCWڊ4(k,đri]߷Fi; v I$r$0Xxg/ˎYˑr/m:%hOMVsEz-0g-4 S}^p_iBP3#XfÒChpQ: 08ƃ7e|{'S6r7kZ[*5(JeN1ݴBX: "Hr\thӋ3rQ|7ޏXK;D4eJ}U9rW"⫈8U/)E/@f )~ML'ʢCzɫ@z8Nع&;EIQfP$%JCF+NMpaAFB]@<oOh\yԥ!a{YG\k5|3{|{7>u|f_'A6"u8gwЯ'|Z f,^KٷrwwedߚX؍?O7'߳^޳kTo@Y:$v )^?v0lH?c[Qdj"@* 1kp1gAc1$ˑ*T OQ0,?/3!C6;8L*z>~*U 60`YHi)hOJ=XȁxC>W:]@iJS`t p6̅5Ti ,a}kp5E'_RQn1 |RrMu Rh HV+Z 3`kOng2=sq6,/) 8m9v㴽 .m:2\xi/3S: lRfrR16F fJ P fH:"ƈ|jn TTHS^jn_D9$Z񴙄?? F?s=@LKjS1*'l`w&%.oa>YJyFJʈD o¤ɐ>]_)ZA6@4隋 Z \SXp"[*PyYGL\ܳɮ4$kC3)U[Qni/^ ih MLXh+x$$ D/x~U/ØfKo>?:/zѫ/ίA-L:W XXOի }-.WQCi h _4~@#QٝQbmƅةm+;? 4 ğRWMLZԆ-)Y/:o~eŐgH(*)>LWod6m9Kw,]Om<`c=f Oume7 M69W驩lc^mQJvxzt|sLR3-ұΛH8" !.Fcҏ< e)v$6Y [fӲzj^\pKGjȂ?lQa8 وQ'>o Mb6rf3>KWxsZXxu<f9?ӥUriMPU` MJP!-l0:(RGD+r,fI}DJ!egp^zZLtRt3+Op Ss/ij&k KZcQύVz9ί4M^xb왃|R6tQ! nGApEtoe` mǑP,i#Ȯj )2 X7k2Kl׼>ho.]_mѨO*"2. u, ,D}:U>|>(̫OA$gϮ]}\=*&ƅMM&D$q>z^7ݏ$ӴP VPBO"Ui iOSy^h4 JSyQMt |ծK WuJD`A~ \d$[S:x.;N9q!5!۬]> A D. 1ia+>&mzw7O7>1B-w7u{_0csqL9{ ,n3`AP 'rboB`ЬF\tR:"@<҉mW^p19ILƎzO>zQ:YoGcO&>km}Ul vZ1Al+֠۩nvmVxVHJLOו1}g47Tg{@g"Fۋiږ E/dy_+sS+s!Aih]XqW(VQCm& ]Š7 3)g;0s\Fu58e6S;O@25 2JvdYjg<~[4m3 "SސJCf32gx_^nNE'6c+USZeH:o@R:ao}!b+lRFz' ZéAեP$X g5<]7De j%jz`@rOJdQkGSUˎ%1jު /4RmږQ18fՂ O*|Xećiߑ^޹x{imXvR2u?OsP8]7#oqxbR1O*fky琛'9j.kܿ:f^ag޾u-(@3,|aAw0\5O1 $-/\ZQ~tmǵ|;uǙ]GW|Ud /0x H|+}\VnjlXzRSSy.]d,K]9)46p(!%Kʊު(:~:ZM.dz6-04 UuQ)& -G&TR)SnbAI[EiB!+)/R0Ѷ舍mrdR qt9ѱkگ,0u֟VQ* TSA5цT03bUO>'f tCsZ#h{^(~˶JHc4iG,7*֚V8CI>p[PgZ TJf~疙F! 7:Сd&[zhx8:+5oC%$"*k\y5YƉ44藕;:ytו,J]}uo;0(xǯq]e *f0L \Q:>|>CD m}Qlc`~^Phjf%?Y٠d*96=Y,@P\ "`ԄQ>A!!ńhcʛXpdc! /XӠ4J,]nB+/Ttȝ׹=?C=b Km4 :w0dJro\^j*^ڶژCG߬ςxfT;m|Se},Y8gp SY/`(TK] D |Ւg@RLg`I8c=$&-Y|AZ}'>B:Cm,' >tpJ*1Lj TtҒ[p w&\K. M*cA h]2& ޒ{$k %LC.7b+]6+^mv܁r;B#ig{*h=#Vv 63(e0矬f8ס~n|S5Yu>NT&O5|pw((}u3,:)!9JF>V}w__?7g0l\^?j7$cEtȇ y)cCU)ӊ(EXS9t Nn%X]qn]|_?qKp~rҽ=y%s~@dqJ4nzgwb׸‘O羟gqßJ#6_dc vEhGf*ұy8CG3p7>Bkw: [O`#MI z򚾲#njRxz>]jC&-Xء JкvȲ%*V$A]Abɿz3n @d{0QOM{`{ qro K{ 6X_Y 5Bޒm`"P]Rz3I@]#Dєhk G^j?/ҀmJ`wE?'ҟc4`)R6 zװc_mv\BkC͂^i烈ł;}D/g?kcfMDCTFM0470mObF9[6cY^} |pl}?e|^"4,,OlS'-hL7I.0bm (_!5¼uV[C3?vբ}~{;ČT8OpQ̸m?灯˄ݗ^Y/gi3)zŠ850vXH0a)bv SZB&\ KЙW kHYYoEw ?S4n{f@w*jfȖ wő%x<ƿp<5~a 4>'SXHgoQSZxXH9KVV/\ lXhf1,JO^!"+# +d P'[`9ZA C/82oW6Y*\Ѣ9r:ztB{w&tn5yD[l-YA1@zmHͲ쟮gRVb[ \qYq3PJp5llxK.y #uO/Ny},%Q_7}侳'wB 馢+sŨkԹdNVutfLHnT:mgR0mWr~/~OES$_n_u x7&T/dY*UJ;8xͯ{<?]u~XerȕOkWa{v tv>'V|OR_[n A'XG3wUV'7@/dV=-Ԁv`ܳߜ~SH 6u }l>˯KYPbϮ>geɷ5bX80ӑ$%Z0YL{ҡ[R{-\ Jlֵaxe^fJnHE%,@UPq9ԎZIδUh&ghW'D'އRu;G36W5{?G-ӎo`g+|Mk>q}YeeB&;+ڞo^hW;†ʙc~BFND{lZ ̢=~]ո{yղ5"k/}ox=;-k12񠔆b9sURJ]rJOS/SPnFsP" Q1u5n*Q&T*8[| }?bCwaі@q6_j m 1 Kjku4zd>'jl3" U  HOM@ \^<[^k`ިk񁐚F84\ U.1uaih{͇&:hCR„WGޡ^kej}GTJ-0{lꬷU}.uFy@+]/&G/%m+'ǝ>K&vM=!hUI@Lf_1*mC C~"{ſdx9x=훵u脾@fWG 6k&Yp79fW|Nܓgk$#wRG]x짙ߥ ?OzwNJڴvφc4BwAc"J%^ZLbɞ?JE=yP ؘ67v%I$gFi^!6t}2c.Ѡ 73U <Ϟ)`}|P*X>j!%85A:,i{!$^pDLNRXZ|(uoӥF/mkVhhGV[ uWt])W$fR&@ZSwODGE @A>?1J߂'F4yGM>yWE8Xtu/{I]-U# QOz _,JXDI?$:9xYEo4`BiClhC\\Uq,i;`Qֲ *yr#ܳ˔74~1FFI-:"Jq1,#qQsH-7H}3%,stL,y-#p%AU{#V|tKӢ|< VyRĮҋ?fqq#Oiu|ʥWcI=_18 SJTU*LREh>1ro˖jW b_4Q4V$K't ۍF w'/rA[ZoעTk >p.lѿ .)Qq> `".CԄu^V?#Krϟ;XJYB.uFƜ}rI62s_?tY~bYYfG9֝'/gyA7[7_o4.i*WptsZt,5tyG?\}_/͖G=q]6{'sp-w_|TTLڶ@guJ asD0- 'gE(&/݄6\=k'm}qͱ[͉s&+DOCFUUt HRAYA4'x=5N|*|K5I+#_.60E,۠;7>Ē xu)@WU;#Y[^ Neש$ U@*bݧ\;_k.鄟6p hWǶTN/D뼘)ro,MZED,: %< G8 ÒᅘF EDfܡ7TFW?J,A |u&+8eA_dۘ[? VtSDtf#jS~TjvtCRK2+fb,^xK [iP5]"6۰CKBY;G&|oDTўV^:Xs$b:QW %/Z7Y{}(y@^} 2 RQ;_̽w݋]2J3P zQ''ɝ)=+ BM8L0&IBCpP JpwK[eɼȡ0} MTenGKӾƴ{'/Gm )xs/[V\9Iu.ڞv˧?{;eYBK iD_Ty2I9ϵP<[q;5.~,84S9';-ƁحIXG{9yfk4D_jQژE^ZF6YHAo$ށL9YblB(LvS3pf ( ~q* հbKiH6`|Z=b֋[UVtSH*A "s,  Zf:(@g@5!D=GYPrKM M۹$7\\Z ?cb~XtX\1Ԍx̸} g05?.3/b=XQ:e#R4ؕ[mQت\Za =0O<Ҿ"YATߦR1bؐ/@I \N)aWMRbhZ4PKxHа {mz˅<2N1e̶5SxAUKqL4qD.ep'+Fzr^N5cpzO}╝]?suY)_}C8b*GO䖭mi? f\̈́+.%&n7u8 B]Š;Zp !ȳ&\&WV_y{1Mo>r4Tl 1DfS[8ƅ~iV,zg 8Q)F+~G^%bF=, HMi?"|)q&))pGz;zZǍ+ZOj]dnN"Fo\^̫^-BNep*lZwnPaۊU A(!5bW/[Zk:`K[l~ѓNOUat/-x1.BafJ}Bpog$ ʋc?` +f@o`000Gv*LŧY i]RbM^Xpmr5$-%-nU86D:'lp\{^zGq3SN'7[R#L63|~8}OX6,sXPC!%3ie!vp:_:mWMpL09q?bEAae.[y2T,+s P ~T^=ٚc)ZpT*$ ӆ-y<n4RkS'Ӯߊ/Xv^\c[0"Vi0x׶ן jʌyk=RA"+k w/ @Od&~W ˮRWL~r e]_jO> }W_PEsY{|2S D E|rm\W~ N\[-:7?[Km lj DƌE\m,KU&sC`WwS[pYܞ{e\{gMPb0IV~OŸ^쳮YzǗk?Zdt6lG]zvwq-odOr)SGRjhw-^Xzo~+s*8%zhbѵgSˆO[ ԥ\9ƋǷʉ3oX\;ھr ?sD<|e?k0яo,JL!` *S/gu݂H JዑsxS1a -Ok`1Mʄ.mMfCI][{q(h]03ѐՠAKK#m ̘lo$ zׅd[U;JA66],<~\szulJ:Y2+Si1 e/fԌc Fq_ċS`]Ic-g̟XOpqZcۂ7-, Ep 02%=VAB4D-4x`, XKO,X^ʈ?dИ;ݠcI'6+V_72gem;𛧋`6ӧo>Lk6+ތ -mA/rmͧh}{]$>^f=>V/Up԰\(g,FS'9?]jtQ" !_c\ _lXUx_Ohjשi0'sc]Ht/5&4NMGɎ7ѧĊ>cm7T=d ^ᇮUQtos wn}L:oD+n$ pdBzm;v1#~x?wixo;Mirdi'z}?E''c{wZELвuyx㑅Te4oLn>Qy}'Ǣpcm<ܥǘOyoZf~:stY9?5=O^v%u@DqX4gGqjb_HQzs9Rw욹.: }HWEEI$'s $ q8<ò"sscgW1l pszL(([{$$ 4g lUp?,|fV|{HhcSgR$I8+m|k}rܰC/g(ze]ss^g:pTڕũ͒4[YW8뢉)*b#O6ʯaoTShFY=qQSؐ3lKry##jf0 CDAF*!oQi$]KEKeW-'F v9VP 5ۊ j7rV X@Xw@69csmN!& T3Dp XZm  +,XgP _Z.0 v"_<XA1NUv E$ڳpw`52dָkv4/R#OnN?oϾ۟6oO]lwPeWГBo s !3j9x"u?Hx쟝nyfk^)z~#3_e1 1!![x%Oӷ>>WO8jV.]N55 }m_Zz@1YQ;[%۾A8So}p98F:ʒt*-"x7|sm1{p햻DW[{mN_} oW^vyeUը'\IgI +?h.6z:,={>G>7/Ok4.ā1iVVڛf}\K w\Ǡr 㨐~]֗>2o~Gr~֚.V~j8;G!㒡'.n[2r :lV2_k:G:%DLҙfwF9ɭ땹B -{gbxo:{ Gd(DaP5(8`ixR;dD)_8-O@ˆv E?!B^_ty%\Uq T8MU("G'zN7w\5c- _&R U(Pk=F'T}M8>lZɶSmS7ewBܴm`Io4UݝfP[`#|C%dT;٫/d  gC*d(喰@ՍP' ߱pnAts]It(!E=UHC[YouzʋPAQ'GUjò##aG"33BZly'z!kFqڌ3:2ۨomWK;B8fi{<ԍQNz =M7]nvba]h?kwWGPSJ.[]:e_ofCRۍPkۡUz,;L=2Spo{S7!EJGKcFm&m킳wNv>xY37qwhҠ7)yoYnTe ̨|`?c#psa omL'ٙr)'?KψSITf NkIJ~>ԭgεgMO1JyKGVw|^u ńlCnk,WjGwt9Λw!G]I}L Eƃ]V97Zskn<)6/&f$B@h8!U #}rM?7MOmB! p Cߩ֙[?Ъi7&U ŜǞ?'N~ Ҙ t D̝QY6Wd^\13qӋ~s6\ē͕NzjZ];ѵp?2OW m>t4.O.ᣑ{~ r$Vq $9NcI 6{wQr?onRP?WTsٟ_Źr8z10ܫˑQ&1+YXl}kxOvvfۓɀc8NKa*ŠL7]k.xՌf<rZ1'kr滯 s74kCRzur. 6Z;2/4?/$NB2&p2wwFgKRq))bN%N~:;sDM)#. d"ȨVza姩/ 6`:NBl*.rs cM,ƘdeUXX G]ݨ GJO;JE6t^_:h|I#B'z'r^#c<6܃ ?ӟwʏN)a84`ݱH:ez=8mkȝTf;崡eEYXt Tć1}ӝ-'Wcp*ٟNyb#Ŧ- 1hM@IsaKvn] rxѫB`tw8D4.Zu1,lMFJD j؀@@-P#<dWȯ0îD^h~K'tp,MPs<&{\֑vIAd$ a?J/$WFg *YE]%=8;P, T՝4yk`t~c E1,wt~7BNJӡjuVyL/Ol.}؇6f$f+ܟmnE]Җ'fI[p^Gz7ׯ}x -7 w vb#noazmyHq-Ҙw.BQhQxc7q lcy[ۉ21}tۛ>|wp|`FnhV`UBcWW"RWcoάaf1{?>yrje&-%9J0O5n<W}ɇ*L(#RA iHl Jr{S91ɹBvJipZtR0Rč;7rNJ]~2ۯl0]f\K0J=xNd3C-mOOmX8dōKϳW7.2rrJn=qǻN7\10lX5d̎nV(>wǽ0P3K Q2P9P@i 9\\| GMͼdP, RDHdP S 邚Y*7*k93:43(t.퓔ykR;lV񁐽B #!Rm@~e:dJ.lF~ӛ޸ P[u(35UiW6AL|. $%C<;qRJAVaFOF4nmBsh]st$Tՙn=UkI e!`V_ex{hd9 ӁY)HZgLVr4O%,h)@ {k)5Erdzt4BJtܟ} mRJ\=~Wl ߅\)N@ wK٥U2[-`%\QyG;t '"A7Ǭ$Q}c?}~!ƫu?3ۘ f*&}ٌ}~W+t F"A(\Y68HL(, *_& le_Л0Ygvq".~89x4ѯѭݫG{.q[.诸 FLyh]rk[F>yF"8Ɣv|οhWTB@6# 2!R53pc{΋njo [- SVMNV?yh&HA8ұf2aݐa5[qPu2䋎٠yV6y-s.7 [ZIna<xr NI>尝WPW $ /u6 E:D|PRڃNvQ]hz)IBP)* cKjZX / ۣgP+hr륜zv1^&Ը#y/ͥ&l+ЗʰMiN5>rn_Ь_NAtGoxP<TWG 5}tzE{kۢxza+UdNQN X-bajE9~RioP7f EԯSm.cڋ?B 﫤/# W#20GTCδSO{6I#yi|KVe1 *Yb(`"%\2|Tz5SoxYOkrV>~e>]zdOP [ttιjewtp@P uFnk @/7L]5 AO,ӄ\.WoHj@lU |@6x& Hb*t# |'20\"ΐzh>RuyT yuapZrZүJa 9W46R0ҙ\˰1w<݂ۿwP|DX-"pEԔ .[cҔw%Paa:D$,5aBXS'=60W@L Mk =PJ̜,4s:i.*L"P841F(lD 6 ∆NnPFr[K=Y۰uIݔOsMS Xci c?U@,ZCa%3DհCH<QAqC)JPp1sG2~&f.٩qya4xr@4Ao)aC"lnR.m r2/= 3ߟUf۞O6ޖ~yxap-gSF=b|fZ?θq*f.ܰsRk&C-[Aru>XbCɗ_kUx`BSԤgOc\ug;m3qm' rċsWs>D"H{aA0/FJ>jT2feݏCPm>{b0?i Ϸ|G}WN_][oA !a=OuGg촺CPi7GwEHNHdN`T`qP1;?V!8 z.sVnL+]`t'0]o88"k}ǁl9Zܤ Gj[ Ì4IxI34*ݤ-JjaC&Hԛu\ ]8"!ͧuwzԁJa{/p\@Px (<C:uO_$f!"?/лFhaXԍn틦VIfRܕL+}/m1/6b#t< -:ή"J[6S*>jDDőa$B`}Kڙ38Kӗ筠k(:DT'ZB:?N+`r)U;O b@vribRۤyL[}k fBC^zv\Y 4 / a ڂO^۟zSsP.J@>/N/!"70?=oRB"/^1@@zš,ػ@aqn ?o:*Ë2<K03m/BƠCV@P3 Jj_-0%W&BPu8V':.X\i: 1hylHy6?CjXbJt+c@@BawE. Q0<i:}7D͚Uk:xdљ!\hOELkT;QF-v"ؓӭbex}+5 /⤕ۉyTa޴[ScR%'I5kǙ5r~|b!lG=6Wh5H&])7ѻ>beW/bwS_%\}]t{69B `C+˚!RB4B@ 1,$&k@Fh3 <+lkq>dl5bڪTr \ D 59/8-u`7>h +p"en~DqzDM=CKS{\+YxeOz v xou8 '0 Nq2GҭGňDsT[+"\ b)AU! %vQ,"hMJ'ԥ fjdm%ϝ g,g5Li͊r>;>N)̏?D6Y'Z%'f5 RTQ %0 B_kF r'RZU8Ns{%GIa0|P8X ~ ]}&lڭv{|lnT0TNT+6OZ/0zD N~3; X{߽h==1ERY@z;h%EI-w..x 1 4f5+u"Wn\K/mkty M(@0}Cw׌ Mb`t_"㕘űMSrN]R# d@LhX>;* ìf]rSzL"C` yzAU} [Z^l(w++1?u'ޒn왴yYUeLR'|+ @8yG*N 9G,=/[ ~ \@#SN}r2yx:/Y^̾/.~m(ڑiu\R>UjѤL[73?4kOyzG=YGrx)w<~۠xB6lYĔzEWu7jךcȴt| S*d uܬ| 94+G"ʤ)eM}W:Gqơ׆'s6/ʉ9,Cݹn8&9h| J#(ApB\SMgm]D((86T$sN\|`=9p1O6m2M#keǕB(^FiI wD$J')[fCx]э.ZܳW'ӢG݁vF>}#=V|; c'xcOib]F+kn6]gX:FDRO !I<2[t0gͱs>.v'n><}k:"Akan.}QkZuU?<+ZQkX>]_֏Zλ~ێ'#q,e1L |B<}(Rbu(y*jC% ) Oi}}?أi5>{;OLWnw.ӶGƵ ȫ⥧wJܶHA}XCK{ s`N)1uM}b]K[h; zAR\_"ЁVH.4ӂOO}h~;KL FzbHtρ46M9i`"c}"4ۡ8GL>&)'[/GլsR:coCCΆW !v/kmIZ[¹fL5'S.lDmA~Kb.fM{; DH]hY2iYL]}:'U`G/t-Nlե~>pNVB3,@ E8l>W}O`Z\qɂykGӃGڋ7+)xì+wn![WI*CX ϼ0/^5[k+LĊ>֞JR"L1tV 2\)Q>`RTCY'4$Qie!*_p3L8 W7#.̓񦇗}lgm6\q)qGӯǔww}눈=ͺE:i<.] 7syYrggхUf]*oę“7OۻbMJ9 5:̎0 cJ]3&3IWYeUR E :DC)1 M+-~~bǫv_ JL˅2QhKJGۈ(izԲ_#/Eͱss׭vMң6Mi_YzԫJ֊}|/6?/Z7>e8`M7V7oOzI5C e o$ׂV.;c}P<@ fJ|d,Q8r/iI&mֽ߱hyvi~ )rn%a?:]6Fg+ ?3yC3iqMM-?2'(P(RpıUZ! Y&xS т!AC>@0) ;pBxL"zӭ7/D, 0>DaAĺ?-z:g}Dڏ{ uՕX7ў"lF,qYe#J'\)w=B\s|t/b'>ADk檂8mY͏q3?萑{gS_%^®+xndXa# #vLϽe1cP~ 51]&Owтk:eyj #w:s\3(erE#>"Gh63qXO#=GZbP*@=LTfxquF"fFo)k.>ʾ1CwA Yg3lmC{QJm̀R'Q<@@#n'LMI lдFxي/@PPf5b03Ճp| Yc]Px[gaX ݊R%fSQBeErz Lgzb[:'`!4-1'FH^%jZo{?0'$Ί^qE.\Fdѷ˥rG!2ӋU@ KFw!fgREx+@@8xu0`o4mt=kg jPecDE@ 'Ozs@͠jMz7)Ih~ )"Lmx$=. {Qz;m;o<@&FX7C`~jH-P@B?UCd$ A0=zS(U_dre*L.( vɼMabM=sKV%_{KswGײ"k.=~ Xt[޵;:r*ޣ.yMD!"'ع^pʈOܦ҈)jVCvtٓt? qu©n;5ZL.4l%:m\2秽enlVas>W~>9-t.k\2.QKv/=<kA.L%jTRJCsWr핚X 1'+rJ&J\ks*SkJeˮƸGWx_v;"qyS %.Z\Xi}l0;̞V(; ;m HBwyrgP!L"X" ̠tWIrG|xkObU9*]11kX3]onVQ\ v1/) fAVJvowX~I^NiIKSMe\B7K " 6R*rEAdQfDacvpD/ˆtʧY0"">v_hO Xn #bz ~-#͞ ʠt&6cJk 3CA:.>ʱ^_þ^qژ`"F` DH9jDA@ Ti/wWI8t@bA@ZЁa( /0b!tTbmi2db>8V5=k ~UAO,:CN4BnϴNtZ[oRWDp;Eɓ7YZf{l~[4񮻟d9ٳuMU ȕ,uxmU=@l48mF/pZ S,xh%f@v JJPo^YF!]OM*˸l?j)l6}͵k2gi 2IGW͝<-Ql2B9O#SؖzC`ӟ wEw'~\~]ˈ:V=õo0",aO?Nmmy? dg n";J@ \hg4EDhAtGԆRX)ֽm0 m54) ,1WiX2qT2yh\DVBPS A -_zNyVS)l9D(ZmO :ܐ`Wʔ8DXwi86*]`]p >UQ2c@~ro&_hvat׿C`8g""*BD B:F®ƞ 1ԥ$U */٪ֶ * `K 5M 2 3hURᴴi"dhGSYL LrIĪcP1iF[36k֌AIo fE BbŒݖmx_gb״xˇ,::@LM`6_@0`+%G@qJ2bL?Q{ &Rz\, 4:ؤ:v\B?C|ȅ quB F»G ôF>}+M >nk?//C8r{( AcJl{g;~\w>3E4YUT}Z8Ž:ڑz:HI+ AVn{3v cDqVif܏;|6n)Lj5LW1E*(H ozK׉F0/(/#o8aZdZ!wtH0R]3i\0ַdlx!|<!a~cDZ9SW̘j #u>x I+ b uT}Hts݊8 (ji@,~WzkfjuwF Pb m qya*@dEshRޞub{%K0i& @X[͈Ί[>{JY"z]6Dh}^2|HJ5}W̿  fj`bP o0 8 ^AQz@60 LZkIVR0,M~ۋOZ'EۡŌa#OF! p3mvJV%è̆ |( %@#CDн\7\7?\{xLDh_aQM PR.d]ۧ b_[֓D5D_CXLڻ ^U1ᾣ\0S|NONF58=57y^:q*IC9& Y9巃stL"`Jϫ &5?VDNt[rrl]?Gl InXS-Tnmw󝱓TLrWǍ_O3y9<KA.YAlU }yn.'>}ˤGƩ)M,zw }O!(Yyf[ٖNEsr斍VvpV@R݂V2zU9I,:%MPK~ˀ@vjG*ThDcJECrE2*:^q}v~lc>SG* ]ᙜa_b= _WO: E3lo_0Yu& y*pHw`Lq,ֿ[۲#ŬzL,,T}vl1DM_迪kn}S+ {][]CAzo4KǏXLwM^IȮ ӌXE<11-0fځDF aW1ԁ3 N^Z +y-V4軼Xx11 ?Y$yWǦ1F%)XQg/DD-tDa*63|r&(v/PhT>xFVx`SHL`Qb8 NG 0]W+N-BcF zp`];Or s _䂘 x8+TN zXh_ ⹩Eox; Y@a< Z2A#"B!LDitQW ك[AB7%`DTe.zi| bWGs^B= Ky5{aޑMSrbaż+tLH3:;"`JgGN֟ƾM6OB6!O\v<۟ /Gj=a'v,$?c4b#/ya^ٱܷT(3Q~KUCrzas#뚾t""kʡhJiv?6SqSW!n ן≡?zj:_oRs.@$Μ3[35{Ь!ԢxS*0ީ=ު٩[6J>}޺] s?txq .k5F̎ ޾DJT!y>A?Lag7rI3"Fa<K3]Yj+øaJS!^#x}rul$6mřÍO7tkM |sJd~w:00[2I5#Om]g -pPoŶ@ ?zno|!'3n~쓿+Wwׇsn-5!ssOSqc{LJepuK#E`f'fQ? K,ez25O%Ut_7i?<#ꥶ87 )yؼ'c5m\UQJGPjOlWO'wuzng3߷gĥtuDjzDKK<џ׈JG|L[wˤX I<^/``$`588-` 7-L.~c3\q"wj3 7E6||4Lt^'^#L gVxGz#Wڐcaw=@t*T.!PQ=,M,tT݁O܎lQ$0^FRػ̽6ͳ$AّZQoDHMb}6lyO!͂0PoP ,9B> {z6K 6㻟h<9?6?:Lo:L\Q! Q!5jb]gf8[>㟨)=lrz 퀫(,hA7M&zy4,ì~vLcC\螽?&}Vb\U`]_^`X_IJYC@Bλ<ʗ_Ư^E^1ÞF]puetF4Swy݇'MswBa/Pt@#&rtlxt0I`޴JP60 N $5+x: ?vCkc=Y9@@v uTj -yFY!b\_ܙ(ԩJ;eJnh^gڧ21={GgX{y״>}Cn{$>+]>f<#djQҒa;{NQ!#wxvyϗ_W艒3l +E~%]@CTz +&fvkN|&XT'Pr  Bwo&[ڙb#.`Q]$J\Dʓ1x#QnYO|aw}f!f|ץ@QvU/t "_lE# tz$ էhq`܉X][ l/G}Vʊ=tl>ֺw-aq?ػ8bWpVZ'6&7ZVZg*޺wQ{ zyD8/npa֎A|oǦGv}풴mیhjwmQx}cR?@Rrx  k4R#:z{@3W4a>4a4-χM{Wko_6bUs&»t|8]?=sr@~:s518>vB5Zlr]KsQZΝaFB|@pP_yT9By}ݺ0IJyXZjQi20@1h^Q9R`pha: 1NlG=aG d:ܤ*5 l#ȝIIY&#&JV\}x=? # r@:BB6b4A2 #fa|lc:JMCb 8b#4s-WB 4(40pd(v4+Ss {j$*@ PUFI7δI{]<=pQ9^(cdfPya3j!@R tOPreCXbsoeAX ;` %x 邖Tx,xR^ g e L〳f# 1"5~'UWkų^֫W}O+{4>-:e;=Hסzk!72>jE@Dxitx(YG1L+ O  -Gfjgn \PH4Ky)a wTΝh>5̿.fPw4Hwa\M5c*CAoL{x.02`D`Aktخp/@k*_t>gw'hZNiV C.֑_ l@2/[p Df ::;~mqIG8gtR+Gw-ϣٵsSI͹0ԪoX{2}mE#4n#?pH|Z ̽*u&=RLY@N[!wmi0#u]13&(k#g̙o*QNeG}&4H쿂wY&pq9k^m'DC;k;nj ՆhISggS8̬A6mE9\VrgL -EkB{,saMMSb[ L>K"4cD'$Y,,WC$ICl4Z7p,P ys5ѤI ϸ`1f;<]4TAIVj3'fj}wB+(KPаB%H!BX BsQAD|} pf#\D4q 1w> +:/17goXGi zFSI0 }[G+S/rΆ{=5Ã[B %! k!Y"P yttBuŴ rLdڕT`(BCM%U$#_3b €DtҴ:[[ºBf(+4&0x&p Yx聞7LDHPZ9kXz̊6shs&Cҡa piU@w͞_ sAWZz[ٓ:$Zb U0&~7j!hFGW?6qoWwG꠹؎- ԋX&{Cu{~@Ӷ+PE|{k/yC\ {!kc:h *R4W-7۷/i =l(+ 0U_e!5{mr- 4$ A`($ )zkͤ;ڭ/I%=**4/5"XIQ( l?&/dր~i 9Q TL~N+Aφ]Dۆuf*uqmXV[14avbQ~ f\1lqu)iu+:-t2d2IWk%j\ [ 2 Ard;t V *z6}MSh[{W<ړ0ˁգ#,Ede{/;_|~d ]ssgyh oh>nG}h)pp}Ա~lj}nHe@SXCWmv咃e5k O2 @.A! @J)|yo`7uoqo6}j[{$i(+OmN80ާN |&i¿~F-) jJ ͱZ8|Եy|SE3U?k{N+  E!fnjhkZ}XX31V,{_}y:a,vRR鞉B/(ŹޕKLpҟ42 x5:5`ȃbb0:8Р޹ M/q͓ ڜ}~QPA j1y]^@@",/2ȊyѾFPs ģ^Wu?<ᝧ[6Xs㑋;Kq՝Me>eM$Nәumb7iޤתܙf$D9{2N1!ui1q;Ǧ'bRӝ1GKFKJM&@D p;?p !yY* n*80{ (;xYS ՁN2S@$AY=Ƈ4|mᨩ=gKh$X F%RĤLǖo8ilw=wesZHpb+^ H1]ā/oPvXvDkʎ;-Atǰ0]F.#Z0^ǥ8.Rv*$ :N|XhA6/4ONqGou4NGW3 !(CEFo4h:~qUNL|nR+{@h,Ho+̋qN9'wGc"d -UK `TEHwp䄟Q[%Pn!,0D<+rHKX|kٵO0{Ý*R viy&MsGhfrh(W6<>f̧ܻ.{-)pt.eomY6D0I:dPm{DYl>7R&XUv /53 HB^7!0Co9Oߘ5޵h֩nQ!;b!s'4.lSƦ㴼h԰+RvDIYR|oM Ac-=hwkD*ɉ=rݏjBe̕ǿ!#ldI0s c5V,n>qPñxٕcOjYޫ|2YLFM݋]Usk=eG. Ѡ Ȁd\cgDZǠ"`e[[a.N2Zj` `x +Gm0[ lv_Vʩ75VҘPRX9`sl{_R Yu.OqқSH$.{7)‰)^-D//uDe 72Vp\$T @:je`(^TS& 1 v x+n?l+XqsD1.jf42Ѵ7qk0pܙQ: (iQ[>wW~MOkd- D3]QpyHq6(,\Ҽͦ< \4WEh7 ^.\9yPvٺv)4>ϕ,c`TD:,MdLֈա\&[yqs8n Th8@ Ȥ BZ$~Ls^T 1Fu?FlZ"4K|`UWPlo՚q9ZjFՃS=Iw25+ i.Hg|Y$(NLJM 3Koxt+6򥴥-害 lK)XX(okM~DfbxV=fJsEK,|%GUƧؑ$Κ5/:|i-H D)X¨<ófKޓj%3e i%ZhCkFYhtnkm/-!q!:'w@R5H;kA) ;< b|Asي}: +4squ?rUcA \~YKZحkKzLs}Ԙ&n_}W:_G>EPZb3_- Wz:=w=Vmn9L!!p`0q6 tV8sƑQ;w..}ܞ5H/<G#P#oݮkb,V,Q;Kx!)Ux+ 7&DH%j6@O@+U\YɶD|<=JhQ.䉚kԯȱl)mPHm~JqN֟]$}!*[Zvʟf !7G?6y$ B27 }_ڔPmL:*m2Q '' WI-'kp xb?Bn hYѽM_ݗ-=es/V^BSBpu8<,ׄ8sި2ڐ mY:SuؑIdb"]F@XD6F,1`#dĀv ,@_BLa˳h~NbHU8R&k|" <pGhMqHHun s3jA哿T.(yZv2"Ɲ")7cb߿z >3~Vt6>psbg8s}pg<1R0?jOo{뿁 +@űg@[眦*q.W}K:iOw`0٤"0ŃNËh^O./i$Xzܘn؏PmxܖDr$89tr HUp]<'@desރnE5߈BVȃN>]Y>C]{L&M|R&]$oNm:A[dpi13G5iw+pcBy)kϦB^a_Gf,[A)o&}egH 4}?k-4'wMW];#M3ozkN\rWz-goJ! ow.>Wgș-4NcQ6*-\sO.^~sM9%aEFEw_ n=hw<|NzC9Z` K7:œS9PP@Yw:qӤ~|I[ƓӗCݶe)LpVνi~ ~j>mci|˸8v>?G<ם?^^:y녕9jv{Z 4֔Q|xxgͣIMxN5ؓN(~%o7zקGyIxd䝮v6uds?iG\'s{ucFƛCyaSvnAFtO#hRvoq^7j*LINl|`wgK2q+$1֐CqX?yna7n b?#O=FI> $\O얢ژ/:k1_6:[m{2y2-v}mrloBeςz. b5 < *a.žkasӃGwSz084ouMW4zٷYoDL8XئHʬA}sٹanZ훿41`xwslVGSm[O=vU5 W8y$1k <#n=/|¾hrwްFgPC sMb590%ւ|'*1zϗeY{]u_˖Biexz,峸^oA#0?A' y^=z3&AY75u?%v o rNݸSzËu珣_NR KrKk';o* }aI _"!ln+RGf?]Ux7;3USCh8\>+߻ O v|,GwQojv*pM2 _W/?QdGS (ኦ + :1VB`x\zs' ד?*kϑ-d?_,֕Czvs=M!D\c>?Gn?wջ\*"ESYAc'eh)8{'*86<;3hyTYyBT$mt}DN8xw{=sRuŰnzs@XιW7ZI1ƍ'-qM8\&us*8}5DXk|LgqzV/,4H`y|cǑ`-9z K7_ݣ4'-!n/ _BCK▟۔@ myXߴA8HIvOM9.3_^q Pv@8ݼN w LGJۗ ?הOPa~>ĥU `[.܍׏H$b;$q!<ԍ A%I$jrݨOdg?~M[߂loovK)Ck tו/My؋TZ߅p.c׿Q%cqF{C/{X\뿶9Xf_ xʡvߍ@ lnrD9Vlѡej́R&O[?!|n^m9tajw =מY0{}/4J_H5zpE[.!2)%=qBtJ&_Hʼ͌9ʋiH~g|GnE>z添L2{Ncsױk}!T{Z}dY$smF3>AkTq)D0iA@fP[#4scb&TSu\kWNuUZh&^B sL{kViȺ\Th#gfa\Ngr _!6 UN{O x'URED肯!agj#PKhn8t~x5=|UғA`pbsz\SgzCe V[ҥlt-$"ePQ >FYgX%0o^aV&4x p,ӯU79Nܞv/WYQ\dzpݱRj#pEX~,#/T)?h;ad$7) 4o0vH#FyNt}h0ֆz=2Lj)SR{^RV ٰbTr 9^C S=bdghC yo}W`O( ӐJUP1ýhrf=bHPrsջRҼOM,p\2 z20>U)թ@W&V$dO>ڐ♃oEUh(tDWa{oENaD xWк]2Yȓqpsi뻯![Im!W>LY ( TQùi~O1%Φ4EM j=+X5,!њ"ݙe^qȊ{俒YXix;dJkvf#-~4W2btuph:BTZmT&cAYUqlZ \ݠ`T):SzMdƔQD,Q 8+s3В8tYw,f[xVL<~O_`%bab:~.C!5x~;.}qͱƜ|3w*ZM}sQ 0Wx5sF7oo+77p23(WIPL}ko3{⻷FgB'2DUo4{.C4BFNeLHVo(uB ~EsIul-jwr2s09B¹ ` HC:TL= oKVA@]@PeER$ $ (EaKΚK$]%Z56T&$fΜ2E|IX7LWP&|uzͯG7<0kt\ VրK &p`o![1f,ީ 4힕 @ wNuh0S%Ҡ ' * u*Kh91l\Rq mV~#H/RF~^r/LՑc@1o~2 xHoLyActAAC"->Qe&N\M)BZNP)mGa0vcæ.p'2Rs}@xʚ02 ]DQdkuUxG_U3*^XUn7G^Wq0D4>h]:[3*JۨDZgnc[UĺT2n8zQUd̓k*_P~za^UjvmCK/@ҔUOIM!k92Q>Gz[ol[3!<*h>2/z~_ SCn4|K~ET, -HOv+?DQ!W23]=R-6B@@V& OR8dnQSܸ&Zpu@eOGpV^iXmB)%5-io~?~,ݺjK5ZVmrۖE/L ^Xkd*` ~ vT WkuvۭL:-yȅw{-YocPV=3~C<S2ēJtS\&М=YzW8WZ(TU !#]Ѣ)qy y&e9@Zڈ͜^y}| V^/[lwm>2%]QTꑩ PU?qc 9 Jq@ )*XKIp(e1$#!qV0ۃ Pl5h F9bOB(.A!::1:d"pᏋJDu x.@N}P /a80sF`= 'Jh@eh 4ߒp] l$bpGMUL4vٱY$' \5 xHC@( Byi&=^eHS6Zg/0a9a0,B,1N?=4}h(wИ+`8W7S!J7uGu|P `u^ tG 8^D\GO7cSpRO&&DoTL|*_bv=S +ઁj:ά!+WS7ʯypOdmb}ѱ8e `꠾glR-(,<&U[zBR*֖WP;(95w֓H R*Д 2"B M:؋UhinݣS}-57\šMo]7vXR7:^?oGG'Ě9vͿ2`3(T #I3Y4JA>paH6\pkš7d@eF{+1N'ainE&DsƧQ7|jƸ_P#a9r0r8 Cݖ M,"A-AroO} tҿ^V(@1zVXbŇD2jP-/`k㞎 ߽EL*~?ڸMg @3R/ƂLj1U БB dMOHKB༷6iԫpiǽ/? A x†H<>5IhfҐ {~wunp3H ½ëz5;:kCm#Wp*pO¯`i{3X6Ѝ՟pvXW}OOLeNzO.:&m6ǖS`wb(06 b6ѡtǤ:~͢ﳙz7բV\pr,=uy l* 摻逬9z5:Йpї;C>e;alBq-`u, S;Mw{ p)ZoQJf%\AgD^3A]%x >< S1Ssc7 zxOCR y^#^>bH0aZ렬 dz 7rY*fVoZ!_]nN|?% AaiQ UFH"+^yzfaj&qzuI,4uQ; 2T{r@X bTm ӨV8'Ȳ! KA3;7yAUtMJ^9;}%׬r d9mF9+1+mk4Ȫ?E3U2ͲGr;K ^<9\2&hIJ_媟.^*(JA xaBrޮx'x&T(<@hhyҺBܙ8,򭫡ݺJf}iUN]=uW]m~bL)O:o[w.]5'n6* Ny$\$x1asU.tPrb{JB"$U-굩g2)r MuG0"%e P ?^y>ԩDZ4Y@G q#pZq>B)챴܃֧37yᘝɳxs]u{m^w; 6inۮ_J8$UɪA kkXa}.k vg;|;jڽº%q+dj#p5_,s{?>DfjL/Y) Z_|hIh@ϑTB0"XԨXjRs3 hͯjut|?Sc™\oil+HUSf@LYt4(?"X'Fs$5癩 $Ek%pn'V(n|^4R~u3geDj>o[qx`| )];/^%MPB$MH Z}J4-]zx~ uf{1\aEI5т)zǹ-l1 r-|j # ,r׏^OK^C%kL \ψ>XK~J;NP6$ø`""wIosl7Ge*>Zq<==PRwl [WMp`hܙ)4M i)8v9x` \\uv*`L|j>S3JD9_|. rr-]Nxɰ@M![LVC0ZK/fG8'&Drг߸nx]b0bfCG'@޾Z! wyf}˨UGݤM""i"+ЈUB)@ ^q>J1wweiOu۟W+jwc= ށ(0/ϡת}W`hf cƞy#H<"U9'OpfNI<zh fNE@8;}ȀLSLڠ^Ul\]]An2Q-Z4z!yRZͫUoZTTtGGW4<Jkrsͫ6:4yq붝>BQ~EgѾ (hC"GJDs՞kAI,)5)wI%xJ1`6-_pABZ S gx݀m? 29S @cpGvXFBȁLqV}kvU/\YPX-G}㉳euR;f 685,Hdp̸^G_U#g+m6^}a4i" Q-֡ŇX/\ SRy *l1Jn>2( 56bA-#%7(]JGGvE'%8JrNb\* blV*whQd3Tк5<*4U'>5ф#1Qg hǭjɠulr5hMfԣi}nF ;jFR ['\`c5Ե(;w0ńxg}4;yDꅚJAOݳu:|BhZ)z,w=!HV,^>? A[V3^L_k{*!bE>Z;9g2f` ]Jd\ rbק[G?H`9I3BBo3pz6&H>I#Y˚xAk(^8CWyp]zJ:<:_n-N/iKi\=(`m t3z2-DoBnh(ehI"N@NC4 P 8ڭMsoR+yku3g]/iBKAC.h}U%4M? vN5 Ν[)9Cx=E1Ļ4_+ܣj\yQ̫Ύz` JWm{'}qԄ Nk3""qɅvqA!D'GWq%<Zٗux |Ya2NW}ϥ^91)Č {߻krlNb=OWuͨnqʂ-Lw50=^fVJuk~/@8-,BRNqBo#sZ<0+6| |( ^9ܠ5cb}#G&h!2]C@1 Xw$EqTwyόyM)HKǗO6;,XUF:G hD]J ,N}tw^Ese2~E$ k7o^̓v&/( yx/\RI:nyI #,zLi=IB2],j\NN4L8Vo*UbKnUψc&#Rԕ(95itU~߼MJX|&G=+yYtDi۝Ų/$J;L$,% n Dy騩GTwa8w9,9-:aM='1\8_Q-բBX k+yblnyХavږ [}Y`f@`G(dCߵ  ;>qTBOZ|oW9uVBJS޼3ZlJ%D`%e9^{_ AY1_Z(ۿ˜o45 5Ҵ'G<|*6MóM(Tz K邗/~.[5{&uN@LK.)uQ7)a-taҶ0{-儯6 #L&5?4 % c ͸Qe Mb[i̘%6>&pԳlE%oxlŀ뱙Pa.C/޼0%!0RJ  i }LVя0< 4NKPA*eІ6dLbsY PC,fDxsՙjq]jp)yDdv'펯ZfBE(Q(NB<ڃ2 }}l{Lxnp9|)`H_X j J kpňH.%58qVX A(h2'q/` 7d"h@%F5+,:EkC>Bkrְч)wJ^ 2mIyqř`mD `~,ܒ;\D! + B:HI3m=9 &fbٹ$A!w5۹Jq^xeG!3}?*3]r#YՊ9KzgZr݋*E°v>pv1)F'M)1 $],zsO´&HfL[6 6D[P3h.`µzǨLs& 0@5] oệ?8o2?`Xք .tB#o a'|=  ] ݅K3] Lk߾aTW~U_6 .LPS@%\I B;`̮hP)V4S63s9I"C 4GX0jYDD__-Sjmj(` SWL#on㥂 3L1;hM:|jD͙ UuN-z'%Jj9D("!3Ϝ|asCʬ^%1Lyv}SJ^}?pȘ*9[fq2Xﳮ~)Mwvr9BYV3DAa)nP!OkG2u`+VX+V:^3a%iZ!* j/ld#ߠmL( "H&\1 {=b0Lk'ݡ?>fTD|gD_M$Skl1<}ڒ&LS moVxi-r)r134hJP"еVGBE2.Ymi7'=B^p:NJSJ g=#UT\O#(I&ߊPOsgnU#~dY^WFse My?SUvTy>j}>y4IAɁy| Z-A1k+܀N9:ڄ%="<-M|֍Ќ0a$XO B>u-  eCmFr{ ^?.Di(8zPED/~= lZ<6LJp>L*\j'rD\IP~3,9cF)Yn}M0F0wtJpg^cT\| 9P?d9 Z"ɴXwMmdggԄ\\>D82-MT;W=^Đp(T>C;ADW() l˚EW W:ԑ+`t(< NBV2G3ČeǙh4NEidȒmhMxP%ZymK-2ne8:5q<.MF3&8ۤBҜ0^pW]w=6fނ"w9J(իk;\<5:JENdYwါ*u!4B -Vӝ=j*I:1gmÖU5y'qYH:9 BI32 qv;U:ltdR"IG)FRǺo€(?\&WX24ґ)L2ICDG%M{,߾ʙT="=mխbo)cgWd*UM:Z)TGy?)Kf/JQ:qP$h30}B s ")KaV\-h8tRWJmC|\JN+y p #b5!@cÚWitʘ^Y 38YYYxt[٩ZdUzW3"qKEH:d ~og]HA #Jtf=, +V-B? &>Hy$jG8c LA}y:aHS$T/y+MC… SU2Tq [9vpY/={q-L6j plo"N/}W и8#^f A@;aRl5YJ]o$o7Z--*r1C-}T ^J\S&q`5{ ;|b-Y{5(^KzWX *Fi=8Cٵ!=*J0 2я RPE~W}Uَn9lCVL4JLkpn5RE%ՏD>t10\gجNe g@. ,7:m d!lC(-6h$xZ.(UaD m3ʸh%Uf0Q"6N-']}ev{n_TEDo4x:0Ýo  ?)VP`vB +=Ϡ!1\w:Ī™z$^ ð\픿KW{4V=v"Xܟ@ ɵ0@C q4[nc0~g zoC!?BYQUљn3ʹ`B o?67@36IŰ@F*pJϼxMbSNpaFckȉx1~ִه5{\%z fmr)Ӥq|f{QLP)G/}, hyJ6,s.!cXV8hjUN&ˣi?BS+i6QJn3LhA,K~yr% Sj∤["w/@!% YN̼SaVaGfkf wߡWmX-qo̰2AizTTp XG=rOWTKmaVtژx͹SazlLc^P+QiYx0,9K/wޙ[YM&w`E KDwP0tayבpQ'=zm`Ɖ]wƛ-ۓh8iv|r?wÃܪ‚}\nLC xeN]n0#0FdQ>}ca&1Bصxx  '`ݝN#']`c"|b+:Lhq "JՓ^ N]C@FBFJyqaQ,iW}(tTXJk>kBfPI)Ig(JCkRnŶ\)!dZl2Ϙ:(`f/teJg5Xs}L;myt0{Mu5Sfpȟ m; IsJl+|^8_K3n݀?Wų27/^_3 ZH13 in>]5[Z[gd#yyta";甶p~v4: iBxdM (ЍLSqN>ChF\I. w,lԁ6PM bC`|êV6hƱ&0V:h#<q",Qz76y_;0_@"D50?&ˇB%PqR}Jh`+Bl,C|2ZUmZqҊp ;?C:/R,4u>g7GnlTE((`bb_fwYNn­-0L:g777~7=VPCG@:v,Eylwc0ql80D2M5Zm" QXCոvu:G"P{M;0O>I h0}: D^W/!PyKW _ xy5Dt$I);d.5~r-EtLW3-`^qQC idիpnoG 0嵢w3ĿG͇͏NDJSy%W}h*;T;iJuN'O鐧\n_fP4.D| ض5 #MMgpt>z.-3 +" sCtRH_s@13~bT$ӫO,!I7Seeq󄈠Gk[f\gfMm}F6m-$;tGdG蛷hZ60k"@:7kǬ{rht"V1jّ'=,ת.h$zi9o.ob:dr%*Ȃ=il aJQ W^ `& Dv2 0$"kqa-aX[LؒAQ)hɄ*P%R 3535 ?N.̗a8}r7Bh 8ڀ-zZ^m}v !>́<2f4~{K 70P5a!b5J]~dz~G|?880 %1؄\vzW-wEu7Ĵ)JFC)c;z.ﰌQC-ӨID%yqx?9;F7;QyOZ }A_4`}+^TA"TQ5fv4q }L4d^ePB> .BD]42OFQLZ69!B Τ5pKZ [5}*Fl׵Bl}vhKaX \]!Ө yNl1H:z,"/clA=qqGk= jWP@0%Vh.{L&J)Zmw&'wwjQg, )Oi4W)=8A.+Բs?8g S=7a)6:٤qH-v4@Z0SR4ͬrvY8}1:km, +"Z=1txEa0$FaHIgR<@AZ/`%̑90{OYZ]S ٹsJcu0͆$3guU~obJ&G5'(G$#'C8!WÖ6;} [jRkv*}ηM>W|e27 [OkM mŦ*͈-m{ADS>*!yi:R;R m)hNE|w5#`4a2՝ ?D*la֏:ӬǐSoGh @ >V4+DKEXm79fΕ{N ydeRJhӨ_ >y!Q?[M:F14fq4A<< P* 2HX!CXsб Nm%>8 _1i3]v'f+ |? >_SHh-a _jB-#zM`2C7v{q4w'Fb$AH"-d_n-HR"@Vw!ף{l#KС1yi8YEm-P0D7D,@7k__w\?[k^7CBkZtAGǸ80=4Dr/$pD'5S۶)\y @04(VE [jɷj@Iv46R[Hae RIԢmMO1}T1D-*4{Af2@Wa`Ei6..9lzp m 9%0'EV:D ܺ],^jx&V{rdjz@;1(.bC3S-ɹ󬃀Y}.[8EЬÏ ?󶖜J5}Oo [O׭x+3]umaln3={~d)&։s#0xW%=Pk&/|O@"C Au>=-ֱW.2Gq(h3˰oW?dJ'kߵ >ӏT$2;`1șrf /G~:IO #]ߣC5\Bwg!C:YEWV =.r `: e^ W+b!)߬}>] Ms|4r"'%o;rbTVz nP]qeE,K:fUȿ@ʫ;}],r@>uze-W\t2{bEn==Md :47==̨Yc`剽9?&V`@U  BB A%9@`X:: Wþ"h]?/LؖG[|߷b\ipܷcN*3;vΚ={;eB[5! @_3:1w!;>>!y8x?YyPCXǾk8`B QG|g0܋WoR?~\>N+g1u;GVs/|~=6iķVLK!^emnd56tPX%g"E #%%HݲʥȪxQx'!ڧJ#rH3nd*,+dD8(E3YMEEʚWXKGEAfil*ƍ\Jy9g)*>HI3uo 9uW%נYs[ki2cQM*z:Z61!M,WRyI [=,୞I``QYjxL4/O ϡQ/Z;%ژLFI%]̣Y[Vl*D;!GFȠ[BA<`γ, )I(-yzeA]t8O`,%1 cY(jBwƱˊn}Hp/OZ9ԙcoЊn}&oA(LDwA|sje R5*%F)ђ'sZqG_/k=63mqh;;wV 3̴߳=fF^\Ew!¨s{+53fm^(Bw:,6 Ѫ{Qӑ]Ib(Ebm$^Dqr4dk]xHoQCFJM.%%&Q^Aaeqa!94+I$ /wa$n9ňq@%iW#*jI!wM )Fo}Zqz^s=䗲%HՋ'^L@W>'[Ig;Ysg;/,!G&dٻZQ# B^d,iG'9j<75ltc;lmz@TJ f ?8&{ʆ#੨ rzh̏E]Xj&ngNȄ4ˌIqI3N*[Ͽ-[;C9qv <φ R|Sizb l6zY QOvQ؉r?FjVѦo׋8f`@fzY/$ERYon]dF|VaSr}"{zafIy},eCiåWoٶn7a#HL [KF'K6NO;u|v}XoEu˲t?_9;yq TorSQh(O_?=a+_9WQAfaP´ԇBꄦtiT m r) ьaRQVCR!!ۑEUF@*)+bE%?D] -jz3Ai1gSչcQ14Wid!Bֽ*\($|Fv(ڻ&ᓯCmTż5Sq't0y c^)h=%ZO$SZtO6ʍE$qjji`A=Ӎr$\=Ri/pPzjƑc}zh$\=eGd>Dž"&iGt>BIۏ{>'ˍE,u@#WB6Fx1I.;|UF _'!qS[nlxj@ [.LFǥn .$4ʌ Ajl3b+ wXgU#}978|UբX,LCM-띳m~q!?DQk/J;i3wrqc7L&~+@d~uҰ2P^Գ[V&k0=5smן$]/nGc_{AXk*LiX^Gk8kX9!ǒ:2IOFٯS' $JL]nUANg))_nc  qswӰDEK -3thib{Z R[ϥ n#o]0>ڽ#"8( "RaDĂndDK} .=:&^U,k@.kq宜pfe9&B1˂zww|MrFAaToaʯ?w1à@G$,4B4;pXYgps,ڱ0@e2ۑH__E܉ )AiFOF-ی:ɷ8#Cc|TK4䓋ڑHЫ c` ,;+?,_pQ8toTek\2ѭj!/. ~ .LZqϹ%:/d)>iŭSo7U.䯼74c!/>nŭgSo|(yA*篼8YsC`=M VsF8uC&U5 ;{V›6=$iS9K?T##05+S3 q o%.mޭ-B&:7̹;Nd2}Ƿ(U%Dc3rK/&IKk;ew!_EV6py~>5}e=_h{sg]掻g=ѹϧ_`A5;s.zF b70_qir`2W5&_Q;Mlj<҂3֍Tt܏`%`[8'Wo~.[|0 0m3E~ziVaOE$À "^zԳ>SxY`nN4DǙ4{aOP2bl 'wQ֞BMf-K>gR?pwD*]:<oSr?୆xJ8lUKXx5+5 YSX$=[%wӐs7~:7$870*V [ir} # 1Mi:dD7*?(1(1zIC <+M@5:an5#ffd-(B4A2y j uD)Te9n7In -8~(t-hKQO;>l9- 2d0KtϒBy6Ʌ>kPkJD0ߏr<2KaѤۛer;醟54*)'NZd ݂!>1rdL Yk.ZK4}&W)PD@ɽqW1@;0ڍF!Qpz!`} Fc_S0Q܁/jche{" 0 {qS#ROdÅD:RF-4 p :@&MԭȍW@|bgE+|>Ocn3f3yu'^ꡳ+xt$.ݺctĚ|07q 3398[P|'hJdu*-% ѝ@nxu&WYrc<6YGOw Up#;z[]OS{yv1g+C?Ė'NxZIk7,?F"͉ŞϣneE ՗,>aV^b?Gel|{kmѽ3VEW\zsOcn-onölBHA\nCKXM %<ղv-5俒YdC+d8F#MK6P:o:D M-Lx҄` ^Aev 2*ܹYj8WAe$|!ף iaJ6ޔ9ve-F҂?iO1]%9PG3,Ja1+{ lю >]9+[y;%9' ;4&:`(yM.9p,.;`PEViD]^+;_0fwiJVɝR,s&66ߥ``4b~Iljkڈfw圃4Ogy;'&ٺa#E<|H俳4:z3B7(yV49gbiBx@B5QP}tCb> @9D#@ @Flns\61jLp'*I|ԭ_}ͨˍuj#e}Vu&EV"Ic5&5n[!q4Ҭ0)r{$0c– F{ m+Ѿ1&CgΡEjSuE֫3]߳Whyc6t.#&v+HvoΖH|:naUĢ{=ՕVsĎ0hLR84gECNhMQg&5alZ S/|Gw:"8"F{?|aUϔ __þ vUV^+A#F<?C !"fݽAu<\VXW[Efg0 szT*&:΋ǍY3U$=sB{؊gM)ix,zm=#t%E${K://]xƧ%.?.W&,r;/-li-tE8kS6y9ͮo~CT~K1hvkِtvթ0-tu(\F.YF3;5``1% >zk6k~3i^#@H8^_oWRx=P3aJ~!v<""LV_VsБG? ټ'r) 2J45©{h;o]⎠fë\>LFQiqBGWD%zP:Ӗ-_֋CK-}C=)1B"j@9Xզwx,n;eaOo P,R?.zlOZ(ކ[DzRk_~Nxz&[=Vo֥)Q%8T WKDŽ14%#.N ^&uɈ@(h0t2-&$] F@^YW>t;`˘[1+r=Ɍ Bbf&ታ?h^wq2ο{l_8kM CYby Z9u op6/ld߹US1 zI?Ӛ`l2]dZ~W0*QI÷Ϫ;es;2ow˭N?+!kj(սhر $*Wz DTpA$wzDAV -&$T1c!o@jWgPEP@k@{oGmFGG&0`?՚?i4۾(=lP*mp10RW\,ԇC v`pIbp A햗 :֠B4<쑼mSqVń]i*X9} $D0'+{5zKNjS7g<)XksA;᜻μ5AQ-1E ڸ/uvz$U񤢍Xg'ݧ`h\Sm`Bb_mܮq.h<`%9j#C¨7!nh݃sUCqоh ]v{Ԅ}F/.v^'v,fݵNu})kJK;*θ[$m}je]F#R Z*ƪ*76gGD55y9zv˅mpenTἪ-JTస%kmh3^K=n +z3?b$LD k{"|XF=yg1Ϯ_OYN7H,,킫" r1p|}aZ7'ģ1o^OxS\.Tj } ] DYS~_lcᷔ'gk[GD<lb@@jH#sbU{~lW pո}6̨{ÌkTi*<)US&6<@v{8~IR׾(hAzDIzAꈃVCɯ:#e fgm r$N#ifJ׋:,XpsyPQX76 z\'^a0/Rʧyc`QxqK7O+LsU: k 䇁&;^)Ĵ"o= 3K<_Ύ3s d0G11#]-'A|^z緭;Mm}#KTvަ*EW^ kmEU.UM/ˤPrű+ JJ`F?n} n@ S5km$*4=wFJ؍KSEW5F$b:Hۑ r9P)QBРz(DJJ"*p}iϠHW.9H,=)!oy_l3tx_fL#'=$J ;`v/1հ_aJf^a#)2|&$ ,HSzp˛SWCp3dɤujCOVx8{4Տ̠1ߨQ?̓` B|P+<h?ެv5:שqALLIgWAHx 7j1 TUJ@jQ18. 8@^+betÁ*u0ѢIt+mzQVd6z$C+W-D ^I/m闎upo6B@+` $0NngP{^/XXl@3焅iOJz&" ˴I`DQ]jT^L%+ <77SlWM"A z*iHOOH%h!=Ibs5v%QN[V{!q0 -rʩȨ?p3} >En_^rclGȄj + !׷t 4\.;A'Ul`AaJ߿7I _JomGFsX4qp_އ?o?FQ |! $mұPW>ulGF3I As H~K:ubaM)y[CM4*smMFTVWԃRӟ;Q鳲XHz}EMD O%ӘvQ5yhTkSTGkhEU+&֏_Um$HӂҮo_ '=b3ߋy{^T|>ķ8-`#7ucNJ l-RD*FYW<ɾe݅c&ǒWQ)#/\ Fe'#?I߻{E,58OrLehCu[%!Ӏ,Q66F {"?,` G|_r`A9!u;D-SThv7Ή'0N nK>oͣ5O%q|8)^`\.i z|a6.^/L(nUȹ(&&iC^gxU G?P d$򶮈W՟'@}kh~KbeӗO.F`*M& wT.UBZR@΃4ìcQ'85g/%e.Eެ0rm6` k:c\1Itui֘ʟ7~ڜ_M!A@?KPnC HF,%1DPXn$H@@#4B%PWoO ٲ{f,A9Quo[7Wc X% \ M@@sJvEFTDX-l1꽕7[w9amg',:x̠nS` .Dn~$wЛx%_ AP` =ჰ_)XaXm3ć;D-V#=E%IǡXsXl&7&qx$  o|2Wf;MoRJxgς@[R*L#Ǡ`? ar+U Q Zfhh3X翰GTX5P{/,tp' `.' 4 F sӓ`@H^ Oy(; g TϑDMY77XTehnZৠP []5Y=(|TB5_JApb۳/;3y";"$ yh ]!N<ohzD`0@p ֆ=hQ~ܛ!- QP<MkH3CәciC= u .RP R `X)fބw 8F77ʾ[J^v+x[I1NȕʲFpYPN!! $1)-SW+Gg8ķ۴?D88GLI x[A\sXxAW#®L`8D7>!^>cZ)''sQ's/\\ߚ߻zMGqkձ"/[:2_cݣ-/6gM@#婉/wQ=җ?dKNg߹tR[rO( r. v]! W#df; n*/r[janR+@RdN"t8Dxt5yuS_ڡ$-*lp2It⠢12sn_>1l1UT€UzH4t' "ȨyVJYy4܅5PuPlyF\0N Jr8 0@e^ۮt?u^3MƗ-oyPYu=ˀ~{GRY' Xx>.x;;*ڥk/ yk5f5ʪ7Ų} NދzϭՄ C{kO]} cpBcz^7f-uyy] /x9KP>8q _w]uP?,_NXl2C]z vMMJ E{{ {h=$"ʷ2kTأB,ǴwZgӯ vق?ؤ ߱q!4ue6r㽞W\OruX>&c@,V w1SNӲ}.TfH< /Zldm3DBk/C)4FT% /{ Bj%P_J \ Et'zBRFjXAmr]~ $}K`ׇ6Lנ>2Z7bUF;?Wm^w3gmPg }Trx7Uj:nPAc,-%@ߪ'aOG 4s?>O^gaVFV z? ~d1@k~Bn8^XSZ!?ׂI'Wfῢ;yZ-krWЈpLx^snba[ G%|`CDH %@ %6jN8Vޒ+DGHCCB@r7Ƴ`S+lY!qCꫥKLO eKR$$U$U%1)U׻8IZbqfx~k0EkxK_6O4_- {O׉fO7Ʒ?]YiЏvLAZo%X_@RC8FTV!T C = o) .e1| πFܕ{lޏcWgE} /'':`ǥ7}|ՃqKy:1>|oqӨBWGBQMСN^G7%cBA @wH=:W텒KŠnK)D`48kaIփ6~bCa9Cy!+a}ïaD}gMd L"RFuk{7ę-ջpC7&0`]Pו&D) ۉӸucJ0N{4H[k[Mz-f+밫 'a[`Q`|^ i;^1cNlϋkQ3*eNޢ߱s~}t@ [)@Zhi{> 9OqSwS%L&+ ,?۲ʇȳDgƒنOk/oAiG\H ґ @LPM"\LOGpJjx|gd:TZo>*kf&'s {Lp/>G^g֙2{ ;ѳPL5ս2#NAs\2~ oi^15^pF0woz\dܢ <B@^hVi_;=vz{C0VsѷJHrAԨHab'!EkDfM< <PWڿj'N^s_X449qE‚Ai@ret9J%dE8l{3qχɬ Vpp̝jD)  )ב|V̠Z]Rp&Ff;& ,9.Q!P!'( J,IQAASw;x'E}ض|} 7PX^9 Z.}>wH&zG]%|h 2աp,-[0;BnZ3h4 HSB 3S쀅LpHwٸAVwcT u@{4A*I$_,XTA>q-m2gI4BJ3v݀V¸cF۱A`ZN1ʨWO)*;7иCG5U .'^vFB0,r3\p-dkbOEEK%ly>/97Cx{A].X@G>p PSn9GHB 8v(7:{R\-I 3)]k5 U1ĥhx\NE NrM퀦Ub͢h\z_PYx4"M 77~f5T!XEYQ$1FMIXhTlgsa3tBg"Xxr&ޜq#wqrOX5,ih&/p<_;b .zs28?,]zIS{h} qmM\+,d1K<ʍITjIjH qǸaؿƚ P B`QDt2 5^C>BMLhܽV뻄: "#Tkph4zc;8>b 7Du9B(.u㚶 ^B1&Qz >?]maW])]jY`7O|}g_!K^DP7+Luv5'DF~L5J]1I<$ @kTd;4̸A Oi@%ZTd4KwJW~iBd2*,I /R&IPWyﰈeR Ț)0 *`Uc/BѾ͐)޿UD<% G@G5ͮzҨ]C;{"h甑pX)%J Q^Bn1*}Tl! >3>(,huZ7N $ =UvV].'f|)6\ ,rȯ3}gƺόwt=JխEhJ'kII&QYҸaFU? ʟ ߢ}~ZMI ·U6Va ,4Lx}ꋉ;JCC!+Kժfm*EQ MlW5s٫U\-[z!vX&_Bz^Nr|k; p3/)/E/FgD jDPi!#1C^"A:*:QE~ "k"ϥQDzBZuK[?GcNpCS ƥ*<{M(HiJB92pRÙ@NfbҨa0ʌK <`pu_+4~q/N(`է4 uj74Yx\,A“1 `I+Wj{I@kå+J+Wp'`dUCAk~O>cxFq]|o pHp6ZJU6Q wm:r9%& >@&p<"%͵q¦΂_ 6Yqb$jMTlml^( (z :H gOs"ABbfYUAg_gT#f=zqf1& Ըeħe qoOhC_(J7^k+lt@}2*)O{=tM=xgZD 6"+ xPɘ|dRI@8QjjADtgR%&0Oƾ*R%@Rr&:&(;N*/ @&4tU!\sҽZrшv3?2@ Dpp YѺhA0 ̙0C%cCXD9"LQ DxxeĢ~r " e'h1ah(]j:mu/x?]YjDwVY"pXaUv=o V}B2]m9CE9V:EKMӒAMZ'Q )-J[t9YIϴ]@"VК^۞9:Ҹ^=y1\]knTsث_Ko3L[f y< `2 GJp<:2[aL?sn:ja;+ح/*a07aS`,{_)-gzFԧ.0\N`ɀ }f.~i0oy~{=b-o a-0sc0kG&/O >ыSSľ9 {v ڝbItJrfcp))K̃ ׄX.zTk5mV8z ɝ6{ դ>&AH p8Ȧb~ mt&,Eag c qI@@y&80XUKrC'\Ss&?Ngz7G1W&xzCEtf!P+v_Mğr< zqt S @Ff"*-~NCWXΈS)V~[ϹWk_}5}/o9X!o[ 8hvء_8H!""\NWa*JAKkm_9/a 'b r:sɓY r:}^R؃U=պ^}H]"}( b"jo!kե,(b52YY0!zK DPiЖuS mn9P[0! G:9WT.9=I[P,zmJ^RQu-ZQEE ԝ'B2w H#!XM  it}NRN] \,6YF>_~:C45wOq;| %$R$_Л:}5<:"Apdx ]g~j2UV dun)ؐZcEό`ZZ !eg9Ak;Y8M־$SV(y\|Yo.?_af@ɀkYgib[ %ٍν\3h\iԽ杻N"<+;퓐&^,c͊/~? h}eyJO{\#5S! <̓i Ш\KبzML|QCm'i-l~`K*E:H|p)6Y%>t=jId %꼕"!zLjtG{Qb@q8`ʞivkh RT_4N?:udDp"\7\`5 L 0okRrCTD.Fow` т.CO~QH8y5 4U7.N*LphFZGmkFZsUɾ?:"SϏgQEjڬG$rz _$@Tgng@tH%vu/o]K̮&7sjq8k9wXj Zٽ&F稘gI5 To"J 4'e T,{Mt^nI&2 ii|  +zEQS80Czлgu@(睢- jp(d 1CQx| SģdW+?CZ\s.!zzv"~-{折:τOCjmj`xG,p}7w 6XK#V;*a;Z!COW]b#Qfd,-fSJ*Z׸itMLHlgX!;ľa_TGOObD. ֛4.l oH Ɩ\^qo^Zn?tg%!-L\H_=I$C4'" >W3Kw #<@^Œi@P4cw!Z55Zx G ?.""ÀX Ԃh$\pkI3^tE8|1YC?.zn)4 g;9>oBYL]4}5{ ݏ ˃Gdh Sϖ Ur'sRQǏ1*p%_1j2FieFNꯜп,,)<tӯ0S'D7JW3ǹ}LRd*Ec:Jjr!ʐ Pgrj{8r'ܴN2:|TGFxS9\![L[Js PDU<W{= cZS{g&tZ`Q3s)>&j1F 0.ȃ'*(d[`e¤$ã骍"_L1\(WX H`>r Et-V!%i i)D *]c{jD82iJW 2 t~$7X{?q_PM~Հ]>4ż}xHۻ٦ ̀ |eq۫?(/> %zb#[/TN\k/x2pذNuJ\J8%i!s0= pK'wowmtEsJᴅy3Lpv|+zVHFZ^B%Antϛs!ۢXxLٳtNI 08Q<&0n%aLKAUq!xЃ0xLI~=q66]`Mzy;/K'-.U6߭:>\@,pdu]0d5jxf; jmGEP΅|#8"TG?~%Zt2Xnw`F?$8\H On@(aݡ[FYL킷1\GM)%l< "W85H}L :x[,&&.[(-T| yb9yVBaC*ÕsR60kKb?#!=[B!weO4kCeojB=l@IZ_E+;&R>arNP<)J؃4FT'&2Ne|MG=_>>8Ǐy/T_2D< `=u :so;pՎt &ZZT*`vVܨaMέbh=fÈ4TO#WR51s9&$/3{j2M_q̔ufZU,Y 9}n5ʿlw)@i!  ֊INB42{4ApBA>S@A>{#f]/zc6y֙aY{&w- @GB*2>3 m#&l9k`LIk V4] mV63+ζJ"H`bz,Ϙ0Y<`^5`8_Oh@)>%*JY7>@aVAfMn_Bv5[n Sz,4PovxM`i0 7*ՆrYãT[/` Qf#!n0ՐlxIH!D@+(*P bbx'cOtiK" I(4ԏrHkV4jo-:lZ| &Ճٲ%dc+!x\i8!iz"b}Q.: êvP8 pYl=8:ZBX쏬Qi<D!@b"lV9M}‚ Ij&KǵX0k`h-ɘuo |Gzq/9E611Ay*ּ߇&}G諨2os7~4.e "EI쳫9WEi e)lEþnE5x0[0݆K +}d+p/^{|[(k_P}Ѯ)sg{'pS9\5^!|>3XUJX~BO皱 㯸Rj*ƽqKhiwnh$eQ[(1hKm;~5C\Mm;KP"ss@uE"ҌxL{zJ 4Tw! BHDI?9n 2nH}ںcJ3`s 5T BǺ {*ex 5Fƥ\E*P"rĒ/Kᑣ2'hh_8_>;~xJ,xqe 8c)W@e@Ac- >MdobhXD9c~E 7'BkH[FB#3~~әUhTLR+.?Y jg0jT`] ) QE2#@S$ KCiؿQ0tTb̉?XyO=G[pV:X~@܍eAM-ֱgh) /FQf{/Gl$Pn p%$J:鈜)(.@+4֠1j`~$IsrFM*њ٨ѡKdf@o=XdhPgqbXhlgVq)CDjabNP)uqw64 @\ \9h3XT!., ysɪ 0K4#ӁU(@RhC`nuKvD~Pt4\t˦PPxn$6x.#PĐBf&sldv@]LC3 QAR%ϫ W@Fc0_}ґ.}#DD87c3wȁ[[O+LkZ؃t@VÅD&8LY(<i W ˿n2)#TQWz!!{ AїHH a z/lP x;!*H4cr/;zy*A"$JAdac~ C}o-]u s.}oY˔<ϖM ʆ ^S , Kd=]= "DA T~ KAD>Lǟ0wqƦzQRϋpwvL . 0c/un?ڥʯz;e ?`7$-=J]%{3_B4SF(^ ,FOo {F" 9u c)^I!c D 4M,QHś 0u맶4ga's #"m/-f-_ uҺ'6  gTC pǺ?FgwY:Db븘3K b2 41OA0R+ڂ` aQv݅{H1t M  ca9#(F$ 'oHE/j[|HpR|tt`-mbJQaBs_|dF/Z c]E/М.h;IB(!aS @f$tbFrE!FMd=".<ꏀH,\ 1< [fTs bݷPK=i 98_¨AL@T upBs72Rl4ĬAQkC'rWÑbN[QU %o&ːyQo1#@:JfC\4H?qQ- —emsXg韦LFu1ӭŹtP˙ $pCfH+ }Ccg&>ʉm_?il ^},Pᓸàރe*C%V(@?A* !Z#>B;Y̏ [B @L=wc: MgT4h4]`%@@)`aduȖdoHi:4Dy&A$H:h"y7ahWݙPM 'beo߳Ѧ䨴J]U\6R6@Pb|ɹuԳѫiJ*J"Lƿ]prb3ٗJC0ZDM.ەl??&=+-aps#y1ASz5& S5eڟń$v;94F3 "򸈼CnDe)MK8BD^4:3W(+b!〆+t*>>DJ"vIYř$:EH;!+VڮoqJqc tOQ@Iȝ$2GqRV'Wfvf Vv&dR;WI#f!EA=9f-_>#6~(.^l̲ VgQ)aZ=FBdY/<6BD*ئͯӈ!Mg}(B5iDKMU{Q%Aj'*\cdN ST! G`ڎ2PdǢFґ&WR\AL@ ¥s]WНNQޒF,*T>;!'a ?N |q7K(g8t3w W]90v)"IAcs'?`W@;cÞOR?-adg^H>2d mncCz ;F|;\/yrX!a't,N15FB@^łMgL4[kxR?LAz?;iUr!\{+qjOt)~瑕l'TWMo ߛw~Y3ȦUO#zeމe1TD  RUx3ˮ&M5aPRjpRR*)0d)A*bj1Y8iaՑUC c?q1v9n.} _GGVIU~ bpswC_>E͛S+gxψtě]NK;L]=q8j=:Idi R٦,Ja׫d}b|\i/fޖT դ[ 5d*TDB}ںp څK< rwhyC[P.Qg=8BР?Iit<۲܇ 1)4 L*deRYLC r1m)jjP%NDd`-E?c.,m`t1seps9϶տA֐U Br*tmXlWCaT On)wN4)qtN:fsjFdD@m\XWqjOh}g3ωKyLDѢeDE./ YFI^Bj7_8y]]n:@hq]~1>F) eol᪷ icZ|IEZJF֮ӭA&iT: *Yv-9`RuIͪ [UӎANǐ,,v~c:{. O!lpc3s{~qwyl3(iJ%[{F^x۬ =uXR4!N߅b WoBN54ޜ:}brR=D> /rz0/jMЩ~ bپu4TL@GuՌ{FـIfMЩV?X6vuHtΟеΞBS Q]?;➣w@f;{{ĺv[߰!zߟC7"g>ʶ4Q=YAtC gԘMU}/doG7!R+K ujS2I+O/ԭw\xȽQ-{n|:N̟ji@TP6ߘ\_7f AP D &L P-Hb缩nq6PpoF ܯvn?ڦ4W(s #GNUۑcW(Q_.Θoni?øb0wenl.}[{Yy@!MԬKU}!OOS``y?S=x]`~itIEN38_18ܘ^;~D@᪪Z-bG d|%ϵ1_L_t -;s۽qx J#l:4Y< \œɅ=>5Jv [KLF NgV:mG}#IknR?*P!c{pgt&Q|wTm_|v^G4 8{ҽG,pѼNtNn9k\0 1=fc6s6"sz&GMIZǞۥڨ??_wGe!<RC`Omy~4 _t=wYkMY!,|LDMn[f0Q6$YS69f[f-b;|,tόuU҉-"cŘU,@xtyG1IGis=9ŀ&&Q Uow'Ӕos=9Co;}3m*ŷ4aEh5G4$pv}l?O[qQKZ+޲j/ =Shz>pzq+ko?.G^b U%4ChtDcm&Kfr:DI֚!tBYϋ1g B=cGO_>z}k=\A$CɌuqC$ ڠ1 w h3 -]v=}~\`w!{KU⇤,)P꺝]hq@M УMܲ-?b9ZBlq_9x+3\OOFgݸۊQ- v/NOnwJP5fkg^l^Մ~)T^a9?c\5++Cg)3hۏe5;F?śopn앟=hvSY j9I2e]7{g*6\&E}~]ooP.hy{0+ƪ+_o.$]u,o54V'3D, 'xS&w9%O) dhr\^Y!u<5h'e7052P>0 pDC|u%Gv⑙_~M>(q]9h^iT }mXN.o||[lw;fժgÃn3T?+~"2N^Ex4%ibn'Ә<BM5eI8B6v |">2hjBÔ;NN;B(O漲|Phwz6Xc9+#V|e3MFo.:涟ǾO/NfǶ-+uw]qF;Qc^87wSӧZrx;ƶf*a_Qz# 㟸l'wyBcl*y{ĪzwQ 1/~#$<jcE91_cT:1Iۚ'NH4CV>}I:i\\M<ӟ!?8^ƾ)-)R!uv *2,H4Q}x-}>=Q߈/] }9qNPEE( 0־fqa~N~{>.jv5e.(ro]k⭱&DiΙZlHBg%OEa>1\cz}fx̹U,<'~y4Yp1{9P{]};[w dr\C ?O#5FJ-0}Zw,ɋYFSy<5:[t64'.9ifyL=fՅ߶p舧6UzS[sLk}H7W;c+?:3(ۡ  vV7-w27/-6S~5 HWy#ƚw-g yIU^C|IiS_q'l;(%_y??re5I@dvx^h1h-Xdx^wHk[8 Q[$83WիYigDD"=G:s J\ChΓS0e aͦOCI@coy6Q<3r}^05Y `8QYdMe;_gy#>2yP}h9_[䡛,V9zވe\dsQHdIo ?;|5F$]Y `-J-{[0m5K̐@ˆxDx(AS,"_]$lãYEnwW4Zcj-G]c* F5z/.@;"硠 Th9;mGyBt,p 9-͏/&C9w HDhV0(73`hX0呛r6(ȾٶKծ)I|Tᛎ^6w/;e'-;,Nm܉},tIǻyEw; MȬ_ȕq':mT r8 V'OزG=y"JYe:$SFNkrG;pmc9Ah=Gv65yFܧ o Y;|oa=hï5CS8Gv. ֥rĤomn.h'&2-ԂGEkm& Us\BrH}M]\=a}%6y *+́#u871>|/!BTt$ 7ui% 9+W 36yP<-jKw[ À? cnl\:$M)oUꥉO?GImj+Ӊ'КO~˟a$\9`VTɜDOu>X,w*@&5^w=l"Hxߛu Rئ$~ (jROQ|kDN+m?:.HRB6B\m:XY,Ʉ}WsC@~dTsLE ߐ4ȵ+.=y8s;=v}Oܪu{^>e sDo еc%bq_)vlDādώo.GΫ <̉NoME>v yRGj>zuUH_lyc!zp@ `# {٣G hZ_$c`:Jmi{pfv?Ԝ:しpb'e0AD"(;yq-MG"2٬h-@jzmh@4iʬ- 밇gTVz*]cW"QmM8Eiۘ ,ӱnAkh``iTmc$(قmnw͞z% : K{ qE`_ST61;Kܴ=4R,(E)_Ӣ%f,1(< Ε4V#ِYC L)LEVw4^jKYcE>ET6Y(Zo~ DZL55צKՁۄ=rc zvN9fJK(0e !;+ x iƊ}*P;-aH-k3R iF#~ bm'|SK3h32Hd+5B@yҰ-;rh6[Q&bڑHcS7 y}~UM4K9XwknkCZDdg>McB@ }9k 6ME(٨7o9$z]FWf5e|o |TbeV%iN"./$Z?ʮ"A@JM;(upV{';(Q[XX0Gv6凛rfݾ`SmS(nm=MACÿNy⾆ޗ+G} L\Iɹyw7C&'W妤)Zi:ul7pa”*,DuX!Y`uϏB,Yyoig\.ؿDACҤU7lVδUo_}P6,ٰeʿ2 @g9a㎀!#iʆ&s!Ι$u>cࢗ>>$v='o|?͘Sk):B$޷PcKN $#a@nBzrm{'k$fL} VGRAFI#_@p nF#<!76!=\:7=\w.:u "ſ"vD7}}fy~ɰA^X^t"豸;Գdkmo6:J~iՌ^ȼr!pg0׉+GW7FB)GdN谹Ha?bi+ѾH`xye 7Q{;J>ϴF&޿虣V6g[sٚ#<~u9Bɧ5J}o۩ݲ;I&s*A.=y@g-q1fPOٿo»(34F-K4jF aN;,bc.Zu]נO7i UeXdM8s\e <0̧P?tuu$cqug}sO6\OzE&")Gaܡxď{5Ұ9;ci.l&>riK*?\\<k>r0i$ yn?J1TM t Zn2GQC&2PC ('}q'3ͅuwL+K[ڳ0HB (1r)v 93ӿ❆ 23,3]a1N-xV@(ԡquq5 P(T׋uا^vT}Hc{}7X,aqiZӵG[VGVi~{0`2uwŃ]pNL#jF8@z xws7yҖ&RE앂\_x* }XRL:P-)(MCm,xTؒVI8xh+l^]/YC_k?S[e @_p/h ~ wNK-_qQpGlFE߄%yz?uFC}B ʼy rz73eɜ^AEQgxh t ˑ -!~QEW^k/?f%[y'wH8(0?.XҚ[RsKy n{5W._˛QDY z4Va2 ɸ[ޘw\yԶX9xWeވ`h}EO;`Z6 }~'< h(hNx Yah Ox m4`ahO7v,ζdrֹz63ǀ&ǂGf=]ha<ၓ `B>$lPp$o펩-s1Ш9;[C;͹BsyV).kUI-o+G`7-)f4;e9WD cՇ4ؑ KxJ 洍{z;̶h!vx *tml}*JN#%:wt`j0io|EZB@֭[KBe9 K˨K )B2aB/Dz~ܜ:6H)N?Ԕꊭ'w"-Y$e[]A~nv{! a慽F;}q^ 8;/?`9l*WXkXrbSLenZ!^k%Q((MCmz xV$*F(^9y|g.`CWSdhxRx*@3ly'M0Kݏ5LoF̾gu1_߃xݥ z.QM4!S!zB&D%?CsP™'׭_ă2zƅx Ts/f=p7r9?>퇄$ y|Kܭ 1Fѯ~&T|C~}^&Mƫzq &6ȭ k`T׉BRi .Zxj.-C|Mn\d|: ni3W$+ΠSzZ:ǫe-qemyșWk6+vwf9kycw_ަ_?`)3gc18~:t{ɒΗmX#O3&DӮU7Xm@g}#t9=`!KGS~xPJU.J ifd}ln?^ࣶGDTOkU2FHD+ hcNIYUe5:.o Z˭9gkj p" :tA7)_1iCBEMsZf?f}y^ЃgQJiX,y}Bl /qzbT,@NP 's3߱Y=.|>h0(J47D1J9yA;w\Ԫ2cDZj n}`lt'@ *䄇cи>iYxhv~i*H DT$N$Q&\%Za,]1-K,5=/DGlsB*ԡ-H8o.=<3|{oV̵QkH;"H\ߜ5i'w &?:uam:LL q .`)5~[lwNu\4\c[lw6Wqz mlx\h8oCy'`G'u ϝ3f'd+rR((ȽlA bbfCݥ%tǺsLg'*TmpXlP974u лV V t#Ղ$h"Z dNE,5ոV4h"@\{ yN>ԃH1oGIU[H2*0̯W΀ۦ oM6 yõױd=ai1sD8Q2f,֔Z|]n|Ua>\.ZO: bwKu! }/## xTG}LLPX?@Ũ{<ùw}Gn㒶߾>=~=N% VcbE/?C:4rO`Ȝ^}Gग)9t d'6&yO\=u_aS1'P)@ck<LC:>=^dQ? Mr?58_\yƸ9q̾_Hs/S7?`0F(jϲI5 Mϻ@Pd%5瑎pfdxn&Gmv9um6_[O/lapZkO97{"N[ta{2KyQr읫`FkּhX+w}f9q︈`r)ѮrS;iߓ~{BxdKpNM 2F#E6|76T;Fv]_ITy\}gGKֱ4OZh=;-1uF0" 4B/՗/:걕.Z8i(oݾ@d&LKAgW zM Y>  z| HG1D9#K\yM WYđ4zy1>wѭ{t~)V$Tz]79CW<_'^M!@ D^;:_D'mgo'wKlu龸_ OYhzz^ᾟ\<(Pg@Ǡ&è{\qNIunn \=Aɳ+4 H5?S/h9Ayk߂76{=˟ڃ= iBLfhkJĘ F;>|TdA[~iyGqYd&dѝ) p GfG~:h7M/(/|ٵءlCc X{2aਗ਼j O[nLKX >?l 3Čm\T3noMq/El6^jxg_BZl=Lv)bۿvSeE>1IcօߗeiXP&;g>b2zfwa6FYi@*fzwj^QdB(8}: QF תDe.PGueIrciFʱ44@PW"[L&wɟ9M-=t|{_ \PВw'{m9OHߛ¤ v~g 5h䈋1#k(U'`A@j D@p#Õ; e7dni>.Ӂꗗqoۥ̚cPbrʹ+e`ŲןD\ r q$r  Q1}~y2b9{Q]9&('0܉>?|CXruki.R- `_$idyD%b7ˍQAޯ:Q}Vٟi$7=LVR4崇U@B7|oTTARe|HI[>\;l mY,b]PHg?UX ؤQFQg^$  /jeOpӗa7޷ E$ TP|)bO(v$N:HY#f}qFE + {jvmZ=nȿ,k6f{W]hm42t)kǏv<) 'R%,m{ 4 56W6S>K4,1PoW'^yz5*?CS޷EIkHRtuSٳYɱ~S`Ҽ4ufp,\a >h)@ fv*?X6y?#N<{t5.TPr:6Ϡaj$z;AR;;Ex<ʫhM``uc!;!+_m; =u)NڭId*6 nOe?*/|.ء1,;X @]=4=^iGlTo Ul`S;L#b~<i r`@".8`Z]"1Z}熫rZ %+ ?D"Ex ` 9P[mZp *NB,pZP0@;Y@K/S(חrUA`QJҵ" {"Rзhʯ*=<- 6h*1sXҰyE%ُ*5`"k9bX:}`Ŗw6.UXe :HK{إChD} _(8Muýbuh"% M7:a g9l'A0iRqK!:<{ ^MB ɯiUM[F W81|p`7&H]uN(z/U I c'3szG9S5hPJLjAu0Ӿ9L`DxT x{^>EyCg2͟ٿg k\J0?|x嫝r_}-ʰ6 oK&7: iϔ^ ^FͧpU K~Yu2@ad?$l¼*/|^_x7X!O R?R@Q!TKɟo`-72,Od ȧ6IƴD=xlTnS˧Mi@F %%ϤZ]pU BDt60rD̶'.I4*Lr^/IWTN,ju٫qNo;0G+̥hbfcŰXUTb4~. !~>S箶_S[,rl@"2APiiԄ׎awԟ1wMA$7}.Oiɯsɿi1\Z{122Yx艆7K*g% ǝѲ9f pLQQ c` ]~wBZ 4 8i:fGiz]S7pÏQ]2iDd9bHlϨQ<)h|dv}o~r| S7m9_F$%ھ/nÜj8}Ûr3-?*QŦ1jysbUi4Qcq²3tYWsͭ~sW#0|aA  6|S5 Լi]c. {oClw8EMΒ0  Jaܥijk0F_5WdFzAļ&_ V%q7,[jub&3 eFLm|qǚ9FB__4/.4jKf涯/xK|Mt2|I'7]ӇŦlt G I#3t+rROvEӭaupF$Z mk|dm0(G@PHa uڑa-8DRixMgr kt:ъkx'Y$P(_rUtsM8hcq5Eza՜FHEz, (d,/G7ם,Jq$dYXs;vPS&q1C0Aj.( Xʆ_hYp?bN?~*# Ƽ'X Ł+f~}{Y~,u`tFRNnaVAtMFaB Q5l{9:-~GXbjiCGZ֋EO}O%u h|5Ւ\I'Q;;7j>m[xwFur`<Oוo–ߖ\͈2{{JYq<w؄M+B"@r85Ű'smHxti5p%oA~e{eҕ. $tj+ K)OT0mQπRUE99_0?tz9 +O\_" :.;q30j+{j^<ҵ3 J)V~H%蝚ب=&H!4F^_J-c#!25l ;"jw 7[cGu 3? OFidݺ}99Ru\4Д=ZK&#9[ +QMa'ovhBdwEHtG9;`jr[ g=u{ ՟ ':t20 c$Q2tҭC8oxdg -&o^t C% 6*YGu~I,|o^Fѹf[j^ݬ1!3# 6#1Za]Vz|Im®[G:nӭ6z>8 Y*#\lM^r^eZgYt _59kH8c?y''ݽYF'I~JIx /cXy%:'~n'f Hl0- ƣ:2 {YSi.^<~CO\@js_ލ9 IẈ1K fU7f?0?uNgTNΌo~_33QJtrY0 fn=Ń1aS+qH8uX?;"P߲t?5/Xz8%4bA g;jʀ=5/RȺt eR)}@GHہ[G|,b]ІHHC &̽DL:Nز˃yFkΩ;RA5L{ vn4fԔOB? $cs7# 7n?T,yԏ)hX׽柩{ruvcA i lȧ=ė/}veb3a\U*#T(ud.md.F[ ^:`ƑONʃ ZݶLWNaYG*kb&Z.'S TR`B`Y2muڲLU3\RwԔ%lOui+mLXǖ$|d@-!wP t4pB*-mAН#waS%Z0$w[ /rq K $U_I դJX%5}J eknU, Y̽f7jHC'A ^a&lLFg6ße o8yf7yXHa/)OLVXҵWPfwpΜKs[;Stt&wd@ͭՍ硪e?])m;ϟu1lpOMS8EYcw#6h؞:s䯏r,lXqj`o5)N?r . IQv޻6w0zn`<ԫY]۾]S3 h/w,J-U^]%'a]o _J; ~ķgef:geru*Tc6y6s|p.0^# kh_jx*|wDT g(o~CZl_&kn (J@($(BBjF;HW%ay!r.lTGMI .X#]Bp-3;5# Z .ŖC]a y|YRê-#fׂǸ(M7C 1Xߤf8GmA:FkO@@oE`֗Os=OHsǡŇob=B4v\wRtk; +p>$iպW ž{SA/jr]_ڒds~V ˉ5@w!"C91tOs <Ŧ$aHRiSgK0+~ٿ {(7:d܊ 1n d4z xlQ c;OFiٳڻ.]3ey%Zǐ͂P0+'g,ײѢAkІe8L ÀGR6Z˅ TUfX[,@M>4b՗؟s& t2@k/r@ .Wy Bv䚂t)5ݴmƃ90Lf Lg?YJ08t+ kۢU&&UL B "RdI^^v4u֞y,+*Doh}%kzp|t]KF7vLoGSW7oWu@`ab*?%\b"pL>X.ԯXkLV gzdUjDZ}d+Hr-&yWނ##32=H#J9bT\u}7U#}?ąuj&s~GQ7d(Y?'5gu:8Jvx%4|CXٙVo6@/*e=~нhW XbQëZ3-w͵X+}Qԁ>[̿4v{anNU:|fD.=vZ~|}qהЧQ/෰4?[v7_Kk7V7~!{e#?ltVOC:^JK!Qn]:xhr7p- kwE܋o,*i]^m#te!yŜœw9uDbnt%ok~ ɰ|k9ؼ}@n<њNy#Bt|Uэh,eB46ǐ(p(3aUP CƦji? 9>O\EOLz5Z ?VgU cխu[R_m>JuRrњ,Kߑl3n{Rϧa2S Ȩ27v5]\{3*5xI&4+,]1?Ye%ތ@ו{7Ԣ"ZG-O&) +e-mx)-XY&|Yo{2cjm.\0B]`p{U"[\bbe5xhh!ӳ.?28|aG@ړa7m+|Epufw$~MbkuVu!ai,hܒh4AȬ!J["-_ F lSiߗo_Cp2̏!dkNYV"qw+Kxg-%d˼3 IIdhef Jn v-f4t"67ATxeb|L,l7X6*mM'1Y谜1\#o1v}șDT//vH?[%˖Y2NOW==w{4Pz]ryOoW8;tMZ{vioUnZ_!n;`Z秆.i ZXAK iI~M*:ʓwtL(5߳4.]ؽ_JY9JąA FbɦZ2{5*. +t-z٫}x1#^.1Gq nݸo8>}ɇ=>dm6z ~LA`ayQYoYn^]3<|LԆ6\YT}UMR/иj}(gKR}c«ω<.Ϊnoo%bPvTzҧ@Ԅ,?s)[Sm?l=U?I3T7{G @(U7C8p mtsM; ʹMf `$~pM3\Tʢv: ˍ z͍#e&5n ֪[sPY'ORR@Sہuf&tIk7<gHYb9xiiQAHjExh?^>RxΧ.`Ɣ:o)`4ݵy"zOK$i sDY6arcIiINwК^Dy`0E@P$__W*l|}e ?Z09YrO$n/xGO\~Cu6,i ;F/5ʔ5)H^v/=ڲ{0\ VgMJsz{xGS?6U-빩`0whA/ z'5Tg&_Sڢa _Ԩ<*[zz~M 4;(!QB? }M|yh㇈F6&M|M!,轵_ډVzZ¬B0'fu3 䀩!-ԨE\&"UJjG 2)*}׵36p`Xo_tڗbwzli Mn'ȿa$՟V_nWCQ,+&=غzovϞs mlxԧɽ0ߵС_ *ٜƒbi&̹{M_w{:,EOϪ}Mᥳmr^Kÿ M7^@ӹ\ `Ey):ӺөDCrcOx߶Njs+Lٸ0Hp[a_WwQC]B<1k 9wS9\ɯ*&~f+VT/eݾvW>AFGh(O֥rӺ|e]満;cv?PZ j<, (Xڟs@ W аOWJpA{wnYu#.Wvېn}~h&l<_E^-Oh|[.Gn&2g&ͳX1?hHCow}:f.6:vmPYsN'/¸M\_%3<+x"T;XF MTU aq :qFf=hT]ǃpqc⼝IÛgLkȴE#)f!\m:`k(YcIXLRV'D 4oK<|]d44/ IN6A:VԈFJdeeۺք #|RextfmjR_Z`D Жyf& /{ hT otil!Ga@bqÌAԴ`@k=7ix3LeZТ90b_lS#K5tg&_SZI~%+4G6S%K:dIH,iV+}+80?.J,4%*E 2'٥=$2G[GN=-=Φ /#`0:%sp[jӥڍNF~ d=Ɣ#Fl!VO]7ԅx}nQD K,z}O974ࣾ#{zOn{q3X"q\j,ű7}:s]mzzF:}n7Fβb~}}]ro eexz}+ykf\)Υ..Ae>n|+L){~rb-ru'm4tizӮ_]yϬy'Άh;d6#'MVO$Jjp C&F&&CGR}p[vķ-Usm߻ 8!aM_f'+=3 (~mwXDՙ5#]t6:ma)OQ3h' <>8gYoy@Lp T_C^gF;dH &9Fv5Ҵ'!Ip2G M7}GU6m6RÆDzw8_/()Z^>4Rn5. =AŇz/ےM1Qya TcYupל[v5^5U z*ԢґG=+X੪8WVl{v߲+\x "Yo^(˞91\L11~6݇{h(zڨǾ酹$o[ֽ6{|%=dxmȎB|t=/5O4W#P\ Xr9󬐧E[XiKg#Lhd%WƱĺ co2c],]YTV++Jb!1e$55uiME -`\|"HSҨo凔4w񙰷&8V0'OOŖF!%y]Re,a(gD~l4wU^`tk6AS džϲ0H?\@V2%YfpO뗬+@]h7'ֵ=c*?h5ui[q| Cpp4xVz3H]L7V9?Y1_x_y`YrvǍdr:3<7Zhew}>`oFchPw aƓ͖|a׸3?  Zќc6S'w}</ڒdzo ݮbz̋֕+3z&kgrʎ |"emz޳_bc)G7OxȎbo_̷'su[z}z\y#c(KxRe4TCS?5w)T}e>W-_[Z\v5q!NJvJ[j޿\ך-pQw@ S˃XIK2kpzCt36J#oar7%TG$ `9٢,ƒ͘t[bCM̗0 W[N{5j|pL_O<{'1zĒ3*geQX+cYYwSqx5 p}b]'$UZ!N".iR75KMRQXpRE;c%Dz%>C'Z(!õёec ~IP(m~k#MxΛLj0(?-1R$uFg+Խ?*mF_Ssji9=.5ja9vR8+X.I=F]߈)9I QhJ4cr,\gESG^'6BC8᣾PPE06/5.&UaBajCQM7G4y}ܠу1u4g^ٴ?:^t7-&͇5^esYNWz Xiԝ?*cqfi9_N 9 Np=W&qYʺJ1l0jYF*5~?O#!'/(˯0RXI s\pNFg;Lk@ܑWSjg(OD 6;ٝLquL#xɮNŒ`s)%4>OsT!:Lp Ju()]E憕5r QI^@.d3hµp\Apd2>M$ h: $D#N< 7*l6Z7ĦNiGs9A~+ ssw,`4 r T&ʦApӒjU%OG0ej:ȣ95 t)=C @I*y%F"VR`$lpVMlv7~;&իV-5 4amSĥ.A clH0F'lDDWaBo09U$JDGh0Q#W|io"F"s) ARR3$i8֩`< /ATLiǣ\Fk17t*zV)6 u`,T^J)d 'MWd-RcAI yE SKdJ&D}Q )LpvWͤtLJ)/W@0j"L篲\.u:U3)Vcf(r~91tH*djD(fVxX  Q jʇڨ'LSaU {DA#2/S1ˀ Qe1Z#BBM.1^H(ėjP(5w(TY;E@YuF bXLjN JB{*3@_4&4C6F,Ĉ Q&$Ab2+l`~5Dl0HdR\loԣ+[*(Qœs+( gdDq+E} a&D0JJ@^H/@T^Wּƾ\!;d!+PAX. "t'\zA*!#4ȥr AzLpvWրRQb,7fK7P=rqBIA,RLkaSaFmLjA82s#A'LAQ5L1L){ "lKIFUVЯY$;y}CP<`|5B]3iзAZ,\UjɬcZnC,K:m6`Ǩ:@kƆLIq9 ț4A?&eN5/n/:v3 %(@v2uI̠ǸL/,d,c,϶F5^QPZII"&AF<#3 [ہ/,2rt;$MQ3\KQed!7̀J97baH8 FֻfOX\:f_AgË$ R#XhN24H@k&)фW/3cp*z -93-\)sʲ)vԪjX oze D4ODN%4CT8K.$چHHC(T>9/(#42Py$AB2f*'ߴDㄉ|钗Rad{(I)bRlxr!ˉG#"X]*u#7^>qA\e>4"g7(V,3R0LSl$ӹiT2j5W.7ԘJ tkgtx9d,N8tLQ+l>ww`Tm* A ٔLY吢ЄG]pN:}$QCH7~[ܬk0*Oz#[9ɟ"0ALZv ,8'KʾZZ#9҆K83lk,\5Gx"(<  IiJ:ri낡{H!6 qm*Bf)ᙊ)^ƽÃɆ<;'%[F夑fBjA2@?iTHSD>IC(l$E jꌒÓ4jr3ù`jph$h^Z"7 z #5*/ ɘuq` EP#QuT @f" ^\԰<H9 Rg.NQx R~efh9D34ڿh.4  fBQsËk9t%3C٨en$ &*ČNM-i  qӨr"h:@rp}, B05RҌF%TSU,剷'u+N-x!cX?sD v䩃鿚hhSQNYBq` $8(EJPJ 䌖pA"@lB#LD I1Yt7MhrnB4Y22'`n)q>H.Єs1FLN*-ӰK=s"̠0^x"bh}m ޴WfݯcYKU jf RQwi/,3x_)MEd&ǜGà:^ׅLmTŃlfQl tZF.U@Pl$gSډ#֭TFiA -T9:6pdTH@,؆ CQ?dXire(5C W@Qʇ LL<@\ܦM3밠1P13phSv”# i<+!brc 4@`H3:ĠEISb:˨ 3C]!4R@!NH܋?JG5'PphlL"J[`C'b]p̆@ ЌԨDĺŒ`CXV+,<, ^?S`$MR `.8UbJ4HR! )߬v`.NRz*ScFYж|ue!UZM4 Qe+UbB*3ce `&ʡ#ځqe?%(ΠX鼨?7Y䳒DYyT$lh&) e EaKVp`,Ua~$DàP e9\s*CEa|+AU@yl0 a0>Pc}E#Þ5("5y>)@2*EAuŬ5W !MӞ7%E00\A 6e2b!DV(OYpEqqު ʃ@&";L7n1]D>#*A2VQ fSZ0U0t)G3[F1<Eei[80G4q!NlW\k#\uh.V2wQe ٺ5#Si3~L#08NҫO!ҟriU3wA8[j`>Id4@Ѳ_o^]39M`^| QB 5 B-tdnEIv?"s!0QOA0 0K[ǔ-WYj$Vz\%DX]28'-fJāD,, tӅM֚T{R'aFpl8&]pa1bq#07%N8Y޸90%sXe)34_h`%$="Eu<&EII i/d'I-L,Mxc.Mv6Of=Sq;`P4sV0ȓy+O[TWש8Nq jnULĭr#2=NQwT)^d+ 4<c4C" QMC8ݢF #H#uE__**ŦX!)1;0Wc&ެZ[ek^SX+6iq''šA$pUU /~p]6D BS(R0S4d3Tr;C2~E(i?JM a4J-zpiH'^0B|v4RH&YP26 EPVO3jŒUL@g'Rut婂Dz|Tâ f@k4&XMm^J[Y$X9LQP-@F4!(Hf)B`DȰEU F5&<)`9RVX+Հ365ʙ :8%ݪ3!2F3W?G-5ؤ1IudX )B^)7#LȖ닩3 ,`*sPns@8uz*fI4 f^AvKyY58(gE 2Sk,YeM!0`U4{2# B(ɃjksӚ;5:h}íTnjFbD! -H`'R] pV&,6KN HUPRh,*{+!Jo|K R [?W ͡,%9հvQȇCph&)|%\<6h s$40^eI\+])˞I@T {hW{g6m1]2gqbȃ;KЛYuU%{r-=Zʤn+E4G53T7 @ U}xviO)|WELIR3=h >g OXTأ7+֘K{ϒ.7!jx&͂Ll,CpX0QdpWf\uB,)']@6 2MZ0id)7&`0PؘHO5n-wI{bɣ [ -%5Lx_' OI;p|OL qhYdVɩByP)j 6RqÂYU01Y@*:%]Vw /h+4wa\CQ L+8C6i H )&Mk2&_B :Hy, V(jn`%QewV')iRG'Wr* }`{)́ .0Šu LH``x-/|H3>z@$g:3{d%pLs5{n9+ eZaf/ @$h6#kh%HTO ֮T^ I(Uk#w-_ڸ1Pjy%y>Y$eDĻTNgI^ eZ )bLHWw@w8)HhMHAH{r l ݟ!4b z|GEGL] <@-@zw57'((έS8f&Nz36F,9 WH%H#Ef;V* /~@7U/^8k]f`BfD\HoٺΠKAIA-=>̹T:w&CZAh˨KI+'3J ]dN09 m*lQCtF5ɡBuDz 83$M\L<G&l  .l4yB D'##5-z? 9)Apl39C͛ţf嚈qJt^oX z$,U)΁S [\&STA0QLk @ CUӥÐ">HLlp#$QGmBKB3U" rEa&Jw8.@I&s-3蜧4¦\QE@#-MTa#f@eU-iQ @JQ2Irf Zҵn儌;mOe0 ET] 2ZKA0앖:dC*@BP৶ֈja0ؤ6̶2H1 ? A1x*ΓSrB@R3t,3jƂ,v[6N[. 09[B;7- Y<΂tC4m[gG7~)O=53Ե@7@Z}G ;'ͦ4 bcgD"ȞseK= Og49e8t̀G`պM)B,Bl9Ȭ_@)yi'd5ԕO51Ar0~u׋gcYZ VgvxFSpF.ڕ5y:h!mylS*IT ֋:* 2;2QQlH4I̻'$`2 Һ'n;#/Ei0S(9Ic%c%,uE5 a](f48@w]D=HT(Pz2KT3%K)[P)I,b {I Fx3 4^-DE|%,YfN4:PS]8RY%33u 9= [sFE0܊f!7&׺+C3.`fJnmn\D0Ӓ"DnMہ9i=0k-(1ONHe"fAkNK:m`=L(%N3 /`͞S- Ha)@3F6+0 8 :?=KX*KFLF0W<4 z pmu4p&11eyڭJ%gT G:RԴ`t&9T&Ja MYr3_ yHÃ,UVZ)gm0rN%XWr9N 2k>g @"mrz9@2Qu"Z(UpJ Y0u@b^Xc䵫ÜtمyhDA^>qՖ4m8Wt\3G:dRRR&Gf_w0 #>4x",X Pih<(06!zр,%5/#H}Tt7yv!0SSBfN^tPoyfмdl:*P @*3M8\4%Vfs8-IPr$d={f&⾽@J > A>n.K"k ܆wIz4qH4;yTHms3DkD71fdԤH,J !Ex;9]q8fsL5xZ޾]YkjL:{`hy: A=1x]YkF>qRdptXg0ľpf*x0ҩ/@j$1j*BmxZu͐Ep쐰!9?JBIqLI6Jxs.)Fg8`:4 ,:+ 2y؀pGFfT8x@:!gj 8̮>@ PQYţT<撋`,\y5OSg#L8(:"( ϦO1^T<?dQ@?w@Xx*(nU,Գ<]*M¢!4_:j2$M4 ɰtz#i8{wW/A9)(s^;:@L\s=U 2qNKt!rS׆] 1 ^sᄸgӥX N6%oģE\9. hh691S5&|(D҄kC2“$< w0%9! L~QH8W #F &MBSpRd%FFv$IuZ L#ѾѢ6@WuLޱAQ/z6w!Hy$P#UTT5 ?љ |[ j<%5>Y0ȏ~Qr@l3WE eDF/D()y9LcUU"/阧ߎ2cg\v4.pԨgY%;%̓)8;TtքuLJnZSh4Q& Xg4fwYOF4nZEn{]ڮWk(Mdv62bs~a&ہtqȳfUI"^BPҰ@̳v47q 1DZ +)9;E)Hg` \ ]"@󡄂&Y{FҤt ^<叝D1F 6>Æ"m #ك.'gTx^s{m詘r"<^_E!Cm `#L%]RAҌvԉ xޖstR%|!|] 8KJrI"#\$$3:rt59OJ_!`A!%=Q| r<vI wd*tU!VpA )B3uzo_+V?u(NjK7WvaL~JF+@0oWy~"W{m,[jm^dUZ_ p穩6֡ )h8Y.D1:ZcΚ!0`b9Os < \ؕy:iQ|һJSHdg1p׀^o,\y:";qpeUe1|:O'>F!#c)EBHTNTcX &f )bh :Y'5c s@ҩKf,W:E*+@Y[:c}XYcSZu/q1dmX+GT"Kr*8Їh)j ) }%iVuYz;m. 1PZ뭕4pI5@Ҁ$~={)3H!K 2F`dcK!KQKPIrs9hSA~%+z^w"5*,) 01T qLqӦpLle}iPPJ,RWIͺ5*j*n:uPS\t@s4W A):|V.yM0^*lzgV<{PlR8PqSdrcL ece%{_*ŸԤ

㳩 T~U6lhǓM2%8 6h' P >bp 4MD3nJi@oХQXE~Cy{p&?h,z9y!N8.wld dO--uhTs㼱_^޽#>7#nvbj{3-Zn'խۤA.@T쁵"I)IJE+/Z)X4J{d~k Y x<>B㽕s h'" `]60G`̉ o *jLF ] X4o,=H&0TTGC&PL#1cÇxTЄKUJ.IxfG3!BNDLCUM'Az =xmZ7ZrM5 Qa (U8(c&FT9ɬa(48(-~<%a҈IMpM7 Yش]CM}O6ʀ:B8)$Hҥ:iˇXyQ^G1S}\8:sлZS:CUV@M#VBK=AI_xo|0$J_(d38 p  7-|L㜛3|w- Aa 4Ęid*ޭuPA$LAh,QZ $+ېьF Z \ M8clXY0)˦Gy(7k6"b"r2dE!L5CuϾ,Q$X( =jᬝOOBp,$EJJÿ@a߇ʡbe-IXL3Ī`͡;a? s8z{@{nG 4ka8,8^a"< e\AlQpb&u"zxT-ξd}9VC w &߰Ai8rM(pG\ n }pÐq5-|랇agŠ1Q],zReKZ= U]'*WA/+jӃYo2vF i XD9+IuaCJW^b*>hC`G'&ꄣc.NZ)"e5:1y;qBO.+?iPZ`q!еQrCƒ^usYzph_kKT4H0Ya:P1axEVdy.\ a E&k_411 !I4$DƩY?[ PbJrZYYQ-Lc~lwR-+{kɆc@nK1̱z@qA- EڕLfNpx9Ha$M=2XxZ ϤyXcݷ$#%s̱d& < NPBg(w|Е^ޗ՞Q- ItXAXa.S!y|0| A1/Lg$`LfFmF9P`Ϩ #q F ]XfyF LiCDNOTdXj@G=.3܌rY47cYBwa>)+îWBw8Mx+Ғe{MeI &뀍~=>y=Bf4\%8GxM z;xk&ϋXWw+`.G\/4!e hq7z;8u[vZ ɕ,KȜqN1մ$S:ayS#8 -H4=*1>!jwHV[%Htao*!z;qrL3kUҺˌ R,W>BB2ѭ 6SQg Er9JeZD^  8LfVrS;<`\?g`&5,ŪM>^X26 gr-#Ǝj`Te*Pn1斴Wox"FHm2v9|O82(P2N 2U$,"&F*pITjj馨 5 t1Jik(ы2cX+9qt`;mׂ.$1$X)ѷ ;u(cCn9vS?;iY9f+v?D8#+"@#$hťth#'g^W.!"P=+>D6%@P?/[*n *#6 pcF(q g:F ߩ' !;IS {6X3 bjC˦ws GB6=R& wb".:gK9ס)nfV8v*?5ٻ*빯6T.*x AM2H jNÙNCFN!"+ kW8^QnEWH9DȌ#G;d@`jCbE'Z2SdbS=LkE!>$ّ8KV5 1/ sj`(4x` :@kf4 %b0Z$D"ex [R8lԐ2пά0C7BK/*E<+av#5gu-}6QP&+&%w/d`hJ4Pxh`je?-_%iPo`wl>oAN)4/.g$vj2kѷƻ4CNu&FZCW xfk|=!O:DD`jg.bݑ ٬?La2-W1=c<-&㚦rX'(aG<]r'E*\PvF ,  AjE@$E/[gd[++ x%{ {qLݺ ޡb6jŊhP;J!sRp LT o-RvM6p׿F/5j:[sGpb#Hz3 pTXTD|y$f2͢Fz&/ObQFeLd7f +|y ;]gM;l` X? Қ|XI#CPea)q,&LFk( dҚ"`2\D3N"ŠQ К!kE(Hed `F:oGqh*M 3ڋg"rϠK/fOn?$yuȪ$b{Z5%喒%I5g$,$^5⇓,"Mƀ1f N{DhD֖Z%8۝DSXM6*H|$6_ ,l\0oThb[_AQrU05Mu^(b=}_Ș$u߁$X7WshdMKx dy =x\#1=b M0'Cr5z{uREZ dMW 'IA# i< EeL980B 04nz aRO.Q$F+Lupݮ^4QXL/ 4Bv22ݛ hovuM 0!Eml!d`0`+QK<݊$|A@X]CyXE,-U?465;dU; y@[36uUT<%w'838^P "r$ٮv7;Dr(;yVL9A|Oi2:~Na&' $aIyO>ru f敏+ށi3ǘ$H'O JZf04H64c[Q] a*䜭~}U֤1W왋*u@¡klZ@Ft5CB431ǖ_0њp]W o#C<>ڡfW"9K ri*ZEg2q P2bP0BC@hMb(I5ETF OvePT7'%9eW@$9jE Xf(]5I@ ShƓKAe7#5r]GRgTI5k\V(o{$}3|5L'4Ϝ82]RM= bpH G}y_.E좮;Z9ׅ! +g:~E(&.X! 9:!` +J8 |@C sX%t`#NP;:t SgA(%l#He LNB6aHlAA:J&K0 %`ұLȺeUX( z8U檦#U rfmNT(…Rn fԙ2ZTϡ 4nI0akV]C-b:d,H!}#DI""QCV/26,h>"6 n#=N L3gc=;!B+0YhH|TXށ9P.{,'R?\C/8U W: 6E\j Qo.˲q55XT1BǝbĞ _㪧{?'0$:*K 0n}dvz"vm=Ίٖ^|卓b<|Ըp-&/Gl#ύ< $]~sEC\-fq6(i(BUd`jֹ juUx{%9} ir5ɥna]Q˴Z$,{-K!Pbz1u2QUATA(d MWE3@7!uH(CbIE ㌕dnf8{aRؤR7Jyb "aӜ%zrUgc2IDa&]̩)Yzђna4BnB+F(9KP19%0 L3@X:i쯈FR̨Q,k[B@ˠ 3t5 C]:cJ;qeOY$*bd@@0ձ~G2<_&0 ! 0''Ӕ$K*q+@Ӎ-,KkڝHqZo{j<=UG槈GeEN0RRZTfLlPCbP s +p`~vMGddo46\CxyzBEab9)h+Avv<+BvF2GNL̈́=&(5g@C=*!?7!" L4Q5/$ x:T7x+H~5À "WpsG6jZ!]^=H0b  c7%*a qb5 k] J_[pW:ۨm 4`74X{gYelƝm 4qW=>%?O/䈒}7aܼpkt$n3K"V`[5xHƗ3n^q4\s5h89&U^]7U*%Y13ju¿|РJ@0敂FڭPh ΚMi70P]5R,<?!0)e?#-T0G%8u*P9_OH)i@?|>**MzJ4%ɺoGњCm ,g L,d| S܆L [ʅY]hZ s5q(" R?NQMĭT,@Fꠈ6xP`h!Q$jJ4[uQ]bv$l?ؕXv*;\x!RɛE5.OQ)+GMd :h@! \Y'j#dL p8 cY>8b4J9Z̖Fȉh#?vaa!XC f$^hhy$ $Lpɠ( ' &o$ d'H#~ďȅ;0yR/"ww QgU N,-% N66̔f W#/▜wm,`זTD3?1i^Ώ]-h5bH[:ۍ+ s<̘MވfITA()\dC˝~5MR( JIR=c ` ҁa6d+Y9ąKHXN>`Hĸ& 50چ\bSR|(kNM;B @t\z^\iG'JftJ[MTq&VwLTq=囦d1tPpBbgGC^,ۯGOJSN31n~o)-DK9l*N%D8*('"#k^&x hnp7<%|gvl27686Y = I4iD`O.EP4NsփID GSOP n J 3]#Ս%.P [88uff93s{.P4Je8JX.4&N'袉JqWx*XR 4t0nSݫPnR(+n-y:Q"Ճe)*:gvaa|BV?TH3ŅGj"nZ/ ğ_HzIqpn"1WyqbTr7k8Ct|zag1UV@hlpӘLx5QK0` b,Ag0D"(36s#20F7ƚZ9Ƶ.? "U@Bq2!1̄7x[̾ kB1)ڎv4fV$I88i'`kcKRɕl1>HxmnQ[9`F4'eM3THuA2ZqVkܳnY5&tbUBb66EF-ePK »؁SI}ۥj)A^xBh6TYnVEL "b)ՙk %xӳF!Z: asGj&/AWd>e Qe$R:t'="?" Kgi$dEl+f}D/LZX=!(k X|![ XsTH;&H#{kʭS `,v4>eL#RG*?ˮ`LاH81#B%ts~ݑh$ufAɄ *BL^7q)FDl[!=C)U=M$%Af3HL A϶yzԟsCX d7=D-WC_1"pSAKzT\DD7$zC >ŨnEq)Q1e#lڒjzHNqbw[ظԼbt4]W6Zw Nh4JDؼfqeMQK$y."Œw6?R` NE5DVcY^V= |b-۳=p2z0o a=1.>0-7Ust)"Oئ/7yc"b0ƩDU.#{ +1L0p.+PUbPEaԉ6oqj T0? PNfys<1~*Au]#71?7x>7N_T%*w?>K&Cl#B_ Fʋ(ai -֏ڨQ)XJi2M0Y+x.7Ր#C GG}b6[DIZӨnF:]@H$ao5N*j/pܖNa! d?V4ot5*`ulc9f`[H>{ƹ`Aɚ+G>Ƥb"[|͝B BH@~3#0k 5>K(z&RզaLs=Lzk+k'J1!u1V& iq:! rÕ, ]7h1 T17} wLxC A9|y(pdG0_7*xQa| MUpn:Di݅i@je]ƕ ,` N`&CJX+<42dL:+yZRht8鋱-IFC/7 T85[Tu3ܜ9!GL!M:1C js|r2v7t\.gt@HqU|mv@Sm776 X3 @vd*e*?4B@o~ @F?S+o~bz0)drFk;)zJ/,;z{} 4gT?{zCSlGSwGe@@k.,5wwJx>ʀ&ki%I3p`4x XpWSܵenN\BoTV<@K}_O_{81{Tכ!(T[oݪݚʕq&3bup~?:66͹# !G~pAHunh!.EA*(lkY5ڭb X.a)䂁*{rhʇĎ״'B!NUfB, L]Owb Vɰ._Spxlz: POjd`G#20r`9j-,~9Kv|`YW59&YBH`ZAfF:I d^el_UV)n NziaD3{_TU^.wPIA ӛ)D_A# VrE* )NBA󰪬4NwB ;D"A_?>D5Z?yE_3X|eHϾ k+Yn7}204}nU+^؟o/IQ>[S=Ki.mOY(s Zc@ F42@}d II6 τzO EE#GAߤFfr ȄFAlc B#> u""xHx(`M H&.:- r~[JcQM; 3 #dQ\&G @oܣ^#_ , w]ꘫn?'b:uƜ#Pþj(xf{/+r8]::E&3\y1]G+LcYs?AkoTw_QW,-*K.{:jyss#412p@)Eexׄ2%' Wo?q",U9Mo*`0P͓|D@ 殟6 f<[RJ0BE@cbˠŝ]&['[wQgZi`=*ǹVE^\G|e\q_+I!}PCڿJ4?f'}PLs;$p2%L@{I/8*-J!ť:"Z.aDoO `<x'i+l{Q}8p|'k}+o" l|kݒ7 xKGE=*{hK,2 *-2wjN^s>r M h7E Gxg=k:kJChָq1rd\ReA?MYur[Ic\i6O a僊2@ޠf A]/*+uq{*¾hu@=Tq0n&ʯo7xE9U3AcGCpY oqz8.X]/MѤڏp䆌Ӿ[-3pf`<>=yeoݼs8d!D"f^+v6/Z|Q?AɌ/)g<-zD1{N1Cs6+zr5.BJQ)zh:F?7V!]K+LFD ̯|_zyLW<V/6 R fW+7 +)0%X)!t𢄽 pM"f:F``($) Q!̖ *i]y5MI0~:ѫTc4j[,Ȟm ؊,4_ye]KxDp9bl_ ow/UD+t RxQ++`oYyc=wH%$woc;)۩^ L[C'쀃߃mOJ1ᩊoS9@t\)~3mgd8wXKH>C x:HQ;%|QӛTTg:rQc$;# ß9KE '>C8-USRRYUd[D+T5N+e^m*}`ЖxfbՁ 9Նi IboȁÂ??@G/K6EqH蓜ܶ"M"aA X2ԑ%,N{捃o" ķq_~-YQLO^8%,P:\^<$ ||LЏ0It(,q53I_ULTVR2jPnrwxNx} ֱf*h0x䷿$>#aKs!(xB{}7eBRpW!,S~B,+lv{$3 @Ho/ڳ $fY oms=%Xp_WvRdLC@K3KnyM._\{ay@!_ ~be ɨ1IsK@$lJ\E;t4dUO),bᢀ[른gd'ޚ&읿 <d+¶?bYsux>E]$-l!~+b8B49ܮ Fqd `\dW6x~RJ%Fk`]OaìAirc.֎83u/z>~ u|Yk.lWr`/}?S[ ÉV4 /mUei|Xh{!Kvn0rp 70h]'1qlaGM4N?LU3Da5[-Ug G`pxښYN>(g$Hfhj,%D C&5*Lgf!T~"\$TkC9 JEOdQdDNOQ'L@|C] NVfN*j4dt/Hy#;^3wx{XYgHbJyV l@p% f$ @ (]' $ݨcC$$;&LB5$5r:K͚$ Ak;E} "L  9srQjV 0,LCL8$[+ B[6+0@) 18BX e@Uʼ)؄)03C|*a%8_\?{a|D+|6ԟcX.5@&0̉%D~z|X}[~jȀZ#oP7u!8@tXgAr@can2 |uѐ`@baaʓkNbү52TB(8ڵ 79KL y?>mޮf_ הC&S~%) jvpzBO ,G.1繕ܷ2yQuJdf72,7ÊBQ#`O/''vi %+ +0 .ؓdq66.ݸU\҃+Y!":;.Z"~Ŵ~SOI< ?l4[E,~PfdE;͚Y&{vsn=m-."\F%$BYh0m<1 :|vو0M2lN%IKՁ; }Ѷqz2d;?Ivw?y~kTKI&@DLOsrO6IdɌcv./l-b'/Q8a;qͭKS֯^zBo,5_̨)%—\ EA+Sq[C*+NAS&vK] Q/CQ\@! ҂Z!Z7L$՘"۰˴NXWKq6+X ؗA~SX> '0d臹Q͡=aV ݸ_Zq"La_TK796< P ff cS3@7Jü!0>Z`P K n•af f[Ǽ0Na޾ R-s-}.0Z6L<8@*R1VGH8|Do*{PsyĆpL->ۭdayMKMGo.27>Az)<2sӓUb.SD-a ;nkqeG1eMNr#fʊ*;_e?Dm2 ^s|3!UPdD )w-\`ޓiF^P0 {K*@bot>wJf#`}=b xx]^(Zn ^5iv^-VXgi`k)^3 +$=(j{ؑd ͝1bі\L&N;FdhJIvORX&I5Y9rO .ϐBr5 )FFvp4EK~Z6+t&:-V q UUN׳^V= _lAZS[kFl}󃢴Ǫ95lҠ$!] 5᎒eLYB$mCZyMG^ PYND3I|3m1 %},E_'Y?1r9# P@á\NRԴ/?~ͮ_Iǹoi5Yo@{Gd}\;\{S7q!"Lj#:EV^O{Nk[rv*$DeR@RX+WeQznq$XdceFPfk-IFhco%djÃD%Khl8Y;`kN XPwo  /.G!8#0ȕvrX?`"LZ3& (:gBkZOP3~"DaelYBkd{Tp<Xrm-T2p},yo}UU~zbZ8&l6g>o?P˒@g![%HAq#Pk2; mF]e ʼnM.oB7.| /r-d;:͏%1@ԼxhjRB4Boڛ!y)X Ǎ?ߐxpeY_aRS!`0S"o2s%Pץg U-L-Rvn,;0 |Iɿ|0D!*\X?3`TLa@|V*4gC S#ccu@ n #"4B7ZIo}_QvuW5iy*@k,w"f'd׼HvK?7Y.`۫8/n>JXplBVvÓE$# Y (fJEqH ηq @&P 5MC 0 [&oۮ7r>sb8:юgga6 '?߃?uȟߨT^.$x6~c~[33EE2Lnwtr3WX{z ygL" p< JcޘX qTVVF' WۆxMYCH mX(dxPLTj|Z6M* =}.T~cFM׸3uH"Smwl_t>^e{Z'4{x?o[p "UaZnҰiEǕX׾!Xd+^6/ (w`J$ُ0.Qy( &r>bXOiɂ$z6N1M2EF .qV-8ؕ2i';`nuAJD&!Gίˣ%L'U( !87xBQjX9a9VJJ1S$r Tξ iaYIby~ 6j#l8)(5vy`·wJ:/c7MaWyP _x0MtuùWAj5#;z <sEp#` ف* rHQ 93p{t}h~gp:@O$@㚺0@3WP!b.&10}򁶌܊o <PIJ75G*lYƾ 7hM9?'\4;ͷՅ6=t,X)] s_Z|/G[f2{BB_}9Mi^Vq"D8k0ѤR2c =v2%S(7f ]`]$XL af[dtl-zϡ֥@K{S( '=Wgs?bۤng~}u\ lCOU_1=i^HZWGraxL_m$`F6+'KX 7A\IbP",)YtN* ITa\&0G=pOVÁ=ݳqNF*aSCwCd~<#%5<"?b3V_{c1gN4oU4E<*/d}~kqMpmOcUw8dJNj?#> VF[2%2P@E(Y 5Ub#.} [BBM{a`y yݼ @87SS.,J‹Ʋ|lޅ$3@ݞ"fo7`&U~lDZ@\C7$s[6h^^de7V*$K٨-B&$xiD8kǢ"L;1z |f`wՌpj(B$λr9 #AnW׶Q<ى4 2 el8j22M x0O&|[taF8 = ͯA :R; `0S x ΂ 'e?椗FIm%8GtO@.:BD\ 3^ ۂkWIq5Tn|\ ; lLVS~`Z^?+ F?b^2i/]_sY^h+ aXInҌP;/u/'me?}{пܧXͳ5~H\3gp0%P@Xl"#E'1EɏZE"{UֽN9 msopgjn(S0A>Ȭ.p #<񨔁.O]n.:+֙LzL}5>~r6$%Trb^K+>E`>QQ Wd"e&ȱƟ ISO.z_U8Rӂ Ì]$Az: ALT:!@%0CC'"~G}.Cp O. TX<ZvVv@ ǻw20#b5΀W*M@Xgj>\@gvWvWR0ڻ^ZvO+(X@mY "\yZ@iJ^mm6D_+ϟO ygX ٣ <4}*W0LWˤ3l,#HaXܰjZeK-5}RP!-+_!3~7Wo'5 @$@%bU \EW1Mę@)#1TYQS` dov[uD4#Pn!Nj* -*ӄ#rBdEԙ *>.|X^E=Voo&/oMGttf.PU'rgtF)' #dQ,j'&ppBsb#:Z˱6xx|L$lJnWQ39Ko)mFd}iP˜`A^w/6wg=arbh_ͿCwgKo}Y'XɋbጉݫNK6ecku'͙'+x(܋6y} J6(-"4x䄝p} (Ģ^d&ѱ4$ @$?he83Cx@A;hrm 'tBno)JM9t2s(CYVbadcF:8ݒC88` gr/ipWh {\나蠊~6$M9Ɂ"0a<ΝP   .Ϗ7@<mXr#^Cѓ&y?V$7 k,Ka&wŧX?h]{0 ՌZ1[-(=8ÁmاGa w d?MEK"dԛX@-hfW]dZsReQg %@a00&Y(`O5&뎈"e25x$O 0%6v$@_mk:w>qԌHuˣ꿽+Œ=ԙh˾hN[.-r;tZpR)?28JlFm+Yщ%Rb? 7ވ{}WZ B8%Ztc鎱R ӊʓt.$|BN-rrw_[ EpU T?\tzЗv{qq_zlRý_zȁz ?mdoM]$ fJ#P%TĤ4pixV]V#@ MD@G` WW2}k..ȑ"=7cRAe=t]^9G-4|d t*'zJ5GeZ3^?!@^q WcU #nNT"<OcAH}N%Upf> =uX{|DKAPП[:`Nu |7|bwZM0u??` O]N Y8 #w1[*X~w10-@``€t0& z9~D~{ѿ:o[ $0P5IYD)tJՎC>OD#I o} u%6 S(k"L?[Uhs}&l)dIYyGVfاjy hJW.;`]M9@xqd,-H9L@8r􊀄 Hk쉢 Hg!zdZ·@0I;K)1DJ8[bIPXtrR%<}i`A9(hq VV- H ]x0[L  *:Ig 7q3Go3ǟ(|W-ԡۙ.LV)r}" ӥ<&EvP%v_9 KOΝlzmTXOu=uC^y͓ @%ǯ>'r%-[&)DM@I9ꌬܳVҼ6Xe-vEЗ#_frDI|YlJ&3-(ZjQ]& ߱ek>h+YB+..޴fvzFp,\tP q:_Iu:k=- X:1v3_2FÂޅGN(4ݡXx 0Y  #=` Ļ!XȀ$5.d 0<SGmU7]~3\ xBMQd *p_ 5k @t820 bto3qDߏoux2ο`| { ~s~"y;ƻ]:/"z~ `5(|`Z8v)|VٙO5Co!˗_޲DԸYUj~BYq+?dů -8鐆5y}aQ˩9 vO!+?wo  [e{u{ O_ցjCK"8,C(hZoٍjpyY=j?1Yy[*86#$ЈblmvA`'ZT. 5F jf!w`yxSoa-Ԛ|(M-2~)Ff=k\ z$Ið^*=+<8#)(-N zzoȎե* ^Sv"Tj1ļD%6>qKier vC0(0B$x72[ Yb1w!8)'E 7qiU"h!R=WFkTPNtft1K^ۓ5 45ꛫB+H˗GA|%""[]Qs_vLK׋B2[gKc59%TTᱠ2%:sGglE#+/]h}d i+@,PH +U1ҷ#x@$y֊n/t;˯zQΏX5*JfQDlD@bz6*$+޳qLQBKT ɤ@ Kѵ%WX墅Q3D._JBGU[1+-4:yӰQTʿwbxT 2Lyl'ѤIH*$*້;bt`V$TDA%?>8}"Ydec}u*h(7DZ1QP"εD&f|~1M` ,4qaz~RVV䔝[=& H TYl;'{r(m2PƟc,VFSvq͆$كcٜʬc ='}}\؝S?H?JBJodv=]Ǔ˵ˎfl@R ̨qTn'6ߐΕiyuBi 0̄E A@ݥK>HhmUh:SFo޴OCC\~ՅMFeynZ$Iig%E"8p#P \!a7i0j_5l;ۙX/ jf_qB_':Lh>_Ԗe5[_&Qbvh>}QS ~)zcMGFڝQ*S_ء.ץsg GWkm?`TҦD?J!D$(' 8mv Eu^;z[URR rn['2cv]3jW;4uԤHna"Lm"l^*bfS3ÝO]q0h~35V5-~bo|AH)i2?LHܬ1cƀ)U0fQ}3K-uhhOP_x Du6Zh @q$tl9Rgv=3<B)ďhLAUXISf뱗5O II$ǠV5+; [%q) cBO|TcJ֙,A,<_p!{BȒ 7?ᝉ3(sVRA)ΊdР`d+>t:̳7;\;ፉDs2HLf;X $Vw0b_ȅ5tHT3ɳxfw8b 4@xMFYkgTdN, $pBN*E_DJı =Z` 27iJO5I)PHމ5\0XX6d9\VѸ '7EF?(C՜#oJ y';)X+ծnܐovn)C 94^; ^ϔgww ҥ8YDRv%[^v`(AN2a`Xy$٣'gdRI<{D1N݂CT[I0( +bVvYyX;#/ԕ%m9Ahd[l7VaK)?/_̊$2{ 4 `0 [`gLV lmQǷ.#,No˃H=b oE /7e?͆l1a§blG@; r1T*C$Sm"nĨQ@&O&Wcn1]m. ~*xo1w+7蚕O lp]vdx^U͜U氯( +}jk =yznfV/ՊܸyzԯkMRfk(^>Fͮ'ctstE 1%'5̚;cʘQwEf̏) W#VtO~/`xNUטn̓,<1r!NQsjDE$h&s;n5W:X[&;?a&D!.EW4ۺl%"5snuQ?05%۩Om==7ڽ챽{&=J_mj3\E4奱O2Kp}7nH48c F%OmPfՙD2"ad0^;"I NgE!z?5)+K|b٦*eJ0ҷvޘ>B1Hj xl=l TTcUuHW$/0!Z6dǚ5H"cv+iC̚`$h*G+$1vLM"?5EZgZ6 Iʩ\u%6Y D@NYD; tɀ[1QE3nuIkX-YU?]Y`V(\9mziBN̛ vIG|)lu>7{=r]dArb MjCj8EiMI,+ҿL$Su3*` ʑ%,ǃv*qGI J"D@|eEElGsOU]98bZ9>(Ul"ȁyl2-}I!RDޞ ,F6t`IS*Ğ$EYd:n˳BÊ8kaG1"ZtB6xg"IL} @moQ ] k>AmBY i ~S^G +Vă ^ȴGJH7?SHg'Ur,|z=.\0 v8eڼd#Ȗh@{FE}R`RŤZE kiGY WQ$ as;l>c+zyauP4,Y3`eM5fx{=y%GjԄQa4;`0!FfloF-Y#ķ<*5n&M(QHke.jFY))ȥ\aj>EuR }IV dʌRMsE?|$$(d3¤* w ң:^@鏫(+S42$æIK*ʕ">>!icslS݊;+yyI(Ť.f 5,tD5Ь),"BHU +Ԅ܏qL]⎶(茠ŭL,DW~cJ<, *|FHh"nelKm󛶣4TNIΤY<#tڛ:m--G"\wϷա;)Aͣj9"ܹ^a_#v]O5Nlf&+ rIRw]Pחo:]f)a.(t!.7 Ig6MԈO!&%%G3tq?|9bV&§ 1hnL.QHL7/!1] +c0 4" iw hFb<_=W/CSzv/ D&[XpHcCbdWpN%'=S%qfdd])iR;1S&M6- 5"wfYƮ-t0]MR5bGD#w7UFar1NBEdp X\r: ^0[ETt\ ! 򍒝"'{1!Me[ߛd FH& ӰR|Bנ~}~jksU  F@$st AHB4IR0|8bEgҰˆL|djunG ,a$p>Ut]X $.#JCZg|r]huSmb_uDikǣG]7#Nb*WfUVK\ЎTEU#.J2GZt!b+f{?c=Wny-9MsQ*I 2cU<>]Z-|  PYLሃ,})־Vbp) M[BJ4-abnjCӣm1 )/@*ly<*i'`(?ݍca `9 5&Io!uz v?W$ h$CG]0LOA_"\ulJ0@"^@lJ ,Sv=i`ނ7Sv5Hy;:6@ٳSM83k4/XpV0}  DO6. Ai/-+AtaX#.iI(en%#A.2U[+bW=,xh,>Ŀ!,KLYIXl`ILZkbxI]wfM_1b V"_i!!IA+wf_3Ge'‹j,L0eɼ;VrXقS15"D' cww>*5xZX Ր=_^!U';E< zɈ9\Sј/2hFeM,+$BJi(*\S{3QI#!3d>DCtx} R5\f1YƖJDSUM$~MzBg&'D%c_1Y)`Z7F9ll0iO '/~,]Jښ{If.A$ʁ [yL+zяKHYK%K!S8^C+ G"͠mgLq/>9,8+KFvZ-GH 8vҹfq@4o ,i)svMkq͚ehm^ 6rڊkm2t&\xvp(oTa^N1 fW6|vVb<#J9Hkt bKvB( 1?gE$'=e 2XooY>X]^zu‹$A'3& \Aɍ~&wT++ZIn2{`hDf?p?Ks-B`pHhoh-2Z:viThNR:8iWj4Y|:csJžjjr7KhSk=@0=reP"{fuHt9G X6D 3ȲSj!j_ I8?Fj5\<fLTb[K ,[ 4uT?AC(eb4b>#RZ9[c{pN(nbPy 35$Bj-(uK>ѯ uM RR@ib"V0?1iArߒف ,X+8%!B⢕D! ~0 :uـTI5G_ce}GG8D_g7Z=3c+|$+kq;봦Ua~YGHz80ͪ|وm-uzBE%W{Lh}ظK | JKZLKNtHΝdf!52O^Fż$2VO>C_ ᣿L2}hRaGC_B|3H.a'!M 38s a{>J5'סDB ɦ-i&I>Ʊ/ꊐja󚢰Gy(~4S J5yh;ƽ%t0_@ٚX.ĒMöpؙ̚&P  Һ[/Snt)W@HD5DWbe! VHO*Ld`Lr"]Eڎ|F yǬ:X.DE$Ľ ı 7w´4>6^VSLkP% ^ɧHD$I#}v:_B@k͍:JB)'CPaoqGє jY Cr1=E. ==ȴ2n v5`ՠ)8@a y +S2S2I]QrU۴Kf{P>Sfz{ Tj򛠑Օ+躸]`Z:+Xriewy j+V2v\'fV\W;D`d]p 5=L(V nz*ɵtaMW0|@ef.@`4QDJ³@ZY/#FՖ5fU$@0x~3&9$]e"&ߕ-kB(]:Vbe] U21)Z0iRA1)5x /fHژ Fr5j3!j%º=HٗXⅣ6ڧ`xA@` >U?qN b23kztP (IT)la̚߇(9Îz@$Sƌ 1Fa` c7LF\_F4<%1e)e ю9T=;n8+ ّt;'| $yVA ό"$ 0 ϳmE!ʩ.wY6f!Lwbeü~Tk*beXF),hYrH%fDkhyb$+hTr#z/7$~G!\paï"=c)R珗@%5VU*H2Y XnchQt$59!ʜÍN)gp?qGAge8TMV9%gsʵ8a_: :!Mg[rqp*OM.o:gcVoB Gp.w:![3_ YiAڑ6 R; AU!  gc`'= E"s{驤OOo&G')pSunYTJ}>>`NK/iŸHc|ꂕ,Ϣ?u< *.$0YX汧 *ž6|έ4Fm*q_/b{˘!H&DSBD. gƬ  )X O#-` $1^ Wpo;)7{~]3F33ܸu_3 Em?'hk I1MwxpgMqBtD(I<%`s*jp#O rA+}4D2-&TcIȋdC 51h0z3=`¬̀0{#Ug)(-5PAOypۥ ޣ*E3'YMX^S6+?!#kJpE*"N4gj6Ll*CwaA`#(!H5huӲl&F<(ɯg-1Z#Ÿa2-3ibudIodԈ2 ,V*u&. GոebvqY.z5j4SLyYŀ,rfG <MH}gڭ'cw~P(* x'fygMc ,:% J ^vV`T5,5 \W-p @>2B@jxE1j0qbs 45̴qLL wh!\crՅ_ꒈj 4j~ZQ3 r)=ۮ#tZq= CTf\ Q*fh3:;~K1\`Jg\_B7@ A,6j͸Ä[㗺1 <56,Gs?0IԤIJUJBJtqA"R\<34~qR]`ҩD?|#Zz (Ս* QEЛ; J5Єα5XI@b[R.F'bY[08:%P4I4`c%1mkwNaN0sSD*ۢHJdm8!8#N9g=Ȏa-0l.#j[!/8Euً D/گϽgsF6m5E|E%lkPZm-Gj^Fg#+5,&nȗ&,H/8J+1' ye+rnؒU,K0 |*YBsѫ\/׃[3nw@(n $ f0ul.8rXSiz i=j:d**]2L.SOVѿ:Рe@-XN:Gّ$.*'TL@##2_Yf{`!v!wW1r{NW$X5D:ܽkuo7h$m1?9}g/j_LфV1ws#; ~6ⱤFÄ *򮠡A7Z^Z5RCFɘYc,0lPrQ+3` .Mass8>FؒI|NEIV _vR "'n2{4 xixļ๚ Z`hC?3Nd1QyL3RbD}Xţ| 4%[ZGϋyB)6^dA ^:+zT`3* dÛ6,P/`X7YK[ L0kNjM~Ѫw[$>KB'EsjJ)˜!2m,5^xQ2i28\FMz4ߘ'1lPxT ޣX7<[2$H2 2o+mnAԼ]X/F_R)2/*34(̊SOorvxMg:u{_Cw)<)P`S&PE̊䡺%h&Kf+B7Ć2hF6Y4@ s|>#0nnK&WQ6`Ϫ1fYjhG /AŋX6m ښ,py['@glAbt3.L+IkBD*$|wtxyD}'rKlC̉jX{ת]yD2_fk+:OM ⫃N,7gG0ٓ,.=mXئj1^i7n|liAB~(uvVMq [P[Ps0hKYrj3e7>(:t63 ҔV5a J,G9MgxB Hы'ex|mL%zkpTܿͅ-ż5"[4ă]2A+WU*˗:@^h}0x9{tk}rI Ŗ_Xq,yQ/2\5@(L{O2_iOnS*@&4 s_1&wg$MFIu/}%ɋ :Q >d#E/(w H|煄Ea/΋ 1 |o&_W8c3PF/4'^('5.Ȼ2ϊ>ZO:)hcs ώ憓ą`?%v?wtrrU'NnkP c$wLrh<ŒW^PC2Ɔ8 E?]lh!A-ytYl }_x;yvsQ^<¿Fo] & JPNxNr2²B_̰ K %}C!vϔ*Η1$y:Bk2a?KgǍf6J9; SU$HC>\$_%s}))ȝ:USBF+q\mMr%{hr3*!o׊e"nKpxkdx,}eӈ|ᑇ_vl &ڪ񑄍a>^]MvsA)}ȗV[ T#3igOeѳfkӓc9G0b+)tx\ʦKYApi+k#C.gug4--Q͏`NE5ZET#xMd䁕l3Ӏ@LU8h`H$1R!e ƖXK4ki# |%_JAU8+^YbD@SzW˳($AS)f^MB\s˒ -,&)rbw@L7Csɓjb9/#>+:n' 0&̸r&tϢKhK awc|7'cI% $tMCEe 8lȰ|75 xNK 0hPh}GVR2ج 'tϢ~|C~~Es0?$tO~N:oFE0@0TǙ%W߲2wQH1Ȣp/ *HS`$cSÄj`z$'ySQ EF_'ncK|7'-5Wʄ!;sZQ_;>~U_AalXw6FK$_|k M*͞An,Z\GU4.WS_m$tUZMe(DbŚ)&?@6+O#2ᑨJ/Ŋ W4/y,+ h 2UC/Eǝ@ P!vv>Zⵠ#41-#҆?LFx(V!VQ71,T+j4'7~_2}RS~x"nW_tz{+ ~ILcYN0BApx >Y3G ߲)h32̱y"Ҧ&N&졎 'iZKe*dŹO') |1e&\d+ sCTypz6\42ffƂ5"*hX@ZU$ҨP}/Pw&UtEN In%|k=/sJ6g5ea^0n*›;njU&MiQ]%06"B&fp(8qlpuXHX(mVTh"4) +/ F TiaЀt7*iςյg|;&uۭ?װ2u%O p6^^`y4$-1V7k= Arژ4u NMK ¹ƾb|O+b@Z<W]ji!H~I!65Nϸxӌ"h$b4[)&qյ𳧅z@hM4 e\߽z f|j&=1ÀYg}jU<7pVM3/8Wt4/Z`2_4I+Ct[BX6c3i2 TJ[bZv J![L$x˓./ӓAIEtO(ŞShnjo=n1{ a#&Z44=DDygof&f|󼳨bjy2jrIѺ4ժ1*R23(s#H5RnC"dDY<3Ik<,Bm4h]AD˧oA|~_}ayW)~hzM2Wk;SebhKz5Di~X#Kos7}7}t\$Ou߻IT#hXYjB Tl<椏mbDHV.<u*p ѿJ"(kv+ [y!\0n&!cH͠ SVs@s1.ץED̎fҵV&d\H'dJ&7TYyY a-%IH+3T>it^]^OE*DJ J54IJTTR]# '7G¤@<,l{ +Dwor c,|lGCbF~M+ 8``2 &Â! -1s[R3!:b\I -R%{-Մ84ݴAI<ȕq/jM΋u$`ʛ&"')p@+&뢚$7rs{8&}"W@)L_7khi~)4Z*Q|243\1#'Ӥ1 I /RJ4Ɠ+38 ƨf 3tj{>N\TyJփoȊlj+0e/-GzR@5/_ڠiE F&̃)} r cxf3i$7Si%~ А43e>1Xa"| 4?6F 4c=QB|i42KV?-i䕖i<d`r"r@ /rშRꈗƐ6i%/~_+k$t_Q*=ҜIJ -5cF|&ð3'}Yn zڂ2;Vj "fM4’bF ͔/X&ҦJi3cǕt`"2GIʪMBP?Oi()yan"0F2Eg[j+мe%5[ɠv[0hjJf8FՈJ>F<# 0I1kqO 1:HEĭJdJCTeDhpIFbI9:?0^#U] 2Vx/N| QjQبх yHVأShh$dF*-j̊BVeba|E_+iK=42(rLScxdZT&IF sy){j~j:f|Di\1/i]3 !IyrwUꋃ~q_ ypM#tzfs~".i+RHZ|Xc5K.-C3{G.kQ2ʩa9U dآUA)MirWzN(+/Gl n` F Y @|zw{f R*X6篜J(W-#]_5ޯ]. __Pf wg#٤a퐘s(]~)v5dC}&+oUD$Fv)$qr?S䴄l)(D$k̼O}jկj7?X#ߵ3@)w8{qPi(U:7џ8FK[nq_߬h<1#ˣ`7C"BB##rIܟd9H& jI5B'Oݯ#HUCFӒibrHaF1cRkbCЬZzZt"4 Vϭc㧬00/m ,aFZJvRkzl0H+ӨqC%RyD&#'IR,z$I э.8 @d`H0r#B#a2&:GP>5[}AI6B#2PD ],י5ԭa1 ⹀eN%Jn@|8Z5IN=0xI+OC0|SL2 xcD[QGdPyʭ(?#}Xg!7WNj PpCڳ -5I!O _j>y"/ r:1<',_"'f{~,`0Tdr'+ES̸(/%d҈WJIU5++Iq귋Y`u_Iv42|ao'y&Yo`>rh8|=4̈́ӒNi 4#q<"" q,N-;:ĔGî[ Jf>_"Ǒّ]Ԉ{ ,xDJ ZjQEC>L$H5PE|T+Po> *h *&4i82(kPpP΂gDI#aR2'E:b-RZyUX)9xV>v-ĶIDrz"dj%AGFΗv,OY7J{傴{Y%t2,ĈZ"NyiLP=k]Ros֚9WV{˃4%[ʉ\jT*~ȕWߎ>*&g) J-hj]b{Y<nS#xb(4oB<#:M *'d>u|N}7ϴ3 yr<%Zvn-kQiig,kQ`\Fs=0& ֟h~bA!7.g$7}4$3ewT(ep|W|F!S#qes)w5ۆ3"҇4W=ƍf L<ݱX*mU!1Li|S7a;?c9H 8 n4;NrM{ B|H:ї\Fn]~F3 sץw9GnO=g65JC͐~לu ]斯mU3?!kS78tG}0=?4jZ;bΪ2'J5F[&;3#eW{ԍf 51t-hVULLg6?eHU7 Ïߥo0v3)WS~&N)Ja<ߚ>50;{'Ow H&UnjDWrJjVxOhnׯ ?^Nb9_,_}l.CGW7rG_y\% Z,gɾ5u^!^9r"5)55XJYd|]S7a]\attf#UUfbl_RRxcPLJw!8tNd5\*÷fbYTL6}qq#8s9VQ<,i?TGOH֎9~G]3bǯ8 N%3+h]tګ3_ndzϚa|d:O%PQQF _̙htO}eXZyfF(}`]"\HIQ(4KBIؑasg#vW.e);v%,Da zS>_$(y=tV\j!dB&QVS" gա>e7\;boiOt&Hڳ+9VGs!b\9<@t@"z?ai:\ cJBˬ2Jc"V U,XQbf*b9v0EFDCԎP8]`x0G${Y>)&ifbdi=!<8KLq6Ij녊4X q"#qAbMSc /k-VGB?FQc?uhBJgȓۗJcAOlKTokg ?etw`.Q`y˷4rLޜ(|`E8( "(T-iA`W/ͯ|GC\*W3G99>aeZ[[~G|Ă#C /޳ͯ,Z^h=Pt}"dxܮ+`0,E ~>T@5`ժU@y#O BYqT#'B|J 00XhI5ՇV%pʰx6tEX.ȣ/FM;!t ˧錖Ew>> en)wN&;gz=[YB6-EoYpJAW9ڂݘϔ,\1YdM ~W׌_@-*V~@D"~8OUb9sr 3CPl yjE$Z{^< *`K&I;+J:n~k</GrȽ^8b#+xJgW#) wfni8qJ9C}7GpUo˓\O%WMJ4s3unJ${vmmW$2DQ9允ۼwb)TM?)~[H<< N)Io@4lw '1OyH[׳vT2q/ (b X"iUx.i)|)#0`^0Ú.Z PvPBXu{\Izhep0r}>_ SF=-՗~mHHea~<~k"4|ڠev7j>wki Ok x!jBVE8ӲW/dY" yZ_D2p$*uΤK,MK,}9f5MӮ,_|:=ukb tAi:=ahp{Hm-l a}0g#}S|^byqTCƮYsn|xRS%OXZjMra053k單,aפϜpq3Ҷh͢5^ZE=ߖ).FmmPtÿY('8ޯ^[5w&6 7Y%U@*p@1i=,#ԻVv̉z9N2\`QбeWm/N-^l 75ا$?/\Wc:h zA5P ǟxފKc0B.8`f1>Qn8 ڒoB~>:*Pt& oA3zu RwYQ߃ *RS8˹c.~|hKd^Sռ28#kUc}TFJͨ(*a@' 5{a}[t S{yQ_?}RpEe k5^BFzuqm5n룎DžgzCWQ%bzpGTR΍U~ޱ!6dSӻ1 .}5q΁\?D8LQ ˖c_!Mh\hk] ~bQH&S)6zqA< Ocg(D CZSc%wWі[nVewyƚ^3ǔl Zd؃AjLFh/]>WJ#OZww tYyN`%;>1Չu \2`4o :ʷ/Υ0[ z>]J֌"i _V沭f+wU @e(̞n­n;†idܬX>‡3HhB,o4ZD"k'DQC%CXɜl_&%BkEFn҂C.s=.Ou}s׭t _Y|W@OX~ǑtG׀]G.Ӊm>5:3 Kʯ2snX6-l( z{>[]!] 7u<fv?XdyK΋Ukd20{(-p/8䨓\q60X^Hvǯ)Ζ}UY={eb5U^;nok~8t+`\m-u=BXO=f @|՚ %BK$sr 0}PhHr3!6YY[uG "9z2k}#}x0w'q"J@ω[ =};Ħ.8|l'.qDC|a[DqXIJH!=P| B37% (@`sYuBgz4j?'O oI;=U?^ @y+G97{)L^c_p"8 ALm 3'H-9nӍB+DL{}ϻOVSΝ>!O24p۶w]5HP\‘ p _LV" PG$rMzNq{=a.u "!`aT:(7е,sݕܳ5æO x_HYʛdʯΚݱYׂڥU='Om䞗TrΪܼ/(bcvgnZ?:zh7emH kV(9Ր| /eD Ϧ{9B)hߙQ#un3}n1nLk+#Bxs2ܦIg#?r|rP:ptp$hr_YS5J=WʮPoep4Ad.%!A/3tHH zz䐳}N)/ǫ@jT #,|W0^8dC6u'pḴ .SOn+8{J_zvuoyNׂޡRńgLw7of)K6xP18+G_3uĖ;?%{K$ R]IaVaP) LgpU0N4jhLgF;r׸Åt=&U_6;y9.}=1|X;6,HU)ٲD[9GhZ.ȞvJ 9?D]=G$. BCO!R4vSUNtո Zۤo L{nJE !!%V|_ W v%O̍^m̔)/EQz|{W^xL>|dB\#g6S^ BP_c S(m.*# 8 MH %n EcV=*T]@kۦOxpZΪ>p&Vu2R}*WlGf+ፍ`pPe)9ʨ.<g_dj%W)iPީҴ^ z&D2*1+=fAQ nϫ%HԸ~c4m^UUҙMg1:AopIGwԌVcNZd⩦ilnMJNM˝^{+,r-Gg˕n]K¡ 6XqX8,9oxR݇e6 Ą4rHغLp ^[@!% enn7:0gS j\Crqqm(l윮?<L{(ؕY^Y]UY wxwG]OdOKcB)UdPb*ckl0Hr|))SE( $gg= 倷-Pb0*` xً6`M3'+1 xFRm;mx-$Y iT/]MQKO .(o̪2 V!CvIGHPkjpϔ(0 @Q+1w=*m:J R"k;H>\oz1~h\Ox(g1ʜSs%۬&sTWOѶ&5xktO|ջ쏬y~~P!*E$!GNک[ !I\25zkiEQZpff[- z?ї5Ϋ-h|LFFDy U-i{IHLc3Jݖ}l]Ƥ1I|j]ZnQVaY/\= H֓%c >rE`\\aj-%ǫW%K,C%*'L.h3s$ؤ۠z7"gP%r,SiR5$fWN(Y4m%_GSVPޘT ! ˑߩb3*)JϞbVsgS==MY"LA;)m -k)Sc;%fFM$B(1bd<=W`:C#r'g]&n+LMMM 6&{%;unHX|І?񌩻d.t{P85OZ&i)~ong2d?ɞ;6'U;I'bs?2-aw}`wSrjgBgEL1?a럗ұuyReFC!|\S㜒5H)T-!ߡjX37 _Q0=3To~42\G|tun 6鰩ii(:~ z-ҫ0 aā NL2T4ii+H֑&9 .yH>֙k|*D]G"50Tp—(=6kmш$͋iry~_hG=x6>dǨh+F5jn/tnu/}s+RJ&fg_#;]wɾmO2*<T:=}wO"sl.0b wߎUeMȮozx(۬}ў 5 A>@Gh+Zl{GVі9էgϕ:i@58G3$I8'h\=7? Lq?)`퀡`K.TUܪ]̲F~JEuF]Y%X&0s@Lʘu9ϷMX;Վ M߽jWl/&=U 4!6 mʾVu--nǭ>Y:/:ѹR2kѷ:hD0嵑 dM3.1,1dn^l{5BcOeWG_uNhG 9$r0 =#oDv=IFhÈ6~d{4:?:XovY5$4͡.ˬR[kh-s)Sq[|U^ѰN;8f,L9^>u<0wy91~u+VS֠9}{C)յY,򨣋_йa`m 6mt%=h IU%tMjT37%PUif_юΏ>Ge K2dQb)Kk3WnnWM niR8ҿ93?4IOpNݹxd("{h7%XsXYgؕ8p׽/XpØ̭X@Kd09ղHtv @$]T?±>!382VtgXqKKHeZ P.jFf)' ZUqw\f:S>WrI=t&uS~_9Y7G]0/͛hwXQfIYLJ.GV3W#Q3v]ƒ_)dP8{Y}mm{)[~Z~.-بq~NK~Ʀp܆X%@b1<ûĮ9yQ?:?pIAc.m40jSذLRxqPy{|ҕO4k:}xd:FH"S[5jChGV %#flP&o9-]&A`_'}yCr݌>G1e5&LB2,`̗2@5I:SWWw}yc0 fv/-O,>ڶ#7f͍%LM2,͎Pst:D5mV$EV։9zNBIJUв0\p=Z-pѲj# uL]hv-T N V.iiD?>/W`,ж$G3 CԹq )Z;Dy NG'GGDZE#{)@QK4gܺ:an >[A8n. 6LPCt6C\h@i:҆zS׭/y'6,'K( LжAM["U2'g E;\-56\".B=ցrYܶy>b@fR-9`rEבʰ k+` eyBABmSؚ;ZKaa 6?E8atL(ȴt &0ZQ#o5;Fk {]Z6Ƨ2#kPګN{$ p/XNT:m$ekŞ乵55-2UEóIp,5}چ,ҙpĈ`h !" 3g)jT':\A J|g)=F DD9`+> mFgB <#R 0֠?|S]s)uO_5&'QD*S= 75 SsYK 76g׋K}g1Ij ?7ۥ+s=9{ Jwd~c韧z䑢E\{n2/v2v6K Xy,Q[432}t2U3< -J#s0 Ϊd .$,7[ v߾S !֭=麅Oa6-x6<):;im`yjb>A߮?=a2adJO.Nok뵿2K_P5x+в>Ub=&8Aiڙ/BjM A(BShn졪qԋa=%;y/Kڨ3 & (jԄw9kfJ :|b낒S PeDR@v V'vI޹Ѵ;qWRr)o2!~W#rʤ*zS{sFSz,clT GHl m'00gVnmk#HѝeL<,1n YVPUR[&l6', ?[cfMT,+.E4TwYIGJ7IWFip*č88 kI.6)\L*ϦʍcKK]1FQ $3lbh;1k͚Ge5mmD'K-Oz֔05vG8I⧪.[e9_K>6N&3f[U!wl) KІ(*OʲVrk)FS8b6c+1^Y5/λcCS.27ՏpEk_"~A~pٛ)Y+m53Ix֞?]%bj*LVse&%d9B!_T[hAиUdk)ȶvMg\j%ƩWYjA BsK2A4έMVU\kt+'Y󹏘lש: m<_ߙlm{`M< S<2oR|W}ҿ+(UDT(&j3NjZQ(#cySIS!<$lvAFNW0rfLJу^Q: % @X3Q`ز[!P=sy$ ݜm1򴅺=4N2_f)XEyi}@|\%;YtFx$ 1@EFӖo[cLgóZnZ= 8hz}Vf2`CKX͈5V1)8MiEIy-O{Zfu^{S%3*r$Vz(9·[M$WG,>g[,;°5BQ"- o45 = xXutw+>U^18_Tt[4릚v||@"R1#W(0Q.d xrxQo ;!Hs>b=2NWXD&q!K)r/v7Wy]/[ql_k@57).\ll7ql44QP?0E7^!*bh #Ȱ*,˷<&@_D FE*^aXE؎Kun,Pݖ#.=9i6kb}PIxhZ3Bk8#sSaA]I]9>€SULGيFVZ ᢄQ]&@qGs4v@xӔFYYJ'+-\W\SݮIg:jIM7 bLGKJT-E7t|ڼ|Mi( xHI@"=v2%m[T&ny HtAl X$+80JS`Ǘ#lr 6F€%"+ZMfoTR\laqX6z4i}M–|<~e8pQa[/u/U/_HYZrɸ皑U4n>)Sː4EYgkONM+G3?tXB/VVZQJ%jT&Qzj4*r^wDQVmDmv\T2{M l\aiŴD1(݌'' ͎JmyzZ!䘜c<>1OQ[J0'.Ԏ̻ON+GKMOʄabEG²2R@ONMv de 6`PK8qCVaؑj*)Ŷ;ONo%MR?k (n[?4%Tr+[(qeONmo\>:V_Z *ʋJUF3y^l,ppx'o FVy98=2+,DHsBFQ^\o{ĽAQqhlJ Q}I,^6Glؼ  L#Jmc--+"KMJs4q׾x֢?oj-eĸnH@ٸ< 9?OFG/Ovͺ@Uaq|@1@ &TYWWŜfq!ÖC7 a1/蒦m2Wg萍н5TXӓ$ sXDNc۞}Pwߺ(WI vh*gS4wvbW0#LZ¨`?T6Q|Nsб/I,Q]f7kk|vsisp>m-Ւk>ٝ]wc+X{nUVY~S4#6vUO-ծUUfCx[wܑ/[&]5]OV9o/<Ӕ݌͞\Y_ٝfϗoXٓj{%xvC ,P'@c.НF`(w vL`y#uoj(Q*7v.LlFԃ/<;uG-g=ٝ&nn4Fy;͈SG&v5go<($8vV؃Ny{vfkӼ~,໇63;#q ;x| X~gvj?ˣgwǴ_PIVd9 ZQWvvg7{p]*TƁ[u1Jx <4%{(`Į oj1s_b{PG-N;<?:j6C#t Ryh=i&kA321L~r1F]1FY޹ui?###i&zceԟF3;cdG2y X~f:<ƚ`bg^n <1wOx=Hc#?-+`@s&$\W\1 rK\OH[K 3&=( xS7gu?>3w5m 8ύ^|F3W:G\O$̊He9=?urÖ^s:S[s~t"O%$>Jdo3S7ac9G|ߖNVxtVGvuk2PP[n4z>3^>ʐ#S}`&ջ*\?ʴ})\F7y>5U<˔*NrZ+b?7Qڎ+euhCΦօ4 %,0vs,5X,Z_J{ʩͰ_tGHV@jkl.r?09aF3 wx.$CYi 8qܾeHțMhbzWzU9!;E rݹT*11agGUۦ.N:M ۺhk)ˁ|)wՎ͐pk.r3DLmmEIJz2xMu+;9q.ĥ \ >XzbJCW\V_$1Yr3\* 5g.MA)G~uF3Q܉:s9똛paD')iS7!gseVShkM 2K8ˮdzK\ʚC,V/n@scQM\EH &&ٙ~Gn4v_1sƍr Sϔ"6_ މk}rٴ̉RŶ&KYl|7ԍfhwsˉrI ۿ)LDF)6u3EeNh9mLSS7q޷&{wU&9}4izͭOFɝrC{ d(}4W,Fuyհ3絜̥eiJb)!of+Hg8"+{PSȦܲ1UugL;JJ~^=u񕨓sj )*,oLj_+ш:s Hg,l AR˺cf@)d:)*& U8d8&]9UwSY/2?P TA<=bhrgþRwn6vty-|#cWn.>5~_?Ԯ nΟ] ծ{ƣ^Q(Nn_n12b@Nod:7uUcՅHOa! I(1Hm^0+E!9++șKl9M>rl )`\dKmMҞe\v !řzPr#U,"ȁng&$2\9=Mo'K_1>%2!yWZ,3T"ADCdZW ]gK_km\ߐGG!s,@ feg׮\g1t~sRcDniZyRTFȑ h`lm*VHeʊX]&bƸ@w $2EϪ0 qx"*q{Nl)c4KYw?qDwm1QE/4ɚla6x%KouaZ ķhĩu3^aGj +z0۵/>řhdOU2@ok!dBߐ+KHd .ɲD0ao"ƌ$]1IEʺQƤ>mtQl#BY&F"O@V5DYeOwNGfdWlp6\Re\23YeZǃgML )!{Ŏ,.^YO G6tMŖ"z*ɔTn˿fsW8MьZ4u,hPYZp]lzGU="OHFlxi,S]I^)J餖!Fi|/\r}W|l?9e!Y֖ŗ'i#CJfYAzF;޵9hߣs>Vjzxqf6M&ҿ|`R;ݺ3Ĩ_查NK\]Q#Bf@񌥌OLQ0D줙Rv*S3^aDIѷX-oE5` 9b'7x+"4RČI>N.$UDLz&,_]8EBKbذ.%>2+KLp5  Ix8KD#.\&X+XKWV$|o}gCH\B&QÀq\TtIY*|9|YKz=ij AbeM#LJKeѷ^x xRo9k|`vwdg@rD`A^ 2񂑯 : Y3u߂T<*=֫{])r%DTrv @YYjDvbTK>cj0c{@e2 C 8-,PeYͱbڡm"xy6+i z#α@io^"/3 &I:my,P M3oE#X`}hky%'9z24hUG+8:(^a/.DqG_*PI q "I(w|[(k)M™ g#F YȈz15%K@ic6U|K+#_uS( *Kv-slZfU'0F10i;hGL1@V(|Nr-ս{ tJqE$D rl'6tҿkdjVMVJ6Ӗ⢌Fā PU2kXan7fcGBo>V<'u`ב`Iߋ5%1ڌiwWF{[ƞZ-1Oz ~6?V_Oy88Yofm8ةk;-E KV0ߘD}B-Z Q' -@u> zx:cA$BtݧCna5{\rTBwg?ج-UGQM._p TxٖiHQ.j{JD2E;'(+'Z C< LuHAk[hub33y^5c Zl/ ^x3NI~)()&{F A^K6A"P&U+T/0h{sn3_nwAKBс*7xPHp GB522/`5D^v|-R6@|QSbc/:#iRH~Ds]IiXd[mEѷFc awF|6QLzxil,M၏ xlo aaFDE<+GΜ2blZӗ\pP|(?@] 4.x]p4x]+b ҿC -I.ݸ^[CIV򉰰=x5gNgob' Qz-pw\ZrAa޽Bwfvp=m]/4_EjAw懰%X1EE S:0qNZA x"5o}߽8<*SzVza\°)]Iζ%d3 gϮyGÚ ׈ _m! SOOaE-$9b+,zq_| 9ol.{)D|C"f! 1$X]b r\g<}LG5*%vٲ37,0.GZ0X+N);[1YP$D5 xG}`f7\]ݴxJdKflӖ106nyU{:2s\6jJj/Qx\i;Ԃ.cm6mꆫe6&){tMX1TUN-Qo:Y[i@lS=JϷto*P8>yWK'>etO9ML5CQY- ! 7,dQK0?ٮ]f[] `sCŠ/pwFܹM}}bҜ63tSa5Buq)[䨡n~ϚTNW~Olkx 5kc?73\pruCos` Džpkj#tEOyyMYU$P]0~jR#6ߩļAB:xq6:+\gCIJQYzvN!{@U/B`1 / 矁b/xs^ ʳ&جߥ/Ӟ$$5I3N[L bD-yW-փOX<ݿiڊ|je]/|~WW*N yG}F^w}vh;oVw(:3*өT{[C4I:G@ǫ_6S%޸ٝbF)ITB4p4f `ΜڮzsV jE kfO^=)ڦJAaY-G-,j+_;jݲ*-R<\M8:)/9x^h&E8J'=̔'Dxנziv׫.HQFЄEkW-k # 9]&mZdxM=&B$_S_֞ _Έj]^rFWR/(%%s5gʂū`e:?LTY3^ÊzCF1u{ t OX,oHVb+i {PEؤ#fsL d[ Q)UJzG>XYDV&iT|7(@=H~ċ2|OU.|vի8]TS',hF,"yWE&(xlkR-J;tAKu|y~Z9԰"CdAOy~MWTLC2ɤ#%Mg4-^T.g6B`Xu@U' lGNmj?)C*ws,[Vظ˅ W}8hsǟ#4iߊs@BꉅL,X{ߺT+, ;!r梜xw괌͜Uo_84&μF( E@yO첪/RW%G,UT؆e-ܷ/REeUʜ1VSqZ^0K"׉KwAG%+:j}O$M&tfB@ڦu{r㪞/55u^)r[EO(c;h,!N,|͹a_Uaa`R=c6;y{xr:eU[dm-|^˵;c5T agصbڜWFw1j7C3ӰRt_MQZulQ_Ͽ4HG4ܫI5#ϢC6x%Vb(d4sԢ( i˽bR GA HL 4Į]1&"h/fB`{jM>)r՝UNv&{tGK LHBZc.&#m/ 9s^2,GRiR'g\i5Uu}RΥRs9Bi5iqsA[z}%}u$O̰N4KA_Py} Cyݟ8A,M &9H%7z#Ub/V'pSfug)AUҊg[ v..mDHMOYq+a~'4՛氣?1uRbyk(QfL]7]k}ÜYqS@" ͬM\Wt FNf4i97m=W_WP(鄪UCHNv=eu̦g{cTRp@sJT`4qoNZ#9t #x@;ٻY[}9C.\Ax<6i?T}bVӨG^3zEuf "fɏ:%Ʌ l 5f-^ 唟1i.D^%jREiM2"kx ӱ=0>>Nc\==oh[}8k&vîJ=֯+bۧyvF3?((E|9iVjL92=R*hRKyD#bVjڭ~i{fL] yUUrFFo .ol2A# svH/*Tнï9S_;YROrnP2x[d(`u-bc[Azc+ jdN}+%) Ei t+xs#bs~Č=_3#:v N7f l^옱촩+>oV 3$w?p%!$(#Ư^d74Ok7 9k_䇔նql]=hh(\;uJ}'.lcwخ|QB~ddʜN5F:'}Y<͊+a[tGEQx&l,T8|FcȼMs'3~5yhkm: OFL+Z@7`+^l}`o8XYG:s V*t.8w?[U.ȴYQhrXu樋nxH[t%[=G;0vЊÛo8suڿx nP!-GpQt t.l]xRqw'dL[B:POm7*StvϪUA.8XL}-{6=E/ m^#"Mߨ7[ ^[_%G[F~V8 t X ~ܝ syg "Uxg9%&(P$7R%v_bx~Ԝ[ڿ3=a}y3̈Yr~Ĉ ]~Qv _0ʆ̝{=iT-QRO[ :/t?^ K`CsAFz ŭ *m \`hBc }P =eN1J=E(u>?wk;6DK%84+aaG\ykOmqZRe::Q"1@V>@R,lRCJᴢ Zc^_-:'i,$6rL^ecb>ֶqsK%f0L0yEF5I0'rbGIx2{:>H{ˢbJvA+^I'=1dibADR*b *f`ʟ2z8 ER@j+ wb`YžT/G9SݚPXSڜB(̄TPTFC$Gaʾw]0o{-/4Ōh`~BdHɫTFkG8\bMbFh9_po,(g[D/CD=Vo:рB 9 3Λl!,'s$~ޙ$C-n͝~3ɍal{؛ ;ǾE/;A: IL$Q5sXq5’IkzYkt)(f;g:7tǹ '-lUg]%& R2f(A 6ρ_+L=ć{ C8nLZjTaFՔz*em m o輡 >~iӨJy(@SJV&+3g4=~/r/H SSLW;3Zx?p[#%r1* N{: CTiv}%yS_35ď[㑢Kiy^*XC Fc|yt/Y6QI^:C"V[4^g *1dpiex9u .DAq|}_muX z ɺΒR_;CZm1gI/6mN.4ug.eG͠~3Y-&w Tǯ!H}fec/؝;W0wR& I2IkŶ/SJ,TTk-?7(x#F?yM%;@? Aoܬ-#&@WutE01M6_o9C^ M_֕~{5f2*ux ɭgmA=-faEIE װɜ+UEuc\0]:qpwҮ^1mwjqqޤͪiE#chқ<'Ot@u2{W8յLyŵnA><눧g! w *,#Tj.3`K)^ҿt^ܙU#v&DJpFp :Zׄ6:vi/N^ao>ӐDL q7iULEd]E?PskVԎnzI:iԠ@4w,K哦߹Gwk %ǎ1c;zd2dX DnDYY`/^w9t 5Y V[҂o |j>~oC)*}鈧ozT&=:X UHI33JXxRș zȴ)S[&HIA%$ELk6#742f''#fJ] Q2FVqa''C}L/^KfcpDADERbľم{SM%0izUwY]3z':W Smuh !|n6oal%㨣Y ЩIs9EB42fqJptYe{̌[i& !98L0m gs'cbЌ875ʜj\V>lـj!&+Ix[s+2V$yBԱdJeib=ɳ Ri2I6%s'/EIS[)5R9CpRɍ;%& %`lvPvXqm&J%Q_(ap02 >9"P if'z`GELG2Ihm}T@]¨~ߪBQ*mgMQpU@:mYPvI^~?IE{_pI1@5D4w&U/UXl }(5mhXfވ$(fc iu h`sư7/B}VP۟wt踎5*+:V <./U;Q6]a16*Xv,|[b1DLv^ͺu-ˊ(#[ÓЅlo$+T꼒&*Qk%.mo.۔{ g÷]$PwRܠ8 ^ČxI0{yQ"S<==sQCvF $Uoό`=쟗m'u~4^uPWW[Ed>qwґp4Re 9'}o njI=QrJhÓlT1 gj;@ c,jW״8z-9p9LTKv 䌶ާ{%6#;㗥=DZObE5@Xe+ z3:TB0PrZ[[CŠ|zҷ&ط,T2v:pto H_sͽFoӌ@h:”j(9#֊OـEyw ='f{7FHN"TҳGy'Yu7̴B:#dn\rxՀlp? v+>=W`J(sQtG!^#R\ !F"Cp&bj\~!e8R2c;1xDft/`6]sr=yF#EY0>'W(PO.|lH D̼l%&`1\zaPۅVTK%)<9uJPl&Α2CdJjSfCAh8a2]s>8\aG[6qo5($8m3͝ߙ M5ς f(|c⍔L9j 8|5)2*QjhJJm O̹lʗ>W+jrgKn9T tj(H/PIO$3eۘtl`QI4ܜCa$K)ӄ5]tt3wb;cE)_1³5'}_$yzc5SMp<#d+[ 5Dԕ13ͫn5?ҋUPr&OS)Ѡ}lȘyO^uJzi0L^Iǯ)̓eb3IǫpY/u'ofs(qDlնurtx&\-s& ސ %^ `pw=Zt;/JVmӾyEJw>䀿'|vՐAU|9=bGP}d3\`ZkZM{8>kƬ>!8s7ix%U154=c6_M< ΑIDPBxm-iLQW|E4/ GztUc zg8Dzj—S&1In!Pݠˎ*j9֚\ wmndf fwUS1hDS\^5#U9{2i0#qv$-Ar(#y=܇h`;L;,w~ Q;1>/lrq*)9ҙ4o2:B!/]B9":Q?4r!LxM_"mAD4g4ydTMYX _5ǣYʥE;N֪kYOɚzXp#PpQ=سuyԭx ӻ ߑi|gslpo uUƲ?} C* QJJ(^;`/[wkt[yC{meoOxՒ"-/b# ɪOQE^U:<%d$z}SfŊxO>b׿sOc/ mvaj<>{k >S2\ۄz9fV yD[#FY-PFʤ(U_ ̵^ḳr Q]c#a6fv(YGHCN`ǩWx1Yvqh7(L5l9AA?lȚ+ ,0$3L뢙nQA\Pdfx0 w+*z%' 5'̌x)f;JS"JoCVK5M@.\k0?墂o4(VjFf*Κ,x9<䖥zGޖ-q(͛ί;@ sppY@_@K[ \2_q'xr|t ӥ aKC!ۿS{fT;G>n apj^L՜YᡚJ]κiګ3AXr/!hrʰjGcV|2vf,)/{932" ñ}iw*SsVjd鬳Uk4"#X}_0U=Xvdc+dSmj(QEpy5( 6ڕA("-XLN,xs `lfUWD0&-:Zc۱5@5sZJeā{Fq9U <>%7Uܣs_MRߵ%Eb ZĿ_3n;&;UyS ;QKWe0wJZBvr@[zLcvݟBW"2q?^%^n:wSWiW<\gވ/PVcT]Cl_p.N ρb6[:VVS w漶5+h4 "̚h8aV_Xw!v "_HQ(ʌY*G|ƩﳋS SK eQ]/yjt p@נB Hi8NWN'Kb{ap "X?rTsʼy{2ibb6-6ըVY4*)bhj3&h9ޢ1l[E6*fV8"JB K3F7^Q_^T )$ .5;%$J?|fx2Q:ҌBmBm"14?f"@`>%bQDȉ0S2DBd11B|yj?c.<唰䪎xzGvv4?|Os_V3V_y "RRڳ GthƝQΓX• Fǣά1p, j8ʛ y>Q 5{N?ng_9hDq{q70e(-H  U%|lebƱW^Qvk[kj™jMl6:ىcXL67܋%B 8pdry3ը2Z?ڠt"VcP+:$ jכ*d2cX+)Bm ixT'ab2B-,ʍnk֟4chyG=deYB#ѯ[@+ڊ52nTOD#[8l ;5 m){hT][e*)gF8JcƲfLmLy v$]Dtءsk[U1N[qի,Մk`͚Pf81Zlfs[qcN&rt]/P^]%7gSI]ndU-k3HѭH%SN;._7 (lL o`Q/ wX\Bhp3iX [D:3 ɔ='ac>dd[Y5ܬyTk#CL|"{֔pmx_watU}W$oP):*ǍFz21i%Ы2eQ2MN KԊϥ!9-f5__lmt%. dK,+$ W7V(xKA^"EP n&gŭAq,a["e/ ݜX0̩AVK<#`8c!0DI] !HReJi-JYڨ'"fV{% , YK߭S2^S+`̗82290ndH9U!L"E}L LRUUpq#O*Ue1~"AhFl?-iȍxVOecLBL<{''ـ,DsH0"TtN\Xja/ʜ8,-^N) o1`EϘ"[S87).HYQ߹o4U@PH8Lj*KN3ۛшڭpWDDQQG{f/OS I^]jz4b]x`XHŠX5Nm^xѧ_ʩ&Ãkb *lp V($6.ť󹇻˶ŒEdG>[ssU~H׈H9.Ho4=wPhI-f"֖v3]\"X~6o7NN&À %GIy)VLi *Kn.Qw{@4wY x$WW ~\#|I3xMhE˯( }+8YWG\%\. -n>ɼr &#^1WEOGKJT[E%d9bKINӑi|,3$MKL^Yr6%s) !JsJq2oؓ&} P=[?@4"HuĴKJ247` ,sO:)+-F~PHXH:0QXI)-J,#R^k;#;ʼn"+V !oiM8xdf aP9`eMZ&D8tnk%.=9,2T8#d,I(EeP.ͳON5&J<h_a5lG)\673Y2W~ p n4l{d0qh, Q<+G6ו-ѹnל{rlMJM'xRV"L#Qnŭ('z0%V!2Ԏ?iEI+Rki''zjiR繭BYꪚ[d #{rl2{ڈ +hj hdN2Zeֱ''M а#hqMiI2JBw@`aT9*-$GM8BB  g47Q|1ol[8C'vncs_.hftu\"0#VY&JsT؂Y\<9iv $UIO-%TQ> ,\cW 8?O8AFz۝O]3N ׶F`dW H(\e?+U|)krONM H G\Nlb_0g&W85af%[)UݵF}]f:x?I,>_@l.gwO ,B댱Ze|i3@xnb{k˵ψ¨ܽ݇ :jst_SVPOSn'-jU]اu`-8t5aw;QkV@Ob GyԦX{u/P '7sX~Lיj%ƟS4yw/&|م4zM-vKƟi$LG'vNkVYQh>{^w͕p7 X$#;rR?,.x/ 6*OV']Ok{F?(7v.rt~VG^U#x¨}`ot/,~(n=`pikX{tŚӒK}eb;ǚ=4e6:i֢E]AlN9(&:BͨUrP-1~zla{{ErQll#Şf&y'?U=nzv{h 8<;UNSI4ڈr3ΦcG3GZ(F4ژ͠)""]|τx[Cq6P}e FW߸фb.N3eU,6tǏWtts5TQgD2>{Ƅ2zCs:ueаᕹRRɶBbRg2e?9>{DHZ vc)ԍfw\{~\FW޸ cw:tL2/{xGZ=i 3U8p~VjYeԍf]-4)U['Mtٔp`VʄMqF3 w&tPĜ1[FSZ[K A,Uۿ沩n@B"M\SVҙ=Li|hCS7;y_21M[c*ib"G(eߜkCknȕΦMy(C754&t,Fn,=KeL,3Y4LJ3u}ǯ1-3-ov_>*Wצn4Cxn=!T*rKUm "gⲃ6Jԍf I1YrrͷTV$Ù1?n4v|'{|WeԾ6WDG#{(m%ଉbl=,ĵUp8| J1>ԍf(p"6妳Tw%i+_8,9w`%/eЅѸ8 h5UmE4YPNU Nk8I@$;y8u8n~y9ռhob J혺 ;wڬce!g3$AEK kgyZMqp1y3 ᬣ#_s%RC[e$<"_J^9>/Gn4>:ɷ'{ ۻ*Ƿ 3&8v77mIHUa[&LRe2;>D *=^wѐ.ͳʇ:/u *!2 T"U 8j#agn\xf<=xŐOR3QV(F(4(I7S:eݵaN.x=xE s) 5 evEzT$bn s8pV}N? îi~܊6j O Jdٝt@Wb4 R>l;zүV$9;m7<#=J"p0uw ^]/auf9I-LA Qw:j'/JMxNh`B"|ϼ/-?p ( zd DZ3;J1ekRBs({/eT䠬1ic]`2 4D a/~YR߶E "2aog^Lj;r"|>][9ݏUk8GF"DYW]>=̶qƻ% +f" uݍ9݋.$"IHc'7 } g>]]3leI heqsQ23,фdIT_zE]|*Dc{•5s,3,̑]**Q۝h^f=+ve l]J%ψ}f]nCFѲ<_ ]>f[^f{F5̞i uX,-BhAAִ̔^of^|)g=˸HkJ9YrUcH-QK0>3w㰋ŏrYņkdO{:kwUW5G:<IQ1 PTk,YmA+#`U20az.*4Q@cg9o@0; Vm+ѾT]b"]Bf-~`Ǟmqv2~8_ͿXvf3ltҮ1V҅[y$ys N>vxOX ,JkPR[fVLZM)0ryHGIIJ2Lvx<'ff3暦9LD=Gj;_k?zdI:=pFf'b2sV(tBUx,jIŬM \W ьI>]1RDM _EL ]j خP1v-H ipҽVU;>(B|,0g)AC<# T ULݿiρDnzWXd>a- #/|ٌNswU9U3  rvI06>XP:{pkS!>m8\9 "ed>Im3ƑD(zRRK?b@R 3pI(a*ƕa4Cf @{}>I߲Y=ty8 ; B[ k>N/`ɱ߳bWLťH\FĿt 5]$2ѐȺ"NTz'WjKG*\yE%1غL 1-B)P&ZEQօPnXGU:! "ǔ d+3L q(9- TA3=* LqⲄ553w9Ws;S2+A($# i@D` @="%`df~~<ʄ t&7wt|m@DYoYx |MR^cxGTjEXMGnrKA׵]roiꐳã T`B{aXe+*^?l''HP*(&$,q=H!Rpx8O~jnY̿]xm8+m6ƩL~YRQ B7)]8f5 ߮dl-HnǾ'2q39'T<y qruI:+s:.e~M7%m3mD88@Sx9 Hֻ A Yݑ9b-1BBN|+hg^7?]2k]";NŸ7WД~W{Kx@"X5-V}M> w8/Fo*DpAh  CΚޕ~'şڽ4lo6=$ g)A !M ƚvEI;LÁdd,[B!hXb MRmG E@}R$D .yc7<jl1aCHd۷*Y`i, ƾ!ɑgVEИD&ϒDG"p"U~u4(JSW-9AլKױkz+^8o#.m1aNG"J+tG (FQ'HW N tTdEuv38} YR:c7 9C2±azq¤,g*"">c(ĬT)WN.OE ORO\ -%-t,n6PV֤(F5YGNM JF8#ڵMev O;L2p'9mFݧn6;Y~ס-f>=6ᲈm٧>(HȒ0T\έ^ԅ4PRGTdvpݯ 5KgW\ou_h>o`ܗ>ɉ5%kAʈӾ 3osqGy.7ErOC(S#EԹ`s?xk`*fO YV P *@]{1y3e +gc'280'u=iNOMBy?X+S@1Wx'a=^*9} Ɖٜ'H8xZL" J{]pf~,)pq0kt英shp|I]䘾GE` KmIxɈ݃`wMfŶ̈q(4kx&cz1G!"E . ^1W8*駹nͼa`FN_ڜ}{d9ozMRw“>!b IGT&|2}~`$I{)I+%R52JhV6͌d&1RT*BɐnA[7`7 V;xnXc:b~tt(~U^j7#aԊ0Ȍxxڌ5&G5i.Ba |c!(Lꦴooն`)?cls+GNncڄw9exأȊEL&6J Bp10MK1 /r8EbݛI\pp (8. i&V%_}O!`ȼ5/kPJ>6[&q\NUSG7F:;"V8qf.Lp"> 9YbR;KwޟŠNJ (%H"ɋy(VUuXpvv}ٲ6N]]'s68=i Μd:z>t>XxG?fAn~%e)^O"T&QjqGZ@0M$%X񌉜k: Ub!ay] H;AG$ B2]WJG^l n5PUFLY}GwAD 6EMn&IVBTd y٤rf]fa=[0cp~rƍ`׺] N{YX>#;_Rw8g뒒psN|‹a.,<Yt 6b*n` \JAp~ ͗~;%:]2gNDZ3`2H"3@2 N<^h*xp{{!hW RP(x цaʓO^'uW|[ŭ/^]$8 HB` pb56zbzETB8I~:8Eo ~&QFQy;X*rhQB9݇_N+FWA,ED:Rbs3)H}E\EЊP: FCA@$VmØf_HょKO۪8;y^n2l x: CC5L.BBDɑv -z$m3+j8Mɱ5ɚΥh1Fu(3K"S#bt@UŰB^+.^4t޻s{\Cò 0H(oJ#e$$T .;z #&7Ak[}k^sv}!&;fnzgiI'c-AlEWPl[ŞU(z~%dMwr8.ހEۋ$s^2N"'Ҍk;GQP1<1*cטS&O㒚- {e)ǯk*UB)˜g9y;XWhQIT!k+K Ce12MH=vq3%Yy ɯ8JHR3sJZmD{FN / ky+;E l9h}69bCl>-^fΦ:.4.5h}UVUI!{əO8ǜLT@ҲYHQSKw"B FDz*?R!=;.^@"HnfdS8w:B8qB Y-k}tt^s?2- 9]y̤U%ٜElIL ;}:`%"j8d3Gɢb7 6hV#c{7 _"

G|S?Х2Z:EqW̬&? rn^DLadqC5L{6$$y;@p_N0 x X]W>HUxc#Cj(W=Vv?&3%dR]ťk?IT'8D$֌< ]8$fbRJ%Rke$JGYIxj71J~Ѹ4IrRJ=kH2aFq-BAwwohk ;JAkG;uj((tI#8|]%bWVSJ`AYU_,>8C"M|T kW=wDKVTB;{i!>8xYXHKDV\Cqk{&8)U `(49/Mo6/zVNJY4uiiʲ-~8EЬ Xq[8yJsh!5 -\uǓ~h-&!2%&3*'3VgYCЦjrA8/TIS~'P$vfeڮ9gOseoݒU1 KTg6+zsp'j{kp:uXk':٪tVm<5&,ikIR%e"f.<%< #.XU^Σ o< y-gi<\43L7jA,a5uKυ5&bL4G{Hs^GEKTE_uМ5Yf& 5uyoښM(;[ (h`G|>Ny(X*-zF 3%k0KʛR]$F%Fw>~Qslk: 5~dolX _]%mTDCۑcM1z~~-MM('gĜ_AF@ִ',^Mԣ7*۳UTi?ϓ 5//8ltw.{L Jw>ME⒐$=|B$ʊ'2C2тmBJ׿7չt+W~6Sﻉ55Ey6뿬쁛/ 7^#Sb8dK8Ԯ<"} LMQ2ang5-͔ NS8Smn 5\I![4X:hhhJ۠qVt*钜 - HzJn\D*rW4q*8<Zp-EB˙a{2iSŒ)$$( &{ Yp'La#!x1sW]r|*;wO PD1=4hHK\`,tJ<9]ҵa0eX%ϛcY3R]p혲yJK¹PȦLF.gXM#w:|5"<ov2 qYPRόYɕ`6!lOTrm3S͹]7s9 t^TSG5aB9ӧRwm}^9 Fg%n%"n߁HkK6{kU?ppn1WL Յ(DP˽1o-З5POPG7 $ \cw-M٤H_  22R\ 褷yz"i;rm;Գe|> )G˕ ،ʚQQ;/s973t`mnZ*V+s{nxDŽue5=eZ:ij2@jTN4RIoMf(Hus*e5Ĕ[*HT]B^7oyy?:}""/g$75TRa4Bd<3,E{ K*g2t{vYRHDvO ﱋbƬ5.b;* 6y%?! y 1nkWn`(rfܷݿ|]oJ ^jL8>P-Dp0^^~mV_D(4Bf Jx> ñ޼)\nGxr:-l#pځÿҐ1-°76]m ̔"HqU=֝eVvR'b4Mb.fOQhR'Od 'z5 ejGL%A`TيiYT(_e]BȄ&:|/)1'$UמpHcSs}I V!}K" u[ӥy )F%mʝQDQ9ɬΤ$3^@mm2fnx/yLg|p5*[1{uTѫOZ}>yY../焣q~Qc'>tTQր$Aed^C=aϣEEa|=-Ɓ OmS6 6zCd1xp_8>*)5*v,YK+`SdK, +P)&d>na' S!j &1H*lFlؒcV#k8TԮ/m+8.h#lԥ=h{Kd1ߎ*'&L=X缳jԱpZN9/P}-$6:(Kk$VxU-e ˻5nv⸪tp3!QGa;wtbZSK2}FIc\Jm^,h=ɸ^mqyi x+F:7JWX@=w%zKݦ=42/=uNgaC@մmm8iݵ/!7՜Y3rdRF"s|tg|sԥ#^SZ9 `s&̇S)Pd?xFT5W*)M<'hҙCG& AIwEo*|%uŚn{*Sq[LDfll-,,i{Om1w ?!HM@Ҡx@… pi7vF1~5AJ~kh@Z]{s{EEIW8AmQ.eg# `#lI1jlC?3)mr*geY.:7}lLCdά,U*`ren֑k[渺2(8k! iynX K@e4 ]6={^)+\ڠꒁ@$3Zg)'0:KF*@DŽ_kJȏKGWAqK+~U 49w)]}\ͥ:ǹއD,Bz9T"z8;W-gxY_Y˧<楝=Jj-Rq™Oa.qkO(ֻ; x`j48 hrY(ЕrxekmL^-nq̴x܀pq"d3swv^9>k󱹑tu!Dfİ cXwwpdrwQ(ü CEUڗ^^4~]ap%X0Ő*eߩNOԃ.%@?5rNz֝L;JZW#" 6-8k@M!!+Or2`~$pF@I҈ssA-ϭ;1^*;&m3`<8ҶNB~S%~BUp8㡤hH@b[Zj,rRgWLu}ND~5$"q0u: qBzJk}1%3a/i=qKd#mt8 D;'zzEd$U**RGe2J~ k?"IAvY5T1_ >oQ-Kr/ Cx*u8mDp̿pP:}XU1ZI1mRĖٶ*X3ePV`DLV(3G!g^2TȘr.x`_"p8F*lCV> I{ jj+,xݮy=Ia}޺1~9-u߫ Oׯ#!b@ mE9i9PzG'xV{7'QkNc,k`G?c-ᆁᛣ\U:$l_0Bw=xS%2BU7ږ̙kVdmt(z35%74-111;'3|%p;=8Ot:,b—l&/1m׵&o~Jғ: oiMSrvoa7CqP508>1 OgGA1}H6>P wu}AhzmJƛRfjn:ΒCB5h~?U L{4ƃఀvDAߌUP I Nw54h mISu`¤ C#OtqԬ(Gfi4P Rus^w!BL&3a\ 2R{Rˎ gyvort>c%o9nMyQp+}cGF6V{%Iet^CyH#;+8.|$J=j&Y9rl|Rqr ;+s˨(3j`d q '@Q濡-tr?bT>Kouo_v"g5HWBI|p&nGWX稁z`wfSU|_afӷs_i_6Z,=P}OUt9u=\ĐQ(IP;?8qXL1pŻ0U#⊥J:*/]`g}ma`B_E t&&?#9VQgxZyG祥VaQy,#l+USRaF=3sgīx1Mu益tM&2FoYJy_] 0dQG(1 ȑ+KPpl6d<׼u#’eH3zRCP'µuS`K|\qfoQCM G?ɟm#0}DfϬwԵ ϣ,U ]]Y:@j15Z⟷Qql+^jY wy t13(GG}O *ޠC5Z:0gj@XڳML^Qu|Pw|4f%"EE6*.xCǛڳL;M{=73׌RJU,p0qϫI}W"TsMm "C* {ZJlqO Mى8HKDIbCV-N2TEѣʵVQi#LCZ~[W (;x(nR*Zd+hJNX@b\m 7M)mVƂ nG.~WlfjN.%g}CV@uح,B6$MDR"1xϥJQSTϯ5FS<``>A) k18VI>bqCZ[ELB uH*[p,s:eН(T8D*BF_QkKƠtBY#zk@AB3GN}^Ls]n4nT[~KbʵhTU]WfUb^E5(z*2YTvZv:^l&œ ι@ν*"AmLVUlm!m T@(2cyOP Zb vɫWB+i-U5Lܘt1UJ01,ϵb8O{tmTIVj@Ah L07j7T۟ZN&4epC@ ԡ1_I$cI r۹e*co'7ғIVb^YŁHHyꢿC}lT͓3ZIʪ@Fل^b\+;cWi$L/ͩ+YDgZT#O& *Dz$JWRirn|\.G,}I3x&@uэf-i:3$Eaw$JV9W/PmeI&mKt[Vm9z2Ձ1OQ}rӝ''mPlp-@v_z쮥0{گ=6c o-„)sej) 9a]3˼!<9iXn4By@e7@IP"$R1v\ykr?-s(z` yt}T`$422/MM4{PG- >3pkF:L\y o47n}MTgz8~XU;pk9()| {!c9S#J *_f'BJ8"FʪFWE*uEeC{&@~NG#׉DFY4fR~ Ҁÿ{͊rh3(0dzh5SG)Xcմ=;{/bjsdn{[&v3lgDaŵxO %]T/#Fe˭Mhzșdj#r,厦'+wYݮv&09&75[e7]a]ӻjqh:bWI@1mux>=xb*qy(HحLRnn)[cIlԔQg^4ӲeT&h+sB ^ms&d#K5X#̬ɑLK\ (Seж''G{%#N€w>ʈ8$?"9![e6olj'xyW3~( ǙBZTVgEo4=oy@8> 窊SXcNhK+sȢdYs''M5&@('ZZ4Xy/&u>75ժZ|?:{;&t5ԉlq}mXlavRLN쾧֞g |v9n(Ĩ{:+.4SpTJKGwG>tl 5ꈵoifQ Rt/K?`#id ~`7P`j7y5Tk=<|ٝf!<jPOj926Xb=V=x)nvǿ|w9p <~|'/Bs˦]&69XR~nPnZ3$F'1'O5PYOAF=KGyiM:FZNv]|[{Z%Zi.0kkBYЌђRh@>ư+/jOb7#;۶df@jr3ڽXgwTLک(#Wzt0')`=P._v[wi}}?3TߴzrW1+@.<s^1[bTΨZKl0{xF9{1&脵}7?Cx]pʎCnYYjfbxfRLhn=.y[VrK{] A8c~9hŅ7n4ðp6:fX@%k{HΝa)UO-=>uw ;0i[.)ںDSLt8^ۿgn4;u6O=F(hxWam=3}u.F&Qˍ&z^[-Mgg6iUȍGS7Leucg9#e.|W#τҗ8ze߸fS295-U|OeȦvE(eЛԍfWIo䘛~|OpfR~H8Wiq޻j9>}#i钋m-pf|jT gĮAliA;dX)^Cge%W;n4C厾<>Or:R }#\+.h+d T]5ũ[2y)GFsj5hb *)_SI;M9UjRր9[X[/C=ީ+#dGG8=Kz?Jt%zRt53S7('FsC= { ~\tf;/[if[#$K/ej|CqF==I ; Rɻ K2Rd5{k:gԨ;WR2}sF3-ltPVX\n8o;Sb{| Yiӑ]dxr4@jj])XI8FF_ئ.fw?89?ԠJR#Gwu;3f;^Vg=˸RQL)#3=@RNbGy2(T#L@ 7z.^ fJj OSXf7U;,VC(Ni5 FF-Qa5t=8fYf .v3V˞sb=fA2b͞:yfZ¦SGO;4KU{Nxe5X_26E [FȂ.$ÄEG8_;]luK5 J7=MЋHHݮVc^mD, |߉ʏѬ҈=ji {pĆgJ|ˬP_jEͤ)DZ*"$A(IJa%sƊ i#miC{3j>1kdj邩Ht"%$n$ ;}sOa2?L15ѻ GycwNUX$Q#Pb+l\Bq p 6-xDO e%> "`,rJSxLxvS)dǾd7bzMAߐfǏ/տMC_ӭ`fvx/ Mv+GHSkkc.!.Йz7豉C 5Y7`Lߤ*BE  Hr`N ̳*+| @a bQ .P*Z7|56cU`N.P!԰dPmOxo[p U8A|{qghr=::F@Z+XrLk 44AXp, , Ms\_^ b v@e?"# 6K^8]ۚSPG32RyW R:Moʶ*&f}oOwӒqigZMA3?=VťQma 9aQthoh HâU&!Qwq c ;Z&$@,g,@0 Q @%xURajpJN0EXlۿ3u ?TGi ~:h|%v@:D&anqW[P(JWF"¿")DD'L]UN<KdyfX-xq04  2Q6IP`%/J$;#gD72} RY"ǎ첧NLr}y[Uc\G?͆Bi^cͳxˆ0<VyxEa,>UStko|Nb[0$*aU*ƙ4N.HJLݣMW'ys4u*@bҧOL5 VӋB w9'oUk+^X vDh#"ēش OIUDb舆]%"/xR< ӶP V% aHokX8ebB3[V0Qb; x AF%|NU}35,u*Nf luw3;_f#7߀'jFܾMrX۸lG<ʟv!#M&!=aZk gj' i7c¸9TaЂ:vi9mD_5%fZA[Sl&&2j!)@ xMHtx~Qi/vdq8iV&C˱DYٓĜ%Ms O`wł;P{&q9(DA򌈈gv) .&Z $-uGy@o{ϰTds <^mXW9J38-8THG %8};$ 7`;F]MhSs9< N GGA3z3J(Ҕ"Ȼ8Zc7$Y6t='3:ϐ/Y;i7)*(ޙW!8\9mD "A[Zգ4k3Xʈ  3H0eX j<4qR`ZDXnile/u%OUǏF#Y Y$**/` ƻT~tﷶ`;FfN~߇N]uQT DHDDE_7Mj'_.( H̙vsBj]sѓ_>;T86ӗLߓy-`jv@VI8'j?r2'avl_Xo)v4$I(ANQ>t7*mW4$^W's|`CJaʱ1CLiG C{8^ C jM ,p<"4# ^;dpˮ|W 3ԍ+1\ pGp8> r-Jɸ6T?vŞkBckx2oàR˔ZQQ9ʚiq?`d!3  /-ea;V.m%)SA ^;rþܣ>c zMF M_>D70 tRŀQ:֗PA yP4rB|)H0rEc`45 oۤCB-[uȱQph]l!/kl!׿4uBcFf'T;ܷe#+.be.X:MjW75 - XS~H F`a'5vӞ{[:GLq{NS-W?ꚏƍW՛i53/3se-P>RյW&[6aږP@چV2PyDߣKߡ#<#Ut!D(OIy2v=K-xڱ7"gB肢h6ILّ lub2mU*2XqR…mN|ɗú9 7]z[E=Q#bg!1gg!>j@ ja6Tʸ7PdݗD)7A%}mвl:NR |ƴg:ŕ!彪/))<4Za\+7K4'9fP $*E:1 4j <&nvG]JL!83cZ,g8WMWؿ> Zv²eⶆ6ufe{T7# \u5.lµ DD l,mkhZcrMk=8)Loj[/3 g ɋ SPNph `Imx}w5iԜX23R'sW@\doIdEe%ziZ'~c:|g}_-畓^PR2HZ،7]9X@s0ڽ[`$/ .tTKʥ5K;`AF|T }:ڮ50t7컥RӖԢG<ሻɛ g׷!~~[sZs_q woqiJt@iXU3}'v<B09bp_f'ܦLn|}ۙ]jOԱsC-u7LBaszs Eͨϡ u~`O)MXWqm V`8Ռ 2C pސQ5DͅY*r2ɷ]9)sJta~ Q_{ N9;]0쩯K .{4\օyIϚJk䦢b* #4O  h U6 $*YA_IƊ2MrMP U'6dC `[e?+nxD{@qۂkIe6l '*:ioAsrd@þSn*?㰑. $#n䈙9edx6mM.&s'e|H ydΘP'SHAHQ )&̬;҉H=z"I钚.R}g]%KT'_6x4Ep$ w.I6& !vj4"y͇La=yޡa$bt:nAڒԩF`嚊pdGjqzE=)5YZ걊xhS(!qHm5+ѥ|c@&ǩ%9pb`d1 bj ,ܻZ{DSݚ$KKJVs%^xO`+$,\ mMFpqQg`-:bv g7eȊT> wnopƑ0֦ߺQ&X>ې6iڥ㗃FG@Y1/F[_\'ZK&~MG 6jބJԅ*$| &UBBWH;& @9"G%^Jvu`o.>Zܚ9#~wiOw$~sm5 肥1˜?{[*xV;!JdW{.#Ŭb),=ODURdVb`Fu={ )9 [ouDU_ )ŒbK,gp衕0{__dN-rn˶e#jרR3'T1dt 9gt:t%pN vxXW]K %I%ӛ훿GUeyю_ U`!y",C16u1&,vBj՘'jm}R:,Z4I_Hँ_XeSm9F TTꃑNطBxZYE v7 wy@_ec[zJkW[q"+X\23hpv]E#} r鞯v(7S8FDUj1eբ Hd' ۭFA'=e/zlx_e Nry9az Pm|uD( dr'g:\0vCb2'XU,DΜ3 <=#.=I!ozM|bL,+OA,NLM.OS8N,#!Ku2 |a㗝2ѮSWB6իm+٧n GB<5߁|2/< 0y?Ņİ Ǟ>"#,N?-oPvnvBJE\b{E=GO;>=+V=G?dn}ʬAiR R߹m.]!MssM6B6J*~zaLOu jQO9]uqԶ>Ue<NJ' ͕)xh~N O2<[B.| SfuؗKMs8ߛ].AIљ!t Q)FZgGSp u)7V:# zf({ ƔI5&6t<wqQ7~;h&I=GDt{Sݸ@mTif`޸ N $JVEl,kd%sA}NO@9ʻy@U#xuPeΣx %A@&BcNx]HSlP2W*e,+vC$AYr4m8`x @DZv~Ejj{Lk+f:nU& uGfu} ))Ă)"D2ݶ'pbjhuU$OM{*OE P/~\ͨ_ ډe!tDp-]ebڀZtTQfl2w lq^88j(b 1#RG3S^& '{M:ɼmq F0E'sxSQϙyΫ0^޻42z`6=¦s^"I`ʔ!?(PD \c /"tŞϹٲ&2yD D7Y@$(D- < L+>'a|YV) 7ӡї27MmEDECm8jF1TnQ#Yy;<z8 DDߴ"bwBDKY3stVVvLgg9z @ zRԪDWl; ͌.EG/z:ڠnۊW6.(g{'}Q"EFԠ+Mio)j"6I`kP 5Ѥs7O оG}?īϋX"\S74L LRU-^1} b( %40Z(}&9b .^s5ً`uj"Z#6:h[*ETFP̙4Qa!M& ?yya-uUqaK"XJjZhkr\) Һ+/ 5-˒30_[m*:-J|N=c1GUMC/`$2? #SRȌR= e%**lf!* gW`W^gՋߑW/)/+-Mjp7}o~Ak9oUFFa폣{WM囧RO^H;!%;/g/FrdrL;O{Ⱦr]p+w+_sd~7hL1>=g 1'zȏuߪi 2F KM#`Q5+hGQ! r ^1or,CҺ5{dLBH%SΨi6xBT':LN36AB&j5*"$q??dҾdH@z)IpgUFDxS!}SǽЂթP}BJJ:Nހ 8`|Dph\&vr3ʴ6Zl#WiT8 ͙#^L@Z)̼yH\7c[&$_j@R9;5n=XdpD-n۫2ɧ4͋hLusj1yJG4 aAXW묮k]u[nntmzuQh{ےcS uƔԔۚ\2=i3u&рQXL:""FhMu}]z `9ڴMvkM.[] Q)Wg4rPDU*fM'3ړ.W y 4'mEiMA{FqQHL  K={xEM4kSdmP\VW0)aK\69x++ƚzP(H+a)p0 !c`̛ɊP#4_4$t/ "C%Ri~Qd0bu)W+jmQ?FA=A*k9G>}@13Tgv0BvR΄>"p4U:)w:uMށ?Sxbh D]Uq$o~,|v%8#Lod͙+VF-luN>Sfa:%gOJ]#a̮-ϊ߾s_/c҄om~}{_{3C__R`!ͳҏ4b) ; CĬ׮R<-c?6Â.LczI&P[bE75/v(s 5vNQ6 T$%aB5 D D4'4&I7U.ܕ0f,,x!80$ &j?f7Q'tyxjU,;]#J{f4֞9:x3iĎQMRT(; CN@Z[keŹN+GzZ(s gEޚ} f rO<sݵޏ8-0"Io{~ Ja}`5m'ƽMwu'^()oy Szk:Zs$" 1eqI d+zFd,Tg[j٩Ģi;izyѦi^ÁXq{%قSP]v䯴 _C P|9wgt@?֞D |~SQ#y}ᛉ}ᗡ[ˋb@6Y2hs0y3 hӂɘpjpf"WWX5*ı:H ^Еڝ~RÀQ)ciE|H U55ٽ"v5֜ ym%yݦ"|^0ǘDzF Ex~9B= Vμ6ch;&GWX׽gZ1rTw:({BGEyS]0z)Ő(~RxHh< yHp 8sU 4y稾[,poP݃V |٢;(xau5A =}=ho?`?,8QǍu3u}j YL"C SaA]ծ ;{+a8gMΫ,4jj+׮?z a"ac9_:T`2U='7@]PQYP1<=/, Pm*; =G .ʙ% e_ z7)):cV'j.oWUo4`yȂdTPfs^@ u@S=7'󒖗xEo.<h}[⛝_꤉ÈJb%!PDPQf\E`Nfa~yS:dnd~ E9TBι-oѵ7WsW}O-ٿ9b;v4?';p<=`v ,OJ>*@6j9_kNdFhT@qY׿;ʘN:`RRɜf18oWaR"X",rT?jVo\L] QB2?1SK#/C`2h{}xҋ% %L lȵ2օh".tv38>+Kk&}t7,d 8'9U_aM2_@?q Ij8+?+\rϵ4=:wKZe#m&~^aq\Gxf趿]?SrU COc`(ga!l-%EHN(U(% j)}ef"3]M-!&?7GdjN!H,R"K)*f<>7Ѓ:h-F?`3e)vc' V84rd^ե28ePdsH9B eR?}2JiTd^o֬VfCF6jj'b,F-3"h (1 eb-Ƚ6Jv}2O"Y*ZܚmLk'#BKѳZ +^Nr`5X͘geemSlt˓S͖=m{ s6j̇[u1T/ks[wUdbY+1NRp)kw9෤7)]fV725'-OZ 8Ss9Rw(GDu>s}|UlT%'%ƂVR{Y_yV[ |gf _5 k#V71R,3z0Y!j4]FZ"ʤd:Č$W-N>\hB r4ךVUhJN&#a+`;}7sTnx$ƬikߓAzӽpa2L%I&BiY+*l^ei 3_?;>"RLuXül-V~[JJ^Y `Cw+)eس@ƂIQeL  s=ޜ~WL*lTOc%^fp[^ۡߺ Oa ЉY7:++֭Fr2c%gw*fjО9/hēmCxu MD\\eUoda"ڊ[#lt'IPoyzNd~M? o b+CśPlk' Se/u=GJ l$kUebM q+/~op\Ie<*=Mn~_ok WUh*JJ u0̷FXJij ؞-1(XFCUdKʚEEzYQo(d@@fT`G1q:zp21by.0L7UugJV,)~.n%͍ B̀L^ !#$~W,K5T[LWAXWQs+8ėXО5 fS 0~Vhh^bXY*RJr{ƌdZBKDϚVL KWRGJkA Vyf QڠNe(iigVFJ%Zu#{Tv͝S!v!j O74*еakFAq<,H:35භSX’@rueҾvUIuȆmF2bNVzv L-٦S &LJ%ܬF,[-u+ţ Tys Tmyt#sL W`ߪR}eO/UɌVblʒn$u~aś1#˛ɣsAFg=}1rhT{iwEDv\ʑmx{OݏF娧XlLt.o*Z)mtIԐc6A7xž@IJ~;sb~swKA\S<$RİGbSXճg(crr2EbXDI7I +˵im4ēBXm\U{#x9BE~>}DKSpd%tqQ,#,Yw`;$?+cG& '(tetı=Pf2"FPa#X!GijI=0Ft9rp Ď(ŊF,sraa+MJs4ex?)4% K \ih v ) Ӗ[/6(=ق p^ֳKQ18 E,q[:dQ+MW4&CXߎ ϕ's5 ej]MpC7_FgL^\ՈXcpV4Aie[%}s+(󇣉KN!FE9-VJ[TƲjmMI}ÄDv$+K4愹( k˼I3iFGvsKb5TQ⨼ߚ+T[/8:hߡb0’uhbS@Xs&;6 ġ^j5U;̣JסT0R{9b''͖4y&F.-˘|K]zӱꬤI2<&&h.w 5bVlG&Q(͑Ej/#2}Tsr4zÞ T`5-;]SEjjs݄R-#h?psrͨ6O>Eeiqo+ Я2xeԲߗ{Θ cfql =YQ_I6xVzT&aEʝO\9.h?d3zׁ"ĬL+J‚A\Гfۭ6J+WVǾ/ѷxliL\ear}O'}b6ja$fAt6&TGƷN./ij\UY%_ ™~e*Yd3EaP>۷Is \!4n n$㛶TsEu.#vŠ{:w1T{]%!#,E:'[Fvf PViroq-ꋦ{Ot/ZJMɢL˱'}bw^#ҵVs8pL٫VFʇ_Lm J-GZ#E2:sC#rl2 nZfRA̝t{ ,GPGoٝF9ߞM`yft(ywwGk-v*8bKIKXK>V{:;pCɭ"z_ F߮%ǧƳ;́v:5s')m;V󭚒w;b6jM]ٚmQ,Y_ۏO᭵>YXMi5P|Nw/2{Bb,ǣ՜@ qlp?L`f\?}ݳ;͞AkeD7 'a79ʸ/flL`3jɵp߳;c(V;UzgB#5ٝvUO|:?WTVU.z7_D:P2Z^ƲmX3kcl^v|JZl_|_ĞO`y+^թUVw>7iFt@}ZB#MSӏ1ҜXYГK1 _:A[5|hU&Fk9!c kk VO~}O:?Oq\{ 6] CpҳcSr,UmlkPb'~1u ZLgmxrYJa| LhᱻIW_@lc9Sο;kr,_zѰwqCw)1-s.Uȵ5A|yާn4Cw{_퐔9>7KUmdg *-N}rF3utr\ **#1Dl̃/F))x L̾;a]L Rhkr̃wddHsԍfq0<s.)ۚEΔӁ͡ToKS7X^֎ycY9ݑ,UJI8hG_`j*W{p8}06Z?7n4C'KcǤn]Zi;cNx_<8uuðp_L `;_Aԍf tS$V˕|=VK,l<:ZcS7!7َ17}UVڙ5z(WGԍfǓ ߷4dmٙ8)x7.{!`wj:5eǰGJXg>k7ưi62pYl]7x $_k3pRԍf(p:ׯf YjeTb&;qjr$$KXb8;[|>+w5S7eZc95)R-XbȦRjS7a]Oly*^}m:ad[cM 3ߖ28qũ͐0wc(sTVYK)iyة:D*--K}ƙ|/)޺6u]B=v,r&45Llܙhw=WJzcwf)ˍsGzVْMp&qaF3]xhVؙx'Rʤ7NO= 4UĐ}Fu?ƍf:+ɩI$&ٹCCJM;u}F96V%(DJe×wo niR% P$,Zb#"C'1rbHh>YFfu7.gutMǗ~ؕ$?7%yjA<\|[K4j3ѻb#}O'{bwY~)],3.{uN%R= vhtT_T< Yl/Ҍ9l* |Ϩ }e]c #HQRC˿x0ۥGSs.]cLȨ( ]MBl%uHR k)EHj{aT.<6n_F }JGF/UE jupֹ°K?%gME1Q}MĉFb(-FVfVET, A@ fTy8=#故4XTȐ-3m-I@!e2)*@s1L%!,\:BVHAmX7K\=p $t0V*V]Y8*#Vȩ2Y\P`l5ظ7=s+"@)4}Yl،52JJA1J@Γk0.g({ʥ|96|!{1zԏ8)?i/L?d)v|/hܞ5̌FR'bHTBrb֟vE1YqWIs:OfC=` (b_ۛ4P9`kO3Tx=rXʥ)Ʊ˶F1.#Tt1 I9 DqO;nm?⌇Y+@$G_T-V+iRk4kZ3p;]<̼),J5Gfw'*#-oeGH3HJs@Bً&J~WCcZp~raW/s+#A>u]# a]d:d2lizg~J|Ι>;qa&lƒy+`]J c#] &E ?Hb[L$(2K[ '<8 L`p`pXzFTR*C JYQ9< N:m}Ig/®$,b(*rľ,ƊSd"WW hJ<1VK^ޙ͆guͯȒB4XV$r&/F  0`` x,?m%5]R6ퟢ %uO^Rc< jqƥg%.IY$v{ ;A!8 v2 _8+<2´*čCHe8ZQ ĒC <1j!]9t BT_2 I,A:y$5VBO[#b aDh Q+.i71&7ҚT%x+)Xi%r制,AڵI ('@#~5ף, >fwZIF}7M=n;Z:HEn\Uh C {z}dK>|4ѐ3(K<,aEUt.XKLxiDCH=t2,O| S@YV޲FtăPߞ`>[jR'CS&JI_DOOrTn\MBFx!.ۥ33Xtu;j;Jx(1Y/$kuN,ӱ@XN''NA93eBRm7m'DPlyb900fh O$0,5?8ֈ\`ɞ}6{ ~̎; )=2EYw76yޑ;&.Ug7,%KpR癉"@0 *7k*#1,(N(Vh,+|b!bx++x'@֛>i@^d|}ڀ' ꫆jX yiiuU a)Y;Y+k\h~x'z62W!XgR >-k\l)i `84 W BNR^!,F}&?Vc{vl7U?E'lx1Wi hIK9jSU@U_J6xmp ~Knw|[Wz?l[!Za ArT(yG"(&ZGAE7pLo>^`{\beݲs$]xѨ?Bp31V>my'#G#,Ia]u9u .amf5vT^֎i~l«x"՘b.#K%]#L90$Sss0Bh*FC=,x0D&x}VmY9"SVLk{i@É@2)޳1D=tsMƪ'Li8))&9 xxS?/o Z勛(jKEi_RT{u -V_PQd6nj0pGVlZb_,;F.okjXJ8.^k[rr1vUZ=}q%hYN3G%G&%6~ZcG[|NBhkCsOSw}ܑK=)WeQl,Q)Wv߰ݜg,o ( ]Z$57(DTQF{Ό!CC&an 1`&8nJx8]S jb*|QK̎om-[MwuKgԜwM[SjITa'C{VGA M٥w钴oժ׮mAD.+~@'$hߙ\zF;InmL GZk_6Fz)$!ISI_ m6Ks8%ScMyNGm"kiCw\I=۱ַ5C=F]Z_bZVZgX`,2zf?q6u>A=1:I3/騵kL=[^,wրzïPQ€ &$edEp}`jŽ6/ WO>fcxHB:\k,XtC,f>fkygw PgyN22iO(k"ղ0pfy|d#7+IR*Z1}0^onzؾ.JrŋW '^Xc?D~V\u"gCYj=/wTQQU4 M#_Koݢb4{wK"0pI终K4<"rnBr [숞~٫} ?>m뛖^c}zH&aAPU].8;m [uÁ6)g\444 D xCY=@XR iL>QO|N9rVYB{,'9hvGgGoL zjT=@VϯޣUk}BP6 ҧ rW0a'=$Pc?wŸlҭ>?7}ƒ}sleA갚ڵym&JSOYU52*ee5ƣ_(Akòe{P5<.=V\nžߧB|}leC{MFC_t/셽`k`# 4t|eЪI-W׵z?܎zN(w4J+w{5n%_FA[V$ĦǂK UW'' ;;V[59&q~_>HaOM^#Im?Sr?9KE]+;ǒBS07C@t=/*ҬcN:)?+QTKd2=y q@Gބ.9ƀ rA^&I&NqIk3{ ÚuƑS< 0fk_pZǟ|>욅(yJv^Vnw9ȯeYݰ2C=aҺA(A BR},]|kʧFeerR?w扂 bfbۏSsco@Qt$?4mc^Qg:n|R[3OF{ Q~?H.$Uڒ$J][ε2Nt9JHذbtOF462Hr3h҃Qdrq襣Owy&iql0X)|]*=qq]7A2zmqe9,4Nۤ 7(Μݤ`ze& suu(AVp- $TP_]S8Yǟn14c-ucOB4,a1N~ބ,k54%{ ,OcOA6{wDD ]rʢK4¾z+L>KmV{@W}A[nO{kardO'~vag0*;xcFˏ הB[5oeA}`lSnҁ_EJ_A[֥T fIVxsI#vC}V#c_aI9tu!NfN0Pd XwÇP\şr7;ЯܵϞH*<-%]nM\enPy _R!]aʤy0q8x֠M1Z~yc7"@ҹ,y~r4  YYxf?7ROF-A**6S8x=ysάl:wFo<q 38]f hRr|Yk+.qH0PC 5k< p36=}d*B9Gwq ~Ҫjvc6!9O{ZW}ϊ f!'9{;'YS( 9ܴfF]O۝~zKI05[߾xŗ% KmtU:(Az ڧN(i2v˷(|L7DWW}G隷vu'yu=zRfT"mf*)CR'p~V 7 {nZ:AF;ciIqTѦP7qS oBbF&Ůigp_ѿ^HbEDY{kOot*ԕt@ .Wb齼lP S4Z.q)L(Zjuqd}\c_>?mcH5;g֕trz'\E?&("q> ;ޯ WJuDgfG_Я1SY>$aF X/9BReBϬr<1_'IA{DbĹXP%%U]: rN^w)ǰ "ᶑŠ_9>e-:tX豗vzQϵk |.Et=!Y.wTGcS SƿmrW!#,O \߀,u$MHXAqe-/8V0d5]w_rd"DOS+nKL!PcDl$P?C'^fU1@>جS2xfE)X7>#U-1˅N t#TPaYל7/ws#`JBh\:iVu&'{Ho9'?71gQ)$ϓuo=>Π9|JGY:0o@c/=(']?X'nw$=P; r~_RԞ>#yi)OK48! B䄙;3?O8 ,[Jؾ;4 5 MnsFUI-yCC`_z#'$/L)ނm%/]-#blקD'W|AGo{bu`uo哆ܮӻQP`,kx1] Z)Roy0>EAͶ֮͟EWrGԤ,n(/uVR8N#I߽IGҶeE},_%ɞNz$'Gj\߆/]N;k6:ܴξhOpލm\GS #+0!d#ڕDHi;y\4vzO oGZaQ&5X]}&. lq;aR]g ) K~j3A^ =ܥ9ⴙ٤a+#(Uؿ|+7bQ,x57¸"0:&r`GCFAԞ%(,FLJep<>qxV<O'`0"8$Csm ;P_e͛\C\c `ilF0'ewMNA mBDa٫!Hg% &p݁<>Ѽ)ΟCtY~sЅjIx>? {;й(ES @ v kTz%,|8jo}@~`E5Է@VЮyg)RZ#6*L.G[">B_$2'{/'`B*?u\]e}jRކ9N8=8&!X_5vjB9:-: TjRzKP:%oJ%p)3gS]q&JI<PCdJLm}e1y+P]I*xY V(k \99NP.7~@,=pYhI;;hP-RmC~pҍ{8Rf,D2P \d ωģu5 l|+8cl<84cYҊ,։M-Q6o0] } }{7&8>o;УGx_ϗB GL=0gt3Bf.sgw9~=F]3CeG bTO0]ypKGV|$La_Q%/ agNjj|Sj~t1R@YO{wG*A*"a);I)G)`ԩuu6/~y{5@Mp&l C^M \xdˏnݐغmZv0YD^gֽ9;rս[7z}Q1[\3apb9sK ~Px[k~he7iT)imGTgt"L2$|- %M*Ӕcġpk˖Gr>J| &{3nC |'7sI]N41]Ҵ)ь9f)teTJV%)/җDLT͒= pxFRwc~9!1 .d'qtFb|I4gv7q>.0misoycwnOOzz7y&f,nh T(H*XT_Ea7<Úyy0>f~+-T؞b Z^BJ%ݑjHqg`9 z*Cy"%Y`liwؙH9^ 9E_+m:pva֖@vyv 9 wjiUӂ3{C0D``NJ LE\/81]0Q˷ +A3zd)x qn :㒎pP]j|a.^o5:&m?2%G- >BIT%>9=v|KUѥЗG|0rl5;'5,'juVY+U+πRZu1$YD, ֬O_!ov "GmX$ܑ3ք5rI ]m*h+kB$$BY2FPk;0ـ))4ԇW]@O=/*~}: ,VxB*Eť9k Th Y>FAdZjb@gKa8;+:v% "ͯzd U+o0Dc!BBSo=s+nh4>Y5-{j ZjO?Nfg0uq"RD玖&'xbDjFoO*u(͞9TX;ښxKͼgA%$d8J%b7B#^M2~Ƽff({CZj^cr MK`zLr |#IƼ]"݀Ak;a;ʖFo8':1? jkkn\de)>sMP%N72pfxU}n 67ެ|jքjBŗ{:w/QEjl3W7k[тiEm:/e: ?7o:B(f<ja1Cws_&xhgL^kr3\pNNsU@uaӉ=$|^(tSfZ`~m_;>O2Go'U\5k!@xTδ#/;-կi^qEm[B"J54GAA=LEZ8=Ia(r竩6x#a q3f%M |ǚ!:m_Cظ|o9bԎ!S5XhhESk{BM,=:t +"IFBCP2 x*-aFtRNYcH͢b11Xþ7M [!;.qfuoGǎ*BgP( :cQ=q'ԡũJ3QGh}dh24XHlKרoE5HD ЉH @x.S?:6ᢊS6~άs5v ii9wNht_t4]HRDƬXs3 a', Qcjۗ{fwD[28δCg^}b^U wYBd֞$y[)B%J‒z.@["`&jA9bSܔŝN@<̙\#v䉭MR)ח! O{;!hŠoMbg%u1[ O{5KghAWH@ 5~%rM$\tvC&i:d˦;Z[f8q a A3 d-5j 5պ"u-C_Е_.;"῀ ʟ OyveY$ [hnÔZLymgFYIxѡ*g&zE$)8q 7 U਌+lEw_D@Fw`1!{^NBHtD?f.|}A+A)^Ox Uy':L(!nKtrcMId0O;'%qϳ4FHjP+BEIb̋^]fK%/ jA=v| Hkޗ\đU_rhʞ0Udؕ8kWZ:2UTщԹsOb O=wĢ N TƯQPC1+#jҙѡ?(P;Aq^vǚM/m&]QѪi:nZs߃,q\bz6n{vD0_sO LGq\`AmEDdpԊ+LvLYZ |Ŀ(G 2UڭX0[KAʞlߝLb8k%^i ±'Z&5|fvh'cVBzՅ"KEc{ss_VDENoUy=gd1R˟`>홒OxQ:`EMZ:VSh>6|"6"pݹRSnrU :gLXX|6M J uF-6ւVVzư8Y)m%I є." ſ7.*wku:\T{**dĖ?d/dLC ٤VRUY*$u(dQ,dzV_#O|@<]9oZQn(hN FVI @8 J,†2B0([HL=7ҙXym=}:k~iE %K 5L.:W[Fg;Yb$lytR2B֬ALpWX38W5Tj<0xe8VbKkZo(٤$Znn)WLS`Nd1ȼVeke[e=ɰ.F -zt:Vxȡ^!5ff @-Q438omM_tk%[-=r1kSў6GƖR!3 8xpMfm7( e1F^jnC#E٨7T1: s 3D+ԴsgѝClIzh"0%B $UJstc8;}yi5 P]qu?9t5mߡjշT.ѷ~ zn܂\`mMmL |c>nq2H'u v3;x' 1ZH9wrR=Y?-p21cϬ;W\_?Y z3}ضF&ƑWg)rMi[J |ihR.4j+foٺS58hјԽ(|l!&qܴqN.wÁ''Ͷ~ R^Dp¬tT,KI"L+n-ҙeG,ƒHLZaJGjE89ḰeD2kD7LQ|`o/ ]b>/; ~bc3[1$9JlQ)/M&4]I}[pZG\I"-[7|Z@R(Adb 0B$GW_u[qXAwI>0 zڃv|&+QsrVFhɕ[7P[3Ĺ `kM'M;hmW@C9DsK&ssh0}?26 F<€,2@5ދW}HV*./2¨彲 :<lȄՌbXdu>g, Z%GJ\&cnx\gW[ĀKjDъIR[ᖕ$\(Y6kMBo4{4 D .*UP .in6\=9i6Muop?[.U"*&$Ґ\wgn۞4ΝkTk<"0x3FXY2׈tlWxr P̡מ, <&eI#*۶+>%q?n{rx 0 QC2 >!>Î:lePҵ2:w4]`W1Y-e WsvG==JI+zTLOKCUgv7''sOb t>@nb"ΥH\!T%4.d4yq &#b>`CIxfdp%%,M.#z(^  Fר>@,A ߎG+DI6r\:8{?fUd.n\:8:3(:#a #VaHXVYZ$sL/#Q52hT#1^ D1ŒR:Z*L)K܂-lA{rl;b;Bjgm=>1Z(UV[QH#NA{5vOlW/|wC/fyՓ{]P Q:5LZMlL֞n@sʝ;GZf/j+4gw.eF [R.n<#O`fyT5(4{F}ۨ{Z9JU#f|lbwhǺ/@֙eUyc;{*Q|Xy]O5Ԝ?3-tuQ<ਛˉRSn ~Pf'vS'ٺΪnzvAߝZ֞xQ4_ z%C ݋<Ķ^G t_@?LZ kilnγR-Znn::WZ%y>N3B;%|uXm}vٳk`jO`y95گ2:Ns\;ѽ~{_{^(z)7<X~=l> V{'a\4F6iщ:H-hբtGvs`3p~䱟R+DQl Ֆ#k4ݧw X޳jeoÔa{> ,o7zV֍ڔ&5CVg^xvA v'WlF+fWR+éMfg ^`8-UmEXg&oXʙG|>u-w9i}n,W5RV%f3,epFco|<>.ڟD!7W# gH8!#9݇Q@nxHt<$eauF3tǖ|4 7oD\)?ubc}Cܐ.|OUH޶g Oq.;7:*t>:3$7jT2ez'(ex鳚=;9KZSby>䤭dΜуT4ԍfqGNrMiڪ 1 L>K/]1u-wk s3cfhkA PJG&}ݤtz#D=7րD'g l|%iԹ`NM3;H,'.MRU\ L3Uz9`MrdRt*1A,Ql?MS7Ƙv-LkaJ,^L6 O{|y'vck{5w %1HW;;!MD5!JCwTr Rݠ$™(= Dn|Nh|e]/t S)h[u2-pga"j1^د]xR323g}j9j)EFN 2Hvn 8#ѬHc"m8o(VO_\blxE$6{jְ2k!R H HA#Mu%T w\aȅWDhpS /s{ :PߦB59ӹhnxҌAM@Uݶ;kvfᵛ=pŰb&e'[EetQ_q+,bu)CQUSse7yk YWIt0ki"Œs7~; Tfy~7t7bԈDc?2N P`j(jCcL3W,hPGuj]j\A@$A!pŝq+Y RƤf4u4Vi|aHvƗ\C,N//s)EȨ }"yװؐ<2]c Om>qt.yd0q;s[ow?F/kd!6" dJ@9~h|إ.~Œ׺gWCiMdz}5'ݪR>@ˆ,v2Ņ5!,vnfIx{.6\ʤ!C&[:t\$BIUg_0'gL 3ȮGiK2=Zn8TdIJ ]A wp7_q܍R>Pvu0'ы?v;  %3ו:$+)Jؑ!IS xzL0سW>R" %v*bHӯ- )4G@q1 YmśL3J/eLfc32J%#RR< ( }7|%>RgWF Rꘙ%dd5ͅHB@aZ s#Y;!8AFx9>~1H]ҧ_Tˢ}Ou]R:5+C|LI_͹n0`#sBc~>鱋t{g W]~oؕ#9%%Z*LV&͇Yew!Vgg 5xh==b#κZ$02P-(b Фv0}~٥jWMSa \>w2}e?>6sQ H"T!SkFmM.2˕Nn:cTjt98%{Nҕ5l`qs^Ŗ"z*L R){Gׁ=)cbاYZI[i,YI!8p[Lxd+6Ŀ;v:YǞlŗEՙC =-.Tw5U=\~aZ]x%3sR \* R~R7-Fc2E=ƓY5b՛U^\cnmsXkKC)0) pmH.;{a m-6X'f^}u!cd?W=w%a:oDk{F? -3yJyTO)0R H!<'d7bn}A`m;g`t R* 1 aE8p}@Ê4 52ˋkF ٚU Y+C @PMЊ !eO&@d;␯+PB 9J9ix` "%Rbܑcw@=0!^(<..6c׈Ѷ*l[0euj3|pYe7gv=o+v6VI^nPO{&yoV R<@0B̄ ފ>#h0D1X1iBkWtA兽?TB^Ũ8W"/yFJ 1ƲP2YeJ›D(^S%ew.E`ETOʛy0()-d:`{ؙ.'燅OHqB5l5"SF)QYusx!YHqFnUFD1dlDy#@'t.>S!sOsLwha#ew[{I(\n^21N v<_05e$FX]jG Q ;f Ur(IPdNwbeԦ / -cWYf%u B%)"ƊZÐ:D_0kzJ/4uIZF) RMP`w6CɲXH0VSEWo1S0Ͻf5yI-&y"ꕝbvD]97fzڱH2 ]L^6Qoj#RU9AcDH@vuO*sMg$5 !bmreg+@NM{ ggSd+ aKD e#Eut{<"qַnx`׷ꩋ˹w7Δ-'>R\̘s6< 2K-?]TN F$#aK&.sfu&{@PӒ2u+=.qz,$ G)p ,KxDsQ?+^!Yi o\ei)*gZ=jI/}IJ6\p*bGtU!D7r;o ε D3 )˕gsԤajEw,['mzXܷ%#ˍ/Ʋh'X[zW=eopx՞pϕ~:O8p+!AȊU F>`Cpu @JeO^k>; H3ZPmm8"NK4 )[ OӵJR 1 ߆`ٴלǐg\2{oi'I, )"6s",q;H"=:@`a1^{RhhN% ^N]L,~V6%UGޥM_t$*>.ex>d8!ޑb/k+ \)A5C kݦ5 )GbX"0$ږІCǰ<הq 2p®VS'B5vZ ɩi_Y3ʳE2<p1_]xН_55tiwn. I]Ӫ߱p{/8>uՑ܃#Úi PtlxBuDTZqD}¾jNGǀ@L໇ L 9 ^e~g_p qGg>83kIDJlUk.7 Vj[?&l|Z`$`= .6P)g]~ 놠4,_W.4\|`D!0ZHf+^0 0SYRZ"gYA׬ {nl}m CU_E+f*p߁>c.i[dph{4_E$1Q[CE0Y"/_{diP \{j7Ju0vq5%"zCC .ȮlNo*jC;5dH0AA"pHomS=Q(A+9;Gt3u)iٿqoz16&NaDPHcy~;<ׁ9<_Z m˚ƂE܁'e1?P/.ln񞙃s8z\p,`Cfbjl5{sVGU~A>|\]Jxj&DTb Why^W('tuu,8Cpz r8>I.s?Z{d?[?!rQ46U!r,٭*;$Y}%Y@p~ؖ|lY!knI׫P 5`ge;2]Nׁ 08w%~!/{c6w(071"0CpT@߿Dx㶹w~_MW?7졋}g_kOXGN1]w{*|67-00l*2H'da}"jr w.v۰*5 :Li:U +5">buuFN s`ʹZ_լ$d8!Bv2y^`Gt+6OIxPуҌRNc .I+x|%0TT!XY;`ij`ȼNz+kLG! oOo(E *"EBh$u*BmU,r 6R.u.-wwD_!fkHMKMYU,Z ATff_4.0m2fLAp x/t%.[qms; +ж]o5]&?Džq&l2)#5]I*F/Fouo2攛a :q kxG=ǜ-_X9:,Ui-Q, (;a ,` s]P[vc9uYtY_s?whբEqECC3e9$~%̀Y,膩ʹ =nŢ*.lf{C`ҁJH`Q+5_+"o "%h匼H)JKh6侯`U)RW]))̽K=@}umMI:Cw*53mR~ѽ̏I΅{i]6nޭbO@'}6x䕂@KI%{L~eVniNj2S7zM{ @$bg0a)-[$;+c"%v:2MbP$L+Ł\OKO .׸ŝr5ugH=+E'Dlx<"EBb@Cu%gL/]Vn"ᘉmRmɪA`Ϩ!oGxm1&mTj栦\`@CHZ9iOS  8ca4,WKb)aJ+٘`[(|Nܣ~}rͿyq+1a7-nυsm:;E#m.K~KyQTSȨLY/g{edzA+cVxǯHp5}}}Js^cb}Sט+iK`P1 Shƣp5TIu%.zO !,d-&Xhl<҇P/yq!'tYI۴592 0@txv o#mF_ b[B̨RUd.[%|Ժ o 7`r![I Ð8u;jgM|ݟbZ +)SNiQ}i!#3j  nfPq=g.|VSS=in>3k`:COޣuE!qZ 2/?'}qѡ'߃r jg  J“MkzRd.us[X5t-_q' ^Moik0"C"Oբn5񻂣,SsC3I=7LUDZIUʙM|iydE8s",-BMkqXQ[,`!Pz#b!"HAGڼRC]XeN45-lXuuU`6]l׿Tf;lEU1{:QLm*rMlT3fi J)JJĂ2 ?|z}˧lG&Ƹ7=]Ua~kI纑_Qj~M'7U(n6(4ЩcF:XZ!#T_+Ў~ JZ8kOٜQ1 !rDqO̸>dL\m .h<|ЊEq7 H& j0FG˾2 Zѓ# t@1W\ dR* eW{9V%˒,v$|}cRZgt%Q@o_iy$0 qu1 c`fs4o,Ӏ\6{Wm!㙻\RpGtmvaʎifKGe2MKuDT>_V0VGfGa%}Ɍ0̣,o c8ZF,""X+8"و oh΀7;T֑U5AFj{%p).Ég'?5<9֋wmI\JV]xJT&H]2_ߗP+ɕ^+J']oQ*u-J b)QUGѭkA;ou\~̛ L!&J:B5EJ r9~ޝGu|GYTrKqkj)6jCpsw+E Qkk'7 v8nÍ@}*_2~;f]%x97.aX])3\P{5JOJR]Qw / ~>+p$ )b^VH1cJVovwoJڜD}Ŷ!bV;dQzc[,P;?5ﭿG@%m/_b >{% w_%Ƭ\y`ERk=?e'bIɌt?iyHD?ʉ qK6/R0&p]0)q9VpM_Rֿ}'Yw--H4$68K;J!%#&]fCQՇƯnws#("hU&+`);neOtQndeUd("W4Ov 8߀@p%)+UOt&}ӆkxGS:,xYmߞlM(55=jVv+p߁睔x^%>*~*ͬ]3m^V]y܀'t=& ]}/Y댢6sk06g3K:mwb}óp *)祖yIMuڼ´Ir. M݄v9d{WzF;<9_#5"  >^6OS`:a\+}ϯ*(, R Sd-@9n&yۻ۶jj't W6tSkhH'Bb^CK^z/ Z<'Ώ8Ӯ|aɿwǝ' Z /MU@5d x75 i=}o9_yL8┠3  L& |hoS,@5`UKߎϹ_Qиnۣ>v\ je77ݯ]gAG ?LA*$ u7h҆+I.{F%4ۊ$AKYxs;,VnF͕FڽI})wT[fsUY<]{zCH`*ad*RZh*2:o613p=Ҋ;lˆUu<)%1̤jY ϠL L*H#pΧ8a. &m=e'.O{Mo z,:`x7r$ f_q)) mĈRVԱ]Y5ҜwzBq돔M1@.3uA鉋]ar;kPAcزIF%9g20gz4!Xlgi)W8ϰ #L{Nd%]Usco%5vRJf2sA@mH H Oi_|Vңڕy-> 5#"BIEI#I s<*q6'B6FYU-0t,ERDFN{UCCezC|RSOFf<-{)"U 0zU" ]f82lm[VEQ2V]e.lkN^ ֖n@ 0NF+s=eGG$%)\%R $yѫt(cغ&4&i>DλH&h¾_Z\ ~Jqx_Q; o#jwypݻlOK'M;BKU UYΫUQKb#5K<( șH\2/w,)ūԡYYG5"1Zw(kɜ9o9V8?qHCڣЇFnK>L@7v =7}^a1S=9ұ|blα{^p5êϐQ0x,D7XGd M; (}5]7Nk ^7eEp̯OAy Fya,;h家1QU7~&0ȸy):\kyO,z:PO?e7QI论Ry APE)0- ILƀ8}kPpxƙB'űX/mh}QJԾ汕3A8܄'lGngۨӼ'd6J)!Tq.?-PU" Do 6Sm( 3̝dAt&P iXd"fޕrEڶ_uIJ=JN~7{t5Heh&*%E *0]fB;w8(Rǚ]2\j4 έ^۾ivƒ }-G `;P㵆fHYMfI3'eܡ5뉳 qM_ULJp}$o%w) #v_ o!H˼hyI.fѧk]\Sسd7e2&̒h(#R 4 N摀#>FNyY J-O>gJT4tmC⮮ٞ,'_(0 OttdCO2I/ )9J4|QWYQ@<-_u>3_տτđ Kn1~W9p5w7nlp N\mK^w&b.8#x._K͇zˮbj:ܕkG$Mߟ:xEzq>b= CeP'$<^QwrI˜f?6I\YKt(:Z E9̇)'̹6LiW8gٹ`:`"8_:FD,9}~r0k=>%Rm@2s4k.[ dU=+wy3Die$֩fIÕD^'" ]Z5cÔCӨ6`$2R&(1sGtCq݉wH Xɀh+RA"k)r1f s}UmP-v_6ppWA{Vϩ=O^mz*r+g߫ԅ]jQ' F#D*sEڣ6*y~oUBbu2K,Ttx">SMD7_Ν k)Ѻcv @ w=18.♩ʾCQe v-Q Fnx|rw)lG<2iIљ j# ee-ΪmU#&^g=r;´gֲb&Dxg;jl8C.-$F&Qɵ;P6|F0ps'}FlKg8&wڹnx$LRVG 9Rգ $ rd 8>IH {;TeZ#\рB4X':$7YԴ6 %˂a jpIOOfͰ ]ȶ"/{z. ⫼ߜ}HyFMe,Sg*CGVLN~iJ2NY\j/(r:vٱe]gZЬQЄL/sAK?Y.:kNzzSSNQmV\0 S؝AKgzn1(S[ЩX 2ҼZ"¶%aIWqhFϘy; ]5@-W'fַ=B:k;+ !c8pLkҘS],L5Xx*9ڑؐ3(<α5`nDꩄ?)hi[ZD]#nz֐OșqR )T3ڙ(KJNT7X $rq@?xz^cc0U$s*7(պCm$B BjbZqY–:sBj g쭉wN|Ċ_}EP0miU:ajg&K6G>4-1\cVihԜ#Y'd5P5hO{aR?-U]&7i@;I7Ea8ѓX-@^Pa"{ .1Si IWFcXտ|-WP]zśiKg{uuh#<6M*fOnRVPS-'B/"7 c< ц_1S?#|lɳ/onwn^s893aUy3=0nknX GIP܍O65\낚9ke*Y8rtVCCsOJkb+yjsIZD/7ٻFpa|Hl4/BX{P13??*/}|Ru]t-|yO,A2u%K%(h)dneN^qVj-==Ok+ ,>J?~3Oyp x;w}y2[^dv<7f2PF?* nxϼ]NQ{}\deߣV٪* {M6Es\WV^V7~e2 aQᖑT?;+dC$RDѳ]1.2͎^ dV;t恧T#or1$FkCQ?ͳT{$uYvݟ2/:)?Ͷ9M=7|H^(|e~?Y]ruQwv ̣!T*_Q*e[o-JE5 '!xcPWEeޯѯK8r-yÇ,Q } rDg^`2,PP_`7I+#~=X_ KQV7βޞ+~EBDYo)3^# s>;+R~:Iӊ{U$SzW.! +~.%d[ҿqDA3N_(DUتd(2%(JCz+ͬ 9j.qy4_P%mn}'hd*C T)Qj8DgiVNvC}PH;G_($I[-΄ a}omkj<ऻ8]rB @D] <'0_W6C2K=A5üHꐶG[ jZmFTw3 0$4RBT1=vR'H`j-J%ZYD=6OWxd@kCf}d񬂹ʎ)}ZیlxujJqt'8I ~8x=&i|&9 uS qVFMCCwx寝Xhm+9('S3)nuk!P%*$c8 :9tz;#[dfnB<ݿLM3U7׾tERBbȘJ1UݤٽJwWu0ۆ921fVr+jU^)O;HЦd5J"xЪ1ҶoV%?˙~RTm '^bJ˪,N"ɄaHrlmVW!X]}{wJ]sfc+z R.3iʎU1VSm?/M;I}:-%JӲV=٪lT%.Z4F5*4*:KQۦ$v,.hT܆׹Zk*z2b,J߼aXcKm `,il`D*eKZkNэndZȱ |OpN|2e=v3Vv_=YjEzrG Q(UȚ.$2ae5dmlE1c9icYS֞5Y$>Y:žBJiʩUP[\؆$ߨ֝ sG8gކ_%Bj: !9bܒ`Mͣ ֤e[#эny2HbI˛^[+Gqf(2 lDIg^Q eu)rҁ6C٧;1D;ʂam(A8}1Rڃ^i _yS{ػ]|1m; &B"VI22v2bFJ*=(m b$"൵j'ov_Q%ES%2•Z݁s)PQJ-g<. 30v{=WZh6`B&BgB4ܲXTZFt}y PNī_M7nST+6ZY'~)|n-pj''CGՓ (^l[\> Pս'}O>ev GGX}A Dtc䖭tVKe/,,wrI)hr!O .2{0黤LᬪsM\پSo4[twHㅁ`B;w=*d+sSP2‡֍&@Qwm4w`+bZ#bu6 xRMxRR!ȑ(YdnxnϸeDW4d1\6'$bxDH+Y&./"\ޫ 0$E תo6[Wp޿SSۢy쓓݉GU('XqTjnEnaxeIޝXW#k$ VlG RIbQ2bͅ&@{b")E]eQ8WRE>WT`f/-wINrm\|R ܂-*![{rl@*1`"֠b#VEi"[T2-{WF=~b󒰚c)(B#k &=e^7yyR}a[F#e,P6.T_l0i/V2zŔd,qk43UST (?sm4i#wK8T|Lf+{_btٗv&l˵&Dׅ3,(4܊da4֖nu利"Ą NS[JaNӴ`emxrҤ]wy=]m>rퟩfӖ,Fi5m 7ȎFRCYI1d!eD[^luk0(DVX :7.$;lF\_?u $Gl\|ֳ;~7%49IdaݥnE&kG6 4vo `,+^u .LbTLCTݶvMcwqncPQjTӗN1KEXJRYHmON-'&M[&sMm(~U?ؼJvy&tݛZe~yVgw{-cjѼUʃ`WL`YgLU(jm;ӫ}W*z@@-[TU(rܭztxPxo ,ԙHkU<ek};k9Sϱ,{i-!ϗ~W&b9TL<)M:ČPF_:{YW)nbb:L4fA>, y2P[gϱlú:HB&b:(N(Xk}l,yTL-j\*^-αTh~ⱝg1_|LmDG< obWܱ`17x9zIl Ihkڡ<l?^u8'ohN6]^fZPCƦ˾ٓuk9{_l@ 'Au폏 b8b*MXz &>6{cl4ڬp)}#9/#$/{rݏk؁Cwr^Ŝru7JtϦsRvM5uec2<] m4wzY;u17@ ٻۦI?\]ezxr@SwUm 83~`E7n4!C}iF3t];;iM̭ljɰEbH{2x d 4g@Bp-wHI[޳s0gEȵg9i0Дs)æ3i%q?&0YB)#VT*~&l]+`:{gH)c4cgt1\: ԖsMhC@L!B"(#F/TA._ cXa/<[NNWDy!O&UXV /Ǭ0("̻]΋i%A WQ{b((@ bN~\i06DPgQ C,h&+n!g I-wHt@P6w.7lNEm f2KꅠL&x pSVrBY^FPgTRI4 &BhIR IEFܔࣁ D$%"oeID~Ϝ܆E|61wI5G\ {<dDe ZUj8a ?-*~hD'P'H_ŐxFC&PZ9iI`Zk^ yV<̭4["0<//JǤKDVV"E[[UN2{Q(-M8i2dBZ})Cjי;Tbe VyE [=6J7]m> u-fHf{rr]{f:M}+>bjQ G)2LǓ>GMϣ*y8V+h؊ǯ 5kVz_pg3I?ɺl&i Q]ⱦiX%p"1eT]мաK̽ |mth0몑C))T75:''w NR ykAkהi L[8;u: K@' 3A"@<[l0yZt Iw<BUg{1WAsI z=SKsuMBddӼrqt$@`xG Ai1د &@꼞9P}N>lƿh'z&ފıKd%E =Ϫ gVBRSEm-B=4.d1Lw=˝N& @ewrET ^q?g)x2/[)~!Kĺ|soC$;6a^taSs|TS?F?/ |^au'zWEK)X>(*k.W^T) D"QGy he=JgO(Xg%Ts§Nn+9m,׈x͟ ]ϕC k Xun>TU J] 5pM{WML<@I1VTZn^]FDza۞uw^ܰǬ*p5+(H`r ~u>:Wbn,VQKd*IMz /`CT@޺g4A`]Z][T7//BLKyx<_123b|Źd|wq[#"~K ծ1,+8AnӚK>IBHB_amCF\cRg  4@000 3_\:_rMz_r/įydNZ:wG$*9*.ࣃqul8vܢQTw[&Ϩ<1SLA `%e ` B>#{8P0NNԭ6Vq>k״P_;7o}&sßxFKc#0po@?ޞ*<WR;xlM9頜zLR8qM*UBR |.7|4{^؟-K!+$;qSW3ɁY0=83!A ;Sa dvI g0uao>luuԶfj"@fBڨPiy3F\n:)R֣8P cF5š*zEi/^d/Z/LHXr3js#VK08AL5Zb^Ipb館͑DQdyynRΪ?WvMdČӖ?M/CpDD[ޠ# 627P%>;a*.6x3:)ۆs+YFXjmbJa%ᵋX*&ÑI J7{Ȩ2~Ô-VSf~ʹtffnN#.H\|Vt7/R_y Wv#O}[y&J֖P27D>XWL,oPoTl^r e ˑ0W:4s]0֖#*x(ke&o7>?TE"̣2<ֹEXHrR<9ZЪӊԂy~+u% ~|K/S;ml55Oԓj*֕&|'>zֽ~&4uF[WغRPQlyVgw&͜Խ)je7\;G ᭗`be*δͳpuL0w59i&?ktdesktop-1.2.17/Telegram/Resources/art/emoji_150x.webp000066400000000000000000112361201326245125100225050ustar00rootroot00000000000000RIFFH<%WEBPVP8L;<%/7s8rF`Oߔ7NWosrS*igQ MA/|B, Lg R1&tF̬hJ,ZH 1 fi%3Y TDɉg5:B~LAi`RI]lHt*x&GRѩh61Dd${$ZW6T#Id[,X0aр$Ill!۶lj`OnK` 0aBslu^vYwukakͺ]zb-vnsק[wΜCRg#Ѝ=d67L s栛vԩ@}_ts iHL8ҎNEӤ:i{Ҧ>"$="tCT é.Ms*j'jD0Gx<_>/< a##AVnJ6NN2A4'OR#%REhzh KzU럨uU:D!> Ƿ )lT<u!Ԇ`Db^((,/~O9'mQ|DфHB|*>O"'+w!ޫ*SoɸuKH]*DD@`p~(K8aI,ogFutQ ~.cekyk6Z Q,A$>9K}y8p 5a/e6:oܞ/+qzs8xad8̃c31;Ym%>(˞FoO 1X.?s#g%_5$[S!QYpqދ; Wғ8ɱV^gq3XP,x2PV3*4ߋI !O,p72P Ir`Cp;'?15b>KG;S7T̏?fXs4 anF;w)GoSz֚48QXQj֋Keo>˺ۡRWci0n.g0Gū,_)'| ޡ, oRcK]zz_†CC5B.EÆhSʨ\W;d,S:|3AƤfҘL0h'UQVRz+BT9Cast%RoZ"dho"#j,TS;h|F2ת.6URE!׬{9oW9nU[2%)SyָrNu7^'~G]}xx}Y=\-Fν~U͟_]r|_|r:9tГ''&LOl͌F5#hMΩ'irɡsOtts~[sH'L-l-j hXѽҀmj[N1\\u]SWxK6@74pq@TJ\so[kM\7vu Q3S0UJL" &3lFϥy./mU]ۗ^֝&|fn0@\@L2JU(j{6$i6vVFw-~?}sԩT\v.[KUb;^ر;qb'qNNIĉoTy*۵T9uٗ{mEs/BLJ!PLP h= B)VStlè[h(G tf [l 9-8j@K1C0DQ`tU r2CS$4V4MMtX<ٚ6V˚*AAcFqzHQ` ( 1P XPz7]rU{?wNn%h2cF8b38l3is68"$::{ޫ<`۶lJut04C R"bw>c7]؊](]ب("(=t%ʤtTjԈ4M8]۶\n4y^ؼw;8BQ% d[,dS2IiH2̌3%ВI,E(9bU{W{GTj5w3̚hffffb_nffjfY33\#\!SἙiݣQը)Y3Zm;6Iyrekٶ̣GXmll6լf}ܞm;l۶(T5m۶mRn)6m}7Zlۖ'-!(b&bowbawo؝ݍ݉Hw $@ݶ.~^<@0m&}}}M=}F3Rѭ5ZX3fthF鹧{{rװ1W]sD+͡@q1`LAqe0AY \q0z99MǏk9aأ1d reQ24em@DkfeY.-cA-pbq`kp,(\2`̲Kb"",ض-:뾟d&>(+nTuY@ R $3~O_y羯m϶9muoQE4э1'1^0NovzMDр4^۞vyP?n/w)J$R2iR)]%$BH)@mۖ41֖]}?ďs#7o6^DQ(TaQתmW{'mWb)R$UX$އ 93k ݿ8,.BNn82)209{\̕4Ňq7˸a=m0=M"PLʸbeЅ6 6t,et\S‚qM,$0)vȸ/8 w3*"50&ЦO 6"DY(=Jq`pϐlGdV1\:m۶ml\۶mX {1]]ȈxvldGݽl۶m\>Gml{ נ1̈=`۶MmLf"uksADdx`.M~w}ξsfMFҌ}/K}-}4f4/gw+|ߌ& & iPDmBZ>S! "RcCT@ѪINJ,N C8]!5bB0Bm0#i)$A$6&iP=Bi1AWpH NHc\QTHl6%EƸDp[2PD a(i.ѢoՖ{ dQf$r09 iI-l$[,ƙ<kq_-1\}?NwU h 4$X !@\޸;$HHƵi.Sulcm˞Z'`D0[DQ;P D׽ၶmm[ǜX!XEivs͹֚ UU6woXm۶'m[>{[eUsdrueW۶m6*ֺmUS1{mmvl=F]6ck푫fmv۶u<ϧm۶ef̭e֕:j׏ܔm3d[Y>پms1۶mvel'շXD, $EPB% J I $ ƠPD@[[Y18oTbx'M/r_9UUTu={g?3=sꜻ:WwUWwUWuw/|W=#d^b,Y[Ōal3AkE,x1,؃0`&E, ,A9,x ‡lh@AF`d#lXF a|,ZÂ,e%qa Ř0KŖ1Bd6im,@ +ۛ?qQSY1`6dc`0tȀ2--ԛiMCWmն1e^km1b@p(^zJB~N9iR99uI@[ @!FB $!1mW}kQZRwiRacqmǧԍzgB2 R۲<F9w/&3DVC 4hL6!J!ޢTZ4"#{{{ϡ@v2M~㄄ɡ4M۶H \ŜC=gzjqxz3+ 223"2sD+7̪-M̬21XJ1 1āg7+\Xm)f33kewCf)ƶ1cs13᭸bfL,6l Iujm۶m۶mml̈F۶q۶mR?5l۶m׶m۶m۶mNDo}_fΡnbw0ֽٮ.ݬ {S׽݄ ԶS _&o/I޿թS9]]KUk:]:Uu}?wG~gآH:mRbzM"ClQXe∅X⸴X-&2[a p`l2ő(3bXDŘqG"ЊcjD 8hô:"#cc1N[pcqGC8.Vmm1 6p,ٖ230'5IM6v9mJ8pAYe2>g9Vm;c9l?~%ɍ ! RhTPW uWFK[(I{r]%s1(I=VtwwIR6X "v#HKw{c$[T*Aa;Ͻ\x<@5 Q)G䇡HoZy/siƏf2?z{<A:'@DK/x3kᷛo[DRڛ^^]%}AЎ;Ǔ86Yv±W-P#R_MhАH*ڷ;E'}jU]n)y7OafayDpMeu{bggwic 9YF:kl}zv!-AyHYmÑ6ZSL$ŴSc=߮O 趄 ?9)Ӗ3Ԋ;ڼoc6K钷quƍfVsre qܘChҬ\֞{{Fn;k[{@z ;嶥+ϒ {Uwz9w]ZiaQߜ"' w> [IY낕ՎNW@$t:VխU'kGWGacHu"Ɠؾޞn۲욕9yΞUN֓2\حŹ~{@M|1g1(]pX5my_ Ɋg/.隖=-=O~|4žqN6_&5~uͷ@3eg'ˋ39ҳBni Ke ?_{yzwNswӿV;|g:ݸ;_ρ%Z}b_'mvkQ[W &_-rv擼sXќnj__}/zxEkͧƶtUU+yW7lᄏ5n+S rjPq.I4*qN=ޤ@J:dG{awxM^MZ EYR.(RDk{~3N1?[ نZ/\4ݞءF^;i" ˡpJ[{;V޾KXtp{ϏT,nwF`#9Fw}a4zUyWxDTc} C2o)l\.A2 +8@ q4SkR֙;uMPw91S2xhh7ѝn}sǣ#<5!.uoh\yʋoX-Y̕OStTw/kįe>LORwe}؎wo}ԴQ||gnO=PsS,Mo؞g>Nʗ !:^f>8n~ټpw97ڄ'o>_0exr(5^- :i:0|o÷&?q_a W><}}ý27x7_0+玲kYM*|l-tǴws^_:V:X:| WUv0)D|v˟nPo{?LH7L֠KO lnk1@A`x9%| 7x0y+_fT;9F/Zu+}άk{G]yUڔTQ8F/JXqf]'ӏ򊷫>xlE5uLkgaa7I/?XYYaի ;.xy 5҇u'gX/~p/K3Uv0 !7h5csKbn& em'w9ptK87=&m+>5;}?9+# Z.mn[L  {'`4Xr|ƈÓ7=w᯿ri~_xE>ˀsK\?ཉ?)H$ ڨj>w1asJL6^ $ԼM܅:.' P{ά_:aaRLGF@}n7B\A)b=N/X=q)uG\dD%W;3ee2x?dx\v %3y>Wmc+ Qn XOow }9KBPⳳKzZ%3} J'n-KX|֟gvw]_~cVTgu aͥ?#ϸ3!@qݫB!?Wqo~ڌD P @}GѲ%[G,:>5Zng^N:~@1YBB$7zk2R@3TC{޹-~1)OMmXH/Q0pG|G0Zowʒ)n0־*f\3_+6B>+5#E,~ֶ?z1#G,4_Oo | IIuVzI~dI2Ͽv %[3rIZۚ]}Q$orASt" hX G7MK.u_<&5Ar?n;Tv{ӬՕ4z j VXDd:>s[ޮ?4w  onx !dhܰlLgOz#OdgȍE>v]wz6%LJn٤S\ / }tMm.iw-8NFww>)By\zk٬Q:=]ؚBwvfܱY7d0)2KW}:ں*767~NV_je*{TnKbr6nxͥR$~τ }—tl㯽*4^5/<0S߮6!l< ub|؟~—t`tj=~`}ɧ&ñ%$Q@f`3[ !/ؓ_Q9Sm{DIn*m{κum;2h*3={c3lK=M&*)0nw_m>Tl] 08*7)/;,^J){_Xq걎ȓJ]?e ̓K?]gjG xѓ*5g}>xCa5U+/{ 8L>zN$ ƗzW}K x`<=K7scΏvۧ)IDe'nMwB<+"Z, A%YxJ:}=.V+"nޯrK.(wyT!ɍIǾ^e9a!ʆUX%Ckh׏Wg5tia|6'&-NWN#W~;6mkE@dŌ >XgQz;2p  "PAR{2x Uc"oxzcVd)QQJCjt_rS֧j=A+5j,q<`,}HY&BL_  0:I y9B<Y:|hPb]KW}իymz-VwcEMǁמ1A/áL>jzt7D'^LF5.y=ikv= }o.w{~D;/?p+9TURoY !bAx%HSJ2lʺqi 8pΚt3X=ca _b6 O&IBNn|ꃣKGz899l^eiZ bΛov(zCw4i hMoGgdKmN!CW`%^.%?2eOSjǮg&'Qb|{շP"_sK7'+T#ޟZIOj@>U$9NQ1a>^g-]3w||A­=&G4 aNRy/`O7R܉Ϸ6ZUhV0a5\ KC"DcsVUp*+8MQ$FyO=3V]|LƢ, h]G| }ap&K:9obz&o̸ҁn˧p\#f=ި7uZxv]Kwٵ{ o9G 4J(zO8x[~ZVw Hv'<kK8&!~D5Bb /3Ձo !},o[.Opbcw~Bxg?h,?=_"xBfHa()> #mA-Y^xģ6B@%HŏxEEKQ H$!\Ⴊ?}CYȊ;u/!\6zW5Ѡ/mo웽=iDfOOv7\lNI97_7uv*.@ͯD8X~[C[4$5}xJ1,׏դvw (v`TJ0(In:e;Nj8'Xsf%ͷ5A>wtU*0O;Tiߚ߿o@J~2A,+_eV#2<⾼Gbqn_N;=~ !hO(exVnпd`wL}uU 2qnG֝;׀?̥-MݧSAT\PB FZz r&i4n;ު™3/=ݮr\za V/Zi^ߍ#ci!d/k,]hI D8!-$hEoRDu&Bn}I sAhSչBl^EIlH=+sozkuqw' /`qYL`W^ `d}JTVgx7Pj7]KIߨwjAuKYYOZV0Q\j{ڣW9_| ~\ꆟTESeӎM,}lަCVLAK!4a_V]D- .ޱ-m=z֦m|ַ_ɔ^PqFú2mLz '7-? #85I"0ooC4Yؚd n?SJ3_T8=afOC{~prbDoL+OZBXQۗP#D#!!~1qA !S]N@RG'&N_'(u?4Vl# ԷbXːU,\{X$ mQִ!upioUoVF^F@z>7J"-JJRnaAi⮂Y6l w_p%'y'k^.! Tz<{t1 ܏!yqGY|t$6TEs: 3^xg4o{뾎750+S>Bac8|1uN|}oA8$UD\VHrnEnuXfx??t.MNH\b!Z):\#N7髫?c'nS`A.5\DkR w}MmKu7S8WWJ+sbS+UYGU`.pAڔ~gu?hUլy:"FVI.% ,BFj [2$K::XK\njvA)tl s÷4??%!goE$'-#7'٫Tܜm.붕9 R8k@&X!R-X?:~u&eιCP4#]؆ ]l@>ʔ!VJB&Ea'uX$[('O!j(va{I.(7@$ + cFʻԢ2V*θ>K{]C <Zhqzpӷԑ7\6tkVřv/K̓ "2k9lp`pM]~S4L)0’'kżA[?-Z#AoB D8 \^q޳%H3m7=c @~l:End $ |q7=[ܷt9}W- Hp[=ܹǓ.n7L 9eW,%#F.Wd.Jyހu8Q{$qJ;yR"3oKΗ9^1,qCk-/uHh2R?W淼vhG7ŋE~\:U צ<7/6ਓLhGsN b~Sg,Y݉n+jʾ?a;!so0J}>|<2࣑N@?dSpo*6ùrEHc0 v W#Zz{1/iH!NzR6%tޘh԰FiT]{;Wⶖz!u+ ZtׅUJ0_ˢ&{ߏ;mNb} OK[ZФ閧m=^Iw0^BV/{.{^=X^S8~ls.aWpfᜯi."0.8rݐ.?5H6ECN F^uw$KWyPǹS_uif=zL3^N^zDZ[~v߄H귎̞V^~>zZڪnkS E7|3Z+ji녁dSBy+QNʼC v4 ҍ=ߝ$c2R=#mv dSfϻ_O:TKҵ.c B28RTSmnkEJHC?[= [ʗ @V}‹[|R+G$7swcW?_ο?W#(1"a r޳}T'5U - ?(9U76Jsdto=ۯctyOXPM["JKT8ZIL H`U iBMNnm?yƭ>z0@>D eLxTW-^CS7/Jwco'Ossԝ+.4t>&ùaDkvO8{|{SEQrlql[+WHcP@ Hc ;*˰ӣsLxW pF]p[?ŮO̬uH u7-3 {WGΛ ؍z8up7P  Q'mD9 G486Lv),տ՞[w쬧TtHtW McWs5;nӿuڝ=‚N,'a97],[Bfv,Xцw8 A;dNNu ;N μPS17L4_Ƭ.Q[˱|19Aw!w›;zݙ+ck0Mʢ .cOoa&OfJ c'#?\^,G /2<#h=o IZ|EpDL(h(1p+ыl˅wwgo+_帧~]ラ͠0B/$QB o/u17~r-Pi߳3vA\ݴˊ8ԟmq/i5,0jm01xݏ5 RI/Q&/"5o @/1ߨ鞜`+%B`ߏzGKXdlKFZbR8+ɐ 8LO]Z~#f fGg}-N?uҙ~>\-Ss \]| ۩:wq(; 9`ɱ!àL\IlQ>7A=*tTE%o,XNdJunE \LQiĪxifhkxCNt e QnN00Ĝϫ`ZTD`e%YEvM )9+T0PymiWziۄg.XtNŮ_gV1NZ(q#OY-;nk.(Jlׯvf{  Z#D^2u?޷dFvow߿jwxRI0l|n7sk6{9I/:q>"7&N8D-?"P-o9)ڥ\}~7H_{Yqs|nA`Op"a%Bt+R 3¨ H嵎m D4pۓ/h֬t񇟯"oApvꮺ/8]w_`,%; }#DL$RWg}>i򷽾59szt ! I0+iZ/  :"_KAMRV/w {K`KlA1:WQ~-Ur<֝HmL;vg_(Si$KHO,tӮA3wٔ Z]3qA" ;mjA!JLieW6v\] "0ؓT$GpX[bV 8tqNթ'(ha7)4ZZ֜ffICc~OhхHepW!o[C 0D#OfVEz/:XC=Q@I}pS=vy[4 8Do@4lxK`-0:u7+n { `R'?{cGesWҥ>>A48Xp@eD ?1 d dR23 Ө\6\Y \c*4W]nAxiYal /a.ךVC$k{9E>KEboacY}1#3[?:Ox1rfqR.'|o12}~hoTJT~g|sVz@s+e쾬j[O7 , ΀''t+&l'ΕӡI}5CζfwM%}#mM\#:X"_s 7M/%kIV#)rRf.ii%ؤ >7P1SEfM "8m3ێq8߿;R_KuNAҬg~3z/<5W> kԞOwq;]Rr5t~x B`~óW6R*ziM|H&a+^(U,L<#,w _+¨ ^L~D'=bN6AEMGiYϚ/!OW6U$@f1 l|71t0.Өn8 }g&kmɨuXXReīrh/N_o~p常C .ӌ>t~-^U6{mvĞ-q*˽eJՐHA#)ٵF#࿾^}`(=bMLJs҃F&{)2(,.-{!M+bcKu@[#8Ks5<ʭ>DI*s[&_v:7Om!$L}v Y\G%utM[jly̙WI<)[\IaG{ w35KHnrU|f$Җ'&MW'|;nSM[p,Eݥkm= )Vywᘖ iwx6BփJ{nfĥj^)|'+<p68~wv罌)C}LюvM5,a9.\'`$`Vn ht=1נLBbqR  3*I6 ddjkgdSfE RƢX?4w,M2-}5k>(h$KRNCF " h{k;m͊"\Ԡ]_wuMCI IGCn_VNK)`)iZ+Y'e @&,?E5E}3+~kb~o3䟢Ic+71nq_~e-=Dl#283+Oފ'wlу>]smhAjqſutF^#`z &Iא^@t|yuMkNzlR@N/r}fˍH qܸ/ڳB4†Q'vo<,$P/YY#sȟ\2"Sɯ(W(, $"f_.]$wߍ2*$ Cq  LOKXM `iC̵h!Uu>!a:죫}KQ.n/Q}Uθ*C˾=+^2em®"{ v iu>|+3襁VCʆIyWW$Va..{mOxQkd ZpoBV/@{K1 gAp&'S?񶥠y=n]Wl7sA\Ą`F.pI-5lׯKdW{ eDug`HbL^oX); 05CҌ6T8rx/BߔJRUWG ?ݎߘ,F!/ۧMOjɚ hAdK]zC ? u BC݁|S-ՎLp@JIzY_W6'쓡Ihj8m YoJuE\|wF@'CX o M@( OjW[{GB|zšjPE6 {w5 ۿz_WyӔ"@*?WeBN/Cߺ![&0Eb edsBt2L& @PE!}:*9 軖_>-|8m-Q 81HTBk2@V>*tߋz RL:n֞HD0D*I@:IL;„@[".WD<1I9u6+M߫5Ý޹h`!d$E}_03?Zsg2ƓnWѤF2#o+49TV9 hqt@NGsօ+/m#(|J]vj-HZ0&[Vs_o3I@~ۄ`%FѢh5{]k_E^2F5ȤKq[+X }L{ 8iQ$K :Cw|S@%X5W5|o%ƈDM$$o-EE|o+@|@$BfotwEGל] %g"c9yޕ9:ߚkҹUI8pp^ig{!=BkWd=5i]ޜ {:j[~b`oƂ`_o}xk3 aY#6ɲvS1/J1?ྟ GŤtW|L4X՜tXY$Ђz1mDIѽ>,~GP ïwT~ {6zy)Jr?/U\Ư93|[Jj++|`ՁSIHQBSuo5ϋygVbJ(ŨgjLųNSK0B \E8ZQ%B uޙ(,PvwebJߞJH `~&-x'"gia3$lh_2*G ?UӢ!{bW?;Yp^'2<ߌ'+!>-.k(|E![*什R"3I7}ڇ~M!;s:>-КDq=7 ?%||˻=5{ p[ݝT\JNiJE![WR2OKTBg_:oBFP4GPMZT Ly(hDCCn=S{SgYa--iV}_󵶝po͊}94ki:;3BBnBTI+̯1}Kf[3E!?Z{((5&?QDμjLCĥKL-6PBvTſʀ M@d@ ?/ΦjK뽣reTٕ1e.Z-fC)wբ s`@EֶDd|>+O lg;3h'1KQtO` ʯaH5925'3w1:/6ʝ; NOjpt U r*>?!ʟ nɗO#FT+*2,Ѹx5g/:co8Uw5Ԩ U_%P^"+z NMj "Ƶ.jABm^z@ 'Lq% "% o?ZB~]* {F?OaUԄs\ &QeDgM$)~[A0Wc"1 8%^p4AL_uµkFLFu@֤%<ֲt\2!g%5JxRَdPPQȺHo+N n/X~JGW4:Fhd_"R_! ǝiUbH\[;f J<&= 4B˼Sח6) ^CͮMf.@[%O[l33@&g   pMh9Iv臲7B@yșc{LPZxR:p#FJs =1O,ft̮_a ;X gW)J5;\," mE~Ivɜ3H& >` F1(>mN|o' K2TDpgx}voZ%Aخ[}nF=aBح d0ۂ ޡ׿c۷6}I`mُY6Wg,F?aV+R@ s؀`_BaIXX!*Y/тjThy2EiLF%ZՃ߉nGF#|؜dByY&h,6ApaI)w4u(ْxZU./'EDK{^[ޠnB|_E?Ļ@?R_˔&ݖׅ}2s\m|b$.q(6~9Os^o %] R@?A2 \tY"okկ/"DߛO3DL܉r@nE='X=}*q"I2DH4T3`ê{6DEMaR0 7IT8/V~ }V~)};[јh'ḪeϞ0D郧 0ތhH XiZ7ip/m`LW! mjtov'Ji@p?اmQL|b(cֆ4ٖ4d   `@&<1J>HzG8ҧÔ}QEo5! ̉x\L_1`!o^#=9^ky)l~[>j,Hijl0M&F0]hsr(9q/? 9Zz% Utdy¿}^;O*<^)[ɤ㻡#qjJjPF?Ⴓ+Gį +L{T]6?W[t~5GڠZ D 41tQ:ԕMPRR3AseZ"^}Gjc ]^O\z)]8^`B|q d1-k A{`V_K`e^Zx>|!+}o&dD>S w|}9!Oy{C\j/F?mtb& |q\ٕ.61Gkm =)}PjPNU+*Pt"Nj 0*( BB0DŽ* t:J{s494+F7{KeU=Q`X䨨^] 9uTPJfL$wf5TzX+j3ޑR8ivpr91A0k}C |'Ѡ[GchR@@8'Z)(sF,uZ֔P|FDʧ*L+5I?ƯK bޓpTjx~'r͚$R}wUᴯBY|LۿNC|ѦnDU|-܄k^q9,╁Pahb!o_roAPZy є{&x+dӬhծEmb)un$ M[{&M]߆!:- %`_D z՗~^i޹zb0xc췾ÐlMQyW@kYʌν,:imX,7$,YejViViz ~I05&^V=rbBGrF7ёU|I׼%$Gy>oɨiBx~_EewXQyw< _q֕C̀PSE5"nU2^"k C%DYݶfVnUPf7\^P @P@ǂ[Fn!WBP5$u*?!X;s> 0]|V|3zT QZ&r6x'a8D.mv1d=miSf粋_k"ٗ[jϹp{7 o4c.nS2ʠ zkJ =jm8{b78!{w%x_'.$:c%4N!/FJ̓6 7NͮwA/;h!@<1DSup2C(xH811EH so5II8L\LW @3c#HI0)߂XaX?xZ E^#6_^ /OKUn1P0|[ F:lfAaX53"]A"$I&/>+;0hd3"(C(L {zl= Htd п-^V5zJLvs҅7"B:Ɋu/XLtu_;%g(qŇRʎ D.pI%{A?R6H2m!r܂ao G('w_F.F 2U'"fNI{fVA/PH%hwr uѭDϺƼbT_XO&4|ФI-~b7]/5W(Ņ®P[K5SS!^V~w ѫ'o~#r:<ῗHϣBʹ$k䰱h6w$0;A(~' (XIIؖ@Bʌ\0DBQTz- bIsCEvT 1hlEALWyB0^ ϛBb֪y%52a5ʨatDD`@&/(^G$iEK$tS2hcxBCid %QDnzOsYR[c>BD@1uvH%@r;`hZ0Jwc*+bfA6lsC)O^ր+ ɫ'$GmTe~ i*VL5?#0>8 fIגƒ$6&헜e9ʔ. S*ל0_M(̹%pYmvAb#ꩿQ _9Xf>Pk7>*'Mf B@9OrAEwKisAPwDTFF-w):]+ׯ ݗ_$ӏ>_MӮ|@횚 'J@}倅)`x |H)!9D?B|%$ԺDz;5k~u>+BVO٧ڑs 0RHxiÆ-KΔe\ Ć6(A%Ⱥju+)5s,5ğovݦ"GdXr֬FW«!?/6vԚwD7fCoɯn^iuՖm>5*R}Y6BdFMvV/Y;YLPa"WN88]kؗKyŇj.[@ߋZb6eNA:zulħnU"OSIVe1)Ǽv7DVdžditUuWU,DfM_錵EGO3AR\IqޑlwD9qo%* <T\ejBuʐyb"U$8 J@HΓЎAoC(5qj"Bt@aysuT?9d`F<BYLkJ$P<ffTHΗX;#(h9~48Xvc&1l}*~9ΞO[3n̈\;'?Jv $BsVp,+t? ..% 0ӂ"Jsb~ئ8$("P?GʝW53\?;XH/cBJeb5T\5ϓȀ@/| 3lǎ^=ƾ -g=96a; גyn*G*X!Ħ}Z֠YR_lC#RMS&{N!c7̑H!E&T;TUWhʳTt8ݷْlC7/~OAkT7TիT,?mC#$.elNR4[KIQ] 7%E!>aMrԼʮё?QM3INț#a<)]!g;oʃ#ClUgԯ`q? Ϯ(9RFG,=Q9qb)puo'|&I6-bv0pq )A_ 5!iHRls?'Y%)WuS[ _)3 N)jEf :pƏ轭e}_ڹ Ζ>C\H<3}`ua4.[G}Wp_~hVMwM&"{!Ăw'?B~N?9A}1/v"K*01I7YN^S&dž\? 5 D$ WyWIB HAqy<2A8 L-HFmhQK 8oွDu++)]5{+kנ'3Xk?)F= ,EDRHgNVՖhRΈjߧ\l@z]/! 1y `ggj:)By}Ӥ1UPƀWpU^pSRlH]z]MNOms_^p~pQ& x&ԏ+biϛv ND[zz'JO+=xgc@;L-Unܡ~W M/=>/i7 uDJ{bf䅏*߼o=u%h$ kt-5̚0x9 ?uvG1AƔ}!8xs46T=3;/Ckv-=–O} 'ߴS0sP9lఫ0s<B]<o^ʺn0/ޠOXtf} 4Um_m౽Nyc~=E1Q 6L_AnpEsZ2 FH # p馯B@D(hC}{A"=n9fᐭlQ褜 լ QxCV{ $E(I|nWg?~ܟh MHU:;o_]7#)i)Q~RiG-?UArLoNB\EDVqlV@*5nniXBƁҶ:DDC!"lշDX)N ؐD *&!(`| 000\B?$ʘ5_ zf5)eG h iW vs`|ObB]%lj)ߎU\9(T)VP=x :LWI[L',FIZQ`ca1^IX:>'#㞑SnaU7^!-!_kxozh~+"LkTlo86 IR]'NAhWDXt4IK~+ Pɬsd }ARk4MI&sBc !CJYά+&uD5=qwf*|4[U׽l3rgU߄_Z%llRLTXb)#0n$ Xj;'扒Rpy]W0$lS 0.;z1={Rk__pM1; 3զѩoDtjX:;ԀfxS]+#3,hrbbL0ॕ?8<0[ѷ[xb!n s.]UzG ?aUYZ@;OփG@[eC@I-:*sg7'ų8fhfj</ j\4ZȢ`b%OOv\Pn!z{df<YYҨBف4v&j ` |Uemp_M{qk6P8Fe_({$P Z|&r[Klq%5d"N$ }QjC>1W'g5S)kH8b^ycQz$PPBJA'*2̮p0&O#,- %X$M PGf$,WRz> Ru=8kC:KhDF`3x,O8ܯ>ɴB?ݻ!r/FuyRT[Nȸ(rG\ euE#;{qْ(vA°Z6`*3@,<=?"BȃȦ=оNJԠ7 ]F9E+r&Q(X1J(b}W H` I#ݱO Xt0%Dc7D:)6yک}?#XN!#*,2%bD?$a-7^{곯(+`´^rYm9mo:i S~F$$V4po" 'sA֯#%I͸Ꮄ<\Ƀ][`k+5JI @mޠ.[EaЕv# :p[ptW=zp-.BŒho?nCvѲZ vIpǔ|EgyYJf. Eh?+ f= -3!@tP"2XNǣW$|~rxU7];c֙5+=!_6ѩIP{(t$}jօˉө{;x~j(]G̃OKh 3k݃?t%Џ:J+QP-ݩ`aߔ݅o$[Kxc'x6Q*F97W%ahP"<_WR$==AQQGPHtǎAR/溟r JF< e,^$ Rc/{B;0t kfZcft MܮtĢo%耭Ä,D!1k*wUabA"C6Ƥ:B~^?0wOYSCC Vbs0N1}FB\A:=BgKж3nnii>'^kJcӖ;9$KO4d5{b:8$x0O\h?_K3zuG]draWFw#?5|< 4c~q4*()sDE##ћ፸!1OVK[u, wV ;/c~ۜZ%E虜!KYC0 1Dd&/wZO5'@WG-Z @B ZltPWJy :q \G:毠\rAh/p)RIG~q㯍sN^iYg%7G{'VmRA kگI^k$4γɖTzCkŰ/ xwŽT\S6]\@BYCO=!O𤬃T.˽N5Zhyj?`?ʙ|j9]x{ GLV=)di ` NBJe&U ?kDhCZʵ};O9yZu2n \x.uGT=$*hWR %JA-6Aҫ]Cg}hN4TD;?ooomlnWa)3jCSMau ZO&mXz d̃w08%&}X͚9}(8Cnu{Y>jJY\8, p/D$l tٟ+C8ӏu tdL!@~.fҶ@ϠFHjJ›[ djY -Abc3=c36!ʴJQ5wҘ#u/vp7> { #$48%v?rq+!+.}7fĈ{?4c.=_hk(%ΰ^ 8 ['Ҝ8g[ I=+6ҤnOؓ ͒oK}"(;+R9ߩ˽I'NTZ("w^[՛o}bGr!бU2)@6F)*־! l!Nz/B z:|X}=iœ9jjB^}\c@JZqLE~JGR9D鬝 28aX0@25&>ݞ4:T&el IZ Јn&6>FwBLCHJCiOADQ&?}s'%͞CnrFN@ i߉.AʬHI6*;}f«ּ(0v|OZyIɜSiikIbW#QTA~\I %fJ޼{m%S <3C 9v뒿Csblw @cc'u cڒf_m[ 5H"vQ_υv0K%V}E 2v "8#S}og"i;\aO"ChشTfsZ6HM׊%v#XQA%2}kYgtÏ/-%f_c?Di τJiuMʿ.Y5f#i,^o)飼YO/8 ҂WMerUC'O&moRts3_5t,/F⯱8IThb Ь#y;DWƭ[܉Ȳxmǜe_jzBwjO2j֖ف z [%_(&j>]M bMqUp `ť ta⣕6.7dfj4\VRt@}Əfv$B!Zzl D&\/YVZ)?WF*iHN"o :WQ!Q`/81:8 KHR7dp_1L1 X9oɧIs# yXQg*J11ͧm?W0ȆDF A>WDm؃*>" |YwUiRGe?md䮨9TsRW \ j[Y_rZPA 6±?o1za3bׂ-pL|LA,^U$6.HcފgVE_lf;2Z1 ~-x~#Տ\ۓB`NΜ{b[+s*r틤 IGԊ7z.N٨2)#"k47tg+кgddJ0Mr=\xY{_Ttã (5 ߞ_ Uќ W$>qCοT"8x>A_htm2NCx!Ԃ5u YDZDHG a2#RRnTBQS2]ej;e sC?'"jfɂ[_{M0s>֛A`B/k!%L{0=ᚴb%hҨF﹝KMP O2kJ{p2Зf|Pf-4z-@0D T%UBb饫_kǼu7Ǹ,wrbvGz%oxK.8w vfd/(Κag'`֢Y%T(x2﫺R*z00;Y5=^#N%kYKY4':'S7s8;z[YPl ?;e T3 6e]L`OWwcM,Ķ: "˴ h2HޔH9 |a޹Z2zzm»-DZ&mޯH+:~3*# u#V!&c nxn*K[zyi=5ԨWX2 AB܆MղEi #'<݌!<*ޖQSu2c$N*TB{[>Zl9Zsd\ކg|+:oiU'4nBxEvߗ=%vrhm]s.gL46I@v ( )قaY)P̣v?c"- L)BޟXړ(] 9k)x +P`aq"2LDUzR!$$oJ4%?4aIs~[pY*i/|vxz09q?_zw69?n [iYT/0-/i6_rzQ2jֽ֪MRh׊[Zм(sx b }?3Np+0 0V%!5f:vH%4=v h Yō([ 3u_HS'e <MLn^,!Ey3xsSH,keV@q]t""6w7 Krdw.ԽvAk-վk):hBȜ mÕ(zO%zAM~"OAvQH_B]ΗsrWmjj W8@$0l%G2T!SgןuK96# F~ah&v!w%9߷Ltkȑ]@G*:I6+H,~"I)HѲ'G#QYSM,Yf1 '@ 19:, qWXiLz`V w3v>ck{]]Þwɽƃh3% հK(j~޳M/1 mG  0djU326tW>>jrtV׸R=pӓRRh"{VrMo_o{\>} d\i?mz`YLTDqp?h~{+jLBF}wWTY C>梤W,Uz7){ooR s8[ #?ek]4nARK,YIav &%ccT/_^v |ǜ) " b+aиlMhKHZez_|_:^';; E\h"G%'ZV|jO{?Bޞҥ Rzh<故ztF @8A&MpC8D}QE8, &<|urG[KixPhhSk!MpHbQc/ADPRO~Y u&fdl>wns j\hoʉ}DfE^ߙn{f;hb~/\y 3R@Qs2x7n Wĕ}OuUFZ=8>F, u*s#'wά5_4E  s r"eJRp|jr;Lu`P#\m 3e M=*CqvV Hi׹{kJ-=M\mņQ٭ӧto, =f娟$[$I_{~UӤyv"7e/y> G-jklg T5{; xM (%ċTCrGhCmYdLJuT8~Y鷌9wkGLEcQǵN4S4(p.+ŵ/VM$ ]KݾZsqC#vİLV sw"wtqBg^rgh  ~{L>[P[ DHD)ѶJ+{)xnr/4 #26@I_O;|r16`'_Ӷ ջC^:Rni#-P"@{=y{|cX&g5M9`)h~c=}8_&vIg36hAIʇyM?۳'c}Jw6NZ@)GY'#AvGg?$8 j P=*oa\A3?^=O}X'T$m8B)|@-k3c4Pڙ$so0O>o#}{_ q|s~Th,(0`=]; )Թ1/̋'5Dʸ5OlG/lyA=>C5eخ+z$/p2?o7Y+lBm|wu]#?O%ڥ_'.6s恦w~MIzrBH(6jE q?E^ETc AQŶ̐?]R _zRǮ BGqH@r8͞|}7-ȗ=~-l^e^m.^}_6P-@Ʒ$'w%?uҗnfƜlM|5\8aK7&=;}fC|&'oܓey-v%]XG94{ kt$Z=)i6c&6/1#yq%}0,͂.h4Na'$?\55T%]z@_MbO]1RÚ md(ӭ~㛜=;!?GYݼ8Skeqk6I_y䖮}~w^IaoLi\XHOQ )wOV vW,ڿgw#rgDt9n]Z蕽 7֟3[ڭojk6)| ]ꮵ}h*Kwvv˶o5 Բg6{aUT2 /[ÂU;zfA)>#$-<ȫlԇTW8? \1_%xf%.9f*[`~9`coA L_>ͷ8d&A\-di'm+%&?p˻ݡ/;݄lgK63-x<֯T|{n'hPV)Kv_3N;g?iM|asM{wfy3(io!TIy<mYC|}7meȭZr)ЩD t-ߤe](Zԓ#H֌C1V;:O]vۗYr2;si&汩~ca~Ze3Ho\Sxd'oV1Z"EX=>?<#C/ܾ_w7~6% e_2M^l=I÷ސO_z~#돿4^\ҵ̷ֿkDZR=I}'nwIUODYv(`mq,ʁ3{ٷ#W7z'MfJ6Q ~n{%Or[Zi3xW}/BKB[jow%1E@ID_?WYSwmv<{>3N0u_s/]~5 pLyk ?.\7C)߂Ot|xh糏kg6rrѯuDᚥ)לO4Z˒Gs'3)Mw?o-ZGW\|?iuI9=:ugب3󧳲Ͽy vՓwݯ]Q8[_\Yf.=РMa1jξ8@g99ƈ8g:3'| N8!x$Sݯ~{g{C)`˸f>yIQ8>KX֒بnFo^wF>{o!GtVG`> Av86,aq} }N]r A_Xa+=]ttkl㎩b 7=NMˮrt4Li.GgKV̪' S>9/jT/Ïw<ͧ-̃t{hLt隲;Wv6i#bDVq~\?};|nbfdBʻY% rUlF67 }4{ȮHblͳ gcA,s2t7j+|LJWq˗77A Cjn`f|X[kϤeI*:ZzSZWS7s][ЮZ`jIw9] z~;0ISϿ1zekf\{X,gj#Ww~ytU 9 6 or % TJP}ƿ;P톍\N8w@'ע`?\ƙƪ(03E5ȏ>bÁlZ~UN'' xR(LLSf>}ϒk[7hr^U2`fPX,hSD5ם/~fN4_VAioStuL!7ԋ^ ϲJrX[qa=NY0k yx OW$V# >suq3>~r|S( Ѝ_2X˾Gn]x?+M4GbZ@`ӐPihao(}+-&Hf7.^߅ƅ/BDM4<RݸT{ҿ]N~)$ߴB?ghs9;Gvf2*m-Ԙw}[̠;53PWV:ybG Ԝ6ĈYj04GZt?7ckf8@|v4xi^ڭ'̃8*œ'C<5!sgd?>=ȌWwc7 &WiSYc˧WWϼPwz]2o/ڏ|9NlS~ o%ſodD6;Ed|WE#hA\s>|.M2ٷm!t֎>o3 rΪ7|Vu?u5Ϋ `mu_1z9|mLy]aw'ƽVY״'>7+vէ6!l-y+|<@ `⇴ n7}ʫEA߿\W~ =Z$ .hO>ս-p"J97_5tv"FL_nH2own?<3'ޥB.U?U;/sN_Z3uI:7 +w?#׭2u/lxՁ/#c.]jLNSH~~Vk[ʭ.'7ςUH(4_`lh%WƿU7Exup\%g }/Jt;/` G/ْ_cU"\zTII-whҌU1j_q89&g-4'nQZln/^6#='ΕރvKr=Q{bbE@C /Wf<6g#-wD+?y_uŌ IBy3"B΀7:o0kT{K7y7e?WHK̙ RI|(q9`XAruBcOZ鷄yoL\{ UsNW?7,O (;2[?cZHtΤscmC~0bWvpdz[cvj8侍& k5է|t`ҟvm`v<\u_qz?~Gvmxx[q.E|Ay]>^YY׻={~S{2pu0UV^iW~Ɩߞ֝nuiWz{`~ ޟPxvYiKG|LY ىx C7FO|wSF=L=:}OH+Fl_yC܅Ӟ,O#1Ĝ|v7~F,L%PUoG ߾5Ϟ6f㝣\%fW7f`~Јt }}3 e&'xPf\O 7{g׼l JݧzvW_a-1ZwO?A]yqn&Z~/yFcSrcӨ8/Pj+{[7x%19۷DW"t{mn/t>}J P"VrqS7vB+e̿t8TmM_oI}4 1/1 zuzw|OZN?{.u+m?r_6䎠7?dUss=m䷛5oeElO]0CTf}:E3xW܃lZi:6rN5wފr1*??v3<~HsmH{L[˞)2)6vυ9`=<=и&`Ԗ\5M_x`^qvu*iWyࢸ̕G,: KT ͅ ؃_0{Nɼ-`*}~ᴲԞ[s KXa*t*뻯XZgnp۱)$p/b^5v]L%G_[5UXtM&NJ7 Ϋ۞ze>[BbpuucQ$hK%`UJtb2Cȫ*&ߠ!v?pNߧnٗdnFl|N`CSDilg^4ЫL'7S"̭Gȭ3{ _k-$WzJ&𫌎dƉG~_?w5ѥ-ߞ;3kPt՝~Y8YJoZk6ں9TCnw_Z>B;H }; n[ħÔJ32{O9녷k=fyo>>2Ŀ7OB4?z^o;&)g{|*a%9 E)2MvT5YNP#u%vP (Wn1+LK w˶տzP, c N$rWP|!POlpO`ͱw!$rΟ0)a°6! ׵3=Tw=̴z*Lseh%{ ^ 1 &x!? W|㮸_2H" g<||xŕ1CGn}8vu3LWD=̯Ӿ3tSF>=Fܴ:&.yǮwr\*Exn:ɿgO %i9NK;8۟-ۑiӪwWoaa&R4[O8sUqaEןIzS{QL)jsR(509&ҧXG-[dVkʿ;m(M̙3((aUgh(>X=RO,O_9w 4T | fZ +=8:8]KÛoakCWVHJgjha {?YH#Ŕިk/7q4#2Zqif.bAL( ^IH0 K}cD N~2L7ݙuK*w1e \@!A>Cgo^׭/05nHUznu~?t?CWZ6B}E0kh Cec.zS'tڨ*!M4^GY~;Zw\ f194 UDi 86Ζ7Zәݐ:n/??Qw;*r:(A3>-XA'ΈR0EG!̅ϿBR◛jS׳&&!h/ ?<=C=;t>Dz= pPր)B#`|4dyѵWF lX [!(A=͝u'?'/[Gfo!J+bjްG'mt Ttuﴡ,d!^*z80mW^O!\|8Tw[3g.qol,m&{Iiٌ9i&ֻsXaSΚ@Zz뚲!ֿ%ٍ靻:IѓMKM.Ι"~yL,EGvn=ѩUL.a1?&uS{OvcJHUWէ2,φ3*\c+kqc  'v99FȲ|Ý/]dna%."`jgѓWN{z[s~c~g(%mjjJy].jrڋgԓA,Ō^簘9$!;|Onґ/6N?ݞ)EK~'@ ŗ^fǏ4ΟYq7lrNJldSCl]{E`Zv]ݺ`|JoNֽ8|:Y8W(09`ի~@QOu{n}t,ktM #w?eX{ڷP-3!kh"@CS K9'޸dAۘ,::ݲ^!\-Խ.4!ָjߋ;y2C_=>k/_өȲa!s͏<劇ˬpkN RI93L:16[?Q੠G~)iUۢF,G>iAsDMZy{q䰗=:v~A澼X\^̓ hIô ˮn)ڭCΑ wZqM.YPbIb)Vܪ^x:ع?8(O+ܾM-4 `pp3f=@0 ]&'ƅ.?_|t7]Y[s]}!7G$B,JtTaBDP/W\'IŲw"d׼O>ݮݛ<0*^Z<09 oq[Pu/펼v֌߁;dE 8Y O¶WvW]Ϸ$6{SDR,~?ݙ,< s`mMTҗzRce]R\11ʮ@^?<3Q5%xHq;n_е/'Lr='ݓf]鈱';4o]8HX/|rQ́ =>|( ̾Dz!8AJ]_Pc@=5qMٳFđa~ns9)E@Q*1*\}>uWF[-i3Y^&JH9뽹,/Ź8h7%=+?7hn sw,O4(6rƭc콿Z~tvߥ湭 :O2rRL0A`J NQ:1PR6la==sKsozV޿{7!&w~Z4*UgUјAk!ʬpRgU[u,Y1w_)co| lO{k[𰃏k%u ;t_M+:Ft},0t0nwBvoos_R-B+h̒KR'icY;0n;}O\5luK !#kt.^>H}J_Y[bџʄSҚDJJ7ssC&w} -ďlh5 MVT;j=R%ĠA$86i\鰝/h Y6(`qzCwAmyW>Ts\$𞯂j]2Egb t+Tu׋9\C>"UR՗{y.vP^6C׎*[Dϣdx[ io|n'(z9f[kx[+V3guY];į_*$oYm_c{[Zh`aP0  9NM#%jRi@.jj{t6$!OW?{7nPکVDIv9*PP1Dr&e)[+pzXd"5n[IS6L5g> ɃqqZ'Sيy'WQ2jesxDjE'MQf8ތaY{FAB)7U%r2!7P7#z_c#HZ)o#{lL@E4KӸ hO^3]vnWo꒱)2Ovi3jJrաgJMw:tm2l6꜡d/P"]Һՙoq2?`ÉQ٥wܗMl/ptv(M$p9⍘bM\gW QX7p$&H7m7Cڭn9䧿] ;vUQ-RR_&9a̩*×v2ဣu̧Ϝfi0PꡃY7Gc;[;ggq)Z(_E՞0?@0Kz@NCQImY?q9d(TLNkĝX 6cZ.UJG 2 U 7$I%}Iă J6`(SM8ƎڵWtӿ}^((dлY*w:[|EU]C h8De>  қ-:M77ywSw=l٩.+6c^{~fESGɾ~D22}SG nh"Y~jj٫'% pRШ&[K{/OubŠ`AX`"k QggDIX#Fu?+d/q~Om1!&NgF)H3ٰ,4/4s]K%݈)9`M? [v0(j07>%:͕03>kRB@l_h9g?:oLPV. k0UȘa%߲Ϲ|H|!${S&P,aSL54[+`(JɴbjyV=:< Vn M"cZ :}vPjvTnKCo4-=c\3$D1Ȝny7:*IQ%bF̄;nQXet:AL<%5rɬѕtڄFNxQ#K 6ƞnx6şi$F˂P˶;e(u&SE{H\؈ǧCު9h1 %C$eˬ+Ռv%Tҵ;>Ss~Z^T㙜Wۼxtc:K_~' 5ujY?P\n,ī6q65ΏfuSpw늶5L1RnJ'E_Q >g ݒPψu TVfpP5vW $"vZ۱F&8-DYqթw[2eGfmwSS)ǗtV뗌}{x>jGYz!sƐd 26R,BռtNf孷|OfBQܲqBsjQm۳D⒜,3Vty鴛\/wI-' P;mwt0!69iV[vɥ3cuil#+C @ o]9xFZ۸=GNph'晛3tAݛy񵣒q08z1mHJ&055:L M$y, vWf3T`zʆ?A765&B}镦yj|+!+{O ofpj ^A]*|2ݡt* 'T 'SS{7cz/##@7H 0B+ciЬsFzjԵ>Ϯy$ ΍K@onH|lVmYNm/CF5LN"kF(+[L }v%ah% 9Lʛ OVx͑n('#f[YoλՓ_WIxJBxq k>}Ę:ʟhzʉ'3YjV@2qp7{ٻ<^Fx{ ߍEXnqL(MwryC>vW\pexH!==J:{z:]|QΑ $x2`.WdQm Y,BبD9 p!ewW:d֙< &%)VaP(%(M =tҴޏBYqEK]E:opN*2kD%Gدe4.@x_ml?Qq0=An{-~mvo@v q 7lȓw|ݒNt'(_.*. %a}АE 6.Zۚ 4[{'/M\Gc5_d=xI>W5KnMw=h!?D'}n=uGz:2~#yqmaeӫA=D5 Y`%@ K`>o'l卟:dyu)̆:{HF*s]g5WzCk~ ^yf 7K_էwG\RhDZb+m @IHG=Vwq&yӚ;64;6} \\f+F$e00HR 0Fpg_"Y0hR2!VDSFK1XӋub@V)(ǟ. M6w͎w~b {ӌ ?y~'eȑN(&#+@@vb=`nKu٤lH]y^~bz7]JKƩcң .Ô olHOyz0i.u\jI>FwgpmWY, HD26BrL^TH=h,{e9[NN9S֓׿RB*BM ܪU24f g=s.0ϰsAo^o :F['mXJQBsM~9uAr'k%(Q󴁭yӒkP儝J4{@KPNnGtj\#U#6-H2agXYwW1KXww{G&C1Ѭ'zw y(f bsš-5$9Rh fP bс3jLfQp ph4Kgg<ےOX5fDKYfU1a :qwSJ<0MCIG>5Xs[j|ԧ?i֥Ho A2n.G4aO<w %eW#n~WC%7utLeI!Bv?"S^ia|}yڗ`LOO6:kh>e+ƅn!cu=8"9hmWyvS47ԘՑ54 wn&YO։\`P۴Ҩg|c$tpmbM@CmZynjg^ZLirk=u9٣:I&aNm~n\nhEJKT}D5~ ;BGufW\ A{"v[O=xyGe]q֤5dקsrWwf0A 7@2\#%q5xk6seecYxv7jތhR$pruH%"J&'D Ś& <0S]y$P"ͮŝPS=$$9dUI6)KP;ƍįٝ-s^UkwM$(Sњm<K.H* \|ÔOqPA*1eN67}),y#'WatBoaeؿ,,~sv7fqAYJbq(%hj.gje6ƵU҂ Ppq˭/&xzءN܆aqRHl.6F #޹A)$91ƒffLIݥ.GXeñ mM&7,5"|.] " K4 z3=(O<]kʄ@7l@d7#9@vGjDKFtBIG[C^ 5!«~ j|&l!5Hdz ;d&͎4B|PH0`c,g(gZ)WA ѱOsOwͭA&ע8 5Ms<2Z. `[jv+˽l)ZVÁq w:gvck]yTqB8ض] ^5u]W lOfU;R1D;xDz[&qRfُ]Nt?NtTHW!zAZ<Н7OHm'Q@D7X$e\r_Q$-1tDZ5}<HQHk:$9t͙s5z hMk̃s8ڮ|zaϴ3ֹU)pϹ_~iehM@40þ?~=UmG?L̥\V *-H鈔N Sda`Ӝb.c(zd"kɈ́3nP'ْP[K { 4^E9Y;T5.4Χu; ޙ=Ʀ4Tr.+?Q$i-ʉ&B!O$h*cUs&u-o$oky!L]9fWOM4J4 Q (TxMHLBieHt|!:/ry!a;oM-("ec!.tFK!V-WH[EJ0W@ɰ@>Ո m ( I^N#%al߀DIk*2*p#W/ȃMv.lq(\*S虺oh jDU?Ӗ[^Gh=NP #N^]}α-Gvy1O#Zu3Bx;͍p4,4\0j^m vUCs@Gt8 N¡p\chQR_v|(Ԛ&Ix^;+4GGY}˂᜞NpnWX!wp r*OݻGԵ^c1 /ɨ{dI.Ҭqj\848DOtLIPH ?SEjf뾡COyU~VV ha) KҝNZ`'ʇ쭙3F%O$X_Yњsv-@C+՞%Y~oM=5 !6*rVZ[R؝܏D1-ڠlTFMIkrVЍ/R;5)/Y %eVᤎj}qla5O JXбEN[%]9GZw`_Voۑ,D5ܹҶ׶řK3pʬws+7. "[mq,뙮1>02[Y-Q-}biˁ }f tBI' YLў@ dN:oZyZ]yJ^ڃ )QJs76n_r9QեFmL7GK&$lDlLx$T{/~ZϨndYOb:yJq2V<R> KcUZ7qxy931U{,܆v׃"S4QS3h{:W}M"W|Fglf`jt8rϸ)(5Qk1A7n=x ^lьusѳ.<NQϿ6]γqʂ3K^ԟ?!'cC8j߳yyfV%dY<<`jC L[m%(@8u+ܤ 2) NdrJ= A. \ ):%46C]c&_ ;3<¤ ׁ$Zkm_4t}-C0FXN"?:BNp1О]PB / =!LPLO*ӉOONϦh&6wIJ.R= &r実{C[ ~VHm@Mj*N߇Sr 5KƓ[^ xsNx?O;um}YRIpU`4,ava~ڹ ފ+ .IT`JQ&$NRFGG(z0 ]? ܵ6'BSR(i 0il b:BDIE2W"!]8A!r e}d.^'bBA>X-vHIVl$dAS;@/젚U'|{mZDҀ'_ _O b1 Y$$D=C݂V58Kfev Uy\lr'IX 4,iR&u0%9'W[u7 ~C{մBGGE^SNb 8PFDТZSF0wl)Dqǽ>k:/K],PA3ƪ=k+ms}Bb F )4NM"C$)E!*ѡdfo(V¢j|lL܅<[U( (˿[=!W+:X<-uIN,oN aH&b"@X/I8M(XJ6Z|DO8>DTʱVث\0]0!6927}Vo2Br'PdS,JƚFvIߺ[A!ץ@deBNWE%fڦu.woC$D+VͤDite_2P#E/o,R}tUHEڝ2Ff[:٥JbId#b 9/3L *lP5!()hbZᢰFM hk IZ{;kuCumQt'&sѦleb[pj:z%P:Oߚ!}5 4P(e\U{&Fh4t+,-;λY gѻ&v^BxMGԓڿWK"A%5+ڄo@s$g%[7e:! ^t+\H;K`6_<Ѫ9$pͅHʃ{Ŀv3a_ #?{'e5*#)OH; jJ2&%9+! "qg}"h ҰREuᄵԨd[b Q5U%B4NH aD@ڋjv؊$#?O Jf%vwm;pJhUø1A`e_Z76& JQD,aneaHS>[ξúfsg{ڹޥ34 1v?-<בu`ǹM[?EK yPVIE- _eY^'_ܴ=\cq+T3H `CWm㤭?u 6Q}%3yU ыt甼'xQƒxeߓuY C(Gdux!P_dՃ煮@9pcAK웬vD0'EM?S En 8 L'*5%CW;|ڙ [Ҡ/TD~U-[|2YssYe)KpFBZ0aB˒AI*+א'X\t@V[[ 7 f MP1yE9EEEQC|aa$zFlx`*v{:S:JsJuFr[gk APJPzș[O=d6/Z!ـRDmᤃv[Y~to.cdM.A$%r3JK8=5t*w@[m wt83 zDŷl1"iRWIY4/؅joMKԑ1.3#c8v8f-VF0 i3kQI=M+",/%S ӪmC @%$슣m^~30CIP`հ'N|+A\ KaIxBd#+4qZꚉE֡'N LX1S{L )Qqo^pp  Q-\oc&4F'5C -/kN3fM|=FL͢"s@@"DR-p #y9KӁi!i@`,!N(趈Q 4@0F^ͨ01~q?2H˺!c' Y!^*l2 F;];+~:cyOS`Z! ~\JC}'6+χSڏ~ˍn6k3Zď/aUeQҾ>z6?^yRQ& h $zb&c2\4mY| '$WδⅯS3a:j0fi*FŇ fTCNT1n+m%GvWVF*"99Z kS4iI>jlN;Ѯsr)7 A1gq^%) >`,T*i0ѷpXY$LM@Δx e JTbmÞFw[1Y35Q#м7}n}#F4:JMDM`ǝDHU;nN6PLET=i[t9^\dGOK.x#{ sѨ-@ˬ;i{e<ʳq!<V;[)D 0JJ׌vj /Hk{̜qkH6x.tɟ@j}*3WR* [mwo42 2{UM%OG ' @i ,E,ьλpZgR$5ݒǼ@gML!RS\_j栺?!o8#!xT* YZVI5hJL$ IPsDM JWb(I 9ڦYʜl3wHҒ-{ giD#$I)"Zc 'Сfl=FWCF@,g|c 0QA"< f'T3$Azcyk&]n[hBO )@k)C1':s0 D> woW%qEqE z_W\;  +[ʿxkH~:9E┍l'\&Ok}v˵{D:ibI'{áӬ.s&=%YLR8ϩ+I6gԼ7!:k"@2V7P˘a",ʖ)XoS=n>ܸ6EHs< ,hlKkCݛFط"oQ5 j"wyg&2\gm*h&ER`s9$hzhiHބI`@ވ97zJp{EX0?D ܺ~5J8ʄ??nn`1khƐKZ[6S.keB r%d`H)5Č}mÈt|L[{eC9Ñ~KDž;%pb=X*xƳY~ۼr, BzP ACW?aVSxv a LBNe0#B hə´`8Q8RiHMV93Ȏ (D ш5u򚕕+MRI6] Sَ{Q!5V!>sb66npDk@ŐiD$!"zm}! R$@$.%c&N#R0D>՟t6l*1ӭٚb#EGJybba3 c}a  x aW4ɦ5J{xRz~dg$u5(%bR\)1s0`u&^.er˰# ![%4G^317TN4skB$xy^{~rr%6n $J2& (\uR*[+轟QI [ g'軟iduV7@7 o@Ysdm,;֍6#>śtnx_G9 qJ<6UH]4ٜtZ|%}펷Gښ"o:UͲzOT*k{?xuG)mꋰ)h2t=+nD Rh$ CRkCU\-&b5X߂^u4R2D8FP LC{„85qOzL 6X6H! eE٫2Y gVZp@H20\V0Қ#_"tk>Y&(2䔰5ݽ;t`Yeؠdav/:37 Lg?P"ƬrWp0UȢ@`l$ d E ȘϢ{aB{EfMT`/04@id,QC-( 1 @hkc}L6, 6)y&sd[cgPGTP0/g}F(V{97[T-~LE9=須Cn w6ꋧ6[LjAQnh9Z.5ա!e BQ[SR}fIZjolk* B7Du3:7RunT @qVVt #苤Ya& G w8m.߇#gsHeHYjdf|Z$TZj?γo^KW;M۸ٿu{ÛّXoMroM,@uPEó[Z\?/|SmhL01k+FTU3Q!Xcm̖ީ+uc}dz:!RbedsmĚ֬bF龏0zc˼}qd8o<@OǭA.EL@Xm}j7g(`>AWh9iq 1UZg ꤢq=Foj}P$hc=-s.="dEUJ`5+K =^P7%ӮM`A;C۬8u VĜ~1UcPV$ y`#@wn[*-Mg4J2UZh0-_&n_[-W wWh cą-Fϊ#{KfgR\ @Q jTj`Z^ *t+b [.D3 Sb"V}Y쐴$#pJQs_V' EK^@5,J'4OvtktKTOKUSD|au2>*P 程9tlhmBIjB428h]ړgc~cVT4RߎVSZ_C{uk "Ͽ#BR /uZa!6,&Ԡ:jRns0L-dY=uDRVb0j +H` "  eHA8CtKq " D_TTt4gzVH97*S%aZu+8-Ijv^itprc!HcO%ϜǺ̹M(/2?21! !Rngұ| ;#5U1Bj3YkW޹=<-}smgnq.-n~S-K#OV78vSOo9,ne 5 rhToq&)Vmbx EˬL^]~##bG*(uUDE5laIpEɨ aD[fLh8l+Ys$ckUY5@52+ec/Ձ_/U? g K7yo@JQqparNrCdl^pgt{H٢VwD.Ӷ/ Ik@x$&DOQ1yd;pP  !@m#2 Fi@LKALγ7I?S|hS/"^" e.ʫPZJP0֨NiRY)O qwi9C zB7$ÿkZA^w爂'u_&$@k@A[NcɫCuJ`}BPG o".zr o N3ʗضx'AZ$ b&LS3 "noG % 5 @X$Ӕaıĕ,s=50aXY0I' 1P亁xh] rAU#C3$#wvp7  I4jT%qYN0*6V"g B &d$ɐM/m!QQxhbQti C ,Mɘu]o} 5#,y!^|?-^@QCDl"1YU(q *oT>"[Ksѯ2GGn7EQcRW֍Ѝa?:%@( _d_.&tq[M߷gS u ;}cZj)I*6dh6Rqnu\J%^Ъ[V0naSvT ?KȈLIOن1$e$ٳ0 QH%qp5#dt tR1s("|Q!BT'u\Hq'tF|LWq?][XT]%ͭ I=E&&;D3g&ҬO>X3LƆ^UeާyPP&f  &e;ly\c՛{i[.m7eȧ 91ӊ:GzׁˠB{n:~Q^:7ʅ/nuYGy8Hq}:wrׇxx0Yb-`Li@ p޽9f`0F7ʊM%_i`نL.G*@D9:GB@rhRs.Ly,PvBE*ILQ򡣑߂A0Ƒ9 ks@#/Uˤg%('r6؎laDQ|"oyM76y['B.۔  4T1QB"&}Xa- k2dB`7y BnSt3K䜏;Ic C;̠$5 þvPiqg9혆Oq59VCj=E ʋ],V`}:1)r$LMj= mREEWk_3~w$I iyH#;FcZLO{:X(sny7w;vQFE]Q%[YA DfhN 1**PRGN `Uu2K@HZ JiLOT _74?\R*dt@O%ijswrg%Y>O!{2!tz`AX1Ɉi!0WI.-禟^BD%~6%Gjo[a0U&k-)D; O8hm]Z^Pz\[TQ$88 `TЂ,M4[40V@04>*bZD_y:&9Wsޅ%wwGD6M ޏq_yLW(N/o& ʄ!Ubk{DF:8}׈E;b` 1;*S'+jֈ7UĒm/|jCTc.j>K_):BcjzŻ*i ={>%2bk{"75, d ujMI}RгhUYAa}ujߴ$CPv8V&P KݢeD̕eP`G 8K%ԬZՠ^)V|EydGnU+Q-Fȵ"07C15X27̺XMKH5hnJ$eohъB7`BbxfI|1T{3n k,/$G/S(Z5,@BA(QosRXRrB-XLAEKG>&֯& > &tKI1I30lMrf7S2a.XK %K]F]hIW(V_N2$40g PG-"NUDa &%gr0o|s|eH1<Ȯ)5h71[jlT%3T0ӄ [RuKD7bK4%D%V7!`ڑdS$QR +LW#H2u1fi1109m6BC'3SoF{ᭆ]'4xmGďxl$,5M`~?5-IԚiΌ|'w?-dR Ē"A?R/$ˍb} %mO_u7l %B쎅H#nQJ؎@sBp5F,Y)H,XTyf ֊ P*zĢlvǔ+~zJS1H֙7*+1ʂ3dEzgVO!fJ)IMfDT&t)*x6N{n{tx+>j! o[̻fз7o}i] =:Nc*Ԭ19*o]Y(BQ_K/Spo(I?6{vn;~t/}nXq ;:u,O/Μfg$Ni@Ozq[\ta*#1w:O!)Ɏ#<ގ#B*Ba,A" 1! M i@B^Rdp p&ʤdq޲MϞ34a1da^L4D%0 $5'Uu5!<Tu*<]f#u.+cQM@[B ^pyE6[ӔQ9>R(;7jJ)m IJ`lc>ɂnK DH9x?9)PN?_6xeOTd#AvO-g.ADHPCt9I#OkXB1"Ӕ$yV4z;̬XȒb=Uюu'~9at.*vOOo >*w8nd@I+IJ-[ ۦN[6@N Z@I,94)T˿cKnHK@MI$)7UEJߢ[fچ.@-֊`;,6Ƹ 3)ґ旅 hK"SԄRF}}N ic- KfxxNPJm#˴6DPX|L|$SHB t}1@OD;k^#=/@`" =Di^ ~NkBaIaǤ5[> W?ƿ#B#tBd\?Z\?Z3H8} }. 'y~=׭ّ0ٻqbT,ul[wŒ:zꦱ>2Y%^-%;O %4Y{6yn7K炜ۿJrZIy;N|oY9:+YwtB '=ӐXfX2/'vZ3 M.j%" bu =Ebi!>;UBr,e)gL`OD9ER\ʔ,[hRӚbTYI1G:MO&>ٟWp0#ifF}uF2LLΖaog~ ?-5J=Gw+γ?z WfZ,X/ALtt&lW#G3Ǯe^d1GF2˾q1 A)fbt;qNE|kRL(98H_AIh$t/I(5-$8H@&u-}msݓi˪UOmm VGP]MNT2 $- ݂4 l_ @ QS01gd:a8L"bDT duSEn^e:G2®9`mMd>6i& םhNݜPSD1T :*&UeLs[0 QPҚGsȊ @V%dc/ wwvT9,uZ 5qQ ^yRS;>+o8KbCv*jq yս `0CtLSt[udXYr0['&$IG$PjKMLS. :D]9qjrgeEf?':) +Ξ*; rFNnbKE $@6qd ڒ晘eSOr#rmÐ0lT泇t{&w3r`Q3:Lh7 5s/lk?5}k /`f0ӣr H6#{+(QyNRpJDo ){`0M3"M3;$#N2PȱCW)8QxHd2Nyd? /B͹_ԟ +5 /8š?au,%7_D"4 7~L:v??K z) M @`"D78/px˖.>m_c:p# ߗ?0~l8" \<@s_dY6β`8g$-~EIvgne ~ f2 tv^´V DE:ou슯M(„c!z۸nzO 3)+ 䖝U0 ZC?SB&w|otrH]i_"_Q ;A F3%`!FEg: `714hW9Bm %_R9Q[ xLч*0Dr4ogpU`Nm1Oz f"6ik $V`vhlwRW 59)&xuA ] %q +yՂ++q2HYwIB~܂669Y_RQtiwfI1 k?l}5.is+*FK,G>/eTD#+yC7x_|83=!JN'8K4G%+-G.E[²l =>g9XnRMBe!-g|S.ㆎN"f2x&4=jgßkse t=De31`J B> sΏl9:<i@xVj)-#80VA S|%"zP`` /ԥ&z%:*:@CUwьQC$5lQDI4x?+Jiuo*MĦbOa.64/%<{JWMP9L *Tt+Ac)YH&YPkNϼGΑ~r+z4>7gD`.d 9k :+WH ,)d%)do,>*o%nțOL,eDO4FX@ab/wXJA^3GQ!/iQ2ZjFyA tQz6 -Y=fz)B#'o3gw\`r6չZ4N%P ;&F?Jbh.RҊl1N݇&/Å.{f"٧sWe0J8bb,hN2qa$;ƗJG?Z?b#(u.l"'aJAz6W!whT//Qct_[wXJ^2$I렛?}$ĴZxDBH,#i^#HHi| th _[?:PiZtȔi&Zlԕ塞niKg#RdrH]^\6ǿGLbm6qu RёaGIGK[ A %VsKRgN˖BD5Nį%5@SA3FؑjR7[BFC/;oC5!ǒ %R^yW;L$%ƺ)1aJJtjIA 8̇?sgh !'dn^LJI+H(_OZ&A3b}j?Bj-?V=GWWB&:0]^1( ĖQ@AReI -3:D7Xkv&=@Z6Gw oܑv܋/eټpD"fC TS8ڝ*1䜊ߟ7jqbIu>~!bΐLuS4^U-jX8`C fw{k]ukMF)X 1WKXcQZ n^n͗ONۉ;}a Bƅw6 rBu6 3nUW4Drj"Ut )hP,lO R>ۛRaMt28kIϖ3#` Y2\Zp ת$(McP 3ҹb rks'T`9Fcx@d>zw|xKi>1O) eEMN4aZ9ΫRmxY(os-!!\g-m)Jn}OL]aB"~QQA*'!K;쯄۠*픢 P/Jmn7$Қu-|whQ؍f8iAFЃ2m:$1AM[\(m霆/nʊ4%HT\FVQ EU5I8vd/=XD }!I^*E:.^K&T8<L5<QD /|aeǔ>ev"܏ŧ$_T@ Ͳ,:#Q gL9;J࿼|NkaBQϤ&Gw"} V Ӑaa$R }-d1?xMxڰ.J{  B@ xj>RF@0gEI֯_ mbsݍ-%$ %>l{Úh؅Ei9|%ls*I҂0tTLoh#WBUhʫ"fnTw3N"bsg~Q|0ѰIlFFmpf ޤ;BJbF"2GĔp$F"990Z4IGl`.yг?D!G I."=PfY0͔l)B󖸪Eּ&f‘YHp)Fk҈8{ w1@"n*r f*3(T`C!S* sB'40/FPzEdWQҡY-5QLF~'Ik J`ey">nNV 5Aq#DeQ !d42-0EX*3OhΘg`*Y i.%DIjjRS\$ 8W7:3U뵈(fY~S#W^  krcQjMڎ@UJ'vu$T31-{qunE-7l)i:IFJD4ֵ$Ց+v/ySRcY0I9`|vۜ̈́UZ2.QUU~y. KLM B}a{^@tSON}7Ǽr~(r;E< >D$r lZ P}gnjh㕟B_+'7;ZtAUz-G],S,, k ] ST P k/BnPsC(\4toq~^*jT3lڢrID &`Wa1AHYHLe/uj=KOҙghZmeb$|Zʹn]= R\Ob]_~#$B)[Z4IC~׭}E ZD$a%g}WsxNBygkjhD9*ń#*NaJ5l3l4myά1|]匱utO7zptIن+"S]wCntYQB,f>RHZ3~u#M)NroM ˭D1%- UkP]&{Ѹ Mg1ݭ# T('PEE}  cXS;Dd]#_>`\UZ` SF'55ʻtz{|ڞ}1wJ 0Q6HёZhhϔ;/S{QF௢*pç%&z,GL v p"gUH5+CP ]cu=阆Wy,'ܢdXXa"Jk .,ٙh }$(6 *@TUB?t샋'pV&,~AoqԋG X^Qm()=vpv'INґwsW=FY 6y+h8S^ F `'[&[7NGHo7]?g$ H"#Aa!ASjڳ*B´0AR-O$=i[5r<$8IA]Q *0PA@ 5P+#@TR~ߘ%I|h*1 (@D9Ja L>1~QSнseE )bDk/A\k3_' }AIP  RSU&I~cV*Ak$F>%%u"l-&_pR9}Bsuw&YU 5+:vBLY#~Ǎh:GJjŪxK;üUz>+"%@$F53o$Gq ?0WA@ HH+&#C ɸ9 \ rT1`=UcNtF-1ʙxƕ=XOf?Tl e#Ҏds:JQ=0,D|l-SZ`ŖZodVK:1m"0sMqCQƢHX5jM&oD+ jWcAiYchH`?35>߮^}{6h][K' 0jS?mO陋@Iqlw]LM̄0^pIM/)r$d׍84Y[{#??h>#x $b}t鋂- u ŻI,6~nBMOT hHW".^xOQ.ܩ<_;\P9ډSjъX=m zR _>SQ`3R{>:ȹ<hݡ KbwKjQ_1K?W;I>'9Xn 7GV1QƑ+)$ϰ\D;bcLsJ D8K7_tv'OMz`BN3M)جoB)%ZhLvF3?ms'=IESkpFJY#ݖ99 cq 5:/i 3Z=Ƴ:N>?јjYPE^ D`~V;q`$ a ΅+Q+ȣڨV1x$;nmiQj`P`a@@XhV!K6%1k[Mz2++):8a_B+ϊ_r(x[e?qC P.Zmz¯OCa}/mvM =PcУXעG(5MQS}{ֳx7s ٪=ѳ/[&O̺ջ5U!d&" B0ȼѿ:yy~Z}NkB;PDj7_BX!}Vv)y5}tk0r^!P/ mq 0`Dr"qOK}(8q#A5?5 ~RbjV++^ L(RsRM} |HW~ͤhT3aX|%'Ü p L9 wVFP0jcU +RHo+)c'C,@`K1ۮ\e]лv͎[= Dţ^P循OaB ^~,߹LGQCjӰim4zMً/ /2gjԙq3vYu<Nc,Yb~G!j 6꽧*Iid !Ͼah݁Ԭ,mxE"$"+\T1`fgJ0&"G\#ߚ ]h7:1 UpUy#ംzIfqo-4e }I 1*3NY0KwqoU#8'zWdU[(S5W2@Xͥpz՗o24tkyŅ笊 pX7U^Pɓ LpEjK14]˿ G/$y< 4| ږ [ K,MԺśt>03p [c PT*BF  'RHrrnti GSp#(Qch~xcSDCcs`ao(q.dK6l1){%~}o!D\|D>u8G"6-=KzBqw2FNX4#di鷵&>Z oS>(=De 5'cXw*39R;FVTQ [򚙽U*FWRcbKP@Dz x m7T҈5Sg=G'*\ v(i Vc`ixqjۦI%|`E|Tk^+3F_JJsQ<* l86 (H% <^3s9k Jˡ,ڈ s7irٍhP%sn:iō;^8&URX qL2~3Ϲ?*v4!Z%LlγxaS?$4҉H/ßSPt2O/&^~h9SdpXTQolȮmH KޓS|>xPresxfYdJyz_HWJ͵hIǒ!릧&2[D T+Oʚ0HZ/(BrS&KKSDO| )O +BDi]++/Aâm*fdƒDhLuX:=FyQ?1y9(SVCU(s,Q߷N֓1b'%bJq0.2a e0h J$1%3^Ԛ_5W8t5)ͨ 0 *c$22RqmhȜ1|KSIV^?^Oպp[g(ad_6([c%HT% X7=RI?q)w^M!¬-rF!rvP?Jz>g zUܪ  Ta"J`fsZS+may?|V( EG1[U!V4u}C&!Q>|H.]o;HȒAS%IY`@8p:g!  NJQ@V j;V'=5?b{aNak2}}Jfz'hmumMyu3szoOpԑ*6tOyڑUSmҍeGY=g@fgoJBE?oG {E5ĸ z줖_G?Y`*k軄/u?)mE* hsХ} L b6ME.*0޻|89 ˘4OeE:xt!2@nLDT~sY6{ :Iw@*㼓U3NV~x%e̍c'g[Mm{?xޓ.dsrTCAr=[{r $Zveִ\+"\z  (j0P{#'8p$tWo|fsP/w$R0̈́iT KMfd҇n-]Tҥk}fpxyt{3%gBL)*VXYQ\bu)N>Lڑ@ R-X-0v,U;Ԍf&,%d8BWЌ |$Ab)e:eBJnTdOՏwO"]ga 9]H&DHm0U~oKeE2Z2 !ybiK)|q4ZŽDWܤC/ R"AV#E, Zw,XNkCdWf+ i?GeBhNN!gmFs(5%X aHHYҒiEÅ@c>[Dn6_VfL!jJTf>`@̖!cH#/E}Ah詼~r ~W?VGHڸ-$Y~ĮiIwE1R{x^R'UgSG{&:&h F$]2xRTēCqI?hkh"=ɜ[?p,?nO &B/wE jyi(cbTS&J5392vsBϸp".:=&jM<Zm tYQ QM|nr'X_,o9Ի ?b$ kg{S{gC({BdNNX 95>;6:F c$MC:d$-DTuo[eia!%E`-&L"J:)©fQ'ͧףk+.Zpk+ ʚFB4%BҲ~n}bՏg%/299ABΠGұ@uL=)!=8d3>2G2a@&THУR@7oJA/JekNXiL+s!/A7Ѐ&@1\ G`lV(U{bI up8壆GE[wGח.G􅍫Xj ',&Qz/E.2UWw=+t.֙j=c7 ;t$  ieBe'7NʾpZ=1(tӟ΃dC]5X"8 ƀȂ05S1k“^;vH>w0+׏Ttrr18-u`0 @SS*o0]F_raaӒ7pTd.Ta"JWOha*ik|zT-ϝ۷cwD&r%Nm R&vA5bрOT1ۛ?@ȖCyvG8KLѥw㉥el266EqՋEo`Xr1Juu1t>U>Zes""xdI,Wi4|͞K@P#*;Wf۷WR:銋)1'\m.&9R!J63jȰ&Y>sQ&zx5Գi:edFjnń3{>Gby7t Ĭlx/]3fo8 5spsvHr0)$#&t{?ϗf퟿5QqBFU?Vk>Twuvp y˵r}3y|$.E`U :(筈q030VH`Ѝ&W8A1?V5t]nM[dh+b%3X,{{~p1?sf8$ۿ/@" D"b D^ՄK'яw(#9RTɢ'G#2Qs5L)Kk}Y%w*x0]{?|^?>濿O%=CG1VUF$Fޠ"]gN?7Lc|^`~O B&Bc" sDydx (, +`Z喉=H1%AU +L5zt'M_Ŗ;Ÿl)? qy8RJIfDy&֠+Z/ŏgct?{,y1zPp>FPLXQ^WΜ Q&B0LLB0;sՐw|mwlݿء'Q(P4{|B"$kI[޿q;4$PuEoΗ?&&WpAs gخ'2]c>(G#+4HtM oF٭`vK S+w e\t>q/D&\ )wx:0rӗnH>fv=^%G䆤KqX@&`Ӗ7ngmgƔfGlچOΑ.f1|USӤXMkm.> 6 VIUOsܭ4o' JrZK=0k"Ui UUI@"Q.% C 5?jm_8sO M Ӵ~hmoSi[Q͕( 3(gҥRŽo%lSK!42wr7ݼw6I^1CT3fwW=)1LzE{U5YE EK#/Kth<ȃ~sVkLRe)LG@+|򗐲X _}cW|ylaσ8 T @A&H! Ėtvk]3Y$/_^a )@Stݸo'6?Bx'aai9(L#=w}1MGG*ndVV 2LDIAoe|aYo6I#&\'p xh 4$qUؚOЍ[ei}>V3") 258[NEsNjhX~dZLT'ӶUyAKsSu(>C?L= 3NCr5d#ֳ |eRd*@ /(!}s(E_~zqoD hgx6ΡH`'7O2\N\~>Kzj̪|V#-M<%AEU3ùpY(K-fܹ})kߟE7::PѬ 1XNJ?upX2@̢@n#uN{sIҭ^ ʣt饿_X-HFWKɳ7StM=Ot"n-%tl0c@ٻRE&$4P6)7x4[<H]䮿~qp{ <&gs[_h-[uFp;T"5./>[ދwzvXs%RN7%0l8j"kuS\Xv W.r_D$ꌍ@+ʴ"̨xn"on4O]%=7hwn./<5^78Q娉rĈxM$; ע=w~۟]kO>z;TxgF?L|<aP6?>8F|ZpԄȀ )ƲKfaf:C?n]%xvf?]l"zg2G%dݾb0àRȡ"bj~0;"v篞n, Ѓ C!䑋?^s1_#P{ʈFhE\WONrXmdE=ؽ_>XB2a?ՏJє:D )AnOJ툈BFP*ӿyC>XHsjG ,$XW?toI_o7JLAzc։3U1eO8'mf$ah{b$2H_or# մdHzLHt lw-w߹@U߾mq\]u5r5-CB])؄gpއt E4xz?')`9ܡ#>4ifۛ1.v VMgNDJJ3!h`$ߢwo| ܡjaB.#~g>c?u֗GjḋMhAk')@ EoF4)̵ & !xkMn^fT4 ߅Q4v1vFx姁eDe}܇f.WG3?=߷ ϶TH##;djvԓ|eF"d}I<:7cPwNׂ+xopv}F !H>툽p>$YX˟[(xj{⟚CZ?AH fџoH5CS;W_;Axݿj؞z1K>?r]ۮ%J,6M6wz:#n>b~3mz뫣#7#FH__jd &ddPr{g6sI:$VL^ݜYwOLNv|&SFԞ ag1&vx(ؓo?br "ӫ&?_FR4~XᮗYc4eSFl': ~;i-r/"I[=ۖߤCؓ>4_w;(7cvL{\%@c9*jnMOs7yŦ_]L\:DchfC37yņW@MbS?u1~?Uc&|]:iK}Ui}q_/{|k%&>~Ϟ g 7~$L 2I.81yt?uy2i3!'w QR{QL>C@!Z5̨S 40cu |p3jyLE uG1m#qE=f̤=^Py>67ّSwO ;SfON+:$'yC*g)ӡblߗKoS Þ2y~h^ O :t8EIT[/{v` 8}_ٜO.wtŁy ݉mq$,o^F+SٓnWwǑЀ.~ڶ?|!b/]՟X $}E$= NO՛E|{o6'={GҮM%pÛFI|M- tqNפGʻ>Fs#_06#&@ aVm y UOÓ'C1S 7} C*0TONuq>Pvd0hE6; 7:֭4eH^1bG>;g7>T'zZ @ur9%{o2 W5oa_] z޷yt^kW+v+J!/!]fi9+!Z)S^ӵձ(oqТK!:۵kzpc4D޾bFZkvk9)Yr#Rc><<}qjZްmxwpHEƵ(o!.qDCs2} HDY֦B0)^}^&0]3ZUjRu@u_^r*K5e؋,@[^;ٚNz|YM͊RzNm0ijJfOBCyTmM'{ nrM,ͮkD>ӂ6YOX‡ۚN>^K*FTY5A\~L,t[spSrB`W#[^Ky |L>wh຿~ Gh`r*4d 5)V6U=d1Sxj??u)߃A>?ϽNWqW!qW8Bߌ!b VUHڋd>Xس"h;d)ɤm^᳁/7?*̚$.wՅ}e@ǟ0ngԚ6^U YV \Ԕw n* Yj`>L&!+vE*7ʺ7gꦌDI`w"Co MYD1cv"Iz WdBL{xqoq=R6ud+Tt`Nqխd|D紙Kg=!iCBE'€ѩZ˫ؗ>-jlGɂ2hkdytep8ݾjƵ$ihp,%ct^!I45j`tz29ĽԤx/IE[D$"04BIL o2xNt٩6ALԈpgRS ޡ{-NW,z@j;Zq_&* n~zz^]b]3h~e]Y%`E*hrGÃz@-&R. ]p ޼v^s;ul[ )~p\ݚVאi$CbA`ƾI'|=qfEN\"SIk',XN}7 n='kBw(и۰p%=҃#!~4>ݗw<+ZQk;}WXdtong^sʹ48[re5M1G)ZƃY}OwϤjocxg?˕UbSIL"PL ꡫ7?Lw"| myA!0Z4N%eǧ)e~U9{ٍ?_='f+RzҔޟpi t2d185SIW493THi<{nee=|GQx ꙽?iMa:1[Llq i!O ,PHVAO @_IA7u0~8,p3 4Ԁ!Gf[6 UjPgf;(,B"p,0VyXw*$ ҅n˨K4Ȕ1`,HCVd\ӂ;g&P:dA"-8$g20 g/@+GusKBcAЍ^we0l| 4E#П*gӽtkV= q$:h HN z H2 iz&;ϫA=Sksu${Ӱ T%ጇvo&I7 PF1l39Xs3&o0 ZudnFR4"Iݺ^N` =ssޤ 2@P@ 34/,h-W|j58 81 i#3a d bXђ6bX|H%ck2W=߫%'`3PL]4@1)|7wd{Aaڦx6$QRZ2Iɢu>muV.kvuh/* (pcp(ɁF|x*>\@;x>QJ&p{g9o^Wlx=U& ھ[t[zu}>>2Al~x\ ޫ90t~;I4c8p+ XXD 5z͗VsJƸq&Xn#>!.? 4{~4\t6A y.%dC P78T,RK{"LjjjFȗ >"SbPD[E0dA-8i%-UJ&MsX^X04zC[( ! (kCŸr!drNŽyu'%]]\2?np/⩦qucOݸžBb1 Dtcv[W闎o,ueBXkݹaS59dʳ}:fKyď4MbȲ:6,'b}yfOV{ Q_-~\u\,sj11 3n p!ّ̈:?[9rb! ØXEti3 DI1& 輿;.F=3A@M@cAck t""jdbm#g'?]@Z - CGRPŎ j#'h|[AǼ1 S֖wc:"s 1H[ <}rGN~c֥kɰi$@6rk R0? 6fh JQXf+ jURK%44n%:be3BMtt0EJZPBUX٨Gc~R!.$Ka"=*c^|X338 V66,l R?G~3_(}!e98q[yЮ z; 7vm i<1li<0B|s} IGہ<@Qc~Z8zTCE&0i:(<Wԛɛ3>gajGwk;%Qo c b\%goHgYv8YwwwMןw$Eѿ݂<~4nI]r& !DE:MǷɛWgYb~'F'c{Oտ#oOhz`oΨ8,f$(DB!Kٙ?+︣<8U#r}Ifg̑UCQ ZsȟF.}=ZN;, $K_XDH $LH@PLŌ.tNw%5DK-vqs ?KŠ}}S,+P7r*)B mԹj ф.jAҴ;9C3HquJ>A t"#|Kv؈f6:PIU*WK5aF9#wLQʬ^J9`)6xu֌5PbRJ:>xO\-pz;^93|3ߧ~ttSL'[)+O u[[fc{3nz}Li]h? I`Ƭ{۷  ӤnP&HAK{klP&uk&,[>އXp'F-BVbѸo@ IÅXpS_:I+bB:?9>לD_7arCnQ;أk6=MmSot}w^1B,!cs@ YhDfߜ~a^F*c/x?!HC_WXF F0bW'Q yjV`W7^MhT2 AA h siCRz'nT`嗯ZMA"p؈ԡq$$p'4k1vBTW~b}.$wժnWmx,!Vݰ'D%\Һ/}~XTRۉX  80kw15t11_3%Z9-YY6UxCC1TҒS *E1/:eH AL0$݆nd,f'Tb|Į\D1|zw5 !^fje u@LB"0@!d<n?7~4uG3{|:I{ٵsq%|07B|=g"=}:w{ݾ^;qN9ŻJ̍vw|֟4q:d3Koi%_qWyO7Tˉ_YuƧoT"5zYtG;=3R><\e<8-u76*ObC> 6#~U唅ߪUi5p8᪖;6ߨD*nw'H8J綥m-@kA螟~ND>cۿ屙1{ߦ)154!m~xqO"h45 4/ETuwf"..iܿD}ԝHܵ,&N/NCtYD DqA&m4DL鳢`8.a;}y`sJw ;b^ZL&ٱO?Xg8\;ɂdQH!EпdɡZZpp irIo'Lw5Qr3@1 8hqPÚa*fm [2"'ȕu̡Ҝ@H:⻶3gVOCSw(ͼ@0cWո_~uq ɭyZV~ HgCbb;N\S,foV="<>Zj2ZFHQPZ,.U  Uޙ΁TH3vTTo:5! x Ba @Sy?M< rW^=''6kl"F0 5"pgI~mSTRãjm+K-ftݞzםdGMT(E! x(' @ QJ%]kT?x}w]A;߷+^e!*m@{Bgwjjt]O}fM&ȷ+\ul zZe$eie:,<4Tf?}}NdߴPzo[wpGs߷x۪sy+Un]ӴŠOh]o4oa݉Ok#!{TncHM1]+r%aƻ x˺5(1w?drj5ع*bOVtw =|owx{7W[;ԍV?̾s7-MY6{= @sKrKb>VťɮIN;qWOl$d4-mO'<6{ݫRV'wg/5]NPd4i}7*uҖ恞krU[!uFw=ZŃWF UGxyӭY9m:4-uX}ͽ@`m +_VX7}q]Qr銇n?Dmh=Zvw8e H[5m;O溬1,bKwy UמFLvݝTx$O.2n.ZIB~M `7蹮c1I=YZxj8ZN ݚRLN?t]\>K'bQ}&~Ԥѷ#t&$a7V 2)&"҄nB{PcB13D-k 5~x !_uaos.'mq_pM;Sgg9a˽:{\xn;7Oq)n'v0KropNLY|Ɨ+=nWɛvbh2 r.C,g_?Ft?UPetčw%dop ?dƍ\gf,<` k Ѵη_(/?_4 _ᒬ٪Z%lاE~.G֘( E#YMb@<]];{j/NYu7|_h a c B0/.Lu 5HuO% )~KL,>Fbd(F@f $:IEE/w%;/zե~5=RI\WgCY0&.hIOdvjO|Z(PpO/W1yչ~?LsA&ߴ9c,zahn2n~+5[>+OJjtr6Zi^O}}"^lz4k?g*yO'&me }sJx-~Moi?6'v݅Q_C8!?Wcچ5LޤW'bTwe 5r\H&RagcdT]r\:Ru0ö}tm[m,{sm F, 6ܔNZʾZh]J6ޮkM853Y݋])1j0^7j^DU ަ)&Wͷ~{;8)XFVV)j Q; >?4t4hr&+SU~

ۉ.:賶#unIK=Y<ț6MjѽGgU.]^OxֻzVpIcQ6΋䅖޻wf߱I?^3_ߛJ-/KKz1}Kfsט=3}B^" ̙֟ΦOөx,n҉kҴ߲;eF6Uch D[۳oZZ]!K(']bS}wx&qo֤2^ujB |)I9ZV_REY)47YAAHjaDPOkGCINN@~J[[M.1MG ,B?)Mu<Żw]3.L"H~q1؃~H֜C]ODAKtS>F#E*t$Tla!r!,RA^MU@(5ˬ6f;T%#'Q @A`gwIkx AS}/Bג~ϾWɶ4tț"ȕ6 B Q'a f;j ~0aM9E-ՅW .r0El8nxd޾wX[ E3jl] O7뉴@HXExN<>u:'F/xZHx{Mn64AxB(!@d3K>pFw(ȄA+X=j+SM3DlH:֓w톙J]wJP4l339!˗vZNt4fV/Σ_/ܸyVjh{>lԈ4쪱f3I`@=leg˭^eq49ں GZ]ץ2 bI>wD=: Vg0soޮ$ȷN;/t18\#^1#7 C|̲nrA,y`?eh_ٽzNfД~{uRgOiA( '%nCvg@=ͺ\ޓƬ|Yd8 ypໃZ?ek-`dl=wh|z s{1vZ& 8lzfRH^`-nMo/.`QǓm(ɬϚ+ lP~TjD~U;/J#M\K~o$:U!=`%b0ÓV׿)JU~d Z*?CZB˫.SwS=gN>qqצ^)5gz9y?DwI 34& ?+!AtᦿZ: ;ǵ^*ֆP$ch%U×O.- [*B,Goʒ!d- 5KFϝ \TefĈ(񅣃(9J K3 @3Ok.$OZMD%j.FUYjf KJfju!u^r !-K4gzGhnP,3T"u@[ԅK\r܌1BU)K "KB&Rl8ϋ Y]+HK"`ST(`C~s#ptנuxͽ)oK=ioz i|rv# [~3mu}qG>EE8%Z i! ^E*!Z ";; KoE\i$L!{3FUt,YN5s&`H4!SC '|- ?MCyhLD^*ZLTz&RGM t}&X(vVw\sk#p z(N۟L{okmq< ` 2/,v,3Жdg1 Qb|,_};@Yf ͘A h̤b&pdpIoWU`DEV N᳃).a/Cun4t+.R wByJ$j%B@6DRyuNCPjpװi`hB)@LU!7rgM*2e:r+U%5tE94D|Q`J#`k!MaAaZ$ZA[z{{w+mhܲS4>@ Y$g L&d} ˉ;M}_k/Bk}P-Ͼ7}7׀~,Z*߳".nέ lXzxw ]-:̤e9}}۽nWͯ{Mr_5k>1xiJǦ晙:u)bcqe/_l ,.Д( ISK {D VnX:lVNT"UTu;}q>!d[IdfF I'ElzO ! T 4E&*NR[B ]r\E欕kcoJ1֦.pIr:j㕛;5dmpm!:bꄹ#Y{ZwTxP3UT2.1wi' ēKUpڢ=W3}ᯔ3nM vmfDQD)4-#圧߳Lr Q G gy(gA9cl:{LYڈhT{!A\QPLTw~mzx,nmOS"|v'EOiCE;pE +%䤆eԊVRED&'Wph`_yk*c\buv t ?kPYN ^B@0[DU vGYaxO7B&wE\ɣt +T*b3YDYT Eb:uJ>I!zaq?*$!2/UDm8/c0nh0HG˲vi!aT0Dל=R9A;K={-N+xDsg??_ymkl{Fogl{?[U$ UU pr6:JRy~6l"LV9Pq6rƁk#4 Oc{ZԹP^bEPu^{ %Ƀ 6f{_ƾ{oILmoƓHFiX֒|3qkտzG(ЬU)B{%- c[6Kb@X?N|rE7{V}~>p ,cat3.p~s FuWYLf~t^3noaT=xR9ŭ ;f$=0V2W.x;5-آo)Tcp[Ah "!,&EA\vݺ1fA!P|{W>U6hߗ%`9TtV>t.$*Z 2MJ&xD"'l z(@\H4u #8U<*;s}9XiL$]Ed M̅Z S;TO.;Lw=#c )da[`00)N57~bMG 2 4a$ip%a )cJNb_}|4\1ғoy|Ui0@{@UML|.Wgbz7Ǎ[I:_F'X7y\juvl$3͆^_]~ý3I7]L5VBٕu#l ʸ~ItHC#95]~_ӘI%|* ZKg+ ]q@{B֞ib?P]顕5ۼG꾃o9B03V"Q?Ro0ȅf|l{>LR5c2%RR6"R2sn;dy@T JD\/[F❿0\IF@n-dg=jrj, $oAvB]LO/^sEA^඼i*}V"ĀMLKtfH~-g57bڋ .el$ΜKؔe}C yylCD.9 %"H!˱QC EF%݂ձ} @[=XǐpSɗ&VS"7T_PXʛ,(ܿ!C64rx.n0"Wg\ *) H s"ty*t`;vd`̘ &zR&@$y%xӌHC9$! !gџNЭPK_aƆFR_T3oLeWqb?Q; F\U޸a_µ?tݖUir᧯ y'H` b@Qh [ @#heS-;f;`B~k, >°^IL jbE<"i8[K' Pou7_oT `Ǘ4gUk[kLr<'I-OV}Aת]^hIvÐRl'Rmxc1>*G2!bKA3no; j#]$)5Lr6zN)puU6-NҬ^_4o~@EU;s-c\-Լ==$\l (ElF~C*2r P6Jx* 5c0YFРtI_>TQ[]_梃MVp^# LZum9r5ͥ?#I8sJ1z⁆Ĺwk~HhYԱ]h 3h$hrěv Ak|ZY4AFpw9%MYK0 n%BL _b l+H6Cա֝o55"d`bDF]PF~D\ a+hPtb ږ2 ~zrD/KxdvQvHy}3&@KLn _/mn}dTXw?^6t 6,;+^|v!AD*vMwwXih~#+׶–Ea, Ν@eizld¹u?d,|q eKeʶ.Xs4xA, U 3W;QT5E멒\61iI ziF#bAKemXt`XNCx&$|}7"2/Ҧy\󴝨 PLk6=%J5~L]<|?Ef9Oi#ƕh_ʴEt-iHlF#YfV$<_d6 gC=3N;ۇηZd X&,Gæ])A1(egyeG~ 9#Cq>v>Pve 8AST&8D#wz|"#gah㴆El%Un*Z`BmH@4|>f9\|^PyѪ ;!WvSg(C+-^$dlטv;ArK,#J"E*4-;A҃mHCC@&kXU\]4 BHC"q\p. n&Ÿ~> FKtRuXZݦ 'QI&D"\6X0GY`Fz!2apPedc(uP#J1 \X 6˔dRLp0:a D6 mwUp IvO$ {@$ afD, S3x<> 9a5uݿx}o}:ѫ%1,XwsnAkÞfݖ>?EzW|q]~@ wO/y1JZFU[|a,GQaZ- v<9cja{yyk>.}Ϗ͋C-V,<8p %A|Α'J':seszǰcؤ??@\tr,{ĠĊR 8- GdmRu?-)iR~(+s08X8[cEPR 05Ԥ^D#YȥknCJ ),̌@P]4Z#aU5KL &*h@JxB8tZ@@Ba :jRt$^KUV&匣W#|W_ZzM<m6?tS5+-e4^-:wZbD L 7]ҷ,sC<2FStάzssfIRݞTr@Wh]$m2x)Y:\\ynע4*,ӱ 5}Kb|(F7 *jr/(A4W-;ԘRuWKHjDLSa_ HTL+N蹲(@*'U *c]W?K+ML$hu#I:+RP12P8$,k v9m&Q JwcT]EjӲWt$mR,RVƝX9.܉bEt7\"ժz/[ne:۾;Ʀ~.Rs1nU0> s$D rX9ZRg!|m8B>IڦzOY^ۆ[1[wg?ِwEHA5 IUkmZJTtRoef$=_AS]_≏囆,V,uB!Gp(O@NO6Ah=\A@ _ D8# 2섪+R.ă_鼅Bo̗^R∃0APV_a% ={8CW4:_9_rQ>KsJE5 qBe= PLmh-\HeAA1;Ch R<ىR$ LODkOJC`/!)p&ZEQъg)&Zl -@מԠİ,G!(URWUeK<'T<0__,۪_Zw;^xD@F_X PYm\WzQsuFZX 1Ӗ9z@)Ire;W[[^;Eջ=nM#|UN_Ic#AQk6+iN4$U"N ]&Hu.ߟF\LU7x;v~OgaDХ'{WX&FĕM+UWA8!TDZV&D"$T@Ȓ! zǵ,TM|u\w>_!ňN#*kF+sMSݿ!"ifL` t0`I2:{/ s,/_jA yBD1JB8:NJ+맍^إ77]%azOnWIW%=G]A@t%'mDF[Sbo'F][[s/װ@衖TS؝TUA0 Kͩ\9zF4i᜵>QnOa~6[_BكR)k'jLJRůN^s5g=mזbhb;] ۄ1Ik L%1wRt1Bוi=tQ&YofXLRE+(q! H2Bˠ5y2̣v84 9|ty]PҀRXA (Z`UūVvvFӟTz?LhɵPXJcMFE9Ԣ!X~IQz5`OI_no UrPoLZE30) wa1f@O#MFE4Èeғt(QCpŎ};ޮ\'vhEX=vd:Bi{/g,*^-J"k#xB@ 滀$ ,7zKix ~ =,EA云$qjjd=#Y*w/$"4$D#եfp60\ T ֕:6h($|~8W+"C](<59;wEfS0uAp.mIRY J-i{"Z0`mS‚Ҫ tjְK:18F-}kE#kΉ-x(Ʉ W\' ;a۶ vĔr}Ç{ooLy~{M xRD{ALlU  s  E7 G+j ܫ/}\g9bj5cwp~wZXy+1&eVY 'LWG GW@j,|$wqqDzo@LO>p@'9"00*!IeU>e "F[*q񸗜|\~Ĝa?9{Ӄmr?LG'dX(; *u !!+,6%f%+/`h)1u.<ރ^ǷmǦЀpGفV? $GU0nO@=T ] |_M՝ɞ}9X`WB+4(OփIFO\ɛeG|Ub34& $eH0:LA ~ˏ~A|+oS?rn&𴐵= d$LҐS}37_/$49cG6h/b}0(5eIސ)ihI)td[VM+&Q5S AhNhӟD$PޛjґF%[*I ; )Fk4 'nežGmѾ:p' ԭV^{oq]j4.`/`P`\n\tѸtxM0›W+}(o֌L*?rY@"Pakӈ̤Ѫ:רd8@evӗKV'ҳ$!ŶYne y5Np,NJ`aCb.%ްJlz#a\ aI 邥 έIi‰im"+pD M(! I1h5nAC}qBđ:G^zsh%d1W~$ Nsc.#Z+'U HYU$AgF.?5仐HU>rP7h1Z5udOx{xא;,B-@ f\F<T¥рP:UUF|,.Ky'"!lw]a}VuJPzYC $f3N[)w8Õ:<)J|ទEAl)RTTY6J,jbd U_cMV ?Y7[%mYLhY -ZX :BGϹnoi"6j (VJqlgfugcv-^Kt_zst8CaIE̸HJXq;;tOVrL]u&M'%o08S`BMBtw9j!<2SYnFNN?{Ʉj[2StB{*7 ݣDG&{M) ȮYG ~5R/-e~nlzYN"BY",B%$E210R|Hi8١^wVi-/j`9ϙG:EFb "͖ oY$qS&ؒbJo8iV[ّ^ ] %\Rp& FcxnWt|. PDэbb Z,_V46 &CiZP\GӘ't;(s 2pb,:eE.J B "[eeNCgOrz༼6$ePIB&9"g@)Z̀0@;ڴ[fn44H5ncgu,%  u2nʠzuw`T@{[ߚe9BJ]Z_r#Rc1H L_TE0?PqёO{X cFvpͶ1NH!ÚSURe-p9%VTL, -ř$A%&@C8qqd'D9Uodgh!F@Ak)ꁓVz1Rd1D,#(rPaB6WEwtr 6dzlSDWL% *2 gɔ [5bR")ba 1(xq=8@@ 0K rD@AʥH"­PrDC}xeD'C4@<0豗6,DT0A@cY]A[婞c7=g7Xz ?>'Vkz_MHa 5a[3Fx?W.=/ms/)3z =m,mJRБ ͞k.s'3KoeyOS&,S,n AҎQ4wV\/24mů)ϩSjcVr5xDP$ҎE`\swLRh-]o}9qJ4lC8d, H AI#@ `$^'!ϨTSU-Y7PzJxO"=@"H w  *UWsG=>U <}Hxᙅ.冡?=VGHe=O쀂RB 2@)=_ɣ獏2|RN5e@A 0fF3=9Ɵ}ddWS|+\E !e72zfͮI-’Y%Qڃ2 ?IO'Z'mBy6tB&IV/ -=# t-qHy!<k ᰧ9jNG%+WXϰ@5`~6Ǯj8GYBQ#1{(ܩҽ +0&.(36Ւ,6pP0"i~%Zں?y^Kh[CNC2d@349} jŅ @ @@gנG\u8 *6/@.QWEz 4@i=>XT[A,P{+Ӕnm[Xvh;ODo M;S!C&N%HVdі΂+ܙ2ϊB*'}5o)%+rAɌQ B"(&/0҄!Q(->K<@nN_E,VU(Rhns ~2D㥑дFXK>㟵4e"JٱAokW׫~Ňsq[kުn`X6tu ,AQ 왉Mn٤+dhEB%a㐙`6XlIg&-lzs%5'l91NA`Tb*2 δK051 \^C|hA2@kakf6@@,St n&'ݧ-:c%Q<88|dŶ‡.=U6jDb=֙$LN<= 8ZZD&g jWVhFMU29fh+[OĭDzE۳@8)J[ͮS0b[ Π;v~EUNk7cp[XaK&ƊQJmרu[(Y49CugC~Q\6ƪ&|&Ԡf;i/&2LJI6L!U}|U6 Z"&AE )~y+3[`zš@@0'1۪fh?=wtz@ 0;[d"p~`<7ɁC)"A R1zA]6@Y蔞PFdYk|S>1zx<{.2B>VG$Nߧ8y]sNxZjlio:Iߜ"{ǵՠO;QB[2D wJ&谁?KHDDŅM}'NJfMI?b(2iL񎯉<0j5ODhK+Z1F0sa{Ao{fvGo-o3v&AC9B+S̷q~j@m-61O"T総d?m[~9߸~4Mj0I21#UϪZ J!HQҐޣ}dg!{ԟkm#  SaFZޣ uVscP7us<_t<x0 6E +!Lx{.?>ßpRήz 7m}] Aҕ)`Hfz_?KY= ڐ*E jF>5ˁZ,3Q\|KY+.:^֫)jr@aYd-v?/e<w ?֑#=ik$ FiT3pY_"UA^?VbM XXɚEicf΋Rɷ0p15 GY@_Z@HVf,œ%wVlxu^Tp nӫIƓgo ޿"dM+F~.Y¨+UѠj:wl~,[jp'7|bHW^%ݾаreoL~'I[n^hpQFd+ qS'ܜ4wwE %kLʖJRV7ܚm Ω'By=[^J~dzғ-# 6DCQbnF7+Df%> NM@dR/4^xfz85f(-ޤ"BMg RŶڷŸ\`ak)Qhn+11f^;e&IZQs,8O:?CW=hxI1f犱4Jb9T'B,)æd} mg67bK( ]V,FlgeUV%iP:32KΌ%ٟl`~OzTb8fuijΤKŒVJQw793R%jVZE~o%sM7u/$bt7/.o [؅%6^9z&"Sˏ^w(֩MZ\gnmiutbp}T{62cHsPBo򣶖w= Te{J.LJL %QeI]Gnxozbx=c䨏N8@J9Ӡwܵ2IӞԟKUfCM&ޔ CiٓͭFʛYVYۚŔ v³0R2ԗ2ę#E_2ӆWLˌQӼ@}cȩ^I+C닒AsM 8)X/IےtaF`TF; P}/TޅQNAKvsP˂6vlf+>Mpe%;?vمڳ޺+,nc#NQ l3W"PUJ-K'WƤ:{lWO/pn|}kqK$ jkgVWH**Dj{v2co3IJQ o)QjM(+zjX fJ*XFF>zz:S@W*7>MŌO1Cذoώ.FTIbes%LSd?<*ݷO.vW{/x^/h h~lN[ si,v#m7*퇗*iPQ2392R}ۜQ6l{ڗ<.gx5"]A wpe:ݢg ҴGs]o2#͠lg9"D QQމf:0{3m7OGHkFu%e :D#<9,Mk37wg3lM aN0Lp_˃\EdSr?lIﱸQ` D# _.(wcu:R@6 h/8< 5^:2 |͕5NهlwgPQPaN[99l 2bA80"u46.8HĴ Swu^v}d*kЃJ9hO4I"PCpY"QqHWM^Ow P:jqN˥(J RڇC٭6G ɪKFuW>WҢw׋}}*KQl%K%2Nz$gM 0)H`(Q<+cQKQ'_A6? #xG#$z:" ׉MZ3LF'uI&{~տe8BȄuGCI`krKX(YCN՛߽= q͹~eO׻"74ۡ!q),YCä᷵ǽUGэa8ojkv?m;~To'(z~7OJCfϵ4< _ZeKW??kҷݪazxywo/Br_zzĂ{u hTí~rʟڿϝo5d#v?v1dG 8f|nZb)='?O[zv{'jlwLֈpsƒ~mc[D:ݠs>w]ֿzgu'11™LCL_cR +$nr'- %ߥ5K9a,'&ȩ^#S>*pJ'ﷵ=ztjsճlm|"h9F"S'?u mofyqLe!30m9~[[ [@OQ!<~/?IbF Hr#vAfg8< _z\`q.`رD XM0hP!9>q_)k/ =zEo^{w9g|:IV]醔@H$Sb=d9 o/D 3-ːl:QXdP@%ם#fg^7/Zz k0tb-6) p IhI[ A9CVbFJU$%h,\=D寵3 ͝CbNE8M0 UǾ V~>6pSOUD1d{SY!}L-Fiν[+Wk2R7@Lk:SYk~״I{G IP.8E[gZЮyO}SY ?; ?q nM I zWKL,p=ޡHC~-Q.hitqG&A\'秲HW}LJ2)bPOJ+(fErO=%;_1& .r_8BWdIyjpI@/EcGTKmli]lmool^NnA&5WG^@- aW9Hqi *h 뎧7Y+ߌĂLR`ޱ;rܾ*7%lMС5g?oh2_$%òh몞oxh\:" $;R Lx/ O?Pk2?{X-_8r9P#݃*K8} ?ǟ@+cJ%>;-nw}v?YKO$jŇTSE1ɄGZoU?[3x'lB!(5[43:+#l;}o?~e%Ha?TzP>AU[M\?Ex7,! a"*nw,9pc$z4ky: eZ 2ڐĎPFQe!LR{G)]GUо'tPtAGؿnJqs 054U˕X@$YNͫfo;KIr'knx"MS3 2:v?LJ3)NznZ[R %L,sr:[&ogj7.݆|~g "4i/W tSrC.AS t\E n7+׹w$gLdN C:PAiqg4D|.-Jq d""M`"0Hbc|nr*cXD-Vs[Cқ??{߀1| b{ SD`81S7W 7  *b'&gUU,Qepi{B""A VY(oRW޾~`j\q(Z/n^mzVpŞm XX XT޵ 5?ë\dv42r^*=5UP 6m|IanamP^C˾6K_U~\ a<E=axS('{TiFWs&|{o_~Տơ& ɧ<ïYyspL̜~kNq㵶kN@C&|_>_W},o̾qFbڜp'v]1jOB‹vc`bIrœFWM5Ν~&5lנûy#Gݛ&e;l?F@v -s&Euw)_1h"E3Hs=.Ji]=!Zl|}&;Hn֐]^uպUET;N3qPMsU}oƞJIg4K ;Lozܯ6dvzKuҍ[6ojL"Z4M־S䌤=#{bW@ԓt9#՛Wn GuϏZW,"fav65oo?9/{n[)Ѐ1]9e[7!sKy˨+ǯHGi2DSKN6#btّz|݄΢!@]o鿮F,")fa&sSorKZ):`g|7q$tUYDeD:brS۬}_~+|͇^pǝv( kԙa@ T%DG&Ox>u g '8 F( "$X@i: Le#!L@!M8A zfFAL ;;b{7ر9LiՙSQo6@h>aOu,_HmR g /e20 6YK{jR :ɵsEq4j\fceno1 Ak.4ms[NEz̷^8Q>O|O-pj#ZdK9q-|CbU/4,7@waiՁ1g'txqyWeoJ.-n~lUK@1A m+;mXxo陗FD']][q=Ljַ' g@0b(c*]`QeZj:]g5sNS k`*q7e7OYg@&kV vgFt˷bM eJ$D;ߟu?$p/N`:2I8q$@p+ν3z^MzH)+ng6cnR8"!uNUH1)DGrۊ•Zi9 b 5,$&-H͑oRE,Hd%]%,vhZ"rLҌҶgwν^SFrxkNܮѭZ5N1UrT 8ٌҮw;Ϳ>ȝviW6`_CC=]ـԧLQt: /2Щ]Oz+ Ξ6>]}{+[<./;A"hmMh^䁿g^ys@$dr֚`"w#sct0^-P7IkH}_mw~suw4-lt97;6_Uੰ,͝CwÇe~Ao./ k?Ew"s;mA 3оi^iO_~ \EB4gqo̘;J6b-[0/4e+(B"ޟ bg|pF `#*=Ը,RP~nTD! ^Foov'_?K>y'ƴ?jx9na;a@j$ @:V?YNF]g*lШQ5H#HP·|)[38uӋoхt>[%OLzƃ4LQ5S_z i3W]gh.1~*S@Pҳ(@-bō.gyD[Uk:5C}s7v>F:0 -y8QW]'ӈk(]X}ޗ}r؍iT{sBA ЈZv?T6pic}~աhȢ\mz?zvEce,J0h8]NRbmm)C`*0>sȾϩ_,|]>>w䝚ݽ}5VF)EFs0Kd5C8t=Ր'j|.E視|SuIoή祔 _W ,FVC|68]c4`h7w1n>U2':Q58/A@ޥa`%jKLޒzyDn$)U)ɏLQѩ7Q@L"Z EZæpLu8>c 䩝7!-wX'/_nqP#xÔZj /ZD={YUdEa)w'&BIȑ~A@Ɂ{-@{w>H`$;qr J!%D6yD Ƿ|<ȢBV刮W1X~դ`k.ZoR+EJh1, %H>zIOoOD;Ƞf [=փ vY g˫s{liI=]G)W=kڟ1X(`H(2ݲjjjv^Lcc>aotfV䰁 W)܁5Lqz+_F/?@Oq"m%FŮ/=dI,6PwYy ޘ{Ϯb\[3+i]grhwJkk ].75y{զx{{ch4b_SGi C{['i: -RmWrkAݻ5w 8őԹufOIQ흼[( $4[K5n8FT#%;F.5VwѯßlMr{;dܺ%nv쵰# vυ1]X_~uk:ҧ{5zx@=,<DKŨf q6F/#nMܰk,$5J:U[ZIP2߉'Cz2"0 qSEc|cqm{Q`0=] rfsm"f{=کMV~( 6X #,A5#"J K]~@{{]0dC@.;ZD}ܵ.>k.K;OT.Q>_s!vΞZ)$ܦ^eo>-TTif!.ۋuUB-sv}\eE%QTV`w"{*4PDJBjS{eVskj-sTI/`|^K b I.OCh`A%I^e^pN:_Qr`:n+" LtNLL)b ~[q]W? 8Dx0VVhV-PϬ4Oqq8aq%^G{o+$5èDHjd*)J«Iz3FWE[)(C(R; #MnV&WҸ_AlLso&:r&'<+ks#/R-x?|9G¶6t1,kvS_/J(%(w߮q1hldrΩyxxr ~,\__2rx`Ŧ< lDmH?fsWS  ?XO]8&4+qaxxuWO'A[J><_Kj>ieSsɠvd#{%wye1v s,^>~4d- X_@~Z * Olf^a& ;7)7X'{ۃ¨w#G87~4:ixK<L*,`ya81/te3?d7]ER~?W>Yk}VjAeTwFyUw˸!1mz>Y9DB\рaIJZ!QBx h]eְ!1 lR%aF( A+PgOi&mCX9[8j /{8q5"MPmy oRUN&2ºÅ6>z^EzNA tH1gmFXfkkBDqMz￧Uw%`$k Z[pQR84{v)]@]EeK?_n)#&y&1c{k)0q/N;{L1H |^G6iBo[giym6|o(u Xg39p[{g׭r{_)o^ͯψ%z 3s]ާO_Ξt/og D͋c,бWĸOu%>}b TDkIbOhPmVtѸ崂mGkx%,/)CKG0u˫ ~dv}x@`fj,2uʦW8<ȭگ4O^wkv\QaKڬ<}ʆlݞnÜm f*S+ ΚFVRs:9VF! P%&?{`z5STM١1 <2g\HCV/&YS!tWL 0uV3eTYM Gk9B0V\ҰAɉ6xjiJTnbMȸBǐZeMuZJoBc$'fc"0( 4ESپEƅ=HVB!k\h=Aƅ%9:A!/Ȥ6h?}}#?fLgt{ W~mi0 EfK<3kF5x|z!cBoQL2 O$}|)]*#SSE(A-zO$]yM7\;0H(G~bfq⩜(הwPڒ?NR*P#IgxOO$ ^5Q|吷_ ]Iu-=gw{?b#'}8}_!`3)LGzz%v(S&6?/75v?woE/[}:3 BVWv\H@(wRb);  .nlSa-t- GM8uB)!**w'XyF6fgk['vSr"e1!۵3wŀgfIQ \D~A6__Yɯq+]m:~_wнJ9Ǫ븜ݶfRR\ 1q̏~&DCnpIK,F y wp(!H ÅJgItr]~7:5 }+QƐ$5qBY&h:7rU8MvFHd @ lB,9X}_VOo?&NAl|mUsZB-L61 ;JvxybddȆɰ!|C_kuq{[h-Pk9 * 1&CU)U_ {SI(Փ[f]'yywvWUЏՠ*=05> 78ư4]/B&{|F&&jΒw,~ۛ;_]ؚvXJʛb~ҐMQ{1V}0N޺Na;Nw~`9c'Gu}RB6 "C45o/2=g[mU7zɢSڛ Bӗiu Xjza9>MN ߸g~4iS^obJ재m*Nz[M.{*/qvW4i1קDY.ַdVq帬wr!䈪,*L2z7l3;)g!^{v쿦SF ĐÎo_//SI&Mpqgߦo];2׫›}eNnAԥ!<%iV!L-Nt鞷Iև9*jLdN.}g"_]r|t f '-T_@CZdfRYxldRZXQ!HBj3L sPL1Lyg|g=h&v[ocW30m7_͏?Zg=RCHk g r/$DF⫁̳׈ވj$ z p.YV~ tThn։LNE߻Ym7y6+P CCV8dܔZm{$`IIsuHUH1Oz eݞJ0:4wkl ISI99ƍWNWj M|u3@)z)vh-H f']a;]k(_3v|{e5πFR1.&jFS_7w6u0R?Pf]D'u#8T*; A(b|Doymcm5Ew86cqZ?n6utmct ]l.U5~ڎft(!u.Νosm?n;wK-GSy.gGN Otx"'"N?7loiM=WyI.q͏ۛb$u_2fD#7]Ǟ_sbZ+->9H$ Qv"ǡh@Gtwq俏1l݋t)ȖO#3MFjR4c5?FU?msìͻn FHRKO;@U~F!i+S eB7..:* 0@܁D1c0p!z>BQ)܍AMR$D0O4P|02"H@'g@U 0@[ 7BU"E@B't"p!MEh0c,$@%s?=G_Xͦ۟ms*&6OSهzvvarӽ 48qR{GȕCOCzoD,6=tY5>qP86yx?Oa2->7{CPOyp`4#x*Kρ3\HK`tb< UeҢ M`y'>rȥTt``3S8D3WM|fMS,tg1bݼN4 :bzk>I @IkflO iҬSHe~*L 4 ь~#/ ƴvGA: $'|{yMN6+<93 ɥQ}~ouǫ/kho)$ mJp.Bh+Rmvk]=vY')Z}(wnes^1'F%S;ɞm]>N**7}k#բ* ~oٙF"n(E(B RH@J$yq HXB B(h,qRҪ2JQD)L$p丐kD2G!SoՁ-! f"rJ@J&:Džde& tjM@aH12"D SN`yBn&V͟>'p Πr2Xpn+9S#`4 ) 2iH0w'r\HmBTb)ŔpO&yjPB,Z꽷jo?%(^^L(Sɬ,j;}xK-Dܽ_zbK׿Pq{x3KB'7kk'Ŗ|IH{w PܒT*JbbiV䠍Ϯ7gn!|}n\܅w7Xy#*`W2-Q_DRSJGMEfNefm$ʍk d`LJF)TNIlG]FfN2I^A.^BX0%MUWj_B݈km'ZgNk/n_Y4`-{{_*1uM]N.H%lkc9|/n,1ĥDlw͎R:=qTx0pzTmɨݾxa30׃7F&adԌ-F59!24p2\K>ߵD){*&yC*b/=(8YI|k2}P[/iv[+ޝPp4&xBݗQ4UIk߫_;3hF6IXb:ۻUBk|r.oߒ\MW,¦AJllrᓉe] 2På$IQ±T3hgvAs]Thl@Jjj4u vW19Ȅ_ ? ֥BSKYhtz|r.,|~*5 Uן;iϽPw|Q.w(mǢ vz#(s#_/+ÑGQ{ŭ.٧?'C gOnRr>o=yH ܤ$[Wg?ϕ>TO/}Uw*O~T-=|_S.lO=Kz;Ah4ae<7Q%ҶVqt?2*;;uEtGyOtS}LۀXn;-_zSNtuv+t_~&/um嶻?Ӯ͓/}Dyw"Ēo{7OJ7HgGb;d_żf:N}*>~Ԇg,vQf^sGRK  PڰIX 9L7<[7ֿ̘EdGdEm)r@<&\ $a"v5GzpKA?x4!QgG3x$A(Zf·M)@ޚ43:_iD;W`& oxlp3M^5|(X $b&P0^wօW4A8")'B?[i DvH& F>RX?`?ߺLRf}h1y~mEûjBm>{Ohl6woSoxySyRw/WC۹N7Az/5vnW<'>(|ϣy.wW+Gowo|W|x?yF: v{g}AWG?[y^ғN'0p4HH:i_/cv󛻶ji%FUn4.o_N>ܼ=ϪҬWY*Q.M%(G/wvϣN\hԑ#=_ !:X_!E{%~鯭nIm=ع/ѽr-ޚ~φꦑ[S>,{7>.{HQ^(C6TMx/};#YCL6WҍmHTe0㒙lS3/wVJ_ oO} {_冈d+Poԇ0'W6 a(!Nt~iImyu%HG4q;SҰ%Op$UBbd@ޚDע{{r5; /krM7莯kc5f\E8.Nγ 1;]u}hxd2"}uϯ|{ \˞cMJG|g~?M7Ҹ}뷎),jb({ɰ֭w^rs8U_=VxӍnA>$D].0w&_u0Q8޺_;ݫy \YLؚo}]7t#! z,<޷`rԊs 4&!)q>**Zv(DЭqC(qAM@R ZD8X64*u9g/lN!l&O_dn)$[)B $*1w.ҝKeC5]AYṾK(rY OjX0ٙH!wEVrY$T!A[ 1Dk5D" J+]Bogy'9,̞zǜ, CHư:M uT_hhoTh`o²&﷌N':[+;}Wxz~#}ZNO|lA<ʤ^>8;Qa{^XFңHQvVuA[{9p]QȒQƦχ걌{n;1h;ݳ v/HζUch[NҦ_TƇr<}/ 򹫢Ҷ'xa-i6(Fe~+=ueUm*4=m jz ٍߝePbj}Y5LqMzI'Լ4jt2F'gn;U?swUhEh3F\kUB\v}ViUe$YXҕ{)2 w!8om9i s?6H8]~9׀yy+!d7Uj(,!n^ ziHۯ>3H><ϡlWJSL\* ([°vod&U2p%ǹs?טA OԄPe[?*!D qYU 2 mq+]꥖Bk{ИA}p8v*|M@mL UYF u@z]|oG@.C-lK-@$ %Biƭ l d&ᚶDz8SjJB-{C5Fiϻ`HE!{~߻Q|O`HA;Pn32iׄgCˋP5oq#"qjA! jn=ҍ"ˏk9; &굃&a TBF7N5] Y$t, CSkCY|]+eoO$X; UtmnLȰFFAARDgw@#wKM_H|.s)•iLN6V_=ِh‚W_7VNm>mlX%}Y5 mi{uOl1%m%OFH+wH+V{\3"N=rRgPHF;nOSR TJсv+rRj=L @ Zo!ӚEȴ,[~qk+ ݂j$E`ث;Uz;<+dL|Yy$$t=ޟ}v=|O&wuLxS/ K34MFNyocHӌsڄ6`J ft4DM (atpp:ֲӔ6t  H'?33 $-PL˛'6=s>]vt8M-h ?=A'^e'zE /uyo9E,В%CV2H7xe*y8e)!5ugȠpHV*1.13XN\ȃU 2ϺigƐdŨ<[J#JpN(޴k{h>MZ{UAuM{%O/1Bە001QNx&hg.r=|{hFFsN[95I{Qwt_R)$:b؂7Rm|K_|K"|U)0xf G1ʍ=7Ai%7OJ. !XeA'7>u) 9#GB[ +kU2mխ^~%f75(\B@AJ/!{4J< (FE?sޔD. `V%Tߪ$ځiB #K Ť0uӱ\B'4g"NHŐ}5!(qZC 6mB&֟ClC&Tղh.QtXf34W0zD6ҖN~ )+cŎ]䤝vGIxl "w"=۵II0I ,R/Jc ˎ%e{N9˂8J´3[=UO+%Ts k#PʶՅ0A颸48TJ-n3҃Ȝ^#rI⦼Q3 b96IphX(n3Ҏ߱}d @]ܤmQ^|b\ILJ=Hr_>E:ˤ!}iX2JnxNA@Dd l YՖ&oĠcj_}^*%>zPOcku+ĮiDKJ!If\0XEU"a¤S{B9$0(bJ4d"P"iAE ".-dPe4y?]-ݒU5HYD!-#f*ז3/j|^젒8;k?$8fRm;eNNl `35 qU3Q_bzX_Ѵ {ͪ=7^L) ۺH:O;V۬J=gۺH:g;ryU| `/C;L'ȓ7euvIh'_IUy\G7J@6r 6ގ;î~^PYMj6K):\l+)v׸ڑq;zgu &21)o5#<@T «!d5 _%A@ Z ? pBWZ] )F_+EkMٖpzi"U?!R-~j iD͝ d+RY![O'ݱden.!&Ղ _nS1G."ƈx ,cW/9h_ȒL=jSBbe$u %o@O!a$`fNBS4ffO_d TxC"dTlZydp$'HK ۛMHPpDMHdDق\B[BKkuf)^iFW"Ę 1qK_* X, $J969L@ ?PZqͰi4 h7K#Uh{~.j,`6\fK#ˎ*.J[Pv@ /[i<**o})z%:c{Q޷)pj'cI4,,͏40Q3ۊ;ƒεyo/Gm1FՌхݓ]O.==^dy̮jknO#Qn}h>.?ҵ_ykL.g=9RUݝ>u.w6;6Q{n'{.?6Te!/.Mcvۖ+Ԁp*`v}ϰUK'O2Ϳݰ>qn91tKT'bs^ nOR:Z_vI8tYzxτ`tv7wCe||'uɗ* SR?ymН{7c _njԽ|ޜ9Ljd/{SrwB(5+A\< 2 ZcK X"H9$2faSe~w0Wwv 2!ih Hq(Lq̧|iQJej9JK:=e;>scrvGb;"`r @ ,Dٿ9s\zѲ֭M k]2aCA@ %RJ |VJ2Zs͘SXrWڔyGtGt;,DBr$TsH%Eȗ|"b&S1QrܣN7M7л#S;Lb@9df)I$GP>eز9H̑UR6ĝ;=G+}Y&ǎ,w#.IT#)["5T79kmr6-߭?<C_`C%6 Fah)wcyqY6Jt-#"玴l Z^ۜV=./7o%R;uGԫ_p|FE] )us ]~XIBm2fj̆m-)9{ Q^MJ`WM/](bLcPæ/]~UgBGzۗ|NW"=?r7pqI c :E-U (ـ{ EG̀K|ыc}OI,낾vH}H@%|p{f'>jޯRhOОb#;xoʠ$Pa X.Arڤ~\ЁKvg>Z"8 <˂[aox[ A݈ Cָ #Cg ɖf=ir|]?כEV2t&:KR RHHKrǾ_(b}9`e'd vBPfx^S޿ ܋xbK/מO;YEtj_ix6 {wyjIG]eK#[6M1lȪǮμX?>\M;<+NǔV!8'˅-%!7љIh+~K~"38Q!CJI.Zv9Hh,NQ>- Jcm/} dmA|\LLaUB5j8S}R7?K>}Fyά1e"F5B_3{L"iCnߞ?#0 d5}NiO#)!eH"|”tw|kj eP T/H4{2޽MXYBʕtoL/m.Ч oވgLJڭ!qmIK>򂓑w"; 9^6}T>ux]V89ɮqSRw<ѕM>`dqm/Ԣ+{F)qy r%x ,k=?L-IQe0КH+7BzX75 (bX PXJ3q0c@>EWET=%pTbĶ 3䭈`ub}UD  !@Ui̠)ABDIQ*L by K^nI9"q %"2E5K5-d3 ;1vʼ.pD4&[D5(la ԹF6%͝ʹYZ&xBUGY½\oà䬘Y"L,&5 Cw!ϡpT)cl`4sJ&7T Vd1 QQ){䪗1lcQlN;;ZroH)k V-B0)E=[t1K^C9!N8ʁ?)@&@:O&W]*:L1F)JERyAg AB`ZoBZV eDλ}5! *M1QUTbJMqW}*x^8ao)BP?03;4meL0bH ۉd",mh'O/ ֬ i-x;iж&r{"փ) C T1;KQvTA (xzj 9t% RwA}BD.Te zPX=\4vzjw ^h'7O$b G.ec]^͟:_a$r>z*j4C]Z7|sIyuEV$/"3RoR_$'~"俰;ݩ7]!Udr͝'[_qjUHxHGY"cac>_-+j} 2ڄKQꦙ8C䧞aHB~?hg| o~wq;x"M)J; <7Ɇ9TZ(囷88\]fle{Q[A$GBS:g ͮ# X{ٙ>J82 z~>fw~gnBww ٜ> Vq٪;uԠ:/kJJ0ExUfɯNcs}H?ьaMehoK8erhN2'c-ňq90#H7.yx4adPƒee[B!i"xgB59zǎ/o3!u~cq]ȍ #\ngnMʉŷ)"r椏Tp+@TŰZp(e@X5 j0͇UdH6L#u~!or,0ަML!5 P#ac!Tf??w#Z:#[n%iE\҈:&4@,|(DZFBS rx %V4Ck>͌sk|!8\ЈtnW1VثM h}"`d$N!D8%ZC(2vckX mH)y~Oa/T)D)j#4* n2v!q ;(H*^0Q2YdP %Frf S %2:@@4{/Z c`ӂPJ)Qa!ΐ ʞ` !L P"!.DiY{1yK C=*D` Py \Χ1bpWA<_U=wݠ#ra d g%QGd"io2x2<`#!a9KҶp3{ 1T;Ib@Pѻ-J}^nd4)}Zf{\׍>#eb'*:wm'Ѝ8wGnE4<03ш2nkNֆvaD ]IAB%K Wצּ[vGLv́؀i}Ij[ BH@iCǭw<nNlv)T k69l($TA(Aqx>fnWK7I遑ZKiYIa'N|D AJF`RMȠwk~vۥTp/*p}k@xW 5>9i>K=72φW;xq\Rg5sktʍ4/!/M"  ]JyB&_˖f<:GЃ^'ĥ4>USݣc2=uD-)8˫ms.nGxO]o2D-5 KcpVNb;fQK+o^VclC| mֱteY%I tQsKwM~K'Ot)&3RMF8,($F xn]J&9_ͬzsL\; FK1^a%D#J5kS%# KR >: ̹+ ![N^Pڌ=b(3Ac$"1 ncOSīc% ׎p?Oߋn,&(x><#dmPD j.՛stn!ҊI KX桉-wkvxlz*8R@N|oT[o4@!r)b#@]D҉!'b0I'w"Zj[ /zշ^K93!PEK_ pBX* `L^`0ylaFh /* (ި;8eMFγԬ<§urW<:cYakzRޣk{8g&n ;HP6/h%dn54(^Qj9\/Z @ 2ȷm9qPf-Kڜ5CmóLO< 6E=ym^烟9A$-4B[ K[h$p,{W_> k=̩AH] -txˆVy5hDԝ?NfQsg5)BW!3>1I5>w̏!8IV/׆ATBCj&~-dgwnEk\[8J beQE{UDP-J~D7i`CTRݜQΖ%ma |FL6\΃bYN7By"HyŠȰCKx0@zD%"Z5Oiݫ0;`aZ˳ r‘z A130 g "ьh;@LG!ac'Ϛ ^y `W?Nǣc7hw 5l#@ sggq$Q~{]F_dLfԍ ؐaEU^1QX /Lqp. .<Bb1t. B TCD'ލ e0F&*Dl5iuEj8vOz [0yVj+-p.( EuK_W_*C*b/ydckP,- V7]czSMC^q4 z S ڋB*I݇KlB Vӱ{iǜw=$LVY'zDɫž ]4B`FPji? ciXQX0cZTSlۯ{K -uEp6$"Zq1;it򢯦UAbIԼm‰`/s.$DQTO ^H# cu/AԖ@ 5+f HGz0Vw+AV-|IYCkI,VX"AOg媕kz$Ӻa^/7#˜![``wQ`t\Rqk^(GЪ!C9:@%fM%Z,,(BlNR"Qj5: #ӉfUv}MQ|u\L?u愍8{+΢tˈu;7~tTȬ t&8L#G=e)#0\ډ񼔮ko#dл s[b<̂Oc!511Z)F s$v!?cLY }DZl#-6D aXdIG&Q #?ݓHi aa` ܽ\3%^ B4 0q  v@i&,G/nixv#L4 +*_4 7mhP &/nPNHţ &1T[L:yWѤ{̥Ul|V;r0t,dG P'_zT#Țz(׫+&f#ZJH(FưY+nB5H ! |HQ^ !I$bw#׭+ 5y[,m밳" !g䲏}⫗bA{F}o7huvXNӱ x%y"1RQPC n&cyQgi,j22c!pJuF$gΞ)v;L؛/ɪ?)ՒW9zcJd+22.la|7Ͼ[܊3ghVV7<"sdbg=Rm9 +` k}~>ɫ2 _A)B'U7bTїUl NKd5RW(%((~A{)ȁ:uUI?'Ujׂl q{ZH/abTaPdGֽپϧبo5\ QKAcc@$3,uҡ=n)6gukw5/YO啴 . HW+zisfҸyUor䖯"tO湩.N!KQ6,韛@o3d6ܤ \m֌;s} 2Ho=i|}0zwUN b *^|wtR|4ig oAǷcʧ?VuhmGFŵc"$dxE%f]7SS>ҤAOSOZ}lc6IE݀Ĵ%ƨƌ+ïIUC驌?1 €nEhI$؈MUQDc~fZ6ev^Qc@ UYЉ5R"4I: ۘVة)b0Ug& ԾȺx@ 44ENeAcH }F"^p;# Q.:P"C"-Db!]Tsr/3w=)ۖp k_ai3yIoȘ̽\ujӠeҒ6#\4#-~>F/+R4unJge<~w$[4[bmxfi|eI+ $wvlaYҲ5,D`RXܝ&!-(H!4׈lћd0ٚIڲd p՗؆KU.5ypꃧˁNnܿրP;6}o8c+rqf=wlweFʸ0Ty09H%%Dkf73wsjgFn5,A7w8P\mG Kmw6pDЋ\vuWk$ViZ4qAq Fc: o4\9c(FlA֖Yr@Ckb}GMsmͽ$9D#E+aѱ Xdlp+ _tqP ѩ>k/!lXBPØ[%D5~zzUԳ[IG,w:K^,<1ͻjVwq媻Y2Ct?W~:A =5g ϒ!AK vuuQmŠmN\y,vat7J~ `OZҦEU}l"l3]h d֙tBr$N0WhPR}8XtvGЀt'}/FYɕ+ӎNg~4F[v" + vA3 c$ C΢!. LT{i|% DXčH8ҲO !ڴPUohc}7{LBAlxX=wt+k$dO$pcAF2C%D /RmxkvvngFyoعVd S!w}db켦l݅veIjY\Dgwֆ4嵝Dٱ=v?y™] phw(̘BF!G pJe;5ZtđI-T*+ya8JdPէ/CGL矞M.-YkFUTx_z]#gzE" xQ-Sz(/ЧY#AyH7Q S>XH_ٔ([z}ړ`Q@ns右 KHev?6g͵noǣКOMm:@L+o*w !wxa>]׼g2rg@S]3Fsk=~GF! Սt'lkBAGCG[B"ۜxy3}k -Ch=׵ |ޢS0Kchp tK)ٗ-7fmM8~湃WRC֔硓ԡ\@H dSyޔQ670 R]~k[>+lŲXa f8 q f\V#Xw@rIwvv`ؙz_o$BkZ sVi~˅O{Byz5xY1LA pA7X´֞yC{*'GRb' GhL^@i.VjKR|Mr8A' 5{*j`ƅi Pe*A-Q` ^ r`(y5l;3eDWR\N 561O֞rP3{.?$[mvn%z @lB^S혩f b7ध&8$5S Ɔa-/Z?kk~1JȄ1kRgn9 H:F^FʵsvUnl ʥU \!翶N|$`!R{j5 F:|h\;'*tvn\^lg Rhb5=Ӓ 0eUh OAH1%5 WM9cѷTOe0EWf'rzr oinEB@ɠ\wGyer%SECz8әS84@ID&j76cE(0f`W}( GX93%/ aM'݀t {1r53* <GcU!2(b[Z=( 凗grt'B"IYlc ORuaC ?eKK,yJ=@xfˉ@uw$u頠$첨`5͇/ʴVV_*JcaWvwlh껣v#Oi}s5%#\m+n z}wl]Z૘!zxJ>}?~a˸W -mxxJU oL}$;we̳s(YY[ށ&$K>4OhTIj.m ^P-zkkNܹC./k"ê 2ɦga2wцlxli;'oXy&Ͳ"!:`nm${ab~pxmD-I4ՠ#$ nLESM 'A XK&8o,t<a@!C IXGr 9Xcq7 d&Vpme,X6#VF?H;T_΃,_| B`=2$vˏW&{mh3 ]EQ/<@^D`Aj愜𕿄WAvIcC-2\<H/lwYH<0nGr10cyZͳhIuJ_.8mR'HB;!X&F[MDZ7?T[fd_<(wwX!~@[~>Z/s|ЁNKx%h^HQ._ӔJ`v԰=ӻ@(`U-\&EZ)>2E BbV`M4]k92L+uCvG&˛W@.᲎I+6l}6?h7F嘧ak:9ZcW=rN 2ıT(0F*YY*+xtx0bmEwyzf)شy$) YR]hX3 'r3=Z%N%'E&PNV% ^A܅g.:TA Mq,DKNxFT:8xF%Jut]gf8 <=i<n$*4(QwuZٝ V/[ly˨%[ vKHe{JY&oUb/tt =o <Wބ@fogMfBJv_>PnًwT@! cz IP$ H*Rd& bqZ7Z󻺱EjOcXw [ tZ5)蛆;*V}KRaR £N` ̒U^$vsum?z_}MFڭ );Ry`Fg n,K P=rgo턴~9Z[o~l pP7S] do.|KŗyY5HlR-тFD'z U?g%٥IbfB1t0 b QuH,/5vl*Z/Ţk"' LjW4ىBUT#kp(0 I))[P1 nL$a^X = M{1sc f&9r Qht@^K*)ӑVgqG_ z&01 y~IM3 Q$f7gP c{?邨#"Fj&j p#t+R3A*CF{DϏS@IxmkIq< >'GWwef+_v 7 Z  ݉#]] o3x1|0X]E\i]-g|(5:Uծ;`+HllX9ˆfX"W36:[kmD "j*;x}HT$j;j3"[Y<5OAA䴱 i%-M64Ȁ1Jm&I^PC4Fͦ&<d'Vq}"$`a?Pb}- I\1 I"NQj8xcWHX%J)Ec"f"ۓU8aLJ?7ϢCtcA W%;q*E^wM N1oc]M#̸1"CE˲T281d ^k1DEo NȈbaX%*H35к?AAZHdMdz)6ڙ˿婇:Ë튵6?_* `gpy"b餄[ $ckgOr^V{#5@%tCuo dlE_yp,'D2ER8~>!Z '+{1W=̨0:$m ] ^q)'F K {֮˥K2O dѾ46PA$cFLmܟyU B),"[9jiT#=8)泙e8i9_yYTx)Fa1(ZbR{UQ̞g2OsG%U(G9pZĪ=P4hlU'P(< A*wQ,#tgCWsc[ͫR'~;ON=差u-N{5|ù$hy|z]ԁna>XnNRq& ݃_|[xf TAP0Ɣ R۠JaOmF6ř$tI?ُSGMu $7HM^S¢EBH!$X5ݹSG8^WN , * ٪x MY;׹L-_u!7&+O3CJx(~mn$EX."V?p|Pj?R %,4YfJVgFjϫuTa%‚! E˞X;nVA`բb53dB#eHcxM[%),QZA~w<4?̄,P`}l}i߿@$2ɠ{qJ;,G0NIn1)dx}u=Lr*KK NzA1[)VM2oۚPrRY })Sf'G ,=s2Gvrmͮ ~N2J}V38 Kz:k˖U{؋>ڦHJ>k9>H&7Ɵ̹ݛ)/RlSlnz7җrvG6EM N4ϝrfؔ ͏ròek(Vv2˭[TJANV6{cj56b^5qٜaźB& K <<=İJ4hy*AYNw}~=lw'Zoqa2?!#3Ӷa=@jj[0FH-3$ W'$ bz-kjڗ +ڎ69 Aji (F!#A~t7p]QV?-U/,6s`%/gh)R%LBD/NUg7V9#NYd=^l S#U,S}c s Ff1?"l0^AqlWU&9AN߭G߳mBp2OVe8E\`,TKC9՞NI^U^ <~m:$5[6"O_.!pJvkKI?5lAh_dD x+20$DH3Faw)U1b­u Ƚ%a#ܠYV`"dJB/"YȮ *ؾW"*'V&~w4ùZd]PX[nEyy|o_=3p!JY hlM̨^~3K`44&Fc]ĬLs6FH0;Ԑ5/"% x(2)F8P*"ٻ EAL4hH@c !V[voZA{"%0/!*bdf% S`UD_v~x3$0ܐh9F`$,F sh f8! orOE\;쒧7NJKEQ<2agFT!٤,\MDvHIVP`E1Fn6i3[cñ^4@<(tMJAV% \%% 74S6[n2II]O+zkbiM:]LY[Uc1SdG@w"(H~$,XlܚS7V8c0 @ l4Rv97T EX`iś ÜC4n֯HxV;ѓH#A^i`#ݶi@&+g iJܹ7a'Y&[" Aƕ;g\H|M7'õN4DV,caw`y{hON尾qH7u)!̘ufnhDSZ†Ǽ5PHRl$t D[ѸCgF ! }Wl4OHL{ X}CCO\@&L1vL0BxGxP@L1)@ԑuv2Mv:l(\"Ab@ua%lofIQ6VoilN$+방Pz_zbDH+KOcֳ' ° u,햄ywxyw7Uʕ޿mvc,7yO/lɫ O?q47zyo+mh|>!HRCtUG{Gzw4W#BZ:,Ώ4ƍ>{@ IZҭΏ4k֌Z)`W~~oWvw>=uFP L:{ .!zuR2UʇT fӛaY殣o/5bu>CY`[=fogݛonFز XӟPC Y;pKr(/cS_[_kX{v L+# [Z?Uy ƒƍ10 ƬT0=SfCW>}66' IGmgH˽+XMG{<";"(>Eˮ 0/oO? j(`v7͛{fn{qO FW7j&sibH<&> Ј=u~p٫d,fmXW2{aH d;y>$/CV|x5"٣lE+-(CHt渙v&R?p[ >s1wE n$ V"A:fCsK3I~gKpd/:RHM8jj}߻Iunuzi2,5CݱE˂q$Mĥ{N, 8HBJ}U2{$k Rd#H]^@ٹ?T9*_Y킷NKLo` Z95I >o|(6㝳GM@HB@vDyd峇I-W oVBT0%R`xm{ûC٤碾vVbwHl$EddEBNp燲I墾PB0dA-+%TCNՇI '>Fwj[@|A=SBa}]]ԏF4wq˕ '[*܍OhTvƕZm&m/tӹk5'@$dDpenm6M٤~f_.oAZ /=|l" 7CޔMj7LQ]z2+$ؔMB@}>*.$;@زhmLDKc Ϭֶ8knPc NY-t2pV]YXKjwڶD: ^`#'wjGt.x}I_T Hˤˑrnůג.$HfRZ - QtY1LLOLoK!3q>49~Ӕv&lnU576d,O+X Q1&ѹWd͍~ɕciSzi@s!X\ UrE)v/H7$DFQRK `4fӊvuG 6-E/T>wYd}˻P@ro~|凾Y#Uy˗(zS:va$cx -}al "V̷|2!,n!Z@LQ$ziSҶ ?̪ňAŝG.'/crNd6ae6>zrm~-xq1h X" AkQ7{侀I.6Wřq%GMjdn`";jҌ(F0_4#UiK,5FۮuMxjRm.voŘ#/`EXDRbfh8ɅJ3bL ߣG\$ $[OeQk<|~=lʲRlN-ֳB-B}+6;k6{ʣ'wn|7;p44aHItc7Rikuv0 5Jv3B_5p9-?I!Q]YQJ)6a?*n=~OM?L!~3 KPMBt~&}-!yw߅rʛ/yN Ȏ[0+Y#ZY%߅dk2M#|iHt~un Δם5'Kv½l u :J7h+2ݘ;.gV߻oxۮk5a?j%<`"5ol7qs Dc;3|m|׽3ecxL-i I^YRۮ'H`o9l$*U[LO lswBA$&稲6yI+[}gӰu?pub=[XylgMy|2Wy9v8K<`4+3piW}W:'Z 4aVRmPPn ,p~xnj7<eRCXmzoDg7>oe 'IٽNhFU}Gg r]턞jvO]J~P0aʾKDhj+JCxVEIQ~moU?+O0a}I@HXзtiBPҚl(׷st)_ Qe`%WjJ=nm ab;& '}# tV`%HL*mٝr?kcuR؝i#RBbܝirQK$R ZLckEFIV iB@@?"cb 20M1k}_OE2؈0.CaE@d6c1, A~{J$E-l;=E&FKrҊ@l- 5l2}kiHcTh  wa 2)uɭLC:K{K HTčA?F'@@c^:mibJ0IഥFh#hE})-fn)=}3R"a>)՚z+tLٔMo+ƼNu A Iɐ@9~oݔM]ǽt=Ukx%nY`C4&?TF@Dd qHtRJJj.T&Pbqꗭn$e+Z䵬[~ɫs?ܣux6~tnޥD[ Sd]\=ny=*<9<t FPI Z$ӧՃ|Ϲ&tZHOVJڈ ~>+DP+QAݶ,}=i䪝Dyz/ڿz.6̒#&WҬU|3A+ѴUC5&xӍyuoɕJQR1j>ZA]TL Q h$}Ȍ<2RJ H4$h46I8ܶ ų눌pzyn`5;̢MI{-n$gsx|byEtr -ܪon{-dch/i[y9DtN|sLbbH%9{nO+y>,-ϧˣbP7J,=nBjL0 ^S뷾.MB[c'G),WJp=G[/5]B^\DAEP]Dh 0A|5l[SybkcX/ J;J4`C,Nz^\C,,Hm*QY61j?mH]CSZTYR}txƭI d ~U=̼[ik/Et9sZVz^TH{tgZK\XOcx4X3܊Z2MR/|&3k^fiAzF-UX5uPERĸfƢ݊dvC2_n릾lsfhj_~ 6bbY#~ꋀ6B0E˲ھ86dAv{j^#![6@09 DxK2@F*QIu`t2/vT"%~x1 Pٝ[THf&Ta96Py<2X(2J]ph^`㼦G-ṉ_]vUFػ{R 8Vk]w>Je?譾*\3U9`E8xAQ.?"nWAO|SU%%lWڕҬ7?cinA5*}05P "4L!N݉Uf70ˍ4c;o0Ճtf  6Z+-ҢgezϲfW|S'=˪Pw2̚[=Xlh9N'ƫw9?]׏iO.R 7Ϸ0Ѡ5܀b]L!mִ]~;ؓt5NAuVR Y !6gY;=HXҳV !CĐ?.gM_$q4%! Ɛ8=5@iTziA'A9ZN76[f8ks59H7G9:2JdXҡ"C0w ۽ 90A j@fj&%Z{vO۵ PQRd}Y9rѧe˚ |nMGxȣՄBF]{{59HhUM%OV# 9lgYgyj58{}u8:"qdn")EViv=?7k6krWIÒ!ZP,ڞw8pe;iʰyzܱPmN$ 'fkJY:ܑ?Ѥm=]uC/kMZYOflw L/T1=_~>)W~wK;I(;WJE/ip^{siSn2 2mHG|ANԱ "ZMM)qHߋ:_k4{O45CeLd:toa$PU-;xR:yЕEg8)y휡'aϿ>ruﺃ^4\?]͗$Ta=lLtVp ڤ_䋝4F<,ҡ?3۽4 eo:kpűjfJ EΠf_}.Msu?Aw ޢ$`(@u^!IU1"J5 yvB$P.SύesS2nGp9oH5vm u۔\c(i԰ݶ{.G"M)\ 6I.mݬˏ@.|KmM;3~`۟ owi}Oœ}Ct|)'{H7{帹~#j]qw0t1$K9a^mX~Go!4zKT w'7$j*ݬSSNRo4atS_p9NNM +GT=tT7rY4/dz㈇wOD4s[=F޶)s4|uKcb!- %V3V([mW/^js@)S|} 茄6a#?ojҵ_uP7MxT@@Ғ=%ϧF)_4Ņ!kvx=_Zy[}#[1 7<zM!Y>_ןY6^-hn{`kjh m6kZEdz޾^zp)7?l6 }mXJm OK!b1û9D@ZT}++JAwYg-3Ty.b%>q->ϓ ~nA!]I-)`O|o$}99 5 ['&}}')'L A|bf7wJ݁LJ4voАJRldvW$Nų~)ORz'ޭa|ײ^z'6JqnjXOhz1N~mnOvSm06\-Y?[ǯ߻#' _bn_GCJbK*-"sO.%]qێS|N/v!TLa XH>U?ZSCpF_!1" V#XGH>F@nEC0Cm$@Xi,WKs4^osKF>*'vÝSN}76Lsͩ.M e4ٷwt7;ZkM9\C%s]w7|WbЖ$@ E4|э&>}I77?^"3jq)<WH0?󱇋cean|[6N+/}O=_P.M%g7VH"3oJ2Ŵ!?W#ў# _myTR.[%}{}gKXuZ%=_.nC@sQsMZ%Anc\[ա-mv''%~ZW}X{hLq-#7wžri/KlH[frđo^@˖Z6o1ܻƽO_1ˇqA8{zi{y>v^N >-nO/e 3fʃƦNRZ]iFИ3<7O;4]R;4Gߡ!n]_𞏷8{+ 1š!mӊCC\qhHMАWD>5cw]X CO}iA{{չknysw:x?)ēvbxd??ҕ?;ZW՚ SJ;/W登]8;1t] MpN]"xqo7M˾r΍a`1 zO[D!qNn3swn{=怆6ũof0=M6gszz@CZLg{S ȶɍoؗ$}H=KS ho8x1O/?m}f}x_Y$U۞(Qޏ0Ew"=w.|U.}ksqM<*~@lO|kY$ Z_ hk~?J֚b6m:2|`#f_?}dV?O4FЌ?ϙ1׿'aAf\k?"}:ዿWT9jb3ޫ?"OoO X{Ǟ[=u]pnO 'meg}Gc'8VyGϽ/>!=ڿ}ׁuftFV l}+;xsZpi\ g'Q={YH8ryOZftnmPW=Qgps"! |uzlO})r+U]97?;$ȧ>S|;k -F ?`K3x{l|3ǩreG[rk2+~祂Ƶ:[ߕ~'A.y(;~IxT>`~`r{[unLxΏ=A )k Y;Q?Љ9c;pFŸv,33S ڷn*8;'mͷ92NF^0c~?[z&5@Ë &(xuy~ů닥- ?b]k}/~|Iҟ>tՈ[N|IWߧW?~;_My޲8}{oKt5 4}|EsݟbhʓM" 2.%dR)aE!Ag'@y?ߐ |2~ډ""A/fmwdƅ <6A[K^^rfac5ċV/s{|WiMX/Me:b-d复no~Αkkm_ge+b -h©r;٘S$m}&>i+1/V/&Iwј^vW߫_i+^{縘&.$sVW:Tud+xѥWSso&EF9Im o :+]}+PLH&@vH %;gܒem܈(q wt^;^k|b].y.=s_ؒeLCҍtoXxb=\2q/՟-YU$D0i|p+v//g4Ö,t 0-wҍ4r? +IƖ,+LI mSҍTo]X/̒~xd#gI+GʇT07*M)(eot#%w~Uz d4HMldVC2UuR'VB<80KfcU>xOA:PJ[^ai*$<`d@Ȅ8K"L%L\4_`AQh9DY|EȠD h˃# D䘮1="dmud]vtoDZn>0Sc]#5_zKH)A5jOܒu/ ;4]1Jͱ-7݉6%7*mK}=7R-Kr&č<~Sew*-De;d>$M_wV Csٽ0Vʮ!-a0cIN꭬%g8\Ħlǖf;nAqĒ$DzixHU,b5WvA\dR;QJM]z iֿcrjhg7#ވdzGH |̸.ؾuTD'ZV@gH:U,;LA@Y\{ƍTsUCLTZw߇#Q'@qDV2n#FSA}fG(eq#,SO.."w2hV%2̌:p!_qUi#`אERbWY2%/Rc-;drѢpppJ^vFR1t1P=`r:~5wz ,eiU*'vJ4`0pdYB\b!;&'f9:T[=@!y/5ynj j7=k&mu|ѯum)k& |!J+|vM$t|8Yw8tn72mn[>&3?^؛GO|7IUޤ|ϖQѭ͛?TMiY?I]2VmUޤkQ趣ml~y*II$b{8/[`EDO|IR,c0/ "݀Hhy~fQ'Iv @D 嫕tEBy&.ŞÉSdC둵WWkw6NoY$20]47u?~=zCԒx'}t!P J0KFR $ӕ=}ql6dcBD )Ж-p#|cel4(r?K ȧ_3MNa)*Cާo*HOWǧJI ljUy kpG}yTu֖g:DaXPGƾ߿-σˣۻ6=^^Μd:!j]rx#߯ M45ђl3T_i7Byח>AB+Q6G}*I2u҄-}Œ>gEx,AW~ݙ-G!T9.:_>|,Qӟ>s>G;y*FgcAx9!OR nrfⵏ&>OPҕ0̉QjV?ñ__:+g ƾxG)U3{LŮ`|Y?L ačg%VW,=/;h2>/{4>a67I#nOrk/Ƀ$ 30!F'/ipH8N|I"# zQu_N|Iґ X; %IB@D|I4$9HU=¤:A҈d^?b_]~'|~`ƚU8\8|x[!J( sT :3Ec KanAM6J7U+)1!ڝԔ 9ʁ}0J;&ܤ/!2"bb"wԷ'sW#S2Cz8p(p_P"YЎs̎B y-i/!+$c_U *piط 8# 3=62WymzEa=ЅH-܉h"+ @zT5Dzl["ҷ2Eȍf^`VdZ!7RZ HbYs<r#hk? 1ͦbŏ+DPm M?=,ϣv>w/OK-}_o@VD j:|/ʁlMe7B`1V}K>靯x}hZٴ^1a(+Tӱroɇ[|%i%W'QTce-a+6Ld^1g#V& [B-Oz5LӉ+b5BX@=\¡8 ]v UvfRGeKm>٥uP(ݫ=L?Oc8zKk!Ybxoowg]ǫ͇Զ] "G^:oZ,gLGGò¿~|H=uz | 5w}hU^f^ݽ ol',Vo˙Mm^#3߫}8Sh6cA>;|j촁2-:ZgLyn|q^=C<'jZUWEsoܫC;yrhhbYi k{ '{菉ȋTy|O9'q!*|dU^II C < ,HcXIc(_ |=/I gyA(_HvpEKd/ "VS^8h@T1a9^sśo{ǃu=4Ց܉^'=T%sΘUOvC8>SE*ҕ.$͗난q_"V0}%e5rkcѵ( 糁T[6M:pmN%Qd7l.nLLe౥QTJ\ Ҹi#o$E6uY;+E{(JpڜWlˆ&֙'E@@)HaӜ6' ݂";eCuB5{Ǎڜ$"sQskEbk<t hSbnL:-00@y1v}嶛?9hS[sPn`u#5b7H#3PAp~,s=ϼ}6kOT>թI܆arbo4"i\ֲpȇ!}o9Sj2)a72o'*P`\Ye>c?yJ׬*S95jX43a n$FEQXb1jn@52SmFO+yTVj,g (FeҠMy4u6x;>Ggf>Dh@ś{ǝ/7~ŤUһh*]|}i׭w@WhEA֣u[v>{7ۋbxApj4?wy\ߒEkz!_{<6ҾOӱ= {cȵy 4IEWݖvY4uG[^dZq@Od&:[&7;|}sN~oVo<).h:?s˹9'z(&^6:qcfBXsS;!GکkM )l9Wĺo, =rz;'9IhTW؟hbEvd -M~o[su؜QhC+C''sb,P|xϹnkBw t?1ZXPXj6v/'= ?0c㩳49熵JxVo\t_UCŁ!LҌx-XD\I8r]InnA;h[A>Ţw)U[7>nJt ?B/>|{//hnq}S4/+m aq#$A~ʹy+vg#tc2@TFH&e7yo9pqIyR=k8S͆ 0##n$FG܂h(\ف#Yy[>XrV.:ٰlI#i {6:-5#vz7ā=+ 8zNNl6a F2ȍ5sIۻRK4O+p`h~#?$U2jH4 0T˽! kG?k`̓4ҩ9nӆOѽ.L,q C_a[w$eK\/ Cf?+7̆x9ELoչ)9HWGMt.I91vG~pcb!6s6#^CA4.N+Op6~ry4$e%ѬAwl:pJ],&xxmvE͑aY kFET2URD]lhe\m~b]VGn5m!E8F stx_~'_~tHe+kU 0hq#.>?~ār}eXWDzՔᬈ[3{\T"=|vĪחF )gky^ag%4_vTA=EDy󉡍r}C[H^1UP{Q4|\ujpg!q&HM< 3ֻj[㋪\s\x2Z(P9rjy|3'i:w2_C89rsiRm˾bNχj͜g 6Sˆ^#qA<Ȑ%Lťjo$U8Ц>YMv%8ʔ{<~snvo,a/-{O 䑶xoـ~k|Z!-MK>~-_ ZYF2d0Zf-zV,j灏ZA&"dKKw֒.}=#kP۵dKВi sb:nm@rXķv[ 4∥ZIIFB᧙~{4MG2 wB IϦq d@+`5pf88o>8Y2WjMb^Acv{J…2` 7n}tlӫS=xrʹF:Wj&$D#:^-mٕt駝[~7E mX F/Ҫ;n<=>d/a+i#.Y*JigQk7GH&-qFG,\tlv% PZݛzUH_S(J Z+r1r+oIkAey6aihu#BʻLHR'B<-nؽU`VV®iV ,uz%WҐ9AEmOZ>ղGH>dd@ y?u^KQ\qK$(Et4߄JW_PmbJ!'u7K5"A8Ā pST}XRL‰i_ |<dMaIP0ѪAɢT 2QYΞ~߇ 'PSasS %IOj6JkJ |8CN.>I}]B'90ipx*YS +:o5e8#Le_y!>h@ayRJ)>ѡĚ91]`U[;_œxx)T,j3K؏ '̀y ʜgx>IB#>:p& KTE_Vl'@I\u7:CI^F?kZ]0R!hVHrvXeCVccu* 0a`SV`F_I|?l ܹkHCe;C):2XyyV@Y,@(fi|#2HݻkY%`NY,2jV^;+H0+[ieTHJKcݯޖAz쮡|]`9spH2(ő-#F| vWoy20UFU{Oɣm]d12`AMjrqnE`Ld fTp~og[ɷod5  WP9p?f,"ØP,2&.59?8㱝m7?'ccի7[>П>ZQАwy#3^3Y;o'OUŮm* E$JF%iQʶyy3E۽K+ɃNG"jB{ \δAYm*,3:lps̰kWpJ +hZXs,) 'L+hWm)HXn}^(+]L=;}V`@ +[GY6ىutdD贏}v)0 {cie`]3jJ-Gf`H/3esG[_& QJ w^fD_H?Ĉlf R$H_I d]IZ SY)(a$q@U?;$ g+n`@@5VwJ]Ix}1\g=Q# H' [ dծ$tj{hv졵 %XZ*fj۟HMz\8B<`[+@6a?@ȁcn$5 PP * 17WZkAic"ZO\HhK<Q4@0cn$czBA*q:I3ϕ&"J+No{s1mA<1 ;Uv%%dTA\nXl_sΧNKUe>۲% ӽ^/4xə?$Cr|W o('ks6ϒh"U*>䎿]qҠm@.9ŊNou!gqQ_dv6J ,[|p6o[q!m 'xr1dP^XuKfFPA{?^B*n+8HbN8rcjPKP$L|к'\B_v`HU(oʪZJTJen<m%分sϗjOn9"BߍXf2aҫ gp[pc[ߚL-;O{P@^㱨ЎTu:N:oEwSجu݋}*OnEB1]w[gtS' 5GV'՝Yrt] S-pPh`Tn_VG^zTJш*Fa:~žo9Kw &Օݿl%((EA VЀK5w#+3z=`\T!w}3B,EP " X׃DIWQQ,Xh_޶16;B?5jل`HA:,Z $ĘOw`qj2WN|]= НIC=]= `,gۀY}eGQGs 4gY*CgaUU6-u{`m0<,=;r Daune8L7?B'_ti c2Va>/OTȥ9#+ױsn)( 0mqr9mPF mV+o0Ht&M~ګ<ÁÙvf^WD= dЩ<.@++Iej BR`}ptwcFĕu+>P D$#T0y m\IaqL/6äB;$p+f_l肠Mt64W=BWXl JO0g;G!hJJ6)8Kb+ @w/bFVq%qy1@ɴ% (Z/1p6l%sWN4h+w2\TJ"MaT dڔ:cO34&xxGS6.G- H DfC$"CC6JZʿfƠ-*a(cZ ?[!P|9#8kTRz@ i=؈ $I,J^BϿgBQ1cĮ藳| CF$ ^%zt%%L @$,"&jpRE_,\ _ny}؅P1_ֵ%`yF;:Ry%oH: R݆4 QcL@^zx]/I-i[oO~ 'sayڔ'J0S:ڻZi6E՘%V? i:NǬwD ]$\֗ U_ڍ;P)tYB GdjdF(t nFdk+/ժ$p{/_eQi?/9afX^`NdgCh5˱nT?i-cDw W#pk\T..R I H A'ؠO+j6*RR×"B* 6@F [=W~AyFč<0q۱AnprT|-$Իjo||[??Rbp\TH2 Ƃ yDB֜ڨ nuuxϋ{[vÏn~۟\8b<'sQ`t!\`݋ZNۂ~u{>o[6omze"V(é\( A`"N.̶qb[m&{yM Iܥ 0FM3 a?ch[6i΃msq1`Ƙ7k8]$ ²}$DN< ܂m٤W5VVHՒBk7>ܻMF՞6/Bzpi̦l3ZfV|ߐi6up|um!E0a(j&0(POx:\ l5 ˩[Utp%%:S٦GZùF/tp%9uI Nx˨x7/NJrl&'ZA{K;>Ill<hfmlxbV- ֆd,;#7'R)hf7 ` B]JohTΐGGWR+9YbHEh3PA&i@)qjQ(]IєM5,>_k l)j$+Hѕdf;՗SDϊ)[`V$($E1+FVe쳟gf ׊"RedG7ѝs Nc|o(M2{ڇDmT8(ȥMzyoE>D==uG0'dqW0HV恻!8^ZZ*`@`-%$:gkzא۪a-Vlwrz V?eSG\ >#L7T>ĽZڑ\Ǿ#0?%B^ACakiA*#.6T'z<ýhkELeJtJA*Z̩(O]o-R$n)m01EeG{=תVA}Co!JmV00ݫUg{ʦqDp!g'(1_:Pgie/Hc}̥No'BlULj63*B&%q{гT"+j*؞V(jfq 0Ϩ6QD8&"@XBNqϰI,) "aRP\CbJRd<~n=xG0ugSqDFt!&h}zg**z}QRh/Ԙ?>QK8[5I?XKFH-cig€ $)8RIh_׻fsM[G^+˝#MB 7MMu9c-!,&RXN (ͩVjU}x,ri((47c$(J$o`BdIdJxeE1[MwU+ǝ;zT/ Xa,ZKH0o60qoj|*$SȐtZ[wU,?>v̛ԣ_irfd{#CwT A0ZbRdeSt|5)HB42jb[>mG u9us|V Sz$ @jdW ȒpJ8cl*{/#G}# Haua$,&Ԑ@`3SHK -V&Vhf{/7`KysC ; 7}ͅҋH1 # ck> 0+'LC&6PS.I[kqr,J\I3QCrW*IfGne!:Uf+1G0k\߹XEKCв; (q% zazPX$uO gWMAZ`LbJy Rw ѫPĽ%xyO!,1Vlس(#8i֒aEc+ "UP+/҉wbÒMsnJ`ҋg[3!"$%RKpșu#< a ;yIfޕT*qIso[)ME}ޕDZP ؠ=Kɤt*%"ڻFK#/ж`b;~u3'gR-y I&BKy"yI̞H{7r?[W<p[]F?}h@mSTJ_(m@%Y4r^f^'; ~(oJܫ()W4 ;T Co-"|i0D_(?3b۪* |Q'prܻbYJPSG=ҤtLuJQFP%eu_E-,g"{g˽ʂ2H $(([eBk~6Ǟtا qk0ӴkI.TO-$CbZuI$q&x0#nZNYkX%*@C3NZ@=Sa 2rgCIB=g|Br2 >ffsa~;QD@-EHA vavM& W/3ql/& ŕǩ*_F9Bi-aTPQ ^Жb]~nG@̀riDԹN .GJCc^L˛on&쵃 ]-Na%IR)84и=ATh̄tv'XXW. &HAsR^9c\ D'V~62$3 ءeaCWzq̓L& [~X*W)UJBAэTy#ʄ#<j0$Xj%aT06]|Ňwx{]H\DX0!JQzluֻLH]]4a ,FTWG(jL%ծ[}}j ) $**ap&2$&H y u w'|ޅA&JsP٘Z;3] "ihԵr,qkޅ"i !9^>=1uu8l0CTBwӪ6s-"X`X K̛Luz_^0(6TZ=p u'hv2վ$iH@ ,)A'"K!*(וdHtR)RN j%+C+ ׌"d=<uw-|ו$(VA=@2=xmۤo|=%o 48R#Բd/ݒ<_M 9=O $=5=d~D%b ،=[웯_/[P`HC}m~k2H`{R`p>/w{|3އA]?t*E*CHyVnw^Mv 0a[m)+}QC ABY( \k2Hqv;HU8'l~uLqʏq ^ߏI C ptUOdݗUϙ3;ى{҂Q6Ut}7:Q,QCE-GZqƭ]xR?QBr8{7H0f0OLY dНHiQpv`)A]LB…T9JDWx6!Q$""hR/03 yY)0D&p!URՑRUV'U, t`…Tvnb.XgU*#adP`B@U` ` tMW 0ur  S DYPoA;(j7N.$>L2uEAs\Hj:vr!!`y. Yaб|rIB0 Z!UKб#vp*&!jXw ]ߨԍ)׬WN忭;RoHKOXxH Bì:־60 [(8h֜#n8$]IN?sz0ےZf@8,v%'_n+\I0plf+Lÿ nq6_Ţd+Iw3GdU.E8GloGxAUb,5S TXΞT.5y51#g%kK.Z %̚㨾RE9 0;m&V-ȓH;صU ^%`PW3`Vp դqtg٩[sBm߿lԿ_H=Z H'ĺusOUo%5Cyzc^79-%#E=>uZ0RqFkG(dvmw#CT`GaW"@HCeNՠTZ?ލZMa@AМ\d>%AIIڧ҇9[t!rki 15fAA[U %~V= A)Di.)}oZ a<~ o|MX! npՓܡ_-!Nj1XW¯ԅ $ T˭L9$/iPtkXG.$b%!I9)e805aJUE"U|x|B F`H`_,27Js(_O_~8O|jԅ$% mh,\š! Ցk=M?u!L6#%u7GB]pԏ>'֟OEʬKᕗyT+Ûkgdm9a5$/HfMآ|8%+IÑtN ЍhQ/1GdfE*MgdB_1cPxnԺdvmVܻP(5sAB@:+aiB eZdYI7@vI?JK=i%J@ZA|ُtlc.Ǡ4 CAGI2~Eqj{-ͦ]wn=%<6Qb+4f[A"23e U>;ijAh b0-됀Pl.{Ӹ齿nsZjeZ1ebt7*btJ.z_;O)F,`y1U!Jd۴cMg*@OQUE-r ٦dIL ӠpҫRcS#7DN N€H qBd6w}_,pA:HUay :0٤i흛} \H, bItfHn޹dž.${4A ʎWr2ryҩXB`ߞK"srREh-xg©\BB CR>t,Eԝ G=qa 7xOh'p%}G?G-A#9L Ca%O \Iy־+?&0phЩY4++ic'GlTk)!,B_9 %W쇶k{Uk[AR  bƝq-79AjlP, <Aw>k^7;ܜ(ጿ3uunxʫ\+[6;aYn8Ra:'!@~˩λ02}gR<Ժv15mtȚpltJYDwUR Wx EG`d9̹\P3.2w?خ͗?iZg ޴/F>;uF-a0S&!sZˁB /l [d@̳" ƹVBaERCo9ri5ũRIarXJCk`1Z๱b?ܟa f$H987hUV?{AdjRDE*H4\+FAZxVkS҆÷-`{:et_bd)Š ;=}7VlbtU_֎3aጰ u¨6Lg  (lpv*aE(Ǻj] PdF % D;֍T+2+W)AaX!?#D X'Ǻ[rA[j[X6#i ݤ/Y|(8֍d/7i}֒"_䗙'qߖ nU9V;{(@1j6r{,? I:p:nIҕ|r#11+,iR4HH{ 4$;c1NLHA#;UpJ7h9Sn$)²s]@t1:Fb{.c׵K Ȁc^3Al&ٚG$ ) jEXRpMR*]ҩ Jz<ddmTNw9Ngv0*XHq'wHG@ת`f51x;IV<z%ub;Ok\YpokMѫnϢD"Ь K&^yI$@ym5yIxUue~hX0j;E[AZ Y@Thز \s 9CDrߣ$b#H,$eER>m#CBaUAt~Zrn)[q %f];ݤ}}0sRN<:Zq Uϒ06[#Mt0(F2{czPT֚>~Y.3<7#*oM!Z5uVW`%L*ύ#*PzUSG&z& FF#*Tԡ9SSǮft:Bs#ArX6YI H`0`6j@[u^beَMLM2zJ8JT1ARq# sR R0"BCšdML jkČ)$-(~ GmnpTǑܑFD0dHBc \Bp$sH$B/rc` kZA*(z w]~yeCj_o6:!R&}D`Pv*hj5g}D*W<3pHer?Z;^cc*!E HҪ$g}.4ysqDc==~x9:qRU(:ImIc>E9kk;ogjO"w!ϧQ00I;($heq@ӎ7ͬ~.Ԡ6c0G{5oԞr[d|/qLYI;.@JABƦHsY**FW`CS|>Ɨu݁H%-26c,r{,bߒHj=+:0Iˢ7!-,`c&hBH7PnZˆL] izi77D,N//^&`&R2y lزE `en$`Wd92") -&`#A-;AF _|0s3&,J،WXI` 8W ;׹6/J6%2l^FuQt<8OFaiz5+µ΍d!2AjJMzΚTa\hXd, Cu3vfv۫ g{ihrwR 4kvԘ4DԀd@%h%HwJL]\NJf5zjjǡTN`w Y p6$'ѢV%1RE.w'9C[fb`=]Z.IIY1\t’cuHBNr'=w.6e~#,b"vVJ`JTuaxTΥ>:,+Wɢ(%Ԛ4W!&^}v t mǯznnݥFWꙴI<$gk y1_*&jֹaN ؤu)2L4\ɆHXH"=$5H˩  T $ a_&5׹bMhh]S9tܥ! ‚pE-ss#5$ pi]FG}c.BH E}cҢ>M bE^ZAObYJVUX7g|ʒtvŒ۬,MmU f2,fڡ/gcnU95v\*Q1Zm )aeL&nUh@.oR,7W ΚJ7*hnFXh UҍdU^ˍ^^;1.2q !7~]Žw{3h 3cGF@ "G[0I[>zwx5+FIUx&y=E R(?>?9ƛCm 0DDHQE?Dck| xh>LպI_w3&9؈ &2(U(0{aʤ;`}zݍǏm p٠W@R#,E̯͝b?X8>˺ ӱ##zݍ X*A#-뀮i_Znn^w#ف큊K05:ﱰYuș͍ƹU:jfP lZBwRyw}aCZ{m<!UNƒ)lOuzٹН~saaC3WTrw+НDO ֠Vɔ0Pq*QvhLͅoĹw]H`T/1Н4w\h|艋~( 9tAy<)dUB~/JVY$XUB/o0r EZLtx]`%Xz]|Zw/n=v0o6TOlMVtSNfB<~t.%ʾ)-S)~E!\S4koy.x}ɯνciK$Ŋo[ͦ;^i_ݲxLwǦYjHp(5](f$/zhψ 3[uUT︽z|} QZ%y c1Jzώ$BXyq,'XS)5Uڗ/:*חDLuWPXNs4E:,g;v~#o ѻh]һs;qORJ Q:n@,7]H~6O!.ʠK<㖈H`t݉R5Lm7|M}l ]O 8PlPx;1δ.'#Wo7x% +I?V=t.t,ͪX_t!MMo2yN$R:2[*C% B 0Xrz2|]Dś:jiLY')VF g5ixeƘFR;sM37n ,a!fVMH4TbYr#kGn ,R8 Ep_K@'Yegv-^~ J&!:{ys-#`dˢCF|ڑ?ewZ7)b+Q`NF 0sYfZ_[|]15V˜&d]҃eƳϮ>>I~S Brv,(aJ`N$ mނ;뷻婛N$#bXA)RWg ipXT,nw#)O晸),|Zg4W{n$Ƴ1S BlY$(yW;DONR6`»wv | $wڨmZB<m-4~dݍ\~OL,"HjYGn^/Ct~qSKݹi.~ϱk)2 K,Oufi-'ܝfo_^ro4ME $gUӻ3eN,-SQ34)j@6H$ ͉G I|'>;"eF%eT01hު鏖ߎNl8 ʼb|'Bl> $$vr}t-A'vttH1f#:!r$#- po?*q#qJ}dF&4.6o{y{XD@TD:GV~2E@く޼ٳn$;͓0ɈкWwp붳7Z=1D323 /vsՉNaרo"Y-&oή>seuL% h4R[Ka8A5p?}[WRc2p Hƕk;#w끽4w7L*7\kUqΔm/ Y)KLgFvEݐ1ANz-篾'lyJKI Kvlb‰q;`swv跳+}(Z}5 Fk$<6d F/7̛W'k^&Չ($Ք ѭX8nƗßݮ|Dh%fBg^dvDW3.l:Uw[b A2p@Xbؑ ܑ+4d6Ayy}hN6Mo7椓`|`_s*mh!l`rKP` <[ĔY67Gh n/|񌲹D@ !I33wn.g }>޾|潾`z[3Jf#A+#0$+Hҥw%}/>7Y2pGPFBotnkw=|¯[:#L0f(DbiE:IS?QY3|m i 8Pm)m;~g"gld`X&?)Y3Ǣ_wv#[$돷]yon7X}NҜ`!e K9Isv#4~R}NRlg;u~/3 զ?Xvǡ6ynU]fTIBP:A`"  ݪYD< b7C&V )MNrO792b$! MQg|ul56V=}6yq8{|u-%3xk i O-_wk隊Y#QA'K%9Ƿ.wZԮ)@݂z ZxjOW>{X?2xġRSonί{ȎyO{[?t/ZšyPBD-NS[qzwnvf [̭~\d"A!QԒZYVW@z[]o_o~zmS۲ʈ"އK OG{ؙ469HxŞ Hkv`!_[KmZf ŪRe?EgK $kM;XWo]:"xhAP )5G6kwy몧Lm7y&O8;\:l{{ZN,:O]rץsK&rUpht@;*<'IM[m5tfwM}Ҋm\S_B# &6Z/q>_|UWo~:/ 9+y_:cI=j{9{4z\wqZt-NU1kj^d򋮽㏬7zɐ&ϒ$ƣvy7)o{)飞uzu[>2zoyJ3H,:Cs (#`\^ֈC˧4', .fe(q/>.o>2tIf$-CsS1m‡_A:z+GJKӤ&l+DNP/b;joqڌK9H.d,/2A$IJb"C>ڼKͿP_&EDnm230hW؋s蠁5s"9IJTR4ӴJR߾ME4%HJ?",l׼W✤O_wt>dԐ*Q6P✤g+hc;UZإNS+,9Ieh\AE_vcUVK6J?4ÎE8e Z?6Ezֈd-"5|v1iCǀ \a.1JtXŤOrb;"oW(ʨT1,ZsDc.>ly͞=>h+ Σg D Q6`ޗ^{|_c.t8gC%2Xb&svG|^c.ңP+6ŭm',( 1b5aΓ?h@OW f\9$TB=DS֩s(:55"!vVgJDD@TpZ9aۏ84~U'g.f2; <̶%d[znsaAܜrpP D餚>.c`ULfɬZP:_>)w7r@݄옵^`qOrjvr1K ċtmHf$״{`La ̘jd5_Mrm0 ̾BASHeq4)KF KɅqe4E)' z \A,Q2xM9I1`BK؄qSN=]N^`X:neiwNolVў{Ŭ8䯳履<͌<xlCU2b=@J+g߷^ܫM,WL4bMޘQHߚ{T&= }+ :&˨ {om>ۛ7s{zf(R\͇tdqiCױda\ ß~[/utt:F+ #Ƶf>sxM}qRGQF #Ozm>l;wbsVz(@VZ ͇ͮOg{{潯VWۛ.odݚvfW"֧t,&5ǿ|HzįE7FҖ18ܚiݟG|solds)`nOYm͇4>ި)}1QL }M[5R;I}Y*in/^=$u1[l쀢ϓ{maG$twy}q8 KܥOPy@MQ?@i1Zv.+ ȇ0;96: m65louض}v@lGӃvwe`zk厳(\:N.Pv۫;sɩ颱KH1i(_MڌrkG7_&EII3̶ cK$$\ˉ:\@~q9ISݭ ƵbVI\B-qiOx>wGhIl2' Ŭ2\/Z) N t]{*gRt' rT# IyϘ;nM/b^W1cAnssG`3Z:nq=;qip"bg r:΋/ܙ]?G\ؙX׼/8\Mԝ#( NxsQܙirFZ%NF-eOy?])ob=Kov0&3~joUFsw]Hth|؀~ܤƧʪƯߌ*/_Of&Y;}UkOmy} gW;s{΢ts[>\{bySO|$V޽EmbpSs{3ϙiˇoqy& ۯ>s!M9tv=&yg/Цp{ݖ/I_2]u^z{-_KBLm wŶIkX\H"&,qa Pntq3%\ƌ]PX~؅/VN޿ z <\>;NL' [tjWuzyZ7pB#=Wyp٥_wr~썼w-@=FޤzaҟyG}pFi]ʋ0_vxdI#&S+y;E}7 ݁#~Sp>iO¢Կޣ柑}I?)?çy|ݟ;n+gK.g~o+|ޑ}oO$ /f7|[\ُ깯KqƱ/6]W}#osʛ@_ۛ3?ZoKoK{=c8}fxo?"iχ?o.?Tp?_oߧ鮋 <Ͻ;̃)L=Bq[Sz{Q蓷Zt^z=F$ bfO~l[sȭ}mGv{H_zG}wwmI~{L>4)ވΫSIRϜ.ãr ~|>SV;  Nsg'Vrn-b{~9E:/D DIOڬ\fL$uNOgcq<+WlZW0RSkFD0.'YmJ?w٨yD1pQI5+'qch׾:ORx0aTyWb_x$88͛t`Pi8\޼I>w;؃|۸ؿ{& 8;Xn.Dw?}Yq~3׵?w_y_So<[QF<;&?'^y+nʓt<ۿs;|+W+dyn0Ϳ '<^٨ eu|o~Snjx߸˽8ryh˯h$،׎qZww{^i۵ku`u']I>k`v_n'$ v pAY/AyΓH @ݦm|I ׆_` p|wМNο}s˳ץ!cu/i۟Aů;ƦL>5/C_QeRtGl_%K}Ty@/m?_%zT0&71ygȖsq&q^7/$nL OV'˃$cO0y^qՏM8yYpa/@r``m˱|H[=n?9bg5 (GrwɯEg#Ja1H9ݼ;wZ4R!A)Ҕ8͕yvsG4mb#CyGϢ';PdVWIFpM'Jud'J%E}V!K)ac{Q/c\Q{\/Wd*I߼ ^|KPwi]뮇鰁okfZНH2gBi镺w'i9ğYLW?y/z3ro};;rEk}_:#B<~/tWҦXC<8+8uEj?)㽜oOj=EQXVtTF+x)I*Η[4ъ<yCAovVwX[oܮɷѪ.Í9|䭂qA=wc3LVOά°_.+^˭Vvs7$}p|PmUONX2hXht\e*{ؘtuPpϝL_A,-z |V>sʽg^^vAL*W_\]Ɯڊj]BY%IZV>osa*vs>]O/wbjQYq9+{1n IwT^~=b5BTQ^cGszwdxZ+F> q6kd}x;ivwGaUkߏW ԹF\5׏pi8yPNi?'bOtc g{U4"7 Mn|jw{kNt7aǿ6Ҷ-fg{_gORl}lrqdǛҤm?S9Pǭ}tZxs_N??~x)M焚9d'ebQlf7mڔՋR9A0OD5E&wϨq5LxH/tG?~sxkFed>[;lUmW?q|Ż4͹;gYj02M_oY8]s[k^B/A0ݙ=o8ǻ67k^HAYR@]sr$\W*d !yԘdCNɀ/tIɯjICT&@`70jҚ;~ژsU.b`*sjϵ$NCN/T1}I8YCЮ, H5f$4g8pi9։ԲJb6_$r:͗ĒrI: 0|I=G@(VW\=s;p?ѓRjF=x|r?flǮ>uQbfJ]>z͓C}osһ~k-XEdct"SzZQ =޴mA[@>άjacDRj>׼J.ɓ޶HLkӜ4u-o9; ANUzy9UCO~yHq{1_K%o4V\I4VaBjssrhIEF+;9[Ͼ፫sƨU(-JqS|kO}®+\rUL3)GsOLC]{/"~ƨ*:p9#0Z 9)nrQvgTt'mc􇝈ĩ:XT&\G}x~!Q25?,` -{~xUkzO+@t#㜡kso_Oo(DD7 [hbJCϝϜ3xk腻oSf>2&*%JOj8]3d(Ʃ oNTg\{׵'Sf+* GVz*V:Y^3ɉJIorm=~;)̻O\.`!Q"ʍٙw҇<;*S'?x~0=**=x6?pfsI׼ ëy۽,)y B2ฎ^[W gg69-y|>V$Єp|yIgw;ݿGHЈh!:-6q gԵ񽻳_| {雀&9Oo}$iYHVr 5kˢ -5^P㇋_Z$* 0&rvZ}А |\NV$YZ Aԍ{،Zh? b/Dq Zu< $]@ެAd2T7aBU.[PW =(~rS/}8We}*ɛ>kHDw|v2#P} :*d ]#CTy:Gbǟa@V0JbnUW98z?m&5޸cLЕ4\C|evDDe HЕ\=H߼#{p# 7`eX.` Е@Ry{S̓&Y'e IC*j[ZXb2(tAKMl cR{/u\j&@+BCl7P 0~"C&'ǤHJvYI*f$j FgǤHt1<ޮ$D./'F:y{]{vhE  T"Cf&NB,}Y7 پx H\j'E+4*D0N"%>hGI`IDRuDP`jJA PdSSG@jhևs7>z${g#6 yF?|S%jp""!ǍT+j ruK ;c-95{&l+JS^]y㓠'PMbg^( #O'eaDϩ^2|2ɍO9rH1ٯzK7JtH>2'`|dLD9;Ox;w\YmJ{/'*!5.޿#W'ٸd4ؓ+j2_VGHٓMVўKY7{PHvk/@%)0qG.Rf' x-xq:'fmjcs a]%Y)"Rf|INHKDXI&qC4pu0SíF4c2tRZke1"p139&i@JZ[H5u3C3k\ҖR%#KKe `3a6 ~,ApZ0ňqN䄳C6I.TeEY QP D6..Ƅ;-()݊EX"2k,7mVd$Pbs"ɘئPF!qwAXD6\$.gCTD$p6b*I:s,Y!C`Hdt!L",Mg\$d{R#p heHͬYvw0f{A"@$jҐu.92fQS:jIB4aTjƌBz uqiiWt\XtSlPM{̦EE 0mB $bJ.dLcSBIrI7aJ"IP]lBT$lJjx)sC LA*ICE40J ĠLWbl =[&6!!W-D@N1LJBBa*=[V.#R<J s367 4:Ş3K-Ţj-O|Cbx+)IHA%4JJHx֕ᆐQTOO|m^*,&`uoN dI)R.b)$ HfPRu'1fhb>=EQ3A85kIF*)?t` tU3\%TTu3f}' z* ^ju_&r$5W +P߿?W}n_A{sAQ=/M.g]Dpd$O 4Z"ċ66?jsײ|:?b/d&i|*C^C=3Hn V+C^!5Qɠj<8xUh*tgĮ/{f#5, V\4dZ5["t- uORhXq8ݎ7>ak`x5Agj& Vug\pc'OxstwZ;<IՋ?+'Vsb=9^hX( pƺ8_tG~M&3V1Y ؐE:H%0"=|<6GD*s0LJs b QQRVA̒/,"E1ɘ{,RBj"5 XdۘdPaS J! |AG\Jmc.S߃ELDJgQ8 d#&Ru+!wN2 ahÑISVN-VJ"9nLB P؉1sثΚrVu!'} qbǘU;Ӕ$:&[!8JCbY$CV]ʦ\$2vIŵeKS1*ZjEBudXaT%(_)˙+kv4vWLϒ7z$_DPB ;r-'7 z# 3T-@Bx!w$KhI4'K,⤡ŪA)052 !{{ܹS#̭$4ۆD/VٷQMyp} [\Dmu$%Ї~W;aP&'aE"8ò)zWnjR)BH9]߫޿;L*5C-nޕ_7̅1W }@ICEBI q)fޕTko Ћ=]p0P%ƌMII7VvE@Ԉ^(A *`ljw'ο9CM>1ggJqq?2%#_Qx s'#,VwjnY.ڐ&D~qoܵf- /bv?wF yF|1b'>ho=$Fop$h'm2|׎8Z"Q.eԅVmu0/O/6oT*hxWI]>}f(=UzOrA, Qhj(ߖ)mrxyVmgT H 03:2}fͽ8\<&9N8hH? /GVOnTsE60E)Ezq#>oAGw +ʞԛ_yrֿJV+ Q_`& M7ҵfn1hgF8]X J7KTdHFF/ nM=!0MsH` uЂTE?R2 <)2@Jix'ʃguxPG'ZCB|3!"lq'ʝ˪#^CJPqi@R y*#&n \|ŝtzP߼N$8 @XPæ&NE09㘙#I2S*dolZIUrYTjy Ɖ#TQCȣdhBf w$^< Jdق%羯 qѹ~Իx[-(Ep6F*AKF-N^lg[zWg"%:f &k2%|ȁm<}\?v]I^Q=Q:EK3JRZٛ[yDd"GN2@T KX7-_hvZ;$^z7 %BuZ[O4Yxr;et_GLtYػzN--:Ac_ٳDՀMOvˮ~5݈^r0CHE^Ttlkb]6 _D;c8 U҅[We_$$z< tliu.} o, lIqg 񀌛fIu.^tl^_ 芮@>EqP=zzHr:RPְ5hJ+ZE>wPQHqP^j"lMes >l҈ڡ\$Vq;!1Ǩ4:7ԙ˲5wLX/ڶߨ 44+L:WuhDemK-G ;(8iO$]g׹0A,M"jK-zBEI^:5u:W^O$,kւj֛=@5ta$AMTg&s%H$Z,e굠jC "jR 5P,렙$ך"s8Ws\d5go?N+|=~ٿ1~^=, fmhq4WQ'- ^8=ԫ&wYUݫj^IU}}ϧH?vPGD,!)ۓӤl<}eln;֫˻.%FzL뤡3ܼA=O?h) ~๶/d+K3ڧ[ۿ4Uj;i\uH d|cŝa_r=~27gnyY~^\l&]rzkjۼӯ P1׺LZH|kcv:_Oo8چ˨qP4zh#%t sVĶ{-Z]wb!0 fP v@u_K+fzr[ߊmwVWeDEI VQ˱ _mu%#Q!LZR:Bit$bx9[ 7Zt!r.=c(dw6t}`8l9>rW^ISF)6`IiF²VeW$zXBIN?SMȇ/nsR%}.:Zg $!8Ň=E<77luIeї% &9BcVYo~Ri tuH0oܤ@O%+2} Ú8Ic Xu33Z?f>YxwvX˜ v+;rpVjJUlrC$)afFWV_=rSU A j܇ȰbȗB̆;|ɕt] N&4(('F$TI8_!!@Ý=M}fƈg?:b܄ FKg0SH=7Lɕt睌7!V&6`#,DZÄR#*_"B˙\I#>qWGz$ @H#IA!0W!CCar'5Z#cP6n#8wcrTT;KntOoq#P] E Y" NRIoQOŢw¥vl`^%SsK8G$w~_~*}}F7cu@.%LRC B}M?{sЌ?5CcER +.0-Pç7Ѫ.\oq#mi()abĔ!P^ع (_xDN41@p&T4"jT^<ǣJN6fjXle;wOpsoj (!k%a -1 u٤^{ԏ'n;I%cQd PIf26 u٤w|QNG]ּl2.*c1av3 L&Zlu٤KQz OTb(Ո0mZ џҎdI7'xT"`ђUXb SIb/B8ҡ6 Υs߃Tļ9V꠴"xҒlO`n[Ǹ䙃&>9utN ھ^+,^}&r7ҭ4SIbE9e@5w[I4"YuHW}9<gݜ4A~cN}@Ĝ4ͳr,w#ASa:wn Û &Bl\F::s *4ᐻ1I0KjHo&tjͻJFX^X_0vq q.OK܅W|9Cq7^ʿ:p|ؿ6%BkgO{rֆqde0e݌ڧ%'!(yK>7>)E tܿ,Di ??7I5i4sµd֦Ơ铨*37"tC@^%hh^3p>)-6æ3Uk!F Oa~C|fi@%5 쵷z8Q T~oCas^$J@Rr^y=2(J,Ova^Ӭ`ԡ?~qUه(0yDi Ij3co8k\:}h%fI0t X׎qjA#@0U.ʡk_GRE^(bJX"E^ɱ<]IsRol ' Wa WH{:/Ķ^ճ1q&W}BZnGdLX 1pj;yW;ocN`􉣂Eþt NW!wUUK7> >q0S0S: ˴ ^6<^:_M2> V/Tc•H^ ICCnt@jxq^ {XiKFI`X9NRۡ{=siiB՗|%b'8L'բP6z8kn;+IEqk"IP-6=5++ҍ+Ӵ+ |Oڰll9O_=Ej"D?19ܓF]gΉ|n:{g(}+CGrpRfisCz7<=EjMdu Qjy1pk9iɽ7|fc𹺤]R7i7T5/'hΟnKSލDyor̴aI% K=7A޷†in9@t*"^5vt)Rٍ7Y>'/ :3\+@MJ:s(2_10C>mQ$ڰU4 aX3+>S6N3ҚyR; Cqr8u%q:iwz ЩֽQZZ.* hN+*X҉a'osntj}}KdiirTSh8WkWRkÉֿCXPrR! 5!v{ԕ4`*U{YI<I #af|m If󱷯aȣV<_ ъBauite%&(FAR S zF%X$%f |Ht ;(+\3kW&ᒵ"ϿڡGg>EL~@eޚVŗz|t~"(^B6*r/[o>En:tpozFd/~1-U RI ^k[%[uun?[3jABD`uWOS2.R&[޳מ}* 4@Ȳ75ԪԘ=wD"e&p!% kMG(~%ҡ@UIb=]5Bt@ʑWHŦlEd@HKAKO+m\fЫQwC0$J4\m#w JȒ8ҍThSʆPGlyU!$>Wġ$MYJ"` _]j E#N +]I\}%g'bĦ0*w&au? inj Z~h[C̡rѸR1p2`ˎ5{i FB CذR2ύ_I 'dqFUL4dP?ŁgOiVPyh|STٖZ _IOVL&P*WSۉCֲ$hN9iZuk| C=f,ޣ94aueEiWdۉT4`םeT`_]>s!xjN 촯ӤYybSJ+h۰5=%8{٭#],P f>VuV|XD#fg֐p-04aI <`ܗ|$?=tNpQn}=# X~, R% ^\trCaBȨ 93Ee+r1 K] oq%YE+7VK*j& Efh<kW3,[0TeᲳ@f 4`"jMJRg*)$H(4)`r, 5зd` 3jȫ h@? &bf{mK# #q3k4M]✛4!0wqi1U U# mMX'p>sM!:RŌ[eP5]u.$SQf_vdxIpk^M#8ca eSP68u.$dZvdDcDt " 2ɔ̝BZp#F>vP*E%$H*1 t0 k]Iq\[E6W륣7o~$S(ɵd'5E~ZX6_w,C0 k]I!ݚPNj/JC&N$[F!:XBLK[ _5+vǺa}Wf m[͚"P0؏bӆyZ,0Y6ei~}uw*#X[ːSz^4,4qЧ12A?6f' %$A|fЁ0 uRT(TN)^d hmg>f,, [ZkKu 9`{G57.!$-^%c\ pJOQ\)88^Fa)FmPlPfY Q8D e8H' <(C\"$ e4,sr LT$*S;ZgЊ" U(3E2A)Οu CRFt>˴SK!\Xa8BGRgVE4n 56J=BcQ&W3 NF1#eᖡsx- &Wb ~2 ̀ۍ"K!' כ\IAddˊ Yp22"[VJ Ȳ\ItFBtIUp'+"\,-pRk7MiiH+8y2"8\z61!Y'a5+20.={87XSvö8[+/y^b]YW5u!57_^._iW֭1⟢ 01qz#z0#7u^*f^f^*k]H=ue_zro O\\ jB  OaĆ[1l& ^x־"Gx^|꓿ eV:ݽ8a4?՜CA^s$\,V`n-ֹUEO&+ZUV~+1цGLLRɈ,#9")kεlώ X_h@0}*Ŋ_Z.O[eF!g7eKjxɹ1hV30q{|7Ill@tR ݀Da"*Ck '0>@HE4,1[ɘq{Ҥo|oG[D)O7mHrXa F95P0C lR !I>'MґxNC_-8@aA1$GX+Vp%<^Q1 33N|;=i?Z+}έ~ŭ)[}ѡ}8Y Øe h)M9d'4K(F|ʽ4i#IT,4O<";[ UuȀ:Ĝ.4Gpug=?Ď2nŐ|vYR#GK֬䮰{dab;i?dxsl˦Y7?->7߱㮇@⪹1]jY]38xbAg9jiHjrAD {V|ꐑcr4)u/޹cSS <9M-X]W/rVJJQ'bfrqu|gRy5 dui0_,U6jHi^bg`u36jSm3z]^cg ZiwzB Ըƪf=xՌAYҞ'V-:=$<ɱN)6YYX}m9Zq94aV0.z ]Wb\W.ݛop#B+):SuJVѱL ˛^~$zIQ/,nSb{^q`I;RC0@wi2Hy/z]9eON([G_1aQ'U*$z#Ac.8n2Hnz-:|mQYZaC@z O&*(Ւ IHpꩈԣЅv+WO$O?wڴ^~M iЪ.QV'̞3 s*mKO4f{I2b6Ly(ZJ )8fnnlg Tt n{-wwјMڳ$yi ΄ \Fxj:4afŶSq0w{ИMB@%2V!FIK$UjR-Nt^.хd'kft ppȴٷf10+{H$0UXmai5[6w֫j j*I}u$B"rKV@6hjK 0„f~t#'t!^ 4llT5U0AV #(=H螓P̙5[ZD5cBM5"fU$3,'et#! #~@50 8=-lЄ*2-Zrym(IJ+@D倚8L< P U[G$ XN M﬷X9~s ۙB5J8(@ dd[uO-< w'8Ư) v]ո;>v> G7H&ц\I ZQ<1+(" lpo7& go򺏖߳Cnց6.( U6~t(B;wq࠽͗%SkDŭmSv-JݸϖhmΜD%Ţ רva~DTvC'O&?;b@6Us,'gEA!n0ig͠y01;ŚSYa'C `#% n?I2Id% z蜹p\$þO2K} JC+7ƭJ,iT+V#v:{q2[mXeZS\9x`Fi|6 k&Z]/\GQ]s-I&꽙#B_K~YA4剪 exwۓ?as. p,tĪHEzo& xv7 `rw8r'z,ᓊxa3h@2BApqe0LFXz \4wي'-NQP}aH D@j&*\=a0w憨]IWɲ{9 т\nPb@hb 1[QG#gR:\dVYmP\~s47ںo-)zle;Q*n3 Z=woE@7;/:dSʖLx~ٗӢ|27n~Q85*uk-;JJ! 1!Fw݆e?pRWiLĺ}|t[RnRkwB_vkNȜP_- ŗq'IS]iqAL=s]>: e]RCn\{GΊV966Kb\feqKDaNDf^ߐMtt;wzϟuT>1\6f 훯>8~}@,raL/ /)F28C;N?:1DA5"ՠK wB;Tԑ6)Q>)ői€U _KImsΖ)Z^d)?fm !~K1i'C6/;{fډNq8<}4V%FBz q'HtK6 EdDԑY`nB&@H Y: •}2H:/Ԥۦy>r*ʭSC-(MNhɤ2‹( IH@@>iҸ[n/okGN][a.*^{Ԙ1]+{rb7G4 ݂h~s.PS9B<,>ꍡ"6T 0Zr#_ō juPň$I[]8a*ň~7Ҕ-ji̠Q`y5l[f/48OMrΚ4OtX2'Ƥ8w|fYT @;f,!]6$R1{w&WS7,s-P-0t ŠttQ'QqVd6P̵ kapSC"B ZL탮_]V C2p<5$H>X& MȇmA@k3$;ӳI%FH q%djK2(Z2͸O6 ݃68kqs41!,9`~0_/u{?2^IÔLܛ{6C;.~`-lH,H}3tr bp (. jNv^O;<SIʹ&IJBZrX x-1T)"nУPh_z`$rmdkuƊyEiu6ē'uU^#ȫ#& DA8[{ •kӤ4Nm} "e9ɓ;mlD(Wc2lE:j Y$IZ}y9FM eG09ʾ _9R a(l de/!F+:aCrmV[ҿndh՛ŨΎif:сP&zk 7/C&nX>NUA}ٟ IDr|֍@ˌ4C9ԑkM2dHuf'*?>Ӻ1:h4sICg@$`tJLyI!Qۓnɬq*?m/\4jҟKt$$UVYG/Լ*_jJWǿ6}!+Q/`g);*ZA{91Yrb%eN$脫 oO1(*S? E!"7q\'\D5!wU0rd & OIvNj$ #J_;z 6WleK H<4IA_c $aI͙h8Ur.虣 p^;C2$֝MabMklʂ ô,3yI.zߍg. RjBC }a%Fl)&I5ڼjE0+XB|ƋJ!+tfE‹Jgg9OL< L&xQ#pj Ҳ.rE_md\Lyn92%5Dh:(!RmYv:{5ghxd5iz/: g 7P Q= yu4vıނa5/C'mJ4]EDJ%a0 eCq'!/;VHmT'!OiR,n*Tyuri?]_b tu FR3:dNgxnS]8Q尀cUn(@B?[Q2b1G &m7o:>;4D'ՌW#Fءu#(J>lhǃTN鏂Ǿ+O jS>`Ӗ{x".hô&FuJ sغjuqJQ%E>'7$l/|ñ™8i-F8Ms`Iv $tJhؒMa֢څ԰;SK' (Q 5 P<Ět.4RAfY>\xORے* "ezES/r Y'@0?A!ABcoϳ#g=pz9]jP?j/L`TPBjPƾ?7糧}t:k3ȁ*p*&jFzqJ+*^מ&5)0b\j@uRhKtrotfe]=x>sTD#TEtm0J"&"5c/y?EjŸ*P€Y'84 'ObfEsE&F]î{wۻkk-r(HdHK$D) ƥ 5iqtWAH頔(K ((hm5lm79oe~1HKbra@$9 afDKơ5iҞK~U&7Zm^,|N i)GBrPmI~hm>s]$J#OI/~.1A_Ag0+TJGRҩ}M۝vڮWNSb(v"O?:d"0' 4 dUdZbam*ca}vJf7V+f͚Ǔe2* 4?=g|M2{޵m#{iiRacNC1.JO1<8@ЭEP+c>7u9Զٍ*Ҥèw!t2AI3M8M (لAM>5XגhR?nkbvLҤ8aK**f͝,[8aEo"Nrdlڜ 1j#d`ִٱOK&j@Z'p(_%DAe}ٖlc4ɡ?4 SScSzmo4ԞK zFR* + #NⰧQ.~ؘ<ɠA9}ߌ{>&TKؼ P66>G n7&siR>C!Go)S1fX$fBҔ(R4*(4Q[p/r'S;>>%<|+Ys_ ~ Z]T%L]eix{9uv95z$Ni,6Pup^r5KGHnru^zsbNc6bry:WKnpkbs2|w L%s Ǔ^INn=?Y0;n9\"`Xo]^KPI%q+_ƃuN .b֠(nFja rtbzۃM.hݑf-Hvځ`S\gUȊ6#NSZ0> ZeO =r~(|&rPf2?{_ SA2J:Uf:L$Iv9z8H1V;#m:{ǫR"1ME;|-AZ ӉD%+~ag +e,MB)SiFe\vzwt_Mo^Ҷs.H>mJiF ̒=cHu6@ChW1/OSEB@!@J!$bbDw*aW“x :7/wȨ<!*d([ZF.ĭw>{ݖA)%ytVh[bM[JaJy?5g̵I%BRXH՜IR@*A8nx K0=rh %&k y>"QǩD)UܒTL>>lS\kU 2b zҵPJ I2B,64sgײBjzGQIăB/uQ;G{~#eida&d@E1$9ĢA HS1}#َ ῱/!2` 7SuqacUu^G +AE+f^JK{v@:#}g|Ȏo0{"f%ovD3F8iHJDɈ.h" d`Б!wdMWW *K(#E\K0a=A6[ۑ%/M".0S 4)  -#RN7{3_w;2HK3&yζy4 >$8ww'd_JF&'S?{*$Sa̤eywbЮr?$%Zbh(x*BAS"ьed&_3.OnY| Y{Ajzk-FL^yZ/ 0b0t8hLbM Pbd;,[NVт #J LXj)K=X+dJ8{`֌ w>/j+ ܨ Sd1t)Պ5mf3YӢ4fsTi@HR:R4 69Ӵl'~Vkoz_V;i3_ 8U'I}W!N߁39$⑭g.  ̧V!A1X%Bi'$bF92eKu+w]3>'!cP0N=L9f'<蕺L?[XeFW\8ǸPaC^^6u]&i}ݻeFDBM UMLRlE"% _Tś5Nu٭Oݷ[DP6qC@#h:9sP\i}^ï~Y׏7uM+SXB b{hݾ/WW|6oiڤcp,3muxC~gH>ŗmz#[3!50!JAXĔ~uhNdLb,Folp>uC7'2:{֠WQOy]=-Eql0S7|}2kG?oaDVP30ΐ0> ,aJq6Yڽ+\wjC@8 &K&HM ZOf.Z&Ĕ !Rz8vS 0t;R.$5err,[4K^1&/B>n ߄Ȣ7~Mm]?ύ{% VЍ6O*V PppҚ Q 3f[=vC}.IPq&nA!Y#aO}_}I11+esLTL턠 g/&sgH\ xe^}}g63>['$DD З"f4ǀ2A=IӰfPq | u!@jKr r"q-"v.g!dfeyR 2/J" l$< ,H0rve;f!s7ALL _H$]Tc* 2 V "1[Hu 4 OPh!uHE(n!Ohjr!5T` TpP?UGroiR!+p:걸q&R%b+@ _H@1iL[üxM.$skX!䆗(D*knL[ō6|^!51:[$ .HMY 7'1`oL2 Q)fwL>Ak&`R"'hCnapm_[%c-hAҰlz>Ad޷8R-4)harT?gf 9-X"? RDl>kOط֬I= [=qa,V\bv7~kNZ JX ,q$|)4$2\Mzw?}WYg u;H6n( ~5 [v^[M_ߙuzW;@,8ҵȮcf2QMz1ݮ~p$q.`=ȳM+,޲*Ib΄ǎ\\zWcHP\ž.-L?x.4s~6}]lӗtSdA\-O.I=qpKTƀjۄ F+o4=eq{XH&!/S$ [][[ˇw&=S/"O*,#BA0 tÇe {. ^d.zXD(+φ|8x.-y1C@`9J!JZh\Ƈss$4qIЕ޹$l; g|. e-wJ/f DA;6iI ;Ӳk@oc:Io`f2%@l&/ytZb& >GՇ@$44`A2w|iZt HXJ8vXzܳAlT̞q4' 3+I@ԄB`9)۴lrm/@% 9GrjK<#N*6 6sڽ]MӆCݜ$MzaMt O|'y Y/1k?=25\5&d%y=tw$|M)A3a1:t_)}\66 J~p^WA-i0g`OϠn6/Qtd:A{LAk^$`u$;dƥ[ls}\֨txVs9fŶP{[RinS|{Ӝ4F%pRi!WD%qIHbe H$\$˥) s&#VэLjcؽHષȮs*?Όt']i@a 4+^ ~[7zB$a} ]Ovvuv&E -NqdUSf;իڝv $0.HUs'#UnD`/*Iɠ.8yʥ{L B B5(De}m_s>ks~H/ }xb[ˮLu#? @aDF'7X44׮ɱBB@rP$ "`dHN5lNu#Y%3NkOm|/`#dtAkˮ0Ս$̗B@`l#+ȩ.$d+"^}yR+nRNɉőݱ}ƅHCU@ d4A6+OO>X jN3ל |M딾 b4- HPѳæ.s>wby]X `t0mZQ|ЍA'Lwou];9@\JOcj{*wӹݼïp]< <L}JBz7m4Šlr&Ŭ'2O3Na\55_u/SNY(ֵ$kS%4 &\f[:弩;)}(O:M#i %` BۀcWu'~*^#W`Pг):_h≢2,4IG z{zzjmy; 'fd jiXf ү|_s!2J! $Ðgd$ΗD AdA!TΗ  !"%a [ 9l@m;M6,\4q .G(|5/O/ǶНhaaKkWja "82/7jk-x>P ]pmG}bˍ֦K1pëO%@8 Ǔ+)u xƹō̠dSόtY'\sRǫD?/QsOn򖋈 *a6! &`̒862P[]D}5>uɴ )`GwDǂE&ϰzά?▍' jn뤃aS9SJGǙ4kGHS5z %])ꐰ#YI<XǼiW,UvG%gDNJkIKنMཇ^87]FPbpRkdjlK٦_}piZ7,k,joN"r\$ ;re'=HfX,5LEkLA㴨0w.r#pYzB* 0E)ȍ(^fDjybk2i5'R aT:&w'L ܲg/7Rc2i]Dtq/im)Y#̞/ˍFr3>Cz d"y oˍsu"9TsW,XZ|$,E /L>ɿHfm+Sxj$fOXW[rԃ4E&s>+.?FΉ'i^Jmua' )}c@5RbI)DՂִRWn8svp)! 87bu b@)J5HǚBW339YRUM )F;9x7/%Qs0x3nǜ;:࡮Y,YzWP1J 89Hڞ"Mi(>v:I%Bd(;993 fffI31k)Ǚ"3,g YɗD曔2RD`d%9R5m,d4f/W򭆶811  JꋙŎG̏͝&;փ˕%W(8K0v]{ͥn֞c$k9)QcgX:dȻKv{nVv @+"̗նfc|cƗ~y5#:LH1&NJ{.ʕtڕ>FY%޼SVm1t`c8E>'WksZؠK$aYe)nVdvZ ޾ʕLmeߤA#1t:ás^]Jje?fztp?EmHܪlnwu+)un/JS^tNN2CZȶƧʕd> ) IҊ8:FAN,sQvWWWK+8)#a}:R*vCŕd^\bS6)stذsrGr_\I楗_1Bs2G}a NQ g8s3Ћ (+6"4f+Wxh K{zgzןta/WwTS/v 9W/n]/?!ᦋ[}2N;-%70|ұ]ݧCfoQΎakU 0Ԙ;WnO_P?!h(cp&yxd+w̞ԛ9L8|_•߇w'}%0y u<oew鮶\L[tH@sv+ EKCUmaEv(>IΙWZ~o+]'G3|#{V{nG:W쮿z[.6ꀢ ; 1DCfݣ>Թv}C_2nd $ ;-X?,oG\I!k}L&owR_/2(3.PJBPkV}A,28[@0a0k좾q'nw'!6|,~O$8A NuT]y:}ߘ^鯿ڕt}gջnp_M%߳E=}KMĽy{]I{=sq&#`.8I6M׽[4s{Qٵ<"nr{QWm;[JZT|#0H4c! }!Zm[T60hTꢆ9\|kS9&nwA{*St?FW[\YYkJmFduDu}҃_3N9=-$ Iݚ[7w:h\7 N_Of\ڞr _MU"ÿd4'*+Ժͳi ңPƋWpwfo9n%$ ̰KwuMN!wr՚%%IZeG>zpW.|n9AǝM$`H>[7r&>Ϗ\O34ξ߂E/4 #%CΎw"~zԝ~Fyy"[i<\IS-!^ُlU K[@9#ug=Jwu7S56RN);B*udqʔ??xo{.rw;?~{3J TLO3{|>[IԺSY{Q(BC.KslHպWCB^ɨE!!v]_dvzyk,%E*@{e>:?bM~(RHn=%{L'!馳o3y(Mez$;0#DsiS|'i{}(4<VI9[Zlu; =bС8#<1ٮyO]-vt_bx6d!2? :^̣GNס2@ /Aaqm/~_^aׂ(I(7i&V/_47ɳP!.8GTlS+ =/뎷+Jp"1Pr;DĮ^y`nn~vAH"@VT)I9Hulzf" Rb& ~9\:wA>^\$Lz%s"1>:䥑 Mzҳջ[}pP8U!̽gs#W|Gs[/SCw)ZF%/O=Y^&S}pPj:6?,i.8՗I/{d˞v>Aj^>2" j @-1ZӲ7F|ΙYRlL48@]*RT,Ի(\tÑ7=w ?:-c|72<6sh7R@ZuZjH;sm!ç=lң=/"4O30箑ջRbe=v{\]wYK}zZL[kA$A4Y49^7x6ѸyO$ӗ4ǟ.''՚injim8kKjeZNf&6OɷyuNXΗTqφ\=enǿ%ŷ]c*l6;_뻜?Oso6B w+}hOx`U۬zڻMtKv:0H?5d./Xa 9kܤJ@3^DR`{lΕꄰ>sxvο/72y[f-bMmh?L=x]ww1&u%EVIwiO^?XC7%֫c~5 W[H{}n Hz+P:HYH8^[Euqj]EjKM|&*bOnғ͈]`[Â};P{I{8,Q}ڿ^^uφ~뉋=r@*V" =H˟r +'s[ ~g<;*&ŕNM-y|r/"a!TbsoO [ 5_^*pp̱ت->o7GX8-R9䷷?-̓vߞ~l!-w kq[/{뻾tsnҹ?)oϭޤ"q(LPf'c̚O~$C|lqVZcpTflQ컣ajKWHaŸ0hTT2dd w<b87 [nz\log[K ӷFVy>\W% v?x$FԹ-G4Krel\[_h :b˸k>lp3F Vp'z[7=AE@%&Fyg?Oհ 2F ~.ܣnU3{oF(L i[,pnNO|Ǿmw#:f&j V._Ȟe:#(8Zg~aq&jҖoJrd zKf0,"R9eӷѹ@4"(`ȭZvɒVix-<éiPhXUd?}UYNiZlXʹ7мcj쨚Lw%~kS|B9>'C )SfdFYNo{c ׅ}N̴Ph^(jfxѳWUʃg%lnS %Wfڧ yjjWU"sZ6-[>vʹšy Txc;&c$ VoL۪Xi1U= k} Aozko.vp(G9X{] :;m = 1ӡm1b@_(r}aڈ)q'yf&EF NqW3\죯I^x7U#Oqܫn97{6z|osWeZLPw{^7'\z|H{~7FNS5C6Fl5ڞؕki>LGOk%6rM[㷮~kvC7[ʔi)]߿kozmW^^;=%8|z!%oK4f1b|oҔkI[]yFw}~oȞvѝHIk>Vl׵2dOxzޤk/yG㐉4=oͲ҉чLڮ޴w#\,=B; TfLQrx z>'sjU #2lDIqJR(600kGY'*I5aeM $*ƨVƉ"LJR;mѲć% !EpJR60VY$r]MZ^&uJ6kl(@E(g;X\M =ذ`xf'-_|-|<&6jvu6l)>{:#q&5z褪3lYvy~ KOS4|#>w;^xH{x'RbP^~|vCO{xTUxHx5{(I2'D*#^^?ϏDt&xA>M9yi慚Ų=8ꅙO,F|!,%daDIh+|H?ƈ TN &JZbC/A#κFP`"$~[^}é t*P]SJˮRx^9ک}=X8e^' lxߗg>[K6w߇ $ENcD"2|I%*IhtA'eKT"46r/Jx<, (Qm?JY࣌ $Y! d~j$s8]M g.tw-ol2"EϣmO_JFqB%JL,%.uQ]W$vKk_V x34Nkp!x& R}M\z &P$*<7ixC2JA3%e@8@OΓ4c`*%( 'IՆn  yҼvT0a:09O8JIB0OEX!&Cj=+f8X(iuT~fNZCm;+8XHjI `3 !Y+=,2 l khA>$kWjIcUu:Q[@K|H?*v ,q(͇qc8G1h ā`"Dm&!8[@db&!*+M,o" ˛nR0E,_„TgLx$XُA |MCqLUO?pﺳN]UBH}FF%kYם;*FX޽]e>uw{:miFW/Yk6#Q pJnBB@DLBmNj/v[BxO@Zd&u s$ޑD(P>\;* gI5P HfK,׿ٯѺnAw^pu=ӿ%Þ'?%*յZڕwݼ~up(g5 \"Aeё `=*^hĦW1rG^|AZ6O{ ؑo#ןZ #*X|ǯ=m#/yW AE'}ڑ)rjQu@7vnyG^Hڭ5_C7Uܮav<@N @'gs S^>b'jI7~x55O2W]  fZԣ,Ӄ nqڵu<2 ݁eއٸvmwG&~mC՗57 k*i!.?:@$]ؓCGM ΉT?熪' & oWCe82T='[NͷK CU9&ƥy*d8R66*IoLx\OE;mlR&*IoL3j\V[= SL*IoL<.{*װڔ22WI2L51MeUOEkD)8_%0sL<.mmB)$*;!vﲐ$ucAa3s.*&≸ʄ:+,QA_Hy?j|鍍7ǚGM3+FF˚jVܱ'Eޒ/aɾSTmr^YVx9ь}C5kKr=gi,MC?vc'!b;̆K:lnb9Ȩm-Q]͓Զ#tm$mV(a% yq  6ODnI'mskui)qM* k f iQ\8I"]D fR-"lV:ڱI3,a9nyp8 8aB8o)"".umKmqg0Kd(&]Nk>[;N9 p]NN/̺(TIt]7#^.~񶣚;p5?iG%~x@8"iҡHh*9s!FT#A1QXΐHìV)3]:1-}vLiSG2<*=8қ-üt:!*& 7bK"Hw>u$K#a" FHvݱ~퓾9Sx$ʱl~!ӓCO*I^.+>9c*INzOc} _VI__ hGI$c8J+:칟_X< Q1MjgԭPbHbE*#[giKճB@alS{cn>{QKyQ>z@TS\.j{3H%~2)'eStD۪TGP2!K'Ȍ?w!UnV6%`F;CH`p,ɍ5'OlM3Nv*/OfoH#=[<`jnQw#-s/jnkԞ_V=F5I78H\˪]}4L˪}7IF.s/Ma"p>=TU_ue='u] -w3-]|w@QG e}4z׈N>7/7~9[>L˅DQTQ}LVҴ|Qjձp4tIlQL%\+Tr$a( ئ,3{9B)ߠr_:*֒%k~#acLk?֖p j=ND%ijꅽ yFC;atۆn y4S`8qcC$Jt:CU@ QdX.p9$WԾN=s7X1sY&Piu6%I-qp9fֺdZˮ\ɺYV*5ihy;[Ý,^n̘CyHbnVhs|%Zѻ7)B1imOUT&u0r/%‚fKt.O}G+ehZd7y\#?) H:iM:ъ#c1M޼:=Nzh/8S ث?}4,2yչHovNy/B!6T>"`-2_tڼ|;Hz@,"IRL%JnwRkޝ)c3!|I6K 'FuKҰ`ԋ!/. 0 X KJ3@8` Y Krښm`TBpi&?:\c^O_xFiNϊ,澵[&v݃W(HVWH.lO VгXg ׹#w:WK'lׯ2E}]\]\w36n$ ;'˼1,5 %0/E:iϽ9# όO'fXQZR[ @:7n7;:9Wypb.S䥑=*!4ephQ!fܚ pDBd-(!wL#&ӑUBSj$lRR3kw/h@=/qL?-, "ZIc1%y3rO1?w̺}wq'UydIv)HE!ÕcJ y`IQԗq'UȠRDE5ԔEG.6:7:qcgz7p2Òʤ1"PIbzₖgOxlN*HlDŽJ.p."5^\f^ndi$XNpoP:=W3ݐOX׆Ẃ-Z?"q\=\ 43ߛO#.gr٧|8J#NS >"̚0 7X_yΤϸ1Nh.7%}R7"i'0qК֩ U^n }jXu0DaYCI:TZއfB$Q'.TUk Rvvr}atސML%8Θia߶{0Lr{T9ʁ$ $_DE iz<F3U'QLQ/x'&!dn'T->D$ŅyRYIԛ񻖶YKOhtwuhNnPK?ZF.h-[B|8~(G%0t3/D!p2p\-eiRIG[i5TP< 侬4g=+0pKu[CD2<۴f$ͰFӊ;^N#Z`~z胃`H5B ymԊX؃Oz oFzDϐ`FsDk _+>Ϝz_0me_Kzz)S`U>QiUv8qJW\ؒpE}o5<^j_ْtI7E&RuF7\u_$u9c^ =ܤqI : Zˬ|mb]o]<5HFdqߺ[61s^qqYsȍְF9-DgxӈEg]>x6JYŕizj( ^=`vйɽznRٚMl+ LIl?ݮ|p%S6r[TW[s %IVڕv,8$fXRK<SZ&G jmmֺyvbsmq%mr`[1TWɹ(āǥc 75=|q}=[JAz)'h%RXRfnadYMr`fw~v[{ ThW gRUҘ]aBK'?Nd JI~jaD3Μkcnz,˕I-CRBJ#n8lPd,`GOUդeJIIzQ(D|fSAR gD?D!y$dDЙj0faB-ũr2OI=5Yvy?pldq޹f~Mk,iccj&ɒ>?O%toK?HJٖ]x{) _M{_<|vZ?$O}dR9=ۥ(e[>Zdw\DQO{g7á7I]}գG?d9.X| m+~'%<lڃ?,楍q:Is~4l˝ëw\Ēi~lyLpbjv<:=Iƺ/yAOn^h%g7/i&9^P8qc2c ؖ: 5 ҷ1?٤+|,Z' ̌EEE-CL^dmxɫ((E*ľ{>dܳh4HWp&)L be7w~I@)%D 8PR7T9 xrlrKg~GĂnJkS< t>] د>8J@Bl%F R)m34md#\4R ԬTpb&?Rt.45f7~ծOy#9H7 eٻ3|~YV?ί2r)ִ(F*`i*^si퓏\{Y|dؗt (D DHUvڧ5?rU{Y{#Ty Cu B*ӈ ]}ëV돟z5DνK< eiÀ1WLin[yƁ[s:#YgDȍ{xSvpeݖO7:]}[eH||drv*3 Jubvn5O*+CϜs$GH,i)8 iɉ<5\VBWtG g% 2 ?մ~ iKhb G67Ēƈ'iVpj: #Se{ӹ. +2@;vr v+ؠ$V?.Ԑ=Dޯk7B£4b5JN۹ÊdrԂ+g*N:yږKGN$kϬ=X`iDnr֭gTL0kG#<=%0k|#11XDE}gRv/^=tO*=n+OՃDoWkanzw9׎8=uEW}{3ֽc+f ZfH6ޝjů~TL1uUO?Z}xv>{Yف\6mEc[=Hdsb]6"uxF:WvъS}G=ykpĊp$2Ԝ07HHraOs;Z1(ҾL"H"~sV s3ˈI3j"Ű4 mgg|ؑ 4l= da%o uuZ!1n\kZߌV jݷCҋp #KxAx8dQqX&Vum``->$l,()0ē,iixe d!n}_aR[DnoPdoYIח@: ڗhpBꀴ>r`RgJnºkɤHVS</ Pm@h GqJuЮBʮ1f7)QHKW&ba K,ӵvZ`1W@^+(ڀ4וmqT)oVJkɓYwꍼ*K:҄a+0R%EHukC+I JdP86#mڰya8H ay3-"yC)98 L"(<-^kN_'Ͻs5cN%50٘߯OoEͥ h.D$^$|/[F"yQɘ* JP(<=>zN/ RBǖR |:[#˹٘MN"Ia}]R0]X)L},co]KDk2GXfy%cL䕛:V_nlKZ9 ٗ$ӌ UG5\Y U@e&B*[/j>wT&[ehehb n Ea@gNETE)uSQBNٟRHDcOZ\Ym'z۔;1!6k_4anřk?CTVdzY9]pU!KX9\*ip4{}<2S3IV^N1*! SɈ-8-MER|a/o=_ʹj.kf.Qf‰w\9>~ {Gɋ.B݀hH"תDc̽S=Cݧ"]:0n58=ՐL[TF*lk[5Րx6\~C ICyֶ`/!2lQ>gbHDы2 F_e_3sWuc] T&G7m|rػ&f|Ϗ~Լ<-h>w[?+dHiRӧlz럝b9w3~511%}['^y[h8yBt%75ib~0ο`Apt韸wLIF߶-<Ժ(>2P\+dHn %8 'v =s{Y:}USzC${2KM<~z6M_ǔԺo吅ɷ`ђ=|[s{s!&ŋ;f{bc:l36uΤC8qI4ps"Yx.b?maD]=Z̔E$C6RE&l"h8!2a$¢ hk|U*MA8e(.K&LҖ6ismٟ~'^=/% !}ſ9wX-u+=Z`!!U$ꂤf5aEBVO4u>s๑mBbSNHp&%d׊*ID@ #t/N|xʓ#Qll# ]NIW\)sIZR/Kb{~iiW,H%JlRWW]ӭ} 5u9u43)EºIDiz%FISF:WU,eBi& ߱ (I>gvW Cf]/7bs`3dV /y;^񥿼3w྿0\{k P`t{c\+x^UP<5y.޸pg6顫.录722&Yykr};I#/5\~J&g p+(Gv\+&]2XOn $gfPI2T*F%LiXvw$FOW%fcQt'yu33% U`x{.>t'N nX~<$ BaoǺ#>7e+=͵05t=ccJ tl6Nn3 {B3oeҴN ׬ ALShyq}cWiZ{0Pу 0SicaD謇8]EuKi1UUL&eX~c֏C=5}qG)yD=!ojf?ǔoMɛ>o̤KS V(d!l2aWյ\}dar߲Da_kYF+3PVsNX NMWabybC;rkE8Hf$~qTMP$U _&\;w5=:>^ EfS5K5j0$"8A&Q+&|h/Oy)Q?hJt8?@]W~euծU֯Xu̕!U$oT$nJEcnT4bǰxr*FںPyTR݋8K|eԸ:1-'6RE9SgHAH;8C Wvti9)OK+~v_Y%P?b՞7[&8xa؃ F5V9I9=:4g#^U1ۍ`[ukR/X!;lB}tZTeL{v˞G!l9Ã<=#lN(|9b6Ly%^[hwװ[ZG@T2drVj_sm2<$O%nWL:/\-SV#^2 {t-I[NF0g~V~6S0&#^IS^^bjn8Lkh˘'[28,*nw8jѪ~ԠqEc6&ƃ815G=ޚzicb {d-G<| !f%K_֙e&XS\:ƚ#4g8F2na X7%WlJLMӇS%nxP E Vp]}v;1FIۊz+ LM=vNHOF1L-1=w;7dLH"Ъxr>rE C& 1* ^>"Sf;(^b\˭JލNWC˄_Z&3HJWw[QX3,L/CK'b-&Pl\*S.6B )"["';ܽ|&Ak*ًe@A.' AC!-)%$O[ 0^?Z3"AZBj֔%~n_[ sЋ ;E5.$J"-V? =HY,E^-#! )ئ''צ ac_ 4:! =hf@{w=2uz"KRk!f}ܨ>?_.ȍ<20LSaArk;gjȺ+A=2lAB1E;3Gw9mE@GGj l?;IWNe-(t :Il?a;|(3pCʹ MڅI7t ;ƶ6͊d"+jbY'~Xq~TfhL PMyU1stD2$՟oM{$YH nŔo|P|{xr2C_{\yXO}0?fJE/6N<Ȯ_nzo$Hz.+ᧇɑ^rp)Sko'>`k_5!{sx7yKwg^) l׾f+:X)'Tܗ;ըtJ9w Z>'+ÞځK~~#^}EDDɚݑ}=S*KigU |ȖswR>)+þ+ǰq݅=Y1K+}=uʣ2)ɤ.}>;%SIR̸wy xc9]:>3u+;pxh}ٺQM}f :wȹ^cTveUC@ڸ5Ǭ>.cT6O$z}Z`蔊Im_{䀠͖;s1H~ƒ)u:G5e Y g6,*2ޛ#`OU!#j#(+UY B P$%ℯ"-rd,i&k .bsr@\o9I ie|qK՝d_2 NzD]VFfk=;ZRfYɀB 6Ȓ%2&OsC9aEREަ=?km3ή~ͭnNm۠tu78ٲf|d\};}7ۃ4IP\G~{`kT\dU:=Ū@-q6/[!UۑBw.l^\4<+^굟99ʟ퐼oGE LPͫc9&?(UBÜαXI|P(*eM j}>0ʟ~wiO`rT9W7$O!i%$ ?\WIdώ#ߥI>O `@}DF<}c؋Ӑ'B #^M'qW_|wiKaY AfFuoTfƿ+z$#XW[?yѕq|4i ;4(j(L~X?%J!{qWܨgqE#XJA¥'̢;5rJ(_=++Ou~}wyYj[#9ln~LxxA57"YwԆ&{JI" Bgw.]Q{Pk Ijk:Nsh6z<4iaW[^{ʡfOz$`@7,7ow+G{*YXVfYCU8q?k( ;\7JۻH֌8 gq5QD06,9{+  Xi-2 C<xh$` f]!˽N^c(C+dzi_+PGeI?C)P*5,ׂTsn]N:ZSQ8s[>al8V\C]f,ڧQY'3/oKF}WF-7?yHۭQ͜`\DGqL,Bd <ʀwˣpX>-%8RIƵ#}+zK]jBփT?JLq/.V5G;,w֤#}W՛ߚ}pB"I"ոί,챮{Vh/[ppE.?8v۶!' rfkB=/B`Ѳ?r {VRC /0V݊_[=r%Ap&)xgf)Ko)OOYdqW|[z_AQ"±V/ 08.wMbQ!$ǰk5@H@ vV9{|nS(}ffZ81p+fٖǔX aYmJkQI 5:A5EEP% L()^ݪrv%}T.Ix"Y(a 8M5z]Iq( 5Sz"#AIIeڕ4m#:MD.L2pVuiWRپ84ȏ%W,A qG ԎҙE&jIը/8ԜWe^+QJd1 ׾W %ƒq`!g zފiiI5ũ8t`CHǿ1%dP4VVE bȁ'|3%OfC0z0$b%%J%X(%`QY~7xIx 33ʌ^E|^@•:Ծ|Ր13mBK5cM _W!|19 %F^kU }܇ʆliʋqAjuc uXHSDjGǤ ߼*.HYQI;=P#RHPQ(cf@@esԓ@@7]+ޜ =tsԓ@p5IvLXV98Ѡ*耯GL5m`.Ź}ob嗗,$qg{Oď/4އ Pt?N\ vh#&v]Sq[D/Ŀ'dp~Z*} "0F.=ku]d> /ihVOdsWOڿ\Et'&nEl& ZI&`Zwɓ$̀S?n竟YeqAK1g>n4O eol]ӬdCΑqQO%UѰE2?K4ꁢ!Ϛػ4tZq"NgnH>B~}}* aICg~mQ⌣厹x޽ ݷǿ'f^t0YA=X 3g[~Gn`խP@!A{o+ A:Xr4c"m;xw:=O}AvC3Ɏ 4cԉThf "\*Зw=*u-i)\P%KN,!g=rh,R8OR:I$nvԖ=CڵԴ)֧q3@a aŅ`Z!,:f2 JAh9AJI¦ r٩$(H mKY*S„n`1>7 tu$D>'S^ P8wrIkQXaqS7 vddvn )fBQ7{GcwJ#QAWz?$w #f`R/ &#;eV%GH0:rG;Tam;DjzoMR̰ ~<ث?eQI z }iʛ<|`e' t+ށy8녛cooP۠-w-@j;ƛ1.0*2זwsUGlnr/rlC0uo@y†ݧ2ԽX c͙! VX"*36"u#RQEkݱ}-PP(LۦZbnht%=ng5hg3# RH܈ 1uq6J#FWҮ.6V6H%P ̒ DsT|C[H oZ.":iIjrm_1BDP ݩl Wm6fj?Xˆj"~Pு"Ix牿Hj{8:H-1hDb!wH$ \"kiU%tLb))\GvOWUZ9/Ȑ%Qx#Y! 7RW33|s% (tKxѯڐH*+Ҍq}uktq''A2'@")}=W\|sx*t\f9CqgI":AN|:5Q6?߹ĕ.qg(0)~$>5uIǁ,aULq-:7iseP , B/{<$4 J,!= %*&b¸x)7DHZtɖ*-F3ow^F.P"^WjUHVfM7E9j&50L$pXF"nm1\>{cHg~`."{3M_c,!yƐWzh;ėM V+Ig/u@sH]QI0훾-+q\rGz)JHc۠"ұפb뻬wD}w<|\'!!4+7D a 3R"( ڊycH{.O;8!6:BJ;X+Vv]z~qy ]N lV-2Nwi`7`5`Ǭm.&}3ZBݞ/uݮQkQ`4,HdŵxJ9X=Ud, tW2ׂ!Pd!YF3^ϫ"ڼpBnOJg! UY7;#U0jI:5KM/Tivaqq$WaԸ±'%Yv+{i8>g[)U)e # RtwEfr1Ou=/ZJY$8FcS$jqhivq;8Z]]qIhpC1Qܮ/6UNYN,0*@,aJ/N9;M/Qn }JoMWq`Y?ܳ绞h~-e/:I n,*N2̚,CXXD2n!*ȲfۃXU{yױjb p0^O^Y"AODxKCsc -p" Q*qP2Px4&Ix@V-~nb300Q-TK62F+.DXNPA$#ʬptqg$ax$ ,o7 PGV?OvN؅Bclj03'05ۙVaC}<- TAAS*BK ȓ)9]X&IKha_#Uc Ϭ?s\zyH,mqyPķ>3iD"swd%vlRvsC`m^%S' \PMAG * NIx"c~bs5a^jRrYa_[txb^;[\STs*j0Sc"{l<02sad[l{^)]S҄VKۡh- uJ lӏl=scRS}{⦉Haek~d=  '.Bk0W :/ĠӠ,n%0+[}7H||H-Dޙ%ESVKvh[tvb^bib!iH$,xȾp::/ ջ+SS,3?[q~pzx(vQϝlLZBLحw nzY/_?KdO(M\{fi;*$A(079HuT* <<^ъ?8Ss ̏?|nt&z@3LXb`&՟ _`Vŀo09!bqӀ*jׅX[oPhҧIߔ#H툭R14v U[60N&k%PH!fX2`'UA `|Wi JxHA5դop/˶ ($IfQ܀J0HtVQCHs;έ2ײHf&+$:QWޑ63hN$I4cEc 3&9c(5f ߅}yBMjH]|՞!Lx5XII;"9E_3WFRe܄Y, NM(e( Q(^L ØVʒ" g@,ą#r.)P1 T3}Hю$ZJmdXz'+ kL&3$XW'R*UBU<|rT ,h0՝.24j[dS;W< LT@ ޥwR :<A! :-_i|c]7r@8F޽.͇HM)aEl3*gع* Y{H{s.(Rx>ar% t)AhD&[f^4۞},܍H&Ȫ@oJ&R0ڷS\'zͼfSy˹ܚ?'L C8>6j0D_@kF`S:iS*X6Q94ݲAj%©թLu@2c""@yBu=1;/7Pj64&мuM;?#ۣO޵#n%ˈ U=s yӿL(짩J?PFQ-f7L&na\D tzYZK5)`=/Ʊ?cW=mxBu-WI ,#Oypt]yߖ#t|f ],FHݦSu<* gS{SV?-ǼI{Q`v XVJTVHx<˳?T4v8@2T$(3}!'9") M'zN3RϟUOZ욒l<$ S8Hr5W|y3y,hÍ`S D< qk`E,@>9Okon}u&3Q GxDX@XU[n€JdrVwe ֦$8 FH+|V[X5z~imPy;s,>n}i"I$x0&NL0l?Hd́F}ht`r cCnӒ&<7#cMII#I3 Q j_!Z56P8443Gs~K~3]=P<tިFw&mW3Yj1#94fT@ &@sJ13 *AF Is,SAjB :kT;ID;HPcC(ROEf5 _B9<UD׮hWS7A$k|= V5= W.YŒs| G9Hs.vț|AA<7*ElZfl IwCzA\y909'a2vq+5oHb "@r[)]셛^8QF ɠi0"kHф/9-7V|h(a}" Ч-mɐ`?*=vdަX۹o1,(Xd*RpY uUYY %y.<_q֢Gv._4%KN{&^iU<쑼*A"F X@, _/xQ qVFg%hEDp|BdzPL8aIui< #u*`gB_ȡ4ohjQ66=?ZPn))%hU-hzY4bEY sfve]^fْH!"oo@k3i'0Ǽ-IA 81`f|rd u=rm*ⵟ # R赃D@P<r+JD8F" !D=kYĖ{\@ 0गIpV>:WMۍ4S:?/1Ys կliNJp{k4$A!$p0sIpqчH0Lsw%tsP'H*u|ͱ=14Lz=!0NRѪcthA8 K6x8vq=Q {^ØҮv AGjv}acU.Zi T:B iP|j!8M><չeQN=Pv1 &eY>gkr=(1T#, uoBIVC y僮57Ic,2dv"h*b$"]gu59Ic 1@x2Pi0kkrDp8OtD EpۺfIqPEIWq>*0#Bi%6L`Ld\0 QG5x<?ϒzxۢ%޲2e$=JNJ QHX/VXmb9Vc<q‚_^CLIMm)$'"?S=0:$ LB%1.p _x1zh"{XVsS, +g#*=#'A2f 3vTjTDn48ns/OH&H Td˩pbD$bt hx EЄef@@[g$ I>U9CA9`gǘՊQE!10 " د?C^HZKi׬6XV~27$z jm,ȫcġ\SO/0XDǙKUe)'qZeNX{=VdD]I"Y^tU0P <,ʘ`7XgX9v5,G3Sd nފ,ȍ$H!z9a1q\Qlq|A~:%:5džFDG#ɴڊL k_iIRDflPhmKO:?7&x&D4FfLHx ,7h̺% cg1WܟUDHk&V0>uaCXM22ΗH(bt714CHNgc8`d} y@i))jӔ#ȔۇgM0G`g2il^T hv4fJRAZJ? s"2sƢ#W~![uϒ`fi)Y0_ :`JZ{0m_܅!\˾cs=E 9r. %ҰAIW PucL,3w5\<}lk=QkMYo}jBVJ= ~r\.g$ M*QK#K&8:wMj^ߠx}5g?Fu"(xOrYJ|R_|TwP*}2`rWd}j)(rcV2 7ī;쬞C'>{In,x+?qxIzpF/^zZfqYHE];wC}}m 0# %Js8T1ScƔ+8S:% pZ[ bxQ!P1_R_kjzWn -C>\BLtn)|PxBIஈ(`UTKp+ »킖;;26k bd%+ƼbIb1늳;X T jvwՑw<+lई"F 4U tBl!VFowzw6 l, 9w[< $xW'R5~"Xma}yPCeQȓRE+ *ƒG%F{m6D<|[;|4i|*/|MJx'n20ZT@)hR" ch<ݭ͞Nww{Ni/3'mtӕ0:;x|A*B#b)_6~#=+*o+D;1a%I *JT=GUC,l)/ 82DXΝZkNi~SVajGU>Y}4}wffȐُmH2A']P8Paڈ38Ɣ<8qEnI(0p,f$m@!Vf}EnJ 7?^^ ,% OEIH#i\[Ed(oeEn2ː,<׾"KNn F$&xOg؀~됖ѩإv{&+Jxzc4 CfrVď<~:co?Q~fa2Hwv$W  G$W CȒŞ}C|V2>io vE"2!1; `k/jkI~t}RHI'%t37߫o0/jӶ&),+t(FyJ@lM0k.>LDI6 "!/Rokq\jgEğ,HP$~mogQ/TERyu"54u%ƊDPFZ/j$6î|0$8bN5*p]9`!% €hŚH {iþagjx%k~N"5k dZED2)4B-rt5/\ȋ~N"y \bפX+q$#Ba<KڼH؆b|olZt?H WDVǁ#HD>(dbH&$'uo8C/ D5'R/7H36*QPw/YS]cl^!bdEC*#v 0ONɗ!^a7B2HȄJݍ$A(0͒Nn1p%7OU<7S'5@6ʢU'rxɊ)c>䱌K:zױ>}ղhUqs_o0EFvYo^܌?CGOo$m×3.iT0]e*:]Hm$m3.hIUS zi^F^8 mqY:#~ 3IZ/iU,*~go\UèrnP3jҤ9͙w3.MQeP]*aXuSD5iL:Dܞ$U7EÈ U2Ga9y3@`{wʎk4klth@MH`bOz=.[IfkѫxvM6Npsc'XI37Ô ~=PLzdzP pb\SRn*ɛ*Pe/'7ee$5 7Zi r(Ƹ$LqYU]o 7?uv_֜IΧ)/kR$7-8V Xiq1%f*vṩ܇?:-͙$TO[Q#Ł:dqri$9x q A`)"Qi "!,@+fY%ޤ :lzm[9)A&k~Xu_NH0”qkT$O ƪ:3VOv""΀F+y#kM26+K#(B"#BnF4>P?rZ'xZ7ʊE6JUՂGϣ\O,˺, Isf+!|3|W: NfL?Tvy:>*,5uj<-NR:㇂}<Fa8V&r-&c$B"`!:]e0g쵐a6ԪGqɤq&1p22b샢}lS%Z) &.Ya23::I cT/s8yw#f棙ɤOE 'OHEYBro"1D(磍gV}LJp$|ʷ}i:iH/<8V}(W}%yI!E F9FBt6+J>E}b]|{I/EEw_lo*kϿ X;,-=0btsmڟwk mjRk}3VziWCOt_x0#xb',b'؜6ͯl|MCO|7$]x[]zϷ^}篈dˠn~*( LdO˜-p".Dg4Eֻ 3. BAӑt4W;һ|smi^h9dGU)΃tG͐5o+U_oN~l;Ҹ[hP4 ͏=ZUjsz΃YS9-C-wRutlڝ-/AS6dl+<' MOPN]ܒ{ȋNP2Z&JxqG I|[tav.MgoF8nj+;]7{p7d4Wv:O)>=*.zJD#[C'we^Җ'^ }=CJB)›9+ʏ߈ۮ4j?bj;> b]h}]eO7粏瞆<"ȆG__ kftb4sޕ>֐Epg-V4}??phH{phHAáw q2>o?>Xo߹hw;0٬h煓9ɟzaks_|*%="hC;o]qOU{] "s9T"!-RCnlyohKT͟Yl $Tt2h< =襩ͽyT 17e[|z㘮d/7N5.Gnm$muỼg;0dlwg0>xA/}J8Ʝ<[ p#PFp9-t ˃_ہ-~vfhpϹRG91wEՏ_2+o\osHX}Ga/gs]`qM(n_hS%XˮgR3F L7)mh%H+0VDܕ*-gq,xK=ul¹.$@HP!J*ls*" rhGD!K@闵N VZͿ"hFGC[)=^к:K4ԶKn^+hN(sGEc2$;\}lŮIýXӲ.esrnzr*YI-NR=Vқ6pn nU&"p*h?'6YIAs+klN9_$7̾ 0?}Q~a}|V<͑Xys ;1#@BSbDƜ(DT{@r@3qM! jƊ]bCz}=޻Hc̮eG^o]<8oy> aB ֘mi/m3Xf϶A@ľ:Յ1a__o^zԟ (/P1fv+4isGnnσ'x'h} bvzbOI-•LI}46"Pn|!W :# o" \ IOgCoR>O{;h춱q>G8X,1oJrkRnWT:xar_l鈥ϋ<=Ѷ36G`3\o=P0*ewɛ#m~E˓g'D{0'4؇ ^'[NaX|~JP0E#^0#b"tÜ_J3F;IhtXghS5 0ctJ[I-!sOR{G&=sX ڵ Nd6^3o缝t\qRg%D`q ;ĘX;۹H;6#KV:$G`Ih Z|؃o4ڱM?? IgCb_?۹HkWg{RC A 6\qߙ3Nai;fȥeү.N:@^2H2Bm!Mōc`M$ o5WIRE[֗3[:#B P1B)qy7mC,`@W  6v"`~ R/{ޔO6:e~2J4 ׹r&_dbs.'9ɖ>O:>+8QL_}POB6oPOβDhe4I{ю?;o {>|#ۉ MRf}'C:$Y~w2E ,Ѭ\Og;g_eEGy@4g "ףƁK Dʔ;'gg~c$zQݔN#ĉD#HĀ!"$'1Wbev&ӭ@q2iZb=kZ'Fp$׼~oHJѺNFT3w<='&4&r#1CPn6 Q筽öL)N& :^d{&_J7@PB7#"glyz-n=V*N&.)2q (h"i3>~'DPF!&9ķMtbxfR~}TCF s“?{vcC#Bڹ5?_G7^W;Wl[6ʹF?~p]TS8F`(bq!L8c&4RQBDAĤUV da'cIݏCFH "L3:~ S\F0u|H!s\+Cw^F~~P=`"(,̰ RFa]5,7O]+7ONW##w'Y|?iPU2Ed"Z" !XI7 |/O8%VdX9@=@H"JJ!Eg u !ygiI؄{ \zT)7M;?:o; ]HAbǒ(x ; k5Hw͖3Kܗ | | <~͇~́zw]?f;/\,Lh36vx5e:յح,?ͷy_\tÏ `늉m2Pn&˩}p/|knN]NZ}r~vQdU FMmrnYlߜ$^~{?ז5$Ը#W/Mt$N^DOD(2XI>Z Ы4WSzNB@^_OT( qޠu _4Sgu^0Y6Wުo82=ܕOŶq5`Urf=vhrP!*&f5đ!qcK$(p3ʇƥ^D'Ede] /0hK9`(Q|@%@~r@[;[vv1+?G{WƳz;'#mXɰ.cbe\ZSNכ01*.p,rS/<>ҳTy-HlQ5%5ԃ?0fB2朕oIkقr6Rs2cs;zBiL"øJN1M E]?7I;g١qF>l0+JKNUOp EA;BcUꟄd.{cKNW'joI6`; NOمa9%-{pGGYO=@Zc {<@ nP,gﻰm3zC* %";@Ġ:6<܄kfWTRI|C; զ! HZt 8|AV7_ʆTҪ7uȋӷ** 6ƒgWB-://-z=sG4SOw$:(*]4-:S .(/G_H=]S1+UNIv㪮PEϨ0_-d-`Rjx779ܘ ؠ޺rwxG煉2jf0tmF@Dp -0No@Mפ84b\&LtG.'Z81*лJ D"|th4Zs#|Mc:iY<"q5,̈́"g9At}-.vc:i'{?ED72>(^`>C??+W]M٤ԉ~urľqQ@gQ`\|l^weOn,}!*- }`L.X,V~soJ#l wT/7 :@Br$|*ML3qNn~vXş2oza1WxS%kA@Rc%pj؞@@>v&{=vv~إ~ط=Ƿ߼"w5$t]{kU~Ϗt}ލf]wy`o|=(IEdUB۾ yJ8ތ3 /v<єұ Uu:ZPƄ#WQZ9r 9cؕg?No`ѰN= Z4&Et&}rJHkḫ9Ӊ5# Y7" >cPir1WǾ=]IG2<5a#9&6]9>iZw#$3m+\Rj!5V{"޲!ѶA2 kH! #YiU0#hȇ$&hq4Rگ+ eadx'Lyi#ue!`\$]O7Q/ xp3ݏ4́!p#x& WIA$Ǒ03 V*h(; Z^v'BPrkxݴ7 ?ӱ}oDbOz\nk#HEO#9DE߁\!tƕt~!%yu,7Y@@Palď`Loz#bmMN*m92㳳l}=-8;|p^3{u-ixbhJ'D3P4뤽.ő}/e%|Q-iDz+s\ \\nOMIڣ/zSniJ}5+B3D"@TycޥI붸/h^4DZJ%Pƍ8:bR& Lpkomo in0%L/{+bH J jmOM|͵ ){@2կG:ml69TޏX<:m@ye5n!\(ʥ@m' !{fE&|$πLPCVO#! Qr:b0?lA-1cYzZ5yFfA_Y:aa%z٫L9_OZ/g5a22!sf' XZջQ R>(aljV8{cndP j5 򆬞_(ӇлճY>צ\\(dvtp"Ey5ʔfPIKF$MGjTv \3RRj+^, x@,"yh \kuYVטHDRcz&[yIVgK>$6'LՎӪ 4h͇Os*U䐗ttmh͇\Lv; p|Hlm埫H)Gh :^"z"R@ u0q@W6Z!asYz9n)*,,Pu ;/D"+B@҉88=Ig= *;g>!I\59Z!@km, 0H }}]Sy3h&i*8jv& u g>hy7$I9qkb<2 yI+#J%5n;}lH<}e:QHHg'Vsq'm;W8j8@Եe;Pв]1C;{Bñp NsLdJM""@x/Ӊ ݧAPbAi巺NU~ 5 DU !Qw͉y'c1X0d u 8 ]`@ ?K,SfwEͺ,ҕ_vMoO?V>xtM %*\&m9 vH[by֘oȦ0Npb;QuOz>V,q兘 xxxٌͧ~*/FҲdGCI2_©FPgHH,:H#ھ1R6vy襑Q+l#8y=3L :f$գQI@YƽG5'F'?h(1%$JTmáq?6bD|dJ]= !gg$jY{k)N* hDWP-J,ɪL mIT4qTa_Xmzm//BWDTɿnp!i~!JGA (fBZÕ`O^QB4?-ږBсgqЧMK"J$*! OzG{c^ߍ/DD@_xǝB XH G%xKöc}z |!Ğ%RLN` =ɎQ  mF1b nVԉ_;gQ} #n(BqWuVۣ1/F[FP)$jL0L9$X ܃Hvi$C$d>9`jqvzr ~ \tq_tdJsJIGAe ,~[|'ѓ("΀"n@N'ZVņ,7mw6 TuE͝_d:_H54,*Ln4%dHHoVk+p9jFbm!!6Mp|(ȩ60DinPC-uSHzi!:8EmzjF"Д)FѝM>)hNRF~T@";EA'u;}^;[=iiz&ku(26L"+c5:ВDl>,3mmd%ɇfva;tnb%ʇԜN, C$brիDH)/<2P@%.@,`?( 3%xE?j XLB0- +} j`EG\n>_&\:u>)dAFĪMW ><nұ_ة>l|P̆Kf1$H;ܿo^ݜ3N%nO',pm.CImI3Hf-QO[Go^ |=E寱 v&XGNkOHk?y__s= BA5TX: Yg<}Dwp'+A&2Zs0@t;WA8b SHCX+i858Ģ#Z)AfaJO@Jiy_UBܥ-c'$hL,g1, BK{6*zˏd$ih}< $lcS@KH:URЩD&ln|&4k_ {(AR c@l'/y oԤE0*mR@uObEIZ$Ug QH0pBqN$ND*'f$4!:]yK$WR¨ς'0#D=Kb䐸ji$ B$1.5 F%9Ih(GOb%Ip1A%tNkx83]T!L}MoH$7b*'(BbF eq%Q@`>SJ``"8 rn$ +G# >s?dJ'E%i%F3:&h3EXƐEƐ̰@,xu<ጋ kVzPר 6lά| (FKG꛿|YsN_T-DEp>\u S "ʏ(DO< ZZ ``` ]=mԜ_~b鑕5=7|8pkmy{E(کeQCH['y[FEs{v+cM(=3$&1ew)jnPj)WmsEd$N#pcl7,xP OqYSL-ckcMq01D 5(@#)] &043ҧyPt䑣sy ո7DI45j  u -8[BV 5Ux{]X[J %%v#֮%D4Қ6/LgBPl"ݡs0#L~mio3 X|dOKPk+NC|bgMJt،asxi0T_36_' ;y6ie"]Jpy"f$XӐ'&FbfbMD*gtwAG,Cj;t!‚Q8 HN!  WV8? {xp}<@[|׀S@]ge(hړ5ki}8h&o8ai8B{h65ׯ]BUā4h<4/l9˓h4x!w>`@ Ώ,'dHQ^xbo4}];mʜ9Ɇ#EW6k? "LŁ6>('.iw>֢h02B4ͰeIHN>7=RB> O$F Q`x-?F=G|!_$d,r@Zإ^}fxXT!O9@$FAa0i&z^`'߰tΕ24lT9a;ُ|7ƞa)!b8FGa/,Ngrqn9qꎾ !BH:=\AB ~ZAz&! R:%=N) 2@վjya&C[X<8SP2^I:3 VYAð#oѸe1nX|v uyvcZ1=d5E0!*ո N%,wue cXN((I-"t9GB"Ş[_!ϞbL I+5%c,w1oEH4 ȶYw>2 OE7C&\Lrr*RVc)Ę3ƈX3xE@}p&Ez@ۑIͺ乶j<`fb ,iK(k͉o:bK nE=9%z$U2HT7XȄ\45i$@m%AcIMV㘕5LMZWJ$&{2aJV>}e˖g^,[:99#Ars!ė@ :DT-jAy.{&w+ꈻlthŽpDC`]sǟib mL Ȗ>)Z;%yWKx LK`ojȳŠ %^ƨ7ҧjbpo<zr~ˈ@ets3bkZJL! B..Y9@vQJu4lARRti? x%1skmҙIb/+i0t#"5k%%ŒXd$ %D^k>UI!F`2sqZ fjsHNCͯ/^7Y|X OP`PP ` %!F$9=~T-l)4g[,uq(f`5#k'WR'uZ I;Jʜ"zfݡ7sIWCaѦx}h'vGZ|~vBN#(!@5!p|9t*A<[YhFv҅o,˓\E& نݡЌT%hV kyHPP#7f$&=aѫWqЫwF7K‚EP iiiob;ͺ!-u!:\ "f y!ӎt\8m27RHtx6$CcL@w7l#/Ϙ  OM 7C|bivIyD%Qu6<WNHQ*-6}検MJLHHR~,#ilWXCUo,?ܮ]4+{AGձdպEo\I/~[:(l{Ά{seN < q7@ΝOyB!J3Y1 Ȼnw_\cfFk=B&2PCw#3 `^{^UN>V|f@U|Og8 4 b0}d E%L.ܡV|7 hNy5bD (hgċ-/QpCoeRO?|Awqjft oc@"j1{lo+C' 80q'<-ó>l@^s_gn;[YӟuIO8E~2ctҐC~lW ˰O_u"y>du2#~xDHc(ȔG$-0TTcbS* ulY0c?䒍[zvӖH Q'|GK?DQ_B gAVRDfk5+Tt)*baE4Ř$iՁ1p7"8 ۊ*dY !!@N$¤L[O*FHGfaAD TG6c.`ŵzc@PJ;M%IS#i"Y >UM1 pasƿ׬MV0``b^–2Ql^D;8618k!RB FB17ǖ?}h3ֺR0FVܸmGŔ>Bвjn[3*bg9zvU>页!zA iW 1Kh,*Fg$ofXv2FRɣ@I0 J2Q> z,EӀ@8 @ y8QEHA$X,j;#^fgKJtP?w'J\~C h4 &InS4(J ^5zHȊ_BkU$xJF,[Hi$WUZ؈3@BT!Ǜ+z+RB!HEcP1\!,{@xQP4uE%)MEuN~,:isSѿO{Cf&HY2$sHo68x%0 $ Uw[Ku(m@S` I86[3`-F!AN‡A+7i>zx)@K(&I0  s6~]HR)%U)raOLDb/_`Fbե lC PHg8M7Ss1%WtJ#b!n'GA&DflJ02m4M7*VIk7 E͸Ђ|Hx Dq@.B"Ba>$Dv~R0R>\MA_ S@Kx @*^ʟT6mͳ-|NΑ9 υnjykosMNԖ&ME":^g=N]q̵OU?j;'Z 5ţ5Ξxۮ~"BvGT_¡Z`֜z)J-YC._uS76Huv 7O h(\NI&V?b3 Ķ6:FLIPnI@\gS Gh ~~nO|/#lNR5zi%ٻDIJ5ɛ_AbfUknݡ-vme[gh8 H*>ϻM'\êƹUl44LA*3U#NZ ~R3RUV\Qˌ֒ME6d$oR2զcUQ|[\)g׳(h}4(M1o.?ٖNrH5u[I|e wy\ԥh!< 8EQr,jqNv|*ukuS K7׮TM>$|{6DučCUr\9@ أM>L%+b6}rkk,,H(4(г_wq7:+wQN/!dsl,Mk`"͂;=|STS=TM*?NٚxhАAFan/\vH1eę3D"a)hşI "qGs|֛R <vRy>=|ן?vQ/c(}qƿ0+\/CaօC9-Љ!;jjHN`@hbI J-BV 0{@J2w8ѡtCy΂8Wj$,PR&ͅM$i,f 0_bA,q>x0^0 N Nl$`7IGlD`BF?e [x b * (xh4?yi_zj90A.EP&&_0De8%a ^WcyfjŨ~ݝK z#sF(ӸR5%ŎA0\+l0T@H1$TM%ݡGn+9M  !؈gU#N ghev6[2a͜ND[ΪmH##eȠ,= A⎙=sYS"XnYFqc>$V~ ~^M8TܒxG}@tHj}fF&2U*I1"ZAJoENp8Rp&)IAIPO"$ fsT͕<[-y&FrkkZ ġscTs7ә")1)«&Btc⠨ƴ`|`xbٽvk1Dj tcq5VHM*!X9D09wZ;uiװVofqM1&`o7ǏnXU"1O9ɱ'*D-(^P`Spl,,sHo9шQ X 2SU \l-#iL564Mk5b0;8:W;NBCR޴u|UhUnUl` !pCk'a%ɠhJf3 "E]?FJ%*Sp -8 n-y+6Uu 4b  Hvﭿto!3fNeeҙ|(\eL䪏r.(0V3Pl/]vY\ҞS+^܂qVAb/> 0|hDHT ^W3N4rt$gn+w_O6N4*#R@#"o7Vi1cs洷K5yBEW<,a0-Alabã|秦w;x~EjgzGN:WIU, I>2iU>5%?qJ?6;˻.r$?)bIE;92jJwK_'ė--U!c#)iʹa+괚K ( SHE/lܐܝq%5j0(2%QPfWX-y|'WA R{84IJ$Yn1j5Ԣ$ű#bl%H !qITF %'X*),dI>HRTNmm}n"\:@%Da#~RaGR0B&EP$ *[a[b YDϿĢܹ|(%uil [;~,aL#FJד}ͷB]Yc@2?k(WڼwCZכ'ן[YYkuO5>cAf54>#hNkO0%.LIh)9ol ;AJ#ۂdBpr n2| DIy)7~yr\rK5V°zZ[1"U- zXR=y" t!jmnjizW::Ls6ނ7jbd'sA ދͦ n!dr@L|f+9+PMÙaF1D+$" 2@* ci~pTd m}yՆ Rō|#%ЍE*0e0T~28Kywa.N8,FUR!Q$K5F`!O6+R;!0j?oI[6@D g2H;RR3ټ:q6l[cV̛rC0PtmmLd@{QOu&#q4V/JzƆw0t&TxVDT$D0OV Fu҉Pȗ h!VƘLDFwGN1%Gm !ȒPN<͟ ٚ Y7 yLu8! "BDDv/ $6)(!vh6@Ҧ#P`YSu$ltMr^t!պl,< fav%e @$Ek"CPa#X@AQ.'Z}.+.}wKُm@ AR_ \=ko6NE TQHQ*׌kdǕ^pQHj  K#:c%BszƨįDjwʕm##'Y^I;E4r"PEp!8e37c'̸ڙ@w|y1` Bh^X",x-p&eml )4L 0rMT'p/YzWIt KB wI"BD *'wvmͨ:gng1Z)K KGCgn5@;P޴scrp9 SU00\]o-6g\#)G76E)l{ek}VgD;rkek.1 l+E2&2w7 7^#9d&y;1]b ~GYH|7)AB"'[~5ݝә@$Cor7]w8h+l5b jl>eznK?ΚE鐺zǓ($b[Z\%lE+MVl'VTJӬ`1P󉷡 d}:ǐiԬ~DFGIe(ooJޥ(eYls8'mӘ2L@p{✹lw~!O?޶m!8^D 7əBYJ#Mʉ)4P6V=vvdrjr*% RUR!Z)L \6Wmd1Y<%S;~B0s9szRCS.\?k2)B9 ꑪHjiN9[@3C!BP-q4z#!;W-l93!2&SjDCRUpzȄEL+*r2I iaH0Q (qB`:1'gBRqBZx2Go.quHaqHN78u{daݑ ;zRр [ tK:L r_k$)F{H Ȋz-`2j% SggEg40"uFvn:˘;+jXBH%}{m 2&$s]1[=?TX][l8AWn hq4ƠҀ`nև& Û)#\\Yf35PJ*A(iJp{|!?=gl}%p,KY0 CVdO^I-V"Eh(rw{һ~ܿo%-SV52yg3#VO}=<z\bDqSbSQ3( ƫ[u4C(aM.4( io?=v i V]94Tc?h#Y5j+ Rr2tnHğ"%%6/,(йG@*f890@>*>KlCʧ_\z<Hcj1bN85t˕oyl3KɊ%L4LSνK`*谘62Y~57neaV~9,HuKAKk#gd6oZnLp4vc͵8sGѼDfJVM\̬3% =ݴvs# L~a.V$֨1p FrQ~нu~dXa+ iGj8͈[L@&C[ΈyeՁQ\tn& sQA@خbQG֬.hm`S6+c#d5:hnd˙duc !MNw At'\/IlN`vF,èQ0ܯ[0 HATB !Dw: zCLgrlFIUm wky,RN a l=ctπ}k \ \jXX! KapA Y9؈̓( BĚ=x"Y\w-_ T<=腸0xXIsjVS)Y@/K];' 4SfvQ1jBY=ПIOnHu~];&K @ cd>?\=,t^AӔ(J-*~dpJ6F ]h2JYWކP"t/dd/=5oEr,_)ayEOA#HI$ pY#禜n>ѽ8:S-ȣ }<(}0O*JVAR08,>WQv ,* Ѥ}ORuKnnE$MG1;=N$p+ iRzZ'lx |VJ `7{Y+D#'UnaP4Ĉ.\J;6ǯr QLኳ~Ae-ve׉T))4/618|]w.^;y}n=d:?Im~Ͽ^TUǿߵ{~ !>ȫVwǟl1Lcia6?֏ƈh~ x []VS7`!S;G?9?/?gB"r;O7euJ|(ʾ>~٪G/|!][D ul#utR "EUP(&9ZU\m+;~ {9{;e&x'MV&IJ!a" E{P%T i),T:N]9ߕQ6ʻ!WABkǹrj*щ0泥ڌ[t`edŠJ{ȎUC;3i݄pLdej#tvG0fܢC%Cpk-_G4RioGFQԸÃOr reY3z4ۃjr BntKPtKRsP arH,/vR{ƆSg6U: 7x'  ;M+G/لo^<[a UNΰk6HRWi)Ro?g;N-䮿2yZܹ!Wn+;'Q@-& ,l}FUe:mL~s],D5}hLVEpGSL,yDM f1"4L"͒:ȏa<]ᝬȪCR[c-lmj4 İQRꊇ+~l7_Rg1 t5HfఱFkU!R%&Q{mQX6;ya!X0 NEpMlRlEGv5OjC+Klct.m?~so:ǟ:[y߶qLm8 vIs:Qj˃,"|ğȅJ:g؇{<yY Ξ0m2mлKp˿!/,)ظѣu߹4/Yf3nk[muLNwCf%}9Vxնm%!$7Blz"Vy<{zOKMu9a!*ӺbD;ƃ6n7O\"K}ٙ.rƄD J3'F])ׯ/fGVFLTƵYQ{[ߤg  v72e[匹+[WYnؕmY3.> ^}a] ϓz%k]}*x qCn`|L)]j>J= WL#ق #W|D)Բ{I w~3P''(\曞xuHi"gkR,\Z["#QJ~'DWN_b}CSSwi>s]JYGC͞$KO[7a2f Dom]eks D:ujRN !kB# '_ !wXt^lR̷$0`N7Ij5wp8 .nv[g/f7,aEs0=5\i豃G]zߜhUʤs2Wt @#:Naj-I;sVǹ.Mf0cBIw}6'))4PWYҨm:ܖ3o9IIA*ږ zd$ɽG['چ&Hd[ROׂPsM"PGP{6鎯Gqu׸>?^00?!JFjsQI8tk9w^JzLA{(Fũ|L\q%]ֹ`:T&CH1IO}m~tͯa+iM,([D8$KNݔ'hWRy7)n֮ra۰$>$e⨮jN7s8' 4@2`)$"?m>kִREi &Q3)n1P;?39d;?~Q.W9:CycVۿs!'$ N馼cǟ{Wu7>$.a+yH{ o״SS&BxYodX65v'я!dޙ8EeF^\ͭn~ψHֺsUwq->B]_'[j_B;N_0gɟW4(`!&j>-pEt5ΞuaruJۖ7Jd+h6:)%]W1%ZI )݂Ip*$r4q?Y@ZJ"jn]vyEg׊%'Rlj;sNvuksm l`=O3I0+PeMsK$/Z_ Ewy&d_b4]]cP2OzMdxԛ]hn@ݑ|0 B5Zuj\8y%f9p?'@Qr$hlS_-UϜresz䀵1ãN67MmrS{Ul4{l@ADjpCqa%RFeu&۴v!_Gv)inkn?k9;fR#  G򺐐&WzK֚_:|˝24菐Ma?4 7΍:>8`I6d&06y( Hr:{`6x{52@ F3{bcg.sDQ&= 27@ Jκz,ATF,_%iˢ]gDc-B317n>j TY9%qY $g;uƱ@#` 7lh֎`=EG 0;\;koG3uzejg|`@>X?>C@fCDzRlr56ՄP"]Kl.qFe|,dP!oNf3dWc~_OJS7ѧJY3t 6EXm]&Vl׸Qb8Hj, !dNc3Z,P,CoJcoX/a P.zڰV)3kle#%lԽ'̼6=Ml/>:\ob )ъ-YN+Nv=$5Oz7,R}L=NW\ f\v)i%= u Q:Ua&ާNZ!۽5 Fk, T0sHs/r퀖8)CT]VN3}X`H] Q0()D QăJ ht]|?_ O%쑲؉  eYv,1%uԯih\`[%d\}Ai)<&d̯X~ȣ?mP'sT;_$5Skd?~XRҡ_~ _ϭJT(A%,HْO%%Mc48=zš+ؿXKQ"hmc呫;}n:j}Z38۫b`5IWlأ1iꃨ.~꼯Li_n|҄R4DL/ENx.AntjD _^7@ZO'0*B2N]pR8edp[DT TVW, KI] ea 4b,^bջ,Ό@Y{AM]\Jx'Xͪ,8Dg:GA֣6zJS D{S(o+ޣ5*gJ1ufdk]  \)]>?m%i +P21)>}|Xsmgy3s{O_!$OHEFBb 2C4L$&&vRB'{ؼ̄| X @_-<\2iG)Zj#}z\2J\OSJNJy ZZXk64f|YƸndqbݺ1 v p`XfL:,ҭ2X*e񑶚5l]Ȍ!k 𥑜qr5J_9OL'jfo·Dl 7I\) n⻫Y]u!<8f L#L֜lzUٶMǷٵBAq[NF QV1~z5o|D˕@El]SZ<Q9sZk41Hl ]ڊz15!՘LK>K:.Uv;8ɁF.=9V Ƥ&R}:JT%IaO]痩/OkY~n +ж̓i)$vP":BU_8˃k";/7_Ri<$PNZ+ *N E#iBO/S'=m )ԉQsPrx&L}=;%=SwG}cHq_mPԠ^k/~,- %A k8i3hkB 5޼8Ĥt-H򮜈NWec)IEv:vjσ{Fk̃kjgє"ueĭ]o#>Qz$hgvڽˣs"޽mӵuB ď]$D=׈tKCni+@`WC Π07@ɏw5dQP3G,?D:YZ$ET]SQu!%BinroHe>ؑ$gưE@Qa #^=Yys?^\~r4n@K[' #0hq#}$ƔlH@ |ݺt_L{S>*c4=wEg$i# P./Ltk,5PTKYrDPq%'5ܺXJ \4G1J)(<ϒh=-F+@hL|77 -b%`-@E6(s='/8K`7Ѱ}}M|uF|;_w}YC.>V2NZܗ $WAܱp3G"m/&Ft**m @PRcYĝg?v~" }Qxcy}Vz5,,*XZ\CN&p'(4r v*ί>˗K= zƵ]R2ģt)#j8%Rշ12 AR(u;U2֌Wۻ94u"W>4nƼ Ъ` S E}Ï>q/}tLx?'TC%$:g>i%d9T8F5Dă&,q4R  -1WbG}$!8{yꍉnv%Q $(mɄajVTH!<+I{[wo4nzF[YF \4BVכ" JL ݴҋޘx۟y=2p‘ aӊ7;P@BUFl~S$30 A+&ܤ?1vo蚒܍f$T5$= x1HL79^9A@tD !L?MOn+c?+uk`urPNk&>/*)y?2i;pӓpjMf7I_]؞ݽw"E jԐԦ%C6 2rӐh'X n:`&v?C3QFma)d0rPuSΝ@Hvö!􈧭TEmƔ+^dO{VY+Lջ;!7l[t C*L *iIeh[|td$Y8/ '{Q T5+:#8W.A*BTv _WEl v\?qWv.;v}Uwu랦 &4 ,<(`jJ$>R&SOY$JeX^i&#<`EBEBr㊇aOe+W4T&j3zK^Y~Ów7jp"N>OOt #Y0ϷWd[|/{YH%`JnaL&1+4B [~)@ɵwOW;ʝYe 2,L'3a~x*]U:8=1qS7zyf T^g={-mtw4M"WBR#HT*1CƤ^}iO,OZ]Զ_n_^m )9#§: FK+JrD_%O_mun<=K,\g1u{?s_~HފeEJBVqք6 <{y֕?5!Y39wyɷLIvk1ؿa$I߉+2.\:xOV9c/b3QH2>FiOۻ:l%NSk"/ι| y^R3pfQot g&:R(nhF5{:+gab*AFsMbm9cY!_fWxyA6BcԐL(@6-ii8D wB𲱓o꠩d|ol<< I`a'`jm%S ҙ^p|]$) oYS> xV~,|48gȗgFf J@Ig|}7k+?p; Y>hϵS/؅s_¨:ӵ?M@䔅?FHvxe'~Y9\85ϧ /0 GP";QwԘ{ar.+(+SEϬ`Wz%\q\TH9q%5Xi!p$>^i%ӀT6~yxd5oGVM^f+W][9̂SAxlZ"EL!HAjDcctO`{υi_& ΩlF 9B7*=L+4h.Yw$i'W8aW+x4I\oa:&cu~j!&KGJ(egҏIҸu:K)֣r (D<;W=Ab~6K?IsHl%!b lNhDaoF@s࠘ksZZBYkL2%*ќeM 84aLb]J>׭wMϿIBȐerٌLYhl *3^E!%58KmsϨ^4 !dimK>p+)K@*gTItܩ8Kmxݵ[NJ]hJx(&&0)IN,jԜ s;{n[6kSρd .S @AR9QDlq Av F_.4Vn㲽v-z4?`"%JXNx* +8Jse#*\<2oR HX^VNH@ODa:gSV?q~=tPn}7=*@qPĬs \mY4FǰWvc)CNU.K%yzc;[4}ˤ0x֦P% oA,ِْ JB ԭY7Cn'N:0;4v]]Մn}񰎻.Jٯ"ͥI>Hqh%I5g IEʉUMЮ;uX.8䫾 H^#u LQ VC8L&R8;+ 4orZaZ)Gi%asRoƒ #[C_;e4P)It-M$ٷ}]ICJA{2KzP@EbUO]kEL(# ] S lطo>VeK.-@FQӻ]Vβ?ǵ;ŵv9S1Ѹo2S/:9 >`= OUTU׆PP>alDfi 62R#JB XA|e%FϣaPx{Mx P700a#NA  W|?}}UˊH+!t j.Bl6]Zsߌ,A^[`Ƿ)ME$פZw::2g&_xW@e]=׎7.5[ِmk`,O{y5wFy{6Uk:&0l)9iFmܟ@^v_KMˤo~rڐ4! tۜGȈVL*7<LBm{]O3=\fL߅VY4V3n qf +IIVZ-O$v]U^9Ȯ(I 2Qu?SMF?qO+mjpۂ}K_f,舼,F`tNAv &o-u ļq:U6 =o1hD5va_⴪JzCuL([`ՅwL4y߉ H-I!U*"5k ]WZuU5m'i~iW8Orʬ~[s+.5{=gظvʎ1'šI/Ւ#3i`8$KZ0Qe;ˢ+,Gjo_YINk2Bpp"di}X6DT 3 U3'V7}՟xZO潲L4JF&4/Zu,Tt;|-}F~x[#ˈB F@a#N0Ua%05g iIZD@g!̴g.zbw#!d5Y.םi3̴F1w LPu,!5dն&-V;1MT;_W`C>-|> 2EGHmx\í½PdT}4%0+pfHimnj_zJ%sU -zjU;j1;&[F]| ȊNuPe&ukEӲ'oyqz[.42s8nz^Õ"!`Ec=Q{q[axی0-Ҥp;webEc'SNL(d\u*8Fӵ+g,]nW3hKH kG~%<2f;ݕEB5W$TU!B@wE}Vaeo"Y2F@#c9`EbYrM<P:$;xɻ3""eD@Ħ'KE=qW@nJOi J'Ú0ٸ^v[6HhAp!Q >@s㼥dآ_BG'7v5;29}#<6v_M8)Aാ3uhQKS3RR3w'z˞[{_yxm%3a뉲z3FOR}Xӡܹⴗw/ncNk|%'Ep ]tr~!Ƨjc7XS8j2M j*iDzݩ {e#fo%NuB7j@s&Qa3¡cC Vm+/?T K~+&|P}JtVr=;\XU= 0'detCӊcEr+7+ou(^`NFJ"hsJ4 ć!oӵ*nY$v$}.pGZS'LiĂ< 8)fNurРIwWg T q{/7Cs'V%͒?6FO%diդ:Zey|^A~qH>q=/lh3R$Ә1uZ(nH>-\e0[kR(F-o.T@fX1[DeR02 Ǹ >rfu_rs- Q/.'+ + C̊#f)0.dnߐN-^36>桄F5>̌ΰ ܹ~C:iʋ-]8 m4^wL"o$(#X&IUّ˴[PYj, 9K5{PK85Ls\[k[+_*O`8FJDZ6ZB_] 2뾐 @%b} CRcr*\R>1~:#>gEГfzT7Z1Ϭɟ*WښRS cp8 ;:eoGz , '#f;&CYMӄ$PHcdCA6Fgr9m7x$%eG E\^geH8% XQ{ɇb xq B-DQ'gV}&''V*Ԅf(2s?9R9뉞9k .hUx\H@O9htB Ʈάzr\H:n0譥PG 2 OƮ1חuj 0tЖ"&Sxf="=xR@LzE)I1gv3Te Q:1cJ"͡%)Ւ"sn3UY~ݕ;{ [_O-ᾨ.Ktw^qZ"%"{ͦih&\8(v.^c~z%3yLu)UVﶫ77j@FY# TU4ҭ^r' xt+]ccKx ˪mjQ|VX4=56' fJQ%vWjr&.Ku*n9Z鞧kH|z^AGL xddV="t`?WLx#BjSl%NRDk_cM~Rf2(2+CCQ+쁦) HUwU!]S?k'iѣ.E#Qu@brkrzP,SgD86]aOr'N-xH 8sIo=׆4TCC<7kсEF*4qg@zzpyӞ9kB*`tVJm&V6Nj5م 7ȋ[v5@Jɟn) X .Bg5,q=4&`P!pT˰?aM6sgb~ae%xmp÷a#Vmʙ⶘;ٍ44Oyq>Ttpl6LucYM~~WQ#Ժ6"( Q@&E4)LV=_6X1fz֥Gv v#KMoPjPϔuc H$GЬR/O)lD=lDŋ{;w@ a%hM8ojxVb@A{=# UėpZC A|z5[g Ml |i3{;`~RdI1.(]E18B1"6O]sNv,J%V9("$,IZKO T;oqw'~%K*LKr+7o #}ɵcU4Iᕕ;>>)#G/>gut%G_W>SOo i=sOz (y)+_0)67:Tn(*€.(--6v|Ê1x%ݼJ~Fic 4ghIwj/ɇS?!$kuqS?yd&x nAԳ#\}$%pwYZD$LxdJS-~|$m#t٤ V]Έ@j=x0N%H&!Zil |tinqs6 ]hD`%z:GRS8$g):Ypgƀ5m[Ӣ.7g}hUGh z8t|:3QBֹD5-)<.1?wa+)[<*"B86XBIZ_cZOn օj{bn9Pp0I'CeRC:UGZqlijwMye V[KVW )Zщ`i[<)؟Y)LBael-eUSLvSO~l6im*ЦW[s>oس@X~ImuD2ުoM Dim GMN]mWH-"ܼM_@I8@I@n zoMXYG,]g[?y>6 eHj@@6ވkBϖtμs`N>!ESƐwBҡc!#3A-Q/sv^ϖC,{4?!j B"lh嵖pqn/lI;%u)Oc]Ŕ qLD-^hVr)`m$̠E,|Aʛp>+'\{|G|U6X1Ӫ~no}Ds-:el0qu[CIqѨ!j?&>K5Oq>B|(JŇX Ra!7noR{<& lt咖E6~j-/醥G^-xd؏uG|PK(h=Q|" z=8#ި)Q"@,w-nš~@z>xy%N,F"R?]հiNGx7<[+lfa1$)׊vo|p~KCyDtKU$l{{99|@ST`kFX=<`{03>^&~WjAR4Z5ʫ 'xi;\w 'MmD*Ȃ NR˧[Ym܈9[T#$!E}gSn`YY^V~M| [q *}bIaه'~B6WXzwH1ܼ:N**&e LF6v鵿R/fr\mxCD?U f;"$CYu_/;0! s}kZU,P[=_KW ISHyRCֶhZgkM"Ƀw7{iYm2pu{|&N<һE2;Xj,/>r{5 D59~I~KM$H1r{~g_'"5hoHοAx >^lK"ɡ {kNFw6 ywWg ˊK!A)iqŧFw6D2u;~㳃P!}L` 5qQɱ=|IrůzED `{ $>C5k[>_g.6=HNy 0 (@{ &dU{$@j?jX{@pXY/EHSo _EYc_-f2i8 Y 9D=ctqp gM15*: d[CPUrwd{äH:-\5"ʤqx^9>7ق+D"-4i#}<;cEM/HW^?ւmMOvu fP Ĉ{ªʓWy_lIw']_LtSWG1 .jMLmO<`?+lLm}|tMɝJGSwu7tR/C[=#Ku#!0W8\OLEtZ-Q \zԞO%5M'LIpvwWhWJZр< o'W2ӡT"#/wA.r75 7yZ moOrQ:vY/OHV_CyOV6Ew?_򮓨h>SWn%Jbyj_yӦH(X* [M_hX)RE[{^' #hI)$X@#h[I_ M9o6E*&3CDWH|(@5G1=M~tuƛ|=r/grțlu cU K(DJ[+ogoKr{gxS,Dž Fհ>:s0r$`)Ffld!rXAєh)S \l~ٟaLo[FȠ=+~H?ToK5d@$/0X[<*.{{)zŻՓYhɈ|lW'vʑdlOlP¡12~4TVKMķU fW#7TOk[? p'.+n.`n61p(t"2XfL:h >S`ݗg;d ܈@,#䣱HRD z}2Zҥ|I;PpdLͰEҜgmCoї%~>`o\O|H_cMt>Y_~hSJ7k" Ij͚\/5>sWwWݔ}}>{.hUT^>d|cyjP7ѦvIKK^nΓoV}0qv)lbeyk+>m '.^ vOӼ˛΋ozW?ߘi:ɒ]y%sKʏuƬgfs"M| 7L/]=l;cu^NNMK2j< X{4ym*Ŧ*`##斶`Χ9)ɲvۛfK:پ>YpNfAN,%nX:>J4BwԉeMUb7xgݾo/1l|>r>ݾsdhl"̼?% MXY?\b5EFVr2D?27qd}í?xj}{^6xhA;gW&+L4 I\'i^y7 0mVw?<[_N]Ǚ{K<78iu-H-kG!rE썣a}_Et8 K.RJg|܉AH0%aRe>fTz561gX`A7al{No'6Yϱ=oF_e1p>HnL}M4}F|f-iKY0w΀S2lp |ti&{&~%8> ߍ8؉h8 ʃX7CVy V|Ouc%'c!MX+uӦIQ>߬H{ꎻ+,m 9gd$x[zGH15eVΈhxtG܈ܤnm#WS͉]v![jHH-3'<;M yqP*l ^q)3mW}NOa[F_MҀMҽ/,J?w)|r.4͆Jh-wba( } BoE6/}DګaWfXڽV5ynj&h†)υlw- |5yBk(m Zǭ(s\M+t/ˣ4w;?ØygO`اm\{h`?["=p\<UW`RރW^b1 i}˰mܱOh;Zd"=yuغX7y9]훜mg_!=v}to$oVf}r۫`nx}6{ME`}BX4xۡdOWdc?Z;wA=q:i/lq4.bJZj$bܫ`תSA#L ӆ7σtbnG|$/@ Ț?c $v?HG=^o"YT ̚AZ8殫Џ^:!̖F ?KW{@>?J?4ܸ++s/?^LeU{$r\+ϤuݏM@f۞/{Fr{aƴ"g>ァ*<ܝ!IӇ@qHO+Q4pDv7zl}^vPm%_G/U6gz\d6Q9 ړyA5>~i޸>i'׳C7g[W4t՘m4gF̛yUKHD{.1ʯlیOn ,jicU9wCvXuo|]D96զ>?HS-g''Q4{?r1 ^} = oy1OΤ[ SU]$oجϜ3G#D 0A f:~Ӝf7N<U!ykOEHqQ2z?km?~fwtHst!|C 4'\}f;3rט\rAHlrj!W Uu[lu7֘5 %TlS(v{,,]w>g^]吥a];6b]_kطUtc. Hmf57Ne[?2s &_r?>vmjZ; rAz>g sЍ$߰tJ[Vk-}tYlۘS:B)쥀PabPW9~>ݲ6-?ߙ~ k/zx)a7A %j&Po>?|MhuU8ٜ(܅fs2fI]oktZ| d/aQf"tN Og>sqOgo؃r' OO5N =BKV)IDBLk^|ƾs-i۞_ko8lE!`IN'_u6ܻ-׫F]͓qt#ևydG4EY.n |hgQv$T j9ATZDթ`b}|Apv6=|w|OLway i1RQ7й{xeޝh^`F6LTJ5{_@9k8]e9 ׆{< |PMbV\'HQ͌JIށ/.LݽIW~??3!s1ޘvsDːP@5TDp8S=M}NĂYpM/o iU-%U+ .!ގ %t~ek߭gN~[nË[Jm*YbAi!cU\`Vv[\HW.:74% VC̾&Db^=hB:b;]nt$AԌgU^9&] ;jK;ٕf*nt! ܌9dzn˜vw>T7ue7}>/@BŽ4h2Cxԥ!bP_G1!Va 㧢7 라45ՃK++O%Պ`wf2GsÃnz•<ەΟK>6`!7 @4./|ٮn=1!W-[Car7h8Oӽԇ=Жo_,zCdS 0g>&n^lڧ¦o/ƛbƇx$E5>9C a9din@g ޻<^uiRÿtZGwrΛobf@ObAzghIh`malS9h?PekV̮nS==>:V7OK̹3Ƭ~[U}\ٗi,GzCCVO?۾sCwf/M^9c.{K|hL 6([Šwv?4zLY DSmvl~8ӰҤ_\GGu'~qofs\FlGϳYEi{>K_uϋ,.r ,{G|h6K~^nWj"y׶I߯/j_D 8t}???-s_@)F?osgyAoIK2S &kaU:"J>㠝V5s|4R&q5 UK,J (ˊE)v>6o}~zh]i,*a^AŹ`Ėse`b^7 ܚL uK7l'G*Zc3fh$]Eg.{G}**h&@ 9CeOugn-hV~E7?zJ躆2xiT=1_a[7swoi4AT3{YRӎl7uh[LΓnp59jIL\cJuND&aV?:]'L<>5TY929k*Ǯ{&Tn^w ;=ma ٳSAmX,;꺟Imywf)P}$LP`6vDb~/zWTeGpYNV+ݼpaTmkۛo]٤Сri4ɂ~f)wem~ Kk׼ly4,H,ekRߑsS$a7;{uX;G!?k{wi\V+zޗK'=9:#7U@cg T ͚ʼnRgrӻ\uGGy a̘8gp.Rn' _|k߇֦w, |&uhp?gN{s!݋_Y6p)eQX6oD* tU2d2ws~BZQveϏ𾙘!3{ .>v;OO+ېzM|XH[$#+y2]ƅ57p?9}u71M?IՕ]i:H8*@&  7|HIK f~ ?Ipz^o67AzgH~;_ˣ'OE$g *0;2pSvk$lhP!Swt=t~ȿ{ƛ^-+h>G_oܻD~W{$jI= j>CGFXhFK_d'/}O-\Ԝg8h/uEO ">9o_fcx۴_o9qg31qNr(U (( 0=]ףզI3Ӊ#w-.:ZHI{{wظ[~8=MfऺyZ(Bf$Y%8d^if0cVOF<\qΌGg29o04a 7H[>8o훯[}"quZtj/r1Lz_HI/w8 `Ag /J x*ׄDgݪϿ9`mpzf?B=j` Հ(ٻ.W[,w/o]'&4Ve#(aVR" ӰKT9}/_$sۜE(7bմ]uy4F pVgϗ//[XRf" >FӞ *8/2wBs'>s|̟6ESZvD}$_y3z&Ś\57 KovSqd jRF(&7tkuD/a}Nv7Dq7зq^1yiSJQq&۬F$.7v׭-O~nt!ݹD5}1:N8hnkF4(l:zceo{ڇ{^iJjEG`Uf5T LuY5 /p|ʇt9֦ ONBwZ(46Uun:K^cBQ.| f+2o*xw¼|?U3o ? vhimQ|gbq" TM>| FSLP@)&L1hj³?9%[+ӤZ}{n^7)B$lE tmoMڙ9yמ l^AdgeD@7H6i?#n(\17"U׾@PIPzttٍIxJy R#_d&[Ht`{\'9)kiu23 9%ћ R"n~Օ6:TDXR3  ͜F8N_{cYCw82f&ƵUak JtR%9ym÷كOHuq>@eX&a}u :GKnycnY.uⴥ_p r@iUTA6y{Gi MzG#/$x5@;x/v!Ŏi%l~C'8E~K~Ksw/NvsCj6`+S?읻\z>A--nʮ}n7VL}챍:q=إ9,w Iap]9 *3cPgG:duB-ORۜFjgFh`m-qwgo;nN TEĬƛf}kl~O2RΚ񦙖YfbՈg)LA Y"l57|B>"Z3ZG l:"hEd.,|D,/7\OGv{ѸjP$3n9>E,L}!ⅥNrq[<Ǧ HkxXњG&ee~xYtad> >NxL}LB{^m"e,θ_y)6)-3+'W=~.fN`>݁ifNֳEU|_\9}S43P$НսQXeLtA&Bꕮ [ҘfۦnȝuyOp< 'vĬlղU$ES8A9\_~dv}^%NDvHM0u-`%ఓ)0Y1A 6tsܣ9PX+y%4,\:|5pA%{,30 ϟxhd]\~+IO ߟ#9ΧbcSOQ1{@GJ@LJ\@B@H` HS \;1(7e1yM7\}أ,:E-v7g$KA o;@!OS ӛw R9cܛIBww F ;u>xtҰt#%|l1D!6C5q LM[qډzo(,_o"7ڴP>'~!xL ͒~&ND4؍}\r6QE/lix쏨Bik"Huߪ~'4՚ьJOG|ʼ֡v_<թI àhm K č̢q.a D#)4bN߷Isݏ<9C O w^9jk7"*!)"64M툝;ynLڧ7MQ5EI{m=s{yc O@R-=r>m4?cuD͔teݙn1w5 KUgJyYA9$ h$5 A;{QL\{㌓3Ʒ Z(ôleZpfQ $y@%]}~^cJm~;`C͉w8 bB a8MjIz<ҝDiZB8Z (qsd$`tKοOWRq/|OKw_n2HrYػWڹ]PG˯iw->9^w`5iz3 cXN:iy'8I5:o/s&t9 5ˀ/0pfXp(o kq^Q09I'5ͯ~_DT RƔgӝ;~/EqV;5P:+x$0m!N4 v,%P_طޮ%i\4',|u6|\XNPKՔgED-!|Dُ,n2WeTx+֨S!B=Jq]'M[nܑm5&m}XI.Y !g/+]5*a얓*>:-f# nQ3:3"IEdH܅^9ytԬ>r#KK=& :.x_ڒ]FA:"%#8>fʟq#ip:EqL$UYs2Pw*ɽ⒲s ćf$m/_N0dKn7~XNiEL/ M#Cav̤ǻf>((5]N#w$ZktA%%5Լ\2f!7qIm7`Q718#FP)^M\g$`p*Qv 7(a |f|ZC|P ' :A{oo Kt*eT_õXz).z\SGwP.>v0c}3}f1t<{%B4/^'S1S f?B3N Ft&yv E~: ̂ g P-BdDhzS*7]GY}Aڨ8OLpJr E7,C7]ps/ƲҲ.~%6CkgVVJEtoquWUeo?7s~{y&LZBKַT^7LkcZERY94RWJԐ|p|۾8?3.}2yNCs:pY~m|T]54U#r@7ͯxǨr3o_-ÏW"U˹u^O&멮=D Bĩ%L8q)=kVk֒y{Aw78^S-i]btQ쨕sܡ0; D-o[Kf׭{pc&v=ou%cŶ 1V-:~kOp^Z:_o}_bll|>3X՚]қ:7-n-FL1d--7Z T&7IJz˨R갔7, ,.򤙩O$ R1LRoe Ms ȵk* ^% $, jc8S2I~GZ/"y,QoSZ *^@lߧ 6f\*;! Ǭ4r-g#lY(a%)̀(ATtPLL%\ In.ӷHȷSf.O[Y('/0Y 3)$B@󍈓RD_QgkJMmLHP  ( 0S#gk'IwG"oFrt pEv4"c1P2@]0K bBz"!@|OH}x+QQ`W.H4LG`$ !Y #z!F畸w%A LTwX-'Lj y70vy12HN$AG@ͳХuEg;b=?B9*QG""@8uQ: ]&>pO`򹁯RjTFrtS3%%a4>3:CBaɘXhzrz:\> ׉D0dCA@$Z ({\6tz[]ߔHlO:>*ULoC>lwӍͶxrYB댥 煀b06[-Hn֚Ysrpd:_2Ac x$(0r@NT;槓.=kfȠ%yaSb12 70O\krX@mnK;zEFDoЗn%'v)Za*dod>['8?9+OweYFϗ(⤛Jg'kWIhѯ ,A q |>}䔠C ܱaom\pu Wwm~~wڠ 0%-ȩK%gTIgf%eԞۗˊVvQ:Űu(q"!ȖBN!̱[o*%l~71lQi_(чrf{ ¦iJu>Sy)WQ}gԕ{1W.o'c 7U]6VբE'D>!$h'hlnEpZ /Ң Z-hv32q=u܍)Y48%"-u9Ge׼FS`o:Ьb}_I?p06 UknhKO{Y8|(GX(9Q(NHglQV $ɢ"{+G'j!=+d {B[p6%I1[F0qLԗZ| @ 3.AxQ#Q-x69xs1@X`d!GlW^gMJ5dgYFMI"њqݝ3*&[HYbR  AN o=rG:߅~hK Ҟ8X(ӕ&.9N]7 f,haɢ@hbdr]??[;ޓw[ )d8y ꆄSh8Ӵ]/sK V4X5ê9C@K;Ǧ}=O U㞘q AFKNo6UnI%8&(5U((9vzwޒBjwa$̀d&(gMmx#fVX[Xq e>RCjS{-[{SH pP-x_~P0`j@- 9@oU`U<>!=,z}뭾1ܭPh=A3]+f Pr l)'պZ1zcLU[,4p^AB/q|Eݤgu@)^V3AcH vJ"Y YLc' mp'IXӡYIssF qtQi5t RZg)#Q@ɗiyT_”3r%JpWgME1A 1S _j@@r$ՃFJ7BbYXQ\[10@6@ iS(%3ނҥ&-gF&@,ᬥQ̏ۅ!CxIId5&J Jl*qM85YYRf7&IH DQ&IQRW2rJ!a]@X [w"'j:IX$WT5A溤X)lR.HPnm$I|q;wxvRH㦴yjO}Oi Ep{* +8NIRTPnBS(_PJA|b%5nNlk[YwXc€:$1E(9?_D=EXCqF,F cr!ZBς! y @)lkx:D?=,GyD+(0e+--5"CZ9,v<$d:dD{ NATƪzB1ԅ I4@Djt>.T'hV$y™5Si[ 58=̅T`{?O#x`Z) הaS t72=UI[VG-Pe3uM:t 6e^I`yP&(ejl BpH$2 Lc3-&Pf,6= 'IPH HFx- '՜fXY&GNH"%" y7IRvx*qsd1Hlx@ ?h {FIyN8`@ @DR:;jqTϵ!ajH8IiM+pU#J;-ƋT`Ed`qDK3)1 44zڍ (39CR80L2WSv5{(NC Vɜ>ݖ[|1B: Z,LgA|_t ʿU%Q˼/Cu =;~jF8jA:1P˭IۺRImi]cS=r”ҦE*3l)O  Y^PY2Jhe\q{q;y$$LbDK QuNb_ 6t#\2qOM 8qTNE* R.9AV;U~x(#%O@IvRdΗ$cgm|SA&^0[@b mٺe*d۪2 S`ZbbJ5AԒp0r$f-a"I9 [)1f0 d"/sn@$`Rk"I*QZZ$ ʝfLǰXK%$NJ'5  iIROZ⭌3[uD'pw,֦.2JH ՞N [%cw9ģT.^A7+4]..8l;ˣ1 `K UPIʠ@q1G.Uq-t0S^=JSw*:& gL̹salU9~}^XŁ|6ye)'dakhoЦV5#UhqtV_s|,3.<]gXeSM7jW|'t< UD`r8bETTLW2o Np譎A>:sAN9gDI xXHĆQr{!C&COlYF`DrO+pEKTe7: cqqK61136y3%uZh T2+F._i=}ZQ4FM` 0EĪ ׸$/ܐ=F:1:]GsN;\G* y?A6uξ?}u١t0 |K]g2X*".|IzdoD4Q nf*P )ә! (g>;^'io|xn  '\Okιp]Nd2@/~?/xNyܵs^U'ِ3t~X}\T :c;ܢʔN Lyc(QGםON"P3zRk?_+e*֍11N jǯKa"M-IxKMGY( չ-qBHi&wD))B~2b$g['`*I;IĕT9b#FՕٖ 7AꠢB,DS֜fU+;I5YS6.P;fWYЉ4LY"#8_L$~Gj^"4)ͩ99@ JΓ1ӓSxkMHki>mP}uHD2K2Hp`BuB7Q YdTA[Tـe&\PPE'b)UJXCL|3Sfs%Ń^bACقn@0ഠF7EkW)gKc =Cvdn>/Bm+s +)X R!txmjQ8UíS gQq Z*#vBTt*7۽r)g.(i\"@ȾQ@;a Ht/iH_'0)e)n U|N- q i\y$bo_NHapNt$`~۞@B-Q Rbq*^g5 ){Dao.ps=DaL}xISLP=hэYUԻ5u1Q iNQubBjagItH*TY]{؜$ V[,A[fn P 1ĭ>aGA[ "^ĢzU)+ x"*^mO5'Ij‡v! ijQ$:G=u8mHZHN\ף!dWiNЂk у$vmHR XN钙 v6,`DfƘ)J-8Q`[c ]ԕxRӅ\ĈֺԯYk:U>)-DNy:f,n0JYخ΀iα{TCv َܴMf/FD9T˜_vkJHўMӦN"Yd:W-'L:VWu˖N I?>z^J)#FW8x_өf-NP* crE9̅O Iɘ"aH)Q= /.)"W"^R:df$F@,9RAu#91Zax) u)3Uh!?IS J@^,ofh>(_dL̓>`\߶;gXJ6yScwg󌌓p{q AS eT%'(`hT";l^w'겍[E +v{v`z6^#*(6)ӈ g9mlv't 8Cܝ6UMu̺>t$TRGVǥ&CQQn4Ѩ99}Ͼ97n0U22):N03Գ#$QU8?4;89{kpdхQl]x3FN;Hj KEb* uh֖`ET6m)q|6"DIFb0599VW*W~6$E_Lc=[s'|n"\ 'F*P`1 C$LɄj:Iy1e4^y۵Qm- 0 d(è,.`&BxhM' ~{qdl~ ߒ1SE.x ]s$A( 4M`W(d[' )@"&yrȟFhjk{{A_3BqD"\Qb A`)9p;US}Iya*{n] %)FI0kmBy3ּ'Ǡf_`m]t˔,Rt:ٳ V!eodv g;t>4OdAEBٜ߮9r@b'{yU'~q Z4xT\ 0S@'5#IZgk]4pkw JۮR񄚾e66 1 $7u'Ff#+s (.dQWH4\WTSٜ$M#$r֋j!Ű0PGlBV6jKZ#rx7 !?q"èY8biG2$,sQ*j3Uqʹ "k1N3r7{}7~a;9 ʯl 1ݵ ?!_VmMRsk6˨q1+::JT~%3T9&#I6r_k+QH5'֛ԅ $z Ր/uvl3; dX ޹n`E^J&[<9ZhQETa4UKtWbJ1CRRЉ܁XKxRv܉YYy"0RvUnMq9a[]/:l_n5X de/isMA Tbu/VSzaW]rK=c㊛7-}\e|%H[qY]۹i|ȹ˭Cr髗IbT!9Vs {` lmn%s'Qr,}vm.-ȜrfuJ/xGFX+0C0wRũ'[fW%-mǛS$'d/썏 wxYSv-+c黿  IxuY%@q/j͢۩`n5yli؞)&)Lg"SH`FNB2?wWoXNV3y^x!2ĩ@O!-52*%Y ܴ/QqoKzN ̄I(S3lHh_D2`ڧΈ2JMlK~_#CNcB¹WcUeO]s#x1C2das*u~ZAvPoO=ɭω03Z A1t랒$E0WHnvNج{-< }>8t:õ348ZSS97FmKwe3ߺǟx:ŰzGC=Ifg FT{X Vg k9^!yc}npmi($0;5c`dt *%ajԽ՛ڣ7ضK ɢDK٢eCgA3w pcq2Fe,l[=9r2vmG'Dwz5P5u48loq"rĬ#&m*}jWD1E>4%O+s6R \g֔3!:Ql3pad r(uHD1ƌJ"hscwE(wZ9o;- c77`ZuH3ÙVdދxjd%91zҽ[P^XQbTPXMoo.hDf# Θ9]5Uδ4Ľ!8+Ľ[*S&E:Eh#n;_4u&ʋ؉jƹ]vZVe՝W9x"+j^!3 |*`z'SSGCZ'yI:`okٲNQâ%f;"wx\(v[Ss;y4OظVDr~n ؂4X誐1y;I?zmIڻD֢%5;?lI֌#"̡ؓZTݣZk-yaUQgU81eV*|]>Vۃ}f+O70&1zk>T7UCU0u'^qӶFI)@Mc`!IҐl%Q΂cmm2]'b P:gIXT:+ߚf$y'D1hlS%;}/ə[U-ra g*uV㾒IV,tvkB4L= 1 i tcep4 Nh4XTh*ZqҲq΍-EMB'8HKl9Ҟv͉Gy?HY)L*U`8VժdڴXizcadJ/WxS"jR[QؔKv\*06'+6* !~50 xSQ4FXUQkP*ac3PkO$#Y3}5!ͭ"Ψ@JdMJ t[tLa{.QE4TA׆Y.Ho9"T81*A{T-|d/Jya)Y0//d㔠jjp"Z}9ҹz,$&seT7Va̋W=V$7يe+FDS١6+[U>QD$9YSLo '8[&ᦲ i\]5Uu+@uԗEFR.Ϙ^ F ԝNCAl+vr͕*DtѢF;zn] ZL#DGm$ vs5f=,ݽ[-Eى5X#D k YdFr& c( ~YNz\˾ 9#t6U*IŘ<{N&ú>~KhD_Ҫ',DáX U*kRHgSfReR P}E?y;_HZD*˙c %3oҠ ~uj#c8U*@X$YH餲zg&ۡu L5r+Ld&e쮘:i!e NR9wFFǢ]3;7꼑-o׏|go$"Vg͹v?og!&WlLn]g;d26+eMmn y'3rvaͰ|ϚDo]guj5ӝ uEg5qp&vv>YLsꜙW8ᔢG}לi\O^BKF9K%[mM@Q3k.ۭӥs0HY)> N4i5XDۤV[7ښRtjKn\1iIV?8k}p`~Rޮ۹6\62&J/i=ʝң7&"!^LB+j:Si8{H0E&Y&]K>EQrDkNr sͶ7ԵrmYGHM~%$Դc#-z54Xug.a2vEkY"m @B)O30-]Qo`~U&fzةxJ޼ZChHVFSeu0hci *!ٿ$#M Q8;B{ͽ +m޽OcU`bɚm5mgJ x[KΤ+L^c,V(Nu{&R7BC M7fiN+64I Ƞ$Q#4n6qa\0`h S" r׍eÎ*Lhb@{+j([mho2nn*"c9XKR*'_]hF\6+p2-ؖ (>_*c>^9Ph Qshƒg@U]oè^ba@,"j&*4X?a:d ~i։]y2 B"DɄNn|f  h°B4S2w|a.sraso> հviY[?#}1{=UO\0ZtKmg¦HI`WmdD̖dMg#ԚRU& Oږ{KP[b.z冾.1𞽈  .XR {N'G BsEf%laYQ ,C6$*J>0V8 Q3*[1Ղ PJIR1pte J4`z\ٰ f@Hghw|ur+0̲NmƠ {k[慻< QneGt$ Spq~YҪloj鮾zx<" Lu=Cɼl==,y_ĀUX?RDt}1C;nB{l&1* ?i.vËlַRT =Cر1h'K$,x3wzi5}sv,dd7X5mRQØ uYy3X'"MLն9ɾuy>"$"ϤXs8t1W-~ޥBD`gܗ@'`s drkbׄh `LLEțe_~E\Rꞇ eP{S 46"KeqW7,y€槼eO5FJC1#{AQ7 EKw3՝$a8>jkB13s)o2fĪzPZ}KxDXzd{FnQANaDŵ5b\2@ Qp&7o` C Z)]\7ʺ$&z)PωL/x7[N)vbLe0 ﶋdZ:$*/9ҵdu> P"xW\yzf6W,:\vyPk.IW DVA%亶ȖC7y~HNY6zd$0I i KE$t UVs2΂i6XnwTv3ӦM'e 3+=^,w M.R9gcZ8T^O\"Pz@G4Z0b{yś˔7^;9S#$SI cS*.V\k1XFd,*ꊑmӀ&{9Q@~ &%3Ù twJܳQnBz,6سx&X"ѮT\rAr;W6T4 M(d2*z<@0)[^ ӦM[.MfA|nPIr# )ʙ`eդ',-O3% =*[sv SK/!ru!-#M5+{;cu;:e(F{גZN 5S+d'{(HL$ Prڃ>EFc&k wj*Fo!67iЃ_&D9;BLf1~}b+ӛ׮?n0:E8C̯ 19l5勦%]/ޓ_r;<,(ΗmrE!êIRu8 f6Z2/V{ā}<s]O;L4uqI1C#?lr%:Fmvn_PYi01rtU3H-WZja٫co=urRMf Bwغ1jh 4 cEx<`VW-3٦~z3*}='-=f^}ꉼ,%uYjkM'or;"a=)jD(UǦM%7?/}AβFU9_tud홞i۽Ϛ̢͚w-m׬K%tG||lאNgkŖRQ!o,PJXx}X6QmY/5:hmO5W.&ϧzǴT2&B )k!b!)s]~,TQV]=!;[3 r 5RYN_ߵn9)Ww|c̘}v_s,,T$ZcZUª{[,I68^E27PL~ʥUۿ*Zla5W7>#yoӝOۢA7OUK7A(sӚJќ: SqR5Cu5wb}  v*c^3ō]+R &X!C `O+RUn67 X;Z_#qrl_@"i"_qQ'ZδL8A Y* 11 ,LChy $pW@s4a)Uqh%` oLwV3,左u:Dۣ qVn@G"; Hp 8N#$-X"5:d^c ab&(@aJdmDZb,%(J:2 !X[+:BɊgd.\ uEU&G0D}Ռ1[uD HrXbgA\PI0 B4Pޓg@t2"GZ=hm"Ռa t f_i*'8,le2%)0¼BliB&HT5'%rn8Z6k6Rma 1a{N2/5%^l͜<lڠ,FfVҪJ>44VA2 D+hJjҋ4?lh_nC?Z]\~TA!h+^%]%$PZ4^Ñvd50x.aC@"W`-P;$/{퀇amj_zXՂ~zPOA~\eT.{SWBW0b̲~KάP;3:u&U su_rC "SW ۤЂ4ڐ;U5" 8[fM_vRӮfe%w 4-<.`J=4Œd.PS!PP].{A+&*}"T)@(d3ם-Ϯ! )N]֎ fō6A5pKBY{8/x[5,`w{={5N_S;灈L፶f(mI]ȑ JuzY0ظ\?d%N1=m-rF26[սS kHK(*` ;$yyslj;by!`7t˖%0(_3puZka)⅖"3DF0QXkoZQi5:&]v><V RHÏ$Q҈t)u]SJ+"6_w9~^jTØ (D%JGDٚqtH5J J1oC'QvV-@>F1.m>bbn?^e*So_ +'NMmj-u@Ak< ZCw?L$Ԣ(wr\V:1/ThJMdAܕ6ukqk.ż=7j4Z1)o8}mcb-XT1>YnW̹ޠ*[tF= a}w1ex -ҮcH2 PcᆰX3`2o: %wώSnP fܬ] Fmh!|5 0oW ?IJDzS +U`8ƨHb 9Ky2ݔHd##$M} 0 6iQ -.ΞUXI& G++}ʑUeN >e\Tsm:Wjׂ{ɅZWA:R9ch1F *Nz g.G^Q4u]~$$H**5vNt.\C)U\$(-g6w&wM_kAKQ0DV&wIl_]($g/e3 CX]gӔ C =W:rC[v-KeQђ .OktKFh,;ܵ.7*lH0ֈ &nA8buc*=+!S ; b"&Č\m))†d @+PٰP,D dV*{]-UUN*lH,vUQ&IdHwaYcwh*@Dzbؓ@|ؙKa B0LxjW8r((dǃ3byw|UC I/}ZrgvKJ-P"VAM Tc,+DMEv1IOIY&R'in#lH!Y5;9K-Y-'(XD>;m$8PHKDIqQl\L7")a4B"*CqIy=Ϟr=϶;%:̤`2Ǖ[1w^Q-3\ V#$h¢qL䮝e*r7m$ Ӭx!ǎK;B :/((2!E8!SC>(\TP,BN8d" c)KNR^x&fEF *F~*4 $6 $ͭA%왇"&Cp' q ` kT _afKCFh]!/[xsj-ֳ.nMwOrYWgJ$i?P[nݱ£y⨦m>7q" iIވ.xӖx?7j,֏ijIӭmFR͆] &l:b*G?^Xp BIVmFrZ_זGUvi+"!װs3Ƚ 82ZcA~M#9LJ"}gM#oH4XT,GuRBi4R^@߰#Gh&bW}>Az,g1$隡(kSYb&Udǁ~B[I@%t3"hDr륷(&D ^O%y$/ ?Af xw\uKe_f+%2ԍHū#-2MGf!3@# 8S˫+^L6;J>VJ͚2*5QFzD!"+Ԣ}-"~hEr*BQg܉i"PeFa ~_J%h7{S0ClH\%P!8 0!,RD_M]Z|7遝~"&ũ2:1S,mm#lh$U wL=$m${Vwsicf'H "'xUnQ`trJ2;JdA@ ?'IqZaYihZr , lF(ԕR9931>_97u,I#l uKNh2SE[= i֊Rј-Mn*,6&E}5<6b+(\hӨjhiӼ%+ux"YZFޔH^gbԅ&JA`L ) ܬ:^i'TG]nYϙCsKgA~)95>R|ۑ>9uS*?VR3B=Q qg,yMѨnwgJ ÉYpF8LQdJTO`e76b!;`n5)\k 33~b KqQ+D&g6HipyYD5TE[؋ JY "Q2|8&"́F?atL#jt1kx'm/C(/듵퍦N, Wa1^r=9(l=/!WV~AEÉ`i1OJsYC֛e߿g6$S*ې@UĒK",V1eGD'H!D>')!v5;!b`M3&)D-/ު!)eVipo"119&@Tr Y}/7xjg[2G7g(2B5ER[|S?|6rܜaV"0W&l' PND $ "Zy*x?U;Y8|dDUQ$JѰ .rA"toSuUg$~d6 <@|%D"˭ *KfhNYjǪ2 #'fn`+P qbW/$l2MҵRܐM xf,@J#' K\ ,zlQX`vHhv05CWx%8Pd(9$XU` H AXB i˔)0H1!Er82 !1F7,W5U Ҡ[:WPPU"pN$l*ZK|"<&W :fR, UTm!#Q"l.EG;T࠮tX+T|ÏTipfN#cW}iPZHž zG.GW!U%-ӯ9{P(Hi7cO3$-C^aB3"`@ &#Sv b a68 'adfuސ)Z)|0`cK]0Q,>.l#aüՑġ x!SAͽMD%d$k7wGK+}7M{ brПQGVf+/?SŁA5+|Ɋ;uMC J@'?I*iG")]Y&R[yHiU@8̖;ߦ:(X-O񨙼vks9=ǀ?ay1Zקj7pJ?gZHN"o`VHj>E]3gRq*Pr@:EDIFAA 8 UxۆݽiӂmCV;b"I0 u ){: ":sOex4#@I TE`2Ao':APe) $^\b ӈS B,mk7)e'/\#L2Րgq Q7BDW# Z ,.O>qBG/ S|yhҝfjSn3u Eg;@$L=B,3;?[H΅ ~b"bD1y]}B9(DL%] ߻.~oyIx|N$2B uׯl~0ī.Qݹ~I[؏b]n^U_BK(j3d@q3$椀f o8DN+GfG$07 gt xJKJY*JD6?w+,NBP'nŊ:kgHC6% { =J'GY`1+Y(Lt`# Ip,a5!U8Q HE/' y,'6E(\!Iݮj.Hjy]ir]{ɭvoX NP=Z/X 'nj7qY0u%5C.8>^]^\Ț `&δtCAeƶv J;HeȎs&)thBDY *w\j&+,19MqV桂(`P*U`} LRrxCCN"B%;=㑒#oTf۹i{[%ɨO8bn$f}ҡ@@+-`ʹrHF 8b /91c+m)}cr%$Th$}DNTڔk9Sqoپr='Kѐ(ԬБ`AR=*LN.y`E*&0}T4O\\oď%X[f(l.pŨy|*RI7y4u\7dJY©jIJ_G$,~NV ͸;SI6hxA^Mhҽr1 Xd×`ڪ`z-@NʭnaE i1h,ض>AjD%El%#B9j(/3%,ܺף\֭Ϟxoq-!ΌDӈW=LtH2e Q0\%N{ӧ _m}KfQKѾ=LrX#R?IXɏ-IL#e@!âQ #(ö"Cc0Q$R0?SO'U_#˓1x "Ia1\NQƈKt^ɂ6ܾtRjV @@jl8mb O'XԊ,uwDIm){ /58%{X_hDDsy2p7ZB<ӁN}ҀUPݑ0Tr.?G+ |"=Vcvfp>R#z$^,\N pde&@TA>N'Ӿ  Ra, d"bS6of.ZgIz-] @y>W, A$D`WT]Q|"g*µ2FنUD9S7]RCϤxN{ am< =((6X}r2J ʪ)d[8M7엳FQOCI,SHLFkS1Uc +o: (iԽ uLv `2=e/D9oGpPb^T%@FrݱPbɆeNWy. _$HDq26hc".)hJE_:I"k-]+bDT!  idj6( dE2ץFh " f@18F*+Os[ 'm@YdS%1XP"g2)C,6F~u3u^w5 6@,"h,85)uaتS{ń>^IþX{ޠZHvJ6x H dJs}kƏՇ}s,vCйH9#a82-b/$nR6˔~׀Ly94Z 9|X4D ٪ VmVtv\j =F D"yDTL)CZKB ˋ嶤t4?θ8KNx&#vE6m>g}sRu}[chD#\w~3fQ(%K}վtRVm0XxJ)Hd`FqʉqZPЭ g\:ۗN[Z"2T oG "N9YJ)H(W}$R=5MSLqZ?-XAE 1VC!Zkw<Qo3F1+oZdK*&}C>T6öDL9yV`Pp&GĂ%U)~ .VA bnVbc`:X~B#̷ӂ` 4fR @PEGMe^j@\4@R=.?d{(R)ڛ9#4Ua"Dzsî(XjږvaH",ʙCE<nʿ|ՊJ;uX fV_쌽EvwJ V:,+T%_ d>\L3Ih?X&-C09m6EFzdcqBT_CW}97H1h1um".й?(t#(4`_"7\i딯׷,RDVUV\kLm0%ۦ?M^_CGǣw;j,hQ[e.L1xa= )ȼ ] B--u,!5 =&HU!0O HZЯm8)ƄOCUD;6j a"&p:$K #Ί t&ߎؾ+ 4ppq Ex\]&TD5(2.˴vJO|mNdqz5H d )$:@"^ 2%Yߎ?'. 2 2X3&w1#_Ֆ?ES$lAXAT8X>5{V = vaj7wLv!4җ9 :Q0̓kYxDN ʟ&;IϊACQLY jLdu3U drO;P*s3'`U ¡T܊"X;5Ū% X*)3>fuYJfM" ;n0jx)2qN PEYahD-*btXof>Jb  ;=QX葮J \14 OtʉGLY<ѲHIWP*WObQuL`gx?vZ)^@:~?5 ,7e}XފiթT_B 5:Q@W4CxmPW>x"35&QWSSO ӼW6RbA#)#|P*T)VijNQrŇp˻e{ω7ӗD,3L]&!@(/ZA(H32 ;_k~J:o$UDaxHhM%zdziܚT*j^=oVMq%fu$BP 1V_8ZnC_GSMhʳ{ ڦ7BNӲhwbGO~o|/)uWõ~Hhݨ3^~\xߌ?_]ۏo{ʰ m5-xJSW9&SI$iaZ}ˌv@ɝ eMb 'c ͨfL@QCl]poWɣ\~{CJ FL`HYUW<;R(w O8 /ZaP..2bB*s#e#K_\{Wowq;QT;:-.MZ5 U5AO6y๺zX}پnC˭ O U}AFktʺ-V^uŌi ViDcok^ozw//{ FVFX7Dɫܔ.r7ysw-hO-jk)Զr,vm7-7mYP|1LuAkzCS Η_ޡF0NJShu8bPldG=fݟqa%Ka:"A$$74Mό B9&9+9Jœ@pfjX0'iGt:,(.HZd5`0@1UXTT"N;tH\ϋ.5+ ;)J cl)Yi(i8ӎFzAe$,b1qJeb;S4B(H`HP#Nrƭ;bYo䡡b?)S< N L;%KM5<1-k˹!:F 5(*fjeq?l&K;+ݹ䜑H6XQg(LQumQq0ws.1vd7l^7̰JL"0eR 2lݍoΥBZտ/ 4_TvV[dClt1gkx^w̖kLeLB&LV{mwe~r˪8#Ǩ8SBElK^3s: ф<қGMEFTQGZ'/V);5?{vL33dŀA 82*k?#O/_w5 =䳣e&@0 +HD;;ۑ6̞>pe^6MXrP$J"XUj+D[v _:T^3kv"s3 #3as;R)_X*((T@Nr_/MD; imZ y0]4e_M$HHrNT-ٟ9N׏qυP۷edl;d, 3#9)Qdfn_kK'=Yl€" ?@S'fFTlBg_vQZw5=a~]2Ɵ f}f F$ E)W=jg:idde[BgsF8LÙp`2#)AAїsMxg:TwK :G%} 3Y(S领)|3g-+H4sM4nO'^xdja9)-!ѓ⹩LY7b}VZs+Ձ@thaʲ~*m4mg@ͨ<_sb\oTU`s,3aSilӽ^8f('Ge]~x|}]ͤyV!>Z9r{όss4OH|\ˊ-Ge$+?(`4k:Li9Ÿxu:!z G hB;ɹxހJ(3-Dhe;$ȕ.qN Db 1@QhJ9qަOa8C4JLd,lgx@zg^뜶=XPNJldAII 姟ZxTF٧hl㢞IvPMeyXw7j}c3bIPMc5柺잴u~nTsZ G% 9I k0b[Λvz Ĭ6kK}&BHTT[/YMj<@̊$%"9icUuMhpǩQJ\ 7xt4rd|fĚ"賵1mHnZR[W8yU;󏤑n[CjC<@ $%uڭ 5fO8s0 k8V޶ťeiXՔ3P)fYuJ/ԫݺflgOZ]\ mM 9_7`ek>ρHM(D(A،TWFkVݳ  *#K3dZCd#&~2^[iC{v?=aޚ(58%4R3IFcf)ЍlXSf_}wT&˸}s+(Jd *tl.{YRgNّ[MMuΔm EƫAtS#ERqJlڑ&=߻Ξ>{[ stx Qpp(`(ٕ9#UúB8Nc hs[ S _k0fT6>B-Rދ+PhP)S YӖ}/>N2'>2q+wM"uV"1Wޛ|Ow5Mc %LW3̡Vye$t[ӤJ-yy'ag$́jI2Ixc+-Ma 3FȤ3G̡@(+/4鶦Ihl*DcfI"%WيY14c-9E=KoqzcxudZI i}>tqIS/Wd-\fFg{|4E*ȥ "Y?VJ%&R#h JH/u8Pv (;!LdP]׷{UUXc@]pVtT'DM$̀z+fYOA#Ӽb%t"'[e"ZȴfZJ<w!*Z a!{̵{;54O d@NyS Ƌ^yx2Di,h(qj&k#QeEaWgWw `Utѽ/ 1!KHdkxG~QV.m1Ni &Pe}-~] 0"=U=s  0ɳ w@ӈt:'8GHau'|>DDCE  ހ(!ܵC_7q6z[pݽ]3e,&1@ n5{~ef󎑇}#m{#Fè ;N1oYotzFBD 5fmpv74hOh}H'(b{~Ȳ iDj۠'~Aq:i[B3YnTF5F5f/Aެ*GbJ_Ix~?t֣d>J߶Sq0h2XĮNӟ,_kuZ/^1I{m\oW԰7Lnlrҷ Olv_pr:$kEeip7UinV/rt Ac#%RǹV?+?d^u_z/j8YDըih&o\y|.'fס]ep:K2ueyH훿?6ebc|DžsxAuUFI?>랿_S[Chx|qV:LuI1 *i|^q3&_jd,x+,0֡R ^M&dܦ-4ۋ-!4Q SPޞA4_Ds9;7a+_%۝4,Ǘ?(@GPDw!1x(2ϳDd @@P-ĬV}ɹ1^vS%Hh^Dx]c2c0b'WLT@,μ[rK[Ood X[6%*Cj \ ]h}+G:yYzwt5vҊ~],/}; U"|!V6ԏ@S&c˺$qxݱ^X 8"lRc2VbS;wiҸK+X*c4 "F6ЕX[?KǍM*ֱ+,X ـXcAVª[6~n)>_ѝs,/`X.Z 1q% p9wnTn}S]S*x2ʵ>3h4ֆhJ hJ {'3Xړ7twE^ke;+?=d#.)ysL3G5i+1OsqD(ÅdXE7ƥR+Si&HrM ֨ o4xw۪")sq3 SDdFQGHc:%F؇ "JX %ܔ\%H7Z#-Z\gߺ'qҋ+Grgr4Lt#hLFeY"uP0 I!&z1i\wVKauȎ*w}fI6z l/tHȹ0@ch>1"T$LabR7EW5 FXzY'r]9js'*"S1WdИ35I,1 c#!JzX;(IıT C-*Pigl/c7t7ME̽좣NjTrq *Jmt/}EA,;4u]Mm7KBY}=^9Zv cW 5iw*x=& vkv5s:Q ƚbbmˤG5I[#oV:JITVdi'o+ĻkMIXB0q|f[[RW׭0Ǩ7WN6Oj0N4U{i0pו?};AM^z3u?h>A8bA%q>f&d\ku՜AK'5ɿ C$7/;Y29 E 45DH~seJ%rꏜS>?Awv7PA*cKj,Ѯfns;w_\wh0#p(u<Yc}n'>]5v܇9N$v7lE7K`5?ڻQgwG\Nvj/*AZzIѴ aٴ}] M;nbH\ǓwǤ[̈_2,~ \`\/]3h&f ⨿&l+?Ρ򤏪I?ԧb8 Y>cǺ@piw%UC&}p~iT7utKJrۗs~\}崒~ךǺ@A4unsX Yj쳌.Cv}/=:g#:GXh j\f*wжX^sPH.RFYPH1 + geWlkwpm"FJZ>͹h eZ~ZBGC3*|M}8s}U U_^8.Dz^k+%Ij5սn;z|x_w-^h_JS0qzވ'mIU>6WN!oT3PPc]I+cM I_o}@^PTWZk>ʵ.n/L%Dྜྷ]YG~2+GmBF}orج+vj=)G4 &yJTt/ Zc޽\&J&nX?F$a:t!$>XֻnTnrbUYwXT6y-6 'ȪW4UW{UC.u֟=ZPOs b]%I˲ɕyZ'iw^j)/w;s[3}2@MrAO %N%:/{D&K:^Ǘ/˫]-.=A&/{=OD8=|4wԧomu,{ҚRb/ֽmO}WNt%c" 7f;u[mDsH 1 iAIrlSneJK0˛=pω7{rP+8@ֽOt· ia78C8~Qϝ{Wv7tRܺ7%Hz6k*wJe3>]# ] G`ҖoG'wY9zi1m 'v4muPH. ,/s@ϟ}z{H{%Oʟ)9?&ggvZ/~WtО:(Iͺ{koKǢiz&WK[eMt k Iys'PDŕ|M"(ؽ鵜3ZڢBK3ڃmU,6>LjS ˉ91[M-ӂu~A,6>yFc>c}288WJ.ox5zk|% n7mqk2ߙ~ Ä4"`ACnpo/d~O Z+/ǎZcP/ʚ)pw&` L 6Aoki[}dj:~78 Asm7}Ԡy$-RП~!?voD`t`֠Gm]l_Yt|qi4AZr/qG3ʓjg & Kw \(FMj4@u8$\ިJIP}n-+oŸ<4$Kh)i^LR8F2q}WX۔v!&1N $#"zBN[|`?HS#dݚq\7>LΠAc5MN!5S{z:XMҳڏu|p5]4mK#^}+_-iǧΠCizvVP3炾Q>˷_Iև7_pĭ[&9}y,Lm>5` 5aGVGm~g=I)g_"ѽ6k)ov$6ż3 ̏<̺zdM.O+,G5 JM)}{͇k=WIO;WNGwcaY&)&}ϒ{zǺD*ۜ/X&)M>Kz2}]*ɝˆͥ#[iI~SfZu!nTí;lQ8,&(LDs~3s=]߾~ 2Ģqh٦+e?İ~2|f[ # 'ͬu悆Fq?zg̤7=Ng {.HUw0N`+p$,B\r~}md64Aw_fB,:Q'tK htmdI[9{\;i4D\,y֝t<ߵHFGI҂p* pñ,3y$OPs|yGmI7ߵup*a( kpu] Sv[~ QҴmk2ƭ͚Er'3y('鳟խk[u`h.=)U{ Z8%sJs|cKQ:8-Է_>qȥ 8[b󌱼eKbSo3mCcR\=/%L % ִ5AmR]ߖthrQ 1`DL(p2WN~ƳlIȆo 8RD$ ?n0klIƝH`#g|_kmIwѱK'4RP `Z@ָ~PTg5%==˚'eh$gUoC:z O5&2f/K2>! Oʉ*zeߨ9M.ЎΜ'xJjMxkAs wZϓ[\չ[(7Y}:sP jS3A- d7 [~}Pyr'V.[o~ChFMojn}TB+{>-NR}7'OKv|jtPT)vg^IhB “/s㿲Bx'l>4Y'%BpZvn/KL|?7!>vMM͈Z/x}o5>쏸s2~IGFӯ&A61W6 _{g%ol8e:>iO|SH<Ư~O|S 09-NJ!\J`ooSnE_\~SUCMk\>jȲB6I]٥Ǜ _M ,)Vn(Xk5do{_ϛ Eұ׼Ҝb~#b ;]|zбyS?+ps0h1lm㆝.O:=or'7d"=5=W4]ieVyk5dr;]|ϞLc~A;ŋYFVەqzMa VHD9yO}Ϲ;S\F@#9[ϒ|?Dǹ( CS4EF ó`ymH[M_ e`ԏjGv^C xPQ *AꙐc1iGʹ1ALUէ\Q۵z@h;3$/ЄPY-lbHNmt8 l)2+36H_3l̜ؑ瞞y6&܀`# Q0RDᔠ˓ΉWF,w0iMHWPdc- f5g˘ !hF_mWF pä9XNSH) LPΠb,t6[5!^0iN'|aqLV|p+J!SH!5CdX }Τ9{V}t{V1,ԛSmar"j+wrr+4}w2i̷Vnqn1V! ʬfӿ mY j.[Kw^xŤ?:Tyf8#5LtLЩ꫋𕵿]$;.<<}Ͻ}R ~^9}ONgzIa%~H>~FwagEb286⎠Y]u0a{oϹ2Z#b;+K_e>H&T`2VPk%՞Ca_Ի=Ү_˹s_]kOzĝi{gs̓D2:}6(n:%ŇRwv)0O~aipjn׶M.|LH(V` KΔ[?{{9w}e)oj3Ʉj6$onc%.חwSn"n~{- mjfi Pmv(\f}mKuMZJd@rkuS`C6W*M^%g)~WI/[S02I'J" H! #]$m:)g<8)>t%ǧےsx *LXDI͵]@ڴrJqЎ/Bo;$y".R$W^|G`K*} <; \kR8X%I&ˤP4*0ギzݴ}J%Xٶd-IV&M0I.ODA+"v~uk*۵"%5,yɍ/F:9""j)ڊ`ORʚ. Axm*,@6JA|fM +RIlnRk<'@V&o dn 4{Not8 )|<`8ǤS@ .f;MX{ uic694a'k]'ΟQĞϗvWHwVS"ffdžiR:+G]`N+ǸRoPr덓4Ys_daUK<{o+eTYP8F5J_| ^~>:Idw/gCJhCZ&f8l KߪJ%m^H`bTpNd󠧼xݎ,L;"yʕb)ϸrDq-fXcp!͋WoV ጗1,(;]J5?WXqW 4v%^"1ptX=eҕ/)nzԥ7.]l+SxT0[:t?f/пw0ojP$SM 5#Jf88AclhG4Btd QK-v$=9iPX9$jM$@La߄A,hYb.v IIr :0?"nkokRjk,55D_K 0@tk7kx}RYn=,Ҟ_q Lƅwmܴ6hs R/^tWROCf[MD㉮_@!7m -v@[,s".P1B$A-Áx48h-I1}D9&Hi<ؖa#L֞^He$79`z7!@Du>O<10 uQ@|tקH:ܻYdfd2H|I"A@"r{s,Ӛ5wBI+؀ &Id34b)Nt3ԯ.thvLa"+dThxB#( f^6}q?/n 1?}nz^!~>z^FG@Gd_ĀłNǘ생0'@w(D㫮3m^:]H3SWߴ1)G !|u?R'8HbR@$7zji8zI1r11*xca{u2}r&Q9Np@' =!\}H2丐Hi Ē%_FZhl'{2;twqS==ʮ O0$4u1ePiB]`H@zq{({lW Q4Pn֦ Z|3g\oK=챍S:d# ӵ*N' eV5Dv5keTnpDRI6DH2ZF~jDfq3C/[|;U/cGR MD*HB9Um(]Z%*7q Z>>[J6P)$[İ^:\M'8^keMܱ!֑@`P5-M o4ڐ4gؓLY)<mH++XXVZ>`#g2;S,1F HTP:X̫G BΐС$ JecEF,SVQvrF&RBn#u\c /I;lɐ-ke,F(*gf^*ɌD5ǯnaN|_}.03򗗘7V` L]c}͝)c\[|oh = %{}T-@jI`{N {7M~wɪ_]YIbY-ヨܞ=Ü4$?BܺU-"@ q# E #@q*L6_);E.D/j{nRq2G&H:`>;b_LМAm\jH.=Q ɻ7#qUӽ޷RHuXj03 @Ncu4,Cϒ VjMbS$UM~1j|e[3H"/ƐGu ]YH!0Ņ|{ǼH޽9 QWO)VTL &~/lL!Of 7N*$>C:y+=p2T?9eOr}h D*PN^l|)j1>#J '`̥zE)$t:ujvh0yi1l W+*ۼa7_yY&D}9&e3NP%^<`| [1SiZLQ+&yWfBT;\dTHEa/&/1K&Jճ'N'Tr呓s+z{7-LlITOrW^[{i!lH!R&' ۓNj񡓳+L+Jm*xLO7Yy=6{6r=lr]MQZ``L9pNd,&U 码wc?DwM7@P"SPFXSCCl: NUqwz{3#/ܬMbKם_fO+V7ybUoIyAcr xe,$g~Oסg?3'ƦX$96jnu=W-7SK'&_ @,*5Y1#M?G]zf$_=3>U %*Yd,H$f~ е_OwÓQmx24_)L)V 9_k$&dR$:U͵.Q^)YO5jt{ T@HWCYTY!{ّ>#u" w-y)EįUd/XNH@L1"aK4̺ `7~;0 ''o9r_tl"m00&MT(Dja&HD-tjx΁\h1Sfo_ck <BF XJLC!']PSp4?DT͞=AA pc&ՓSJT"?v'1 D1'6LK*Vj>L%ǼɬK|c-VhSgNձwmFquwfc*I81O=iè 1_N2PEжKtLT]9YIgXT`=VQMS%~]~bMzz h(NaX`Fi Rr:>!EMH; y"0 >štQ*rkչeMHX3^OQfS|(6Tѹ-wM,|CR ./LF'C$dr5Nt~`.%oyNb0z.և&sӟmڙ}Zvba|%SlY)UGe4aBl_yaJ;}eXaZX'c?\GeTM#uqu?4.7wwM <9gKd! uT2U|t;zlH>/AuP:$e4);KC~ i'Ks~H-Go<ҴO#L~iُcW,"ݷitWLhDCk#PZ*ͭnitKMr 1I`e+,hGz[mLI5Y?aQXnТ01R0I;t[6~H5E05(zOEֵ[(WdZCY @T@ ;D;0>YfMM+< FɵRPZCkcˌt\5{ᯬ(FE1bfL=B>t{6:˔|sBJӂz@j5 nW^Ք>،fhX:JF0B F$K{u % ?=̈LR)E i4@_i^fGs4ƌn?U8uSV^EGihKe%#Qj=\;Pݸ߬2'E9Vѽ/jGY4JzEXcc5tv sZҴ~%RqՀ2PHA!b(;ޝ|JS\:h"{.+jH A5Qv3tmIVAסY#(No6D#w WRq Ivz-Iͼ^y"tMTJQEtMBҋ=B9(\4WrvcIOα]/z-#BQ$&I]WC<ˀn{o#1D<WX0&?$$<Z<웙B AzQ3ɽ"| Ki$}v0mސ-~4p* ,΢&B %(Л(Õ RH=F7:@B$%2 ZBB$D )B$;EKAH`Pυ%AfOjDšl X՛\Hx2-ՅBBYsYN,PSLc(X H"Cg J]aNz: i&wOK HX2U@MDSHKV,3T gRQPqL(vŵ6bFAڞ3hd05$E bKBpx.GU+4\HЋDk$D 0(NP)A̴|Z"3YNm}UqX}T-sZGM#uB̷oLx+Z[<͊b56`b1Ix[1n+<ԡj1 =8eb˲:vjƄrc̱YmhKyWʖ;>pNBK\]6*&&ɪ|ߔd ,="i$kw dT]gJC^Ҙ 1 %@ 1["<1n 8䂬d(S6kA7sӌ핲ݛu ^zF־1C0p`syl,+oKLr@iirIFhfȵR#V2ٳ KMPjfmcuCRn`u_Tt~D]C-IZ`E6gH4OQ) Y9a!֖=kvSRy9JX\Ak%=93䟛(a֖r|F5d."&m-?? Wڀ(42z*8W#P <#ˎ Ȩ׊sVxY+ 6,sN{s(VVfKVE/ MRͅ3@dB[>[RsyK4_'0dRBZ,:pl@@Oې%3\3Cpȕz#bHHhJ8Ei\dB +;r ! p垻a!Q@FxP~d$Ʉ5\ɝ\nm.fݲK  X+b7u6:ـ WZڪڒ-[ kC3Jӕ(ryrlHsq̭ۖs%`]dT!گBδ f:le{':B;v|itjIg Ns;!av]%qIm8HY Sŀ/~62ێY(k6's#z$dvՐ+Π ?<˛svo@eUAU(?ˆĘM<N0/΀@x/Y{HixQ %j,RWˆ: @b5,#68N#0\u<$ =@Tq[qv.sE6Wt+*=w`U9 RHǭJ rsƈ6Jsi ?bLUz\ZtAejD%W *!xnKN9^Ui'>k_мXKk'nJb kP BΝ;9ev&˶1U{3gl j 0N,Nb6'LIiy2$f>?L%`5e 62g tn-;hd))G'#\EsW $*7d)5Ԝm%U=/oJ,|(jMPdf" sR҄xœ](JT(Ŭe8ER= 6}rSrmXdK&zAPDH8xͤړ$'ʹM&U;SSQ)K+7jzXkl9*=n©rL@OEFzs?(•<C=v$$o`7Z GNDG-bf5{lI@z7&vW:\ TVPif+؈ 1d!~XCz=U!HY 5aK񗶚'-6K'A }{:zefD` ldVD?aOUu{7!;{Cf;cRmrʴ^aH(f 4ulHy̩;vVzBE8(0J6TDZw3}Ғ[f~cWNXjU؁E@@h"SZ֚^Wiz9;O'AP=3'TKd+S0^1ٷQm_k=!z}oȡ>:gW #$ԲZ@*'H6QC,̗-!M=ͳxdZoMָǎ+ ({,Ɣ@٭ԅP8ДM{]o imOw+'nEn>a/ z KhM^?X3[}`RYwG82# 10/'9EOېT#H<)Jh%B *$n m$Ǻ~>,OK%4A8oJ+ЖD5YqBZ0lbHDCQ ٞ'GUНڴ4ۿyLU~m~L4cE4ުm)v3Z:C{ h,Ky{U_ dk:4N晆&9r#YZ8_BN' Goh'KNR&Z'=,uvt[,CTĨP°:$ƺ*N}OF*̎ϾWTPjyߦW|w^m8`M )i63} dӢ>+ ^II;{T |mRHu"V7nsS9WP8@SHmBn(4WOHBI?ǹ"sT Hx0moICMu:P]TGf|^/Ty> **&A Rw͑^u|)dLwfB[8IlzU78vF}ǩ($H@U Пlip)q~I#ݐr]:KgP`qU1o "vcһǑ4mXeνmR48nK+'o46{ܶo#Rsiө-ZZn,YVzcsIݷ) j<)av˖6a-Å[+EknC$fƦV3(;_8+oN-h"Z.P<]k%Ut56%}^O*Ee GʪԦIWg҅$F!+_TO_ ѥ 7ND Rfu(|j欝˺UE%Nf{s ݣeZ+ O 5록-l 6H9 g޸iY94Ju55ӿvgda}.p 9ɎƸ̻wV[3GrX`W_tz>rO嵯߿S)W+كT,Kl- er@q??:ÆtwWuDE^ΨTL}x8욧x{X6`o> ߽pù|UD9U:MQU<Ӏ>.}{ /LӔ&_}.XRi(dCMifkߚ[0$MHgt?So.E-+cVLnr٫z]'4з&}[mHض$[]In3t`ön}Th VV{ TvzgmeMLthUTD]F.bպb~S?L &^6Sn3nInHJr[5f@q%~i͂m-6s2i3Mh2t|Q~֬([Z33jݞ6̓sW ?t,zܖUsYYI*M0z sKغ K{"k\*#m5Y !j89R㹻܎ FA)QD%I1ʚ$5㹿܆t0p$QXGЊ]J!qZe$"SMWg9n߯cYx)F.wˡߐj|UTXB:N^"2TIBx'}хHI>P/$L) ʍ44%ǎ88qQd h0,>0mz:7&ńNjOuA4<Up~M iOmqa9:)ggLPUQ@B4;^6=V!ZCz"B6Š)9/w."[a%V@EѵZf!P@){bҭ?fw8Q!; ]9/b-)>[CN|TX N6fy`G JWv72kL@;pN3K YVm.\{$EvfGf\&=JݽL&[NS-XS F]{D7cvFsq F5Ubbhon@tT;I.&D{:[n~ C[l@LZPV<2^KS@TkTtfǁ=4PLZ1umu4=6f*L߭:;gjK<  uԹ}{B Cmݎ]GՕM]BQ5Ӎ~oҠpy{>aN3[uTTCϷ;4!NT1 1L5 L4{gS PaJrh볗7O倭jhkU0ۙHٍ@۬0%9-#R%yi}ur&+mW#|O{&±VfVtr#&R(mZV.Z@~{/ e g+j]TrIrlIB3HX6@iSQ5}rK [^u\H+D|gR%QJouVxmT)e\5H+ e7TU {r6VR>zu Ɯ} _M+(驊6TM (Sػ>Ϊ]tMn(R4 Rص&I(,Wtڨ㨔I((#Wdi8!vڬ7Qa.w8?^d>}]v0 v$&2E !:wsY _w(& #HnD A#ͶE+?nʛ9# ՚ƨzvƤ9in|O][vei'޾`Kޗ!Px8d[LL/*(Fb0g. ”T-< Cv dWxǢYe-?Zic#(оіj,5jdD&qz67Q@lmJ“}~ O\+0%1mS[dWT;ɲ;-mU^nϤݳy醓ģWNô$*lAڢ &s j0L{Q$v0q IDb-HR7,+X|PnҺoWQn ,ʎ#v` QȈ9/ "v^De$I_#HӮ`ƓvgR )irjz6,S) f(,3*5NL7bDpy@ԁ%!XK+Nz-$% #E淒,,Ipy[:( VYQJo[(W t:$bFs.YcA0KwlDziv= B:13 L_  Kڑ[sR=BBg0[)C6 #z$QJdK'4ڔ6^jGnh7`(T~͇M="J%c}{7xi#f_ӫὨ${ 26N\o:ZC-AuGqXLK }jg3*_O\j3@f,|9};D7Wm Ľ*[n|$|#%T?i{;oK54l!VhB4Pe잵Mx8iԊmnY#25H T7۔*͉,!!?5Fu))/ Iۙc*/RFֹ@8eԔ΀blwvkw(' .?,ͤY+A eE9# fw*\{Bt sb.pn9ۖ>vmZXTW#&iOB`b#Tg x:0IbNRZzH ʑxt[W, Wkxw5mhd6 G;H?7*y|,Y#g.0Y!#d}^[!۩O=zg\0=U:) 0-ZVH|n3_uM%Dg @9G]-Y؝7u)ɫ_RF8 :ϯPK0&4N{: MCp^4`W,B f|~~bA(lY+X.\ȼE2lc3ˊ&=/(Z}ϼiL =7%)jAdڬ1r\캫9cDqQi.ho0s"dmк5Z+7OV%](\YƞfV*1-fҧޱkԜG?A DM<; s$5 Pe6ǧo5]~M>Iݞ)sm.NzC/עuo+ ]-jY@ޓ]kH(=8LxNWhM.S AG.ND`|U-~pMc[oMLnW]˹vy\MԄ!(4 s[J`bRnWN+Zb:#;7I|2Ek RB͠D>n#++5-5SjJ aQm%Қ>Eb`kG8!-R ʶ@)O;F$^`ĘQӂ!jA_Ylĭ /=PjJTam/`bT5]GJ1ZQ^|藆$9>+9=i7IBH$ҝh,_\˗PXW/-hOA."0etZ]Nde "S /0%r/MV+$jaSpKkb%)K'M8.멗%aR (N"1% ^YxY[ $d A:$3& "E<\p ADHðޔIޗ+C.A3K1˓$y5ْ{Xl" 2ygA"j0JvFכh|S1Xh =Uŋ~bLH䨂NWj^od*yP:q6̘*0,<92B;/Vل)+Ŧ$FFz+mS4X$FlJkcHrD(^E'ҫeIL]&,` da?_|5u;$ck}3e6#D(UX^s;QLpk/|*MI(7Ԏ=P G=Le(՞U@.}{*)^@5Dઔpo31@$Mvj֙Xj]I:F"r2-0&(R%jJJHAԔetdm>wd0.dR !du$>He+IjtHgHD;rΔ>PTdJ*4_0`êm5dVT.ʓ @cRL2jL @9Z U#heä~DQ(͌dR5 `xQ0s ʹRfEG 4 WH% S(%1 _R SIl4ц7czѷ ܝrV͢6#k]%0 N=crT=˽rǟwS]@H^7?`eʅ3#?{$?: hZ)h&sL,3M>9 \>N{V߫W:ikfMqu]' h bKvRvM_帿<}i'dnik6O2`tAvJUjJ;.w>wgZ25 $|i$[;\ݴ(al7x=sd`9TW޹pi}/ il]^dm3ۡϩc=qAIBk kqqQx뮝7'jJ.mIUtzV$:} .h%!Uٲ\}EJ NGI+is3 -Ӟ`Mё`"tsKUIG,2nXE;h)NlI͈E]]pԖj`62 UoG>u-oegH,^`vN]'pC E;cN:${nIՕkJ3 Iu%8bo/z4]t =oMOՙb "*n^YLB"Y׎||ɔ7 b%w]`TJ}eeM 9 |QO*1ϕۺ8'QpPq*#ke{T;%@,&IG)2*|^oa+ԺGo7qVY =ig{ԏIfQq3*IXtOg(kkz6Y/;UJW Q"UCQj}ޥ0a" !aˢCOm%=trngJh_EjQ;q8K7*nx&*u&SZ ߜէ !DxB <68Zlщ~,%ՏkĮ&0š~`#,UcF:MǿƑP :ݑ [e3D8iC潰ͳߒqSql/aܠPr9%wkI'D̙Ry`MNC>CLql'?[+O去/JDB{D>-LLP.^EZ7':Y omBĪ0BžD/ĈhhC_ I0U\f^}`X8hao{EIy/~޵{v7bf hGl}$:-97{/lW~qs?&?Ё^DɔlJSo޻pHn x_@ ̝뽷MqQVu~i_bJZ>=|nnaݘ-&FP+V$ J^T2:yΒqŔԶtqvaA3!%A 1cKX1_^ޟ%|?/ƻ<:) hIx:X( +/lmrAd1H!&,'(f@~:{;_:g7;L( Dq``%,9nJ:w_M/hd;Cjg̣2lxu +CP[Ē㦤I]?ӝ./Db0`l AJjd"xg){CN7FtdJ-g융tBo AIiL٩Trnvs[&TnU.G]{\DYdDl4 m3+.{t*p#ܐz WX?w[#UKoZ&+ ǃ#ݴXjfޥsk?Qy, hu.6Ą3(wn1b؎X=٫ay"VUs\ߨt; t96ąR*AR6kq'n_Wȶ,R4zlϬs Wᖱ/c-bByBRYTv,7f3xQ+Bu481 dRH$ڿ Uj/H٠xX{'1Z|QŗN], Hs`e *EHɊ@e{c--IrC(GѪk:/}TP(S+  2t,"mҹ]lzXh.NJZ]:S/ʴmbʸqu>{t֒. 5#FXŞ*KnzƶI6t\\-ʈ8!•N;үeRpe=bRT~ ~= rQi&(>j- GVhl8|@"̗,M $Cʝ*gRަN{k%V#*9Mķ!"m@duB.](dU6&+t ZsUb,x`R7sHzP^8ǷU&* c 2 ٫kζ3BJoq2[f|OU9:"<d]&Y Iݽ"HdG{;{#Aaf vTy[)z¯Hdy3$1A ee.ʾyc$XB׃e cAd'j\twOC^ޣ12vjsMF⦞X;2{|I +?]]kTLؐ ÑQ9gx"W8ӜnR(vFt ɳ"*XIr֌ː' F|CjV)<)ƩbemN6HsJ yUv#ʱˆȕ k& 2w \ ٩/UBD&)V?׈^kVzJl引֘[}rE|E"LYS2O-luǞ3檟?"2i}$$'o62G)i۫'SnNaڡsb(~i)) t7$ٖEG#@EśyTmKf,Y-!8q@S-"s`c$M/u #H Qh_Q~2A ؼ;g/]  CDi. =uDn3`BԦՍИ AQ3:as a~}9+U{SjwL1%Iviy&Y+*3GV|,*63,J o?)f$~Dnm/,>\-ZQi{*2{hd\f2MH|֖Ȕݿh܇~?0)!6&k0'٬نp{ iX=L#TY(YjƜ>kݴ\!(ЛdH O/]RjUT,-MP+Hs+ Zw%S:'`_8 nDVƜ?&fbO[pD]5qr8h$2~.n&4=[G 9qn][I 1"Wd'f&ׄ$`,oe!]3WUᾃZ} $ GA@ j"էͯt haHXNria7;w;i>8E:7^|gX4$#n+"ZzL^b-׮W^crMYɑ|L dpj[/Xgye1heyΊ4'zlgF)>tV49&fmDS/=غW=2hcxwJQiϹS>mu*T)$k獚Z3w`gT"eS–HR 8x'atH  'K]n rX{zG4ߦ&b(ʜ%YdL<V(RHD7B^H ;3j6Ԁfv7xC.U!Ɣd }>-{qS+gL܍A06.y k'~JHv ;}gGu1);FUa%Zo'ebٹb~$#kJ/_x2Rcy͔/#nk,ٞꅧ/8HZul{|gߠ2a @3'FjiHP}p:7g){6xE>(G g\]ʮ]Y:$)0G5ܖʽLyD~łE^yKS#O>o2㏧غghEf>lQDqG.xܲRҥJϹȴTnCPX5hdh dq nFsO|ŴP"Pj ST.\$]iۏb\#LﺹEm q8¦aO W&,bmR8O9[4׾`U)F8ѸGިq\;.uCvs !Ub4!MsZZ*7" [|"tnK<bcwҎ%ق ʣ|T h:=>aF%A',sbco<Sfoі6lv ZYusN,,ރW1S1r)U֧8w5ePRm񢆳G8zxvsU|)5갶 mBP$%azv3PrK7<΂3{͓iO7O|TMPa1] } N  D3l5_R|ѹ ȫ 3/#^(V DR"\Z,~G7_,1A\V(Bt6Z|Z fc/2C=WϿe vgl95IRnyw~8 O.Vo;ƊX,TmT\.ne`u;'@%ja,1kh_-ji]ik%FcN@*AFAY$4`]qO7EF[n;M(C$U5Ұ4 κԁm1Y#w0_;l&MOmh\!Iϟ ?Jۚx 9\444E[\ޑ*a&΄oC Mه?aol`jQ(~FG+wv_ֱ63 B[۝2{΄oZD&阚,lI tcY07:HD`n%^+3Xg]` s*BC=Hθ9+t#BM@*Y|4s @YiVԦ .t5H#_y|qn*~a@=qyn'0Km;aSpO KbWNM?MxcNj hO$o>861m͉c¼%Lb745}b+H#?8p7?^mS‹yS8+-%GVU vDIER)D (1W\מKųԚ+ʞE0]E5]k-̨`!ofmfG-ޙD|HuikI5 YD,9#&D:S&=w{_g~jq+ikhy'b)yj"v* tʢDS➔8+N!5OTz'qZXDIOF˺XTFa-$R@)3.rRHU}Z[RΌjZ:@+r U%ZTUVP0ps)! MFےuFM !b !J ,ND@;ji3oyA觅{-~d9]<>!_|vZu3o]J2a7qi"Jq}\wш`+0ON޻| ScWU|Mze?7] -8[?w99熗,Z @*T|^A);2=(7! 'b`TjR@av|+OlNZ70)rBiGW?ie?)O,{$>NaLK F/?)Ub# 1Jo;}Wx.T߫BǓ"AJ.\aeuRb2Ȕ4e=YxiᆪX7U>h2U;ԟu8.P .^~S0҇枃^rZ* soYM9/ҝqVr`'=ŏywܻ*IYkT ' B<6^g=sR$* ѮxYr"@W=cMutdAA5+XY>zd%ƪi6u@$uu:^rQC7'v? 9VC1]&;N`"z#xݚhr%͑^ b IȢI+]ٛkV O8Vb9.bwe:ȕ\ery62rHK"#cPcKWw_ =UeZf2Y&Ë]6w LLN&nG"-W轌<{m&r"H]i{IA~nAڼT~c,LF"r`{I`mvz/NpE}IFCe&I?K6sq85,;t$y8R-i^&QabQNV?6ML讁-*!1Ѽ1ynx>rˣݱ:ȯ7p⍞ԒFje, RGN¹>[7D*oxn|G|Y5b5=MHtDԛVИ%F6l5|>?nb sm/uSӶ Sqf(f^)>Xx;;K]2&i?O"fJf A t Aee Kn_Kgt5DT7!ńY [vC;yHi&JYs.At(oe$^W|=hZ Fi?LδވBccX2S#D~, &DPD<Ϫ/H+MLxS: f QHܶ@ PJ|Ј3_ dYM=\Āx=$$DX,ڼcKf׳5mQh)b ڣcǵP6;sƬNlfvw.\\CLlp9գ.83!(n)7DreҸKu"RXVp43eHAJ^w{_Xkh#z;9L t B;@D81ϕK_{w*5]4E,]d_2:r衜956R !@ٖ9z2(Kޱj'j% >v3G?P0ݻ=ݯޔp5mP`S9z4ɫ="pmVrYV̌7pgtAB@UC%s>,g ^d,o{5Ex*'4ݤ$ȫl˛,i=Gq tb#-5wl)+LH#iQT?n\۬Kw=e_Ͷu _P.4!wF$[VYi0Nōm+-;H#IB@cN#HHɐBBA|@&P-{s0fkSi.Cк>զj t\aCARݖp΄Ą.Z\`usޜK(( 4@g `PŸ%u&$$&][MP#Z2nRĒ=& BUuKsgU(MsLJkoO =JgU``8䑻}⏞\{~ wy)lx9ۜHSt$> sfrҤV 7.N碳N|p$#sI_*̦vv\4Y'2k}$T,X_~.3 Hm5mӜ,y<|V=^Wm=4)bGϟճQNX4DHHZ?Ṍ-7l%|g K$JAh\P-HUB7lWl@ݴZYlvi@ݽKRbWo޺!ݴ2UuI}+QUT*ַ $Ul9^6d plȬ/z3;h ZVU5">5*tO " 6{rZ{1%WjUJ0=U <&n^2 < nq) 5QӒDͬkH`rdWx9l& |햵-UsQ {G^XmЭgYD|-#XAS(5L 3Gs&eQPe-$*ү?]6MkU# u0Bj J;4)fc=EB+ 4bfYBm:orqOf@FGj'q_E}bUX8?G=j&9:uXhJoMbO`QNh8'M?]esp56$?{|#J|L"6UA]N'! j{ mHFOXi*e @D(B7L9i2{M :9i6gם,7ޒۗ9oJ3l[.^gx"NmKL|8kEKTKP yo[9KH3=sպg4K:memKzvCk\oAY/K4ۢ]Y'fvt.f6z]U ִOVV8>zg"5]xqЁʼn6$Wd Zp4D.[֜oR[w4 @jp#-첆5ؖ천ֽRƻ_>iuGjڟY{GlŝV8s]1wܷNmՄM_s)5m`83ƹ3R \@/OD -,`_Ƚ"@"OGj7gdf)Xjq Gw!@ 4 sL:d?<`yT$:̆^,{\$>~C;m{Jg~S("B$WQ=e.toA2w&Mm3,l^y_w7衅tYݗzE;LIpڹWyO?~[KN)ЗnvBϯ䅿c;˔cpڡhtlK¥} V'q)ax%gi>v=|8Smw sLǹ/vCّ L̜ٙo{؅_~a]TCla?{Y)bm7߳mÔThz ;bp.[ۅ'r~ܙ*fcw5`aw'3 v#z1_],~ @d$Kz]󷭽?1mӇ_z덓$ ʿu_M5o"M7޳ůo/ݾWrtL2a7`Lod݅/ʼn[N6Pۂkʍk+]w~U'3{bՙ1_f^v&HRPtcזskk~[vƁ{||2#~yz˽|s xrvO;rlOCK]pK'N;().(;bk~ϙ-w$6+(ZTMiJugQP[Yǩ~F;q?ÎWfX8o KaFbtb{ΆeCHڀ_ _i&I">:_= m7ĝ&#izJ#WbУB{348 X2MK˝o2>7b7ZL]8 ek b#тi|G)ig[O73}ݧ ﬣ >d;ZГ;2W_Sْ6y!/,U%,@@J6%[}NSұ/X+Uz9KfhQlݚ9NqRrI׸#w?=:7>r<= #4B=YLI}ݙ? 2HZ7[k2'+/D?:T\KViņ 16|scWڥS7E? u IC `ffGn7DQn";tJï6-"#n 鴝e5)JWFDC譡3zܝ-@54>8fB P,}+Q' S1Ci&(S~Vw6$0|>q!{ቩ[#%AbBnuj( fIvؐ+H_H0H`PpI&o?;JseҾv>i6G4Jf7Hcuol: HXMggEsjؽmϲ zٲ74Mۿ[wNaJ:}&wE{'7k; uGyNDF¡^ǟnxg Su=ܼl l<ڥT4V7V :`4-M-׻о;PhFF:@gœEm4䜞|y_j䞺sqss2\y [|MhIk[m BP&Cہ1ceXfraIQݿ:`zCpmIwxuj`H9} o6d Q O d&,)m'ɻԦIeTNmio]  (s_O{[|Y7n8~B3GG<{/{iKO]S&̮BK>aל1GOï|8]dᏮW+H+}?kkc]Xc_6'l;P+P?5C9YՏ>Vɟz+?UdFL-/zb0|5+bߣϝڊmJ`'}]ýlMG7Y3Er]G=c/5~֟;S^;:,`TFK7y y?w_뗬\Œt^}?[n~wv^}mX]/Oo^bN:C7J? oȞΧrS5V[~+w?p3ef 7m҂%f?{o˫kX4HM@vq/ʣns _tv_{þJ~;jǎYK2|=MXү^<)~23_N:sꝫf&y7nBUzq{XA:˷hqrAv6#._}&,w_lwS2\Q{c0i`՗m&|Jte/3UEݓӞ$ZH8A @"ol4V;fݹr뎭X[ J=bBETh=GI%@ypѮL(+g+8+um& "gV"61߼>;I˜SX Z3U )tKmMF6i]\^e+M/=>K+VqIdWuʪ .V2ͱ#=)N71UD2 m,Ίd7/jwֿHF%grDe^@-LؑZk m.i#敞ɷ=+W? 湺=|OY 8,&L&M|wFȨ9>ءܵ;4+OA^c4~i*zDŽޏԠv&|]ѥ!jI罣I+t}eѓjҭ2)ϸo]vQj=[b!)TNgGeoQm1_?jMƂOS N i[郮s [Jh}kV|^gґ&7( jsZ噳z {1\ l<5R>o',ihUa7%1P$a=Oo`}w$meFw<*/E=pCI$I&PDfH/)2핍FO\Laęqxы. uX/ghY#zcs2Ik+g7f} P/guM:rEsHb=^<+kEgΘz ǗWpI+lRO?{7]~6.sPez>ܷ=-l-$n9a}ǒJs<y ߻kz8aioq"|?5~ zC_p?' b]Np1R^*{ Ń+Vܼ&^u^1ڣ@k |v龳P"d!M\M~꩎N~5\b_Wn|seH ^{ˏo  L+ Citw* !R'gz/ 9}0q‘tҽu/a]*np?uPhS4AdwKz .?[`ߴ~2P /|]PP~פ7@t꛿;'pQN׾#}uhmi$yqK6(dɷ-rJzqiͪb_ n%_5nG{H}}]AOVN{oτ?{? 7u۟bO֔g՗L8tҽƾ2Zwn-@1+uVU}HlfE=k^ϾodB su8@ɶi쉿o*7- M߾T k[,`rwϚW>7~3 /y/>4ydV= 4Azݔ?㸧N>}q_B]XgHV fnqɉ.& ><&7[߿\g, 1Z7]1;}ǵvbkzF}ULu3%Θ^{8N͗߹0?=I[G@THRkw}IE9R|$83%Li7 N[q|ߓ2]图dv~:o1)5v)l]YݾguHlUXX׈/E.Dv/-C|ݓ* ҁUI:,IHcE9+(]Wer3#'=H>󕘲~ѭ<%okrP{gѐaB3&'\Y)$*qLfFϼ9N5?n4-;nҷցaLws+7Q{ac"iسz__}\fa\8nPqKpm<=ײ; qWe:ʒG xv/RGḿ!eAg0vAY^=sGN'{cʾ$-:I;!Uw$N8`Ĕ4TY=;yn>2#wΣ!w:QvqH!ypki^&˾$oI x)yCECį:cښ.q`o_2iff,hVg~ ߽ =S]<|E؀JyJ9~Q[!~:Jp/b8{'ϜyӽMHechvǾ.o}v7 "qn Pp @p~x;{I>1&$nMCŗ44p[ZWpq&@p~̙wV͗ѓf7N6[G͟{p8"ٙQUwO^cfo[iZs5ɹw*U}sn^u2Nwo|coOV,~k?11mqH*kc߷SnX9e$48Z75ff'FӚM 0%n8F2khld&9,b#LȜk??s*W:YRp*@Iɾ'̢?mMt`wTǹrlaK?Һ̝A_z0wqI;}P"?G0Ȍ Ą"c o0T_z\لւݭ!̖, QuȹX^Ձ@Y;4O~/HIaɡUɆG5 xI9*?~'|~:)oXX]ݔʙi ɓEڜ7k~3uǂ##b>r>=/=XtmU,NHJ{nK8fFSTBČXB= šJ] cMn;GS-ys/xv_}{8;OڊQHkyZ*U wذxZ47>AZp>8Oε:>iӷRJ}}Jz oLPo\dDeG}QݰIh>ۯqM7Iz.tù?4.q]X3{y (-]X(Ye\3{/x܍R#z_.x__gd{e]}5%;6~"eܛ;:}Mw|çɣ9|}@:ɪU]ɔiٮw5c&5RdI[]۪4970|o|r²RRPp_S)m}P楡샢 0hi&WR=Y7ن+'9jg [kX_+#ԤAA GT܇s 3#|p?5c3k>.%EW|6Y&U  3wpr {F\진 5C3Oko^zoYtY|:3.}sfuI8gGɹi) +c[?Ҧ6.u|p({ ]0\4[|:t>H/^YJdF0w|ƸGMv֍˿ob峱/ƍBD9ܾ?O_ɔ|uazeԌۻ6=Fem7/|h:nL:G瞬]gL6uH֮q&;tr6_xW|7&Eן{6ş4%vgv!=w[7*~')_5םI"Uhlv9Mb@B\d%2ae%{!qa$ 8Zb &!B0K@{ zF/y<>^ }5~Wl+Mc3ezX*dKxޑBrSIJٛG@1; BTؔVϬZ/v\^41%VR蹗6[֫^"O wzEwVYOWZ嶊^X6?H>ǵ23ámS7)0uwzHsYo~O3|, (d7=|e=/d['3G^H6/WӍr9dUR۞wF{m"Ȥ#dIZ@}u#PO*n8sO,#VOlN0& uHh /uw^i;V/,|<fs(qհ֕'uYoH¼h2.7`/Ff64iߡD3L\Y5M#%Z˳)$[=Fn઀t8 ;%rmfvlv_c$w[/;30\[#!^rgly_W'bfҌ b}PN-8 q$ 5oVyy3MЅrS޲b )٘ueJBO{8_T|IWɶS:c[7ܹSIư#َz]'Q1r[Il̹2g<[ֺ{@U1I(*᤬ Wj֓ͮ77?-Lse^}ߪ{%xRKfLq#yO;n$+ / {־iH\"i ޕMvvF/%lϹ 6ęI&*7S]m@m5ov=a)J:}P|+B-Amuhv=3C5x@g{JVqq'*A,|,=.{}O-9<53W{c4֋xdolrg7q!)GfV^MGq06!th7 g7qW'לw/| 3Rkfhz6a7(G+$ )a,7igد:.wo=y|i]{HGntgN66xOIS9wH5}dͿ]VI)U 7hI"F^T@9 ØΣpB/Ex=6 $4@m=sk[ך=@WRQ+tpS(BɳvEtVuc|HBaƒ\*)H@`+P _&hEt sn^WD Zi6bRoIJ&HD1_ib[Q#UTT,T8ny3Q]#pɳV?s߭8M~Q%RrUIY@`*>_W/芯iæ\qֹW{J?{/iFM%u w!HGƋNJmpJrߤ.vS.>ϸ:!ґ~Jh=㣛LE|DQ+WDUi&kՀ>^mC}WU yϯ`%Hy+& |#;T·x59]Y $F7c\PJ%W]9R,jp.k蘆 j8ؚLTH,\[T ^4vZ59RǞqw ϗ 7l`cߛ Ugba\~+eʦHE^I9=̂De 0֙-1_P l7%k[. }[kj$ZrPbR /)*3H78  l={( 3 p<3 yȗy6ȳW-*SNI*/(xuB2s5igmM(_y.6Ot{"2)V^I1EG|"F6JqeʥHüS?ZM׵{ֽ1KscIO=$Сur ޱ3 *04{O~QewiܢIyaS(J{XȽ]^ Z-xfgdN_?~/[-ou-jƼ[ݾcQvIx|fg`U|f6iʏ<}_&g6hzvOAYFIBNX J> &iɥGM6'Bg W4Oqq#+gY"ARnXnnoD*%_?{pQUjL*غZ[$e:4'#aj]x` ӰS 8D-պ@oQ= p$i@pE 8+U9 v@8`upt<%#Q zYXpXvwJ@{|r/H!6=.Hoس(-%eA>4ي_?[\غ!`+ʢ@i*$wf<Lv7Mr&N},9${͚gE_&HZS AO5ղ3i3S]eB@:oM!Y,-J!%[K!}ͩÊUHtc. J{nI!S?}nrŗ. }Bmގ*s^Jym}\qֿam"D5jp31w4b5xYc[m@?XK3/gzLnxEù?Cv_u&XE[_+6p?eg"m{cy3!dj+6:.-X/# (>g䀺{"ёC 4YͻGOrCf|d3!dƙ3 c&=Zт&e[9 *zѦ[P^~I.3\|0(B j e=dtͤ]\EgR= <(=P@oQ-X0dPjw6x5' Ʌ0Y23B )ȀW's4 D02e*pɰ; i<@ҐGLA1('^NBZi܆CE_rNXJ"LWvD,wrMT2ȲA|Yzl-' l3)9(%YT^b)6Rk<[UBʢJӤmw_)הT'e""#|hvk ,Hg61݌lw4̆D9)\?-Je U9PMF$NyV@U Am-*ikĜ@|+Ê.0(P|tM*1t77Oص=;xҴ*vOLY )e|δ@iZIDUMƎJjʣp6t3 ez37TxKX8T g܃2DMZzBZ`[6fWNc|*JpJ7)~D^Rz;2b$,AJE PĤ+n^H'rS$JGEa.)DZn%r*tqA Cҽ$sۮ9$2RsZy*3il^ەŊkaYlǜJ{$ [k?ϓJ*3i,u/a}]Y7.Jɶ+A"[XlŸyyey*3icWG[NFUHv%m|03iDcqDigsUKe-A81N, ӻMs.rOw>kx1$I@B@@s9sΗ.Cqjo븤aϚ:UQR$ hJ$ Qh <'_rcxdV:!Р($* S1pЌle$FB]wY \7 J` ^$^O]"|j&a3R/x}SK%9sb;ԑDVЃ֥`E$`Ȱ6VgE13StCBI\N9 MSNؽ%ֲ]8(4vx'7M*9[dIRAZL_61VCgE;33jsAFz5 ublox )W%$%d!ʍ >d7?3v eQ% 2۽'C?pL m}RC\f4jSykZP#u/dAKyo]MYV vG*Sw`,.XHZZQ ߮ D- S ^RPW:;ݖG1N#a Z[>N.G4aV9H:e! #ٔnB蔅4N! CgS}pfQ,F MI*0>Lsѓm 0"N,VeCJ BG (owK*IG"֟gnť&"kGDr+I',|L4Y$AКyi-Z˨fhM2~\K$NAtWG_{…G̃jjP$Ҝzɺxv1oƑ\A=:ͪ2\ @@h "|rw텣zGRþՓOu'/̳u0HL^A?~]F\i;0oϔ 1L\g= p=j^Amtڝ};3bZ/8&lѐc\|*U@f:Phn RPD7U]po6gGO׵v@hYL$K$C"POA<ÝgI_=+1qx1RT0GPAkW椳_@^ɍxj,Pebae#LbsҸklWV+`(D랗 e8חIʄb#Yg:+Sپ~E0v![ː,@^S\rwTlNZZBj8࠵y= ?E$p MPvΝ0 B "^crЎY nDLAm e[%QWilj$׍|vĤZMhb. U;b3kSnf cIRr~v; ?WaԿZ9S^SҗַKQ4v곆6NFFr&3k,fd+> W u(ȭNZt6MM84'bN'/ u<،-hf I,^ƻaS_9I݌i]Cp:87h4,vpĔ{UjE=A2H!Uk^2'm0Й?U[ȾfG|1ɒDg3Șr.qÀA1Ԅi O4 |]Z7'.rg;9Dʜ zy ?wqEB|y!Q>Sy$h$.R:d4#4x B&~6 .u!YC| M%-{KsT3%e&)> KޓbE؁10A(*ZHJQ ;22)=jĀcvڈrz@BSI&IAR^$HPth#$I 3)sGz Qe˩U-֕"قE9G&!m&<7vmȒvס[̃"T rDX" /e)6N]cӛ; Fd8 Zъ3{)v~{ ([P1I>Q1DbU`_'nJkVfGlˀ! H!}yؚU fh}Y{ فxP2z1u"{KcV[بav9U!J8l\fԶ"3Rt@<kPˉ%/y - Ē3ʥl[Q:"M!GIEsD8Bx!\ D/=݀)[W= *O"QN>;}9SǦ,~;q:1%A:5 OYӛjO?oM+@Nf9O|A@`ԭugE/ IS IH5D^JRH;L\l3gMtWVɆN`ulj I)QL )vwv +E IJPu^gH:[3n-R;4N+d9f( 0!JHhOotLR6f6C ?h3AF^NrV僧9[Y\6nE*OkLE w{Gtn;mp0ߣjB`F!LM!/㗯YL萑/F1fU~hD3EO'Jԉ83\/N:/wOT T3ӎ]iG LD)!4Ec5~kfɗ?oCub\]n"YLƁk V oIkL(M<ጮ1!ql{&a * lJQ@"4eC=ԁt¯!$YsPdlNdn3u޳2 0%|SG (wB9~n[m7jPEUgr Je*) AU [^28k9S - ZoJ.3 g* :8HV( 1$g#(bh"LmԆ:B~a;IIuMzAQ DqI)$,TrϪkj];mG('jMA!߮pWȝ1w yr8! !_/I Iṙ]v*$UN:$L# H< /uAgA$I2#}+QBW DXhZӳ͕_}򹬸Te|"L% ~x]2H0F}ː &:? Dn Oz CӅ`bX 4q!B;E%xA@^N,7%rVr"u9v~~}+qN!v IƑ#0 b9ps7#Ivw'gyI+L"L!G#Fӱg?Oi$<7V[VM'b' 7G8T6%i?30hy"`Yg8pĔCrYsPh#$H?Z9GLIL >oփקa/FnU¢~,̀^Ǩ2ރ"6p/({4)zċann˃8%$^ BC8)Fkޫ>akeb'C!+T;7"ɻzS){>A] pJF@?|^|ǦJ_9.jX1h&O AN|#hm,_7C*1*1b~bD 4KA3o$|"V˞$/A2a~՘/h6%ˮ44ISix*XOb(SDe0AJsiWme d܃JV$`%u.$#Rk<&WќP(W2rhWtK7nmę wY=Q j7 yʾ"dЉ gڥIvt1Q絧sA^ 5uĭ]H&d$*McK#Fm`W;RP^ՀXne:7-R )*뙍mIϥqJT6"hqj°̀Rt虎QuܜHV@;7Tlz 2Ikv7yдT(D mlu-_%wӤ5s.Z &Yc=1փnىXkY_ *1<.۬,cJ:zc , {OIP 0MDkn)If\.T~ (Sg4jȔdx &>|T^SF]uzk,fa dJ!M&}7H c3C% aGd=m,&RT)0FR!(# f?Dc&*;SZp NһHjH^]f+o]B7FΖk99%jeEVsT^ N] g-nc5lb巔j).ǥB6/jɬD㾏=IB F0(>X~QٜjN^Pםٛ] NmbmJ$ad` 8򼫌( ,VVX`fsjVshݙ=Bx"Vf %UC D lNNXZ$L\6kA|` ^)cJ̾$@ $JX,VY Љ^3pR j-EfIVOj*1oHё D*I!jVlN5x냚OBYRԋ0!#%O H̛? !efےDʑ$)"αe;1XMI I $FS 1f$b )HS Rst?3MII<-Mvƀ! I7@vgKAb<`J@X/J0Ҋ -ntvWd 7fFp˜ c@XR #wZH9)mG.S8ݳ4dGsi#xzIuk/^SL 0F {'pj+3HIYM+ؒ|C;S*``~H XQAiҌq!VqT|Y u$`S{EswXdFݻ6@ |bc KqeVfܣ5< c,ʔβVf`xŒzl'QAxzGj9 S2Q#;J Ri: Yx+'m3H~Z#R Ryj;2# :M#g b~׃q̰5ɤ釟Ak3LAo\dF啝&bh_e1z1+ib:i{6Ps-h39JVEfNem5}/+qP7ooq8ʈUCV3{Β\UM,:*-(mȈtR_cj|vXE*,Mrڐ$Ո$<qdAE1tR\Z!OW6C"FZ)Ro:@T^0x,\Bo$<50ZuȓZQrZf(S}fVYVYP]gvdvn0ߔdh45MheVZUuFE &*J)It&̝;d?S!># G'4L)P`kƫ,1kFzUX`+/pt 4*4B:%.w^^۠S4U>I|kd3t8<;J|DIfIRB3 q%N(Hse-LL$\2&lFB/ $ɑE+V zR%zme;e |aEv4EV" \6j%2vpBZxK 9104ٖ"#~` E'p zQ"&Q"0ِ~^$PQ]loLUP9'h\VRhl9H8Zt( &i8(@b e\}h񍓦$.$,^g4B'a*F'ZUDˎ:Kl}R6+6?s* %o$ J<9Q T~$εj}K7:LH ,tUN+1>No%Y ~p$hhQb@5OQThJj$,c!*"THl~r*Pkb3GM:6$l:ie:$2JeIG,m5Y4(O7k^RʑՒR.o2Z*ϓNd ?kNzs4$yܥy+Կ2kV&?ɲ:By>Zߒ)&:ôR=8m1s"Wir&c*:pIyTƢNavg\C1H2&=n*=`y)CtOH1!HҮ2O:lr@ŚXrxId/mߟusA: ҿwhi9m p F݈+gn iY,^0Yms mSͩ$S;*'BA~/.2|˙&YqYqouc7TKDhqAVR_Ұ *[dEpԌ%QR@FP#1a ƥ(Pj>nsGkXZr $SD `Yւ<"jsV'윅sx&mm"dl9";&I!db Q!H0\,)˘/OMIIii'Ex9Xp5Z&OUȚ$vo#A7X2E\ PiF+aB@zчJSe Ҝ 1SǁD" 5qlѥk1IcOQCt,v_}enW[Xe-h[]i9ljB2){ UMZ;zţXm:p_NRGq[^D7ZwlDmnTny]ZP6&qM_:Q\]5pa;t1-jhM1JZY4TGv7ªV43  bqԍޱTV>r$&8ƤtYSUG^e䌝[ LЌ%A4D $\n+fUvbJ`Hm5硌UFhN&D2E rw݃r=g`pvW{!Ǒ6EQnp,3οl[$G{^zR\e@0 a<]">:G><7!m{Sی3ybV1:3igN {I K_LR]zk"v¬cK4 `Li#M{. W׌uOeZ.A{ӎJNܒ6;r,wp@Ǿf$ jajHGN kZ,Bœ%4Xki!mv 52 *Yl3˝0ZBSo{N^fn=bbv缺&,B;I0Y,Fvsf,ppjeЄv2O ê%;V65m9]nڐ dDt0oӕ) XQ6.vna` V l2Fu@97.*+FJdui$S1LNN~9m3:߆Ef[\0ޢGJaJ40E-(+F_`:W=U:+lXM2vLo@ "o[Q"F݋91mqMP):r^3O0DrH:H:ha6L!pN:oc0,1&P>wn۪0 M"ARQ`Nyl3xʺ^٧i.N[-Q$A?"=a 9Bz] J"A>I8 &P 9نTsl 1#G!IGǴ0 GB"ZfZD)f6X $ 5/=y5uH j"AkF^fٗ;Hmɺl`9@7|WntXwwLV=62}zB&Yޮ=v!q =}ǻbt ;R]TImuF~QEr 2n٤c&[vφ+mHA$!}e\e@,C9]SFM 'pUJJ ;RU'۬f$>h(e!0khL5K X(%&B9R4#"J glȆiم}FenIy! ΘL3RqQ4KT|ӦaiLlxKz(ӌέnoMp|U9*e9pLV`2hNW6FJ4O(FdoT֟v˵%Ce^yTyv7;'-ژw5ASu>ۿ@W]0QSGFNjY ЪD^4Sƚmϱwteڝ zD#W>ژ B 2T!nӑ#ƜN̏XpR?J/j;~^#WC} ÂAcS#j } }U/*drReoZ- c!_ h~ƿ'Eds\}6{IlK/v _l.Ohh8>^#1|ù,|w)h>cU.Ze#L:󧩹*ٮ$lǞST~lkU/C׉?W|?-y]\)/]7Q!'6FT FM=%;,+ĀLœZXBu)E yQ t, ԧp Ũz`X:jF*@ l$+HO bH $M($N9 +0!S=LbrQX ˖<䔙Mӛr1C5NR) I)/4Z[>~~V$I .%[ 25J{v!FVIS$0)y7yx1zb>SeNf,нε.Nsp7"pjL dL|u?ۑ,/l.vtvnnŒ;OnҹLWԻ% +7n;;(9U7\s<53?%DACfo=N jjl1lm`CH_updʀa})ru6toWFT'|hTq*&sDFpqQeFvɴ=K }bpBzJ"<?]$G[c _~_c1Κ.f>LvмgNLY9˶MkoFRfq6 ꉇj|z R 鼪3Woj+9Um~WeW8~S6 faRk[jM$rJS٫2Dy:0Ru`(0NɸPh 쨥wtmF+d<+LZd [}7ƚf (Ѻ@ȢL&Jo vrL#El.q®%JDh1}jAZ-dFh*e΀٩*qL#槑 Gr1n'#CĨ ΄eC\8U43(a!ᆺt{Z{stFe!r/"^x $Iڹu*( #nkl !3gKKgS3y΂LK3yAW^- bV:ƄΟ5ҍy/W]_fiӒ!scD23LoBtsj٘ƝfIso_BȘ4^.=Za'YYմd0ai㲍nq_ZQʞ-nOVv,[n):2X\BiFZ,g,Λhs)T=(Q0&5zAݳqYq yYԻ56Y9)jHiG9DRinJ"TeER(w*Vf8R;L~=Pٖ^{~ib퉭{v2{-5!Xh2j_Xdf5}>3)S;+ޛЬlG__B<KA*بWx{~(aGLe^uA4rGdUdjbdպǶ¨7g&A +ĨLv>iR5LXmm:lQCe4`A 襭& ; + MH֖KΉYﻵ6grk EGecԤ#E>'U^H$J|F&OD(HGHͯ|Mcvx eBٌ蛵C޷'r"AbSH,(S :65$*!IvYȈUm QHR$Dq^ mrq7اP"BgN< ٷj&rNO#Y*1o9ob#?cՍ I@JHz aBJH9`&j͚8@Tt\6m~#GW2QK^ Z/El"CR41]AIY&f5)cs *+y1=,N?S6.,Qp*cцe@&2Q4K%-Ȏ TzS|-L"^Hw`kNDJ|ZoJRkŸXVN"!ٴīPTkbU<(Y]oJ#>J@\Ă@ g)LDKr`^oJVP9\.jP [+ {QѦMlB0Q+c}FA>'Y+gf+TPK:H!le1V7C9OODЌgF1UMl @սI{alY'ĢqЗTe%5WUv_L93dzٖ18:m4HFymꕥňAFA+GK|EP3 kv#:IMٲNEue Vj; \өb"qpTfaIՠAh&щ=I?[I>(-/mUU7(IQ0ޓ$f'TD0i?_?Zޓ4Y1ϼAVJBf֌@1[υ הЭ-4 fiCwxQ@b,LH)X#<ɩ {{9JQS }r4iJI?|؜&4z@XCX!L(Qqof|Mw2g+{r0SaEթ);A >;gC[|v[TOR(k zٔl(v_ۚ4˶GzMa]32~)OԒſf9zLx!ljу)cl#`0K 4Zvn5#5r$i%=:t61K~* 4Nͧ)J6(]y&zԸqim-U%Mx[AF$  Hf2ʰ*>j"@6`#  lV04mbW -GCФ JM6 T2,1߉i׽={ƥ|ن[["Sfj^v3:єAྲྀ;6|Wt AWQmpH#ąIoݫyӖ_u]^m!@!) 4nlIU=# Q~`Nݮ %L-릤5%_2D#q^/9ұLdzC,Fİ$ \i  &`U C%F#$CJR 8ZՏuQ+bDxp&bCD ';6ֳ|ħ1+ 7~m 0mZ`mThKm5"b7 ˔4ؗSyW]M*h]̉&Aa΀:i$$u r7? _v4̙G{.HZQ3ux=<:Y u*|η˿LIՔTRGgȈO6@b5Idn^B˔TN湻;5郯:,m&}79 B]7Ũˊ,M!<•A,6SH"Iq9;HD^3k=ݜBJc]Y0L.UḒy-gIt4H4ٴrH'ULA$5̈́Ԗ!QG=mi;rx3QѰ OEYd S4*8оd #či$ 8 S1l!ESTF#A\U#>IY|3!eV4]wD(p͒dTQE0dˊ`2 EJ%"a8p率5M7w{*ueI^H^@X6rXa 4:4rft3!!+Ѳ%V&Qf44h ^.fD рUUQ77EK2hEIbD&J~Gfu Gfzmθ!ƥ$h J)Ԑ9D޲^@)Xa&-b`lһ]QTJ:& lӔ$Rgr'Z_eۭ; ڜ0R㠳LD8V7YD#$_&?]$mR!;#RmqJ"Yݔ)q3 _gD[)*zu%fӾUӘT掵dQ,mN*ŘWhB/1e/ 5u{ţZ\u:ڜbX Uv5˔0zڀdJA;Y8VWFydgnX$cNB*"b u趃6b|wi>tf[0[kNh)ь𱻫k萪9LI)enT Nm]&`3~Kf;m Cj13z`̽~;{jȜ.(Aҥ+U[4yX|A: +Q̰KuB5@Z0b'rLNr&nđIRfmMµpo&cWq%  ǂ(5UYbZQ QE'DWB99:v#okpڌ_/Ս0%Md6H q <EW^eK׫w{b_v;哸 ØHT4X$7ˢ%FN`8^q -THl1n&Ż3]t߱lV14,[3#)mJEm 2D3J&XX]TYͤmT3رo[P{}u4l,7y?Pt, gΙ1Zauqy/ iz]PPV-[O kYmV*Lf$r|b!`]ᦿ\S<kٻ uݎYɉK3d ۴nm}ۆcr?UMIK30_;6Ӊu|5Ǘfզֈ}ά}:Χ>?q֔du<2xى#˥"lyre7OOΚr umFȟ[y>#CpSJV0>T{uOۖ$*4ꕮ i3##9Ŵ<1 5&)O#C%ſ”L ǩ! 9'u7 rx vl=nnsrv½םf/9|6}.5ce^yܷjluĚ8ԵZ0ʵR~ r9+̯=Ig~֙ gSaCؚtu֕!{`Z$.7N?^DSYUwe|}tҍ+ѺM*7I0q{Tv3SWQ=q9n+LηnrB=a}(Jz#8`PUVOИLʸ[o^<zyts<8[B&jg TDq;$)S^U4Hٳb;X'Ƹ~|ؚR:+MMZkS, s76 }[RrՑFԬaLOڸ{G 4O>s~NSu?ԌlmjjgfZwHb~-5QWYaلZ#ȊN$)Et3y4R ĄdT@j8B-Cլ䦦eBZ=ۺ4)kyaLufc\;Ucؼ_.[ednzq*G;4JVcz;ZqS:uyD+ [s:꿍ѳ][|˖O I5 !Rլ=!q2 -YoCz1SuD*mp}>gϤy Rifݜgky`n#Xh\hA21] 4W{ߵǿld'$p[,&s[UC#-)I1Lϒn+` 0vZ/~f.eSC}b-ԋ;3ÇG[[2ba1Xz9\I0 EM1TƟwW bBfG*}0s %֏ԜJDifI Mz\h7i3Zrl}W'if=RQkVK|.x4zV%ˌW ƈXS^aOk5LU}9WܕVSԠlq4ϛǟ-MsZVZ(4|=ے~!7_2'-?2`i&U,42BjXLPX$6̤Qi&ޓ'S%ڕg_(1ɧjJRtQ `wkGe$Ϙ{ɟKΐӌ㔃'oI[n9q1naD7gz@,O%Ϊv28jh5aNPé#[:~8{Rĵ^(:4b6C@'Jf _w3׶kpd,99B !ި9M}&C6)G+ zce@Iյr)o\󥏮9՜':~^/~Zf}t2:!pӬ]1t1Ŗp,ߴA&Hwen5AR9>VsS|hḇ5! ^Z؝eiї{Tґ[/Iܢv |s;!aţ2gM WFE֎:|}vq*}…ͯOC:]jVzzN26xL8 *Zc7>ljPOL;-ZV@ kM#т**UϚ+PUǼXL#Rd%KʋYQBDc4ps)C RbY _U;0d!B D2mhAȄgF,9 Y%CZIv&L1Qr)n om?3Y#FF* Ȍ ʄ?g'^iDSvC/w4̅A| &) x DLig;<>&uqs L$vhU ͹VU#3ZDv:85%[- >ԌgЙaaj8c"ŜYF٥]]PH C 2c/SNg*B,?̚_䢫n ӌ3L%*،22kzFgYftDVo*QaܥH+xOY7e+ԦWCE\Oq2 l9]~k,T=0 wgn=^) Z/[ؙם,<酇. QPד@NFfnoDR{ MǷ. &2d$5_ܽ<3uaPpPN (fmY"y0[}| ZXIPAK;#PPUv(pF6ߟڿ-K$U7.ۋy=ԚkyEf𳷚{}翞GţBCUb(AN[}vax]C {8@(?3^?쥿wʣIup772WKd XړGz?)>Sf6Zdb9ԍJјE22VHsN3/cY!1ݜHi0I{eSs:& mL9ʘE2W[|sl{[УqtRTQ}boQת’S 㱤O#kB:Uyn\6bx\~?bDq'K]t'`dFY3)G"UͰaܱunXe2CU$'u$4h5160\bHxȉ<)u9AXs x%pVbzSI2>TpJelY0B(-#\=խ $8K)Ұ3cJWY -P3{r#i\TLRymXj+'p8>T1h*6m'į|{}GٶV/`$2i͎WO=ٓ?@aێ(q`bI ps+֛tlc 퀯8/5ʺ`F0Dι5㕯e񦣹3)ZEv(3v/< O.>^|Z8S.4Eƌdp38.q*Bx&7^Dp̭c$Xł8+߻]Qt=[6կ wS38|,,cV_ѷ*54w6{BrO1qE/J$YY5n^nqGnpx><Mog6rƾ^&-XfUN{i8mJ(c+/J$^IU ?e0h#c!5Mkp3wxmN|i8SJ(c&qI,+c2GSsPj\wW\eUNxp<*ݟ-Dh,W_DRaj+$- wLI4! IZ*Bu&?Τ''8TA JߜG-D:6cZh~/n,7u׬ʉ5U zaM8,v♠ծY{vx~$StGdB '~;➏]L"=*k;K"7aS)DQCGh{O̻?g yc+X2ivfASQJ;m^OCQ%o2Nݯ[yغҨQ N\ @ Y/;^3%#iEf%մbM-ᴺr/>IFe oVIOr[s g"==K:o"hշsН%A&ђ[zOr):O-ri9Eb7 !Q>9O '?UI?ힹQtXi >+ ݙcjE$G?}Κ9>Fܓ2 Pv 6P0qu>x총`Шy d|?CڜvFZ̊\14&kOO{DϹc{p6 *)xN" _鿝xTvA:iPluqQRL IsZBENfjyO}]2X}#ZpB}td^!e;_c`}D2څWt6֘趢"G8^1v58vڢAM/>Rv\^1s侎.N"+}|BTC_ф{dٙq{g(4Gp58U(.to10}_G);q^xB4$SMtK iw7ڸE ^Uј}Du{'0bs)nTfB"!Ќ[ 䲢pP}G翂oČѥ[qmF}_\)@Ğy4 }5߳adGlduz4{g:\|!,9~ԠxWE=۷u0Cy2/}M!bs%eS~ڽ}??y9[G_7_'V(˸bZC}~>zb-rk{ncZgdzTZRf4fG3]{Bnw wS@ -Y6nm?;O=ܱI 2{<]r }0G>h-.Ywlk;{^K k|ZYe33.XhKv_4#ZyTy~[ݺ_x[Ѷ3ۃ9:;dUE<?ęݷhU>r¼e!i2bޔ5+'+N&{4>sIp=E!chێ#B&Mg?]gl Γ̃=脥aPtLm =@"V5Emzq̉!iFh*Mݱ,Mj u<I㨿εJYnq+~fl;l Xw;7Z6`eI&ʰQm*ۺƶn eJpoʁȥLimǡIJhU|\'rcÖ0т5ӟL}}eE}]P[L~N hU7Ԑ="k}b[Xk&$e}Qn|d;"gkG"l]]oSl] HѪ-D섻3FWTכ4BP8h`0+-}ՎD@{.` ;ƑSn|&(3؊@R3.ʪ=clFFSU0ş>iWЧ[HXر^Y:c!l l@RȞNz8EA$e9 -)!yo`Q, a_?P,w5X`_p߻^@ZjI~.GqF4  ҈sttIj)iR&/5Os-#U9;ZY}Dzs_O 掗eϮ,. jCr21D.U{I=m `+2#Ƿd`Hv;c9/g 2ZgWvWI$wNˑQZX;ZC L5hI]}ٰ,?R j,%?Ҵ >'4t4h e_bqw"ve/z>l0,>~veq]PfANz8J.{I2f"ԬX6)@=0aVP }2ڨR溙7Ҷ;yыJA.|yFXez{iAδ ,jX1TӬb+ǒΘ|4d+3WuCG*?W(C Nˉa0 VJ]fD"'>7OY'+gQBs <4cڤsdn|4zMin oI佋M+:@'eyp^k˖vhAmm]yh󲣾NǺ@cf3 zm=.yuo]yp2cҗHڄsO^MJd6o. l=Z6Wٰ-06η](;.;r gqCsdqp{^}7SAzk?;=aC^ZL6F8W(ꤹ. ;fh=Q:)D "*!S#C Jrpr++ڟ0PP)RH32H5jхZ}I}!C߲$IK{L̅ PS(/$H;guג):G2|B$8;DOc~ۢFSpvOsS?\xu)_= O 1*t-hm;3Ϩ$2ͥiK;twgmG>t-?cq`$i,|BA$`젞r詟_2B:*dvM~;c5N3]%t1I0J|\БQ>?Yn+aͰ~oO>~6>oҽSM5v__9(' ³]Ϋr{MHxts[ZÙBz)G\~$7:\cU_J\?+.=-O>I=a"/޿=@Қ/ _Sn~k˒jJ: w?u-(oFYj m{pw}$_H2:y]f+S.Al>@{^- Do{6mb4Q λ+JTuf&^RHrDtXo r-t.DE7܃B@Dd<) i jZgBYu0lΩ:} RuZɱbwʑn>U]iR6p3jl@k]"v R%cnͰ^b)UsvnPd:*]?lXHcֽKINkU.N hM~HAdiBcP2,W[,mU_Ku{^~ɥ㤩6ݓKSOdZg׭e.ֲ0cW[lz|ۄf)vj4fKWN%ւyvcox8VMvmS (LBve:(aP_)VRJۣ+%ȱX5 ֯HT[odn\mgƈP39ʠtƾh:Ҵq~7. յ>WrFjsWB20q<LDF mz&Ը7}sqg3Bsלn.Nt䍫psq^cW'Ɲw?{]&&#|Lx usc{̿xFJX]iN^IASri3}/.e#IG,Hi%_}1le{=Ί^u,e#Ig㡄+mеIZW8ʎDLhoe#Ic_D[P:eaSpg_}ӵ汛uf$b3h*^\H|Z}2hnsϮ=>\I٣9K$6~HxoF꧿_ձ#q<{,nyx;c)@lVBTJY^߼B'_Wj VpkԮ`Kz vXi I!w@ XIDj$b6X]ӂ.Z p̔TQLŊ5,^|(O9VF^5Ӄp!1i ~B"ZftWCaҬE QE"96w }n&1@A> 7\= !ra#^'L0ed_LDN4nk@I8f4$Ipxnj+P7H+H j9e33:puHRȣ@o3^YT5bY/yyP\k!mӮN+V+ KOkjH+>=zxNnTVme߳x=_prDȁSu%7%az2]kcۋ$ܠ$-~Z)w=51$Y`@[waogu%_4%ڈǥmJ-__Msyz0o{V[/?čBSkV+8'֖hcA~~)i=qn:=nIe߿|y-Wq;i1Wʝ0ژW{Wwn{u&#H-{j|زo7kY8G|-)(-V>zDn NdWҿA9xw8ko:[T$w^{>y]ɗT"OzDJw rfijѣ cgb+:K_+_kZcj]DU&>M).c<߃U;0EXlƛ 2N헀 [("6֮lJ0KdA휦 Ҏ>%ۓ4˚t Bh;4&:1*kHjȹ` 撈I 3L1946kv WSR~{R,M!] ,vsifLm.!B$ci.kM= %* 2ISRy{R/M!ҽXF۾L6M K@iARoB z7krC\pC]پLS(k32eucّ.w)^'~7o%]2tdMHEgnw R+;?m#N uQ/;]Bӎml+w<̺H궸7Oj_j\I=&Z P__j3?D$AY1ҘY4Ef K8+gٺ N!W49:"T"%tC'@E-$'$C] X+KuVW(:I@Dn|c0!;fZXp;߂ 'vG sH<% = [DL);e#~:"ޛeѐRr|=U dA'iAI2K@vKOpSSi"/.ACuɤ55lx El:塢sYG /{v:z93F'팾zp 3LEDTZ9CNs~jQh{j~rG =1nvz |8i煾zF!bScG΍u3 OϒWZn8cNE ]&֭\P١czR~U*ӃpA.dPy}^xF4ٗ53BhCڗ[1<ਖ2 D~G}{s/i~.4ysTrTH"$=:3WZ#(zÆwJD[ئ_ ?O:לwlm ruEx:x 4QP-ORmkئߏm|3#uu-62yqԾ_rg/{>@=nj?}q3ҢWzjy@=fXţ߼k:zkOcs7}5#]Ͼ薖lk1z4/&q>Yw戠lYvӚ_mY{<_hR1Y\;l:ߨ1[v-F;'ޢ[]6|;3g6l>>fâkI%+qm>4LįYAȆvԶ=f uB;qIoM z1QP{k!tfMULɦ]Sc9FXE)HD)S 6U[+( 0۷|`!D8yUS6սA%MMLntTo6_I%EG16FoDu S6J#] ? @ t\w̾EJxzo$~#%]*&[τ\b$zB!b X#k+ZʴC_ R VnjI"rHT" Q̺g槒hǦx~qۣL%]紐;!A$ Owq$AI%ROER D32Ny3,[#dh5d$05X"ϼ oImָD{)8S"S] 9~zaoP"IkMDUס)P5$e2JVYbҿޔdnddOGFyDƼkd"hk~󤒐k7DkǬ:dIb8^ O="OJ$iQ<;aHȵ7[(LLy I:͟;H(y6n Z"<;!a%&eU}9XE4ʛCE(1:D7&6HEcJ#UH@ڜ~.;o D$|"I,~"/2?vq]jFU}-˺U#Z-xfgbU<04=O_v}Qɪۮl=:^@U-w˾U+a,zF;gKO.6!g4~ڌkGێ@1X_6#+v>,):!+^^#U'};?cQuRP]X^/7DzvvM$lv>؄T|ށ?LJøvLWPtuր߮wE?8 7M=o VfÏjF5]+L^o-q, Љ y"_/0 UYd4yسTv,v+޶iv}e;$m0˪,6(4FL^&ޚRau0#)ȳUrU7KBX,$N։n\<2lE1UlBwD|fj$ */YSc?W GCQ/|qhjm]7%nrc㠐VToyfb?Fjc4l(YFI?W G¢ًG^JH%=_s![LR^Q3lb$ ٺd̪%q8Z!,^<[6d%1FejG 4@Mz4j8BKR}K5[5 jV&Դ @Rƚ Ѥe6 %%MashrI%.)5RxXemGzE!U;)^M3HU xZQdSR HJhBaL()+9J0ϛ %',1WCTFC[yNi$ cC0mZ$w¾R) pS[@(V8*筎$,(RH:IX~wid*CNd2$)NZ aY#I.4=Whd!aٛeU(_켮4;Ȟ dOe"qfkqùFI$Dm+V̟_12 ?[\kY(LB5E rBYλ%^q|iD2tLW@\Og!NeΡ0 eWJE{ *:dgVMVxuL2vX[!Dea4h,jP.,':*]aXWxeǷmI+Lg6}fIh~#L,gDG' )*:LG:+ K߀p$QLE(Db~3\/DP2 kI kSJي|S ?铠/)1#mE *"  3p%IKyΔe9%`KKv2o.iٮN>R lVX|>ҙl99 u!E4Zm=u@mFߕ?؅l99uk˶{+ ?p7B 9psr u(;ZqtZ2n :fK%Y"fB0OjĞL>MjYX05GQp 'l#B}7 ɧ?I#n U?Yy:`4`"`s'loNzA Y~4٨ 7?+Q_T\/ H;Hn2&0YCAc>+59bvŋP(4ٰ8gmf逽,#ڝ] a&'QW;rEt:J*GjO;F[$ ! ~ nf*Tn?^8׌!Eо'1IJ&+ɼgKwjZ0ylM$Aq&VJO'~o:IFMhZe  g0>Վt8&Ě"39dЄշ;]4~]cd"`h`wnz1kAɌZ&i[ԝû@ j!ɤ`)9_46ChaX[ 1nX]Kj涮604Ħ2 v9uVM#άᴻ!޵hJ]1cg `ur'g9FDuJ }>DYLNy;/HpUOY. UU[6gvNɿe^.x6HZwL"&0@HKLH7]>geOq#N3CH~6==: D:r඄͙>g91.+2. (Lgm$cBFyq\*.(Xtm8%awЎPZbem #֏&G:bm #*c%vh#(~ rewɓhgdFyD錮~)' ZV-mh3F,y]U.f"D@LԲצv`H]U[Tn!h jY}Sm惙I#<"ԡ=J528ٓ )Z'(73q& |Qvf#etR`hR1@H UI"ŗcI+$;2\%79|3O&b* CED-ǿDim*VU>m3Jd@l?OdԜ,2];r˩a?yD!CRlM| aFaH{3*)bUMx q[Y@/Bv♣BQNӪ)ږF/ 'mSaI V@`Na{ʀʃTXK)]1r:_ሜĨ֍ :$F3YjLH@ LTey^d9yգJpYRH 9t|[dBO^pLc:d@a>p@~vQAߪ's|$DH9b:+`-mT@S@Qi>!.X~օfjɩ<j%KRWU^i$?'tpO9\p{TڇYݝ—=^/ZſTEE1K@Fmy{Q$ODFSYJ1Rj+Lp+l݅K-.QBҥW*Ui %*;F2;.H"}3 AJD\$Qv,.0{5r#KS^[e?z.?o^|-(bTօ_Xe hgXZ[sv2?)֪Fr;!D *^a5*H ½ 1s' !Sm:ɚpqΣՒ̔8m:fEO'{=t F}f>f$!fyϴZ6lrI*s "`&sjݷ<=SIY&RVZ[\ʾ$&`Uަ}G|#)L*~y( *wԺo3y$S{IY&RVea).ؑgטi1kM2TNJj(_b fP,*i>L3ҹ6 `А'Vf/$A "fJ#sgtJ1BD~:Ʋ# f $7)Y a 4 aa vhfFUP `6UV!Z U acwIj7X" X͐)m0AZơ@>U8&"X@{f{0Nkh*萍HQ#h77w8%c }5UP)J*TM,@ O֐g"8'u(sSITI J$$ÐXm›IFA+?[:P"DbmˮVf=曏S py9d ,iSN[{֫&zy?Oܑu_ۺ^ia*񃃮ɒuli T㱸|ySzd(֖\by[g+|yV<'? 3ձ_nW$KTtO$[r-+jf׾ ۾{Z? LI J;rjf׾/)uNڿ30ePܚ%eZm.Wћ+>$$Sn Fj&Iҧ2=_^ܘXL)5ygD4T A` ״ZJVXH(cz%<-< g4?X k"1<+)@l'ΏK1IUBP?r8H'[N `(+8۽t%k襐,9t; -#o\\o̞ŸVp ĐB= W O#;,tsIAI={W.VEN1M3MN.n:Qr/HB:~*FԷ|ζ"]=S#ݻ{w½U_lQ&eMjH= #ݻ v/po]9JN=eeNTX( XchzZNn@ʔo13vZuOOw=WϏ4*'*L @ C$Yra3ɾ77T,1 vg82d#Zp 2rjys[9i~;˝g82;i1F^`KxFsN:1SQ#/6h1];mpRH*@HTc-jdk\6}ػ.cC[r0P : {.i90T8D&q_'7!Yٚ5@" @Eb(\̓eSwÉ|i?a`іߵGf]4+b y%B%/0ijfN0F*`otf2ƌ& ?;T))\IZ_VFA r A[ G&TN! ;IL/THKa״gcud~X;ݾEuL<Rj#цm<mv~[ՀH"ZBτBՕeY~-ͬ=:ID:I%~&$LEiH4羈V 26xGQ@I:)941JH&|߯,ih\fU̾nH?Hgr>;6}I~2jKni;32֯^R Aj3RO͊+VT-q˲iڳPJ -d4i6la`\(Zӡ @-y@J')wEe 0@Pl%W|!0'1!p&H1R'_'٘# WSH~u1: )-D$RWqr0 \¨H@0#`ܢ+3ΛQ4j': IrBps*!nR:S15HBJB`t+À=#dޚ$3K%O%sT%%1Q s^Vg@J.0|7e{AI9AAMYBG0" ijRI:V E9ri秓7kߛilV|iFXtq̎$샨k5Cx5/D^|ChAN /o{5E]x]1MH a+SDľ;q*t/GR ьS/Q 1'?kU氼Dq|: xb(DFqʷv$]~ӄ~@/u60#2_==N.%1ۜX/AOW_XpI-ڒ8e%D=~7|H#}iȩZ(kN}Q%+"IP9_۔u'9cGt \2$DLetZ"/zXॡ'E(mn{¾ְ.ANDX~/Ɔe"%4{YPqwmi 率EL- D(TtE=ԡ{r_}ClJ?:&F.uOkƠ|k!J)3p,63=m$euO)-0H2=PeI{ѣ=/e6ct@ŔIQ FkW6:QIJiZG#I4$2@ Z6v 䐎:-9U|'_ē#@$TxnɄsk߻}z/,k$,`rab,,&"Z^tB1\:H Z,SC$$ڗb`[Otȟ!}9#'Y 8-I*gfup)!  E+\ Y]Fib$ĘTSG#ۊT5&ZUEb2Z@44"WirNt ȑ4::3J7L:H@#LcIjq =je'%ۘN> @VcC> &?*q)sEQ(z9\8!n\"aR|?5ɪWS$¸A0@ Hb6R,wʒNOB~)AU!FVXa̭\;2N.z3zqH̺iH@*IK[K:x%jw?2$B"s5P?nw-CEZW$BÏICS}=/x%56zҰw/]XKI$%5"qRѣz?Z7#8N"hIDDHM&8ܒ6\Gl?,nI&,gdK׳ycƻ!m}5D`ζ_oiEw?,n'0_SLӐ~iϦoZDq-iqU<]F/[tc=7nS3|DFuT?*A>ntIWR1tXnuDqrJAIx7V1_UYz!Lqҽ]ʊǸI ܈ѻ|9f)xX3ك*Lc‚9L*Th̀%:kR4o7K^_Jj:IHX11U29!(M\G)&o3KW瓚S֕9)$)r Z$@p.`xVV z5oԫI5erEclU8 *[ YNm߈77GF9Ts&C)Q *X6UXlG)Z@\#L4y&@20Q pV_#9tkD 01D6$Q@q`ZȔsYLZKZ "$^9m7P9Z!(GB4:7 * PBhhU6TPR#Mk{^O6$S` 4q%@lp,,!`1!&ZFj VE1IdyJ}Dl3j%jFĚV0]'}&0!Ndye W6L1Nsķkh >DH R6b*dSN3!!@b̐ȟ(+ИaeJdV5#*U ׀fB,6dj Z3V`oNDl`yAxjVP H\jfFrgjPeJ lJE`"J-}AF0wAݬ([j!A<M * dІ|TK޹eۭ*}NN)69\lm0@oiDifeAșOμ eӧs.#2A@#RxRkMa8-{_hv&[sZN#r{(5CzJ̕ٹ~WFh g(I[@.|)'$w(] uIq}EK<ɘU9]r}2ƌ֛sÁ"#c $8Pݬp!5Y%H,%"<4Eܐ2P wxbD$MidXAwVA-;qL;NZ%Ӌ@:O; 1k㣳!8;G}.OPji s*4dUHBm>h+GKXLH+NPOd k9aLhBB\^!b d"4bBGbZYwȵ?3Ig=~H#pnƃdIAO603}j>Nk;rP<+- reA.h nMɘ~Pmy;${*kR„c =YhMug=sKܘ- 0`f++#Ɠ RL:֨ÿ:`gX;ߋK-1qC9Bgb|s_bS>wޠ"b>X $\6̓7!v u~p H>Z[ $&SiRtT%qGru+l I' b.s')"BDO~K9$|Пj0Dc-3) {DІ1(+z;qM!I5Kz/ rP4QY@+RސRG"Wh&o]w-s; Ou%z$XpibȞ0YaDDAGld1p%cN:o+.OܵCΙ6<>"*#-%cÙ(D Q Q|7M*6{VӏjڲBT*2/&;wnK'IY?6EP4a2 [wZت,Y  vВ3/JZ*۷@Zb"?va{ fCFVS@r9Ҏ!9uoQA>uvz۲&D:)f5蔮ສqĊx21_`AښP#+j Wf$mpeKMY!;Ao̻2?P,L+%"9#;H!eE߹@m"^;+LdTW7NNկ/͹WߨM핔~]岂j!&T,VDe $X$QIBCPnIvd[fAuM] +3Rcc0b i$#dgS]ஜ /_[ L~y!)4GV6"]8:-,:K] + (Ҏ`.TB>(Z2"&rB:I>6+YAO163e ZV&/'eLaqQr ʩp3P"J,l^1 dICƸxw#k:0/9$Ϯrn« {P(uKiW\,Ι_I*&teטL:jOҴld%6'"2DwēZʶ-jL1R]gU;WJ֗I`L [j+[niLÃM um6hA&$Ѯr P@)ܘm 3s^ @b &lf"q$ 6$Iֲqc ֫T1FQQ5^6WU֜reAi )F{ z[5"E @",-#BBVYn&f Bh ?g#c}o8֞is?25J k+ 4pղHbkp;f%t3]\kNu;K ^LK/%w3mX֫yyr=Cn3DN#u{zn5scmIL2~Mq惡Mk?~22dptj+RAnh=w;ӒeIó'<直xM\k<|q"2|/10RAfhSvYq[IXmEahwLUg A,6Kh7~ː(/Fغtrt.E?3=_ uE?8fN; Z.}e,yH k?=` {\>nؿNr Z'.[A( Ӧ Sg3Sav mjwXF@@hةB &Zunaa<*qFa'I$0aO,))h(d!48mT75M,p$5ȮQQiݟ9TtP^5D촴 1-H}a3Us8`=5옓Ngn!o9L˯$QP-M A֕PW\]o?' ~򜳸9%C_Qt@C-zږe=QXU,Vͫin jcl\W$8<`C ]}*T!Rq:ȅ}]aFB򊾃\b@-b0  Bvb `p[|,GG#~;CTYJEM- )4I#ٕZ7̗NA!E"MIqTqۭksUhk#b[;.(V/Y%FH,C\J3A%"nl]IXVm)+TA{,[P=L HTO\+yeC)Ivq*\Nu_hEG"2:,:d_oo ODg3:]k͆y) ٗxpDȧH3獤F&Hn4|,N'.Gd E^tis2 480(FݮjLiVa$C"杅<9N{cªgXK'F.1x)kQ)QgV°WFPmF6$[g w|ͥerxP~j%QAߔG8P&"+:skQsN1:3J T䍽c6T-WokdQ4?Dc-v43|* G0@d + MMtC;cR@5}bK6*,K 3匫רsJ84{|cCr]N٬!*mkne6 ta;ewgs.Sd!kRAY p56z>f#/QL۴́cM>Sȯ-|HO$qf9R8DZFzrhzu?q{oә,XuMV"&&%O@uLO)Z-6<}#0ȿ9*WJVAe8/vR Egf}^%д&V8ҫMA ^c)a2H(㙮,^9MD,En6t!]=㾷Y RD,kfTЂ! dwBza!{fѤa5QP$̏篦 Q¡׾QMG އ4xkoCd:OP( ʃߜh]V$Na̩QISIR|r`E9R ^ꯕݫsh=oଫ&(CQ &IɓHi4gTz+u]'dW`oEw N)hlmstf#m<)K2[q"q )zyyY0LkAV>>>Fw~8)KbJDL\aɫl!$:ֻ][g\!f'hSH-$ S}9Fvnz4?nav-g' <铢 %"L&˖;#lyJV:W77Xa1{ 6D{;s^8'$У2 -۾IDMȀdkW{W\hAc V~Udt׬RER&*D"ֽag/U1m#=eBn /q~MdĞ~x9RJ_;kɃ!Qg6lm^#j /*_~<}p11kw=0>*k6Fith7 )a+H޵Q71% b~ <훭!ٶ\Y1*g\ҷ]*;Q[Օ%@o!zMYL|u"আx!aDY_y ]41%9Zum ŗ&fFpt0}*苅6Kp]غgJù\!(s%)2d#y44 ԚQw;R8Js5SyAw|Dn{'Z|Ttg\ = X({rW+(? /IݦV"u~+˂'2 A(ZO y2oՒlvEC°9$+۫cKQ^DfB">sΌV7%d5lW.FYQ>;1w{Jn,^ ~6f !'>{j;K)g&mMTl® >a`HHD,dq+P0l4-qkM:;H#DM+l\GHV>K,TG?5>IWV46<"to ktD+%)B 1Rf7Mze:-YH^zjXw+s; m7no*1q{%vDƕ|| 0D_ >\Rw٦CΚk] ?ɭPI3הg=#66 gKMAم[]a;qtBv&3cLH3)|bd&T~6=A T(TPR0x&HӁ'pB2Ci~F:4-:zTJT|LTlY%ԕK5Ǎ=s-M^KV?GBj,fd̐(?dR-SO|aA:O:왝MQ 09”QF#i.[]Mk~l)KbDbZSU-ﻄ1֖e r8HjG\MT*w3OiU}<͉YU#ԉ~+\~dҔvt2KcgItOq8)#W`8̭,J_f;#dIyT&OZgaI}Jf]˭\Ko6rhZΌE)Pn5&nܘ`H_qZf1IwݼSnclUr: #"[͓#llUx,J* Nv.5m@+' ՍJ-8yEjRYy4>DR{z֓7߻IkC9W6I%)$`0jc@BeifPz5&,o2H [PnW/2`B1:$IJZb93rKw7.Qi]y#:\lR<Ў|5_Nʞ q$|J1hc8R(봬$Q<٦ -y3d,Ʋ0@)r|0H ȆE|eK7gsaf rcΫh}g"oˣS!X͏U 7%ѯ,>ssu4QYmϠ#?z{ #޸`䷭<Zn6LPnR7z fFR[b^Ċ $BYmhYQT,, Rw-b>aetٽ.Hﹼ0l[)^,wΚȽTrâsZhypaēʓ'_y;TgX[eJӺƂڹhZE]gS6Y-γ]UiJҤX2<<-rPs}plkJ;7|#EӨ(o"8x' o}b$EX'nnOa``L9 ˠ)^)Ye[RqO!xIiIQ6i#3Ra}.5Tf\(: ;ui29`Rs141#5v $T̓ fa+rt[r\I cČTxb plf;f$"J#QP Wp\k2)фffCրVQV~Mdf? Rk.qAڌX bj.PY6R *y5!*|!$6x> ]PZ}_x[3W|F82a95JtvdIDf j0>՜~q_L ـF?iqu{ˋz)' Z!m+;;A^y~u yN*<;nJ$eVHJ\{jK{hbkYWK;?j))0 MsT̍y?یI־.ڈ_ 1QG9$̡ dpq*!u#mo}HKTmmeAT@G??%GT, 3~_mؒ-.?ph{nV%㴅Pwd+erE0:\O{z 4eWt}2_f8KߣvjzY#,C(-m܈=w.nCIɸ }{ce$Riݽ(=1'3 O٭&gJ|w|vGtn K"NSoՙwnukH&ٺBUjVa KrM ](f:TArϲuݬ={u^!lДՂҢg%,p6)A q`i'u¦w%޹ 3noZIrRssP fIX&K$=m3RR}sYw)9Lr|5ng O8 %ZW׬k7%1 &z\Us̭_]U@/+@dz},pfXx #j;w/!'unF4Dm($c)ZMSϑy#@o}3Rg8u׍~'6+/Dޝ$$PYt{svq4einث%Ta`si- ]u+-$vHR3J焜΀RY$μ4Uٳ\]F4㹺67[$ [Ȏ@6rPǝהl&hT6j IPuCMe Uml ]e':~Cͱq]nN6֍8 oTiWI_x*MٚQC%9%K6F0hFvpRo6#=y__iO…C"Ј3Jtt΢eQ9H<mv#lF]n$ji tQ 3ޅZ(3tHsE $4|fo*:wFU>#Df`ɩ9LWpB+¡eG UTme2aHK3U Fڀ:L:;M*`V[e3z0ATu,4vv!xP譫3֑,AΎXҵd\Fwn cpe+>Β| "+oݼ%+q2[Yd,E4^bM#їF[ גO4niՕjs*l0#{ '?ZGYL؅T1 L̈́$<MPp*wuvy 4@ř𰧻fU<=GBC^.*g?+c|O#u#wU$1i 颧o}諯dyF~um_C#WW\zBqTע#s1܉@Wkgi]2VW*R梅>P+~=:Ib~Vry‹_kLˆnT}ıғ":IBElߜ[8na="E*<,U丽;,|(ًY"KY,y!Z2Xqc_/dbH%l~jR3*jn%rҟ, _6?bs}U6!seYGgQuDHrֆ>S_*e}.}EeSt2_9j!ۯNZxQ % ,Є1NW̪eHS\ܷ6 *ڟ^Tsn;<욠qƾ1fw"ޖ!݋\$قK|b%[@Q\48!Nm96dC gRQ&y^TWr{Y5ZYv;zOe>IǖY1+A%,Lq~>NoT 4θ5z/wϝ'f0=a:*ʷwh^4kOtDIEElk$rޢ =p :Gy(ǢE5đӶK$aP(&/hGM$QƖ >-(08R {ڶw MY䟿&%t2DCGح X#/sچ;zV3tBފxLZH+'k CT0jv[Ӻa][S+fw Hye3'相 bZv,uհ3]l:N[OǠ^3Gg(m kaD^F7X-ƨagbSir0D{kE@m1H-$Q"E3C_>vq,IǿZisF,aȼ>FA<88U|@)֛}gS)JCg'`H'ˇ]iJB?[, )N}-J p;lA e .FLCi8:nJ ͗< 6JN4Xt =#oާ4$đNTz)`<ԋf34%g|-4bZDX*Y#Z­T$PxCrd^oLIRإPjhH\MƏ+ *ٛ7$}Li3Zx"d^:]!|qK+[6# <Ŕ6CYDcd̂"jp4bag6 yr(ڸ"Q>6G)43qU|"'s"yf,Y_Bk4@BB[꺢ҋ!S y+Z{ꚬ}1G+ĊP[%AqU`U/bҸqiiWE'^^;-Fk!;b4ջFj(1#78[ԔP%M',U hcf妑\\Rj^dJ8E)FaQ2өgjAFDj+u8DZNޏ)eaj"(;:4m-pK]n,=~eR;}㯫'IrY! )Li[#4^ ?䆯-b&5cʨn2pk ՂfW s"xNC FmhgQǁ"e1łTXi6|OHC4IS^d%Ѥ⥡EͶ$jI:!ڂD6%yr7 ,G"9Be`T4My|mڭ /':M7|DÅUz>l\ ufk/% tc37lm%$M8ӮHJ[ۯ .wVŋhtu\qgczc@ &Bpd=%$'lƢY׼f^yUXzXW*/V R^E$IHhA]u;/cDQkBLU6 *B%5$'lC/n\}^f=9xF8Bnxf_RU ?K 'Ɵxμ;x}Z[`vgMB`;ZyʽkZ)[h|Ts>y ]-a?h9$zPKo `gt=h=$A>3!{XuM7ojyPHBBJ xdJY(3rpRue>?۷kONh&B 1:rhbS l#z;׾g5%(o۵( @2y $! kneV9mem7K?0./^6I\Q -ADԴo&cב-1ES;Ίz,6OEbgh¥ z[l'd# KQIe bn;p=|ڛܽ^?"#4̀H}Y(f1 r$}u灥p϶za%OޗL:Į ox2)M ATVXIx# O|كH++U (LVϰ$~ D#Ң@_c>d=ّ&@`(9]ɴ7. ` X(/#LV*KIїݝ;}j򚟫O=ٻ_0T<ܣ2>2] #NZUӛnWg 3tR$ZR/ fD3 kiRtUKfARLwuN"6ZmD )5}LMM̘RǼbFbV >{ނ@PoM UfeJ''&2=lJxsKɽy~ @!P69,"BVV+n;RsK%b q YdG<CH$5#lJ[2KQF;g:Ұ6 [NLL& x-2阇L"H<(iD%g  FE,#0/[O(ի0ѺB<1dV1qE5lb٨@ӱj @ >1rIX襩:LdךhGq&XDZC0H#qZrd9#Rt@(=Hv},T8dF&nj42̑ؽs ^Z`Y&`Hh0 IC:h2ȷ|?>}?Ap`\46l}D4l/`.5{>y/ w KE1 WvRVQJDM,_u7ڽ~z)..qf 5*r ԕw˽wNa^vsVC 󧎔ۺ> "B5Z wJ[sv+{F0xDHplBJT =Zkkk uչQZ©RhjG-|7mN2 ~c&}V^Z[,wNAF L}Ey}H, NK%bNX9Kd@j>$c@r^vewUU~JUi"`BLi$#*Id>Ar-^K& s3MaHvlTIRDN7vfTۀ=TH҄&+){r&,V7Xg.2h5WT^>+OH0@^?_N HZ>#YDdn# 1*nNCCKxPEmiޫ*šq]Jbw{M~/QM)Ng1Gܝch& t׍HCטu }^itZe&~廪͞~2Z_`Y1Il4d_&aY|kK~z큿Ev҉jvy=umwIIq O_A{̐fv=1kafcB| XGF@Iܫ+`@u Wbِu˅~?/\lF@5Ӗ[h;BPf_Q !XkFhG@=ZDCLT@FRVUoݧbQO°Tɭd{Ӑ>q^)1 UM:u `ޥޖKryOFd&$"eU]8\ QEV 3V%L\U wVH4 l7$ U+?,=2CDr+kۋb q*N3F gEVUIpTedFpr@4t1-"Yq*u@ZETGUʙ:E+m#8 f IM#@b NJ,gXKp1 !#i_LT}B87nsR5OQմK0J[*ŐaQE=UdRKKPJӈ$3hyN5/2긖)Y{k PdzReY/vIBBG \U&Y!/4DWCzw$'Kr{f3KR$Y#翩~ $`(q%AAJA0 +]Wx7\V`e:jC~mF?.tXo|db 0[윧¹L\-OF*if VԤV8 5f 71RJo" xnC"-֐N[HU@ z1,qb^ ;D8։-0tQHPŠ׃͇6Ѩr dbN3e?B\Y`o*tP oۤ7P*Oc]ЖG('VV3z7+K L89HMZI< ;^" +:fAS,inxhسfkS\RߕJDCga{Ai Xh3Fik=K՗XU#\ѰSƴ5B('4sS#JctF/i9x-be5ֲ!apyhNks]G*ˤ5,Ӹ FVQYM (^ww.omJgW)f>47-Y .’=kgq`rM12|dU>..* ň%dfkQS7Vt< TqSlAR$1sEzlv "uجix T 30;{.&O&ŪijffZڋCQٱ^3 ڴl3ECM7k3>]ЄbQXv)=MPa9Kbu(>Y9b+Ca[ouKFΪF[jC,Yl),Hz$Do-n0EloY~J+ 8~e=+CkD/yF[fjKqc9Lj`aڑ-%Tk-6MgVF)ʆ0%]Z?S9]؄;J-/.{h2olT`d`*!/)×7ub4~a6\:I>DoJ-;֯sN[ܞ+-/Xig~]+4hU$@K1cqt勽L ғ3CsD0i$d"M;5[\. #֐*E%>2&9"99qL+i̥Q-X+\9'&OI̜h@Yi$ydE6]*45Ճ@`T8ʝvu*KJ%exfHľE7*,55h9xya :J9l?x9{ZQc*`n7čG=n۲J >{tc6lX#ߏ1n$*=2~Zx eI^9SGYNA\ӐE,OC-+Ȓ,o+^OݣNvƌ_De{K#K*8`yA Rj(mCNИk@]h% |bkE!]S';5eks#_Maa)*L$ϊ!nQ0KdgAkTS^0Uuۏ%JeqrxH [JJckZ7y#/9dy[F6&}]ݚJM>$V.ZzBH$D 娚ҊρtNb&t&DRŏzKtKzmPYx\vNb>x閡M,(H.~OnM%U~7._jr 2v\ 01MT9&O={.xc}Խ7giedM턣VƵ5WB Ԛ>iW Eh m9i2S@,:)PwȐn9p{H@0g=^B0[Yb:Hmm'-< koK|3C䭍R)| EX"TK0D-< 7$MptihH'$^kMGTeNlevB]ĺHo餢'z\ EVt/!5V@`"j5c f+hlKTR!4vr$jb V6;eD[P[`VyREDz]erdotyӷα>*jfiЅKg^emtx d>UqTbsjծzҳ;׸,|kLlD3Lj騊wKc{ҊfьYXK5[;.2Iy[>kx. -8¼Lxao^i!Iə؂)B<B WŚ+q$ M}=iFŜzS(dߴ;cɆ%meӡ|X;_k? 8d<Ծ`?NMnɥxF偄 Ae蝠c8z[tVH bj~/pxĵk(Hv:GT!1]ٙ6'fEe ^U[8}-'ّC"Zt폨8`i'RN"9HH"y򣈂@ΙPԋg(gED`t2lI9 )<1Q baO3Q|-V2x"<;\r`6OOOk^2BIJF xLmC/~kYh,0Rn,w|,(S mJ=XJ*Q,gQ_|T Z'\W85)1CKv MFvxWS/wd7ل}-5R.ڌ$ڼȦb״0@U\CUnV/fd^j*zND\,L"K2T&.4Ecd}y-t/Ay1,䮖˷ˑ 1U*6*4EH;I2`t@д #_~``|Lmx0.%ʳ9R,u2;e}Øk?~oc 1e-Y=7yg*8׵g˧;iW}ަE:Җ5tL Yib''Ϋ?~oHPfJk˗z/ilNxyrY?>gRh`n22-b0;9|25Ru7KĞkd5듘dzc`$B.|׻X홞ċZ^,-Df7gyoCY,]$úHR$N_>FP9r좜3L%H{q_w>i#wK PZa=Q-ޏ n:5yU(DMT$%4u:P'6;f~3>m)d>%NdTN+ յKIMhHu ?';=:<ϒ@]c^&K騾UIH~GN ےHaR>Y:-RTbs1֖XJbD{> rМ7H9sxRfJC:-RX"{Dqo0-#E5cCAKbIpP 4H%q@~]]{ܜL`xGA-ʚKĘs5?Gk,<j3 7ԗo?Ч]D9䏳/5FZזW wLzp~Dq غL0!M{X^tΪdJbᵙ'hf"6^͟z T9+Piĺ,rwT<썫?e"- m4l@(XSi/d 9HUe 8޳DOs?؝zHk3TK*}\T)Rӡ>SqXvPKAkU#E \&KPbGC/K"wKQcBqC7\Slo14)I+{JJ-n,h\uiRFd@$1 :I8Pq\y\ΪYnDZWrq 1 Ԫzv? zy칵Y= @O>w7na.=gu!uxe_Y'FۄtY-?xٱ?A[:P>+/+Y*5\ux"?RN _I;/^}d@K85nR<[\/pޯ~PidN/=;>֚+SMsϣZ¾CWX囟 gjgPY)H6^Mwmosrmf❍ZÎV~ߝc9}zGJ3:HK_J$m=cuUr:3Z[=_/݄Rz!Ҽ~Ĭ}HeWm|w$Ҕgw$kݾs.߽إ68 $ pP32g8v姟fYABLro1J^[}/M!҅qNvcG'ķ5fEzf@[+V޷ݓ;6N=Xr}W jO~;-h@.7 6O{~Ka;#D יۗu=}s=;lp`39囑<25;cf#ai w pRx|C+fӃqG(ߌON,1Us٣xѐnEAM /m=j}ae =ccz='o=ھ\5B=Vߺuѕa I1t70&t͐&р..\NY$ⅼhһMxID7%_Y]WDmgO{)2G^yhĤ͑4&.l N/qu3_ukg󛅺Tjnm %M:-:ףzY.">yCRN Υ-1BxGQ6PDR껍FG'/EgH z{`CIoh( e ɢXQoˊW8d61|*pŃoesE髼ՎxJR.=̊4|d?e!GTbWվtD–Ǘ>=1ٛkAARDƏCm۶N ôqW#r!;}dKac|Pje#\yѓhM$͎k~yGpmm9VΉ@Y&xbc"5h/"H|lu) @;;؅KR< ^MnIQ-a/?S2i~~<IpUͻyZ&{@i{" $ҳ~˶*jSdZ))q|L=j7< 6i)LTF6CpwYn9!1W|z_{r8ܠu2(Y T0`KEN}LWw>=oJ9=޿Y+"z1!|Iz€/MRхC⟒\'cKkfh#1p>5?k0-OF3yhHMkcLuF,% a~vCxZ=}\To!6+ %R>k/XYԺG#dH ju0Hd Y7WIta}YRվf@&7vɚ$0 7Ā$Fb 鏵 bpP&+zkVhr0*0ĜD#˿׍95oȨh4)|ea(yYͶ7;SJprtƒ &a~;̏GM~u8g~_JOwwADug7ۮ{sOEl[g+}M)$n??ݍH#ʧtV5m:;SJ范&`SH&[q~|l|Jzd7qUlq7rd.Q߅Q{௜'Uڥa[K6"ɢ4>0mvI5u.KlYtלZӢ5ȩ~6kϟsJ,?"⠱uӶ뢾G='BYH[tݛ5_mZ=eJj2u`zn6`/0us!qu[-E]j0j1,.47_YZ)Ը3pn|<ų#DHטno~&@{rr?/m}5S^PȧLIH>/i줈ASj;n-'E绊$h'bg]|Iɦ=~O'M<:GszAaѴiw~t5WɱlN]nϯ)ߓOgvz444US?%>&&=!_aO?S>7{z/& G36!Awoώ+'Y uQgRش?vf?mM&9Of 㱇ksJ-ú"5[M"nأ2I373ݛd]"eRycɪQ'A_-ߥ 4C$M)a~Hs/?kFw֬€l>=z;Syw^9I4-&f,?{ շ OL)򭳨޸os՟n@sy\yK= |z\'$Wr}زb`L~pKmSM۪e$&JI+C\J ZdȩO;gru帝{!7&+4N:.Q.QJ'Q3Eڀ 捚P=Vvy| |I]]=):]Qg3$V:fDgk|[?]ɾ( KDI^1rO RO8ޣx_^36wh_Mh4O* ~m[劬/~4@K0֭jPǑUk/]0TRrAPfF)&uloo @Ӫjڼ"ʹ1ǖ)\)~IA)QqxY2;1H#4Ƙ#eH.G兦H)qEg B!*s2 v/$}Jiv*l"@'P*Ef}^/7 lGLz䴘uAS|kEglqąfnsSU-2I8􍆥@d)PH( ]72'CRϹ^d )ؘ  1ֆ.~ R !c"37 ¸ l$ ؿ.ޠkCVkoݒGC2$풀{deķCRu'y"ole^>A Ȑxe5Jj%ň$dv?]0$tߥtԉ>{+ ,*I>6ӞvmUG¯ߩǫYZGE"  ̈́)#D FrYδ^H0O7@\LX@(f4BMmB2pij9g  :/NhL KN( 05꟩՚w?N/؋j#XЖ,b&!g`r$FIaE5sL IM5XZB 0*Zh $r_;7~ԳXț0QPQ:ݥdk'αA7ϻR8>"YMc5~!smoZo<{$c|G=BDA!-TP-E)stTvCt?"qZMlT Q+YC}܎kuϜy[J'9=ڭ4K/.ޘӽah?RHo&V5~:ssoZr{Oukݏz((l0M Re <mj6nQL^lSÈ ).(ʛqj; ?>raݢ־,A{P1LL_ L*`vDRT% 3Jp5޽K'4/e=٥qflf岋vq#GZz9W;}5n5d-C,KR|;ѿǜԖ "~2kYN{ܙ 2]}~.1.7!nQ'/}P&@z״n9}>LPams>c?gِꑆ)sT)ÿS^k6Tf8w|ϔht6`(I]"p&hm.'?y4z˩צ'r@i5)yљDK ibq=!4oFZ;gC׆SʅS`FE&7_gBBiNVYf^M"6SΠ+7*Ӽ8 tS0 b6r:Js{3>KLNoT VCȴڞsAqˠ TK9O/BMdlaM D\ *ÈF# mDwy?0>ԥG,sX Ciͳ-z>E)X2mA3nVw@ҥaJ{" bTmnZQ~)ki /ӈA{ؤ7-%S|xT^[wM詢'OǃZ|uG2 ftv*wDdbѵB+n*dJLm^fTGZIZ,{}}IW3ldT"Vq_Bi]DB%wC/c%h%[࢘qRjTDIfCBߓ L\) PF֒'moX*Iנ^D2m)ScuiP]^Ӛ'G8VԼ/=(P=s`Fc5]{QnԔ:ÿ&u)$o%QSP_rCUt*C;Uݚ3/ƶO_د)Խ-$ʹ$k#X+dφ8x9SS~23c[.U_(5%c_g)$o_>ԟ󇏀rC A@Gv\uk -Cg[5`8TDnXrCnU.(bʎz)&X6N,<,w\>@ZpI&'L; X׹ߕpP- cy(+ fieŅV>~%~j4ez>=826'8Hd+崦rJ+&*~'\Qݪ?Y؟BQU_U W)~n$XX U `U?:e$A1phN" -swnV\vZaRrLǿnrL @Aқe0.*iq5pVQ (aYS @NIJVP$Cob".,DT@A7hB*Nf xyM=%sgI$z^p3sk~.izP 5=} ;R2~vY=eѬ@{ HMS /78?(b㯥[Z_EY5AeF,K)eɏu;ˢYz&M^~K6لC7 vUSZCŽyCG/G<>_ȭ)3ɜT}gt7o/fg={LU.:o܁ "˝%_Z|49یAXNj߼,9w݃W~J9 aWM|MG  *҂s>Lm컹+o%R$RG|֕W?fsˈN_87_t]ƺm gᑼ`D!5k *wף("Y|t ݦr~2mvAwnzĹgzVxvw"y$o6. ʠ9Գ{VЭh'vv@\7V/=L܊j"I궕Fק?ngWwB-ЭEOnt:sk=]q5gmIR-ވo$gC4e8EkMikpWC;YcC JQ&p jeɔ}{:<K$-l9TE_ dCQR&9@^}lI5Wl)SR:$CWdb(@ë#Z֙e?%ô;/>1Dc$A9O}o_䒘Si$/>_16^ctA""_r+jE͓+W:A53of"I_vSꉳHou&hƣ<+ F&S.U?ݩAZB8#4& ԵDy N$8WE ipiu]dcb;o{K:HrFWYIƇNL lI3Yb$-svj?&`mƀ gtj`:'ɨVAD8@F@CAe'񌡰"pHs:ڒ$aNU}Y9hb@EHoDBxĄ&TutFF E]4Ǝ½"]2TCN $P08fd-A_Xx8t>>_WX<_C`s-qR"}2h^G5^(LhxI+gd$5%JI4)b;.$ҋ}(kB{7~\=>i+SF]Po~?g?/v^vVzjw=F=@8- `oP`_3O7HW:]ގr6}C[YxIE?f/UrF?o'/'fg:[ݺϽF;<E7y/]W'uv֜t?T.x﯍ ӇIWYi3Os/\۬"jY-Nt HZ%7\0+~ iˆ+t=*i5gAEf7|fm$o%HI׺4'nyM8ԣk~k\Ip{o!C˟]֚3/b@??cmE{@Y)Sx|ߟmwƛ}\]8kz7v({%[1 lt2 7/#SkFڥ; FhXZ#̟\pc膾Kd<斶߼c.(jZ1;^3;: :d̟;}`6g$B/&"n{I 4|X7gxOqf*fpLhA zsV4VLv:+ ??.X4  O@gC61<Gٯ֩АS#SDWdc]Wp1L^{XX )+J6GJ'%ԧnȒ=F.w\W>Il2%|oK%y"'\8z{|&r[ 'ZNH)dS4VMjQ T" @+bC)Q]C4>Lkdj=֩h@=Ż~!)lɖdq$qNYB!C xmN4:|\6 Y Sw>嗇a8& $ ɫEP`T"'EX[JNe_`(t>`Cwa*XkRbX9v7‰T-Q%mT($hB`"p(;x hih PJE"( p,Q0FK\$\LNZ(l0MDAfDsKAúBr wO(mА B ;Dgsy kcmD~T)q]9$DP"]%!RU| b^41<\S Iv9D"Ѐ.SAv?ztsF*pO}ϏZ)cg~avj dN%5#D _JUpƠG_jM7?gl\P8 $Ȇ,sli{N-u5o>EZ%[ǠcYݖض g4ّibtE8Exf'?s38C7GffVS]p5[^'VPo?OgF:NNqF?P?E#<_\`Kr~z;׋W$IS:|R?rNM|a۞#c;HyݩZQ)iSz' >wm1t}S[J߽)Y(,ʚJS >7?@:fV7uoQcohs d4D8{Bb3BBOnx?w糛[ 4@ J~vdw n~U/0 Ad ^Q!d@:BعϤo>3-3àח~n`6s#7l7Epؐ=U*|AHغI'_\bz=8! U$VL՜*gBNlqPzڱS2YJ"u?'|pP)'YT;$Uq5'ZIˇq{=Z$|4HhsACXm՜*g&I˗N]hqaj:-.;%IF('|$$+}nVά MZpb#UIa Q;%'֤Tbol`C V MS XmJȖnKN_hru 5MCUü"`l}y*`& Vx 8‚@VJL"Y Z)%ţOp4$xӎ0YFB@WN hT)(-+a,5ARZ:NwGl1ԝ9 YA!+La)PZ >I/b1ǟX2\_Rt4,B6$HȩLAR!īDGsC@ b I]ɨ EF(Tjz hV 0jT mŽ i]< Dv;ߌ>h7~>VHO5Ƥ%j'R@K (:S\ބoT9 [L?K3b+~KDM!NF@Tw}Q }A<^I˞į=YS55(Vj"u P Ώs weܚ$e7pߣm[p9%L lY5Ha6j~~a5z!8'x$IR 68xO3WB;Ԑ9]o٦L5t;$pNSoJ k7ȥT^5T?Q?r1z>fk&$ab}y/GKӆ` VS𷯬˸5y "lItMt[ϧO$/eNENUMpROVJ 8D!FyfibO60K!HxaW/ @CF㙏H[' > qk^{)ōP{;Ahxc:v%^??/u>T)J$ѯM.da_ǹX bż4EW$ـ~PYl0aj#?=gBX;_ywM(V _)VE%TPp LEmP$<}/ TWs>? kuwM9vآXb!eoAם `bpa"Vy{g~!:L]]*}W]Bj>Jj/[S_{vOj+^9 1vp;A"]qg;㠐UE qU8/@RӗϮ\NEk7oDA*/]7Y&҃B) c73vrNd {c@IT?>I(>'P# {t T=xl{Z{yچfjPx苫C)y=";8cKfR(Ee4 E5fv+S[<$&'*XR5ȮL$ ʊ9Br_MOC'Eo$;'_!HQWlI4\>\# (ZwB1\ rBggSoIhf^-匫XB in搢@Ra0R!I!E;r㿐fp?5tK%]/EjijIR8ay$pl#Nҵ̫RI?cM$?95]fy}2QѪ=Uu͌$FԪ=՞۪}F ZuϠ꬯Rsɩi@Ӹ==AҖ]?ݝ>E{eT^_3wU5oxsQu%+jy+@7-Mf$ơOCݴ #,QWsskݳ%UVPl[;EewmA4R ,^Nv6 z{YAB#SncgH-é~]m,nZz&?gu'~nOsHAoL ńь<߂ȿ"iiO Eb·RͲ4G$Ix3a٦GaZeRߙ[ QݵR?HnNY7H0am]#XwdH-.svbWAF}Wf.tjCsGm,#w o^|g]^ +}$H%,Ehl{mH q|Gtܻ _AP6%-^4<`ॱol$UsoN[ݯ\/R)J.֨k33ޤOG{IٜLu͛+I,IqžǏ;7&I`9ٻ:+3aƾcaǽ5&I;:/)gqRY*H%0Kg-oGwLhž{"uֻcId9K*<4B;hot8ns]rB<888<l1w3֫>NjvwSeo.%8#p `-&cyW ?9i@txh±z --y#4O'!kc{8JCǙ-F0ucnB'Unz PE>*l!3!jRHch` @c뻚#o(\ 5+˧N@=H.&՛H&U PkUGHQ.3 wjeh%V%l}K-@ʎzQf3辥Vch*Y 5oJOe 澥6Ϊb򵡅=8BIb1|TKY7MY3@0KOuڱVR(o1DUT7W揅jmI `bu(" ,j|vfT_LZŘޓ\.a"j,)Xyk?5{y;^on)ENJ",e/m^mFO,+r8B`s=dզI}ɿc)wxT,L PrGmn|ǁ'з7Ej7+D5Jθ}hGhKh0 PЀ h3&WH/$8 ˽M(~(6)y][ͷW28=f@P )&O _ :++p$«AId,^wW_>,~5]m4AZ )4]骠_F͓.d)V SdM3-M~],8e)2:FtjqySji+b} \z(-+=brjO鷔40;b^ 4BhbxO]xxS?ѩ`OȈ)LБ 3G&#ov q̷'xtSvl8 @MF,YnFFx{IN>4ٖ_D2pje['Ԯؓ stviKɨLAO]aA<7gN` <$JYgQ\Df xWxI&Ӈ8 /H$$NRxx&8+MA`VNHf@a8 CUDGdjx<@?`NI0Qܺ""hQGM;E'a$Q%hL$ vCIHߞ!߭@dВ +8w^3LϠ@=Ƃ쏞0U0Hl9:^L\HB:;㆝>|I2g2JŸn:w;  s"vg~$ øԢֿ㓛7 + Դ__[ᆂD2Sp<7=^nJES~72;ZOasB|n:ݳnԩvnK2Av% gL];LVn.KuK2owB2#ZIhWX'gf|"kh3WFIl%ɜP95V}/vM v>_2?uoڐ[q!QAZ7KOi~ޖd GƦݹ_Q_ٟJ/RRh|Ԑ »!{~=#a2dg_:g{=>K*~#b}X';nA+-aoVkS7/&Ӊۏ2Mū iky~o6s#WIP Pcе쬙ԍ/tk8L$1q"E9i=ArwpՕk.]x \6U 2 W׋Nq&d{~͵:S7{8pL,4lk5x/Z>u2u}Tr@#I{acQBI{׏1P`_\So=j1Dqu^gc)r $nYC-#53>}[y5@uT%'4󵴷j3O|"nͻp!vdN &g< 偮Iy>w!fCV3m]TUH#Pۂui*X $쀧'cFR[4?\XL^j4)`L ͯj2i7 (""YClW|JW??pU-RGM"~MW8׋ƭGuE #e(PdF p&80;O_떧 Bk](sFBvIHG| Jy* RF `F zܤe<lv|S-OPh uaR8Z:ߧEByAa hx!u*HGB~sMM@$oYf% 4|PH8oeTWUfI \"VQo[$q׫MI,-0.u?(c@0k X~$~FeْA-eWߖ,?s-,E8b.k=ϭjcnO4_jVj? &8z8–-[F i,,cٹu,dƜ.,S+~k`4GaĢ5g,U s?ŝEQ3*Ւ"ˀ $EH*%=Go.~^OnVړ1GS9- KQAYҞ⎊pÀS/%}g߉UYc$(@XTs5v'(1dt>N-%N'NAVc>2gQkrhw}dNn5Gcc@53By&~ l}nu!#x'Uj;3TYJ$8 `jA{CnC#x-ơX &ewmK6ۣayA0R&:-q€BQ?R$\٩[+!Rgq\ma-vG[H;R5ٺF|5{//2-ڗ~nsN,&4@|wx= eżZY͡3ti eҺ-$͸#caSA&QUǫsо.j|jH)W^-z=JloCJNE\/p3+"O,vٲ*sǺ8K).g=kvfiXXIaT +   J6<9. $IZP틓@J*")A!BTg4Zh%nYŠHS_b{h ?.{tXjX:׆<ett PJh%hy򥲀Ӡ~LiFJv&|R+3rH`!:IQ.|uI@ş䀐J)drwa2YR< htpY1@Hoyɲ*Xc(%pH& ~%Q:U1.+B!QKppuS 4TXs@xl1J*e%x`(˺SRMIhap;MGG5^z}rZ.W !MlF W2+DRa%% PM#O{G5_?&ZfgdC4װSTCVT*`dT.h4 ޭ*fL\ @̹OshKPQă]X K%s1yd\c~jB IZ$SeӐl'9TXNxƻz{s1{d\}r]D.\Lh1N2 )$n?:Pta]$FTfLt^XҁDAQ kfb, Ιv#&AtG}jZ8& D=LTvZz JGy<_OfH%26M#e$ MS9\b,*@cPV~W_I^? (v.(aQqw*+)Iy 57Q/:TK,BL-~EqܘׄG4,f/dZ2Ȇbf-f6N.LװTmLlrOBGb dzG.\ /8 *GNqYx+*)N,M[7*@m5hSw;0Qt{lsb,Ԛh!FNTl ቎ʠpx6}a @^**Hc!؉ R`x9@aq Z3`0Vp-)+⸬`n 'M[T7,$x7RfYx?%Im{̝C:M" AHha% P\]pIf$q&wcF6]XQd#` 8Xk?HǙ*0~~pĘesX|ɓMjkTGA+}+qcFrO__*<7#=b`$ Zhd8J03$wnn5֍0PA#'TOr'p0yB!F# R1 nQX!tRPqP$oK*wX S&/~HLNSwWS/tn qj&^H+$ 5Ǟf+A5r5b){Ix&!"5#uS^ߙBbH]FINzADh]7p/n+[utB^qDP(Gv 4g#!p}a֪Ʀql\N 3fZ0YBmZuRr>)Vj;)فdHI4p>!k+1>gBB uA!95B^f" cHbZ#$ET7~HT~GSr[mʃ:z֕2i*N-y`#0@El[š9\LGiDmT-:`*zGQX?woc6Zp#PS3vm:B "f>lpi$#~WFYD*p $(܈=Ej YL= NMhSICID+r cg{Z4r |Ҕgܩ{X`K? edv|~4B lC:MrIȈjb^ sE5"l f0W+ DŽ䄯ht >r)ƀ{sXNi x?^L%ZR6Sk2YO Cx'ؘ\6?7_dę'"(A^{]?}` @`A{z1 1ۚIǹN:6tNWO<٢ ¢5H%s1}L2?B^6`HHdL= ֽfmʎ$ ) z!^D[ȫ zwGH^ƵPRiQu_\6Aa\PhqpUWv{^q4bߔE1ˮ3Y :;ᑞlziw+z'[_?'C@fQPaJR'f3ȕmS-E7/$ e2$s|CcU=6Eēq;'ZB SH/0`KA[BAdWU?IbhK$C%aIA7Kq$p"UH 2o>[csiEZsNF[6F bCsכ0;?d(S{(KEK8Y19LrxA ^ QoE >")3Y @&@8yŷ= Wu$EuG. E@,N6ӗ~?5"x<*{$<d{Za閉ƛ/D,;28ٹ7|W9@5Dl^Ug%/“. 616QdыFV7Z٤+H&$F.4C(  Q 瀒wyIN -ChĒb (޻ yޙHFGdh(޻ ~,$sqӯAPz5MB.ٺ+_-")}T}je($6P̡M'݂2 {pn %w$4>Í+ E;)Z9ϱ>EC+# |sɼÈsEA3 [W4d Ȑ"4gKC\&:f"I:f.`: 1t&7m]e3m%*+qyȐF%e86jZ\fvăJi(\ /sM3g2U5Fe3>XqH Qc*f\WY3SSqm2AOP҆H;l[Cpiΰrx %C; JIHo6? L5SRz%cAŭ0TLdpmsHtH-I')2+xp % DXp s LXXlK2ċ37%yR3^lKԊTvF|1ATHq5H Xt`k曱K:jLR0#)yJNPlYű_?pw GvKaJ$ g3@tCDPȳwddp I.6kaG_i:Q+{.tѓk&mc'd + V0vliU= 2J&"kskn& @$A ׆wK +m\(p0H)͘,hzzPQ4@TEl6Qq/L *bK):Aȳ3P`q̑Ϙ a`'swi^0d Z)7:Hd٢5%V%Occ#Dia鱞+޳h!NsnI@(Jxb:V窰gTFS|)^(DHˑMi '("c85qT D+C%t#n`L6q/mM4^AQDݺ*ڙF Kb:IvNr5Nj cYNkW}}`lO2Qr랽-MC2t$*x[\2Vv;Ƨ+oO=tÛ/ID&0PiS77Y^-xaollu!"Yjy&Zva4)(*ٹbڎ?'^@%gЃ0j9|M^5ԊfΕ;w^B{5c\qyrL+:ieTլO'#0)^kDpC&\KކCh,K! YǙ6G{0mM!ZqvOXLp 6㕵7Q(DMIYY|S]J *H}0GUpd)[ x*̦=FL0یPW)hMġ-D 86) $؛MA(LBn6 @Lْ,hIC∀ ?-( J5rU5"41yH h!JjAN'zG5!ɹ?/~wtbܭ2 QÏDQ-n2[XNٵ2&RBz e3M#a `ϓtAp3|9b-gK Vd&(ΚiBIe<9bA6uEAvUfNLSeP~k<bvۧxciʂ}΄R A@B+!cK9" |M>=f $ KL`f]R&\?W[3w" "4tVTN[P$YK S:iv[fa|_7I6ylwgTWx Bj΍]c.'e*Elt+Z?N^VjJ1q'&@`s6$iEVPW9%1_&kr۔; ]h􅛕#F<$m c35c=DM!X09""]e+'W&A}3lQ(bdb6,iP蔙;v[Y,zJuaȴ^RBQRs֛+Dg!qK^́]mmߐGXOd@%B#˩NyA҃:\: Y+Haia9HtznM0 ֐qVXƝzs /YA% Xȹr㭃LU SA,8`9IjyV Y#(3d\~@peNJ$6:t9vYK@YHtWq˲ْ8/uzaEid϶ My,:xW@ +edѡ"RdY[F,qQY͚㺂<Y\*F91 )=2TeٶӭcpjI:`X\E[|b1|KZ#m GU",~XkeS].n D.:x4Nպ$stvm.xG7 ¤Vֆ\4 [r%9 uT7{u6Lpcӂ8U@C[*(5K6P+Q& 's|Q;6GRINŜٻa1Q&޸`SŒW[F1u[u,7F'gEwl~ծD =kP{sҋ\!SS> wlI"_Ͼ,,3wZ9HT[Z ǜݿ̊&H9U Oj g ~bt8t+)m6Cu1+qf:f* CPp:)=j0yNX J#xP+(b@5T8HBn@ȘT!c>h7k!8}ZT[V>>aQקuF'Ujn㵝^ϤL4uÙ_$ġ5Q'H(Vs7H] 6Dg RdFIJ-Vsu4r#iEffVvdOȁТ܅x"@%YQŖpfrU(%]!YS\ː5TC- h~`Rfd H-k *4MMҙF i`Y"8BJ-4-xҀ`%GpD:洘3Ue-тd` i}0W񒿙8a)(5ݘ;fɺXեikTɘ9ƸHqT|F"R[JdϷ#NDeX6J-]R43cfgt1j$K)&ד-=*ob6YvKV0 R; ZX4]a:II e+:(\[WKKcEnY{>$&sSڂ\JI )Ґ!ʸU)/y* mPԚ8գQ?Բoab2FSىVBH]g\;[:D&/`u$;$91l.,8 }!>: 'NNgHЌ!V7Fւn W#KBՍK#i q0-,] ҵ$XԏqTxa &4RlQ=mK&N15]!y8+)=IR,lZv-F쫪23IJXjC:94{BӄK#հuHGG\`KmP>34ՍGU u %z&S޺]Q?4AɹxZ svytO]ݲ5.ZҐA'imuՔrjmn0orrIE8%DeS`GcaIU|}Vvi.[Z N@H$H iViU}:EZڑKX,\aXWrMy5|SI FDHuśIԋql++klu v-36R"Sh`j7;Wz15'7]NbYuu1GRB9 %Qh]ЊyGg@@oL0fFcI$P8a,DGz]ȸsgziꄭ.FZUJ%~aE)8 gI!e ]%CAn묓Uwtz:u+ &OyN)7Ӟ?P#)̲-BGcWZiTnu}]z5p=1sgڥBg׫GhC$,f=S•MwwZBc^~N?56I?뾘H80 >yfS$D_J:6[2^eě3CxrqD*?+} T-cZ u!ԧ-g ?rn -#ٚ,Ps]1a*0@@\BZ 9lf3W8a )Q~Igk^5eSAg(e%>R;ئtUūL5^^u{u?1EcXNNi0)gg7RIBExibXtϻ]ʒ8v H᳆A4bV֙J/HF|>MZrAy˾D fv o9P=c\q<4n}< ݩZUpjp5GR[̾isiݩmR6S{[H NQ\1nr8KJ tCs i>AҌQQx9O8Ό=)gH|1]rphט;Tfd߶phpx y/8#f9  @bf!Cݘ&bNU Nx-Js΁ujgBG()P1[⃴Eeٙ9ertϼI_xuz#%15.|> VNhSb,ksL=/]t+ƙ5GebW/zm핆sUFA$Yiј$9Z#̩ɆM|;ؠ3t[ؚի\MLGF9^JR}h!zΉO(O #%S}G$h=JIrf4~뼙~ 5$#*ZEv>SUjJ njK+QEDDu~$lԤpDW((C jA~S%ҍPc WV~@MZؕːS۬AiwM+|V>z]6nu?A}]M]U}'}5ľL9:nuW#(whJ\*oZzA*M+ܥMrasP2yzcbD~LZ=qש(xv ~.oۧ$UZ!vuݓ,qVfP9:g @R1Ί\ ^܍\up2TNtz9v*#T0 s.nPipbGNDK!Wa.0l.g(LidSȭyŒx--i$.VSfNfvM#(m3֛Zi+Ԋ[=(&0M¿PۘRb!rZ&&gֆЍYg7HibQ!K0wrbuO#M,8"\$wFW'8U֌P2l帤 CֶX ;pнeN)#8,uȲ ySaŭft"x'j?}g&v|KJɶ%Fş+궩{篲] k0հS9yntzeWGq"AeJ a>йtl*rZwޫuzCo׫PdĨ&$vUf*祛WX& )`޸f5{2mQ6~8Nrh:K Y\{{V97=+vj02/coy*}ثD(fO+PVSof}을$,4m񷜆hʜ W?g4@11wskuO"pb#'9`>[٘VLv,2̹YgCHzࢲrJE vH~1=OvUt ͺ6&p?ҭSYJD mĉ!4z"&餬^<ﱙ_vG %^prb*T\=֫K&W`tR^Q~] L:b%=z&'yM'V\%悬Бawc &$2\s+'ӎ?31{/۰:$j(`qkӮuS^=lIk%k +J/;S}fvshdrƓYyxУ_0h̪˫Ib;y=V='պqF2.٠1g8%GcGIl H)  ;;n阭mnAhH3sWӓq$Đ$Q898/j)5I?ט j; WQB^RZhdhկЖ_@ gD⁳_"7'lʒ Y6&`w1]&,LƵ=}f#BfrH&(61:- ]ڔT/U{O^ aau/ ̅t V>\҆d҄ؓMW[l ) I鱉+hAk>$6AF D1t>Xy4wU~\W2h8%aX@ؑ IWRYKM^.N+;Sk ܡ{" *1# {v3-#X_ Rgߝ4mՓNOg""{lQ2[{46va(֒#c;=z)WevSTs#i@Z fĞiE1aP xrVRWH`,5x-aKjDS&EFoeP$9jbjU{3C8q@QQ:܍-zx)eRb] , *@=&:z*kz (VphaQ:lZ L9s4Wxu n0"9sHQCzEz {~֠^:9r|cm-xaAZ)j]}]J c=O=ҫ3$J0!Ri1i|9i$!@L]ۭ9>eKk5uTLȋHh 4tV-]=ϑUu::J0rqf#wMcg* iBǏ7$h;kP@ Of4} a!P4dQ^HtM6klApzr9`Y5[on3z43$͋;x :ܲCa: nN +rk>o7f_i4Sj=Wl.H1i 5_$8>l J;foծv|$3U|ŝa*0Λ2M3q&Y3HЉ$CWڑ?ڮ'GbJ{F/w>˨>e YVڑs-U<*٣5;W :δbƙh{(&[LDB_c%w/yy$f6Os [MEB8 f=Y^K1"@dA F^ԖBBJ z%Dq(!CrB߷$*Ӊ/ q :dH"# RĘ{qM@M3FPȴ:>("̆{<"2a  TYX~6fױ'%esebJ2msJpUD$E(DÎw)*P7dMLV4!q榞߭iA6Bj:6ab x "%wIq7/evM șFw;Dn+a㒢p>2e-I@ 7l,&9).u]QIQ |k\CH VU0qӮ93 Fj%Zſ<5"E|^H9{=!]Ta p䨺@P 0eZ!J4QQ>o{MGVFI4;ݬ&66 Cs*ʎtZ.ԭNy);*sCB<+ Ѡ35XTn2K rJǥ3^8L%)n B+ DZ7=ڐ8T a{3$C ATŇ.wTb d1hHvc8nIȜS%$\i#f3;"b_rE͌^;-'%= D4oϗLIY؜yAvjAjk81(bӒ A" cYgJr8~G. Q8x:PE[\H (B BX7$;f؃Q*CPfw9Z<P'o*}fIܬ林5͒쏈Th!Ϸ1@EmDi$#PִG?L:BE3_F-& Ti)t.@F: H%*&tֆGBg!OnjNhⓁM5)̒ ,3%WQ??";@sSF[j /84&V"9I3y+^ U` <&OѺBD"M ͲV[R>'nbТz i1eXo\#R'-5h4B? 5׾pUbC $KF"6tJZ$,UFsJ THR'Di t!$"1M1II#,iih0|ިjQk!v4kɨldE-IF2m6M1 7:Ӻof): CLFHTlX=BၤԜFɸ2[֍YDӳ2UPM}*$1& $IniCȤue(c/r֢S쵲䯃CO4LLv%# =a?E+- ';RߦSgMdϥ9PPXj&N'Ҹ_D,5%,[p"-L=j2[@](T$9ѫUZ(B`ToB ъZ [ t) P>2@0%HאI(( ‰B@hJY ޳MR@b@Eum8b4ؤ/2%2"T^7 U55WӅ&}n CdzI)#ҬY18d^=fŘUT-Eӊ=Gp$:b#X&h)*wdˢ00GEh4`QUt FN|/ &Rpe' vAfLOV HǠr/ӮӁT,iS6?=.uȬq⣇V'>Aflћ>Ďtv~xv͜xe>6Ys׻[ #fK 2tSo+*-ITM&vXf׻^Z3ZPm~*11 )SR$88%k(P+s_xY]O~iooǚPP2%Ҥhut7J#/<xa6Q/OӳaJ)P <[+QE"tܬ4߆-ؐ2H&}p}ZKeg!_{9[oFMx=tK3X&P0TbéDj @i@潱+s l8F \"<[W2S <%s{fЌ^O# *%,92cS4U{cvZhARqX+!MnCAp3ſ9 QԾv+[#}{ ZF('(ir3ba"C89hE.;5`iA t:|R$+*GhHRXB%-%^.9 z2ZGBfз 0WaC=adnmQKAıue,`@{*kPX,5|AmNݷ0&Dux1-3r3ݫ;1R&n fl?Uy-vAꟜ ,Auqlb7u!!$!3JrZthјYQkkN_LNYc\t!Y*H(D)0غ)%@URݻi fP=­4ۑ*%*K͑UO(VADdF3ɉr;庁c]]8 Jz0eⰀE H\`ۑ('[ NS:S#HV|Xm`lH]XnW2=1؉km}6[-?;x;5}?+%S1' $H?n-YR̍ *[`t%a~q~nC*s);FS1s8[CJ?MhEWOӆ `t秜Yfu\]ĜfKbsi=HJ%rWa-OCȂ8òh#ipMy$0]B`-ҙ%:FV^|פ<0A6snڳ'$|3nf[<3Wj[|<`Oy !7h7=nnTRN|KX3)ό N$2?Ep<DCb@G5sg *lzܰ}b4M u[&p6%,xɻZ[朩)oጩk6" ﬕGt4m>XDT5QqtL iZh)Pj# }k'L??#4x/3,)!F}cgؐ``\$5xK i4 O0JEsT}JSZbA[A kUِc7x0H+M " @nWBaňМ~؃or*OiMS3ϑ/9kH#9491njfI+ hU-"KeIFId \@,EEhts3$QaT)1}` 9% F.Oz%is@Up.X¸$Z(F:RYhpm)$!簪84bL2x`qc0g|>Q9Lp%2I$aq*9!*TH#Wwrv(p4KQy8P2)\P`g LBRpp(r $fFo OLpj UMDA[lS IDD5$L$Sip^@BB`-0ɀE9 aFCE& d"0a'Km_m1ı$iXc +S_2a 6[iQ]W5Aa a&4Ӵ'7shB;rB,T2!SR 5bgY *X%])_O.,jYg- 'jBЈ ZTqN tϿZbJ|cg} w1 *Pxȩ6~#/J!Gf2abhOrz4%-K@fJw.A&_2 Zt9 2>jI+ޘqEf;4aS'>>d .v^$ uE`Q;r=gU`vL45I͒J:VJڳ:|~>׷}+~lezhǷN]Pިxb%RO$x6.K`lǝQv_n7s$zhcl&MFȦ+8mL"!re&oX~V7ۼ_umUh@XG/vT_I7} uK-~<4$mq>VБk ;걨)@NNr-S[kbwۼ:iG~>q{Nh"#SH]ۄ G(ŭ/-KIvX'To-Q²~(҉@!,Xhݻ-MURx':w&K?#V?_'SU (h/À !NH:G ̀\@BF† |f໿S* , Cɫ`Ui$hX2e gbCO4tøBqW˂8)8'2>tg>St4-[-^f$H .э >I,f_7ofMBJ' s¬&HHqIiDDvHR4!Fᣛ$D$:k@\ AIVᰅQ뛀-3&P,a-5Eu0sVڌ1EalcJp)%*|nR(2DqMsBԩ@3OkȹXƬ@H..bs&fb2zT W}&M"sNԵ乌1(XXAEZܩVy L*͹1*Tl\ Ch1$Lq4MoyD-ɔ\xzD=h3^.iWeMv3 理)jcw3cܶ;0~nqùͿжxMu:)w}fuܼ>GG#5AUN-A//CBܖϔ;uʹu烀L?lzMznC־ do'g/&m5FYrI.:xdj>z{%pN60~o8+^h[TikČyIUóOĚ rl"i$^.B$FᚖCVeSM#j"LD}]מZb)0x79 >\0b i MR4Qc!eWd];`_4#`*IX(IL÷ V9PsNj@N^ʅ25$@@uȪ3ȖBL /ZN-CW sYe˫#'@\jhjc_@Γ,IMgHJoE&@hW,ɑR.p@"*n(BN܇f`P0ARg x&l3:*Z^842G.>{s{RqN<^odM5]Wk߶3ZKі_8N&$3F q̦p3zT&t?|ojVrkF{ TXJ/t8g|@4s|rj 9e k~֧;-;ڛ.ӯ|'pb]UuLaD!X(qf}>W-슧3 |vX<"߄tPvkgbH᣺X,fCo%Wvb^|vZ<|WVg xn#^Idi"E z׈k_Nig#_ړaXY(Ž.g:rVbǩE~0 !& ׈)|d&p{:L ʢ} (y+vgӯ*?EK)`]:.#ZG[׮a0_)D̈́#;l0%083雥f$Db [6HBhFiKir0I !J'%h[tғ^A*eD+D)p*/U+B!za*$Q@|H5ljjF&v nRqMe#ҲΚdqZfƨACX UAfn3 J[runղCҖ4 ӱ/+Á`SL]':I ܚ%i%c @HH$g+upր"'#q<iḞIfM,2% RzνNCpB˜SƜdH#pJņ/ F Md5w힜r.I2hU 0J.;/2=?%4{EZF/ !!+ecp\?2#ê7KKJ_\Q;V׸IZTp M)|LtJ9L8Ên aהloQF-74 REHx.!r!w >Mm&\Ѿ?V?\ :n! 4}AUƽtqJ@l 퓌jA ip Qˀi`x`!Wf=fb7R 3P&eCBNύ_ZP$F|f Q=4Iyk w p%; mz$a6LO|eȴD H:oӄÍ{fuTwHi˞$IzB99OlXjzӍ[ZRHu_9c6cYqyw]%?w8nUI{8qGyZV͕5%uP<LNM۝0ܯAb޼VaX;2GR IIBe$؍sMF! p8:1dDH?E&DŽ~f ֘;8TAsc>_[hr?}Wwr)pbrpf1$k[:H!W۷`@!l _qYms#θQ F!!9Uh9CHox{*7];Xgݖʠ:p6؍!.qgsXe!+J5a/1:yl4'd|IM;Npx,ۗfyU>,z^}c{0zB8g>iN_ͺw!oIr\^o9_}`>cS"ӝ!޽/kHkc /=w^M]9ϴN%' rw@JsIit4 Hσ'6Kɮ\I!Q 6;I:؃ $+3 9Ia8Dk[KÕH_%AB R2LD**ZG'3rD'a ߺy$*B+'4_'r%L9K omkwn?w cd-$$nÓwv",wooM#ݨD9#y.MWW_oͅ!RD*FɨDVf'aS ,]O)P\tYLk. Y:u8at 7v<--l7(싊9aGY8ٽiq4Ҁ 4%ئ&uaU7R 9x [+j&6Tu#X{SQRP,)yUE%[kwlBe\jW 85N?jx8EY#,fSr74 s0~!}7\Zm뮃 , kK&_{5kKc+lO~>tC%;:_qf'b =骔j J3p5]6u1*AĮ{q!ïmvC.z^mtcQ$8hg.׵?%5eӍ史=bT"UƟx@7l7R6.͔Ku~ Hc[ֱa7^a@tL)BfxaL $U}݇f,И T/=;dy<%H)I"tIuC BbݚK pr wVEfF9'@$N -M( Fl; q)h2dr!%aesHOOIBc H{<*r$?$㋒uTdX!=v"U"/{.\av8>1{?u~INJr fGF89?QSOj']bŔ-jFZÝ6|ϚWɿ&=Ud N&/ dl`4\E| ^j\>fg iMlaf=xZ ,^WJ@/ܴ+cFNLqW\KdtQy7YR 5yǭ~(KAtpkPf8[WP?vߤo|Bւ.'ُn ]B<V(EƆez0-'C/J\o7>X'! `Qb`ލ0Hڭ@$lA3u-/E,(3 q!*I>g?n\"&>:ޗMcYUKJ4˦B{α΋frҕcëw(>R^YWjl]@#~y~l?Xһ|e;̩[/sqTKڞ,#{{jy϶t!wyy?tx<]Kպ Ɔ*[5ڥ NĨxA~FyNUuy_s칇;wHSg_ԥVgw/cCk ꥬ: t.ZZw4k{wKT 6ZѬ.^xGvŷ"|x{ɭy0zל9>Evڿ+^ā{䔛uuںc n՝aٛ:v],I6/!q,U%LޤSjgemg7KwɭjqWU:i0_t筲$&rpKoy۫ we- o{.}[3!mq:HfG?Ukpxǧf v㷯:{]FRf`|"E_ITA{[ã.!eOkɧvj7/l_ŷ6_4FԎV =۪7nZgle:aޏ^VNn.JU<z!@Ut% 3 ^2L?oQ4|ݰ3ak+°BjQ($,A&ߊ18%Q۴&$H<%7W=[$IH?h*f)$}otRKeBT3H!4 _<~wKjI-czã) SHu,o>q]4wy ɗ'ّ>CgH8'v9}@C=PuR|\;$G ,{lzF&e)וl޳\>}>C!!F:j}9KO MP -%h֗tWq;l!M%8lL@Y;%.zڕ. ҖqJi_g6sv$DLןz q`:[A%nqA4hmY)EO$}]n2X;UXHf^=1쮨Kڟن4'Cٵʫ֫Ҵ^͌ #QRvwnڟY'iғO^~x*z*d*޸ZQʄXo x{=MzWkW}*RZۛBtyLD9LmyM*@Y@ }'O^U^4O|Iʌ^sD*KĴ0F>asus`OХT}K'\Isyh ^Z/ ~^VIVrM#QB a €o6}4d^8`-57#'?ĪHH\O ?0-sfNeWed1r~˪'4B+^l&`C!}\pwoªte>XLT(Lk ,j˜lH!=t4-{jWĮ\6(:Qmz4ZGz<{UK+O?a҆ĭpTJqs\ϘRTj*dI93J6'M>~ojBj("ԜNJUGz-z{(RJ+ҵ8]d3t[א(N(*Ln4V5Zt&q&' /%FwH=&]nWq=WЊܑ M`?^F &Jx:C]R+M\yhgT-5)W|.1=z;-/}yÎ9"1Nf?q~CC=f/ZԬKCq#0EU^{Vvuy0$DI }/$;:;I:!Θ/3&-#ސ ͘`{|+:)C u&Ş׈rGBum)iP &D]r} 3͑Hz~;N|A"HfbL#lIUsAЁٖŸ* UX Tz-r#Տ6#t-Y{bZɬ2⩡G(XL鳂!jOy[8Yr㐧T禥gIVI(kQے5>%])Mm+J!٧kQugoh(>R&GVvY~5P˗7d^k X,V#”Wgoh (u^S֨CSyImgfly։_ZMx;|;I-k@x @[26{gKkA_Z]S2KCQ[ȘLR#Wn),x?gBltn;R72}I"J ґ ;/J!hgj ar㋒䱞"$)?ygWtA*dN +dq,ȸ6($ՃL(2% x g aRQs#2 ,b#BM ID\3Q;l=NM#<+Fv72 ,daJRȐP%eܓ{ʹZȄʙMh AT J2e穧 1lxo1((0[Re­`҆N;uCIzK!fInGHzhfWS;OIm"與08 "J+StXwl%@Z$lb{n- Z؜5 t4|/nx)d~?q .C/;Ԧ'#)dͿ樏_0;CS*8J%$&]n_, L:|.ŦTӸ< IMh]n_ NJBI N2 RFM;XK0j,j~ ôFGzqq'PH: Qd&*QLԹ}9,qBc2;5 ߏ;t'mf{mY1#GE-JaH} ƫSO0Z7c%)g%ϥ"MRiy95Ki]ڝR\mCjgj\M xL]syշ׭QBU7_J+PQnd!$q )TL{-#OeJJ< 0@WI!ɥݫ($"mLBP gH@;b9iI4THZRHD_Q;;Ue5t*[=0vT멭w~Ta?ʘX@H+_y;Ue1&]+? Ђ-M"p.x8FPd|B@øLCщ=)j!߹#vGkS{<W~ƹʐi'*g? }ͦ_RIZP OTRPRI$4XMAAueAy(5!7[j>dIR{_UJ!h`澢?]=@$kL[0Iwo|lAumA(>6[jJcf?+N| ϒ߸%!ӻYqBRAuCAP:7[j`c8u :[ FQ*:@k PTE /D "F 7&}ߜᢠ?|hKl$  HXrjfAʶM :>I7P$')$±/›"$ApB(1JYᠽ`$HtEPOFEQABkzݮ9M%2$l;-9ujtTB{TRR:56{jEH8[,юiQ lEÔd(1 1PqU!#7R5"&sE5J[FQɑTRJ".TUŔ %BZF~BHTxRʼnZ m( ?'PZH# \ Uz\@}ОCSAX|'S |'nNy8iw3S .<(o_  _xPH =eb<jk:hGy4<2"hioH'?tQHeq<"ߩ =_I]_f}ڮ}I{cIf-~wKz- f:shW~9c.H !P眤|R Xtēx-iT`+0 5fE)INGѤ.1!-[2S?yE !Zqodug|S/ljN 9BH{NNmHEd}~P/U|K:t:7O7xIHYւ FDJ2tM*Y, IZ#z>Grsu#$7؍߱tmuҬI+[6M6B] ƎqKӌieFBuu,/t;r7t29*uwATdY( Znn w6/ݟL@aFNMة#FCǤ [k]"ҹ6{ꪃA&0j(O-uyzz^h%F|$fOm]{HwfeB29tBfL*9ve,XcI ldRPbet8$һm45h ݽc.^ٵZQc"0cNkS[˘L!QN ~kܣ>77I;BLVSSxzޓv5i;KқIpCM%IRC2pNCM#x7ڕL'g:Y0[h%~CWo\q+tt-w@Ug(1p+ڟmgufTwM[AXbhZҵ־vOsHPznHvZuw6z{ć6*,KuX:y4Y ȧ*;ߢ=kklӲ=Иm8#nH %!]];4bIh*YJZ7A-?} /X4 tTLu:2M6;sOCznqf'>TJ(VQmLHVxSIfrڴvWѝ@#4m~&04mz&`fG1^(۩X UAeqz18tؕTdW/n)@$@TWT$,k x"*3wgh!Cm7.0XCuƇU0Wf֖!#Ū6+3G%74Ƃ{''弬`{,|d*т.Zy;6j,O93=;U=nkThNT2XP$'%U9ld[+*'μ$ْ;?zL^@[ΙXhEvNjq NuOg"ݹu[C:`cc뎾foSo=~yv  ʂ(`e\|G)bs "QS=j[ tXloSvISoUd"[^8(Qhi筤k`n{S7I*Z H:򵏃}Ot]4Mk "#GPUxK `%ʓ.Iw=(lnjl ibIJMHef)"W pdyKԛKwN Wd+Jpj I#Z>ֵ]MT}/ea?(&\HV C\)eqJ:taV4ocoU-}sw`RsˑibwK;&SE ECL5b/$ex#M؉RLP1J+an礽}GEu 8wjTf kiRpc"/ݱرf ]X)NSe[.Zd JW܆NZ\EdS*}/$O6@rS=qS LfƁ/Ff<{%ys??h?/!@nR@ Ŋ9BCЕ!U gt q}|(xEKlSޖ_U4V|ZW'rMăDE'^\^N͉M%uۣI@3i ݤ[ E񦔈8 k;/ml3: hwsΨ]uePE.Aѩ+kqij/ⅳ^Mm TT#pz^lG7K|s|\$/e $¥( #x'ա\tÉ08Άʤb vI{]yA9ih7xBº0, {9)ЕQ+{8#TF`VAA0!\ū-926FCQM| HSA2L*,L -gW-:w 5 V)W(YSTU)ŭs8KoY?nԌ˯M;|YݦYyuOe ;wĎݻtja@\*+K@r[uRj|"oRbsG- @HKHo.YU'k9 x6t{Hnfcgxgzb瓐tw*5 Bl Npײx~~9 wMļÂi. Wi"MhfG5x/%^9񙆹|ǭG:#fAឣohϿ~=i{|qy"lP;x+fפ{g5ώvړ͢#;"a{MW)o̞G:};IySj;||c1jNC.xږ"0V)B kzrF Eo5@` ok45p<(G^^4]:Dv,85L( [O"`o) 7fژ'e VQD.IBR UKbSRQM06y:6iHd x"3Korgn#c GN`iEV̓vn6BS~_4ɾ8m_xkDI=gbRH-;^exߜ2ĝef$5qȊC!WuuTA?nNm::DI(a7U}GWXh:DcxgJxffdP)isuT~s !WZ~ L^ĬCoJbv2#"_E=UH4 -h랯W.(p͡Սv.OEsLhb5L 69tgs7gUܔM @C?Χ{Wf&D\2(3'hNPV6n q393=6*hF o=\3WN-f L.$13NǼ,* =pwn{ox$N&$-b1ϊfƒ∸cp-$| :}Z8}GrÉzdDL? !r`{XL!vvG(]8)Y%@ATn?#ʧ[tob֤>bYqmw7曒5>Xf༩g!؋ RLaPMqYv(gk94F*5VvcUpU)H2Ͷ'/F>Á.v:t[Y d~kK1b;H;#;͹ˏw;CO_e{\l\Bγ{D ֭Xk?|ʜNq[?S>qT PhNjrMQ=}iܤ^oc42F Bskz 1܂,l  f="_ۢU_I?lsҊmLόOSRnhc1WBޙ2I=ӆ a] X0ƹ @;Y&>{%5Ԅa^!3:dD_z^h4ڍa*D c{8 WōO69FfQ:A"aWkqNNe)C񎀸ĽTJq 8gٓV[aN]xd#-'>H&VHk(l&@B#t*LL3B*ŧt?4C9tKs2Ibl6Fx(3B`qp\C^q40:[J< Qn|Zn_zȜVm.LwFlɦ!j" !Ćj%-ޮّ%z$66X"J@5KU卼[MQn2#>xb Ͱ6jٰx| BCK3KMf҃+!z׌Xbk.8J4]%ALl܃B1U\fH0\SR@0@xxP/})|i`wߎXgLpN1ǜڠ<); |&]ormK ;eH6>~x@_9P1_+dF'L5֕RtΛi1J0F\u MΌb9#0iDK>a3n]TfJ"hﵟsoV*zN]o `m-^X-2Lkzɧ:sH_*!q'JѤz@T%+FTHV)6LxCbcB2FY̜S{hk(ҋT~ pdՊo}‘VJ9MtΒ7O`i'_>PPN qADWʐ}_fGgal㕮c Pjz6 K\Wgekxb^8[qu |!moqṍ{'>1}W2?~ǽ*j N'D&bbZ>ݏotgwFN@mz Ք! 4w, or2ޏEU$.L``T]ˈz_9Y@`I3lA=AwȎ3I}S6`w`]q9\'8Fo)8 0H 3(8vZ34{gpsX'_gLqJ̴4\ 6 ȳJ0(ZvvH1H8px>['T-e3\Y.Vʔ#{\DyU++[,N$a Bl)+LIBlUͲ82ap:eeNAtHRMH4cOTOo83CdFkk&9'X*( G ID%<u$&$J1*ɲlxl QJ^Mpt7'Z+c BOaHq`}9Z3ry=n:7e3 3w I@ŝp ÊtnTUpp` Gx ]VdJ ٖc3M460lth8[f?6x~2!o>&[*I]R4hxXj{CN42!Xh #jIBXHa4wSc_=0e$o0 fi}3A!SʴAmgJ7R_h"*D LM֔&6LyٙEd{AzHg;IzKg IrX&HSdx2rn L-ƀFcR hd(lIXZ<DD=LnCN!TBJaK=W7{Il HL=0Dޏ Ǻ+-d5F.?.Sɼl3g+ܖrL.KJm;=amDU P 68kN&$74nEGhSgNZ8`O>ǀ¢a$aT5;_i-nUrVXqdZ=xA@P0"1 uZG|!Vtjo@Po!eq v 3S .'PԛaU7)76¯fISaHJ2͌]^IҌ;Mپ HL 9=Js!Z2@Jѳv:V D> ڂ(@*R:s))ai&#. V.9ձb!''`Z"& ~`  sc LÁ6+IRE że5UHBx8.*DDG]I"gEP ܉N` қD QiL֑f-> p >\ ɰ]^F0 "4KC:5W\z /!d/GN(5%o栅߻@Z՗|˻D¤0䲗NW_@)y^.[KekN&rL2W„|p2"u Y+hI,dlu@^O=)Epvgl67HG٬!)o={fe2-sMI˸ADmJJ=s=ϥMĨg0Ym=F&,hYQA50ImM%lb&`TUǝu7Vh68L kXĠ!31u,;C.oSÌ}ybip2:% @ K>IH^>1fZY҃ z-MЂ" -Ĕ-f$lT`(TV&GWw(Ȩh(!LD#R^GI ÂhO$5_:Qo!-+!kc#g2LvA@`#q7+Zc߰-+V2_:)X _u7co[(yF;˸PSt̛I3:'073{ۚjA6oT Hz7m:xR{[M4& ]Ŋ"*Q^Z۾4I5ҐȰd7Ez٤ӭ&WI;{Tmk!x@1ci7 lX:xje5Ub$s#DWٙqYN"d@ bB I8L6@1)&X?G2þB6^M ID D;+DdMk*hcDvk知c6X!b2#3E-Dt%)"^ 2ַtv> =[/C]o~G;ڣ!2UD!mhJbaع29-)6NQƈ"լ hO߼bi_B ) qmPFZ؞QDIor:c]fIEe]A1{kӝnwNI͆*9ˈ*:k $:JM^؄n OmvmkҼPKbbxh\ Na&̝S֪zχ[Th  e<*2+hxm m;IDN)-Iч!!bF ݶ9Q$$ W\ fer(5 _V,>'BxhF!.{ eBlb@ d}?CDx RB>ay51!}'Lݰo BA7Cb,ed:ê.]:bWD,@XC0d(P2 +[CG@& C2TOPMHZ| ^l\ (WC%u]BtQHQeBA?ӷ_⍢>J߁2\Q"OA.&O_14ZU酦`аGH.fΜIz=gs7 x Gh_HVWOiAp>Cs(m8](Wia*Pm#&\3B [~p M 7΃rK5ۂ a$2~ah?)hpduLhN`R0V0ybP: n3T kPl` 7N&4#F0/Y/$H(:sI4k31*MH8 bi{V#?0*\4_5o@iO?@AC f·E76Jv H#<'qj.NJVmpb"Ŵϙtz_. w BղCsד{S||R>u`СP3Kb"M4? ΘdE6Yb%-|N~Mҵ68,XtqpF^$7\c1j|DRX L-@;J '@GC䞰WEJ'JьikҭJSAT]1Ʈo&&Fd}p])g'.*1>sڑhF>-1-V8KT@TNHv5l=c~/0_?ЬR{`()I }AMLی=G dfe ݱXi-%V䦠AVUbeX>1&.V(Nqw I '-,TԬdl761*.`sb@VBp2[Iku6 2¹YLe)BMg1u~vTq=7g3#/J&݉>6\Yݧ/)$>)҉f&7\TP"̊EӢ ]8E k( Yܽ?)!- qʬn*S,^n%U>ר3-qv8sC$kER $و4:&,[3 jv_ U.@LK={O.Hn_4 P8`Burk$@9źOuf$ HoN2zFG N78缜MqlbjH&UBX_t01& Ms*|sfnMs_f.^uJ{fa~EIHzŪX;7hReA֋ xZК"H&Q#L:\9U^JTH՝):)c5&M#J a&/2"rq%K5b.q[{!! á:StÑ#Z5T͆ʿ$VASk& Z ȾW&C"s?)W$5dvin\kc''2V0(i.EQݑ/H.p٧HNO#(N2Đ+#8NO3?=ɥ(3!n(XHҚ%h#(|=2;ȢCB$1<.5#~Qc380F]HQe2=8aFjW7p`\~ e@E1I5ڭT4UU!χdY @)3R/Fn!FgIH*hXg?D1dYDj@˃yIUl&(I+U MY]ShѢNA0 xw`N{{#r襺A3>(4iO~y$MpoSm\ji5z/2>/2y#'_n6u'ԳJij:53hŔ}Dp\٢Zvz{Q4:!5C嬃CFDcɪ#m݈%NAQr2cmQ2@-͸tҖ7TJ0T[m$S1D9m=#Bq,}.P?3l L@]c)D㌨P(QD#ĒIU!f+Z4(F!az ZӉd͈1Q{Ff+󖥆Q\e;( Q&qAxzڲ13sQ ٞ~5n߽4M1ҋLTD bD@k[ecNxEiEt͈E ؙZV&w'Yi4W$$hR#e F1ExJ !30 'u!R# p4s"o(ϴ#RLyvc&]La78&  LHӎl*4*4y6_9D{'RRz.8\PI w+'@F걠TC1❣X[[->BE^ DG束DâP5qw ʩPHK3O  !:hKyv)B!buʐif4$:ˆ}Z s  Pi3qŐlH`.M_IS/ !P!^7ԘP7O U*\A0$%ɴ`HC.U$>%6FU9a $B]Z6Lcx[dž׏b1ⅹm;F@:a}lx9 &0䥯eҼTcw3 1VD爪%ظS,7`cXF6 ӯ"B\R'nE16R+蜨cQ =dnUU'tႪޤq;߬_Q)PӘaԵkr)-EXj)*@4A g >  BB4Vu b;C4.@zb*}MHQ$# R&'RZ a6@=P1Ģ}Gdnb4kH ҩUTGgUVΟ̅Vrn?X."6rQέ??X'D I$+z4ms"$ ~R:Ò31\rC AdE|^7]9wl)YYF'䏖jW|i`Ф^8ix--GcZVSh+LvM =i P@h1Fp1{('ͩɴo(F{Y`+DkXMp%oAl !#?P>5'5t@oOe6K)oQu'&u;άxɐɻ8|ki^]4om8͔-eA9WvŅ?3Ч;`49@`^ T[v䙡9ĸdNI7m: ~S>aZIj/W_ UQ}$v5HqHUui9*SU򺸴= QAfd,g2"hMsh\q^C9-VhpUf`'U6D=svdm  G7-h' ;kk(Rn(vl˖#b XV64bKNDkDׄHJM[X&jcu putj{ZpPIr ;pbr@9l #UʉgM#9؃ uu [V}۷SA u: {B0X $ӏ'?iv<1',1Y_ӽ$;*0 Ӥ M[_fJL_S? Uє0VX[8V]첰B11T(&zfuws/Xݷ8J9H3lNMFR\&,RHA4餆X&ktmL8Vɀ4Eh0RsM+RYoIwRYR14E$yLX{Ӕˬ-.4J1;eBA9+}q(CrmK &"PT*PvHȊ8Үha5OH,ŶkO~3d;h+'Ǥ}՞!A 3.vw~. =c nq5IcCmePS,Z95lIMgN3eqK$ Ֆ  ﳰMT g(p6/8 y"TP Xu,E(!%`V-I  $+ۊ;gZDd Dwh`l)Zi$ G2%ʜ1=&/3sˠIE[EA(hjٽ@bt1e&r#C /!ED͜4DnY 2W%ј:W@#=8JŜP{;eYl)HAD &Qu1<1e-ivGӞ73aVU(vWY e1 )5k鯂 [U[3'ZM~)ւ,` `csk!J€=`v@PML\s8֕F3fh]Ð = iH"oO847Z{ĠD m6rt4Pc}L%a 0X%M L4P [gɀ Gf$\ِl|٫lE,MOk8L'j'ZKzLLm}Ll|_N!²X t%IīI S6#m>S'yI#yUEkG\K=Nذ*߼7.ܪAS샱aݍ}qC F ]aVCL>9T"Hr:Rf&B(۵iN-z1tftZ}yMRI^*ofVjGC'Q4JciQZ>)oe}+Wpd=wz-I}?wbY~M;zUӗp/,**eEםhqzC1DٯbjkBlfǶc%V2&Ⱦ|/)Vhb9'pR{~Z}ucK />bB*mr`nsP%Z#{ vh0$3wnp!k>4F(1!ٗ*ZvmNV|hZ]sQ )ؽ.\ܤ X'ݽ/|{Ą8F' ؛֚ԺPm R u>7lCgLCײq^VX(\'8:jtkF^ļ7'Z>>jU?%l,pͩF M12f]{=Py_Oַ]3U7O.3ӳsE\M zbV`71ɤȹMJ4~"nKIF]Es0=lM'3}%GlιUC\έt-G{bs!@ )9O#?t;ar^-=e 6ʷs]Z.8Hۧ1F&3!Qo;ی"]g (V м4#ҽClȥ:d1)`u&@*UG ؝Wk7]n݁ `oC'wrf{-8F3CI 6V6f.ڱ{WxDN,65]s1pwǬ& ;as5+,-d~v25e]uӎgY q'wItTY=g$7@ =-H<,͹s7+jS4N#kdV>SZ#qdC\{df ]q 1 3eV;$+4:o)7.¬ys ;mp'eS&m8mFLJl4,I @P {Rx!m)/6sAQ1cWdt0pvS=<)l4 hV\@ ɕ֞y *]Rw%&MPdO2/{Q~I(^C& 8wN%=KF&R^ujwk,F?$`qԥOkCC醭e[ͪihEvddjGC(V4Q砼} }Wg1e.ʪ#$gVͰʁ!RcjE7A nin* |e"PofTRJYdurerlw(hh]u1VɎUA]s9~Gt{pmxSi$CNQ =GlV̵ue4N`al^(FV] EsUkbH1횒F"l#8"vJHXm(%1D嚠=*m'XE(Veb]'acux!"'~m06lc(;x{uVf?IͿU3GV+.T.:B0\~)f+Rj'5?lnsl$iAs-(ƜJˍ $Wvmn[Ygl %S9. E5pոN08ǩiNlDF-.Дu y:ITvOJ0,Q%Vc:!*lG^}%MW++y6XDܒqՈVmExԬ2t@#z< $pǹfD`Q">?gdV^#X+D)>O'M{6ǖnXg$5q&S^ p&ZpR#Xb8Uq}!,vQB9~0B[,X zNi=ܶ;۽=X*5Y}eMsW"l@v0]ʇ̏䟙aeohYIH61Lƨ:fMLI$24}>QcuqSK{ CjXVE6f6Ѯ,7%1devS=4V-Wyp[3\lJ.FG+`l;:f v$M܀;V2R$e2$Sw+8K*Yna0J@DR(w>IYIn8&y 1*Ƥrzd XDYRa"2TJ`W[q8w~\W50j1;!NmҀS5J_F2(@źћ*䘑mvxyX>/ U3Ricoۜqzڵ?!G"%a"Ox5o:A]Mh,?ۑ0__YsBCdf Q mytDHӡmӢ,ڸ<|՝5 Zhlnw[/f$?.ZʴNS^sօ/9/]*8MHq }MyV`F2ڢoxƬ9a"70ș X0LeDf H:t $ƸƀQr:͉BQrd^+WV`>0 AIuEC]pb:#w0ES21js ˘l 9# FR밐-YYݰ[,uC3v"Ue5)% (ħO 'efjxqdlQW kXVͱ v<_ FxC49hqKkɿƓTYe L[>>iK0[2ƺDmc&FRbC _gm="Sy v>`w\k<%uN#bŁW.-8.\?}I| [5 Lfkc8"L66ETqs)겮wt,KhᤋD`JKUȪy-R2 b'2yOp Uaἄ1C[߲Bk盈J(Q,kJYV wL$]1TKVNG {QM+ bǮ!ThnE!Ռ ;$k%XhVe9J*suэGWmϸ  @XZډ`JuCgdf3ZǮE1wD7NC(Y9s4hX.`fl=/u:N7 QMJs@ Ha4EI{mDih9NigC³vBX+46 arI5Ua7'M'S9WQ+2Ŏ11b,Ԥc*){@2yKz-G[$vMrC4Jk|v2e>EɄp̣R@R9Xsn?͗]0 DKcG[Q<,3$%lҧ|Kа왓n6-ΝҳuF41p[D{Sj+!/C餽OiڝEM5erf!"V7J":X#?d{{r/{Nlt68J%c03qJͩni gɊ~tҶcZ;Tdխ%!=c@Tfz1]v(5ۑhi" L'?X3_E51sС9V;i8z:x9K(N$)⼣nl@i@ dEdYh^)34JFK~OO\"K*eUaĈ@r* 1:sKK8BSoZHN8U"BXسf!*r#Ԇd`=MUr*i< ,(9AJ4@Pʟab4Ġ\pU! jGJI2@W0^rö- l#[~@[Ł%ZD3+5`__/o;jU=d(lkͰ-D16v$NVUmQ0iB05NqS5-jqPWWW [/șcx̘=keڢx%LM24P'M<|Үo{.6MFDF`J#B*Z6w_dK$^#ӥ76ΉѼAl|T*,a&33kS/`:ރ]N> [`B7fEb%w$)`!#tdFLdQ~`NݮI$Kg\EU3 wڍMd)bjAeҘ(jt s|!XlUsחY~ #EglZ)4cAb{qySħ {EVR&#E]JJ6^,wiB V$(4_,PB} RkhF'SUD& $`":%h%QƓ HNXp Bb4DLT1b*܈ujF%: YFEXD(D$zP$1/>^FSH`(r=Hd#}w}?@ tu{rta`t_#  +}F&e!P(Wp~f^l{0.^),MMmH鞳D&ۢf}I_ٳДv=gjYk/g8"7p`Pkj!,azV^xs,DޓEvT<7O N̽\Mg Fs=+Z0Jo׻[Ӭ]Ŏ+OXK*g ѐ$qXaGycN7pDv\6-uAB`&`C"-r+' 8M*6 VXA`T[w}nI'gqwVGJM#@u(k!LHZ%W~}|>4% %R^H *-m !֋W*xI7:Zڣ- H>XUɆ:wԉ>_B~T҄3/)>{#8>#FnXM8W uX?9/\VugRJQȪWJ*EXI3Rd9鸭;=4i$>%u ˑ"CKFE$? 7GHszE%y")b 2"bHY(0,(?Bߙ:sD~5Ekث-5)4VmX"cXu@ D7Ͻ7=Uo9^H;߽gEe&5H3F<"J87 7 d'mD$(nwTԚp#P$GV[Oh8YQkcAOņ,r-JdQAFy;r( ;BN9kzFi!t`X?%ByFKDii%Yy%?hJ@mY1IhhSƠ`Q,@ 9h!&7>5g/ϩ{ 6=#ڹנ;Þ92Oh ݆$@H !y9v`V=@A!Cʋ>vgO# G,YsbDEs旱- N82HLBl\ 2VFG~0*!+l9vAQϒQWDi$#&1D!sxa"ym"=$@$WlYMT_dzRkFvۜ->bʗ䐢/Z JmO>ipio\ЄOJ{HL~=J_]zAix@tk~74A]y36 V2*Dj$|"cnm.dyXؑD墨>Jaw% Xv<&&VݒA՞t+j BZ =v<&}zQZAjXTb.p;g;3lbْA4+ (%.3 #Zkί uUN)V~b:qLrFh2(;FtjkI h*Ԫ$%IJ(^Ԙ1U7S&$pn m$D͕+!yIzJN!cQgqQ![@hem"wkP7-bzm`wcVIdSUcCC,rdHJ{P$i7gӕt6{Y[BsN ^L`(U-K} V.k’ /lMy$'LÆm DXc3A؆bI5i2-ysZJZMQe1g]V+{z NbjK~nC=V<AF$nD`#:6=Xy˛$F[̛w O6U Ӑ 3nzhH(7%!Տ-5.3^8 j7 N4eT)Rl&eFz]cB#:DhfB5EdlZ -,MP¶'a@q"h?g?Y=wIҪMl .@ J`8:W3'2 ԟ&ǒݓ]-RIB@vJd4F,> dTrxl%]k;A4':,nZRDXSO2;Hq\NGi4' [nK*!VC‘V$ʿnjI^2'ijy }HE@[;Qb$N@BjpFx&fF~!17^ 5mHJd~͏>Y!S1(JuX`x,ޢ_zI3(}xz<~g$z/jbàBjf.*"e 5^lc(+%|lC Ah-kW*vd5{JigPbw Q%HN瘛FfN \e Ղ5eo{L)Ћ< Rn8bG~m毽E?P𵥣yg\6^}\8 {mW;1oc~oVqmvfj&) 6j SlV UGv͒Fău!`S(nEA75$#n~ (iEC}IoT[~ҽ3'4UNG0Čӕjb wU򩙇]+eyU$!x3cm:h0 O+"WppTyI[My~5,X6H \ h]0k$WY:PdQmQvX !H;{.Ba(QinASX A߸x}PkH#ƦiۄwDZe,bïƩCSnwUUHݺjU|š5[K6xd0%=b>yOMyp'JfRdI Zz“ٴbY]U*6pL $r5|O)$$;27kU+"n'_}'Nᓳ &n3}O(16b=/uϖA𢳭.Xf˓Ikw罾Y!sgj1eңhQ; Rifp6y5.~=-R_W33t""eh`6汊shҊH=+C򾷧`ynf˟Vi 抢exs/mgKqsp5 U(3h/`qμ,Va̤2n\[H#3w!5J͘Jc.b%WoQqnAc 5;`ФzJ2 mҘ{O^Q%TQGռ#@j4j^Ԋk-$ޠ>󤡴z\5nऋ7D87#BB+NT_E&uGchܯl:O 1%:`,<.7TrR8BdQ *In%Ӎx v3;w+U慛t9 gXm0fTΔ.b=ǚG{RdEnq,<QK|,'ޏ$&mE3ҁ=_-lv`b]T*\Fc 6;7kz:WH9 rMA+bǪjH"F95҇8zN#^d|]I.qq EY*qTo0>t H}GVǹ6A4fI`d Fc"WLgoSk9-K6u0ٽC7bI&!|_.}'bEuKӆ@lqjW") #F< zdYaw6*;lۅ* jl_˫>Xwօ$oKS7"02eiw-{ ZsTd:sMNb3;rbTPCIN pdai]y!7튊l9N3!uH4*o"-X&dsn{˚!F.jZxiw3 :`ڰ5vFH&n]o$Sw3#^Z\-5HbLհɅƝ&XBJՔ=֌n=;>PgEAbnøN2hn|}sWA0ZXim%(# VAxwHa\.GW9ܝ1ʩgAG[J%KꃞRɆwܨa=%Na_fa}A.m?\RH,nuO!Q0Cp gv`vY/IBgml#VըM}C [JjmATCr$kr֣ fEUEsY;e&heY37(!6 Nة+[^˴>Rv:ydq#وd,va(" GB-~ =R_c%'άlXPRhv<ٹ$$9@N6 (j %Nx8DZlD %ʕg/x_vM!c PR3 gqY'Ԟz48~L3eYA$*q_(YN X$7+4NL8Z귙H-$>;e'@-:@"RpNm~&H`֍͛m('-blv}O<{XkcTF-}3.=g'9ZkXQN/Kx[5I@k> 6=zC{ZVI&qth96"/AY 8 Jtu܇hl7iC)l,ݹ|#Lpv ;~4,yըی3cTa3vI9mx3YڌI*hE9gG`k EφSsPߥJ je)lfdM 1AY(Y$o]+_&1J D 1)YnyŲ֥~dlQqi.3e!Ɵm͕crE62I0. V1 iLo{?ib yqcNh%i$kZ[>]"@IT7oc*gPP"{ڨ򝒩pR+z qxFAEo|B4 ٓp̷e'yrW8wd`ZaifZN G3'Hu6ˡB#8lpB4q?9N&wX;sǽ _R>.p_M3"V@< kB v|W&=d7⧆Y}v{^a݈btA5Od$N;EV4֡rJN?hfo$# <:7}j3M YTjS; BIFR"Q֫ P*]G)Q~8Ў'pYfSYVr+vKdM"urU.It$ :I[(DIF UarC5`d3tG 3hpFְ*|W>, 2JB*(6y䗎."92:d8(8ug0)[y!%sDp} +F."NP'iFL Vv۝Z=¸ $IkfLnjs!Zx]ƝOÌ DI{RT >vȌ`%a {by!A)ﲎqwp&UCb)N:+O(&uPLDKIbt}~ھhMqP:r3*JW` ˭[QFċfHEuJW=|3yW ?:HA*Vw O\ϪR ۳f|OGy~R7@i+RBuP[SM8ҡng<-2@ /ES3]v%%]wÓgro *CRIɬ8,ӣV+s;ҌJ{G6#I<:ZXIH>C&4R*Jas:2~B (>ifpp,PۋSdaBcJ2d$TOhF|)8C/&4b&)Ś#S Ȃ=@Y)GDd#[tԺӬYGZY 6.Gԣ3=fZ@'rn *|#&-p}o/g ":kO!rx 7btjt$;Mև_?Lk"'n԰NZF6np]a_?TH\sq웟!u+ 5^^0w<,5IǑJL_&zTs{bR--URE0`_Vsc;k omrG2m綋DJSI2R{3w9A0Ad\,5C=W5q8SQ$8| M"f?;Y6=)UD4=Y lUXQ뎮XY^+{I"ƼSKEwBU6hzચK޸GLW44޵P)1z 1nKO`m\Y~e*ɶ'oqT[~D{kFpeW5D cIZ ȭa]>nmݪ8ڞ-=Ti?fɉe3}ixQA]k6:j5E|<:טI7GݎY"Hͭs1-> Ӧ1k/mÁM[ nZ u`Emqّ!s!)v^x0n.nO ]sӨKn'&!$ у'uHu$՞RlVC5‘1J 4=P4R }i9tIrJ" 1Żv^}Ցy!O \ݛoJ#M]+r]4hgC(1:p&T۵ieX٥Ci _~G ORi%V,9дBqY9yO3hfx>]ߺGH5y^9Tj9WPqlIw̧d*S?J%zۛU0 'hrӛ5Q|_bRWbw9P#d<4[CvCzrDByV9jdn:mf.CHg0sAE[ڪ6d\SOhVxr]l mxcgR밺J]m*R^>]'67`;*/۬FaW"f1my~=qG1Zgۀ{ -msK@HR>s6;zRIۮ^rT;Y fO^9[Per:ܟ㵅w>ﮬa"s49??Rt&ށtڜ4j2<,tV;VFYw]OȞ:yDZyǟuu4;pNfO;^7ytW4m~Z͚ 5 ;ZL=N Քw+l~j9gG [v} ,t卯YUޙW\IZ{X~ձ+L__񵎌e>􎗷;y?PTZ7[ 6}ڞsfn^}. hG8Y::Ðd{O=?śFcAEyd yٯF EₔH=5 6P|]aL,]rq]WD6y'de )'X w^}(^ZY ]O$&3OhOĦ kl7) #lSRH͡P>5x?#/{y%1/OԌܳfx-\ ݋bȹۅ1if 8 {ElF/XuaБ%]x*s\5NNQBv;B/i\ ٓ f7 wTx޹T[ҍW_ѯ΍ 7Y(V+ UT,OڿIT_m:q75q‰Ԑ)M93M%\(C;8&H>OR!?Fؽ@"Y!KRPG Z T[*j6P_(vFNR(Pub㩳4^#l*)MLeJISwQb=݊ E*J키ʢ/B01&N a7eU b\X,omUc]TPl\0=[({Ģ)u~%ǟ7>Q@/v?",nPqRוywN=l\!nMǁG5OHޞ7 nP8u#9@&԰9vϔDMׅۏH쬾NJ (h;O*l @(Eqaf8~H* FmD2 O@ň&H> u2[-[P@#+Lfھ$५͍1T}z{qc0r6Ө*A&v1-{oTU:irш)>j}Wp#$VDN. RAU{b`Uj-Ѭ,%R h#^c3M3F x_bcz{$ ƨjֱ~< nXJ|?= j("bmȅ$6`-紛=|@+}3ђ))"/b _^o'sp۩I6{+[1Yl|s!,'&Wr/tf- lψ¥R+Zb(SI3;O&PKHmsc]1+nO{g{'p<*7^eoz*ԧ6ʲ~f ?5sjUOmd7 FK&@+ogrK1uw [J-C2/mỔrinoaa#7&ΰ]{֙5*Cֲ@ۗwt⧼hZʵIl`(DK5nkمÎs ~O0 /$--[S B:sK>{_/LH˷U3sU-B*{l e xj$ЬBlT)SSHV2ՃFZt;]:{"K:-a? =g?[/?~xyD48=*f㥉 FJ0;-뭊&0-| j.Fxګ{Q(?f9؝Dү"UmX$ꡑV /r/=|bށZ'WwNչ+0<%V Z?T8 tٗtp狞:q63ڦչ'0z LK#wxzےdܟ\\yX ׻ *7*}r5?pT|<;ϸ 'B6>Srԅaaƴ3zXl5WdUwyUYOv=.ԛd _8\O],F2ţ+}{hKD#Vz/,K$l<[Z.+ui]WF F1ȟ:rfMf[JĀ%+O˞kIdj oz?J/+5U56f>L8 cdʗD wdO.׾"MWO>~iA6s(*~Qbe^`JN{ HՋ%m[ݢLHm5FH{5adHeïvT%[t+, zyi[Lc0&yˮ)1S|n//FuҀs}fG pG} է{쥴fnW*9"A Xw37aaGQU @F#lI]~3WFu4M]5Od6\`d Z 'QyetH)]_F|M(TO_{ f8):`)ީl~k"ntSଞS̳*$ щ,TY,bq\:@}!2QhW\zD@Ule yҵ߱j5BJЊpmtuW>01z/̘k4٩hK偼 -2?r"Z``:aX/Kx,3A҃1OvudTϭG  kFXOcKR^o?ۙE YK'G~}bi(}&W 6ܛ7wX$.N&n9 e??^mm뵗['>?U-wï$*h|I2ҾQkA_Kr х.l IŹAN'usO=r;n =i߃Gӳb¬m+K8+{[߱kR8f,yt:QHzC8 Cq7l+ݾ"2ky斾?։8H;!8lB;Q=RO ,}SJ dtty+Z-eyHC_M?'y%4c,BnhD 9^BV`D $&] >J1 ,Iuj{:ѻMH$wsoRZA]pTL 3]W]ߛz\ӄ}!C)$gL#~AL5GIkSls1m>GEYp)`/L]>agFWnkk=sy*ˉz#J-:6o)Zآ~ځ«}evctm;2Su2<"0wlJ!h}ޟƆm~? Nr_en4܃m09t>EA]{xȽ7@UMeP:ůbkr<5n* ~Z &= @Ka~iEOw_Rթ_Dز8fliDzk4ϭhw`IWZv߾R(d3}t1fy}sSv}9?MAio|'zGUФfם_~1xg36lݯxxУs \yI<-uDa9s-ʸ_S;0:}O |D} >H)Sd֙!A$L 4\S;M>)mX cĊ?:R5MXTNB<w+8SxR?`9t3K״}oM%.YPD SE3+4 i߸gj !/[Dj>/So~9>my3hY,78 ھҤ[OmIOA>:f#>s*#fR Q+,؞1zyIuLǢxarE,xѷ}C5)F Ր-F"Kُ1jSxRZO>SۡgBz?}:TdD{Tlx1m!dڹ'b[PK yz_m}D{Ī.aE?|\Y=hl3 Tn3{3afѴcpJ`F͹8Lo*4a[mZ_n#6gQV"N!.U |u+rZ5.98LDFne4k~}:p2پoV0<;]k2k$̦i+5*uM{҃gf?}ߤ$ϊ*1Tָ\'7њԥ7LCo~}֘KOqAGb>i7(U]VYQ4rr.C֔Jg)%*(|m'KZⴤ cuTN+HQ)@ Ԝ$|_W4cv-@Mݦ{ͱxe$z mf#9LO'%{4w({Y`ؠM+}zM *P5 n\9GK//RI˗}z$zfҍK'=g9!,|hCJw2_$fޗNzI)K/Ȫl(ЀA0U.vX{2N& "NRⳀW ' rPGN/P\,"mqd 0R5h@trA3ck,Rrr"Nt.otxA"wv'u:r"t-UOMӹ˳F|̓ǢµQrZsMHI 0^hdRuq`G=y4QRjr iݰwr_M'v~Ҽ_ qNxx`EB}=3m/EĘhz8x4ou&*Tp1.]v;IO;h͏Ȏs^lHt^z_S[` &=1ryc@vgՄUȧK^?;NZްz1c 3Z̒ק+WmطיF.e،3yr!{@)]=i_o-M [$ƒY3! H6w/>[溲)‰_;h![.vG//O! t|l**N,F;z6֕.'=~BtSxț^/_4Nqb>W(' 'Ê+ƒ1yjwM/i ݤ_tjdD]=kWR0$+ŗuG")Y p]dmmt-XsrMF B"xS.풓F=gW-Pl*DžvIWA 959$`7 cl0cW.x+䚉)1NbEᘴ OKV*:̫lbCE@VZzfg0Zb)ؽ$?}ާj/|buTRy( ΣMquaq{]/+'uni6o?B=T܄6Zf.?n%S'C|u5#;օUzɪ'CV |*[H+{}$d2Jl[,d+Rم=a"oZ}_S$e rͫ}3B= :o AJэK'!Fr zm8%W5*r;0 b/Kzn5]{'_s{As8τązmOf 7+d"!IGr޲dfs&b@Q%Vs8﹒q 0fzNI+9S֑͜:]1D=G Y3sz48eItcc}zaJ.htJ*DAiPwcIvs:7)n;,&K!Y?ZEƺ^[V?9$ڷ|:,sҙs y+=,4JTG0A83@ Imd =FtG[mo4q ,rA1 TN5zShzmeZWI hJrЖpJcr@fE3%FpdN=N$Boq̤,̍ e&-hg,u$ H'r֙=HR v~RNGTD$`C~48ʦ2zKjL=RIrnq@b\nU@Z,D P YPc.cZ؏&cJBDfC21 Nd-J Ui;WtA w#xF1I$aF&QtU_܇iסlC wMiڻ?RԩuY|S$HeߓwyP-o_ s){'?։l>&v~%'_<>K`_+X=DVI=]o}+w+:闁C&4$ɉ$W'ƯZ:?eHyH"V {[W*ELeJRBeQ $n&1vZ%HZH6e~E6K4kي5/ϫ%]]ۊj4`Dtc+{ɰ4G^,KtF ?ZTxTA5 _%U}l~ILpEČ-Mk&ְ@zg<\Ru%$K%A itR4*x Q] _y|%1BD f$u!Hյ,߬%g/ P_V1'JTɧifPRgcK.-љbLH@ b蕲T E1ef1t1` :QLb'l.bl.*YTl=UEXvMH`B*>zP"[tXL9LuS,#*>Rb*SfS]W&O Ȣ3;ΰ+%qkKR0tr a4c˳)s\D \%[`QՑ#Iwox5*LU'Aڪem~yƵ@)hfV] /RФտہO& nթeNr^q-A ;ЯlZE kä6D:>HdSiƐX{Pq QKHQi]Q`|R$B5[g sy_]URqw ( PY4>[# +\ lE_?u6OIZWiGmV8IB%kyQP-RV eWN"lTUmݪ336%u7r1<]RHO1I|eIRdJMJeh6EnD}(oKm# gUH:4'ٿ,k?o̖- cGB:[*sAV ~xGVY] Z*RG*G2ˬlVNʪ@jakFa%'h= {! WW+6L%q Q>Z0HAONY1Q}crJ d *8ȭ(Y\9.Xx8Κ\iQoKR|\[$?F_$1B 撶],O$(IHЎ$՘)n{`rIҰSN&MjLoM\N_te%|jxB/,M5i!?sAN2;%|qx0&Hv ne(;%|fx0F~aeȚ;>SDYi?_0pZJN2i& X+Ö'nRWIAYi!r_sM&e_Yi!~Pl?>ss1_l7 ="w$7]FKV">4IC[: G':_i/Jbh%d!_)nDw@vjׂ2IO. e atVaFΥNNjHe'1Vhi+kr1!Ĉ9M@ ww ,9wZ/gfg ' CPàrz,?ݵV7&2`_#Hƿ߂>M@ v]jJ*Y;1$o)Y@`u.o3:2fG*215m*h(+$̙bS!i=飚 55{OlVݜm?y`a`o2 Fl_(ƿIǧVyvHgmp@D;B2W?o2ECƿLƮ?L_=#cL;տ'u qYe7(s !uLnUߢ*7):iS-&jD~{lvNI̤m0#xq!#h; R7 m._B⊿Jy5d̩ MNb$_X~_ BE+UCF:vH8/j[h'GG}5ҿm /ѐ1+⪝hMCە/'.[ /0d)=N]? qߞ^_2ԩn'Z W|,~u/z& #uANLJΰ('hi D-C1E1 ^>nם$;s:IN2 n` @F &'FyP-io!5B 4 lJܜ.W`ڹLߐIsd^ >C98$-61P`}w>OuwW'%hiX̵(T(-\rI#>s%z/2cOFr!qyq7FOt?o]]!g )Htb)S$ܢ( ('] n޽-P>NaҷhGI!Z_@Ij_\0t?<ʧ/>$VĎo"hF?MF=E䲾dJj@8OhQXv=(3>+.H7HH>7muJԡٙ7 2_|Zi_yӣoݟ}͟zJ,Hg<߾dgϿ]o،Q(oIڔ $x%LAE' g<}^ mڀhn|{i/ #Av@22⟽D%n[|eοNz S"<˖"lEiTeP=!tբJ=9Dh>*$9]|~YaєYqXX&MIQ:̬M Ҕl-o/HjPqa BD 1`S]s/ڷu^&iFacA$hc3dBG?Y_zͼ,KUFp Erz;'7S~j=ף;*!/yS?uͼ𯅏3+p/=v'ϜS޿|&xͼw&p?~y|i9'ueG9oDyN^ |_~!u c!mv{BTd&)cx!e}*#_y^)sƎYG߷ƃmge}Xeݏy`tKiS~'ueX Mdl(GI f @4*-ϴ75K<̿! hx@N9)<~ԬaEH@tz bTyy0?kL:F%o4I'a $0 D gbJO0wY(O-qp [}z-n` Ĵ)va*Pc"DU~V~宫obO47'uym{Dblph3Hiә_0'u5#Omƹ00XJd'*`} E;Gyg֍rF5ǔ˧kɜOHPB$XZXܣkιcHFIix|avξ퟾'(JIڋ]G@RY|b]uOS)P({E>ٷpYDLIJ򮚙'c}PmN뾷Uf~|xAw}d4[S:HoHg~$ʹ:҉LJ4 qh!`FgB?H~vvtK*e$%b0!oN?0YFT=|&Vsά}ǀli< <7ωYÌ~Qfݞ6~Z Xsfw?ۈ4^'d 6_ӏ:4vh * ULx i<,< ? mTrnvk@)8MN?m"sN ;s4J\m93`1C3B|L}QYSK+pWLjٗvAs%ۤxxϻ$%cG{/v}2kڇ)A$C@1KWmz E2Qs3MITd'__:Osd0`=t -ȸXR0a(I1PQt, &b!*1|kBj0:t7 [A<|qV`=@;4gRSbam4]=F)*/u5A{ *Cș)!oUZe e_iTbHBo}qV`WC QNX<Ô\VYr VkVI!Q(Y `{dnݰDۭMO|gc؈rIP%ؠ8,<{gޅ)M-Y$ٱS;Z~f4&M< ٛ \` NʿJ!o|Ws6_n{jI t\U$E*oXo-l=@:2NUxu#`(;K `4~o7x^{G,7-b<ԡ2_>ܤbN:3 3o:H&A͟l픴!at,f{@T KT0CKD2,t'<+h *&d>L%|ڍ% ufHo"l ꞵkWeXSEf=nMI /1m_;2uϺjW%UšqYWKFS/@gS-TÙ 8Ǭu)5'aY[b5B(!i0F|T)Ts`rףI ۷+c^jˤ,c^u4FR?ޑKGfUF )?Jjz(`es9ú'w})OIo׃dl(P謂5N@0tS^2P!,Y7%h6l&Âjz?+S,PiFPdY/髐T(٤1gO梓֋{BlVtG e~E݃ʆ= L']eR II=T#I߷=,h6TZaECM5/>D3IJiоǭ$rMO?cs4DqR'qɲfJi?f s҉OMڷt։N]iKTJI[C[{:齺p'O7i:3z-vŻL.IG(V}ܦMSKzmUzDž6e͉boteU9)Se4ҮH޳h<=$[RĊ;BLz*g1 @_wN= Hzdhm̾(SȯF4а#LIk]ȊsPtMs;z抶\lGْ>}p֍p iϊ$;JY=~7i 9i6-mS~y7'%eF Cj;k@LT~CFL&D[ NS^@<[yZ'%NUFU hSIA6fdBǭ\l5pw-muCi׷B:ܽsW"II%%kN2#y_QQ6S?ػtknH#uYl.3y;%])0\WHX\!"* `%{gY Zz%;dRII[bۛ#{Y#," $_c;ZG3IjzZ[+%Kׄo;Z`d jsooSubļ9k6)Ok !Gf*2\_cW:ft'M{Y7,>L~e.PQ49 RKT-)/R@!?[md=H=g+CۏdcQ9^'uQ!21R~ K. b-2T:^?9l_|!$M;XNjWmƢ6.|qR-@#|;Ll7KƓI=ю԰c9 bSΣ9^6oH'lkZ<ֿG @в=\ BFH*AI#$\sq<'1<] 'n4=lV kD4V DISgOO-pk(j,SZk#LW 9HuaP'P6BC~VsM]T߀T-k ^i.$}dռt;4BA~Vc))/6 )m7#\>՚ !\t]TmQ+̾˔ c=;k+[\L^켧 JʑdvS}:z2ȕ"ewAa׳$S`3vy)+5;w?HΨ8)юA[nO R6e$Ż瀷Rښmu]:DJN6[BB`@wZ> }O۰a0-T7) l0УIv{*QNHwbJԛ ZG=Mm"%F5(5Ǻ6%!u[V:?6TRbJ6e sMMiBKa@A|n-[MyX17HnN_4Z M;-f O M%iskkA!O_yh=oGYឪ[xW3b)oleNuغ%g$xq5‹RH[~sE"KA$kN5T&X6{q^nsƎd2AD^ Tki 0a(^Q1^ @k?"D(("«d &-  3ȋxf*y y;?RB\24B| JE3lJxD 7 Y\wS$ּvId&;Hڧ2BjP2:CJH#m@ FtT%{kMg4`0e_! 1%uhjUl~ Θ]흩Yf QI M&[g3N*7t9Ki9:鯵Lf̚a]S<]i ''2\yc;=r75OÎ+&'*g%+ƥ};Όم@sHm{K ϶\ﴽTt}ּ/Ajè۝_+i/͌XV>Fz C,X0XeLA|V8"u2E]зp–k\4.db`l O)F'ݒHV]gR_PnJ?th7L^-eSD MVN^gۼv i0O]a5)'%HS9RWdsW{HVC P˕4-P`4.)LII$^`=y{ 9tI˟nsoVcfA[ uɌyv޴_&6ulZw(h:aMtUdT0$'j !-Ʌch5Ie)&Q ).q`%y>(x];>銟'TmlOWQ[mu[֤z%ŋ:R&&f%hd^~ k o2l}͵~lſRO#JqŹ#(R^ȑ_NƬXXhlK5Iĵ_E JAIU_T#g ښ  -sٯK ﶞ=eיDYˤAbB?gL=cջrș<ױ̢i\ͧEݸs)/H ӪM͍R .x3{kOzvHÊj UdKyKb֖?_ZsO&V[~qLftnWm:6{ْzv}iVV6>_.wt"dt˖,.^{'ۈ7%Y#-v>ͯ?26^ =^`q ڑfLx3_fYXk(ϙMԆ{-M k:]wӟ ىpwxti^v2-Gg Uqunne-dł)$p  b#e“\ѠƤ$nP/-#%" Z—hՔM&몥lnj7cČ'D m[[,|ZMK!&s,0!uE0:%Ij(k}iCu%mVoM[tMZ]VnZ fxfן׺ղKnz7n9x( rf5 [j酮ĕσ+%Ήe-DH+@P~`Y;֓ɐ' xɆm ֬nڌ\X㫈=U4T#xग़:fFNY0%%.waL ʱ7_AEEU)Ђ7 fCoY F`y _a*iݰ-=Gʼ,xD1YG)@N% 9V~@/yí`aUC2,o߈Y%};1fmylФlR>d3Ʉ!>f͒-C7+ؤnhE[ZtX# G<9T{DȸZpO[=F^XQ.S]3 ^؈ߩmP j._di# [#f THxѣFv\@7O8Q:<$:ca/)}XršЇ31 sclu:xSOk6qdgχ&ݷZ$r]:%#يcXfDoljG\mOβv[y2rw"eJDс91cmIL%j{S %aQj؊߽H% oF^Pc.ڟQ7 -[t|=,%!Vh朥y-סi邚Cr઒.5UBCP0@D2 ʷʖל~J'Tgr:K~ԜsW*~f͆ H j\%|XvH Ɍl6q$Xdc./h݌TF0/{_eI[Q>8 hі'q $\/d#7ف7 ׭459^AIWu,)6%jM-vɦȑ`vZݭ?dJb A%QVl1w]/TabZ$&!`N/`ʵ{]Ke8*TپsJF&ߪ MxCF+nr41N-\ѡh KAtp/P0j\r:@67^̶ٺi=i^QEXY>xfْWd0.rdez-%mYMqhuMJL;`|mԱD,2%i%p.ӡ%(4%jzVFhCYD \) *H;k^ש2z+M-~#sAԓn r&A=zG֓dK{(Q|GSl%=u6dƒF,![.]zu"i'g:&-7%5Cp9䮍"zm-ꆬJACqvD9Jpa @Dn;P7U1?А4:!],Z+CMA'N .Q3TJ\Y÷P1o&oc:>ǵb%  "WH-zQֹXuMfOPqZLj>aM$Ej}m-C5dɴ 8)eO( \Ft&dTBf-dN:7<7hUB`[+KX-H= @C9Y lWd P4r Hn"-LVr4WLƴ0Lbh9?4Á3)7%FwS陖. 2`4 *z,rI vD5kpMr f .ՠZp]WM)Dz̞xJ :m E ǚ3]M|?-J.zX5к׷t-`q X}V3?e a5sV 8eĩCS0XT#<#_qJ~O-@1j\30-&#aFW[oPHiuۈ~OLVHI8$I H Hs3 pmh@a#*m@҄O)IY =T9r$D] :#;ϰi@*IdL!2J%,{ woN,#UQ5hxJS>̫GDRA hJ^ٽddaXSEUHG^PM!w@(4bGrc>VQ[%|T+ 5 Om+ Ѩ5Fi7^S*zt0D.p 8J+&<!UmvTQ٭n`#%IDx!,S (Jc 6;/5d1&+u Roa$$I_anz[u]FNf ZE)OdSo|yJ3A0FծxЫ~v]*et`6[ؗ6nfky2 'K[u$ZݮM ZxkjF {7 I(z\r*JqTQL]S7soos:lgh3Q'Іh:ԭ+jكD)鄁iQ]`t½eL؈- "w v3Y,pz'JuCϿ11;;TE&-'M#5m Rv"s32DrB-+ZѲ,!T%z5l"-T;?pgYW)tK,v~:uȨuRdۜs^.Am%An1W2;):H7@{#c6"TcqmIn:i<}^.<:fd$sL(+&b:0{ |nn^]PJ(rL;k ʏҢҭ#pUiĔBS*.Ic^UJHSe>&;T큣 ŭ%>jq ûMrqg;)Zs.2<O[ȝ| JHg l/MZLYsbS%P60r(.:Pn'I#T E1bsNdVd툲50 Bj{hvl(-4m{.A^y:g;XkyhowOYfE٭*"H'M[D+]V82TI>;+57Qt(OlcKs]tۣ&$y{QN!7!\xvJ\hh$K3'…ͫº:ʫ9N"$7Y@i=\[mm>'nSy@[{3NCg̈́X-eiwt9f N)3m'dh p#SwUƾgjMOIndJ0-% 쎍id0#ZYmh@7'@.~dOC90E/QGWjSViV)`Knfʀmj_fT~_`aDɕT|ĵdvndJb\ŸpE3̍Pr;揮$Q*Y N~ x߻=_or)q $otbGM81>}:\Uŭcw tr8pdx@`or;qׅ\~2i kUAE$8 ]Sru^0tmuDUmY_nw̛ [2q*:#T{v-{3p|na@~o9TYл_kJ-HLni JGϷ,)xF6#yЀGPb؜*w(I Rݵj$9w/l5LLZ *'8SgzgUz/Zm;?AIʨfy&]O7D Z&%Ė|!\\;o7brApNFĝ݄Ũ+ 笲i #B Xe_ T5 jmFlq\qNznBOkZh;6lmť4;nԂaY.`ryjBc-kF32Y(!vT94pmq;21zGoF4$CH ٤F'Q+bL(Ljr6BKL(RIpLHpT7͸j:[zd$.]^ /閟![QX)H,RԞCdf0]t5eaЭ]yZ؄pQ-NlYW"dcV輏`qPj1Pٞx铌b%A)1S{*A̒A\F(5J3*jpz[30^u`^]Z9|s&'-H8`MQ+(mL.*r htyP3TZKOr%-ISҲ3ShY͸l l.0"V%HI47_ ʤN&5JP{ʮWg0PP'*|tNNTBet\L@F\C TeJiׯFJkV:KxI %4gl0JpTI}u -77W;,ARaF:e0 -rC ]I:GPD !IxՌL?VAoWvaժQn^⛷kr f- qNWVNQ)z#T-*B)Xb[Pk V9j5 *Y3!nZI+|,;'U NcjW67k¶0<Ŕ`cT6p{Vmnt8H#E*I '*oBfWL@ +q}:= 5q,^g&_ʀ\\Quф)!i9$| LHUG!հET ep6B3?qem}.?Ik%2鰋8 ىwuꘫMBw=e5+;S;C-"'\^D:9.I!ʂ“ [=a;?opD+to^E}iq q$&# p*Q8mjhQq+i@wa\I )D) 6Qt[JƆ6ޥֶU-Y P ZV-0C3Z2_]>Ry:\M15('˫6ӑjbm[Vw: +jko `&6ZQ;|ø=ok ²TiubareHFN/gLm-y)®ihTIPk-IR-O pթ%uK+PՎۉwpIaaP$֠;I,1k&/:c b33un itЭ6=liU}V<{Թga/0}@x ήvm;NZΛJIoӘP_p7t7h7QM8 #K rRzRha9̤~ܶ,Ә8+=LP"3S̡JAw :{w7ңPY-tW",|LʱA`m%sɮb=kimɧD(耱(7#FoL)`ם1|dCytKTj0>S "@W9[82nQ,VɔT(_멙́vj/MЂ*`;!?/d60Ӊ%pJ(-ܮ*`h麝-3jc倂TJDb0'btHn$Di'd"&uAaf Ty됲-ׯ7=I2Tp \Ra,m'ftMus~LI &r"y׵g?܌4jgJn/c5/Τف Q@Af {R^Wi|v!5T;,RCZ cyhJeZ݂&&G0@2X^t bb(w"/ aRI.@k TŔ(˾0ψ &+8@+){ǗxDM$b(  IMm7_7ö(mvrJP뛐T# ^ Jf3YNFHXP3U~7/TU^Ï %LX2^=P+n Z€PwӞbaiB5fFtz|seӨԆLܿiTmЙPM]( 6tv3Ң̗9!<ިX$GG/qElw2MHiɿ:+h6>CjᚚY01?Q[6Wwt-Z0 5(59b+'2_Z،=W/ٛJjhxMMvf:vCV-Rj=Zkfrre ;WOٛJj5Uukm͐Xo\3ڇsKKwRf7UF-E]Fva_TRu7tzn NsG*?P, )qe;.(hwLsZpq*dr`C*՝s5bbU .c_mMg5YcgN$O,V^$II\P%qщON8fΡw!w˃sH 1M钙z ^jJL( pBشrL[xܕg~€g Ay$Pm@]R\V8sͭpxjX,#סV*x[7wL'䦺qC,bV2Sm JznOD"3cv Ch"m::>xV >"$BdM RjW>(DM=V[j!j@ga6Ʌ2$j6!p`v^3߃2! *wҥ?롽۔SOeao̦'4N+oW5QB Vfpfr}!6 `i)IA`lgVqtߜ L-.ߙfK{ff:[~ dLeXqjFs*ؠt&˻`=(d$6wT^=͑u׺,oxy";Ps =0z0PmG]#Rd-7 B/=Z ˂ ?~e;dg`ޝ ͡[Z笃h܀:e>a8`M|Dt G]ӎl\p"G9M r3>A{dDf5"L!kByOgۯj2}?_+9}MfRjF^*փxVrGW 0q B%yԨ^nlP>/o;STHv[bxzb鯙8{9QiA'ғ&+wBHzBxW K_tPBq6GB|RHIԪDPڸX=.<ƴu5kBnMl!aD-t'0#%#¶1:)+< iߩ2;o)@IV 2uSIJED]xsF^hXs9J(y.ק/.SH%H: v|7&&Gz\ajFO+wuO#A]K\-bNBe XyVa]kޘԄ4PZ;h5!Ż3I24%2A<1?oB#C,$ekܱ4! u@1dђ[KiEQ]6h̢% , `D@CѢaGg#0z\ku`Eڑ$|hck+F (NR8o= Hqq\m6ҙئ]DD"q @.qyːJk%}@EHiH -;VN[ENNZofX/֜ʋqȢxmCg;Dԩh879[)GN?Pޗ.p®N0+vڷfJLI_za..( *^' H#(gMślI0ʂ M[G<;,XV&0(Rpu݋D`>KHдm(¬MEv/tLٲZ^Iv}Ǖ(|@3H E-)i7;ᬕe73^-k}gŢUOK4lU0ta:Ҩ5OO@$3P4IcD]C% URD,+@8ͳ%P~k#W`UJ\P2B0 ͌pj!Z}ۯ D5SH#b0$5;xIA5(F {F' f^< MH0d[ rP=IDA;z,H6̔jgٜJӁBU+DT"ZSjrL)B`‘AU Z%fS"U,i9I3 G&\ S '*<{g!(D&sqH`Ц8% @42gMLg#(NKQ6joTcA |pL ZT;16^s4hOޗLB`SH&̟4f[l˃JBGHL`51*!{YO(! 6l>)*-"|EQ!;(&Z*Ss'&V@!WfaO,KPvV:F6u4WYi'v{wX- F$)(* H\Mel{cu%f˝9S- Ek$^@[zdj"CX"M$jSV{ߞהūgE"c7(S#V4 @2JQ%{;OQ#_ j]nER VZ%1wr!0GV(_KX6` "}d-kj.:ۢti#o>c$ IEqjQƀ\@&"w蝻 ϙˡ(ֽiO.9 c TS1~\ 3 QH&ѮTMދU%ASE`#S/ժH+&ObpK1PeC O[Rh T$K6HXu@1sCTl6JqJ#fuod$6t./[K *,~ y?(qJ K|TfRȮ*d\ e6fK0!IP?x+5e+MLlDlehŀ>$T[fTyG J#YcXҸ7- QUJ4 #v>=DN6AI DUc(%ݑR kH*SV$,("#XQ,At"ވVDV@!C΄ 58+X3֐l'UBAH'uT&A J -3 5!V RRn4VX!toڈKj! *I wa\TYIJEszgNd׈FXT$]';Mj\!=?FqI V0ZaAK%a:pkIgy*щB|B#D2 `XȪvDC2!25ȗbɕfS@pD֍.* W ҃LeHa%nE6eW +j@מҁǻ8gNߙ^4 ;UR(ѐVkRɪTÈFb(Io>OݧiAUW"B't)J`xȊI`h;!'ԊwwZdA}! 86'U62?gXABȹ[P] =iXQ؛p7G>}{7)&Z,F ~+){r̮x뱯_j/ĺCfFn]UfsZj'-!Y?ȡhLZXҝ[]˘Z|Lk#'H,S-kz2QG  ָ0HrDo L?Pվ$铂FB$T,81SԀ;Ài$Fʳb<438 *yI9_bXnjj8 ,L#|,%R/Ijp {R̴%)AӐWTMJ ہ Chr: Xyij먍H0[MXӪRﰽ7f$ӚeSj6D]蛎`*,T<-0ړM5,O6!ykԉRwdktb)R$`Y3S'vžP3v)0 PIWpFVh8Q`{=ĘFN ef;z\& ך"kꈧv3IۙR*7 qEo<5àȄd4~[fl;h CUaD si jy!ƜwTxSMHVҭSBinmxZel7Զt%*^jJ#eRE@RhV ɠH BmA48) BV)_ې!O;m_uzYI6ՋK 53jjQgdybOj:܌S [G]_VԩfGbŰgUTݚ'*1O:ek]'0p4&udU'wVbYo;<ؚFju̳l c~MuB6d d?f;Rw;stӸȃ3ɧyufBu˜i<5:#sg[rTܫ)]zI[O~|PyTQ羋QT,NTsW?Y0eIYdB\p@;/Pwa5:5Ojܢ{Zz,k4|C_I4mLXnņ:i S5/fAy ӵ#OW{.f}G}S,@͞WʙHūBeMu㨮V,@`F2WHU{Byf"dUL{ KE}R{J:}o")v}ֹ?wFoS5\4<=R8֥zn~mu?+6$gZs[β|Ӭ !]-eጩf&7&@uVjՖeLU?{ +HeLIs \Ź~LIՌjÆ\NϝRո6b6.UpKIP gK!T/q6מl5G'mZ&y Dd,)\HUY,0H3zLHT6&7x舳ϳkØ WZ89`7MLOXbivjGξ'ur#VlgHHr¦֘6q6*O@yh 2#I29Au-F#3FyXʉ6k1*zZZ]T;~452#Ó5/֍Ԉ B{SF2zZ]~hI\(^u|jmI'ة?u,"((R(rʙIN~Ȍd5%ܜRqؾj `&1(eXhj^On8y1eaA9¸rӨ"MTQVhCMH2pA2E.+X96fĜf諥ZDY"aV$U #n X[2Jj=5󢨌, 2# "|I6 V);\ɒAN5=dg0%}-mB$iU,Y8XjTH$G@W3ҟ"Jt?u&M_&d;<: #2svچq}zCT*AZtijYꆇ1eo\ hxDabJO]$ԴqS7Rjw'  <BΑ"j#smv+UwU֬ܪuj ?Rn5R }sF@hxvY]'QC 04A MR88't!}h?Qi=|F11k^izukkoQYTDIw6ݪ~kf7h[Z|V,{g}L$k;%gm8ё" /APH.M =B\znOS}ߦ(1-w Oc,LF!^gU@-ڒ⺐gyJ9sMjk ONcreX}V"Yp11y< D˙Y/Rm }Ofmw+JAaZ|7wR1o/&$y:j mg3CbTj",ް7;A'QJ1n&$mYyr#H,+*@&C+aÅo ^Hrm/;S oqTBkR7P .5)|0PjlI?J9^%;%eB=jϚ+?ېamv=c?dA۷_gɟʣc>& d`Ń7hj}W&ip9;#ٙc?aFG*Z)]bPT9evR˜UY+I+[fӆ*2 a$H/hqHуSP{i7;>^Y;ʚCઊ>&H̚,urzX X+1tv>!ZLIŅmy8[`unPDsl3qb39we\z~5f$P-4}oDo>~bP,QKS-i7 =~tѥ/K& #5\ $!{/$ŒDnEZ܏f\w)^PPִ؎U8M Ր5]vl՘ak,9=y^PN8?x\}a93\ zZj,ᣁgGM~gk/ le$.IJJ3K^Uo-^#nv^mCa@ICo\G\QDL%˲i  z(3һ/Ȏ?>W}ToQ},YTЉT`W&HG*Te$k$ UnA-oy^ajQ76Fرװ&T1>7gY^Y7}\Ԉ0EymBڸБ9~s>FeC:~ѫ3 []/W) jk,.-i00.ڋo_;w'VmZh?mS9`w:0+ CzDcwQ2R *otvY[8|4 RRul c;,7e/+0 ל776)@Φ+}J`fNBtQbG*!j  HELCo [$D%D)X"3d |&D|)q9uյLE!H"B· D&)'SyWm|A8}RH5ڊiAߦAհcRBHkQk.IeF(^{ ZWLZb#}$gR֐`pLZEgSҗ2-zywT83*ӟK¶SHFʥc2W"#B+`NVQ!hT`JT1.# kp2+1ʽWjx>=C "BJL#UO&=꟫_huz '*Ɔ$xy{v˅=KEE@!7$6XbbvDJbⴉ@hK po{>Tz>=Vǽ9Ov7 kE"ӘiW2_O!%>LUJ4ĊOuxLx5}b.s"=$0JFqL@#4jfl|_/^Kn!"QGS4nNI/*hn+GuK>nADf9H ($Fؼ~Jڋx "'k(rAR)kB,ð3PtXB|@08c~헜*,'Hl&(Y %GU,e.=9(GE8\&I/U0sHU\p-Ad@Z_"NXg͆>+XaYSҙYc4S}" Z˗x+rLP=e.`'fR3h~(3&*]T}b%0Bb"+|?_ EEμȢBfv1y.U~bqjW/bT7눜ˉ9rDylb$j%#!BiKIRpSV {_8/8;!c ߨV%o)T~a~Om>\ 锿=1&Mng e0*ӕZO˿HvxͶeYJ4 ^#>U:[x Zy yvh:YI yyq P _=#o shx?C>ޭZwˏxOO6#$KCUBlY/IV/Y)lYOnKggƆCxOUlc{DGfY6:N ~\աc ǔC /wEbBlKw8vx&s$=Z4+ !6ѻbUޭhT+ ѿJ1X] ~y?M=򒼉lK:y{2I՗̓- "Z0۰_U\MT>ߓ*i\䨫Cu v*T5Ij m]3f!e2$ M:jl_e&9''/K5sUjQ.\T^VW5M}^ r[m q0@aLr,2[ȍ,"\289sw6Iڒb]ms/sEuLI{W3nG \xRx+@kS ?Xguq'H3XèAU@N=H@%-8:φ[bךt "CͳrsޝU > 8qrU,!&e(%^bIJ>l9pε\ g9"bF+Nb)%PuV vbJLH,X *̘ӕ^@g\- W(tR'q=f%d. Ҡ0MUq@<7RU䃼C Q F@,HYW5._Jth)vRI%,D%qCI-R 8"]#H *1TT*d - wc7*ޡ'OՊܚB2$A IDN-lq5C-#Zr@T9DbL*RPHP ^0.#c+jUA_&SܚB2sB!BStNhKpHZ ϢTalCLɢ72bxXx ZyҎOT*P aK@yp u¢IRGu?W%P,rGxkO4Dd 9ik1Z8oC ri&1k u&#rMA.ίLj֭_ T)|E]{ {kzmQ41f; .Cѓգ) 6.nF7>3 rf31L^a~.fm -:9_ڴ[Zecvc>FJYz'ASe@-܇h]If UBu7DBfW+{7XˮomH!KuU[l|%+RA0A즴]*il|k6W7j\aZ*d fGFU7)ӵ)m& KxM qKiЛ a=GnXw #}jRo ZW\`ՄZ_HgaQ&7ѣV&nFeJFsN'e Ep,mI9nI?9~6 $ɘK"x*}HCu{/l4X>Y 0> p *R]l'[zU2Z' 2NŶd?m\;fimzh?'bl4$1bs7: [chik{sGLDݯٺ;7u`Z%U_7%qλ08R1V1T{G :G-8}ewس%= hwO+lW`›}Z$XU[nFN`G[P\|v. +G^F JK]t1'!MҞ˳*+^.'VSm]zƺH)U)m:i) T˓[o@Ӎ<%R\ԘV S @u_,[eY @ E3:c.~O>e@& T5dbA5z9{vqvTf{SA~+$jb31BbΜNB"kĺ)M !)3Hf&򛑏80%Q"@v1SRNRze+AIوAC} cnڴ$A+FPL?~0NGiZ }|?ЉtbfzKd^U^6ЅB3R[lIɡ ('O9t{Z3SN:I$)䘵T)a:ax̦K'UqܨX& *^}Dj.-O?ڍuw^̒-Uu'3…T răV?t_M91˟s>FfC+m[wm-YU],wFJY<v^}$A'%@Xs Z gLD񓡥3[ =B[qPķuҨ<]ǛJwh.֎ kc 5wJ ə-u~kZq.Cy_ITyRHS"쵥5Ueӿ롛7k)+x@#;t0 IB%D7NoVe^ANRM~0A4*skXWNRJ(́{Uى)I$ Nu[+ ~LkOXVPNLoT?/{~6LF~e9SDq h jl-(;)SkW`9(>ݳ<`=OzlM?7A:6&r+.g}ŒtҺ<, ^տfՋ bT!WFG b(meP`a&n$#=1 OO C*#A¤3@;7snpdk*x d E2!yjCI'i&ިw@CQzmD$C6g6okճH!%72o!HcfnkTquYR4^s3\JX7? 8$nLdc%@#cX(eidp:݋a"IJ8֚A')cNJv6Q @f2:D"fV: ħM"BЅ $ $_V+G;'ؐV=Ei AAJ] 'ie JHٻ~@,R'B--iBPl]6L.F"&ELDL'B'w|Dc ~`>!Klۿ3" 8T2)$ DĤB GJlڿFJju QuTdE')!"Ij"Q*8T)"J,Q*/("i$ ?*r0? [h (`8)Sf&HsÍ\] IZOg9}Pzec߼SzuF4q;gwCP!#quͯ'OFи.] %Y4n.0*%8РzT&-?%bǸ?3^9l6tft=cεj$z??,%Z1rRH_9lkڭϺ_c E/ R nAޙb_S+rx}_I'9VFRė.E )y A*('\)OQO; JhV%Dda ';mEdgKak"@IVmlKe}/aJNo𫎢;&%  b,`ﴎ-f_~~.6C ?+_^nt&2WVQUK͜~WMua*e<+}=d(f,Ӌy3IPٚvkGS HK{v7AD"#zC~qnbQViI'n}njU 5%*PT6ua,>=OTi* ,'D:NU$f-j֎hJX!0O:TM'IT bo$.jn҃RHҞ0!uTetRhtްr2/>2pi %A~3Du +_:UAWD% "}'}] M6 ?}uor:L*Kʟi r<0w2BkMr<y/IvZIf󪖞Rro~txva'xG}%E3 88@mѾo=L2`'yͯVvc#]ouw{ {$Q>;B:RK~6C=!Q XU p,n!K6#6{qo )?IiZJ*ʼ txxNVֻAʵ56%@8#xwN4IUpE=I*JIQfTR7*#x]{G~zMkAw>ݹ%H;C!|i:iI [NЍ jG QTя 28=e "DH i9f=N/z$NS7}r]_oGk#]|-ogz 7F2ztц.[$\.)D %Z,Qp VTFkHb ŘJ~E}];@\rb['T?CI)cE[βoE#sw.I#-XRI ԿlJek޽$SeR*AhWdZD6^G'T/RVO0%5L/[."@|@U':{*>\H/wܭB~49p0DUETBq?M ԛ$;i_Ż ŗ^Jg_>.K㙥6bwF_BEYEm@z揣iGf=!aCTWXe[I fy#'ΝwhmRHE3Km4rNz٣40R"~+Ԯvi_6vۤ(Бy ЬZ8 T [X yCudKh:B.B 뷬fT]Z+i `TboXp&K+ Tz+TR_J:?p;/K@׈q/͸% s*b@4m Ћ@-0SHj)Pjka!b QuF2L \4I"@k qmQEP*@3;|  JrSU )RLZ]\N1\)7/k{ѓZrME(9&$vUCLQM=DQBD8 .X( \0iмCMm|EYX<28eQ*I(XsIsG]&CK&&l;H"ϫGFW 0!ug&%IIjGI?*s䫧-n_śT2d>x_+v/#{Բ e?wG1G̛^TQar`fvI~,`_P`_mF3Yœ-2T t?]#|$lgH7Em^vykHtur{Yn a/g#M_\{kேp?N\93w]~ӲI'n1%֗Nz_xF:ya(_'mWNןq&šG~Ϫ1ke׀C~J^,;QR2[?_ëT,҆`}-h~co__Z68πFv:1l$qQkD]k]4d)* VxOjSõ20)]r]Pu`‰wtwFNw :M *+y A E;pVP`ܢ\QQcs3tlS!0 DG/_ពjGBN'? ?LA~. D4C+?\ oOs(6<V%ʟl!r|>ґ"7%pb"szG#! J+"sRAٝ- F@/(@+ 12Vx~>woLߺ`X8y>43qE L?|U6g>QSEֽ?|+nk.MEXa@e=G+Yy%a9ҿסE̠'ꨉ8u7yz#.5%_-LtB8G P |ϻߣ$='̻h2~Tݜ_7 *zf>d=i]%YQ4N,\ Gڿh{/ήtIcԃ]_]%IϷs ]^%&S;u7 X{'OͶy'$%}J9Kfp;ב##ɹUF3{~(pۋrɣ w}:g'_?ڤ,?P˰1I!Vy-;{n^kZϱͿ3"gwȗTZ:7/sk 2 zXi VSD Ae@" ?_sG7 *cBHLQ]TxGk_~*vuA=='@iLˆ&7^ ڏ5.>fijjԾJqh "i)i~VR,RYPՀF\9xu`ef)|[n]?6I'aRjv)o8~}|f"uwÁ;%IK.RH\Cr˻!)MMv%!%@@'`65EZv`d/yF.ǎ%0,®O,lhG}})R}T%:Q"P)m+T)F2GƕͦHGT2 eL`A*4Oc5(V]|Ʒߍ"1ࠠ˞}FBMI\:;κ~y}%s =c&\Lj^?C4 NխD3ĽRPݡxŎx9p⃾^޵# =M1ViZ0B0,9mn]|“Hy<8"&npH#"[J8Ke_[]n]ǒ#XsABO(Z)G#KGo‰{=vzUJFJ,w@]ҧR& HG'~jˤmņqfxJBڳ>s2Oo[OBf/FLvnHOf۝4c筓^8ޘ:=ń)%}ܶ ~,UO5kO:쥍"=U_vffy+tm5w=儐LKjܴ *5#쿫~tmu47ڇ{"gͯVBniv۝?3^W~Œbs.%-?|[~BN~f3>7#L{X`ԣ)nvvങo qk!+tw UV +(TЁrt#+n܁!ӗfTFō4bbQ !%d]\qιgve8]r2cH+&ъ.'3a5M_3C̙my;A=.c˵@wOCK[[.V$Q&@VdViT9z^"֡H_!mH\\3.rߦnGi6C-w ӏ FI$64U xFF$MKT;7!U|V/8[IȢ|75C1y3;&r {>:^N*jg%0^ )5DԷs' }"/N'C[ɓӤcѡ:*n:-N5Jpے?>OpnX͢ka'q>S>jmoxOj;J6>Yv:tq5ŹsYkjlue¨@PlAG' fx>y+r1icXoB ڦچO)GүU+&DB߃eԔ<%ѫa*Ĝ`_p]Fsz90@#ňCkSmv\̫+xS9OוKv(2bNCJgKS5Cpf 7el0تD<O>>S=hjs!הwNWXN?b9pGL1Dw`+v+܅5iC3@dRa1٨WcԎx̯ %jK\FpG A$oLۋ8vm}r B` BxO~h{e~U}DQ ,Aq!<k%2>>2@(e8+YRdxRl@hZC@O}f,=Qsfb@yMm`N}FzդA8:9S_r&l׮r0[Z$\t,}f+e$B*)-_Gz@uWHwʌ+ͨIjc^) qWt 5oJX%82rgnPF{+5ToАSW $b8:"*ie[k>FzIV baSY-kM.. fdP__-=Fâ`8+3SX=,' *屹oWd4%znZ ڧPqH4R>qܴzO=` lLv]i NW&d\; }fo^Fcg2s8p󓶨{lX W^ o0e4IY[G$uN\0k,:3d9aa !\ N5yj$r YS m]ݤ $muf !aLf̹omhM)Go#fQ7_<>4DՀI.4-׏x O賓{aMgi7vϳ[W=\ͧB[O*\|>{_n{4̕rٿs~ɴnOoH%MYqYϝ.>i]xn r~o>0\ߌL~O!ff;d*E_iAX<w7:W8:ƑE0C;zlҟNTҔw㐌77gU>s>~h{[ί1nɪ bDm>dgzwQ&Y2ֿS Ҵzth,7_Zۙ 7uVP#Ó>/ w&P?^{wzv=s?{*f2=Ŕ6kXl1b4h jKgVSt>bŰ4A/ pⱦRҭ-48& @X$Y;c].oKIX:+ܚ&\UdsH0p2QqN5 !ѧtW洦ZΫH)di*Xk,T4ݾ;I6ZBfu,v7`)h"@Ȯ3>aۑ|GZؘIT IJ,`Eg(wlW!g+͸^1FZ# $ZW? ~ ύ{Hx߂q՜?~UG]nCכq7?^Q2ڼINc 1AøJv)j6P1|ӯ?äW283m^FmĿ=3>T#6 *Lکo5n>c_{$"''H)Lm(*XY7l"K?4.xQF6o\"%1r= 2]ޢ;iIDbPT`vNhSeu3sYJ(؋Ee ?YNL\b; Őnu;4ln=[m⒞ƗxȤ=zϿy#4ȽݦLY$@U_U qdF:eTJFKݔSHA: ZjX#Yd \\;]ZYϨv.%. :-`1ryDg4B@ypݺG͜_oyA%TlzB/JMe\,<`P+$%O;:3&⎘#r}]څE3otܤ#uZr:W3-2+kFs:OYSA p}yg&NCqD뻧syeBwV׫N c u4$*)kkTErrV8:@ j8afd =YHj A 蠅u+jU ) o7i-E圜u{k-%xQn5(@I\jwZlqmýJ,6BbQ{wa1pM;p(ҬY*,Bv~5r{s!7^l30%\k0?m,Cl>\p dc?H&᠈JP$}I)P8`YJ bh}%sE|?aLYe &$qgCٜy8fքLݪ GG9)ǰRO& ҎŲ *U] |EgNuG; ̙p1+7k̼DۏxFt{V/~ >y1.\y|:3u6rОqڋ>+~%ڪdC4?G,I"|oISNܭӣ(Q F(&PhU0[\Ҋo=!k Id{#3BGT 6$HWRZiɫH4~ k ᖗmH!X"eOA`ؼ7P|AN 1\/)$+>jcBJ wP|}^ tI gɌdĤ'کO7c3OWnO( <CC MǏL?LHdGx8-:ƩjMt;7Ikz|Ѥ;n_ Z򩛫51@jH@[ێ3"q56絴fD'pOtU'56qH3_e; r=wfU&ݥp_jq~׷+Wnޱ=ξp7X3y`t*6n{7Mx_jvu|i$p-SWWt&mh&!q]?/۽⋃q+KN=\7@UL ]w+:k:MzZK[?;HOlB Oռ_{ /jcQ|VИLn٠9uہى4Zm.4ҦW~_b7ږK7-I${tWGV֢%Zuǭ|couf3V?fl09۽,^ytЪȬ{NMQws 9XJ 7;|llt.O2qC06Ǫ7;ۻH^o~ŇQnl["K7=|ńF^]v+!bQ^lZY#^|6];vKʯ'u'[, wk~{uYv$ oRa3ʷwâ:\|f~K-ȴ@ KN~d(';:+v a{L)+YR{PwmLowյO*BB5cmwb٭N6$Pw]cSD&ۃ>8SRQ t)(朻*(D9lG.1ګP +`pwߪU|<ӵ</Wɾ &9 c3zG't b<'{ ~+ ]4RКwܰg?PYw]|>7h72?"p jdlsX<ƴAwTZΩӀ6B#EM1PV^ 5QW[A'n T E ${R,3 D8hPjlCPCTRuoq0H&`YAbk<{F|q I|IĉጀLG R5IZ : ԼXqpϟfux$Py-dI 8K`iۚx/q-;mo%qq@S'qR#$lN:^wOPc4 L"N;h1-ŎyZ4 CHovVP3 Oڰv)ɣ=ϴ@X;ܝcBCS)m)eHt۷Ui؜TP&q"' ü󳼵&EZ;PR qЍ_~IbO*/Z1-{J1a0}޸ &NG>,5;qm;_F SoM-r+mDk=0NLmUa߼؎Sz?n)ey=ŊF-i ʲy=@j'E *MvxpqK\3%u>y1qgf֤Ⱥ5|ߔ%im4Uk~e8琤1A|O|fo}bxy}~Vu&|&@CVjY L.lgDQX {W]=[Oruxa\>^VXvg(h=j<F/q!VjKP*Oo=YͿJµրۏzҥ} &|BlM9{ZI҄/@!eɪٷzR*3{gEV:0.outma6mfH1FEǕ̦]8e=,$?QrbtۼNI 3N>  gR٣) l\=n; XOt FM͆^;u}雁=wBvVLHL[vckEqaAܩKwRUIt.N<# FY t׺JdV0!kxKcQJ#m˳hJ%Q$Tm_ۿYO27:#7\ ; :.h0x qLLJϸŗd-ɧCp,HtbYykC$Vȑ,><,HO:biAڪ"%2x$"6W8cNw}b,@ĐИ<_%=HL et3zV,58O+ 8(C E#-,C TRDl-h Q,*]ܩ Ɋ 0>0M{m>WjP3L(&M nH@oEĨyQ5;bW']P joZk{p.pTHfADzSUJ&ҀIT ykI h,g{߄ )(u4cN5Zx[W"I(CF5fIvH}GɥmEUsIC}#ڏ q)#-GWdgVfkUbn:`r"0#aRB hi̅ȓMŕ^mJ3moAG)o)\iыU;`pKhtUu=fd7)[H?*Y/{ וPϝL'OZ4͊}-gG'IE{6.l9?SXie^i \oSPJp)T0YX?\Ha?Qiu9;Bua; i|US૞nvΝ}/A}nDԘ*n-|ZH8#;3s)[o\=++T` ^lliӊ|ڄϲ{~6F ѩ CVEFRōO )715(-ќS){H9f9K'¡pB'GvQQ8h PJ,T RP~Q'up` TI}1"d b6%I0r@Q!"H3ڟ|薦rd(T X I H ARJU8XjC1E̦/ 4qyKK$7vZ3.*־vNCEu&:3b\Z#Tۅ"nYdOxr-g| .Wa]H L蓨(UْLjw"\V 52ܴx0KPMū lI[;eXRy3KQS3D͸;d2ϖ51@ d* w .PD3wu ޞ9b)olU1hP(0"8UoXkaMcu:n 9Hl[I gO:)sڛ]?Tmu_. RHgc5e7oys1 ,Phi)7nz6ĥk;HqnS6Rdۜ1ѱ P?mIcg}*{&تChpΊCG.󒠠dh^dz_4"P?ݑ;=L9nz]VUCwYJk^I'U8IV`X!5r.Г(ҝHV8W@E5Lj.iB4 MW^[3٢bt5.(Us$@Q15ʄ(WbӬ;Åm1-9bAk*jĦƊ+ERe)1hh@buD0$a& feP֐xCFkܒ{C) Zb|'({ CD |low گ jH\Kitl,ɥIizF.k} N(ڝ4r1 "d5<H0alg.:` ÿ >ka?H2V {сvWI:HqTc\vD3= ۻ/f:6Tfj&7Obu~5Ք]-#w{6?3B'j>-uoe(51?^4yu?O:οxy{R?l?AOm$poiwVj晽fMooVEK{c7RgìnEX:%`~]4[SS-?3{K2gvh~ Ѥ~ %{rKS& tӊ56ωOQ+>㢳ojgB*ìj\;g6ziw 9G.| fjz;ߢ-Td= +m9W/׋/c^) K?Y-T3.$7>MYxC_/# YO$h|z-ܷ4w.O3&g+8x^cќ^dE8 h"Rjw./DZ+WjO _IcTvp `7ZFBXJRu*j;,^ƔEK^cvvU=VTbDJp^z-;8^|cwĄAIn(< H%/ƭuLxߔ[I |z݆x@QW4+٥ޡ"; N{ 9PH*lUMI+{Bdd'efM-W{K "F_$'dcs=3S=#<%s,'^y3;ēeP7!F?)6pܻid$V~Ϫl3H,b7FE##^i#;QS] )<ӽ|'BE-JI,SSQwt0tQARL" Sd0p8LPUBXa*bIa k~Pr44gO^%5*4bCyTʝ6[{u3#9kh#dt̃ --iq55gӆ N*^h9 T"*0 [~\X!Fl2 p'eґƒ䬐9LIjEh *,$q/ӒD R0B'g[MA|k)d5$%c;^fldDDAfX%4JYk}DfWpH@A%J"q*D&qsxD*i1gzK&gE EaI%6lt텟6!$U`릑fFι&bmIZ)XH]uGfMH4Zqi=':ޤR@M$hۺ<nD-Tn-*@֖B5%[\\fE;V)D[Fa )fyo<.u>;VԯքLj4ixcqs-硙>5 $KV^+S[vݺȓS*XmJ@g7 *A¥-ck$~bAnm@f}h9g2wp,PbZ=mV+Bd:ı?LqtUea6ΆCxQl#卌0X^6Qr:{j-;(H~K "f <0e<=$5(5Rו)xB`iILOsĤ{"J$3% xMh xDnE4ᢤ DZL[" @sh dV '1D (P8dDb(IVZ/L┐GCpuš~8aA#qc4J0H 8*O~c eBrQQW b 7j}_#^Kdv^9Z}t.kOwfy ڑu=kN[!zǵYsT䥺dL>kIJ/:;||0Ni =4͐h5 "fPƙ\uVgKf;m`a]sn_cŻ\Zr= p2ZBx҄tJUR;)P~ BOJy5-5If&|ʄtX0GDԔQvx%ub-S*&gHO.;)k7K`;x=|FQroHĻSS*ʰqOzl!U6f^yd!iE T ,]g ͝fo_M8~::ʢ@Vi'@XݶA3ZbQk^=1i}.D}M5$"$3al6x^蘬KJW$]Դ2D{uC:0)I"\dlZLPbei$K:%rlř02m+4ReeDTc8IeT^{E2k8:ggm4+OVx '|x:GcϛaQZa@~UuŹk}RZx*1nVE'jxbRq{q nZZJO3O,[]5KM'})F\chcbGg>@k.Wzl@cNwطz{?px@Dγ@tP"DfGwwv}RߍYeP:ܫ$* "Ib¹$^H'WEUqU#<@`W 64wtp7@н@S76dIVMl9g/0E %7no_!5IV eQ8Ur֔HhG8$5{!mj9IwfKF\O5ƽ]tU|R5ذH՘ǀtmFۑS}O̜[T:  ou[ . .*[\xֻOfvoiQ%]oS*5ѲO-D Y58˓SD׫e .B-놵} [;uG{o:_Hߚ[z{άs9y3|ۋiխez]?-'/PxI{ӵ>C =lȲCվRj;hfn򪟈lwp/~3$`k.fgsS^aqtVҎ]ABP0fieWvͷmhY#5|09j-vQE8OI#a"Q6ҭuוvhƜ2=D<91qL#Ѵ-::TcA=.`z!*ËUy0ni̞&Bj-G[ɖzk0WH 'j3O0u6]G.)UY C\]Zv#stBA%IS" YOԑ66)ks.6ҹ&i$6JAeɖV[e,۰_d&T&"nqM; NBH) Y%3ذlYf eGՎd5[LufSQ;%D60 qQ]*.w| -K˅STpYٓtN35ZC %\S;%NTY^S" eFQm궼ɟ)kZ%>rR6RT'dEY4%l2$ru٬ 4:bprVQ(X|I.]@-iVjҨ{ eJkU\M Ch(cHny~uo=;6~+qjKQIa{E. dFB@I%#DPS=J0-A,Tl"`W:TaEXxCx8M}흓ȹ2be dOL =lxbF μ$$8LW"Bl=?W;܃ ؜(Ƞ1@Nu]vIvZO!aFM֎wsfGF ` !H[ 9e$d%΀M2plU L&ݶ'RI廾aEuhQ`4 4 %,@th{l*u- 0 w1q vKIa|ʺYh"Rv <ȷSYɥVӕ=u!J#9 5a\͖Sܵ1dŽ%Tx鷝$y<[}DJ}1֮cWCj mtP:urΎ[s$íQYfN _,0Ex}䩃Ćw@^hj)SϚLY=8(GIaE)$6#)]!/7Qx.G۷ӐE'2K5S~I֜9 yJ8PB7~1 =;ߝmGD<@*ZN0Qz$x2<x_ݹ~( I>V}8C<,w"'B/htRJSTpX E , mmMJiz^Հ 5^kq1b(JDMCtM8)}n܅LHۜ#w^l"$:JǷ^9;i?a3wLwW[|x.ر_$]dgzG ?I%HkXsxO  [;5qpT=7%:`R AJXq"DۈBeE,±o=+%'{戙gA@>4uB뀓 Uƅ6{;oCY(JEDrb\!bSZy:E]m?y]I 2*1AܜqL^G\%ޡ?QS77ŭnjد*_Xe2˼pV;n dSL"$IlB4H{ <'e~oXrk|j(6*']'AS䤑d<]eT[uL<$yFaB6]l/z½Ozp?Ṷy4RP\-ȈhF >1N3oOf=W޹C! (Z؞YE6v 4Vt+%;ӌ4~l|kИ`NCta7Pa؞RȚhJUN3N>g TZ9 n@  w Î.p6"p#!)W*faUsIG4( HI ݽŔWd} \G"^\4&=aWWjMrN/m]o9J>A ,K: (GUDL+I>S@@yv/jg22Wt%#S CfK;-{TP0lSq1.U%IEY_[pu 9f٨4yELk+.Cҩ3g2 h)} !Y؆>˝ys]rHlP tҵ߸F@glR~H=2j2 @Hs[$E)oQj2Sms㞎<d9@ #K~5|L<2/^wǾ~4 aS}e01B .S={R{lu"bO+ҡkí'=O]b)ўv\Ma qP 2u١vqYI?/d*_^\k 6Ϋ<`(Hl4*&JkñTY[ZSW7:caGvio1_ue_`Pg)#5tW|~dU<x݊GͣkyGroXa1%ɦlnF9=="0R_㥕5>rAڀGlmxF׬g WaUXP3]MR,try=2RQ &T9QZo/p@Jq/A&qzGʌ2:R CaQ޶Cƪ7bG*|-* A$fZNSj39at_),HKԯ z VVC,y.-XIJ`3HI gZQ C>ȩS@,xf$^ $4W:;A4c@h(^ϩV%L'QtG-g@va#)w QRl/ иA:+[7%]"ڳgYBJ-jf'-$2FAF%;FghPh0b̔pѵVB55TrH,z2oȬ< W7kdx8R^:8z H(Fhn[3䉅a0Ɔ;dFm3BmY_ɐ:|6&НYGDx[bD{K!*EF@Rjk0)QrOnN"0٭*̷$=dKa2C<|j0Dk `HNV[S<Ⱦ;)D"@5'){*R!& 1lE5%Aj^2 ~|D@33GL溒Cl(0Aa!":p&3j|tҔ3W&Cc805(I!n*(ZMhy.WB'pU8پ4 Q z4(X*̭{iRe+MV;7=-ʫeషpO ;rBGL?`Y c (IH2j'_(%/Wa_T-#ѵQ%ϲpbUC&hid0xv~pb)FM*&NxZDAj e(C7 WUux׸E=_n,$[u>UKC'@\Z1, n#>ax!7&r_nt:ڧ֦7`(w\|f&&g;ғᠭ"D&j 9Eǣ'UoK_6%39K%Ҟn[-T3X &'hΕ%dՑaEۮ<:Y%} g`$)L#1qG+gP:wx3tEYƵ vT 0`)WxjP5 G3b#OOncOt󖐭u'Jf#^Ǔ`)Cy<"eyrךjA3MuzYrP֛7 Fvyvx2+v "enҤb6Ot\_׏YQM׼mc!M)2gmVgv@ 1~-fXfmԗJvk7[Ҹ34ȎE:j;Y;_)?4rx8=G&Y Yz4;aԪ&fb@  qR\d5;^vz]FHBTvS0e!):Zx fckW )oaC hLh@ZRP 95Ν?nLc.e庬ng yy2sMզ DHK67! Qh4ڻeL63ld٦ۺ~:֯--˹= FlWa-lBUi((6ZE&C-ڠ8Er -82Q@UٱtnaDs8 X<< !Y3<œ5#>VSWX60Le8F=] ġ0+oH"R=,&"; jõpB6hکuL4D3JuI/{ao$*p1y"),rbuxnGpQ48 CƟKxMZ4$86 "@2Ic"B"'\TvB9X/=[B2FO(;=C }N9 XrOps4Ic4uZf&G&6gN0M0F^nòY<22 i5ƐzxJ#f/@UaSLkpHN3?CZ@H(1 z-nN:LVI"XqaFFx"C"=AKڽc0^)e,8aVi$qL!$稂eA ^ r pӐxdfڌ<=D`uQ͚êGЍJR^#_u]|}{OȀfu-THlS '̼/jJzg/{o~~Oe+ 5y.Kߛӎ`s1\- VS'ʥq&(_% Q] KLDjqgDՔ<_ޜݑ 0TDbJ tV!J 8Xy5,,2RvwlHEh|(%>!݇s2R%5<b%mvx{uGK)`'6ʖ~eo+RE8,:^sNzh}"`jeo!\(gb%R-?MbHML UۆBYҜ 1j*Aİ$.oĥzOMtt5gA ,>b(fa&B *4UF}1M ǭ=dR<H@@zj2mbTCp$-`Z KM -m++6a  FI >Jr$KylȜ93-\O :5eNP\P QΛu= e%pH⤇2N,m],Xڐ8%EEŕ/\WI1u9{ʎ:^ˍTVg6n%EUpC;&wXB6KT!S^#-xvTMNI/N,Rɠzl5*ٯzk? nA2g%^H*bzZa'uuF#W=tvͨ}ƝUp[3~j߾xm)&04*]IO M&6aabe*ql_Qba3XEbI0\[ #Ш@,EY7Ֆ7:t +@H3H|+=\UxIEVN="ORwٔӠ44D )R$ Ɉ@TFtD0>!z);?d)-> gh .<%?Jy1; \Vޮ}ab߷=WVh;fq'0./o/HCtL)!H^BBfсt6._S'c~"EUp,3_B%HNHKYD ,i.ҭn6,%6؂ǜQR ),'vAe$>Ud]c8` `ڙŅ(RIs^RzIjC{sUGoZӁIOTiDBꭈG2\ȫV &L pZ1}OфܝؗE ~lDhdKl-'>"YE)  d"93U}2*QoA%( Ʉ%c+  p) rKLկrT 0"kqF+"+xV [8ZW$|B)h[TVսDRVNM$A틴"m*HR!0&dF k""e*ئ" E8) Dlus&ʲv1+;#gpPbt ^kSOF:#?ګQaEѯ_BBFr]b7f f7VJk`{':ݓt>{M F3[q!Y$WgkGk?wb)hᮨsY_KDPZ/:F@ƞU7>Q+%+FJtujJP.S~HlX@8\fPlY7]@dh3"/]uB`gRJRY:bb%[&U1Ž흒 atX!v :(#Ւt& p\sט3pXP6֞xeBGBUL2Ѳ ACϠo[9a~$b TvcQI]Ԍ<`Fȹ `%͆I K:;9"MgzlʘK7lΙ)Ӑܺڪ+!2>y{8"74!pwSt_s& A< ]S1!fB@ $e'5%R𐝁 3S_rbQag#S`j8 $}Pذ{UmX@a^Hxe-g :PNR&X RHA%bرI6/f*ʔ-ݤ=ꐴaB*V+(e[Qc,:R-['l<˜ G_H0ˈ 10H2J7VuZ8θR !%N_<sK~W2eߠ3R78ָ+)©#EξNTBPdz缼z]PK*.P>aMU6&Hdըی3cPDaؠ2}d|Jd}`H|fcxGBʟ'*pn̬O^C#w _xY,ɑXw*|NPuS_8 O޺&C ~*뮿˻I_ڼ J"YijUu|luq4(uνwWRڌF9'Gd\#詪k؊Z͋Snp?:I:ol>zѲyqZj/8I#sYŽ ^߮F -^\[*i^G4Þk$O.Dl*B6tg#x洌,T64?Ƙ0jې!Ȼq_Lه&ZV%azRP8߮j<̱|+. (#)2o#R}7 ;9+HSʴ"'lti s (' nԍwxOY){SW6 "5ގ&7"jUt-D\*-F@*{RY5t/$t#y伡G6% J L*tn=w֍5']]/]{nQe6mU:?{%/[O@PdzluJ*_ǐx,dgAS6.\DQ鼷X/;ŵ)$ASF pöWX8tޛ_"fqpߋ'4TnuzP>w躹[蹂R s1Y!ˣ%dMd[snc|Q` 0 ];{2遛RHh3%R O%f_)#B7rzޒgY^ia{K}+vy] H-Idc"^T\4H6TاR =tj<}M>u)$*(INXQP t"Qi7thuK?cW|r3{G}&}t_(&}Q/33S.݃~1օ +5 K&Bwxx{*<'h"_Q=Xo/Fb%e7]2M%&I7׳S}yyJP "'F;ɠRXHhUd( (/6[JLH+w׮#])z QAN_rwGqֵHM#\j.13 (ĩ0J}b7rqXNR|o؉V:MS\'藬daF͝W7bS%_iBxܶ'`wCvY` pNeOfofiN&Z计 渨J\sC`qWr/..l9C4 gm$:Qƴa w+ G4"{z[Bc!$ Kd *ءM?p/a%d\ǻӄ&l; URE*U{H,pӳ,QQ ґmSvѣ쫎"ԘT]f* ISqr84USލ={ӭj=m7\EGam%p0 ほ$ze{^s*uBBsG_tS,Z`UVmdv3j{݂^1Z#Xc&t?us !d߷P M-0N%spRcN_ `Xm&3E2ڌC|#5TRsLgG{"7?O\W@p Y.=I*ArН] U`z H |o&jWDQ{sb(8b|;  !/-ZF QhD S"SOhUZ:NV?)C&qƬ.M^QV@%dAwrcEaf٥N Hl%)BC!4sDA[7KZ + %(EG ̀4Rg |a̮)Σ9!L?}K\)[' u .^^{[/6gvI'i~󲳙`nqMVaqizxϦżW>z&KK5N1 A~ѽ|ёtRqluub} @PhPߦMb&-67[D{?y IyvvZޒktXJQMr L`[]p͊9SϰJ>g5~iGp#'I7TI2R\;GrV-gu|Э?O#@Pd\58$; HYj`A]~y`E9o!NF+>w'| $[O)7ĤJaarFY}EqΕkB;S>^G~D'j K›T(aurHl\`Zɞxԉ'0T9*49HQ;Qf mMeV--\B)S@rۥC,LH~֞8n6Ѽre5Fqg얖wTKl=rL#zpїSI$?: $+7\E2")lLE@Sϯ!_S 0MJGR+YJUHbU E8Rjzbag.@'q >l!Y5bSx̞ߝ=17< &Ќ"UԷҰPLظ|1Jƈba %H:/b2OZ8*SH 9%h`3rEJ-5mhg&+ȁ hlqm9-w;.=Sl M=w5{nKIF UWMMUc fRdR "^Hny o& Rjk)ŀ,%\c@[Pd"PM6%O+|XN2EXRRwg/l0DΨ&q~afL JxЧ$!iq!LjD , J':I ͭ^3`~ז"c АdTZ j qCwzQՔNiS+5- G1ɱdAg'C:qrڥNyō4> UkfWq|tu=-tQ:ׂ#P#2($AF 3+\A}l86Iv jK@ %.ĒE9X 9 (o#N B` %: # ށU26F~zMp]Xə R&5 ,ZJxZͼ|Mby=!J^ S+3$-SmUᔄ b)嚭0#^)Z~AMT% eC6Zq*) T>m6!Y\VEiAO֩$ iY:g`%[I6B D@D@ϩJ*DWGy-޵#MV,OnQ K 15ReYJ?N@f݄[*.qVԢ,ebbV,Q$139I=V`^Mw` )bY[oll c81%T_)O`o7I&x&8Z`LQAK=hDU}pXŧ:s"%K4 ͐:r&o|1(GK)|Ц̉JACF.F[WNMlMDn& tAEOlLjGتGMD!&0 G&>Mw4u2x=PDRY3RPYCB5g" {&!>r`D06$U J-w1Hu2ԩiqTm*|$fAE66"Yx`Ѻpb"u % %ʤi̴H')0ϨԵi(  6ZceAQ2+.V:t&x"}FUUe5^"D%çۨy Am=xF"lVJDEpVx7u+<7TFZ a6%%+FY9FfIfn. s/$۝_Ȏ& i :"\ŗnI $hyz+νѣ<>1}OܪK$PU4UxlXH|P AHMn-L-?P]sNCyahPJz6!@8ɛ:som?CR+>ʩ8 s/('gr٧׺roq{wުq/=C_ ?'uFbĸKR|-{9]>Mn8m')ySedxD Pb1!ЌZrCS' RlhȉaH a`$2 DV5gn@6XhNiAeh878)Q.-e(-DfFn %m (f XՀC*"Mn><`؇xAUReԍH >V g8ڵLwLiT=r 1IRŠs42"{z&dQ7PmZxqW 3A&IѺ+@"0A"|3@^GyH424)0(!XGAc$uuyp#}8rL` ?4rͩVLg~'!,{b҉ XAjʌuhIru(N̎fӞ*Fnc2ǀh%aP22t( %$z$zG n/]MY@2H c@T=иx <\tɏ+8un8*h,!,Zvu~HHp~#K#!RʰMFpWS9'0X$rbB~ "=qC"Ne8i2.P0@͵nz?q+ 0 .I4VTCˁ6^;2"V7d {茘!i=}42r&4!H|I7Vv~umM=J[߰?'d"vO%.J$ !N.ؓFxၮ'k\CV3%թ^#5VKA| .E?6Kei$U"BD+}3Iu00@X1Frj}/SHWxd}Ԗwz[YZp-T,.Xfb(v*T:;T t;rGd9ϸT ;mTn:3.\y{}MtZKH}z-mm}l]g%:Uuک 2 a_۫ &W'Eq/ݮ&pHo&d^;X3Z]#%)kqڻw{-{IܞCq@ oL13/ͿlF܂oj% vk&ַ w6$ą9x/ʶM_NɔU?*(BOĽxlײvUfWvSE")eLyֆչ2qî/B7鬬i%H&Mӧ7Uݲ`|SĈ4e,t"ƃ2hs0"1m;|z!&RЈ6["UG 8g9ZN:Wt˿7Ws'S B\M%mZIo5';,tUPPj0ZJ)) &DLtKN6^NjDZٽ"weYPDMPũFx?b[MraE<|G>TtZJIAs3$%ՁRͮk/`˥^UpN|tGr~4R;X-S4K_JڦiMW̽)7G9k$EMLH_8u+9raᥝb坢OJ'E%# |M -&FQu"UU NZlU~Obii酵ddċ@y8b$g-|dե> !5C;gE 8I!gaKX"#kb3xt8i'* .WU ųQ4'BfY1g.c@_#PɸzBr V\=0e<~u}ë Ja^h2m@*}8I^8'Ic6xbav pGކQ$h+9q$I), g = ND!Bbq0f8V' [?K%㮉6R~v$m.oO̲ڄUdVidjҌZD_ΘٷݜtᗊcۿqA={ %!h)F7IiDf#tҜT?X(t,/j÷NIbQ,7Wk%bO±}^xX`L^؄'ʗB`U~{IhbyƙU`%a`X$ i8cxOa$KO[yȹTd 9  [ E438 c- + hGf4ZR%%IYβ$$_#] zDz5gar%AR$K&RipT[ !Z*Z38UOt q'%&5ihI21* &;Af3˙SzǚP))D6Gc-V`0i_(.TMq^p^lc3C5>l v)M+T,qin|0ؼնyhLb Ui]6Mmhhi! fi +yFʯ9hn^.KԖE"!Z*ayH B`%pԫ,DF^rR岐T~ jEPol3# 7Z]UE0Bx5fHKX*ϕ[WDؘȦ A7˭n/B7jͻOm#%լ+堁ύ? Ylt+!bFm _;c T3SR~0`QI ,P-vqLJ昰R 5  &kk@|TR+t2E5|ϳAR&8Y~B7>.O0#5B;ZhblE+YM}p/{G"5<մ;a?l1Q\| &V.,kDBSfJS䃲e".u^UbaNVYn4uR=I1ƘCqF@# E-ܴ5&%+vd`EB%6VcE &R!Y9VqV eHBŰ2 6{QL-dF8b t$)$ QN4"ajwDj) 3#p|t@D28bfI%E'b •H"Q T1%fv2uYe>NyrF`ϐEfMgbɯtyv/g XU`ԌSD<2VA:f̟ȇ$כXc@A𑕍QրDG9'2hL gv #DECAhMO,3z>"XiwC5H+6 0[lS*|Uy]=iųQ?Y*zOm*'~uᣴL,J1VvMzJܥ7|kXnRJ3m\HVB*^vhc1 "kXggG a 1G|qUEY%a`Pt+ߔT#=Wދӆ rt!3ɠ+>|;mmGU30nguD6RbzHHR`@t#P 3T:rīZ^s$_D6pC~ Q*~2L5:l20zp"a .__H#&8tF)0CH^`nF":Aj{ ímͱ'I "R3kgWttK_^znkkʞ`xHQYdN+d[<%#xOIgHxGCǥs`tfOmV?tOL}>|bNZvFu?wGǰq6ɂh &fFNRIUg sRp,sӺ0 t,;g2EJxP^:2ތm:|JA"@T ,L48& Ϲo8OSnjӿƥ y״LDz7@!'+\^qhHd4&4^wH9;`i7I%|ǀctGl3䴺I)KW"k{l !MFՀZ2]=|g+mʳэ|Űݟӱ>[*daFQT[;Y1ܹdc68 Ζ޾b\8.ǧw*9@&H  7ꙶw}Tu"IHAT3 LOz& szX+,>F ƩPa Z$ k;=mvFǢ B@wk/OXc n8I[sc ȏ; m-%DQeV$C YZ pW t˱aJdGFnfzs>* 1rXP6NCO^IA0'Sd>'!SEʞ79s{oD 3pE-H;LV9G`B*03%oBy`9Kz1|+zI-ʱ.գC2E_[B2ƇvU.sf缵-T"ۡ0n7oWO郧~v{H3>yo60bՀg{ǰͼPь,hiM=ɭ}ug&R^V4`olŦ.?1`6xɪy]\}.WuQ䶦â{iU7ЖլMroy]A—(4HD\~KpbcF%HCac$QPa\#7<ȄИzlQ>b2& db90}6j3BR01ً^[3Md2<}2D"3i FHE u.fgdSD b-O!Q,[ *B,c[?~aq {l6\2AA@USbr(L& mQTD/ڧLks+$I#& 8v#x00w~2Q|oۘ?{>Mٓ&~%D~5+Vt/w};Z<{{*齺NB~@b 0Ͼ@CQuA@`%-pZjo'X^0R$oqH`ޕo(tI> FZR$i>]c'1}!yW̷1(f|^6PY2@B%"J$ҧlkq]4!M7~MxS'~l${s=!! "8N&^^/͸ ,/xbC \O|g 2bNe}ׂ΄3V,7ǹ9O1Q0F՜d"'(pEz^ke{y*Xx!$hͿGaZ1!JAh˞Y4d\)Z*BxvIԶo4~Cr,E)Kumװ#v7lT͍n=@ۖKȇlQr)S>D$AR;޻g2ʞ:pV⵵7<4҃KW%"#*" ,K)քSrcl2y7S~~2/tuٶmUӾjw˝l!$Jܢ?4ʸƻ1Llki>y>/↛ m%rJ葢'Fk3?ڃfvF !j@VhTXpV]sZfKO># j˂rwQ.` yJLw+K0b'T^BfۧzQ ˙v^,Q!%4p|u =F O*ze6R\T lQQQ,$$ɰ;I'[o0BWbf(8-M 0JxG랶z[E=+Wo57U'|OCm(6r8G{!8ơ$iS NL؈HO$iJ /H ;|" XCh6~RnlI=ӷo>ܭ7>K΍ȭ&A.LrP^{𰯽U񅭍Ni%?ڜ[f&E3R/}( |z8?:xnY#щ$2UõXt6^Ϭ>(:ZSyC= -<>.3\򠯉.q`%l_o.ea("EC# #{f"ԳJCF VA .@ Ptt^| Uf4a24 ʳ*][oU!{eH 5GՈ^Zk6X0G'$HJC c2w3íϤ{^ZtK66P֛{84QQO~ ug>t+ }Byr8v,TF^VvTsm=pgFew9[J2c'>ITaiRiRxmL >6wbqlևITEO) P5U}:&lH7ogaw;zZ)Tn%Ƴ 8DlνvO?YJY`=}lD[d)6բ#%Q,Ub3l:6ߌ]m7/jjwCzD,QYyD-4_o)Λ3lvie֝E=5u5DDIe#Z-{Ӟzr/_=oFڹgmxVVkw=TUCT"Ξ?};GG꙯LOZvV)iDKZi HBC1R,{gǫ RerM\mAmyŏ!C]Lު6&D݈E/RX\Z@h]w<ֲ'thŞ1Gk!Gh{3L#j8Ь%KӚ Q# JM(R\QYKllDCwqg/8IԉeAk2nhx%yZYraq%IvR\aDvSf$p87G,;eTKZdƈ[2X5F!zxâZ$]܎a7)u^hmw\zcsohK4H~bPK@"D=IB<բR*0J28BGRw !7{8|rT/{,D:?]Z5 =ؕpo<[Γgt3d)ikԳxl9c>{gLO:)Tģ_+f?<8\mIgc51.֮ϕ_{ک(sj4ݑ8Q-ؠE.ͩTC5;>˂ii'L_F֎ dҚ3lPuh5i|{  eH .D%,B3T e;$^,+6 М~tIVאͥG9 ̟!AŽ $T)BT,Ann$ VjB ,HѢkҤadށ%P k!JPaP.U"*Ec.FXvKϔPSL]W$Z)yrkCNcZ5G(E^!c' pH۱3Jʃyܫ'lKy9 nm6EA:eP_pkQDQ)/G  w %JH9b%S{c 25IWDjRO%]$ Hě"}?V2SH֍~_7{ߟd6#1DX$YSF~6)NG67fvE -hcRJx3^}O&.@A ׍V}wulh UUڻtTȥX7zBIhK 1KFW` 5:]#ݻ/vjh+ &]v2ܯ6a[BN/h7)PY[u4 5v5oZt*yNI{9'@t}v8ـ]8ϝ3RSHf:nq&]n;sr9C,E?HrU~Uf)MEt{}ga]:kx"#''7<+BKo1w K^;n>>5_v;k&1~{M9C|0%5J!B6 Fg9L_qeV㝛C6"lWjwj{fYH.ր ,$ƹOX_kLI&nS^"6N D\%8MZnV;R.{u%C吘pU>I*#PtW~YӴ6#}\Q0L~V}2DU]< <ǤN:X8Z}-Q%O$r7Y63^9 ׮r>pc*?7:[j"K<z4J4z"K7R=8;>KJ4HY Nge9VݵlQyw롼d DOU,Qc@/7_fT\TkOlǿ3خ nnt5HUߡ7Ggߓ?}+Q ,Kj`D CTo˛γu ZFL 8-vxbVnnmi,rtVH ,bX8Wv* ٹQϰ[P($[t_-7t 1s1Vٖ$Tc[WHg YR:I͒}A}:er>c&1NŗL Iuxpo]KH}oHP͈jO%]$^i>ܪ9?D'f/Z6~81|:i9HO+|ù.: xm3vL>@FO^JInXe\g>H+3&0@g1h+U"Wqsfҡ3wP3"lhӔF fXV4M<^r{K%}yT]iƬ{as(~hU(E?5_;^1,eT<u?}um mrs1d4VSq$Een0B;|~.QBsr7!D EJcE_\K.Оm^_,u${{ͥ&sA &*:݆Py;xFyQ)oNue:R 1F6u(>kg Æ2*$G)c74ݓpfPZ5Rq,_"XȰPNn҂ٟkbbjOC2qPc U:MMq;O|ptӚ!ߨyƍ7؇8m%Wm}mjS2M23 HU  E*sϪ➌$/hʴ%Q}XX5Vp|gj=hHTbUHƨZ7+J ^b)I9L>ګ,nuƉ=IO8{t;@N|l1aM9}壙o|nh|2 JԢY@-1w6zs|^0+nt]X$s?=X |=޲ĺұ$k\|=JJKfzAԙiař)\HDcAۖD}#@nfi/]tgÇ6,T@8LIso4E:MK?نXXP{clZ,OMυVmOGjܯ- cҕsO7"-?7^>SRpn@ DTA#2;p_or֝qͿ\{p:LG7wݟ߰4IBGp!f[DD (0+v&-|h^0vVRK]`70JiP&{2bЭAĬםNIDwFP.ؖ^_۬xur{Ԥ9>@,{T˺kk97i{(@Vvf_ujYi@![yN;mo^ړ^퇯(B B@H@),X# |mΞޭ" e:+Kb#$94p3`Ol-pD#tW`&ch2Q -+Fg΍F1B @@7GX! V\fhReP'ORX,6Fe~gqDVQt_V/e;eah៩>%;{G~v)եξ/54ߞ=كz/ީfiVV_zlЏ0jmqg(#ocgרLf%Q-+ CNM|4\T<\.h, u})OW=gg)GFzud^$$ }Fy@uQm==L;@tGػHI_')B7Cf|@'!9f5\@h/6IkWRGu ;HwI&Wܤ0MGe;I.%9+yxtg ޝUS*E&'N[;c/n~۟>_z|H/C= (Tt th%|R1#^ztso(/ݙ)'%#V@)ϕfg%b=MM~fXÃ'Xƈ'*)XWJYОԾgLa LG3=1+.3Kǩn8tA+"$=$mݵ?= n~"/v97;MIGt[AaR+L=|7;<ë2z(<,E0m[<+Q8NtQU1m/J4oQT Iƒ 0\=Qs43)֪xM㹷(*S@LhrN$.TnMѺS,n>y9>*mz ș0g"L`fѐpdٳQIg_z߱#WkuufBK:e0YIXP5td$Eڿ^{뻂jĉNkzTpp*{6 Nհ` `0frɥ2+b޲".2%"3:Q PuijK4+K70.` і4&%'aq[3YnXN\zk/cwaXor܍fGW`PGelhQFݶuш99 E˃*W IXo@L7,Mx,3 nu;eVy>3|u#+ug-gGe7J F֯?P [mʹ@Yi';M궃acv&O _w@c3i6Vߦoli|˒백^0=soVd˶sR{55hɞMۑXq BY1ҳH&Z~[#o/6M?H4r_oEME1;03jAb&Zke`M hEC\ u/랑ŘE XIdsz~d>epLhe\ov9+945ihgʹ֕Hnw!?sO ldW 9{| ''\UVf|NӖoS SG<g_vw{1D-OF#xwon&|KF˓="k{qѹv|>)[SIO vcy@A&û#遐=? "~ع${׀.$عDqEh^.. _pMHB$+Gx(O"AgDM.t -M(j8KnC)V^՚01 fR”w|yͲ(Ea(Ae `UgtzLّS/kv+|=|Fiu8ڞ(W^lvo4m)$ lRqN']Iվ/о;GRy?9),qɓsO~`3Ucf:'9@۾w b23H!sd'Z3]2Fξ3 QN%7*V9 't[ћU$C7LQ1Z[*0v…E3ov. gŢD:{l jHI9w%QF̜=[ )f"ƵxMUDwW5giFs`&-C3_ q;4<zcPZ D&$Blɩ_N bPJ/#~,Pyйv<R|#nqfh8*zl)15 X=gJ  Pݱo~a<'4)T9v5L͍W"g 1( 閴N#~V&!¶2*ʼeɴ9N^h{|v0,YӯheC^zs:AvyʪD$K+&FڊҮJ?VX)W (g/ln{#NpM& r;7{!nP ᝞"2$!2|@A ٫S.o9l)`bV@4*ApDS$8EnܬvMV~uMMUͷMU d_'tÚæc6챦A. JfǢf:\2SRPJGsà :M*;Iwy=4o I6'8tF܀VZ|Q-)TJ`X+So1"y9a'2/Z4f] %`j N0+={~*ҩSYn -N6w8߶{'x094K Ѐ`>aWj*&'"? U& Xdi K3 - FnɆgCDD pt>C^ 4p(m*\έ5ِ PC\pYJDNHKLOWƥpɮR? [2ڜdPQ)P=x$J[42^Va>ECOe[LSZ * 0Q@W" FWM=PJiDvTR%VC .&)"FUJ A!xnPTqGr&e#|+$PS`*Tn@,&-?)5T5&Q0r†);xXjsK̴ڕ/;>4PΫQ#,!qlL!E#EQ㛵<./o9cš⪕@ ۡkJ0?V; 5'ZJ@fH (fU:!+N 4YrP`e1PP22LQlbZMwD|M&hF&^QAȓ^an>)rua@T*I& MB5`[|G4=nZ YI]ggQDt@R]ZӶ&>3~r!v"%JB i_|nnw-I#4|*QmŘZ>tW 7疸OT%;ZVC`m:s1u\(fSvZV^}2M 5+F8"૴MU71^X3i8_orymjINɒz-46fc/SV)=O:[jsR`s^RdZj@eA1n퉾s 'Ew\ʅig/O;Z2rߦ`խI5͠?eiTTC>8WV"Ġ ބ~/32Э9"$yꦅXh*An u3k%p/9U[Tځ/D/C8߾;~w{lr {;>{gq*ޮ?L'Of Ԓ`75aQ: T$4%(=u UW8OI tGބ (DsOw]&>WW yk8C&tyFYRhbCi'=Lډ7'SE3UCjj"Q,P%{ws[$SΩ ji@R>GaIc|Ǐh/is[*`5(Uj/|۟aĘ'e$ D4\#H`>$D$Ԫx#I7j}u_3>CY3|$!F,boQH4i r/s-D,Ffv8'_3&⮘cg;LxmoadТ1Aſqv~Lq/NH1j`/56oK_z)I@Oo]zK0Z]Vxe', %NtQ[["X0֨@43(#<-N҄&\1/WJO@ (! Gm407&$ŴjbX͆bYzOJ`g<;1FNRJ(*K5C)`)xُ#QNWw N`L k2P q; +؍VPeA)ieZJ NGjWr4wt!jOs95z[t%ZH7R@{*2քD"ʉ;cMinBδe@0Γ$RM,I,9 4݂em ]El[I?AB4ɜt$ XxF ~ƳRͪiҰYpsVcmx!"1ȂlAhSVK'[Q%lc@]LQ+f4[l^q(NRCEukQ84 }uN @(LT@ t!9 @Do\ U&^"AfӘ)]@Oϟ'amF_JP#L$Z@c3ytW&ua@fO38뚫;ݍ?gϧ\ _I!wJFFYV6J0Tӕ8p+wůui۾eeh=cljH&i*UGZ};EQj֐5++9/$;S_z`:iRx,j! !qD}c/L?nԎniՕ"7=yZkJz+.]`׹@ɟGIZŲKѶ)Yw~onYq>Loy` -7lzyg]E:n]I8z͆YqB`eg f!ڢ;ʓgךm*oFVDLq!pp!IySKL50Q+-#6,ASķ3.ga9&#c.XwW_6r/oH{"Tѫ_p`!ieLX0CrŤoj\eFʩc Nb 0m4n9UfhrNpeD{ȓi}wj_7 D]GN[1wX9s}cJ|Xz #̈09k*ϫA7(u25ňdBdӛ慚O*wOBSzLЌ8?`gZ9{2ϳ4w: w̮/*-d-bf yHP{K Rht/l?SN:n'{\UO#h+[#ԡ~V8[+u؎^ )Z~v"֧f} $v:@".xSg P!B`x?xcfY^8sґz'Ptʊ>|l{Y$j ͋R{n*n1+5,E#ScУU3Lj+@rQfc@S4UDJQxk7OupLTHk<L~,VP%Nq& Tjun' @&`N}tqL(AV<'?YҦ0$XLϙٳLO+e~20hYf/4g(>MgqM k 3V,y3lIan`P+Q֡%-MP}]/ut;`s}O& :5 Cv-?R8W[}*XE+!ղL 1q bIT$ ȠR ZBAŧj3YdefeR=F5j%%E;"k`@^^C,&Rduhާnϭ7!=fSUQ5!A(7FIe"?m||) hek>ߢ aSk}#_>{Wz^WRi' fP @8:IbQws .kk&ejuΫ£-â><6U-Phe*⣱6IΤ]E'xx"^4Adqx S@EMYKb55[3JZR ;(}@Ce /a$0 '% %[JbKݑƊl%(4KDJ| T'llxuWC &K}ACSd&D]>j}I_1( %UY_hj2*wuWŖp |.o`"Ɂ35ɩW{WKs -5ΜšDqRp`P$%$,IԮ+@ MяDb5Uu"*(ѯt􎧫G̽,( ) _~SE, %#nGz* k^ɶA5u_ez\{U]uV|U^olMXY)㚥͚9w=)c'b +\ubi*X4i,_>jx ݶ't\4Ν빂#~ R?&]#jE 5ĉ|D&:""ZQQ?6@4`+cIxW1M6(w7318ܬETxL^ ,KpQƤ5ELP)8q UcCb5+) "B 'SX U\R[nW>R5VdZK~ ~et*uBκQM Ҿ8ّ-4RЀsV .Br^^$ldVD#l.S(P\&"MXFmjT"tAs ' Un,.z]tB] d6aEtuli⪅æ9Yyt [uH*ŗtYsY9䭶$]eqzP*$Y rupפl݃"pdEhpg( ෰LÅȲLkb3GN„e:h!(`Dƒ^0XE2;]^<F}%*cգYԐؐ:2cI4kIo36aCt&B u<3BN5[xwܤդatj7iF"&$k_C ab7#[MN-]mW4.S@Z Tqt$ ntpjH}Vj; n$׮MҼIy@=_6u<[H!S>HhXk a@s45Ĝ_|nk3m4+AbVΜOZZ9ZB$ 5&}͋N8s If9YeG$(XXUᨤh͚UF}p4g/d2$CmJT~|3kM,a"[9woi=g+!'ZRs#F  &%TEL(LM7z}ow4'm1t x>38Ňۓ|hU n!$Dǫc?Pfn"`t|= A [1G;XX$!: ^l=jKtJLiϛ|ұ} ʼ siS4` T:N%{/|m]X=LF"{=V8eF"| 2"A=X&W ]ژ焤NѬ)ŔZо1iM^u)r/"=xKD^YF&Nk|[AWDc>%KSƳVOgGMz!TB~̹.E}>&!=>j}Iڽc.r$f{l8&uFfF|am&L$ٖ9xNO8Nn eҬw5$B2#\C5?-G'ktLk cʝ*Vm@_.9`X 5RD9ɝDҴ rg_0M t<{Ą<^I~3x2Lqnؚ2i镕TFIX*)Uqr;{,rnGyg O5eMFJ&k̪MUnrn)R 2qavY/jؗD/}e4nXI[\vtn||~v`b44BO]Uv^2[\Gw"3H΀,h܀RL?3mb+G#+ɉjGg 73 %jLȉB8)A?۳9`T+> |e]&2A@> ̃,n}ڭI(5,2:|an ZWqz /I#= (^ve_2B҉^ {a|qKiWw@O A9QMZLޓ#iPQ'^lDY`%k.DcG“+Gō\¥܅YAa3,vQ/o|XB9q=t>ko4wv\tPVO+Z:{7LNqsug4&'N*Bք#2p 6?*Qu˲#ڳ۴[Xq֫irAb?ii_a/묶;W <&PcՍ"{ t|IqIg Z(YqZw6sD"jeWڴy9(N˛\HP@if]:[r z<{TkUZx}ٴN:tqg2f@%/%²cM^H4ܔ6&AXkL"[N"mhDdȐ_7Tkh@B!ILT6Мw~?6T7hlV6bq$/V*SfƔ96|p%W87r[/x' sVYI\xOgb+Ya[vŬV)E lI)FmN6kM"eôd7$5[ɊZNu.ծ4U;窢xZ9˕-]wYժL2> SHr4awg)+Fc9ފ!XIᬣ^._6/Wlng 0ʴ@Zt7֣O [XO 7`!jXU:| BdD0)vI2;=v_Ien=&; 7%7ϸFm:tՇ_sE-[EhރiL8ԝ6|p/=]ݹjQի)ڹɑnLgvkJi<&L{\ug;)6:M^jEfd2rt@>M .!-Zi6݉Ϩuܹ}r yU=W,qyM@ NL6ԜOH In[23J@€l}:T ف=?%Z0Ӊaq\1FƸuvREvl:[m gf6IRZQ_djJ7E<]l_q"B,vrpY>/ɳ-oRc%TBi=3-t(\tMjWe"NaGF p^P̰RfbkzXLY b5bU&AIDȆ2H'Y$6rhI GJTeQx4mtՔ5!3qQB~;%.] 2:$^&@#~&eAǽ:yk:In٦^ NV5M'QSuF`bL.Uh1yl;`z>'v 9˝/{;fj{l~+a$ Bw~<,|~5$@tw1]ko7I՛~kf[.'@rN^w\;,[pX>}3i ^دr6|jՖ%յƭxBf&M0UyϏ+=`qCIbwv*WJCDƻoiNc䓤;ykZ=Vb;+HYc?*%i=c)0+,y=6 kZ &?|FErfn& LL 跸(4Q-_;P|ILXhrdY4Gq͟|_@eXGB6[c5J]X$Bm\ٚ"ofEMծYH ) O /1ԋ3~2[D4 \U (i"wKDB@$N-_`H!tk k:7g:&p2[ [ح٪)3!4Ac #t;~A1pXmbsf>k8uvHFLy`qҼI WS'C%[h͌ 'SJQ[(`ŖT `L %H:No"VMqnY.)֘] k4aN#~wՒa%$,. DZcJ]tFܛUd>s_ٛ@^jjR5jȀQ z=🰋m 0c9unO:<<"b"MMӌ^CNM%䚨B|R)5'H%FH#57iyLi^Ph2 .Ʀ$8m@#r={#T`TL;某pTJRU}{_Ͼ6;籟[a;"0 ->(+dMlM#ʼnpFlH`wIne%nL H70"ehv9oـ3XKeAM 4";L6d;?;Opj7 u5A wxgN)k:M3?b*8jHՏl}G!+Zd1< ,:`>X139ODSfFj46̨341h Gq&8@.OI: U 0htd LGK0bр@&vJIE t2剩3:3ZT%aIҵI5@V"а" feAI"[6 ݾ"$ \CM gAd5鸟stfK)`b L^IrƄV׽ i,$Hp!uGqZ@x|BSs\v C8}"~?&f=qWaf f@WmˏcOi߼qWuZFמo,@%Yz6jt .N:u-=% ¹3hVyr.l.am̖LyR@+yy׼ z1hS^q-ZC#Ȁ:-kkiSu.5Q˷`v|_[6YG2Mc A1J&;->U:!zJ$\-ByYlƨmnjWHZA+ZB4ip3o .!l< mjS]?J󮟣{xbsO0\葃?)m%~?cxj%;b"5TjXӚ1BcYAkF8FeBj0VR#:E;֪PR eVAZ $#(FtR`4r$`oazE#qx[.INpk^/$dvP jVvlspͨ'XD:9^]1W;lR98"*d:c +DGR4#wmf&ؽ&ȳw*S:Llؿjg4QSuHz"KLU;nC[i\10[}ayҟc"0L@4%ՠFXHJ351rp[ U1;GMS4 (Pmk0 eܾ͊L3Zc MJ#u;1(G~+Kt-;c"g&Wb;D;ݚVJV@7 2YBIRDs2ovt:!ȶ=%sst.X6Y2G&,ش %db.2tcT0%43So$}A=TmݿHU,G_ZrD VpkP3j=!t'%A U(V8hivs8?= %=° 9da )mЕ)ǭM?heCD96*cF l _9"U4 ])bQ ͅU+gH@$ w9#`5jT}bюU;5@L(BG1TX&PEmWD%iALh.Cx'XR?F Qj) RSZV)oO jJ!JWaDL<;~%빑X-狓̦~'`Q>s'5OE7g_~!鋧g$D(bv^dFgX4OY䌂r(- $jvѻK{k}] pgvO/=![O+52 u/+:1 GX UK$[E/%:2ΝIn,#<'`X5gKk(e&ΌH]pm͝-"GWq1>1/e]('-2tP f - đOf#&tHמN׮Ӫs_چƕFڊ힁C-g0O;phRll p޳=*{a$o+]8 "Mf&XFҊ y=Wש-YcdI36QlIQ.jrZ PƲ'c_qJ#Icc,]՜f e=p7z3z)[b28}?礓uac| ZMo_՝$P 24@hi6SO1a1SdV% tBn@h"PI08^mr5]л6[Z+E>Rp(P%CG>QMjrԂԷsHz? a5ǯu{W]_LIPغ!$c/,|Pk6'Llli`: *E(|K4FDW΅!݆VH^ FAWPx%8:%tQvS/=LY bTRmx 2HB36#h |Ӊ"F7ꝷ͎(sOhsXOlZ eL?R>cӖV).j@u{nydDz_x^V[oe.y~ᾫC"ߐȪ|N]]$D:gmg~ F緣YИlo&3v6ԉ+fv[zamHܙ7oXspÕ+ӵWMtD_tzU&&G3lhZY2DO"qҾ}'>iS:i_x (R>}l7]*~B-61A! w'|> 9W+*  AA&CܾSq5j WLHT*Nttxtz|JwuwQũ2,3zo !6n RIqg2H,gX뻐/ba,"<*l/̥<iuAIpZ̫ ".ൔc % #]B(_\jR@t#!d/}ϭC~VVct~'۸ z|fYԷOٻwK'掘 )ۛ\|q/Y(&: !wx~颷O Kr0#p ZNhiԘp[ZU"˗b!j@B2rjX 8t9|&1VP\ZwA 8 N ŠWiXH idՑ@w=oT+/up/^DTI+qn9iںu.MR#h>y/zw·8x`Y2ɪ L-'NA//v' nV%Hoӝe3dSr[lgrt$qZѿےS6fRRTM.6L8Lc[uvwKlIiԞŹ=AQĥ#P3Ɛܳ6p kW]~7%in Hh(&2TH 1N1ZP"_[8@ 3aet_iAj&7A[LM;k,=fݻ1M˫4?ڱ:Iօ&(57 gcY1S5X H#dIjahҮ=:u N=un|:U0!䀧,Uo~F76N*7SW 8U{H,ꄸQ'Td$Ei]Jé TD5$trU7Ҍs<)TmqPi殺!l&YF݂F|7:F9|#t"x[ք% ViVzX[Ş}<*߾q9:J2 <|A=9 Ī.&4%-wv)s|S%x#N-rCcňk|#NSߦ G&Cu aRS_(*2LG5x"Xz@Zu&!s 50| &-S&rdSͫABB%--eJr$Qݓ)4vI2aE%E'6DbO;jHN8XL1v ;H!$0Da$&:H%ZFOϰfQ픃}so[tf~ߘnY@v Ȫ nɍw\_wrMtB{{5]b0"FᄡD.H]g#&8 >'_.ܼlIn{+~aQ 7/:z其X0woʀ/qrB^EªL*ܝ1i ||;o?\}zME'u`+thwD!(. (c6rh9|hiq1|ٱѹ [f0W" O, 7ID-$'IA}GhZPeUuZyoaul2r<XC)caX*˂nFŝė0Lq_ߛ)O +On⇟š Iz|n Ĺha\P`I Du*͆'R;ZBy74(sRk@F/Fv ~ƻRo[ڼ?xxHФIc2 O{4A2 "$EG\HcZł6~듯N!OXm=1ڂ @3K!3ȰZ"f ,I!!"ŶTC'¯T $Z4$b OH 16cڲ -Ȭ?i l *Kg4 Xkk 52Cނ`c"ucr֞Gn@ IL|a~txj^{lfp 3-5KW4,Xnڦʎ0`!%iL$(HBm-Wۘ kHs{pֆ]Ξ zVvmBXqQMqg< _(3]ta[b ݩ_9l /2RÈ)Ǒ6y7Š~LKozuI@ ˔E9dZ'D/IAD( @#_R})a[JrFx.őXZ3Hd ΍hZF ݩQUA!sU?rJDŠ"XDH"1Jț3 dNJmXQdH|IۛvWBsV9:tEr$bGliY9 ( 46lk VVR-9Dp~/҄dL6 %lE#bTn▝ ΢~;sN8:!-?I'IH$F^BĞ?*pC8ۦA4%zZKq :3 2[drT$ ]:.e'5L$.KVL8٪׼1mnIv.*&b<U{<ˋzD+GvRU c׌P [we.ż$Ҋ!TP>NA7dxۅ"VV߶RIxM=I, /G:#|B &i[OI9"OV+P6 14Tqhz:C26KĶS)u/:IЄ2\]vĒlM68l#{߰_S4NO}ŤܘW^T8m8kΣ>~tM!5@;b ݲr3K+j1ACenADĩr>wJFʜ9/^S$M< dږ񜜖Q8-Iϐ[=:,*''l|_yOgR{?3wGv&Lh5ll^N lC^ŸA^'<ۥFE5c&bcPp 幦#Twc\I@^T,JoEU8Q"!qρ0#y2;׳=z~m'inB{0jAt{顡]3٦FZ/8~*aAz/^sa-!r ! ˚LI̶jOkN;񠀪Z3ew}砭6q>,O]9)IPOMr3L9V 6[_ !YP#ތpiQ>Pc/nW92 I+:6Ih>b|n_A鹄QTF5rI3ŁOv׎[0Q p&ګ߉zY5dlNmY%ĞYbj'%qMze 0kKʿp-oqDXX,`jԖJ~* Dԉ5St TՄn,>u+ǒHP8Cj(!NS MU B` !]Ddh*>`w8ן.4]&J0׸솗0!s6/k/\ƴ{mzY}1cSgo_r:Ƙlg'bJT*g]}q4kS# c-hQ5[ӡBgճؒbJ;0ka A :3{}WT{syVO4Hr a}uzIMJ[V^RJ$?\kH2o/]v1 cA pvW|_re%/[4g1@ * JF}mf;~: H:!W3Qb!KaDhEMCَGQM%ݷ/N!=ΧI֪ނQkbSHfhJSVYb6,d*Uu'|'7uu2V%7jA5J AVt DN -N+;EBokNH,%ʇ;FF#d^ Qsf!R_OZBe'‡eFC!m8&R) 6 0C`1HQj%sM*-@(+шFQDYGE `R!21^ݴ?MZXq[wG11Mu7oo|nDǞx]Y1 AR中? KE1, 1lt}'e?[g dǽ(9n#7DD)AF%Sd%]X,T$a_?!#x 9xŒ)XYR{p>UǑRKxl[z1pKޓawFɶ 4˝nᗌ*N)ƶvoX28)hJ4DiȬ9W'rBHGF%L5FkZrmɒmV+.*FY5S68q^yTH$9MW'#o)kkPU{̒ܠ|;{gm;T~e>WaƢqSN>1uumsޛhhW,Hwp TǧTq;ލk}v U"tՒgbI9 rYq&'qS\\-J2''l)QWW8 Vh 鸓Y,]ʰ;RڱBx'nޟph&^~mcխZ/7nڕ>lןX99b\k#U~dT>+V^3oNǚOϿݠR(4 ;:I-[I){z\ˍMf.4S}ތM?^{ҖoFNál.eX?\UgS;O3.PX^S1ϓa*/8oT<ϝ,2f:G\Gꂦ5m^(&w\8tg #zs2>I <p[0[_v0;K(lu~.-r^hNLV>66tōVÅ\M?J{(nL ӽzQH:T+c_]nmǖ/9?/u!RS\8HTdf$pSg/ "i+C祢[4&-\,#nw7]6Ԯ4)c=ϸ;y6·3mN..mڵ%*::vL޼-gl릻QbpNBd#>P &TSQ O'\>rn- %_ݜKi_~qϕ|JIM, ު!=M~s nkKhd_N8o !5N27IDIF YтQ#D un%hk<鍩V\ Z;%$AC!aˢHeKs~ ղxVj[ls.?^P mAjO 97* 519b &X*HHѬZANhhUA+@=ƘgR\Av>rُ˺jxI4s4 Vf*b[N*Z["7܇   ݉滐nՉAי4P;n" 18Cظ鋓0ᓺt]iҾ>r9eMF]@ZlI$NkhjRXb<}~&ckF9k:G &O3$g~>abWY:uʹ'8]Fb `u<K>~$mTCOxafv*F X ]c8xz C1^q$Z8z2k=?6ڦ^ME^X(t ga75R_q}.9>N=Y'ƱN,8Qva D|]f+H}fJ[!/ljc "*9+֐Ut }IuAJ򴏤lMwkD- RI@8:vrW ]@G/^AZf5xobm\| ^;V,̣?U _XBxqL׻ 4kY J Jcm4;Gz݈ɭ*~ IÑ0 ;fe7Ò (qMȧQ & NcmڽOם{B'5ײZ]-hUIQƅni** DIx~_59?Ta}ө=[) c:&΀7ǂbTDZ1P tWUrLe5oR'_HUEK:6 / acWDBܩۭjt~^9\剸eA79^) Bhشt)6PBD$ XIRjpm^ތnk>=$ !ؙ󵗓[4;aĆ󗣌ZG׍i*rL5L5I:ucLUi2]1D ҠK)|cz[:KShEq/0s-l E" W@"#rdB D 1Z)Qdw98M@:ΉģLWgBGwO:"Ӻ˱@~ e N WN_'/l6,/ST״hiDYpHu O=!c]rv;Pa',6Ib;7Lm=rZERu{%K;@j1p |Š. a"1Byߨjj "| ŀ|^{h$YKLMǤg8a; nE΢)wauܘ~׋5_:`xtb (vI~UXk8y6l?_"48ƽoiSԩyϼ^Ʌ[}йV`x@E2OIHh!gqdmE&(m-JchvZF?I`XNڄZ8,gB(!d$. -a @ 3󉜁 X !o*~w-NȜ,rpE Պ\,޵6ݺfRbA Z@:H>JwP=p2,o99 X Άje'lB;^V߫U-NmGQGdv,`UG E3q岀:HR2ڶd$ W_vgp7TAB6sլR#5Th24ذP쏑mZNI8-_ ƗhMI'6>$M¨fgm4dԓ$[rQavR& q#,4I`(yN¨Bm^lL2QPH(4*Tkq,VF4Zоޙ$C+IzѼh6^$-}'\WFqiJoyB‘Qm6 Zz]bn|ZV-k]^eUMRn478BEUzkfDhmoOO@_Ĝd%Z./LTqYۺah<3$5s.¥|[J#GF+ͦ> ՟]61ӧ%Ki{SKuFW(NXz {$&6|Ζ]3::oluG!Zl;+.#'DEr՞TNX6!IDftDI6tQgM} o.Lmd=ּ-gj(2XRlJZórқniEx?ܨ7o8JL,,47DfOKCwjg&i`9ONKVmycʈ6-z6,@@r+̚@B#j(dCFH ! THd5QOaQ"0P +1%6$KI@N R+Ѕ ~%Xܱs+|-a'v.AO|q4cЖ^B߲Ѳ$ ]bnC&Z*b Yv7˒ *` -);ѽ$ D|+%PsL Z'[cbhΨ_|)߹# bTkcm@iTT[-K:+Iz*U]/* Hl@ د㚁@ V&rT_SE8,P3jo8K 籠Ðw vH5 P dC%t$NԯW{MNJh MU K% j58o jW X&}5H aX}ϣKzV Q@rcQxf<+,dcKUbJT*}IT|oƥP{q+>Oe332Í]P+J!  &CSHPI5IT BW*4Δ8TβP=6ǁ:IX ?5(GyC1~7PPX~VI3yRǷ'ϷEuIgY$!q)ҕ tRR b^1-''6n 4]32@ J[H sK BZkiJgDӾgZOoj,xʥWro<Ƃ#bj-[kTUb?ť6'` $0-R Тb ?Ɩ[Z's&]x58PRňK T+9yU#mF!l+'p BMC5]"p0.m;y#!,i[c+^sE: |tVtp.q23[]k׼ OUcB*?h$J\xjDťt֠Eaf0^\',-y\]j]fKIP\HO&4$pbjʕsjMcRf I70O"A9EZ\#o =V: &prIkʕ_-Y1i)\kpHSj4[5ђ< ]kB9 =&nӦ\lI479bC Vq(rZ`ս] zL߫ GBQM"ūbSp a@ArHVM;aF8!:Ҝ|y+rUOU%:TsSⴥb [tIiX)w.x3}/9? mO-1eጢ#m5P !j)%jwl\ӣD 7:pV.P9rHY/H@ +:$ $5ԣNc) }a%rp Q1 V(G,( :\d J!ieIxJiL g;Xl qe4p {PYMf;C1?$}>/'!Z+WBfT PA#Ni:, 8'0PNj2>X)0jDƫbcCt9u?hnf*=uiffz%ͱfI'H2ը$ZeEv*rX8!8_p +uī.ιY#p].SC!_di)ѭVg(Hܗ]n 5@1vn@ <>g 9#FC:ڶP@(jOI c ҁy@`,ocBD\@_/e 䙦 ~3X!M%tD(3@UV4!Q8p >@0x8m ch z4{H 0ȪLIN2wiaSǿ(ױŝ1NJQVpĸ7KR(iG10Kmnha(ڣ&hn訪HkjfҨ\R/ n@+pXn`|~n@X`ysy4;Z:ÐQmX*x)Mu]M9HQD pC pI_U LZR8mΆNV-JL0'Y _Y3qKq8;J2I]-ƪ+:`YR|yZHJ x(bBA0MVUѕ"8໻)eȺym_/fG^y\dA1Pӌ9aYtkRHy"PX%)6kF ȄMEf}G֝a8v/RaV#<)Ψ#'ʰfEbR$ۋ7ݧtLHsq~X52(@Zc5';Ar(3}xً5*io"9lؔQJت"-Aĉ` +-a%< 'i5Ym 󳺹ia6btzMm';i,cT7DZsVǕ7$ZYmVpafnn7v3RŽjy+1LZ"%(''խ aeֆRM'CasF# b=Ý :JxX @}|99'9ڡn`:5p J?櫈c෎k օ*!M@ODYը(\]e@wM6j{,cTuGhqd7['GPŸg!#w6uN $0 CD9-h:`yThfQQ#xD+CA}a30AFG8IIaC[ Dq]V!$c%PŔh $-,.a!Q ZV3ա) Y СDb>:6-do{Hx 'I"{bI !pD*!m`wGEL4{lH$fv/٬SB:Wc #FJJAD>x s8+oG$/[,t#AsCӁʣ ϵ̢iwK  t Ad>nGցx2ן\?yBPćpM@`( h@"ik=ސzuq^諲ɀV_ ϻ%kHlDJסWmm$ d+% k"ZavgF{:z oHq(TGHqER+훽@ǹdw'4XfF (>D 70$4m۬6vO>M9qq8yRCA·i+[oݸ"QRŮ+[}b.7`f zX9cP6_S΢vFjOoZ(^BZ ?Ԁr ۜqK.SF4O'ra bgVV@%?BU>!3gNz%\7~t wkաmr+=Jc};> U%ͶUPТUE !mgWĻV\INa**P:QkRWAICx0t4g?(e'URڱYER/*Ip,B)PbBɍ2 U3?bLhkU>!ĿV6WWRkkg?OQh!Y!&NH =@7Qf|>ދت%X"+G#Nvn"3^r91V#YcM1%eXL|'ȭUw]{ْ7x`wv jLjV[ub[/3j%Uv牟j bA|'gWW$oQ %FL%2zUb7JKɍ@k;7=Di% +rc@"^!}[gJ`-5kHGU6!_*[TYv1)"f~j" E kƟ37~:ݕ+O)Þ&FxȑE[eoA3݂篣` D5wD7}Ӊ`(v{fR$ɠlGuhBV8X OI!  hi&gैZ8V ,384 HRF5eFqj8G_%`cMp"`xPV@:X I,:9RHu~I]= h+H( `V+ii-l B˜jz/whbov̵iA [- 4Ԍ8N35c= 4 wP@,$Iq3fZI1&I]͐hte0ZRx5"0(N峎/EGCsk{05I2$Dh?2reV>ׄp&bjm\p BDGĩcTrkĶ?`sd8 AQmrF G Q!yȄS՝ܢ*-L`7[)` `VJ$X5h TMh֑lpQp1DҎs@a Wyc2iGdR!]P mo|W01i߁%"H|W ff@.p+W2>M(G+(aQ   R%ac{8VـDbv`mRHOҁ! Ί* !SR7o'mKFp*JAE@8+ R@FTͫ-0T .Ev߻!'3;IATҭY*B@.0X KUJ5K% x8g6nm>?LI) ug 2i8e9A_Iϑ/#K2A W6zcʙ!븩{-?z܇$) ~HI߲^1uq4]osEdY]e4$mt_t0ڝ2R2\G{a憝{kM4 n#xl . $') w|E'Uސ Հm\*aD ;R@0Y33 7U+$k]9qbm<:~zcZ!YH9> hX;DP>eP"lRYIO{;0k@![̐`Tx96tS]Y?}+B8qgM!ם6|ll"X)bL JCxNjwN0y3?xzC?p l&I8жm$\#,=TK~?x׽ڏ\,,8Upk;lds.WwU,PZB2$a6ɇD%R_qυ,((t"Vb={`j?.Y6N2R ܧ }9}xp@$Dv(@טhЧkk XӞ( B$ǵ~,['lE]t}‰7g<ὗ4#8IhQIsb6`έfzeL{FH#ʦǵN+'i|17g#L_uich|bfx*0VpVs=߀18 z3|g퓴B6th'XW=?Ҁ:j@OClf>XS[(~íК0=600Ǎf$Evsr@b !VLԱ  h D1K%4!lE8@, ـr5@DQ@VD"˛1dF&($a65)$jQĶ6=Y&6i6IPl|ZHRBʠ5 B֭{ H .3㜙i6HO~DEQwcE%'j`Y8aH1ujHwM{rjd8 _M~z6 + Ҝar$wB'&oyY|H;m͆B(D0 Wp#1@H9KP_]R'C~gUg:ր]U51tgrq1nC&mf*Yh #[`eLjgޔ16Z-KO''Ȅ &К9S/qqdΟM%hB:ZòiyCc|H}xxr&2ΆwERgf‰c5s|č#O]i| FtxԲc#wyV \YjYZ?o_ ?gL@Ȅg̡{_>b?͖aw=4 ԑtB;&}jVo2t1gVQrXMO>u5SNi?7GƟXU44sP#0vaǢM:/ 4SNi}UaNOع8LN`ٷYZψ#:$jqhGІx RLCE+}4%%&HWq+HJ[@7)@@k &s0nx IQB)P"ahl"6 P6IEAlժ 1=H1* QߺCY(l lI&I ٟ͢[Ŀ*C]#^Q-FTff |OܶFChjBP?J Ce[D|I$u$TQQd$GB-3!5 rXy N5or&'K}M9pάE͓0mR5]J0\ȱܾ&3ZOTֵy%ٲrfRp&Z{х]RZfDp!- 8aQ@TP ؒDjk3}& $rF;wq1fFm4 onhݮ*d dpdP~vWp1w>'/~kT C;֤ !vZ9#z] a :Re:Li4vzMCmHh*ee"(Jgp@ܻ~s¨QDI U|?<};3]t|R35gb'ZD3㞺 PЖ`weNMxQF@"~ODL "Bn{"Kמ77kmJdZy4p6o֣9 m0?Eۓ/9RLGhMUAau집,{8RybYNd=Iu]UvE ੯Gwx_Pb/n۫8b6(c;KZ7iBz^ͪH%4/-3O9^\T 5,nF8l]7A6cN^yaMu_s 0RnW'=nt޶asxˎ[j .|ˇ)|={gێyUM{k[v=ɖ4fL}_;ԛ6Urۙ7^cAđ݄_b9r*q'TKf2jwǿ1hN6WM> ~:&+K궢/&FG.݄Œ4aB&>siB6s^Wƽqת}Oh49ON|Sޮ 0t9ֻ}f4ffpw&1${poś R֯{=zxOUFc$K|gz e=g86OUz[-Ѧt޿K[!pOI#hƨ>?_?,H r{RUDz?+nfry=tg=rwn8'IWYnFs-a|T6$V?^u3o$޽<_*t݆` #_ga}[ ^%B pS4(~.i+gz1 -_w;hx|rV4G̣бQ~㓭\ #옼~c]o ]OMqn?G:RYgڳ}'ˢ#Q9 d?|`4dj_Un8N'I~?ŢǮ7ODvFۛAalJ9jFb򮺕Dڇ6wT_p&A3{S.U0+}R䨹MGyϭ>NJ9mO-[[96.(I fpʕ6lO/T ^h6NC߰x+A_>lxEBαf2jk:H 'hy29SEJ$UnsX^^vzC6XefBd2̤'\HJA$@A:hf}Z<>H3)]ٔ*`hDу.j<L!'wGv/;] 4Va [22}C{{g#$7/ %*b&~>-oXx:m Pj\=?am4h[2zכQ Zt0HAbHAT)>;.ż[QTeD fD`&o5;,کy3](q> p#@R1=lաQMHFw5-SîOn NM;{gV2K`O)Cׯڹ5fMnLu龺\+Z3:WL|ڻ7~Vn7"׈k6x?Ϝ7=2渻5{}h^>ƅ+Ԧwi{4}pfҟ3t>Y7m.f~zweV){?f&. ^:?{$%VS=qX=}sOIW'_oHXO@,Uml;laϨЭ _ OS޽cm2 $,f}n:^K'݊47hKCԦxgA߹9H%Jڽ{:+%۬ڥ8kzZ?6uw/ر=R4=gckv5o^/K̜۵`均/4>%ww/Ƶk߉ǣðڲ]M=;v,wNg*.,miח|g;Hƾ@%GpxK"wԷ٩rfjKWM:ww%ҫξ5jv6SYz˯]#m~8Tb6z[4fo~1y,{g.UTIh^nt{k;ڙro"V>5!<waaBpFAKټgW.^g˰Mʓ<@͠,.ؼ{>i5{LEN|]1c‡_g߉0ZuMBEKlsbi]^?nn QdDU AZ+3An87`94b*|&4&0ND'HD6go#Cx[Qf,xú+RB2D8O7_ 0y鐙 !;}ɇv7zY7PWD@J^p N#%4ۑ+:|Ϙ{jAy~OV6O.U9-/t_Xc⤧=yH:9]w:㭃hpZ;{4Lpꊂ.̿>^1`eyvw~s3"Fݺt5\9=YU䚧Iϲ%ߔ9[n,iwHM zj>yˣrBMk :{Ƣ|*l]4 .?}+7{w+Sݑ.0~4~Ѽ1q_X.ͮ M|XP#Fdgl>{~O|O)&aI;p)UZ< Ysc2?ĸoZ/_4x秿O 7LO~6rWH/l=1fkG6ݤu}'HxA;{ע|ݰ-Eȥ SV s5C>E^лf6K 3QsxˑJwiu\^-X3:بM׳7/ J;;Szg+E'Fw/n _%M|1^K,8 7HDvaIRFc479q :fB>LbsW (\{:>98]ǂx$4D + D^I@2{ڽotc=\  +`V/Ѷκuv~6ֲÖn9o C VūjOǷ9N4%rosm4a U%y>Xﻳ>y;4% 3)Հ2(5!`  ,㇟VPNxY|Mk_G78%[.@ҭhB[)){n|yn#Bwu؍ 2 JY gl5nhrB5g<=Fy%d`L&HX&1SԴ}"3HUInIh ]@ {g%?eeLI[ۆ܊R3g} VKZ/T2% xj c}غu/5h&=Vzx`1̲o9~DiƇn/ S#3|vN#Jl{ ľ)e˒֕#GƊMYkxQ'=ВH*҆=ɻ޼9JNr7дul㵛斫>Ci?0uL.Id;n GɒP1׬ks{Sgu+.xΞKGNOߺyuMAW}q StMR\7;v9 餭=؟˽xqh΍DfL|3'XHv{2ݼn:!CG D|8ĖYV=21(ob%}%ēN?ө kȴ~f >PzLkxNóXIv{9x }^3i ?Zr>0еY8ȑZ8cºs~5/!q֎i[ \> iPϻueT;e=]WXZ,Vҧ]'oJrO49u sޞMgtLxSMǭ1jY,.#D1/pm H9zms*9e&i4G5$їZ Pw@jY9;SD5un ᣜg>V ppȪ+)w~a,6!+κ~m͚[d _O<:'G1阘B"M-ul\s ט]WlV@L *4M,`̮REb$Hz$̰@{wS?[i@b` Y5q6}L;Ney 7ʎ\2 qFHSf/:BHYUaO}Yy[ΌNwuf5nqzJ~6tW]n-=ߥb'^~j;?yjgL.1SҲj ~7+V=LC֋Fk-ّyϯn}5Fqdi:Ne.lOYHw֙Dl^Xκ'#tKqy}1=6!6[4|DGu9$\3~fDp^X]('?ޮ꣧]QG؈ I#2`}tRcp`$Vm?DҎ_`z7`N]8\ *fHzS2~{tG_[aB uNxF{ҌDjO7~?sL~1<"?_#vUD38(fzpD2[we3\{-\-k$%taSe&9_cLnAN PEu?y蓛6u!ϭG쇡A hm2oťFPBuS=e1!Nx?d_1-ld,$DrP됍:n;gC4Ht{pn~Y?=%9}`oiَT` Ҿ ?q]ivz8jpdZ(,$aO N s)螨nmwq 0!0ѧ<ԕ!IW}=[UPUԮ]RӽJ hpG62A, j,!;D\ E~f}pr ݤD#qjٖcH%k:+7xHG4cA$(*HlѲ((׼B1HqMHd).zmΖRlSrߠwC đDo>*]&Qqr2 z#e}G򴱇jm DXC,N{tO|Vqgb[`Y]Cr%K1 l1אsAaJg:7O'74XnzkB2k)UR|?&S99^̔xt9Ha(% 20ʨPt17HLm*x3B9f@ E RX@:6 ̍fJfU AiT}-I+Ä+ŢX뛛|hDC ʀw_rܓYGJ2od"N-,<3'Y>E$,Y?. k ?ww^w ke)g[z.^hEdH5766C7c(٩DZ Mgf#7廯=폏BN jΝH٨kQi_Vʥ2,A)L['Y7_jEɗW]o>/nL!u)h_hvs#20A%? ^㮲Zf_NЪ j?<&{vA\{(i1>yܿu,3Dۺzql# *InP2#]D%yVM #,3g}q%l (8%A]3UBPӬ˥CtW4Ǥ h<:yV.~LU7I1A!FLSP &`BR=TTtgіS L(ȣ3ʻ 3RVȽ0<;#PRvw!j\CHbXP*>KwR,-DTfB&w/ޜ{yz`gD1$[-=ߝݣHV 1;r06쳿9e_5(ARD*XRݷ`{'I*ƅ,@6Vqc٬ ;@2H4Jk3wV' +*I: 5Nhb!C-ns~Jh  %uVNoq+^6/6yԧ+MvZ8zH{g_VP Gk( ?hyӎ^%YP3nY% ^oOz9A B4ISm(,xK-kU 9֯n}h}hG,a@H@{uSe-/tR,? 1'_~ɡ{tq*9mt8tR<)  !+Ok۞tE3X @C_\ͪ߱Vt !Iӛ䎛+6btڤRo~U:q6+C^nNN_|)Ē( bF*kuܖNƶ1TKO;c{{J5SD:ͱ.eY"LNݣVk045P RXa!wzA7۽ %nZp@bL6nQ sIՖD!K.oekW=JpxHܰnф:/p̉}š֑˳s ndrv窆gJ"#Аަ 8.1qɿdiic@4DѲX 2 !@%4&gZ?s{ӷ޸駟B+#RڮE ZT1 B{Mo2L?+!4+D8ao{ͺx<&WlTao іB`Fr9: j*xȽ̼ Hnl|$x="S-_X;'C(n*^(tΑ 3^(I& hmyXLg~]ELDŨαXrjrRY3d)I.dA  =7btB[ ASQxikK,)br[ 3`! 4G{}OгI Qʻ`15^]9^0t'՞"aVND&1?rE휿FV312%^#i|o4/,i)NJb ?9hm-ZT[;ghc<_^]?|r*1XIktpyXot.flk٫V_+o=`b>jF 9o$q!!1`(r}\,m!%B(VT-Ʃ;g\#WiPpqw#aè" ^s%g;Hb.={WH_y UV>m/_95ѱ-O-X}4H`%wy7gɈ `YFS󷹶<'v_u.6 s$wR.O8A`Q<6㦧HB A(cTrV/;^cK0 4@pb1ҟط̡ǦeG}_7jH5%+qhőHIiC',nyڢ4R9` _Уk};|]iZhչC(ei^SpT$h2* Fad.T.?:}H#P*%yVoY:keL+v0PdPSų+ޝ.O#-u&LH >K7M&X LT2oݻ3-?tգiu1Xh$jĮS'>QN;[" =pKM&~ eS44֩`Dș|ך;jgt|J#8B+K iZ՘kcarGC?܁7'趀{+"[FTO?7;+-k TX;j"YQҦ{uؘ1FbP-ǃ: _ifHQ42IU7} QZΎ 4:`ĠWQ^?hiZxU1Sy<2}A'GTrmm DM_ #ou4X3q j8ObIj|N z-P.^ŏF1_i$ 4BDpʸ/OyB7bB,^YS[+SgqŠ668D'a 2.K5˴19l$^4FRh%% RLUȖ)"XgDnSv!ݔApT4ܔ4%hVO#AOtr$IG$"d@(rGFv1,=t]t6޶-P9[3-ɎNmŒJKiUstkD"{ {I`57z,B$$L=3!ctDB%B!Z~HP2,]yH Y"g0m$a,dP~c'TL'j hWG"lC`ǝYzvK .o+k/)>K"Saj*^M3p,h1α彥"7]Apm1@@]YBAY2B[X\Yk'6h fŊeb,c PA@bjϡ| -*k pL0R F+ )@%tRLgLo L/'ͪT@ublа:T;)Y:_k2[F*codbܘx˞[7lWbZegXk@{簺C f1ND#c90HQE1t9FT+G҂4MM(@ {csȓH4Ɉnk/ZְG[5PX(&w{!MTqRZz9-#ĀJg zT‘FWCN31{Ytxa[Tѵ!Q.!ެgtq G } %1@VֆCUp^ 1:g˿\7?!1f ŨY-w<-b5b}! 4şymUb"ډ)ݹ7# Fyn(3* Q`K+5UӽaV_P&>/{=H*@а:l.dC U(bWr0UU٨d^OgiD4-KMkah;lȖwCGdP5񾅫ewҍ};?N=Dh%zCѥBW;r kAe9 >X< !c <̕No}`tgڇ%Q׎Ƴ*tm%,,"NXGh AJ`2<8V*PqL:zn=It4ş7x͇mx{lqԳsNa F}VW=(JN`r@pاdlJkؾ~rλF,tǞg*{RӚq͒;xcIg!2ɻca~Km7M+Uz1 u[#dAH[ voMAhx+P6Q8a A8cUj4Gbt aBQ<<"ykAc+{X?]_÷}u2 ze~ճP|[A% } (=>M#oyzu vY:z<9TV&#,\,bJF5WQV,.W āp&ӛ@4p霢35U'7u=%*HQ؍%)aR=[kTeaXWä`A]lU5l.8bӱ(@/T|,\A!!t]E{taK{ǒ7 ^dZ&o 6#0VEV Ynb@~ސ1ַ`")fcƫN9EKumyt۹C|RB6rj[ ao 6x:VEzbQ^X8٭'퇙 KVV[oK>s19:D}ݱ"d0襊(Hp#7޲"Ű(ΆF/n22DH}۽q_XG1~I;ĢHM (!m+!o- 7~;WtTʣ"|>7\0:oyAQYҹq׼vg~iN]j@i2GxX܄!jb|MJ,J)JdNW%zzrsݴPř:9oM;4zejgǺt2Qu\T%2 #A5{B;zdc=mq]C&>{8WSk^8kh[t DHf1Ez~eM NRIZ4 S•[Ġ>PTQEɤ-G%4 dcq}Lfn')}/46`1#&rK$-7|a[2&8UA4GxX܄!<4q GGp2d.ZF- 7#{ TYkRRp.>gW{x֧m1^o2Z3 J` jqAedd囷_wܕRIOޙƳ MWZad.B2R8i:V;1: %M@#G+Q̞НL%=K'W3M8.R*P&%(+qgeaPq׍>槩^2ƚGf'>v9;gf>4Y 93IRI_5Khe}r0N ' >\A8try*->\O'"Т|{c/[+zՏ,@#Yb7_ZڞJ:CZWy۩߆m\R}gPq|ŧ!Ѫ (`EdQSIg}o9۠6;L]<Ӊ Kd+*+  2Pձ2V>'ԯfLHRp݈W5WR]!;(H&] :GXݓv{j{u*i߿v7Vkzh'F ĉ (+)%D 25EhloU-@21}LPv$:yѯ) +Gnjz&j4;ZĪx2lf F8YӊʝDDVX)!s \vVD&Ut9m.1C@@# IIZjOM)'$Qc.(*\ܚOΙbn 1+];|yk\OQr]䝘 mDu4b S** 1jn9Ӭk8>7,7Z3{G/-] vvf=U kֳeL `+KsaC/9hxS_RĈ +{joRr2]=&_t ӛZ?鈏NYFH7 X`M)jӬbmSS|swZlbvWR_仍et4jڠ`Nv|燤q4%@ԅ\h.ѴS1 -6& !j~ԝsAgz;xs?M_sO=}DnU!)N$S7b`zd=ݪv~ȿ`p.8`e~)`<Q 2IdO RCuQbHRUDڠAu˰ تDVso]cg[ dtؓ{?5Yd ?0{nRn)=sӞr_?%Ǭ2oLoBtO{*@ԶSt[]r->Wo<ɤVlr '&ƐSC>ة>aْ` KIj=n[=HՓ6EJ%Tãnzl9]{Bjlk!)u:+rLmϩO{MAK$IVrƍ1jMFvƔ\0)jn9V+ퟸ0\X:z3~\hžvg8t0uJ"eStg:6WMֵ}G?ۓ'G+d}ș6\_}aO<8\fWT4t7*BIj憶} a>&7NѼ#t 9\ cRv}E 'ҫOL9K$l=H\vh~QJ9YH.18^h2Ns'+?R4ĉL2JҽJV(m7PX=ͮ}\aB&RAv?/) ASZs`Ѱ-$Be'ECsu 2e @x@L:iqu-ڶ1RL+i+[ ?ĦS;ђڭۥcc'b'aF`#NtAIXAB \=1T6uԵXKmHYp Bd-V0+%ւ܍-FpЖe 4+F%r$Pgsl{T8rʠfܲi bbk9%:H罀0hi,V(fN+g} 3>TDԹn+]J_>;Pw;<*y,O-YK&F4nT1wyp Z8\LSZVF$\s[`;hwa^%Z6 H†h1rdġGWG}ml'񂱜,,$UPvx-5'+j)3) X.,i)R#& pFjkdBq1%I  I-Yzܔ!S_* d%хܭY"N RB7DeH2D\kZ5EFǂ怵 *Ԩ:iŒ dBwq')fKPpgB'>G2ᆏ|jZ#{pu2!t&}(֢ףzIPo+YMn{H>5k\TB$|FEm㵱{ᓵtkks&Y1 )|Ei{jW1CfEbSqutkwY3?@=?5*|âւIҵ4Q`P$C8'⹪䄥? ZQ1@^AsS"bAVOĐ޼;ZIbTIV|Wtsm@hSٗMq4gP Kᧀ#ADةl Em6?Ndy*v E_@`hQ- "T@*j7vg. IWe+|]P 9oi4Ǘ4ɔ 6wE%xrEȉ!7$=ͮUwے٧/\O1; _ڳc {Q?i-5))kȀdEaU s)꾩W&eͧ{|&y\>/$r-cDXK 44I{wوKWY (eEFz-BO3Ior3.=0!nm)ˮ[!3X5nsAc}85~_']oBo6~<"!!brS*Dx[*w_"S:G$kW]ű"MMFw ; [3^LVz#Y}2Ɂ¥˱"q ^- j}adˣ#hk;'KV?/F"Z#sɟ\ڣowvSIS7;RTs뀣"X*$z_>53-rΘ{G'.E3)#7zCtb!ln]SO<=lSGW<]5xTmGiVwJ?J{̙sw꘹vi6}0%kvM#}wHd}}oGz9ysEcɘQh_jYc@zRH9Um#o9~Mtb7S,d]|0e1AռgfrҎXrxluѼWx*ˊ?6>N&=md75!Aމ^XLBYՓ+l1,U7?f#DC Ɗ ?t){s΀n2ˎDAhBwMTASK 'Ǿsd=m}"g +9fhQ[/f盆G8 I97awMl>z([]vZz`R}o9v۬u~&ol*Aa5*#"ʟ%Ɣ@:m0|{8vZhM?6.,t~ F q0ж3HG~?ۿƶ-.d]ZP9)YBwdEtڝ)1agEV 9BSwӪPTx6rڒ9xV]蒕$랶zA]nkuc} YE: TdE)$jho"H.SdTF eo!(85;I=ghW,7'dVP=qҘ֢,SRh%C,r5u~Dbz\!Fe-o3}oM\uzbHQ 6l2Fţ|e8 ܵ3 RŲUI.JRI$x8q9N%M,Zr[.b5 V:krMWcYc{>?脱W&~EZce( iI-;UjA2b(H`E%sO>x̅?jX/;<)Ų )hku kRY"(Y5eGՎ-ne*$H2$ļf:́&"SS*yA'e-ڑv%6OO -K$t/xxMʯA zv oH|]A,@7S\Euf>pב&_apC[lʝˈ+ݝ,PlRwjxdphx#YPq˘ÖuT쁢g% :z-^hcZ^1sYzdk4u]QU@kr3B,piGD&xNL%"r9)Z[hP;Ʌg"Y:^A )K0Zg ^`$W3Āhg6N!X}DycFРiO!6bDrV9HQAez7M 戢@HD0+'/S&Cб2)ڬjYrr_Ë07dL] ٙ KrMIr4""e1Gw` ٩nG;9oÈSILEs5xp ho b'@~XuV7/iW]eM*JccYL>CdWۇ8.RYVg)Ɠhv)F¹Y[>!DL D{E$h-J%FS>IEmкO& eD4[eChY餪qm1R"W!< MY";H(I!!hY`n&45+vE};/Z`U3j@m,z0(]?}-iҩV0uYZc ); R)_ٖے{8a*;ڥѽ Pm&K>)U8oJڎ˥wd)PB涌:"ϣ#4t[U.ɉf KԽlAG[Uw 7О2|n P^DMzF UJ9wDK?`ȟˋv0Xu Ϯ|Z8ܝʘTM蛴;Zā1{F_q ȉE=.VD5-F*kX"';]wMʡ!0JǺvE.o0 [QEg'#w~yOZ\c;Nmxh-P49h&͞L9 6.lsnm<܄+|cߔ$ <[ҭhEm3"Nd%fAN_cs&""0CgM?hMK!Bèm]8N%C&RHdfNˢ*䩷|MX? w"Ŵl"khmjՔ)68?vVHD8Sóڇ߈D]S;9QĐnDcmeiES=x"n}im-k`XZCTJ) ڱ0K,9#W"ppIO' W;Y -bp9P0m J)K@b&#!Miαq*GKI9vh78:uV:w+vSNF=HEZ 1PJV '1wVW5.6U], RgM8R BSPMiETmc7T#w6d[ Zs XMр)t;%JbCs˫?]7@H+vAWC6n0ו3"] wJѦ&p9v?0?e9q mE/DZp f#Wv2SO*DJ"~:& ;wи8v+PU$dblL)рd+WCY6&m:vtةw.m<ϭi ɉuNؚfTepA$r[?1vKh\ܔkku(%1#ƓBF!rg(SePcPFP( Ɨxsg40xJ[6/wDt2dI2V$-C$lʈ1(YN*x>,:~iٲVe-y)b i`J,˷ .XÞrR>̐^Eא[ XCJ٨~xŴF,e"iUB~AVd EbY]-ivcpUd e(-ӑDoSʶ}}Gdp'8İ@A%juDq,H\;s"3LJk4m˦ ŒД?_w/ٵntqE7`p'q,-gLu:SJDYVŃ1ޛCqa8gF &#iMC͛[\Ru.h' sBV%$W!)9ܺ.WضUz#M|4#Z:pl4[Hfv#-|ף) A3"d玑^"򢄱*=İM{s7h'E2V35z:)=ln Q_&b0(*Ud@쑑ʕ:rg][!d:h;ktݺ \òu6YCMZ@ϒePi4Vv4i mW[񂰁v΋Z$R_;KMxU HQ%ʒ $R7҅3`\hOԺ4)ȉU9xsMXZ IHqzs~yL7jξ{7z;zA pHJ:n?K\gM_Dϖ]V-"S~̓9 'NtM@X9"T7$?5r}r5ɩK.1I&8m[96}M 0պoUjtTv)T $ac1|V!1 4F@V37)|cƂD׍**4'8 H5, +͞ߛF4 N8]aFP4P CQ!:l݆}1ӖySY٢9!S d'VtCX 6|gԅWa q1$PSڐMr$ZVLH .fX HΟ6!0n)j!iZ5]˩:eG 4 U}?-Qb5g.Ǥ>lI՚6ϫ: (a&=m[YIMuqX AVAyU1܎$+aK*I:C{R :[."__ZlF MIƈI=HBA9N "F!U[Ah2րL4"VIA˲qW]23jm wv@bKٿtac}w.sT:j—*5&b͹GݭTdi/1kے6yZvqܥfx*`E jFQt3˚lX!?htX|Jv~ף>G&zDIޔ KRa*&j|Rk!?vN횭_;}'o\ON(/=fGW~M˵XC;a]lح+-5glzN'ƿge]|tEq.k+?}o(v-sfie4n=&1gxs20gEЍ 5aѩ:p~ݯ;mkV4;'϶ٝqxa튱Ok{+US2uMb6p0ԉ%kOu96k_0Ӟ3c;vmݸJۥ:Nc)XZY7iY9jXO -oQ7AV$`-hb# >}FQFvR r,RbQA?d\ a<YNl83t(Ѝ!ݶn@ضV>OvZpHCj@c(ְ - G]\|RdV "tY%3Dnv2)xa*+dȀmJdbj Mj7|߸+ZYR>S띓d GF~nM`xG-2+t8f9Yw 1 XH\R$u+ >RAFPD%9\Y[°stbJQ& _K:iAD A!*qݼHH$*eV'6U5լ,uZ<l:c?_vF@.oؠqxɐkapI$kVb+oJdG;Z16|b/P@-)'Z.;HeHlzMu0r9URA(qD]Ywi-KrY%BT < #}=7+"Pۜjm`4m+ށE'$Dda;5{]B9Zxm!ՏqUi&uؾk:E{,9^pZl0U R)®6M;NBW^T`I)m.O0=\FХ6O0ZJ$)uHZ0b@%CѦ[ S3s2PvHST$1((`2LIpJkkwSUV h0BclfC# TCA0HZ MBG|i;spW}" 5U?OɎ׏Q_mA A BMy C>AɩI@丮aXFSc'ûdGs+AזV yG|3t'v;^>UqcȖTRWV%o n8828Urr;v&Jjm^0^0uuvnx%׸ٹW䨅I÷"-tw?w C_mi%/S=j?no3BͪubHbmi#gvHrsvJƾG''m~@K:#,n[D+5b}:#ODҫu|N|Inr'-Y Ф( }L+Qܾ;R;_TR9ѧQg[Ӛd׾`d=Q`O;,w~/&>SqLоwNg.R.MYT맜N}Ԏ5+?g^Y]*_w^ځC>6|dkiv$. ˬڻ8mEH̆kW;Ȕ| xwȧ/I~?[qy?^\b7` 8HR68}r6+Ɣi_U}T>f49v7FTxp*|ٺ }a$ғNJUbDl2 8߲4= q]RfXH'1(uɅ/=ȞcP_ uV-{DL՝K|9d7#/W.~{-ʗiJm\?o3ߪg̫=/;9U!6 X'm|rCiw*̽ߖRRc~@Z%Eg5 KDA.xgC/pm6VŎ7!pmyTAMvTAO] 4#LPkK9)XU_5oBWvT/&syj'O{M$Yݑ(]Zdj;q'[* LSzz_m `⯶ z-RH#UHn]M)ZU+1itڒ1Htek;C!/ڸ 6^4չ5;{E6( 1IL-@Hg|5! 3Üq9~>3Oپ>#gc(|9,\tKByֲtVFUV BEFmwE9t:һYM*&4If[̬ IQ+d _jT'  [neH\OUeF4F8!*X5ATvbEhA&g&Q27g_nsoL;g}_"G4NQL0.!3XJ`;gJ!&amZnמ\Teh"tތx{7:ᔕj 0Չ2@v7i FXcL߶qpReaν7 S sZF+PA\MMdIAQꞜe`+qgšHy{F0 eN:' lpY/Cǎ~o#%W[1§;p ڛ{,rJۄ.q#g2^jɢ$-q]:HM?W$`28iF`=usδQrDV(w1#-)f:V&.YtjY2+"G6F5==q*v㥭k׀vܞ籖5kL4km0fޙcU-QӮNL% R! ftiu7nDkGv1X [n~ KnT+ ?NeQ8r嗔V(c5y+ʐczsXiF:y2ۄM|;M#dUg{Id?6uYFя,=2ZF*dY^}ﻴ8#p #r&;W,TӬK?I of7Z*l!}L$^(d#S;1ƴ13(qlF {t|G@2ol!;'bp_ 4!(}<#e!8J^Cf^bIr /9یa`CZLG$#;~Wsf^濂khG) {]UoK]ki%Z'a*E!ASV\<1gpDׅOQ&gǶ&iP]Gw@3}EG~DXPңNB]߻!8SI=[A7n|VޕO`{"JJ _:X q!l*?$"7]HPk/[%o| E8zM֯}9萇W=r[쌍*FRi;i\0NeY_z 쑗ISw5g~\q~j7g=~fn* J*~H42 mM~:[ׯzrylĒJ3ʓ9|ߝxGYί{f6Éƛj;urF=زfh]4+\ޟyٔT1-NX6.u5xVMzv7"I]q-/|LԼ&4fWXnmwwݣ->2J*ogTz`U PL/` r,-WN/k+|3[m7\s|uBN{OOIL$JNa)O/!idV{W =zmTN 2I[3l{~YՆZMf  ;fdC;=4r.7UONÆ3/ :$&zSN4%U>=6?>qWN11DDiJa0G Lmpt>Fbny'{hM}蛞z]XY`vB(CRHHzhj<3 r0|Y#ݷMSbs VQRtPѦ)guU27nmC,Vf#_u=gI%FɎ<#*-.~N&k{$/"[Nar1¬.[umk bշ',k7%X{} 1baCpT3>'ra\.vY+-7'sq!Xpt3G- &T*F 1KCC aX3.ks."96jyN@UwLG`R)= q`H ^H3}5QXbV{2qݲ򝽢ŴDkbfѝ(ǖO-vlLqM%ѵ %jw5qMrwuqiْov'g(5*@ԲGtpkQ*:H4U1 %c[-a/ƫ6kbg CW@_ijdf,,پ4W HmW3 KvYtTd-JP8pm 0_(U@6鉑b&WqAKϫ\kx]M\2GWD'I2U/#,"ו !{Y̎҂(ŸvNW0$cH'>!LgPT|R`Lrװ-i@QCMgwTrB`1]q#L&5]vE߸ 1p/'ݩN-V9ɨ[-,G=tgE}i뿄_Yf"ƤIU1f$I/Eֻ2Ǐ|S%z^3½g9J/X3AID8 mntЬ8 pS2kN (d7۬!/qÞ4 Yqa($)k/vkyaxycU[JŃxS;@R|SRa( O7f7}sw"[{SBO&"H ,:pul.x qLAm AZ%ꯆy- &46J$m{}8s,"$lpw90i;`(u~k_椑|r+G)(v ߧWN$ƊAɷg2'vׅi~G)(Nu~(IbsVSӫ^a\{ =ǖ_RÂDdV<>+_aQ`?Mlo_b5+% T[<#o+DC_s10yò\^+IĬE&x]}Sr/lʋx7Q݆Ң,iIJ }gL>JW0aBqPDTh/8@хgHљNZB]V$ý&ncFgs2a&[\-O?88G:d.[/4#ѳ#Nczry6#A+v7詫.4YL#7K#ԼQ1nx̆; `%]5W g$-3> D"ў{^?]va+һ  ܈rlBHH";K$< ez!5T @QoE{w7^F2&lB%gM2ObIii \t݈IH9#`RwpO^J rW0'R(miM-l e-=q,f++)ڊEH}؆ [TԞ3o9IVu3#NoZ!Pu $x$%jxq>zއ5D@[_ T{Mv٫ŔsZ^A^j*ӹg DP6-tG[!"HOAc=X[ UMjafE'T"V0 MNziy#VD1>x}bf4 SokH=xՉՁt'$ȍZS!u~J]s!!48`,v%mAoݦ tdD-(> }H*=XL}4x%bTPPɹ 7Fy+&uib:*JD&k_HӮb+(Pq]eŀeuQʦ/^&4Q#Kk-tST@#Qa:16[xn1؊|Xyue4ZqnM} f)f[' Z޶m}H)N [- Q(W(nAY 3wxT=|(!ϠD@boy:ڶ=4giPdd(fa͍)ñ۩c;I SžKe]"6mɻ`DFASAɆŜyT4;t)(R!Ahڪ v)D$E*"i%SHO.$-A+yE"hh@ " "q;L,"$bbo\2*B:AHsX&K۽y- *HUTH8\EN{ R` d6ȥϸP(i2B4Q'.,<3߸ {!XGIwU?xc4}vK%H+: ͳ-q0BPUJׇʽ r`5jEa %Cި&᳇>XK%kkni_^x 7/Cɝcf!_ w:z翀VӐ/OI6[z Hu6q =|mAM@8 ~kxDdObRͺ/ =u{H%#zf1={ye[t>Vf&7f{׽QSYߔ0ޮJD,v󘱽(_}6?,rKeV<|a>?!o42qM{&[ W%:_y!+{F+w;H1&5\"3"=O ,GmgmYht9oc˻_7RqjYkPr&7>aS4CѡS[|Л|Kr]֫3\8J+j$-o'<*gYvԔKtlw]`{+\_]ጉɴ[ m_\=L[ntgHe{C|ص~ٷo~isso=QA[i-բխ'Mk+$ϣݥt cjAʨcO~[f0D %:Ic[,,3%Vhm TR89G$j5jqhص+[67?дYx[S(5,'Jշ!~cADAaZqb-$ks qNܥpccTvޝա-B(!8k / RlB؀J2'MK Q{EPĈ J!|D)6P1GG|q\BrN-6RE~׺ ,(x]$G(Z5<_ZBNw1: a8wZs| GŰ,X8A^˳$ 7rA\lFRsɽ,ѫ^lbj}Ė?}6{:Sİ-ri4^D,cԙ݀K=$v{yǓX|w^n=}lP%3mnPIg&ffZ|^٥<6'm'z׻^>"T$ϡؙQⷞ[I#֛knY}:41'Ii}繪o|vN @7{wfv hą+tJu_V1X!!`;Cՙ|楄n wqJڎpra~}=U1y_(*#lq$ZA?N4D0|OEԪ:榇W>t_\ZVƈ6;fS|HtŴIL x+%g5ﯶ~~ch6wiMQɳ)Me2W@eJ@pϥ$Ł8I4أt;1+LUc1vNGUkO[w)Ac@1wS{vtx&w|ˌ)UmT'1z:{:IHВ\.0.˗6f(zNVO Z4jDzsDJ%!u1a,Pvv;d ^Mk/y5孂J6XfoO E_Q늷QtÒdRB*RymiYR2`*9.E Ħ/gtm̆J.6ԧ$~}itaѮcԹ']UKA4`3l6e..%#%PTDku ́M_!$M˪RN3^u˙w&I~]vG[HNd8ܜYږ\HABD,Vs#oۍ$˭qlMs{"ϞX&ȓ¥SfYdr^;.&SEEY.lK ww [:~bkgV}G /6kf+V^>ϱR.-;9hpG'$I6bU{qanX#:ԋ̔MQ\u?;I4nN~aI$?ȱwv|cq dyڽM>\k՜E H`ޡ휥o.G"4x为o8kk̵0o{s HྟqY H{?Ik Q$$+XP>Q  f4BC{A\F@r╱QᛀXi_հwC &&AN$^5yˣ,ExW)5x51/f/h*0> X\ɉas{! *Q}se_Hzݤmi»=-={V/H_cDD2 $Գp$`w{<cIw2E35 F'ֿ%E`t֪L6r#g\ p# ĭM5ӧÈ% _槐0tdVCk7`rl0LJL?aS5 h!I J0 X[U"cNWK)NraE+, A~aPkbAб-7d%R@JޏDKn@oZG 4|ȿ=/+VYKcL 3e7~"%Mͺٟc[MUP PQ@PP̿ah%0+)4}}{87$mhkREcQ41|<4aJ 4 1T&ژMEQr6 HOZ)S|OIn֚=?*rcEM? ƌ _z0ek*o}ʻtk%2sleAЀi{OO'! \,x1f_.3Hw!` )@B؄-Z:A]/d*tBs J-1Is5ة:4hY>]]Cg yB"mBHiCmKu3eKlIw3d%eQ|bV(dD)CEƒ $"4(˽#ر$dyӯ~1Ы!8T#TZs<9 fmhQJ%Ym__I Cy &}U`w5U(n 4rw8u†55PX7xG,kSIY؄'ZlBI4`UT-mݝR1ȯ#u uę?(RH_Z/ YݽB`V~(FQ( RH\4JTa(ZKӷ9z3V+u/mMJ_gQcX/  84A5N"D$ٟwutnVuIN20WWS<gIo}I$8W)_`Q+_Ֆn{ܑZ"< f˭MD8I$Λo>x1&E>jVzX-gUZySeV9J#ݽJP{)IrORH/|͙xdz nU>{sUJi&3> A$dJnXGk'xѬCc.UZJ^08#h ͕x{NԍsW?:eX73U\g?T'D*CKDkkqF+1)紏O=}lQYdll,^L{P\'r#q:#uaJ˳1gCۋ?2&Y\+s*zAMrk-G~uqP!R hW &ZPssRjN׏ܛٟ~",dsrDLrwo.|po'EZ(C 0 r:[Ypր8ðk4Okln}BbH{|7\>NvüiJ/N|8ӒD}8j ֻ#&spsolOC֙5lE'qW`uYBH;8TjfKyqc&sL\e oВ=T_3(@fL%$ħIjF5A 2{+iӜ)Њ_䫹Kl+Uɩ˂ ̠y3.BO k3l[0t945 Y}D6-ߍU+ 1&d©UE1AVȍcV{\LqN"x}n l.CM`#@x󸝗e?Brƙhhp"9`Z71$0:$iqF Uu]_^G婇2&/{Z&Zp o 2}P=hCM$g18֝m/өYMpZRb k p$ KM<)CY{a;V4yP*T3tXC.hhhCye-b~Sy┬>*4[$6X\ )ZF (FW B^'sʹ HQ=`iBrfAxABXcN=fHZ?}aedP&tLK,̂(d KNjV2|MF< %[`\fYB Ì~;LYFUɮA Þ5LaOhTf # ho!wբtx“+dNFWd@ NlAj(WVXl,BIE70cux؅kEu)жअ`l[i$$-03# Ժ-uktq)Tg$lbͶ5O#[tl(j f|꜂1hh: LتȕF頌p '3'7VMjG6NjwYn͹A!q9@%+<}'uU* W\q08?a*gտPiqI!9dᚯN$Ǎ3ff+X1m'46*ad:PU%O@t_7_:R F02*|\D1]M~ew @=l{4-˷{J. `W_6!BAۤ"@ HtMv>fo30;?EI{C WRAʄׄDz߅I$]̙O~m.W;. <h$p`֘].bVf)˽Т!+k3Pxdhb% Aˈ@6|a+;Z]=H]6)zaނKܱhS %KO駯ܽ nN'u[i}FB/FK.o4/E ;J  ][ܛ6`:@!Dͯ ߅&^$;tR L`YVRC~B|c'7}dSȺ7p$Ӗa8\x`DmcDlzc{k)̞D')ݷw&pd/F=F㐧|I3I9ÆJ4^b?!mdͦwA&'k~T͎`ħxG/r~;72$_ubɂ װA}CgIМ: ~ag J=To70┸ZFgْJS<>Ap/uaZd,IykA:eH~ChњIl)P@gH`>=hΪ0:Ok+j_`2{-ʟ_@@t]4LAN (ޟ-م{0ڻQ 5Ӯ r4"LGȮ3V4!OmKfN~u[G@1~ǜO}NbG&MB j> VN@ H@ FN-]Ni{ZDX\&'GCy*!64 yϼ;̡BvT( <ѐhn$cܡJI$qB*4*|$eEm #g  j‰0L눭kU@CDD9Jd5h$}?B-dw3{{K]Е 9&b[QB1"Q|6d'`57SKǰ0)gLy4E`ƱA[Ljj{\F=n g9h@K>WIBH1x:kn:O m_i/$69|&(aKQ=3TvpJ\Fhfث WWɣ98/9 H#!C ¦RGe37ɔ xSICBY:DJu9BLgR5`y?OEsr2b(띟55g`yS{NvZ~h1b(qΓI$RHOPurZ%V}T^Sn`I| tE/t`:Ly?-#(׳U6oK(*J+]$%š"!;9=ܭ]=r%ώXf/ |  }? U3|ɢb_p3:p<]}s 4c%$oE U368hShX}E4?7 A.^q,aE?YBz[w\ .]uMe{ŀ-"#9*_gwWЪX]![V$ )$j*[~J{238x8;s`/yZ h_TBXh5A7WfU?:e8egveϱU@ 'Ϊ/K$!q2wkśsƾS3DCb^vr,FiTywXe 2?@q_Ky>q&Wbc6GI_ClhFt$IJh} ;_ xƍO:R<<yI>3k4pX:54qB6dSSC>z.1N=?2oD4¼7!uy.?u5ǟ.6ZmB՜~Xx"c(Œi0JUo\s VkpQZ{S݋oYʹ>ävߐOk҅3$y\ڑ83%I*;@Ȥo>~g0TUO{긕& aa(d71'!7?}؋iZD˅7ܮ}*'n6y%EM-ohqiLbݡaHۿhDBwWAs B jٚPG\H/#sc V:DDd]0ȕ&"uD'8`kL=@v5 _[~MLL(EĈay=bF99O5K߱#bXjpxjzKO;W \iUqTnBz˙;wE&s1I7Jصww{ó:,-z I,>eS&$|~F V38Bւ̥MZHd/Ou]m\v.Ȭjih]m: NdT>5&$O5wH= 2l5-\=]츃`Z"}vy܌UI?| 2ܗh0 gYu v`ds!jHk`O%J -(1TC$j6(h ]P ByE2$/nIfX,3>$FZ\ Bx,2 'I`t_W7X$!}QdK!}:TŅ'e*@'Xm0e #G3K" 1ЪV BH*&H"r\^;kCh~S:ggL m:+_MH"H(6bC`Uk(z746a PmFBwZ}*`k$Kէ>+uU*gK:U4 q!|j F"' /<;0jEE c$ ^'LQ}JRFs *k m2O+0 3Ĺ|g2(T:H8Pg;Iϸ؟J.>{=[$m'!R(:H$,~|?$ 6$c/dj*<-m2SSq ^xtX/{oo2HeIRp9F=rq!/5f{P2;¬‹}A(EH|I ?Gw>x8OKon,p^7$9.MPphs?фHjj gCe(uZe_9]1f!;JN˒$oJwEX2$ng+}?zU&foo(mAY|64%x-IotGq2q̈񶂥:[ 9 e[hU݂>|nRT op:[qc≃)?#%bfxzWr-^}@YX!PX5(~EP:ʢ?Sz6ņm ֈҀjnӢwKEĬ+$㊋J!X4XqSvє9?w\w^]W~Tʺ}H5gG.vb'X(2l RI24_SiMZ}Ǜ;pJ\]U:ikeO:Hu' a>@j5)Qz{Iz '~?{YQۧ>mo˩1YK=Ӑ:#yz!A@O5N3FRH[*>^g;2<%]; `ܻW,fx |A*TLH& KIc;Gmu9DjPŋnlО}QDQ-^9WrNvob7.k_{ͩPңƖbP1qR&߸VJpCi2ABH՚DLŜ_ŗ]t_(٢E.{wb:\mgf>_FJ %%ӖCF>iQ(W׶ll}cY#zY8cA "G/ n41 b[!1HT*:$1]iE$fv $c#Q&Sb"T*8 B@8HId8@ Pi,$SaBYJ*NAq 4$rR)8ٹ9eN"BS ԀQFH(AھaRf(P:cܲ 8@FB3};R^; d&Y {taCGB%=sfvȨkQK0wgڤVGxZZdj^o| 8^ ORl0a?akӸn\ 氟j xHS1,*ky3QufNS:* )7\Mi|:6l ]NI$n*g!4uHrWeﭱ.FڟVr~&WqBު‰APҜ4*l<*owB͛ ;wRvŲO߼M5$II$ƘQyZKv?}+ꫥx3& ZUx!Y CY"qŞw`yUZQw(l5+C<" ̜ƕLbii%sTd7ݕ7ja^y{ڜ> 3oHQsu? 7\ɖ3j #/O)页֗uJttٗM7n tLN:I`s:NiWr/$# @aRm+KtDY2I'Z_Eb}*PIn5G;I҃w[J3HSdwyl|O1>Izl+q`(@ Vɨ qiԽ|"J 2d~}.%})"',\hnPR[[t] )%>րRžE}aFE WOOclK!]x<{}񽘔fja3b|(nƁ(ř y/⼠/ovɺҾ~Ko9`sԷ_|T,_RuYJ1Bt 2۝+oBY _ḁWG5Tf \kW[T ( %^V{;} rJFm"yeO`_BI']ؽ1gG1"uVrThŗ>)NS43FAV誻~e]eOt"?  [p!Gbo09Ã]^dc8mwTU!z,l\m@1d00بT"oeh+-J$@ a6ɻwB4]1T?Wrdx#EJd7 EoE>$gK f'Od!EZD  1_e(̔O(,8gH!KC,V( UBZꀹ&uo1^$'٢V^f h(Љ9 .2h Z#ef3s IȏP tItmkFcHl=_ 6kɩi$u3]!}Anz16$C'2VtH)[!Ak* H}zG}Խ67-fq}CqvJ%e!_ӊ{P>L%ezϨA oȩ ȵ ziFC_M!YѮJ9uϔu7zT  f@b #B)lm(ց04 ŸH4>ڃ0hZDF@C:HI!IbG %AπrIb$ H`EA!C|Qa_QP,$øf@;Q0NoEaE}* H :IP!XLO%=keeW$a1d3 S"BD q$IXrVH0 zgȮW$ˡR@] .x F"SՁ!Jzֱ~Gd{cu"q#Y;v{Z,%c*hpJ#Kxׯ: j!IW6 8f6PJ'R$'ӿO*Yo37'@kF9{LAJARk!B 3Kp)A*< 99?iуg$䚜D17=pKoM2d+v'Z8qS )΀l$0A'VM%/ZZ^H[Xܒbq1ǡXRQJ04F;ɵ1j jVE[\-7m4 1rl6FxQ#kˋXBlzݧӘg !k)G0y/b}t_;W0glӷOtQdz,FRL#DJ/W+ NNEg4KH(Ap}'k +ĝֿyR?/@ nwygڽ-} .j;;Zm:ߜOԟBB1Z5UAqJ@PI~=)WTQ}ӿ\%jdN83wm֖}fLF6,dK}`+6wi.3hz:i 2iX zfSռ'ӎl0Rc_z}Hm\T%"oS)ۍZ0K}Kj[i40.kM/9is۪B\uE[RX)Nk]>sty=yY㪿l̡\}aO79搆.lN鰥i*eVvEkB<<=/v#90SOv_?YOk]~z"4kB@?bC_h"Æ8::0RDh7w.֝; [*]1;p]%+%7[i숔1 Pa4w?@_/"iƐ59Y[69&5S5-[;~v t`8ljId+!Pm߲5{iu?jlIj[mWP՗Jt{moos%E!`>OlJYϫ\5W䵂b{hVn &ZQF75L5 Sܰ։q'?7ݹU WHϒԺu@-9!>?LI'_yB&bLk1Gma?t[a- ܍vvtAU3fΜ `^ۃV/\zp%%Ck%5 i8J!`wcQwE/@zF'es "] 5#Qtw2n*+*+ij2TǹWƣW Zhz>w~;Ko>yŃ]?m+܍o/}Oo.SC wS2RZQX^Mo{}~}oV1en,H["f%z]!~?v7d8eVj ;~G{=z_mWH/i(#4ق`7i.IO[sM>R=sv=Έ(fYZ;?uCt6T85ҁ3]bj m'?P5zK}!ܶPiFR6cMʥE@'Sz[+$Mqw֠'X"3M(pDBR'cX8; ߿{}I{O^\ʂR< 0"nڐqߙ9cl}lǷg^w+0"NSnUC9lma~qqz"# QP#o%\y.47j{[IXB%͍ Cڏ,w2zլ0@# H 6u/ (,+bq}0GsΔT:dKdSw6ƫ"TWl#F$rɗ@|ОԗEY/"g%LhN,QE9&BK$nv7ʈ HfWvXH Q/iUy:90ђqi{ȩgΏf#n\{l1]XP$5‰ܳGZұfhIN[R0 Tꤛ6__hݛs4#iû ޶AQ]0XF-G j)-7ZJ^f{|mKKe_?`NsӬcPNcc slmݾEO}w7k>dB]3bԼCuJdxDēdu3/ݹc iH'/> NN?-/lhGKLC'exخe2|S VG_\bا>T PDϢOR\2-; wԻ48߼1?u{L6(aeC;BGGt=dS|pR`x]j fN|jb5RZۥz]dʚog0,~K_%.]^b$Fzoؾ8=2iRW_LЩHFby奷ߝe&2soɱgӲ0ڵЉj/ۤ̂{\xnafO[~ݳ1SB4Dה_h2Y_6}n{ IԤo0ighb41}b~W-L|z$V[eR~ J]P#z[8=C,W@Lx􂩡 ұ;5GeGzj 9;csP/9,OwdW|gƮ_ u+όrj(例CPX"{5؂Oyi R_3~&m/Q o,R]No߳R.8~k>r"vY,,8u eq 䪹 'YYczK^t@ :Ly+4eftoϊU+j{ZWd“@&O\U!b@|6Wb؜'~ +63 D4fA &nن'z77Ƚڞ4@Vpt6t&DbRIk1}y+ ouŽbsReP`Q16 @ %U-k}ɔݶyeH̀PM]ׯ_؜7@W?6߿vڕFI]JG6 <-n9h'ߌ?BsksKR|oˮIPވab'{w6 ]H''Q昡a&$nkX+%֞Z^O&?XKO} Pk6g?)3ok};b-}nl(gHM?\ԭlG~* Ke쓷{h>d^z탩~e(1 "qăx'!р[4BBSMS{V < q\!@^&<:;7nH<&v4T9,8Y9Sp8M8Cc qŹk)!~Ev}br1efR~(}爪H  eGڽ B0yC4PBTAj{1;x{|t|?c2JyG^P) Nd,b@Hԧvp͟MvXcE+1T9ɲJ#+H۩cNWe(a:^gV%G$;aJV'"%D%Q\  q1Ӧgۑ{w"0 Snx, $rEǁLH'`ӫgۑ‘_UkIIgj%9H)B.8̱ӝ=vP?cCNqU%3 < Pcf)`q58{z32w |o:z߫>邇_aGM >R|v9YtܽP4亂|X2 hÛywN~s%KrMs>_mo1H htC,Ba%aXzd4ϷΑ oUYe!>0Df"_֣^}VTVQ6!xl`=LW™H[/,<:OTFHCKO,Q+Ml8qg)QGNDHd0n4d[?|+gF2b0%2%R#!kdlSS˼V@ R9.~Qv+S2>4UA"c%=֌M\$4h1At`(UȎf:-t;Pu~[fR9;1a_P5[8pqe ܼ概>75W\laC M+ :u텽|k?*4mxσa㟟y hnLD!+[ALsGM~%aPn-Ck*ı͢R- MHб!5sU1Z^y7ٸȩ}4 (9#@f-ze 뺎Xm6ڒ(3ԡuEq3FfzYLg`@}_&A:4ʖD۟=#TH4h\Dڐ ӽig/<۔tovii%힠%r6ۂa<62t5F5Ѿҹ;bά ]s)1)`" O]':t>9e0aLFD: w\iBo`[ ˂0 ٕK/<*2Kswב#\/*.6 '؛2&y*T$8"j(-6<`r6dS~Տ k]is'W1 4^8VesYƪVA4 pPiv?Gk?܋޴tǥjOnxh %GeS &£"5 dVX^6x{ۊ4қ2^ u̇,JCZ0L!UUKoO#Ŏz,*TF %*g oBͨG<9Ǩ_}+HKMHMAٞR@p3!~6=4W]vpd"fژ A\'"JzRXX*ob1. r820Ign{{NKJg#[]>aa(`9e'ʢAZz!b `6y.ZG^WOH;Ld!UgL2f;:ou!9jbۢ>QaDt -TV 16e/F,,!As:.7ķOAR|LMclH+mw#BR e5 R(Ld .` c?f[s4)Wyk}ܕ$(E7vvxzwJ!M#hf[vC47C iS. ػnm+Ρ;nh5=m&Iq@)Ĉc?/R"eV z-F#0TD$ϜsW|D=I kL4KCkh@44)Dɧ>?3Ib Գr Q@R,;f2Z`z^fAHܦr1\:HuFy<|l<@$X87 X9Rf12E,V-KL anMf+g/:w^D2eՈ#Da@v">'{;u鵛Hܝ!EbK WY*%P_mf$or84m7Ԡ/+`"NCj$Y8mg~OݫHtӟ OlVIŐߒPD oEW'λfFb,: 8! !L%"ΌvZi;zVglM|$j!x<Ғdw63Ҍglx71=p:646-}dԣ=TLˁ.!_*|zDH=lNWa|&f8M-@5ֶfB0Hq`pp֣g=Q23z#8inBQ(p@ #pxBT7n2Tg-┻LExQAd @I# BQe QS<4_h_8w:wRcHCS*pt ŏF %b^@D]jי-6vUv8V1Q(2LB9&=VJK&V GtLƻA6d4Q$FK8vHHWe\P3/ܹ'^w ZPKfëI֨]؜IOp9Spwh=J*8=M;dn[l&6aUq=Qgu2(S[,¯]٘hD&⮸xWUiRgHF%T&bzTÖj AżWXqejרeI;OFnMN,cGBX+{xn.p+Stw/{KH'(#^d[*MI sAU/qq4q@ lhL7-VјF*}syc g}m- CT3.8,w\f7(<1Xs䙩2C ϊ0a% 2kUKi8G&|"/WZuS߉Q6Ulm۫u'Mkɣ1*!WPeC-/>Pi<﶐ t7%ZM[z1 Fz)na%Dwa~יn~]#K8iFjLU9K|A Uj6k?JpqlZ2dfd;gmk\)d0h$(8R<CIns̍Z.@j1 x1o@$;Ӈ]5Ѣj !| qp"Sx؆X֒ &@2`#j-N|JM'u8_OS}٬4|9?h'Q$?˜ʮ8IX[E&DMc<;yZICvޚ>a( {%fFGa#'s3sn9NTRɽR0FHTR'hKg࣑gslV PBJ,S撳l)-N$rA" @ 3QM <]b,8H6{!G0($5ŌP{Cv; Yc  Z|H tɯ[}W3!a tw67~Gmt+}=YHH VB5{{efe Zdd1b@N`J`zgfP}\_HI$1AU 9qxCëjdϾץ.Z8H(Lf: [2C;A8ȭ&{t߾:tzVFL<ÌM@}IX:2æÚԐOHB $5  (\* l,©")h^⴪-CTS)ɡ6\Ch{?e[)W'b %}\z[. g)o(\}p y>8QlB 2>&I8K'ÐiuȂQ(Έ>k !&BQ !kz,T?9݉Cqk3L;G;C!2ƈ ArB AL&/@ְPN#yMp^p[y][*a-r*$i]jx" Hݺf"/'$ ҙ[7w.WnFI42te` -h=#֌YO#}5gP_P{cy ף a+*:\',PAPKJ9$0z@iޗsJ{?of3Oz=! Io'j"w^R3e}eH^:!{MM?p=#fC.5Ė+_WUFYPî@2{͞qqs;@T"BҰx\JX|"~\krVi$7Z:nN<,d QF(`s<heV\RABH5%'qh@cjdJ; IX*Q,E ,D V\:T7$ƪBz'V28D. *ȐL&DSpuTނнP+`-ZH"-ZOr5  ׮"J@cmd&:U-CPA#Dp 7-Ī5n~GƉh)Lr>= Z%rV'`,t_xP }XEKF:mEPA'e`Xhi3 餋P<grK!RӹkG }@&97ZSA1<-d>Χ`'͢f[as`5tSM>'uSl8|llTٯD $+^=V$Q 1T$r4:nUX3.%kɡ܈&x$4nr=0):cECSkƳ+nl5ڍg#yCdQjeRv drfǮ Յcpdŀd @XQZqm|X)J[od(tʎUJmaJ - ]85&)@A >HYj?o !( cD5^O͊Ydz!;fe~"J԰ vHD)D9 [ 3EZfr&abx-#sjP6ȼaËϴm,[̝(J( "nBۄ読M>K%ϓN!d*kgբ6Ӛ=St.@=$qQ]8w&v)d%ZDM7x"AR˟M[HÐ` w )y2 *e-^8]LFgZǡ$dMUFg$)z?1qFə2tK]$0S<SYUf^hӇppO#ZȦ 0a)g^Ү}:z M=#ZpxʝgDm/Wfef7=-jw\Hol+;f>g:2gm؍݂dG3tlW:}4upW̸#m8bﮢ5Qin57?&5;;0b0YvqA: $5g~# !Yʻr^'[8tHE{hzxϾ^x>ON̨lq:iOBX\,sm\N6Gc@{4Rt\R}b,iÃƮ_3t\/|^ʄcҼS7a[߰QChQ7ؼV(JrlMuI,Jiz SNUئe^qʆ L Lfi A !dwRla;P6 ~4b2˂v{g"QG#-UMٯa%')ZtΤ)BЗb MӴ~[eъ;}4glo{1ANC䂰3jqG)N؁}ib8ՍQZ?8l|+{~RQ'U͕+@QRg'& JgG Ҝ$!-KFFo +c:;0!zPaS!l̢(+lѬT#S;d1 <[V2&=`ݔr @8`0.?x0hUg1Ƨ[DMTQLVX)3T RL QT-5q[ݙ8ɤJ;"#xF~9A<`Œ@!9 2D̃DxZ™:"Fp Tl؊|Pp8 ( $+$-3F Y/ĪuHߞa&>~'~N!‘LsD0@Gx?WgDƠ:DU f* a=(PҔaW{d3 71J4dJ2-VVN182SUCB%އ lk Ux4fK!1LQTQ!K8V9y7#t"PQ!V՚0ԉxX!"JH/2]0t6n&iL"D&#(YoV,&vtyjN sY*s& 4 -ndܢj+elPA]K7ФblT44E=9l$;{1aU WTtt%eQM;L7>z0 jO*F ډX%UJ,5,Ÿ ݧ4 mlleQ 1yiKf]W>}RꅇЩUALf$zzb+'k"D@;+PT0@!MFV4Ͻ^(vt OEQ)?zC-_X%/p)^֮?8{ӯ#L+a5LqvRTEN<i~͹4, `nh^iUy$:]R~`Oj*66!əYGyLG.XK[nG<J0=jiTo9__^3A;^UX\~6vc /Ʌ)ӴqISe])7}rrԅ?&lQ>&*;v~vPi=Z`[D)Iq{hDܺ~R0]8 *El2+.%4\F\~#N`!%G)L$cF *1mDHONJT"@'=/uK3WQY%NoDj%G +Cɇzdzg98g,uج\(ThY8*Z1U21pN'-j>Kj09Y+aaڬz]Lĉl-)VƧnu}rmh}%TR'ZpJfXŠ(Bp< b$xFD5 2@J)(Fy{x: J'C,5i:DvoFڳ6renD+Rb51粑=$F6dogY86P5hZIl.Fڒ˥N<$$$rAvra0R3k0[&Z 9GI(Z V.x,`Z*3$ rB0⫈*\\J&3|GB<,Fla4`=b9hi IWȱ0,vC52tHNT!a;ժ_+Z轢^!LtPPTNH[7Lhv(9HřXnI.'([zlL@ 9E^dAAET8Fs?9-ݢUBiYBlSǩSA%B?bqECD, Ɵ"*ԨtwbTGwin'fQ$]׭77uO8 3eEajo9׈yvRAJ]'I˫|T&mfXہ6Yl4jW4L)mGU.tloaMqnL`B8І!w hQ 3BgAL)F"j5#YBDFCTvS[r\V@to:H\&&SrӹrH.Q:dҥI7"˰_̌1x[@oEv8%Ppsqq: H,`A '|y/W8dvJ-j ggg?KwU4 '"ewmQϯGU*]$oMY R-Gt5_Gc¦[/qSA{\ XtȘY]K}y,ghN|၁*NĴ?=̪#?ĕ^洫Q6k2?ݛ:xǾkvCv{xZVHumBdwW* ü5^nKS>swe1TkU7#9QuNbіf~ڐh*|/`;'1{iNpSmy FLi 44eu49{=&yb E/_(ve$\R6}_ q i֊joAo-=pDEAMOxr~tyue&@x3?OrM%ɦBDP+4UxvbL HG )0X4r#:)4f"6φ'^ksb|$/4x2`?\qt*+hva5euv)ro+XO"veE[nz%D~ Ƥ=1DN81< Al~ً)>l;In+J̉g'GM RTKp p4}Hiύabm8rlD3cV[P X9jhPN,kb5mjka\TXB4z$%Ƭm#XCñQϥHsGРe¦5$Yۖz,I=9\%;͢-+/=?GwC1bc1RFzت)$B7/p6:>6-[]dG_]ψa#d,Ɛ-M"5vT`Pd7u[.μjR B.4>Vm54'i+a7PBNzbڦ۾R'Bu@dd{H8kfDO6H~wWv2{jTcA"o&P Yd "l) [{v@r-@)"0eΐْ #D 1ZmP %u55dvJs HϲQݏVjoێ 3R<6:vZZ *EqQ餇!u=Q;Pa/T#̙ ATG1U &$5`rpoLSތhz%"჈2eO4I!'jz4=iV2#$…v$*".z Pp9lV:7 Ђjrそ҆)Gq"F=!9&'KE3.Ir PCT`bj|6I=g5: R&դ> ·,*$r&DQp/!sѤ/Y$il#jgQm/v @2P/(l0p-$cΞCe]̨4>M˃Ic4J d2@E>  X'XӢ3a,'NOJ&쎦%4Eb>)̪;WS%< Xk$6/ |o(,boe`LZe-nԗQУ[:dJx)7.oYU`Y VPw@O>v`"(r$J>-,&(ΐ"qҌV5;{ ?ODAQ<Iʡ{!26ϣNp)z C Tܑ^J:{<5J~3Z])dSſH|a*Y*R,E!O65i/z Uf/Rl?x/!9`j6wCY=K2L1E[.hbPcMWoYoþƄ^9޸QC_^ޠGV4M~{Ƴ8Ie۞PP۷)Xf>M.q:t)YytMɐ6~~,ܾ|]>qk,BS]y,&{N*쓱,*L^lhun*=ijoE:ww#˅/· b׎}x:n'B"UpЪPq`;wD;!oL(*14{0;*FoB 7=\7f;-Tdz,@1pX#zarsMX~Ctkm۟Fqx־Job HȧVU{-A###IQ:=Bd{j拵Qුv6͝TRƖv#SrR Q]drsq*@*]{7@UCoeFO,PdK ݪMYg搀+BAӬ,T@<8JJPRI7o .wi5d[j+lM,O,Y;uR2.#Ɍʰ6;6NR$Kdre3e⋝w5aHK٠Vf;pSI`ȋ5qr[gK.mP`P yY Q Մv MTR<+J&R*qDHB3U<-)hђZu;xf_Z^ -q!ո۞=g_.aR0p %NI:rZ^Jzi㔤ad(8ls{u&Wr#ZC;m4 8&+%( OYudrH_L6n1 5KDTx)$i\m&!Q3zYG^8INێqC@2RsB('bT07qUz!-9;lI6wN Y'F$$:kGE懩4+*Hc"Z43&IAdql”`}Q@%5,(CQCUcę)Eթ \o t$4 (#d# 8wCoJ'_ך h&҄@QNu# Q @01P(6:l,f@#=HݺTb %8a>}Aِ`72Dq6Q Ad::' шXR9Mȟ͎;L?"W%#/.^<:R^[eNǤ#xэqbtE*fφt|8ˢky3?Zl܎ƽMY?b+}V *;Þ9wUcz4RaґFQmhjއLt̜Sl]uO2t&} 2e7 VC }$nD*5ΩUS;ߍjoWg X-P53l 7 w.w/Ɏ$G:^S-!nH93oymKp1Hpzɋ;7fw0<׶r?9R&[Of<ߺRrPi+֗vj IY-r%cwύ1T_GW'w x.?Sz9Gk'/JVR)H_[\ϙti2|YO- _K;Iߘb=9X~)o[>?蹢ۦ^b/MTp-* u~dźO4ZYv ^]56~'Esyb@\ ! \ݭ"uOҺ;)bjvM0q1cۻ5ܳۂİނ4ݺש8vxs1p=Ksqڈ[{a @pmڧ%n$ˬ*phNڟ_,!]Fmݬǁ'>qcs|B"3hձaeq]PZ%RuL#bAR#U{BD\fZv vlgN4[s`\-{@za ^nƜ3a9Ϧ<;D w2IX,Aʐp^qpFB`pΙ76:X[(NoأCH|"9-G^ 0gHDlE} <\{[hq;WYRv_rHaA@,eܖ fSE(  1+ab]cDe'%zHx]QZXCJ᭗+*pd0; 2PM250v87ИQZ\]25%Tl֬6l]l5`]!!ST X;ix6ibC(L2gE^ʋOzw3#u e4A-r{A6v/-&_`E2f5﷗uMzG/y r6E1QQȶdj^?s7lՂ~/xܡ׃=bԪgk4h DDZPΑC"DȽk@A۳&aţgDVFMt״/yN&X3g>װEUF#9i6xNpEabC6)]Ҵ*JSܩkփ-7iRQbNiatv7hO7*fBXlv HXK*@viN{4W0 ,b$#D[v$)zu!DBU{,;=gʞ آ"ry]u eZTrAQMcz2!E !H2oQLДNIZ #PM"#D"hmLE$L_ch(VdJEAfFIz;Aa}&S0\lO?&P%}v:l,B!ΩE+|f^۝ݬFIPK$XbA Z!&1 _IUkv͉np ΰiEko"[E 0bVK.u4!3͗] ^$3Sj{:``1Nvf!q (5M3umDY S",JY6$s\92\j 6u!RA eqጊJ:WYHIѭͭ~!伩 y)-X `T r-diԷVxFRߦ+Q+[7WwiawM%Y}laG4G'E*؆sL2j$+m_xޘjCUh;ڦzֶzol]C)m& vtpO +Z)~j:nnji 3mSHuÑnm+CVϲMۼ]ǹv&f.`r"\8g%C\Bmsmyܽ-hm wh(ˬn9l;鶁5Zd{ۦ^T\&C0N"5ߢ5nu{;iB_ѵͧ,%6M;W^RP"+&gV kXT/@88&츰tKc/JȰiA,h3"}峥AscHÚrv C$]ΌAڎ A=Ks:'L,U)$(#o8jMmTc-}}?WAhn W7F= lTnP0fsw ךTD fkNHq .s^Qxuwb} VĆ`5g NJ9vvDCT='БH$8a%z-t~J (Ȭ91dixP V9cRIh+`6c7Xڛz]]^ PQ`%1L UE{cG4L+nK݆q b7o|ͥ&<%8DI ں8$Ei eIkm(߂,*. :Pa@t(2/$ISc``3z]3 , 0R!LfV Ī8y >R\j+ ZԻɪ8'0Av# G$"IXb)B!sU{mN*.NdC"vEqdLjLba%<9Gku Djr &G L•tHbB@Z&9v QBMFB {9N5q٥|֟Xx`^qsQ605Pb\F CD(~#ouÑp:RJ(0JE1 LJMH"QQ%Z]T@VSQOԏ14VlCӆtP|*UeJ1w!<00B$4qڐ}tӌIP>rrS;&P$1}=i>4 R!U\D%@[MqH( 5ϥL8(&>?@\T/Fk]5-^ܑr󊻞T<5IL[_%-Y{;p[ݳI1 I.JiwjϒW]5Ľ Μ.-黅U 2bq1Wd.I@p@5W;H/!M%>=wJ̘^]W%@)aV Y[RrOp/YZ YfLwzrdkUٍ5O5kr dԉgϾƳWM]ffgT(ضB8GĖmɕjbs5Z1 [eR dF*t~%rN`b!Vm0;L&FdV ]zW>_`vF\(wm8pe UE2gb&0ag=j?H$B%PiD&R_ IOJ$œSMι~:D0hQW{<MBg+c j>]I>+AVVb'/s H2h06=gu7AF]Nޥ e%?Ӓ?cZc ielB=gA7)D$刿mZ ` MF6|AIXh쀏=gXF#K$&&ޅg%l&H7Z zвYOdQp7XBA&\ޕ?o2Ɋt\(ȬgnU# YzhFkdSM=,0,%^]bmX9OK&;h*Z98Ĵ%@ C,ۘ8  X_2 A CE\ep&g5hcBakq6 LSUtoW` Lr3΂3Ĥ:nlKZA/x[](Ѳ20U/L"%zQCbJۗ}x2NׯQH A'=WmIup8^@m D' ;E>[['1 bY%dq$khu\c١r@[" YWd91 %JL#%#| O$8"7"IBBtƜ&!g`c$ @HLp>JbN-RE-ŋPLwjo$:C,\"8}{~*dؘ-XUAMM$9GC^*zK4AA0f#/9LB> _"lz0jVsb0P 9 UHIQ`u$ 7U mr3GQ$#0F A8HD4Ec;" *"I!ko*&eG#2%:DE( )\6%O6 !g'I[@ \"j(',1'[hEl*bz]%阈e'I U@ޖsYePn: AYQ *9,)M&I8Y_ H7LO8!2 %䕒Mi_5ͱ2z+C߭> իV!o+;'ͺ[,WSϬiϽF-Т>Mfa8hUd|Js /k3:G4,ɝT(<H)/&z/˛zGԏA3J^TAvDHwd5F/n[n6ۑm;<"PɅED(#DH#MgC勶wRPMT#X .C!p75#h[!f6 K9#'5Y)"X!ږdP4}Gӓ3YDR d" aҰnnka:S.cdڼw 4: h§GX!fn\t0iEr-ˆˁ2RP}$js`8%5IP]unDzsKhsVf./ϛ|h\ ڬy8&t8|]m/BK`+OVfy 49#wfiFS^_b6TGal$!_r CRjL y Yc,sKc[AJ4L<#+A |`fL;|*a'|(՘UN*! Y-'!˅O\-X}*vQX&N#rOgDd,vrvmxnAC1*r8HPu#SHc_\:p UjA3*e}_gOh[zdXdj6]'T#E )%;}=ZMSn[rTH&nج?V5eY4M OpZ9,rf! E ,b/k  d-36Et)^XdQL4)ne; *Ec&(eX4b0m1A,FC $`9 TNi$D i3Dh+13~k H޻Se"WH|81Xo^s,QEС.mST SH" EEsPv~TżX*xZ~L&fUa~՜FXGYA@vQPD8" O추eKIqxdF&}EVf4  "^es-u"^YY*`CF4*"&6q}bi]2H3nwf87`Kx ^J"%S4V*E+FLm>/ <;B Ș@ ɖDb⁹e%Ih F(60`S]>B, ng5CgArA!4&*A(%&U׈i` I3xA4 [ Y#IDdۓ>ZK]L=F"h +Y9k7ڵrKcԦ+6Mک;+[,?a )9 b*Wj۸5;0ef* 5n}-lxfnڅUT"q$]8.QJjLة#.#xNi֝1$Gj$L6L*P:u!PТk㎘@XZhc7ćnJvYE5F` HQH%꜈q 8 HvJ4 PRUFD VUf#o6kwjBS햬y+tZ ?B!1o{W+LW- dF"#:m_D#v!Pt\3ōͯu$D TlQ'z ,?ճ3B A6=k %),(ivUKWx뙙YyqxJ%|- ]Dt"-Bmj4"){\bQ$ (aR-?.6Vp*VD̒YbljD<Iֈ)XFFٹDIIK(EfF0&FweSo͆k[t6&Zl6=d&^C g>sI%9Y] T)UcDmX D*֦8$V+1$VsyȦZh11Y5"h\a$لfso %6BD( T}Vҋ\X*M̺0j`3BNn۽@)M$% X)"Q yԭQ=Ø;s/.׫{e JdBrF7M5U+[_x珄̻O 68WfsjɌ NU+a㾐>FFяw>Q\o 8Zq{)â*K]0gb&l%py|kqbuaW Nmmy Ia%Knz j =wqrs:0 Z׺e-Qd,E[eV=rLy?:ϕk+U&(26^}@j$J-5/hS3M X;LHKpƉ8IS%Pph $NҤH" p&ʓCZjEf4v koC0fq5CI$8HX@R %"ҧ GС(:XD=fAc2AV 9K!u!HGƔl q&IܡQX2#:Rq-M~ZR`Du G-H Sദ$ OQ&N8<b**e焅rNHN @14қ2M<)aeKD[vd,h0v"{WbTCC4Jb\ ]-$*$D#t&5:,O P@S'C)i" *F'd 'XlY1 RI Ds, 5W6&A47kΞX wXư:ٴ\gӱbhM)Fl\>4z+?s RcqWZTNC$0vS0¾Fګ5\ªa0Dc5' qRd;eSqd1[ h*qZ@D'\ kwvPR"NсUbḐ%8 =K"-O3KVLDs-gHƂ[ xT' R '0 jSDX]CAR1ppX2 a6 ܕ{k϶ԬDᷤ~dh|Jd߂Q}c"K/_\=[vYF2[>~T')ZVM\cY홗s|NF +yU i ǗMcuH[Z]IIZ K.?}]mJ .%nȅV90!u,9֒aUXhH"rTrPziĩ` (``Aͫip\s{bURuFaGzMmeԱS) k1 J4JĵvV:\@[F-tZ¤"6 XRmFRI"#A)8*XPI I'S)cB ~#b2jhZBbS nƒ^`&T\pj68oS^2(Ib#00'p=Ҿpn$K .:Az/@+piG'2\b-N1ԩB&E GIѥUq3&"@EH_IV%l8|+x,uB[vDPYrf'پ/^+t,s =^` LC@b?1ƉᏎ M_]uƱ摰ŜW>pdŞ0 =a}U ɳ::&Aj- Ma#gd`}i4p[߾ݒWAݦAuQAFquZ? [yy{/7A?NNjlwd(vMj{Bo/nrP~f" v۱X4M8߀>^(Fq.ViJ.a@@P 47H9wG8!NY!Hd_IKW=) 47rP 0 xI Ab#FK{I\̒Ho5jDtj1-byܞB2H괠gŷu$]_\zvlO)HT2jN4Տu.n1C0c(׿?zh ηsHavuj~DM~I DY)"SA0A` $S添+)cH~u&a XaGF{|MVJ! KRH&$k$&ddW L&4pAB)I]!>|MvJ%Ipzn,Ƃ%!V%)$IIޮv_Ep1c!l4 'pƀ5X%W֖QsIrS{pZ16\bܜFߘX)F1 e=Th,L/}TP7-,sêdU4Ukdz4u³7I<kɓsX ZP<{f:2*DlXܫxJR'Mn>\ ORF d)a\moV CWY (0N*&ٺ*}ʁDE;41[Hn?v]އ"D {ߝGma&ˎrG<"J՗LKr{H%b*(Ǝ TUg&Pmz=pHqbf>qE~"$]&5cMiP{t\Y1dÀ G!D=)s5';ag1 @B(r$uhpGbtMf 8'A=w)Vf Su'cva((1_9UNSwNe #*vOQPхi1^}( ʼn\4-!O!P@6=C\YqƉ$vL$AUy*PA8SRN@]zRE:x{xc&IȀH93 Ř1#8W P9 4 >w A.cLUmCvZD:C۸B]Bm41) &4Sӟ^RPpI3lr y4?2&(6xrgOW00n1#mĞыCw{f O& ؃-N"m&1'Mַ/ʚ<>4?>ˌ:5]+*B,&I|/|nS‘F#1&P#JJ;rXo\t6:-$kю p)˰[7n VC/b7tox>wR)"#,`K۸h'E_ih !c%L:%~9&8f!#%^`і0kvHᖁ̦S^g I3XgDv`~pIIuhƦlJ%+U%(7u3r6VF&3SR$"whp|A[ܖ=?!p$`$BRx0?w6W. WT8bfX2{ @R BP:ST0 Tg4R1VnXدn\6$X0h8 Hg$ RIS7)8(cU ("4k>`UM߶J'‘%3_82V f :ۨ Xȟ?SHquEF~;-c; ;I_3r`PtnT^_ݪѳe{ lJۜs/'w!r\Y3i8VajtZ؜~f[;mtby {[{LGޘw.WY+E%4Pu} :+ϴq*]/w,?o[~϶YDZr6׻οs7/N"hjlrn_?YqEy"x,۸:Y58%hı|m6=T$]=lMRaP)dUO޻BIWndZqxFPĪ>WGy)?P{[UfX?zuޜJoRLl#/XkJ}kå]-r[?SHO~,]ԫG#1hT,o2n Sp MƯ`qoau̚6˭=}u؄5-¿uk܊;4u{.7붸$UÅK+<,YOp閧6}otC^fR-7C s9A$PGZp=lxƗu/uFz7,gVnp׽.pjQqRs#̻nf郕2,@]I#oC\(/@R횂҄TdG*i`,TO'^dxej[}m4oh?lG#=_NulvSTOy @rJnݫíD?vnwVΪw>6^0M4R RҙjZYf!08Td*j7 pTUvX͡{0 Q5' 8r̊(@&=cEtD450zΒ9j)G[w7ſj3=V;/\nۅO7Vڍ++KGh-O蓲eշzp dmzs?psUJHBjxU{.kYkˆ'wNݺw؟Z GUC&fʝ0"NZk&7_2zox]Ӧ5ܔL61$QhUpnNpg;#6lM']t5%eO|2=Z@E(JzNx\uPbwU?#餋x87sׄJNHdM>O'x @[,`1C]z P *bKL,{h$T BHgTwye}Y$ Iѹx-H^]n$E(?{T[K&蛡W쓺5)pvca"iæiHZ:2Wm 'q*+sP<I?a9"6}~=C:Ҟ_On叫XA 1tU:E*E2E) VY?ժt{:ԧZzj~\jCϺBe!st Z-~,8jU9׵lХRH$Jg hLnq*¡[Js~kO7ete6:;sO!餾L1z+PV9|`'{yuҩvzYssOɘB"a֏u3XL~0o hyB(*vդ^B;#1_/bፖS{88̻o+GoJG8:#`K=gHk-Q}NC8<;rPk=?Io=j^VL}X K޹2&B[٘Zx{n:ΉHyXcc?VI7õu;a*r}Əkn1А"l>$A=8>~l|Wx+ 4_|wQKLjXyIW ܷH'm/@9^{ŷDíH{X߃{7¼AuyȐc;|Q8mw:M2t5Ĵ>׍;ć_{a _}7'&G=TeFNa$b2B˾8[tB՝=Z=U&&Ȕ^5-0$¶Ѩtw_χ_4-fdG[:p HdYNj} 05/&42.ގַ0'ʶ_l21TM>Ѫtį OX ctPw\|enȠ<\nwZ{qL.O~kw&{(|֌G5EJߑ4:?3{p;;f 6jIKddc%}IՈd0/L&Ĥ=8Z}~.Oӱ,o'r_͍֚͂T,{] uZ;h{v"x`:q=pXCMw+~6М#{Dzڬr3ТENVW?O| 7cqߡSA33X 8|/МX^UHwm mMOMn]{Mq>ZLI #Wy $H@ŬF{yYEoiv;3<ʧRvOB(H X տ}y˖X7%w 4[^y^;kag59 q0xF'k$%1XEdSQ)qsl.lO^+ҖWnhg-Yug! ,>k;酑UKj;OwDk(hgq*<̶s?ݕ@¬ +݋[qoZ8]e?`կ%tJxEHZnDرd@bLאim?F_b77M3)hY|s ?ue8x?'S/_7?qfC#"k  iTtThg˹{W/I~{d&oGDB-*=*,ʢzp꺜[g|Tx }M)^!3htVUOw]Ιg|ԗ$?/xӛo֧I:4*NB]soX/H ^0CG#"#|W) \z8w_,|r޲6v7aP5d}`Y9 64 V)>V'({+ho#)'>sr&b?#fڌ2#헂:1ܱf6,w6imaw?~zɍ^Cv,$PYDZo8[! 8Vw*>O/}^n?tv6V:r :^PwPo1kH٭qŊySKvAo+Iێz)'-3w1oZ@*|6 2[  ^9}3fҶnWήļx+/F')vCo" rsfMՕfb`\#4`# r+ɯK" ;YiF:czu s 7}dG ko=JE N b4{ɎcGpEsS@=>LO=&qCH)'\I)H'+3z^b$ /]ˡ t瘼qm«,u/'$b&ф\AwK\(԰'X҆jDħZۻGo9$Xǯ6;2\N_4ط4lMHĴ‘{aVh W -ݝ9 mI ~~{MV' LLr-@hvI36hEh^(YXn5ۀlH=&o|3NtNsE6S9P. wE&Sv]\w^xgǁy  zE\s[^owē̄%̦h;YʹؿR_!b;y-hoS'cl/nõ̐:`8Ph1-1, '}NsZm`TpÊɇ.YߑgdpF$I ѝDdR}{i?C߉e|Fy>_ iʁIc\<[t#;zTV{# XѕjfTPM-Xn(QnCt#j6Ŏm<VtXMv}z7WH*mT>r:d{!MGc+ןk;H;zRW)N<#oCۉ!Q錧TA'IW^C+G-w,t$I PPzy{x-D,֮_}63~©ԷKeӃRv/ܮW_uHSA)+oʅxFgF6Я>ێzK'HgTXȄ;V}FQm^ԑ;$RHCՋO]dvq]M}&< !g5C72`dX&فyӝ|a;>38!o3^(3Q1Rc njx8h*6ogÐ3}M|}Fݖh̭~L0fˆ "4em h 4] r3lai$֛37XPuR>z" $8e+~?=( jn77:JFP5DGbrDD<)hM}4RcxQ*Q)b^t!VGIP ' YcCHDd_ c**Wt 4RAK# G5'#]˳'yߦ?;2i᫳&),42TF c`XE|ظ̄7d8ʵtԉ ^ANH2%3oOx|NwYK 4c契V V&PAPPWn\WO?5|F݉)ؒY"Ȝi:cKh1x82^C|8e<?ּl!(wF+֞e?n̝loU Y!|"Ōa64ƯI1u;_:*e IFzg٘wE!] R1(c0bAt:3</shlvHP89Ò)}I|yH jèwcg ^s'@gk;AsxXx/ۧj|0]PMtl6`U` ];2}۹j"vثCϽܧl!c0 FR=g>s/\gXy:2XVCu!_li̗gH%I0%ym=>EDG3y!B$ W@b @s8ossI^y`?YJ3w̹;QVB|pz:I<9mc5:@ 2\AT E*N[P#`޾yzR{i& rʇ hq$,%#@|BN]NB.a'TA5(hH~ИwPoGW$=\B) D`2װL^c1! ׇdK3]zRwx@g382]-Π4'=RB&R46^Fl\Vm~blҘiZ5Ԍ MfdUtħv 3;H];\czzќ٘"Дt RBךNrUWGv9 2Ԑ`NuXʂ_NDv >`$ƎaQ!#H&9?#$+cPq\|80Zܵ-Иs@g.L6{"dgY0lb X:Xi˗^Sz{ Aw KWW~` Zk;b \|x` tVBTׂ6ۃZ$͓ \ <3*ށ9t0.m6,  xv$t_}Zd =HμYρ(:H a =Y$JFpOϔpZ<`B"e6L6WsEl.nklh¤(^ ' ۷\{VsvK Q`"FYf+4S&ݖІIPEEC%M4ݾN ؈M#:܅Pbq'޼$b Wu/Bes$D GA_a7ȸLJcs (a썄q$Ȑc|J$9rgE#ܘRek*gEmnI?^F]iql;azS'zMZ`ߒoBb\)\’ L pDo> IpWF U y1(j(bL+Z\ȇs;㲛9C#xe#&!t!MEcyw>.v ύR3#SL(9H8ϕe$տк-_g4+ql˨dclo,x,37ع8XCful܆\81z,#K!)D%C2Ft@ZgRs}{ 2dFBB2S PP ,SjB(,TŪÙV_ϝ|䧪SI(c-T H9)* Gfe*goɒ ct %X6yM'RIKΰ:BNEޏA)e`*P2˨=/OU$~N@aGđ҂0 NPm5ThNB@ =usR+$]G?ZJc%xpu2`y$ BG&u7s(GHfAU/]`@HG IRǞՑ ɵBvA3EsQ}կ_ݚFry*:!*3ploi|J%e"%4/hulw|{r߱8i}So 2YN-5 ~G& SI<~7 4&SJ%&2;1vO*u쨚c"W2n?UFAl" qPAjM28HV A:y];ؕ8`'I0 |44<.E3q Wq+]{ < Y  `88Hq#rp7S$?r+ XITGG= 18 7 5Pgb@虔0 8r^X4KkmN ˈcıĞ c R(݄!hrW~<@M:Hj>z8=yƋ?}VgZ ~PLQJyA^^`:޽V;{YK{_?X]2*߾&oQ@]}nv=w}5a~6و*;ψ\ 6+L.5 !s!~'~/ WpПK[o8FhwT>]iA h.͋S-5{8I"@RqF87ʩQ 𚋂є0cpL e%ʈLkFI$`?ڟq&K}AGFPNH‡{@ŕzv[#61E1vQ b9 BTm0F#WtY(lx @p3cy??'%i&&8Q]Дǔ:tʃgbB4>pfIn؞fl0]?lg޹DŽ[Ѿo씦xȳNCA1!K_eՍ׿;]!zryGKԖЛt`ũ-~]"3\|HM*O).aJf@k\ny,byf޷Ynii(!KMΜqw+Q%+>A6QC!|-O| ee[R# >L' Oɭ%xtdP$lihY}$(|d*f?;],>ke0;R@K/F8JR.c lXvFo0b$ HOy4 n3%-1ݲm h쀉 P 󟭌w~}鞄HS^2꘦f5^#y4S}ZcÔjJGo7ԥş]'(<:2i$f3#@q 1KQxFsf?.ۚg#3:#5刞0Dt%LD$[p^3jN;x݁ZF\$Yu ] Bwdݘ{?vM[(q#! 2 (p1T%:LltZѤ8J9;,)Tg]-I#Y7J6dɷO>jZц$"= S}[ܷzn̶ a#FA)㢮^ül^x,/ژme ~25 qm;fQozE(um`) bC(kl-P+U~eHYԂJƤ3h# VQLȇvZ?*7ޫžLcgZWq"IRmjot}MSSf.7ϑ狊ñ#J&I cZ#DkvѦHf9:GL݁ӒIPLLQr@~սkAɚEj_73sFYjӯvW<[A L E:.:l]xk7-ЌV]@jP t+4x.@ٌYD8ꈅuR |'NZ}y \KD"\J<Ԑ}o-tL HW Wv)vo30yVK^n@%AûߪOלy'3 %Ļp`i+ zĶUfieqIRMuLA_jiQq2 WZh!Dz˿"쾻p/cPPBpfP>1o;~G>q ́0}`hd}|>`w@cd]J@\1xUĪ8w;p fWNdHU-B7$!\%Gd T#iӎz%^wȁK:(( `( *T=F9'OiD5Ô w$>'rD URTODSl@#0Ôzm dΚIkw&%raSx}L<8\#b$&U3Q;ހ+& 0ҝhɸJl=>w+qVX-LK4 P;hܲN-y˒QmdtҸkWX! DeE^`(1\1ڌwK} 0K4MWyă4%y4jj\;Fn`LƙRc}u7)3pjk3F/>”*oIoHgV 8R41ܓLB $^̧ۦ:M[q<&š$LO͹'?H ĮاF)RNh@N WXKjfFbޖ'&GQq":B1HHŎ%iH2,W#foAL#KbJ$K D 3MQ6:KVPmmhʠB$ꠁA*J$J& .dp 9YMG#\L:'2H8B8#;|cc-άT؛:=Դ~ iNY7U. FQi+`q/)-JI"F#Loh./Oo>z@jș#J|>ȰW okrv)>kɋ \ t6d *`d{ǽ9N=~6X$:zDz%"Fޞ0G'IFOuc bIS~[gW${z˽֋=&~)$VO>f/V|xRjuHc#ZT&jkLj&]2T2muZYo4`Q`RW%.BSmŴRks@u\y {uu{+RH~0umr Bi00#k*D EКE^#ev:Xz}]["C _s@*$e^d=jewěKs)$,e5dZ Y  ^>vh4I㿚ĥXgeca% Ts`NaM0"ttHd5GrLH;Bcwf}G#P ӯ4]&0QŽ] i%D)/SĬI4A(vƔlZwiLQg/ې68^ Dz9j jG WЪqKTBB6'Uж-7H&S%..\F|#b,t$@ DIzFWo0=ۗb-w)R [jĐ*8 {)=GH[b@׎7u+0O6k9Vxئ DSb$xr$[ xmumRw:W½&j)=9Le!Z&YPݣ%CQ ]"phĸ8@6wA^ݍe~7> O;2%ex6Rs >9P6W*pcΈ;(xvYTYDJ@ZqQG"qkړa9 Jaͼp#͆nYa#E}V;'|DI:k=thѶBGԑ48Ƴi+bIG9~{/ڷ* ]&;++ Oys2t?s܁ʓQa' rQ5]=kF;+z9nRye!W߈A/*d!I=\uW&lx簤/v!*h0M  /Q#rHPmԏ]wn"y>F)zS}/?3R4wg`b(DWZYH @ZNU(3̷+,hYYlPIK q3N_ΎeZ(bMp: 70=nEЃpFdVV}E.Rw' #;Zv 1b gHofp{+HԆ4_6dbVqg{Q,\vJ,aZqe(Ԋ i+l#K>{dϋ@&)odR@DJaԅto~SpDȻhKBBl冓̞^Q$&mzZyp"D:T5 ,Y :U*+D* e%)+@{oGH>ʹfKDN@6G3qۃ Ǥ[7e gJ9uh5grLj#iiReP NiN(zݙF3lt]dYs%׈bJ.7BE.@'[e%,ڨ.,dylIP[2w Wldkh壇omoTL:O7e/bГ2[Qibn6^g48ctgGUJyy1 tFoUVan!]4L3vqQ)=OB BFӷ!n?*rxIkÁxΐ Wvz2 Gr>up[<0A@K q4Թl1XE8Ak');Б/cp{3i$P@Y&NXHdA ,V +7f=V=>T ɀ(p &\cm8K^}G V+yo LlH %ZkB:-O\x1jk p$ 3ZhqG&TT3``TK(|V$7_\` l lD%7LkDA8A>V'(%EW<{΀1( d*F](.*cy1>jhDd"̹#?`@qvg9HlQ]a@@<-i V-R;&RګH #mhltlN [1ZrW9jwφGjP!RD12է-EN#s>lҊVmWi&ւ{4jT|Ere`׺k hS'W1G$"3TƊ&a_;;yo\$mN)#Lj ! Ծ*-ؠ ][G6 5(G#lX$$"G0=JTF<@rS#J?gW%?4RFmpĔ536J^G.^{Л"@vhmr`靦𠓇śiZ|u/H[nYzF=Xĥ]k_ԑo\fy`(F+m߭SKՑV?owIhLz V?2X].p:;=z\OǕ?2ߡ 'HZr@%K:v@עahw`R~ ZQ&0`vxAB|=QYm[dtRv3C(qJUTAU H.J8Ș5H*DdtF+fdE 0h 3O·i]y7χBj^זB5`oVaLI & ,&1$2Q*AI6@D3DJR /$NFpE ѣk"A"E`@x؊`K2\ bhd-FluA/L$]IXx8 j$|N$l;Ѣf\횊HzTF|Yl0tfW0Lyͽ=!CvW/b(;ޏ Bެ#M嵋_bi"ᄠiN: _QXESI"d?]Ӟ%\`-sv{B.jQjH0@7NwP)$Q TpC xikT6I0H"VGtJHRo|ZΜY\9([0jUk0YK3%N~(RƉU5kfĩq<i|:JBm9W ,nF-kqZьn I`٦$WkOHךyk=W56kqXゲ 7M&ڌ$n'sN`u3AD`-+hDJJaR۳Z>ENUCYP XI [wSm5:sG{L&nLXcRvcuiY:??̽кjY.ZGLQN1,NJ2I3׍6\wrg<[bC[f?JQ#Qbа&$y),k d>9Xmhs Đ0H"71=$J 00U0 Kݡdv^jVdN G1QTʊ[0$irZ )A("pׄ rwj!1D<&6 Ę!FV'2$ABItK+`FlInÐENDʒ93%Y鑴H6V:4y`&M %a)aozؓ#3#*ӓbUk " A{!%Q J !Q:X`$5cYhil1i֖ۜc 'e !q42 : &mKr5t(Ę Ae -mꄒviln[19״w@:!GCkk0Y'-A7GКpe(!mycn_0zg H&A":U dXՍahC8< 1p2PU4޲22,&1"Y?1f04cJ%Au5 Q[(Y"dD̰ĵ;%a\?,_"U4Zh 'P`vuPKM5)!Fxߧޢy5IlcˢQcvY=n_^]!tܾ#Р1yN/Q(q€~${X2fR76Db4Ԃ8]u U&n6n+E>Ѡ uPD1F`-p%0t ,d}R-Ūe,] G:'Ms8^=La@XЕjIīS M6+H};wH^d_=\;a-BWp1VIz BWZ&tϦ7jŇgӞ8Hht"-K8U?詇_OWE/㛕Hbcdz|Ja0 lB33Zׄ^R`!ɿU/TREen~_BBZ.|mBYK4`P )#k#"@p`͎CK'td *3Ęb ,"Qy /ı]k5۶hrNg3 3O)e^[aEZޣ˃7r N GmF rx`` 6PfJҠFĸ4"FńF2c'rܘnXkK DdXW2LTH Cs LZkK2TbU)cpeO|QqY@t u%@9mgD4@ BPk+{*DAU\IRb眊ˀp¨ ⪼iTtX F$EWLX+3W$6令l%$=*=ܴm^.Ps75uXk֭[B`.!SRIu$)nĤ-F%Ԋuw Z[5p@ia@ mX^[Z=ok1,G(-qү[W]o7'PR@U0ӐCpaJ*H%8ʤfKBPDmG#Jp$ <驤 #^IBp@ Kd\-Gјʉ[bOa\ÔX6|X7G59.8?^C|F@#xjwB` t ПRzK 8k,C#Q0r 5A:W7QD]yccb#)Z L@!$ C)͹9 hP^06keg{TDՕ'~pC)4Ҫ4SEXH$05ld+FUKx9e0hdBBF۬m˒Z 䶕RMFe-W8_<}rhx [+ɾS!$uN# I{o\~Ě!ܙ/u7X  ޵mb:)}}etC +I`BL!=ctqTTFILKRrsPcbJpkfאO)c7,,RزE$F ('i K 7s= sk32ЙJ'%Rץ,!X[;Y2('@>P6׀<L"_LdIrʉfO ,DP[ߤv}^Tn'b3Ixd;%taTDR&CN la`a AQHEKXȉ+JYneabD4u9lBת邚Ą}KPCXOwQI8cM8 $NYd׬-$իSI (O0@VUr$7 ʐc %ԚK]˙8+e$:2TйDH@%,PrTU+R-Y#q^~Mв \ĭrINBR#"@JL3RAXTɐR1h|#HfKP݅0[YL MR!1`f:C("(! b:_RIza(,$ [_G iɒj'5oz㋗9Ζ[?|`o((PVƗ ^!@p?ɤx_^ݍ{7xyevz()@ڛ7 ^!oA?DP` m 8!O\Hsi--dIݪv8,$#s;pHvc4 8ㄸH`~sc$YtՔeV Ls ڹWvtD5o:T1. F+p e%Umc yÌRE_[sDEPf0,[;m6\ȨfiQp&F)17J5vْ s+bWrk-864:1uH0֚Ӗ(x OjF D@k+r7:1gZ6jpsT@e4jDjCό_NѬ"vpi#!O75-#8&^ݪ.#^/XXzXt @24Q A´6rV`%^lF#C.;3H!V4qo'39e'fx:<kx, INjFD].-:TfǼ?mܚ l(-]Tc}'xrB':M# =%g4f1P4IZ(Uaa\lIfc\bIB_dW0!(,9tYmWݭ5jzU>@ r.02 ߱u^:gFQ5sj KYЇЖ;@fJۓ @ Zb'| & G IvqA[Q eQKQKp gxPB#&Z2N(,a%W33զ~3+L7j; gCmԾĄD9ڱu! oYlnAmב@CMH0Jk7D)nKG) o=Sf(gG}9"NeTDHXTgeX$XI2o<{ZcR+%#a,ʄΏ{>A MNK=VvAA7&6l$  wxUfřO(%z Oh0H&Cp[!d=IcU1%W3߈r` ĨM5aB=S˽効^;_)xN2X|[i9b:X\t'阻$B!#.dCjl$rȖ'p\aue6pcsHlAS)qJk@d c<}P#`A@ iä%'z5*ł"y&;;C(Ca8J9 Q䤦c!ĺ%"రݙȩaD4‘I*)!2, MϤ}. @ *u훵D`HRi %Gr0#igy/YXy* .鍚&F&b m$U2|.Ӧ1S`73P݃/Xt5Θ9Y SPh-`j6(K 0-Y6;Lib)rl▬tpݰ4b["D襴YaoSwg9C%H!Lؒcf@*؊+#o.$ѡ]KD= 4'Wh"ڵ`M϶ͦJg#֏ƿu w+ճ:m#/^g90=.L|mTn͹ xWa },9]"q"θ"4=ʠEQ0dyg2uXCw@Yd'-z$95٪xPKGHiߵ^5`p/GcjRB8|fh*8RE-9.wRqѿ@MAW:wJdCLeVlmZ|]tHa{bɃ [))Ww.Q˱bNcoX41lZSvfG|#UKۺ%Òwmʅrn%*y͖l-]ܢ$Gf+LAVG0 uO.ͨ&YC%T'QiB$QOWGmF5o63JnR}]1Sq3jEB}/(7F|Q'HVR% ""dy2 MlQ:z.e+{Ybv,+9EIV\ŗ;}x78 6W#㟻ϽBd2!7tt+vָCfD6@v.N g@@w Q|L'gC&ivfFl-NkE*OSU ,а/mR Ia7/Id)x˒Ԑ1O;B+*6KbAEz")(k+GhYgi$0Z5\9ɒM/[:IBDUA >}s_]! H1/+*㍉Vh=4%O-[ R@l?@OBm*+?"ctCAQ+HS>xCᄐfȇNRIb 9~EȝnsʏsO?dq0vz4PftbRwlXqyԮOgv/GYL>Ɍ׵ [9ڧJ;/xOWԼ1fl0{L6uXF69w0r9'DS$W(6XYxft`\͜ B4dg1&I!cAޓʴ _l1!diCFK9V \yV9r2MS8WP\䁋 fXQ} Z/mJbuȩ*i 1w lhMXwrLW8Q iD,) dd#a :/PlBИZ閑Ґ8*μ;&7}R!͖\2JOhXwpP35ކ[`6-R-YR&k{u 9aA D|!wܣD Ue-tC8BmtRHek$H')2g 2M j28lj5QI_N l P֩Fhp0* 8qoZKN͌d/&,7x%Pc֓DqJ=mF3!@rIKIc&UAf2]S`!XvjiT! LEe2Qer 8|?JN!#N$DWv59 Ԃd\隵`1O,v@(ѹ5@gdjkX}p"TPѡ/ɝ+b5NiR[_P*z>h Y9rq !+>EE%7-LϲtOe B*F?|dRkj7G pvGm*di)PC<{&ثssoG 1aXJ*IK B֒ܐ{@`6 ƉR' c f`H7[_tm>c gƚ{DS6sJcHcs[gWH0 u r*ק`Ʊ6=ؼqpt@ۣAAbI"N )PNvka enm]`#Q{ߟ nAPO#( g:Ys^@UOnG=6T)=t(-x{\7< Yʵb>S`Uq饚ӡv0ߠ$6̽ӡ}aDc 5ÉjY=:`z6g{9TQ1`*t~ڷt/%v3;ݎ;M;7@P1<6Ō*{\bi;6^ 츢yN8Yk֕hm^Z|0skspcI=l9iITw#g4ns[6h7n,JCg#F_eaYa(JqjwS]%d@[VC($}fϰbe~l@ `#mv=O:'Cy/'Ln e LNԁFr?3ЭNHdPsg<08%"nlQ4%ܖKhcۦL[">XEbe b8*N rI"8SLWN\Tؿm"?/Ҝ``2CQjAwJE]X/@

"Fdn,%87Y\Ĭ5evAdF8k[)ɔ s4Y.Èn3';F8vsRmM#W NمGJH ,x ^pA7?@VG!*8R)SaN"$W(eIT@"@}#/B20TЉ(i$Q1t5=6csSޏȰUH,!c.ZL$Dz`]4MM<+-&-* T fܑJ46b=u$D6f$F70,J\' C X/Qf"L8~3$s& )KN˸DZOĻAJ$&x8nbo1:jvمel|tά-j(TP``[[Y 2eK2܈${^vfͼcΩ"w ' XYH^umM KJ5Xi RIw򧩚NYM-**KQ0 򢚅H2`862!C ]v]񉯬gu~ 6cfΆ.5H }%[qpY @XH݈ktkޅ@#B 1wk#j iA bUݤ Y c~W dY!/Оץ3wI$EGYdH:cd<M'1(!#PĞTX?' Rاn!hgW|.E}kX`e ab#|>atW[j d6chϟ50|>+ CU+i.S{—Bvnۻ $X`PtU}$EloZ2d10iPl@zcot-kL6zG麎˷2.qylX8cճ0m6j+oFh"uXq҄^֎ <2 FC,X w Rmj l$I;l#}om"&*ت5Jڐh4">Mh?Ux" ; Gut"]C!숔ProJ<> 䉄SY%( FC[ PMz09H&*)q\g(E3 HT, 'e]BHQ\x[[[3uGy,7&vDOSD,[e *VŧmO K|9 ZquFS@>>Sߧhj=7>acm ʻӑlN,},ҶXKZܭMDw/g]&Ik̯J6]EOUvT0$Zef!L,_G¬*֬$ՖXjlw@5%4msAgdFצ48>c^SzuRzP#k'Ch4R=QYe4 ul|-gJ~fկ:8R8)(K{ݏ:7uM3lvAn1/=CIHE`m':hyA!mb$dL(UN^%|]!;s#i@R)-H(B%c@xz5S ej)V!T+p>e+վ'$<?iSK L6P򱐠6*c57(M63BGa8-sKV‚`8$Z3$HaՔ&! ʰQ%DLh LJ2:h:MҰb@g^$kOYv-f4>vn'ޞxuNW 3|\K^cG\H߶|0-m>)0'Q*95A" *+]`n]!k]c$!Δ>Lr'+9ƴ}b5BJh ⑽O6+LS'&ٶdB'$)&2]Sz{[XbcQ JU[0BPCT~WS0C A]XQC-!d 3 RDči@p]cvI&f_ԁ5zO[0S2h-Bvjz)s$v8 $*D3}<8(@m] ZCV7IRsO M&!AF]H#< A\Vզ,X+ؤY* 3$QWsɈkfu~h;5RjbX'%7"4IاhKUław5@Đs1 A -DD%Ho%L܋.4R߹Lf,W%2ma`$ZHh )+؏\0pDJRE6J9VJfH%o䴅֨a1R 6v@3UNzEQfSVE>l^vSKVt,~qVNX"F.ˎTG:{cڶ|5&Q!ν{2:ܠ}E'e~ME 3*d '\v_N3AѤe-mz׿LlM*ڋz}ǷMV應3S֢ͻg)4kR2]>ߘ]c8r xM-@4xERp'B1Ҭ5Kywo}tY;@쉀i-D$E1H.7 UPH|'[*Dz͜( '0aH 2#* J $apMέ_cjd^Eqı^+DsK$Kp2PFMz{|)WʇU!d椓O%.嘂fV%{_H*iOIN7{ cuy39p#+u״ZP?Οړ&͞7c˝a aV A"K)䪂|#,\v³˪_/Ո@m'+bM/'h.Mw4`e2ۤ41 - Qr{Ԙ>5Zw@6^e^rٹeʆMwu8p{RR.Z;Լr2T( b%h@H}Zw:gh\A@HA qО@ a"LEgOL{n1F1uC <<14T=QnezvH.Y*2m#H; \ 0L}o7Ats'|H!y4g8ӑ-Wk=ӵdࠦnDs2W*k,Z]6-큈X@qЄ卧e|\wmFBIwR ' gSe0V;! 0уV7s~m4/\׾O&/)YY .S&EeY($hVͪ-1Ĕ Ə?ϝ>!C E/Uϊ+ W=K;˲.fRP-}t3hk+?idOR8g3<@6\lh#8rH ; +x0V\M$ E8;g(`_̿Hug-8UTwK>>k }NJeW!24^[E ְR,-a% PBVqG9Y*ðdFڢDa%%&u3(28M'dꀊ6yS F 3D3@'X4(LJ'I֚MY+ a0t3źFpdiL*ܾP IPsJb{[` nvJ$]^156K8B-@NŔ؂ FiHom.Z@i˭œ4AF L` ȰLl㤗StLZU:qQ4} x-Ģ:՜Lfn 5Q 3 j 4@JD{{cjƿtG Bw*)Qd,QqE8Um5]G3} 2@8Ž)|\(g-~K5wȠ RvWa>3)2=>X6BZX1Z(? Ұ_=<'a82 SbU&҂>nSi.L G~S2HXU"Z+AJR-4<3:>ۉwﳍ[k iDy#&&\~+$Wm䫭NMmL"Cü'b 3bN@l鬺e*  Hb3ZZclGJl'C8 $]a% 0Cfb6KjF>x- 0xLIS%\CQh WaQXi5 S@,9pUh^wv:ZlA˪QPH)DY$~[8rߏaUG@<~ӥ9H&RMDQe7;da"[Z!r8.JNBY!'^$񂵱=?P!Jc%4RԽ@BcM'Gi=bwΒ\,r3eHd.œVK2 dP$ZjW B$UguHҭinbt}9Xmh[`X'4p n'p S{f6t$7 Y|p a8M.#KW@*mdC\8c.& TkATa2̼T4 C/@ ~2 @ !jsJ# "Sb1A ĸx_ç佗k7PS) ܓ2Uκȋtg֑n.zip-V!9b-uħ,D ;&*@A A$ ytSBjB}x6e,m+@nJvYd$p%Jv _uupOb.ܹcTI T*4('V{mI\⫬hU<yLh dڵ0z4=fV}_{񬩦kG)Ӳ?pI=8~{Z3](Rs[dIŅo;3=ޘ^m5 ["*WA$);e$Uu96 !G1jY1$T81[;y2!j&+a-"9sΪ NWrXjjG$A͖r.7krp$k4L _8k#bTfV R*w^3U wTXDJwֵcE;Bc(*%(APCƥ17AjG9=I8 Yupt X0}</j<) @M grn68f ɼܯgᐟ 1$bYMN(@"^6laڌS#DvPȴ˪%,;( Zj0\= 'i#!*Fﶄzd8nUiaJ@L>"@:+X0 .X@,?eFcu|;zzY6m cvqKmكw6cYN'r~5/wO3%Bq*P枾׿i|zҋNx>SeB9JekoQ`Ο6ϸ $rPo*2Vۛhb;=9' B)\)V=\0ITWPj^RB$:ƧJwoځ0B631h1}ި:P L%A :k)IR2%P3vz wous#L`́=Rzqdۿ:prRȊZ(sʰaXH ӂD&h~*xM5a@u?2~':5bB%9wéۋh1$-jP0ŕ_kņG[ {n҃AG" BM:N{9"k#Nlf aGFd1ntJe8 rvK+f؁!+!w|{crfKF A&̗Dl~+@._fMk\X=eI2wGx{Wx.>u=΂9%$9J$ttC}o|!;\##.T"Es#._}mK~Aڰ˟'*Q=.xtT(sIlb2Ysx5Zt@mg Q*ZXed 8Y9 l62iSh7SƦ|Uqf M Jg=%*ҌZi`RH # ؙaЌ%؄"@hhDJ5r-B86dev::C5nT]ۓ'Cp42x#I1j̞8 r|EMVX11W ->9e$u_~t»? Bts}c*Љy;K@4s)q[钹]Gucd4& ,E&Ho2NĨv:S+JR`°!o,͚iJtSw#G7ĚD@p &nɕU~oOJ9`s- mVN9Iׄj=GcR#Idt 1NRnuW_'Z^ ;gBŠ7$ -EZy;=cU  Y )#mqy͝me\6H9&Mű`La9-Ai=:Bq{$h灻߭ AwjARpv6z`Èp/42dS|HJ#؜W ,mʖaZ|;lCEd}lX>'Ah+n pDY HlIz>Y&A\ݟ]5YjRA}62۽h2\7!N^tS̙kP2Z&; k|#Ժ+n{Ճ;xDjKNӍĢ% BLVdIz<ɍU0b`_4  Q7U~=ۭK/<2!U^#ԥe^woV`̡n])T/˚'kʯ8⍣:V'޲s;O߲/l4DqM_I$GZ/L~l#hMwsMGl#BGnt$ J?~辕}]f47/Wrx@gbV4乕9i(+%B=b2]6 1NqR%Tu0m#l,+#ؕXԕ/>U$|@,H**cn{LJI01Y1гuz4:aTنI**8)*8A*< t>^-QƞkjiA 'dՋ']as{L있+UL#IPbR=׌…ۇ& a:?좡w3z&]Eed3N.um:2mI䣁bh41*S@T|[^u6Nr[礧:MnX$D @fovhʥHݖ!ނщr2z ڲp6]l3t6M@-MF~N?\yʘ2RR:OL4pR)ʀje6Ŏ#Uh %H2d!UNRfem8SKV`wƦ.WSfQK /E.6hb C ,!U#jB#qRjKڱ>ԲeVA!]Q͂ EU%@ H3_` lc!Kx#ecܹ9p>efЮ6bMt"y~Ƅ WZ+CDixÐq7dl 5|M5{FmIx "XM= 7?.u xF3J,*xV6&4CJB#ia(pfN帬L>B[]pFrkOĒ0HT:w_{9ۥ4*ag`D()uX'=R5'MzdxG8amqk2F67{~m4vU"WS:/^u9,bW"hF#7O>dqtT?lT2 A"4U9]NJt;ùSCN+$T65[xfsk)(*-`k%p: Փd2*T r|(i}U\w G?{?'őM{Wm6Qvh4k JbJra>zsw(/bup}#i~Bt {Ih[.?Λ=݋L#=FH[ |R;ĉmkw)c0dc" dHhW{Jڎ>2WurjqBq@Pm˾,ƭZn~_nP;L% "1M8 "n53nm(w*!3P [\᭮Wί6*oK&&CwU3}4P+7cpUn9ǻNue<vَ4Y&*j9$ι}zXOMٛBj"*wVhFؐ*ڃo[82.4)#qpg`"ǮQ5-ZVSѦk4Y`6ۇ LT]1` +&`u*o'gU8 St%FY1yVZ 첎U:6xl Sd<unVWGsȣ㦯D5=.d7JdK"F)٢:?R8gc`T%̔08% QYf#R(l ʕuUVV$:E_W,6VJ,BW*`D: | 'i4 *Oۅ;$Y](M,v(F@YK]|Z?A=i0%j%"OM^ʴp>uxC:-;-y"r ō?KW2Rzo4w_Z0m57͚\HB6:St3J@FsMռ Zc W!/i'7bM^Eo?D*a(m@Tkץw`_*Pޫ *Tw;Iw(M&FalsGQ#G_ [5ե<3^@L 9,+Պv{,v'ˀo'f+]jɊ-a1#]U Vq2[[1R5{~$6l:qpJ,I+@1S| q,qWdzV;clvkLj|GN;!A_#ldtJ( ^Ήva;}_ L<6VqW%ф4#uh # Vq|pR`⋞^sCtkJ^s2guY^{Mq37]Vg]u$rkvGǾ ꭣ|,M-Gi`86~zɿ< F:q-[r[{^ Ll?e`]iY)A55:,3@9&ivP B5[AOZf4^jF{H-/i rrge7HJjXT&Dod Z m-q$`UA;Kx/Y6w; X" WX N}O&/d?XIkU: (>B|;> Mӏ?fx $xf x3<^=K mxb+~+,G“ Uzsyߝ=y5v_hOu=NuBavX8]>x?K~Kt؜4>ZGiJ i@!3nWv]Z3+lɧh ^4ɐ^ n|7~vȉ<.]DEü5{?e{ZXi[e/tgCUGIѥŮ_207s`:1%"עD^9w.D 9Hpx^ i.\~?; B.e9N#w[NU'g5,+@'CpF, ϛ+mO#m2Q',\X:&ԣ``\0\{GĀ#6/#oܷZg`߬Cˈr~.ΚYpQp*dS4^1.90z@ny~mzwx'HoelyzOގQ* H(>Wd=E ȍҽ\+6&YGU쮋3Bji97;Џ<̯pWtkKl_3m~z/L0ytk~'ziGЫ`^g04燵W@@5v._]+xV Pp/b`YUu;ղZn $H`ŷ~Xg zK0Zf@0H}㟐q,I-lښ6aVR5P6ݕ$_VKDy=Y\שBPzSm0Ét!t^iW}* !9׀Oq6piu!skkTZݕV+4D) =zu_6gx6X| +:~<݆LH@~rcWJ,]zFV1clڜ%LFMe>!8"e|D qcݿs_I>W 崼9{ȤFZVsٺyMiV3a0jiI~&bh Pu+p9z7Fnl$NRyU[΀lI ɂ 5y3blƆ 7$uE4pN_ WcIԦv yO"V=L$= EJC~ +R_/;WG[=ONH.py9iw'ca+#22hޙ0 "2# uM{c_.&0]z;E_gt!3A ,bRn%SG } ~+MO j MGF=$IHqZ{wXSfofֲ?mj%)R[17{M,~v/uV}}"-`NE_˃O|w?|$}'ٸ9=YpN憀3&p$0b{<_x'ui$a~`oKٙp_+FhD2v|eʓk~W~KYcpyrk^QVQTXl.iɯ?otAy8L{I/qdu>!Pt"l{<ş.rOt?3o7g^?zoaGQcbHUFnynΟ~~W<; $>3}$SfđKm{SOxj$͐]/lN__5#T@Zh'6'n=_eN߿W[Ha{MG?U_r9Nf7$pL%,OD0Ō.#[y5H'evgd ~ Nx\=#Dz|RK60AHt\ .m.RA2dD[ WyV.@> ImswUI,)|>|n幎)%+6`Z`` ~d67K\9vZnk"Rpċt1Jᗿ ( `:񓁬 X X\:Y9v>]'AǷ_x \BC`2 A k/$ i +OhNşOt>Uxg!PΦ7lc^,F͵HDAfQducOѮNqow;;P߁M66yۂ=[JLJ1 Z mZMWaZ NҖ-G^6ı=[oITxІ7'T2TC8A Zg?V5v>0)y'&y&Yk_o%W"Yӡע?LE%"V<&]ۄw ӭ{4iL|XNp'N|-"U|H*0H eOU:#t:MٿNA&C[~%'D6 2 L$:6=|گ vof҆כ>wbmH^2`'{l >ʛs@Eҗj΄Hxۍ+x-#SE7󾊴;VX}s->_xֳ1{&Ys݁=2htm FDclb<wTQ:~nu&>Ї$ sjz&7}Dž9_}@_\-|BKg#^Ɠ}*"%_"<ݽ;kR==o/.BU&WWcjeEPH>yIvO}WGg&6;YAWt?g9Vx_=xDZp*IT m |طLdGܟhΌM;;VfF>!]xS;z{UxcWxuCJx`M֫o[Uifdt[^G?N{4T8Lv5/zcM[yy`EK{y鯾S:6*tLF^㚗\pSnײNcCY~ơGW\I{;evntR6Ċ#+ ( 3garc?TJ%4Ueby"]wB4aTƭ_|SIR|iLE/ qTӯo|M;mDk*oM ,O`\ VY%_[A +WJF__|yͬGjI=پս9k7FA&6W{zGn#\¥YFpwԝ^|LAMīsƣEnWltVVGމ dpbgnW3R!QSpՀ;=}0`jP}ߐxZ}m[C o+N]gCh}OW\VOt[BчɝQͥw*F|zyg뷾ן{f@@eWT9|-6ٙM>vuK{Md !R;;BOabNoΔw-ܻ_ֶ\w0; {}(yxzה:~u?Y;-w]P6yu.B_ϗ%{w?čygq#fGC dOgh8}$7mʭs{O }jC=$%Ng`gTKB<á[ / NW4$ԤwCZm$0OvM# bYirB"MKϵ+FoΒ3RsM9&dI hdג u}^ az?,h 2 jI̅TtE/o<${4vfa (D9 7aXQ!ք3.i.F|g-uV; dsj?|Vũ] (8@RG6N9 6PsvݗEM~_ZMѵ)z*O7Nƕ'x?F_X/J3^)$F;<_҆,TO|HՋ_Me^7;|LVA*jK \1Zi}ץH)-I%ERdmS2w͗zC0c~ 49鑰p_y{XLv ӽVyއH2ˍ,_/?YM|wc05B1AA|V}ix5;ϰoYL(0$JK'3b>/:<<;IfO*\ { p!uz$IaxV}Y'^`kfoиIR.y')#$Za77)"dvgs/^y/}:\~gdkiZD%{R& +zA"U{J>9џߧE?򕗎.:7#O4 52-S}ReWs }Nop˯W-k2DͿ}I)/mv/9(#+eM~ore8 A1 kIS kq!V`Z y{ w]fj8p爵1vW54>)B;MBMJ^O3[`L;Ttwd\mo=;ݯ^Ƒ,w8ь;w6ȣINRq/c  ݝ-%~4#1n7m睤l2!~G3R)r|XMJ#S"+ăVէoI9q[f갻֑tbmR%p5kwH-.]WLRIaBzīYI!pQH]`E!~ wݮxSC= !ޏ\3ĶGKf7-zv&dF3q)R#AX~D|P}E N\'/J~ɋB:(A1_;4oDz+_f/K@ '(}"%w\!;X]///M'^ԅDanKO=NșY~﵅wLDM?sfa;RKƖ@h.I!7TK(9'H1KEX֚[6|7Ej'.8 7G'76eHaEwMSwi{QijIoV,půKTT!;rFo2W\5dWiz.7o]H/عT7Yl!8R%l>ߡKci[|zd\-1}gv-e"!g䎇lƖ4ɐ`HHMo> M*Z:Ӥc!ՏOġH;J28ovgdOg9DT}E"+h&Hp|VF:'_ӛ`h:| CR1$Ɇlr죹~]M\H(\Fݴ=u^Ɂ 0i%HE\HtxܖĦJ:YsDԌC"v3i/h6bR PEʝ_g~5'u\뚅䪏.+[;fϯlp-d+Glo2̇_ˤ{-VCG.}dw{֓-]&햮s5Ç..fnSl̈́rЬz'~) wjq hBAOTw\&nWێZSdiczjWwٵҐk8-olF<5yC[#g7ﻛkfn3G=x1*kt:=vN>7~/7MyIaK͗T!hu/g%}v+z v庄;{J,\_nPVKB`"΁tC3E۾zBbw\"o?;O؄RϷ)Cg-ʶ?5g-v:a($ oU$?{4ӎ"xAYJf-ɏōjCңStA1AMs>79N1Ж-(MyT8cV~m_ =zC3unZHs;9*wDӈajieB遲{FKD/~s QoTM$pRY(;ES?)Q]^ZUxcUX儤{S-\Z= 'ZhukJYǙer,ҽIR62Z17TQoXsH_Ќoˈh GD"ɱhuT+ʛt)hNq]ޞ6|"B%ztǧ-q-32 N0Z4&OX-ޚ8Uq1{ ?t6&q5& 3AXURҍ/hJ;ogWrY}i:|OF(5*FѸu Ovu˺=!H`<-2p  ,՘lu=hZZX,a@NN္hjmF(MsO9'׈X[΢|/*h|ð|?Q5챯4e/V}nEu vfʲ[lr}eoצB^DEl85NEs^M>P%ʋ>#NLkkXW~lSɭ2.D9U_tm6CvA$Kg.%':摃_t4 :V,XfL[L:yE3L. D䋮SÄ]#n!4x tJ:iڕ5zs$=ٜ5 Yw-ʝ9]F[)g?׻FINJ9ÆX' ڳכG7!bDT]W3z$p fݸ}86ӳ}\IA9=ޫřjAײ2PWqŢDiY4CUCM[ijhgƠD0ޮD;,^ۡ6&bP&1D-Ul\ѱ<%=bOK1-\bh4ٕJ8'_i "EZ{/r}o#M}5kC>X{.j~8r'6~aP}%HdRqoq*)|MhF#7g+0ڕ#iT:{A@(2d$_:UZF tQ4EʜB;G/Zkj=emWzMVxM)LG +fN32tCvN,AB@f!m#pS4MmȽMh׽/~4~h)_64{0;N2-,g]$|mJU#LX]$Zu\f}:$մ\i֨x%1)aG,=G}۹~eu_W!UnU &&߀ݓDK>hݔC02^[CkkgB2:x.Ca^9mZiX}?}+$NF 6,ђ5=||ӛ!wWǯ = 9;y|[l674g\K?mxze3_-Y{iPHH\7FzNH4Sl͒k/8FRJdPiZ^@ip?b@5F=Pl-0V=B`JBEtٜLoxԑ3;QqKe H7 =4D]k߸!{@ANFG]VS|fPTjFm aH9vW,s\RHu1^v755⪡CL 3T^= a04N߅hۦ5MXav;muV8X~yO)W5pꦨ#c۴l7wkiW}lYyZ4 :6EmS4ڤɷvUqa}O"2sz~8)^$qݥ ~X{N)}g4Cj[=/ްrJ$^y^Xˍm56y^/SyIYGt9CbR5 ,wx 3/yKrբ~'zT:Y%gTI&1[eaH.Ի vn,Ԗ1E VRyh-醥XMy2 Hi]{lqT^Q'^ :|Tn^ ,? "Ro5)6/]hEuUjхsbig"^,[=TybmBkpF֫$iճ07[vwk\:pv+Ń&,TKyUR9것*wA=o$ :۟ˎ*L@Ù$%*I=~pau y O"KT@1w;),PlPdJ85ыC= s.o?!kq(D|xš'FGF5g/_1O~!{_DɔNTDJ$|%i9֦wVKT>?N$C KbHm.QADt(j/;t +WqO_i~A g86-U *ۤ!,!-ħ6y*IEQxT=n=h_JR^g4rQZiJEٶ(B3‰>*u랪zda)qKj?I05%s@XƇ 2t"Iۗ/ZhGp|ӝQJz51Sk9"4>U<\I~IBZ9N"<3豼c zuddBxvX`̰#'ʃξ8Ǔ"cC MBKFnFdQ\M@r BTK-4h]2%t[C`85nłް݉Ze9wٴ5vyVap*7eT` #ص&[fa!CU +DL9 U¬⍋\ELCQhp[P]GIQ#X@hT b=Z%x{{ IԹ{)NO*0f;5:v]y D+g_=Zt6>sYZw.稇3\>?ƥFurBw,]zƑ*>QQUIOϏ~d( џ据=gHUeRg5W'^3|ن(b$&fjXvٛ*G+uŠ )Wc蕹)W]/Z!vVU$h*4  nM*@]!%o>?Ul̇Kn9I)ߨ, ŌaU͜'EsF/dيo$5ds9q +F)2LND R)Ll azMj!]g"G/Zj TxX^yL(LP']zTR| MT<יbpԐFz>!B;SX'H:\9{ Ҿ?'mv#rue^s:|WV+ֆz'y&[yQ ļyP]07"J*m9a"ߍg)T𪎠v%!$}&Cpv1T9'"*uy*Ih]GxJ5l%1: 1O%IWh x(DvQ=0LjU6eO6>95*H={݋% S4^~>ʤڞ{xCKT7լP֥jS;>$څ<H0ڠ'AkEy2 {RrpIpHp)IQZm$@ :n,8$@HkE]FNC$kS]ιQ7|w7dhΕz畗 1*y\lLNp(|^g;-G17~Dԑ\.=Lg cޓRKi}<@ $H*>; djưqJku!#S*yuxNEgdE1k;jeQE՝vȶ5uN~[n8]Gb6bu2O+);ܬ?_Q@20&b&ȓ3@U]|W8C?ğ0H䑵ET\-G/cH]>6/`_OOl7R@-Ok--3!jE[=luD =:VWzAn $UJjY>VW%$;A\"؋:"RGcSՐ<7_-ry>`sZ7V$yHzHI4&[ ]^>1VnݲV5$BzϬI0َѦt1BBU.2X?%ۈXjS2dBT;%Ӯ虏y22_GZt3Sk-[6h&yۙךW[8*{]6[R{ϝ:yQ6,V$=uu͛: s'Z%e^pFL(m JgcY ƛaJzĸ\ RC D\_tnذOgj k@@p\T]eSֹ/0T 5ެ#FHQ)UW޳͚tyyX6O;yFƚE0 ̮VA*MYa&W=EŜ6fDHb U1fVAnɬf|:*RvHP0\ݔwIg>ӗ}X۽)n6l\[۫{ lybcjۤťxe[ՐHi`'=3cykt_+?y3ނ$Fi1;u0]>aϓLvgasO, m^浣͎\1B;\ܷ7se],0Pi!}olRUyUMbK[ENq7dz>6hnUL'W9c;9u6k@Zp#uJ-FV!EkKcD/(6ZwF \D 40Mn"Bi梵< zsfTjYox(8@\7?cTm?.zaQAjJpTdJ;'MQZ։og{dx~ M~#ۆvY _Fw5L**_z}d뤥mn@5MS.eZ-3SG%|ʃ̃<{.иoi*m}tezPۨMszRyY,sۆ]7P"QiӼ/8Vڀ>1<6_7,r< [pmlpBDlD0ͪr-3I)6)c01QFI;^g]|?8`xS g$o*Uj޹C.oU7%_4ڞnSKn y;OUao-)3qvDj#EMNElݘ8{{7w8 T>%k﬘e 2xnK3s5ٷ?vcsm:SI`?"p/9`ԑ6&5ի>;>V~6ݧT/&ykՠvc%1۫Ϝ{|~$ gD%ÁJ_J1otQ[|hyIo(>?/?8":J'::]Lzg3^*ϻ>Soη Dz_"}te+l{|GYhpYyzUX4fT58B=f9@]WHۜ4Hbe،(mu,+I@u1"k,V&[~+?Xr#wW_ _jv{o0w=tk祊;0''wW} cY|~u]i?m^&i٪w;IV8ԩ;Y>C6w;x.}ӳwRPB8 Dt:^9tw7].?{9}#9v~,EJ$Ӳa驕䗿{;?KVϥ4Ɲ7W U0D6򦖹Nq`÷.mmϙ.4鰢SEJ$^ЌgA}fa[\!̓M<<> s]/C2(t8:HJ_i%,'T[ȯ -=mpǾ/{ ɦ]{ȁqEmUGԽ ù;\8rl~)S<qB:ĿrK4w7&p,  ,dÈ2g ԋPDq"7wy2=S Y{8!@о8C$~nO*Eц?٬Tc.P=?Րvዑ'7R륻s%\^~Cs#,7+N0ڹ04a̩X:/,& <5/BƍZ%ě&[s9ؤlm\OuAM/7&}^D=XyF) (IX2 Yq(YԀq!Iw&2mrgŞb,hlcIg~hԐDnFUL $1s _Y2gu慑9U=ZeD 1~UkWw'ʓ\m)Fo`j Ԫ '5:Q{ W^}}b2O;;L "TJQ\2WI RjٯS jGLٲu[YbSQ3+8N2me Nk'-{jOD{o&cYei\=j3SfC-C1f](궯NgPM;rˏp7˟X⺚knCJw}}:xUJ;.xWm~[];/s:xj wTC_Ί~vr^vGuUu_cUiN_~kUrWvU]5׭8SUlӕg1^0(I5i1YN[n" $)? k_(oe?-DONY|y'ŝoy˹?&,Itr/.裸F1W{IBMHT:9/WlΕc!54)YI% ;}\wg &+\+fmٴkm{+(s[&"ƕ-do+^%J\p17Ҫmn}i[o#Ns@U=ܽr/o[w {q]z/z~K6eM Iuv͈(Nvg s8mwKzۡ~{o]gt\bTmrkbgmxhQtg XJ.j7#G+0/Mm鶒-gwç:z;P]3a;'ǯ|~ޥnEE^1kz86W>hS\~>sCO*]or7.NZv)"I,GpmN۴RѶun33Zwͽ5Du=rD?\qk^rA~U7a圓 -y\poѨ'ø5!WLis͛#n[X4N/wӛ.6zslwI0̬ /{?+tQ;7XoM_`.#k[ uO>+8?VՃo>YpY͝_} w8S-"{ߞ{~+aEdҎo:i]Ǎ9}f܀m~ho;״'= 9Ĭon+vϋ -p=ϓI^]ř2W/|ogkއFo|WW.c/\K7ky= !|HA_ k#٩Qьga?o4XL*G\f̶sI"3A%jb閩Dʼn^N [TsZ?[1%hi)'qȿK͟twrɚumlsٲZ_YP>} C%< s|w2i0WY튜YEEW>/n mظG|9 Н-gձܵ#Bx0-8lE0R?v$8&eD}L}z!_pQ z%aKvrP0@Fֿ^BK7l|pVP -I!8E'8_=^RC2&N`e8)cBXLrd ɟ:'5pC/֛jHO"Dή_=| ȗbI$ 2nW"ԉtcm1wFƅfB]60N"~`^{bW.WJ G/}=i-cFt4SlI{y5=eTQZdi?g]5c߷ɏ{rdGy[ʩY)\̿#a_$uP74U\L3__=Ҳnp4\;x⃿] iXZ=[ZMs)aŞuՑj##'Ek{B/E#5wҽМ;&Y/U`wi5N`; w*ۊgu38'u8ܞ謖b+9:aͦiIeG f1O4O8Y-zЕmf=Yǧ4<]Bˠ.i vgp?W43&~^PK[S|\P~iYNEOuS%K~UN~uZƦXEB53a|>39]1H6Ն-?d_݁Yy%yZKٯF@u &p1X9KG~y ѱZW-a>Wt&uQ UgK ײ<}E3XL+zC{ݙ^1ǝ{<-jeueo!b,'^g|G^UIY@ 9+7^XϬ^}=ӏ%Bn=K<3.N[ B$K&]ΫT%meQc!6B= 4`c˯}t N_4̛׭ ^.DSe"ɯ9 W3* |@zSjl3fzק?IYoԢN&^!H`D/7Kw!~ 0913EqFmg3ҁ Fݟ^ H V[(8#:K,xu2L24XaS4L )kdgXzuc[]Ip^) m} t B{Z7Z˂*H`MJ2 '喅ƶ[=j}kd}{5REB?hQ}_cT[y7_IL#:if ?gwZT6X_ld^ i{l05}CC–:hbDosO 8p"&[Ε6?QK Y& %#z=z@q$hRNDS .6%aiݷmcA%)lH }gԕϸ.͘7\4ߍ dK=V_%ՙ޿tWs*SapWMva'Ϭ9#J_RbP˭iZlYϓ.ۀ'7]0Q_%ҭ_tNLUͯpJ6i©b Zp''^} U"-K ʨ)8]ZD^7OvPD_ygWƀԆ:0WRtc~v e&oY~% 7vH&vBC^~Έ_ e|s'Dhxo|nCs Dmߊ İ#_v 4ʪh+?=<Ȏr!HdoG9y^LN v&$hxx[?}|+S?)6HcJD m;Hwl$!ᚵ^;_g/L Hۏvl#/VZz"j8),/. ]NGå4G%~bF | u?Q2xjY/&ܾ?io$p?F ઼K*RLp NiMmޒ[s{omG ?WےV@LǪ]lN";{_LIX1<,iͥˇҡh`A3HM+ZT򩖱U`HI"GM:>`د p9Ϻ?lF%^|fM`vk :y 3BG*>mǡf<`Ev6FNIXKegjk[oypl!01^xeχ* Vi &&\q!/j%OƕΊBSB [H)9Zb^}pݿERzاi4X9E1kUbIέI~Aub2U5E\I&6i)|2GI@wB?-C(_wi3\' ͢/u.$R,}$Kk;nDȸ è $LZm_D62 +Fk?b2"fzf"Y r\$4XJ&Ak6  "m[RkrJ{u!J& iHV~ԡڷߞ`"hjH40i+WMr*G0h $T%5h˳+%&XqIV 6W.ﻮ'n65~9çQ+N2 V5pA[|Bn |dp&M'8)pC \>2w}&U bG-peu#X܇^Y>}[^XEk"Κ U5zy4GF{aam@Mu"(POϾ]q(RCjPرU2?rhGkol=0B2{w @Hwe1B(?ZlH*TEϋ,t1{Rac6؆HuA oߟ]{ g% HZDl,)aYM)CTȜVw9fk ^F:-*Zk$6'}' N F#H;[CD'E63b0` lalʸ'[}y-3M 7Rf>ruIc=%+;Ms(G|9Oх/Y6c:]HdшvHwV|m\rBd~!qڄz:zSiĉ=%qBd;zEx^/O1$xɂN:_y Il#S"py\a1VE>>u+p#T)\ xsƋ6+{vIC睤UJ{J o$#񞃺Z {'yRn,jdM]:e)N{|IWG<j76NA4do4陬9<ꪔ\+|WL A)Bm ǫoftնBȑ1+/9mC86G01c qݚYW4-k-//[q>' #zձ؍:M̥U3v1p.益?VqDpnqN/ 4MnsDeor‡b,qG5=7 {ܖWoaJd&jnn~ ^WNcYJ L,kU>+_7v[L,5є&˴-?bgג9QJuĚXT֒V/[C?wVn lQ{ ٛ6ϭCNΌlgMj,%08(;c0 H ^YѓGus,5@nOr_]%ؑPi~/{OL;ck)nĕ2d'FUzn_cocGf%vϥqRBQPsa6.AԸswO:^X(NI8j)?K+1ZL&$>^]8txj/=*?~Bcî빽qeyяԋIE͸–-q!8upqBNGeK,ر$)@`(N&s9Q X"8#؂XwZ 8Q Z z'E;cTORƆBpK3% _Q9#D"HQ$w[> p~x5=Χ4K(Y4LwwlrŠ[3eyN/0Ph"Y籝qo C°`k2}8l_GdPGV{~ pWȑxJVuTJ;P#4܉u|HzJ)Ջ6|HcR'؅'I2HI뛓&\nlJ]3HO/7ʫY"x 4gf@4p2C:Ӣ PfeHdZ4Gc!%\ A~4Js˕ u'cFH!JQsS?[Ӏsܰ u3Yݴ;,]1[Hɓo\ .}~'3!-O1Btm=wKIy[ M|h (>;1xyz'g\U E];ۭPZ:/'D",E( (tOflx^zӗuy[߯t63%y  bؾ׻[*޸!m(+~/ظDt=|MFp 8[ɪm!9-ۆrUC^ܼ&~\.6Tp<( dq'__Dz 8Ք&Dahcq.6ROx;,#I;>hTW>*}%Ҩ*r<'|?1"Dg=PD*;wx!-"rA.Xh,iG @O&1ZDhʷ;~,4烉V&W=Ny+Wd2LQ0uڏ^}>􎬦HQGAbȽ;g.ji6W.FH#gw+hIQȊ^*p DFۆe=TD|DW! 01^" OjۇUqD"SD&,@NϿQKfe5'$B [zdJ3hjN\?Rs~³?'O=F\=jpJy\f^*_xO%e|9 kUmGm%d\*Mj\n IO_K\k6zG:GcIo).qyW:4~]jL, 'Dl0R2ʏm S;52d8d`n&Vۚm˲r1E/R[ %LO`1mU{7fq4wQ!cT 6[i=~1##:[4rn]`oo3vM}& \=Rގ=Ӆ1(%V1A-D Cl3ϦKWE''Z$'Cw?;@r.|Jd[ 661{,s4 #HK'HLĪ6AkZr[`0xY$GI`Ņq BZvnʃFsFVM*P<׎Ȩ!s]"HT0((vD/G}秖@X⥅&xj컌^-;eY~>-̛)k *^@-E_ᵙf^{*T>Cufg/wnBU7}: ,1X)xD#OragIj4#nTe3P^ePGR[-AoƃPGb.,Bm$4_w5toMLqR}3DF#9WkC&)8e4t"T*\AsA5T7i 92Y"E# @[D# %)$'Ց.P h t%:ʜ'5Vtx1~uЩjB^#~Pn>rI=+?>Bm*L!nM[htO{leE !ǡyjS~ X@;ç?؛Lzr?BxG>S}XPR^+6pq:m̧wI}^!}Ji?z/I{)_f%}eRl}{PrSPlڀ̧9ʤn>:N ,g73v>cϿ?zLteX3wݕmEo`VcWb2)¿x))q3ivw畾V&^z ; z~Joc@}cVs}ϋʤ :c$+%[Ү@c[~[hl qӣG33՘vs^K[c]?k쌩ix8Nâz E43oM񙁶IrY$ЯtcWRJ=u2rk ?opOh`H*s Yh]X߭Cv$_(P 6unSQ~2h!{gg65% )nLddiVF˛Y$_1{n KW!Fzl\c#J2썺i#?Ol/fK:l vgYmhDxYsMF|q77}!r|}|r U 9r]~Y[yɹA[5Y 䕛qH1ɀ$ vNB>HEP"^yL݉/wiTo]]L oNW^y( @3W>f$\r뺏_Ke%Y%gmWf3C?G";M~ϲqPgI#;~EuJarU]Oo _B}LL0[{|}U*\{ +&!Oj yO &V88{Ftg‰P)P2}&)xIw0vPxgB}*H[~D'-!pCvoqRI 61i '`ʾ2{~qKeec9)DS\UA v/-,t.^s>O>zf08}A ]e^ӹn{j3_8VY!.b3D@')hͪj~zqCu|!+m;vۧ>5$ܒͣ#NIjtB; 6$7 @5#.?'q2;=_g7F"A<2uم]dgyR>w \f"ԑ1tƅNtjIG: f:= 7.E@Z":%Ʉ =C B@؇N \{}(Z3s*$6$}쮘k+ZbyXjL`=QHW|u1l)m16KYX6 QH;>#ג -YmYhQH;舑P"2ݬuB0v2J `E «:)bӞLz;.cWNжiٜtcU6'ng': -#*RE:ѻzpթ\mKUPnp[>˲u(Sg m2FphMD&SzBnzo/_\' y:5H CjcszBɪ%_6ʔHV<@Z\%F1.hC~#-=!Oas~d`2$YN&%27U=!Nz=}ݞ2ҜbJ,*#I#9?82rݳ;CO[ݳ(JaY:h+L,qrg"?.}*T"'R;[N.|fa p/ xz"X~y*Ҫ7zp+M"9W0t,/"NU:ե3M@Xs\Go 4Ց=4]r=*ש]ZӸ +U=\Ÿ,2~)}AP*< o:UUI`HgVkŢ} w`D:+ЫSs˅vB?Hu ݟ9A:<TG^ǿN2v?nO#ԑ gz1epEL_GM:ZcoR?;`ފc%jH9m?&kESf ^`2TEBeuIpWϱ7E9M2u}rr{!/5՟!sMmLS[Y:CR=gC>*ȁ^t>kG y>'k!4ڊڠZJ%E04`pޮϟ_*!>QB5 <3d쒨kE(8_@=Ax Œhs BnSnokpFUS \,nvY4˗]{V|r,f4db,GX~sOm{qH_&F4^Rbe<:;E#p aex܃ٹ w?$vo/:.=}(=_A1-]%viBC4Y*„P Łj@ <"4|Ţ%EO/[qC'NJe9/ +; h! >CkHPEҊ j2@Rdsss;? Fct|\;`QdD@Z1 jxLOդXg hIz./^ M^^\]dRB?3Wo?u~hz@1m.;576 Ȕ5K$  Q28^͑#&>`銘oiSL ^ȸ0D (FBA_F$Xd^~oPI"4-}5z?0sW$}VԝY1t3f[*Hx0(_F{7\Os=Cn $! eD>XzqL893;l?OJHfC1%7 =rmWI7&6uQd']E3NX"#Fp0o;D a>( !CaIWN}x7oy~nZX_jtU;=3}s=U$"Ydk4joWGk^V_ {Ɠ:wzpJTr;hbٿvCaKp,{wWG $kvymk;\S^)©r8Ցb{}^~TPh֓Rrpkg*3â]q !n!?KRz9`Wz]Z/պO RoJf]]+z~ty_/X옒MՑ"|Wq{t2\n=DF.Yc}~&f ;\7Ol\p{YcU.,G6gW"-Mak49]1OhwsvlUAzmnokA/h[8ϔ1jH'_ކq~[CFn0oW#ec[Ր½TF5XGbjt o;U E݆)~[ϨFalA[ʉJ5 vxBYVVuÍEOOn7g'_vCZ`*X‡-VbwùǪ-2G jRu+lN{N~X^8zC2ipc7J qnE+|X7/[GWϭ8eҮHA+S3dUʼbxTTuv|}܎ƪUnVf7nt*vG c揾D9GƮij(*$]i~h .eąTeY٣&vUBMN6d) h,hQ9xBIr5|c3x!1,60F qa^M^KV 3X3BAHD4Ua2]$ZY'lԑ^|e*wsf)jBOئ jDPxsS2Y ~''<|XupAHE,k%_*\cmo޹/L,}~uψR&I6b꯼ `qNQ[F{NcG53{6i]?*~ApeͨG5 ܳu{,:Ĩj3SwO+֞S,*qoQkzT42}8_==枉w*v<*`EI9YuS{OtQ>=L$D+y"ƾ @C[P73TpΑШgP\Ȟ~K3׺4bA|V'O:#@:8wo],<4/揞f*;qtF{OeM+Jyzn/;P),su1S$룶\KۚV*Nn^3?bB!eUΏ=guT$RO^ċϽ֒o/GPGQYO +aR3/WZ*j28*ljo=e5ӗvxA8-v*?jwheG^]; -i.qBG*BFd 7wzU\ !`WO{p C4$?V)*'*SG%dNv谓Gt-CꔩLPVHJ@t`|G;DU}:HHe>36l3 Mw(g1C2PnH%w _xvyT:Ap!ΉR bF1%َZ2@jg-|=6uRш@O6ǂ3I*@%ߛL+aWc! P/g' %Og;(y(<|/HY5W!4!^6S8 e ?@EɸH 3_|n[Xq3P'f7fLfE`3U" @ %?3HBhmNIis1T(00ȐԳIr/0܂m(B8T#PP/R':2^`ЂND(gPc#3HVE7@=)g") POĺBbTqjHH/G]#v8@- H2W!q* Y錎vnH<zqyID(f[YuV)K!Lyf* eRSJېqbeC~Vffm ȐVbMBcR>“Nԧ <Ϋܙ۠v.զGxSZ}zy OjWԑo*SК4wGԑO*UfELϙ+"P悀pmt_M#<MqAH<)"QǙ˶"嬽* ډtU5«Wu T^wUq%5!kKkv'0J)ϩ"TD5ߒYPz}y L%kVS7+-?tGYrԄ6n:SeQ^9tG^]5AҖK IT{Ω=yoBy,wa&GaWx=g гm{$W-^>-tkv)ms4-<퉥{^uK|>Ƥc+>HQ._;n"-zvsFEz7^{ߌ 'Y<6 %щvpw~f/: ALcZ8cW}'}4,i_Ob ބ^ I4F{ơ*}\8K nsec$3_%oBÚr,쌱_Xj xz\|IڽQ_[nN6cH)أRgܸןM#5F\Y ja l]'etNkʘNxc Vx]-^R=q296'o}uVMǏ >ג_}Ԉmz0aȆ۴ 9']ׂ͏\S/^3zo߮'!B4pK $fwigv}m]mݠsv3S{NV6ߛpqRRfwa\[ݻMEm?՟P)i: "Ok@6Glwzqc_or'hʢ^rjmYӏ@6 &eXB L˜Ⓥଯ7/9a(N/NxV?u~krY? X}ůxݕ_d;ݯ>oS菏o[\t{$yː(Dd4RAHj5A7ϻ" >4uiZ: 2L6d )IJ)&>T~^ww׃%H}Z4V *r̿`}4PqBT3:{Ɯ=6phe-c"6cKɰ!Z| Ȋ].Fڰ,{,N5 W,D,3ȼ;hA"HP>Y6T$݅n:!qzfY.nw^wx/0%6?r%]ږ$6uI./ty3zedqMȋ7J0 (YEx{j*Y2؈ :ssL ),:<iKӸ}7y1Pt8u}/[0gV~ot‹߉jH^ZB}A N*خ'xsF{,ޝjfE˿3Ap0;z‹ǩ!mx+3‚Z a@0=Ui֞x Vq_fk[jѐ@}Mڳwߴ%MzIEJ\9ҹxG/3'S0WLɍG>1]Y0yi=>%,K Bmkt:C( +J[yg8zin@A}h!E|S!fvrY21Q5Ux`vˑ~vЇ1eTUta18\`v}ɇ_[Y}8^7:'eecPEsOas;̴5oID@vQMO} F{?G4%'h޳K^*;[X&pWH'61{>wc]mkPo0iŞdjΔ#]@&]QΞ< Lysͷwg[7PwZcR?{/ʐlL]%Q O4vГhCohtd 2A7c vB;H(EҸLD.V;%!O4E}W8<08mHPCx 嵷wjuʞܘ)$˔K,}5y?J?7?&u'M 80a!wrH{cEZx; prٲtLK $øPEEi!%r]Vfu&vRzյl~I'- r9P0!Y,F $qfTF8D 󀤔t19xX3:Xo+%E&,+~]%DmT˼) 9obb%h!裹Ci~Z\N֬6՝+!ؘ~!}-@ʗw] A)TX&L!?zߞCse !8 UJwmݪBc:r;FKt~G6=hNd6; H@(l2ic(@"^"{q2M`|8mS8uu]8`x.p}2M`Qm%gttzE0y$3 lW$I:$QK&+9wt7\/iN3r}jgG"WTq>ЖV:E*ed|:%6(>6?>~o ;vGE/e 5S@4UR~Oh(1Ri X*ݽ3*/{؏S ԚUϾݏƫ! =E#5;??e"rOĨ[mϞPǫЖ-u i4K 5S|Y"~ kV *I|"#Z 3ng6Ywʤ\oQvp|,I5{h'>o=g*I!b$V4iڰe}I 3^iօϛbIg"ImrQ0~yV-(lY"9"MZ)O^['%m)[7Y7%{nhZƯ?9KH8^gg{zqO//xzV_CmJdj+2 L\^|8iw Md.:kHA׽ǧRkT4֟D-X##Նu,4!HΞڠܑꯨ{ȭr;nJMblOpGІ6/M ҿ@rJܨkpfa^ r;xU_T46'QDkQ-y:Gk3/pDUN,  DEF^* r-?|4ZSmBTT͔\Ѷ +q$@\edPQq(1 g3( C8ɔ! mlr"TZbE`8=}G#^+Cy|ͬV!# !Ab[ƺnOxĆc%1ɼTWwoi;Ifrq9'_"7풑Ay՜j%YP'&2]|c;'&'}/$T m6wѱF v~)f L!8m h#ti2mXnɗޯSH(XpЎ}#bpl?RQA\*iyv'Cfi].?*po(Cӿ|Q`I$@B8(EH"C(J$qo=FG?ZcmŠ$?@:$X9#j+'mdQu3ɩ./F "^f?K17,}qN#F($٤eثMν(˓%VWٛn iC_??W@]'E~۹A[7e]|3|Z]'a?U_:$v|ܠM }k}~摋(kZ}K"z^$̍VG $'ygD:Э9 XK_N7W?"Y(t Bt4aSa_N W#}>, $Z5o~CcɊ@X@ * `-sM8\ׯxhE")P.H(8o$xhk(Ei/ɫ,WMj.JWYăQL{ޓދ$…;rg!<;v[#y],"q?&3ghĒ#?u>Nn]Pn^%@dMl"I:cVFQEBxk9&r$4⪒(xrW#yil&;ضq$P.A%cGIop(\_E׶Iћ+.nb52$`(CTQ>`mZ{o `(&!1U\"Pw1DT)2#=pQ$j*@! BFH2 ;F@ %e\`}n yj/FZVԶ;0%DFy\" "*䙸(HҘ@ŧqb15:1*ʂp 6f+Mx~ۇFڀm:@8%8Z٩f"2+~6}Y%I$P+8Myo_0NJ=,뾠Vx #szѥ]* 0 $S݇A q,]lMӍZvɮfh?jl EZF,]|G(T -FYz>)6G}t9UYdXm@My/|[YT%ޖKs39UT',XhĈ@D؍\ZXd誴XWI1Edm?-?IWO"Υ-3VB͖/w̳J#ZiMϫΩ~WFeM]OJG~3 S[Ԗ#}^VH՞Ji6)ىm|J~]J@0عHGJpPvg"\ǂ'JAx/fץ7yjUoy֓C@bZXPl)ש'ץ՛g\[JMm|MfXY6}"eRXPy_)E|m.Ktvڪ޲Ie-˼4g $VdN;6;?FH IJZe s>36Y?l]N&5;7x5V"e"CDŸZ7^9MTȮQ5;1> nTE Pkh%H*o[6MLw9 HZGǞwf(׋e mw9 -КǞw'4V"=e"9I'λ3Sj>pՄBe5WC(7HU8LX]zf YibRC̊hS2SB]'(?1g,CJˬhl!]>?BtN?RN+a֥-c,׷PV&C򔚵;f,4tb[j()K %Үڴкt{/+m+nʱhliNxS$ Rw8/[('@x߿4D,^) ;BGzl{+s_҅]) ό̒!/ sxPԮⅎ Hes!$7K>$iBYҦ]]}陑DK?ީ Ӊ0 eIvkdE5_@=F$losQM>,^@8SHb ECFYOⴛ PfgKu! 7bp!^ {uF_Zl\'S1 \E}'Bb$d$kۈ8A1lG,)D$Y<bt>}KBdHQ(] mZ8,s+}9fx-W't2ѵ!N=߸kCp3ϻ[~tғb{B󆌓^)S"v\Kֿ]5v!)}I7#>9r%J+L;3`<܁oIHO`>o+SYIDY@T.WÙy]1$@ 2% ~0Byv?~Nv I$mg)/h(9R[gʇt)eS$qǾ9ڄr1d<!8g:H p@]7͞8 ]`w@8H#5@QV: `> @ O"&&V{ PEY?_ [䁿ɛ[=Ԋzoob^S5 UyssHtb^Se8B85ÿ(g<$wV$"A{on{*oTV|kEH"PQA|pG7ڋe*zUڽ*oU$ԨxT7S۽*i¿ }7ڼsUysREz"I[FԈ?[")U&k%P`pqYwm<Ճ'GR x' [CiqyKA}!Yca gA)nvQ ݢiY<aiHvlC S=sĊz. [t.4e p84(l4=Etny A03|58e 4 ]I<04$8olC5LtNғ]Hbi(kW]"1̩fd fEL[}g*)rw66V̉FEkZ9[+N 7Q,W2 ;N U SڶO8]  ˤ?.DR>KpJS N*1˦[8q, ;xpJOz jU =Fi+d#3s1籭org*=flmY$ѡD3c/TX˖o`[Dv.p>Iv(E2:^xSbKs.jM&tZhJYs@3YrhOD-X5ַ%" Ȏ|Q|1YD!KfU$VqK)JϲQo--U+j k0 Kӄm}1X\7Sf.Nk$&m&DtMBo­HJ1C;8Q@H76_$k$1dR )AJh[' )j!O>֔!'n=" wh@Ժ&>{tvH;}`t黀 M;{S w4J+WլqQB{9[kr8.>wЗ7t+-n="2T§k#-}rC$^J/c)"Ň?217"PK.F'@k>Ĭ|ɷIV Ǩ!yi6/N_z[E8l%wD{YHJWP}),qh,V &H%@A꺳c`W*m|hErf!B/9ޑ;\=+U:%jH6-3 1_N{ՊA_ esP'YwgX=W*LW EəjB";[:YR;kR,o׹sO+uv{YJ N%ֲc6H $?ƝguHciNEsKjY=eH;K1ǰIG(H"ݝigCC^x<y6İIG(H".;N[Կ͑#.4w>Jsdv譳Nt5>sX$podo 5ul^u͠[g'.c8k++}|pl, ȟ\%?-D_*{o: '9~ݰOaaN!ˀSfsf#0&#3:˭Tj7ڬ\ݯ*uFćC rIO f:u0~ZIm꙯N45ԮȇaT#eHN+&&F*rUzciM<3ow #x)jD{;RQ7z۵Г P j^@ꋾ{;kg)lz9|z{@3?VM4(Yv.@\69|o$ :ó~ Dt3s[=':XHyd/uohKvWMF#(TRz,ۚDҥu6?vX'((4O=XH]\y94?x`L((O= <ZH}n;{KI{$.u]yFdub5 j+՟Zi?*9t0Pqi!rO_sbzL^)",Swy,ZH2HNL]?]Y|vDqy|zkN৛RI]g{4CX$ 6p*I~aۑ-csH2JFsN++!$nYB$n\wɳf_/ld9!Ch^f `;9N̏jHbG+ sU#d}нܹDor'AIَiĝM󷄺nXʩ8mRTz^C#R֒4y<ǎ=և>s`sFׅP5yRsv!ջ O8aѶ *}s#egy}!Q.MƓQCf!I Vl3{yQ3{#ݢrxk6_/(d87.ak \($ tl!~?ʕ#};:dpf{vͣϹ?eD6,IGv'pnOmZ6<1rks0ޜwʏ}^D[{S'>QK{[O8' Vg6:ϡi!p$P%O`ӅѢF\ L3NJ- 쇷Abx.||~%)cźS% IU!Tpw9OyqL*-Ceqm{{IvdQ+SxmAWPAb U`Bz1Vt<]?kYϚ!YqHrK/+=+c؊x7+p I6>kbiav_]wR4ڃ|e><{cC5/tU{+~>X>|xWWlye\gpzǰ'Sw%~CK/+3HWX?ǻkw% _/yi2fT'D¡_NY .0.3|dH狊"ca^CsBKwI$4l"$!hT[q u@{>_Ϥj Mʕ4L( B(@A(]MwQW]ɭ'{&^P_~)pʗ/MS 9St]ۂlNk|4OLz2#Y+DErV~ ḛˑ'x)~ψJ{VSoO:cmE>rg\|y.^Q5%LE cJD?!{SuW_}] 'z7nwrwtz:W^_x SIUxq+w.IOGb3+'9J(Qߍ BdDe6xǓNOmE=H<(zLg$ٴ6i݊w^?_g.|+>|7@u#Y񱋋鶆rGzK@@='f_a;[3_|#KFlF,[tĤ\~釻\ 3}_}vժܺ|ݧ}6cG.sY}⑞], | eЍZC>3kWȯ^HLmr|l7_n঵~ښşn֗kVaj>7{G>7Uܸvn7eS_'hgopBŒ雚_~haqQ南/Fb Mݲnt|J/W_۸;K1rM aA>$7?Qwה/&`Z9򻵵C^_7/5_W$0!*7  L 邮>n hĩ~V&#G.eRa;M;FB噻஑-^-x`5G.w̨\z5~[ذ뽧6:oKZo^0ÇnsNS5_#Z nbV Ѣ yZ[նHV֢E]OaEŠycԲ ڨT;5w**zƺ{-I>r[֭*vp}ۮ6:}C‰*Yyʥf~\V~5D'w#7ćx,kk!>??T[` hlxkQߓ.KqIuxߵxb!~=/{u^]?xKzw[^i=’,t-9eŽʞK%~u<J@ ]yZZՠhO 3#4S_T?w+ ٷΛPҺ{Uڨ3ϧ$iɈMl#vG R c#'UF $#zp!YQ!Љc^)F*8bm (f)q퇹 u:b$^w `38qۗO)q,Bҏ |yoM }wgr;皟ɖ߲wE|;w7<߭ZXCHS=zBX?sڙL,MMz571bӣ~k|%8/v{_7]cB/ߺg՛]wlں_ś >dmc}jkm}n(}SGk!Gd'_:cxg{vOYCjj Y._}-vi>["Qijc*_[8//y8MlDqTw~tiaoηZL_w᳏HFҊ9*t &8ȗ <_^®c/W~\Nu#m͓#o>;4w+b.xh(b/];&C4չ&qPyf8+{ҬXsk\5W_F+['hr7fTvIUrx<nT.#HBa#6-ƽT0Z3X=;>x$"ݺL;7t3\yO]=o4]eJYZ(C}ɜ%իf^8,Z40D Q8;剷_ї!E>rY:]Y@e.2kᒻ[%Pff_tuKș=c4r!kM.2lK^vDb>;-yp[dU/^`p,޹ZxE+?rY1]\@eg.2k撻W/Z'lV7™@C:h7_.+֗4(>W)Wfl;kwѺU?x>DGy-yskn.=ܹḓ{-]jW0x巟^?R0sAeϜq쇵˿ZEzgӼ7^pEY}2N}-_j'W'~%u L&{bvʙ9SVG!S+ ?YGg3fw4u;_uAA#3gɛ9[)ٛ#?]J9:$Q k׸~C}8o{.0v3T1Z)+ٰ K6^W+?+vbHVsu_a黯k뭒~ɚ9끔֊):˖Щq풍_k̯&64wև A!x窝@녮7UbYԐ*~5řS pހe- d]ZjHZݻ$"{ӣòf-%䋞 GV}Rs:n7ȦΜqcvYo~nxժ/PCZWfϮhD"M"Σĉ|1;??OiƟ\= C1k uD /cv 52sKs k3DGSV\ۇUVlt>.HyodBǖ^GUVPڦyҦ~FN8̥W߾Zs%:YU\VPDN-~ {9F\KI:KDÈ1Кon& ~S^qΘc^}">]K^y1O-D/mYW.O\1i.UjNO1͉EU<' NǾ +8Z;:w2>t/bʦѩkqљmǟXNwl(yY_Yپk =ɡjWE@:0G=w/tzRVRsMz236@Y&{/2t2bg6<R}]?{'QCHJ0kWs]yhDI#µ eN.\iuż4Z6 ֽ?{ωQ%1(bc񞾩 qy7!/| -jV1Ki^H&:/|ȴ06G3Ʀ8x/Ϫs;8*?Za7Қ3y wwM< gչ'ʒVUCo촒z]RV?|vk Q# .z:%in*7?dmmZz~U%WW:~)˦O!-=_#GvGoa ,}ߓL2ݸa]횻~Y h4x:56N1Ek;;PWƽ÷rX;3R:{bۖAaKWmLX S?;wa,Z1i=KqxUe~ځ۴Kʯ$Zo#;aaj1u.Cc܅L`54i?#IxWhfn>h[5 OKϳNSZ}!v^8xǐjϔN]f5?]8ħ^}kD+&q u6ݟ5 ߡ Q.!v^8!yՁ)+F9Ơ^jk=_W wj} DW҇o/V/aM緆&rZ5\/Kn4 w0c0^B}IPewYzǹ G 8IoH"7A#;uq҅˷[h]a8iWUyNJ;LY;p?5Np)XKR~B {4~/o7Wfή{,S FR\|BC<\qUv#lz~l")e\$DD"%s6]nX Sxw"!tzq9pŸ垮.Nqe$2ݷc'>'!Fܫ* C]Y>,\MdH\~8>%7R-Rk;E]?_ e-MYdo_tU/[!dNiʗ5KLvZcTRo,M"ș 5-~$ET7˦DR77W>982qvMߧ+*}Vky Ï(_OMeȝ]~sHW&mZ&vzdax7H$8_ɦ;MѤծ|qFSڻA"'~t>_jϛwxu-6ɍ昨 +LD,^ERle+ r9FV)CLIlWa"ioӔ~^;4'c"@: >x rοZxr#h]fI.ȺB1[BX34TҼ7Qu)4yH9lӗ.@B ڸ)޼}?Ry#~+_K~æuk܆*85#騌hnً26uV=cd8<(eʝQ?H H sjM+nsC*oOs#,1Fusҁy%Tnӯ[O\ >ؕ ]m;۰ցcW埪5O36ϝՁ.eٵ^_zOde yDgXTKNx];oO;wQi>jlJUoz͆!Q՘s^P)tr̠Q3;qeLHwp@!*XT֦r5ɅIOu'\Q&s!"`)p6 teô}(Cb aF50ZeoD&^=d}$jD A&irm9ћqڲnLIY,"y‚&uǪJF>N"Hk*ReaD k-?T6 RR#Z]C4 =!".Pj YЈھIS-+̃rc!VZ%U w7UY9A1+;6oys;AwvE|3IWǝʍLO :=cuZEޢ }NocS(>ahqZMmՋʛ;c֌~rzbI^Yu w:sLgΈYsoq[sŽ@Лz.ДaKgfLr_TbP"QY:9޶ufuV=KZ.@Ы]⇗C5~̸#;ge^n}/{-//-ι wW of; vU=Kn? {'@B{?_G/N[ٿ][qkyS//[s1AJȑ\@wyg0n͍[[w݅Kk2]@];z}Tۭ^wgɓ6*UAf;-vR=%XnaĉqOi;h)F~ۿ/-N mARKX8ɔ3}efhF :;$m˺:wg 7ZD;g]w[^jaW9Sa,V;9xU)ۖLʌRJk,L-VXoX6-.n+D|!? W{'U*`bґ[FYKA䮓o.yUZ "ͫ(EkrO>+lNϪ쮮 )_oYc$q;.S 6 T𖻺U1d_ugwܥLyWޚm`M>mН*X֛j^AP*ӱ D- RJTp2)XT뮢tA'`G~2)B&@Y[T=KrءSRTFpB !AtXגEK‡w|V$Ӥ{#n;T+\Uo1Qc[&6S@"C[tyҍ_ʣ+RƻdUyEI2t@>OT< B텪DXf)Wt܇QA%AV$BQ0tRb6hN=i?g=[s8o=+,SGA:[[ WEܸLV®},;o 苚j6mH,22ictzǠ)EFÙS^ߦ]};KVmh{z68G n kyUK9q>gw|֛uqF BI  PBu >xHJ)FK祦 6H~U05Y :: "RWHhLc N:@Eu)H)"dB0Pߌe hvw'v9ˉ 3s4F `DXl:.|AQVKfH,iKd8!ym)]feLAJi+|3:ǟIH {8b{Xȷmgr3ܙM@iK?b"!X|eEۧР-Wk%OgzLSiHi{fGG8y$f_Fѽv6c*W ʲ*O$cŒ$@Ψŝ-29֌3Oh,iA9`yGUG᱇8"YŨt,yOOoVv4_2͢ m *<1M]D3;O&rf5vLtȯ"NL||8s v$ GGKw62)ci/OJ U3<Eк,a|o|-Lb{ ӎyO3͈ lkyMvLQh.: %l*`l<^B_iS8lQ!HWHs.*-H$C؄7g8$n"{CrZskGLQЫJCT`ub>߾궑FɆaCq GcLDIms*gę-">5Db 1$chE6Ioy2DAgchP,&=\*ZwY CgccdPJ)n VgMJX5D},4HXQ;6QmdN>9?}T%!ls0]mU"C%MC\'װ5>$TJQ;@YCΨAY,I#%M.&R4f.NE^kRb[FǤAJJN}]71ێT+0R/#rG`2-cӏ)֧60Rf;Pu.+0xhXWқQNvSK'ELmE2E8Cg\ZERCC.0Hf?&g ("duUzF]*w B|*rssŀq٫c'^Hvbi`VB믴>Ug ?6@jUX~v9X\dl=w c4J`E,G(Ѧg\ k}i06) PE Lį(,(" D%br+J ,@!aEkϸffתTA,0FuZa,H `/3v Ce,]0"S髕Ht X:-t &Ea,yU-_NSH)_s UShV7?fjar@vбJSU]h,"EQΞ'wV%I:5xZv9'*)׎9"裔 dQg9|һ(?7I NM:' RՎ^x&pZO9gF!c`P$` :{].lwu5K]ژIkCCIGyd/SUNuBt1JyB_n&gI}dxS댫랂B2 H3beWG5$`c:7$$ f`&ͫ-7v6 :T8}Ȯp )p2QX*{<ʧ ~EӍG4m XМ3V HFa8 ,#$z 1G $V Ad4-[P>}LVA=)XtN: TE' E c!$znf(6uQ.Mogƒ#̒T ,#.Dx&˹=da*w&#}N>A~nQO<#42sّ VF"tT=B ##0c jNe_73Q2YE7g,Ğ\DMm& |4T4)X/t[O(3pJo_z_?xxi_VS rWج뺅l)!4KKkvaUq EyR \-Eou"ǘIXFVb"]h~e͞ kt68|嗼.<3T?BBQ'-I:֜RjJXFՉկ=( I*!U#h-e>V ٥~v'eA[,ڙ (3aIUrv"etڅG$aB*us:uN],+Zj\2)bHnX(K1 <'*]́Bd_ I1(2zZz^HŒ\ZKd$Ŏ*:w\y3Pc8cߖahr5jeh´F.bV%J!w*xIC_ah^;2A9pkcM'ê^#&lХXjN/ vhjKb,]}ʟ,_i[s i"\9׫;bKᙍGG(n==&=0-A:% ~7'[c]gC2k`P=o 颢OμILLRk5,w.K6k,N9@pcbsc/2Ӭ;\0i(lޜܤDjHGJLa2M [=Yƫ֓Wyv-ԲK.ٝ L$BH1gTa6"F%`>}1"8)B;+0ɇ=3[7϶фA'!"D!*jn<ag>sj`´ZJA\\UжWqg ]ChQwja~k_AN(۴`ixf0 DePBHIGmx7 9Ӧ/V/LϤji0;DdDx)3}S=G(9WLYnA<9R@*KfN*ѧ?Pռokk׋:w_XqIis3g?{5MUن*͓2B4!B̢1t3[yFp2+IzV\u)LYUf[PYѓ݇Ly~/E@Ѧ„Tܻ嗣z6 Ѐ5&" 1(9Cu2ep!IhGn;/'3BKEQa|A^>jڙ'֯|jl2E`B!pvMtDH|oh/W`26[1sMC Y D|DYD YU*#ŴH^xC()ڸ98|$pˆU"Ƴ&Z[H5N2K_5zFDc1%ǑG`ч=T:n hnki|trݹB) $rh~/~o N0BI ,_\'om=rUgsU9 h(>oտ/hsN.On;/V%xO <㤵}(7!pWyY$SvB#i8 [ #ԴS׿Ӷ%hC&ZЀ*ƒVfǁb" geW&h3jz%kO^.'aw{_3J8V~IC}fue]/iuB#ݽ@Kު/h+HrƦ*OVn8i/Z' <ԵeW.N 4Bjqo9}/ƱQJj&̗TŹSM X!9/Tqׄ%gt> 8.M:2E4kF펑^_2Wk\l1C 2憄%qB3¾j$cv`_ a%i^FIB!ľe#~9^k$f35AH4,//R5lΚ6te\;/u$NB +Ss6{yg.1Ph@H['/NIt%؝TEqBCtI/XYqO3"DĂ6^{ؙKU"#'/q7 ;F 'Z;49 {u$1NH5:e@w6=9~:a/j8mO:Ovp0\z\_"! '/7 f {^$]wk?h>Cm|<6V{B!g.yj,m'/~XgqB!,HԑCy w>hyX'LZZڿ͋zNRuICyGĊuVN1SR5,PCW&-[5*O$Vh#`ǽL ̜S`PKjH8ydXZ X; KznT/;)d9'ȓ q0Z缸Hr~<4pruy!2~i}|؊Wsu. #9uEj$RU3կq22>ߖڵ+F~uTo i2 > m+CVaW jBeFq8w [,k-z'ڳ(Iy႑5ߋ:y; n90IQzA0'{aX$-hafG"4AeRPk~B3>nwDÄtrDPX$Y:-(^3  1t1! sXR:G]|Bh@&n1 O/=5944ܹ @R \ʼnLa|LI41|d8U_kYY+9mqq˶Zi3R bqJ[Љ 8$Bel^s($:ײm^EZL š{/:HV TZ[8n5反ST)iYɲC>Y=gV5sa2 Р0b5Su2 6doroEPH3FO}guj &j[6*hPV$ OǏd1pgP14H̥TIKZ+dY@HXϬ :Oab PɆ_ at,/gRWl8UmRX#İZL3ZrLw4v/fHhÏ(Q|N5kτвabamJ4Ja ӋwojP,avQݽB=00Є咘F[19.,hRMMI@"M>S%s -B+6-j_A;34/ShN::%.֒lTBʐЊ$mxZNucDp'F8 q &uX-&&PGmUہ;uW.My53r7p d\Fm%v{?b[9a "+*٦F?뿟vD>ɟ&ı_kRGHI:`Ep6} 9nѽPĻ eOxikyߡ6DBٲı#iHCm5e}Vk+ ,$B(Z7}?O\D=M?GZ#2p,X=aC0imgt_ z)zʯ?f%9rܿgYkC7DAE''3˳D$8h2b/*aޅmHE5d$/&=. Ҁ9P!br,[(f\D=!rHh8Iq h%_j;Q_J)V!Z5K]c"!4KL NE.oJ;)R9Ƒ]HJW],>?/6n٭g-YF3Q} c:TeIQ 5< ? 1 vo{Q?0Gx@;JD4 )LCn6|=G5[I 1`~_$%%j 0rƦ3^WФ =fm~ԧ- ahX?޻Vj՗⤇Η= 1TnWwʷ,Smh_/uYDҴ;/_~M;R+gz^K~BH4M]y' :Q$6n R6!IeMǑXGcWۆp#[ʛ9L~EX:Q{i1wfq_?pV~P҇]x}C/u#^z͓voR{ HE s k:;IT #;Ymi{!ʹSAlYC?q3T g0<&9Z&bb`,bk Y&w ̇ I|dcNz燗h`:g;<E*ɤW sβȰRؿS[ǿi wD:b_teS}#1S|t_NBQOj.Ow-N]uQQb:_M:qw/itif@J>iXi-٦H6?g|1 /9${_Ɯ)U ]KhPB+IM|魍C̛{J=W QJk82Q#mm8-sL甩8v+̃zf'v+dM\qHxsJԬY|!%z(+<_-KșڒQ[Ϲ lz:HT,JՍI϶8q1Ыq"VH$:ԖՔ1z^" EIZc {TG nd~rLsJ'Nd;檞AI1NI&Ou_!6{]jW:I2zdz^Tkv2q\prMTu7;UH:NܗrUĴ:zh# *:\["к"ca"uE(-00:T+ޜUI[N3p;uZvAOk=n룫*MCYkA.InB"o }k'0o3 5J ZWu!dku_^.5{3D kCuXWHE7)ՍդgK9rVoP[s6#HdؼU+Һ=!&,H,iƶ( ,=y.d{ )hBXT(=zK`.|-;2FyM308&G @,!8ahU3X#8Na#97^YPJ:y k#d]/_pQFjmt+eåXY2ir~[&~I ǘ&FC4m|Ҹ^ ᅱ_y{4ȉHCa VX;c ZHJL䪗-mqϼӲ#ݚr_~zӦϸ~ZnkQ[L`\4`lt `#‫s?i=ο/]Q ?F<UC1m T8aaL0`"db2t 4XVs#Ƅ!Jˤ>xHEZK!LշvRqQMϪ52Qv&.ƱQ[wn?+A*;Ip65߮{rzBŅ[?VQv[0(!P1r&ܱӾC:w;>řtO|df-!6A;"уFM;$J#<j)v'ܱ-OS Nȋ'?Šݪ"'\A'vXTvxMD#?| t}C[2|_k.BV:-w}쉏(J (5Ԣz!NJkDBzDx#ڎ v" у&WO?Mq-n|mVxB&~<5;v< p&:=dԌMvpΌ\K* D@XdѧV Z BjpW"9/*NhiJyѽೝoX\UiNR _C[ݾ  B>;/xǧ."/ VI "xx8vTm#3'wX 1O8BFRXOGEqā>Q{.O{ }RW;w8k|e4yd, 0DařMM<)JE BH* QHB@0GT kDGME_x:3)iPh#Q)2h-U? A&*'™Gү(f~E4*,#3xCt e~l9mvt/ï{ܺfiNH 7#/m/j@;UVh gb `6}B\է##'f<$Hwt϶~zr18W:v:e}$3$~,Váݓe[SPt7 PY+pf*@4pfS6(ņ]xM;b=UqcfkC .L0M5`s/=zNzWmqƜ-a: !Qn>?W`$jt)xiJ3.xK9g VVt%cs74:Mb =GMZ_9n]u&8$F|e P؁8=;SDTV,  }uuݑӌ'nqy-%ցɷ2Tִ>cT7,H NJ2Qi Rm+YKWyI)ˉ)aѦo?rjȃtPg~TB.`֔0tRY0栟2kq9;Eܑ3+h)7eiAl *A =aqYiWɔ'$!|Иs*]MR}a]CleU bBj`Љ}suϟ|՟/ kT}?}u(r21n)_1L0flvO\͂.d9 Il}مMfԣz=uyou%Т5H qS 4cw~5ow%h:־ 7 v 篝WN8pQN"yۜ{yO]]y;<ΒTys1&nD--2z ]<#'=봧G\C*,}K81;y略uFzwJ6ν5DۧžwzI#9zu*R4L:UP\sRxf$ ݓ,mqݹ@g|fƩO&Ůtz{lNZɏ}(dn{9(agT*;ӒA$te^pWHȗsq/814y˓// []K|(9q`9,^R %̨Qx7FͨICVA&U)=12%vZ;0gx+kbaQUak71=}{T`NJjav%J SI+$ *EY3ZbYc﫣ȜԺ叇_о⪧Kh[zPAwZZ/ ~fdm42-7NYe\ai3o˒fXyo}, /z; Jo74.ۊ99#7KA8Գ|Պ$v A'_ghw1i Jo:.vWsg vo.# t֗᧝}i[ߐڪܙW]8Vz/P~3^5tUsOmʹ3/vֽٗ7Yb#r AvC`vP`HB~H{ _{Y*-w_ &A,bfnZ?P@Z**E-!!E$9IU  E M-YR .;8;Z*6Kݠp6<%&4!X?ZK236ϢkoRh@N@,du(0OP.;*pYoWN=>/8m&%:pwf5_EE/۟,\C=}n?!JYNڤOba؏PmV e`dJDJKU5r*G/Rc~؈RS7}`_rN=hJR 6pږ$5K7wr8qwzgwL:UZw 9,&;1S lKXM%읺$T?Ou>uϕu}ctiuczuڕhm&7DEo#7EdvD[7'gu/7npmq`֝wN~eMΰ'iwҵTt{tQ ,S!&cLCtu+}exᮯ.;ܛǯ0O-#A܏,]MF1͉Q5˼l;LVUAa bi[7/3sC矾ΕƑ}k4i/'[0VMϯ{˥O{< XoX_6HBc)[j(˒I2gAjt2A1B*ג6}Ȓ݌xFG q0$'rG E@N@ 4/^¿5Rv, z4(5Ht0QE![sQ~>:R[R)fA`.U7^(W;gn1Cva|)m=dI7=6/fŁ>Ґ,vMB d29A:3_7."hMʥ9 "U 8i@K0Lhu#Jeעq$Ȑ$v Ddt_Q¿5Rf׬/Uy0ʼnpA;B[hLzYԭcyt+k(< :Vx4l `Խ<~![!54ǨZ^ڥ0y%046:GwdiiW5:2u퀱*c;1ϥd83}g=,0LKLI#yyn%11}&a &/Z#e_}wT@Vl&Wwj9؝EGOA-M:W{+ޟkHK@)6!bLSs=뫏n%Y#tb4t4cʌ۲?v@-@2B)%艹.I3EENG4]b$bD^텤k\yXb7{Uv+^ n=/ uU+W/G7=һ!OCNauV(h jEDe!Ɉש}&Y,R{{. hz§?O;Rg--Z/\f;Hb5 b2"4hq섢 #n|a_kd&a— #r o+ \ۓd!>J?f\ˌE;23t&tBD&j⋲ҕ+ӏ|f* ?}G-bM|V! j_2E75T KElmZOYqN|;~g#4/D+V7/sKI,pm7gV+;а\@%߅4<% ||,hښGu ntE Nb绀ݬ~"N;1x.y5kowWʸۜ=} `p !ds.~07| xWƟtͱN<$o@AWُIg]P@%k ZPǰ&  1\b,ڻ6,ܾ0Cq!HIJ$ NVIC:J-^M[Y3gk~n3O)Qu-T-m4-Ĺ%*CPlA2!P8l@Ac @E _^ dszR]C2mtc&V" =;JnO Y >ԤXЀ,[qJ7'=}Ԭlh# DS6Qr +PU0>UG_|}nTO2D@ك⺷;c Aųr^`u, `5fI|g?j+~JcQS.^aeԫ5޳G5~c}a9O$ـr 'ʟXגaV? /rCEnaBrP L2$+@HX ^ _,Yz7 haB{A ҠT??iaCܮg;0OZ_HQ rv/nc)%)ਉĎ}rծu!B||$dWQܺSd8&|Q5Ǐ=I[/{uԷUƜm^\ύhM5t7{)Ϲx; #cfq_Rs0g[muMVNkΒDm;_r ]`1K1j=\M \0E}rB˿:H^ƨd Ykˆm;oU$2hۇAM='&x86Yh9g 6f- Y# "U"!'kn={Ղ}μaMK țgTDJ󅖗ݗJBLdv]Jwgk_;R/88CJ'FE;Y0dw*;_߮{Q1ʕ\OZcqwn|}AUJvcC^3κiѦk`T(z3oJ[?{vF̚S{ڸ_n`/%sȸc3t<MvMOhztŨ&R1_߂3t_v~zC>i:s n|up'a ITQc0ACTI9ˮm-ؿ?6X0q;tqYR|Q C3Ve{ ẴhwaH(n쨝K^BX.\jjvJ#Dst =1t>˱; w=T]կy&bA4U:UGv!QXbw?[/P 3_nl ZSE+l&ڙ"櫵&iX 7x*!RtTir7 ]pNr<=[<8scDe *l;֦lUhVRiyCO=G8tčo2|P_bfZ|pp<М\C$ 4l`5K{LNkUNR6̵?a(dotY%8$@(Tg<>(;__]p?N b4ё%3)-j0yKrw9=w'^G*@he5EMYǽC~=}"TN2wR{"/ۓfvX8HTVd@)S5R&F`cyViۊbzYiWlڎ#' K  `IsN d`o74tkTpiV'1Y)3afRolXr舕)SM;hXs_}~򙂺ԟA8!*mzȒKՠA4L{u ə :2jUz;~`II~QɰLPVG1_,Sȕ>鍟G!-]$v/Ң) h3*$*5}_pM-G}rY[W"&Hs`3"V@z-]D8%7q5C 7;,a2lzWp8 $FGϽSzXAWjp?Xk b*ZR+ fyEFNʴ7ܟq;I/rY\"Iiv>ZzT9诐~OB~ׁDި[DKsiQ'Euҳl_~DkZU}%c)䬂XLy; ĕx2l{<Cmn`RI';%HS ZNWN=s-ݴmNmsϯ\eP!=35 +ϲ1 T{1_zCoi;bN7ΣD1K(sS=vSf3WWؐ$̐lMjb - ]93_j'$vb_;oo@`TW=ab>kn|iwYJ l@.F @U4qϰ|5 XUSjgO]w[0:e]I3I%(n|՘JʰR{[oyFрT\.۠hпQQCb:)ɴagm|ѭh[&'XmOnOik/-~^k@u0`}˥g<#~/K(S r4|;i0y0دJӃpccq]q[wV8M|ˇ컄bomT^ ]lp/jۡA|ʕ|X!L)[nR'8eP->…=ҠyV5Gu9ʦGN%gȴl:;^_Zy=p U0{x>Ԛ?7YT/5Zk)#tC?ݶnB`mO/:)BznCN|Y1d`NucեGg}=Jvh3dѱZ7_hx/QBȬ襵ZK8Rҡ6׷'g{QO{=HQFbX1s'9Ӎ(t_g:(8@@"01,JaA 0QH0 @PlB&B h(AE#aC$^}O2D(eR`Qfan *բԍIl1 ҐN2&J&'%* o?Q\ dHFGDx+I)p4>RTO X%+Etisf-3oQܡaʅr9͉w`}tRf."7!P(ig :YB S#Scr ;Yqj},Rj- &$8fRwN IoY7iuO>>8ಉ_?] uO]oca|;ya,*p]`JOMs. gObkӤzĊTs"Y+c G>ޔ$*άc ӿgk6QӄڢQ TR48F<5O/ `a LJ \mZH{U1oZ `Vȥetd d0Ÿ"yJ>A /\Ś;Lu7T)_ 31E!@M&VRE^I ^EXL'\r$ͻbQ)bx$.BvC4\ٰ}JcPLルZWl ;wwq9 %W<.ƈ!; O.-ooG퍶IfN3qg#I~}K:# ;k#]N,ʛ@k- Qfe@C^ U*qt5|} <P o&\B$,*2÷ϫ>i-ub^S!oTXZA)$gCFsɻז/|3my;]R3`\0p׆ j@ષȆ>+NRsn;F3'WC5 1?e^-ԈMז4);3jȖ)\tPyg{uۯmo:qN`Mmc&kLˤ}N/f=f@^r\m.rzQ\)G<>xb- _}u)P ֲ!B MxbcCPb@R؉gN޽_;_z-K2DQ'&n{ڏ>)sNBjL[^$:pѦo|c+@"[jo)O.4P ;Eچz{bj_>nxkMoy~bܛ=Aa!Em ۤ/ JUY^g lt55"ѱv|]&9cq81R։'OC8- 1\z`h>췱yn>.\>!0jhd(L e٠3|15#iNLy5?6DuNo*F-{6 gH'ͼ3gW8tR.-hotٌoRf'J4shEt4(q/q&wxdܩ\R"hXemSd3 9gᡏsDIڌJǿ;-]Zn~o:"35gǭs#ȪƢuS<78 anIcάw/`W=b4(r$eOp ݊4V \+#ktϼR`q~;q|!jbLjVe=i07 o@fժh-M|l`gtgi. 7,aV>`L]El#XᣯL2 ib{W[Z" Dr>ZwkS&$i*17B@wkco:,[bZj-rkCq3hOpM^(wpN̛@@He; a L`+(T19"@o!T)P"bP#hE*BTU$!%EC,QbHݦW5QϬ$,$Pj"J.)䙨91d8!zTc} o{ _I0 a F@jۨVFLEu$}s{.72&"˪$V6EzXl!GAJ.DY@UA [@idC@f4v. kЇTh\yT 4yb{Tj# J[j@Ƕ<^+qQK2L }!}q&fx7^0JAyBf9\SV:@:bѢ2X'l0#&8ҒoE7aՈ'HPDd˨߹tӌ[[eՄS݀hsk:b kjo| *T9Ӭr-bfFZ4ox+fMKb|fV-}l/W;abҐ—\ xDSv: %cCVOu&{MMY֮ +L's#?&&ZPI p+F6l -[">y%ИLæAҚ,7VpI}cKa;@5pvYA~-omp"S3c(ήZqܴH,+d݀hk H+0ayz'M1(X!&zrk-"J10.e ֎h%ќ aZ%tBt Z^T0ï'6eлV 5$dVo`#G!w,]i- ޏ8~Ԙ Zбg8YhL{Z["ku*}n i٪WҶhmyӓ\0Vbİn.NVnT\?%w2%.Y0 b#+HkAB5N/ii^ Y %(E[lږg7/R @d#G=cΘF542>enӰ8Q-*_;ΝF=t=6wjσ q7_(J(Z+kgݥΛx4QfƯX-̫ ;\ p}\|]#N%oʴ0ַ_W?sl/dTT-ʲF5b[k|z qoYy D'ot C;Mb^_\ YbTbi\q]4l0t>6I`(,8DVGd[[wK$Ɲ1Q㆚K5lb[5=cǔmsuv f>c y6FexD&ˀZ@-7DT"ogX_8P a E„i:;&pqyT0I J*L\V\ AVq1em*j*ur4~59E '"3s$ `ިsɦTK&PԒj uʡYr5Hvj% >Q`Rf .;k-qXSsϛ8u(Z-8b1nͼ{{/[ggL䵚S@Jp>kOmflyHs zx1 3B:UCU%WD<ۑ)q`H!p޵n~^_,yc=Z9-rڏMai9llrv9ma8k-e/]]pf-˞@C@ZAg$[bVLrJʾ!&6jXى(R?ۙxԤ%:4fsbj+y0 V-9)!|&o{PIE> ć )2z7HA}:XLT[0AjgnhFOﴰ&tB߸P-ꄖbmct㨣R5MF8fH".ִl3 ERs3ˀ:wk ػِ]vOY,uCm,J'Vp͚h6Cf cZvSJWGn4\[KRTKФ'A_3aYK Tbn RPL Fs$:A={hWOo=텧1C4=!l0;JC+ڭHؖi$-)Y i95QQ׭iNYX֣0xpd0sLxel`Y"44U[ h\*`" "{ *ث>>ITzh*]"` sA+N}~u@}z+]j6Qm 5=UhMR-WC0nRBk@ffܘ右JmQ&XCy 7;Sh7SuRIk&aA cGdS*/5'U96E,BE)[ UMNYQt(=D˭tHF,Db8MXAj|[d u)P0fb>ߤVD'⩡3 k#ʩti!)[ s8}(78a Bq HIöZAcO T46>%hEinA1m蝤Xeu5Ag +("ߜ`" 3CiF櫋pXSZEEJ 4 1 *; IT!+{?B(0pik$F]Bmdũ/Dd)2 vV9;]}Q''@"GB~_E6ac<.B_DMJ&r-j:)3 c,6UgUk}//Չn8{[[ƙw|1oU6㕏9>.c?v/ș-a/<OLq_ Xg}Kȏ,z!hbb3%ʰf}=!R{C%gW"dY\0}FT+"X6̼eVͱ v((ʂ޵kQ^|ص>t߄0M ҁ_Rk#>vX\}hw 's$ѓ&lI%jE.^NILZ[[: &HB٬/:Ec 1aϠ:m')Mwn쨴} uQ\B6-5YYV)'-Ήe4}\MAbg'4{ W-&mZMf+y;NLQDXҞ`8mBV:)1 4#hb b.V!q1&j]np`d4 F&FN\4Fo Ksբ$,;`*)=vK+?4Me- A2|2$ II %Af?ZBR`@22d_cNar$QJүO{pn4%%_in$|l/e1exr/Q|]:@8o|+`R't"BW0К0-N"6FEp%b "k;lPءڂ+B 065B݋n1 ](:Oi DS#d/`.SX//0JejdZZJ.)$jQdeh מ7y f%Tȴ, < tm4*17o08kKg&%b eEY2mp( )226hl]l6\Z ?$t :ҧM)#dօV BW[fD  f)|*quO ؽlUZq*?XLP2 \e.!rJtU@dXBSJpB))g"; \7>NPZA=/\N c ƁtV.Atb@M 3xLT3>4!QBоɄܘH`%.h)`Ly@ }rr(a mg 5OlT5Xt]"ن"PRGj.RϾn8q.*$c!puDZ媌nOTKU~ Q[?N-*OTK>T1UP ի1Y?D3Y#fXl ZObȹc㾩a#ge*.-dK"A/tU$0*!I)+% a1B Lo \F <݆; x00"LS_p!H"YK7("l| E CQ!!De6y ƊOwdcI0L+AaQBL[x{#ݦfj㳫8Kx @[BqMS]VYa#!O9`ի&ċNM^DM +k_ 18Z {A. ;C8֎yNF rS6QwГ@_vD "d@fBMKvs4!]A׽e,?kL{SK5K99%$$ 4U36ߛvygڑ͝rF 9g48XTW1vzȺހ#.aɁۚIS:g [uw%<‚3ࢲޖKu`X3p(θ2nqo@Ùg R-E 5F7K{;5Yр.WQ>sЩw^g z7,7յ0,0H{`G΍ ǤĿJPqɛvgx:~RILdg,1YX>5i\KZ(T/\Ra4T*rVuҞ|0ώtƇ.nQ{2i=`79(kĨ. K6;}ϺGόҝҀaⵞ3rJt=yŁ>{7$h,iigV[M{#"iOʴ+hY.!y25 f໑텧lт3\W_nwʸrە^\t` Ό D b$ņ%U&>{Di8ݙa=9JK`UfjTapBxbN@tHdJ7O1ͻ6w42Xnrމ%b*~Y|v7C(-:R6/{ɾ5Й2t&Q?/P5:ffJIdhH:xb?UfȝA&(BhUJ@JS TY  K?-=&SMRt) P}D(/&_$&P"sf/wdOjYӊ7#1N$,u$kmZ.uDrxdMߛe2.v=<[h~c* -~eV{̿n)i\]7ttMFFZM>Z7?F~?Jq()a<ϳₛ Mi<&'Ꜣ]y"J{s{~ٞ->Qٛbkg}= T@ D j N57 zI^_A"V0nl5Ku;dbݟ^jVL(Q=d5 BjJono*HhX1C&:?F_\~_"FraC5NX.B6ں{G1 +/ve6z|Ũ'$T5AE$W*8k4F 斛~Mļ_)?i Աcv9GiY1`)YJbg$CD坾1&|JP*:bH=*{Uґ@@ jl s:ff-2Ey$lQУU< 4KVjfMDa35!h2(h|-[ZTid4q0O>iЋRb--< -jJPaAqDC K:I,EifQw<"[繨C¼W:(e*7(878sn\iy.Zuɂ:Z>=(R !+7iNVĉt-в@B hDimu°Ah9UukE-{*?T4 Ehk Ģ}ֿ ) lƄazA/7s"ws c6y8â=Jh9-i 5PV5v0O>?aPfBGxgZ!xɬGF3gV]+EvKӊ>qUj|Q8a%lPLP$JgQg;^OtDjI7-{ާa@#Jg($+Jo獬9﫥}zeHO4 'bI]LI5Wd{a>uK-klha0DUk1":Ia5p9]]pO9׳ yJOVȢ퉣O|/RI:c[~r֪%O}J8gt`p$R=#i 1-d\ mox'MhV}zwXzfN9mu.AP۲gDŽj[4?nH0Y !a>\sC\4ᱛN++TխGSgO5pöoI]蠣7ZeTQ(UZ׉\3$ ΙVtn{O:Lmk}/=Q*l`RӊMY"_6Uz3t=t9m L)6QlM,Gi$V1oφ+}wfzzV,_is"N3ǐ&/,Blz'HQ∽ DžIq&M[wK2Xv"' ^Pb:)HETekvm兇ah-AS)+Ih-aNmۺA(پ!jƠ0:r\# |~Qz~OHeAJ89G+<VI\=*;!V .LfvÂ@T)xabj&靤D GMS@HtQ3llq\0c9Ib"G2nU6+۵s^~̈́E4 $9LҖbzes_1[2hT_?:hCTP7f/ttkTdCts?r_`e0 y15bXa`c$=7_&pDg%6t,!2#$P'B_4V0E9WݧE*0 |/"lEs[R2B {}/D%@IdDmE Z)P>)67RۿD-aB`lҭY%U(lNBQ֊jlkxw^s-Τ_^~'A+"]&:%e/\>: EqXIϳ9f$6{d0tL6LQ&6HLttkoȍd+RaWGrXj梄D]QDj!zQGPHeLhjr[M61͏7Yn)^o|nS1!&h`QTr˻v̻4!3Sdj Ȣ)dӨ>HȆ<-+֜"~_8þ5ݶGm!jVs(ڙ9Mn}u7}Kc> XQB _,b[4 ~D>1wDC=8]+2eDL`aTE 12&W׻kp%q%uHM<1;n{ݓ>r[gŔ-%p+$dZo#6Ffg #^7C_22seq;Un q%Kp#{%O4wGdKSm<* /Gsl  l j)4θ B1XlC|{G>nvpYrGRݟwH̵Q0 *YAm6|d[uTu4`Ʀ P 1az4]AV;VlՊd'ƴM6g@T0 /m7KDgvÕtcnN7pïsS#΁Gx9Zq]SmAXU Kms{~ɒ9P4}:-f۵&6& 3`48c%jZ-Vh@iC@JH x"u70( ? YRђG9ե/! [6,dge}Ey?~Hcg,.0:Z2Df GS"b AUoYfxzARDNH>[t4RHS*ҕpĚ v QuD7m|Enx\\O=eګho?rﱞ\R6qcWO>k4u3/'[/M߿%6QjuT9˜'Z6Yj2E{![\Y1{:&]{}Wi>!f5PKн*eJBa$”&}ҫwТEKr{19 /E.߸uk8Ⱥ(k]{r9/~kƗ˸j(S4`< K؇jiVjvq".B"VF7v|E`">W+JY1*$H}B#ZelZ C9 < T]Z4CCM`0P] DB#TgihjRA*[ `,gr=*M>&PxJ :H6Ϊk_V~@ 9t&jR"j0\3\e6CH(mU#i)/i ܲ òj:i!Z.7:Tyt4!|k1.{WsKq7q)P66TБ9(M&".()^`CVn@XWT .Xc)J0NXͺ0#gI, W'C7@GB<߬5x0"[??%'qCsrx&bw1}J篢4M+8mgȲ3J;vֱi&0# !1B1R7l)3s&_JP8 ;؃ 7cek"wy,ZN' ^{ڐiץӦmah[ۆu_c[>[vh0Lౕ;)b,m>?`a;s ]5 {f)Yf ka 2䍴@*I2d3qzS;t۫LZK{Q*IMHԇN-uğêe<ϟ}sҵ;\Lt#3B2 :@뾶cf{ .cjulAjPb ̰[V҅L7Q ԪC6J,rJ#{bjcEw{hMV O]v?߶2r98v)W6Fޜ7,Y/6xJktPj`0|t)fD'qv{NYX7yLvҹM1G&̋C%7 '|ycH!Ҷj)iyvvץWIq Y 0imOp^8j[Эx@p4sx#{1Ņȡ)V6=odk5.Lđ'w9|Nv:Qp7 Fyu᷺_xtA8N~wa\&bM=5J6Ԕ]GuzشU"2ժrJ蹢5}q_.:&&/=\xì?g[6!c4ZN7W@ xɥJ3$asjfaڴ.6s]FBE5T*i싷es!f5n~R+ɡshaϒTv]Clwg.KgՌԄp#K{|ݝG~kJ@삍D(L,92SAt 'nso^7 o^ʝr[_xFx=C\;+\>j-wž8g\r<?-?bo,9q>ᬣޢewk4ktS4SV+ J"t; `L`) sg9/\"UHY#3#sHdUyȜ: a![6 Y()}91z|[iG=j͜75mnc!bɈZ)}U)mzXM d-%ו} e! Z#\bq_XoXz^F\ٮ=J!-qpw}'fB;~G񪋭VN "G^ CaD_;v/o[o(.!:ҴwҏBE,ZYv8}z[lG-\8vk '~YSjp0`Ghe ]$h44^կ=|5q[r=hk[֍%N}6qӸʼnϨ9S6SKAQ`zTy6?,ز&?]b_ GهϟW\u!*T;²ExbD}T *eέ‰3q7DPTg^yn|MhNy6}n 7lr&vjI+gEJn5`bFЍ{Eu+ lmtNOםklǏY[:Bfȥ6x<&4iN'VhTNWճ/QsɎQ`q|q~n֕/9 _;R>U[mņC*#0K-Yi[;Xɂ-5Z*v9Mqyy]g 9|n6`Ŗ­ݻ *10oA+1Ǭdw[NAw+=OkM'w_QǽK&l:14WrwٵAuK+H$ R9dnp-?lQ+}npա5´HV΄ֶL3$bMh'mp!>Ih;٢|̀dYm=J߇LK϶Z9ptDվvjv; 2hBXOՖ^Ohb~u}!Y|8!5heG.p[6B, ACaMylB k0?~ɷlt󚑋_J6_7 G?[{h8?Of~:/]E4|[aqБyn:y3џRcς^?Kנ"|}ˆր1ig\kĠ5b@ 붽֢S.O XX8 jB}P fW.&.pBzyeۭGwn~4zǝ PF͛Z R ]!7 6(jWNe9B̺ڃ`BgzK]+Pp5(`Cٞ׈+㯽~%^JA(ŁI1-3zjX~xvnTO|$7ZgQm0,=8_( [e}]O2׶ l߸ݼuQWf1_::=Z ]z-Piٍ+/yK^"_KM| q ×mOKz@6^ Udu4\F\jLu$՘wE):#]]}>}qGdlϓA!O,\f `18HQ?X B>w=RǖzceV6 fV2 4K}1{/cmo{jg;5fXYB)G1 >_A:!&7y.V謢xGAfೞs8^̖b~&#ܧ 5=t8H`Py:eB6W`rA}_d]~Zg˾|gƲwr5XIU1P S_uţO*{ _M,_>}tn <ǜP8kמP͞q#_e˥qm?a/D"0 &(JL%jky{eovkV$OinL-Jh RIl@fɓ_gC_j+@g|힟"^|ʽ_-?`sGT~_Q"H?g"0!TDX|CG|mhxEwe?~RqYIܼ7_2_'F6)$%NU6uX_KVsgVhfM[B J~g/qh-bؿ4ϙ(ў&k\K$BU0 ϟVyzPdn*9}_ؔu_(c?"{My/e*6ωHKq] D7gױli.{ۮ|L~g7mkm Uw#xЁesٺ[792µs?Cg@q1p )X>uiWho+LVQ̐oM ZUEW_OOM/qXU?kQ LRI|չ}ӗҍuMAq MQ{!qG+"*XهKXPME_®3[Kgxꦎyqۜa^nW_ǀrs|Yd$8Re//eGT_zn+~9@ht̓wXV#K ǽ v#r{e~ NJblc>@~Ԋ{ꫝ;|ڄ֖&`I.ȚwHnYfM8[39>`^myi3:S:C .}gIHRRkDi~5 0H%=|q{SkRX);8&޾Rͽ.)6z@uG {zYJ*zx60|pLt捣:ؘ5L5`_7>h=P &5ٳ?.qyD3(5Y߬s>1OχE7|dҳދ={Ͼ_uBx!/Oɯ-}wO%-{̊9UP|=8. >q~tvv`D6F&`O23﮽iaɠN\OdԏY⬡$d)bnĘvֺ]ve5;)Ya,$}[7n'놦4Ci/~ӒN jhㄮQ qߥq.d"<-X C;N67~ⴍk16 :tu_7{/v ނS_ /M{\?r6;iddDȆC)! =J"Da=#cKt`N񧢯f_ӕ G%㵂a} F*hI{|O);(~gėLSZH)5jUxB+ٟU~?x=TLF7{x=&OLeEOmo73ޙ?x4?X4?T #WъNH`T0ttrZ$\CĠQ '{ߐceK-8rtCC P.#(KQjT0B `+4y7Ot_Ϥja>cv9Zcv9$L"PJoPU&AI&~*_&wR+&~Wv(dR1iHM% wpLZt&lTبpgafUޟguƩE0 #6 bU]SP?ȕue/@]E[ Pq~LљBJG`5̒uZ+9p("aK^zQ|)'-9nb#XgO*iQ:ձ3(){D@NqnSٕbAՂ gҰ0RhVҠ  B ƩtY` 9a 붂:^ߞn{vmgGHnW}+:,&2z 5B@?e Rͭm΂ `"%l@ F6鸾xCAid_Yq]Eu}1uvbQ)$tϒ^p!:3~R5{Q:¿n-;ts^JAgv^^?8jy)*試Jun) wFgW|]Fjolc/sаju`^`y Tt/.:X`ΐKeKMzV8s%M$K]+.; T˿x͡.M.e玓 e{1I1~^6 lH\8{ eX:i 45*>$N@=-/.hz徂!Zb-$tYGzݻˏr::1J ɰᯣ%GTzѢ.8x>°"ثm &:ϒs#{GXTrAg7 */Oq::@%I;^Hsέs't+(^ﰗ{G@μŚwwXϖwBEDl&a%+/̬NH/Ig]j2"^v n`%f_YK#P"ւ*+/u /xQ|!deZZF gol?WgVm`dA"Zhlp}mLu^U`Pj$[KhrU1I#i޽sx[0K 5j[/8 je)wv{1JM+-`n8v7ٙT~yGqlH} ŠRҠ/pA>b<]ǹr eBg2ZZfA󼑏<uhh0)!V 6Op}?MIo< rR$_zaBAQk8zz+v?x^ļrq6p铗 \*^.G vQk刼3*. @~$`)\"ЗkG.\Y?3xlw1^rImɨ!hKξx3*+.5l­ Nޟ %Cwzʣ2?Ň=e䘐舯QˬfgxqMdsylRvqz^g-jVBD ZJxނg콌P7 F>NNi|Cel纄՟+񀆱6]xenSg`oyAVI-~'cjNCWRSN~Grm7$38P e8ŘXxeS3WoNG͗mm}G21YD6@ r诱M7 ׷o/\(}^m#9?ݦ~G.ޕlM+?=F@/FBtr6;Ͽ"$uc(Ni_g;^g_|.@ +[~VsDyXs(HVt68?Z.Ntrsj_&@+}K!ȴDI֚ұc,̟xF%QO}lAW͢E=:~R7?KTGyG>[ Vi=%_Ol@mfߘk/aI9s篾3-^lyγ3ABѬX&MpxxU`( Ň7 ~HE\è&I)3xQ8_iv &UK_F|$ZrJ GG{3f9;_x)?z>ٗ(~"NwH%L{bpŦ `a:/׶~<"Ş8#< `*OZ􎶁RK""vӈgz;>͏O[[? m*ݾ.vh3_\2ֻrv;Wǩ߭fS'Gžr e3Y|ŃݹLUejVw+TaַDڍb:6y+t텖_ɇ$,w} bC|k`O$7G{ZZEnQ/'Q;N7*6۝{ݪ%u8e6r+E7m2jfrIX_3[o 댭ֶ?<09a9MO/5~eI??xƏWypR 5RĬ L(7W/>&uҫzT =ཫGqKjWLGʫlF"UGf$^>ݹ"M =r4mvl3.2΃N"IU3Yg)зl.u9;qI}yzo`q wwΝgU˻5SYWP@nvt\d.TqhQ %AKVv@Ź5/-%c-b(.Ikō&7/ʸiO|*|`E)ׄvwQ߉hk҉C({LȽWhw5A$>(T~BSkӳMwt=b|zdmn֝+b^'գ\8oqCW>NS+25ӎ?Yqsܔ\ԙf:cerĪV!'U1;ޟvƕCsiݎ6px]Ṷ<{k=lxv"QOCba ;LG| p`Z6,'6D62.6}*S=85F+wu"Rl# I̍.iE݄ďN֞xϻI/UEHsu+䯼[eZv{gLJP8K\ejkSvZq( 7,{[-/$z}^u@i%qW©U#̥>m0q6Ug34ym=;i],9>TI}FIݿzKo~}(|vvpC(bϕrsi[>& <4կpLx (?tGriLTWʺHIN%m4VQK.30X ғ1MetWF,# 6>,>,ٿ{}O$NnRLՃnX0%Z#Мba* ֤-d}zi+~Frs&F]mhUݎBʇx;Xo`IyznVdu fV4M#'D@_pۥ%X5ca04TCD(|ɯ>v&:r @{{V`^ ?~'6w}<qkqO If8k*P*=S}s,uZԪ W#g:SѰČ]7=;F m*(JEޜYtҺم0w'ǘ΋4xIJ-YUyȧF4/ ]Y-w4D  ~>=Cw}xKXCOhS6w|&gFvã^tڤAG.` HT_WZBT%U@ ƨ{ZߧlOWZ1Q'`!5wHrҮ&)ݳy;tg"A'L~w s!/:KF0I֔mԢ${jdx k/UkANʶz617 $}tT Q{g/}?Co6mWqo MFĕJ>׌DyݹL9ґWRu_PmQXtgO֛oEēL Jf.{}"UwH̝$k2 Nnۖ%sU 7M7CSt]?NGHD$ %p<&"'*W 2/q @H|ݷFPDf)b3%jĊl#:b=TdTGLh5X&֛&AZ;fIy6prlLsv%Kx"p2ĀŬItԊ9O}ݽϧSY?q"ie0{),/_wGzhqu ?qpl6Ժ侯V8YRK|fģ=57dxʐQ#+qbCCE}kKyU`<&ڂt*v1$rKw[&~f^Xr9 H*B.6 mo"5o MDm~1~wapo(3/Ս5{wF7kwI'W ݿ0ŕE]D`΃G%*P]7g=Gi9ZԽP)Jm+ܜO5bӮ:m, ݩyɽzm}W\|̚6|kJmL gT5"J f'mks{FAȯFnkY/ӶWSgلΖYOkߵY P-M" g EQž4QToE}b9M;~jѭ w72: oIk,a UϱQiYHJ(Fb*$E;﬜\g],Rҥ̝Z$rLZkijv] $i`6kžif8oeq `,r+L8uׯ ӫQZM_J bm1Cd1 AdD I,@]j?j7ڗovKBy贃zfS=tC! 3euvc.3̋IĢLbUYVQڌǠ@#iϮ7%wV74+tawpT٢<7MDs17uIԟ:oXy+N+B}+%'64|gawv6<مO+=5.Gwc[ã)(GjOko~[ݥz夝WO;ZWs)" 2@WVi z?r½wdC oI=˩+wϿ2Kҳs"\Br5!jXMK1! *j  DH H&r @1^>^]&Uq~oHvFb֊%A Ϯi̮\1~XxθPnlF93`t * -,=#kw5͛qrAH+!e^97nOb;@(10'?u,<' /9CaF\y(~v'\|I@/Fv~oɮ:uAIjd ;>.L_N7:4;&6K5|UA+#`Hoq<_>J͎P=ꯋKުۦb=`*je-Ò1q1g<{rXfux }[JSh< pJ>a6>ts~ԸM]?Y.+,0J8i%Mbѳ _EW^xgL@wzf9򸭹CB1M&e,NJCqNS_qZO/vx1؜JVp#Pޓ7c-mnMCLjwd Qʏ/$}*\A𵿶dN _LҐGפ/_a7gtD얦fA3[vżm9īLO:mj C;DZOg@7~w{:&+~o\WhFV~4OqyA03V|k$%Ѿӗ(^yfOY,x(>y9|CMiJ_gwmTF (QU%c^(RnZ&]HBwGxap&[d0 Q $ H(pH^o{#92"`A}d-X_F2l{d|{[zy+d I'=WA?]\:>/V׽y6+GԕK84$;ڟ1 TGLJUssJʕ;׽K++.?zX.lfmf=u νNAjR?5]^^)?'k&=\.^:*znL!E94,J!H#:|uU*uӨiP[bAtv](p ph 6.'yolSMcLb{۷!-2%*";voOAǧd|H2`d}iqEmё:3nM C8lBFěMۂ QE1璶aϹ=)˶ΗB$Z%L%TIX2bЈ( X D ExBG>Pْ盱:0VTIȈ20HI/5ew 8&ީ}@s1}/"(mYߢf3)(.j+}Z]=fr+uC F[M$F4" ̣&&$T;?` = "N*IO<Ӟt@z2Y[+Rf5i?Ih 1Oii@NsZ PDMOo >Vh@XJc5 ?W3 ꆔut9E]ETĚKKVwKs ^O:<7M7kNja@fB܄XLw̾yW/?nXss)Vqr1J:[9SJ &Ft`4X]k#(wtV ֡%Y@^K# "k_omM'}sx,5CvB.usɦ Og./S]Beѳ֣8g^ޯg^qI=;)^H83cXjYmFSU(!Z^ BHw7y_~W1Πyme;U_&`ܟtqSW||Ãj`CD pRZ5+_y<;k}otXȋ;L^r7 gq[2J&:keʑ˴8I kդ\b7իoe<dQD?C)ƈfj G@8Vfթؼq;OͿX6p;OABm*͛p,](@[Ӡ{mIl@X\ZBTA bp܇Uo|i OѪE&uO,+>̐_}'^zHzփ;s^ wq:d1 {p6;YSMjĉWA֠DRi&ML04mJswrS sEzL$I2P0@ ^NYC̉3{]c@ Rfm@l &~N˙WQIQ e#b%,.?e@JJADvgn= a&D 'K#gChLF4wsGIU(3~m0JGkLy9a>C0k SUJᓑqF'oI[h=" ~e^Y CK{躕 y!^|$6nM.p H5d:,J2 |UbpԽ)w[M-ZjgzبPx9;0%u01_1)Դ4 sax{:M{DžnQjRh R `"o^h6&2gH£o^h&]I4e\ [~nblP\TRsV KL?9(^f?ݡmJiJ:SAG B*&ݧB5Q%3R㕎-w[mhubuTa\+ !"QtwuI+r\s{_goBRqךj3008%.ALZQ0/V! (iw;HGV)V`GґGkEQ&J (qI܅G"0*Jw|^ѥGeɂ Be}UYV%"Q]ƐtG$r=ʱ><-?\߉hDLdX[/*YDGJLBbY(̀RYMMQCV;p *ϑf1oj : )8xr6|d҇q~ޘDT Oj7 gCO/Dkmcx: 4n%PAy&'s{!fNc+j2kT""rQ "]|b 0[a1<6j'Q`ψ;|ce &J"`+߉jvvB1I7 ǧo5æxfX@ =QE6q,0 1^_E+  D4T /J ! (`Q%v@@&S?}+ $^E.ҰgN2fqXn\vb<@0xb5MuCeKqEMR!CWYl4h|_q \]Ed0r']um @U'n~˯2\|gI.25LĐ[Tqh-<qcqJ8r.Ƹz#M Sd[ș(OT>V67Mӟtw[E[ΊVlf0`'Q 3Rfe/]z./IL9O"d`iZDM/q=o)ҔlQPmfܢ)]ZѰ~n\ŧT0oO=R&P(Iax $L|ϡ"LQ0E]wK_ͤl7~^l,hNb`6$"`e,ʎ2A(aRHU_B3jZg&+ ;!!#M>]UQ֙sN +aq``ő5Y203%`c;lYk8wd;'0 A49Hr|5 Jn˄W/Ws_w6o܇,=xq8`S4cPm=pe[c EsSSַY>7M&ov)q}|kq\g µwA" Rc9Ip)5a/M 0V)BA "u͜t6l7ycmL=x* OD=mC@̠s`Dž-ÑV}ɻI<p}?Oj)%aT_~uE"(SkqWL9dMߓu}uq6m;m7CG'J̪2Zl <"V *4VV5.$o77sd;# `IzRe >Q!t쯴2` c> wLjͼX F :RUtH; E $FD4UBE#)ϰUg\s %{f*Մ`+X\ c\%̴OqpY*RI+VIj"rlA|*[Yc&!sû/IE7AH/M@H^M zs_mg2;*Tj8e8!޿(GG~燁QZK Z4YC?¿8 pXg4UQ!U(\Ëd6nOWF\_Ew)ٖdMn">.=ݑ  P)æmuO;G 3xSCe9߿{=GZ94Y`*~k[K=uTH|_0q(qiJaB\1 @y)weKP*az MPQ4K!WV!8ҿ}1hx4 Xئx>/NR& /~/Z/쎝y*8jv\ -)c11Jbm @,l}V)&I c1ZC}vnnc_2LJnI5.%z <;Ӳm UdRGE:ǕfeUJYKixX2.*8fa@JgT}՟G kv K`CR'B`D!b բDe,6NY걳^wboMviEF+9x(!As *$,ً~LɷmMn同PvX&7AeQ]^͍]0Nm!nKO~쯇:3N [21˃^B+@0\]g f6 z$US۷üvjq@H-q/ + %pA>@t [I+ H[̩`i/ً\Le`_%R28 +xQӬ'T~zev)c=,5?D),FjR@/We, iǓLQcFFiGX, sJcF[B+ 6p&XvDc1t'Y5ӆ7*< IF0ʀ Aˀx"%5 $B|/"3jd AE+8`X"@PB)1 )"H"YQą۫#Vɩî-RaD:Q:k7W.nζC0@ laWF4CHdr<Цfhl,OZYo=aR0ZRKax9 7o#B2Nr0#t}Zw#@mNt)c'mon*:ub#@j0d8ߧ4=% ts@f2׺.s#iw*V#NG5'@F;ԍ L };raCUDހ.YDhIW>[Ao QLڬb]E(W@+>j*|%?Vɟk4uZK =x~8hNj'T D|>1aMC){֤&>ЍHQI(k)ɫ03hMyO@L[ɽ~)tܫ@}䪌o~M?Y+ 1TUV0BA 0\H"?^5(![]7ϬbfGFp @C>0hZ 1ڥ0! ۫A}Lm]Q]eoއtLM'f𱽤=5R{d;JrT)a1eaLHH"z&4'.QMgg_ :PX^=PN$fB$L, n!f;K^b?wwJn DL*VLR.zʵGKhlJwfszgB\Ҫ39z6jLo;\jI, 1l14/$%%0"f@%0`-y@a5H@4ywsU|U'`'448WWJB=ǠhoƐb&=_sB> ɚ?h'w$h#^Gϵi6m ( =YaY]WIXBN]W[@~K?'h3,~t{d2nGtI8}_?mWhpY )%a ?w^k[R$ Lu՞f| $E8L#@IJDx(JR%!҅]BQ(HIEt$p Z5̜kSNJ{<=M=/` Vz]Q6bysmk/=Kv=I;{a`) DZl6 䘕sG ӷ)rMʬ>nT " g DŽoûܽB(6>.(C%7p]Jً3W.% 3Ļ?Tk85`1PWtl0oD`EqdA!|MlY>}~"<-w>] l×]~̻z  &Qh5˵S (oq$3x>zQ|Vo9H,dlQCY QشD1Uo9)Tip1z[r_Vꏢ* -La $TQ:8L-*h̚]7gm7! V]v+%`,XFURa&9#;HY'cSIs*p"Ԗ3da!D" Y0h-ƶXR3\O}&~ D=$i(G5#`YlYHDHiIs%^ Z[F:~-۟d\~9v6G֪pБEyVNy4YA\5V!9b6" Y]?FG8PsGPӆrelL$Df-@+`$(w7[v,sۡ ޟy RPu,{"aGNeznx՗#IJ1zF'N~/ cٟ[~hu#t!ޫҏ^MD9U)Ts|m>?@Mk(00AjDPuԃMt'_<?x<||zWhX]j/V%&]O0w` tcRa\hE0zx[>xԁ3 `PIHkz A71yJ}! 3)Z!k ',GuO=3naZux!!*U[j$Z_\ _4DP`U-}4& xT/3 u֔f撜IV G}#zy1Ҷr܋NknI 8Y7o*BDj4ȵTZ%kX yZ2ʣ"ol ,COVhpH,M>l H#n\42.fZ N418qxM𚲡-nmjudW(s0Y:$-~"BFTKU2!3:w>O#OnD@"N3tV;3U?$sן8rv66KOY `QJ(+Žp"D4 %$.y+{ۓ+La85XCPț)B#1M>t{_=f}BْiWu/LKƳ׃(a)PkP+,L~;: b9Mڻ}4/5ȳ\[7܁]S'F N%"VY9umvT7_?ciֳe_ܫ˓MF\ŒK0ʅ@I"c5Kɶ:8=']?U3dYiTIdl߸^ifV œ5N u6&G_]o  $8(С% ) a.Y׼)IQ |:4q"+Iw{uјUhKwVD#%%%c3R=<Qm/wso]. ^̝ kc'B(0 9$Xx v ڗQR  p #Ԑ%A孬8u0%5;8 13glJN fƩK\J̣auJݘ<+(C,^Q(: 6Br9 }gm[Ok1KXa#0b jkMy ~k`덽ӏ7^w\oVM̵'h& <2*9c,uD'&uW$Ŏ3  A̋K{@*)k )Ju.d^6%:Jd <^H ?^qiMOP I@u&m@Eev̧,ziM{V~ى(?yW}7/[y4+b z`<I( TE4_Cwt|&܏ҝ:%ywǹ%#:2Va0naX@* ={姲7VH1T1ؼ+B0($ 'jS$r| Ș +}= .<6ް`ї!1,ALeؓZJ3uFY8R1W'M+2h]-"ZXTuN0)bSxaH5+?|nD'ec8_p5+K{\`P EbeHM o5!q6n2f7ŢOcsD=<8?w1\Eu^?E 1]@k 㭝^.{8%$DeBXKr5f\K+KMriBLUk "01~ hȑ)>RPVlR>.mjr>@;ye+Hc(&B\j0MĹۜJ_ay!J+e[ xI hʂi0S>5QDgJ'%*y²͗A$RRX$RZgrx.h#U5(v< ur7MH4` ^QQ 1QࠀIH6v?XSwYikl/nf(ڪ8V' EYŇԂ}r̭F:暏y<}?ZaږJ(OW4!D|? fnGX`n+'t[$(kCrMw=,Y6~+g285 FZkPxE<>,V:F5I!д濗]MS{' ꢒ׺'uVq'1v KY-<#: 4sYd%G=^kr0%+v8& M 9%=E4+OfNt+scİ0)(**Dy P܄1STedSH>g #mC:] U   Jc@MCV_":8rBdBpZ ZK+Jށ a*H%"* (%HFwl9pU\B?\;xd6SYVWrPN֩Ue"W:g=$u_:𐱷UP#W6UjcB^9t+omYf&M*bDi t:G.3F&#|␉qN $]*, JZ3ɿtqaH y]|/b HSJȋ8lSN*:C}.bȮ7%,$$ (14NHnB[qU<^Ua7BN0P[m9{ŧʍ+xri\:q5nuBj%ύ_x4`{f>)qgj= pǷ?zE޶| ƐhlGBF.& V8/˧cwFhL^u_jؙp@I8+8їyz`HgaGm4C B'=kڙ6.C˗^疉&, -7ܐd k*3DԒy\I{lD[8  ,e!^;EjڮHI s);Cl$7Rr_Y+{H.EI4sf5g[ ՖBӿrn!+N # Ui70nңʷ5W}߾CǕZezիFu߲߮B`'P{hJC hDj0! J^$ v(l5 cg _.=*UC[@6mB1Z#L }%xW 7(ZAANf(vj ]y͇SBI&N %i]a/c wۡ d"#n>,-l7%^O?q\L_,|[Hlwm2N9Se i" 6j6=RAJ2PUB8+fCJE YprpQnIUz>jW'+ BnkL-}wy?|m]m;;b"RZxW֤o'k&| |ԹXL5oҠ'YFBy LJ 0x֧Ϟx@-.D:UDȦ:;6ց}*`&l;9d&BVIItRe7^9&@nhKB%**0)]C(( _R.Ƥw͞!T[bBI niJVBf+Ya50*PC(ra6Vu˒TI3GZTFG"B Ds[jLrOE'hFzb,.H5]Y&DG)!Ȩ*82'cQ cpfLQ[glr IQP't@5XG֊F (#PU~WE0ڏ~g)j9b[!hc 0@Mō/Fv3%]DAisUsB t (B+ˋp栕oj%P AiTW#6p34of,_8CP)KWFj$Lv?,7u!,="L2 Є}x 9m@S7gIo2vpfl:#@Nj%h{a 6MacD9GdyV>#J<;tSÏ]B`:HtǂX˄cܧ.ؐ !ӁH "m =oN|CL>nfX"* -M6&CkxmّG㝢[J5Xh0U?Ca"c7L$2{[93ꮟ~6'Iuq5e;6m(C+Lʫnn@!X.iб')4O_7iO;O9Eg{>Վw8y(Bϥ}O W $`*͵; E449d kRFZІ#hQi*)aX&-7kޔwNt|y2>̧Vd kL]htO>! =@Ӯx]{ar bgQD? htK^1J]D!S;(L*) AB4K2 TQXJ0Aк"+']> xɔ\W3t@fZ ,N[:#n,);l#pdpxۜ@. CnD.x[HGʔfI0BCܸ8T+zEq g#wa"\P MkWͻZqpe4EG5c-"t1 VhȻ#v]Kny%VbPky>U'M*$#|=a^8@(o4)yP$@_+" ׬uR8b {o,cX7ߙpg̓}BGcc>OsiZ€E=nG5~S fmJ[8-K|CԫsN]0#d'>2?>KYfBӑcj&S&w?Pwx`OfskU 8Й ,&8.!l-س;ڵb;\ qjA.AB=A_z  pNOs4M?Z4T\"JrrF|GFq ν(xq{YH#f]UvV*9Z AT#Y( ǵ@HjOC-n9m& Ey.{9:U_Z-उb(cXQm8}_H#QԈ7"[~(&9ߧN$5?@pj`VP}К5 "q`\$ňq%?lj 0Q<~Jw!@X8 7ͤV᪷MU*cn8hy9jG pWW$@|\AJˣ8x-€4H)J8c,en |`Q-NӦi0ƳtK8kCΈlyY}38$ pn~{p{aƏ˿S:AP~㷎-9tf^~z*fmBĉ,zi/})?{.J TnWrǺU8L`Tc<\9Fj<&`Jy 9[6}ϧW8>s/'g<m݃yp50ŸzJ`޻\*Ol4:gcͨZׇpz?d7A^ A .$JKFUv+5UcY U韲+gpL:S;4z'Cr 4Wx\bK6'=< ݢT(6*bJ  Q"};;9w=sNM,Zg i  94RzCLZ S23<8l;u^NWH` rT2='@ C \Cӄ夊OA`5$w}v;(|&0b  TbAF%qAѻKpˆ3)w" t gp%NXH(;~`]q3́4N=&[wj!435 T`.[;}% W|v?n @ mgvV5%V d㢓i1S.B%5K 'PC6xY/,`h 痷nl Yb"e{sm*MGz\+vՍMQ+IF/Q dbUČ'N -iPktFaiI?FI$y-$誮%w*wruSLbOniI#g} NaJ$ F╔"1dۯnTmcD壪Hl2f^ I\x(f"P ):0C )b@d1h [UstcP bc90:*gɢ-iyU^ձ- $ [Z&O|}q8,1+prdM+u-?7ܨ(^`Z (/t2`AUYjV܈4IsZn䒡<=/m2@1( p<@e#`pi4gBj')ꋅ4*Jw̆~ih@# dg?՞es'j7o"LH0n{'CYO\aAEPXb0hc)G L2,FW >$t"F .ALg>}V@ZWߪ=<@XXQP,Gn.. $c%33g+A#. Xv,wzr$r EfW~Dr P]0ŗ\}" D+TFq?m ig3 "B`5)!DX7&aQD"-#qVf V82 .E8 (!!7L,a! H#+T>fu@ܹS8ۑ'TO嗢d̀MŽnitjWL3ܼƶ0妖.C䴢נüe /ZuT|XMG:oSj%c!veMzvE67Ia &_汷N߮pC^ʼn~S|'H" 3J4Z'#)UȡbD d Y_hm]K"NT{:>XmDJJ3W >pJY+K{2K ;;t R6s=gzðެ"HBȨąt7I,(h#;(L{'nu*\3dk\ &(\&('eH-@ H ] vi-gj+g~&p =Xe(`LKk"Eq mFzpZGejAvڀSHsޫV\EWC,˦ }c^¹lrb0]]{ )9#7@&@ V:Kc15y} o}wR0nm Y!@fbaq,I pXi24&t tE%H,7b%s!XD0As0l 0L?'H(ҩ\TάO +]:^.B(&9mqFW,UjZ˕|Y -RǮȻ[J*HmѽKQ`YT YplWl*~@MȗINJ3#TNk0Um\(;eZOsv+=q(c@baR !0QQD=P/ wh}bds M\z iˇTl6DMd*L&@mUJ[WJE܃/(Bi;aƨ1((ʂM Y6x"0r\%# Tx+!9&6ޑȷ v8Q}i+hi|Oưe TJЧ5\cL8v}A tڭQ?bAJGׇ Fb rPwؘb37˚ kd</bpf]fXHp2!"F-, ̀njP#@-$WP_L† ;}IBn.a%?u@PkDQB3[isvG4xz̤.|U?˷I1|@= ӜAd_ksQE*4.x;2QBZHXgV^Pm@|첋U,!Gm'2#AђTy ,P7I #EL^ogdOB,, ],j)*4A#UZ6S+KzY^7 ͈t[N#BhOGt#Kt>3aHk$/UHjIӂ`)&NA+gf^9ߣ\_wΖp"8`;.WCYcf@B\z=QМq(@[Yk=Ŭ{=rB9OK-ZB LI \,#Ɣ%kgA3yF\g}I*pZ PMI*)Rt Q[rB>6mr4C9z0M~#.?.=40Y`*LB;gJ KObܓUjw_._;Hr!QN @1!OBT˥%QV0WlF.} Ȝ~ѻx`df/"Sr!Gv4(|+# ?gj;|꽳R B^{k{nVMXвal~K֠2šL_/1ې7J$j銾C[ nx0m;kÃwF_uuL|nUQƚ|,fg؝QokJ-Gtׇ3&T/V^\ᠨX$ד5<hX0kSOs >y s#$C煹!2tRMr:Eq9B_s-VJe42~ 3Y?e/,z36"Us >^ [*ot[ngX Tz@JF,@|j(\ ѯǹ9s<|3>s>t>ђet5ņNPPfiB* :9E0;c#8%XOo+%Ձ`tTj%Q"`<3 A ՁIKrao'R  eRVR}8շ9ARHo\Uo4=1 T1C R PP"/Z W}0uWrp ;Li-?Z$=3 $? zqz^\ت4y0ܒwr] Ao!|xx0!3ce<G'!Bx8VyZ)$HTh}x7IOj^ MWXT^XA͝22⛹ }vp4s78 9c]ҷ=ۼRJ-| 2kW{Rvn9(~Nɉ}zx G]4Ms vHmgv\X c( R6 HH`E"0d 5!`V0X#Fk{Q0x]uMHZ:AZ0rrҭ4# khSg X=A ?,*Ȫ0 %'[Մl_<ē.'ڈ!E{Ќ*q}[qK٫S/*U|aSэMPT 8#zWY‡ 2N ЀI 0A1ƀ%A^ $Cdel@&b1z2m)88.m: !7``0hl>@4 00#% `S!]Ac-/G:I\dPmBm (s`|&D+R;6 dB+=wI?{[4o j "9kZhPŘJ=ϒI ?9uU9dzؚzc@"? u-W5enqzNaDz0iœn8)[/ld0$!g [R9y J#v01 P.٤7R@Z/GG̳ aZoV,ޖ^Ahvmt kjd2G5D{;?T k*VHh`a }eF_pHF]H*fƨ(/l 4g9Bߒr-Lإ"Z2ڪf3%FK,˦5cd1zBdhEN I=kxs֭KBfYl*4*fKo] 19Չh2Ɨ ̕$Bi^_ Nj)hH $VWLZIk 56EGPu&L RQ Ct}+m[Th.>P M`B43iT"! KglVUTmp,EȁvLIaBtEU؈g|obD;YA٥Y*!%\cHQ*tx5m$BS&G( U 8ƀnOWDwS13~h~=wҦ[1m.õI@cv5CQLi#~ 1$/v7Mc Ȩ Kń*bbdG5x;R .Ip6gt;d6^ EPV5%I0I v/\Sݶ O9&lӀNFyMAjTIU4">!۽?Z?% BwV#.Vܶ%D,i5Ĩu3gj^ˁ9d/0:fvDr P @I#A9 ioy鰃w?H&~ E-lXrxk֝: P9Ɉ֔D"M٥%{,ޛd/(/pkW<^:K%x1usڄ[ߐu<d 0s98c<gk%ϒի߯fℰb*݅WՏIy5!3,mNq c)NдB2C92U KZу5Lw;JT;`9%m)ꨌU N#'X$J^ꥄ?\ۢî!kLjj'qA 8n<>O+R;m2f猪(X12 է)0qMd%TqL}QYrc))cc[%Na!6D! A&@/5Xi )U/'8Vi$DtR(H{eؐihaH Vr9 pQm+MYo+X y$I/L8 Jhh.'XbCP&p 0À  g=YO32&z7+_e3jޏmތƋ pnWQ/KTɀ+ eqݼ˚tW$5zV ,?"Ya ȸo5;9Xsu`!Uq2$ϦM*pYPԜGfҶ!S4 c-̎ȠD[CՉ{ԛ>.Wkk o8ga#zGjljbgb=Q٨⎇ ]~cT3bUc" *p~yd;@@TU$|KT6E;mWV.hI.S+zh*j i-!=;@X`'آ# ?GPL*oN3n ?Vp(/0j ]+X`#?9ߛm8"}` !#R$= 3r ,=c8wttH="Kz/oV"Q8UĬBCFHle2 DH?uql}LRR`9M`^7{.ZntsHy{r-$Fz6;[sMqeij`"  H*F` nTF \ҝꎿ7P*'cM9"{Yqn8 T`_  'P)s%<8k5naӏSp6]I(J  [f(lhc'B,\(7vS Q~`&GGRBHvyնTW3"6Z6ljŊKAk 䕯91O lB(34b nbjYiFzI3d}C0\7fYJZA@, mG$-%Q!(+#JB;lbR@tA<4CXMm鍡s)Xcb+4"[0t$h ntSo޺D8Udxy_T1:gl[6L Mpk.ޫw'w,'Tv-n $>-| ,D@aW9B j#m NY6#&cH#o:G4[ѢH@ ĝaWұw5P/ `mT7H/$p8OBHOB0oPSB,y7U7k`ʯ^TS)|i&1hP@ XjNVD@BٺoM;O{Ǝ2^Y3, RBLDJ],[lHkH*,Zd/o~ᴌlh3:smXx!cAg%<?m7\D6FLIg ~>NP1^WYA^:bA_Ӳ~]dṅ {^,_j{w"\ԃb2@d4[` LK֔\ԥ誴 =J,=Dlo;yk_1m^Zd)H0)⻰ 0AAǣ~ Vg9_y!0(3[q ^(1k)qqKi,f AEbڇ;6_K`Y9%Ȋ2BJ(U@*  $,D`cRHWțgz udՌV@5P̄6RE eI/ d 0mvwq2+6~ Ө.c>Se3Cji D* &{LDb@,`K 1H^ txD"WҵG;-m Rz:Vxľ"Lxx QzI$Z`Nݍr>x2JPp8|փ.x̲\k*BAP|f٭XEB :hH$ k$ uH9p; a*2a`E-u19Tj'FK27 Z5DL5d~)Z޿ ?_|/τV=<H uy!?cĺAaEdM/fkofv]+aД`ղ*smFW:כ\զꗽż7|E"68p Yc_?L~ߦ!Wo~ߛrHɟ&TD'WGNBC9ӷor?{C"xm~"3yOu |5~}YjOECH )B;>H 2@ L.@ 3EލaM,`PjP *QTj 8U]D Bfzl=,颌M=L[J"HB (A `'q &&JFJua\ȉk0ss5먚ؒ`ł"(\NZ0r)rB+;&c+P ׊ @Uv*!+F U! yE8c_=Խ4RG!$NS' Kݓ9ƾ,VUDFwm?/Y%647pV:pP Hu!Uύv{?!SWLq`ly^F|:ʿVv1{z}Kv)fMż5v7?OpWҭN!jt ’'ރZ63+oN@&u7WYq,i0$/DU@"t!gwmX~qu{1YQk`q\B^? !{HWYu6c#hY#n@[1>^ش3Fwad8Ou&/*\"#h5t ͠vʠ|@0.Bt(R0(>vv}&dP@(4'AuL~Z,b!9'7o4B/ׄuZ>o Gvs+TxC€0T)vJzZԮzHl2BH !y4@H@R0@  2%.ʍ=VYL.?sv2n^")΋E0yPXf*ƍ/#;m3t0{ŝx{|^;I.H;DH5 8G`fdaE \h[$r]!)]Eb ھNE3J. `hBTTm俧{tOE-Fâ8u"fR?&fC\$X$[N1~w><@uЬPVb?'B R@X 8+૎/čiii`6C q2 &- h5,3 [ ,8h;Ͳ`gs4L Ngho)Pv+]ϝ屬/n4n-ˌ%S) ܴk{HBAO/z:rX7o&|7"+::yE+?+5S*0.~`ë}"\^WB?+V֭Ͳ|V/w.g}@do&?+?$v㢕l503TY{Sfk7 pt@h,s5*4.9lIHC~ľqg7z:|1B`0*/}3S?vkH\e:@Ap†*rEޙ9>7IBLT?Ɇ *HJxp-(EaLQ%*JWų BNk/XfŴX*Lx/)@H|DGDitd:%bU2IP$7Rn  5,3@`8 j`c@B&6&w[:(3a<Ͷ /, @W2`kZeݧ46]Wm'ajx9G"|>JEs\tC|%{ Zc(hFA9H UpUj4܊>`Ai[YN6gkCǤŮFC)֭Llϩ]sal'rvE/ݐ 4Mt~-$:tC65[|EϪqM; ѶF^M`UUȫ JDK:}ճ)e$jja/Ot>yDͷ:$IKJ yqW2T J\~ݤȓBrTw|z* ^Y$J :Q:`j]HC^ \f{tX0\aqi#-AaAFNc02A~bvhU*5oC?JvRkyTFT"e& n^e"WeiW.nW\!T/*@`}92Zb >fX,StB S0H *?U0Y~)5ɱ5&bܝ6 + 3 kG9g;BIGM,;r&S 8t$E0H,IA bĄD@1́"'ِ_ۍC]tFzvul4t52J՚MqznPyr|:k+0Gf,!-tk(RoDg HU g=tC-!>nz7;nj|h;@9E`0 R -N^ Gvy Wg_7U>Xj.FIIȞDA* EtDLW0Tjjrweσw{Oɣla:TM@`19#x/ТD6^p8 L, C[3ur(r_<Ԩe q^|L3n&)n5o@BcCE=y[Rz[)ךdd"G@Pv51xb$HD 7zH+46z;j>3 :(tW ,;t ʖYea"˞*Zkoy ?fͷAZ4Ta?hemL<((RXΡlI>^U&#R`P,h4oAޠrlo^ x{_G)x=Nw/>Uwadj8T3iEBlX =  -Z*:֒up6}>A'fѢ}8? Y_OOG@^iV͓#`xߢhot+~7* zh'}C޶gdSB g4k9mX)xjw2yԙO]=l3$5y6 ʈM_J! <͂0lWOR,pW =^bk0iZ\_Mf|<g{`Xjt3<3-uK$(vqx6$X&k @@ r73L\(WbՑ71 S2xMgk{ˏL(>$ɳm6[QwSԴ 0t]|=BK>+$ 7iApM=Ed(hSmtn?{ g~>{ȷy Ww36ivԽ#B`?(Ҫ}!5(31B젪}Kj =i]=-ɁlYJ\a4%"! @΂BSN*(S@dj%[3jxGF@`RJ^(!k(WV 0(@^S#5DBHV(C  rCȵԵkr`<  B1FhCR">%E$Y¤xF R4.b&-WBm|UhSȓ+4ZD ==ră4e!Z똔.dPڔ7Att YsdVˍhIGJZ}n0sَIx&oO cgz,i@H l!laG_f ꜩˇɥF񜓓=w/a  Z6 2F ~-lS 6Zx3)X=|D0$W ?{d6 <WwR޵Vro⎅ i@4.$ݝq04-!*ӓ RS8zr4%.BMPa h62*rV5GBh"tùp` u5ad]ik~'dVszo4QhEbUTGޯA.9%O,ntdhcL򀫝P^a,I $%R̷Q˳LrR/N%FUx+G"Q+ZHJBU<トd]C)[*γ>S :b V3`"HB"@%ՄRxŚdԋfo̧׬ΣY%X" a!kk. ׮!R(@[EQiB$* D /kA0$AmKHz&=7f?^Ot\ הmcY)MJPqy9'"Ynއ5Os۲^>pyBR߮]zC ~)V>Ll2ZɧUq;6 AvKM3e 2vv,[G&R'#7*JG}9qdwr3ܷ^1I_lqyN"IQK>f7N,GLrjD5u8n ˆũ9F}h*"ai,Q,)xt *^տ>z..$(?vüEf_tIZO*$T^\lbH_-㹩/S_llNvjuen}<&&z0_}5-eݹ<пfD|© 㮧݌q^ U_ָñ\験RM&C~WAw9mWE97Ru9wӓ*kuDۚ c -:Vܲw-%B5K2*a YiM9m]7x jp|TV+T8W'iv2=/O9Lĉ|oQ\?B:bC0Nڝ*&-"Y2Ejz*) "P^-qGSXGYIn/ cb9aM]ޮVE [ZaҲzM>{:ߥ?NUgwHVnBkzZ׭>p8hc?{((v)b!hg4D:#Tb퀊 }wpsK:U<5` PQ>+:|E5f1"w/o~wg8{RQDR$,`/|ҹ(=Yi :ز}-LNmEifM6>]Y[i[bWBb{dQ~}{Zefϒ#$Խ~ߛHK}7.j|[I9թ%FL .& ӌM)!960n?À_ߍ# %T+lF;_)۹dlwW#h)i޵-7a z^rF]6Y-TNX Y>xJ;.ҞuCI{8ֿ K14cz)G+ @+/q7hMisu*!Ii!#(ϫܔ0A~x eUDSJ9lG<ی*5ia6 iR8"nP9M"~?oٯk;=M`^?=0z^R{ HDHb Ϟ)?6P LpCi^4lE[{L(Ow߾b*vE+=q &.M$8KY5[hCwVnWf3I1l t+")1(@Q*aR1:oH)ԾNkVyD̓ ߷WkEes8"!LdĒ; OdGc FC)# [*#}} ^RlޟM,ǨHCUnFA J ]b%7~fݼh?5#A=r8A5ǺqM7u?}ڃfNh9jq@d)C 2 `LQb=RY* C^EkOeϭJ 7>F?cKIʱHdDz&~gy}NIA8ӓ._Q C  b 5\M5@ (3*(JuB=m5T͍@EРIK/ NmDF & O"  `0YYx ?SMu)]gӴ._.ܙry?1R1yjᎣrrw4P[2Lq+Mr~<]8W=w6X\0v~|؎ 3LdkZӮ)y EM, ?%ބV&Mt* >r)D rD;O9 N%/.:iINBz^rݱd.sؤ;&B[ɝdh5mP It)-^NtƤQ{Vjiir}B殿=ds3D17<#k+ז.NhC]= Moח X:~ь=/'{a4#I[ӽp^i'"|uCWOYA3W 6T#K㰀301]UP";filIQ'RDɯCBR)HB\mo^qp }o}f$؆ ߷>*;RlP75$4{Q`ʟ+un1S\kN;o~sZxAE&=6կ&aS,lX|P˸v&`z:k@}9X"[i~oMu{vfk=P۳Cv~ljȥSH4㱤 Rm :u}?~$ۀ5ἱ+I0oe=t |*@3^ҷ<(Oј*HW{ jPca"P4J_᭚>\gOJ4NpuW܏dPmr2bJ)v}DȶN3*GvcLrѐ-J$yq;ߜ?{C˩L}~<܈8КNݍ:9TM>$ =U6a}y1('s?+GbǏrK*3O>㯶OwH*Ŀv7VIcqj²p O2)NF6'ŸFho0ՄSI2oC'3hⳁq@MQE  4#cE$Qw|M]d5'x_p_7R\ aUGM"F됪%0/L>',:c݇.GtbXq6 xfI&9 O1Rc:- )d!쏵k>SpHRK '٪*Mަν|\ 5WvS a?8"͖tAcl):4-gR"C޵.?XJ`< {*`qSu<<40Zl*qw-^GAs D-')2=~z|g[>Vߓs(*6ȄY*6E[!]<GD$:5F9G{;1ЈItIrsPdi ?h{o=ֻR٩AfNy=Ԕ@ŹT*GX{dUAd߱y5;<'>Nɯ~czI3Qtf/>#VɪOXݮTZ\ؒ)aZkpL7*Q:Iʒ&(fJr٩Ԅ~sDUBxqW⤙4}s]L}͑8$ste]o~LU59}e@ܹ@x6iE C@1&C%tƄ0J {~!GO1䇹ڌ5œs"H$Ie5&S#oL:\Xv:@ ius+tfObֺ]Ylk ={Ńe=鮳jSr{JK ?(Ԥgku&48:]kwigl)J8eMx{+Wl[3;ańL^z j e`]:ČppX;~fۖ9W:L] IQ t&N L m{iҦ2=:&÷`8B+=ʾ1q[[0:W;tm~U?~rzcpGBo OgwfPY#A[ݴINe0;WOx Ӡn~O7~Hvq@ީ@'73/=%r}r'K&c~^wg[4VĀMo˛ŝ^ `wWLmٞ*i)Qlp?z[9T ܇P6z`OF;i "u Xفwɇ_rr޵v/=U|]hIG#4IV$}ӷߺωo{HuOͩ\\'qBxa6R +)<5V9_t'ů9~~hon%!g>0+j2w.ſߞ+["࡙A 0 $K8ADm.T:`>.O4339HAd$}~^i*$! D%*!ڻ_Lۤ7xVݴ\U?冗u397q cd@ޣ/k񊮧]tB6bd"iqsK '9kBNDV=D9ኤþɯ;@Mɱ~=՜g uI2^CV`gPS&e8^fL>eұax`m*0(n.;]ox})* ɼߋj*:.O{nDخ)?ϣުtn<0ΫCq}S8k0ʻUn_K֕ȫ><)qc Ӫ`4ynLpEIq<@ V1H}& BhN:ryn^(neA׌(CHĂR2İ&EmoⲠf[kx1Uu/nf&A9hGkcicٽzĭ_m)t3z[pc\7DLf̔R6'B7?: l.Se: y$0a4# ` jPc)lug[$E> [Bjp]\{:IC|3}36f/))hL@9Ȯ.˼[?OOTg<D,#o_z=ir_ 7 $hm;q믯;zoȝ2r?`PڼCTMeܭWl-C gБA⋖*((^,sS]xkyBNӍa ^cVB1R4Mv#M-u!- {Id> a!1^pL 8pߟ Z8cB JI> A(b<Ȋ2 Qb_p\+i`N .솩P]>ߑk0Z v/9w3WJ3zxU~w'rG2"'(/E-;I S-C- 孂&Q0.An]{u&o/3kjٍ[2=>{}tϛuحvzC'My25}ohiE0&# g]=|J$[^_|@: N+[y&i&H2s͛u%in7NGmBQuq1Ĭ)x[HQ\늚Ғ)E-=a.?"{Ю-KC.Q!UnW=w%6S?4Ht+S={]ܕ9qX,eyxޙM2uZ_Tc5 A A*ĀP̞5~?=mUNdLQm*76eyn~?"$1C )diQ? Z{n|MKnʹ/}t| .~ʞgouVq'-\YtϿXUp+ʛU3< zo uYKxꋶoN)s*0hKI0 WrM4&8 p5>:'hlm""Fuemd4!$lxZC.K?qVy~t_*\ݙ+ñю=nϼot+K&o5F5[X@gU>8+zl=Q/ iYUO._,¶l~yurn JxCZ $s$,[UdAPDpxx@X:Zd 㜔v\5Q+Nh-C}3ػ6D;ElI&A5(H NaE0bD` jBԤ hvzA4Uء6 w~b*TP (4A:$^jrO?_l2=\;<66}W=n]ueRZv3!`oR}6X7}[kݑYoC]$;2yORYҼ[k\DksZ O3ȝ=P9=ʈPeedflj-p"*\%xdž.Y%-[wHeïĚyw>f>çE-: X vĆpخ(m nh`/38o4zm]G|TLP@H=r,5o6i hU[[x~~sƠ:PTqqhf9KQv$Ա.u5#zŒf}TEn'̺8WM/(o4/ɼ$N+6~TR|;y /7ޖ9p~Xi6B 8ra4Q!Ee_4:zqV!3KI|϶N"g;(hijƉI`I ֩Vy9ըit~wX@;8"N= @A%UL*TCGj:-fN+ϛx['դ;6un6˦%/ie3RV6 o%iktW1g,H[\8o M!lwi|n$kԊDɖw"M&CF![Ҟf1ia$h#IG=`8 /j_pq}b21.0nJ 䶾~Лھ˄hޥt)󉎞oԥ4$}Pec#0CχמTͻhOh XY٥~a?0h=z@H Cɻ!b(7[MqΩ}X#f;~@С]W,O,T|k4^Z h% BU1^9|K+5u.mohel*φ\;{ǺeW׻osxu-izXժ0sk?O-^O)5VfxE5|})w}ߠPu7_?wܶ͝ q =| WJ"qVUz,ZcGE |N^~6p è#lcVFn#w"Rۿ43*y>?;:}`&^3nì" WwBUHw#䤟wq Bo5NVpfD~k0!'sm=?AWcߨa)ViӛRѲĪm-?#.gwnje'jƨ!m*@t+QTCdgk?ǯ KܭKvOTn Ef%>J&K+_d8Ƨ])qM@Hbl@MKu/>4)(4X\x6 Zƭڞ:CANFa|"F8BQhHP~$i:} i{Dӛ}{Bwy,>wt2):EihHdg*1'h}E؀-[9ALCը'^v 2ƜltqK=[-3ӆ[6E 5g}t]٩t}uá/ JR)OpY$LJV-_ֶ'p5ᯍ~͍94);eBs__M}h,/-L&Oo ZwF%Pւ񆓬,eb*Ɏ׎:Q"^589vg 6 3[So0uPxv#1:pWղLl RgZ8yZJn=.ϔ[3;c~#jz*Q绽ssS`WxRRDKvԱ؋s|ݾ)CE޴߉MBD[թ78|?kgS3k}>3zLg!aWs A Xw䴗%)VFpJ7WMfܕDv_<;\6 ZpHU.R4`E@[OMoov!8HuJE+xCghëWEL?eI1=(2I֣/x)ﮪTֳo~5SpUW7?۳)mj)aĖ ACث't45r`An7mf#LM>=ZvV}1 3%ݪAAX%IkGXyFj_TΟ~`= !2bߥ WONc{yz?0+sJ7K՞DoXz_R݊ChߩpZmGۥYyQ&̃}БF2ΰC$xBVDůܻFks{zΞD߇H:o9+Գd>aaw6_Ntpw隫/,ﭴR]\oǯw+%,~<6WItGPEPS~{y kZ@7bJۻ S9&(E>! r#SUReD1AM;b=zSvǸ G Lv KQK/|aY>f?V~BkL}*{zʯw pҏp-;aqDX=:kG 4vd+Pet49;>rZ{U)z#Oɕ nnxPΎ Mz^4*?(!ʢ*JېQRPHxT1,زNü ub01dGMf5- Aq{XX7 hék= KӘ=7y}ı=ͧi0dR8E($AUy>*#h8%b3H 'n4"2஌Fr iWQ QcE ;{uoױ|b|gG[yl.XST!ڱXۊE,1#!ےuK6ZvvN|Qeu!kVt,fT 0yu7Oå2pKgӦO1l)G4;tmDr|u\d(V 6yaJ9Nf"~vujsx?u_A9\*>[[<3TVr=/꫺&-esn3dщ+Rgw?fQ:-Ɇ:xf\ۓI$\:v Ly(Qn`X4b~ɜ?-H7mxis#LD}NљE)IwA i-h¶Z$ځMM\杤CE:unM}E^-?{bjNYb~=n2/s&;%jTa%R$7D4duI[#N]Aa?ɸ&,J>Sؒ*F^Q?=kvcAB*֋ [6gW.3]Bjᶒ0h~QVt$8 $)JY$9{^AϽg_?BB~מWg#p"AK@ĦceRlVyrF`:v?^fua⵴4S] a8*  t;0JXBDxgd5H8_S#Q",e_jl oatH((QD[{eW]]3|9e K(ԂbA7,.]>}s;<*2Bá@ $#4v[:}ݕ2s_/_p/gϪ|O HEC.v0P9zIP@Nt??_坞,J`XфqR {h bpbBqB bAHE< ĉ⽫&Ww.}jYJg IlsӹYt,wtyK@62zHU֠v|L'~^vs[UcE*BjZ\?{w:{e|Q={Y=mr[W{< * ƨ:;[ʕۈgnMeW3l?HdS @&BMk gחV9Iddm)Cd%3񹿟P*L H5S’z# !G9N =T=(upI?#kJ5zqH]=Y=@}>C3]>ёDhwCpGX,֚#7r!ע=B9X@kmćB@zS/ VW {~+1m5ۿݏ׶|lXs-hD7[Nke!a2(R[W3?a9?7Mqי^EEgC74Lnt?N墼gkMwewpMC jsetEPqK'/iKGړPVa-}ORD lZv<Иs֓ןKFV@6?Cs+mYy󋘹h9~ﮟ )gX X6Ic:M˹PϺ?<R'3RCm99htDajD[?cft{+עF)+뱶N3!,/9(+b>?2'􈡄AOm͕Uh ;LI'-i><-1nZG]69npp';07ԫ"qWhuϞ5%dW_ ,XPvOu.mZڦOqA і@y5E=6gIޟ$ʭu٭Ov7mƕ_CYR2 B <;+-Yj J¾0eͯQwui#%rE W&7VJ?Ȓ$+Aij7;̸z'ߢmXƷtlSOVe(Ea sJݤ cFGAb/^-t#jjd'YW~Sο~Zd{q6tϊ]nE Yz;z]/wB0)0i.(RAgS 8Xf Zưу cHCx: !ry IDә&.`ݯ{'د=cEst{Tݯ6m2\}{c  PjSm)Z s] ,h\n]ϲS_yRG_I>O'N6+c^+DK&l?7HTs0a5Aί rfQ~9Dz={c;u?W3x~P#ԐP"T x­A &NB*ܿn-v(x'%Ľ?r»;ҋB`e9w[k=;M&PK > e Gi$6WTM鑵ݵb=%Y\A;BiGO͒BqBd̎뚗1P8&t?CN]as `)7`ro"[F=?5(ez"jMa7ԒaDfRc4ot3{Z"+IJ$m:d?[T\:%t/7+q셍S^X\x}[4 " }y08[=M({!XK~q y:5I{57;EM w">l&'Ŵ}h;?~,7y|aQI@tVII1c&ިpi镉LR2G:%%h# -P%K%tFK5u;$Ogڠ}F̞iݡ&PZ>(K.tWcVwb6{Ma4Ј1LBC LHZ +]Mȡ߸Ml8}bĩsQ'۬ 3 S2P5s;Q\VVzAj_ielZ24IdٗI| eqM&LWu:*; dN3~;Ϸu52-l/pq6!84Z@|KuOl]~ ,MF(X8I>2ORҾϖldvbӖ_h5P[x4DY9|ɘ~߿F0u84ⰱ/Bώ}ԖlSBO0 ?-Nnq`P%2 a Fnugf]}}7lpo4XKm'q'k_[6 8*=,fJ\$}Ȩ$2rP?*((2tU) [i6!M=`s+~zNvϲ\S.lEuw2qe}vؑЃ IGЈm$ysprAN*拝fC=V1晫FMף>t&9l8+ Na j`,} )ch2(^c-B`(p 'H@927i&dFBh N:ҥA>8A<˫Ot|Q $n&-:"Ƅʶ*΄IWA_Y=nOTOnwlV/S#)Bk!by%9Kk' g!~ke&=iq\;X˷iRંk~)x瓓nw4*u/٫U/*s],~CJuN4zp, SL'NVbeEwbɢAJޤO([%j#1}K= ûq^/y;ݯ6"73PKfL nl5H ; ]"B6 (Q "H( L|o(fҙ4wC{,? 6͆^,EyF/0zTQM&Nq\=g5newImGSw6JG?~CNs5ad4~VL@PTo'<v/idgGΟ>tὝl:WUZ[ʫ +AV#Y$;z&gf''d־(Oڿf/=Q9+_kGrj̅%?ۄ VS2ֽ"Ցt{Q CLZb#qӖI۫pP|3{do#Nxjn- 3MVDDhfRD̰5#ij{tjFz)QԔɲeѴX! *`%[zࡢmU7Rb)Keir6h7i#&Isb~bu:n2\Z&=zZ5D}>c|]F%zZRh\X8Ҭ3Vt{ }so?)b.h eM0Ȇ=HfoXu"$ n&J4)y jza'D?;ߺ0^wl<3+ վ.ib B#60 DC}x*5a2 A4+@ ?ĮP^u삾~if94Pp  Uhk;+`(rγ9♈ƨ7Wwz#̵᧒؎]4<[Z,GR +A)<_«q䖷vd76fbeՙHfF[5oHrH$: wRD"*sa@u]n'Yԅun W7nĻe;ۇ5vij%.hf]g/%~ƍM}\SA9\n1F p}y"rݯ{3p)'v6pDJSS!#M%,,du}h_ܽ٢k~nz~PKPTYMd$t)(s0a_d0uRatS%$ 5Ն;o<>jr} 1.׮yf/@`Dh \Gs@Oϩ~Mh=X^H_\c1^#_VW?v\ُ,@TԄa(=CsC'߲=z;N*t$\0͢G[ir#IT^Ϝͯ.4-x|Xrr<<&'[R3]gV릞mؚnQ]Jz;-.l:mDm4mޟe9{ ͵t'g5; vUSWp- ke3WteE_ C3/]=V8nѫ qӈSFWJE?(} TF;n ^h ppg~~g=tR i3n?<1 G&C_6Vr'tÄ0vǝ&7{FcP?z"V!@&B2A :@'P\PW* ƴ7wDnKJx {+,V)NblM"!_!g߭ u#tҢ#\"Aa[TX{-9~mq#ceY6BMἀ-ΥΈS!;؁:Ҙ9 $XBzpK_ l]W6lI{=:+|^cd1XZEzTִDZm+Nƪf}|'}k)(]Q%Dv׃c}YP~in`VC, x ,ԧM !ֈil' *]U9v#oA#^4r6`+/7 hF).,@+O=fҽqMһyyW`1of2x_0`c?"-3'zS {W'$SW s$H)e1j[;éd\9?gki$Sqi`Y (F#Q(Q&l5G v߲8]|͚֝Z^6*ƚ! M]?~я>G{5=\1ѮgX'j3MߡlwS'(_F w홺+B3|yNXY7WSH]pBՉzw:#`@5$D gBpэ `xtܒل |WsGVhs?wqIOo #%hsZ`,P= pȭHLgI힍XNɭJ#vXAg"à@#|uaT5ri:0`}uz.{D̀9R=BV|&#uŪPLJLv^ʯӷ_Ι9HQPVx)i,W8 e ]-IV:r^B-*wFrO鳸>.TB/<*m,?}!NYa>L=F@O8A6:wapǽ 2܎熣(0@?ާw MRw?̕2o[ ev6r޼,4Xh=19pd[=Y6zܙ}<ڸH<7?_$"TYd!Cf"Ii_?rPB;1N:&ʳN$_D%Q?EMqЂ)gRBVK_9ZccPIYE%CId`ExVf šڝ*6OnS|r'u\9uβ -T@s٘NPx8}-操)$1[u놁1@-ai=>񴧿CMkb!=E'.t d"O x*D+jx0dUbƍ~l^z d,J?ǫ-ۼ'0OM4vn4qggm}FE\~gÉO^:Z,!.qΚE0c$x?;sz@"d֋6#, >%g9 tu7r˽Ҏk?`bjb"-7mQ;(,j.N#9ۄQ1,vJxeR;[ސ(k4QN@2Xa3>Ebw{WW_l'!XĞd mpxyÏո$5NΆ[*eȽB'jq^w.C(E j,)o@ GOU¾/q\7HWe0vOx5#ms^l$%fY%dmWMkٲ@1{IU785r֯[1HzHic /w&cv&9 9LҖk;k1v`YG=yu7mVI7a]-#hl(*P Jĩ 0DJ $FGD.e_?~gJvrn<$:=(s؆I_A%HdːXRXv4v)/0UL];ˣC՞=^Yۣ̐n>/S+ʊXqiSnfV2&DFLF&aZ3`T2yVa4I~.&1nA\M3ŒK@I㸀Mf 0ᡰP"ZJޏȀӽdsCFW=mJ)岵davɌH*T()!J"kNS>fdW{wU)͇"Ob)܂ $Z${qb#{ew6q6ΝIYY7!A>. ~U [եGK.w0C'`#%(F'Q~_s&fW΄vaRS 1@JC&ǩah8j4b=@ӐK0Ϭ(K0-\evR4JQ@!n!&dw63Da1/;ᄩ?[FS]OΪ>U>rOHa%|b% C+wF'R͋BAOUUmu%dECڪ摏"A.!=Y" ! 10dW?]pa9gHBՃ/촮'yF [~,uBV!CΧ^viDBN_s=/W["U\$~.B1VXb9U&/YM#Z?SK>Xw7u\[%$ϓxE! JV90eM24m ʗ͇%bxuegO*/?R>Ѵ]k?_֡})(@+k}+ftbϑk>^E=$s=bP7_졥J$瘦iHMF&:@<# vU0p\^QcO2©wpGݣzr}JECڣM&\˿iPtCa`SAMY]yڬE|Jh%%?$®kw'Hh_2As2p:yВ5;i$KЄYHHrbo@Q: .ߌ& UlףN5`)H`8X VS>ҖʈESNg.m6XLJ:* G> F"0lAzxDiQD%p.6)[qƳgfَ+Y&x HNdm[+iŤ?V?s.\Yi 0І0Ay&6ICmQenP% Xp(^g"Pk Caʣ(юoWCo~7rµ/={-ؼ8ە6.xu"ղt!*(ktvSd1XZ&6ԡs]e$"^ w1> o3`-A08 ;Є/Єxه>0ȌrF\3R^ <h)Ⲍ& ŀ8֨iʙɭi5^ՖnB;Ya=d\g/xhcSM#4WT!꤬LWVǵMfE֘%IZo}[wm5W5=hsloX]ߣQC㛝}j: ǒF3jU;Q9 %"uTs{9z\ "_ѕŮr%X[# ^z,oF斖tMO̔0ʳeJHΠINwwacnҫNDe:m B$ Z, !}"^;礪oxpI>kdcC-y`,#Yy `{W;@H%RD֩(h=)OTa@ހ*:938|csvDbk۲149rIqD9,N:Kꊭg6:ִUbƿMSVbVk+-Taw27u+Ե ^k`uɩe7MSOeǿe1/gp?6Ovww dAdY2H ZrJ$@* "6VFD<2F4BwpI[ ڢVp`1LNfgu89CF3tX @,5ɲ-<tvM+Vg ]I{ux++۵,C6y(/VM$;sCKaRP_,*RʖS(!LePY%3YR}ղgd!D…fvue$`i@6A(]TA=%rjOon,$IrٗFQh[j )&F)JL gWfۡx$ F9$ްLM$I5L;w7\S5ˋbuA_`HS|9mj,491t=\O$Q|EѤo܎i.ɟ3[Q ""[+H`b$ bOƨ4ekC}(٫ `UEovV2H5e@NA|7(2@#;5jX:d{ bOm\(ٷg`42 w T4aK#a.9'=$ItG֋󚛦#%nڊ%8d*֗I_bP- d01IbFkgtp}3nW؂bA X4D,a-iA{\].?_MnPX8 ,b+6 vD@KET0FT7$龽gY= `JgvTCAu +`,.IH0EXϚکzN),$Ww@ E<+>3[XKIBa^ܴA\5ΊyeOyM~1!FT VcB6`Zfc<0SeW›C j8G' FEN>uy>2 &Wk^xUn6ʃ.>=>hKfȏR鮜ϴfEn"T4`%'p=)שd78UQ,8sB7񣰒?𠇬>-ڴkhWTk5a?1,蕄mCUvS`AaH_N̩Aa1ʌ 7+)QNEZI"9S"{2snә19ۉ?شHj7F+Ag]*fTIct pQ VAaJi&ނ:9{~]wxFn׊eq+Cs2OiU": ] ǃ3 '=pz)e,n*hdK,)#j@ aĎ:ܶgR*'kv0DOȒp:zP)q3:5p$q8k +-Z;(JvǾ'Tڊ*5 `40C Ha O¢5*W@zh/ V5Xm)'3C#t (ZX t"bI"(,,Y R,)XA:Z< 'l@iLMx5UPlqv Y?T=mJDfH&D1rEz7AJhw @s64P_uR2-DʈA6$~RwH|l$Rk r!XzC;c26J@,#/m@g\g&[k Essiϻ" DBt \lBFJbMvI(e֨y&o:C5D#’@h% E:ZŗCm.\ŭ5[6|xm…\ '/_{" Ҫd'm*jٝ]F7K'CK_Cה+s\Q?c`\İ&T#G4tBL ::5FS)1i.jbbUprG$a %`է /օy/y qL nѣ;' %=XPR ed@t"DTgB0$օ *veJ~|4Uv ud /Y!VA:㮆vfd!셧ֵ β龰B9ӦMQ*9m%<k?DXN(<#xdcfT,46\*/lyi͡,UUREEM(j\3L@г(JSl@bl^aATRO:Ow V8^,>Dj#9-zMeb:5$YIdb*kCOG8E-lډG컐ؔo^wxLҾY^Y  `XZ6UbJwxYߙ},6ꪚ! $"Lk UjF!x#`*!ATJeMmD* 9bI 0=@ b\mRDk@íz[ qq^ BVԇ@@`~ B(pل\uuԽ mArɮM#Ԅ(\+)kuX*FeSU(,P%(<'DbOr G״N7wM<kaICsz`@R<Ο2՞dxvmZA[G-BR0vb030$Xyqo;$4+(KyBչxD Wv[0g]`9Rw ӲV"d|SRklq.Q경in,m"s\=;ӟ6@3Rl2[wIw>m{VYS9@/1Nۦ--gábcp4*kYWTUnv$Wg4CӴN[e~ f.\8l|M~5IaQa3x{{l1'葰qȽtnprGHC%W/苭7>+ 뀏|=PZ)լefiO/3ӴG(1 B7C}1ܞ2}BaF/^u~MM0M۫Ϛ;q 0kt8ݗؼppFQZ!fVkťLd;`jEYazᖂC}@1&`:܁cfG^;nD4`@T9l򙎑W$iB~=43> CGO^79,XQ Skڥ@tfwܒ~O RFLW:CW.%St;WOLJ>b&=_OK(o?jsK%N\c]_xu]i}dIzN=>?1)L\,`R kMAf}L.mƍMNYպ~&26%{ щ xFk|S@a( 0Hf:.,dTO̾NosO&߶|SǽwkW5Lmp(kzD/qkR ʖ  T !{#EGq> OyVE#U_C#sVk^Z*QMlRl`h''Ź]^^6⿻h.Wʮld=Xe 7 Ur崹+ݫ%|*D݂p c6yc4) 9Ϳ1 JZ 0N(EB8 Oqj8eP H-eߜUɰ-F#A22 Q4K @!Nk2fd߅a"* DxnzU?><pT`@D#[{ BA*DPD ',ɵ'+!H,L JCD7*zږ?w/6D(Exn%Lԩ]UX>uc7f>I)1j l<B#jd9]^LJZGX +E 1 \;O' j6ՈsC3XҸtѵ-¹kju2([lmP% Ƹ 2}2CF^eDB ۘ fԌ͔:03 ὒs+AI->#wj M;&X!™[JFE,,G2Pn6[&Ѻtu[n}|~ed&fXM¨0$W$5zQ`+%5W K[jfLP7/'jf"Sð@<: p+ tE \(WD%JztΛfrK7ԐUL*DU>BlP"QD Et(9O2Vj?.K!MFF%]Ձk*24`Unݢ,Lp9Sy7.SSʖ ="F06٬bd`?ŒGdNl"y!nژn t۵8;u\~__c#bǪ>}`5ek\e`ϰ6YF(@6ʪr}:l%hyפ!JǝPmĀNۙʹW%O"ȥmRxU!ɸH %$V/D+6NO8;g;FGĝ{PX$7 ڐuo@C86;Goo3xAG""ᐟ\(aaD3 - !d&lhJ \"OߤG!+E,SCSq`O6TЉʂIi+/vxrt8a@a %F ݽ6$ӡ%M=, gcΔ8 7@`G՘0Xj `4#|l0-Ԗ<ظɠ[8|N aJ- Sco|R#`v5SjۋgRؔ|v;ӝ?6N:S-lfŠޣGy9s-6]jiNf遭G^{%}ЮGOSҾ9cy(}.Pk:}D(;?2ѥLݍ{͔+$T̮r[%DÅycN#q$k?Wӿ]' EeXnE3a)L4ntá( 6مow^)+= !Ou<~Ҭ|~[;_ڵ39RDW0&rqdwaͮ,{(ێL6X0"$EkZG$-9GP3vpHVۀN*|Ԁ8eXb!jK*YcOTo!jA-Ɲ'(Qs~H軀2BHlw򏱜D(7Th݋,ʿ EjNɮΌkh17{=0q4_szƒcIҔd\)LݧY?-@/]!c&A4n/";芝ZU+3MpF\! @P01j#d&[59˄MDH2wOh=~͵ \gl2{)]a;un"rB)b@TVF\^3$X@ Huh&S+"mO%C"j*\= taת\Xҳ.sNv7GR4P%)uҼ,59FF{b8]vyﰷiDJBk3LV#}0~"0WfB,@$'r, DymXV)@bCȞl!zz*Z ~r6W_~ټReS^f + &x9\ ϼP)1v!ٌ /+%TK},Sa!aD,6yzy&; X PEi=$]\:f(B6Wʷ~;9IvLw* `Ȋ ׆"ChK/~{2&Bj 8 ƔN}@!@!GY Л_6}0hGn ׶¾ @#BL(!XTbp{szBn`Q Qa@-@{vzNH8 H/ʎj1O+@$V*mBG[BłAj^2%EI./94ݡ&PR' a p:]e0Ri8 ҁu|zW/t'ך:as-dyҖdkj|&+Boާ=*:%͇<`:W뛦v'k 8F|lZ(ţD=OټKVqIֶ+d"w`x_ 3q>0ux3e  otJx^$=Ѷ[ҫ+^?Oԉr\I͢]^X>mse-k+ ?SJH ! A] O=!7ĭA+'ߨ]e,]rdNF{l{eIBB8^I,3v{bk` d؅%Ȝ) nRbJ=!`kls~&QT 7e;|qw:ߡ#g{ny`}cZ|rZD{[0P*UΈ|] F0H[Ǜ 7u*oQME‚acUsf^ڗWlD -/ye?&_g`XFm?O! 䴵e v9-5k$cG8˴'QLȍQ!yf N@Dy|c SڸA>kAȇ\Gȩ|'џ}V;A&{'QVךR_*!m*3T#$WN]V4QjRIavd?2K~Պ\{ F`=KZbQ ` ( 4IZ+LP2'QɫG*#Q,1<&?c^%KMC-3 #A\gLh\% LJ Dԙp!z8 Ijk Nx5f6K> !XZf$3Jd ,zl5SbBBū?>ž̬;q+B_]Z@=[75WV0Jf<igOzϴ,-8zRqqΩ/ј`L7w3`ڢ0*o=s%mX"c"$$tv$re2_")4e}Xpі dKX:oqBc7~1?Ѹ+d3BD T4 !2e-; t 9n+VԈ=mpmjS5 Qm j Hq PBv"y;)F(|B>S:5U;#3=Ǯ|Ke haՑ0q9_!J`eEL FFD5@j|ޘ+ι-N̷#E'ucm֋P.;2#[;K\e 3 f F`90;rka1ӄ[`4Bz40@U"h4pFx~Jj+8CKJklexy@Q*XwiMdcbT47:w;u9>:InrA3 9i]K(J4߿2%KfN0y˓p8 ǫCYg=Vkr [!W;LzVvw43?fʅ/tYv-YGFv 5Q.U=ю;5g8Gڝ dHѷSGC!ȭpG tTΡ:>CpsזM5396ָsȽKk(k `@,Č\T`q A_8 a +- 7FO6tV.G˩ٛ5&z"|Q2SNVwk}\a$ӪEX +`Bf8\"DfV5JЇ?g'݀B{g$gk2UA|P@h&[rÏY z2)FZT:{/$@$tޯhf"vєJA"S#IX"># s0 Wp@zvwNMmikx%*8sY:>QRة& gbL)D n [ ԟK`=SbݖͩDLJ@p@u8{HW&k{;3\1C@7TcyY2uT r+#6{- ]@}F0Ƚ'@&XH[pA]Zv taɖ\4{ D ;s\zauֳ_xPtųf/s$s.l# O1bFo_4~0.<9IK}ivbcM_IYEuqm~sin̉ꬍ1kxhtϨY멇RW;"@Dtқ+yʝ(/@;>oߢcNQFcQ/:8+#Tfs? }U^䜸C7L.+L&U-lRUݭ=N=]6i,6bҘ6͋N`96)OYԴ'\ab>+2(LI&{&qa Wce{"Cx% *^Eu {IlO]NV!{aa}Wd>}~&xaOo@.+htQf ! QrX2 %@df].ÄRNΰBC4r=Q oP`ZHNx!td5X^aңQA0쁒6͵ϡvUֆǠ 30"'72jr趢ˇCE xOu..+KԚhtaFNѳ>߭}q<ֲihD8stn79WokՖ5sl=Wߟwv&Dc_=tg#-6|/_{ ^qF?XQZ3&nn<ܻklEek&Xk&3PjΜQ=; ) ;as7˭Z $GMOi^uW%2:HȠhjҧؗ׌6Lʭ26rJQ,tK-€PvLZXG5kvVnb[z.'go~7L˗Q-CiJ[N_JMBjuN$"|~.& YV)+{eٞEt*9CJ*'|OWG& [!xbEE4>GQXu>EIFO]6,1UPVS(h!HQ]螼uN%)OYYLAD*!^E4Wa^ [ٲhD`ww:La#׎\@ߊv~c.˃D &+C \`fei]j-ss#P‚E݀GfBsh鷹7M}-^HYM'|au(R!iDŭ]C»e3DDN Mb RPX8c.!?$tz!Tiqw:l :hOM4ڮjǬY ) v.)Z՝q\}y-!G\ycޖoBVo|̈ ]ma Ʒ|v};ۊuXy[^x8V:gz#~7t꜇cXiyբɭk'պY.u$ٶ~ܦ.!矼s]{ f[.p+\'<.s컨YѪw^{Hj?=0 xMlz3䙡z2QBJ4u-~!ՓTrJ E#RB$PF0OO(dƷ z] ހ-L@0 cƅ8"81!,B T#L}`]{@߁5:! I'"O͡W l#7%q @tfObgC1F\%uZ>yPp(x5~G0ZP[X{ YPR!Rh|bo}tTuרĬ8rs>ݪ1epؚk4֎ta8 Cbϋܲsuz5wy/ 5| s''f=M>&oNm0=0^Ĩxv[r.&{g̾4E['jB~yܦr%2cvDc-֭MFZBQ>[QseY?wI3(GөÚgWf2'ZGlHɀ$ ̮/ RFَlGVu<^@u|䉎yӯ\\O6rҟ;ᴚ:f:c9<$+NY3yLa|)ڗ'Z!y;KdG BvJ\8qkv0IS3蜟ͪtJKV`'CX T@X#HK Ud gl|=# "9=G8jxt0v-)HꞚH.{ޏ|u#%OۢŞꋲG8yx߿=dE 5x(R|g''y f J k-2G /&{OL}w=r/NOTL;y-'mM4X>cTn+j1<\s2w왂Q+Z]cF;3Zt/Iӿ sZJĥ(4 tZp8&D-4). +[~!hma7J7ؙnP񑒸w__rfo:uTԔXjKQrYo(UT_MlĶ3~颰묥s8P/s~w=grh*#dL>"VDk9B aFFo5ngɗrn v7KjFqLNHk7e ,$R*&H-J QA7<5b7]C]V3 :鼒g>HҚL0-Y×KbLmZw(i_r}g\ "ҹkхR`R2m+ma'kkEwh,u~vk4Eh!PwSF4\AK2{.PG5jR6q>v9F2Z߲Gl. Yr(_JKGy'$dulJ8XqQ #aE ]yb./͇Z04ƇPf(V> O:U!@qBdžwixhP-F^U6Ȗݖ!ǂZ]  }@lJ~4im1eڅi"D&~6`uT_eQcOKc:06X6D'!G,3+Xu)ٴm[IxINZFw;G ˕X|uػKֆD]^FJz9O$ڏjAU0LڥA84,Zxa5玚gTgȾ\{jPLV˪#0[ =ȣW .glqVqk?CM DŁ!{zy _?=/ҢH@42j!i.dX&~? 4a!4{PP'p fԴTkqA%DeOCBIrK)f\[63aL`3DZ" aZH t`( A!JEO7碹Y}\c)}3!Ga(<T)-^OTa]^[lY;fi;?G~f*m7?2T28}){eͪMzhꖒ\y2//!PQ'L;[׸Fּc}릟JTBjw~=߄.,rs9~y\ T̪:[عw<03ǒ8baa6OɊ H:].ϤON>V\|U=0:ZpLi|[9_} '_}+vqcx/OSQ.(fVW*;[gl2Gu"wjR 5BN-h1,( %R t:5g+um> \5적`ր3 0p4 # c,3 }UM*l_"x[&B{:ol}RJ DBŠ:-I\B2ފrjǸhۏ~8n +=njBdewc擽.jt6fqpP&HTaF:E)\dԩd֊U04}gJ- hw;6ieCBZٲȜw{3:jJFq Aly=CH)iq_l6K&K(2Qw;6L怽u V=UKO*a6cXz=wZ7*=7k&U#UrNDZtj1Zh1MvJg`Ch<j :,da=scuD6z5gZ9@wTV}y1Otv *}fyK*^ߠbJyw6fI*2Dq(0'ʖ=>,T'<)-L~ €iT{zqj8ܿo㬸rFYcg5 =X83H$$US{d)n8[k!B($B`;pd1D`f$1WE쫩^ى>>b>?a8'dzRϫgrgmUG,-f8BAzRG%U֥+GS@0TۜS ĕ ;\`&ў~+^:cyK#C UV139ͬd1<:5A+Kt`PtkNԳ;Gtlyvs?ReOF'nҷ1e>-"ψlXm6J! Sj g2! SN@$[4!00CKJK,aStahi|ZٹcO~Z!^'|])LxIUWsJ u`rAYF ,@ e'E\Q<0͊8[dgS}L}Ӈ>ltyP7 Z ͢@G6[7{qeX*gzWK=5uWBۨ fUbdja~ n3ޠ(p an a,9 B`bq7U*mP[q'%aAWPGVȞ/D@BypۇѿEGp=B\m QLFVyY 2GiV\RW#) M VȪ?ܬ/{q1!G??տx'8`SÃi5aJLM.3 /l-?kw@9wob0_3ls> : $\'rse9TMUDdv8Q/.=OAѨ41>7Z=GvP`r<}Med*:*yzxqK5 2 ש^ lԗ)gT7L]N6caekATNQ {;oYڙ)r}7Gw{z1Ao%I3x1CuVO,>g*X1%zxȶ|2jy.$M##{^Bhis"*HGYEHb_ ^ i0SF,,x3gM/sŪV/ĩ|׶qyu:̉,:(ƒAYǨW'ʔ33ǟ9Kxmu-O7LF$e>{~'&z(lZ[wd6deO (MA6FPb6ר_L,H PHFgZig I'UH" 0K+,-W GT*gCBתNxl_R:D fUuZWOnys;N4~jLA!= u#"DL^hdOB ~XEAF猄rɺZ$R$PCD,%1:uMv1*?/{'Iy?+đ^j%'$D1D"V8J mZ6 r2c7KՆW3::]7&)ҨH>IDݬ'`QGpR ET Y MӋ@#>opKxʂӱ'sW!dץB,Xz@ rkHݕmϙ3ӓ yTh4C% w. "XIFtxw?qaeǤ/i?K*Sf&DM\Z86q=7w{7*שta_K'fa/Ry_Rk|TYE/<@W/_w#>Wp7Od^֕\f5'F QY/ؖeQ_$T ԶUqPr58 #PpLbrRe8nꍒE"h!T+uAP~v||c< <g-O`*noR桝K߄qxO Kذ? ".jsqnL*]l诓DJMf?XR4ۢ;,xZM*>(HV2gORwC ~.Խ C5 0RFdGJ#N`0]!Hfg57Jkp7bKKvpi/\ִN߸'j [otu#ק?=mVeCʉ/b)5BQ˶, 6"ʃlClV?۴r6we߮2b'㋯'}j.pͧFc<* 4;1!=1= n.AD ؾ0JI3^=ʗvcN}jCŇ*9;6q(o'ѓK' ,a!U[KנGC)VA'ci f :NFAu_8\C7놝|-'|z`b>!|zOlb~ Q2珅m.x948zw + ?[W 봅}~o*8@: B\V沈Q_>ۊc6(u]'ZBTp|WZ<̡2&ᕉ\/\Vz?)QyS7u34[?7aC4b rtm-7zǮBq>shݳf~~O߯,H&Jva&Z0@@#n -w`emi&D_wG9o'޵~ ր cԒ%ZVGF0  1xBViECB__e7Ƈc!w$ʖI.Bq&΃ӷPYZM[%S95%vm3^zmmFƷwN@X\ng93z\vav/<[ҲC5UETTx M)Vxwj9Lr/!mqP~HyҌDYM95+}q!}QL@{8p;@H^bSͷO)2 й&,޾# ڣ&-2/ sK DxF+}˜X -j+ǁpdL wޗS*uptԳOi."ͧop^bϔn_纊%2jrIiiKӒj|7.6^{`!/!Oy}G,{[Yk7`r|὇76o?jgXJ@գP8clŗϼ?ۣ':AS~3 +?xFV9TZЬu 1mHɸY 9[3yeLqRX X`,~Cŀܖްq! OzW9noጽ|W?oZw J6<-4%(1꫑b)Ed30q_wLH;_lizn+ BBŷG8TP/hحP5yCD=Q1*,>-&;3$zRA*ÓI6q5] 7{cq,IC"Rzy,猜ȶTM&[Ȭ^i˘PZaKRFGz_zGF~9.'ۮBۍeةDN (!D׈hꎷB+HܞQ9y&]goeM6= d Q1,nr,֥L O[e~mM5Au8(\Ғs_/# _/Z&#̓m_W49!dv7_!C1oU٫]m]be" @(췕XֆOWéSa,Σ2[eM ]2b,fm=IW-ͅNCQIƸ ӏͅlt܌+.Ԯ; /˫R4"b;nrI~R~EF=lU|랷;oLƳSV7b29ō7m!B#A$B4X1=Ril7fO|?y)nص}fo\ijKO(n Eٶc_Ļ.? X w->Nԋ*ma:Q[nNm8+JQ t'Xy~[;+~7Gk]2/)8"P$#H%jWz^}W-])SxuV {^{6"obit !:5Kz}cCeoqu֦b[~cX쉩Otzs0rB iNkɝ0~==v dD4nT>Q?y'.NM ]$+&#M8osg/iJxn.KzN:W=Ǿ>>gaVoo=}O*Q;\(yNظ^MC( 0!M^ .ilV'K̷MęGE 7lP e% Pv3ߵ6@kZpg퉺LvBؚ䦒R_: +Wcgz|"-)ͮ&c}՗jWnr.W L V'gDx2d ql-^v/n 䂻38<-/B6Kų8wݚ,=𠶜{x]v@4^$/얨xc5,!&Oi},lm !ZNwϦ)u/62x0ei W/%WG?hx\H⎄q@" B+Z1w/n3, UGgw[>zΙ9ot=}܏3dwBnP!VpbLG=> ߷KVp,tK?>̖Hm[/m$f\as/y^FB]2#緅GWk3'=j{T>Nm5!S$0QNj&AcI@XK!۽l<֚'t#%yZڍ6Fn!0ib˺&8JF86~S5|A 6Aapj<B^ZQDzv7\iDaäL5^'#Ej w:r ^h(B@+j)q>JٟJmW=hpHTSXBȤɅ; ^v~+زQU'FFyoocKK&soMx1X1n.rjjkr=r8uI8e }4bٴ@^>vv?1~>?m]xQSJ\eAY^~p-/?h@))i9YvtWiJyŇ.:nMWhg*Ji. ]`1v\rU};]BPmAwmpԝAukvR̻y<.:E/4=n5曁IGWӖYۭ{b<4H9p*j$Xj,D&J*>m(b߀<_Sbx,,KϫB0H=f{Da!#;?/oB`[qL|C0`;l&+*u"T!~ N!#9ҽޗVT:n]wgGPY| gUw7|B֤R;#R#WV]N}<&pkB!~BN筗%bwEzkF_gM9Y~S(q&.@S{btٓ3ߪ(QR7Lدv$/r?U:}뵕z3ҋ4JDT_|@ :,k}7Kvڱ''=.ʌ\) 'q&l;ooתx$8xo9+e˛6~Df %=O9 J勺pJ楻90'$snwLf3*+<;#v\*#713E<}bf4&B95)+:G#8PE{I 249’r`8>*nA^S!EyOj`#^W/4쉜uowrKy]0ߋ1D>;" ^]GmFvPzkZ?a='bD鎺_}jYkea+3)ΓyѺdsN⚾{W4,[*lټgwSo/ه;*6)*nJ_yͯo@#/sUhu%t]Vm!ڣԥOp-q z)m:{M07|^^@.V]N| PT2;-QT lA"ܫIzsTf8 v$4#MF!ƨVͫ\i)X@F'WkH·^N$j^j^{!@U~i,svڃJ]tkmSU=j旻L@La+;U7ݱg׸~Apdp''Icqec}lè?~'PTb;^yw cE6#Ūsiяdf]fqگ;KUzҲuZuZ4'=<Ǯ˸u˾Ipk:KFֽz{遮X;:ep+3L:ڡѦdVg !: x׉î胏;[7Ea_f=t{sbw9̒/U1?9mk_ϕMٌ(Mnר[Mn^s0 d +7'bCh*+~';!gϋL O&j^* R/-BwlwNn8TiƜ,$4"Eeeۛ,k+~P\iuq{rcZn9bO,1EjZBy/{TOi>ru>?)Tih_;Z\/.Һqdi>VRxpxnM^;?Kġ@Lre;rӧnۜ,GySo|TMCDt%C`LdJ4D< !9?Nύ0 UYwgӒY=)̥$+A\Ց7f&rzLuIFo'D)x'|yU'H ի˺a.5DLOeu잉(z|ԝ.^>HjsWpO軦<7Μ\ʓf _lNT!աwۭ}E1 SHv<aQ"KN) @ WP_o5S g $&ԯ, oFzҍ=TE?}UKb:jjvIT.wa KǮQ:ټ/P)RG2 TgI7b+#=lH\VW??6/P?"t#ހ. Gy?=nח?&nj|=mtc稾gBLwq"3cbM;MEHc gΙWZ84rзMby"&2 =3-*Z8tcsNjo& %ׇĮw~(so !r/B7MjX@CH~w#ԭͦ&B_~lLk H+TVVa+y5U|ܵI]qGb]7rlcTTH.c! ͩDX IRU6߳2lvq4a|%b.'&;A|U4;<Uε~¡P+H=0d}s&=Ǯ+?mm Ш6էq<i]:I(Uh%NkPf6~ cgofb=Dʉpʂq}t}[0؀B%G/f8Lw= PYreiSE N:δR%&#m%ǝSm=}(kNJ `5gI7NaGCv~Fflv R-5ty~iw}~47cv5*#p:P"`A ɦ "ZKkVbz9EMU<{{Q{gO~EӐL9)7'7{yӇCLb&8qr6Q:fg=x6FW؂=:n6~9cevvǿb]QS3]{饙xz@T5SMR]̶vj~'"W-=tjV çe/=M?g9MJ߄.7!G>6q<\;ݪ#Z8+5;Kq]^U#R4'mҶ >[~BNإ"@_P2M}Gӻ|<{Kz!kbž#34aȟPLg.gv|ӷly!;]!U$S]O2c~xυxd R9,Ob 68ŭtrnlCgxi9ncޖ u wsyҕL?o-UY|z. U}O,{љcMe &Ma=\8rJygҋeyZZu >$h-oBw^$Yf_>y+thq3sJw~o(労La2 BΎ([ri׽3nCmKlP?:l5:d ;n$F;9g;(пƐsMĪkt3LYeG\pɼØ?EhlP biwD/YS0Uo81u?L !8F_%)8!v(޳Kotj*͡F?ZЧ L GA=k5lD Hy!s_墸-˖ J7ڝ@> v2VIl0dzJ%D17{8{mn0u*Jx$t=N}<"?׌سK%Ds6Ӧ/>5=rz {{<*mjx?Jw>iw.q6iG$95vu 8ئ:+@?P[0v.B@)@ g@9v@Sc_jfb GЪTQI3$[M%g_y?/Gg.c̄/ S,vGYwMqB^oNx\.[So XZs3{qឪ-w!x{>)ܔ\ղ,߻%:U>'5d`B[I.fv+ڂIc\|NI h_C5j rAjã<ܪfH -&b9OCO e-5-r65crW>OE~^>:ގH`q%2:cܡv?P>]Ƥ* P·}3TOa $L -Y )4Ԋ?/\Eg:W.ADB¨p1lwM~R*h-b6T+mL׀2!12Bv͝*F[:4\ /Y%mbaєXZn2~VrC{nxr(O@dy +x;^Ywoi&^&j#x_߶^5bӒ ),<= 엷 xM:_ժL ZI^XKd2=Y6uդu׋:K:9"tC?sҷj]3AzstlT;o6o@E_?O֌+Rh\]2zv!aZްS0Bj/n=vU;+5M s X%%l'+?дGt?&OzW+'W*bcPG%w/!$nAKMC|MRPysՋ^9Nyh귚*܎d綗Rr0qnBCM!;;k ,n$~f":?{JYZJxa_K:%AsR \ JA 82xg~]&Ƣ$(!쐈L@J44v^yi7חJx yyzof< 7/\g Mڴ<k_VxG;}dPK3o>HC_QܪXZ9fY .&x_y|(lgѷo_Lu*nz>sY@|ƥ}6INm^ɠHD( ;BW;Lh`6{Z$zf+~&GV@\_uk4\ BQ "7d,Vzt..[/v[9rws)cRO?9kxHcΤ|၆r7ʣ5ߒHQ\,( K 3@!$ADśZrI8!7naz-D7JuNSi_㲳A׺Y#u{^q]_q3qwtzEX4)yNLt%-Ov;pOE8~Ш <~ge!+9|hN /5t U[Us[4mLU1ʡtTS&I#yFhqyj[LhϢH2ۢE1;3;ݭGYYU7f( 'D$Vd˪R s =ꏜp B%qհsEP#׍Bb1FjYAq=!SJP}zii5"8A) Mڰ%H(T!xI~74w!'W`ħ9AˆF>iTT* D$Fk&k-RCd]e'\+.r(W$Fb N+ѭWʂH `B r*NW_R{_2Fhs1[Y_5,8chַmSY G?{.%PT| +lc.?DS>a0 &;,Bxn6{:fuڃL?؞iɣ!#u̱ HN?F XKc!2CLuǖ Qpt:YN+JWd M=f4D/8\q w]}#\>`NK[8TH 66eoZ5WQ)큧&t}حymkR"~<%;Z٤8Gs75ex-^VG¦yػ}7xwѽMf]qա_0G*LWČf}puFZ%f °MB/$s5*" &B)IQ=XttH(lӄiTVҩh[o|pݍ8]#¢!JxM~u'~jJ$ @⹆+ԓֶGxN\sPɋ5S۝wXv{ !Աc<k+4nn{:_\ xmj'`ՈmBi06/AHP(T A;ID9H)mmF`kj' NjwsL>H5F7h$6IluגUyg'8unۘ'aS A.0F0^j$1T@v!<ue-5FW9Rv MvPN..dRyki3=2Zg-?V@ٚm3Jq-ҞI7@p " 8i>f&Eyd$n+'6Gݒ۞IݟuiXVH qꔼUԽ>h[A|p^ip$<{k^(ԾNs ycxǜ0v't .-e1sۥjĖ?kW-wԏ<6W˭% [;eL(pXHCP`6J p%ۀ63` H~MC_Stt8>9q@7`']Xo+fz4I#/5;v5iL㪨A_jФD~{ |2*dwƹJxUɛvi]@吳vB+P``aW(m^qamXu(P6G]OƩ`'m%rk?xܖ\j0ۆrjKg R&fO/rsc0Z>g{DJ'n,\v4#;g#XÀ#ic<YzKz>q㨚 )aj%Q$c0 I!T qϝЭPB0(x޺h=ʧ|($g9K Ўc4\M9G 4\. K/}GJa */z]PH#ԋ2#a NSe2G~A?!܋ <ǥ܁>@ ۼny^?Gu_ۍJ/)~;}I/mǯR_ω(hϸRӞ(!r L nro/yX6}-n4 eQ(ӣhQLHR'j)g]_}gsN~KӌU+J+Y,UG魯f]WS2.~Uo2+k2Ej 1m ɾݓEn{~NՏ9l &݉^5H斡؛Q3 ͧrWY=пwj+ {+cQ T`P=0\FKb6/*xl_É hfn҈.Z.|FsehY/w7.9Frףp2?xd|dW5[>!7Uw`V52%JfJVm;EvV( e-GO&>qΫK<&Z$ũ+jFPIb,yȿgtLJm6Ƶh] žCo˿yeQu-Q=P3f@c/&J7=R2kx8՟==2Pvwq% h˂Kd`H}&%#$(GOTF{g4&CK`Pko}ئRDVOnN,ځˑQssP>vd^B,^wq0412,?𚔺qwE *,]qg`jW{KT a}ֶb!=r4]V?&:N|9Oٟs7 Fb5s /%]zo0 J-oY- ߡ2{>C&3؊kf;ir(Q<"B:)rC.ٹO+17&| Gyo&cDp,Ț6 r" C/ ֻK>5%m{֫{JRYoFnFZh_p}F NPo ۘX鄐Oɹnd-cȅ ʧL!ÓҺgva?@SۥyrwVn{@%QzC@<:xm#B]N/u>FLNڶJco]vZ{V["+CcY ة*ԛtl<W|GKjW3w;CAF4ȱbk LT,{+r 7 }K:QłpEjQe]ǔi?LvJ5=+_mH !&hIfq*'QK/D;c^{.hV!:?ԕ3րH^X$lZfs.ONC^sBKRӯu͟.쌐L\ ;Y1$VKhF1 ¤H^yfzf;j],7vօoEI,S{'eRúW2JL28o?!|n@vj҅ܨM‚5XjJ4S-ZXd1\ 3h»20y$C~qf#6Am݉.5 ]9ǟXTNw6QUq'fɾ'i'}H~z#- :Pv?gZG4J&8"6$w `KEmJb"@8/R$QPV&`h$ȏД7']./ hbn㙑8 *ُ{Eƕk ĭRؓ4YK$vlF*D<2]&p`;I2u"Xq7F>Oe\ |)kM:BdYp[. NbYG*|LlY{Z\K9Qdτ[el˸',GwG[;èqT4p~Ζ-.c=yW9\aÈ'P\ mS[v`kt6I(%j E]r sbö Rbb僰YR y@ZHK5(MEIZ{:S6[мӫz 4:tP [Bc=/vHdbk]KBFKv1jz;mvw|)âY3aG20SJ' &j <݀ 6X`H +}+:DmBN:PNpl|g[z<{:25+.l7M S5t~8=NtɈQ94LBmR`nOm}8o> vOhQ`M , [2FuD<0P }h<*aK)R~闲û e\ UpUۏ4zɍ9 0Yӈ"dRF0ǞS`ۛQj|>ɲ37bb>lcû SԣH} Wohˆ"HLUkaQ z2!EDjNzLܭ逮ԧ2 jjhXGRڵ//4\ܲ~S՚J'kDq8T9`,>ۇ;Eq큷?yLS7 (Îu@Qkl(#z=\a1Y ̩C?QYD:M,9eV#? }jR_͑Uip=8lTuc/_z`m6|KNR}?ןK[]wd׆dJf2[AEWN™Rrw~?]p":{;I]9qġc+mET }QlJD fW,8(ID%1KҦYu-C2It0זݨhhf6Xޞ蟽 Sjm]^1M2,;"L{XLЊG@K;xIvڼ'Im7KLk0U#a lAoo: "3(n|I4[q~}r֙1P "?S0 ;Gdp*1l˃ȭ?qWw_"M=$-oX㴟~*SxSWr+ZovDQ5%USGg6[$W,u~rƮKKyhlo_TZ =ղ\uuao|̑kI.GhӲc=cƒeqT퉠Bx5aWԏj![L@0Hl)3s 3']&zM$:6kl{e0ee^{O==_~u0VhK%PXP R;24:2nt- ` BZK1SK* D y;ebAUJ: ŀ\?oz'!.CEDzW~SlF+`%$?&zխXtKXJUS|8A)p1Ѯ'Dn:=riRLov7 9S1--ƘbT (k#qvc9"ߎy2 9ߝz߯J`cGLH_L#sǒ:QDwEw 1Ř eC{,L-C_}#FwDc{FD|irtQUy:} DBµ'נ#Ap('klIb|q6MQĝuY7M#Ox:UmNWf3폾8<BݺIQMpXZ R;mueoB_,V=TZVQ}WvugE 8`^ڣuDٜRDI8[o} ntG\{qiYC-&^(0/ȽhB=<5YFk6F9祃nQ}W|8a*YC Ssbފ*oFF&/Yf{eX8 /6Y/ $Q+Hlyy|b#x~1P'OH"QL= _7RsV{=1 JXɗ9=Ž"O {y凊OBȈE uSCWAVjTS#yg`pLΦ`}[ef^ݐ[}\*t 3PV Ifh `uXL_6Ό;0?2>X2Da[wXD7w9|Oa1qư p3{lL2Q i;o:6Ib6$c-oqIFsJ ~g:X4^%Wvv2Vaet=Ж yc!H #C,lN2EMS2zOLMq[Sur jiqg/ mK$bW~6mVRKE `3H2聜kaJZ@Yv'|0 5~>S&dB,[6kPͿ16^_aqGLK.y7X>H+x/_,n9Orо9K=ȥ9]FGj" w NiAD!0wtWٌD@KQwqvG>ٱILV U\f5Kt #I3UD q͘%hM2%ROl3& cz\$KDKg3Ku2#C~ JSSAp2C#5㱻.4pX. 2:P>u. -@˯XWV;SYE^M] Xv,L1d`Ӊ 0EgF7 ,h;S/AOx=}QiuWUyDݗ;!<~rᴻo`@Ԕojc3|~h*ZI 0ե.0X4ʴ M<;stGS'K sâϻy~GeFR8,h7$0`k$zGG8s.!x96RC4 6%J+wzӝ0IP&LR\{x3vCxcaҋYݻQ$†bVt\- -hS6nN }f1 @g ֊ G(+|9vΗ){?1ίG᜕S8j&>8pUL# @ U脅™Տy.>ls.ο{^R=<X*@f`#, Lu/MGJKnTq=b5V`YZ -h*h (o[B/,, INv!IA-zPxte rp>̗}.#j91 U}_ٹymW #)4U@|_ .`jiXp2Tƿ+#\Rhf0Ư߳N ϧg(ol%S?*eJw{~7wMC>ȈC;lGJ.79H#Ù PzT,*@ZNhR!t/j,=O2k2JJy81Jy'$j1DxעT.EhQ2hBT:U}zlHŠ{ID~%~-[Tg_p*eAw> %&Tr+ >izʴbWFk xS/|:+j\ 9el-o(2Yz徝jlztT#htGiŴ#ތ gh)2. b<0(|DQ/h340`dV/Y~N[.~^(X6+[TeG2n:+jXBhΎ޼}׼OAUKO6|H/FuvYOAqdwFpE *7_zee%56 JW._ YVas|yӿGg!M~XSxdk4{"J6ȑ&S'BhZ&[G%}K;cEBzSk *V3Iݺ6O[<_hjV`D`]">rGx`KNJ(ƂDEĵNZ;i4~*h-u

lY;,T@Ò-S[ L9l0<|vcV綖񧠨a劊ih abTdq$fк< ="{A  "clV A`F,B[dYG_YM4#˚88:M@ϳ`> GmS{?%81M+3A0W@?O$SbˠY>[[m/v*>ZN"^B;aPd)7px]֓ O v +mbAF/>yX܆p6)>6e!vߞצpqy1j 9e* -@kPE2Gk2{L=%HVDK? @%8nRIc)W hfэS<30ODxWa=lTȤ;Qσw%pהU1~"e%U&5S W^~DӷG=PL'3 @VP5muڣ0)vTi16i޾°l[?;Yj3:J:ԥ&i ="گ#5Ɩ+/nG6_ஹˊ*(iSL⡊FBD$u]-W>He1Ԁ$4OM~sí2hƎQ![ V8+b4V3Y٬h5"xʒF Mw[+,_{diWKh6?t;3S2v|!%A[b N9.6N"v/df=ӄw\.=UiZWw亷dg$8x)jYsy%9#h<᷽rD܀iNUBF׬gxE]@a&a |"3QgXf,AQ9}mq)G]-eer40] Tg%Rg6;x7f<\{Ug)DpVk*D ( ; ~^d|,/fWf`Y "#\Q 8E5h.<.*],sQ( o> HsObҗDeHIܗG'0'i !5MkVwF&D s^4n ݧA [ Z!O(Ò+a pt܂$aRӉ|ƃb;ZGgM 轧EGG3(MOQ9R)CNKx::dd]@vI1ȣb˽j?@ D84  RXE Ye؍e a]NHmNJHF y9 CW>,jy)ł5K]ԩf}uF+BLDGT"qDbEDрjqt&Tc)LrA)4$gݼqb?O5hR Öj(LIg͌$3}OdjL J+1Zz>_c9v ph752[IMawΛ$OO c!'TDG!Y'I6 ^5'3ƲM,RE)@ )+A Qh Ī\l8sZ'rD⯂۵@rߠ nroQŧ& Wy zxu +$O&7ݎ:QbknW4.DW;_ӼE/i,ص7g*fk7@ ,\~5mdML#@(eMI() )Vo \!m]wBʎͯԱ2'eGqrlĩ}qZnC>W+Z*O[֊#W>ZdžuA)HAŽ&Rc@F<P< (t4(&V֋Ȁbt)?>dk, {>|Or3rKNQȀot%v_PlaU3 NZX>CӁn_~oصg~ TuNÜ+l\)O>* <]45z}NE7pIb)ww4>RlQgy2%&&l`-`h bR2v$KWz'ϝIs~xwlBD>q8yrźJk73V)J,s0mӜ"IHkpAr܋_ɵ(MM%y9ppZ*ϕ0qG@ƯpjLр e3W/^qwϧSw9 Q;jShlEWdU;#T3n{Mea6]j/e )CX}Z([7M0YBdWA5 '&HǯOu혬PwOpYqr|F |TQLEFFtOvzfSl:r[֟>ѣ1xի3B="R"*(c׷FL.jaw&z&q*)P%4I)ǤsّGu G@^O@(#P71A)Q]z'jP[a"uf6˕M2Y|,vdL䞮 ҎNg[{Ah̡x!qV-NOv^w[瘎 ́H Ԍ2<[QnFGP#CQemx lEF&*Nc ijT1֓j K?'7}NoNi9r󣖺bdzFc^>niظngXe+ȥl ^iVL₍1 ,ACPđ-REA:RQU^[OB5<[1֖Z భd`? e@_ުd9*YdX@8*cBm(Z㦩ߥByT8.S"(:bX8=)"2 B4)V@a8sI0+H /t4`V_; ,T`Aoq \x@7?({{ >ɛa8sAυ?tHdJ0_/J% {|!#t~/WA/zIO&dl扻rSDV4n-, "R d6|_P6_d~x>259C8 Unc[<h FRBմ*ZZ>!ӥCU%3h"( Ԅ 4^:D(%eH)9^fRUB`A|9~ܟ}%@bKSnoH'']S1($3@dc$,I i""W'JPU׻ւZQ]KWTɰ6\M{'8FZ,NH#xj*!*{% ˀ֊Ittc 0JDMߝ$W;;I̅~E( Z^1F5 !O)'~Jy;ڞu q~"4( tDq^ ~\NI}xh/n#}$T0C|,j{ii&g"d3F V݂y,q&H-=.Ze4X^;F _`Y Ϥ@iK؝d¯5́59SIA!%Y`92ysnlfmªSkAz~,J6A$"Y@1"B+4fqH$,1W1?nqquZOƻb!L fwxGv(8kZ:BqQ=O\`شOQ|}SE؊IJDQB uz-zգYxe"㲐 f:<[xO??}y'ld4Ki8f@?ds+Q##L@=G@ަBq[]r^%-@d$IKQf1'IFq9VhC(- ?E8l"sgYsg¨[zzUíY2Xӿ=+`r+`>R/ f::pfP+@ 2t PU:0˻"5O]+Rq=@6u\+5,4 *#[.I:+"w?ۿ~6 T&D9LkC.il;8z||ؖݣQ4V(p#e^EU:K%~S{](5$9=2Kƪ-٩c"1DRa[n+68jjFİi0#[gHJ-Q(Պ4snb㓠"B<|k:$X?ۍDGc݂ $R5Zny9˧>k p'sB !B.5s%A/mgI8g+v$m$+h"Ndq;m)QŤ5N?:θ H.DeaE8#لF2"x LZ&}*GwLĿ|vYn3ݍ\30*f%`75J hO,JIvHP!l,ʪs; %dq5 eH+Ju[ .ӛ)!VkӠ}9mVS/?\(AʶN^Q^o^z{*d9 j܀v{B&u&+Ѫ W)m`j*%b8"KT[r . jag 97*98p`G &m# rקY|JB h)0Dsez[krti eŽB@4%P%0ZXl+]5p60{cZv4@C *H BD6weSt{526떏;9}t3:N-`IHG"]$ž(jSbw< }ܼg"D]UUXs(Ţ#xY"Vw !4w?q?BI@ UIJ T3]չ䓱*F EHA3: -)d7q%6L1D, )$*BpE`*Gd > (s)Rx"%Q_TjczGJbAr朑5R!L$PF 6O! TK; IFODt )(a@8,U%y1yaʪe/{;y<3@ Ъi!CGKXxax}רy P1 "A+Єܒw  ( /@| tAmG]c!N֩D&C+'?^ (HLR}n հZݡ>C}! %M[* #VD Xwbt4nJ>gyɖ.nּҙ Z&DTueT1:c] dp.p4]%piok ' `;u݀ɀ6 H sn9֞io]/9Or6͝UnM':akye^K[8K6ID |ҧXUpp 35*n4z(T0,}VJͨJӽ1{ժ)KGlaA5U":.]=J@3 A O$CKϙr1vX%R03vtW)>"WХgZHFYt/eZJu*Vڠ 6K;4!H/"3Θ ~Z3l@⹧ukO"$*C#uC'o"aQa >~{ .qQ/K$>Q`M"c͝Φۗ$𴍐4XND,Yb ˾ԛOe_I eCB4ӜvI[VsS(<5Y['TSulڼLqZ_F!szi0\K>ރ}FE0O01֚_qBY<:SC=5[`w\DFU(5q2ӁU" !?gҙk_t"ɻY,fZ1`m:MhIĩ7<mV&b&IC -LPM 2}M/{nV[Y3Z$) bkUeZhXmvgo~Ԝ5J_ni+Z5lk.QPAizV2wpkP#;>IH3Qk[G)+[#%҄ \Rɾ77%%ޖ4o-T_i~6sCf.D#h_\ޭӔl%McKlB>AK} #L 5Dso7Wl||N&>6HB4*b(Pf'"$0C\ agvMwhtu4\jfJ) 0 5*SaKk(V`X8@ #?L  ?O!-@ &^4T/XX#`@h |>{} -Y46phΛEͷ _LMI*GӲ$!ж%I$Ta IbX?{c>b\>vG^!٤&,Ff F604CD:BEh-Pfݹ 3&v@ ZS_BC0 V@a2EC% RH]fb #NMۏi DCLIF &ZC<ݵl;⢪ҾoԆ*Bd#BH+PeR~<~BXȲQjB2%hج(q^X|m)Qx*8.($A*NТ!HdDjH= SBafeAY*{-dg@tkO-`H7-BsvT $@ *A3C V3!^/+=}lQRWm[cDK`Ab 1&lT WUtqMGgaQ a 5&0Xx'fvFQ.UH;' ^忁".tf!"K)`ȷҏF*nyklwVOpR@ ~nDb SY|F{q%\\4y\N'UX DIzNpd[*%y3ިoqmY{{.Io|ٯ[62_+kx}O Â4j5`+b&`Wir[hWQXHSk8En1%ߘ귓ܩ0_-w`&;Q?6-1EģvAt\p13CD!9^yHM\ ř^D\҈o@26!,X7&9Jb a%!0Lqv: z }WݿY&v33_>ח%£7}tubܖF210`qlV2H4/T|A}]%q:7HO*G/VHяa5 D4H&/[O;d<#y=g?J#r*~? 3D Dfm'?|9Ѝ̘,A |GI&($Z<ߓ>N<zyWAI@[v`CRC"&^`);2oSf%OFM3D uELpʩ>iH##$IQm$T1ME 1WB51ƍa Y‚1 dl)+.;2d>.DiMX껴<֏"(Q`Ip[kXTHmx,\*rɑy{Flcw߅˲#WN *Hr2Nh0С;5P [Aq[Ls!*0!8[ d~DGkU뗚 f"}|5PUpPh(hΓAxoZT$%T1lc'!ƭY(#Ҿ[ˎꭝE]PIA )2"ZILXD0IFa@h!2x< "7pe _"QJWhVE #!\m&q-\{Cƥ҃`! ĚBK `Bv|C?zvnGTD| t%!ؓn @x YzA к4m2/33 2k*F  "eFaDj?]udr ڐP{ ~^A܄vgNA)"䁽RԂ2CTCc61X%G 1է*Jټᤚ$/D)3 D~cc^t78wlxs8;>RBq557E %,Ѩ>oKYZkƈEȵTϤF,0?(xO2+ jp6;]r.amյ0!H  w a PR=c՝~~DRC^M%:y š1/]ڠ"ڤtrx{y\w:nPھ-A5)Sۓ#ۚX&Xo/-,AR ێh)< zI] `rbcd@[ [;_PTO&a/ŬP>-Q7az Ǧ_r#!(p[ݩ@.kvAHj%Rx;'ѡ<0#2#?gA5xR ԋ?3_Ra @;Vk"4 VbFp߹߱6Ԟ %"$Yiq6AN,e*)bD$SW*+묷y aՒ :dR&m3Wg!&Dg&0CDfEjR  "$Xf@%d:@}꜃Ko6X5ǭOt CIɚ+0Dcmʮ2qd|>UrU5fUܢ[Q拸σz?qb "ēEPFP&"HSLHSz/<ҫ wt>KiVn59 YXjfbZztu}yWX87n꬝!7ysYQaK32`=Q+K))@S:AX[߉:I86j ɉfdp o6Y[B\SlWud܆vﳯ3(gAh,Wl1Wrd|pL@Zʦ p0w˺c+b9k%aсsK:_tHS NCbHVsdspgo۽ΩoQIjb0BA#5Upk4jpM|n{:f+owax@v$*ږ\x͌rrMO^UzKG;B mJ/aX c Qg8ʅAbun^P"8(i&yU[uCZw $̎'/u4gvhI%A  `?]k$iH)pFP QNLA#ߒ϶Hzej6i_R\n0j}/Sd`X|O'M\5v'eJ7!IH+v5pPOB53 ` g͞ϼRadô!>Ñ.(4l l4R*rZiːO-]箱8T,c' #O1yɚI?+ R KMHD'qVɥp0"G$-UZ+Iɦ5+zU=p>$55BUkݏ7ew \Av>gKu '{3CCa5w6T5m2цT@@hQ-q3|`>qΎ8,ӭ%\!& " 'HZL2""0J$ۦ72Ռ"SPdet'92QMs@'>{$*(4O|ErDX!lZQ"a#aFUt@{8n/"E-J\RIl "  w]6[9!*IZ2P-!}*7wԹekW2ܥ&>)#\vF:R/omr]6{wʚqlj 8H>ѧmD8 $3\Q`h=ǟ JcER:=9ͩ;y[ 뢵h!*ditNwϻ7Z-}cKDK 6L, {A,Wyq`)D~8XI~UٓE=#EnžgEap$ 1KYW}=?P'/EYWtIGf;M ML>r4RpP_4@8{QJ('E#%K7'&Ά9^Z0YTgiYT0LN Ic1aD8DG qo"x4[sz9F@LPqT eщGM%H ɡ(˞Z: {Jmo^X~'c_Y[ 4%p,o`=9 z 6୉v|$iz ci;zQR줁L/sLOb1E^Ka<5YUػ{@ӯ]AwoXB ;i/*C⡽˩; vD"C+>vf),lI%W/3],+r6 zG/ko>RlY$sH DTmSIe.zP"Qꘄѥ6x҅cPAԈH%L6Q4A _ Mʡ'&4 `cQ Z]R\R(5^N|خz}2 juW 7l>_:jbgu!3i[-Wb̪8ffD fzitt߲O_{ I kJI )}3AcHV1R"eWj33~1结i9+0Dנ"i &C(>6zRT;Gy-VJ-N8DskE[(b)3j[ \g״SOUA+xc *$@ SmyܔBT?iZԡ:E+:nE, }fg Y貀ZNpjy?Я֠l%`qDܮ 9"ؑ%DT #9ZG$F̤Nd,2?4 E%@,*H:UIz^}A;zm)A.0DkEA$5x!i;(5j@Qn0Q@~ogn^,,^Lэ6FNRr!&1FoÏv){Ut vǒu{w)Z|ףw+9>8| h``PIB"(Ie; -%4 ʠW1PHd7D0;^5\y(z*jW)8$լF`JH4K7H` c7i8s}ѵv:)MR: )yX6@Ӕg +dv"Imf{_O/;zgPEL_zL)d]塙ĔKJJ~$E L/JwnK8tّ# ?+&V]DH~`2csec\Gf@Ib?ynH"1  B),/mWAC1#J|6A!0F,ֱle%0Xd&?#"BVf`$Qҽ( w3W7;iBHl(̖Qd\tЉ/)JisdSQMIӑ)K1fԗ$l'E ь)J(!m͇P%sS[JkH%>\$vMcA!CaL?_?~W2D"*8:h4.654uU"~/^ۢ>?>."0g0ڈݶo%_)$ED|s`RHͱؾnHNM&nM|f"ϕY7XBʃOMwm_{EXAU@h%C٫`,rA,}`\[KN!KԤHAq -CKڝ̕2!qAmYqtN(j/e^ CXex$vB93ldaT"8)V!tLT&2UFJ hax2S}8^<,d(QÐsA&\j呈&Mx&wT i ++vp&0]4dr_mƟa?_eTzlDũ H=YDvmV㉚9^շA/&}Zہ}|e'No^lV$)qmʴ,G!d}͐ `<%ʟ2:徹!e}P8d gJAyڏptհEH3yC(_6r6j{m-_b' e+MSLCyzP+4+ "dsJؔ@2TU!#R}UTf4 $0qJոm7! kcRNlw'jir?bS1Xb u>tKE5xEN9h$vԋRs(щD.B}O2x0@Wk"eQ]8=|@O+NـbaYvpsE 67 e5&%JJu6)1nvYj * kHX+D \fmsY=vkW [hjoy`V$86[mM1\GxijNa[<\u\ >:|}a,09 !•s>8PTm*tT$@" F@c $ib@HnTB>sg;RRX$#zwlT<}*?PBakGg 'NU9jӭ1YW]$9WrSSѝx?vc#\ @x u4˓Ts5.,,eA5™{׷ﭾ!G=]~BPp{tJkTr8 )"f%D CX(51=CIW|iiYYL7Pt %= ,%`h\lGH|/ ,(93-O?(u\*3)٪zU0!@7 +ƒ#m6%;zQ7IF@@@8qLlL/8 z6iBDSxc=?oŊ!,!P  hjh C'Kܙ6MD#**,ӫyUk *8yzlT)j Zb#[Ю3w}8T{QHp> m(H[C ӷ^S项\.ޔS؉>HG-:7s K-~ќJ*gf[cČ)9m>ҏa9d6g9 K+3EL~'Oj_PfCzWtԪGZ]2*+rQ6~V%Uk01SYG qSurmA u xէNOm4>܍ImYѻ)+rfܬ޾M0]N @X[Ч#S|U'hK4<`;W..~/dEWs!#RK%,Yi݂&tԺmh0˩1U~ fPn`JРi_K+F?&6b>@0 `; w_ #$H#[wA[F-js{(tGl|VFeK$ ,a7 D!hA6D HQ)G<]y ȇ :XUnLÁ*@20  f@ôr10N,-#9VH2^3(@der)@u}gjxhk 1bt-n9D#SwEQ}X6oO̩݉32BzYzu;>O ZEJcLsS-dNrg*~&I;V* 岲_~t} )T6-,]Z8K ^z6"<ӒX.3^x{DP4eT:CgGs6? 88!b( fsp(c ΃ `|֟N( )/aYfFr0J4A2`6 G>ݑο7K_+^' Mz -|na?,I@Guz ?ypK1K*yKqTIih h*32!'vXD3l%,2nѼQ?-iRg@c!\rY^; +Kh+bd'QFNpE^ 9<ߜ8aS8j~q$IQ S%.6s4d8wcDļl-Bcd5+_//fW>/h)mKrD"pr!.pTOX5dp˼ ko{|-&/Nǥ];O O:j]}艦Y/o pQkC9AlQ ITOla9j]6 /]ΤX^h*XavGڶ)錿FxDHdG#10=p'B=zudV׻Ӑn2B6 >qdS{>/ׄo7-ї&4T /H/wKCȕ>]ၮI‚z܉ wxNGhR/u7+I}@vx[.z퇋?LW-QOݞMj]movթffivfp|KBbhJya2wu\W,˷OK+ 2̥-OƉUr8I(!bH9Yե(W,kY&(* ]AnJw2ݏJg.H)X?-yÂaWUkʙ!GݪG>&eulAѧ7wSޮvע CIlG/#&ӓVI2,$/̲CBsW.L$!y(/g9yUf,uָ:/hB8|To䙖V(~WKGysh YWyF[N|v:l2jӥDȘB&vO=S-NKpX"A#uj[5uq"bX&tjsB)tHQIGi)ځxn ׫UT+[Zy+4Rl[z&r!+͍"/k,4+("2;}@{~? I]\B>.zknZ]q ^%K%:En0Rc]LFIV> ip ñwǤt.5jKnP6<b?ڦޯZx֦_ol.8C:VEEڋ_XX<{y]C徒l[I2B$ ,V2^LVmsA! BDP rL򶬀"I[a˹nU.y6JYU.kD4Sۙ \Y( KժSDl6WA*.Z I٦n$PNI#a T22"F` 05!pYJ&>̓u+BfQςde`"LJt؃v.mva};= @&UAިjQ% H{Y.Sqщz9GҲkRj(#" 6`*Ec+OJ{C߿.? ksd˥ZYfNMZڳY~-/OL޶%:PëodR$Nk}G{B9}ro)">Yt HM4)P̕ 9`@pT'-x@5e(0{C@&U9 !Q~[{ 16XBUsD\%r"Jh Kծl4QM:%>\Ē/\%@i@H~['h^,*<_;@(CU4`XH sksD/ fAHimezm ,-j"y(HHISVm<+t tNsnH{Vw.D3fyyekXD pZK1y@1"х`@(Q+H-Ok3 eZIOU%ʞ v{=tr ?7S-˻ \9+pFj;Kt~J% yr)ξuN}!8 }Ԣǜa:LP+1ߘp4DOc+/.C+뛣Ϟ}~\0i4iz2n4Tj=jď0sdr`V:G0DPN9#:dCȅ1^J "Lmn@ՏBV_Nwgcqt.XfUm*1y8G\:,xQ[!7E آr-{!U B楩gkӨz7I9—zVt`ӧ$y(ߌ>)Ŕ:?bNӡx*mG[ =o6;0e@$'fmkYg~u뀣q!Å8 ( j>E^8,stIiC6?2 Kc%_m[pa<=iꅀե?J:חԛ$[{Siȥ$6+]ʎ~vÐm.WW~&yl C\rP\T6ñk. i?W7<8(f҈.F~BG4m(;W]y1;" ^#+>2JKwsaWڤNn_6\grn{p]DCZ.w%"1dOTNelӷs HП1 ܫ%*])V2y[}C;Z}i(;R栁&0*L#z VlQz-~;~,yFwʗT܆iZT옶ejԯ{A[ϯ^"VLyWqS?\;l\MHFX~Jxa'Z[iq?لP?.Y=+:kT}C}Ui[c=4%aY"}L6t@&ඟ〺~ꯍm*U88-rts'V$ȲTc["XR G$ýZ:*ɯMBg.ѬRD \n %\3Y[ہ+Wv )'\{_o1;ǽCzݴ1vGȗ84ˣ+HqTc(閬q@I_ټ' >]16^'[y`އӎ 'u޶)ӧw[Bpx +C|wg}[Wa`p(N3Z2O|gt7,K1 ZF/U#ڍ75@`>:A,%ǛT䵿_9vzH;',uc^w{U*׌)tPCah?K'h:R>\tsقm5UVNcQ3)VEBtLW}/r"SN)%ܑm+=3DEDzws倳΀sx1#[ckj^~Sɹ'LGGPF˦TReO;8P7z9` B[fMfKSEg`;p:xr3ʦ`B)J(+GUs!Vr^RRm=Ҡ/,e+b?8oz-rPad2HBpKbēwk@b \\i09`mzKУżgXBPI4toG¨VKO6NHџjM53W#D+YuFƑRC)A;o3HE 6i @"3MI%C&8WM]ە[ai#*/='$*i"ʔR189Ιw v[DZ>/L_}5qktgo"7%?hB37,'w Sȟ[.c_@;yW l(Ŵb+ӧf?/w6OQ*&{_U",Ӷ@UfVQe D ~6XA8 -stA*&nI=TfS(ʚƉGp&lѳ%Bd}2\;!B|@[c![+n(ͻƸ5Zu վ6i\jk]'l,x AiS?JkĜJ3@K 𚨠6&ߔRhvPO6=9}RB$gC PG04Eg0)lNR+F>4**W7Kk BJ_i6:Yvg~|OSK|۱YN93* zekugS8u>pBex Ŀub~">]Oea9=\}R~ҧl=n<97nyo"3Ű.mxdLjX> {SA([vzؑ҂Wx}|dgLػgz5~ut÷<qyv8bsZNN!x"X՜>zR8Yabd!nۮw:wOn{`i:<6T! RȤy$Ӎe1KJmd4vL_>Ineld٩'{)i_Y%{ r4Znc&r!GN%Wr}V=Vm &%lyqހwggڟcGZnCBODm"5丹<-9?l#Q;$qeԡ1W7&l- .+Onf6^V[6y5 e8ڬZr$2닡FήLAg0qPW4~楳'Gu))OCE2k昂e֭,s$8%U5PGuMSբsnsP[ZTՄ\֯};de(eݩuԙ׽jel5=t^X 9ylp&甭->좱&T qaLHԂ ɦS_wMnU&xS XcQ2U*oZ Ny8ti)-Q__/\4iX*X.\!Y^Qҩwg4 nâS~e B2(zѬe]" %]pQ5h9$eP&BEг=9zp񣽖e弧!%:N@Ԍ\HZ`0H>jC~Sd{>s3߸2BxMtfu~S n4yu)Hyۥ8gBqlF$@q:`p=Ѧh7`I{ %nf$4.]/y_ 5kI*- 0PhDi5͚ms_ѡ_4}Apu]297ίmǑRKz{iRk'-5* k@9vM^χ5GqŮ/ɭ/ɟzsޫjOƌ6 O BeۙL>o#?MbE'v䷹ D=~{׋関艭/& 6ڦˮw\A433+a^fsK:At\Ő ,t0R|剋l¿ .zكoa׭CkWy{8*:a|mCO{QѝLv?qY> {vўf 8"|&%j)ӼY({/>jQVxVث0Zԧ2]U݁AF+O]]y?Z<uJ u6ܵfuaY\׻V=u<0y&qf9:$K:{7FW n`~TSͶՋmU)D<x伈ʷ:a1=Бu8źj1Phưb&B)  @ށv'{#ǩ3%y+b\F3dL-㏧| A.\G!* ?w_9 UoN-E2 >$mMڨB4Q]3j;o8IrKMt*_IgM/Ipٕ&׋ eyRmz}G~꼺w7 €Oy fm3Mz~T/^ݶP)xPGcCIS!L($ ;Xeb?ikm& iSPbDH]P}//{k)sJ ւn6锱hkl{'+ŸߡBP@T'%'fs.T˩JĹ\ Ր< ֑MYu6Q>Yoo[{/e}TF&ˀY\CtB;=LƮͤ&;5//ۍ^MkN<:/ P$ oںb"BTtܻ|l|:R6uN|KPlcSR Ղ~47X!<2xEg ʻ5n T^;-9[CbeyBl;T:om! A{d-Le oFl*(|+ lwzh!L\V; u&Tί@%ɝoӽ;Mqvͭ++]|ԁ'n.o}H <SnCy͉T]>wIB0|CI R)Zn.)*aHO[Eo:]LXTd䍎g?v*îIv.Y25m(/WICሜZs31TwSCX^8slʃl`$.|6ꓣ6g%B|Б][ zdׯ>gܖ gy k "ՃiL@鏅68{.VuA! @1Vs/<=}9 |'O}{u~U !$y7)H9hyүpD_ڍoyHxfx~X:J 6`\Vt MK4rZc)(QRh::VWACR/(j,tyDPz3>@ P"N6 ;wO|ű_z752|!J;>s`~'?rg䫓q]xU:j2t۳;Kpۀ WN,0"jΎ߭1/{hRqH#|Pü=Z)>,;P p4-/͏  @Л~՗SՁ~k~r([lv/UXuPj?~6M7\8 US%FoU/MD7xYe\rUgWFw. cFTE.s@e(Z;&d4?=y.TߵA'rmzÀd?mA?<^9hCFczEgw12텤V?\[+9۠'=;OMƷ\C ۴:ה˞i1t{.a$O} {7]ٌTc[U xIGd]3企Wx܄jS[*vCHF.5UDvt<<˴swӭ)%?PNӴeu᏾nUU6bx-h~ɻf@ ;պ0$GEo5e<4g޴b =n.uk7=P8± &͈aQqOy6 klٳuʆĦ8PT?k_.+v?@ pA%H6S<}[Bwc٠7#MuXL6L+h悝h%mB5zs׿wO{BԋIPfcVR]E]@x_1ڶ\ oKj]u;/L GNJ'ibW% y' 蚢W?_7ԑzO;! EɪMAOiݘ<ƿ鶋 'ˬ^I&K_9\y'ꗔEHe'kgI#~+ڟ7uCי>5yO2n#;UR>/2H@ ܬG߇p=x{Cs`Y?3+:c=m]TZ:l_:z3 )(xv7F$y1kH50XM7H;s.yM=g,Ďo <`{b'릱5,xVQIHq* 5AJe,{MPdBv:2 x͕ p|J2[7O~{ov3WUg  Stdz.ƅmw|I令mE"Հn @fpNy38hF$e_Pr*z^q xsKV(v>yMOS20N R֚*;^_w՘ݾ< ƪ"Ò41iZus:Z&|Gq0.ϜE#wvà3a<ۻ}mN,kΑ*uЙ#U޹O.-";BI_n*z۳KmÝ": /@l}lc.!9Q *ٰtä$?a w~K2-ߑf Wr1SG!O}:N۷Y7y[I&4 rcpA3W}C9JO=w&jYukh}wDx?;1m?/ˬZ&*ܡ^ksn }L:&:e8 #pNEx̲ؾN6iN6&$LTZE@naSgdxmSGJ2h}'4!1_q۸_WkTuM"OyV~m%H0 FjBY선| d_[8Ϭ'o&w"![^!m;e@W>dlWgIo `4.TaW>2f;F6IY5ߙ}72봪tsYѬAhDh ]LaP!w;PsGxsդ;,m\=Q^wEQGx买,[;:R41h$O:~|.ÚܸYCmԻbv]I ᚬUK&yY %ʉr*|v[szkeƋPGwp."عWu1$ڝdDh=78eQ`$bF(4* 3Z?K(r}w,'l;,N= {c  ^dҧG>\Jzfw>#;ҿZX2/jJomXp;)Z4Y_״ cy)dApY/aM$;㈠AEWؙvIY Yz3-O~bjw/ Β=-ū^ٹ\c i 1 m[FxYOM ?; <Ȕ.K|_,6!]\w͊bvVi_s~ZަiyL}35 NyZzzwB i3I?Y Rtp0al~-ґݨ⃋3r-C#1Pe @5G4L컯?^}Vva@5Μ]GU EXh%`oQsZ輛gاYwnUԺz AEK2e!+cYJᏖQs??Nx՘0)v~eOިǘA!Y+fFT\X@yc?h9^=j'? Fao/PnъPHPN0!adR.X}pk{e;MdK4L @ j2ȅg6~^ SҰ]'w֩߷h}KNqcOY#EO2]7lFұx:{`J?D*SR%hj r읯[_mPGߒϴj64MV6zrX^ЏIn;|W7:Si 3[(uwߧi~ s6_Uxq7#X@ {hKz_/g꣏[l>x4Z?ce̮qVB#^#ێsW΂!7 G)vpfHeW灙j Sv;6F&vo0E,ǢNrc? I'kzX}k~gj56v[[ ago\i>Vؤ 4R-6l~zٕ@ rfMA|rЇqweDȂf-]ͥ5`n@# "&4 L`Z7zj"?wwSW{t G{:< ,RB^t(lK;Ws{~>- !]WDkingIZ0 E!jqѺ-Hh xXYеsovZc4=w}Y]{17>/P xIwnjc4ҙw[{z, l/д #C!ABKhS7dzk$\a`8 YSo-SgϹo~κX%Ez [?Vei5b6zꄟS)B-{፼ |KSy6K/SF 2>8FzMk7:1 np,pA m'ߦC$m&Zŧ;ʞo~tr$tgH(7/'%:(:b@25)͑6:ژnnw#7{2>w$tGb(ٮct0(pz D+X < 8F|Ooôx}1_5БXMl:|ɰGW/z: ܺt Oyb8'8K NnQ4TSɰգgk;W,;N-ir'oKְ<ͤHi (@ q%ՙDcw:kl`W6@G`ʛ1⏂,"Nc$Al4v;k#8ٕEaD}?ċR"ee@@aXӌښ}H25z tfnV8.)F:J72hqɓɰ}O;t7;(ޛ K R,aiA1 'gڲ잍;Nw޺ JRB<$=@0$H#GEd8?xO;-R[^P J [>2K7G_V*  h>o ˓ =ݎCCu3UD5c5݉^s-nϲ3= }uÅY E$%Y90b?3o-+>}["3s)(Nk{_ܠ~fe{wǼCG'+scpBYd=E&6&ɩSeuKN}݉&tP` 3~mι*s[5umQ|04?X|IY^e]BG Ժ 5_Kˇ~Bs ci,GLs+EhM|Lڃ-OY^c}|s d]<0Ҵl "9 \-fuU9Fh` YP\ dŇ:j]y5ԿɄKt҅ 3<[td;qW>j}g,+귝@҈;?BwE|Z5cȝA Z ʐ y, 1c873ǎo>YyF~/[(Y[;o=໾ⷹ$KzyF+ >n;o,@&CF֛~ÍC 4p/"A'埋71jȞ.Zmv݉LdWKN v` NJ>_`b(`9ac$C"\ȝ5qvvO,N2b ޸Ch|H<] f:!zeYGJ i \ft":,A3NSE~= 'H,ok5؄u3,ڨ <$V7:gX*h8Ea9jչj%}xȟ\ Zjbۑ ޮnS&@ #iB`tZ6㷼Q&ȟEI1,Y6 7rmLJF}X}o^7QaSx%9j|]eI2`.kr^kNn竔 4kc~'-$ $֖-]EEY,l =H Un9g"l*id-HvDS]e TWCrLNfÂ56 Q"G#+~$co~s Wb9*^"/EYP=FEaDDCFCMy/6hT,?y$`rxGRq-!:#4.p,I3 FǢͭ3E¿#E.M&RAV@h(*,s7c_{G SH}1C=}?< +bicSI7[}c?"72uVstL3ʑ>ZsZ8Vlk΁iD$ &vo9Ӭ߷{E?gN~jxERhJ ,K T}t߇ю+.]}#.6v:,~ypw+( % $ntuvwMffM *?`rg<<'X 7KKiyu||w%P<"B 73%XgP޾$-Mi@dE4{w e-DKT(B>pb݋$4kMA]TG~ F]o8d_;S%ݮE:M;dz6 vÃwQL^) /AkQB>74@8 ?|T)j/CԢW "$` cSCٳY ,Ǹbbͩ6bPnYޛ*K Λ|kJ{>tu*M'T`~tԻ'׽wc/SRĜ޶'t^Strhzbۄ>SA ~_Cqܽ _OT=jQMؐWֶ鰷l{n;}ՅƧ۳>g~KTS78X_„thc0muD F+h *~JtؕQt1$ЁTnt"?orG=r׎[ފo:-Xh9͓C5Ow=#(҃!~8Vj䑉mHRM|rn{i7С%3X6_~,h.my2k|sެ;,൓>? D)2;d\A@/KGƅ{^<:̓ f2EzO~3fN.XEOe 5IGV q$BM1ye9%$`$;&A,4#*dF<4wnKp;[թݢr,`Tn~G*zō t(j74&qHut4'z{Ŗ8gqC4^g iS$ ?K-a pdpdk;P}f6h>,(^K߻$k PK[A7=}_46}kcS%T Վ}ly9Ơ,&NBxދ_]]^idMLV1XȆȖQ\}Z= 9HtSb#̹So3qe*<le -UCI&a%ZB锬dV9ȺIwru蘷pt -y M0  )$3M$#DK&ݯZjoEOTCLxAʕv,'D L:D @Y' sd5\?;$lVB&B6~KB:{%x0 P@̔aa ]Q "*㳟dTR[^|\5u Va#y /- *'}O/$͹ 7%+!1\\[A(AM'UУF1|HLZy釴}.Y~s_Rl) F @C@- ٯFfRM$Ļl} #fĐiQYA!`j CJ=|IN&o̫7Օ+j $,HG*Y[[KYe%*g}%aVB0>+׸x%R|f5] B ,,`})شëق)$S{)d>?+@#J,Q`_n0KryF1A ŚDPKqe8&&f}o<&=?W<|`Ӕ$l94ƅA" DBT:A^Ģ{C!lXaF@Tx*{lULjG@+7=X[8m`YUg>X 7m>غMӈ' _#Ğ{P=ͼ FmGJ|$䟳PW;מAVT1UHc&muj f,igV[8o.{dD]Jጾ J#дz9|@} 7F9T7xwv$uU 3snj] aǪԊ_`*ʛ7hwq܍>7g"4߲?L.^[/vY>h\(8Ruv~{UoܘOX=FeW~y1`\]efѻv-[E̢S9Ksos;* _.^)d 'Svf0+21RLP 4`쏲',Jo7B*䧚mYlryg"'lKO߃ ja*ԂO}m{N&IhKw,Ž8;՟gZyerk+esGE ,k'ȥTAa 19ڛf˲y]>L7Sk5!P/% IAH^"ɂV syw{fc7Mf{mٰNQvN~rOt;W כtAI=Ϣ5}Bz OpOY>e4th~s #2DE ZcV_ZXִ$]9JϹsOt|k. 8c<Dkzz&&cUbFhQ@{!Re,ac_=o,X х$#g2b !f k@815%hSMܧG|n{.̟\,%M =JJL0)t(4q&quCŸ>~jI ]ڧ> ȥR.% Q u-MA <]m"uk_»0"( "%  M!.'S?{n7Ho j@W @Aߚzb{Bt0dZf% N0|^ BNm!z5JHDS,({z eNS= OX$@Y/˸1ƠR4`P,0Y5ˮl'$ПO`2 K4*p@a(o3_ 9hru);ITt\hhmĈNC*q Zka8v<!Y[5>~kC/3HKqH5<:GU>ZA{GB[%F%%ytI=u?]73d5ɼŮK3y1)h{4KMbw}WJke2FBMħ#V5۹D0(ՄEU^w)G*@3ܨMɞo=kkI2e'$ 0Z$ uQA|/oʉz#J4ȍz޺II{}2S}' {f(CRLgU;:ld-fۡ,$!gZb !Ik\}唈^GxڍS*cEoyzӮUO' -<_RsTSz%p7>{`+{z)v>k2"~*w+p"yP$5?{mpvzFfΫ27愭٫ uz=ӋGd0չSFhĘK4ߎ@@:ضͱfbq?5PR{b$+[䌕խ^0oeԔBs.l^}~ϓ7I?ݦrK71KI({޹~ހ?CΏ9t9|zV_qnDD}K̒I?cNыj` Ia#k=Q+fk?>#,]T]Nz(ZtMcj! 7?ތ@kΘ~x>M=ll qu`c~vw]z@n{]1yjV3?5t,0܀&Ln/;i|8rԕ J$Aִ%AK "+˽KnQN!ѧ#^ ^?O7Ll>FPA9=1Yh ]6+@DTYyFj+%IAoqfn(. ZhҪe+n9$2:`Ir 0PIh8#Q锖ڐ#iK E(E\ Hzz]3)s, I`(Ӱ@BE]LMO_DzJr]V *Vq(l2F&6OW&Ga5F *m±s Gt! B(4eQ<Š& P "2Ap\"-2~gNIԞIΉ9q |ԔJ-M䂂8ӄ^@όh QL؄OwjŹyNpݩ H\d\TK~j˵^:@*)#'P[ V>BḳAe-2% ^i"^_pۤOOeUXX{ёa")! [agěV֮c,V;4ޛS6 բ#y KAPQVpPH*kVc JNiwM[O64X4Q)aj1L ,¤q`|VKi`ⴁVr^M_v qYw2˄- nHcC(߆TfaLa@%DIw ͵T'NĀI+"Jč(+ FP`pmwh[.E(."H=|y]0 N{2'sb|(HՂ Uk_POAeʸrL鮆S=[+%ƿ c'EFA0T;R9jMlN(qSPޞqt_/ 4eː˼߂"3Zd Y ,-fȦ2i`K_1Pucl%g<$(>C~a*xI[!!'57Lt%8 9[i-Zs?\δNAm(K5ӯw#o^\tM^H'M_{=|bn.8Ŕ0PCVEG9Z:c-fl?}]~Vn;;v鶹wq'ԯړliʖa/W[u7"g#Bst{ %WBHHFn7*zCld2'^hИS꣖PA˚B;ڊ\)s^JjK"ҥ-(aFCZ[Zǒ[5YJ\#g7I;ir+MEv .s2!J+$6@`1`!EL*GW锨+_h*H;bļydznݵ!gMYcsuY|G x/Hư 7L}t«]W"M"^M7Y liʺy,E0M 7 {9ӑY2,~zVLj𦱖)%YB"b]КRRd`a2I˼5ROҭW>_yO/HrPly[H1$y !H ʥBd Av`cgNp_y`hdeq\Gml0uQmv;w^6'%6q'F%4[Fw9!D&"StVP0 J聶xA7J 2Շ/M/~_H'b7fT[ QWb $<*b XdYuT 19"nfwWN5z""@N8+8@w!CS¤siCĹ$6ޱ?%ՂB0DRqt ^}b7&o\ge5g.OK8r,@Cz7G(Ve1x'`] 2U*(?E760cu RBңP  dJP(Ht*F`z F a\CB EqK $P5araڷ5b'(5@(Jh&@A1e\vaиN$j0 #S_j^E^tYJMe65Y;"(0 b%@")% Rb w].J*z3LILe[Rv@| jyL֎,LK9s[v|[[Lf=Z^ė%zm! =)mit=c*skkFC-7\ҕ 2KugG%!>gϊ(F_|oLF 4dg晾0BR_,uuq% *#fie:𞾲rÓqU?>`frqpXdz>b2lO+,P&QpY$_ *ĭ7 D/WE-+ -C'$fUd)H} NOL3cߏظʾ3_Z'WUee+˿zᤗ磋yD^ED;KfÏ/I;|N'W#g8}EaR/BguP}GD{R)NEլqhn,ߍ'om:y}&c`Mhø%rƝj26`QUYl`c漏 ϶t 0G[0ϗ5l`yU8!N^͋#!eo'bͣyf~޹_9a`s)']Et~_z=#z]㶪=~&5vlkbAxSP;oPo "9j:ߜu.ց^-}C+墟3._yN;z/Kcc%(KʲkLK2I/e >6bEUut JH" zO!Kؤ6NEjA {3w8zrΐ̬rjQ}u6x¾SšD JN]Iҟr0w';' K$F y8NU hN-f]> Ͽ; o[{zk2ʛ `OhUƥZ꬈D:b60z(V'g)7lN\R"! Hp8j%bB$k'җغVa[g#0Y&:tgxc@imޗi()|gI}BI+ 2R" {Nyw"|HgüyB+ LA B-M1)bp&4H-> +2O0Y䰍Ż JR(TFĩ `ii)»rW׀c߀PLxQhƉ0+7|W .7@0y+絜i8lv_ Jxq*AWf8Q̐\sgͭXX\&޸H̉k%Qd/FJ% ]/5,mw8X#i_盽xV]Zz8:oyr kѝ P-8['ͫ)CY)VU^r#\ )gKYp8rDráCrRkAwқn9<="К,I@+<%(Tp8Ϙ BPxV`e]9~-q)f<`bb }>Hs3aD1>IIHۼ&${cK2t8xӉ= }{P+W P?* ZB # 13Vvf,B-03%{R7-B yoʲw@pSA( dL8%Dl wkav%U9H`}%=_5~G&Ռ2k| 9l3iczTo}E=}96* BQI|7,*! chQ0ߞ;hb;NVRTHVYD4 84A84 R9 ٗs;̜W[s=Tpa\,Z;HU,yyBB(0+X)^70-JF Ə~1yXYE1qe!eD`Z"u X d |P:1I%xNAD!&DHtFR8 nfH& 𩔤(@> f8+vRR-C hkLh wHPQrq \Ô,!63ܕV*h}t採gn}R5L3`DNʧpGl 9:^fE;WW*\7?BA9!5ԥ02~DWp4{ViaS{3 (L iV~kn[Q4;ͺ/r P?NaK,np##L.Wp)69!BM \%T #;`P!UeRwR-18EN&#ފ8b!1E-j=2Is/ɲ*UF~r=n)89$_=nOPO0d8C&|Vѱ 4rl7vaG?]jᓙ-< Q=KعՄsb `a1J7S$XR/ɻ@iQVBq-BOBf?ﬗ *;6wuul6f(!VmJZhvmz~!žM׹9- Y LM 0(1&Xv.lauDĢ^y PH鄛UYǎDOşj89Jgx6;hX^UabX}@jQb(!Y5Lw[<[.ɐJ9@ ܋ڋG~I=: O*@֫oDT"f Yil j8 Z>j266 爱0Vmz L#9-i 0 2-A˛VKF} BKË,tybVE֬ !AaĂRKNThaT-]}ΤIɨV"1 0 9M=,Ou1 *B0!~ L, ceX *$L"qYD:"PEtCM-leX@$B8f@)x'l.*op$V᫒E֕pj|J(P`P'F[NU  c55CU&MzWs۾>q7v#tdUqT, ADAd@%(A5t.[X耀Lڛz^A[SGk׀yB v|˾#b,w% %آ/q:H挃@U*u:_}gTDh@:BZ pnu番Cpbq"0tnV0w}xn8ETDD(v)cjLs,}WF]r޺^,&`'bNrsFˍ> ϤKpmJŶ*!􂵦+5!k@W(]vCa/#qh]<{E|apm"PH'%Jro!qtЅ":1^*uҝI49PYrkA{XDX87~SL<xgsmսbc2t5}'3Κ~A:2E#X\JB#[vh+&B}3UƋ֞nǭ}JLny3:/=\$ZSBD@-BR[*;p,+1<)|^;X1E8BFF%~lev˒~h!(Y"LX}h7yᩙ<ʑ4bX̳zF!Zh=ԭWmMoͳI!Hp٠hzwݑƾ`/_UB\X o3;ήFy1&n &x^nm19܉=R>_,edQbrh!/bmHKN4IJA^ |`i 6DnXq iJisc M@J0 2xKkHV)X|[A@Z_ ܶ}:E5$ :pՑĒ'3ZcR*ke lR"OgnGO?v=7T &`p1ZH{@vVZ-G~9-UJ& p$D c(ܩJDS^(8r0ir:D5 (̀(yleDQ0%$! 7@, eX;1XVU{/ATM\_%}2i2*"XQ &*{Alrqw g iۻ 6(R S .9&$KpYUϤFk,BʀRH 9N2~ E@$Ȩ4Z-G|.t&-[ڙ44mcs w}|bHj-gV11)dH\ȝz'˘B1 Ȣ B;6>j^..ԟjLdLQ:%_H6h6>]9An򺜪爎bGʊ/̘ʼn}!wPCPˣ#x}3J207<=ZrS)w_+7ou));tFɋjH1mM4b8hkZpx6~Ciۯ&)T "DO^LDBEbShwYl(|wsrtt5YhW+…[&Eɟ ڪN쌪$}w3:$11Şd׫D! }_&}dXDik7'w|㷄Qٱ%m'׌L!BM}o:٧vQ/Eg#$bMF#zYpoS„ɠcw[7}W >UmQTj0 , M`*tvv8tFT9oX-ϼ-΍dro/qb0?' 69tt'NalW(ʋZ<ӥw_2V{ u3B*%)@" 6r/eZaSL8r{AxGqܞ@&wtz4^v!/K ː"o}h/xezV5 e m>O9~7_;^?}~J$t'ˤ$Й( Sc @x]\"fm]h.`) fA(嵋$?줐k5qߞRRPvpctsC-/;cƺ #NQB%Ofui0ѕP!F iB$c*q#A!8m$Zy3.XސSp/:DhbT-AsmQICf} GoTׇ\>dS8=^D鸠9k:?zj  x<¢%4,k Y:$Z८k:y3vʢR <))Z3HLZZ~J 69PDlaiRoV/h?!,рIDn 6q%"yDh"MU 2X>AL F/` )IC%1&;X4 ˢaX!1Z D HQ)t*4B5U&7COtDpbQ?X-lCtȺ36Ug州,ne 3 x"h[@%"b6ڒLN"HjOҹ7"ۦUGpCrr&m{( z%YDL@B0Т8&I뛌If&֦^ R@z܆NyM,hyvfMy^ 0(' 9rK-w=6 ϲ*>UGwԏQ{=~f;DQ%@PH ob0_V5}v~p]jڜIo7Ke=p&| oz}+^ qIl|'U/l|Pb@ܳ?뉅o& rgZt+F@uFص;°QH/GKY\Ge}r~WӰ/*//GW?28W$I`NJ$N0K (|RaF:{KBt%'<Y0\h?0|j%eE#y,>01Q de#8g3^^]h-\de+ݳ;PyP[n30DȠ1"DaNTsfM.W8'-ֆP2l8ezg5޾Ci,K. .6`vڽ\-B0~}[zBջ-bE(C#:47R$ yAj[b6@ ],qk8 (%:!Lȴ\涓xݯ @ #P3$ַ0U9i$¶YjgUB|JDD:19v0Ksg/;%C1"C{VeW%T5?S X1UmazBvk"7eFuЭEG'Sn@PAaA(-pn X) #kR: Dfu!Bke8 e6 RT*pE8r 4s$io%c tIlDmx]Q3cxnоnG iKOjLc4bZ_V}1I E\!j"5DŲSGT%0SP.kdcz! UAqᷮϦy1'a:,O.aR`X XSf:CTZfR(JsI)J0G"a hŲ#qB ("0 [;l#  s1l6|p9R+0H`"L֜Z{Hi\pȲXathqt1$3dD1S ySHi:2w~BD:oƮ1 Fe,nft7ax^xr /8-n7VHV͒aj@X zw;wPh@YŒXdwꞬR E /5'E2u@%*Fs13^H{Z|NZic0r2B%$2 T!W&MƤY4,J5A #AUaU!:Vs/ݗj>6#=YiYFB c`B/@M&6{ g]1.GD*e:2Y4{^rUV>46eO_h}Z>85[䪕lc W~ۛ_^+Kϩb$̓,?+!T7:@5pyC׿"hr}Z̡>,}Ot83ih'V&Vؿr;0Jx v'P&m1v;u_Fɻϗ,̋PSc>G0伞E*fŇ1/yr,E&BQ׸cht~+Zn*g(-,d!6!.yn1<1煳=vv;1g;[]0yt/Z a~P"G;^pL+%?@w;iG6^މLg.b4-{h xc BbDi+6Pf(j,ND *t`q"/ U9ڭt>4mڬ= Jbw@TDB bbcE vc3uNڤ oM;i˦>GF>"]sE)~DI~ֳ+ ꪮ LT9'AvǿA8z}cDq*@4HdLaF`YlXrnwYzgtXOHGqjHVܜ Ǟf/""2wvS,L>hɬĈ=}aiO7Ń|;Xb;mKjX0C\CLnpa휞rݽo>ѯQPt{$d!qC640hiP+|d:uE*jQL/?˭}b鞨VA-V9UZ`H`"YHh"Q)Kܤkw8u|sfG ĶeE}̅H΋-M( [I}IB"M67*e ئ0$`NL+(ҥ,R/0iZJz0!LM<"g߬Y~Z762FKZ#h.,sYNeͬAW-d ɣgf֤KA:x-`@=Lb-vfEw] ^iSA>Eu8yYv zˎʯ4d2'$αͤW!iP|m J h)Q]^[>_ny&kt W8{/yVeP$U M,͋b a:$zU2rp%z#J"g`#7\H8Iy#/~#'ȾEG*3 {aRP :r]j D<  ,K ΁{$VH{>O(51KY& K*XaH%3eZ1q3ðiVv98TmµGF)(J4gk bb0 JB6JnFL]pQUV4Z%Ĝl  LenjYP*oi. Ñ++dOf=ԍ@dĢL$h>iLB=Wdy׉DEzJJ>"$R-^s8,@#pU RL: .V\F[feP%VCDd(K\ AYC&"=C^LtIK 2 *%p$i Ă :{ynga2hN(F La"a"EIARMT& ]j7-d@WAXKP2C"2D9 4bu(*}~Z7UD8n@`1B@ed_in…ާA>]8-U̸G#_~ Vٌ L ~!Qc&: žF;7 st?! o[tnln'@vY!x'4kIPok9/Z#7鸂k>lB%MErop0Q5hhD]ٓ*ۍ}p雧T#7ԍbz@G;ءI;N;IFC2g$i!8IG땒 Ш;ݪoYwZL৹w).;X~Ll>yXԩ%Gu1' H<oef{YNmӟ {UYD@gO$=_^h)')Z`βD䔀d` JuamY[wgO/O9On\ IRHSJD8,>៕32ӕlf;:Ox9AFF 6B!D,%a~WьW\̠D~^er/|5Q`t0υٮW,D"X&Eab4A DD]1Kc<\^O\nxR#'*P>I8$#F!]%*,H9 |gskҋ뺼xnTKNB` 1W@>P4r"@d#4 -ׂh'ȏ"]r ?x4R׋4,d>5SR,gy*W| me/"tj~͍T 1I)(*\E4C8e5"Ҡk]w.P4!; }%B,Ce,ykCaIt %[k6ݞ<1o5zrG  ɘw]A=[I hz>/H<5!s grz@"hn'IJdk@ F2] k)Ȯ iM&'j pv[ Kϫ$t\݈ %i*eO~B8}fnkW{ME4"Xc]'SM5&LR2 SoY 9RD֪Y:=Yawz솠xh#2`D+G:HpN"`^.UG#5P:l 0aDhMN \r='CNWws }D⠼ a0ۜbCSv;Y`l[mڹ:B&EKc P#2>f ^ЫJsؼ*lrf dsY}#$2"Yxx "0 BfZeD ˷_QT$bmX arC]]҇sFH&(azP}=e-d+L X&;i%X(DlX h50% Z}Y !trC +5VaAQ 4 oUdM2fDa2 nY!@DV,t`Zq*P,H44Yà DBF8VA,80Ye 6Z Nq!:i>Rn OTbn6Fa F VE;Fcm#.7I[2!J2B|[,kYb?]g96l]m;EthtKl$pH~' ѐ@:0kGgr, ,7ºW.>(0QĥuBJN]`jIžn#hm=WjR% vS"Vt p>4J\{aXF{ Z(D9\u/pQCfY#lG)>@di=6bX|Sԗ7wмe&hvG0GBQ)? ߍg 4ncr՞NBv|2=AK+nldĢ śmIP$Dh8 a\K P\_qo“!aץʍԒ/&))|D[%' g>Ih@AP٩|~z߬]*W53EDl^7R>BtREO  @`,H h G` U}bHo5Q8惦IgLI|&c BY߲\m~/%گk|vk%qR]uyʚ܁U@wZ\#Z.V g3zeM0)^)Cat87 YAԠq,>=nXy'h-.r DDR̐3 g ^B;gZ+#//Yv/t_. L{, BOV|0m((1+FT%4Gʑ6ղxGl_U$f")d:yxa}ؓ*ܫ Bv :Duy_]U n$:]mj8;qސ=RY}A bCb#1R ݀GH|jɿnwm=g)Zot:`xRg6 /tvD{`mj0J$QS~lձ2]!x ޴.x ؖ;-Dmc4ª\K! mnzɋpqpO9,_ȇmh&J1dtLR<[;s@#whѶW/`/AY2 lx^yxYhonɦ\@4"FSt5fv麠׋? 0LQ,7(4]e~~ }[ j3XiSDD~x3(Ua_Y#҉7H;F*+F(?4O jSD XfV"C=F 0ɔAtVi;e1bq,q*w9#As( $(!=T(kV(iJ^$ʳ.ԋ{o1nFN`:p虍!˔6($ , [4 89L͋rϿָ9nrb叩=jy(1v 1$(Gh c+zfϽ59;H(ίUȵ[Kο[į%\q^tIx3~1.0zDDMjX^t3b"tUYL8z^<=iAP, |ႌ$aj7C3MLӱTw,/7@x-DM5$[R $AYqy8)(2y"TRBh@߆@ c 0ھ(]#(HN'G"<hNWsvHBP 0Lkϑ z= I@mJ;ەC5̌P L0T +(ue&af9uꈫr׶(= T"IAԉ.DbQHN ee^ OqmF}kIק!f#$# kC<xb!<G0D`!`nHLdMi01RAKfNW-pOdYAn:\»`ωI`YAp1y *I(WrJS$I3Odf 3-C3{FGiھ>m0(aH'` cFkLҠSHLNYHHI%֬3¿!@%`] SCf"$BaR垬{#[^[9iDZ|]^ E(XyW :s 6VID!N(Wlkg4;XQIrdX<-Wv'@pRW`n׏%NOߵ><=飗υBuD i,$ 0" Qh]Nj}8ao2[Q,KhK]HVszL#כ?mms|]hn P8ȳ4 N9iLPpPCB*"Gdi0te,')U2gqij9=Zgw^3r)Ҳ3%k/*mOGEQp,!Ts :MI&bp(Jj28rz -vxGL:j 0pD8N8"#`ʦH~dZuoR-lÐ>ct)ADdTēkV&<$RFYpGSwz;Vs+ 2.U"qZ^P$r#f̀&X5`}e:TuF?H!:0!`1$B'Kc%P"KǝD,vbY$HXQ&A$c">\ (:CfFӆqa9 |6uF]Q|+iʯrUCM>}lCXN=)ϒT t,/#$« dFUFQTDc/#UBhȩdzf=T9%'SC({me'=iFmD1֢'1R&Rl=QE3r87=qT&mE|'Eq]ˀE [4E@Ȱz`†DyQ]D]'z_ю|_.}ݖ0)sN0,2ಠ`BLӫt;LOLǶѱ/5VsmAqP[Hݩ0ݍYZ+5{*F&ʨ冉4,A\' ~Gcf}gB鎧]W?Tp.mG4p8nxaњpзN߻2 ҈ FCxjrK2DRH!(0iok?K,&6[jA5z˹ N <\t_K1at0f'.1H@I!)i7ST[VPrJGlGO*z+DmeC 2fD$)?<}?ga!MQKBP1컦V٘w1!\1Z`+&d NXJ#55!ZHH5 klK%Xku;R{28z!_AҜҜhY %@X>+b ﻧ/`v7UrUsEPMLĪRvY[)(EP 9P2DT4_(.t{q;0-?POAB+ןMQlK~ RRgols BA3b[|xncV/(: C0,5:I;blQ>?G['8 ? #%Z;l[m*kow]42JXM#PA(4gIyU8F /a+h5>VwH0ȵeUhSႼFIU%@x4v}6/Ρ0&uH\P 2Q"„(K#f"Fi+羪dgQA Pكn8bԍLdzZQB`xCT+.?ɇ %s)^˦6nJEY;XHyD֤,!',;2ɉ[e&;nɗ@Pgh@HŲzk$Dȵݵfs+_nui3D{+iIVLT{}*|jB_M1ֶ>k)?p<#D :+B v>{ǝ>z6"2EYTUTn xVL|$%NxxQXm(0|s 8qn?msa}6i G(8:B(K !&,(Z6خOeO/-g.7Bl𨬒5T\z h^^y}+E7ZTdd+"hsvaڠ&9JVh#,VMs[}Wwoր `?v<u{S=W?)$98F:YW4;ve ܌4U:\$(6ftX@bV?E$ 1 6~n<à0D Θ861.%c[jRB֠gֵ߭6f$!%F@里a1mĄζȷ} )l2 Wd݂ V3s Ps5o"S1ЃgM4h5<"1&i=P)kd(c8kP(0X"I7| |#0MM &n{a BYE{]h^tߵ0amp,9;֗}9ʁF DX,W2}dY VM(cy²~% 7I0_ӫ ib}R7zSG"5U(oF.х1%}Bl\Rs }':v! =s_L[v4-^/zH:0li{ErCqB-w9i߶O^c*mQȇ^*vXo}/gg7e ׳* AuR)cI;o{-Ѹ߬w /H].$ojU^Ԍ-)J%l.uVl MXk苮1<-2Vz[d|4KMJȌ&AW8,T5^vswGW@'nKӗA1dH` ~’lYY)#5 \4$#,Hys+;Ads Vʢ갽%]vu9JV\a` X_0ަPITVlxVx'>1xk  s$7%?(o*:KNѲ^dP866NVC[?cHDA2 3Y y fdpnpnFt㽖gsrg3^ף%٭SnزID3{?2W-e*Z;DZHqdOnvף%{7,!Q;.dTEP1QRf4TWKF>@aR(d)[1 (-tbPBPUʀ*}ܲf30(   ~XIA]1Lo,o} ]+64RzM&a׊&pEfp" $iǝWJAoM 6}HvKm *Xպ؉hg$m[0/@rK|-ZYq-s5ٙH/RaJ^Ezjg{[<$ @b"d5s$际O1b@IPCoo\K;G1hT]Vً*(K i(B[T_ϵ/(5+q*bNH w$FRIA]Ddh%Wjۃojym`quzcxTr BdPDQR%Qr|w,]kB{p(IVH-iUF_: &,1+BZ`b{.A6py4}Ѝr̬JeisKBDHD:G/'|S4wzԟS$⠔rHTmrLV[3y9}Ƨ̾g'+~.>>qh XK9D i1`l"KǓKΝ>]< 1>L]0R/t+_ @S F\Go)rn#0XNh jD"3vd>P{ΏN$j$G( Rf;'MOg|b,h&t0s+1B]UVڶmKg.qX#hRg.T1 rKti#ztɾgq$W cMvІ 22L0l7À8~DyM&h+$YJ;%}|+9ASVLKߤe 8P丯? Nntj]Ѿk4(4aAΣ4xkj-H΢m!3Ur l03PlWRr l6BD0D20@ P sEJswzi-@$L(P%/4i MD4EiCȷ~5/^ B H2Lfjk.ٌWQny. vnA4g#`D$ tz#82-8B@^rO?BA#pN0Xڷφa ec . S<.Uzع`ޗd&$W~zOFgKTUr`sZ5(7`wAh/><<%vT ~Fy.06<8[v骾3O]rQ|I?aq-R\_1sUJlJ,,b{'{19TԠYOlx/0f6kE5B 2m*~[\_xH]5ޭj͉<"R9̢v[I9v0~+ip6i~ӵs?)娆.6P"M0d7$+ny˵UFf52E\Ue~%)}>BTOM[cE YpgFC GOJz01Yə\aƱF@fDJ2Nߛk K7^i')uXN`gb!2{CJXf=mZ>ULX9si&sDT+ho|XC=ϟD%y8YztCij AdC%"N SXD0RI)EbELYűdžVB[/egTь !'a D* &ds$^Mݏl%⏶K ̽A| K5~  rDPx@Q}Ol%uٙ{;2beܽi4vZa@p̐2k6,d?qH;1P&jM``HR!eǺo6 q }'D@(ak~7LGe緢|10|}{}ʯk2ޑFdz;S0 a1@ tR4l;S# s3ۆ5{ bNך.z||)gv_&ϖH,(v8hSL0&@ ϼ7ƨ}kWl?8¥/.V5G尸Gf=e=0k-1m,I}MiXN- !p/toz˥d8p?U)o.xMds^E>$ -ҰcfY8EiHLWqL/8 TSh>w5w/o VfhS+\"FvKokWﶝ缈tk 1 wb(4e]#b8S x%,@HTe4 * ԃG:9mh1e^fUg7EF,sNN(xo՛| #c !v<2  ܐ`&u1 3O޶?,K4z>XquEgeΏV [SRv %u6pJ1T_^|겪[,ywcC 5CZYj!B9A<[_?u_*8~̒UQ;iY'E3EbÓG<<'׃)6RdZUWFtyt;Jѡ55"4Plڙ|V#ɿ8Ds_VSH5M*}b6CO^c1]i]λ5gMQIltOd@yC,1)"R)xMm.4g J$-ZЖ59=.6jjIYCq㔠"(G#=3R`qĠg:n ,,wG,7/:YBr!GCk )p{ޛ%7jHJH'DyZfL&W oEs)ɴ=a*}O5WovJFsG©};Y͌(f>` ħ'DT"2 .L`~s69>o;,Zzha'T2q-æ?;~2B|َ I@ϱԖVen2e ,R)OwWZx? 7֤ ~>t_6N}(dCPbXNPP [.HA!H4=;/W)?&Z(ϟ 0ՁA@ 0DYPHԍH`X#GrxIE&8CϺ 3-e1̐3CȭlYH@`*= (@jNLƤ_S@d`xB2")!ʳk;D<9[8۽yyJÎ&$i& V+5 ,.h0^ No>4rX`I 4dX$6mpɜxڂBҲM,nDBΝsq-;ua=0.] gO1HB>c[hr=nR o?LIh < 1$D)q=OzcV~tUsu_eh S6$c?|dLqjs_ ;ELH\ju)"E"[Vr!]FAg""bInuvv~T#n{[OU󲭂wgsɄ3ʀ9E⻰Bkmۺq ZVvA»Px ^ PK6P4K&8BO:yߌ$ @F;߭~n+'L*KT:TR+>$uĠ rt{!'I8n.r"Etk28y!Mf YH8h@?ܮJ$]4&3Β# MKb /ݎ7?~9 I?VY $IEBR!-h"p (T"|eGipTvO0Ty'3:Nwz`|qvM;'@, i=1IRsփt(1rF#Tc)i­yX%؍iD3,H( GTV&Z_r Ssjw(D#\zoq}˃Ö ?ڳ[4[vthD4/|}Z<|_;zbǶ L�Kt$>zlšBnVh񟝳ޒ]S& jUb:Nڌc"Z1Dn i{f-GLF[9y"]4 Bߎt0zP!.4/Rp7 (vĄ܂6.#'R1w ԮO2WwD˱^ kOD"Iӈ8Ȑr SCDUx17 ^꽵^3-ͭ"lډtm#w~TyW^D)lf8ȱC(sQlY_nI"YF#1O7;3v;P~֥WC$x2DuL- 6 vd|$F4W1YoG3wm/w&N?Gl_ 9wP2uN6 0Gy1x oPok{64rnW^BH,ߜطE @R)dPN5C.$}[3@ PQ4#; 4gh.e"OH i\ȦTI*;Zktz!AmYd@瀳ЩrL SQ;BZZ#Ps 5via NLaLݠDv_a4_=P5B@˘qR љf6~+2~| 5= : V!JXWS<׬~kR2\vUZ#I#Ch)H!TGkҬ9rikzo$ߞݛ_3`4{2Zy"'%4R8("E,"J6Ĩ4kRHpP{~wlJ=o-"V84fKwUj" KEL e28a=8T3LJ3VBTE'ygqG(KbPPLn[vnuUEYeq&W \C B1AbJAq5wޚ_CvtSlE]/  L4W(qv՟xrN]{(oXof-*fta4Qİ|-2kZ{,{ŖdL\;H%!0eyZ2"MW1dR#?ο! * m*p˞{PDPmL)f,[%WݞR^)m8jnONYhG;% 4Q oΥ;_}r9Q6"R(Gv)n.};ʒcDCa)πGgͫ_7Q5Y4f[0b 45Zl@l%qBTTngT'}c͗כO^xsS?_?h- iHj% E DD@jcSzz֪C?$dNlvC &p[AT-Pgj(8 D2hQ-/B{FM;dT^ָ/F r$ycvH2n>bH\ޚH|@(7C@@{϶>Ā( Jh(`R52ԚѤ. @"45iD }khit[(6I"x|7߮ br &D" `H3@@3g-Mx {"=]9ᔟ~([וnYӱ݋Fw&We VAFVo'|rW?_R r7DE(|GBzU/7-'7A~`B*KK [`kݗdOmOf oV64WH;`?jzyq>П^y[%~o>E3jInixS>~e[_cs2d aH#to2Ř&m ș睕wxsV o|pD7ŝ˻UU+?$"ʘYLIm쒔\1T-%U_~~rپRSuQ-2n+s X)wNó7ǟM36Mh"#31owbOdM^tZ1:_08n,+{zΕ"]i[58ӮSf%By2}7sz1k/8 8 DrOÚ\&d" 7D M7x;aZn*Nqj3ŶD\]HŊ0$<+w'F-$-#C\u.)%ݾvq֞u%FQXX| _q֖.HMjRqsh?̎>SSH՟@A b1 D64^)nēU%. |LcB3u w?y.2Za0ј4&hn$-jix_x>o 6:O]4Jt4 )шHD 'j)P~[9 9uW tnar"RC)T$BEZBsCOn [;v'?%T#4WuT0{%GS`@TQE悓-oi[KpKZqz{d̡I :AH@bfSU[u"kV_6DeB!$@ aI R.;W^cwfdƏGKQJH`:7@*@a>qp}gj's,G0j +-QpDJX9ݠ[!,JOhzu?w_rW$><&UϦ: B 'קxbsrejrhMFL0 J=ք1$^nL冹x1˓w7u;~E9H7x6t=X[{XI" FB Q{mҗ=]_s#4|={HzfY u.58Kɨ]qJ*Xr+^{k9wֲ"|f_OZ%bڡ²pCc-"|Ҁ(- jR-)h 7-KIse8:s2ң/7}? He @I~v=SݲfEP?SU9C@) 0@hp2T٧0R2T @v& _0܂lKJ 8 f@#_Iyޙp`֟Í:C'JA_x3rji 2z/.]Q/F5] \=dda)A!*R8 P[hDLu[`h^2&k@b)"BWp&`i͛ݹgwSk^H Z 1u4l1-Enp]:pM2xրPvxϣĻ%\вNO՚,'!cOpe E+4`Bl#찉EΏlV.$3ڄ(Ay$==c#NL\J}dvx//#Yแ" i㌷.csҶ"SC$f H4))ݑ{W'[;('<%WmN/y ѻ+ǐ謫|J_& /J ;:kC{Z 񸈋>+DŗD?m4Uz]VVҜ0Jj^q_/t >ɟk'G3ix6 {vͻn#> JH\m#ౠ&F+.?E#Fd=,=JE\'z lȬvpm(+QjY M|N^ws[l_7,^ L%`^MrS<ٓEjVl٨1y>K= m}XĆ|ʞ!).O_~?sd:>巶|:I}, 6LII&|HNK $ʥ3wi^s2٥yNs h%bA1 $^ntRo9 &mRz%%Cۜ̎4ĐDV=1Mq}jշ>X:>1>ѲGfׯ-r0YZAI7%hkr  D=VO7r3ƿgqo_E$3#ld]K?ۍu_ DH+hֳ[6x-?{o4dwpc2*R!4(WChV$w'uz*o~'[餇'|4>09#N!,yBMT<٧v(O'=xџˌgnkY_d!槍w0gKyzz@D#*UXdޛmt98>6jAsS{Ą "$B)@^C2,3\^,b3U',wϽ]~iσw2hɽ@iRC1^ Z՗cZxu֣T QtU=yCNT󓉯װ ;bjE[;}j`,ã }#KLV?P)IMB,Vt%4Aѥady! @&vO@yh`_#\$WxsTς$gf{o߽*,TٷJ߻L;&p"'O`^48DmhD0AqDg0 I^xFc o|(;kKNv*@z5`i\!j"&HCNWQK/l<EOI='L?kR$3y7U}uUS 4yk~g4ݜpmN7=!'oP sSXt7:cd}ɯ*ۇ~v3{SWFO=QKFQPo+7q+LΥզnZz}HQfKզ$Z?YqG;dюc D h%'$OOVxNgfͼ/'o5'R87as;_:A` f`0 HLr.BnқHu@& "X+CI!r2o3_iEH 9&"9+CQ20ÈÁ(2 0Yi_ܽO+6I;yON}fAō^b|(d"Eb"nHeCqջAC,D /}ۿ]ߩiC0J j!D`~SH@`wNF;Eħ_܏2_t#V r>_q>I$7ҐڑS7/Jk7͇l -/ i]T 4%ߤh? tk0, ^J)VwrEuw OQ]cRcb<P |_3n~޻{<偼R(۶Sksw]7#I`hHu޳)VOu]O;"m0?uwǜ=7#$Kl T h ֘>>ÜdWrgbδIB[U4I皚x-!.Vp{>~/>~o" sokn>vN~ 6u+]90 8rS.uy#\ezwlM+  zV(ah cspͶg +.?PnF" -|o'Rz`֭u(>_`x&9}6M4^y_sCPqߺ͛Ծ 8!$"u~3`kwnDw!.pjgbaR |ֿu nTps[[ڏI=#hI*ShźjաO#v+-\^%4nX1\ml'a>ay_?MI;ysw?'R󌿪Gboyv)>f#y?IFחxUPJ CRH3w0z33>{w^x^r0w͚4j<),xP--M՜Fs;Gw{ҝu++/l-I^ps亮߄XƢwK$I@p>F+>Z$m}hTGl? ?xtGmWG{6oPZK+vrӸ,s2( qt9KG6Kx4xu;u36(&)aBD+l)Z{^{0yd;*D^3Ыh8C0"l5!2hm4׈qz̐:B&Qɴ}8 o\xANyp,e"qR4Df|z7U&X&oZ+쬫6*<>$ܳ#Da& \9]'7uiOw=];M[@a@H[U =nV >(|ITF6~HY}ADd>LU n8nBң !{y -U[yusng`?}?!𙚡Np-B)J aL,HLqT#8&8&#'TQmJWI[&uE¿8i%Gs ! gl :M]+CT#z\IF&w:.ɫec]Etw?>+rVWG $ @Hzht'(Q%d>[uTVŦjs{:lžByqI*u2;#搖x8͟ݦeX P>S:^NXeR9Td q;Dz>:DyI^We[݊BsB;>+ڇ򢍋to-3ZW\/:3 :BC zLc`k,n%keKmg~1{T:<>O}u0nHc{(;Co)|O~sRm*A%?b[_+ٻzr BS&8ϸ_Ę^7j( P 7 5Op:)u.cuOhW/TnbfZ6ah :$l6-v{hG^k|O-] [eOٖji' =TK2 |OZy%}=ùvU}LjZ̫_:tWMNwgJ6@cVޣ= nWXM1,Llzzk;1_vbnvٝ}n(Zű1ܱ. $shs{Pz`-C GV)Zޤ  oۢ;I݉&*m7P1ȖEquӨiɟ8v7iAPPqbWk9rQKJaCPol?8C|HyGW zV•R/酫(8s3W6I37“}@@l$.$^Z1&WvR+S'/shjovK]3O]9)$Y={Ͻ ˼'F˟.QPō!{3ˋQ>8T٭A}1n%)DhW -)8IGuZQ[b& XQiG@cKQUW|lG8 URf+oF~+[{86WZY-rIBR"noGU&{Tw>qu Qm |W*ٟƄ4<E933/ǟq;%;m۵b p`(H? p>UYs!ҍgMwjZjeK[ٖB\ Jp_W?_|%p~;xoQr2N'p3 ](  $٩: d&}"hq߷B櫝~̅-wnO)Wvnxo_d'N:}Цm" |>wy :K_LT/w[nYW<& Aary B<}Q3B0!|k7dnͶZŘפ?U؅B 6"؆y`7<<&ߪoi] +!Qt ϋ |9/MoϕL97_+n~;3?=^[1L7Ȭn4I\wqf=g[8e^U>O.E!QIk =$H@ +?T9߸±˽^?6xl,%8nK0RHDv:Ev~\w^w@tBq?ln25Tz ^FVL5wD2ҟQ>DMJu?LZdJ~bgL؎EBHqݾR4~/3O<SBd.6Ϝ;x'O˴ +qEz>oER͹5Lmk<+#v^ixE5pDMRAG?ʝw)1 =n<88F2B(S5^5Ct&E#W5ֽ nVpYVv{ (|~=Kz䦺c;65%{Q}k`hLrOw< ˿}^KM־G~i_%❶Yn+Swpؕ*2YRF0=&.{9&lKab`"~#JrE\UZoƥ|Bl=9t &MU=ѳ_={=4?O?_SǗ珼w ' ?C}ث_M\8U9ݏoezL7()Z޾՝ }h '5@GƘ;hj|SڃυSl};9݋3-6֥ o'K ?sgeFqjpc5M!W_sNt7Z+ ZsheK3ggD⛓HWUb}b%4z+#N}ܤRm/}YKߣߞ]?rփZ12"6P$xDhԵ%"m\!GYjvYoiI*.vPuïyT6؛u뉿\4bc{AYT_*ͧ=[Wʴ8AD0e#o-.F3ȔގZAcu/wiһ3f `v9Gf9Z]_n|`ro~'<0xxGxї$O鈄hhiN+3iDkФυ=+ve1m+*ivēЃLwmv%t$h7ckyF}O07^>_$-*Ov2Ƴ~d6ktΈB mvLGs{#Nz\G;*nG22ȤSYe|XťZre?m\goǹ֒ӑmIĕqjuf^3-_uԚvi|w{?Ny{jpv&|s`Z䱳?߿tY"eb2_Cץ[-!DW R}ɡBXi̚ p'۷󊧑}95*Qr)7|z.̶T!ܸzgr#  ,X'fd[R(}^D{HnƊ@& J $BwM<"b{sWu/QQU+ƍ)6NTPğ䁀mYn)דiJ/_c"yU9$[6iAz>_BGdb0EӾZ3k{~oxdk~⯵_ pW9Ҙ)3W~ݳ/G{ 2Q ٝ_{ێ< Q 'h?u^^@)"Y}>>r7@BZrngx2r'feׂX1+iscq՞_ j81O>џ~`\uSQy!?_eݖtօ+@LI50݈ {uYeh*_{hG'jY_7tV(w]'TEI\V͓jֹ_ &Ⱥġ#Vr+A=^s$H@A HL!8]ջ[~m=KM>ϊl6Db9ŵZvՏu[dgc͠r+?&VWzC&t'y彜R.1r'OY{W ?VӈQDPr 5b0oum{t‹<:A@JUSLG=>4?<Ȃ^/ͻxW>?kO$nC RvO! |]\Fe'Z{@S/D!=鴗)3;qƤMh >P4{Д챺T0E^XnaK/%9yK9Yȇ9 o5jWiLYTY7q\ZIV];ݳ';zq=<+Csj|wL>X\7kg"P&y㳗jxϿ{=!dϹҼ:5iKD֜+F]̿kY6|d]Snؖrņ?>㑭vƖ N~0}cãEx J  'nB[W񮑄TPK q댨"Gg+Sŝʛޚ>6vgMSCgxZHLޣ&##(v'lwӭ;SW_zYU^3ఔG?Ɠ y#0Z_R̉#׏V!LˆvDgQܬ.zCa;wMVr}/yQAZFqbX@gp(PKOt3 ;1}O?f-s'hÌ !@D#CM(1oPqn \Hyf֕l_'ILhRX_CMxkM e2}^>}8jI u7P7;pZR]܊'e}7%ۑnDyh5a)M]sq4gm9A(,$7oL`^sϨa5L4,Km687w.`F)|ǹ[rmn5=5EH u㕀1@ `QfsW6Lf<_釛Vd K$L4*b89,őJ_HqdbdӲՕ+!ߝr3#" _^YI. )j4tV2á5sn,.& &'ޛ\Iۚ',$ o^4{Uoyw  ?7.v 왙zz+h'-HF6MK 1rѠlVl>ȥW>y1C42%|is9"uçnr^K:gPLt^3C;>3ӏ0z}w}2?2ThhQ|i);af{/m.z4=uGU%ˁ+ V W(MrBwSDB #Un֨u[]ܔ\o~O|n]55{-A10NV6DwSԉgڃbɐ$ gG>.G#/3Y[M>]a<6W{SW?M ¬5oTA}n ^ rmVwh"0΅$AF4G p֊7[Ս|nRtQog{l+&4ӾJ,ވef$SԻtՀ-S/$頃$k*n(’Lq b%20V5pQG1hJ@40IMHU 5Z&J0~⦆2G q6jSvFW$ƒ7.=Pz$TF=bY8+$g*Wh PWR{0,qq;!  \-*:5k$Oܴ\?zFJwYkӚ~Nm{dZhJfP(5Ez%_L錓L#ʋNXF)^m4ڱ(ZI *N:W>T2) Ÿ9mH$0s vc󲲬]h)蠷(?cRs~.&'=1JbiT,.ZWcro}*SFʆ-EA[drX2B"LN^4.(1$)8M 8-KP;Aae]+~#&x`쓑$d#8GHX!A,(BNFZ?s6?rД>mڽ3_s}*nH8uuc80>; ;9نRGJƔL$a.!H ;[wW&?jePGZ?~[v)xp^Ǻ6tڌ7'c];㋻﮷nߴxoS4.Ī&E bj4N}Dx_x+/r 퍻㿼g3Dm.+{2x{ =x6_޻_wً|t۽/y1^U_u6d :-R̋(dR@[<'\9TN<ӹ57SKo P8TB' qkE9 X+2pO}E`^".ah$NP eDyルI>J6s i%ބjU/޳\>}qa䫞wطs;ڷn>/?Ar&|9H8k,vW=&/-F>\V5rK^י?{go^<5$\=DEF6R]ϧ~~JDu{޽{mIz=f'CNM]VpHsGĀ|#FxCcjsƱC[KGVBL+Q]Q/_a/h2h:T)F-ChߡMUY1݉u7hXg}AE6aO>04eȵ&??~EEw$NZH$"E!"60^ߌLO2.yťha]KH樂~ND> jU]jȵRVDBZG:gh1Bk{zdd޺`mw+ hy;!!&f8OAQCZHao;zgŃ+Y7௦'޺7{bu;L^4<وOK<#*%Q(e!, <{QDIқ = 0}s77<-[z}yE!IkHe H|l{[3goY`r0H->;H3}ך|>{<0x. 6v朏qVBzq=w;2?7K r }ӚONwk-=M +M.w*:ÈEK0>!w5ej[Vܹpsɍ}kCCOTlw3^Ym?PBbt[㽧bu_8]'e]]g> ? U 񑾪dQ&u*Du2˭>sj7w׎,5s^{ο9ں8$8X`&cvr6y\mtO>xf<[Jbo;O:A$٘nd\9մ~4`LzX `&q-pUT208XY\@Jq'?e&~0se,]go+: ERñ@QFZ{zؾ۲-zD1c dܙxvx{ R=~YþS_A~ M~_ tb!G.857I[yߨu~鏄/7xj]W[ IpPbHq,f/8jzfgg[D\5)d{ܺpVRu-@(_łYjd6Й]w=6NcEͣ-zD)x&,Hƛk( I!gtrO#Sh H HEAZN@ 8&B @q S/اȓG.FCbDc͜8dV4vƳ3nE뻲|Ԋ7 I-=nuWvp *"SZ >gPN?]P"vWB۴Af}}|,`nq:t\S3 }PYo>~nwa1ɱwLžt,?ƚ"qtIsV†׽Qq,1ьڛ =K:swG]Z|{ { M8o=?u/|梍iS/6-+ёt`XϸX(fKh* Έ-Vt(C bVVM$MCFv SӠ/oztyu|O ]˟~S-,*L;FPa%8b"$85ꔾܠ-N?lE}`#zko63kI:"Ŝ@o?.c\{Ы>SAފh]mw_7yf9a篊 f%Ky'jticx^q;|Ȉ3V6SL+D 0J)N#N&~r[e"r<۞:=IS](CHIQX܃HpCe=a7n_ا#:p-tWwMnm'#!ZU΂߰æL5+AN?mT )tș* dêjY``e[I@{h7\ Nk@V,NfbMdTP1|eɎlrC6vTȽJ+Q{ݦ G}rG-Z~MCn^5uD3,f ";$N'rl }[!: ׵v:HW['o0"ES+f]:9kf+@ʁEZ'w zܾ*0+NuST_MFaM!*(-I*ǒZAkOkmJkiq6fcԨ0kFLOO_D_4=@]bŵcVDuX ǀ H'Q4h Z=CGrK/g@5?rmGtfX J֝U5Xȅor37alf_`K8_tSt"Lax Bj:/;!2Ld( Ux9!to*-J;nQB~)oA 0RGvx^VWq[L]`S֦ M6B7SRk4W S%/!g!h#HIm߃[?C(zԜ]-zOQZj@U[f PVG<}s(M3v~ɁN@FED&X(R# 4p7٩Vra) *^_=7dO.ؾtV)-N}S^chU0[Z<\T=:4ݥ\ X˶|.}~Rᗏim*swkvQثǿڄiĥ}/ #3;/*eib?rGzx'ln?Z5@2D9WX6إz{IY]upo |I1jW|*w_ja _%4I}Ў~QnjNtPMG:6x̆v/5D|<9púkwB%!bz~OB ڤcm%V 2A5RFma- >†}#9|Z=|p60B4%J&)iPe pgÄ oٷOB-g`ʋLJ*5>2W/5sx{GdۆNQt6)?o+9䌕_XoIXxwη񮻶:{Vd R*`uk@VA@cB2%wpܨ{KZ6v^l7CךN.퉮P5bˮ1vk,B(:KQ=..F5x{_!i.Բc㖨a" Y,w|w3ۢM]@[XZxu;MjxlO}6~ ӝ9no=/l lbHM]VHvFkv >룉\RrӚic.U$31Ò!4[}43p\GhpU~qޛr nO(T$I EӮ+:x@?K >QVPˈ6Q[tz _<ǽյ{OiڪQ# R#1Ph(6#Gf?~GlĒZ1T[.zgߓ5qNjN't'uqD0#&qw\VSƋ8uaH (1C퍑*@07;|:{o0kܦ9l!ndA=v!-Z&#(b'vȗ^еFNt_b֘__з$#CD=c9?X~EIB2nNv]|淛\ZLqKіB #l+9SvP[.ka~f\۽ 8An;Kk'UGvSϘ$kD+-%`Ƃxh-!mNc^}KGx:{| ǘJNuu+ֽR-pePM`U2K"G9u,pJ?:Z1i>+2+1uzwPXKB ɓ!ܙmo3uSTGDA1fm; sǠ[B=24fmb|Mv ʙ6MjS]ޙ0=}НG$~;'b< rvL{q$03Rш( D>e-_רēIQ~v%^WwTGl~'P^Yuot81 V#i̖%81f.đ W$*()) daE&_KǬ`m;Iݷ_ rn1N z3-^r9ză˃{sk_JTow͹}b{JDҩ t[k1ovR"!0+#YnŌjJ=վ/ʧ="zېdDBiSulH-3I5kYtnQaM;L^䊆!Ca!p3*Ԡ8^oESԧTgB EsI4UFY dFfS&Qd}b!{GuE"GF<7)HI*H @QSRvfKiRwCQ@p#-u:ykg Pþ%(H ɡ,P&n] J & 1dY7i"DCU>ZZQ8箵˱7Zyec3~۬B+1V|4}++.1q_/NyopS2F0,"2H#A!ɞ}{.]8=lkÆ"y?VzY 7 ) F!luj U嵳́etyw}d!n~eJtu@OJbD&0IvYYw\=wLJBӏ^]8.a - U.(2j9\:tmxK}ŕ}sd@c!Kר"K1QX\`iA5L""x)/"{9&~k ߺ_۩&g5} 5 XG$%ZF4L w]⡟7+S/~z ehW6uDV*@ Z'% ĻY gN;%Y)„h˰r"[A8%G'i(jwM @ ,+i4OK!.E#8`kY܈nk5P,j >1|Z'[(m@RRڤظhUyYo}Qr% ]l[H tkܺC=)YYrfpPTƤIc.{bf@&w#~wI nrn8@Ȣg+15 H ;~M#߹䉩@>ş*JN{^v˲˧>r>2q|o>(>-S y#WQLR3aYg469,yį}KZM+kXQYW4n.iX'4I V+BbYHhj<:+܅&GO߯6r8kpWekL2mC;McM!N ehs8o7Oq1Qp9=իiZGkpyγkm @&$z\ ndjd>tzyzK*6uȌ-RΩw=ILFO(mvR>_#۶|x}⸗zҜ։3;Cn-b 3C,uIJg%t30Qє(O@8-Оh!d gOUwbOt/Uu.S:n'$ʤ;q|2$MUtpI nE1bHjTZ9!Cw}(}^x ncJũ jh܍lR Bl^8]2'R ^j.AzGcۏvT6]~92=3idg>\a.ÙNTOF6,Vs"L4IGJcȊă!]A%geI(JYRL3<\# ݎބN# !,Sɇ֕ͬtmN\ aDC4 Fbu=>ac7r/Y(Xakj%5 $ PD }wZ&\ |xQHF5S5TDyLWgx@A N F4 |d=gh6u kP<G]\OM].Mϝm.h>^V@ZY.筷:aiipN4*\YG{ysn{ղzu%O{a) A & 6E Pi}_Y1 Y´ys| m^4=^Œp .~,~@:ye Rq 4 Z@oC ٢X@(A;EqԾib>%o: nxyY0pRYH(lU4u4ff2$1q2F#c8Ս"f&*&A$0͐ISL0,t'wS:5.{ۃi&]xh^ޙ`[o:{UC1$4edf&ئEOx>+wz0 b p$8Q6(%ic Va( K*Ɔ S]`wϩ^bވ Z e8dU[P$Cd!fsSѯI'/a(J:f CZŌFԜ|| ՗ڈ?ĵ2#rU8R)~janiȜx2ihe@d ZqoAnE^4=7fv%!@xXTH pjFK9+ wgN>xBZ|w!rb5\O]{XԩFYRWon) @1 j%)bJ HNg5\1Ow8TlWl0R8AMap/JoOz"uJ4td ǤMtp( aûyU^x9!@X*k1z:wMؕYh-)]=`kE;nevLH)áƅ۔6[9,x0Kq*Qe NP/&ֈ-3KУ#pp aE%[Hfg>s(XC?hWD[1!bIw[ ?%S6Wi9TatVʢǷV^t֋@ j]Q<UxΟ"֨w9oIgXeΈUӞVI|AJj5 Y˛V$S -ɠ4ŗ[~/ѶuG\w]iŏ?~Ϣ_cE_-T2c `j- DUAň׊5eZ*Ic{p~|`Lbovtc!h td Ve!·ZRKG"@C(ѫj(\ݯiHB%%$(6i f0Zq&, .!VDYT Z[6r]‘xwhY4[`Kx,;I@`fQDV]ҊMPJ6Ԇ5$AєTBɚkhRl,,& 21{/!Cշm^˥Tekx\P/TĤfCV%AT~B A^l9܋_0S+.>8ҡjHUY`"afS_Y}m r^}*ѕƹP$NTl=+,+Y)VKE|uw;^rT|-׏mkܶBverH6G7ۥ+D,>"#Bd)!ں[p?ۗɣF;su+>@g=}8:pO xo~ 0y@*EEvٖbf-T_X3v74S6r|Q"&c\ n- ] w]Cs̉/W\ 4)fjHm]c#>q*ـf4HhID"84U^ekm|C37 a|Q4|n0@NYDEΆRvKO}DUPT}0Ph'dY.LK\60A.t)yE&MG#pc˦ 9ۺe/y:qK gXʀЧ',x.DȀF* 6F5 ЬQ!&ŢCRP`ڛx,C?0.3XXA=p= vcV) ;/hޘ9TAg .j)diAX9 ,E5oa8G NP3:iՀW &2dwN{q#HSʽf<'{$;,q)|Wk҂f7U2D]E1V \lRZ5D=eD&єF3P{{QwP$P؂v Z@ t,ߊ BBcݷ6unϙ7g?<-9Xl8>d⹈k# g4 ) b8 d}i5|8`seEuS =>>?ܸ<(A'!uwesLGOBچ;Q9>1H9Z pGҠ`UR4m,S,}4`\OKSx|%@c=cۙYݺ^x1(+Mj`F ]j81 p45DM~<_kE xr29މ^rBM2쿩f A8:k6n*n_IxzonF0QQX\X Z8Af[䥚n{f^;>v!##(҈ ɈdV@p:F "" s%<.05ܟ1ԚFƞ!k" V%V$j fSHh"5$\ LU,2>a~yژBո#t r-7;[_5I7BPλn4V /7}kR+t5X#Pv+7m43 wz 0Pk9fT8 A0U3J7ICh re¥ޗfEkUPk.)  Ŧ KNٶ|qBJkVDxD*if)٨E UwϱvydTh}I!Fh|2|ADH^ð,ݧ+dZ`U.Rnp`L(o?qgsKʼ̜ht{xidPsY#/IaJ#R5P,lo-'ū|L.}uK◿( %2 f! (U C"Fџ#іL-6[IB ^'X:Xol@ØŚB"T2(|9#|pk465Bu@B !9?øF zCH5ru*A@</ ^M(Ѷy)A $-3Ώ/u}yLe 0ܟnRX*Q9]䵔hs:/ 5hw;Te]B˕w4XZюEGv=΅ giTV+?WOKI 4< Z@1[ߋQ5Pf qDInIl5t"P&+]4H Z@3yқ~ZRx#@3(_A'|j3c4IfxiörZ6$Sߟ ĒB|; jf@ Q}tL\ޕy J+VJg {Ap^dD[>| fգ>ʰ{κ+|cWQ__32AHKTXbbj ?p-kiSeY>I{fq߭ _#k @8Ъ:MʈP!6뜮;Ukux7;Q#[#`~*rs() - r% D7y5OkLTML_I"dv`DB*ғ*:g lJ}rZ媫( DQpuRq & KXCo6M+ȲYq{|%Mݞz"onp{(_m*zN4/;:Fӂ!,(OC 8p "Q'x[ANU؈fBPngIn"5'@鹑?cLGM CMZ״M[$%!!٩nQ@1T)+j+3CWtlq_ { kLx"ԯ|s#iNCӝ n#hn2ɿܸKe슗j 8'dY\(`3jɭ=K焼}16i۵ѡL{Eu{ oinC -^l89ʦjIX=,vY<,NR[57سr IԾD21\>뻎2uL5SЭt )iXa&xt 52~5d}uTf l@a㵆f/ #U SE>Karv>h$ BbK-!!"A1IL\bv˨5Hb)DUq# U.#YyTH@gegy%(ç;KD.􈰱s1KC _8wY&XZX+ԊCyHz#Z)dAB>W$u O d8faPiR5Z592›e#be/B W|{ߑzMYQj B!Kɤse[? A uE-r&d_jJWbRh "~۞@;UNfuzLJٓ'ax5vy|j ;¬5!Nx[9qb9U3M_o-$_2Zduw ?{͆kC60*ƽ及ٍ})շuk-]8q쨽/.__YvcR#?O $G.#dZz< yq);N Qe^vv,*MGC1xWf*7;zYvd^6TB8A Z7Gtʦw 9M!W .7޳xRؕcjB$duk 4`/c#Q9NE~-(5̚6gEV_qo0PHĭ` `PH&g`އK") X^އ`@(H1E(4aGqY <'Xe5ޥp1S#۞떖И` E\·U-Pp5uv;|TMcoHx;߀u͒>=N{|+\~y8遟]][5"o~)ꬢCmrN_U["I}I0^Zb+As2)S񤐑|#,m0֏aIuk^K@*\! C6<$ARM @$:~$0!K PxPea8- dTU fVF!t49E{*qGdphLLP'82/% Q @D@i3:vfyR{ ]]fDcpl$c0 `e 3P,%Ҟ`8snɨ 4P3@ @&/B2ZodXOUv{{w.kYMWH:#4EĚiGVEBj2|jސ+U2V IyQL\jV]M"b GMVPd"jH %_A4`?[E,!6"9B؈(IGKTDO]sE3ɧ^.{bSw猅&߷\?(R\6YtD'RYd1 A2!YL+b uU "(BOD{%I]Ŋ`߃ʪcq{ֽkf$SQXzwv~c|92煫 (gOFI"@2G AZa5O]MN)Ft+0Vd0@ߥx&Hu՞LmOZs%8gk Mz8jfo][A L-FevS qsǖ M si5OrwjKLg$.*heL,SŜ3~ћF>'gRʼn5Xf43(0VbA䓚[bٽ@\[i)OSr^=襻J[vD" "5W8QKQWnV`]A 7ڂ-xr\p0o3L-n{LBz%ʑSkKesl+TegI[\fN9Ц¹v+f'ECgB%t$# )ɔcnFH0@st52j)6DE+)ѸRW;'?ԫe[2DD4 $OBlE5@L^-;u_qE#[uİݠtv>ӞS:PѶpK5#sŎ~/|oruR4dSۭu7o%s@hw.n;wqE|+EsgeHb=̈frs!J,W@EDdy 1@Y !Gaf5RL8!HRLJQ`*@_͜(}p B Q/cX#εJtsMꞍH̷65:fha-bD'4FN 3,!^XA3gi/d$~HSG:DpF2I!),i43 ƒLrJ$BI,@@UTM'xڮ(?cԲ鹇զ3ST IpU5 x1 NA ~ >Ow>cv!đ.f32 x% 9eRy|I|LBgmD 7:",ǵĎEu ܴщ%(OZ!I+#k* [=}Pqq7}'C!!#p[pWȭo(Ә(ADT&RZ iIz5e6|G9[)^\2Fr 0J v YA%z.n0=4l& ^shU4` T`\|V J.ABr {ݻEV\ x@Ȑi p+t@2qюY4u^3NZ:aPaX)@RR./"]wŵ{K!<v*EF__6[I,RN$9S"?ôdyα/.TƮ磩s,I4A8ZI^q.S&;jl*a*Xшt8N(eQvbݣrbF ua-G{5ja`Sr 8(PȋeD$I*&+kgwP6leuh@̨ɧ`$*"? G#}JA*q}6HtCQۨBQ4Dr: WM@{E(9G6o6zYb *iJ+A KlvV_\xv3%M@l )(H(=Rk=_nѵWoO?{mҏ\R:\UJ1x;#vX lJi9z nR'vY71SΨJMX0bAS9Mf$ ;m " A`0.{Y:L҈|Dkh!K46464ěcf^REPšxw$i5)" 5`58F8H J" Kؚu$QțC3 &j%[^VG}02R d21òV2ƆH>Ia@,E^EpDH\( '?]4sĔf ;mW!D%e{['wfkuT# t[_Rd+ַbÍDh@kbg $0ƽ0o|=~bᶋ2fhOsd,Ke8\>Ԣ_r5v a?AYR%iZ+z\VE]x@np"^맫+o{bI [D<Pb[iaY|p8H* Tgr̭i#eJ#Hs^RHX8Μ샔` t#t :5,uH n,˄Jz}GTo``fF5؃Wjuz_U:F«}|akQ Am^/fOql `!6wHA K[U Lc{x*mҷ,DyXΌ"Al{*vkV=kpCU$s0%KEޠNplWK - hN9pKsX= 1$523C ѩLrGjB@j h0'ٰ |wޏ-S&Y4 0@B)Oa2s '9gm\OIwڇ}mSc9 -.j-LH$A; |AKv(6 @qN1%J'2Pl&G zfYf 泊8= A z9%;6 Ev"ܪmFXY!Ѕ8mk}s-O:hűỏRWMq lyk@Ifk~z7Z)ZDY K rt&HwRÖѶk)bb"_@/$p+&c 1cBNXT# I(0rk3Kڊt#}n4q!DZz+em,7TGЀ#!YNxfXån{EJ0<(=Y`F,( avSOlәylqM̀ 0 Kt %5\@/l2fֶږI PUl+dA!xJbLp CǑa4p(b `U3✫-_~gHwBp3c"d &66(EJUϼձL#|P).p[YQqOxSyhY&+>}"ǩ}-{uQCzWd Av W F se^wDM Iz%'$Dn[ 'f1k52N,o!kׂ۠eQ@̈,!"^x?7T!MFA%*R _?hw Ml]?dR;oLJbF;GTT.d(|P [T Y FH^u؀@ qT@tj插ޥִ oݸ`4iۏNv'Ngߍsto YgV.H#`>F-@*!9xp_8/MQ:8PʇK-|c瀪u;.'q'벂nPUHg ˁGHR䂑"s$(D fJŕ=Ƿ,-EZO(@BhefO0irNI3){cuCJEm B 7JrJH,H-b̵TP1<$tB<`C~e4}"8ŀ3O6X.[w hq)R 3A- $䈻&: EÂxf`fA-DJgH!GpVO 1/"++~ rJsn ]p"^yM֩5(Uh)_$q}y%BPe@Ϧ.Y<#LLށP0Qf CBR!D$a-%(4dAo{pڽe 3 d CxDfU# HA$k@v/6׺t)!d)Hh2]4v-$w MT}[<+zʛd Kb)]2:%cσհvxc3O3maS& NC~jjF˜sd)*j VF~:-'rjY]@g P㽀dXL 3iƈ !A0*'m MK  #&ױ"F',Xހ5+^#ѢYg-& I2E\U#PX2*lGMqѕZ6И>Lbo6bFlahRٶw\qtsʼn{h*9-Ϧf42C[ ^'F=tsYVuO;oS&[xg~њ F(hzQc]4iv$kb l R硐) hN0|vRj3QqXgzNe4 UjY,5%hc;J4Tu"ib cR$(#(p__+ [N4oQiZyc/usyI_CQO@h^FpǞgjAC[]l>vh^P0H ï9b6DIF{LHxb ki &/>*H Êϸpn\"f!W b\ *J êdId0\`Eξ7V2BQw }J^աn#QD JԇQУ/6 ?Sk#|4W 0@&ƪG%"U\Lhez@_lkN*~ɳjGO!>3A*d4"( a( \ߊ'x#- DcI@C/ ,^LiK596!ĮLo׿?iGnomlgeG 8VH7 !,Ϭ/83#Guk[,Q dj0i21h,}j{m}vfD]7ZׇDS19hd5)ςTE;@4L^ꁊnpՕEk'Mm1-^МI9ҘөIGόL_%c}ʢ#=ZMͺY$&dntؖVmO!ϊ0dD-C%s 2G2gIxH  J49(@xْZrEG '!Q.KSzDXPiPښ/bc!DJc,J&VM^zDT9[pAH\UI$eBuNY֚wa`C[8ϴ' \omvuÏ1:)H"gdZM">7\ǿ3Z7.-`[A(k@,hgJNO};.`*՗Ԯ_Bk)ШoI&iǵn*ॢhrj/?l4W)gaBd:0&bфq: bxd2HrMH U3H AA*^ 9%qD1ppYz]B"KGvS,S&hh 2`ˍ'F!k+|(?qͳT;3.$;l$-4X} +翽/~{ ڠݴۂ 7+~0 )XҘ~ IK=5ij4O X$NUlYoK';BSFͷۅEJar㝟-ضr_<^iXwh|%#Im 8TKjpD"|Yj}=W7܍z?qi 'c~%D%giM&(&_P姀qEzFGh|מ?vmM;޲JOuT[_t`.{{Fv9۰ŊY |YxMh("T'aǾiHG@C`LNwƉdJXTrk4 *Ft-tJ@`A\E[dUwwq%s,2>$ѩ:ë̯۟z;ՃO; DkX-IQ lA?Q YE)Jj'* #=0hA"2:H(zQ\9Tw{}ƪ?wɖDC`\)0@ #@@[1(*KR |d0 *%Nw;{ Ȅf^?W4@<^qV-tPiAFJUF,5R! 'ŗcD@pkMJ-?Mi]<7~jӠ(h΢աB*ELj`%0,(>[]wbrs3[rf##OZDSJu<__H@ $G+!p3tIlN .0(i`,5іKGkʊ&݀a{uw׭sjnI}|})>Zܣ# )kL(vFl҈@@ t֐ `Vmuj }ډ^1K.MCVk}$F-$H2 i BtwJXZh,jq)YT53~\)洆/U1š">CA ńpC%(nLJ3 'kY򰐄d_*D@WT7 kĢ@*7m,H<e;zA]Rd0Dj&d}E$-Y6Sy?E%N M]'-2H.@A2>`|`[Hh$ =,2G2v372/ye2 H D G"Pa@'zM"hv2r ӷLq_,E[Ea0 Y"Ԋm&&(kQmLyҌ#c,%[K.fL wjozƜMO^sW<ڔl}(.@_|˚M>w㥯/?l4ȿ֦O׼&߅׌jYywҺ[^꿲ܮ'VKg}6EeO +퀄H mւʆXd*QZ+dʒDh,U6*XgZӽn/]y]Ox : •t.tS\wR . t0r_6#BPKƁZĒQH DH1¾A-bFKÒ"J"Gݟ|WkG}i*;bYK$q!7&V:7P4Rr7ŪaO=s\oSmK k}mSw#r/hueOF[_kvlcET V0q"19-/yP'?>3n4@l'*Dž.&F!K\Blj|JF \yEK++u@kIo 7fQ&^Ƒ(8S\)hܝiB $ rr,-; S&Ԩ) A $ES@(q%"0[nD0!s;@a(AѼL0dt6H)EJjSde>گhͰj7X8gz(.`a6_pqHn %*:]SF'!Y$9Lyn/K6GvK"%,mM)B{e Wa8pvYVwαSv zݡx-Tb$0LRo@3 `"VAk,/,B!W! a(C $J >y3.IqEA'U+kz!=b%:DpDp3U"(΁ gaY鈟B~b]2]ɨLA`p b5!,AUTT`kUI;#*%cP}k? JWVjh7gœq.# XoJgQQ7)8D}G&lΕ=s,gFLF0737Ӎ҂J6)5qٔK&83}bfC#,_qbK?Z !s޶"G'<5m5Z{}m `0 1m9|fF>9'g;a9xt/aK[}ލ0@Z ҊhYduuQըf1Vf%.xIKb4Btjtcv.K\6\5tU- ;IMS좬 IVMax2K@1 L $q}FXow/cL .x@֞C:Z,"F@R N,_ce"dD*d aHGME_hh@)-,A)0| B'80aQgZ(ezp˃YX|S+?ԄoQ K HI3?l5WbE Qu a)@`BS6'ovvw/?JXW7P4hAZ7AP(EJU@LfmKtax=J)m/E&` <gZ\]gl5k`Db65Q%8^XETlfk eYtÕԔ6p`wK*qn΢wX# 2l؟>/Ye Qw iv,%<Bf>ATǬqBI Fbn1Iv& y?siܣƇOG5! eefh&PRՐvj!k9½d.M$$ERR2i._TŰEuRTj oOc%⣇=RD۵kHB 6@σ%&[Jw<lL]d?""04%Uvq>XV=]=3x1Ga~w~|z`S5dEBz-VbsS;1.ޝ=7{cLEV2)DAB k00X`1hdU2A4j rT?dԌ&(ш ~`j/k~)Q WwAS=v[ǻZ-kvAJAP:*zmwh:r=mfGxG q΅꤃Ԁ]nmyQ*^!ëtJ3yI4ٕ>lT&K@8r;ot\Xfec+huG'P@O@@"f۠R <[1y;nfj U`³)a2"נ˛=m"Fp#5_XLWCGJ"QxR$2M<׀HV5Fřkٵ3rX*mՓb_B4f8;HE))64sйd#4$,l3ΐ1/5*ma+͉ mi 0[X" ! O\rBH[JQ^{zSWLWzPP@Vbi2N3uK)ow"Y@EC A.ʲ{ڵ3s4RPXga BZ+W!5YA qw᯦pWC~ I'bd,&9}g;W%kMl C2S$鳤͑ +&#b4d\NX@Vt8SfYNR%`('Wf}p6ތedbU0*O1E7=cz"gS)Lud—S3 D@յYFrv tqS58 oCc?ٍ`a;['1s.Ǽo9IAq|q9@(H^K(a 3+5U(PIo5ofngVp|6h!u[ah`'T`rAux8l@>vϏF}a$ljZKuHD}*BQ*9*IwN۶y| 󊝏& ݱ6:\1Ȩ3|&ȖFr/wjTc&D#I$ǁB 0{A6k/>_>;H7ǥ[8YC,j[vէ{T [L_?%ޒ 7%u؄0sWe!4&& TaѪ@yՖbyUz唹LQzXK;Y`&Q: &*0µsx<@|-"`L(;ު`f2 uNXM7)BO,tN2 ^x|So7y4X7A% 1}`| I)XORM!Lѯ#s~Q\;CpȰ*]ˋ`TH Sg !+Yv[;٭+>gaw̛cDvZ'D) P`G %ZYJeT5M9JɤOfNN9Xrɟ}c&qǝ?gk?q*?_>҇MY'r "/.dS-V.l-k6=6 g>ʳe^ɍ2BlG AHobPXDCбm9nUt)oU;3mڜ"C+k΄{`H )ĶHE$,1^ " a]__]CZɐgQX ).a;`0@ E1`ֻGNm)N8' n#aHdIt Q$A$b!@,ITz߿?xN8)WԢ10Q-*|]E@| KTyIC 8P,ZYi)E$GeML,}d۰cI9@ a,Y !J6 Q  )ZǦB37k2OC&44hĈLU#QYm#tϜZջyav=,v;4.jNNԶfb>n1z!e<&5'eg0 /,$UZ/c0T}eI!AH@ř4~?䂼dӈb}"4[3]WـW 44D2< 9a⢅TXdP n!Y7!!7Dbrx]l3bk9}ĉ@-ҨE}y'UY6r4t3BaPǧ|Τo8#+Q_ʘF@o=7d04 faBd0~ ! <MH PQ ފ/ nh d'#S0 S%~da>P^rx| W{FeV+b9I-?!52 zQP?C|vIMs=瑿O[ j_8`Glm8lao~-W{._mtk7Oֺ5f9vϨ8^vy=V)Eٟ8gV./ӪUݾ Z&anl_n&ok޿qnɷ'݇Ӳnt,?;=69}aA2=}{_~oK0gL{w'7vM=a5~Kcv/?c&1:aݾK,gd6tkMʛ,|(2J Bd`%MQEH$PL2z>d9s[ րς:|Ҽ]ELn!oԒ$GQE5EA!Ģh DSdlo4*أhcha~x5ݤAɋoZ,N~j)hwTs`)Fh0d*jPUp NWPKph\nq/w{Je!)2%c> 3o|89ӞF0Z S*}Dj|$;Z@60Y" ɑ MRaFL `B6~֘䉶7DLk yTSMLXP&Ydb^%5K㰙)KsR>4º$mdBDfQKrw7z΃Q)Ж% SCLι3mũCbT{B@mثz#oJEx`x2Ӱ$',Dvb˞?_bR7U$ӵkf(h@JҀH$OTL3!{I R|fnܕa(C 2@ze<,\(H8;>a"<%Y޶xTڱQ Z`cVNb aWn8dY%`$ +u*_ر; -Oc$𫈐p}p\҇ 䢗&[]ۚ|GgrUiutmlUHA,15ogަƺ`Q @{F>aZ3&Ù ҢVF9NCD󮕿 9 H[rLJ'h=eֱi%Da=PdZli}տwdSu4 bSC$$ƴC8y+V5)$-}" @| dm-O v֯AS39?HqܺL"0Id cA3ʠBݫ6p_\#rb$A Bh6t,15!rڪpKh\ފ[6=,<BHhYߟ7S:(hbXPJc{6fw eelrJ]i`{Kc-P`ZdIj&gfܣoE+qUTDX56yQLIZ W%jL,MVUd"{~qK{*K,m?aH"#MARD ΪS/eQz#ab\CA5ӪHDDb\4rd} ˞<[BUQԝm fĶv'n"l'@ q &RQXr#?򺙷4)(Z-1(VrOAKԊ1` pNHɎy bvO>m_|CI52! P9޸$1[[ilg?֝{#\<3=ܶ˷᜕s;9y3z_?yY#믇kA,JOާ>ɝ\ U pɵپkNxhOQ}xH{6_A@=1N޶;Nu^-mZۑ~e~ӖxGI}@U~iٰfހ@~"\:D{l|rʋ5X, Q$+f@lEp޽&7~+wvjqzt[J˩-fAK Rw#+KQ<zNJp[Kγ: A.T]d|r&Һm<A~# T{m-SB þbzjp]q*ҙD`ZL*Y ⃽fPBIpN'x1A{+Rݘn@g5;s~,+ʂػ$vF5N2Y7P%>6E1ZHoԄ{|dcH bB5 I"&ɏ!%z2Au\boӟqénhт#f)q;zأ-#Qb]y-cFMGI whp1|^4rf,,c!$)^,=ȤQ6ZWI؃J)`9QŞ fxLùIy~G/o渤EN5-aD z&l̽OC|9F i(0_Qه}X+BY7D‚.8t,9Yd_t IUDGڴ_San7dK0Pmn` DGYQ|a&t+Pt*D2rp#mzqCeL܌'4`H"=i 5^Ys=A@fӟkQkf ֕xKF !9&ZNXݑ .'H\ZYSͤm=1''^M(-K| " TUݷ+wpPMȏ:\^ҩ=qgx/U7H$ ^OL0vX]Џ@"SU3  Zdط<?f~僑e[%M<[, d*nE lQqQQ\Qǐ|s+pA$}P fMd`%B Ԧz΍ }$>uw|ˁ 雳go(V8f 9ڥ+u]+fe #mƂ^Qu#@  #kgTSYLWfXƞh|E;" bb t_Ijzb3|LK7.2Jxtt5S QVJy? 'y+jNW\쨼C*hK[?(O_)Ol`:ln^JwPSzRT)aoyc%dL@ɉkQr'^qM~Bn\͝YFJՐA+z2 Z9RpԿ{۶v["T2Xxa^@TGJZHy`$ Et!H G#Du4`'W}+: ibHau} 1ˮiv4rv3 /S}z1ߧ͝+J8O.Vq;bU:w5aYB",uTx"7 EfAW‰9sWwe &-@^C*lБPsPoGݿ= ]tM@.rbnjYK*Z %LkuZvܽЭb8 ' Tߴ(oAf?l7P$(6_D6.v"go0yeѶy/he@fPF`GXZ=J=jQJ(g>a:ELr=_u_SPsɀqK=Ÿ9q\t)MQ/zOS@x)r{/>u{OaN={5,S}} \|]LL]+x_F@ ey߶`˷_oqޞҨբswW2S\ELbe~W7$:hɁ* bTF]̸ӏ^LLH}yx?Hj4IP`+uB1uaV,?1*c&x3ud?(]oEl2Lи-o㦊$ h!@ v{9;>BbݏLXs}:JPKreƧ5(1 !6݋JNj, (DUjnX%`܋si}6-0f o$@Do3tZ{qU0N%aԑ`;񰠥Irt f:lT9V2sM2s WN'XuT;Le%}3]9bhĭ;*I{G;MPqhXȨeo׫p[޿V"J=We*Y|ؕɏO{-ah'5Vp*e' \T al79pw4 9rbO\ɕo[[℮5D%8l#e3d+Pkކ`%/s=mv-mߴdw|/'z 0Tp-#O5dW֓;ݻt͝0TQsQ*:մ_oFKD1ҥPa'EG_]"F *jjkX!H)R-O3#*p]u5GTQ+,'Nz&Z? ;{yTDOE N>[>le۪7U2=OoXuz26,e 9;6a*zfJw:L/-0~9%KY(B#IҔD[閾<Ҿ_Uq:K[gSK)8K/M n(Pb@5|\i2$ԃdž_m`4H (te뺾 Dhf1>xQOS ĪF_E(J ߽.wLSНqfoA[04(k>'8R7>+|0X"':NYx}27\8/\IxghQAƂZ҄bmF-he7< Se:yϯ+ůUT'H 5u JeV,kÉ- 곞uH>s ֘(0@jlxQE/hx=3=ԥBDUߝ;}Ϗ۳|uu7>#:ٓM KR~4M_@տ1?-9hY3 eOW%S-׻lIFmV7}b?X`\EȖ rQ98x Wla<˙Ϸ},5n<keyœ⯽t( -Zl<5nv y7>y܏ygWvĕm#.>-_EM>N~W*0Tu^<}2 ﹳ2mO>r=kxbBh%Q @# '1gnS>w_QX g*X ⤌Ϋorv;Y0'ӱׇW?'%yH fm qc6P#n05H 7]no 3 %y Tm֠9+d02"=B]Iׇe^'=ZњDE_"$VC4 k;G<Ǒ梨*~ z^pbf^YIQb8{? SMK& 﫜=BH]Jhʄ$5h nT,KLlF{P4@&upjC '_o|Թ0t}|w\BǗ%^vQȲQ(^f'}~f (9vo_tq1$uw7bbjo/)i῞{sha܎&mηCOY`ģ?m}oI{޿Gor]︫ƞ^&xd-4=5$sPثQX./qM3N]uA5_nbug?X1lzY>j74@&?]<+^3rp4jB(ޣy՝aE|^jwWulr@@LCM)bW%!p(lkV^}L|;h0qJ']UF|iWJkQ6<~/G-s,T+FSw.cYKq}^V6/$RLΓQL#=eYP 5OYݩo.!~+ Ȱ 0{&P3ǝ1[Oc);/w'8V^?rLM Tư=O{s C}?f,~w}ؒ<4hj|G~wO|WzOW)Nǫ1j;OND%ᖴrRRmysl,ܽZ{rRL4f&\=ѥ+%ˍ_OJݺ3fSs֍o9g$l?ylTȭpHٽg:$4ޝpr/,5ӂ[gPtj$o2pU\u*=)ϠbR9Y%qXnSH[]3n %  k(ރœ]0 BZa N3l1>>rgG{ [v`6ܺ]?ҶIol=_}ڽ1:e~rB;wÃxm1:|6N(l$6(K<Ѻ3>sC?yB e榯T gEDDJ/s gN=箥aE\/W/fe(zGmoŲ;8t-Ol!XKVj4nF!{\Tլdmfdw-枷{gޯp(I \mR׋3zOo-Te:;/asPzJű$P@IxPNv}`ڶ쿗6Լ%*dEƐjr Ik1y TbF*?W,nyWoaI_?0r![mj]Uˏʓu\Ry?WcO>M2y۩*;28w#;͢Ҵ@3#O>47>Q{؇??(=aR| RA`x":4:>YM9EK4'y40gCĮyؕ0?ͯ5Gv xpҤ3}}L8Viu\z˦7"j)q`P,A#q?pE:)_ 45G7"cֵlW} :!~!rNIԛ<WN>d%.?^]6M+kRƏQ'ize2ON.90.[^nSNjr;]dzVKp~ߘKڕFu[Bsg5tnbf}޼ܹ7Z{D۲)[ݲ]ϽZN['EucZ-ޖrw99{J%Vx$rr*7↓$6YȳS@aN'LDUH.%/?f.O6旇ފ[x̞e[FgnQy>ᱛ;<Ґ}|Ȇ~$Qf3;8[= o3ݐ l6i)+[/B7ywnr×ԳM>S|W= hqXuN5jK{*[͗~{1n< ĽvSQ4kƿeY(/6<-qqmHKq/u+ ,nq]  N](V#At.Pz8Ŧ*|׿l7dkinOep@|Fd{=["U5r A`t9ZƂ`{&KstC!%rfi1[eEW7}ƨNy鲅dbb:5˨$'q8C$Ue(Eo[": q$^ k$Tx,(^}ExdٓbX&ȺF'+Θ1рY =j /^/خaSߓ@ ,B|,T2 cH`k/eޙ{g]~wͅM>2.ŀ`EW>>ʗ]}G~vDC6ˊ#ӑbS0u>ϭ\W2|ɿ)ؖ5TrF5.#4;TܡOmO꣝K͔`ǾkBbdMw0zB;]|[l}L}tplA=oOۭ4Ζof鄖'PhMBpfW󺻇p8 (LAWnJ6ɒgㅄ>䤝qs35/ޗʻjtyS6w09P>)+:ssO;6R{W>t@٘GqԱ@l{cB{YP-uPL\҆gt{C뼊4Wd7oz6ew'o-_1.R^KxcV]!٩Zvź;>?_:ӗFlǽ7b}v6 ae辽 Jv6{}d=!k<,y!뤠J"I1N-2a0 "K3 SKFjNuףGᶟ;AoZ-}=U ,NH%SkŲBBh1"2d:CEW~#^Hғ=q*֮fƁPI;^YY>ʹӝ]ǽq _CRySYnt͚ Rԣ2kVIB0魚zKzc]>>rm >خZ3\Dw`$h/Đ% !er3j悔ȅ?ݳ7{LJN:jV;P"wfKJi5U2C) +#E}g/}^3_4]IWnĬ;%d>_7@ :;P|hp?n˽7zUFfG&LrkOcg@O1Dے؁+}ڷ/PAWƋn?x(CsyP&c** BNDT =Z7|tѺޮsIMss-_jԡYo{TUk.eyb^/c KBiĀ",K 3B#+3f !ixcdSC1]zs_$]S#/ﲰ_e-l>.ν26lG{|ݕp<]vUl&,sӜ6Ғz,wε- J3(NNO>K=?n؛u-DP0 -D%4EP>Y>Jتt:&-%o`CW-c>pF,#I E8Q $GnДXfHh3# 27HD BCW_r(.8ˍ9L 6ڈ0.-0дCh?h"MC۔ޚ y: n<GLzۥf˳#|g:jdTA[ l3M$Y|vfbXa;=vo;]Y6P94#I#Ǐ'Ц&\ x&Mԃ4@0 1>Puc  u-t<˴l9B·Thu:i9['^:Isy8?ئì&>Ū*ɵ1q(4rWL;_sFJG_ؐr{GJa2⾆Txk4@ie1%XdXY2m20v<=Hbf`i@bNf a]~G}`[Ex^pnDK<qS&癖l)ɳ*W`hF$m!N$֮KQ'{wڤ'2t̢Q656jC,q>pt! .ڝrSשݓ?2c&m%Bxjj T5N+}[ĚHܪx"g{Ti K3m;/|fׯTKmݽ(p>UgT()TbPDГ*&+夊h~v>NJCWVd5.˪ʥbB%wn7eOA1-ܴz_,اc`9IPA1JUܶ|#Isw=[7m%CNg.=f87"nYl3PƉY`>HUFe_㩦B`=m/\e=}3|;vNXI$Ij֩:[|E~ap a1 [LXJ E(~7;B!TWmZȇ_ ̫U\! `lf9Br8c5#`h0%UB]#C׭ y>M&0rfU{_9؊DPʻ6c&xER;/+_-kKw!'#ɌTliMkm$(b(F]~m7_u,vg\9_l-cɥ*ҳ'lFr0~NzMhK+1êDxkrkۚe?V@6/Al $i:67'$pWgv+SG?]tό}Z8X+9w>[ۻmvҲI9,R{,5MkQ WJ-j]p3nL8vKdRj mpvB8en:t;]Y.5ը̧lWit|'XZaAJQE{\mCS C}zE߿ؗ?&383`aG~nQGX[g YINWGB}"~euWqݯh˃iN(CD{f{e8=0cE8CY )|0d|KLfB&>y۾n"{ˉJ%1 k" оWwu*}=d,m'O=‹;ԶW3ijq@_w-H&>5ڊ{O=sʖFSAFHgp~zBY CG|&jS"o3^bm!1P39">vaZ ԨeXܵ_[-o+b{Ϻjf:jGW%.] Kʁbaafb2@5EZư(P4 $Lt V~!u^sВ߽Zud%|iHJlN5$&@/#ǎc'Ǩu%jl%: omg㝀{&VPQэQq8LDYN^Lv}>> :$.|WY@cZMi5\i$D栌no7uN9/XlK{v>גD"0Zq^n:z̉(#\ F G"Zn.{i@э vv0,ojMko^8k3E.-Z+ک:x#X@oiwwKY$Rqpŀц` SIhb,V~'=8Bpo"EBʦ;Ns C5$]hűÝbXnR?!PAحaNdAs&g W­`5Oi>oiQG "֞ih/Ǭ;q݋CfƒmǂO1itG7׌.J u31* i)>3g}>5wP5}27fw]9+bu#dHqQX9 x||Ikl=>\NYh]tV$\l!86d'Why( jKf;""1-Roy3縩`n^ti a;zĒw^X#zRL3I7mzahu{ڛoƶYxMT)LCRTWFMoׂ,o{ت7?JBE3rW2rR9 70M& r`Gk{҂iכCg>|rEtm+D {e5֎:tjξ JMHDۚ- zdzYug^(N:; #tQ)d`Ybv1!pt&)qt$t A@iZNs{k>K=7 OXKb(hWvgc/byMI_"Dt(1tFa`")V*cdJXpE "Ai= :aBQ4zXz[FoSiQ Tq+L3.iȫ\{o!ae\ | G96oU7UF"=aYN^Wח?%GmsTs[!SuǨ*%<:iؕk_$jq$ZcKgjrR!~Ɠo=?w%>9#07,5ꌆUY__ڐO#}*ȫOlh[%t.N+)/qu̡;btY0ԑz_PEXZ:Ö(KӮgoK1;)>2Imc,,#QcaŃ'khp\@Yӊώحz\^F#>5IeS\ʑYN*Mǐ%'VYn`<"9;K'=xc>1|ʇs.etG4 ԉC檓1AIRylNн_+w3S-鉧}fZJTۣgB}:rj_pJ0%Ptق˩~UVN=puS~_ )w[zKv?aPkJ]IU+/Oܫ ެɧh|w Z}hom>5[Cz~vE5C-Ŀ7@R5UYf,25}eҚ~GO̚354ZYmsF9BݕmOwhS3Ce/"M~O xuCZ`[')?T [X (,* Nv&.ţu9SOpm|a;,7I}WDL(EFaI0N#D=o/t2Lp9A% xifpm2)ȊBdKE>iwܸ.e oM& L[ PD pYprx즊~;hT[}˯h0ԝ h[>yl ,~ͯ }@z֓vʐ:"vaq VwM8*/$ߦ)rP>{g'ʻ.5aLi׮ǖ֟bZ5nfK@xGccW5޽qxoDBH_Nm_Jp͗.2Luk9$Z1T[6mӂ0*@D#1J5G[<-TՎ~>(x-T3\Qs N^0 $Itbޕ,:K ,\1DBiJ| BA|{9`Tok}s ǖ #@fBR)rӇ^{5eCzXa7D C)) C] d ^Ŭhx D`PHn/wHr='[cf]+.u>/P-G)P߂5"Tw6aCϸ,i+M8)kikt%%$zGii@kB/=@pȔolDO_kT #~'WW{mCTyI4^n5?:_xtcF췡y-gdtJ& bHxoBR0&2>=9'W+߰ 2abL\T=N?Mnp]+K4 E4Pf=';24aXNkɠ!Q;t b 4,Kf+|ܸ=##:Ӌ3ڧF-*oV2V:s9/}HيNo];v"^-FW2D(c)`ho0]SiAP 0ƕxL٧mskֲNki F%د"+B ħDh9p{3aj^NW)W͇#W a%vorHh/<%gWVkJ[AxAyi[eG+ֵ8E4-ů?Nf#I;iM= 0У|ܮT1aK*YѦZzz[$c_pnٿњJ(jKwKS*$ @!&c O\ӇdB@i7!%w$A&fqq3YI9mDvm[qn2*>Zա@0o^TBet'& HwO'3t=$NFR5$#pGeA(K0S@ LS# 1O'{ϛ%6--ovvqd_=l{26ٶN@6F3_X\;doX>tݓ;_3̑2g΅6ʶ\^[)~P #0 m-]R0H)W!{B?pCS} ݳy>qܐ\HtDYD10j%LZ0fvs6Y_2)Z|fq4Եx E2;DePQYXߒ;>ϕ M`Z v|wCZX>hz7>qmrM5jOO2fR==37]/fD.hhm^ģ?mT7SK{c^,;C=㦗MCWT6=G ap:znDX [PnX4Wŋ&cM5'5)n&r׾SN)|aY~ *g` D*mqF}(rl5(jQ 80 G֯;IIAVX@'E8[x_`~)HxU~k꼏bX`@1#zP 2iT;,\gef֙^f3:IkH' *ve`$%>0a:PHZdMQ8X.;|ڮn%#|+j[{iB쬪qso_d12NS#BqC G@)h֦`.:?T'k\h9AG7hwXx#!י\?w Lul%;09OBF$h-iYVے3؎ X+bKf[ɚK~)}6nyxq@VF ũQhUԩưzŇ{5&Pΰc%+0qYv>Wx), ӟtCdgۢm;k7װFjZعmb'Γ8ڈ ?b4Q$gL8BM'0ݘ'L|Y:WCMlD[u=QH\5Lmy`c}O7*'PƚR95V#6e>-N=!μ()r:'[ A²d4zl\ IwWʭ~K|MhӀ$#q  UN AI!DJB sx}2hn=dN.ly&sbVaa| v R% ﵴ7e7% =7tGLvj^'B /\-?nmO cvSnrITtxkeNe>=K,v2 F5i9W՜jG81DO&̆ ݷ}囏n<ύ:_ayѸ˺߾ҟ2ck&vQX"w~cr}绍V/poPMlpdU6y k`SP:]eB"Ra5,0mgƦ k41%N.[ugW6GeR6G.i&41=O̔'re~iaLRgz =/(SaU6G&V$ߌm8ҷ( 9 "1b4`ѭ?<֍I notmKQ"[c ͑G-UUrq%k $37fuT%Jv*àϸPw%Kju>c{w$.7:ѶGd"Db#f32 ~Zaٱug`uz$sǃOZc'-uF̦Λxq;kǧ^D|}0y}Vtp P"L6kJۡA87 .sA6 ٌ{g8FO0ob̜<4m]Ob&{b;3mL;͆ޭNp5>:1m,}/)vc_6֮)??%eLomrǯ2:bC/XضHTP%S ! d৩6d֌hxԭ{=?ҩD}uv_B|+KQͪj,~7 lhCg |d *A*B?d0d 'lK$E" t3Gc85:׳?1^=i&V Vd* "mR݆I1 :GQM'4ռW_~˃~͸lJi@A%'x|uG/`s%9ӼBhu=37I1(5w"ǀ1 γGT0^ :+;~{qҹ S_9ng޾] lxt_=LetW(.8a3YWITes6SX"ٌe2u:CthyKIUY^z./:wye2`d02Ob&heGz8^|3wf_z%{5` n[1͵lgyJ< c5S0n%k?ڼ eer̂ɶ2;f3;n: `oRJN fn+H]e? drpBٙN kX6Al@TUX:o8 'Ӿ?=ؔA"E *7-)‡?/#n7U&\ʽPyL0˭fj'f^vGX$k8w!?jN9C;1-ثًo?}Ã߱r<007܄ƍۗTbAVq63Ύ<ڣݏ}s6tX'1ag?nF װ"ND (}>8;)qRVg[EÀD+a 1;zdhwHMor5\8Tʠl` dd QdadR$̔ъ"azۋ2? m# 1f7yGp("I.-A!f74qdvk BJfF=P_ a>UR#|fu;aI,bJ84Gb+qS i Lj'Rb6=aϷ60h((0 ! cbPmzWIߌڳf{H剄 L4AZ>IR"!`fY ɤnh2rժHa P6#gIEC1S> ]J6zqg.խ: 71."Akݪ4D,U.f_<[n59u7, igkժwznD iqXXin~+ (md6LN߃A$y֤|w [ˮhFECVK//:G4,HEaj$P2*5؀ND RpJA cݶکֱ+o[7 ;V([={[1--bx L6s1`ij)1!i@ epE틳vϣ_.74@.!! ]dH6қ9,XBZ^KtAc3c @ug\?zA`5VBQ(3erfُ)BJ< Ϭ1\eYk@☠:.:liiư;BQp!m?ƘaZhZbl4zJ߰)QF3K#[aPBVƮ3ζd) oZ}j:[,\zlj?i2MsTzkEJd_&`җQgDn!0jZ0Q03_L֢ф!{w9PHL">"ǙZkYONŤ`>xL/!wQe' KݖR7R#dԅtxEA?:>0ư9#N>iOӶkz8+ffPb8EEi|9+PˊӬ|@c"50-9%}*2Kpc J\1kE ib.Q*wdfeq* -7MEP/Oެ#n׵2CICo $ &9\letg_O\j-}N;ޅLcŨOx\c*SDR!8*;URQ5YD_t4T $-ĞKOfC{\#,P³a/> GgB{=yا2S .v4}zP/mP6{a=UvοwAN4ԝ)fJ^L\lQO8)(D'/Ww@{Y*ؒp%`w̗?UhorR|lx.d͓B" 0E4AYxjF:JZ)"rS{Y^f0 O Z$l*aAUDQ [p|cеsk!%ipfMlZE7r{Mƚ1hfA "@嘟aGdC,YX| ]-Imc$Q9#dp~;-x$Wi1sgʌ]nn"[R}N%7v1 BntW@#e l&M H1Lpn rQ}?_1!ydýJZNͦrka3k*@+@ Ya 0S{ٗ('5`zȳ{,X'_ㆮyeشv,y'A[L 88xR>Y7 Ֆ^\v(7{*KF31ZjJ SX"Z)Qvq(!_v^%DD.kjŴA'5KSņB7(uJe+dtvTSvlZ)|!fR55*B"@"DfC0DP̠*8D-,7fL/;ŗhPb`11"-)8smX+Mپ|^RL- sH!mq洘 DR3 Ph: jRzz8Lq,}ыj#€B\I2gƤEkn2x wsUO*X%EC\?%m$IeK=%*z.XJ] 4}x0 f w"jKq]ūI DVW*rf,&,FQ=^H+pl 7 - 괶4hX.dH\X,$ILHԐ{ڹ?-{Ą"gcFF*yf|a"(H)dz JXNet60[-:IE|>.FK|Ǔw ?lokڟ.1)m>U浩C҂źIU- 5&bEjk n?8xl{.69ᝇT[=]|y[DUF@ :0 =M&\LE/h6'(<~&:Fv* TTD0ihjî|Fb* .Sq!V"c:> U9l8H]qD "XL63+t5JB-^ViL{gE2,)ޭO֯dӐ*0#ǐh{+N 8=eDJ@Ncgb{U%[+>~d?oM˕FQm@gI0Dj-~EX\B!$E v˦A h̍?iQәi%t 1̎s&L5& g7 {p=׿j| !vVX!?_oxy 8ܤÇ^"CPѿÙõʭs뎅6ea]tDO TB0rVZ準펿krQёk) 3t&28H*,[°gKX4~X:rV;i M-7N31b= MSkhhʷ1pxF|MRgK39e+{mIX}!XG? !hW]?) ¤Eb5 Pjѕ(UI@S`?Ѓ>B@G!lt^|HǿkFޕm0BSCBB#Ql%ì_p@,NN~)#VpDǙ ^]PYa75(TkW?FҪ;IG#bͥ tf4ZB(#`dK̖ϹWL!y-_uE=;vPItZ{67~M5ʎ{.EbqWr2ʻ]o;97Lӗ{9+9G!zP']uOj{/B$[h46qOmmw/d^q=C53sQ BD X/B$†4qvs;S٧vx՞2 ZxE"1Wj`*@'JdpMStڅ}x7`x<wg-+xOӎgIheG PJ wR?^_^; ?|uk c{3J6ϞR%ҫ|yҶY3zV/N)MV 0a7_z'b6jihGE2av7lqÜG9zdrDg`4cDmҧ*3%ﲉL $rՃH PlJR< F2`ٌi\`\昽q$% Dj#::4PMrՕt-TPM0t xZt!f1hYHI w{Hzi[7hA;-͂e[ S)t]tJ8=[,5+u-/EX[Do2 Kcϛ9Y1Zj-nf (?g^W9N :9U5M{*zo7Q7JG佪1FwSkht9YC.7]t8\)<~Vl'e5.w ն`xyj"%jw/$3ٜѓƶҙGਦkEF$aDû`l8~ Rjp"r6|{$/U.ӏm< m72'߼K 90=lPǔ\Gzr)^~+|maI S`(U۷x,*!^MT0;1hX}ڜD'ɖVdp3Y46$`zhnЩya惷;@pO<<Fwq6ZwǍ!ʺ䱓 ׸(~kʑiBM9W q+:awgU#c@n;;"}k3)U+ rm9Mm]<'*}֟1So,_iGI*5CFmג{B',phS, ƣaU\7n;(vv=wt wKBgx G\Z"#ĸoԡ-M/sNJ;XY/shD @4H %:!ădl5h" >UP)qIgl2CػUJ²=ǽJEцҚWR[iZcAx`xJ3`NMR|:$+šHއ(`{[=8F/\В77f_QPr0PvrlXٍqGZޥ{4V0e,>-panV\Udoi#`(tDBؐJ "MIB(+_|euŝS&9;K/^癝KgDU hT4#i#=ԑRQǚ*LQ\-h:s|nF%a9@tP`ZtAu8Q7x'h6ګ/--Ha>p??p}E*{>^P_ !\5{Oظ%z)h,gf)Vm3ڈ Ċ#% QsQ{=@& )6,;+-ؿMcädqbZ0(}wќ21V0)`XX6oO5c,{p@5d|3hM;)}R΂ hRB 6ZliJ [ٓ)R1t/Lt$ٗr9SPEJҮh/fCQf q1qE %T 5 [#{'?DmYΘhc)պIKgI`+RoМ Z8h&"HMPcv?_,kv_Q<91vtl 4/i˛MFڀHR GvX>B-J H{/DIOm:AmnV2z"iwΚ9힍Z޷rMYR!l'za؅5i\2(b@듢|qeUFt [Kp Va` ): N_D>@|L?M@Iƚ-߳y<`qK{aGN-Z䮿tG2 Fo&ؿZ#)a4I- Gzc#ب3lqM09B]{)[ex<KHacfh6zAD ̊fExoৎsb *:p!{L-O5a,&U΋]2Hl)H723%_T'5#-U℞*ȽvyKc6zC:kIƺp0!"]S J?g '渓2ѭxet~|8MHj\}rv/rXgUG!RJ^8)l͡%R*9 HFلC+ {qw)c_T19yϰ;B!!wZJp*YM!Gb]4Y(hhP<29zAͩD=5z { Q@ $0$%w+"U찄AJE֯( [ >L4/$Q5$xl dl(]-Eu5ٸԖd~9r3DrFRVXK㰥IQoUIkK/#3¼2nA+|~,IX`oon>֥N\ ;8~)lmxrjE V4ܢ8%1" KyϿ{!ZJq(ݨRK@@$U@)y ˤ|̆l'f$@4pH(^}B.a:AܛfH:GmloCekkY8!ԒH1D9$}鮄Cn"v4fi,0j:W4S̈ft;YOsdOg qXhda(ܨfOhR#Bk;j< r=ǿv}s?!7'i+9 US[Vx^ ۘ: Bzi`Rr<}J}JGڂm׆8̉u< tTcp>Wo*nhWB7NvD,s Qb=N۞?ba ڳ (XJ>֢B L&a@]19 ˌGQ=ÛcHjhK֔a w[@ iP_lKikV{H%,Bt6$McS<4XݦפkQ쇂|6\YN|dҼZq8.&  f`2 Cջb;B ɠRilţ_x_wPGcwzD{zz"ab#ދ'|L.zCDCVB=X-Ys]>=;]='{&mfnПG QI p;E/z#e@ld! *tdhkk䣗+ϔȴrpDXC $|ܙaon8wivՙ i"u.iM;vgݿ̧t_2hkYt|UN @kFb4iHl4_#vΥ$]q7ShJ'Е t:LMPaE ,ܟ!w\JJ!V^C+!sњx?ꆜ,D% M5گ =/ډm/]K,d>TJD 4ON/Y揂f%} P|Mn K(Rm[S݋ZډɚTO-h" VlR3b,K1‰M]> KJTIV~5[i,ZRcNY0/谠r$"QK@2ƙDCjQ\4UWSP1QP4#,im|`H9Y}/}  Pl^ʅno\G:iJޡ6E j@D֬LWtSӯzrcƲA]l|C{?@(^[ ~/!ZZֶsO:l0^2\B]&>OT=-~A8L 틆<|NČh}} ZůLk$o)@ QQ0F#JUuhXTeUnI;(`ywJu-5 v?EI.3# "'{$9o;g?ٽ$-6nPz8)fͩA6R'91+8ÂQ@F8MmǨemijʂk;κӚ-ΓU>3CX!|Ãqב,BXOZ=dNM+.jE06MI7`FVr@rRV l&q:Y)mZb#W&K@񑵩gχ~=.0N/MywNF.\7c ?)S&Bv;<>6emYjr}͉:h{KUņwBhٚ;90 S?:GWQ#Ζ~B5׆DD8ffPjMMr'>G{:nhs^o1cb6գSi$ziAvNCDq?efN۾lYvx; 1 3bppLgB"\#Xȷ|1qĈqK2}S/*a^<Ǵ~Y8LGjE>ȭ=\\6xGPWnyz#f`z]lgC]W}g;j`Aeqst׳- d:4I"sصF-rgU.ӝ`4D@$] ~V^-"rɐP XBۻ.޾m 1)gRoN? !iN=cK{A{rw:FR GHTh…6d9t$c"1Ukq[Q $&RXn=Y貚(K)2m[.Z '~;nᾭkMM#$hQp+s+`]S ($71\h߽wZwj_R~et:ou(ck SC|f\̐}A,NГp:1VGgI֣@= sMoTB`ZL&4T3{+hI@Z[JG5tJNC(:C;,0PZB@*iai`s.镵V v^vTx.R~G"@2I!PK!|oWeJ?za'ma5~1RFSx 5)F9l!bFfuϱhFhЉwtT+Z*[mFBX\!o U2NYSeXoFgP<)G^/vE,-zDq7NÏ˷p Y<'KzPmF-UXLH-U4Q̪dŐɼNw*W;ǎ Y܅h5xW !TQk~?.B2_ Y%)ĵAFJ3[8uk6w:[ק̟],i.Y _E41Hd!b@ A JM3 íCE R A jR's~6-IS2[MJTLs"R:e(48HЗZ('+3&8 0°VsF6TE*G=.*.h桅O  'e 'J{AQs? j`t`!G >Z+=&DԜNC)ؔdtcIaժI-n.F}ڠH(*3VLR.Lfa PcTX`*Ȳ7bIq!BP#Xr~ox>G|or){ %lbc~HRf8 gJe3YjP8U]^Ih\ X0|1j~GBSqƃy|jB|'*qձ=(yL UP먓ous{gBDJxڻ}+҃:r&]ܮv 4x,@1m[{V,Zlb-n`_#2Nm?Х1"~үIQ3,^GnbʼngLn6ImtN96fv΂+rKPSt_ fO 1'_;Msou5coɄJ@L8P`A܅FF   h0Z!/U xdp&(zРwv@Fݙ} &!FrUP 4BK_sZO866(aƨ[eUWꞙ70r D4#*ڢPci~ֿ?eW7Qys:`zHE]xwi y0B@P0o[1-^Z񸜽-Xp7Ce$iv P5:Qz*x"t[`]؎9؟4(E"/=mQ`D;!wn`v`[ g3,RjĴ#7 ϨV4}VUbAlV5B.tՒ>ϬȰHр ;fqP֠* Ĭsdŀ+MLIMWatBGK#JCs!N 68*T:IbZ<TfČ [xByɚ/nJx]d&"O NqM~ga+~ ÀUc76FmbԙsoJd]U]fi_Fz { z`h2lL;DGs,XJXp(QY(-lGL:|MgOn(;"@+l&1KYZ;FRBaH !Hb6?um.a(l,]a&SiLfji 3am"+m?AS Q I؀Q%0du A"ב<(%F; D,Mab9.tt40QHP.(̮F-*ޒ[BIiS'y:zʒFߨ߅ap]nN]V<Գ<~sXV1ʐ Y՞.4{%(ȑv\ZP DހWxf#"aZtWh[y-˝-bDH Eh+CyX)m)CڅUEG/8@[Y%G^*ƹ5_Ő=ItMO# éA6d(~3Ч@2W|R~Tk, r$^IρlXH'0 xU_Tm6,@c)br8gcoRn]qEBH%TznN܊Z?1b3RVsT! -TT%@HI;wߐڷ](\?hsyhxڗy[FľibuxF}؎ xJkyP\SRrEJLIbdo$[QM_TI*(f3xm1% LK0JTSb@ҍ(Z5D&=ne9F)Xij(48Ok <*;sa_QԇwPVA"P !B($$SHP(- q ( 1O /C1`ѣ##%!C QCƠ3ۧ~49QR^6xޒKUɰJtCa\'Z;۾€^, FRl*9JkC.2,c/T0M PTNA/IMc2"Ay}"` j4bX9<9mM;͵<5DU@hV_0I,]QCqv񱚹S"L):ˑ2ٮՙ%ʧcFOWXPnݴV,ȐɆB**pa1\Bo"sP`ZUeD55UE|PClV! Õ.xdoX4#uͨhg9\iF "$k;lBM4zR*MD3'XBΩy7>3%}ۿK{An CqrJ3kb ڶ!c<֞HnlU_(V=V|@)vl밪4%$F#ߺ\Rbr~D&V.w7>UP*PXgP0^2w2njN!"MKZKy6{\p+:>In)wI0C38*!hTҹJLc [<Z}Kyyd!X r1:( rP4{6ϖ>|Y>1kmS#E@Lc(%Xfo@cF҉|5"|( i9+,KZ4tMqHx Eb՝^sGьͧriTɬhHd(&ɬFR۠B̖t3Y?0j"&\: Ÿx ~EzbA(QMZx{3!dyu! @D9"Pm(ZbNZZ)^ZtIQ4g4Q/nOCƣ <&_DbDGA}7PDF`68<)vBKZ V*  V0&4lB j:3LExNvLgoNv]*GAb:ٰI0YCj!WH)Q-jrg7đ\#ew|SXPH(Ƣ<$q"iX4a3|,f6?rʂ~,&la5+G(JhL&F<_(NZ{zoo͏LǍnJ,Zg̉R0LdպS~gop֚ui$e_ z*2`͖ŃҙVz K:UZ։2l W XDh=+E7ι1bt+28@bx5t Rqy}FՁMa $ L8@H_K맗?JU$1D6RȁsFຄ )ЀC8#D:-aIJ NBK9GFjjU?՜>ϩژPؔ|=)VY#Lrlv(eKO |;N[}JHhT6ud4B ǫbQJ&XbUDG{攎"&#Cb COuyo^q_vr8F?w~]_b]Ow Æ$+@J ubuS+̄b1(hG &Ԑ:`w$u*ل io&dk? ׏6HՙreX-~~f ]ާO:st˘Մ_%"k9$^85{Amza&?is=ژ{7惬]u"=ndcߴeҩ7Խ8W;ɸso2_w~uH%|*(JpHt1s $ 6hj-,ÖzeyvP`1DKA {8+W:$",P2.hv$az.$8$t XVLP \BLNM`M-g߭~Zٗ y$/m"^8+U3qvqҰ@c[KyӲ:ܘJ =kduŝp/mBPK is-yq%^2MnM¼X"cAE±a7@ֳ ro#ܐSx0Lg"8 K}u4pVH@B\"bج̃q==hǫ4U1X0P?-,x]T 0-H0ccP0 qۙuX&L.` \hi+;2F(A !30 IN=N0F-'i= *iV@VQMch LjVEO9Ч%]٥N<䖝#_?g5fj9kdISpW ml;e\?|c#[+O*H!O5:WT3IM%A&SEDqd.$i?/JH|DuO?8Ge+hZݔJIcΌ/dhJ( E {+`kDIfd4A4ZyFFm?gf= 㑰B[Ϣ Ԓr G&S/-f}f~1Z̴05>ĚܰNU(c%+ e@j|]ZLE?q Jg@B|4ߙE.B񰣙؂O`E2 $U(XJsB"8!CMA '  A]͠IviӡȨbS>)SIkWtoE\|uRX&KkTڛEv+B|Gg#%q!bb p(aPx;l:eJVH*ST^tx=g:L$TU18R`Z 7* UЙ֨Rޱ>Th'ۻd  s(P(,canrj<'́ȢؔՍnf^nvF|- ֘➔>FS֤JL=WxDA0sV'e +G]\5tAK7e%YN#n3.-1Cl=lpyMizя%(5Pr?[dʹ6#* fҬoNl2|ޖO]W7N5!rr x=$mF N=ڃ䤛˗sSSm"L 7ܿO(BWh2^?ʫ~1@OX5%Agx8ChկW4 a D (c;LSԡ:$dߙ(w݇;]#$Q"##Τ]X&(e2 B@54y=A0 RG8rz +| Z^oJ^bg-{{sq@5!e)Mǻag+x橶sVn]BHv=Yc}5, }@@J/ŪtlɯXBy1>==熟YmY4įNV&|~b ?(BXb SjݷdmV}7NxVݹDZ ё^O*;)MW W]op:=SH14ԉV63><#FbvcMLrK|7[ֱ Ʉ}&iheˤPXHNMY""HH=nS1\$Gr /=E뢰$ԯ|q~~kO(sE(HQгj31N ;siln?iK&lGnW>͚7H\?u NEbYRҗw ޿o~כ&nB, (0,sZV1EU(*! Ԑ ag TZr!z9(Ӑ33tg( ^оH?JФs0L_l.ne D:BeqHFq\ b+oW{r׎O5p vJWe`ҷ&GX:?qYks -ԈCΜ *58b>NJjyMnT#Xy]%I^n7Hb纁+`&^A17?0~8\—g@!>).$A.(BnrãC?wgr˰c(2PJd" }=Wz_azx($\A_׌C n®^/4lPTTus ZPPY6a~I!f[rh6Y8AGsGd`3oIUU JP"JtgɍT.o@r*|I}sn*c-,ktd!-dcm(*TjdCTz\Ȍw>xS6ѽ#E-bsF8mg@@^%֤ICmٵ#ZW'G;STloO&6P0`ќ!/PvN(QQ" 9_= ޖwZ-RǁYʼn 'jyB&BNt=r-%ꎧ_W'"68λiۯL%6ݗ ϼK^ F-|&>wed?QT>h~M"ppXڞ7U!3)MM$B14w>;f!BP)J f1TIYT><{&̹XY-[{Λ==T3DhĻ8;G\blS;6_i7 1swԪ 8F{V<%QQ=>(Hr>qb>.scфe bUHN TD/iBkOQ+ D_MA!a`\Ģ3i$^16 c~k2fʂn´}lj8WzAs}^7AQrߺˡb̶(e@Hp#X0blOс&dQF P і A} r*aJlƎvQe/e !ww図Lu#{@Us.#ma؉wR VvEbThEYӚ!lS[ye6矇OE$kG {@7Ν(ZYGs^J J̉k[N8m3v^7l7 "-ZX~nbbv%Ӳ$XT!P GWŃ4 RtXV;-rA+LؠV)r#J-Z\] ̆oxgGAef ۼf}/ԞAxh"|Ea!`sz)[ |dSfn2@%Tc$DT4"^i1KLUa뽌ex*Nn 6:?ne)mm]9VL/+Nָ{-s dC:A6љv丶;z6|}KMj0J4%Ԡ thYvU-vC3>@(Rr: Ӄ0]E7 uŽ OSc-h*ZRQmtBh);6ڽ–}+s|PM0Ǡ0p6x EY( 8™I+ŧh&OӳE/ tU!.oZSpcmUǂb?KB)v?) sm419bN&C WBrC R'}o Y&'*|(]{7t5Ιq%l|]qɏvr']hyS31l^Q^'bi72ۭy&3yr dwz{gLeߪÕ~3 Uջx= @v(5WY1Wq"0k1jR ;-2RW?t ϕL&ON^m>>:U67_%-'fqC Nz=b䬖3Ov{SiV;]$hR,);H 4B>HCt~? hQh#]DTI ?$A$W".R),IC:?iʄda~I PI=+`IC ! yN1{vv93^{ 4<39D*[9,Wp.]j+Y*oImaC92ɂӑC!\-dB2P0%/VjGd?P4Q…2gmቤAD>Y&c9n !N|EWSQB8* u覢nT7؂l*4[C@O)"QnpBB$|" @@`aVlE'#DQTi B?И#IAy2vsvFiVaR"E,[v،%FIGY֫? {>\xke{ 빬dv׫ܣ?\>~XGΖ `Tgy͞'BVMuO0iZ,Xb#Őa@UX@HeA *A/4O4DYe5_?c5o&:V樭dv;OT=vTrl?t8CcZHP! ̠6`@J4F"K("'ƛ!rubQ%5AibC!W+5D;ޞM̭$IWuH``˂)A@5K\dJ_Ym7 UG|MYmoMxPHg,3 ` ,t("c*Xo˅\ @ՑE a.TZ/PD1bXMa#fK .lٝn D6uҎ ťI6f 744-j GDH |%˯GS5zǯ%}ؒM.##GQȕ. 0{GQ]XX 1@;)D Z\g ~_ف]M- JvB­fFTEҊfbUDlE0PJ/:{OTxC:W>CԃVZ_McԸV`Ik6SQ$/6$FS9pr;{bXL'#'!ÚDU+ěB5 z㓑*I8#IYa'V)]mo,^KB'YЀfL,T D42>kT0BLڻoxG4yh+!| >5W7V5i]}*+s <źҊo]\UК .~ w\s봰4mxn y`AD2aDW\yJ'~+gB-7Qϸ E WtH !.\-? >oN]؀"5S:[.1'@̆Z(|ﻢҔN\k@-Gi<  d ,aP"rF'i܎>3,<\>hU?7K\%[C,Pm*g&`.7;{zssGIپ"j/"^bK?/99U'cٕ&.=/nm~%6w?hݣև^BpzVS LpTIP8:̳b5Fa3SQ@-me@jB=o*T`iRóU>ZQKRc PȍaJI+X2rcچTDc` Db!Kl S$'mE'FO@g.r" !( Z M*1Ǩă3ߜrw_vmØ7 Kozv~&O @k1e8 ̌Ra`BVmn/R{Ob")ן6v&K sULO/軦6<ozUO'f? /F~5E!Ƴ?zCv5_I y_ētYw+ ք X~DVa.JQm^9wft s:C,q&UߠUzBsc[>G6 [rWׯuf]N/(0UE$Oc4E!aBB6#u 2D/NK@jgvĻVt&9H-(ʊQf۾ZoOPoz#: թzdsAahkH^SjEilICLz:d kn<`@u? <#0Ca&lq#MV@9p0߶ nf|U nmP :r5# f H1 p6 =SMh #.*v|V6P(r>svkJ'qȲ - Q^PZ<N!8B&WH7k@Es3f?燻/mY-$A̠i~RA9 ]WNU[>Ԅ9~qocRPX$)!fgzEBDy`yY(?|ŵ~Zw=f+DQZXH3qH"H!(/SJX|s{Wn `""#/ȟWZVfeX!!_Dah,jlԤj6Ouhq+6 g8ku]bZޣg?o LABpDh6rqMR:1@x9 <>I]K%ZA%RNnGpAFaOTMm<PJgѦN6HI(^k PŘ c&ps!%~&U նYg:y_Z*3em'G?sj3V{mGxNOAh<  n:iB1@!_qZ])V ZEI*#GVQ©gҽ9Lk>^xdqdJZ7>U( G&NhbDEV Gq2w=נtvvQn,`]y ⚎)T*iTۧ&W&cY,KREY&;A`(3 t03 \keCPXaG/m6n<1P8[lK1nH#MU&!]C_Q$8uHceA뾔?$=̆Y7mOVn;264XYHʌCȠꏊ  !2fТzZwcIMɞ޷#St/$5XZNaƃE6"0 yBWeap%[cHd44HdvL0]ѕi !@tCAߦR.i&8L#I@MRF|Y![ENEj ~) ~A`Ul{ƭn`~Ùw2_{ pD٧55s o̅5ԇu)`fRv5dP5A#^ 0D >@"%NMZ33Or hr O!G)OZI1BNso1 Řhƕ?ԹkVe *Җw週w1hlΝty8-N%+Vm\{`{7݃tW|g|?wy5g:~`2^HfU{Jr5F_N,6~7>Ql(G TD)Zqd%LiʲR{<˽mo+T* erXCu$}e6<\6w?<7ћrLH RZ+)@56bČT1^Z(/ĕ,!9'o9%c8&Bmh4^ȼׄ)i.ֳy~\?{&=k gV]nC$`"PH/,-g6x`_O]cu)z͟ks#ȋW* 7lsd0-AtXr$qDeP"=PNmZѭҙf@wM/@9Sq] 쬈7kF3u Wc%oTڐNM_=ۛB~oNJԇ,/L,\qYq x诎դ{6'?~ %>Kn'BxB5^("2`jֱ.\=p q2Ztޝr i4j7:R KtDŽ (+ ]* ODzq5iJS}[K!Ț D|&d`l׿oQPϋi4[aQ)ӺIw?D3719/D[DLT .34'ijoP+/mHuf- I"fAZXM &1L۴A姕D  i+>oj,{gg\98a瞍DϖT/N?l @pOx17nAW&&C91ܰJ<83y 7L^ V۩^s/k|* hWTNfΤ ёL껠G x?@`_^!3tWK]?)LĩD]OT|t}ً>Yeϛ/b,mtc%^Ƙ5`D*UO'Ф{f L) )9/?ΛF-,T`B`>/P 67/uw@Oȃ]-84tEL$*cJ@I5#Wizm|ưxjjk0cs(7iؒvmeU3>PY°5-pTӯZm @O8}b v#QMûV ]칤7XpgS^  @s]!ypWvlG⾐2y1W1b(hYP$zkwv mpGȅa5Y*ab WTһ #og|]>8GQev N\ {NsraJaT h si/@z9sR  @P3gܯynmZe{.W\T޽GVPp|0G5X1A睳#1 RgCڀ4IH<,:X) u2cY"y>k6s``SL bZ$Qqc5WVBzhגMq|Pө]XH @K|BE$թ6J`)`Xh ! U *udydɟ"yxm7NlЅo wZ# J>0xr$@K{q23XNnP"afRA[Gcz|LĻFWc<cmPOeշ j{g}Tps/B?94 : vZ%H`9\qSʂFSt{_K`xRճEA-ZTh=ME IJpHF$D!0 %*L"@A t.ÞDHUW{@Iv:mND.R-RÀd!sh JHR%&^OwS{nilY*41f bBCQ`08beȧ*]D/}#]-C@/xus-[v= "pߎl}iXo24c3mnm!߇.JZ5zɿq6*7>1[$9,fЀY: TSocU.}Gl34B1{Z R:c_ˣf_Cb9E\uИtVk`IT=5j4D.@[,YQ ER9yXad1vZQ6p!v_^* i3\"xgLQվ֕]4Wƿ^#|uYt0Ȩ/&;*<%^9QMbPEAtc΄xP"hQ.:3v ϶ds,qMzaտub֯^$=9ʜ'J\jZ4Ͻ=a׀q9b9ͫ/~'}m[=<;t+oS1gWV2wp;pm 1'Oa+foi3luܪ,Ka?+嵞76:f*1$eJ< `%Z_zRֿ)rw\Y<ύXa[LAg ?< ݻuy|(`[ 3MVI<3+rTpЙDsr 'XZ̑aQS!Uq 2(MuB&"JADe_o@' O !PwR#F RK)Ǵs]N~!~xqD0\n0EKލX\Ʊη;HQd$(͖; !Pf0 Q{ (!, @Q*)_ G`C&<8Oe'œ[TMx 5D,Nج,aDZEey/Qp h4rzsl! @"(׈|l~BЮuS`T&c5YA^>Fg_`p.)e\}WZјe_(H\(P%䫆ci8sv|Sw4]:wTUV˯ mp-bHm,a P@a,NΞ Wgz0At^7105[0`U_ Ra׊~;#^{ƭJ}+cQ{դSoA+B ܁"n8M M\:8l>!ӓ|d܏<ڇ86YZQ2u߱;k?M~O|`K?qs,0?l’~e{*4s6 oW-V-Y/}f ]VcaNXl"叧ޝޢ7X@(w3<󯲐Q][~7 c*gn[$kގη.FCf B,!_UW{E#YPW-9Իsu"GIڤ$DVpQ 1-/zj#+\/n|yx^į6&b=3v[?!k! &]:;kMM߈~08-#Q)8E!00Jg**:$Wly7{X!BB(UYՑU5z^c./f\{# kSU[LKTMe@p,?Yyl~%CؑXJvfJPvOW=ŝVj{׫( 7 u@%y6$/ϭ5S.#wlOanpouwW4,!LAU/ÀolhJh]f| WQdwAO8oT܈T02  )#B:sٺlVG,> e <EhmDZt-Q #LMc c ΍n-óZZD1[%{ yJ{pG#N.\?PWΚx6Dd@`xQd (( J5>j`sNzAR7Hˁ,kZ)톩 R4J=^9 9?gƫ4-J$Bg'$ptZ4+Ov?(I+jYlt.:Τp@0[w\4w[8~_F ݨLH +q6!c'GVvasenrF }gzZ/.wὨǸG"/ ;j}`V;,H;3bΔqn5)x0Vڛ?Zs}\\fGO,$lt!n esCIٿ.ͽ}UG|7I DBE, Tc([+E7X_hIa*Vall)T_qfzg;F9c БKw͊WmwI_-9ѯLƂ#ŸgmtߛKܒ 0ɡG_a7xX6F>r b@:*Z o"#>b ]4 /!"] @SgLDQccZ}B>7Jcr>b F y朧a5vjNt<\ʘ7P,;NvS b@m>Pܵigܐ4ڪKņ2Lu׼3=xǓVB;:Ӑt\3}5@P  0`3tO*&S8eP?p3S ?םTᲕ@i<?HG_ڻ d_P'ߒ"HUfRi(~醝=t Oַ]] M)G2/wg1vڋcQ b^lV|6/+}" /ʺ|+UHkiD 0J !w*ŠzRY* tZ\<ۚvu|ɬ8Fm~/gU_aݿ]ݵA_GnɅ8{Mzŏ6@fG<(!4Q1bCTwחZYRABpxr4|lޜ 56y7|)aKgEpk+͆R5n;SG-];ߢ'Jtj,3X1sb{*/Go!1Aq=?Tp8`+ !']wZ<1Xu,d`$w5>`أ< rR$aMb 4Pgk.DEu>q\di9cAISĚO4SWRg֟'8غWx)RQr㜡m 'H.ԞD_tRe7|NtɈ96.4Sm.:lK%|.-P7dBpQv|m՗Lixo=t QuԇTR~['j\5wRsҌ$,HK CID! ">8f%L>/_|{hm;?\:ǯ\_ħg#d`b (6Jj,PiY@ieX 0yth8oyjZSR=iP$QwVCPlѱ"nK܃da @`! q,8gɏ{omTu;VS$'_'3R]K -5 Nk]OL>Mי} RL8i|Pn @D@|}㩘LcfE )A da1 (tN&h'5}`zpB A1 nk HUSӰqgfXdT.ߓc֛qix󉏧>oe۽U`"vPJi+HFJHǐt{xŜkFzf=aӗ9!]XJo-n;/K^1~~ϿveWIy{C(0!W,(v'^؍u'hn@TDve,KH-]zhw@}b5k 3I\ɢ;ung^ڮjqC ?آ}ACTl1ș٭V$&#Wz}ſSwi.7{aU}rɰ%:mIG}fK)?orjUFٳc\_A5JF#9=N7ь@*89 uS9(锝uf6<`1 wDqSp^4 4,ZX5sS8HcWzX _Boz`2́(i@G\~: |;SF!OfFKsU]θGTQ3;y@٭cRဨ4eZ*yuНwmaZ p!{WӔxA Qޝ[GS_]-Bpg*5ЉI ea륩 .uG.#)tL@aڏ 4kl5SPaTSdl?WSZҟnK/= jW,Zl77Ahyu' Oczekrfnnlr78t1GL=upr?gBw[u@8ju~w5>c"2ul yKP 6p)H0zVб'eV X#/&ҩXt\Ppn>l~~yw/;倿ɵ!8ԵuУ_\,.▝Xjh+$YH7V~Uy!1w:Q:I~Yߠ0^$ W5}E|Zqpw~Ifa1w PS;[oE`khʻ:lC~Ig77i.ѐ-c=Ivz.i+nӦacAj,+<$g@{FJatŁe8t(OrGw׷d׾LL]fSb;x Rˉ\Tw)ޚ ԱXA/!~¹Ȣ_wקPC}f,`KN:{#tӇ|X> ?nx14kHK(Si`Q+߼|X"҄e b9e̖a?p?S=ne0ZPA䤞쒹O+@5h=~^ߺ;7xOKAMٺ[]W??*@[4Rq8&ūipQ/c.?KV>bU dR>< yR+!,F1dC CW ukhs?.~׳թզT@8J;z`t8xsm W2,nJ+59J8fX2ֈTtb* *m< J@Q 'U܄$}@ETx y'쀺I$qm݁R_"|qD" 4]7YBjŸ]"A8OT4𑡋0)BM\ey^F,tt3R':B2PjŦw#Zd j LrH\-Y=^F ~ɱUQ 1Cz[ /ˊ|aΛ֖0-8KraHzIxu<%hiLÄ,}Ο'DTm53 {^{Y|M/M% xz.$eKG^h%H%i" ;nv.Ӻ$cߜ+<[pl~jϕZqi\hv}CXr4Qm 8FіUJި݈%|.D^N ^p `I25y2 h`;!dB[@4ԥt`DX#>ΟX;OU  8Kgn+Di| 1 >7͛xYFqdIJ[BDo_ X7kZ5){_=Ҳ308 ?QWflM;f~.},Yik눻-˚ω[ MhaJr+U'R=!8AD")c.poiL2۴p_ǞB9 !Ϋa h-aVXA BD "VDnz,_:g=uoL `AhKh`˚IE@T0ɂ{x 1 C^O}pfԍ9㜌|!;[c~eG$H `Ay?YşT | yDaL!xKpP  ߻ >~)Q0`v9dw~':(Y蚀@UIiu Aq0oW_׻W~tՆ(e-(vm6 YzZ;O^{|[9P= *-lN@,5S ÍUi"͚&Ovo,Nƞglf,e CGW!qK^mT?TEc~r&j:AHX9e ˽ūU߬V!$p Kzwjg2f&C^F6(jʹZ0$lK푤/DoBz.{;_إ][λLS0%Y`4_qC3]_&{fQvs3 il@iŒʩ77䨣H ;Ӫ{kJVfY7vz&?<4N:YY-A-%X_2,JKC -=S.ߓ.h,JV/Y)r_!qQM}iЕjcT>RBMEYb̺[]t_o=yqU?R \e7ا҈C J0M٤2jV cQ5?9.G:D˜1Sna_AړAj,Ϳpgj&F6q51B$\ą`5\Hs<+y6pUN|);K2,q:L<0Ɛ>v.']X43pk[g=7ئ3D٥o5r;"7m$B'˪s9P ]+׺F-EAxu\_A 2* ̮G g̏?ocVn4%|+8@ $M+ S'D%2qgԙ(_sz BtQ`+ZRhq_C8-|=6nm >O%>6R2wO>H h|OʀЀq{Zv@aBaw OԎ7K4OȜ)<\|2qtaznf1cI%E剹_~?z\5D+Tn&԰ 1!^T'XRE㰹 [uܰa}?ƂB=݄i GpD<GL__= hQwNzZIܵ$ՀAx C`DПuts{OwtSaQ{h"5)YB9a+L7m#G7滄Enq䄵_%؈bc_SJS)Yg |׾y?B" .J7W^jQx81K?5W-LH@.@x I7Am2Ա߼:፛.hKPd}Wmkfb繁bẳ6-mX|ǑD&=c"`!!07MO{NC9[W4*k!P|]oYg u bBS{>7=Mݎ"tH〄⧝Ζ0|fuha9};9qX?f mnۤ]ݻ/OlI%D4`&ry/1MxzjO3K 'mչ~ 2DXx*DBD>+Akb0 0vC:О9GrVC6'VxَUS'Fr])?yTO뒣-'VhQe!.٭ه|\/`2Oqi|C9Birb]4 jaЦs[)K9wմS'F !Dɍ WlXAkRĜoEfᬂ֐ 8|=)gT/p|:Q_g7YㄐOpkB`TdaKnf#FZl#"$d >Y l!7q RgXwA"Ev,ؘ# OJ/K&gMffi257قҥ ̮ x_8anp0{Eظ0bjP"^mt3Y`yfS 0U!p$jiBu&gvow=fk-N"曍1DFr˞mfFS9apQCЀ 3xڀO /ZT)Mj7ʣ!K@B+30kwV *|UY' Ќ6m:>z\w t/*6>yɿ٤ .׀90$(ol!pbHć4zKt_:r1s}cSaPBf  $!0~F@Z}öWe-f(ѡv@*8dPIq C k%]fuh&b&f!4ǭj!KS #)Z0)πq z:=$5pg;z0LȁV6@Z)P RZISjA=0a4 [H,F[㳛#~=93}ȣ!vc@ZK/9d]:I' K'S%(Tb"q}ّax\*A#'(7ˁ@󇧂^ ~Hs0<:&$N{4 M3i&':tE҅Zѻ=Q+;#fmZ|DY; eOIN Vfѻ kV̞5|vs^8:u_&:/~'_u=\Lފ?|w>آ2)W Mp*, 7>*@ux@pM?M⛳ti!!z.e䘐7K[-q 8 . v^gEfLzrMh(eA@%jUoT=1HRT>[W;%. RJE3$J0h&eGej D&pT'GkUzRYJlOV$ף@kWlύ>Z3c.YͻHl-w%zrs!/OݱixᄼE)PMc$.W7xHFf!d,=cwzh6wH+nk3ClX\c"Wߩ8)"!XG%ƀjؠDp߳N8X?#++~`Ti 2Cn™0B%CR5I;ǽ<ۖK$iPVy K%VXrkB/i^+ uۭu3ղL1+LpbUU-eJ)O +yK?i >HXL+m#'ak5'T`]s$N2'NGP4KDX g'Ů0{}|S%@$+Ye2%VnL^ U,:jҋ4͸d(V:łbxXVo"n5)?3X٥`,B'dJ { j3d^!Zj G& `Уݳ&2uk!jcšpBa'P'26Q6nƋ7ºZ]JoVKա T~|Kǽݟ1S3v3!``J4sp&ؚX:WLT@ $AMK}GY.Xc|t(%ܓ&m_|N"G>8q?"04=0y"1Q_V]ܾQ> e]WFMj}M)ӷnT4Q\.eJÄa=6+joX ըL "*bN)Sab&WFUUHv%-l U<1V hQX@'F|&o 42 TMlR8| A 7 :篓+A{sTv9L r6S[Yv&&jοi;򿐢Tlrm|;9cwo;40Dؖw p4ɪzih 0EAǯ`)KcCWCc4O:{G[p_v xvk ~;̈́ jzF;蓓GgLqX4hSviOTx&(RgǐFzJR$:z8\hL bqi!@0`3Ҫ.̠d(;3]ߊ xdp )t wA2)h-}1ĭ wE|KU P>֙;=\bU1b"Jz UN@?,Hl-hLH `j H`J$y1ɇs:HYN|#nd8 ׵R G+j?l%IkM?}^WWܟ{wZDCbĞ78԰) & =a63s.MY'i`x2^siɉY;' WWQ"<&{mȜDk*`zWF|/`ͮêO0!xpWrfݾrΐ="i׭k~;KE<;-!-O:隣6gXhE5PT Գ _onLw(3Ym-nOBGJ+ _ o6aF}$0`ZMj7(X[ %}mQ#DQc75ǶQmG2@$5@c(wY m(Ia)a:{|쫫3D#0dlc-øP`QIđ* ~(O&4AD4+֬w >)VhsX)vt,Z 9չV> 8Vq u dbd}#,> g.Kosc>en&Ggxpݦo~wnzޱP$Ϡ;$UeS[zVqbz8 ^Oֲ򓦄+ꭅIA~ Z |'Q&y6]<UJ^Yp"AĦyAOqL9N@O~@T.2@D)D0j8"zjsXX+1,b<PJWgMC%D4B3 tSfk ugq}!@8p! Rwd%T]ܡpߺ5&CA fIR9xI3r(Ƒ#+ƧRY}n~m1 Y\WEU0|w" cQL #?@%D/1˲,ȈcPpǥØ)lՁuqo,WHL>Ӳ,oۆ:@0?s :(xQKqR@b˯FW/`%V@PXb0)"l Z? $0 !A a q+  ~B@C7!dIcYzj&TciWԨ1:Z- )` k2 ԃi& XZ ҃ ݐtje\Y  @ ˰3ƭ՝;J`@{=q<_"M#RkZF9e*Ci=Q :]|jZ =ky:GO4- eH,IX (=A A]o+Ug|ʿ/ҵ''>iBdmg_Y}[3BlC][3;FYmSa&Ioo\у7O+f~(r ή_OtwX_S{xƼ/>u3,_;C ׳ 3g$$bLF632<&QU3)I%dp}س3ש0 7cXWma&>Ag 'ctQ{b йjUW6X>{s¡+ S +MkhɑTl|ٯO=W|O6aS d!5ge"[5ysw"m|1VLC!p fpw{ش"'䘧 8^@OAU20Bp݋/^03=Xli~|Ň )OPpY1ᑚŴnƊ˪e T#4XNG* gN(T詀bH5Yw,cr}̼.C0D@V(0=u)-;i5P {l0X]^Yk5 ?ZB55{ ?mxK]CYgc5(2vHٴTbۤl,&4 sgޙp=A5  &&Ԝf%`VbXySDgJT \sp$R)LRrހUP!2&3Q\O=ͻՈrUv* :o)ZS9;$R’D!}AnZ}DmŚtj5u-|$,)CZJU3Pjh4;~ʛ1~%67i$hn<%Ykƈ'^`M*]+ŝl?=*=)uj°y.)jjӓhr PT@UGt@8Ʀ.+Kӷ3\]Kp}zO|пGlUxwX=Ve#C;LSJO{^ 0T >vEW>@ʍ_0&M+Y]MV]ʃ{!\&JIja=&} rT/|XߎBd!%~pTMIܞF L n:l&:E!&ڈ^T}y\U:b7 \%) $ 24u. xc7,, Fȼx)7HrBTGKg#60ܳkni7?Q/>]>9 9Cs~{BV ag' D((HBfZ8eb׊ܠd礸75| cZdN;pɲbUn##Ia%X)Ltvj- nPNC =\`Iߠu9“{'LH  XBL ~3)J1\,rB3 e{_|^)(BKUv{eX$?܌!!zgRi` X^W~iO>m/]+"{MrHMf"\fD @o[8a*^`G@W6 hv$4scUK&Z,έmoopߙ42?{|-=~$*˻*lʆG](?bQC3.j?'Y\M5QSiU$9]b|t89hl䤗FZ :m'1 0G6H,+".vyOeWEn@rw!P9w_=<0ʝIx̛3eF Es˵{IiQefp%N8)f:~#+mv0`كmٖ{'/Wz?6Ŭ4&wƜmg\ϒ~9  !(p6_};DԊj[-DJ`AQlFph NbvQWQE3Vi[u Y684cVyAB  4C ƒ@JyH03# BPW>ro}v.ƫY.>m%0OU,ImUFƘhnV.p/:=F_Wvkzdǯ^hĮ `(4kB(eh -jo1Doj83O6oNoGQ* |afwBj=!% 3X$$ ,V^!%Z*ǨW@"S.B/ӗGc9MqꚴekZUĵe>/ƴ {¤XJ̉15gyRB0 JĨW\+|-*I ) |VR{GRZ; NISTi[دzԳ +*TༀmjYVH\}# [ꌛ'ws& 'wE t,\E"3X)VydYf$pJJ`IPP3,,DG*}A2!I4Da͆0U%>q=4 rF";@.ǡX•Myڽڥ9/82 +!է&+ᅚz:Lv>!,7;s.i+mL *[SOR-ȳrF2fW䄖LȢ$$y6ǑʢBa[Xpc`:&5)gL'o=MX# ,#d*s*H\ķ?`3)F 6 bnLjꡗ4]p];jOyk}&(ȧ^Eb5hrnZ~ff{akywrHs#8e5ketdqʋ}ͯ-khd@O>џP5;9bOWvzE_D+gVw-m4eBGo~*n ?DdB%2+yw qV6ٺ 1 *#  Tq,d_Bd(0@&u.X uUo ubOjˀ @B;Мt=UݺiٱdHhekRㄓn(O*jK$$hP@)֧~9]t~>iD틥 xr}`BuKS P"*ȨG 0R i4rgS ^̖x&)"îXc07DD<󅇼[[%>{L!FHH˲'ކ2uR5Ox(r)  C-,HQ5LF;kޑ4H B0,% Zi]\N]@gp< phDm})ɢg% #J 6sթy]6XTJP;gh ;! B~9aДvV8Lln^:,lm%-K Z ~'?w~4rXBΝޝ|E_ƚMǏٻ|H۸#P_օ^J?st}C~z_rj G7u_%|n@MYB4]ܱL7( [׺\fR7y*?roؿV6 - {E&|]Ӹٴy- gV.k͢L\- ^m2/]d1{f׭1?5U)!0ѿĨ?}OM8y$"3Dl S}l{^y2"}}=须bIA*6v1BD0l `$ I X#B[%9ByR^.}N|-H\qȆʫ^o>C>:aϾaeVjca@8O IiTz)tL cmm;bA3!&,I4#,#T#\wt8qwTɃa-CB+2̜ b3)$JV"+{J͍km±C#"7PJB&:nԆCEmCś4@7(&L2%KA4 7NEJE[B9 &ݽBGxK( BX&G`i7j@5;X#!9`y>TWCWP $nے8Zi+p(r㋑Y SˍU˸Y_qH1ď_ bE.<$wNn Zb^0H+tf-NA3Ѷm rS ʿW&v+d^wZ_Heүp3uCmrJԀZNv;\@M\T^6evJN%|%@8v eGDC 3ZAw鱳7Ap`FХ T:z,ݷ=IZ2[SNB*'ƾ%h?&}0"hAxOQ Sv;ybMע]֐M|S&p@ ,z7~s~@rcٳG= ` Dp o9_ѳ .2gD݂Iܻ;u! p@R/޺P"!oR׻, )eg\=1PW֔tߦ4>`V{  ec{f֎ϨD68%g!@xH^=wmɆǬskSJV+&(K͉f3`dXM)F#8<ǖN;GRuYu} 57m@#<8Ek,]uwLTp'$ bX]:*H1)&2;*7ː}x.ކ,{AM/Q7QI:U7څ;^ql5b[4So ~$vSqEc}I4` Iu杮GF@ƕfQ64m-щ$K3% IjbQHh hC @iqϐZJђ,o2`co) ]_o/;^JW9ډu% c8W[)}vf5y , 2&-bvb"AԂY'KVdC1eei t)t)UqXthtr2!s]%Tb&o]*o2h,:a}K-ISzҗ씯VݾvI8R?\65L&!|Q|<,i7Mu϶GOdcEG^N!\s+Y7b>\Ǽ|ƦE2\rUb^@FH&y˩5gtTVl<6CB850Pˋ"@r>i1ԏvk2]׷d{r-T$YNhLN ΂JQ6a30ݔa]ej ,->hg(nbhrזnWez)l$8 8ڱr,7ؐx>bQXX]/9*gYcBJB$n  mToj݈N(`TB16$'ǻfxssp*EV]dvҦWXp[X{N)+?:lJ$B<Ƃ&LV5b8(ia~E =2\jA-5Ud!hP@sB 34Hht$"Bx($I3#PN[ .&<(U_&>]SFQMi $p5xLW;sɊR[g%+2BF%|Q`6{K֝,X_4mYH`0$|yA wz 3y_A ewRcTˡxWc3lcGfi*+9[ (SBb]S[eohxV591DBu+©Ė;?.ױ`P03 0`e# G BAf@V^ԙ3P>:ooȵ>W\ee!Y@ h* S'Ӊ^NW@Zv{@t-5 OA|g @TBʹYdr6)+`x[ D^aAl<\}zC˞?tV5/'pBaՇ  6m7˞-/n6S(*]WS[uq+}8 pT=0p @9 )Ӏd\(B)v_S5~B2YTtGrxxR~Us¼uSxݷ'^!XM$,AvMEg(iy w& pnK]5?LszP), DNwX23-T糳ٙݓU 2[F{ڀU"@ɇקmqXO l! S04`"$:sEDE]1ںQ+r5Dݨ* wyg涫ޚ]${cB6.ҙs (8΁YQwt3u,EX{]U>ss;G NutI۵ ?r6g׿;,x7L @P  iH}kpgGLZ,wFx0BŊA,ya۷LO<ׄ=xPG%mB1PR` @dHS'=Ztȕ\Qϐڳ<5x]ۏ}A-9_@`13t$/Tyec*0c Yb"{ML%g=b]̎b {1Ul?;T1piz׿CÙ7n}晀wDDQ):e <*QV!<#|`xFfNlGF{{?̌~#J+'_~ ; A 8OXH{`FG4.l.lP$nP[߈دP c),Ii|,~5>gq4ǮO Lgi:8,ՠ\~xSջ{*Ջq|Sa~>U_[iVh*80jK&-{@zG[楄7k̾oo4/G5 YaU8/K]5em|"brI7$ Dip޶}I `%!U<+'$WCDJ3ecZ$WWue'K~ƽ6L{ۆi w/58?q󓷛.IN7t86jh'vmUzS@A 46,,t5z[.B &DLHWOLOS l@7FȘv&nȡVzE ^b]" `ei Z.tnH AXWV(3Kn4a?/#b ~x0M.W\BVl áh8!3KpĹX;#JY+4jͬׯn1z`@\IV bUb5hy3&Bwp>R5M G'ϥ< +@gv?g +'%,F:]KfX0U{R'Z5/^j <"(d8:4 W[S1$,!Y*g*53!J3 GR0UVBq֦t]ɒu sټYk!!m&,jw ""ŗҕ"<#w;asϽܬT^&#W-J\k=_1ŝ/hQi hI|k%G,3s|o>|B7i$sƩ=\EQ$a!7]Sz:1/4o&'lBYLV̵Z}8v+hc=g(׎w7_rn D3$[ʬc O}&*:ЖUcOvj: mp}oM?3j#LЮ뢠&LlG8l0E1f,A8H3זq ͏ަ$1QQE.OKf n%(| TZW  (W<xkA%v%B+97-ȼP*rvPu&5U2+EfJ)92lҒ{>0&!==ڑ:Ҕm7kXCz-p n\Ѡs#&55% ԋ1\iE`yFʔ0! F w㡦)IanHV2ҥ9rj̼Ӳe| pdp0 sφܫUÀ aMD&ū&P {JCh\tL@_ԁ"$U3SχBiPobӒl'nN'&u{KLZۢfL%EO-hBAA \{K!GKMrP VT.0 J(wH+2c}/+E A`dV4 uI8m*ͼ`<{7RX̖/Lhgȫ([JP4ZKR'Y/fl46 jQd@}@@KeZTQBv? ު3ks'$pأ|-Ń|^sC`Hab! J-p򔧶 5A'Jw`rf7,E_xW[,v%@{PyMK2EϕUĸlphneЮ` :fIrs$[7iV8|>&R[΅pc*DtU3uvK-xWf,yT%9h-fGs?sQi= >~Loץ-|nԭO?޽V׆3[K&~4\^"1])\O=t@em;@@l dozaӥgL;m; _]cdo x$IIbDl)v3g'Vk>XO 2)B;;H]R.$vq{=Z=c `dmDkjJT2٧irwi\X !]HVA xF"b$<f~Ur5>Z)GFpL2NjaX$Cy\*5upK8k\=݌QCc@)0E ["餟.TyW/``QB~zZR!"YH Y,-FL ֯WB'7v6IJdn#@4vF9^)HmǏ ?)u$U#8"4^zU1EL(ָ vS<۴jrUf{#> $;I_؏w2&=C*/ROP&- lR$."1P(F l fI}$!qH^{ L9J.#':!Qi `/r&v"E32t e" {j;,6'8sk,8c3 [·wܠVΏ y1@jY<["$ө'2 b7N7CI 0d(ZSH!"v? ` >(]8 גqc= d,\ f, ܀Ȯ_ >,g%:ѽbI;1EcS9ﭔN@DXjǕ b&hQjH +ZC}.+EjL{d;]qˤD$A\PD,v6D|N(udmcL⇅TT@U^.?  ք"UTѲyfCϽ]6gm hY(c_/~ƋZDj |x"}%O$yζT9؄([L ?l 1JrE9lhd3䀁坬QoKIFCdUQR+P AB uA~  5vnq8V/7 '8c+}&>9hͦ:ȐufoFbi"Z&w{pF3^Ddf2oe#2yr@' K?yo"XV$@ wu5gYuyhƒ%\" ]BvGt*{SRzȔzǼ OE;t9$3CILi<4.򁛺ڦJ@aoy?@,/TὧxN~ 󍁌pWR"9i5oYcLE!DfLH `ݨ{|F* qBpM}7-YBE@ DՄ=Vʗ9'M',$˄,C/B!\f3]xu P'.J.p8%j& PpMQ`P ;F[/0jWC0DdeVߒXW6z5'/a!&DtPJYW`tR5`cB()ȹS:PB"LXJX͐&KT7#PpPvlP^ck,^+p HD~&I09,4NT? ܑo{`ó\JN#@% E8Ycj EIVWw x(.6iWOXaDCSKT2YNLW _9hK6iN"k]~J+ ِRm3 "cnSZPds#^*pCIL;sE@cZEB$ܜl )2<r,+Y*LGqS[?{LQuO3H=ڂn (A] '%J@ĉj+yW\SU_"r)m'Fc8BJH`Oi& 4`ZLkA(vV`Fx,8%XDA:ŋ[mm(k#!m;pQJ[}'B&<˕o@B*㵬^M̸J0r#i0+勍3^m%]Trs So`_ˊ \(V8@I./ kJ(=d&* [`+WY -gB`@Lx0$xfc - hd""Pmk'} ;ShMg 1R"xoGyoRm'pdĭ MHRN':wKYbo[Wl 0˦[1GPks.(T|8exm|Xٟ ;lXpi 7}<|oN'>ߦϑj%TRԐI>7]rƵ@r6X=o6.|;!p;&f+p)z â>YqF\2ƊL@Ki@yLjcɀz@ P(BV͂ ]Ix E&9o).C.tKcX'sRv)/}j.e ZϘ Iǡ@^Oag}"1҇2#`aA/";W)gxD.^YՃmBeZ2i 'oP*-$'Y,u!ҺK#ymxxȬckvd(qKYWCkj(㈆ ;5*zyX4'T}M!DଌZNfmxmp4f3)TZD@Aj.E E98f#' k_SR<` HtdBbf-CG4)&iqu/8\ָSoK ]nI}u8-ӧ82o4x9awI>?X_N~jyȒUxA+Vnh7zn2Pƃڭ8 /RH[2)LxIY@Z f @l}mZާ*M]PH)US (cAÀ 4ﺼ(p#n~wpטXslRcP1$UZ4@u}&e)ZA2v%[YhjP=fᶁ8VTN- DDD#0!Ą')!|[h'"o].}BH%KUe" d"pȈʪq怨0X*]Y z+̪]޺l'6bBdU}`>o IIxH"+t nA>SQ&2 ,aoz5+0QDAb#!KW5>Gn U#՚H.zINw(CA4M#`* uy9%@F".us$&ґ&h$HArZ,DY)X\nbܺA  J;A{ﵘ))tB4\!7VIUgrh\4hCl3uTp£2X*}H=Ȱ[zpˌO:CN¢&,$jje9)#LѶPs!d:K8WPFh$$]P!a1!Άl}g;o}h .xKʌg*Gf\Oا5]%v<. gYA;txeQ.+wEW蝛_?+J:[@6BWтqƊU1x"$uIl4!Qm$|?H)V4W0x OѓͦX2y]FUpI^Wo+]1f~I1 ,"2-FzK`JF(=Y ^ hsoK1\Q A PF*cF 9vz2Ѐ:FU}.- 'ƒYg'M>3BVjfo,]!agA> +< c|w҈.E@xFb/kV%Xʖ]0Y u NFJ 1=3+@Dy/}mySV!(rI= BBhwO瞁WKY Vp<c47 I oE Ne+zc!:y4bÞ.QfS>_3%>'^O~{Tk'WOۃW;WR `i&Gfo.%De(d*tLP9L@.oBeY=[xvԪSnkӛ}xycV/%ݑ C=Y_ɐ(wwc0ˑ܇+d@5DZ >zͥw]6?&hH1XᐈrSK*bS'ӓ## m@ìc!U v>ߤSU&DWZ`tC-y4pw>y\=wO߬.tx:("Qk;tF=鴩:Q+.Mk|vl:4`rR"wRB FvHMYJ vVwx·OlWۗ&gd|1'RCnbN%鿿F~kp5 K$6 {Q@tֆ1mۜQri$WymZHYFSR}]X9ծQf;\J*$NE~zB\ %}9nPejveQ:.ˢNŏl*fZvy|{WwmUW;eW$Np7+:Ȏr|V\5CZ2b 3,H,[>kݞy o3u )KEKm 4 0 iޓp1Ep[wɱұYqzDx^V1DZ%g<}.!x qrj&fa!d)P#OZtBO@DW&%\h̒nUl/mJ0, S^!y2=EU!3#<n\ {#Q?vٳ.Pb̂qF8\p$nO2qeeəADY@:["Xn]XCrL֭{ Rqk >0eˁnK_&RM iMumK+A8Kʂg\"ı1.:@V#\%vsBs$J,UEh3.嶻xovg7qIƐN)7U{5x&pBB!]LBݻ ;rYz׆fsӮsUn'{2%!IѼPOȠ!ͬVLn"̚$k&~h@ 7I|!º'ÿm*߽]GEPr)n,o>ʆ)?[+h@pW<r KEG-9P׭I3cNn\6RQumKZi~bMwܩ:4\yCJϩZoǑp(QzfP45ba! ɋ0Qt-8;T H0쁨 0/Gïid+~bf2YRpy KNbNY/j k=\ 9n-M!Q!?wT& =g1sb^3&2/`Rا'_.B(":B S/z[-dr eW](b1$!x%Y@P4k>`嘋ݘY?uMF}5 `V &Dn2~6k5)c1bjbSv Sp3s{4I;c}KA7U+.90KDH y x@yNv ,-p>.@&9Q8;c"BJrdm9~ r;Sq`YA2TzjTz?!y]~xݿ /΄/K`YͭC~s )Me$vo$\XeU˚1 C\O(#Xeg'sH#Ԩ- :U€:sUZ@ Ĥ÷ f_p&`ha Y*]I9sHȪ65iYWwuz0޼O 4 Nk@xWjvQ[ g +2.ka͖t>v܉o$+w&zUm 5x`ZÌUQIt+෨IԫZ(}2Xj dH. ޺{iJG4X|`8M4B0GK*IJ@&)M3S˶\?anw2!7O* ([!1FIt#$M3xӚ8"]KX\, G}usWGRӱ"mDEXesY.3wÄsn'$` >=ea[ v2J ]i%Kuӆ[553vKll~ҝa9̫kLP]b PY8426N[_b]4ps7RDᬶ+/8-oOYӪ9S+ (Y #(=L4ʞd~Q?zG\i?" DXm?kjG'}sk<]҆v˔@@ ) H\#8>&ʰ$+Tk`> ~KV1!"|KJ cQ#%ҾWc+:0B8ze( &~QP= H2@LbĔ%}[т!tm/>_(@& ,, 3Uȣ({=5^ݬh!DMhOI~% 0~y`?k+a2ћ׷:sNT "@jDEtO[,!b#9꽪<(e3&*h頍A}۴ 3CoAxR ?RZ#|ᬠ!46!+L c!֖Ԭl0@Nl3`5.]B %'-$ŅNRn)!xnR":LHJ[ bCyS$t#[劐CIE i0T,C:&P:Pw9!mh¢O>cy:KqcΩHpUDׄLTX`oH%yH4Ʉ\yIULM;ĂCAMw1ZTE[)AP: BHH!V:ȡ]H򡜶N7NcE EGVCl#"Uݦ jʏ16wwV-&1l- 2EsÅk)д۳- ]<UH8Bv-}ȟ7ݛ{|~cl9·%FL@q2V!̱ g/~].)2(Áۨs ~t]AV͆;}EQZ !}g9)gkn= _9ckΦl~(RV ۫V {7J1s8{/0f-1@:( /jҲ_.MrBebdMx&0 AXd\D-!٢]kRlET&B渵QPx,Pn'kB-(iG%kg67@(qJhf_p & (#Zh1>{oLGQJ>&A=Gs,2P$ ԫ!Km6 ĉ qRؘkfEj帬Q uJP($F/o|u|LS?ծ7qCf㓼禨MM@-'[GDҷMxrfXaSI1, s2 eWf9.{e‚h&hhLT'[vqPk\{*r=xn7?V3 =?<fN+K淈\mUOPA V8%*پ v]dmB8:Deb(=!W8iAcjJ7[Dy {nx2^ZbYbo7Dŵ(lGN4*U 7!VAبl2xӅ@ Cl `)k4fI "9_&P@ KI|(ճ )0q;ٿ;ݽtw6't#9rPAy:?5ek !N/+I)Jpaz؞nE mZjAI[׽N}ulyĒ6TIKT IB>mWRRnk [ ;* 5rYג_.S挭 `"īڟNq -lPBtB@%"}&E } 70g1chBJkEc"à-EYkͽ5q+Hi%uF3x !tW8}F]+sVäsE8Ki;-NwO t gڧ *JV vTj/_L3"J`2L\q&n;Ro-/hvX($P%GEa9T@"+`p#$HY֘P|HduR@W4fSӿxMksXZpwo8?q>FdMIViR:M"VBQNIÙSva4<OumjL\H *W&lZZQY2*!ox I %n Z. +u&+WM;z`JR3^= `R{#,XDNpNNzDV+qE@/ #a"I|{೗6x嚂{NGXJڒ,3) VTV(. )glskr"ɛhB"D4J׿pՎ.mE:?t`@&Ffkk]z%LhrAQm[͜il5x~f@g4.Z4pekWl<$SLʀrnQ۱krB֭5E& d?htC{06e?eI IDKWIKlF0}b1I⡞sIۿqο}_IzAR"U8UF4qZN"q43KEqptdUڿ&n}Б82v&~ WʠP"o =Gᵜ 0@5w瞟Y|v JVRKL@eY8+ ˢ{l8dXLȂ73o pC?ZrHXYK5H* 'rQs-^/=wͭu^ۛql0X{b-&l^ܲXt@7H" jm7fnryljx>A!e9>ތ D56Z"{ GsC]#.vQX45=u"q6NR|LlW(+B[֨qF۞DӪxTTk}cGsh~QD5I'nŐZ06K2i5ݵ]\5YMQ|qNǭQأb[ ٨Q?TV0Fك8υ @nZ .鮅-!xYT 祝i֭@fC0iBqgj``\T-x+ UM"i*ٴ9k}{挒ԏ2ߧEb+e %1vhjkN`Z7c*XoN2ғ,b5I?G4hp&_#qӠLB$!ifI36QdL oM AyxB0 3b7? W/>O μYW&pU(B0! Wk9Eg-@]f0câ-, gS +wf܏/U?Ao$l\|\K]DH@Dpͦ[w,7ԬM$$DHh$d7  )!O&W'r}0 D㌻ ܅_bbj@ t2Jrۿ'fv^\0mE )h$!H4Q̠  P(wuAZ˛g$(Jz0D>R4CU4&b]$"B\NlGT=y6=`y #-Hz*Py%/.E[e]#w\ũ+6'{A9>J$sb{glzx> yѩ^ypgs ,9/d!E G6;*lzM(m `cP3 }|ՌwJF1#̡y( *K^z P[B V V@+, UTI&hàr/mq[ Eֲ.yܸ70XQd&XQ؛rߴoEckP \S9`l˜& Nb~K/玚>BT%lA4a4c@jIbJ1*Tχޛcc'HRp0G\9-ɑN8gZ:o櫮w+#3 zGωO"pB 9ݢ? <5* yDgJ=g)f#~c301H/,:GzNm:$a ZTx3+rN'gr<;%L}G .[ֈ)Mp\mm5<]9IJdwb|Fتm2Zj-E9PMj0 FbiM ~t:mk*c聱^gIr+vO!O\O7@j95MQZA"@^&<xa.Gdl,Mx =:gk)tKJr6!e4+QI$:^Ru]xakG䱰ͷ pCiA[`QtҌ#BQKmow2 O[QQFZij$z(EDPF׼p!~צa[c0Vh (Ҥkc˓68|9'ijN pL╆(/{nzio_Q ),K, )ij()QJfԞwYhZQ(~6GdneH1̑*jOtRVdrWP\TMxN1 I\c <^G!r>q"pjclmo$+Czؠ!mAŰ~Cʟph;^,C\̤΋18B{P5F͌IJN76uVsY(KQԩcXo2= 1OblAྥH⬈h` <{77$MԎ+ ?Yᝆ98")G`Q֧un=1ڗVћVxͽZmp2!(iXn" xf "L<MMŝL'XpE,;nrC_8W7-X |tX5I,%#~| %H$#rby"Y8"8> Va"iKy4grzh8E{Z;xx}ry O;g辫߽:z%o.0ZZoR4롷M!`Z0 @B(0 (e ܴk(K|wv)J$ƥ@$d@g{/d ?Af6F!s P% &Ȓ'#DFb!Dd|9-{;g&VİJ**p%F5)NVsbLj`'طq@/3KNa7(N)>48E.f8p5{8̒i3,ݵM yt1GQ@~a(ǹ$c22Z]lG6B?5g cX QQ"4;K\܇#Q0=z6kfjp:G=bV< 9H.q$i}^M'z)z,zYME-,Zc LG7IiK Iv\%?? ^\E Tu@+"@JZe y ѨdI|#O,@1S8kmzdq$8sm2N&=@'琟q]x9߸+C |&$'쐘6_FmeM:Vd ֈ-G@]녱΍_Bރ9Jk>rpA0|N| bKpp)r[s'~:L&jW XK\/*ioL>t̪o:4v pƪx&m"1)y]҂ jdHn.fYƯ(9_'I]dPKN( "P T7ƫdrrNZkPWv+@ ` vf49U;BLb "T<JN @ʺ;MYkR%bE+),:#X<хrf9sc1[k(`x tFfHWӴ0sS@N[JuRx|ǽ}%ZEz#ZzaȆ-0xJfNȠrJd `ʯ L:½Ƣ\&:;L9*}d2 te/urzT$+O]j]",[1[T> W$nݟ (ňx_@ 9 pF]g;p$np_^ t21"Փ'z lVV}v^) <솾0( - \ԩ,Ȩz"1gV-cG0R$"yJpt7!8=@+J-ʹvb$V;5UOHis]a:?.5<{L;vmh"$!@(t¨R"0 'Q^+~I/}T]N#d*P|y>r:ayۭ/tyxh*`#@hS#!o X r-CL\ #yM7NmF*\1yW'Rh}New$}[/?w~=Ol1XݚIe8fFf7Xř<0@߱І!쫂lU_RRs'AQy1EK%Rm9z]fC53LJ`îzmE( t|ٷnMRQZÙO| aᐴә(3X鮼dXvO`J-Q$f ` ,dQыBw"($zEZ}+V4)8m_^;1BE 3SuhmQ$~&H>.@Sv>'uUӧ< v(O/W[2rܘ,8|wr--H%&Ni>ɠpe!!>8.pm%|$`H- |ιw:^?JxhȖ|L!%ǰ5d,BQLI"D,V@PFy!8]6 نXmN%E~kDLCQ0i)>K ҟN;*vݤ5{\?,}t =o ܑMY9݂cnՋϬa˩- Ϟo{efmW't >2CJfݵg?QWY@XH fAaE2T2BaO}Atɷ8?s򇳊KZ4F8Qs% ̹8!-Y >l.FEdD|ۉ2Ua.ĨDc_'$B 7>j4?d`n ͣ=2}A>Ri/r>5};>v78]skSʩo{U"O6n:Z۶aX@:ITdH.zQ\s|GAWbwCC 8D~W<q]#J*ގEFc9-c1.ʥ"9H2 il{Z͊~rZC LQHi o~U){ea}zI&g_`Z^+}; [ xbwym0оRdK8Ԇiƾq p  GCt0lID(~|rƸ8tX 14bټ!UluT&CK? | 4=XBТbڌˑ] ɴ4$35**X u&(3Q7t-r#~1hk,>d'Qu;ŠX(&{j{_&LflniLaj;[: ]! p4h\2:{l..!GACF,A R@d{u$W3b= qbtWMu]WYyTt=;~:'sJuWdF Iӂa* 6RiH]e7df)Ok 45kFqsw~(,"E]5,*&.;,| 7`j:+"> :nƆ9=hlXy' B*Lw?Bt"+ڔ '1x2"ѓb~)a|mu3"ǾVOFIUWKQ&#+a\ǀD *efsdc8< X \CcHv`1 XV%HȉNX}{l?  Rq ;ۙmj}p JFp= [uS;J2RR4J.\)͑!}V>eY)[醾=j!wGp;wϰ\Y Ǐ[s7m^DW:7TY~@9,-s }~a%jVQS^'  nހJC4*f>F!j9S~0@Z7b}ު:ıQ(;;8 ! ]Ii\]ԖqL DU|e֫.-F,$}jG) c~y,rI8'N,FeMԱ))e~>יRJ'Ʈԃa!Nd{ѡv@ OPSjF Y^8>dܱqk qk%Mo_/uKo$X]3kxT~E=4Bohʨdk8%j\:xzӂ $ $.I ]ہT}([F*{ڨOwbU78Y')D{@aA-uUdt8ɫ,D;Y1rQX}w}K#K̮mԡ rԷw{F N_feF5t"n1[2wvC̢T[UhRQ/q#_RkbLQ_KKv Lwޡ쎝t|MO}JճwqkoڐLX{ʶJW t0+!kzw֑,vj# ͏uAU `oovE8?OjH;gEepBIYJNɟB~Ǡ$_dW6fk8YH-R?Ǻs'AU;~ ^_njE" [Ofɿ|MV/~=Yt?{.Nӂ75Ƶ*A8qDݗV-KYSĶ]m FWoIݶbVg5.C,}K]RW7u9~=IQ*$Q@\KK ,$%ޭ@N7ZaQWcn`}o4 "ð,\P߹plOb44¦ IŔ3O3tyIY+}9*8SjWJ$\o8^ڐyV[n<}BoDNe+XC z:]2S#:+껻q<-ne )B$P \Svr\ ݍ@HSx ީ(uJ܉~2Plvn& bPxr?m^pL.v!<76n5.rbe8kO޴}K_.A׊*'uGUdȮkmcf~~s@ӏޗܧ6bc?7)S `VuW ǪU: UUy ǒ%pnDBWTb8&21ПI&%ś@P8;UoO-)54_II\p|`+ZjG vm$ ji8-.\)-vp߱b)/vgOnF-<K=wSr~]3f|fd7C=kr^?1jEx5I›p{_<4rN7'ZQF̡8t,?l .}d9765x AƆ>TP tMD?u$;䒕A&v'z}=_ [ K@h06u"w!+w*.%sUMu}/iKdmt㽷<t&%=3zt\7RTuw:vV}s8t) Cc$=*sox!̉ND;3 ;?$"\>5;?PWG:z=Gv>1bÍ=^N- o2ZN%Z?h*bC{{d(3D'ol._ݻA`fp_܃mqś"QURXcɔؒFM-l)T!IkIVͦ@HsFY2&QT]l&Nx6_2,Nf.J0m:dE21ɓK)׃s&8 [#뼵XW75»i[Ӟ;<.5t?.-K®:1iNɣwĉ?CqkS+WޚojqBY||C]o@ Vg7zzԃÇT}J7L7Um/TXm1bձoܞvK#]vՖ#owoێf2s{ ! z9խfa/t@B9YMoL>~um|ZijM) >[/,3o]Nv6uF>wgx׻)*քUs3ݥ[..SEz̅G7:t7~9Xף3E[@-Bɯ{wFM}c*R0\pw^usFfjnFpm*3o/OV-kE6c%N9k3i0{;?Y$?m(jVʍ8zl5; TίL4sw7[93Ԯ *'sߵ9b3Lk+w<_XRնx}q }|αÜoz.]^qUVG1LCPp^eq{^[3"ZhM}]Z-jP{@B{Mx\j סE7[kz2 P vv*e@\VϸNu%{zlў/K߈Y-ZVL+ju쒝}45%p]a__vg _~eQO?Y m!97U|kc|4émˌz`k`JgōbS:If$#O=%?֡^6_e4snβC{Cɐ(K\;CWδhOW-1߻{.,j8q^|bW\uO_[4t5" [A_j.HX8Uevwe inA5 jh k=u!UΞ{uSkTajo"zk+UfmT?tZ~U{*ͣ!ąݫ6_OϬ4/($Rv#ui7ྻ_? ؆*RLZ/Zpם{}!U"#I[#R/j0"I갹.\lØP1_}FqO;6đ;׭\'- MCSd%2:). 6grd B&P#XxKHAD xHZlԆ8zi|U t<"Մ#n:"xH$dej޸׻HC[CZyA^ӁY9'xW7/L2ȃ& 80}4o"9 9{pk[5-< KoGQfN605-KwjjM&.+sdfEE1u/<+w#׎4գ{`cٵffUF$rMaE*W Y,_8jvJ8PІaK7>h S >b$ޓuSYvR{>wFw꛳E> HOƏŶ԰ݝo߻hrcw{ Cw52 +tWGƼ!N"YT[i';{7^nMΆtRh4=G?OQiRq206*2pdpx߲~x/HMyA5S|7 T29n]c̏ i̎4I{_*Xjk$kJ!wW`$mkO]A0tA2c}cѫԸTپpF{q ,u42fPRsI˜̄~0yav̏Zk!otȐR`=+i"- NA,ow~h^7-SIS9CĹ^έN'Ҳ7ZY(r*N֞њ^#sPa(sCus_ZX ',m|fKZY+)5[2 U7o_g 5~ļ o.SEnQϿ'[8=Z2,ki))>=c[v!&ulCF:/|2UOֽytQA~6EȢgՀM8<0R5$gսH8Z17c+?xCj3OrpK67=t\H@);MJGӱ9Bv୲&$Pvnh W[s ^6w4nMi?*>z-wJ3=k4'hzjg]sun.ܓZgƦbPfec΁y#ir$PM{-TAu|ʃY os29-֙H?ٵkEԂw.赨5U7Ou)YǃOH$zQiZ-˃kXN˵0ۥЃqōg4`ZӨ(l K*`m Hp>4*P;( Ƴ0|>O'uoYi&YrXO>vQ;?SG;~ڪWW!-bYCh֨Q :K"4}ygJ ]Om Ƚz-٫]֢^ldaA4J[H=9~zYmLC?lr/vk%O/d7꜑{7%q%Z>>46!VX#YN㮱WC|/9IAb~Yý_c:CtgC2~ 1[i\Mtݫ!C^XgFV[L*Ugֽw!ʉn0xIsIȄTk|Njl55֜({~Ư7u;]oX)#rC]%)Y"/"+YG<%Hd]߂uc#Os0@L̍W|^[dzf7[+RcѮj^m `f;YonVUT9\9` t@~^?2 |AQd[2%ݧV7,,]]LkߜI>yE#Ϫcz*3%lm+Nu\rAtve|?-Zڦgνo\#S;}ۯwv$՜XW1go:;[TwUmƝ\:|{~64twEx\z-껛f?%<ݕw7Yvq10Vqm0h\^E.H܄{6~tGS}={"_975zƎ~xj>dNH~4~O޺,2<5}{ţM+hzԡ!r*ԉzawYgzG*'Zu !yfkTh'\^J!ɆTQӗDƾ]_8Ҟ[\f٢&L(~T>SlasC8%_*ץoL{aQ *b.!TLvwY-x{q)^MTnV#OmX;t߮ZPⴁD 5myz;N_ۻ7wؖmնR5T"5r>S8!=6KBI'\&~B,@T>OFs'n|Pcyϲ+#we:?X|]#V=@|G 12#y Ow۸dF<`زcY EVVHuc/DʳW,±GXٷu2%-a;{ gg?J[̹u'V1- /֟S'rnŗD^mt"U.k 6zfawWӰ^ahAT#vC-+: З\M쌿S swoi+3y;(FZc*۝^Q4{Ot:-ccfpHupWY2ϞF]Qd՞Qӷ_Svȩܑ{t <NjXx |eOܕ>UuthKOjdc?TkrzHJ8g^tӞ7F*k" `?O/qU{YW^J v\=LDU×T IsMm]t:V,;>~fC'XJv sC:-txnΛZƆ~9|[e/+M7z3Ov~,W Tzsskwn !5~s.:񸍉[:k6!/>sױt ų_7uW$tp۸a82떭@Z64g:K#op/l$\a<'XUqqdt[@iEaIHn Iy9 Y!dtVИ;uIܿ▭@Z/nK1y9p ) ~u.sݲ ,JgFƀ֨G_Γoέ@4}3O'a_J'FnbgL|Y, fݒೃ#_o#:U^GzƘ`0CXx0,/t ēź|vt<6>K&"GƭHWdƣ11VcCUt4HMHepO?zWc&0h];4bRv41={ˡ]n[棫Y7`䭃2)gHV9ÜӺ7:oz2Ϫ&Si8XP];l!XQ3gc3SΠ |3c72=w^HOnűsKgbxf䡇W?5iQ$?to|CVM3,T.vZfcziMK}侎qUIYs >x'?qϷ(wF р?w{NKn^{?LwTڗGisyw}ɞO|t_D4Qh~tW)>χH=rDhv\W$?ܹ9HݓQm;Ԡݸrpg椎3]""i-]Uk}㕳o~׀pHt|#u#ǚOkzNĺk[vԾt^|,xr}5|&g擶ވ>Q/*w˨w]SwD`c>3onԜy#9M 5{͏O-h;ȵ{<+ű@ix}p)/Kq5gni}ln|sV TՇ/0ѻ"1Bx͹Sx. ×B]g&K-U '4,/~diZpQiWs[8Y5\.O~]\TTv:Voٟ)(sݓ1~ƛTT_䖬r[6+oaD_|{Ko,HQ.DzUԧJצ7!W} p }l7ud^;ŨF`HY{^&T_hS3k*\=- ^MDzٗ&:W,pqe坿>r{ӘU\G»m(@xȒ8=RnYnkR8K}]NI@$m'5gn}4C95[̛t~3v'{m^;bvΐh[hCL[Wm~מ*[Ϟ]-]M8ˬ{;Tr' V.S}u )~*Ѓ/5;Wԍ_/5v!y¹;?W hS%>~N3/#Y04PXX`D3.7xύcskLc (SGfk산qiSxYݹ{͆Ǵ$h`d`$;eŝD1.k_|nk;/}q˟V{ f~8ͧ.ž\3YMX[*cT.9~n3; 8FuzV+{|H)φ!OIӶAWXyݔ3uMkw<_j>}˗`o$ Ytgr~.2$=O^"6X  X\)]≘6 ޞ۴+|-xCs6ްeAɫicqAͺL&;C3uBޞWVem'' {魭٘_rcq6/iF*\m-aGAjCIq_ʒ AW:|׃2,Fiv}IWſ0;7.^Vځ'`"''6/6 P_Wwmkp1 K9yW6>+}k >y\x[^[,~Dϼ _dXŦs[d[{S-~m/))s<ǹՈjU[3 }lUvG S?t{7>̱g:zWЄ5w~;mw9CT"Ες!3S{mmjFUh;_I;ǐLϟ4z䨖cڥiQ3U٧Фݣ^fn_:Ya^5uQ~Bp©w/=#$')k#[q*~H39\HeO?*Öi:ܯ=jqze~}Ź4 67wÛ{upc ?|V}uӢ;ߊuon}‚mkԠRi,Ȏ맳ϫȦkyiHS;u׭Y5:^~r+:VɈ8au=gV켺R8>fGOtS dsUio}y]MxXu8FWf7uәW s_1jdLS ~ifO˝y|iq˟F(Kݖ;yYhבV,i6} #)s.}|Ej4fwZg/ZtbEͦJ;cwWN@I$H$0Ld&۝]<,yK-4mO'?iB'gz+=|\2e꧿\ )[̮EG/71좀Y ᥈,op ASNKkK)A12 2&I #7 ;gsԕ]9cLe x#:UXDS&/eH,ᗾSex2*=vzg}~<ڷ͆gP4:+vfEՉ/^rJy`~Vt],_5tG~ꦾ5+,>enؕ`[\Eeme:rצ,k3A;YەM~0ek߯Д16*ǹώےKT4Xkh\Sů ~[cƲc~~bNb=5(^=U2nu iܗ".U47A[,27̹2qJ=ojԴ 3B޷ݎ^CJǪ8>'¥oý9⤜,}'dԵ,%&)FouܿuL䵒 mogϞ#r={SR v_Z:J>Fde[9Zb5BݺǔDq_3'qSazT1<{Xm7 ~(X|x=YSN>$d%G]|;7.6teˮݳ"$?PEn wR^۩pz!NHrZ͹߲if2_`.AжlٝC`Tz-OZ 5h3݃r\Ok9'g~^֫?%?Ȓ]SbeٌД/{?ꃿ2WΕշk9]:k(E`բʹxEz$1i(@ Л% i @_mP;&d7wmH+F~1?|jfo^VX6rxGn}Op'xR$!]{#'& <@N`'V(7 "<a+ޱ*o8M7RIxPRn.FbUʚzlϟs - 3B+*q9٢K砻,˓a>nl讗gޞWk4thPkit7[z1p 5h\ ƟږRO-bC0pxk+o8㕨vJ>5T G4ŷi5wq ~S0̼֜~2qvEUKoUkʩNʍv{Iyup]&4Q|~v_¼øוὼ _Z[_wħB؆ ,kެz|HX95\fW?25W!s ,M"bml*ۗRƦVSӘ2[9>%xZJ'1"Kh3G1}dtbM/jَZS YI{ʷT MTy]oAlMB#gr b zXh8vةKF'ylpdTf,?]5D* .iz)27+$-sIS jn }˞=C Ѩ\Ɯ՗~3>uJF`YFK TMy NT<s QȢQ%]Vʗr՟_y = h;+߻ ⎄eg"Lv@5דDL AI+wfN~W omߑYnɢo,{Be ف"|Q vZwf [{G5~R:i]/ϸ|j*dZqa! d&ְ9iƐfAgv^iI[u IiLnP۷H0G'7ݦz{4N`vu'Jb~ Wv%9>#(D}򘇿լ zd}#S_zpcIWqrNٕ+DUM) *b3ѯj#\Y5b+Nv$bFBHh\U~&8 qZ R:mvWoROY|xgS#S`Tx:H"(3+ \z|?#-[8^*f̣}mN(`>˛lڤ#^F[/],}Q?iO"f~zjc/~H0l8>mӯQw7OVлƛ%y D޴ƜԎ; tV(*y|Cpůp K.ɓ{@-Hφ QCv_&pZWXr7?[Y$OwMVK{{Qv)qnh I~y!^H(9meݒ6﹢`\:>r~ȋj,m򛀖w k~_]w?RⴛF٦~x-o߾R%kޑ>/.j+@2݉/QbK{Kdt# 3n$ FcP#!R0QNN*at9R+]eH7'jOn^MK[,y>o|czyq U;U#/k~>\K^՚ͧl>"-J܃Iov5gͅIX'7NF ڋg[]]^0r_3S=JzVS&^ <$?[NXkV34$t 5?l𝔘8vƆ{4m |>xjĬuZi<W]cń㵴f22gʈm(X ~{/]qWݻbtjq==Q򯎩cR8?l~ZxngzWW\':<z|~aw$Zs{{|qeݠ&I@H]͝⛴{/=~hVۏ/7Us [=Ε [GLHuj Y]xڿʭv (Rk һ2ra M}Ь6_n/֑riJ&oLTĩP Do3XrO(J P) mhbfJ~|[oJmUHk25O:ٝX- *I+m+鱛~.m[ޔ{ꡋ{^\)LDT_`BK_yʻ^hfs;RAx 265@)P. ~Xշ_t/g^p6ݖPq\n9_ծXg+;5dM-&En[?_׭~ϼO%4|]$=v1]YgwVnPD1C^+se߹ Of͡ =r< Dkᎉ8^|&q78^aiX>(0X$z;c6j[I7`@59M}Xs%P⢼;&V{6Ji)BV^8jCGP8]?QF Hiu[wgye6?fa#pO"2qE9lo/nm<:a|ݯV"I@&OL|{_ *#gA橀HJQRf5sV0L}I 3qDQ*CLr.|`s3+gʱ{Ѫ i׉IYtĕeV3Ic?- 77 Ҳ,{z5E<ƽLֲ(s jY6=} AYج8-&Xl}c9SK0afw-ohʄc/\0B'oZnۏw76T5,T US]/Ż6"Mp~lB=wgkX^hղ+,"9=LDkGOھj &t%߼-^8 D#RkNP{g6(I/B .]*? ȭ7svOn9Uֹ֌ɜڼc J_Ñ]S>L.LY~oxNp=KPy8&]jÆՋ}D~c&o+i69vp??T,R^_K)y4awG$&wȥU~coFR$TA = uF|8 ,l,Rt'gT R{Rb^nI$@9RTѳU*;=t_?kfpu3;nI٣95o!3.;X1QDɦszb4YD/n^l8`6 ѱU-2t-O2"wSc_כֿuHC1z΀0Kbr6麑ZD=&$/?:yzZ6 F9"3D"5Fܞ -F?\ZcV҃s7e@" b*nWZc@WKn9| It1Ma/$!9ZT5Z(DnxpyHQDF<~ڜ9:,@>[OFp ^PaJwʍ3q*'J{$0(3!]f4> ;fq|ϯ|_8qcF/K\u\xީɫo);ӅOtP0XS%ydH=@‡KO#Sܜ1V>: Y?8FZoP h]M.mMokRkW:)誊^T\PJߝ`/kui|lR6l]9mo{i+nSk)| JgͻSO~2a#L J{񛥸~'Me{d:h3>yQ+昫 M5&ȆO ZT^/FD|Q ]=:ҫ/5ң^;LţIS 1ٕ$:M{ z~(nLV[]!][v5wਢ"yִ־ʺz-5{/ra\f)g|9UUkoLK-zdZJט~[WC*ܻƩᭁE~<6ő&W{@Dy{Qo|2[]%X4! 0!p`L㜳38(eUTp__ ч@4gi8/=gg[șHvҷC\ZyQ0ex&53]|` \J7ri{H{8]d[i{\aof{I$[h;tұ2d gJM)fxkL|H#u ~؟GZtټktVsʟKqD.z+9ͥ.ֿ㫴2;.pEFG$|F`"`5CvCgc,'4lk4 ݗ%ܟً{f\aFB {=_tQXu3I=T+uZ{C3(WIRCԺ"!. õc߶36=+GQ]WXVCo^[V/H@mщ*U0 e&:Qa<'qdR]4D7f#:֗>pH;}|}tcޙZ3H&0cOמv;/:6}YzyD'n/^϶koK3è|wI$y;u2N2]"|Qr{ س;^cUKF.*;ޢ'Ϗn_ک EMhڗmi՝9Didz+qN,;f:bۢP \~7ڊ?W\ T[yk iӻV'߁d˒Ӂ?f =5M`윆mť ΙlY*Hߔu馚W9Qq܌4e?/-՟7\7z5k<- 7A!ϰ8}>$~~-K* O)f:DTcc0rc E̷֟}J ۭM.{ ?~)Kוd{EdzIxL~_?p-Kᑤ^gi:M[[\yv 4B +,I~;ߜڕ.>|} ?ϟ9(Ev urOZ fw{eb;=|Njb]$ۼs ?EyưkU,m|X:!n*C:"T̺ڸ.mx"ŲҮ3[xROq :! i׫I`;:#:?^jt}ɳgZ~+ǓDc$#Dʐ.A OyAuC+/[?WbƢe#DFMeH٥^E̓huwݲ ~RF' 1F!Tak :]6|ߜn'Djbӑз髶2dzū! p "x`"D0 ^aODaц?B~Q6x__^Q +!ޢc垜O|V̻4Ҩ5&?s4M"l8CPU޸w ~l;ͤ603o:r]7a7;2W dg&CAOML"5]60APy PLkJHejRykHx6yo esrC @.yx3V |l(M.(mn;)Nj jи}Фg$x!TTDFösD k#?~mPVQxq2xPq :HaXlߨ;UEj3ZF#Rߢ*٥*J5莣N^Q3 SWFLf& +{1BeP[+m HZ͟WڼT)t-N}pj꿱RY 7|i tp1d-F-r~js  V9e a$qQ{1s^鹅3_/7߲ a<{Ծq}:Ҭ: f8u[,;. 33*ɮ~m.دy5nGg? 5[U򝪇Ho~ >t.&H_3c7z7:>wgTx$R_;UK] =@vݹ ^".?լG}wvO`/)IԭNl=HCzN; lF2͟8 +2Ofw8*J'gMֿ'e_kp+۝M"LUPT]o:tlz?Q3slz.O/@{9Hu +/G5@CZlTG l푏va޻!%`:v~((ŷe%_r4?8S 8C,.K1Ih˱&tq̵kVo@Xҧ4vfQ~#}; 9t"k^%7iC'Aʼ7;<|up+t_&^-}d@_L @m4e`!558g'Iן{xQWlɑ yNdDۢ cHBZ:sۿ@z+mة S1s3T+7>xphihpvSE7-b%J%a+^I ,>=5WasQkp狘76rIb oM6ڶ?wy?ɘ?^3 icI\{uE?WbrK0D'Ń3Oe4ա~+jx(;|’o`6ǟ9 &7l% ׊?>;ίX$_i_NϽA&}g҉>Y: }lzoABcuH7_PPm$i)# 3O+WLbe״O*Rebȡ"k0]<}B|OͥbOeB( FTu/U~q:U^ 4@$IC'h26S?0`铥]'J,ZꩤSfr s>l/։MWqˍ|0&J; ?匛6O.Y*ZVU<ӓ`ι+#0olWk`7,HHk<ܲf ]Ck 獾,6j7;:Z4hE=/)z]RJ0XI2?RvΪ?|Kc2Kd ͡wR}o }'N5F]@n|^Kli_=W}[TN#3ns7wgUWj'_)FLZtkiINm?~wtoU=ey#oB>\5K_yaƏ 7wݳgDž4w|ɿQW~CVzOӴ֭_-\Uu6c{d|] #T;>__q;/sQmu'ξe1{q\>e=$'v?6I wmXϤ}/:j>D1n.!wtwP)> g@&鐝 -)ϭ滚c[EFM1fziPXua {?8arYOe¶IGW?. 7JP.0E]Oln.'^:/?:RNVH3^f-+Qnc%̢Gś9L5ucߡ )do8 ݓAS|iL %Q^@ `y wߒX)wv=#Wi 7&dPO$6-z{gmxFr[ri` 0E~voL&!g2~_엽zVX h1=c@w$HHQZZ @j@HڒHh&-=QK`gDRMXgJi`@&]r)ҕ%S6ay+(qU =_{$Z<:ڧ+˥먮'i%a^)W;6Cu >wv"73dqiXsՇt uTH)Ok1i[Xåښ6RD<̦ŋ:AnlXS[% T=@oĹ-x>3s v@)UL}khXffTw7BZ O[k-/eK[uVc321f4ވhy[ϹjEt9 Ķ{2lSciL3>rG~%"D|v,Ěu5&SAkKdmcٙ,IO}b dUE 4Z&Sx,|K_yi߇5=@G)uhc@#̷=ZԸtioi왼*>@G2V4Uaw a}KX'`(e?yUݙ5Aw OCv-~|vؙf^U/ozF2_$c e.UFٕT(u H2|{p=Nÿ} s D\qn +s%.ݒ0+-Lg4NAUJ,ڑrY|pBg$.kJC,؆1;'ehO刎/ =qB?RVj/^IbXm;S)t9D{h4wt&ݕ8hJ'v y]w<=-BKon{ؓ:NY,HuǢx] |oFk.#<5Oz6o*җԑ2=gF7ԑgEXCjƁUu$Rz|87PYHC{w'6tr:adנÞr搩 I赟'&H9+EەLu$-Sr,cLuhPs7 K%8a;]2uߔPylઞ>Cg)JMV{쾿[֭׬⍯K|3ƀ}X7m{Yb0r{g/7|8s ڇx ̝)}Wxɥ76㎧k`5ۋJҚtC ]gN>o6K )jw`(ꋺG$#u,NږƵm84z^&D~׼EV{\>v9uxm^udvg 3+'737ί0i܂uSwg /=iUqU7|0ƹf4}Ǘfݑ1Jn뛑l/'lڳ+^}g?^g8V5  zx,l/޿Ei- Qk&:wV7A4=W?ynmw)T7p\:R?n#[J9P dbq/pIq+~7o/q0|T[^wê00'5 etmųc+nI{d:}Y{,iůՏO}PW(D䨗6P{Bw{)* %=}[FǽAuɻs᤭Ɲ{c+f [|&ԴJ; +g?f.fi2;a^3PK$bD6H$ 70,$ h$5H0K 'NG%ľZX!5I͐j4״gݶ۟]2=ozM ߌ3\. [N] [az͛,RӭTV}k86+R%G;poJ^!2{&M^6z[&"hXqM]?p`>)-Cιk{ܘ,~AbCKlX+Rt?]VVOXCaaɦ\%!Au'EUSݙu4ˮ18y$T4F1qpE]Zl+b`yixNqke@*U}c%)eO&/?ZaJ߰ qݘ'jx)𱓺mxR JXzKL)[+K \:\S3"BMw?!D̺)\OWRdHhi+.?dDOf71Sjyb$jIiJ$8XCn}_55[qĿX <:Gj]h}BCkFۡ{Ŏ#uwdA8:DUԝY;Tcֻ>4N"d:ErٝY;CIx׾խljw?`:)k+62] iZhkvD>.@@Yk L@σpa @ ܔ9_U?~ԁZ!鼿--_b^vQQ_,?HP)uwwK{_ұxL>p;oI~5>w7P܉ڿ<+5_wg5N׻E\۩{7;k,ګ >vE={ƴ@c]\7Gܢ:xbXX#]a=i{w'_r9Ckk*--#1+ȹ+?l8kn?'қ}yd62[*䰐|X₞Ūv+cGϚt;ZMlڹ0o5hQMݿx ,¢aL`Ix|A$5 !4hH&!"(A\[f- ۍCVEКZ 3N|PЏSc.v㋨wiC#” h˗2D@^/_q78sO=wt*QK^=_k_xztxP {c,W4r S8hhWg>GHCT/T@XG>#!(gH]: m@'$[zI(k,dLv1& XYAD8@ ]jʡ èqx! VCgP[4gV,[O>} O$Kyx3ֹ$C`G9`2J#9fOHY I*yPcDkf'Ahia؜ Ց5œlcOKZc:^),I^8Dr86xu,6(#3Y{QA8[N#B3<,DֆXh;%rA5 $MA-:,VGYvph 7%NJDI:עB!I&#iIXmB8Lx8_<0 #Yt ?{lKs`nLs{[oS|u:ӭsP`v1IE=QV+uVG2C/ț68\;MM{gm޺FםK:-MDZz<ۉr,g zя=:/?Fj<5lOȻL;Vh=*\rKDakj-"̙k R1X1m#[nT{ppr^=$iF]}kYu1EOl—Fj;um?v>jiKKrz9zYӡ~;~#=F<~VnWdVlYŜհUե*r&޼Kgpwjخ[^B΅@&b x/fݪ'DzD^y"B_ጌl(dVM5Pu,*B ;!̹"pvr伶9ܹpGxS$¬kP^0C{bMsF6dNWRnM>8qw5{mO_s|^<ݹf$21{tEK>(1jGH[ =+[Lwu$+{LJ[pCw,Tnn^;>o<ݹXPSXENrVЍqhNe bٖEӣ=w ǥoČ{}WٞsIߢDԘ:(R-}o |]--/̮/b X$F~'oF3kVR ({g9pWliǚh/dO2 ތjQ-":{ʴ_EVr?-I矝x{]\Z˾q~fS믿c!sx٬OӗomBY/l46kKjׄt}iDt}1r`D;#xM5hcwljuʩ_{'6#<\\X? q"5>T3mWWnhPˣ Cb?pw`NW _xںi~ BƑT:҃/KA-eY"gPI͐mZFy'HܗPn@Ǘ 5edHCǶT'NJvHyim&/Ix qiiǾުӫRH+E{>#a 2(D' 礈Dup@3r8[k̒ bI"jP88ɛ(yĈ= \,@=Lup~)K2hځXQ[<7L$yAs:m ܟvs֠kU'Nu`n5)$;>υΝL H1{WZ#翎JSG"$ͣi!RHN&"MUaGHZY4u$#t$GbOGN^:R6Rck˒ZTvS]-r:`-6';f=,}~PsH)2"j:!YoM'JOѫUj=s7Ŀ( m%JPGB9&d>w:ѧ].>t;ՑOm:z%|>p;4/Z"oqP꺭u;9XGg~IAp\"۫=a@(:r/z|y}:z^[}kZ#9+,0mze6_Qݒ.KwQYb߶}t+!Y%;+[kbv59P!l+k+#cX5%iW:&^u?tP3˶mW ,Vٺ^-¬CS!l\sv$ry+OΌ:P*sgJo|kO1vW=O鏶@TLd? ^_ud߽did\6]O}ͲnԠ<7T߸3.ޱw8/,-}Prsl޾>%zQzEJ^s t05 SYmO uq9 l\<\{{I[(܍0c*y!馣?{F$lΣ=ʒ:sKs\Ke;µ $U:6 ox N<('Z9Ȥؙ.|@|xmswuǑD ADWჲh|g<^/t|k#nqq^0Ē=_1`WAYұIֹϒr{z.O}jyZը#}Έv,Qs}] /}Pt t'|}}jh;]\ tBs,P 'VR}?n+ԅoPn}_oib/KMs#P?O<ɀp` `@[Fcoug|'kä{zªOYK^P" lQZM?}GC¯+SɬqHm3 Xq;MWp~IyH5K\B衶x`U_>_5rlTtik &[NN^|_r0hfRs+Vz1whQaɴG5x^F];1Hc!* t-^7yG6)ϱVT1Så:_ BcՑ4JL KEټ~wѱH U8. u?q)Xu$(HSs4f9z=,9ԑNV(2썇tۧwf5WSGheeOvE\BjH|zQG /M oTGXeO崝]USG"E2;ㅝ >nAȜ_E'h {>r:G ~VWGkώCR͎]3lw=vC>,4[+ ݇/-.oSv\9 &bcL~*>2y4/+-zc͇z1ٻ_;{NWAzKp.?B*6 yw`Gy^p v' ]DSe|ke տ>6CJUudo l_hE:&qHra-9Q !wr?qZ) ~x|>`"{@8?퍾hE F~krK[* w?꺩On3Mƿ,`cFU{|W["{'@ޗ-._Hb9\Ϸ畻Xb;<`n~Z17lݏiK3Mche̟ȇM- +s}_J+ Z+> i}VGBpSb,/rooOWpȹ|_ꑽW txV-ά, ‰ Ǹݻ˟Y [)\fɊ]'w=6?Qg,=*T+-o&j{c=-MiʄAmi/pFDg)2%x0w]tA`233jy$+GjnX8 X4FtWp6G"M4\A&Jm@hE ~s߈|aK\e( =P8ԔWV/V#BЛJ er8ɾB)=2#W}檔!_-7=꾯9RlrZ@dՕQth]uXx&5]k' ymW: pA@xA}9X;o3Yh3|>3~g M_{Gc_<60J"d6w_qg~qP+8*@Lם+6w2\->\$@a(shiu6 YEoɓno~ B'+͝?nM msl*-ړ䕛<7b:]7NzmeiZ6epv7DžB!r'{'R#;}ioL^X hs4 - oʋZ햝EG1tHEܠB1'ޔ']]'BfdZwkDҡn=+;+-gS]G,1]wppO|kϓKjn c-yP޿y̦ ;+Nn9U ̽)O[RL{Lo;V'N>V`K›dfWpUt- ?p,vNe*{_u*Ԑjcm4#$9m8 2K(bh^tye꙼7~ٹVWvޑA.Nޘ\H=MA')^8|/_=^5 `cWُ eP7te5Y5 i&eon4ȳ2DRCM몽CIy͇aιkwˮY(S9tBaKO{#?x\ҜCJ+vt/[='^txV AU{㿷>s0 ql:j羾\5;.>9hMRG*;e3޾wB2G+$u$ -ȫY}Sk)-@NTG !ֈRŴP? wqHvtb|,)d0/́g:kW/3KYy|^|r;ݷ{g|B8yM@Q7Q" gY~,nt^PɭbҮb @$J@&mo 'Vюs15"6y5zWt2–O}_(v+o>иTf "[DJRJj-cYdz[~;˕W?waTC.`Pe]>UUp\C~2/ﯘ9O1! ێv_ *pTnܿ B֦a%.7lKϵK|G5_3}B;ֶ<5E WMNmuްϵ߭9?EͰZ9c뵵ISe i\ iB;_QE+@9ϨH8)ݰϵ_Lo/e'6T_3}i[ڹ/8eoˆ-v"t_Uu/R˞e?5)VXeHWTnne"@ Z('[M/=i]>Oߔ`mL^_U\;==) !˺|'fvh8u/i>kzmjyzCƼe|QXqpc=AjCG&m @-o*;:iW]îR  1C^i;Y|xb'cȂQG?"ͲZK7dn8q$TBAXZRqѩp^f&PNW|&+p)NǞe$Н0{+R#苒g\ # /D ("͛=]nEO7=R9-7{&cSMm2=.~IYsPH . 1?I9 nZDHQ'I荿gKΎ0y~n'HV۞$PtTugg'Ȑgqz#CN<8X4`㢱N59D`0ug)My;xF(qf]5tVb0@vCQ#IEx8Jvwk rpOh/.Yr2ZH!ZcgmkP w􊣯%pxC q#귑2n8꾟|[5 VSݻVvuT@"Bew 9bkv?)x$SA2 /9rH%)gd*kÇF%>CH#M;sb*K!Udžvn ABsLwK 84bligevSNo#ئ"E~.KN]*҈c#M69]`"ݢ8Q%С!]MREԡn)6mNЬYVKޠ| @$H $H`fm\ aq$}~s@{D3 *{5R9ЁWF^PcBAn#6C2*P#|kv̫Ugn]t}B/d3/}u?>CN/dt tu|K;&޽HMs6Nܴ6Nve]*1K}Hg +2UQXŊvxq]=%[ CVڞ+C! v^7[.NJOx {k:>0R-W䲜0rѧmw,Q0+ub'u-3@v|Wo~8.R_nXP:' Y=V@ȱ3Om-,jHtسd#D>g)Fc=Obq#>>+t(ˡS(n C > ?/hdyNY.ud!#כ˹{pL lrҏ XTs/v>=狭erQނ^E&^]N+*OG>2+[ yx3Ko2$&p2Ox}GF,|62&J#dHܲM0t'.Fbv/[~`)17,Xܲd]d$; ?|'؞z<ε;cE`XܝCd,(3ܾfUA*twIy{ˡd :)~?:Ul' I?t鉱X08ah,nKc?xX H߮5j}3 /u$ VqUA*e _St-X24h4.F2Ϭ;0aTCb&_~5y:0p_c[(_R2ˢ:c_\]2i㊭]Z \7>1\"G9*z%'#\X: }LڥFNc2!ˢ:&6d_\{w|DN;rbiȳ'ca%jKkogعY퐳'e*;W"K8>tr+HCkqvC*s!Ҫ+5p?27Jvx*(frCy`lܑ'o =qdb~r:rOհ+fOeTlKn;]˰hNј}/"ҝ*FKےbl1Qێn2ќ=K,qW'KUbit0j|ghpXm_<턑*Hrt*~G"6pq=/JVV R UG,5Ցߑi4Ռp cLH:$.c  ,Fj! zM%N*H$$(.ȰEst{>**t2A#Y3psќt#xes5A"ĆЎeHzĖV 3ͣB ¼p,bk?>DUA $E[Hto.Z#Bah#6E Q_D#z$G|^5@#Sljj:A ŏiJOa5=ZDCg^ȡD=_g_Ԓ ISCsztK괍8)Mz~"5N@$b@r_T{rm]?4=:ݜEVoCӵ9B(*Dρ/P] ^xCs<ѳX/<(kvb7RxS$YԺXcwL\U`z ~ʆC?fS/}8zȼ)B?_\:Ձ(@o 7.2\hAS [lN`[籱B5mg,W.keq!sԋ>w%Lyӎ;s 'm|u3Gꆕ㏽Gq MK\\G(˲o{v? Bx{.c"S{*<w\f&9[2k~6ѝ!re\ןcn6ܣ{w̓Ɋ$jAe!y.sl"Y{h%Yc_ί> jA,aq1+~NBNc|4uM2$/[8h@:Knzmgb+ڻwM钙Zձ>#q5wo#Y/18G%8 GR^:QԵ_:HrÚH_sOoQ.K_s&ꢽMO ;rُ>\&9Rf?{h8tχgb#.:4sy_=ͽl,NZ]O`myލ uܥ#I: tW*c{WS6$_}3|k(ףXeKeXU$ P kyKn[}ML6Tk-H*'Sނxd7zWA'O8{O愴?qW[^|wq%O4rz]KyG:IYDZgn[ ^wmxnrt9pL=覣q&M.P"-fn,4Z}>M;6w~1׼ܸ GÏGnRILOw6|n|ŗ58]}ѣZկN*=@7>=soؼ}Ƴ_^Zx%=z멑X2i M HIC-OVةD s\ʪ5{o=Is{M rj$uX4 bӍ -;kNS.ȭ)}\+%(]x1Q{ ldCZ09q xNkb5Sjrŧ lJѐƔ2NQ[ nӱ6ޔv֦4썝8%("z3^s*Fjo56JcxoU_=RݫmJ \=v.NvPd#a?,\]?waV7f k"uo;3EYF)C{[Cʟ~{RCN';Z*Ih4'TtíK_^m{r*>q*N:W$kwGkXl>8DO_NޣF+Ry2-۽ZuL5H}-^*HԺw?j$[i3*M˝vLb|t8Ds>W=[TXX/LByhjVs#>^j)ac 0ʧq9i&IvJ;ǯKWӎC9V7M< >]}+Ց &{JXF.iJu;&o;I4@kΠyE+M3`Q\A_wՑfz?Kn\ F"qJz7&<I._ Jp\R7G?s~]u+Jii?!It>m߅f ڴ @"`m aO uxChm҃.>XWE=4_:}c6V\MoO׋!,HĜȻ %\{Gbmo#Zm}g,?~O xK}Dy -A(NWE?6_j]X  0?[#װCq^焝e]Ԙdϡ-k/tbQm(N{~;ϋ ߥ_,pʝۢÆ__jF>;x8" \lQNQ#w[h`x Qm-5ݏAގb=_p䏪C\b"qvsYCS6OFTM'Z(!cK^pYΫH`bٯw:fto8 Pg/p,S|g:YsK;ᖨrsش=۱;eou1Rc-@ދF*NΫ]V;tրf.WM9  HC޶BDڮ8I\+ߋ„m]_r5wG#-='Gj"5F iP *dw"e,ǽ,/ԑ,DМhը:_ZϿ6XԻbjn\U~wӎ"5rF+h~ڂL;]Tgʑ_-!]eTzWj:pN~o$4-[4ijwG #z;]7$Iᖖ\|M3K@TٛUǻ7qu2 OcGm=X Z۔1ACjJfZ:?#t/MyvJՒQ:zxB kyJR}Uң r矣rU$*%wU{w)OkjbG0UMޞhr\Ot$kԄz ~%QGVĶRül-ͨH>/jකmLKԂ7NUB\ɭE ZIt&hk]ߠNgMs W~&͆M9/ڑ\ߕ+PE  xغ"#wKdwOuA@" ƭg~s8̉v9%{,RPo~ORQM#k.CfJ?l/ GB *{}ux^󁫻:{9on5W]UzNa7'H50Woa<#98d{_ /!SDT=_p'>ɘn,^ffCo B&0I?d]~"̓G GE-3VJ/:4uMEYfvFjb`ICR&Ke O$ u>ZܝȗO9QO84dY|4^|j.«Qy>yĺƟ%Yxƞ*yL ~2Q)AKciJsH?^>oybH2٩%ҪkªΊ$F02&m9r/W:7&*lac4~lUyGp>yTׁn}7F%L,=ђ+%ސu+,qP^zt&qh.kG;I^̠5}+,!=1s$NwNK~{mԐ!]>|OY$C|vѺB޸2U* N~Ν˛E -W{tE8>Yv:~|/7lmJ>ieTh/5d}r#YkG|X yE^԰XͯFj2O읇&[BMCOd̖qjxiS~>1/]HsMv}vq V"Ѫtw,ӈ!63K\XWD߫‘9Λ29E_bΪhhOGn|DÇwV1LP:'msU ʝ :mF+QֻlWq7\oȩؙ+{|9_Z}efկ⤖s __5'|;AKln4WP-$Oje:~D sW~Z˥,rB![̛m4Y;*y{ 42pF(Kӊ)|XN{bk7vO7ɟkZ}id7*iPEtF(=tZ- NJdN\.NwUŬ{?K2 !0Dyk.qZH`gy޽^5;ŋ4lK6z+s'Ho;D yϧ^][U*'mmqd⑝2w.Vnң.89zw2"'Wo9?~^!޷E5vpVE{|dģ~qx]Ըc-όLj?@wk =,!=yA >Stp׮:u !kGWv^1\(|ؒ-y˦zej{b3!\~A5Rrm/cj'O/XNq*][{(燊kƹy(?EV\Q\K[rnؽ J ǩ`.3t![Z>msf}/ߺ=5ɫ"z \WKBJI)Sfo_MVky8ua{k?@Dt!$(gPxrxMpd[N1$JU|f^~{o &Wd4O;?YO-//@˦:E c9W]>ݶ߀5Ǣ3+5-dl?؛|cxӖcIz(۳$ge[rOٿ ,NZ6qz?/=~rfƻGIbHgD8vBQ9_ww$W Itwu7|.BXif~xK,?ۛio}1F- 1\B,ꝁns $YkdIdH{uW0-^8m ږPʜOR;=-n{q7L7UI~ѪW""7''m7c^gӬE3SAB8Enulsӷo wrS{"3UO39B#P1ep.ot~2pwZߦ>sϭ2ʎ(nBr5kS ֻBM(E)eEhXYiT&=>r"pçGuls(.~׮nkBKӮ]pI#KHx쒁#]Jy }fO!|i߮R|ɝO _ zfp\REAp8 w Uo4QТʍoqByUGnbMh>1J >;cUȬ]WϕC( s|Sު!N ߋTWt3im5Gk_H` 5@oB!wbqw6o+N㴩C]:N2{BoFDRE*tJW[Encs_[R:ɇPn:kq=7L[ W&ƝٽugLM^]ql&+nd+7?[׾Va~ ]2D#]˵Y5mW0ĹӍ_D:~{۳9_ȋͦ $l4T=S\C]M}Îunmz$w%OrSS]r>?$*#0 Oq-'i7alO=qpږ;uaqmٻzrY(u&oQX$tA4MmjS;ӟ|]_/1د2lhSPIwRZ#z9~m_4;?=*Ĵ"\Oζ7nD-,N=`V&rPsA#Wԣ^wmWC<;Ceg.ps KrzG|%}G)Wԣ^t;~u~1VBw6u95hgmxBVA۔ghN} Ԕo>gs?됩]ǫf6lڴV֘lĶmm&2rS[=O#QU=ˊX:{ϧ! 3"YY׭]M_y#˖X3/#6OmvvJL>>r#?A}ϯU]o[¨ kӦl㲹Ś}D®v7Tt_ww_ӟ34|ds|k݋>Y6/&%7.Iʒv\v0c\֖gE/T=jk]43@ĦgbN2_ďmu}@JgΉ^"T I0R`gUQOq3dSc񔫿M #IB [H-?;L{o $ >|cy!_]VgVE/`d*vf|3m=*T>P0`|_dZeWIhn>g'˵G參Myѩ8⦯ǹ{w_L" 9ڑKr!@%\ƹQ*-/^'n6\l/' V8CB}ҔV{俻̽vJ8f9) w2iyv/TDJSZ7_~ c|~"*޷(x"/~@6bP=|{zyw* y YPȥ {K>䋺p43a YdHuOeIkmŝ1F.fâJrC)SPƩ} =S疔T-pty[ 2Zx}#ov(i=ʕǻvl/=CR 0s >U0Pyw0c'1 MFn1Y[{K8OM/x$ B!:BS?oIϼ+[Kis(i1b v|-b W䅷=\2%)6xXӜ*o4{/ oôw迪Dq\Ƙtmdsj%-nM̍byfaa;OVoP R7X'"\6*r3+21X_#7F&Vd(Wn"*`p`cB8D^?}amӏ⛚":-@&h=5W #CϬPP"{0 65ߵe:fFHn~OVɸ}DW"Ԧdu]kgnb$?hYS ^*['V !ZЁLSxyv~{}?׳Z8F澜9::A:F3 Z= TMvmpbvzU#MȐKM)w.95t/Fkϡ:$ߧ$4ųE;CHD"dXK,HV `|F1MQNlHmu=2mDx"& [7=-iˎv-s!:eGA9Ҏo$h#U7xg,F֢*$imCɲyg.9#CK!綁/XBS)Ӵ˳sv|6Q9}A)%;0ʈ#jKjO=P##D&. P9l~SֶJ^/n҃%H3me#ch#8^_lXߓY iU xGdaxٔ{6(G87Mm,B^lXw߉! >n4ͲATLx7q>r)bizѫ)!C ig=bMM7\?״޹1-^wǺyi2)Ehr*ecc)\T?f҇24r8h:oB?3vsד)m x*k%VMFTb{഻R=Y(F %_5?6ŹRz#vVŨ㕴=fkιdהՐZܻ`@u=ϯkJM?[w=UeU#jGo:L/֋߿˵ K붜]ߺCm7ֱX!7_$s露/[|ѬR8;CM#sWDҩYkgÆjF΁m٨N`<{:|Z7&s8Uǭ/od_<߽~}UGvv_9NpW]3:%XӸk2˦L׌+_RvYցUזV>ȎW9pçkg\Փ,x<ϓw=ZS{nrvMݡלnmX}ǟޫ/O<_ϞXo%;E=%grN"e?]KsM-oXu?F;/VkD؋DsqAYd=a*1Rk>>ҙ/f%lˊ+]Ӂ0-{%g"m$ _ t:77k.hTx=ĩZ~ W6y9]z3[l'W_H"7&T,m::}{\tE!Brz]s%W esG LM{kW׮Z}BS3t굇s΁Ϸ\!&?It˃wF_:~g,9zvȱϼ=ֺjA7^n`ۊ7??N +urryQ[\p xE,w5Ҧ{/,";=HyRqaV|,g\ϊEn{K޴[,At+-]-s9rk kSK"mJx^7@kN=x}Ka4fwrS<A:˚7očrP?D~N^XW\X5lX Bt?yu 9TRƗ**զxT!q0_Ncg ŶKETsU8p~y.OzB Ox02鎪`_74?GmnZFo蓻Sh#^D9Rͺ|2ʵ2:p?m OCrfѧW.5s t8\&r+#q(/h䧵nZhri^/K:#;;.2n[c>梁ItxxRu+7jo~ipv˞ij[>!mD=:EF?g\h;ǃhG5ty}I*@*ۮW jW5, &7Q@kk{dI`۱!_5I^;TQC:w͏kfiB;o/W8TiQ*zk&J[r֙g#rͪqio; Ya)H.E?J.`jB謸u;oeH`!~8$/{3ݙM [=8acԺ[WLWt5/]qDKhi/?5 _\wL"ôd|.6?&ik936Zd6mTsw*bf:=[;>Ys>w_qjlufo]x&EL?ET^xngS#tH= Tu7rĸie͔>$ʞ:^nm+εlhạ5S-$u;R*2_YEH7&`E^)dbJ9#0#K>=Y"xs{?pSgbz㸗^D*8qrک)>4 G.Žq+BfSY2&t~!}  qwi:pۧE-Ç_U<rnAr@SQIENOBQP}??td)[SMMِ}i]Gq0jـ&3xYIk]+5~aȸZwzm m @Z/c~ 'z;gZ M 5rVKIW0eK?HtH.MH#eoz ^55=i9a@g&+ԯտʭ͍//8m- % cih0 ,♨qƁL+niMVqpV;s [[)R) ~d"Г/h톁 CYg[kgmtqԏ̖V+͛$:w/X%€Qaqh .&f\Egx l% VJyեk(twĊ/צtݵn\97Bdbq9 ~J7KR6@-VNrm=Kɳ#4*k~*Hؽm6/,$q3Xgя?ag?[w))QyB>fK;F g;ޓ[x{a]^*AZKAj!FA{-͟C)-uzbn\"y,՟_K#b$ҫAe|?z-M;ߏT5VG^XeLͺyY*Dz*3w5 UtZ}ǜl?b'Έ}oNiY!ǜo]zv};;jN)#0!=o;%-w]yG@+vS2LmdB0I'}o*N'J]j7 ߞ|PVE)a `茘'OۿS"ms | 8]ߞ۸^TEaF`T3HLODO6D cgP{D-qm)&` >&^u:\Mُy;Z|+jl>јzJ=߳8\5o}YSǕH]w: \-ڏy7698/1^|\ϭW3k˽ywo ' y={5E ikUg{1+M۫i~\WĘl|\5WސH-HR8twwK}CX6^~N gܺ%=IJh ھ+ZثYrMQHIueS ^oa{|hӽ=_I1߮W׈!pCwĠIzם稁(/5uyNIzoQF=WknǑz< '!5}[o^'nu쭬JR'!,?৘xnWpǟ^z|W̳1FI]z/X!WxTF{e?S,ѥgU~B$]d+?"Guk/\⮨N[c $a$WGZPOi~YeOu wv_y Vޓ.WH0c}t?%/VS765O?C7U(Hmomoɓ20MuV r#?=ꛒ f<&8YV\2L AbKwD%'ojOGk$ڴaM?%R\#Cvj4鳏OOfZ-h I ӿ\׵ͭY궨șM)S( 0L"A"&B Q2DIGo88RI$6Pr-ɓb;KEJXm/>uH$QU,ɓvw-=K=i9ceWƈ#m-#]K=Y*kO5^ܲ E2{UmESWSwfN Ŵ#iP'GFsoN%JJrKG=@+尢vQia*Sg)]j=}?2\KF[^Ƚւ;P {V(R8[ޅ!d$ Aܓ-xcVƫu3˳m)0<.|+!H5+;fDlRys d"ȒȐ`7Hz|z[&4þhҢj;Nz,$q;6ȐY۽d.Wcd_۲uY>ocmIle1#CZ`VƆb,(f+r ]Y_sR{cc?ٙFIr iʲ܁ K.ѡipXmT9c\8tM]E{XΦ\ɡ Lp/G$EK Lg9 ֥qt&YƣX)3ASN{pMn ߗ1/WSi׋ #䑰'\FAX\mJWȓd]p$W)!:L%<_ ܣ6^WRN/ :Zx6:6sUPXeԓ\=`:lIee6]׳#:\+*I!0ֿVxY߿(ھse?ʼoY{ev97_fMM&(˘ {/}6[omyaRHdO:܉.@"h(.|^fp%-86{B͔n{|2$س :]WݫL 4-I .WݴgO\Ki qĹ,F]_:c;h%_fuEtu<5W9/qXԳYRy|{'VXȚ=,x$Kq]7j[نp}b<xkC!IWt۟`}g9O=Z娡$$n /pQ;n<5}eG:.jsя4p yn^cn&{/o΂1Ŝ$R^ݯk›˘p'̋[_^Sۗ-{lhm΁h /A5|,Ù2i빓U țFu{Gk{I#IL75nLV@ډZ|A(0A%ɴs{6xо"Η}{wrs3y67琟oD㻣u7+ @W>\}ae8"$Dy;&޿*q|K(b7MWraڗWb=t-9<ҚYd]éR IXP3Q,Ɋٿ6_;r呿QmRFbT8} l~E + i!.cM;Bas  $eew?1o,âpuHcn/pm)P WS_s-?5Er4D Bl@ۇ<7_Qj$-AX-.t:gEcrP;w##?Et}?mAdS˓“)UףZ' [=Rys9ljࢂGO)| wU!Frktc8i^HOBxȇ In0:"4H}UWq6,Fž]@j pEjȗگ ^i,"=}853Mlszd&ġhlVsyK0'` ֧/O+5Dpe:!B8!X|!ᄅ$ND|}l].Kry"Q eWOYE&ԑ;.py"'B^.RZ?wO$8F|AWY̧)๗B,d |ܭw; aC Tԏ4ݻzJ<i'$@p-_ILCi~le! kS$*c -:0OTM"Svd~uCX{ZeO#t^9bC(r`@WS>c q^U_fut0 y^ӯ˟9zo{~m ;|~m.p▩XNy/qR4ǶvJIJmmXRFeSzR^'YxK #6!t;H2Κ!BLT̸ɉM ~IJ]-ظ' /V)Fl"TR8++Lk:#CF#8=m=ɤSnd.r#zG?{!KĒ% d8c6}4U3zer)qG50_=þg;LHZ\{1!">T -UhwZWXW&N>x0kf1 l{wM !8m&xKɤ^׉ VnuE_ο#/{$@{.!#H3G+I.*u$״d/-[<Ӧ<V7*>٫dHqͲ5Jފ7{-eW$)I%>oyuni67?:ΖғoVI&KYe8%7\?".c'&]WE$1!9{k4 F%"c"IgWg@-3Mcp6rYacV҅9_i4 DE_)  SX]njczNz>Ȉ}VN&;>R\8Qų^N"I82usP{A[ |=t-bs V8G\3G>4f> vnS7gύ3EbvI!b Id&_W9axw(1d-o^HV ƣR!' ?ib>+d&č+ekyo2ubQ?qo_Gk!N[,f$ft ~хCo 5,I5^ps-^'@usJZ_lDIJN1ZK((t|D$i·X@rJ=2_/CڙL^d3Q4  lXMTO\Mt^ $W CM}yGL4M!I+$BdC6&ɓlڞ/u߇<`쌚9QAۛn9޽ԟɓiڞ|!/ 73jV_jM Hŕfti{8F8ET`%妌n9ݫ` >_Uҋ-\)ݬ#9ۘ2gjo)8XxGbdE7'ܩt $:4"r;Qm.'.ޙ}=y &˜)#YU!^ޝA@ZEa[ ?䇎YN^4ܨ '{ܿaA޲=h!&*yUO ?o +ժwQ s٢z_FNpff9e˚ }e=I/~eyп78Wqi(^ZuNdcMK$!b#kL+K{7۷6˗>{Oґߩ\DjGZL+紪ho Bs/ 6*$j~7J03ChdH=y޽Wg;N6@s0,ic٧uJtHdH)U%\}[pZ?1qyKl̋_{QT_Mg0m)ƒ47v],:6eȠR]|yI\ڵ~L{3׽x -~ƓZVޝsԙ76&-]m ]Ռ˾\k:qyʪ!_;.} wLuͼU7L6@[`bIsV&|\{>qyʊ^/G\0r͋_ig0dŒb!?BWホE@j=Ư3oխ͜'rբC)8A-%{D6,!²i?E_$Βuq ib;m1YK+<㡤g3eh6DNXy8DFf+U !(%r8#zPGv )SR`i#EIh#ٵjqa );fj-'۬9l/s po Z%ks4ATx״?B :ЂtEfq_>VO=;+&L `eSwxaVcg;xp?7jCSy:v~`h[< ­Ldː$<ϽcSݥU|‘ ōjnʄ)y[_g0-h p).w[<=L+ހx}ܰ>0e)B_ЎQ8@ɋ.{gO&C.3`cYo{vϦ[.4uҵ[ܪaڌ|2$ %'bE ރg.eo<s9\%txObx3tMmwd bf+-=LgCIx=4_C(C'1,u6@ Moc)_eϛN*[A_yR;pp1 v` IŽ?N ~H#z)hӭ<}KXi7"#lb vb i]TBDWiDmghA];|UVJy0xȉ6*Gjluo\괊 }_ 潭9B!|~o9?N92<!P!)8s2r^ۚo˵ק#w )V06/I\&UvI?rW4NFv́iE*slףce!)mտz5aFӒ.slף^JTi6Ez5> $ |3gBW@)o H''fk<J3\t}#շH\CwqM sNܧF\ ~{/K!~G_w2wy3<{'_y>db[JBpHsN\aE_j| D<3z×#n:ئƜsw2-Gڠ(Wyg+9 @_YBE7nN Qnm/N2^sr1s:E:A"E%<朓F\-|}?n7|a/ٷ $2j3wt.eP5&[]cVO?O kK ę* r9v  0Y]e۾ Fd˒A{l?~7av?p咘6^M-0~Jm U/O n*QGصSPb( f.ط~h֏>BXo)i+g;˶ڦsJDcDJdٗ \U]1.3ֳ:i rbرwЕ}}yưk⌊60;q DH$5n=+MI&ݾo{7\c 9u~i[~GfT}[sԉ]hAREuY:SIwj?&QoRi1p4k{=gZOuAl7a~̉k.mּL\|gV½sY s7d]?H>-xXl[0MJpT6ްҹh17+>w&*[jLJ7Hvgd؄h!LSIZP\v58':\˟M"<}CO> A?:uei3iiٔbb8߮\o?}#2_Ą1S}Rِ IƼdHh: XMcU1'jCv<5hrcw9Ɍ5L%ʆ"Uyci-|hMd)D!=Ҭ~l'j*,} ڼu6{,i"C:GɊft ?^##K UGӎC #V): v](8RUyHkɐz; 1MEY1kD.}*#iǡ86%i3+5fcGx;49&kx7~*q# /; ڢ>ȱHdgnHЮ'~'7F93,Nqx vp>~3꫚ȑtr,>q{yR{/6ڶ:Bjھ5"3gdW5!Վ ߓ}[~!5h۠5Dfv ǫ}MdH.K(8,N:-^j]蕂pOzھM"}@sl_9./Ĵ3ϼ'o{^j]+5T'١FXe 9Yi'`V'~IMc[tqn/Un/=U]}."-{غ)ؼ~#n>MadT7pVs~C߉:L8/F$ wg`㳗wYg q5ZkMWi=yqCϚmS- ܙ `@H9v*ԋt"ZZf na:DZ=fSG3I$Duu@8/r[P"0]W'; |袎|W8)>)gJ~3`]9'W T^s@\L8fEWJqР졂$<0uY_tsGlb ]"*SjEIok:/݄,qO~kmo}[*Q3>OY5ilMl5s =u.mBL];{%?&~Q(+w/V2-ߐdHΉJhqm$&k浍v'7GtBY~$_ڱjfڻ{M 5B֮:W'9)H0m/O/0.L%_xz 5 Vla=+eaSK&sQl6 L\QؾbOAΆ7<*ǪK}o_lz-UFɪh+:a(jffb2eJ濦|H/_k_yyǰN1xsz.Kjf6!&aɒ9?=W-ݯA3ejԏ(rD]T ?#F.sM4\jc/ԙsN~vs):]'j6YLekKʯB܏{/ԋN/0;blDro?m텤:=A c(qSd`Z׶v!5FiO7jO'WٺMka/k~[4777,NPt_d4 #cd(oBPMCj+ODr>iðub7?g$ nXXYwHX܅-ܦIغ)VY$*ևAx dTڗߦȈ( , ɗZ~iNS0<4]mvəҢiiت5ѣrZ 1i'NKkg;+nz{Wsq;lGoy-Ih{?? wڸ.%v61iJcT7ٟR IX/"j~;m.-uV}$'&goF8rhf69ʹ%~^l]nyC%/dK|&3*Y[3CԸϡ) ̓tVWr%F>[;?i:̐!5sh?NDiKŽęsZˋ e뙫22;T |yM}_B=U {Fcݗ`fĝ%.۬y)*pitOYg_ BI&mtܻ[;|PPdbwdJ]cΡWN@l)r_k!촽o7JPX,jzOh񰹽k˼4֞[( ,6 =6?șcI->,أ$N|G-i,VbxgD^ D6;&.Z6U+_ Z,U8svawOnx7iskg!J‹^aCdxJӋ;S;X7 j'aCO_%lJiFs[/ iC9zHt{/ƴ/{Z;F>|xi'dkM̟O:u(.zdK[{܍dH'/_{1Fe6{+2s8!ȋ/l^V{f s*C._<4ג2nrJ vXkF} ${>x璷jVK> kQq+y^e{|G*OFυ_V[_7"U#}XS 3w2i @z+ԝGpްIZKIDo= nWҹ~)廯xҮJtʁH(l$HwD/Knd}rz>r OTuwOT?H0j$;(G-, Β&A0 F*B= H|I<@#HLFu$TSs $BB%A>i~FO2>q&gҥzr G@?^Ó> QB)0 ܰi}t_%j{$Qხ*O-kOR0wk)n٩z&aV*^WJ\ p@5 ;ٿc{poBԺxMߌt\gg9a=4b}&~jٳ$\ 5}g_M4 gf;M;7_GH6{۰8Wj:ڹI_[>[myJ&wJ;wNq{vk Kn{R3{v{mf,j?6G>spWnF->mlso\UX&/BWC C,=We\TXW~rqmQ}"֞.xɕ2yb>dǸwzKlƵMPpu=+AϨ>HUYX[|j!:7o.xZLi+Gj9Pŵ;(MxxKz{Ϣw_M_g7lc`)f|3S׽9!# P>~#G4 *N^8m?>`AB3CάLD J[7c @܇Cz)Mc~`!ٮ@ I549 wZ0 ܧ5O0LBy跖ϔ,uZ|-no'X i 9 9OC.A.F$"@UgYZ~ UA€4 UNt%tt(lBA}K1t.eY{rˬ ܯ^͠;JD:jYk԰)G~g3j%ybV1+X/}Fta'׭>:Cκb9yr@v_ MԳȄ:'C:I$^}{Ys5Ϸ |ޓv|E-&+g`]kv$02|e>]6XE ܥw9'?s!S5Ro޲Q^ Ki%mu>(WM/z~G=v5kœaǒȒPWou~N/ WC~c\'lE.hC,ibo-[:;mE%|!~P'3;hewѴU/}ݐ%C:)u@򣴅%LdKSKn(-6||e +ܴ&5K}: ~ݝMkO: K<3ӊTqѴU?Z̒!T };7mbZuOG}w/1)v#pfFа3xg<]Zt؋pPk0SE17V4[qY hюbʤ~i5Ӣ%pE\&drQwȸ*qnG$}pI+L.^gGNZEc֗χp籅kw{~F%e=Sڅ0Nb)ӇG EW1<1]ҎMyzTF+wv~ޘ?<1ko)Q9W=١gaRd;I֯K̸op>]Z{(H?0bA\`|lm -|;[]{ON?qUrGqn_pvV`ߑ͇֍yھ?gYxT-gKU~44W~W%YQfD^EQ; x7htA.H'\zP7UK 4)qZۢpIB>zY"@Tت[& ABn8 I:Rt) @$-tmD'Eb8fP(˄%jiBD  -7  UNtmK\Cgͫl{lSY:FލPUc ށY#šB>=&o6qM_эUk=}=j&r;Y k2IwD7i'ʑ<دVCk#^u)Y#MkS.Z3wR ~meɲn^xyeH{~!Gy;7wkXsS{0Փ*; /+,K&/wQD>~aD4TL?&F2  ps ~9#G/޺پpԦ2=arc/7Mw9 f+ LK<^\Z֭&/m(C47L_nFʢ]v:_4p]xjnI=q,tw<)mF|)C9hv/'\$˷peW\SƮ$F¯0P#4k**2]Ψ{d>s7ߜp䡴Q'pƶe\=ԠXXi~fMFl4GUbyu1-MPKݩc,8-NΫw.05c<7!w5Y8"{+o,_Q\;a`^]]JJ.jwy#kq8)]BĴ'GN>uk7 8i w"b,!u'oa$p\).rs7 d]i2`ƺ__a\~ů뢶+:k욫ne)[JpȍG~qRg#E^ B&LL%hNLIu}a[/k`cAc[iK+/_6xd]SYO=Iɮ}*8LЅ_zԉ$fEλƀl9,0.[9GJҮNo)9/c;.yS%imò9{hįoVIJ[nߚ3|^U/?Y|STNB0JrqeTP TG@%DžrFV8PTԌ}RFsM[ fɠy_TlꝳŏM|Ґny&.*v|2$6'iG}G;Ca]aw/*aa׷.1'v,wwc|4Ao+(hhj¯4楀#m1icW :i S9a`>ͻ|Jeӏ$w՛&-y`󠎅oϙȾ5 =0ice xbU~cHNۚLZs`猋8I Pa~?ߨXL?鑗?vEsNV뛭 C)HQ2?8wjٓ:F NޯSKȾM =`Zn˖@9'zYԼSj[%BbL ՗Af庠*m"h%'ZJs%PeVeD!ڋ)͙]X+e J3VX/C`]Ti\\\`yڦ |#dXueKGm>ϸ˷mYe7&P[:U=Jrb69a8Nj.ft1:ZPXz4~p?7vۄ̭mۜ'l*uRW+x&1Ru8Nj.d|2 GZDDibYwzX,}溫MXUhIjuW}h(3qtiF˕7#.nᑈs[Kz lN*iZi] G ٰZ#j{Ve $LpUZ.}M>v"rʧ x.zD"kJT0_W)巹[1AF]ä9"廍uZTrؘmAŅjIC5k˷_}u- hD/V)@ha6=o%!!q[Tp&[]n\V6^kj%c\.b3 #eDө^i!/!MV+_xX.o NјIG"ɑ"E ˾3L3`kOD7ݍe$Pvn3+wC8 h7OMZ$݂#SzM }1𰯥߸u4sXې2'T4Ȓ4;(;Mrs޳y :SC9{ں꒞϶ٛaz61 U1ф 5&{\YRW:Hp0}zW* Q(55Wo܅5ѱʑçn|iM=^xȇ' Q(3BkӨԅ5vSr3w>?o'3zOGo@Z{o+Gz/·V ;;vYG?/ćo'5 OzW^~ӻ5\7ya>n?TJ :nmDjH@QDIFIQȓ $-?J"^nO)s Sz(aݞH.F:GuVgrlA~&Ԋ`ӟڕNRQQA߁*q ,}h^e>u]/aty6k4"'ZMy |lqWG &tY+5|]u W[/tdz݇++P4X80 l!w 3MÝwk0"?}PUa\voL,/%PerS4*ҟD-EVV餘FOl)QKIM^A6j'8lK|B- INڮaȍ6( W5ŭ N?$vL/MܖRZ >!4 ծE~i$.B4tZu5AVKk:ԒJ x SXh8y~t@M^_߼o5RE xT[w: &%[ HC\Ī+NZhqY$'kbj}0F L=@r,U!HME"w{MC8?7J4,mg#c5 !`^lo_2=Q-#Y<5?T/SߩӹKb$G53O"ɣbl= ܋6u-z1Pq!A٫{і੮Al'>25ՍfG4b!GIkNlo/DqHRCs9lϞ_%lMē4E>E*s/keH3޶1>}~jě[.WRy6?$1\SNrg𹗼2>l;364b +yD-bc5UrK>zZҌ#>Ӟ* 3U\w8zDKyZҌO郧>ӘA~ӋyXW>z.iN~s<)5?;4 *K].} ERiXE֯`"a"U_nj@J tP2C4|& a"0UM *0Q*}BbǝmleO:eb~V}s~ +dwՉWr1; Qeot$@OV]pmy:γqQ~o=mfF]k_U.Fܤ_jM|0,,KC6/:);\nAU8{j/o)ԇ6oiUePl2u۔3뗨#]ƺ| ?5Ԫng+#|H(7Q{jIDmmuSU$xG9i]Ec<ΝՑmOT]iG^y*[kޢw6M?##S1gz TaRPGބ1x>K;UOqDpWYrǰCQh!N~j4GNLLrI!:b1I6՟\}ۉlQ8a*i#P-u8n1=l%H'PG9H½wu%=.(]:>7۟Xz%81{27-F1e U9Z-b(msH#Ҙ bqd=sj$oU=s"o)՞t:.{⒧pzDO{2b48K,NỏfVh&xݢw&,M2@α[4x+d_)=6=1$CVvkjfl*ib lNIbL>T+d h5U8%;!^Ẕ2]s<֦YdQˉP\vH`v.-|טvȔMjNXxAvQ-)hu4cOdGO|oO.^!M񺕝s΃L h;ǹ VSvcMdGO|7/ɐny{b1<0Ybu`9&S-MqVФcCkdH|eC1y_sJXDe0ˉEEİÆ&+,hJ=hk],-?!vch;0@Z;~]H=(FEMVEϒ!;&ŲU|!9lE k<5$. 8E[_/w}7rt?RԑHĿ,_*Ho*H_V){"ǫ&U[4PYSmSL_o^qJ)W`Ep,hj!ScZ\fLX>w0qr#/e5g=Ϟ%4T Φ=_Ra8S=OuPu[6jKwn{;3w v^VEzr2tTT^:=ao_=j?64pdV=?A<5'n\ ƻOT>Rmک6&u ?OFֶS_unyuZiXywWLo^} Pj)ĝ̜VEZ[ՙY?<И*6oRU@,s輪lM׊ǚNa*\D5}~Ӽ{9~Ql:ܺE]gZ? ߁ͥd>.&_jߢ47_m<.c,i2\QG/w]_.[=Ͻ.1|Sz\JD ;Js <_Ó_+w;whT}©{D"p7TNQΕp0{G63Ke]!H ?J1RͪHAyC̒cjf˫TJlfdjfB^H3~4\0@4jDuJ@mjfhkLyǻ3֎>6r_O8VPl(i JkdS~'ttl)hʝ2BQA:7|O,3ũ:KֳJ %&;DKK==5ztݶ_5BZ;fYy^x"\ivB)|b:|ת@Ƭ1p<32]a՜@iK5Sowu)?@o9kqc4&7\!x|)s~~M۝?d,{i"R9:vg&hc 0k@GP2I]ڿgYR?),E5@JG}~jsP//]z%ijP; ],+ Z $C#&q#ۅeoeI2HP:!Sm_4}H֑=$!H~HT?@fv!_AԪЉ,*9N% TOgiը vL;:h ,.tH3`S%]vdm MƩZcM'+5l*[)EǢt]nFԠ*΂CgelSN` ϳfnP)k.c 1%'TF[ա) q,>S]衤 9s} -~TG6y|gEV9·Қ{g8~ԑ56sN'R'ԭ{ri|=u6ύ:ugy"M7'~`?֯$IƏb zS鱘[߮˻Ik}ȱc1~7ՑHO*.ąxґHoUwT;.Gw6Uw$ dt4? I9mjU_K ˑjuRheԠ}#ѧp>qޙՑ:L5XXH }J/G9^҅mϿG|=2.E祗ÂZUɏ+m VJMo#[IDרW.XpU$^EWəcVe/)k+mgwD33FeI&VqN $qֱUX\]iO산GkYN7=3dž4t/9⾞s0Y;HzXY--O+P"ab,?&z~qN[Ç[aԪ^-TwJYp4O5_Gf\q#Q+Ȥ6rD)~l;Z0ɟGf2+]FQoi15vxkzqcY;؋qAE1ar,?&:Q'[Za3kdxYQb=_B-&'R  g59m?oyғ&] ߌ#ja@k٣4,N Ej1g:~[?[(KzoiҕfXY +(6& Jˑ_t:Z#~)'Rm7cH-3Dư9XK$Myo׿Pomg֞MXq3gqX&Z#Ut*]sH]{]] ?IJ6M.H"),Ǘ[,bSkEhW8:oJc,1ɻkpQX otӣgqqxzed>R[y3*qRL}xz.2M#)us$g:5UE*/ven ixosy@e"i}4~%iݣS/'rЌ)se4[= wIE{AMuSo]]RIz f#Z\?euPe8bٺ)kfѫJg}Pe$tì"ѣe>D΢ Y:}޴%q@{yMVmLpLf> -i0UPy"өa$I{HBTP!@0l<6QY4^;O([5 6xGԉ쿿2k&|o;ݦ( Kf2;qwc IܤH Ruaja=BX,5`>.u\\3U¡@+,ˍaGlI̫Ru2LPޅ>ʬ_dMf$zͯmH߿KwUmғ9z+,oɉd^_GvI@BSۄ Ҍvq K>M_~rb -ppWucpvL:.BT"h XpY.U[Zk79s_ uڦ 5A-/_U{90SP'ng~:=mJ_حOaUnq:V4cضm%|%ck1db/7r֑++Wzqd ,[/_I~pyfro#ʕ+=PKr wMɎ|[ҋN+^/WI/7ȇʕ=lHVb~MnXFu_H{k}>ۑR0RPO n.%YqXuťmY5|odT E&u>qUy#߰BzF-2᳃JH6Fv7u=;~,)*H}rd\ֳ}ǑV2K*Z0*HGuEazF-2Y\g*:ռy'NV&ID;Wa-+2%s4h&69(nO\7v-m@◜QA?_&pn2ehmέ*HZ_z T~ 692[=gI O}6!a7kk#th(:GWl.>:GW̿<bcݫ*DZ4px՟ xY1VduÙv;[~'?~[I'\s8*Iѭ$Ou8ӕxg?cI\t|u$ -|cZ-:FI.Ͽ4KJ>ֺKAk]sWi=_K^#-TJ_Cj3"ⳳH/|HV>آS|Ÿ+1,% vHV+lm\Oڟ|wңñ6(yAb$cwJ>GZ`PsOvy~Eң·? 5K B mvC7$ z E3 s#T׍Iud$Y;lC\AX>Zč;|]WEYk}?xOUA'~* ˏOS`Ҫ?w$p]`hMЉ~?]_~}x*5$Lt<.D˚gzǯ[J#YY֗*2O\/ qm6"co;!F`rJ2jHB.9s MYI4ڏ5|;_1p|2KY(^lMCo6 #j}ϥw $.QZBȓ8z-pڔc?δj_ߗxu m,{÷G6Lyߛ=}4w5$i  )c>ڛfG>Gţ$gn,)iCb=MIVɔwlN#Nft=w$gN6e+t "$+ZhB/'SwÃ;7J#,?yVZM鄞͙`zM ع'9sIlSIB{:yjzďzNVu9`atNs󍶵H' ߻9|O܃[MhV^~P)>g')Agj?1(>نԃj{1ᗈ߉FzǍ}.QvUBu:榎I7~w.ꅡC?P>t#E0t5kN'xuQ'P+nL rwrNQ}GG~痆vjشJrPnI7&zMyWդ̟mŏ6iI=Sf'O|"1u~p,*Nr#g4tH w]so}|q+OԓYmŊ{zR=v{oWOZ '_s!4vz'4 W^7sI=i-nd' aU%9\ZS+%JEyu`Jr.~[ coX' ;j@@B%Dhdm HWRve}rݰt@eqˢwIQr;q͏|M %K_.;fi" }v0rʨq?ے<~GI nSÀl_VˡMÎ[ry0X.q1)q~s|;Xk2^JЖZxgW;fwIZZJ+ shۢ m|Q9pXj*< 읪MH ڞcLm۶H aES# جn$WKk{d._A{Z+̶EIo}Bi 2s|BiȍwLU$aIkm3>/CT.n'&n]sԒޯ*NgΓoj5~W.mRM귱0`Oƒ.n&դA#]ln=MjIWcg3@gYk>x D!3̧UǞERQI 3{zΩQI 3ϴe}zAatWǎ$]םݳ)x=`ij?x}#*IWg8̧{mw'GT\ړQf4u.9qf7:-"VtnTs='#My%\/#hNLv|lsW9RmOTnQcݤ%HZ[LʬJ=9:ɼ&]hMrX$ex+pD+d쵵u\O`v4\x?TL06_Y\Unn_^]-bM 57C,/|ԖmRs›L-5]IIYr jt Z09^K@̞KТ}u%Ցr0~4xīsMU"|AkrJu#(#]R~~P}tM4:4r dJo%97]H" \߇u禴bZL rvTE}yџdb+ez:l|$;;T8X ,٬ds*؆`}+$m2J[[-^MIvP x_M+y]3wnڄwe2؝qcYoVO?_d->p?x i{?_?7[Tאf~CSrX[w ƁG]}ӑuPڎWoQpn,3tbVn^`Fv[w5g2ؕk7}͇roxjʻV|};i }(Sߜ(FWq<3Wc78WNv~`:8m͞襨 w(q)kѭ2ҟ]ǿ0ÓG77xzw.Fnߪ\X﮻~ϋI1jenwxo>H_K'Gɍ3j}?e}юiKbÓX(Q\]Dk@0ičRi5YՏGxmZ{~>K#l7vMc^` KLyh'ԁ/ K}4vo;`xEwu:n ӳuf)W>3^s`pPr_q 7,،Y8d;My~Ӧdxg7},M;rx?.`q z[7sj>@k"ⴻKdHG=6~haDOsǗs{pp!QY>=7<~,rh~:|{ Eϖf>+#+oP/]\Š+aAE6H].+y}I4UY!F-InYW|\ݝ3G %e@1U026pmϮ( 54i^zu7N )+kε\ QϷ'!nQ#=$ӖV֨Y.A\s]^V1I6E߿VX4p}=<ݚƸAt.޹WV߹W`gŗ~_Ù>N=[9ȫ_eYH~<2_ˏ ġAn >9/Kiݟ'__p〒-RūF:y/6cAv۟>׵ņ\{8%|Vf ltt1NwJ9_6,xui瞸&km12%vW_)pňXϞ%o< lXN[cM'NquGwc[+xq/M9} .So`zt)@tJwxxmz}pu cs }Z=WCk7xT\X\9xtTw 楺jPy&md2D76ß F&Ax&$Xp۱cXtoR WN!Iq^aIMamgO[B/>~ST{3S=DP+N;Ia­8Tbn&H )$25Y& DRO` g@5垨:.<qV#6Yih90DKa׉ ArB͏ g !יNhO'Xʼ(BCݼ[һLiT̵j^:fUE)u9PAu(Vy` Fj ԔbR -ei 8&+''D1B$jϒwmE5Y&C;>)wa㟿6U+pn}uDxG45>I 5^rOLm>e?~dܫ)t+"‹lrhE;msfdҎ_y]o>O_e#g^v\HIsdGV]qVmJ&xoYП__:gb,|>?u˜ZbÚv~7ҽ7%6|)\>x/P2 X\0hs­ߐL }ue_[Zwv;xQWҥm&@(}i+N  mGי+νWnH&yWu@eӯKԿr}Q,E/4̓A0 ySIXlRٹ➇ ɤELnuMRs}1o …nS9 1,O<ܐLz=QW\6<8qϋTB6F AC€N…Fiym-/:]+6(_&mz_a~ws>=CKM6 &Nr7HM;rt7KԑJ+w67 7`E s9KT-xa&ŽsF`Ղ昸;ƫjj^3aP֔1i_fՑ${xu#ĹfVWE!x.*i'Jɣ)ٷSI:Ko<ʉ!ʈ6S,~JQnW%^ .tԗ;+ccƕBbLtԻc"ۼafrǒqjICWଈݴDTFRP,O}Ҕ3?uQ|s1t-ݽlV&T?_3儻KaysQ& d^84ԓŪ|ʯbyQ8qg.i}PI;3ޏݦڬ܎ =^;;-Vzx:"rBE+M"muXd Utʗ w3ͪH_8v{H\1~X?pf`RM^aKD]ԍ/ܯVxǥ{d/>FieE?t=MFkN^8\P꼠u1w5x6l)%H&Qٻ.#vQ=h=쉝؆ynie3??\(~^Z(9yȎ|*0{"'ϭ+# h5cXo BS?[zÔ,_rkYw`G$9RT'^˘Ɔ" sH#~y綿 ]e;.Zgcaq[WULFli3=\NU4j6^jl~ؿq ti3?Xk*{ۏ͕ { ][?.#\G`E槳%7E)R)a훶nxief[ ꗒ?LX啴`fS*~iAzVۜw#GBֽ4b]q£'~Q Qv<+ 1q7 _c,a5F|fq] 3d>$!$g\5Zą=˒#M`r/M \P 5iVճ)Sϒk3zͿ>qI8's _CeM}%D}Pp5'ďJTtr"=̭/Bx٭hrm]ON<\5S[-Ό^'\+3jHع:=}5e'V+]G9SVApxe߼+WkSA93ݧV9Eb Ei"eY+}$xr2n)S_zˇ'_;!1|V?3k.•ڴ@ r=vIWe˅\驯BH\SEoN0\Z3"\զ Z tNeAeep "dߕvc}B2e|E pZ;z_6~m:Z`ckeL84Qoyj_ $ ~%V PCDCgJ"\ܘM175\<6ZOax;%~os)DʒO+Gr׈܆>gH{Bj`<Vw~Ce[/B^nn]NɄڋ_ןT׍Ж,7D7c֔_mg'|헔'/s&OY4;M9p ?nʝQS(O$r/̖GoJwStiu? Ma6 ܰ |4.Kՠm Jxl-I$p/p&vN-NKX2Z~{ dIP4h9a%Ư9ȱs[O ɯ]މZu EȚ_.=c' 0mCkyo_)ֻ\"o^//΅K +PvYgNYn쿹%;鍐R*C[-L%iCߒSGe`pv.S9_QvS3op#\oA;1*r[cfjW6-[C!U]/L2W?AY1ᴪb1N@͕^=BNK BQ"#w*^LX09ua9ҙuWz@x]{3f:PVb}ؖhIqei^D~pv2i0Ee^9.{j4P8ZmNf/*`fWX&l:9LF2I"8yFӓt5EP&]q*ЉQ{4]Tas, #fGJb7>CGQ&Rua{h`LBL=6d[ԸS:ˬ3/0QTx0NHd.\9ԛgch wnNvk:g;^hI{o[>n%㫋·-E3Ǧ]-4g`_SG@bgO 1-F lO]٭Ŗ  ߾bgv ?B|Rփö1gۑZvZc1jg8%ֲ$ AŠZt<S|xD\ A]@tVLԖ*3B>y7;gX&Ee 𸙠jd#b.FI/xkHk_h'޵noFE>]|Dzl"wpfV]̵t*J B$?S-U;t5a4ZZޙs_}4M?ϙ>]eJYtƆAntźwo?-:Ef}:]LVƠ7e3IwXysil)%)>0?ܔ}k(W&iA|ْ_D.Ni, %o)PV&OuE{(66jy͚V?ԟU*H+i_0 P@*S-s<]Oy U3&tiC+:ΫCw,*qBwէO{ŪճقLp͸OM68 ZUc]>)s^i|Iyeb3ͧ^zh^s+JnPAjڭmTG{O䵕6d%?v^|4;tNvE# η޸#,,ulE4zvуs :?"|) ]:Bi۰}kh|n\@MZͣ.UlEƁ7]nw=fz|VnHyR%\0. lWvO%5 ܠCћdÞ?knwSEPlO =g]J>>݆!9"|덭?`ri7:6V=Gr꟤U (PE;'z; ~`{-<[oB@bfptS릓/(M"}cϬ5)p] ȯZ2Ӹy xAH=^٦8I`eC@9cg#w&U ߫Yv^"a9\Zgp<2jP:dU% V\j%k;5qMȹ-@{,6]|Yq7~U+L.H8Ϻc'¹p1u h^;:`fȗl Nxˣ~Wp<;z00ς׶ V_ՓRϵT8|uѣÙ~Fu,Q]Z~jL٦iY"$3^5 |D0rX3,: xUT~Rz6)p B{8_ ~ q[6P eD(Z /dېԏ,KO'Uڲ6M7 X1v1?X/<}|XJ@G{5/?p17H5(XwKZ_}׬~ ?pK[e=kIMeqȺYDJÜG!3׽]5e-QDohzPYooݢN&Ss$&s^g-V. z =MP1o*R. .iXYpwDR[ W]`iS-**7׽SNV1n+RJkG:a>~cz(s5L1kκzn >h"BX缅TncOZήhHD3 Fj! 0Z_x""-J'N.դ%oy餃aٕ#gެ^E4G9;oE5,7-ӛ$E#X[L}݊;z/>k7^s[mUQ v̛ͪ|%D;,6-mts)b}B6jv?jV>\yiܽwzLCߙ״o@M|::Z-S{%Xn>Qmؓm]k7M Kء;sMx *HZ԰--V-ϵ;M%$XL=s["Kg&#^tv]-']ݱ&*jo=B~lVgiI+H9(K(*k9h#\* í12B|F7Eh7G;rM :*@ Bjݯ9\l7n6jI0ƭr y"1%TsW'+sD_ftLI-lD hHkH,J$&X^@H811OT>Pٟ73:w'[i%4#m{JPB9Sby b,n b'_yvz Dh].3rm*UӷyeLRu5=l+bh8b3F2erm|Ɋԝ+rtcS[" !R-‡[Re>ڣDr[(cJSq[v1=j~]Gw3U;EI:[f3?lJ178?q/,>`7MORFk}e&٩%rΙ~zK*H3^ fϊ2;NIKd2n0s.7_W*V"MvMzXe+\^KPrn8:55U"ᗵn)SAZҩ![ P*2BttnQaZn6n<[ֿvY5nq E9,+8{B7`qV{-UpN$~,Rj:%xUwelO~?>&grh]nšv|A 8BŠ'۹JPITPhTܤ`&jPxX:X?K'uɥKūnX@sϮ9EԿcU0`)p udPD!RI"Wvo}&i³Con"GoV܍GO 7mw[n6&٠߂ӔŃU'E*j\F/%vn>3' XέG;:bCKW\6yZMcuVsjO*`qQ(+)k)}іjB'3n2>ӦUqs9)a8fo靕 %vuH8-phpV/LU"P%QF%"QM$_G5i~rW.9Y*=MWګcªSB$򱛸 b):ۨ]l7Ο) E\.0tsGfjB(ڴ]+*j7w9VWw%Y8km64+˟vUgmחvˌ,?צP6+7/媀ts$Ͼ8kGo?5ޕ}[Bkci7ΎgZf@sOL p[=]9ږ" . Ed5Q/DEO>J/.*:=s۫"Z]{: 5U [\(}o%zk݉;8 kZj f!f:;%%0fC'Z ,pe*Z}ګCGof<tx frD9pf -階W4U | vkkrL]xTvžĘt&ndK9NSo P"-V%QM?7q_stϟ7Nzb=rAb6A(>2R{Zޅ$ DSƛ|7NBm97t\D#-E AJi6$`xRl!ip{ɫ'>'ecn.㷦BڪR<1^Vʨ"hK 鋣L`nr69)%Zův:AoTgI:]B5…@Š=CHþ$i)3LJ1A80f:h0od#59ƒ7@*$o^ FVI4ŬUiVH@޿./?M״Y}M‰~V\4ɓ=4V Z]7qE꟩+Lå5WV״:UNhZK1X^0>ƒ;_aMU0T}'~T$t` wƒu{KU7ӷe͡BʹaijPߚGU}[rcX3kDUI-ݴv?Ϭ~k+IQcSv}lh>6>/?yh=9[vǖ".vGexC%G 4_Tticƺ`j֚+g|R~o6"C6 ]zDqREҥjۻi7jPhXvIkWgD g23zhCJִK͎Sv諵ډ7ۘ[Fet+nj6LֲeKՐּ^4^rIzVP##hf]t ܷ*[dKJF_E J*XbgsRɕ6pw<{y~<~̓O8uJ4/Qt I(3~"8 t83IUztH"ZI+mTׅGʑ2R$ -gK Hms?]Sqf:|FV/uBtV7vaLAvʲ"Vu$q˥%\r3 >`+A,3;< pP:1mߨ"i$VoɖX1vgo{9 Ɠ'=+SӦJ!I+jQ7T~߲B~(v?2=/8g2W3KJ~)oM3R"8\qlV O=w. `'ʉ$HQU9-6G_KƂ?}﷖H]=aCrk?*M=7f#`KL+_W[;ϥ. IDE,jϨ¦CCNnSƁ3ƁRjG ^/f/Ȝc7W ]YӢrPX~PXBF^w<{ζrL-1G9=*ie0W=TE7UM;n=^+?i O BB lKz~;سӪs !2gQ^2%]ZcAP3~eVAIq@ߔd\dnAJZz8gva=j $TAҕ5@`~h;5~~Kޥ)ă-ߨLM[U5U^J9%J^y|u%^r @5̎tݙ&U+wˏ̴OwJ)ؠd`#6V+ٮeiqff(MU.gr_(u/Z_r6M?BZv_Y푯taǦ chV٘Cd-l9[=qT߇,WJIJܴ _ΪU&aTGy|n].Z#0/s {iܸcI;df.M5*kI$nGr,@[:ck ;:9Q!j\DiJ],}>vgO])#EV_s Z98z{u w('ʫJ*EcP'R'a%UkF%*ESRe-YQ(uW^@Ҍ[㉜i_D5 "i'\+O¿8.t4Eȶԥ#bzWI04̈sIwL9rhsc=pQU|[r*_nFIt۶#%_@0pS]"a/'-?¹a}Q}Q=ӚMDo N]@7= # 5pFUǺt޸"a.g>tn$IrTжu0Dd.b'&ergbDhוAϻ[՛f$cۛSN.bGP!Nww]ї(8"V5KۮY: w~0S^ xAmAcGhNuvϽ1Tcb|3S;KG% ph\2 nod'kFҲaD|@4&HK\j%;{'>fgU4|ņ/L%[i e sC6 L̹UP $ih:1g fܠFx]C_9jH3֒P[uc L4[a'[4FMPc#AMZƸ~>bh@Bk9`zCzР!񻲵JGiWA&C63P>TҐŬ^F $:3ּ1z?6$aU= |slD#JyD8W(e|T>T؟9]gE>GA ʦiG/[]_[i@s\T#[N [)[ʲl E%ߌФw!9GV㏊<t Ω ٖ!Sn *h&R}pHP 7ːzÐĜsmLԪVͷ:f )NGɕ1RJ+D&m5U vSlѲP& F7W&},b9 Ę? Um c`&0?d!RC/T̄HU}KNB^Zw(@}3RӤOCX UՑ4!+B*bWTY ij $1̂T%NZ@7?ZhҏPI]>D揮n*dG! V]!#uYQ!TL(ܧ!TXXlVʖse\Z7 &:*{Df V,:J!&XC!yBH*QҡV a!-}^ex>Y!IQŘ8*L[v]v9 Ɍ u_^,ĄZ%F԰8hbA+o.BRTE:F1];J 2/8vάHXDҰg.4=ucN+x»$4F)͜-,-SWêkWq$*ƴ2EVHI̲K۾ĦM^$07xO~kt/ݎ0hXố缰nyrCzÌ@_aO:q"(9G#Yؚ6߷wT0`yj[9n ܑ\/ "ޗcpKk#'3yvV"v!'P9jǒ)up/j.ȯ~>3}́[AUVO]F},-)dFmwûkf2 rrܨS$aGDq~CIw=CsL9z>󪽊Wa>FY ΍!k6Bbtn682:GwP9uH7m_jy=T7ٻ W'2#$gl'{)Ĺl=vCI˝Dn=8Ud^~31omqhUEIp Ia觖d?k+#%U9T#ۦ+8βl/_l%ǜQ3KX)Heä ~%j|0(DVA`m4Gd}&M+kq1%Tn7ii qV*RF0iaH Za 8Tg쳹FOIFjB=dg>O;mi.h-{Ӕ7>Z0|142IhCEy:P8\gq>D]܅,u^2jp~}WMk3>^  L4Z)xKWtĹ _uLD""B!];=6ֺ ٭\zт/nZ~@Z)JCb˽FIJ0"1 oJfX^Dg4ř⁂@@7FҎ ku >cekl #C 1,ɈD$Kb.!w{{_` oh{[Le)Au O9D# Xmt~uǎϮp퍹&lDˑzoݭi9k:z`Gg]_mN- <\gGDSc@t{NB RhqJu%*~WE0Zd-HB֩YR=}'!M%  -RïO}>aюhΈ QJ8<HZj8kw=$N1__O>!]]o0#ٛ5625:A?wͶyG%/YtHDDAu F#"dO5%.Q[k,n]72jywl,e!T[MirWʫݲ zH6cIg!@xlј cbp]\xpOv(+Hgcƹ5wԄ̔`y>9R_] GM=Ȅ`hd%cW+gXD @H`4$h2}⁁@'8m#&ur6FZ\l&;Z1%k(#pW m~V#<ɞ B] @$yu_: V*4h>APN0XKPX&iϢp [[TD ¸eDb[_.^0Ls*TNAZ:J i}Qyz 8cytNO J˓B6BJI**<$F;9("T!1"[(Ze@CJIͪCKB-WĹc <2DA ¸2؆2,UqhtF`Cj!&9OX& ƾDڔS(GB°VGꈞFm@D@cdR C&i]l\! Hg!LRb\!aƥrlt,# )Kz$+w-ps))qfQ`DHcsH\bNJKSk-#"Pe_IdљY )Ԝ& \Nʢ8ƹAyj]um+1 )yUihmJh1\ uD.NѧyW>$fSEHrlJKHWb$~׋J46)ۈ9W$ ,qT>"!YR0XT.2[b;⡯65>w`}D$rH);h⌱@%2HBG$ 0S.2.1sKOܿzBB(ވ 1mS xRcr';gw 7Dm _T?ԴK4y`IaiLjJW[ )t0\ԒPFg)d6#/59ƞ;B1'FxXʽKM9{W(YyY$Lp,E NJS?oq'w!Azbay8:e FieZ -T lKfXIsov[x+FN0!,>4vR+}4ەАT .50 1?n,0 ~TS8N_ԘQI]7ۍ;&4>Tz6ҩ[| fVeJ D`8i3?-Pm#D&umox`d,*+WTa^dU`!:mir+^PVf.t-|Ɲl?U4҇3yRL˕!Z XD"{T1S<紹]7qR%>cKIzD"23OKulQ}AKwPM~MYV 5qqcJΥN?#wX)ݍ~r1tVPT}}YV 5 Z[H\~1{Ji;/ض[3| X](G@YӒMs?8yDw^֗:_)ᮘ#mjȃTrBYR StHdB :sϼHC*%u-c&ʕ $^Xr>le S:N/l)?Q46Բ mպ]tJ5O!(B7-ˤ5{M'#ɛ"Ҍ&]\L_b-)H ƒ}bԶvzîܱIkn^Bs$J"Ҍݫ&-_ ._nse;RC c+~;8aw>_&y:TԂF%JH4rYV]XO>\(,H,0:GB _JW?=G37`BҌFnhrT;m !J EeGҭ[9|Y 42#RLWrb9Vj[y)ZW0@0LЀ1utk`[yhH#L䕜D^k~z(EZj" T2oOظCeyZHH!H"$Ka`I;m#6.|yDMTtsv;jNMW(~/3F@Z 5H 1v6.5/g!"HoňbBx #=ˋkHb&aLLmie7.?1bh!b|#M߳/E׭8TȀL1Q$N2dzf46B"2k֝\h``{o(b"6ȶ'\ֲ)EŤD1ҟMg^J?ɑ KO))H$>iD3|g?]s]uc$c0mwlօtJ  F+@zjt: * "SVLEN&t ׸ħs Q)!HBBDF |*bXm1tu3ʆѰtBBbE?6b(t֓k~54!]@b K:5\V( $Vj1$G8z)ozB5|#c Zh)&˞Ȣ4P'qDHa1V&ZnC4OP0^Z|}S?6^`SBI~4#3Әd^,Z(mdHJBCz\zn4"b>4O>d c[Z%nI:Dai?O5*?ﻷ-9肝-:zSfO8[9s-~z#UG%rzzTpy0nO-O.y,a]0Xbk_-蚝CMK\$DmP͔Nq/zܻD.Eyp3@pgt-϶ZG%Pc/YHYYrMƄ8[^sZ|1'B5.ӣh¯.iL1boaOY?L-G'@2ޖp+mTq~U'h?o$D \w7[ꀜd}2V}xh"nB qnE; R y9j7${O$.xم hz՟ ;$_ajYfe6Ւ4 BBPX٢0ygaG8g}ߩA@c%&)`o|@W<]?5Sw߷7WL#(HK$TS+V**$$C ]oJ§w媛[cI@D]jP(-4in1wۜ^ͦus-֍0Ja L6<{$F"=e/ lv^Y凯[hnz-F-2uF4QY45ɹ18 |<10Y͛t>:ع{_`WKWW~YizqAZދM tYl1|qMT)l5p A0bEL~g^{~_dQU`xUß^yn۱ejf5`ckvѶQȚZ5RXe}n B$u.'n1~K">H]}rks{>DZKt)Oy!%OlThrh5y$O~ȝ(?ő=J$9D {AlEi6Q"sGkVK$EJ ڐ{P)K W]&qZȼWDBM)?Z2@j˧"Cp‰idN~shMNM L /LT]?qc_TAof  Tz%@pZYx䵮DfU i11`x^ LM 1֭v%K]$iM i3gxk9wz`%E.,PԠÇ:So=tId2`:YF GoTI"[\:fWmqozuh97zBBdM% &",#536)~K OF,þbz`!K{$A4tt'>@H q-u*8|rO kXDB/P:h3o<ցښ<raiH }HE>r,~ t'GBNGXdpZJ4>0q@djJ,ydr^rrbªRA z`>P";h==;`F#aBjd C\;-OU B hJ!>mWҧOjn> 6Hl-Z0H -T &))EUrRb9EFmQ &*ڑ]I *4yJ |󻱡-ojUtrDsb SO"t9 Ý"=`}='H1!'s5|BN|ge6MӢSjd{U9fm͆ޠxC5nv'I@`kif͡eNȖh&j+2ʣ1J;Rim9RΞ.:DgaLg"W5+oyx[989eϖh݋M?Qblh&&LUDJ2.X۹D1s|xƗwP mуNJI@P`Іvj&ʤul̩4RcCi]љi2J 2' ,7̊g@-;Uh|)H{ \V4$788^wES.-Ps T']bp3lh񎲟T374 SaeX^$vDZmVlβa#tIT)aC>XXqn9a8@čwz _֑ۡ9!4W[џL5`Ed*=D3>IYvF.0{˧$B5GW!1{{O HY$&)bw ; CI[#xT"@]`^wJ" I4&b1nH erHz/Md61qCR@5pzcW^%ҮH}LҰ=5l&]YϾ^'W( M,1$ɤ'ncW^$zCW1}LҰS5 8k•(h'@06uD2l3kg=y>^f`O@b#0VqqĉR';zg+/|߫ #.K =ygCy-^jķ>ic8^|{BXzSl4\7a|b@E~0ćHvR.\05r>.9b`vܣ%\@ѵvHA46a\>]> d+kWAMQGRS}*v"b0ciapDZtZ50Qq.eY.2$%3[E`@-껇,GWIZ$E ʂ,F`04l->dDeHM 2ٔH,uFPf$CF EpQXѪj'B9$K GbFHL( iE)SjQq&i9AMYP`31mت҇"t%ŪUPFh:j!# G" T淹5i&M¤@42٤yѬMY?{rr#:j#%MR6łE&4Aé.M50pT<Ԙh$Rb *"gˤ*tcxmr%28K\Sp -)ĥ.H$+X,-XXr[].(2boVA$ʲDPI4EBLD(hX B`1"jw;P֣; Hk72J'jn %F&J 9r VG855DISP"HHTC@$s]b̷ڝ.2tW%5/S;$d975#TXN @DU'$2puvN* ?CǼ*0+&y4dl"$CK<-)TP(1 P(rQHl3l?'%-gTwkbFZ;6H{ n̢͉"!8':`D( B#˟DՀ>g)I@CaF?#\)sUvC5)7I<$bKb iZ[rk9$/.Í8?I )sWvk[MERxXecuG9JE;KI-Ùų~ $bYMK\ɡ]fiϖ+ nfZ"" o"oۗ4oh)A1slD@  D:Q/;gbK2E8?U(A` $ !(^`@Ш<nb(U&m[%*L4D@DJ ;[tf*w 1K,bGC _$>/>3$`dBZ"j s)֤b1D7}Obi8j,r*1$ddB"Z kq)֡b1@1plyKlo|au{E@JtqHt)AF&mQ|r$i!>W-[4kb1BvVKZXԤ]O쯳pBG#h9#zh~Y)D֍0 :u7.-t$)=̉0YJ#5OKZ6t&(ٛC'%eKTNNk{.?4ŵ?ߊVԶtGg=l/w:iZ6˜6@qK! Pfh8+<1} AArcye%KDw~Z%Sm?̕=xBKBs `d?D9GFbD@>N,aݎHEqrD7qZI$t~DD\)qpdP8^JX:3:+rlpRTp{P O%ta0ɈFt)bd,Je,gdAL=|6yҎdFBrZDPDkBHXaaI Y‘.rWJRdX@,墥*۳VwWVMXȬEB(?hNS!-AU.^<"q*I3JRDؘbЋ[|mυ?,wZ"Y%A( BfbH{D❘N qrJDB$B&M4"YXMd [ɇ+%nbg E-ݟm~0Rg%Z ĈF8vOJ1J"ErpxٰІK| ㆍ+%^bqeB۟`_qPԘSA 1 8UH+DgÎ@Ɨz ۃGKXqyk\+jY?%Bj 1h-iC@$o4݉I}L`*Dd4gyK` 1ҙz3_6v]B.DwE |+ 3%zw6鏸8tUZ:IfIH_M~ KYڠԧBp~]5S]ҝ0$v߾goMs⥅*j$2MMUYڢ4B6<}]{2+>sg> ÙVW[ RHSKM!$GMx%y(n5Ƌs+ËUlP"Œ) ]f8%B!D! S 5|TQq藣;|QUpӶoKj!;e&m£ }EFWy$L;"i]u%D Qc HoKnLT|r&m+eA,-+ /"Hf8!r2SGPJM.iC/>@ ArTi@8]CDn",\?!$p'(Dh@rNbƄ"-QRTeGrR,sڄ?lsB(%OH"`T|!s?lpZpp(v ԒiU yPxƷ5Xt? +o.W)|;Ud|LqZ9&}2s /Nl,bι-'價h {uIwE@ZYeuM1 6/Nb+¡.P2);{2RxĞN%Eõ!Ws̊i{wԗ7yCq18 pP;gͲG2\UOۊI4}>!5}q-XTs>;%rFWh5"Q/ d@EqDq5f!,O½#F$PKd*ն,v9dڸ.'Lf,DaI|t[kv|kj9%X[=Q+Q|~˟f/9\JwDi>5JOۣ˒H*7\3s颱1q!03gdM̎b"EA(0@Tdp0: o "L_w5vL/Ai\^sS?-.aIr"R~݀ވUD ~Bb/9NkFE8 v̏)[]bHTqTgD0]Hv9zS/C *> /RҶr/)'I\G%2#]v!?,1N#M:$#%5! UWĬsE.bfJ%[-YF\PԈg|3(İѻ^Iuj:WBqKgK|=}H?dyd͎7P ƞ-׋wD(n3:1V g,'d5mɞ+w 3#٩Hq.Ȼ i žFߕR֦-}3M)@ȍd.`#X8Y)D(z2ƴB-mO,,g2HMQz3* }W~s#\GlPG &Bd:;&KddlVe(<4y;L)sQrϤS *n28p:םA%Bx"/;ъ*QGԱNx ˳Zwt'(eRv_KYO_gҡXdo'aʔ' ƶ(wt+bE@Q)k;l2)>sEU,V{4̛+k0:Ӻ4ue5#r ,d7[:UV`&%?_KbZ691)xVHW@6u+1Sȑ4WHk눘#0xFaEK 9vUHF[X^]<;y $muzaJL\e_ڿ;]0TɥH$F5{vEe1RX##%Bڨ17Z|tJpo ųW,F@6u5UQB8DObz!B\+R"0h`DoA ̭ DLyad?Ϳs ڼ|b_pu .Q H#![QL+`"|4NrD& yTXcXu%=݁&JjJ( AFڦ”t cKpE-(,𩃍G~p\>+21᭵+LvD7u/!ڦhGYn 2+|o>rsn7MQWӹyI{XDX6#)6P&]%%hϣEc!UPE[1rI$uvR1ѢR jH7fL3i !K(f " HIj=xud-~FT]ˇHA\:XG `u*tŠnn?#*D;4} I#Lc:Q?C5! pUL 1F>Ymd`1ߊ[O~g00=>k$Ἵf[FI@s=S "= 'Iw܇94}*><4h5֚n .H?k"u:GC?q84ݿ2Nd-rIFUbQ֒":־MPŠFD7XW7$ŤvDW- 3HB47'K *v}5 0០p$}Ot Z!6S'Hfר{&+xj!Cv> 3assƘj \$FY[PJD@t*I}ܒAz~s UcLɨ6h],9xP8ed4\@B%#Vvs$#9rG91m8.cPC uI7/lJu3b&,$Ѳ'ш4YEQֆ [b (Wo>DF;5dQ8LSen۠@꒞tT 3q $L ˂(*+6('; C>%2jZ^`(!$Y?2#?%)6E! Rt7҇DF?6m% (NĘ,zsHqIMs0)G>K7a@Dop 4l%Қo#Iސs!jqsq!}ʤq;E6]*!ežh; Dr`CT&+uR>~#Qo>\R\%E۪"x@i #EJʋ'0_T: œ{\5襭CFi(%.)7 ]TZG"iUڪ1-bp](-/i?Q/*I*lvGr&1pr\#@ pEm>ې³FQ@BBNiXNA yNxنR%oq1.i󅄬hKGUq{sWɹ`=-TΞL.hW'"G;A:*(9$=d~#qALL""G;^:*uFy<h@4{/!K: .KJ 'i֝^t0 YAB%FC"(?z_IN[NZҝ^p{vZ1?W*xg|N[=Τ?' fe`){R j%* e97b$feRd#ݓήRY!˨$7zfSR5WiPVv20i jHi0a6撪 M.\kC.6fcTm@Z/T=$R)?ȓP % vnN4̆)l ߂+0PnW{RMAϫ]-q oQIJMRWgeЂ/NO'Ef[yxGsϭr} {xQ:mZbOt{CI۠^\PҾ Խ悴ۺҊLV|UV}vs{[;t8kbW 7n[E'Uz~|gM /rj;!º.z>Xn[ߞ;lA0^>3^~;5o;ܲZG"#{7f_^g]]}l~Hpkނφ.szfˢ}0[.S$k+]:G+>Ba:kgÇȟ_&sr,UeO6s갪#vҵ1q)zfֳH[=^3U2S BDZf/{08o=5i)$ܥ'FE/W8td;/G ʜHhщw$&e7yt=9- 9L^"턔LRH7ya=񞖅⎼6/w.n*эD mV=z?O!{._/oZ*S>`=勋\VO'[-6(G)ԜHs Lڀl+{xeIsw븊B.3zoi5;gviמc e[:l=A.nxۢfs"&MwPrܡ_5ܶO+yw~_.֣FOĭ[Usx)ùyg:*Z]HԖrTCmKw땱,|6')< c: ,tӴ4ZBEQN3В\KIX{ؙ:JU'w#ݔ]X`26W8#Y")"YAJB3t+3޾/厍/ $?ofͳ>[dN/{&A)|uj nW ޚ4u;{>Pe C}{_^ˏ'ojIB?h V~Rϟ"hoͪIyDL_sMlCH SƞBWU܏xjE3S XlMU^]V:Ü^Ӄ+bNۻT?.\S3BvaK^u#͓|tQ1[1#W.] 6K`#?z>u1ғ/ZoБ'M(Y|orkU̖Ϙƿ}>\k] Rlߞ0N&41R@ƿV-kI wqbip8ea%iaҩD zb`@]kfzm ID]IUh4ć9#$bE].wI D%,owR+8:P:g3}v#uڌ! Nb,qXK$ 07efN óMU/87uO1~[8(^FNouxۛFZ;{_2vxbˤBܹw$m;{&nY#ˆ!$8  И A@MV]^h+j3a+}Zs޷Rۭ_koN&+o+\ +N\|\{ͳ67 ،.(PbH+Y:laMmn[]C)g]d,S-*ơ>-T+ί1mu%Ȉ_mZ%b8)rBֻlkwS\ݯZxܯ' Wtɓ\ĩTF F[\hI'k&,}"]U eԱ?)\9I'O$ I$8a7#_ƨ#w#}Vm+K&pq!w]4RMv< $\W1Yh}AƮ'/iuM}wqWVhd-G?5 ERƮMޝU^ ˜̶H *@ڶ~Էv{ZFf?{[Ր2w={)S0N elDy*Rގs}C_֓{+AfG8[d|%KZ]\Rd:ɹR-p74/|-Xx/]+ʥ&/̳'gxD)1!' I~19 #ۧD?j7}TIAfvp7E{gJHn/$F}{VDGI]f>TŇyEíg(lɟW_>d\}fǵnèm~о&.ijgy({:w'5~-,[c+QML=;Q.{ iNY{Ov[-͎8-!f]ufV{d|`<>3I4l>'H&:!FPJ/C PޑW(dx6fk' msߘNgU‰MO@> 7ir͘QyC>Raō|$樜&U.8޳TRc%g* Dq+=Gbqbn'a<ռ=ŧɹn' KwRaFV" `^OɍR^kmRs>CrDL+KUДsŹs͊36sqb\?(wXBk6,Νzb K1Ż`2h=5fjc/[`QcԺ3Gu^$[t1*EltɵCK7 tíL:I9\u<yX,ZZQHL455(@""(=7=_0Ӷ:{KĈ](nBeͿn?{FOO{:v {106=cr< ZRI"Ƿ}GG TW M2gm?_+RUb:ϮP2IJ y @I5)c`#*uCUut=7}C(|zn":fj  56uauNGo`4?G ]KL$ՑW!ޕ0fZHqPgt%.f\(9n .[CלU$҅9k2qnOy^#NJ_OKϾ"tD^fr>E(Ԏkjx_4*k퇛)T %FR5& !4RbDs|O6KZAK"̹dR:[TA#|DSERA>o8]_qjdVv5/7>. \rηeV-{|O[>O s$o韜U&M}޵#e:#b}W>sM_6HedeN RDea\2l5Tn6XU䔧)8Tq/>l!?(ԱY5T>MÖ~;5j~qa˟s90349o}HtUt`B۽-R F3Cyr=7?9(&WξL0$MFKzo1gϹ`o~*irM<W.&6[[ïT=RquY0ř;og,hX|=3T8'4ylM͇||T$CD-ޗL8x3wV߸ohUrN)oT3WvJ"Sצw~$j)#+L4әmoi4%kyt 7Eg4vM_6ׄ8#s|~]ZgBCZgjk>vgw q+SWfC,O/2kxBq>mO@8C2b1I܃uhU;\-iYI[1])AKs|Ehm繟*:(OF/swBq5OZ'P8CrE/5Lͫ][qMLg청m|?A .(lWAx~~I>k-[-叵xJDaTmq}طP En_ 1hZ'UL:N ׇ%fQJj! @eQ}7@0QKŬ0ɦ9UUC PۏlǦ(AK[ BD8@%2ws8_+uH:ykS"7F QY҅#-8<ܬ8tv?]F܆g]}GOp\$} o4ՐB7׈o+ 'El\P_,#I]QpVpna4! C #@ѐ|/qS"Fv6[xrQÉᏤnΆpFÆkjkrQy.@/rdq"t.emwх?LZ = gꤘ(/OA ~`׷[l7}|<[gpWFYwDJ}OJ >vj'Q&I4eءz6.|Oie)V/Ro{ZoDtýeݢ2E:WIUK2}YMg FF.͟CqI*&m%c|9ۚè 7kܙFU |wwU$/:y[sڙ_`ձ迎*&~oM˝֑;j' Wgӂ@qV=ST 27a/t WT4v}-ܻ8$i1 ne+T&]e^e6.0|IAtG"h}$5q V0V1$xJnCM§si_}:h,6wNE0Tu ~p 0-=3ɮa= ַ6(ju|j6Xu6Ѱ3wֺ"mN3Ԛ8 B/oP"_|r疋'f?;lnn^Wͩ " X5 [:Pԑ`YTCmxf9%l8x'hѬ\? i%U:e]ڜBSaemǶصlLj­Bݘ,+<]NJOWB5"Y^H;lԤFA;Z3IT+Ei/G5ǔaGkgOw:uw{Im-ꦃԹ])JE-ޫ%FP͒2lh񼼶M!u:;tjB5x-K,9:(|޻X1 PSZ|]^JE)HƿyvX( 9o`ZZwUqQ ].Gi db\3o|q#IZ2NБW$Q2b4Hd]or)da,4 vr=;5W Ѽ]m֙fDVk.OO`Pl0[+{fXlhqL^iGyVgjڦ&:hnSf(K}\lkNO z(uX _6b 1j $C  z^<f~ZiuH %T=jxk90j|·VN/%P%4:ÂNKii֭֊O^v?~dfD#Eڡ ϝ'J!S$ͨ\puh5(:*J( +y?YiJT}{Bq#IP yMߧYQissk3*OPԪֲP;P}OEP N)CęiU@Bf^HTbC} ni][XV ͻP=[LU-v4,gkj䬳PjIjBo ?[txᕜW8F&/^.w5PGn[>]X*xpKknκ!xޡ8ۢU[NtFAId]+=rƫUqfAe/<3S&-bE*\s]مsai߰x۟5*K&ʯܜǿxz4$E)^IT&W|kE}9dBi-3w)?Zr{/o>e-2SbOQ(k,$Iv%Vʊ֤hv>u;_:VWn^~Sܹ=~]7b=cȌ`@Q(k_IuV9Vh9rݷJmzϋ[J˨G%w3onYkcCf#kW~rݤ};I+ۧ͗Nwɱ;=G^W;,#A j`7'h-/ν*Xr#ԃqm5Mb4a2iNҒYZVIYًVTs#zf*zv:uk6+m՜[)ǡ vq\;[O?j.?JzdxdCz4GOWGuqRxbAD\{&#z~OfpnCMlTv+jLcN)ŷ%˞\x'p'iI[ʤvV$ wǃ!^]mC}. "s}Ct'!80lF MHJAvsxg#4YF rs} ے$ĒEIb.6.V"*9B04ed3dvhB`fN G1˾[*ODrls$"3/HjI o{O %Ib;Wy:5}{z w;xѰw|ץ7o'Mjܧ>̞r o{tWSThO}KCyY~Kg;C%9HNUhk]B8b^LW"c;-ĀD* H sd!]aZB-{[lyȷL50F2OL^.z&v 3B_RBPSM[uzL_'-IS)FhQQ2:eF (8.lyU}B؇' Ҙmv/fnA^DYL3Z @B'I|aFO3p:)*ɹCsf1?6/c[k{Q=GsVf7.1֍^xأĸՒt~gM^@5#Kr t]Dk*Զ#`r^pn\sn–󆤮:w$Vs@Ț̈ ^Y%~~HǾjp!*~搴F@m:cb;^8_.^tRܸf]`m윑6%ڰh76BweۊF9OS J:bTxi墒e QC-^+b^|HMגty[B LGq7 i֟hڏ?ɋxŸ`]j?w]YwtkWcjfmt3~z6Mlү.T=Wzbx-' EQi?.jbC}sIe =?sb38{^ݾU_m1_ZFYۆ~ըu}zDیZls8WF(L;/Uv>|+cl=Z<W9LlNt~wٺ/"q2kzXiqӣłݸu[wm_A,Q͠T<g`! (IQ*` JSP8$(.;&#`| 9Z,9VYу\^ 'P"m b.@2|U\u2佶2pl^6Wt7?uuS k~XcTBe(\?dNmbOg~t?0tl%폯9d \ͻ8I`RjH % 3[RC=+[KVxhEԑ e$NS~~/\'ww^NۺpgS xbҠLxN^V$ݾj.8y/҂s.-;xUE;pOpi{=PLp]lē4Zzµ _n!l⟶Z8 H747 +/+ BGl . -hIK'J>f΍FcTP R;Ikx)#eKP~,Gmh',Ip)*ݽ1'+B 4R>FIֆ{v΄QONP&$h.tVigc'ɹb>zn7KvN:?Nڈⓔ5 j$lhK>-IX+n3yyblFSpkTws˼W4mNQOjÄͅZίSIL2ۂo-9'^# ub8D )5$N{U6Qw`8[ *9YQW?L\lKZ宑ņ|ZpBLk\u۰B E{onUs#>opڶf{dN؛Qrbt?Ǟ5קώ®y 9H%iH$! Y`ߠWqqqQ͵cU[J%G-bL~Y)_O[\h0Jk_uw$>'ria߀NRݶ# $ .B='QGLL:Wv;_%Eq ]?"ad\c&Z(Ƽ' dbϮ={t|*e8$*[ִ)3Mk&?;x炠\tB\ 1GZ-o;^l~׫?arE;^[3ˊϾo{_q&T$]~.yϭkxPR2}X' esvV0e@KN7d h@(hq?>O5#O6?91.1ttOb! 鱨ɶq'ΪdZ"t(ߴ{cˋ+{Ny2H DfK ϥIZ[^[b$k^|π[?w-:&c[&P VH. 2>,"A:k7:|O"]ntxraNOCe@Af)~;#mդbrJMb%&"ILM 7LW!¿|}ei/Ss}Ig TxTpDB`8pǀ (` ҫ䮓ɸYzњS?{mjC"n7~wf,oe+D$8s~sųHl1Dn<#yapG xd$y2ZǽW fz;5@y'wxݬg.wJ|ݑ;;,J& mXHU1"'L%DJ|ޢeQJXr+{_r$/_JީІ9N%DNJ )JO[7'ZuN}EoDߏ.Z!f jCTDMqA|r![k䴎V'a*!JڻM\V1 S~xr͕tZ ^=7rb&px#ګ(؊`|]iIhKj\=b<#0^YoM4*q|p˭yB꜃s7}XQޅnDtVa6sǺO<Yz8b$M75tFI)Hn*2tZTV_JGtOC9p1cڞ#UL8&bj$Y~.@>e,D(u''7-]!.vY[n'M-|oQ"5 Tє£AN6 ?ZDr^.S3]L ՙΰl ݾ批/Zů~f7tMn%_qӷڋ > [k7^|X1VA@b朸e;׾/X.]?-gɞ9> )s&++w1HAEL؛v!Dž񅭬uA NψM+ٺ)sALrā=-4a)QMfx}yyT@,yC-M[m`mf`0Qe8.fǯ‡l RN/E5X'سI?3ߕ{LJj0@1`#1KR{j2Kim cK?%GVR}R7O ]TA"ԴKY-TEfߕ+IY1oy? eD8Yy} L6BܩK^>{ڕoZiF -]>J#$qdp,I146wB|,`ɮ~gϪ v?i:3,Tsׇ̃{oʐNfI!Φ9&|ΌaG- >j}"jR$iaUS~ _-$(jS7T/O #(!(%ڈkZqz-`")4Eݾdgĭ3tL'DE@˸تWX[ )ˆC@7?eHrw̠ =7c cdHﶞu46ɪֳ,C$?2zvhEWVbW$y^cV4-hʍ(HDQH&M!1 T29#-"/xviI5iAml9XtNXL-~Is]}VE )/"N8 (RQ #i5zߠg]d,d &E,jff*Us͌X;l3]22@amg.%iO)%b6$JN`Sɷjaϗvs8J)D*^vϨxR1 ӽ-"`8*:Tw*֘P$I8=|O 9uEձ|*KWYj_@w1B" 2g 9l}E`9e]/,ZzB- :O4ܱk-̔D$ }BFd xC1 5He0%)0K t0U-usieѕ" =}!(mhŠ!QA gNFH)a$%/׵GBzT@ T s0] J`F_K/lqt^9^ %{D=1\j@1@0A(bZ,M D#S3"Y >;ie|PDbcԂ}Fò4QAS']X" )܁tNbTʌ]bT_fn뎓iԓ}USv_IԶ$60p$>B;O1M0^&6 `N4N+,Jk>;C[7U;A.,ޖ4.+,F =Y՟Ϫ7o6Mhf/-)u[fJlĉ$ =DzUoX-Ǐm[j_b6fLZpfO\a_3xQX+(˩4,FYE( $uÒV]iMWxkwSf!.ZTPYo:lJ6ہ-Lگ8 CGVvj؎?j7f_? ܨYRsMjZ3εv*v}5PҬV9 g[.^_c-ʛg%'.pdLjaHM|׆[_ (W9'yJOwŽ^'RՂnZ^ʟku˷p ]s~Kv~prAc;߽-Xw4vA*O8jJ&m [4߸(|i-*z[=1k:x;Y}]А&ڼjԴ ϵcv:tC5 bBSj q6*2͊Mde:^!7hJ>}L;1d!, )A—Ȃ I"{N@GbƬ oj/%ϋE‚H&앵@[pqb~4™ _=I8њ&XH^rGwݼuն{/mdwFsjp؈OlXM8-ys9|'4YFkz_oMIWJ]>O<F;VrYR%!ETR,H@L-qlWCn~]"'b죲r8s9OSڣm[ \UӁ/);.j )q `5L%̄pc*Z.8j ,犺7B+= H ̧: D1 0E2О۔,.=쒛.O/ Ys Ha1)9.$KHi` jN"W0WffFL!\fM|6Rڒ[^D,s;_I4Dȷ)vVj̳Ŧ@ f} &`Y1,t6( HW?P*]& LHuc9#l&mnȔ_lGk~1u')Qs(F@sF"9i8u 1GfȂ~}K_7Y e@dD1e$A!#O[{Ȃ=xZ6$Ζ?rUPvOV flv\5S4{3@_47~8%y~''+նY'z`/n|h]ˬ\gظeuyjiPL: P?Z/9f)U7z?`ϻoUQ m`|vtCWs=rUU}3ډNLCƽ~t2]1 7b>2~c 9}5 ydٴkwcWQ -^ztՐ;ɫNiz 9ޅwd٭>\p ΍ϻ22AFXM<#4 a KU1ܥ|<۷jmi /q_3N- -AL8#KF˚l}jc)1ZU|fAՓljYc;8{>v |t-_,rAC*,8KeMj66ubXسcW:6m>wXI.Rؚ`N0`#I~&z[<]0z^ɔ^ja:N|mI_ުnuqȕb3J5*25:)j+sߝxG\YѝKݭ^)Nry1Lw'D}Mͅ;uB&J;(,-NSg.5*4r&z̾є7sRah$B@h !ʖ) ű`& noŎۏ8ⵟO!E0gc?$6!EkL  1fOqdU2$PDo`b^Cm$@h$qe IBH0'/ʐ)00MZp%Vmpf cB/u2<(c:"h!,OEUd ۨe^O7BV?B(YKedcFn}TaVQSHJLM; x%XXa1Nu u@DRF0$|j8uИvfό7Eݺپs ƾQFhDUG4Z$@:(TIjz*%.KŵɜiL/4\ݑ-+1 X&<ݞX:WAc<yfպTr3~^ɾ=O'jJNIoοgilb~ӗ}zVa>M@-I<%3) D,C٣;^ǫor;QwxͤV cZh@c謧¿c9:^ooXR 1"%*k >M"%3'=^~2ATH?N:KZVfOt~qG[_pCv%{egn4YӰxN+&ןxYy$ێo|d0i<߷2ݑk;CE`*Kv-&o3#5l{;zIK&}N)L'eI~Ҡ?~bww}<1Iw =P %@̋}Ovi^Nb ?T>ձ+7ITQ2p!G悽E<¤I,{qJГ҂frf}'#NW;ovn!?QC;uwR.פ(%a;ܷI }>7VsL8;o8m?"wo%ESZ/X9z1kMpscvr_&jWD w$F2/7j_ُ{D0_[ՙc{eEwmAk jW|k$J$X5vhL§^CTv`to9dsԖ{6hV|vT[ǢCog^cA||W5;0:W>%uv]""-WĻ@٫v[{eEc{9=:QS;3Pԯ٢O"67`Y wtwH&(@"Ig TQUime6 ƁJ.[>3C=y{~R'I^ÔaMjvMU0)$@l S-jk9<`ZƉ)&]RB62H8|h|"N03u'%:FЅ{}ađ[L*P2fSj&.[`kRmP "*[[.FpxeKT ̅O* A ԙomV^`} Y说=ԢVV vh.⭊_>_jfk%0)Wf,W$KK]&"BT['$ч| I ꏱ'mNNJז9QB0WIڮu E2&-K")= R5L)V6yw7]ΫAﰓvMwq I.&eCǗmh`Y6DMSpp*/Q'- $>vZKYgRɘ'W!Ù>aA72Z#Bi89H& $p=o+H ڢ2)c!!0S\d\)9r)g L V Qܿ29ҵ #iA9P:Q^VP}E[{L(&K̵q'-J oJc9ʱVP'hVLhC6&WWGp%hM`:4^`0~ĭH#n@J8lJS/kOJX`AQ}Mc~2;P+|y9/^}@|&sٸvAH۠ !7:aVq("!0sk<^bt&-TsigK .SW<0 V äL񮮹/aN4O%5ښ4d#DB<&`Ga(t~i {.ey\}c좘 @jt:Pp 4=hYe OTYf :!LV]k@\ Fr;wβ'j[B| 9?BlQ oUbW=?; ?|zDZ9Yve̽{(6xRd֑^ ǎKUKysSYf.X6yLv>L;ez->\Gv$!bszqs{v͇wNb7|hȞ,Sn;#R FQ.ʒͦ5AEF CdGcÖ# aӵn$+nqHJdlIW: VږӅ/52X$.z|I]A|k_~H@$rb;?IEVږ— {rz˶CsV/1ßXO_WTzL2bdvLkLJҽeu]|bAʈ~c[t&ֻ8s,▕QHxlf}M(jve )mYi?*fL9OvΕp& U KAb.2(O@SI'̣Y9q.]e7qޑE+Vw'iHcRxoܾs6DWP<1c]-k~v/ryҭ"?\|Tԋ.:rZͻ:kU_AS3_V(';-~a]8jxwzVJA~ѵ|Y}ΞCs//|~sQx/=}{7$=_5/ѝ yb?%բ`$ eR9EkY[E,oԽpuYG{ni5n>K7kiUaҘ3b5ӜfQ"Khl@sCY;#gE6p=Bپ̩UO# Y|~"hH;C!,2VhRHCfkه1lòj-jNxek,>;],EJ̑ /[mARVENw \r#K1ZKܜ[E5%`ژSGM^c!V \seېUu)к'ͯkBHziAkyfb7$׭/b} P%}6 ҽK bD;dT"*E SSIjep(MmRl*:4q ,^gfuK`BwaLΚMI r:6PCRMZiSA*]m]9w 4k~| M l$mb)F9=*Fʴ&SSJr:H7u88tk *"m#ge]ۯ*Սc S0Ф*6<]]L`RFGjJ վJ7g*QЉav<$Ky݃zf:p,sQrq+I9q)Xx!h], 3Mmc Cfnk,%S/vqֵlA|wH.4{V-9xN|I54O;SQVN ߄)){zѱ Ygyl<{?>)vMKL?dl$ϕwN!="BCpi4y`>Ó?.K9J9&u\Pxuep-V9ATGFK)uS'_uu0JcА0cF;4 sKn֞Z4~^@wS^Cemt@Wl(ƺ_u37 Zc)pe-seR"FXkܵ)=_-u|'nC(3JRk|扛<[JMB$Ҽ٠&fp|tl|Ե7 ed |6N/k)3:,e-e"6}+DJ K4p8Nh"(b4SWzH_`5ouaSuTڡY GKɤCDA8iWpyÖBiũ?tfMuIи܏Q w.M?tZ$T2tWڏ^^pIp ŚJy2Z$h˚4=\;eϏװVf;Qq2jxC&5*tU-m;[87%g1,nE f <I,h IZFm#8e)oX2 Pވ.{qFSB"O=\Dy!4RcBzvb5jH"e;w@0L*C4Bm䔍/s+2"m}#3#e;;!FNe6 lyּpHQ гP')5hmd7̻]VA_?sCiIArP4K(Aec #bn9ixمY,.Hj=kb5OVIPl$TPm ^Mn)_OKS1 s^)%CD[)| \Q?ʦߤUc]_wnRѧʶRȘ:) }c d]+!BxJIF[FJbF$v`ERu'RЂx~'Su|Ew@TѤm.G6뱔ϛ~:]Ic$rV U* 7DRjm|*mS-ViԮR+""*/ʦv_;&>1yϢʿL>o!I 3#3QC'sɏCZ \x5vT%d&biޝIC'( 84c ::@&\h;''+uMNWX dV-DpO ]b@$ Ƅg$ueWMѹJ0ݦj>Y>AT'4ks' V-E;z02sP\Ķ Ia!?,1m Nl̳,N청 M$:xW˜-K՘cN5*M_څ| n'}t!Cb"޵85 LstIY=@S`9[+ gɞ 7D3_nxߵ#ƗtA;D /HUf=*=`9: $qkQ!L$ͺ`]i$m1囂BoApCOFU<=" I\0RKJ cB˓beCu#Oֽ$IW] F99*IaQ}Iܺ] L :@s7YY/;` @g?x$ Ǫ `BAQyϫ] W ܆1[jLK$)x"FEp bxZQ~0D.JV(LV[J p+/+$(M*&?fȶ,XAPM[d~| &)Ņ OR.S뿜QF\'uyΩD!lz;Ï:;U-x[D4hGE#=2IF?}LOI5m$9 U^w1(lQоcߏWR[%A ?eH-EUܘdegJI#&eO{b3_7 %>j(*JYb8]&I Ģ%LK"=ŠGv | MT+ L2vV+[ݰ"xIDI\)?$ 9/'.֙i{~$;y}ȞCZ5JNDaE2P"bUK},aeO;˙$JzjRsϒ5nALrZA$FVgw;9d&m—:z/u#Y(cRhx$5li4JFˁ5K椇꾃aK l+EJRCvbق.&I MԉQ嚮##% ÖEX8 b(TiBy'K&R/6`@$7g|n3AxiJa>Ue|D 8ܪqSΑ0Q gP!Ib0•8wncrDRA f@0`J\=pUIi~lQ,Ì*dP،ZfX+U6M7ŋDD|ҵ*?\3Q-BmaqUk7Lο8I@.^C @nevB:@mEU4M U^ՊXdŞ?ҪA* 9wA) LL2ؗf6P1smc ʜU_pJc2a'Cq7'XE `bqSt%A\3-*g0݃ۚkZ_|5W|ktUqұ5}n nϩ$C+p y!*^ ͗qx'?ý0r9syŪtMߑe͇LfW.FRK>BD]/G8qɡ4Z#u 5y`cK^/ 78 ۥ& vDN<".aj%AOqZ #X4X`b=iM9*&P,Z&[$@p, ePT{[V*yI$qq eЀ6p:@ xh(2<, bQg%x!*Fc+YebXmsz̲蘶@' h{3H\r8kryXC\$w9^~H 9G4IvjzI,d˒4p(#4)xA2*YIc |!-9H0:`L|?&ii:DX"7Ww4LY_]Sx^huF.6O4偩w!`S4`r#03EQ?}r`FeG4c6QuVPR(]{iyp>QC& VbM@~-+2p3j, l"Uxu$MctIq(L"Uz-u#?m_iRf|bAR2-zp nZqI-S6>1*qRLk` ==h #7)Xi;k,YܿH R%DLo6FGl9` 8ʝn||}fkz:&XHsRz+91CX.랆j %hI!ybjDVY߶9I s9f4Vm%' 0,lrj ;SQض(v|e Pn+*h9iOV\®'z&MdUNM`yKҥR,v*sʖA !Z:IC`|Z)VT XRݑ\K(BhlJJ="2URx^&en#@K!Je"eI"Ts~Bz`K}o+tH) 8IX3o , gb$ggo^d 7Ҁ@P% 4 BuK-:-z(%Z GZCj.>[̽qǒDb=.YH' Tmlj‚ q~T`ƒᜑO ?HmVpCAISݞDi4`1;t6@- 44%_*J&91HTHB5pچ$]Ɉs SŪi\yu4|ޚizhX&x{0Y@>\={1 d( .[M`Kޘ;6ƌM'S^&,;G"<6BsA !QKz_^YNO .;эت,55 BH@ KuoI%SjgbB"6Jg7xjSqjU̒9},^ d&;@{y3RA%&B oF/ǴK:g#u7cZ8ʌmH7*`(Kwm&.DtWLbyXmiIWN•s<)֯)UcdH}5XN6Ll"kQKAU(359Ze뤋;Pku\ee"IUp'}^g]d%p^چ6! mm5@좀|!lR7 H"iy447OKu.xrJxÏP`pzV >fɛ 82Hr>hV^lݮ,ݜR}rj*} s"T;[9g9*gb݌e"5&*ԐU`%Vb@d)*}g$=a*ɎMt/UV壼FPvyr.W,UT>sX}4(d[ՖW.4y Z'6Q۬9A݅SUnRIi=5U;kvZ>@]<;\3Cd6Z3OԠ1c sfI"#8[l( O!B{χJ)ZG($E Z6Y`+l; K˾~s'pMFH (`"G4r<`f;bN>)`HL"pjqyC 8Zh*Pse~|"윻.Fd1 2*0M#|(H>MYܠ74;$ #(>]%AqMҊBpD0Gbst.;'۫;j9&m\{VPNR->E?Pۙƭ_+։d"Ɛ=?3kn4)3 njg+`@ &2ɬO˪9!R E+?Z !>41TNdL$ hA(en艆 Q\娬r25{ if5[Mm.6:hҟݱk'xcQJ)$Ðת@e >S߹_Ou%LG0G#Ei?(LeQWr!LLI6+ 6D[@I[S˃W䆨,~'Ѩ qҺ mC=L,rOtcL<󫿍=;o k_]@.Gq7R|=\K&g5le|.%\!uĀ8SȌ6<$Mny>d!?bsR&|r#*WBq# 5Lr`cT6Lnm?{ؽNJ:p 3 ~o `!t 7D ]sÒ!HM=qE7ԟ~5'.fQzDp#AmHD2'"%#Tmvj`pd89Id:PE8 WG v Ez)N ECFr$ yc",FAt{*D"I% "02 &~ l d2G hjO℡Wl eYHp(ޙ qwVY-m@ X .Dه&sn9[g] ؅AX=>,n0Y0 ځ; ?33A9KnE!%[egDThpf(5ĸ5@$ aQ$I@X?arxGC~;RV!a@X9 ..҈RRi$UW:lZQX8ޫ28TFs R}ʠǞ/WȒȢs86mQ@>΀Iʴ%^G-/9QV3,MsG4n8p 40,ŃZ`?Q&*h8"[ۅ.G},'@Tp0N =%  o!~M+sG"<'K/G}4'm V\Bl@?Pb"q.9AcʓI]`_ rs;32LLig"p9R#@- f-p_wgC5PӱD}.ǘ,"rhx쑀4;0utE@hNrߙHoxu58Υּcԋz`@ ЃZ?=z*g ma<]~o8_ASً:B7c'U]sޚwIyR}a w6~(܉lj;q<+i:au ANH {bԗJ͛?/y[N)-ڪ3lY}$?io;AAhTYN% 12"ԔY [ˡVVU~5T1̫,7GuX!)$@cdjf=hS[u#ѭZV!,hkpC)X!ZU-єA*@%H5ˮh[h[B8z\QWYZU,Q,Cޅ1b=`DIdrʾ cG[̭S0~yj)fA%6ˈ2 ss0h;}:IqmNnؚC'dXPX 0*M7U0`RI ƅs"EpZ:EԎ;kz:L9o3`+հ+TC2&TTqaNdnJuZjc׌W')YϚvJ~ʤ1}ABʴ;`eB"0ғ<9"'8;XthfsrL?c!ە 1#xvqa1`"CY`j?Y f(H߃kB~Z Ռauxjd58z.-tKߚBߔ6+}ѹiEVgH*Q Wht5*NҺkeCCĵxD^sg@J˖ߺԊ<)kV#wmK@VE[bQMlY(#_MP_%[$pW.sYfފ/8r!14P#k<Ioܑ@r96ˣ–ڄ˘,e[ _Zv4;qy3)=gS&O_\vXiGmS{6{>cg)xtII4p\8#]*mҡvPX ' P'S )p!z#@r'!d 0Bi2+SY :km&Rǖa8#1aHi~ Ǵ [UH=-/hC2_+ ?JiA2)|D=-g;H 'i/y,[G@hpS=0'0O|`s>o7NK0Uύ%l:f  & Z\]U# Vm׍tsO˛?J{u3Er)=khjl:ϵ'ѭ@@U`@(I"pY2)Ӝ8.G JIkkR 8o~®VWLVx=J>t̶>~  Z[{p;.;-ǍR_& "v`Tdfr|4h0X{ zb"+Sy^K,2WkD>*dž75&"j4G1B(^ys}")(Ȉ-fEcyWK+/έdc9g5-W^rѾatuWx{f9áI)I`p: ok+=z^; ,JƁo@("jx Bp+DvnByol$p bqX#X`kyا |@zE۫6RoB♉WOze۫H+ h~vBٽ4B^њ7:>Sz/&fgv9|յ@f``7 < @?xz0G{AC!N^ڧ\\;|^-?lGSR#zhj`hUL! 6_#v^ÿV]= ئXThw'?:دzm3Ɋ㤨]:FJ`p^>1R3VWBµZu-lbEQyg$; YIPI)$h=YU%s:!ߔRyјFx,(I|K*3 V W=`:HEHo$ s'݅ozw Ve|z▝!ޤԡ]NJvWE!Snn^5[moU,,RH Z &a Z AJt l- VFSvZM:j%I*7ld&ɔLmÓ쬔 VL0HeggsT Mo"Wj%I*Ok6$vI@?/r_d[ hp*lþά/#For?nAN{μ^KjiTY!VXh8νShDG=zzm3fx0<HQSu {Rq}^-uh[zJk?.!Jnچ)> 0e+K&⌐}T]n oj30@ ǬȼpB+00!\&,l&2-q'߷9Ng1Z0|^b`ӦW Bx֡?HAXI8;jqJdݷnO'9a rfz.6^%~EƻY-hxIZW'B{eqvv )I ԀeIkϐbx#9Gϕ؉ Dخ@P>@~;x*Fb@ % n)νtUGIIZ |:9WMyVI2U@ܚ9 7 i00 ۦ7/<8$iVXO'EA@>ꁩQz(ui6}1׼0~nzph_ 0[U#X-FJZ$r?x3̂ T,*K<{>6j9&[Ѩai$ dlDќY%qzXd BiMYGaq$d H+4KŔ%s,HiL|6gb9,K% .2!= Vfis6vAi*q_g=@7pXD Քy=H!Ot"NRzߘ_ؙJ8 gOf #!Tކ s@uO $ob/^pV>&.YYLJxU4daYN{)M< ;[2$(+CJZ NҰN&>&júX=d6kZ^@L$; 3˄G),Y%glwh؈1`?mq@S_8XںL" G0FM})EIe~rEg?ԷxWlvOC;0]!#< |`HN 1Psny_VZ\#[8-ϙ$>XȌ\Ϧf5?_~tlw½׃!^ j#"h4H;S\O|!̻*ޟ $TS!9Nb=Kߺ^-PeY:wuZݎ4#Ռ`L'f'*](VD#z_3>Vo6MƜ#堭G>EYu7nFqZJVl"/}[^v͋hl.rvÑ$nk'≴>6~ye`ZYQv$DV/(ܣ2c.8T12ЄKB7kzlB;'M"?FδyCHҌ7b_sY~OreTh隼8Ekx+21FfY0s"L%7G~orהA!5EF$Bd14칾FdtˤoԉNˌ$ 3}D5KIDka"۹Q1n:5`At-f.3;؀@A؟`{+a!g1R3fA`E;5#xϠgp;I|%Y->#8n4b35 +%p%ڥϭX?VqLƯ a^c|| ?bD@,QVJ 6&o6'D4"%2?x8F;! mq[TCu'>N(ЯlF m%p $Hjq s 6'HH\|,Ch#0A$d2&CfPC27Ns (sS,H[H(ѡXIShr t#0|\%Q1'XD .UQn"WgdXRTZX?KDX 3)KɱXI}&p*9.' b$b)j@+  &Z@ p5ōImDHv6ژrɲ(WAӲH}& }[Eimm%\JidB]ɶt "PTY4^ _iեԗJ@ӗEADX%=wH+-M!өKC֗BI &+Cd Xڷ_N:үkiԥ{.6?oڎ)?NmW'&DHC/ل61##BLFqxm4h7lݝlD-Z#@͜%*f)z/>38_Ymw9{U`T|2e6oqu4-ۤk_ qeS{2+A D B$jV/Lպvڒv '3J !E4J\tgQPpa Q0ԕbl!:>0g%2OwUz,?X; ,;<—PqSw_>PK.r2{,*n_-#Uv--yfdbb=g^mB\6r|46?Y }YERb{nbϕ~;4qzm*ʌ'T0Sa =ڶB ;= ]늜)Y!>Q4X/E $}<@oq+SV"3%*)*Vg;hFƢD@D([Vq:؊Q!CʞhTVf4ii,~Kbؠw7 |!Yb0]kK"-uhآw2k6حntSz '} ai5[F{ ԦZub꣗|0PG6Zk{E{nG/}W6\y#.}oAZR?0P%~`(tijKJL=u]!;!:ct]2$뉩>5`<8Ue2gfSY{J |6]9hrm{jas #V5 0s1V+O=7;}{]ZvfMU7^踖]j)$I'md-͏#uk*ff@sc 0H@P2(|Eu[:ݾybvN7pY4*@=Ym.TPYo& l-{W_;g2ޟP`BO> q~CI!QI miU(^kќYSq$Bg< {3wP2Ps\(#8 Jo @PQWC'l|u;}V:*6sJeV}i%JQt"1C> 0!*Ͳx S/׵Ԛo3 jMإ3;A:P~6D?.y4ȚrՃ^ֺ@ykl^]\=Oʄ=$"hU[ɲ=vz/- r/ )#=ffvD/Uϝv0͉tNaHx,Ϙ3LX%Kmg8*}5߃uYW>}2NKzTई@JFDtjCS2qv4*GL_+2WH>N6˭ˉZ$jc&jcdE(.MaqU֎촢߳$f-Z+@n FW+rӐizh//. H:gץehe'YKzTIڼuID`3HQJ .yn/yo7eY ==yΨD1Ut.ӹiH_k^_ջ`IֽF7n/n{!Pm/}@/*Dߐ u` *H%@@SSQLdDM~`1MW!/b#cBB'LT bnXzKt >틋oAPYfKQyS 6aJW>o@2-!wt oA%1@ Ek13 Ā L 1#¬N>Y<=<}vseaa?Ye].BªT>94…橯pʤ>$=8t@.Q}i!3k(iВ:ԦzThY tp]P8P[oIZM9 &H4bYƥg<Wl;~+],־B9W;n.WqH6^zs5s7 FdXܵ,QN$<'Պ˗'Jyl9sܗ{3V䷮\Oܻ3mUBY/@c1p-5 +jKkFڕovD ؏M(hX\\>#%Wp k22niˎKO}8~rٯ9\{l SB h "Ze,I6 m~O~7nɧf \)7WTW<{?/ RʀR l %@j_Q_xiuUQSc~}CMmU$ tgZmt^NTbCaW<* JVr6zMuo̯6XH*܉9ŋɟQ}N4N)ij Yͦ)ªT>W;i0xD5B@gpUsꗶK6rmZ ЗU}E~׉/$\恟3ٖ:XQ=G82 3D%3vUm(#W(7ڭ>I̹A'M5ͬ3_*翉%;s__W$[eLJȺ4+XDlFreji\08YgnvҲFvXUt^ubC@~zY3 qXP BŶht{~ѽVO=']^˓w\8c$4a5 n\̕3_#h8< ~sD5ĴFyIpygeyA-\Ga46  U4P,wY"038!?! af |g)$`aJu?z|w$۲5r꟥Iý{Ο8%Dui!_#(KU:fy!FA! cBǗt [I@ھO,32<   9 +FEZc%* Rf4}pj S㦕G@lN(G/~p>رݱmraoF@ ʂwqh-I>{ EA Oj;m5ZL@FػvA6XEY}[0A6mѮ$YX|O4Ysyp6hp?l|mhaަ| {Gᬩ\]ɉz.gtO@(窭S7.С ?D.»]EKfOw 1`kvs-չԚ͚sO<]t9jRlrV<*l 晔* OAUOWq /|@Xh!8i6&ar]β1B$X $jv@Ly\{I /!"t}[i2WרڢaenNInlˆ+ YO!e3آQԻgįᐉrOY'Q.Z8d, EMkVQ]`eV,*Ңb O|LTs=0d{UC ,< #/㊺ƶ$x]G9KsfBauzl/:Uv[O+jf!M.'>{4~my>h7S[Y{`fJD0E 1TvotY{e oa;S(!uK#@u|t2$ h[c2+I_LWf3%ɠ@W @3_v3)q=V(doxʾl\I@xF7lM\ڛZϊ^4llqEmF[Rb5M|vS yZkey'Q|d*7$uu_E/:Ś[/~Ś[ʛqE0Y iվe@orV4AG`NhF]ȉ4dDidذL0I= j:: 1<;2GӹSѣ>9vمh:96:onk2`4&N(EZ.bHk$!䒦 &i)pHjXh/Lu2Pc izat"KKXG̟IAyy;]f?%O6&=1ӣ^5tvZk1)5:h{nj-u\&ᳺm|-Մ0/=쟙P&cϴM}W!_TsO_oy"[vJL]Rw ȥ~nr<Pnj7~CQ0+I ? F̆6Rءg8F+@2&uliR +&&>Џw*=#;]uXMjrʚ:׸N6j "r=H"Æ5o7>;<\=)3^iU֦] r|ꐩM5@ 2zaxw=_-x`ҙ_q7U"+BLʐr̆r<|~mOhWx1.OBRtܻ9Ç(M.@6+P1KBb;"b ~8m!ϣ}YowKWl2uߌ/tɳD5Y'KoաڿrkRT]uf'ԑS-_p޴9pt F[0#df.g-K|Y.K`FZbv- b '7kj ֞\݃ ݖtO·U Fa{?6-NiX/ڲ I+72 5_G +Je+zZӳF tUAfNi :&{:YqE!ixQmle,b2oҧv*z_"!OGrh,*@LnhƻQ&͍Rm~0i %hIà#ե|8'tD&%Wt; o?$BZb`eZs²@/{CӜ_0,M!/4gP!ODq~-/G2Mh 8'bCqYbF5d 3ȎA}Rޅ9V#J>ڇK>g[/ v !n[5.PHO;P@bG$`]@Qg+kgk*]޷W2ohBUGx%JD"lQHn@+M=l>%,_[nqLf:/4~y8%vԍ?giO7NFRs4ɕŌ}/IX~O^UKj7փzes`O\+jJ~?ZY߿cZ'G^-kw_-,۝Cw}/z>9˻Dzv; MՒXdp*'-o.(]jC9Fd@t2=q#)E=Cb;t}O;"]pQ.H:ik 33osD]~Z,#2 љyHj*vx}/MaBs1JEk:BkAUT@9}"^أcU}^d3>~;!1s&oHx/n ĝFso\ڞXvp#mKnI;g3J)#]vL x PIB<`ÌC+~bYjǎ{?`ՓAU947Q/|^_`G2ғ.:~6Q#uhR1]љ֢) x8cl5."tP O iȭ>OU?̚3k9Hfm#|8 =PBah6EgT7Ag y5~;Gb= iW%DO]BDi: yb֜3\IJiiƥ6,mlZpLC.^$&#LCVvZ|8O @ c .[5љMuem/p֮  _2GQ^ J겾9C|c(dq!H~!y(V WoCp)!p'8R2>czpBLG4/QHn1`n /,aհ0Z/xh͏/PT̿nf_B.Z7zi"m: kYzJf Iü&l#lFSLwެ;sgWǍA5C@#]j=:6ݒ܂i:XaX Y󸯎Ma.A.]GY"Lq9NwO}6}%xV\%crt LE']JHSYPDlP1 #`S:2v*_V-Sવ_.m܇ƥ(m,>Z,m;PT$%&6RGSegD!;qm@[oeuaiQZ0*lF F+~jy= =qSghFs܃2Id~ Q] 6[\6Dr-|5dq ?ǿ2#Wg6j"y/=aC _(hJntSy})߬YrQ r#,'C1H$OY.M^:iMX`䌉ڃX@LŠr>!Qv+ecm.Uat0z&˙ɤ%ٿ }&Ku5DMi$D R!(XS$AXꔧ6w}rO~eo21vQn惡59m<\hrCp ho:{*3%6MmXIJʓ^ӭjEEm)F}|Qycj3 MNԆ:L NakUCC:4hvwں{[.De3b0w\Vi2X2+\va•VaBe3sj'> d`IAUw +f:_L1lV #N]cchdp4IԖ3 >| ",TM64ŋ6>"g}rK$fwÉ!cs31D2 iQjpqKdiӖzw{4Az"ch&m^W~ͦ~krkfBW HZ(iViؐXQluc$twpɗ#&&=N&g)#^aFdCŒ~X9г4P(0L$O'N 3=pYlI׻6?~d ÎS Qfbd 4ϧhd f&Wse-++sz5Q S8E3{pE3 No\:B=z|oy;&&Rj+B BsI5fZ5b2l5NBU w MynaԿf3Z4`'{kqtt5ub Dz S@\ 4D!0 m`55i#aP mD-Rf={Y1M,|4p>RyQ>}^jHFü Bz;B|Cۀr Ě;1|; JCȻtӱhƊ cï%aDmka:mlH-fb]`ڰgʥ #($PjfF8b ŨwKsZХh@k"ɭoՌOUMw{Lh* {. w{6+}9{kW{j];>W?EVFzmӯx,@&%Z+kQ nxq1zۯӟmn<Zh7)s缋aaOw|&4#N4 8O9|n}$ѷgm?k>%(QU=Em=! ^8: /(wZ`.4~,8A9 ΂Z+ ؀CkB~ OyJhXE27#6W{.|a_]UV)zQkƔ,=cb=DS{xn2?Tpf]:UU:z*ڷy-Ȃur*IGK:Wsk᜶ߑ|["Nw< O`bܝ2I|Bu MHpgQ(^2*k\J _DCm &$ڰqIoZPssgv#ח/^(<.DҙGVNӲ)٪u/sdbq~wxQE@'p#^!h0<^w4=F$zFX{=K\0a Y,ZOG릾7.]{%W󉯳`j$ h|G f cAzM{cA&6X(UjsC\vOG5Ob+5iU+7C9RѬ%>}ep N >%C{~T]W*=)ONҾgp+wX[KфvE8*֭vR2[0LLs{6薾ۈ(@BQڽj1 $Q[qĝPfBȬΤnx:smʛ+PY%-*d d;3z+ppʝi^\OWVAfԘ (4kUΧ"F1& l; j`+!?@E;y mOpY ['t}a֖,~-iqu7Oc_|P aww.l\HR'D8D☓T:Nn<1` HFyC(b~jQ"i/9]ݻ[ \{ZsfneXD|57LKwkn2%)= ?3 TxL @TBY2$YID}I UVnpK ZFs}!$?;׏y`&(!B1OOŸ?d؟;D?0oʒՅc+XWE|A'SUڜ5S,CSoheX* ^^c/ͮ~1ZI1F 4J I>|X}@'WYOC cH%>XvkmljJ=d2Y _| wjgLGY~ix5ء6 DM-E+D-6KD]݄} Ѧ\{|pXT6֫u`R#lB)HBb##݄V~04G600 Ե>s_ʈ o!!'!`! DY-`@C~Ԫ76#0$ ˤdmo>'oro(:=ޘ}<452Xo^51-uxcf [JiŖզLjF&xdg=ޤ(֘B|ǯG*ښTױ+*{$Գv:Q(ohEN˜_dBy>T>|.}պQt5tM˴.*ldA=b(*7KWqސ[s\/u*AnQ[ _+E}]"EJkS (gw R #q!G/ "YL$_@-9zxfO5C^!|oհ_i6ds:]㧺{="FIޟo;lR0*k^W}$E"ED.`.Ly]ow7&5b{R}&icVe~ jR̥ JZPhQX!Q;R`/G[C/hi3&p&CѶ.*a jQHa;ʱ -zդ=cT}:?-'ym)\7U|IXȲ6 "PmMRijɟ]V]Uz^G}z?Cu`[] He #Xft]cD-R_LHylvO|onjG2XYHY?QADcX!́(h I[kykyN%hCT;/c`]Zfv)טsd6D wJ 3]ʝ o bk~uE# A͑FsTo$·Q-**dWcsLB. ؄,9??Pwݫ{/5"׎\:SpmXK;da(C_nٴ#ÀP7*$)2N, duSPe聏DzN4ݼA UJzGjLq`Jַj\Â7]wbyl R99 &@@D#)-ruF/I0P7`'@v>hʐbk_k~6~/[  ET%N??jޭWŹJV8*qYhaJwaj;ᮢ:7N3xqPX(l J׬T4cԓ JR˿<-y*N zoMF;8\3BP0"ӄtrޜ2VJd qj(#xCu1YA9cD5~/V Հ*""s2H OR=RN887+4R; hF3Y3w#=,Sɹ_ w:#v /BgM)%>'u|QCɠtXj_Ffb'EBSiլf(\*4p_k0UfQx#{v\Ψ"":>WC.a/w<%`;"U- Eq~*ʌ42VbX&nh1^e} X1" ]iGKP$ӏ9(Ո.%X˚>̥>XVrB)T1a$( wd7KaE-H3Af 0@Ai̥e!+Y .NOؓ7 0`0` i|>~lu,cZϘ 9/#dL,QT݌T $&jU!?z[M׮U]Siq6-kU*;yErW#Dlq)jJGThdͦƚb t?rW4.[iNrMi)H䫐Mx{>fƷҏ| ϼ*}-Nhy_bݺ6Tsnà@quX{|[ǟjᒆ[ y7#׆^#NHyJxUqxwCVM_Vn>9 Ͻ.c- g!ޠ;a$pkбs&r0 -0;b(&YxFI[O O ɑkN=v|ߚ> Ol=OĔɯ #q\]ݓhY2aLEDWrfն7̹o^hMt(l,Yd̽H[+7yjaX+om.\-,62ڪU#L-&'Q C fƀ=brU3<3,u% irٯ]_d8* aU [Fhg kʑ+bIDba@Ln@Z'! eNMT[H!='yHome=ҕ4 CPE:kkL:: Hho(M܉|C4)I]C-|-eП˷-zpesH9he8 8i35AbkѢZrVb mܐEC9JCOVksjDشb¡@1Z  -*.6  !rJ#:#! ގW} Yon)OݰdCoyYĎ7J`UQ51O MYW&|ni=;Yo T4_We}bтCGDJ "H{I9>7SǙxR+T$k I65`pڜt՗An>v^rS =/b%6*ՌYN7Huw~q@ONtpؗm3v΁E!_RA6_S~cB:M;}~AJ>R⣫8U\x͐Ƅ3 Mhov O}eچ==]u)5Qb\J%e, fϒ,ɎK1\,Gtc$v$Ui#aY1\0YxJ UX$wg?z"xN>!ֶ~:Jz@N? k d'F큥Cis$R}bng Sk2u[;ݯ`o>q wY8h+kxX׮}+ǎ\ h^.Cg'G"~ !sW$3EbL PEX-($i0iueU")S-LvM^q,va$8iAy/2L&=o^)EC=\ئL&Z/B.pq`-XJ Ć mrǙX1\`wt[NW/5)&0F\rbL &;^G,#g<֡[Gg7_\4vU7yˌ2@dx2&];q#`*EL!wBٳ"By8}; q6H*PJoHB)R4"RQՌ AM3"sᮨU 4D(j족eW= @xTQJݴ}LË6|=92ͯP@^h 0Z;+W-&7Meoq[0 eol9 5U`Ը-9ylꍫg޿v[;߻sBK2BdЋͭ<|nw?|DiM]Uos I6)71-w@Yq9 uy-'|In;'MK>!tMR 6HPP PPR(c#xf#Rd}gxY07oߡ tդA`,mAbhjT um פmݑmG;;9&9̐2 >?gÕwcMr]-7eyMM yZj/rqѰOثv68BM)[v<܊8IrZ ڍZ3vi?w-SEʋS$%bƪfOAɞ^W?4_amSqSjڈSyLODnȓ+ [}Et Xr]tUŐߴ=~MZpr[\x4#?/uW) 0'n\Ńnz裲N[y1#N/97\•X(SGN6hi$idʝ@NCHzm$năQs(dԙ܀xg2+uB9p{? ٻ\ )1.o!xdoz_|aR\s/AMuw*aM [$)}”-ų[ ĠY4 >!UϸEҶ1 a`0J2Jm9P-K:qjOU R[ȍ?6\X (odRm&H tb=:Hfq.Gͻm]j 1pkO׌IՋr3#dg˒{Fܲ1 JM*(VPY);eTMJ.YܿE%Z|V|^O_ 죌up/v&F!)!%h\馤oۨwKNL`'9zwG&9[pbJaZ9o 6Üt֝Wm6i@$\ j(؂SN!ۉ9J\c>5}ؔ|ihQ Z> ~sen˘22:x]dʭ=5/lLNOyUC;deHdB^]7nEx4?K'm̏V{sBf͕^Tr+C֑7$9?iܮI$ (0dܿȲdt/blB'jAps(m105c5*426| TIp~7.fNޑpP$POn?&- {kgk_\;Ǿ' /{j'o~ 0hz[\ot@dAZaN FEJ<Ɏ þ{6|'-P*^l@,KFzNƒ=AC{@aքBB6 J:' h DdVh8XD&BO\lgьfn_vTQ=_jm (2@kʛ$WYKZ4B˗},et]a4\wq|V];~qW(us.f>ԚۅZwVz{%^miDnvo~9bRNޤwlyt6UOs-gb*9SLi8i]dsy:0nNBO2%YLZ;Rnr N?(! S]osA_\Ӳ}; n?\yImr=hY^&KՐ^B-Hw#|Ųݗ=6{̬飊n8+i@M}twjW[tɺڶ3pT {5V](27v|`0D>0Bpl@RQoT;oslzKݸ;Ja@#e!bjTI"H\,Hlu{F̄|%æ sHuffjMM]U0 sSD!:*F}E?y#otc{맗̡H6ח: FkӍ"YBHvySO 71c$m r.YsE -H^rl߷\Oypt]z{R䬡{ .`x{ ?k,g(`]V `5O»EHmv2`I`RTZ7+   I?trm&H$ټqE (H)1`: 0iku J\s:ʪvRo: `T!R3ei/GUAޡWm-;|n(O%]ŨZbW<2/x؂! d %7,"0ECZH V`8 [H;I:q-.K/k|w<7KdBhe0!!Ό!j@bk|,Usyݓƽf۠W3U)CVr9MHwѿp3! Ki廳$K0^qVV@1$0GK 2+챤"JFPT* p;3zsѤ뮋d7v>S˷]KL4޺q9?/x}W5ng /ЄlK#L zlN8`8EK P!0, 1rr3qUq+O a430aeNzt.X:ђ-+gyjЪ77T(>>R[]/L7+wHrc'ʍmDNUvpprpht@cxbɒc 8Edѐ!Lzcΐ-r"24oa]AzEP +7<:!KaICG#-1Qx 3E^b۱gpn߁K@mMڛ-"C6\Ȕk.@izBKM)pClud D ~nFmݓi$@Tl*aҲjm[1ŠHU0C<s.^²lFX$A !F#X#he "X45TaNcN"N`0>[N.Msa`WT'BӸ{[DfƠsu,V{`сk2tgap{}bQl]:ҡK-zE1?se)v߉Zewnc`׬e\0rj-\fZ0,@ 𘂼FRUH<i?_U]Y-S^K{`'o4'&_NkH (B)c f_T);x D!-HyVat\Z}s\Z/282ˮkW  8wgE!E_a0iVF~ΆZ ఻4T;Ab\H޻]#t}pJxh"%QKOB!ZyT-"%ṳi:*lzHL4|ZߡɖݥTV+M[yRj:aJd*i }As,nM}^ ]lu&tv,&b[O'۱LؠkX eW}r'@P%KZ`X 7h򓸉S)Nڃ ~22uk'swY9Vr93nWsq2q%׉.T ɶ/nū#FIڪJfBԃvTp2{ӀmN:]Idٰ:`h}MOeݐ]_mq?Kp'hPX X'YIb pBV ),pg0jEVP*k"DZqE*ɛEΞQL1IRsƜyHt95V>!%Zs5t׷jZԅ4ծ^V3HyVD%ѤX[ۿcڧ?z5CԖ&8 '7p@TZ " T'\&z9cDe ejtdn^u)ngq'zYmŜB/Hj6ײw^.)O\UȻ1uLb݈]jS$Œύnz,Vm/~~_ ;tUXر8hFj_j|s-nh\]㞸J8fO8GX ( H@' q"qAf=`a]Ìm>~ߟuynq 4``Ձ%``;hxœ-/մW{ pC׊̏v8Z IfY#'YB@8Its*(^>)S_3%,CKf)(Ђ}[FctOƿ޸o:ޕ:% s@pc/X̠RǍN 5D8WCNKO7]DoOr&{?*R+gD"i[x caf*ߔLItG~u8# P*F HTf¥ HzI f+*AHT٩2A+Gf/0'GGի>-72y0E-!;..Q"Wb}F}epfQ')8nF9D񚾛sajeI-~7;]M4J 5 x# %.p⍉$E"p(xȅgf>@]+WPVj,Kg 5ves5C#I'OZ81n힕y,.>_Qw?j3ZRp ʒ2F$DZDJ{GPqMGW^⳿g4Oi0 i<c8O`Ȓ"(*bU)(_oirt|z#ft c@P4);Au!">eB ZAny j,Gq0dH",chsu4XfӬcμs&wۭׯ$ 1CR6[OX pZX ,^$9t[*ԢBXLx78[p,yXX*P @@XM1q46827eI>XofGҺHU{Ř̊%>h$鸥%>NڰtlSdbVްA|Wў7k#zsjiz.΍.e~j§^x+ = rϵ ۰P 8`$AZl#oX`\ܸ h'L6LQLX/Vũ7qtCuӉ/n]}AsZ,һig a8 pB.  Ͼ 'X~瑙HTpzT<Fv-?d#;O\^`OoHfPh c@DmQBk"фZ'!6&E1nfmBĈz.0d2JFYh#-Z[ $⭲D[yjfVӬ Ln:eq ;T(ͪIw5\)‡ BQN:Z>C\PiJkF}zdf3ϐۄ\nn5gLY\ȈXTofzkj;ց%7bl_Ҿ7-:-5{uV6N{c;;,fڷmF5b$"J|_jl*y9T6kK7tE *zP$t_.Fr(uY{cܨ9Uyԍu.|m'LO>p_Q1 =]g7œɨ0a.$Be\qk.Q}$}~C7DWλU`m> l$@oVs/Fb4Zq\hb? r@ddŰ@ & iH9 ap{>k&r't<uZ %E{,Rh0˜saS[ͿYM ΢# NN(G}W1e@c2$@x;xS02sP uqytamK[h.O`$@!̡ʷ_l`|k]6(,.u:>G`70vAo'8cKxkؽ gt[4~xAϏ?x=/+UR !2Z~ף@ٚGyBȩjJ $ixFm'=]< E57Ug `X""ƬtSY$bWEQTP% H@nu=mCZ[ՇZW>&FM+|c"24hɋ?x/<ݿfBK뱀a'"nD:lA$y 4DRRx!,0+,AH%8c vʹYFoR{`ԒWÏk[:fSuMijn)_8ņc杪å#f5!)>V_]gSa1wX>5fg_7l#1avY@tox Lv\Y7 wN{!f*;X)Gt6ĵAhap2. ?T/?N Z!H5z81% dIs5MK0wvB 82No$OB>NRX0! XKP{E^吴Ej)38!O 5$lhoW}C"<- ~#x͚h9[t~¶:~kU?_b<NSnW`!Cf32 %%RL S D]}t 09y$M8:\FCyjrХh;hTKÊaAHW]}xf/?Ѓc`UzFuIi ވ܈$}-q w ;lB \]@3Z)?)գ"[`fr+B`:0=^H |A1->EDݗvӽļM2ųDErvm m#8RSkQ+LzcP[-jTh$7v:n3$qr(r=e D<ߎ罄nL.Wo8js uҥdar+gȎJa #)b.%EG2%51qҎ^Ub S+?_9*_|: $Ҏom@-%08f$'-:{ |KdIeACeg:;bȟ^?sF`v^'Њ/ZPyJX#>3%O .J"!Oi%³?=v}9߾FrL>b3,|  %DLW61Ug_䃧nn-N;~0dԡnCw Icbшig召݁uBVM_%đ=໺sn{ѺzEuw;߰ C`Dww7 Ŷ.% \~*pǟ|~GxZo6< };knlt PBz<Ɉ2*{&{6ޡ`P5Bٰoз/@>ٳ[=C4w\%ƣT:<: t3z6'5dpFX+%q1LmLqALeB8sa $YήF(t&<8JL@ݕ\3 8C?_e;Vnz_WHMxxY*pH^Tt%BH';17S?|9%vH)Z!N (~$^"(fZRfEb jx}3WdPjOpŁ`m_,: p hP/%cwS Ōv9ZsonZ# B[U2^LfBFL1Q9hzwÃw_@~o7U_I V?但 LfLƱ7brW]Dr ^My=>)t 69[9 1PU#%$!@ /'Yv[w|1`2gbmqfsudibXnO'R +9a .4ysIɒo`0PP鴉`R%nf1L0l%ժzҪDSIKSȖ LMfS{W&99D":5:9?&PÂ1_jݓg:~zoR5RIon3}O>#{߾w鋄IeoWQueڛG#_2sIGVҪKVrhT= =B Hv,@V)1Vr.~4EM&^ou)B3 v࿒X^fQz-ڌFkDt7gs8O+2oA!g#X,WXҩI ^K[~8 +V봽Z!`XA@0@%]"E#T.1T,3oB Vf[Hvd;`dfAaK;k&_DJe+[%LjB30KREܭ%[}#y{_F.slQpuSh"yV󔒯_ ,d0Cwwfv{^uYwgo9  *! %\BAZLf1EbZ.>/C]]<{?|۰s5ϚzĻKRQ)}!]{%.v,h.EQ9O~Ld(eb^Rp;{ aQ}ߛo Q Ww4-|(3`ڎρٸ761m&ornO[j J0`Y11*.H!B,@@?I#@b S4ww`ך_hv6@{L4CtNxc"sEw~"¯E#8;'`aM'&}GT.[_sc> )}d s[czMkYŶn$55a* p$G6`>.~)T7_>*@:a:=78zP<)FMMXI3xz|;AJ.iL4 T_lXB>]dROz:;Y)VI^ew㜧6l Vʙ#t :|gʣ? xY/ #e%N<9:fF.%4sWFv0)V(`ărލX4UF+CM\TbߗK3P%@dr>lk:$DŽ(pgD}V#xK a /3bs5N\]B0<섀 #ç;_@p['څAI:??٭31vg^-yEK4 ")AQhX6;u*'Z ,,%0( MSnF 8}߀K|50~'Fmf XP͈*QӟD9=KlK'!,G@BsQH E[JfXzތ\Nyu*HDA  PdxL Gp$FTWm}ntZPLLECecI k`J3[Tm5:f ^zҮ9 K5B\C?`JVN(Cz]>?9`! 2l9G@VnXZ-?t𯩆,)Su8EI y?\#ݾ~8&'yoR7ۈOΖza<)%e(jpe0;#G䚁B žl`öb m>8nꥍ;&n߿in]u G%>t_;ĩ@AF`D {jy OTWYM`@;NV-J ~*l4hW[f1-EP "h(BXfDӨR PXT$k A >u.@;͚El@{U3чVbJdo [3VB9iЅrZN!H,8;TA6uT,!D=?bg6Y>4'z_ 5%eFZ.JF]) ] d&h'6Nu$5S$unkc̛o_v+E'`lQͤDp:IP !PFI0% 4|JTo>7eoJ?߲N!IPEރ% d !3gynf C7t>xx>aQB,"K׏/>jث۩L9n߶~4G=x &) _(k3뉻fo8j .aa6I-ZfC`+C!7X,DUU  ȫ9~#U<|\piDF5+fIMG ׫V 7?6 9wU BlBj1S'\֔4['#"t|tb l)Ielۀwj gv<m{J 'iSP#80J±?4,lTZ|4fs65S!ӻ@=֘ $l{G1&Ri%+1' ICӌ!H@[p$8i HJ8.őRLv41eK8mYubC|k`$n}~/}TA8 N3|珤d*X,Zo~~&V|kfl$:y_:r"$qLv4ՃPȃoh;MެZA &J4Z;k;I O.VF0M6.}[?%[_5i.>rw!cf0ٸ˵'8s#Tø&@#:~x&?!N 2A49 @6N\;55Τ' 9/ J42"'O{.%oH X9$%%>SQ?NTɴJSKĂLVtÒ:Q;=˳Ji:&u7 QyPEx&!$)]mT͟vuUg\ҷy]߶@ܕx4;yI@C@ ([fC_"`߀r(K U}w"rƙ; A$Vku{OBÂ(*0 a|Z-JQ yfX +fOoT_K:ҏuCv_sڛC0vR"k &awؒsAg: U6I!4rD{-VzMa&AlW ^cp u㈂/߳"BVD3T:^` h*NҁbH?fRg!/FnBitS{(A. ޙDuD9jl݌xk> pГ %nv'rLP ő8>Ik27Zw(>\ƹ{[ىn4aI3"̔;VY.n og59*L\?e*YVÆ!G9~i>^!7BPo+Iu&!G`H5+f^A\#pyq>0:q\;R%]doicDKz(_R < -[^1IXԈ~%l}AAmOPgqr]H̺25cV4Is4JHEFXS ŋn); Pe%bFSf)O:aL2EQZB)sl_©/o4-CP2%k» ʹf|)HoSԝ $J|N*kD' P|E/A1y)M7ަ%S^!Ⱦ)Z\f88S1}rr˻e]h?pHyWup(_yVwg?[UxGBɨLvg ~ŦMm!W k6JRjIvcU2NVdٌ6uj6隊7]w $|VɪTu"rPbY;q7!j½=lAv>y D/|kٖtuxxeas,[hTWmxjIˬ!-Z"t%t_"Y9oKcw]ʩe*f c'c{fh94dcսuv}Qؠk}y[nsxgt&6{_c'q_y\;C͍4Ff!Ɋ ~/r~G(2wWų=.9f{@%^P$\5:4%Ip/$w HH=|ՉSg"r2&p"֛^2Jh(v%=Rw xE.+eY9ӀKM?ѝ[W ïo" kp]k!C&6]i}{w("61Nۖ*:;b^a %ƈGX0ێ7Rwr0Tt ~B4yIOw۩):L\N&y%F^ta״r9y?&o]3vq_a/ތ,5`!USts` aƎ@=HA.n $ͲbI9>D9H+ V 7"*)I1@uMV#D@\GxDžno[u7tOKEeGͨCA%QxJZ&uA)=;=lD+(j훢΃x1IR&Q!aQܠY_%qQOj`[)3HAox4ܼ_w#͸AUbv7';hqUcS± J2QFENY)^'QW3֕dNc/вdV\Wq*F FCIae+mCR+BF=h, iMo梡oXT9/1];yݭ'+h鴬r\_H%G5;ewƹyF ә[ku)[QϜ~8IiKZ@_IJﰆ_^ ٯ;b3569b-g/>y걢$/+ vxECgy_xfH.V?(ܓzwQʞo;Vn_AJ^PVe9?벸lle8ڨCo禃;I{Khϝd\!j٪s7i5Πa(ND~khQqFz;g?rw88RѱYd>OTXkq\k*Np(N0v4Op{)nAm{,s\i/F?KS zĞ7i,Qպ33vf+?C|*ubv< l[5] ,aHx\Ѕ3 *:Sh 9z;X]-IhG؋6IP.( j~ѿ| Ήh".@޲Rk qMdIKƵSN9e)9eYZA.Wqz'ǞXO;NB!,"4`1%@sb 6z:ﰷJk_[AT; E[9yeZ+ڹE}D&5Z)-RZcbT.ʵ>>eyD= u`қ(x*Jpn5w_#Od/Tdݬǻ^Dz+vJܰW +euw]j/ uS|g*GI \Ŗ@ 43oRsVGo?E࿎:3jʫVz_Ǘ\1E*؋{#ͱ0}8a 衜{T"7[԰4u.p$%*qP? /,]%~u c&m<'OLf\ E(J606A ͡XнkP{%H<шüK̶u)L5RcL+g2\ipaO7d.gSJYI$=dkHҨd\wK &SRp7U)a)$F*@` (X)B抖z~Z%xQ]P?tB>UKdE2~q 0hTg)$d&BLy1s ḰbEVT`qq G+D 6P-h87 80Hʌy S}̝&t IH7w@2(cG| ZA8=5*&u+_zh'ci^dF˶h)OXVɺ 9_3mE\83z*౴ԍԿi pC0 EZl8#hs K Y7 fJ$ 6H).'"a`  zƫU3<L kHuD@ۉrP\pj|}oM`fq%t-yJ3 ({ߞYi~A^ i9(>̣fTK%:*E,nAo O< n1'f61- ST xnosW)aÐTDQhz@~G}i5?yS}R|MN r!1Aii*2YǓS@57ΑtㆍjȐ4# T͈Y A)5|.sy509Jr? s $IHAE T i䱔AFGTN U+Ye*W 5OB ab&J:BS2脼atUfuڸ٠ogzC=.ZſL,)["r9OZm@QMUϽW+D_e) h[Wv<oLfj^ѝ_JIWBe{0%$A[]k[ wI'Qj%҄#h1rՀAcC'c&]t*9Ħ^|9UA߃SN„Hc|&;*zSvJlQQZW /=rފ. Ń3Iub7iY_ $ϩ8j&,[Y9r$m&XeyZʝ\\B1 pn DH֐  `[.(c2`ivr sD'n֐`~ ~8;-7$ٽC(0Xը\*vSkZф@~_$rV f r!O~F@ $ΖmO6l90S\Ftq@;BV= "]hWg7U AC>O68]+BEmkO{hA1ﲢmX]@!0RT @7^,goD䆌x:\c;~/:,tѰ9sT!h@(BA@ JpV _OlJXS||eNb:5UO=pWka;Ķ>=M?=NalklE}R"Ѐ]4%e+٩ vN땢 aUI')XrtLV}_8z=?4O{'g<py$MyLZݰX?o !Oq4'6w*,1 ,$^'i @ !2~#?-`t- - ,(}'nYN@;+!BLeRe`M M0u9z,(@ $ j G_8c Q\6=⭐њWL%mTMT><1"b6cl`6c'j1T{d"cTNgޟ5XB&=)>}Gj|2/l,$Zs~AUQ`. ?9/ja׎PC$p<)*QDh'B0@ȕ' ::#Բ(.Հmf xo5]b㰿WMw}y7OĂR`$n,GE>"de D@Phkoe:"ή. 3rk~,oi\7P7 J|IV#3Ժ/AƎתx%Z 㧛 %(҇+,`8 6m>lj&N3( $T܄Uc' IGrfbpz\齕8pxA9 3_q0`b摡c0v| KM48{; 79yn@DEu7{w6;YQޚq DAg$a!ϡ9Ю໛q 5|0g0b t@'W-Hp65^ !]":9Tc`?]1.? IY:{c[oIi+.{} k)Ӟe7 $<}%Њ ڡQH.7I@< cr#LW|ˀA ( !3 |eZ o%\U<)r]SG.jmjuY oh>]/Na槙PHfnh>]zOv0}&Xe) C%kbZUV-Z)MNqIɲ|gQ>qPp [2 !BIɕ-Լ78\pɅ. Wi\+ !aKhth؃ (OqVE tdt,EɟnhϚK4 ذ*d!BڃBYzf`,ƣD.uX?!+&/tqup.]3OsNIg,|> ƇθݺXe]|EENRWۢW-Q;D[ 7xD@Z^53MԐ.=$֖ A&-e!3 B_H:!.p{Ϫ4Sf U9PV ]'G *rk{\%X垥_T$ $i4̳KwVb xxKUxnEzsf5߇OFQVSY#BtafʱLfk'<ݡo1qE`/vkfNnO{#bkgדP6tE( 'yVMïw7f~uC_g][=V9ݡ5OLJ?µ"oaځpFWSR a@Nego|3 E)m=u,J-#? R1`ʒ$cg~(;;ҍ1UΧ$aBkᴍʥ-%`^lW7b9KnKfS }cVւq{ -9IOa ÍB쇑1@M++f9_/ٶ(hY"O7LIs+kg]V$EVkȄuq2- LnKnh\CeKEfGC}XE?~Ɨ.ARf"Ww`nIhu dޑF WnAVI *}9]!2_Bfs>2UqAr>O$ 8 ").Bf\ו.6(&kKt`j6 sLjwd QIHҎhQm LTn-0e?'(~:{ HCpc$x2rtƠ _3xζmi]iP3\,IjWIW[4T2@DI탺"gRUT!*mh^IeئIyhW {!v22$W p͆s{*bܷe!BRgaf~*&̎aR¾0o6vayoz6Jba> fg&$z9spd' Yj'VLvXBWtTȐ C@*׶tTȐ+ ɕI|^LNc+3RWȐB=ݘ Rɕ0쒛){utǛ"~wuؓE]fe;f~A :.5nhDam }4# FQ0y"T5Sp>>E pމ36nofWqӈ*R~dtJ`qq:8tM07`e5;s<$1g(yQJ3b @u3_N8'[L|?eIS݋yTYVxW̹Q&ە?wvWϾK%Xf_`[%H6k8#7.]Q {k{O ShݕpW?*,ϳW־0\Zl2:qZQwO,ObџqnT6W1ɴS'磿:NvpLgH\ j =AJWVGDoWnHwe_TI]#ދHRvk4f&u4i 6؄6IJ-{362A9.okleЙ* rc^z² ?ʝbKjj|/ݘ`+Bbd֝tu/]9Ew3·ʹP>R{ L/FamلM}cʮ[Q(*ǡt0! aj^$="u[!@WN|v P2s_U[ſn&/,FD$n4偡 FΪF00Ú7'хɓoRlс c^rαtkf۩>!}  7rkx۴p_w#lUxs<\jL?Nn}3=pFF"E},/7M:-WLTeu o1,$ {E}*h^RC{lbߧjJ)KrT3^َM‰ߦ.3|gTal3EO$&qf[S/,L/&$\{d"ȤCC F><<*Lyv_}?~gfmq~vV/Gݶs$ oNmˆĉthS Ʉ) @SSAE{l?6U=WͶA|H<Ժx5g[|Gt{r0{1Uϙ]#wRﲨ6hXhJ3-8vY9v?6 o`S5<@X+%v5" Ͼ+GC*6xYupi ?͜J"> >/Z&XD$vN/I҈Ogέ˹h>[׿Y .]8u'Oqjx%>?l${ݧ<T'\󺤷1RAf=ӆFcX@w jK@_]79Cm {Y#$9km6%F=/ H+[5<;H:#j4w5{H& g1 P`6 b(D;FpT~'4ǸJ]Gi/M;st%IZI5[_o:/mAWN1e XtQ?u>CqoY_&~Q¯JC~{P*;+2oV&v1.tt5i^Nܿ8 ەvlYz{ۆp"z}56yQ= I{CH- ]['PZ0orQe@/ԥ @8?~G/o}?k]u'/vo*/^Kn=s?Y]ORE>U]oScc0Jw=UW;@#`!H00"(*eDoǎ{Vg#7ګ4+. E|iD ߓ3 cb-vYO/L@z(RA"Xjd 0L*׎p#jPT #nرь L\fMkMwp|_-ۈSBu氬Ŭ9W'KZxu -!r4I 3m\" Uꤳ"7O@G7"!݌ĝOwYHoa l6\_4$;z'DMZ _An[Ȋ%͝BlMj!ӥ0'N\EiHA#M pi(}1wcb>×qKSi lcĶ]/{!}Hmk5-GH^Dk.@( (~7FL/0wH4-0IDHۀyJ~ܘ6W $"s\-}: #!:% (,zS;OADT^Q~2j"IBmS!w'}n;B$6t=p;Z'"{*imWT=ݹʣ"bss+֗QǍ۪摡||x cΎ8e}7P:_s{)?uPkW.ߙCEu{IgC%MFnq;Tki=ei}QrEA<pHG3Ʈ{l<ɴmA6Z$!D}Yoh;%8pWi[pMh &0+J5qv\6aħsVP*.< %1.ř<dܕՌK77ʹpq~B<njݻ^״uDqO1˯>[7$BU-}W=+5} +|w6Bowv4W!/lvfHkr z;{l\ޅFn˺tݳ&Zf́i*W7|/yxe(&Obc~|r/(3:m=O~웽~߇aCٻ7US,!6SҖ4Mn[ŧAb@coqRC l !x$S؆@pn9d_0/} {A(otUU501G}wuuZ dJ~wI,'% rw rSPyD];Qu, {Eg(Xh; oBz"}{­#2 .svFN/mXOTY.nE5B0cc$R8dy<_rSwmMdŁ-d7Hΰ6Wfg"TgV7%Z&ocj~o?U1s;8A "Ǽ%f;O [˗1uq1m<הwԡ{M{YǵusP]2GAlZԛ=e6/9GW=5mqZL?Oz]9K2gol|fuPs]5B" -,S9 9MڏP!p&ulrN$APj#kh5ՌMD'Uj#hD tR 3|3AI {~SM)ss}?^5.hgc%}oQKޚ/-Oggwt˒IY_S ?nwOqL6i&:ߠdg,Ǯ YT000'F~tY>{-Y#&>"@e-A+IpРO JE qӵn,caH\BV=´םe׊$|g|[+[I*s:;;,׃aԉгS7VVHJE񛍪r$R}*av4<wglҨS'yh*D)m"Uycqʑ[tVjx3("Z+h%ABA@gu0g,-;`+!]^o?xk>\yWT Vʷ.:Ґ_~hd>۞[=UK:][*EpィW,:#c]8}ϷUb{p "b:7X_(Frlq ` 4/uЗ_,N7}`RLP.{c5'et4 bE"@`0WmZDd_s8 [ْy`}-*8&fILBndIKY6+O'ekyV~;80h pPb(YG. NÝ=`^7((׽}؈%foyC7RO3qirjѼ#<^z-^t|ۻx 5|jW/{-Lin// 5Q>;%wA|Hd܀ZGkjaHr}꼹DGּPi]y}VrF-R'h 餓{JF+T%AȵTZoX)op%_?4iR]`ƜЙLEa"kH%8Bψ2x&n?k'wR1r@@v[kXF˱WNܵXH7K&툅dQꗟR.2&M:ȂY5zN.gob6CƁJ~BT"?յ2kW|!ĈM U< jpp6T,fʱAWv]i9!5>d$c޺Q4*bhr}Sv0b ?Oeo jD_>@ ZZlV*b6MG"SL?lzItB۰1W{x1h0 BD^hv|cI, `=%bLPȑ\2Bgq הXpE&PN4Ӕ r]R( 5c%2D$nanx؅pE3tɲ=~z4(B>/hP~C!XmJ~ 3.5D18qt%q l0 O"5R~0z0GVx̖mI,3h=,-b]b͚#ޞUjɔ2A`HP^#. D#&VQ9X[kV+0DQ2n8οLŐC^o+#*ov쎻K}Pg 0½F?hUưԮH̋uŖHc蹦 >.oK<7j95GԤ]d nB'^H5WAr@_(g3W̨tRxd]*H!î b#_D48$36]r)$foomJ;{c2m4.8XFԦ]gkiBT 8$pkЈJ)D z%71t cu$7ekbB"e?t~ؾEHyڅ0Q8L dJdB 4/ٌuLn*>mIk!0*YiD!!U »CPY$?MM6)x/1ootΒa'Ư0PI@,h@+" Q.@ğxO $fFm.+d㡱e 2+%x q .&XCs'ik}=}ua&pN; ŶwerXgwS)D6Zlb9%!P/ wa YA0ym^|tG9Xsh[l'#@O܆-{W.0{zMmo}XP@ !IԃF ViKs.Waɇeu KsCPoѾ*u @spe}HdWUa@'x/v`h ʟEKςY.6;poZ5s9Go > kϏYһww>yFDn>Q/g9bD)i'SL`'чw@E|"MTStj7.V+h7X'7FKr?nҬ/?hNze9fεs]x+c?>X>Ty/hTqné 6@@@SoS(/ܸ,}7K'q,/݈R?rsH8iFBi\XJhˇ; `kf:U:Uc0z,G ImVn4*{Iɠl  ֐5?aStBM;iFS #1z9DjH@j;BxA8i++mqUSHC-|k3֕K۱%rA@:<'D;"+gQ.CEE*YʭU[R.@ST-i<ᓁ{Qxp'NRа&,j]%d!(#H5rMMA`0rpxh^q(hP\qHճ&U"!KK4ʴ8p\xsM8cc꘮&*i6-)\J6l=<ǚ16NŮx|m~U'4ooQH~ȧA4)%ߠoy =h>{%UjS]yvXkzEo~{H]jNV Ύ5~]pa+OgZIO=]߫*ΌzAGҫѰ6m($/,zL$Ɂ<)z"x$B$2^0rG |m`gzQ0CN,7eK7Dc([\K$",2j5(I -~Ob]t>wX,M$Kbs@ {ㇿN pq9I|>E;5ZѰ-|:ڭ6HlOs;ߺF;{zna5jTb&MdbiOT4B1qwoVMiPĨ0b!&xp݈cn>C@ "`Cgw\Agz;&ϴs_}WMOEPfoT$:[u9_n 9q苓q}i#ԍsأ>BoKWLly{%ujI*iMŵAB f]K(ǾϳRmG>cߧ.up`P{-o姣,T8':Kwv*Pp>vq_w|'Go}s^twѸ M%?Z6럯o%Wƛ7NhdCJ=#x>@6+lq>wop<#~NH(R}Q6L4[Dt 5@PVpd(J\2Bl`TRiu ʖ7zi<[;wjM0z@4-l鈦=~ vQ{iЄ͵1 :m>swFQ5_:O6Qad?87nW@󤬇C{FgD;X䊤H0D1\:E5Gj*Qᙄ (T\䡪V A"AjT &l8sU]7t^IR7Ƙ Pjp:RR*9GJB M0r\bIoZIZ 5u9"(7R4t<7l98F;<3 ý tIS_6~WbѸߢ.O~ru!w&RԨ\RCv1~[s>M5P b}Mɑń[9Vov?A5SIl:dDY+]+ʮr}Y2Jw $Veөd3ĉU 8`GFXS)n;3P"t0m[-&5#WuKw&؈n9{P C ŵP Xk@ JA];P~v#0Ԋc%B :L-;=7Y"`C$@@` F@tIw,oxfRv֦$O4ѷ]:-Pl$=ZF\ jN6trR5j<[ՃۥOʳ*Ԇr&фP (}a*{? 3Sz`5D3)NwI- fyO RxҰ`xt~_ 撧i\y~zJJ !@i"@2ݮZ1[cͫC"g NZ25]n_Wy7/N@GSh (/|r9IO'g̭=fpɩJJZ?2sT⛴ M\ ~'ʥ".(3G|l 紿([XѪ?›ŦM͟h7q@vjRdphr2Ԧml)":)<G{\]3OͿ# &4+&+ +wxD 6x-ե6(;p utMgd߮VO'Jz^ HQU}R '}yhU\32Mx'q~yƽ!77p,0u}F$ULrtMAb _Fw&0 hBRDԪ&Cna^OWmT*hSVb6[>սBT3jD3Bq7cF˲ݑ9 ]ܣW{By&v] ,ZpbszO  %j,g-<8=;+TS׶'KV:5W֐a@&x,_p?Hg;+s#S.ť}wb%E'+STUE.8I 0uY8K?PkU˳.@W=E| yj8񑘷Z*DM,nY JL2XL#\5;kɚh]H&A-e ދ1D3ʔW/4o5 -GRq/Dn^ARVOF —~s`Zz'񝟧E^I$L3D9J0):F`K{39EZ`|jPHmQR\ǤB %oYLmCq𰛗nSݴjTLlr5[n {'P?;s5!d{? nWY''NǗg"d>?8=8\M;*U/ܮ9~<\aet 0QosgnѦLηoudM@2#h.Q>Բ 1 C `fW̓;PĖKoE۫kqݎNMuE\=J*ƾ{csJ_ɩE-I9{yCJ/rOU͚i,V}a^A^̼m /I@dDv ź5ՐDf*; a{4wRi4`em|8D%O<-=4g4> !v `Th!kq<9:kIHNˇhNgb:g2T`A`p*SK_8J#EbrjLK2=>-D+UiKiBny@V:.M 2Ғ-fi wl۲o> {Dujs7jƍ"llMܣݧZ&Eq^ A# ,k/lrPovTT 1-dDM+b-YFIiȬ2>+뼜)G' 3+%%O)߽Jn%dÀRHT26nҍxa!bMb Dl%ԙ7nqTh7϶| ڭR7GlȪ=UFI& "iD -x=Fj"S5\RRE^p61'PePuAQpBUG/Ώqʱ'9;j_S'!<<)s~cOU܉ZėaG I2@۝?\ gMGɖ*8BJ:Jӎ޷Q8qDL^"<&dAVM-@_s]?O(Mt4+V -X6jMJex0;UP yX(y-8Xq)]t)6w*ͣpa7ݽ[;L/+dr_x"G֫I  YFqOlf @M?;v^ݲ$J!z|_\H@J& tZ"BD3)ι>S?{[;\`+Cjz˸9pFp0S/i;g='eE u 0ghH-x2 ̖W8E,>$8ySW3c6?8(L;b̀@&k$'FyHE 6ag䅦ͬYNtT5'C!bl9rJ.] yvLN7Z8&zL_XrqSHUtݎ((Vi6irg`IQǧ!1G[3Jt(gd1p, `UEYLҐ#nx! pHlIR+n_q)WX/mQŪ["7M?Jy)Qǝz{ c@|n͔ɂ%RWދ0 cd@1V*e4\N/(#>@flV$ToQn5'/}Dw{C;:L `%~38*T3>:GdEW;>7c1?Mu?c, Ɇez÷ʼnW]0~nOa_ " ˿K)K ]*jڍ֦fQMyy[]zFqKW>.KY@I9]_֌tdǙl{8gu3[zmW|)+Hf7<ϷXeWRB! IDm=ڋ5>y% 3{]3D1{yG D}4Ģ P?p.+)HZ^B7(‘}՗KC,JZM@CV@dij|`3*Ifx>]%PXT3\D|-BxO" P3^9Z.5բNl@ܑءNP۵S'hdlaʡNzT`oԻ}~U"0~JK1 ZacKq^)f jKyTv~_*3.uRT {V.nq4 bw*QVR JtbH"Gmd+hT1 lHXAD>V!0* r .j8e:q :mrkVZ>Ywߞ `|/= u}RJ~s) !w~E6vj݅Ō^ * |s(!A)g++nX^YӔB FRG)>{kG9yG %`)pidéCYHQp kXe HcpBYm3rxǩ;U7`P,PԳZI@:<&Hp(&iK!:VB*?R_ׁNjo?gaMjNVC޻GX.9d۾_ջ%w'yl |cCʷGM3;p1)*驅N a@`.&PP){>gO' "P?]"mv2b=7Yҋn2Yޘ恅m{?,v[eSmjٯ &&}E+Ҽ?m#7tv0eD36E8ZTmF{d+Yvh孖 #1aZZrƼ]UҸ%D/00Ss˱OB7l+ !ڰ19 -{Plpq:bnL @p:[TCճy9\/A pV0;ұM0gfJLCIc߂#1}c/ܽ2rf%~Da%3- 0A|M``x%۷KS[NM+iƄ"uh3z{;$r0Scn=r?M?wUK te9b JX>jEHHl̞V/o={Ӈ='J7pN5?sCwlֻc^d5.D(1h}ęo'ڗ }tZ޲5٘V6N;|[Lۊ#zz_Zχ@qJ$Yo7bjKmA@PhWZnlˢ)o_q߼aqdj{41HNBA L4"菎[wxWOC0RM $@j 9@emDcNFB s"<~돮܁mcVhD:; >p̖\;+1aǯ"2Ru;g=%h&5v2/5[ONd/SʪiOLjN"wʶI6WJC!ZU. Ob(I'݁Q# HX: 1EբxM't@@:5&5 .9%֤8;SٓT4Q<.qe qauN[T( ȉwmp&dd=uGn ٽ:+ol+ZŶHT`uIt-= ,5ZܦΜw]V`?x J9 0qf}IIHbvJv [DM-qT#=uqCB:?0ѐbM΀` ݲUl XL ԖdZhTݸ|xlH n#7J7,NMqQ}Fyb-I46gU{b!.S匯Jb[9)h j 'ch7ܩ.M0ҌTl9Mǂ(5= U!zZrFAMP.}ӯ}_ݑvu빎ol $y'x]機GO5-쨦Fzs9S(IfP#iɔP>'&i/ j juZ Q quRu`VϼܪglYeqPֳ3PhX#DHubLN˅?zȕת84afu;پt=ГgXgB\D .IO+ZrN4CaeA=dY호K}IȻ=/}[Ά=(ަxH yy'M{vhihuÇ׆[BN6c.ŭF X]j:W: (|EڗHHk;9oJ^(@1h(otG +M qŢu0sӹֆ v,䲓RaS">WyB;~rG &4΋WqTIU ~S)呪R:J<АINgN *W{ +:ңmn1<ݼ3t,t`j-פ:p ܭtDU]AVXb`vXA0Y3Ay :pvCPJbɬqtLpDLP6LYԱ@yFаnډmzHEJlą;xNWMr9^:nF,;/32"w5}`%>Ksph]C'o!EPD;Ǻ6jG?hJmu:cҙg'u;56xFn$xnzPvnuXw0X;ۯ~{yuʯvew_BX7(Du{Vשw;>7]\f̄S Zz@bz r,B?~;!d U_Zzֆ46jpNFhXX;SsBjUA?o\a ;!X!|rk\o^bs$ #Wg6ʧv1 Ie8^& HC`35O,IZ>%© " bI`0\M4) ihI]XKa ֓ku"7702]RF6ƎՓqr@j(`1AX=3@as@ZbvvUWv[@ċajN RЈ%F$2ox58'jffQl6JhYܭaQކQYf:qC h=ͬUUEϘP! |6:pԹխ1}0ysCCTv-oDPK&5ĢОO>ޏs~yx >P(LArQCAELUOI`g;7QP>,8!^=zAwɺwd )JA1`@)4CdVWEt,08D5f`[M\!{~HidV&FCCw59|E馯adp~|ݯXWwO0Ř @OrJ2DO?q&wrN|uݢ?{l=6e!Zn -7yo "$Б !YAQ4rʮ:""0 :Y-d )B?)d y AcfS6 TH4/WmFqeoCa~{JWԾQwMd䞔GR8j#V=Jۨ&Og~qHg+gg[pHa^ l $Ӗ`3Ufƭ/pPZchH9vc+IF E:͌wA7QJg:؝eu̧c@CO~>\C;_rA+8i͐ѯ76JkQt0vruό"™1l8-+ DabhmY}s.@% B+DuK%:D@Ԫo`{`Qo mд5c;cHO.+|"w*% x2wom{Z{K_j-qHL)=ɻS`b&V=:WpeGiw5X0kLH:CSC$LMLe3u8 4^,?߳)&ѷ mz ?m]QeQ[hٞ"Gi񑸖IxCw YzSd!%TZWu$CN1}ϳ]AodZ63ͱ(oqPf9;/#E*,[0i'((`xw#;R}΋-@<9߸ؚ,kq&gng2*gU%B/r)!X w LA =EA04(s:ոĭ )<>;ı)o#z ;x>7߃~yaRT?N/ѾgT69IP`N;8#8N $*O\$ c- ])"B%2;}ڟ7rTLT-)-\4fV9&Dء!n:;Oĵ}ںr˼;TCY\G,R){:i(-YXPlbQ{G\].Z="Q?[S?? kYŖ+345~cZ+ w#ZHO}5Yb8=_c;lBƗH=-A5Ѕ&P`SH`b RÖ䱢"Gcw3;_{gB"B}|6x%UJ [{6皏%@t-MB4&¢yk*$):) bM*Q$rlR PL$R9ѬJ:ufPI`Q)Je!s `lAmH d"F$Ay/y1oCZ(,`ˁlSLjJ^y-5;Ző1PfI8Qkc-r#0FKk-MYiLQĀ k9CF"jU.UVQ`juc!@3O{9P4&Y;ж˗m^ANJ)) ђXD`jCBi*0O.6^$hOHQSATEQӹ28IWi5 k"j&V86OU*'*W!5X#&}bڗcbS "B&1OyrJK/W)l^ҷsA<$H7-XV?1G2ą^?N,]=poogiTJN0vVBf4 XߙW?ީ8kS%tWhhz8l3aY4ᡈHf-`P_bvJ`vJpe!Wx[wHd/EN1Oy cL*(<ه5,_#ϕAhEaAAK'g0rjA3ҮJBL?'{׶짜hkK0 !< QeHD%?:?IYq/±'*Xp:4HJCX' %KArx2#W+!I9' '?Q%"O3@A܂Di!U*@G=IHc95F,x, ;Fb+Gkc@>Ė5uA$Y &Y>PƠ%&zY-F˿N~[&=$.*Gvoݜ'} s]IyuO|\-y+48NB2lӅNl- :+) &jC6b&HسotEkgUvOSh6t~.!d`ըx֍j:HeK - VIqޘH *$58VXSV[jMnɆB$FJ"oxm,)ZZ?D>{\@P0B-Odw\fd)znIz5mLjkwnG1ئV\s-K 6D`nu  'G;5E^`xo鱻mc|⁣rj&*Ie#J9Ō16VM'O4`PW2ą2˵ɗo`{/X&x|ʄ5k5î:?BAHELtܷؿq~^ߡ(emFwٷ { *n|gCcYq $P`|կia^`[ XBQqk >D$*{WTAQAnpPQ@<,1 ۭR( PDhs9r&<&e4Ms 6h¸$fY\]nM 8a{tMԟg2 ^K~g2]Ӂ;#.!6Yi+;_[_lOXR.w0COMadȂ@()*;|i{C"Lj#9uT ѽ(릛: ʄ?A5JC~qLJ W CU҈DHd8d_ wjTMԀbwq9bМ ȰF5(" @m.+QT/RcBqFiU_Qѧ%$љSJPPZ૘XS Т8@4 D{ i`\f#Fڀ*+W%6,jo-퉯3kQ%@`2xDB(w3爂,XM8sOk9輒DrB>!SyYk.r;'n.Ր`M h1 }ѥ1R+V2ԏ͊Z8DVʖ0ֵ@gx]$U]pAT[É6r?/T^V@n췏8٣[e.`ʙ 91 %= IEtl{aN:q{ hC=IYUG;) %a=Ka1`%|nyC6l\j4fiJbJ6R?^%6d8xs0p6cMMM])E ƈqQG͹Ÿl \.訨vȲkn0KiR̕6[iؠnhr,QI9^30ϧEhx_ՠi`EGke;-l fZ?3w~ﴖm0=t =5c_>)/ - |ȠWw  r^(&-֙Gm{'U{9_+`('޼36i>+Sb *%p@Q҄>#|&Kma`񍀿x`R9@ \ Ե¹jC~\$ȸD{Wf4\USrʫ lK3q j+`^T.pd0bՔ3~ŸQGf׆0'X ͘OJd}orŚ9KdޟLw4dz``i#*d.ӎC3 kHuň25+la(@"&8tbq`5qQpKrҪ~ϞPfiVS !:u|j?&_lެO%bqdfcMm)Ѣz"`2j zd2 E,CfE lO}s|3ю=}X9NJZEӟKj_]h{b젩MދQuaM&5OiM,<~gJ C۔)=_PJ#Pv(&3a5 82G2hqhg "HH!̴̣aQV- YFxZmolQ@l5!kt  X9x`P0'b,#E5_q`#;0:5hWZ>Z#HM4Љ KAt( J: LW1>'؇t[bU! 5/!&8zm`!kKe%ϜFYU*G*I Ʉ$\Q,Cܑ+^ `LL`(c4 ,3 W_N:>!hI'akYQg'G* JTmUVQt5+YG) uū(C#0X4-*qcӼTa ު t!̡40| 6gd*n͎ksp}T P-#Sm.=yel$]*#2A'9%D&0'5D#ŗكcjLwn3(d OP 7%7P?tЭY?p߻??v=vF9>i+s87.gw\YŦ3䕨C@S,$y(@bUjSvi+Vdn!+߂ X e˞βw >N;uY: DJKr{e-EhL;"\8rqKGӻBvtYbq1;2ʪۂuMAc~DfIQuG]=|_CL02Ʀ0hV}q|t:":CEeٴ.H-*D?ݸ3mΓټ11Uji枤]u &d+o[$ʬA}zRz_\~ڿtNuD_*>gFX9zT*\]Ǫ8:;Νؼw6Y9w4kZϺ5k^#:CMc%#}\7b5FE vH95jhB:HՖ}[<}\ B[Ѩ * !SzA(rEh @4a<`s|U ?nB8re.MSj7K4Fu 0W6 ՂU|!,€E=r!` NctV* !%<_K|bM|Ͼ>l>.(h >~fs`f<<.ꗃ #)6L4 #^э4> ` 0Dnؠ C`YqI\pT_$ -P,5W B`:2X-!DJ]ޓ7`ߤr@v_Ѩ}Jdy8kUf&.nU/%Hy& QB: BCzȬb*ဝ ݊M8:tF0K=s#!b]1 3E56}hD4obKNܰ!z/EA]~H#*Ws&Hс'u8kvx-)4m@s AOۗ-m5K$F8CɺҬ7tRgj6\YYc\ѐ;G- MʉLR4~b{ *yY:;qpj7 CJ.XK.(@կ|yBÜ[3tc+,l)W^FI(M$fU '<ȹ zI!g**pⵗjVh!*\FMȨvۀ rJDFC]K+'{%Jֽ!X^-O ]`0ʉ JqI\ @5S" B !_EI㣱1 \(Ӫ+6GՈ{AӪ>S0@P$Rq ZFx}ƶ,Ӹjzo- 8C\Y*>ى.`Tr7G7VknD zv"oCks[P!덂MKew|2>E P0F7#NN%@?  q})x1p.fb""%INfAoj5Pn!ΰXyBZ$/RtFle +ꏗl@CA\jP?/J ǩSAwcR=&J2kbLQu8~\IApȲpLo-:VNG%hu@OF7p(9"WC_3 %?H\oE[ǀ8<@J9 m|rP<;E qƤ8ut% - OBuPΈ&;aQ+]orׄWvm/Diό+:w@1lʦ/bA k:m r8V)Z(xhanhTjIa;W#ΰJMyl prX_,.ϛC{j[^3![e2fu(wHV^1qu:::L@ȦiT>RVWΑ%ga\ވ(L\ʙQH r8 8ȟ:pƪy[<ƯtC؂*)"*g${KW1̼n^UL.Dv1#q8rs'G@ctQЯtHTG ٗ#Q2qR@ Pm̙3Bl;AVO~\  ;ss92qI-|!3.? -ҌsG,(ܵ| 0F"j%MʰI4)F,4QЎ9}hsHDcY fq(8zptpLsFr:eSr?ߋoM$@~Fw4dk*ȺDAJ"lBq3vm^NKΡAET P)@Uu]9i՘o?`S!OuG&^I:gν("NaXu^ZPЉp[؇RӳWX m9jA**rNUumFլýৠ|:Ց}zPN!A/DL.ηTt8X4" cUu" zh(Gwuhsqxj(|.9wja_#8ćMt5~^7#C*3Gą8 %/A@}x_/|Jb@jwz^u.A"z/@tV'!*j|`HŋA( <6]qQ8O;Ztd?Zߪ>IV]B3*sId 8AhE0(N51VM1ݾop#9}ӆKBaD@!"XZ>lG&MP3%4ɞLg@o  4* ӆ p1U`fX®.8z9P'Ha[40B\ &';i+ϥq ;d8v??xOx.h E@;NaG_Fy_ ̧G3gE;Sζ,nv۪LߤYS4u2Yf{U2;cr*([D[d\AY] &1y>z+)vֺZa3REYr  !G$06QijI7PK{C0G D:Xѷ,[/ʲݩ,BcsQAǪb'dox=)>sLb!I_NHظT{<܀os}郏셽^dF4c@:'.># \QLCГOk2x퇌m;F$CN`3:K2*Č؝{JؾΔM$cHcG+c0lF⟉~vԠna,2g(2ԩ9Z&#d(L{Z/j(6X͎zsnKDO6T'cW P%- !_Aãlq;lC-YH+ɻF(T".BhT|s /lH[ډT.liWGdVsvhxZ;(q h o;h5Lw ڧ!ȑ̀ͻr#?NY'U_ʮwYm b(5u6ݫڕ+X,cY73'aXx) 4 PM]u(84 *`a@>7aH·S # fBάg_\\4kJNI)hPtǝ?y5!)vT~gZUt@| AF`}u,4ƀ0_pig$Q ΀ IsgXbIX,K0q5NZ(BXvL]*x7fg2  I?46#I"\E7)?$%Yުl0 iޛUz5ǖ`M7DY pE$8-u.Z.{de@Y0Z7B/<,KÎ}:0^&+jd$,NlSpaH0t1dac][FRaB 6'M;] UxӐaUxdTFBՀ31v.m96=[U˶tm_@BT7JDDH#u+k<]Ҕ˼3Eoq%Շl +A߭٦e嬷\p%yE?QJF 12\5^".|O(Ռu`u]ir-?&"uZrLUU"R60ZPIhPH'S@EM VWC((+$=K_`W菺SkL?|䣴 Et. ) Yp%Qkw"JRCsb^VuwP2a^W&lÇ`ho20ilQBHXѶ%!$!Q7ޠ4joދ{4|${DʧJoQYP wo ,W _z8bѣG^YMd FV0`>V٤j1$>`R~}ΫG.ZNtvJ1{5J"dFE=KbNTذ}OD4r n򡉧%DP|1HlIByTD2h[H;6O^Ae*L-_ddnDi, ۔rNbû6.1Dv΋OFBM`?|Lw81YXH,ʅWh A$_V ( @0 ! p:Bv+i1[/W*ZV CB~ ݙKlEdmaEQ=h!t x: CQ (\BȨٴ󠗠}=~e_ޒ8oRN4 D'P`lnʯxQw2 $[Ԩ5γOLo#_n[LMh{CVx?YR5sSy",2R2!]%j.^{c۞.9\je"`#  K;~E!7e'e޶(7Ѓ@F9;&uQW?5!/rO;W(1_TR) Y@Ye Bi)mKn9tC={Շ>TZK8*xIi& bħ od?u(h6Qϰ(Fdwݨۇ$ÎޘDN+32ItH2N  85 6?}ұ  e3cxf#&/4-9 tdPP+̚5cLQTH=~Xe6Lyh 5#3%+u3K0 ԋvsxDc2 U܂CV1F)!c- F.I[X`Wg,$A 8# tt!8[\} U_IҾpwW8BVkdž 8\27 5[~xB%xe;H*5@諲(r񁸍wBFRvlm`;hT4ؼ_B^Ձ{0xH@D~Ёځe>xpg5Lf'C/,31f5P4%$X} _Nrv!aDbe$D\&cZa"((B +&\~#F] kъm0(DU*rµh&LoD[ hN@ SJr@. ! d?*OxMl>E0Ga8T|J`QoKE>+>i]ԠXpH'A0 wfU6%ʾ wx F sf֭j Q2*o8 0 :\ jz]Cv( jWfjAGEF@Zh5M0Μ,^z؝\,댪,(2]8;} 0hnDY$R mSхLDf U90c D 毀7- SB[X3IÆ`(s2Ję$JŸRP׽,Ǚw{֖ͻc! $ūa%E2Ӭ6CtH[1Ƃ0({ <?~6_|p[`P{{a Il0TciT܈p ,Sq10/:VŌ&v-V)(g1B{;FzJx06~!\E)_avZ[iP0 ¤P스chesr)bPA6N(wqAf3כ]}6~ף](1*zeIgWpq4]'~!زě"r/;EJ$J++P%%B MD)ec¶bE5><^ Rm*\[(DF폦@[H áߗJ#yloGfF$_ .ɟ!@  BZ EzN#["ܨ4O=(:Yk+`8<2=ۭoLX AM*C򎈢uS!T8Bk4%ȆA /ڀ# cٕ n[;>LK[{U ]]"YԼMW#_pdFH!B6 ˜fC#m`aC8C8ijȾkU m ƌ?A(FɄP$@Y~/龎Mr|BXf6qrpq6jZ9l$Cj@Ƨqo7սL3!gǦ+Jh P:ߝrf$ńu2UGQF:Cacq1Nzts=mdll(+&`J$12I! @lf4Sb^ -kqٗƪXl.Y8QP ݢLqU_, BCUKȔ6q (Dh*JRTԥ=gOA R g衤"LPbEi8+$W0Za385"el|4OG1*ЂPԢ*Yؚο#XBvC+CX05{>'/#;Tʔ fD+4w]уdG9WboLִq{P);Ku=p8Q듄#w{Sm)Of@;!T'!xz)kdMN$jie>8۱[OT^C鸮=AiaLcKC`n˕FP 8_~M{2Ћ P4|$E;;ڷ$ƒ~Z\MC!28L` . Fr)zl[^N:>I1[9j /nō ߌk:^?AV9k]ߓn\A9ן_`O?^5)x MC6ܿz|]?`~0TM6)M߹ߦQו[[-݆aAxǯ^U`;8#i8%snYFeYR$[f͙2gbelfxFCIMxmQwp-b c?7cl4޳| TOH}gNrH@zlT;g}Rbp%Lߵ{C9DĨ%a(Zmx Fz=`b8`FP EL|`I3G֝|/_n^^X0LOɍTd[MOJyV {szAVzBjhB$)OƀC,No[ؕ'Z\BZ"tQ;Neh'~fk{䍶>Se,4腽'~r}כ OWvBkXa o, Uw]JQ:o[q67,{~7:fldQ:'%Q_­m({]w9&Nlچwv7';9N> [[;b>͋T{sAНL0*ꉘ{4&ufݻמyk݊6=6^.M 6Z3ؘ89rey$ٌifSatHkHMVD[MCKmj]qFɃZ&(Q[?մif犕37l4ݭqMr?qݮ;>kVx쾧ʊ ~b^0Y;}}tkŨL1KOꪸ0q4X P4juI҄|xVx"oo(KǴݶr=_tG8f:!h}Q1*c$_r]װ/Z7:ȁ ;Hzt>plÍaiZ :˷\g_W%o-#V}]2BtOfg~?]1}1jr) 9"!QScw]ւݩzgUN=i.aBovÞ;f wNxĽ8`v]9Wyime7H=|4[-UL~oC&`ov?x%?4&.۽6˿4z7uPsCqs!hnZv];4a_dpAl?r{%B6y-͓,ɥjf-;&w6]gh' 7{>_sr '"`To$㎸M=A*oxfe;)Q 66 &<*.?WB*p6Eyȧ <cXXAj--Zrl:<4etɟ$E8{M'TB&S̈́P xM{5_{J%f{~z j{fU-BDIsM.u~{.97i'~QR ]imjfߚ-ch)' ˧iƶxe%Ҳ~~ zs& k/vZlv[k6'L[&MuqWb` p ˱A,BPE#t@msf|(Yp /^uf{yk[Jڥ+"KN@ΜJuwV]2:p# /-ɤ<ҷeӐ0Rh֜jlC핸f<=g-؞{ҧ/͹T[H`iUI>1bgHH͸ݝ0 G.n0Α0rGx%::5qIcD7o}Ԗg|Rq2帊9^Çk`EcfކCUgR%_+Gna˨H`Bo#4OאQA@,6&>7ȇv;_O]H@E=W?f?^v/9>b]L9i]`v>޵pm7A@ L80+K3Ev;{**螾}^Un~^D(F>R0<+ߏ:-]u4TzPh$X&Qu͉ބx+$2yKǵVM@ E?H57?bs~Vw_$[W2e˓gś4Jqښߵ˵DabKEM}p4YtgxUhzշ>=k#:tKurU5;iv_l7oy |oDI !?~:W#Yc ;'dg/m3ʑEn@}A^^qV ݣ156ll80L4L+*CO9 A5Hd"<  f|m B8˨;{f#,6ipȦ>٫^ |lW{!|UŕRnS."PJe9a'b3NzȻ2?\77+-<'nz]h|ݣ7vZʫS }9fwy,'S.6Y>;֭~ #t;9ɭ˦[|$),gNb+ʂzSK M XzIoЩt djbe#/u?{jrb5* sm8ij{\~pTv]ۥ!d྿9V/<]*/p_=o~w+Y@ 9=3ASх;0Y|Y^׷'}a~K u' ?$oADrc<Ԋ}ye1GX RF$0`~euSuP7]flqMa},Lؽm^# ϝw%t}ng\s|MN'ɺ׆HlL! G1=ݞxmusg% 13WBhxA)l;{Qo`6jy-om"`dXtTgt{} @a8e+Wى(7k?$>i {+ug3~!/{oFvvqQ'˳}*ֻ*?"bgE}~fT9#\=Kp^i)+ŖK}7j)Ca12k>~e4~& Dǐ58 ;iNy!-鰜?nWmّu;-V P]I?9cu_9'Q׿Oj?֣c̮xD\L{G^oЏ |M2W%UQq9Ԣw-9dvLVjC6Fm;$d; G Kz`b4+7U/'̔/oY@tov<{ߊ b@n~W߳oDA}zIw^zP )HPIr0*d.Us}v6& 7=n1žeQ@Cl#\;JD5 " &sO}syƄ_(?hzO,c=f=u 5Bih< td.G_#=6oM&gh,l-Xᢞ~;!r/4124IF 'TEV&! 5~|ɷ3b~@`L!a8 ,*YG?<&#DCҀȚƗ{B{* bYm0$2d9JäoY5V#~{ pP'@ivuM7u=ϝ>M-+t/g\Y[y\<6q۟;0^wluB=3 f= )hU+JgԠzgEoQ}kGtjKЩHJ>ctznr&/|ަ rv5xJ5M^\djx P͆ k[/}L*8x7uػvyZV% aX;f~ 8sob&,璎 A8@!Os_'㮺CHcR/Y=Bvt[~P(ifW<͇I6oW<}i8wM5=M;}?ǟ'Eyfgpݽ/bpBZBe4_Gfh15h (kUKI6*R Ai/<}hYV)xr%ٷpviD@Y>o>VWk;oH?SHOY~߹_xvTbq?m b=gǍ~?L劦y5ݾ(hJk'MۧȂwԕ="#Ҥ94<-Y$VEM~v9cjKH #::xa*܇-n|UK.};Kf52N< tW+0d%[/s$[`*75{-7/ѩ Ηl6TL5qM(,q<*$ny֒ȲޯG<FYP6C.n%$ |RSQg)HrTt=nWGR*>JoO6?{7.CP )2,FT[jptvmb`C W:7ʎԱMKу=(JÎ0re7T G5X$=B#ӵq) @{1n]cd`N!bWmqtgO-tq̓jZxN)~x)ڵn`EŔx(*Fc5Stu ES `҈~Xi\|jP̀x0KЊ? x0P U5R*}WF1|ҍs-![_i{jaxO>, ({+ߎ ybë^zD)_xĆY sg3(9ArИ-ZՈ$oݷJ~}i)RZl:# 3'FO-LP#2W$p4zFlu[OS2 UH3aHд[k6G Q@ ahk]ٲ6\ٜm]9RSP@&C#Z+ UL*9v!_0т~Zr,FFtd%cH/@B"6i)t#ߙC#訳$]ro5lӊW+=5 nZ:2252V^WP7O9^'eVZN[\hg dshOe5ϵil㜝Or*}nB7o'vmiI!F H ޮ7{ouC/_ony_%x_*^Kcޘ%Ym$0ݫ*MrYr iy?kg/,^nE,ف 551it[mLu涍ώl.Lf=Pa*y UD$ lgEWkлJcƬFB9OEl(ⷨ36~Wi=2NxyMg +e+N6?{Bdz'jm:rc%ʋ~jZdƷ:^EΡh2,9ңz1t35ӈ#j͸(`ngf#WY? BVXvS~g|aMIES$|ݫf f҃G!.ĎSܓ_oɘn"ow"|&h@'çN~T$2XԤVKuۘ/]ȧO.yoy <=\V_sʅKt0[joP;pi+Νm}Bb[4(ʹ|کPq|-1[m[LwUt9F\se~飧?y$M_?9,vX^LɎܝi !(V-3T *E?9vVqtV=0Վk7=n_;}=z>6v `p3OpJĐjtJˉ(pUk[jĠ<>Շ;әx绣 C1[+ Rssck>NρS53_%&yY:a~)+ڏKc{OQcr-g gK23BvdZh&Nzaִ~ ,xtgB@{PQ$=}&ᙍ=Lۿmkzn on#A l|7tqm]MȥYīMC[ے"Xn=w?>n܇JBlL3 ^:g%fZ $fOlܚ;8tSmIWcR<PEr .h*/ $:Dzy(rԾYlsqOe+%\?)DV[HJz̧3a7iTLFoVրlK[]_OehKH\R8S*vǠ yQ+LV+oggŁ.uhe1gj`k J|7DMUft*{X CPuU| ,f2H{>|oCO'+ot_H󨑬OT)vI!$Q@!a&n _ wd+<$%<[ʡ!=vwwfWŃMi@uvUϽF#f0lUuջK/VǛFjZ_vwU{?rƿ5зl䬧0('eodke⩈7Ajwصf9K<% (X5t!%:kOo76囪;!FD޿{j7jO( U͉NcK1쓒q,N6m#p}H0ޞݢ70Q( .0WcK0lHxT@ԍ"M?0Y;6)&ISg9N +0 X8v&[@u pNͨ){̙TÍbjcNBZ^\iqDt#1D#uiԴ'5njb1MvL!1m6s=&i/zYհ@h:f3!QQASKZi}6??*F'ǭבP0~QVP^HL;CԆhj5| Q#\[˱'=mHv1t6†e‡Pb*gWu$l#ªR"0NNʚyoZh<.{_}W1Wjnڹރa !) b%uifo<]|o4K^q%X|b'j˫Wf,޶|xcy[,ɹ( ؒ{ ?DԪ8˷8=9C'B){u~! ~j|z;x"HNS'~hgLܒZ퍞~BӗnN-:}-_m0Wyݔ̢7 tJ`xEhool'.l*{*:_xq~s3œ{gM/lL7hJ+}w/Nkf֜so~J$0ovw_g];B4i=nQݞ[C+~G9js;M߼GI߻?W)㦮R {^s2yϯq_9c$৏r6_pFʭ1ש9#\$IxcF_}lZ/m]?sw_L mVp4?"Su}j6W!mZ Yq&I2IiK a/\amN;BWվ,$@ 6X gr` 5LSfVvoϟk4r'e{sgytitf9n8Z5[l<ԕ+&O337{k>qa9}*xCEX*-D 2:\8QLyY7;M^@Ul|Vƕa[И|ew./f?Kzԝ{ ~IL+7tȏ]v SFg[{ȍG(1ye9U{_\ {GN~u?QAO>OrvKE>1!AQ`(#֌7C X@D!2ѾbhiiOԼ/oxg?oTԈfi@V{-}"/B#t!e%,{I!ihKw^\m>wMywRBdϙH_D-je_H~t "M Ov˜l ]AM7V8r—Ghi{[6 75 mG> 礐YF+~ P/1Rs™*~Gu!}R-~26z)wn_{!Ze3ǝqLp5h]8L#zmo\ɽlRymnnyl-˝C cK׬3ꊙ\|8 Gk? ,'b! xӗܡn۝֬Ω,:_6Qf'BXS +vw8? Hk:  l)iHj>P&femٳT[xh[;{< baرR;@YMőQ1;{pMDq6{j߲$ٵkU @J_&{kSEIy*̤`"HL$4HZ50;}D? S~R[Woig׮Q݀MJ,k(5c.Rv,/XFPdZqkܭ熹eOy+\нPB`Ѹz*S, + L=oy&QBj@PD͌ieΛ<˝F"C3[R | 9bXGP,8&ip(Ȝ[?{izr^9%#@b*E sifu ѤgLW+\D=g[|= %4j0K97 Pk{ݧi:gƞy\'ה-]ϷZ1q cAQPwɕ; a@S` cp9bOJL8V'!dld:*:Zųֱ#ĸ$%dIjrj]^9+ T @  ?b4o O*[)w"DoY>H 5E @;~⵳p{X)qʮ G!^]˸R`l]yk[ou?szSo,94:ipyOީ5ZFMGG9&ѱ|ɈWZuK 9fl~`eIkoνd$հ i$U;k ?1&nDx {*jCid!4r^h$/2zCYkD! nlIL5΍F^)Dr-ĥmǯ"ţ}O{l; ` @(^O/y2]< Xh-PӦPSp l—(y@ÃdL͛i  $L( 4Q&--5,ڭ߼65KҾËš(|acw<5SP- #b!}|3^0{bi~_}g':k%t*PQ^; !l b 71rc_=o@B~QZoqaB'SşFΏ${¨PIt1Z^u t..q]75%MD-yk{t֦E8f,`5Yfʔ֍¬W=oC !Ш=נ %iVgdvp߼ſa_JLf+"@.e¿sW6ٙjRLJ?xGs+^j\x?ݴ{xx{ia+3k>! |[Sx6{YV7>,Gu)y1kj,ԄOGi~ܿ/G︡m:v7$l ToU)g._]4^E7J7B :n")ad^W|哗l޹-n(1hF]Fi|}7fdt1j~bBZ`j8"ׁ?<teDŽ$~&jKxH`B˻ NM&gGk`ݟx xe3yB6W⡿/Sy!~qV5K޾W^’,hXruS:&87ޥ (n80暉Is_%Xkop" ,3!G؉x^UZd̔&չ7%bGA Zc*\>!'uWnAĀ'?5!')&0iPCP3%j'cVlpbTh>w>$% >?y]f֘O3p8D{Ӑ-$ʫ, YAR+Q(% 1dqoӭ^a7/1<;tћ5H`tM"Ġz44I+-$:B cͫ<ȶH"OL/jbA+A|LdTI-*8G`ڽO؎K[U֑rHy,%u&o$ĉgmcl3jJݓvRXp6i[(g WbvSIUgSJ["DtW|2Ϗm'T7ֳqן?X+ w>AH< W4ͺ5R/>eAq_te# ̟KkQwAb`OaHH\~[ c[>%߾e P$g2w]fIZيsE'=z}R0.N3w㣱a6!`(ĂAUҋxPV'% iɾi?wjd?H#%[(k_S` ?SΓA.D %ⴌR)P Y$agP%9smU|=XPNB9&ID Rp !hJ=$1@WkT\ 1! 6Pr dukO.=_{a?2{04)/*qPy YQpnT}v{n=p|tU;7Y]_x/HwB/Ș#sZ)yW7^4e-՟ϲwtCYi\0{uF Ph<׾zm]3^Y:7k6z0_ Մ0 %i7S,=E;:[jT ^zN?-m3ɬjE"o_'0Nq{Vc6b ʅӳڰiϱJq.Pa 7@}`IdDեvUyE(*8 +(S%)R @_"ֆ-bD\O"ܲ 0q$%;ES,h1f :}A͸]f\EMʴD!KJ\TkT+d|cl+@!ZRE 𓡫h8N=*燠I1o(΋k1lOJEF: HƯ8[zz}C-#_hQ.kdz#ừnF,c}7{ yȍ1[5?~.=׹Q Q/8+, U2 _r:O(jZTlu:gs!q;2]7=bQkCYSYl4/u$/WFҨ3q5PVE;?QOåᭆTCL]j{ty"K%1. g7WGhˠU4F ]Y]Ͽd*)E]RLsOnu dI G;U 1I[@ @7f r|`Z% x'v՟k-OWT[c9l=̧p/:kzBGe=6Č #qYBKXR.*8c3h| @lkϼ%[w otxaWk[Y;H0l8FU{dl4j[٢`lޯ3Yb BǦ[dxB@vJOMTOJ:nz?,D~wŭѧ_t՛}_Noj=llZ ةcO~9C*m{v_|]6#OR[r-'rWx~1? Ñao:u9݋rYl(ڔOv>-y_Yz."#\F{E_R3ٮ.d f [ުche =X.^tS::{R{$ "J K3]Fo zm{$ޛoo$fWZ`Yℜ|7n]v6:'9i n3|%37[fG_4i0x-]8sJc3o";/fWv]_qpg,ӵ)g+n*l -䵡!/GJ]j7Q`}NNEDDΌ<{[ c:3PV3&-"J@(©V}2R׵3rf9zOTrϿOF.ר{w;,N;hWF1 7G>سz%}hD3gk>-9|A%ZTIf$ u~.'n߫{,{0HތMTLX]7Er'¢jRٰ֙ TRl"hK~ .ZW4Gʧ4t$c#CԪ}*6`m8& 286jÿ2X0أo)|=prʠD$!D`d`"@DPϝ%wI\#27V-bÈJZG2FaQ`Z[pK3loR?]WؠaBHھЏ:"QLS# Bm`# Fd.6jh4ٮ,MKkȔ]{se aOLT3t7#'Vdkj¦=).' I&m>6_丽G$}Cߜ {ONZ0^UM8!gnh꬧*T>?75\tÅXKP&¯e$z5se8OݮWͺ& <* VmЕ* bZ`[ ]ϸk]>HY #1!kJ7Q2M=tv{-GA~+C|_I ^֙;ވM'ҀWإ2t~9˻wjؘ4pL?ݼ."}IÓli2 P[s|Jstf uT!#9NT5RҦAk dZuD}eV3@KdH$Ȑ ;J{Ǫ{l"^+ ܂ʙoh̞dɬJ>y~Y7[Gc8$uܬ -:]XS7KX{ 3mnn1fه.S((Vl;B(bL:t OIR;2+Z?vYN.DpI h%䦓3hC-x#) Pvܒab<s z;W})Gm9+vC`i5V> mj7Qvf~Y NMwq$ P0DazkGkyU{&",sV'ǁ_'ֳ=60qnPw7Zv-0_ɕͼ|P vJ(TQs١̴^yC<2%lċW*_jZ4=;ܾ$H4_o788+$Y?ry2>qNb3> Sl1hpltðDDg6`꬛;nVѢ=Uqu٘7HaΘpW4_lS@(ފg{QKu}҉O z']m12ﱋβky9=J)cb:_~Pku߲cҤOmz,/P|/\{ΝM䚐3BcOw p ,JCLW{>$hK7c [|@㜲7Tg vQܱ 2=5W^DG7T$?j]ޝiY~'anJ7D6iRnсTh|N.w6p+ZKg7US{8q-:}6m>}/hZ?MhH -t|E W/qn?\T>cŧqzbRD_3|>uZmm\Ҳ%NȝO>u BwDe` i4K|`hCAZtq\{ Wg`'Gaeoyɐh mÛc_$U{}ymȨy=8ff+]0:dWg0E$4P`\~jiYEil ̡!$νB H`T]TM5]{)5\Ymzo'w3hɣnKzBqOdvzIikMpۼ~"i+nB`>Qgǹ{/{G*XOUcz7{,[M~UjQ_nxp#ղZѪtnH{pEߺ &U0yyK ZxE({2&5!B) jMv횯W|nKs&zT޶ѪW`K!r*0ephhwҹℽ@b:({g傾 Kppӈ5ZWr6+M3QIBd" &B|!2qeo9-C.+Q'VϧOl^0Ùq %PAIE06]"h5+W2S,VXCm$-|@\Q:ڊ@:36!,-?XNCja8 $$C Є0RW8LkP۰6oIYZ<)Ì`ETʴY}kv,0d:.O\sPp.ēePFuܮ;d`͒PB>\ DHlv;:X\9@Zl  F?  VOefP_eC ZRЙ!.Dc++BƘ(nvDِZpOڠƮE"q9"婳Z[o:]p?4h#Zi*v(&Ǻ "wP@ DqV48D ;d9BR5zUZ#ejH$l0D*`tny{mY@H@% e~"L`Zz/ZfLEQAKTh}I|A1UȒa cVMME#!06FRg9&$&g(^+x53aPpĀ ]wW37h&? Y5)&mE;U{T&W&v_u'/yan@Y٠{=:AFKc0noª!{9cLCsuP؛6Qlj/#s6čC Vuvηު; KG ~Q{Ab=XKlfD($- D憏BXsS=0)X@Ih?G |jYﱿs]~-O6ʰwQǿ&x۝A,z@ّVZD\6gW;8@ :h[gtڭ~~/~-Ou˴KY9/ާpx JAES2.*O7pH7ba6Ӻ&uVx5FS tbTH5sUe V\|lbOz6c/<^fKl`ckYQK۫(?ߌBZ2ѺovZ1ä"VL $%m˥5,e[xLUX63d(re׶-~ <vwȌU-1J8vS93J˜bcnś) 0&ݰĠ|W>7@_hL ܑ[-9<}ex` G!kVck'إ8Ye㙎??o&f߮`0,DێE E[?0od?Feom7? |omꔢ>>x\f)J;OWumJ dLZ3kr7C _XX_:}a{y5v(91?7fo6jⷋ~m.,ul=<3vs"L@"2#g$6\'ɀ?l"iPsr/ơRuF ubD\;>7RisqkhYNȁ"u&ꏅg0T:JX%eXAk|G 3=Cߪ[;,BoB T4%GYGܒ6<"|=w1VpyGֲ'mY{x}zZc|n2tNk@Ty=NY*ב>776#U[>r]~StH7TyYCV۹Wcnzqx$||-Dè>ɰ{: GَN(&(:*dB[Ғ=?iCvO{/꥗j|^gp;Ӂ)Iyt`aVAcWOn;3_˲:KnQi5XpX"l 9]ɉZX3X\ }ОWC~ . ! u31$NΏ=cXm(.KAzv|/ _]8/[oMtU$ X", &``,iЭJx>7󹵥g(3=sSq$0L "iŎ̹?>Pu;*XzC I&PKFKO m0CKߎ|w?)G>S{D5M2>^馩-t=7,Ds !a:`v&v;viMm_}4&J8eÈ!$lpa.ۮYƜ Śh cq-9MmI]ZU,>cLꤚa 0" +kBI3F^IPG3B´ QcG u3H*xzZ#&$e!or^^0}#%hU|1d-$OO9p ёs;  Lź~qm=[Nҟʥ}%xC}!!BRdҜ0,Skd% p+YGHqiNot=sRneяRCڴxûuҷYFl} n WkroN)Q( Bΐ3PREE]TP Z$Ḧ(Jɜ=)B5 (` 4lSUZ`~yzZw{cMһb2Re:;Pi@8E|ǜv OYоʅ~AQG%~(W:,EGKem,xoj#&]~ӏ5t>ёCw?]@|)b -Q"s 5Žਔ^܉7 OS3oQz(z/Ɣbx_*UlC$D 92pH4 rj7,P&_]cbrچJ..Ϋ8!$fI1N[eDv=xQʘ5Y/K`rG2{3}\ 0% }!JeK+n0˚&6Vk8:.3jiG0ĺɕ k}痵cxo\k9 y/ʼ< KRa*gUrXiJ*|<:Ӯ`U.Ie#NOoϼCvCfփnm5]w7)R]oW]>}ך>#73㜴$3n۶4؊7뚒^`,~jgTq$ t}  *zTUJ2"l]fa~_k߲i@-,JncHh&dlvk5_qc{RDTw3Ў1 {/ȨXh?W3LB21M}~sߦɧ_IpM 2pڎ<[Rާؔiȷ9K"5w4ru2`]AMIolie26ۚoώ7em|:nqGLyK%M+iJ^Wm,cd/"M3ـ WBbIɆ+86]/k` D4+cìpo<(2*ek[3kj:oU~sxݔépz,Y/]+,|A,"[G"  *Hu] s\ҦiYLХI]pPg!teQ:O|r4m}l/k7lSxW+.ȁ,tmO8E-ś( ykMEK8PfˍMv $BC4*AI){l{|ϧiqheViB*?0HJ妡hQoɩw;YZ &TLLbjt xX"6_f73 XV3KT‘h QPJ@Ja9kmw :Ph=GbR.8Z=@E10&! ܘ9VhV[Z}xD [>(F  l ' 1mRDJi:}hYꅎtZf!1p .wY"N$"7⣟i97Ԡ[l< \0 ;Q MjDd&= 'Iau/d8"ŽA#/z50ipXz}Nӵ1-@J?:H xn/* ﱻLWXFT!x% ^bʺ?,C{'}ƣ%OQ~sLﯯi}N:&a͋[w{;=F\}˦ŭc LLF[ηlywnxm9F>+TkWCpYQL2yT n,ge&/#{n?۲);3~r~lᭁCE/ pLPS ${ ?Sˏ#YvPDĿ.۵a`2*7/uį#ȋҤoӂAC8 ^(,(>e}|(pcO+|zߚݬgM:!J oαmdSYPmϋeONH.&r`hS:&|>t?G}v}z{*;޼{וgn $[,# /lg,O NsLSbfx#bete,&035 Jךe GgN6V[ VYMU~ϷDj.DV/Gr4ؙOr# e@ҊЀh *U13w.0ό懜k}ՒtRWdEna4<$9AqO߶fujஇ?oZ]$MT!%rH!U?%AK/-MbDIL3.bx!CW*[s%y}>ixW`)/AalPt;)5L*;ah2;zt K׌\=2{a:ɠvb׶8=W̌Tl è[Eos'n6B5ol+vױ}⣑khLV2`οj%t .[Vj%t.H߱ʰQ;r)L-6鉻t1rVVgf\#.&Qq PgҌPh_1!V[C4N_D6݂z׌JP1 AhHLHc ;)cIe8]Lm3E)$rslK9՟Y`}PX%VWx糘@j$3|[Bꃯ}Ū,jwǻT4PzV89}c_y;GgQ|"Pe&; Qd4 l (o=%=_TK2pTtʰ◢Ϸv9掯!cRqDU JyC8(!V_ \ ))pNՌ}lk;3T0H8_ ~ =ZeP1A,SsB}4k:-bb%Ua(:ٚƩ]ةVKJYȋ/Et5&VX n*p;EgX0H5{1 'qG_##_tWc͖3^2nCqJ/N.㾻@JZ}bf3sIZw ])Jx.IבQr8J5{/V9gl{wH (˚bLeilX\E5 :]ܮh"•[Ef%*},;"q{Vn|%J7Vа RlQFʈTnTP!h%haT7'SZfun.Ko?[4tVn!T Ԡ#Q< nܵebƢ]|xg5%ȬY><grH.QfЦϱgxޯͅU©`Zݙ<+bGpӒf*w Jȓ~:WjX+ooks:ckmtu\.F)9.MUʟ6?{+/O[<(K!a { P``PT {C{_ݽ鬙0vwD5b~| 2PZd+Ha HJ0 %ꩳnq}[Rʾaw˷]9K_P*D2-D&i++y$<)l6 e ĩ 5[մ`N!JK˗z?ץl@舗0\$’U0:6dMhDݺ<ԯ'SLsCcmӾN~8w. Y A˱o_RgTCGcgצN>=Db sIN{զ1J5= ^)]/9 ~L'&7VQ Wkϧb Y5x#!xu ~Y3w_~0W&7H_by-8 Yq1O ]=<E9qUI'D&oaP$]uZݓ;nf2D kD U̚Kq[|c+Eh/~1e)O_^0GRJ=tž+`6 Ř%/gwN?Ff!,`:e ՙNO[nHK+ I,(OMjzj]'FEr[,BIȈ{r9૱=<ѝ}3?qF`}( q0Utܯ8;|ۿ⽹O{Ĵ >J~wBcXМAjV B+AБ^(OHBEKrs{ $026gE鄢yO /mZ|zXGoIG)f F}sȦ tX"Ѐsy9):ڶ -@Z55E[35$*+IyȆU(TpTǁYEPF^/^| 3(xi_cfDo)rJ WdVaUk0A cցGSoT24" k^`!A@͜!ų,;ǩN?q^iF= fH8xwΥZX'WC!CԨ#@, 4R4cMc\t)G gTa5UG[˺#y1N(~S@f`Դ qcTzq\qG".#~yJI8?xø~n٬zX5MRwܭYa-. q}g9'0.lXvӫHHMЊp2z C#9&*|rH &O(oҩeZk}Ld1A)Qqm+/D]z_\]~F0,kzGGE| od㽉o_J*C\!Ѭ]g+3v3Υ!t2LnsϾ_vbض)un4S0h1:91$ }w^Ul*JR!BģHRi99Zlʟ;ֱhY^cPǝOԈ g/cƄ4.zJ:$w,I3 ӁNp[~Gκ WSl}7dWENȘ7,*]Ȕf-޼^bTܞKK&KI.6_]+GKN~GDYZvس /,}ɏ퀔5&a2j]or|rC?OG&^j ! 3*.f2 Z@zN]}&(kc_xpB™}.Y){b@Opd$4ThV'e_lgǸtR@s4_-rcwU_mKrC< !8w<,( cKU{sfF+|]`kK1Wpw ajA"@ ${UnlMM2?r]{aIy_+kZ-vFc5~"\(8r:rrtEl󢑒50Z"| MTSfynnR.gSs# 7h19ѹde{7.W!:Qqr]8:,N19d0]K8!scov^ A@(S9a~]93:9Xɸj PS&%rwCVhXa)hZ7V M^k{G@`W;OCTh9dA5|Fh]QT0JKuBD`UCλKFfg(,CHJeu"߇95yyM+N@$YU{B&{/jy;i߻=t|0-cnHqGE޶=~OlϴcQ娶SC1|bE)+pSx=n!23 8LJcل}E&l&e"CW:tY Q4dfE0IQ㓉(8WD'Q$B"7aujQ/ ª8cr?y  (9:_82IHAyjOJSnUq[,禭PО"iV ͭ!@fͳPc:=gVbznڷ;z5n*=T[B{#gZBd) XiBf0!%̈́#" .)WWJCP7gG'Y }}v<񴡏?wfՋ(b™TfyyhpK08p !BԀoP0 Iҁ=qF`9W0PmRӡ:ϱ{qm vv pR/xc!b=P%5qv=ޯpXC{p˜2Ns#Q Vp "̷6>P׸77 (jl8HDYVݾ6W7J` 0G+&Զ_ +D۬sO+v4f^O>b 8V FxqyrTn; > ?xSk~mĮgU`TO GL Jk 7aks#iRa  aYn9aF!ȬaFbUDDߒ!O8T!;ZD6Ys6VۚqދOFb ~ D\-S"iAت+LΎQLM˳Cy7& \ w JDxcL2"|WZMPN6 ~ʅ`W'h̵ElNݚQ_hf6S{1EJӿ}o+~> 2W_ fo9|C쀵Cq[*1]„&,gKrbohтo^٪ݏ! mnMi6m798v̗[]UO|.5/8Z3SJWcyI{48:Sa+F ke:%]v5dd5Ʌ睆1`kDi\8˪ڊM72?]N]f$qq((K6鞀BRca`k7TQaQa϶Tm;;/E98 EKԷ=hȁ$v0NI_\hjnW\+R9!ۙ?9`na Kq?E# 1eeDwL8u:[T޿1p`"/Ce)5gPn[^$j\3~C"O3"0&8^7{&6 F[nzؿH&XKk؊PSҭ)I2U Pȟ.`Ǥa@-,JgA HՔ`V$0ipֽdWsH3sXpKb(Q'6hS[@U@XS(aJdlBXڰ1ǵ]#H'h9O par6io 0V8s-mJCXƄ5Q pqU7LV SXp!`aF`^05as#kij;<)UC"I,X'-4nW{QD`AB&j9JsYVq8&c%3:T:03`&,A#_ TPΊe|ΥIM=XA夅7NR*x#R&O nzQ͊k&i),6Ƌ-"E !*(H| V'_ yCjS hA@%(s]VaaBDXR%a3Ѓd7,'b!N@'@@@CG*0(u, 2P%(tu8 @-4il̊K 9S0X]3κS"17hh.4%g̙c_vj u{ 8u'RR2UsDw" Ѱ zMuC~(◺-MN}vVԜ2 ;Y {%^'0:$jC Q(g%g0:7O \|t˥`U@ɯۻ{{Ag3Z8#{oHbKrK>Ç%zbEHZ imm7lF UrrZ %L@v"~9TiA0ZRJk *G Haāssڮzb80)i(Ur&RQThD(SKKLTG+Lɲ/P᜴ۣv5wYP_kwHrM\N?Vzw) Lm;[fTߝ=޿MBk,L|NN&JIq)̤a{l_Nh3F҃ci+K#zOݹF}{s{cf8sJu\>/Yj}QA-79D^xٷ^&]cܟ|IF?4p:MB)wQPPF;)8O1oJB< ͣܰr>M6-zHL[/ Z't^M_S6gMJ-s6C*k|aʱ㾶K֩+ThYʖlq(g<Ѵk]p.5h_4uמYXp: $M< ]ՙ|ܙ٪K" ^QA;G3Fs3t_i8h™?y#asкQ/sP1sҝmton)3ʖU>'lP=pA{x.\cک⾓8)fqu *]g½}=,Sm%TK#}d4ٻ\. `ӧWo]]q $TH\.tֻ,RhB8 &q q=>gGBlK/kh:z´Sagc]lDS\Ksa![׋r-,C1Y$Y$4kb%8 uK* :<@iHy~$d~+ T4TNRFeV0߻/HiI!i*AyI!I=o7F$zsQ*9{e%i*hπ4;&8O=|`챗>݊j`S!+~ЩQ HUb^5$z:s=qʄg_w7.nX>k^Ɵ1n`7*IJ)0[.)|hX8!!3CωMPsK .:o 50za{WS q%YTl-lBrt4(;X&P)ŅYlV "Hmt d1^mu yf S j[wM  JvŏyAPvfdAF#Ė;pU$_VLc#@o@`̬N tМc5솱j+D6ycMRiȚֽَzg m!-[Z&&9UvBa2)x-`Q co`L€YmT=8iaF,(P%+E]e"j(E*_835ə9#,&PEZcS* ~@ XgѦ@NX9{&[ |%/ؕ$㴑h&^%1T;N,1U#ĺ9UiŒ'2nq+acxHD{XlWC129Z-6\ a%Ay(O@˟xzjXϽ9v7ĉ&x3`lS3-V/ĂQCHHdJ8(IvC2&.[q9Ev.PwjOǁzxds]Pybյ0*k,-_>ʱ:\ r|BH220MZxQPUF[ݐ3;겝U,f>Gج``J)98>ژ4xano !_z⹲)=^XXxǾC[\1olB! ,)"GyBȘlhO_@ cyRTx8De"8( %@ ϲ P:n9R2>?"QMsyKB_|%S r{KK#↲T"p7DjUV}.b7yOEuVz_O6hsŠ`ikZ4RNֻ0[>`֥$p>m7_˹П2hwb X:K=uQ096uD\%EChPާró{I>wsǎdzw?wJ @ZQaB@ѼT @O] LNY|~3m)sY)[t=l{;&9ŌG/1bI[U?RZ1q.ML?0|(?1 sV /MgM=}Pl) T1ɡ8vUZ2ݧmsSH 3lDjv}io^25L_>PmTr*:FGfp%<"vr> }g'QtjZ\L>9'IC~*Z _7cͣ@ A?uO+^OV#YV헉g+*%M`x$؜_Þ E@P3_%+6V.'>Uz;P$dvsч~_Wju;t z;r 3('&`8/.]5#s;p_OyރeB%U[ 奻M R?l\Et1vMZv}Nu=0 +m2=h m$0ݠKhn&"\׌C0kG0`_s{-$:[Ux*sfH :kTJAԀVlGD] (?ZU1 ؘg|9nߩ[¦H JJn` Jpq%r\}T+LS݄.MPuVO(oJBӬ^TgECS`B,wZ\)[xϵX&)q(uyuIGFlTռ`ECbZ P|׎ľp3^}}]^Qsp/ P#]rWvⰂM!ގ|Gop]2 041ϝ*Z:V 'p`Vಚ| eD)ܣi-M).hJ؆3͞g#Rݒ)VjeR_x%g5^8,͜r֬u4X3\ɨ.U&+[**DT@43\_ nZE5"bU"#f`=E]qSx,O[_Y >P.;Н0:4nD Y 5tr s w?p\ Z?{H0`j؝o-xH9騽 X9R(Rl6ݕI/9)n$OV ,yԴ~U6Np7+e^۫,rh…RAʠMx}߸xo8pavF9&vsjEcVb*q}VH*$:8@Ȃw݁|̈ l4YZsʧixZ1GΰCnKcV; &.& MJN@>50Ew'+,kv%@'H ۘ$ *q7ʽ9GL% Ϩ}Yn .M;&lC9!fYz E`ĀP Yl!sY͘yeVO HF-flO,[#~C |gvZiـA+ES?$nl('\iXn=$By1].NXχGz(nA#=D vZɣ<]V. %W}   b#.n~ OX[#m,Dl ~ 73DLZ(,A( % Zޟw1Ն8x(VOe)Q=ʨW60XpඔyxS~K uwIC6kN@IwsT_;wnGک7;އF #=38d0>כչue^a߶gU[^婨MH(LEɤ{9uw"At>χySz7I!+i!Vf-ϓ2ݦ k LB#=[/홊>>l_Y(a< ݆Sac"0uw1p %dAXc"w:j&4& y݊GjJג5i,%ΖTyc {E|"_M!@8Țam7 C4Y#)T:&TJ`*)({5Bkctg~A &2eU)* ,-:BqRLz-E &b0 6{r\vnTȘܘ% (9 8EKXݞs+YmZneHg "\P҃Rw :MR``aXK=|;CN?V'pֆY2@Wm G2=;.*f-/O|[7 {ãPEmDp$6 !a9JO@k b0Q3eQ>h.K&& &*_1&; $\;I#<xLw7k>\ T ftǧ>u?7.ؖ{c^l9چ/6qd!wME($^#%|C T 鲑Nѡb2-2m:C7Է Lzɫ}@yݠ spX81kEYWB&D"UиP:!녅0- ;iGVa%  2 Q%Q'?-*LbR0'"bML30\^B@ekԵ ~BkP|P Sd׾C&6A @,b Wy8C˅I t"1$y "4HÅwmbca]'7@=0>">1VW` d覤eHR@sGƻO}+w#| Yz]6L~ù) 0 ^^uW.>xSҮ2k?{cc l_U"v1nx#<ЦsCur ]oRm~nslr 2zb t"@g@4S.֛/V8*/-ęƽ-H2 * D  Sniav=ߖjN (&UƀuܑJ7$c7Nl44`shvt\6a?'ѥ1+0u4/glYvWTaneʩOeΪ{w I ƀ6Du8yu}/E)H!;H.ըS&ZϷY_ spFF-Y%.[M0M&_톻0@ZTV(Ӫ<*"9($ aH  #,|eV{=[뱑F/Ǩ[nKz#H4nL%a (B!MTB<ŞZWNݕ=y>=0qȲlc]~偪ɫ1Aۚ(ٖ#4gnvd&0]/D΂?ثye!+%cTdcZWc-fϳd? 5Go z,h9 +%ma+sUn+tt9G:pZuhE mX_Z䑋p 7B(Jwr̞FɣDLB3So9vSڥ"`qBNfX7`‹̔Ֆw^A!}cw-F^N D6ǁx1ᗴ\83[Uh^TO*a_X(k(uV ,D X$ zԼ0T@5jMة0e=Ht6`5'9.K"ަc KK'uvZ),cP, `H7{ffAuaa$fY=>.eg8cbwi/NVTe\u|C&IJ` Ei@Lue" P@XQR?Y+LS< HH A)D6E {.,ޮ|' p*QբF* MbR_'7,k{ @ _Y%/XPdΙMY;+͟@fKkΕE[}޺_}z`@մ{PqnG6WF J,@SS/`L&EiA̩UP  3MQi2QY!Uxo-13*_'*H%^gOJNPLrؔSv%0:hS#$cQIRM7;˔C=R"}GVA%``hꆞ?+??vZВ׿~O9?rUNy,qz۴܄Pf dT/ OIm@K݅yn#?mE4EDq55@r 6&jJbܣ`20sscYam,kHDcr#t9m%T!؜ 55 UT({w~~([@Hb$}3Y)[ iBIPDS 6ZPW W@&\ݹFz0Ǥ˪ek-4~~ge2tr} *0#6@@ 9 M%Y8'e{muugDˆ!s'CB`I%cu``5P3!C,Nq *,Ug>Ox0&',҆YܥPy픍> E_RZf+?m;<2a-/_֊f[nǡ9Xk=Mq;QnۆlTSx~P6T28ݤٸA (ywJot"+QZ-JHsi749&X%d7_DPA B+b9C_2Ŀ*&S1J~Jy9=&xzܫΪ$.֪Q&F}15RBqMO]2Nh9K?$20O$d HSwhjasxO#G!@pN|a"eMYq#!gYh"#\RZ'h`_6] 6Y`E9m]^ѩ(dgJ 댯閭 > rT&8EXe~~yĞ'SNNk^:z *-Z9Q;p&%I֦arqcИ%W$D|ޚ#^8Il.I/ l8:Lǒ0$.| }ڗft9k&sjp̒1jP7]qN|ILѵJ983aƢ@eZ\Ȍ :ZeoZLGj0Hh!Q6bǎUuv@ + "I ,_ `fgv6L8 !HbiL׵Hi0e}*@gj.#ї_ hR'ߤ.1sB AaЂSh bc@Zy%M2I{,RFXxQ<}ݗ(T´}_ ^ՅnH@^*pqJs8 QJ >wk%>7^O3FC_'YJc+1ʎlTɅ =Oĺd G:MNj|j8=Ekktx? BA !++Օa"^Փ?GK, nJyO㕘X-qM€1⎴ K8eĮzv4"f?E8ךMLC38U1@}CP@ 2ü*XM? t[ $vJ`Btت; S<$Aœ_ Θ%g:B&hJWqC~i*!+q2AXT 0 4+:MS: W凇HJJv z.sPp"l&5y KT/m˷ii@NήP} kn{'RKxb2RŧQFugWBmib=jDi0XSȓX)B"5P, 0_cΊej}154?URK|=xD#ӿ~_P4iH9KA"!w8  QxrelS$&_ugc s} dvH^   3=US~yoS!<ފ'Rؕ n"C5}MW֭cmvЩ?}[٥=ٯk#NELh5(4{w÷sK;8\cmƙL'pyjeF\2K]Z@$P"GdS*T{}h+xY"M[]Vq59[Jup'ýc՛f{ \qb>ƒ0H@ RBdK8r4"3+zwG?WӤ4fM]:zmѴky~>8W3e ]fmfE4^O!;`~ZC Ef74G Nj$:9u<ڱho|(0ܙ7)0G{~Eآ R =kanex[9cCׄ C@4: *OkLz(ågBuSU5f_L2W B/VoMo?8 .ޒETۯȫ%{dfQ <KUoev>ȐahBu HP!=QcOؖݾ!;w]i,e_;~}9ov;ehxvV&|A//ݶݑlb_wM8⃅#Oa\y}}l`j)ݽH4EYIral$FzIO/v 0ڇuяn{|&c|T<0;]ās[e׽bÑbM6}ajt;R_71,M.umhg@AfaH\T$/@dSd%ٮ;λ8# ɟEfArT!1q+j.vؓ?ѥ*+5E} $&ʾbI?{^CFDU7vOMygN6:PwNZ6L1;Nb[CEJ % S -lY0Q'\08fI[[f[p-&iHb63D!f)$DTB; x'-hkj(͗R&@/ a]r/ ~Jm}o4JJx[+uktn u殍bXo4w xLt6N N L>SS6B '}%ϣYVhRJa!n?1Iö&UliG;n -9/}5@ص4'B@W%zC-OP(h0jo{!2H5wU"p!`G';IfᖕI'f4xgxZɲUrFmfM:B`d4:RysۨC'v3hhݘ?]Y٬g@>|1 "I.)]#C0,Wp 6-*|ӗIHU@y9 ~d1<2Tb'<=7_9e][E@Wr6RbTЧz#ԶnGo"cmn›JnB8}?٦  ߛwX4~}n8S6e0%=w~nKy=sN@ΡNH3l1/~pڛɍj(:=ΙWkPcN/֥H#Ш.[>-n dI[T:~úk~K~JsG8 >d ޏ1Cq!@N3@j s60\0AK(!V2 !lԐ|;7Cӿk>v_)%!r,zTKIDA]Dr͊䭐hpD>bH\| ë :"(Zh֫[}hB`:@C ;6*(({%^X0%Q%a@5,(M50@ & r)fU0F@H@G`D([HсB\+469Tr"E0@PD v-Txg.XbT):wKv8ZҳĜFE Т݀fɫ?OJd3'[6"JchXS2e x`q܄3N>Tbؼ.~J1 $$J Udն0l#93wm8LfvO4#;w)9b햬@ZOwqWgT=q9v:ޅjMl8U`.#BٌX0Y:K $,F3bܹiZS-A"X[Ӣ.ɞ>q"P@zK&jrVk騚v@`{7*ZfnhF! ;q4j"bzZk*=F'B(P *_wp,Pb䜴N XA]tHK P`Ĝ ɻ..t6vH&t`O| pЭ1%ݔ'@jj7% 2 llDPkGo6m0BF@a>صߥ৶Yo@==gkǽ/Lm:!5Lj<[b &8 S. fU&MQRdP,ed.}`'q#3*LyPKhc^cvF$a/^&{BgˑW:42"ӊ޲) eGj;ZeF8 ) B*[ \FO"CԠO5`NKA7N. v#IEjFؖg e!6[(t:Im䙓&rVT!K'2Nz`^M-a [ Q֠h" R!fT=(#AAm}Pp,)|:1"߾l(({d*GV&cy'D$Rxĺqs',ww}UcO I]:x{(np`F?`GqWU D'#Wzjͤd-7ћ֯g (z0/=?t2FAͣvUכ94y&%Le؉.7 lEȊA*hIʕ:h3=0Rgi%̰P&>OF)@eOzFN8.hrx Cu 8d<,yp^¦< ~Bog2w~JP6yH# `\J&Kd;1!x2\ʟ}4]I^NhCeo>}'ͼ2 젺QԒ%Bm3ܗ-|[EFG@0ֽem͹rRg[1퉑aD-{UP @ʐ8'H}ʍ6ԝLFVA=ߗQ?ȝ8O1:DV8ZTPDn6KF?&==ٻ`n۪+i"aABgM!SVYP=y|+kJyfh^Z<:N?a}ğ"gcI,SݟwqbEܴڌh~傋qv1Kj#vlMKOmx894׀ͧ+e (a!SJ+ΥqX16(j=HpUZm#aYa~-E, L<ԙ0װGRv sRS1j\0]:l~wu7YĹǫ"hC7@ N Ă|A0"(/b6ИK cagCx'bR -鈴6dnųw0kvI$X `4D1ՕE͈BWȱz";Hl) V׉l+Α5;u>pKI@f,Lb":'MoV= Yj,1!9rZq!HbjyYKOp5NݒA:W\8 L`@q)Гd?U~Frn_JrMҐ8r/ 4c[PC E, knф  i/ء c28-ѵ4x~ϴb20#q{:->Q р]‰:,hU'\iE#^ [>`at AX@ʒɂ N ʐCNR#- ݯ+Øܒ! 9&p@a˲Y!A@Ui]羑@jmf_T,e#i4c{ -'T'&7QjG!t-sͳG^[;rB/ ow,kxIKTw3ѧ/{ ݏts~{֕'kM҉ncɏv6DQ lU|9K W'(NL¡:~R`W;2G3 S_W%<4lvt O|~P |U.?&F#q P vH 6-\:S1#5l5M'| NEmDu `%P  &LGSeYd?`^067spkvJwË˟gܘ׬;LrKl[9rϺY7VuMsW\}-SB`k ԎaͩU`ŸZ}>;jjjj "T5(c0~S|w(R\&JĐDȁq%~"7c;'KEX"$wʫYb^{Cӛl?~åg^oiΝb?`$GѪg턐[bjlKsdkLw\}h^CfQ^Ҋg?6) _̼꾸]'ޣygWKGmF@|mة+o^Ҷa g* 4a@Op6UCEʆ BeF @ Da*$yEp=Ȼ\/_|xKߺ/WyJ`N~ M" )ƚkH6 @S{oKBu44#`tU"'R2Wno.Y6x)D2ltdBXc:$P I dNe{ @`$b,LDgQi!Cd:Ii5sM"[R[p -/uuaqB-܊gN:kMzE4AV I0R3hH4x <蕼Ifd GnQ2f  5npҹ~*s&'EuhG)h8$$GIczdB T @`+8U38ib?B)rWpH(w]̗<\ D] ^߻z!4f5snwnPp^f|Wkm[2߫o?ā4 X' Z U"R:ƪ HiptG;VOu{nzTWpKbZu6<'4J|6nMWd*63vWŭos~Wzt5{mL4:+ XJ(le+N7Ѽ[,Փjt_])2vvSؓ}swZiϿ~42,0 7 0 "B S u DI`54"JpSFQʲGҙW__Bw6^^J1=3obuC6㜩BNHԸ75{&l 7"]r,bFx7+c˗tb hTNl6uP(,~u ̧ݎwN 3yllI䞮zޱrUZab7\9"MaT[X\`s5g:>O⚁azdˣ/&o*Δ9݁0~d!þA}+|I38 C!F P> PXxgc3g)* N}Ee^4.dU!%OK7$" 9oI1uRd873wʁRv$3%.XFnĂqE G:f-l%@@ZmT,ʁM8E yvRe@jaǠWz1@x \b\z, w|>*X@B5 bkd++* xFD)ܿ QF{Y&L]0O  CL<;ވ3ho` ů" K`ԀILf1$UnOzd祎 Cd^:YIX&Z\xW,9uSR91#;@Z@&`"a69x_2tAģ72,/8\oyRzI#ub_-/O<,91q I %bRA`cq P%``csCǮbHKiH“19:H}a&OLjS~M\u}Z@F>ى pY͢{pD;&6U j BQ4? V Ea;io9AptG1.` WFaX,J+=!)da7!U''ǿ Pɀ6 %k=ʆ K7~ N8J)*"ʽu)E8ϋ:G6XY׫Tߧ|֨r_#9*;ăuk?÷cL9IZABAvJZV€EĔ p߮..>Fl.oe͚ !Y#T|Zsnx&3B܋ AX!M-&O`40PG9oS\xeQyo@@-!/ ( tյH/5%~u;A8#Q#AKpM@˜ԜMcܗ7j7p[6zHX2*v6lJ[TgŎ񆳤 BM˯7\9uMɿ)nXM3Po!@t!$CUԽT&Y V/tm@%+6$ ƘCԌ'3G_nu)9~{ 2v?ν#\6OJW9M]֩7gлԍy ÏT;ЯdcҔڴqw›VO7(nǯv:U&^9N7~"Ș02OFg'=Q'@p>Y(_D[Dڶ{r$ cbԍUȜq%m qLY>mќn{h{PL$#N+c-ŧ6b&C\39̓ !ތj+z͋7'i28#'@뫧ŸBhpd@S<}b9=bH8$QQNz/VG5.wZt3ٓۺޒzH[VYS[>-TmonCC[CSXЮv/+$`UTES[mbg]eWMm{D6o]nO:؀%IOfj6`w"/p$$fKL&Ews(Qu6Tj]an/4Tu%yO;DDPGT(Bɐ۲qW/ 5bLngȳ:?߿W[`0C|F;;#2ȜpR` qqBynZʘ ǖ\=+zCtan"K7̩-vAU 1=B >]h c(Nw$2,̬1}+vma"*N׺viŊI(&aE$Dnʝ[H+] [^O߫Iy:.TsJ'IDcjz:m@0l daJNQ@+Hz#\Ϲ90@)@Hg -@l1ȖEx`]Ns+t mmjX#M"FoU6*6e+ 5f< AHayky7!&oIz hĢ,=k!iIJ5 OBצv*NQi+P/?V'#U4ZJ%D 0j)hxBj ra.^ *aLvd`d홢"#$%[R ܌(~ 1R DaD)Mp['Z̰ñʰA$7}c8¥~`pVKo_g5Kzq ɂ3cFIȇqԎ2F/%dnT6Z.I$ϴ Peפpf:/dpn>7xʿ^*g- 8 Oj>j%rx%VK>ʷT`֤Bx 2K&$:E"!'SG yBG|UYS~ A =̘@"/ $5HJ؎#G&x0!إ耰Gu$ bvٚh+^ P X2tHS)Zt13 dܫם&nΊbhgB$e#njq6;--,AYK*~E>>WYW;By:lqK[_Rh[[44_ȸ춘>Hg[J^)5ԟB"'V+[EM4eMFygnN:\0!3F35jn0}2J2FBL7#Kj#<"56[6Hb|LXda#述IҴU)QŀN@pOmq]OP[hNxOnKZ..K.KIz 06P9>퍈L~|=S~jTXgoPgmŭ<"N[oUTt봎ɇoȥB ,{z*@Yi䠵Bbn Q@*&7K7PK@tĭe"#g-ճ(@ "*LJ*:vYxT;֑#iYgy . nL&Cޡ9-@{L!C4(陬B:S 15z|{6E\3s${dw,^iT( ٭x+ۧ 9Ï.(8$# K xQ8u O+Y}iK[=5R0pCf1x2@fLy/I~[=J:"Y*ja@2Razn[n.!)kY?)jೖ-ܑg*B m&Qyژ=al 0(A[J #Pݳl"(4))L4{#IZnMID}w:8̈́{5,l%a ByC @Gm6Z0-zO;M4 3gg@ 4!8I&1%` ȧSȍ a" 3 QG{ƸH  ᕛykٺPأLfu"(DJ>wygFo?`܇8$Y$ҷVUQ=ue$hwCěvךOU8&9ӷ i?u/$90 AxKs#1@ QmW_|I}پo5R`\ U2~#cK2@T%Cv7[GA4揙bATq5"Irw;) -Pwmx-L@/N}ouizZ#,R~! Wx<|;13dӴ۷msx@3୴v䌵ifY3yKc.-LˠC8)RPv b%k PW@U^?)z2ʰj#G1;K)N"~X,d\j!~XD%u:HMEQQBȠYFkZXa+~^^V!̬LJg}, $ AC?A#Pd9(qbbmrfR㡥.6qK'Ɩ䊮̩)4eD I'fe=##4Zt*l`2oB&jEΘ1A`I3"}#ZHj$a )Ed8䜂VVe d-Yp9](E#{c$F)wܣc2W̚x.R\rIN2Ks,An6߈)^)g{Z)!+޾)dT8.~@='B!q91K+@M*Dr G@(UdS>[ϰu!~A3c30C;?=ί*%n0p 23M vc]82.xx ᑀc[sMMs ~]WYsѓ0 wIUDK_@ |( ԂoR WGA/r꧐fǙp$fYMXMuzg&՗bn%h  /U{-w]l! 0@$Id~G^9xJp qGD0`Ü#a8`y"ߛבvJl8 lA wsIֶ]mfVF Ԛg|߀%fH:|?j_iƄu%bg~VEPX#Csg3G~ڐ%9[-h4*ۗ=$Eec "V Etvz64e߿<%8[*uZ <7SCΎf&D2R<|a 7I}x1T[05v"0L<th"kXS2ܝ-cן^_cf虢 _ KBfxfԪɱ:&sY0\c r (t@"]VxKSG\EIA(XN͐bA)iG$IBB,e&,O0&W׼lNM4ƯY[Mu$82N]Z7ś<א$}HW*J8_idz5d0ǬX]715 2'jڸj3%FGەN(l"!6`eU^l\2RtAz8V |k ǿ#Y6])U]|liDm$^:K&K񵯯{6*ji Ld8%mʷ牫*jr'痿Pۺ:@u0sOwE!p-_g9m5"al=ӕS0*VʂmyY$DZǣ$M+5gX H[$j0'0a҄uQ pG\*l[K lZdL w6 (&$\M %>RI[ ;f~/}LL&P|GU\;HsZw¡'UoԶdczq9i]=T69Z99yUMKQIC}$ҫ0v\7d6*-ލ0-FPH< 2 QX*6+:ťL6U. A@O' G~G-[ޔ]t,c L ̢{ VŇ>SCA BMoqǿGQs9AYy(:(5^!(Cus} 2d|!1<]_.uC\ID*TP CSxIT;P^Cl[xrL[bzbKA?Ł#U:IߌQ'bY꼊=Ð}@ѽԻ5 ,~Qu>}FyKu7;&s :QXІ9aˬY+I%-݉ K\`9 5QUEuk>R2zL=Oh@n#`%{^`"PiB5*` m4b%`ШxCKqf"[qdmJ͒Y^H.<~ SS9B9*_'.@buVOeƘMc@n[n,cwi[v`#j#QL67ڮh !LKYIݫp] vNe̯F@&p!q&R˃.Q>fFcΩjBK$?Zo<!"CRsH$`BĒ 9 )XmIV$:aB@,XgeY0@EngsA;aNa|-J ֙w9ªCU5եix AIU&q= HܩQ:1Nj쎯dpZX05ٓe&X>$47 Oe%5@0]*xđ$ȢOZ5m?ovQB>۫/@Wj0dfsNy7F5)'ݿy{ hU'jPOHNg_OŷZ"k6T7OXɹ,%i ÂK]'1hT|ŝeθQwn)uJ溭(EaڑIG^+rSFZWke}GC2O=Sqd+.!f{@rVǶ8@# (hpH9$xdpdړ=pI[Y0XPCKb{Y&CPqVrY ex U+0'(ʥh|V]8ТûP::0(s%&[G4b7|ּ66{ @UImBͯЛ)%uO7c%I" vF "wT$aqmqa,2]/ȯҊ[m͝/}{HcKqBGYg?OxG$[6!8+dc_%1q q?r,!߄_{MoԽYzv}>^AgؗqL&+bkPB@>m"lTb *pL:` '_ixu? H$=֙ ^r4GF9-6W%Up)h{[??1JJݢ]ţzȎB[mL,X/4Mrp#d@ BʚMz-}mc4DA!^3۶*WލO|Sb8dYTg<ʘhT(-UI߼n-6GUYp$˓kY5$6^2)b?ԒM]n@ib6o8h4IW"}>>gՊ7&oLĆӅ A/z5 wZ^[O`R-Mzr~UG$K%d"#,9dANLęS۾j&{8;eVh{)+QT[fH]DUo;p:PvImjyyo%Y7\A copGTH*q7o;AUPP.\#BaCNxӉ4E8:AO1  ,T-X*ۛ !r Ba01JU؋BMk7LUQPݸ @&eex E0G4f>" GaHfP]w]9NJ|d6߽g_|B?r|b xگ 7D;<-G#>5^yjGV:6=)fXm&0EIbbGҐRw=~H^#@#Cs4!r@Cq$V2S A;iDҝ3PArv/bLQk85i; ̔D[Hn&yBC!´Bep]IA\1myY!fڳA@fQW಑<-`u6KNvr 1#Q PxyGҪuW)/= ڹ^TTnBd~ZaAN3=1X}jP?VX Abv)jp'1&E%#so/i]4ڝ 6! 0 .cDc}3UPvȒ}]m/UI+Xc=l+3PqjΩm?fNMJ| EM9z<8<&R9ކUQ[Oɠ!krcsXq\3h6D)Xav[y dJ2BEAj$VlCHdGsLJB@EQ;rZi@֔X2Q8FIdfЄ|͛1TlC8摥͞r .1:`htBAݷ7 9C %&Vnxjj pZx&s|B(bBthH?`_`O+/;=w q6 e Ӈ7\=ӓ -&3IQ5+7L9N$,uz8* 9}s[[ԗ<ߣÊiRB@@I1/\/d45ZvCzΒNs3a~I5ǎT6- V"2*_σH\i̜ݩsDˑh}Ң-Ǘm ظj*ƈ(؆q$\G @w1 pHw pcd DB g!5d;1݂Xb&XϿN0x\Z2>!Q  x;Hs$:AeH2Jڰ_sC5snoOP.OSW'8F{k Q| !+'=:XE eht ,CDAab, ]f7g0Aͣڥ=vHz$ `P56RHps3̈́/?:b7W)g1E!}\륁E "v,0?K[}˹ZcFb(#L < < N Lo="ylfwrB'EgkYC+x$ݦ^qD^X<|iATq>tmYwgP1!CK!e*0S\qA&'!5b1En,D0g9I42[#o5Psܚ/%kj}H0A҄k&0! spQ o@%+A,YRfV1 _)(}44cR" 9[@e^wDi(#VN{MN4N.3KМk52.Bg4N'XY~/5BcÁXsaaSp+Ds[hX5B6M^Tb3Gj}_e :ufAdi+ :0ɐd~r9Dpٖ#= qL"c3 ;^Tk!ȃ((+dS$*?8PAQA!SW QQ<{ٺo& YlEYu輍٘X=J99=r bnC-bPA'@pkg-uJjZVFBP"j; cAYwș+Uvt3n{p "hW񰜰d R[`,P[`ЍIQ95_ p̙ݫ(+P\HIN+0.Tޡ'ENP ,J5@x )‚:)$`pF]0aƽ4_ "HJ!#[ # j(<(ϖ#$,6DqVndl($z͈GNu!Qw m)vPP(B##57pk΋{ĥc@D%`p_<7?uxEu+,d BٿDkza@qN⾢'O=z).&dͻfn|Qoc C$E$'/ n,NmO&P ?':7F8ݡx˚qۘzQբ^j-⃏ MzR1Gcy7ABYX"5W3&m-ݼk=;;ۡg\_yʄW^U-zq./Xٿ5B;!$^yd}H{0D\3N#Q Dӊ0.1!М%7sz#EA>’ !xp3L<ȅn<{w$@j-Z- zM^Y %%S+aJ \=@IiI- ߑg dG'΃c[Xp# ~࣯nr?;B.P8h_BV[&~qϵ|6Bq 䱭뇎`ɹ!pKJy='H0^1U&s{@ ΁KS_& Bh}nhyJW;8{gQvDv~WMwTG)e4_Ì5'ݼ jj`]EkӚw֓NpIY ~ipy>|7W r!)Q} Mj`慄yVnCibt6Qlk!JIR< JBqLJ1- 쭍 gg{C@$g( ed)yk0j,&_i&bro( 1;MB\[υ9lgpM1 ,dh#Aek*S3w,*ljn"eb::t1xjj!`udgYN3&|[vS`Rŀ"`.cRϩDÝ4d#@P&zɸBG$jPCBˆaӟy5>uNtk8YA h&ޝ5Xl7,}_(ɟ%wR1dNPOHs9Lƾ574!nWFW]A [6̳'lY=das񥎾s%)O`9KiEDX`<:a`ײ,ԈCog{懲bCӌ8}}ó: Dx_B! O 90`DݲֈV2CP&"I-NFՇ(4>KD2M2tvehW7Wj=Û hY)Ƙ'oPv{LT^ ɠL)wqWg- [-2 a-T 1*tIh&!ϟ`o 4 C@9*JH=m0p8G\"cΌħ#eL3BJ.˵Cŝ[9ŞW*BωU$Ү:VaQ$ 1ܿTK|llv0 PseH TBe磭wc=__Y=~'>)͵V#&h@y] km o˸V_S_]'Ͻ% _hןҋP ` "hFح=!̪tC V%i`Y#fC"ljW>!.Y M")V.$`aOĮ2ta{»/}~{_18 92dtD놲&AS;_ 2]5(381}3"SGLB QզL$Hu@L;XE3: biV("$VZuYz n"CRiNhAT{'eTTqP 1t&@qnX,"N Hˈ̮.f" ?N.uU%[4v@9 x$h9wv%t`l⤹MV3FN;p'@=1-x)ꄂt}?G._`S8itt*(QqPI7:\PM=k5Ѵ"HD00Ӏ @QN_fm,ޛ21 _ %B5+i,W%?n4}|@]0 ?MxIBP-T%-1AsK 9NhY&_^׷mww%.() NS-]щ"9-oX7Sd"֟6wk*u|0!TYIO*db1i|j y{?$ }&վMv`nW,0AF NH3TU?.0Pc9Q 3Bt- 30Ds&0TH ,}HÞ2:EB SXv3,yˁA$r4P?@¸CZq!+7\$n$vy+VXEr֑( =fܐaAz 4^F8GF"<@ ew|7~BId:Kݸ A\3& U ,:ˁ;LtkA9SÜw1ɔT =+t^ IFb0L׭wYHf!Fu&4V Zc&1FXA(8'L7û9OTG UQ XS[~@S\\m\Æ#\aPbԫy*&G3k̩)\PqKgb⛁@-c E0 xCLLM?mIB)Eh9`iL_np"edYrrE,r&'^tGɲD t2jO3Q 8h^3Xake(;C@tJ˜h\ 03YF|3c@DYNT@uZယUTIM6b$An qɪXy)߻A$D`(LK_b֎G}#n7A\uT:iLz'MmΪ^:E͑yƤ0 YMzF͙!Ǿcq]b-E:t?wUx/ 5WGx?Nw;KOhuJ M @5аRS$\$a0pbৱ$ y&< IkV A @33O5yI\Hvu0K ^z&IVf1pdFL$KAӢ@fP( _c 4( 9J7ھI-n\3r¼΍Atij` cٳeYݩ %qِ6o [""FXQv 8 @|0$r"&JX 0;Ӂ1 a\#>Y!GX$6H/Y0+tST7t- B-t~Nd[c'OWc;#lLh|) k#DP vo՚/}}\|K bP\~[(J膛hOB2U};,D'+@& *>P3^+i p b[cC0 (5 * :NSaR@qi7ΜWie_NR3%Խ 7T&why-7>R=:b@~U~\sg`c%а5#=^9I8en&"ȤC+6}bcp+nWBV8JeQ&ݥ4Gw d5kjIзSŚ)YWk1@iN5-9C(<3oL֬u(b7]9&6g~5zCʀӹ2\RfdkC.yyVR9oqYPym,-y쳻Q_JƸꚊf:>f+]o-(;aN! 箛!1Z%m&JJ (4D ?01LoӘAWH &0TvQV9,j" * @ c@kP8HL5p"8V$i R7lk/qPX\dTp ^" Z'2&.cR rGG u\d[1\Xԣd x,y Xҹuf7z(q" =U9, ft>H(q{5ؗxQBS΁ PTy=©"L ֙bPԀ6@! F!@?0` jY*~FE:uJ5go] 0B  pW{o7zûuv(8҈/E<$r[DG'?A?9j3W*Jx-h_8DV2E1, fD߂[E*iS%,4^lBE8e$W0PvJ%.$2`A)k`b0Ԟ~1ziy+t^ŌxQ97= } N`9AU#NI^J2 E+B'@9aBL#>ھ04%@t#s`1 !t`=!4Wp"Yٰjx^qTPF9'kȥQTZ MG%.@@%1Į @14!BPc "!tz_S2)>&:AV54z_ߍ,;\jВC -2LD䠤B:.YXfWv0 hT7:[&vEp{ 通GΪ$al؈jH (R)B9}2MӈYW\qxxF8sOtrzh+kD,|ْ3i}9X )Z k]4 AR)f:v7; LK 2zMKA[FH-B~/:BCQilzؿroz_-%DDN$IJ-Q@ _Ar2  $"B&ΡсiHtTxxR`^+]k3](s6! 2y)[ :[@O|Ouqe!g Rҡ1 !ʔEE)]9iM/KsW@^a:~QM? }1*Z5Ȋ K1aJ +T` (%Q@U@Y8v ++>'vӪ0;.@ϒ#MQ(ԁD-& sKDGdfM$˨U` ;`dIG!HpaDAرo HA>}qYtS29 MILMd͍(D]@-~_J{epܵWݟW_ 5. ԍl;)$`+Pk8uụBX5+p|WX@+Y[y FP@8-ͽ>4+v-1 9SB1DҐFpaL-G*v*曢fb%^/:3&DV;wکS<Õ ȸ=c rv~ǰ)z"+Sr6>Oޡi;Yzzo*bhBhCP#Qi߂\H$ےT00ͱzg 5yblיۈU4@ @fpNt± yܘKd”!5|3TQO6| /Rs6 4 NHAs+s !{wL'$^呎-/{XݺO$}'u֊Sh"dE@*)ׁ(f3V>pe@fȟq= J\H3݉$1 pxFő"11;Tw3=V<+Jc:,,$~5 29T|{@D*x8^ "HBߩIZ0uk@/&l/%Lժ:*F `b.YAAAz`J3G:}gSmx- q'3/c!n I9`-I`UjdGR`0heBj:рI'De95J;̱ TD]D_PO@ԅ /zf |QWR0,^ӵ Dֵkk%P@"JeZ zf IKA@G=.LY  %iCW&(` B U+o0䊖k C:7~z0eF ! FDA.LW@>+!ME&L~0bb0D0 DWVJn* @nfaf.vţn=Ev{ٓg8c)ۚb Bi+gkԀp@.9hr7$LR.6W/pҬ%u yidvpNηeb`Cq!9r `vZV( :_zA;jA8': Bxn6c/1Q_TyUg @6s^x>o?(l!:OoiKc1QcCڂ8 ;=c1 E\} <=[O,ABh{N|W_"\nYЧ|`-/H31!NաPmZK-#VsßE1KU@+QbmoÁj["QS..Fm({ d]lckc:N(C@ DV|I[$0L0>Ks# Yh$ K8NSwh 9?n6?04 Ӭ@ɜ T]Cl:5hVܺHWL0 9 B` \{Fl b`LXAd¢9Zs'ކ @G`HQ]n@] F%T\e :T5N _![ ,tJp.qn¬ZP P f/&Jm ,SvM6nk_z` k=: *z@ry{c޸q 3b;}=qs_qtA 1*( GQ@?` P*2 {%f. ~L팕wLȘMin.G,'bezmtA v"]Dd1EUc&/l-.87vFT& VijJmAl!պPWLj8f t x³A+7SjyP{Yc!?i(<,Cwg˴NEܘ\|T']"%I1U )(t6?dHL~"֢wģ=>dNOL0!BMɡ0a12쑇M!5ZpQ(H#$/pKP Y*DOz6`PA!-p،Wu4EF+57DɜBWN0#Иdpsw$rV4je͆hk%W"pK #`SL*.K^28CA?Yd(l֜no[V ; \pJ\jH%T?0DyD,%I (7-QV({z8Dr#q,+EP \$<[ XLHkswO!CR&#c@"h|92O2/ iD 5V ~$nIXKSϿϧ֫O;}*#y~2]|zd^I*TRe- wWJ5JsHB^+0 = AUR @yb1Ra0 f/GtTC MÔA^^$5쳏9vUPIW^Z, 0[[f>A!]T5I0uP!C3AURLQo(xs5+ۑ0B&2ݞ2GO,8$NvLAZiA aA! ,7#0|!aHtԜ?^Ć۝M\֤!C-Z2-!R(bD"G2.z]$g>!XpE9S搜'8evEEDE(FM!DRZeȁ25Kk_х? Hi /s Ee55x %f;d1OQ4| :7FpF폮j Jк̒1@Ou-~S+'|VЬc9( ('09{ WKźu"A'Hf[r - NJl*"[K_ْ&Z\"s\\j bv mz4=~4"$ݐ]|f|Z&NO츃HN^sLv]jyJZxr*jVOCL;Oez #G[-- h[zLcnީ;` F$:d #D$P@ ZFI{j I qdL\F %IUS"+CK2nZ&ŻQU 긋T ypS 0&ޖ06f3SO Apl5Z6`B6pN @R s&0 !J[aZR*rRG|܄ؑGvA((qy=amw^͒9)O /bKBJ [k66iZp.Va%L yrA KASH:hqb  cجh9y-nxzp_ؚo7Uk17PIֈjř'?w9_lw4>!3v0bF,]QCJZ8ZI<М`94 ܨ@@:|b Y`( F#oaӗ-| W|r-Xt L:3sQSX>6i8$~aLe-:3AQ[ Ll,4U%~ղdjAs@q$tB2e]<2" a6!@@< W*wj+4:3;4=0iI+BY45<;tl( SF|NX9?p `=m\i)1-W(߽55Qh /q=I4Rs!i#MALno ~@q}xhrZN_D^C D@HKO K0k^zxv,7p-ϕhiA=Lp]{;,9^IGC}JН?U@uRH' ̡#1-K}-OK7`9\a>9/]PCEӆ' .N#4˥S#9p"DI K{<~:T ǁ&Ko⹡V7:3jKq_6vJ7yvf}WʪJuh<ջiYŘe%cXl(ĘX#OM%n^+WJ2]b\)$+0;}}?x6R$nX%lɺe8!dس|thV yr|AܖӢ>?~aف1w>QL7X- BhheZ&]FK4,4x٢d2$o\[Ï$8 ۯpJ;l`{ԷH*LOSHB֛A&!u }`LǨAy{U첬+j;ȝF/;x H%-e啚+(X mh\0L7` [̈́$ء{]xҽH~g=ɿCQ!X/Yʪں ]a9S-!R]qL"= HɕPn8)oޥA69gTۊ 2WNB6B̫x-d/ko/__c%%(0+0PRR?]Z4 #chcw cd$4"J96@rBجʰܹ E_4gI֪mu;F\-: pT|=S2zps&󿃳Xy,X6%Tc33 8Mޟ,Q +L@A+ifق7 ,sMٮ\?|J?0u:?pyh 9}s/~Au|nrC=:6&L #Vhf7U !2lh|Oy'OBi!0PQ͔ ‘f ?*'QިTCI:, $]b ~E_>"F n@@\?RP$Ob*p+n~&l# 0_B&G1 t, 0>Z^3T#kKtD)EBL^Lzk'~)<~yg\k3$H 켶[JFP^*c/`S_i}‹.w1-L'*KaZӂRxg(;Ӌ$itN0Nk D:=׼E(@"k1RwUH kB tPY˄q|"pY?:%淦|#%c#T 2L,bGըQֻ1ي8|b݋Z?Ӣ~Ľ'N~0!3IH(KBQ%+ˠEU=j@Y@/Kܽ҈jCڰfR{lZX+c"3P1Y:OK^LA_YÎEJGr9Sm׌L98I[\wYH1J agg(Hiəo΅8[iFyCKON@D_6[k-W5.6:U.Yo3=|Ѵrw$wAeD Ѫ)}X@yl%\k&a%:X3 oC*sicyރvJa L۲-6ܓʾ0J<|Cz,9Q2&Q Q[-°9)}3E#%: -P25L1a! 4C@S0 XKpvsQ[gQBJZ U##/^ ,TǏ;UƏ؋,)_A gJ/>rp'N4$-itq{.#^iE0+d̞ Fo kY$XS 5pK}R {N8:4NVaQ(A(0/& %K S`Խ99xz4Aa7sDH"8%$ F<[hHhB22;z8En.B)y1oy_ ξiJ 2J 1а9rhG$mnRy{ .1v}A}6o)0]1}oXUkWdz|FE]((d׊cM Io"ɒ$Cy#p\7F"))H6m^/G' 57iNPu#F v{INL_5lDlNgb›x>uv T…Om~9P`.|FX_> 7;TxFʳ٩YN{5 cW}3W= %ڨkctQ"$0߂.Й{e>C=658;-tr}t%‹FeK$ZafZ Kx-P;q;2{wZ(8J Ώ5YY ޳ﱕĨ12Iq)7]⌍ ʠ!-% Έd)^f1Y<'=Ȁ!~ڙ坌ZœDP jD6;8(A2e_fQShϥIH)-[$i@5b?ݵΝ:O** B_?;ɴ\;< 5 nY"E)*ʭSwh;~>=*v)-z&;JmGvJcvi9m_!n*,,LU ^H!igy!H#\A^ Bb r|"pI/ JHŀ[v;d<X^|-JnG25I/>HmjȖSoZX.J 7{ڄ'K zւCş/9fpqa?I`ǧC`aH-1DJodXHqX݂ 2h[ԅX=NVΩ2CL"qV(!>'@ cG\`l~\{;R7l\XTj8aUV+~;}rrF93uV7zEf ^$*O=L6BBX޸jmj"qlf'VD-.BF&Ji=L&޺9_z(1R% *Sys% P-ɓǝg(_ %S s, EfɐtSu<%8 [0\ v.Cz zp\T$S0YhቯxWGn@ꅡ qK28%^;̗ 쑁-` c?OT/|ݵ 8}Cg睅xwrk#7aTp͢Tz-ɔEў0_Mo")l 0ޞw?BOQƯY4m'"e%oIADEifΖ,(PaY4  э @ a`&zÙ \YzaV6fz%p=e%YVAlBHKh 4ckY4 h7v)ԑXv#Q "$ P0a  !Xj&@֖>kĭF7Y bXɹD%7Y2yyGaVt$jj1uGsmk' C[X6x&5D){:HQ[Ă K1(Jy}{"z O*jz|X֎).~"QT@S>W N*z;c_}aC9C\d:5`F,A>L%* 2]e^d! 33Gܴ$f4CVo©d l坩[d6g`7u̒ %Q)Qe ll-\tbbu2Hg#{Ym=YnANu\lfra>Ll *Z>Zsh3Ƙ֝F+zUն7G-RLaq9o! 'Hػ_!׿B 2V,w>&7- dD*vF`w&Et$-޼k|dE(31}Do%n@5?WEPWxA-ch1l ůl7*Mocl&l~6N$b̍0a[4Fͥ!wAagI"}F)ada׏5-Md  v1P/-kRhz@!, 8P/U`K"XVz R[xu\m!fm'qh5;:YV j|-[XlԈ5*,rBĻ~bYTViXO,=o+P3ԿLHZHƙ Ma7\cioXo5 xMz8֨zp:5t@ l7_5#+Ua>柙#GDNYf%NYnA.Kp5`aYƄu|M-z^ iC L": pT0,'g [ OE1V.rY IB$/,;[ (BBw2B,Y0Bz}&h JXlAlt*4Xsp_ +n/Vto&@R?LC'Ѡ, PjHmK΃W)oKdwJts͚M'twQa(:5jn]vwz}No ТMXPK p^@}it VU9; C:3S4"jW9>Lk q 8ɤnSEshfcι,=:"-.#V">%uGš+4^er"[^c=I GRUVe"\z {Yl^Xҫ2 *Ŧ8f=jPKNp֫d:CF[f5g7akf~kgw~)BbkDGeg!@8sƅPxeB4w3)|@R"I^>Y>YRNeX5u@`F@Af0!,hڊ伂@$elrX j{ ΋?&RVM>#v2frB'H?Ipk' Um!dYZ912q*u0I}`ڵ@8Зnb?f<>H7 ŋTV /i5?"f8n@ͻh/#5EhgFz:B@<<Ӝ,R ,Ba$' X={td5jf1]0uJJMMh*9sӔIFo}mmsxeV5)dXfnTQ9F/Յ`G+PHKO/7C"\Y% 5>i@A=: 1=|q)P']1CPHHT>@TysA^oE:fF-EmF\FoA Hu䙪^+U9/?G%dcؕ/a 3f“H:nfz"fHGdJhJDc ʚ.{~j!8v- !BSrg^ h{!4^7m' #>lmu.Q-,5nɎ)aŒߩnrkveߘЅbRZD;V2ԓ\bɨ (rf.NE?6?T|3n2nrluD[9~(f!/*tqE.4ZKeʈ`S!#3a8?{b:}n1-YyEQh+:  T\[޻<Ɩ6ٺ~l;b\d75u^rf_TLtp)Rne+3`N388i:n$8̱r$EqBEx)kzӬ|Pz_Vv(yE\Rډ S-M',J @@Ӂa (T<`݁7hQ!lY8 Pm4@➦1ۗ f /C^)}w{wP3$Z4Q,A.n,Q$bٱ-@Ď,VC")sV{ B">{T]5axe("ʠpDtuDkx߳e" Stc3X B2̥TgQ~ig7/ ?@ä|g|ѱψ6rab˕a|z9{4c{7˔$"ppnPWk lp 5N ! \3_\5ӰiG]zG[GviI؞4."?;LCf:|L$2O?% ._jЪNv]IZ= PIVq#+9c_K?&޺1mGO7!DA%:"OkKUa5dqB(p, E۠"D% t- 08hƥ<OTC2k,"` ~tV}_*|g_d?+s؏ɷq쓡ᲞSoyx~;}ds}땭'p"k0D;r妢Ak%3"MfiF?|РMt|*MdZ\/'n,(Œ0&/𺏅!2 hJ_>'<>pN?(e.F=+F$n .L0P {o=/oYC?]{nDVm&$mRDӰrq~}E wUg0D7.NM}gByOƺ\ᾫ/36vjڃ2'㮢%~<!L ;=&2W6\eMkvZ`f-E!\!>ڡ*"@= `$b +YCYH]D蹔; Rb0.:`[3` ߉փoߨC[< C7$T/SֶOJ-kqmu󧆒܍#hܺivZ;5_k-3q:w]#xniZ H?:6Mo"hѢmJlĘp)ިV(@'|VÄ?+x5Qށ,Smm^&'7{Pmpɓ7 ^@& x<}V2^*ss1T+͡Cvc~wIT< A 힌l=Yv|_CH∗#(XLtm_2{݈wA*×{t+ӊ!7Fҡw6H&w 8Nۑ 8+ YYXUnR ,fn=l_&ΥF$Cx `QfގDTBK?+ݔ)\fqXO/<~i! HrʗC*n=JIi&/v=b.]AZf} QtxXlu,$×ß ;Z8`bP `n"@#^0,RC!3RxQPKԋX'M\d:]! GpM:Q,h(! +w4~ 2d!sx4T 7 *OuAF@'a!A8$l&L  ,AB2DA)Xy/DKM9~ijWډҫe]aV@]zƈ>DZd8j&"g<>ߒ##:"rs@u;;P0VVem" FB8,^ lx@^@ay\$|`- A*@ |hFO?bS}1Rh< )*o-U yJ h>z\gobH|{zKbVvedn"&:Ev_D 0!W&+p I.nχyeyh-Su2U i\>Zy*M ߏps?E$c`o>=J?>fܥ?7nw*&xoX@ &Ų-Hy\5޶Аn>[1?}s6G7n{.ƇiùObv]wGS5jU7vރu@3w~􄆤H6ũJ q߹,Ѻ()WsM{"ݢ\8r!C{L"P2pHrvg}˴ ݺw]^yO@\nft x[If]gt1kN FTƝej쑢lu.?\}_GQtJ^i[ZK}C׵>Qɤ5 6dR{'V1ov d#A":ZZ[ynJd=m `y2V*/#hk9ݕknoˋq/OtȰ[f{gU˿,F7Gu'' OHQ T.zXP](R-hwį`%C ,|Iq$<W)}|;曮!m^.pI3L֓#IhA-Cv҉8+&atس1o?Yj[ CVQ\^vmoa-;Ny^K.~rZt{PJy&n 1CwY]b $+¡+谴V *y+=PW\`ԍ,V5dDE(;G%`t-K7i=M*+GO*M%fʳ%';>棛,m6&ls3azqR`D ͙Xv)nCz4G{`myնl[; y_zJ5N.q9DWyPcدeox_PoCs, ulZՅ0cJ8xkPzsE ,6"7*[.oxs竮qlu߬}"IwjeAC]6AX+!'6MW4:}Ck yҷ\0O\ȼX0v?ttw/ԑ朆ޝdK!jDEbȕf$&<$i[|狟_w'̅q<"/E#9җ{9Kkʡ1USa;.<{;ERP<ƼI?9unȐ`d@V2(~kV& 8lyy< F_ [\R?GfKsX􄁹ߟn>e7A>y-m\]H 1vFj 4~ ay rf 'NSnyz>]q`/]+rNE .ZB/Q~ky(8/ DrhV:d8@Xr$7|#4U1@=rOtߴ!J@^L{'=g^דݔBgKchXzxgFٰm GLʋ;l \0IZd^'0{um$_N6^̐0E8tݿ㳵=[OJk_2kiwh7cy< f'-O׀>2~c$#]ag KP)QKNBLНec9gRe* )_DE܎^_Էe|#B^坒~2̲u>Ewu.<_qܻ)޽ aY 8}Szmlq pG׶vE[Y=36޲czo {k} ^sԙ1ehϐd{$W0Xo:mgkXb.)@UbU zfeiZ488%N*ϸW{m+܆;wuk5}gǡK:;d D-_ǐ)7͘/ Zq_=Frڼ/6AkP :\tx}ɕ7_;_~[S OXƊQP#1Kƒbe?K8+#븰Y鋏N:rӂxnSC JH 8i ixi JJIN,ڝ&fYuF/5.՘\nxusz66y$lg#C&v_]lۺW'v̳3|OvwtqȓpV|uSAdx#(,67w@*H,iHXaG9dZ @dj,WWGM/}?Wg0Gf9ۚ>P^Lh5NiФg=9pǞO'Y#neX.g,am9w}i9Fc59efL~(R%qpqx_zj| w.eE#k_EJO@(4[ν]?>9?/9 };;Ǝ or:kkqv}C7y{o22ZQ{.>vۼٹSf@`iR} =>^;~J FKo=m6e}\ⳟ/*|su ;.JTUD9ED B e J8A87*D9%N-AU!Z'ci0CcBpii1K K H{ ٽK"NG7`Mn!XKz)E9ЁL^uÆ >*ir~tzϼVwy 4 `svCpMKD\.:PGo u李}`bhxۈ Q@R4T!Ѷc+e7)gT ^ UpjK9+QRBNuixNIv(EOJhQ`IQ7["J$iHS6j)gx\‡0.>wh @u>zz3ك]\醃Cy/XĵuM0xmiȄi6)o|9PI(7Hx]6sѷo{%,=e>.(y]0znZU-#2XogǏ bt`V&`duwVSe6Ho>/Ew /]Cr&{ + &D?N_{Wl}zs4B1Ɦ6Q I SkĒXBCہLJ{IՅVT _?joY < EF3@B` >jm55=9მ}?RϪ7$ʛ$y2f{Z+bTw61oեQ4,,dDq̥Gx`VHXhY\NӰ0%#<`#{a :hT;V!ÈA#1E[*'~s9N:ثjc3{.7YC/!xDžۢ F X@mf`wS1]\ͬk~+(~:|N TT1akv/LWNvL'8awnV?9$54/~[ݗf[>JN%;(0t]q|=w#kn.0q_/~_/XHHZ$K"u,Ou.Nrw,|'xσiO|tσeGFEg)DxNei\$g]y1$ `!(PW,R``}0nUWGNT~7LOe_6았w߫qBDz3j4 ی絰Uѱs)fY zc'}r#kEMu+~:*jZ*,mCcYFJ7r+?q_ϛZ ɷVչa\mq\<~'=|>mSN&nܴ:M*ɺ:ފrDRnR$"4;a@A׺d 8DJHf:$ǟ'Ma/y%` @Clv*)5ztZfcɓlusxEEp˗phFͽ"Qm.֌~4+%;Xy9J:`[ZbX)OOg4K P~wѫ@B2kI McS`#!TK$SX6c=-R[O]Wy`1!d9Xcb.O3~Z^3=*b%z=Pݽ k)enn>),Kr@Vg1 Z϶@Us eRB#k͹.0gQXmt4qwZ~Ž5H!,~:;-P|(hz؛<ɽC^׿*Abؽ;=kVrs|^^D ܏VpQny.7sj'MFt-MW7\ʶ \:K@fLU`fIld='|8ۖϢ_ŝ,ItNKNgf8=|kOm੊SaW܈ @KОO0(wbl2xF c[tf \٧;@J4ESM4 zٌP>p5Kܡmӭ[ծzty/fz7 eiӹm ["9-I筲]%ƪmU1-jbH&2ŒtĦXsX!7:=HI&j|CG,Hy1m1U &C̽mGԖYD<ܓjoogzrW&7a>Ա;c=w,_ Y7ݐj*XB,MkQk%tZ0Ho ]zbNݐK۷Rވne}(%Go^ڡo\3Y,9.P1]5zVmY9n^GUVi1}:E#;}¬=0,^&gZH:c^85j][xK"i&k DCOys'<P,mkۧǸw}}g̃~w&@k.Wuaj{?ojb0 iKi4#m2?zWj GAb}㍜ ZAz>^Ɨwk^Ln{){}c2'߽v;M@rg]. Nu'3ˤ{<MinSMLbVoiڊ >m$pimy}-Ww. W?8tp{ư'Q^mR``+h3% U}ŮGw=㜬%GdGB!9(җubwƦq۲+Α+3kߎ5Ԟk Y- 1nsQxڰ!:^:Z7۷Z^׃T[O:$o;۫=~͞ KPno~fXYC,Xbz9͏h1ٳM+v܃XgM:H}, Yк;׍~33C)Ӣ-16>٣f[[OOTkGu[`X)NuCH^6lQZ Km~wƺk֚,nU4? Wq;>qwl*PTVLŵԲ47ۺ#[]"WEF88'  S `W[?{S1w[q!$X+@͏S-$"fb@5nͷwzyn/=sbn A\ryCh$O8:~S_T/wԊ5큓SE%( 5I2Edb Lt{iB Bԅ.L6A{@G;¸pw߮t:\O 'z @xjgZG0xY T4a&GgM"=n^)7( i.'*x+L(6N^Y}dvyZVgA8*uB*RˡI63>Nlfv[ &d ph{!Lj$PNUcۜGDjȢҼ`dLI< B2 T^SE) Q.A0 r0" ҪUnii@'说TE(ZfkðM닫2u}VTLnWͶ-ޥ<4^0 LFp[@`VJAK)0[ n\xfEܱieު6ѫ?:5B2aDiu)Thnl0f֛̀[) HyU}R4򏐹# $ѬE=g!qWo0=96(NX-9mZw) MHLQ$'fЍ@p(pVT1˳JýtУ?;}`~)G[W;#cD-CsFg+e:y9 !X H[}U6dq螻yIJbJ^2( ZiR470a2YKp{[͕1VdvbuLؼLG=)j~lg/#-Nwޢ]_'2g(e0[Wīm/}.XV>uW!!Ǡ]DAp6X*jcCdtwF=sI)hTk"]!)T*z;]KXη{/ANd.꣧at>o]|ź2Z߷no}=: WuT5V+=LTĒd$goMwcG"QHE (t<Ibiٷ:; &PM+wTٵ{srC'ԁawpM+IC%3U&wPeϫDjki;!5 |zΦ ksuAznT3ˣJ9{f _yQ2l +O7Bi{ZjM SM G16u=[k-0; ɬ>eݺjU暺^U f#δ$Ͼ_psUם.zIJ%_ƛƖ7Žm9):KxBa}J&jbwحK/g1K}aE H<I1f"l(XLo/ܦ 97nX6Ax:4< =-iVffuJ/mΝ'|;@ML P}ϴ j]N:lW-īl|~XG{{h/W|/]`[_jk2>+@ C:NEAhHV,l`R a-֏w 0{ D nƁ[@ um.v-cB*8n;_^w2FaLGtyh(9AE0bGF-H%8rH 1WՂzS\ZR}P| J %bQ*veeD8MWpxO`"e&3J$4u"p L0zթ ]"bX'EסMGՏr&uG~omGrOZ#!pd hT' S6Ǵ٠a{ :s' eS,UX&f)Ⱦq1 Aޙi7딪,-ĩ'o+vx{|DgL!vMx&TLwG3'@b^>ÐS-1pKA35#ӝP-v7LNb⫢9Fg@w!}|XY{XӁ ~=>Ltl4^@Cp)J4RV0A-lꒆ 'B1¬ܴ \b=i6X$ JpLʂ.C!oḰ /AGnòMԕVq_MuْwBPUǀ >a 0=<ӨSАu_%>ucg'Ӣs`2s5QphLˆ7=֏5[U (o{c!,;8c1@@YA6@EZjuHWn!U3ɰR{òKFH1B~H>^2,:XcadѼ'pñL<$B[&htrkcg/vkqn~D:BBc,ڏ&! aRXT%:F,)a9&cºρ۠0Xq 氆mϟgMטVe{Vժd\:ry9\hVLr rk\i#QMІ˽b{ ry  7>gs/:Ү1i[_Y|V-Gx:MVmb &# 922~ *!(hݲ"cW<E_Gw\|pEEv0"U] 6@*)n.X-O&nv廊Un^fAPO:t.} -/mx`Hl:Pݘc)IOc;\YMsrNaW׬֯{k_|%F}Ǜ]:K“~0Zj{ e8\,cs2/<8F?,֬uXoP)PrfA~b!bT c2X`w$ rzz$'%)"=ѬWϾ 6*$b%!V?yztud.LǠE L8uG˾>eJ :0P΅E"բgu]}K*I, s $e)9/֏43}L^P'@!v‰cT|%lFANhg%DgGGtCO;yzJU",ðѷis׽!+k׫%ufRZ%-VT  ZD摱ҭb!Sq&P7+YhU@$%eQ#1wEҗu^ndJZu%8<9צ~8ɣ i/{C?7|8˂ƁABGd9RP ՄR|1,.끍i[hJuo%-oR lu.h;C2_˗,3W߿_"~=r_Z)u{Z*z=r<Epe%{&7]=R(Jmu{NnB+AGJX"zV82399J, W@A;R$L"C,rbf,%#1P&]i*wXz **"TAe#DVgfuQ9T̜5جSyzl6>B5 X{(Y0Eu$7.Ձ~=eU(u[Ek.5 s(|4ĢJ?Ќ(#d=4c2|ըn;V~EE,Q}hs03q:H4";dfBE 㾱όAf[[SA 'sʠLf`MpЅ[TH)ĥ @R*Uϓ$I1kœ)~vl 8{91 0&H*f#KjUQK*Fph3*2*aB'2כ5]&ZEvzL2Dl&Zyjg<]b=YWe.hӡ hhOɬqBQŕ2.8 eـk7JlcMdA2+k``̒|4b6WȾ'!Jf;R_,õDzA~Oy.aJ{͙f6|ҩtvq'im2 y Ek[( 8@J@/nHq1Z'yJY[umsJz}ڵl0q5Qav44huy]`ܣwP2lr9 636axWfA!ے{ ǷuHNZ#oxÆᢽ/bKZiL߭pJyeyēj2pa*_aՑPDL/BZtD+WJogcgqPOes$W7éIOc./Ϻxj/ty@vZ҄H.ɕ;o2bS+M٫btvԧƭ}gj7|ٴi=/ox{̋pл#.b =:DE͔7R>7=Z !eueIyG͢= sw te fC8IY,)ac"b,]GjjbCQnS&mfF)+x}P; ӕ i]UqJ7zJ=mkkN^ʪ?)$RG`- Bgac* ƻ}ݤPjqIL'Q~,Ŝ C4S\(%uVNݹ~k‰o}P5[_㙊(TФZEp@BhiRfS)h{G#IZ.GカF[k`L،oEؽ90]-!Vِ}URfnS%Eev=i܁ rBڮ&Nצe/T#Th yaahXI۳=R9!p(iHlU'dƍ*69e T/4`|jfH[>İȹH0@^fCJy\d@q= J45 !A: {~^.2]/YPP6emuҼW29mw.Yru&|f@}F)QTF?;Jra2yg0 )Ȫ D+K<<t2 ʿTxɏkO5#9Ǜ|ĂErݗ{_K[?tRB 耂06RD?@#jM?d85Fi̖Ƭ2f,sNYF暹R靭p -a%NX#@Yʝ˜ ~<8S^k 'MbO{kLȼdg t̰_b2\ KM{ʽ~y 8ZȒow [6# 6)(Tc(_`fowU6׬88 DA&P T\&r4yџS-0!'%Aڸ[uur_n\Ѝ؝p$M$`>`'$H(ԃ&BT9ǜ,їu Sw;~v|_tFMm@Zϴϧ*0 [Z p\%yE{!wO^P6&C %dTQwI8HXjMipy)[qpVF[S* *&F]W_5sXzp׍R Ѝߌ/NjO0ڳ_Κ-1eCw7+Sl;?^91ICTΌ2`$H 94*+1Ι9@)2bD X?+_2 mܓ{p[ɊP1Ra#-()r4&G6 ]NȮp[.,Tє)à lH"Җp[I]m%8BFtw=6ֆ 6fr樣~NH .kqh P"u` 5AGTQ鰃S?oSg:6+8L pa5BP)h(lNkH)Qڐ5$4R*`5  GB _Y/;6|V rMgݿ:VyR igwI3ӟ; };6"IkSwq!r ЁDi`]L=Ս`tp +k:aD+ #xl?%20~* [WU(;{OjIv)67)i\U;s攏TcQ6Z?NFAg @3M\/gDA;*Ϙb8$)OY5a`@J'E;pǴ !zJΐP 1v%fH\Jԧ몦.\~[4ʗ2mɀ0 TAۢZe@uo c}c1$B 'P(dzsOiH鼟m0r?͖zcUYJkJ%LBNVI $,o15 !m71ek+϶OJSք>n!eWLp@DrR7ָ6y4zZ>`ziœի  2͌ Q1ymF f2Xgz CFz-Y[ѝ lq !Tdt@0 n?;ޚ4;ݴ^.[pM݈q`f  sעlj`Έڞ7}s*WJqpڠ8j_mZ-(Sccu3ѴL"UJicLvIפK ?}GBSJi:9j\a52Ĺ"8B#5**ʍ& ɪ;GehPVRBq$q¢ߜʩ6L`_F#r 9 Xp !fQ:lTQ-㨏:s#B7F)IS v+9% K-÷pa RtT^3Msvx/_3Gy}ϗ7//'`_̹8oB9ޏ9eʮΘH7QR/7 +F -wK'i")իC@Qt5\>][bMSr (x*oIm&TV*Y%j cȏqJ&+,] UC%ofZQdGyI mL#:30gxn|8lPNre t*aL7  AV&1xFcJӣ\$ Jvb* sۘ%XÅߤ\}*РS(ElQ~o d'tt#4(Zwi>>qB晫*5k~5 #ϸDWh͈TfkfJEԤ/mL|[% rJ*S +)iZzDFEGI4y/08| Oװ񚚮ѱo0P v1Pz9pbkRV[(A c !;ȍd0=3)ZWٴQS'*NY6?3բđf/VV϶:P `% ^k /)[㐡 +35ۘҡS_w[{l؎|eʆȡ`@^mk41<_V2EVUI Bc댨y2u$ɡY}`)ig@O&V@خfD-Fd뼂JU$%ݦ^nv[byL &C' !vNaKmXy{b)BbXCR9Aco.=LNUy#ېC&.Eg3}.YqcQ .X#<5A* }mw˶*fE hU,aoS@ )StxS3GgAyF/qYTmWL9˚{e"")țZu!)d/nf>NyƬ4/s<ཻڼ/~Á:5`#MPI%g*IaqcA} D@N3fjNiT.|>[v, 66nػݟتF`;Fغ}վ},̚_Kv:{%uſYUkLJG:w fjiP)soN*InfEھUcU\e󂳵xeJM,\"_2aJ@!K%DX00@D}',K»qL2dJ.@x3xhڜWbqx0UϪE)@(IYU'Uعb!sQe@s}\XEj4M nnđQFBjJu!: Ͳ Y$BX^EQX'Y,樛f{Qw^ɘ[%eΒo.⇭BjKBDlr) 4fT-V#39U; sT}1} Ro:-)(M?TrVK;oS C G&w `*i %i+Z$,TEa ,[>6lN=)Gvd^) =p8zθ2JN10~&bwuwYt8>A fLRKԙtjaX̶]UXu!7MEarrFi47"lP۷tHd XQr2ɛc{Q~N49!m"0/SiVGoi6;K]8J0P+R$Q`*ܞH0n݀_&Tc7mi iB7ǃ@+ m* cnp{Ӯu?+&M!s\02V'S]_ovپ~g`dאFP€@Bhi(QN&eB7WV[CF(E gPRlF;R_E"`P VT2޻=sea|7 nG[^jetzBFN= jc@{[+,bټ9\J>v_ltR-kJFKdYᛆAWpK>00xzsq|8AK?~١9Oelߞ:m񣚰 m[quG>D^*O1)wxZNa=`'DApHlʭ:'4 й33'j`oQb6/IP0bD8 %6EEk%.]LAdEa|Y;DT$#=%2%!DS@f֟i\R\uv ݏ!vh}X/zj:򧳷ڧvK$jIv-3qItUC+3.*kĤ<,IM`ȓ`Lq\ZwFԕT\@6uî%{DJmVnXڷʟdyvR~c#-&NfWdE"DH-dVI+vyj$|'uN k꘲w:)"C5`+R8;wm]DMm$bq-c9Y l0u1 B0"8Ή3!TZ%g W5 2) E#e\ʬ.ԨLv>Ĥ&csqUxXqW8]3k.7Zfwy?Z'jY;uz#q>˩;1Ua` ]r',NbRqt'Hm>v PEl.FЦ-`1oڊ#%Sh=0I=gvFa|y3Yscjݟ3}þ:hpBFi 4 wl(I31 G$򎖂ݱhs@ ?w+. m4? RSi("Wd8DeTDC4ʸm7w^ #/iݿYѰe]]+J'Ւ.GV7,Xb48nț}!7h&!xT \Ҟ` !&]kg(u?Cל>B9bJRV}vx&L-C?сUb9սcEmH"RrP%Qm?NP|Z\jMyȋCĄd CՂe BYK NeP៨ PA zL*=aFڤ@C#l5v;Q)[o]r^k8.hi$ C<DٯXX)p]F>uV7gƿc n,ʼn,Z0 V! E".|>pjw忍jܽfKM=*=+N{yKMb81&4:$SRpEc%&{3pU7鵹@m:j4]7yDթy&B8Cs& a=:wa-)0>qՓwo8m8-#j N OxB1x1ԕ4F@AHMr \jRO4 UPq2{\cf@mF9=~??xҀuQ@_լvL#VrE 472n>`Gi+xYfmcpngzQoJtBFj=ߢ5)xSk>f_f { O#5p!bvٰMnb\u65"-yƁ_`z! Ƞ2p|LV 8 YD.<[Q>6ԀMe@8*LR~A\5#jaG zm vTŠ(>v{y5x;N#-ČTGpZD"\[h1S ~6 K^%D g1A 6d`*7&d趜-گNw~_ dWDv5yM~hOKj6/sqkQv bE``~INHȐ JE ?Jg\*ʳm]oQx^kmdC݊+ zLIw >~XkkM%^e~Ljd4*LLe'sZ 1w=U솒j8:oN7T6Ԙ}GlyhD(4V0j&e+i 7BT5UXP(3Nf)Rc*6BYk7t.=v%@7F0ŋV (a(D Ϥx2]*C<)ʄ[UEpVi*utݓ~Y/0.(?~,ajH8vH(0p@`kI_4jV N9@Ί iYk$g(&a$-^3 PAaǞɬ]4qϰׁ{?%zWuYXm,~x5]PAt;D H\∝{kGf)|@.)Hu3`i, @ŃDdQ)޺|+;^o6ƻi<6{qZ8HUY7\K44=.P v]`ʩi;Y;;lƑ "" yJ,"& i^XK# @" *dߤAϙf"֏>Qiǻ;ɻWM`6]cC@dJ~fko.00g`%-Y+Ǧ_jVEnD4]FzNRrF8Fb  ?4K7Ub>Jۋ?|̉Y?H 9πQ)bJ}F48!ki_"jNnq/J{MfDXq&7w.xhKcƦX,J Wew(= ؈A9^. Z}br,A*;{C|#C;ݝ] >[- +M#ܪ<4°D 2A2 @̊..s6N<9kl!#fB%9=TM(Y j GV '4(k&4&@"T1/0RrȓmV$ G1V HP((2%c{ⱢF.kx5;&U({4 Dtz¡X_Bi/양LȒ%Pa 61:}Nn-FV#6-˹%VB%7P O˧h4d82 NPn:"&:f0 DTt4RUy כuW/ck|th$]M46[OI8ՠ[CvFAͭ/7VB0*+I-/jRN1Dx@]ˠ();͛jNS@P_T KgI0lȴ?LD:Є,BR0D}5U`"ǔ;>!,oc3Sc TNcBNkr WYEv$0`AL" EJ &py+idD0mG6%G r%BON>BR@aBlO9P}DڃY ęk+5ک#fyja|ѩv!)<>}|ǡпe>!|ٞJaalxwssk2"Gᨨ̃Hn:<|Z4q>6wg>&ҍZS TKR4xZ'46!Z"}=SABjnɑRd Up} u)M9 SWuoɻ$,MAh]j N:ZA*n)'gL&w]QhhדW^r&J73cv1lDsB!,+9]u [JIA `hhg:l!! L@H.CZwBꪌ[,C:%a՘ h!״5 +Zj-ݾmXҺ"cWR,7 5|…n> [<-SRw?5@Bm&NQF_{5KRf „=萪] ӞXxhi!]Ԣ;HC.v>^uqW3;A>/DN[|{%gY r3c׾7QhUw-?z'I@6 J*0UIp*,X!<=qmlN:vu'Z'$*9-v^*NUl  6( w/ -h'%`× VW Uϡ|v = ;m$󔵞SfQZm19-m/j " s˿T[lR3hA`/4R z'haJ&e7gU_^.y>z8q!j{Wؙ 7CS&HpXs㦧NWY!\ ǀJ~[ F@ W DGAIcEN,xc6 e+;M-*|QAD( @T62:Ff@#ab" 1)l̞O8b#x~@3n.VKkd" ӉWJULR| x&ɘpPMdk*q:b SQ )H=B8G̷+ə^K"p2,娭!<+ Dn )cD  zXAW!Tw0d(W.xe/z%ֺ&[BhހѦdJi{3mGqmȨf-,gMi¨6khpS2%qj|GlDQ+ifnE@ @`р0:MH@,KpxҦ$uflh+xp zO\E Uߴ;9i>CoM,Z.W[˟t,GU*Yɮ ~@lb4lCC /P}8n r3v"2=YRc,eNBG'NO?컀5L OCN_ umnf8!ul<Rc r眱ߍמD /``ۭ |O&D R,‹9;C(sתt wˎf;;ROmrwGۂOhpD!uMۮ!iZ^g9#E]z据].'o4 $P jʐ12c:ꘂEJ4]ʡ,T!SHN- _ic) ?%"d)ʐ2tAH$j *oRS{}ilngaҘpp 8 Ҭ?Tp0vWyZ  ׮ roxUDhyhe` .,.\UvXm |p;;R9gŃxQ]k er ̒TNV`WO3hGH΄HtXLej^540$6 键'OĖR{4Uf)2W첣'/}Ԝ/hNNlEH'CP4D,#{3[/|lOZK9{$p\a5:8?Q!^,̌X%+>ylw+rR" 6 #EċQ`ar<L +d 8ԯAٔw S_q_}"|]Wwi)ut"4*o-~f]dF F'F`$\g;mh礀d"բlhSKF*hq6\hۙCUVy2]gz?o]߾Ӥ#N+O9tAC9;ml^wg1ÈRC)̖3WحN֞4I`ܹ߯˼_!Жj6ͧtfzI!8c νc! ɁОL{7=tmaݒx(ةU2肯J8#lXI%6g8L=' Reaȏ,jL~tmH 7y|RY3r,?F]v:v}mTW) ƑKq[N@ID䟌h0ZyAT&9u9o$MJ8A+!9)vRG96{z/h47͇.w03I.?neAKU@xaxs: {4ES̻DZ Q8N`=}6-fQr 9TDV^쭙 ;a f7CVDaD+\9=cIv `B0jDgzp9@]H`KV0V(Bq 548A@gpb0n {<]NC A L9X-Ҙ.!m Tg w0eфL>_R׸?2,4#k Kjbi`^nEQTbm[vd*>-rSKݹ/$\` DDZJh),:ib #Uw53Ȯ( >$8յWkWZ&:L+븀ZRc _2>&wGaryAApy`L}E_f/ݷv-A7q"3[Dc K}KyBbw.Э?!T4 e N y֖; \BljH6 ~ňy󊮈`o&! s#BdZ6 _4 v|?U9t*# ofv)uhl/ D驁vO6jO,ifDETvQU,z%vABQ %hY{~DkFU=Tlr"X7]mu. ڻpMn蛞ͧiqȇr?=cDծtG im2##5bs!͍we,Fhp3@1P)~ʾ,TftX Dèw G {ټMS%5(J+oAw[;O]L2b> O 8Xn&x2hm<$Ϋ7FN&,8eH=cy 3Ҝa)-n )qԺ-fΏI Wf/k[ftz?mx W"pI!J]}NɎSk2ڐpMs! #OPB}9*oSn~{uuܼtkpΞ352HAYRJ`(VAyMGrJbKʼi 8RMrUS,%tR.b_+N*|Fo^,ZDcYYU=?ykNA5br/ H1Dd{CXk%y A hmSv?,,qhH$`@< 9ꤧH -TIHPeY,g&C @V @꫶'ه ̐`bhKB@DX pʔh;@pX)ni_Ij܈8 G,_ ?'c.uL)Wh"S! Q! +HcT 1dNfF*6CCS)/sѷ',Db鈺a,'s6?J6Q/iE"eG;N0 J, %Hɰ 9"$H',Y̷I$y鬁l?W@Wg\ЁRarjxlK;r m.3>>U,Q Hx"\If??Q\ﻃ "wlQ"` NO3z1wbw!k˥+P)o>'h .omMOh 曪bt;1&DhQpS"rh04L'Ǥ>ez1Ъ"ё3Dpٳ>ךRA* ;+"V^:&OGP5'-E8ߙ &ۢ/I&Vpg@"x7%PpY,XTnWR[0m8H5[NM" A#"85ֱ2ǭJρfmOnְɑ-V Z 5ƒ/Fgk 0]„&_BUD,jI-NmzEO޼w PnU{0d6A%*'>\76ܸ>AO@a/ZSSSG*$DgWI[N%8SW}TMwf /r.c<6;C7M]zb3\,wJWs+u+BKxZey6ǢNO7xjXZډ0 |ڢIjZ㦛hI>湯n/OUs?NFɉDQsa=Tӕ)5_|W>,7BCy  d5S9PԖ2)y 6hLДbuWn;C0H?ڦ:$ Ml&DȺ7؋|]Z8tYiLqkQEQÓ۩˴wc\WrCTZ7ʒ aԻ|} ! [ QWa|G>c;ǐW 0jD<%#~Gd# :Qg6WT"І&i Af)f)St hG"0~:DҠSAZB갑cGrMu0H|) ,<%\à0`_:~my 릎5N汎g'yJGQkQC:D!#K^?5N l#Gq̧kscyiha^|(aa .16rk#("mpsDRCj6,PإvQ 69/oQ^)d?cEQju໺HϬc ")ʈBf׶ " %LEףHpqr`ְe &B2? JBp؋ҚVHu}wsI4`K92qVUaQqr2lzI!4 de%+}ދ0P%sXs솲R[d+::Wۣs:*Ca68؂7]`bn Iˣw|DGu'w%7 :rh%03{vI 9:0Q 㕩I1\43t-x%Ԙ6upM]}}B貶v <bb@s|Df j(Wr2{ҵ/=6k'Fno+GXGTwK QɄ=x SY4(E(pV_g* rho?}!r*I ZPY"2ŒWD@ni2lüD͹l"9u$5m` 9S# 7^>x yKګףUCtԃ:ͯsxNNzvgUi8'i}_lG6.S3}놬n6y?X˧DXk1e4z6`ԗ/*jj Ap^ @3+b{9rdQRw֢5iVk8v س)9{ΛHv2RI1 "$c VXyBHMMCHL0d*6Ua&nlSBA }S҄PBDGTEz607.P9>b,ta4vpI;kF~Zc0.X̎jDj(w_5mb@uCDvq#iN]13X'N$|RϨAz[ \)lnT-pA!? L2HZmv 1S)F͜^T6u # Ԧ }T:j/k&XڒW1]LY KHROgmTH}# ҇'|00*NP,60Q<-Ě_ł8mq^D H$>jIa"CL( h$W1@iQ`\>)X1CپMmύ4q@Key/اNXfn|^`U KNF!Rd.g;s,`C(%?f Q,Hi?gp4Z$@,X$X(4WPI_Se*e~*0۠SQTnY;/Ե6VSi튆˯[u11仱c]=d= ՊGV>hppMoչ} csMB[1 5 XV;s G ' D #^VF6/b%eQRI>B/$>X)t+:Ѝ$GjxǚVZR/[3LKuAZw`.A"B 0.h.EQ4!n eӘ{t`aѩ0^bHrٻ:<hME'7l UpTDdv~3OXa` 885Yn^fssCpv%>¦Om?nVە-N{65qZX!ʨ> iJ1G옅6KO7['NH:m(o,jBT.4TF enlO )%*Lj;BDQjyo)}#mk-Kgp2C@bLkDeu'mhΉ+"ĭ{@S?BKUb6ԹtEk5]AS#Լ2{TRh>ьIPiTō#&?\iY̲z8IUF#GWiBKYz~Zz@-h1ږR%}d0+N%;#b4x55ߟ^C]'Ƿ01noM;׌eBxww7ku$d Mf/?PJDB}I8ZwsVLzcqqCD\aL,NUPC [oeꓷh$@iB5ɈafzV AӴcܵuH/rƈ~$s'ԡkZgB4 p j#3zbhPv*OFm?UA0 [Y*6ʚ1S&ݜH)%CaF$JwTuDZDDIʒ A} *g+-HSh4`Vh lBQ8"wQ!Z J*0L6 [ROSR+R|ݶow>ê9 TQzIf@c%9 ⓡs{@E!Rc"0%@1,; ڤq&NoI+#o[K6B)iN Pe~ .]˫EϯAЏ!A_JZ0nlg2]˛Bߵ\$dqu,i} )ּx4Ӆ!R=bHrC-؏efzIzbw=U@#at;n> `a3csg9 j@aPa ZZhaT]He 9R,r'JB"V b$Pu !LNuaD.W$iE7dlK/4۞k&3IPp SmRS<7!i)D¬ȇ6 5*2̚.!8P H/Sn%u$ "D CY?Y̨EqRDtEDʜ"po$&:RW*0Q ԅ FQ 9i HBv!)׆8 ىDDXPE}yX+u|>EC_Մa.QC>Wz3sJ/+VRN%Vfn遛&.G},I v6g̟eVNl3Z[Nυ>8cc,uxf&ǰyvą*>LjjpTЉ*UERD:vMJ5H&2 #CN*" O=S݉p`,deS)bL;+|]|ǎB{.=m;U;9=TOc- )kJtJJ Cܵ|M *GB,X>V`F%$PPu/ <GdӔ =T: 93&S`0Ȩ`6d Gm- ծ_,Vzȸ/:ѲAgK.)G*\j8OTw;u8zOLhM,ȽQ9 *$$5+t"X>e7C@w\d5 4Lf6R&ç6aD\mTUp5$@[Lĺ9f#B0La}hfX佴mLrϙঌXKnTfHIg,NV&T R<+x-ȍaN= ,9ߜ `R, +[<(͛*6@tû;]G(J,:1/MѨM3Ǜ2=zYwO#Y&d`LQ;|w: kq>Iٰ:B#<Ӵݡ Q뙁 T6nku)ÅjjUiʖYaXwܰY"=*z?/?5x|u'r_H½!ȷ&d*I*!X H\w9W߱^.PǫĐ%hja&< dNY$n1WPa2!Ypgz8.<џ( 1z1x_ziHL) a:Ld1ޘhh oV1]F9U@€tBuXFEGG#WaJDRH^h4RYk!\p$U Q˨ WC뤪S]@p)Jbk $_Kc& `0%AIiJ6(NˉOcB.Pʣ!'Lr[:7o֬7m^PLAy] 5T r%X 2&9p8|"PoC`2(C1^_28ֺ-V .fCCܽq4Kߋ{6-dXn*pS`o ڎ@] Ywz.}SaKb+zhb]ټX ,/PQ=a<`Oz3vߕOZ0Jc+I9$[Ml9TGvkSz0!y4;^e)Q S˫<4'J J'b3Z,y%fs 'aђ['TbƬf&XPDqP5mwƄ$cGCՀ  pȑnv-O婱܉@kWZ0ۥWQ!&a k5r*"*Z/eDa;cI+I>5X?ʽt? îjF ievA";r,={'SxQ`ae\FoԊ!{|XSG z'3B#s kp;MTblg'bk|H Z{H,EB0t2c\' ":d ӏH7ĹW5YĩZpƒ-?+AQ2 6e# 2Gp%dIT a!~\Jo<*w9+CV:@L@ ckh_BXw).G(") זZDlsZwݮKE{ Uh,~H93=-&>ۑZ&LWaeP@Lk.cb1T&0_Tgn?|W \ &(0@_Vݭ.^cFLJ#1S@k^ "S<⺔L\pI;|v 3RTq%Zvr(#}C-/M>\g}D:MmiB "("4Z" LF $6!(+gdŌ7JRmi>Ǽ ZfL1H] )ziV5MھRu瓕Uư2pNW4rB-0ۤ, avK.uBto,bY#AUaûťMQjt׉4lQa2?^ ;Sn$͘!ퟚ0_o1=zEkOZQE A ㇖Cح]xyK}r;7:[iH8Dw8jz5p }0Tswo6`}|^`8iFWО!2)QВ\v }60I,apǧ-qϵwz_w.9CfNa*Fi saN,g_?>}u0ݯVk}3!G1}fH4L *bu3cqtYhu$!Ԯ|KD׻Tb߲ׄj-Vi"b@#=lIl@(T2(V}/:F $lu"{`ҪGD 9IԻkZPs4ؓAZ}"@iᢀv˟⼨݌vyWU@ "5#Gxh% M.\{]m['|dɽ)L:1hnIrOBD3B  _@UX(dS60=Ut-Q$uRK-_a>70sit[LUԆtpʙ_M)'ߕA]){/7eJ !'L?=Nnp~yw8Wެ~#v`bhꭄWQ7=¼|y mvb>YP=A 72^ 9 gS,6x8BKc@% ,Z:DA JAC%R-( _K! +~2U &a}P#h<6#o *QKrHjM+w;=643'`Y՛8Έlkoh0:`45#'(893~loD>A s ]>Ѕnk8!oMHذ%KIW cI쓧DaV7u1 k 8#ҼSBsύơ;aV4i!8UJM@X[PuU2Tz2GSfu)GΘڒ3ˋBQ!ΐZu+=Mm2\ ̸^2Y.-$W! =c%JRh21 jOYku]d9,S 1 ګ\4qb;<O?FC^kiWT5WKv*%DVRTqieOoZԻ[{4'R+'3e# ?萘DD@z1m؜SeH' }Ъx?7wֵ$ 8s?}SI6KI|BH]1fpjjnJ=<+I07@]x΄a׆Nm7- ˧p3ͫZsqL*mCT$`D7*ZsB0N#RWݤ u@3ETDCǏ 6mۺU,? W1v:;k}f݊6u3vԽ!l$ݬVDP&}OTd?EYXxAo"M2(VX(q0x._JY3߼xo^<ڭ,|mmE)Gΐ͝U},H58H~Gq" 0^Uڔ"'֫e,(>79.eE*$7YW|~q)bB/|a*@|Rx?_x?ӏ['@҇b2#J Qy>^sͰDR:.nG|Y | ~`S[CouY54WQHIixȌ_ư;ٻVݛ $9X | ~a~u(XO |;󍧦]X;hɘ*jG8G$-Y3EHƊ_>ił"]?jʮ}oi*@D@'ɉhmDC`Y FgGS!Uf}k(e^AI'>sθzDɆ/ƪoj}c?0޷zy盎hlWq*/>/hMQ;[%-YqE y]͞XyC\X!QV e1cħ{)FKC[mf{eܵOm"EX/u'2N8в P6IJ 5J}VK/cszA!% JWjLgy`,ɒ*r3$Hu:)KtS |A,(XcLD\4@@u9Yq"$>۹,g &= f-# (3 ɑ0 kAWR@O$9DR H T"p#˿.\ pI4xCkBo?ګ_>T8XbK͞q<{!%WߍPK=Yln]o2k)HHJOc0 &,z~Ma'ﶈ]pwHGbt[JC:OirelE[j`1H-;VUiQ ibKTA0'@x.MvщPlRbVRev R8!2T<  LZM.&qhYx(לnu"x R1\v{݁ЃGwݗ*L0@bOWa5Ԥ-d5^}OZWm/vL=籮@*b RKUaڏnGJ ⠓b9<[)T x "H"k˖ɯRB=5$xq嶸 آyИe_ⲍ1nώ縁@p 0h6зS;'``VԃTһ`ۃȟwX*ٹ]zFZ BRh6l269]Xsf zc1u]3 Nn.7E$T3e!kIcS dfRnvYj+Bl_ :![QR򿚸o0C9KdׂH.. Q!H"1+kYʻlL5}vq' 0y0y2H.I+tOa[`%JɒҰ @[S$(xF?L ѪY-\o'|tc9]QTN8icj,VتʂZyøiC!G$.e@`!…AN\HđC"Y>2ɥ@׆0? ;YHf0 ш@nKq ODӿț\sz${#+h!Rk00`Slg lMԽ] 0!F^! ;|"4c֙-Oy߮iZ;,JuTh`H#Q`XÎIP ch?!@HUZM%61w|)cMZw<Wb1ޜ@#:椉*" uXZA gѕwvK7)Qr"XhR3h>ץnC60Thѱ6va Lz .nN/Kʟ:dJ~ mlN[BYξ!TB&v%{ ~bӗvIɹgוgXm&قS&.I618Ic6L=h4VGÅAh*܏,zJ V[р%1$(lDJ!4j\24=S&W69jLɍi3-i3SF)@Ǩe F84>/>3x8h-$7~ R$ /~PlLwRj ZBeQpY2>?-(_ϒˮf_CAUgTRhQ#>Iz0,/ o.+//]8ѻ4xЯrËT l( ]+0a'`pa׶;;8>}k6&4ߺ/W):M"d3  8 Z%aq$I 8(C!± EerGx"U;l#Z4VhECF&Qd a{2{2y3dH>HoˁÓiG_'mڎVoQjǕ\f͋^2^x밾qPlxo2"#PaUASs}%Vv?I.վ0+"@]ʍ}iw.@fÒX@Y(lQ c^2x~pH lDŽ4 @XHWbBl x_y[GRx]/2̊z&ee0Di7շ~={wǚq<}H[VmIi\lAb3*WVܬDJpl"^꿵̌SEeO)2lF.0wRFႀ2Ao"%!$Ր5 SǑ@bF;Ap,1aSw@?in;%jf_"!#:[sv60V2Vݨdq7(Px@l1jOm ,վ4?CdZ>ߐ/b)oS,uVcwf൶ԡN8oQr]=I{FE$ qð,6'ǿJoRLjgH/>T9[;`:~Y+>g.QQ*J䊦o{ 8\w=:̿/@Fg@_eƢWCv1D,@M@pK{ÑÚY܂t27zTV؄RF:5b&ٳ<lnOiEE(\H`pjQER3իoJl0YI'Q2L 4OZ&ČQϮExvK<Ƒrd'Ѭ$AȒX5M̟(EISDF bHQ 5V]:S'Ty !oM Dj1"+u0/۲hmu s,LuXhl/J,9qksR/O,mu`ȟ8%I DMZ`k;>;mǺi%me2҆ٶAZQٞ`65697.JmlSy `z+>8ՑUsBI9t$ːqGnro0nml"w3JLw{7F<샄r~2մ a哛chCWï8o4+m즔渙˖i[䎕5htv5(Q}UiF3m=8zsmϏ_vfɲQ']j4$ KcvDW87T6i)'zz2?'Jv[KxKҚg̓ {QLJtLe9Y 74PSUyY?vGGCz JSk&)+z*ٴ`rա+v^4ȩ:/H}jڤJ;3M)0"-Zux~(2-B tZsЇ)S]¦m{jgSٹvOo~q?;=!™*B!blILY΄R|l k>{o3 ?9 '0J̽dd|+ AOc RJXdYunDCMՒg ^"]._?i掣E b_rE s#D42u8HtmCOEEZCJƶ `>’#X(==L-c[2Pz~)ЈQJXBg$ֻ@7)r`ЕHjX!  ,PaӨ4" Y  cW(>H?e%9ק©mS#P[Ѧ|k_|Vnu-N@ik^^c  $e T_T$ X}AJ_LI {lkf~-wk5(zm} `6Nv\7$R=G (|L{("LA<D_/󗃹-yuz܍/ݵGOwΧ'3! @ G@ r,` {.jtfj\(g('EdXSi Y*4[7,bV i[/&+ gA(!Y . BsL,NeOOZYh ەvkghZ9oNC^3}*@ot5PkBbVC/c>bNw_/NiN"BZw >l.8, !0eNv=7m Oɬ~UwC*AmȽCʯ h 5l TBG4/NW><7˿V<2ǭ5\3!%Dbƾ _< k*&ϸ p1ܝ]st^jArĝ)>2Ĥ&jQ,UQFo?l:usL]-l J^c 2(0i@!TD`Cjq@BlYIJFC`b^f͍z| ?˼0΅1#;TUݣS#92-1n~j: $0䣱r#@SNm'ƞeX*$J"8J8|uͣ0h(I9:MtLn:hρ qvobw)<04 *Ε7@خkU̩)!Pjؕ ѻDY|U.7{=׺2[UV5PڳͶ&ս55(}Y$ʇ2R G3exH8|7w 5bQQk2L={ȟvt a)#5qqOe~#u=˟}}/thdfY`%]_ڽ^n[numRtJm88_kdOJv0x>*|Ϣeq6ÜĮAv3}!ǏP h##>EJ ADROj[il9se /7'K̯$XVNEL*2K3DWDmP@EDi=^ͣtR=9kwc릩<@tOo܍Dr+C-sh*'>w΍5g׮u .#(&MZ967[&+GWa5'>7^;ۆW椝/>Q[E1QxX62Oj)rT%C_0yāDR%;x\LiF:gMU!vɻR7HYd`b}ʣ|0\¿sǪ9O4G h 㯦tIC(m2主E{ 23&,BN̼yn]tm)^) SL~@r>khB~ܐ/j|n*+\~?,6&:6񊇎>,xBn#.ToA]I QلNqlgY ϫDR2#H6v)2jˡ#׭@j2ʹPt;SuO8j (L `nu>{9lyg)>&l U^&?3?tϟk'c`@ukHаu+vyҍ .XkHIvZ;c^ AQ=GM;5>oi?uvCo:e$-:(ǩ ȴ#;);qai OI&3i{xU# *=T"aFblH@%E!D[f:Yr~%MԚgFNj5T(uG,r-{ӏL$zr?ksJn,|8R=u\qGŶ?oLmvaҏ^<ڼoEp["^WgZ*. <ԃr+z4*L|mP*'.kSK* !TuUh`xc..[CFDҟѪ0.?چf5y-i!1;]YnnR>'6cS}ֶ7I{:WCon{A㗷'k[s3/_:;(r7YI7nz#֑"Dz6~2SWˋ ǀY4 iBo?2HjZvpSƗ*Y7כ?<]n4s]/*nx Ҝ# E0KwcMcKBpz[7X>dpƼ.Lw;K âCltlĵ0U *l/c=7!QP;r{: >=fڔoڞ5ݻ;Z]rhV;ŶY'+uWooOnWaBP}jr%\ok]185h=0[׌ͣz˳tA]km+DzS &+ #\k?8{6_Y͇|䵩F{%CU]'w<V f&P+"Leѡ4sN'O NBZܤ\cPLÿ/'ؽyw#3͋Bu F1{Ӻ}rb`_\ʋ鑩Iʂe$,nP۠Q@i\o`4~9GF3q4VTIUt1D[,>&y*5 N&{{gUwēfv=RYt&+ E1~㭊r]Ts?PpeY@TD@n?Ǫ8=;҆YxA~u|m m&# 9iι8R>L^.5uSv[R[Y8~?; wvAA,HԀ.:" :^ē!ӕD4.u#؃!sR CqU'Ӕ3ЫW1dچε?]l^ op{! i-/~6ٮhTۥִ@l:ĿFc~jOmbӸ E^Gn/nYح| T~漌m/I@˘x Oě݈Ĺ M|;YaVZb`bx_t\&-I%e"-L  qMBڵ8-jP.jх^-9q):(M$uuven:Ux}tcR0,j(#I5FDrU7笗G[k¤~4/?~/tN3y P#C~=z u_6/-h @afHФeiѢ7}KL yhl%5!Ճ56{D٤hcָY>/vd!*ΛDgnhPN]v7+ڮc}JqlNR,_7@wmۘ,$Os]xr/^j#}r/[E b‡|Ѿ RfVv_u^)};K良M+ɽ=YiKKuPòОlL v>us6 iE^͇Og*l$-pGÄ>6z8!GQq7.v5ob=^4i9~ #pG ] GY `%JX5Zh%&h6P'Xbs϶k6o>ذk==MnIAH&`T,z^ A; |HVB7" ϕĹkm^_/( pL%F`O d;?նnaWr\3S=t]9KI6Eb GB6*15 6]~zI(MG2ךּl:ݒBRuk<}LV2~M]S +e1TK#sdNlL퀞alN%ƉyGGܸe=g(s ГUVmkM$-!iK=7uJzkyL1aF5dGE{BH8~h@`B9aTrRIÆZˈF?@3C]mǸ_{K9?=G;n&q}oT`쳼yRF]flth^.bI6QO݊OIXltm!6o2׭xpd8˽D|ۄ J^`)k H܉N;:lКFI˝SV&2Lų)ΡTY.n"aĩ'\)|!3Z®4KE99,ZMx|6}Qya#iRENG1ľHSxi[+w> ^ wu^'dv&/2ݺIЧW]Q?!71^zH-Lc+"7N^u=T)-P]^fF8GOX{ ?皃_sZtBÔ^f2O9Zu+"WNqdZ>O>x41F+凫q#q7mURE3e/^o39.|[MG,\K`s(5[t1apKh#P$3ܭ״ԳI!Q%4M<֜jɊi'8Yʽ`Wo}MvK6\FU]YWZ<GMgs˾֗' r^OWv( d/eQRx0֒{%3Cxhs;w> p_aqVB#sXTS `YpDzuy[;A@6`~`xKs{W;ŋI3jiH&DYđjTOʢ]#B=8#@Kv5& h`%hVeZ `\Fԣ38{tZF֥.˽ݣt!鑬: >xPka AA|͇E5ګM7>9 EP|PBb19~a.߽3|H*22Ic6zj2);A'DtG!R)}h8\ 0Bp4RZIh[A-{Gs2]P}Cf-e")[5S|˓mќ(+D%=63SuӨ=wOyd⼏=8D-,H?g>ReoY<}Jn­)G ;?zƞSJe7QaJbg$@F#U$EAn|UFgfN 0# "U4Pn]Ń*͝p:v |@rV2GVwMg&9r=^usOG2z}*o' )xI:aeurmF4a w#=.Ȕ?. e<T=K>._nAu\7kҮUu .yj.ۮp"د2)q:3z8s^Eqt*5ݲbOݿ3n{-wϱ꒭C;, 8t*jiW88 HmK'x|#UWiɱۏkPޱl?Oe\ zB`ۼY.{xT/)ۗ$:z{uo^Yl> )Л]q8`E7ǵdE&,h_6YkiEZ)ϐ~6]CMPUuZJx 9!nW)r"5q#]j|,y \4@ةp\4N|š<e^kl"t>}\[vX7>*}8H^uK $di=P!\Dz]v+@ۉo}-s8$0({9Þ(`5`J2LT\{ ] ZqWŃ NыU/9`QNC5Aιʊ 9i͵EbyKiûݟs)3 jՌCZqIj TSCQ$WVʵMڛ"y4Av aE7p⻶=GVĪ_tTPؼa1"bKy%A5@"%1pG˜#f,wyB )x)9ezM "ugOtozmccԙm6 +Հ\cō~|!'g7G gx\9pr3'AաHJց]Vk?Kc Bq%]۸dAj[em#U@XU4[4F>i\"}rwA/j+<^0ј _ǣveei-']!b5kr[ߑQQg8Z=R#ИSm{;F5Ya75kCVoErVG YpPpɆ7nBOWe7q.AơyBUz0Z>Oќ.6IU)$)sq ZF Pn2\'pX_ @^J J4*` XȻ 7vF/JJa$#Sɩg! NHwqЮ'Md0[9i;҅'q룭[g(5-$JiA&Km=N\M&/LlnN̜4!58ϐ 襩t9^}C;Mk8( ЁKm_݉;2,~X:|z]g]qt"D$; A PIf0AX*2$Sa؊'3ʂN{y(mspS( 鑜LNeQX0;p[Exi9Nj8x8ZV ^]7N'+X;Yj@ A$J=D X{hC\W+O9?{W|Aw`ruPVfCIGlk Ӻ[V>0]G;\j:s8`,vܺm .ĎK?m m9&5Ū W9 bVL˜ƀh)Yy8Zu>6SK֊ޓ%iuv-`[7  1?'3dQD`b2^GmRfEh|Bs8&b&FcB n8~tb?g@FmKʂ:&ChʈN0aV5ĽɠG;ϗ{V2tGb/;i[jYzc!Z*Qb7ӹ_c;_h(%z4?P N|/_zRp)ti퍙9EA⹫0nwϿ]8k.9`#(E Ȱ#R}%4Û:p`7ͫy1doe7ôm}4Q(m+WX7jq`kXЙNRiX%m۟!/)CuSdw#Lw?f lM@K@P!wOF̭_R`'3*eKg2{zFlEy]YҦg&NR}c'W^H5g]]\0'Ԛ:t:[n)e ҁ( 8 ]WՋw zTKl$Sa3 g\Xם17inwҴιlH,Hd,`%Z]0#u Su %&][z^@Ú| :DAT&V`޻D TҀjW@qFإ#|oExa*[$,s^GA@!A sB!j<U5ZAvlv@u%qn=c$RHWڪ2 {Ͱ't?=osbe EZ#p9Xm6Z7rynj<Œ |̱~*tO덖d513p* R3#y;NF;8scW$-Trϙ(&j8[3wE@ luK\q@TF]'Wl BڦF!| :J*S]|:,;؊A6=tDy%k;6z`:&s Xݴ(npb0 #c1yjIо?#cS>v-N\:n+HŷP\ȥ7A/:]=? 368>-/Wk݈FYꀯ RJt+5 NL? wds ל ;UO7/;qmqw>CC0dh|a Fi6N cD@ pvm;X%h܊̋ Z&m9b4VeEZb$k*Ӭahr Qs³'Z늃 npA`٬s_tƲ8q|]pvlSlC 'rpGuKɓJ,OLN VD{]#@')ߝ)Dm#u ]`M)@)$(Q;hEZvsBgv̸'lrk q&I.P0z M6(goyQZh.BvR-eɩ oo,p9JF&lBK1UUSk]}s[w޸-})}YuJ%d8 M,Ch#f1o@PL0,>{ۥvu sẐ|m^V$skeY&O/=XF=kVՖL1 _M§j9%ZT3H*$A JnًQWZnROx}u>e+P5n1K^C6Ct^^ʊ̂0T"ق蘆SDR#=1y+ bo5c/VOӎe4C I>|7ߞcɺ><ϟZܦ6=SZs&; Y:@$)$I\%2U- -%FJ5rV\mvGJ*8W5HO^ @[c, Vx J`EҰ׻@##ˇԫPhH3m+5B$}s-[4)d% gzRhI̅zz[У^[O;3hZ^mpxӱY.RU65hc3ң/]c뿚 Ji PCeCfos?1=#<6oM#oM2pe`o0D][LKeYO|3Tg(LA`B!1D'td7+={$??ιly281bsb&.6=<޿oe\ JB*xt镀>8쯜&opk&wGtAu`xw|pj` 0@Rz;JJF+j-`WeJ\MǏޙd!>mOf7ɢ,.Đ.%RxЦ{ϦB19TXW 2vG\;9{޴?[1p#!bE^0@ <$`cz-~2xz_o]J"Q{}O`TU% lkx×C5Y^閑,*VC"-6.;P٠6b63"82`24yc}(;=:_P/:]i/Pv_aT,|rƔ~Ksu}@~$t[VGwg}ԚO_ٯ^<ޞ+x~so'țڜW187*׿XdW_&]6x@y}hMy$ꮿ=n=]=~Hu7 I 5Ro~ ubZTH!"]QtĤM99$ly5G7)c}ן?沶eݨj h"p@ k5qhɒK^֩ WɾԵAFh0VgXMw2+~P/?dq9EncXxvS/,Tn>eewvqRuX 3un y[xJ۝Q~Luc*'y9vy?=tCw߷jIտB޴``&tsVT\%=-+\LuUELƦ-ȨSkZz"%yꎻnRFnmF9Ee1I,=!G\C˷Ո}4{~UxCo48F\UvOS[m+@׻4ҨL #Upae@t!K"X0Lh/fIVQ-|u|Cݺڣ?ZT{H-/0/5 0)K{^w}f_sw"[)A"Ѳ!pVJk"H @ ,89H Y$RB}xX5x5SH>!fxz. {Fp#\Ymמ4B i&C(x(L̯kmJt[Y *Ef~s8E| ~7ZL{tu; [PG}D`)rwN 8JZDl2[r3{& #D:8enzT9ZT(P8R.IVO/T>znS Yʌ29 85Dn̖L~Lmnj&@.&`G^&wy$rS_p, Nd3rE[~ЁQJ7ITFyծMa{wT6)虣sBeD 2[ueGși>N׸k]6' 9"uݹX}Fu?|:u]B=604 : Z篑L3O'_g?穓{v̻Sn? uIz?=GFCá[OlK^pNDWD7 1ےڂRz&lLʗ(x;(ۼ`״P%"I=Xń3{5ǧ1VݪB8h%Zz0FQW*c,glǝ]>8m ScRPRjXmF_T6=,pV陯slSck[< WeSӀba,y &s#iP'mIn 㷳ǝKd9jZx\8]5Iuwq iYԂY,˫~L?{Zw_}:08[ӽW! BƎFPThP jK:3mYnJsu9Ekoc1.Q|Op`B 8POFI7JID22Ʌ%*&_SΛ=:W{f>=Ө\|~?:gà-#zA$s*4ȱϮ.eR[Bu(f~O\v.emUFqMVx6*e _A lzDa{&+YYAռ{P^>xZC2a~vAv_J`$=LEqU ̴֙]^+U|dΚy6<=XdOy]X%s ?̐Q׷8!$Q l ;+Zu) ~bЌ8Ɖ7od`$Ep˻nXj!]͗,IYc*DR9eل;x'V Q3AkR0W@C}#0Rr`eՑJEGCP J0i5FEbYno`H5e/ӏ_2֍'fW^FC;䚈h;p4剜V-x~=%za҆x -o. #냾Wo݄Lvڡ1T1 DQ?먟x4ģ@ `hbf1] oƏ/c=MsQ2Cwk.fihyP4l+v2Vjcp=1E56+f Mboӓ"87tftݯIZ7T(\j' `o cL^mDmy N4oq1ofdn1n,:r:yEMDb @rpucGQS 9ۑkgV\'[C|4}msn~Yo~E?LoBdFnӋTϗwz/}:%i +@ 37z?TRLs*ֽ>V?&g*N3ⷼzO}&jgJOwo~5o74e}ۯԲza?}h)ʃDy܅҃G @F$[Gt*bjJha_|(sD @:|4cXT"ʙB ; dҴeO(7 bzS9OMpsWCѬ&\STRxh҆VF\g{.;'߶}yNG52SX(ʹԩkƛ"X{xaӐ˹sқοsOٵAmJ.kءX@vc[Ҟ0[nX|=-{)w^T,Š30W=7Wp `Ŕ8Y|U;y GFd}*P L -Z ^LL )茨1# \Bb0Ngk:aћTv)>}5O'~9]tTɯ`WVQNjP4e9e61;'b|crYA7])z,l`Dz;Vq>F9~ָFlڙ¦ W .5`63Oݨ|R )``7p{6]gx| O\0xzdaa5pB#C 1PXS'`n8.̻;ro%ҕ` %P娼iԍ>wnt*v+[oX>hznMPȂFA(,T56D Dxj0(fYXa@G 2q2ej{oAGb*(Hc Q P #+\̤Ĥ Ʀ`p 5˼|!}buRny(uGV6RKi`c2P)\;0@Q ̛̹=/gn;Gʕ.m$;(+RB4 F(  3x 1JT7`"AW/Ҫ_ՕrM6.($V$671_)5S%kAA1{-Vi1Z事~mÊ؆NmKZƍYb*2i`QGо%u"҃ #* w9b[3A7GC:q{\׫(g/SW˂k{ue qCEQEƷW%=PwblY%4ʺ;D]k-[ ( AD!. *bBKV|}mU"Q/)Zs$XmdJ!'Kja8= )#ʇF#~ڡfw Z{Uɋj$/{e\_S$ Eg3WN"@4FX80;k};n"o/mQ_M%5nkJz\WB6w=뾖/ꦒETd=U_˃D>'&~Zf!m б(5'T0`+z $Kʌde0*"\s %hS3)Q769H>U /6 5EM\[J]~"~OhL.BCD m0ao/Ntۯgn!3XJD=.m{&j]4OK'Bz\HE8p?ѝkZnnT+Cw[~E^J"+1VuY+n}>H=sN!{K=v Di'Tmr&ܲeވ)[pr4.T<*Բ*)?bZ*CHGˉxW&P@="%2"Gdťc9wMTtaFcCmMVW=y gp|6KRqhm5ժ3?n|Ct+?pkZdu\1;frQ|7/* Ȇ* SV[NU`͆3"+0(Tv?^EP\4D_4B99=GK%w]lZ6Hݍﲬϯؕ;:1WAa|&M(m[bn P.=abYނ,NghQd'1"I tK6hn;9N rۢ%tf m$ Ufb0K 4@;L U쿔r&N_z$1G" ri4CGV_##%pCxDD.T/wGMTqonrËgXJddH(R-0IiiْMDr@.޵|]F<xcD~8mZ+>s\I<>KEJ|b; ($BzE 0jY4U׵ZoLx" A=k$T`f#s"=w Yr^Kd%H0B@qV[iY3k/|[.gȘ({<6BI7tvIHT*@%6 Ia(AAQpa26O6 7ޚM;%vc!=lGZmHNm D!Λԟi |{QSZΞP_3'DщK7++ZR:7 ݡ;>K`@-P= ޠf܋+CtozpMDAj51 Y`Zۤ-I%fN <sat{lH#\91JK~^+V+k5OrO6$c+˔9fNr oL EEr\Tbe۩]$L J8B+D2ft 44a8BLZ-$08c@$.@к-dhvUTQv G2#}^pU5닑ß ,9gW\).%XsMC95ӷr lmC?'YĖ2{ύ'p|g-~cZ6}y|ve::X̹ps*gyO5\&n>Xl=iq.p7t>L5~ڷW Pp+"2c)7 6C24}qm#y&B sLYnR`㸳`|BJC<7x&5M11<;v\Q¦R;=!Pt[6w?<`KHɹ kA(IGq'.B ]Lh;0Eun!~UgXtk IYH- @+a=0) Y {6ڹ!ƓZ.!X1[PGbHmdPh^t'ߒ4E'=$r?%ڬ%{KEK}3ܯ]]~ͽq q*2LR"1؈45ӆ/rv!'Ss$QꏄW$SA= 2W4VQI FLja͚ N^ZЍ}ڴYg>YIMI$+ /mG?-bB}MVNeHW^(Ppŕ >P#+=ߖ_^wwۿe1k_>6]_լKt$ȔI>q u7?z~#$ $1adֱ |~A!$ `TNcg3wc5.oXȅ}+ki  ZAu8x53O.c[o1]TtA\Ed&;~Z\zvMB=qi'8o6nuw nƀOk6؞Ԑ[TkuzZvtSCCJ1CG(x(AG顧P Tn̓mv<䜪,=ҏ6@jV!b?|dZuNϮOWBGd}䬇]9#ʖMAZQ-{wxOvkׂI&d*4}w"wk*SAB!Ƒ /` U<8qKnհ"0D@@+i@ lX $D{xp"CMqzm2zYnI6NJp#q~aj`¤Zz8'| $,0/Y%TрZMOABOI hz";7&gi҈nfo|$h> R$O Z;4t0}_V:7^'5-q].JQlDfK߫[/zow-C[/ޠ&jX@U\O>~K(vq0Yݦtӭ2kļ}bVDʦl.x}8VAA[ږuuVE5`[$ߞ?HC39t94m.&Y&Kk2N%m&^YyYo`yy/h&05KI.266!OiK7=Bee 嚳׀>noq1,wy4sx0凅5xa(A̮|OwO]am6˼$tU.,:e! !a=B╓˦nd^w{XR: $lwz۴$SdMӝwB991>!r¶l.+Q}b>JipSQ n.0PTڵ|-8Yh!ZeN*4 I%Ύ#1vuBQ%r@&2IIL SuFuݼkh:GX\ءaaݵbQeFZq5cV~À-ImADɥQQXGȋOP$7@;\5םsᬌH8o$~came/ .6C|_  KJ]PzwLtMX3v\@ DTFr̓$o![!X3ݶc񴟮4K7)/@m͸z5z!Fzq>lH[QV ;AX^!Lf;k"kU:m*{%NZ +ۜ6l4Ce^n8 MbaĀ Nl$Rٮ>Ws4O殽'6@l!\ۑW~h̏3j-lŮ~9QGs#d޴|]6tMtNo<Tk%\G"+I/t|m7CRe55I8tԦZzQfh*~xDyKZw/9F,P/`)喚XIx vlaKN"kA)YO8rB`Fjg-ERfM3,m|qWM6Ն6ݷG *.zA8q &t$p-jB!@BM9ND-uNGatLPuxP0ɎpHf†lFx&( Ji\FG9[V'u5qj ˆw&\I?=BM8BH > U߉~3;VӇI(uL~zC?ia"%%,xVg1Ӎ=RE):4sǤIH( G &͵ f '1hA 'Vdu0%΄6(1%K G`b@H(BeqȣD.d&_f~yCaF1f)` r+&WdcN!zN3+]k39yj82t{^2tl~,>fN/.32h3jj؁dCb VUfhQC%و.ባ# l#ܨύ(P6IXB:o X [2N=>bU^XBn Ϭ5QUvOKm#(m +(fH`QbNTMjl`㥲)=^;O@@ټݫ֑a`k2_[_ CwN"E!JOgC}a~#'ϞH4w?[n7q8{;NRl,uƀ!wΊF[C٨n!ZV{-Ih,z6٬$"!.@]],Œf[!/6 xW_=ed@CNECK ъF?H%Ri@rCYRvOLH&Bh 3'|($X(B@Nu@{: ^>sR=!HhyT(бuրC&Hu)x-\~AAk C/4;a(^)Љr5a f{M%͠Z%՗3 ^MYoNCpkK6Z߂8t$+!Rr{'$dm.gD%$d#u:ζb'mջcjk*U\o%>pP@Vŋn=2ٵb chD>@!]:ǻU W-x^YpEƣ]U_Zȃjz%(X)iCFu4Klru57U 5q.J@d @0 Q)|6niT&f™+[EQl ٴ5B]/Nx\c0Ad<}#]p[iUR6$=؎)0j {7toe8Vn#8>wط坑BR . tMst" R jdP2%5de0ֻ^7CMҸ#t +K^B!MR"=DDnTE4D*T%GoNiu-I+ M ML]v7'4F7qRƅ549W!-gjODe[dT#:~Fd 2 PFa襅^=@;iÏ$$d[nneeX[!hMA>aR~?23qCZn5n XG1Č7\MxHҽ\(qdE8-9˜lhJ elbS_. oQ vkpeFZ!0zwᥫ̿u.F4e3a{Ew9EXBZT,y^: #+p jv?S'g Iۦ&K}!N8v)oj⅐,pXczHW2^`1YxZȳи[MkxR0+\ordbXjߏgD裠 |OTpnUm7GF.Rz0$fSz؞$TEAϕC$$!1GX~\v), uMGew[xHTm=pW~̇QcUxڦ_dy3`8qBeGƷj'z c9=5V " Àz{OY ~>o_ .+ d6*8g/{{bN=zܭ 2F,jzHK qoLi4!UB7~tګPd&Bļr6laۊqv[zzfٶvӀ;"Kq-ȍOD DW&L$&q=+޻Н#%V`ߐ L(Iq .w{o]bܱt, ]%Ko+X1$}`-74o!5T n@''sGv s5UUx;/?,,,p[ec2w#!-D,ݙw/{BM[q_:1LD&8*  .2ʀ!;uVZ4CІm/8tlr)sP85릱lF?mmޞ|>c-T þy'uQgӼ r>@[#_"6a`4L Rh0N t=ݛ_W]䱬ϩ+k5dr-5"DNZShEy]Z`mxlPnQ DA<49=G- ΁}2.nwfECh6Jk,auZ LbLm+!ܫ7h(Ro:uTMBJ_墍V)=WO@=5`}m2~cocQ~2پPm_qT:98 6,쏔 ^RrlqtЭQ-Q20l 9UTz:;։d X,MDs?$Xf:půgRr0 {mvوV tdsϖkM*CB4Հ&Z15 7 bdkw hD;ah c^Iwlsnդ\zBfjczYPWKeX(  4`ThɌo$[M&lٺ̾E>wu,%\e]0+p!)/GR]X,0MX(`YʯS|{\^jCS91`B0S捥ڽoeܠkgFjΙCxp _-!tOG?ZX/\X2Yx S[~1`Au,y14h(*66Hax?L>75L#1 [ctQЇq9a@Ѳ!8T ! [53~Or w3S>VR1%K/6w.Gs&YyswfbbDZ e!·>Yhx\rtCuƊt"n8$^8BMY2|w_ ?13ur٭_c֒E&Ƨjs;+X (xkDxb %*H('aowͽx6Nc9} 0)sB3ڻDg4)Z+UET@ k|a RR0n3< Deg)ym֗ZX/BUE'}+:}l`Y18DzF QH)OlFTW~͠Yѓ`]SD' yCⷻ<$ a4ST*(E$IDcYx$ A MNI3 1 9B]@4AC"zƐ^iI{r+tZ6`|BE;j uR-KZsaIƆDl5zGjptC`fxmA]ln:X¢sQ,]yPܗ6CW[=[ebSYw0afJ.="1}26]3b0 AљhTql"ܷ7x)74m4wj׎ 9+Q *ƶ,˔8WvL=t' #4]*agR5aTcRäWs[_}z @]*$ l[esaH Q `etR 1$`#lte94KbY@P2~ZaD4$T.5䵮Ac\6FDmd"1n΁oe\;0d4 HJ#ZS6se ta?Ard/_P۫״[߿~JpyWE͜Iᦰ'PW綗]Z2uAٕ_'<|ܦlں? ʹ@̐kmJUݜk(wo5TTp>`1ՒXհ~P`)%s"T;4/XUWV.cN/mU¨20cv߬۠n:F gy431KǬ#hQFӗhIm'L\-rJa) G8=#IqՌH8s B楘O_Mڿ EYXrkkz?sӁG/̶**)K'oq. nF E%^R@fnw,IJK3 GP%F;*9{3~gǞ9m4ʶo-168WRq遐t6}{FrTns)$(U@3Yܰj.L:Ln{ "_B^(y6yK 06zJw?tVɥ\iRf8 2SK<]M( ;v_{jx_wojn?̅8BPw1qCW}:STMVEK5oBYȝ12t_8bDhh7VmT,/HE5Wa]\MxgT$Gw갞jx z[U¿lO^ן:_W^hLk4I L 7$x6+&w~>AM يڦ$B?iR5's󓉀]Yyfx$ 2aDB`fe<zZHߜr6@/=>WY +Ȫt嶔1%li/]Dc9":C'ZYcJ9NĩH:} w#hzahn`H BXFמ#qD{R\#u'tE\wӳڃ~`¾5y6p, `EV.J9<GM'׏*D;<%sNѼptK燢{g1׽xo`8IL2ʴCkvu6$2'ǿ[z\j7.6^+YܣXj?=owP|nKd `؂ydmjTMz(8)(0(!Lwق!߇^RR(K.P3pe-<JyS:5Q _vlFSK;7ga㵋QQi#W.ؐmƵc~j_ۣWOjT]N61'JV` ->:j$R;c+8 BGj+Y1¯EO3&~1k:8'䑣R;8d%EG O e-)X&-3VvOw{3_w+(Cs\k;k?]&k0N_YnYa.$q8nɡ=nl2vVlڞ OQ@Ԯ8)7Ϲhg}{O{9FNIs[ (ӑP-'ᱸ88ѳSqᯫF6·A>龛 p,M r츇k0 <$d4|dk` R ֊]}oBqgEtRKyYc3V y~ًW|Hɩӹ/4GJjcS?\pO4kzf;.:ͅ&t(iǫ]DO}t(B v3mUPc+L&N-)~YQgF~C-(gc k}ƹg/aTp<aMJ gX Gn{' ކpY҄A5_3Ch N 1:D$Xf*ufG͝ob<m 1ߛr"%;D[LP53D2k\+:=|͛%.AlnÛ)]kV! @sFhLc$deV}42D\0*$ J wnFÿ^-{#p{ouidJ"&uK¥^'*yJKHoW J+TyiOO:*|N_F vXN*&ظ$jDz3d'y,KZӝ [%Y,,FvY+c"$ tD:o ڍMcy.0.2}sNW^"q@DȜ`n*NE u<^]Wr;rW\_̭cZ; E]&SHƙ]5=J7 #G{$,C{e6seu5˵ylM[M*Mk8LFCmt.+j3fw//+^u+KAُoba93HVt@ikƎGPt n-|j5&ff@8\ zο{L/N_yo3 YV֖dH6W~7op$: ˆ/U LgO^xRƻ??5}g{ ?8a 3ַFuUhvcKM$p[(FF `I߃Wp6;r$ߗ\:<%1uf'SI Pp$ $zHrp`q:K+d-?X4nkH%Nř>VGE{ ݍ_/u"f?}ۗ>3:N %acǁS⓯#"lA莠ǥ __ڮK:3iS*$QQmiq.{> I+dm=8;QM 8ԃ0o!3w,ݶ^o6 n2TkEҰzJV=CػE8uS@lr2 z]dN(r%@Kh"%n0tBfPGtiIDtE&" D"e'eImV':amȜTȒl M +y#͘m\$wG$0fP3tq.mN7…BHyTy["X  0 Q/@Cc9RC)AoxomfG>5;D:Vw+P\9>?߫De;rTUyzpɃc%9ar) |#I^FQ $zk\<%k23é@_8У~ո_Au :ýP3O1;t>NE2&fO2 WP2t[~:nmCLGGr`QWnЫ7@&g)\mNNdDq'G={ocGW=;c+>x-S[S%P]>|͕co,>0q)M^sYYojž܃/c+a$e%VNm Mۇ:? Q̬ `{ @ɒW.wXiBv @znO..>\椩nUV>5'ppqԫpQڼZs%B#3ȮykD^{0Wj1NQM8fzmaY.!\$JΈѬ72Wp -o3pn/"'U[Y/c+67$|+,ҵʂ0TPH*U_? ʦsgff~:s#47sɐBcޅ:󷾘/>tCG$7 Iݦ>|/\~, ,; .93=u?dtM=p:[CULvi\m^5!sYTet-_XJ'`CPMIvf \f,lJD`FJ @a 52Г3rAC&hxD@qp1Q19ugc'Bh_jfML(':+S !ש)} ;N5泺(Ԭ9o^7M/..LHv2Q$1s G@Ҁ()n?i뉷?B@aB*13C~b÷Ij:Ř*1ؔΤþ/~s G`.#v7w~o 8"BYgָ=l_0%N==ǟ;pА!( SES9%1dNFKn BfaCDB@qc,ľKQc dB{v]'i(p% ̡"XT%>Tt\ C=]]x+k Хh٥Q䍒Ch `  /qAE"bTWYq(xi j;Tz9m,D&j5U;ӆ~mSjD6JymuXc"knS r{ċYla{|;E-32YFOДHaN-ŭHwh9jp)j|ÓjѾE&,iX^O&(n7[kPLYMOKt!1;4M2^h,CK&=K[=Ou)HC9]:dG#߄8?|P<3wUi1v#Z3jv@j:|vg;muJ:ݒ1@ Y<.dׂ1h(F% hR@n` # BK ͒Qf$|%e/u4_ᓔOOY2uOy;fy O|*D7ɸtpuw07 tQ^gqCȆL!DI5>=ơvʙS}$ЃUWg}տLʝYJ9\i#!ѣ֝S֯I~-{}ANjY U j]9g=n8d9dg[ܩW ݯlSQVRa M(EO<4q9YUX'Jy"p$lӎXHC]T 8oP{q"S_ 0k)P5BUTr](N;K4P aB 'zy1vv⢵%@8ArL }]>](?{]suuLD˩1;]sZɮcf#tS|X-:-% e)q,%u# E FB@hP!A]" AP g0`>|h]u.zŕyd=y76|ɷx3WlvC]Xn@>+mG%G>6ta|5>޶S?i{=CjMLGc(LHI$Q35 .Tɘe5=VjۻxxHH 3ln+l B\:]Ϡ95z~uH(ݶcߋVM'TzA#YQ"83I]M,kgon&WI $jF*55k44J(#8\a)Sn T7kw97Coi׻J'8p3~f*52 P2) J+ bj` 0VZ=uc KkM`VI1L/TSߕ;# CǁrQuxICjl*,9 "dN,uTMYhWdOlr0HdgX_PW60ߛ4{R6c_8x(۟6=NX3w ^h[H]\!cE1I^9l.o >ɕ-u7*\sHqKڊaFC=6+$e ^fɺmڦ臁9J K|]^90?:'eJm*R6hjSC[Kq7Of&skAǎI2\-_ÏL|1򂰹gn3eycQru]ME;4#KSc"۔%%2<^P!&A@u|3Yg nmca$$I$#EC"lALbNrM£sDMOyBt.]S6&ʌfJkV1/UZyL-.$/C^JnτJ:h|OLlؖRw>͟|9@̷7Ggɣq3y+rڥ{lS~ jG?k=tmD@XB+ $}PO6=L}H-1HڣD s/gȇ,=~4Eb0|텤%֭HGF_{|A{tCșj--*?ԬύMoKw^qv{Gm <Wil||FunZ,C|{:>s6. YPApg<ϗ]Fƍ;yӖ T->$>w7II7ul*r?|:_1R~ 9}_7ޯw.@U癝fea?PdCo؛>S .bvoE3\ֽ 7=h3g'IU=F{>nqȧʤ/6D;^mڼ'w>8]~*ik A\}rSW{;{^?9ϜȶU^E,:>9:"&cZ~"o_~[E~W#.4YePMb?G9eۇա^,CQ0,n#G}g|e-UrSjοsN=w3b UGMuM2;Q`8Ы]}xgV_VtWgKo^lxOLsߟ-y=!6KN~Rv°v űIIc.|۷3|nS_o^՛ 4,Pa >EFDS%h~$xE +=w0n840m3g!&% (gj#E1"G9 3[| `4f>S+i h&-D>PC „z@4Bv ԃ`dow͏MV j] @a! P5P^FPKYw2f):Tu^'jDpD_;yk-KY>@ɨ%{RtB.߅I}k0d۷H7qur:~)3>:,lOރ?Xv61 ʽ3^wE;t(q3їT䮜斨xF#ٶkmyQ׶:&58gt"54gt!?7e z~e"8l׎yrGIdrn7>0ſ QLbXl”b7_xv2K%۳؁ jՓYѦ)8 ='ﭘ|jvλ_FF070%.K6g&!S<&Mku?.r gkNZW]U{f)}W.nvG_Zrܫk5q_nMMd)R=UŨiOU:ee-:Xx匷v׍8FEGj_>MhwŜɾzW?l?~goG_+#!ZR- ^uܾ z?~gɪ{Йwvk6[ū>|Weq)n>g(snD=U~B/[Q$ _f6-vM*[˺vmxO}P}Q D2O0kƟ'g^_uU:g=$%-u{{oo}촛}E\c wSN[3'?"`{͞-zt8v7nڭyʵ#EOFʧMikqt8)ʫf6e$ 2E,!x^fUo͞]7k{qOÛ+n(Ujdd>G5|oB9]]'u GӏwǠ$\@@shǹX!a!/I4j}NǴ?(c=ÿ'GHlP2PL 60 zL[V&P,"Y+Q8xC2S^LKv ^SQ iO~{TS B;i1}Y흺װ5,< _.K|!W͸z,;'ɕ$.N%!X'P 9eCpNO{˶u. TE[הrN+EKqciԊ}ͷO藜6ARrM>tmY]CVl#?F5t1WQh[yoK{S%Xh9 }ʹo×''iX7qu/1EbD l% ].ZEh#_OO ҋ 3N̿y#D{;p$n}5vlwR?_y'^V; j`!"`m@PA3)-Tw YK0H(}cxLkb\^R[{ 3/D|#*¸>ĿغlOB8xh8}+t*9Pa[݊Hìϫ- Vh?s)-L~zfeY B$U3xɽ9P@Fdxꜞyt ?u%IѳWoz]}!6)Xa f!z"|~[.Md<_VE/{$grU5:)w~A\U[|qK5rί(N&I/;OϦܡ%(r]+~^&3׽8D,JDk(Z̖ qs8239EF+R>%ȠY3[t2`Zi0P?PE?lgLȒ޻y|ҫo9uZrn^?rYR75YgI$S@9PnU`{*RYdPgPp4/=:h,4m9a >=#;&sN#qÃ\8ړ۫ffr?xێoƞ`$v?QayP,ԀpU4q( #A?@t畫c:;>#?Ӎi 0??u;Whqfuί=cw] Q2gk-O$BN?͙Ha02l^+3R+o-b~ ̴<:ө%jN[޶U>k2$o%w?7iV^NC}hلlNz:Oby3ޥ/Aبt@6xO{7l'>}r=LĤQuܽGsJFV4uAW;V6Xkr$eWN[EK8,wu̵hdq$F(6%7ƚMwuAqbUk$چKs]$s ͪ9C=J$ɋjBH|39` !0A"HQGfuT @qrr.PP ]NhR04ma|zO,E`8mԖ C0e$,C0$3pRt2񩥦ӇZ/X,Bh5`۷|- *bFkONA ?_6iٴ.fxqwӷ(g0lɀkv2@a"/㤅"@;gqOAXwG*u+v=+&톸p<wnrfTEQa㹬4~9{< p*`;l>9:qgmxv6YmYVN{ӽ\m1ƶo.;C)}RXrho9wkltwŨ7r&q*3>V];I$NPMyt/D|8[]xNreܛ\^2>#ܹo2b_|(riK1ɷ˺ 'CA~=̲sF=x)üár-,Mjͪ"<L;!VW\RunVƝ[Wɛ+rLLس_oZ%#;zm3TNw\t:H]gYݟa]eh^?Tl P3ĠesDW̰TA|MţAKs#moXq:E LWnt E4#GeBm( wafA#s嚮^kOtj|ł?yX8B Ś+O 96|}]!)! #E&Jʺ,s?ƑkIJHܽ}ɻ6h6z1W2-Wk k8eP97'|Y~WMڧ~=v *Ķ )fwG-?4%qu "3&h!u6XayW<&RJ2\Zf/CJQh>,/֞}m m[m=EVD*p>w؆:V}nz>ŹA3_l6}mߐgX^wk_sWDpH\WoOr3ι=DDjs gs]ɨ>7> k@[N@glgY4!A%HG/%#ӑnj;["7%҈UY 0 q "S3tkWbC gwRiP b2&1}}d6|:8Hćۇԓ[l:4={7eVa:BPTl7wFgydbぼ{/NhsBVyN^%'lQmlYux$!}6Z={TiOUfU(>$[Vw []m1^-q!&tu}߹#c!49-Kx[~ϗdXbr4kXsW;%FQ-}DKU@Ujcf9LPoT֫]Vǡ͑;Ĭd6g/7RbCOz55x@.4q.Ԩk`oBr8"_ԵaQH-M)5\Tu 9/?zԍ8K8qF#@/W.uu ~7- Y[IjR3>:g9 z-??q'^=}3[Z~ӎ 呌"kRr)Β+*d Dp#hEh9;9 ˜j<^xekR NJe7DK{!%5ɣ㞽([?ݜDseLZ Pp@|Հ7E9/_!o+k󜢥P@ 8#!Eєo\~N>Ӟi*&R*',`L7Y(E{Z][֢œE6B,lik~xg%ܼ{r /.ȝܰȜ563ԴgYQ۩"io[‚z!5j>e%ӊҀLE*&Qv6c08*#*3#9Ixe}HjޘS'tɬN tߜΡ.l,֚TQe2=\UzBssAUL!?1CO1W=S5vB`#!,ѕO>T|!@dD.?ǹ^kȯXln8>/{7ɸ[~,k2HiMK֫ZV-D)mB{rT呭.y,oV.il~fNaƴ 0Dc/ ן._Msk>^ދ 1ʐ0aȬ0Aݫ I~ƇfOYwޏ?j>t!w]IT?EQl6:VZDC9(/>['fQ?OY&<~ʙ k]3 (t%v^*С (-m#Z{/>fl.3 }&M|㱖K<1م'v+U(`9CG+ar_:Io`x3HN=:g6TeV 'Cvb\_88bV4Robm9 (@/v22^/  ]E)Sͧ]iLx[LuTf9Q$ `۔G{hyXjqs՟agvu v~P@nxIa_c0:ly"fFIHԂS 0A_`dX=ryQEU8XWHeXWIl5_$T,N $Ͼ IsQ2\Y2AE +f)_>^K79(nu9uyp=XOʢq^Cqk8X gOh[a@{*pPwx&BdRQe8#wx`WkK?Mo \ TpK;A7{gU6}DbGEO9!qVWq#%V!tf/ .-L=?9_cx{5aʄ9%%'oI!% `.ь K5;w>psƞ$ tsQ=2Evk Z< 'F^v"yݷzY.a >}>FnIr$u()q oq eGǿ{Tf#%N"Lkm]3>=4 "Z$l- |oEsݵ.M=D:Zk+=Y'8B\cc:ưl[叇X5urm^3BxiֈanӭWW6`ŝ7+* k\|K#p:~ gr$R NW|iѡϟ oJd2Fq)(]8$nɣrZ'^ וS%- FTJ Q/@\dqH1>"?lGU=M^5's;6С6R9ϖ~hz: :o hjJ*vVo& 5aVr%Cj}`4΂25Q a%+d\'B«kB9tY|!". r4c DGcxdxk0|br&ڦn.ӤF3o=|'>}nAN b6\}͓9t#MfS* Bi~b!zDUYcviK]fmJpAf)ok7`ngO͝{QzO*d Ka $H^DZt|@KgjPeTD43վޢ$t/jv)Rlv~QO\/Gٕ=uH<&Q{5]lS}m4AҰ ]0ͥ?\/;~~wW{= {oȗ&c/{/48L!J@rĐPPZZ>f7X &$hK˹vɂZ‘ _POKۯ4{~p׫?R[Wae?#soƮ~Dƽ/]wnÛѫ?s^r~zo9}V}wCRdpֲZ-&RPQ4F17AIj1D:rTI_DHzS^N$ږI莩&# ZγHp/Hi֬_Xbw?apBCr^a{^z\t]g3w8p_JRh=|G76>~)1iZ]Y k '*{fz/LȂ=DreÝYf LN!DKhN*2"(@ƔBD 6esqK36俩gf;ٵk(U_۵x&pڵP!rVBujrDsjƘS5-<.h,,v˯mLdQmA?[2)i 5!0$>U~uTb*ʁRjN@*Q^ОH3ĠUҰ,K["XODAk`:x³i$UM4Ʋ&/1;x~yOmi==7* ܉1pOF+C=`}fo_FBx[<Cx̝L ʶLbEuLpX+ Kpk O'^*"%ZIX4-;$`\.Yo O>@hcJ GiYŰ%σN$-ڴvq_KxNTDD'!kfu.1B4DtH,i[,&o=fxwJ#xT-T")uV߱꯿HܠXjaf&~kW?8 5AD9amƗGH^&:a#b?TRnk^i3g+yKR xh:rGQxٻ/$鐤qg][*~SJy@8_JqJ4qo!'v'4Ǔ#|4\„-KjBo8 T#Iя8CxPkΰ|4\C]>C<4HIńDRa3Dx#N$Hta~2&D$YCLhD@5[.~"q\{.砐X=fjo#(}ӹP8P`, ]! \ [}'~n %[s?Z!M շb i̡-FgZ`SwhLUUFnRk+TW&ࠒ"5I0|>-92LDz 4?mTuڐ#}&Y@!#a|Xl#pUbf,u =zهN_%-{. $ tto=ti8NxSy%T'Wo`ɿ,_ $͂zf .:2-9z)rF5kP_ Np]+Pp,"3QY*$؍"ЉZ׌f"0߯_z8'?IX<[]o4hwٍC2j< )/ZR]-C2- AZwU5d.h^]܃ 6X  &>-Z_z^j[q~ bo4Z)~=MtvO$wr}f5Ϛt} tjP.( HSD͢KH93qg0/,4Dd3Y|Ӱ_0*ffr#67v4hcr_phJi݊5kzV"k=F-%*V{"[HJp@ s90S]0"h%C cmsN_?#^-Оr{ek#$T PC$b!H d@87M^&E;yS3<}Wwj,92cQf4KSvH#.}qtgN_}mo@(gnY]Si62u7N~C/zu"ʲ gFˏ&сݨ' bIZISϛMNz~$1гFLupq6=պQO4U05O&!;X`R@tm-4vL%uK U h2xrA)]aldN,)l,XqVT{:nn0k풛>>" ?q)qDemբ|"P2W+yNSQ'|&,XɁ,k\&"`uUcK*0T"Xg6}+xilkoCI|Q#Y6p"煸ٜ%%% Da(UTRkjy@ƠEG]l2;f;v~iBSFڃsGwszZO knxuWHa@H#UY5 U*LeUZhMPw1<-ZfHK ?W6W+9f]+夌fz8֙Buqٝ'; hIȤ18.;`Z3 1'␃b+O+_q (5aHBr,L-jKGaC,BM.}#0s{~_^Vpz3_u!.3p[K.xqPOeQJqa# BP75DK:q$ FI|c#*iTU'PdVw2KhvLP59 ` @o;ߛp q-Qa*jf u OhEё $d„Kx$!2 t|ux&V};=śohf5fj\O8ySpLꮰ&ÌzGR' @Ft68񼾢ST' ,RL) KlT#Y|* _U(aVJSg>TA.Mn f,/yhoo tA<O&4D%3 ei] Kx" +DA5b @A@8+ݰYn_xt>^9 5Vڍei;Ns0~/~cիZ}23ܢg$T3ɑOoUʼnAIISO]Q42SNjb\E׃DcԨb㌞g1p:PdM?pr߄.ҵ"W,`,aX'n }+HqY- ,ݫhF @b7QP>:H$ixE6q]B, (7EC/c!xmH &E\& masY"՛n7=~@h=lk_[/NGWf'7ng )Lus&% cy~j!߾1hKwKgbeD-d7\iEfG7zrƘŮ^|貗0>trD2f5liZ3u 찖B vԥk "l= hGVR!d&^uxcAx~j#: 1숌}!aݏh_p fAuu/lr+D[֧݂$a 2Sq$$%1O&^ć U@V -fۨ|I@"N^^ WwśoNTuB7jEi!pM1Ύ_@~l7-(>Ȁ[\ח)! yyX3odưo8z@0`\Qg0B0'\``PBl= qФ7GRPSׯbL5cWC%V*jq[ɣ_IZuWofO]C6I/%vc,Ҹ'yXl@r; Mgj ZZ!Ow0LPC] LdN̽7-N(rdpa;;oK@uKrpcd._}+O)>Ԇݹyt+N W yBἑ'tT:#L!A ݰYy7k.u @.[ݩ4 \A ;=YT\ NҐ0l# 4{ҺHV }Ow56 0sۑ%:X!H20-yopG&Q4ńltLly0O.`}ܫ47 yq2 Y*pu% Ec>eN-/,xۭe|)Z ſ3 u e͛;WaD*3cwO2y6UH;~):d(N@@fȼww]wYTGW@I 8`PS)'z'yhYT|8cl8OKt^ Z}qyA'*lLHP{.3Yiv 139Ś& Ey ^LDihԫۄɪNe±|wS| d ui箸>2Ӕ EwT I[diBɟB,Y]߇ a7 Ÿ|=y{3`tRNLCD֔})ȡ=c{Qm߱\`$G0BL2HJ{[)JHOP-@!,p`,x&b,11[RS.%U'mn&a5Wo:v V,{A]T0;+-oҥwө/LL,$)#"aL2qEfN|(  { p0!;˴f,,nXJö{}O瓕o"p^aU]?Z9nTpgq!f SQ1g-;p,ytpkk>,$UiNEI%RFmp4'j vJ@"/\_!371!a7Rj2!r  $/jD{~`8>0) DH 0<#Q; C w\X'yaⷘ\kC!LD؃TeO[M 8)[H:WCcwF#)6hX QnD{;Sz!ī/8 ŧQU{ȳ}e7$$$F"A3un%n^,YVεƒBQ j4od7lӔhL"TkH:bW8=7^LFE#wL?ٶf8wiPeR%nOX`(yA1Iw=)[xΤeZUU.ύg $:o0Qet憷|(HnĖi'Btz 5Y!K(6fDzزvD"N$$GQ=_(H03^dH&dCH+OuXL~li 搝qIA[W.zyjO-&D~Dh,^ܹxTZ +bw!ϣm˱7gH#t|L~]CLc<)Jm4:-  B nH xq| " :1p_X*;1_;{n[ҵ,L E_xc ^Ӟoo^'A-J(_$&$ ,V%WM()-u f6fJJdhh&` Y!(MFz c,df %>$fiỸ[g̈cH !l.D>qտ0 ێm|L $Ҳ$me;4J$yЕ.$J+p! x?]p,q$]ҩ klx)YR4qumǼk3vho&"P*>^o +rK:>`tb_ ͠W͕,xO:r+,Lzp1mBow+l_U݋,D!$9V@G/~,Ʌ)V Rb.l0 ̆p,Unt|Vms섮\ TβbȂJF4[ϭI;<:U'82۳#ź]u)ei7Lkt'+;SNl}J$ 0>,3PENE~(/^rƌhVMaMԕLp&2a8oOިfy CVF Wғ". XݎtC ؗX0F}\eHF!pT%,~j(0:N9(}J@ h@}퐺J鐮l㢧cyS|cQMK K9D$r^'j-5Dّވ )LHJJi &Ԅ>UHȌ ]"6DAl\gLN!hMJ7"ep{o:I1hgma-kUfYa>H0P33b|{?}t^oOj)sv x)-Tw~'/ttIp:A"oCI鋇vUS.DUfYCs^[鹽KcuZaA\`>0Js_iLKo(2"oc\$ wFv<gc9 c3Zg3BgE\1RƹCB\z >/Z32 UbpVk}nNbUc \Z" ^F3i^\-U 2m}b޼jEco׼Fu `(cYz$xu)s.m ܔI RP.|~AN2)X \|,ۜȑlORuf}h `_Uoft>ZyzGnXGakpӡ-Gw{WSi}ӭ>`f*:. %x5xfOQ-˖pp0ogFcfI=^q}ilKikWȜd>w yUk2צ Ќ5ז~|z[z?oɛ%m M{b!"n*%>{صr| aʔ*d:15篣 JLg9$oY[EA{hOBeqN2("+SqGc(Zm !x+!ha|+D{%K._Z?⤤{bQPm@B> 3):FimmE>[76(Y;(~I;*xCREYb'gaQ10ëY+KhtufR)^(U F\;LiIT4p2K.4JC(,dZc#g5`UoȍϴB2"Y(%!Z( &̹j59 nqXCx<&ݴ^uaJδ ilHVB"-_".V,7Cs|%- @UʄÌf2L& kM7"x1Df"R4ÌhȾs2<갭>KҦx9-_5())^|Thkd:5 ʭ4Fdqfgqtgo=&Dp"WH?PK_pYA aIP `d9"@ҌOZ6?s0RBdZףulOj!WۄY%B oE1ՁH-:p"O$J==b$5=bf|,Ók9*R D%kRgoᯖі%!@F{XxO,6 lutd.7L|%HGI\}i +;"#3W9 !qH38lt"xS Rvbe$3[>qx{J*)Ae( !! ;r9@?|d-&3<4vWBsgꨒӐKj枏sZl~ʞ#NY{R"z2S֘> qc;)51@IP",]|M`BJV[ﰌ֎\TwkC37:"#2}ZD2 @tH\a=t_k쌘&b;4O}BA$pzA)E{,. vϰ̞g?Wދ%g" EBM"8jFWeBE'%Z=3Rˎ7N-1E{̃ίFOjNIfHsx#3~Ͻ)`,7ۑRxw u1bAl=(D i7d17u 9IZ?m_C#FJ#A'Pdl}Xg4$I G,j>DI~/G *^Gz~0 (n%"AFc+[%-8S@dƧ>w$>(]f)%R`T䬡ex[z5<;R wf*&o d.˜\hyhiq7(|m˺Cp(f5By?z3pסUyc")"P=jd4_!hRgxXޭ :NPID &-Ű T@`@dRaVZ\,|~ۿ΍ƙX:@ yD ,5.N@ \@0 ͶTA흝DkaprN#XX*v3%#Yg{\!xb|9G $0 l^)Q{,v ezV1-'<PcI^wPr: x(0xf΋2 Jֆ`@@mfv[l_+!8Ht`61Knď`$嶖EPP1pl=ٳ**l 0jpF,逌 z I$dLgIf 'JHīc_٩JJF^(I|dylERxXҼ4tAzʛ"kW={wnFv-7|7u(򕟚pѼ¼Ba gx} MR\8"XOmi4V>!Y1=02yP4x .z=O"ڹ/\" ڥ Lڌ_@*Vw T8e%H'ohťROk:#2~\'[}h)}`Yc,|,QM^RpB-|HU\ovD8*]~\DX|#̞)-&҉4@:S)3o~N#tp9,dxqAsUT{J$t~܅ξ+XaLGQ?C$џУ]ykt8l )n]WGwQgXH"4MP0^T!uxExl%l\ 2>I|Gxw'VQ#iIdðAY!'E.Pi彣Ʈ/i&BE|V5\ajf.(E ؗ_mQミ*uB!5c3Asǎ7@VLшSGlTQMZ}'m~n*ȯE+3gҬ}T@rw;I c!#'<1"Fλ6x@UEpږD>:-O2[Guۜ{'ߚ}kDB)r~tb!#V$1_r 0zW܂E|Ye=劉IE3AA t%5D¶ r ^KGd*Cƴ%& eEfIW\".AE~Ef2"lMк Hھg9'B6e{],MP5 yY#[QGhB&wAV{}R $YcW1!?yiSfml4$8&xw%oLeŏ~q }q M!ޖLןk'q*ĿW>ܞ.ZKV7^E>]Wž$*E2Ҽz[bL:TWǤ‹2 3HR.UżӃi?GbDN$"50k AS$y@@$:D2VX7ˊd0=t4F_xزdэRvW +z!5Fŵ$1A0*.Hca 4 yUWs!ݾ/% ] 6(:B V2_c$Dfa"}@793H4÷iHH7%&[AeهY R8:!νXzٌԹ^w.+P1 :8YZ# !r @@Ak}+zћLi#x#ZUuBТpD QH0lj8I81OLPuVHy3 AdbV 2WDzD1L#; -=Q5u((,-]xk[OGZ:TZ/=7yh.r hF 4NWv}ovGtklI$ɄVwPJC0-#gv >o($'fIS}N E8 g869ؘarj_:\;PALia(': ҁif6li2-iZb+.) ,KSO,J[ 2K1e &Z`%!KxuBs$3 P0`&) ٹS,: FCPJވ bb!.G:Rë0b!NFtDǠj@YXܕ=딠7s_Hz-&2N@{G8C( wN sFPU{gps gw!cxV{!\i4lYsL55* g٣JL{ۢZVD6wSk"%dxJCAӚi#]`F*(.uJ@0-9)XZAcl_N[5ׯrdBFhb! `u tL5~/rdGA8<؛Oo2GնOV\[SuAӼė_5ޒ/$D;^H&#uI+5K,]E{zmA_Zvةswֳ{//Bfvld`¶hooJ^˾gw^1~ʏ&JîtW{dt沝1;hʇ 1^N+0 k6qa'{<3 O~Ǯsof6'?g i_2L{ !tAO$vUm!G)K"$20fy3g^q}5vD[#>/pQ^K*/QE-K;.Lq! Pi J.AXЄT'~wn q;M7c>H0,M(kyj)<ᶜ.ņԹ9@dB Q1XGiQF  lӼV PJHajRTf}I!:ShQ0ɧ`;K!mDz6š~L ~RرeA%+kgx9γH~8y'K]l !!-@I WD{քqCg=9R>sjK ĽNu:B'BIj-M3hr*TW SO:GQF<.Xccl`Ơ͗ZZ`85:"c!6I1q4C9R!3#YbizҭN&JbgfszXDŐ_{a™vaֱz~FEI~~ DCIJ!*ixK:+Vn:;VԫtP/'ď~S2vo㊘M1Es%HiYc#+L</AM<rk sxt3 Ḟ)#*8l'R))&YZhk@@Z@N܁7Gsfz*֪_^q=57% q2vL*31+Is%r0`"|r.bE#[TZ3gCFE8")@I$EDCC!9ACȊ,g !^<Zk90.?)8H:}xBȧ}#`щOIQ(=Jr) KċπW~2 7z\FL< A*1d ,liS TI /Nx[YdBeSɦ+kdE#rǤMPYW۴zAXmt 'ܡ"H8˶@HTo[>>AsB] `C'CSpnM1fH?8*@OaJbf>9e8y8A@ȁ #ƴ=C} r'OB%F8NR!LI@n ixt+*(\!{įͿ$Ғ,Rac8bCCH2#.A"16r4˽#\@JRdP+qUXV1(qb!q+f1Q[uRI ?;a9U&a:@ # -nFd=7K垎8;ʟ7/ORU[VˠQ~pƞ~ٲhqp̜fAdzK_CtNy|rE|˛ 823݆DS!kh})D'%Rb)IϿ6ZHg|7.-< ur̅z\x\sU_xAcْI2esfPٺe[bv<@9`>?7aY}'/D?,9zN#}A7n^ms2RSwb~;ƒMQ`/@m [<,-"Kƺρxi+}4B.)NԥT,.4%0Ul@x7-~*5Z1iׇ$Z[{, /,4~o-2bmkloh ъD hx{@> !udu/PE+s>z=uXi!TiebtXd++ӯ9:RNԆX;yA*E7&~ͩ/ 2:I sAAm;twF'8U(E" ܍zN.T}3(Y(ʂK#DEҁ绑tBGwTMO ) +$D"bܼO<җ%Szr?}Xvq詠o¨b΃xPF*Z-DLcŒ̅B$hR &́Amӷ [o]DԸ N脥,X8R1H:6AXړ(Q0Kk fPP8~_"HM ҝ|[qWq) Z@ު ȓ)jׂz`$"T֋hlЄ $Ͳɑ&|FUs1hÄTVsuʃTת3徢C갱Bba?WNYTήF<.3„(3@i(A5?ۉy{ ' [F Y wchT` eH\g(R2b1 gJ5 2sz1 I=, $&=X,Qm/F4h;[T QZ'ĸ0Gd7η˨`nK gg*ݣY DSxK}=#k8Xry}&an*4=|DKIK|C3k=G^1Nc8h#㧱_M'?xmG  dDac鏟@n}h7˻zf9 E͛wmU%:^ 5Kӟu<5pK]֧Ņ̯e(>8c[;ә{snRCxL2ҔKz&%tEp!iXN: YL_@n-/M{X{oÃܝ4 Dn*ŭ˛7='v@Y< s<"C>T?:xlrLϼkh(94?Pu|*D 1 vv[[πS?Hm+54$] 6'ݔ|u)M}KnqWB1%h|S;Fug mwCBiw^k͹tOO{鴗eӶϯؿΣ5J D%"8@08(Hc-5S="B o$>W:vAuiS,$9L'AZXZ?p "&D( G0>?hJk\֗B8Z.ٻƱGY^5Q.WtF?Ym$~,ARW|<Pť-K[ 5Qx@z]go0³SZc1H^8CaKX{ȟu_L/O]_ol%o ,uruht` FV AJTˆ64 N8GXF@fFA (К}Pv0LQpHزo!c+" Qr@fL|'Bb102X %KFXE~ZʘRDp HmR :'~ eH!2 ɔ;l W00Bi 7pڤ#뺍~^("Á*f3 ;3♄<(co/ҷ?K"qZS ʧnt6nR&Ui造pYID u$8"@77BF̣| \dnk)n[΢>i"euשui !4;6Rg v2D:e[ac@ HH|RxeIFdWt 76K,VtnLr4Lەw`#;fϖDҰU۲A_"v>(册g|Y&FvD"AwUqj5(>GuR3h_v,AYX I(`$BP]b{p?GC+qG" K(d^1cf~CMgWgZU]3bKp.]!6bɭݥiy mxxQi9CTG?ZrgDiXߑeA Br"Cٻ}_>&mnF!kD$S A-[o4+v7CH"kSڔ՟g!釿duzxuȩ! ʇp5K~BQz蛑ĿsQ~+<5GW IzոI{8P"mӿ{Qe)XдFiי?Ќ\>{S|Iyu͎/]h"(@H`"QC1{Wk4Y|(AXseSRil@*P0FrJ9dיJf^m% pBC) h>+Fm-q [thP)Pa zȴ{S~Z1bK *01"J[C;+%d ۃbhjp23fG x ^4F:-֒uci{iOl mXI/; jߢW?o?^fmH uAGQSo4.4!! ]|ʐMek;VqmH\u sdnCǴvׁ1,oMH l yb{;Q $e$$ϟRھ5~oODhyY~mvN=z F6&(.)lFb*TBMTFQ)7M >':8rsLBHj!N.UM#U4n(*t5nVy38 `GEk$yEA;3MSuJh X*4!(X/ڂS)NYA 5O+r" ,`"9RWL$ di4<&sivZ-=u#6&T''O~^JBOsk]5> i=HLGW'1Q 0]P~Ghz`]<5ha9U2-M52B#{H+K?fe֢ZӼh.D\` H#1ovB4| w*}IaVB/YG ѐP ЪhTXLb"*q#Gx э L0Œc(Ƭ 8f " D:̀?3V,rv8#Usυ/þs'zHĬJC^ػD qypޕ]9]^-~/0"/A~ P&=W:< Sɯk7#T)G{֦f&dt`"? ܗ{F6kjX}9?܈,^;-ۉq\P] tgAmv:zGڋ!ڼI$שMhuuZ_@ yzijxftR~Ma {ڭ;KDy1r%e7*6u%pCg5 N=zh6.]?d\9PJO7jx?U&fmV?;Ou [M%;K`y:TkNHA]@u _x_$1C+F=7:aTuXalZ2 ;y|X?%y^Ƶ%2&L b5Xh łG *hr 5}Hb<,lq. *3?fh!Eez_8|}OvtF~+5qƤ1#Ji J| "9H!-D jiR3H`,k@ޛ"J)KXK(CJHz|1KܮEc[*}&0 ֡G 2w= l̙B:ww3ݧ{v]y1[ )9d(YVZBB ÎXm2>[^\~b@:_H*}ɶܘ}ʮͯA .&  ilQ@t0E:c)Q% )1L L` AB6V*:;ยolM9*Cy`@h8N"BK$K iCBz{Vʆ:lLL\y$ OBٌ pr3|=]6kRoQy%T*,Dۺ*.lτDsq3R]ӰC{Oi'hL q:|(V] 03B.|?rmcN1˩0A)X%)%?Fy_D$ZOmx KW==[|UIT,u׮$mJtS$ r`l=f1<,?1VR @߭/_B]0cy}wneu+/}ꮛCr1[3{Oh}={1,D>ؗi\$} N\Ǔt%[Ju~v9K&~⨻FWFG嗎̸#0 0yNġ&eot EwFYf~Q L*΂*-jCm-A'n `oQ̫wxCV6FzE$<R.lz,KɟՅ@q+I>Wq7s tB PAj%#`$HBD$S$ehV&R(VgQed8^7|'ú;͘, VblSޝB7AH. m K{._\0wG` THfl&%@x3/]mx8?sJ?[Dafar'] 8_&jqK+e/(Lz=c+wcӣ[ܲ2O/@P;P eb *7z쇲2+ħQ[7%W6񛭦<$*G$= ]Ţ( '5K I5EB]p%BH @v Ms){; "^%;#TbF;0CyBtK #F"dr{Mb9u7He.01lU68 [@ 6+26ATH)Nm9RYpWIDY Ɏj2ʬ.@ d-Ѧ:,;A$t *j3T 5R$#-#cB%< H,0a0)9퉽7flԨ= ?X#?ѩMbdTNWMtZ؈ɀIBG,U", oSLG=1ƀ5(wj#P5aH TBD#a lj[ԝ^fym%E# Fs% 1{A(UWxFPVqMЀíd7wWvߜ *F[k'W[@ 4`J an31́,B,QصH#ä˛il?w"?mj'w Fmw5ďPvz[n}Zxz$*>rֻhT=,AĈOyk𭶫6n\w]Y=3͊<a E8EBx(=X0!\a}9 R: M + 2EɤD2!XBHĴsNC-!yl{Zؔj삭Y5{+ #߸8/ }OOz}Wqq wLV\ϧ3ufd?,}ȟ{ĞXiZ :;/΀\.9\S lYr vO#O.T;Dtz7^2xg[[F-Wv{2{N4I+WVTt":tPb }:5|-,FȰ!57ssx.hԕ[ܸz݈?s 3 -Y ((X!%v5HX,%4"^D"Dik' e34ъEVHJYNe}jθd@iq' `A%`(bcO ß.L,Oj2.laIϋBa@:m3%!TߚGXkQ]@QBu?áF2iLRctHq[MA ;|9-έ?Oi>sL+ ҷz98g0#)|m_̴Gb|y{ݵ /];,;_-M8H7ȀRv>Z9F{sf3LE׮eY2u'\~pEs6WcӿZT燦~[uǿOyOKer"R4ʒD#\;ld>>W>m4Dc@Vg54\hgYcyBBe 0mawxb݁K,ghB& 9e3nz'Jb#4OͻMw;oOڐ!)QCI6/N4IE4W_6lh8n#!V|m4fy}zu/ C4yB,5SD= (CS`eɓ:U䯉:/`ԑ/3}RL:*8YIQ -IX.O@aT@LTQM7psU;h3%lj J?,P 4M"`3Pj:F"Vkgpg2Y%p*.au?Нq谦wNl9!q6.0Aq%ԷB 8I2V2rNQzC׳Ӄ77-VgX,}6A6K )5^؋=#-l(ڂ\$H$6v/e|zF :HM7|% 8 ]/MBzSw=يd+LSO?_>?A{?!&7->ZK{q~>=u]ig7Nc6QWO /sw\C:8\{z}s". 4$2"Tc:$ƉL]x*x!# RG&` )YU DYH#M UKrzc9qcӧ )(4[^XͯN>ja@|$2ЖvA6ݕ^\z,ĺk,`qZk 矋g!>fT^^sS7|[<=_Uhԉģgn=S- !֩=Y{r<'|{S.P6ҧ'սQuW&AIv@[=#W n <"v2m?5 O59_ifhbQy&c"BAX1eT/ n2\@O3Ϩd>űMnRKW)L`Rcb0Ȋ}4~|x%<2*QK..ZУ;3nWj-ݏ_^p=ع<D(r,˪&-2OL 2Wub]%ݢJsݷN1F:XDqn jG O_ʊw^ܾ>?Fe Qk?Xkܭ_~~]amW*s\gAs=fM_! ~dȾ \ZbT VW;mҐG{.oyx'<_#URWndNY2;1Hs>iZ.o79юlk1dY"6{oTmm=M~M.q绌]-Za2h%jvN{=}Cw41J']KBȋ+K-y^>JmӁu/(E2I\ypQfL#'"TF\ K2 s ~ɰ{<]SE!%w4Q]4&-9d᠕ZF 9}dM1@1y.V߭^!HD0[H@aχ5^dg$iЉ@nrJheWjB&  YӼ$}_w?j &o̜Ȩ00T [}J[ߎ 3[Ӌ&j#aJ0ڑF)~$*n @f.]v8<t9amgL "Lݬ1H K8'f(=|9t[>Wn~EB6gP+["1W~t;~5ZȷG}C7s?wϸ5?|}Ɛ:hui+`C2v%`} * Į~8*K3.C0J0ɵHّkI("$<4 M|{>xrЪ 4XR`n#5Nn.ӳFE'YS3h،SH!--4&k^gęmrA:AE+C $^{c_MKW>ݮ[hQgcQp6Aq曞?g|(P4Q[|ZFVF8/ PޕiIݝ@㖈p {$D6\Vrn-_3բ9Dwh,'gO8FsMϣCEb^z'J@Z7>r5N;q3"M֮+Ƌ)߃7Ux4;_dv޲r3Q矯V^O &'{> {_uX_٠Jo;wM[,&)mraGйF5t_sc֬-g}!)ӱt$lI<m_nkK#ip2bKQyL+.hx0)GMH[Wwr9cBzTf]q %O4IuJ+ {ZMCJ=g1Ui>ΑET>f$.{w0Ck:+V Md-_ݰ-reJq@[0M\팯b+eryq3uQZ69:2(jK*{[ࣱO^ޓo6~يZ3Ր! l4^ Jj$r>{S"a3X'TmyIi!b'9@*i=RudSFfJ/gI(/u[TՎ 'agz@CP<ҭJ4J66h8$ /^8C$XP%ZlG.=J02  |FBkj3Hhqt-}AΧ%H"d+2uU[O0wneHQm-U77rd3Nvgܗ̓t>Ҵ#ys?9[jHz'8Cd`T2"Q3sg[F ف%=Ĕ%u7 Dar <U?8"MxFut;0CPjNh,U˺ơ˦*^5L͖ TJj[UZ:п'sܹq2 1Œ1Aز/ۼz ~7p`@}>7B98,sjp% 0s>Y+{smNp i?V76CK.ysI7z$:2q)"Pe{ ,4 TFv׮)Шq f7{?ȒNQӗ~=Y/~+*COv{Oކ|+:Eqzu3 vP׺F8/ '6 "'(sb UPa 5>zOg?ܲw9Y7b7GMoU=\RR7{ni?[1 NF %%)ԊY N>!6)Vo45p^bTI?YiL|:nɗƤה#/VvXR̡fȀ6Y ]j@b\)sfgm `3NDPxI 4aԞ ,#ap""A['NTS+)  !_'mþ氅+hp8Kz[MsZQ4D >T|OzIƥ@`"[Ik*O3,igjkx9f1zJPL@J^)~ dwi- A@} AN>]{>H%Ax^l0^ɷ NPP5k:p}n{480f+ )L`!} EcdB=>_H0"({!pBP0B%:˻2DLCP+z $o{~ռvokǶՐj<S~c5㔱ա-v!$8ʔ̞=*Luk,["9xPd| }[`V)a.| Q`uo X}JKuZ kOZkWE3pRV0G7N՚ :Fl?ʻ5tE4,ѸXK\K_C_O\׋7dI\iA6ߐԽw&!vEWe?i{úV5VQ!m;?}c_̆>ëGiR ![WA8S]睂p;KwW,Iy>!@aZz/Mhأ_/ l{E@#zŊfz!_37RgBx^~{cO"&=1nN[nopXv7@?)AeD_Ǐ>W"M= 1lJwXlmg!6KAVL>!ybW`<6:DjoEwF5X h^A 7&};QtECHKH-.FAYt'5^Ҧyty0 V0[@AFLyk4лj.$P ̙@]0-{H}G5=%g~Zi ](!ҁCAUwy4? (}1- NC}^zʫג9wZҘtϨ<79By= Yd tߺfFfCn- DFBVF28EE*m)᭓sEvw)Қbb8V95V՜v}ð٧S*Pvy-}ܔ Q NEf6% Ңpx~|4E GSx/O$H`L`8>Ҙ$]4 FDa4+Qnu Z"Ed- kZP%jN5`\I88ű9Z{.݇rI|0{ &$@73hN&{w,1NXԙwv/JuzSZ $ַ;1[I4Ӕ5瓌Txhqp"S  %RXohZv:QUAP>2V؏5N'lȥb>#se'd 1*;3"wifUjy(H m?W"! a#7m `|4sO4E0b8%W_%ߛ9lPqؐTp^-jDZZlBEC;e6hsID2xĿaM o7}ɠ7oIw( 5W~yӗ0^V?|毾{|teNNɱe@m-t*Tzؗ*AeYzTv dGȟ}ϓ8f?'_~:pedi] '~k]9Y5vaǯ/s5"[LÖ.W"oLo=:[֟F3a^@DV,]},P8ĝAߝ+V}MgPK|%W&h ,᷷_^m聗*A6mmԬ;~ww?_eUiePyWM$51v6nV]d$(c%Ȫj}PL%6&/$![q5%D!z#(J í <|>{7ܺsiAˍy-*\5"}<$݂ܻ10ymM<5p]^dMKJ4܍ަ&lӽՖ*y[Z-@Qwb$jk Ҍm?aNEviV>ZA}{8H$JWY}rDzX'zW6=natpːO=;^ؓu$>p?ZPz+!@ U]gOx$N8앢#b)|SW[XfM JgЌeĺ'GK :!pj;`h(#{&dG趪-_ bC"qsָ(ф/JЋtȳrHC6EB*ό9HDӐd < c ǥH8 ,kKavpP˽DzQ*#K2$I#Ú1ٺJSX І)5K kBÖg6E$aH֍l@rX!ʉDb(@-p*hB(4NVOa݃fx[.׬yo3y6FD fV/ިnV?*/OlC5K\&2,H!߻>?m+m8f,'}ܝ^|AɠXLZS`$Iy}uجI(ܬ[5x{fK1U2; L\7/!"27M>47co @\QQ'=q2C@ (WEڌvgWB8H%݀5@ U]>x .c_A4'#uCEORc@xH*jZjֶ*兦7V8VlBYvGNhףmrI4C:@ߝ㛪SRHޖܥNt\߫?lݼqg_B2xoVS-NJ; l|"MHVsN>:z*=_ _?oF%h6I?ioޯ3M!&q'H0*5}1>~"knT* yҡe]kXM9w5&u*j=16q?90͕ϟk0jIټG.wsQ{Ճ+߽)6{ _@wy2RwYfNU{ҺmDJ)zO zWڨ7_>Hh%wߋ9 ^52m;[+azvej٘" Olct(1c|{Z4Z$y8 !5u(]W?yas-C=C̢?tźdMC3m?k;`=4P3=>ǧ}ޕa'&ON__v)cƴ]ܕ3 T/4 3ψkP P!`GdR^/?EL{'M/ZMZ+6\}?q(h5;kIik]Ĺٯ>Ů =&1}0J-X}! ||ffa,+^;>LQ7<`-{@ :3ˑ3~ gPYV{R:wqriE0.p+c?,Djcu^2 -ڼ ٻ}ZbD)WڹD^"| x2`!XW 9FK{gwP JM7-dl֌y`#Ğףxt/c!=^(a\?H_q H;q2lkH|̓fmd_2nIo,UʴŹO*N(Ewb)f 6/_&>@jlX \T$]4H@8iĿҊQ0MLIm>( @M |X'pvF R|ݑvyaGVs{M 7inO1*rԝf:l7)|F#h;`ڜlm [OjzO+1ǝX6*pB-/V jF{e [R` /gMURV~f~o킦βT8JTnxkCksv&J Ηd[n7|i{^Ta{MXNUEYEkzT-S>خ֞g77WR:[}#seY%&Gsծsv8/>A S.={ŅyB5WgOdz}e)G$H Q<|n>{;裪hBB:wغ">gpW[{SU,o@obWvt~: B "4-ۊk+M[N r5GsT$,VYp;yT;u:0$4)x]9><νs}]N{'1uOyaHϩovdYv/cӑ<>ryO@ O_|Gi Zjezא&c9ijU  ;=uo'r }?gݽwUvL'4`2+T\B+9o''S%Ǿ=g ĐH/e i`ο~Ϊ}͢Q*Ϯ^g넏XϏrٟ[:]SCqWW3m|ݞx--}#ڸiVmV'D>yc6xʍmS=C,:߻9w>9BtAF'<= BP,Brη {=9#'?_PrEUntF{Ud8 с+!nFy UI&qhv*%k<eiiV3,wOGTߙ';79P}|xht![QR,ͬڃ4i3PT#*]kW wa ہ.q_ږnq{.gH'o˟k:>+-vjro ti[ũB. v|~%'/\3lq# NRc_sf@qȪXQV͛4M:\=?|6)8P'NKz|#o=lhRldIhA:b*Ȫ/bm(KSz.غ=lrRO7xb^}zĴ ߚ4bjԨ8IYޚDg}2vxJ)])0; ĝ9ù{UC'tѨ 盋(hDfPVSley wG.-Y2tO<λ/ jw(0}^Zr6xOR<GEH."S^,_F1Q/}!ž !~lz{B_ k;{>{?x&nÖeTd %?'= (bf# fYw9[(&;RSLF&I[5 2#@8$eL"0Aȁ k~*i\&4^bWd4j#C?f`t7 "xL S!Gd>Gvz:8@DV]i춨H3.6vMOqOv~EXh/vwD-5V M^I|8.2\i퉻;Llgٶvao71!IdQ m|aӓK;-B-0MzU@'H e- f\N-'C޽@}&TC(۔-THTƨc쬸@A.jj8n_&5]֝SxK uUd9ޛ -SLPj[`|Eς6_jvl>sw2Gt+ؽ}[XR!-i6ol,"\x:*~W'~˺߉/Fk*zC#**ZAl>ZUW:>&vL1inULDu=q*pf/.jia雞|Sfxܮʙ .+ ApjL݃w{};W{Οvr|wV~.9zUs;9UzOJǽN=vmqWNdqێO7VŌ~XӘɊck.k/>3iv7eJgLPG5͇wVM׽6>Q;!^*Ke}ІJIxAOz.]x_OI0zeʑTIaq a3a?ԦZ[3Qvƶ|w; A%X;F!i~MM!ȳpOeGZ/m=n !4NWOYQ3Q/M%b4>)O$=<^HWrTj E]|gɁ?%1AXTTq#*F5\b.enWCe)($ף=Sn˛o,KiU BD,I/,z9 lgfaO`ư5'O\YB *eTI' {U8 \1=ѩ{l]DW#5i߁#y߿e_*VFݭ/]su-OiBPX#]t9,n-?+Rɴ m~c7j,8~պNLuԀҘ򬒗ڏ?сȍnX. #{)V oܫx=*/uG]nvް]݈wi".pʄ'y?[$'s]z cE5]i #jx3Tً?~V-vzz,,9⦏&BYK%^;ÕF؛>6ON{ P8\FƬtz"{КiZ =/GcAoW?T:gnăW_ 8xy"m<ר;ECRK/>ImM#S E40D-Id2/ hwH\m U)h %9 }Gx`$'Qdűk.yw6d 7@͒t_t|G2o)}3I%y, LV\p^;-@|V_x `ĞH[܇~ԛ-Cf]Bu>sm;`dLoތd1, JPa@FyVSfAӷ:e:&|%9 !tWΜP:Z[AxdNq-|r Ȟ6Q-} yC7TT/Ha|?_T@j&AhjYE2;~l9QpEu% 95 ׾I MϨi&7~nu?'Okn&GY\3iQ,yTEv?7ݭfm'qxtҎT/yzmXkVT7v%_ZNxrjp~aЍ7 `kCӵ;޵?7;2ZzM JOjC茶$St2Ko?[cr~RZA5< âKf&qIJf "-|?Ʒ?pqgdŢpR~){Õwa~$yP޴cs|-=PdbYÏ~^t@5M^AT;b3S>vy^}͗.RϾ8':^SmXfRR[y 16ltOFW,~YGߋo}̟R\<$t]=m֜L|k³ =vUJ`7_ -!avD6+i73NOR8Qc ~Ǥa[EMcWF@V(ly0ilƓ4 918X] Xg#=VR%.o-R|O=|x܎>5D\r ?ue++ד$!R{ o0rƇɞ[O4վsʧ{f ʶ$0*" "RFA ˧^\:Wl/\^Wmxp%ۥGME33Qj ~"qC CWE'WS0cg\iT@lMb|4#{\Q3Yor@o/l|Mm.>_ ^y ~/.0` C%m"0XP9b>xh 1⡭1U vy/&[GJ6 TB;I1r17SJ@f ?yQ0E̎ ;t [&0 j]5iMGTj@4?0t n+0BH*wlMYM87KRe*ÀUI:'OYnn.?>ӻG*\I^2yfEkKFFWaLG990I8[%QB*]IC:D]7OX&PvؾCDO ʉMTܰqT/չO}ݷm‰ͪQ[id=xB޾~Fq:-(]ٙ^ߺ<8.W˱nOmW p6*T:Yz7ʧotojCA,P%U d-z^{&?7%uW!=i#Džpg镤6Մ#3A_q 7h+"] CK2XlOPqKٓ\ݙdƻTLE4Arbus~?ʄZ$h#6mF$^SCWKib _8wFMAјt4[m/z pPeŪ6&=HYݍoA#Otw{_ԇ0&%Yvwb6oO'+x1e~o] /w`uBlNKV\L:/g\o1s˷ US/% >ڢY8(":H8-<(Cki{cMAkhU`GI=M&wl A/;=Rd.?''37.$nli3e!U6 /䬜~slS!GGP7f;BQ$$j0UϖwiiIw/{ܛ]u7iw""֝i׵2P5Ǚ獀:}FE TB VniE rU+I P0A%4$Zh T3\ L([vf&N-lYd׳kf;prqIK\/3:Ȍi{aXGi2@,7`?&bpRj!h tX݉f^o=UF &a mjh |{p ~kңDDDhhhN@+Ivp'.82y7h, *W>G|>x%V!ʒ{>bw>< I ,BryL9ld^N'0I!FHUP R=H ݿ`}k?LY, ms_}Ds%ͿAbT `HFN2SU[pFAXjV .,Id43ŒJ@@ B<p@t 4 >{z[-PS{ ?!$ DH7 |w-s~, W?| cD@ ^ ">"ghAh KEcj<"> a qR[K#SIͦ`4 hЂǧ{j\& Y2֯0' "@7Ԏ%@g `jPxV0PuIjE( F޵"?eU)bq HFAgSVVMa ,'wG<#8;DrLK RzaCYe|/'^ 2sDoHiNXA-NeǛv ^SXN _GTz闎~Ge:\9RJȇ )AH!xQI}.$߆S*mnt*pl(,~[)~nx^-mfj2LYDu+0̞r꫹<|ګy/_ŽAk<صRaAk׾x'}'RǛ]4Ŕ"bw*IK' &1G-5)"9RmRyJe @ę3ߋS0gQKſ/薠S }2h_rBVΎ1ͱT"eۦou7vȪg+”QխX]캙)Os1Md e&}$!8T/zJ4qTTQL.4L8do|a䜇.s*-W\~=:ݻp^ww7i 13yϸb 릾]3aP܏o_-[*eF#"h]D 4oA=U3k@ 8cbCI}t=`8>啡>#oh#e3F?ʶl?uvl&46׺ ˇ+|ޱ9*Έ qۮųm__>iʑwPzstwہ.2S R;,aDP]?oy!QtCEJ7_y: jLYV"], 5a(mAa!dkKA79C5h&Xa\5` ^J9CE:FWm'kge*[SG;GW?F &6$Z]!-4\Px&F]jD?-%fߒh1ؔjŤ&Ƭ1%Krwܝn*L]cewP?o ʄzjugZ߂Kĩj-`=NボE=mXR#$dKD<42𙁶ة .$ϛfۯ}MϧD~agZɩ_ MňE9^ Ѭ8uͭ&㜞QvVY'e$-$h1OB@1cȐv[QN[BMnF1P BkZL!LCJ%a0 %>wM?13g~ώbd aTwaE^ٚ3hUyǠV`iͥ+/}:<3㝡FWp(Hv n_Nޚzu~E_A¿܏Z$8&4ЊÌ,ԑh!06r db5kf?k!1-{l|$ESx e`QJp .}3v*!ze~itJ}cIf "p ,"jtEA4JPGq'?}Huf \^g‘4)~չ@i )_9ً4=,ErG !&û#ҤW<44.gh^xIe0/5IvU;ä6I@΁.|N΄!6p#hI*$jyRFFk/zymF)ud"  z_+Bػ DQ5Ҫ(7?{tCFLXpg=^$qTaV.L9C# )$H$x(K,m?!?>=AQH0J:?|}KA߾ЅAg?|:vه$t{|o y˂ҽW\XzuɊ4fdd8Nwb9!x:j-TkZl$E >a6o?Wj>5Ҋ~o7c @ [ZwbTe.LkNd}e^'Ln?τV!0H`H^I'יXAyZ)uS?{j/UGU>-GB'%ڌm S2~9mѶ]YsEo~|y%7|lJam%KG[L+B1|+<-pESoҧPm#Y%L*"挋:jԅOzx78 N .6lR !C^O^'6vO6yzΞXz{f5bj`B;os%6ύOsj[kg~t>bAtS%i1PgO$" ݋R\4)쨍87YZCNS u]&ث xVK@7X\e}PTô,xۤY@.sAsP9<oZ_JuUlߩLwi mw4: j^/"̠WV,AJ -> Ȧy+%m IsW@0 Y IVNfyk5͐a K%-IV ꂴNh8 ޲E+@*ee JЩ ,ЯےB&e|{'n2Us6< p@%gn4DPu=^Z0F*x:`( ڃ!RkY>zpu}l3v3FH04+vbB4$H@ Rj` Fb8>ذMB 6"1BTr&% $ыekjn~82lWt(Źn o>)VMic?qIb&GuzO?WD( ŀ(: Չz`MNga'YI&Ʉ r=<"i!MB8ӭ`E`DH>ۉ0c՛^,ChsPH}*:Ճ$MyODT,dh1Ŏ+iߚsw`8mNJQWueYɄ@0 cTZW u}u ~vQS-׮e "NOk3^+^Y(4ϳ\& f)jbP";Iœ+c o%X01pgK91B؆`]T&2 7fuEWfV~ {o**>#Yi=W]JdLٌٮkƁ;Lnv}ef> 6AD]}|y뽯?sҚ#zw_QeX_f멽ZAT-i]?)~iW;4صZ0u4xfܭ棵N BM x~kǶwjVPQe+5(2Eqc 1fn8vj9QioxmMcaBi{a< r7ʓL^M Y0#T M& 1aj̉hS$ >0*ފ+,hI-X|n/[G,-kþBq5s[Y(t%tcK<ƛCG<5QvXMbHxH1tG [ǟqHHnqF@.&qm!!!lmĸ+ snҘ <^8·dD{sZJ)cc8Z5"Os:|}7_~{`%¦Km]jَ$IwM7I`@`RW(EO>"T2A{ܣ/2 YiD!7~-|Tx+W}|_sb ENIU!p0@R1*&)K_9EB&VA@W+%s!MJp!,)݋{ں0d單X,mRm̡ Vt- 2Xs/"FP\@=%ΐ7 4)7Z CO)lpkܓ99Ѯ0Yp@0Bgj}Y[߻p?Eݞ;]Sy&r ڮn#;8 "zV-z^0QSN~!!{![0'Ju&B&a=L`>%ƘJFn{B?$ H2H>w=*4ͽ5-;$V[GSS0ܐy if!(qo]_+> 6~cI{$ nu+ OHj]O8ݘz!,xt/_t.tﶝ>-|ןW@ HlN)k,b4G?eǻ\%wb*'.5[`aoA*zRO\gcɣdV[}[}Xt k&Te@Pfc>}'}{Uթ]^Tm@!hFB2l,1):(:V_xG "&OنSvFn._1ɒ+H)M ~pWFaw g24#8i[w"z]\JB3͋D [T)X(UYAQh5e<rFþXI R%";'d%>eYld+Bd )~}~zcZf:ߖt Q/V6FWnڌ始[ݨh~kB6>[qhD-ݷ> &8?xAm<*l ײ6M"[dD$(MAd|s["/PFBKwd:I_,/ {i2ug2PFb Ol~CdS% 雺 ZkhOEL|L+v~ /8|y |7owh-CnS}[/]n| GH?)*ȵ h!d:f.Rr @@ E4Z"(:-50~&ଁt#@d/ʬ^,bqCL)LG\*֠xr+mi=6| Vh Z՞K<0Xb_4D]+&g6='Ic,c$9QT `Ҫ(~92E=ABkk4jAG-B 6!J.vrmni=O=QJoK\:e`"CxȀySt@(/B6FXp&JZhgae 9Z6iymrÎD hȼ҂4aKfgIp_0\!*Z8 !AX`EXaP#a=**vA͎zls C2VY)$Hvz#}3?魚v6@/$*ӔyB)W 9R]` {?j3L9Zq>/I-lx 1 +205BtzTC xvɇ|$eqC]q|DD ! >opbtêgaƪ?/iK^)Z]eE}*hVӮ@Yˢ~\F-=Ys6"_>zK||d, y$HJVsЋR3鞐.b(uINx^.ȆY H=[ q wD1QroY@Je5W^7\zG 0f^mDUcG/l&L Z@"@Q A76Ljd`!dElx?*ò:_U?*(P-q|K+͎4"FRvcSfk#{Z5ZBESX=%&*,D08!CaERN3$:"G-l) l o1)!R#a8%E"x]w#e}!^764v 뜿tqg(k3Xힱ/uypЙ\WBUg+cHg|u̫p B3{FŹ3Fn_imxqa9jٶʁs_w@zAǨf鈰ݚDy+]nfFt+ Euav׆o!{'J@~eR)}'pU 4rX_a0*6}\umcFXSJgS#':rfmQ#{l .@}Z?@^L^ e8-!Bl65ҡ'Qv o=ܙJ򧴼tQqDpuT {=Bk`@^}r6HZ$F-t'YIOB|8@֭STɚ8ÔHS 0Gl\OtyDor9HJβ'( 84PVLuzʘS^ʜlef+bm4?fIl$ Z3J*4pU_!l+B`^ty̹_k/x' so|XpˇǦ3$(uH2  E^4>mCmo<i~>$b$rG;ΛhklYcc$U9Tޢl!aFTc% EUBgڃlP:T$lLB6@Z~xY}m[ONcx0b>F8.dY2 f{ fd@cŌ1F 5\a/pOAKceQ`L p@Z( cSx$$~2vnc`@ټy_`Etb┉疚.BJH ra5gRkĽMRIW}nI6J V*+),K3,)<[ _j`XjȫBL1X@^A GUR^z:CҚ @zM(oWQcJ*8:O<̡-";눞RV9YL1&یƦIRi @,f#)NN$Ev6!bO/HKESjѲ}`%axC`F'֯vߡ[U) p7< 8 6$RHҨC 84!6Kr.t-zr:B nRBHغKjOvg' 41 abꭐHʎz.YmR/# GLs!RĎ׊6C .\ p*b*-ؿK g+1%s , !NL/ 7A$YBs-,$8&2HN}1C9:K݂0xbDBPȲr^ !ZŐ$78'B.a>Rh^<w7m^?oT Uc+un;)ODx7~ijaƥ7{k~ᾳGq(gyj=g>CpD8A0pLXLi;9~T?eGPcJbYѢ@T6qRq`#"Aw, Cm7ϝ% ^MF t!jKQ=*=pKjY3G&NFOѧhw[I@:GBR1newio_zjrIW39=,;J|N\` G_Uqϝ{]{x_;+8nYRgKNT++hnvṯ"[#UʳzNUIVu>_`(YCbYٴ;oyL;}q4L"AEWSY8^ؽq|.kG T' 6hAR6v 挴#}Ͷ;ӋN(ѡʼnNUhɪɬJ#ڊ-6:c.,7A˙[PcaZ_>Sٲ/'q> "' "PqP"lCîܦ(*:}eshҐi%Nč8b;8jZh4U4<&3ǹ3.I%jQH[0dE3S֩0)2 >oL-`V^a/. %+R[@~8Hv %l}31J@d~-&K`6$eޡ2.i1 \4vUcC mG֐n+?h9$~\d?Jьؐ<ELa_G5JυMaIDl(,\Wnr/dFp"9&07ي$t%"E fOְ]M. g@a+6urz;[y][}^R'$@ HB£(e,$t 8O?I`F?",@f*B0!y ZĐZ’);_ L2R[rz{5b$f@؂}7w0.Y1sl CRO= H pepTAΔJ)>OV2+AɀWhzu$4w9QkVG9uz!h ݻf |vls6p~p^,x)DP%Qvv=Pu`xoN m*>w3=`q×nVy+ܧdiPΎYm=y{:͜ ~Ek_G3U ,5O ?R(ɿaz?$Tzc{&NF;9Fn) vVa ղbSf6\^x&pbav-V:pֿvC|;-F-'uFdHxնI|u}ޘyqFKYboĿ@NTs@L)ssv]S##=j!!*tg HFWKsv'(kQ"zGcϜhݜӪwQTyv"6kP':w[JvĕoIyfyKF*[L9v]R9GQnǺBJAlu`9i}w -Sc"ƅ4;$ -X^P`z_2;V/mA$ʞ=$/h$zϻe(^gMt/G֮Vu+qH,bH<sus{ܝ/?mmo|Xv1WqH@ Jkz&iz}LCAj2M7gْArP 6lvqc>UB L3˒a$l bid*d~l4Dx#9P{kyQ>%]Lu}fH(錍96`)hnMx(?h^ZH#X L2nhG|d%|*/y+Cd[DR BEQ> g$n'a(iblQK&ta&I^Ov!:IL"e5P6- >f"=j^NɟZQUK:ZѐHKU&@aF.HME$ n'(A̞QQ1SVd2miN_iL,94 qؑ S r<] o -Y R5- Mx_1 /7īgY7~+K2YHX @rǣϻ?> 9f+T@5A'/< ?8 ;mw49rL&L& f>xߏ#yHWJh;7Z@E|CA2'OCBh͠v@> E6=8A <7pq{ꖥ3/op[Ьa ] D\A@w|azn, :eH#a՘TX<+{0t&ໝ%]is¬@B `CRKI0h)h)geEUIfA);e 1 : $c ~4 83%%_w_+], %v cb'݄V*>o8xʜ AJ2!(P@ҚV8ɩ;!AQ"TdT9;[1{E,9 0AIL‘S 8 "T ͷA J t۰`8[˟jw_?gjî?BΪS[!As{׿7|FڛW*Ֆ>yࡗ`wb;=o)y Z;YR5{S/\t[7NSﺹBx {ټUeLs}x^ܚ!zhw{꺳}m\BGw:i_0@eKLf@;])`'KW*]G/"8EyS4[JQOu,J0'&m;]#$Dzџ덺Xv%FhJj[bψ@u;>ӷlpJO~!pkQjB$u4À _yg2Tsݯ}խ5(G}i(*L InKѭEXLUṕ@%. 眶md‘L"~4s~GzuDp(J7VI%pF⋈tafx63O&d78 Z hts|3EUc_3^]w6s $ H.]3.+s ޾x,vkBb1(R;Ү%kv_o=;OˠB`^4֞KaI z?2LFg~[ɣK!y6#8=N4#TL|t4[Li7SGc; ɛ؇~hjA@'B{NBxXdI}7hFKF5Y2UM˧OXP<%Sf <>p0b'-^8q3rF`9p0*^!tyliY\H"Jr_rD,bF+Zž0]ڢ5+h>HAo>;,vS6ز)0@I& YΨwN#0BҦ%|Rz@1;&}?%sNPBEEM`EtePQymge-kq}9_Ж|)qBF[(8aaq`tJmd"JKސYO]fᙫ-/.AR2K?ڠгnoF}l[lFe s~uL}e=gOX,c7*٢Zd/yrzW"gQHZ AA)T "p_pםl zY IQj~Zc˷VkT†wuk'NZݷiN6f1[GV؝Ab *gN2eA~!8z~!oz Lkrpq*!'jAVns,`"P+.̗26GHś'-R\NmGyvyxz@l+7=G0zȒHq* >)&yzd>5luBj'z6I P5C޿ le>Z A0G@I3cH2sHYu٦?Ԛhi+ }?z'-c?]3>I 70즧j6CD %@ZKpp(cPɳ1ii.s9gg 8 2j\[T,SM'l>eσ &)NͩV+R Ҡ<_exىe]}it>|iKi|>\PvExspu=7"Vn+!&O#*H 9VR,!ARz*Έm() g"U~@I"*hsɛ?d=#$7a0JZhRIRHIw;W%GXȏ;\Hjst&hfHDNJ&dZC^CRgйqH]~꟩?kz`d:8e364DfI N83)^taO,.x1SX`!sL.@ I8E^&,'Z8 2?Avcrp|{׾_7D1`9'`XpdKgQD%clll $Lt26XH$*J%M'a 6,)Ʉv Ê $M.QQ0 q`$䵯QurOIr0 bvÎ@@(pQ?~\oqTYZ/8dlQruWV ka;* PZ RY 8 ՇKz_%!9 aGsA',hIQ#i wt<L8UaI*e-!ov;:sQiՑ9>*@ #9C$Nz]s 0RU=A%Kld.&# 99<* X49|)_:+MH@K-vH:2I FA{0,%IVS;BB ܎?n^ޠω7'WQQ}#\g$]ݽJ~7+JO}5%7ȯ|>-F&t֎m>>5RP|.yح !x=[3}ޙٹى0L$}:9-reqJ@3,vFrW'W.{#5DJ}ѫ̰8l Wƀ&oΕIpwٙP-Г gxյ&w;{gq^5n w MXՎWq(NعkoGV>^G'G~q,it\ZUi֏ \2%5P3rTu\XT0i[(( hlaT[-TlVрn~KNc(:nN0a 41DIm:э!1VKdf[w (!t'M-N-cq]*/*p oXh$6A*;Q voSu+dfzf\C*gC]WӜTNďң]؅7cO%蜋˸pq&^lqWDc9`[]gb(s>pi* ǵ"8s ķ |f0x~Vޒtg*[}URX:O+9 uI=ѽ9js7"@_=S$,vR(@I;s ր"Xh/M ]?yװ̶_dm]iZ+6\e_#,bb&N:^LHGGZ@ܮ Z[s塦^,YJRw3Uzͩa@6L*h0VZL? WE3+#GO1&#B@(a$աUhk[S ?\T;u43L 2C] n]q>q;W̒/m6795|隗tbPN#YEH~L8Y :d,"HCڏob;*p1wdxQn/O=u/YR,  Xv -B{5Gu8#!tvCAF ҤxBi=>ca pBnP\-,ddڜc\9gz7 A"ΐ@g`2pLuܹ~cQ2:3 '|<Kl!*@D]AGLy 5 ç SSW"b + E(x5uTx7tY6-:,?[X<͙ yݙRchYQ=/d#OuWgfw~)w'dp6.Mog8'f쨄Oժt'y9}[w.Ӭ \~+nu"YNlk]meS niM?^EZpHp}r3ώCX'*&N^bY%.vЪ *A,|{61EQay̕Q D3:ÑrO]W\#>t{Ȏʦ|͵4 i0)DH 6Fk-eRSY/7(; SѾxҧl3ѽ3uU$rXG΁}~p GIq Da_mB+6(M"[@dW 0P[ tyaZim<8x517t;#boߜ(^nrGT' 5Kj^jX\LZ~A4.KM y@H+a$DBr Hp8$MR?BBĮ#Xں:8060Vh a+>/6Ivq@$\\#J8ED[>0өL"Mdbe:sR R~R&,)$lﵟۿ鍴ͶțQI\vPK*[B t?pn8Se iAZR$ga썜vYt?,+R <[E݉gts^̖ `'vⰮ%rgizA<1aZd8ʧw}1+T%D)/i)e4")A77D]jrhHrv8&X )@ո*v I~'Ov0`TB =DS[ق P4K!7"XB6KM.PAXk a"m,希y2C596K!bRR!ԯ)-\JAQv Gv,m_EWMNt = Su BjLgh޳CBW15g: ;$EЭml :`n"N8F,JN͌s}=vA!! B-P@,NL{W߬[bS?s2c̓*:*Vc☩ܑ$Ԣb 4P-?b4fQF84;E&NC_xKҜՃEzY0p2l_DHV @P i&RqƁR5zPEZϰe;~'oqnW͍G%.$dGaA 9DwN*STD%\|%KBH&y@eku`oʲs-4ڒ TwGy.@w3Υ$j&j_?ה/y3Z^,:-1}`i\Y<݆ ЛoOjʍo{MuO_28BLVD΋V9 {L/5&@R(Hz-k|*9iOӼmًic[g[-~~~+U4ElY2e5lWfXJ0H9!;_Z]Os*=6vR9X M}ؐ5#nxc'-Jemyl5CZU^lT%&uS:Z&2y tڎ]ͶCt1ϵ=zk,sM;794^7/Q.qGDQ#ƾ#`z'H0#oJ^#ad0 Q9x" z@`M4ןS29TiaWi|0=S*|fNlF<X 0"xh:_<43fjбLiaEg{fF*KMGO8r#9$x"9;=D=ȆI6?c"4LDeo&Q2aa5qq*N+HU;fI _LՓ~l=&7;{t:VG8RPIxaH @ ,K3!)!z9f17]ط'e}Xz~A9zeWi^_~=/2XF]wy$ע=zw23=B?DѪdս/hto8q={^u7Hxo(*+sNk;noOB*Tim!#7,a1nvD4b11Ȩ"|?ny^\ư=`ђJGn;uS(ڋ]_Mv[Xvj d>XqoαSi's[ EK Oc_h=sZop_ھ𞇼izVNiYY`;6&a}|= |c5_̒Ap|?‰+Pnys@|olo.>\xyu&FC*s @U& wY2Y0ƥl! MAFE͂< ,ma 11s)~HvCUs 9AGtroYկ>=X4,*cNǪ;nWfhkWF9Yn+AߣÙӛ~\Ԏ͖Xn jDn oqAG@h$Lxkʂߦ\BZ d[^gzxW^V[wr|A,o՞SR4U@LT e|aזu&֓ نOVۊ/*@}k:8lS;׍V] \[nQ, k5 ~lݭĭWF!&4q Β^=ں#stvsr㈟,\ҞIQ D{CqMVhFIm+`4 m{/;" IjOQ$$KIIz%` OyֱEz_zkƈ9g=?m7TsBŎ[&ǖs2gWpEw/M:acgfW8;q~/Rg"+j۵IV9'+:<)/4l=|j*uz cv?/'Ժ5!kV. ąws*cθvC}=IX r©[yz97_Q/cp ~ ilFv3BF7l=mh$]vT3,`B%Ӱɗ,t_V.WJ:mluR͚r &+;zE+;M6s:0 I|2-< 8vC@Q6q ,$Lj%X7NOj j[kl!;XD E bg H=T.4P tdjthQESyFÚO_&0l5`:܂iZgO݋PWOEX&r$%Bә@`XkԷv۽wL!^&FE!ɔ:gAfLzA x@zed#xrOȌ@,ٴ[gfa"щ>`W\Iˮ2:Z! ~.x#2P"!Hoc^HM^{̏'k$sRmྟ&ggY|wر" `41CB Q/EL^y}l{j8matg-m8QίeLG(ΜpLQDH*jqCM¨K tPr8<9LTK$t1h_] fݾuҗDt,X 2IѰ՜wX;e᰺hnH S"3]lBKg6L }x7q5{Y iۼpFڟnhLZߵZSj#ݑl_cjo%9 a!J/N !IzRYD V@ |jҳb3HMp,((eg1+(i()m[38V"t]}*j%+px#@@ p  hg WX}WnWCߢs)9^FȮAI-\Bd}J,T_7ǿ/y PH7}pi lH@g.FV#*|X IN#.\*-3Uz 1h2A@Z vБdw/'){:p}bf5b!iuXȢL]~kJ+5SyvJK0ܜ F|ۏwwP1-࿓C#KzA#Uw:<|ZPwJ,H7Py*DZDJu|.w&ʷVF8z>v[XLL$&)$Pc4bDf;~lk^Cn] LN J%keՂzR z9G`!BZ^j(-P_Glā#r?6y#nND=x!OD2 .4Ї*>G&#m41l}@o9p˾t6kfȗU\)B"`X,]I~uǮz?k+e܏#p-ݰHcT_OL%in͔DM b1+oS݁n /YRR!R?PjiInM.j'n[7;ao.h#x|[V13k񙴻&6U;ԻK/H;In ȸ$Du+2Fh<)T0!˭qD=7x?4jS<~8-2( ڍ=]y:vO+ _.&(! Y EQ+ + &3ly[,Ԋc mXYPl*;c@-\6Ơ\" ¬C΀zrݷx6dy}lm?zpɸ#%I7Q{`z˿^A1J>˚(TFr`Q@@Zv #E-dB b&ɴ2. ",Q;i^C%>/Ed߷_ @Z.E9Yč4ixV}rcz*\ Q0ϒH„ԄR0R++.IRvO7gkO#+pvlANME$iUԎ$YFd^IYb:刂atU2CtK<Τy1q5H*?U,DQaB{&c{z1R_'[ٜ-(uptFD4eLצ:P@pfF}zhm;W Q,(dȪ'C0ήCڰR|fZ }O\0/ܗkq̊d*a@+DI@kX!*ݞHqMJ5/ t/+<9 јW aQL gm^x)?0g7LvDC5*(&/nk$D2F<7]}| Ro SVC#WhSi"jp<&0B`Wc nܞ̎2/O`p甴NWjF1Z;ÓqkuYQڀ}et6KM)+3ׂ{#T_*n~=q"Š-vs/7 xgiv3Nu\5J h2;8ȻHF=U~Uq) C޴T7&q'Z*}X`wn>>\@:ōSSy!NS[r5VWgOunl wUNA(pnd=f;-"z8#鷴7IDG񸕽pS}kV~M^[E[|W_MgLɹ0 q2 z lj:ǭ6;/dGl/vf6C@&5jϵ7/8Ki7y9KOf7#H:@p%e T ~ʘ5QYqN~}#=~r5xv: "Ba5%[W|8ķGt. ,,Z]QrUǁc=ױ0 h~o}ڧbyo *p׼ִ摓h\=CRۅݠ$+ocw6>ެ^T\;xUC(+D.}-T;S\fcFuWU Е΄̾YIƝ݅q6/Y)M!O!ΙlӅ|?ÏW}i- Ov)UNCk746oe3FF64!IaXY;'uf4T pd)|H /2YDA8Jn{IQQêp!pEA7 + _1 8A[!0.QRuJ)G6DJ74Q@c),#H P!@[e|ծǀ2oGmw[hWi =Ƚ%"fx;|̒g ,I- ,L(~f♵ΌdTu[~ֿv9\AZZ9k%ٗwmgtюW?gYz3tPgF1.B$vGIݹR㌇&m[{tiAMt6F=(;zVjbݴ5T|Z_Cr~w˵3;VrcnЕ@e}vswɖ^wOT~\[ ܴqܐ0lPk4;X^x)@큗:̬wFq8wjK*{eZ݌Z'Wv?ø1;_wͯiجtëj(G=79P^xsrV/-.Q."̟d&@a׬&jB%1%4ᒤCo}޴oFG,Tc%1>f5O != ;icig|ޗo)9گsm)%L DMv7巊9Ǔuj8rqIerYɭEʼ%J[O1kWf7%j'S'9aڐkyqs [;^8UWyȍ F̧F^y .Yw%[]V>{hb,_m[1A8ZD5\ϻ~g}:nG_5/+j+R3յQ/VhӳKF0R^s = `PE!.=in?t;x% kdgn̽ qa`WP3>Ax2L*)uT>`g@t~imiWu԰:?OdjApv^@(PVLP[L 7s.B]eY]lc\h (0=\@Lo*L+,k0 ) ҄e絾 &;YfD+?ZPDI67AE!Dx(ˀ( Z $?P@ ]ʌ'0%P;9;ޡXC߸W !9Io[hKӊNH N:lsSj֜[R%xI:"J t8@tXs'ZY5oyro廋v^WD?Oeޑ'rq雲݋GǛDd #*Qs *o'A(Y&R2lTYTPbR$e Lj#TiBkB |:,[@Ya+a7ޏl2Ke6VJj,r׆ցD`vEV\Q9ŰOd?j^j{l9|p6G񝏷^ ?o{<=9цX/ F|O[[?Ý=>R /|[mO?Gx`Yы[sddW7:TZcl6W w\~v/Mw$g#d3L[EPao~WѧgFipY/`4T0K,Å>zG캉vV%IrH-F=;(_džśe~p>.x 3t[EVsC9ԖMSpO]hEr-vczM߂\ x4>홴dت1&AVԀs[! ^,iQ[/Vg5^/:}}X??FtBj f0Qďu A>鐿oٔ[Gڤ=alF/ \hzoʶn݌&-FOx͇m "ɦxO+xffhSO1?u2pQ^[m\H7I4SϹrfḷgN~q]׸;2nK@pC}B_8 ޟD~wXlFVo{}d\ JF>y8*m'M^$t{c+=#.M^${[sU\~ˇ+d~L6'{\չ^onP4~L1Uy!`p(ܓAGY3rNf)b|pU vfqt] f^=Q s\g&eD ǦZmIf #Ļ`^C|L~ 6v{܎>pfZiwQvUNu ZOfpl[u.݌ EFܣTȨ;y smF׶%N 6(&,CiB:#$PK N H:הշaߒWGMuA.6Zz~@M|kk@s(J=oU;@m6jL?R-`%9g gOM4]8/QFuL}QG^Z _|yc_ۚiT}x7lՃ_g5YSGJM*BjO} oO>L^&x:kR|m >:8 h8ٓ,G(xwK_PŏKbnL<(OcNҳ2 _31 :bR.De"C쯧}ຑAܞe7j4M櫙U/ hLd(!Iw0=/bviO* W)y]%m{_f_3_ŤJw!B]&2t.G}G|V 63=1 e QצzO^x.wo;~]o< q)o,S[{u*JH傟5/ʉ͞'{#95;~"G1dlVl<.s{Mdfdf )0d9_nS;!JS0}R٘.MYP]4(9SM&{ݴ#N֕;vo|]alAjB$pΠN`D  ^n?LQ~E8e BZ"BïWД%ʽ G_w/`;~vgi+W{QT_JѼ:?M~a9Z>U. iZNk6Ջhy7&#z)ΥU2z0:Ys(䪮YYa7E^YhN[߅~跼.V74_wUu7U^;Py|0 x0 @k#@HB1 \=&f`)z.ЕP*GhQ>)K}zo\\p usӋo}ӿPyb\Ϭ 7ȰڃyTgmΤ>;ү W8~'_oUG=p2?5uԇ +{ xGr^W?u,a&,״gN3~纰/7"\E!“?f0 ;/]i1X5V'2wV(E$mHq'%Ji#¾5yΐakBQ_Fn)L.Yo/lQ A+A =mCyj'6Xt|.qje<{ݴl; 9;w]|ƛ_?l)ɢbN?Ryp]! tן: ;׮gLUmIv Iއ^MrޚFQ>!nӭ{oDo=>_=4W×n fU^e{UO0IFKk.5]Y6.IqAq]槐liwbl5__mUvf0i^g9g f/*vlAّP//F1Y$ <+Al њ ~uwib&yiH!]uxV ÏS m 5o^dijIbT9E4,J`mܸ̈́rWOzFF,# ] L%FbRIqW4{keU>1Ig9g]в|'M @ * 2hJE(EoVVWFԌ,C\SV3KO,qO/N!\=~üz_P[7o8ʼx&`XRӦB>V~, _Yy.y)֣]?Yn |u-z~)w,RM6OP>@)[[1}>tg?&|g_x_{C^yǯloyw_%Դ6 k0ONU/#` h<@x  Z v}u~ 8s݁z./ࣷS퀛i-ч.@/8~)'+5M^=Idw0O'7R8pnvWWWJ()q:/,GyeLL4W}qKiA1Ǿ ?stu|zJD:kTxYf=OoCm7=[ [nϪ TwyV?}1z_zXyXop4PQ::?)˗@ g >GʌLV5G޲_.G7'6t̫FAj5}Oc=;@/WoC}= whFr3 hu<턂/sQ\ 0 JDA8TRTqé)PA $8N\)$%zqjQzk -2^&hXɹ1key0 >]^IjD" 'mdei)ǑQ4Ծ `AI5._sa:4`&ܮȸ}niOxu :?GlV^"Ͼ"{ >\])+߹w/*R ;*x??U'oӪIwcCG5z@ qy#r; @_]CJ3W8ov]ԼSgc6^n߰:ʏ>Snc&O\p~q܍sF~~{SȨ-3f&R\@/+^:[4}Dێ9[ $.QITѻšw?73_\(!KTX]YY:1Z7~ITVL TWv7_K޶?0͖w_7\澗_t2-}֣y?&7ul]ݝq(>x(hCs?O=nz[CG"?_`&n@-ހр= 5!8n +&A),~,þkwF/Lt׷C̺B'z{1 rkB:?!Z|HEGAm+PScA¶QsSаQ=͑?rR9p=* hyeWmw"4i93fsekl[t}ٍdTX{~֞_}.,"DZbQ0}3C6on1.ga='tԦ%v ad|:\L+%z>7=*3SW֢_ۑyeuMo9C[ շGw{]KZN춱! }rIX@_N{˦/BG񑄨Pk_@] P*PPj0DE~R- 6 7Ǭ.we(F@bS2)o@o`i| 7L)g3CO[ Y._uΚ3'[CiٌqR䄎9tu&ԙm?1[ɏZ*Ⰿ w<@HR_$yLP (RϓA Aڌ?@%297p$5d*\5 T:-OtELנHhg!7I!@Zv?{5L?/?N q¦nu$7$?ݰsus=gۿ=4N( HGa-~bο P@`=.FH?EotOlϒ`7$̳K{m-N_߾ݘ c?1VS']P |7' gHb n&|gj 4k)<'G攩жxhʦMj*; BFb.Rg?ik=U:S5QMTS+FT@{CZ?!u%G?Q𜧧˾nT1wM oZz Nj,rJxsw#RVEKppb6WMJ"f%Ӎ~StCk_<$eϳ,# f@ԈEgb#B_رzZ@ L~d`َ [C3s|;2:".v,?,᯹ſgJxbj_Q-xs*MQWO1HQ̋ g>c$# }_9ײ|w8H cfgS gb%D\ϪfF:W:\1(tTw@M9x,5qx j 3hSRE 1+f 7DM#YLfqrbS"#FSZ5 ^\b/&*_1!FHC ̤7 54Sbaٻg]XgD:`W6')&D3tlZ zҀAQif7AdE-imJ`j ;' w7 }i=;xO˅7Z2DV N)3׎ 4YW v< )[kMIbl I oE3Y 1!xOVUW%B㪅ll ^+x<16hR5h`4%mbQev- ^ TgR@Lh?mʾҜY ##D~od%h+;K kRiJ/E{`\ڎ0֒5)l_95dn~kFꊘ?z h]eWpٛBI!ҷRe0TV/G@bƬj 8Q*ZG=0D,4'҄s/6L2}0BU-ᰒJ2zw=]1˨I^խoU?w2~o {߮*mw9ӗ~K3g9[9*FGEX'2 Ȑ2NLtaW{ %`4^FdR즵b2 Պ4İ󵢑6~05HCkVNNƻމWSڭ-q\7 ̓hۮp*(RS_9Ku-Dm+o (T)pI!n(yqq'.zC1X_࠴;og>sZ*6ޣw߻fצM@ 6f/v4B{.vjw>crDli}$ّ)ݞG[{Fv6rvMm7?o\|0uoEd&W,IOuj{TDpӰDm)Zؗw3gZdף@Eک?֣keaSlk(8rݶ)F3X4s[E'qGXu9s**.\4 8+ZB`*aC&e,oP.>;S^OO] 4:BuԿc?r!ZUpHj*{8㛒mT wqA$q=0eUmg'd`UFv+~8fY~#Lx0 &YGa6 ,mN E:A ] `9&B$h2K;8|x*yMH]ut'W6(-Ϲx3㗆G^&?F{ͫ~n_çCb$J^kѣ8]-̑zy=2o6Rנ3ѳ<|y@0a&hȔqt쿅mԖK E%*B`X܇2_0ȧ zpÐA`a4LAMu:>ew[@4Y>-̔V!z!"u)ZIYL ,QtS&=ʮzۏPAh$B$W69s74p `놮i2Hc&{C%  Qk& u J"#!S%DnODW4tV&x khĶf.;j~izbOF1C[*(1{_$H2`Tl,ňET`7`sk臀 qp!TL$ X65eA8`!+DV׼I 878l k ݟө߽wN]7Vw ]`8JH9 W]e-!fl< &mNģय़}.YЮ9+r&9omc+0՜'x8s*fki̎#L3&D 6Š}t?R,gq bWI6yx~?E5)^'ߕSgN<՛+_1fնU/jKa<[LΑblm'?f뾩E'Q6lYoPbG2?bߋvcmREʌ2^ souo+m=w@|-\$Ӏ&$rp l'NIpZ 6E}rp2úG{܉_cBuw=;l|ݏs'^K<vf/e[~ FGo^!QvLI'=h2}jɿz0zи Nkd3}#8GF ;o Y ;,SB|eA @v3YAd&a.mW xɇnδ8nwU~vJ[{bnx+PI1" 2E_\l;otj/F{Ε*jomT+4""K!YIӬevO4߇,i*(yܰXY8{{؊;HPP$b%!X}"<(03ܦEa:Qo{ 됯[녴Ƃo+$5\tV;%~yH%WlwSKgnsKvH`ـ Xu v.k/]Á+ofO5ʠM "FBH"aDԸ^_mt["@%@*)+RJت@R+!!"#'!x\R՞[9KQ%/K'W#w }y ͏zg@'mPrPI c^` @kg|n/gFKI&I lrQ:t6z[l9Xt RAj|Q]9b+WC zs5s&UJ/'[VPԛmxPK[}qLbl8NR0KvF(鱰_O|K:*CǗ7}3!6BŌR$.F_n_wQcd9$$ΒlPY5(˵ )5$"&b{`F<nL{NzŮ_p{~ wtxxRwJ3zǗr- ߞӑX:c*oH8xfA>gK􋪒0 B8]!rK0932CE l('f C(KY㳔H̐>O=kxsMoBCug-5wyۂnjQZ4;{yu>zh'J6K?}FmC ҭ=TAQs|ЉX;3B)VuNI(idR>O2|BحkjѸ;$,rt*w(н3᜴joAҎMCNդ-,ZO)qǬ^Y7Gi<ČkOWe BluNke\ԠڊMQ[ /Sg|ndA = @`0%@ 0&8BND`p |S)  y`tjKpOb^qrfSb"aۜXׅ{CdC?*G!̶ `-?}tυ `*l< ayu΁Ec:}AN?Q(7l/0]_myt54\ ` 8vQ8~"T bⱟ<ח4) vO/!?o;wL K\q|Rh(b%IEG[8 V`4DpHfﳼ%GoK~m}.=~<>6dS(""Z!pyNIt#SQLC%&ŅCv]=b@J ccY0&1):!@r&'HD haz뽛|膥2`I{۸gw}Pu9bIq^! *)CIN =lu |_N0{|3C=(p+59P -x @Tf?bzGPgBXWzeW{•zx(>NmP h&Hԯh ߽rFF~1b$@S5~mw<99wy6KKcoȃt}#bp}_x*oΚ+6| >Y%KDt6%ctVEge0Ѱ>땑Wk˗S]i88VVZ^XVVSC^9eӓc=J79$ϥn;AXh_J=[}Wף:fEq%r;?aB߳˜jO:I{Zgqg%tf%\KO6.a PC%-$j9kؤ[TP2M cN$ V=dqCu˅yM>+.?g]! O8,4\dYZFbS$i;+$0M!,mP PAn?^~swă2a V{Ta⊈H˨u}J2LpΡMc={W+n};z>Np!gftê/,;7\7g}]>|v_?Enw x*3۸mP:\샷 0f2`U!A"LZہdij+گOlnaHaf3O5tglAskK#ͤ#S&x9AcNVeQ4b15 %\h!: 2̞ /UM0Jʾs};+%Wvffi qmB4+"y'~y :9; Y m Z7JsAP˙RX16$hQ lA>Lr =W2WvAj 8=XvS6HS •,v㝨dcdMe7 hI("i +z ^fT cJ!{ibv^+,Iތ=ȠdC)VeN R=~>$o6y`Og͋a΃&2 6_74L1"6*'G? n>~_r?.B/<&~Z8|V/ o >&G-f6?"b٣?,ʌ#z}޴u?x^*<#\az'"Q h{<"H&*8tNjمΞ盐ⱟkVlī]~1]ǵQk90Ua29Lp_G]i.C b@7h[ƿ%k\pDK 1EZ왇ĺ^S(3گآT.Kme_~79T|*DFbYK'%AB ˼3cWݲ*NR jL"}.aX+U0W=ߩ"({e;;98nךUw%8099">^[!s呟dWFFĮ˄Hm}ozEe^^K%I]R #|)2?,od}NNRBI搗g,A6!Ј|DC [X!SW3:z+j7b]^vCܷ I7mxZ0^CdPiٛzlURݭ'"ܫ,ܧvn^Qe&:@ $f@xfAFB]b juaDY:malpt w^ C:^4U=}hhw; u{^ (JY't{Y.m%ɀ `@R95  ]@)kcp@}9[^5r6l>.FIOa·4"?;H maוC>٧Pƍys x}@}UKO S۪j} cز  ar :yT5"yjݻ7;-˽Cr/m O6/FC[}mm\| {؁Sp/*N&eQŊaI% I {aljДQtm#x/xMgޏ0sw,P,e7L;NlH%!v0aOD'uCm <Y߼!0V *k>4A2ǀBLќDkvC|0F6xc#q76aVGM9`*l2"2I hK7%24ZBbUeka 2%~Zg:=17wM{(U"9J&Q/L,8Ia&Qua¹N?`^^_GؾQjحЩ^ʴϨޞɌS+sHY%ǥjsLm ?9aJa9|[?2P$ebZ<RёNGߩ؋YpK|:3v̻邢9s^B0 @@$tq |q\_t3aPE| 8k~.b-;L*c@o$?ϏjK,ع} ~݂wn "†/Zut%1*ܻK>??H X&a /q=/~د(i6|S`|cÿZ>&B 6xzWnCި5FH ADf2!<ItK|6ݴwz=eD,MϺV V9 ,u'Peutv}4aU|qkǮw/ML^RC`H& IR滥?ȥn{wԌI=)L9 H鉯Hٜ {=(mF-7եϣj 7T5Ɯdn0B^Xdưt63ܙoM>zSalv뚬qq禐qnϋq`HI{ͤD$8l /C(<+~+`At_hO}>8E,5^y̏~ox/^2W)yvr?%(V ?G\t)ɷJ߽h_^nc#R+m4jCF!̥}rXͰC9`J7'A,@O9,jTNmy};~mӅHCE?(HZ0 :q}GbS.~17: 61IoaǟGvOM#a~NF}lQ3^A2YǙ]Pb&$ ڒIk 'e F&;tYGJF%87=Vjÿ݀n\ټ/5̳+B@0,U`ۀav#Wi٫t87]:i ( 3i\Бv:AρED^>gL퓁r^3;W;n%dw}>g-0Xqڸ'Gnlps 3kWg!{ Q?\QVD3f@ Bϑ4qer̋s0zL`K?1j- ySV X tp_za+#fpFF[)9iY߼Ȗ#H"׭sn{nfi^wjij?_K~J!?sB)К!2`", D#).(T("v;sIrH/j_[ŭIX?zYq߰̆6 |)i N/Fyc0 b)!F@  FxlDA$(7sxzvWX0Q^hB:3  9@#,w~R:6:Gc% j/Wfovib'n e婤 I85Y M9kH`KsF pʙɼ|G ":Ӌg+*E8^X>xtS>g<sV7D-V.1ejjeK.bRjN[{TWpu7{'>O)<0=+@Vw(˦7|Bz^SJz%H#$x=}ɵ:rjqe2ko1Q迼 %)U# R'jHʊ* بz)BgD2Pf5~h@3`,(+Vn7@01ǃ Pb# s}@kDJ!3 Ac4 >x38k 0g).Aׅ>)##W"`CgF'_6 IDl{QQTV/TcF?9gRfRʂ}9KTb?9WGI|%ސ 8{jW0r#HlN;ڔ3`w)/ͽ^»?c$Փ Rᴧ[߱ҔG]udrICP4Rݹuo"auktl ƹzuܽA0+FM'0ij`"t%@ SzH>2s5son]x!5kADʈw5sYr0_o~k"3YQg'YWx%6r #}K3\^ ;>V}tյ(4K7ٺ[޹zŸ@DbXkh3'NDXR!C@jX0$bj>~󵚗Jů.*!2U6ܒy,'uSFhں9Au^_[ظ\3:V:o$ "O[%(ƳE1 ӆ^QbX_P͈O;4 U@vIOL'Z۵LߚNo*&Nu ܨBS+g;-$T;;XJJ[;P0Mq!@@U֬TT8DHP;Ny<ڟuȕp6ebp`rV,A%HcSY:<\Eȏ޲6XߵJ3\Bb)Ւ99#Spsroc ̂ jq]6d_!H I4v pxeF,D@p!p)ڇ՝hPկd7 ms\W0JԾ^_p~_?(dCj ;ܫ:)w7噕'FC&}8DR_j+[K_I"mNÌEv&qѯ8:Ct/0OY5F0˗ ֎FQg~-5uev/'z~B" NU i_xNSb1v]Aɰ~O1c\+!+Ǣ[Dp} L=LMwgpbMjMgC@tcl^ro|k\%e!iUmfB6m/.2RDUa}լ ^ ZY {f\u~¦jj3Ɯ)<Ӷ\ZOB*4u75oS##Əv 2&&}ѱ$%|x\j?ýꙿz1o}Oѵ<2| A1@DZtQ0&`c}Sc٭A@.!!a1Yɟ瓓'%:o&`J;7O|x믝/N y`.~uGZ1x;H/~L7p;zZH}q-q3??EBհp/E=?ox) b&O'7b10HX]tsyc,?O!s`n;<QF;+Jߘ|~_.?X֘FZaBywxIM!JKobdSb7t*Mj_!N6 5_(n9Nܧr)IН\XKԫDj.̜xU &ёÃQI),TT|T'tfqJd_Ar[YMmTD,ư$Kə*DUX:*!AVW:V e}`5Gڴmn!D܎p~׹âJ:Sz"*ڈR|ET\>gWO0y^:kY/'\!w7?Q3_G (I8^F6K`'jAꯕŪe6gf\tHOk-6T,v+IdiITɎ:bJ3/ mDU^fFUSXU.X`Ӛӆ7zOs3~";N^x"{ @VoWLS,668 ɐHq1ZBjcP Zy}//b=9|J"D(q c5 R@8%=^V6{ÄcyMx~g%J38N|o~.Wf|\a՜L㖴aWkeH'ē@Wx0@ F0;rPZ7x(7j|9sJdE"%ŽNjsJF`Lb:'YYi >5QO[F@uYSsho8,ogε@"+tsf;?BQaC/}=Wd_~Kj[/QWlTҿ]sc=ɯ ^-RIXK Em {ݯPVnm~:': ΂ƊHJ!%!fÍ Ilx*X" }?\3.BGo+ӼJ9>K-<|rA]FO}/8ױ!DcvNY|-={W,b%c\2GN4mo~F3-cP)y\0\I޿5ۖgWsN3i̼m2qrѢqcwms;[׳}a;iv$ z@G:1,ɑNqM ٘CM*\-FB(,=d~Xs>= nAquY aNhpľbr #hk]$ &CÕlsGF( V\5Qd PHik)V 麉6q̸LN[rdoW \]irG,C #_yQ,! *dޡ =gl{򒺳{'7#WY(DU`V `%2Ԑlv 85|ĢΒ%XE~W&!Vmu$ dȬM(Cvy;ZOSH q_,hM\CJԘ^-h$Irk#+  ɱm}Q_( K{:Ia38ۑk ӵ@U?(*EɪVH`$Uo+0Q͓$"%s_1 -"vk:2+qY5؟J~1Qc[O8Q! *Up}Mh:W Sj*TFNǙm& ƲQSsU gX?^Aྷe[t8x#͓}QEԐ#go\2:EJ,$D!h;M%ͅNmTaOXBy] #WbL#-;Q+D[ODIg!(X(@{׾&6:%D=6<Ƕۨ͋nn)HX6y%{哑4_a.3$yPx;88]u/=_sL;"s7ve~'^R$SK'MJ"BfPz0#ARXP ڋxQjL;M}lRʷRH]s-'UeYmAᓞE".EL!1:O֍aw ##w/nQBVIIDT[O0G6NVXKMg\H`Ky>a*zp5-lCz+QTG'F*ųf3 TP`>YMRXYZ?.{\qTFynIN-RI$dM!^Db1; f Wvӧe9uslcʫM/ >=_U]*W0Ό*TXHl'V)+ sA՜\-ֻ'*}QV(l`(6rgY}S}"6كf3ec2[>v ;Zn'B c$J6&q4;9Je՘- âbcWULnҬ`߾KduɉƉӜH,0کv$(%VN80IVXUȌ];K B&, 2 W혊]ڄA̓53gCɳ!IW_ " A( XH'tbIY7%;IW=(1R]7f B @"hTW<4}eI`<@<{wjDG$NaGrܜ#@_Ug$?w_{nZItӈ͞R,8#>k'T;;I;y1T+E/J-cNSKK9/F0UbnP?Xh c0,eI{r$G ܂%Ӡ"s%Tr[ -vP]@pS)ݜEΎd`L?5ˁhQBB+P=R)Yc "P>}Sb+:WvtQםPF;]̮}X5ӊ2MƭBO~j-+KM8}sBBJ t‡fE)wX~O5Ͽ<-]lOIs]ĘxG9E̝=Hƌ vF.L}D?F *Vsw|˵,61 ٵ0ṿpYF!m LV,!J38^\xvbhĿP &BS9{n8tj.(Ȅɥ,rMv((b=9=GM3mhUfvپЁ=1$ %C),44҄F"Um=e+b,^5ꂶBˆƬ|Z_W5'W!*P~ٯΛz͇U,X:wϾڍz7W5}'H W/ǀīSH@ZS1VwGmI3*ns}93\qC.DS(dr&ɲ+O_f_}K}ef.|&Tj%Ajtua!LKe> uq;s)КZx\7hBtjܿ0.P@b{Ӗ8EDz6i,ڹүQIR"U ʒ+i'N@KRxPSY.>=sɦŒKv\Z58G<Ѹx۹[qlj\SWL<#Pa3.wb脸 mV{XDB-4 J PUVɶ@0P-!Q6868Rdm;1j 6#W8BIڛH==7]C#݊SSҫ3tu;ѽCTxY$Xw?6 #dmuMiش[fqDGFj U^~: Q2Q3l#clRژln}@=݀)PE %LEVg2J#=3Ӆxi (֜3g&rG0rɌ'ُnpcFrγI$ߚE_%dǼcѱdlʉS:6ؕٞ8Ӗ\) #*O_~/E+]  & X/KzMo*Hy{2tT3q89wiDg-fQ[ ?/mm)Sl!h*s E,咉TuϢt걘ED"lv48L%wbAC& $M:@O[ioe%XѤmB"_#HU [tKnױU7?zƊ5QQh=s̨ZIv(o!9u֔EOHkHMR1ؘ֘&9E46%sW .fռڜMI5[1sg14uO_V#2ZjWKI-i9_#9a >$z7bE6*J0PvՍ'힀y*O.bŁ"cÃe5y]LII!l^"_W}ٌb%.:j1Ld•}_u?F# n~Jniwnß*m~>4Vc6J**uu|G X-_ðbLYcRTg^;W`LWί 1._QDȠHV6PJH4; Z$g*6lX 02H#L XAXj QmdX6zoD8Y,rOW\y25[˖bS3m3*tױI8%Q#R@H$@=e%V |qz ^@'}ڂH-aBD. wYaf1#ARbEgGǔSgvKD^nB _V!.cݟ' yK@RA6 i`"-Db 5F]PALHaCJ!LsS-U>g|4,O76Ӄ4x_2+]poE #XtT9fZJULԟ')t*wreage!C&[9h r 豙Cz8= ;,iׁ4#Z*Qʅ+D$ ,/CkAbsj7XQZ XAuƊS濻ԭdP迂 ̨,` 13>w f́=:7괶&֨eBnKi2LA.ZKcoa?_p_i̡YT$Od5O<5FqOg)wc܍̕-~LjxTһ% +#H^?CtqK!٥7}Ɓ+@ђOimЈܟ-1O7+#-=v1KEb7ĥkh1rb1y6՞iH&k:\ήI( EėdqRǎncL l~"}%RJ wS"Ą#kgI;=vthܻ/(d<d $z XT> 3G$p\dBs`ȨU4{K BkJ/>*867^\۳'%0<8kWM"L@,C + BXNؠ^ |IA |>cda:"A9N8ZyFv~k}뗾N#Pΰ.p.A⃕Ă.-b4ǻ2lN8=^Gmad`rxY3rxf!¥&k)]kJhNMFr1BͶcS6R< 췖o[jef߉`Nlz/z٘c&vFse>\=ӌ\Rv-kpB3P=e:YzxO ?ɃK.sc6gwc_lK,ӞkvzV[/d?}.=`K- %LLw[֏癫24T&]Vj8` n+LݤX9C]aZq(Kfigg5r>*Ǜ\W[9?9/>}ګ9E"Ғq/gZ^35Doɯto =K.<Z! O4 J" tV=7{ݏ֞W^AVxO +.s0X*l{_ב4%WL6N)\ʅ=J#9X[ǁ3,| ˵$m=h αj)ɔTQ*үS݊->H>,B2:{}ۺа+.lWqNmKb@ufN1HŽˆ`݅z~&eu?̇ Mc%?A8toV\yeQ/XY5SIZloyO~<m*xBB0}C ,jJ%JRܨo-KTWΤ1F^kJd~쁢:[ h_(’bUbH{Ⅴ=?5v{[jLɘW{qȪ2WRD޳69]"d,,X ɇ,Ɋ˲qə]%3^zx{+}Gs4rD,&*p*JY.@Sǥ PH7>LM\Wcdqɒ H)A*^ j$8 \H{_|]b@J̥^|C=o}6^};;2]E r/yLcg0Nh"a[zŒua>+Q<EO*I< 愔7/๚-a!8HKԤmty )|-sJi|UE%Ώ46Vl/ P85I&a|ֿVPWnwaMjqͼl/$Of6mo,G?v;dτ7k~b{i;I8:ӏv؛[z "{ L cVFŖ|r UsQ!z vLlBʕ\ )$IP\3ԮYXqVB y-~ˆ~ni$AekPbh(HtfjW Y6\8qL2Y*4!k\ڰǴPMdjs ڊE/(69 F)UKdwZ-`#{7c|OTڕ%),uftf5&V仝zKdtyqj1SU%YٵIV0kzV?%RU+.IBKA(5DM^P y+ O0nTGh 6~…Ԑx"{IА[&Ux#""0NrJ`P`o=-5s҃.x૯i9 )hJD h(qʟȜ4w>^,ʓfX JXIKo^'?ד_rИ<}lDY}F}N{_'9U!˯T /LOb96<NjHrt:I@}فlħ~go6N>p%NZ!3"`k[_ݚ;N65CHOB`C2@ڊBtѴM_ob.˿ ݩ*u2@aߔxrj㉜I|?GzB%D;>ꗂz¤е"8ٿH0̷n͖ U`nxW8 0 N?AKGrGG/ͫHŖ/kP*{}̌g9y`>Z5WqScܳDğ?;-~.J{b!"*e^mZ-!XH|H&ѵtC $A81(D<Ӻe;odKG5Ҵ.tí:dv[UX,啇ěԿ&HAP،w_~S Ej [iCXϭ{#X[ߥzyje(!NѠ) d`]2't<&ZpǘAPuV 򟹒ŢHt#zPH Z7- C8hV^U.-_M6-#ZalIq]&ϙ2hJOJ#䞏82t'1R-(.&gkBI"āA1h޻m~`Oܕ2et%@3rh2DqRQP(!J@Vf̘%%cfY:$Ef}SwLtIi'0#P37{5]t"`+3&,ISU= NZrX; ٜ# l1 9e"kk*:-8VaEe >tD^L+8KG ?ޔbPHS?0O?5&F|A !$Gdz՟HxZ׼v`?]t0xN,GujT_E݊7P !kG& $YV6y+鈶%ŽǚʍTQxhk_Mc.i,0e/y_!_tbO~>)hj;+nl8̹+[_Q>#) D'qGb_P=aUg3n>[IC]z$J}BҾ#$Vc,b}o+o$\{?7⧏Q(AMԴIC =ټƭ]V f!v`2j7|%Ӟž6LҢ х~OU0t)noj@ѥZꧽuŹ"jB7$H,"Ip%]7{BcDJ )9+L]vG3&Mǹ&IuKnVc|3]1"Ӻ%+G.Gk뫪5PZ{k⫩!phPs^}5J-YTr/se;׺ɦGlA5ISzo_|D뾔%T1ްG/\Zh77>%o ]NjY#j5~joʭ{VkdL>S!ي-& Em"%8ٜ!'tjM YH Ȕ R˹o%+ęO,`lV~1]md~_ zrp(T=hϮgԠe4:eM!˴WT:J c" !TJ^4찶nnQc&RanpnA4DѩZNBxg-î=^x6 wme5ZMR8}p-auJ86fxef'$dTif aj;>NM8huY0΀$DҠ35ر3Ce\ ]ꢐX% &)k:HVB_CwLFs:Ƶh_B Flz]iל[SL` lqgӤ'A{Vh޷A'$+2$w\3_xP %68zf~k$t 'W!p EƇϫxMSBŪ`EJ]Z6ff~P0 7P^l#Y˳ FKѠh$L (W/Vk]I0.3Gkz/^ /]4t:OQ`|g$Po_;t 4=F\9~70?~^tU@b?${GnY>on)oOkP+'a/.b#1.~p_k^B,*s\UR1eDۓ4k3TxVф}kfnR?N3Fqˍyx yiSDY%^ e!ja{w(+>J{JU&Z ӶN|-)e8y99_ {!tx*$hQ=)M?v㴉cd㥠$X|@р} k=jwR = NY~c&E(P$+@)(118z fi !{1Y Iz#CmVlw+`4M1T/w9rqp("ԠF^۵ڒ8 uvG:v\O Qyfh[44\ւ5d~.?gIye9g`apPxߦINO҆w{Mﯫ\&"Flb5oE41Dalb1HQ)I /NI&˿)iq/*0{e$)t#` Xs8$ήhH ݴh+d1FĉjU !R~Yq/c?)fn~=6#5nֽ~g=܉g"FF;_1_.-ng4F'{ǭ*-Us8$!ԟc֞/*6zD헹l$]/i>4RoxGYD&β f3_~ YY UX"aV)L×r3SgѬ*] Ol ph,[%&0*,3(@+8A5 {Oez$CrOit#z觢D _nE3j$~kY+ %k52[niiܿ@*\SwAT2i Fԣ$5a{O"^?u`yOF2ረ@|ff&Y  aJ x iM>A$@!Q,0;3#l qy0\)ȗd4WQ?y /+cY"W+#4`#DL.L_i^hkFTZáLFogdU~@%v^F{޴08.`|oJZ5ڧ%>S̰P_f$i&mm3xcycDyéPc׶i"afhى&Im2zbKS$p%gDNj] ޫFY6#q0@bAC c&-CpnoW:<-d1eRhfJ"Fp yI>0dޑ\=r<\S_)AiMyPi"5}D-hi2#c8 uύ/)JD&R$S=)0uA7f2jQ>' "ng_]HREvul""~)UB{_jƙ7u:R-*vA=.;OJވct>Z522O0ܔ3gY˼rDh_fd̓rd)=5:8aDUxu@z&fX;P hÒ }!{NgOΥ~ _.2g! B`ˎ[93ߙ_M܃@"F8XNz!Xv3Y*pͷ 㻏]h!/Z>.l0*}5vD~4|.ٱH&VQ˷LSd! tha|uU _1{V4͑,A7 e׮īT_cҏ׮_$pE/KɚcڷWq~Vp/u]w?"pgx`|Bdd9nRwuM.lL`5Wn(+`CB0ƨMPq26/aΘJ%O\peʬJL%b nǢŚ,`ǧ' t}.ǬJ8Zz8IR1G|pt ǧgL!IOO!IjlmRݘj>7bmsY.JO sKژ]x2\z>`ͦE:zTnEb0rn0)a"2L{L8 5sԟ -?=]ļiͭgbETkqhy+RWvZ5Y/fFz 53*s 'Z Yh"tmej \MQL;l][//u9ez  8pB$5d3Ɍ]3+aj}瀧޽ʼn?J iv R(@!CƢf2q]T,vf5 NdFq)_U/߶,$sq ^L(KV $ZH*C9_C#>05k۶[;S~ 3G"Oŭ,=®jhO*c,9\1d4yS`vj@ܼ-#O){f]0 ;87Mġ/.(DX:2_W*2hTP~ˋE؈ZPvrGz@p/)K5K'^usƥxIfuRF;9NX|zA,d3pܰB.}/bGo9ٕUݪMZzם+/#+zYf>uL ڧ"7}jEk1ú֪-}g C>!jÛ  ̙9}P.[(PLВJ",AW?C7p3Cl-Eg}=|N ڜ$ #$$ T]i;xH RJefd|Ff'|9شxkг.:c2h1BL`Ug35~EDlÌ%͙7;ڀl}{]ڌE:ϭ|Sq'orVmgVC3$-r B"`WVebjpcfCFMb?߾#q_5•h7AsP꘠7א5ZC=yZe4V~]t~Ey8}}* g`͟&SUq*RL> t8$ŻkX!&hLk_,]}n.x72pC G;]3#{ g@tG&I4vk/]\52pO>v~0#{呩Ȍf@;RsA>c;>Smf@[uGhS$BHИB-H%eL3㟚6I]97Ajh'\c?gLMTgsϟ}l2&-A{9=sn(Zۮn ޣ?\־ q56g|[}Uebޓ2CU$]~ˀ$Ԟ?~~Imoj5YSGU7]*~}osNR[sK!QkጯmßvҘ[F|2=VA1:B,lOn +^趓7S*!Qe/W=ؗS P͵.Vm4<hF!*F:Ur3]SVC:F˯&~嬐MΘKJU##\)0%ьqįjW#g)>7\rtF~Ɠχ=KJ GFXz!R|@=ɦIe`h6? M>|8:逯WH/t!$0vvf7SN{ o}egćBzS3Z`7[?_}'/"6pHd( 7I?qrDg zzhg$ m)5H;~|Ap"5RÛnÇ ٷLPݽi2}TYi+t/3loHd0Tiuj{O, &D4IsPXX ƪ(@<蓹 prU?5%o_޵A pF rVo{Vlڦ<Ϡ)'mӹ3 ͤ„:g>tsC+F㬑kͶwoҮc)giEW;¿Lxg5wQo5q^莥?ī|sFmkv>T,Έ?4'KD,\oͱ&|JwbzRG?*ͽ'=;zץ;pOIIx@y^gS-S'޾I;Q2XeK!YDoxq SIU3CGnY秃tZnl]nt*vᵃQ#nY}:Y &Yvwn2avTwpT%|LHUG0iz$(W^oLx*캴)R9O: FbO(^S&O{%:z`VѾ^+Wq,3RʂNq7d'z+~|2D!LΪ|.,BaInz87sd C'hUY#u临fȀ,{o_h@oh,CSfHWpǓ{@މ;qeÐOˣW^+szjձN< ezp7*"$˜טRjfV4 3vTS :&e'_$pbr__ԑ,xإ=dFpH)|ńrݭmwĞ?`I8vî}͎,B|wr WY&O\0 5/fl;s$v|?;z,U]ު$Hw5Iڶ+v ߎ؞ZmXi"T{ژIH!…mXEPiYx+cۙ]Әv,xbDgY-=_;|Yk W#+iR t#2-Ğ z~U#+E;Ȥfddk eF~#IZ^ԶM{/E--(G߻g FQd$EnN* b9k=)08.57,l2Ԧ*{ ?@ !&#u呚^^FX>ڍ#=#rb݋GB8Q4im}ux*5õ!2$^x]zr;+vݾt)jԇ<3g'ULO9cKGjEOzK^sM&t9 x4u=ˢO\4%nS)s'gwz};)tw7_3?, L: V?~i[JCd!t[2S{=GkM @=9^C+ivs '{ ƹ=xhea.` ^5V{[YSE]ďfvER3sţ@=EON̶ o|9-z}곹keGߏQbsܱ:~wAq]^Ի~ͧm4y_㼹<7=)GH$I߬ޤ>da|Rl_v;R^yNH :˜Ҋ %i5yV6++69H9C7w& Toյm1!RUO湆2 P 4BU)B2HӨo]uN6pL˾O魎{ G8#W,D6@BCCN, ²$@ڎ<qZEQemo՚Vea[ytgƣj 8a5ќTKWc%e l٘ziP%.Z L a^y7t| {lU:`T"$C8ت<6#:]/1Yь={^^[czIM"spv$Ge%l( E꒵ Ϙ?`s'Z K"D [%<[ A+3-XQ|8~[cKТ{kbpy嬯X$iaJY,!vc ( ~ڒ.ʽ+bpyD$YzVSFDp l,f`L\k?mIإr2KO̮BKlIfCu߅&Я4KcO&*]"&7M'fֱ]O&HdJUf2e LF-W&LJ/k#F#GLu6х[F[::@tޘboABfK[1s-i 'kbJL{:UQMcxI~FIݧUUx(\iN&C7hJ6%+ϙMaI;a֢q{s7с̧|{HUu0[Qm޶k6^1soY׆}rKHEo )I`ƃuv*kfm7愃6q|φ.(ҝ$s,u\!ĢKr k= xȆ$s!>ɳj\މ6yB&Hٖ9*%cɛu~#>mٖsRzK?IO\On%&+ޅsq;y9Y R爧BL3R(t37_ ִ=#RӼF3UO\+ޅ<ۈ8Zhd1#V<-5 t] ,qR\Ka'/ְ+}6KCJ--)D'/SK<3UK\ȍޅIoik$E!HD9KIN}_[ eInc~G JVC$M&vt@ IT9Pƍ>$Q"2`n~5CkR\`KP" d-HxNg- LcA|bp:9QQh-D:N|@0o>IRh^cn~ʉBpv" e ,:iq7q 1v#Q(^dy:M.i` :5.m&,7pf Jb J*$ xoT xac&kӿѴΌe: `%vSDOe`'U+CO&#NB$$OE(^,>٩՛:;mMxR1H]'33 HM,NQ<3Y<iS bR^ӛGZkzoČZĹEo{O[ʺF1y/ҥSZp|۶$ w@3^|4jK w7|viDBvcLfϽСלuSY,L:Eh-JڞLv=?LInupFL>','~:)Z{DlX'j_y9GpyM(mMIPJfōv~" ]/GD+f˾6&%nK) &2 Ap ͎qG="zB0MXT!3S|A lo"A("\y!R{GKx0 NIL=wc{}<-y:o) d TI Mx"M <ِL,eQ}޿ |7a1Jr8!t^)}g_~$W:3$1YIѣ6P@"$r3[ahFNbYQܫ, 'scht:y9NPRi?3*הI>)X,40TE鎖oVPp kZ\@b|ǂWiȹ{?2l#n9 LTuLz^;ҙ1~$9]S`=vwgTAAOr<̬2#e\Lj|U Xe&4$&$=@_ ꗙN`|@lrV d;loLv%*g]SwBxvE443vL8WڷM NQgy(6"#''YLNgi` m+l%u SEu0"L=pIuIldMBGx=[crU~֗{7k%I/mf_^:[)v2x-U@I%(TȤ] ήκs?wk szH\!,0yoI3Vt$>x5&׮Z3;[!HrXmĀcߠS&oNNu 2Tg%4M+~"#2E ~S\8Ҧ>+8: '$!4y7lL0Cjy;LʼnH;jSJ$if٩H%b('f&HDF1yum3i$e LkidH!,L&K]Y˅B4NP7 R3;F[~6щ8tzlYk#F9Cn/HݤRq/ѓw5X8mK'5`v?ZbR\hP>͙E1\x3lFٗ;mv-6*xWWoJcs^r׷N 8zlaGI='! T8Ǯ}~+OgƃŽ斿8-^mA4 AA7%a JfiH3z7k>v8iI&'3/u A@b ̨Pz6/"<-U4]CIH(79.`[ ibQ&L2 Yg*NXHzj`[YM ȵe(}EG5>2P @"<, z_CVQ9ՃYs̤*W v?'{Q]'dVoB D֬vKH !(`YGgԿ Viѫ67 aJ!!B oj&zX=o=\@UNjU` TJ1bRԂ w32|a)ZhAfOVXSԟTS6qF#:I^p՜bQ`(LE)G>oZX2H҄0 D2-8ll:5^ OR]mqe/mXNmeiY6rCGCO3) S~E݇dqZضbe晞\M:+ϱoէD}?}P<,L9tilQL:Z[/f;V]5~xLjũ~n : ՒJꪛ"tAYlN4p{d,vzJ徚SgJ9T쟓BҭK%5L<_gdA$I@A8-K' Uq%i,yfu8T#pjc`<` ԮiCjޓ#(0?9W&jȧDŽx"ml+|zﭵ h:K?YN˸=LcUr ٔ N VY ?`nF7fI|NʮPzoln&r-s&[ ۟ {ȧ+V&\{=*n̮M+%yx:զIeGJ5E)`&΢b؆.Kn#)K9CWwp\~V]W} w z! {kp!gVعQl&1XXTSmjNݚQ+RVr>蠣 Ci*-p|M`G?cV_Pol9۶K;ϓn)Ys>vˍw駮{LXI+:d)wEG^/"+xAI\Hukps21(K-QDxb^xؐ޿C>zg q>>M­bۺ_POs3;GI>Y "47`Yh_&°I>Lr}rITt{1?D m+T۬𨕑_q^L뮶a~W2]{~Vb~We<6-8Y ʪ&'ՠђ.p(ֻK)JJXK@"׮TLVTw9ђ!nL2@$ ,uXD5N&Q1WW/d J2 ^^]LdɵF1$7e !B6iOz쎛;Y׾bԟ3o&Ncx}CvRvktQm$$S7͖r?h?r$d(JkMq ']?ML8Pkdй_D4J"&ma`e+}󤣚BJg!*2& sDNSR_qvFϣiP$Fy 4Nc|!S︅EA Vϔn3V#xI'zDmA&v?JKRTC $ &ZfOKa\5(֘N=BEx3b]?POI^G(v_hpCUvJ*Eq9+$,3KgX,W9f b4j !LWI),N;!` }߅3A&pr!rqAWD DAvk3HBJw-r-y + &gJ1)5RtԠ#1Mn;zKDnuA L7%I{5骇! ͅq¶Mh]I.ΰ7[kM< jJ:}#Vf4`$IYTi-&E^sWh(b5%XwWM(qڄ7kƬd?aURĝ|'g+_L%RajE]wV𾠲ݪZg` a4#77}fkUլRILlڰ|YZiHZ-$ %ݬo9bH z׹K̙M &rjhX-y4fʗMعk#7sOIMp_6<뉥ߓ7#]6}GRxpӗ߶w^%1#$DO EF @ƫuR 1`$  w7ȕF*`"sPf&ߒ&8 @0Rb(>+P5I3JJrd'>|n}j?Ca@1jX'Sp4@h 6&tOw [D06r6qhoVH*0@P@`Oi{2a ]FϠ*k9u%IS쮅GjVظjbcyhᗧU/4&LQuzjM=v<űD H9 1 N9BP]'pOXmޤkcsH碜3 &ocY&%N&jd&VOY\.iGH, V}WfrwMBS2XcMbͶŦM}ÅΙWNf΅یܦYC8 v7>7Q wɻd!(חsbcg^P1_Lq C9GtDX3I%;q=OP;'ԙ9} F&>r]9DaKOq}<{m]2 ,LօO0wٛA:pH\.sgG:5G ,(;x "P̡f>v+k+q{g㐔!.'$NQ%D*MV*:,jo\;^:Ъ鿮}pH @ 1rdSOr*Au!/ n"'$:,zW 1(CI4MzC%r Nr~Oz&cjY#w]晌z]NxqZl۬Za5C9DQs d1Wz;n~ufz5NHƞWŲI ClJ"p ;9/ƍ;o>~%ɟ4;ri?sXDs,܋RX ]Be{9tjpt8v"#:0E'c*{h1 fXiN[Sc 3p7mLZu{AF FT+s ÚS!",$Nh0SloTp0mٸ/$2m-$﯍WbQkХT_?#!θ?G<3* ɾ,*Mzy .]D`!O}ϕg۾;BgҪozENt~$xH"*̙8":udJlc#q&\qo=Σ\RQePuAoYN,C;a@\1v33Vh:*ھ*~LesFߠМȒv|L:(<[}Hթ-UXPdI9;p1) &(P|{tƤ.B󅟒Du?z]0xmG\',{hVMGq iDR:,18ńwQdB{{VfM=Ma UC.T9͗.{.8g4 nWԚ+!"DTp*5f."p f d1'NН+{tszd*t/ҥɝ] s%s}3h a:HW\P {-Lk2z.mF NwEq˛r8Iܤ. !S?; |!" 9 C0|bɦCQV6Xlp+=0/ID^a=:Q81>&Vٸ2;ܯF7}ڒ]~Hӱ⟔I)p֏ޘZI**u.T:(CkIra,d_)lVɵ]J,0z'~e+m Ԩ`:"hR9B-gAͱD#ug?|/AX,B;Iqݲd^m+njŻphhHɪǎj8ג<  lu&ۣ~ Ξ'*~J+f޼G5[+KޜA5ti#Tpv*<܀* h_OHK!T#Jao\ԩЄ]y3rƾVB\|10r?ew ­W!uvc̣CxäTp믱ӝ5,{@4V RB(aLiz/\j# "IXjO2P=̶ ޮAvHa<Ԧ qcCg``䎺oʤcXyHfn*ML.ͺ|]ZpA"Io:h\3x'r-A*N˼/R.?Uu4edfꔹ3!&P4Cc@=mzonU`Z IyT*"@4x4͞Aȵ6 P8O”7^͘9-1bڢ1 T^LʛG"3v6eL y` ۛq&{r;. }/w-pǏ7p~IIއjQ3c[-Ǖv'3ܰR)rsKV t(r~ZXHgKp/iPvWsrXp$e0ӥ\Ӻ-:yy'G?oa)3҃%6ְR/ >9pgcwV7Ym;nk?cn KI'I'# &YXg\ɠ_ݩw5|͞_mw#l&Nv_ .#y@ejvRyQm.j`HC-Pvj*]"E=TD!PL͖.F:Jnh2ZШ-?ac2I,6yP*kBv_zWVmosG&9.qLԩR< o1A4cksH2cۘq,$mK# /R,,=;'_",ntb+AW!uIҡsX7WX#uUV0C}4@+D,M␱QhB堁9pDL2=:V9Tn՘93WxedDcD|pzŰir3j t F'glG 3zϔӬ D*6aFWnˆ67aYY>QlKp*X\3G>#AD1wN-r]rwje/{#= ]NnZ&[ο+g{w҆p j>+n*D3~-I[0!bZ|O9c£V =S$}FqgKO [j EHFF-{sXګ# iƩ@}b8qx f&VH!UfrJ~(cJ!X `"Wt$hBL$#3U@b5XAg̐d_6uYlo^Fтibl4=ԥ/o;O0fU}H,+LEՎ~bɋ8d%F(|5@7p-Q~)"J{\"~qfwI)p71|B؈k̯Up7ؒr[X5#қ[R7g[ۓIZ7ZNwWڸC)/}GXn5fzulOoȠP>;S/rޮ|w](u Y1s ml30L`FѻhM.~ˏёrD4bd9F¡` c_&P s걟:V$ȋ 8sbe&=˳7'G,ul|Z49dl$b"`!1 ࡷU~;c?Zl1g&ӜGOfjb.J%amGcIz ,pkFv5.}]v!T[n1n:Jl,R2jHDȼx |cXw| Iw/,2"gS@8KŢ\:=T [y,f-zh]0X͜lEK !H]YYl_3;ȦqTM1p@YܺK4iC59f*٫yZpsrFkbBe+B6_Z%9E+CI`7fNq s>T[u3Y2vqo牏$o%q'(A(PڄǨ:z9y.ئ'v*Aes/njWn~]zp3n'5NB&jaa)IU`L]WµS0tZnHI5 t$&qk̵{o4WBj$\ ='U:TvQuO1̣ѭg1҄w.G3p0$.zVm3aЛ fFf'uv&|3B(vb!qX'NB(G (]$HKӽG9۹gke9>p40Uy,S[_i]YYgL=%7mw::!9 g/JsB7w'DLDAla޴W܍0|`$ eqqjFySԨjY23O- J,QʉAʮSqՃZҝi;EpHE !&Q׆ ʶP0(X,dJcz.5z-BI#u`9s1 !Gut62[ϰ K>:Iw̝ys@ i||쎿Ӽ1?7yKL|@#R\EKfg{GtT#܉  `LOёBmdm;0*$2xKO~H=\ҍ6Ӌfs)M,rw`W(;0g2Nu v]æ8K'xpT$U19()p9"#lTt+E tuu۟ڎr&U K8$XvkM"s7+`#_HVMvsݍHDl:4Wtd\hmظu:wq B$TS',Nzm'T--cjH-reCjNQyT< K[翖{*(*CNK{Xkhd̓+tL$[nsҸ`W׏6aLQ[opl̰_D@#cm<lj&fy6TՄV=[,cBB4V PVfT4o:N%J*įx1:{;/6i$XNn.]h6QQ,b,AA(p$ а!#HCcAE4;cū 8 ;w,Q0r-K[5,I0er@5*& D-*$&ERȀGr7C&֠v74q ʦg3jcY"Ha*兏Mcv4uj'i.39M&#) VΫq17uIm$k;s~$~I"( rMT ӒFIPz,Pt[Jw@ xP}6H{!;xM3FgcYt|c`PDXz.13Q1k%ܘxQr Ba=J@dC e!ɟI5/j"0v$#دV+ʎ,,Zg6 9O7rE^o&/Q6 " [(H8nN @O!rG iBZ34 |`uoH 䶲y^q Mth=*} M,K'YIϾ `2\TNu7ToHRE2 R$  Zɢ͵vmJ6q΢Bdq@%LI^cz]q!*H Fg^GEl}pBeR6tAA SR$]}̩Bk%*l29b")"ƀAPD]@ P{!jɝ]qB\ydH9r$q ,h}?U%N Vs)ڵKC8A6ViѫmX.ax/@$$ُI40(Y;?bpMo TNzHFqSa,9*-?2IÛ@v o؜I)&}Akai׬؛w`Lp"*P)`1;_:,!Rz$!:4JCbr=Gܝ󔏝bſXn2 {!g5Wz5QYD5SJy0.tiJdN#_.=FA]")VvINd t €ͿSM:$"Xz-!n8%Mq]!LNzHtgHbë !Ef՜p Ƃ((` T/пBI]s/"̅{?eV5&.(qBX@#%\FuS팟YTctVj4IMA"i02< d$^fml j]dJ|ϟ͓cx[mf2Xʝk*oD =%ty т}HT *Ssa?O $j1 D96'tYP Fb_-]<: GQTE-<u"tGE覲xYMrK >QXg ʖu7ǏWU%'9Nz2le*z>M*y .m)H/es0=d d}{]w%H{NԬ iu3Mr)R 71K OJfIIr6dHMUV .i)+ "H0ÊlVN'5"IVQt{g%o>z/20L~w9qB;zbnkG~-EL{c6Ʈꉩ&$9"c&G$"U_z"yK$5O\8FG$ @QqgqQ^ LhtՓhGhiTPD'5t"x)6gZ %*D5t5ϲ*Zb@~˴oSY~.]jPʹ:2tZ&JmdF )ȤoCꫜVMk?2$nJVQޯBؠޣd2~l9+&5"e4\6#߲\?8D}Յ;Yt'iGö8kUMj[ӌR s9LX҈;8zl:++:p͐y_{Fɴo̳hB5,M2 YIBDl1=ȯ .c9Ww0ƉvC2a¢ 1F+aQ۫lXDHDmLMrҚc{MnW[^sݝ_" [o:R7)rRCQ L-%;@OrK!{IJ -@j'ɥӬ:Pϑˮ-ΑQj6Y@ U/;1|fӕEx#_cyܡ1Hbĉ_;#sjQ1ūثObJK׸綱GΎb=:vhE^Ww,c,Ƣ{ d Q7ҿKE$KO'Kc_Vņr7|&!o~\껺f`JdV<=c[,^&:!wէ)Fw`WE = WU-c  .[SFd v&~N] a< CC{yE0gRUN{?Uh<0LΖw͔gE1tԭ(hca_ּBAg2q'x$v 7.սrg4Lce5GqF(D< 1US?Eh%`w/=[K R T S$Ymy249n$=9//yKj (%$j1 ᒫ'{jah̦Bʒ ۔ z?+%i[LRm~ۡ ƿ](m|.^A$,*nSs`ɂw|T iǿ!/ R4iGJCdH1Q@ZxH0P+{lv/߿ [ߥ_6ծ'ī\;=_~[G3)+@fDP*Ai%CBL+%XO {nZ#G$"tQt-&WtP U]77hFkf2L3k%6PưB߻}'8tfL19PЬ 8-NxU#* 8[Ƴ h f}X/<"yEGoExྲྀQ-tJVd ~>UI!%ulpY|TQ_ؙToaxH_$^;V&aR9$`+~(ObȨë2"L$96(Y,u4m*i"]EvsHQDzyNR#mu9Xѩ 8ww)׶L8yKpHN,|&{*h1E⩆ƯRXQYEjۙI۴]揈b>I.24h˛B!~%%AehK1vI .Y@*FZ%l9͇Te4B6\ߎ*aFZuF5SmRn`GD'/ +ڷ%ʔvX>P"=?+C׏5=aUiYJ:tf (UwEKZ\L-P%ײ% DhcHu^G- Vd26#!UıD[$Iw膶uCMQqaS QZ77늛%emK@(@0F B<[\7D33o&=_Qĝ_q^=Ne#N]pi9%l?Iu9{,mDZ ^!Fzy K|x['u`p'3=So [N"w6iB%R `GrfS /]$Z%fY8ivF]993'A^S܃NB@hq>%.lfm6+2&IDIyh!DU+VվpluRƦH5^HvѸ }uݖٻ3mҽI#M0gQa5]G.Z:cvbjkʁ1ǦXubc-#*qYh CYuI3Vc'4_#̣ֆR~)tIi5ݴ!'6n$ȂC|B bDD @ "|AH"u9D8֤`J>k"şx)v0E2fټgra1$d?{)Y=+}%t~+ݭFmn,RHD$(qVV5~[$t?dȴLG0L@c!!] *u҉HKnMT[ j[OeT6e0 B0(/dWh'w}+"SSw/\t;Κث<* @Gq- ~#&AA:g @oA2XMdWs0y+;)"ar6_'rgR"h4r'鍩M,&K>ros׿dsGgdͭMI*i<e2vB6H6g00ykMa-f s1[=j?ug"0"Eϯ 6hPJ7*}bWZiYHMeH -zv]]?k@Wژ^ st+'9*9}QSzصK ו~EsB"c!'3wA128 'cz&,_jB)ʙKGRSOդ0RK7"I}2ҵu$@rg# ThBFjڳ.U5i脀4Xyg`؎vkmo`۪4i K@tƀθnT7 qiUބMyo7 6bG4 KXK[cxݡ7`UFw+GH,ܽB(l;4XSVSbl%cv7N{AƸ$H (0\i2U+) hց+ +  f -1+DHnPvqΘF*K,1*`ht g0Te4O{pstY HDG+yX@E4o`)Q'xzu&8F34rDA) d91*$_zPHLrЏ\o~QtYqT\k6|lt!AtTBu$`.SfFՇu/o᠙m;V%A~@0![vLaZbSwЛDepPff A]C2ks>-1/^ԅDt,ҧ%./({z8{KZ\~!(;`̕'Dj qI 'Y4F$ x֦i#RuSWzctbm:BfbrS@]Πfߍ WflICfsnkl``#ftt94`jiw7ߵZWCKտmY鬶IyBͿ3 VL$=\rs'g_g6$tylRv'R$;{ ,3 b(@3kbfb!@&Dx| C^ ~ W6B o A)_ ~%?Lyhc:CoG'%jjvք\n;DvkIט18lE$,"BGhO#M*>DmP)sۗo˸.,8Mo aaV+~ׅ="iXgioo_*|3O^ Kv7TkJ.T`LR$J@6og~Ȁw+]պ'զ(ZgWk`xt>RA1լ%]˰ejDYOL aY74жz#iAu.9`mť̝[$P…ThlNuھ AM"Ff8/j+.iŪumdɜF72 _YVj皸e54[JgvhqQZdr\5S/^x-ik(-BZ߿ {~@+A2 oQL%6#'㟳{)@PԖ+S!Ϳw PDq$AJnFL3dEFdio8C1a4Ns [SPߣpQqLPiV;N}7Pcv1\W>& t0*z:؄E/+WfL2*ssm:m\ʹTj7K$K}HX>Zv 1t{UmT8D)5) C BNalKļE|s3ߘ+T~f5*Ψ*c[$$s1*׊q8s *zDg8}fV3$͗KH%_Ӑ‰Ê'?|ܥ+±Bc_+C _R^Y|5w*_>FN B> gb!"B#W(f\Ek3 )#V] [c>ռҴ z4Ad2pT*:Չb uQk2ԵϬX%KtYfNl nVc7]L;@}֮O w$.ZHV pĀWepr%1'y-Odžh4Ehjv9!Dn̽?tڵ98Gd !Idg <ͯa2dY azfG'˙s?!}7g~S?,̝ {?w\_jrUM!)JU7-/|?e]gC"G8oA [l ֒/$8T\z)"d뢭29Wέyqgrz_r\$ +Z{E\dJUgӔ-v97 ϜT}Ӄ}5s̕PmDCzoݵWc;r00T:+'& "dF+ L*kl⮳,"k1N^ qqT8H` ` $CfyxM[Zk/] Ng";:rj;;/P3jI5@G؏0rc],"ϐFHc) ry-~?ZM QWD͙B UPD /Hއ^wX[~%' lĴ!J v\'< B :mk@8kw~ LH5*8煷[Ym~ 9F=(PO)$ r'@8{HU3_A5J՟\K~bDPì_A O,k/z~;Ϳ D@@]U1{<nRc}pT&= ÿԍ2 N]?)HʋjOAL)?ySP_x_UG}[>bACp?II]\S+%?bbU~yGl;M653~y~~RH_້Ue,2Bw~޵G,%Pԝ&}Jԁ~ P9df]5+wC =T{v!3 #ڻf90vq U^/>n~@Ug:6o~w˸Gx &W͚vy;{u7+dxOOy/v]/uiE\_JrF$pUmrWf z-\p7y=vo'-Nj߷G]Cl]S4EW@yz!D]HWD28pKB(_aw:0]c!Lj0-ltFyS Pq4U H?OmCwI$bH -9k9'itSN1& ۂf2=1oc VH.yZKzpZKld܄: ʄpe3+YWH>L͌хۚ?9{7ټ9B={&J,D^)4 FW(pd+^=T^{:Aؽwymb (֛bI@zjsfŖB,4!GlU?$M/Ƽ]XPCj8oWǷAi|&$IG@!t1X>|̈-,"lodx J0jF#by Qu >G50(D"qL m%ȈKFe?iW;osᇾ o!Jw_ &/0%#[ /F\aÞԷh=ki,KbD؁zMOko؞Xc< ֮G?krN7$ +!4P8 8q. ]w N<_Xqr 0}$җ[O"J ,2c' Pӥoxr_{J_ $"MP֗9}>p"]Ե x;![R qw6y&\ym $ejRGc{^XӄG¯OZC!mh4]w@ @ Cfp1HE~ X #Oח!|N?cp{jе[~op XK<%@9-(yfhzD-}b(,P$^cD $eߨb3gk䰖,6s>BBZ4 K8YP4H$ xه#2`i LS)-ߴe/X T?mh]BH1/lC"5%*t4nNDfm|B@s: L H`jou۾gUR+O @*VIYFH4`!Lf}uĮ?HbVn"iM }+ɫ :_]J[MQ$bPT>-tio&5j0†g?:I~&2%j6 gZ!BQxG\,N|fYtAÏvMYH<-_33BtFo*E~oJ[\ ,SAtnG M;FixgX Wq֧gXd~>VymvkKuM;[|m:v}9-%ص8"m; tg'xQ_eSvͪ*^#Wحd;9nρEp%6B&7AXo.؅0rwj1e|"j=j;&BT^dc11rM0 "g1U凨C,FZ7{[QUimCDObx/tƁ#(Sw#_ _O|no|_QҥP!D5a>7ȹu+MgO3LR 겡grIWz!$[,.eXYZ$ ^I&O![Xfc h(!ޥ|m `A=s XlUq=CF8q򢄇8T֦bV=7(T$lO;o"%ZR4dzQDdAL^x 6UvʐW-<;SBL2e)JTB}=1}\MqE-Gҍ~y C]BHFUǭo+X+":hrvɸ1 bӈ5gm|TY7+Z~(mRdf:? b(7_grIÈu>Og0: |%H8)tI3k^/I)" ⸧ͷ Ŝcbrߟ PVa(>|K4G6 ѽ#Orj͡5@y }7g_nXaa!3st,I^MQF@'kp x,pT DvuǗzTګu7Ddhزxܠk^HjqxQ0D F{1*BD/{?oě~' s󕗡@M+<[ h4l7 'EH6Y ~-n6O;KK@p4m>pHBq_1 f#H 9Ed翠pK6sjeqR A.h63Is+"e+D[Q I@J' @^-Wހ1qLZbmEښDd$t`D$&J4B]'u} ?tC-_vW\']0f_Mz㝦$ z !)W>/8h YTWԥ7wfy3 4 Kw$k7:7_4b:~ӧ~~~wrmXnK|i \!0|*?JYMzmpγ;n{yCBeQH"tF:H_Ҩضy43X]Nzg;]uW&>TT([4-\.<4yʂt>6ǩ̴8!L@N^{<@?g n7~ā;|YEAr.BTф4Hв%[wdj=Dwgyq.{Z8F]U`#syj[ZOjR;ٵL@"K]5H\K^%"k} r;dE|pW9o]wR`}_:\>!%u ~蘾;Kh fw}M'o+'R>$[k̟wxʹi%6vYy}ltUDOJXW xבk׮"B>GQBƋƲeКkk{ֆ+θ#67͚o|#|?>ݲ!=l*p8xʄXU+D~Q.Rͯ/$-I-β> 29Ka!s`e_i1Ї6ڐ^dXrS7/Yu.{+ZZvN'͢Xt6C$ %"b_<ѧc?\9G;$#a cHryB/=?+^aF]~1yS'T7CƆ<*oyι%;V{WX*a G+N9ï"ٙ.U Yء ԼCa2zH =i>.oAtjIWi !p?DLkYaqg{ #ԯQV.#|M{飈;*t 9|_)aI1< QXN|J=JYo2E%>F2)e#1}PQZx AjBi W d{=6xRљ,4wy,ج܊$XPxf)\0liK;ǿS *TUIFC{QHKUEn|DY&+چMEq˧?Y `tW(eo 5+ /5l4g}u?bTh H&՘ID0m?^77Hhpc,{8dƅC"M~]O RUB]"0R2ʣ cD!8"3kH Z4VB(O%DM$ahġXѾP~HB, qJHTg+UQ\I7,&q`듺\ X#X  6l7"z^\Ahxm>f\{{,bdԲ蠛 RB|~-N,!=2>K`$'5{\FU.ƔM׻@ ڇ G=1~T|gd,x@"(،ٖ5a2pS]RVzΘ؇ROq!T= A<1ɵF'&V}ӿ6[SL^㞿F7矼ܷ Ʒlwݸd.04S I[ƦWOGִ{k:ұnOW!O~*r)?=Yᐊr)`jvqHkD;>M#OTϣ6v@vh.aB#r<"H ?­K+*Q7NYK$]gQJVI1d0Kn`q&xcaJߖLV45>jRmj# Pv B|jD>tѨc<{QyP]C:Y qbXz:E阇.,ue.7|hlcBr)[{aODL֪\ P a+*JHcaI=dxR#HҸcrͮzH:c:u3Os5U$f*h6I(}Iz[$M̜@rʷ}?_kIվ6Q*8vۺ3 0 GZI(,%9T{@m$ÚO53r!@|!jWiPx(i=Ÿ"9p: ,7p5־1 cWrzʾ[fKxP>@W"q=žݯ'7 HŢ oB@$ԞrBS/^Q)cӽų4W^1OQNp+1v\ 17?B~BxI8^]!rǫ4s</ -‫b,RJ^,n6Caр 7# ̐5co,~@ amT}AZV=`NjBc^q܈!tCVdpݘJG΃|Al$&78,5(TIA@`$xkϼKQڰxkfs};K&A;7 cK_u8E4]vίa,L: Զ- U\/w\u7%\ b[v^& 8TW`]P>.s " )Y*̑hqL0ŽUfoz2fvYQ0:@<xbe؉]Szց\1%{P )<DjBd$Q\?/ze̮}jWVO7}_My;|ae)AZcR Q(DƘ4  $l0(A${MG7QY(! I2&6oim}m3}~$%\EĻ<z|#CO)J+\I,܅/`a;KlЖp%j*v=gSR3QYWA'|g2eh9 ־HoYﹳ~2I*e->B@µ[p*.U ժ?O߱Ŷd!+bњS`rYV3O`o* 9[7|?~y͎?/ʼnjLd H}&\m#OgM:MЈ~|MK$(C-j,XCẽ :WJxzCJӂe\ʴԖ\ىc,>Ub.+)nJzaz?y 41 nY` Y1_<rg9kQu8bյ`Q%0R),Aj$Z沫SdWSQY}iݚLH*Je Z "Ƣ1vW|Y8 SZg(C\MO=>֎m.4L{N)xO Ě5Rc7W ukvծ7$R4v.@3V~/ڹ'nZuV\c( K \I2Qatd訉t$ck׊H.Lk3Ө6RN' !L2UBFC.*-'.Epyi,c"  /İͦǤCfEIThT es_)xX׼P\9eߟ!yxA|@Cb7|dX~H$ekwA jlOs& iMrc'n Xa @RK Ң-]<$`gI82pD*r (>Yֶß'$vB M{' Rg+D*FAK㍗t΢;] hߗ݄'r>Ir厡b#8pYhh*[QV2B߼㯴bw}5(}{b*Мe4`q<`=_K8BwIHkK5!"SA$h h_rssM+M>= H0{6D \pRIZ x) 钎oNGo˼ 1s% Vu?D/G75iL&|.X`C/-C|۹$U9)9X `SR,\sUr:pDALRjekbE\TH<;p !KG^Tc%+9!u6>e`{c.:e s$B8&j/ˑ̽% (s yYZHe/x?O&לRBJh!J r𥄺E895 ܝԑ~IcO?3N WOJiョkq6vװrn׸uqm_D{qx&Ngؕ1HuO=J_#;-WWyeEfּݞWjFua둄ŹM@a$K-uLv|Wȑ?{_rط!IYORe.]uBqK(i=2[)6Jt#G|j SSmz\v>i=+׆Ie|I]'%z&v aW% u%{TVUك"oEOFHK(m?ӁIagp1h 2"!qSб% ;0}EΫ 9979…QVy-ngV4:HsUAAmm]{̵9xaPԘG1b$6,)WL[-78/!uLWvfy>mʷ'T1ݒb,TAC[$@Za(–>~|*7-ML=.ޚd_Zosa0 ~[0_o~gӣzI釔"l3>a9cIa᧊acϦKz2Ư!aĴ|aA{ 1  T Ž&E͚nB~K?di,`;bi}͚ΤHH}6OEq,*NZVn>nHY$jb C<('M kIʼnh34@m`0 n]bEU$>;w4g* ca*4\N o42pwm'W:A(@`%QcgT7#RpA )M # *cl;nwߨHu)gM~Gx'[~Czo twYy{/|7v;@kVM6 . vT `'h~oC+A"f!2ȕ74;YS˖?uf( 6o;y_m!ԟ- C qwMK9ܒu*}a g瘄<ٷapԟK6ݧ%P"w;wܐtzimWk}uϾAk"H_]Hɒtn=W&]lC}>z{Eoλ]uV%AHͲ`&[gKKX͊*ohzF?m>+ؚ sBh8` s ?Tx5>|+Z1~߈/N? J:p8x0К2_21ؓVoh* GO>33FiږLlT%vYn0~,(I#p$_UcI S޹tbIkILFP#;5FVuoͺG)@xwi][H]}TL4®pS,glmhHa(TMoꗾm#@il vv*_>9Mc1?]8ʗ_7 qh$LgVY}Dq(Z Fmּ`IHhv~W6iI6M}Ol~xPP=n y\,upkpgwTی⪮3Ή^`Nlg-]!m ߸U\eLC55!g0ȷOϥ}f?7Ztd gf<4ZfYloo}mT3T-&4G ywtE\ٜ Dr<19)$8Uu+wҁm)Boݡ.ʧq$V,ts<6Bg!2D+d.ًo> :Z/BtC9 R{%g]#~z+_l« ſ^>7;Oy[ ޭRT;T1z3u:DjXKEX1DLmkĹiYNi~2:䘁]v|qE݇v=r769(;;⠁1]xAfng,vVun/ZWpYz=H$<5møD԰t1z9V{xV(u4.[yzVQw+7uJApHB;UwSH 1RByvr3:CZu[<8Q7a.\(Rw;Zץ6ڿx)/ڄj\zC饳ĉ ,wŬ͟9R7BS! "f"f'dݘo~ ^_Yc޸U%%榻NHFv)0 c(𬺥ަy =NɑL hh HTeq/{|oey[8}UT r&TGKU~j_ =gW^6o,**NHE  Xu*߸ڕ/{yߜrɜ"XqеyHY>_eܦ-mSM |Foc?>X=ngĜ{j/'@]^{wx=MT8T8~p[-siH7sgb]tٮ2s*%-3@ށxc8Y{gV>}WgWO =S>,%S5}7z7b2,ӳ/ot+)p&%{ccۜ7] .p\liO}F5L\ɫ_uGu6q9=> Dg ܡb3:i/ЀM=IfWyʟ6(BI!zax!yxseA='QLRɏ) ~_2OL*T"Z u6E94ʚCzW5Xw&YӸUD?tSe [.?^`~ iCyX::?o.Ynz:ڙX4 h&DWz]Y[J#vclx_X,snE_9ܻO$=%(܌E!–'.Y00Ah\oG9$i(ȅFCg/ڟXgo$#V}N<_cY{gy@9go@d%ΚxUXwYm^_cYrלּpOy|@tt0hKo{dYs;gQM8sW^uH8>\yEZ͕ 4F*D(-Ýp}ia3'NWݓ3CR :&0ZrW! ]sn9ikݓ1[RSka~Tf&1n< uIEWκ[R'E;G&?Ot[zxINi% )uhZ**Ilwwz]e;Y.=TA2|Q/Yӿɬysw;̼$뺡nS>wɁCAf6Og̪6+R,#oͪ4sKyw6VE9Ў4aE5rhsn n䫿wwUnbK jaLQ"A.g4ۗXZ)/ #DT.a!Z3$1$RD0UIKo=1)C Em7SYcrhZ@YX|'tu?OŻ[wq (Fed w|c YR{f{\bv3y; %(/if,I5웿g+< wݛg0}ꭳ,K;ۑ==oi!6W]k!Lj^p.+Qp Mv qfDhgzԿ8XBQuVϗG)DDG"ƍnǙ񄳿Kֈi&$y3|^eoa{o{4_>8/ݻG.D_bJ sL3/tgl]j3 Uory f :'^G^smcVah@Qy-c/c -uJkUrm&2IQ)Ug,HⰂxs()PS]on[s{E׏FT "€Yc7UohſOocjǿ1fC.'fCR C5bdOd"al+οq=v˿3 ƉCηz1TZ54ȁ3C5-ν>;K郯/MaB`?=uv=¤zޞ($[pLx "WzcPDĵn d%#m TR!{UqK:d:se_#CCRh}^ {<ѓqYZnB6/D]-J_q enP**zOS-s1|IvS7J)- cp6OGOnww%ihLSOz6WYx4;a*ܚʕ{BC"Lld{h8"9!_ϜГo~č'ϋzۼșޑze<#]2O 0Q[73Id_ऄi%BeͷJLС'O ^}_6K}mUnVnN{xSy۞?}J]pk _g/$ =̊눺);!YfHe}yjc>} diE%c,vgVҪ/׸ gxYF;Ki7v_OOn_{̪}ɡUpB sJW֯vD~љXшBux~6F0!sc _Sl N[3 s ʨ156E;LVDaa^=K@`!_%)YjX AU^m=38ܭ|;Ja;= $N> -J5XQ>&H Lj A: Ǧdlƒ@pUDoQm_r|po âg ,4I \oۭ|kO@kؒqK&\c]1vg=yz̽< K԰mqd{?:p{^:7Oxt؇UssJ22Z?N)hM7_zyF/#;Z* u 4&zxM:9'b,IzPne3aBNXa Gz3mAˊN#˞D:eNgTNlɏ {r**,sr:'V!xa" Y-eO6³c8>ev*$I$"R~RdZh]WTC۔$g\&u1hE]fi!!.r6i95k禹HAHnqIté~QX; s1^51F A_*0;uXmb";]SAӬbiWm2t?z_GZ!r.:a@U6ȝ8# EMeN}shw o_[Knjŗ3]JԌ -dT!"rLNGk~81nlGFx{U iLy7^ٍ~r[SA'VV:҅twךi_/^8|S u3w;e1g-uּ=гBn/K.3ȿ8O/#` @CsO;`o{^i:}./ ^E>X 7 >`z/dpCbY#t|X|GR!$ If uXkŨ$Dˢγ`AD2?(CC:mHI (׻!7%PA%Is k)l Ȓ(1ő^?7r`Io_&Xr*aNDt7#TbSrxH($4U)ߎ2}٫Ul*v6UWzԈj"hyg+fNx=P&Mm408S/ilU,  5pUd+^7͢ݙC9̷ɢr/NY7R-!.ucs\XΈy2mU_rڄSfQn%d$xhou&d wcc9 1+|7%8Dp[W^xgFu|]VmEvxÞ/ػػ(upB2,P:r}dϯ$2E8d:sZVKUt̷I݈@ܓ⠐Ɂq ( ebiմIݳz*eCX'+!4 !).Tv<ߢT7|;z`}xtɡ3o)PC@1v[򶴸()X4;$7p$x8~'E; ugV$8e}E',@o樜1,{vAJ9*=hnBǍڣLq$2(:Ǩ$A%(AooQy{Bۺ,CU'7Up0۷P1ih̸mn6o6HC@N&Л}d#QTPDPo Zd @?!M$~PMDe$KmƗv=]^ vemCKI AVAW.j׉_[@os{sRTLR' sG0[ v5RfP-]볳|^QyRҶѰt ϟm1ƝA)" =X%uݛhnsD$? L C:$!ٚNM(UPK hL5: BDq؛:D1 rzEŘ455Vӈt=SO ˨MMQat3Jư'u#,@p>E:CH:'+O5!BHVutٰ3$wuBzSb%ˇ']e+nRh{j_b%F/@C >8wO/ biU56fgVa9wΜ~{7|l-cpjd˶SQR?v@N6Շov?;9XR0,n( -l@J`A!!p*ltHfǜS?M=/LpK޲&ȐZ : ZkbQ:&^H_HOuΚݱ;dT3.8}<=DŽ8esK/0⶝X,~q~J^{.mտ]NGw!~ʷz{u]-eYvW||(Ӗ1$|[D_&;lzG/8UW-?olGRM"H$I~#V J$OǞ "pHm]<2ؔW:ےkEis͍@I|D?=BŃּ-)LJh2 YwckaҶ[wY)`R&EGb.eF"g:DX{;B$26Zy͠w狔Iu# V,t{:Z:k~Jƌd p({53Ekn ;IX1!$]I 57vG01S_Ϝ5_([j# oIdD]CPgl2r*i: ӛ^0Yaߺx2S|c9:6O֚^3v~dRG|<`Iyo兇|.5 ֽ7S}xinoymG:mOz2#ʾ:Z!mP5:VRҳ$n"!\F:5IVP4y]֐qK@рhm$`SCxu7` {A҆ ^~ T 3DAǃS<AwiìQy5LXs !TcParVdXcA{+2\U.*pG5^JXhEfYz(f [1S Y%J?f HI2qE~*/8{lRRL5g"0b9"^$3pQյ[Ξ ¡s}R@H['ՓAh쑹p@g(΂cz(@a61Хh*dI`Hy];b\$x[n &F'toPsgG{~Ҍ FlGF#׹KUkqafcu-ϴ} {-&!ϒ͑:4CKa,3a_ɿ g+C7_qXzY]儐/W4c#% *Ζk!)-.F~N`VaBu]xޟ8)wT::Pe8Z pӤT *t*b_)K^_;+$E,7'7(Ԋtal\D 4Au 1J!iDgxcK{Ō.A_Y%ʪL"kD(#1$TZ xm냒 }l".j5*!ÈnŠ9* 8XbF̞s[D+_#ohz2 (Lo)%`$0 c#yy?]_i~B0!pꫮ,m K.&(/FCkac 5l:s_z z=,֙ů_ҟHl9vNKt{Rc݌A'mNHn6BD2kt*՝5T ~㠬}߼;-hAd6 3$|-:[ּn{s/O\{n1pկz˫=hٖnYQT0A%C7xqH1]7̱iϥ"4hed$9C@,gt)',0;TP?Jh*˨p9ZR6hfP@-AQJ Z-!|EXjT {Q#q.V?ԐL:έfxYXz9NEarjf{{P`0B_e^&^7{ΈW?ԮVVƼWd:w:2u;@$I:dzbe5ڽ&"\ 4'&m8o݌XzwӇF֙>o_^VذA^3oWQ*}9 {~T o»&z-s4gϊ8hv:ZI+~G h@Y-)򑱦x.kYFޠ?[/# c(i&^}<5bbmxqe#p{zibգfVDTbXwmT=ܥJ->:,y >(~%%f^Z @)N_OIZ?ӎK >R5GmLT2|`t|SQ5=r%P%P5c0PĐ [ E㐈o iTaTa`Ki{߹^TPVQ*Q@f~1heCbjP!% bT<*BY" ګ̘|нhhaF6t+XV UOY47VY)Qm^a@% Z*|Z5p_zly2H+ɠQb;:wt/]WƝp)@&8@= Dl~+DV#<}+ɋ^To8*; ׹< ׄk2T% oWe:fgec~?TCq&ZU7V,g~Ⱥ,e_m׭!PaoAOB$Q1M0?#ϻ+b)"n*,<\CLIW RH*^Eb଀eT$ Z# 6;)UM6,Hap~v7 `)b@sB/1g'"*$" 7;A`glPkx Iʥ7wcoσM'KOo7bed gWza|_e-9jCOңraL¹63+$I Ld0C}[w[Y=Q1y9w3#:O+}.dhXiuoW,p9,-LF7lyAKx؟>F_[;P.qUU09L1,[b#Ïom{شʆ7Nt vȂړ|eіA8%ƑUo).)oMztX)#phuB6K , ƌF F %K)La(\ϯHW癬C9M/^>KnH.⸏"6>~RD#2^ Ykb..hOe:q'f?^2.gYb5ܺ# !zh-\4e2m}'f%IgJ݌\4oYx{_Ԗ 숚~Iʲ XNEjʏu F`i{ ߆~vWci۟&A?6=t }+ C=9c&ҍ8e´[6;UMcnO-k'eAHxVE:$=W=y^ܽo.2?Mu〦 LPq !d~e B=i2L
    p0gv6]RtgDW0vFr~gƘsyDwYD>KX_Y ;P LXsǒ9*,Gۿr)Y-q,`FR -26|cnc0p_YCM ]>`&Y)]Q%Ϩ ln4.#X@WEUș94 Ҕ %f \K%MʰAlad , :> n%FhqiRt~ٜQL^2Xr1/g }et#@Tt䳡w~ziݺ%pܓ=%a %kN޵%rmno`EFB~H 5Bf)I[q]nr'I17~"ae*: YJL}K4SFܸ?*}\вr8׽oZQ6Fc~-4kkDy+~j⛋Voxd]3( ;e O5˗FoD%Q'&(#F&$*`(h]3*VAya(E׊ĂפZi9qR-3S c ƊΨAD C"pGZSm>=WͦA $*ʒ.;mg1!P dU)}k*evV.6P?cm7O˙NW8~H`/)gYzT`Z2+(W"(&̗u 뙗:1廝~S>%J-ժ9 ^Po`AfhNY/y5u#WL97L ӈ?S{t'fXB=paCJ՟r^o c]U,>N6C .zK\* &KF8 :aĦ?|)'0œa^4s]dY+b'E])wsB231ms|;"xLg#Mu,:ި ϬB5-=S-ehSJdaw2r1_R(7y!CV,d;'vmT=DfM[l Okv_gq~ZQ(%.AZXfϏ4Cѩƥ[g$`6hNE-6OI!ú\[ ޔ/'>]b#!9]+d۝|'uk!QԘ'tTƩb\O@Y+BFSdSbEJ ʐb 09Iryőc"x 鵢dly EfVNe;ᎁ=~f] \?;$vdq~|WJ%Buq2c +ݓ$,+1Sd׏&ʑVB:U$2nvvOK%l!6#_ #!&JH"U7 !Hfysnu}W\b{EnH#9.lAlS{wMp?1l 7l`y_B*?>#KV;>g:\ׅ"tb|.Gfz~LVX@;6wNV_+kK*%ӪvIU&!?AoY2}(~?4`O" (>7Wm51 nzwJ!AUk,5{H Mu}fxS,su_,"M .2c.+~Lf}3 Krӏy+$3aB2DCe+t^Fa#4 46Q{Zfcyӥ Ȋ@~ A 9R2cb2R @vQ vY^`#]DQ%]|%|/*1+=#> ~],NN W̃?]+[헙ݪnˊ-*:%Zك8$]#-eH/n8ްx ,A\iRȹ*`i"p6зۄ-܉ۜ^ pX{Rtt@PdX.͡1//~&WbF4 )șZ %1msͯ8$쪇A)O- J+q ~vb_cfXmn&}8q @ @eaEjHHu 3 [4}R mH1UBVYP!`D75^HJXX13R`#9ON r)k- ;0F%!ߍƪ $kősSOƂ!j<箛D]<,_ k Dt^IQtWO"K@ ̎1@`X f3(_2?6e}0G(3@KtqFu,o(P) 1s\*ل?2Z@F *uK$@~ AA @iSun}u׀VouZjɽ&Rlk~={|e, otEwr;Q T1%xwU>J0"C0Ea@ fLGDZ6$^Wfo+~*M6׭]uԙۖݺ#[2 }Hnci(ExfFhxW>8+NFe o.f4"HfvXw(թyw! LF3I!֡"zJev<Oendt\WeRf6}"X;'j(5OQZN9(9'} * ".nT;gLe$4t+tZ y*gbgHU;GDs$AGqI V@&Ai"?6UD%n `T0Vʍs}5,* ح?b6c8K"A*T.o\_?D셄(E^;5@)ݭUU6tM x:d6;鐤bBw?0s0(te.K2b{:TV}t՝X !XJQf1kX :0&rHʤ}O ?~o L]?$^}Uh@6/cLЪ'Da$=6шd@gQ5 <剑v 1 ߘd>T#D IxuK78UmlC vhʖEnv cfKIib>Z8$f.7ĈlpG/?m3řb3? 3Z9%'8'/<90ƶ|@Dp*Y}qL&5/7%_\ o}+2LB!Sߤ~@˘_ϢqJnP7<%HP\]3Rz\MǮ[VjeDAf hA$le'b3BKɄ:JD_2%EsM.pȑ9O7@-EN"n/m1Ws_/Y>Z@iAq3|Yؓ CH!XSǟ7HoڏXk|awe p@H(tNY1" 00/870'K7 DAAĈʼ5kMq 顅VuO?[ -cmev+Ocյf#3t4]^uZ\\D{FoJR3!H0f%% c,*u' etAj=IOm;)]z*jO9mBS)pzQI/ -Ƨz˼~51qJz (ZJ4KPMA'G{V Qgm@(pL8UW EDf ݱL;:+yd["$bEpipl`=ê=8u G>a:aE)y̓Cn؁&T qq> ׉~ӱ㤸vz5mME%<7 2Qϓ1I/xؑ+h$/)8QϪ|g̷=}s l{v:֍0/L`zW}Y$XxR 0c=;?QXHWLیrw Rt?lZ&BBP@[}"QʁV#Y޹l@q_ T!!%nD4\zI["z[k!ϭ-V<Au AmBBQRngoǖIprg09JSN$@͙Wuu{ p,)z9fL&Ce$D俦ذLٹTxK^2͝ 05sMKTJSy>ߖ *Y$% Ss8jM{8\Hgx=hiOp ~6ˌФo}oů|Ԗ=-T^\klshUηVF@r}k !;EH7Td_ڷ[Y +Zkit&Qy?:m؎1騢`l'qEԮK3y$aL0]2`ìDa3:})AX$%";rݳxی{|?fbʎGmRjv]~Mpޔwp ) ~ԜU׽8odUcϽRs XIݎ+t3,n*cC5[c鸨m BJH toPڅIS-T*˘4&WGi2ps"ve<:u)[R,ws)bkt(pA&l/Ӄf߉F{K-8"GL+I:ӄ F]=%:݈/5aT4Mhi\@MxlЌZ:Qm JI&ncE)/2CEtY2*)6b5(h̴mVb|}_Ƞ5531l=W0u+^?; clЋ;\sČ}rM.bO$TQqd27 s)Ī?*N;dhぶpč=+i{EYjh"8Ph)+@Xm CK#s$뚵 azǺaBp[IZ%Nmic,dX5tB5ׂs/?'E3]ٽ.pn4ݴ#~0^,K"@ ( llL  ?>5=^;c~zZqD)=2 @,ȧ7y=mC{yA ,"JT1!5aZŊv<]"l~ixG߸v-(}7z#,1Q5?E% C|8`d~w*nW0+wxB#׊pSNÁEX e[, C$NAhz Ky!Gfnmuws' ?* <Ye8sP\α\v*m1_J!6v@!I0Q5c'#m öAh?+?G^hثR=Ys @e`Ml tb -k,ǚO"tN>O$N6 FR@6V&Ubjyc"[#&n=|:xk %AG 7"J'k3;C>'FSP)CO c8!ܝ27?yBnE&&EBJߪW.Vp+E2h]&~4j^frs8Ҁ.IiՏ_w[W6 v{N S`*-f$%p|$l̸!bH l%nr, WƱ/fTʐT%*m0w3yYșWٕQO7ꦤ\ڈ#1K®t9CۈgmmYۦGH`b|jSR垗Te!`L񵖾Lƒ$0aV&Hp\kygD 'iM`w֬̈́ĈdK,\[%qC7[^t@VÕ ~pNojm2-X%0 H Aa'g7l#9~ ˖ς2YD,d*I RŪy3`9ro Tk(:̞'۹+ rZmT}/l{ەoStлذUE>|mn,G~J/%tp|\KEl>Qz}5"qL3Dba|2zV4>GƬ& SwLy>G(ɝ6y {Y1# l)aPYibƞYS5 .]WtD\b߂Zgr{CRa k;\ߛe඘8K3۶gFߒ{{G4 'B냶{n)rG64굸&H%9kZ4RM"G( ЙĂ0 "ApIb `.7u'kc_G {p]O5A/k%Ny^K<kAF||ϱѱ)?>S C53A l3,+-?u } @w 5H :*G|@G^I:Iߜ&~ LjYL.ȑs +p>5sU}Vo03,9VADi0}_7bT~  X0/  aY˩DES9o~r좀r `jn#"Ro\7R`r:Zg||9.@]_;g}v͌YoeI#: -^vtqֈ (!&fbmE=c~'D[e5ZǑUF#[IsMlW גq;.cmneM獼XG4i0ECY%X#Re`4(M`\Kˤ,$=&IHl<*mk}"Oo & i3"”&4Gms.'u)h&8.7͹N=I{Nրi[`MElЙߥ@ٰ¹ j^&34HØEJK.K&!},qEo6{SH  Y՝ು9&mcB8R^ Hu:m1vjCb>@.6iN+i4hL+BR,6iȒ2)-*8)om FѸ-ozEf۫n.=HxO.M];`lmcq+[W;"[RH} }6Y WPv-{}M1)7$rdg.>uD|2(@7c,ΉHjOLmKfdES[|WKP $GCݧkӭ ꈸSG?2e"?}9\8K&qcQ(qA=wؖ;{ㇽ]q͵ÈNp~"-$>]5߾}?mOMyE?| `?UES'DyJw ^N@z>M.7RkځW=O z-38אWeKiY8<3ox~ahML ~=J/. |K)äN09K}@ՏSNwAw|UI/zY5mѡ9)0ob:BJpKpE̫`JsR3 yKI%_?&"NIjpN7SqkOn ""A#GZ?@9=2^ &˜qq\5K=]Cl<ը̶ƿA{^{ *DΠ*Gb{Ae(p:téz?4 oh*ꦬQɝ0ˏ:DLUk<ސ٥WV^~ uq W)$e" # HШ44|Q`-@[֫Cfx_΃p[gp]F]v-+m9 +,֤Vҩ:߁ڬ5:yӝכhzjX3 ]B+[ZeObeX#W Mh1ZZlE9[ a(٪[$VjH F.%cgďm6=93TRGL Tʃhoh21_ca0bH ;5 f2&XejF KVf1+ЃSIҞ@mXN2+th"feVD4V6.LBZ|nYy`h ,fujID<zި'&U&'V֧s/ԽLPaE@B[/$ wOcWj(re S *cmK!PxCqwm5+~pk&5:Ip | N1 E9*܏) 1nɸ8=C6s1wSm"uĆ*2׃/+$VXxeȈڄwos7?1h̜S"sPc\vs{F]ΰ3  gTvƩ?0V/רrvb0l#~yZKTxO\stC%F*q[_,'@ \] ~צ צvwKuFgW@{SWkX[ӛ K 70_/qN0^~sSMp8m4Ї Z`XxGJ I-~Ԉ`}a*) 4jٛ8q`A}9瑖fcmy@zu,sY4sO}EI Y}%;غK {܌&;#`5 @d'6ud]7!hFnHY-5a&)(clg..`t|m$Gy}ߓ3Yh{ErQKKlHL̍; G9+)HZ:D]xC7vg @.Msfdp$>W{FV%@A$ ꨆF:V?[g~KBMqf57Cy/ YRnHb)U.R>%e 7ϨS71|Cȸ; ֢ؕlSk [Apn#c&8l$ǦxwsӯA-dwC1>%^T,d)AN XXlgƜ q/= #g~6-!%c!UXF:we^|\Wt( !T6˻Fo]pa&ԅ{ŕe^|wcǍÕQ:F,-m6kkjwuGr_7 Q چH ۚL~}GNzA\NbԉqVp]fێ  e^5螙)?z?;2r5R1p[!^>l' _ߗ% dKq(H+ ٞgb_B&CLϡ)ɠ*)B_t&p (pzt :1ےHY4B!0^J(Ydb+H%y>"+:Em4\$]E(x:_y[}HfZg! Ӷ ˗v1CV3}߻> 9Lg.Iܿ۶| DePvۻ|`ȟPTACSAk?h6eT2&GW:)V/1JWBatHD!F;/n1.>+$ũ)?{' 5I~͖=闥iLc@.?6&hÌv㩖^*ۿ 5"C }#ZZH$ }%e镂C"8m|C&5fS ];]gfbǿ@r9ϑjF%η c-iPj=/i"lKQ >*͞S)Kċn/x>YAl=8-Z#vVZB:7XE$V.~7g)b4;[EQRu޲m yҿgO|%-Y@aOXJax,Lq/ d4fZBPS flN=M9۔ Ǝ694^sen(11ɇ>;R+ NzTJ+a+gEXBc'~{ h2T{49ө5Hſ[IO#B,sa% gVX HHa_~;Q±婨A\JI!]+= Za BDE" ޖ!T0)({gXSͽ`V&ո`XyW/[XuD9"K̍;l{cwrXءӘӦN̴q~qHv| v= L" HNܢa1>p^ֹ.pk7pξ8wH ^HM 0* IΰH_&~.8?; gy`x3)Ή\Oa}2u׹t }uL76 cb#%Flq[>MT@qU2gC^1FjT9os+e(+& BaYEU]sT.^ztK{?lޥ Xp(LLfave`l?UD:MXS+ΐƥcJʼ) C #quWI{zc&cnzB3{ۧ{{-5:`! 0NJ{D[czH3jO7w#vűWW 7;lDCR lL ϶Zg&ۜo99f1l~<?2.O6\+~y {BRNEW3mOk#cB-S?{2وvxu :P9mS.qi" !Xڧ-mH1(0;7]싱)E[3NlsВg.~WC6%AA[ "l J%Y :E%hksN_x _E"X䈏(,IJ&P aҢnoO()`\:|WcߕSѺ|G#49Qe<"nit(I[Ik/HW͗Yx098I#(" 瀪v[SҲi\+nE9nK08 `Gz)  `u'u䮥S̶IG^\mvrv@w[pI89(p*7VS{ @!< ./A;u|zUL66ĵHtIcLzq0׏v6/ M6\ NV::uuyg♖ƒWMnjH$J۹k/ۿ,#ݠr~okq`/=h*xq*GLnvyY>ˉ8"Emۍv/ ZPH'B5s*mNt E>i:|jҏYy"E.ưvu-/@lmq>j5I>A:8 flk-Phh!P@Y.\# Mc߰GQ^!U_ΜTOe n)kٶ<1YSxMG jX)k:^j QS> "U| b䕬+sV`c?H}Vݭj[W|Y8mRyXUh eTKډ( "\9K!K" D`ү)G*CؓNE> ksTHEaj#%:)@7SMaYB!=$&,8zQSgz 8|IXoEx=<[ t@*qn~EbpcpZ\?sK4 ntgj?b ѺuV5_FaHU-r~}NU5I@,U4YexR] 1|( ̆gdnէ{.qDTFZQ D(1j"`"mㄨR lԣ'1V&n#ib'(0tѧpsoQju'j-!a0X#&x9,-9 0XKrXq4|QdqUPC\ZUȼ[}d8pOacgnv|sCY)_95_5nĕI:Д6> z9=Cy|IB }Rl,v+/QUDڶ!mڸ;`M\ΰ"j(ɿOS.9rk%-; 7BE* tc5 ȫ }o(҄n/+ނ[0蓼S:c0ڊd%$Pjh_nv\3"sT2%Mrþ}~~y!5F\XgߘVqS^7ek1d:3\}pH~B!Ϗ͈Qo%c"w߃d2v( 8[s>o?6t ~wnwlүsAQ;KzKzNQ}$rІIOZh_ුJ_K[S$JF /!yw^°NԀz# fW[l3}y]-h|6e(!{]Eu\іHCɤGOB |Pl2s%/ `޺uJԖۯg6RD7= ?Tի@¬enfo]ᣢK;m}i& Sͦ)ǭ_yO\BC,_/TNDƚ9#}Œ2[{#@e>Sί#YDMܖ7fzol;{;"Xͅ!OW[wLĦ<DN\p?*iE !L], wU6ծ]RbGCµz1ߍu7#2h˶]vZ瑊6sZ!W0;|"GX(8T(R gxrO3x'1VװYkڢh32%?[5B<(ϵ1%\BޢBh Og|zۥT!KuoK" uH`9XӝF^ qtߨ|%9QT읷{$weu0ͱybj6o''9y~9PMXKy ^j 5qw1~`;rԓ3") CK˘ uk%LlZ$>?g+kgV $ MX;*L8iE)`r$sV [V;I9`gaImQ4, #AZ^4AyI0f36œXg$DҗQŔ߷k7} 3ȭgm8Vͳ:"jAJTSK=qlu{ϼLH2?5W`+Pd D]P uOV}ɚKLU@(wj./|dDLd]ANp̼5D譲xc]U",]tdE{#TtmA%=ڝGAU)?qMkLE [&h1i}D,<] $y5;}Heoo!^ζ*5@2Z Nt ²ĉ_pG_7n"~5zSѕBxD\ZWtj`bLWc) N,0̀A)*Zez2Xb[@YHJa$lʬY E݌E;Ep XԾ"톀K+Cq2"YMq)hE|{?KbIEHz&"#WD)ot_"r(#ˆCҎ(lE{k'Ujt>xk:lҫ븳rz$t.A8p7{x|lZo3jF/uiaK%9ۖ݅<էBt]@Bw/' LHCwW>^onŗm#Yqbi%98h^;crk5Y(8.է]@јQ?V{ޝH>tػ HVS̖ژP$ BPX1?5Ϛe4ߛ^?'uUn% /JC% @DoD#>CDIRWbl^יO~6Hݣj&qO''*g~Nhk0 ?s,ф5?Kc8f$AI ž>Snt>f-KB0l$[6'D[I^Ppߘā1l-ZM˶*҂BiAfV;Y ]<:f7 60}tٯ 0[wq/owkOM8aTM iYA Ue2QW[tNFR2}kAjk+\,*>J  P%%3W*ƶ{6﷎sG6MuۑKnΡ$Pַ@>=} b#ngn 'Y*Ie_h%CNX xhvDl!LHOo?<;QF*+}kk޳F.&ϴOt7\`u[[{)[ !P'-{uΐ]W`_̀T`O'[gM(R/vxAiM b {fY 0pvPE'<8%r"y%k>9 cN8|G͠<`?vGb;#<ưלͳ_O|Grf|q ġJ8Y!5oək V_Kke7>|}n%o9<3K~_ŨR)qM_dG~gֱ\$ I0N6;o^/xTTqak 8C-xڸK5EYg:iaañ}S> ۈN[Ec>XnżcPgΤI5m05?dfx`s@|n5 &9fOfRXBqV|35~ ]AB?xe'i=m"jfDZi@-_Қ,$EM9ɠI=LüD'{~lP]̡F^EK{z.`Zy_32`M:C(V?zo~nnu;NLJdڷ}s㽎ha=zEןq_&BA'0=5ex)ޚ&K|&GPJbqvi53%NyϽڽo{4؇RLO); vlHMGE'}^cmYf1%3/|ɟH#$+h:.Ij=gSg|S {=߸ xvN2!zg{ԮOgt_U/qՒ+muXP1& 9txtn1 ٓh};2LʄN⩝1G5fxޫcK e%W}w|aU3 s fJL̢d{Q;\'RH/k_9KlM^oLjM`0V}&<QJ#Cm ɚ.*>oxͬNzL lS(1O٫*'S cm,/mgN֐DZvxWMnR֣2Fn( #dy4+Q%D9 PFDEb5©aiYdw y+tBnYXAZq2]@Z`Ƣ@JtZȊM%y[c~E푦/x>VITFQO9s.ؓ'?[*vOFҖ:rDKׁ{DtCJi/Kۚf~z \E)͂[ 4zECj?I A2wƝ&CeAlS0Dž`̯W*n<[Bnl{2"Y"H&0ٍ1pLcɐKci _ N14*XsŮ jdk gi$y[4fC J^`_խg  6O.+vn~N`LiSskTE*H|nBIuo[z$vYE/IDҮY_]hDbw0H}|A{?U}h|gk)S0WvCT$9_ u}XZ1KPZpciؿb4Q vԊ= MC5Jf$_tG_maGor`@  7V%Zش׹Yu>Û3Bm[z7L x4⮢rv Gw7挟X4c5 Ա,t?O7f/uIDzGVۃ7m#`J^Ѽ191%K֗'D+=o =\gv[ BBwH6hCλkc;D(D:([$ vxrgg (>[zٌ*Gk@SEeI.I-vH%',.y1 BTw!2 4# \FG1uZh#6=H_m$Zx.?}Mż"6lZX3B=zEbw/LR?b6c_! K!:)Mb#:g[%/ّ3&j%rHYT\GX}ͷ;@̨wņrS&?Eo~sVoh dPe;|{RsA8]dϐO|йJ@ؒ+ލU18Ow$ֈ'N"9'_h6L~Gs(uULߔLےEh6^/{|rzQ|@o^6 kJ`(X-JфDII^2 :]!/ʺYq[sL;1­ͬigMG cZ3Y7x?]ݎ=~r6-~u93bf<L2u;Z|wJ;߰^o^޽~ Sp렇{O@rD&hݷM^f7*#&?fߣʓϐeDϙyѯ:A~ED >$<.HmRJͿTCj~N\¾!gSqaUߵ 21oF(u4Gh__ l揪O;}2`'GljW&F Iɤ;o5;ԣoW;$-o*: ̪,{/׺qeǹWo6s-+;/!1* 9N3^ XEynY蝊r;+qUPe237rq]/4lVA\y &Z 0S'KEs̜?(v=oGpPֻ?ZU6v! 05?tb.si¶(h3uh1? ZDL)@P8*v- f 3aXC!v!Eiy͙]@¢WX+۞#OXb9Tpj<%H4 I7>lxEkڷBV}(c ķd]Ǿ\7E"oUQ,6X1NۖPV_AIqəl1NJ"mœ7𔀆2AԜ >P?=m Eǁ>Ǡ1? Nsp˄sownw~3ݾCꅙ%" [{-I1 4N|[f1oF%a!oA_yvAԷ KhA @^Z[h*sO=f+Wlt -a NoױxMy㨯 -j=<V}H0%Ц+`;+6vK+F\˥g'p"ĶnO>pY?gO kaΜtHCPKUy"&ֺG [9hw1rTjwyj‰JO8 z\j+`l X7orcw:*&d"@˅Y,? /Ӎ6c`wv5ցj!@7y d"X<*G+P?^p RsObش9Mtn舂>2i:2$@}:!gJ;M#o[knk' >%#(^N<uN,ms A3 B"͈?ܐ,(L{rPB<yoi|ғu<._ qTMe_]!%)k=-ubIi'͑qg0!ꉿ"%AwQC]v_cwoϥ+_SnxG.l4v+6DoTQ8ZJh u^:%c]:i]OӯU]5qP۹'Qh9@F!jT6$֞N-4C~4#ʼnpF#JȈb ~Zw}Oxdt/-?u;>pm}JU%.+BN| 8olj߽˟ m׳Hk6$]fٔ<˃٢I5۽DB9w~NypaH~9Y\+4|ދ~̪D/Dkoc?ŀ~3y%%ۍZz,37E&ZGO|“$Oi~>B/^/|rS_c~_3&O_=vyg[4S/=Ԏg==?1xj䋞{i^$]dsaαHLz&!Z'lλNI㊾~_wMw[~w7n-n@=N?)S}Gq5o7RoT-Ͼgw#?Xߥ/tW6>#OjOOWv-/t 虆A?ް75'!;C)g/BGU5[o?: n_þG We \|N^Xcu p5)ep ,p<>KxBA8oR\ ?a 1mmĶQЧX(q{YB%?2 WHP>xafN'v!wY}}EM^=%;n~npNrTc/L|dRLh7xccׯ)>\64ͧ^{sg\=STSK9fn;w~ꟾ_ ?8rXwZB"ƕA˦x˷MG|eeyۯ)=*Wn/W',kȥk$aq\S9o?xgsѷ1W#&/ PKܳX3ZZ dmiF, =~/~.AxƘ^ޖ<ö|Jܡ<4YVBczsU ږ/ !de.f@2΁I#,m5eT6 L#%w=W bC=v4LɰRq6TVSO.w؞SmxS}LJ=5fĎ):@T諒\xqۗ~ƥ>~tepË_<U~~jFvCЧri?񹧿a .{CgL s~0w3W 榽[D>'2+[Nwunsl s–eO^_Fgz'?}787]?8/*Αu |1?5F䫿mCm~hb a:_|ӟfÙ2@̷{7~M?ki?^>Rb?xU7G|KxMY* f>k32r(e$6䓗گYzKu:PHxYHq36mQSHce*^j_䓏jb./0c@A#QBY/4{uW?4qOBO}a׽>cVOI4{{k&RzgW=3.Hݎ /v Î|o5qNIV6o(n8" ojyz͹CnP_YT4gώtXt`R"j]Wp-/Hr[*g全X ?cxi|SbJ9BybF&oj $MĶPVVv"j|!f?|KZ‹\?v}1r^4LREb^z-$e`j=T9s7X!0.vqbUA`{7k_Y-&&y֎F #!3_w=ᄍ)M!;y_o3rHp,)x6vjR.{[^M4Z[)!XC'zSD8`3X ifg 9.,}nl` }k~kɄxRcMYJT=.0s{~D|vY%arqrMmIWgI!Ia@k1bpoؿ'q88=~_mݾ)[c}wf 2wkW%U۽Ǿ+o?giϷ74C4_T/;|b@ul)?ų{v>&;KwGT}QZˏ 3ߑl^sz4?41;g%mw~#`[ץX7}D3wo>V?\onwyses[ ^ J<>C^#O[:ݗ$~otx/_g񇋎_w%?Boz-OLowwSE?e]W$~?_Fۧ""1wηL٫oWQ"/"Σ[&UmoW#-@$J&Hg_STF5*oda9s~'C!QBfF[Gݭ.$y^_L|d+^nZSn7.7ïjX~ʝ{;{N:Ts f8'`)ĥcxܵy,`iR'SO>?ބ PV[ \tJ\O(kA=u_k]ޔ3;/M> 6FXw@0SP1^?b|;A}5Ic=?b"SK6_U;6ZbqEU:?O ~GabqfH˖?uL}Ep߸u[u9;>믏d#=[_pe/'O~ohI3$? ւW-R47طg@dfQJ]LJ|Oe?; ;KL{)2J]!NzK! ,v/~kX[ʤf rfH(~W8D ~nl7@}D_bx,KTrZo(._zCM\?>ּ_`nu9lٿm|1QܭHLpaH^Q? Z7oz(n!R0!/ӻ&45Tye%|2c gE^E"2ϻzc_}GlwcYDHW}IcIx?T/A|ڥ7g)63I=~g}{A]]m NḪj$V}S^|Uhoz[**I(m]Fٻ7>q iu &g$Lg>nI~D:)ƐeΐnW2Ȝ3ه➩~JiH[MW"DYIR*H[^C>L$T#W|ݟOq)5K&ɋ5"1;3*>*s-/4&/jS@ YٙY~?=3V3h+~˛>ZW5!^UA#H{[VVCϿ'"?Y"M5LJ>z:%dVhLMWWr}j/Y}#~c4Wo]; L7XG緱wmf.}5ƻ4z+*DG:bQ\$ 2 =@Ծ,o_9?!h$3?ӄD! %۽xu :*n8P4/ƞrpBlN@B8d k1b(T-PJCh[ƼDFB.7 il$GKbiE!\j,X[ ^K'A-fz'BK~D{3»l!9)V\5B3- qT(VxZI *5#/U^Zx؟ڳlEq汷G5 LVPUq2 D$զjg!Rز.kPP#ua&1=ܸB4 ️@txU#JӶqs'6rUQS:R"F(_=tUUĔ.ɋIq%5L`=}F6UL0:Ma5.HIKMNB$p>E* t!7S`FZ% Gնɰ&ı]D\\ͦC4 6UYoE ؅cٸ$,[Hd<}J iu{i:@]y{=(е YHD!J}E$%b [R,$YEVZ@ɧ̄=\G2qs1 PTŋC^d|R6YL^H\MN ?Dص^K[g71bূt\ }ߺ!$Df(ktیL ~Шfe](ǂb ) $Cˇ u"@6@ do|1n.7q&]030 aN WwHk:-DR Q~vuavJ_@\ B/5%(:<7tP5^# xcDF 1f~3& nV5pjAnYW}ngIF>//_'>8ؤ:͐? 1uFjרL\R[+T)1*cRY09nXP8MW#Ja ,2te(}TX.RirGH $aiY~qa*Q!OZ|F >Dݿ F^h4 uU~߆peyh܌K2lbT[IP]* (G@f"kTPi sK+\\ h1rx(H[F\^@_炆+,Aʄ.}laq:wNii-z,֥QZ6[=. d6,%O 'D$>xp@T>M <h9Q4'Qϼ5*mfb2c\,$"Kז "S??_՟{jUNYLAUuji*lGx^9(>i/&VkO0@XIVD9S|+.6zţ:~!)lo'PaG5h'Jm+sg%gY^ʇ8vgcGlS{YY1*׈[lÂ؏y1ͦ;_r,#|&zTк)O0 H7!T n.eSHN)ا7C)20x+u^Mf8tOJ{ E Bur&'!ޞ`C)?\xH LK,l[m6p4Kɶ^kp4DHJ:sZl'-hak@ˈv/8=jV&su&e) @Vޣ֍ $Feiu~4 C-.7iL%ul5/E)6/9lיi\ ?"+DQ.$nߪV_ D|@ѭwRW/9|dSͬV!+i3W GE^$v. v`,LŁ{0z{-)֩8f+dOY BK ׂB1KvԎphSs9`KVÑ7!&e]Ր" -m8˓8D~\ڝzXdoIM{)gLf%Nߓ AN$50Qm'^!>xRI7xGX4e.XA uJcIᄠg%=S4klcjkıߕu:-6YF؝ ]b8  L}nKSZ%I`ݴwen7"eR~^3/j9c٬C_mZ7f]%x6c!x$cv2oFG!nA^Y2!F7GW/S`?#v>229]K30%ǻT{hOz ,T5M/1壋\B~ g<Oф(B>hHFbl^b6w8pHvY:TW[3Ia`V9"3P" *zi:xvCtOL~5=RҨ%ø+L zpF2mGO&WqHwKSýTfx T!L~Z,Vlhf+=ĉ{Иj`$wƍRo@rS j<%#u:s k2}_dc!z0G:zCr1 I&NUaӻZs$ \NyP -s0ҭBZJ* YϡbX,l}0f^Q,hY:VA}#mȚD,h鷍n_ӐvB,H/h,*`Kcf1h ɴz0MlcaQTølojk{x +Ց|(Qh:6IiQxo)͗Ɠ=Gb +. ?Yy1XcQQ(S8Y(7D#޸ DH+zSMC&H2cGqd1qev?a{'Ҽlb [je /t9="^6LVZOՇF6 ª~ti5[!.Vu UQ0jq#TGgXNWrC<󳦒{ ] <%()Pn낏kFm1pr$Pr B5c F'<`-ɥ,gdMUp}w_T@/Ih$a+Y2 = CD_mϗ*`VNt t>)su\ňuBjZ:C"K}ʴBN{M[Xn*%) :YX\mVݢC(5YRToJ$M mT0FJe L2{=<%=ii7  l~R UgSL (!4$Jcv!p4UC+tɤB{' 䭠BuԂĶ-P( )+xԌ,+>q WUUG"6"%DB6}qu|YhZڰ[D!U !j;PJ\[![lzÄ.PrxʈF! V\M\jMV[8r S#.A%P?,!OUīUSYBĽ?*V}+,]"[\t,h%`EHcL%vP#d̰ƻnt?k8c[qω]?rcֱ֞xh g+]?f{|6Qċ)q%SB~JT0)TuG D'jFN%^ADbT7r]C+H(/l}狙-ZC*R?,V o OWXm:~=^Nkp͋G՝1 <АYVqT݇) $j?.xpGL{")L4:Ht8B 5-QfL@IC. 7JZ,Yd.qUQO:AwR9*NZ5l !J{`U7Tb!Rd~j:Ulˑ^4 \}[i ;ݘiT9T5Q<EUr$6n%r%"ǝ"`Y:k!N!J̲Ўf~T#BJKJm:Ie6l!k:OZrHPÔ206M8d,.T^Yn,z Ų>3bbX 2rQ{ibN]rlQf} (YRm\C=BMH k!NQYN80?}]fٱ2PXolYukkHqD{3[9m>Vڗ к ޾Ӭ. GcLۋt>y96^4re~m[%'$͎aEj1=U!byi}aiޜV8 s8 Y^Yj(ȥ6KSa}$l>wCYd=M *I/\? 䀈 d,+, L,m|⩰!i%: ,jƤ=$KjZt+sS9d-t$d<响e=y~ef0H %ũMB2uD0Q-[49 Hnͤ&}"fhf' .ӍZ|A;\EU|0g(˲m3bMIFaqG3x.-vE"HSW{t۲@ m0f sS;9ffzR:ٽh9b4&]@ "a,O"8^FJA{UgUS+Z?bR#%",|:ŮERd:Il܊u' vh XNΞEzЪͭcoYߕ@;LΟ K8 K1N- #T?ZdȌPZ8o˕]A4v)hHJЄ^a+xqh72d=\+/S1ΔA2|Аlmy;MV` 7MNc8mCMYg_k |=2%R\e F%;GTl[D K Fz~O56(G G w8,uJ%Rbh2kl ;#mqDhBVi]YW=(qq 8 -c# MȈRu ؃o/ؾ~މkࣸj2.ksH )dCa_ JWHy1jbn =g1O2h!S3YzP@(A)ajƂ5\o0Fpmwl=[JUDۤl$8Uuj)MOYg%5*:xVh16&kƓT.IfpQ|lxdDɂ(`$姎cR';CQd Θp'%I B)6<{p5eXIҘKXH2pV?&B’Bly8 ?aӆ ^ً~jE!ñ [B. QidIގ=DOYrBl QK/{^ȧk=N?Q" %(uz/9CFBZX:xPK c.w!x`-,A.=Iv{cf(Rdڎt~&[Ou*N 9f+(C ِCC;Vf}k,3򪏸?o @$/ddbldE_K./uVw]'IC|Ҩ{f f!;\f?U? u6Q Qcu!8,(q%%} 2< KQJ>hH)-zy"%$ y֘ەsxVѣ7Uo$+pp,UhOpX0`p :~ fOíAHQ{YR!33 q\ !IXEtM D*f%>qآ`f/dPRXxnRH쏜2Ib<`q؞rYB^upj4Bdʌ,Q ad%`PƲb%<6<!-;iFG>ew:dCwFѾٳ\rgo[3rm%j@Q"$v-u?Z7_o. $Q¢"!2b-ӐV2k|@FK#角NX mX?-bɪ1Pme˥$ÀbDqiR$HBT g6 @"񨶭YJe*C B (Jsy"*@Ht1xkDX.;<1? MIcP!@ PK d4`kTT<&GGqp4@cvC6?z^@~"Cޙie5oB-5B!x%A%l,?mA{mL1#&Cdw4@v$P)`5,rR)XIf58RFN8fEMmxQAiI(+aX15*: 4:7 vQבs\VxRnxo'<7w< g-!}xBRQIm,Oݎ)jԙ%7Bњvsf:M%6р2>4PƉ&`l=bXR$`ys$+>JfVS7^'+@^TuKFkE0GeJ|EDXXbH"[W8H$D20VQP@4WP jqhиȁ*E eiuFHT)US[J̈ )WUWp7ۯ^=e &"JԠE"f ߔ T;e*VPXk;"lZ]L *E!~%m,+bkRL1 $# Ӕh WYuby aYK^B݌6^[2RD`ԈI'oo)v0d<4e_aZR0:Ff?Z40PU1DBhQVEfqpYZ'FTEvrGAcMY @祖Z}kNRL1V<!Vg.U}y*RE PE;ZeHS SᏚ#,<-9*-2{A*/#z¸a6R)asW߇H:ÙtKUYU $fΖ|CoX}Dr WM32fZNkbO^B lSjw7rris#>].0 8Yr)L 4;tgtsjM3Ug5;Ad dvAY܁{5k291A /`/A\AMo̒u ʪkQlas:gKl+¡ e#88SL̵{]CԔK&9Ѐ2 cSZ0X*Bq594<B,k0KE\ 8)Gp(Z,["KTT0\Zn'% 2l޲p`nuua! *OsHLT%$3t/r9 A+OҳQ,MBKbzZ8ґb<[Yb=Nx",Z+24UKUTؚk o7 W<ɜ{SB:~a{ fZ,'rO .O1"25L [s,#J$篙Hبhvh%. Tva^}joRQ*\RUՋ++nRӅ1a^59!*[k S3*RY9%GoTk`cХpF6!Ss .03ΚR3lPi xZc- nYQ7<$ QtG\&@.(ՒTmnVh%veΤ Td.-O[,d@ɠk%'+GEh;==O0MA$Jj ٺbR6cy8XnƟ l ״%3P|$ZE( `j )==-M HT|64IG DQ6V c;A4%"#%_3(nGnR[|$ND 9 >1gZAlA`3D !H3 c`W Hf"R೒Y2I@btYXL;7Mj7t,k^kYe:J荫\j4"=xrH.Sdq?,#k1QaxĤ&kq) vr@j4)ms9eׂW88F!hBjMxIlCJ/ kRo Cr@G6E!΄bM(RcB>DU\5 8./Ԁ!jjJ@0x] D#eƳV2Zv6K%0 hSz{jӝ6z[90#:<#:MF ʉ4€VTר*۲^T1`iF28P0xyQ>TG[a[-u/aر4#Uoi袐Tيu UlD T-&#B^ aʰ &Ջ|uU.k΂49qޒ(HM֠j*h78 0<,iPnY?3^#QC6MJ"Fcl8B@Pc9c粵OR#9zr8oZ=J(;Jya#&66GCHiDEh܎!cz1MݍL_cXb&IΒRTH"b *OJJڔq @"vƒC,.հf' DH[{pڿ3< ,5@ Pt"N{Ҏ% fH1ShZQߑix]ϯLt{DǛaEsK}b+}Y; Sa4QOJntNx6)&Uesc45OK+5Eu $ɾ,+S#h!k6bT-w87 Q I54$[:LKz̊ h,0z:[^6*|z=sȒQDa〷@ XU`0q_ѻҞ; ˙*Oee\g#.zW߃TL+P\)Mg6cr3 s P9}C:L>W[Vx+0so\ԅ.:i"xwg[:z"wȋlHE/Ԙ3FXH^fIRXΗѬd-We{f[!A´NQۮQ#Iӂ=z<.90@OjcsP׾7 8ynb)pAUc7ώ:X)ACd lwe@􂉋ނEIYcR".^ቨhȸNn­V.1m^<˝h1PBREAOeY9/Jd%A܄BI'񨾰!D]z}c|\0@RU58#;r&3jd<gdY0b!lڟ.<>_p7lQqiFcX1a!G99 aiYT~Ii'ΐ2hڷp Bpi]%28ꊨ:pR6qtE E )PH FǘJ4*(Kxÿ[/ev!?*f @&**hmKuŰ/aDltFc`|[ Jڤ^./rD BB@$:J`_ sJmW4j8:$TQ"6V(Ct!n{vC ]㽬aj,jcu\Bϴqyc3<.~ pBkhL91,XƟ`#tA;i)҈A*04!C;+U$>'%s`r@tf c5QfaTFA m⩂~((dg m@ |DD3!-u,& NR%2mp<-bv0  (]2,d :I!r]"wX#V# ch]T/H@rȡ ;-%?L]CVb7/%F1+:j q\S$ E.D/(2y E>0% G_;*uOW\%j[9U)w 5Hf@?BNҬ#dˆkI0ԴNvt;iǹ$o 9:<:.L"H%G2+q*75/Ŏ^L_//LOH oh7胧r]9+-A(,;g#X(Tfm:m"BpwORc.%-$##DJBJUKGRYx\,iѢ `Aۊ^\?]UزÙ5jp^ ۍhOD'<˰B ^O%^5X=^OhHbٗky"ht1T iN}n?%WKdcicj e2GR'FsENV^c@V f2*9lB-K/^ D 1O2P!U(eZR&o*#J}<_!;w+@i4Y#RCi0)pQ$8j5(.IaU]".9"8 ܳHAh"b\bq6\eHg yiޠ qt'WTeaVKLqR< "Rm8iuU5R۾ӖJ$BQuEdyKr,R)"*kyG&g$m烘|:u5ɂ33;W,Ѳ8IԨOzM݅pC0 f%.3TPmtcY8,b1? )֦s'W %mVPԱwP,hyQ_,%D)AY1g5*VyOq@&D*,'rc.*RlKeEQ KDQj-)ŶSKf@^c$&H >m F(k8v32bnA *%FF}F]E#֙,[쾛<Ĭ6M<(T1yT] c &jS6) jORջCEHK/BҸ( yp4{XE%!Jؐhl]bVqvUΤ,,|xI^⼼صSO|D7]}I 9OBV,ϡi287/_,@|T</j~nuʇ^ H㉛}}[shG m) Y/KKt"nkC3Ї6"'8v)!/:4ސ8JxŎBD(F-%1. nl!B%ԕdryet:idc_47S hqwɪ@h"AJ8 ˖akr6JU'yUf +1 5HŢe#V| U|0sz̠R6,UыKy9>,[&\u, 2 ܐ?`u9@n~NU}y  (/hkvFhT rS9rS궕$ʖ1}UC;C`hT ``hH-6@+(83Zs ʿ1!@db1B#ܴZf]V"C 1{#9\%1 Y & ڄr YbJ(V{drQg^zqkqT~Q][!Θd^LاJ{Vˬm '|ZBqఛK4/7,(+_ڵ9PQ!et7w$G¦JU!ŸCO~9<Twn1G\ON|$|^rP^4}4.y _|kVi%+x^SVd=&'l( ĕoFo:= lFN&x H@ubxT}FDf1 Zݨ^,"9T]o5n,WYrXYRYbSXB:Xɨb|YTQg6mƆȴ\ ڙ}&V9OEqY!Po!HRbG,eJȜw|aZc0/$nXxwpe=&Y|}([eRÓ%?]ۅp=o4Rޥo8n"9JX][gExKдs3y(25\ZΛ'OL0Wí]f(U*1hi@3vkaZ{u ht L%h [0 pd*{Ƈ\ {լhR!/TZtu6 JnY8Rj`CBJ{:@̴gbFB\gBiy.#ݠj IHDtc'kVl/hs07Dt}Bg.cG? `Q@)3)I_9Ӫz3ҵ [5% &M&1 0 L `FVa%I%FDD )ʞ &$3eQ&U25Slͽ}D`F{3BSZ/˽JDbcL, .{X稜fiG @g4k⍳p0PҊ3|ų@o5_HJe H*'LڸS! bi[*.'\V6u@HN-Oew4"dt XQ2AP`amg z|i1H4iq,Ju^,:N,+=a r4E$"kzT.x,Q&Ax&C$9'~l`жHNaH(_}KL$C6Zn#怮 QA 1)JJrHY)m9JA2!I9@s%`-nK5ɲ5z=JUUQ@6UGK4$2 ~>Sh!I&+@SWqTm!c1$U[jd`m owŰ3c$Y ]E}:a6{SjQUIjDN1յj2\Lw=GCP*dIiJ9َmkb[|16coRbQ%Wx &L>^$Mjv]b1bgF?wB jv7ǔ9-mrf Be7y?T `2U|֛F=8iB2O0{fVE&qJ!Vz.g+0I$Po9N`ClNOkopgg+@JAaᖻm/+tm#c+0IKf?4 #{@#a*ձ%ȲD/p"*@ 0‚ h?1]uyY@qĽjpKF2O**,aMRb-ι%yyJwZA/Г~ϋG8yyz21Da0 @%l;(ޅ6/WE+zkHH!|?D@4 SGPgMkՠRN0h >{r]21GE#nTBA6c)2"ػ ļ7Bsf@`@*SGƻFQ|dDBGm]ET½P!*q02A%p22L#xH~ y -NUiHH:~uJ_֐ gyBm=BՖ*hSYKI oB(0l/=!#8)':GҨ0͖%K]@YW7#bHQhM_w)dĀ䥐YTFg/ho.Jed"{S2h2\O++hCd\薘 Yuy"]3/.?K?߯-`#hiΛE#f 9/˯> ]PHr3 rXl#!r(.7j@٩]Pr#DNa 3ȵ{l ,SyJ!s?Ofc" yX2iX&eΫm(7'6N6rxtAm)s^6u2Hᐊjp#V #pU\U!EeE{<(h'qvywϊh9@ASH_+. 46Y2v>f q5E{SPk](lT. Sl.!h:GC,i%T9 \Pu0_lS)SZIƓ$Ԑ4 뮜71n0*xJvT<aidj/Б2 ~, "0 45@Aׇ  3(4!&H^N&AZyãԘh8Ƕ0͚xbFz5b(" /X`T\z+_ܫ*i301- yL4RY{ 66O5d'u\i`OsAgۑҴ@ 0T<ܬZR]۰5~4r:0iJ$AqJf5Lj.]zd=ۏAg1%kR+6cb{U34~ɕh) e+`Qf\31E+LL;Α¥f?z~Υ'V\3ĠއSYq#:+f"Fud7o̐JRH@2(Z&f-OﭞBL d2/>zW\|!d6xH^X=5<$;ɗiP* 6&1.N|\4gy0ϝ꫚RH.1K9WTu+K{7NuPBBpN4Ovs"jP;r7s@:b$=ML3,kwV:~XnccL_Suߴ6Vl!bX\q6臯KL4"mpe\:k+~:Û%~nؒl3H%jj*~]܇.+kߊ-yD BW \FJiOB(lP'p&woMwb!̑ RB҅r)-ctg]*83[!{Ou_ޗE(tO:@i-eYm<_^q7TF.g|AtnE7;ȕ7MEhf$g ϭFPç?`FJL=)M7an(I~|{sv#XReg fH|6l|XoW+Td** ~GP-/GeU7~yWp?oԷ+/* yv^hB<OoBL 4vJ£v-GG%wE5wB҄z)m;)U@^Wa6BҞ\3nWQGɥM Ĉ0DgZH\gtw9~ć?q\x;~u+byħ'7NĻ\O2>' ӛ"YB; S _Mΐ͞yVZ=y B҅x}[> Ѻo YDhG=W6}q{{lí+HKLJlG8_[sa@ q$^[=shv)ygӺ>NuR扊mԑV^&_}3퀠 |%Fm:#f,ng7b4Ҳ\4a;_y;L< Ђ8X`T?odk4w+d2Y4˝@Z&dHB%qZD?gBY! jy&Z!̑o[o~?\qrךɇ! ._Fc?*30o۩Qͨ8hnZW4"YM%, 2Ho,^}fBCxڦ|{<cAhɳcwĵ,P+Հ`V`Gitc @q1SAPwGǼ+r7hg,w~ɧ>嵴.^IhmhI#6D?g}_hgkWC]iGA ϭz]>)DhQ)Pa$!3yINݢ[2. hzL,{$2)jA] y[^-~Xzmccmȭn)#C_X<A:1@P ^e=U4w r(PE4 XJA)h>h~@?+r7i!sH8IHyKU $3!>.YHy5F5m (*1YH"+˒Si >/W_L24I0Ψs4Xcyk֪Wv0dzpLz%%օ,aw)5C "A84$Zoa$; 4e\!ly\B̍֙਺oeA FEjM`9h/ \sfRcT-iJ=#Ni!DPEvKL;/q}}ӡ*E)hw{ɕbѿ9+ZMFXLJV! :IٗA*ĀɋmE@ VKd$Kj(+N=S$2 FJc]vz.5.,,.u@F4M-\Jg}iųS0A) Z1! 6xGZZBdPŸC˧J M C=3DD,XAӊ>ƨrEa!lxKePp-<kč>M k2R 0ӱ1!p&h7ktMBȎ⺫޶ݓ3GCp0BI4BJ"QLtk .| 16֐=¢AwAXBMiD^uxQ REivy"IԘd_E(܂OIv*TA*K +g@{nZz]zs3&94`\0FòMRB@*2 ālvF4=!Oq>5"k/R#Jr1M8\b@b^#\5ʑ,L5*y+d0͞Sc!G"ܢ 2OxzEGASֵUUMLNf yj4s'"L}]4)w¨<0DUq7JJ%,1W/QLKN"SgU֦ Dq#t5O biy"4t|7(SY#46 `I' zldCqbi"e."wyF!* Bhg|zv&i&HzHܿ]|ޝ[Slf]WtБ8J״oj$?SX$e"mfZ]+&1)B}ml g\`IZ-F_略m"j Q 'cIݹJ\-&|C.>]^9Q\ϵz2L-_^6kZ eS{\Ň"}Z`5n@t YҊI:|}jP J2 y*p 2(!`Mx&E>Ʌs-oPwH!LP<8+;:gW\Ko7$d F&m*PDB%s+quWQ6git7"Ž<2CDHruM޼@(a䅄Yt׵-(Q?madZg9j$`%vkZ< Gha겘Zkp"4'E y_o5L؏Dr{U]ne"QFZ1Aةynii]z%إ(.&X3!nXRް9_ˈk!hZ#҅\Їl3z;BB240!Vl:]TIFeD/ꠏ0}ǂ )7AO`+rPQ /ד~'a"`Vl?$ q)s$֎E;:Ն7Dh$Fb:οų{_7G 'x(B.}&/j!etH\ՅdɎ<[5D!dG×.Hc)5f#)?Xp_r18}yf#c*B(C?Ofeݐg%,""dԓZFF,f^fjy? 0TPТ}Ht|Gም{ETi%dVB @J)vL%7*2*H*!h2=ZMV`1zWj.eBUm4 (1p,Z5Tu *gY<^բG/H<',$}q`"a*RP [o(+21|TC4r#갎Xk*۲ca֢>[:fǥ)O404> n8lؗiɾդn8|SE3G0av&2*]x!eFJ: ;V\ECa/-n[8.r0klyJ. ?=.| (K~07!:*8Edbc~D9_}E;I9,;))h %Tb ᫠?Hہ3s.8 q@1QP b*VUTO]8`l'b0@p5e <Ѓ A`@e}Ue s]P %P} HJYBEm\E:xײ`H*4Rc 2 Ub[Q} z,C^BXby0VHBnOl8ZF26q?@uRC< 5CpOŶ3~^WTk[g)JyoZa9@`BQ x mZ ApvzvJx'Q4XHjVT.ʔ\(  :\Gi*Q/  f@ͥ;l9cFNe7i!F$RH-WrVG5 ޚ1D ZN6Nr9D2T ~fP/wo? BbRrS+8Ja 1LTiFpHmUv(=}obnVU9OFݽ"YJ$/޸3رimlw/q@!H GJ(U" Lj8>(U%. eA`!z}U g`o*FN^,r;Ly ԈTꯁHۀ 먲y -u}J{#&]^G?Ee8]BT9 4jYhC9l| 8:x Ԇĸ1^(DaqXƷMU_B<~ kԘn4%d9^cfP{XAVܘgTe,Fe%1ĭ!H.'Do*iNE_*ٰޯ6ZЎˉwp|qva9Sb˝X F%@sv6DB3PD)5PNO=j f}1H*5_18ٸBw~@u9b<~͢/E=,kG=]'~7,Em鯗S8 l.{/@$ dF]w PRkضQz2|4l3 @' jQ9bY( hj 6+]fAkLX(AAAdA5ûşthqf u! s|\)0ⅴ{tFb*PPF*<@W΅1CCw(bcͽp.ckK|<,JXe%qEԯrBd6Re_`UehisjC^fó H\Hj匠c(71.V"X?m|S}-.@$/IEI(4vhQv=ޙ4;۠=׬c]Z)9"U{ J'۲t;=/߼`Q5|Et}aE۫SGteM_s:Vݨ"bF,RI#lN(gXrUByFp<*#,8F$CegsY([>r@5V܉Uj@:FFu"O!Za57lL$rÖi;]zCLq[ŒM$AIbF. 񆵣NYF!+Qȵ"k٢O;PkPiI%0\0K=Eeik0LI2JDtQk0FΊ!rh"!Q3^.E5.m1"GU/fEViRZx\ xzlƑ7*[LJ /KHG%26"5+RVj%ώ>r \zx1AK aUgw 3"-㟲fV{Bн}o>f5~;oWx8Z~cN{pēVr~ 3ׅOXs`A(P VoTux~4MsevA6 U@.v]M)&o&- dRDpHߒ-S9&m_l -@o(K̈́Bh Z5 ˁ1yP7$2NdA& ^0@/ .M:nJ#h" /nFd+D)d2~{(.1QK'2-g !SUz5Xdy͞o]`ZMaDK6*jAoӟ@`ʡ^{0T <[ x ^mhM_:c4^!M2(8܇ [ό; :&V4mcs49%FA[|3 peg0 lgqțl,6-R% zYrKPE^F@ꆕ"b~~ۚ1IZKqԠ6;w+?.77js g\mN=N|/Vo$JFnxKdeq=O*MU`U v љN7K,8^]4~vHy(F Es߄R}['krl {Ps9K>OJS]BGOGjPo, ۋe;60Dp*ٳ].Kږ`!nYo/oQH\I2JoX 2!`RV0%N i*J!TnL$fũvVKhYPb jj ZIPW!<(De ¥qPoJ}LUq a#(xűJo2W4 zLP^eǶ*b9*%Ȏ#G&"z#Ri e}  \4f.=JdZDcdqِ&D [.GtAK5؛d! {2lLˢ5  xfW#%:2'G p-*hIS|h!!ˠ7>_!H@5U.WJ_u%|]}iE  {=qC7A1 OK 0 8j~,܅Kjc8 QHOŒ?΄amc(H15S!u:#D &IaWStd <,fŪdyB` ɒFu)2%`;NSU @zt/ҿC_3r=lț"SYFl\d; 'luQ Eܩ!-S/lGeocNE*εLSTfG:;w׫W^},`*lވ_p;J(g3 -fB`b!n3l4ųz7OJd qX? v6ɾ\F"n?U$Gem4Di~V/F| T6Od}.~vamYvko H4!t_nqNwʙC9h|+5:4lg*ދen"񠎼'Daq.[հ{A)tm\ma?xb80!%lĢPi[~K 3Ƅ2 ٪+d2780-%7&T߅:KN)#/o1dAvs+$jp:L8`FlNf `@e>a 7޳5mŀT86A~ iDrM10pn3ZFӄm6g ^qR{Y= a!̖A4ٌ}a{\n,)3&r72XX wC(5N3&!9~O$ay>C)7]R2 oKXUrگRBs3e?f8S 3i,2cP "J @lQt 20f#a%繸jt֧߳8 j%p ȇ1"}-N{t"G7#O%($\+aɜ^46>fY8Ԑ' E싈2E1Bl2.{ }JCb#!TOk9ߦn)TGpF-|Y$?D+1Uk $eKA2 ܪu d*F @Tԝ6-u`eS BYQ 2c@A J2lj1m)[NkH>-_7ħ]zaB36)8Rg7#8\9TMnޗ~HZѻj`XdZJ-ח< .^dH]&[`z곈 ЙpKZ} Ȣg39}n04n|D(M㹹6"jVga 5ۏ (j6BNrQ0g){&*x/$ 5 O(= x qug{ƔzB-c!N![}^ʺAFs`R/! m<ǢsWQ!wRδa녺2 ݅"!X: !&C* (F|hYClaUO0#j#6,~ܓ HXi^ňss1כH0\`mh˖+3T]kWȅD6{RmbHH>ô [bNNqco ͠n(M¡>돡`o+& @4yۖ".f|iwBe`?*P1UxbXdog=4*+J7"|Dz:ů*و(X!XD9[۷q';!H#eJl||ת(f(N,fZ</Tlq]xب)>ό/,ZOE|7am/sʩY]2g\$& ](d"L"` nԪ6!A-ý⯳<<򽨄yPn@cnoa X O,*lM/$(Fi0qŒTe$ rcI QfH+tR)cYW H2krȘ2l+뻱'+Trr{XQy(XY61ر(1MbXq@+HO-w!"*E^V Q) RGy-ӵdp=qy,446B0\Sʀ֨m`muf~gq+,]/?ݒ[Ë9_zaKi0hNe%4}YWO̱hM&횐3)윚ٌȶ-~[cH!@ ,0\lۺ{/,%!Hl){'SU%ս0]cM6؂Y< ^ r,{ zpΠa +zxnQ0ԃh\~'ߨ7]h"م1y(3kA_EaH_N(S))FƅʿKO0 V Ivr ՉvB%]&$Q!+$X #R5wKbGpȃSQq鐻ө)1v]Ccv=Bx$D#OBN" I2^)E!NX+_E;!|Bx@Tx%08 #å#2jvJƫvZb ^oM L%fu+}Ov%f.dhT(E =pޭvݶ% 9 wbcaYVbߏ{1c ~nC@'S[zKbexi%>5kb|Ĕ!e9[}EMrH/bƌNESN{=RJhjS,>i*P9Y90] 2YHZ&3M$Ѱ& ѪWM҂DT_>OӦP)U}Ò#]bAubFM:Df3/IvUj}’Cl!cJݑ7z @xË2HX򀂓m3Tپ#f`(Tmxߔ( T) XEf {_$l DByœ$3&_;[b6oW*) |ؙu3DfE0:jXF„"ZNdu=EXQ3_T\l eq ѳ; Zoፉ݋4V΀oOI+";Dl#1kN"".n2~?4N%.Htou#0 *ƭ~RρȁkG/@=inL&lgxbt{` :L H8B!\tp$Cn N}{f{ Ne !^HEB0Va3Kq#kO&,HRP%>BUϨ!Tf!wgyKU$xVVDZzsyA'a-Js Z 9:FbwVdvII,_vs(O/Uf\[@|TͰsUV,HQE[沬;t:l(dCb #" )'>R!!_؎xoj[cGiO30 dt3TC$"ظv5[F, ;ɠQ&rg (JDQ6$W(_.*Ld "3z[Ӯ.c(y$Ai$O,СabGDz@.:Bd H*K`XJQ{t[ ZQmAP# }C+Б v`vH[8p`")=Ye&[4S"X1s&l͓t3ٌ eEIJ@aSzÊu!(b `⏴Rr}U Ks׊H5az'kqDst_EQ&$@ >VhaˀC1o4vوVL+YO o(JI;''X5ѹ Z3p.LZ5p 6cQ:r`4.ՠ^V oF8 X s VJ N?2Q9jbǒ/t8(F7 A]+ Z@!B:XNi1'TjdWn:;"Vޛ0J!*+HDrIQRfUidasu^ϝX ϧCEvwRB{R8'6ɽR*Jަъvs^N>#.Nj׳a?۝X ` PƱ3xNmLŊ-)p4+i㻔3LBNDY#˫)Mpi9or_I2fو|` A .`ܮA8ع!W>[5m6;(of ZL yXX>8^p *ޙ\m}j3wa"! {l?F>ٍapPCjB*Q!ilD>0,biUrrȪcmƾe~(.Ϯ.%Ph`&ar;>⅟O g3n9GՁؘO֐90m"z$!Zp|k(IC̐e@ &$uP!.8mӔ (=Ѥ%,쀱`UÛv2&HYMD&:$.1=д _o2`;K&8B$G%q9r#b(\Fau-d!;.ytxF45Gek3Z%QJז%!9w}ԹZ +0eUT jKO*#CݾS&&|M;&*q2XkhUCa~΅i$SMW&Ӵ ovk.C;mǡJ1D79X1rPOTX,pмs\ƈS\NYo!ݢ= /oYQ)^EɏR#ŀ)Ĉ*6d"1=4hB (9'髊asUڤm΁fYWY pL\J>VE{R%6£fjKGd²k>F C46 Dla#ʋr/>=ydR ~ȴs'l/TҰRbzw|sLTprvE-EuC۩~` /P#F3G ޓ^ /hUk멂^vI"1Vu]>hh2RFgT r&Qz fd/tl Pg!aJV[A֟-cx≴6C+u3quv!a!fgUl JDY IvoZ8ǕZǼG@H($կ pĩ> F K PH1JC*aD@ ˫<аPN,*EIU$[8b1;|R.EPp5#iOc 'd[L8Όͳ(<$[[ַ"1QXY}}aTire|ZfvW_m{WBm]i3Y8`?.n#q`|/h9|zz$ӣcQʊAByjTq1T4 :泅ds4a;2 E16AP(ڢG/qydyElD$LcA>M$:rwQ(Nwq䣈fIB{yn1]^=,sMe 3Bsqb[1jFYYuW6$bp8]"lًOtlX^5<(9/]TopPTmD",EQDãX4pS8Q8"5(SQő6[ϰVU]갣rr16.|!ƸkNc$kDQ a]ꓙH) I<dIca@L n(?sb`)%W*aaa7:A]I!O5!;BcʴWv"NQKViaq9L;^'k(-1ƻ<(PbgriȺJ3nb>\lMe"^,[DL?$tCyA˳f:[!*b~3$ q2[i@b%*F$NHLFuFS&û&|{ L x'$;E-6c{a"gCgD(S!HX=J<ȍ\ ?$a%[!9}[1a *eTTG ,0o]DHMaA,ˆpj9)J!H+2J7LcM 67BD2hb컶\Csi=ǭ,!Wx8jull7"c#qe{L61FҡblnyDiT=ëĢ*A X[9ۛxNoHH5c?kqB_p !io~, 5DH' gOr_xSXY8| }ZӢLu[R Ż}wNOC=] 2 $ş|B&KaXN2VL q'=exWYv}N hB[7ya eBoBUAЃ c#I Ff\YWdT״Hf Әqr1ӆd`/ ({M"xCA7yqjƔr˰}rÀ IsP+H&aaUXV3o]ͦӍ<$pGo ED#Gqy#me;+n~neuI\ %ɊM+KiH 3-6-G,Y!V.QGh …,AT# $Ev&#_ [n:1̅2dR `T~^q-\^"ܐ4ަ C& ZeXDB5+,42@A֚h4Jᫌ&vOnξ_^>bDRskq;qȶ_)T1'm;vxɏTݕz9y@ỺD 83p\:\| lg lk&gOy@wGÀkt(\>ό5;QM.MDfNc|wI)TF5l#{jF+ʗ?ʫ% X * ̅)E9ӦG[0I4.QSz6OY@JjmUOzIE{0UʠoWiy^vy*d;tGq\> +y*2 +K ' -)Q = C-~)^iL,Ya𹳪1drݳeal"`os$Bq#h!ORnGQbjTLԼs&]4%9$fԁ I@E_xea%*WZ:ǯ(;7Jc ă ;9:޻j@[qRfZ 'xoEɨ b ,XO\P X|+jX*q)Q|ÛX9q (D_QaЁi}$XVuOUl6+dJY4˜MC9a!ATޗV(6 !ψ^$Ƙ3Z(fioA4ji6)wtm(`,o@iʮS#n "M!V @Vh eFeW0xFqP`vOuС?SZpCuD iĖ$B ?K)1i-4+fWOGh_ҏV*cſ hMhg5`zaTЀN<C 5}=A? *Hp'>V[bfHE`<#@##o  $w },%{x 48CDV7,IBl|_y< ߫Ua>13ߌm ~C fo@ :|3tQ_:5ng[{οrS;_~4#pr2#'> rA/% |Ҷ}fX"Of퇀V ь&!XM>cb%~ajֿǼ;BV:{Tċt:U{_"N<{Ʉ>-qfu>=</6}ld?}g'K9|owMgiutG: )ti onPVǂY@A7 )~0Kmp u*x_H3F#PܵK8":1!f#x*2WPJ[ 0 R5&DSp. <c3E,nпmV(pLT>c{ovRZ˚v>a& :ۯ=yG4{W cU˾7sDc1&o1]"뗗' =*K1Gkl9#p58iO$8 Qs+  )uxgt\m CCvyV?w!gT]&v mS k^)C/w~y@kg ܹo=hxtgJ8޹+_#3>eҬ|wob;R/ujxO8ɿY-Rga Z{Ŵ #x Y8#F`Xv[&v pyXX̀τhł@s0兀OI|  rA ~STE!VqV`-R@˂9PnD`(*c䝟~ThA9nAsṚˆ$уbCm3Ҍ2iME984ɱSk kiwFn^uyuV_PaX+x1fexϴU[{?(Zqˉo|ݎ?HҺsfPX_$ώ_t5U[p 4ˁBB(,ãHnh+r뻬3M x-,eZY?}0)ւXA>:CCDA ,XU?A׵r/k7^ʻsG汻Ӈ|F&{TmݮBLY6'/ow}+kI'4QSPމ2 d=1[)#F\8fSGw3lN?>wmxڱ?ߜMW]^1KH?.1'ŸIy{ݧVʷk|M]Լ{aiշ+v~|jU+>}w0>@r,9N }-8J*pP@BZ@HӑK.l"$k pg.>47%WWBosǥᶇqC%3S=Zφ;kɘy:Zuq_;dj3UvG_k6޾ˁ3rx=l#RVZ{Q0?O|kg#1%ut!tCh93w:MG.q|t73S  ^Tn|֐>Uy椮@pH{PID֜2kôdv~>{2>8 ק?HoNb5 DX/ĚŽ~Ж]aԤ”@>#P1sT|W I3da#AY 'a2,xS?g8L p-g!pObÄmD`+KLBT%!n{3,fD26ǁZ*8:[k!ǹ P+' xd<zAk;*[[xW0-3?m# F{o#TPA,|MQS!`0qA '{W23 tR,noXvBs =SO}B]PnZ"m_7}ܷqmjb/:5gw|g b~E??_0 }W񫹁?gr98+CHl>kB}U2E#Ү <\W=ˏ6oFkm_p'IgO+y{o>}]ǿ]awǾ,f|P sԂsڙR뗗gYLQ%f4s figg͸'ɯh /&#'q`-a:t L pp_̧ 5KBZ0@Me?bOEº }],6'DN1o;oV1śXP&zXoC6"EEߞ 0 7y~i:=|XgnZΤ3g+ VcbWkO#f|c[< {+.A:?z03ǞcgTap`bLo/V_)iO햍^I_Eʞ?=bTE|4ʭ?X3'5?a6> s0: W#H {эjwN%b+>;HmY#ԒQ 0,<_mwohsʇMŒ,̪OBCuy0}qkn9莨FEO]-opap hǜ^/[kl_ Mrm!r{Ȟ+YUh=bB2Wp3i-p{_6{@+ e,{+7{Ê@=`n B߆0XXX?z'g^)~` ``I!y/4~+D1cgX ]:;|3>^s[sCJ(9@Fh?s G#pYhޙn}yۿ Ҟ[ :GPȸ9νڻFrI`Z[O1>N:\ ?pXuߙ؅9k  f~#{~ǩw%.xwao>=G~&e ?y?ܫZwOލUm^_I+o oN'Oն0vҰ-?W)@/犏0;}çDc1zxڷYߺMS) YlۓMړWw"Bۼ9 @؎&ol5 =b-7l2@kѰ,@; 8=C aOBq#p] A.&Qiw/άVb=rfd_~,:^9s)F` ^Qc iC`gT#AqqtG4#1l%hoR1rVMwp N '~&Ym+yd[:JBbm孙p!Z)`$(g@A0=|Lp2?!R,3;CJQErlA[,N>2\r:ou6Dd,k̵^:nYܪs4aZw閕Om[{Җ9=D&75O_{mM^_d*_P!{"ȑ@A(swOkn-A EY\Mm5o ^ǯLz{&pjr$}wٵ徐Lx".ǟbP̨ecku~wL/YEt@w3.R»ژD !PGusZywEFaG6%6ߴaЎ@? NpԼ׉!p8Ԃ)@$@A@UٍA\0೯k5?>zYS0kmN]}j'O$!=_㫒M-PLHyubdڃmTkl;ۨTRG[ӊKJ\|_RQuduW]@yt톉>K5hVC`lDZ!חg -R`G:g[QfwjiV;UcGR0.В@ kvĵE)M 2}MSWR=u;gss/oO%YYRE ʌ2;dmnF~+rtCSE,+|tO.Цde7[^m^d/ľ$, HE'OSy6d2{u3A1(,].t:ko~SyWǮ u?}c5c2Ep]*z) iv_̈_iovx*р)77gPNΥ߅RGquϤlIf  Fc¨ks^nxy1Ğ7k_$7(C9 @q#,Y"dBuE7v5 fd#Q`hjBp5uco] @ -gN7%Y]uw7\f0))+K g,=B0&Hq#_6sCB @ H 3N!1N)ǙIjzqt.KC^Q\tm9Z<;Y}_=")}ӋWsI$o3>nӍMcs@ BQɤm{vq!nT`/N ͟3XfGTig3MV$ZX&J- {;m O@\q|I1 vQkŵb"J@UPsގIstMw,sW}: N tSg#;7SX XLR"@H9;ҡ*46)A\[MG.pc@!,nk`V úe&VF9Lp)c񭌅2XM`f"L69øZC8| rxA $5s((e<"y"xrRyQT,I=Q Qrf.8CۄOE9D%etJ J6Z87pѥWC1*{Ʈ}q VXAW<|&wX@m]_ x` 24SSk}[2t?A!Z`fAyH{ºPqԏ-Ft}uL!&7drz*a'zE#sÍ Y>{u# 'MxزK":>Oh}&28c=]ɕ{?]3/ >4$@ȚN&ʍ7tEnO^+֓k"HIȔc~˻^\3;58㾔O-4G&jffL(OB>\>~3|oJ˩Yi0mg\={ aRX8 ̂c(x *&}=<|7B E0*J>jDCi*:#-cl1?O56oeg 2iƵ$@.,|"dѬYXa4ٱ)˦ajSP/ݱNCz8͂X8Z-#.uow.#ȹ bHyJߺMcù_-J 5RO<_f9kxMzLL/O-KPV٩ol&% ӭr$7ASBBB$r#j%{zӽ[4%𷾕0&b.Wq*~uZ o|iouvEYj*k&>C]58o_Pk{fu?^Nի'5^  a1,9y =4&G+Z'8;6ya "`# 9=C0;h: ]6BtO[xА$[DhIȢV vaY2(+b "P)cGGSWؖ=f, xm) 4E1Etr[\H1/]&.t檡6ꐃ*~.'etq8p`}/vgL]K ȡ8?ghR, 1S$J?C~ :I=citO:UM1pJwͶ(-r&D8>3U }G4l E ѣ[aIajn~w)~z^S{/\Io=oz$5NV>@ldž|E1ou3'.ݟwznc+'T )axQ^9zN5!Gݗ9|%dorZT\ uz\:iv'^~Y>]p uʌ~ԃ5d}ao{d#8lQSjXy.1p(2pUsM $?M@ Lyd#+pTJePA9 Bl+9rTSmkj}F.;ed%켣R6S7㊮#V036;{m^%ЀP(" ֐ӭH31b~4z jZK8iIC M 7Iel<[1ĥく!\NVWǔ*->++c.f\lڬq-yWG;Zּo6zl->X}wwe3<7ƫSPT\R:hs3MW7"gq>=Mj~A{NV@}YWŗ 5aUyx-RI>?iĄJRB _M\m-j5-omW 4mj;n+HW)|r5AD2F v4ˠI%Ū89bQ=+ضz \12O-P@S2 &("ӯf'dOaa b\FPu":[AgnEc@ t`2paV@> ^΂CȂn * `1$wak;l㽒u0*hV^ފOF"%]9{E3}PE -zT¨e4Њ1iٌxPN}A0>5ԯ@HX )U5&2\[A*@BWpti<{WI .|Ato&.PGwwE@|D`2@P`I9|?"iLe=.6_(:&Ѷ*aM5}3u{"MȆ ? 7lƞ]uvS?G?ra[߽̏/YX) ǏC%Dۮb)ԒymӆM'."o:jߙiMץy| Ԅ{dcg).d@s9A@B6n zA|OR$ Ay @%i䨎4#Ry\X 2ˆWwx\cftT%y\;jh (ȩ`\X2lBv'd,7ebxԈaiCK[-d3*"W5\:갼Y=% %{2QJ$, hhG[BVj,{v,y~j7zľte|g(=ʸR,BnO.v0 ~h_~eX%…WLNX875C;cE6140RHsykEg<یpPK1fKkr[z$H*[HDh>t I ;-DZPHN]WPnw?Es6Z{bu(.2U`K(+lŝ@6P^xg *@_!oNUp[v:)0QpH6`&imEJ!dm a[B<'Wc# C4Bo l>{NI\N"WdQ:yP^H*1`Msi{` ץb[6wж@@cSh`-Ag!qWֈ}@5vY0⸨S$TƉꪎAv+hb@`&!cN9J?CWIhũK}}.@[A-]oH9$&C^4M^ZT$0T8߇ԣWcRJңy|< /۠ĀgTV$Ȝ1jr}"Ⱑ>&#qLӜ窱O7OJ<"ˏӳ[H3ƫr(UmJI;~19FO^XJ4ͻ&5’ɞ{ >2WL/\}45.DV=yӤ"7d`C5KEx#i+AK{5P˙dZڧдo?#ɯU.&ZSȉIHBC5UW?!o0,91jhė B7 !Uʵ2L#Pe|&XT:hӤVxzlA88{73ߒK_<=o3p~WcV兕ˏFE(2 '9`q"v]4eayˇi6}?d:bP4M%tPflDӓ8}aSK_1ڱ#F?O*\h] t? ;)^Ťiim"_,c6]N Բi>ģOG 㵄qH(wdJ5LdE5|aY-I!"A_򂂏UhVXrr&^ :1DSfo@cuFZ U0]ήA r {zQfW&Zq3nP%VDmZ׆I;\Y#Bv8͔%kDpJCjQz*|`OALtnzф(pSx€*И9'`!`D}Kq8Eu6M0 c/Z|vg6B?ឺh.4N?/|#$qǼO=7 1aXy׉gۗ`w{ i 4ȧSCA' A9ȀZc?knxkάoG5E X/>W41/ Lp۾)?CȐw+4^0ۆ8JޫiGxC\PິBx=m1]+֠Lttc0/Eu6}荄; u?b-\zo]TÇ z*3:cg5j㦩שy.Ϙ+&2|,^} d~lU':؊3^U#|!! 9J.҃gb"F`t{E_87_̐7Р4)N_b`ʄJixmh5xlTBztfw 2@gkdGX@q1"Ş=hzt>,mavudPv*zНB0 X PΔ2+msy܇ Z#hxa@ӚK uCy\{"]6|+Rgt9/S0{ $ !*"@#HAlz`:#C@{=#C09y e) V밝B0h0S9}@JGE ~?(Z'2'FJ]q"tGZaku(?@AG?M^c2ڝ`BjWn;b ]W$lvk`q UL`/,*O5&\T57YC߲DE%1k0BeY+4jXlN#%.  ¾A)br qQ e'4:P-noD2 &הRK$7Ut6>Vg.w43#trVB 3,Ii&~̮R1>jS2PmZ.~\{9itzjfu޴/g,@6ޓ^$pO^f%b8.A]|^ەu&ycljmWmR8qƐ&bKj=ENPdf_a h<wm@佚&{!(#_٪P2N+6&V\nr$hR4FT!9!|<7[޾k$u39#`I~W 1at#d"7鑯 14um MJ1ՙRr!kU_B0ߛPoҟl0W_#U\y7FVgYju17aU`.gl03+,$U״YVUi9JS> 0<*)vN{!tqpI {%E5<(x1,XQP1W #ݷ[9nE7u}$"( ͊($ail!U>hW*VߎmJW1<1\1=L# mdm̯<(^#fxۛ#XR8>E?e;GGE R'&u/ $q4zUP9Lv !MuK1] RU]0cVŸrz΀6YsgQg ] 4Dqo B 'vZ۱x2)YV/1d5x* A8Jqـ]:L{LH/Mg-jAwH>︄.FV1k-38l } KЉlEö*G❄2X e K#7.$||/R/e[zJvY4&b ®nq8|痫y`)Jv#_: RTňwPfy?HN WdQQ1,arե C2Ыiuhr8)ws.a %*&:>Շ[-Os"ˤ+|@p38W*d&4Uf[UX~yɩC|~WXa;0U=TLɣ"5S{3k،;F =,&_~F W%rW8,Ƃ|*2>diX 3[w;Ŗ\5*uo]4Snnit^̶.t3Mgx*Jq}89:bHϢ4LeεmwxKPwd=D#F$E$,{=jsinnnaW&t"]L2jH⭫גCR8b)pH.oKe%՛^&43P Q'tрH$/;LB9!:縦OH |C&9إȥ u@/3٬\%1hg7n8&au/ٗ-"{@@,SD$ [ሀ(l S@ >F84 DČǀDLYc4- S-1=\Z@[)Uiv>G V\r mHPC2f !d2 yS6}Uэodk i_Χ M)+CԘB:CE͖L ]v%!vKi\T&PI*F@ \xfH l9E2'(& O!SHV̉2#D=vBTyt8J` l0'qГ0#^?d ~GHuQ~tra@8.+8p^H2<|p`&i  EAmIH#9V~ x<ϼҵh F ^[)2)x c4OǮ鷅Pk*`2vcdHȝl)LC.f sGX!G(1T1A`YZgnʪ"IzU|1!W{YҨK;p]ٞ$$EҁK nDH߸ QUirxWI1+7Wؒ\ӞIan@O4E4$ VtEBS'[QoL !L}&ZJ2 r

    {xȎfjreF h7G!dˠf@ ; q$>SI GT<~K1"ƚĚ8,5JYCQa`x)A\!!M`@`;m<c+u(1!ΥE4`>i+MLTU  Bk>: =@XD !Tv\ Fu5C(Bh4|ŶA#5 kKB߱AEޕrp*Ehzu r~>IDM`H8#)Đp)(~0KMlkga`6|Qӎ#HbcAxQWp$e 0K7ȑxC 5\xC୑)lbJW.oD[7uAai-Q]jS؞u-pu@#$T&)m2K 7h i42+~ WPK`#Cw&WP|M܀0ňYw]a̰C\Qk]4tr<<P@$:df4ca2ɂdbXo܌qBUjB1ͣX`}M²H؄eǘB:}F0p:mCI;@N3&T 9pw{\~[,6ɷ<"w8&UG|oi(Y`#(A*1D^ E!E-0h2 @1~t#m 6l :_X}RC* Fk oӥk|=99l/*Z뾈&i`p(J2:&M8_m&'#.x}I KVSZl8bWR5}5DsY0z񁶱vX/G7k7.Eo*5wڄI38z|,"e?bD&r>^QzyOoe̜)&0f$ >٤K'98bC3%CV^J?B%'[Uq ϴRƦ9X$2 ;1/VFP!%EQAs+ ^q<4EQ dZSBT(V^InG}S#6P@Rmh6Ahi5Y11|pzVjCJfڣ@2b_ V98r ` RbOkN KYJyJPWEa@$ZޱE}GmsTyR%O\'cTTF*^p&;y/=(I?2 SInjK&in'x[y >̀BD>L20(L a%neaȱw0OI D!c! *TW0XY;b,[JkWu05p¸/PA a0Ƽ?KT#+ƂmacGufhEٹ18_E<ԚptƳD)STBX"1]6pR\ SY@1(A1(e@jV7A0${vt a@A@`u`LX^^x`&X<}#fDA ͺ@IWŅq] ؑ>l >cض0 D"ͫ4?K[Ѻ ;AH "5q.-]2-AQ/oK{hLVv6\T# T?4杚8F0rQW,y|' logj0 IjѴ|ȿlN2>_GfJϲII[I~nWS| D^NO>YB7L2Hj rLD4͚Ʌ&Y\"̩R|dZ@Y}`W C$ CP 31lA/cIÛ0rMhN:樒Γ5kH9O`KpUf4*6oCUB%5@*!bQrF)$e & >4YY4E`00?P~i:H]mŀVP+ l-K}akuA~h!7"Wӡ[>'Ο^ƻbidY!(aAHUgnZ ""?LdFHw=;oo,.WA }@ED*56QDΨHaO'R]"ZHX(xO݃s ۊC2 ʶ]yDD!:"("+ 異#3 !LM6b~>nlrj(sv9` 0$M0mZaZa 譡L|N٣٘Gg儋1}%k$d}2:% fX*P ]^1#q}'&DQ 6 A /N 0!|BxCm[Ki [ݚI< _&, |I/A3C DL0mđDa?&X97 $Oݥd";_rfዔJ } Ah([\7YH-d`"&~ό[p8jd8ja*H> V%u0oW5Ҁd|'V}l "M>iFGBO(", "y>haz2IT`CL@]>r)^ XP1>ıPlOcoArpg ʩttiZ3"?*r͗Da3˥s*g֕.vi& @Yi9ia)XOz~43Ěw!,/"]z&#-~Ny@|dd]HsfK{,ZbIs*̓bkeޛ?.ErجG͋R^Z9~FZqO@%N4iBgLi6bfzauN*e\~HDI0qHx#63:NܠEC!<,do$LC̪-%&IӘ,6YTQbeõ'^[%WOq1f jLf_Oak,aMS.:^_]r,mvk.'Aj*^BIXxi\ܬ+[ĮEWk6"ʊ*8wBE` W3"lpv@u.lq0J?Dfa(+JEM 9m٥}n4hѵ_y'XqHW? w8r*UL+h~ WOPsNkr6/fY+ͅntCw:Z!xh7G888,Z~aw=l36V=;9mZƧyqro߳Î=Uw82CBIfƣL-j ߷8ñ4P(|c#jQ%(US-* BKyD@{ l , VKX?gpXWɤò\L3qD:h5G5  (a-j DgF߀cX}+M 5\0#L @R6X:ZLӽc>؆ygP빼$M'uKviLsZ'2`CXFDp~#BpT᷍+Z#B5A@J7cMPKq9{8/" XQ^JQ\h.Yh&1  kg>0t<:ubLBF9S#Dzx:\ Q%OùwcNGu4gU-nlcjMK={?y ?ˬ馄3" +vr{m]9ɧ3nOn2|$0ܬmdObi%Kawf ?q=jTׇ@J2һCa M/e4Z>扵h[ͻ£PVO "\|!\B gXemvI/knr !ӡD߿̏)J= gz$VtDiaU/? GJ^ۯ)"󲩡jcwص$+`c!gV`j4,,V` B5Fn'B*L-a?4ClKd)y§+؄}Y%atBj(%ſᮂ$p.iܹ'Av‘[5ثP YҼ(+E4bFNʭC~KM,jyNɤmJMJVid5"P a. `06Uj4{zw\~ɕ^xT`nIzu]spmҧpc?x_w۟!;j6:J~G/'F /J)ҿ0}0ى'H7A^IkŧD`fcbj=%+@9OG&4C]Nk d36@(}hy*h1VdXW<^o-Āf3aei4` k'AC'GxQ˙ BQˤfAC܄#`A\~dh%(DNE)` d(']Hl nC62Zrهs?jNXi?>|ɬ24.Pȃ*̐0ճҕ0+yETB-IwP&w4 Jzpu@]3mQeOSꐄ򟴆 0H!Ų E(PcásUZ[c%(tߝ1>bq`YΪjFV8Ƈޭ:pMy%I4m BG㑠LEU{7~, h,ײcZQ(C,tIhaj"l pDxm&XX!=K +Uٞ<|yյ#Po7]^Hs ]~u;x,= }sS~Dig' ;)T4+CWwi-(!) ƝwKofZ*7'RHj'-6ɠ׵%әw8&H Eָ3nNK=?-.Z%5L~w?״F}DYMwjhwn{Ƿ0 Hv߬$<1:hQTHb$p]M1 9P? " bp֢_&BAn.]xTD`Gq(p0"5|]#鵘C 1'#݊6IҗCN`KBd a\ eqyM)TGum =jҍh`e,^H`Phbp'3/is:|;q2a5L52zk9߄g1)\"^WPL*/?.ɧ_,>aBz:X!!CM={`l/1q?inZZ%ƭVn_`#}?x!$Ԧ2v*vĹ ӛFH o@+,.V(1ɃX$mh&{K]8NFmN쟽ta7E➇l|oO ^zˋə1]b;np$i(g_o>s~ݭ-"&E6S656Tf7',WPth D YVG$@! @ ':@_!3?/S+oyRꉛ]^B[9Ziġޅ8"{[V}ZHi%f n8]7į9l4w?JB%읠 nY?\Qgʕ7JmJ0ݢn.gSw$+Q8,ya=vmzؗPuW4N-?|6?^l~ӛ;߿g~G=^f=SB}Jfg;=4ڔ;ǯ9cLBKYk}HYбmOޅݬq(=7.g/X~];?nXWs_(99hbw5bjHW W/ ƧҁT۱WWCv>{zՄP3õ4GN ݤ6cz~OCG$m_.wH FOt&M ?!pJtQ{ $-L&qZ#j^ž$4VhOf~A[uOp[a=keej,?yW$p+9m#ddH$I92t8C@ W"o}q _{/AH+KߠI.ǽ35B iߧWXeZ,1Kwl G$ ;V^8cBj1'̿$vݧg-sk&B]*4e慱{rL? ‚%MΔ 4J}ՙ(\Z4"e|}~9{2l -VYq;~|35j`޻ &%P,iz{7'>Hs1y-~bv tL)=}g'^ G"3#rߩ:}c1.AX=$$$=bA^16>RN0z}I2ZKۺN#`P=ѭ[U~n(/z1I L̲Scsߨߴ_ \N;#Ёnߏŋ Pl\+oqӣWbwlͤ,>cdlPPQ;kP -sNM07A8 WR )3sjZQk`QEQX:چasC9`^`ʃx ۳:;a o4{$}fpƇGKR>'hwضLAJ"ӌlO9kNRajζ͟X>e>|NcyrA?!2ㅢn]e4tpTw(9hj4`ܫ&M w!C D5\ l7078: @~~xM{N:ToJhS6\TV)#5$/GR,c"IFsFhuh=GK̂YZs Q.˶i5}@NҬAC/&LYKP,(BI4 UV1:)S}=yy|j@:0tWONM$$j4oX7U ,!Oy_ZB0dݷ>v:o`=*7dI̦ft82Z"8|Z Z%g~ U P*]UN<= Op1Ȭr U6*IdUFM>HE^>Tط1iaI%h$Bvnm\ڒ t!eaWV$U,dAJϏhwnGe"GJ0Ҭ͚WfClR,R[y_׌$KN-]'*5},iy1h$>1AӟI|dt2ީy(>gG`PђIl'CB#W'a1:AYڥJTaZG:)gN.۱kUwYYX@NTq;pUootσ)?G` ( kk7+E193ϊS"FIBCQy4XP)Fis]n,1WG+E׸1hrx5w^Nh),*LM`YMys2`0lGq s[2@+ ̤åIqH< _dJ@,*DRB *HV^9\t=j:¢0Zp8DHiz]2Wofi'6TXV=˧@ċ$_4lsS'v?|'0 9J+q88ʞu X]$7cW:d''NxpO5s^V\殒* yB50s@&U0K'Ox6;rwW #6qd"bsupEҖ!TbHF ùȻm;ݏVtO'ޗɏ_0!xO0IT@wBE-,Ccw^EìʜôA HhQ1xϘ:R5p͢0UEM)PE0[[C0\(RrerI9HXϢҭ}e&Bbe*HL.,!Y"t!-dW l!'!l"a|?;뤭jneSkؤ>䟫%IJv!Qg΀2"% wW>07Z~K^fX:cK pL,' eMV]1J$=ׅSxOh}2K&!4sщC :ZƤ<,T1{KgR&`ZcZ-9PLJ-XVrS?8kXVkHF)ċ5qLF:iHa 䒉d#[-Tӎ$3-.bgX=:z(:-a$̨wL;.IaT"Y~&{򓦖'#LU̐ ?G@;ȩ2]V/?I8LCذ|_C:wL;bfCRn?]/6m]2FO . fI#n=;]Ƣ9r5~c&kBUhdO>|4&?Dډf\79cnnk5ӠMbV$dKtèBI?Qk V*a]4Q.G9D:JߠckjW{'::KOz5n*y;+r !ARIaB WRt].H_JfȱJ0PUOoN-7'M COjGBH@7MoEpHf}luЀlHh9nZ2RT6ZLjPwWYNGE4 *$H>%+|Jtfk7b>>D-Hs]@8 ՐEjvKq7EӚ;zmbW~))YŸU4GrU,A^LO6Ozڡg7W@flńHz/׮ќ\m_@$KSPR~KWQ8hWc7n[o;頤?dؤ"c@-a]HX[T%⽑"ak'$?iWv[kzۤ+Is!-ll!=Sq'_O)&:k&kӌn%VfÈV.)]'Z^w@:z]Ű$=NUԵ,R ᐚtHZ?݅Jiv}ӞԏU <]\=i8ΐ{nhv[GChO=n㜵<0yK~ʑ7bF&=7.dơd`b :Um;R4j5ɸpk{{)+ށ?ƻ0n`f,O8uZ%K,DLdRgfUaE\Ѩkؒĵ}qp(CuG˟$q%u-J2ʹό1L;n)ΥOU{Mq naCmghu!_4R*d訟[MnNC7$UPFmke{GsF& +'&'.׮Yꧦ?a=9= ےFfi1&K *IymCv%gZ|RjuJYt3Z8@$:]F rU".Vx_D(hNv$ߛ/7y $ Jdzɞ"u TpDKܥu\CL# `1?yqI7a;K @D' :P|6>ՁӡTՄɻ\C|?qvٟu?<'{A:ol~娔惜iGA_=mz5u11nzGJ p:8x±MjmBz3wp.h6J1^>lTѮ<8lNrqviz2>lˆGoqX3* |R `E@'nPjC97GcT$y00Id`✮ʕ:M']5 aNN5IxP@t 1h͂^ O^в)}*fx Ie&WWJ,)ۧB B9Eo15*w0&HfL`Sbk27ɵ3/r?LmQ)7"Hy^H(hr@HE`h713-g# &PB "&>AъiUwZ,BSN1q8/@?HZ䥄H6)tl9K`qT890I?RF(7N bX>"XNIYjHi"9--I,@+;| B%JZXđ=+O ;Ru.ڒ8M CtHv\\a 5̽cE7&i& !OETr@ ;;ҎA.kږ"]MXNOΐAmr9ݢ2.QrWneXz4TIZkOϔR ȿOht^mnjux Z?~@7d1S?cYm/WdR;{MH㨱j'$@!&1TAhi}lu* mjM!d3Z8ڲzԪ iSnmc"ʻAb}Ish~icW*;!oh)cfZjaGi鏫[ c1oYtvYhȊr)ْ%fP$ *LŮ8aޑmqn}#kLb$}!]7@҂f$1'-@`r&  >]VD,xPdq,i#[X7*!P2PxNAFxĄg~@5?h@7WVr!$ $ ː TeP@R "1 J< G Q\FW4ۄ{RVp!k77cw0cUw-DueP~h' ɌTi'<=or3} yMUs` v*- r#T`p'䆑YA%xWZ㧾9ؿgylxrHx""[$m7pQA)D| G+}JSQR1s+F;Ĩ*eB6@;w):y"*h)r~}&M{`{u WD8"G#obZa@r2Igt9<ɀ 4cYbI\ݤhgæBAUY?[ q^)Z*\\o'^2?c!PC42TM{sP50Mu4a5{g=46-VЦFCW*oL3TK/v&L&/)Y5cF+x[HFkGI'= 4N^m?Mʫm ZM&p&PFEg| iܐ IX~vWtE+ygDL6`XPnC[ȗſ@Wxu{?pבU'ᚈ?i^r;ɦm/GBSICfy4, Bm&%3_;wOc8imq6{@y4Mlsd v:M%[5 Fgt?Ml#҇g(! v.=8+ ( $ iI1q]Li[ݱalq$LK!B]4-=N}]MH_St̢~؛N 7)yl㱚|r]=,7L601ג`W6o+,ܼ&$ !xygbDRNl e_#$((JP:FB%#3=)q?Y'@QAgfG` pn@$@m3T1$H} %QdV)GMJThD>aI&|Ć|w`Ȣw*`ukM80qe@#/f.Kb{8}یs"g׫FbQ]ٌ y3B/iKˀo.ߗ~QX6sS9n밤neSyU' Qͅ44PX RW6Â&o[X~A܍2;r*ouTޟ*k[9sVWTT!9g AMȰ3:}"HQ~ RLT P*3IC,ER ? &n8C8 q2KB73;d`r3bnzRzU!.t .O\K DӅˇ(,I~\v:\Hd1[+b~J;`=z_ɚ KIZyvhN0kpȸj;ZjQQ3LJ"91Z 蛍I=xDV2\$/xtNl$oFާ3o[iBó0mx-!5 =~65KAy?=D+IAOj5骖5vn;BBgWRɖs=K5g sÖ ^B"wHy, o/ΐ[[5ts~x7+?0dE*fOqsh#[[,v8y_Ū8]$fHU),_h~3Ikά/-:tYհէu9nۆnU#G/ B. 0v?ٖ?(möY $v3]yX\L 6=nyp:S4mAXf[>#fW||m1v4HdaFJ?Ak2_uCqm_&NJ6 3S}CUACXV]OZGp@VrRJ\ra eΰI]z䋨M< Le2ɴKz(^wn ōnbQRfZ[uݴo%՘V̜ߊvהm;!ũpf}Þoe(۠.ISґtow]_md?)9,v7_s4cWV Lf̠^g^YaOw<\2q ֟Uʭsp{d&ÈTz ɚ_ʆ׮CPXA'e>IpUF[[wl;imN3.uH5^QDXR2\&@JS2p0j'e>#"L^CWXՐ?.rI҇.ݸ6;u lp|ۻAaD"֯ t 7u pC3L H0V_+H."N*YY*rpx^ LQ>)EkX*' 7)>LSM_—8LiLbj@~x:мTTvu< 6) _{WzM!:-=-qKs!(R່(֒&LyUyŽ؛peԇ<?udəbz#, ZJeۿgek1 Kף'j˃Id`J>1B%ʸIHF}<FMV+%%ȋZ~3{zHw%H"5&#HO_Җmݥ aY]0zܤȓ$6@V`ƹ "uY}o*2 m\5g gaHF$qRt|D|oz2C?"j-o*;tV﹧UqVV jl.4nWVOI[3e5F)BM ehMim=ietVVW.+ *; ꙊG4)jYbM!G(S`K])Z=W *mщq$'U`4˪?kJٚi4ύaWB.Ar-֝ Uĥ_WI[RjBoſ͟S NWxi,i *\ ޡ~Y?N9kpNƍ%IXҢݥ Pgur?(@J*H<uݟM٥/#@>u|FLTeM5m: Ռp.!r !Q[ `Ǐ0cx! BC]Aj@XIQҤ*KgVUb:=IDTp zʼni;Pi\Ы&,Zvvr׺+|qx]P_SIu `Thi~? .~az= 9ywCPYCiG(t.?Bql:WP \w{jxD̗4lvDڔxuZ zj^4"FzOJo>?:^d/~yF.2$S ]Qd>VG˙HiC lW}_Ⱦ-ydY\$X.΅.YJq2kQ4q ۹UK0vzMڝ!udrK怫tCK gٖkM]:F 7Hpc5dc.dO\3KrUI䄯qᢗYR,Z4n?`-&sd]43 ־cH>1=)wqkxKSGq_DJ* pY}LF|7VRUkH ~MI^ҹD& _v~?9@ ` A`#?8絴a?IM[ji!KFG6Ai_O ֽR/rj1"5 \d $@F؞vo59ao1[#8og[o&=~͚[+N촊'zU57tY67s&5αHHWMf94-3(i}osZw#RcЄxG^h=jbXnKfF3hTÊ ɜ>̋F\Zĵ7QK)*woӨ/H]9Liej͟d2y (EJ ]BEѪ Vjk0 }^Oڒi5{C s 68*1 8¡q R·`T(U9Jd!H*VUȥ߸<2R?^ "WgKbܪѓ2&߱?jξK?k~a~CfpV~T3HLhe~M3n~2D s Nb4*?:_ Cry#Vٴú¯*sHv#bh줴)@2j[{j9`ѢV >Dd'gzD 1N#InLأ 2mjrLʱ!2D8WI !(q쯇ިR:(7.AeTE : )+_?`iur TXi/‘& [- tهgdAYH瀧nQST+O-ð)3\jB%Gg'AY@T_6&<2D8Adk`"!Zb&d8kMv6'!A6hKնSçyeHәnD7!r*{vsy1h_uW o=qWpPڠR6M~IRY~bbJ ` R4Φ@dDI0)u2jNlb a1B!bVSYԲJ$ I_F۸I*ar EGd_eA1dpbK 4/L$!'UHq #\[yH&ف ā1修QzGt R|Ls { iM QK @f”jem9%->sO;H(BŽqlR(IrxSz;|D<[}LGw4R+0CaNXF2 xɃiI"e>SjrPWୖ9%I6(ϲBNHIS$A ZTAh35 H"k2@P8$%hEӾ&W?)YYБOiHkC&#aH~l]A!s>+a\Φ*iO;)~y Mv̌'L 6wY,掷Vqdb-`r<$f%zkr0cY0JH>[V%1f2K@UU {>/@JE "rPri'Hpu5q+j}ZºJd?MO=?~k{^?pY*bCX:?ź7a|aq{:|OAOXʄv,ģ?b5N)C4ͨ),DԔ2EI6nŹKU%BPTKF8qMXACd!Q 0CV@uy,lPlqlpN0xN5)' sγE)fy@'E.6y2Ӹ 3DL̛+ #;a~ͤ ޕ Yt H(DAHJDOs(@PUra3D?͞@=u-CFsyaY"%ڔ>j>i9gA*iΜ7 vɌ&N Z 1M LصB:2u# S)>X䅭Hv &+ŕnIϞͤ # /WpPe"c̴G';_~l:y0!ic)$Y-[}d5$Bwbu;c-dz=qi?222Y%OlbޖDAI^lI>:9B-)E{DAd4:Eΐ?6OkG_iT*bD^upm)O luXy_ Q3*K 1a;8fS1GìD6TA_̥Ab JTmqP(0Ԋ@ ؂%,7AI%"|I{Ô&~ׅ~/^\X\L?V-y9ϺduL (<#+a$sF!6ר:BK!V!BN_Њ][/9*DJ3d%R2(ɨ&t@c}To.}އk3g dJh$ Սh1"Rw: ۑ%] ix)Qj[=c=^xM C I_^ 5 پDKB@3ZI|YJkU@3A:֖VqB-- #_f6(HP !f(wD()f9&HXS'(YøuxoŖNԷh< #(M`U 'xS +'h#ODU bA6 XN9EN-%͚ZE~cM~yK$Q)h@j7-z[.p@TWQɚ6rQ_6f` 'H^BiJT aA?65h5ƁA=PrH>[^0q1-ȉi_(D竌iYƣC /g d,C I$@zI4RB;Y}ܙ2/rSAbFƁNBJ@*K@wa ߞS0BOM-I6A 8)9 Jы᱉O=82\ QadZ&"eF3p')j{W*{ "EQDN@̓Z; ޕ?W*JA_!@jH4( CwD=sz&͙/1"dxs(`u%HzLȗqFY%KhRC:s0g"AJAp'$3:87:{_&QSJ9p/M0\"2&ev2G} IeNc$D'ݿ+sr+4(4<Z`m| Vu.G)c+v@%k 4B1ZQ/4uzƈeH'֐oPT+evw!L aǷϞ)WwZ.Y*ev q V=H+¯DAr*"OrGR" "vauZWLeJ%CK T9+|CoE2ȎX%5j!&F =}WGͧM^FPVlz?LErR51أwYKB fsfYx{]^Qo P/Z[o>x-ϥ{QcK$7*uc@Chvշ1"tF A? Z_4\JZҳt#Q!֌/Z-VcKn+7zMo1&ŶR7:721G> }vhڤ٧PBp@8/)@1;`˘26[T:Yq>m/Oi. o-.qG iC[/@Jp M|Ynr~)Ա{11v ª 36NW@D,CT" JPaA\ k(eaQ/Xɩ%o~f@xt* E pFSBAXVLሀE%!<t+5j4Hkh"Z4` :˥Al5pO㟇]ʛWgf)plhh'l?"6e =qsdQѯF"?*l(<2<z{ua:Ut:YgPڅ|N[,7n!8/;S0Hd6`R4Γ A N7gnNAq-=V@jFX(&9}V27~A U aQ gK޴7 rp)5Zd8-^x~Xq(x]> FԀ9&tW;m|rۖ6ERrY />gsg;5͞Gs:1dݞ؜nh+lNg[h5LM QȧU&3L2i=ݞRݞ#Nlk 9=E051d :4A<ap.JhscCM@ָW 元 Yx[6h yg2-vdcEH M@r L@ZZwVD2F kB^DD*HhMPAn aLr>5Iҥ}fL9Y#xr}fAvbȄ(GWKIN| EEW*',by>TV%&x8!1#Hx" &VZU?|m F-Y +bh!ov.g? cIQNzbIky,tjI0HȀR#w~FQ]CDLEEL8Sぴ.dgW#imUC}PTԄz=&%@bۮyD }%{ѱT'GFaX 2OdLZO:' CY^nA=R&ߧ&/Go+KRbC_ r3W)SPb_K}6 ORv%B QO.r>GDMyr诿{Zs]S&÷ct *ܡ)d{ G W 0`(Ig($:v "/ҝ\DՙD+1/fy/yvo: E*槍]? (;I>f!yͮ)e9cZ}"X Z9)y^bXkazS컩G!Vr|}rZыb^X1S 1F+8ZihYX㸚/H //WP)Q*/"} & lAOĚypFew> G Luj!A]dr`S6$.3K>]#'^ԌYVqw{CaT^}߈<- a6qY .`B@X .HV.X+Z>:A|w~SBZ y(PB*;UySIsZ'LFFRZ*fn4zi U yyK=o/+"F[IɌ'pFAt޿¥ $4H |6ΠjP#xR܁+څdEN嬑!4:K|v&t&)xҬY#L"lE :XwXf,I/>! WwV,d=ύ+(9y`៷V A$V`3ӌ E bӊ *T'В$!I2Rn}.hi/!{-ȡLL. +{P J&'2ԽY"#{`jaF$^|Qx!af (̤ !p [6RHٍRr*0G1]P@b~#R#NAQ˒Iw6Fj=; =)2_ۨ"x_ӵQar )D nJBeUxn&uc%I׿)cfv!Svh0_!9ӣAwSeSy@6â83T6E( Iec 2Ii$!IHqY*:I-~Ii1X&"MZOZ&3&'&[2Bł-b60Ȉ >"{vQU]hZv"ezRjʤ`UW'P\=0$9Չ:*`BC(@!] v'4<ϢK>%ئ. HB;x$"M@ ="fMA3?pg:l|6xL J^jܢ}.c<*るjOrw3AB ,>:H5m7J ةsvKK,x1C-m|w+8'T0־S_8ۧ6B[t`pZ8c0YȌ.' bG0KDX74.I\G $Vl& ؇bpXcbȭuEYZ77u&:DUt'E1f3GO{ 2 0L8ʭw2Lbã}8C%LkݾJ筦썑kD][3K./bIGW$5>q71$ߥSw Qk rYþ*ff'-4A蒞߱慑oΟCc3iYGl;>ƂOlyPD0A?@Q h{Vʕmw(D81) hF`^mdf~%Z*DiAz0k64Qa:@?5/'S/7}"=s@}`Q\'=4X QxWy_mI T*{ّ{^a{ڠBG2̟7>lF.L:!F6(\'=)%~rDboO1$\OŒiHr ݜ!P'c wꨌxtj_c~V(%3,R*g.DeM1QOLi -| $-P7@hiҠ.~ Z=JE< Vh5*J=KLg|/gFGeTTA?^&&O+});*w&%239F$=+' c3`ʜu!Bw' B.^Qkr{I&Pg,0#ORS$x\f݈fU tO߉͆^5pVT;E]Kkeoc56@hùdtj44:"(U?[΋r#:pX/1ST1XOm6P6BݯcNx`; no"fÞ+ |o嫖?\~;N u5Z*8y*x$դ}|~VYDjL#Eռ(6H!VDrYueSG&nm{!\,WЪDQvzo)E$HH͜9+@ehF<-OQwxهA%Ho4X\ːIM1cy>3q`&΢Bc)D@{uз!b>wWDNIʜԞ~rZ$ƮFG)1+kH' u(A2 #N)Ğ}8bDjp [@d% Pd&_rڠʩEs )Vϑr.6 XGC2 9볯 4"./l.rЛQP" ;A Q͊a `-^&91qќWKv_h~FS\9B;JN`bSnB̦n+݂2HQO ,U "OM H: n8)< =2>pOaa[HY, EA (E0'n?d3&KQgH[%1U2?xd6r`Kшߛ|O*U@Z5m[[C_섌RN@:<M𸗟x[hk}H>K9TR&VpHwk NFy%&/@.B&â-6uǮ'<ld9kO65DL%6+n4Ai]y +{}!>x)JZ0 CRF)u8[&n1#*!DR <֡) x!q-MXʣ`JZCyzD@*"HӚ"iZD?t o_.^HpN^.,CX8DB~rߴjc-v)4aa|3o U*WjP~0| ;A >%7M/9F/;܃^{5,C P6Ϙ3fOo.ڀ|$Q{ ?%Qo9Y<|Hs?/#sL ZK#A1'?Y?y/z{?DoaDCd?.~0-QjSp8wsQz=%?$/u s ̐$9D & g 팄#&u %1%c4ɸ ?SP`U=wdVZvSpzǺkx#mߟ@/44l[漪mK(NPx GAIXײ$e̶xw ֍p+u,yoI5w!qg,937mN=B[3E]F!e'A&.`&bmpˬ!>P h~ X$:&L>G+t9%z- 㼠N_e7ٵ2HV oTR BjIpRH38c̞p܎R5`21٘jU.Tȓ.yʖuUG{OSlcÅ|ȫXigO@C˰jůz5D$r_jm[$nR[ԙ9]Tsa mgɸxQSms}Y:Wt=oJ _PAsD,~H (;5Pm]LQo*uaCm*yEbCri<* xem QUG#opQp$߻1PťyŐ(%AQêC^`N:W _KR)@cd|'~6$h RI(DԨTENUkNo?WX?& xyOfXs՗@QR:󳻒QB |7{~Q'x( ]!sghǼIKߎl 7eV,h6ǘXfa1;JI6IP.B[,iO5̇KI+Bvcv꣞# }/7=Dh7sк0H,Fڌ7vC7kLm#⁤z~U@~ {'TkN)Oᔌ?R7#K=9ɢj]4,RC+ .>G%HJwԛSH9K @ ̜sEx殢 S~wH-S7$D4'2X7:@? $1]ɔ+ KBrċ8{R)`t8SJɐ'I")%IǙ $IQ(vS$VǙK PK6v ҏLȨ5'HqS!"N y`-]XV8";fH:-Hta'Gѣ£` Ƅ-h z&O 믙'z$5`8{g ķv2蝟*`r ) xϜ>n8LN|0o°E0)uw- Er7k]n=Oiўu" <2> 2 g00H(TmÍh:Bhן|xנEZ8hBTJ-@ 1Cˁԡ` -Cci ,͵Dx3៤w0 *H(/" c:8h+̹|`QE x gb1GOM|RNۄ9,έ -\)&joS*i`ʅ)= 7z#NN$Ů)v*=`;)pBLc(N+?R9p+:{I:Xh<8rR2.ڻC4geyrE5>"ˣLqBeZ&﹗K[G#rI'5jFNM0M=uabq 1%E~I|Qqvn*TjF7Q uށ-3Ҫ7V[u1qxB1 F8vWE5¿OEwFhQ 4q_93Z1 Mս7c;e13@1Hˀ o1&g?J:2>手!(^17ʬ!iFzA@~:Գ;,ڿpv;Rt;;i11@Vt'2݁g1xoFȈ}J\lHE7;݇eL3F*S4TϨM.(4S|:sLD_EhɐF:^q䞚2do2X$* '  SEdV-b8Y?0I~"MxNNhb$F,T53R0&U|‚w-R3XiRO>#9RSAQi1XPyANwCpSdK8VOTϧ2vlԅV" 8JE`@Em.p/& P8v %.0'ATf7+Y2!A;ukQkqH'ƀ )]ԣ}DǣU55ȴ=Cy$ܝeDZ1s/Qq%D3 |jB30ڒN ӯqhR-a*,nAFvQ /!lXCp/p+^jՀ/#/c/VɕN^qfvϐ6WC+US x.oM4|EE:qNj]$swc>pbrO*UM&ɆttBȬ" ="-Dgu QR s3r1_d\trNoI֋F!pȘwQ/b)[EGI{3~wGhPbiip:YQ35";r M[ AgeJ/lEҙ=#!sEϕBq&q٠wΖ !RP8aQd8+n6;!nLV@` !!܃\$zǭ~wv]R';8V<=joFCV4(P1LfiD$;|`N|iPx@ɟ FOY ~^VT.\< P˝Q!JLVN->BD&bKZg`eU-\V(hD $@0QXpG/^oV'TXd1.YgGF0sɬEFư%,KQ.K ĮqݹjSRFd{0Z†Ƞq,ذq"(cˇGnұHxORtJ?5$ *A2o[B٩ڥaD5B8Gc q;,F@+#)Ys0vie]+U8#Аo `[Zƥ7H@bU~"kk쿔3vRN1 u a/f=6 1Ln ڐP T/uX@GWZ2H*МJ mQ;'l DM1B x~9ζ-~gStrR*N :Y|PcH,!ţ ?6d%H!dζ-#v da}e_$;LضsP0d! R-:cےR3=YSVSӦ?$1 l k۾fڰxw(V u U5ANN^ # ض'$&C6|)uHz~[@QqJ-SHw']#1E_/ߖ`q¿ñ#&8քp:OoT ?<#|0@NLt@v %g%yiÛ|GH3hY4Y$__xO[Kn&O$iyނ~eՖz{) 6N"Nc>vqV(G y!OZм$` b-ׂͽĝ#PF<.N2@$ᐐ$Tgh{j2{;%6_z 9F iUKY@pA@Rл_eX(:6@mb ҵ(9ZJegt7bO]\ #(QiDvxcDH'@f axfxbB &g$^;~YL!% ǚ*}(;ސ>mWyNm | O xЦ%?W˯`_?ݬS/Tt DhkEk|fIf.P`9!N"bGEԄd?43W9;1Y4W |&^)0 p|#`Ϳsg}h_-_{&]ൃ͚i/԰oBg}_1.P׊O(?ߏ#|8_R!4Cp[K<>̯d%{ChtU-ow%6KBP!W/O2u44$eQŎT*NwހP rF;AnmO P $1I~0m5C@5y9_ ?\U"cq/ G1aL>Xdi%8m.,7RpcUJN1%`0MbCiaH`0I-iw) Շ;E ` l|Ff`C% °w; S`X=t 4'c5|"QwM:Ce50awa VO @`lV-نk'efo : -)e))QO}C12ͨËw&Pi2 zN4~Ϳg~9iktR,3,7:+Z :%SF_oA<.AS{݋GlYOFFOC xdQ8IwڛûMd{T$F$CpTp"QOQxH5y&T݉p.iIׁB GaJܝfy,=.Q/Mu{ٱ3R&ad&.<30)$Q7WN; $"_ۄwg0ҤT3P@(q1G şI+U%hl Ζt ?zl_Lr~4wd}T6m|oc0'^+T-A{ H#JuׁÏmuӄCQu:L6 H#ÿvF͋I/UO&Ah25A88 醽 b!3HBݯd&EAOÚAOO^ b ) zh(CfWbIuQN~JDfUόi ?_M4G b1Š$1A*KU?(E^)P*0oN1(Y4->\?ӕ R3QE/TDzt]{ b %ǁ<^kf_AE/i^5][|D6cJsmF2 dl6D͙x?_EZrI3,EmrR5D3!ߤ`ÕHnw7KAD_Ɨؗ*Y3ɣ`VC#[:$4/ l? \ɏn2PLNz=N[4Ժ/A6G8G2ooH"wnzcR;24A8` -s> @c%̛ M-TԂߴI{7'lfbҿ-blb ;IR}H%6wRV?tpO@Ja1vߵcm͜ P#T<HۅԬ\$b^>n0tomwҟVt5iNW;.7ux؏Wss¦KMj<><D(ʦVxCy7qQ</qo EiؑHs|xT}7]NK7L X Qo?>>4v({"(fQtʋaQpI \3(C68YCC~<[#ob<3Ԗ#vd{H~3|Fݡgool|js'`bcNHC(tIuI+{`-"M^VDJ1g6?N49y.fR޹/<Nf:Q(dVf!cB˛4u[Ggߙln=s/BH6w9<=H_= :ǩU`Oa5޿Q8}hbꗶ͟Ped hB@*37wI$ Q[!{13OjRy=ON}YN7bDm|Mg\XX +}^ /_h> 2j8NO*~?nݔ7;~QҶ<'zpj 2bLYs{45'7{i}ݼ&o(7g8rIqZD)]|ƺ.-$5h@C#BhBq*p. `K-#Z/ ٺ6\;C6iA,Kũ*lSN LԳT`gyҭN!Mmw6u T^v#ܳ'6`v3$?]kQlZ/ga]?oq򁿚?'SwQq}7= xsX4mp {ԭv)hO44.[-+4ݐgͮDn{Y թ'(2]Ȁ??X ;tCm?y|gjj_a'Xd#6ίҼ$?ZïgjCU6άu# 2 RdQfXOڡ;eT/:_ jIVU?UfIJ9vTS}@V o ]XM2#,Je: wHVUy_+-mZgfH nz:r$"~UAbjm)mu m*w|wԞm*9W #u]5۽zU(zd٦T۞X Uצ}M]2Ek"w\8իBT&&hY4>6ٷ.\ҡKu֛䵻B qBu$*Ԗ]+u5UNnXWE)/BI" ݛOܵNB./-:d_r7% "ۡa&Ѷ6J_v}my.5dHDj.; lӪGlYcs%A#jF5/ӴH_sZ\U7ͯ ̔e괁 j$&:9ײNA2n]E*A/>qTەe']\jf2jWiN9M߯5e,~p3( XP7ld7݉hQbSOj\J{r=ekHNgW4  ͓ip>o9₨CEJ<<H}WI+:}[0Ժz*t؋(h"@',+ƺӽ+512qP OYWϖώ-tcx8=?9JQ6i 2`AnbwûOKI]ʔhL+ufO/\ ڤBVZ^fx>p뜼퐳Z'Z^zsZœ BDIv 5PWјHBi 5GZV63+Ut~OwG5K4%H dZ M8C'P" @)mG#q(TݾѪp ~&u AɯuА7WMًJZMf/-Z;+\=R*6i묶vh:0楯S5hqi=;Uk . "׏$ Lliqr&tta9KY/j:Ӧ/Vj<,^Hf;4s󁂛w G&%R3`9Ci|n-ۭK1T+>ռ]lŰY[+uYU?zcr(j]>lwGηn3ZvyѺ弸*ˑ/n'w6IRyi֪uP;ۃNbwmTp[u̬s.+ڃ3;nK}xOĜQ2aC+?Oځ=x/&l*, !*J1 Ƶ~꾿u/;K]}&כngSo#fzun~ؗ偵''K~ˉ:t[qxjK K$W$3^bgL9ލ5oc7L._f氃 m?dpoƒ-y #0m\i''hY}ĜTwЩ9h;Ľn_);7dci2A> ˬ_sӁ9"_ٟZy>wڳ#ծؒ23;کƆ' [5)Q&CXHDs bWrF1]Г3٨qLqmfa逌C;ٷOJلϧ1 NEeg2vM_YOn1 M<iN3}˙|߁Ol`~t>sNN BO*V$QEL֧Bz`@n4 c Nv`F&0k{|G.?=6жs8sWnsL~ffivҺ76ZK)N*^v mڙlqpd~b_6)$܀MikL۬Noӟ?o}KsR5L_Zۨ~fj sI:maM-$|UlqoRUhknw_ؼ B!Gx_ɞ_^khS.A3J J{ ;F'A%ag -SoaroiY@ k$|z]h=#}S*gR)\CGz3SwY9| 99oJ5y$ ΂<I._6dMdc_nHe|x< R?ӫ,=D']PYdKߕ2MS^=? 5ʤ ْ^`%bR9GY9a9J.I(G-Ԫ&Zs5Bׂ"WўLhO ޔidDʶ8D$v\>r_>D9]jRCF/"KKi ɩk 0@x LV0?R$q (D< tjv廒J ѽ@aʱ3koI/X?`om=j"ݼc?p>r[cQ!d<&KqI RL.a"P trWA-38X{P4.():Ξ_Ǽh`D^R[ofd$5ӦRՒ.~ cEʙADHQ0X]a_߱ rm6on[Wxo-M~gKKkg8% kdJI**YQ5Yۆf9\y$_TH<m 1PsJX+&fR71SR{zײO}N(5ńF ֚f;-Oval Dzf 6kT14WMY;Jj]a#!R bJ"?'M(>^1HHkͦvQn?~N9njRSp5T>Uws@ G# lw$WfnQ9҅JncIרF ׀ TL4VݓNEYT !?SꣅGk7Zg3F3a{|/-\2?21W֩IvR@m?6`rxʠ^,ݤ0c z 퓇^Pgבo*Sh̤;oQ=S)\?ħ<DHC̈́;π] aY(@V%%8זUx[9a2 > "@i@c0sꏉ91V{u ٕ}E\IxV4 5KPq+NTfI5m` `I!uЬ- {;.8G˩PT͚saB5ifzsRIDR&>E!>d> kDzu֊cq=bOg88\;┄!&; l\-DlЅZ,I *1WL5XJeV#¸C lǀ ;1؍ٔZL-`l4@bҡJ4ڴ~jPVa`;,H| Чsa+} OmrZKk~MB^罹:zCRK+yWM|᜜g gGkF=zvB3S*YORh{Yyy ҹ/-ϻvAA;Q)AQE&'<,=RiHImJ_svsޛɫ.pss=e|?5om\?ZtGؙ*`/]SXuKze؝чk^~_%K`/D>4l:);>l'K+z۞8˞|t&Z{JF*Cld ^eZʾ[?.}[Z+~}+uirKz U0TŵfX !5-srQkK/Fk[)Zti$ QE_BJ.nvU7B/c`@_<Њs!|)T,{`쐱3Іl4W7a 2Ћ.=c>wޝܿj[K֡yNW^C;Joj.xQ6ڭtHvNt;vk _p_mBT.Ϥi.xt-^/-ޞ-% wj:j 6Xmo>- 8|5xN߉$[ 38aDh!׺;[@=K DPER?=̃| ZqZ=RG9\/t -JqXD(6I3%pɈG=rj!{גooqSVotbk=uHi)#%I@C5;Ơ;~ ~ |٦DmÓx?_ tإ0R5!Ɏâ$wdr.[+S,6oqIEuI߬qG'793s;fxkM?W6U΃jzFcW6M+*jQ߭!gxm5Z]2~mh,)G:DM`W zCUh"|pƀ1 hr>ǖs|g-%eeaΈƤD@Ca-Wi=1qZ{XE&-7Ui*@{z ]:!DiYp>E &k+K5zTt YH]@`"gE s]YNeeHɀ x?lB?ƞ [: :DÖFN6B`ݖyfr'v^~h@/W*N௜y$N00DbsO XQG_XIv@LK(}~>(l=w]~V_:s[3đ 9Sjś)+B亞BtIJjΫOkE9@rmȪpȵ`ZaҞNX'!R(@NU !HeR})F\nD8R DIQ9Tak(+TȚX$=@^"鲣pJ[M>9GN&~㪗̸b#waMyhL50pH5 'IG+B7hU@-"$MMKҪn&F`>( Q*Ǖ -2P?pKo~nه\=bo]v;\K` 5Vί&(It,'#hI$c c/\(37z ˭dm@2'0!1BTvk1(1KʠRI  H$UY;c8 YqH2"'tsKf_b%lDL!w|>'0>D񲅰=Xy@ 0@BP膘tAc2)#WOt\W}MX5tBݘHӉ< }n29MyWP$f 4x$;_F~)JD6ƈ[E"x}$=НbosfH?{;3I'gË'$X6ꫧE[dQřh,`0 p?YGE cNgl H#45@L=B&smj7&h ѬpTsO~W-Bg"ӋBr!cfi'hŎ^`%s.1Uj&rAiKn Y -2BhTG 2!ѺX %Ks'[]o+o\gnw#{zuj/0?/꾼yvuV$U='$_,S}(>h/pI)B`+F$Fq@>zkVW@-qCZ' Ig=Eh$N^J)5MZ`r*vk=xK[uN =Z?ŭ"`bIdL^c6\/Ɉ$~BP|te9u׼F[BdY21tg_hE 98!_ ; e]>**5wë9wD"R$EIȥ*>[|7 .:ϮPxMb!L|>?Y?r}i쒏~[]gjKff5 ,6hMzZrCtoOŘ4٭ փ˃mP @x^;r| ֑#3PV`ܺBS]YcY6 p.\l,Ƽg6t1r<%O(@(BܙTI3P[fvf6&J;d.why#ßmDHU@{KA4ZV,7<ӿvq1/>R(c>תp,q >2ɻy*`vfxCym$aC`:&U~TiK`Tx>+UD1lI T$ÈJ%l ++ܽu5G?;cy{G_ـ@y 8U7GK-5>Ak ϞkvY5BL"7? hEh"UE?u5+~]lQ]7ۮ;FeZۮ0Y'b0ɧV8ZVY(_d]h-m)FĿK8@ p@(u*hDMDI.R^w\lҬ'?)=#OKݮ|rC v=Mb8 ]yowެضx N1Qa=Lm=d_,'&qOe'"H(CeVYN\lꊎCZm{ԣ\.Q480{Au ~Mx!Pfя#@o? 06N }RJ6bȿsCC<B 2(I yysB{9?yS쉑^' 9ouWkпnj)BDV kLP ptCEОP*LϐA.]u(7-Ȭunž2Ё@(4{BV|щc7KO\і#@=WArf58çŻ ݑX6΀f cFƖ{~ ٶwv֟ ?U0`Sc*zbj6 S$¡,_uS:sF}o7z|OE0h>xE ǩ UU ºyKcRڃT9(JĎfL ܵ; m>BAM0{ɗ` Lbm f(:l$B{Q99YB pـ3I\]}s|\pxH#WOۮ֛UWʊcfJ9,ct8^贯D 䋣!(-HLȏv)D4gI*xyk&ja'`^(pٙ›KpWe/6,}w*FMP*BfᱼRz@ɆMYڬ۠ct;BsZl(ZH<;dP(/ 6gn[;10Q2PbGq5#/-@DZp/cl.q(#Fqvl1Ƹ Q&Ỉ}5Qϵt]P𣏛^%xIEԏ^\8eEɢK*xМ' n7 Iޞ9-YٍY/D> zW{g$FgWl 7]X7l7\k܏y<}*ϊZb/<8}z9ӰC*ֿO+K @F fQcC@&v!BT,|ւnAL+-@Y!]0On/,^ `Hnn˘0=Ykw+63\Y}iO_sOӎ$*5tchgjjL:k͟:cD웙 mu`>z3p  wX)(J_6y pCT[,$ b!CuvhH9@"E~<boNpknj|yAY1ÿ yR8HHgz'F'U [|r ~yw_Fŗ/ _>jBLh%*V3 hnzBtt#Fh_BoP ]~ѣ'JBA 8[J~=j2&sծyFϦgrVc 3h yv{[m6*pX'-WǮn̯7'@='C//UV{^7G_7?45ڱp?猗/"ah`wDp L;-Vr t=;_\ TPhuL+dMȾ`, b 8]CBu:BPWY4:z{QSgS ˹R :,A9=2UE0 $a\?' WR _mo15/7QPq -0(/A2[0ƘXcdBeY!#qtƊdvy~KS'3G/ߣ_W;y;] lñlgeHsIl2byk-\HcJOl/4Loo[j'|mW2Νw Rω do4p׹w?@+ƾZL,crt| L͜Iy"ذ(.|Ni0($ [^]UID`'PVD3 PIt.wNr vȮ엳~.G./[@)w]՜Eo))=mV4 րIuKK=[78(446͔P{ c][u旻qIvANqe;;>f:*X*Z9w{ GģTQ$N $'Z#HzqE $lH>RϙX,?-mW̕.8޽ Խ)/bcz)DWbL6.qqL+"L$F{ݹax 0B˶WyʤzoD&~_N ΙM5pmm$B- z7]L\ Ezf"@Gܺƥ[x u-Q|Ic_I K1;TbKe˖be͂Y(zۦ->qjw qZz820==,k#AmVd={m`Z@Ŗ{T͋.uÍ/j;h3a6w6ߎ6,}eb L~U{6J]!d Qe׊%z}g\{6e a%$]yꬉHQUC qJ"ơW岇R4cd[t^e>',3Y^}FwwX][W {4cEK4E'͊F@F5ֺvƱ'jjjf5.a?.!>Q:{[UZS* -R#x)w#Һy1ج}Q;ʑف--24"Z~++;ӧe1Bn|"cq^|E!ڒ8@Ğզf吪 @D ڬ*Q% gfWcJ1[CH"ifiTݭa|Xp 5cCvgSv1x)iGIq Rc; p'?VгƘ(O?y:M ?"k0Ǹ$wo<τ!Rm4eihMdk "GjN/"b]s8e6-u MEMN{YX4nU-8dmKKٳ6RSvRRt46k}aTs2P*"V`8:^k㾁޺t_X7%v3D0ZJϯXIH9grc/q쿆?|s⥇7_{zyx={ǝ|xbo&r"][*O'$.XBM׃??;/j~ L5)̂koC}?MeTUkXVx &ST](#*#$\$IdC<(7bp(C?fRhk w5Lۻ<|m޲%?)<ݖ~8+,|D-,!G FbQ no({swLť:V{0wd~6{=~*#2͋TRS l6k.u-U|^c=ǼүIm5vS(B%~ ʍnӒǫpJS.͛2q>'G38U"h $gƵ;y)E)EAWh@X\^uwRt{%TFxT#@ZB|s1=r+&S@ A~ԷIZg=aqY;G𥫮1zV>FLvQ+߻EZ1Npk N ߮Z7J?X7c[ ԚϜ۵r˓nj+w>c\yF~/Lְ>gnp%>vOh9}ο@ w1EauT4iJ0d1KDAn_2#_kZ[ґ'U g͉]7Kz;mf~[2S}hh;~ R(ߚ$'5Ul?˽>Gzuclq˾a۟GEX %_RnٹMb;0 =PׇL31]ܮz#caû tߗzsAJ_mQY\LjSUm6Jn໬۬%5W&.ol(},6taJwU{V-!o`/?3xb{?a'=/i'Q?vtY9htOF{O|s F H~W0H`Z`=hoǰ󄌟?_W?[r]ɦNvm7ߵw;##Z zݍQ5ohbZ+si,RT{4):cw)jliȦJ):ErXx" !B5v=.%u:v7t"PNI][ ?̀k[ P _!5h VR(40` nkG(F~=ڪS`e:a+z󋆾yot /VeN\29cKC*vW,yhu PrbeqS/sc%<5&=&)#܉xQBW0 I|g&>p3ʸXl_o3;7sx( (@~GPXHO#Com o[?Ii ұ8Nno0!U '\]㏟_j|F+ϔXBD/?޳>5 0XDPw&s1og5W͛Ըm?늝 W4TNWc{Peƚˬ%fi>Ej ysDojǾ9_;* <.0>'w5sG:&1N;pR>ZckCS'IUoPzE񂒍(#jKIu^*zm&?S6#\qvweyf\S֙)~U{#MVlluHE%2ދK aWq흑}ɮw| Hrvjnǟ&?^Ļ"9k=gM[S;,Pпt*~rƶ廅vNJB$P)PqTDEa39s15Zآu?Ov,~ڍ;]eurRZx,\u$".!U3]o E + `)J&cW=X/Do`?sK_#Y7i_UWަhmAԻ P5I=q="P-gwc9i֊G zUe^v: 2ט,w2=j29L@uc?heXǷhۗw-=w 9'[ܦ]M\sj#nqڨĔ@sYUcߠ}/.{[m1ӗgX!.N=ɫ}4k` p,="ݧsȄ<UCotB <,0x amgX ei"a݋;w~ N%"[ǧЋ=kbԆjn?$׍L|x?1a v{}S2(BV5"FP(ußlvtt->qˎppwUk_{4-U'̫I_|j`/F;3C;u;#_øm%OIg?/~iWj\NDGZV|rϻ\g:觨fyxַغKi۬u ]Tn=.8;iܼ_dKWRGηE1e=G[)0O~vbXq*6-_ƱK3~DHM!2>Z@P_M Q}WTVУW'ڨ{'2ZdR 92m˘Ȯ{\,H;Ǭ|9;瑛!4n?A'>b'tʃ=Qi0[ ŶݏB6RӆO]ciUQqHL} e.B=w4!! S!>OG3RYrz.?=cvFP/Wp6*W 6y뼀rrXH='@N]KVA^#b}Ǧ"?Πڦ?;XnWy_40Djy5ޤ5ko{'7]rSLEl_NI^x% K)b& ((-GbSc+(U`QUk# ,+0]Wv{O >I2>`=Mg6yB_yq[,sL7l2̌_w`P4r-0(>pvrߒ&L/!7_I@'pY^+ d5- zpʜ`dmmNi y4e )ak|nSoTЩ5;wgyBB«pЌyIQ?u”sac”w k{~\5ϣ~/|Z_9yU綯۶۷R~Ըݯ}HL$-q@ګ_%?VW׀wr}la 3ۭ|(;6VHN'ϿZrM4O{Mi5~kdb+) n6c,ԙ_=i$S“& )kt KTiw׃ļ\,81OZ:'ҬYAR_-G31n|7kvVLWM}@ n&ÔX!;/ Յ@ ]hD 0m,QN>![Q8r,0haL6U ^<۶?ep՟5s,}F+WXsvWznHn4Zj2(DQ']ew> $@7` .6I: zNZƗ{|ѨDb_Z(nq ":L X(l 1@Z$,BIPI;į x3RMJ2usߟp_soy;7y.9Pg{/:_xř/=sʈGQi.;voYo,p7hidO`HHs[[|i`ݓ6f=k PϸƇg}A7:Md ;:twCiO.@^љ|\9T?q~=^ÁFR\:Km5Ž o5M+/J'k9cu¹Epۮ q =k~l Z,(" ݥ{=RGWhܷ$zEbMʣѩ-=9iRcrJLrIɈ ^Q2ZSVUP2DRX"$ZuTn%b X_L:hs +%` t;#q$NƉbbq˒BJ0=̡*f-.$H4$#3v ${ $E Hv\ f~zqځLV*KM~߄c1jRn=^&#'஄7o 38;n/r "E#URIi DH@)B܀ XhB1Ԯ0xS<*ϼG񾏈N=AjpW05cfC_5i GćRUƞStjvon˵^ n hgU2~dCN?$]⢊z7/]c@I~6Y/4E/ Aʟߖ Pw;c`t鵴Li)وZHX75ӃɨHɵ7mYpit.6Q16 #aDrIf*il6pa}SRf1Aoy(nh`}9f23DuAYUSB~єJ䙳(RБRUN.rDZF|rص䥝}/1\TkO΋2um^q!r V6+.ʏIgWg;`["iH5K>ɁU~Q\s_|?G%dC%}foNHnؒ'Mϫ(* LEou3da뭸 %ˏ{W<[LJ3E$sFw?WF hGJG HЧd(I3\Z_H-'x+CL߷diMzX3a% ,9~iipQ<ɰkk_݁<3pkZ| XuaHEwA}zQޑi>JށmMCBAH;rʧi7 }B==c9a(-E ;@`.DDL(Ř eXT"6S̛  HBꎡ oG6O:sWKa (X ot~Y8p䣷.̹9|vƚZ\WkvPifn-^ZY)/wQ.B^S[g$n =\EV񠯝zBoWi3 1 ^0@`1LH; _d!1In L+IYK)(.kPfSzG,m_|{+glO.W4UѓS{v^%AAV - o[&eti~Z/=)ẫN[Ek/c TUj:r;8F!Yup ^w|h̀\'=7^G QmV]Ydžlixh,\%{›5r-ڱ]^ v}Eow:r {" .uxweCӶzP?ZI3afx 5 3F }m~J-V}<|΍wVt%6WRd9A"Ţ鋂 h?riD-€z<\ۋr3%UPLw>x}*gHp@e"JL9I8 WkɒiD!(8Nl0xn57Thcu|Kj)MUqa.q̬ە?}0Kj0%u@];˖' -Kub/|=кUoe\.SckYQTIy . X|SO>2W𺥑-craE梪hF \ P`R *@Hi3P=ͦjoggBEÅCfFөʟ=!2#y%$-+VS)/lr+-4lDQzީU KZj?{uۉn~7#O}?mԑz|$Fp5  ?,96ils_$=[Z>vFWwsbzSSۂżp#f?4lΌ;WlbX@3_]/Ch,o;,zGg3k.u&JF*EO"ŋg=zt(X:̈́͘:QL>׿OJϊs(0%H8T?\/s^t1HDj%zP!7<^߻_=,?@A.(p(,83nj`cKIh %1CC-Gea>jUkyLH hޙr>vZ||f-U]4iuR`ib1 ``̱rG?9s=ގz{_>(̸eCǥ_C_<ӝ1"e$Ɗ'hsA&ow)UMH](<:I!QwHm6x2C^ XmWI~݋wXUMuLbҁ R,G@cQ_Ne3nXٱ3=vұpͿkjߘH*!.:u@r- xӢOv9~Rzt?ҊLlԼG)BbHnX)LȷWrѢE N` 8)|z0^\Vd߁~UZ|A-rV$8\YKQ/L7dXm\% {Z?Sm*mJ|(.Gl(O)-Zaƌ̏6c8=m?<(fel{twmv|2Y֎v[!7Xi=PV0 ۱Gi/_`{0}byeV]p הm"# |Rr%\Yy%^lv:_(4EHǬ/ʍs%!m3yQ;=ǵb5WqS16=kcoq2l8%rx}: G %lJ_kߕ$3xɍas z* ysHafxh8 dž*/uհ.#/"@k2Njp]qֳ/i9kר{РkL+C/"˜lUCI |6M9]]Q=(|/'t?33EM@ܽnO^5oZYIߊ z|s ߭\t8{n}A|YQ)LJG`!Lȕ~M&_9iۙ3@~cd0@;,=wӓ>p>|s[$IJ3_˯4_mSǍ:p( ϟRt(0{%‘3jR[%yF^$-MВpMFj|8Qxj cСԱ l%{oGo]^ <jb 9hgI9.^dgv=Ԓ4ʖ\gm0dϋsEJP!"cv[xlE%qjo]6d=~2Y}\lɘc2{HܣS?I:k/CXH*<)e`>:b@Wc*{pz.dJm8țѾ/V9 ;7,91ZC"gS)n D7ϓ51aѬ(f<ЧrDMF!T[ퟗ={!LE,Q{\nwYuUM}*Ivϲs/KȿTḘʕAYoM_\ mE<D֞3ggy/2LK`XbX 9* Wnz)3 Nkܠ_˦g/XL*̮K=ÊE  QbEY%d5ej$!Q=: TլWZo%M_~WPaXffWf}MDSv xZbJJY$޹xAihk'vt@hKd}7GD8F(aD ]̮_iZf!B7'yNLȥ>, Μw|;S[~j? ~L^hϯ%Xٕ9lP&*$z-x+Bv0)tm_}p9e}RTw DPa A@ |lшu i$@Ԅv }c#`yl h2XJz} ~'Mc9/(0+0aK%,(;wb2ܶoC}0 Uw'Or:ˈoQԱU] 7|' Na3fPwI qd<3.{!MnMH[ً{JMA0渌ØPSQU1~JEpn Z`puq8FBSS%e=N;C˜T+X4O a4@ ( fǹgDo8/O2 ?Np۬eP<J &匨KF:bes$OT iSTr4(%o"*TӥG@_RT,mUU*N-' W8XAex=a(!:Db[Qdc/mPkUF +*(1:9*,ed8Pos7J{bñ2e/x1Ըxh5@,[ƒQូ'sa 3̎!1XߔNm_>g޾hHLzdsIV,p "35s&}audb>hֹ2{TT UByycG{sHWeOgU9@l"rV D ً)uy\‚YVqKz y &.fҥ 5"|cpc^(Yƌ' TI2^rֿ2c8VJ~V 7y,b#CHbl…?KJTG~c$1y,"".Ǔ8oL5`d>B 1#~FnoĆ'c̊c4kqJbWXFc"V0R%o50aWP(Z4M/fDM8c>lłzKH`]}4p3a4?^HB.5lPT/$s&#8g˜q"G3"Hw#K6OF01goCȮ8KðM~RtRⵠs뾉ż m;B6֍v+ߒu[f="Q\t^Z}eݙ  ,)B ̺bX".5Q2`{9VKiP;!xF%—j>+z$Cn!Ē cTF]L꿚s){,z 8xec"ؚd55sF[|Egx@swAŠ!cKdSf4Ol9`G*,\'*,\@T-8z\ST`c!iÒj<Nm - ϚU! ,"B%d_P$`2K@=~)GK&Lx'- iIHEi+,< =`HֳٔNl0G: &Ϙ48?ǵ9ʔ (zkTjmܞ+WOϹt-Ԍ̕5RVIt d f*"~:hH)Dޢ( JP.÷)m)3̌'Q,H#DVYe*)S, ^6ie)+l"ZT2`L eJXmZՐ9< vd"6Z*Fh[fJ*:vJ X)٣7fQ21/Zx [HFWZ a FèaU +%82G* x L4BD Q=rF @afK'%̀pP \(Kll^`H%0Dձ_B1I'YGe8^S#p."yյ꜖RQp/^JY,S=TY$F}F՜j d̉=e1âRͷқͧƤ-hJoHP2GR"\DZ{\,@I!PeF zjbԐ1!?*[%v䥒R")#t!ET [%vU\hjC$1z 2^S2/X 4xm{8weW qxM٣%ZzȡZ,^VD! 6@ˊȜ#fX{^c,BDE dg*,!qKFȡ9X!ga{nB|Mc2罽p?l_kDmܒ%1*b ĈJ EE(pE )podOi;f:X:c’h D$~B\$^C].Y,\HLtQa+g RPpCr՘>ѫ,VDAyxe͒PDC(K?%: L|27N_vJLhPdıf߼s7:ddѶش8wmL<]qғ&uԝ'C!_X/d Y +SH @-ZL]6: #Ƅ &mhGԮDFts*r+G0<(|pf-e (73sc #FDD W*eD[(Z ;rH HVHBRD=CJ/S~6\8s7}7De"GZf2{ąFkTz5# ֿL:t>F$#hB"HDH"@D3FPi Tՙ?&I?Jkqsyh.`l?@(n}@Jcj61;;>Ѐ x.-%2s8p`Xʈ b lh}3UZZ1Q%@" \uClTdȞ^Y8YoylƞHhеQ,1L\,ą6{"wvݼ _?.X;_x5~;(3@dEI_ßN#Amx#fķ/,] nKQ 6s)!5%^ 1Ft,Vr20+K\GZgLWīp.@c aZ1+2 A,^b%GK˴9PpM,cQ ;*t feg{?g"Eu v O^eEK,6]Q×ς;cOSK&\X-Jc Sax-!\/6D:@P!]UpOojN.Z [xl3A85^iF#zqڤJL #` lVI.Iq&l$\29.M ci*"]bD'Gl 3 p^GS$}{\$ `iK<'q(Iɾ4FS&m_9?lsi06œ%A/Yj 1P %BRA{W-X0Q<<\ _:&\Rax!XInUo R6ĈrcY+^ dୀ+S9PHsԔ{38F""x% hY$O'q*LItiHQXcx.CNNrٜ9RłŦ&*s|N$iu^tjtiY-,&y-X^qY6 |Ippo͛`}D!oW0̧e?iSzaG-rh %eT 5RC?2$~3At&vi$V*ca`^ ˄QRuY%D^ ?$QWÎ2Xem |/ 1G J7FĘj@3@l # 2桝JMlmZ|zer$\;}l>7ܔ\SP¡Hb?62%HP0q(MTMU$xetj4\X'6T ] GE!b sp3L@cTg;_- 7M$*tj;nl~ʔHֵH{Ef.F|Z8G CѸrqUx26; |{wM/䗗Ysbv]/9ϞXoZz-^ R Yv/#$ Tj;HWҩx PYzAL"ňZܡ$&dEq'1ѫOi/`qB@)kj/rbD(N#iɿ SwJ*Ǝ$tG & E%oBX0oeqRI1M$WzO-'64#?H\ʔ1x λRa{-b㥚'N!&肂)ﰃ~Bwamef}W `+cH4ߡڡ~d*0H]NEƀ0?S~#Jhl."W'Š(poj l*:MҟN{OY[0v́,k%4s٢1"*1; "F,DkQ.K[W Nÿ=2ho0 A`<1i܆*@PaeD #XiX&\%]t=s4vRD"YfK8v m q;SFtY 9yUXw齕ˎy[~BLi}ջ9)" ?mn{wnsگ[;>ETd7htHñsx+4w#6!]Ci#2̅Fqh{7H7:Ͻ5sbX0ϝ)•KJX=|-#+"I'G'G繒 Oe}l6%@2Kx S<LIl})bo="ͻcw; $eq#!Co`j]|H()/QpGP5# =`h# ,ts$Qw 4"pw rL %>PGlBaD1"+D7`U Yb@ =4NDF0|2 Iqv A>0NP,Ȗ 0£gxf#8O X?\kBm"1`vM)$ ~2kو#>B繒 ݦB$y{_}^#;LR}V)cOI.1<ϑTw^؉ Q('z{:hjZkx+!D2r-F,'+6wxvYh+D|,ù=%;/Q-> ^M#PmO4C IS+]Buxz^4;PA%U|6+RU:Iy|v㩵n5_!ZӍۋvdi%zkp퐌, i>ϕbV!>}wN*:6i=]6?9 ,ݸK/COj`a9!AB>_+fX<;p;;ꔐ,Q@ %o @<^Ёy^v<{evaIУ Lе0JEDW ! /G繒.۷%"-y49Rڐ%n:!5]Cs> mσ}<[XvIڻ `;= y'TZy9vYzR f58hyЭXR!]cnɠ݉]K;> GlZMk(1^N'0#V! pqZ Q%<~=XpY+2DՐg76f{&*+ $MC~̳ ]nGP 8>q~/v6\ETs ^uQշL 8Knl QkVs-*Xmff~/Jį=gu$C~#(~ Q3gPʕ>hfu=$pS <<2F"ZJmrޞeIo7S{-cډ⃮J'eŒ ?ۍ6 [篶.ƪx`B7N }KW < mm̼D,XIfu[˲ÅnxT[l1S\*kJY]/m @Gm@"^>p/fBȸuZ\Qop9zWYQXn Zj5j#DeI/wXC1lEnZQ Vj;)*hVu, sN'\-ڳ8:) H2n ppp31ׅ`Jx/ 4,g <z'# \ ώ H\'UMG٨[8}\%f6( HM"ƏgGu+ ]]P K <߉I45`pgsbZŸվoX/urUpdx|Q#-Hz1Dp  s o]kIq"}PLS((xZЬbHڨxm~Xߙ85?)L${@ꀲEPfb;6;ȼ\??6o.H` EW]7{tE-]7/ro;.ݾ芤芤Ϻj#myd{2Zxp+m勮HjsEW$\ >-Y%5EW/0o}iI΢؜`Ñ/:' n\*3|8ػ$U]tVRdq_芤EW$ )?{n[=oCgEW$&pu^REv1/"m]wJ/7m/`U>eRi݁@3DEPu2l{S5VÝZ\m>vNOk]P)q+|ۓɅfl'5JXZ:[VHyjjTl[k6wlU}:)kЩf 5b@oS4KN#fjcmO=vRV'[h>s=*+w|¡'-MrN0,oN]߬|T崫+gf_hԈM]]m̶4;봒I4IB-X5:/)$5yRHSVʡhKSmx:+UfɑC1fv+'-Lκ^'Cl7KF6ڽY'ǭovםŭfQD `y|g;Q&Gg!76MtMoELnY?뤤M{MoЏ^8?!]q0W>0>'Xn$7Yg1[]mOxMN#jc7Xɶ9^+E@jVX-l;zVnbid4Q!ཁxu  A ,@UaJ?p?}ʧ~]@6ݫ;`:`] oԶUczA@p8ηG]IK HjݵaO-mQP@E>P2GE}>g&ڮj,@ By8kQ!CuwIOR+V3m,5k'vOY= pOu*Q }ÆvMt3 q~0ůnuHz ?}ؾ~]@ҽY0KA7d߰%tm `WL>ѯ H)u݆G!u-g(CG)yQԵ CzAFWV G?|WHCt'DL3z͵q!um;qmHzvW}y/ ܡCʭ@vq>t77uNwn:K'n*ֶ?]d8]Evg >u[w6 Hj]qӓ4 T hꅧ߆@42.Mnn̾eJҶ;wXF,ᔇѶ8xW[^jԍtlۆZ6!vIyaσІHqTћ zk#:E-7 $kvXG!I8}q!Cڏni-rxktO,rfpѶt NEuu8pCLmr쐿?io@?}=wNJG6:Tas.{ih ÎcѶ_+$NǛƩj]T)Ğ{BiAs̋5~1&}%Bir04=X4$?ӽǯ HGb?u >j|òO]XaƱ&y7~C~_۝>yy=6Deɾ]X2ÎGwy7WCt}\-|`b)vJ 0Ѹ=|Ww:Cj=M]F_>qs>EiMq)a'Pռl))¿ -$㲢0Œ)4aqA4dP#Nmy(> -̴U]Z|/8PH"vMj%"y횆fx;Tjhհ!`Y腀b.rN"ּl;n_]q:6oOv}ZH:>ZFVΌf۳:]7Zv;,$M%yJGBe"-MrcqZ#ûku#{h[e4.IiMo<)ܑUj!Awic);}q[5nIc9R(jՅvO(( -Q7uHZS,)lhb!dY]o*9v#%O2fB/`M;eSf+k/of]īkl֏; 2,.'NYؘ1n{ju-VhSyes=dEM|uqKvwkk%G,C}26DmVVEY[7I%(KäYH,+Ԑhwt7}>ak7n"P)Bŧ!EQ$Ly*oAQZuZE"ɐbe"ȨSe5ޤyx^_~Xqc؎wm'UɠE @^(AW*?' ӅJaDi}SW5]lNsh#u,Ag$ $Ū4ezf˺i-E'K3Xp<)ϓ|\ͻ5lΡ\;퐧PeB(aVV`˃,ឥ-eKhHZfX*K0i'n>[A1Oo+EAu Q|4I>ĮU!];pQYK1 4IlU7-'Ef4gur7ݦ΋f"ZUdJ["v_<b#KHz~2RL]ʖ#Ţ$_ĥBzPxt.;H68(){ ljͪRt[TcuQuEZL"ԑ^"n]5_unuvӟDcN_jҴ%|i[J3RiK m*-ҲeJtȑ"$ҝ9]ݼ=춲=> jLYۂ`}OߒZR0l@Y74ų] ?xy&;1 rSJA3QZЂ*/:դt~"`R$KMSwǬ~V:n C<1Q9_E6'n2ߏtz`y19y^cK4q4m"lR+b}6Cܯ񎫷\|n+ԑr+5NREIc7!]Qj1u2ؔ,#4 )DcJ4'eƨkPtUƖT'ZNh͛-ozb]oևq3d8?V~N JIMl'Fe6;Ng2K,*2hlܦ)?C@BHDH&`$!eP&l'ZrLjQM1Mǎ41eS&׼QmZNg maz MLm4l7疽ƟkMϴrXhh;iф&hNBh::fI4)5~w#fX2& +txeudX2G/kTIhڲl'87>7~j 榾"5":SnX#"e0e5EJ_|>CÛq|5'W{3]wK4LcDdݲؾf#MmAc ڜWIGlR41 XUDYPoLTf@lr#Zgǘ2ϩjVwk:c޼hPS$ eT NȬ`l"QdVm ƞln~͸5ccN6+MElMe1'.ie%;lع0AtuRfoD^ظ6~ggC 9 HHGbّ]K)*؆5D3f+fzRQe6KtsURmKgE>sϸldgp`YF0G2M3DuM?0_  0\qY$3<Щ| yhvRcIʋ m9m'29Sc#e͑{We!fxIW%LL:E&e:ɕQfI< 3MlLͩG $XldM,tq i ePY8MtLSS9ejrVT6!Oucg,6'ܜSkXcMɶl1 @xbI85B#α)5ya8pSCs)ȯ| #=&*&y @,)HI4!g46瘷n6nIok,75#YǧZtjn><1}_Hsk'+CHXpўWyPo^?ڕm)xܚc5'6rFMBHhR iIGFPP4e#aA{OxӶ?s7cB(+qԺ%Q"Nxv\&s dT{Qh(&a[%*p0,d!iLq^#ujk0FtI?LV"Zgm:sɦRr2-Ư<|6B8x /5=s-y9  H@|l"f@qjzxΎ%=qW \!p $x HwO7aEs&zI FKJZ璵dČegփ,)[…2k L i~IE/Ptȥm?m&^@ pe` gRO9 \)L; @d5c ?4O+b Y/0(?rF8P&vSJ};mjFFb|)3LO R@l&{c[Mve2B%KNOz eDڀaobN9X)F4*S~[1DUUP!\6"xԃR@d:ԥ=g< )()؄k b3Nf#qi_m^'*jt%Xq`, `@ Ԁ7rv,B/9Zd\*2ciSTEJMrtB9 pCr*: zʼng2K.|Fp 5"~ہcmMdSEߚ\Wyݞ^܅!ːO>s `dRNWI䒁jPi`M ~{NJk܅.QkԒ SL zp'>?ncwvN3%Z~(l{q_^Ѵ28(F!`J%Ao~}M;d+BHTR%FY!0ЭxP2 r`v=Q Bd <(B#eL&ȅp[%1[1BHErjt5:Ka2 *ȞL5lC.ߤ̉x7xݍWnDTe; ea V_RҸ|sg"2Rqf{P=9oqiW1>UnQjI2Xp0%7#c0% b ({ e@ H`70eAVDM6Gysz."Yf_uN=E=X 2pf|Ӓ||UɒyVk~j'mJ+1&l!Q+bGEIhbd G"Zؘbai&{|[H%'Rn!(P&`Y0$q,%{;g Xhl$. >0E[|Z(ݫ-/!R Jd&:<,QO׆N&m:/!T@yˉE1#N3/,_v\;ഉw}SC ѱsj.kEnzQ8#bƍr69ßq[]g,dΥPo<ࠏXθ@~=Oq,v ;__t:{%%H"&([; 2td0yZ O $5ICm2%YG=N,WߚƓñ‡Wbb2|fcYҗ#"DXf , T1r=xÞuoxήg%|am]/>H' $B-5"[~ Y瞒xs ԝ<5"LINrgµ9@ T0pˁkP __N!w"Ox/?q|3N؍gh!`7skp4`PD0`qPYe x" %WG!ًVZY8Rf܅tY;5 h  %)x&7/AF[)9C!`u>Aƒ@N|E(b"` '3̌ d˗)-Pf14A\I'"SvVIҗPyfORZ|lD0~t06ZP?X NY酳$`e>Qf2и-bm@ /șy\ 3(*@1QQ]f(EQw/MLU(3J2jOw Φ֣T3}+'^ Ύ=  |T Fä1ֳ`TFXOaUWepF ;Z3*GF!@,sl #1\ !Rba&{~Δ.1 瑆Tf ^I!1MGpH(?/lqfQΩwzD?:Ƭ+~TmƼ۷\MM]1g鬢W8;.5j14륤Y'taGP6@N?˂顤Nٜ 5:IP@>bg N-Q_r׍~[|p6I̭R";SIډmd*?OMr!Se柅 i(wląuǕK=䥙OIKz"zR][fIϹtY%ᜠXxIg*ɨ%,:v9nPD)(=[ē)*/RnM - x\T$dȠ;N@p( #w¬`g9( 0ן[ @4D"9cbNcj(fU̳ !P ̀@duo4&39d= 7A~HkH쳕˒*@Dp#gE?Aƹ$j0{3D$K#Kxb--  rݕ/De gu7,IJn/t:W`%[#Mef^Yp@rxcVvn:<@N`xc!.Jz"b,@he%`\2M˗0_ojU=@LW%+-GƗЧ;}7\O[ T?3 A/Z\1B!&>1h Th9JfyXdUD|5Ntߙ,<`ɓ&^Hf و $ী?@P'`2v[wNѐL [H™-r7}Dɠ<Q*0eTA1#h5pK3}DptW_IA‹U}7`Q$c> XZlO܂Ԁ6HI JnF"l<8ڪ!o~O1|2^PyJO8E[Ϊ팪{([ږjTa Q+JJ(H|rb\*BS"KI<3n*a[&+G |^y] m?M@Hu<)Dk6;"*UɈuG̔Q.+=2~3flrU #0uŪEn:eG4}".m~ݧ z!,L3$½~}%fDF*tb XhD1JeH| PP@* JnX`{a%+s{=M0MeMMJl=(d5@ՀvTzڪf_ҫ2jȇv&SXA˖- P&AofP^ip.3ӽrv[} h+zB>8° )Xg^a=QB@SFxC|o0w63gUIm ^caObݥG <~k+@4Tz( D(d}ﺱ|v/Yh_PgF\9H"SÀDDjHNP݌dG;{ʐdnTqq2Oe Y&]c&MqV4＀͸'A^8cl—nuA4SK af6f&BHu ^}S,>jb+sQ'tg@aDZl!FXڝ+"xbidDB4ʽS}D6qν?k*S`EBr! {s@ D`gv ; x%{Hٙq5<@V oRM?|EVlDԇcCQ6"RI`H!!\G̒Ks(iu q>m!F!eHP%HI-%PJDaD2XZ Lq#1ƻ7H}?g҇s\|D>i떮Hc!={ٗ-%DdYmI$!6ؔm`MٔH{yV6CS_Ou!#'叺SA407_uJ*xn bg#_}eE(9w9y2s`Ҿ=lcX'nWԅ֡쵧eϻ쵇]pqG&EfYiMfԔSM&ւD`ԁ"QeNDUͻ)BIoqY^Jܓ;JrFC# 1թ(MKςE^L\c{%?`DfL +Le9+B9~:~ xv]SeǥW̭fyGT6ϔ 3)Qvi{nUbh3FzhX -lЬg^pH_Nn՟t$xkwRJ˜%* P^t_izvfWhlJM@{ LКFB>^ D>Ŏ l^%ř ePk r4F,kHX[d wB ykSΌU-`2T*SptoL1F)xTxw4xBUhRk] TgE/"IYuY!Fb)hS=Qɘ@qJf;Yf5tȤZZf^,"`q;7Y3~Ԕ5L{?TװptBdo};- ڧp$R6l XC1G1 MlRD,ȀyfZc5F|&LaDUjp4ʽd;k# 06Y9鴏"b?XTlұE{*Sm)"Tf,O\*ǡ~jY1 ƬΥ/W9uL$-3∧XawHk>h{JXcO-oi_ґ;xpTqT%\ aq[z#.M␲H8 n0BrN+zbgUv. kej<& V1|܇y@zp ׬;_)\K)W':^oa- H7mLtDQ͡\;CGʳJ;}n[.$T-Q+P-8rfݔD[lX{$ժ a^f<G1X*]{Oh9aqCvu%Iօt7WByO^XG]{Xsn]3uU\$;,BB~>ۆ99F!jNࢾS6zSי*AF,c, \h hʇmWbO]%Tٳܙ?B!l@  `9{&?HDQi΅cJ0Yk[/ꭹ]o0#X 2!OKF IL>1׿9gf0co=D+%D tswvxLSKS"03iO*[Tzc!X4O΅:NdE{~]{C%@"|@/Bu/}ڬ}K=8,΀t_<-UIzz8 x*JÔQ-CUZ(e[ZX'sL'd]eZ(0xNqy]&"#o*q85c3Jy K  W ~R%V0J4nBh !oT1jExuMߢexH]q&=(iJ"턒 ÛOHϖE.ĴM"wsi頥<>g NޱpaIgHkm3gF]7k]c$T(D^WVq }<xKCw{YRmo2mm:dODv׈p%"qvrw^hB+k .ĺ&1Q TJզxKPbE>f!0Tm3L3 :z0`ӳUCeO l(?\kjdaejO H4' :A82_wSs>{e`3[>vDJApUg.+MF8B{kQ"A"Xl%Le¯='*ui_Ε|՟NuF>5M91E&o%]mO ƶ1jMZ#2w,1!%\tF9Hʊ g˱㰥B4P7b70!y6tu]!Diwr?cB&Z)lE8)^V~JV` xWl҇[|2Ji_E:\Bc7߰b7_c ^v#^7ߩky}!6u6C56Y#}܈3Wk !MB]Ԍ$B*XUs4%eحs#ssۓlcxBi\ r 9jbPJ($L5eGVVN&Z7h*%&\lmWm 1D+vV?QqPjP ( p H{E}F}Ѥ=(|:$*+)̞u+-WAtXY.!]2vMܧ2ӟEi{ݯ&/{ƥ׈rϰ4;/ivxY?_o@$}) qM˱c#Ɋ),0&! L8OBrh?WmoXb q3'}V˗=o7̟Py៫8oxVc} {})LluiFMZ onoEs.k?Aeі3^;$SWOzh:T<=۷ {ߞ33^Y;\&@6 Za'y5VkrE&?v@$bonuoNnK_#}V$1R rLUT|in}ȏ ^t!/nh_8搗<_T1cfEs52dd`V̈>8?5Z懃zN*}m%-#D8ñYv& #+2٘D3H4e(*'8,( ^/op RM|V`C`9݆7Kʻa<|.@?80"`;#lK-61XsijD3v=:uF*->[& dCt9qwCh/!J21Fz ͞7}s?gN"$IfyRcei߳=̱KL̄VE>g\OUY@WBHԜY76 9$č)[5eٝK{-r`̣㵏yCנtdz}th+ߪ,R* \>֥h>DɁc28c a=$a3Lxuj=$5&w#⬥CZ֜(2(y2+KP?bf \ )i 6y/Fjܓ%k?+9luZgྟ ^dY2sة˶8J3aCt4Xk#tm! E?Uֺ喲g~< ~m 6% C91c՟L$Z*aeZ`xDKKe~>7GW@_؝ζu_]?D9w<+aĊ? ks [3 ~%\aƒ6B\ʫV܀l8I8ς5默03ĵP_0X`p\;b8Y-4g#.LVzw - >-I,oh DwA$ ys]Lݻ(z۾vNaSşFIɄ`XF;2TWd ^`r`JNgf'7 AQcsPaVU!6qӃUK8;AmֈN]WяC_*7IX:Qe/J+ձhJ5%%*#Put:_24lzT֡`f[htN  )SBiD4!h=hhI?xP>XrM?t?7j۳zymvyI>6p؁dKx  @YUZI@zkmOۨz^C|1GЃEIu'`Se;{D=6Xx%CAQߊp!\z^G#U>bK'6'h}W=8%3W*s޽(CUQ(Juk R#Te EV;&W̦;y@r_Z_<}9Gmv{MvN}uLE"R Iuw `*洌1%w#+=t{ɞ7wk~87pZQ8Ȳ!=J\9xe=)w5{:p0l&[3O|O8볊5qw`}q۬[dn|4hx̔`LfKW`n]?c"P}az Ϯoϵ=?mP!H,*Ӑ>uR)=qw\ϗސOЍ𜑃~fY,PYwXdn$=S>Αniv`Ѐ sՕ{7$G`5 (Ec eqbP*mP'hxU#av>Z㜳? -P5=wwdC(/8NrpPo'm?1d>۳P(?8YF։5e㛞W˞IøuZlX3c9ZƉ0(Wbfɳ}I]AS+Ayr*mZ{^rvE44N =!L̋OW&Pfϙ1p>!L*86OGC"%+MU&Aυ{mno YIʂau1ۄv8{T<*B+ʝb3:vZ|O:!Ib9dE}.y:j`+%EJ, a0M(o;RZj +:CHPNB#I9Z_'XJTn SsRc,dc _$ubB{n4c7W{Ab86ƨgnv`Nc3?5U%r$Y Njf{9B`CBdH(gie "%hmK4tŨ!WY2qUQ?"Q.weo]JO_ F|ƣo)e5.b)Ŏi} s -6Lk|qV}]N3䛩> q&5Z|:#_~ Zd%T8&?ִt?]ΟzR(CPf騟L&:j=lÎ;ܾ")qކ[2E*gzeLv2]~f["3)4ys׆a+8 XFׂ+s(|)؏V,8b +e*A"`2EJɿHL:WN󤈲]sԚtixI*فD&rG(J(d 8zO{0Ra'v"?? >xx8#p%+!cIlɝ)"eDt#G5 ځ9 %H2)~]l ~>ws^ZǽTWeu)c?!mŃaʟ߫-*B|GɈ7>օ?mVּj{@jStyƿs>v쉤s7Z+\' ACe}9 R#fXlVf eg&lLٍ?\_vyΞCgKܶ(L'$3TnU=n\!Zo;T/90(B 3FmwwL|7y[rO✭ug>REPﲦ q-eD X5n= yL؏6#9Tc>|^HvL͌]HULXw X j2doEܣ^ ..ՀIH #40&9r)1 ;)p*vm3:[Z'ޖ=#xceBȫH*+D*1|هjU!\(@g0XfD{hn I#v~t ܜ_V hTⴑQa JN 5GH4ҭB9htSxe?. EÜ~sx0c'od$@,TLi%2*Ǭc=FɄ( ).-`%ҩ]7]+GR(UX5rxٔfta!&2yfjj'%^dZLo(iXЊG.>*M(݋0 `xhyD&̳֑c̀n6t y7}HzO1g"@:%WbN b明d_"fcC">o@ɱo/_+7i,`Lrvp֩K5￧I9jN[yN :\<^5m+%/t&,IH`apLAPG1D8=;UE xU\2ثm~⽸_񹭀&/* TK1? -  B*e~k3٫]*:_u=f'3I ´ LE:gd{ B6 d5c4̾vdNHjhY/+<]!U֯_[7q˒cG֭񟕵zqTdHOu~<]Y'Rj+$k<r~r@1`Eh4Ѿh6fg5QBs(1 JFWIM6s͛45*vY"Eit"#i{V+xtUJwHï_Ow)۶E3L#H Iursq|a]Ue K]>{S0SNEbV-8iU UԼT%XkE<85ӕ 00Py=5`T/ZA\H aEʆ7q4Y}{@ى;ЃEP$,'eْO'OH?,*e5m_0ɌDG/X} @f^%S8!l%(g.s7:EAقV%n`DKQ#)|Qq%3DP$!pʒӖfdZkIzZ.*^) `%bOzP$! U0 QL_$r cȔm`X"aeRb00Q fFE6apm`bw2ăjbgrAÈ ɬPw vJ!`-h)g0C{ Aţ cžjZd`e U$aen^z 'WCOq *#=y±[F;܌7n{$*b ;f{L͒p4;iIQe*u2=i|V&;ަK?U , g9 5H~W1`0d:,;pŐ 5u^7=񨤊@rÌR[qS UCQ YyudԚ! J)JP>slpʙi SƕAŤeOl{-B?Ul#ɖ9ְĩBH^#8A9M5WTRm=@er/EKFԙ e 3BEWU0^q ?ÀA̵s^\VTSyƟ=?qgj_21L5QbbT]> 2A!`\~C An{P1\#i~͒ұGw KEǼJ2C!!` @_P(9'4$#޸byObOm0`l }4 jQz[>/E8><l!%ey-M?Se5anvH0$W2x f>P5H|}s_A;u`OJ;lHȾ̷aXNoո?s".];AJ @;q$Ԕjv(Uъ7p1@hM%Eu(VIQEs%ܖbG#iK+\sڍ2MAIRe280d/J|Ȼ[:oẅ5+&JRXM{:ni,G`FTD M\ja/vj~Muf渙ہ$Z@ٚ;s,]96U4{uF;_c?!f88CNJGՖ_mrYg\dt yi)48YF \ vj"m>@$p#bFIqՇ~"DxJ) (^=i_C 1#aK(+鉫D$rȚ Ƞ_򚣏> s6cZf"ߌJ??׋Pn|U^lbf@qf*7.pYcT%  (C*{gC ;Z50gu~ ,́w䕔 !@P[LCIҗ]Y>*^I%EqMMSY;lwe5ocaeg$_Dy󬻞r٫auyMR,}_IyC'` l/+1ěN5WMa+){ oCnI'^N Sl0s !!F G٦ù_ $9^RPgݩ9ϡ-E4g%t;{mKs$ ^W+V'Y ,^3>o݅.ӭ&N˨{.\m8tnVe* b'Lh'׾[z[cN<{ε?*kM Zx!QufM:Rv󙟡VF+ ")hWM*ҀCa]Ӑi3ܟ}~(.zSVc:[ݞl5ш1/A`% ?}Ox 3>)UVAԷ mY, u|?{ūOZtI+$BPܨE2kSB#v,.XOW0""t:bT5( <عջyvnO?ge䬅T Ҵ- )@0 N:g.P,R&T0ݹNxpu˜)P0.I(gt#}r%wcdwοFɜ=+U~_JqOG|$@ \@#`.pau06) i 3 ܼ݅ש S$Ix/nTEyP6"rBf;yY 7%_`m3?N*хdǼzMHYHE48^p̊YcEQȚ):xL|wMsgmR_)sjSu/8ߗ^dF ٫_0Z؃[P0 xcP  '4@Oy@l>yW}&F¾ЄX57[Lّ[Rao\:=+{,YW;!E-Xۧ Vc5o*m^d3+_'xHD1ya`qfB>:_̀L-#D*{܍wI[EٱLL-@pBĂŀXz`EL UR@TcǠ;X LNBB{^j6k1!/n+7S'w Q̬Ur dY3.p@2ӹks7gm!Ջ.Y2?;ʙC0f=DT\ {9/ Tɷ*A"84nMl_ުM KZF gĦ)C*!>F}8,AgWR ,Lj @ o|4l7.Y8H=_9 ?r 8"#ȃM$P 63m*BaKi L~]/ k<C/Aגř- aʰWI"\Lsv4æB*JфDebK2Ԅُ@@Alɒ&D%ѷ=jxD:V&Vk?ȑbHkؑ1ݔht ;eS@EO/`\b`U`5.ugXBj?X}-0dըRkcgD/LNܚ薻_zH UCzL \<CUPAVA>&è iAu $:tNFfsE*YG'ⰲ4hQhTGڼJ`y6NJ\#\⯟|A ?DaÑIqߕkOBD@ D`w@%Zd1LvhYx*m% \ M!ﴣW% n$:ׁa<hDO"rXjLkĈFV'7[:r9>N~NXϫŽ @d"Vk7WnWI%uPx DE6E6`WS4l* \G[BMȆM9}NYKQJlXDA=EPE8#| 8E} ?d(^!TrTcdD5}BB0B4G޾"(?,Kohf`00GUDpBDUA8t:\"-Sc3d`7y[rD@ i*p P0[qD+>Kˊw^2_T| Kꚧ939SHb 8_o}x{*3J|+zQusl(D=sǭ=*GDjjRUAIDu3!_IOg ǙR.0o灿׵uMDH-H-ـ1ZqFd(_H*.V;E+sSѦ'W@{t<{¹.Kn`1a<%T0FEA@ 0Gi32y,1y)AӠNdW6uW0CU3MC&,rD".j 53>FRJfdYE4 m73;@*qa@7cdG QMHtܮGnؔYd<̲7BpVHU3qp1EfXdD*  .^IRF"d,!`Kx`<RЍ& a΂PEˊYEgO&ۮ^1j/|c@|89*@+Ǡ! xћÙN9S Hbh Kjם%/OM::Q&6ɖILKԧaLFI 3H̝IJ3VEnVڠ0\`/%e-N,cTB) i{4>o` /4=EB ,,lR.(hRd - ;~,'-i5rMJ/ 5՛()4 ☢ <= $fHaU=֨ 􏌺??9Q%@Kjp\b@HA@0%|Cqda ̌%s "" 38HA@NY =Ge攑=lYeSMė X]LcљjC-',n$}ξ9=e3 +vec,f:+#,G>_FzӀp8V85_xa|~{!4#ښb3ȉ(+zhI2w"A Z4QifpV_Z"Ta4`Ϣ2 daDE zT#UǥUPU7HRpdw9_@FR fS)|[!ԠA/jڵe?&~ޯ #sW/yDɅ="Vp< f2Eqͩx!C,&P/+cN aF@AD9U0g7a zz9Lĸ$r"Dd=FE #]A<;=e,,q1ec2&l7u9 G& YRn` #"B$DBD&04jnԡH? `eQ^[Ј #߳occ:F2rPq|(aӤDNC_P*dˉU0330 d~QpLLQs#6ޫli|~rX6^yN]/]66KBTWpph-QvW=;;a.!Qy;uw./]~RL ] Gi쑽p5e[e^`'Kra9*=` ;:z7H܀"pfRJǬ_OSLΣG ⥮e;7= EL+MfW>FcN= !N\lJ_Z-4&1?p:߃_~'cOi%@F[ ;h1"RB@ %_APr`mUrAKA%XKJ8ʙP1)Q"(jCr觉7W0̧K-ilspdkzaWgZؠ@*Y<j$DbK/5|tQWҎ=S~VvHf3zH?%x=%Ý㗶g Y"-x k!OۡMC0;)8jAraN?<_=9$~#jvt_f͎Q0 TwybݕCԲvR[KsPkjЋK@#&[93)}{"*WprԚ4[X`>LN^Y>upiT!ҹ 5{nyb`aE+$i榿UsW6Ѷd3MI<ٽ22ͥp( Z\/ K t+YdB;~ WRGv1$QV]<{ԅWDǝ"h`dJ tSIqq{t*.-FJ -k ^EIJbkTQp6נ~0i* wCMo\xƏm9xKZ^M#RL  %DB(hDhO饇TĀ0wEȚeDrأϮ~”of&Q$S%p%ў. q6*+f%s IJHP\S]r&+A U(,`s=kjp *`i hQLہI+p*b|F76jÀ? kee_cS6QS%KW,\֜Hšc}/@G|cE Ss ) %^ߦyYqc.sr1 &.m1sH~B@&Qf1,H$ /y4&h1Tx4Ԟz^:LR$DuM:#ň q ?NP$r$0-KCG {܍jDWПN^$$.ۚνPDUĈP P%rb_aCpq^154֔12#:"qS0f59dI{HWꥡ0Uy`,'ZÞ6<1"Tր(ʙJaL8)B*S/aȟy.\Ox,<^=šA"t'vRĤx |꾏U796AKy.B,|؀郫 ߟmQTBx0jJ6!o$D.Ovyy.>cؑ2 |`;h`sBza[JNĴivKA?zO.F=t?߉OHϸw0MB[nִClR%F GO2x-Z{'I[v[~y5lVcvwM]Jd?P/ )t3xKR)^(z#9_ O=CϤAk|\τʀO#iXPoZ۫jKd\9+̨6f88t織7W="p =b:HidK>~/۵܈ rwW'#1xR px; kiKTщD\yh #<`繒:-Bd~zzUثLv-@= -W>0Prt^1J;p+욪nj+D(1{` ҍzL_tu箥ITu'V<|  APaT%<'3$lQv[1x^KP'.@$y2}(F{v bĺD9ȣ%ОIxȇ|%֞*nL*-rbƮ:t;^y 2WPqsb/*DH) x-IG%=q\ڈDtsvw`KjRGk~&5-d#"/!Y;\Ig."lA; _^_8 tC2#[繒BuǏGxS[ʉ`872mQ˹n>g_{+i㻟ꆋX!Jdz"E܉]]Ҫ>TxN#ڝKND߹xO*<@=|ó=Rx#.q I'繒~WUU&Nk;AJ#,V?D&4JNAb6`n HZh㉯Y!(qo7nlMgGu+ "Rk8H ՖS8H3jK-S0p>oOU!`^Nڲu8q]K05bjQSVS6_5- iKn vtS}Qm</g.ƼETRm6Rp۳D|Y@ ߾xԚ-UqPKVMѲ["3f?F=ۊ?sώtc"K ru"vT}ڎberVo?xTyC̣QOuuUıY|. H:*vAc=noY=3b.V+Ieh=+dH{o7 X-NrU0ӅPQnC+!XDfQ(l8 1W`skQ UƂ ge-ZQИEʪ~c!Յ, ɸr?}Wbm̬Xdt`VHY@o?k <߉VHR̼D,hVxy$epNGu """x3Wm{:+'YT%*bf>UQ#X"G)@ru{\'Ųj:'{~ݼ ݸbԥ^3%R+ڍ6\Y!) :mބEẆtIq+jg_tE>xM?$. >Y%=|kjdc":YW&Ku^>'py;  &fݳNE'%5MS%u[/"[u.H{ԇ-HԾzԛ(}EW${?Cl;rII~_^=Cy[-!IIS5]P 3lk46\tEl ` ޷qR;sp!_k˵o_GTbON)jӨ1HtIB! -o.AGۼPiB hBW<‡(3wb .-)I |+~m{?ZnjMo#&ŋ@W{gԴ ,9R#l#x줤'UEr~h|pYY{Cl߁ƈ{QwrW4ck`74 ɮQ|qo|#KYg9%8&\O6O4dN۸ n&_ߌ=/v4n]^N'8+l5b@?`tn'CyYrJ#6Vca;)i+d5KB׈6VgwnƓ},/5bژHm6YIʧ|4_X97ڟb\5RmLҬOJflS(YhP::loȞ;-i'-ʧF mq%F\]_ìd!͒16&1{?HC>lp);M}ji+^{??$=O95iVĆ\ٯzVov]T #óĔD:-v 9UDGk ФC` 0O'LM'Ն1Q!`D2IlpsJ6w4HoyUΪ%FL tk|]Luݸ xR6[G Ӑt?uI,Y))::ێ v9XLShۋ$ [_YthZ:H-ň{x@q8Ѹ$|6wSj:6\b%iNmr:@' &n:@A^P TQ=."Y0!3^Fc i@E,5O[6kv֦)ȺŴ0O#mͱ$4JUǡ>)_ `(S>U?yTX uWCnHܰ8iS˦HJQ_؟G8,\б'hcF!W$uܞVg<:tqT(K*umWrHZ0I vhKw]Kq~ӒA7:m?$_^qb ۏ>GpJ6;ڶ=+$q?p;J O9G]pj}G aCy𮐤*Z6dS[ѶAǸFF |87+ I\ 6 )Rvv3X< 9mg;rmEN-hb=GO=yW[ԱlH-#VBnb ž" JRٺӆE9i[A`+(G>rWƭ;o!E=ņ$ڮh+#u|8>G&?sW3پӡ^*\.c86kہ.^zZ9Ѷs_t4ݒbigBW2j=w bymu3uɭӢpR'1rL#Ep G^|]I]H=_ژt jm Œ( a={+hӎɹ&R7ް#Zd6[5H_ 2gLO'ܯ J$uiYmސ@u'&G&30< q ُ Զ9{d,dfp4;+Q5iݓU&O>Ǖv3#ùKG]~E|(4bT<4 D޲%*b2mv^fhEoH n1Z} p\{o\~딻T f!tmeQSBub6?nt_uCrFWQ8O$>rJ 6vb;? * å#?-[utx+9yjzSBm4( .5^/-oްպڲ+1?_)Z9? u!shTbqí%@c,#N G^<[5{>hH#ݗogoE3Nkqn{=m-KMckyc$ENEKǩt=īh],fs˷MT|5_݊u)Q{jl׌Gr2<.*1ILaaz]avQ8ͷC6PqBCo IUi[N@ϘЮ1[n+/ڭ,Pc%))@=oWeYjI=h_IHjiunzK ż1)^Ov{[7r| l9@vQвE AD0Ibۊ<D3$9n9iC$s{ƺMZsZ $*s*-n]!/ovGgwv3;z!@6]ٞ{[`тII1"(~q[͜=-_ɬSϒkoXI+a>%CJmn"^=Y>riXqvDUfuKˠ=]5.K1X"hIMcV#]l_ڇ_m:*?1x׭n eFzfԘ.-]E{R,"aU9n:nj=]e-S'()8%U^-МA{|veյhIY>I K#"$>v3q~]&1&4;,lB??9Oñ!5Dkp7yoՃ5;h}H[ vY{ju|iEn&i5ѱSkmM57̛Z(+:{=PRU?cPFQ%c0NJڲʪb˦*ӈzܺm~R/.ƪ2AK &fEQcvn EXJ?{a*qYTjַ~:YlC~ڲeƔ+ ×Mx[]\UַSVWzvOI\J B-bFլEnQ}`ծn&GR{uf_]%ny)1z ႬtKN:vsIlR 9n|>q+#S6'B&Ioݻ%>ri=ݻ'WuER<"B ^"V-N{oAGump dIZ xk⣤̒|Hl_lAڣw/lmtkl&eQtn{9ǘ7_û>^i>sA?=JC>oq01 pY8`4 iĦ4F:+3,tiVEj`"eřB~d]qSt.K g:u:"!~B2wj*Z;;\o6V,3$C 9íWp,4Vq+a&5WI'AlRAE\Zb>J$ti0vl5b ~w34g Z^pc%3vYHjpre Y6' $33>gjײdzHwn=\;f3}hߔf:u xk:_O*lSU;k2PҪQi3{ɻͮW 'lIjYHi5 c9{NO^S2ݞ)V*7?e>zt.W`Xp2̰L@tSۏZr fvbPW1L;SsJ͠|g]x 5l &rLRn &7~pS[>W&MOC|nrGͺ~`cuT1%GM=q+{e Z$&*r2209cj_V3X̉E3+%t*' kC8R+jVٜ8JDF[͔ZYis݃o~e9pxi*͠W$,XH cYbK[戱U :s]3騁8/-7Շ5l3MlL-͉HS_jّS{L\< ⒱X9LG3#e a:qޜ5g d̕,HSV$|GrY[Jnp8ǞbcSd8eS`kjӮeӴ],6gtu6ձVəbm͉d1u2&c 'V͒R *ednW0 @朱_۰Z3ؘsbEV;"e XH["Vp0rn 69o}ߧic- gخ|}?L|^/?(peDsIYll,l1QM(i#k vflS_2&UyqP9gmb,VDLiw:Vc X!nCh)*oxzƕ6ΚbEqX6>09E'PP@ V2okth 64VwAcjY>PLsx[!blR˥Aa[fa~t>cfʡI`B/KNS(AID)W+{_cs9bӨk!V~*LGwR aC4:LK@0F&~%]sML1'M7h+N6ZZBW㕩 ,Y: ^pjለŏ&7Xu@e;ͪTiC($Yt8'31V D]3}ZO 7i31ޯy qqs0؜ VW6Xgu? I f#hL{[^ȴLl6=_ďӾ? TXWQ̪;2$MRL> mtl*p4yL]a(T,I/験_';`O2,NDLN2XnW{#{j7k|&QPA`8LYÊ7;v:9/Lsi:EXJ(($}^d_311!$l]$plÕr:}Ƌf}xDx&X E3 4]˦9@xy`{_5dN4ЊZj2RLLl5l1 qI[4k:i`E8C`+|rڦجmugb{QpX:ee%i:4,>Ӭ]W"V  *x2:4۝VJw#~Cj@,Pb& `1dȆ:KS j 6C׸Eǃo?Jy*[e534# Yhb;b++ϦU@BhXxw.}m-~sͺi"#  BA cJ0yH/mMSjd0K9T!)F+.MN 1@ p%'ug'R3 * XO\=KtUf km(E`KK?Ҟpa99K"R@=gW0[`c$55fA$þFH).*140ǑN( ~B9Ĉ'r,NՈMu`:_S$A!`\0kآp+˾[ 04̂6aiRf]ZNBxп^%5lipT`p"%`dg;zc{(PS+D(Q66grg#\W*;dԣ}76 ?oծ?0_TZ3فw,ĚQ#z{l|*Fm 0XnK`;X\þ~\L>ዯڑ6~ڳ_eaa @ p_!i{uÐ/f ^ eVf2KX%0ِ(#QrHj ԈigUR9) D|W\ɂ)0ԜNQ ժGhx(`1v}~CnWL۵+҃#`4,Ѫ;O.N5D+PBUff2`s,e`_n Ђbj Eԗ %|u#UIE-j3 \+^UAd r4ڏI=2nC@4o߼ty` N2=T)0dgSAԱF& A`B H/f,3Z.{0UTf_!>;z:& #_hRL-5R )&TjytN͙MbFt{QP{ke `\!AI0 FlPJvW>ga/8Ӭ 9CfV(wUJhEi&)Kv~v:ot tcŃn!OtA_"0^V +{ w+E!l:_A?QtW C|v f[=eG֡~R+)_!r֧CSj)ڹ2rvPJ34" }=n+sڇV?0ǨP5BƬב"&Ei@smq/dL ;d.LL  DZ֨chkL` ]_]s9oKm3!i١-Y·Cok=WլG ר=SֈBCC( rӉ ``Γ^Y3{ټ<;#mt@`)dR)Bɝv TH~*C.[E`/!J@@*YANj품PBKeCB_WvY/HdL&d @vq"!%PN!& C=> ه e&X1HZY I(dmSD.=BB<{Ϗ{ӄ,b?m ;jޯ}Va?1mNY&[~ \Fe X8 ,ETfL-P -eԒeIUJGE @thW5hOd?+yR1,C9B"$T%@ 5P%+Lf"})3s,IF Ѐodq?6 `\qX=>d \&9[qUHYW4DhX<gkCJ!$3t:H":vg:"2Z AHHmTݖooq; Wy}:)85,^9Z B29*(Pa2ROOɆO2=ļ,P2CT20 p'BvrR|W{R>#,#bJ 샂 ̈́7 x Wiw$$_'eg.~;D0Icj+"t ?ƳlqY x]Nx.?b?8Uz6c+؋SFoM8H+ő̌< X֝xye5sXDc #FQ"(XP5˖-^(GAfQx|5!o/iAFpB a΁FI*H`qR~1o|^<+ Fc U㒚df'%ۊT0zH`cQy|V/ɟS!y L'l9aJÚ:rك<"Cx"Y9dh@NXYü{];-1([9z2feYP=pef;I1㜼/N~.rݘ~N_~'<ǞE6Q .ʞ܂PMj,O)4L޸Jl2N3K֜{=-DtAbrNIGo/!s&ʺg/sYD!=d%Ef4:āg(y3`B}:̫_g l)jAP‚H!'f0`[\aP^!bBd2JdIo'JN$p d9i$uIDHfS0./- gK1{nVAl$Qvd'd7V%' M^Jg/s}_GEլxd1:O=@{Yg#vGlX܂H!Kb9I \{8˰S7#GV9A/3w-2G".$C+dfږmQ8ɫL':X0<S> Zς Y%8n`\嫀/zakM E6b#g`{nG&P( 0%̑|, ʦc,S;ڗ&?<LlP, U=w9[']rnCօe:m@1״)խM%+(igQS^[8y"Ig)scvtӝ8MPġp0*( fT maimN"&ɐ4yRʒ$ vw~K_MŪk09 Q6DIwAJ48_#-p@Fb@ ˽J]x0ư ZF lQ01; \r"qϛ¼ D*g=lXB;wB҂EPQfp1(Uo*ly7iBڃA; -o4;8o;d*|+ģlrG㡇87x͔qd- =!+ȗ*$pn ;}B8%(X\ŋ^T\D Yh!M,X+ܨ.xjkQUtItOИQBڛȓ0u)Di܋=DA8L1'1DD>O;iG,?eB F`2Uw{t2y)[Ѷ|p'U,ktwmtftKe$b%%CNxkcT=p@Ky?홓c'N3icHk3?yu6Z\8?JosJœ=Lأ^14o3c3w%|jΰur5$U^XBGj`lh8I|^92" D0 @Q0USE`/+S[)ô^#d䄼d5OffuLƂJ'$ŝ4 Uч ߝmq۟>1fXꅵ丠e1p N+ :Kp]G:$NbT懜W cF?w6dv8uU PSq%^#RTH7K;{修@)/lﰻ6?}Le@3A01q?c͋ޯdE ̜֧=Fh=eAvWV>OA)-:0S A#yDmϏ1fJϵ\(L* "K@^FTE5']fg|߫*p͋"9S @'V{U 'Ο31pN_Ex[{OE0VdA:rJ4!`eLF`T<&NMRםG ݀jwz ~3a HfTi4e؝tD 99/{2UkhNJd=p^]%חcHM"8{K7(~#s;6ϫJ jGS*(( \` =_JFjT?5k,# lp(4a*C1F&]! ,׉kd#*or4[üYwP$a8۽ w:sIEkZeYfHH%AZʝaEahq!Hd,QDl$U1S|ނ: YpV!o2P}K6H֠6T(26L+f7H Vq34M#fMNi^)Kx0Z;:&7LYd8/r/B,\݃uݰZHDBVndϿZ|a33'LL$hEqI*-Ԏ J":J s\@i4{B2粷ͼdƒԘ% S\B ]bcNV;,FFe`bC/IrdLl%Jasؿ<'X7ؓkSc5`g%> 6J{~ZjD(* PDȗ)p![Z6 WepAOKʣyWǹ{^^r^@ ] xZ7?4#,E06땻J<9@N) e Cп$h[~_yf >:.G>tavfAޣ ̀AR\8^E;A Jay3?]|aū_r_|{j2DA03TŜuzG  2z߆K?q#. 8];0 ^YsoRᔔtbJΐiV< "p#R΢}İ1!%F,J"%;6 7},dFydž7:CcJ9j33>cHkQ@4BfFM"&@f 37wPEȂ̱ձ">=.€`7RE `-MjWoH6rUj,V-1s{jhvwd=[0Z:OzAcV5kWvYIF]PP(2mVcv#G-l0ȱ RqVT}@00d2͕nʔ"T*'ATqэ$ՄUg s۶.h/h}Ú*UЃRul4̳’Q`!̍{?z{X׏ݿ Z+`:2d)@ DU/(Kh!4/Ũz;vi6@c:$n&gCVs/ػD7x'OY󲿲ZsngQ'_|x4B'm[ ruPDQ7oL$,"syS&^24+co>..eKC\ezo3ݹ!C ȧo[Y0S'5o3P-;s"Sr Y+{?uٽ_e<PePgpB'TI=L(3;D/g#b#Lĺ M2o6* cIљsMOkEJ9OwOk U-FpҤ<슢7~R/"D@ 'fV XSy|YK}h՘cvF(qr/0XBFbDWJ+pc ǴT_6BPbhb҂i !.ϲ XFrh+{J(xDʺբ5!"_sB*'d,BlB($I,h -C*>r/Gca}TK4ݠŒsZxmG琋A[ MӄI;G >9jUch>@>B%QXc-Yw e噤P#U`0Y%F}: J]X$-kkxi -EӋFG@58`5 d9bYj#|w*W+Jjd>ZMM Ŧk "XdV 2vy/[eTjt%:1MZL%K3punWN@$JsC{9wc*5kك ;?/JDlp3# sk'L/W\4}"Dvo9`'`w&NU*J iP(4r]BOpm݋i"E0@+TY6+xudh"R!=@9!n<~](>Oo˦7IDCa !ZmŽ"ݡZQʁ 0<%X)Ff4yXq8)7Z!V<㲇 /E&]@>{D$#8YQLoj\Ψ!~n &@|\@ffSNrW׀B/Q8I&9 5m/QξSP;R_|] S1T*L`*I[?Lx pdEdTfC@-%JSR2KSشSfYᏘiAtyG-T,NzOJl b40)[՛&xdml3cwJA~_/\z)L.jHϵK i$zfB>"UCQ]hytNZ w9-@%^{fwk:f:ъ$K+DDuI* )ΆpUZ9}C&Ƅ)xS(@}djhj~k:(L<0 C4 j!At)Ll \,3p%.8V~D" A"10 ,CQoGb#Д)ʘq3-ckPӗ{!n ~KsVN3,J#Կ.zyucρ0!0I+i쏘h&k`fZTRgG*jB+A6=N= Clvle %@T>+\g#S5T/R7d6fͩil`@i@{%bDtjb3q<i25*] Vms5WGU px,"y/[r }c}Ey#G g}D 9q$ Ш&uTxj!VC#%{Q> 8N[~"D2YroR#C77CgIH}NˊsnE^e˂ 10`p>,K=~;Ҕt >rgS.2NaPi^ oX{LV͊Vg*ՀMܒVG{w'}D+3#\?JB#82ȤEj*5iUQ>3 j~ (& D1%gAVWZ&c42U˦1^&Ť#d3\cY yR[DfnәKt\3D+6m]5R)%A!h A5U$NFLxu.K S @i '`7^M| k=Ly˚);QqLJ&ys=b_8?l՝O^揻˩׋I)nRw &;X\z3Nx HX`Kpkj`2w,AŠ=fG7^g**,ZWq?Vdb&JP&zl`G$h%N€O Chw.F*1+ 8E9N==9o#X1`!H|F,vjb/h B!Opѕ~v{CWLMQ$&-P(D!ϣEOpV/X[𙳂 16ɾ,H@@ZPS:aP,]'s 1CbDLM'ީ4O `E&,1 cx7d3,P@ ds9$K Iy[m-ֱy?"SQ51diͦsK+fPDIf"' \HN2i"ft`-d4,)amKTe&{$Ԁ GˈCYE\@ DLz,eFT(`'j;F"HQ8]#Tڠ8(ږKIBDxM) ˀ<9  FFDA&K<&vbt5+]L2L49z-l,C@jC~9|U1$H#n=W&,6%j3+# ISd̒3Kq'X#E8r8g, XHϲJ@ sb*w#VVȵ ex07luUfreփXC 0$B#:vکT/g|KwV.cΙ5GE+GbISc*Ϻ+2/0'S)l15Hc|߻۬oQ\ `+MXn{픸32SHpcK+CB93+",Lkpa&,¤S3KaRļ)3YUz(΍&VuC:@7h %;իW~k]d%5yw~{L8Ux IZ*1m4gD䶂Xe>c&税b9!0Kce!%3VZ=rdKI`LԾg?LVLpGRGCUfZ=IV. #~z \.#M{l1K^bs6Y5o]kf]3"{3-qJT!48o-|"T&UrxWL$lH=S>]KNy‰6R/*Jw͂0[7 4 gI/,O 8p fܒé hJoz1O [>@` k71qw|$+PUoEowIJ&ښ SHG4;㈒לLf"jjc{^,YjWisYMZQQ<69'aD LMJgֈjL -}/py]Ms~ BL!@rZS˽^b\PVG͊8/WăJŐ 7Ju-^yRN(1ّi]`mg{u4wgGhQ=`*{|(jI+A  #xN b{Ҟ]vX2s4Hh*K1T鴌6<6U湓i22n~aѺsV9H-I"Pxj!@ $3jӫYb)7TעxEd LiȖ9V @<]WNjR &fi"*`Go>lHWCs Mr`rZ`]5ƆIY6`b ȩjcbuM>t%FPB']T QI{H2PT"́P{O -rg/XH[YQJ8 F̘?@%Pܮ;q1UI::ydil.(Y3hYkX 2W9D/6*gz6d!'e8)a1Ú?dn[S.ݝMjR@Y4i9`J ,{O'`rЉ;2YBD*LeQBtk,ec:ࢣ⛮IsKr(x̤;}/i|:(驢ؕ =v7 |@& b4U~]ڨZWNHj6{NcE]-2*v䌺HWy/WO P8!D)'Oh-z $'J.I[dzh׫fθJF7v8^d13rZd@DA[~čEDH 'Z[N`sFw&TPzQjX]ŵPY 6#"P%.GOxȫfOT(5}Fu)қR~ƥ>i+E)+R":S^DĎx.Xe/C:$]qϋQgg[|*W;je{o>s{u;&22\}[`@tぱ2 =Q5Ћ8,ť44+$.L0-{9*mЕ|mBL3Ba傏ɝL] }کҼaaNؕgrbeZfҲ]VCL>WEϪ؀WfW*w|g4&^Xkq`0yyX%sNߟ>v< ]Q aF*y52! SܕH&q6ũ @0>"9l]N3FT%gݵɜiy%"\e-IBY>vd~u+Rֳ !MّNb-;:"2j5y3lٰ)^A |2po㲊&LVآܯo[M_ l P0\oł^_8h}ztCADB 24$Ȃ@[q7)sZ wĄRW+~O/ȨdDdmzqi&)t R!Q޵]TֶWr1Fd# DbNKHY/^ [$@u?UK£eM7SgwQ:aaDLjPxpjAB6 `:Wo#g+XEMctJ\\P$Ba/JCcA}2fl^E`fYHqdk`sm-gZry+c d^Dwvw3"q OZu"2݂m2,JIp^bnp=#\c/hÈ}}aġ>u}s?PC)x2oRMäJCbd7ٶ/g.1v(/,sU) -D<$ ZJzO>oAG%m7+w\տz3R hUC*}IF¸|%Ӭϒ lDD-MD `n[̭MZ"֨YeBRLe-BƍJj/[G'neZMj@A`l"F@@@&P_) VFP֞KOي}m1-~{w!|y%v ]qMAbHG$ I39)fj#11H+ߜ7\얿f,RBNL% c}ΩsOqK-Rl27fDžaZǾ`H4"/ c~PM< a5u(-Ժ5 jA!N-;xԕP@,BwkD'ߔm~VclΖh\dP- ^ų$D I%cVJDN8Z8Ъ2lRذY|*B#6N+ 2bɎlt`ڃ_]55pHG?w|ܭǵoZ!R'[.rE ;*6vp$C@{wŎOWGGb"qDQz~{WԪ޳a%[=B (f‰KiӴL1M$8 Vd4/\qU.$ *sh`naSOy;]O_(D =s`Pg@a9,+mai-DM AN8*xgjE *VRԘtiTls a(YPrpH&QEx((d;lpo#c&V(ᕯwY+ZjM:pK"σ *b:L96sMG5W} 3. hUwx_vW K> 9ѽ@,{A5FkA&=?^9^Y) F =yJYqP//p aBe d<ʕ+Q(Cي+/\k&sqO>SH|mv : ߳/wγOcQׂoONLm(9a s`?iq;UjQEǷ9} veKC_J/"dp@%UX 5L@lqbȑ#J lWS,3&Ϛ 97S[i^U^e/X40cGNJS'l Q{=H쾲J%D+>!䜐 yKRHG[av0!$R|ƈ[G#a2A3l#CJBbz_<ǿc.Bߙ+"!!RMl)}tȣrS F0Ue3 c/]tt)%auCaY>i;RD!Ӡ7I,iMNiI":*eZ3sngwU1K8R$ȿc? p0 kG;_eWB s|{GSOyF6Tֶ(2P+l &K4 ]{y&?BCp0E74sRkMW_ F,AHg25ڧ.((~~-,C^|dq&F"D+BQ$ v3^z02O~R9gJyz=y&vڬ{0ŷ'g]Rz[j Ww5\PȘVhwb<Ȉj'.:!4d0miQHU1QopN8'LJH2p3w½WSD"T% {UE,0* aB ۅX/b@A?AʸbspTSdu@He8م1(ATBcY˧ca ~KO6,WY-bs̈`C`=.Y2] xȋ &>W?g?#Pj[: m~$Vc}WLKR2493VkyL7=9 dfz"*Ѩ`xTQGdM">Rymd="\X(/g g`T(`@ƒ^\KÉԨ#>%J\_e[QB@W !$k^0t2k**Y} Ù.X sc4&D,l1զ9Q_uW밟aDM=J`y䢸˱-բ1DIE,;~DD/e`*D,gP͙A*qAPD‚ʠK QaTĢO(r,P>Ꝺ&7f6+e,i!HDFCBR""b=5pтL!T©Gߗ0%*m U(@38NB e!pĐ,U~_t0 "&d|OK?a>%G J '*Q>sc(mb}, ef,gQ+|,K@l`{N0 "cg`쭡m%88vPe<˝@gy+  fKda15ZEޏϼo|KW!Z]-ɋ($7b#pr޷SPۨ, 'VWUU+-ⵛ>KQd ̈LYan ^"-/e Ac{O( eg1,71ml}a7eR诐iCI@/s06*UŖtIicTE7P(AP*l\6H3q(1Gxc Yr)7#z)@@e-쏑wJkaymp,H X?%pt@cm<(y7( ޓۨ2Cw_q{[jVRɻAY%@nI̒y>0!4/lhI/ï8^^ Mɡ;lV\CSM$hRapSt"6< -0Ɖ猲"geuQӊ` AŠOFj!&dfbN6SxrUݘiDF0X]0(I钔Gݗ_z?KxA4RضBT[SL(ƦkW)`L0D7-کaĠxeq:u/ BBǮ\jJZbDHl:<&ߘaB׊T#7豻2#>,_d (B*lK1;b9]@NYa^W=5hv" 92L>j?]KZĬǵ5W44jֹ ;W0S(g)q"9%7-lv֙M 3ɫn% Q$$C 0)=**<@m/Y0i}GkGd:MP&bdf^E4QkU®<"سMp#H q")rܔ庉kfWw t09T95N^Ph6€o ; ŨvUd*z<2w* *v-c[i" zfh1El8<"GrQ\DhFiKqWST.tvcE-M.FcפH!ɣD ibDTCC3uXQr)zl6 s)Dk hql&A1U'`jV-Z:鸞te0{9M0.[[Hm`J7}! D 1~]NSaͽ"Ha}oQx&8gxӂԈDOS??J;s-Zȧ^Ιa3}eJmX ,0`X'K64HXTOka=sR"E9g~".\,`((!j"j9maޖ̨X?t`e%7""ez!@U(1~cx$%mwcNql:ݕ4Pԣ\Z~g"%6Ӛi@')JJ c2KAe%9;pizpK3qas*p6GRq*^"5&D |q.z3B*V4;nj ֽשJ~IQ#Nҵ:r(ihP;]9 nѰ# ^ 䝠ϩҒ QMk<".OlGCb0ay"BrWyb0|f.<|NhC/sѹ/ wx{%nSD4hR] J"ĦrdTu3T||-PwMR!&zڙivK)O{`FɅ0a0}y}91<DŽWzFiDFV<5{@fCGy2L-R!Bc~g`;*K2 H@ZN!™\IڅWnp>q)$*D\+NX7Q! XO4A㱔c8 {WاDǵ{vrЖA0RV䐧v44e | ý`_HJ~C@X>M=ӽOBe洤x0x xm0Jo"='C8sKe ';d),<޺wPq㵪[*pzJ(WK?<:v] o;9 {DcjuurJRZBhw׉O|/a׃>qv[a&fiLKj׮sn؉M&c?p+i㇯tÑVP=t(zdMXPRNې qoq+)x]8QtrKLDDZ\F mI0iy9! fJ.lٯkI[q<{nn/ǝP@KGi{FvxDZ_.fE4qI=PNOl0`ypjYk].p ;eҰM_$l|%r/T2vT6̺yU%Q Īq\r!>5+ S9;܃1hǏn6%~N[f,PG6DNFˆ݉ee&];ϵqnѨ-yzOgMn xdv6b+RM=]ZIs6s%m|7bP!J^v>\uIS D@4$qEaFă #[J]  ҎZ)UYJl|l2I\ٔ8gtAK|<#H^=SVK)KZɸ+Ra}*) fs!+"]G+z۞zvc8((hG#/*υ;r'] qs}kHztٖih&OR9"*5S/h$ yܳryvsSDr,sBPGg0bcآ6_Ag]<|O@!wՋ5&n[;jMN`v81yYK%W+BO>M>"qT\9~{, iN Mώт!of޸|yexZ jQ"f9$K2n@>[Q#V*PiT>Vm~V'o(sZ! h@uoq4c~Аy27nX)o k+rF1"kFͫߪ?XP{P f5 VE#.R'3O,N6ZŽ.._mRwv)TmHB,[/v <7ǚ|Y!q >XGZ*X-Ȏ*ݲ\5sng lY+FX*UqnTT@q >Gqm=P3Qډehv c=L%ׇO?4$w6S-D TbZ@-ۏӊ᳣}lsqџ*Ya8}nwnG8>kiK1pS3+x_o*_nqb Q*,љ+ H_,v?ﶗ[:sEu0wՐل|ىl1cɹ#e\KD2v%;iW%7-`*o3hT+~V(&z?H;]-u{]4Yw}]YNY jƿqI 1;_t^OD4 KB%.bȔ Zs_tE-<(?E'%u?l?I[KtA肤EW$n?:wYIucۥ]]Hm/:,4]5&`"Y%mGQ6?y`Oj [A)f) ]]4e[+&/ ieE'%0mOǿ!߷eC3وŅ-J|d_~ ɟERjZŞnz+(}ON3Ӕ Z.? fUV~mۓ]flku(m?II[I,uh%ڇ'74+s Cax$?c{"D+}e&Ko>Mn'5bJ}<9E>1m,Tug)]LPN7b檺'VFKw֬l{ MN4GFY_흙7sܽUyQP豳6l1h$՝mvՋL4uF#YƬŇmVFgPc:=ޓO_u EXr:7\/)+fԳ<"]:#; ~x (p9vdz71YImy줤l;5h?b^6P8f%B"bwgu+|f<5:6<5JDzO=o]ʱ@ʫ6;$_w4E s/Wꡳ6S4NÝZ4#Pc;%G~wjg#wzœ IIxx_7j#[uhfdt~MpibE_uI%ܪge|mu=[Y^2!ݩ;|LÝ:^X¬SqQ3ula}g'6u7 FmXvOUFaZwtW@$,l^NJbj9n5dpණ{T0 :e^V]!Ix:iY{7tvIvb!z pq7⮀$-lCM'1ΩbMaĖ:F&q 'o`/php_[aOM[#vQ( pƣh$Qc_K!Oy 6< הGb9Q![tdc'MǴn5m_eMLlxp\19Cw$H@~F$Iz6lãz6] m3mw {R(m[Ko`Ƀ@H6pT3hk2Kݺ'.uܶ{{Zu15S]4o;TwXʫܶb{:J{Ѹ;xWHֿ}uDzMiu0Nv㞊¥ B{pTo{m|oiz>/Cl[oxUZbM֭&mK{b`po<؏P5^]7 Iܵˎ3v~j7T?U+#9ʎKq`dEiLefHC,1:aE{sz:v*TImMږXJwrĨȴwر6/> y0R3qpctMe ȀZگEr؏=u~E>l1TpJ"}CasHMXѧO v`ZG<uQ!轟@ δH ߐoxB]h0.xIW8>Kk?v[?žu!NPvj7zwu|OkހpOŻB'q)NF"]WӲRB rϘ:[. I*-72r{!=]XgG!Wxg̿t4/O$A_%ҘqFGդFb)t?{wm]!7tVtu#&Bۦ. l@5;GzgPG6vt[:qðmjZ(<̃@qgGw:Ԗb|ްkQV%ٽ42Ҍ#㮐!͕:نt]ٜEl9~a{@>31J m֛bzGLPVzOGQ^$Gu17_P^oX|k-ょ0y&ym?nK [8+4F!(,bzww$I`Zc~.u+6Mү!.[c| :L6?rW3?睝N2պPlp X (|8QT7XS~#OY٬{Zl8UkcGtRm7_Ú?o&[+ x;TqXf$mtDrlg4&]ZV]4VO$ʩxрRA-F+Mx}ɛB["Y?O<3M3ժxI83Գe7hNʑ$3=0KS^'JI.b{ZzB%nxT"IDH\(ngkGc7F`7L(-K75~oQ6vgԱ${Mg^37M=]آ&#)&7Mqaߘ}>R8`vƷ~' S'v-eDSr\dO1rEQJDny8yh{i6x!s㪥Zi㚴NY̺ڜTXψb)/"x*-QvDdUO#8nrmo>vkD͐CoUӖhZOh7Zބ:%Pa|/wA9[eROSWzҩoJgr68(☜'aи]UC.dg=Of-5-ҨeqtfG{-sތdkЕ6P~1N=C]I-IvJRDҞ5qk)Qp;Mk&\6x+Vc,X]@".1KYvSduE[EQAUKǕ Eӊ};ʃ9׾jHUǒ({I&*aںg}l;9sO֘,QK" A]f'v5ڪC7]C~N2w;VY[^3i=k"uVjݼ贓TL 0'0Z؉Mȱ</blM O<}Մ|Õ6e(TWn+}5fSIbJlO[2;ν>ۭul:&5% ,H`:ٸu=]IJ<=&o*nPDqFU6Vk[Nbgkeh%&I$WR*e%w:wo8ޮzM5_EUKʚV-jیHL[5św:9Ż@V o] ˇW=v-iV$#Hp}"R7ncVl*{^ͽQ/>um*Q[oAtT:MAHӣn#{g}8tPھc-{̀ ;\TZ^җ,,xabnI(*a72jYy%v8hgwGS*ʕ()$H &wp`Vӻфkt$t}N/D-= C pu .rq9J8L1b.br3 ] +Yi1gOm.n =z@du-Q.Z~V;EiIl6?+M)&8u1GÁ_J֘Dtyt~mAͥurbykrO)cEF=ښi?2Eo/nz4lӱiVX6iNdFL[qѥ^&VG4a"T0VؑYki_?nٵN~3׶#}GM$hsUYYٺdoyAvG3nM;(hF)gl9ћo"F~cOvzê:6oI|2ȶfȌNӧn3kYciQ T,@5>VxۋٺO_?[TFYID%d.-Zk&jºi,MjXpHC)8}ϻw`X|xp^tOW&$bŕ$οPݛ Vug-f4l00MJry\FpvFs>t;ĥ1YQbלR')q IIMwlݾƞW4"!K:h)JuvY޲ױŵ< +I3ȸ(TDn=Ě>y1{rrZu6QY ng$q$v F 2;殑rIh"eh!VsUw 9^t]9b~X(:R໗]_u冔=Srſl+I)4\R?`e["nvt]wuzWFQJ]VVbuѴbo*z~r5Օ4ˠ$47 "nvwY@n+q0$_HV) PGkz@ENĀeQDv4Omq9~ءj134gي厗e=cfQò0nE22(b*o7m51' GM"" ]6UzLLO| ;AP#oy7_jQ: (Bsؤ+n16i\ޯb uy 9m2j@4£(aA(CC2(c^c突uyg hLas֔VDBvGqjbWĪ* ЂRY5y^6W~MM?R ƌtNB:$2-3TZQ ʌ>}di" QOr)mm77;>ҝo_ϝjݸx"k +".O#i:g]jT({?GlZ}-??\fE߯Rvu%F;K<hrn,Qa3]{3U:@{)BיV\V*_14!k1fI[(ϴc}wȜ 7O+ˁA2 ( alVig,,Jl 6fn'@lFo4NWŻ`q_0kB̯ȖES^q'%I*آ~rKBx0F;gfO05m9Dj $c<(HZɚ1O$ 9/fk>bcSbNG;6,Mި0B"0()L'i ƽae*58D_)ʯ1e+Jf ̫ŗUe08:Q9v!&OS洨!ؙ*Řu [!l!4QLPP4;RX"m鶓tYݖMgOo v`Y~]ĹBSXӋ5L&V)q- "n $ wGNfOnsnU31o璥iOv+?}GhU.WxnL do|g+NJ9Xj#NrS4 I JW RG'PGP$F>1uh5nFj &n-6kʏ=/c0_ﯺ^;{kb_vb+3P5fLigۯ:SC9s)+ =ID/s%;eӪ@l<= +;x1̹OH@ֶ xp$9 Qy|iթXlnQSSfZ>S!,}́7N&hPBIOiXܰڤZ8Ė 'fgǞ\s?]k)V|6t?\D7R~P~jhe1-uk`4hf&[4=?k 6f/j7N.HQ)aĚBE:T2  0< jd1a~rC,N4" lKL? q !PC< p<`0 E5"S|-q QhDx q7/5xe;N$lۛڨI?hm *ƆFـ%@@ N@2al4y+t5[9sx(ޞBgR<#gQk!r07kxJ n.FKv&2Ւrv,BU0IQF_M5#F9 =*l@ nX\>\C.j҉670[^Mv/Nfdn% vZUw5׈02fL`  5`l Fp+Q6w*2"@&RT#:вq{6 jIfʐeAJNѬ\iwŶZ3 pM25 (sY`¬Q\G6j*"Fk.Vv :( 10/Τt=K0j (dDe/Ʃ -s}hX?k0 "D:eӷ|'Al3a,(3Gq5׈ ;V>΃W* #bMOv3 Uμ@T ?e҃Wz<\ tΞ.ϸr8cLQr" SrSB.{5לsyP~i iRU?BeÁZ{.3{U=xomdZ'`X0bo+M)IVё߉ afCA8FF"Ќq&p3)'A9TN3Pj?Fpigm @V^yFti0*) "%<G,Tj}iF3_gqĶF 0o$Q}4 l `5F;sBbLEP:K)1!jR]/`{! '[2բHΈU[` M(=`A B!LKR݈ HCMpd*ԃLdz88J Q!JjG{գ)y1%Aq!LC!J'izlܜ·RCbcaWO,D"֐0AZ 3ɶZ&2P!T:%%"sfDeaHۿ*y؄ױ<4& HejHt8ⵯUj}z{+m 9SZSfwM_+P%_J^ hd>LRRցbs˶/H"!1!a&UKfR<ׄ] >T?*@RP"a P6??lS`r"A!#q r!Ep%5TZ0 7[0L͐JFF(G- v`8,# H,9T` 2!8-ڲQ FC{Գ26. UAdJ \ P̶e5';FQC (,/P%be1XIe8Ji}0Vl%Ud`I x r e%@ԭӒ^(8nU}2$GOՐSJ/$F :==@`)2+ſGQB !'"-<W *3%X/DQ(JZ01o[t\ @AՠRGE"5"Fe@$*:vy$ 3|5~(3KVfq Z )? {'J@$fe':nU7%' DقS T8Y9Z@>W{\f)+ y=[,*˼Vs8~q*qڼNuNy+f4,{\!C`~.8/#}o@H Y`2wlg98[ P&*槔JQT 6FŒ,KlZ(D 0Fij\d"@FbDu̶ q/򪓓FM|Rۓp[Fd/7I3K;E]ߐ}1OzS}H-XL8gRW!fuvQȹV-gߕGux(3;ܥۅ3]"?1|OJqkt$f XAAAY, j%t9PB1&OY(0쪓.oc=smQ$2 㑒iCU !`PI-yW{@Q0<Œ(T26Ab} 2pH 2O y퀠'>)$0a#{rߺXw [ jxE`d@)%qc$_ftQ: 46ls/>X݇u^V:[fbU/_TPXW{]i%X>}># Vo Ɓ#A~R(s'O:^Oe{1DҚ\F?2Te猘 ;7nP+c*VNzKc ā BQGHr؆ʒ5R|^F8FĖȶqi Kn%R-/291a`_L,H WեO Sn6!!B L=ogd% q+**9 3ߥzJvz]@HBoBFnsv;>'';SjH\Yd ~+fWvPNMى@֒r&`}ۤ%YW'%{"~Sc>% yND3{Ӕe|k/JE`Qz$ rp俸Oۣ%6|5? *dJH~P9-s iTrɞg)_zګ^p tR֐*%k!Xyի*ƍ}8s_UZr‰YjC g%I{7m_stfuL !SG0==m==g9W2o({Nnk;*Pf+V,<|#|01SDt#;sY%O AH&×Kǭ#g0|0,[6JU{U+=z?y$0 @dFq n^h~?T Ko I ,@ 'fβQOT L1PK}$M i0ʆh= 5D,XP|f[aC) Շ,[q1Ԡ')s#.+ ѵtǬ=15n6A0"Γe]toeH57}_ϔLņ*0P$4'͛0BLfIpMEn|&z1T_y9=#)eD%[ xK)tɼz 9JlЖ1T;2 h#otuE[JN&BBۧK5$6Ȯ$۝ՅThhPb}f<;BjfnaD]04rUW PC(!+|Z$bA2D:;A +ǽx^ڨ>D<Ȃʺ3O$PM^tϙL|ydn*g^`HzV*t.ɛ䱈s|ކIV{c=XOFnx\gnL[*sx׀Tّ8#3@ٹ'/yIs¢Wكec;I DmBybf"IeLYJ'ZE}hakLUyfcF||'s?}xc̱م&6-ܛ H#SdXk $ N]~M|NԮP'bn44I‘4YwtHbV-}Dc|P;T+ =ZqkC6?⧌񹷒tTSi %, W0w3DtLJIcS4h^ٖUb Kr6!TcId= WL @=\+dd'eAD+6YPW3{{/׽ۗ˧D7H6֠IO$YmayOX8ς Qo  ȅd2O9TW:pr;G.^cw0mƆFl{"Uj53"w='VYdց#IG=\ b 3gͅ=׶`6S⡱ <֙xFd⏼is6g?[˚Y@$#rne]=eYH>PGP@œb"&D۪+ʒ19E6$1 XUR0/PMl`3Җ`K3" JHB])̳5do oPqS. <֛Oų֌2CyeԱ2> `3:>*DUT6#G$&s *mA\cEXhj[G?yNHQrGXDnkPoKIHΧ{P wAt B4R#G8~4!Ql%M1`a F""Z٤Ng^Qn7@D}¨ !P#xCV4.͐]kn+ +fQutjݚJsI" K!!̐!@I11: N tB9tDEkyh#7opVAQ$0$K0DY1I UDoey”ݐg2HM͆ PT@ֿI@CYmRC4! !zlc g?mzyaK{ɇ1mm[ -D9M\JcdWe[ltij  +bzJGV?ip!"&`dk rKΈmo7Lcmk!5-SF0 3lV12@ ɉ+ -%QK,={K7-ad}S? vNloݬ]9@Id6lZY@̤(d8['6[W`0+X5eݸ̓_ZZ󘤔0og&O0L'$*1)aM]{WΩL ik+^0 ȓ;-BL,!*-Hf3*=` e ,vQu$ȎbaӰx^}ND9Ok&BgJrXe.C# 7: T @S f7lJt)$SVHeJ٣LbW{3G k1_0aT$| Iw33'Lk4 b7]Bd =T͙ZvV۹a\I'[Ow70y J5%v~bz$ NqKScV8LɌSL dhsBԜMU7$Vsm7 # 42 vI^EXmmu[C=Iu'_/"_NMB%8TwMz$(FfTu)@M8<^qCR{)\…/ZNZ3kɅ)uoE/7+t?8x K)y‚I*X~*Ңh?";J3HeԂ*4+(Emܷ9$"Zgm1KQf*2 !reWr#N[FX5Zz]"c⃬b6{3}IoIFc9d(($P TS82ZQ<9yJ!غF#?2-x ;j%7tێJʝ50ϻЂgبƱB?X].GUKRK2R0dAS FT02vh?IvT䛍2l]J,V3g5Ҍg"VĹ|`{&B5Qvff=Ċla>X ŢyL]T 6`Km"KB9:9/ NDʼ B6ˑA0(CVROQ% O=`-}mF_!LZ@m$,Aw` 7 0&.Cqf2ī>#djOBۺ㕌CQj@]]1` +tVmlV9C rnե}QW!GDpa40C~smw:I:r  䱃 2q9#TJ%?jflL=}>>eMeF;{  ZYRɍu>" @+ \y S`08_*,G6"1r`a  W" K(t7C//x!qƅ]s 4mlҺuLYDSQlɹ*c3*bi 3RLu ]΁gqYꌃ=* Bx?Gч;"ehm5IeE{s  s ReX9; dl!rsŦ~m߯mm/o)Mh8(̙OD6ZUTd7%̤JlO.byrX:V;{M)ΊӚ Yɐ8 qI3TJC$msAT58g^mߋ­)ɗu\Q6 HMD@$!Z8DJS9HjSɰ?rf4 e)iaI9 Qn`ؓ3~ݦCsdoF]sʻ#u/ϣʿkyśyP< H=)nϳ_r/WdxJx^(Y;lҕ5?+1xV0"HS !9lq]qTemҾ`=G _7Q'My֫_dO$vif%* ]q,$jDQQ)Ѹ6Uή O.}yetɜçr#Enjxׯ0ė|^wĔRQ Sq'Jbֲy\yzOqmO @!kx .qaYk4N|^M>Zfv,/jɗ%J ~ʦg{Y|GXOSgP[[yW}wE>qФQ+ 92OZWFaF-!1pf6qr.')"gF2AҁR`[5vǃ#=. Ǒs TŨy^QWċ;G+yx \h{mY[d/' hGa XXE4Jh91U("7TеQ}hz~g IeegP TshwS)'(~,yDޞGL?yurTc 8D FCSo4W *Y=QC-[<4HM On9![7`ڰqe 601<TCVY+1 @n&8iϾ/BsUA*!-Cf3j##%wLfg%ܴ{ɧ a9^ϣym9  .I&mՂBEBƧ5S||'zK%XLKl`K 8guw<7${* QyY ;bڔmo{3ZڐXs-eu@xHCT@4y0ٰM"2)%5 X {M/wy M.2=Ne%U[lg4وr ,Bp`haW+a }Qe+3{A. Ogˎƒ\a@P?zOjf$Dے4D8HqKY D\@_%iLf` -$eHd{A3s,:{n{j{?ϭ FUo(q K;-=uh*a\6p{jհg]ޜ<]&C%qӷdWP4* A5ߤky{n!g ox+hV٘o@a:M߲a6X=c:Sۯ,lLU/<7YFEpRҙw{Bk$J"cl̏3>aNrۋv%׎_3-onKmiT<1Z/;|e9y~qK?ɳ9y?'gi]AR@W  +XE1}cS/XW EP! >XOw$nA)5(iygkpXNG̿TX2ޟOnaA䲫9Tkx F_h\nޫJZajM"b#`XM0bQ>B|aJc\Z,zWA4ևg$ {R qd :2)q0嘖Q/rD"*} j,k ?!&Ib1ejBT *J LS+aV)x3=뾺wh]!%ZbQnjs KFp>KrtWK"#fNL8qQhv]s@%!*$5Tflr|S#hp.iO/Z|0 I9ˋahOUsAipiPi Vey<-L5lDÓڮztN-VinvCT#^(lg:|X[\IDmY5 Im H lƤmMKݩNY*SCK͑ɫwۼ~דVSNE%D=#FUPze$],8<2U@ 3|$QO8'h++ @I`u{ԊB !R*EK=Nl[!126Ap!NSbT"(Jz\ K2q]6[CMGh?c)s۽ǂ]7e*)C/V) wڎPF[828ӍeaPoAwCݐԓP} +!{o:~X |~7 ĜRɯ',Xp    aI % cƝEDHWz4sJnV'yalKcCmuˡm]* WfgWӌEt>ļ A)ݫKYyP |~C[/3pfID8);E؅mѢ.!$tzAWxݲgkǽVsK4PM,$elR!,w&`WAC ;$Xhz WBsRs2* S cS*\Y+.=Yˋ`[+aANR@c4>seۮh/R21V̂q<[kDhj8XrvlGt VH53ڶk-S4 0v5Xhyľ(gP)&̓}C#a~w?,z!U)h O:`T% ƚe'(5"$hRDZP洟65V_){pw.Flh_{i0S}6OW_9@gď%QBԩFc-5rQIpLD],Gd0 $es<΄_Y^j-7ebd'UIS\f h i{-~*W?.d﹬'?+Bvn(y #{"qfe #,B(;lB1T4l2&Ë<#d Xdϼz˼v$ Dy]"4$09 0Fh 2W 04V&_iPJ lɊްưe#Zi@Fa_cΚ- `0#fD@h *ZF7J܋ vǗ"U^e X⌀FQc3x O@U6P(ĀdI?Yk]t"!/ס.%'a73,㦌b>BR&oebֶ~fE?<>fnGlV &ATy˸uN+i(6PgRr_|'/X M0/~y\MA:zٔ"T{-fE]HXބ BVY?Abkfړnqv㺟T~g9/<-̶S$dYwf8$ 4)ٜ7Ѝ c?sFN=>N;SHh*U"ar}zy{9y D @-ߒ(;l@;P$SQBQiӧ2]?\y^O<碯_yA x 0ɢ»%T!SqUr^pܥi4(1Da/4EK^p0TY$);{d !{֢B8$"HI@PV p9Ƥ`W?y- o"$ȤaH') f"b693BSG!h(AUaNVr*Z%ZK jىdnT_E\@+`R5ZMSx(#?$+c364fT[g ]z7ʒ/$59h:lۼ皨+֐*}OT~΅j<%lTupV6ΤvcF{Ih%`.NݰXrfk5}ʆw}k}߿ sVgO^TT ɴ4L8T=ōM?M}ݢ軧dA&,+c@TEsû0 `(I:)Ͳ{Tn+C^0#U?lTecx{e>9Q;HbEȚ"Vp3/?N!<1ƦPg20U ?؜>?!v Ep!/e͓85aD *LR_ sUz4\,a,6Z6wD |_~?^ṅ%␳ٻ"VYHgt4[uUw=kQ+XQ XC<&]qkױw5 S(d2n]BALzTdu^~V"cmAfM"uMANY?,f@HljI4*צHz YxzƚO,Rn q A rf̤!$HFe(&ÌaL.^  +^| ih9TN4UgD4RQ.12._jcx4M@c˴%D'"sњ78?5k c ;03RgXzmFMkkHY%LrYB&w:w7a; "*,-xiU{mlgm6M,bQMi +YH4%B\sR /QHC[s峤A)=TO8Ҥ-83]IVE(+aŞn80Sd VHukLUe'2BR&omǛr^FYJ<1+`X3ؕCG],BgO{@ƤRnf`\rRh0qW`g-3X>.hW#s?8}u."5jC UtszS4T$^ HηJua#G@'1+ YIwH*[H̉]N|in:]s oW b )LPAx`+譐J7 矴`>H>6a@"!@<5\lqHXed@̅Ze/v('J@Ȃ {BQ%-+eD]hl\0VҔ8CV=Df5c ]ux/2UCJ:LUk!rA7TsOX'S3LYVX23>2b31X ZVTX!uNd1r<'=0ěA q|\f(TwϱX`+/Ȫ\$""db/])f1'^yggy,W`֨;tH{gI:xWJ5B`>Ȳ'o:ۨ yNz2; ʜ -ݳ1rQ9Knǎ_&8mf,-+UHُDO;^{hq$C>)k/#$e э'M(h儤Tр(33KdS M`u^8|˔k @fωN2l~׹oM޶b7QXiku ,3[S0O:b N7,}򓕆++ 4y/iϛ`wDiee]NppSC䎇[Y8G\pœ[t3иǝC,%΢1[\9U/#+U!6ןq jo (8 I#r&LͫYP$+~xKcP1a0f2\!DyI I˲.|3aˢdLX2adzT5,0w@;3VOޕȋ$>&,] <(%(6zÕ0@ !FP10g|fX8T >dm @RGa 'ry_8:#IOAB J(]3a)O]>bTlukpBDCƉ( oZhi+˨!CA(15'3搣S) ]5*>bCeymԿy޷gQo:4':߶+1@ 9&!r%D$ˊVB| VP ͹aJ,(5v{cf1hq2u4BX1@8ɶvlce,bT++җAgVƪҪXinqW?e-o, 9O W,:N~O BL͍Yj?vHQ]W70N,Zw,.QLf?K}]~:HO ȁt16_AfY2!fDTU?lĤoNJ2KhU45Kmo7^)&}=[J 3}I zNgnJX%0<ٞ!ĝP*2b Xs{g\,Bi' g|XlD Ο=sWnd9%3D%Fj2|8_M_)a͂6e8T8◦|fq1 2wA/{R$ 0cPZxz 2 bbS 2!Ԅ`*4#x-PYaR3ffNO2afo ydf(M0ù,i13DURآgBĬE)z##|s,|!WphvѠJw÷latc5*k`~cJ eM!/%>cDB"'lFvT@:j͜%x)Y@@2#|LXIًc5 !.vp`XT9!퍓g`lVy*oT)]4:?+b3/RNzjEPOBAOoxV^$σC/ŝu:3I(1KfJRr`cH<( ZhOy(ABT a'zK7מDAez% 4 ?*7.Ź꣮ov(*['Go`N3؁^N!c /A__kH}̭G#,_;{oisg\U3J;/{i-XH!F(LkS hP/[{)UJ e\6dM=}ѹffaTQ&;oޠ/PeT [&"5*\bA2c{|4 A2՚T,Ԧk>LI(j4MgEp[k y⛯BAēΆ EdX$FFlG sWd9%h g2C>b1ވʘXB(, Dg!P\E%e8,A1:'qΒ~IO ( #HZړ#JL=ˎ%e0A2cI LXTlC쪑3n`d r1lL Jvc*xSQKo~ s6S1Ñd,ґ WgY?NZ1[[T1oQZB2:K[fDLyޑJ"^'(|YV￈p3AGjKf4ïVC 244ȮGXPo˰E}|,-^/9HX'_|rٚ7^gzMB&d5 C褁 윹ÍAP` ̊ n*`^G (Ƽ]siEZ rP`3!7r%ߓ: dQ X%3x09FA'"*iǘ+{BduDil)i0H. c9% sHsh$@HܒmjixX,p(V1~a>W~'%. -v ̈#Kdltg\d,@exXnR˞WP% ߕT0q d$NV TQ$EB`+zj Q@9 XK Ȃ:/N,zW^QZtrg`L>dzSPcwx(wLXoa8p4c,\L=+(+#vv 6u@x  (|3TȈs' jHRToJK!KƎeK愲,E\%Z}_'8,~M꣤M #ãN޾fֳׅ'PJDx * Õ0bS0 r4Ơ`eSǥBaI򫀬u`4L =۔];'t`-<14[pf5;gqOgRIpL٠G".0F~^(S)"@ E[4z@lGơH79~4PR;zb 8&eKL[D 8.\ #c//idhN18#[&zDdJm蚡#2o`S1I&C;&pdkŽpň,K15H`v " FW`6^ROKD!=չK5POkHF*ĈDFƠa5el`jdЩqi{*lXy.% %ybD !J0LRgL IjR?Nc40"7JkNE*b;aW+>Zt)Ď.2 Pu^%OM].2gkБwO#a~' B$Iݕnܩ%ti+lMn Q[ 9g}cv5!9d2=c m-}#8$.7zF1 uy|~yp.o\wȋSp©x!'roWvG>s/9D?_|I:+Ԍ&34  4=X|[( (qqTTLr+ݺS˹KHacE+9s-bCvtGAysi,? `Vi[{߉>&|pen s :8EbD9AP?7o3varJ[mTޮ`JM:]N.ei]b =PDkMp-)F"St`їnkxVMb[ií߃`N mv9 )"Ίy\+/SQҥPcOy$hsʌMvQ=7 cgkmu&&h1Υk/]ʟ ˡB;F^XSQlkTM/H!n-&4 ]iL%o(%-1ԓy~-k syKFnjr)I8W0% `ayՄ Q08(XY8O $tN"46˜MzE.bDḡ788h^ .l7qNYIG._ϬH.L#e0{!Hu{lآ@]e;$\-ǮEL` SB$aYWzp'Y׌=<BN3Ykh'HlV 1Mf[*iӝݗ~6`-MBz*EmGa D@H Gk CCtJz*^νk~G\%KkJZBQZcD ]## z/~_Vs_9yY՜0G"CGLtjtbʭ^3oƠ:w`MkI/4^f1QS!`2FѕAKo`O'^E2=e],- 8kVSzϦWcʯJ0PڐM!Jh<] vLW$bİ5p,;6lA4 SgA=HK(ux7̆x >Gl *D(=bږҘ4i~k1x$\Ik.kKhY9vuE#M6b8pGPk0<J'7ȃ)J Jhr:v<%(kР2㑗pr"< OeנnByH\GvE:NlDyyn eڳp ,A {(;OP]^ IꊟMT#]zEΝv)kW2y2FJz=*/DrOOCAs5k> Up{D6y*>noOMOtG$d\؆ &X=UTTSs[MJ\%0T3 \ ҧ:; p} 2B"AlTCp~VJ~OSyWl4nD}g> M,eVy Zox{_ɷ*Qi;~c(VURSm!jȹ* )ܸSgGx(GL*Ygr4n rpsnbsj/5Y{v3؟Ob"n󅦾w㙪nY!n,X`RM$\D 5]U(n"x嫧[E,I"\s*fJ/?2Q>IFg{bGk0V#[Vx?KtFeCꓧ }??ɺaiZ=ݙ 9T7Bl>DGVyL|Djgu+ H:|bzA D+{qbýJ wK!un#ߏ$mݾ芤." N . )8a[E'%M_tEm*Ť4]2J?Iߒn.I!-Ӄiu صLc]Q 8yy 6vn ea|vE$bQ. )3o:+ j*&O^txKmi~~6~߀O'Eќʭ^|ϓ[>lFيF<,p/=z5vFPmL6NN,YA#ڸm}챓U tVc0뤤^'C4'm>Tw~NFPw^RHe5=4H1_eb)hj'1{;hIn;a&Kl.`\?곿?l%Qz=q/_h^,>ۛmsO=&ۺf5Fl$/\u\(A]k̓/~:ͯ>5@9>0WwIYlZRaLlu=O5KFlSY67j6v^jta@WЬyo~%{-4u]o\[Yg%C]kDn{"6ޠ^MڴnCOcX%^Wuy쿳%rݱqv SHncQ=U|8W=O+ 'E[$mL{ʥm[3er:?>8E$_3|ƴIm] RԶ 6H IsT`} eZoh1QĤ ˮ"q o~ . InK떯7ܒRvAYq87=s++nTHE=-6Ku[b=0|?|_?4VǭN7k&= : 1(<]blNǩbn86@Q><"Q!?^K-6ƮŽtahw_˯k5#+,ToFVAS;Q3rCp쏽vmM,v]=~W@-lyt]l00Kе22=ntZ[+ ɯȺtfdhbZ=Q".G[ĶT_إoa&x6PJ*>x:=hp2}|KVנס 1x֯n-ovH'HmMyZAlglѯUWwB ]ؽ } ;@:P5Ĥ| ˔o]00yUC^ۏ ֦Un s[ް#n=Vq^/K^AtjpN!Y]Y^*XؐK"==A1*aѶ?|vF:>ԷYc "W$Ƅ&U-/ʙhuk%i\tJQ0D(2tݴ-7[VoltOj2 MRUVdWbH&,1lf un۴xg~fd7ܿw;򼿬}y뫽{%{%atjȵ[뾔5ڇ?EʙMNTRJḘw/_VBYԜ!p:D#'TKCϩP3f(X")PD0~tﲄHNTLZ=fo2=rۤul&1F*g嫮-ݢ`fQceŢ&q|tfsn+]W՚lER'"OUe2VQOQ$ۢ[I* Y^oZ]9u=+͊c@tZ2v[~jr[^iXϘEmaq Ods_0|]['|7eEfl" Ma~Ա)DWt|l\Bb*xly̫'+:-lQ i6%. R$D$ix(:h9w߭fWV@-)t!w-u(V )CtGl aSf^3"؃ǐ㠴(Pt22o]UqޮW殈2h5IVAS3n!#Jm'UˣSm$mUb7ҪhҖ"s3&kt%ںC껋&l;~]ƚ >=gg#ZMɭax=+5Ӯĩ6[|VEDݵhZH\J$$obfx˱uy~~su.oوӶԲV1{n=!k婏m/k y0 L nf9xMK}_ ;rP7c{{b"֦IMW1m<zSOjd((/nw]]y͕U/ knIQYޯ_c)m=F[f2+ЖݢEIIHX*C"t hIqO랥m-+7u\qlYC,O QFJ}~zCǏ7oZZv/RnV:o|c>+ϵ{&4u 5~q7o~|BcqAQVw!a\1Y⋜ x˹U0(c齠}Ӌ}{~ᷡqut*N6Z>٭?NV[PqG2ukXCA7l~9X_ٹinvGWl7F[4!WH6MjL'Rdˁ˜~xn-/E 90fukC Yob}Ȕ5)AI٢^2!uzUb_R1=K1l3ݴ.bèlciz1#cfxN$W ҎL,[&Nb\6FqJfTD/#gw'v[3=p Ħc5oY`a9E&Cf>̽:6/m*#*8xi`Xٜy:%5S 1ʛUUٙ39&yYcCXQ`b08oco5)6[~a cى s y(JM*V4K mIgyuVef{45SV"9yv:+{K1C̡LlEZlO0k 6tS?65/*]`*x2 ,8q TpjEbP5q-o̝sWi5As {]0]_ 8tRxS8Ƀ2ʐWvdGL^s;g՘J!ղ#=NZpU~Xٔj\cFq}z󃖎4jf{fV^XʝWF=At7@%qSh F'v2 uIXm 6o?_;>quFn< Gڔs/߶eNGI<@h05$Mk#5[78~19s,t7f9qh 6mfR ;BAaLA44GfnAӮ٤Ȳ!anv4ڡS6JAr8b,!2'P }~WWz;H^/@< g.TǛ Z#79[z7{Jfۯ6qgZ I{]a;Jբ_ |_i 5-"`wكfh Ӱپ_7=愸V-R$^O̱RJĶd0ٸlڑ%+41vָJ4'MlxvGIO (gVDOҎ4y^mf*?4Y4t{eFl3MtLU20X;ܱuLdNL<k0T\u"rlhzl̝jc:Hz[yP;)A8l{DɣTx ,䙕h5| ϙP+tZjCj%I2c{3 4@=NQGu|nnJ#X'5ة >jb ԙUY7^P~WqXӳ5Ě`8̎O) 2ȓ! B k2J^I.iÔ H o8ߧW?;Fy۹YZF*w+ALP!!枔8 V\šHMk#usCHƄܜ4$ʺ#-C]|Qq<@m&>WϠ*GW!>bGnjұBl;>?P& B:eV֤I<!cҭ41x=Gx D;Cb̩V٨RacU /ZJ@k&P 攎^c\ k5!e㣶ʠ*Q6*]tka)d)J Re5<_pïm;܃Nwe79,_~s~~G @lX2Ė (.`!*\e5{Ly}hX e"^!BTQi Z"5p &Z'wEkRf8H@aB١j$II#猘{fcɤ n>%Ïɻ#v=ڪ&ZP(&CQTK cw_<.29ˤ{}00Ql1M_VveAێ!ϯZ3U)7A|9Pe@PoV}ԋ_>\{kY5b\ټH5iWloJSk D B́kgZbĐl>Ij \] "qE 2SF BM5J>'nm48x=IY/ ğdG s1CLTɇՊ]2aY>4=ة }C@Iؘ KǬ3y)aVPO;#P'fM!-`刺XO}$%~[ yHZ}7qͻlg\y'5pQT#8X1E[0@: Q~AS Q4QP\R& ȺZC0U$NRBD5ae͎䷅ US(\XFHPU= :byP5ѪFGq/?z+K[oV,~kG3^9ug .g&UЉ䄶*G`+SA(jn]H.W7|j=͈cI7`! {;FfsbԈʖa c,;ԅ j1lXgUgɆ,5R*Jdf"NR٤ȠSNo%ڤd ˩B qk7^{pއ_y1|D4:`uZ0-/P0-gv݅9}5_fֲQzʠp0xr#tYH&IH!J>.#{y<՝3N>tƄ`.ȿDUʁ[)DJ-FLP1 Ba1"ݞ]a c@YohXW&B~hߘɋ S^[@  Xp(':y/ Ո|=l EֿE!Rc.]f q14V41(aךv5Kla_iwM W:U4.t}T`&[I"7 \x" ˥XVI%T Uv:DV%ّ-o``og5T#ܗu[&/|.WA-Վ$O|Pߥ|ݴ*UÃ:Ƚ>׾?K4>{ ["H~C:CaMDզ05ߕ0`ٱ[ s] -U !}耴f2kUmM>vDt4du$Hh29EG_#agk-2܏~9#S7[?xh{@pu_5xwCH_BVk/sxи-+.~hDLC{H[ Yo` deL;c2JZSP[Sh.PcE- fAV PZ5JC$(y^teA={۲ Jv6# T,{j%:xGw㕭 Keb\o=?u?w/YA`ޥK{c*. P5'|UgIބp,Ƭ\\Cr`Ԙ2mրS7{Zzی$Ɍ̌]gƪ.}ro|-Dg|kŌ GE1wԐ?./ҿ7Ғ@,[y uӝIJ-@?p<<&VڃLä!%e0ױuP`WoU'K^!XAיTFFK76ȳkgq@<2NaD@'鴘RᠿeĘ) 9-[0/$.gQ kkJq|){4cS ʖ|n!&ɂgҿv 0ʰԊ+ց`&=Î>oG5eud|M]s#BZ|kt UFDYҒ]:Z_L*PУoz_7x2zq>  If`1Wj)N'\ ӌ+O1'!H,~Eh߮.Ӛ}w[1KtR#>?$~vBY7_} 2j_l^U"j)3'X @eבYBĠGҲ 1y-wYUw |l Y)2} ?A(AL8s;M.9b0P5auxVpƥt N<(jw#g3z[={N㠑Ḅ9hfxD V}'߰vNc.X xLO'LҞoX`nĠ $ *HT׿qs !JE aHQ0<DCeEpě[,;<UJ'NxaG9d_e"K%ys`jY|;!97;Eb#6a~, P$C=F8cLwMu,%Fi˕'V ;n3P^͜!P{[ *[>Ј*EJ7P.BYdC]TJ@AK`Ad/`l%5zD, &`3D`YH7я-?}k͛rͱ(Q,4 {"~.'dJ-fمN!-*z瓺K؜oO H1&R{KR[Ϫ[{Əj_bB#EbJR #` H⒧}wCwp;a$! UALxd8kᚽ?=g#^>:wwN\%FT8V( $P&Y =.D[>5YzT˗S`XrO~>0ӭw>apXZziUsI>Mw-(x}t/;PRPPYwĞO9LB&fV2Lh8ؙ4"b"9YyPx9Wf YH)" YIɹ L͇~hχr눕 b)Wͭ=:. 'ϩ/x i t|Az`섬os%T1x3OF+o7n~qԺ=eKzOngB {NW *^tds{״'CP†+|.' b b1H`RVvF.D'V'JS2SxBIsL}D~?>Ux0HC:z3"B3sFk%2z gD  cxD\.%6s:e[K/{U$.[/Mw;g[^g}lԙ,өY^P(bR%47J!G3©:EƄd)5" ([C!xF9景z09f$p\C?Mߓ\J+C,%t#zwCAhG>k.rmû9hBnzWҢ"ik4SKոп 9wiȇцYs{$UqVqO]/L`buCfcX-~M?\?m_W~ֳ0ͰX*%LDm&'(xj5yFY˲"6h( Ia`{ eI11TU>'p˻rk:WC2$drЃNJ/;Y!Ҿ{1['33Zf~ 'mD,gٷU*#%$ $^_XZ ۢ|/xUT")58(uk4ꗨ99x<] ;&K7O؄Sӏaac)>yų,Ug}NO>m; Kd¹gi3,t4̱xc"̸s*cs9w);Дx`6U>x (&GoTOztؼ1;JsrNf>DĨAs^?8%׮(y?GkX K1H0D5N}'"R~c] o3b$ 4 ғ =RM)3%P($w Hܜ`Pq( c4fR[y=C.3 w F$Yuӯ+Y pn>U1 #e#O)T4arfg[xH2a:JbdRc#b/ OzbA@#ULo>g Ma}6S}6W+U/ӻ$ EɆ1l9.#dv>;gώiӸQ+P9pg@v>x _[˾+`m90v 7L*IWvOF}J~Iy'Z[%{pc{%ɦB7c~Kk6/a2Y`&5}ԍA}#ks DѮYGČ>Ԥ7#xf,|֟X&Agm1+=ff 휅70l+Le"B)UMjSU0ׂNLDQȘ1\V#VN Ko]_^|rnk^hAI 3ߡg޼>182(H9+YI0,B5\>"8V`߿xo~IԂDN-Jۑ( 2JBk>7kcHPY39`q\O]tx@$8rJY.ɴ?pQ|/Md˅t}jN+] McoܦƆMHO-PVkד-5$lbQ wRo]OngvLT \q/Y]ܴwZh(>q%FaUsG1rQhJ@D[|u"XdP ?>t9GI5-G^UCD3GJ땅gg`zz.^b,aC-bgMg ~ ֘.TxȫQV'2?]@C1%' ̻+lҷ|s yGUk,F[|;K?[<554KsEH$%EN,(DuV2(p#I)Q:sڝ${\Մuq3'N8PtyPpa2[t4=u'> C>lILT;bj %?(}jFU%Y4u{iY K;^ iOI Y3B38{UG [cB&8}9ݐh6AkYD{4B0Pdo ;SU.Ȼ0i_y^ZzםWG?kڐ6_sTɧn0!]5rȍJճ2I Nghw_xY #9RXX@jFTuK}Oo!Y(,>R+vU1 SAMs7W"YA'q+fH' 1Yn"o*3Gvdk㼍ACg;+hC%JeA" 8fJr'j8}^MX*hLAS3{ MNt+ QEw>bf 4o5f *VK{S6TBW{͖m#'^Ed^YB> s˒KB7۞SHS132+6=kj3awrglxIdmqZ{-DJX,, 욻6i"hhÊDeE/Y!cU0 c.Q~_2u.[X/_qٹF8iA<\jx7'99-XZ8NQ",k{M(:+ +9E0^1gef[۵~G&P󨪂rgQZ !Eplvd3-(PfB2/,A8u#k>R*"=BJ* D`[J:vX{9 W`tIKP /ȼHqrzŌzSH ^6r>N=mubʦmuG1޶GTw6-SB_F!^#ZNYl*Gwb+ϨOhbވR{6Z÷w P/ܦ.qS.Ȋ< VȦ=y]t"ԅy\ն)RF 9-?].:s7b}M/#yS|'-ZX?Frㆷ/cvxDX3GJ7D'7.3<5:근a+wwpЪ@0C bpjl-Neo+j͂0j +Y0JJ(1هhJd.}ǖ_nvm|dU8$L)RYjZ2imbmcrykvλ҃wy_G{\!n/WwJ%1|k1;/PH)o>_ޟp#J!ȝʍJћ> v:OD>@V/"̠`Qf2٨![߾n}NxPk@{WcllߧX6}uua$[IT[uO$FҹDOWG' =ļW@ݳ̜˳* އPWG{޾u># AK :EbXwҩ (5NhgNyӧypȣ|f@5a&=rU`_t#!ݑbv1o0ZpxX ÔhOltw@DT?#-hPግi3WL!s6!KDz(`4 .v+dG56)έfKH=f=9 ֙SN;I)y䝟Dʡ>An}MּE}><: iGPm˛[mϜRwM4?Ԟ?:l76jǿ͝M%"ƛs+U ΆZje6I5wBbiKD˳V6T aO#lgV6paR@`Řg6~aqlѶ(Ax]{9R=:@%Kz: 3ϲnnZ7}3vG-ݭkD'":쨲D(BByr   X~.~`Ɛ!bE0ާՑTOhɕ94$^:@rYIQ[>KF&Ыd=~9'\B /;-tr+l)UlTprsy;[ B{%n7z&$,X+UI;D{[KV{;K0ԊI9W*1"w ?~,U _gQ-d{5 "6ïmp+\y~ 9}.'ʪ:`14 UPGLGjt,L\|\ZDVS<,.5U}XOr$?,]D,іŗ]y˰vyˁg6Adɩ-c(ay}l9}bdtLST K0i`|rWxE}G+{˧:sx D |ZW}𧲡DzU~hFWHӑViR{F2o:lwf}mtwdu=S׼VN,sAӂf fIiŒ?XCNtBx}4?f&VVˉPP%dܾ[-gr"$*O}ћ"l! 00"uE5;JJQw6͚G}kEe} vmBegui_Z,hT.Ʊ53Y,[32{aQ:yl[Ȳ9 * 6]yz'%J0R05nDb6f&9OLʗ:J&x9Aµ¢ND = vut{_{u_{ٔ(>tmkFO`F4` O!*SB Kt ~elF#)\j<㽰SJUL,oMUǼ`TAyT8d( Yւ{*WL!GNn}ٗ(`moEDM_ `urv.qzQijm#+΋>kڲGpCT Äc_bGLa))$J$9L/,tD%٘&#F0!f<Bz74sl(ބFoxCv$[[aI1~B9 q՗σ_l okG%fBɺ%yb7+3Wi֥.Db1"5YVr?mq$)`bA 7}aE}iufnOf|&ץ=~ASAAp'ZE; pĊIn7]Vgnc>rgEʣ?86+Y UjOt(#Dnj_' W=\ɪ cS]t(&e u dғhW?A{>,gI>lͭ!">5[?[whaqH0^lڦLYh#&{Dj`h2hnyѮG<{sy1тyēo;cԎU_JP}Np3S$,(ʹ&xWr2>=@L 4$}TiJzx(Kilœ"Tr:fp +왛'SqPԤ^=B+vs`yQw1}Wob 5幣p7']WYJejzJ6'be Xql JfaG]m!x+QbRR%*2%g_R6;W2Pti)zi.ꥸdr*6cznԷLI| u֌ԆT<q+oQF({0g&n~^ˉ815cI#"U [))U1.w:9,UwEۿSY$*Bggi&5m7+M^(s*s$u'M~hFJ=D^#@ 2)|ȵ\C}Ԉ?ǎdiEDxu;3%DŽ [jL2C&,rV/+͜+m_.ϴ!~n-P'3$`ԛ~ٹ75;#! -CF/4 0&Ǡ*CL#FƾkEy@<~ޙpľvBhb}adUa{@: 0p ,@Zr6rtm :"ck8aD0) i7 ڡ L(dj4 %cGtbJ-'\3nXp@RG&oDfV;%@`;Y[O"|((!*( Z}b@=t[A vytSґbL[o*뢽~_շ0s/" T*믦Kou/='M? ]Z&05g#E1S9>`}:VP L0#*&QQ56)~؇`d_e+(kg\[`f1 W&XҾA$ .P& |lBԣ"@I_ 7\ʣuGcv4h1w,Rn}O\.~=(C^ծwscg*կJ{ℯ҉N1O*';`_ª T,3NgʚaLY&rKˆkEPK\F52G@3A5HR'{;OXzLCճԱȳׇ<t2Ld#. 3OaBz 1TaJ:JDtTiEɢB2{ n<_0 H3u)4$ٲ@ (ۼidzT⁇I;Z+N;e[yAJY)J:SYQfvH)8% s``JwWDIb\/au+I[ &@@JA䣭2@;fV#I"ˆM+B-:7_''#IECO8L_}7{@&sIh Fp hsp`Uf2Mgţ$WAuum"6X֔//S(Qcc̎')Cc{'tbنk ( *p@ %U*jw2Tg4]ɾ^ʐ)?xL"  m@ Jc_iYѣ7{WZlP{r$Ӳ;3M#i2$r8_\%TњҽУXHB Q)<A$L ܠ6?{ss3cI=>P'WZc+|J#HYkx;*ypϼՒ `G*tƢ+̙>oĦD,x}U/TV,@PAeNq{j=99I"v"FO1}E]&WDQ-&Ba vES9)\j'cvu,tKIJ:5EWj=@Yx$#-ID-uO ҌF/ܼ9#8)9vSPf2@bȚ h^j<ֱ0 I{ zyH' Ou;پ-x`;(*V04ؕ1$ܢq!rZglxM+A,)3jj{(ɹ&?yM['aQUO%59ru.Tx/增w"a(5n'f, }{fXOK&4R|D#~Xxiz~tq:ϘtCyB LGܗ3(nTH2ݗ̞Qc$<nL[0~Og_]?F6.z1,zO:cFn{ʫQ?kL[$}btf6"b|aK9T00%0LUl1iwc$v"Xt~SDZ.)? t"}Ngǖw;"ӖJnhCzΓosMW̙J3VI~R) {mִOzg\Iϸ{G_gXGE?,Zv-6<"YFb3d>P7 N,ަDTÊl^4G.ʸ͵R012nB 韑9v_/Z)̗0D eyI!כjw9'܇ @) V KWZaTJ,2V0()U3 !qL%zS=Kx,W?1fB@^OY}xLUlpj%fBq3}Q1?Uz=RQ,$pan2G׼_kl.|Ĺ%NՒ'z '$zLƶʛ*vsjΣ{[1bXiĊQ,fy@/ss5_M\ſeS1'Gu02P[ma1$aJ$;SxKMG5"{]tCszv"bk$#T:,/mhu[ѵv3b_oN!̘zAؖE׮Ʃb!XJ?]OƹзyQmc3'=ZҺ~DCb$O*62mbĊfF|/|'۲TGv"J &Bor~.OkUo3pJS%8p>bƙ״.:-2"6N7M &=mߋ3r6솵;p|cEPD?ԟDg~NW#-dvĽ-Lg= Nn}si[#2c!TbĚڴ)^ -֋Jt%9Wc  a![W?s#xNQa bYQC}Yt΀M%۟KI ɬ8zaV%\q+|s!8;G5IX@];}i'$<3Tpsʻl_mBIH0 kV<K-BJWpMa1s&  O8zA#4$c]UUW( x9ub@"! 9)ӌF?q OBĹd^?rJcG=UT7Oۿ*6s~86Cf\+~D@ݭT==g3N#A&ओN3<7Q9%#6W<;bLrsڹn̞!oojuM523dȔo OI9g=snc[P%dC3A6 ~L*50PX oG6>e_߆K! /JŃZ>u䂦͐M :Ӂ&#b2P϶XB@@>*茷e=b(rN⍎WWRXhLk B\K7ִti6 =MҖdCNZI֛u{k烐>);Ŀd 8qVk#eD'c8DZ~rWerz]j` a.| ' K 1D(S`>XNy%D鋸Q@@UBp}*{Adf 3/m!g2(P'Jh˥v{YkǕa$A1%aMŠÔO() 4P/F?^I}Ğ3QkUt :3[hSb(Y1gŽ",{cᑳfi}8+Cʨ&J~ 81cvER  .EKRUBs7f4 azBmݔa)q L=gl@nϬ!%2JSˊ7J (z}1bi&UU{b9ĂF2,=/1sz% [c:dH֡bvx+fY0p ;\96o&_X$RX9G5g E4 d]H-.$O8yPH \*##&5cZ}CV@PJ(J&g$w:|[v'd.Ti;MiL͡L." Rz?!`n~uN'7 2j oGrHj̐(&8  *v %ŔYG6sc|BA1)J<2ߞ bߺ$g,\.tq|~}X1ObY6缰bl ēw{Ț^':]<=EYb`@/Gd!W j?*;~Uxo';˝<Ivx~&0+b#TbeZP P<2Zm )}x QYD;P:DWzJm- nrj@}Euvւ݃u{l7(|u#,d07{į` { - B b k\p ÿ ͯ |5>닻4Y]2?^xJ]sb5m_}vgiuusl-g}UvVWl3\ *࿀Zҍ!ks *`LSQu+#,,MM.o0p$@6=J ^JK1)%CPOUť>~b̦ Mi_ӴX:-بBAp^1 @ ) PJS-6k%՘M.| ^|ʺ 2KH@d=O Q/ hP6{ ^7D" **^vlYbw'3z8@ h@LɌsd'9J<*ejJH;sy@UH|NG=;9B$S䇫ejZ2?Ekyϼy~E05]1S~ˀb%d=kS%~•~)%x,%MDDq]Tբ"m?PW8S 3F2CI &1^Kc"yKHе b2p&SO{iа&i&.b7LRZ^Bf[q.G "JW]ZdE/N07UNӦà0.xAV^[cD0RJP%;J &cp΋wMqUbfܭcBžZ[O*@L]aҍ'0r8Ր3e(j3D:xf6u(N\ nN®m=PZ ֹd.Lj.΂ ƂB(C9y vEɊv&ȧ+.XR5M}#1YO ,Q,Y j⮃z\y=L>&nϮO.e`.$Újj&*Ĉ@pZ̨ʱ,QNZ hO˪QUC<&5FB,Ċv8-]%TDhMEEK9qZ<&CeDjm+_ǎ&&8'O-LIJ7HҦ\Ƃܡ*!kтzŅdfD1b|0q+lO 1O#rbX*)M I2l|i:*l2l܋e"F3M)":,0E' :铣bnU_%$pmdthΫx_. D lp c=$p_Nf<ňc6H?9VBȱ3i&V]yKCa 殸()(zh-@hD@Qpj6C >xBwg^ة 5v f/Cx죇\fۗ/%94@VpE[]+ I]$ 1Ȩ Ebo`Azb7ƀ!D@T pz`0u{j\+\'<@5hq/L|٤H -O#:"2q݉ _RzVc!5TKۊC"ax? ep.AQf x C6 P#&lM{v$P0Z/h-i<&zʮ/>SGqJeK ?!&4 07<"hI7=$ʕɩm&*l#c;ׂA{2BM1&)Ҁ/ ëddAeq*+,%CNEiÈQ6ۈKZ'1L4ti0="z!Ĉ.!vY PNgJ8gg)3MX&Õp]Z²6K5 ڋT pẌ́w&MIfר+Z17mG}?=ɳt=ÉgeM Śq&m (fۚ7赙H$(ݎ׳Ѥͻ U̲ ?x٥0qC{b怅-MiIWVNm|i?qEC2tmٖl'M;;m8.%KD[  ^&kYUè񍞔e%,rqMFS @;]ISA1 ϡ50^]&1Վ h15 ?Ï5Ï HPr2ªN HO\.J 'ABDkЭ{Zhc+%p68ʄ] uQ~!"5v6cNx>Zg4C^?{lYI5-\&Ԍxr;.-Bh[i ^S{2'v1Yl.xF)˜0*9yˉc];MEqb2F4הsչ8l+t'mn)9豟xEF}b?C5؇=Em\= Su DN%)PѴ:y֬}c7\d%i9^U~x\}^v8 kMV荒 Ot7ؾ|y^YrhFvޡ*{[rWYKzkO}j*R@c ?;‡$b{DSQS~ 8OZr WχlPW^9U[[|]&x/XNS-)> {҉gݵ0:rX Ai[GrD2*돚 [,6Lgm c0= C`0}+s%ծ*'b3N1'ȁTT&y] ;㿁lK[CoGQ2K,H`j@yiPt'v·ιx>|tmXJzDEPɎ9>}8`??1tUo͸{q[m.^kw`Vh́BkY SQD"@r[MI',ΐOXdz 6r )'nors6}JC]E+D V0>冗T@hBV\OE * y!_t-_3i~nJj{A'5-10!_Ab;ϕ4uٔVCD59Eӽ'T)O C; y8}kch#oUO M_r"-3X2o'gyWom;,@0mO W䅷|24Wp?Iqx,U-)!<ڵ2rÉCWx0aou'3MER(pl?x;M)IeI1#v-+Ɂ'%ns$eut\?*Ҁ.Pţ4y"X%Ǒha6SLz lmE{DmrtzZȒ:97ki0r- ɉ:[\ V!:hB 2ռ[R(iҵctdPXܞ6-Ƀ$rhZlv~vTy6k9QQW=z, o*_=c*#[R9~<ˤy$\D~ PLtѸqۍ -NPYg o.6 ] 8 l?TS ٥߾ZS4>;귇Ζ5SdT}\3;>V!"-7gGOGKF|04KjoX ˍrœ*V"ԒJ,E#=_ ܻւ9ȑ6ʵe*Gq/sH:P?mZQ?~;oSWoO0^Uwjeɋoc$V+U|.̕pq#Mg>Hm]Wa 3O\#CDj᫂!tQ}ieģ*ch's^,V8>j+ȷgP)C(vώwbUH ɔ*ݧY]g, ia׷cSDyb% zlSY".Ϲ1kV+G]̼}q6VLג|__o=܊Ox9ۺZP0\[^/)=xTխp혴Ys,vEW 9%/> H-H1\V1X v|Y3EZV^WofЮƍK1_r>Lgd{2-BK)8^܌^#ZoqjD<|*ώm P>c`\Xd_'os芤-WB+0o9\:NJ2"MU-M⢳EWllk."mr󊺘bgs7D.&ajbϱ X4>v1=?N:&gΊruAREu1#ȶ]tERfkΛF'Ωm}& yɁ@$@|/AKh_|6^O5CS21s..$EV;W.>f\#6>;8Ze'/:u͒{71yQ&u&W7V_lHvqt^\i{m;)iww5M>pR=x3묤Wqrx_(OInnt[df4[hkcyyɲv-ƛS)3O)Y;}[]|ϓlk׵4U]5}pɂ-0FDCv>%~ᗛ`q}o_bII5_ߪ{*NJSKʕhVҬ%r%G\#3Ƥ:Gd'RnwǬHsivaBSGmE{6RP ;{+J h 1Y+9$4fINuKulkck `C8z㮐{NuSsS\?(vhyg?QkQ8u J d]UDTh4Ƙ7EhAb}jc7v꭫EJ:JmW).UH #ځ4!_<*{?Xs_gA a:Am5!xg!4~Пۿίg#r;I5$͟CZqv8Ѷ+Kjmyx+n36JS'w<4Î M&-ܢq}'ڤ6랗 G\jRѶa SΣo<ʇ39]!I[YS[ܺ еL-v,)H:P%XwGϼ]!t>gc5Tot[&k[L zOG\9Ǐƽ$la/tGQy]a6}l!ƲNJp* 9Ƿ:q;up}Fi)}׶?&;P";iH7N@,(< T^ =v$fD.u5mF5R P: D.Cܰ<XFƀ>2 =uSN7\7`]xa!0ax4Џ[*iA85xSYw"HZ+m8UNն# N@3n@b9,x>r:Aߨr2 EKzQo4GOo/L$ZK;`iˌ[[oǹ*ŮS<\#ˣ}殐pq%2-6"l0Pjmb=Q,A;pB/ρ @^ k;k^|h=\Hj]hݍyÈ$#nNv@naհw.5]kiˎ.tSk䞙P׎5tNX*@ fNh0%NU17\&1Z{u fpu *m\p1@P$5_]z:S:Xwe$q˞TVjO֑~IoIRnR&K4^tSL-;Eېt 85 Rz\ݦ9=|O\7MX]uys]#FdDtj(my24zlW\ВDQASywoԅbފ9o7; Y+L]pyZ?«eA\Ɂ&NU&j mI)RӮ^Jsmsη{PxGaTMn )Mfe1wOnqŁX:h _bUR/.φ-/^ZN3I5ڻ7sZ]-[5ޝ}~/q>|WLjF4җoAY 5:RCc{}I5!kxLDF߼=ϲh>H4O%' m):˸mztT7]T)[[}7 aa] ~zV|u`Կ5dz1I㓶Jn9 dF{F׺FS[~TdƊ̴0|a^%-sֱgkUX,M*2ˁ HʛE<~6ei PMoO|y0Drs~̰u%BJ!RT-fnRxlKlps oK|LRTllx[sN.=׻\KV~, '`!D$ܴFi5Mn:R܋QHżkMK ?xvj~c_,&Q+q"ńԲJҖzل|*rnD*ibzjR ,M.b5y([sAFӫ>1~$*eۜ.qkiZ4$QMeSL& ݴ?x=]ٔ;O'()g+RƊ֚bY[*9*EG0M$~}=lʃC|;d~Tq*It-'nklY7>[)]L[Y%Tq/./ܳn:퐁cO#qҸjw-ܳ5;rڼhX:2)0Cax(̤Wt=\x/;T;GV{*Cۂ^m-߲Ŀi<؆HD  U[(4Lx؈ ӨSv:4.R`lU%ѻٯRiahd,:[)VVxې]s=ؾ-{J<"CT-@S`:o[ɖdFSz\]/*Sd*"ڲȌ.I;ԲdQsE4l''ZmĞI{ۭRD3a=][nT(IՑU'7bSQ&~Ehifz.<~ szVzEk$٫hDB2+p O-YwlLclcHa7&/٥Z񩃭w-x+h$j9٭<,ٺhR[>ִXWaxt>d=㷕VOgvO4$dlO׬j[JNe-{I-e ]q`MIYNKA *a򕮠܊FGj1 QTJf:h%[-^MµR-%O@@JYLz9oe[N4dNU2 -9UɢzUnAE0i0ǀdVnn[.:/XFf{t#[xƫ^דTQ%_wi9KtF6{˵JU~[ ^0򲗼Ŝq~{ޟzoM 4{ǒ lW4m̞D-͵wheZTUeI6afȚh~ձXs\~dX[Vib[7pX,tڜ5+@Y/yJ F ,J0#&`i@ws~?[|ɋ_~U޽]@˼^Qy;DY3xw6mbclO % ~"^̦O\yENg!OmQ Ĭ ,oi|#an&}cw_>,1XcMaEZtܖ-[cexV:lOփ,*KI͆j߷ԣ/~@ srDWh;NqBD ) ѨRܮh͊yIɚ U@́p&cC62Y Zu~Y*7[b461έb9v}uԨ[#'<7nؙ+\{CsgSQn{c`NɘSÂI` @Jr1훵|}C?egoǨ?mN|gWWv?yymґaC^Xe̊ufg|Mgi]3_;YG;勴a-{Mo_x&k/p,ՀlRqjN k?sT+.9\E?SkmRKhpW%]{Hz2`\zA%ssКD9JfP3*a`#WIfFz `G6 @gQ c[(pCBWwt! PcF!TFeCȀQ( BHHr<ˈ0|-([)*$#Y|mSQ#F-g6+4wI@M|{j^ջun򂉍zsg^'\`]7U)ᤌyQ_|6{Y=Y0!ErizJS%SNa"4Ye$j;rFټC" ጪ~F}]xͻGmH/ȱ4?H {VsYi`xs/l\:02 VT ̈-b:v dq1D|Δ=#%Q&c'x͔&e #ØPU=4 w<sjDo)SV!g͛٠n>{NSSv PK% n*p}lA@P "P3OQscjN:.j46CS7;ptjϸg#VCk3l⦯OZ,W*)0T0B~_G_)fپj~L!s{o^%7W?˻џ,ww_j\@C-a#4 h'P$<=A(7N"S 20/6Fdcς쀒3%W!{)D5jFt֬yhYlw?PCr3ccgJ ޺56BTe^L0%RSq_Y(0NY4y?5n2s.7/ohw{PiNHUkݨz|"#X+jռr>\V w}Ec.3YEV8uNď֐$Q#VP Uvd -Pe@̣2=g ̪{ƀzՓ?PkvVĦ46PXIQ0c% Ex&e'za֒-X~oMs/2_v|`e $?p! z’R2QƳYQ۟1jYךtV36N(R! u)X# @A@ՁSߚ}TX2+>kGdq@3SѴXiPr ," ߔAߗV/kܲ0!FL5w]K}}_/|"o=7;GtL-~f 3,LXJfcQdjjqFr`A \.[O{kuZBpr ʌ$+o?v%COsЅ_@!"j-EgjO . HN2)Vg^yƵP4#"%&hT9A?*YAARVa&rƮs?:uʪ;@ b82tj,+&G6kɚFCxL/fb·ąD ( "`=⵼[ÔAA gH!FN"Rn\n$A7dwu\@FA2a*39;Ohٱ~"فuE`k&W^@em0b"΢}ڵrᦓ78OJwŒ/V"o> }l~Q݋}"f'Ȭ_pp,l#RóҦ1GSFN[klMšKX[Q>kbG bn!6ŒDIv̤,!% 4` $0(!RmV*)\b!H փ,"5HI2`0R/K=i+jqd&!-ZG0nXN-A/ z|gzzC'P"iE TTd\ 2Z7ި.I nԷi Êg7/ E2| G`G4Gbm\MR"#58#Vo1טRD#L*\)TUU0WF#|ƥќ;ӿN>m6+JOl,eF*9}- VHvuҺoU-7$~1el@^ʷ]?.a7K焎D*(E\뼞i]ˊ\'Π4u9OE6gيӘJX6I|AJ/; #xo1u"TԖMg_?55XF \3~C,)]rKgfZ3{D }7W0$ ut9N*XC 1$oY`G-`'&>VƜm H7

    X"rJIfa *;;aEie4@ TIxIҰʡ9j8+ e#3ol[o 5QBf*U"mI(a}Daɒc(䂒87=}>h|G*4pYrּ~sz//5a)nn`@ NV&uu]id'͊جVGc.쨏ATycgv|=I HH wE!bw׹Weog/~ܝL8池YV >sC'. )\(S'(XӨZvbᜳ5rųgI_vf>=dPȘK=ږ ܲi:{J2alY6`u׹'Ju_soU2';Z^;N:3w0`̲'A̓#]՝v~עswvC뀒=^fL36]O[t0v^ # 0a?4dYWa<ճX f p4Đs=>wJ(qeV|~OO&!t,>c qA?C W͌zxdU>Ǽ' ~5eGYs⢈$"7~ă.|I{A#{bfwG=J%<%xI>W@n7%Td r]D`A2BH2s(icFe|;۞ whfZ/VE"y&JUFVf/ Dl$>HB6ZJMf#ݘ^ OM^zs@`6ZGd2gu/DƔ*ՅtħX;. Mm)ħ)x{N,ﱗW=ZڅS˛USu^DCe#Fhb՝DF 1ȧ bZz" ?]W6ښ) R_gGSÿ|I gvdYN߸޼ؿ^g} jQ6y%F ޸}.mwumlْQGlm=~ʚ{hEVݦ1MתgEK]\PAb0㭹q-y°3FF&QaC&_nLQK.Y\I TU|KWY `ѻD&Ra ^ ;11#Ww ^P_6yɊ,j%2@[Ms׫۲cr˚`<@Nþn=(|R-rd鑞Q'lE [wDrgxƓCfFUezp(J;Ԑp;EIW#$8\>&WKSL1$$Pran])A ;yqrQt8'&}V|4?`j!ĈLg/ NjWvCrD0jkn'wm|{u9م{ ̼ $2}>>}'5?da #mhи6Z')H-2h/}__0/R@!B1Kvka ;`aLQ$:Ьɐ`~o$+Lj<`dFb {b$åcĵtl=刀J$Љhb^4Ogф.-+ owkNS+Nzkmq]q-lkr9>eMsJDHƭ∳:Vͱw/)D!4D"9^ʃ϶nLA@|Ps<)X2Ǥ-Aq5VTu&T'cf1KƎDRʝG\\Cᰨoj?.~a33BYf9罩 \2g  lYvW^!Biܒ0# &]JR ­r6x8z):>'-3Y݌7r EVP9IZy2FCE$gZdR88O`.5Nm5>cGiw==PTecr&uۆj;#jzq&JϬi8g~!/ԥ_"18ׯ rbQ {3c)0'2Is-EFR*i.]W~IyTY}=-@)]0P`Xj3%k/Mw<pL=A -M lAëwr>YҢo)tK8W\YC_f_JO=((2"V̊s=+9#84#F6&?񍌢'؜||1Vnc-}e @fb͜DVL  6q&h5~#g_E?[]$vy3@قA?8t*۱BŁyOy9nBMuWs_.8Yf8~70=:@ro'y_jKw籛 Y[%N;mYX{NPEZS'ɦk::o8ePQYF%paFgKѕdOV>_߁{ufLQaA58_k](l4KKdqIh縔j2BDG}l|h w;sH% v@?gӾJ#xWGCUA'!^ -8¸WswH&H٤,mJ2Q 2b9 ESתy[a9]"&Ψ`> y>=ej> :ʸ΋bH8\d6kZUmv{GdĨ1I!`HxPS"   .!vMح` J7(J ld˪q7)`򥝩*j0#5m] $d5s?/#uB*OQ t֘M99vs v#SvQDT>2gً"yЮt]mԘTF FKݝby>tꂉz{Mvg*f7mHVf{նz!h_;xye|PuЅ~nuG'kwdВM'To_r 3iDtU-k# ^L}g'C.U}}"N72u1o]:BKQ͔|Ơ]{~8/][֤CF s15fjخGDfF3\0E=n{yՆC%}%b%"orv|\$Y@:2bə++oSW6g˿|6w-}=0M29s@ /}PBPv0-ZT)BcP]DKlJ%yn}4B_@<wa㥀rHA)!JJ%=y}ՎwCqw>+yuq"V>L@ aEl zj҂wA ',a{j4#ώkγ4 %zG;??cGrIٜ4/ ãnWw\u0DjI]4hT$*eZi649W=7v^8LJ!#:D £B(!"禂 JH*fוU1-pw^<_;ٯ?$_B ~S4QQ#GtB%6̫e\?  x/vމ |ƬaCiĈhY&A\R@xT@-=G6Ƕ :1%a"5/}DPoK03^p-?dRӪ/I+}>?$"E #|e$ q}!;SfՀ'n`7AN VBs©NSD@ձ>},1G;9l|\1@n9 wlƜѬ]淭Cgn6_KE "Ο SS!n Xz!쎨T&q *^[ |_9]?wdtF;g?*В̀f  n(h>h,sL<{\Ou޴fG*UPX7VȬo|}N~PŌe JJ}8wĘ*oxzOwN#"H"`PQ6ր 0AFJp3$pyvVd͆lr᧮([+1P4Qix FZu$w:q0DrM;.ǘI(xI(IelܥyMl5T a@Й\(sa{ns5zb2{Vr^4-S SHh:sBV-`cy,W. 'EMdnp:8k1Σt5߃5yd態#j! 6B\;a%k=-C3o\Q?}gz~u6xq?k2cM^Ck#U|#/sTs{A zxY^]=4Ƽ69}65?Q'f=uZx FDl0v: UbfUkK6?lzgKQM#~:]NH.j"<&[%ރHePbLLex>oٗͦ?\k{|R3ˎ) z"ui,E LSdExCit^uY}gTsF2#pє[ʊ r|ʷuOLHi.|9e`DL@- "Yʹ}5a;׊N0$20D$dG&]$mKF}`b{  E(1h J";n&iK64k2d*fDg3 ݶ; 2>`,kq͍.*1=`pqT'.bW&q2I@> z@S,$ S'QzY_èΣ2orW=G\޻$lLV]T}AbVYGjN;ǞqHI1ګ +/K>T`x0RM'Rښ,R r&'?\f9?)"2Kp_xl=E[?Nu{{q 'YQb%1sꤴE#YH NFG؈ytv\*SR˒( V0s?";t<> L|j8` `6@?h=9eg٠5Mn 9j$B0%X2ˋ: Cr_)H"kudGOkkmD3.?/^CE&J+)?3P E;KH4n%_w۹i'=mJm|$m ޘ٧ {pP6p-шɬC3=-pV~ ]|ԼeLj< d%A򬢵ӓIv)b  =AG,N=N˽.Q}֣7z}~Bp:ɭD4e3DJJv_Q1WY[wt}Qi] Pi8sCl {8ŧs\wŌKpḼXT2* aZȐ$ü⠦ǵ em~(MKAJ.ZqLV}p ieyYS H5(CD$@P"9S_]o9ÇC/ YDscyΖ[g쁖*z?`GȬ`81N݄u^+)9rRdQ:Cwwi&icTCV*\<~y_17_'rӄ[@5\ @UB8$lbxˀm3',.'v 23\pi{R+fEw90<7k*p€J3sG`ǔo &Pfh@I3IkҪYc ^2aLek}v.-g qb'lb'ǶA&p^q)aKja198":Fa ?gDR̚ۼu6_ݣ{O2WH*w`KɞV0B"(Izn,3mLFIdBAEZ2dt {5#ZM^-ϫ*'T5O4Ǝ1~*sxi=~\$FQ(X;drDULoy+oKO>g^@ \=z 7Q%XU>{xä" ʪry}j3Mq&G[?vnjF@V CZfaܺEGH]_e@ TO:xYذY{FݬnpZ6cL7&nwA"ԖNɡA.35nOG$Q ZNZ(KfO7a|\ڳjJLShn\R#b񒄭&i+=O||=Z{vn2kt^,/&ē͎%-v}RAU%S,*OHF;E[mrQ1д88RYuXB]h/̑BzqR) xfe0=#JD!'/>AUMƲ]{/AWIaϊ7cؒ8o ?T1Tϊg<|W֮`-e%e:(K:::Itdlg,̑Bmz5Ɵ/+T mɁ.R2'FMmrF ogtYuPb5& &:@jGMY~G^~ d37A??bVQuf٩8&d& 3wotR21hȇE3s;x.s82A{#UB>0̮O*"JQrW+]]&4V%,[ G (~s*>xGFն;󼢩NXnɀajO|0#T-ּ> qc1b'* /9<W셩>rCh]z 5$zNriJ`>@BF^pإ̇W.>7sA[6Y8/fr`l7:Vo tv瑨1bs1LhTcw"+%!Po/'gg7EUlѼCE,mlb =esF/hZ]mB^*^esK+_= :|Y@e 4.l`ނ{;][UeoU:>WW~g#JGCfM{g/G6h!NL$1 U|YstXd`oaĉ6w8<)…Ҟz\3R`L| =f)uP `0ǟYS a\bul݅62 B抉3)*}{K J9Ώ, :rH*[|Ղf/Bad|@\0="4r% Y\ZrYJ}ͼ82%):ԉjEs8GtPYɵr9̨PXyRR`8՚2naCd2/2 0ĹgM;XUفk&Uyz+u|*Z:S 5 sf g$R7OUMLTOI/eq:2Jw`IVAT,FUߒzm.*} oHt{JTL(_ɱX@2$ 3Q8?bZZH@*N5?v&b`3ޣ27vWm & kލA\]IC9 # BA-<&)QFI J:WV\2 #SeIus_7Iv`5O26aD"MJ@ #Qv~˿3w?5[amJ[->Ou$u*Ջ(;l}QR1*9v^r4=O|4H1S'Xv>.Yty=y-/ż/yC3oPy>`#ArОeͿF!\(=_׿cub396ccαTPʢ:UqJ9u"Uq$FA,5OS i'T k[iтP98,Ϛ^ {rXE*~°~s4=܊"ZPH8|$D( HX9~P3ϻNS!N[h {)O2SA:D+TTdCQ跔(q,k-;{M873ks/Uq :f7 Av6\(OZ٩7@jQ|"5OhQ],:%=VτayNY%z=JYxtK]&b3]ɁI/ m@Y} u./+,80žq՗6LWS!"%RTNgR$)D@,)gs9qO``p.Mx'@kVhD )xv&Q] %HDAJR8\[E3_aee)V^!4392Ldȃ P+ʵ?O~̘S;?&ڇ[aK@&>DdNTלڅ0M|q~!O ] f*U #P(,|?Q^ ,QZxG1'DeRȞ~ Fdؖ\tbV|jP Ҿ>ו}D9S%Xɠݓ"fN+zf{aZQ3W+]_HNzlf2Ф̚cSiETV"OL16tĢi}9GqVgc?{9yx΋ V0OZtd,\G#&3v%"(:Y!˿3q#NcG `D\wA.< +P\MVU ڵ;I:! Rq|ymp_UM! C9DpV8"W鳈[1 +G̗w3uU#>;P-d6C;[\\;"?vzƃ=]fw< 'aq.k*.*!GB+@a0Ab X(Y+Xhny4<˩a %X( q@y9&J D*J,|Jb]Sz@&֍0@f$zMSQsSvuA G7iY@+;0ɂ$T8Z9m3aPeaB1ۑNn^9{|^h*DٗjTp"XrkA[(]q8N_-(I(@|^X6CMhUV3LlwY`csDA-^%.uW|f?{|n%}+OUB?΢XSokmy`D+`z(QwnVyYx-cσ\́P8\Su1;3F_|sx:e CS>K>=kͤ|]}d_S1hRdtS^$# b *wv U~{U\d]~ZڼʂJq"ɠ=(7!HP[np'y5aD$s^wk/q]<".$ˀA P'e.3z48d?Bd;eWj 4rfQ'&T_%Z^+8e :Hfe"f1@,27O 6DY KVY-E֞=h9f*]k!KQ,vA4d+<'V,  #`WSԍIyd4'VE,FάMt.m3&ԍ _.L< !"E!)$ S19xv֔ݿ]34þ8e{mnU{lw-d6e 2+v޹淩m0cU5UwfyW N}v`NOWwV ^JnX˂F(2?Rbľy;TNr3l-Kl& HM v"W0+zm\[L.Wŋ@07 3b-e^a5N]X*(͌- &$ fT: fk>FO@mD@DdNTDAS8a)`uOYDW!B !,!~Uf(l={%7 6JԺƬt9Ka-KE OjB`6Y|;K'/UI%TEbޥۣz'^-Ɏ3kgߐB _q2*jlGM8dKFM22x:̿5N5?eM<=ig8<6O94&g$ӳjQp=h3 "FfD똵2f,Mt*IKրA3CMeiˌKDؕvΜkJ<" 335_Ѵt쏞= 'i  75'e 7{ۓ9Iޗ}\В h]+h˿e+# W9枧my (\6%W.hґnqWM1`Sijxp"_eIV62d 7y+W|5:BɍvPpC c2B`F|ygnB7guKb ԐlVlA<Qmxilg_O|Dz.Ss-LR @DqGI S i'ADt֊>z ОDIFJ^ycp2$d(ogQ( R0`Ic/wٔa+x/as?ُ.?I:D+.V`(Zfזq\{Ok9!Bg~w3VllmW|`, 8 h=~|8Sj jbƄIV̰SWwjRyL`}|),6RU98@.D SjEj59 T_b( #Y:N9l eN  bf XtunmPFXm%acXBcaZpDKڀ]Ο#_%ElhBȜ2x= an`#g?x?seU{\Lήo7d.3l_̲O53 8d&:aI@b3yuM1`750L-n%N"ƴ L;~ɀR@F<#ܴtѠ#bOǑYlT&Hg(j92$lKm`8=#`BFƂpP GF a$`U!MS#…H**&|A[;7Yus&\7Zm: 敶~x׮ymQVZz\歸i~bНSa;59y4<1TEB_}1wSOMge`H)$@JR& 2e6+_7o*CT]k(Pa_V0ֻӬJj!T&;2ϊoNi'#PvS򯭼0tZ͟,m^5s-]0!,%Y6\f<]ݮhWyJTH6=Wm0UZlXY6*cDPTAb{2DұZMJLa٨npԲôB<ǂjb-{Ԭ~yV/!倱vA-295S)E䩠1$V~/;]@P4mq0S"A.{}vO:OH,\SP0 r)A?qriy$,Otg?~^f5g,0@FQMC915Telj4z3qxmƭd]gOM;gL17(-9{^P$.?!g,G$̬Ad$~FAhjfm;؜P7GZƳNԞ$JB YuFUo@|)D(8AK0cC*蜨0  ; GZd} /@쏩+Yϑg:W^_ˤk^CUF9x{h6ocwn{;Go9u. Wȳ>qitfVB&3)*jK B9gsZW-xA $@,R'=gh TQ` T @Dc UÕZqDzwoSy hh,';%p% grS2.8߾[z~'XvBflv.h #؜Y ~J:@;6uWqA7pS}vͿ# <ɶ؟ws\[p bq: *,( If@l4n">% C$!MPlGzY]d )3cU]4"` f OIHc5 6*SHuhq299O|~jhBLJEFt\ DDkdrB$D YrT aɩСw\)DJX|霛S3z*1lXg.}֎a17S p K$2γmQGq2I5ؒŒBgq(kۑUWf0IZ5Mm]7o gl.b&TUK,'~c≟qg(}eMzM5O$ؚͧJTe`HETEU +ljB~t/S(d "үբf=aܬ>.WŹ4eM{2fۇº-~GƏ^?36.?b×%\ U{ ؎i6 &qH=Fa$o ?y{/&M{Ry1QJԼV1 |-rXH\"2?ݠ^gNi읦MdYs+qbM7 'UAxF=5c<="aX{R5Ws3J  &"c1;'iM`s&!@kyʑ-8MՊa'^* +=0GHbF WG9ŗ̝ɗ1f8ik`kf'R2,~) N uT2܌,T'/)F~8 E0e[$Ni2 ͑?4A))/mުXԂX3iMnIC쐸:dys)"tB ukT g9# eq6UqU*1# QQG`yis w~6jo; WF _VTt "t4Q$!7 .3>#W| AV /XF-)VUYMxV S_v~T檺g>]4?-2MA2Äh~@E} 3}(d" h@-BǂtDb $c;O$!wc/adz;8:: J2.T I%+QSfz2W+G+Iڧe1 ESasER0Ez8x^yN=J"<izuA13/pUT; .@A<`nI6pAv 8q}BverNnM1-5CTMqrw"|RK3xR*GSmz>).| a`{[+d+kZ,ĩ=JS4N4Ý@(1xh 9*xիWM;7L:򫯒xjK;RsPMZKKfX#B#O]'NO+E1JF'fUw _: KtCaiKO]oF5[6AWT* #XZ+{M]>_Q.Zo1ZkB(EFEFD#JHlGǦ+ z_;fSIJ(;!&`K a&1VSTci.jN@y-dcO[h1~Fp)ҨDDͤ%HD=퉔+::L,DhƖsI9 zYҏ=c63<5T2ЭsѾ c+˅Sإ][KsW,Z@^83c.(4`7Ԟktz)ҭAz1vxQ $ӈ5*!CK\X+/SۥWPa+)9mS/ə#' 8䩜P bTgR)LTL\R_Z؂eEql.băz] G ;^7ᑷof i*M+3Z)c#D""\/Z-d.bDSd*XU͘M5MZJ.c&Z6lp+F1|Ph<&Z ,rĊXG%<7$ѠtJɥ&Fs,׊2Ֆ9\ƈ־<$UV]sxJSJ b,3柙8Mi7Kӧ8ܩsB(ElыF#&dM{:*KyХp}ǧqMQi DJz$p<!ef"R5LGO>I$P%ڋ+nSx1e[أgD2\֟ꆖ O%QB†{GoK,e.$`i( 'N$+\I . a;nݓ|'ʠNK6PG^JwMK( 6{ Zw۹#BGM*nd_:d?)42ܑK1hjsjo.S&*WyԔmDo<'WDC"~*CۥA+<WQXѤwh`{.-2}R{[78OEtb8,4Ïq7=>l{՝h=p)D"O뫊N0ngϟ3ʅ|v _I,iȵZY &;|2جW1S6yps dE.c@]jj- ”lb"ɵS^:I-s a7\ Qh:>dۋƔj5)tj]B'h!RUuH&Z9̅|†`Bs%0W{f^wOzK<ԁ}d'kȣI`z\ICvIWV;:`G*U^R {<O㟤qf6s% =umJYKBr)kvЫ0ݝAvx1%{0N;dLSjSĄV&` d6KX?r繒/DmVfpGU{E4ۉIC-(G~\;FiI.O,C`py nq+^"axm~2OFfI*nV2s%m|p9ÃB>J'*Qޙu@1nwxg?6 SD>cN@C8ϕ44 ]bBPo큩vbj!O(5FDB'Rh`ϞJhLB|<T턅RR@QOQ(߱!u}>=;JR#i5]ˀQ85N`C-v繒.nbQ+vS\",&ɵyR)$P0ؾt繒[ֲ*;K0SӅ (F]1u@K?xp5}CSC9AY_zL7!G=9uХ8 pO82{(6U?zkgy΍06|ps&XIR,b ǔDt tY#wjzCΜ窅waZ8 7NqnI~$d:y V4aI<ϕVgD'("D3KC%=kaqX.)ɦ [J=nq⁃wᥝNۿ ayLp풎lJEĖ4Lf0A7s%gu=uoWZ= (9B6'U_x;5zV^o]# hr{<+vmճ`+i,E੶ BBのw;X}1WUk?:y=_*UulO?$1w`y%eBX V-ՖϩЩClM ZK쥡c"A7.;MW`DWw8 p_4o74ߍ^~6--@jQ;z;M*h L^91An@/Pqby5"܂~gGhA\аۅ߾5egYk*bYQ'J'm-Nd9f5k$l*i1P, Xd_׸~]5ͮpB/ AE]C05_tARN44e.eL8m/:s.y]tER[E_tRR DC,PpGqISp𢋆:- `GeIm3E$t.+j . )'}+`utwgm^tV]Q[XW"d.H. <# SD'w)\9kmo ;QfNƟ{uʐ)ع芍SUWD_o ^ۺvںd25ٺvļ{|fS$a o661N,9~'cM  !m~줤mObY׈B/]w۲4- tnNrr' Lƭ됶7lp8tMimi'x PO'_'4u%}5}۵CX-%@_!~<dׅ ɻQ'Գq_2>HGԶytyg4p E{p~`(FW箐~OGlLS:H.0k;bp/hT) >{mZU!w4_9mET7r[Aj^N5UfiygGAlEXxd󭻛]YU$mQdINʡ?{"Zv̈́IYѪJ["Hwk|}WFխdy,jSrZu7m/oY zNбMkJ:iH3Vq!e7/C|evػ8rV\~+Zׅ#-+to阾mb+tz- p,0I@ W/l|ȆYՖcpZW^qb !mQ2R=(ۮW6,NsUȖ `9;lj nEہd?|`kzIO7]emQȜ2uh[PE[͘vʰ!ciNw6'ot*;*+AI]aJNxGe&)ABg^&'g|qtL6L82{3ET&H@HH4 UzR]MjG9d3nyga p@@^;>/3^QBpxw}D51A0F #D+e00r_!z}zl M.D3홍#%z;b2j5\bzWP16Ze6iL3mJD%jdo^O! o=>qq7-ijhK=Jkكry_o@1j)ʻO(xF6JpI"66"x efH( J3d+ǗYAMYeŊatݍNL8~V"fU*LXc(d`=ZҨ]En̳VA "(c}̰C(E+S[gG+CJ/Eo],# Tuޒ%.aO ^rh<0SUO F2[ѭ0"R\22NQh2b.'[b8KLPf3f=tg{Y5fJ e Bzӽ;UDEςbnm~LNIs* 1Ψ}i4!^NռU^MC\d$,r:ϩ /#IK=EǪAh&S}[s[5n7D}P&ƑW l>g`^-@Y{L sC~Ҩ)덞3 B4coHR.-{X~檓Wte+z}tǽ.r B$1v]@<#C~g'dz>PZxsF@`V W5U+j0Jt+B0WիjxÔ2EF$iM8iͦvLFcS&˲7 {m걞TCO^ҸeIͨ2@DlC|M¯{c?xY\kKIӴS?"nD Ԁ̦Ԏ. TR/t(7>j`ZzfU޹RJ/Q 21[@2=ĝN?5ORI)rFΌѝ*1o?s-+G},G{_ӦȳK&f$=zk8$6>ƴs卿#/xؗ{fLr IDFüǝx~&?yO|n jUZ%9^7EQo_PE}UuDZ✝W9$@0(xw#\֨CAox Y41#javT98bFMAQ>LTBzҝu;lׁ*p^><ٶ[c_ڗyeR յZg}Avj ʹPT静<69R!n( /j{&wտ8Aj( n켢y ɻYj He@u5\ui@T `srDʏdVPY85QI bY*y/{ouD^,I?s~fۿ֜'d=iPx\'7@f."W"3iI2jU >i {966[7Tv)u0$(AD M!ʂBg8OUA zYkXՒR_w&e~Ī W|%W3;u9`$#02*͎+ Ӹ:bS0k߉Ȝ[~=^!ҳHߜ)6F  WSG .a2__οI.Wo[=2YAd jp+߿m3ӸQ P|̡n9r5%P`qL3̫ 3$lYj6%C1JlFeyHQ65sKӣnZ7*%0IP<4TjCpK|)B Ф!rϚzﱑ ¹C4f$ *mg;{ƒ}e:PQ )l,-$DLJf׹آ.v$P-*uCYQoATv¨ST_eHZwϺľwG>&Ɣ3حBIM==eQ:wl˲Y3s6U8\LvItGDY 2Ad0HU AHH͆ȅ*'GT=*=#Gy{A.\fEf̫˼o{hZ1#EI b7xFu2 8͵vX ^`y&s{p^0"x6[bE_NNeHRs5vGiJ\|6fZnd&* LE=:3VÚ2#Yt^?=K'X\49o*umgJ): Aw1w ',A: `@Ve蕥/n`۪< $`T g 6>wi5NdkszO0 gD)FRMw>{ߘun'>gTjY$WӺs?Kef3U6*;p ީ,yY|+'W, ĝk XJqv8j=}#xcb0 6!&&̓ĽplEx E jzz0:Cd+4Ov;pMpȡerr[WwL}@\OT TAɓȰeLR>i%h}]& |_xT'}B^ÜYgtjpL. DLҧ~G]Ǎr7\qN;Y|QP3e5Nf2(K?+jy\o\iȼvoJ횹7:<WϪ:n֌4(|Sq+^('Eo֛.{7%*#0΄0!b%Ԏ5yb X!lO!.Ltӭ%w+.\؃;iE䬪rŹ~ډv4r ,n!#s5TC2ULyafiD)dOu;^I2Yy %1GB5Qˇ:b /_`DǛGHD тa d|I%`aѫ/` &;B]3IԐ,dɤ<F|sW,+&~We݃YuEɜeB` { cCYǿ-J!f쌼q/#褐T 5"T&c!.$h{<ͨIM>WWīm_W,O DYN(D@*j ܐx!Z塨qܮ>7u1ͫA5'l((Iǐ%W(UdYiJkFEY` V~rQqa o<# 6u„I`HDN5򫱪C,sAJ}ZPL- ɜޅbV=L1k}#i@4PJyNqfg[2"Md &L!i!i$Q%DǩMdGzH z.Y{2daϲDIe%WC?]姕iͬYba1;LObuȃTV-[xBH~0D=#Qln~), 3x"៱)x!O+n 0w92|WUN6jҖ1e~Z5C (D^h [NAZ9ld=Y*9o +)@'ΰ/ݡ||幰+?v^HSiuMkm= DjToa=[', +="/1HBw:/-_ڠ Cď#j)GM#ΡtjK6[KP_̫ 6 ÈILyR+9ӥ36csGrH o'|9@ʼb`\ǻLO}Fk-8HX3 `i"FlcicaS{瞆D*ւi5lKitRufv7iҫt/q-Mu7+C"vU3`3> BWv#)iIxG"_ˊGdd,H2$tYU yI5O~f(1^'PSOI^U_"$51H+22C/A= y [aN9_TgE J%`Wa"nt ?\ ܿ4P'#Dj*DSp\'b Qj,K*#44.ad#zb ! &<[R|}vU5~7ď )_YVU Oصf:8L0@yPwT`vo9Vl.ZЅ@ڨ}] N;QqӬ5OQ'j2- ) h~6 X ^jɖ5KudwX% ѿW^wXkR+r`"Mz4~AēlhB3u, fVh?Ta5̛[d Bur"`@M?,Nq/ <肤lbա(mtdesktop-1.2.17/Telegram/Resources/art/emoji_200x.webp000066400000000000000000107566601326245125100225200ustar00rootroot00000000000000RIFF#WEBPVP8X ALPH'$HxkDI۶$Iq3{AD-Z,RC8~X|#Rb*^>x~4<"# ǜ0cܮ'3BrPe_<:Я=ԃg  o\| hRwk[w/YmYZXE.?zE]A7eY 7tշ-Ocb<0nf?KQi 6 Qͣ 2GT 1mXRR=%fMК@!rG9iI{I6EPeփ4[FRԖo?O6YVOֶPW֫ŧXS߿NFg4MˌdĨnF %'^IڝN--↠Sf|5+xd~H`}\]ք#'qW~mY]["GUY$nnEW2k??y/5+N]w)j jdzAFr06rl`+;s^*+d[ߞ:51+) /xFǭPWA?d&/6bBw$?+31O v7!vA1)5gRm2)lig\uSF<}'=˕)a}5;H??dEȸYϦEl6ٞmmvG%{Bn95#RY25tl8\.)=d dȾIu~_[p]̑yot| ߪKMt/NQi:*#O9ezdlԧ-bk@ڑ2bn>R >Α,5hvQ1AC|sk$GcAŇmNm\[67燡~>f[wuN#'){+<_RlۛBn!dHd!\[Ư3`} =r¸e>z.͂N!tRmˌq%' )6k;q~ _ 95"9os0iCe4-[("؍@I]Eid@ŴUF";p(&-su+wA}@J[?r+[nAΣUf ]Ɍx1ї̄u֋),4܇C|als@ݫ(`~jګ<32[]|sI> ?XO QR])#glf}cZț˦Da)آČ2s_^ș950rľ5Ԧ},r3j`rqʯMǢ բbdxsx] FHoe3VjQ *^<˓> 'AtԜ9r0Yf\܅F)2Cas g bL"Wk^?ueYcs7F>bYTsEgeS`±HҙmSCR=Q/OfF4^<*54R׈;էi{vf0䙙Lh]]ՕE{/;*L.,)aCf y )3EllyQոL#2SS#}騡1əJ2Sd-♆έ8꿲}ZGOICUyL{2:-~SmP1Af /3cl= Ia$nYs's78d8TDDiHX3_#9t9)|I6xv^j?`z2k9.~3-F)#SJ1d3.ӐاlEDVBL-F{#1Ґ8HSsk$gc d8-hka^c :2 {{D߻֌ prS2c< Փ0\W;dƈVe8meeȬMO—4ҌҜ8m~ߐeQQ`{!R)\iv H~Vcfo"j[޷+kVThF=հ*{  22Yy8qԓ2[ "|T [ :əOömiXkMcX?CCRq{W< 2kl/)jY2c7ԑM<< 5>SϐY)0_H~V`4m ɴy1s62EVzɋNˋiK/edʳ4^zE=Vqh:/[*?ɩχ2c$MA:cͩƲrIف`"zz~t5f )O2c(+"pL+ ˌQPvk]s(͙O3[5/sbާ1ƣ`Ne4B8QqW{gک!]2c"* 2c6fX! 1n%_u1i]}1Dm+pEC":?McuDCb^b`!4$U˧la}_OºHWYnӱZSɌn0SC#gFr0Uf $heqs?! C']tnݰay6Rq>l2rYq&iZ }>PH 3t뮯:o˸0Շ0q1rk$ch ~>"?XAg5]x!q|izj(]ñS/eӸPf:MPX:Ht JyOLsBxS!C_#9s(_S96W n T ¯ʌq'H/e$9Im+ϴ$9ҙ0?~}M#`/,7`Ѱ!m,ˍx"q Hj!$j[t ^w譮Jj|,ao@4ԟuHcͰdLffٌ}0oMzwQ,g\xG:[/ɡX0NGzI6)IZ:"2dOR3ZlCwue!p${\pA0󠺾ʸz/ [П28.tgE4yZgೣN:Z+aa<7>ÔQ8:5G۾ qԽ%>Fs'2rPDUo^'sSϭ4jML:+y. qjuI|Ng!&x{Bǫ7SOaix7/"@`>}aW(? 33 `!<@]ӏ;g?F!:2$dE &aOΓZCyOuz nq;T}3oWԈ~꾗' \ŒځoZÉ"EmN"1Z!|zpVxB hYj)#8?=875:uayע O27 0ɔIҥ͐W@s) ralABfbM+u8*Sroփ[xSp  wtίy a,9嚲0:ЃO2{ +C z;%_GPU ~޵Gv5nvOj{Pr8vW}(ydmJbc/n/XFy?D>\[M2%v5!+ߖpkXV@ؼ#-!%ӑ` r%71$M'` Emd %Kxkrn F~ɔ<~bo~H;b"bvRL鶌<#5f[r~*d .S:ӫh/J5H0)w6 €8D[`2D|<\N]hp؈ Jѹ=IWC/j?\;p@k/&?R6mZ;_5??BaF#^8W5U-'^evo_B\l|2t-$RgOR\Bi0'WV3]|<^^f[R0,MĐ>9_h`2φP?IHkV9jnr}:>{lyafƿC0Pz e*tTyBEZe[W To?tp-x O-H*5r O;\V|]uًǗ7OmBʎZq'3Te{.tϪ?j3v͙/9h@I-&5'nݿ~hϵ%=vGGmS*4;A)c~ ,[-&CS-a|4cjJ"iɜ%~A'(NXoY8Z:lE,Kr7wba{~}勑y!,;"jp1P#cof9Nӛ'_<|rl0չ2~ fbE [XDt40l㹛iFGk(7󲡲߽N_Ȇg?S`ܡ|ցRF:%QGm-/=!(aJhj&P}geO9OF ü؂j31{ڹ װ:t`#/jF!QIyI`E ?goR3cٜ?oɚ;0w&puWBHGY> PDtX#3[i($E4 ~jb1?tzu {#n~>9sě_]u5{_zGeSN(oz_~t0ff$`%eEo0>HKd?Q~O>>0t(6~V{.2 h('3EcG>(3ŕ ~P)Hnފ|rl)͘G.΂a*XTjVRNw3\cq`eD 8f2!h4#x* G ^ 椣,~4kLh;E0:w+,%;M/NkXb_c ?,)VWXn#<؂ưsX2TiF?Kn:ϱ}y8!^6t/*[6ACV_8Ϋ&䤟,~Lk܁IDL}FC}93Ee!^eGC¥'[ (.? V3\])UC"c!hݎ!kHpdDAԞkk rj,~LksGx 9LV?}y+ugx'ǖ"58ѸKb!$ F$0WV(# \Cc:=x?`C9L s~5M`XZ՝h]qsQ~5ٹ9DǪ /7/\95y`cUgcK q܆S"r3jBxeه<^R"Vב0oԐH,VBo}zð2L (R@IGY >g`tzcRXUenϰ܌6 YN'ǖR쿳Zr*w!~C9x|x\>eXp#ļIC"v' 8egW Ż)E󐾀G n'? aF%ȉ{=F9񋏿6KN))\>TF ]g[xD{g'g>S 9ѱb]4s~YxˡS/#xĜ3:2VhX d:3$a FW~:ub,F̗}8ي^Kd7dtx 1 8ʵ,0~19ٹ#Ycy+DP^2cG{sbb_Xǩ?hHh ~\e}Wt:3S& : .3Ї'6r#Чai;}+1gYc; }q1$G:Veo'3En~t ~#>9̓2=så>7)ADၾ% C\il/'4+/m 0$4Rau#1gZ;vJrmB?2.9.&?C'7^O--WS3+[+ZTu8Y.RcǓ'/ *ry1wu 9Yu'4/74otlg{"9VelQ.!5zعQ ]bDfnB}Lf daב>ÿ(2VzcF&ף9рT1APUWa*?rݦf&L3NPpd\xϓ$}fƖ0gO.ԛ2:b lAAF;wbj ȼ1#?c.3S8¸i~H""2鍈Z s;BK1qtdzdʛ4{.3%GUW_56_sT Pc w؏YVGU9M#HDiDHcO3?xkm^H{fR)^"$x_݌Hj8'W̾XMǬ![䈑R68HtSa0\ u+R7qn#kSrmG*MQzTHڪFl?P>/9F㖕LtH 3nR"3';x Oj.ׯó_u7w<Ț RzMq|oegy}*^sy';]bmf(Hy3 kABmm&P{*+TK6˜Vᑟ ,3~nEJxEPGB=\iOj`qq_W.Y{ל9J%ՓֶI?|(Zq>ȧշyg{Tт$Lh@zaZ{.&T'V|Q2'Bp%Ycԑx~A {fܙyǞuKآqs;9}zx_p<62x gvdW?K#zzmftl#?_yֻI9̸+FZ5.yjo[ [@0n'6Qe9ň+٤6|Q "TS s/P]~|3V}QLL̓/bc3& >_sxo}-~AA]&ܙ]$ɩ2?ˎ-jPnPj\;;6## *^'1Nz曣# q`Q}iоg=Ck_{/o]/F@_@Á%2oޗ6,n{}_߷ οsbi<"޻Kʅ44{mϒذXW]t >!uݳmAP$yTh9h60 T|`_7]{/eߛؤ?pQ0齨0CPʕJ)SB*WZRB~l GgHݟu~T:wlZmVS7[-߲}}%En 2|C TYRPI%/ד: (83N95,F/@[%f2ef򰰏ϾIb* b@$xx/3oh})EeGw 4p$Ar] JBqoPA/CI BQp )XC6ޭ<ϗ'waBh =`@A ^>PPbz=0N`gbUmHru"ۺG6= K **PfW mq1(QѺ_B6ˎݸsaPO{#@,jұ,KHx 1d#rxmK7e Y4ñw730.-9f_y-ģ  NNI7Q =[[lTbzQ~j1flՖRT"B\`mȿB6,d2\zRƖkY~zC 8oTl<8UZEl՚ʟ }_:&c.,*s>#w4ZG F (?l!\FsxHt:͋X҄md="l8~b>̈́2e'Q7 :RV!2W\Dg+W z0.IkMiʸ<4knpœmb˼6TS ړvm0i=f{T A:/wWC*^[Ɖ[;IΛ)[S0V6BUJґbT5zS`kT.\5)DsƶlDj*DT]1tHɲBHM& Lk+ ]=\ļs;fÜs3ZԬCd%o5˓6%;l+BNL(9 oqfd*JR$Wk&+i$ri}?dGt&1~Cyh| -3֜ךr+ϭӶ%Ϟ=<B{H7e ۗ|EpRk{ױimaWo_b,DZ(w8mvܣ/b.^^%Ήc |a2›4xJ/ϯؓEHމCm`xݞFa4Г&smҼ%]-|I W6@4TO6D !\[HYzHCGNj섖?) B7pR4L|Ӊp訞lE˻FvF ܑ7H:!1|u)6YƕסM+MéiWt(+~x\ə102T*lB<<<=H/;L8}a&AhYM`datÎFa w~kґzWoR+Wg߻2}3%Z=enlP#QP,!=g;9vǯCuxۮzeO <՜Qm;:!0#P̶r>s~'/q*rѿ"cYC/5/Iyi@f"M{*u7M9zSp5wL&8޲E Ђ} 9<_StC!Сkp}|%Ʒ9 {n#H>]ZQ#"r'iUN͍Zetl41Dmsa Y`2Â碋ui8'Dc4z ; ZLMY.1]R{lm fڜ|:nK:sv\Z.=s6Mm`vvR3gӨ}V[3 "'T&"\\p~Nc< /0n0.>?;y`B>bx5J0;ƖM_(r^+ec+Ԁ&j[Ӛl oiEcm+-#ɩc2Lўə?yU"nht&8mFsҨ^AC6'|;b{* RLhCuƹO󝤥3GMf nFXWDmk֊ی\8ͣ-}:t>zB%gvQ;꺤'ovvӕqW;3s,jݽ}xUkM6B '!hրXMXhY }K0|:|'H)4g +ԋL񫁣c}A<-igt zͺScX벖B;@$8=ki׵937ʢ.٦Y6B-Hm||T#r7c4$v\ s?8`' jM:QO)rs!+tR5Qm]ÿo-yUX/ED-ˡ=Fk\4C=[q\:\/3Üe)9|m /Z!TL2AG/&fy}K~BsE]Z=JA1b*3HKf{fn~>6_$.NX-HO0ԑt]љN(M3q94gs&ն][$Ctȟ3v-927ݑRpZ4SBiֺ}>@gJl<1TzRCo3 CJ4,3<bFU/1y:E-|lbI&<lF`ƤDNA>z>ʑ(e λqQ$GF)]eg153L\ljmhN^VC8 'b9yyR88`E/ jәc״wFq >Tq\54CHYdΉJ[ؤzPl= ~E2SMٖ)qh+'b%tjsfnQ,~Zܦ mN.-o#.’5ZE)2͜!`& Sb(3ŏ!ƸKp%‹2Ul(n[y3^HQqK{= y5% "D$M]߅ [[lrWڇ=s= ΙT*k>oEnS}:ȹ]ݦ+F|6 B]qҍ@M{zzw& =cPtR$1NՍ' b l+ʹn=IFoƥ] F>~nLTۻ`͔!evcAG@;W/ispz&ъa9&927݁L J8 v[XEzYj~~6S2!WQAFx IE=&pRO&mUWy|Ƥi/E)MҜkwE%Eʑ F\ͣ30Fs}3؋YĎ?j#pp/ MuY0p|co2Td̜V\1a*YZ aծ%:gm9FT=/mpTtpϯE^->~fK() …)bg<|ūg k3gُVss$l[wayFHCmĤ +r !.6FCiU¸`D^+f GHO~=X7e e(wPU}8;5ҸεK.ԗ01 k& R7g$ڮseIg^>`FujsTLA;G{Ft4_ șx5j_ Vb=~ MçNɷ,s|lX.^(M;mbcnR&)cqqJ6uF Qױ.Ya~mj֛#XaLwmϐUQn~=X8 }Q=0 o&3"'օ/!&9{d-mxvj2)9|[I6A(Ɨ\~UTL>-pK}@3<~34Sy*<d}O^V{<L:v~Twԩ}q9T_ؓPu<0EU2‹}sM¨8̩oד͋azDIy/cA^| k$y9Ы}rhnbXG<m-/v/̸/f 6 0$okoqxNY"^{U)rI^{yLX/ ^Y\!"^Z IkD뵄iR`/j~0?+LΓRy3{(΍R{lTדMb5?@xd+רg˟rln*rn'j[:m# ig&ʘȌ۸C2A W9V1w/ßXNE ?]*#L72."bdՀ}+<B@fndU} <ؙ߲)@QKIq^ͫe, XDȈ ByQۨ+n}#fpe}.H䵙g ʹ|CrTۉ'Ar hkhiz."GC:>qXÏ:>B&~3,G_ zcoկ@=]F >{:.VrK?_]ǟ6VI͊?9sb;7f}N4ѷ+X Wp_5FJ =Fé>&|WLl1[ۻkLj:!,8Kw~~7MAzw1\y =i)Ո Lk1 *"/і%Pg"ETN<]zgA/ܘw<ѕCUg`-3;|QWbs+cipck֞Ŭt#2ǁɱ`c{Z٨c(xl/kBDs/Px+ܨu_؋LVX*MA0T|4;ͪMkea$TES,`pOD=eCwx y.l +S0'manLYF{.dNΛ?lmw ;L?(bQ977ZqWaU2v5bɘt3T uQ[[c^ӎEMd3#ToЬ3DZzŒyA80m6oq W2#P+cA93t8drW5lܽK_⳥"7wvlf̚K&p(XEPHpD{VBTgz/='[(MQ({gșsw{i;* VWDME"9 kFqM4W@6׵3/H)q9) '2l6K,h[xwF-q0h\16܍;l4~bl ~AEŔ ȃ?Y9LuBQ·VpE/}qa4^3x:dj+~Qa'2μ곝LP6/f^m[¿XU[G ڣ00'OAri4ñ(p@z~PTU$!])Q2S׎qk*r[IiI5%m;Gk ^|{z;ZnM ڣqל6ϐk8~i|:z9H6M WX}檮#k2X6/xl=B|2Mxl9ƩKsi) 27w7Nz9'9cM_q1u$Oy?bv sʿ'[?.>eph2CyDq{ 77ÓQQN{٬ d¶25DŻd{W=@lQ2M{_짹!u +|YeJgB`1> >^mQŠ  tiL8(.KxG͞byƭH| BjWV60A:4PO ɩxm֒}f|mY$Xi-@L& rJnc|m >]A66.7[/ WHjժV>Dt U;r?9FU#UJJUZ\\4{t?OnUr%^Ha:ǡ3ڶ2&rPneWz#3,j!3v.l]gl%2c8Gե!a]`&biÿNğ9@B]<1-jBl kz |͓aCh'8ϫj{y N=m]Dmg'BچZ7W3ÖroA٢eX+f\ H"f&J~hlm'Nڢ>]5*+Mk-C2}ӎ-/_8k6SsC(?%j[2[ॳ!o`IG턒g~)([lS`"Zw"f&f99e53ydg.vHLPԴѤJq?YwL^a!95?4x=(j[QX/j.m^ڕl-v_cMD;6PlE$nP],e^j"쩝DmL چm-t^mڍAimPH {V%Q1  &\VX67+c8*jb֮U߅m!ٌڜU a]lM~t`ֳ1ԃ/cNSGuÑL61Gv   mMb򧢶!ƋiPndd֝~CMٷKnC;_:Rm-yeMݡ;׬cmOxj3@md֘,l*ʴU~צjF!'5ɉڦ c]Dn6fVms$ԠB|M&ȬL6 m[B֬;דۆ L;#\g\9&VV_r(V6BV tcO9?79)hԃr´Yl޳i!Ö&¶!rub ư 0l):L7A(a׮B N(ꇞ}ͥ[{2h/J؁smCso{#+$4${<[ܙrd gm^ ORaUVd+M x>a` ë́_z|=+@8al_W Z)j ?n~U  ҩh3/ e84 Y6J/Oe4 Z~^ `BS}M !\>H>WXE)hU" O.C:/t[kK!Z|x)FQ[~<ӼKX(0H G+F_ݯsr|mSl@zoŰFVi/8aLP[Q."nϋfjue&0[AIL%_?T1ZoVY=ky?Kh=jiW DDz_bZNEYDt)|8 i?сyl#Dm^Ō=d{ >s3rX׊ $Br:9q태ԅLJG߬YcUU?dwzRml%&|W)"?sgdcB~ 6{o}Heۯ);l3:3-,;c ʶ_kfrs+>1 [Iʕ,$Fu-f6SUGBhwPƱCcfFM v>6ͱS"М&d-l5:DrRxuB'"c>ʴ>ϼmz?7jֆ5\M2®D: (FƟ?rl C3YR`+|g\ J)85M{̐xmx3Ժlن#ƄCua[L6%8 f$`\A/ST+|.BחbٮrSPm^其UqRod,9؂a1xmݕO-UCPO')?M9A2t\8WG,}RmcZ1nZS-lJ=@TlUqEmhMM6/"!̕F$K֜{A6'>y#@..{]cdooBe=8 暣PA9ws+,^SƱwMurFGx|ض7 g`ӄoO?L+PaCh̍ʀ ۨXI.NDLRcfFH${rkX=NIZi 4 b& XAw3vsJrU=B`k~mpϧ)QgcSS5;g;Vqy hޜ\O:m@:R_Җs͝3o?,;wJmNyȢ̣hN9#ۦ"UZ0(_ـ+Q p Ŗݡ'[h|jnv,80@UjPhx[ؐ4YJ y|>Nm.+/++SLLGőm&E>GVZf&mʼϔvff^?pucsʠ&n!ͽӬ8 Om n -<**l>ʻkylhA`CIY+^^䶆vߢk$hBWjMm8)wTs eϷ3*m:@"JbFOr~Љ-VN7D`]XpnU'=19\|(sA=4ɔeⷡV$&ŒixK4-4;9P6B4ϥ '9 B& *.Cbr`+9'^ t"|6Ai-ʉ u+91AOAm`tr*0 ꞕC@'eF.Jޖ!o5!6-lR'mKԶOw$f!?C4 4 9lI,t`W@FwQ`C ;6 lJ`F}B.y1Yo׀5lOb=oo!3G|#d6>~HG& [qFSشa2yԶoPXZ~yWbz9I"QDDlcL'ph1G  {B Y?Z/l搫`Mߐа`_u&m g ƪ? Y6JPWupA_m>}`/؆<\6֛plюd.!&m`~{HgZmi5k]< OMjz6|YS nW9N ]$@J~]vSL- ]Ŀ.M9 y^uJ=7)(;[1:s߆᝾$6Ygl]w뭚 1LwZ/+ltn;`⒟WL#]1]\WE$I'Kl8WF i;|\Fx6 WFp]Cܺ^' ")Iܫ7sFf *ۻ(?0`)Z.e|$Nl eO=LKnAMu". =6"p0^!2%p>I>n/`<8ʥ˯mp U /]' 6Oߣ@Τc&[3ʨz1Ʊ%T؊+R;9W|z=W7  +Dd' ]|ڸlm((3&Vk8 ;\hJ頴|@aS^x#i;l-I62TN5cUYF(3rz&#Sֵ8{lC$m]-UKZc@:}?G m8T-/ kg¦RH\C>i=wY62 Y3j!i@RfRWVKDjOO !WFK><{d:G"ȣB"Ƴ߅N5U6Mf^{UN]s*+ɶcu}TBuඞpl2*պ.NM pvA9TyxAe"uU1ٔ{lJU(nhҮ4x8Z ;F79oQjIN\mPxz\QBt:ek?TiumE?̐]m@ޝQfy.1qm. ]Yղe&1ٔ{l@˘<r<"BQco>bYUl|)[ ;܈Kim\w:ߋj@ mC JUDʯIK6qXyʪT^ZVؘۉ8YڂS5) qȽBD6-^o<<"*+ms>=:X/TgpaZY!jW aNu:*Iz&rN e2ûk*f_(e&nK{l0+ ]쒲|(47^6k=e%N'^*Y~P"EmteM;kbl4gB6%BŰk[9ɴH6t^(*n,xIt6Z඾ob"WJ7lJP6%8^! L jבqlI /NvA[7Q۸h:X1ݧh:$j1}Lo@}m mCڞ\bfh%pPv_A $(*r f$q/h>2Nlu U.]v//j +\rVMUF'¢hdsmTPM+nGm x] ʦ +Depᠮ;̶T ԁe,g7 hP%!leNSDnuo<{pjXIOQleXto{HV ކ7Γ~m@[{~HضQ 'R; ;  JIDxl7̆/8(;Xm ])ŅoC`N·FHzgۼop+'ag3^!@$K&*d#4x@·b cw_6h'\c ډ!WV|vX\D6PPrl(":#Uf]68,"0j6JII `X@*v?m_񏧈l^1@&FޠQ=fuѾix 1_N)l Lߟc4!h'& (WC Ms;ŒJpS$r QB'l[تh'Q`)lCo{x}a5ߖ5Mt~W 2µI p0ncȒ̽lPc/}mJt>c>mbCRm<$z1۔_ZІBi֢mPZʰub8{Bhm{R#~>D|I86M(Nm8J%g]GA6M& V1T:*gB|Z} ,"YIMSkʏy5х 9^27_6  ##14L3)hBTيPۼSpq( ll9gטzTg۲K7#oP%U6봒I;J --͈R;ݷA*J]0XnW| Al!Z[ YWዻlӄOb!|Xn36#f6ʨ/:o,i*gC]LtmTR(`TBޮ`T&lJ.5h d^^CaW!t^(^H6M(3" M9]Y?bi㽽Ī* Mu_676-˂J}gk7~9sv H>rFŤ?zuۣ?A3=?A9^è>0DL[m F4b1 biBiIU&my)0)E1 sХ] #MwqC6ߥ1bwZcF Ĵ@*@E喝}'B1 Ѫo^cm6a4+r6m0o!\[ZCbQcYwE'Ù-pIy"$^Mz0)Ob2n4b>Giȋ3 3o|jRjf5Κ ۼU-v\;eapl笔=_Pڪ-yD'{:[ 4Hjq:]?X ߢ>\  6b9]sp`wGW`"-T -J呵V6U5XiɊ.pm3c΋3x`#Toe*vƪŨI].rޙC5rzweZ١XedMxUhu]NW#9^,.m;UƝ"٦DK)ɉX~ٙ2.aM<3̷AbHmkTxqG"ܹ~#Wa ŝ[$LI(]q>bŋ?, I& fVK&{\O #4lSC# ٦ ]mMMm)[Eolٽl6nGADS~b#o ]Ÿug WMae:g m7O=} j%UWxv֫y2(rj#qpSȟδFA۔uS$[mI2,ެ/v(4[#β&lm]oaiǪ{Ĺ |rq\ ] :J7$$jnN TjP+^a11%)LU;Mꉪd-mSyƞSFN?Z6 u4m'&~Bh|UW Vxj}kH]>%|O)70Ҋ3Ϙ?/F<)8RB)> }|[U^+ک-2,nefr~':wXWs ѦBԻDX2C䧿wB2K57B i_*#jFQm^UQ}62*UH <0܇GosfVBa^U`1gϽQjpL9mJvU&?_І6Ds?}gSeZCH6ۙBvr UsTۙ[ܦ?ڄimmF,0~ō|mOckHb> }*yJ^H7 |5z\cԲ/קzGʠ"nyRWfچ* Q&n*:Ӈ"ҭ'j[X9 FFw4#&k;a6TH6AZH0䠤*Ҧ&i˪rW+E|-K{atL{N%?+(mCPwdЄ ɦ T6|;Q"d8,h!,ae?֓ beHo# ݆T|jCH l 7huKJCS~ O3JCmC 7/EQ4Q*Ed'^`A"3e^6 S,uSX B1cs݆T`OJgRy"^ E{Y\/{[}v ' ]~DmCkmA"J6M*,&(!Aۼ~tʬ]6̬\2T̶ !z݆zh~1 xFt [V3D/okrc} CJ**x/.W=K2F¶! 1TO14axaq?NF6!bmW2ghsOlcǦU =NJX9(Mh-rG ݺ⓸7oҾrT*5Em3@[H0m%xRK b{ IxBj]ȌŭZ6ڔh{xdh@hdTHJ W 5ږ Fa أuDmc!xB@9秷\\ D3}&Xya]y@mL,jԂ ^!Ez*ñ; pdSv>!MS##3n+V6o23V#jǿφł$o`2Af b  v2" >=ZT6LS1Şql?JrG@ )#DNW=B˼7k_fZZE6K206)zYm\lE_0hV05 &SEn#Aer x$z Iz5&htjPLr@X_;ҚE6Cwn^>wGGG֋kHiVx mFcHW|sѰ@m>&!iMl\b,ӽ~`ς–+"Q6%B?'m3} ~N W@` .\}? W(W**xjֽ{n[.o! XdUe2#FEW&٢n+Kmpl+R$jG`tu|ӭcJEl2Jթߨ~a>>y#d[rv6r6֒H,qTSiɸ;^4ғȜF ( uKü l| Cl &l._CޫYyuѵa븱2_4amJQమoU3zyYQƏ\qb2v?mBLIб}4ZT`4n'&}:j$U"nZ7CjK'deXҀ\]_tWZNNj z*rM|JzFzr?|ݿ_'%=:C{ j7i)ϭ *bɷU5ioNwCъvGcMm꒖ )m3mONLJNINJJau.RSRҔ?`#H01^j=4 rqT)}?d"q+ȼ߄l¾흊mrl#L[dr.a׍}-\xjęfR"WfS+s3}}=4p,.E"N+-j!XcBo]x2>oZo Vze4~mƮ:{-)R)Q)Ocnzh?+ͽ h}v>iTi a%}p mP 幫Qs-bj(iň,Mh?f֔w޿śq"yB+kZu/ߦ mle釨|AM[W$35oRtʾOXJ;;"BIq-5D!i{\"!=-!.[Y9I7Fk9 =\B!wryL7QlU ׄ8* F:Y4/NHn D2dc3M^7B`?hPF6Ue|.9"-t@5A܇} nP=MoRWoa>J+9 ڞLO2'4~||ܻԯ ?V?mZV3Y}&3l N!2lsspO1$9ĕ<ǭT͒jaC8JC Oxe&m O\p<]&MK8lvqDM}-/]c7JX۲i %=G׆{p3JdB`De"ţqH\&䮆@\! p;L%4A8OaV˹~ @tpL'WYx(dfFpeĎ|q)%pZg~n @lj//fI߼L,dX ! ߪ^$l2[XoE~3߭l@L6)<\WD%#PXI{&.VBMGk M!+WPZI%1oLU1F D%n(+%/,!z2l>dY8UR8`^4[m y(3W¶R %q>CaX} sFԷ!mJHm^no7ƭu#-ڲ4Nat "E :7 b.lᢀ<4!Ԯr-5f{9 qZ84Dm+'3:$j!%_jqe(D;@D^ӹDuyhZ zb` h&L^~`[ $ f;! %1H-Q?zG/PX`}29-@necˍƁIr>}@Ab|Lfb3a1L ?m/q 1=}u6.R^a@^C!>GՓ)Iy`mẘ%JE$^x)((R<4tyw0a$= z"{xgX} y(6>.dmw;[5zpEd#Ā2-~"!0VKRPHC)eל-pk )X~ Ey$Cr 41WÏ׫z퐎xSkm 'YzN:7Xu(Qf A q9.)VUf %*2cɆd#"*5W,*^ؼ=T@HN1tMpS6I2V=I4D]?pcljF%G%{zI8=.JZ?А|/El#a5^+q&-u֋BU>Q>3MKp VB/Jنؐ xoCMopm%S>Q?pBX)uTV\zI4%p/OOC%U3H8}!D^yȍwC[(l,>yW6g2^Z#h!5PA:Bêj"ztj8V E.8Ar"hv9߆ȻHbE=f\pN!f{ Q*|$5ݰB &-@jٸq㦙3XEP!|_oCU)Gr T!"qS6iV7z\luFHs xN@a #~De[ZUC )s! -%l. $~ 6&ejD0#7e(qBF3AjUN*kIM@ׯc\i"w:xZɓI;c@%}_Q65+s/5eH<%!09Zf RccO G5npjoC{m0*\NӶ!\'S6fey6A-JBXÑ$"l_z]QLRs&J+,d, <@Kon?blt#C1{{jɼjރs4YCH6Db9 bBlx:}&J n`ЈPij(Z\## DWH-dճDʳj,?9m6V:iʭW3 SȢlQ!W}( Oux69=X~Ů2sq27Yp#)9͐mЇi]A lKc^l1O dK+F{Wtuc^?%-XW^ys'|#bK_`C3P;A{a6d)}I c;W\k.KV}OC@Q?75QDB ~+HowuN2x?.%QL VpM Zhc7+?هGV_*$kRI4/u^9v%4o0og1xY!n}?l<7wnj0n6=im|^Fz&M7ٟ /czO O!YW(yº^^}Y#U=1)&mIYA4!3vk|AubS@,;Wb9HK>AT_( ֞neC[0MُOa(mT|BF!j[-;'؎ҏmkvj$S_E_|u,(рyz>>zpm;hm:Ʉ\^gESqLXO) DEYLk!3O>+N{R*7qI-c8GFb̛ 3k16[=oo_*M!q\[Cs/x2xLI yl.6OW|yn Q ~OL6s1{M22wj eJ ؝EHv1H˱˷0 . ~Ր>}!\~DDo!{{Wy,VbrPf c\ԣmLP:M>w_U%tFajBâ0ȃ`I2YՑJ_N [cYCK}|uh#Z$W ՜Z*CmUA'#c+=cV7qhz}% :Y T۰@6zv h&i5 R·-d+Bkq#c;Ky>Ѧv/.\DZg\L}ʇ8A2mb7]Ret:G=C{F U`OW* $n;Y׫[ئ'd# Jϛ1׀olF5q$ ڂ6e ZnWɦv<PWlc+0,X01ذ7BRD hh'y!KJ|+md7DM!3ʏ#qLdzunTDzyXT6မ=Q5x4?NZ- \^ڣmeFʷt6vc[{pOi~Ritj(R5T(< iҰp\CC vHl/%=կfsSeș}u<$VH mv;dA<ȶA\ .ƣ4}d9XHj}GKH+bNߺ_ZeU+pw0z,'~q[I60IV? !V;OQeT:<]ceuoc/(6flgB. =/LTWZ0IE:6oax">?;7Yrs4\Xɒ z" ZqiBLM-[zjBCqYg0}MxRC:C:m ߈֑b\- f s?.ah{mS/p+tˬK f|RZXYs|"~+ $W9v;k~y~Pd-R D!f\/ k"ht]E,"P`uk׬^r5`x ~9mʙoã$LGWGH\_3N`7G(R!>kUP7#pm4bEpֿ͇0">~b4ƃ]{=8~ϝw}Ytz6*(_A7Fńh6s𱙽o ܵOӢFUguʆn:,\vBq< WU&Hn2ny/\.}@%7ܨE"QްҢMf%jU65c Jsq\M~}ײul- pl̓:+#bqɌ(- /bI1/&<+>Y mͫft/kgy}[VԵsı7)7G0vcӞ,Bvظ|R*>}!6:d\9?Pư璵<5 PzGKrS-*-Bf۝N{Ϋ #Aۼk|_.0ƶ^%Y:ro~P_X swtA1=ŽC,u37TxSrG湎>R-㬦r.=q:j%_a+ms0FCyV͎*xeyi)o{&?FIeo=ׯ-/ӑon{6/*Ʃ4:iRq?ՖulJT!v&ﺐ[F:0n7$ǡW7{HK l^]འݞ$ mnlthO9¶̢oU˶Ea}-KpCφ^Jgy΁T뵉c,ӞM+hrg=cǩp!ȗ4j;FG(#:e`%K0b!=xn@]_.+b"1=% nz֫.7%1H ]i.fޥ;l\lxغ(*3lA`6*nԃToCs\gY v>Svn_+&hRu]Hoߖ̞{`Z3R7f\p#/W}!:3b۠ w -=fO "GzDK!ygc{GlI-$1; -A I֘lA?<> ɇiK L3ˆPKydFջglFI3@6]+l)@)n^atp}=·vv'!+n](EÀwMۇn=P-"I"Pi 8-c_[ !$D=~]~"^J[2CdP*2?lzS+Q6, k+BXώ}`R\g&T)C6RlC |W^_N6eHg6 RM;(&p/ɫEg+0"n/ֈ#H5 J|,\pu/hԶGi ̅j>A=ؾ_~ۊ B5,-dC}9ņeC JFНN4 |0Bn?kܒWdCnьdn/**iC|@[C'>6խQüXɗ<TӦW2CLM-m6Wc)ˋV ma d'niTd%6 [.[}%?X(8nGwM( 1?ˋ)S^'?q~Y~FPp:"w@$$ṵHskO' F M`hrKsz {6'Bd:.in kziAByg M ƜxE-)&q{$A= <Ŕ)mBn? ^8w}Yvˌfd֓ !wv39U4SC`?j ɵkRvE+0T_587y@pTkٞ[ضezZxgUf @Q۔Ou//|Qq @/l΂k$&WYEPL{fnF}ŸɌ+S1lMdCmC.'؀P*vhwIBN&)&>Rz!@~MҗĮzV'exȶJf=&A۔0e/~D\v l#ܸng`nv Mӽb!q/Tw|=o٨@U\:E*W:)0P)<7ԱgJHccnMGc9x60]f01ԃzEiaK@[8}*7ip (ֽ"q[ 3`. خȆ۴4ql[= Q?W+'"v^rYGoNr` J[Oٟª@okl8eI6 !o(Bm/Rlu^iW Z>^p*^&qَ~O,mbw2*>GfvF0BC\(Tp{7)0\C#Hb Th.P!56 *#ւufX&"nC Ìh @[؀~$+C͢\E86a@={.OK2tdB3Hg/bܠi U>PGPz!d}gdPOtc "U{*3GrC1 "PbFmp;sO17#("Nǭs=EMU N5rϭp6PUR @;$n]5Fs`ZI.gb6Yɾ "iglӬ ~hP]uI;tr..œ`18<(t~*'Ⱪ*CU*3D=%V;RkPٟp??W܈% 떠S|JP#x`+`Wk=0*{fK%&~b}omn2rE ]}>e~n.'r,.A߀ǭA3ؾMS=*TDl|B RQ?>ϱK0ˁki*f(.rPn{+ [[XpMe41t͏gsAl^D6MLxo ;I/v-6d|cuXatȆܚ;dm1[3+uj%sD7Px/U7S^7gSeCo0MBn`%)ܚB}#ʭ)#c  \Βmߓސ [W }z9! J萮>fّ[ 6Ǧi}^׊./0[j$t,/g!Õ4i& ̾A糿e y[(iWeO/&f󰰰l{|vE6M7t;m!.?U ݄pk+-{Evւ4I.5׍Wi0fms[ V(ͭ6(7f/٠aKjIJu7LqIe>+li.Rغ@j\JPN&e3R-M!4K_cK_i|~[ ዯX{Ev׸خ5q+?jq|(MI b܆rsp+Mp|rͶb6ML[IܠRI%{M*R>HAU0hoO\QFhFA(8c\l&6XDoSE{P`Y vfkNly'`ܤ $=GBBuȖjq8M.k|l5ʆJoRɝaI6 L,g)V-T0"n&_@]Ӓ灍*vT4F97@ +9I*ԃ/2@{NM$6[1Wˋߦ+HǗ9jm &k@?L0+'2{\׏[N .v]2\I򮨡!JEΰ`?~ j9,DYZ/Sܾ>~qIMp$ ˧ڦ˥:%j U;go`Mm#.v 7ikM7Ɗi۽pnbҽ"{r+,E.Kí"ނOFB_SEb!nmeVAʷ}?62ŝ7dbNi}Q(.uKdH{P U=!v?ӋC>: po2ʯ#.5Pt!ʱm^݈Ǚ<ѻw`D˘/=(JC/<>~R}-Ѷ/h5b)ePFٖ[]Ú_`1O\9P/7X8to Tj-Pʉ7pᢃv/?G]B t[xxяt+Wnb [R?Zܿ m^(bgKuUyw(f-^т=J^7?YaY?`i o;-tZX͋mjХe47T<<Ȣ޼Kq:n}^"8]ۋS-{pSB ~<]cm?@|<$d/iC-PV jBm7Z`%M5a&I T>~2]gm6g@[/ތ~Z.aF/eO?MQfbe8U_w+2zj-X{,olȻXץ۶SCl9 rZ |) ?/[0Gނ 1Z|1SBLo%JF0{" yx 7F43kb(|E[6ۅz -6cnB6#_Wx!$H+]r‰w[PΔ]wz޹#}Vntܱ=/ y}'/^=6ww8{q 7ɝ)Hf 8;nûׯVו~CّLNzor XzԦ[-; ңJIkC'-z~m?'2 fHCȶF 5%ճ6-s&뛀nK Sbdfu9J2jx/Eee&_;׿M 0M"]IͰefڬI z_&e؝.Jߖwaqu6Xq+ń\k^57'ɰ{_ %W^t^ڋAl~'m8\OUz|/ddA51vFr*3'Úo37},Q ‘z0 羗qe<<m_?rmUIc{]a {ؔ7iw(@!Yv5[r(Xf~[AvO;4|{Ђf)[ۊmINϕ'e])_S0m٨Ⱥ̉.WRʼnV_'nyϩ#g695kRFȗ zmmmJjlh{劃: dVUD(a[$Sm`XJj!Ur(rVDQljk rnxJ@~'% Mo6)0³A`uq0ǰU@ex9@mgzx#l FͱGXk#</߹׏Np)cG"o6QF+þlE{ Y`V /lFX f.J]0rs`OG?ء6B/ar U%o8;oMp}F!9Ն/ &Ʀ]xީ~! HY/6g?B*zePOAt<4*d)5~wa]KCmɒ hc ϑl6 o gm(6fLŇ8ls5"\&zB-;|R̬Q۵P/eQ[\zߣS;$8 v0&1Ih6#6b;ի!Xn+6pA (A3H܋\^͂89F8a?эm4_??Q+~f}MN=Q3g %,6 zPhCa?HMm~1'⟵BP L׍M')~;35$KXx,3RԬY ԍm_?H[˕" (o"؊ QG%o4\p6xș06~8hFIæ_ >/6?0tacJ/|Xl0]Ɋ-\ F6Gxfقq:jcky~u9}_b{VXt63c|ӟr.fPԜi[lcihfOo, 'K[oֺ_h]fa]h5-PʨpfTW?S[rڦI, ( zjG7o^_ Jf|m ݏGo輖BAlg5ƳRc2e&6yvG.6?a_ֆuѢxQ_76:, 8 jz#HLV _ezR / J5"*2 ݧMGeQ{zY`_n:h%bQ⫞|5bhkD݄V ["]<cvu8*BCa+=  W< #,Hp6TQz[u uxOaLI*l>| Nbvw Yľن#Aa;aisC&ئ>o~t8Lmuk+DopOgVKl>w:*/cK$ۓ= yr\+ n\(0/pT  D<,Aǰ[ hFƩ`֏50ichmm)ZS%3՛9WDҷڽmvn8/n-Zi gC?9klvٮ2JZߧAV~R ~5 !aZ1/6Egyia7{Gn.*Ngdք?[UE:6=!=;f>e9uiMT2m+?,/>ALQ%`y]S1rk5lCNqazݶM(7 /|/>w<=AͰ(v;MhYye0њ}xy'㒚r1=ik|irorw^eM0֔> 4 Oflد $jD_A0Io:fZ0mpv|o|tn,<=sD?<] BiåK"$b*_j?}͇x`Q>ū#pV%7V9u ANT[}7ҋ r7nꤌLFF $-[/6B9BnԈwĐ%D|GHm fL$>+STQ,05Ga[`3rkAzv9ú4bkJ|h3}B4͏6m#췆mߛh؏hKl!L7ePg_y}*5OVڈsF]QOqoUg\)v>Ő;˝,q XW9-f< 9$Z | 5l&e_@g"Lya36>⭝P{ʒ*zzF9Os(6}i wk-o&2Mq SV k6V(lZYE6>lI35aB͍~ِb+[%.ۖ%K/(Z95hc:luD1ݸغC vBBnod'_W6fJyR#Hl?Pͱ]62UVˎW!@zw*l^0(kte6 {|Mr+My@=iˣg r`:|Зh9 K6%$f okcvTKLě0=Z㸩?{lu#!#$8v+D@HM Ӕ/^_Hc0/ĖHm ?m~ϰE񔝄l Wfֹ]J6i@tm4̖Ji/~&<4FCjP*YqTKڢ{=3u*K*!lw=ԏwʻ`֗+3d.x|2ܞե}sbA;?mdAmPNp,s9ԃ,OOgs -fT[};u5hi{ۈ1hf=/?&d5ӥ^L{`i0nCJ'ljl[>5Wʕqe28W{\֨P_]L9߹Nx8>- [Y~7 {SJh2Фg^߆؄x* 9oWez6\{ Y=7`qNlUH =ፊl0 O~6X#m\~,v W{"~]<~+YsJ㶍hjJۈS~xiĶ6V6ˆzBmB/|B![R){q}GlndcXD4$^5{͛o$:sX#?y$׫ݹULWS|ބm^n=<lSx~p=65UᶟmJ|#2CC[&፴F6)mLq#V%ҩF.'w]/^UgH|ӶJ8a#\ VV]uemw(!ޓ#m24vgggh{9yKDfC23r32T\,wA\(#J#r_JEz|\_Z6OSO^ej jVMUfŶ>]._]W-ܵKo񒟪sX?Έuz;FS.^`8SdK.q KHzz8r{ls5Q)ؖz;ÒZ!؆{_փgmfQ?Sn?ќ:`U=VS'븅t:?&i& [*^p伽\la|kx! e-Pddd( PP2-U,14fJNj%ha؝|[~拯o+,}\,{)[5hsm=ҎRediOݗYčf3>':ܔk=jWey5j?/»gbAL6Fc~UۿWo\kH;U/^=aujah0{'ίZ/Хkw4! SE<ή9`p;zm:xz(M~ux|f]Glԕ!ee@*VoC(r׸sAo%fy+`?2=I޶4N.0l{( ]cB,Rm`oV.6.u?.K$Iք>5+vM_杨NB&l+Jb[?j r'[>5Gcע_fZlĦH?q[6Pl~;6X[&i)\l0$7e7qtM WPp[uyg$u/pَr_pT퀃lK%6Y;@ 8yc]bS,jc9yqH}JUM '$禼>q'bOöب[[]H3~G1>^qkRPސ*l;r ",c`gGieփ ` :hVr=j{[{ NPFק*:C敥R#myز9 dKm[=6حmNC0Čj)dY\aB,~`}l5>(cww668/Uͭ˗)9޼q{mnUQ}*a7Џ 8yHfV4_uj+;Vki 0OODs?hf7F ӥ slY(Lf.l`mw#ʗ"/Y[.6P?]\l۩^<%)h"o?n nQXG ;Wh)eI&laAg2`3MƃmymuuiCv2 *6.Ѻֿʃy^b}ö{{g nLRox+ V]T[m'd,t6d 7Vڂt ?6z jsbcimE]L OxmHL'ӸMw`I.c$u= X4dOkmS2{86WظG~16LZj[i7. ֦Jo nTzK.dr$/S[\ 0ޠoLE>ǐ3qo+nÿ]6O/#}'o߭l~,kRj'3Gze1p^̑Ց{gʃ۟!OyS\, 5f7 %J[?tIc<`S[،c[h YU>-bHJaxGI~ҏ"~}򅝔3%#m4k2s\ -wG~>!M: .?m^ej] >q{CZ1`zOt _ox]91O~NfyT@^;&hEb;Ȝ)OJ49e9ԇU570q[֐e׀'놀*|0c| =8iz&зAuCnmkʳ(nx}.mkܬCl<_,, ]0>mcEy|Oc) 8A@880e7Btd,U/q|Eob!>3Y6K7Q1>-MY*Ըm@|1rKo65ǝl^v`>sGٿcn#3D$o~6t邱HM-; 2"Q'ྐ~ _ Lp#a5DJJV_$1wul'oPz>k1,'b8Oˁ$q36gF :cy~x]w;>"A6KqЙesQljau`P dF@]jnb۶Al}1,i' +(uvPHQ L cPPaHjWD[ -F*>^Ĝ%>.Kv]~}ڔ/ŽG6< U{@~sYȩum3 }@_> 7?5Y@`abX/ '.5 &rAU5ZAt9:qC*~R~;-Ajol,-UUFgm ~9ehXI6֭lxݰc>Wm=Ϲ3X[v4d_Iђ.rtc˅،e[!4tV'v0F[nS) ܷ I?ݥ 4*GU*lGUpodR0gw"ȳݝ De3p> p[.i LͭK&Igu"}6bkwIIl؁l'CCWMK M3z<V`ņ mun|f=bHYclS~)10cu+IPll*c_O~ʥet Wр<{x]w4\R(G#[U.j)c~'J H P)pѝؤ,{J[3/~$B.EdӄvSls3 |J7[֘#^^0aB5OzQ4$'8Vly"m^mœ2>;ᐳ_8Q? k@ qςE`< y|1R{e$H*ɻrʌ'['۲>n InR>b6m[~֧GBGvdcVR@ [zN4ɱnգYŖflrڙlv̜Mkz;Gt <6pu-^beROF^q,O6d2n6㿸?𯘍mS~P4*G\F?6fDŃ;3=oڸ([~}$[~m*[.\løFq m}|z'yir!~J>]~%pmC%*y+6KS͇hs:/Цu3OPE7v{lYBV-DmiMO|iw:"fqjsҒ2srcmvk-?(lhskO; л֖WPF\edy||'rdٴnݚImXlά[j[ .&5bk/D&d؟5q=}9e56G&l4 OU6zmy~IJ 7ʵrVP^홲ui m>?$:Mrފ:6MpBs̯7#xxvQ?BtQ>DfuRH7,e$a6i$ɕ;֬]`-͓-SHGsW{G< Жhxfc6g  PCmNԷW=,Ү3ߙLȹ~ym@Lg&6%^WCkV~|+n|v`[˖2ۄֶz#k4U{[7."jq=Om}FmfƷr/ڭuW}mfӚtʬ6Uf>Nָ@mNmRO{̄FOKb|V$IǓ: '-_kbuEf[ظ{kBl*`[6`ʘw[Ue46."?{Fw[Ҷl!DMM9\Pl&ul2P`ۃ.ڇs!NWmMͧ&m!Sh?tk*[?"lfGf+11B|f]|$LŰ,t~.8ys!' T-3F"_jCB=P~KP( n*x[L!޳Bid[n%W|9┟;>oee#omB9Z6fD\t*:(cMH=V.ޚ ob+ԚCP] '.Wn@}ϪQm-mЮQS9h٦ >Ylg h-~Ob {qPmJx{" 1!2cf D!hB樚kשVqPfu9؊ըX\A|pWa?o۳i[,ʎ_&u(6ovuǪIʹ˵;?vU*)7*jݸwߍݲ\߈fVlM`b>w򡾴;ƠSlB}iyetc&"'m=uu+1|{چPIۯx|Cl1lcyR1CؼH8~Lk"dprS C65Ztr!6znm z=#/{ }C~K{͑s"B ?Kўym_{]2^rD#žT9R6$i+qK6@nlہwo F;MR ey~1&$}-w%zrz.IMۊm2m_M dkjaLVliPXP+ikm!zm$jיɨÖVA/ބu*J^'5soc]n[I6|j)R~]8dks6IZt9@1[BC$c v#6ɶ28>asԍmCN @ww6^Q 8!`9LKs;SG.c)"n 4lY%!$8f)l!1j'_{'? yX=pu荺£FR_|Q_ԫ kU }\l;DfFlG*N2lzmClwg]5%ml  IR=@$`f `b~ޝm;ƣV`A yq`ZJ`6g,W%6 _~Kl*#՟nkoF4@4eBGҗƤ[z=mmdpOgKϊ ̆܊磊b&ɝ6#:~i2_6ssp d8'SIh6gO>ajNcZK퉎uJh@!>m"/<ͻ\_x6+ц`ׅ2;z!b]n/>eD(6Su{8('*~CMpﰨmp2rQO%v>`ʦ-dpH6qo"0*h)I M[%xdپws[MOU{r% ޴QoZ~ټؼFͽbT!BniY _C\mޓ_5wqHs%U\Eւ2*L|6TstuIp"ۢۉZ[G&+Co6_S 庱Ч5 _ԶIMtdL>SEӏ6 l/mKJM6M}v8UC3/X{ 7sOy=*Z`~.6O(PEekKAӊ'M*7NX tBW-8 gmml`C2o=)~%Nݮ)?%v,4PO|lalzϪ/-Enx'-eݯV_7z!+V S+閶)PY/ۈI }CPۈ8$y.ɲ m;dXl-;o]m5 OCj+niq$@CAcr >+tʌ>p>=PAm{?SԆXspGr(ym~ n'zۇf 63CVdQTL=(6z6E!6+ )c>rS[ښ,'xS(M6lmY3ۄV28'9 nqlSC.^ᆶ i [d#7UˡxVZV&,6"ۚgWNӳx "/}he̠[`8:Ƽn)nSX]ZtaiNĠwK;]-!9fkc^(."Cs _=L=H7Ra^-+,5Cغ'ں[Iqx]8{&h.к*V Z(6Z4vA9\+> F-iOp'(csimCX6l*u>rhLU]Nu,X.;xwnmT o>Vr4'!Ŷd/ϧU zCyŴ.EC̊F\n >y&ޛ p»K33 rco1ċlt=r:ךwzx04[95AШOSRRc6Gd u,Fe9FMqYʺN{^- oT;u!*F[Z +g@Az'am!|. B6['k>c0$M~fDi'u-qѶ0xyJ6~x\mhyߜ˅6Ťܞ(fa(߁2UɶuBvپ.u8݇O mH鿜aٺIw"7F}3 Em/6N?t. Pƞ !9kbOp1ޤ|FQL7!ڼg"'knѶ:,᮳Y/ajv,2[Wt2qXF +sM0׺Lr>&ᕼ&Ukڞֈ6fzw@S7OX Q&'7uR߅u~`aE #vX lnllA\?UH=)@qz8oF}46,] i,n?}r8l3-3leo!o-ޠoV漴 {6o!&UWǦZzvN9d]pNY0 *l8zQϞ Su?}b_&9rl ȚBޤ1غÛ}H/D5`*[jŖKz^ Yޞ=(~_fS ȦX,dEYQc'Fn/?do2۠>mNu2} `y̝bL6P$6ʆF_qu`ӆ- /qjb#mWiྒh(j#_t ^~(ofnd@6>}-^l+;< )d,~!8->cVa7̑[U6|#mW'ncGMs!sx;21 ߆A;sN1tdf`s,3fzCmߣkO"`P͹v80m-U6mM i4/xG4U"~x܊ nVӶ֋Uq$p>.K$f3pIneNhۃ ْh?lz޻8ZCz_a8cUl(^C}?7uS c*ZcSyy4Ԗ2mƝYmFRy~X}ڐ9pzxl{hCN=UoXMe+1x!Ĵgbf=4C'JqZ|3c9{gmϔ`D4yMT}P:{)ސqb+[[ }b鐡KWh%—h+oUjڂ?`edKZ&ݠy Г ܴnIur)SA!HN^4KzäZE0wHƵ󴭓Y]̶Oex!fapjxϿ' ՚)3Ekϼ#T`u]`*5GKK~oP fKe¥,Tze46ņds[O&}2gYNlk7o#>H}}qxl+!f[۷ܹ iܾ}u _WLd -s6qݐJO4%M 2 vK& '?2zF's~# >Vs}"٭TU4ӎ ۔`6Y&ضly.c2p?? B0f*J86b2ʖñhT\rdS,~@H[HKMq{'i,Vo^؞9NmJ24;ndӬ$DmdzCRMm;o"&)#?P[ Tɜ`nLQndM`3b {̾>~$ߐ@]8wB Cnɕ)F!+DI)8ِW^1l. `ch[Tm۹yoy~RQ3pZqS &O p܃&ĆMlz6;{dQ!l8z=#x]{$dV._0!!fܹsI͂!v}@lQ7z5\kCay%#K pp!on޶qvpzeHlJjs8{M'ngɩIvn0uڦۨY+6nݸpX]o(}.ܖņ=l|򳐸|FIʵsn, y;mצ|>Vlyy*[)ۢz ktlbj:*AeG_+EҮ[l) a6?m5g95@6 Vm/0w31%A4#a?v ;W nu.נȾe6uzpf]i:ۜ8b7B:W,6˗/:-`)w)gAz4^.޸9Un HRn(m uG~^Aێ#ǼRmBl˩tl+I s$7L?PVp;[󋟫l`@~+dm' =I%DOmq+E6υ7\e3̤$3e:Ͷ7mg%FY][KtsXs4H0TsŘƆ< uE ֭}6ȳ_&v2Ux_pARdELI #/79;-K%M5gltw[w@N+>t098?cʹb9jNVDJ,MNX7!ϽJmlO4DZPn+޺["szyF( Fx(r X= gVhTX_J[ M\c$2U.-nlSKllNZ@6Il d&>m݆):_.pSJ?DfB W!^7Dl\t7)4/[K>At-=.TJば72O^̍em7g m6*:8k36$Ʊqg%Nr pvmB3\lgҟ3`oX/J廍M+X=Sm@?wbɽ pao C֫vjh_2tQ*h;MoSS{XGY?&)auH׽ p(A.6p))[ۗ43ے Jf ~.Z6%!5a|rh-O` ލRn v[`} `fj=8}B]({wwz5ɉ[OFL==SX؇TKz%j6-Hu-U\!$Ɉr@|\aӸ`˜#Wڃq8w g2ц$Xvt >q.^|\̛ V1#"o6A.Q7wEg.(!<]t3zv$Q1C?eNZ cȽ I@I{?gvz"@3)-}҃1 )J 7P ^92'!tg눬(  l3h5Lme_7òGmHO>𛊙;s߃$p˳! v%k[wO6`b6s1< vMm S;GQ3unQv|*OoS6c֖⇭f%IHa!:׀jO=`yl}BI:BnP7+. B^ ;ir0ޅ$v:PpS:#{!э9V 7'X3G#WغlLyLZݬ,}YB&ިc_Sa40 9:" `I]l&=#``-N,,VV{mrcHQ6b.;i/?2;"mK>Nkr%6CUW{mr6!з̆ܔ9h{|15٢]b;)eԑKit4@D[D`{ŧ\Ռt{VlI~F򑎪VEry.%_)_q+M^В߮R6yJòkQ95R; V.[h;^> d 'M~ -dN :ؿ2Wkah֗6XF_еє䌢Թi>f+I޷?T򳠄 r&C~14dgK?0P3󳵝!UkcxHyޔ\es7i2d i= h*!+[q3T#{)y;J6뎒PTj;aF-VEPp!{KKe2۰n|Hm_._تth;K!avVKxL*XXO.xu+X]t5R$g}LEE+{?zN0hSݗ+֘ǔdS_Xt[e6]gA +_ پ`uw'[cڿ|3cE PmWf1<ImԜ,Bk<8F크~`MJ]vpWw]otJ%O]p^OZ`nB%-R;&j7mkI}!}g7+ { ig}9$͉A|?4a,n!J5( Uȑl(m<\V\$_E$T?ůvQ߫w%=4ߑU=j3 2nssx{7?}<~L_*}&g*~ڱoUgDW38ơ-wKY\>A+rIn lFg~IDMF?ҍ#.;|lƪ&A5dlɃ I!Cpg&>P?wRcVM&RY#͸LSwJ/l 6m1& Jx!}Y#&vmY3!`}rxMgl#m_1Ag֣a|ܑh)/~n3xtG=XXc/RRrqG?\T@_V6/DG^b$e^r3Lu]W͗ͨ;.槛$|t 5Om5vV5ۚB HLM7_#$I924l+'9gֈMD\l=ߊ&[·J^]Be {42T aOEYnxb GiW8g`ә-6gpRfM'l~0 9? @p&z|C ;2Gl_#UⷀO?Q#m,!(Uӄ`cHt!a#ŏݡ21?:U6{u#<8cP&t->jnވ!dPcI;ՙ4'c=l0J}rlm_~Do3Tl˩gcSϓҳ8Wn*#SS/Oj>3xr۲Sb|^JRJҦ5 F9*Xe;dž!`%lS:9I0-p4͖|UMi'-džPj/3/(llF Ow^6IҎlڞ#<X;Mu>,-fC6 ^l/#~l5;E G6$nSTۖ"s+$/@4 wIZ\k!naSL`?$j*ry!̖LZ^`6_>džP6zFg15nP6U"4mBAh .b+ mwfJ@m}dž8cFq+:>rCumùW' x?v(9eXYPt[~؂c|J mgR#/ :C9Uj,xһ41 ]K[4/XέK~l_BFn;dl_m^.pzwyoSpɭlH6`Ysȝ`1G5$̱["`=R8$ PhC5t6B0³ òF6Gn~2XM Xd慵Mp]Ẵ sjPd2e5m~l9dȽ! p4j2C,61T4(r [V2TwEi6`ks@Ty`_, f.mNJtXla٘ꓓmbjy2o~S_lo6~g!XصA6:M6u=(6ЃO5k(QPPL|^&48oR_l=ek)f6lWm-sÞkbC p p*guE͑<̇z Fﶿ\u7 mke[(X PV{{O Q,6kP~\\`+bgFVe4ڈKC_ Kʍ!/k8F- ơ>cѯ[hwBET9bZ* s"mw!0ٿM eћ͸]æch3Ï߱U6ˆt7)fay?UCaܠ55&Aص&`tg[ٶ_ +hSCz7E73,_Ne}Fn]AR=`zJ!/fh8l|)ͽBg!]Lc,]Fx0oia.만vm>p۝ kfK`tgkSVޞ 6U^6| *6ҏMJ2 /@e.만Gl@X!SuQ'27%> oCx„l M +`qP/p@yrkyP­Xrxߛkׁ([yI7MÐl9GHb{;mĆ}CAgrN麌YV>R?  _Ug F6OpVwZTزgq0vVrdNbj焹 ͦܬJ=ge#4 [Z%Gm\ly Y­t_W> g{sprpOπE^܌&ʾ/4TvyƖy9IRQvfeTx{T[qGD;UޢpɿML?vmzM+ Řg#2!fEծSOUVy7y6ݸWZl%?NiFm>1GemqˑE(6ݕcUa,iixg$?JB? 1K8c_%[aEi)9 !S8>ߪNbS-? Rk%i{姞Ul&o?њzk^!;>~E>ܤ(w+HUJC ea*2Ng;1"XX ̅~ R93O<rOfby=i'Kx!bO~Iv=#~9Գ(mx7?OL3n,kN0 iɵβfn+'Kl/Z fgm 8#5lzBy^ ;2cؤ$RG֫ l? Ym5.H~xJ_ғr\aB6=g(=|O ԐJ K% 8H>E#͈1T6!C+LFu[X`ٺkP>tf]AZeX c1Y99ɱmW tͤ#ۧ%>d<xJ/Y'N8~A߽{zƉ# !x[֍ uEYxPf[z_E'^|9me'o޼~ppQ'(Mug[`ca䓊I֌WkHBO.[%S 5 HMB/t4KƊ'p7gMaϧ̔?X`>k)zqFjf:\6ݨ(Y4ؤf}#/WjIcj lw5rhjۉmNw:`ÙS_O(jvїS[ٌz K@6xrg̭қM4*/qdg}.pRIw+!r\}ke {G&q'ׇkNaOپ<J3Z|ML ү !TI(YfPW!?דE}vϝl %;zw@OPgxŶC6I62vq!F^m0bI_S)kTmWlU_[m[R\ / h8 \ `2gJYLE<J tjͦ+#bfT;%@VIgźA2Qp^JR&oK JP s}V]dz#rsjĪ!ٹ=J}3o8 + v+r5NN_ Fu:b:TsEby"R>k y_}$҆/vVdn:am{ + Cj~5l ~Sl,Y4#Vً|l|?yE?.bq2ӮP*䷿wf*aM|m1 GO ҥBїKfNwߥő.qpQ-*F7WV>>0*"X.Y,_ܶo?Hh?f}RZ\E}Wh"gOW¹ -|áY6hrC $\l\cF<d-#j}]l"^S8=%Qee *[*/m?"P2Z;䋺$[+l^7 ZHKtט5!?[.& "i1=MȇfnL{_%~RFX=Y(s`mZۥ%l?I*!JZF^vj睧pr%Ij\nah0M6ɺRpZy5͙ GugnP;,ۛD^bK ps :{M_<?*z/(yy` 5ftH [_ uSuZS~ꏄo|?m5m'ضw M!@λwjܿ]Oe:i.܆ǘӾc'n m^(pحqo8a.`6㧵ۖ~\8-gnۂ }dxuOjnF3哯c:Ǯ\mHAt^L͸Jm^,IYg[X3&b0$y];O)s*wMwkyi>Y>Z@;ZbgCx檝 bX\l6ǏNm%2j+ln1(Ll;+&Q[1#6r"Rgz Z3m̴V$+ m UKϐlU--WmՒ i׀q~L R+#fIvBRU6'Ö/\I ٖ-dJ үV%)>E#e5?o9ݡR5:jNF_SIw-;5H}lqb{E;~_+l@MoXRb۬[l.BvJyz^(x,GXs2=z$ö3XT؂vl! qkq?iYy?%f3A?%Ra35)3R%oZG^/#ZW 1Դ~AG;c@D؀'1m4FA6ظHl$04  %-+p?^fu{*KhSi<Rm^O^iQt[{P-řjfe(T3AeR_# dsH11h. C7w خqAZ+06rp(+SXb%Kv 6#wIZ~EYn6=RYgnRFAtN~CCx>/*C}pԀ_c[Odh\pyF'tF #6KxQ6LJv}\Xkc-&`oF]ipEJ`I""nE%GEIQ(xg=GKy 3!xH| E2BoyDN~h9m%4ˆk=&mpsr7o X&\OBl,lI*|m1H6w_$ېl-ǁ"nCH^eT/17ְ#Hc=-6x!rBfe{J2بlWK+3Aؼl ]:PMN+`ҮC(O᰼9K"WcX>18 ÃM ѼaE1E)lX|)`Zp_S Ơ,!'Jv{tzd3%U6B6ϊ P& |qL#F95b 2ٍ5IĐ 47ah߶pC`cI.6Y_6XQkYK`6TYUBoPmms51r"!Đp$t/0i&LKPx`:Ѥ$ 12C/>YYPL Xl󟀏I6Ĝ/r}pB~d q!ϵX+JzuXZqOl_Sws[ETEg>Lr!\6:"PftѤ1m|Z3封QC2_~r9C A7q ڧ>QV1c yMK Kp`2k8 ї !~I[JzcXjnC*{#ܪ j[I.̎p:`pǎxkۼQ%?ӮwPfOS\|Y gL&\Uc;GvSJ\n½ o^VHcstͣ8_gU2n\iON q1 thC~OCߗ WdxISbjSOm 7\E tDf'J:YN)sо:w9v6ph-#䋴.vx1&j|}hej#;#]p3Dd-hؖd7.T[نJg~))p[iTftփi$Ņ"ޚ?ʼ^(+*hwػ\Ğ퍜4j9- u`gPwҗM3Wqh9\kM9~\maRwQQP6CvG| 1نnّ=v]d2(Os!5 i< r2 ʽ)ģCWc7hLPi>,} f*@ }t,Xr!Jbͪ``}ٔ0pXm#F$"u"ƶh!%T7s~\v& t-%">`feoXǗq3%qE5p.M+qKPz m4̙Ϭ ͍)k>nL|q!83/%JaB3e*eh8|/cYh_ ז2 >|v"-Tv@ܶ"(s ez,aD偦r5pqF<ʘA[q453N-@(5U­1 kVUIO7^۪44Sal8ZuϨ2Cơi8e fC\OaۤjeF/=Zޙeє/QkMxI,/I/w@JQ 7{/PghCy.'!ȋɩ1@Cbl8<676~v[4y#8i +ӟGvhp9q&Dv Q PpK isGbsvH\S8=mTo35?zjp[/)'=T)_6m"TT:)_ζ6PF2{Z 9:7|vP3bCPjks:3fj>ɔ|בݿ6oTmݻo]JKP[q.(a*<}']rڵW.?ydڿ`}*vє[ca/Qͨ?}2~׋z7BV})b j\lQe;}ϔKR+ܹu> {Kۦ_<dڨSֈ? w^eDiC(lOG ج7[ po<s邦ʇ{twi?)yxtff<lM\jv^~53l_Ir[Vҹ~`Y wwݷ<+;-ޚCOYf8/+8wix;.<= 7o>|lwg\l]YFFrv6 mIcX$[?ҔƕTJP=S:W;癗C|Ul'3cp}OI[-ڒt۠%@$ÑCжpB9K_ECܾљy>D6g+ޙ|bEӭ8㫾ޯ[V|w؄G]m؆޸w/GtkQ[} o=姩=G0jĮ̶6,~MTA~J8@g%ks&Lƙ}-QuWhCoV@{ʯf̷SͶdrܬ_A_ދ( ?l џ ^ʽ&vRa5^ӅJz{ZB6ǥ?T8ʱW3amtbsyϼm|X7}3'l_Uj`:1GOA6%6[s>7vg,lkTKo<=tq~O`8$K\l K/6ݗcSFy5tbkH;3)؀0_Ϣ:d'aoN[^>\[ ټoHpW_WK桚zl/kPmUȳgyɧQlS==ObS`6M. fik17s_fap%{Eٹfa_ `O̖Ե5*B<OW*%@l  Z8 Z2c S;%aApD*m6wPI6? \rpw@ eJncQwlErH$DG54OS3^lmf2lm-MEi||gwG XbuPR|:Ŗ6;z,#m 96T0ZHFc9Jm\ 6? _Y.A|4R/z֫qnǖFQAls[ fb  {OQniMM,ޠ[+{&۠O ԩ.PCIKb{+*>4Rnp m$!4%=adnjHǶ$-WQ.IP@ؐyK$ bMcemz轠WN/Wmmok`@p;򳀎cW+"V\p3KoCft8mF"|9;!Snl敀CX&!LkPH8@%_ =9`6Amԏ ,~˸z9£,CHtmIh[ \l[E~c'/t2r'ZWG ZVxmA\&6@?w|-@^_wKz@\ld?ԇ/Ym R7RW~;*XK 4m_q>ogО i%g~8~46G7~Sem o/6 :^S`V,IypSb o[.6U 8 }g$3~r*}R7~'H.mˠs$ҧ-~%Sa5-v  Y W?fG9"bݑU]m~pC{B7 !B%1@;OV 32n>6:P5qӍG(mz-x X<|\׭M7z{xmoOGIW~bCG^~g{WC%ܞenXW\ۊfa TE=wIaT6èU.LG1m}/+%(ϣAZ"]l!qT o 6NeDb,e6W~a͏Hh;d܀& 2 >.⊯lJ'Sa0"g+]֕OX-' nM.^lАf#Cxifj3Q76:J''q6Ű%BX,i)#`dԀt=Oץhp#S ܄::UWE$eĒmU21O6% ?h5a3_[YO֛ קYhm\l>n`[@j RXo*V:4z;[AVgr4қt8!Mk Нm]hsUAyᇟmDZsnaZ{DY5Bضr g'F,5xCcbfSU*a((H{զ,pxK' /ݖI^wiR97PJO]4dǩf3*!ÑHob %&n[s9??_= GPrmcBLNّ $p˦fCe@IWjW~8 !Tư咙p.m &4`jDlYwWEj s$mouH.!dH Xú<^ь1v]f l:[ 2 +? UH.~֐<]س/j¸^m=lu`fڏ-Mm8"gqaM 6bcY\' &1ڌ¶1%7WYdf;b=*'oϔȌF\r(+G\uZ!j[Dm[+? uTv|?hIQϡ<[frJ-"/i"6m-QiCHըUL23fWf63 ߨVQg*.v>s56yjʍ!ۼ8p5vLi^mX#1ڡyEvr~v}lsʩj*? MXjh|m`BаT+x( FmS[Zoys4QР?A;#kF3dQzBubBq 5FLz ʓwûMk]fPwz〳/[_^P_YwI6#/3:N!TrYJ߲gܩrtԔWh=;6tJhmp cuùu\qZmv}ښJ:dp?QlrN>t!;sR.`9e'Oe5~J1&fR0Ef3n"!Z6sAe32|ժZU6y9f: [3>XUXrņBlMg\&l2c8v)6Gf-Utd[Z$\c2ؾ-W2<(8G[uwc ,? ^H4{櫍tpvJoVn]˹Ů;94N'PMSŕ-|[Sжy2cl2X6m,FKՋ݆8&ŏ&qdӤb`+4T,? /RӴ$Al"[ n'ugtM+c eޫq͖xl:76F}l3f-ew [JwTشe<+T.6+ͦ=%:" hCwIAuZ՛ͣمTjz냅^$$^[8RUWϱ^u>|{;'4,('K۾W/gw6,a(I4Đ`'0ԅ)u+(lea l*l>e6cvg~ mZ+1p5_NP% B͚KyNU8yU86c$OHeKUBc#3$.~{Eae֋ ֚Ho6XkX ;k* ^g+{WNۡM"߫ђTUוm(tֺjBlF C5MPe6!^byDa ۗO_aOWNtTm]urwb)y^z~F_L),tag>uRc7xWmCTYK~vlj\ajIURe6QacB/1k /N~Y!a<}}-=B:G -F-L}ߋܫʇsҍl!KҲe VيXKmne+vJfHc2S⟅\FO bN6eDSu8Qlk`^-ǢIkfD!IkSg_,;uRuo3bKs>EhXҽ7\=dkw:!/mC_I>q%OC=B3:$#7Œa/&eR֌c_6Jr6j@Km\ʈ"'KȌ;U4GQ"H\BZFܾ 淡Y li vF2,WzlY,gؖ:2Ty^mo*ᴥ}\D`#AIʼnpI[dٌ41iCiҶT l'VhK^:2]~5&_cR~~{6sM6\lQsL/_vӋBmWHii[!.pj9G#v_?xklC91?% 6x~.R `Ժ%LEO;~xzGƐf~3VFvi #Qlk\e+|bYnqG.z-eܕ7~.yغ?¶;N6fl?ZC#;2olm﻽W=1muho1^F1ې ~ؠYx-a6>y^P|~>}. 6.~җaC~9fdYPm 'yՖƣkUi\Te:D<,&~Q`6UÀ Qq\lhNW~`B̶bYH6%u X鶞4HZ;󀟔|~t+dڼȆs)֩O\ \l_4ơo?4(Fl/Ӗb{ULm Aݜ -[jՅwQlA0{ -4t=yڸaTU9o?~MmaUz|:y-*MJq' eCI}r7+']bGtP-% 4gBlRM1keOU)rT3\n6HVb6Sbۤۗmbl&͓K/6{~FbK_lA+8wGh2QK) Um-[}=c+Rߏ?z߿QO\~SV؞GxxR"iQ'4/WLF?,15--%Rj9wm63-9?Sn4-]ϡJV%#oV&7ގög73mԶggms]lA[F r$* l\l\fd$ g%֠|Ȇ74l--0{FaWu\`Pښ3sfUrqV2/Uwv')iזĈnq[i?TRV4ڹ4ؚ^O!tl\{dkBeBmͮlli~Cw $k=+H-IkeZY03헢 [#6zXydD.`{B9ΟAѳ&Fsl3Cm&~=ZzRF螠6&H7#Jk۠e$fGॹ  Gf5TЧs6 ?+/8)]3;- n#Ξy6jxa2yכwTi(bD[,ĖDۦ) -@'M[86Pn AN㥓n;mSFBjo2[q{Vl3qYɆd'}E;!c~J]zk٨^ gڴicc.;k]-\_T0GdsԳ-nvMHsc2@OZj6(6nBSF$/K%MZaPRl G]u&w-G\-mYDv9d怼= t| v .MT|AlY׺MՍXFHmupFV kHpu:Dln{7Cm$[[ʞOP~J͌Fk=i3bl T QlV$mx炿b*ߩ[=\mU#B7!$ʡ3ߜՖqtbkÖՊ 9>4l _F&.~؈egxSd x@eLqTZn0)'cOh6f`YNa[;~LK̽(٭ zZ[$lɶ-lV&;Moc.< 'b1ҬubkܖZ mc9\H2ٶӃmmV>6m=l8خ]`.6Ѕ^79VFry<#П5][_>'m($l}Ns8FW_Yl/؞CګXxMff1 ꅱǾ %W61ω n62OvWoEbaKÖ\-M2v 3!A?848ͣ7=6YH6g 2Y1O N8u1l)ݔ2U?rGT[zoѵR?Vͱmc,#T,4朎bcex\Gl8.]ՉX{`%pNzlm!J @B BoxQK\628Ds5x3-!"?(TQw[K2YBu0XE_ eDO7 b֗?P.=#p onAH_)ۅBSΚL!di>Sۉ۶Zl,; q54tcդۚܺ5 Սlc,#BRep=i۶y6xضVB=c֞6m1yGM 4p=uȤ.ȶ6/~X.2k8ڦ}V7M(rkp5bkD9#Eq)#bV-)[zY*PPwZzV:F]W#VlGh gj̭u)tl-2swX+5Mh-1ᢶyjvD<[ekކzz][6.eDm4/6k}6Gg~=T'f6%CO FWor-mb )Lr̀Z\Z[ˠ,6 a[Kpߒ"zD^h6I*rLf咨m]2ض>盒SNmŖjFmrKs/6[k6gO.VEk>9gM}A>S6Om A2Cmz@^1R7]>c$N:Yd1:WM6cE8)=q+s$JUgUݩ nmgM8K8l85[~K~@:˩l< i(\m ɖ7n;nܐz^L不R8Z 4gqJ|nQ"ulfAJ,'re<6؊<Ξܸԕpj-r.iş7_mCu Н25~"QM_.G[M[7Yx3im&c6帘 s>OAn Խ ['_$B>B|ȅٚ V~D쭅nmo#r\s( QO63Ndj6L?%8q(c`j? Ό 1׏eH 6͘68Kx]3Q. Cjk"q)V4O J}xHj28>6C}-:D ̓im(rpdÑdOa)qt%F[;12F~>qu#t,N颙vk g2Y0ؖ&L p{ܸKĕz񟦄?>#2< &cm ~d78C/K݆< pZlZ]؜%zO9T?;Tp丩O .KC7){x_VC`6_{+'vŘ'v:0݆O{ê\lxTb2gr-V Ͽ-$R N? h|  CPHZRj 2*5W۾إ\ՀܜF. *$x9 z `[(6W76dŷ8p˶m[~i `zŚ4m s:C]0N nE3?ѿ`01{OMXʂa,xԌ?\1Dp?8obf5m@x7=!I'ZZ-3X-oBXfLݖ}qBn"7fbv<>ÚoNf3Xs/!37Ϛ4t|jfvfZ#N&v,fÓ &XH^ޞ58LF^R̘|[e`aDJKD'GߝGŨvf>LlFF]&K{L:T-`a:x-|cόH;ԇ|lr0OI$[e`cEvO [ޖOu!|$ Ée a|-5zoskA=~-}:˛䥓 k@l-A*ݫS9FdBl sB۔qO7* VxqK\l.q\lxUʮ3i8mAZ[qtq~ne3MޙAy̖<@:߀t±,]J `e R>lsh hm.vm&sH :h, ( K~>q$+O,P?9{w]It><(Fy,H:]>Cl;=e~nlFJǣHj  ϟI e&tNt n=fố4l=~#_[(Ȩ?dƷzQa o:v#Qm'a+ tۏl}AAq # IB*m4?~F+8 ~g@pJym|Da[6mu$O1JFultiC|ljHXЇ;jю{dG?8m4Þs?aз=.\9DwoD`@uV^$ i_K_ Cl KAǜD?s (<[a͢w@s|{_2<$VŽv o+6n@ɥە`=ڌ=a$F >DD%wUM9 ѪH\m+lv g*|hhRXv7T/Y ;ȟWV6Ab a @6j RF@|vdZWcğK_ ved&:l=Q94u‹q`_3 @HHlDI n=gC~zcn)Gn;ڒs+J3Ee>fbIki~l4 DL2l6K/&٠lm~ -O&XNDRuI6JH#x IZSV'{>ҏ}f,Zl;LIJ[xL J$/ u86Y)LsR8Uːn'N|iiUp˨?ByBy?w g^ntH&Uk8rԅf>.UWk_=w*?&WoJw1G9O?%,|,@bn?w$FYFXrIvd[DX4AOzy@OΟ SpZA_?ȧoN6dPhXVb\Ph+Yha4BSMeDj,X+Θ- dI/6Yw}W>eھvܓCokr++ (o{'rEE9->FtdCl\l^4͇V+sLcm)>p! W_l;2$zd 2I⓻` Ĕ C:q%0xɫE@/oSd+MC$Pڷ$.6N6}8ENA(yT-6yrM"tp61/qYC9kOCYJj׏ }7}]qIzI =t/["@?#cTomv^sdClks,8ҔfB*]$9ݚ9)ParH|]YHZ36Ze=mKr/W/һ+='5NyRf`* v/%8[[BN;q0<O ~Ds⍲~P"5aD%B*Л GU.)+5wr*6SWOz|_ yv#?$'Նq>C=<@k@.`-ЇGZ8P_WAg([C(h879Kp*>Gj[Q 1?@XxUwEca2:~)}m|oݓCWm^4<3v~J#@6L@EףwNF[>O$<tT=UCU[maU@ύw8>S-l8>JKl4 6Y8dFjlU9}Z5(_'ac>< ?'LCm"4̞r>%,CG?УHeN; {Ɋ lnm#%;|Ĺ' 1ܝۿPF=o^D ]~ ^?Gy ?oiq~f{"lOebR&Gwrip>PD2a+;r d >gmz@mCf)gՆSHZ{d\L'4 5EU?nEPN~5Cn(/*v6H]OU ?VmaXXOO$v`hxO{Hԍ3}%E[o n .l 塨?(1{8 emMCSAS`N|.;)8N)+l޼lAAXL&4[=QԱQ; 74;<\ᷚ51E4ߏ6-+,1[ ydc.Lp]T8IT8.!uOc>{x2Y~y K/mph8JhqZ> +%JBhM,f\pɟlPܧ8(fЍ0Q(* \Mٻ?rS PCdddB$ѴcpM5A2>A͖ Yv~D𝺱.R~?e4˭전ijM6bdb3c٤[ 4,F@Nd^mNlNWkxKm)8Ndׇw;U RRWSx vx[ͺ mgmڔe=! %, mCڧ??jF6А1O*G 9] AZUe6nllT "ܑ)FTl~FYC#> ʘO{Wa_3Ly:Pl嶊 V q.v㇜sLzP LJVc#| O]WZOgA@na9uWrVzCƱ;?7hɗg 6ԍl B߈qޚ3eHaA!Y#sc8f<;ݧ䞀0փ(K/zv_ bad8sbn/xC4-1_af,7/.6 2d$캴iX5מ)zV-9|Ȍ{O6}Ho~T;Ntq5곣w@e#.%ҙ6]9IGc_3ehP{%`A<-^6qm}} {7Ju AynB!L-3276 `81:.U{ֆ7|&_sFٚ59vGq4M/Ucq:^@X6qBĥ v̸C].%YHzbjzzRUE%% Aw<J] &PԌ̌7"IyS ̤Lטc6c$5\J=ddnJZ%u ܊Gۯ,r[Y7yN8toʹ iq<؄wi4}RRS^G-p$\JKljޫ<"8q²Р{#7tgӄ G`/0? Wzڳs8|ES.64=3I6UnL?uVٜ\lAؼHD1ë3ϝm?UcW>:dS4secQyW dyy`]+9\ =񶾹)O1{FDP[e0i[ң +֜J%>:,ZFT} WGhdf%= 15~L%lVH|6ׇߗE ۡWsiSn%OG`b(x@Qg!B?_;*羏ol is樖Yd=k{D) F7fƜqb,Q휴TIn֔ݨY m&vZF\3SfL= g8y@01t|XW -J>Û@0~mwKI=ȑvvnۯeѲ^^܆ʙd-N-[br- pԫO,UíR?EbYȻdbnP )0sXF;. ʖ ep2]7/ڏDË2LaiLD!Ǯg(oMSf 'nmĠA9m\U$oY u$1i`nUsX rmUy%ԕxm|hK;E/ڏY5$uv iejHXe[n{#3FVAۈQ،UYJcovAt g/ V뜐96*&B^8E3 uE<1 #fcN8UX?~q;w4 USr|lyʆߖt!1q*n]߷(A~'mY ܻybō³!lTLh툮W͍ƿ?@6>I=N {׉-jtih8a_~ 5HHFR㘇lHOPhXXXH7Cm(UBa>R++#BO8㘧[E?ù=b`9M .g=q8`w_C5{xFu.Kd6|F(I8}!b3J*d4c{AlF#Rِl& _̀*صk9q+[vZAN`+!r[^]Lm@Pč; ^4 {:4ܵ&{5 b1@Irݟl6էHʼn R,Ţ"{"#Rʸ *-QF4(~`(~ ~ӳqq2oMo.ZufF.iRBl$HBfQ1#Іீ,GeeT^ 2y{K,6.b=D_o) xo螵 {JۼVƔpd`GKo4SP{ǣz^l.Hs`窩r8B?mOn5kX6/7[jǎ[VM,-foró m3p^Kpg;>|tufi1}ջmW=W< i8)]*>Q,SJh#ڀ~PdnɊ?~׫r|۫&wӻvj ._c؜e?lv(2 艛}KH}w m[o-͍͋දDb#D䠉 !8h< I`p3Y,.긑p%Ej`hBGP?+c0GV7'DŽؖ`mqDV+x tg<[=ʱ& 0^}2cECmɴ:%݄2>] j+_Tjs >SuO2_Zf3![EN1I6{VHp wg*T>|AFҵf#!kyvy j9,%SKq;=c3m& n`S [ޡdx"8S4Ux{Dw{_ *-ͦBꍬ"|Kqr1%fE$̬h"n]U \Z'2a&kTO@Nm~ܾ瀺ՆUlo>DԿɃlhHvB)#V5sOǖti)]>>0 l*8.}AiQr%3 OE.쭲/lL766EsZاuVi}Y]#dП ٖ?ȡ9yCZ_Fi_~2x0~u~3J3ɯSj`)n#^|CQ;R\lETs+[{bM~ ۰m+0qIET M!Xqb`'9bp~/!pIna|z-GR:A6Rl 62Zrs6lɧkOvoO:!4&e)ӞYK xV;FKb3C֔EWqUR;FMWA2Wؐnledh}2%*uh`Y 'wzϷ 6g.qe .RsUN_ߐ}Ȑ^^y` bw-opSof2WcOB7 Fbc+gCP6x,cmG?iMք1~h8󖳡W [giV +] փ#E &\AS,Eecx=TJRÍ2|DS2Ɣ`sփeG55|,hxz4 i!^^ ۃ m:T28 mc9/vnmÖT-!Dr8e>QIh6MDjW˰Q}=<+Q1EhWMs-^:. B ⠥j9t  smx+y'm F#}ؓɦ#VfnJp t??S#X>Ex(军mpکKUl6Mxk׎0Y63¦[E q%m-Fˣ^FH&a` jpZ7b22'1+\.\l?rCT  9l?j7b [,=k@3ȱ{4Mn"=(] |Ƌͦ ·M|@3% 03jt$5н(_JɪI 1-{|Fy題24ȏg";Ycj6,3d Jo6@ =:Ca7=mlP!7d a,NkU2 kݍBLYR4fSFlINك ו#}XNű錆/6 72gV]G?=S9w!OWΌl6iKמvo)m`y@}n2s5bC&ɩ;XߓSfa"=9t hQBIdb AdAG.`X`ςL+x[3Wvߑ~_q٘Ʊ3#alm8'nџ2#x*_$@,g88GtDf 4J͎6J|6m¨ވ5iSzƃ S5+ۿ)x֣(r1X.Dh62̖(3w>,I QZ r*p񫞄!R FÎT_Rڟt ϥ;O\l;6͸[fxe=Eĸ|es^Nz%$+=Mx.>b A,'<"okˌS>}FȽ"+%=+#zLB"%! .,8y\Kz(<˝mdM"VŦ/\.qhbg%Jv7P{AJs]W9T'Y>8/dhY -îiF~R.ɲ)?6Wa8JVB;R_xoD^=ďMq11Ekfޫ UBBl0&lJ)[N)^-U, m%ky^lyѠF1S=8ddǺzpḻn7*3./qvQ *.~T]W^'lr*9;Cz#^W$!ߞ2ޛj|ʮvNxzTɬeї 6i)R^ӫ5=ҭ ]bc0m\2$7li¦ 7<'t;rƚ8_ r8 h~zȕYC^N/*ŏx-S&Wv,@2tdK=}9Igl"{O;n~R=86,WePW2Sd;rjE1[L,r `wvŶb#*b "`D`C޳Brf'ON5.+;%pfQmq+.cb:m]!GV󁚐nN}JL'fGG`K=p#u0T y\ڰ6禶ޫw_OIr;a!\70 Ox!MH-4\QO`3LGO2kWS?Ww/kBmn``;ן Q6-ߍU` }Vfї gjs?yG7-+HvP؜̓*+*nw4!V1hmIs"nx1Ui& ͋7NrpS86gV2jRŏlEč:l^\lnk~@n^[=CzhYޣmm1$ F+hl>Yz?\?/(o-]/@7rI ŋzCfgP6Lk;)c-bnݶ'U5Rr:5$f@.F[|vz{!u  DİTX\0>Ezp0c\Yʛ1zݻçW5s{pG]ۂH3U6g;`>{ֳŵxUupEQ"Br/5+3L?.bm$=-nE- -|r 'Iqsrˉ۴9)`$)Xc(17NEbMQγE k3-%בu,lMRkL=?,xlm!9Qv'4l0ufΕǭ1 7jbHnQA(eM:F W% ?øVj;i@n?"UÄ^lh%p>Ł$7u"SL71 1mC O:DԞdؐ(lÏL=#R\Mk D(`,2?$EʘIL(I?Yn9gwF cAeA7\Ȇȸ5őΟT_:C21lSkL+;-+X5fK`Qa`ڬ`Cl%̦9MSOք7J1+[A Fcbd@Tlf ř'9Y-MF2O@&gQ-Mf*R'9#vOmܪ9'Ec\!El-LH."Xu{:,È)mST ƏfZ]DћS46r :t'bXi%\uðbL\F/m@ *W1́0F7 @UZ;ec(~%oJ)3^ [rd8\pʖKlQo8n}a٬ fP5_tb C|U!f#UB5 ?\mqpr&%Et954q+MW5BFHCF 0T4Ri WDQK{=US֔+ T(KX)mwJ80:ܚKt_mD:iӧ^E !h[# 6+ VqB-حڇ] }/[ 6*?nc"jIHξx&\,PJ`_$u dh^OHY%p7q~ 5Dj^?3QeӌߍB>Iԙ;6-M5)xެƿ OVKdk% -,µ+Þh 1ی|v=AX-2w'&^mӹf6&UZj7z Zfn.&hu`\WѸA/Y1D$EbJ$Akδ [rX3vYIc\lf2jC$p4ߊ{ LFܰڊ7ƂfaI-檘3-v@Tx \mN [{QNGVA/YPR&v3g쑪򱰵6%@[_mo+ves½[86>M6:iw>sv9nKHжHeUEjH;[A<KNJm GU7T=E/5Q'ݳ6w;;cs!dᆢY&\*9W8G$U܂NJX·4Fzς[nˈn bE̥$љIS?kw3P=II=ĸag^yF Vl7^]I y3׋1l#医7XA(;oyXulh|b+=H6ϣ@I#kĕoNun/`<*"JsetH(rrjGܨԗB%AzR [esDKaAt bg`@&gA{AUTAg[L8ii HϠ7ps:l&msOŨj;dy\}83i}GL^r+ LveZf& -6u7 mEN a5n@s v+[C]uٽ:8ڹ ne;%dFUgu~190+%^fX[nmvޕzXd:q !z1dN=(ᾴI*e CϭH̺VIrwtd{;;IϹ*(9z~5.H/v1餱m<1 1[=pTR0֕fЛ9n<-ڱ)vb:lf3#MF\/?Az M,^[V-R6Dէ=.1z>j n*\@zj[" e:S,lÔ)p1ki%-L9Em>2%TL凋G.wbu,I8YfxotB,jjjXoja+qvAᣞlsҼ{l7FشK 6&=\6hwCsZeDL@j7b ϳ<<c6fܷY[ٸGl|6:(h[[m=]pثc>g:6Z9!X*$5Rѭ 1Ct nM#u`8cӒ8ea4[ K*0.7" 뫩&Y0ڽ/N-4X8@ۗ!89 K%eP $}]!\}71Z1 irڣh.'CRh8rn:ZI`zµdf`T6Ƶ*qUTSTUCb >6 0G6!\l؆rq3#~9 .# kj6+"6nkK=r@nurgy$Sc7E\ӯߘR h|SSQgUx"475ZR`E[L3-cv({WG]I2.۽A,pT\EӴBNN4hI 4rA۴kaYْS5(fWbIiIk)twRVlAE,JϪB9kl=6(13.zyTF f&ms~OgC=>.fqFBz2; X5٫l Sv5{9 ۳:x @ӈ[çc3oaC\r!p3[7zR̗q3j|'٘ƵwPk+.oEц0E>K3N@~|>;" KC?ӁQDY Wq3Xsg+Jm"g{],o9H?IٜE{IN๶5EmKbնk,MbK18r-r} 3yh-ΆWx,kwYh'(^l#+C̨O6+A!!(}Lqzo7DzȎɼ@Sœڈ4euWK:FQbbvQJ Em XW(Ϣ8-ȑ.a1p \Ѷbպ*,AE9Z({G]J3Y6u;K`Ů`r7_lz%P0 ^cyqэm6v2^3-n\lp#ds3s4"(fF5żA}T3f؍B5>측W}6IzDF((:&VhshЯLpjMaTם,̌}~ۘ4Pzvk̓є~Ϡm>mR;9݇ J3I6O\`8L:m d!<{D˵cx([\l\zE z6[ߩ?~!o<[9"j %jn 끹i6qW<>yf&X^!-^8K覌C5!Бm;^.cb]!xԁdH) [uD"mN&m7xQxHvPK&~ka糔;N7,0>`M@__>Ym$B; %gQ)& jx ;u (qUTJتU6Zp'R$j>tULD-OBo4'MM- 1mv!l+o\Zmc꡺!~jO}7t܄^J$Rџe(lX5rAz]l$ꡪg)w\ȗ$Y6k~~O!O4đSM,}Pp2Ɂ_"%b΢ټ O1lūIݭ8и:gWI57F,%ismZ<ߕ Mgɜ@=mv Ϫa{V ϠS!!*eV1A;RU2?]Pekv[SDSH)MEmzuI] ~$jǿCqbԽ p1 12L<-X}-3M H8gWs'8S!+x }n0xA+&+!M4-N)V>SzaH̩5"K<J4d}5/φ2lo(hr<(mQV6:`}8JMhbc`[}(leZiÙxG Y'.Zbes3}-Vs%֩ Q5XODJ?69ϒgi06`Y~3PzA&`}q O*N '? O#7|h6s+ӷ4=Bƿ1٠Ff./tN[&g<%FiJ/5}vD'u͏7FMA. I+XĶM%A5W<:|ܿ·EV1T9n˻'&W+ c+q3b G3L_9e9 8>{#iWC0N>xA0sڡ, >KKZw^,+L& mr<µuiRBi]O-.¼QɧljQ[ZTDoSE}$ePLLcgd?mKZ^aibK"'(w-qoXufP%õNl[]ʹ v,,C $rmA'MOk9E?_~mC2VsEZUpHoj_%d+~d'l)Zr+ 7dz[Z,:]5k:1SvP#ֆJK0|nϗi[M] P|NY+ݙ>A!Z^r.>jM?Bb3f9 V*V*^ 7{ItDGPI0g-~] P1,0̸s J — "A'tPzR^Bʽ 4@e #ـ|HQc^t'@c [l4nS[6 VF xK Od\04'[vR][S@WCmŮv6i9n<=$]B؟˩Aq*㸾T)f{'$8G&<ʻ@S86(l8Jq9g!ffdxF:!R 4&)6ܴ7hFA߬$EQG$f VৼdL^ӷţ[dA= 6zY1C[+d͗ܓ%AO+  D tM(=զYATo]bxAO1!+ZpzewiI ),RcJFNr 9Hfjn; LjbνBl?}2yW7B<~xv>R_2 f M_iq6S#k~!殮v: wMz\Tx+3G[Lȶ~NU|:X6lAݞBVQ+ޖ1܌L j. FE3򹁙3G"t(mD%*d AErljDơsQz#1^VdgF1K>wc<@ RlO LoO-bЍ;lڒg [IR*M<ŚVը'2MLqq,?g_ȶoTg ^ƃI蝯]muz5nZ@6r+a^f`f ]XJB(s-QD|2%y{¦5P`n35;@;,b)hWx,l3 rY~_ p-1`pn,۷ْN^H: aWM,L.~}Qse[SsK=Ië'72{zxc4M~ް_bu۲{׮Wu`D@6a`!UveO\9X0]]gƱ0lMgQ`36PNFbɽPU(+3ʫfeѭ {>_ᐟ }ZAͧ F5%okG;wT /}F B\J2Cڞϝ88`@Rܼu0SE.+f騌 gTEw joJGe$j޽[3^CN6?=3ʿRnG/7%9}`Ǘo`)OY:eɋw>H|="4Ǐ]^<cSĿz,$HӽMuF-rc'֞B9[T|&=~ԧdN"Xŋ'&[JL,riI#q^^Y>egL}g;rϢ jnjRB>Ij$ergpR5-ɚk^ ϻ }F f1ϕzJnm^\i+qpOMrd_x=K:*#!IGd\c] Gw2G˅" ̖<\1e!g:{bO5!"]Z|sxmGTKhO"GU9t}V} W8hm6.I򳈌s (N&:dR(oJi`E6,fSUfمCTl3w f~~ E>=lD>[]2j;0fvbDG  \hI;y4`tA$T|lxRXiJō^[z>²A~LAd}̫)@a[ !{6p?jj*2m페ˌ>?}Ѻw~=e_B*H&1˹)ƒX'ʞRƦt@=c@_ؤjtLG&3rniS!Y%Y2qCDz*#!jQa l#(ߋ*K:$z# 7l\gG)vOD7`ge`i`-;H`8}`\%Rm{Efoh4 ڲо7DJ[fw?gp1 LTߎtgwSD[@%*VSzfD=DFi32NfT@mJCUq vcTF@} a2r'6验;9rQb$7>ɸ!̔s mrN:Һ c\`ݯNdt>H7?ٯn^v} 5B8z'e6Nxn2: xyNFZ)\RaegF6ڎNcJ4sLG_֤JSFUZg\Q4۱yM7ciKiSC{}yg磏in^&O/RSS ڙmD-dJ<(FnnS fKvLW+52k/7ū)#>Q}"$ XQQ:HhR!wf4HRQʌ# (W]aF)_j{R@( 58hE~"~X@ H| 5 hg-~OԌ<}\N6Z )ɡ=wv 6+<L~sdY:e"p?{l;EY hmY~OMI%?BR uBUa<.P)Y{ 0($]T$ oMnR4;(rzuPS(Ɔ_@rn{qz1u?yuOn5&/2Bx ]2_PF;Sm:ʢ 0C! SBAs6!ټ(8P,F{Z#YF/u9У"6Rz5ZEL"2eƈdEr' 'm >$]gei@/)slh}lOe|X7 w7)#`?n0Fb1查)Oy%P*=ӄJKYY2૙/r 0YRk&f V(b_PQeh8| G(Zk s{3{.AV  k?B{i)p0>pe sHQO+ZOpw$=ɒ )/^nTRnQ>~8()@p!$.Hwp>AO%"e"7jnO#Gd'CpB%BRS"6[1*I 0nT>+`%F,qPq?`Խ kfD<%NHkF{M'|L(^j?E[< sؠWHzCOE'FP5ȔswtUS?Ng4 Qg7S#@yL99`aֹs#tt3rYSRM>&ٳwp>q(Q'͙_/#JXΆA~_^ 0g sa),FP0naJfWcxQ (NC Md) ?D2g癩g@W0]HpLR? ̰h iޛnD;R#w~n /K(M_4cyς|/Nų]0PlŶ =j> !pLzøp?E_GD|S>ǨᶞRp{x+RRΗFS7snv?t Vj76 ĺ@g!-wBs\&1 "䪸 "28Xqf W6 11z{жߥwi$7";jXN-i xa^TF8H՘jY$(=o )4Bb/к4g't&:#~k(A쳐 ђrK=Nˮɹi 'R# 3-"lVc~>9FQsB m#qe1r%Uֻ| ʹδXifI+-&8d܀-vMs{6MSlc K:MP92 7@\J3 dK󷇸mE#ݏ4⚋;d7%\JgYW[͠Bt`b~RIvP^:K`3wO&:#?r=2 YOr9F㹭ʈoel0iyZ޻zh|:f)] yBp:@Bߌ[I`})s ̏ ܒo뮪Q8nUp qkJmgnVKxicT7J>ȡu%- !8U9p:Hr ;D`1y(?c,I^MTdir n M5'Y*m-ʹ %̅9_av31q\2X/\UQÎ]31Bq\PҊA+TAV~=P2gme<2nT iI8й;h`V\({cwbހcX5 .d3E6fkS?a/v)EW vh~D~{~2!|tRxf\U XwЦ8$d{{y5fE}xlm7Upܬ-uz;^Wgtb2Y~~csp[6n(6x?Bqc?1[@2 ]p"b,W?F(nn_ v} t/MZT{g3qd 50'R/\CP/pIQ5i?C nb#\++`%+HKg iHH\ޣ{ 4oz=v]*۽hN/D xNc=.糸jJEdnf9(H 89g/7RA11jb0쳐FXZ9Vk2?efBfcc0qnx (KS\|)VjjFr٤q Mqˈ];)[XX UaF0cǐZ>S~҂+Pj(K)mg}L R׏OOŨxJvxe:5o_H02z~n@%3쳐w3y j͆K70^h@!. %ps_U$3[}+إWW|zЍ.3~,`ʼn+s*ϾB VnͶ=-R3rϴ>Gzҗ,A &tM D֖qY+U2q6Ӭ%ng<ϰB.5!qH8n ]J~}RmIWYʅ'E?dh 5`{^1@\}6f$>1_dQm>" ʺ'Q$);?܁=;ؖ~m Dt6|SS#A:lp+ZSПGt,diC;F@r/3<.IB 9<- 534M||d* l?o)ɅX0P+qTº)ǺC8xЊ%3'O<$r-Ĕ|VA!W8cjp !+nJxQdvT,didKI3 Ȱ/CeFRHKq 動'+tdXMBfP ]PMCÂ+w1% +C?ͽz#3 oH!n^^\Y1W:;},UDY|<P:)0y,J }w*s[y$9d)#rAn{r]_fF QFR ' `QcRƢ]73±i4ۜXa c9=ʢwLE"u}'f+w.<C 2nn?-Y55lˎZq#3^THPԋ H=fz 0BA)6$f/k u}댜  2XP3o$7w%(i/jͲ1׸mscED@xa?% s_v{^ÂGE"r|ٚiMKIJLOH6;&T ̶ċh$2lٲ sr7dAk)JgBD b\6CqوK Tj3k#v躾wLaĉHmXw>H|`([lz) scETX܀y@^ 7h[F(\Rp7mkanvP.a+/XRL$)_AB4h-C@a$d&d,] tILkno\ьX@UM}B O$A~s:%܀ pѐ99"iu ) TJgѭVDN ˃)7m)TXv,lКJP Sxe_-\ntbIU D8<^ÚJehWx{ =~E SvVh@9  ^i܏];?]"=GI1UpSqŐaNk{?TX+SخnJ>Rj8_cRmP8%W#>Vo׀2&7{(nʼnUNՐ`dtL׃k4kyU( L(DzՏyKlvnY"ˆdmߢk!Q^!fdx/3S`?]vQKI) eLIR)6*)l)%Q\hS uT&quN"".5 7>Vf'_'$\@F;+=s9q:@vo !du|ޱ*7҇o/JM #Mm3(?l齨Wҹwct,Er1|g.ά'3 /$AW5xp[qd4uD1PI`ǚծҢ8%LrcE*Ղ0/0;ڹjv΁4$9.> =17C /{%9cXtYjr,eȴ&=&mGbEޖxL XJ⳽H#!wwMGֹ/~֫M%%'%}D_)N퓻O*m߾;`n-hE'p i;%K,OBWpq -URapXrRToܠnZmФΣRbyB7%JIgtK(%)Y")q>W _M-bP6?` /#PuaE >scE 34|Q뗏|S*aBѬBr c_'L~n'(fh9_޻ܰ%: EiLqCAZW>NNJ3b{m;z&Y$9qkrpC(5߲Dʡb"zB11ʰL<"b8e*q59b\+U((' rV˱y(d24M G|(U{OPmg@ᒥJ*Q"T"Ezw׶lZٻZ?@\O"&H%`Jm/߶oU:T.Y(̛9i[9Ux啝  jݽUUzR^΄T;Www}_㖿ZZ5oسފ<_.>A_ ^nviN>-U V<,*YO\1c{Vtq׼$}G>d_:~? ߮=jطk)ͻA踅|Z<>tZdK V(ư Cп7N+O+ﯸqz~<}'~u3QBMBiATg4W[Ush磌̧~UՕ(qiʯ=y9;.MA/~MaΎY}cΎ㇆jY-E7W)IPH0!%!)IBr M:>6bP6Ov~io޹^9$Q'=`t8r'~`wfʓg?,7B _9psdtauG,޶ij-Z:;xwoEs%صӶ?oj AiQ1A pX#>YK'_b(s@>Kʉ^ iNSO}0(׉}Kn6q*[bBM10Wʽ/YѼ1W@w1̛ۯT9acΚW6s_OV%mvcaxrU4P5[⣿e+G,}AoH_ֲBG5h_Er{1~rؓUC3U3GU[7HRTݙY}3~w eڜY]'tj!/3F?\J#n }#oʕm5WBpZn5OtorѬ6A&W-fI:VF۪Ld?ּ}Z1%Smɯ &Y _NC ᯁ0z ;GZaT/SBݩFC3cw?}_~ٻo^)$ELio<-БFHX̤ N8*eKم\N-sJ׍1FaW޳?a$Qδ~ J؍+nx:o 6HJKz< b/^<88 ˗Y_/?\(UGXPqadGr?OS/Vz͚+NE֝t,HÎҖqE%;yۧZ*]GSnuuR =e2pU<y7N{6g"/? [KD# z {[WB(7?o04p¢)!I"y F+YteʔdE gePs[֋>Uٴdk-nzfsQR緝~d߷YpR!wKGCsҗ2. gfղ.(o(YcE/-CyNOЅ Tt!{ZMIʚvQy)AY6Oho+l{HŠ4#S%V#~9hyNOqn8A;n>-.݈{>gVQp.UrHLaA(R"~3r_sφϏO$|cD|ө쒹fjfd|,+bGVܜtp6 ֐ϗ%X3nyOSٸjRN\ҝ/_ej۶ֱ<%vLx7#| - IڒJ l o9ŋW/W받}%hQ7?D[z~^m6ݞŢ~Hv[R$vu瀸m}S⢣cQ;Éy'0^^'CZί[a27܉X)vlhkT~խa=d1v0~GLV.S\#VSV"rЍ!7x$MmZx~&| !xY_$%żB-v)[>}KW|uo/}>,c 汾Axa h+[]x R*!Z8Sjn%"f5aPꓬ7f^xu{''<N8D-.Q/>?:,+h}]qg0%Y0c[*nY T5ݑp p[ U+ni<4ti?mͻ}_IJu =dd?FvFRv74~&w*6 Cq3?2Ny 1 Ѝl1P=3-}.OOy170,Z[ˡ۟?;N ú(kd߼V&ot P#_VFڭzr/qC:fHւnmuO"䦆O)/ =KFWXvJ+!F SK-,s@l90l0$)v %"n)&Pw3AyST%y%;xN\"uJ\ h\^"?r f8`1axp/vS0f?H4Vw.=iR)/%dѵ$?6y'dQ.qj:foqװ#t%t@;<+0r{yIH?0 1jt`-=qrRUG[Ɛ|DA{>JtR*I1w'ֳ\NB:PkVo2`tX~k3HBK׶ cTЀEǚg#lM`( I0Jǯɡ.Ե&dmz7G<*Q40kPe\^ofkfA֥HZ + Ukδ.|.YgP%}!5Ѧ$V7EyA%€edFd)0R3Fs~b{8'0!Q*oIDIZ5T[m>Ɇ]d@wiiPڏtLS'WF܄v, Yp|y=OZS6 ߒNnZqTȹm0G~+?Χm#"?-׷74%4gús4sY̪PWq{FOC3{Ȅ?&E .G-ﷵD rf< g$:T/x[YY,.JJ^hi}FkIV>x{\&hx)䬍om/6: Un:u?>ҥvPǚ<_S|P<=.S?N0*5E ?"%Ƚ@K35BO2AP?[_XgF%E 'CcsHxm 7(Z| {Z蛃/kۨc-8Gk-$%}{D3Y?^|D;6G/13X@(>-ģ E׻{WZϭN']gM79 G+@{F?pW0B]vΗS=&n[c RlSTgaV?g#~N.cs°+L-Rւ}5ЁMKܹ8 C9y!U)#}@ύW_eYӭVs}#'s(+ ܫм7oبxX#-'WatXοA9-e8xS0D[24TړOm;U;Z4SPKW|Œavh+%҈غZ9W[ pa#zˡU\w'he{ ܢ!`Lˀl}@Bi>MUE.tIhQV]LDpn# ]SMb?tdA Kto ĺ_`uIX3 ZX=d~qS ZBl$`_\_Q=F+.|n͖;Shn]pKlFWLG4_W7eJz3YMi٪xD(Xuk46jLkb"x ivvqdjy?H'wNS )qiv=Ԟ J e/؇n=$E1S?Ȓo/5 ѻVF. zETe*}r)+4=^ y'=}6~o22J+`r'qM?wF>ؤҏPB@Qpg=rBP?[1jfc[s.@c7p(˱؍}X^ՒU"*y/Vkal#qrnAyfL\ld'ɪOUr./.P([upa>Kq&ҢVzMUDmo@0ľ؊79RAU 72f|w~n!sJDmI?VB|÷?~~uג>6}ѵc[P>r x U}"3\To˯wT ,HÇofM :6TVWɤ$#˺DcVŲBé44P~(c5`74Ds<q"%=U+ I|GdZUk܉">&qCR^%RD8KXw&غЫC_NhoK<&%+0<{~} &|uh!q…2*4>RZ76@1y`)%T `̬9\.h'UcG 7@kz:)s}TܟQ圽a[ NS^% ]/޿}A /s/:y6 .D;{ӓYF jO9% `m7p.ȉ;sWFyE9(bçSXm'd,7ˑ&zͬu"傱&؁fp9bK/&yKj [u4_zI4* aQ/p./n4rL*hYuћ&16D6(5iYFVi@rҒ9gg#YXg 2hHOW>6Yz5_MQڡgU:j Vŧb21L 573m9 +J͖;%h>ؒj5gglkQlA+p14d2J=8 ZyVR# Xx9VJP]o1¦|\sey hSf<*e2Sc+⌛#x'S! sU=2@T ސCFZ+gY˽)(r1h4nKIM[_)SuuشvnXC`I?AϢnѪ\ p eJWWrVeO kmŧA1mZVpM(ZQdvD|\kX\!HabOui7&^ѪY#q}JUuLd!u=5+Tf m=yzK fߎ7/#(/۲$f~hVd:sl8 Ic4`&l`t;# g8BDφ  71?h33q g4$-jSĈ~ Uő(:.a@u-uϬ{Izc=w݌.ɋ\jZ691aYQ f O% Q}V ɫ+yy:4U,t3uX7feڎngo {U[n< -Tf{!U<TR z/XEdsD`رϩS!alQ<0 aZ=<1Hp;$Aؾ#O-稆gl&x@~}*怱Xx:o 99VY }Fh%\X),Y'U(Zq >]b/CGz<牓 mRK``N g5 fcmxK$kM0(m ,HpD\5/š{({GeEޮ]AS|~Yo ؔY/h[+$ϵ9a{iȞ z99} 2C1QPCCiIҘ=EkDd|݄AL+Eè5pe[4UkUԜ 6vz dXgJSAoU"Vj_Cim&֎h&PBT7fdy`/]f= (bߒh]} 8uiҵ7&7h8ܣC{)d$Zpȇi;CpHq9,W35dz |k i+IX:+t] r4(傻B_gBF T)e ~JBQCT2n6iw;ȗRvhX ' ҃W<]CYHa54qi+r6Υ%>0xJwϔ%3횰 zJ񰙿`G7,[4QMe`1tEJA>d,J΁HZzϘ{Q@{t&/-t& R:b@`ÚCO|~F_I`:/r3%jO̯q7aXp[t 9-b`qB 8E^+`0GaoES#g,6BQ=>o,Q^On y#]fl%վYKyCG .`ȷ@2['ihn$R4J볻Z:P(^Ki w%N8-,˔8[>Hhc{&f=1y |^5)<ȥ$}.-b(8]Zh6qyv"t]ՏN5'Kl\oKp\&GyУDɅ6xTq_Gμ 6.Si+8Qfy'u'7*qp딊݀󜘈YyJEY]fkg5$h[35@tdQ<0v-hحN  ѸNe㪙 ȓϊ;~s)0lHN>r92,] 9K'/: !`OB%+H @>i8wgpm{599ҁu=т !Xpt9y"loJ#[n3U-JLqw}$A G2eQZ8;~=yt Kbh{xEGCVLmw%O{2 ߵWyF%LINd[z4C=kF#%>ّt+T7b*d.n᧟cRsHh^ZGgudw(EE1T)rF@v6wWmBǴE+W."S+B|%wpYj';GT{5+L%\%YA_8!ށy{Qy|̋크^]z2欞 LVz$sԄ{F~~A=4DӖSBɘ<9,Ž<A)dCT.?4V2š8Bs߃<- >1✪,Ai3Kyb3"Fp[~TߗD8ڽo>эM75R<РM>}mXX>g}_v3 Y5 @@9$A<+`ۣvC#``MnSA GK!*Q`Ǟ+8(qŠ8툿DnwA/b+a/5H#Cg9C L=PF;QO$+ .-_T\ *dgbC t _0%. ,b6FU90Nz+ Z!_m5wc9_] h֣TJ3|}S74P.iӸZCxޖV)賌0i]&WTh#ZLΥBHtN4DP%hǕt/Tկ3iEU Р3Xejyо-!%l%Рgfx <WŒ4*>=^-!ô%ܓS2%.XAej %\| Ly-_5',uRe3D6RPqV>w ]  xU`rFZs! / 7s.]G_cba\gw#P%(T'J]dԤ *s,{|m 957#qB=8ao DCX9irm@rpmF5*NH)^gܠ nߋE|y10Z"-cfZO&6/Jf{z>Y[tTF_z.,Ot8> wSRKh!V,(*> u@q*U#z-,\Ui6Hd{ 9K:獸ÌgVٖJm~2%RY^g\XM2X0P(8s/Xp `:T HIȒE:#] *[b($";VL ߰-;1i7D7S*ִPO~C5ß{<.6sA0*H~v]+cZBRĹW)7$Ng%j]o*ܪ@~1,hWVJ匳xf* U]П"';pT%xOw*]b'5UtK'4gLc .165cmfM2sq ES K.H 2NlHMxLo(ջ= D+l)gb0o|atRm=@ =toʔU6%?o.]8<1[\6W|܃2)+fs"ln@'}Av>E.\hw6Q37  tDcsT.~Ԟs*ms'΍N .:&@ǮUNm/p 4TZJ5.rsy^ ak+`LpڂR3o`4Kc#X?[25A4e(t0'B~3D؃RwO4_.df!*mnǦ̒Fǀܯcry^EpLQ*3Cg)'^E㪫2~fcBҟGͫ[l(-q.#= { y^[ے'_ؿvt5VDoNA#UZI~\cãu h+2V_3Kܥݠ`H7ϋ';d˙d>~ѹtDGPΈs/?i\ MI' ׯeTj>s%qBx LxQ9gp *A{}8u!gӜ"xz\EsPnfVCU5UWU^#_\(?]Rs7]F,+~ͣMD)K4iBGQ͔IL|Ͼ+ו{s~| U׀ ;i#/}&CfzRc[ }e3t8OOCyD~Ȕ';E7j|U&h"R WG8r-rnئtԮyо-U6pɛ7/ۿ#:`& ;d`5Az^:M.OU:MW n&[/PmAv&|uQ`@}~|P1K_۽҇')#hg\]|{%.x*{xȱ is'~ƳFJP1=-z [-x{TmܮJ-wXm6`%"kT"#?jgjcT@rj+S!4azfʗVJ%v] 6j q_ B=} "6sy:Y9ʵ<Ҫkpӗ-ܷ`/;x٦Msi,$ -׮/2gٳ]idP#r!ec]%D>Л!Ώ*#f l: >[D]HLO&}GO2eҘ~J\4WޥQ0NfkIڿiE朴ڶ h +zB"tڶzaӃ74*}oWn&%G|,hiWBjW1eEAuNc͉k"Tzg7=nj{W/og{^œnNѮ^b-q =?.Iț1?/is$ /e.P`]'-@C[uʷd\(lѽʡljrcv>WOl,̓dDDu7 QY`aBůowid|*h{ zWf[7)A:ι}`wi)%Ѿk痭|#d,W531)GF :Tq+?iyJ*_=|v}?YB擗QoFps~XlɰJ7o+[Q:8|V$%&/Oo]6gT^sNs-[ Itp>hZfcnzBO\0w>]4.ߗkhl jXḆ6. HDw4@?hZև =aGI5}ؕ$QM1}+HWjx@oZ5v^ޣx|Nd(_>7dҤ&jؕ:J"p=_R+UTTP?E{5_"w<L=;>UfRZ@L[] J}U,Y/e,ByA!(6\ߗ%D7ASbܶjMNUI@Y\.yoy2JaC/P+ <\j5jT-S׍MUrr *;BU}kz^nnn G5)K` l2ՖcU-ظ{?(_,",S.sٱ W]p-*F= Wˍ۽k]+ݦV@bM_ԓFU [7XuYWя@të}{8 |f-6nKĊXb*EJh,BrިG2֏ι667&w}@0ʻ("!<"%Dn421.66\D8:G;* ,&ÈzPπk7WqPu' _#CQD N 02_!9ɁcVOkޱneNVWCM7<ȧbkY}A fR3Zd[/֌J?w>7'ǵ_5sH*~z.%ڌ=ou {AӴ,ICjj)F<⍫a͠gؒXmNݛ%GcAp{"Rٻ4Ӷ0Nv0B@E 6n%%_XXxYg6&Y]zw*_y0[M1sR>n~]*tկOjT0&+La@ezXrk)oqIomXӋ|r0)y!G^T4+W4IbzJ,YTh]n82ґ2M s93*?=h;j[Ii,9MZh4k@HrWW_}] YD_Fjl_t8FelU 6j2SՅ3ja3IT1G7^)F r^cPu/vk(VY {,꼽_[g³XM]@W/͠W]B8 ֜v.&՞1sؾ :21':5}Jjz/A>Acx-CcϾ(B\~54p 2s٢b{7uϺ>\97'aN+Cs][ }CLCoJ ][cWU%nGo!B)@y.M!r=NP4\gAݬ.[&+sZJ2JĔ XRʊÌEژlʙ|,*,jeN a?nӎY;*}$m;|c\8+3{GV|IL|7vU1ސIܜSy 9)8: =(7#e$k.xr#kp zxnNxp)`4:!ͨBsf(zn$u/vkնGQc , SWd%qZV3R9QҲ1x Z-`TwB SHǚ?,FQ gMwTMIWk1懢ӑmnn^EC]'N]4_n5 +6+iE<$Atx/gNڵ,iѶ 7ِ6^1%3#ML_ 8& ^usm>[c!Fmmb5g (7^ ߷mRN&s۫zy`XfM[w\P/0ymh[,E=kíWRlǍ,Ic?& E%Љ jC }ˉ7K StN-|:ӠKw4#صcsuϢ UMY&zfS|V"NeS{;0&q3i Sx&BlC龖)\WNI,$óY|@>GĎDEIxCd>x~ONBUe"4>9&.!6i|'Eաs&yX-20c,fvD}K`%|b2F9Yy(sXJ/@/E+#=3@>';U6|\lu\L}Ǔ/(בCsP䰸ou -52>iR2͛vw︎3]k]_-. *ZfER왲opse 8lTDpI_jl& ؇>mi&&aneA} o?q8:a9{[K1M5~pc9qWtoke|>U^};O\v$=aVJrYbu7T /NP~g:"|)=Zy|ݓ~ֳk!Njy_7Z5"fE~pNg,)AZ٘RC(uKFJ)zxy(#1s֩ɣ[Y-P&Sڊo9阽dx8׆YJBֲ;Te[7£ËQ}e" cn!O{蟘%V1]V[yOEP)O{)\|gV'49NRҏCBMADh!HECSyS2,ѹsg pXl7/v ! y_ZoؔPE}_NQFs!f;hY`Z8ӹbHl̘Dݎ0Y~2[+q9dQ҂o߹XUx3x񓌆7$ -5u7mTv\Pg;mh{B5^5Hf-5PQu0e$؆)c.S|"=>5-ifDaC{` NȈ!y@v&4] fAWU}{p~/ ^O 0m\Uigڵ}5AEy<@S>"uaJoBOD 5Y(ۜE "-8%;3\J ٹX5K4ifi#4o*(HlBbjK;o.NRz쌩WlQq1&<{/X +xZ2Oܘ^rFz'p #yo+fHJ\9n-݀]gHt gB($(K 3' lh_A 0; k.=1zC_,;Vw\zE,nDI bʴ+|K>pf2YL,MK98)oĤ) n;~dG]{n9zql8һ 퇘?7Q/l8\yyY?'S^b=* ; B1YZ .2Spo.| =-[,zu%aUENu~jkOmoF@PWa.:?V6dn8Q0zUgǽys>I揽611[7|^j@Њ`7NԖN<ߏQ>1Ly ^HőYU#BJJ q-c ^uӖ}v9E\H7oJWPƋ,bugmeojrJInt.L@N%M}Qϙ[mV>kMϾvLɹt<L+)ڍl76NN; 貘oow(KzTO4$cÉ\0U^InUYk7$Y[J8,Aa[%(ۧ 5`c9m0t9M@PR[rT;#?ڙ%t>[λ G % oi}JXzV |֠Ѫo}y;_(3L (ZeAd 7Qm/ُg$:iŤZ~m_b_!_P9䊬E[v%[1c9@1+h:\7xc25cBKLD㰩[`'M!f)Z;EƒwjW;\Պ|cqrN2 N s4!p ".^?H_cNЗOLO_;)ZZq/ya9R"ASYsӷĠwZ(u1&3hx X- qYI/~GLRد6I>z<}=F-)T큔k xG61׊ZX01驜%d~勲7LIz $Ϗ$>%~%a~}]o>[$S3ٌ#X Ϡ, NcEv!MuּJ='c4zHivC۴Xcܨ~+&N0ѣ?x0;N29ŬqcGx 36hֿTqbFbuZt<$PnKm4E9h 8ljMO.?j|M^b[J^F'2E/ Yi>+HiMWi~DlAdz@_%*_|ұy~;]\cslx5r)+z[ǡ s{utT$n8).U0h6r#wsl0YJuk^/=Ѳy簍{)aӚ;ņ"53лPÉM}{fL4޷`k22<$@s)2A Fا:IpH)[, ؤensV▟O[c_SQBpl*W8'RRW·x^FHKR{(\nbK/^.ɛʹv͆K2AL7iWxLɯ`ͺ>a SD9@;uJ'ޭ/#}[&_oW]N9x+5#$rCQj+GSW g͙ jyswnitvҶSXqygG\+ϝ"H~ u$s/̯Ŧ 2~w֧8h}poeqU<$}ͥO%d`!Jf2!+a- ֹn J')fXS79|既/Xcڊ öxOd'|A7B'JB_'ȭlH .;>LX5&3\_;m(aٕmJ H,}=)2y׆ӞBbM}so9NSlTֈxvhƠfЅ I+j‘VP#a_q(KSǛd6fIX?Q9ݗ;g ;lU,:7B\@j#b p`Xdb۩Q _Ҥ d .)?>^Ij+%sƳ3v7[G}4s UO9L;_eц(m{l ձj#,q_FؖӖ'7|O0m#/*>cԔ;HKl9_z}S3vQoLG΀&&)5o/TvMv_@\ WxB) =fQK`9\$? zUZ$ߺ#BRVя[j?HG^Ŝ9ڻ{K6iiUϱvIނvxS*Ms^g>AH u lo." oDDu56SV {&D&\!(ሱ߆W%/7u@ A9Bß qlK&{lVuC3mu{;k!:PPMTS̍H:ё kDgl`,sWw PO#WLM%.jT9K~<(5V0{ܼ۟]QԾqY)8x#zٰ$mB+AE - Z]fw luZ{t2>L?I Mo>! ,E+u큓4GAt!_P:u6ìMu˻bgkݮ&6np]υkX/=V| `ܛw@?;PEUbt{R9[fw{Vr'$БwD qZ# ;N¥-#2(qmx]k^ P=EH 2""lSD6>5M%7ŜgBo=(=qO&$fp5foϡ3tC8d/rG(A78?1~/4d?@*;&[ S}_(Z[*Sj6~yg8aԕ^V4WTD=V/q=$z]Z=964 @D^ъF`z@"WD}+4hk^.A.>@<̵EzQ, 8 \l G6àj!T$] zfOJ~c2#`]f,kn,钚yiHv$;d4gAaa7QȔٚ#T䬓皂hi?TO8 6I6#j_&C%"H;:ѐ) D.IX Ȗȋs'Hz"8q9&W,\a {T/ :6]J0?EDw/^f+jWb-Vaəe l2 Q_e#9;~M"ggK7ǂ&&A'G-=S #'>"~'筶Yd6l$OlL4@Wzt׆,~{9 (C^5pCDSN]vҽ uL&cP8< "o/9Lf - LxxBRwQ>E]1C@[@{ @|Be[wجܿ?cЗr3Qӄ6ol9UD}}GC `tR.U(c'WmU{{7 WUf͐sǡ &roŽiy \(f{\·7EE#`4t4*0m"Q4ese ͘N ,d`tCύxKM"sZ'J}'SWmMG*KHY*05GӞ'lPh)oDqI9 ojĚǦ>1B$RQ3k6\i ęQ@ʌ(lRp0~1cr3 l~k{u+JfxOH_՛UKI:1Oz*xXʍ@$ >'ۅ$'84먁0bb-!/b#9_0cα\;jK,faV0C\7'nbVUž-{EMKa/TF:h{RdVPyQSS,0ZYOY~QWQ]@#[Cccip͠B>ٶc|a ޵mfVz1ʺrWloQ9Ġ`'09FoMhJ;X֮̒ow_<ZkMh}LL gWG*t.){M`z0] iE5EEES!&L]`܁fv {w( -7]Ǜ5PG,tu pa+GA_d8k،QMי|8 6[p㤥<ŵOm^>=`R6p,!wP?_ve2?^xjW S=2_`":-b|Y|#fcg~$G,ЩhűADvXycFiAP(Z>P@{F3rlm,%^8 )l=/Jy/X"]'h ihڜUax[ 94 ׫9l1Բ.Cjc/qSUΑ{wV6| KDC}0W}32S)ks#u7{鑘dp, DcZeIv[FFU(5z&@quχr?3S"=+w_~m4}[ƥdPQ1Sf.ˌ]ٶGY2'"h[h,PB]-2y~NiAn㧌?[ Xa!TK6!3F]DTӅPL.=AY0xąT۝~ er?=݀Tӟ}U)= [fijhop(3 #fzߑAuW;>FɜZTE&斮v6"Ō|CA ^o/FBΰwNe?pk˚+û^ O~إ,2KCDZZo}qLYXfU5|5cE!`amlz:ak4Ŏ5_o ȢJUEabIa̪60]%W1ȍ]ҡ,2Ap'7-ٚ8A=^[2024戰3Zr#wzdu1=cGyKڵ_B j1lY9)?hFZ(#̑ͨk*X+ʌ\NBJ;;kh\:vN$PzsdֻwN_t=@G1cYp}zeCkZl_@>)Ut;"/^8eg q\pbQ_$ ͺJm`U)נ]Co.siBm~1 'ڇ}!&ek @VXغd }Yw-x O1otP5[Ȳ+:*Ld~a~}^@bmM'F:}jHpBJ:ς%lqff\MZ!=05QA5{/jFT6sԃ M{QfJgc@䰫Z`}6WN;;u_\؁.Da;Ng˜&DHf'9QZUE<#a[2`_5eELm##-wt]E] KKeV Z}M?jQqC/^e a=FwYZ\mbne!=i=I6),b/[/:40~RZcy0],y?eVS.74d LϨ 1*p'Z17Z.: .b?[0hI@Ca!&:(ܮY{C$FjUv>4gbI%,Mn`F~fgkoƪh}ٚm^8+5tJ|HeiFt^ :O+xq1VY Qfn Ӵ>A:C3c1wFIgwcSGSB/?L칇KA(!,Ɛo|hH\DC%9u9dCĺK z3|>eS^n?g$r u$v }ʘG`jmo՞wkq.nW Yϓ#;YmFo;rͶg>WvƨJ@;UM')W7o" ėAj>q^8{I'L0W4s o􀓻)z۔z:xL7Oxk$f:S Cb9 ~LmC^(Ep fLs3v:utQ]f\v/Wo0C.-~@-sƊwbu4SOPK*Og;Xd"W!p) 4G3j1H˰·Œ?oگo-L"|7\ TLh̪Mwdhm^O_uwR%`1<" SvK猝ZL^1SGM<޷|MOm:#jcNL39t+| me~0G!xF^U.'Tpf~5nRc*AO/x6Wʓ1q'q)ۣœ)r.-P0K#/~aiE@F!tfȼ~ ?tiQM/9^ M4߄ᷟ| ea~iixl >஦5G?Zchh. 94aQP8nLFp>$6mn"NxN{w ĕt8iƵ$hȨu۶ q$.2܅>ЈZ\Lt,lMfn#wb=G!/ t&[\ǗEh껳-зm _ 0[ |Hu,񌬭Yv\}% ү-<ֽ0DeU3Ic0ij-) [ czY?ȾU3+8L\sJJ- w͠xܥ?g^~ YC!ؽ]P:d4 _r,gM]a"%% Z8Q]wyct NM`G 8/u^̞ ڋ/wI%(uRJҩ@9;51FW={qi .gJ]mC{c斣g4QQmӠuh6W|vC.@T V*S4\LgWHO;﫣Y>m>gs6C!sv؈naBJHZ9 =)lkKFg^በ_),;K.]n#ݲBSA;pΔ ɲ]xy fy*zl 3v5?34$uasЍH^XEi8|#ST >uR~z=J2;r  ?r t= \jNx=*^ tw9 ֐0co~`82V4~! Mٯ2 n9kj gUr~7j#[ =$XHGre Ky;myTI!tTn/usU:G\QI}e_lw]:K6"7!k$}EaS׮2 *{/&D˥ǎ #-JjĐ2U*F3,\t}7ueZ ^. EJtnnʮUIeIڪ#Q[3~(>qO~\SPRUo+y ]GG<]a]u="ۗ-!)A>fj]6ɶN}A tBtIֹmKxЮb5+sZ{O6S#; D/hAZ1t@hqtNbˁ3!\ dQ"9)M ٺkC*V"`6C}8gc[̑hcGN; jڵl(w^xzYw$BIT{e.VPikuMZ)ŝl4{[ 4QJ'HB[R{1h(|qS:v!d5}0m9Ztn>uok֘Rץ|YZ㑝k5{R92趼a@B:4T+N&G#{F &|ʫ#P)9!M, Κ`dYfAD_s hW >_*6\?H郇SDɪ V9jGJƄ{Rqm^S)14:wTSxpXo 9>8_liGPc勇㗠ܵ+_~q)\.T +gk&nQ5O:O9sI6rQ}FJ2fAj#l~IDBC?1n GQv|Oޱ09ytMnWH4@Yh ʥ#z?~8jEO2zIkVrSQfGSQ<#Hi Q--RdHh8r2]ڪMu_qiSC; =/9z^sr"/Q4YҩUi4T'qu#ǎ9raF;~ϩHϾ Bgg.Avmige`uqiҕ,̗x;S,o%"2O`He{|ho誅#O}\y{s8ʴVav7ZW܆&(@e]k# Ke*$2;kK.Cn(L ;%wK5ŋӍfKˢVˠբy/ }ieAjI"\٫PU^J Ԭv+?Ĕğ׷ (0huyMuӚ;yKV!ȅL5mB J"ۻԭ&->U@NgzO7ѡbY= `'YѺtř}\lf8ج>yA&]B5YWdky7g7FS\ RIDNZ-PkKi*;gw"4vn\wo^(QPt s$z9QD;Gd,8pY&ڻ<`@6 pnKcڴq(Λ#V- g &w:F>To_lekBeVGl}=%3'OJ ZtpwCplԹGϞ}m(;| )<,4Ǭ`ӛOBE|x ?-y]LjYv)7^_QT$Q9z\vS8us7k]}j ]\t4,;= 9nT`þJwGU݅7Gm_| uq[q?;6h &dP2m;A ƭS=xZY$s`Cz*4A&A( = ->*W[*WI~}? (LBt5 #YfO88ygRG/;'Jװ^ zчɿ6ვ9gO682{䁖>&\ !Ra:ieH 8 8M$LwCFsUZ\+-+1S.riEb ?pu׍Ǘ xM@1xʝM_&Fw{{33N8^Ni/<癹a_X$UKj Zb6\@m?I;)S|T%u#O YVk6w@"`ɇ+Mh#T^%jZ+Θ؂NPagֵ% }ܞݫ k3f3/ Oc. vgTj %ͩz(L0~Յ´mVUFTeȇ(&.Q[RjĥtrCj٧9]WǏgc?7P|6_}gD&c@Zګ~^Q9&SMx.ԒI49SUf@s'ۀ8 nlw&%,0kGʸ-X_z\-}G~UY@ai'7:fu?/WV!eXsA{@P_j2|_rgBm5`9~`&ai\T\!;i/-xcJx7MiC%JD^hĵ IWn}79PoEZP(z&lh 7Y 75Fo=<LW_JI;AG /̖>qhUsCvu9Ϫ8r^N\&ڲ]6Bn߯a<2D-ΞFV3#F81!aNn_D&i:a/&>r}k,L[riV={߉#eӳ/Q3ǽe,5n>f>ƑL7[ H5HqA˺+>Ef?M.[> q $rN`D\蜩p U"_#+By͗uqr8^YHAN_ps+c5)u=(=ZSU1}D'Owf/| f799xM枞٢Jp ' ܴwb]2V]X nOnVJ*ݏ>ԅ&>w /gn4)NO哯g!Gf5A>eTS2C5wX*;J KyE*R%{u:<^)%Uĺ?dTbEW*6]ȞҪrp|%țd'fU*У(P㓚RA.eD]1B F;MQ|nJN:y{c5[Çy6 #)R rcbKe99 J[#c7haJeȷqPUq^Mnd1: tJjOR~B?U#]Kl9't'"jaSgf}\B~\ C_^ȱ k]"a<.dP@<%=Vk& ^FmɮR59)vY֐NW_<+L=l/Jkjz 0N@8 P˛ʥJE k@1᝜yU ͔U|iO`0^AA}o0 >iP s$i|4GO&pOXj_?Q -HYy>^ahzHQBW:]t}8vsLM:5sdX6bငo}?>4='` dYp;Ϻ |NԳ lb)hM1Vh|C~[Pt{2 Ft  k :ަ"l{ZFUuoz\\ؕPm|Vs#aa):c|Jv (vٷ;*b< VC o, [mult$@2#&)O5[eC1>2Fk,k 5MB^*:Q͜2Y+FWgXNx;͖X~YÛv(Gɜ6AFdFh^[jxP8QQVN-)UHn.ik !SLJd:Af3ᖩ1LP 8Cܔ&iJrp;T9E[`]~v+E|n25fW)16 Vg\l~ |֑t)-dP&737NX:\v $fB#Ѥ_%:DH89,]n1GR O{}@FCb=K=:XD;ЄTveR qI:#4)2@F=N\|^_mɞkU;SŸib>gEwb'bfoJ|;^o+ 9~/M18nu39)!ǔwv1}ʓԺ-$)6I aaV'6iȢ 0I64y瘹7c -fF- 5X7%`,̟Ŋt(1bRѼt@S%o.mf *}l-bCPau7wRǹAJjWhr^dK=1^1r?Nߥ71nY75xǡc{ k)L>{p…kڅ8d4FjL@ޅ["ц⟻A G$5LtHq+_.)v5te0TќK3ׇ !36B֦;Z+H A!)E7PR8B F1~{rI ۀ+'21~VDƦ u6x<Ź:םD!AshAH7cI8%ߘ_JHT-"s@$ DL;P$ro[Tm"H1 I5S1Nҳ:Bvt~L0;&P'T j7.:AĈ*Mg}SΑ8n | /_M ab0GzWƛ))]be-n.m}SЇ%4jc`aS KSOݽT85[rbe+TSvk 1QWbN ܷS w~M|&h'3 ;( 7bC`#w7WWz`Ƞ|ODnnO s,!5D9O YVcy[!!=ϥIœhh&na{~94{זRy HP_(ͱ:t|&1W²-Ջ+jr .B%"VUpFjX_)UmkbubVyD2 HZPwV/t[M BV>i'nPg͹`׈QP)fœ92.tע_䥂>n9m- HQl0$A6TKZ"2=IqS/_uCiNsAtgu٦&&UKGqu4S0C#1Ǹ a$VȬUHjBV+Dl]C$c_qKtat|jU{nʾ)?nFk+V\jyz'օ6JWm̓v"A5";~L9gH?dvr@lf\D2cR_dk˖fcD>>rv ސs{0<r#-+ͫ/Qѿb'$~9g zBU& 3.9Emwgt)QGʝ BfzKe.64y%З}BQgQ#|H,{.]N籋7bOWtӕemENcO_0(LqEQ_w{zϧ]~8odRd[#:ܤ<vSQnG)%yr[xᄀt!чku?X5(-M*d]!mJbP_ȷ՛ ;rAN|}@2HMH8Aj/le.ŏjc% rP~FG3iY ̜b vp'ev9e̲ͅRxҖ3.&l >1Mz29=VH͒*W`}9mR tԺߌGKFV 7:τ?ez<`&ΏLTkuyGQ [Sq+hҚmJ^:̼ZUy_җAz)a/5I>ǀ(Y&!ok^.qNX/S9@vY-TmyrF!^}[|2%ry/o=mkjJ <W..dUx`ÊDmo`ЂTMxK6lkp^.j $TµLB((<Bη~;}d?@DwA';QA#HDu`] ԅ_4ܻUh'`p D^ vɫwk4ݏa=dqUK38"#|3 /䘝=KySbNϦm]9Nz1!nEUzu'"4g!7Ci,f @zAᕐ!޴[$+ç]FŚ`|@)&\,",sZ>bgr"H zAIF RYqKj6^[+R|h߅lٔ|46 9 葪t^#xSXNl0~B!yk2&"TGFX[5eW}#r )tk~cl?s h.&]# QnfevN>3PP'LH`2ӑT<O&s-Nn͋K15ݫǖ, ܶ85}d O;+0f.+K bt%T HǗAǃ:SCp'9\Ş([֔R>U21{QJfĩT1ul|TChy+qIp@+ ''@M@-ӛmi|l|^~3O2n{=*i(宔@k0`7FA7<~JA:fXhTsH6snagLfԯH՘G۴}R^<ŔtS>}4 SK?*d+m^R~pꉋR;+"N|9 ~uT@j;vd"'>$'1{*g! ۞}ūE& T?‹,rv=2(_Jl&JEY=@} Rl\ '7fD!c3VbS˘`o |9yFzݏkW(§zIƲgީ@OLЪj UU=xۼڲ!\{W{2d+p$OB=+$rh3{J`{D@]]%sȜ怚s|dɟ^" Xݵ'Hj* <ͽ&V3ʠIu= "ʥuZT =r=F %jm ܨ6b+)۰ߩ1-0.5+Tv=ڋşw=c+OV'FG{r-=4`TOBL\*Fy8 dn `l; `͖k_}MJ}j7` %myЎ"%@@{µ7RGCE|~ =ȋ7væZk^[>48yO"TѬn8X$yx{qCl(v.rӲp0MAS_#Q>jL@o2`DUmEuF\lZj΄.yuŤAqVHiH-D8|*u2|jO[>g@HZ1e^w3SmJ%eKqq;9Ksѽz+?1*T9Ձ@[bC|8;x~n4[y 2Y/_p~ZߒM.@M̘\FWp q1Č#3*Xi 레8Ō64yERT&/.XiyMmB::<aJ|oȞ-H{t7%Y閭k][@nԄqB9av,нA !=Ahudk4Kg R8B i[Y^^v|Q Wnz%%ŴX l|WlOQZ 5,<}vSeS 4ea,* zRz1 gkۇW/.TPXv[7ήD|/&Teʢ9\EJ! qZ=OM/ 6R_TL9%g:>ޡzggb; -7rb \NO975)f'gX>9f@(S%>H͠u*:C9>oMW VH{ BE1 J*MX@yF芳;7ڙ 5eE#[jҬ lM f.*@TLP7QZ(lY5KvQ*hVm+M:}Oaݖ4AݹZmOUN$*X2r{ {L 0MK:6}9+8,ݳl,gg F Ved#.wYE%# \"D_ṅhF_eiq?GbLuʽ(!s60uEcCpw VYdRX\y;W&Nv{]8~R+X<Z .€Wȵt0Z/oJ7&|y1NOs7@[wcgHwp5?']"5Y Qz9cimdj7̏[aǍ0s&5W)AT(ۅGz͕}P PJ&ļ-|R$Ѣc1.tXhRSqbZXjt^t$t[I:/ scBqF&T&iXwCL$?>D)S)Dj-e &tʴ@b P4'9,GčB~>)fYjn-eQGsVJ2V=N$LW @oRhnԻ%#h\asz>E܇!u d*}l~ *(٨ޖ]BznY˽{xNdܸj{yUb!@(+Jv8VxfR*%G(|EЭ @GmML=(DfYȐ&om!m4d ډ}tƛ$-UK-dGUS2wV,:2^E%2:ܳ]rBb G܁GS!Њ4_>̶u\ư%]hK#AX%,qO-^_ERks`Fb`qײ -[!2iIxy3a,^k mbF;sCM!bNdCȼ$VFY b \Lb'ge5Y[2|rqV](9PUz]byKbldp*ǟvٙ6:r~Ӳo{u;Q,h؞>ԖOOuj9@ˌIi-Yy]:5>f ZeW(k&T cۢ@:d- e|(\t"#O!#A2y:qO;`0VvX҂fI3ٮ{GjG>uAU&ZKpBUm  aO NڭX VX6'30˂D:JCXr*B +X`' 34mCJC}j\E :!]1)m .a՟\aiA26)q'=Lf2ݠfMȢ_B^6 ;bӡ'6w^1d5:v2t1V>9=w_TD2AC;L`d_x§X وhT|ܞMœ[ Le:w%[cgހ(Nܿn da~-STKl Pi"UY!=`Ux-*Z31_sYʎN4_K(0YԲgiۖC e3jykɋ}urޔt~(mQ^ s"Fl c/F{D9vc ;`r@بMSwCP/Qf2q}KHH(C "O~@f '4bt-"Ұ d K86NZO4ȴ&{\:n(.cio|˾w85r,@qf^F~ Ds%On\9Їc{R2*9+4EujF.wrيHǜ+H4də}NR/^98MU:OHCr֯rX3ϒr `cCl rү \x$cTvjKKR"V8|CScCqT>O!;3{߹c38 >} ؗXvE0:QH^98}36z#y )18QxrsedX Dww=EA->=S9sXX/Dp%\8{LD^~ʮ\  y^GԤuaI(B蠙oreASx`>K ˴"yDL*osɝ[G(XlY|.3*|&}v=!1 tTmBCtN48P+ 5*eq)x T3 8g?`MZ5\ e DŴ4]*TRjjJsj&PCOZP$S[nAm~XCh:e\xUF-0)NW]'V%ݛ]NNn4+$QD:Bϛy,\Ҩ?Cb jkiW`E2!E{Z8ŕ.0feAXd$CBD%b)dۙ{cL&dPAɪԿGOs -Rݕ9Y9ed\>ټ(]wP0ԵdQRՉ '##=~ͿbD Ra=ȶ`ьQWviRYfX2FL|"S 9"=54h #F] ]VT}d"@ d( 0{œ:K38P2`H5r7bjcNأ9 pZy&~WYarc(ܜ׮59Q1!GTHc!Fb.lKLBὗnatΏ#nbVu >_FBK?#@C&ENz#_>Yꟃz?g΍||c֓^ |:9mv  Rx(c[u yD/45(ˆjHZ67@>ms<W{rW\=}]L܂Xd8avZSo9'P.CqBt|-d*>}Hfq'n>KV?w9&:cW`L*.EnK֑AaQPFC/KY ^\אȕ ` v}o^LYX?R HWR^%{F7`G@t=xڇ6eھ7xjF($P<&x"s,Cگ^D4jX>TjF'!Y.8ԉN2DmdB1HU$-F/v,2E2yAY +/-=o'.#ɂv :ow1C.P+Zuڶn|)Y[ı놊XrE [K&^͝LVm+^h?a7H.Px<_x~BLOk<|?ZlTxֻ 4D^d9!rN.g趶>љw %}Ǵ$$t F4)݋'[<Y!u$kP4 #EnȔmk:NNP^MȊ8*l^Dyܢ(bʙ jTyrRXs؊R],nKbxFTbN.E6FXA3)1S/f/CO,‰FZC4ɯulJHM "c} S"~` IiO8E7XÒ? .z;[)o=wmgf|O5ưL.WYdͷ!^4pPy*ER!)\J6sk;daOήk)у 9:rvWT/&:d. Q Ǿh:;'D}!Nɰv+-\^]= ,M3{vld`hF@r@I!W8MJc⡡ڏDu ޓHY;>|.d G\6{$'pkԫb# @vgc;;JlgXԙV)Sh/)[EeRIV<]a:_L߁d!Zm.zw1=7k}cvawI"\J'K+fЪSQaxز1QP1qj%!x7~XK[4$_c[ $*=ӈ.$)}x8x!o:uT*l&$fX +0wS~Lb3)r6#az+ff}#ωUO. YHԬYQA.83nK AqRcLb呓1(8m~w"Otu¡? N>I^jP{\Egm3Gr +.̧'E`EvH8IKZY8H꠾'WeebݻIA! ) s8.@Dd4֣*6, 05&xu=6}`DZC'}7q{HPF T#cGoDmk{ak%WLc5.~Xh“k[Ļ;A퓢(e5G&&:)J]NW&s0u9ُq@nm Vzzt7;g-j:^y}LYտxqFq'ŭF~d"6~Js 2/) s֜P/U C0纹n׋ш_h3  tsʼnżVpV+pÐ -U][u4rSȔ GޢZi)g ]/ƾkO3mØ8g=̼!*TEkwٵqռ}֨V 4KK[9(i/p|nj6KЋBVRhD֞1?,Yt__mOaߜ_d%P3_FM) @)_dž ˕x8h͍u=0 N)A"FRbvx:Ie^oQC]$Aࣘ& P<^z/v[[NVfa=8=a6Ka 涫r?jm:tb=Nx; `R5dYZ lq+{@Nֈi^WwxgB[c]"i|vR Z}8$$!~>յ_TNd_ӑC=(;5j?)EedtG̸MMb|/N2Z &S ?dtw6d(3y O#}Q1 ΍*(ꨎ'/FyQ"[78 tGaSm'G>1% mOX>Zء9=QGHB'.|opU0IZ=v9lVn=^qlq'1) qXh%A^qbX|Lʼne9@S-P! S)K5̚9ܤ][e*t?ώ(Ư۱SgoZus6dsO aOw? '|sV.&(ѲCqlszGz/ޅ2Kd fuPuE+ U>&v %zSV΄*4 GNh!bvu`RoMWkFoۉ,ճWm帛^Y'{G`Lx賯踞;MYْ_>8|f#At Uiq9>A_2Qf+)v5[ 5S)NFWQ3giE$A7_$9OrB<%V&{Ý:ky .gMo߯az|x~‹A0B}^,c)o,뚇?˄}|CsU:jԉ}y'fcB_!'57a*(pKe3H7mfMc *,Q-d }(޽o:BL_&/3/`'O_DZ$zu6(Wt`DQFvggF6wrWR+ѭ)h\Ӣcr}]֩іO}9ٟ5,?GkBsCeD'g7}9o*@W, zY4)|O=(Io7?̮+;[EnҮz/Ȭ5EoMDƩφ|[gkiL|H3ڄ,si VB:B`ypI4ugvm׿1WЭϭ\y+o!{۸a1醠>xw`/޽q\|jAA+r*( 2е_>FO-[2cD#ځ4mWM&8D ohK)!]ohVI_,/-!a-cQ񃠪ܱҿ_s>ĭd7.ĥ"tۯ[sbn7# P2?n zK͸G^($İlB^:;E2_Xa}wc bOsMoJ>LF$( XTm)x|ҒBm|ei>`%ߐYd(XoُI=ӆv^՞QB1I Ofـ%,9 +%leD+ lWz& `KsoK+U2nͭ3c6F>~w mXP<:NqX#+pQIRn_ 7sJYs>fgEvS;6Y@sWb?m Snďn UUf$]x}ތoЋuP r*AH.CF.kqE>v:K 'A*n= U̸.]폦((<k`kEYN bXQ k5#mƵc(z?hځRvzcʹK Ҝ \Slzܕ2C_ZaF5F k$CQ*\׸s"lg(1|0zO)`ƨŠ@fKrԐM ^)Dm,af̵ӥU 78zeW PS|ᣊSjt܌kW_CT층759}M9F"R3g(Sx[aJ܄iF MW*7M5e׮WjK|jP9A^u$LRJ٧RqTpF˙NP*R!H<^Rjˍu|/G8||qTQBb)oW PX 0TJ+SO_;t߶8y^rV*o3a4TU! cyO~of{ZBkj\^Gq Jj@HKD~\˺Qoח~U&1s/)6KSm iq{ߒ45Hbn"1tՁ8вcT_m!3\'M҈#xFQY8bgܚԹrI<OY;nܮ#~Ym Zpdq#=]gR &hm⶜FOhezC::[ddq|Z`G_ɘB1HNnXWB(A + G"*sM*lY'"&{5тx<0!j!)\ߥI &+) ԫS0$-%7)uX^+ (hsgL_9BVV+9",z^Y*+* _SB&2 Օ[L1%Uq.>YgoBn)Kˠ! -^1c_G7%R5027F!UȢ QC(pTrF+ e~yVڣ('mdlԢ0rqj, Zb]Zt)F 5+@t~Kv%< oZf!?r9Qɂ?id0kYz:X̍~u1XPPIJg>xN-P_KV((;[Q%m/7GxC gwBU=}ycq, 5f̂X|aO Mym#.>HHSD { :[rGGNk4zǒE4/(p4-hxW9t10cÏ&c_ƒB>0%)G5⸱+ h6jd:"gftn\rip&:^zsŢL~/!I6 6K= F۠G(U䫂v,g0Uׅ2粄$1\ξLA8kΖ"ݷg_wٝ9meY8ز>2OQ>'/sԑvP !I;c"]O[n^TcX9))ױP" 6 VRqs$"d1ʛx7PrF"*wOOLPgX'ӎA,n1-S^2mrt ;!/CSՄ%}sM1҉V3<"̉HNРI(^3&܄\d;=z\LYi9.i\5k~kx~Q]^`=.{]tgL?nObPu#Եf{CŖ81x)xn$ %NEn=[+T8L̒dH|te} Zw;*j$Ӗm%q[Rϑ~{Aʟ؝ǀ"1/Yӳ{6#[rXi+!q}y%f)[cG(M/vegȾ8g&4J ]O i2KΞ9JBtHIT%8uMB hͿMUheѾ8ckD(@LQ9GO3<#,qlUln1k'F%Fr3GZGT?*jȩQ խlD7$d΅15 0[P 0Kc5eSAu'?EňmnVJ*RkP9MO'|' aH[#fxV!r;i,ԼѽHwf^)VH 4ٟͱ1p&mQ~IL "yUY{ qǰak_y1(7l1J) (,#3k)EULzԤR>+=l)@noj ǥ ePՖmػ2yd#ȞΗ̇aٝxT!ñ)9t~OCB?,-H@%><<bMz=N8փFJi9p?Խ!AZG#sBd= uJ+L_nױWn}A!e=uM+TF)oc%nPq4Al$"MÄr ݖNlFh- ?ϖ5&sS|W 8oXV %/f^uռIG[qF)'B{ֆLzX5$z x(6* 6fv#nӯ'^" A8S@^fwEs.- LP?jҗ[xxB1 0"d'h4g߸gz\}FO=Ew4,<9Yd4lU5lH.;&I9P 9 'v'Ztٍ[WnfV M'Y~#&5'f]*t'!myD؇ECeWCc0GCc &%܌f%MJ)JqpcEڢ͸߿nQ3vOzf~v5c;hX0c n?z4yXsLٰZTa/ M[ӞG\иiFEBFX*qSGŝ<+j5a;3dҕf5 .KPwd]4Ϥ4t}5d;b Gs*X# CVv%J,%h5ˆb@A ̀ɳBm۽ڂ0ul2]eVx2W) j=pv^ӿ3J9dGj=RF5 ʎbWNJe:MfD{Ȍ`( 5%fHYn(y? pv̯ЄFXk=jSHhULC#z)+Vm3硼OZo Zu.`>aBv1ZL'1gvHĮ|his'T(r`wn+qC""*\HaRi؎Y)geQ`;BZ bۣ )Q?mHB-)Tj`Uc]j`lE&p+Ct {桶znN)'U?$(W^/ͱW|CULK+9Җxq^e>Qgz`Ts Q`!`f\ՄOm4BfmR fbw{x3X+8ݱv ˮx&H l K%j 24ZTdUKM?DіWjLpjOs:/i-焸:|OeGtfeZۜ~Cm:feʛje.?vgݖߦ6"}qh~0 ͧz)"Fh*k#m߷VޚtsFFc߯?s#uoJΩ֝ ,pD]I"Ņ3{òL m@CсQW3Ɯ΅n?T_Q!]4G<O}R}vyq }7LF%x}~D3ѳ&ޝW[dBHJ ,er~P]ڤ]z.)>䴏[Z[ ,;MZ} tWi٫z ]Ղ kQ֭tu4!Gkeʳg1 Ha q]|0㫭zr?-iJdtB#Yw0|.M cSFUmbg/nJV ԩ~nbYCvgY]:LlSV#HxO<3L4ZTrw۴ɼGGʏĢ[oqAny%&pUUE_H&[mmHyOfTpUp\ G\չBd][N2//~ֽSFq{@qwYpu_br/N 12(tE=^I֘iֵ+| O(Y%4)M.ysf;GE+i_'l6C2- YגPo4:ļ5e4l#hڰ4vkO;7/G7{rIL}Ub#bi_EXMM4 Ղ:#Փm1S'1 ^tl ÉNz2Nȷre-]@PnֲhK*p*fiffYjh]ʓ<@+GW_Sb3 T9x54D7sSZDy78$fs+b)g ۆN"89Ѯ `Xv*^x:111m|W޵Abv hк8Юo)Vxɠrc!I-r n{Z_91Z`JFT{ͫ^4\,$|%Ga2K1 .s&k쌿 يun*{ď G.+r9t?%kC@rXusF43hV@1vS>E W1:=f0;cڬ)CF.ҿؚil ɭ!%CKU5~jmM΍9{5y1*I+Э4_f/~"?d.l&S, Q ^#MT}PW@a&J*;,nS:lNǩ9r4|eOԿoq֩ɑL^?c$ 24Ћ*%n <ʽg/nMT\eFԏ;J]"Zano{ˬd_.]L ٓ&tb|)ZgZ3b$u_ilL4{"^2lUp^w*v:@"&y"dDc@b|iS^,1'Os'hLɰ0aF).t ZOVdn1hG~❍0˙ŵMbrOi?,H SGM'Yl *P0NNu!5NSMFu\H[`7]~fuw i Xfċ(~aՍ_nﶤ;_rXQG'b5WEN扶HYR'nc (Tʐ3ь;t7k6ӣX(%P*MҔRF.m%YD!SdY:#Qx6\m?[nXm^q==yg y­t䚳Bfo+^r[: !/j UV}Ї}Z?zJW)Պ8`Z,X^̚,8|XڴazF5W`M2hw N_,ak5'_ C5,Kyy÷2'i'1%,_t 妄w;+{րG֎-'^g" B:Z;8[V=ZJNk=yHZMw]3Rܥ`mgdǰ}2\!-߅'?i[bDsƗVd"컝boj>#rWd->duRhL nq3)( kG?rwLUwI:9[V2sX$;yuyQٖ~Am^t<)E'ĒMb|w f,RܐNRU3:5"G*ZڷdfŽ]ղw!ti]:!y?%o2i.FSn4fs&SO%;z_↏A:wB +4eG,CWb||yUn  Hb{M-N?Ol7;/LSh3㲤qA!,=vL"ޖ DFҩ4;xʼn q륳0W+jVZ"B8>(8ysnc奄:?,",dw@ 9<ݧ Xb s ,)džUCt;Z _Uc0htLi]Ґ?q .^5!8GJ^U=O#Y.$_ύ, u%Jx܏ qb浂<0ఖ\CM!)pIPr=[MO5>Qy?suz'b~h#[`#ĚSCn4Gyشy͚5Х[]NoY1@d7\>oߌTN#eeC^JG1A&4:yvP3*. 23.:Iukq皐³ gdC8m@r ,!gG`ƨ`9NU6iIWP,UPɭJϔy?u +fG ~OHiX`2\}*M). UCjF4K̼*Ң?u/|H > k҄vǒE5uFp~R쉾jݵC˻3Yzg ۾0mSgp'qf1hZ }WnB9ܡoCO;O$NF*LxƑ;w/dWSʘ>+yj 1ha Gi*-TlfBc*ھLι _dlKW^tOž ԥ5s\K#yW"/.ͤD}>!Y}p%k vR\ufܾF-IJ.u#Swtu4rV 1 kJgCjihEI9Hz]| JNՍ_f7~ȾL訄['_ <7D-+ϋZkϴH. d8|2cTUR t#2}6uT>$sNfFF&SH= :g +KMWH]H޿]Svf\aO'TDsdWpN: ȊcSA<5S4`4k3NiGfVf.f&JJy: E)޸u-QP)u\^Se7[ ]wC #YɀX5)| c`iHq||Ze十_ *Kw^zIt=*=g'#\\NٝM-b/RlW_-A^BTBN~/¦(lߩ *=~4~VֳVoKˋ 1]< ژ0[7- W6h68F:^~b \zǁpZ M^iW,|.= 9!ͯocD> d~)L7 pz_vʂ 3 1Ƹ6EOr CM8ZыkVQ˭$yDZ~CTM (2XA\:jʈWhD]JOz4䳄 .3_ˏqv;ge q9D2 v`N%*1943:jQkh!5HQLORQGm KK79d×7^ͨ"ֱSz`O7Pe؉t Ӝ𸞍,=fczqn zDY3QOp`kI-` n4Iy߫`p&.',SH#:Ϻ=r&filb- H(ذ>7b#0ݜD@C-3l@3Si͊Ldj(wiwfhc+? 2 13 |zX8={j++X-ɈsEe\qu4O)f(b{ mb2);m5mLq<\Eޥ! ha~4OK|Hv odOxRgkkWQ , ^P.xȪEoo?]'~MU{múvX'=<{4@o0PЗD gd Փh|ǫctK5ًqXZF9p`zvKGHG#k+9Wu6Mdj@,XҾYͳZ@frܒۦ1yٛeW1 l'c iߵT>6(,CM*Nܚ&c{k(#slÐ+|P 2_(MD5ȬAty9l&jW!1=xbH8h!oW{) P6 }yNdDUS,_>}ڶհA%2do) G֨]^ݪB\(~1PyѥѿQ8ѣJD1]>hϺ5M}aW> KqY W`Ngyƶ; )y4-iMY]{xuԅ~^~&}tB,Fu%B+;@ZA[k|+{ Ô Q%*|y vosΚ+ l8ڍyAwnq\=Icʭ[x x^&Wcp S??[YG{3, (9Tz_}־ }+.45!|e>>:4hyVwub^{ܡJՌ=VO\nu*D?G|{p5 j2/FPCfyA93 wf-*x:.޿N>qT[% @9.6}3&66Wl|e :Z|neGxs\XdCm#^eOS 9ƍU/Q%(jgnLՌr-3#99lm3@HRwX02{?4yޒrs\QPRFlTZAp(1-Y5Dj|"/!X!Cqc nZ7u6iѢim4x &Nٽuhq !09s=p {9^Kf,f OH}f 4&x;WiՂ-9 | HJ9[ADejW\T}$E jg\:^ϒS7W5ؿ>R…0eT__m 4πb(;:+uޕLաZPjoB W-Ut#1ДݶAQn@R/NUqŐmΛ}`0ﳨs_h@Cwޡ@2E_YT/XvOQ8LN/;7C{w#pCckvGHEqC{oX"H5槟~' W`5I}T`BBk&X$|̫V'Qr7Q'T; p_k)Q /)u:sQ*,j^YQ;hܸ\J5~F R\Ҙ7S5R]+a3׮85'(]2rRh+ip|bHu/S~Ǿ U:$ڬY?w U }5ӑ3Yi ["8m^F^CRzd4o4{a$ă-y s}p7 ro3^Jo@-'J C?d%4N3&_u|VIz 퐢Yv _$ͮj|e}Z"}g`jHׅa)<`D"Ŗ ݳֆ%d0}Z̫zU_qlwTu LkM[ gZ> 2&}X_P!9|4Z<TLJ{n5w倍^QR~[l_.wrV,y1a3s)b}wߥrd'Lg:|w "Y)@ 0Ԓ?oz*LGqǮɴ'կ^)s䯌EbmÐ ^p%M LۗŎGjƩDUo ==KSukwC|nvzv?>efn*P%{ -%?gL{.ޥVp> A&aS[WKLKMx䎂˒&WTٹ}Po 7"o8+N~՚viWu_7oӑ-SFFj.qs6f^xT$#3nT/:v?ic;&5g朞O+v PB)ɸ1nwFR_}^78]_|9` [Ny87UVlhXXra| Kæ6weW֯wl7/ZY}#5$_!o'19Es<`:+^.eu(m6hb&$`B{q#an~ ^1% QGA q2 롅FGvߺlS$Ogk+UeE&{cdи>z"40RC&6Q8t K0tH]4۳%*u`vAJ5;mՉ)N{+q_ER]iC0n 7Q4Iü:Ay.9ȼw^ƫUoy-,2a|BY%vLPƒ5dj}9IxHVh&j4>& /{{Ѕ-\p{t3aϫ`,o/iluЪ_>eq@Uh6cl7PUgQu 7 "e;]qCQ6y\Pe[1\g"*7SsvY @ s-[;Iw5q~$q/9vm5Y󿰯smE Jb@ZY78*˕Ԋt5襖t/nCA+Cc.y-fm./L'` E{\41(i߱sĆA5,/spjfe+&Iߔk܌F]iQ+6 z3= $;#b<%~2_~YJc^L[>2SgL,4v,[bG''cn*CؚCXNȥ.ASXFYnü3RL=6 6YlΘx ]9hǮqw wXo``y5J;Ϸ::EٳoJbO,,a(g%ׄH)^PȠ=` IS4Jftr%c_ѭ:EB-$6~bK0,\gS~2t/z O\ q[Y6=5gyt85UJT%(>%n.ÚY@ HLlV Ki}o%3GcrX,97}A-jym' O,Ob#SW;'S?#rQIYy9F =K-oiݣi=W<7>)!*ݦ14t$=,Kq, xHg FBBmt`Ly0WƕIIIld}э}j@2]G; %cP UՏ1/]da ްe+S*W eOK a`}sR`'34j@G1 _ u/MQEsn˓r(.ьݱ1@BH=ƒlˢ1i7Thj\:QM3UԔ#:s2𩐺NdI{^9<ЪMfCvY[͟WGPm1*c*Qfc8xB2WQ*D '8F&ζD5w6 ʻ;ST: $HiOLAUDfVDbZA/md2G0t853,X.SSZ PϘ4 ;zF ٪]C~K4gO=c_r8 hS)%+(Sm$eT dĆ0XIMXUsAYv!{`Gԑyk|A^]sF̵$%/jWBK@?pLOL悭wJ E6LIل=pF'֧FNOY|[:^Mgj51Xڮټ}Ӏ~t"$8Z (BkSwZ\28 *a: Xߠ hP"G&#QO$뗂Z{|xQS(l,\uup{&>RiNE͸ۭ܄_&ٖe%b ׅK$pe{][y$:8)8\X([Z$o97dk^=R9wJa!aVz K0.{àJmqz!{?G%_/\L"f= boc2~ =k뵿hhЁ)<_ڃW҃sK>H4LwGz&P.b!<~rkз}BN)$`ּ0=7-PsH7,j&+)v-6ښNYޖB۾T%4xц,7J\:;˾h$8oS,KHPj}<y1vQ\eY/aFf $ oS 䱂ب#_ EM)47_KƛI^~e]鬇:2R>H9k4>Kl'N"ߩ䃽0 G)2|!i/jDj r3U%=#&k sً8D *ZMr*=+mԜ܎%|w">BQ8/a`N5l Jzc#V"KwXsTj:耢-$ʽ͛*ID36p=A6SLjj2% >se:`ЫHME F=^or3]Pr`ԅ$ hֿQ@RVn:z5&AVn'Jvz֕ɢnNQQʙ r|Z#K{G"EA?R^tsUrVdX`AsXuc z[C{4vϰˣSYHtDM(~DļkJkfx|QZ+@ =Za6zӣ׆7mo 2.Re8єgs o<q<^+ UȊ(PgqFJh$؞B^QTC6A/Rk 8Ni6SOo>d-kex7Y4:_ՕiovȋH,~\/Gt7Ŀ\ʋB|AsIę`1 +Y6" oWƔB7`͖N;;49HȊx3yB}j4J] Ρ@&O /k9h)oq[鰔),vjv֡NUvypua-VAX^$f1ɳ b1yO(T$Y7`-ހ*ͣtU p{v\⇣ lyf !'J#XhAJ a)xvVԽZ,Re0hr^=K*,GԔA/.>a<D}j͕KAkxFOHc^%~aebA-DƌsX/j\*,F_Xf<"+|Smt2cl42٫ׂC av> :w%dc Xui7P~lq5@}_ZȟKyee`n1ELr+ڬ>DJ~Le,T>welϪ& y usvФl e%nƤ֭$UnY(ۛ{T5&IcĎ^8A\odeC@l۫P?[qTpȇd FBnV X>t4@Ň@ [Z˕`:Bɓcq(Wv}Qfkx!zgDFruV-h9έ L%a0xӅ/l ŃC ,}{rL^_wkOwf3mہfs )U8FƓ~.Z6Ȋ/6k4, :\p|̌J-Fb%|6V3+;VjH0KPsszVe*r+T2J) ި$/7|^{"V: D}ZTh={߅BȠ`K?XWT#(.Zje~ g|HSĸfJ9F]fJzW --53)ťy9ְ9g 0G֞\iiWF;v7rV=Y:{/j:QF4e#᣾^Vv3Zd(l"О.?j7S<^Z+8`׏8ޤU+1ٽǸcd%nV'Y"HIk-nq=g7̏ZCf]]m6}dp۰>}g_|ndvw\;m9?q}2'z6%P]I±s:Y n` 8lzM>,`SuBMhO_ʌz|`<4Vj\ؾ>t$/k>&>6u]"Ub7t@UWȢ2$^N}_esl% VuE6u*tK8YŸ]?f\M ]x*FraӼRZBcrɬ#BSVkf#5Q?gٙoطГ ZB}2))u5YJ]R. ֮9O/y+5@srat Ox8ۜ@)$oEtX̔v+ P^gl{c{x"JID@d{" -dժ@iv[R cnBVaIL{͚>SU zE eQ:f W(]L{ٝWzJY&t=}:s4Xmܔqȝm=pVsYl?iv6Ȏ j شe1+L.rَpp4^mkE/q.P1(O5H=ה@y|!n|jb܆|: a^aa̛!4&@|57S'q7LQ ~+*K (C|Pd9TT%lM^T jHHbfE*s| INf>GQ`]`B}V~ {ӗHҰ{[@zZ.n0/Wؗ͛Þ<~ջ?=So,ܠC*%Bt6:{@ N>LIi1<;P3ܥ(l\Ԓ7WA"J Uu&{ & |}ȇ׷3,q;ifŧp`J8еI4 2U' فΡ"8V9ΪPulGbڪ=kZ=r'9;Rݱ\M{o뎱ӧp8gK/䮝:Ygʼ$)Hm>Sȱ29zi<Jp/6c~؉t&եY93]ȡYtFC;0ǴQ|юVYS ӄd&?gL ȝ &1Aévu"8W&E \h2ʕ._{m ~Rnz֝ԁ‡llL|lMlJT̚VCfoCXH'* C<}TЗ>A&-|͐?>Te1d5yN Q$ 1M@X  ׷_;rPux)D6OrRef'$ջq{N -+U8J/tvD^%³\09`pΖpJF2 H\ `SuGD`%Hk`>K gi'W0A tJ,xxͶ~mO?BJM@ b#ݍNUe3X+U -Wa 6oZ| Rs^bꪱcN/O}T 4Aqm~-o"e'6L)hX2S;1TIY;pDmv4&e%J^  ׊8[B)* =Sq]mJVhtEܲ*~@ .//fy"q럵QST <˙(wD7Fg38yy=p+pѥ\ER5* FWXm!RNkVN$%Vn틱 \\ߒkv -x՗jĩ<2ki5/SګEkf?R߉I)hw\Ȉ@uћB3iE bZŰG7eZ.<̏Nj\6t@2{s84G‚/_5{]*5Q<#ȅ>9[ R<,U໻%8Y$餆CM"15,-PML!w&I<4, Vx!vj4}tn>EeStFa בjIoNU]3O+vGNڛxd\+tsF%M&,>=6nbgfWI ~?Е=m>m렮b1`& UjOF3({s[<5l\v'ØGdX̶ qc|-fz %T}n`&`=Oi:Ǐb3f&! Hݞ#?_UX)s_@$ZFЬ!%B@ƒ|Td3/q)yN ȋK1Z t8aGदv/uY xZ>Jq2 *.@1| y͢!Sf½=[w݊T:n027{(,b O7'DE@C1~'a8M0Zz *t?9M.Zd;@@vM2h4j%zT-tӬ?|C_q¬\{@PI oX{ ~՘m>ME|H|=/_ٔ8v<73 (TZGܘ} B% /j'Mjt0w%C #u) Qo%峔Q ?PqSDqk"gB}o¦|o?w%L +R785hhY#5KOJ حm KHs)bq>A]j^Mz0D9VK((A^F 2G F3?9"gƶ#l"j^mC͹90Q?+1nuK]œ' {F# `8`=f$EPq՗W~U9˫߹)n$+( !yf.: ]ֺeQ+k0`a3B܏{0nNϒ2fHE?_ ߏ:p="aJ;v+Q`d8fE%=b۹ixrc'_j+K..Ca"DTע}8գe @-f3BD"7 '[=tenûRnp&N: ȓrlp6B/79"3Ҹ΀ݛ!2UfvEs6URr ɣ1-h >W(F#Y]@5pCYh_`K$ ᠪV8OY3\i혞 jT*[/jG&;Oįqge_'c}Œn (r%CM" b98dA:>JӮ6jGD$= Ky :b9|u7[  ?$hDU[ɛ[3( FwyuBN+M2̇NPԓ}䎥wh.a疿\QŋcwkT""Acdl١^ޭ(yOV>;|񯂄QЀcQ*nQ+\eږqgE4/] a9VĤP$o&^vngd+N+ٜUְףWeʽifH:,ڒy/j42Sv5`-38ZX!3 tvT׶{ڷ#3oVa\@8h )LxIޜ7'-U&Y ̀4|ɿkT*n*_NfO&[0£FFC1+4Hp5͖]Nhd EgO\. GqYǧ[,4Nt=lWJ}|#۷Î&6:n ඍ(kCu%6Iǹ9P J-M-+zy:;8@1S1-fn5%\!QIi !2N:tXl.yf-pZ ѐ~\ƿ3pT|ftFfGNv8:奦>|Xsz9:]KDeKŢZn}Cj3l0vS#c/@ve|h(|Τju:POJܢSMYWɮ3'Kg<5FFqԕB'~o Sdc_KXjR;tÚUH]j1cj0(_eY՛mۺyʾʆ5ƈYyX-1кt, sNv<򏔌/Fbso˰@z¥@e Ruõ[j'B%vdyPav"EkF'|rofecڧy}Y{r%1'h^_EirޣcHy;0d]Q>>%D6U/ڹ<27[u#sI@pب GMM qFJJ;k>U|SRl=\!&V%G֡FJUͥXT-fm)̆ X) ѧB!~F| h%n/΅"z1vWpk!rq[;[څ>o!P7!dXX k]{K ܠ98X>!pޑ'uQꆂ0U N[܆|?_P2,5`ČGx;TJybڝNH:Z ymtʓsK8 \ v#V~?~4z}@y_Pʀȝ~PkaMPQ;r:Qs, m-sgV\JFx7vPSG8n͞r[,sʂ%TH hҢDݿDS>+E?PbܗWOԲB~ZVU1 \JEߔd%ѳhRE<:y[-׼n^%jJ5jZt}uף֤4p|Wt11Of=G@ʞ.ZܑEk֫QC'._^͒tS"Ǚ;tD`g+cZ׫5(-[,l[{vۮᵵmCܸi A|jVZL #]l,(((/\iGwtP۽${7pcO?V =*' 'BH:s;zO4o,T6 Ǔ_Xb `Љc: KᯁT_1tOh\Ho 4n^ͤӽ oP i;p`;@ ;bIyOWB{iUdr˜znV /GhK8%i9[:xڡGQ/`a]V_W[)ŕ4$j&?1I*( v 90kN2OfiC5A.D%#=sӒ"n/0vƩ;v؉S.^~6_l-.;Y'\?7(bD.6N-Gs! +!G(O8-J^VȂAI{Ɲ=ַʽ!Exoi{QnM+ph27jא9;j^KLPGmvÇ6 Ri 1eP/6RYЙRԌhTMqKR/wË)2j|gKE ౄcvnENO $(RRqC$ g`?b2kkjT@qd\G] Ya5jCStޛLg!0_)(hj䟄4cWRT5y:{Κ|5ou=OĚk֪ۨYD&URcnܓvS/6 :M~ڬLJ/#Cٲea@@ҥy`f%;>؇G\_(%NߟS߄̒:G,55G~{1 m߻7[oguȖr=NR[x9S-mi5qXC RS?M!>Y9˳ L%~8WyAa rerh3a=_:NG971~cvMkB8 VG 0wk`[ςѠwfЭdJan_^)$6sU•a<'14l&ͧ1]% 9sJ3sWǚsb M+s/=3̐!ϫͻ8| ݼsvuș>]AKƹ}ہcʵ" Vҽ\+9vJ:_~ׯUVL^%Z)fMz!'`>'rOSV{k},\]CM=o̩١NLz `9~A T-pF,.WVsTL@D}SQ:bݿ\DR˅VTZiu j9^OޤWx:2>_Ui 9T󐳞Y߃Z{cq#DeedD??V/-3h6fc8W,'QLp )BߜU),`5Fmp‰"8ݹ/Tq`Ez2\H^ϡCOP7fĮ߱⒒Sm$DEQU5wygCS~ n"[zTOOCWao]=#=qf;B_[`BJ n(dt؎j2]#*H%Z?olEL4EuȜ\151n;@mpˠa #Ty,ݖVbt6m j g]ceȇa"!V݉MKCy?)eVbj?yӕ"f컖Vvyuް>2W2/22¼2RUmm"ǪWm8x[h1$oTȅ J+8f CQdhv2*"/gҐ }rVMV>Inգ|l]5s÷aS̙4O;J\/c8OкQĞʺB.*a5'zA1^k6]ά+j#T(2Ut}ohaAazkҗSJ.'gB:BpBa3]M; tLjVSN҃ q{.:ysUkYQ4A *8ȵ_oqmOЋ82au&Vl !PJ@`Y)F;ُeX,4h"ц2g| @^Q[mSػ%/0v4>UЈ5>, ޻n L;aQhw;V!rد(ICDhj2IP{&>n#ucu xփw5GS?W,Zφ1 (AUQcCĵ *_cGxIK(rt a<wSAY.(X< 1yVYj1f7lX`Xê6&B_DŽq InZ;l={WLH[5!$Iްje햊/ k ާ ~qDW@`n]g;c!4OjS2eSQNkbE6[@g܃WytE;9u4f$|:1PS,] !9*3>ט; sns‘7u ungߎNILpev: x?^@[.8{ @Fjg Maf4$<^XAA WጜF2kjkl, b~7Rݜ@FQk1N^dȑ;VW\K5VK Ƶ&qjn]^q\ ȸ29F{]wUf3-XZX#s3}~r@ ^ Pn^u"LqzMq̗*q?E-xu^{{HpJ,.Y ZEi̒Ȅ/ӯH>!hY儒%Y G2_<6@y; (wTg:,Nhs҇MPue)'I^U%^8Ҁp1|)rN C} s©sJW}5 kn}~pqWyxY >U9?GCuYl )78WƄ)s3PQB$eBd!&<]\.6eC94{_2j-}R/J=2|8oVؖHJOhj>-rj)ϹA(Gc5RR+ȸT 3cNpIi#m,|-K=>r-.5\!1|iۜ)v:]3x\?J<͉XatU5 v¡Xi{Z{&=Jf^Kd~͖aZħ\P=1ǒHF_9QUޥrPۯrs,GD{H7K܀F]$qJ0\3ry  G=t쪘DK8CJ]m&tr6 ]4_iaBC&{7Qvdj"gC 'e9i,83>U?yrTBuNo|A :!0:$h@U? G'u|<"CymJiF'u6k2Qsv#~A-5K=*َ5{^9ZI`DuB7Am$=Erz5IJqMoNEdy3d ElUӹR0݃kA'*R\Rd& ez"]R/dL›ac/Nio+'U%:_ cM*/iئ '˻JgiuZ©yk4X4c5N_yݨ3JBm.>$〤S`^l3zI5W@gvF#՛k#+Ӝht+#ỗ ش4}]`g-4cwr#-㠔h.W=[SD;^dx'nшΰ'!DQpOAf5qnT|ul҅  1Swr:A4`ǥl]!E&XpŅmPyoB'>~/ Sv9ZޏX҅Hgϣ?d̞.ciʳΤ0GB2qIղJrY贝GMcj̔.qg=eܰR+hkp0YG$":f7ʜsmvL/)#7/B'm_)#h:,q뚋k>Z :F{uXzt\ýZP+PnH9@͊8<0~ MR;!7uH..) -rBT`o[U5ĵИzH| ϕTXR2*qEa7=h+܄K&A\RXj*!] .)I;e]y;QD(]tkDlm*ʉ-ؾ3FJ#aheExT7PE>/70ﴊxlDjMQm,a$`ο(E8i0Oa ./o%=WV4\yLR p'(V#:4{$n- tndjp719XXM`fGy'=hH-pqAPО*PeVg/So({X$xg$Ƃ#(APSW9E5KEv/}z ,2b۟FtNv%Z؍ۀe;˷7s*4N'MÃ/DV 'A7|gI^F%FQ(n8UgoToiAt!F+1"EK:\l qPEc-|u0kf J̩A$4v2UPb^; XK+[ӋCTNw%56LexP69!:uA# Wo?SYTmY'4p[ѕeY;V:ȺvGJ ୍?%G?ye1N +@_`R:ܢ0C@WB#{ yo0`Uod7%xdRFf mb/J[t5? Y2Vb^d}$NK 'I9_01rs/ou ka!k+/=ӫ[OJk!18fX}@^Zj\Jiy>Bd pbMi&t5*qjkgFe9˜&k-Ys =㝘ZDZay]o&"FYț<6ǹi5|&J-+A'-c;W`ނ~ӊV&jC]rqU SQeNJ @KJأ J{#h_zZ+^Ǝ^xU\ RYt?$wp}9]MD8tV)ֽr9_Z2JT/wIB'rjP5\$n] 'SfH_Ͻ.VK_C*Bšui؋EEaJo|I_ O<˛J›@ 馯à,$\&#I6-v̭>6'"\/Om=[0>^q t`oy~M>8o^Rs Z)&(4 סl'jU`K7HB:o~>W/*S蒞o|N=9 Vj +ڊfТ/ !Q>oeҶjt欴\A ȃs^:LEp(Aٓ#u4L\ӓPEڨ{Gc(V01kzIT,Eg_$8fFAxNv;8gP4 8$cbn-nUЍ9~h:$UcPG$)[t+`f\=u0m{xaz>L mXSTc?).G_]{[DF\Hvʑ2!Cف]pnxP#gעmrYy.F֢m"Ėc$FGK؃⢠x' KJ*3L%?0ٗ%ZUiw9p:Vsb'Z^c^pmY c#D%bBƻHmrI5gM}ZZCW!.jAuf<"АEQ$ c.ֶjs>X7/ IN{Vu. +rcCu\>N(م6MbsJ M5d>Ng4/ vesˇ$Ƕn (-*; c ܶc;|VIBzN[|Ɵ+RjʓGkO8 \nGxdQ9Z;L˯~mWq'SU;qpp"C-1Oޫo9gD% |=I0qK+<1NOS2t1xZrK5: Տ%vt^;rv#&%?ǽ@,Ƭ.<|E+ycd碹/sf*9^\r23.H.'F#MnzoxE蔇WuT:Wպ~gJ|nqvP'һIU1ܗۤ;Z[l/dxО)4Z9ׇʷ=vΪh  RH(6^U˲rGe. wʋԡ J(E6%my=8P0)g<*8$A~(t|!+Tv/ Lխ B2qvDK]bu $ey=7" ' K\8Od7Q]~3BxGZyIJh\Y4GA1/m6_rɤܼlkO ( `Owss/tp4E߿graڋ2]/7p1Ŧ~KQ` lG>{?`pI=X~"bco@T0FNu_> aŚ/=uεgŎ؇WlW*tEy LЖ.erv62$,i0"pQJ<|Z])(Ow:rW5r}RxKuأٲIw-ѼaiUڗ8) ܊2 dyӗ`]]CaiTS0^w9T'˰n0ApvJ陥Wi.{ JS ( VC"zzZyFT&nu :Rzc.)4 ߡRCWH;ɟպ%%Be7 ) :x[+zK eO[/ZP|Nz#gR|RM.FgƂ\%'9Psl*7f OK%6oѯ*% ^yч81oՙc<BH\[Ru.>~^ΐ20$Er{(.:r~QCET;V~ǨW'#C);ѩi/4"YTo>Ϥ%(st ,Y`i wfwpcD9V]_|!'L31YeoprG4./0 B.L_n ai DfH|rb̈́|\!b=uM/fONW@6T[Īʊ.K? =]jQ"|bKk3U Ou8@t,(KPr)/WEZuI/r% n]·ȗ.7mr.6œɓ뛨R+#emШQF֭VFPT.yuj'w/b NlQY\-1N0oP9{*Ѫ"ڻyxTj5޵u*W@t,~Yn%4k^?T,طhJ[(0LB%Jd(iԕF@fqrrQn@ȴm&uUkM sMB|| zQ-;#Ɂ9\ Ѭ!4t{w߼F^zUW%;!S9hȪsF9;{x[jGc>әԎr7MoFғ vLow#d"ܬcNz9b[&c`ܛ|^6)[V Wk a/IOv晙ƍVB$cr)6 ClβTs$ߒ}SBC7^usS/Q!ЖUB[ޏOD^m-/R373R=wM5zo o4_s}\3ߎkjY_9GJ(MV*nBOޏ*Wg#Bj5$cc?{)a{7nWΗ?jti݇>C_~#{Q)WA/.o-we-8~}qj DZDQWDb>:nqKki挧ףoVMoVϤ\h5ʍS6͈; ;e;xf#889XcIYӼeg!(~ :* \'O3)\3?gуŘp)蹫Ӿs>s>3oJ, g˶䦾 {có$9L꣟jDZHf3?~у4-'z."1"5[>6SN`ڗ\5c~u ioq%o6 ⶓIvU 3w@ꌜqIHzxbA&X%fzpVSk=i U 2vw_R]ҎlyjƔ{=kKr9ǜu7`c[R~|ByM_|çIy? !3+Z׷~9lDdE˕+? 5Oڭ:;8u>7w6Y!Xknگz6v\PH}4/}-'+3#=^o>NU ">5r~OrV<1΋-xARjuxZuuʅsW37c/eĪpavg[c j d!׽eҳsdIVxނˤеft/~uyxv/\GX #v,iLUmMT=9ABڢҎMa4:a ^[:l#OljFFFZ*R8&O+G*?_@hc3s5 fb$(5!PSdz;29ĉ `qS$o%e-.Хm\ 80lt$3M#sKZ* ||Kt k$P}ňx Sx 7^Z3~B&fUʗyMbhg0nRS^'t fΥ-/u L4&ꉞ=-wXl^G5yu+3|<5VaWCB;<{ c )[t:-gx=3hI2&% -FG'Ou*/`rLL?(mkO9}S+'2q7EIl'V<:KdA"Mk%)l>m̓'$ljTƫx={:GfW2~z+&!悲tSN˩V:KwBpU0Ixx!䰂V&j@%Lph+$)X3E"6EBirJO=^If=-7z7S<]"jD[2!y̧c2H:WHŰ.o[ZOmǘ s`bDŮ>XT1{c?;8|Y.[\rn0~9B-ˠ? W"/K06A:6>I`W,Њ2cU{Hϓy'0$CYt6F2̸zXcډ}@u?h1zUʅO̶5C fE5 =7s1(Bs0"gj|?n.=D;\^0+td/%\/=tβJ͜C}BϢA̶ܩ bq≞;C6PS&P**Ys^R[`F.jJY  a+<@ŭ7%Y/hHK wE~G~-i9SW$?B[Vd\4@E1O K9yM~2'nR4V;=E,@VQ/)O4I<@,(r%A$>ҕg`oTp|Ȳ ۇx{7y۷wRP8ڐ׃s8d_ãN <5,m@zZ9E\l;g)-g AgOL֘ʌdf5nH*b/VRdUg -$btY̩Xl O~)}XU^a]ϪTf9Buӗ ` HB%{:%˷I@nXըO>a ]Q138JђDuenb^2BcuTh3&#\y R}ȶ29wASB~N`V~WrƧ}-Ѱ JkSrޤm֟UW"+3y,y[,^3)bj6$ { {5;ΊӋQ3F5d`JΟm䌝%܎`[LuAMd?((TBs4iKA2zxX4#d"KjEm'jyeYc :~trhM{t>D+À帇dž̯k)V,rJ-h rz;Q13hHKhŘ-oʻ_~2c Vw$% ~''~K8!>! .]Le;iɦ^P̓&K|CèK %tbv|VKڰe}h$⸹Q/$Q#'ʨzRW &D? L%`:r~g@o|6{ BXo5 }V!j*Aa7XƆ|Y)2 #tVYH6Tk(6Ұ_`|KҴ797`Wfn )fU3͢& W6h`AשWMfm鼪46q ^yu="a;=@?Qndȏ-+jxGSIǞߑH`3}.IjS柯ͨ3:{D۽Ub@] ^MDW{ : [ԯݸgq8);x?'yx04B㢸7fi?':&RSO['.opE0wN~a({7@ ==c0na}3{3LoyNi.pH. %Y0{ ;ʴc:iu4W_ }ZOnn2,Ƭ2r5ObbV_4\x)s"e3*KÉge`-ίn/+ ԇ ߿^)IĈ;sz kD^ȸ|{(Lq~}Y|3:`/_ 2퓙С^Wag*$H rX#hI }'=.A ٟ~VHy>Q:NִY]z׃dvbWmә&=쟅pj)eD; 7HcO75Gi` -v-Q`^"B`'ZR I{5t0JƞTS_osPÍ 1`g")2b5 ;.14T${7wx "-kŦNECxQ8wta8hsuhtSD]Nv}P%zQ ӧ( ]+@G ͟.ɄOL43|Ssb&Xb7!K%^y\LP.ːѣ¸U"챌<*lL[BT벟'8yFÃ/-l<7/%yj4]$7jӴs;vceנ̳MĴٽ3: ̨o@5NP',et=(j[´0J:Wtkcέag zz2i'2v4O)hd K:x XtK}4bE˚ravP~ vbz93Fz^3@[gd_Pd'3CVO L9+w` xDyxKgjYakBe.y%vtT`s># da$x>J,_rcxRᒵ"boˡ-J7~2;1[oP dgR)\*`j@~4eKxъqpznIZ]hbveGh)b߬^gc<Жb+✄Vnm%i0 7`7nE`UOTvc)OcW}cˌ:*X+j- 6 / Љ")p}3hI r?8a9Nm90MX[TKd_:qo`5EG>~jM?[z,29Fc@ޡYٴ+ޣS}Pv+ Z"CʂzU uۙ@N_6Xe):H&f +I2wMgeO8;" 2xZcPb?6(Hfjh<@#z)ƛ Jǻ]nx׾˻cyI dkIihҼ Lݽ-=<%BV 2ρG,jҬ(Es?/s-uI;B jqjcȝj*2F [ ;:WA+d=dȟo1v8+KMJLe3Id~mtϸq#/9Ou#Jl-  CQ"#k?eV\|m*hK:3] ZuS řv2Z=i8勞`6VL\er! :_5O%?õH.O<țDFs4 )wDA|Č&Ǝ_6Q:7hùà:]-kXeýUn]\@xtJY޳N5+D2N=o4jЊX/img K`Klky^̲jx"Ϭb-m~N UƋԱLb p /!, t)*ȋt;3CҞCx*14$ab$&D@ӭRek`dw/I?mMmW^2Y3ưtHt ̈Ex DK;OM]mT|G81fq=bqHvݦB?8XEC㴧梏Qw#>NY Who XE.0_wmPj" +h ȹmˆNyGhE<[Hm_3ғ,vIvns3BQU QF5ɯ y`[bf )sO#Y-]Œ xdMM n9/q5੎oq.6,)FeyIQr}n\5w@9[ahHa(ֲ6Qb ÿ/;Vv Ppnӹʒ!4t8N"rIsǹPNU2-O~fѺ#s!|5ɥV\_m<+1oÌeH 66_e!Z:f@/`U5J"Fᆬ}MRdq]d~Mhcbĭu;sd~-Y559KwAYS`\/?S#pBkHl7K鷀F:GS ntPGb#rE^jf2+8o{qJWڡF<\s(4Gd=hPgJ ,hBbFp6̃"+|IHA_ʑtj 1ipooDqsv<;c1I4x1$$U]0[!^7h7,0C1,Og(ۀ -+: X»O^5s;%+Gi8l[?OG/fT+ Dgp}xG<lkO%<'Ы] yXQ=r mv @#;ozNH7|/-r-XW5g$c8Y< Gд c3v{>Fl7MCv{s^r_:k \RAO[<+Gѧ{1R :{A&X ܈U}_ihS "ÈqN/I(ulԲn|! x@ 4U!ՠn(BśWK,'t0uB[S͏68]}*2IOHq65GcV,WZ^ܨL:j޲u\/#xN<'F)1H8ʾUaDq?wki (K)ˮ?gknybҋ:|P |xޟAXURy eQ5\7DJiHcU] [~xt"|Fl9-G6ģ2 pbIy,qg쿋M7 IWm`TuO d]056Q6YQY "[iFlWǣ2X/Yrrhue*\sjf~hbٽAoO_[!:Zǹmc!8G iQ0h' jPXc8\ Q0k6ͬyVq}\Ì\`J;OodEA>8"kNjjc4JO%l6% j5`Ԩok ¥s~9T, &~赞VI[Vj*U `kC[,eUL{2r$+#3Aў9n@Bg d< ;i܇4|٦L=!) \dƝwtĥee;@ ؈o'._BQ&u-?׊Wג|3ڃ ]bGٽ$ޮ*|>KŘ 6a13.7 d܌G AR̈́&-+G:5Sm'ٳ=- (B>tRӫĜUWWIsP  fn>SUyO_,I'98eT0ZIwٺֳVAA&-pJ{{R&_X5Udp{8gDWW5&s ꣊բWܱ& qt[N|lH3]@G4aY-h3u>ec &aR~I?p\R]wH82J/4OI'5JbI,YJڀ}xF{m[xi 4ib|=? >jCNN*P:hXSkFkӱi(ygLV _"83\Lߏ0ya"ӷ0yōqDF,Wa*7.X0}7LwzZҰ-\ԯ5gwfd6ix!w9̋wvddv0HI wȚlHfv^PZ COyw]7},6'Y/7pt Eu.cE`gM\9aOV t>Q߱\,}ZU֧?][BhAq $H`ϳ__1MOѵGۭ0/7<)2Mq }tGf+t= Nޒ=B(#p+QЙ+aq{ D#J:\a^pǁ큹RuB~/dwODJZܑ2`\ = 1iO`ƺ`8Av>s#??ݽ#B4O8;0a[)mANЁӦLU]{ 3£.yBmѷ$ v?]1lrYópq!0SBsXc ]y_tXFxشʜyEԻ=a}=,h71πSŁ35jV*o ԏ7EzMksYo"g%}A zog-፟hG{$cfmm.܂=baR৔ͻ58MAMnojҺo]wӺW``R7᱌O 4L)*Fp[BRB<v Swsz7Q:bZYSnZ{ⴽ^,23-o mjL/<4[d2#d2>IJ,ތo̹@%ncc\KߚDer۬DU J2hE?ϱz'_z?lT8qbtզ5 tvSm,| .>5!wClj\Wo)'>aDOY0 vw)3:\ eK K(n%x205(u: ̾E>;">@ bƹ\3,r#cmEdB[ ?ʨr99iy)9܂(z~j>"20'(!kF̅Rޏ܍Xdm@6Z i?ffl0}Kq͌4RpUZd7Աggs.~^mԕv4:c 6u'?/eއpW`xd!]QXQJP2VT~*nHr <)BW> ?/Je9hCYu.Zc'Evk >+ h)H^|G}f6}=%M ?;؊݇W5x%% aJ/س?tמ!?k}Ge'Z`rYrOKˡ[zpy.21y[0uQ"Tb)c~+Z.GoVh UM`LA =}t5@K FOHi z2|P aMCJ8YGB]{_-i]vZg`Mܧ:6E:?8єpcFEUϋOCZ%nP8Nv# ,2?w?{[_BWN3{;B,Σ瘺a̼Yo77e, 4<+dt\'CoiFi{x&}pVdl bbV)/^%Nxg _(54aWw:qm˻,7[tH*PL 0RIc(%\H:Cчlan+o<*bJ̸eϛq".o9u7{PK|0 ZZXYY- -5GwDYK˒\(A0\uI|jt Rj0#+0wDPe< &l.qCʻQ+(e+Gyѫ@&NѫQ֖2jhcTD 0Mr^3jpΠt4$<]9~O?lԆP몐*WRQ79xO@uZ}to, S\>sxQt\梃tqV'ɇC3j,{i633%P=F]޾Z_ӳ0H)wWܽL5Q/m[5̬;9h"(FVz hwyڡ?hoo+m鉋]8Tyj5e՘ا| vXxQz-پp^xs3^hjL|͆7ܽqvƕxժ8 I1A_upq-PЩ6Gm4A 5—|d+uwo^><6 u_Z=`MYu­a#4%WX>$r7D^E[Xǒ|թjAzA\:kvY% lP42l* *ͻWz NBJVƑ׆2d^ūҩXT_nY+i=+M'~<, 8N1aуG=V@oEc4F ЕM[6;*tȲj]Ȫ|.@Ș>6}*MWޏ ~SNLOwә7x5W,9:gYAP <$礿dSP-W>| Ɇ*%pkޢm.)PBxi`ĩ Ph+w9C:DhlyMޢmIlH \GDp7*F:Cyȡ<ꊡĉ֒lk֝8m,bQ A-aralW!74{GZC"+>&T_f=Uw$yzn[4q3dQߥMVrHw v1(cpÕGQߍSd8_VLꩱ!>`ssR΃ \]V /Dټq"~KYb68H.`[!r-}5z`+  y&=\s#;vwN>;/(}#$`[_[p3ЦKdad=SB?b׺k-{q;nGq`i.DPxl1Q_G`N-= Y-O1+H:blnLŎ:52LuĶorl>fb3ͣ&[}<{z5Al@> KùZ3mCuQ4ϻm،:͏֔4.o~UxLօ5Z6SX4(w`IRƯ!9lq,ay,۸ 3A6@I8 ܫoy M3cW HLLQG7lc*ilW: s$ف˟'^44| OqNJ7j4ϐ%fwцlg$3f O9K`7jt_P [:S>>ɴ'jڢ1O2\l+& 0%a},GPPͅVhGZ\Up\YGA㕌$X}/ߒ d)=ėVZsƙ:$э $t z4NU([Eb ۺ+=.|8pKH#1;RcGGJuJ4L"M4mB;Nz3mMr8S/*, $Le(}6t f}Da~ >5)޿+!Yx?Vby~iq/\';MnŏrgNƻ@;c5՘̌ ӾɜpWCQTi\>gXdQ9rC2(9y0:t32mR,⩱Qv9ˤpzgmisTGmH u~o' –CO՜(({?cqNz6X\/Z. =*gMNFCy%%j:IH}fE֛ޑ:/?#AOE $P'@p$7ψBau#L5mTG|V}Yg5)G+9~dOߒGکOy5iHy/Ne NJ"uԿ$AJ0HO)L3'luGOqR zyR- rv*\{ͺy!>`Z bG#и@ in;9NDBMh7]/,Mc{V?}i2sZj>!܂CC94D߈^[߹V^0BuZ[:>5Qr/{z,f,U\\ZhI#ŏ#dq#|/y.i`F )lQCn` v\326jw) -GU\*S:2UIbЭwQ?@iL1ZSaiqrE(coFKGɗ&p ~~_oy-wmbKŶMq'KzyI!HM

    Z_aڭ<{a<Sr"Ez3ým9O- 5FJ~Ibxm/_Ʀ48,+Pt+U~%́p9QV#GĜeU_"b۶.>sS?0~L.L=^e90RݪZыwO$ 0Sn{ . )t`9i:7jd,_FNYU.5mlV~q=lW\7x3rD}3BW^SS{>Klȿ+.eM+9ԟ SߜۀaC:7oڶ6C[m1z}?1wǡì!-'ʒ a'1סG-ulq?rD1OFei&T9"7A_Vl>nS){YIzn^5l ޾A]oڊ򑾴hõOќvSn$M?xGu6_-F{ѽ OlU+>ə%.?82xYA?T3V,L+NÜV={u+Z=:gk9𭞫4N+oޘ"n[o@oPXxLPF5{H/8gT,n"@n|ϡ&N ^>4Z G;*¶IaIĎsn^ef)+ۧ8`tG 3 +au5i^RtHץq7YO0*ڢyZmV\|#PORX֩"jm,9DM5Ap43g[MdbfW@ e({E߾ɸ} [L/Z]jYM"#[NJO/ ll' ݇A:T&6XŜk*P)q̨0J֢[r9'=ť$G֛@9oo%{/cgצ77H|nKn(EjvpG'<ޞme?''G]o>k?E]\"qα/u|!s|\d;Aj/9 D_yʱIoE5! m /7;i~7#%˫?xH;c7s6*QG;uW{Z|x pΜ1흘U AWrf1w:g&HH7_㓚bѢcIJ~=(1 :A"qɱXG{Rˠ]W7%l#A@DPÍ\ "6Z,ˀdNAt)ދ2Ѻ\Y܈[~.~4Y;. t߹XiL'Hu焅flߴS WEZ b c;-pSno)q`>Y9-| m{)0[Bw#XwﷴfH1MKw2;njŷw(KA2_ pY1̡a{@l-tpJ!A dmoS"LX/n'((i;rqQㄖ땻pUdz6h`@#$`}-ϼ(E}~:7 n,ௐny/&q3 ƒ*i.P6bJgtW20VܳD&߫)?&;1#5sjΤdEz##o6qDZ8&!zX+)]zh:xaBT7Ɔy o\bd{܄B6Z^LqQCYh}֮gA9C Te~ mJEvYڐ^5]j\p' :vsPͦ[p##7w9x}ApTwbe!f~.t BM+Ae9Gߏ 9شN[CQ[5ml j e#}Xz!'(QUݒC[X!Ɋ8,%DG͊[H\PFJLQӲ97 *}ypQu~4V+Vud3QMF.J`^Ng8j{熀^1 S)wpWI9TJ^2j%s~d9c*Ϛ!J{ PZY܊@{KQk06T+JJh2V{X5=%Rq{gz&҃:irYc>:Pi>lC]4pDeE؍1 <%Da>&Y(gv) tC&Y u;A l57's8&FtEAzzL DuPyPQGr(K{gI{s ?9n } cɠb2'QYȓ"#^o6jd H+,ŋ4*l71]3;:j qAJPpzR*cA륃KjѝyzʱnxDzs]t.ѣ٭9s"bCυ+H1o;@/!dsWT<{*z]Wʂ&m%XXޛRɇ5ŊȸUUD%A怳WʊO+SpS(ٖ/vtx{@P-i* X,  2Wʓ$nFӂ<2~AQ!ט[kĶ%6^EXJחrZ+7VmkrΏ Hȴ8 ( Ǥ] 4 p;Jnòg2V73x1j-v?ʠ, sX6f[&\ ڢe5:øOStsR"Z , ~mk4mj<5ĵD3fRx~}ZdϯtKyfm6B۟ ,702mP#ZӏДSC ƍJ'VJ5 _6ކO&Pv_%C{AݶC$?JSk &c±R|=dkIaUXpbR*ka^fNJfL @6{ "$~@IU~4Yw@4Dt{*ǣ°{0/hp8n~y>v^dW^ʇ=W. =F|6@QH&*=m\Rޡvj\Q??ηc: D?<:esjmְ,6͍)Lc \.eA/gCG]{rbrnEmz"flJ+]<hr'ɱE*|l6k[x^g8qn wU/AH6=`2jH5v[*~7'[G~03#^!=?G1W/_0SAN>_BicѺy['.fGB\nlbi%B֙8&^2XLqZZj >PW[=g S)r_Po6ֻED믝 Q` 2lbuY|].xo_jԃLـ*pciC&q*)6O mYxM4As+uv}Ajԏn`,? +7q |J> aFʕ۱Yi)/1ژm5=á owv 5jhdQKrdfg#ܱ_:Brbȝ=jm=$A nnu8O@{I=R^;\ Ӥ1O#v|?8Ɲ-{O˯(J=vx:(tt/57Oom.*$ts[vj*V]h4G/e/m9VɊO81:( !f wD PΤ#p_.6:.͑=mbƵ %ܚ[яN`5Ҹ ^/w 5Q~q3l&6IC 73c)6dj?͙)^তP]aQcc Q/8D"b#aĘ^_ÍkPPΧ{+[rY72ƭtkJ-[Ǣͣ\h n%qC +-pOՇ j?WO؆g`n98ԕ듅+ҮpcIlqω)^ /-3P {`?Y:WR~MNDƂ[ nnK,ܐb3symPsIYM|=\pfZIYLa%%CVT7ؘWZc?Ȕ+ڟy-;.ȆV*O\c@oBx~e/չ6 U:Yk%#j?:9BLW^K6@Xlo>Λ%}1K|jeڎ$wK˟{$p~CܔC=pw|kwضx&NolJBy٬\݁R%?.6nOVsEg'‰ U YWs[CIN@Q\>rnͮ9N7~Kt0E@Y.|ᑅ tδcDoă1vQwl1}1xg_l0xBOͥ&b0}!ڼ̿'['/Ho̒ [1Uz޾-}!̤mn҃3\W.M)7o_;гp>=S ,#O`3waK+쩻F )^aO9]˻GK_ B,ۿDzoU߁S=p (w? ?i:*ymXI=aIM2lk~ylpn]d#{,7_J廡>'i4Jeh ^cѱ}E3`+[%=`,l97v7Yx>![z^w"_f8Βs38n{`+̣8U|J(J&-)n9*a]'}[d/1eTkǗ%mluULmS%"d\!/Zy|jZԷ[mo,NOx}(O.\iY3Baϳ##㔃T&Ț7rbBGN"-FqzJfcnCKUYBX"cԿY{{ǒlUmL֚irЇ qo-$ 7%קMuB wmrcu߱u}(3s R9kCMa }.۷oGm 6acxȮBK%y$sڏUxxq4'7،ַx!̘>'^h+55|Zҫ.>7 k#LQY<שM5&ôtΡ AS|qݗ|:|ms}/FL[m$u [{ƞRY!Ms-Pd~T|]JO!tLʩG.¨?>yf10S@CQS0]6-c}ǮL#1Mʍ}p؂. ¬${s3T}3g?A5VϨF}jg:?%<ŠV{:69OC(?| Eۈ'd MIZC }ϔ~oK9U#lOs3՚vpkhCq!\Uql耎;| ?Å*s2Sx߁[=py2kC ƙ1g":ಕ} ۘ^y'p:XޢmekR|ܚ< _[' ;~٫b1_^6~/jp!A$=čil/84A"[qSwH0?pf4GLx[Xݯ_ތl3tIqwp {)Af{3ns3֫[ziSMѱM6]|'01=-4Mp!AmTz^zcf"}m5[0ۂn7-ꚜ{!( *1T9ۘ+#e[r״,Y-b$n6{p:.Ltlu=svn!Gy=tǑZ9C6z ާp""['Zv)}1hON XrLƩtծ ҉-mC Q x"N[M Ң,V kڞJG86lM*a:/ï^׸yaoĵcmE.gˊNP:V)?zbcf*n-![qS?nmdL^.-)=gc;E~oK498ؐZoeʌco ŷ`roƍ/$?G̷oZ69?Å>kI$0ݛ6~7 m柁fWca["c v>[$vipMe|r%<+Yʤ{'>]ƈvy;po6IIV~jnUcQ)5 ںl/Y4AMmuw/ue&31I%RD )=/hd6 l?ѧeAMqpnꁏui ًPe^)xؤNN&Jq3ڂOhMn.$Rέx1m}@J ᢷB yy+bc /dK-wS7N[K I>uv^vBolˍ0Fm)oo`kh3SXznfyHUR׷-Yk{}rI B3~m;oi N?uh&H ƩE^*^ˡyؼ۩Yk^'t+ c6o^[Xwtf5= v[$>%V糆e,׻/kQz!66VֹXkf@ jlz0KoAd{!؄>ܠ Ag߀Za~2{~,q!.6lƔ!^Y6)fb3 –T }ͨw ]C\ GH Wh)94l)GE"]mYAߖp>fo* V@jµy-p{N>xIųEN"jl5[J-A;zfZ L(0S P?`0# s`?1G}|sԟIA%ʵA{=yMbrmqqC]:y F1IƲjbEfSI񻱶EjL^ qw9pVFE g, sEgIk#[f&md~Y[rz$ 띖АTg^NX*uPlck{#F욽bdžnsA=a*Ld|lH7~5܌JRmI']j3Bo?hU|ZjB띭lUy ސiLIys,^?e$5඾HQh{OM !2NVCߪEZf*z?[ H <'1OH? $V0^X6ӃoKiW]s 6:V- ,̶H.RXǟhڷ?!6 m%e`djPmFfD*V[C&til/?wMھoB*cPK p%g9{\(noYn1܌C89p,)r&UCk_ b(cqMlpw Xx%mwL=X n'YBEI`9v΍f{`3fb!cTUI@e],d+ -q͢c*|NW*ސ羠m^AFjwZ%návK.>#U>;?| 9^H5st09nn'[ ;e4G]йZr3CаWR?@qW:~c P&VlWAmXë^Ql''1ſ +7t?ndLmdc0b%%=íl+rl[K n3f;J}Jnf4fb3Ʀo)Xgmo~ydpda"u86E6OMw .| x( # =dkavV 2ꃵ,]\jq{d{8 Yyl#JʹԹ f r36ᵖ/)_K-mnBE"JI HNtqUAalk7zIt$mxGI1J@0[EņlpoJ&ե}im?1=FL fJ7,W I}+HǍ >0:*#Gt<{Hqn5SɽWr>`SbI;zɬ-wdE,[ҎMdmevXK=4W3o/sktKNoXfq S#{~q.A-,gsbs k|Z/WswozD;ΪKTeLs^߾vMls Yn?e8;s/йMI?$|*Rsv?L(m>7XnH=8<9-֏7d2<62k9%hKfqJ\mmW|mkIa] .\oԕFxȹy qbp5y|#[[nl!:ݿ_y۱9-fͼaKi>hq-ߖo͸}~hz ]Eܞma%^ouYh s]۶}a=@?]ܒ/L>5? uλ2`xM>U+w=KǛN;wm؋ ^+R5e*lG?rdun)B~` ٺǃGGXv;+m//l_◟,9cصE⪮:86c,usP77X|ʅ󿛰  L>=},)' !ǛcԥMg8uߓ%QuTw$Z/hSi n [kT6MJF1<|#M\ l Ƕ6~U)u~zZ9 --}m}̾aEAPЌ7-Qop$U8C?eSC{u\: VUxff>־=|: 8,.[摙sp9^/U|w͖n!Y}~\vgl+KhKstLG~ENX7O̬H{45m/~vsq<cf?~cL%;ƙ?޼ybRuX?O~/ndԾ]4}FϕsW;ipz8WV۾Am.I\F&SmOVgI\l([$z(hsŋPq2NJD,c0 ,9r`c+-]<2˗+4*2qխ;5jr8Nϝw-uV b'\{H?e\9,,qLV֠ g B,U?S,7%07sߟ2b`fbnBtG\0v:hQU͖+c?~MHSr@VoMq5fNſ{S14\^0sH865Ŗy_ַu6u< }ĊXFd'Y=?^7F#)M%-I$˚z1Cwoڠ`@jÆڠHRMt\p[FVPr[f mGz׮^psfnYĎI2|']m}9>tNPF]b(Kv틳#pVkѶJ$Wn^mο# K~L|u{;G;'5wV;EPb؜9oܳ ]͑qwh SɷYsc2q=_ "#F"0քS"I.7vݺņtcO=('G$PBU 1"8 7%Jsfn^+衺qYBy%RhWA.0Q_<ųu)۳ i-g<J͕mU[t/ޏ+yh?( vgw:Sa׶lnWɯQ6β?Gbehs*Z#s|s/,J B,1"t5ҋm[NJL~Q=+fp//%8KCC"790#iv(\e7D‡n#/q8>}f͏+l# ti?I;͇Pxq?>=_TWΏ,:!>?N<8I|?B?_,1Y+ǡ)/[ ` {Rٳk/sp\짰j US4ɋ@_OWR]p۔#} cu Xpĝ}F]*,<\qi,Cg[ _ #_Usg#_mnk#ƣB) '/6bwzE܃֖}Q85頌f+vmXz؀-?PPYQhАnl-kV,60uNW&ݻo+ 8,XnGhנz&_/Kgd5t?kO8KGоA:Y|GSGj'_4k%p o]9oiV熏4 Ztu?z{;WN.٪-6qͿ|?e]G]txg5<ΦSgN-7m׭رxF݁$[q]5hҪMkۺlvUjcӏt3I#q6]FOz5XBv᜚$ T`>װ8CsZtԍ[RK#etEۿ*~\uMlf0b͞};_xZ۷1|Қ QVul׆s…3W#޽&ς6~濳]:q?kײ~:alcrf,^ZO]}d~G`!EH? 'PI?nl:w4mFAX:cN͍AͣשZ|'Z4plGwmD2!fÙPPeiUwRs&֯-__qض%4MAYM6ob~mzOxU|Z"B!7U~٪jv7W:Zʛ_Y@ۆ6.Alə99:71tլޠo7b[ܫLJk&ۆöp~kN~}>_g}~_{QqϏ`OSn$9^aK5㞵+X'[3=/)~[Ș]^q4/Gd'~t?Wt-dМ]ge z%SkFZrB뇷/ڳzN4{4ŽGɔ)Q64JJPeY1ó{wS@ .x@c-'{e,Tqs#?; S{v9%[mkVv `ٱX׵{+&WV]zW#[qIնekmWܹܳV Xun]b ˝rSGwH~+Hnйt`~9>E]u\letc+zPz;MDR1iBg׎-(W22pXEච M3eW*;|Ѣar ϓ;4㋭A}ݙ1-sʚ\N8ں C53syc꣌ ; :o_ݯнb.{e,Tqs#?yK)/L hۨC6Cf$h/_Б}e“G4[rf,3wh_~[ǾF?r+K6Q\)[QUuX-!1>A 7*PnS<㠀C{|-J"U?6 7œ5Ǣ/HqAԮ TO[,6u4>eYIiG;;V߽e=nuH<Y(N~4wy~%&$}th>2c&pw%ߎجj'm}"F];]*ucw~l5o޲k`p4wf-~'Gƪ^Q ^1PwK]$ܪnGֱ(V#p.ߏK6`P.mխR\`6K.9t7lHyK>th+w]utڵCƽj ֥{ݽNl}xzs ۻ\4Vޟ4+sp~UB-EVo\GeWyw[w.d*35aF8+(~ '8M}xcuGhYC.LdtJ5O5uOu&ٰߤw[/z~aaJTmԲSߍǎ *<޿kMGed@bQW^0C}g8)4U_N6>ZF{{?l =[Юom;T(5 ^k֫ߨQJ-FZ[o F|Ek1xVgNCA6nYz)#Ll&lBA_& 9HYb>hY?u=hp I͸yY_Q;hPd~2f˺u֯(@:P[9)i7oҤqv-:0wyM[ڲ'p;s7ϠmMYle&m^6L-ַȥI\#M = EPd,@䀩ƮuA WebU.9%nac/J E>|-m&' XH 2ƕ==kN3+DwxJ I&K,mDj҅y0sVeGׄTy/]QO׀_yӵ_oWpq՟3-GbJ@ݍ'7'Xq;>%9N81Aۚb 1ʰ )L*@&q/rj:;ct9'۠"[ۥA(g#twCI?p^ݬCNU-\l+k5YQёk2l3$>G,KG¶ZPq,ْCgZssjNꝲ 1 \wʶhCEa!aoosjw@}ߓD"[I _`[O7րmSօ[.T8n9}`V|,=G/9rpXG;ÂW˅;&4 pz!%TF$OSC@[cFzOngAњࠞ[[7WжmfVLn싎>eظ iΌ+p *ne D2(5!@&[Ms%tWzS 7EMnMݶlg'4ÍR`AbquiŒ=%% \>6=ȩ9@fGO&@kpF_[oUqOsC3nH (\;P~'8e,h[@-V2}^?yh&keFl켴Xc)&q2Z\ cfxlK' lu{,N^Z!eW;kYWi1 e<q@8EV *Q/?!ګhUpDxmd3N/Od lɛ]a"؏N3OR`f-b%P*5&4`P Jsa?O)[o p1 vDݦ[8 % ε\o@F2_?Moô1ut\=6 _eꦿq]tn~_1<[;@нƒ#jFrFn&.6ϟUS7o߹_ *8lsq-κ`-d^yrn{s4n;V"dDR\=J wˠ I`>6g(n#)g܊/_ݧ=&TʿP`9oRϢesڤXlMv~rZIBe׃p qq+L?@~ʵ0pmUؖsq[eX""k򯅒$.`ndFb녢PzUy'6sy - |Jٛ=)ս§zHf5t_ [giɷؑ 2ye?n 9zԢ4nh:^kC9977wV0NGF>p$ Vw#c%-mSCul%jq*I G; ,kk^:qu_Q{ɀ!-+OHԉnAoM{$ssGnu}]r r^t$|kݮ%>\/5d;nDAr>yFզIݸQV BF,tW;Uq{(AU&nL׆!>LlG~#3<: N #6tx&S1׊PV"ۭeihXDIEț@~TX^Qek.HXyJÓSI4Mpi@|n&X%^N --FHQ2q=Egfh3rDDǥg=˿Yi'o,_> O!&S/3K`Y;/?|$ ?{-{e+I{Z`zRySt3nt(J um,l^俸~N^G$vۘ msӗ78I P+i U:?bdQ7ܓkk +Fm&y1"%n(ǩPz<+FȰ5b,葔S8nuV 7H P\p\QObEL%p[c9 ;l;ihz#=$%npҦ#sn@è–'O,4%=劉ut#!yVf-^)wI-%nI2v#S_)z ~03\I ;L<']@]ZG27H `5{ˤ~' VA/$*uGOcJ")N5UpKˑ1>ڑ(k\ʧ 9jKYpg\I6fA% A>?|+|bX`PTI!d:p8=7m[~2 7E`0M_ТY9n7IÄmxCe2msn5ƶ>j 1{qC'ji #sc\X df>"WDo?^V͸9aR6y ȥ G!W f+aGqj}z菋E2XthRb&ݟS +X^ (DMV ngak;^Sˣ-J=nNQ ݆7ßO?\2݋ a0q<9G*gO*DR@*.mP@ ͣAh'2Zc_mŨvX6NAַP~D*-ޜ,Im⛎*2@O=/빱@gQfۘc}<<hߏ\:*"_8!19!woT^rt1!))mGs58Jj4V'j}0-_>v\qQPe>ĆEp[sc(BAH G_1]ЂzFh5%=½1gQJ_ؚ,cKՙwo8uWfվ}vrli=>9ޛy6}Om>0nGs~=I`-<6"-(oamiy w۰Ce'ڭIk[Q۞}Rc 1tpgk/h袽*&%R+Y)#UyJ\XGl"0I1#k"t0f|Tq. WKz\5߿cI/Wh_k'0\~[gAM?[p_Gfv6)tG͘u{c:.U9l.\lsdW.Xv9quvJl;^d>lCO,]~p"Ej*L2%e}%^,A䷻/T au3?ەQضW[dQN/mkjp7׏pϠguT g`sF%ÎVOИzi7HFW\ƩJpWzguW.bj?Щ+uF x}q/}t*慘C6;BZwhxhVd::엗WG(yh'*` w @@"[ojֱ9;nFW u<ڟ}th8]SP_uN~8ͪ_Z$ÿ\[ܸ~r˔e n<{$=4ZU& '8c_$uOI%Y TՏ9g9ػ[{$nK1pؒ'9|}̪҄惆9d̖̀jsٲ2,6M٩2)L #-G>[tanE7t7xݡ3l@Ltz"phxŽ]]<Bڶԩ͗;ܩMRBJrF>o(T?=WKB˺0} ҷ04|hxɵ-AQ(q.k^UrIgb`.fJd <9i'qI7SFgMY/R#.jd\jinC1dwI⍋+#HpQ,BTrS\uUCHFdҥKWhIykE.o֣?M۵A6+ۣrC`qkk\ܽrUk6|' Y6qjSV=Z, ^?M6/jp'nIދN!!K)YnEQ ^Wœ8*6CyJ=^sh6qդ(%^6xdnFAN{~̄ܧ;[J#De8ƀtH8-އ_&@zȌ;Û(E=/ӿ$%Дd~[FN$[Q"%É_cî߻wmwo_8@U:rk5_b ŧųT#seZwjx_ /e&|“o^?m;1uD VݍO5^Quֹ tl:[8Qs (pi3],LP?&dde$|?M̦ ۺB$'к#jޣkrbӿ_ӟ;](,(u c9y36x,ॕiVzrrӁˮ 4*3&kYYI)29d5'W;- _^r&)P>@[^vQwA+39Wd$e O7!dfz_N?C[4_0agW.\k۔4eB_qI x ֧ڤ-UL SnTWBKw;^Qϰn#`qc헵 Daڢ7m%(&4LC@W|OzCwH}yб|>IOVi鼠sl]n[:5[w:)Arqw3b ?=q7$ !ߏ'Wg{%|ҐmNiHN-1jK.&gyA-| o(5b",UZ\_}T=$Uf ҍ67E d2orX[ g+M7׍~[;mNj /?;p78rg(A& g>bg@%n4geAۗ_Tೱ@?!AE2m )`oΈXZ.tT6Z7uF/ion>[bܫaةtL 3Sqڱu`ʬ4ؑF-d쾴xਂ$)Kq{M잣g|N{Iyݧ?[O\I]nr~[ۻvDnJ oXKu犆ZlblgXZ} >N,G.)C^S{4)*IuB%.Hw\%j,dpX71>"Tc$ׂ/AlN8ZA=/AzZdAo@bܴХ Yޤ>[Q#PoƱX(YQMFU\+V -muC,+Hc p@0 %%LWh ˭ơGリx;qLKqf/㼧Ѝ\HѢϙjb]5ܾ]}I'lh 7,83;!FĒA􁌘Mk7*ߜsejc?8Rl8-QB4IG{0\r <:0*Z爋r,߽05݂&L֍nKg.n%=pdw[%ɬak/̬gn_]`Hzuٲ.T0[VTj)>!111V"j/GΣ6)򏒽{P J=s JNcLG#ӹ>l^Ӟy\߱i&iUSNҢ~(隸s' (/$tU~:mk͈zQ 8)hERb͌g+2"Tj ɸzuٴwq혷:__N=Aq\K*cw:fvG}&61d-HeEc(-Gz!xt܄JܗYot Y^ N?Txt\Bκ_DSJ|&#.gԛ kq\.Ǔzo*/jKrCQB=Jy#꾽9#k)쬰6'DO8<'i>TrH?}qx?o?!TE[m±1Vl3"̰F2kM- s|6pB/(k`hH_k&DjO7 A +/vݖsk֕ 0ϔfUلښrЗ Չ/}!clK/KVI#3e_]$(~u@t <0#t$"I%U>(m\o@qs (lTG E:]_0P)G7kQSڔ6JnZeGq :=UԱu5UӎO(4RCG,WKszo6FMMR\GUe>4C|eފ۳hG/`|;Ekܞ>)9zp }dj@ۧ b;P>չqgeVUr`+/?p5RO*3d8GgtQ]MT_*߳3A(J9T4ui7Zd2(HF{񞖬E}x*KLKsq'K43_[2B/:OsQaT>Sf/ %Nm.$R.baU䢎AŸ}媋)wV%~H8\op{J >fC20b$.H{ ۷C6}֧:;CU.´Y0Ly)ڛ-bZBڝv Lh1ė3"$v1ȌC)ξʯDP+U؅Ghyv x+!ZQ|M< waR(~э&WWJA^*Z <,I`!7MNc|]XkZoѯ˂3o ofu-u3ť 6z]ٸQ 5;Ɂ-X2kxe]cb.pqU:o.pBo٤av3kC+e"X MׯUi !) .Ƶhc愭緜o% k]M9tqS?هT=4JGټ.YN WUR<IN *@,w/l((A~R+FUV#T1#NLp=7bУKyx!9ޔz ѩ]^ l6 F+7.LA<(qAzRa8|.qc%/(1]hk4X[Q_m$lՖ~u }c%Fb[ƥKA6zS뉶>R<<(s^Zbd~dȽWd"G/l͗{rWZ{AbM<Mbm;XHYBo9fKR6LIw['ϘA?쇪\c3؇X`vXb8{OW#͒^ONx-DCʝŠ& (6.]˱6 EqCـJ`ضo\_R$#A%ʓiD6C/Ѳr|oƣ<(`:Ɛ^:y?}4_𲔍D$GWR\'}Y-3rGCP%ƶ [T""<`{^9{pџ> i^[ [6k3e6sKmy8孷O)ϧ1+^Iٜ|юaHӨ_A(/;7{" i܅7BQ\V4ϵd0:D'|EhqwiՕu1_}u}P'+׉"3SCs |ߨ!L À$=sB2%~Dc2w 6-;E]~jMA?Jx09}_YT Ċ#]AȕEM1K\J̺SnFJ%[n;Kmg 6_1s{S_@l6J^F]FyrqA]5l ZPcO֋= 5sF a/73sW{A}b~۞ _\{$K4g[ܴĸwђI O܄>R6}QI.\f KYu~nw /2|kHNJڞ~g_d :/j11K›*Q,MICQtºo/Y3/D#9 r2UtPrW9b2s#"+s o+R)g>q]KPoW (Pw_ְ&x%>}wU6Xv!{E 1 EfWAlV4+l)m=?^'π9Yt8* yo$Թ4!ί-{3RnNwۋϬGNA•ܸ}^܅GK[:]yc /Ӷ0T+ :G8(J2 zŇv#e3\y0J71um-?Cҍ/1M870OH<߽S*AG44 =$) zeVF2CUfȆtüOyZȇp%K U ]^lԅ5Ǧ4Y~}f_DkOie=2%HI8C"Q1mZd0\DǸ7 €8jB4Pt=Θ8[bctq/N(QsJRSgj=!a^xo]DKGv!ɐlob/{3ڜ 6/zUX~ނhb\?ɦiGl:~np]|rJl#fh󑮌Jvt%QLNSj\s=Ԓ5{3Oi=]g~5+ 9mA`q XV: 'g<]P'!^+e9gG5S %$^ 30MR."T]JǮaRh+qCaynTNكbh5WZӎCܬ0!GHz!*"IIګJa\C_(]RJLه tЖൿ}| ֖bs ٚPlCPT[ ;5dkb5 yG"I6'DAl꜈1Ӓ)7Tt1%`}kI&6ΉrKw#a%X3o(}^Bx{8E)T mTȤRe GW_U4 ~Cj+wƯAm6N4`|B)qQve'n%~0m!rkD d|~}L{p("9ؘҸ}"}9ja*XxO$>՟d0^eA􋂍(4JpK ܌jė)P.RrM&Ja)S`@GPew 5 $6(4:B;T3Xju$zn6eM.׫aː$#YTB!Br9L${.oZD % 1KY0T/ ,qΫ8:@XNpYX&)H!'V9F{9 ѩ?mSŽ6 m9 xg51;ZrM+<<҅]?R <3ES5O$^@1b/iv!k[m 6S2MೂvWրU F_[*/ꀪIހ<5-mTSfpҩ@1U_յkʳKѵtcN8OH?HxrM3CAjfJp_ְ3k/0N%Q0>J!\Nj\A\2e`˨N_%np~]Iͻsxa5[^je|%~pm)\Gi pB襻Ⱍa> MSN::۰*bJ ^P, nvOt`Hxf_%eHX@ߧ۔T\^w6PmUk# b;e,L(c0J]JWJL.$T]jSDMyPte`ۤQ-P8P=o8|MOßkc]We[VdS{ OK@{YjcKƍ q<5EL%qtE *2\q鲄]R_qgȫ3ͱn*ўK=>W% NBͺbZDS̫J\W`r;cg=zhe{ y~|)b7gmb7g=D;/rȍo7wۄ_ɼؓ7bI*8n;SsqkԎnԹ "綐r+E3Zʸcn[QZ\ub#BJB'O[i=cCjW#u>(` e=[ obiGC Ddb+<*1q&$nsV5xbGޅ֖CP!pU}4^)s(JK)Ϫ1I D/GHx]~'C5;#7S`k4KC.Һ6e@ӏÇ &޺ 7bH6BqcomZ.qby˨, l `fwW0"ܮo0ٕnY,G4 q=K:s>K<[4aO%ͫ(4bp= ,X38O)W@0zx6,{dyKDj+X% 툤ı^[ehB9^[..wCTXHŊ)\`YDh#pKݍEn1~^zKIת ?ZnJMGn.Oϸgz4ayZL9Xdo3+S1 >KАuNYfMsF6 T@{Q212Zڵ?uI>Pto/{e;x3r%)֋./J~ёq.GubԿW)aCymFb7.+0+zjH\hʇ9cDum㶠Z͖rz~yh@{rMRA,W~Nd\nBAИqR\⇌u:&ҀrF q+!#ˆ)I*ZE?w{e>}@eϷo^~#Ŝ?7Σd$.nc^xK- n??FᵒX{aƥx~-REYœL7ۆ330reQ "웇̱a ;zܸl zsH*p+|'K`_*oD.;mMElyr0h' I% .Lnwy$?NܭV %`[\A^ގu([gMr-h c/jL9R0 ǟ'1Y(lqYB]IMQQmcТyVULk^Q3wu/¥#UkOv/jS 1 @aЭS% =nePW"m_XYЇv#SZ^ִ9՛ 4\YWem~oX (Gs٤g-C=> 1t'f[/4qr"|u&e  ݥ蹍~6 :~zD`$ɶ]#TfhȄxläSBB!D#}.?ld—4CBa#K}^lFfO jsKr^%mhp@h]4$K2A1J QPj]jNNz)Y)L>/gW@P`YYQ!IhKc,ct S'g \7|!j!L0pb5?c&\`%E XEt>0nUͬ3 ޏټ͚5j˗sُX#ܣqzc54߯8\$Yˏ=\}!Դ#kI*n8vQX3kSPRwĪŴ> Jc@@b4#m]\ C6u$?n,ISTpqėFbgޯ ͼ/k\G{9^}/_'2p;x=یqt6-}9yţIk{%14:;sW҇LwdY}<ʯ{vWzD2‰ߨo/amWO{"%oVsɾPv֕k(a|( |Oa.vօ q˥Fv˅Mm哂,V At?^8{g~f}K\c,'NG?B*(ŗ5]NSƎ3d d.RUv895nX^Y\5lxI%atփ,jP\@g;=~ qd~\=/EZ½1k\ g(=v+?kG4(<S@n\X_\S] _6 V%qrr/|q~vlv%|9jm!WLoY qo=ٕ!J`fmS8E u%r5!{Zf@LJwu}!޻t9ve(rD>Z n!)V~Xw N<ٛz9stzw/"tTtn,TʳR׎@sPPq`/axa4<^H1Z_؇&9ऌ>+s&a Xo"S?f]+w[qؼi1*>Y1EՄ@Y^> co)@WU4W  S9:ԛܣ~)=ëGPW@ T=uiϞ2?Q <4+)MYv!ymCͩy?WOkb$E!h6Yd06&ҝT̖{QY9qvE3?L8AMG,- TiUc}Y15//r{7uߪm-9v3KRR3[ TӮ9  ΆnrW#1r Q7M1 mV7i?ױo&wуeYtG>>䨁1])Q39QЖB˖p[ъ5ĹڴA*PY{TKeȵ!s]n;ivYo ju.D牏V6w,Hu+L=?.ֹgi5 &( WAUh qWGٛ&E(EA8ppf͘QQNޔFAlfl]Ad&V`;n6?!c Z+ (JIID#̄^|UW?qBR5Okޝ9x(#dڎ{gѦu%TmC6JQ&ycbdr2J"wBB^MhIۤX U띍z}o0:"woޱ{͗i_kP]i6sAc欄NJ I Tσ{ Y^Ku}Bz\cO'"_׾`"ENݽe@s(%--NBb:FnozG.?e-r7W3mwfafgqȱnȸ)ӨS֭PDmʾnK߹Nn%k8-1\Sg2Ət[\Q L5X@+Ӿ cFbv$eNQfM|R'֔a =oUZbA oFr7[쎍! Ji ,dW^4уZ FWls5gdH)c.3IDfQqpA݈aO}_X7޽eT˕NW̵nFp{i1ɱQc5 Leu ΡnJOMzw|;κu{R+Jͬw*^_Bwg1 IlM7g'dw=fT)UVLQ\ȫtvOݍ2PޏT dCWϧrXB\}* '5Fe _ŷ'Xzm02DM E)J@O}ϟܻ{ePE2B,jslmsyKVçMٺ8\.)tӚsh۲(҉%@-,cȈ5@Bך񯬭xP6;Fꐏ8|9Z3ƤKDb d!:is.㩝ѷ~rLI|t1*{S0G7"27ʯ{3rb֟_+rqRb_iy_g/Ɨk>0hL84v잼&jԵ;;gH$>?BnbMڠ. n⟖d_>&_0,~>~`d?G 8Fȉ(rfΎ űrv߈x9]]r X>l,>M8k֤(Ѫr}sl K@qI;-PBVFT67x _3>|MLc7:.xSmߧ34mB9X˥Au:5oܝc&ę٬Ɯj֧CFؖ׵k˾ֶCWwE9dht4&Y[MOJu RXiX>~% aZV Ӡ#1: 1%AfODydXcPg2!2SP a7-fFe\DWƒRx Y[&Y:14$9۷ɡeK޿y˦ߒG2(VczbVo-T| @* x wfGޒ?:ХDVUGhN.{Z߁0ߨ={{eg't?=}B 1(Yxzc7ȧgM 8v3"y]"R -G8quO18*}p#_׾ f ~cm40YئڟQS}k (BAD-v$ƶ "z}92vTSAbX[dx-IhD}UD8 vV6}(4PP] f \VT!Dmz_ksZ~ O\X޸!3s0%ڵw5s 4 jrJIh(܈gZC~|'DnPʼnX7ee3! t2)oѭ I6U3HKPA'Gk/VrQ;[oDSpL,p۷(Gp7"ZݦlVG4$lO2H}^ 4/ ˓\e SK~ (KڟEBfXQ ZN91I4FE`7|SGDeXmـ\KܶfE@ŒJR^ǩc0@2*[Y| ߤP 9PTC"X%TǑ,^oַh Xc=^rcR[ll20{ !Ly#v;3H[59݉5=yQo2fs8z !5+b`"ՌzhJbIT,wsswwQHuF2t9nŊ&y{j$tcg\)w)g2 H'пTMpX#{6"ő6U͙fw/BK}F(Gk@ieeR'*)E#B\SjIKNJPvLH:Yw&ʫoK~٨Ī!cI)WK䳆iE+U6J6C?͏S,8%nwn--(AǺD[BwrU3QGLm6 20UUUtgMbs4)+vFŊUq -ە@ƪ dRmUpzxT@Q(S@eT3m-?o3kȔGj kU5,+z-q]&ߧss Iw.b7P869/_J95<8oUzeW3zRe+F>HDɞ 4mڤ 5GD(vgP1&(^RP^fv*އ>P uӛpcOԓ.'ʈԘܭFW-ExM( "ԊV:Q: Rm4T\ l綝lpSԪFsw3Fyׅ*WYM$*U{_!Cz +k*KŌiCųm$:h{:A/2^\w !5 սK[z0fΆ}WRi%wD+>e,Q,UeR eg{ E`"G&+Czpĭo]@J-Hѡˮ`ܹ0PVb4T܀9MQh#7c3#E=' KvOK\K$}l#7RrzL&~RXs0g>s.3wXX2ҀKQ-Vz &Gb8XBorJrQ葵&61^^6uo QTuT.@C HuHak H5L=&^U"1*̧3^e40-_(󽗬kU, />0$kwb8DFKjU%n/s)Gp,!c 4*dM+$EY6ExC"9P$K)-U<@1,2ӂVðbτPݥSAK>6@*_CW$XMdǒDP[fDI""B I8cT+Ӏ1#l|O&1kL_?^*'gPP` |PBnxPApu#^>HsZ"z vT!lDN]O.R|GwTJ[ r抂$S/+Z'@c[/70"M(C/&R:DvѷT9Ҩ=]^ N%A r2ҖP7WnJ} D]4ВC-vx]-o$E>Q!Pt-zt7v_.Eе]ugc&~j۰D Gx/d/p7C_4͓o]َl=vq BrOOWGC w}hҏ0wVEjM.kע߿5b?'L?pUOUmdk`ˆqQRxAN-Κ `h` J1W{" *_j1lfZUP\ͨy;lYJтP4Et5)6y-Iasdh26̹A05xZMRjVK]e'ʹ,^&s e+ ѻ\e͆~ۋ7.\̯pLcK:AF1%7>`)Sw҇_>:2FBH} ~A{ʉx{KIٔw:6=g֔}x␜[?lԄ,yWU.5MX;1,fkPA^ӿ9D˲]-k #rkQ1‰郛E)_9i @1"?^ s_<9MˑqL=^MZ']Rp ">e6м>;=J@U_tY\@Zߑ F/2{4x0] ]vA޽r~y貾s)1N?8BT:H=qBʫ[:u,rcB8>_YB) XwcI$wt凭;c1+ [>> &|JlyI'  6xM25F|nԸ1 '"gă_L(*,: e[yinwN6klvm~;ۮۅ#O ._fz-s)|kg_Ek:y>pew4*ИAV(mI ˾C]J_\+ /+A'~('v9[@-!ˉ4D@7$*a E^+P2e|vӣ XTi|4 ,8m~!If|Γ \(w.U[z t͘][e7quXeUhcYwt^{4$koMPB9[6,Ăy_VP$"WaimoYL_U~.Mѱ]OKF:3G|N/ފ(bwdD?cr.?]/=(u˫DnL猈soJ=o;"}qD$dW[}I2N7X?jd^_BX9pQ]loSG1qbBB 6!f+lfxxKc %K2Ts$NX(TmҪ]EtHX(*KpQL֔*Kш)B3w.i0> "85r(FjC{Qi쐸E$s@LXW`9C 1:hP`r!8~f,W*Ɠ.|"u'\owDFߴN8:.&l#dPfvDZ[a#~&a[R IYlYk0DH) Y_h\-r(MޏmR~%ytMss ߝTHZ5u wRgtSW6KU쥳8qqLRgӕ'Zl˾:-YXY;m_,&HReD^͵}1b{[O]g +R $+|=9_ ¯#T>y;fQ&)=2Ǫ%6/`!aI$˂QkLeFɤ2:#$|Bp:F)mu(EΨ>hֵ9y>>y/:P[YNC;+:N'?VhD)-2XXfRbP $Khf NDt?PaˇLPڋb:\J}mؖϒ2D^G(MM>/]S2s & N -{_kL}?zVxxǠqcT-\FC]{`nf^٨CktͺI6=k>>wQL !?Q~gUpClAޟ3*##ҵW+˫AA() -j@`0Oˍ@ŸR$%ܪ @Es g(,iO"^&dгK* M)dd)P@Kʓ9YWg⏤Ҡ&:)'5-&Xs-Ju{oԛ9b s7?@ PY#䗒ʻ'G|J9C_Vy88pɒOxN=]|)/3aB=c~-^{Qdi^mƛx+g[^adV\`H5^S df; ^TK6CaW[Ό~3'ןqbKGSz^gH>k_ڻ4mdxǯ!{om0ňظH*Qp98RH j8nH,Ώ8stl $FWj?({K6TRPO> `I}aZJoE^J<8#)Db"0~!6Y扂\ok)WgV,@@mzoNv+b<2}4\F,TCiiHBsS|c)bsAԩd`dۀ7wɣz6;풱u֫Sbْc 7򽹥@p x @YzqAax-~d^e륟2Mw +p:8Npj'I&Ni2L@qQCӂ0pW~6 >^PÊy?} jHyqҮ9HFvq_h?P?v;=3gP9*#,ӓ_@R?aeǚH71m>xtW&q3/>n.婪_U?K .?k.^ !j~N0Nu#EBw\ʤΨFyMI*q4R-iK˖7E&g@bBG!nelRfq0?J*N]x'iMEfZv˟Ҙ\u ƹXw gTϟQ{;ұ->ΕGӼP~n_iÓP)؁r_>''2>rYlU^$2h%Iz?h\h=ь߿{b}8k4%HYmU7r/'v% }0gQ;>ϱUW%9jlk8ɸ2!ZVUG lP"~:u@$i иpT%€$Zg2&otnÂpEJ#hmg|Zl %jHZ452#A^'DdZ<}ӧO?&]S's?ȝ',h@rk6?S/Z|jnjV2)X 1s+\DUmشm>GKGo/3SYN as1F߆ ]K9 !;qǰMys˴se\:rhf9K9zrl,_|JfNlat/*TXQ߄j #AnAM{RD/,߆ِq܁R ?& ̡{Ը} 5aهrpk'QEItZ1µn(wĻwֆ͵sf/>R*hn.: 'A^Us_QQ5%*_|5JwZ(e?C /_ 3vo$%x78ػP__ el64{.9o|y#Gk8{`LQT!1 qBog/4t;y\}ұ#KR/lzJ+$G)j}-"T:P^E8*ۜ\D$ll$T|:X[HYlJGLioxȅ8X~m`WkinMJ? O*]S15 0XEnLYGS8%)%%FļW/P K??ܜN)\ ||~$w6p$K K8$PFE\5wMSS(W됗V!@c}qɯVpbgâmPr+N5SMwۍdOWmK#9Ϟ h̶nx)-/W̏R uƒe*͞> /cLaY(+aƎUyCu7Q 7LвLFzg&3r1 p2gI{" *Wy2uցH;/}(zRH GϾ-/m1e@?~B)9c%c P򚔗mYO  {2r5c)9PG2y$DWhm=粕n+ 9=+ь2VqZOW\ YR=vqJy}䈥d="w''N=zIBdٙ,*DV8.EofG.U7ўX(%{ug9ѐ@ީk gNؙoB'c~c_yyA /շ<"1\MQ^ eV 5h#5DA)@U|߯Ћ4Q t{XH%HoX g+OAV0R^E"=2JA\>3 5A߼7žZ 5UCoNIBng);`cQ1ߟY>M#xEYw ɱ-]{⼺Pv-lY%e 8 6m}ޔۋzՓۧ׌n[Z O6- Cr<*נf <{Ivp΍f޴J nTeP{3s-mwܵ{ϾCG<{}5엫VnXАT95qߗ `6NMNRAN$LFrNvtJb͸JN¿ [9$d k+(~p3m^*NH)܇[`gjC(;pC+s}.Zoypyyc]-cAT(Up\.ّC7֋>gE0eKPeuPT!i\ݦ x~Z]]b%FP{pCFcv:?}cMeĝ  "6qm"sr\[=Z`,c&?n|@E(6(#P/':gs.>by ہ}7a43@Su O+ٌ&97iw i<<:>g@:/-y9˨%.A}Sцq_ .d.v{;q,ӷ8qe*A? 銊Hۤ)F4ݭͷF;I UMWgOjÁܬ9׉8}b BkZN2SvH1su|{Lu Y,@FsWZ^ZR;O_&O]=sڪg?x5gw]S]R9ӊutx&2ѩSˍ2tz~Ǎn-n,o=Z|6Tmŭs=u%E qλm36qy.&}BS5Ǔ==eDeC݀W)9nl߷Hv2yo]ᅱ͆pFBdQdrDBTdfkuW9HH, "矨冖ؽS $@߉IiCU^6|woޮrzfq7)m6{H1>n4x{{(&{e%t)V R@lp kD㑵ߒ=h6'6Hor^x .R9'XvAaؖ$_aEqP&؀V'4,]X{hSf{lbZ5%mȾ \i3˷>e-;D[FwL𙣆 >s*Bn)='*fs}ˠUM 4Ƽ+?x +JUu_Vk6sn9tsX^;!rbAD)g(I~-_h <Ɲ| nHJ}DyK0{o-q0z v#cAx$>#-`"r"[a#ksTHy<]CkȊP@dx/ldٶT{ZK8ىjx"*e,X"[RhQHmT=@:B>n:)\ J|p5w1T Y5i6@v!N8l*B xk ٧ Lf;RigK ~X(V{5P(e[}3U}RKKO(hK"*y'` VUG)ʻk:MʵDbe.Y›5zv 8X&$w7PXZ2?(ALp/<9lDXdw;lߥz /O1ɬY.fODzՙi1-yXsBĤ ≥@'+K5%8_eJ91G9"ņy,'"%D6HKyGgeĵCaɒ?enmd-+[]trbp8i> )BwKU&X4$kBMdC;:50_or~eAX<.Z ITyJea<;` SBRzKhvҪA7 7;B֑IjcYpI !%"#yG\n_^'4q7~8 aHu/[>0R7MDninԳ%/„D{D%#K"!;/&`qһ$"Iy9K:'aU"3n;Yb%^ԛ ɝ۬@Ny. xQP}旔M;=YУWtl݅h MJ1'OF"}g+CŪ;~W%#6@%/CWgx..40y@%Tcݜ-6/֨)@\.:G.@]mr!A>¶ 'x6!c9D{! D;"H2_t_z Y/{fxąs&:bP?{׾OC3zD,P-u%- Uv5m5IOdH[EƸ ]@}1˄A4򔵼dsy<6_w|yh:karsȽfb=53T"%x k7MaeC޷McC1ŕojN niSafLMM=|(/nd_L=Plܒ_xbuߐYơŸeWG;̶YNUOK!A2>Ֆꨈпy"bUߵZ=>JfDŹN!=׽Ŧڿtx^3%7-{Ԟ?_ynЍ6/]$,H5xDfʹHضZ+M),fNeZ}ԍ1pNW`f~[CV{43N;d\v}xJOCe0J@Yesm^j*r2@y|YW¨У"yNz«+~DGGEGQ!ӝuz/lO47k0exT#} Pܓ81"ubn.ג@IiNZ*WX,PRCbkf*%po@Bܳ)`&T+k\Jr]-w^@d__\P}g kX}t$Eja^iQ Is `x3VA놐9Cd:}c51}>cMJX4j L'3<-<ռ2T55"5P7ߜ5?ܼvݻ欿jaqG&$#e2Ǡ7棄 Ǧtv)_6֜H\- Y9s.9QWޓQ(;m&o J>saB?/2<*Kja828v=[ZL՜SBJϸ1 Bd nP̕_Յp?2[Mrxpl[?ZpWBa0xԢSu8ʗ7ha'mbmdmSglX4WTT×-WdoAVfB[%SϞJ_+UTM ! QL;C)ƺZ&&#I2#}wў*lNrv^h/fyY; Ś]VPc ΀Rq#4\[A` &`[.:1ʢ[׆ se\\o^xc]вH,ł2N[0כ+܀3nzC( 뙦o9"Y턕lBBZA.)v'Qq Iܜ/AI<6呲‹v=fٴ'&cd@ߒ_[HYB%dy2YuE2,D@A~1:i @(z <5cSTtQ``~zgkR٧_r\޽·fMF*>[O ykл&nϏ-1?ԕ%3\ĺ G0[7Ƿ9|tf)x溔Z^W %q`= 8)4SDIB1m? ǘH1Xo s}>v0`̀'^koW[*B ץR?y~h\@}ͭ37?G v~:#ѕ " |Y_]|v{P~Ušގ(XISK$}žraS%o\iRJyV ׭Zb͇5+V,eqKL첞:umgRgs^LDK vsmRNX͖{,sm}|;j"HN}3gqpv1{:59ۏtC?P{趶GȔeZ̙S`#Cgt N 3G#yAW7~+%vŀ;0WpgC=:T̓ L k($*眻۟}Vl?Q&)13Je5ye4Y %C/ YYo:G=Rz4Y*`.mj2QT!`^j>!-A$ݵuzRa3Keji)闣PcumՀڄt~WNQ-ԫxQzuL?ԛZhKhASs0z=(shIϗ6`c|:BBd -7 SGFV[Tjnm3@?FV|Z|F߫Y]I-~%h1&цѕ}lLKՏ?+#'Q{UqƖ&ʑIQNݢ(N"EnyZը4|4G!cN}Ԣ#HU98Pk||PyR-L_V-(i-q@V=ơ&$td dI\9< 7_yZU;í hN%/)˝cUJKɋ:O3LҀ ?v=:# J$U*:8Gj#LLHP)?~W7=|u fMKBLdgg%4IH6R -ſa԰"ہ6o:E/d,Odהw7w%ۡҭ5.g0VTܢ_唿Xнcy"Ry;>P(!h?O#|sJB]AJ̖|!jvڼp9'YyN0{6g+w;@sT+ f 6!,#w6g0h;}e\ n|bK C.UBNvcKO $X$ DEJ8d|_y N οRJW h-Og!1>J0da+ Ƃd)@ Gvg'8[VezJybfۘ x@|db4~9K\tyi"̚ƒKak\R$9&xE2n~,}@&5rOioWn u46z5 bړZOMm1uZ8 RR wՙޯ4n}/3ΞgS2d@i 5jhEyXwqs NʵZcn}/_f-n"A&ikvAC.fEE À1NPLu=uja]E=lCЊIe_Z_97ju!ϊ #u*,{,[$O\)GE%0\>C2Bm#ȜLZL: Y  :{{@bsdH1zf^G{/ϐfAbә> cȪWoo/l e(|c"̥>枊#ҤXbF$QOچk mޜ(^@?ۦV@v8-(P.̜(g/v߳FFYk pˇuMn]k1Gz*mKHXg~Sh}e| TPA'E(~Ų{Hz5%hDBRvs(zwkhњ6p'ۃ7ň"Gg-A+D\z%%xwӰ+7 T, M#BGjL2ڜO##.-ٺ<Eɲmbpa`bH|ajK. ϼn*ob9VFA:m]`h!$4+-6!=%̶-gވ̙_2ï0":oO9,%(0Xqd Zߩk˛[*8tί@R;k yoJB(59@YgP t*sq?ag0G,wI'ΤwYZ ,4 Fш̹M9SN*Etr#]i<$0Z A;*Vg>d=pw#Z2י1IkU87.`yay{ uM)'Y::4Z(.)MfJK>7I䒵ڴf0 닱e7uD,) +Ul6\DUeZs٬GJL4Mnz5K@' qèEHOLgYIEϭD{,e/ jCjEPУ|sº x@XXC$IQx[tn8Xl5=*΂7Ugƅ-P6r(CKK23$Sk(sY+ADqL~[*88<0ylK?In{*\~>u!x粒`n;Ie֯:.PuX]-5vCi/F[6U`yj 0fXl,ؔ#]ottC'[?[I˧C s>vxjCObuR5NZT%VQrYt9(0 em>wnqJ"6[5r5v:f& S(v╺z@3h٢9Λ3717LM*C[av: GoB9II]Agaԇ2e(P)-`aJ:?%g^(K*`M(6u+l>'PCPErF A-m1-u8rH陕ξ5.SKftCsGEODDa7.b޺F߿qN0JM(ו4cyr)’cĨK,C<;8nS 't:y ;0^0FYk}! aޖ2J8xw+*=/rb(B򡄄%/X11R!8QsB),quRo7 PUt,u/Ծ5/U5^7xۚq`4)ߺt1ަTښs/Hqf"#Bڂ:T q dN"w]R?q IQ7 9[ɁʢWTT}lޏYQ$3i M1w= |a}{U.Y<Զ'-=$/Xq/^:whngBhÊaۦ4R|p.uZϣCQ] wg=i 44.FHxw 69d5SVYC<ܬ6,\gʵ3[>nסdFTXsPJrgnOFј>Q_UMnEO@IfaqSFO # 8 fRq"Ds;xSڅ -%zfJ&16g+g?\v $iv.5Y5u$- 됖DxqQHckj~lz nM$^dEtcK iIbX,iЙ9TlYy쌔hޡ[¾] 9pzv͊ $Zmݒ稫m@Wᚘ\y`͂}Fr͎ӛqG,7˸4Yz[WWsq{UMo[IF!D=@k!Ee ꏳSB9Ećnzuг\T9譡 ) *qc/ JϑIx[hӊ{ԖeMl;FUʚRH%Z@z,%s cG-2q_f鄎 !Lcq-O _8B5.k{;#+Ll[ !^9{g ZQC.*tqr0ex[ӹǭ}"B:z3PƆ{K`l+qwS;rv<51")CƟɉ2K-50w5f.1_ȚoN?JHԄ4uYpOԢ!Prqc2(po,)a|.>՛oּG |2M](};- U&%'74mвM[`.66^F̅ $ ḅIH,"\Fg.v"xf;4\끦 \ۆ{'FP (L~ɲC(|}47#Kb|;J]X,)TK[L ZZ9˦?͉2z]y!:"X'UcI V,Q5G I5ACqmmmd1q䊇߾S)&&!%:Ӵ1VPQMlZPe=+!1-e+K6[p}iرs XN^]:iޠLl$Yee8< d:^`JۚV:F_4.XvݛF+UiԔW@M׈|Qynϫ:~᧏Q#>ӛ %˦dDÑE\L ~_B4 upkTP~?D~v]bFY&%,SRI جɄ0@04V1v~EQ VW.@8{MS_>UŞJezU@?v[tf8pIνWkG3XD1 W.t٫w^=vlۢa]7 غ6\1$^V%±J5Hf 6Vfa>FD'[Q+q*YAzU`zҊ9`SDH讳}lx|E;Y͋M$/ZTb-_BeECSnT=X87R7QP6P:`AdF} Q9O>Da A#k"j5#.@L;hgӮ5dM=dtŞq; U`erהJ}N`cپX?[ ..d< U\5fc^Iw!y*dxzCtƞ)4Mչ̥u;>0m[7oRMV46$}ܒepRSxymɵ;'ͫ>^PJ)xkח2ioMyWfz.WiP[|sa)1 P˟W&'Mp\~/ 0!+5#'?cG*S%(JU:wj7L@Uh#N ª17jb Ԥy3vmTt _ߠB!t<}^**=[36m LPu0B^.Ō : x6BH{)=JG=:kϰ!@fY+?^GEo֫TϜD5v\p A8p%A0Dfs !8@^6 ' iȁG^N\*} g~Dͭ^SL^^>/R>f?w3Yʌ&C~J ڟmwZ!C"?1) `M|ݛĜ鑏;//eCphdJ2yʭ^kbgP(5BZXܹ|O.Ҵ],R3=&mr׌16M QAkCa/CԔWK܀:iT|Q$ٯ! nq3Qs6EUD„ ҿՒC"~fcMKmPm݋Z& wX{taKJ͕mY䤐0"-=Ox>!_{݇0F#_ĢgK_ҥȉ=giuoKk7lذ#6Xͷ}[QL͠EhGaZ>3S W;YxClZ.rb#nS֓^y=9k&9Z~jҥt߶/ :аR.*%:eFNɖs.5~Vvo~oDZIOY3c ҮSKrڵq^GHJ*! ֣deW?d|upy g>_^.q#; coYP*)j힭f:yђ#73WG}X*Ճac#!qQ A2!myCb5?k0_--}^+=Gm*/[߼x)m[Xf ψmxǏov$!K,\Фt_ D[4/y7c{N6 k"n(^>c…u˗]^$W/Ggt؃{ݿ?zxν=~|'/4fMg*knMԋW)>~#Xj|t"wנ8諒2oT9xXi!&"٧TZo<}NG17*"Zi}C_R%CעqB$#f_`2U5nRZjwm'n:pF7 : q>C# b8 [fVmlY>}= =sPN6ة?߽{'cAUQh( MpPf7^QmSLK呙@DRp0Sw0ݻe# =*U\BT[FՁqkna=~z64Gyvm(U*dJ)XD ҐR!}=A׏nnG;g„;ay=qަ{7h"nwFM*N/TV"0}n5`ڎmܰ2i , )eܰlDE aQGeW-O?4umޒ..gcxd^+Vb>rٯq}=L 8ȸlETsЪZR,b?sWѷ@2eIG|ZMɎE12mؤ>>Jh:#;2V=IWb@=GdS*1:غW 07c١S݂OW]6+Ӧz_uhm֎&cgsM<5դr);|j5QrP)c1sL,'2!([^~Ơ1q2V?19 R\3ӳR@-\1IRhˀKlX $oK&o7!flzT\"ǿ/4]s܁/_><>a>/zӔaȜWELlr$J|=+>5 Ԅǽp޽(EҙCVHט{?o,n(+!|jQVG`E:\`B~M32@uh}WL̉;Uh]NByA' )[7lM$&fyԞs>*5MۼBNVqtTR~h mEb 1sMz{^R_[j58@ ܆)r])sN<`[p ?mӡQnÇ!17h 8T Y^4 NkfHJ?:InRnlUjdIKj=#> /tV_E2QIi,NF %Q  @g+B';ѨEI.eLu@|4x)u/J[x[g$lW`Ϣ-{!A"e\U+yFHS7 BIn/t2NH0Z * Ȓ~y%X=\'WCAH vzş]J0>?)rU9U]J0(Rؑ E1n%x/v K5P瀨 Vo:W--;UF1: ǷwWjF.vB.}}c?9rP[nENgw4.r'dbSD6zR_ۋAb5Y1X ߭e]rԭ8lކ\pcI8+ϮfP/~K"41] u²)0L y|9犝 PS,7]D9%a1P![]@-O9JwpWs4 2>?LAk -FSMU:dž8$1ڮ)mU&S䲐LWpc- }ѣg>}8(NJX2p@j|B}zцF.iXXeJyznRtseGux|>I 1fbm(3 vf|3K* j5vjnFc#"qO$A;pH*'$TEvT!1(sJ[|/\(pS /A?>4=|f"1imɝC@@mwruC.eEbډm@1u+L^ 辝+8  q)3뀃sR7?~+>?s!зBUG7pftDȵ#9sK( D󹇬'dMN $$)+] ߛxɐL8Ýao ڠSn)r 跿[%,_dX¥کkcUy2-xM?)U+u=@k5֋R1OqE*9FΪ/K/ z_R86IUJ, nj{Js'. ߧe[nτ.y( :?-OSj!FТ5) ~?(LMȤbL.@hGjJ]+9-.LQ*?sZ"ʩ G)Hl0L6ǿ%ITATqI'PىH,?(JΕ Zp NAy{I)Zo;ў?!(l+@ޱ(I˴ me&e_HEqH-5嶅FQhNSkenre =18gu}6B3~=3`os6nn;HPeps/9ɹzAyS !cJ 'wh*Ddi[ XUDJ~$+3 w|Af \$MHq2 9r-{'611#?A4n ]g?Xde(pPZ%)1~  %= YGhg|<Ν03Mh"1@;JI؊5U\+&!A<;ɑ^<#׭WGU-.2 n9ԯpcf+ v꩒򉩏l1$nq$DV~.[^UM'4=pS0qV|F61dGR(Gg"<k]ƥ,y&.#Ȥ|U5|S,VN5dj*aj([Cq wPZ#)& jz3>8$s}ZweUk<\En#C[Iå~</BƲ92Ǩ|dǼ{իwqQ0b+[27PBgRI~1tPWѴUmDK={TCiMLׅwƴi\͋wuܺ-[6-\:9lnL)2<͛wH޼+?`R;2= hHw.4^p-Ny{׸Tepl ',]XX> YfUV#)}'ϫW["Ϲ{R5 M+qù;7ɣݠrg2(Gs+*"F ʸ 6בvvRީb(*mL* )ȏn+׭ظ^kX 蛿a˶l]=뻖%8KQiJ4σ:7.orS㑂}> tb^jRw\{}wt+O{pamw2nj41,mdWoؘحB Jy!G 6@&ѭ\P/c@O1cNJ\}F/ۡ]y5*[狯+Ui܈_WPIXL:kΜ ުF`wtuvyP;  ={t~*n p!V2` >0lv~A  ,VsaB%w_0^mkSsω[Cj#q/ƒJ+[dwfHN /{}9C*h;zkګ(:U3+46J^fyB '.>:OLtԷO.s}ѱ3:d̄aΧ[[ĥIqf:c / NӪŔ:Z=|+8w0{HMu~q8:KOscͽӚ"oYj?+uԿqw<=ԩVp9#+BޖBZ`x9֪!׉y+{L.w'igf0tкN B/oNz`C;pے"ͧxL;YMiWdeWt?Dtߩ~aGAi(1h¨j6R1++/ԆŨ[mi1'dI8ӂaOỴu=lé5ǐX`3pR3wMw}^IO~Cx;] na/ּ ~v#]DVIƱ#),M0d1H&E&vjwUX3 ǬvqJaϴe%cxl/~1{Ƕ|PlƪE',OMW)/?<ٱ>!eǦ^7j̱kfaWg_6ڱ_I@?>9NP|,hҘ3Gq<)ξ{5*;4ۧ{Capݶr~ G!'hC18UWq}lR_򟈫 k%Av |C[ۉ.S~ѱȘBՈ}݌Й)scu$a]I5fƇhXZ[/_361ۛL |cP&wi2%6*ȍw5+b2<5l"I=t :_Y"pyU0??mtBDqsn.^ke-DӨ0MIQJ>Hǝ,#OM: $2erW^;Ak?DZ#^J2~t:I }`҄ο?H_nLXʚbSiUZòa7͚;BMc5k.墺9sӯ<~FxI$8J+ZSMdq^˝ےՔT=.\jffIBwf321G'g +UQ%&tB/B)v]+K`EDT-e[MwPչ%'Lu50h ]$LG<Hݗ0+VXJV;qhɦw+G3n^pU5{kZ_/5"x9V3{ʮM,F{$xD82:Si\{sI) _^>|xmFv7zj^e>DVzezƭ0lS&*U8Tア:MUƜG%s7{.]2qWF74ԝdK f5E3zFyT~P2綸_d&FyYٜk#N%39:Zn.AC'RLƵRW+еi׶M|7eobuTQ^.?ܓEz{z`Ľ @V3F7X=Q14w&U,l5~xŅYH0;Ncnonwv[ HUH # B 1nj 7{p3}'<I{J"-Ŷ9^0*rۍU5Åjc~|$Gݠ6ߢ&s%ߟz9lݥo]T05I ]#IZ2Rw %ҩ!r-"YPfHM(R#`ҤH{ Z !μ@T_[ɤE*z Ȧ'EF`g/FL2_GTTskBO-TxAl^pjH_r)FD.w%d‘m FtjPt`k9ĥ!5:Ry[Mǭ m.[O j~ټة# o20 թψ O_g 0(h`5ɳv3M8v]B+趞B̽湻J/ƅRn"aa2x}9wkgاNSݍn^qstǡD =*Gz`o0?,ydMAM[fwTq7@@dW4C! &C*/0dEiAlrؑv6S8`ӎј |^-̸smTU|\jA.&Z w{"-t]$ej-`7 TLo;8>v&,==eR&S4ZhH2qS>^`V9,|CV:C/KlUt6__BkDx|uR=/Op #nx=2|}0:sFygPIN6!OFC'!nÍg_@)IΪ~+Gl .kUH|/=Wn d.L <;}EnO;!S[ <{E QcMm*j?f#{8XqՈ Nsj6 dQ*a2j Jl PzŤګ%m9C>sqLA; ͕pg;HE;)3}y{wQdmt, <50Ώ<$`Bt朰!K( C#UEM\tL@u2B,ݸ3f}!bAe21jt6K)+9NJp,&QQ8Me9e,Vʖs5hljfoncX` ^5 ^y7>hkη!eKҙw7]gP֘PWÿۡyDx&+irMD,?8d졃hR4p7 U֗m'/8ԅZ?xPUe%#6XT6t"(,F-j55ONTM jtyGߠ>O۳̅+γ[ /tuF %|J=p eHjq;c22aD݆LYE";5>E*kPBu)wo3_T#[Zǃi+`1OKg`KHkYjqs?Ӷ)-ʚ ѭ.)iğqpllGN{b^ V;N`ѝv:W1񌞔؄ڤ<"duVJl]{]tc(yJTBgrvQxj;R.g 4h b -c4j1U?g5'^uީAn,h6օ6L7ݑJ,˝Ur.r }Yҍ9e|n;kfQq5NZ]G?Z7;J5SKO>LpU@q\']ܡ:&",x4L'_| v"<"I4W%}!JTvuZ}꽁JncUS@+K+uI"jhf-ź&rx.܍=غUIU224[247e^u|#D*n$P h-9=/IW˷.of@7' )3Vpڵw 6&9vM$1˽R;abɾدԊTїIyMpۚ#`9;z,vI턩z8^p>Q{*WEঈ~ /1}7F[ j}JLQ1|L7l#!r_3S*k 9HRh|mq^̓?99/G2DP3i 8djAV{|.s Cgt j˱B{mʱ7;[kRg.9_uLyň/`bz1_@1[XffZӛ-[ϻжmlmzvݶ3~5xxސ>̀6rЄ4^eX*;|(8:1^4k8BtkH-I7?X#LmНg^@}JeX<_xgك"zp=L?~z/4Қ9k"/#Syu/>LРfD\95Ya$kzKcJpz΃QG JS;\T"~0Pg85]eYRs}8e-Tm,{)=N7`U=k3z)B]6%GGy1g vŸg,6 gZ~&Uej͢v.=#HIf'뮭"8c+$$ eeqt5}|c뺗;\qP*g(8.9qz(vY'TbFF])9 k b$n,X1lmi=]/E1r``7!r.7̰EZ;s*]G`/N"b^ysZyH 0RDEj(Õ ݂.h>OoWR3.Z ƀ )?\M H֒T{_}4m=ʎ ә<"4` D/+=RczZk١01blWO1}z8{S]_.$$x̜wK DX\ǵ1E]% S8-F dԫ!hѥM!T$BJŨBv7eplx2!ysX/bc @.\FNhy+K_5aS5"x1s#"_Js'iH` .d TU)%-z2V?x#NQ˫.T>Tʻ6mi#X/}>|K;te6Cpf0?jJ3dyuec'<7 x$2$d_OEVl/ @[Ae)%bQW|"ݬ2\@#  ^kl^⡾`3O38,Z,!:U|T;kQ* $s.JCal@HqYV~rC=y kMT | fE_ ,;]XAekMF= 1{-fl}5Zf\_ۮ8yh3;=T\^B on8p3)T_lfWb# f.*G 5* _'l:XŵJɋ*2@gM|XM C'/yDAw;*᫳&:O_ Q̷%9KR^ Bя;g6D7U`^ZL \[r:_RX|DX \P`K6z|Zyc%* @8vC=5Iq>p >Q!ULKBʿw_Jj5:)q14y?H2GME5`&~*79'Ƌ=ZsVPt6m ?dtrVEjX׮DTS3LׂtY%n]*)M>'cp<Ћ~B< @ zFuG=N$+[!u-C[_eʷu|."hvjn7 I^YǣO~Ce/IN^CI9<'fR߼yTg`9I9 5SDߙo"!p|+`UoFbN{7Wط5']8ގ:#c#v.aOJy-ӧĽl]V#HS%nN+Pf|,9-EXُ3:/tpph(_T7Jp5 f:Fӡj|ΫTx77Α.nNS)̲Vj&*!PKD05l/XRF̛EG. >%bBϚ~t.Ueg϶X(}ADXfG9,I"\Z/dFt`]%..zn|ǿiob~* kƔGn"%2l^6QJ#AJ=i̤!5Tu 6NΒ0N2>w>JpeeL3[gx ?59!=06DDq/:jяЊ֣wՅ@ty^Ry͵e4j9@_)ΒXE0]N]dpI岪#d|)i h29@`pfV{ygW\ZtӾf3n53a=Cxfg(̑emVLacuhtec(8YÜ'j<,a2F {I"J:aFYhh jp{1.R$V.)əmb(XVgGWz8ؓuta̭k塖otܥʌorNu )qoWQ67Y?TqQ_y.W%3O~x0,oqt3PBsw-6wErGa"3G;Za_xa#'?qb AF I-"t1IϔW+&-꿒qɻ3`-0gq-Ij0BV1Fèb& 0vF1 TьÖ838 鿝 سU+*Ћ覮x4, 1[aa !z- ŖNA3a])]٘fח?_(~6ږP_޷u[8RO,VJ#/Z yњvqVLhm(Q|bM~C &R67 J⊙1BzCadsٶ0**!?`{076[鸵zC_b @gHYCYd1 QAn>>MgWFWv I:i︳j\:`[z\m{؉Ћ/dA;gߗl6 bՎS&!M-zvZKUDҴ.$ˀJ^X8 nHr+ބ91z2 Rr"yApy%hࠀ\J%xNP}__+rxnfg.hlSSd\܁ n2Ӓe`^:ස, u{,|܄{Zc7y1(2r˻0"yW'u G|ll'(sh~lcqC~dڷ)Te"PrŃ 1g"\>yp(5_?Y.递"~(- +$ipO7epH`V8ܶsv'}xWNg0 xuE&P\NG<+9O=Վ+௏=XkB2{n}D]їkD& n(@{gw` q-܏:J/t,?Dtp'6UIGΥUj~װ{#E!hڭsZ)yԉȨ1Q%Ai>30F Y(:=_ i-pugOQ_|IYE,[I)$Q5 E(JuWfE9HQ \l8R_9NѰ7"FJÍ-_2HNcDq$x '#rvJ{\Fȏޢf[ST6қpT:P<2*ԛs+k$}p]I{W"֍+d  b7.[!&€4Xl:BC~j{p^jeN9Qz(Yさȫ)Wط^}~Id?tť;HD۶9=Pg= ! fgw)Vh%|8#/_ n]gGB;?~X5,P .SИ7ECh ջP;Vi9 jI^.z4"  zYXch&ezL-&V}xR9406?H˚p(eXRկȎ邪VHg>Uïf-3'ܞPQ=*@/ p`S^*j{wN#/kYsg7j*@HPQv%p_5v/p赉8!ElGuu: 3ZZ]ˎG#o_LEݤD^' m#T u5H 2|;nx44=|.;tIN<4k#9/xx"=*Zw]g\w u{_!2nطa2 L3U `*r/b/-Q8f"a=^2 w}۬*ymR"G*ݩd]Tt 1s{ػc};2I\<`U)g-t۔z_Q1th5db9%wQZ USO }gX^Z"O< [.LL_m!ܐ2LwmTm־".Yc֛OOiIẚ DE ,Wus@Qc:u>| ؏ylDCI$+e j|FǍe#&ּ$p2)Qq Y.)N殠YT-7ދ$Q'f ?\f1.YOU?A |y:_ǍXN|e+FGǔ5CRbYێ|[ʓ?O(|]4ٯħ_οe "%rݛۨHd-,2% hvͤfsLc%Oru ΖԜԮjV{a:ş1f9ż]~j1^iى~}*Hf9U[\aN],;$$VCέH-XZeUjK ±<_SOM>WOcBԌkTsz,ѲXycXbΤ\3a6haB!9ĹL0h=V"_Ma:5׉c_y}Hr5*ŴgXU.iH%(n#4/_>_`Fռy~=PqSWֳGT%݊Yvp?yP]޷nW \h ߪP}e#W^a8&{*pZ;uµ7]8{~ʴЂO@Ӯ&_ico޼~á[V 5K<I2)=}1Af4M L著CBͻwo^O?__ojq`矄?c,,в -5Waĸn޻0 z㠨Տ.u'ԯ;dQ7.|t2;0lӖA|QO"JK%\P'2N]Ab焧Sl/Xnn?|kl34yՖ9e%aQfakxRذ4dei`Xfӡzvo@{{$0_^7ѹ5\"W5 *RGy *% x2?IEni(KfM}#5nZ5Grnc:"ŝlş_.NE#UvͲ.6d@G?yO=]1Fx4ZVv;m{uwә S]Дpc;ȃ%aM?* P59-)--CmdӓЌڟxQ;U@X]H0!?+iKxA^nɱ?{"-^lh@^ẅtlKk^ x:TYShO s0N,>)w}az0[nB\5o1T9θ(cI_̺ۅQޖg5jYKʂX 7mY煱!:Aytl .1U>fkW_)\}Y_^~V S(WW#4fɻ|&j4K/N8L} uw]ݮ2X|vӽouk!~iJʲ^v]ݼۊ:af1`/=8`dZY_p"Dت~V̘`! )(4>M}CJګE@KЕbP8 ɉg)]7|y 2I뽢֤y׾[f瘍ZBkz`ʇzt<[H:1J}?(KuIU7u, kX(I葎[W'_vO{8I$+ /;x|rnMg61%I$lmIA7^Htݑczm\^rZua3\6%bۍi£_1r⪝CZ,?\u4ǩۡyQB`đ͘%8/wV%2b3i-R&y Re*uӯJ׃? O!qR_Q…XaQt{2ӎORR:ܚmGsju)6Fg16.nOmiPUW Yx{f;,e&4eYчEϡ4%.""&$Sr{~} 7l t~4emUc(\)-8m!dW_eJFd !#5Ie΄Ǧ]L2zl)0$xŎD]6)#1o0WCF;g2v,)o)k`B:0 HGH i#$lِƵn\{?m!"H_H21/+TX[Zn~Ġ_~#i_jb_D ^.v`'ǂKsfag-KU-P}R3#0|@ J4Vdv%w!Fpp輙R26_< iO6 umR34MuEz?;簒FP_{]JA1mQ%a $'p_қCWLգMOV rWL²S وH^emIL! [m%޺MlveW"jGۄٜDxW$E'xKj"Y0L %bDQf)+ ^突,PEu  0&sKɗm"^V& f2( D=J.a$hUCا=zrr%;9yrU؎tf)Rs#:z4`Gԏr]2$*7AIy*_33z۞'VtIk"-F6;#OtfKq6\2bS]RM0!87s\Y$tw$v˯͹,J0c y JAk}VY9*pR Px4ii[ePVr0Kpp& ፆ:޼֎nqOҴ=?d5 +(~,CCp-|/ jWQ6B-+ -nKyxBF, l{;^!_eĿ; #]jaV|@enN '&lo`o\N{16=(.d[5;,Q525U1T ҅'nTBwAj %k5.4תfU3ծ7A곛 AGr?cxx%ay7ΌL{cfXG#}TK,Y\Xi~2PQT'˴V>N!\b9SA$_m5RJկ6 tDA u)Z9ڥj?6E'O͔lK뭩*E&Us]% nW2ꥂ6?%K4:uU2E:E6{"I$b`L!u{xێ4eG^ g+lCW_TѴ8,Bd *!l|\FxDL;IN×W^RzYIS+h8̓Dmqk.n:k!Ƶz:`{maoD$1 |rF?ywjDT QvƜaSJ"UjNnM*pA I(52k\aXf봚~RQ CBrGrJ|#XK^:_A(jA<{Qi[b| &p>7dk-Ycq }[ TiO#d]'."J:U"OJWD) _q`e+Vf%lkp(;cӌߛo}(MlJ f`ӓLOe\( 낆QEe2U0C̮AYMc;٢xGBn̙mjj"YrӖ`Qv#w"⊻zteGJ`,?,$n#;R2q٦bVִ}0 O#1V*+ӕzZx)yWbcEU̒:;Zڛ3 VXUuw1Pi, hzde80 =9\ǮD.su1֠'vbwK诨1USyԧ2O-t-"CRu6'5 }t{*3nȐVnM}P6uaj0F,H~@QRU":15ҝ3eDR\[,~A ++:LS';ƕ)󐽠(1\s_O-f%P6e*rNPQB L€]3PBW@j?yѶ5""s_i깉'?0΃:kr;XH UeUs"`'@m"ѵde`y dVi+@]ھIfG(ϙڪ~.(bx]+Kj}TIOBc4r8S䵽ghV:l2^C~U5f)eOG<Ҷf3CO w.7$YZ-#) *6dmWRTET1Hix*2Hy` j#?{Gp]J6ܶvQb_}|crGwxHmSƄ5̌`L^aR4z*W&y'+:դ e=RC+$p"G+XeV^h]GaxUQSLu z3O]a/> Tܣ&&dfˢ+bJo2m%%y 60R-0&a(tasw$}dv YAfbC-e"6[ʠG;,/:nv$Zr:^C+Da. QAeS&7/DB̻,:!!U=Fsph~Lnf.YԳr8\9C lSN"c:v*x;)26C7D^; 1$~n v}üBP!(q!ڲrO@7CL4Y!Nj_ Vy}6,讲E3]z9OC9`K黡&L;AQ+>ToIXA&ɓJ}3GZxO" jе1f, xck2FQ[7ߕW2_YR'P4JC]acaI25`Je-8WC3 ɶlRues U >y(lO8'd8\1jO^9B"/$ۃ3149H2 oNQD_VbYFrWE0]_|b^—h9+V#XT֣h=F,y>ԄLB!%Ut sUDX.`P٩'k2#≶w=~UVg@'Lꊏ# ةI:(d5H=UNa[@ƛ6LDcQWBBE nIst_CFȨ;|Iwr-ܛ0Y WCe@ _!h7*(Kf,KoYb&ڶm&5Jk GFެՀq[鑡H{ѽOK{] {dUMNIMxNnr֊kN0% #IRKxz8㯈d6=%u>?GwP wb1`sɫ!pPwUZi!d ̄1Bɤ[\ k7og{GDZyD Z# @%2hacP&NhH(fdq8[ZC٥#6٨y#1DDwdr[FQ.k-o$zK9* iu ҂^!M;Vhmrp:{۪i_^!cR:q/n|9⊀ y]D:€q3Y[;ަ E&A1Jw\ Q˗DeNmuZDV$~?ե$K6H9l>۠_A:6 ׊Mj 85*GV+RE ysOYqZiuU+)3LׯXMsY +av: =7 _QS]Md3 X7W[9* B0}%4*Xd(łIk{1.[\w" _e~U#d{܁hN1Xu[[8NkC')[*we+#Os{7]ƣk0cxV5HK#'/ ?W' 7>E4N Əެ2TCI09-WV6 22]}xeLTu^Q{xZ4Kal*5<.S +[]ǺI ΰ1B)dwu`*AcǐSEC(qPtѣ&+[4\֗jLdH#e ?z '0ͧ䏘ĵUXgH9-y)US֡Yۭ+W~*pp5JhNy:h6̈_ʹ{;\C 2u¹k/yG3-uRL79A,+OOciF 9$B=fGMY~Lim ןˆ,h7HQI(jKNzN`Y촌CQՐG)O~->l"j :RjP#WY{7<$:]/"ʖOJ(zYJRB?TqB$b5moޘ {}jIbn"X6s@>*el(R%ռ#>.hҠpʻ&<Zx!ǟݾ$.0bMZjJL1@0#BD}Je|]7]ęG/pJ$jb;69͒OwiI!9܁)/dj q01g^·R?k%`e^~oWzZzL1Cv Bgz 9jx6Ջ$7y<#o\? t311uUVҦ o?w/ m=4]wfk;|7Uż ōx+ˑ5R6R+ E~1xƕ}Axٓ[4 bw>0x@9-4N f _Ui,wKf$ K5 # lُ..2pԎ^I(ˢfRۭ(u=\7e"L;U j_ze2ݎ>hc 1#6 ?v9$@}uqHh5`q8dRX @|Oq&+ H_}ڱp@w2+ۮ!ԛ? Vk  3 zf8&JRnn;t:/@|̜D猑uzXZ!{_mT.Ƀ6lo*]\X Klrǘdٟo)Iq_^[ԍPmmRddʩB94'8۲7PMAz=u0ḵƌOK̥v.f-wwӯ6익ʛ;APĴ&݊fQV'i̫ v};O/P#jc)e%o +"frVkqeo+{N'@<]uGҮ[S_@'?-6f3|hɀte/7EB\dx;NU*x 0JoyЃ,f@Q "R!6N*PT'}U?cKA0Q2e[ 3(aJ (uVjѣ\=9vרPNBdPP25ʹ-,Z!Y–`N thTZ7Ck*y2M+E^X;3(&`?їaUkފ"@_c v䧛mYeۑEt &Ze6mƶ[SO]~|Ye|%Z7|U-`m2~Q~/Iѝd6ˠu>.`)t-t%6FGpЫn!VEo(w!;Ġ"0@;ٳ2&Fq;T8Z_ p> *.@h-'rL NckA xIhY̧em mrʃ, | :J?^]WsR L\G0r;+o+&$ػuplCҿ˓T/)1`Byޛx Ԗy1CJ0(Qt(GٷkXR:YÈjQ6:Ή?ЕcKۃXjAS$bGD1U 0'Z5ci=Ӑ|݂5s׬8qhD&yյhmX @ԒƳ }BѰuJ0b:%;mh9(N"Ҳ-sfS+&tðꩇpYmEvB=/{3J\wݼo5S+`&Ǵn`*0w}K&ZY$jypA3~}swVÆwH /Dv;'m#Ǣvptj"4M0wGu3H}uq+jPY Oq#3ƼXf*@;AtNb9%#g^Jpؠlu ey\o퓽ZEً m4v(Ssa ny} W:7yAєMBBѼW=0r΅FX|x2mwݖK 47qmy(bK@q|#g`n{{"|p!FwTL xUcx2 lW{(ٗB,*z>ΪG#*rM]gbԿ46W֥|pN|=B~_Z=ZT+Wc֡ 9ի_AV#j/d&((1%]d |jPM_nY-x7I?գ2 >ۻQo ?Z-a-m-FJrД(c]۪Q&C wa7匶OmΞO-.$_u׊+TF&XP퇉pizcͮuvJGӔ@&M۴uk߹[7Bx qFx8ܽjuͰ@??3Lփf.8|A}rIQN.Y>Rs#3rYUՔ,2XzW9q):bUxw!c7Rq|d{\HitZ5~%WbY* Jm E᷇-fQTorrir<Îqy"^$UMRU >k |Utu0&F'%!O+vٱۥ/@.F4^04 f\E1fn*7Z7˥]萭GjEa% XJ>"YR[[ Džjq V5A0oK/&@HKx6nġ+^ -d%RfڗAJ ut7kf :Ůǩ|F3zb( }Лr7OWΩEzWclgaV.˿!E}"{ăs RlF2?eP>F zA/u"ز?~uJ(0'\f:6([C1?Un[`AG @=,\>jCTnܫ q!\̣ݒڭ&j|(6oXP>w[A[Q*\љ#fR!iKE,e R\N "Yk $ 9lZ>`edhБ@gS! !B^\+dR@J1EzL[{/C׼%@ZzŢk`9Ph𣞏0 Wo N2uFm, ,,)*I0PwTCD9_?Rg~ҹ_^JN*:[lr 7k論SA3`iVwE{:)HfTꚱ?d9b*&W(՗9N"`$%jT6\pz&bJ59WI؆Mݰ:JPm" (>;ȑhz%nb9h N cb~\~e` S(s[ " ޲3FL YMWg;7ɢ QCuJ$`v&aW)i٠yͫ#3@}w4X 0m<{b`]r肈YqU(xƮ)襀:hV[ۨ VϘ*QJQPX]a>N`cHligK>[j _J!~Q!mP6ZIv;v |ݺUR!DOyGI^'4wcgv>Ep UL.Z>./_Ǜ?˞03 @-Vq%!BNyPNA`csW! r)(M"~gf0,Q Z׷xz87hh@xG?,B$|,XļC6_Dfl84A_&X?{ y5Qg3Hvq.6+Nf *- _O@cOP36شD=_A]|oZU0;XH%q\ e}c`!cWV1PӉ{s3=%+ӤћFh^4nilhq8f<[ŊCOn}wr-2Pۆ4Blv1h+gK&`$l5 $glZ}IW ]|XMc\,^!91}>le wtNP ]x<'H!#Y@"Wrs T(14dߣ'1H8&>φZSC–lE1j)vV~Ul_R, d{ rgcV[sh+6IW͐:ܮ0^B1s#BoWTnWd\*+ՇJNW)b CTZd`1yY,~Uu*Zy[ RD9$#u:xzYoF`1I#[J"?P){l}ҙ 竱^` T(QD{AuvY_פּΝ",)|lܸ J'9ΖO( ,csmG-i *Nǀ{ %`!$P%zCƏjJk gT6J!^^=R{ :).6kVfԖҊΣē o#D 7pgr%JeuOm"(1ЙMJ06Xl>\Kj "W4WQvD葈?#.Å -i̐eakEOC,,=`e twNTzc˒j A`~xd$׎CС0%įI.8kZEs|VqR7EOW!s9@ .,(75z7WgZk n-Q^%@<V-=7xmysd3KAϮnY:kyG6T!h-C0|8*nHDni=dоns@`*9NO+8Rc/|bItkzmZQc:Y^GL]dڧJwvΒ^L Xwj'"v=7?mIosFPD|4'=J R}ޙϬ(pEɾ|^ zҦfJL.-+έH>UU`a q gw2fh?aъk-_}xͭAOt/Q{:s]ˆ/5K.=uI_Z-T0Cke8Enf,xoLb$QubeêG/ߐ7۷n޸n-%962|2$A]6=䴹|u6eov !`/t$"}(~'@y0|ǁWx*EEB gT(MWCٰոmvӗ\|ЎuKѶqS=zhI-n1JOʀiӧ@b­D~9_d9< ."F[ ]& NJkT˓.byL%8^)^muJ3 VN▯H31pX{Swf͗_T%6@А@pswl̝_uE\kO¸/;Ew>edU5|Pn[7`# i6$Uh ՍP-W0k) 6]O7qØhc]{)J,]X$Wr;c8(VD-T(<1i{vn `8{nnzgu]ebE^2`IP1Z԰6\͊ٱCAl>ߠ}a=@ ?U$p=A`觸_Sb;kqdv'Jfz&@S_ }+y³I \k5h`FE8e?IA (UxͦO` VW/n)Y^^n*vL}j̽sR ;=##Vn:Ԥ] + @gceMk;qWp~o1w"b:OSv8f:1?rb};6V.b&+}Hu.=2BF?V Q̘-"Prt$%| B KV2oo? TќpUko?+pn:C]ο+_[} 4нOjmxL,gy͙2b$am0 ˻RYQ^Z<1mZ-~ .n±c\25k8r^yQ.QAI:Ӱ1J- ,8$S(ڭ~sE2#K .vDgs~e]'Foe*†W ϒ`)|SN1wjd NS3%Ĩ9y塤Rm&Ӷw%Np8g\LdhOgLACY5r6Pooƴز"CҀTB;ᓘ`•q W5:y5t41;>*׭P2I5և*f".ver " xQrYLhUY'aAP82~A#妀,e rZ ÑP9quݏS$n(w&!; Ǐ+,E:aA9j0ѣg!0[jmsA "Ŷ".Q1!vSUӌfRgH¯ٯ͛+NK]Z|kU19~ARw.=hݻOٲ5qKڶɪgcf>kMiCl4Gjӫj!Ї~J:˒A53crqZc㵗0U)5r):JZLzi2, 7&WMs~S{(G⣗ewo[fZxZ6k>=\9oC?!Ӓ# Ӓpoɥ>Ij`mZBG5RK}8$黜[Qs&f]g9&eRcݚؒ Oӧ#cA%:UH ?R[|슆UZf-W7ɐ-U1;2kvgO=}n&bQ1s{Ia!k2H*BjxAZǿq__Ȼ9&XU@ie%ucA^i+EUƠ ~@gIjG-BuU;K]d xCׁ h6@.Uy}xpOQ5ysyu֠m2T@O3F,qgh\Ū1 U/{t0jӧ>&Ck{ z .Ugm3LbmqoEoAϴdzbJ.ѓ!? '_V;-#' 80JT:Sߧ /tiKjܝEbU|Q+R֝XZԚw`nj$3L.#3(gd{~Jiʽ\`B-{e9A?}ʼni] SoڵFL 񘳅tU H</L]u4z"qeFv:sjN=hJ{{!;aS20eINDLb^Acp1AM_I_/q+} }~rtn%]K_,HCc>6( A gd>2m@(pڵ'9q "nzhCKh' i& r4=֝^*ںa׶n|c+zql.i s@~}{QXr*rk2t㸝Jf͹;;ӌљLjFA 9 1dԯSG 4%Ғ7,v+ KQEZBJ* NfeC{l ÍX Ru".fa;k҇|1d Hn}UFwKr jWz=I[בOȰXLtM@3Rm Y?ua<7U6)գ_{^spvsZ`zsz $ oH8~'J=KD axjQḤ = ɟܧ,zWbf`XE׋ݧ)q=efS]c0eOXdkFa5|J 3s9\5c7=d-Vqc,64 $3 >řYwe8~a]>v1:uҁ+ȭb^*ƱU)֙M~2ZTbz!-.&L͹mKO'Ru[{S |y0.C]tͨcpqpo5$G)* nqxTIK!52eJҸeG3T8O@ru^#gy:T9Hl_xN#;6pÓߺi;lrޣ1x[_!cg 
BtiIp-ZJ>˺!` }]$)GdG k᩻W1XM QxV %P 78p+T1J$Z;iLDI>Y_tX)TwN65zD0@S9yOiK 񧊜=o~ͱ7 ƳL|+6baӮr swoJiXvU)縡wu rjMh&25}N92I%Goy2T;([BLN /C%Ѵߋ 7V1/w{0KVby6E Ͻ"- ^Y|zKATz\2<b6g4u |˴ԭ2gڢ!t ]r9;GMu\/~$?1Mogr< YyBИ|?.]26ly3iJKZ^7Ѫlwpkj#1r˲.@V[v[(9>41A߲*xd^|;sҝje|ta9_}&M} BT)f3v Gx'!s#%{ie E3S\"M.]|΅Ƿ\']/ٲٸ=[gX3Æ]`S"U_@2hSn6wVF|=Y>5#HzK&ڢT>/NtBJOCMm'ҺXDGmTJC{4U dw3l^=>r&8ZX7CKBd6 7)7=02dq:WZ7BHPrnÚP\|){pH<>ju'n<~o7n}NV"#D"T~#McKI.9ӟ=C#k:{ӕ{q s wjUp"̭3-C(N(("e 1r,dm6 R%ջd/x;>VzwĻ(`[Gտ=^ТiOG7\?H'*^Xx^?k!%!o3Gf-RnntqZQ?ACɇ eltn4/p˝v(W)+DZNS:Of3`MxF5܋śkEglK=ͮGz ȾU4U7jx2b)!2v^(+w *h r}+7xš1rArᙑf +fĞV ݽo6UyIލn1s8y:sz7"0/eѤ5ĥiX_ v]oޗ((E 4ʞā코mfdY-yM6s ɑ –ךWd<{'+йQ˚?)F0g:Ajbmwe}Ѯ^"[s?VG Нz_^P v{ }6F.R^l`Qp KUFz녙K`ٛ8nܷɱ 8=&yHwPKD׳Nt*1M$@W [Vbj9A$ߧ1qj!=wRu9ǭAqftlpU(#/e9 Y/4*$ՙ(jka}}޻H|7r\)dln }u0< &9ɃfJsqɃNr " Kp&"36rKzz#g6v2nK>vs?)A\ۇlRÛr:;iNf%;*Ǡ7~ȟ.7-DOY؞^_ucA/\4ݚ{71 ៓+58[vid }A7 2Gac#Z?rh O$ZKw==bYBvWX1/i`WWUd'}z#w9ԁ^N@]܂̾%r=R2cՆؾʛеKu;Z~ ϩ ePϳ#3uk<Úb;ebiȎ5Le~~1m(xjq-b:2*?tW7ӎ 66 (唸х|AMiu( X/^zU6qĐT1]PF<|qϝ܌Vu/YDymشeێ]O=5^Tuns HGGЬz^ShC40<$>}ų Ϣi[#Aag/V̑7?Fw]Ҟ:tH_ Jd}r@ 75NԂg ?+5f(؇ߧW`4owVu"Dk {i v[ ~ZNzS/ܐqB%̉h7IdTp430O&7;a uWHVC` 9QH^ zZ3.,#|qq|pr_~ѰBHb0Ba譭ٚ2kؚ_6x|ʨXn:Ʃu`QSKI^Lp׭`Tݺ0 og13@,6y(t*i/#)s߮kswEtVA V5يjnXEaBEv%T%Ž%(X5\sq/_z&i՜``h'&ETϫO"]f ua0;saK/X8QP*  nTQ$FQ+G5{O&6Z{[ѳmyÂ=i9P5(>u^zW_z~~ \P,$S⎫2@!,tꅕkoguShwG93W|-{ P y[/r}0y"r+ϊ6}ʯjG^T{9UJqmV6T Ⳉ-ۑu +у\ g!A~%x\[GW@۟X\YܠH[qI rdZ^ߴIm NA# fHjLzpK$3'}uj}iҎԓ*\?]1l>qrHU |*LD\5rv< tpG=JlWKbS$h R9U<?/} e|J\,IL@ (㋠$PFy|@{r;W=)e]7XM_#>丧oD KaK؂ڶw#8Xj-=U!酳 e[r *L? 2L܄ZL- jnQ g*"j98> )F}ՠl!^p5U`}E|q(wh\tp&%]fNDViC|Iv1My6. ˫$ 5bUTFmѩli*{5a"6!.=)c{%`GL@~Vjkɉip݂ =8.fFq`zUo8ql Y:3OyX ea GvspJU׮ߨy?/BlkzF0fL6ݒ%aH,e@$G%QP q)v%T| Е)A.\y߹ۺZi-!n>y`FGwt`*4) 9E1Lcaxn cCjrw辰&y픜n훳_@ &k}>ǃN1G_#g)BǙ?vpo 2~m-)ʴb >WZ-)AdӖsgPr\j.D#RmbKZ"C A??Z>A2``5*:Ǭ`F`[[ r'Ado+~:%&ڂn:'}־铈c ~;!aO_`NA{Q\;QS,^ec'^t; M13>!X34/@7!|f lO A{cB;,d H=FdBD 7EαC!(/'(Ӄ{ZŹ~T{ܭeb^`yM]N]8oZE,Џn a?=S`&Ej ~tK> &{&gDIف?eD54.+_$&\UTn}˟In!Yqlr!+sU \mG65"7PyÄ~eқtg"gIHк}_*ēPGϾG\Ȁ;y[CYw_u=v\c7GÌl ΅R>de^DMv8]9s9liHA*fixWq&ڊ3+Ds͇>/D4k^~#r#&x)JX }]yJV'puԭPŎhܜ R#|vUJ@qDJѴZ yݑFxUҭqbxnfԟ@JzNXaRѯ;;T>b 1>X;ȘJm_("MgӑVI]AU:0J)zD%r$_UC~%sjwo#"qٷ>h-Y|̃HD,3({uS$H ]V~D&xlCtMPׂ֯s)< 2؋)ĜQoxW_r/zGIi4Y3^.I^># SIF7h+30w s ?S + 7b*%=@6[0j1Tc/om-\҇"LEj>ۭ2ԝ@[@[VnO"Zt-5Zl3B;;@EDFMܣ㖊}vWh q:y%os""j\(Hs*Dۛzs4fAO‘8eM$[ˇz%2τ)LȔ!*lx+)PwY25lwyN&3! 3]5wHwѲ5N~ƍmv ^gW%)]WzbH\{n :pNe'5XgBّy15fh><"{"Т(:!jw:L_Fi^\;ulmpi5*Α2eLgb(rB%\_.P~«_ܯ FG 3b=Kn䷒2CG)ݹvxuG;[ 5_",O}\i&AB& @Wa %܂ VRI?[ ˜ę;l%ٴ7Op9{e3}bك7ʳͷwtsB('hMJ%--qx>|%u wHTpVecwG$gE_6Qj5U'Y63y2W#c %W-Z ƘimL|\|cuwH]b28;wpsPXԾXVKBl;3}s(4 8jوM¨A 7x;H]CǢ<[k]KL/-l-I 9 MR֋}@x$ePcCwIqֶl9u7?gd-p%4ЄqCE<*U\}ۖK{肕;g&tҢu5Mo8ү|̆8#Y/n>k(_fv5ɶHP2&XJ$踶OC08 YgciB ɢR ɳ> RyȀv%\0E3Cuk[zv9cGU@$_M36iw EeX(wiUzO"["r-93~/$>=Hq(3ϫ˸Zw'kaw}R|yjqg%/r`9(D(Y뼠}yLo +X+pS< F3"Ze7n\\p*:?%{3y+TNvH;fIp8Usfi35bYJOPj>+`_4-rh'EaD_G&cEt!Fa2"Ǎ:mXX;2J}.ڂ8mC8ż2Ksp33{meV"U0kE=L2_ls$rCK Hf )Q i_2nb *| (Z:X?\U@=6v |uJ D&YIʾg4~F!$ꃥw5wr6O l!-sԅKxIT ?.ɐ˪G6)'Tqq"IΩDS/3۰S9ʷE^\1zfpa5w8|p!S}㪎` .S-IZrɎroMC&4bw(o͔bUu%gŝ\)P=!R,9myx3y՛y)6NaKx^}DgT7 !.Ď l=MsO$n=K ˎ <sTp/5bpWf"P'pWY@F<0-LṲ̌G$&Grrsa:yOT!LozKJKilCw1kdLJ3BwytLCȸ}@w%Xɀ+.8*Ne8cP4ӆhn|åפhOmpߵ[+>c$ ]"k!y_v;9~꘷Y_y6l7mP]8:t[o>S%p0ՌTB~>6|hJZX Uopq+qK'~vbA/^yuϊppȘt=:x'{ -(hodB412Ckr {>a+K(@i*be_3gܗf,#&vWF3]}GrU'"D(wY3]@r༇"d_WB?f>I"5ViWw?٤n*)wEd(̍nu%ۄ^- *ں-?M@tYpCӟz ǦH!Z CpP?ЋB).5?QJb9/O09 ~G7bвWIGG4d6[Q $y*7bu/ )DQӆ.,p([s끃rt;gԱߘx88}5ܛE̓m×ZB z`,IګuQ(ZrP r2GC͋bE\A[9asR^PXnc#C87skqnH".®)<^1cӸ1rѢd?܏+_JT"uDi뵒/nED`Gʓ̌}8ގFt_nؽ}/͇ˍohSEWYi .GPmD*~!Sn|s6{UfWC+1Rku7_~ d$j}:XV8W$3C9/ *{4lϊiW٭mNϏo >u++y~).8Zi2oDteNZ@i@}8-gtML7H/Л'KYx]oLj\qeaW{fIκ5w6fI>a|3S*7הdi ;Y`ڧR{0+8O7Kg;/lEɱD _ տ7n.˃^V? H, TL*f6{@WL::Fbjf ɼk)>rW`t/&IXA%\`j 3a0Y1s{goܶAY>(kn ñuo<&|»y|#pΈ^z]"mR5 Dw&XIUF*>7rQ=fXҥq[ՂӮȟpElja/>|7&c9X7C,Wq)42q Ұ,bqSJ?b?EQdr{kcP&nF'T>f&a*W u_jLw+-LIzEkDZ'3 ||$gJq)A9Ʃ=4|6o_j`KOVfbE蠱͆m N}|#̒G iwi 8hmlh0e蹅WAe7 @}/[`GEvC>xz_-Ϧٟ\m͔[.$FbY} ,/Ԭ?Xg}\yn *~ׄB͕ U"%L;GR.%2TdDJjK/~+<{@Z!2l6V_褬]!nK&`9*[}XN4@N'TVʁrB]G|΃?P9B˓MM_4BCK0Z%7\&lZM*«mN]Fӥm*W.!);uٔǛoG*+=n>.ߐXm9/G~s’t{X6Bk34fQW登ld)=w׏'MXjSd4qSfIYW$拶!U\<{dKc "?B FTqMGK'~ v:P&2Ụ_ֱ*omXC:ck1ۍL1*Q٘/m#$rp׭_NaK|GȞmSոڝL'1s 6=2Ƨ5&JЎOsCK89 e.x`9lhN(U|ޔI=ס9~;eg`N`Ɋki-0] )HtSimwc ><||,kʲw{k&uu[QC}[ (TNp/r&v'JE%y\:].&+p SCaq1Q/8FURsܡV c*AY/׷7 )ӁIo^CLFTgօZH~Ѷx>>TR{[Ъ1 H+҃MqV c%JQno`4֢Č_$&߳NORʗt^;)1Bo^I 2luVF 麡q35G=eoX;c&}\oyeQP7<bH kW۰6R"_嘉Ls ɂ'핋\kj}9Pr}X5> ͼ@"w]) 2m$L<RJջ&A|:W().=ӡv.4؉!//wMoWT'?ر';̂o3GS34/wK L݃.CP³J)wSbW-/V~9[?A޳hkT7Boؒ3+Me'ܮuտ 4V7+қu#k@d~_p(P+BS (GgA|8)C7xs#eo SxӺ6 1nj ;bQu˵]E u [Ĕon67RiMa˷ųMy[Jh\l/8b$E(4^0~7UMzx10YJ~1vKg7.jG14P7v@\Co8:_N?[tCG+4ɿK(Heo?/izQApÕ?I$Di dC'$k7$*A\IWy=NP#[6]~3*xFN/ͼzͮ%Ƭ-J%z]3Hv&uZTjFWe #_"fe b lO&S?M`: XgwVh46PV!癈"xrS:PŕW?K& dx` ~!^^nv*nfiTn2.(t B|7t5W-"+X$PVrQn<2FkSFb.,%8͟t벮 k3L+']FX! `.SK. ;D7\z_f`k9o8;'F(}7!!OQ7nO *eЙc45:NLǕW^_ EGqN)&S/=f:`}n/=ԲrHC@%P5&MeC"e}4ķ,ݻ`!`"3+\ZE( ,~f5pDw Yk)ͦ8DH Ϛ"XHRCP6Y/Us}E~Nֲ^ܖ$*MbR BZ,;_20%,A8~8/T:&Scf&wnc-M1'iGF3z >/,"Yu=W63)PW`kjN5pL_oZו6n܆re]ˌ Mz &ˆePd9%:||jrT. յl2@]IjuLճǜQxDvx/0Co͔$i9krwRU^BzidI p rDsC D<>"]bo ~.´VX2/)Y&$,5O^2}Bzd>:zun;mÆ#Eml אE0a ?ur>_Hs<<(vC@K*r:3w֭tڮPUM%v~j>˓vlQ:qdKڕ/kn1Vt- M&'ptؾrQԩv.ZoAr+j;נ:Dj)u,z71'8g /x:AYM`姼ϥ&Wӓk'hZ=R<-Ԫ^b?ߌkrֵ=4N $IfQMŻ@{Ofk ~oWk01Z(ZR5wo SqO.;27<^D%#Ov'LQ:Pf ة^@aJo? wKAaOY'::=9_A*G<[ JFGZ 5s!Nb9*-}e̤jr\3S` D]kzivRˀe*zB_vL7Di1.y/[)bmGV /feksp iw{.5.UNaKM䐛wad:wl~æC}"AP >U_Ng?Xի8co1h7OvNR.ԗa}b5Wy{l*zFQƄw[Œz/w+ : 0DgT.vԌJX>b_I@U|S= H\U&IPу XwGI%,PzM7jy2璕t ȡ@E؟ٮBiɠ&zpo.c3t ɔj/]l!)>x21:{ը:OΫvcٻs4OqE'pӷBe4*Jl^Olm}zl$" λ纛F ް°5rlDz CWcGڟL/.6,rVUm\d·r a݉.s4y yO\?v¡-$K-4hW^>X-˔qbbyW Db>=d4b /g钀ʀ #S%1>x׃8;&s!"EpVֽG2*܇Xnۄ`k֖+*Z8mʳ7556;{+XqpY`1Ls@٘( 6yeY%S~=fsiBY?_|FL{T=v.4e m+\\8nף^qoGSf }^tdH_~Q`bt?,zYUWBEng{6MGv6( )b%6N hJ`oͪ~Ōmpx-'"L?%'}=Xrvc!~rV^`;ChZ_\4e/!ѥKw R8qE}CI/[C$đ3(9qӮjբg̢k..&؁Obs_0O+Нw-(T?+90 ˎ;v>#+YxJNGgZYiQ|?F<oܹ}haZO>zbaAȶMF_̶2ůWa/Tݤ{Uj "RC /75#3kϛ"Gf5>]FxM ,[ 4:4 ,|3#t9Z45^fmI3Wo`jwEbs4zk]k>"z\~c<差8*ryڷ5XHA禐+Zݩ%uz]LW4?=bv/CIHXf1jaI;2y>;  /y\eq ^6*IӖbّle]۝ fu(V#+g|o~ƭ0f|EP%~J1e^mIUL3Rl.3ԛ2w9c,B4KJl%P&112jfx7q6 ժtٲص bLV+P-6RlV`>mq W^πi8pKB1Ac]q D߼l/VkNn9$;iLX̴9q$|ߴ YP Ϩl4 &`~f]Zd+#7(?}.W2LߓV_,.~6i-c'F wIJt4ҕR[lN9Ub5{y^%)GZ|Q J3 p x1ɇo t@sJP,\p+t}._AY]ߣ`ABhSK l(gr'<'5d7;eOysن ӷmݪhơG!m_*G)C>k5`EwR"ӖC3*.gZw% 7kδBXgKٚ:)pnUDŽR ݅.9iBQ5l|,"f3yVбt5~Դ٭[[]k!Kф5K-Çw-Xnu-.[]>1r׹d`v~a|kL|7 7oc[G]rzW7d[}g}N7aY^0_H3P`_'yj  k11ݦ0*M-Oo*"a~,е A/ۙt%b3pvy &̢NBe$hw۳>^^ӵ!9s\"bb$ʢeIYs#!GET>NXMТٴyC0T!CMkT6ieQU=0]h=Y;BC|2+!^xR|neMUaR\|eK`PY̛ ck"zEw6^+,& sDM6}hisͫ 8d/y%/9v"7__*uo1i&A}@/'L5 5wt,J@Ƌ(&)VF5Ĕm_k!-Y~2K>ML=u88sM< L>\ʕ`DYc.M Gb+YOVjeZpa`ZnN43_F-<%goB(!3ocxK*}@"i-7k7_2_Æޫk19[Rh7R%5w}*񲙘gXo1{2?DWzpce0F6D /RZQ<_n}~ph ( &|#*R )7hA%`~Vu!)\gsooeXoTy|7w^?}X`N%.$jپYk ~v)lgjx Jd =Pɧ ~yLPVE)ZqMrDLOjk6&ԓAM%-1-1MxP ی⾤ui;3gV:rx0ެ|ۘw5BN]+1]hǶ3*mafn$̀dPPy6!u,,l? }UpJ1I]+ _ yъsJR_11*iŴK򬇢kQʎνp˕In-z*qR0E hD2fo?.Gƚ3Ø@ lFפ,y tV4ƤQ֎&X_Pe^Am0R +Q,X2Bm&%0iG 8S 2 x4;B~bLed|^UHۋ62}I _ioVXŔ|_=+.Rdd2S?GY!7IK,ʈO)[pU}F4︰GK6Lec2Vn}JZ Ĺ~_KF3 4kF's3C~ڿ6_U%K€{<*3,ݣk` D-\ja 01BeD6)[ hkÅW~#OR~PA[+/:nbp6yj!XXz+g~@|ś fȷ阓1[m@I@ed9QO;fnWoUxIhJS+*Y{bZBk`3m V@|Xd68EAlj~ɾ*4-Χ䓢DRr{J(Q6QaFx.\txXA2c`n?aqQmFb`P[dB? 7qֽ{hn^ ǖ]^x\ z+b+[p0|Ŋ"KMAs|8EQv-ze+M$E+n.6?{] S#j{T^w6A/2=)I+N#hMD^UzN:c^k4(_ `uFBf0dl~_]D7oۼ[; K7u]EM"K_{ͯIYYIw5[ ^Q_84EVX")P/A)6sSE.c 3Tద\/#ߗV:hktq-Z`GK K7ZPZ4-i0I|k.Q7XVTRIfн/_< D^xtBSm:~b pi} K K\lS/ޙmBlhGdo!Lo_R c9~cbg+IńzMЈZM.V ||̝N> f`U*KY1]EO,HS&[OzH^N ƇF =\ 1ApPߛCXllVoKAYN5,y;H?0.[.ȅe9a {ǃܻ,6|Km <5;{;L{ɩ=LMQ"/BsZwu厕8 9Z:~ăYX3+PMW O]{\qQ;}7\7|L+`O:`T sBLߩ%>!|K%ӟ(̑T_:+N n0iWeKdCcsEe6XN +#[?4OC.v|dVg8$ J;Mv0J]zKaՐ,m~"=s>[~ MKUe>&բZt $~3Ծꏄ߱%>Q ?{*U9E[>k沓-Qcw'Cf]&+)wNP03|%yfBBf9ahzEƹ!ʡJpCua[ʸY L+h[LSЍ-1 %5Uj<@HMkXc$_賖12^.^!qFmorG5ʼnLCv +OȴIGóAtEvY :/ aahS`p&*1bCY%~PV.JDƋ^UUQQ~Rlf2l S O:8*h~t .QMhE0k(F#b`#S˴dN)U* =tx+5m\Q"]s`cu+4aݾL̲qZWT Zg@j^~Xv4Of|cI7Ɨxի7-'gnΎ66[\բ ަ_\Y&¿ˮbLZ8ѕ b ] ?i |*.ҰiϘ%򺃀& FSդ) Rُ)#1`&I3;ز㖦ti('u-bmzb=+ׅc,g> =a[0sп:+3RH7=3dQ{`m@A8ď;I0]I#~\uaELUI>0bilo->v/%NcDmP\ $ B׫@-n;!V/_w!%P;@AfԀpLeW@j‡Og [T5Z|mJ1Y0/nlo/AMe2(AޔxmTkЀ*=|rjXz~Ƅ IL\m@UNCcCp"rO%0Nabn1!u3+9v~=1 ͪU,}bo^F+| ti݊N{bw| gc//BNZ_t޸:BlV#?\W*ЇO#&ይx;:Uº|@]U&vU|o8h$Sf~)*!"ʫQ0?$þn$9ԠC4gC sjnC!6P- )q &{ aA19%(aẌ/NC]^OPnhI-8iMd5BV-M [@:T0?ؗGl"=N͆EpN+Sa3Q?u\?0wu)'65?:p&:ϩBF/E&8-pY5fPT` K7Ť 3͟i +%4 pfĀa<V^ZV*P avOeR?61/jIC)u5yC 9bKVMP5,!`DcũM(* Q`LRP@I+ MK} *yC49| 87b{ 9JMDd*aſ` ~ Bt@e7~!kC""F?61/ac{mN}ÁyvQLiaߔGְD(s-pE~q&* T$-%<!;CK4D - ښU YTr@IPKruZ +A>?:9DS957FGf J Hh]#u a !`D rOnW*r?֐SӦb/ڥ81|A/l0:hO+nf􊎣u-mx)}\ Nd"]'wLN7]"{uja!&:ϩ ;M`Bn?z^2T` KGOMmS 8J K'Z4,ҰKQ,a4@\`9|;I xZ/p(˜>\{ Bt7y]0 h}?:ϐ#~lb~_AC@yPR:TA]a0T` K7!O~%} "8%Kq)/=gs,bźaaNa3{9\6׭A Q1q+LV \m*l׾!qe^i*$@$'?:x=,ߗP Z05rֻg }JBְ `hԉ ;cw=AB'Ucza>t@v蛫kKًv?3ڠDžGrclp`rr/=8e5otnb~_Azqqs]wl걼f ?^@QJ!{)'L`R:(r{ɸxSM*ʨ}0ȊYS{YrhW\ _P lvFOP>+J..}^P|8wzw-S/}N]rtQ7?R0UC);߁`JrQ`Rfg`b%h+neW}V$Qr^D۷bQE.(-pqG!nNf$0l&opq:g6M72o}Mc۸9X[~|DcS^+fKpSO'IY/8gkĖl@˷-`C4# ST"Iݩ)p7kfJ_wؿ;x 4g@S|1W:xZ}P j8 I;VAW(NKUTQᇴXwϲ {61v4T;Y;o6 ][LYMۺp[p Fa^˝jE>ް -R}G9[n Þ8¥w%{zJطdV.#O<0(n7ShRd)o YC"F[B5θEq3 2GW0/-vr܆[b>&VZuu15`^:5j7o j^Rm" }1?0?3QKMWM"T۬3D.I ͍%?1ԅblYX8ϢN]V׎ 8.}س]A40 ׬g*Z/bCUa فZrno g]);ڽ̕O:I'p "H=? q/ԒL%) {_\4tI^Qn3@v媬q.#]qߙj]@_h#/' 2QE<"\F wD{5NЗu|Nۧ>>*hx{B_o;fnߜCWW[#Əٻ-~v :kN4AIq%݈ߗU:;/Wױ3ԝ[ 7urr{{I&toF HcHy$`j8;oX6d>Fda+ 9!ؼVҙu){QPjt8tM0@A狡cp˥zVZ/j)ۭq"\?`⒵o^Rt2t ~|Ŕ*Z!a׶֞ ; أq07xӶmM ^vw]`nBx(ĈDn*qyŜ/&́:u)̟&nƍC=٨TkGeob1uɐ^9/_>4w^pȷD$ ?vJN@!uͿ`i%wA7j+v AF(Wtޗ?RBJFBj__o [5Ѣ>$DM)$sHddw"1/P g yML) KV? |Ʊjia?3~G/^>9Ѐ{SRzi{NZ{3vG c|RrR;N `8Zn}f,_`_=y|&7Mhcmt*杆ى;0MOd/Z$%/aKkX#ow/~bA 9}1ۮ]Sh1J9'0n:/Ӯ$e*wʞtcvИXwӄ4w%X-74%\c=8M={^6~s)&9pڭ8̀ B7D#-Բj<ߨYL6>%Jvՠq%e`Է{jOit$q؝2 \+Yh~yrͼ5sqnq=&˘tnTXalX {źUO@LF.)ris}dw[F%AX/ >}Dٚ鉃֥0ԵO0/§@ud>0 :M? ͲMy'7?zgSdJƆo4>aN!(1n޻wfm?>^3e{ QW':Yóu E??D۟d䭃4H>{HzX>Ir8H٠CI1Z(mnAGb& dsS*p ُ\c=bڢ@Va$Z>|uCb @&(j냖Ӯ;}/*嘜{eh4J*r/soɩl?\jJtvd@մSibw-Vu:l;S̶#\>@#&≶ ao_|F0g$il/JBb7yY/bzD(F_opnn&ɂ rI)X?rDs1Dv\2f; G޸4כwZ"y4Q8jYр:۠2>\XUQPL镟vc)z!ո:ljyꈹ}x;Hx; vx>jBn!Ѱ&zw zSB'ӌ9.H멢Qv #|{J0rs(iZdT\!g0PZ mCS7zdͼ-?򉢡B Ȭt@"'ԏ1'>>@`FzRYc.%~P@rk=Rucwhڦ _k$R T<_nJnL[*) ۉ> 2Q %oʕovU e1 |O2'_ Tλo=ui0|2avz(}XQ#XN'Ą2}x3qE /'klnk#61<_S~W'5l"9vl`& AA]0pʧ JH*:.U)7.J&ե$! "<Ɉ,&Oi:~mQ)<8KdP藘a/79Y2LOM9X 3RӾ<ݍJckopxQ~^ќ@'--Sܠ/U>zj//an<"x_z#~,FS 'HS=l9#DhvzT֎͸A 5: WSHi5% ߉MEҀe#@z ^kkr9l#D{=Ϝ(^1P9]p6iڙ˩v1mBߌb+ \7+z5zo( TPQ\ y PdğA )Ŏ@_SZ[_Z\LRhm'Z~mp$v[FxI0_̆Zd<^?mw#'_ j{v ..h!'an }p…3c#^8>el\l% { &U7K-vDSw9D>x2Adw"QF Y},A-fKsbks[JYdcS)DucM[JwcbZ'}fT o仼;>E^]01>o*P %6ih/3îNӜJW9@y7Hd64B쌸~'_rL ^e)FV4# w0ɭN8,nKh:yӽŦR5 }0ۈ"N B0+P uXR VEצ9_wK\ŵKiиDNp% q=}LO8 ^,h ކ#X+Q%9D~ϫԃF %g\sT15g~n57 qիHJ raQ,zc'ø7|T2#Z`** JU|Yͪ2#gD.71MW4o`CBW%Frkak'q*$Jފk 9?5H3xyWy1Ԑ-x@Ջ؟8Y(߬Q4UCu&) yX1GDh(K`W`2lA@ 0zAzf& \Z}h}D{ "d鄖#) %Mo!|%_ iΣQၞD3W=1vv5 iUkSl L{=SmDC$M]jJMm#=vQn ns: )NŘ&ea;`+1ymyJ]u=4TtpA?R.K :Xf튌K9_kOX@AjUMw {>viO-^7oݺoeCC2AuLc͛~lbM3pO/Qt\ ðT˹ez`V.&7_nO ! `MMYTfVz;Qs7_b',{*;H܎3eA8g^v!0^-ߏ43⛩Q&'dxT Zc bC$4o5i 7ֈQ6 R[L7Kg}:ji:e7nE3M`lԘ/Jbr45ncdQ EXX2nx!_ ݹ2Sw 4e-'Ln`&z0QWMaʦuKڊy hiTRRbk%0`ӗT]q ho8ĥݺqvʏKhA卄m+{eMY1xVbOl7}oY~ň#~pWBDȅۀ|.]q@f唆["b 6M Vrn-l$@tIx/[<|!D],d!j=|0RBZV؞@PRz1Sb~#[9{% 'e}JZBB|NHxAtV<IOŲk2~fיQeccbG+{ =5 &{#O *L6; _lp3:pu?%$j?iWЗb6kG%gt(/SF=aB*TIF"\t*DDYp-3ϢH"֫lZRȄOILLR>GRR >/=03r۲O-gPyQK~1JJ^Flr{B猘RQePg=:Ҏ{xg `mw61l4N^}STlڒ8ဪvSz2c$PM.W$3v@[>6` ۂF69bq#wr]zlbzgwjԥ}Y91j@+V+iK%ҕpJ C)uav$vMz3 V2@ `f–k=}A)A{R"ɛ6ile&8S/\c0ٽJD"2uDYUVRЙռ ny=(uQv[&B8lLDĢ#'7 9J'IzoMQ `ҽ6I*U|PRJ&И՗$SL=?H9NkGa-G&;1 HA1POVSU-c W@JS]iB]5,5L $P9m S!N "Ob@7~]9!xol1Snꝡ"؊Pyоk8?԰:UX]ǦVgaL:)"O1=N v<Жฌ @FxW3? lY W\q]1rА߳lHK!5U`i5=Px<ċBop;4 CZM7܏ _L,'_ʨ Um!QMR3H겾5Y*8O#^ŭi)A/[ e*D`mj@IC%Ά33mX `FlJt#{sC: YȆ $ۉmv^:f,Ј^n& H9IY׏EjF^+%LBܷo?AV_{-W^ݽu33!tNW_ %,@ 6$CP/׀m-#j; |1̶m&Pb}&[Hwm0kqYM]̷.>M^|tKNek+c@(lnh{H j8ٴp@=|BMH C ~4o)ɶX&řԺf͉}rHW]Uݓ;0wZ-}G3$b+cC4@Tl["io&M^X$\oA*JOw8J ?A;E^xS'̷&@Oz`2d%[+շӦ3>63j؂1uޜTG̙;5Ԟ?P<(@4 % ul}eQ䊙~9N//L tTb[fTmaruJw;R֐'%zP7MJǸJCX[ft`i;)K~EYAhmuɎ7NfƂSeǕ|kWiA_Ie(pc> )Гg2SWI@Ҳܨ5Hō/^ @h 9u T*` -=̽33H ՞?Tn,3} 'rN̪N~'_ ӥy^]([R'ȉ\ȸՃ'r\H4&1'7uc^8ƺ?6ǐ~"ZhZ^N/޻2:hϖpa%>wydl瞞k\tEÅй^\"# _{Ƌr2%Ás؝F^쭴i3T.m))Ȓk{iN+X<6(^Ϻhs l2o3Q2mrEYMG-q3hX{Ϲyǽ|K§dIŇ!JwQR^,)TeV{/H΃yaȮVrӬ\mlVV7 ju\b쭙sͦdcvZ)#fL9;[l}SMvM6p>iԌU=՗xUr=һMLFb X16eB"Ez 7I,oۀ%bjJY'0(U*aZ4o< B=Q y-omwDC\5R@I\^\SZW\eJs() p|0ϥHb+&(dԜTBWaT]7 $j%;iCLƶSV[%#)eQG6"nƲ1r<,n'w6jRʆwimH,^3꭫ຑ ah ]lM}3 ;zڼ VAȜt /3ӲF_S(E ~cS@{Qf'eR ӾwWOGivQ5N>JS8]o' N5:  +Dt HiPuo0N푤@hɥ?/+^L/n V2Y8N_c͡ϐ!-0Ǧ社F,j ^ &x*kG?~d-0I`cu%\}֠شMd2qeCFCf"hM+0)k@nLpk1Q)F'~>g;Bs6XkݨkJu/u-dž Dۣ7h%qUZJU$/F_ZQf"dz2|^HݐsM 8B! >)x>b ΠޓuBTUzVbKWG>_TJ= ؖd!ն]ΖM sՆo.Nm9ghS`J[dQVbe^ǧ x=e4U%_ Qi5u$`PMz]&0g]P)1q[IByDΖ?b?4"ቜ8ҔĶ~ /w5=F.iʹ d"7u*̇%Ld׆݈Z'(@@^O$ Y7KfVS?W:!|$L \čfRFFxhvJS\2Z]%k~si7N~S/OT -ocVZJZiJZz,%uD6SF[\@.[{qJr]Bn'_}|,t+>` OBn=?? 5\).u"E;S}0kmj=1P{k]˗b Ao{=Eq&w&L*d&J`{Ž#Ny|3NSQ{; q'wytp>Z #jN5c>l|74LA m7>wTL"HК4` +R 4YPRSh]|u!T&_ 9[<_ 軝דSn" o+{ߕ)-`X].kWD_~ZiîK9.-c<=B{gc\JZ$ˡ9g๜e,&}?t)‘r^r)pVor uΓr[2N})BA,-bQ4罛%UeF]m|r=:!j(]hX͞6ЪԩrzZ|V@Gi3_8}MÚQV΍\DU7V&baps=tBs2c5d3fDvFWpfL7۳9L|as)[M4X3vq\TW20'k\qe{?=CpmF0swFfA~:msDS]t`cY 7(w-iT!Yi8}]æT6Q$-/^p2JzՈ8V^/CD*4i\DSlJ:\!)NgrSwũLft) 64Uv~׭e?#7IjR:?vc2=#Mk;~EYiȖn_#)(S8i  MU©Yyďoh*b?;@Rji;7OT8p uڍN3*s[S tB0{|Z~d ,%b%IeXG ᗣw__E?CSfƧOo !"q~CK~vl~&3y{7x&]c:C $~̈~X z=A\!ixl5NH % ==h d,vhf}R-Yyy-{61QRߩ~U"uW ESd$Z\~AX':`}%ؼDnodC0eMen#끺=k3eƆZ= zGo?nP/YqFӗ:Tf$LcCFn5 )fO,R^[s(R9L;m(uH+ѠҒ.܏Z} C,䷫>oG*~$"͖p%"l/]Y紂.w2Gje0$XsymWRqe,m I4,ٳ8YicxST ϻ7 xQkOM@i.E.fU^*b| NSWL6YcF%d[ȍzf !bѯjIqWVjj3}?/P\M0z 9(G;exٳW`ZWN^̀cb*a$%!TZ`QWYSnucV+d&LY#2 r{2a&V.{ilHyT?%$<,yL},+=ktfnHv\.tr&'ޚ3& !>B%*wzv{,b]Ij%=vK _ PVG?4J`JlxJN 8`C_US|W!i`sR'TE&=^W.Hv lc/vX+еRb"~@t`åz5jaAD{ |J8LJ(~ܹSOmФ)P HH5"@N#) 3rW[KË}I_ܷ ذTW@ PHYO@[/'(m2x%~{K̏ 1򢇢Lznœl'E/i0 Ft2J gW SlH203J{qhw.UƑ\v_@uiA{t4\Ľ}Zyb)ϱb66 6;Ο'"y|9-hNRVT&hMnդAwZXEh2ekBe&KqS܄`S^r0?mCp/^zx;Anm?4!ٹj>~L\`e =5&&ɬV+ly,}Cp͈U8&#h%%֩Ex7 !$y3u̾/р`FNB˗WX^eGH_gd񔋟Lj4,8gwu/A\*;lƀ]0=}|Ɨx% tñ0g$oig`b;/4Q_Dˮ{+VK&b 'h9Ks"S[^6Yw d̯dqK"VnX^ZM28̦ z/5ѭHh1|rwү$A WuE$5<=C7*X^VXJ/ZX΅$( &` ۫0"l1~9y.~9Li^=yXKV 5$MN(a~VZ#Rm^l:=J)gVJj`Tf9?{]g &!5*lsa o&7B.5sAq.s@Owig| \ cҩ5'%=Πߡ{bSMᯀbj*{KJ5Zhm\0)[L9l]:S}dLȼb εom % L%YϹ o#! kr`[KG`':j̝KLkg̴}3̌ b7e:(3 >~AE[s`&rtoY_6apXȋBOE 7bH嘼a= xEGc9.qkwG2OK$jҪҔgkEƆ g)[1cdt8;mtݭg̕.Ą7[_d9CCp GStL N1ыn D:$'F!b7e3H“_hG dz'~=Ar^4LCӫ bgwh+vU6.f[}7l`8P~mTJܝ${mt+vU6V]|7o7o^z3)+-Zlg0x,JHo (A\lκu1ˣ%4]&ڂ'H 5{"KzJ/VfhWvr޵n؃U&Чcͅ +ߖ[.E C?WFڥ# uLa*E%]*%Ue;UۚWɲo\:Sv0? `Jz\&ef]qSя7M_eQy<79dQ^w?S/ YT/Ru*) X\qMgM#&J>lR`KԾ#‹ H\ҝ~og6FQάLV*Y.財S{6^&chp9)HM'0`/ia{vb]`zPd/a4kCaafcJk^'e 9/)ҸrPol'lfky埶d@:ng퀖._#_C%3M !I?Gf 3o˝j|/UkkjoFxDMYHtX]lF~l="gx^k>C6f{6zE' MC9l*t$5Ƽ;q6ؙp&w {rԉ_[E@RQv@ Kc[y Ysg[o>h|Qo]h\(ti  .Y'hu jVm#LՔSӧ|+"wZM9^)Xuǡ 3|t3TG~HeldC艰Ώa%RKd#,ϓ&8 . :9('[\-;O?ў/]0}O )G,phDM93v-SA;Sfr%_ԿYA{eAn^AUJH~qMn5fg_Bh^^E]H]rax`=Zkp-9 Ì$N6XoSg활\ B :Y/5 㑃MYtҟQf-x o#F^ ns,%ṽfQ0e:౹*% I=/60>ѝ3i& JNsf ìU Bqb{jr/okJzz䯅'*`%'&,aDɫV䌫`#Ϥ?{.!H&6f0*ӝU+3$U΄^Ct3Xnxpi(`b \ ЌGLt{PYb^6Ĭo~+xS[} [ΧڏJSpBi 6JгhJk]" 1+EQx_xOE P+J9H[x0Aߥo Zc@US挀2ǥ,6FgI+z1cqg9Dy2 6Y{P|& .HG,Zٿ͔)ڊ[k -r! x5֥#)+uQR]VK}h5"幻Eʌו0l.<%ҥ쐎뙾.0n+J*Or`c=1w+9!q{Q^g꫄Mw-Tځ5==eh!TYJ)s$Se!Ft'D99+1Yzv]2LY*].7l"K 6LP)el((ä~T}鄾*-~Kf|#_ŧ:oLAC~hmOr{&0'T$AfŰT^hĶ[/;v4 M{}-)zU [D>M @yBt-X1h_*2;M!tzh;˛J;%эi'1V}EnE<-&,e{ ǁy\ ngå.ko^)3҄z PK9ܒ^G$ˆK^BZO\@,\X>[9(Mf^R-c^6Ӑ8-/NQ`nWlg/[Qno4.'-`hӞP/tgf"s42C=-64K\Deh\&7Rt{1S?#WX(9K60M cgw3DNfэ۷*M-h/(U3&M!~J)r4$n7nWf,|$ߊrY@^\l_g^;̯bstrHL%HlCBM7˭[] 0 J{—[O//zW \סeY>'9.D5!f o[DP,ǎ\,͂SYe$ɃH0S[h0(HQq6&suh3`׼!AN3ϼ3y&KlͥAUsueq-en1\C|Bxs̕g6R.6t^IrgW֧)}JR_E{L6)+BA@SP)o:GmqwŜ*Q?Y_z.]1Yr{:#R_8ؒU2{8ةCq8Ex guI;]̑EN%/[sSMKqv* \}7ϝ:sp_)eM&jJuybUE=tҿ<<,ыpxv{_p.Ђ{o\"._(+/fycz;az-C%^s6gw); '5Xb{˵EvxCl5̙VߨZ-Æ1LnuhhR8˱kWV-n\k7GH|[j)7je\K^ec~45Q^ bx٦EmJa EJ?cAv?UՖ!97rީALK3P>m0&ziAXjasE3fj; O\h3\pvmRsw Cy!`CdGW!-vd|ji>"TuX^@{䜪mgN0T])N9^fȽU._qn-e09=E$ F ayuOŭ:֮ƼĈcmq?zh;lI-dC5p7q_T TA%S^IYV-Rt~ތXA)[} <:a,(e%W@Uw7˜姴qx!!%XiQ+]gm2K?l3p])pIWSò[@W+ʖuawl%UsRL5A w;c0= {椺v=Pʧn9Ȫzr)MqAmJJ7~mEE2F.ܻ\WQ;Ėv҉g !cĽ!~LSʷ,䥺(rS]fjJC/CF[%no U.0KɩVHS5ȠUo2o,/gHf1˝WISJ[YJEAUyf54$!1uTկLw?STQNPPv$N"Aƣ`ce#,]0?[e*mylö9f ecÏX-|S|lțG:{}rY4!vyK.\PYt_]>̧uehxD3E36f%5P;zz9r)6}t4_hn3#*#R %[U ¿pEBR0;aBU̷&I ERWH%F X!˔\tL%}k{,/FL$܂<3mU=c/E 6Ng*<׮kQLڋ!.I0`? F,ceZ-tN& rDp kh\<*M "X7)ԹaNX٦?GG^V  {L_ /,%&z. /&ݰWZτ¥>O'H |WH I5.?;4 V\?)MyiA[ˤ2?P܊CgČv.̶b 2ٰE>7?Sa0>$ L3WC@eKb<ۍ`6Yʶi 6 m}evzqp.mcc# OZcCb_/^4$߇޵6DK˖%>J%jjKƅA/(7JR WK79݉Ii.:fW䜆g˘omr Aڥ6kzq]`XjjclnfbDܪѢyl و}p2i`$2M l |LKk RZ&gC"~rUlLSv#MV!*ۚ2Mf@1s?2}qfS78DBtzؖmr/Slik<7NE 1 ܂ ^xc9fߧѠMMgQvgE`,x'lŋ̤64!-w<\W|"UaAP[Oa >̋ޫ#uw UиaaѪQV%\n)TPY{ZrdٹXy{N>=#D{g7eLJXʵjU.ALSp_f8b7. 6",n,\6ȿoK>6T`׾hK GH . نP|Զ m~ Dt̸`Q) ߷ Q>-VU^p*A$ܼIȋ\P96sP徫HHxyg˰Z!  ^c~_|V`}EQouB[x/`›ͽy=,~C`fJn=GZ\wE R&*Vy\v(޿V73AxV޳*k1+f]xH0_k Xfݺu(2fփç~=S |vF6unm'3G' w_6u9axCha3FHpzTV{? ":$`[],H B2Ty&cN X GG'C+^jTԌ`&Hm_yIküHUo;(*;IX>MˆKlyf`aܦzBsԂ5!3e>1L#%)D)/@2d)nlҦ$%s£ xTn]2M^aԍ2]3zu|./JAY.@^ܠYgκ͏uwO>py_S'/m}׳RRc?vZV/Vx,QbmWL\d-*q'h{-1I/I7 n-J4(fqUЬ-r;KfW5:O5=*"ˈ!]诲ZѴwY^{B\T o\ {)ެ v13/G \KJ/2'Yr\Wyԓ-jbT"w~hdP7<3؃ҧ[FkC-9` 0?͍ĒBCw d1#C-}i;^MIBEIJMw|L/F_ Da'3އzL{jyE8bP .ˁ\rOk|R b1/!ɽh<$Q<~19m`z3/KF4b$kYuVLEϐIpVpxXldY'>MlFz?FbrCeYZy9.)5-51ABBpwȎr`Fu}SԖrZ`wC$'kXUk Wu5zCyo$G:$-:ud_J2.`hCc^Cal8<ɏgVŠg 83{-湈`f92czcSGe^wܙR#I|{8"q{ 7ogiTNUy%zMCدWp6p|+0I0"d9NUlBwB:Rq&]wH* Rz,g-y cwO›eMșCEԔ;t@zNРa`>Iڒ;6ԥaq، nݥMS `D :7MU1ѓgor5r׌iϚS'u"+?'9@}RU3>&'7[fΒo8eъK*ǧ16K.%wIߤhOȼi|\dQD#_TTU׌-S$`MwH5 ˓$>ϕY\l6m꓃9>jTuͭ[Ji؂]jJ̜ 3|g%ɶ[6GVa"'1Z_Yt4 !î4 q4nێXbo9M_I\'oFhXFF9]!.BS5O8!,H:<ˣct.1OO Laz'"{<[Q9[8&k3KؾavƧo;6>xʨ''ڄw [f1٩g   8ܰ@fFa6 ΓyHFKbxF .786m$X;uOI[ne+?^r`sSMlmp5Np[EH쎊&JR",i댏R# ⋵ힼST*DFl &^g"C_(scSG7\86ԕ;$TY,lvw4 9{EhJfFUv4¤MN\SRz_-}3i!c#F;  .e^JVʥ&]o:HؚzQ} %3F~zwaB8 }"u \*3[o>=lg:ShُM~ sJs*VL4mNcE֠f5hPT8&P? sΉl?ϱ*Tp os2&ےo/SN :'-X؂! Ps1}n̐(g",h 9cH2U]Y?3FNazN|M0glr7]WjY4T<ʜ s\uSsu)5PV701=r5F*jg# 0BL/ ֔A# 斺ߑ!UOyl f?9DS Oѭ-?jX:'rzr͓zz+mp/Uw#AӶXiĐ2@,&X1 P }^K~\l.'Y@7ȌU:U'0Tϱfrц* uM?|@(ڷpγBFf~b| ϰ$i4Dt=6^_dg&XxEMEO/g&F 3]<$L C1.Tn<˘})"<;sY;odg]gN8{LkkrĹV)?H-ŰH;S,|M9jm9he:WSi DLPẎFEѵN*b{`OMw^`4u<;H [v7D*b @G P3:F)S&áL }^j]yMՍGV6l+F؉d;3nt^!5 {8qZVp5kuDܖe4krd ~vo}b×XRd7[ϒg N0IVM)1,τ`Tz S/) [6ysj֎TE/{*@/%P]r&[xؾϩBJG)eTץsQ0@ &`XJ5jT)!p[a_o8}wtejڥi2QPDʧ*˻1C3nQ[m$w,\ kcR#ȯ߫Xk1Ņ}TFlTҲcC Ԫu%ܰ&p+S|3nPSh0xo -rx58RmnlbcUyz?!Vn! ;NBG6hT[/ Y תUc@0VbxNe0r˗fvj#;[[Kku,Eo3AJ5i̾̀]]-DܱӐiF1`;N+:CcՕ`8)rv,SO@W [ :~B'K9\[fw0;e.‘c5!b˝DppK֧*Hgr+j"JEDm1%Fn\rTwD2:.`/mf7KN` 6=һ9\`@ꡈW6QyW6=wzC_Fe#\GyŘ3Sp #e/ \\]4xѱX;jfIlTp9;ɾZ{Uk; !}A3fꦸyߚMГ,wFLwK̡&; C=xsޠfJ@ƁRU, GOhy%:kTl ىOlw/?A8{oi-E ܺ`ǟC> o׏,\JVϖG9XD; ߰dE?n_mo,D]Yݰ\֨9>6?ۣUyl",ԧWfɿ}mZ[ʦ(s=l :ǺfkVB5 el߸,_b/c Wg%~tǦo>bsJ-T5a1ͫi9tO|-fh[aMOK:qܜ6-HM^/2j*6w&z4Zi5ܖ D9ll-r=;(Ҭwl6-Ʊ8Kk>PZ\ g?Opj n_tyS^/2j΁W]qiN>f}= l[[ns+^feH+'OT/2́!tU]5\w^͇>eؐc-{VcO2wnMaޜ}v'^[/lGz [[$,b62^|b*@Y}i;c%hu2nlcy}́K׬[~J#B]#0_Yʭ^ʥ Pf7.~Qs"*)vCue 2mz|{})M7U`8UF9el1w(j~o9^?h~qJ踱P@yRJ`_56i޻z>eLxo\qOVSÄ*aVNG(GQ-Sn#':Sx DcN%e:T{HOyO _(N+NiE~%[Kj_ ܷm+2KU{<dLP!M9M9C]џ+lLZRm;kTW8xMא+xU[F۪}goD>JzX#Dk0Ǎuep(R]B`k,;`Xҁ<(V_|%㶈fR,dfE␒:)>xq\ ™qmk3w ѾWn?T|b e՚]W)rGxpݚ bQJr`jySJs'fu̒׈|7 ?_Tf@R`0LVa`Oe_K9;עwb\z-߹V -9;׊f_%9;EƸ߹D<-Nmu´/ӏ?MfR;i׵%<?@,ufl, Tyv*o \}^^ZϙU]#ba9Q;5OP00 X ٨Ŕ3eS{f1(uw 8 xA6 tQ砺]ǢQ ߩ0Ori瞠JQHf8\lcl0OwD$6ÒK`=۱2mZ2Wltl}Y&I]d0 FKlDb;`w_h.}2+ Q?險觌SjT;L0kp-';cG^ K>u9  12{NxyrUkw9WlalHO"wjD&6.ÒޗϝdX{ECsBOwjDlYR/?ɰ9~5vV)c \FMM+#b`S s Ys)WcܠÀq=AaDpmf};i;v7

    k4.X{PVӰDQyϛYc# F6Yep=³)ad ݵ>L=oT/QLJmln4qoa>&F&Yc v8nq=P 1x!klV6|DG {SueVp?E_(l! *Ǎ8|L2r&9`7y۝M/i;Ÿ["Uw|^l=o½}1h{1릌ښRg8]3P{4`\0"אد n a>/Ր5ܩ>kbNՕ~|]gqg, Fcky8坁bll5o~w~mi:fubLvf m&6ygTnϋNx?坁bhl5o~0)h=7a0;*99Im N;P%uUgBy"b낺3W jA~(Z\B>(q Z{y"/*"/r]0T?1.!]PC?]B:C4-VP8 <D&*>Jkϲ6@?;T??j ~W|?5ڠwϯ/g׿ y?Iߠ?~þ/eG??u'v? ~c'___so¿!@Ta=w?_5?[?oП~3>˿l??U?#KcW_/w_?_n??#؟~?ۿ~~!Wq/?+c??g'mWg7??t 7/Q|_g?>?'?O/ƿ`k?oƿ W7?w?k߫o}e?ohw@?)wO̟?!_\?G@8j }ˏL(yډj9>sͅKYJWx`M0hS-Aq&-/r#<-xx5^̉Et.=VCWM-t[`L.޳^?Gq;"9dS)D ^$Nj AG;SS0:Tew$4?|EXn̜}zIuB6O;}=s\5`i%()ÈQz#܈.\}] T%gw,NCkJ94Zj%gs\M8@YJ-l}略m< vfgo_,+ K"|MVf aHlf2BR%(yx܂@-hD|QԀ@S27zo ̊va+L+ :+/̀twn|f:ܣ<SDJHc&o T<pu{guHo^ xM?:CY<и\[*;*SrWSQ2M}rܧ;J#h1- S3=zt1fsa!XOGlL!?{9*$݋:Ϛب2 34ybߐ' u GGQqnhWーkgI@E<`6<.bzB~.M-R8M[UH =)$L=(Cd*@~4[ RJ&H,q;>H[. KrZo6֊`bh#dzdy٧Be=;\[_zN5s5*m6 @KLJeeg&-Uv2W˚tJ4o9xrFW#n`HNaZN$_g8g)vLߴo H{ B$JO]"扢6pwjQSVn"1dё #l|ة[<2dO9 bŘϏHwW:·q;Ƣ9 3v{FP~>לT_ һ!BTPSy6ŏ;01ZR/Qp!w&.L@:'OPJX!bZ`3FTa[)h^I2Q菌rW]L3IP%jr MRdMEDy BW^K|U +2<Ƌf;5-to\JfJƙ_gLM׵{MeB,_yY P!Au)7 b #G4!O.ل-pjr&+{ }]Ta g-*~۞1ī}1 IF9E?T ҃(XH'oѪ?w^ hL{FW2N*o)ZTza04_Y"\_캈6FCX$}wP'vҾ v:Z&Iΐ~>W˛GZ۞Wiӊ2_Ys+Q#! N E#gJE*6$f;/a,` OԽ@9xto;||n)͔?O}ËClO.h\J=zsIc zkkv<~S|r"š7v߄]7;ѶV7_|>| L6Ӑ>6 b w65֝[ n.d\6*MovI[AB=NJ S.Dy-=CTH$;]X+fɮir:v`&vS6Ą 4!$FU3?FQe_'ϠD\^w== '+͈z071yBYhbbR~;I:)#K<y?/^s$rp1~}q:Tlq%E\d0UϚn"fΓZuՌ9R/yZ~3V H=X!}i-"+f5C{G}n|u2*ڠ~o@D +<}ްvUv aGEݨeɳD{etHQ4!Nuh[EY3[j5o xy<ߴ~2#BX'*ͮ.]Gh"q+GӤø"ޱ⤧;N`t#mR *̜>'U_%ox*Dz,ܣլU&2-&t]}LhYR0C2Bqs"7lL XٟYܮDE虓yPO!Qп{G=rQ鬝h )nhQ~)'B@h7*KR뾂Kꎑc.܊1|2$>v_9Ļ%_f. ѥD]x_S|NFc_FK g,'jVGpہ'xrrRI:VE1*/FO;=%ښbLǁ8Z/v59Y Fbi2xbz^UZݙxo9R(~VoAaW<`\㭜Çم|u^?bL5jDMnrʃTyF4!ĺ3x$h.]1|bXhErÉЖiGzԼ)#۴}3ZrRR~W dRǢ6$kG2[/m^?J6D i:!ߺN¤$eB9F9d7S'GOwvO1%Ǵ,> H&䠼IE>~|hUO_M' !&s l?k2F&F㪴`njhhM̱N~rM6Oņ<{a>Q A,7xdq,kF5k t2E+z܇$+n_(Fmߥn|| _4Pۏo] +d[8$Y\x+z&Wy6J~=\$פ ί^ rl4sPϵxp@9ɯ_P j:O7cLkԓٱ/ +A}21d{/%s˳ZK|\ !8ʨ7 -pT*̗YV=gހ/ +>b!& >(-seȳ`5M Ur?n"ټ&CNf5$f =3؝BPl]~u,E-^d}LsxBʸzU8Q_3)E$71 qpc1=U|z1T*~T O53Scay^;}$@՚e/˥e*I%A-bNBk:t,޺~d j4thv}cG -c9q`[XS/rU_#(65)s.5CUCQ?J9鹥^VySpLӚIAA<oMH\FΤrx Y}RawԜqo?ڎRE:1BOh8i'h<XT%~Za yJ -*s$m7?6\m|rZD9z!rD!{ *l(P};4_7*|Y_v(#0*%8=ӏ~p&Ǹ.:cGke8TffY~P-3, oLW8879၄؍Xw ݗ#[M4gj8NY^a:T4/Y^uhZuS,lovj,##3A% ݷt]|,N/4L5U}CD&äqm._NY*oFKWnhʻ_t"OYgKa qEf_7v #AmHuHQv:ך2k '3,s4SC~ Kg@~-a Zѝ2elp%Q}n. *@ m WR..HQxդXmMVaK &^KUm6A f9TOppdiFRA@ \Hn_{ U+_~\&ʣfryf ?#eYZG=mЎۏt-8#)D22d$RW"3JOì. <>|6d)M0ɋl[J{" @%g"neΟؽ0_s&bV9H;v"Ē8Ƀf(7^'ĵSdBC,a_vzyi$R+~$z16dwZ>oCBiΆVf,MeZ;ת6@BWS s?BG n,u߹I bޫMhL۹K?,ѱM+CsJp(Ǒ=+nRzCBƦ*E>b?g LA\;;FgyuUhW錻lKSf:>&/;L!a ]P %*4 {{ rg024Aƿx# _O5;Uةucэ.niqxal͒Z@*ϵh: WEz&ٺ|bmk˔T,터]RSt@P3kr/ =5`2HG G3DDs՘[2s΀zXaH2%-C.QP?o"d)$up̧gLkPJlt\#P էF$0oK$' g6jML;&Heꯖϖ٠kw ͶKO*FwP::ǰ٬?ݩ0/6$ry 6N ?t[HCb~ w-& +W)q@wuUM)G&egaQM!~xd̫1`B-îÔ[9CDuO.݃ 6$v'Wd51겳f:s,%'IOlʯ쉎UQ-ȟ vּ=}u˖޼rJ+$dG\ާU#9@i\2ρ!Qe;0[ȿ"rݑb r]ENLLr$glFY]\bz3sASh[!\qn `R0͒O]"?-}rN.$47#+  ^܏cZ.yMfıP$mc󅁨3nsI^C/"5;H]7zTKNcU:X`urRqa)~UU0|KF!jZ_~Q]Ŏ;_` ,R1Om)?@^[r^g?()XDwe&+^o^oxa.Jri2REBן_q b5ki,ڧJx N(M>؝( s8qPX/Sd/>O-e;p$u@y򸖸c{vL,>{aX 8+):,s@~q?~.|M\I)BK@l~,VgLdnv;ĜpO#apD]ĆDrGDRMO0sx]:kOff)Xlwt{{*XW%R=f26"u+5sjV㻡F*!0[:Yi!diq:.cu\,{;xq 9mf>fPxޯ{{U*"Φ{wgZPV<K` L,Hl|]!M+7hW5գ[htla^I؀#6Šڨ9s仾`A y\$s[Ŝq2>( ۦ]ykjJR BBˀ7i@.7]^Hzh{_BX%覴!Wm8)D5lFmJMp[ oatɃwrN~f:7GsS-ܢs\>?:6ͥE=4}[?1 *7!ug;[7zO"sq ©4|k2-K9Di! " `Ul˝Sr*,2 U}8rYx芠$qNȕt?CMP*ui]TB8I(UiwJ=]W{ ÌNqX0N2\J{aGH@}GCfu,6X8k情l)~q_a{p0}x=iU_kqnhw7ϋlUG*<V_φ5p4 ec7դ~Z+ c'iEryUԱQ} ^ =ǃ; 8ŖRn2+i4ڽ] pW(erC,tyﴊ%{#\*o˼Nq.gO۹9SHЙ2JPoUdfUt^zռFޒq]cr&1>ᠠb4 ѺuJkkA$ԪS/ A=L;#pyO#SrGxh뜝t"o曕dv6  }}nK '(?kS ;a\EPoà~- 3n7;ݮkX '0m^<1Z\jƱy'ce|\>E]v*E4]XnSxg7ٺ"rDv[YbDAss/} (Oy"(o/.s%OtVL`1|+jBN-dyFhO+rwdW GLfPn}>љ9۱=1 e=T>v(3꬚^Ì =z|%o\?P(C~ -۳NH9ͩ Rq4L|k n3Nn"`wB;uW>r-(c)Dp⫮# QPqՀ *g-Xh- vS$:k=U!Rǒl0L'GY槰jቼUy҄l3yWM\ '<6h|[p~%{폯@'mU H|v%Wbr'x&Bj$C49x=|bq3@} !kl?84$^*nBΞ|0q m?ŵ=ºD$ǴAu;3RNk.\);-ޥɋX|5"|ҵ6W |K8v(ж|\'Vg)95 UEHŐ+GkFNs/Z4ƥmZwzﻱϫjeL`V~oQHuɸê- \6_ң_#a]zt[?v`=:]bt荋%E̲4Ǔ: Jhz+Y R % vE?Z6Gl9ֶ&P2wljK! Zs/&3|v8b6T=AAy"GD@[X:{@/TF~7[Y.Qp[bDqbn+pM7 (ᮈVc6/ D֙UdsoTn]5z<>Snk]USRN9_i%1B$"[UŔ&RdyG;a]qXh "&BB b0ɰH+){{xM 9hStٜWUѾ?E8ag`9BU3Acp"U1]41"[iQ;,vnpO;/!ysT$oysM՛Wd)O[k*;M*%˦0ZWTʛ!}ѭ!\32&15w8 ,^~o[QV edw2itвkjy:{pYO/vPe9ݫ.:˃mZXe=UfaFMh Y*:VD Pg髬7*S׸HiŮ ?U_ԛ]Y.o/ 1t$?եi.8lb!zO5֌tR:l0b<5/cޚ JXnFa~zeg4njY*:aƸݕk5R%Ih+־hN FrU)( p(/ 3%~9NN"{,uѹh@짧<(niam-;ʹ9f* ߺ_s~WdI ݍ'0+@!/8c$DՋ;7~>4Uԛ Л^wޔa($;a:ux@LO+}G/`y]l'T>S?֠\xl\}`KӮbp'0, v9XTs fh)nQ~LZ;E&#> B& c[dgy%VXR5X}Y4y>J BI?!NJOZ&pBhCLRxV[=@nzR\K$}{G,ְFKgGƾ,%JuQWz %L._³,;Vzt_ gCνdya1ebgR-Ko2{o"iϵi,>}K8 6g,}ӟ5]S}Ұ!ڥ|ɪb[z-p}egL#JN0@|i]'&籕fq^?ȫA=ϟOdɩc+dC]R5hC-XcQe%-;H G:v 4K <ٚr,2q$3'z6r HF5?R3jKR6#AH#ZI kbc; tY''ƨNc a-/ަ>3~Chsuαm)kP3+_ %^yg;L!Cш]6m$QfLF,)Ϣ"g{\1Q>1G#Rbt=~V P(@SθRe)bȩ/,FU{6_;:SzԭJHF-7s &`~ŵTguAN:h}oκ9, E~j$~8ۻ"#8jj`G q3:)dy@ EJu)oQfs{sԺC5!ȾJ.YejٜOJeHN\|..eݓuI F&0>O':"M݈^~s}f4Pw;IlbH9]%;hBTbm=ScO~'ytt\V%/#h]**[Vn"#Yw63ܔ;)PEHLh6t1oy=![esZKëfψ.?Ve3A7ryngX"%ދBlAtOt ;q.-1MdFo0թ,.ɽ;7\0xJ]r+lksǒѰq$wiB敥eODlɤDdg\}vE߀rUl}ˎ|YaDxΣc8RL.7E*<`-~)УI\f '{B*mޡ揢(>kaNJ'KԍdmҺY:M"uxܜv"/ۮ5[Qb5~:- x|ì>0H7[@_xS .tyX?̓6$zc0ĽD[_CNn&, ySJ?M+` iĜI+69 w4A s]fwva-$@3fφ DnS T NoQ{E3t~o4MG%jU7)!Ɨ@G^Y&,i~GLG5)@|g? 2) yF$`Dze8gJ`ۣ ~'ZdϤ~\xWdvNӈU=KVi/{%c]cA BȊ#Շà}>Յ!SH2 )iYy lf؆X_Z.c?xtR +?];6&8ҽNA;rFq) ӎzHU*i=5@-w?RN1.)87rϹz< vY8Y8⚅ɦVGA2A>4AΦ`wi앳fEn3+v2o=Rb1> *3 `RD ^p+-|/7}ݛ 1{#·w6iX$/JLra/j* [wEʣ _X Ma`N!ڣ[;h%}ҽ WTqk:<9K1I>[X"xng(D'H'LZA>.41Q M ApS1*v*@ҔTV $',DkqldNa=[}xCƪP7{h\0򰏶Չjgv1\닣kJ\G&;&kbl>4PJU}7f_:׷?|W\"|ջԡĘWAT2#F y撺EKBය̛k'wpeۮGq ^i١x;c3!!>Up\ ,#PQ~(:äay$8H(B=E[NoRH_b׮'/]X8&J NNKֻ/B |Tէo\Ӡny YB*;P9j`rD1rOɂڻBi9/o%b[@=׿~qNW\vgsh3r{ *+tكzN@}[<4ili >( mh7BlW NǗLYT ^R tFlF>y[~〩+=^R5_=+rH٧F(od d$VsOEi&e*9f[@FϽ@WW+4gz &KSӎݩ1m0, Lïa@*% _8P)}0 Nxߏs]_G$A)^f@=A&p\ATu)UYhN6DÑ{jK (äV]6jizI?Sf I=?ڵyp?5gV f]f8fs8S9vv{Hzꕗqԗ)I-=J+x;UqY-4pgneY ~(Lp>p4!\/l#^SM;=O1|OӮbsʾs"@;bΐ0 (Y4|O_ggpd#fd [Fz*-kT'dƀ2$;,W$}ojri|8>ˌ\"kXpO;6^yjmsW`z-V8s;@q]@~:i)<  7Y#if\g ]ځ-1siz=z#Z2 ~-[/'G#ͽ x7;Dv'1TxPJ_=`qf^"c3/9 0> OE"^) .?.f-Ν;Or@)3OGK2l8(ٚ# >Z[O)hNI ߲HJ _)}2j&$?~Lh6ˍ܊/n0>x_8Dd828…LTD̋|rRI)F?-ɖ"STwS`oώ!(HhY{:J fL13jGT4",ALAje7K\h?3ȃ[AMdd1w:s cL6GftvJ,AKLKe3/#;NZyрSlٖ}92"> ØOe]h곿3.p-ш!2sZȹF?b>)FN4W<UWBy~-QA!7)m¡e@5 [xr1dvUCi}=dɬf #5bDlMVzesm%py>G~9P[v?2&%?4 J7]MD&eå]$(ih\OA+vDe8ۿ1 X45y@Pƛ ZO Ej0 @Dfi(N>?3gŰ^!,y" ZLֈ^t7_,}#zqYDd~]6yhܿXgGOi8mz;#еcrO^tֽQ/V1SȜgEU3_ jh[99IsɆ )VW>oo6&W}jNcL?T(!z U)+W6D/ %"s5 VǮ@xcGe! "p/ >9b p4JJc'w>q؊xA,/k4"¼&RdF栎]"0?IȻf <PĵCV0Uu1 ?ާ$h#Qz8Ytu;B+((:e8 8V_cJu<% {V > .AT{< #\%_ĭ`R'5NPfLPR%N5i?/NfŒ^ѦgمP:.[Wc!ÙfwI !N,}3MMeGPEml[%J |nx%>96 I1$ Vc ^Ė}$}#p3]oȬZZ$8״jNAN{Н/4_4n{.nh'<7`? ZO҆Rj{[.iZs b]K/| ]TQ5Fڰ8XJܐ03?| ThrHp3I{,7"l34Sx)2&P?mk撴uv~]gx":w,88WNA!m3ĐM$S/(*fb̨tfAF8ߛ,vP ZǛ.MwM/ўzZz{\vF^rwqBXJVaX Yƅ:?dM[I=P>G# x{M y6Hυ,zMxsw4FoBm22;~I7JcK@n񝗁2e:{8 d`v̊H$9R6~#RtUܴ{q}4ۭzOT}bscy N܏JkGVǭsPXMG2t: 78Xs7! w`NgF)TbŨ3ДHΥwW`PMMbʏY6L-P>sVħ\Nn/d 3;%Bs !v`mk[D[< p@w"`0f}p\Yta*Q*`^jw)1-r:q{9r2_7gc@?$ڙ ElD)e: $w,Ez:j%"a1'sK2.w{|In2Y20IL,b o5o s)&`_b\e!NE~l-/qb8[؁S)$E{~7%J5{Zl d~g_& YA*"z.kݸ1߆7/!紵ˑ \M*o@~_x/?pYS꯫,$)S\#/[$4HF.:WY|.@d_:NɪdS^M%%'1jpvLK융B]?X]3Bl.c ͒7 AfsG> ,9"Qx!-8FL9ъEr#։5U'+-ġGr=k*'YrS76JW{(p+aU2oۤuz~.0>A$ dF=}[wlVK)wp\2q̩kƚĤAQH&J]+q8Pѿ4{ gJ!mkttw4&n7s6G-! 7(B͚@I`;u-Ona~8«EҔ hLUvCw cJRh%rg+'Y?8-k[J >ZPqC@6y9G4,tL?RMG ڮBʧWj3Z9LvRhR~h3_y>ۼڠ/-A9O8?my:|SO4@[FrQ/A}j26fҦL4?{6I,Sє)w\98Z辁 Lp>&[Q-C%ѪgY1.rՏrL7OV 0{cX²)SV :'3=%M0S2'Ңe+ҶD?]MEqtW[%x*N_")JA[Z. i:<ǵ^+v<>!Gvi~L| DuJʈ~J̑5jC4LP?/"ݢrrK˳hRmw۳@g9//VaNӫsRLeִdYlJ<\*,+1w@u{Cv]7 |0ɋ@Sao'Vg~@Z}3N؆WjBFL(1i5IԃwUDI~rxNRo_ˋ>9o`1Z|M~J7{wWCT|iciӹWIO7NLzni>hP䑟QoZzֈ35* @624_'in'a:כl[*ݵJn!)gAI=vcr5O$`r/l|xl+>7KM[q>A* (D@8~aeۥM3VVl*> {5 /H·- rP{(O0~Uw}q^vNs§wxXp5Cw{Ho7xKrqHy++L|n5e^MTSjTtMJ.U76wA !!qET'^y /LޑYsV; `n:pηkA=W`-~gS2@.ɶ#~g:?NG)!Rkģ^!6Pwjy=Vݡz͖UƤceWLQE$ 8)~+O$&v[x=" 4u pZcܹpYӷ$0 tú?}`ن3&B- O 2fٔ3&l~LeX4yektss%<`Ot) ;2IE]oy_XEI|j &ufT a":[Q}Y/.=cq&*a"E0}-æm^EYZ+1n:q4 X-G2IˆEA(]#e?ᓌ99_UuPzPͮt/Ӆ<&]T_YB$ݶ92\TuKIyQI]rDG/r4TL&a޻%{BSî'2hNWA&,-Dd$͎GǶkఛ/)kaxc*xĨ63%WcOMeám<3 2T)4Z؅*4TrS{|BBMT@l闵%+=PQg0%3b1޸]JuQ(Xٌ &>Ԙ]N1 |7by EF8ֳ*s} ;ߒ'DB1ˉeFdߎ)Z(7'؅>vd5B ?a1}3h7Wp4 6 fx[M|3tَVGr1bG-g @ v9>"uya|V_%K>-3TQ,ˋ1+=5pjl L҉рM~Yx2ub,70 y:2욟+1&ӀSvr"E藍!sWKۤ.~ x>}=V1hT6q̨}p xeѩ܉}R|]KBjiQiZhs 7Aޟ}ڿ%bvV9{ ݚ>sp?NdnJ]|h2ѵ/VlH|(\([3gg>ag/hcX✫fYŐ&0>zIK[7+ o}$%])'\~w hۯN@]BZ7эPs.A'c}r>#̲es} ؋>A  O+qR L?B{?H욵IJևg+1iZ*m/#gU+: ![.lxN9 ňxj*jλg&ˮ Ȩ$Jpn:8S|Lsn_bbq>bwf?g1yHOCDP ,{#Zsgֹ5ˮ!L.WvNˬN sڮS%3gPjP~+g=Xƕ4-`ߐ?5Iڳ&0@xD#v*ŐD7b{'4``ҕH,P|ĆPN(l C8$?.U'[vv!; }TEC,]X Ĺ"C[E!ir GdǍ&=#^H˘G{i9)&"F{d R"K֠~DrnF#8T kBsm$ϯPsg!PsQZmzU`0O% DYMK( xá,iF!G3'^` $:T~ =F !Crg7¶RٗhBh >IX?Dx)HIu,,jMͭ܍Ajq<'{θ n4ɴ_WzqϽsGx^mǑK{gpzhsAaE\OA5%2)3:*{|Z?ԇ`<47~"(DbPnz[5(*ƉF~u Wv{&ilW@UMjNR4%W rSWKL_Ve?QE[31 ܮتR|{_g{i)Fh]8@Hd$**P#f5V-~sQE3`BlљR|nEj;B?tqg-;Wv$U?>\}1{"ĭL>g.s'V=QfY*7UM )_Wwb<,7)7bZkn 8ǧʣ̜{4p2y B\~ʺ{{5Z5 !zuW@PL1!lTAKYQx@YmE>y$[m\ FBܠTgx)r ;ؑP#2`3@g?bRwNj\4Ja*X sbڰ=Ix< :Iac>͚+(Vb?(fr5,|[-Հhr)9}g2zXYq@FDo Xe*U_SK)/OtAryuY71\/.[kzNMFc G, .bTRS^伟M$pAɤAAǤ|Y0x 9C~iBׇ-K"[R3:tnO*a-SYsH?XȔmh3ڠ>򥇶g4x q'7c5m,C3%sqbh╏Zrr"+PKogvoe*M 0+~3:ũMMvz"TPV"7Om1)"PnǨeuu`?hʑx%dl/̓>D s!!?I&6JMR .!.:.=yp||sn|=-c}&*a{ gY)y&˲pwAfn@QD Kɲ@.1$f:P"򉝭;u+WG׼?׻ qW8̭m ol2B%zDdJ\rey;2Z>&Tޒ`&)!b(Wu7r|ǝj\! pN>=) 1p:?_͞+)|!/g>_b3x%v2"Mxf_ /Ĕ|}g )dvy~ӫ-x8%OX†FZNي6׈p}({ 6hb4"Ɛ ؏ûV  C5gA>d{EQ֠{qd?&o2O.jLPޔ sȭը2A4Ƃ̲*{PP<#iaɱs} MƤX&[ݙpξ'Q}KL\S|h} Iz&,JBGdp>:ŜJ^>P+◮pTcYp~{?|ɕ;=!0jKwA->f%0AgUη~W݀,KjxZ!AiSPmaAڊ oۛTJ F"֞7KS7-pm7C-j^ռVKy8͖VkX;n>}vYb| I)мIP jQ'?˧=Pw(vKMK4ꭦiH`ѸԪ`ߔ}<֌jk'UM/aX+TP\>k@%D3ƫz$ʍ:S2:#2}j3R ɺ/s!>T>&DtCp\o0nGKmfUA0T *KĢ44hMz㬺3ny&;)WvI9nFiD[mJag&yKRMD ͛bm{g/dBLUR!}8ײV͏ @aY> SKȀik%os=\ʧl1~6J=I9]٢⦋blsy bS׫`r*j/DL;?qFnNWj}('c3/M [MXZ Ϻٮۇ \0Z 簠ݥ@Xј bøۈN_!'K?Z]BW0*>{2C1n0c'yMhR"P0삅v,gZs ;D2o=XR5V"NzSු0vM0I0-um,]qQA$DQx#N}Ǒ -bcO4/yrq,V TAc|cx!t6?^f SW/j]רLPt`Q\SllKL.tnX\ \!iY ^!J$!#|TwEt9]"EydZolAnc!Y5EjC?))LTe!K«H9 ʳ9b㚯5?'s߅/?c4Znش}!IKS ZB eJqI4:9cM1WGttyS;IF<_}>=tm܈IPaXK_ꇐZ8Vt=V{%NenR6 r*4M: HG%HӾO>ۅ'ZXxd}#>I37 -/,XHIѡHܪ&6LcvEțDChVͯEmv"HS3 $uxIx lFh=~^aZ KPו|6lV^M#~1g3Df!e1 LZ"m!;Ɵv'>D1  ܗY+}J5Rlc_\e-d m6 B=x`5]@"O GFl+C6q \e9_?Li8Xn}.>aQؕt}ݠ ƛw`Y=PxBg_ KYxX::ou,W4Z'^"?%ޜ\|1 ~Juj ۞RbaS! J,\ZT+9kBzPYeO d [B$R>F#,} ~G"pd.OY_k>ڕhG/Vs,4h iϗ|B3p^환Sׇ4>*'Ul`əPhՠ7_TK,>&z3w|C_%-}[ \c:vM/a{\Õ<,UcJ?o2MB_x˟V$A2Ց1Lq+Kc2T}EyB%|R]D) %aXN{o&E˧9m5TZZTx?L6 Ucf-k$˽&`^KiR<0Я>[mfS_:p Pf0t0i;mJHk:A3 i_~*RAӫc8Ow0P\ב` ۤ]YET#̤(.WoȊOWHZCDLyxn?𓄇U)D%Z8'/&Գ^3OgBaYߋ wQhf3F hS{!mYْ:~@]r߯mz߳2 6wSBy#} 6'`o\J!y=%n 6Pb1P >ٙjV+nt#E衆tL`~L\hoq/ĕi) B ֑'[Nl#8G&F,.*uNĸ,J4֮\2>%6;zm}e1>jЮu%>YLX˖Q*X`rS>Y}TdJ{%ѭoLP:V)6@#c6|b4| Ϭ8?iicZ:qc7H{'EW i.J38 Fȅ^X>MutםyѾ2B6S^_AqO{,#[B%шK!;s nV,X6G{b̃;/u \?8=٨0y HDS8[ZZx<Sﹼfd8g\*|/-5ġOsXCZ/֛u=rk!6XO gu"Uywwh7U:9p r NL]M( H!$Ǫܣ \E`R#5bd<ӎ}&L=sKy+/^xs);BZ#]D# KFm D]0$@ؚ5/~:T5G((;k;eߛDv59?๯)٤7,2ThgO\"v4FD{^SR<%ܹD޻zDΗQګ>xm2+f`D}M70&E*pN,_5JMs_oV谎3|lF4&NފOB\6$ҁBq/>ndӪ\?{ WC$Am堿5|N:EZq X}7mC~!IOc;$³;BY*61Ӝ@[z6En3=t.ChkVQ6V,Ịݤ _%"= *..G6nho z&e= riyF>TsyWVu#,RIE:hV +=-R9,jkÞyBftà"' 3so/$2C rF4wW}qf&p.!ghsŬvWYžÌ'@9gRzR$ReXE5+ +W%ԟч<~l6'_n+?= yP 6iCc>t%}_ DkwqU} j(YQ-Yd 80kmz.,^kqPAu킨HЧ W;ޛFAЦ iX%ƙvZ>P~[ymf\Yq rlRS>pef#j L]vْFjqӽ]ʦ dX 8Ѳmb|2,orUWw|XK{V.+ aA±8rl>|ݏ K_+ Fh[ӧ2,֢$DEKy,-~R3 f`*~C+ZE'?ѓ*&Ȫ 5[?a qg6ZsT@>*wg[R2519 />dn_y>o05//I5B*1h'(Qal{kGE(=R=AWƒe:`2]ln_#I0-.8}Ƥ0t]CPrZ+f>cV҅t@M+W!ѥnIB2vW5瞒׏^a2&ض|;yPD|ɲdJh/ԫGe]@pFDo17)L$ZTTm?ʕN`xfa1jV"HG&|Cd-g7W~2XbLYWw\O<@O.a9SoD:\kog۴7D١M%M-mKl$.Ux6FNS;͙Jez{+0t4k}#r\gq=ێ! R 豝BDo-bw~.j}J01Ը"kǃc _AL!Ey0_)=!wZ#$3F̛ė)lRYm+)Q\ojWY,is9o*pK4b^c-XW]\YlydɞTe|a߮5:Kd^`s V)CdtS?[ WV`e.k.IG|b&ɴYPZ&fb[&s=RU4 S17!҂0OV1/HR<4C^ 1d^>m 3SoUpy.x{\f1AAX9<,޴o_~cp~g`h:sŝ7[h q^S;yf>mA=.EG\X"Ok(`C)ojD  Eu\-Agdp yLtD`TM)_O&>0]oU3t]a_R_6VxQ3%,`}HOJahCXbp+ӳ&/4Z&TQua ƒ`Y)Oiyo҆ MBo_6LVF_Gù7R}[*='RLu9lM^|y,qS_ 7njt *s8oX?Ŕtִa2lNdCbVGtGm#?/r,J2m"vS.ntШh/_9lwJ>!vbu,rb"AdkkA47JvĈQ xJqSԺAS/WB)6,ҔT,}(Ow|94!ˤQC&$&[*q֪(}rtL"t I6N]b)V_ bѳq|uťy^ MgƧB"ƪU?ǻGD*Βtn}6|Lh]0Oj]ߘ9`WHq@jͺ*b-3g?,*_РPNQ+!=8;#ݝި 'JX5|/ `NTiK̹Ey{ 2%JnI%X3Qzw=C2l'm^GT8\*ha6^2=I*|6b.k:.pdZ2{1\yY@(Ng9P<2پoē?*gs348,NReW59%'|JI -:4Ԁ3dF[cWju2&̒ϡ^*3g/MԐ]M |XL2*2ޭ,6i|17TL#ޥ3OyF~#r9ȁA5]솞#Rs롲؟T+.,% H j!V#.3SGRΔYT/xA= Y\^wl]6HeAy' ȉuWqެJi4A;aI^eOqT>sϾv v@7Teݹ'@S1ѯUǨp'хDLGU4<8lG=5B4'tX1%xLꙉ 2g^j;PI{I+ }`zŰc#YM.|ȷg{L{ˣбZDgP/p(_;w at2Dq uӯqT IKz-!F *qkz0%+Is^"w KEB\#<Ҩ$Dn U24]~9Zљ,OV]#Ǐn0!~(pTY7u:c?@eg/Oȡ ċ{ظ,Eqk< 2%Qm1:QӄjpV %U8TPr?AA7o߈%[OeKqh#3/c7.mnH:a/|A}foG̃x%ld/Li.R R,jLyP -rxٛwE.Pi@.Bl'r_Sr! YN1 Dz^sj!|,Aa |sA/&_ bj0K5Y3,0Te]2%K)Yr湳͋%ZV( Y̠ As{Nƾ%1"q>8fQ'n$uw (  2C9nP mf})J1.(>e |WrŅגס[ʬЎ;tJ˄)b@ΧNC`'Inm+Dh0^vMHTh<{'a{_ϧ֡?qM+v$AoEQ;y/* ϗf续 Y߬ 7$\2{1ܚ3p:vhw>IX{ ְs aN=*=N2i74Fdpѐr}54?ZgSI?>b 8 P١n:~00!n"&Os L‣CDKo¥9E;oԻ(7TJ5"}{},j,;ُƙ֞BXϊܕ|w,Jڢ kaMET 4ȸw"szA_:i PϱИV޽=>h-x}pqWEѿ0 ʌ'L oS%XMgEwFSuG|4T R1X|upP ?N˗Eg.c([y?D@QyW˖&@Xu{rAmBfҕ6H}QMAaI%]\ Yzr幱T)TPUۙЋvb2r6/h<TFkGS.n=aX5y.ň;ql%Db'+R'8 {Sa*"iOQU;!{&8i0{ɜBl6MctA))Omf`Ћf6Pm=pal豤B"@jÅrd7vDe9,=d$^OqHՇ{bC'SGGArc͝Q"C7>wj@;х{v-[M[( dn_>S6֧ /a\'50P'G?(R Rd4-3]D3;!`S.J]Ǐۗc){ɹ< `ҷc5Ķcc(86e ?oְL:ᒼNO!M/S3 .m W%̇#cF֣ԥC始"ɊPdHCdipjWo2b.|A gCqeit3QlK2A&[`g^Tjd萱0k.4ݢp )=Pbl]I0$38-0I>H)X2{PqBL X# ajWDMtٙ p? y\GtNI໎uuFm& lL{.;$F(&g <-f=m՟p//=$@_esz|'$6^^€)\U-X@9.2/BcDa?:HRYk} 2Va9L&Pp YdDJ~3jx߉^cNo4T#vw:ҧ:(j<`9JK:43%!'Uzq枾A\T|2{$Z(A:& O˭ha|?}X$-P#Flf Y/޽QsI+|ұ~c%ʰz>*]hu|5=jr]y!)3B[)Y.Vg-'g;!oP='.(- -&ߴlS ,(5$ n{Ԣ,,FW V r-~ImM}ՖvC+Nv E9REFnaic_A^, d쭱K$qPO)Ê v|U`x\ЂF;:ev{CJi|-O g>}?i(l]߭%b)_"+fuw5DTxlT)2l,Xۈu:Y\~ilcWlQw$JۼiKqolPYУv ꎹSAk<Wp3 P'4y dQ'@h{K h?2|y)렰Td]Dz}_^s&tk48HIkYa[/([M?oh7o3I";`(JޚBKV6X 8bĥ cΤ!D3W=9yųfJ_*%g̐(`XE{_L4}oe|0p\AaG"Yq~sE`o:keIa 5 K?ܺJᚊH} X6Vpst&| ?ιWn :|yN%՘I5}e~/bOŊAf ӵﵹ%Mn0k ZZw=[u 4~d |gMP()ҡɯ bEY\/2#M^bB ZjhDbx FbՉ)gM*¡;a P.BU5uA }.:JjiA-]!}b#d#hFơDài獆/b5 !#N4ܘPu6 /`!oi0B/p/b cfb'oM ~dxaw hǣq1Qj%:?MW6\WSiK0m)ECxzc{(YY`mqE`) )0ۋ]۵u WM,RL~3Vg`a*82ɑʂ2l05:uv7 ˂h%T̔*)@F[(8D `yhUx\+z{_DR/(Px&@\|CTZ<# M>wiX6@~,3NhLeuCF׏ ځY{tV5BLNGdt\ z {KXDOq3@Ye[w;ıeZ=~Oz_""Ydy~"5s7Xy~/I A:\EkX%3LQqixtRN v ԅ~Z~]KwR{XoX#@Ο5 ׃*dβ6dV8$o܏)ǙAAbS6PvHGץuE{y$ )NRdD1ѼA~ã.Z*P8sc`Vsޏ-ߝ`֯mfGr7wL"y7sqN@PYt=yLo}Šq}$hdiI /YlnhFd% Q+!z%FR<:|2gi(c MdĵE~#G|$1$(=CV< b 0vEFP,ZX] w%Dk.Н@s;N7:,`,HL GZN=gN󀯋V宜~5 2`9W3 (eD`Xl>8D*%cSav)h< tJ~HKDyțK}*Q|}b'Zz;' lN]{*hQ^`kqWVonĜw?huI{"R vY tG4"1vj2L"KG  so Lb(VoĔ JyUM^2繻AuϽK9}\#E2ghS%g!{}W{^ׇڦ@?&86}0Kd(|]V4)A:l('jK>a>somtsE]hcOxyoU}=Wڭ<\cV3-|vc+ݱ[|? ӵx|dtݨh}?{ J}_JEU5f=Pc18qҒ]v69J<0!g.S<k5xf U-u&Aq0/|${3{`6xXȲWW*6 <-zkaT͂gPF [qMvwn OewX($"Su1p7J"p&!V_^]N]01 ?T{eznHۮu+##ȗ`AO91m fx7ۜȕ d:wO|nRթE ެRS#GT9Fj #٩aFXДk}d1mC'1Skb@Rt*ICi2hoE2a߿azi6$O4ejbBylX ^9ḇ{v9/: 5<4P ^nv&BMc1ދ;[6‡pıYK$]_A2!|}B)vҫ{:'@]w6w<+iPo:oލ-}HFit2o‘AY !n6n +zΔ$ ܸ8%hdBqĝ%l|TsG&7' JXtȖNj/(;DK7$Y6ggn>wuo_DqD5[Jq|CG>ih\vfz6oWCs |Άn׸3U8eh't,S%Ip`LV ^i`AD(d9_ܶ8<țOڲ+vtuAbP9 W-.bp84ǧN_uڙ8Gpyջ.=`QU`IJP)Pޕ@Q?@&o|8&Eqw*>ꎗ/ c $hItwbll0M!rK;4čmxXIQQ{*1#7P鉖eydMHiu ._ {JR3-"$XYEKWf$ ۸Gv.ee}mx >`_^Yѭ>P;Vu|j,tDE jp\'W⃱nqpf{9 Z9I]'ץ˵Q' aX+~zj∢IXӲfңguQ?w?W;i/=#e |ul^ܻO&op1nG _D%V#&ٟKa";6[XeT+`yqж&| % W/#p̿,uk ˡL{h^19g| Lʋ9daUwv _7wA&ݹ_/r6El7%+|Vql "#P.c[vR;,pUKO1̢0z?[gvI0MH`2֍7i]-÷v:%Ό.j K5MB(8*J%bh='C_ha HNoV1v~>^9K6KoDfg06,wjR;2F;Ml[ZŧM}6M8{Z>~ G64?'^pV rIڝ4)E~I֑(t羵h݈=(^))JhNk^SYJ=oMWG7FZ5/xF49ZHݣm[җ=~Ҿ&?9 "YT&g 9ܺa++П~vT-hO _YRMTh(mKlԂnu;&^MFx%dV(913z27LC~CldpAK4X_,Ϟ$S7 fe'g9v"(Nm @K(hI =wC|dM+_GGT~۪si ;ԃ>k + ȺCE?k^_`5}v{~Qn{nMӿ Pen$gྜzrHZu |6>BfϖńwD8S`pTZ'G*|zO&JVw*=$e ש88@?d{?^,6=OR}8%&~3@󀉟sN+Y4~)҃:LoJͽct;"3 ?7:ј62gmձ٤l4s #'AM=[M9ht3hWa=mΔviNhS]q[P6;ѕQ̳-FmaJ:Q%|UKq<-N1R=~ChN' ɳ?e3YBLlk1l@Շ8\ҐmWUkoPOSy0Kt Z:S.dV~;eyK z1\\$^RAZTՙYVœbGHpS /K_ <z9%C=jCQr(oM(E`'5|cM9ևGr,zd_o1]X)j7VErwGgpT9*Άe/iR˭ q]{nPpӾ'y~Hq' H-Ձ&h {[pΆ\Avw)%9Gdw kWO-3DsQ9'zauU AU='J.Ł[@I)@[Qj3y/d->[<=o|OR4v_u?(gùnC7Eu2Q`$D 7 #$?Vj#1ٸƆ6]QSj?[P܂ I 7kakq ɠN`Cwdp~1 @A6J_fQtRG=4bQ]{s-O#e& T*)԰ojCJkV;9 7,-jg(=BlMk;ya҉/\%+7 _C9nUVA!9I\^1s},lQN{.<-)c{t9Ht,;<Ï!Ӄ󨇽ŁȽإR4+V9~sb1 )f dcx DWa!;zk3ie*;.{$},p$r(2pje<pZWT[Cc6L>t_O+Qh .Z[lPzPF0yV,6b?筋w\+x%:%R7!mڞ7-af$Zc'i\3H(ar{&mk' .#oDf}Dbc≛hzˍ[݆ a>{a0o I}VbA)F7rΐұVm(XwR{&̡-NYn3u;i\k ;p&o]]Pbov`&=iޞt$kN T±x~I&H +[OgK} _SID$ĦB3 1;s_FmvTOtWMO:ga$I)QאX{F91!~i<֍~88q K20+/дXQv"=&ZniH=2Miv~! nx\͡qc4gK)bÅ[rSv<’n,MeLewe56?"՜"8$+xz uTJk̢dPL#著̟1|_f%g"c_CU#}A+ҭӼ/J-wi!rq[]N-aQ̌ǷҌ Q `%zü>4tdBG{.e ~b25|5\+ kbi2|U Bh@-!ڎJ f_ABox9nuV?^hJÍ xƱf0j#]NJSJ0_< wjV 'X4JgcDD+T68fk=*V D"??ڪ}O sAkhY>3 w] +b55d]zbﬠI'P 8R!F'Ȇ[e|kL`k҂Z? >I|Gtl9_#z]+ר5@ؾ7 MF^jYZ6_%KnU /)-}-8kOҩ_DzXN}]8v]MAS}Sj*kFdϞS#&;lt7st+mqsH p{zLLNf '~F>-XnNfJK.-NuAДzB'gheK]B&s% L#b rA[{TBPj|%ٖOhM <> F /6yxT r{ırh# )ag/N/J&&P{T埐,I?Ce$\ʅ_-!Pg:z2ƏΘ@v^>L!e9a7b:,gxp=dTQzOW>{pJƘ?R˾vPMBw{]2 tTq[getbXwڝҳ~L{#$?I&~8w;\2]^Qنs]򒰃,rCImbGI^VEj$T[Q27E龳DwD=e:wőA\`Ӣ! $>uJXZt$4u?K(X)wRrIC+ <z6N]m1.T]c_*#}Wp rJ-TҴ::8t>504"F$&&Υ:ݍ$  W#PIB'vVV5g1zY hF0Ğn:c޻V3k-gWbqwoQ:{iGP.tcVt \GTXkw:UY̴r>9LJaXX59oۦtۜ(zi2%WL{^ii9˽.Q]m㾦O3G_ 葠NƸaZ79P>+u[156:y`qz59N d=2t^Y>;~Z)ּjbAS{e֓8A2o}S{R#r:a{Q?%4ki-ǃJ1RQƅC=1%f+t2|'Gis%2̑!?Bp/kU[3 Y7=l^ЪxƯIKn*gvEj9k]_W47͎c+F~ ˤ0ΰBrP@)HU:=ĝS.g}JUE5/(~SA2|o3TA^|}v+V9v {gJQBVNYGY \◂7<D 3`A}_yJ5-~^_„q6Ba22w݀Me ?5.;8q$q[э)3FˌaivORP3_ZĠ1_ 2_₣WX5ײ8T)$3[ Moj+ }1!TiQU]X[gͷ˵AfQ"1h!-}Tj ,PQhw{A$lCPk$'X`-,UP {/,`'Fj $d}aƔcԿw@>]J3m;>M*F*bvʋ 0~$T.zSHM-g-!kX/Cn+6{#Ud@l(KNC]SpܩAX*aWai~ GmJFR@)+m\6 ;l\!#@*~)^HСk jWnɋP:W姄J/!yG0ZL;xs^§=j]{y+HhHza6p0O+4,'G hc%A ^Cߪα5F뾳r8ꭦ<(+ѨkJtǕVv9X?]}PsyfmS+?x,e3袈ohʣ"12KTq9)ژWiλT~;$ũlG Ց-p!\1%8}0I}R@:v[Q [&i%;5Y0j&UĦ&4ø{FfB̔}׭/]\7?Em7nQ'ͳ/hV(2SZī<7ui6Я.46?$m=D\0nsq3Or" 麇+qGɇ!%D'O=y0$pHhrENBp)t[ &>VtwK=Kv +oqFػ{F8Ja`*'Ƀ(,8% G~?ɣwG8/sٸK, |_%EhȝzHQRLL"M CvU-kG/,s; .蹳9o1AQZU,z(:ݠf34ZYo!ԉ'5_jƘWkԮ]CѸz)f1sxp=q5J FU)=RitE ׈Ren%!%{!BC]F -Kxb.r3 b. {";ByS|`\>lke &=#w:=ڝ܋j^?mH9׌}_Il,E<2/VulwMss"@a -O{1tGDmp*s#>"33"\+Se!vV৵z*w}oRW%?$:\tĚFn<}%|k866 ,mŽMNN_c$="+/[g# nZu+A6eNJ/e>9 B([Q/ h.f5S1>5AODФO-vbި&7D_tI}Z Wqp@)H ?,y8n+F"f#Ý*nf@i'iCiGt#KflFM  Ni2=G]sewz~wt*g 7M#mF!uݍXoG!,|t!$''AXHVc.Pt@/_.A| e &,2GvB l~%Y@ 9)/7QO NAe7DW!]Y{E>} "̕ A?rڴA"]c.xxNW%Li)2[Ry$ P+)fW Lx"[aM'2!̞8=3[-i)&[08yϛm7,~ ׆IxQt0n}3uA>vICU WZmt؍*ϝ Ou\zSSH7= ر-WUm z5hhjA@%Iἦri2K %)Jn$p[S00;cjDUxQPH"껽Պ6}@IY `u+Z^'y\w*O$BoT9^L50sfPP"XS_Pe[wߛFd^#)>ߘxj4A 5?-@cAAR;0λZ[dO({a 9!ET_Uo{efvdh&-JK-,Άz3mrm' ABt?w깼q{7.!k캔n1~GI\~ŝfcEi WFa/-FVNFs^Jm'Z9tEoq`:WxsQ]jsõ8Sj۷L@)m%.+58q A.kZȦ$ WVL%u-T~wβhQ. o++2. pZǭCd흁dV/p2ul{¥wHuHK^1 9ygltCq'~s͕[f-B$s|,ϼƒ?n/;$4B*Gܮx Pr_ޟ :J~$+ڿGp1 TQrFO]]X-|\kf jVAe(JZǏ`Ǔ 2mSQJ>dp- b +:8?6E_ӁNd=Bdo6__"FT3b"bFŁ"/DE_̫3n5yZl0)XwŒB'~"x2}N{yHC'-+"JAJ]. W:m5U_9~B,+Fq`P5K(߹2kݜcd9!r@UKQxᗅb|_OgZ9Uv= WtzrY|Mx0K#rA4\dy2qE +7 p,"$!V&m89?T,D7q+H ^k pRej!Z)؇0O J2=߬1<'ebLle ([-)ۨ)W]9813nw j'J!p)vTes!Eh* 2 4N|`kWjKDPcmJ. ( ?eT8/-Tgїq{<N~\(y¾vJo~gYK: Q>S;$vu >Ե-D.Htvefm}ћ*Gp})= 0 B9U02O S-8EM/9>y %b5EM$CG~G5%Sm}6$ĵxlqw`$s͂Bw 9;X̙b\rƽOe&ooȁ0X9ge0"0UX*C-rnI@%c+B9e|7lJrm(AӳYy On= {Z5yqv}oŬ -jݙ[V }Q`I}ɃkzΖKp@C8?l٦xi?X/:*' ,pV^C 5*u@6a} K"vEPk$GS|_4uxRf'f5 ym0Y?\Fj[,v2 LB7$ |5.孠Ðj3V@p7ݬ޷F.L } C S'§uL7'_SVA J` ׄX'|Tʛڜ&uZCHȂğn7smH~&[Xm32x8Ndc# ǴۓGokI9q.3nKJxY7֭Sw E)pزhȑ[c9,~&DN+_KȃYJ0aȶzbH@½xތtת)NtW_8' WFY f>T2$U8ru N̹DloZӰ!F'N {5܏%}6R#DIuP43Ց+ =OX@-^|N˫Ȩ'+[J][4RIݵ%`1A$ͣʟ`e?!q tb~!Xhc, V$Zpi]t(|3Hw>ыѮc:w%~rU*ؽ8qDY &ۺ,|9ԝ2[~xVZo` }ieWpoEKDu7 i(hVrYW-f@4<=MHO‡K?t?Y@Je3,Jr[{~SiݥeQ[ic "w?0د\:d\2͟<477J۩؅AQ1:; wm>n⌺iO/;gG\3Mw 3QaM\yTQ3"TD wY}ˋ~[&?\VJNUdWeAǨwςxe YEP; ,qm?.HF{NwۂQ}`U˺Gz]WA^iόfV7,"+H/̠l떻v9{t!2S?;c,cnPvf_<ıؚ |#dutnJޙw}KsmUbY}.pJmW~ ~DMBtDpP:`Y+`(A.bBʚ9 .$x#LEeq2H~ K&qgg](1\}!:Ha oeٞ*3?GuTA [f fx*O ;H|uW(#9!m`I_W0Ԩ|| 0k-LŴ$+ \X¬-*jI 45%tNG9?v;ASe⺉@~tᐌ<}(Fp$h1[zf R1"J?eN5 BKx?Wpְ7=-8\I:3Jo(ynܨqkA͆7S+6H8d%_A/ylni?\j |?ǵf(SFݧOKqW~";@p4/iONԍ8d8vۉƖf1@tUZ 䈟}/A1( XJp{ۡ'd6xo Fhb9#FS)jHӇޜ"z>\cq*9-nMΰW_};d2~|zWEu5o$~|;ml l쾃>xV4|-)%E@$ ){iif(x/'߰"U8)&E9Բa*KlS*jVyUe.'࢞Q ߒ{aI,^EX^^jJwqmi(3ou$o7*/2hRjf٬m0a٢Um[[ϖe !RQY_-%/Z5eڙVæi.'Z,E w5Xji2<`:*Mrnθ6>goۚiaG@PivHRN(x!_ʺH> \Ґ$SfR{d*ԓQ6̘^.,·6::cu6G(aG'HօoUTbӵg*ڰCPRQ gPR+vQ{ [@僰ΕGOsU(;r;$l 8l=f|}"͊0J6F %lZKi|cfR-r$pw #L&>>QkF-@/^J3X ֊O~ȻߡV#^jG0PE)}E%cpbwoC*-7|"BK0X6B|W$?EOzfcFyN>}qwj~f|DܩHO2?zthk9(;,%檏ۂ~6(+ō-Q#yd[Puv{1Uvrޱ^`2>*)XeW.-Z:YN5yߘȃBڍm4w:2?aBvbV,)= zaQgMt`ܺVhl0lj\wcZ=ߞ<`os(^n53C.58{[z>n(E o9HrHdϡrO+Q]ftƳ¯|aay!w͢E$Y﷿K2E-slF viɐx~0oTYKn0N/ɆBXlR2mWB.jIcd>#ZyB7&FE~8Ovew?Px&M RWP5=,8X)bMǧ7²ݩVgo"7ȉ6ROoe3hBnxeO*[q-u+VjCFN: |d*&,, *AZ!Hk(M;N ⃓Ved*^1a{Gج{ywM[KL]_H_ h"H u+$'hS3Na%Z2 *Sybu-!6ڿ^/X`}u΍7'݊m=cd_Lӣ֤|]HaӊK6j˫T6!>22ߒ˚C- (~R)t.64LwGXL_Lrdf{4%-J1, }޻ӦzEjp|q.iNqshpy=e \hPui)$3t]WENYqgsk T=2ة,"fHXKLO,]^Y]sUCٷfؠa-2*dja4|Bg\ Zƿ&,bʙ`dgY>~5,(? ~WS\CfU8 2I!.jCC&+33bE?v 7\cclVP:[Ն VSo>!2<$>l][nc'OfK)Pz\pMmT~82xƨ"3qx&Ǟ:RI4M߯LoH)Ja\MRr#:c3qC"Fu n廤]GUgY-kA$$Vizp@*,"Sٚ5,e".Ns;ԦS []G)=s Z]^J ohsuuSb /d۫ Sowz 8-tP;L0-Ӏ:\uBVK6?7>0-!jzQeWscyDFfC7?wsܔiol;iўLFi> gsWb3 J;neݵ~9 qi/}; "(E-uX$ 4EL+ o}אH5ysst˾To**욾Uܘĸ?" tw#<6)[rUz t_%\Ex(fBh8:̤-Z2+\,$ɑ9n1>:M )Nh=>?FZRڬ%ihy.rkH*''lXW()Llج3vтO@(]#9lZ# tS8\ Ot|,3ǪpiF0Ap}.CMWYhF}FuA ?Fe}kFhU`񠙉RGJ%=]mjv_9и~FE?W @I7tsS$g}l jj"\MȒ5ɮ m;wP$O%5p t;XOγ^;Їc!E 4_& Q5F]u ,NJbdnD1S?V#Xm2YV$[g2'gqw})qC1>>AO sܠbsfqv9ࣟ}d5&`FwM ` Mr*F\#. #^T\^gmw.c8{B) ƏϏu*rp1B|[og Xkl >ٟQ_{W,H)|iGŴI!_޺oUgv*VX [OkO?!#C(˿!䤹Y#S@ʁxlzvP Ώj3m gƗߪ礶?'sAƂ;x!lC;ffp{%VKR l PyPQ~8s:2vU](=pH&B8%v(Tт!*:r{6\jߥ 5T{cG ɉ|d+c}~1>Se4j[&3E wl& x.92?y}?sR-YܐJW=JcmӒ/W>Ωǵ*"ѐmwhgŢ?ܝ. ݣHwpd-7N G8}/aޓKx1 Jf^t(|#6]2KwLDI0ދݗ&F %#]gQy|kn {hXՓ{OO#WF9tBWꑭ⡆cLC'>}r@ֻw ELV2z8ı+>S  pTpSF:f$t姍N gU70S$;4K`)++#rНTĮVP1?H:0C 6xEg[RTqS2;/`Al/&=BSl9jyBz;CP_[JwJzVez*zWK:!%] D]"ӳR&@~c9`nQtΉe10M Y"FeB\X㣩2өgjA:fJSSE:%i|?wA0Ġ!?/h9 l>FsϘ0vZkalR{ QNnѰ;D$Jk9* F@dXs8[(Wwn@D~?yQWaU'AzD d!l(e |R01d/# ?{;Xɢr5;ڻOfj%s<I=M.{øF>Ʋg%~-Oaji&9{h X{16qz;( *}YRwܞ 38}IFKp_儠0,FMsOg<+ۄ/߼ZF.3 [ez6ٺ "_!Xbڙݷ(}!1-{&M<^+jd`.;\~}eծ!Z+=:"ްQu1w>>^ "+:yvKY?5&H[>*(j%zZ۳9\[٭fX7i$ǵz d[/]#֬x d\s'U?B L?UpR[7S=qg4nsm J jAs?'ԫl0w¸ Rқ?U܉gGG3bCwUꒃ׈."Yvg? +u]Ea#&CP?i,ppxdT;L!]_f$:-vs7¦~of\$"tbDBè).K˭Shܲ^/Br5kC-sΆn M8Mk :^t i6]VxA.]͘v$2,1.MG8E(d?h%qM5gCq!bzATw̭ABJ_(줨6QIQ}0l`Ge%EJ9_h|k it% n0Wv"o![i8Kvm:/l{T2E.9l2ɤMz"@do`Ge%EJe;))`}3?iV|ROrf?@]8sM39 #ܳjv2+~fyӑmvw`:PH&z^ i/&0x9H.07@<+$S$$EvОлZ@~XUL}i/k)ܿjq42PրлJb;d2iZ4&c3<7 vMI艭L#))1A?Q1y@TJO&)*5DHLэm:gF)^cY\{eo誯!#< dx8}+7FU4`2 LLK(Mƻ^A:?ٝO nl隚ъ.ҜL$2<}9EySvӌO ~,scTZȄ)KC]p̐\cRM^F#4+6YHSk%n"Y9@MaU_JY޸o(scł{uѧF }+u 2_T24ߘq_=>?v߻@WSqUGiGs"}w%>$Wtu=n {%bǬӤYAxj*b0UD7f* _q"G6\'{0 ,1x:8><z``_Ǣ7;}E UIR㘗<abE#xZ~AkV'M`rk^n5[4Oq,1M=0"?[-10(:=qxR|M%dIL-F F5~?A;]/բ^(1-K3Ȣlfbfb 3 G"0YXQGcвGuMo:dJ=IP4+i&K.C/~]l"SSX1M9s,Z.(ɴ~m헌]UsBEWƊyFj$4Ǔf)`WSxu}>~(UM^cHju*[@% ԅpa!{L94b)X>M=9(],I\D3B"2ҩX&VbEN3cTY.FfA)&>[*ZJR+l] ֑u+Eml{ZJP&t=O_]CK%&[ z6Xqt4OA&)cV9#ʡ'LbLmnAiTl[yhޓPGV(50=fvaGBˮwAOf9\ ? Z&ǸsjXcy{]!$g2'ٹ"N \T3˻QNUO:C?#^U 5L^N n#`㣩,suM#&UbPo?VMG]H3:ĝר%5lKˇz*[10F}'#m2p=\D3B">1h#~O"#8  X<~[@4R˿+@Y 淟N$7]U]PT5H Dp0z8Xh7Dkwל֖?0Nδm&qS;hHteZ9O#Mqk|1΋y:H,)Yhj6}zqD0]sB^Z+.kd@g}'LF4[IHC7yezv9friFA o}26&[$&+W+>Z9cXlVMuY +Mxa? Ѯ gf lnLy6[i(V?Kq =$wxIX'J#'E@[([qf֠ q|M?69mB~\\Hp{H7KmyLF*u{fMrPET}cKrWÙٱ5%J;.i#  B 2VgTLCe0͖pn} |4Y.p<3#㦢l_o)s^lDh˳iнroCs0pA\YbO羐JOrf?c ǣ;p3X^5yN{TIBPX4E\K;R[A-U-C:{ӈ" Xm.U1pDMvNu,#!R9G^5}4)Rޔd .FdXzë6KZKIЎВqꗊ;CBQiP)1fWm+]#.nOQh7Z gNzzcX衆8< NT]7*Qĵqg=]}UFQ;l%Me!YH-^4cbl߸)|ʶ.u~&Yb xqJZoIbyP}Nu0Vz*E/?uenK%ZMHH!Mٶe4ĢSTO˱ 5N]dR`}މPe:ſ?DHIL#m\}][G=*^ez*Z"Ol͚Y`Z3cck[SZ(!"ƾq U'gXv ]ˀԉ A5d-BNbJoNC?$j(G ʅ$|c3δKB+t0CQ#6INF7L/'"iAb\uà=vRmktz,+dfoG2q8zGi]c,+ +^ TOiP# wܢ(nExf \ЧmƚzhLgb?kXvƱ^l<[>rqXuwIcJH c*(f::aaXKFԡG΃z&jc,m6km8E]YlAAR&Qu fRq/)zqvsF1WQcI?(k嵯:(gho.Iָy8ݮjz_dMS|sB=AG#9rΟj`;y;X5*Є cNxwP;66,;PJӉ^3*щW]sG>x2):i-} |d7T٨G-Tލ(%qN5=qqqpujMj]@~.sI_GʔеɚEҥ>H3:_ ?RK yo6TQ%yýr_-u#UqGAmefuf @ܟMֽܷmhk [>,`6Q;d)h95IPB4Uk"Kle- u2B nQi%EJjG6KG9bʷrmbwFDQ3X12~Ik^XOa/wȴHl 2-7Y),/ƹZ syR߉iX'#"!V5~U7zjxxK3&OCa՗U~ʖ_tE TۻZ;3.['ߣ?TZOAT&a:GIp¾eB.ũg>-qD"l*U{G( $;I.'N9(mwqzǍՆGE!&ռ F@?|<lF.-L T4p{~[JCxx<+a); r`˳q""P;Z֚++W Sp2*q<2r4Szꉢ Z*mȯ PMrU_K^8sr.+֩*Xx~Yd7 Sⷫ`K|m~k yX :kFm[C{6󁷳y"0(!M0t)ݸLyW8TzI.FmBY *lD17]X*PohG}ʩ0Bad^Uk{TZґgI }n\w%ѐ)*QڹFriS=wOȟ:=a~?'::7TijtO9G/0] ʍo C}QKZ fuH T[X4 %I1٠yY+A٨a9DP7'quw-sPETaoe09|0ѕ62&q_=!-=CՈI1?WR&ǪtSӯbk}9Yӕ3S]ɜ@+CC TJ(4r'_ۢ^HzNC4Q1*E~'ٓ[Kâ^$&ezhƋrzi63f#@V,.M=U]}a֋Ro7MINLTuj+.-"0)| Z1|2P>NCk3i {`mqjF;Œ(2ob gT_'0\N-irr]&v ]]9s̄ خY,%:gQXϡ;ï%D#2e>)`sl \ *"J<* c Ρӯ?XR1* pOM 1]aѓƯ|RW'=.cGwgv}^Oa &.0qMwӂeIGެG-DJ40j!Uď=26[g vsmXa~yTJ&H_ F}v/'U:K\!4"/ cx{%8}o1KO"&?Rj1.x&ÔgFSX Ϋ&ʗ VS^0f[0;ʹ>|!:7 "E8ܱλ%;Xr{7˟;~މ*xc?Ho hם Nլ,{̀52^쐺 Գ< a81jkF*o s<|# Tv= .w]>H ?VrݏVD/"ŗ?"UGw Jab-ZmINnlH}3d&_.b Gt[zIb؇fK#T洶PE5\9ɻ:=w8ST*i0[59v@~Z doR2clbW8?6l(jxoPt;/1fJY`Tn5AL!/,%z $Tti?> O(c yVBΎlϕ$ ^ѝ*KPPN|rCaޑe[|Fw&!AzX׀Pr}֐&!V L3i80kL-Hة+IG"ۅwDZn]]߁tqCcS1M3SZ1U3}SS7R鄆Vǖ/?HO1u;zq `OZ).C!2|0QbCrj]ɪ@PE-#qMsSM vQ?,/J \U9L\A%T@;'" @<}|!,33n[饠\PIvym|yB=ޮiTInVx~EH+KVc+iT桪 cNpFnMRA6] MZWw uߦ yGӿ U\\WewH ߊ4Cʖs)֕:a{;9QeOgPP3˰⛄gд*T͚|e'S;L$2/ic&E6~tqɧ5%R%I1٠yY+A٨a9DP7&ZU1ҌLK0 6~b"?Kta9\މ}:X g;[^7UПݦDi*Obot`z!؂IWjT2J =zG뉽MZZob\ÍClKpڤD M"*>U? u$>0jj,ʅFN,߰L ]q$2ԥ2L<)HF/C#]3AŚ]c (@qP! P[ 0`yc×MtB3bjJv ЮۈzϨ.dOTP N #{r˹D&c녧@ PWR֥&2ƒMLsT]KH'qu>.gr>Gf,JelV *;6ٗOidIyv ?v||@~=SoA'N"p6.X(@.4+0Rb}cm{MV4sPK@A/T֍uRMH*KCp!Z>'שAce4%%RᖸD w0B~cE=4D3wJvWxXch&?*HU^T O@gnc|8i*,#\`aP."m]gDOc3DQj.Dהn2w&]͖S/:ɍ_dKGdNuϑǢɞyoY$oྷEӸtBT-HHwWK.Pf*xP2,UBeQn+D HXl5F&=9't !׎hӫp͍e6/;r~K W3BA}|iIDrqoD9V"}&C;YAPWЕɱ.HPXJ^{Y7FOQ"2Z} S2ng\)P5Vdz,WJ"7ě><דG7&[*PHWSO΁%tȤhJM~8'hBcB4DQ@|Q75JYi6nIh<%J;a}#QGDz2"f6C?$j(G ʹ-y^k(+9#$U/fF=c9,gN@vԵIϤ`b>ܕdhzH>H&m0wڢ[Q\~/h@{W 3{!xtۧRW@ZvWHՄm\뎷pY 9.FI14EK.! "E٦ΒDAb.G"󮡡W(=ӡGX~Vn\@웹 Pi aplv-hN|W(|Vm3YؗD=8yeX c䮾iFdB}q7o^5o]#^߂7F=? w_FVEzL7KӢјT+.z%SE^ѳFv[iLfQ{P?ya؂ǚ€Qz+9'jf8/ F`-TASw,u.brA4mKTJ/m^Ek։U}fk&zL7hfUmy*0f'JژQ&guM{=:2f:p;`[]_Ԑ ܨ ѽI!TI O@ʵZ1TԤ2BZoy jRTfq=3B[ z'V٤5xUļb?s69s v@ '}Z UMM\QS~8 T "#nT–HAiDהush[ѤP \v>EǕ Tl{8ZwϗSnu@l8{>&|O|N6oq%l>OF}ҧ{>B̻4+[WYQ>nƆMs:O`tcL^I >z XN>鳮HE#=̻~r#'Mjh FW.J"VKr姰ȉ '$4{Ɔ9u%|T1U[5Dث2!_Di*P[o\&vZJ9vEĴͷFX>S.%ՖBvo~|ܜ@Z%h7,1 EI"O;juK=`&ɫX"NvVHVkT#G'VxZ(Dry+½bЕAl>ip0d)jj@bz,T(Zv3 (h]`|yw/ tM@F%WϖdPZBm##>R2.ב=nޱ53.؅/ɝY GLlVD>ZK5`_Ĝ+' _W:1@S ؿ Ni.'@ң*]귲ܙa-qel&XloLǟ}3uݔ0 eVVvH U&:,A%..,W.غ{ڕ^Y>RtFD"j0[F2&2حQf s6^:Ou 6Tt{c<FǼCZ-4qYqEr"'iCzP[/Ԣ`Df.N3׹۴=4$kcEhGF@tut47J "~rE†FNaQ.뛏co-]]&bl#ퟗR&*^na.(= ' ARHIkF:N& WPvqQn]pN,jHl .!H ǣ;p3X^5yN[eG{ȧF@qK `:࠳+44U~u`2 64鴃-YY«1CXSʧPQ2vK ׵Ě&16'6>,z ;+~s@!"'s-5}xuCa:D% DU}Q6=-<RD6{ah6ERUJ}g;׌fg *c5: KvF-b؋Ў<[VC +2 6IBPX4E] cӿz=áζ UEUYP<Q S-JA FS x62&hb2S:^0kn@"ewgsEAX~,lhkO*TNf\B>zqGD8K"RF19J2:;CM?VMG_dO3+މmQdeؖ!w𯩫e$bU>K[?3Qu!, [H YVd< >e: KVgK ѫb8!`Y2|zJT (g[<@RQcwS;tVF:'?C&?6m[ghq!h#2 XΣiZ٪YUtSd0XW4^& }^jM;1a5iϟAK2RrG X-!2MwU; Nj)ZpyM[X9jAUk۷@fNF rt+,aE͝ed*juOxtRo⚾[Md]X~:.v 0'cVa.<7X !&gIGͽu *d4}I2~ʟ!N3DZ 2CѴp~Wd20Nb0l,zlrNDxpO~Ȕ$}OW_"4NXc=ŬaA/EuPa&ݤWwL.PnB~QůE T1JP( [R_YOpTGL;"U9 7\~;2%lJ !@TTt{ JmeX'R`ЭU}NY'}qbmg/6kU\BlUP%WvZ ڄ+>YB'oAvh}7Woɝ*5[3&}eFu1!/1r{RUqNMA@Z/dĵ~.fNHoy=ңLVYv`zl#$kn2?wQM73^L"&.2ϯTjFeG.v bQH)RED1!ILqu)Wa%#4yMw9IO_Z+4Ac.1/}ł=]ʅf}Y 6p0AqHߍF[B<;%a3\GӍ/bO)6))~J8I?lrI5^[#0+74>lzZRau_oyp;QFhU2wyqyʯ=ZS!כ]ś"[q5uAf@ۚ lp$du%ʬPbLylGIk蛀Tz=7'2򄥤W-ƈ(lV&ypH-*2sPI8Ip$DF#"֗3B1up֪PWtvx븪h'˩"¬jFAL~[̈)/":ND?Q!p*M;%5 ":0l0`m9L<8G#'d;7Az+ o ic4a:ߊ˘fKAA[#쳲nړiIט}Lw'?9'3 6/6󁷰Nj&_*Cĵ,"N) aъ.`#BDZm,ǎ_E3*eiL^H_s#+I sα#}ފi A2D)1?a~60 U`U殻'!(XxOšB 2;<'EF -At+Wd: J{/Hso)pQRk]Al􍷎ke "GmG8uП%4W&R ^S)Jcٓ\m|.z1x.p-]HwϗhHۯ&L㬦v XaNB^(?RhwX^CiFɤf g&

    x%a44tXIX/k1Cװ9uWh GrꢁkbuԻ 5@;+"sg3$rpe^dkvJ?T\kU? >jhWQ۪(ba+oM,%F&PPui647@K-I #oW |!4GhS)HezvS̄TLՀ 9+i4d!S}'ηPV?n#@n|YȤ)'2lݍ'pB)# *[VUoB9|PrǗfbf>'kr.lw#&$# E2Dd NKgbBiS_|^ޕcI7&=VTͮ;-7Yl2j)eL;\ksiM8~ ۈzϨ.dOQ,)?'::7TA_\wf^Gg(•7 C?*x=3e7E*(Ǽ޹ :LIL @2pW:Qw suPnO ^[45t-hsӼDjpVK+.0rg BY-4V q5j E*{_e$Ԅw%nʖ˩ph~:)ciӱ5sLi°qfRt(>H4)䳩WXp۬!? @UC}|Upޕ-FƇ q$ AF_FsSP6{=1nOM&#y h1%ɧ_8B^xX#лi#o~+q&R~@U6ᚓ$G r5L.mAJ4A8"oz5k'8rv rNC oޠBy׮.Lk2#n2 z\c ֌ULlAv ԅpa!x,bN^d:}@Sfsk6h8ZjVO=uQ\^CLb)!߂A.2A$v&PNH6hI$L7o4L :D&EЧpҭR3zᅦ@%D2nq]3z`Lj=PlUEc>W`JI#0g^\@gl:z g؜e_Hn=TU`9uwjcηDe<9;dVɍmMicP&}JAYTwHUq O.|t U@em_C{ؾa  E5"CxR)Β|:3Z1 Ȳ7PY$q  rvzU+gKrI-h'TNv ux/JٯJO$2ROrf?c ǣ;p3X^5yN[eG{ȧF@qK bYp9sL,_5iRiVCh+%h<-6[}*Aaȹw73Z12k^W)~O+cXE7HChtsSLB⃀j1bLmnAiTl[yhޓ_'YU]YB•}/ 4+Bl'g!L"?^(D1-%ef~ #0,s\Th:~`lA oNhwLU%D6  [ȎD|mxjJrj:'_x˛,}oggӷxu}>~(UM2 "갪Aq:P_3TQ*ORB_ ]VCEH#y3T\ӠR=ڭ70h7C';@[\ ]e*4_V-+U+chH\j֗3zP.5{pmzT)'@ Xv]_i 28u8:8A|(Qx|ELɗCEzOiR->(b y{W̘U\M)mFhnj::!+!o5}b?O5h.rٕ|o %4:Y(/)J7Vܖ\zf %$s{W$}X3 /!՞C_!׉|E7ȫJ_\>$xE2|9mE6s桭V*e)nM ^YM5pUf> ƣdT섅oL^(@m#R*I/%WDX&"ŒlRaQْ'd;.cFE:L۫9uͮ ~DOfUzz\w% rXz7~Pԭ+eiW7eݸ`@mHwe`;$UUcW~E_Dc>uلfX'B-)W)8QW0E:#AEh\Ж44/r][e`IOeSǼtz!F8Uu_Vfbեp9NDJgT[;:uX_ZC 8#2I3iKnj/j e:&6wL9A״Dو iȲU=T*#i!i ; D0}9.sۥReЂIꃚ"ah; k,3f '.VEX2Oڰ+DtWbAH,63{qYN_PEn$-/w R k'l2̏@aZY]K2/%dŦib8mEHbجʈxhY[_`")%⹦K ٪DwiC :q>5$+5WI5]$RZsʧ@3Fc|c'ED&샋i'uK, #ў1ĺF$]!Ńd?ˎAR$DP;^& 鱖 pjC=0b>ţ;rŃ#`7raKdޏ:_UN}zȈ5O&2%L$%y"6)I tU#꼆y $XeNi(ņFXqn/S Ĵō2-刼Gk]U1ćLO\,|{86@e=6U$28Qt"f' pƁIëxA;-p@o.//u#yTbpFc|޽_yqg)A$X.$b7D40؉D\ZZYp;|mEi ؚvKe8z,g%B[!nAsumI h-׭Ս[s!_nAjO%#>z^AiMo>RiB_`P<(:ӌR/>^P,Je֔*ۻ=^XLVxr&Gl{75Bqs[N_p֛Udq)GUr_dSZ!DwF;aq~ ,v'$`T"@htĂsFd%+S]>T*<I][T!# 2%_Am>qFڲAHힻ즅s<qeqz5{~0(ˍ+Pŗ/54fZS= (}k]"嘥 GiZ30sd/r3S-K2iIZvqB%KWvx]YU57*"~t 0&z6 8G"2gMcg˂ˤ<1ZhKA:Dō@{hXbrLcJC@01z ̈́+s.#V2lO=z\fqpğgoP:>jw^ ?mSceSpV-mK 3#GdK#-Ŗ'tȱ֪VSm#(n ~/nJ7P>ԯ^a!b˼l,mAћ#n^φ0ݮ8#a-MG*[~䋺feyLא iRG@$'lQH]e{n/_OnG7Ah/+܇,uD8{ZMc7nh]b;x^Ncח3t<ؤ" ȡiPM&N%C{+ r>P|ۯkwRE%!`zp%V N ǜx~gfj_y$ f1R RAgG*N5?փoj tr1T& ߢ"'04Q1^-#79JP'V_c,Ou,9yL ipJ!u @l.0ߊ'sk [ё1eJ/J0XުVT󧼈`fB[Wև#$K,ۜ?_&eCzG>1LDƉ;9ޑy0GCNomwHmov: [9s 88qZ3uBOW|7!oa:̢ǽEJOi ယu,YNMʞsW`=1aZ FSw= S3*<`77v+@/8\^ . jQvS/ l9"\\LϐS6غ: Ely ['GB#MVUFe[c%MJ,jy9wWP0®&sQc]wÄL^ ۗЌΟZ4pj?oq= J5{ܵζW g|B6eS]HS 'nKg{C~IHg_=7*'ֻ)Ÿ ڄ h!UNo d[/ p|*e៪g+][ S|E CBN{y a!#.(۟՟^q_wS! P\Wafow>:u]>%(B=Iu/q|^]z__=D\Y`Pf,xkrLskEn0~jV;h/Rg26Xy4 k=YXqۏM|!N`Rp47 |{F7WZ{Ng!MXaxP@[u <:~,?;Rwߜ2U`֞[΃ v+RqTETT؟p9!lWD"Ԝv/ë!!;؀Ln-E ui!%@ } FSVl!lNESh/ 5ք^~]Vc/-ME"a~  $atSlgįץ!^Uz۸׉=6BƬdXÏ%ڜdyקD)(_ 4Fj'2J) QźVw}ѼeDB9섋{h%*S̓ؔdi,ycqQ/BR:HEW?z9EW1Ƀۇ@>ثNy.Ԫ}L#X k 7~d w)?$;szHzX7T]ۃ?|0XK'3Fĉ!萐ɺ\PٹsmyT` c,s3?(S,W7.\LVfVCKiQ&9Y^ulON7̓xS~Nv/-I]K[nqX&\ʄ XTG"?[ZE! !@d\7Իn8 "f{Gf̑eW|D *"Hwm>i&N<;L'ڸ$5&daLI 'vx(~*`f\PFJoo}+Tes;>Y]|9U\5rGRT%X*-[ǀ`e#B`w=ޗ=/U.ۡBgk8]pBNXFYQU(,l1P&'4^ܵe^18Z'AJbF麪8.7TЭyva&[X-2Ϭ'qvRyKUڑax%bx$dKQ }1Oyݴa6KX/?8|9w*WUQ1ƞ<OI/-I]L>~,{Yҵ]YZɤm6v]nPǛ)dt[?#fJZM%9DFυgѤ vA(VyƸ.^ A{xw4(@+U,zV8WhcgV4{WK^ر(bF=d9+G?-(f#ܴ͢YS0s7k-R2raa\hZ q9f Hq^ݕgn6>1 r;vee`"= ?n=rz5'1oBZH&_akpA.{60fo |fDFGH''%GLQ CsbjMG8;d,y}Ĩl Mb! XG&]ԦA8ޑ'4J}G-fNiw >]7x_P7{vհu{Kq\t#W8 &2F'KHjG1Rꏬ5Ї+O~Cv߭N hW_u9fAD ۍeyiY3YK FbK~2T {uUy ҭ%^ @ Se~%d6~E! H!3N_[ s~IOL`z]Ǘ&<29N,?dH*4] ]t"P(T3[D\:`\,zLiVF-V^ 1z`d1l֚^sq{ȩL@o^ mr`eyL׎n'LqD5ٳ 'aQMSdŪKTYyBLj4&/ )-T6; a"3>Kx8IZfR&qss"t2N7eeϥB(;sZ('')Vœמ"LnQxRR*ͱU'ɻ GtIWꗊG N{l௘bbbd׺: -{84`v(P;p%$)!/ j2wLWz&ioVͫwc, 14J:9Z/XJ9'iD481`]h^x)sEs vT/T.3Qw~]pT*bRkyxP}v*xVȉ3xo X_G|cD~lvwɹ]@w/V'!o 2=#-ļkBP9U AL^P9oVKs(G[?S )7]`P,A+>N99%@E9!]]jC1v?;ܓ>6Q< !u*Ѱ5C\p Dָ1^]bK2pcG} Znp( }-#PoDoIƒR߃J<a>`wBl}aKyFbrPW܈3 el9Iȇph$qzUes3C $uy_bB_OnۺW?a.*P xbgV-Y mT2.HH_PSK5Cծy@!KxB-c"`B㻧ϛ0K@njz#mWy56ˆm1dg7'1W\5w0F?4]yoJ8\i'^Ch3ֽHa߻ˀ4s?Ѵ;aWXl@gϓ}!f>ɡ UW RcmT1atX{ӆDZP(@cB(YG) .^)v..5 WkT&:@w%5lZ/NdFī(Lna3W9=rzFYNǽAX20;Qb89jZUV4m\aGV'mڍ)ɺ .ҥ;:"Ru#%8x!)%Q0j(WYz32 gì)m{IA!9X:T;bIw!tCgz&GU*%"(& DMBYVS` ieKHRdߔsIѷ=F:jdVl_zyGnCg[D \Y6 V I?æG͕y;E(IJ[`_.&!d[OLe*W${s*PxԌBu?`\>/FhP2B/gdekφX@͑ٮ~+?G5z jяԮ"u&n9tN0r# >?i 5V? lmȇqղT.>(~g86؀0).Aib,MU)JY-qE ZθKTP% Z L" /©L$eKuwQE!0<W>3=Ќ.#gÊ:p:TKbm9DW5-ͭDP3hP)l"k\t:nja|(vifM?l6eK@BT 91bv%Tݻn^46^Yrͺֿ%4J5|vQ4Ϟ{bTFVK|#ƍ~kT#09>Ms=x/dhr8_3[4p1aQgxZi?tJA] W^BL;~Ԑ> ڞ4'X;Ks&,~a5E|1e谀 ZV+ a6#} 3)IzZaAHu{~W"B7㯀jYbec /M.e_o$zM &‰d2m:%|EVHP?NHǂ(\Gmt1g{k gtܭg}SD5i `C;4u)" rsrNG ;;*YfTjW[9F@vw*saԜta9+#>!D2I&,LcEjGR]Bu`.MPwWcZ)!:spnZ|P(PbD!Z2 ?œI=mS/kiBo̳ (c޹4Azo}}:#gy:᝜c%ODf,=i9xPd}2&P᪍vdC oClԉG޹U[hc`;5  ƹ-/aQTZ(0 ~e"kh6e$`^ tV v.É}+Y]HeH aQa:l}cyEљKJD1 TxH!Oj?"r?oH=?7y$#b-1vh~M@p=<19rQ|Q$W`>nto53>dlGgvyG4-& l)°Q0=;a-#:ftN:&0C"j{@VQ_ RҵWI'o'eQrXSNUgZIV]C7WP]W?V[܇Լ0Ѩ$Az(G~[V\ǰ؎m.8L424M<.:.K^ %A2yڨC`~u~ _W+7ӴEw;|,fK=p=a߹rC7fD9XXlP ~m ~Ȍude9?ֹӜݜ* )uLv5Pɑr,JU5mS/c|ɩwrP1N jb. W/GEYJBd$ ڼ]2SԭclqXj@his/>\o3HF\ 1WY݇ymLud`p*ڍ|_tbڤw`%{$HJI+=n*%1ONOiד]HF"s͑~svH;T4];VyiZo>~OIbBU"ط,:Tmq66̩j xqxKA$|\rVxUcn SnyX ܟm6T!a}1EcnKG4&2 Ȭ s`2EO$sZ+ oÃԓ ImUnQwfNU{UG,(ge%IHgu=;!#Mnc &[R7|E<{ȧvɀhb0*^a&:#5˒;w=$nHZtkGj(&b9+`FP>H@0}m|_$eU Q%X&?0sl ~xZIO qA|Z* IҒeĪ`,D;TRru.߶emPPa2acE.3 qvm4rIN[H3Cm{>6qczQ^j!x}Ll}AkǷ _of#f7--InLEjVRslGQ'%W)z&9Rg2.k 쬧(_)\n&~ZĒLN/"qƕ' N5pIfcs򖭳hAF%(ŚoEw[^~&GpδIPvPSNUz5("WOs7 !2&zkSF1%((~=a7.by| |qZͯ*i׊*g싈-l?6/;r\eߛ4O"a9/2oW^@3xK.%i3c Cs~"G IϱE蠪(@%'1k"C$2@䘏95KE/ DeI Z) О yʼnV>+ .Y#,͜ :Rf=qwloD? D:.h"t A;n>HfwayCMìR?XUP͵Bז@o>IFA_A`P' aC[ims 3[lvE0kbIZ ȞQah {$=܏Gb'PbMl4slm5mb%<`q[*i3"UY҉=8'A 2tϩ/MAv ƟV^EBb"}L&Er1A}XSnKxdpLCM]fFrL Ώe+K/]4b}w -|QI42X8cRԐ382t}(;j@]Nny\֛)yΆj*,zZ W`8X(VϷraoAdv* !v ?7<.tΖzmщBGW rCԼxSzui]\m - W! #}pe1F)3tԭclwE*)/f,\@Z#6Cwmu7uY؞.D 増w = 2@SFk,f+{ٟdvRvSa R 7 뷙Ƒ,$lӕr +ǜjLEI[ қ9sPy9t\e.&MZz! lY8l{ӱZO wLVMzdg臽eu_Ц ̦(Nk~(i~= w}-,ŠF#;i4>+yuQ9^4tz#2:Pe <:^tUwrߣ <J-mA:h`m ?@iZj⒋rag8W1?jp0겷\n2ކwspm$Gs&K/fyl8K5)afNyvugM-QX8їcJ7ecMvUxtG ]ѳ䢜!e6Ū'8pT׶0'T7D:|'ެ,* h Gⴓ?TUg"ne*yWGNiёj|Z! R#k>NpMEuPgS:чl9 Ro0Hq(2~.UI"K+kմ?NeዲEY}̀>;Kt9# hڢ0غ#A`HyX'`8(2텑l1f1Y7\!kR ŋџdO(5:s.K2Ma_݅tFxTkR'xPH$:^x7*N P9IܖFv!Y(cPn5t<|Qk G _ш$rl`aG w;8HdЅnQ|6Ң7>z_-={68y^фF9V /%WD&R#^` n4H{Lt\c0խK6탉Gdר#,zR˷MdAi)W.jRn4`[piR-%{Jt&=L@^-צ^h+yp>Qp[IV؞7-Ҍu<[ 2Lڙ|:|N.c֙cM:()Aט>iqF61T|9!Xv@$ e bNp"y+LK5`8P)Ό7}Aj?]#ֲ8gs)EG՜_`\nI?K Oϑ$K=Rl4x*K@|4H6GVۏ$i,ԊJdž8q(j5ߞ"\AP M wt52H5c'kd sbZ =;: E%BIL׍ cjɚf5wG=CRb~4K95Aw|E" W-= CSzppi6?dRZzF+TVқ"UeI-{@

    є_+3Nω"qcԅ6(5)UU.9arcY"FK"I_[1 ∌=j8l${)b+u^HMiWr?n/xs۪ePؙ֢L{7ûK0-N}^D݈gD3/ 1ؗ:16k0*ӼȒhA#:v "Ɗ%畸3ZnB'\Lm{#Q& ;MG/V :cn7#Y/\hN Ddk}V;I #x(+*pl!O1+L9rTӞ2 : (z56bVXGE۸#ʬvjryj *\GEܦK7]M_Zt|pg3w?OY'i=[KȅI5}V岰SNi[묟\=<*腬b!W}!;(2t#zF9wWĥU_7i9_|;06>jCM(JH9 a-*ί$6oo֪R$!MݏUӋJyI<F/4Us+׼-胾#큌02K].kd$E˷$#UO\.wƪo؄r&LavE[ϪԠ`ע38Mg:+F , Kؼ:3;`ci0"Uk޹/fP2Yٖ3hMe7BSKr`6bD+85)hEwBݷ_~S7-0+M28ѩ9-Y% $Uq3鳽 إSeYF0-wS?*x ],G:ٮ[ /WYR:"ݓ>&Sd'n8rNd^)zc'R1ĐQe*KJLq[٪ #aZ*m2>w8w&}Tїἴ)"{{0AtsMh'̬:{b~V_•,xD.F$eSHkGE3FcHtH_/} _q̦= Yw;})O (-=K!;qvy$Ap4.ZGޮcV5V \ZNTUr2Q\!7ߔF #]Ibj5xiMe<_ٱœϏUWjI*+9,ǐ Ix7 1 m2 0#X? ٔtfݾ0`FkEQlg2e 4J;7f}|ԢR5k@?SjoJg +CfjGMp t3JFfPR4&x.lFq5Auz!;`|xV=NWoԇ2}ݯDgpI]&j"@=J>a "RJդzOL/S)ygIe,vsM6Z+85^#s,*8j?OAƭ'mh|p\62nH,u?5@8X1:I2yw^5~[BR$T룈m0Oac`:/z;أaxI-9WmIJ^rk>)m!!gd8SdSƊ 7`%KݪdyH~-swg+4C6[$ I"A2U.PA D,<(Br3fKkg֬jҝ@n&Q7,*ÊO&b[}pgŅ~ơqٕY7NB0IHmzF\ j) ?1r"l [ 7O# ir)TJBD !E@Hycӣ(Ƌ0j:l C$(3JE.Cc@<Ъ;OIcyEv| AYۇ7XfֈZf}!kD^.)xo3B":qN~ƇZD cqWd,UOF!pX<ϮHl4a TpIXi3&F mg `tV9 (V_t3 yr3amp]G{,PHҠ|bְGP661KWR&]N`}-@R~lҟ캰0֝ƼK8Kdh@[%[\!V )oz%A4!V"tY$C8($LΈWq!(0ܠ"]}S~Ϙ/CA5HEk֪+5Iu =3dN4=V>ovcCցpcrMj8s }ecЌMMr Àlz4|,1_(ˑb{Tm/MnrGkjl{?^rlL(CQ*fP(h!~^%;>jהtXvi(b}nËC+ TȓN*#m@0 nu/s?\(h BׅC=j NN>[ |vؾ)G0V+L@4,(1>tR ;w4cq%UϴNGFWGyF<,Q x] dܜj׋ 6^:0giC'wu )2"ROz5;yPl6Qlύ(,9~%>E\cBznnz}͹>[|YS4w v& Hku:\>RGL+QA<)yU )< As &€-1/ 4LоyOd9g%QD<[ cCW?c8kO:ݹ͒.F܂sDDgh+CC4Uy*wR,q}>QiF&ֻ22R7LWBX}nW>_Iz#΃%&J<Q> #aHaFӰa|y)QT-HHwWK.,R % >RM-QaD*o4v4 I-Ѧdc9kpi;i$7߄mW)8Bu 8TvE mEEa\m5ѪM$߱>KyCR2> ~^0~3no?{VseQ +J1"ѪoLD3x0h|5@XEȒ~Ls'ʒp+ƹ]>&&h{9|L ~7U:;xuEXޫ#,I >6S*hXN e6f{QY8~"xl[Q@ o=UOc̄-ю 4L},R^G?s=aa3kE'9(gƟHY?2Ai[̰1pɥqv,Ξw.7IG'gH-2k2'^uIf2W,:lT;K*VήW#1qA ~s^e$n[I#T/^_5~_;t~ }feѬus3lh})/` aZVc~ /KK'Э眆QH09S!@cgrxEx"7Vi%,ղ]hϊ(O>Z8OŽ80-OqUU T]A̐=/sl-c=fS:$;oZx[ /&:mjh}{aT׸ $ `'8Υ B `؃Z?_K1йb;HP!&D𠆺u2R7LyqH(ʦ֏a/r\YJōTi2Pfc~߭~i]2ES8!!],b')MLlu!oBtd *yȺeXmD<Oraᦻ )֚+(9M{S} .u KgSl!-FT \\x.үL))bؤEgD˶/@ܳ{ GgyFR ض%:v;/N6c X10_X c5@,d]@ u8@Y"N.?%w&Ԇٯ} 7ub'Lb-=TĈFPA4u&5ꫴZGVnEH?gSEd.=Gm}f01ݙӅ*]Ҟ+rS.~p1mr6|~@Gd7Se-CIZ%a]Ŧ>]p-p#J ]nia3lׁ{X@D =w"Z1AƜbL$ƛ,Y:xؗ[KBg{qms!F Wh=%<2RI [l7[r?uL$}$_Wn[4uB\~pX97{ᕖ s!(yۥcO0IXxV.hzk-R^0fo\(>5DZFn%[OeM+ )˧d;vzVvcpCe=o[Acڬ$!T~]2o YIzc Eq)+m;ռ$Qbz݌etGf%Ս^C!s=ԛicˋG1I`s9tڄN j HGJ[w@. $2mOpPT  3p֌XP}Zy]%ʈZ> ~;^ʔ90$gPATɲޤnUVt➘K҇󏷍z fr~Wpˍ'gpMsuS*v,wE.p| Ć:4e{ʕ.trb1ɛR 5Ӱ#t@A LRԓ,Gw{&:U1"1{Rccu0Oe[ Դ`pF1!%O rTA֏`7PA2m?}/4boM$ Q'ej|73` ,k 7;e4By.lǽ pyA=ܥ|§Ʉ).oCP7!qHIbۥy ¯lb#90zUtD-r0W뽦 |QP<‡ͬ)Ȁ wGq9'c2 @I ObBDYD ]NOfPWwF+AXHُssUTC+Is>,rnצ BxTҔ?MΥჶt)T.{JU#2[Psauvd,.A05.h1xQx o4ڎϕoO1걚~Ɂ(0N>$7ګRzEfG56d-#Yͦ|Yt#0ϽM[{o*ccŧSj!Fы8,J>SfHWY^@t_uJɹO8ۻKu\&ex lsmOO kj>G*Kap^aWlî92 Zbqi*9g̬IU]t؊Iˆ-D<0xC?{/})a/+يPJ'mESaB X |+,[}a[i^tP]! *CZ?bO|cҰV$8w4`]옞8>7>/{4a0D?ry$l+h,;z`](NIL @2pW:Q|=$J /kgl[)ڲ~s((j6[\!gAlӮoHmI曗# _;WS o jE?)Aphj:SzZ0qCN K=@xlAkL,kN涡tkNeڶ0Y^V,K[u)_ h^7U1D$2-PF|Å(v t/]w䨰8%rMD""-O8"Rudmpd]h '٘SJ\Fx$AnfVT%{'iNWIr:ܧÉXo>Le$v&6Z8bm$p#bdNGM>&&eC<=rQ9 .MԡЗ@ֈ#xŶ?-4c~iIp ~5]@?8'V#ne!5aHq.4̿G/3Y ;UsRIFW6Uʯ UhYl]D%xZ]$auS3pԽp}5vnd 8VrȌ:Q.u4kbj|8h0!:f+eBͻ$lRg 5 3V3[~S$Bge X U_o48e)@) {< L@xF$OQ=]x9B'%b[A_P?GЖ-kٝb ř0=Xp#!x=ۜX%=*N#.Z Yu|Y d}Jc!p4tI`Pk-M'mُym>uM¥zBc?FV"(Uh % R;YÃȰ"ŲO"ŗ?"U;5-)lM.AUȘe3D5 ֢L ^:\;m7\ ɕ=/(FR4h꿍l$WCeׅXl_뽋̫N <q#5"B3G$\+|l0O"Fuayډ1Ch5E4 >OġT$_}Jw(usGOG1!QY^65 WkfjBxy?ta+ ח61zNKp]ȯĸ 1C鹏'D:p0Uvњ*=-AlQ6SH̕= H܋vp#(Y*/5z2E l_E@:#/p!+:זc 7E ɟr \km'o_7ס0e*J>khmkG{I=ke9=WheCŽ\Y) s'y۷A#ߜZ_+?9XIl]$v\7XJ uՅ$DU$E!6kғgplb0NP3\6.445WJlͰyE |13{h@Q lmMn}!_)95AzUHKIWiU4Yܼ9_Nb&(HY!$%/D:+yZ Fy{ ^T+J$^%6r(9=8\BAdžǖ .RO5؇vշ0V~!p}XyNoE0Y;Z34]֬\g FfdPTUpqH}FQ^`U2/h|OGW.!)VFO:t$J*Ï+|8[j^ʴ_O/$Z"%ʐ7jeܓ-J?0*Kw54ÉbMIlkMX!*W/ fИ D#o vp`qV/?PQm O 7[Ō [wOnse9YöO;]pϚN,G -zO˓%¦8Q:? Jniw1YФg;fGsB61M`D I"r<(Y AOmץFTsA9mXG5FШ՗J@9md\?6b]͑`g9bel5;Qt~9AvJ-j`5$ڔ`Ġȉ :r5JkKŜl s1w!Um-[~"ygrzqW_.Kk^R,Z6Y t0*FpmUfDP`1MhǬGo,!H[YI:G{ t⚯C'9[ 5\kvۄ΍^k-$zM1<'OjT)py(400fUNCwr/?C#m-U^ipe~#]>Oݴ?(ï:@ߎING*a^Hd Q?ωwQ?(O-'G37yW5z -g+2C0gּwaBD|!uZbem_#Ь PC#I2%,Ɲ95|.9j-(UAZͶ܃>qKzF=-Mkq>$ _d ,:dQe \V@!$gaa'= 5ǖ;ږDU zۼZ}-V5|$gC@< \6rgi뜭1WY>%K|\+ *<3a|8&,%e]nBf[6YE\plslڑ ,o.]ퟯl 4CЊW!Woiˡr/u)C"l:VNDrLEm܍#$RRNFvw*͠V/顆d,hk%ܪv4#CzZB* v{yZ[=S2Omlb1͡1@b³]\LbK>{S~wW_9im6gOlT*2 ~|QbA%~2lQ<q5y 8FJ*yO6(|tlf: )'nͥfJv*ˌ:eSm# NpBim.k# u.A>t7aAävѤv+)Ju{uF}e>T5N(<Zvp%|e:eZb {NNj|TM̺% Fܹ)Ls䥩6^R`&==\ۚyHZbBhש-Ah"W)^ 7@{wq*6iN-sQ{W0 >})9,!Vd{ЏY+DI!{4WK(n \nSw/akeE=,YVhv|yGI_xN wXKX/]q^Q Uq <i>#۝C2%f\wfF2 tm d Ci>'$lJDՏo^pM?X!UM(w`icnq (^Ymn(hkؙH,@$o0FxPܶ`^uLk Eb 25)3V2/|JtR1#UQTo`-VqugĐ NFùӆmٚ.t0UmJ61+>s;gFX$ _E>N'(ɲ@LrQ/0Qm¸茷g~Ss–}`1PoAUrC="*ی޺FC*0'c]_Fpj5Ph쇏 *D8$NL|&D[A@㘔 A{ lv;2;w9̨Z(- %qEA\ׁur!}$b'aI8oOmF8« 2.ﶱ~bavp[AI2d_M9_恾R4zv^k$MvdQNQcX77L%E&VR"y_Iz ϩ+kz-hL`\;q^п>7g ב]G?_*)Ceiv=Fn,l /4Xt] ikM+eT%! LSJ^Kwg894:iR'J#[E65Z*yƱnqYl?12x6PLdF1WS<+[.N1LØbϼUddd1?<5\`bnqg۲P\p{Jlʉȁ)_ǂg'E8 ws"nT3֬Ơ}TZȩW\csF]x:|Zfmf}u^:7F@\}F阴eZ0_>C.2 }ۚ){*a@oDT8.$%*Ao?]u Kߝ]26N$f{mPn*YǁAh"sd!r(3Rf  7Mo?Tϓ3Ve(-FEMJԨ Qameݸ>]p1j8Ebjن 1Xh࣠ k d#2pP8PSm/k3yjeD,t(th[XV y٪[T:Mx: zS\!I1lK^M:)!/Sq/u&!?MtVWqcRcI HEppL75Zoڋ*I5y\kZvZBЛeP*z F;\w 1 lR㝙H6m x07}τeZa,C@S0yVb şY38cT 2K̟cGwk&J.g.ƑGk@pc1m]{n:e 騤  ZcU\}R$HSM0Ca`ꍪM-^_%&Iԁ~bbpW Y{KJ5N\U HPwt S'Ez-?~u;dQDROȝsy27 !pbyuMk}D'u/MQȻ3q(_6@6F|L葷2u嫅պ&|7odQSyj+ E5̲4JmY?34<3*IE_m_Gԭ=m&PӟUjMLnOVr"a(^~Z)ꬻG(~ǔӕh!̟cGwk& GNTP%ߞ~-!zDwhdj Ƭ[&dR)m}Er@өP&' PD:b źJ0&_~eSԨiAs091T\Pn49(b|S d2J!mԢy'Huic[S5(w84hyZ+h#%i{IR*%\uR4PؙM a1'߃NǰMipө%sR2ržWxO䙿W&Dh Oq/6:eqKZw@8aP?L8k*—b)IAδ 1zbH0+2I-G ca{sÑsMz3 EsW 3u&5*|\6ukЍAwu\M+vӫ^~TeAY)ub E2\G1m/޳ثHoؼYDpMr/*K 7~}i$ݜ|FJ{g"oW;B˪Yzgv ~2/v#}>m*PDK_*An;5}+1C|Qe.3ʨ,. V4? H+9&Ă4W9,:DS>HU𚰰"[(aXW`kbT'(",gE3ݿB5 Eb@ȔVc= nkМ!LB ^&)(֍&D`o nOE9ұ, *,ǚF<|[V"te8Ld4&)[+8ix|.AN"2&qaAMzPe9·w lסmWQ $?gGTAUu|Ͱ:+3 ib9! 内ctbe/- nCs:7#p5T(_#%M=XxQzvuR>)j亰Fjg|uM *>6k`h*Pߎ:a_ܝ_&O0>ٻ/D~$}Rq.AV5puy` zF## +Er;ќb'@?,U.HoOm m)`UH)Fк͈^M?o/VS`+T+!YBNVfe 7RRQyvÒʗx :M qOcy'^嘩E3̽ ^I*5SԪ='&s >/kũOq 7kWd&D8*Rb?t,X+e`^f}?sq+~AR$͎GcahVM Ȣne͓ s+&tjaeR[eQއ)%NBnH+ ̰uAC.x׮M1IKn)ĺð\@;]Ǯz!ߪS7b\O`OJE?z f(j&tճ9Mh#ьm>ăpRgbk]G,Dp#26o5\o򇂟nIb^m2&ƹ8LL`J"ħx}s܋2H(^BY w2&GLׄBs|M0$;H>zna8]ʡcwM\V@0, |Ő!1&nW/Błk78p}A #ԁs U3 EL,gr/iO.my,KȽt]EI!ԭC33]2F4,G}C'_:jƤY椅TW;z T;.ƩBDt"b('pև~, lK8q(-<TW[^ I4BV4(d#G8MpRTGrs[`C ϭJ[ (T.kl񸃨1osiH >GL 3# s[YҚbT+z pg;69_boe)+sT~iz$?P,CZqG/ eMANm~~cTX$,'ӫ;rH> sfuó15P7U$ ,8t&.OdPU6?mȝZKv0WAf_`A?N4?rNA.G ^x[7+3XW( no3zm4䈏sņÍ~$TyX\ j G#qWX5s1hĀd\Sk=||OxJo|$u[|=4TG^r<7)֞DXxy8R\ -B?/>qe,4Q:;A|R|#Z~վkvW!̪gD\ǙeY+LP+KuNMd:8 hfKj >#KI$Mh`f8瞒vd> H?  `ۨr# pC۩gEhɬ}&j %]#\aiL)Y S&%5_E%$yx_FK;\& dB+3ٳ9o0lsS.Ǘ\zT>pQY@cM~CUWuɡ/;fؼE8OQKAp,J@n)YVf)-&ы,; ,5n} ]/ rL2염:$H7{03#>)BD-DC>=X+=a;&3V}S>J@oy9vp{ޡ8Md!X|q?i"h!.-of^ꍜnH&]1&yoqJ2%wx;v}UAI|8_ #Ki 唓+Z;aX0Ck!lvA1Q,dmBdLۊ"u흥KuBd|0zڙ8+6yr)P.1u/M:EL4ˏE0.V=Xe` IGWJĹgZ!#+nO̞hkWC͸AM>//"z,c%|M+t2HuX՛PGl.PIJ'Eh \~!*dN-ﲫf*zgE$9|}\ r$KWFck㰗}TEisԭ3bjJv\qF@efa .ɞ6)0 0^}S:whE/ 9&_j/j ?9Ekʧ2юh>4UGfJfLBJu \Ѹl1fyQWE>o΢?,|oٹ&j5HhJ.}g~삻!DВr7KKqNsQ[7 $.om:s\Y;{F3q< "'rm]Ȉ0  ]N߀U'60fWz[ tSL o¸bV[8> m$gz Vͮ|.5`?bh ]Ц9W'W?x3 8hB `LSɒ\ bu! an$mnQho#Z}yN?7 'I!!osl3={rB\/q"zAܘ`j7I;_>AP֬-n l橭6*CZ #E}`o7[yz X(1֐G(,ƯL}*b_^cƕ!O@b ?|l=WP"B˒P4e&9uQ)yms PTCapȶh|kh?O_G=><ON{lz q1N:mwzMX?D^O.FI_B͐;{N:P(aou5s?G #Ɩ>G7!7[%Аm5c&,ًGP묓`_l{xa6:AYpqP]aT7ͼFtr^ ,l<⋑>KiIGlV:6ڟ},167+C] "P&) ˎkKbZ՜k'@ΏC[̂e9|b:v×g+; VL5HSu>G3'%MOQoy3&: `b \ɿyhPٖub]o0\AKF:cu]6S})xղȫXi~Hŀ )PU9T* cbq\zy@/ *3c(ěD(!QDfU ʢr7qNL5Pe؜u/+|IvwpmU., 6ELHF/zMePDw)׈K_3%]0ȣ$m)}Ŷщ 9蟈YH[(atx gogUY`ja#"<{KcȜ.xקC؝Ee./Wo6dp֘ 2đAz =F@q2ŴCB-0<{K/wv 8ht!شo~(iu."byଖW8ۗL-J.Q8K[/K7!.ЬiNIkAZ)`1eGHz֨ ƸӾX sN?$뉂BE甭έ@b$`%}pcϙ+4gO?T#ykP$`IN{r{3{2hei^%d)Fo?|Ц~?A<7dg- \ fOM߶h^tޣE*/Q9i)NWr0eD h &Ϩ9ny@ qa}ɞN|51)v+_$SV8%{Y*LL5C"oU,7ƫ?Ljh/1iaY-W_&\ןFB\+Hy=y&CMd!YP֠BѪ0ڭY/6P/0 C3niMY?Vk4'BؠZ ba`Z_62';Tko1cJ;?TŨ[1,۟!lm[7xIx\&Iq) k66]IH@ NAvpL򠢶6@ "gnS|\p^gjP>NBE|G{i^c/t$ խau~5bcn}d{@?濔 x.rC{$@̻?)dmb8DoUF|l'ZB#h*V9S e,w8Z=y:cK B e(*q0eVx7>WoQ.,ME6ò?ʓ}J}&j0c7:{^zO1 D_|#vCmR"iYCFAC1Eyak @)vl') @|%F#͘e|ZÞۈCt{mmSfq%)o }藭ʒ׿2m@3+OԢ ݞ#W`$yLs ,tq_rKZ*r6H̃+kؿ. q߮+eD Of~%ǹTTs,8B`.`@8' Z` fB®r&ge`>S'Յn x)t2O:Ct]=Ŀv_5ڱ/6Zܧy\ѴRgX]`6,C=9;nq%Z0Xܑy +:3TDW5'*Ub\DY &h;P@tmGК/v[7>%Pʲ>Q a<֞,.\yN<5 LtM>?aWI"eQ"T"SXok ju@3\@-jУx20[+ ȫ'!aWd56$|T]<Zc\2TB|Am`>mB6҇)j#p[I62r!Ew*[H=A 5T徰-"oe|`~CzuPbEo߅a-RXgV(JXkHl2#P\BCX\-qeA{ %iRZ.jY"Y_g@_Lj{"?k=شքS3Tӂmʯ8HEp?4*GI8@\#Yhe4dABYFaE|iZ{etEH.p36%V¾sc;¦s~* eAVƞ4:R>,KgB`?\^(@ UI“QPy^t~AY]3e}iF+=%u:j$",˰ qCAzVKv!ͱ?GY[Y/0/uJ,Z_H 3)\Kᷣ?_BSniPBI`oO9A_i;\ m9kȃ-&6ǐ IMHdʱr) Xhe\AjVs9w0֪"6Pm`i1`% iWV]^dΒ5m|&$NZjbMK"KWKuH]S GVK<=ZFPd%l:\"+1[~}4/zWK-#ICCߘRCV`:߰/C! !fCF7(7t_t=wG598"أ=[?(F^16Oce?(S0E(59oFVpDN͇<ҽehTĹGTLda!1 %XDf1RmUwsr@!rZQ`9 MVm?*ېt5>A," 993WoT^#jZWBU;{|hT3̸Wz|C{z'a@"5h5$*3°#"+sV 極|x"[A&X"ց؀l FK;QI8zbs%2ؒ*{C?}=vJ g."0l+,L`*:k۱_ãHr~O:T crQGaNm\b:e+RoP)~Wo v|~V5fX# oBl9$<Ιl? =acr/szs G,Xb_1uM%\4j*!£Bjj\l5eE?ϒ~rʐZNZԓ9SDk~m@v4}ŷQ -diXے5%9M/۴.xsm0O*ݴzju-0嶎kYWh}b^;0lX+|_ulu8h!u>PW)2A+Р9>.pry=9>% &ᅰk2j>`Jj&M"2/|30mߨ|;_2p 0/dlh\}803_u|)# uI_m` ` +C5p Pj4Йb%g"38Ac'7@|ot0#ˑIO@s.mG4ӌ 't̵U:jyd/IEred@òܢ^ךy&Pv= Wo$=z -Cdw0^0Gg.٬븴/Ph}F#.LGk *4lECMS8a CEB5KOg<6SآMS_Ԧڎ,Hdi[aúi5=}4XUԙp3<&iG!ՊYbScA~Y)}=o*RH3.Q1ц |&cI-skcVC5__;8/X9)mD1/bWݲdNlRdDQE@p`Gt72&9bH!(5~ԧ~v_0#E{!FjbH+H5\uѩc&U8OaOM`9CWvث:2Ly0+/ڽIkl"t\]OG vO@BDLEYz}G~6k$VޔQ~긨6&}֌{?BߒnHRQfBS}3|i('RM[ Ƽ˚:A|Fe"]n5ۧ>Yh`dI}Iicf][?|g `:Ǖ/sc.X"Z.ş+VU&*vOhإ.Bő\a{1qy8Щ'sܒRh4.*2qvw=_'kg񟃬u/J+ .vMgR:~)}lD8 @[b 5s\J p|4Fu_XFCE"^ojn5W9ɉ"NsC\YDMQr4 M5ä'-GѲcV]zF #?j~}9>ڳU_V|q(8͔<5pK4hGNnd`W/fbdg]I7`AAze =%2t"O2ql_ ['u.9Hێ%fꇟt tSrg!N3WkZ1wJYko5=68/MuI ]S7lPXx#Vp̜0M'2"ޮm-YsVF`RB=RCefvHizP1PcW8 _ 쨓3egr Ry _N qckaa۱[iCubQUч%$-/6wo$"V/dK)(0Í=e+f|R ujl6q4OvـI?`vIj-︈4L(V1Zjtf(qד} ot~+:sg[ʖGH1 ʋ>ɳ{DNFPԇ'RެMdCE=1yFAIUv #Jҁ 0Aɓ%Cd?'Cq)ګqU|~L^?s4c~=?SXKLq RiNlz3&^-!;zenN4k#-:N#IH"@e*k?ޥPԖ8?;|!b7<7#560FzD{>!&H/q\B9x#'1a>7W}Ec5ypxU TX e? S9A~G|Aqe,(R77JJ+vNciͽl99x5 {m #ugLlEL9LV4@|4 {p5mMBqSU::́!znU`E|nf*GF%0HWT.c81p{~xf6R䑝O9_U $w_|]hZߜT4mAJCj>؈41Pkd1z~NN\uf+` ҿNŎ\֙hc`6p 6m4Pg'2u>0?wEj2^*ߘ́DžOeQyxxf ҽ7`]M1U?4wygѸr ܘn)/R&gK(O$ '홥k8B1TDEie7URk!eHNIXdg?]UIlЕ'kf 'ygX''VK:*gu+}A:DH֎7Yb &Ɍ>dBў0ZOcjh x+>,wNͩhٝxo=Kw3~ءb^ڒn({开85 ۼԖ9-0=G:6 |G8oa۳s rAxqC˧&PF3Pp,fCLBI&<\ujVrf<&HebKqaR>AU$d(W 2)٢6LlZ % q6?;mi^&+uQl\iw:O*鑀OIܹǬ~pDJF=, T́.j=Xa*]G,Y^t_B)9@|fdvY>0 vZZ=C_PNVA؋o1OܙbdJu ;M^$tO=V5@2A {bo.ɵZ.#*ZhPJhsNL k2-#EWy7̓ѲD yEKU*u(NTUWP3霡^_T~9Vcydn 앸RU1?8:3/{rpWPwh]jIV5$!}V(+FbHxd `fg|#]Ćt^Z.bkSwU}% ns3p:n?\/Y&fObI+j댼Vt-i0Q)ګxKDDjZ5~bD0C3|F̌L6ܢ lTP J e'\_ ItXfmSW%肩Nb"U=-n}~n!0_Ȇ@!Yh]̞̎ ~yWgi3\\gz(f-|aGRGOp_y9{ToA?4dn.By3_V<ʵ2HGӻ9SL1^- zDՖrvr& V's((l[Ʀ?OaP|\-*!^2:'`8RseiNsVxwJֿb~t+@,dO?`@e%ԈU6<(l!(.`pOll y>?\t~j('?k#IbBVE9ѭC{CsC44ʔlU+|(=:XNN2[r+Z"˄i:LN"AwdH[.7ֹ.7W.QʼnS. Sxʓxl qEdDnN6"؆vU윶SᗁUU`s>B?H;YS&7X.nav%Wô(4ʕv]p܀Ƙ3RA-tHkfK [Oz 4l=7״{)M{h~PL9;rjI oBUQز&ZY1Ōy(;T&_/7V1FU=Ò6jv('sL2#ajwku@̕&NN|wʻpq<;r&DZ^̖x$M2* 4=6UW*Ī^qO_ڤNzP~{W.$ł#y UFnn#ȘݪjA/!]㬐} (=1;(^E`vA8݉!$q|r'C.(b|5Yvزv=tR>NX7`y`t*% $0 <&(lkIkw4S@X5gnr|ws-- s79z@DA2U dQ;?4lv$܁l`JݼTF jB—/b8N MQ!4ٮG_wo*ZG7GIO|sXPReS yn1? \&5V-14)xnFNitqA2{hƪcܢo0f%r՚@ (m{31͟͟2IamNt:l3noFHzWY[*AG9&i~}ݿ_8,1ֳeϐ] :GSwsj˱f̓ 3ս BǍniKf8$QXؙeQ23֖lR 36]LzEŊaDAvqvz)?]ygxS3 U4~nohfP5MuOߩD9/~ZjϢwȐz@]CiT(l\RHG0]; MU r1C7r"hzݧ R9nӿNztlJY` Emӿ(4=2"cyly#lE:x768t$}aRXʱprEg@BpSp1z I==`s ̈h_imfz.~ܜJ{F-cY2*8Qa§V%ڮx[7ӨQpp"%{f =ߨ4>cֲ(a 4(},.4B!vD|ڇIE"p7%@.tY^ XD(y镥2e"F#k'+\_7L V+pkh_ݓjɐqi[`yzfcۄM*=~N 3%Lש|#-ϊ_70D)po)Ru]ev2\P?"ό>RȶnFԹ Z,vuB"@%-k(#MU 3@ ͕]c{q3/4!)jW+ȴhKAW&[woյ)m=:ǢtB kZmY`Y9;];Hc2!2J3lk"1FCl怇^F㲺LJ]#)FK .t1nB@j6W߂ğ6SZI!w)R:^$mnl{ ˭@g>6}53|Z(4p!uy|V+؎&Sw} s :rж(.nf YIyNY:Mj D7 `a±+i)]^oU:q4W]6:,Uq~<өRyW o &FORЩtgzJЙ :qena) 2■>Nyt f8 [.,35?Pﻝ%tOM"`_Tud 67o5g2-x1CM5IRQ_Q=u[ҋ_qqZ0V!C W("qάvq)@Յ$|xTDg<ΨL$Ǘxvqt gԢC#I73tHFj y"JlO3/; N$>fV0'#,j4CE} 6I=Xʼnxj̞vJt`3FL>4$XݠoH`EoK[a\PxepUݛIjg#*>QVz9LzkMhijR7:[k9"A3atȶ]JuMhOsJ=r,.zoL%'@KN҉9Cl!(oێMO>DȦšr`z<)f==qp4`L2SF+[ -wwεϥXB3R\VC %:]`GI -qFtrM P{n8|Zcf kWTO@IG27D2Uy:U{6v6|a Dmb}˾OVզF[xXum;X.U nCR9Nq4*l8)h|YYSCw1m2> (uDi4\glVBW,Q#zHo)ؿ5|Nc:*j2)aoho[>jۜ`轝qׇIPa #}\i4U ß]NOeFZsZsjA% c@d/~!38jO֖d'M&soF[v-+LN^ Ab'8➮8x+%K[2' *lQ䚅bC.M.eK PHU%otbβjmXtf^Ԡ<រ =rw`/録:w0yPtlB5@w5[Kh?Z8C 2B(,uL(%deKvr+א2BُW1u pj}3)Q[i5؄(UqD1۹ṜBd*\*Q>J"l rQ1a@SVxv&fVnlhE\9/]}ǐu,ڷJRB+1:䙠OnќAy-v3Ȱ Xā\ш^v;!I;-2O*;IQevEn0~uL `͈MP>|n]2!(˸|?uD_qZFrÙQW;8 Y[w5R?)XY@"|~B81-ɺj NsUcΊ׎ُW"=jEq(7L$m*L96'@eDgwqaj ".fGE:ݪMLz|jU$Z9'8|* vIʷ8ӫ#pٱIV#Z"v|nZLI V.$ #ΐ^/sva!#EXc h<2ozLA.ݜQ\y+e{jipIӂ ,F ôJtex nP&gP/ι zc;")q&Gamtv!5e*~44"`ScƖ0@k-%N-im"`yw~X7+|[5Y4>&PUQb'YKK]gtքPYӺv^>c]{N ͟dIs^_u2~-?_8c??I2:Lt T^×ߪ_no{P& Ž"ŵ>>gꏐպ7k͚'EמJ>2M:`}QkjFX_/⺘+\r矘L#^)m&2 *4&O;N\Q_ Ξ㬱uQ}.fԆ c,f!nئ r]) Ռ[YrPq>d]mʏ-ۊ ;Q;OקW,1X_E,nc?6H,t)^ ' Mn(7HiK"'X;h;nQ 4nԍΨK#-KfliM^ z|] [βlKDnU 2I:jUc|x1ЮcVwΰ:0v3pD>/ᚉ% #z^zzi ߊ4HV3<)4BHgq7+@uXwj`,!$@%8ST4'M<$ZfF;l% ;j 0 g@PSo_X ۄ=0?#܋C9JCoq s?GL`¡0DѢd^f (VE?:됸M(Cv|NN*$&]zyO^?;i5ف\B3n*K[ZooQ0%ƌB9pfJ=CQ([x&nȕ_e5ք=x?Lg޳G _ aEHRT7Ű]:o/-| G|yXyBB]U9 `8}#)R bj(.֪FvRb5((05 gq+ݍV'u:@Tbs{N^rSּ5Dd, iN­8 g4pя>}Q0 u a:&)T X֪$kQk5_,GWr?BOw asK!r`3ܟaϳ#zNj=:i >Hrۘc2nT}]D hqBޣI1Nh_|R%PctY'#@tq2c̢i&jŽ [ ˺!_`]BPYyH2JWgX @o)]:+M Hzg3TnF/?+"t}cI^ܪP.X 9%^䰐 6ԢsuQf?ph2W C/H'Z\Ȕҩρw{`N2[Kj-Kc$QOQ;(r\]L},:+K^aҌ ̍o IBi7y R9ݰں_#?Z ^C EBcݒvfEs UG؁F^ʿ 9 UIée;r&G(ݕd[~|NNNeg>48;Hhy'e|b5Bx<~f&CI&N@ƒ'!W >{Yy-nUq9pWSퟹg]o5gv xiKBq@t8'%^Ly=yĪX n3ll!'L&B8b!: Hq`oi5!m 75F| Mg")Q:'@! 4W0U6U Gi8"6W<"dS 9e3GNl.%]]c9h3C_6]c;e8;:ڌ{k>H\\jNK˽"Z\`cQÖN4k<c*3o]%s%"[ W.=ӧ, J#~dye/2-L:p+,Jk~.z8T4a"|ih\nY9Xuz[,OD7/sJC hgY2QPv#L!X,#GZ0<@~!)PrVlݸAzf9Zhb JchW@ \g˒+@SiiN@- `4 ^^y+1SE Ky]UTnU{g&6eeGS9ij< :!n4zFtP7Խ2+c=co q҄j赍Vy-ugGfη eU15G5h*h9mQPBO?RŞ_F4~D4fKz|wa9ȁ}A=?2H ߷!j|@JR@4o!=B{N8 .m!^ve+WW.I 6oLȊQ6.Fx^[b11^nd0+т\.WQʉ@QT< )ޫm~|=+-B7.>_pvBbs=EK6AnXt XOnuHގM`ł!A/0͝nʪc,kI .#2V'W6U@m&8swR$>IǾ=ňq&? &rbs\@4;;Y}\p7G2FjjR2N >@Qׇ.V\! 2$x3 3EuÚQI5r*sx3H1 T gi [^뻀DʹYcMM-Q%+Vfg@Zq;6ݰ8#z7G|Hy v=.μۦ(_rb #,Ri~RRTFd%'}d`91ɔx*w)&1J1 h_սh (};SfMHtiJ\gv/AK^w_Q+LtjqWŒ,8鴩ExZX6RcMrP?B$wr D!Srh R)6b%)44t' GƬ <ĝ Hi.TwuVxh*^P{pqOo!Gmg w菘Bc9I?Bh ͓Adg=X-lч_n)2CI3Cu<+m\þɄ]ΑPl1?vw5.u 4/gY^>\kƶpvPb-YtS1k)?wOܦUڟ4/؏sB5IUx"4KyǠOi~Tڮ:`8VuC=[ zƟ5{@3ې+:wT?aImFN"w&?=BmoK\(c΂2/= i ~oUgRh ׯ/]*M4ȇxuG1̄Umc6п0,a){E7^!PمV{E,N)Qwv6M/̽-a6Ou q@d ㅮb<,42upa |kaj3r/$/%*Pizyk*~-(n3eU}]L–[u iWe.cT|8r3߁@z1e.g3uGHdL)nݜO 0<.suMī"t&Okk h:KE8(VܓbfO7#:4 ӝ8oY4=1'FBGK‹Mm }Rupl!P ڹֆQMRl[^:[kz ƝK ?d b<ßݎz9XX' R$*&N4"ıfAr9MzI2dkulq֫ȮuI|a醶KY(v+1^t3m)k4Ng{q %o_|`O'-^UKW?Bw#24"mTWïk_Na.Q@3`h KixZn\v{9`i%{OϿ=sZ*Pl1;i'65êP1 b7ss8.TT'6|?zD@J.~|A'iNWHmヿAuU_LlPϝ'ORõ\69+f ! !SUgfɇ`ك 5? CHHN;~N %g- EmSgCwRFâB!CEߚԽf a%mK 50PCe(a5+970~ϝ%|ϥ7Jz4,<= < U8};]޵U͵Ga1KR&,Uyp$4rʛVPNɷz5=+=:햡 [4y5ws,OqSb+,?@^TS5eS* WF` \[\R os{O^N1+{49?cHreRD:-#ů؞ ӔJ'.b_e%up!K.(͟%ޑ5P@t Ku+m2V {0ń{ D{">Έ܍*rN#01vRFG,H9qbY^ZfV -'/Kg'̵i7a'&^r&U ?+gD0ާ4sY"-xˉr]݁l[J:m4U2A;Y`Iߎ A(S*?L}Pmwa<>"@nAXi=C[|R,cY#ƪpC.jU'MStd1@[Fbv{_K-D 7VnU&L u=Ps Jtrti-kԼw-t&VcّbYi8d$yjڔZ{13LM. "MЯ^;Z_yuH oO,Q ^P}a}RTœ:b#J~JXth S;%j{hL5JnN- y][Z 6^na1:N,4M.5 z@.p=XErd@J#\miAnb}1# 8%CkR4}%&8J3F8 ;GzKVejju'dydp<\u^HaS}xGLՋ`@lKgY.)ehDrL63Vb] f8韔QLv=j~UjVZpk Fɦ-@A]4W1b=pNc18{Փ;9 *ΏЉ:58\kmCT⯯h݊ /+<غyXm3>Ӌ@~R,[wo"Jπs_R-DL֥?l dA'Ӛ4d &n1(ۥ6b Fy!A76ko ?vVZ55xy.3eb/`"j `>(|˟5eq9"q1:,PwoBQQXHȹ-A1/ ]ΑQiTʈ)bF7nIj̥ix6Pμ8T XZh_Ku TLögjeH7 `*VŀtJ0rO74t%"uCEwHDP+HE6X2Fg0[2%&ਟRƜ%92 71@+՟hJ|P+Z@&~[9Gú^{_u^KDmc\@_=NFYDgn/2  sYZ!w82xA`C2)P7uټ&c}&xX ʕ찝-w~z;90FI1D$'b3|UoE1w3*Rao 糢A/ci,G,ޛ( 5L{]/ނ`fIͪTIcnPo \%@km{dB~`#Ѭ'\XgLxB_r\S٩)@=1 ye?@m(lCq#mm8jjD#ō"|1pjGaė?}u5DX|tydeKU;rq(筽3h k~hOT k#",IM)Ҍz!Zk!/邔dy&ж%%.& .q~B1)D B~Kc{^/x#Db]Fe+ְ)/)Հp5e$VftXT"'r\E}@>+=+si^ve'FA)9HCyX=ǘ,ݪ?>kfI,s%p8-|7 $:ҥ :SU ^f 0`XV+">h e|eثSZ]~޲EKZao˝xҍ*p~ZF1[ E7F<Φ @o0amB6ȺJ Ԗ#Dq`%}C !._fQڗPm}*0HӴUBRi3'䦦F4St)ƣ+gC.D9Xe6t\['Qd}þ/%r O8ZH > V}>S+~wS)ݙl5(B|)'a Wؙ\`fJ*vޟB$mc+\ kV̰,:H}b[M8 @nAgXӍ Y%QRo4~m*Ϗ3wgg~/)!6>'9GI4"ZT`gY %.{!syx\z$aEKEcDNӐ/D"1 TĝW.Q(=+!'%hrnilYVn H+5` lLWAOfy,o6$+AŅ+Wa*6҅'g$kzHܜ+fNEKh'aC6EXH)UPLr] }~Ă2Mv!2uJ4a=Q Fp:ngY DϒH+BbNc Onl~hRlLgHqRcq]3V*Ƽs.ecaz {>Dxї̨{?ٻy *;*7#,FK(wyVϤ9IlOp]йM`ShjUDžٚ|dtO9XWUŽD^OφBʿ}yb)*Y-Q}abAqA?!7UoJQa"֟ɂ+m|u"LiD/q'å~dN3s4Ou=0c2LޟUX%+0MjJ`X9F&Iv# >AdJv6@>Q$0yRB7;b7)Z!W,r %|~ 3Jxo r)C?2s7*)${in"_H6jcS<'%iBÀP9cHNk,˂=Y'2ChP1<1W~|(e; Hrta!eO&1 `$HzHSCsJ-J{|b(0+6,ͫ6:B]Iӓ:F o%eWMsE3ޔ1}4 ?GgZةoq~zݻ(0W!Qw[ D C)Ïټɸ]̖Ƶ*$YB'T`n,@J%r`񦲧yZfHVLr`Öl'N^V:Z/WamP@E/KW[@-Ѳή[|ہ׼h, 1Y k1<%JVz_ҶS$#)̰DXhg'p]+w%ATnԽ@X9嬔6_ʺ&ԮG0b"6b9t*FpZ6/ﵑ\<]0@> %^2U!DR##\3Kcg{u=JhYY͝M5^:7` 7SHAx.͆f|>H,rK5htOq |J +H2Pycm4.&a#Xtߚ Uh?{=뼡 RQrҙgmq3#Csw>4js5?2KGg?+A\Gk\J5awQڅf0{7;Airz +5OY3Y3ܛh5u-ocZU&6c&6 ̞z`Xäϵeh4&[~^A)0{CyE&ȧWbs(wKCi1; uhdW9i_'ykx@"25ϳ<+,0Hgyy]%oQ)1sa2r!I64S݌P΅c&<~v(t;yecO/6݋p|gu{2c _#E6}rݡĪ&{Nr틝VIfMg&V'SaI:ӛVb|:fH{n f^܎F:g/Hbܛa/VMWZ]5waD}hc 1To-̵!=]R'/A QFŤ3A aÍ? k =WU+ȳQ0~Y^䣦 ΄SM&LUOv)S,r_9V@2Uf2ʁAt6 2EPlC2kJ71<4IZ|n`ecޕ43w`o.} %52+NQ| 0:\.ѝNH{sEb jB(>] iQ*5G fn׳j9MSR5$[F2}y7ga%oQ'p,~YUfmNla.yb 00%:w"{c*RZdn}~̈ t=Oo p5yNިL@ 30OO K.Xm:Q)%Eߦ᮪{;{@p# F Sf9& j?`pST- W ܬ, .$rbW$v!UD Hi. Cg7Ee1js[wDJ.p(QG%z8e8=[OB=2lH7s1' D\P9GmQYRd$+`d|wHׄr׾dlyZFh/|[n Ԅ+k^K)Q9dop04 `9;_-v6nGe!"vuede TWt*PqycZB[GŽs3w&Cˣb*y =heX|LlEt 8ٜ'-l7boJNX1Lĵ=SO$Vw9ܼv?odžf^Ԏe"uh/ߦ{]cG'tnH.X@B&,]3,P:6MLD72Õ(B d tJO˪ c qV3x)1W;xsD.OuC "+-KW16 a]ląGX0?XPʲ"| y4Yw{xc";5hP,֯۶m ~1_×~cgI߆'᥷}JUq2Γ6 $L,ȟr?"b8ԏ ozy Y*u颳~c0vRu8fad/[r4!i *`6t¤:[&>K#!$R eebyBDnr:-%,0 iHJ Dywe: sy4g+C#Uٴ~ #(ٸt!T2W/d,4.w0i!oU7ukzaWsx#-L(שHάӇ$aEG-VGb%ă>r,CT\LF*>c]NAg䂝猁[D^q_mx>#dx2,uf9v(#nOCGvJݒVD~C%mȆv(H;#~a‘V30\((ĈL:X _e8u)TE}F"#q.TL0p>.[ON/Jei饬>{ZFV`Siw#mևwfXMu͌btk6VFH7uUI3UUICY<(Qu K#k)a%}Hœh]K$,mOtCuMkpʹ6l Sdv9y!q6}?B339 -nuh{7?/eI`DWt@ 95*sP-Y^Ty8ߥe\d̴̦EȐu$zPWԁ=k#`X< MxsBe44B@EӾuprf}y\ƄBi5}Pc` e>B KN "2]~SS>zE42;5b&Z)<7Rn%Ґ ?8U۬ 4lE'nFxϥZI"C#Ϫ\)Q]df+pCD7T@)luI=6K/B{YH% / M!I**&d\[.E cR,J5TW1轺0e_pDgjW,3ܾjD۸7;UPY5҃.FUAIY{W<%96 GietJ޼\LB!^TrH(ű~y K>|,pPq+-gLwNtz?՝*F(@;F@DRQWc!GD?fۑBjDKNwo]hHҤy;HDZ`{i7΄b*w{IJ1 ^p&ΒKHu.W`1NJ[[Lc7h i ?q=F[KXpH;EhXr@^p Q!YI e%w_3x= <+a ih_z#& lqAol(BĨpB}6ט58B`1SއZ瓝t4'k<5 B#@1o ȴKأ\ 2T_JUr4+[,ޘ {ؾUj3gfkWQ5eOt-9H(33a!ȥ^*gn =Ǚ嶣z^Ha~AtXͫٝk-sVTz$K|cQ ^mFB(\Q[!t=Qu /As|.?LZFLWCJۂ68MC< \ bcsuZAk1#ѱ8N,ƭݒcULH9U{எKnL!B=J_ArzXABCZnXe&DbH);]h}>0rSG _ r',Fݨw+fa).xӢTEI1D.IG}M3QUN^<_>G#8n&Sz^聈pn(~:bVVٓ1UN02H_m%鴺OD%TS]$ Nyqj&X=lg:G am `kgZ:>}S(sc9dy!"!D\AwF0/AN&G^ eg‘+d@#-ܰwff$ =o4OŰ屿Zî:7fxΔ_ޘm/3t4ʆ: >dizY(lƈiF*ڎM?%{uE\$Tt(HcW5H& yBG0*Vʩ'B4d-gW@N&u/9?C=en{Ԯk\*5:wWz q߽G!س7^͔n{<(³ 6׀צr-=bpo~$85%ݡt}.o}]+Խ6TGڿWGah ]nj{CR+Z, jM)8TTzt1*8_PJÈ-w Fc930l4pz;YO'XYipiO$zFӟS%oX?18VT[CĦ3ɴ\m!0r U܄M\g`ֹ-KҠ ߟQYLNjBiSr:_JQlhúrA@hirJ1Kw;4;G@PVnE9=⍅` ~ X/Ϡ9 FKduVVN]64j;8ef. X|h׈^=՚ $s)4l}Q,ASf><@Vh)'Y&qXR pyu<\˕2I!۞g.بͮ-yיןi7;ډyD˼t(lI:(GE+jnɎtQs8RNq>2}S\mMxz@\LP)ޒy8|4qkH![>Ρ!GR["x v&Pndgagl5*c4Road6皂|$л_2!SLJ7zU|e:n5W1Z=$L![N؟ On,+ޭ %/Jᥐ5b#v[HQ(PuT}HK/;oTKQ(^[R-.RVDԉT ~dz<|vJ9B# GBED+CD^2 [~4PRz_yb-hZ+~j .:AXn2{&]nq{1*օ|9M۬'^`*dSSplIӇ P g H 숂$ܺvn-g^s,:F…h?xETvb(ECۋ6kX#!=,> I4IfebYE4 s x y\Քo]<%| 䔰R"볂(Z"y:ёHN7dcpX߈kv‰xCg]͌~|AEuD q;Mv!LQ*> }La!c{¨nBZǤb"nznT1@Lb"2[VU!8:@}Ko'sI]Bbm>gLygBӌp@kc!>y vq&=MQ[6?5`owN7l<0c.gS͕s> EA>p $\y@Vܩɯko_$ۇ.l:hط=S_A1sdưo <s#e;` :yD)"i'գs˲Q/""7 QkMn*FznfI; >v 5)B-Qs^[dZiV-iaW9Bر'ڂ)-ҴxpId@?a&Kx-dPNEtHj68^s& g͛zh)]?6~du:/|;YQ'`C1b`FzRc Q7&6eSdŤ5J*6g2 =nAUQfcxR۬YJ6qُ{s;XO s C[Z,]nk&lzQ+>KY]`!8Nӆ|1h|v yo%'A]H0fSad,a5TPR3C} euOSZ(\K@`-Bk a5Ymt^I e#77Be lU 4+fDHPOnV9jX~jDma9Pd['>$NBIR48- {(\o|D!EiBLH%(~t2+fK 0Af,1>XS;;HÙ@, 1>4Z(`ܥCuRh; 08/Y牠ikB hN!;E vE̾Xfq:sBK/4M6]B7x%ɂkS\Z@ɦ`5珓H`~V>6ѝ4G:hT{$ YwOڊgͧ+uɵ4;)mwJn!A'xpJ|H@#6l~Es}F#8 rk? מkfyپDA7ed0Q[2dК)LPmi 3›.p%!nΓ2|#fa:5`L 佈Guj 0eԳT3:1DJ$NyF<./jeXb~l`2.tKB+Ϲe;T>dя(zD\G绣dZz1nbG[}匠]'Vbv솠}m11 Lؒ@wbܥZI 㵺&R)ha{zؽ}LU{͘<-=z S闏|ׇymXXIŢcS,f^zM{Чӥjh}Lj)@U]y8!O=c Î#uGʡD3o)Pr88wBݣw|%8(_*l#W:v6oÛDwK2=]NVڬ2MiH>-A2 H T7Ȅ4T `Mo'jb4h+֑Xb2:jƉo+ޚ4Y?om@BnS@cC]FGDl&-'\Ғ5}E10?ؿOUP>2#+G1#0&U!qS4 *d^=omˋ&>]kNR!"qɺGn'Y_A{Raq& 3n4 ^J.ʏzFLb)q (N9A \p+p\rUcơs H6OE&hLz6k$^_Zd^2Z:H;s4TxV9W{* g&ZЎxsojڂ݌Be$¶kS~\m 3c]%vUGS$~9 8EI Լ^QX<%;qDs&b'e6|-6RJ)V0sQȏxǐً;b+7XU)@k)їZ)[ }H~85u=#i?q&ɖ)eW meF ~ةXtW-< ɌDּfxi]\amB$E)E2yۓ<>E퉅´"Q/W7jSF視v6|6,vrCl<$d9,+l:#G4"E6SiJ.@= SU0nO<.P#՚&U+ "a=׻DSiv}grB2>Ц0\*;* lby'P[L"?iHk^]pE?|8 `PȬXZ*1*kLEDɁݛOӏ ?'X\OPAZ=6BAh9> 1:Y6KnVpX$N' IN֣)f6TNHץ*SzI@>nGCwŋ]GGsW Bm2 27f|dLK<[B% S[XPh jl' [fo3W3uts,mɌ4n˘xjRx2+, Iaa sdE!)iMAc׈8 L/n?Ն Np# Е.|RTaq"4"7tCӎͷ {ymQD}DJ֋ccq>4[sOt'[: NMb^fjԱNF@/64/ywZr$6C7WyԙH}o5um=% R6iob+{W<Q]VS* :'`o»PܕS' RVkx˂c}84+JϋѠ?i@KvLw#pH;RFKf9|,Kl-Gsh?__F:|hR$4|ԃŽ@ÇNŌ.iTrnMqey I@lݐc?PtCuF2>wɖf`H / RY3MQ|V!T[Y/b&b3A^+8P-,EMߠCH;` Hnsp.hKs{)2$8pq~HդxYUq=$dx sixrTԌL?k<mbrr#,C5hpx:Й[[TQ??BQ4^:=C%4$e ܊DcDk3+CF˹2*F Kw cqR-}}L0QZ郱 fԡC}?> 7h׹b@%2k(*!{p $[G^F!SX|'IJ+e*i+9Ha0UJfl172,Jj𧑆's[w Y5@Zg 6z*'#ц*Zk1oSShՐ r5 lIP}$ᡢ$fFJCumI(̘/oQTbEx"C 76(HI K/42ܔx 8'ѪsM1q˷>>˯FGJw\\b7=+m5O-&XTze$ * &QRTl2uDcji0mGu}?.Qu'2ă@"^&25n1EFp!]:sj+yCƔ#}z<;H쟱( :3߰24 `<4b9vxE bsSl"MTDa6oƯ A$`G˂*}+:BS^z`_x)^;RU)ŗ;&UϥFO[43֮,v{hiCkp#jR*;8})J:!_̶.oo0!]8N\QbM=/H߳KEk* V@<yߝeOsK~Xs&PDVv9ߓP ɫ%%Zxaxt ~ΘlTnKnڷ> ,A&Jr]5g?'l6eBwO $²'oFwIb`G'$JCV:  X=7%K,F& 誛Poộ#k@*3{ eZ/C㰭R_xDV>|GY̾5#c䦁dPU|{ʹ.z/](⾅ W$7rkF8 :[kO!A%QQLRĂ]`.3ݨ XrHrV>Amc)EKv6߽?H;Z aDAɓcbL J풽/W#l6Ot!c{OB~ v=ZױbKAwGu/@tۜ*x-dq3 ]='bfPpu/, La4_/Y!G.x*\ڄD0#۩oiQ7Ӳ:.ge9}56 Ղd;FNkYQɫ")ihB^!x[9Sޣjqg =E7 \&qy@}A+$Dvؙ2\# Q ҆giD`qd+g>{ϰR®ۙ'#{89xe~Ω}.y]|P2;*qM8:{Lkt忉YPĚAdh&浉&ܽމ yWiJ2Bg UObǾif1BY6I+b)2oA2E]UfH/T9؉nQXOZ9Do#O4hրO`/km}9IIő !kD|/#d 4$4b/,]%eA5" ^l YPՅux.x$37p-Ѕ*(Z{<ɟAxb&I?+hp&fխop;ÎU߂sF[ 6e&J}mOlf/`\ ʭJF:7~`QCʹXmg˦7󎐫y8n=JR2,^p4Y!zH מ~H;/<>nK"f ? EVao,᪒OD y"ϰw Pf|svF JYhI? cm !Z@+5w>mkf~5Uz;G1_^(Q?Ei 3@2^eΜ@|gD{X1.CPR@eR#_- uwgYWP+)-ꠥ=NK\Q~Er X G4lj8Mosi[fT=8WKE?I? lг*"f_7 а3psܖNw{!ޤa-\UΒ' [BS"_SհjleNo."D=8t"HcJEQ%SԢ"S4F μwp7&9i K^Qʔ>},VcyLWD lB\zt:`Q4e$Rv] ߍtDg\re ;DKm)8>K g7ɰcpE&l^Хh \% 2NemÊ>S7aA"lF:ʙ͗BZ/C~D'F=evyZ;6!VRɭF$%W3jբ  2t|2r+o`hB^@IBSsC;Τٲ^ Iu zyX)F1CcBx*#|$vh$ʑR=-k- WMD1($U%W:P/ft8E>i!iS(#z*y0s2wэ=z{KeFzuub[K)S`Z=^=r8`CǞ)ڱѶᰑ;t:.VG "'ѻBYPj+! c#!;XyEo;5ſuІCY1C* n()xE QX^+m ^$(WΥTt lB{ҩWg3#UiV%H_Wh^p2N /}n^eYN$^~t=)|wzp*jy,tj~uSK(*xMFGR͞5F,|~Sbeْ£m:^Yc_[ıI tU-/Pu)ya]_+GxwR$[̈́U/2>҆(y cG/-ޡ£@JC4* ^<lCɅQooox`LqAdf<THTg!2A[>$} t \<.T[׺&63v_2  u[kGQ3hG{op^֓44g0M+Wzh㜤V-:u3~!%y\[M*&Ӂ} 8IAG08YJxQfs/>TR7U$DVxFדiֈĨ%ȁq5uk ll4ˤe?%$a&otMٗ83:oV( -fwI_Feb<-?2--ƤCn-SoSB9&eC>O@Դ1iK `cR_rЂԙfzXՎ#7!7CiߨG ;$^~C:r=cOyj`ǐ06/y 4$$ [YJJD7Ijiu{os fEɾε6NcMssG:ZCOr ,[5{0D,uw|'hX*XUu BSM7 @ mˑzv/<*@ #α8:Ee$3D8Zyi6YPSuGPI^˧dܹ?+ -LL:VQA,3'4 rumQ+dJ=2\n-̣$`&DڰxMPTdH\(~tMz(K!{$JLx4­"]F7Y54dӻSH"UBxѰ_{QVc'A\"arlA>T$,qȋFnQ23D`*#֤W Pc„1% bQnV*i12w9wZw0 ;ráKpX]y?fTpj,SqŖOxT@BFu`Ϟa1e(c,L4&Vl{ :!I)0<Xy/ӖI13b>L{yp!*Pݶy[ 딥cEGwnI4mہ mvrR&^7NLڤG4cd e)VMsnB ͣ$Ԫv4l)(vG6黹Da"3!Hkv^LS%zؐqDPF}W/?7k-5Ed[D^{ j'1wy mկrT`/ HW*t\W՟hC/)7X}N"}LvXq)Є1ù:@|g+/ovڶ{Nnoadw>|#,cPBƋ8*B(nGBGF1qT $9'fsNsPQm ʫwD@T w1<٣M-4oϦ0 Uϙh\$rF&GC# VM{tcyIGBip$ClK# *0Cc^.xH9VD{d.I[Ȱ-tӗF! 'zq]L5x/[".`{^19Ej!aeh :sEa)CmR¤eb AXA¿4(fE;[)" .A3g$}L5\S\)0 eF6)R?F3%:@E0|rs3`,VTM'h[ma / =Sż7-] :Ɣ2 CR?TXgOե3hP E4Eӧ-" DݱQK$WjK5Bqr㳁aF dSJK64 &ҜSƔ#s?oek4+"PasƬwתUllJ@ZLXgA{՝*Fׂ_`C|!BL%XvBHm([ڇ#Wܼ6 ")"Ur18 GTdnXM[ TfF?)jQ,KAa1_G!@L9 1hߴy]Y/VWN: $L`p{(F ݜ_jKE8-ԪTvSHb}MQ$^%o r(鞈i*qJ~Y2 Y4)Z) %I2ˀܞ<"J;R xfZNDSH>Gk H0 q礸&MmMS@]`c_9fP&VZr,ь]E@ MgnR@r>0`fYէy\h>M0*.6f$SB(gtp,Q9sWN">4/5/ IdUl݃8H}2v]B/?RC`mkIkoMZdjYJ.e 0[$Վh_=ӎ{!RӻYG?7r^od9lY'Nx)Z=ֳLHo4YfW iЖ~5t(Uِ4 L*To8jpRK+:g]T6ݔo*QhiW͐/Ae\|.;.TD(<~ W;$qOH`*}/ }o.VZ53{JAXiqK<܂:eg'"o>3"n`vi!c>4Qş ЎF֓QENВM"Yd,B#mO1rR9mQ+1JM% PIx^:&^@/G*he$1%N׍1oԷJv *3/GM3V~PyaL ν#?mY]%Qe)3}2&I7W-70{ v`fqH .G35fR7+]EvGБͻ2E 67E/X^e"`s]\,bmf ǨM\,t?7X}׉1]GeA1HۅC[̮bN;MN ɱ+N&IY};Ys.' \R z$hI~|uU{m#t0D!4LZL";rf.>]W5ܮ6Ic=; 6IOTÏH4;ɢ&F i>Q;q2hu`wjv81+.>9vpcfE`n})ylbΊAU`lͬpETscbU"wljo},Lau;{8PP6;x6NR"XEp(Flp>av9K/)~~nL`꛶Xz|ݰ$$m kVh TvWъ$II'OOfŵa)л=4qAHX7E`W_-?wz`|w!'v鋹NpvW;kfI2?9`zB̷o+}AJؿ2Yo:%نjO$'2~Ned2(>yA JzsEÊʪhZ6׵W `#XYGS\!l/qJQC :Qyٍyyt [8^pu<IJca6/Ӳ^:ݞظMQ22&son+ ۖLJS΅&c'#3_e ʪA1# rZ ';7%h&CLB@~u|J$cNEr[/8D"X75r5JC^G=lL^Qi0t/ה㖠12)oq.Y{X汫_)B>74$CZRv(/qpv2s{_SMPbnv%5iuUUeiU-ގ BҤ!f=_LrݴH:} 9~p>qGC`VV|f#`ww;Tę-C핫rDʯsx5p-!>E*mc4ÜZ!-7Jqڜqw%"_2kbm>-0LEo# pFF&c.w zel:zem΁a7\86sͲMٺj(`&30DS#R DV5̡Uw4pƮv}G ~SYC'=|˯L'qh[Rtȣr+:lR(.$TfS 4MA c%珊A \Pr扽c;$W"(hPk~VUv"& cI䫗ʨ b]dr&:Q CnWK'|j?*D?ٿk}M35 #Cn_iX趿] +m/ }}#_Y\ܻVOBݦ M_bEyeIuUfʴǎ?-B8\{-&fWX'JX`Iu72KŇ.[rgM|_&~aKKYs: yXdJ_FGWBek[בnD|+'|~00R&r6*S]TsR@F \L- ])x/_fɵgP VFFs%-#@E|g_|?{#ж6mGd@0b哅PcXwLؖd@+yu ؏՘l}3h$+`yjZ6(&Z|*hT;$wÂc%zD+1j7y5&VYf˘E_#lL( x7RcFG8}_j cb%ƒhkk>c4g0Ux% ;[5f0b!8n)L! 3Ph폇u6W92o./4 &o5IW o)P-YEMm}[r J6R+Lм$ 4Y_XB{|F_4_FU:ACOBx7$q\s!=ٗFeK(~HK4J PC>[^Weim*6uZ=mbcj1=,mkM28~y_>_<ݎ )}P*W<4 uղyb8rK#1b9`_v2D׌ q ]y\_!{mbF64 Q "Ù!{þٚ uʸ.yWmUTeVm\xj%,|mrv_zBACCƂ)1?RtԨ1c)zNFQ'_ݼDžˮ>Zk6\ZN8X[)j9(68'Tg.mWMSCZ z$9^[3[r̻deR 8bbRQS򠭒Plo]ތ D(,v|7k&%?h]Un@p냟u:dgBXT q\աR[+ec)tP7ff%p|t}9v '(wJ͞< 㐕,>'?cd\Yll>ay%RDb6(sX@'Qht<$ፀBX4\3ºUZP|EwI$H?%Y2"ɤ-[9:yiaB۠P$O'C> 0ؽp$ R9o8P]HP~,$KLkṟ n/SI 'wNTcqs9^h ~!70NcʌQKz wg0ǙG:^m|'`?/ѽ ҧBBϢ .hC Bvs6mv(y&bcf\[ۏ$a(a5R2 R,aE*|{VHYR#̢P.|#ҮGQu9Qy?W$3T>wVyiqSe<+*? }MŮL|᝚n - *_h9x(G ďuUu;4~n0~cvWd[EuX|a8NIҽ[4Ϛi؃HإǬWV`8VLلn]S|wbf;>@oP*,=5/HJ=Vl< 4t1 PfֹS 5)"C|IJNo'gŲYjj]ϵ8xJ1[ D3u[v U}JL4:FHN> *Sϭ \$:Q^(K[;W3ha%kU m}gq"NbX/'3+J{aM.U6^yn}ljΒ 2zx6ބI oƵoCB;!mǕ{ĕ+\T1EEwXY1 8;(*.z}>Ux2=g歃rOIĦXz(*-=~a ~aXZg!J*ﵬ0& k!uj,-c^ۀn"OZé{{p` 6} laYvb34Āޘn{H+sSv bwl4yD1iTiI2_91*Yh?j]jz+1' 5BqVr&AKS1d\vΉ|[Λ,I,v^Zp͹uaBX2I's1x u+J72iY\w:g#QZf|5 ]F>z |=puLT7Fɒz5řh֭Q$#a%'V \:4ޅrQ= >,u8a"IXckџ\Z;7$<f:MǢbtAS% j&c|OJZ;~פ `Jm`tdxvi6{#e~WW-O9^_ 5J6[r1X hߨN5=q'VN~ό M?R`ZA"}1A郾w$D;6k%W(oWĸ/Bqi-iıܧl1˜Q"SS?,D eT9LE߲ b~f_$Ҋ3MuX?kN!`p0&Go ˇ^u@#E쐟K}S=+YfKd,)G&I,z冏&/j '|hJlC;w\r0f^t*Gm!ȚN\9EYU2|Z[[!G &獖Vn&R񭊟 ݮMLKN!wW.xgO{k=wᴇcM^m_ZcCG=3h=>1\' tΧmRhrw+jDE0`OV?zzAS%.eUOO`IDt m"ݫ>t'ȅ!(`t'ٟsA:Sg%ZX%j݌MZOT8lI-THwv@8&B$Ib%A@=ӑ@)Ii6{U(䫴)ZBE}Q<'IpK!IMM  tkOWtNMoIswb, 4I gjYnv0ȥ_ 2Ԉ__go˭8[Eż.֟)YK?^O*أ1Oݝb 7L\,t^ eEjEFy7wqzӋ$܀ׅ/E*wÞ,TK>"3I♹XJmӯ~^fY eV Bľ,aw+C +[K(ANݠk6Gmk϶w.hXY')\< jըP8%}h!yܱ;,i P-"Z6:+YM/A'm+Vcc8u0aL 툉xKK#Usr]$*o>vվ;ᅟӏQPsJ[Rѓiy3x />D ӬIӎ;惿i?Y8,}aWK-i`1+AE*r20X{ BrNfzT=F+Rl^CJ #fEO#Tf--K=  Z=v(h%L >lv%7+;/Ot3Y ,/]Ûձ۸pSL.#a]!%M$t4,H 0ae7/ꫳjBC :H=ݖE;?R9 D wΘ8zDmAl8?1bnRgVö=Rt0HZ)x{}ڡbJzL`"9 3a92z@{dA3Ů ,!̘c_`Ӽ{-Y(A#QmnfD\ \rZ%`7z?ms#:C,QgctڠsN6HȑO6;\V@\-Xs%d'oB!vCG;BHƑ/@:rtc r@$0`liR-qJjjY{8]]4$nT4wK j+% "s2up3 wWf}s` 2#m{~nu&6C-yP3zQ7p"$q"ƻS$lFQv$ 68}IҠfI'n/&I8Z(n61̛y'ŵTpF?-ˁH r1}[^ 2G|c}HwY Xr=Z"b(XWKQ7q=yZ 嫤ye:Jc^`ֻ3+aQqylG\?H|AJ6i}9܃'aq M/x[44ѸkO7}P1 ke6^+zNr`bqRrLl۩ŠAH~;?~]QVm[As!'(gVjހABn\-rY).DzE0F:cGn[\ ؚ3Уzx8?ib?՘)Qf,Sz =+|U=6?=I u{Z?,)`'V%)Q m}[8rW?0fmUp_+ۺ7% V(MQѾy9%h~9IBt|aVBRmyڏbχG:ʗ1vTf#2Ȱ+!u^VKIjTb% E3xl5WIoM+DHcOٽ;H.:U )V %K8}%epNLwIN@SLMeFܕtdBbw/ҚB?A=vhs5s/l\v?x4Zk -XT㯵p6I@B׳')jv]TQx)=RA#As!&1075y'V걱:2HJdSﻘ*Ǖ`nH0ﳐ㊦u~[ A\-Tz&4~bRDd jpO'U*]Z1l' g<33t+ AnB@s|^ɴq|4%%,c6aHt2Snձ,P I2V,S[M@gW_˂y('׆Ǚ kFYiJ8\[!ׂB7ۺtgr(M\oЫ۟A3O[MoA Evlx!I*Tmܪpdd[!ȧDM={o,4 iCg%f5=N+&rprcSU9X$ q #R=bEҨeqV;tOi[ña$q)p`P:1G\X/ ' z Y5rܺm1ϫI͖O0ץ$;#_qpT:jˉPO>0O~Sp@9o:Nkx/}J4HP[.&BB ^?LQ,xQ jXs$:GaVD|{R^[5#_jxc$FKZqn}b>P6ӳu'p:}GA/ y#*+ן.Fڡi݌IWeG38t:gW;5qM+!9*E|Pz촰8R5"%k+9+ 7ټTS1 vctH|ޱSg.`mJO:CnKUi*i.VR`8`G7k`43rX{1/?a;OPVP y(%~e i*V0@U(i'YҸGx<-q;a|ү PBvr ٭}]'%` x6T*m x2M$Đ 8Tbesx1Qi|u"8޷@xMN`ޒ| ٻ.x\@t rFضd=Y;2|C6YD\b>[o԰\8E>9^C)cƐJe$!؂`4m3R \ AVD9;*)LzA/^Q*'^tV9>`*ѩe6U1yPG;ƱELؤo<ƞe3 N߹dN:x-Kn"y&&G#!Tk5@]׮^.#rl1q%  F|2M{T'Ѡ{Eu6a]s(f>ue͎?N[n@{ڷ(ޜ2F,Qnh}8fLr=+A'3;]ox[/t'C+a~]l06Ž X-+=6 r6N 2 4fLHT<լgY!VʻEubzС'eO(Z#HpOhZB/9e<6.qd댹Wp2 :k\xMF]Gcedy {똵`E%e)[E(l<:bKv5bT9o@YeQ+|yj|7 M|6D-TwYT#(ӂRsU7\0ִҰ<4I}H`_| x8t`E,E^* r}_%% %.e;&,w >Jl:C+,D'䒝JG+|*sp18j0ZY'~+&3JPQ en_eסbw-¢&I 8#;<dLA2Sl&o/ZӸ 05usk)KP -7D뷋 _չg]@.qWk2IH?)koOԐu$R J~Waݽ5@р.ETj9Z?X`zs+}80g 0[aS:p`Il}(;_ ]Z31UPLɛQh.e|D2_ȍaLUd fG|$ |b 1mK|p"}qbܚe8yqJ'aޛL:Ap%vjL(&Ex dLz wc;uM'(I<ي-l%ą9~ĀG0Ao;cwò@SaT~z%E*UGRVYSNF F )G<sfv%>(g.$z`ū/cC_~08{Z+13] ȡ7Ƙ>Wǟѱ [hVSa_+:d~8EBűGU;#.Zɝ`{csOk&8VI+1lMQ Lq䳡]]JE}Ebk`"'S#H*meKT`ކw\"21rPK#(n3֨<ګ(VTACtj;Bh:rk[QL5p)w 2;& >4 %9mX+i&((HͦBZ'`"8a|SJrK 9}α~ϳ-,fcYJ1y3QwƾFg%hUeӫ>R5BL?3QX d3fE[)5_5@PA*G(lY֎0}}amV~^ "+;M $Ǝ"\B9^η*m{Ĉc-˫/HmkBGFtx@ClFT &64 &H1HTh kՍG@fs>m S"ݿbkQ?Hg!Z#gc0pg|0يuL%%E 1>PaX^jGwtWbDU?k|ݵ'Ia q(kf-')IJSUrEBy-Q뻙ͻz`QU”idViM Hy8 -ShM1p$_Zm?!s|A*0P &[O4˷!I*% sIC5v ^/r%Rm)@Vdw8%ddc 3krLait,m<=BqA x3<Ⓚ4@)e] [>x1!e&k(kYy_\'绂Cɮ%r6HV7_)9#u\ތ֥. cL4~` ؠHU8ffw|&wr2>m됴. Տ%C<8}W`$fFj>V\@Pd aJ!6ZtR3lit4|!d@nj((G3u]4g<3r^h-;JyGg`[iӹWq^9"*ٶYvPJ#kLT+`+y,MU0,EThXKw(C\Va+!=?D]S`ol`aFG~OHNȤ0f:0(%P{R%8V@DHǘ]4NnPQLK J6?H;FL8A&[jVkAyT\_Oh~kErXkց Yxx@L.;pdjqϬ`U;~&R[˪@+Q!w!k}QowN}#egeaKD#xé{ 2M%_LD J Gjt'%#f)tYgvhՖQkVB眈vRj# 4WrroDskbh6Avvrw7 ]|,-v1BwH?*RC΃,E=G Tk?]I5@ *C,ŷZ om|o1p%1Ya󇎹tH<8Լ'|u(mLw}!hvQʛ#>*0~E eB+K_ǩg:`3ߥΔ;sIڪ4C- ['isR!36)DܽI{P$ZJ舗32e%ʸmWU^PuR=[om_\nۣ@S㵨^ʹھ^LijȀ>]XrLذ7qX"Rm?b+9PYe;ݞ / 3r] Z7e/uLTдdNאּy5򜔩25_tۓJGgcŘgSp}){$7@ܾ &Ƃ2樤~<CfcU[ꩾńKte jC=gWAf9 }N(뱤Bmu *>bAK+FZ2,YRZm/tR;6{)F=aؿKo?T?EkE!Of?@l\z:v<`?:"#MPA*lN[쟪m8UAq!ȎMdcH|j`rӃ'KKX¼/xNhTj!VL.ELGi-Cy:AjpwlW5JFj[\۷Ӯ+*ہK+I/y:W O[p"Dj3EwE"؜4mk^=Oy wKV'^Af„byљE/[_mp.~$V3rDjo{`NJMmiwyK!,Z RHbѢ/w~TBIeJdT-؅J\L|y5=B7>0(h;Frins ,lHP1ɀO2aOpO_`b[|>K5[F:Bogs%7@gAmcDC%2CI:gha8׋V{YWY-&⭎_tlx砢+y0!g:a.ӜW=d=oqX䠄:=Cn=>|8k|s77oZ:mn}qBdT^xwS[ B9C.wE"EkH]'?4S4k%?`^d6͘ߧOfG͘x %vai}oQޟ1tc p9vB'+ OAg\9'JG]ք?~hd2OmWd䲰p߰lkjiqīg,HzL_suêqج83Q?M|5o6Υ;%(3_JRij* J8)MD\"ԹޅemL~.pfYߛC-.VmnV*Xv4= EǧPSކY>]ș%ѫr.YbmW,anyB*ה[~lpbZ"9c?rgI("@|_h$ #:@U`/iy* n,\8ͫҊO7#DF ^&Œ{s@{d9- ǵfוNbn:v~gwSG'#;(;3+iI373?9L=pkXA~;4;cw4I㸳9 ycFNIa' t x#X]PQ4{I`IP32:B_38؛GqM{vctP="8ީсf[,KzĬ^-̛]jhb2Ja9H 8⅞gg0vT,r")~y郱z*e*?g*5B߉N{u:^$s絃oq-.ic?O@#4{=(˴s[Ax(mi߬_ ]뽤MLb4^hjT2+ŵTnL0x+#uP`"ځ:P-S 1 3ݭ|'y{gB0z Grs:wܝf"=h4ETLaz՞Ay`<]P]T<ËΫ0{`oo%hWb\n}'b‹SY 4jm+A(k&t[ Yq `sM .E ^;LJ`GEG~(<(`F4l!g@K"MFՄ#m'{×]=?@,]͕HLъF)O,fmY-7ƋB\3]Lp#=A4d ҵbB4;>6,j&yࢁE ˞W}⋴ۼҁ"ap6FTsCk¹/GNG5'e( 0/8S2:m6w,eq?HFw[*!24V-$ư03TN=F耑!Om2ԩ?+ ґ@ly=]JAR(BqxC@$Mx)fY ]A_d>5k29-;tXME{"Qdĥ$gx1>|95Ub ۭrz6?6\z˖e:p̆v~[<1 I& S2WqM'S` \* n4&c!UO5etE|9\U|*]@Y(ؽBM { nx{<[hDx"-I:unj*A 񤰰i[T[pm sgF D@{"ة~c2y5/1Y oMPo'VTba%TSȼHR'zV\|aibc>qnGBMCo e|y2mGϯz;x͝GfՊ_!/KQ[vB]-υ NQ+p]0<}OzRhaV󙔡4J-b-#ٱԨM8ZJAlKs+xe?VB슲 )O)f uί:|MDY|kj9muVj(7YuwPS!;9AμYFbø#(!d5%IVL$~θc7,#EO()6,\y~ߺCfܰ7k+)?n'cn[ Lc'Qy[mG|WX`%򲐴,NJXSsksΉ@\ t0wKnjLyA' s0 p$\#JjN>x]r׉ E8Cހ) :_R>Hy!k+@.9W5j2{0*Òp/c;Q\2"OH^\uH{`8YHk0SJL3I}K!1q`wp/P-[/yu0]z"֬׺ 9*`4_+~x3;1&G3Ȗn9K@r뱇 Q,By$Av ꞀkDsѐp(:zka]،7 e{?=rMc[z[6 +]D-N׻ɀ ̀ A>kI5>'6 oHWvB|;K7G>{Muξ?ߏ]1~}瞗unVp]mINΠ9 cJD$_[:}Ȱ!Œ?|b"}C@CI `uxRyȽCNȫ! \i-qW,Y&FfqX/$D/ Z-wRjJ ;ڕ]S*Ԅ1icJ X,퍘PDx].Ц)?~lMl A@!{ +l{*?>D<G^b#^z̅bǺvTL0Zn`if!df3#La cnzlrb|$] t,w>*Q탽#팍G.A.wخ8J-kӻoؗvAUgm+l[H%]StQdF&m@L#<]^]6+P$@XYo3dw?[7FQb! rvDoF#i5VΘqٛ[.as, v@J >K-CxWfTHd"N.#/"t zjv{כUjo,J\/~@ơ#i_1Nlr bL<-cJǶ)WW$}B6$ }ZI'T'X L>Ged(Iuwna9P rubZt'vu䭼] *NMƘċB?<ԕ+L& pfMqb4.C?FrE~Q-Gd( ~呫▋`Hm.^)6VCtઑnSR$U!7Gޡs7yvs6K]P`S!f[u N j7Hu ys(Q$V܊dzJrhvI ! 3v)e:2N.Cpݑݽ,0N~aF"\H?< ~׃ Ý [ 'z Kq7~K˼j/AG7.^lw,Xwtȥu0Wŕc\AڪF=lP,pN^ThWSZ;yW%3xZsq$?Q 2^Y͜c&Cp\#ZS3jLWu!@U9 !Oyz̵8DJQz!ʃmœM)ICnH+"K`ٜ zf{& (S8z޶W4+vcTHMJ+l܃e>vZ`k"7Q%!w6aj,>V|E``1jF̐"f=QdĝEwbv L)"]?)7*0 )E-MW8泜S4+C]:n~2F#;*nqhL@LN+(NhV]׊,f5cSA!yų<^aHVȘ:d_/HlIu?3)!(or^ AO,6țI& MPCtG֨~XZ `M8vFǦߛ}Õ6B!$9G :߿],fW@J\ @V;bL\+Һp~BO}*zFJ}EORP,i7"x(+G}/  nHn7 v~5t|tȱ,Xj-] S'ACژQ2fχG%b2avY~[d#!8|k2,gJ" q ;0UK]8ϓHӍGbuYmN)I.X^%VQDtMt^"i2Cې)TQ/fDe$e*#Qn49Ӑz}wF`Wfm۟mط?o{nA y滛iimw؉dth…BJQh# FǩafoWf[,HB}H- XPX 4;}0|K]/z,+\x೪O~h6BW/ˣ^L!M2Lͷ2Fa)ďl Ԕ.,;ܢl&#7pAgBϲW$7XړNW&p˫|!nA%K9 XTyH~ %8ho!01]嶵] /vpCQd(2a9QcRM.ǿ@pnv#gH2LD!:òŧJN2bB(_q1|AABK^Ϳg"vF =:]ph(a{VmjZHC9|W#ǹ+8E#PE)i6nS"BNQ[|Aq~IW2@1\颡z `^C$p=7OcRf-a34 BA";K)rļXe4`jx\)aţu`(y`"co "fV&Ee"}p[X] X4/ S÷lbZN6O!ľO$_PB-g9g#ь )=̀>)5_WĥN89j9f(&>%[l6i_D=x_i4q8j 4j"qh3 d *[5 ]'4BhTF+r? QYJb4\nᬗ j8]Eh418lm7g*:w(-䵁X&ҹ _Ilu^z;"@Z˨JI+˴IvxjP22sr|:M h4C^Q8>q:< v܉R} |F!L,dqʔ.'ץsQ;_t\և.9f<9k3DZe[pXf SH[_Ak#qJy:#0DC6g jxmX3;0+-JEi!m v\?W e%55/=~AAA6hta͒n݀VbSy"5vG`f"i{ɶzeu^4ۗQ'5+q%mc(a11Yq1d;BN8O)"ٹ q h;-(aџG9+X$yW1+tZR#ԡ_e`@wFhۊ-EQövD۴b/l5*#*dy[~_9^]=T(h=(ZU=h.s0|`lUy+AV'یȺA (M^Rѵ蕟!U.~ >nZv)jQ1@٠%(HQ]k_R`VM{.ˍNLI?dgb$TߺՁֱV#zx+0]ba]ʀ\^&d!xp0{'S) TL}''NŪx* JC(~j"5e'KhleYb7_XlY1pŲ%.nRlod!I#mEx8F;pVZZ;Mt0D3,M(qx"WcgoϼoTT ?!Lޡm_u$T &67/8V,/+AƠ] >)kC",Y[7L&6>Sdt&x` (B!8Нd3:Z"f_!=] D+!X!U)3t~߽֮4WdЂ&O*%aHE-7l8ow5,V#@#LK$Rإ0{!K`?U lOn$T%YJ:K:#u[K kA΃z+4E˞?$Mӧ cPlj9rk!VeW҅712'mu#t$10J] 1te/EUGtWr YƐ:v Օ! 4vЙtL*JQ5c?]%{+C:fm&i e 9VVY}1rU^~OLL^lu8'wּjSq\ؚ5`$ x ZvjYs*:Y&_=ڨǧ2@Y5miJBR=`CN]sMa;O;cDF\ؕE fNefm P_WjQ5 VgDKԛ7dqPv{Ƀg\[HV.:,9j,PS6=z=3@#ɛH D~Q*<Ӷ:c=[N9yդGODpJ s8 Ϊ1HgkKqqf#:ГKb- \(%eR| =ar˹A3W mk he_Tbݡ'ߔ|dʬ cO\o 2݂Ôgy>'6WE  'l(ve9Ț_KEF*hlżL$݄hv(4!=&N Z&#*}%~hhNhבLgNO[%bOv0Bax)JevXCۿA¾ƶ,τv9\[ۢY'".$MחگolS>ZϙT3R)Nx܎JԑjfKNS?M&F L,nLnI^4՛}} _ʥD0 ]|m$!CqOZx+ND6n֮g-(w#{lA{jxn'@wƮ,pϝ#H3Q-D'X RI#9x"r!4&&C$!tRqsڴ ~8،O 6QCַ@KI glRՓӻ5.MYcIng B իcDWsHSƔkdq n cX㛪© ozW1LEiiǰ+י,`H ;M듟9pNa)B7F Ǿ g4X$1["T7R]ꞭWJ;b%߅yu0}1觡E XbO!1-vbixskp8hed VpZ531]oŃ:HzxkunT _ CݟbG3Ѻ'?dHzLH)(1 <ڹqzS8 |CrsM+ tfyL3; X҉CHJsN`܎]K4"iLs:;B6*]dzGcCY0^ ]z_Cxe8lИѤZlpX6O.6hw8[;9)~eNU]eI}b_L=+35&ΰHj2mH #8.|ʱ5o~N4P$'bǙ2 ;ҙo#8mweqҫ;&ShI&P1AנdJt3|O#>uӁ;<զ`isÿf60Inێ`mU~d8f: eȐ8O f _㬨T\`_Ruf ;7 =hcjd~Si^MޏJlXhK{Syu89$O'45!'^~XxGTkF.AMtcʣr&-sI. 7nsãJ /)Rק[ˬh,A܍@PPXd2 e,6+/=R̺o1Ь]ؚt0ay?nj7!Ϻ2\{,M&uJޣ#9S;4H͠s5•v C  D[V5Q17q/qDEagU.-MVFi0{ `OF=:Z܄Z}m")3M$Y*S$HC}Ӑ(pٴ 6i`޲Ypr#Ub1GQ>=_HCd7;)jBsfh2}\2'VUu{P/q>>IhMFmiV:YQˉ4YE"$x g+r͢2(*706ӥ%!;57R*6ÏwpNU%f;ޱİIB\biћ[TB ]SwNW'^Tv/uu_\nSؿx Fp_o9/qߧtW]jHB~_C _," aP+pB`H[>C t8w FRJ[rQl%-ް9tmyWȶGbL""&b %0WGWI2('^1*mlξJxy)V2OqG*臲M-L3BW#½h-= DD6_=䖷)o\uZ (; (CE2ew}1IWzE|:P]e)&lV K,?r 1!)Y;LU\e22z!GB<ÀYSL5ʟ4pGVGMW@B9:!x9U''XqJE(ZK0M-V9q"p'qTx;=ƝFۦ1M4B#x2@ݸPJ<#s\930o.볗v0-ʚ+9-0̫]= Ev3VҡcsgE C!l/UvsN1Z97MS2,վGob_Q}3YыH 1.qGBmͶEX-5%#.zXZIggN(Y:)l#qaxELjeGu QwZuf|]X*[x=z'[*L2X-8j.\)\tpc@%=ix>\9>OٹkDGxs  oTn9.kY/D߶"Bc+08+69XS[odit~p¾! D-+3l4¸CX%z'R'dz:%bh:ZRvb69c 4-tP t4܌(~$I";^ɚQϯ,QmaxMVqLwRȳn ?Лgd3j{!yvdc ,(r'HxR׀S)) w%1e7:|5;zFNW8D|uZD4lo:aFު:Z%^| x('uXmggےy17NN_e@TD'Uiv*6hS/UllMs:n~FÈXw/^H[ "jgr8{[o)b[I 45F+];I3փjɶD $cNࣜ6[?ӄjڡ"4m <.`vn* g3^ڱػby ez[[hHPHi0I5B`880U#ɩ;ҡLtUyazC +6"v\ԙCDCo09eT|T/6||$ߒ s)={%wU^Yۏj؅)E$G ށUGԗBxԟMirwzpi#/>A{VLK))6D*3%z81AtXx#dy-jKK>/xiO`{SN%a c"ۏ @ˏ]ڲS .zT'"m+^S'P4\\ҧ~oy&1tL5 )SO*;s:Ҹ18GB}0p0d$JLW䫎j" 8 Gd .`͉X\h%.Pgym(lA@h9Hb ,0QW& =Bi > 2Y{z˝_f&k iwHtAG.Se&OJS@S*p77a_qnvT3YȗQ[bHO lW%vУ}5 FGOt6 7[B=`{9l2Zm~ݩ&d-1 i㮎Pm{Z=Sh;f[IߦOZH@]$Dg8]6ŷAXkS$oɟCtiaBσtP5f*o[cbS]B/Su@rH_R#TDXY[ELFmTdWVN- 1FZ5bҷ9yz^hj%-١:bx?i:[> k?$]a))01 C?"-:D Qahz&ooGq6gM59QP=3'Q@f` HĆUf.h^ASmۯWfjRqS\.])gߥЏz刉&Mp;;TOhoxTCy>SI)x/C>^GNH/r:k Rh vhNmC.M3B%}w5\ pn2jS-x3OPae 0 &=DWPAѡ%mTKn-FZ`XMtU }Ph٬VT:<Kh ߯_ɾ]o $E-. t ccA@oӧz`M8u"qOJ.Շ0)FWUR'^k 㕛ٙJ6\yw$Oֹz|3'%]0n&10"5k5\7K^FA0ߡ\Q[$"pDb˒w_2X-`@A[eW"q]I-i*#3Nwra4ϊ o P(^ɜ&0@sx>v0zǃaDmHN6u2{ʃIT.=wIfRvC'`NG>vR**BBvEAD"ShYʯ8VjniUH  Rg֌1]Mmi)$ˡS6 p[2% Q˶FFܿEsn"b5|`Q!Tٹ@|6pme!rquy!BtAwq}םV$b㎓L"l Ǟ}CU'L;߲Ĵ=nIƫhI;B@=VTIa)Q- >^0M#LQWenѫ2bH ~H]<egHP-7$A|s" JO Z?Q l!\kƳ'DAUFV4[ɐdXJ)ʡ0j5o}ZΑ<62@|8V!n2'bg%1N}g <7,ڝ#/:=f#fc}k\h.ɓpi Fw3?#Uk*a,O%+2whہZY _EqX}ƐG4֢9m7aX_C OB 5aB}C1K3 f ;a}e =%"X |ݯ}nR_A:P|?K *scA2e}k\XA}Euv^VG=KLҿN¸DzN0~j[7RNKFfй>"Gxt =,\i1G-#gM= p(0sʜ#)#ڙ<\ikuj"~( q\%f";o`4ŗnčʃ$= wUy;(47FDwxr53QZ VKk&y|N(xJ(#y(rڝޓ;g+} [qYLt7k.GgТn8Uo4 CQUSM D+}h|+k(Xҽh ̮wuD[[X:J8t lTհnڛ $?DT|F[Vj:.yw*t'OvI=ҥԑY ?kkJ xv4KSnvܒD[ WߏfS nvx-a:O r7}%-GWh6]Ļŭ ~ZmҲ1$X,{(_ooZ*ݤ%t=h n]ٴn^PDA"%A֪/6|`_ DwM}Ϥ^ 3UsYG@Þlt2fFƗ윣|@)D,yr h̳ޖb0g=Vdg<8185,[&ed/[*c<$2U6YuҎ]l?ssP>nO*z>-iګ%'21[/%A5h5)+GzA`ɬRlOR}x»g*8.DI5u=^\kI^4tSONRC wli%y͹rZ@` #ԾYx=tk?cԮ?O6 Amo~5X܅s΄=Kbl%*RJL:2)]Y{#X#H3F.;S,L S`V2mHȨi*n(v^#ULPð^_Q U sPq-u$/Vg6\gS2^fe971!e#, ?]AHz:'<(c:nl&%tUx`_vTn"=hvbwt}pdVYb|ϺKh.WnN%H 碟2-rHL7ޒ.`tfNk8xF<y8Ͱ?sY>떿gy_N*H42\t=[{E)cl)␟T*jSTO]&1לژ^(6/E $ni3 0eJvKz[zUh\2aLԻ'.3wJ4`kqw(Ų2.J.u\bO EvWx+r=lhK{c^u?C RWP\0cG(7W MH\aB3tx<&mҴ.Eh_%Cze-<de5+v 31vhel$()k )sOVSXmn>/C/0KV΅XB;H; `˦BBfZLc^t@y:I|kA6Py ðv .FcQ&8A$?NeQ4BWPsk2ppFuqTC03F#A" =Ff$"--6r XXъ̗^%$95&=tb9-ΰ7 }IsC>[d 3`w4ِvEOI@aGN}zh{3,PuƣS v< i^;zr󣢫Oz@ "޲@t Rvvama .6Gs"vUY?b)BR IgJҒOȉ>+F u~jh6j8iYwHYq@/)M ȇo411dAd7Q*xu)Ԏќ2Mn sRy?cLK}%?K8-ޤAc6AC 0y% R8bH4xIv ż'=kteDKtWCf.K[jW=|_!ߺp?&E2)UZ ҳ'?KYR-ƫE${ٝ% 7??JbOxυe qQʤ#e}f27π/^D;1r2A89"{vCEaFQa[ GFu;N3m܌2hbf"jo{ <ɤ߁ &;ce!5lϋ}}׸4}%`P 7ms-CW]KS M':SnK6j{ *<}R=إeMIKlG3\ws}M/BG~Փ`ȩ&+\43er*iņJ4 ߒ,}+HMD"?4c75 $SFg@lBPIqgC}s'%$ ;>dUVrF2g Cx>@B :ljhR6Lr&qY" 6]`EeK8W?CH WPdI:d ZPH#Zr_ip͎e|bਭ"!=B__l`3kr%M c>^[0n; պnٜ]eqE+y^֗c:*im!9*IU1WF vB(om8>'&Gkȃǟ!iTb1sP&$?m/]ዙoƃQ6}"(qܳ(᭶%4]d/ff9$R+FqC@w?ZW>XO&|-P1햭\W-7)<']Óh'M}fF F 1DcЄTcLD Ues,S |7f|~IUa#7=g.T .UQ8/rqHE)Mfn#3>5Qݯ=DJ<~1n|xFNZ\%Q =YVb4c- ZdxRHQ8b1ne]]Dth/5ɖVh{H8!e?~S [0} +Dto ?q8ygcfWj;nG D햅SF?BNRo2:HYdOISLw;ϒ7?EmA,ߢ][F})^`icr9˕d主6qDpXpĭl?.m`kz³8cCq_G͋DrS2])b?n.y~ {] )L#}M5~𾎳 n-V;J][1܏}m"4iDum8Khl<[PSPpי"km2ob[d\*}\Dfփ7=اsˡYEud1:,ج39uy~;tkҩjmOQ]79ߺW\鑷6їʬw ؠ>ԑΡn ٙna;7f+\\:M!W]ʩ2?,-WMޢtO+W`ʾq\}MbL}nkesDl(D3؉֖LC1);߇:';T :2$E^ ta.%VMͤ/ t K𪟦һ>W-q䄂Jm酏?嵐>,AKG.E}( ~ \TnĈEZ#eHLpIl6$Fny8ycbm%j?n"UZMbW^mOHáҤ{*sbxC4 \o> j"<̺"^dT 0W@9~fH:韇>SS#3 4󓓮1h&38#MYR@j6b_rTjf" 9΢?;< ι4>MH`ڄy'ZE!zE6ZNV v\8M̸9zr$h nh)WQ1ʝ$!:jKWu6Jl  pUIn.GPzlJanįP_ca YYUX tpkL_wQ%aBn%WHAL6z^[G>b;g {\]*jF`cЇP9XIɅV"HGh ݺrn?;Vٳׁtg}Y~;fjp[_6&B,d3c qLڤ x2ctkq㬛`}IL뷳1x,h.- ]qy$;6A1AhC1O9ROW fDSm:jƩ\{Tdi%eܮ5ٍw=γuVznlG^s4lat Q|^y:+Ohg$ 1NS.t˺PH>$']'pdcr8]o!_7 Bvƅo7-mlY >#^Z}>>*G]34M|'mAAhu{VAKxRbb+:6W!~*J@m$QpagKA˞s7.L3]m\|w! yI(zIGsz0aAE`ZLaJnY{D7L^^s~iۥqo3R/V.MFZȒqT黃@729"u5|RRZQ( %X&3T9)6* :)7j8  wp(x OYC$e޹RCYVqv$j]֌7f7zܡ E9-=!7Mr21%+,QoXuk@ jq,ȿC/0P}h7UtѓݷE͏TMV͹9NN'-iSTuȅU)'izj-T¥32$9\b+TkB 1.{_M>io)ˑG9m[P!9+8Le)4? /yM^/&f"4 -o+SF4Ccr+H;N+GZccj~|oE L-PeeD׬bғ ȽMg,e_AoB;%Sv,oCrD(obDue̞NZxwKx#Z 䋾9XF<|ec4z6zT"W qδKE騂.Чvc3f1=K,JqyTO[h)%ÿDtp^hYnoj"$sv,W;bkQ_-ًg䞑=4J!'RzMf`{(mK@]/ 8O9zLu'6i13 3CX >|ނ!6BV-# c`q)0sD4mqm\%Dkl}ԃr<}r Q{̅,/F+rm#@Ev3PY 4TuDfY&b1.& D[n)L*Io R :[e9> O3<ف@~1'f섳3 p<OO@dzO#%WC}c~{R_?Ny_:|auiɳO Ԕsm8 f$|R gӂdGܸ#Œb1*9P [_G*fNg+Z!HNBd@SXC$[QVyT`2?tgYW]JrWZ=33HFB<4T7 ctFYOQj! ͞DN笶i}eh D꽆ޜSSQG0_ץ͙l>q{K3b^,ĂAHꔌZc yK} 8R p;k 6{;(Ae #jO#!b"@+q4b5Uݺ%=Z|+0El aуÅѳd 6bp}40v _ > iǕ1D Ip@t_de?<6ħ ~T_t|*E#{9tc3LO0E 4*gSƨt HJv$z5T'i4$ھXNB{:i⇳%Uk%Խ|ID%Ļۃ?W9k2h\ (FwޡnZYavwbH%>=JZdDk͊ouӗrշf&K?}L/d(>L/1OyY&( 1Bww !0+Zf&45jΦuɱK{b˵T.{}yɣ^Z BAKGlJ uJo&Y:?|Id  ؤq ̘,>gk4)vY3`] d4ZZ 26^VfY¯0Dld+&ץ}i\2%~̐-6>G~V"תX+&fsw#_b\@܎B2ZwicwP ǯEUJPjР$Ε?i{3/2"tZ[W6\D.[KPS<H yI._o DBU#RLyur_It[aU rNcSHɡr܈O6#3,)H^=-Գ)%Ɖ}YZַZ]kΨ7:t>Q J</-ѕ֍bHU'kTLj;N1[Q0#77r!Z:O;ֺ<g w%'ѱorX@sO`R Ẍ(+xE5: Jl+w<΄76Ǐ3JY`mTuk0C0fgJLX/g)#Qd9].oU`Ǐꏥ@UQ3yX;-t^ %A7+Ie5mcC`':Rqk˿da0?lU;U:~L9Tb=yn`%2WpIޣf&B\7՘{CXtZ6JR/3HODtE,;zu09ra+\N3ar󵜩>_ 9thbY:mLCt_p$ܠ'BtO@ [KFF?zpeY2h5?C[i8p1*jbEx "<+M1HfLB ,*ptEL {x@kdRBV>ؒVQΛݝmse6\#G$5kpƶ<pV]O- l:l+j[W?^t{oÉ))BΔwbCBωɫ|AiHsa,r; 5}pViW v@04|'9P\: [ B7yhqoxa^Rʈޡ )h%YΏŠ2aVIhFZ5gHʣOˆÞhTjTVp"7?ښ僅`tVxex>&۩O5F|d =!$mw~MI*9j^[8q'ɬ;!WkޭatA@C#*:Jx}F]ǀx%T,ҳ|?cK"M\u+uG][!k3Z$|ֽ(O#Shk}i\w#Im΀53ϧqR`_ٌn]{:~ 3!CR >CC) %=䑌}F> w7 tdFQRUAN<{ mYwW+!+xI~x.Ƕߌ & @j%u TeoJx0'l M/V2f҅f|4NIW{؃A ?c՟G{蚕Kx g1@l+>Td :̰,pUŤgn߯EB+RLvYZGeFq2ֈkX%z˲T*8-#|L:dxDex5m;~mǮ4qYOk$,jd䎦^*2hG:£i"8}UfI}E;',lp TP#?|?g|$,g霘Brtԛ7mO{&ǃj$K1"hjz7lUF'U_g1q6hX?n%"ПIJdrjq ZL4gPFI[Ɔ7 DY0q1+Va#)w`ޑkwo9!Nlaw'*FX༂GȱL3ޛ}z{dJ9 30aH٩,dž?;?G5Ǭ%Y:hAj|uU3c3ZʛADV SXhQ$V.4RNh{^(\AܲP:<봃?> l+G٨`xz_| 6]k/pc5y=dd@Xhb9A=,"QVP,'9W7- ^CeZ v{ T6a۳ *U]:R;}x6ňnܬ ЏXCe8$-(N|榶cvOY |?Ɇȵ< &9mn'.Jy2/+^7ϨYn [l8C%΁Yw<<_r0=@" ?= 7!^uDy\>BuBr/|߿ >ߤCOv&%3 bPƟJB,a@ޙU/MD{^!g <J=A78@^geScug)3M1R;6'Ä"ܔ[@7cm _OՖ`hۓw:џ7p1B?&Nq0bԌX} V@ԿvbȦϑg,%0伈ԙ0[A7ȡe!^|l$uWv6GcSI&H&| ,'xj'$]Dd eрbi!/T0Hp 5o5G-8_ zo°KOԿ^OKISk 2LB#k[t{!礚畫. tjna_GF6X&,vзLtM%M,|VrSW";sm% F:ghj&dxa\dKyɩ~KA|󳎼,+kG`][ (Eo%)2._Pl>P@[S}诛K7 ݗ6='2yDk6%N6b?p)M ކj-|'zu(о )Rymk_1@13fl:R,_ =Vc*H*z S,n?q82!ܢ7':uX(DL76,?7J5mHF:уev F#lh>H ]DH#W}w3_&BwpNI]17*yjӆ ~PHߎO::u"hiL%G_lyRA.t_) OOBx H:i 0y>&0Pg=I8Ido"& 5Lfi=ܢUk3e kȞʵJ#CpUP0v8@5Q/PܐCO DH]MNEBDFKEqdܹKK*oʶ'Xe PANA.[7 =i&^LæV9'kaRӣBbZOMeQ߉LuXlv{:<`K1VDC}^W+mS]3 _' I>mD]@E$eT3}z[*2ԐmkL#W  U:-!Iv+x,wM sSAp ^,.wPw= N*\,wUyGv(b b_<, 8|n<,nz{1&PrǍR8R Ŗ0PmYcLd$eKHoXmv?]Y P R~4L@;JU$U_牭wCTW|ߐy"GѥZ{7֧܂M}&oઠxΫM>SQMs 4[&.;`^TŽnJ4o+JaQC7}xOn+vFOùW(}M4L$V&QN"ʢI:׈D%ϠV!Jx)L=nLC"`aW@t_Z #0RAd႟u ";`pf85<"(^(WJJ$W As68O8r}QԯnPΝJ|`hQtՆx>g$2Uҽt} vxE^ `d05Xq И+),@թ^ǝUwn5򥔳,y#\Ɔ> Z(םDOKKI6n&AНr'v3m俣ߜ~Xsb3d50 GArj|d09M'hy-@ (t*\p]Q'[YpHF3Ps~jC}0&"_$c$o:kdz˗b.ǥ[^zaRy;u]I0աhPm7C.܅jCcby|\$1[atMSraf]&Ӟ2뤩TG:h]!7ؔksB"Æʄ-\*5cwy%|V /F'jl{)xGW(~J02q_%B/jhѳצ{upiFUtIE _d FbZ.>f'"Pw0-NG;Urv~1;!ƤA)%Nk{ZFKTw3|hnl]q?h1 ,WgUC 8$!k`ʰkn[O4W%|fc[t|NMQ#I4ʡ!zDBaH7`Ɛb`ӯQTI1\)[}k57sc HUPgCLm;QAR2a  r#RhrWLmY yLHGp,E0f&_%xOϢqp4$/P$U_uP!Agb]:l]GKn0l1)2*vACH9ADYIWI$+|<a , qq=?Z UAlpR ĉ<ј%Hx.IQҌ3\*^woӆ>*0QrGQW+k__epKțOfoyqQ,lғ Sn 0n8#HZƫlįc;u $2P $gJSt2Dk~!ˠ2!},)/\Bҧ`9ʿ=S&GJ`KzbKY^Yf'wu_4L',9~O,X_7^C 8J]tYt3T P$ٻXEzu75}|6V>R0 6j3Ӣ9XݰW:/P{k8D,I w ʰ_"Ssq"2; ҥk Z ඡz9Tߜ0]_([XW2J> AUǯ̌ݠ_)t>%V.BLTB $o4 ~^pF?ks4I_\5f5^6! iѻ1nvr_J)Nkg3˹"@m~(!Hx* U'`CГޑ'x.]}5A{:9ӌ0ϫIAMyCwfԿS E?~HVs%_?Som "D+Y#3&}̲ 9C&M/CU %&>:CPyI. :ӔyLo>J=}FU*zJML;!rzDA*ƥ#F7zW}ȐI Gm;nk@2xK%:>ŅGX\80kpKUSئr6dh-:=UY-5@onK4g%`A vtyi8\\;xDUH\nM˙@&$KWtAj5^Z­@V1 \!479b` 0,Sp]&Fi `I #5{T5;-yl9l$ *o)q؊ /W$.jq# 7=3JpZI0Ow|n[K6[ף-PWGTnkz11{ צ8+@pJLՇXRa}3 pݫ-^}$yi΂̇z\X3R jdA]g'Izڌ$Cga`aF,5qzI^25~7qkκ0q~$xP17aM19]'%94ST+2}۵lO^KI_ʰĘ^R/Mk cSR 7K11v6]Tk<'(e)8iTCm Lt&ߴH#: zA_g[f$t/n؜RG-0pe[r. 'c*a5XEN { % إd۸6~usA75hz!yNjnBsq>Pҽï;%P7zɘFTѪ$2 B.}+^m@d׊5-3Uvf\~t{_)|_whDM؜=K@I1nύ/(u|;=v.' Y2hig\D[N9p_ƈ=d~29*)WYgXwWɃQk%X^BY (zٗ6ސTRxEl$yjiaT ZJcO8(qʱ(}/3"!2n  ': ߐ:*L,e |JӶ>? Ni4V1)>U4;)y}j݋!w$VLPO@vkM-H n,ͿaRU3%|h[p> M^"Ǚݼ?p̛Q6])U NK8[2D`*Jf4 L:j8ה[sfb#}!bo9` })^zgX&IsVnZmdycf&r}=FY!zM8jk}CJ쀧6q[^t3f{aLվ{),W}Z-^l?:b|4D?L״~nἏ$gF|M$xcXh[ Y~U<$&EV&uۺlCY9Ȗ'ѰTj ŝVߕ@4|$ͤT%+ua#3Oq0 Ka_#ew_N&b]9X|)Dg!=qG/Lpچ($T"56*e%ǮE0Z,ԏF箇26$V3~;!%C,FGMcHـ}]xb.Ou 5ـ{M}1͖.[`O p?SYyTmSCCYOkr[8 }*?"$x`dKj|sL%o`ShF`UUNR|f3Zkg+/dEA*@簎]`eVӅ]aOtDKjnRd|kq"~ |@0[(׍y*9FcB(WE}&*DG( CH%LKN4uS5^:$ug;J[$:S n/<ի;|XL+ kY<[J #oMZSѥ#lktD.lh16LgGr9ald+zuno0*h͌ acEt}5eжx&FK̘ebH=P#|ZT+S/R4N3>5:8:U$RP/ gƂp%rO$+ɤtT(F TM) =M3XF "K W$YXJUC,˫~ZcgRYZ%hwS[E|忁AKb7Ҵ$AȺrR% ivy^UkX{7os:e AQ^nBPmKe# Bm;N ]L ||1|p՗AqKL' 2f=d1%JxP9 92;atd#8lW&]8g䖹Nݨ?EM-*ڵW'00BR(f6kh50 k9pB7w 1.T*a&$8`[wEʍƃ@LkOQ0;c]G%9t*2B /V `1(*Kޚz{'^I3W67{!U c y"ԁ:ewHRg?:T\kn|@0!|w;rABp[VV]V.}}@]JINpK۞“qA8G^jׯbǕؾDK#uQ5<emFQO:kZP2fѵ=Zgj0ipH+ &G<'%#5?.XgEZ-_ݹ(R#էG?W H#G&[hH Н6Ϻd AV^M-DKi1E=]i)ŭU\ cr`"^@vHFF ItŢٴu{9oȒRz5“Zʞ>V5߫K~KfA4Hـt?}aY[lѽvT%sJ:͝~Cпi- #!spu+ 9)5lߐPyЛiŊ?BuL6 <( giM=# rem[#9~: /R(<_7m?Ŝ@}ZoN:%0x6o /TֶAsE )Uz"'%$$yKsTN ksמjy6?6$CBHߞwgB+YzEh۟$OTH˓1q^TJ'l`ZJ1?3Eb)3j YzֿǑrY`or.]yDbDjU _y;w WU+ً fw.*KO7۵j2ИPj}*LH?GFe TF`T]\mYsJ-[o֤+v6wyV& (%!WE> 8a<(mY\a\3ItdnG`m8F?ˬʸ)} fL[5$ڻYVPluJ oxMrKv {*BF i(dH4`h`ϻM/t-&ڼ*D~c@BPOj6R{[w͟B"o-M!X՟0` 2b)G'uo vA?dJڃIi^ꖫovtx,Vĕ߷(QGs ύ:zGo=oe[}߯ ,1ݣ|jDGtΓl)ns<&ڍTc.8T[V<^: QiaOze?_,Z 2u[>]ڄJVi3՘(Ԟށ;Y(/rW#RCJª䒌іyu}ZЯ3XI'瑟7jeK S^NdtDz p xk-oӏՎ{DRks 20s/aW=#Sxu|ksLnͿ1T@ڳ=GTJ?Qp9^鎿2NJcl䝂~O_do 1!|Hd3#A1MWhT9(6'f猱y HbSN>cs=tu.v: SfEt#>Փ U+xJm>UQn#Knk%`#t~sfTjٻ_HneZ|ҙ$߮pd^gO: zZi8ops(2Hϋ{WI,v2ۀov:\vAX5 wo ğ*p4=(vshd_8|; ۱sDE I9Bx.`NOa"lBXUe'l!o&t@Bn(> 0o9E+a؏mh0% D("Q+)TQ|4Bˤ@(FB0siqE>s1ւ2A*CRԭsB=wn[TM62xэ|̆jze135.&km ~nkBϚ3ocѨa.;z+">_ s'E_m1E;C#,bi-BoЛ{s1\." Jd[*@SNOa>JG%zghU:_g rڈKBFyq'Zr*=OT:.]MGe9kWt GJ`n)]A*)$Қ [VTϣCβ "ʷ;IՖڶ!;)@Y%Z{7K-K-'{-^3z.smreV{F"&* TܪKFKΘ8$gYKui"ŋ*).wGք =w4bVmoб,2g (C-g)r Gɾjp_6xnoΘ0l UĤ|цJlu.-|tWQM\I6tnEs.kQ9ɢB {7~XRl(!&~~T @DL5l4G^\Ieȝ'9j;+}`6f|ICs1Z,d.:5ϓ-(a~ie?x|^wu=ڣͳ96d=rEI2 p,"A) :|n@ô "#'mqTI37* '!6[g [@& $x:Ÿw֟ګ*?,njUoyUv;Q?G׎(+Ի֕(qmڌZK,–*j϶r.CHMA(`Ӱ\mHEC5AVeD)?74J; t7r^"#c#rR WB/fw! [6 rN[AmͭLLl O0mc,VcYK+Vk;Q,Qw$QF\4jޅeuS? !(JEXqVlNMBP}5Ǚ.'7N?1t_n1aD1wmQJ:Ѳ˶ Z( ͬLL %WDz/d# ^Y3у\,+bզГ4ڿk1ܤBb9CÅR0DC>tSCዄ噴ߚH?%3s`h2V ;PiJ0,g m2x1hN~cd>z&x&E-2CT<[> ( gC@7Fp ;Q>Њ°L.B)4LR'wt/ 6'r;t)Rd٧Z0Ϟڱf[%9c^B=!z='k yeOb`dZEݼC.IQXk/(xPaP6tLCAq4l)r)V"'@Җ4n^a?wql5Ů=忀7bDoU lrh6:І52JhkȖ! ^$Ǿ>({(-Ĭ[OV6IZ8 ||f:y{ۂ *0?CъBocRc. /{DA]+W6sAT^2Ϣ9A]q\dzբ*Y} >- +ITWDCZx7m-_uݿcpp%-YQ!N0N_6YM=ٲz yd]bP(yy"e|Z4}CY3{pn_*C1yA2}n(ᒁ$Ir_6oղ` Ji67Cn'B g6X9-]tV\;ʊ<} K[ƚR@>1r; OǨN-5k<&B.R$KNA$niԾ8B鿏%O}f՜=eSM$›0 ل@J;{ƦB)Y => CNJ>r{:+_F@a++ 2 X<>#j(Wt`e :nAH'rNa tޢ2+ޮZY ]zd2hsxYf] Z=J|.w/&x{-r E,qtʷ$hT YqzPxt^;cdOج k;$*%j/WL.^nl=7B\i+E2w@=w 0[;rV]q}k꛶GHMSWZӘ%$پRxbDz |m#s g Q[ɮl w/֥Bi[Tres ޴AEC8Am2(ZUvT[DLvv׀.Gs$ETDєSR%2\=i+buzui ^Tw-P<V+֔bM39AGx* U 7NrC+ZC|8dHD_w-$E >WlpTȐd=O,t=tE}qyjlIqKp?% ,uOLC Aož ľ@3xB_@Un~+YUbv{_8Z;HMR-3僛eT++^_$ =%38O;@-HYEsNO }`Y/Xٍj¾urS)gqcECN'/ᴻ`A/҃G ;396c#''uj>zJ2~r=I# /Qq?6WaaHBkI[ƪ`yU>qrmThF]R١ҕYW yfN3N7+-!X zp;C"gwN kLep1Y-}*#x!-w?!5X٢H-ˉ*5t`g;a9I@|-ky8׈ :a(i!^^Ħ$m`ݍAiTAPo]1Ĵæ_$MhvI(TUA噌˜t1Ɨ:;e?C7S3!ếql b߸0RH:؛4ض-a#D s4tCK`7"c$:\}.RUDB@^DXU0/>q5%<9Hq \HE>2Vj|8)Ff"NA#j/J* l|!"m%r_ ݔ an^+^{] ?Y~k*ӆ,ڲh:j~iA(1 7W'*PT0/zꫬ ]S++|/ YKzB#^yNn(7®юTO F3 /P yX/A-`e/u$˔̙ɊL[;x7d~YUSuz> {qI TMWۮЩhdry>Z홽drd8n< 2ٕܧiua:f;˲Pf5~W!BEz 㔖P8@شa{o3`q1;bNˏb8s뚸o_՘=dwvPޢiХw`^yR7`.Z#L}%;v;\CJK<ԼJaTaK&pY wj/^~:?m0VD.OZ~Rb[ >m $Ud_#PvY>;N?@y76uj,s\mܵ2DsA{{P)_s%b7@ Mz;ԅɱ7ՇecYǭc^28y&70LqPt٬+MڐS{p=!dA!&_Y0m x@P)k|R61涞K )6i }Z.0(wDFZfD8T}8a,δwuz^f`lnvoQx&RwD ?_VN0JDfɜaϩx]*j;D&][Y/.#ՙ+uis&׭:[\ז!c+ ݕQ=JLJ ϣa s8Pb >4&*`p!4_E;$zT hR#[Bۯab;0&VBSmU r>U(O`I)*v+$G ;;>cX1xqtqﱥ0 ~CZ5FtJ/ F5 'n1VIDlx7XWٹaWVT]`ޮ`.&'WM&k,P]4b'>K5|HC3tTĥWs_ 0^me7sMN#fO(wYQb.Sݐ+"s2MɌZ?7!LQo`7WһRȢ>Ι<]wpbv$iIIq wmҹNJּ, ;D@O ZVUba^FxCB贵\P&%zs_ud@ff6?x!;x`Hc Za8o&5W_^zWXVlyR jXޓر}Hw2s׾k:r.)GύkD8m.MoO^{ۇӄ *G2N/DGFuy8p /T5~۫bB4s>zJGKƥY[ko(4,Ttv( LLS[n&-UHVVzhYh 43rr=P*o0vҕē2#%8JRf(]wc8u8 pgo[O?G yFu|^F|A!4h?i.!#6jzh0DW!S<&|\a%u#b1VUy&9fRG{Xu{9%C*>b?rSԖ+(_%4[>=lW@H`G_B'U[dB`2-Xv9Nq~h85MfI/ v H柄 *e1ڵwwޝ9?줗͞j34D)\s z}0'!mIL`X1I uĭ|]OfW$E.%)5Z(k .Z/B\{Vr-`|Aر#/oSƝqHC͒XNLh]@xGt?_!V!~%&ZO-vk)^~7i}t\Z>hz£ UF)jUKg%60y(ƅЈzF"k uvx y+w}ٌ+Gա9l$/{\V"98-hUڙ߯.nMKdW?q[#/tY 8^% I'Ko"kL)sݙWY^7l=WCXIN'a s%~ø(9<;>)mY4x}KPO C jc sHC7xuB e(rnMXt=9q8;Z2r'q|>_LxɁJHK+>d@^#St4 /7h8V%d 쒩DgN8(x5cE`eMmT[KN.=0Do 9ȿf"6'ϕew8UpMSAh3 ;N4M Bu*Ygh Y-GU^5KTȧWM}#-Q` VLP֎3Gpz7?5=Ƌ}/g^`Ցn^; E.@x J{/&9 ]PTڠTlYml XPCKEy.AH7ņ( 4Ӏa001?Ɖ 9k2[P,Ǫ9Lڼ9m6dP*CyaJEh'Ȝʨp{(XvU^ M纇{OM*_ZхD?9Cd-7p2ebȣcGCH:X烑/W.^Oχ./?abC{&TUL ,#E55ũdh& 8e[_j]NIo<.A:3Wm5o|X7`-Hl%k1砛݀V@˽`g\@ӀxJ hVvgT#pzrfF8އID%j9|}Ng,7riS" ?{﫫- %rﴊSc^ @qVi /ǥ24nV<Z1ibC$A)h-s?:mb4pRYi9Km1=T̝]Z·Ld^>^`L8kK+?>µGA;yC"Ԥ_XQf*'b֨$߾Bg-߫F;W wG٘y,ny.2G%?7-#`-2T}Ni6QNS&xt/ kMɼ$_"ڧ = iSəKi!-JptoӬwkM;{c䄥ٯWT;dTJWⓧsL$65P%['2 vS^KR9D~%NYHbʰtJA%iDdZ`2[A{^e16\ϓcOCZ]׍[4Bmktx{nìU!}k㹉exqҗc s/wR8(ry ŞXkW t<ҷ; t4z G`MDՄ\S {$Bh6W؉6Vkx9|w݃6r5|[u̽qͯGO62󦮌. KQ3W;fx$Zc[trڄ&X/,vZxsss4;:EPjֆŷ -6?S@ Ĝ?97ֶZ*0A6gh^\:q.yajlڡEbT,uIDYQ?В z+ݥl?H0^8zc#:+'%I0@'̊&.m-\eՈ)ceu'ws|HđN"l!Iq:ߏKJ>EJ4X}0z6 -Oi?Bm<[ -Rb#u 3LkOӾA =գ?nL^Up+MtFņoLZ;AuwCnSیD,\^Xߠ6o?]o\?2[_aQdb4Ciy_rSURyܴz&1!,/g=;"ScnW$HT>:%>-Z4ѱYdD,+Rذn?@ޢa,t%e)%1tR|٫2MVaILﲜ,N [:-AoRQ5@+#@}c3ɺ1R 1aY= xf$O$H.aW~LϽeX4޲C.n_֐"vLt/VE^٤SܝQtL6 *?OlB{a W~~bg0#+8 JX?1 F\{<DZ6B/(ɨsiR0#}'[pW \c D u^Skۮ;{CW@ݡ1\-tȫoy ilb5M?Z*$5ܘ°)sq WuYJEݝtcgLa<}Aϕ삍Uhw}!b q]]ɆBXPKx*v9eF V5za-*;,} c f\pboO~. H-.qyU*LshnSGU7i9 a->du'0UM]Xf3+Щh]aF=3)?9Ԏ*XvðiG7 6s!`56<PG9;Z"/WcFbrEQ6«Gn[U&;pj1J,E.`ۊ:h~vlރڲ.öP噫XOƅ=Vr 6b6Ḷ2G;lh)o/ޚ"4? L0}koAk x/;$/QM`& M! b(q fs02p&'#/Bk`iLwĕzcjG 귅DRBt;`+*JtxpQ/#zCLO:]ppYڡ*|Oq"=~\L[g3廨\R>F?Ӧ| ›]է9| Ob dC/ 2kbxp_zAy$ q@ՇSZ||S)OF+إ}z%gbz5z q-w%IhF(߉3͊_q?64tY:E__7M_dH09 To\VAb)Wl&1K[>9 ?^i6R˻Y {CYțK`lX SA[H??Ԏ7Wc>qԃ C^!H$fx]$lQ~r[=1wo&ޔt]@i :MmP&`M N F\pG[&5g;V%sކ9%!Apm=_dN$n.&;X`_&i}!S 5)'-;zA ;>iWs!aiРXq|*'ՅT JWAF⍯"nJȠxږb{WK @_('?ay N~Z2W勯l#@`!k K"P}@,$ieYM&F !S._g+OQ6?@-.0>Kxeb. t*LT'5djk5PXFްݛ $GW@ǣhbP@ K/#h}LLbr#ޓ k}k*;S0n,\wsjBm!^$2R)3͘䀀=(ԉ)yrM,j~ԮMKWA,⹔OFޔ>09h4%3¥(G*F MDfY3_K6v6q܀nj&˺k7Br~P@GՁcXѪHEv{Q^2[h: c^}0Ԣ;pdPKhNnv\`Hj| 2sRonɒJƕ&͜γ)v26sӝfh;ZZV\\^89 韄'"Vߵ֯g8 O޵'~EWq3HE$ٙȒ=bhb`CDIڞ'`C|ZmRAlfiwcx@%^W;Vt {=dL{4ld37 -U_4^d|z Jkyg*S'@ZJTޙk|yݶVSfn_|cNTѳrugx3Q$I~kOtYah&ҡfU&1Qj @4gAS܃P"RQUJ SJ/s Lw-ث!l'5wQڅQ52qo37d!2Yug+2ca"]6s5F9r 4CI{K_ kiقvHzLIlC":'q@]v3nbxK@ KMbCb|O~f%vV*3*U||`a:%f~Z'2E|=` -Bn{Z^t5[VK#~C*00/E}g 4e)gdZ!;jRڼ~h ;Df!oiB)Yl݁=lhXvm9, рm]>V6-H枛dNm=zu&Rð϶}<|V:sv:+w58o:)S":pYAqhy(W* ( c\4e_ ܣCDztR^$%C6ﱍWa6mxFaԀ %:6!*7#cнd=gg@8KLs*2Eвﴭ~( &.h#N0m*FU:G e?*3a*=h籆*4ft#y\zS=3@. t9["#%CAl^$6܇vFjHB)ͯ<:sܱ{b6uK']6XwV(ApȵX(El 6ÎԼp&[>ueB7bꖐ?W8ݰK!XJd[pU4ѡ[)u|O09dVJt,->6n~dir[$w\*rH0-jn;׋5p9tn -ĉ CON ͨYFv)e$Ϻ[Y|M0+m tk4D |QBÖs9 [V%n¨3հ+3ؐYMZ87KqlGvv-%_/ΔHXp񙢷/챨1Es0DU\v2$|OuD5\T#z~Xm!b2sZN&%608 k'>wc&HOS"}4J M<+eGznb !>D|8Bb8'&Z1sĘYb/e`嗖5l}+AuVAjL3bS!nOۈC)u\xb]RD!oz&FP;jn \T6ż` Jm evZr̈́f] G1 XVFmL^Uw^ћhglcZ3bW{yFsƋ$kh^#YJ~8:`Dh9 1P/}iϞ>>ҸQlSN-"Ƽ^FD B~!h.GaZ>},6XA?kZm>k^5AsQS'BX9.qYGbpǠ_?Qt!&o[{zv>0D,76fB/}1:lF@`RzM+2Y}'jSb^TDZ{@c}Y1ca,n-Zb jRxlGa '˗Vcs'1^ BbH?$ teD]267E$~2=mCTi={vrVj%? ܾ0SdXר1Y4*:SK0Vzj*Iqh`g4UiV:bz}4Hnp^k;w/+L;1Q9"u[sWms-T1BQ5WTN} (8p0NS)JE[i3dIο'1R!f3hƤO:oov @=".ktX>>&o(^Ni'X¯>]Jr`5лp)dPŮ[lb6ʘVob/96̯-%[fzd\{V`O8ĝf=OML vTX~P4PvTH+S );ׯVR$fh@m" T 7d9cB fLQ @%#X~[,`;R.Q?\קJY$ $xP3a!L^?r.5^͆SsM@"( & ѯ!RZ>Im 3l9wطښ^-#ԓq(_m+ٻ߶op<@jX"*Puǁ9[$FlskYO33<d磜?xTW;Т97!ndJeHk|+Q lW4^%[=H,*Nf;C0gYEi<'9q}Y#H;hm2v~6\(i)>Q Ӌ/,5Mw t)5ϓA_QX: N|c?B-1^* :i~B䉥S돿Љge5,?r~?w'Tjto$[*ho m8L8;WBE{[g*!ӼRxDVq*U4u,p3QfTp6*Se̓f{oWR?I-rAo 0x8XO$ƺHSSԎQ,|"\˹kͭ5pS72a %52vqF}) yh^n}7̕]KeC !t칂^w+*£^QK[6AO?7!zי]b+?<`B|\[ZhC3_~jtܥHcfq =Wu`^ ֌!QkD[q~&zA/vK'gZ57Kv3u\F2jC`=UND+/:fĴ %q|6OX>Umvņhȣ dq /8٫ 1ˡ̆ xܫ 46M~{^~J6)}' l#o!o H`(Ȟ]FWOH`rj} wAS䛰`d^(ni h(*S ұr=M%L?L_rRxߪyhl;+1:sxηZ(tq D2,fwMrdH+KYJ#ҦCX'JRt.E=Rє(&+7ؠ??~L+"70Z@7]RKy$~fd! ~OUwu t0%9UՀ&,Tr rR>oj<_a'CIR@0zܫXQ+- wq8 BԆD,SL1 0h |cpeӴJ2KLoߚ ~Ⲅ2<6S9:lñ [*0Jqz~ H#g84K9523 e_0Ǎ(%LBV]?y1)d8$C8:y 凩WDm"RfDsJ&vu85٥|1$rfUxl%G61!Jc;o7֒K$y_XbBh۠Ԃʒ]F 4L ɟj<\,7aA!jx탍&+ ,e"#4Q^2|4Zb8p8~I&;ֿ|Oa\Sr*SjW{NC-?6GSOl QP uWqrj)zm7 RK7AԗU.q)zN 3q1@d_ d;"%Z.-چ-N骸oY*c]:8i-5qvSw.9ܯ'kڙ*\<ݘ]⟓۸$nȖ[AQme zhBOW< VZSGSV0eD\MKlwvN ]o(` yxt'"5"H.AF?TvyAf|P8VJu6f'5/jnIeQQir}p/d%QHR(1P*+7ug䖈X~ʰ$+Rb'*G v토rʟ yZHK3{"tJ#Xs,Z)~n쿢uD;X;S6gGWޑ`qh9V@oP=19SrZ 2\H R(bOK%DXB0!)LžU}閪 4$$ⴥpƛIq>q Q w-KTgr}L-w,HVEA+2ь1af1l==^\d)7% 7ݎ2 NWl*C2 "E|Cӭ,7_ti㠢ƈkP c:q)l1Y"FC˄PX'Q@aW"|mT/V]uD>Jt#iu c#ͩ}`~mPǽ <B9%‰P1,9ʈ~9X/N:Õ {6u;܎0QÁ*2d=3o" =n]#cBA:Ln)r_YS wQ5_Nw/bҊx޻@qN#'my!n?e $^UELϪM)je*I⼳ 3x<Rc tn@LoK%؃`vVTmzLf@zCI N-%U8!i]H]d[Px =@ۓ Ԁ^:ڞ/ g3{ԁG T%tK;Oq 䯎(gCG+…m6wIusuWoip@_ۺ')!e/G #[SfC[ޡ +p(b鬉u+/-IV_!U-xm{NDW*/\כqQq*mx5)#}rUw,osH냾 2M( [E9I݄)q SQgz쎃&׵)1d@>lmߕT!H} %jfxRRx_ 5mſBb^ &|;r?@=s1 OJ' XޛQ/` G;jQˁ6? o9kGiDV/JNEG?wM?,jJGT<*]`G߲'P4u{2K/:9y4j%E=t]5͞14ߍ~ LFy=^d-*oȥ_{TZN&tO~R^i:p'0Vȗ*z_:V4i`AYq&ͭqYaC5lж-%Ά^ B7|jjcy_uk2|X<9|7PQx.W }(y@ZOud!+5UK$8,uS&hώ&N\0ww$Ju^gn!pWZW S-r(;|Kc qMSQ"VNû-m"v /Ёù! _ص$M&P|%f%\CvՁD򪾐.Qfm-L:$N+-8'QVJ)˕2B"VmlU[ha{&.Ķb}4a\U>pFk|nCC/v!ArF1PЦ1~^Q8?:njڶ܁7=}nܣ.ZVVF~)n M՝-Ez̝bA E0^֌|AGle.(oBM??Z&(]Fȡ, Hda9QPSH!pʓԜ+nJ 8D+e"~=oڥbp3u)$P݆d& ]։^Q)wi" Uh'(7Эzm|I᳃R櫻eZ:,Z [m%>dɩW>BISTa^e_ߗ]V9ө?~=:}D-躱X) kIbz{؛+j4bXⷼ /iZ(rIrkH޿:z c8 կ-t^p=2`l~h1ԓO+sաCOy5dPc_HG6$L}nb5lD׏y#F,~Џ/Ot'Nc"b u:X_w=#6rp^#db(ìTO?#u߱smAW7к5=/MU?~ϵXn,b'1gv b)`Qks3Cez w)xӀ'Fn㩵T4CDC_gtY!6Y_ҥBr5Vy)V|!ti̞w+ΕL0UQ'Fu1@SMyZgxvV?"t/H-NwluqC'& 'fvz 7$ Lr?2hK0"Ƞ K5u·v_}CWFUgT$w4 vYIAjؾyqVQ{'"5Oni1.m*#C8rd8)_*폶i}~VHښ~Cp]F?3ޣjY/Oͪ=Ti sBpt^aޥ['nn(T0t5#u#$GK+,(GCH[߰-L݄?F* YQN"J]B&'ȗ ^y(nʢ^Ë' I II p(A/]dNMR=`lUcsGsU%"|L,!}KF8. > sn| -eH|&r0jwLVIcG6er7VX"9jY&KвMH/ܔܨ4Ygy)^V&*=RGHv6tfۘlZ!U#Pd@$vDq+/?*dLXEtj؃RGJf1895]Յ.:Ul^\u%~Gog3~=L DL!Z٥A=_)/a(6N4mh9G}(7$fqʺÄ7y9M~6;ol>,V}-.1=p)ztv: u.W@dC]'M/k9LX6Azun{U;GKl83N^ef ^o\.rĄfX^<+g˫at O˗GXv^;  ö)}z]pO5):""Gr9 K*2s n$FC:IM&_9o,$JF'bPkÝqs>Fᗄ-įxY^ZYy6 (ZZ?{)/Һ4p i }mZ#0[$ {!h-jZO*(4wmc+>^A3D*8{ℌ)w)׺AnX'H5(괢hXTM|n fhH.Hlψ3P 1 Q_Wmo  ~c<.r(a_!ܗM`8s;IQΫn<;*٬Q+?5&;ݒ2aXx""i.(JO|ϨB`py0!8-[`e};.B|$Lډ)Յ hD @~SG- tH/q3Y99(5}>(VP}_: 7FKƐ Al'dpv=:԰>zՀ69e3^JM8RSo=V-q.z?PGY_ފ46g+Q`Ȃ5kh^s-z ҫũ ̭1p&7Uyppi=Xg/wj؅VPYer 2bPHmː Q=7S>R޻XFnEΎә)"T[EL A'0!ϺLkKZrwn"Tܪ^aZ0cMgBRTXlBJڢĹ¹#ha 5MTbԦ AS{y{Inm%َ'Yh.tW¥pzEWbvNMm'q46+=̍8^%T ȑ/kzh]7sծ)#ð ЅcxD1}t,d|g&z8i,d!lIV.Ő~0CWKPhhfW%,Q\\F6]د}90kbFgb%P5R&ℌ)+0:%a0NCa;Ê0LaXI4tR}f|,%K"Q88s1WtS:_ha&eM\,]`0gÜ9LsOL(J:pEn~eCP=9P2'cꙁVAY5 55ҔKq"U'ȳuS*cH~۪׌U3Zwfă|'z=JrE<#@?U|#q5&@n"/1@h0֐p-;$9R>tER@EsBHGCHB=#SOHis- DQݰۼqg牖rӅ2h"T+ȯ;;\,Mǩ7n8 qK'1LsޙQ`|dgI7ٳpXD)Ssa̒Q[@ hݛQJ:Thi>cE*.,c- z&/~<7+rNoxn ς|SC&(craNHR/$] K_.t-dy lNRU"6YEPi7 )  ?TEGB?xvĒ(Vj9bwD˼;R@7mG!ĭVx?Xv$ъi څ[H2^1D̗©uLDoSS8sZ[]7R_p R4lP[璘$'ZFY& $ʷ{@ǝI5w;{%QPO|iS/yMI7rNoZ##7lK:ҙ.<\tɣ7ih \ [oEs6gS!?*6KAP:ć#%51@]_& JCm(1,G5P"I4)V}2T$V! t㰳T9Ț]Kg4~Ԗ u}^ / >zQ%gav8@"jݯ-> }\^2]QXGY# {t7 ;Bgc*WL m6E4Q;M F/ -qt_t&=FG-S(y mh+/Ǹ ބ7,p #vMK 3=L Дeĸ=Y>m@,X$Η4AB8G tSgAf' I?d:1+Ʌ6E49NBZ6}ݚ('?2Q*n/~vKO&^"g7pEs8PTd.XrtDS=) r70f|?lj˽l9wa&lFd&Cb}nPC2{cb%Ir5PXKDz3dͩLXSsW6oesY_JNءvu5׌NXWO9sӛp~&tJJ=܈dVFȧ* fT.o)NуBSD)]O<&[ 55n*3I~G a$ ^ 'RPvnfipI46"=9i(D$; $0.|i@g̟"[p*][qQrSSƩK2PH,_? wp Q}"lo0e/(YS! E~%k@G4H9{"ͧ`9O ̮8ē˚p KMP_sV?D/זREbc/_~%ϒs6+*%wa:kf1ޕݝ(DN@vdg6F"Oϫ*L+eߎdg-RÎa/a=1G2S'uF5y9Fz.@5{˧0qi%ᶞ3JFb,u?t,A}3wS%`` Upvcwp_WGAܘ!c$)T:.2i=58.]2RM7FnWNiCk/Q@Vd 7Tm8 @33>a |ۭ^1ToxM De)H?x* Ziccl"+dWn8<;Gj\6(Ot( $7IT7$l|,XQ@KwU{&¡64 cƅP-Q}GpQ]*sDs-eb[ol!Zi!:v"hi(A0hzOv)ձI#,s(soca'1ZL U| XQ!Gtn\)עh/HiL)9)IeQII$[[`2)9I(Ơ/ kK7S^PX )dVw5g1COV!- p(tf be3uח5 aJ|X:Eعl@Pd`BП+!wZb6ҝ,qaXƬowh "3@tW+3zԯ o.S$ُybUUQ~='/aYsi󑖙Kvj͑tPsh/e7jm AۇĶB*_AGG '/5V{ f`%cWZ?@4U մ05 hx„pDK\Ypu_iՂΆ8_>\G3)\MP-WKI3g#s\@Hɪ9œ_o_Z7('x3skױm P9A*B=𛸘,oU*''N| rFUrK[őSQV! 'O7Iz !w(i'je2l}P[4lѿgb}2MްW\9g 6LxKej (#1?2;o-g< _۔?bܬ3(&eMi\a:5;9*N$-v8ܣ-LB|'h80D55&[R2l|ݚ$/Y˳5LG+1k#w$ Gy1d+F*18 dm7ۡBW+uw_+c'S)b=Onͅ1f&(sS0&uQ9wzw(<.C@|.%7|]r>f3S@853m tҐ葘.]D*?-B :8Y;*k|r_7^KODz ]͜(u?/өv TUŝ^!8-*JUy"DA ͵}}mF67ȴ$J#x0@OEjl[k?Lbyuy<\Y=ɼ6H"K0é׀߃B"(e-'G yj8.טO".%=I7 &ErPLJ%};2 SYQ-jC^ hأB^>?+Ϥ~T,GKN-ij3üK0@#Cy:E.s\FA@{Hj=tcTN悬ArPtγ!1Q`w6::L%}4D^'` P>õ5X@b"Gn̞.?52gL CS]1n,by"5-ŗ!$wZII\ڀW)F+VĪbCjIJG ӄKl9tA7Ε6{ &2&F~.ja10wyPl/RyOS~nqK/K`3&bOGHlĩJV}pPAI4Q ԁ_.8"u]૳5e^2 dofCs]p{]; 03ltZ+`|QA[[ojgexYfk`E=ae9 s0g!DrXFN2b\fdjgC6Ez'4 dw;B>eU^fF5oڇ2g%r&fCQ@~Gk-M o`jG=#7*Ms#˩~TXͯ~R;n;6Y}髶?x7`+z$"81nڷ%lхGqVĀ>PgZ~zo^d?rGD(*K3O|rRND|ΡMyN,їl(UOo Lu 57_񢐂$sT~+Ec!|]!_=Ƶ@$53[tP[xj5g+bVa@ke4wԬqg_#b_GV9{!Ҹ `8SCR_vh1N#>n;ɚv 9ZG !qH#N`L *$%yں)gXA9U!AU (`Ќ-Ȣ꬏q˻j#US2ީ|ROe/u0;! _eb!J1b4~i5VimAzMy&rqxXxg>ux}ttKx'?؅mÍ֙ԠQ^~)LCP.[ r6gXčrF;&q7 ՒC+G`W܋g[AVWL*"=m5b-批gK(Zw [{;kidťh_}1ß!܂ y.ćr^y,T2k\ײiCAXגI)/&<^Pjvb'gxj:\3,v-pg@d[{IuI,T^o#_'5Mz;`|QXx`֭?wr=!M=U ք_1P˿Ub#~Q6A4L)F4Fbm`5h6,ZoEHVGBy/Zd|s%ٜQM&8h6j fDS `6nf#IaЮNPXFlͬʰ##PF lmB>ZJ%mwJ#6"0J×3  !+'y*}I pwf|'rJBŜ̮RJ'& t$iB{/ kO MdD~gG3($v9{X. kK8R@дTCYEi\mek_$X|la qkMbU&'P AKs B϶2Kb0:q5x.g~1utdH)،ƭBǝ Qk9tVyKwoEӈ.Y-`Pw5ubxР^BBinО?q 2we{ʶt٪|lb si|@ga;_t{{eөa$O:kτRr|h,Ec!k5cZKf4;ZrR7t똥(b|e!!&"mr޽ #,r'= 9GL0r5|##FRV)(=c,t< G*o~>b%r%,`w-=goon%7[ti=EAʶ;h̢ T$,J$"pL~E4Ui < 6dbT<"c7z_bI2 Zެ2Θv>FrbIf%K0 Dz+&_SC+e{drzME>Hk4I0hzhǣ;-KCh7G\JTJ-'l"Go֎l@qΌH9D` -$rN: N̍2{!}'9V*2ZU߶|*Y#hmUl;g( mo#CZdq:CTwqWJ PwVo0Á9JL9vV`J t[i"~jڄ£Y)(Dؓz1ǢlK&mVg*XXfb˿@ıst֋d_xif7<,vE˫K";T-rūz] :'Gjm2ks|<6GIImkp iY[]8fu@Ww!=y2.]_X2yv|yxNxrX8,5W@ԝR**[)}0-qӾ+hJX L>$d!isK]:Ɛ*0_R ̙u'=u*C ڮ ]^9VS8ޙ/%C{*H>lFl`}_|L1Em̃8MD 3@`^di-~=:̑Z54/c3v u2x&*UצbtaKhɕ˫Ѵm\#/lΘ!j–äc@ /y.qSs ɸ0 $+WA\Jbq%:)Ό崀|!&A[% C%Fzvmda,`Eq+k7`I;ɶ/׷Q~!<6a'q-}!jU;.:/xkmĪmPZes:Bl Dt.A2G!S&EO6{RX'wxS:3~vՙG *Nk{Oݗ~<ÐZxu{súԳ-HTSҿ J37?\RUF؄jz1'pGSkzJ 9VQ]&樭.Ph,@w_+ۚPU[% Bv!L$,ՎˀБcYݤ7ktn/`HQbCڇ_8Fŋ6u"Bk%iB`Qo5gw9譭ζ`9|ϛb<]!W%.856_jN+f"ud[Mm4|#΍C^:m{֬EkF[~ 1)m&ǟr-R-DZ rJ4@ynyw߬2Ǐ'|ǎ,W*PWUSIjB¾z&!&)ȴ DarnɕN)6kyǍ4XUt&5uT8欺iA 6^JxIRǑ!GC)PjmuJߚ]qwQF֋VXCuԌW 6gv+-b8KVpA"}L-i@Ga"~Eq=j,☌pJ׉.a0,Ĭ&@a717jbE1y!ok-oe3 \bdhA*KBn 8Q*9~  Oj*ţhqUD1Pm]1V?. oCcw15eeb&ړqB=p֎5eoծ`=AZռU4 NTCcVݠ=͛ݧsE$|ya\NFL?~VE^)xySɗW?{*4S]? O#TzWHo]7 Q&[&ww{Z'] TV8$:r)W6'E,|e h-Z }j~Hk "ue5yېOUèw) y ,FI݄- R)h.4g\,|9؊9BtkxǷfԀ%i+Qc['iR%lSwQ D]\ (X'Iߎ9eBO?qqljޛqq%PkH1:C\ɓB ]  ٯ/m'.ŗ~1yIַ=y{fp!7/J q֔K1Ae+;= r3mщ;+x9f,|,]伴gq8=}ݕHݨ'Xz]H0QtW!$Dw$z>* Vo&CAHc!wa*1 @`E 64<.1=?zbs!"W},X 0S.Qc?In ;W>HuƳ,!kod]7:!r=pMJ fpp ^i$cM.䟯[YApYٸ^]:}1o5wX z+o"}u&%?\i9ʥp3 0NlJOK]ɦ0[pIk?<;AS~B #A5crt{6te Wɭ+ GggUі]\fX!<@sc V ᦹ5Ϳ#y! nt߶5|5ےCo392ElB~76b{⏦k|[251TY>@utyAßļO:2?NBlQs5>.g+86;cٚ]ֻK !|l=1`4E8SH} 䇚^`^jO9BOT~3r8У8R;(slsv3a"}.Y2;4ϵVܩje (3]f=*la,̚ լ{̾ݎNy>{56 <4$תaIЭAQQ_": LX +C*DU zS&]oVbdĀ=!ΙgZI^`Q3IU)ʆ7iKcWtFLzWX@bnZ utoN§o\!+ⱕ<[L҃Ce߬,A ֡{'E<@Lk RbU{đ`Uf}H\/ܜ؉|3ba \zk  0JY'eS-)ٹ6C,yfǠ.R){x=zŘ1Q~qrM84Qf-wx 4z Jy]K^o,4sᯝktcc.a*<BGe?3mHLTMVl/Ibk7]xC?6VskZy0\M8Bg~yNJTěnJ9eٵ+kLw\C mbc/4$|]d/{HWeR9N<_%_@rvV8#E)DmFl`ѷC2߽q];M˜c̜kQ!o6ǩ=K3,G{8f7XG^ k$AnAM[#!U5mpT>nC2}å$qϴ.u#Mķ逜D.$nl}j5*`^ B5˂YCrs8 XjCU cvKSt_CSuUKiPeiԆU˯G[\km?@Y n bqo:}L1l!^EX钇s/sհ(/̱ZUP.X|OEFGHϓGZh+_c܁BP_~P_TB}G]Vuc.,^;*9`fZ,Mjet kA{VVĹ* Sd2գcf[sG : .vmLCu5Z*PЊ= }O-U$qeV# @q̷ͻh!C2Z{Zvn+^1moS5P cld_g:9SLqKº+~v"¢F."C%7F=],Q7Y,ѧ,O7!N@qm*v ;H0'u33>"q{>7 YGDNO=;I5) :[R0@@BK"cn7;=GUq: UGo %f,qv/{cM! l;5kJ >%;Zg^f`h($ælnMlՍFb+*@kXLpuyĊ^(uf P =1o71on3Z_cÿ&[!ٙE\*^'$jqTh̜C}EQ/zNhf]74K126` e?PBe2q&Ӕ%-xYN {ZdzWi-D}X?3F+*9At#*k %D6pF4LX5Th F/OaI"ol7oΎ.Yrf&¥7:̟zD iH8t뉤䳏 L昿 M<,idK H^3>NR1n,H_z n]d:ZKJyl?µ#e(sSuݰo_wq.>Kn2YC ~fgڴ3Y)UTI@+UzƄ17aü|85)SXDˤ l~kb7=Ts. ;P+2yV7u uh,ڌS$vnﴅmjP8,/`n@O[h3O GAV3YHѐKuF5 gJ*'Z ?_󕮇'' #0mndEnF"WmV_^?ܿ0LIr훐v8`_r3ŧ|j`"リLHY聤sk>B ^ *1 Me_OLn~hAת-ھ)#I ]d23 ˖˗\ߪl.EB3@P̤!$r &&AMJbWJtaJ;88rssFeJQ@%qW"܆% EgbÛ9OD EP ؅-{̧kQ3YAS9"2-o+oӗ  AyOEE7i,Qe \Z3~j;Ht܊<*MN;s(k¡xHLJ|ιj5)RT:Bm1'fNDdfFXR q<+k+iBO(|' X?d*C.#Prcޙ}* hCu9ԙ;;|scWV%8 fwm/4Ta_c1?&MHڴAR G+~۸ⱸ1o:}Qh,('I2dCG)'W}lI` ʫ8ӡqn 08Rӟɮq*(27Nr9"0+470ZW {,S{JmcE'3XQmIp7F(@٦j65*mN+j r$6}o"Bgy)| Ec(xsp.rާoIxpF΍hZ@[kA}j.g~s(u=M^< f)J$cK(6@jѯ=KuO''Tu>Gn#}"ྱ&-eSR[_4K`?{HW+3Q17'̝ 1s-LCE( #kVp< <tk䕁vA'+h P( 0@9wPrP ,!ZEbGfһoՑje}Vab5g(T3NC)Vh]m볯'~pGΟJg!J锶vTrV-,B̛50ާ; ."BfL`Zýd xm |^xH֥&hRX%23cl_ Ls'ɣRcQ맲>BP`jP #s\E!JSi$Sl+54<^nyĸS _1]TxEO(\(nDmxb [^qh@+uKNN?8Aah@cdr!Tkq1Sy45/05_>g}h"&7ۊ(ÒoHu{"v.ЛQ/M^& RFTŊsAw< ŘjVMѽBq@7*_YP]&Go"¡UAづ IE=Hx#4%O!cTod#͉n :]5=T3 CP8*sS)=0|Ё[ݻa4@ 4ey! g*ipE\}~M̉tJ p;qyCb󰴻$ e3T!T(X{nX[Be!4GfMͭ|IQ&& g.=i/g:o< NEj,tӵ Uer&˽{!ա癩5ga-~CF7rIHs5f6u 7*c/@@5jQz 5͑m%3OGh7*#7RbӁ'o}x:Y!vuFJn혘b0洫ה`WGbe>1M$peF~c=&-HUUA9\y gH̵%-e%Q1z(-7bǽQ{ PqkONۥGy^#pnHL =8[`W!hpa+4 R!q`,2 X* +"חsMJ;Txu[Մ 衏5Xj<»m  Z#"zg醀Thr?-D$\qgɐZ[q k?YQG~ ! W0+Њ,!4uNf(xr-kma.4cN,Pw(tT)H0Đt7ُYckBya<~Κ}c1 闒eDMhw Bi);ϗ^[gu9Z=]#Š*wzP'[kU痢0E\Zp%S@npC"V-Eflg4%6gԌV6[y9B(\P Y0TIQFdO#@;_up12J}ٞ@Cv[!%7P'NDxo]604:~0DfM.Qru*&QOUΝl"yպ[g'–ǿa-L rҁPcU27%I:3W\ym3pc)A1Ӊ73y!>ג#e+$&٨]CZ?1bˈS(q_xU{wP- Rj2ԂUšùƮ}0mrYMԽXxF^]{ `>SƁ0 -m. cU5`JK`7o`|)dv>vSdu<;IuDʓ$l웝>Jq\ۮIy|D%i8P֫uj Z 5Ti J;nͥiQwµ3[2O!`'x X#Wo$],:*xZL}$DSVH$LVj -i(3j ^]zPޝ9?6pN"RO1;_HD`gO-A.ٿ[%dRL1X#$;;&T,ՙ''猁گ>^:dPKCLdg)LާLu.7ybM{gu% ǥQ;;鍥#<Ӌ]`RqPV9Z\)_ ;"25&Tꏔ~Hz:h3 g$}iWÉmhO.%=Y09 J6$֏PelzPm:n){mbuMĊBLs3e\u9gt.C,)|눐dant/V5DzSDp_s\ K'=!- ΛJ}Sh#M|O͸ia*e.YbxΘCh]{%iQX!"JrR\q(+3֣vՎ!z1 p!%elJDS!_YwIgf(v٬.:Qk`Cܨd~d56_so(Gn3Y:.X/ƕ.4%UΤϯc6can6RBY@§|µѽy+ج~MǼ+!}jG6W&3ѣ0l >m ͜kL<U|^I+߆ YU^mC%wS\nKNΈOA/ SfopD =wƶu߿OxR߷ AG-V7c|p%Ɛ?rݴG<YVd.[ÇΞ!Z%1;ijC,^t$Vx&)6(8Y(OsX)d=fNђʻī  0 @~Y:=B?8ȼd1(9w'pi~'Χ U8\bZlۑu(.&QP7S#9建-)0k܄gᕨ#ѓw(QniCD@oTeK/ȭlch,VUM<4L|R&tl B2JoG) BUh:'%Ce:j\Lѽ$n]LFuJ&`֦ڷ[gBuK4sw+[ÇΞo{z ͌~?dӽFPcT~olli/ .:rHuޏ)ԾZspEv2 1r/ʎu2kߕi^\FDҭrV#Nm L ;5grܭ{k$cqp;~-Cz@Vi5Ht28>#ik%>GK̲c×:5dH#~*+čf{ D7V:DpY5;4sw+]wĺS|:njgMi>L,킺7/ 7` Y^z"] ޷+!E{rss\6ZBګTj6ޟ\cxJt[2"|k:׹ǹk:|9o@3m)n/=ݒu=r;_6~^}GV.oN ~m8# Ea%:c@m%Hfb=7AhNV'R&T?,TN-ݸCTr*^*l(X=/ ّ6*S4]V9*ЦT9&U5|"jX4up4wŬ.OaȰ{..#ԗ p5O&]7ᑬm) U)"0Thy'Vr]BHǩ>0u/ͮ @y3Ll>1X">~m `^t۽QS[!3?]V@y%}zɛ`ôI1 P֞*1BxQ0[jӸoaay: J6!=7 euq$^rCOM-؇OaXRDW4WDK2&c;J6t n CIHFss牮Wڏ.Lg"rt 8sV39{&0qbopY<[TX W)e2Ʃ$x7!vID)%~\7?_V5Mf ^dҼAn<>.jhEw8;5W4 L*7E{}pmN%{ni/]\두%/t_n To@`s0=lør{m`)c9D~6u`(ie nfW8 gsZ&ӐNEر*-K!>;X*Bz ݕ-ӲWgkxV1J?hy tiep?ڂxd31'sN L%[!!>H (},$^k.& @P-z&~=L$w?(}v s9?R.xonawa!-thamB%+hU=zRMCbnr>CSaԱ&SCb2KBઁ]GjWQE"1qN#R&x@n¾5,:8M0w:m9-sZB4Џο ^܆etnv w2XaN3LP~6Y m}RL^bHe3L"TJ`c᫥h}MK% j@d[_KW' k`&p5 d 7 dď9st,$^k-<;3򍝾 I]4v>i̅VLhKq8\E$9F, *,dZgj%mW_:N*3y<8AK"vg7 rԶ)8o<^U\{+aݦb7js zy+z:^&N6%eks(os]ڹ́epn K%C*͠ā`6* ? Zknr]4FDZ5lSq (/iJi|1~UGXx([Þ@a\Ro+g'VKrgƳl}Lڿf7'I4<{CCjmJ$!|c9K:;@*M14Ր[yA&Pe~6-]?E993ٶƴwA|I^A7E`^GsirD$ e6o*I>\Z\ p'3oՆtssܷ_/舿>LgPI?x]q|ؘE`&"+r@BS|] :Ib i[ҡxtaţI94CcZF_,>[ {,{) Qޘ#Vc.8'q&\oY+&1=pΡcsBkSB]y˰)jy>YA /r۲z1R:{+eH&8ɞ =[k_c;ۙ8e+l*U^7w峔t:J#/`kX~GX)̈Yfopj IJIznJ 'Ǩmq?`XoyQy#;Ǹ "[lh~sB[W>8YUw YΐJ3p-f2 LEIS2l},&/ ]QJ&XKy`/qiv1#M/7|1^7+#09&s/ !u7o!a *,;2+Lݯeof{ս,&ff05=;<ݼ4s?bYq2춒x&R66gT6C@p5\q׏ϝHj9}sE"HK5 >=e&ZZMZYB[(sOsغwc'0&4=ܖ#_mc6jZY?frjiKuBMςLWcO {nJ6n~?+O]8dtz0,X/ZSſC2\CZ #}ugflO)֟.'z #ɁrEaQYOS(kw[n5xR &0;IJTtUeUOX( N ']{8eLFO. ˌ*P%N`>vt  h 71`‰ϷF{4`q5a9dy8B%&aPꢰ=NHOHt-#(B]D8=x:i4o#(fWF.UN6nnf'Y"7bj|3 }g%7A |, yb /lb+FEiY,QNޙH'K:{xOs˂űuٵ5CҧЍk %,D qUJ$Ε5Iz;j́=+ Sxy.1 s5'l%~?7ߝrr )VO Lqru'/ۡJ+}6QDӡF V1}"y1s>S\0ZC:椶%NߨL*˞9Y e»ij!-RiۥH # Xd*Vt4)&){v==:=7C^kh;wO k葸(N2ScQpV*Ig;av!Ld*\ޅw3kbENi% VJwtV#Q[p\/o b2qK?UhQTE88`6U@ Q\`}aϩyLKT#A'!PxB̜*5GX]mu޵;!qOHL/{FՌp\.tph= OD_zm\Be  K#0Mh,*fcujnh(KƋg3$YHlNϱUi)<$q=gjCJFiq`Z;IŇ"?)췞D0?]A׮،:w} CG9 bz?k)bm|CUy2gVwI|*u4.5qvQifp_ԎO0xvA޹|nv"TA}?\.$-0A ^+RñN {p% (̕?O/? 0% O$yg]9K!$<)-z Io m] 0xoRbΩ@*[ z}ru ={ߊ)t>hq»i0L1kEG],%wq16J$`"Z`n xo,B69NZh0s%#c`Dz L0%:x:N.7O)yL]s!Y%gXHc#H?nUwPυo 1 K^ە\8r7e nQG-.%IKDn2v4'gWe GM]"SBp `%$gIo0sⱊ_< V3\u $uӽ*2h3mr!u|ZA/@ \x*g)qJD)N:0Rh`~C1L@Ãg.@.pX=hh X7SMy0C Xb(`5&/bjU+:D`0~T)Q#%U5jn'9f`œ;Ⴤ@U=P#XFw=w!L:, j֨NIU>i V /NK /Uӻ)L.zCWx۰~^^ "8?lyPY~!v*c8 &cTM' f떴5G vҨ~qe)'pꭷܭek+j;cS=ѳΝrpH˔AffRqilӸ9ky/'&1.e*JL•kB վ{RVHueN}RrчT?&wG4+?puN Blw2ccfvJ1݃f]ݶw[hRl;qwBGmWU4;9:*Bډ.w^ 9OWtFP_g&OkΦB8zf; %פƣ6&8ƌ%G2" fYg[ƴ'KpF=t;}Ƽp1܈fJP䯿FZE XaijSHs9]]Mg36ecW|^?uP%QUQp:C/:Aݰ\*)%-+ʵ,DvV9/i@ wž E1a MR|2ubT΋ /6A~{ t̿n懌 d A %Q4 QkJh3[3aC\n>ڔEo|omQ Cy [d%foD& >.Hv:2ٗI/y>f}.d0R峯$;XSG$`v@b;鯚238x s-"qҴJH-4Hc3=MM lK >b깟 3 ȊݼB D:( F IS=꛾a~@1K fۯ7Y JAq %gQ$C %/BFIg֕k }uOٻb?D7,𩮆ȌC;0#^& PA6Qdɴ7:+Jgg ƹHm)v5{I8qMia^pz~8 J;$/_ %}[^;+ E~kuS4uH_Q""CFb6RNCb^"ڳH(3+zi-1&eX$xƕXewBNf#.K6"$v42Bc(YF~sn­̮ Q9?Qi=(& )+Q\k5Nao` hdT*s(ɳftX,7ki"\ւhK5סWG=9<k4ُTtaLݚYUn$EǢl ,WTh߮{dZ\PzN ?Dx(ٟgH~D4IGWr|<϶qGP64R(d͢ v7#KA̷; 7?]{8FLC ec"W jUgO*ćs"CB%M?͈%$B4`2ˈ@说!'oeVZL{j\jyf!z[?.WWdžiX߈p-}nG1v>Ky͛T\b2@/l?c_EP_N/i3;o}V 3(X1'& qSc[c(,v-axgWdUrJjI8TB"pU]ǙK[S>E"?1=x'L|e$鉽 tyEL E$2@QZ WHT! uFjrh ņ UlW8 hiM9 `i6#iCz]b]r'p-Ą{ڮ]g-YRa9S9FTqt:ݾLRgK-t 1R|a_7!ii}!$X _|fv=bV}o-rXRrxJ$t%F[6<@v4Ҵ<^ca<Ԩs\tI{:o;1$rlB-t;y]AP;;[80ǡVLPT\؏G8~[_t @JNEm֟7U&MN/^gt전>.Ou h3#iCpԸ=~ RqhAsV@(Rc›xe?sEG'N*¡4A 5XO6^ 7ORRy:(s| .F.>0['Ӽ̓O[ [ekO >Uݧ1gg5ZI/6u5Dy@ߓC_2`A9nwOe-q(1g@qŬ17^<+d?`4}nI ^'[0{$$]vҞr(lvy&nQޞ)j/Q6LAOnS0Jd~%̆ʛ6TzEZ85G > ̛nzhtuvh|]tu̙?'Η/oO*Әk~l{Kα(#ޱdn\=7788qk55 ]%,sL-O a/JM0(A|{܍(AfhT,eЋT8̖ ?Y?HnI# \7'4)g aK2.rG VJ=#qpfSvaRހ"D$nXAXzɉ| h\[z{b#+0 U%xC*vX/qΒݵK8z`%H;}FM ]=se-Xbr"ZDk̋s]>v :ݴ7Gqfeg;,s!89es}ɂX (4G69V_94l k rBd)+k/w# ywb?6.o&O$)UW:gsE m7+Kp mB;hd(X GWya:?'quQ5 j8Q^^e'Im^BZMTZxrhփ _LyGMvPZ#m5WU\#FDÿ[aC#sz6R<咢P,:K)&CZ^f1O. "9I]{Ԝr=Z %h^"dQMDc!^N?Zʭ`EO[bœ(ۅ:8cb*>zAqB?d7=(C$H8NkdG1Z`Ѓj \|P)zD 8t#U%om#zsG/j+1j¿c>i*% ߡY8}_ vG±$㥾m䞐1I Tj;E0ʼnPmVD.mU(5'h^Cc3uR6cIj_" x׬JG׊¨LǨ"ea*۪2r%_^Gj|f Bzf58c7ͯ!3GUly,k-Jd@p7p^}Cu/t*QhзRgs9z%>nS(e^hːXV:~֡wTa,Hco[twugOօŻ晰} ~ Fqls2MsEP-u((idt0/`r"G Q5 cFN;jTv버wVxq47!R蛑2mQ".CWo“ iӏ- JAYa9;Q2ZN* Z~ŲnwԐO[&}1j'M <>I0E셝ά*Vu å4AL~@\EOK˨yatr m{̕ENUxhQ(iByy jîb6V%D_DXɦ\yCԜ oZi8mZ3 ~d!z/6wL Xry2Kn_TmۧU0/٪ !L4uUār`<3@lqQKD.fF:buxA4]&c8O q/(h!ǟhpņ%AϬw^z/9T XݣBɖ[R5Udj2}ƤH&{R/s.@*R}e+^d$N D'ғϱ{`?XJϰnTI$D 5 7vlZ851He^jqBǝ+4HL8ޟK} nh!Y&G׈9WTdts&:cA'[9 Fl…,8e#ﳸϚ+ᦖɓ)G8.I| 7.HOoɴ{8Y* H{|iqN y*RS0gBB` gm]X0cS#c js,#9H~kR_VRBٲ5EV],GI7\0Y%Jb׾M#`Cg-ة6^QKP/$Cj 9')){C8k9%P{wűI44jUpoYs(h><꽍_y24IOxSYUp᳤#ꬪ'4JlKr պWR轄's J+LGTȮ6Mh{W{F7QvT*Alx V4-)Q={5TM(B2-՟!t̍^b~cD*}v; \Fc":a &B6 y|z"s.5U]C:+QOHi-D*R|̷1f}D^8r>/#'[PN' 9ŭvIWƵv,ӊX 'ABuɒ܁|Js/0n22cW (ܪU^.P<%gGNKi7sɀoH蕓(5~X 6 60|tҟYAR%@kIZĢPNSDLf١klqVف쭭yO^}f X,Mk_Dsv$I :\of[hA_B),U7eoHc8")Igf@/ L8=N4>`W0W床7YF;=cn=Tx*vYj)=R< +Pi'~3]Zjn4,C@)&F?Ѽ'xy!{,_-1{NqL# %P8bqYƵ$U}veĘI.7 K 0?]ku6аJOQ+8 %/% S^[ÒMO8"e]6dn;OD!?4FB}FՇ_u|_V!bĻ + ez{&I~-81*7pPƐG6?zkΊL,ͽ"TѭDXimBcM{%͵9}ЬS_q+ֿn'$e[o, mN5 4x#c^ "wg+O^=b<ݣP wyT{@|OSc"@":}*¼uPFJF 0/B2G3̜A^la;q(<pݶNuzUz< [L@7}|zCOJNi4=XtmF`ݾWzF:zoqM W7,}␎d[u_ %)xe P*4wj8iװӆ`zd;OYVay# H06AR1AoqM"q%jq/]4<6m=1d"J=nV$7a6]p%T$Ħ09XYa+X)NǪpcJ$^HCqA9k^\2.4^HO쐆=Tw`0;}ifomlHxbV4P W `w?z9.?%†TI=m_6^G*H3,xw?4ۑ#0@:tpNO6Uq'S*ִE_H_zPV\X@E#E٦SU8M>KQSK}H.̝kJdz^cID3Sm*L78?Hؑ[VL_""6i9;P̞mB8Ͼ|v)7q$i21*rXE}th܅/=@"l-^EBaE}ƫy$ rSI@ 5-\ )jx$#W.[;)_Կ qWT35! NtjZ%nzg ZTMMJ;dC6&*n0<,s̸' Q[Ka1G'L(5~DTMcKqM;%ޮ+Qa8({Y)*Iڜ53~HcٹM$yIG]=vd *FS>?0 {0 +* eqXbHL&UY*wC\hx$AIft#5*bS :v3Ez2O:V*9XʹpYLc(vHWj7陈Iy7s O0#&uwi)&MV&8cʚB0="63z9usԥeaEVI[+P c:x7,?Ç9CY͒c`ߓyC1Qi[j\LW4,s|U`Ѱ+yL7VZ/n9m5D']o¨VPnFߕBqF'# iP+,vCx|u+U۝&eY`(=zH!~>60 l3SQ~nwZtb N*8=G).ii q_wLO5;x|%kPK`~Jz,0ЈI.UuPGOMZ8de%c2=IZP!lw PXYZN}$PM$jM"ۮܚK׸v<.=-}o 6pO׺o]8yYgApV(̻,3P3~`$sge 5ejCNq#{z+eH NxgS)aUyH˚[}na+1QKJ)C_VNѩs""]䕌`Yj͔26LX 2 뫊ݽgm1HBETpu$U,0D9%f(i"T˶S53. M/s6g ;w.)E,X"6yD(\Su5-p]5~[n8F7Zx5S4j ?5+gc~_gUkx9j$KFNݍI攨y'J[QVoXM"/dJ۵^k>{Qcw|}ݟ~PrhFջ^cnKtf**bQ[XJL`(ᦪNc։I2b#- n(Q3^kc0q5`j9:u\"WTY%<$~WVbvUEµ0g/Wo;xޤc6ln"nc|H Ne鐅  O/(vĩ3tg1~}$YU1|\P&!k5iZ[a3?[ ) u;U̘:kH͙|># S>ԖH_|^]< UՈۑgLjHZ"!lZ*鈿g}|H~u~̟n[4;0wmBxdͳ|ׄkS4b:  "ItDi O$<[tqX|:`mVeWV#Ȑe.$T"ܡW&nu!^*+Gxd'הY‘!d7,:\KZ)=ıS;:+1)|t4c唞F Ԩ`Yԫ"?anŲZ3K/JYEUW1qN\u)`|e2D۷:|E;tgpaM6<2'>6ɐ`Y~h_gŜe!K{7-\uٗY\f1Xe#%>cHxejM9flQheLRWxxPaYؐ/g k/ t2|jj#[66.Ĉ1^tX'N~ vKGKo }KU,pc?QD Mz 3RX#Ǘ3zNIMD-3G|JTph續](m7֬ـ`$ynmizCGKKW׸B]W`* QiJ#vaC?j2L9tNFXۅ t)LD#b Zr/v浍fP5H)k-EQ`!Qƌ$iWQv܈Y-AvzA">U qiljg á_֝ʓʸ ҶL~B/_@l%wݼ~tmɚ{qgr 8J6=&x!fM p_zruF4,Z"LnR/({JM/dZM.i-U 4 3<)cu\yWTB@M3Dyi@Dp "5; ^?|~YQjeT!ȑUD<Xٗj_82~_ iCU@Y"ףu4^ )|mX98~l-LFxQآ%L }M෉y s%`y/ P2  0ѩ:n0ӻX.D#}T =>H%RPX) \mElǨ!̠#v0!?l0oV᳐-=' ue*U.%R}9ZNCA 1cD/ tq~RyT$J}q^(9T~Yla[/6Ed2/quo8ƩA.^!rMdQ> nA^8V+gzfhRLV-c FVy-u QJ/4@<1!0P y![;O;=$vW2Ga#@X 7$HKN>,&|Chזܪ3~ˆ=ra 0fT"(%vt3z TKXW0x}K.)_-,)vU2sIMڣs^[%u/U2ԭTb!==p- _ GHfZ5Z?c%:G'sC*!@DV 8(Y`ZgShWPhZ7j'9[enPW|){Y `DhYMA>pc?Xo1.M~Y|NnJzeGBˬ~5]'5Qg6R"5tՍKw 8d ⍑u*m4 OCRG&k@ Lk"3EF,RL=J>Vck@hn>Fe0Ϟ h[aN}fG5&.yoFx {64P$dz1/9FіrFBй 3=<)j=M\cYmV0YRMºE>,_V7mHfwk̀]M+̸.E D$N~o|H #ڱזzl[bJr 1hQ)wu|v'nYO4"g9]$͇Pn%B=5F @wRM2E4 `c{V7攂>5u1Q]!-q@CT31m0^m]Z cRWDX w~*gP)`bÁ)q.EYC j b^וnǤ;XxyoH:'r.f)S}5o &N7F/)O$&oR/lݘB*yҧS9i s-)caVj+ɏ^0DT:{LA Iq 6U~-6JԂx"1hML e-3tUk $(SOr_U$xwz2p.՘~g.29̦Q9m^@ړhE %D~Qnr&ὮMFy̻:eh>YukRF6r">E$eN8Vkviؚ@3]k2\ dr^_q'IUNN>¢n;anoJJ6AH9Љ=g4[fP ZTFf8O $WO!6~3櫭df8-tEktԬ3\h)u$`/Z'/pώQt5TD}IӶ=VoyuA#U Aga4Gȥa֔2ooeQ*Yi9la;zlĂ`S1׻Fe%i+[_zztXsZ|Ed8֦p-y%ܡA쟋$oɗa뼁a4韠s0z=f;p?#ظ|b7OAբ>RZ$Y@Z )4Mg?֎!n*yֹMp9ozBSs!}"UW\ lL!_Arfn)R։pk~'-S ߱f7ڭHME7M+~lweepuptoy$d =,F@`wD:ES hZ1Yj /w%T29^IWbQd]Y]_N;dw 5Nu},맾VVKusGse*00RãԈ4k֥!>Q2g{i^A(*/2c%ۆq1rǔ_̹.a$idJgt- 9܉w@NZ/257+h5x/ʜ/Apőbؿ*à''Do+bxlWKM)a~Ι9˝;wP?P?X_ǶM=8Iwđ2f?ATcielQӯֈKLP,/3SVn%hWR=`yYoD fAsM&XTXґ=U$-*>)~w \DnKJ14[ 4\N1pf7By D睐ԥAIFjxX;fRhnq*􃊀Uώ\|e4i3&`(y^Dpyo0/gv9WvG!D0~e)FdC'J.Pȱ_3ɑPdc::ԕȊC+/rr+KALہ>:ԃ4k J?Pm0RtOꌺy*:X*XKs/]Jpo^ ]?RsѨD@х[eQ/~ 趍;ZѸVk0;'كS+Ix5®]N‡ݢMqˋ"zv hsBiDy9&(* }a*^cܥɥD4ٯuA!GG,cbѮІ\'nP9˅oRQm=<s!v)!S3'o &^=FxH~هGޚ aK5ywF7(w'SQAJKu%-BNQ#낙vZ+"ѲU6.s+2ۓͦm =ζ ;`bU%jXBc19(0y]4[2qagy^Sbqī@3hUiN])0߀ʳ\qO1gJ^r[B6iT02'@ ΂`&٣"Ť;jf ^.(4LI̕Ŗ4q'_^.C852D)$W򜸈oseso dO 4T]HP`֟,=m_q22D\4I|8 ml ~ a֣KUx `&EbrJdb@5 WW  $|?ilR L}?\e>ʗ&TdεdT0]Q<^ti13G5:I4w$8cU^NW'j~2Mj2H>> |!g(UL@Sy)%rh! D[Y N(zl=iΫ6 ڝSmOk=eGWO{pмcW<}uoYLWlv[?r(ĸi/c8[Rk ܁ Q;wʘ9#8J<{e<1BtݷcI` SBۘe6Irfsx<ɏk7ps fTe`pU6!JioY*RJ-hR엃3%ė %CnFٽwva(} h[?$֐g@ϛ@>FwO+:uUV,Ѕ:P` ͧ%"DĤbW(fru_)ynF;fiAjdw"Te:j-dc?0TO}=_x: Fn5aih_&f?#SÅ-lh>$wş%@Iu8ɋ%`lhQJɦ"dhn$gL?`j{P92tlݲy'9f1Kz7rZ($gzc6[S/~TϨI[ ;4w$&k[TriM$KZ1 rIJ3/b缢Cgh;]Ir3;䡡koY}`s,_c7|{av2_lz:-6X̩U]Z{$W$~t5I 3 F&A QXiT_R4\`Y)Հ'I6ٮu AU--忟!Bw7?`@^;p!Q]b 6zqsdX]V-ђR[7&kEk{UAaU e挵w6gG]{ !-}TҔ.`s(ĵ{\+>i>xrx @U2((H{C"`9Rg"lZN-&S]9pQǧtc(/EI-ز1;/Pނ(wR *vAtj-&2x.b eڀI^pO=ZdܷD#)K/(rl75G$í$-l:(+JFy " O` Xl&Ce=H+rIQ/^-t~K'5KW'Y4{au<\40ߐdWШQ7:vj8п!5.s~gs&!rH9>FB*mE ʞ4-Z+]G2s;Z.Y~T/zƽ.˼d[`1K, o[Yw|=쉚 tVhRd0N6dsdgPl duV1S4'>נ |g{̆<<]ۆ^H yj\ܒQ(CE3*.+濖[w-k)UU|lC.ՉO?T 0~ym"ziMglȟn~Vz51V90pFmӿ q\I#Hr /jBzzJuDI/dUjn]G沛t넥/ 4y[IFB!ul0oǭĠ 1}sj 0jU%PoӧS LR;L(>q`3#fb f_h}9'P-۟J Zvf>V(LZ3Z%6Bž^AԸ@Fjx? R 8^OWdibۿn˴GXc ᪅;FR5oU)@+Y-cS.SGٯ><纮$6B+t  %Xz{} @\bnSӸq/Иy ȘxcH-`*DU~?V4~| &'iA]Ŏ~5z(cu+yd(mb|=Ӈp8@,}1Y%5XPbʲsׂLkKg%KǿI@%3%GFA*0D|B8&l)pٖ#jqrN04+]yu%n%LMqnPT*f8DWElԇ#pa`Svdn1.tBX2 V9f]ex&_TO*M[|Bqtch 9)r)U=/|7(lp Q]δMB@|LTxVǟfU^-5)8,bxU^0F}/zhH^?gǟٵ3O3/y`odjUc̠[p.GQך17nⴤEaj+# )r wi7&R ;>00nR7-ew s`XJrMhtqv{ Ocl llCHd[}Q=U̮pAIZUZE{!jB'!|`LV{=u t!af/mH!uXa|#ɼnͪF+I.|8:sc),D2Uh40 vZ#`*? p@Ojull`C)@J8dtU}dZۨJy܆UN^qf\Zλ$s,lHQk%˿Lt,G/ތHXO%|*aG轗3Zqmd]rlzJ mi1V۵PMa bMUuɉ ǧ`Þ8JJg9XX{``@HUGEɩ6oh3 +Lb0Ŵ|o Hĝk.ft ]4U6EYwr,f+ cTz|ڽ>L{ZzkG(+{u* KZr$cdE/u_l;qq[Y;PNeIJxE(.C'llVz U5& ̓_(ƗyG8|LC F."9SzK oJIb0FB)֢k/R^Wչ&0Ihڞɬ|A.'ԃvs9xC,䒖HˏXʃTݽ*A x! uV%,#lr8OE“:L=)E^UuP 6)%Ky‡ߜ F"֓ϑߢDфwU4##lp6FKՇgZ bMPC` X#+wj䳄ް;V2?X\id*f@akVJ7ȋ[ƭ^3-⮶k4^H/łMBf'[$(98^qڗ\mPB%,ӭ(x>8 >>8b%*ai ]G.4)r# =1_aeeH"2y;X cuk/=*wmPDD&-W P Asjͮ2^,W&/T@onƓ{qv3hIJXH3op0^gXw#c@^x7쵞UHrl:WRkTC =U.8%xqµSjTp4rwGϊY^]3{OoGGՇ (0K%zdiW ,Ҹ'Nū$7Q]\u %mzz} K-cZ)qzǻőŜhP3m\Ovzٵ;4 UUظ |v V缔Y"%~J;>?Gw%5wۉwvpPXaƄK}ZjH DѬcW6PfY N\K{x>nGrc^] ̙&hN!JA#8SS9l:UY &UTAt/d]Cao+C5գcYtR\-Zƈ!nvq*t破 s" qr9`|,m[s yսׁ8*7ڔC\ZD:G,TJu|5wɤxq|P<-azܰ .>03:|?='Rƙ6CNfba?~Od0Aݨj泛`kՑ`Vjn&] |>軎aj%Ga*P7@ cKS#XUM.Q ѡsj;(7uG8o3”jүrAKynUg"in*]` bAlM} e-$, L9 AJ-].cH0.}̗7/$qOFsB$ cm =jy;?cp]?j=8A5CylZ=3ƭ Wfݷ??*ԞJOOzD>f=$ܽd4'B`M5"Vmdt! o?KY.atuEiP4%T8+ ۹U0&b2*i_ prvu4;<0mO!ה A54~6W 26Ihe-E[hKTz3r ?Wk7D]|bʉjkat$Kj[:AH>vMT0$1Up=K 32M.N6^wu/^bi(ܖQG9][G]J0@OiTzXP`v@D#ezyiO-cJhBjỲum*145;5 dFb嶎LNen<>A=4=W REK1,TUsǨhL7?dT/:ϩ|%Qo"Er`/{,P1Bh"&z7 . W,˶h7o2D6n) RoD8o~|'S:;ӗQ-Bx8o75>Yo1?Eunko,ڤ ,^"8Aqq2ɻIDjp 2jcnxVk:'}r#+#nz:5<_KطLĀWjtm@M̼2U`~\aT^я#xIj8xRoOtjeeZ-1.]'BCN0t>P&, -mRdm>!G #]-pTPM!}4Fwzz~x1VNaǫd{ S ?Tz'n9mTe7H}~Ϗ`T<Wz3A&%ubH;]P60;, !kvv_PY#W59nywvBrmt^ S&)JNa9VӨ)otZ sF_^ ͽ&t,H &SJPd*%dהIItn<g]aOipEC?^ J 9D95K`pX 2' ▗Gw~E'U;e5&RȍcClJ5\&%z^یc};EpNl$DycVK>Pπ9n@AgNI9 v11JL+3+ib"EgvbDп$ɀi Iƫ*[KJb#'kW! zgA~svp=rXBDAUu93dfzMTۢ7`fhCtj\|N-< FPNи}ˉֵڬS^KVLAd WGHJQn)%Icι7}jM{Zb8rt_ǬTd^GWP![ S(s =k7 ~$u}0.⫒8/n߼lzkZ p0`EZzvUOouʇ=5XCYg1m|Z2!?"]$fҶvOa4~,. Q2RE@3sԀDsD-}3jƈ a ^',.(뿉o1K,r\ДG!Ac7QYzEp'>қC"LI=do :X_.́CѮ f2213,Y^3vc|_ud'W^}BdJ oq5Z;2,)# sdl| },GZ~N*J!_!Ʈ[Bڊ.zNzhQXuݝB-Zvy!vqHCn}ON3FCy;TNѝ9u@X&|'B2_&xA* Ι/󢻬Su(_G*Ìw'Kbcŗxd _j/pR+-P11G/K/oEe,Ž6\=Ů  ^i(?@ t۵5$Fиi[)F< {x+2iM {4e 4@V~ҫ^ߖLC 0H"HCqdCn 1|h'l?P&ֽDǨM9=Fx"/c[*tIdn` UJs)M4ǧ|eQey!ymY͓&Nbu]J5n%O$^7؉F0}8WZnbb}>B4k?wr;(GٙD,ņ=5 HQIH85ݜ[1%EcEnfw.l޽\m2wK^R4*Ko4z(\Ɗ0Lam!:O]zsh[zOWgԪ3RX#N-"(L\F$81(J^$6ȍUOhv O>z< AHix#=dPO]?K;C& % _@$3z[XHeJm`Ix;1K7 % ]V'z{@3l-ZksV_TeƓ#(R,sj̑#w0Q]LܢGHb?BzYHcg+#`jn( ʤ\,x :IDνnTs&, + ++/@J;}ZvSz"d"93,UAPԙ(yۆ w}E_;h]b9W!C?0{uy=Z(5I-^m!GTn@0m3u5I|kYGIW-c) ay} FE>^׃aڊá2A?-{`773a ~pݏQ "чpl8obia:<)dQ^8ۃIa4AH=r)* g_0NOg{u™ӿuw#YK;cyUAJj5 *4W"[_^fiyCL-&9U]hdUɩ!?u(e4זp""faƑms͵%kٱz?LjK**hK'U'`1IB(beK|vA`q(w nư+|zHE<ߦ#~-5'6'+cvNJ>,q3)Fi)2ѤM} ? њ8Jm1nmlZ t搫*"{qԴ^I:1j5d Xy=gGzW*=2+NI9/k#  Вe%ᥐGІ;' Ht ۘ o_LjN|:{o"x7(ldӈ)m%-|[ǝ Yj:|e# .k{JƁn*L1ARyɲ:WB[)6qܬLVg7yɘ~VaL<͍eDC_NK"3pÛ3\''^GjfC:= t2n jTړb\߳C Ij}Muqp~I b)1|yd|o";u$"2jtcxɗO "i(sXQ>P_z9^dQV}җI,.g ~B!5o2%Hr9n'^ušU˲Hϔm0RhY AfqrҗW˙*jg ==P5Y͡27s4Zw'!ukW/MjFn .u.g*EP}!#ɀh6LH^A3#&K̽Qݻu :٦mx{]ͅ#4l(&Il 0S=xwc~/wO:tB_mojHI8,9qF7G+V/v6 vlX)3GvE|Z0nP xz^1:& næw雦mzI@;/Bpx`~KB -c1) (ycqRʭy7zbN9GlNl%-Ծ tC0|ATZLC5Hubt%_Ff]\D>ݶ*#vt02$QW7^ug,/韢x͸c'>? ~#sWxPOVF|V{J h%p;1kg ܆Ե3fL -% F+EuA#,~E.IR]pqnԃ]ѮU Vk>ӵg_|5mh4=4Klh?ohMx *Jwz;n-k)x=iZa[=ig+NE0,/)d0*`DZdRw1߬pfAO+)ꌉ˛`0xh\ c tbz K"`(!?XTo(Q29sŘQ9k#8f@$"Ma%h.-og뙲BYዃQT˼MKcx{K xs=khdrk[ĎS;#[b1Ĥ"NWFߧoɣ֔3S+P@ġվ2kpP6V\c 7 `)ybs"9IY&6M Mz? fOlyPs*6] Z@ip@aǥ%aQևy]rX٦@.()]mX?@Y^cT@X$ҏY wR1U0ϻ8lw.eG"sDz4VϻDJ%;$#bf޴Ih#k+ />' _Z17 ͂W5;P <Y:0盾tfQS)䥎 =RTOy7늼" 0ۮFKsS ,05-Ҵ!YUN7RVYs](~Z b0t ԅٞ8*8üv zlZ &c A,o|'h'h0fD,VcB^- =؜2 8'c?W8AaW k4YUx&%Ñ8sJR?d_X(uT;Շa۲Nras3|d kڷ)`@KƬ[<ɓSNs91@rcsm~auT\Ұ(d ˵pS趯VTJ7 )%.y{򎿞X;43#ޥ^XEv 5ULN K3X}hSY3{V:j+F0 ڔ:߈<"7S ~l7MgfSZ}fa~ܭbD gZ@փ 0ql/N  /hIڿ'3LL?_i.z69@B1&k}bE3Y#3ǵDP;8Fgx =QLqigF!<B+2m!u |'E53v^L5*+ag6Sʜw6UE29&Qp9Q2AX2=fjײ=v[!CXf ,xA>+`ziRt5Buܣ+`/@ ?g(#?l$|vљRG飵y/MoxKHXs&km5N@rǘ$V}ۥtg6q3 |FpVomi~^\BQL~ =J%4Ϲ؉:tywU8d/uD{G5mHBD͖ 5}n2xAbŌZWO5_N)O% 7"j5A. QRV]`$9{N9ݪTѡB(=k'G¹B[biEKp<`@ig?Pb]{MyT"^4\ VpuiAw2t3SP$~et6[4k[\+-23vgph'b { B嘟|._aOh*#H=+968 *nݳ'mk?> fST́rKa/27u t Fo[ En8iNÞi}^Á0 ˇT /ň4-VVFGvQ`w-iئha>8Wc#tBΆrO>Ǯ# gdIjgrY@/a+{8-V^pW^LT.\8ߑ; `l86bsaOVSxaɽ*Na`ruJs-p{B>y\A3d#i1ˏa̋ӝ"+ҙ{Bыό"-4 aK!ALyxȉ?`wiElJ(!1NC'C{V0آ(=rh;FR2VjI*@l̈́z^na`:Dpվ\D|֞.ƈl-0RTr'4AHea{?3Lj9C&z:ZD C#m4'-!JP2[pZ^{ fΏAR|\ptːu\b/maIZ27me?hHրb݈1uk\vo֩yʼnIyRn⺙/'Bh!(ZmPps-Ҝa1£Hl,y5ZMY&tZ2r548O~yI>%ITi-ظf?@_.U*k3.1#.UO<#jyD@{#sX$y ay?*v2 cz狐F98Y=jLR<9g-VP = lC¡˖î%О uL. j"<m|{h`xUr2[EUt@*ĝtDx/dXwڔ2dVG2$] ,"^Bc K2VGSMNWq qZpK7WN*`eO $\xЉSprC@_< ңx$ 8|a.E\3Pi|;ܤ#i23JY.بۆBg.)UD.#"d2;Y_m@;nIƥu +2Bq+ɷnMcrHETU4<[q˪^q}&(DdD'nU'paek6<mp0D`e׎2Us*OMm(lw̸V Rmув|*#˦ɮ +ԿVVuW# e]> ͎K.ms_^>;DO M/:+;VФzZM[_9>߉!bTDIHv~/7v %~an JB0Ӕ8a4G5꽗Q=~\Ox:%Hzp yQ = T|j=LfSpZc4}q7+ٴqO' *8 <8ALm}uxk2Ij/,SR38\'fkefHbԘ5';WuK`'tH"K;l&}t!& /"g'd߶ sa; ~fӯ*C["7g݁$vqc3/aTp V 5q&%Hnگ 9hf5%C6`fO> q*XyZI j' YðCYSQ&XY8pE1=:frBϼSgumB`H Tbݬ]eE<{(r@dg_ 1x4 RNӘLz P|3w,9ODYtAgTV8mUQj(*^sE+ؙY|QlS웪֡M< zΐ%KRniz0f MꗾV'+%ˢ(yn到4Ӫ ]̖b@ E uzGZQ=] Acs/L&QsKa6=>R!_sa[Tka>#؛z& LN6==fO&7:`8 GIӁrϞ " GB?utx'ٞ_I/{>4X7Œ)jS][OEa[YgV(b8\y07p9"Ȉ᭷baQP YW\S5]iG+1?y[N3Tp o+dҞA`;8 η~!&^%1='=DTpTAخb"ArIcj6P9V8y $^QamZfr8kxuY,-M^HB$3[M]iB$|&f>44XS5{t.7 hZ#cc|D~@rbRV@526ܰ5ACHtU2611Ol[[nb$6pp;mzf/課X3?=J gBa.JW0-Qr @QʅʙYZZvaaÉxT;}߷؎|8&]@Ŧ^iIZT2KM9#Et^7 ,Vrz~6z!ͽ~R$ox h!ځH$`%OB})U n(gW\DiHv"UHϝ5۲y A?iž!(vϴU5FFwAe% EuW\uЃѾݕ_QWQ)nDEĆI=JCYnΌ.BC%I(]i|AN $ZZ4 EQ1 Jn>^g:< Re}3MmP6tɍA\{Ĭ p #tG*.$-*&9$+OPQkV]_mn؛ vf lRF Nm+`;7YsYMqԡ9.Lk:RbfJ~(BXF.F.{qAqGy!&ƟZW 9m#Qr$=2@qb7Ex_\0J'aM E =o\p R/h6Lndob:M J I⡭,v8Rqa^%b ҺݼI5Ep&+aM8>ԠV(Ut$a#M?RX(ŖS 4aZ?"ƋQ-{{ɇ@+\qGd,@jj늮Sd)|gn*r?V%'()mޭh LI :ѐѩS3M 0S޹n(S_Bo{EyPE[ 8"g#ռeUM(Kpf,us֩w1,uW6a&KMwoA-(`'i5G&F\N&ͱl`;=е'\2Ú{ yDCrtF~PzW\qKJŜ<sBZUBxu_ eo/˂&P:X]n^uTU!z3"FlboVzuWB@R&+ީT.noxzԑSgzPםq8~ 80&E .|) HŮQʆ^'W`A<턕Axux3ۃ8³f L:/dU㫴rR B YC_Ҟ&ֺt"L&Lwk)I\ C`=([%"Pt5ol:Y\gtk@xm3F L*|<8ˬ'Ysؾ7דqvtҹVN6?B{?AZcU玃"LW̘|f ^@NE+q~LaD WgY5;grZx-H#GSt¢Ca^GZ8>eKz{7Ri/pծaG7;Uw/ή7Yg"Sh2ۂAuS=q0gwIցC+U C*.Z; VI:Q!rER?_8mkVXEcűlr T5J!G̱&kѠ@(O:/ϴ 5٘&8#Mخ,1#lD'w"8u zAZ w[ˢ8 wYu]A[gE(KU*#ȄIoDm74fOd[F_C3XY!f~9N /gFMm&h Hx)!*Y`3=ŮɈWii883Ē^cz8r8{?[x|]tȣlCQq#gͬ?TNxJjr(@ p1KT{chE '[KZ ~\-REYTn:6&RW2"7U]IVLK-ƅ ҝf0'K2!L+|_fMxg9|Aqb(MF9#7T+Đ9QѬd^8 R*SЗZ|\T ?NYNeJSW_"6ahKƑ6lX2t]\awl?AT%-ӄ" RW J%$3H&nX9G %D+=bK/DHЊpΛDyfˑ v cYӗv7bl "Jr;N^@>y5aO؅C"s|CFwzKs]UHoӸ_ #f{k CqtA\7 H_yp/c0ah[B"65M&ӉgfnA(d/!ɲ^ *c?yd鯄cŞPzN1׽eǩ\ؽ]y`ҖƖJ }3v1[ڰ#8:i%1z1wrʙ-V4P`lNW7ϫzB{\͉9(bWLgdׇ wU]Upj$5ew1e/ue jwPq&|دKK99Fd6I_cE΀#$td.q $VmN[ 7EH2{Ň|ya| g3?c~|PJu*|K ka. Oi;dYgmS01_1kR tơu"!=M+Qi_r*x$X5L ž8ENʘ\JJ})ЪytF!/+ÃQ̅2)s:#ͅqL,gkAj9&:s( HM{7Y*(M XG,X$# YNKm paNjY I@[ 2+C&:;9?3Y\xTNumTOjUSs?c?dJ1'R!bƠt:VEX2(z:6c5Lb NN+M_n~>E垣rݢ$US}o s3\yQ;,̞^jJ(XX`3wH%ie4?+HlZh,P8JʭO Nm ,xΊKnu wth \&dC&ZT\Vp=w<1#q"L"&Y_4v 7xU۽[I# b5A#j&cM̚ap]4yq!x@+b,?Hh2W3+],G {c'ixΑ$FXg-Z^WY=`60q"ٸC=>ͣ*NJ I֨T'-Jg*B.2婲nzt',_{1%h,vޭE|h}'jVFYu hT[q#m5I+a xSh]oҟݸRRpj] O^洲X4dݵ\lLo]zm#s"H>b=gOe4MA 02;*= yU}e¥XOAo1[D=\rXl EG'њ]ʽ!Pp1A#M7x IJtk>=(D{)#؜gY_QO:DiT#Zu-7x~T`6hǞެvYLy$1j B:um?e7ON㈬ʹ~.}z[24[dgMfE-g7j?%XL2jP$ArUu4 vnX' D&OK0+^ L'KpzRvX=|Z++ʤU EQ69M E P_JI¡SG(:F_;aޡ3K7U2-Gq%?۽!ZzzԈ}ѐ9B}-d bZõ7o&l }~ͶU) -tt>('il,Rz&a;MϲkMpՊήךNͣ'\olO$鸰Ν zrUS퀆sm>\a A6RWW(Nhg|]QtuS}i^`Kܙm~wUQ.䲛azˉû3*aS9UlA%݀O7pU4]e߶LCgE@vdF5Dq+Q%ebHoN!؃ 5wJ:TJ3+ !#)fq[9o)]G6_ewIdau$6K&S˨fijU; `SX?rP}aJblڝ_zdMCO^_:%kY/ԈǗ[-tWyxoNDOǥYDG<1+L'Il5P,ʵXd!\'l7zihoɏww2 7Ѧ2T5̡Z\ _avJ>2nV:x2T2Ǽ%`L49s`PiRA a[K2/|} /Bٷ͸TYƅKlbi!ՃͲ.a76`a xBu #1nb(,H=,XׄxyM+&y`L,''}՛㲾֙čF˛;Pe%2<]hDazqt^}?5 kI\SMOPW0A)/Lf636y,f{24QVJX7oj/pӷzhb5s) 2ֲ;XM&a1boZ܀rG X*'qeי[V3CwЎsAlKBMre$Lodn |mlC ]Dp1N6'+ nNWGdҕ'}׶qw㖦;bLLkC%PWלS[41] kkU$)f{ct—S S(];{6 6@Cֹ /VX:vb!=HX!L i|Q/vU"%tgEW+‰v2(su19Lf],nŪx^ϫnаdr"\`aE`&_W fGCR WU~]!.K{qКCesna (s*rM1#py=U֣9PZ7bLk L ,n/izyk4 Z BoT㈑j7ڐ@'\cl`WW}w46:32wV*Mw Oq_]"2-6l8GWlL^))A0 qLS-9zM1pa:X:v[5H>4O ;0lX Pǹvy9.َH\IE0DLGWn b󳌵?Dؑ=S Y/KyGÓU`푰FB"^j76*,.1);$X' 'bf i.>!V>bPψᐡn 3p'#ׄX""rn te* zLN :e>l4|{K!P!W eIDi];QOc% >tKB)~%Z_7clD"QA9Pk@_hz'%E?=-M|M1^&`> BzhVLU`=5饬yŖAxdI[մbA1;d7[3ҩ~raF Vw7"d"ƗM?5X! 4jA6c)?텘65DLx(]?b.ƽksR\ac6pb8 &7"q!cd.sc 3~4f^xH/g% g:Wn%Qr7O;Kp!ǽ+U,ny$V墐ۡQǦ󪕦7^S5c=ϸ:N Y_ZsX(=,~;ct4?vnds聄sRH yU>Jw5~Ұ%ԉ10QȃL`w{r}jy{R@ڦGM4ı0M aܞX) MRR .ػ, 7;w3 WsA2 SG-1,ᬡ+._ m(\1~f`oXW"pa ɺsp1|=X@àD mbP 0Zᗅ]3G" |Փz+;gAsxtz{*r-RY.R6_TP*u넭> y[9&.\>aKZ͢avjJ\Q]p-PeSۈlbVګ|+HB$qo'*s_St9UOvLN&|!7mEWr-҇AVozoe=deb֊)a|ɡ>Q=nGQWCG.?unYcٌ*/ E܀;"J7 U}jJy ܷ zr-Q~n}IkOV'QmYolY{^ .bY+.< 3E8Qbp rܾXF'nʈ5ǒ!|;Hfm1fz[)q Hfk-áW_'݋`7^z?Ow{GS^PÜuX` JhϷ=wbc@iIJaK(/)V׋ aЅH^ynÅI'q{L-Q)0; %W;N+u)P.HH'](\fu{53Ң:[c ??J o7$}I0ZL[%+?cgсZ`x[\,Xk{mG;ϓ%nM6F^E.7=K%d~摑Swȋ}/x8jPuU Qd@рV{'l߇~ W͏c{ir1 _tn.d;S5M 1J83:ۛHg$Igd[!#+SbtWM!\8/9Sըyt񜗡tNy?b78c8|#*am.Lx[CC]LZH _D[bN]A 3Qe-irQ#4tmIhk$/,>#w4g}ӒrՒ?H=B>O2:BY铣tv)' ’1c.7;8NArT#fGs sɚj"Jv+eIJE֡kKq`6[Wj ]uK;7?;]\}st` B3*v 3l)FpYdX&LOkX61Co>1L>ˌ*Πyخk Op$XߡnD=:_MΞ 6e1±Ή)a@j[TՈ':`>whxUcÖCw M=hD~Wh*o.?4-}-_+wdxL.YIRλ90RO1ER4j,"%@*_` 0 PI*wxv4#Ry.KFDQSxI{1LPA(@7VLwv VKK@aKAÎ t231F6eqpSb*8tlQ` nw,|OzF;}!B2*2z7zm$EAb  ]R2Hj]|p qۿ๸I=4;o'xa}Do ,, 3)p06M9[|ڛ,ZEvQu aECz!"&IV!:"eļike;ZaLS-2$&VXms@ {Ip~oԬ0aw0%(/"Dr13U!޳>?% "iMNm-鎶QPhҠgnDrlOAjc+F(͠0|YWD*"baFc_MQ"jͰ˽0/54=\N<|ҥ\Ys]MqtգLd a?t< ̇LT9Oe= %YaM (nՔպxk/PLZ }_ߙ%WW4 uyUUNfʲ1̉.8Ut xyAt̾^ʅqnRFOj z u2^F*c/;D9*?H.bW'+o}@mT2*w|=#UO8Ζ]@16H㩌PCwQEZ A]+̆L 噇?$|Wefщ~9 LnD+؎6p#V}E \ח=P_@kEJisn.yo~"ڸ# @&V mͣ|Yԑmp˫S^#vN- B`mkhܣT=Mmi[] >/[ys 2ri+U\yI_˚@\n3R0PuZ+wUO\?O$ȲKer8` BՈQqWZւIdbEtnR h]3'~T}8\"7)$-F^9;bb?coGTU^-ڰgdH ђym,ʮ!!֎j1(SgH,hDhFjwi Jfa>j^r`&:XMֱf`vkyFHf%j[_xy A_|ݿ7?kc/ Iv^!*{sy*ˬ> TdZ'Nc~[$D ۤwX`JuPOzp-I8s  . ݓZuT_$Y22뻝Eh1f.g}-3w|i30݅@f'Uuoո\D>Ke[02{P[䉂ɚ!T ԝMEr`'Ua`vO5$k%!3lYzt%7 VznX%o;ԧipH3;+=GkAHg&FJD&hA;.WաU&A:g]sw`Qb zi*?thbq`QlA/?DBQ"K!Vdf8Lp8$#QYM/aiS4:apo"κ2־`HGaGj 'ph|f$l01a<`%9^1&&n)7}'uSؚx(4Ĝ&K!/ӟ/ӅQH3 h&RE;|17er` #! y8;o q~]h<;A{]ʎ&2U$Piw#l~C=9 66(gojضVlH\4i H@41+z/Ì NI74[بpm'9y aC]z p|.1cpD֦FG_xQ$!y c5~~PGn'`x_VOĊ^(ķMX!E'#):QIn/.5E XEʝ֦x<`QWcWOYS̠Jl/Gϻ;_x-mrguں-zYַw r;`/x;okFGUiMo1)Q|9R,0hoot)-ngaz<$)Yh*.uiB.Dzcߝܯey=^k&%,/ b2tuPޏKFX7WghT-Is׽Ed|lh'{"(*zJā[>>ݓD=]/E~T't",HBCCM.?ΝxӵXaOY5U- HGBK;0(`x>O|3gr_@sND) >U![Rbj<tf獌1 2I>!6`^5qpcH_w Iv{[1c-Yi= A+l~\"&WP3WM)=GHd]>)Z߷l@ bOjkO|2)s<"dS h!S{nm@4EWሆ5r@Q*ԔqaI2{\cn^݉`Ɨz`G(f~i΍؄f;`\s4K(7ObK˃YO,_4ۅ ~:71-^rNGq?$R&=DA|`狸zcA?E.&q0e#e\Xc;ȥMߕmM=3YX#/1 m>Z9߶JX]V-m~( ^]}GyQ!dz^LdoAH.ڰfiOvzlwN=&SXV|3M¶ > >ˢA=5OĉUP*keUgKtuUxstC&g2qK\E8,/a '"XΨwB`sNп͇o䜝|#,E>(0<[G@TkSR%[(Wz38u} q3_luLp,ڛe"Slà:)\dοDS"56<N|_= &Q> HYl`D gb0 2L9:a(? &8=k7Cj5mK=r34q0UJ9 l1S}}VcKC. &$ؾ&*|Skp_ OGAh"^gߘ'Xܺ=|"[7Ss`R4 C6O*aʺʡsk.fG$iZ#"<⧿yZUQAE<38"mޞ*0Xm}fxuh9]+0%8!hGH9ٓqZ67j) t ZyoTϜusH]Ҷ/fTEeo9.1c  {5Uh6]l !|BV:  Gb}uZ'97`Em9T?fJڥ")PB ؼn$rKVkEV0QhJ]6XtyHS^aY9 )orѣC ˊ:],J6(UbY(OTL*-jR˳lĒJܽJs de7솀iǃ<ͩF=`/ZͱϜq3/4_?C쵔o^%K՝I(i,%1|^C:i;V?R'UvYqKmnT,>' {\mlRԌhBs;Ķ( ]M7 (²{WXŖX)_9:^6~G6^æO:bפ'I$QIǽ u, 1/l) ޫL7Yі/\,|9얦].x |co)4gUXbJ6ڑgjfê(nJH{ȮjsvC4*iHGfĂڴTWȸKߟi..!El?ҁNz zK1Ko_`xrH tF$X`:{SKF d`.D!ȭ\Ҹ$wLcZve9CͥI@A 0a-;dj ڃH=;D%C Qx1*4t ɤmPt l|vƅo򱆛l怹n(͇JÚiWi`]qU2Vqdj?:4m,?ʥ>Qj3ӛ.~3>nW|*,0z[GmA45+A7ZS*:,\lg9HAg*e!њN)߅c.tYb}V!RnHi +l83%|KZ>W{'zj8/<*7N2a٢w@{g?(Fx>xa6(~ smRΡ) %.{ : ׾ x[xFϻ~Ҙ&hSBAe$l~&ddP){W-O9QTe[KbtiH3t`搏N ٓ.r^,#8GtE850 w%IH;\U/ؼw3 G1}L$vṞyw[T(V+w=MwTWSN:ҡLv2$۹.*CuLCkdޠF M%oU#EOS툛'<6~!33(bV7VU8ZHEg[gD =,gIE]Ѵ [=1FG0.ORŏ`$̧~Cf'"U902QU:@d0н_=(Sd* 0g{G.JZ,&*HoI(=g%4B`ZB4g|&ă>>U -lҸC" {"k}NC/l,R{}"-QM`H MVRj{!NbN.3܉;?^g@ 橋=E5dIb٫A;(%vUYS3*qcZѽXNzDam̙h]ݝq캀%"G(+H3fe}^zks^ޤ3j(J/B9=U-{FM<= Ma+!Jmf MY5#jϺ =QdT&ԗ;?MH^n|xB'k!m|\ibBL"g 8W@Ux4$ja;fv\^<)R0+y*\O%B-dV$j'M7ma~f3j0}q$qy+8ǽ-*![CFU^C◖4yAd2dGw$j0 ֌4k*ǖN|P\Ey@K˃ pM\Gs(ߩ6MuRl R:8 FAvVs-O+8n+\R:ڪE=Tv, j{L`f]Z=R9dUnY+Ū?>co'UYC,h[1{ ]*z.PJh#|JOzlz-ţgT<)tb6ըgm ļ\Gsai^)HAm4ǸܚH[>"b[h.t6y8]lm,n Ax׌xJ 6̴,cM:a1Gv<\AOT1%]?VŸFfw?5:)5<ėB4;_U"kеdǺP3]#ϸlWJ{hUڰhZ!,#z QVn/&W=w]2ȡMB!4d 4=.#&4 Pa~g׍OE:y2#YZ|Q& pGy`.؄> F}^ljԫxum)N)QGÕߒ.#x>#1wc!ыj+g]*(sf-v `XM1RPriHTt!pھ5Fq;s\ζo9[H@n.?X+UEI6!SDy2b?~*Hma&Fخ鍒IKpR?gAc%#hވ _f Z *АFA;iZJ\@3;Ĭ^ ۟ x(5Mffr[+Noʄvw$*i>Ja4N[\ _8>36\y6}tZ}NNVxeIq LveIhO^#$` D<5[t>C LhT\: A}iH\fȵ5K[C#?3qz72p\za.e G݇ė,,(x5ag yeÃ{ HC7 ת|zɕ0 QLw]XAwCu!}U !TQ:¿ ҳ%>wCNնu [6x ɋ?kԑcu g|!_v'tt7gArla= ̑Eu)&t1}>믐s6-p.lr~n|7b4bJҩ*)enlx%pk$wb-Uhwz0^XA<$_]ی⃲V 6-FQEDSsHr|tAb1VRү'ZR^zs1W~ ܹ'LgQAS96ؒCӦkpt4݉*K(+iw#/˪eכ_ ]PwX5uPߖ tog4;F*a.R"DvjϤonS.i #}ZCmr&AdYI!|@7:+t-i]ՊR2oemw$Z4s[-e,w7mXO%x63_V[=|бD6 zmuܡjR7;*;uGV{٩TJC S72B?PĤ/aW"BVI-]dBONyD:`nn0gCIlyykmyW6ĺ/d7ճN|ra& -4){DS]wO1N&kgTPُK"uq~&!rjZX"Z8Q_oo ѿ?%oB%cCឨFCGE qC nrb~H|:fMӥweuS,jR_̧F.ɥowu#~tobmmNLh +*V46gKꪮ94 ௤}ptL1S̒xjWo0ZL a IVH+U:@kf wtQDS>k`iR`~jWힾ+bF``*Vs}yh6᧭X DeEO~BP*o,5+cQeA 3Ev7V.5j hIPmS`QWNRNl (ɚ.WZؒa˟Zu+gC'ji} BA9`G{az姉dӘ&z&1뜶jsF+E'QgsyW:8 #%V!u&o{zK[ ^@~-*{ ƌbYmr7vގb*L曮Tik }{\ZJތG`|[ -f0ҢQk7=N,+Da \#Uɔ~?Lp@HkWЅ#Ik1XMd7pII_<ιYhQqk-"4p`%/o;8-d&N}'S]L"r|璪3j37/\7 YO0 JZdD4(su,@&LHˊq2Ij[l v4^$z)]߲Fwн:_'-P<|*n,T+ Bg6r(O`>6bV toU ,_vW~&>:Wl/%D~B4.^d':C҆g.& +u˾]Р0sDyl8w#tpGO=kyDJ5xx&+q?܆Q6xMkaST"Y'.X&^Pi1kz2!idMJJ+uda ˪B z$pscΖ"QUL<)Iw $)tXЦ#&r3l"H#I(N>t+S-.$o#;$nnzi+Nup OYLNJДa-#,ެd|ZаbI0]sX eC*f%\zCGQ'5 Zz:S6]/"'u &؎e:MKS) >*\Xk:cV1ֵԯMNcF{\>k^0g_;UAq|wL #Ԗg_q6{NT @ɨx/32P~H~Bg?WyFP7hH)cEy sXi]MPk:[:omA`'@,}铉P;4#0/m^?K-l+#LG^gT䎏 L?9uC. h;R @8:=!amT ;ҵ+5DDo(5 = w^&o%ojP:dXo{,IDZێ珟.F[<}ѝJ~aصԆo k&5c(u0Rutu| z/A. 1駡(,nYox D]!4;&~ (;ҀPy|!YwWONvܞ(k*+-_oaɕnh=g2hߋ`z;cUJ\b٥WfN6-lrRRjx٨-JKLKmrVыTsAx%`fSAeviۦ2E^&[;{L,B4UVWuDh>@{Ik/,J9~%ڇͬV,;aBUD,Y#6IĊ)@j]QeA jiG 7色Or qݕZTkjLxһn՘g4h*[eue:~!R1I78k]llc[[x@@#f=`=~:fCn)џoΎ7+2"|S-;,MpuP '|oQ3ֹÕ)S%_a->\mOaa0S<^;>N}@U{ɯHÂ}bGj>>ll|R%`l~ vhq*D*QMKzɋz-„hqX G%8 Il ɣֈqLdv {$~|,&V>=3p 'i\:(Q׊&VrT }Rcpnh J 9=SFM#\4̘("b ;]CYecW"on ik)G^Wx,rDR*hmS0t0T\8~jT q{Ssw|s'n/ʛ4N&*Ųt$ #EG=TotT1vJെSSf4y[Ioa[\H ;!D ډZ#xqxR TeA"y"+m1׋ 2}h"K˵Nm0I$bm8%uגkXI&=48+a.T.B&Y< +<׉n'S[G ʹ1ө1tjm^D zS A.Q-8 ~s#& 7fG|XsQ"4R6et#]cۡ 1%(Qԕ=a:U=lO#ouMnGJ/7pjH]VۺF{N (h8kݧ07Zz|YN2=Jg~jRԴeg2XCaQen[68b[84 ]p:_`LiMrM@eeTA&H@IL1r3 0zf8~\D]}gapI))FE^cIC f /;pJdD{tc) = 7MG=rlQ|3W-؅st*:m9z?h _\ b| .2[r!))׼̑*<!(7Af(.9BU P,~|3:<\Oĭ)BwaTmS Mp(~)%qܘZs7&I7.3NZ ?]QD>s?>n @\x@V9޷yC=mLi#.R9g20˵EVhcex:t "AC*|4qOÐAwYLaPT*,Uͣ&* ٶ(]G`S mr^Y_5<{׎gӌ e>\~syDg&PpUM"+Ev& MȪXH,YIL庼u%ܶAoL )?2kǥ˒^0`eI!daJ8~?K粔+ T7kAO9YZ3)*d}53ٹMX{IxvNyELwj>"x xKeRvJiWRR?ӻ64Q)t;~#f`Sp M)Iz-es"@QsP}LzC^48xQQHYZ @jj4+f>iJJYQ0!K3ۨ;729ۈz,ʉBwF{6:!LǶ0K] euCN ,F tLHLH ikf6GKW{z{QcW1_T|z`o-'gE!#X4w}uR eθ)CgQ/@[. }]HC>[|mQT{Y݉SН*xSь$f ?8=R-%6Xm1 ~f"ynA҂e]\*;,T 〖o 쐇/3;3șfL紝c:}Usqς^ ܑM`-qDx}3Y ~^$D[Fqzu66M3O ɗ X1={Qp#g9ʃ}IB%|+eF޶ _7\>xo6blnMv^~ /O9ΰw*oD9FHdP h& IUt>j IxD"O?TpS*iƺSv4\0Ҟ(T46Rr6i]ο. 7(E"4@ t<} WPƨF^xBթ{ԍ_;o;|[=iq“AGɐ*H@^7kTH*V<3k%6wA|SY>6E`*~I  1- KF8cQ% 0dib2Gz9hҪ1R$xF?qy͏vA0[3Y *xNy!UpGFRZufh,d΃M;lʉ Kr_&*#^JKؼ|kfV5l)/VV/W?-\fILPVD?:Ɲ)0-EۇꚠzFVCLfS4@U itABj]l!}L;!Ӫ[=G;Q5X qETNRoiho`c)ڨbT~N‹78aoonl\C$DxeBa:SnMB'FJG7DERѴ#y)ԯ~#ypc|hh zxl.X_G %5o:KS'*Z_AZ08r\֛*q`*M$tG-N?ui|xY3q4mm #bLơK0 şofJ(`X2(U,Hw VA' T/)595q՞B3gR 1#`B`/=SB 0]/_yI6ʡNx}#i;^56l~M-i1q&nx^tCp+VɎvA"\um h^ bB&iZ*(@׈EU"@u󄁺o1#sRZ\>4 •~Īuhtd!vy8)'ťx.jb?,0,Spμ: #u^5hԮaŦ,ULMoXrkS+ (:i="Mz mW+* ?_~4v+"]p5ݨf/}ʮƣD.ե.@v> ÉDbFyB&73g6}$A}4|\ tt[8=ntmmܵMdc {A+#Ju2~4&W Uʹ9\_tۡ<u[veѓGW;2j5& 'x ApU*qv׊*AED5qTL_s̷L:r2F}eD^ywcIk*)C",+Iv]\Q:;BaRm.T2gڭS)PB^X x7=xH{o$p]]XR?;7p)s+#%'r'Y*6R{Cۍj0y=FpX y Uc4KRt43#Z%7/wZH=g^ [Mb]J˲+?ҪKRp/ kz΢HUL!JIzǼfEv>P :7xJ닟D;u>ӝd(`9D'e t|( M; @%jon[~Q:GrzЅvb$襃"(wˑX6TN#}{z5tU}SJz&S*>j "V L(*1f_Xbb[< k K< &}"U9?73bW]_%5:Q5G;+ ׬S/.jFhw-uX7~c:=6}[p4Xi @ m>rZn0ԆyTB~)I@KdO'=Q m!JheͳETɑ罟/[إϙF~UZgTKtoFz%òh5BIDžK /-%-N=Ca[jbYez[ ;F~F=?"sYQ2í}FrV] 9qU3PP &^uhC;RxYƂI뺵i]@HSu|3|Z`|z'$L}O\H'1a\Z/dxg& 4+D8AÓ)j@Ruj8|i' 5_'A[{/" h&?5BdT=O.0oen鸎8?VN,أdr[%QuRyqWANmJ#YPT -ЄZz09b]ReN?2ZtmmĤ'F ?ڲ0Nt+3h~ .cVf85#k>@0{{iWh5vnkEfWg)[:z{l:N}OܟAӎ IpFXܲʦJn,oU.qڛVT97 BԳ>8 Ho`ʱDqj6dcQ,;v12xҩ t k,D`f$ftY$޺QBЫ{N`~M'd(iRf鍭gSrEb׈g(E[L>I5Kn\ 'e WA@dߒ-b_MZQV79CF=0~JdQt-nBE$0""V8ҷz${K'sd0L)ElSTؚ4x1gıRXKn'ډx{\T`a0n {7(ٺV-ޠ49/,>( Z4`oWDNI\5I]Ek$oBaIjf [5r.MVIv RZ'(d)6v5TR"U9&/~W Z x?˥W_˛cdpFe|vv.4q'I)QuSHG3y ~C-Y5br[Sd5$52]cewRC,6 h#Wt%{enPwIO iO;*qWWgS7y/3L~S|xbq{yLs Npi Znnz++Bj)cy*#듛Iiwloev4#XY;cC4d`B0h Y]8)2Eo3Ai_Icex G6;kC˪qJnv_Ýf(ʕCM/xw#G~GAB.iY7g aCq<|gfJƥI_x> m)fQ.O0rMmqr&e?D^^ -6 +,Ha J2^vp}XgBcҾHēʎ J:-HĆ%TK+ d|^q^Qg ˅61ؑQQ+/24szÎ5U^W܁ Ww'\2095T38p3@t/)Њ.gwe$ldwNFpEٰfy-'  -WN"LX/1&ei 3wc+j g: 'nӐw`u^*$ktyBƟc'[ w mGg aŌr`;W4>Ib4ϵs( XVefan!EP:$VGLR]>3KLXeSjta&x!LnbJch@TVK4~ GcJrlWQ3+k庻dp˰oNa: B)_b kJ~y-ßcTa_ kW.MMgNU~ LC}/gߐV`8;ͯd}^c }FJ*O=swS\j̰y/ɦ2Z:3}ޯBNJ bM^G|0|t[_2G۩yiz}hQ1]lҽկMts/V&vܥq( Gc;}5KF|;|Q"kM|eJ<A-Pa_pΈmcDjU 5,$1`uZ1W,k4N$Ҟ1׽+(^zY).S%N/Js~F!EzJ71xRbJکܿ躢MT@f4 =:O1؈D==vﳿ޼࠶Cr.y>-`z^I! #&nRl"\?9\Sp?rNUTv_s񁃥BeoY:Sɰ8 ׫;oф->SΚ˙ G sя;,p3 o \7si-E*4ʨ3V(6CxьM7b۴;~FK&otT,D\pducD ǥ uK!ёm -1!;B;Vnm`ztM٦'Afڞ34ś^A lLʬD[sgk^4K\ ?xIR&-dRB)?%L1:S1`Қ)Le1r ¨ηƖ+"XCll;Q(P k,vm| \eO1NYxWmFyM̰@xTn-L\"sF.V@9UceK6VP8lӝ}%?LA# ;Q-QF]dh0keqЁ(dh^~(ls5\u'=0cf͔;hοlH7 #6"1 ؞5Pf"f5WGf%qg5ih=_g1CPHd>~JPֵ{~n?<~Tln,{/F5LԶL{ݤ2 ̠ܘ#*گԚݙCfZ#|"0_6äu4۟Mh)9}B;MB:8 ߇Q/kZb1pl&Whـc󍓅yN ӶXӎjE@i+aH`)dIPB77|b,cB.uX*-9iyA/Һ&V'# U'4ȼDOU)vh.(Q jg8ttI MohW֯WhҞeb!†޴恸6J~DknHY;GΚ\DA+7_@r2)\~r{݃EdDro",g@PƁ)aW n]𑫛zq*ĩfUZ}9}ĵolK&\݆,UCQ|;K`S,Qk9 q : B nh|o0W ZSӘ^MTzI *KJEP^hr%~᭽EĹ0QJ ,0~--G-Fu)K\R٭YأCOKRYEk",Ù)sCTl{{ڣT(Ռ)Vk(/KnXLQHZcSak,084'joai;*XUS|'c-]Z߽7<كvYԶ{I$pZ 9sb&ǭS2l2Q(So rVP0yfp(pxb!(@ x[D w`o#-zxs!hQ",536T8Fq1)`ȳ0W&-4qu봸QefZLtck8'&7 Is ;W8jFԂDTf\nΫ䱓GIOѧ #o{x.^ygaw,?3Tc{bGLnb@ O>rrtcG6m 5ݟ*$L[Yʭs:Ij o'I6;o31r@lQˋ^X%rx7c)z"ĉw%.?ՋJ8KTr0jt-d]^%JE ?K_N psV~/( { r8U{-JHQ$fiꁋ=jZqYG$/u_Z%~>lD"Je:6t-q|ZIBQṀ_q &oT ,z]56aBΝI܆Nad'MNU1A n~|9sLVŠ\y) y=W$ڏ.y~H# [k 0YC XE.-?Е鳋Дcw: Y[MR\ػF X2.{.j6x?0'3X@~z`~hB6 (2m}(5&% ^C3K=q{fg{BTCiEV [abŔwhfeaԘ(CmQZ+F y vCnڮD LqI"U_o5:+u2RaoIႊFĀwJ&PĢ0Ǹ5pDZ<T+iQw ^~ ml Ka7d~֦֛DxGk-^AS2ⲃըӂ'Q"ޞ 6u1G*m7ZW3|@4;Zɋ)4\FT+ҷ_ƣ#0^"?]HEX!ye&L]pc(yIݙ3Wع ';- ,>b6HׁD-IzYÅ HT sL>)Ċr^!G9U՗[;\fo'T5 :!5Q v/`0orîmr5$ h׹O5^t}vCHBC.DBz5x;X첕*UHW3E@Pb'pptP(MD||K4u/l/~ˉl2pPB+Yc׭jT`)ܞYM3kSNX3_&MY  |Lg!QX%?_VzY\&aw_brLKֺ쥦?R)M✓ '@`v(\u'΢ץZnJXM񰳲7ƒӉsg 7AfБ3ZƀNAo8mAu5ԴVnWEc,J30^0)?_|Zfn΂XKkFQ[1H 4Hjx8WM?a)LFx>\9-䐞S rgʅJX9@S]gú{L)Kh$ 띡{'6ct|? n94zR2ՌłƶԼcs*m%9*_$A}Og3GU؆>y 9βa.j=^@ޥGa '%&WѸaWtѝ '6+|Tcv 6􈄴 ΓN.Z"oEo ܢԆV0YN<=:Ԏ6^tešQ_$!26VX;)‹<Fk}+Ƴݣ@c"Gdy冓AN<Vȿ7Ռ^ F^y/ x3iIO]N'eNeScHt$m 0i3uD݇9%gZoS.}'\e qV58PHtM^4~DҋFf$UCG손2Lnog/L&+ d4P߃(jՃJeGOsVX!k`3q @&1?9wdx:&}+R?uSd-V=ߢC5u=Œڵ-u@EU%=~6;9u* y%~vsǣN48%ײT7ŽfS5525mpL¡;J7HH-"P}7%AV1rqNgG ze:e+O(;:MLmXE^/9Fnjp e%|&~58Fh 5N$۞\+YX]ŭ -1iyyKdo.m~Q{ Z ;qTB68.!iCQ(Bv;X3,IDTō[Z14bnכs?f -ѷD x!DS WxvjFǖau{A jizߡfoI\HBN%=44UKr/}b|v!p V5gm%a˼]o83XƢ:ۧSboeʎRoH'%paj,"obׂ<M~n9 e,+ -~2렓 _Ԫ=.KߑS zF_ {@Ǿ袛4h,+#͞oPFb_"rǜ!WmJSϰsm_yM 0#b#jaM RL1!?g-'teĽ:8=P&nw7ț!AQ<Kۀy ofeݾt4; Ӈ QsDJ!"o-oDr|oȠ1_!w+> O]Bz' ӥY~뿧s@gEJoNo|w, a~|LTm3q5~%9µ"pjIC׿]:GsֽxWn:0,̄1D>y˕>r<+o1 ǯPmWT8Lb!Aq%*89inɭz8QtP.ON Ŵix2ُVȆJCL7~~qX_Zwqt\?P@&b.@7CKz.BӓIR0a?8wlК,J[7Xx˹܌[f#ZR똸۰}mT 3h"L/U<ő{t5;lDA^>$-?} zOp:Lt8|w*=$ch\_ eio`O(&ǢQ;1s 7:L²rW nH}7@Ny:aF믣i3Xϻ{E=D,H%em~Z_Ot*Ivr bĤ,Zskކr7 ?)8~³\Y~ŋ*^GRΚdB(= Ro/'Oy'lagCP^$NE%Ej]GG"~jUTw/HJi-bIsMPtA-DlXj:̌Б,y2"yEt8ߋۧF .49\zg4h)n/̘kVOWor3 clURB,T va|r==tzo ,H /d9 eASpwɆociu=%4 [Kq,y`7JQH4NrM3ZIm9NxPX4Q=.ޖƤ8wc#H \ꢼSwv ;Hf_pvځV"Vhvf;FQg!oO pGYHǫC%(> ]h1ܟNÄ@УI prR¶\7 pÉY{/{մz6Cց8PW]k^YOa/]1n_gfE2_R h*=Ur_Fx'yy@3T4oU( uD2n,{'$[V/SgI@G1IѾS9Rg=ȡRm5(0jP#7GԽFFOnl|@ ܁D׷ GQOMjP/{\r0gם5(A\ĜJ ]o&z_%Df :PFf^l#'/WTz+GzVD3B} l=0Ǽos W3ϊ4ΎɻڼPG-:5ic^ߥ=& Smabar ?fݎ[mf9EqWASE`eXzíhȻC֨MbJ\) t=l2Kz.\ ~pAȂy 7c_("} 82@vP2dM..z4ok=9ZC =b(eux?,Sh,jZR-F c_~HRf4' =ž/5zA\~B^3]#!y,Udv_Y#OH"jSIw\QKSY\E֞5>w_0 6I/sG 3A jXnFH*?M,OGȡo* >Ƨ+FfZ1r9dP]Sdh |oS\R&+_ˆ 2{7 NDˆq4$*3+9GKWVœUsLhTY wͩ;Bm@RJ}OzcTcHj{A兀n<|mdy:O$,y;3v?>1J\2 KRxk|&  BNbuhБũnմ?LgLfuTZ"X?L9#_R$4ރ`<.>WR6NvxY >RU߈40>zٹ]JW~.i-\T? yUs FQ/6q2WbF]Xf~᩻Ts%-6C@m]_TsvޗRr@orSbGEŅ٘$;}o ˇ_ PO=1'tr?lﲭA'5a IG|Zq1sJmP9d6J"wZkBE*yڻ.QNdʿ`{~GvEΝ%F!~8p^cq?7zzV$\ ])@q ۓA;TAEt"e0^/4~_LX]J24/ ) O4ҫφHٵ[}L%.ZWDznxbw#TEXۯ ©jE!y QWms%]`@^Rя_)b㿯ދm}:Hg.g+Gn/'si0CR@{E @މ6X5G9}۪YL(Wj\@kbctvYM 5'skЪqvۋ̬Ot9֠xb8wyk.aB>1*r8ZY+(ô #eRY[ ^&\bOhY"וzU/T\8w4'䞸rVV Ϡʇ-E(M.]f{{)&x َ9l5pl~TB0EblPJN?Jr{4`iI> TE}_J$P`p$e\Ք|囂u1ꗷBͰ/Dߏ JtVvdFuF9x_Ե|-e n-'ƻ"_bTreQQ6ae󲺌&`K{ٹ29HqqBd#9&R]NZ, NޓMK!`]|+g5*!zl?(,fu5{-jGzם{X|:b*6dwNdMF6ȭ&XYU^]N)*֥װC #`xKcf\P5rɷ.Sc1NH~jQ"<pzeU̝,cVƷ^2 tS,/Yü4NRN:)Jc+{L-.n))U3[ Pvv7m~N@6<@C͡bŃ&N!5e|ˍL{̧A! ֝ZyR^bK6/Ӌđ^֛L;Jt9)a[DHwq]JpGDu ni`=#de [d,aZsMˇ2ZreͲ7IY.@T'FǬ'e㸍O!ygr)s ^$iE;sĐ#D7SF=;YMnDcuA"bf-]LߐC}{kKA-q =IA_EĜ~"s8ƈ@ X2e=7ъ}GBMr/X/LʃBYs:nr vT(=C*J&`*E|sO=y㵢xJG_;A!X YWu:0IOv SGf7 b7}@_SoŇ]fMAQCbC1t¸>c-erak@gn|blIo#IIT'}Cs!5ܿJ&\+u @̰,Pu{R\RMFWT RRKGASnKU{t֋ƝN5[muLΏH7@l9,:urt9 ;H5A<7VD\*CwV⏄b eQ]Ę}e}r* j DA@V4 aW"OLȿM nWN/WIe근?ЈfS2TZ: ԓϜ8ZʓD^H4Uhy9pbt7ͻZ]$Yt\G-; Ծ<~x5׉J&O?*%Ş7)|Y<4ó1`^::mgƝӃ'77mC Ja n<_ŕcD~BCDMͿ̾[ Ё4).M@]Ly+V5>FSqa~?yYX.^2M Sk|aӌ=oBɄsu1y}"Fw@vN(Ooe4m/GS03XBf=k\zwE(5-=AfAL0u+_+GJ~BCDhmM滢:msF, nwIfc!%x@搏%Mַ{.'w~|/,iW:cKv0+m6YE:Fsn";8S!9lEYL+mm#5Q8Kr'W GιĹR5U;(2)7OHOj5H/6_5Eyպ¦}q9^4}g=UCM6)Y yPZڌ; V(V7]|OIlXm&B23QQ$_EC p5Ym)Ub?뤇w/!qwӮRjV}c)Eɲ@-Cڜt}Jk[;SߚnR{Uz"XʆV0.|m vPP 酕+]$#5[Ӛ[弡X*s{z7P+O秙p}Y@V\''! >]Ѭ$醀(syGDF~M*Al& IP/ +y֞ 'ok&(r46=6aom,fL=k&݊{I\M$UG|/+~MUK]MiD3?e 8(&rV`iC#Ψ6W~(otJL|0sF)Ah냬m{)M6P6-aüej Z86}>^8yG.b sJRm{z]mX=E)8+?FmdȻFL'%-y*ްZ%GEA2}T1@Dca? tL2ak .hP #/t_3$.'S+m_̶5kiGCw.,3QLE%?f1 v|mF8}wS77 < C8F2ǰm .{cfPH:ѻAᆫ,?Y GP:Vx: 8r*QNȃ.Rw<"&#G1qdFu/&)Pyn!R%1% W:Mg/ ŎRq"||H2c<*1mF1mQ,CbY8;)}CBM!Mb!vkeya+ F ZEͣkz~=+-99mMH"{&NC lE!Vgz?g> p|Wo!pL<}WWͣ+vߦČ$<[-Dtƛʃ5&!uF2"UF[J)B3i?hgPH*tx_޺w@JcNM*D;+ ,aw_Q?;ŵϣ[p1=CJ"Ҟ)zb7S7KVVkD،GLzjj́)jUKW\L%B4FN K>zh @5R$nכ)ޫI9 +C8&ʿ}?HƏЕ U\LN>SNuefjR4cSYۅ :`(5L~q%&z`_(<)Ʌ 1]1-5sO}m[N}ܕwX3{ Z죸a!o$S `|UMLP _-t^٢,[d9PEh6@޺$KxRꛩ%C|Lv<:G8r6TF*}5uMvg[qDI(-A0tZ^%HtHvT\(4v6ڕ;kcԠ;eFWM{ϱ2ڋl{ب98Ekaz`^7Z{S);5> Z`z$[wO7-(t0mJ/޹+堔cE~lJ{@=,dUX{ ͢tLKj؎4H#r#yN=AUR't F:۰M`zP69Eۍe$38o`ElT@1.F|4 kuktfWUk @Ҭ˒@/8;X ̃csL^tLpm0 RD~֑l߾r1 AٸYA:SIqy {^D=CJcBXs0R>?., TX&z95PmW˗)_w Rۭ#=USDZn\IXx:C/߆\K! QJm{&O_]EVGJ hy=,fWbjn }C{]9Hp_뉿/>+UeMQ\~;w4ednRMzp&L& o~DlIO=}ּi{4I 8R:LnX9YڍWR FR98/ _LyEj/أ8ǖ^Yak8qNC/bsҺ_<|/ɨӯ aek路MQa~{D]xECWB{ ]ljV.a({wf8a3e pBi!*w}0-<>7W%dȬ5H\-˫ n Th9lBˆCT"k ID\XTRSYjg)S~Ξz/y (#NkヶM\2?/Q+ E мw g^͵悚><dW֦[}Heݙb'S"%l[>1,@ឱ6QJ݃?Dd 4@2GpIRoH[:k2`jjvc4Hk4 & Zo z%@nJp戽:yAuj5t!FI!5$LR YZK1&=*2}^/VeRʸQ57uY-נAB~esq?r7ۏڒ 6$X2Bلy qLږ k5ҪB.U#ZT!KauYӕ/.nD d@zS<@ٗV.UrAmUT)p=Y9^놶+"v=ȶy pz`* sUB/~? haЎ3߾˺AlyHɦtrz?hֲb"WTʋfA[av3ڴXpAO89qX-(0@WM;9I& [ڢ>;Va[T{;1a͙ 0pĊ#AV6۟pS1Bo@\"wlOEQ监C+.!C_B/3YZʫ,:f2bߗ@bTeoTr tg/ wy|0G uvrr&I14Vy'b]1Q}@zKׇ6lH=IqDR8TqJ6Ͽ]@NF',Gre M&stm x@}އbƒGF&ˀ5K\W'm+P[,Bc"1e}-DkY?@?*H; C\K)EJuF;H `&l)*Ф-P7-l*e^ ŚtcJѨ 0Q;]3wWb%$NIh||HgH<)gjҶr([DXkWU]S0ZB1R >)//HǪk0xvE9ĎYBc iI>yf!Gs,=99C4 !߅_-uTJ<+txet=Oz_z}6?m[UK $}`!9YC>,Tk,{ ?JL-GvYYXM>^~lY޴#hTQ7ˣX;ahYX7>c&+p)lytC Fz,wVω'_(4#1Mtw?i8ɏ8+$GPyE?kP} W$ejf6&zN Ķ/oQω"ٲy7v2(ob7-v]pdz}pзf$p+ isQ˯11 ᭶ỎCO?GTd7Gh۶#{ yMJRXtKZQ?vf-Ժ( LkђB*KX{pcjUoo1"*ܚ/7JZpjNhTCEf'53Q"ul1d8^!/7WkfB'8l9qg8A'oQKɏ-C'H]#/ c#Աή:}8;#<ɿRb"URyS5v9d"3]砼vtpq1bB#%APrRepM( ^Wo_eY.2$\a|GϏЯ|)OT=d:g}>  gu:{rVp/4}vgR !鋨3o$^Dg&'ycP!Θ(*fx沊ZsHhLlEvQ>QWMuX:+Έp``UqMXcj^UZZn Fۉˑ׻fVpo3ӨGZAFZb0Z ?9}meK8.P|`ev@S eLIݨKb'h' )cT'I@| cd g0#vQM> J5.Ux1qGbi3W̗vaovR柶v0dx5L]Ay"I݊L+zgh\**;:f4͸!1KRyB?<*}.Ɖɐ4s 1+@"~JM,MNq[͆5@G'S>KĘ;ܪBP'ʣc2FP_'摧ڣ Iy y\jnYeN0S6:jLk&ƻ~r|z&*1^^n<ʅ/ao9TMaNdlq2S횀&y%-oki2}x׏Ӷ kMV /A~#&SvwoNFH 3J}o5DaP0DY VU AEݞa΄Sz}T|*FDC>PP\lE7 MGzgP{j8u9I+zj,%sU\ѷr?{ VKIŮ}q_qhi>Rȇ`{J͝L@f'1 s [)&*FgA1!Y$s.8j;9UmM`[Q@IOÓ^ILܶ|)rxY;#Wb켖m߅,K`ĻNeyE {uA "HYoTP1BfKPMmwTvԋof`wCs´YNah:K~ѯԬRa$Ni쇤%_;2NZxɣV| |\JF*t1!éjNMsc.-N)ʈ8?Q^5IwnMpZs=ӆ: >_2g{<`Su^ָcis$>\'4}Na'_7v%-EeI9=ri,䛕U3v*k/#d+Y55}ϴX䳾xJ6 +GYT2WMаb֠u H~N^Wy1JP ڦڌRֳaUl y̿Hw~JwxFW`1Ș1J(;Z4N<44RapcPk֫3iKHYĹaI2Gz!LY4zq9)=Ox4qR!g 1>!`ߓ>I*$MX]EO`pO' ddUSJŴ>|r#Hڗ=x;IE0g| RK eTVJ"f;'-7]WdA݅%x_>ι_a4qIXM/Go2bHF8r' )?8N>3iwgLOm` DoR"@SM³.zmZ,pA쯷NV@ÜV':[6L-\eC2 Q? ɿ@,Śj*#f<~@+B=BJ6͏2q]XIAּaLQHf^Lڣ?^sMw_SA}evP1Qw. (-wEgnj|n ZH[m L ޙŃ62d~ cYgMIa,10`@ӯY+P,AѴ8VzX]#vats_(n.JFF#nڧHמ9vŵ%b @SmK.|P7۲,ֳ=gcs[@ zhCHV5~u̥o =l.S S2hNwH  눇4-5wʣGc(Cl'b0 kY}pMqPQـmW, lV'1'#N\F?):~_Yi{Ѻr,<"o-tYY}vໝD5] /0)o- O²O 7ߢ?=Km#4Xy h7)r#f@`֖t!Ry+Sz(VA(4H ;:G# >yи j҂Lr/:EdC*ܰII)JQ B=AoM5!Ǟ5Nw#]r'쭋5M -s/Z8!U!MK9^3Zfm6]z.RlodtťU1C~-3(T&ꟶF<7I8G֭sgW^.paDm}d vO%܎3CpLkXơ6)Hi6;_QkqNASߩ(7od*"k`Wg>\`\m_ * \pGL{vRԆmxOcN"  ۓ$J*^BR+@55eeFS:Z{>vRpoK%.WGYOcCLxTA}* t k c@ Tbs]^6o2.^5\#n*3Yc_'.5 zSUJ/!7 uv0fۗ淧$BO /\Ω?01A4>Dd*!ȔizڒPqz$l<5#^>aUuZxK_u̷~ox /o%|ȅ?z1Gf(EZ*x`uJ!4g?h%SRFl2}z(.HY+Ɓ5#RB"'/wRDlN>|Ȃ6vb$4ko6q3'Y9Sܶ} 10'7l3p4(W>3ap0Sʰ'qEyqd-iםa$-℣cB&{| '{  otp;0$Î5#v-2 p߳P]k; Mp 7J /_#80QH1Ǟ}x6*iS"eI@Ud۩T< /NN{ݷs;_FGBȞX|1P#/R_*z̴;L5%-DtXуadqw45;<:Xi+"9\K3{0N֊xa;_\cTjmWfM&3V *9>̯_ТzJxH=R+v4SJC,hhχ5,2)m|ٕ`@,a+>Xu`MAI;R#Akik*(?f]ÑWX[3dN$DrtlϷ*~hZ`Ӷ} /u9*ת5 [}~3uR`KP?}V' @ ०;Ys$V$BG{l/D|ɻl{ Uzovw<=Ifᦓ{8!;f:deZdR}/[.rT|u୸Q5QL[&D~zoM&0D7wԦixx;!w\?Npd5ʼnou!^U>򅑉?j@n?O\m4t0ZҩxMx5cF6 >K$|yLz] V&;/edk6e^}3;uF*C{{+zzYso=[X(A ; h{6 g)`N)ΰOvlIE";TYL˩ceu~g靆&}̋ C0!+_yc\".|<^uga G|Zնe ]#oLh~H ٺ[5_8 tOF< ޖXѦ  d`M-_r䗘| Yw@e4E2E7s\eESiz6QSe:s?G)ES&}f ^߹MhGp?OV: 9ޏ[ClEɁMԕ/}º{Pl 'Nm}B'XgAG6F띜>Ffw!AOR̵5 ؍m9H&65N2Ŕc'؅Za:b>ri Y?YX/`Bп|\:Dot\ kn̙Ӛ9+ iz{}6M@ -m kmk0'Թ>׃ ԍ1R]~5 «z<. 1ѝ,|Vf$ς}V)-n@S?+O9/N+7W :F:'Z$E|hPJb[NKH'VYgwk͗OOfĔ'4ƃJ6J(eӒnOXooiзrEU l9lq@P"XC񵹗eD]\$̻,Ⲑȳk ={44 :y.b6zy\}zp="!8@a U9'Ȳ$_|-D_SlsyA0PW-6mOhS><O.3(-⅔ܪe^ utK7#.-;^epQzq }?SŋEJ]6Ey!6v:TZD{}CEZ+>nBykm:h59!==Kœ|)$+[z:> (MEX>=ח zI!> t'lb^4j4|^e*lh H3A_+ _﫰Gdpv-kmDв_ z&3Fd썧%Z d-ߘ1G?Y\k2Z@_)-r{̔C]eC67ǵλkHW ^츅ۛpȧqG,IyicWVsYTU0 IX є4 4R;*4o.RDڔOJ2o2HHU 0 ^0c^O8L\0 ӷ覫__2V( դKj˄iAj#߶okmki|Cp,Pj"e<xvn%nZCR^RWrk9$(łXoz .&*LDjtK; SPi0bG?P}>U6裰vީ{WP;hKfZ[` Canc Ml'U&W#΋~F Re~DQ!cLrn`PB"+mwnz7ѹ>ήf80imh>,M 0>*7M5PJ (6 V ?ja*agrϚ)WG$>)Hoz/:F}6܆P?W\%t:]X-BG% < , 2Io=mo '3ws&FbiJgg,1g{3عU\F<&$Ly}!,Wh0P~R%GD@Fi[orĨ,;@iiqnV;2hTRӵx93+'Z?ITō`x~MGwPӰcX}: !׍cH$$R1OH ?JAewtSW#1sh63>\溧(ry8<-MP3m6 =hq#U6%XϜ_Ѻ ,i zd_ai{g` i2[B"kyԔ@4-q1jdrļBp ίqIAzPPVb 2\dL?H=\Kԍ Pkz7 |㎲̵>A?4੽guݢÚ϶h%IB:v{σ>v9 {~l&({EF=? Ք ꑄr߬ETBM($WPX|62듞ORNZ]g׌+X׹ǁI!53FW玉h1*`w>Mϟ %w_0}Pe] ٱrv{t^0Y:?MfymO&VQ3hiTQ#tpY N2#]};֒jďۓJ jfֱ]* d@AAP$f uZn@VAnc]Y_e'vyRsM>¼vTսbG 4>(ղÑf.4d/-VFa1|D4%]K15}`́)`G֩5B entZ<<NIӟ-jM2v- d5[D9H4獙qakX#U?,u,RVZE˵`io`$@ZmsaC­r)cx~T!RLo,rZ|\$":qb1Π-MNqVXc?9 g;UЁjY=)ۋA7gP(UԼwReTS$ gk(I|<SQX(/uS'a$E՟G{ ?E\ԂW!pz@ae~n|7_OO0lDȷ*xݕ -NZ(3>v)__ @XUt{5VxxްajJ5@tNZ)+l^88>w[i_Ŭb;Axt$<-WfopK(ŘHE9ne J,R*&'O)WL;>*ONy:~o~;{FI$nP+A>S(#(umI7ku C_.%^ J0@v%ʎk4=i4rz^Nm3' BЦta͒F5g7!Ք/he]Ձ$KUB;o$uùt7\fnСK]?|R!qR *sݶ S]/W8l]T64LT5ەnת*t6_}VORڢ"u-3c1eǵ_d~P8ZZyxťe{kz4G!Zמ`w-Dir|W L^ޕXSN5ZF/{ ֊p@fD(0g}[=8f&9=218c9M+k|!,f- "0ܺ)m3Hw8Ke9?z, |PWėu4;W{vZR9F6VQAVäD55}ł qUhĆ:ۗGQv_K!^*\ǫ@>Aޫ f~<ݕ1nA1CHlY\C^/4ucper8ߨBox/cMmX]Wš" -I  4W2mZ;6`d2!~xu> [bY뺞/$v3 첁4ks|5xk GV x+a"/Rd<tl &9b +PyzR#cl}ż9k|ie]s0 G MfLm}H/m=X=giEo!mAeO iY]x luѤFXaO8=M]{M{M$$Ӳ0n'B=k+pgBeō+(!U zX[EuhyS)ܹID~c_<Ӥ̰hJPLq>¿xhmrj #:Z w'}5P-O ⛷{~In!ӣ-[h?tȱFx/"nq\0mlr-1Pp=鸲*ocW|_ta,8ajnn3Y]ꝛhS&~zֿc\Kn+R_aZId.x-zK[+\lST b@^d 0b ;E|+ŹA߽hbCp-3+W֝_s[ ^@| NI/-=L3ܲ"g2JGR_-գP貢GhS(x񿔭v w$z28[<`LP=-3ͣf +$HX|˕A NXh, ~dAĜ>+t{}o=czM;6_ůa &Br2xu;P$IqɻWXwәvQp}x<Iz| ᧓"\΍Gc/ǟ㍤_~;07EM>cl)~z/N>^*p_iwTc^XCz& QA |쁢J/%*ܧ ANdMY(pAJ;(\Cx1jk7ڲ3OG,7QMaCh:ErW$NX-䔽žf7N۶}fh=AZ47%wj,ɌUc}y< ։Y ˽dxmq/?gxFᗠ= .> )`r*/HLt74$j (=5~FXX/ i^3V+5) Z_]w9%uWR[&Wdb9hwɽS հ{d}@@{Ɗ;cۃ% HZႻDlS>1; סmHXXZ/93gn8xZ^k&6# `_ږBRTr:q 2 ?v}UA菾6WE$$zY7јX@ִ*U403T@j8s"+ !M6kRW9No/Lcd˻GUAk+O}7 N!4|ǡ=Tv{ [oSl >Ԓm/) < 4WgSwqTQKG߅aF:1G{jߣbCR\QpƧBWò&d |#4exq6ΙAluXF"ߙp)/3!mcCb"/j(JFg.IV=Rsrr˨,=86XkU /-%Oh-綜_TҖ|P.A`;Hw7a0,m3N[@zlA8WٌQD|u۠C8]꿍4Wj*6[t<Ϻc`VQk<!Fʞ@J ̩Sf@ {0e1<-@[ HfQb1Įjիfz1 +.z#LE .̫M?&#~M@iPh4zGw=t@f/&^фP|N3;zY\-&DaFTtK?jycd%O:$' ssGPWdi!l+ Lw=X g}x.+XwVBql!W`ɫ=hV ki:[H[?wk _]4X 9Lu_wbbalQ!.sǪ[IQSQ )~ٴ<Y |A&J0T:]`,1ֻB6H+`^'b'W?m4ZFTi?{hng!FTeT$/$5Hċ<7rS|bs_[j?bedg=45-C*\4Y=GXDy@ѡ=k:Ơ V_c+Y+B6wzJ b'FFlvXxd'G]f;g$k@SA_p=#֨CeXo}5vTFYsA`2ROPЏ.ݽZWo[bjk; 񺷯:k܌lEeM/ase %}Do.G$pG>u?2F T`vJ7zXwT<3D}/|Ƒɣ,YaC^$wGs`"qLpj&?״4ZA֟rg(|3yS30@^I/"&如lڷ&+o괱QϣD/dHFèۛ8'ڐ ,oj 7)1]!sGgZdQ=0جO45M) ( G}yo;!i 6=T,eI1rʚPl%Jעɔf< %k@¼ʋBi=xjA] .km!.D|bI$?jwf @Yss=퀒O+ N֡楣'L[ N Zpk7eޥ(9`j|v ĭN׫ mAG?- 0''2 ^HQ\T_)f8xo?l H?i m>{x8?X&j)ֵk =8Vr#J9١lWEM` gߚK}JdI74f%Źv1!C]nӫ0Ei&妦"΃Fk[`߶SnQja([U o0BJ"q'!ȱ w@1γ['CθdT((:OYoX0{]bgcRb03(V-YRxA#C(_!y*19(!IcWbD^z"9z*\@ %v0<,yi} fc`8yjuk6(#I S|QCqEǶe 8e}+DF KOu 49NhƭJOxu`_tg咽K)c rjh\IwILSqlgT WK&2wbvin=@1>+%'4%-')Tn6="" )P`![&̹(0}gh,iT+?I\0cP.7fyNxp5kJ0Ər Hj9W.>}7ֻoRUڇ r \puݢ=Nu;7}Y,CBSNp1o Dgo}tiv0lwWN@ m*7^T[)تpeDVD@ 8'(iwݜZ/ "a잺a'ٍ5N4"j8Qbֲ*CbF*_{p+3ծr3|nՠB9y{%&ӵ{~GQ.q{Tg hYZt"$^u#*+GZiQ.4L:goO# Cnz3m ӫyմ6cb{J~Z*k!S@r/@t^ g!utk)8l :ڽJx9W/'.z͟.^P1NWn}mVKG6lr ,?F̄x8:uAm=V{̞]v# ?_˥,c\1sDnq%#B GgV߈q0,}OޫCՐ= US\PzF(4X]ċRᔆbuˊkvYZ#C-9\qttD}e([;0*dlQS p-S{8U+ @گAJሀCwPd8S5 /P%ʴ3A%">dS"23__i:DOiߓ'H! +VdIY!pmb{9WN|]q?%ìԭlB:׈sI=F1LA=YXN7q|DcasZ,{ uQUܞk_Dީ'1<42Z ,mKݒWxX!yuNetGyQ;!4;>HPhmZYUFxcHBC8f)inߛPVhb;/M8Yހ"&,[LB}cTrX K?ҶS.{ʡJIb+>K/XY0?ٜ楈 %O/aqr~xSQ1W׌7|..a?Q\Oi3w(Ntvs]/8 $!"yOIx3&McM˰-85orszI{yr#]yv{[Ńݶ@D௴@"(ʊ+Rb"e=DB*#q; Ԭgb!ǻYyJ5M]9frmllI|Oep]QyO:qZ=F"3[CfJ?DLe1[IbiW҄hJZ!A;[۳gQ5V'Ռ>Ż+JPKgIS%<.)eH7`O}iS8B`\列[)B-o@dq~_,\Xvmʔkݫo]2.3|6:/JҀt*|?JO]3nR d_|{;>CZQQ,sy\.﷾ƛҨg&]DmHXel㾍4xK kMHg `LC0QQ&O4~-nGr +])OtRՓT8xMPW.&Abs|aog$}k E'4⹶Hgy%&lIe>c>Ѝ2H?"2XXrSe+1]/rJ))(F}mNSOL 't\t'YiǦ^vYڏ~$[\FC*Q޲Έ3Uռ6`s5&d͂NzI07Hl+JUaLdU]5?RENWj6w6!axQLe`ŵ,@Jl^d<>̷%;F7.];Z\s6vc2Hz3*{ʉy>p!YCR‡%b9];K˛Ww^T9{ԩlKүCRީq>|b$%Lǯ}|ZotEF[DXn4Yl5n6TR)V3I_ Eʫ*76gk1)۰z%#gyTA ?Qg@,Δ%qʩֵW^ >f?>pc(G*%RT@36Aa4tqU8|S)>×,rGPrSJۍَ1.1waZu*Z}7-Nm}ѕMxSEg{ \Kcз 2ld_ 1ʬђcx,*zca+6̢Omiuj].f͎V8XW$arM 7wq%.~VpDrDE\@W4n-t'F>$$@U)>s)ǒz@XLb5}V{P)~bO;#Hc*Z* ~p&c ,'ki*o& B1(bHND埓R`a$d_^Sbb#[=Rt;@X$^R![g 䢦 _ATџS^LkEB 2h /17P taO߃HSp,w"#l͟TGɝ@8!2UOkt4WsyCRjXBB%AJd+Dfgx)U6ln\zu}6 GqwN݉ЍlL''p:߸,=+ooA!i2ņg0 Yvsx*oAl*{j14 tLdgi#⩦2RZmM_8^|@4E kjюMꇼGXI.?_CCبc0f}怸~閾Jg}n$o煖'CukEBaõ4{kg•#i&j!35B@ v 0ʮE+M-H(C =zNnE4em@]k k8CpGILe'Z_Chw9כ||q. |/b͒7T"-λ(OK:)cX0i5%c5ZE,nӃFWHO1ȕ˿gLqHAi\gk꾍F;mA`݉SobG)e+KzK4sUM"h]ŕ @' ZkABD@S?C *iAP4+|s*%S6?\֫RY2J)9xa ^0ޱjs5嘲!g蕿`ou%KV2Ẳ jPGgDxg|qz ijh˚Jv*Y*MF82{=sPߕVo}c`+E84&#ja1Jͺ==FՏ.,rQ{H}D_u>.*୍JX*nE߄S$ֳK?rvASְL|Fn̍Iρ V"w*==٬1r({?OYhgG-+93wgEH+%VdG,r EKF^(UFV1Ws@'ڼ"q8u9dYF_ ႸzM}L瘚X_I8jp],υ#79^x8%ҵp}^o"v&Hh,h̽dcĻʎn'd S 7"`3r.؎aoM@9/;!O -[;:e-U ;-+/Čs1y)y0@tQV,{ "lBjQHV,_Eޏ - m㞑jB&PlyE41dj~xaHU5"Iu/&{dR\.4#kոod%H .ZsnV)Q?Mv9Vc?1&[=WR8=Q9BQx;OX5k ÖyW[.u~![*"c28w4S"tT3 "/Y+џmgjn>m􇫮̌u!kCm+ӏy!gfd yEv0ATnwCVEFސGyFza~ufnk>Eq Γn؏T/pcԀ0W#@zVQ=&VT"lpcЮɟbToCu_E]2VߺlAHԝo9@D臽 JĸtU6% Qt{?@x`yc]X@H_G0y_<4,kFm6_\ #~Zgw-5-m@>y-TڗEq<;+!ԙ!vWYEqqV 4T3F0~]42 =xh_M50L<fjr%MSpL1B]_aE((S?qF"eaD W갅Tܭ VJacjՠGxBIuɝ>;NOФum}ִz^| 5Ȋw0: ?q_ՋK뒎%]xRf8}ɳYSGk +o^YpK#7kJatTc|M$a?Av/OƼ+"D~h7i𶦬I0Whh쑋?P8SNb0>>{(-/5"& Py W?fS>K7R\2gi߉Kwf" >4~B>3"{MC@*[:4<j@z^a{')e1eF zeq='UÉ!8+n hz)&[cΜETB-o lAʬʼ 8b+>ZI)aO:߬ΩFevԹ{~/lFf'ԏm-v>:ȠeLdu4cO,e4NbP1rx*u /1ܭ6Ң^lŦ{L3/6{IĊ6C_#uE FQtL_ GڗJ^qws//C%sb֘'<:u[=s>ca#>xt ^P3@ _W!li'?8kH-KMAb UίMfPE2R8PJY/#; p4卛h\N`̞䩯sW ni}p's,$/CTGW hV8TGef'ė*pX;/yTQa|Lt៉KS(@u(ψ-8gS`[\Dp}]n!!1oB&}R&`Ez}CP*cM<җvYmуj@ֺ#ŖX`2@9K,a|==.nוi> bZ\@hgtO _G: ی8 8|y?5A(0͕ M {`ؽlϐU[հ1-PShnE{j! D|u&7@6~RchfBR#[G-zU͋ѕDinV*?$wy"^ MOh h?f]D3/UO۲mߑY$zw&Evȳ= BN10DO:mcЍ)^L8kM$xՏE ɉџP|p L6yq+BBk pu~7G Y3Ya;~WJlSF@A5VP?GqTVٜ i̋[c)Z* 8KI=2#p3`0 4?o\tccT=uJC"H(R >#3׉2)| [e5DPZkC01j$*Aی z\M[lP`8_=` @ dW[%hWIfQsQ k#pȰ`3A{G !ySλM'qreix~Mk93/Q@{y<`Vg_M2Cg~ozPޗ4;BYS4f ɋ '/ )ŜZNl(qlDԀ[+a {MI{ub;'5^X#eAc**PLѧ:V;8|Ev&l;X-x nl ?XdKw({-t4H`$b5=~BES@A$}':;=$R$8Ҟ7&+HM ߥ~t.Κk {=:`cN$NFow4ũW'51@@w}?#jC Elӷ|wz/wjggK"J"H+VwJn^@BH|`>a$weۇ,v<54\0!gHNYa^"b՛ OlY  ֬TmQrtH#6l7>q&+`pV_,fOΜfYGOfoYfߺzJ~HWyzZ$AI>ZV+ ,yVG-< ŧRrjJ4KOlQDANCX 9ڑ}cwx˞, t @P^q<'V(IXfBTe7/v+Fw>r]ca$tW6H|}pQ:MHH jC.qc2yMhXю /ƦEUtUW:Լ]ueu;IF(ܮ"ZjY}$OvqKyX_)vdYWAU@4UjQ3V\2֬xcy }?>A|2 B$6AR뵿“읃:L̀ Oh W_bg}.x׆sGEp=m>L7 38Σ)Klm W(&[L"|$C5w\e%RbVCYj Jb`B|NV{(gU jqŊ{ scK]._SC`[tW9l `@5a6ȧ7^6!J~(~a(lB|Gߧzdq P E [۴ԆRի B#IORXЬi)Ѥ$B =&5du>ԝ1 j3%MUR(uv4(ݳBr&Jf[h3\AsFV{Ы0h[iW'gXvC<>FpT󜪗@sv [w$s ybi[r7+ `* bXMi`D)"qRGhHا %Y2C7r_^_Aʳtcp/DޱrDwzcfwTr>CZҮ,0ʷ/-fE+W^r(b~ceʛśpo-s;4iʐw׀a Lǵ+/r"n7sR;)cv9G'5߿04ԛX?yy.p^QkݽV7HF<= ɑE^athꏸDGz:)`8&JV6r˵$N$} AZN>Ozo!ZbM "Գa24P{l<] #PtwgH?uj$Ŕ=2{6ѼJ&ۻ|EF_ ڜi?8gMC6Aߘ 9a`,ʢ^L.i;.{ÕK5~yeYnD|)r^aa+X"m[F),զ!RkzqeK ū,Zgٴn6QXR=`ϲ V(hۛ!ޥ3Y{b+umr?K"*,Bܷʁ28_1c|W=TX.M' ~]'3GINjDƧڵƒ0nBOZ$t} ~sWGRJv'4/W&dcv!&~nAH2zWS3ol95~3ư2cwG;E t#Qhpֱ9ϭNlԀx1kb,E2"6 0c-Vkʩ <)ڹ 8&j,AU(urY]IT>;GH/` M1ŕZ-(0GD'uw4j/2bKtǒw)'`MA)3&Nw'&mqux#iVc;j[EP@~nҠ N 1*\?΢?oen{yB1 h9lWu'P5+F@v` |Qă/?0qR4Ne@w@MBoUK_e\>~h$ybԁ6ŜkinuG2|Hn\Aroei\btBءʑauT$]_TZ;M&WHX!A6뺇c䴽6Xдg!44ΐ zWFv(k97kW;;7C$/=iGE`<#R23 .E!?ֱN'](k >NԂA 9.gNn[O_f[myFZ"k`Z b)|l?~N(Ͽe*r*lb z)>ga`̀WtzFV^ fIs Ims) tQoŖ'nZhcZ%z\#_?rRn,$`U3:>Ʀj"Z - _pW6:$MT&N)ˬm$#O K"6EǛ#狞5m`p` /X'@$pݎ炗`0.Y=1} >Z19Qry-9y2x޳Jwtd%jyZGZ8:Vg:w^͘L↋u.LRQlIMJRat&SMt}7Cb!Q8N922g<00Pг9N j&ՒIV˹$ój"NLZۿ_#2 W?gd)Gf;0!4qCk ZSL[<'EJl㈵Z**}+% L`Fͪc3G SgϨ][ J]0׻>^޲FA\9݅?rEiᎏtKP^K*ixCI Pi.f~`gO"9B߾4;](/ayW~3;(Ilc;bc;j…L.h](3m&+e_X>|' =;tdž(H4$3 h,-XtR _E$/ !N߉8{ !of E{4@HgLHV*sʡ EA@gzff&*^E\Ht:jU#*VJV1q.N@ d3hk3B`JX?JCja@bjdxbmNA-Kp#lʽß/J'l3G貑=a0Z_#jxߵܴZKxA^E1ɖl#`6 s#sqo1ŖtA`l/8bؔ]:b t-Q.w} Q7^$jc(ׁˣD_#2  :TSEğݕP_c ;31}gC]jj1񄔦oKM` C GP2FoCOգ_nh#GTԛ"BQӚ,W`_,xAp>ݟSSZFglbQsu:n<+<¢M W`KؗEqÂSI衲2R$"2ƦGxbuR(:+*+dw׍6]/m=vvSi4i-;a?E#rG?>:Hr̘(|FR%Uf%0h0"SL+d j`QlkZe.Wi2-h=C 5L(n6.v`|B#sB2cv<#bY(E-( /2KmV#\eBUk#pуy[ *0O 8[[+IKZ{@YAkp ۩ ?‡ƃ/͐*R\̓)$D307Fm` cz>^԰(P=GّQ:㯙jw:׃ϼI=y'S;Q"֛'^ J,WK~Ɩp ),)}\`7MSۍjap.iPdXxpuó6'+XИS`M?ώ~G"C+=m$s}5Lႅ'jņ["(J A*Yc[lZ6{sk,bRzhBc˃v`C\.?E$46+}9=MVuN 4̆ 뷭LVA"`2[bP0զw }E0+*>c6s}_\AS hKv%kea[O?m 8'M_Y[omƟďKD7oT痭}:z+n_~EJEB^dTgm,t6dJdRÂ(dl5尀:+U)E@dI:?H.`FcxϪBp^y‰5eܶz>LIjgm^ k9V$غúB{ht`Cߤԉ"R3CkZ }COF, 5Q۵@f.7NWY&aV={m} _^_h"+j ?#$߽"pP:ƋJPm8my3EC]ۇE9{\ނ% ?{"~ A߁uu݊__v۩T)1}K dY]%p֕d3#>'ɒ2+w$BW Vb75,%# f8U[q* f ECdenuff<.áuEN7nj^"/e[FC*l3G貑dsOmΓN h M.f8lD}+˧nAr,/s#t S*p#3,Krք1V_ӓ0L$o6P]@?6G԰yJS' \z y]M¯WaAc%nWa4\IDi _/ εhC)-`K$Ӳ%S-8 {K{A89I{7Lh45e2@KR]h5a]&X@ FAh;;|!lVAłMYvbd9^w3&l_acvlv-(;d0ɋgT8IrTXZix,<2 n~&xGetRm|~<,{27Y5g"RL(ZI3c߲J۹8 ܉7Q>[Xu 7A՗;tq5 hssҲn?] f{fOFZN*/ Cjn;|\4O Mf"5JzTlu0H&Џ)$x_gpa] W~W4'㩶 WfpB\Ow1cV>{vPqr{=јse } i^z)qzYm.+'gE ѽf$dl#``veXacAli9N߶ܠ!qs9x D(WGG<^ynV5hCԸO^F^9P*8/gჹAIUy+tN1}'TA:"yVvG8wUpr >o{jzƲn{\ɂ/GP£Bo$ߥn:_.w뚛NP;N2ĵC ~>vs3̓ۿL|dh0e{%Fi*3hEػw-Y;ݷ,W:z8cDi0my/g*sd$Hs,oT* ɪ0G wuPaKu;_OU!AjJ(,8@ZnH]EnMMl>!(sŦٯah|"PnxN'E<]=^u|DbO={ə&eh04 dG|q2 uJ_` La|1A (56x݆<fݺ̥wlt(US7u٪Nϭy#HLI (0MF|؜?ΓJ4(%"457VlL7h'92y |p "JS@HۍQʙ ǁk+iOʉ cUXep\>j132 >4 [8*⠝qA'VJU|4(l:tsjEMK2S)k9? 9~ۡYֲ e4źL^}:vVDJ.' @߬"}| BB9$B4doP6!%bH4̟Z-CkxoY0F{<Վ<؁jx"(x>&ːc4_2%trGn=pJi=6F]f5'-û(6j)\[4<~ɁŔ\!]@m:]+6۔msi=Ȫ27Hw]E !%%vԓMF 9}?C%V#}bc:M׃j6E%.->,28Oq9?}YWQ羼’qO#wM˵2T;JcꫯG4j쩥Ptݍc]^8jS;N?VmVpʏZaqkKHr#iˍkmx v} -OȌ(sGFV;Cy; h-(j(j*D8Tz%3e8XM-ŔZ^yb1,7M`wܨ,7\?bP n]37 XM Ws*jL"-OSx]Sd'j}u_ ^ /a!K]O CԽQc.XH8p\ ȕ&3@rBAl$H[ĥɪf>~gwzGbO ?GВ7֜!MqJ:6- Kk~Z%$EQrE]X#@43R)˶SI=v ܟ%u_J-ܺԎExcV#Z28Gf;_Һ. 5HP-JLvt.ٞGpWtAm m^/P~mD{x[) HFǂ[pD^;!IeCt!q|RI(ѩZ_}cZNi2ow[v-~vq5O?T>f$|"7S\V4Km/bэQ=KjT:H*.T.GϙV\9x}BsDUw W$FU7hI('mvhF_HtKMiwo{ <;j&9OJG \-iхʧ|sgSnRP콤!=UjwVkiU^珧mn-4nc=ȍUKK p1ۛ4V\tVUu)Aj\T 1LV .V_&ia?rĭm ؎ G'r~~]KI{\0P}qN>M뛴7}FCH?oMTkIFrFu)dv}Hn`EqtHRhQVR?O8*&W3nM'TյRgqFyli-жm7}Eib&M~{NbCeIM{ٻ +cP6K%R:杽`1TR~ZVH`^Z ehUF  V[D;` (m@Eb!ӣ2IF0/,EmVpʏZaqkKHr#iˍkmx3'?G!C=NT >FT6N:Sgfb Bz{ayQY%Cr-9d]e2?쀹Iqһ;yV Ohu+ &֏ZMАۋY͏%{~*,Ug*̻Xqeu̱}s#SY/LZ2_9yf"{a|Ac#zD-GTиθ;p۱41sn%2Fg<Fq-xW)}Oe[^Z  zB'm+z)I ZMHMȶ6=4.W;Ƀ=zL@:H=D-ͪAjTO\DKky z賾4zy`]/^{ 9+[MDj#!k&Ö4RϿaG !e1e1";&ZcadwcwsQ4; 0ţMV_V#yRG{;Lm$ A03[y8yn1|] |6PKP4!آQ|^n }xtnGOy$r>xTדJ= ]_ ˭?>}n)i59VLb2di2H&W<GfxX>r 1gp^TIy[L]0/q Vt0+sKnK{^5yI-qI̥u煇3)AGodPZ3K ZQAxh, S ҬGp\jЇTjS'?H׍6"/bqQ=>7 $W?łʁg,権X!=@m#uBFwh1jgՍ$nǼEٖy4/WF\vת?E0Xm-LY 5/aĊP@"ʻx%rE){od\""pم#B5?~u! GꒃIg?dBSRg|Hjw-/bL|*ϪпU+NUٮ3ƛ5õvEm(S8E)s᫺5 S&OVé,\DD?7 `:*1r~Égu^sw A oHb`kl=mUz6Y72 O 'n2JMr; WnvSG_uh~b]2 C2XO b=s?I.yY Unld}vyoF.sżn8 :lPA*{*7qJ s*Gm:%/N$ob A3xqˈvK'# 좂0.^GvӉe.j ggq/o j E-q~:ͅqN?ŌzX&LPѯ8K[3(ЙuyZN/}Q*-*& #= #kš0ޝѳՇnI /_UN˳~"d*]ΐDlaZtDA.n}ZϢVK 'U=s69e/W/-";\39b-6ŚP%IzBnpHIX*0Fa"S} SW bLȠ1.Yاdm' }g/KZ\zm`+#sgLX sQifʊ9_[`10; Zi5I*{iȹ./V =(zՍaAƽLܪ~FQ4(2Ieo4K1fW5Kacs܇{1!sh̦3?KPB N(Gp:2E)bRBNik|gH"6p ^Ȱ{eàSNH^e\ QdT=JO|g5 }Խ:[vb>`sbekN `} m İ!@;^]C2Kܸb]K]nVz[Cv'*4KX?释~X J!=,#l~6Q5DB\by{U&sb )hh q1oK~.haTD{n+Բ/K"IS^]Y8]Qoխ#؞xSNa -lt y;k*y Й58.v9^Ϸ嚦} 1S|3WzyKS+U->4Z7'KT廡IQ%*?ME el$Jsj:MͼG%˔p[;4ȨVL]h6=ix]d^%չ2&aT{*̪hewG@i ƝDaJݔ܍_,$!npmI۔ d`P&ֿsۧKD/ h_ ?G; —9, 0Gj֥@= |rRg." ,j(51Zqkk'ܔ^Y`Kщ+Я-xGme \a $:kgF,7dy3KUڒ>dxzԟ(֘8 qAT>1 m/NrE R+vƏ&;[Q.cRY@TO cbnR0mCY2WR+\Hx2oNQ}q iaQ˖٤(Lh6b5cpܒ L0p\_9Zjax;j6i˭kB~1)_ ֊ F` uJ,ߚjrl,e gc lww Io>J{ 2X'fwQ #!)CdSpzv!)ڌ;TzKVƧ9.=#|WuoYSp =ʞK6y E%eFy@6A+I$?IW/ʶSF:#Ko1E2gldEiBTd4ݲ00D-0z@?`2s?rTFc1HvVn7}_aK:r{,M"n#(҅ X+Â=VБɵ2[m5Jc=(z#<$!~ xuy/wCO.RO^;.Qc(_ xyI#q$gtd\M^"QUK~EI\ɶ#cҧ- շcci1Ҩ:*+Ol/\ ,i|`Ak:)]b9GTj4D, Qũy?/ ˰섲[8?B;?(]W; {)̏kMK'Bfz+>|2F(ʗd뿠[VO;Jvzpod) 2yj^4H{qdJ?^J p^B \mYjKvglrJ;=y:Dɢ]gA 1WĢ.D+Ny4J @us&Zz:]kV{$u=n0I[ /TTiĺCu9y+#\FKnՈhnz#ٝ sk،CfTʫ.xFE0<'蹱#6 ZAa ixsy7G?M/kIN]敨Hs2L qG=@?h1m _u 0^l.&y=~QkYA2%6x|)6cg䥘~o i-%WZGUc8KTx$U1j84#6^L+ 5UNM}t[E^cKePW4CO',b̼t|;yI,~֬+J}J44"K57^屌O\}@/iͰF[$D .n&ջ3>5p'7HܲBpy7~:9dH㩢 ܢ$nx"„HkE+Rf'82M6UF|-`mmJbzXup@szQ|ēm"Ƣt:KpӴW~,__Ԭ-ؼBغ~;?)?{eZP]QU'p"_Ow, 0/%dI%GDv-Z }3ʺN!D7}M%]Ƨ8dXz,'MǙݹ|Bk~RgboaQaegE1`ʎvΞN Rn=(yIWYҔ `Ϩl1SVc!2'aCEUD#LDۃ@ l =6?.B1r1h6Dmߟ̢3BEKMA؇&.AW>pn"D6L| f4pmpY^7Zs7+V|aeC"L{ +1f9B*<0 wAu'Hs62\^'82NJQH)NLXuFL37i{z;%S+uQ0;JۄJ\QyUdLo*x"Ԟ1?F) yuBl|uZ^fĚa06Qߚ0t ;K4^jp3< Dn~Mm2R%ogt `IQqHbd ; 2b,Ą|%{| x\HcHk抸,ήcLRvenBe`D(6xHj(cɤOc%mS \pRֺfI0x?adl$_VA9ԇ` 9xUލY3i0;ϗ Sg]KI SBcM(=3'MS"Npi'gld1rv91Ǡ7dhpVesgpBW pZ5^{z px.ƺO|>Q2r}(r s Z(,SǙiYigq8k} rL߷MHqaԞ"%Y+rR^SD8_Ul@72pJa(Wjw/_%l3G] KP[ߓ>eMH[0H7RnR2B ltѦwű⩽Wy<Du>Ru؁ Bu4|$0+MˋHJR[ְʀ}asjQؑ\ߑm+F*iY޻掋kVvJLߟ}NF _{p (jmdM'8YsF` -9Gn)/N"h 9{덲RY,4)Ⳮ}FEV'#m=_0M.pZYFWճBy!1CUIqM;ꦨ|FCƚx8` /NH&m e gAOnS0 hFkq!p{ /mPPn8TdDvK};'[P vWGu(F:˷dRdɃ9(JبO\<-9*ʈiX2 -{qGJ)"0Q}UmS<'}HR|{,OP3'hגAIR9"HVz?kF XT\-X}4ǹKDtPI AXny=ؽL{H@J+3Ŏۢ~녦q!܇2^rdA0AB9-vyQQkޠ+[n!2 =1yҸ(V7 , %ӌe, I%92C{2)b#pdِ; &%h}J0ȧ-3(*̭y"jTӷ aAmٝՋh9w5t_M6nugraڼsj>b%6u걘z5Z;S)P|̉AWV]{ޯ҄|Isi zaݠ>Ktэik9K~gG8XlGT׃<-u%wKWƪ L2L4{×Ar~]] @ckBU?0rAI7cBJ7;ddx2i+ ZU@g9ѷAg:Ϫ'd.0uSeךZbw?9ݮg Eڢ.ldTl7n|zb=vkB<7\xa#W3ND  _]^w .Q,N d1[~Z ʭ;>6ѕ8K)Dh)Gs,~.?TQwV2 (^rTb3ޝPH#PZ&7iKB٠"z H tiZeI݆Mia .6BRQqe"˭)kDOIj?,TNٞ8?fHۼ&º^]tM ƎO?>괏I/բ!n4_,jgtK{ZƎA·\L D1bJbcK  /^eUWw'my Uݤfv;NΉiYdPLbtq5S+ ļ m@⋠BMb(> ;.h\g2\h1b}X! %qMEG~Z 'ܓ@tz`tPMSZ|9#j0#K.`jAFN~tQaz'\=`{+|8pq{VR;<Â0pg,}  ᎛<JŇÂ9gJO}6Vj Qo֕6D8 X 0=3p$;;|6uU\;ėH"AԮAAi"^0oِ+dzw9sYIɄ"`NCIyziY l]t IFv{cRd_Y׋A*TTEQ,y.7 YDɑ;N N(֛6TA>h)L&?~4X#t4d6'.v{icA)^SoWTKch^|*H3:W!&7w~f;_W O}B z u(~@zPg@ls\49%Y(Đh_tFbi4r|:}CUom~Gt <͚*;:ÚxjSzvdQVqb-i_ v3E»wDgt8<;u_bLVUBA,iKLl(nN(cQ0^kiGsUn9h{ ߖlyumhRR.}.n` & P=e4HfT;CNy%q]# G3S վTq(xc9?% '{KiXI!L)[Ź|B K}jwsd ڟKNؐ<j3I(ZKn`ۜ8B썏)_ E>>7pO|qwFNdQ|R)_~ك%mlơ}T!0Okw )x珚F0_j}'dLw@ #6-e"I|j<*hq 3o^.W!^HZY*+M+Ox.Ec] teFɔ$=ڮ/ԭCԮ≠/ W鵤KSQg>"킻LZ_ ҉b#X"D+dB#Ohj]iifQ^U#}IN%7C ql erE;˽fwö.y92&d'R{Lr$Hg/ƺPLy%9`w@UG+, rN 1<]Dlǖ!frZ.lurQ }[H]>]138XHp*G/ꑦCG%lRZ)0%kۙ0櫂0#|jA:4^őИ5q w*;3s$|@&HӲ+!҆A (ZdV{ -]*aa!t'gyPt-V6O)daOO|Fzk8k:Uݝ? e"xr81̾ݖуuaY;UԳuSeu*"n5%0"w)d{3:o53z#"C11"_Rτ U2ϊ:jlhXL{?Y@/dWWl|,zĬzړ)y,0$y[r䟬;c9;&P%P84J;bCmЯut?ճW6)FO]5 딀>0)i< 2zJ} gc4m&u35~6 9 2-J9nP@FKWO_ ;RNMZdu9ژ28NaaK@h'9L=I9jp£ށٻރ}"ġ@|YK y^29*mL*\ 2-a#E\$3/|el& \sZ^NiƳεWqU7|pC7{ $z kh ^Q[2ſt[aچ`Ku@r@DT7]/ܱprԍ׫,ȉ i¢ gzd E_ajgP*^vf@ z:~\2'n){;bˮ/'1y&4yo8+'H1N"J,ʯƜfMd%CkO䟻G>2h{$34)7VmơgX岼Iap M¡ 7ȿQHḓflEm{"x*:8URa\A;8G#$ 0̓!ګoȿ1 V;f/E6ݡ(j*(%BL˂Ɂ0MqT>s#Ti]AvQǝA aOlT4E5t/h߻\}G }:\$>m]z C*[\MX3>y-Wk_19턥^=+^ U. G]\Ä1 ,ƵgD |W7)lTf}mx_6-Yrk|ۯ}9.Cm⨔>blg+9ә'pk7;Ip7[SQ{Or+QH ɭieD6E a{ ܤQo}`҇ j߅]1lɕă{ml,GM^`@}yvb*x^fu*,SWBӄV^ .٤.M 03\?6Z>+@ߕ֞D $w pm tR,rt 8TzXI~&MMa}$ry\(i,,.p6~G!$01 `rmN5XѝǴ >N~1V=))*wIIw69nЕI*UEߥ? Ё-:xmtДih9~rҪ0U$a]M4 !:<0)g L~&V4:~6^ P*?ǔ7_cj=C-!^ ;T{^F[> yC-$p#19HK!n\KCUH(L?7B\vdH[r2BȮa˩̙tVZ?;JS7<Dk}uHMר`8\ɗ@}Q2o{Xxƌ5Z40eA$K?Ү2qE'I]jx*M!հ~3fy[IX49I@kqRdЂyV6p;7.DN +nCܯ)>+.=W[VPB[S]"m%(R2gCHBZYS(ȡ r:-չ>]>aQWa d#j]y VGk+K\۩V/^2884_ZOm a4zh&m xu5+LcW,J#ǪqWpn& Hq؂Oc!Ga9x%gRvh$9ANӉ_ː'ԛ%1Ɨ%Ʋ4}p폁!&hoN>doxK٪S$<5DLc"> j?!W6aan)Y۵i ݕ"mqtXp"8'fC0کhyvl4o$@yA~j3ZS,l2MwĿ r '\NSqio9&r,Vcs9^G;0q}e Ul#:)2oVb;֧̌'9 ܡ |nweA_f۽f\E(瑓) }$'L=Z+&[V|> 36@72l)=puU4rv`.td!+6=w#6䰊qXbaRgFRy لBO.ߩؗ}ӹT|4/l79Yܫ-sbg/=;iI0 ;IVҊϮ]~mU3Y<26l#Ni GLѭV ݽw.xOſ4D9w9=n p jS=x I O<戒³R3Ӡ:r4T-~<7Wv 0^$텒oi8zYӇ~>$ۙX<;,/Nk ^ [Dg\BF2ϭ}?T}a *oRجg]uJvhe*#z^]rY5Ucul(^"|~sT^H3߆WN{е_fӉIT̉|:͠ƈϫ&bridFG,:EsE}w'W !hYHvA+;_f! lJdb`FQޒ? >,:at\aBʏl?jbz`8Or q_I˰r4 㴎K])N n(yktdpQZ-Π~cN5S{u>CAyEAz*IbGE5e>Zэk>6}ϗ ҈ެH/kkLHNGȹ䐟eL{SDw1JUNp RT}1mJCp* 4UFP(oӲ`f_iTJGN%Lm"hgܿh1 96,!I+ P8EkcuM|®ӹ>x uҋ9'Z`1!#O]:ґ2e&* Dƒz3#k80^wAc2YXPVi ~*5l>s5..V N2gål =]$;hD֋%Ψ[,1[ qNص2`EVRى0Ow G_(iL=1 X!} Q|o Wdn(D}ͲnHIV#wR{K;sòpaʼoot̀4xۙ;dfLqL@ȟKt!"`L 2dYx8ĥ-0ቹ#h=ۉ{Ϋ#Azg6Gr{[Nx?Οfd(K$i@ lzf0Iħ.y.p&?5ŜتomhkHғR^/TDcW'*3+~ xgqs/=7?Umcrvl8oJ^inZw^b.r\䦍r3DKtp;=Ə5_q ۞0=bOUV"y9\qM^*2( p&ր3 yjQtM̗@T2G8e\6kXA3 ^ YAm܋cpP"@{-GRn麺Ta-O}gFJ0>7%K623,ʇ b7h+-WfggV|vݾ>%FXrHIKW[cavgxF<68I jl^HofڃhWؚ W 6VѬzMY;L(QC<'Q*u>N![HLEcxlu.BR̨SBx!֩#΁`FJq*nRِѭiZ PFErܤ;le٭l8SU Yҹ3o/]hBe6\LܚWMj6J `pC*IRb cb$v$dф{Rw/ɛF}Zu%Os_aSSXGzM=GφzG֎~L,WE2V tm}|"X$~aJ 3a=&[ l3@ʒF&O3ubԟFg?VY -|t/tx},+XĆx4hC#L]~.VRmp@'|4޿0`vX2\Xp^bfc} ftDԸ k90NBDm]G?:!\qUN< |~VQTo3c)NU7NPmo-5l cMC̭*fKIdH韨ݸyGwŜS+kۯPt Au׍`-W%I=%q\a!l-R)4QgJ rz$w?|(H$< ui>.B@ZLMvap_.J (M*jѦE5h;駥#02]K&Y@+D_#r:̢r;V䶦wn/, $b,‹MqDZ5$+$xû1,6!hdT# gCq9!'Ec5B3]eNr"<3xw@^~q'jRnJ ~I$"lڗѾhc6݌q EZNb~b |(%C^B`oX܏>,_:*n8I4`8bf!gN,?|lZ+A$1L8@koL8 IO.ή&B)Qs-LFtY Li&kFݷeNf t (O'5FZ -FBv]uYR@sVb<|_g9{hѫj5 9.VviNlݷҀ6+wp9#M- :jH]nn6,2gdBG̮=;Vݼvmˏ*zEv"{-7N/'L-pşJxFt/*BC'ף룂uW&%Z HYYw jwl B J[^0u>$pӰ`ET>GzǦڳ %jWPjg.rS!#8&;P󩜰7xYT=+"ѝ /UñX|Y98BlI%Wgu2V4St[$c293MrT[D.s1"55п~_Gyлgī|Ha:$+5ASS-|kw eB46`mBq-\~&be48Xx@l^+hh"[jr.4)Tc|71(#s#J&;BI;s.*0AQ9(?L ՙϲX֒~ړJ3Ta[xC! NyhvS4fEۃfؐ0h4> U o֛m%Tͬ+g[FB.81Ͼ]۾ j0օ*{R`#~*tikLdb>"62RuR@ZY2zYu)ni5V6{_1} /y2gD?IbA8R^W p{8Zh VPsC/ b\)ܙ#ǥkC=I3^T1g}f,y+ 'W?኏ ^`B,'x%6ª{T4n4a`/wja^QMaj~2Vכ{U@G6).&E]kYQֳ kݱ/@.VBi(7 Ź+KKby*/oF*VXY&Ot%ShvTWaEj_E0k)9-R>8=BC3 -gUϿ^=PtN2;MfpFn&j*;ASM.Gܫ|;e3Nxu. Yt<.)5B3M~V-8m\EY/5o"OwotDH6Lw/*cHɮLĿ-Ê҅IsdaFi:UlNU2CkulET}U{)*ԝ2PFíۑ$=ezhG@ 'c+;!RX8_r^Β穄G#A@^jd8>iE[r_kj'maIE~|_Bqꀢ!bZ/hfJ#/Rl!Q⠄KҘo |T]ܰ@Ih3  }&ac?bǪB%U4^ 868\>Mf2 Fb#; gd %&s\`[y{Ësc>D\:IBj Ud&21dOSeOXAW~C֩%>q@JSs]r&݅TQ_oZL@;Yb.OˎR>[qK!( YUZr [q7m~{U^.;y|jbP~QLL9bhx&cWQx^/e4~QiE\R/K{@o-qg%̤  +6a-}9Uc-W MF%O L%֧ =N5M-vkv'%qaS(k3 1}YZ1aM0N `u%'2 >Mr4XLZ)\lً h,x-3c #A"R9qr{soTa@t2o}8(X0!!"ٍoʎ.z~M:ĒB3fw&mޑﭙX gQ0vtoHX#^W"zb0.zޕG%ֽR9>8yNPFSM % VBە 2 Φ4)3d|dP PZb+f!ړTQdQ:O6Pyx@M=,<(glL2=ްa w>5q5B1)KuC=\LM-O?LڃD3cmoB9{ۖv)C%̤>Ch?^):TZ)w"fD7J;ݔ 5qZ(~I1!4`e| ^t&'@UCrt6tduQ>]o!OuT.Iqf(HaD4ND%^Y|rq`Z!8pOղK>?\eB,@PR괲^ck|(wGDiܝϘI/Z*tv3NF~`(V5ks886Wxq`zf3S6T[fgz.Xy1W?sGڠ%O)H[Cc;{%3N ,;/AtZPhk3^8McU l,# n,asٴL{*^󌱎3n5)B@`L+R4<',y\%ߞF|{H-قs-7@S!͜=ų KP4ݢ͌ov2'P|Vd)lYfSXN'tEBaꛯ<֖qOK7< 43$A8^u~1uE`/6(zW1@s$6/C yZܓ*u2+96uc/Sgi {R,H}q)kPr%CiRx+Q[Zl^M걐Ҝ>M߯޼_Hǘ""_p5 ) ?p*ݖYciYO tX5 n?laǥE{k $ـJ3ϯݯ:R3 Ǥq.@!z{YC2H8$Aq"y3z5k)g||/L5bwWQHwqpX&ۂ*]ߠ^ >i4TX>#?}>2}1ԖLK捄DLT2º^h_6ݼ9+8Sʷ]1IqѲrl{m|ƞo%gl;E?z7/3GX}a,6İ5mx4KM p?3 '1$|+9kҒk:qiZbb$?G}^疵9m b=Y;R?ѯG-StA3O,Sg5y{8d6A:5Q5OS8ާG7 YEqE"цfHs(skdR ~ڵ} O J֏jyZJbS]>=c|cEW4l^7y۔2Ř.3ԯW-I+:F_dH /,",g~oJ!ffv:ÕYnٷugTh?ǰHvYN9rZpH~ɚRvu8x@a&{ռ|΄@ !2*j$y@07#}E xM+d׾ ʟkϙoI픏Ol6.!ٚtX1>bBkaۉz1,0^EjzcE#uTbXJ}|n6 r7|xM? dK6?"p*~fP!64QcFB(Ku e@H&E [sYчHwB"R%? .;ȶZ>TׁZ$P^t}/07nEL*+s'@54H%t4ڲo; ?o8#QhDok`4;UY>I\VHf&@:z1{*?O{]P8|qVQͪ-Ṧσ}} 3*Nj}I'ܽޮ,ɝ*E Wxإ MxC]P]D2%!kZ'6? ;\y|61IE4Xޢ,jO t,d!9%N&3м#G !7"(8csz͉? Nϔbjw5`ڵL;"t̙ [k"PR#J/٩ dU.*.>[KGeK~wl٢tWw`&,X3# ?(isdeʛ@IQnm#[1>.ՆiBpSM>)l=Һu4{Y [e Cl~FdTY~!l u8Ӂa}ԩ oS[Bo򕎄}Uc8oG %ޕ¨/.L=Po:?6 jnߐ9`Uә '.r|'xeN;.SD=?LnJ q\l( ³@5wY` tJ 磡3KJQ6L N<,wϥc0߆PicpruoPМZӈ ̡֕1ff%2 q9n]k}G z[ Pm)8ErgD( ,L:u.Lt^;)r -Yw.`U[F,PEv㗏˼J 61X.p;{rkjuYFAݧk;a8-і#d!N]VRSb?ؾ02zZpp0M@?.4հmv$!-L <[Ex U+oáziw):׮V+Q/l]TP//$F;g $- Y=Qt'^H0./v,VE4P>Q(~AP46[7N%7q7L {c+ ?n![ĔWX$$\:QƳ$E4uReseZ̚ ?TѥCMͪPȀ̮H LmZ~ǘ=8f!HeVGB22x~ ;) CKC(ߕwֿHX,$Ϟ]zK{?ɢo539j7 kB@ʇh2{0.gb#A{ndaG5֣1mm"jDK^z2(XljufCS(]>ݘG>Ib/WAN{ ZD̫V",/ί OyzPf+`a"g+$w5*񟢢K*CڡGvş2GNpβ?׼m 5t3u9j`}v.Ik&KA%ge^AK 8;2P fDt{CbwnN1w}"cηb{gԴ?~~"B* Mګ ҉_ԀzױN/OQ Dn ѐ}tp!f/V7B^[jAQԡ*TCԂy } ,0WOw6NP~j4 u[߆ᱏLF^H_#S)r<]ml8ӳZ:W7Vz3^lʘAXo@Luha v8ƾ`ɏ&bj]v$%zL;ӆtKf~PGG\7/}WUfFs>NѐXkZPCK#GܙY)򑏅}/K@[bFS}C-fZfLSr8s0ֲ=߫p Q&Dig6)Z+WĂH ߈@\+fC! Wfn`qч R"Nb'Ԗ@AU>C[e[b>\eCEDlI'~E9=sPþz^J6“ٛCp*vNGm$>\oX-έrW$~hk/#w+hR~YŸd(XK ג}_2U g-@LE^Qq߽{pi [.D |#K~<&=<~ -~^ToQ6݂]HчxB{#Nl'|F L_ܟ  $8t:r1p߁O^pW{#BCy uh)§ x%$y(GADYXDIPau#4bX"LzْlMI ~I t% 8ZL([syqA"a œIo<8煑 nLA&YdOՂY fڋԤ{>AT3*`۲S  %sh׾5|_FtW$~5fH_NATiVocˆCgx7&]:c:)Ji4ݥhPďSDf+FH$C)<a:I03 O3EWhvșG`LL>UMo3:s~ Up.Qe- B' 4@`fNj=B:u~VUsuGMo"Imc'\F\0%:BPYR(-1=>#5PFOCAsdȓ ,h! `U`(X$sj=d]o 'UQl:diɊJ:%qPrBlV7m,_D~q' &aZIp)tqU% %gzxQ;ua~' i`ʔ􏃟vnr9<]rޔ/ќ 2kBxK9*h'Re `:46 G}*RoX"s  4c()mNbT)LFP<]XbH:Žn w~6}΀cDk\ qLjRS$PƃZg8--Pg:]teFXPy~TF9AQ+(rI l(i9ly7FxGS4}.+e#IUpp~.h Br. Ubgo>qkp]񷗕1g76Q(o-6D, w e[oH1M ӎs5_F"An[]fM1r s^쀘a+#q!Tؼ: z8@LH>uC)$[Oa{ء _Q"R a @P~^$҈EYj,t>{'=G>Hs=["ިNbwSnfr9ՅZܤ6+ӴBIND,36pqv=9ŁH(4G~̢,{"rJ$A/Hܕ<"򂨒x S{as\S9ת5M {y#<BnܔYkGKvI }a-zhTTȵ FW0,I1E$; @ mDRyc[ňXf\&huhK-+Sq^?zwMNT"QcQJ>sDR?{6}wO{]Hsɦ9VՎ(0t=Lx%ЙWF0*SM'q̛,qwМp.c8.4:/ %!s-7 YgHU06o*͗h}Ǎp3|䰞j0c0Ji>.BKZC.30 &ˆeI);eَِPR|VѤCUFUb7:qv4uۏ'AЙ9l@cRj}\7hl7vq,6eFڅsӫ0+L_K4>R҈T7֝PK NB#fA'4^A1qbr' |6f4{{&gY"!tjq[ B}iz dr{MQa'M\BnhOKQ`v(݀s1\!ÜͺzI0gԈIDV!\~h5$n?,]a^YLbw rӃY=XPи<##|z*Ny%9Y (9h; yߗdSf!#SY;+-%SmM"WBSNB*5X@H13Gevv eBn;l\r2==Hɬxʲ1 #<0HR'=uo` ? r:Z?1vΒX?5s Z/+Ag:@vZ*)ٓR0wU_?SS{P㱤+:UX TNZ٥SXoyy4Xlbd& &>P :0AE}/]Ȓƙ:<2*صZqmUPBǕOSDu.ĺ|m*1o"(Y5ё̡ܺa4g׶b8=Pu,Xmeurc;@YuaϏ-’~@K]3+Ű"yUR@X铲ů-dܵBHu+qO ^ Ε3Nr `]kK:{{D?_wg8mL>j_V n~񶊓0F@{ܳ jDxmyi?l聺+q .=%Z./:Mܚ fB򓡲v#s8`hUܚ+փ:3/BgHxužllj G|j :Ӝ,fgo!\kpUk;F+G;d-y@HF}JSxX7::Qaq.VYϹ]Fw+ׇ:m% i~'J h0K۠3eO!&zq-s Az3#~Yԣnۿm*'u"z2yIp.c OƊ+ Kuyk*|,g9ِIJ(~cы7O>wsmPJ0&bQG qqn"gn"cʬp!/#@)Nfx7b xfsehT8[$6#0A[SWӗzKpNO Ղ͢ܝI 2Ltm2[t3VvN{&l"p# ʫ:3ȦƟҏ=&^nNPE\SBvtSQ0-M_"s6Be{X:d=0^ϑ'𤑌N9%G8NwҙFs>VjL:9چ9R<,Lkz8;u325 _Mm&a ӊ]P 3{w/3fRjo<Eb<]'Uۏ#Dܮ@}J!QBr<`32ڟIk4fMj@?:PNKIt4q3ȷ0}-19s+~'Mck:ɑL#6ޮC}UQꙙvܡ4KO$] ,f :J@C?I+ i{ٟ'55od(HQ5ǻ8J*fzۋ bxE*9嬙;΂)q A/؊dܴ,n{/A+);[LٵXPD8=(D+Èo gզ P=m7e!D\2U2v|!q }.9;eqv80ѨP5xւWV3:?HpNWM_QisƱ _%IW7`j⠝b3A ,CMU't"L' :g(±5̴;b]dcRN^kz6]YEJ:C|MJQRD^ Ri:!pQU:5S43.ܷ.SQ:+Eqb/=\Rw`.Xnݵ6#X5l-Kkڮ _!Qo ŸРo|B#-=4{`?Ma^L< :AEmCA1~ $pItF1!^]Xpcfh#V/OBJ\!-4\Fx㏞> -BJSn }<%$N?x x$۸qYbg r[܇Qveg\޵M[L=5ΠT\ƅ'XQd%T\YjvoJtkh_ [?m(g'> _cZY'>F,Kf[ƴyy)3I9g8E^a D_-ۋõ{jtU{\Ƥ2;>01l 9}{\x?kUK!cނ|WF&hkЌ84U"&A<Y(m NI>[6i>f?|y 9O#nSom.nUD__lT E 3WA?@%GZld[7e{eI9`x 1r;T?9A*`_2hjcl)2/g]p '4nwj-^Wa,41p1,1 y"(;)1;V{&)Rįس!rJj}@L-_Y3 fHH`Wk*Js"cCOw!B`0Rq٢-TֶCC6Z45(@ kR4R +A?~8C 字 k@gKBR8; A)iO{oxY?O[435xfn Zsm#L2Ot ĤFD`hֱLM#t62{ƮNJ!Ѓۈ\+ <5 лJZ" | lS]yz:!ޗ& 352 4$8m_8wʫ=]5;kqǫگc_b.ÞHNJCV\1TDr$XcW 񼖏HhQ=dJE.uuLk]v츀mADטaف%CծY*x]BU퐒 _\g mڀIz3|UMtmgrƸK淧Ӭ*p:,zzޘ-3wFݍs?7 @/+eJ@8N`*qyx P4KGʅ4Pv5󴒝`?w <ט}e!Qa!Q{'bk#ow ^qxEk!ు|9N.Á~ CbXa gýo̢]$tUUTf tIiS\):;S5w_Ơݫ_Op玶,O PO0grv;YlhFF yth;)|(<&E=BUeUy46[.(?UPNRzt}˅Jʯld{\nH2ςxF/(ᢘ _19R!-˶W+,6\wR5ΙwDž[26~i0W4ԂD,:{ٓ!UZC4 < K>?rJEgov(@V1Rԋw)cLK# ߓ? _?`$S. +/9a> i: .C7*MfXO<1 6Er⠬Q'&QtN$ȗL~'dM GWB0/ٌ%YQ ͪs2 `/7CtOnX h'EF ݱϒ5؃ l bHcn $U~ B4/OI Cޚcc(6%{Q"v'1-(gw-96[R#xSa𧻨 vΗInyM6C@s5wahTP}uҼ#-8g8'bnA)'*f;CDaN^#ԤNU! Wvr4o\/+5/U21D_GƷۛAz13dE3JI'm#K jMiUU/ $wl $ uFTq#-G?ݲmUx؀w2ֺLRהRI=0RREȼz .FZL !2FN%clQEY[ `q1B5Zd!Ș N3ueB2"&E8\+F h}EgV15]wյ/m>Tx/\3%MʴDB _ rTg;!b7EZ-0'-i٫kx72"0)6WolW)E LWT`p.*UTUI ՙRNt^|Ȟmg+[۶2y#9Pܦ{_z>|QtD|Zg kQ/>^&SG e'/U94ƀz&05ӳ49m㙂R/G+xOChֹvTZ/|Vȶ$[}Q0;m/S8 I$8շ#[zN/92+]P*:S-.h:g ,Ɨ@^}wNe{ۖoS>C1 {1^Q8jhzoޜ#ʭp)^ynnLNfI7: e؜&g`?mUAU0zz6C߆(!d8MES*7q$ReT/- L`0ӂwZq+X|>b%xwEʲ{bz0}\s 룕MV:vr|"鴓FF)PE̚U}HZ[)c7ЎJӾ~/ e%9~Nۉe4eUsnx9y5fiD@_I[^}{܃̰vq+0NLʅRj[YoK!fadEm34 _BϼKgFKDp3w 2Z[ g6`VbLw\wSٰoh;.A6[;;E#ׇKjFJD b>.N+? Z':F˾Oc!?UޢòPS-ah脭" ~g !xA<ۇ+o&p;*cK=b'l /|<\ V'ާU lsx觩hǝ"4I4u(ʓ"P ZTOcVKk 1|)&ك(ވY+nM@9=tPe#\LͶr ~;{DZn8h[٢VT>$ddR)Kt0bz+}=;2d ʦ RL q0JwbY|b{CNAfD!Vgg/NϨFtyB;q+4pƉ>b)`"m4;;YRy 5i^i&w4j"(*ηRE`Pd_BK4Y@Iuy›FiXSL]bGhtu*#?{^[G\qw^d-gH#Ow1$*WN/fO7v] 5;0BCǯ]KS6jx ]FllvD_"sRSխ"cf`_ ?d0"QMgu{q *x*z.H0y>A RFbjjxw© LV0#Ecad9G{]:D9Т#sR1wU++x{ uRe8 ' jBViJXQ?'- B `}WcfK8hjh F /C>$$ 5[d_`r" 5Ȩ%owҵa'zC}kO֨ }XP(qpj#pq6>NLwdm2ᓸgaD\Fg|\{OGxk2Gٞ4,'0c{~ iRnUH5n(j߇wb@_Bڍ+Hj0>i9Z}vӾH=QJ>)'Ȃ0&E+QԲ[ 3mh' y2m H w'᤻(_XLtOIk /eag:.DAM)/ XF8"YKƸHaΞbrǞFe˄e^}ˎ"jYVN 䝺x;qa [mDhFC?gr6,pxnbw}#&VMWn}z_]]?l|s5]ԓQ.ssM=Ĉ*6\s,![yrgJ'+V3ԏ1|`$?Z,NH/Y8\& z$_W8u ֮ H6L'~?_>ʭƷa`,F<;J1Cǹ73rPƢ"6ǚ:VW 2/e/W% \_ Tqn?b?[JA[Bo}m:Z'w%1ᶇjI[UtЗu1QCш:32 ?5NBS W/_P8 TO]>ǟf>q79FD46 V\[=NIOaʮkč P/1FZ84'CRwOnPROnl/,Ȅ&:jܺ'XMݒS/Gd2>odcnY Bs9$Ճ{iklŮcvB\ 1ęL{ *ig.yIxJ2꯵8}yKxu}$ЯWNޯ)d吇$Zo$xlȁv3mWfNas)Pþ|1בjkrm_3n}P+d|m񐿖M*V*BEUnE~iqւ)=\qB%T1Ғ~EVre?'B\!tD\n&voa"aQȼ $2QypI}0`)^-LsWxBdo~B Us,70/tg {ƂpX>\AX`QJW|960EGƖZ"uTƵ d:!5"yO^!|8~F|NRѫ-+ri mk}xAUp$?{uI6J'!T2ldd8ψ]0@ihȨʶ{./x<<%[sT c|*1g3h[94u"P5]] ij\CYLоr>VaRøƌM PXDnFUyI'w~|$MfH(g=-{Z jW`?OksN,~(61tUfd 65ۿc5"vbׅ/C>jRЇ 1;?>toL r+x保 `''jfz]#s{ >8Lj2~o0Qӎx6t.o|uܞWKD˰OcsRk oq,$ !G=o3\/S6EsZg&#'.u!S |u  _ |FMlh=vy1ERHK| 9>Ȟ(k(AӖ*X) Wz>L}F[QZ]K<($U0 U?}Ob|Ũ_fuO (S?cw1&|inXFErL}!='ye7'Dޮ\~ 3fhec< (JHћ}րȿJT~7`KD΁+?#*)Qng⋂]QɆ: ּ~Ecy!u{bq]cL\]ҩ4dPK8efywA0t2BߡDȌ*Oyj0ᾒq6+ ,®Y.p6_eS~ h!{5h)sa` dɤetbJƹ d -X;@:"da.N9 R-d"٣Exqaҧ0*cYRw}CXУ)]n"aV(uU)|uTcW<< k!UkgrK *:$ h/7%7FE-w s7|U WYJr/ࣚWK|zIZY#,kjXlt h}c)tϖaFqp}t 5 ‰`918.AUs(JM͏#b= qdzH̳]@JL:in]zZS)AZy.ԔR+gfqs&$YPwWU& pdN ]͒3si@oZÅ3#xf#&bA)&+}P7f so)P> 00#藧6x洐"Ms>1,':OU %얽 ;]/،مYrv艕^z#H-kz?c^iA SCP's'm <4F9VJɼ61ԥOlE9eA~TP(:WUBRIVolnK[)D"Rc͐ W5t*DzO+|0f_7OSeo;N_SܗA4ᡞ?Tĭ <(c 8%Z|HٯlҚtO1Xb^5cZ 1]kL7|]x ~ilh?mz=)V/JzJ\E1# `ʳA [_!*88E T>lb+t?4{~T~3USdʂ38qvc xxu>3?ar$9Sk1X[4*R= z$Hߙ{8AKt+p'|j|}Gk@ZUؿjwcHPLB;e֤"]3^nA'ċ_&'n홹-f큩y%%3>5wU˹1xO̕g0}r[X!ޔ̴^5< _P>,w.;" ,yz`Cхr^v˒RLlD\mQGdm,AMdj'iK{ZkQ!Pw=+VEfj#: T|nsS_SA0t@]3V@J@R() - )=RDƷf -hAM`l.|icەJ׺UC݅K t]f:mͽ/C) }_vlVDV$CsV=w"]ǁZ)cQ4C,3gn/8d>ݙE7B٠DJ=3f3EM !J"q=+dil4:á-c5ie"fx/ J#m]hKSv[V0km&M9Ʋbt{g䢯YIsshn\V2lZ#2 ntnB9CX;7f #^&]&6Vv~t U`Re։W2>' %<Ӑ.}HaߏWBxc'3g,JGf:1 ^ӚPP*)1 mbxϙcG+a~eD8y鸻a͔0 >p*V"&w%J e P֚\1gtQ *s\eWfPMybC!``?ÁR=YA=/%.CĹꗨ㩱RIj gq%%1bqz{G2E8ue ?,IvVW`B(.j2e.s4Ā{aAypd1/I^$ 4n;dpmKU@k>gIsC{ҎU$.vs@-_+烈ʸ} 戕y "wW2s YX"}}?Oq]BwTCl3Qkʛ1 HjieR/ en?"LmBz95ljG'@)i-.m Tk1 ź%nl%)! pw]Iئ#O\϶* ĉ@w(tKwDڣ!DҖcrvcK]Os~4t:4LOH4AuvOS:藛k_vGY.1ꞑH8XN`c"' %n<2tY+ȫ(x_ )<͟,u)`f&{d4A5uDKd N_%7.W-&yUXcyl~^VL- "zV VʽBP3pa;i,և٩rVwsrq3k!td@ll䎽>"Mv^bz2·VLK+M ا0TJy̪U Ln֚Sm:>rP#7Iªهڔ@8Mor&K ˥Ϣu/emni_X#QYfut[K!wRsNj"C ĭ[鉗g]K%*͔saY^&ly`ae _8(Bxäo?3ݓfC9ǓWb*^ T CM2kC5ASS R 4G~Q]HY[+Ku~=yyrM ͪkQd-WRi\7L.:.^)! d)UpAY?aN^T>D4uTBs!M՚Mڿ4Rm\"7 G\#@7͜-A$0¹ڋxvY!{^%iK%REMr!SՁG8ꯗh ۠i߈h*m(#yruUG+ՀOo3YbZQ)RΖ#YN=w8HTJFSRr2Y{8AYAO~ea) L+ϯ̺vz[8݂r9c2`&_Wu Jjnxky qsoT$sSgT4DGljh+ˆbxh[ U*<дڳY!:VPp`7fY9{KM/^'ԊM_@zJC#(Lsl,@1 j]KՇꆌ5CNP, \n$Ku|Bh$^ !~K򞹫wUT\Y1VOMfuC^T[Mhf1CL _\{zg8Ӱh&={3g@-9vLC;[T0;@]eR ^S->8$k3& ࡘ+YajD>6#)(,[aڒM+pj5~ﰼ+ϲ(ݣxߚW?pɏr4%xyAfLx i11 :mF'8pHF!Ƭ{>CR)WhUX*`?>5qyN1#:e$S*Ґ-ڝSn^c)m`s)WS4{ǵ;F+Qe{wkeJa 'eEQQKI4VHPN$E4Lj,;N{*0ckH/H5<9==Mw=ZʬXވ_ Gg.ArY[- x4 Ih-1ύ?(ZS]uBbV8$G MPдdpUs\Y ̀X G_Ø67JiG%%(w()U-Qu,OQYR*G pQ4ot kV(8UUMVN xpL?aa€GY=!ԃZoQǕBG;>A0C$Zq@,@[ 0/GOӔVw** #? =7? rXF?B)vV}qzu1} zpͮPE(+(6f},^(f|\B{!?D^;0޲l} K=КfDI~ף#/$}z!qLbi_u2ل}9%i=n|wELěQG' Wwb=mDU@RI!1:>9sj>fy<"P+ٵ56my+ q}۰qp܏J/پt:;bj^ ˄Q14>zyg1TGNG[)a}2&aB+O^8$nN$7&|8 XIy]WZ76WUٍcseZ:bG> .Fބ7(Qe8?Ы+6?I'@]vH)t'%ͯ'mCZ:$O&HCPW|Uonz∇'q UmMBsQ$|*NGRiG%+pu ̍@e@JXٚ, Oux>Է|_0uo-). !? cEٟ!091F\?e/1*:P<6z6G% t:")o̾(ħ rU VyVIoml0k֟-;7jȒw{VtsKbk6XsңJHi6G X.c΁*ϒ dڱL3v5 ޛO%d?(jҎm*:jqn%=4FjCQX oWCx%eQ4oV^)'>>:#Upr? 'Oȳ'G@|L;HK6 ;#./H(IKMnoS^P3?b c~M@l$Wqގ(!m,3W\^gFK1-KZZUXkuTQ CiQw(yhJU7o3-#V-F@ b'(z륭uQm\۪[wKPӹD(q) b@\$[GǮ@fQuvOr#!#mIp ᶏARHXI+FŁ/-'eCvD#UAeGf&qBj|֖D; 'T3\iD)1I7N> VJa)[T \]o9oyIJ2rZ b+!HC jH: wS;Iw#lEeeN_MpP(/;b c*L5+yۗu~R4ܜܘ234(}ً jq#4hٮ@tm>@,PDa:"ݐ@)q،9GD[&k>.Daj{칉ev)z7+5?ٌ<g>B˻yye±oms ,b5R`^G7,vcTEp')wp0+*t݉IʹIrܟOAQ/W%jL髦%̘:@)vcﱝyKcX3tIlOYBc^Vt~ċs*a-^j@Hl#sG17нjPr% y!=fG ZS8*a04@?xw8 yOdƧw{ Ap@Qe.Wn(_u5 R1\F4NxpJSّZ*W\+&1zjy9k|9p\EHZ ?2~loxbtyNo'kYۉ}*o uH"Y5ov<^tzWl@ _qwnw"%ܻHZH\1XV Zm CL~]gV'}SN`= Z3Ǻ5hxӰࡁtc׀u.Bi]QKqW0JMI zxfCfHwwf xBTݽ9H+*SOUBMUN|,@m?&H`f\9Gvps884Ò?eרŧbLSA)SXjEjR9^YyxMULUx@aS_WyzU 1SpdRQQ.u'Bge2s`]9#^~oJa֮^ߛ5e!-U"nާx6qG{^P/^ hUJX6ae"g[Ym2߮bVQu`_\Qt/J88p4ʅθɣy}M";O86Nt0 enju7Ju6>mG:{s0g@>?9dsY ҂\oikoJ fHͮb:fN#m9~-)QD=՛r֎ g(|ج`;t'sF'Z+hI!!>UoǩcHLZrLqyY25N~M/nJ7f*vSqк 3Y+MCԇ%h ۀlCpk .pVG.v`5`Xcr`ugOwµPd!U1M8;N4jl%a)'LɬNH|J i M~mʎV5{&tU:KhƚЃj '14\F>2A^, +SP3(Uvkis,,}ql N|d-hqH˕,B*~@zZ'uUpV.יS}^sEDImYB?׫,!PNyLɲFR;FKK'.$&AL?f5*zI&>Y#` o/kcpQ̀d)?6]׵d h5Ӵq{FJr ٹ&-2<8޲l5ޞh= 8^h-{2g(,QnH:iH~xmQPbx-," N^y>^ABn ?U,dV&naE7yik1ڃ-uY[;᪋@tI~b{ 0T IxwV=!ާ?x)IfQrMC.7ЎbИ@Y2v;(79r%|w|92NڙlmC&Wt##;]V3']yZ_&5]Tq$YI;GU!~mNvb?wZ%DZS x0F/fS;K *ZߴX]Һ+);c#^vw ^VÒ_s08+$UtzoWpmFw,Z}{h[^7棧}H*4K즣Fڸ 2S:=i_瞊XԲֶ:~ziԩdeFlEϤ[ѷ>s[ #'*Q~fono?E-4i(&~ #ȾuWz ]a(B Wԉ17:VhkC O .A8A1#㱧9{Z"}$Y7׹+.Lk?rәyC'UM.ov9rقH;[^ X_ fr_l#˥5.Wg+%w,z-+q <P,~ wlm~`2Ӂ u2LzMm#2̹'v&l'Dawy.) fR z87-~Y[R#z*>:x {Ӄ[xp@+HYCW:&Lj 4 Go_֜_j~V6mG_xі $ M+KJ{ֱ' 5˹To"NGhVbXmܿ eۇۑjJ}pCsLsu]K/DP JI݀>S>[wgֹay ۄ&iKSvV b¯w $MbpI62iP֋N[a"!S0b4͵ДB/b4~<`W[o]d7;;uBT0}O/*sй—{sQgn5VV0{AuA"ǜ-dՐ ؀\aOӴ09sFJ+}ep9sUuM&9z7}6J@hۈN>p!.D.dC 4S-v x$bp. oqj,t-,+ь4X=E'YnǂǷ*QkUP&:kS]- ŝfCV /Gi9j eM~:cSٮ_Apu %&z:ƻ~|7nv#_>^9c >+t[]AUY;cx 70Aqt'ρ_2. h +2w@dfӮJ o2R,}jwEs w.:OVr_zj8+JU%MVɮێPeU_mTu]8$Ukv(*u}]o/ h*-^?9jDd Y uMD[ާDBWs& :ux|r|Wo]կy<Xa"0̃bn? 8(nTtAڦ-jv&R_#Ut@ W C1̡jB pX+_ǨvΦR{ m`Q^ C\Ćr 3IӚMR{2ѷ@fVp\gᬗ7|rLCĔ[Ծ1u_z'g~ra$&"&ĻL!bq><Re(p`5>mk%0[ "_OBBb򲢑ٍ '.I6b ´KX0h pQv?2ĈƨYb"Zm FP9`GeK#?Eo],#*-#^yEqZ^ҹX<,/=Lh&4;9Pdɻ9Zzkߌcu XTAuOwҖb&yl;WwÚD62 %8f:K'TZҺ&?ugCVQԯ:M5a<,\=={J'u%/9et6< @A.  O&Ir9r14sKT}TB7'k d. uGy5'nίS8]w34"y%lHF!Xn-eX@Zok ki?+K<'GB2KK5sڐscX!Pꃘ(b{3@y;]G"ܷ39Jj"|W).؆D5[TfP25 |u m `P"\̟ќ$d"jޫ .sq#ܥm7yvȅR1\;3^SuHO@|٩(H[W RP'ĉ!\ \iwcyVZ{<'兏ra } zDN`+ m,h%EipOcDDl~5kQ/ޔf<؍ ]pO[~C0=zԖɂ^h8+ʚ /(SXDK3h'3,E _r>dk׮!kHa7yf Vf|iw[ q@rGrhG9Z2z 4Psk %vH/`YT|=5cV/IOЙT&"J*ʱvM`l W^$zRye9j=M:jIGG2s4Ǿe]8r YѲ%.U 93]Z'&,Z+otsZj)͛"k%|q&{SwPe_',Ԋ70‹37ҍ^<m:Rx6v(Sx-O"C@ľ5qÜ H艎(z豔zM2i-Sm,p/C.yt#:.:?8zBP^ָF<o 5<) aꕁ5k:T[I*Fh֫*JT&+Y(>ф컑Kiv悿6T9٣~9U\o*BqJ{PyAԨkUOF 8wpL77ކ)&{>gdZmm5I^#E0DzNF|;o F4RkWB"k*0i-A412lЋҔ&G!`T,k= O5Xw'mlD">(r3VE7V o1..),WYϹ+Q5MzgV7+"P8FC(hh|XR,a6@,aΝoq.nD=Y0 g&YYA i 2!e܄["rt>^BըlTz9pEG7΄U=ե>7늹f$fv4G/obk3(ߦB9kS]~I9 Jޢ_猧ɳE1$ڻjdQV>h 8 H= n b>MFv.$'i^p ݥoJf Q \u? 4S'BgQ4|TM$R^8)h@ӺLs lm4# R sFVsnt978LjbH)jwVut%# <Ṓ`pbd` b\/Xo{y>l^fV8+﨤wJ"oL*! 7/l7-92XSxj\%XBiM&M,[BtV7|W_[#Q S@<#l:AI}*F<`zTĖb_E)ʨ[!ſF7g+-_Kw<ꫥ"*+XQ4`3cVЧ ttP`D pT`~߄0d a?WMMڐ!*`Z~lO`B Jj߶(,[T9"D`F ȱ rg5E'ԗ@u;Wn~TtNt[d!Cso$V-QW&Tyf [¸RiXF<Ι;Kʈo4+D(:%ܑe7=!WKFmSMgo&m{Ӆ Jgϊ+99`?.!n)1{:7iFIqE <@ÃȀ2:gV^oG7鲋LH6SG Q?i|-Om0U%Z 0`/sۀlV(q \8r" [i!:s"fݪvGlֻDB3ILϵિkb/.i@ !w[gT.i S"6`hFq8sp?&w~:=:BqYL0s1 :zf#+N~:ױsd))v sЀnق}j6q#iʉK+d! e7`a F\zJaќCaoh\YsVgZw vM~/yg?*i!lZi->nQXψI4NA hF0cXu C/Qg%<@ ՂqtE=]j ZҐa^^:T_{ EKDzY~]C2gƑ$Av -ߦs|bckES>7ar6xۚDȲ@:oYwx@ GrհWŷqMqU c?:٬==ݟR( (,锦TZ'KšM@г ދ+];:sJ&RȒAF+u suX{!se`ög MJzS ?sB&)lfu1NlgNI~hM L\Բޒ3Z+n*@`gNe:X S͟Ә.R'_ w}\H\ 1nh1r] Xٰvt VԶV.B_Zb$OcAu|䌞]Faٿovp Ptǫ,wcx;8ZL`|C8j;+(ݰf[% ;/ mu6~q> pr;X@|H;ֈhč㯃H{Q-kTuz9qIKQ5KYa^#yRW-EkɃ3IL7F7\.hHf)IlX_Z-YY= $yAq](:+iFVJ|-Z :$"d]jyov;aj _jIjÒ^99L9elp%8[@E/mp y$ѶX>F9аi+2ZvsaMum*p2 f2~Wn5~9nyo>=#>E(TlkE7XG]0I 5S }x[dhFO`?@]LI#JC C,G Yv$~7Xb.Au[;hp.'!: ?Nbs7(NT6ףbJQ[ CH% c@r=(,e8U(p#hݕ#FxϯV:+M:O+ռEx=Շ.+8.6zv:?Cxj7?j" OD r2b뽰)(D숮,5UL8(t(.b*i]:@A$3GB+!>N͍KX&dl YQ5%%[&MּK1ga+L1s9|9an{i `VSF7H H425oLZTShT%4l62 ѫo'pk{@eͽם|pxH1iy'Z&|C|ӿn κ8bM{/5l ꚥpA:p- V^hw)Aj"{e!oLK''$gAƙX hCl2('\vY؂՛ r*9x_AF5 e}TTX-M~a;1~L)oD"""9Z1 L1WpѾ5H^*11F0WbۀgNe ov;aj _jH$/e$WDuKS<-USƮ+6fa zp#1L,VoeZHq~ &$ (ȨV86T^Jo$wS'S$|[JXU0!cP5tj\श5@Abu$>jjI y}V󏼠7'V/\ТƱJ( wHސ<A+H\6nyuhW%-AČJu;<8T҉?02z ^cDu Ӡ#}!µW0<qE?ݑ>#= 07S XMlmi0]axb8]z'1.P̹"k+(f{ oSFJ'L̀K6yd&z%o<R˥sV/,@ڕN"c_Ĝ99mI3qk@ 2^/^W;+L{SuҶ(bݐ7dC.T .`ҀrJb˶BޘNЏ򹚄5b_}#k| rG \jB9%wxowj?@V,kaV]Iw(:ʃvժ̦E߹Y"fnzav9&Q[cxq+ 6qnt|׈>Aϫ|D~q-kR$sNE{9//>"]&'!OiHsy#OT|4ncƽw8QS6zj4G^fX_@*(zsx4Pזșn^RzY_ nJĜ{x#pM|ê֧fr$di=˾1T4сʦaY(mV9 (LHiC!s3 G9<6 gv&|ִ X>\J!NOoS(@6G VK60BVf?N9zCx\x u=nFocZVДF:p+j  `\Z9(}}F7gos:S 7,,|%9kB@dOM4ilOvb}x=F Pp|wz6+-k joUsT5{9㜲R'x&[4&5VGZlet&ckd(*ΕA˴3b.ZcUKu`T|swz+܅CysGg{ͲA+9|RkWI(j,FV1Y>J3jxMߝ2qsQyL N݃{r(_;:?s#gq8 꼄'~%Fbrrj^ ޘ/T.&V: Sl+MEe 婰pa9&%Y]D~-WHf% a$UIRBcLeV@a͚%$Çb L򡶚Q׭WՑDU8+ArxY.( xfa[y Ge^J&%pcw F(UQSVg0ǃݟ0>hb}:aaL 2Hf7?MG|nKVƨɯrm(bArBnRKR9?8^Gh ۂU5hWz`#v*Ai*Crڧ":̲I",a<ƠP]+]+"(,oW5oeUݸDΓi t;]&g{o.%1ܘwg Qfd솉ԯƋж*DAX(Vث ќ;?Y[cnўPvFpO{ЅCG3^e<8I`b+4RZP䧎e<eURYK]X9zK+oG#t87 6w,B!YN@jQ:P?^ _A&!1V5 pnf}u* זX9naɒ )٦J-k:߆ bT@(<,ld@%~* iP+ '!t: ^R;97M#FF79lKcc7P׊˴Kp9d CGOmCF?n ]MYw]o:P> pҲ7)c*sȟzJTQ,O2@ 0p-j9/ࡻdw (ZQbgc!O`,<*E`Mx.ժ ptP?nXStZY&5Eb[<]*<>\{l.tfke5<@[-U+0Bǎ-[3_38l7dԣMf5>VRcnz_58z?-z5? PAhhʀQFFATXUaؕV*?VcoL[B|̩k/ \d!iK$uwKYbkO7OI)D hGBb(4 EB1fQ? GR8mn%1lP=xo/[lؼA6KSdP4TĀbyϾpW+bqSD@VGe-9g,DlţweZG[y^ E :n,\[t)hC]jjy@Ȉlۃ*ugL?|2(EF츝.ڥ^*dԢ}׽D)?k"Q#L&㮿#dT*JKJ׽sgoi+fY}Fjg oAb|z"9fMq*,(#unb%r K7!UQk #*w& A"mISBN[yoTPG-C͚!z1wBOD#ƫ+b/ո7O8Vkϭ Lt젼a51Gwv~:V[ E8{}\f |{ - VYn#EwL5ювfRC-C~?h WN\d<~Ȫbf^&_Pxsަ+?4^:`-%?E-?]1V]?Awuyz0yIR>s:Џ&DL0Q/:5o]^@%Ÿw{k?տh]S^S]1%ܔUQPI 7(%S{?12Ϧ:>%Tq$[1f/bڦF)@z}y;؍abM7{]Ug2$k쑋ǵm!OS8̥}qů~McNSX-c/GV9FIgTT$8whвt?%%6}U+e:B-P,UɜaI^<2jD,"Yc&f*u* &׌$=QI`W-YuG^4zj_cž(CCDkrԨt7h~.`붦)@bW GȻrUGD N_ʑ,f JhR$H)4%=T)L0?|*W)5]#PtDMo1\EdwK 8~ FTOJ"W&77!+؜@W: G,9 mgc^jωa&l)(q_Cq&ݴA=2wwp&Eħ4L tADB=Ut^Ov.LoDtKKqגˊM/>*sg@1PFY^l Z>@$O35*%_Sa+3L4r9gk<.ߊZ5Z^v"<4ܫffļ끷,\'=ʘ3s4ɰE=+:|#(SOĹ1z :ĺ.G1j\p/(TO&cZ} (*zn@tJN`">F[~XjPT|# :!5lTCa{@!kbyPoؿQ@۩![t/3AanoSÓ_.5Ofʠ1P XĔ黐y xX9MC%F +qJZoVyZt;pC*'?dF4P}U}H6`}c0TXJ+u%]-ǥJ4N("_$>;Z֞#1TWEa3͛>Y:ϓ}UԲr3& ?m;j,]SNn,B؏vܒGR͕Ϛj0F.KJ.>~a%.#ě-DRpz]1,nyðj(ar#zb&Ä;} 796·|#**&"gm YRG+mv>xzFC{<{so]%Qwen,U^)}!,vܡr˘"g\a6$_Ra]nEf&5D';=1xWШrC^H-Jނ63[Xnx@̪DCPwK/Yd!0-[5!r$4*YDIBmɀC: n `ć:SJ.dBIe8g=H<)FͤF~aa?7Xnp H$at.%l0\?w'Ͱ]h:: #AlJRr>_wr,a'2\h> ;oIauua`߱6gIG!Fnm!LR4$I=Z",{Hka}ZPr0t3W{ o9̈́A?N(9X{R{kkJ2Lխ R8Kɵ@ %R>HaR`86MPM ͮgyhM0XimסO{=(t1[CX@dx67ѳJ-@Ca};Pí) c 0 `rAvGxs/o(sEm-9d&Y@W^Q9 I}W_UojNs nYec}ԦK.ċlZ_/? >2S1ך(ACa&`=dPQHbL,2۩XS4nQ,*y$͆&%_&aDŽQx>r!J1VұrF.eYoܐQ5=[L Ǟꝝq o $m-?xJ<9Ғ Wou5 ~c5lP!w EY鍕̻:,1GBQMϏ mCyuu,a#jFͅVd#!M-w^-\' uO k&*Q$B'n R h+5V*aAn]"Kcx'd߯86ra*{ޝW~}cGk%}tU(a c9z +'"CPh$E]ɥ*w}3ٰh(v)poH榎W[F_?D€^8-US?0Yq&a,}yZ,̂yM혨M|RmQIՁDAD)uYJR5^$Zghپ6 3K q4x{+5:ԄQj9m\=)I|?YA]=-.V2(vPªYydC$ QзN8tH.$O%6k0 E1Ԅk-KEYSTY /AgEªMΦC>tki㒒n:u6fW ]њ'*}7]Hge2;Y#Q LM\~Ƒ/,I׎\'$mhWBStuEE-FFiuO\n7bb'Fr;<vihge2h8SN3+ۉ̲CuUBm:P`KtI[s2ߣ@l-CR/˕b{YÖ3N{^toXu]FVxVuMF(s.&ܶz(:H4ߦ#}G*,ʣ0RZk/UqgzB Db6Bp")nỹ8gL^5Y8uPX2! 9f|5`ЧX+& HbB_u:Ҋ ?VЅhHT?55//.mx#RݞpzC%8zmT@Lk݋Vhm`SLis9ApT v"I?Эmgu½ț*q&z\nHe|GтzMw-Z[G8R1hh4[KiL0`Ln% M˄ADL!,G4l;[Zp ځ+_}Bz4i~(xHB7 LdvvXv`9_mE)n;wk{j=*1 U|4?C2@eCD|;F;$QuG̶/~B{?]A7Q+:t %@ux#x!bEP) cZbc'·֍[xiQ %،GTᅴtnY:x?~b(4,Z ;SAXh>!@]% $ʖM5"[07aB\eb} )@h6FqCF+0CwkŚ5;,31_*§ Y[ +ƍK7Ew*U*,\~LG^VqpwE)IOb\N F'jo6ZTm$ΫWliWa)j g㚵.s@{ϱHT~ڦyDuN`mrCD({AD2mvz93-sbK$G.8#B%-v8 kN†Kc6 Y+p5a,BE9I c~ PK.rYaةCU+w}B -\Q׆ e'6@ЄaeAJ~27vqsb2БL6!*QAMsYl 1ܾb5zı? 80;/BU[riR N@ 2nP{`|Nĸ¾*4 Q r1!á!;axdS5t'#$#7 [[;lg~ZNAWtxx0ڴgCO@H$'2~BF EFsGIɛ^"L R0_mx=ǞG?cȫ6d3@ Bt%3Ҿy_vr|hc3ǢAqaBˀ9 ݯi=ӺUhcʦ2FoBua2Җ#zke3Y>~ e/_G'2)n@(Xj UƸpRMl,e%$sڰM2s\k5[1ͅOlxK`&݄A4h Dm` \k(2!;$ٍQF +b}=k+ H{vfR^O~?qoSDby04 Vѐ񯋎`%?<TW11wphXxSvK&Y3I$±wÛ))Dz.]Ach&Δxk$^Q*# \=>O@Ԕ>J#h hɇ(^L@ {w S U'T-k6^-Eq%u*2s$VVG^cYv3rߥ"bBQG.( zb׭H]PQ7FMj%Ok (3jǝhu^q/eů7n`9^TMRG)&Hm]Au^NaR{$y`e]R8% {,ܪ;7,T3O9Ҵ7aTKx5yaxP8duLM=q3hW~!1],RfX>bW(̼YyhYw0K.w7ߵ )>S M~YqcJ?@}8 /F3* 7`U&?:[xYceL7 |_2_m\uL4},ѥ)du?~1[y%0%u v ɗ,bƷ%,!8e;]xvz6Za}ɂ8CUiU&ۙ9E2ԽֽI/YFY;47l Նl7fSaGei m\*X!Tz # S=4ebIhk:PYB `Uj^?UVZ-0ϯW+W19^y_6Ht`8cb\UέWS0mO^4HwA4x^LICi'.$I`2֛@\3|\՟Q1Ͼ"XTM/JgOӊn.7s8v$m!^j%19LYlAZu\e?I_}cS1Anqo }M-O'FX6YJ=oH~IZ6:UPgwe|Y*"j@JɪBcTMS[tiGBjܾ|ih?H)٦zJmڸa$ ?)Ƒ|97&$97pKҊ#~̼ABƪى9rő 62ϐh2 -Sz`ba{a8پ4POt:\'&4q G"29ɏ3YEf# g֍ۡ]lHLs6do0Ʌ7vu SE̵2 {aJӁޮ@Q$M6"[1[WK9Zґ Ʊ!(ۣ~pCVifAs'܊*Yfz#Tmgɴ733`Z\;k*u E*Vvw{QmVa{Yp 5 s]B"kdGng3"1Up9WZj2 RAls:,KdNc>Ľ8u4ܓ?׉ Iy8r,i3r(W+,?p\Y L}28ߐepE@ 1NtȹQ_X̼Yy72p wZ@h QFS `Q9X^Qv]M:dYP=\kK}* U^&=#L GGh7 rl0Tf8;{ M@ˌ$±^NӘweO$`hH—9pО+=ݹ37Tz0fLt\γ@Q$XK&15|ܶs9*c0B_8+]L;:xZ[-h]P?0eƋ>fT e܆.+oUM` e.qsPD n9{JU)Ё  4 ޷kKSK 1"mm'w@z$\5ܭ>D;| 0 Ha CɁs|o/pDH=VAu ƴ 2IO4_7J. 7(|w]5ٞZ ]hL tkul#x"Glβ41Eb!{RnDI@Й;p}5 d˕IؖA[A0p(-zQxϮlzi04-@uS@\G9I\˅̎p`zPOe C1 ܬWJjX8 fAx2$Q|EWWx礗JLr6!GBZOFktź N;#4Ρx~5E}jg,_vVe7ok? 7r:Ц1\ 0xX7: =h2<I7S5}xknM= ؉6?d h{?N`4Z>2a5OːkG`vCYDת@3MD/j2M:|{!}d]4x$D{ 7w p-< FgUm2pšTP]jxcvxBK@;U+RxT*P2=+a?{vBu!xE)]ֺrylm nv,9++)_o֮oV^ *yoʾkd~2 -B$qZ<\&Pip)/"U}no< k"s!Jɐ>#&Lky{`ܦ6S*L&@XD(1>anGRJa '6iЗFl)k:0W;DH=VB/3ڶkuJ`a3h;Jx6cA.&Ry:jk&y)+-u4al+E?mU06\)_~קykЀ mh}چP1l4Vr$dE;l@3~ߤrtu8Z]BEX@miJK^^xV,_1翐f}H>F?S0a?ō#Ad!%Hɤ"Zc ܲQ0?]|5wqGF*kn(&0f+!L9چ}b'41?(\y۠`X_q)zxOa ˯y~$-$ K䉨 *y:iyChf- ;%R+SLyzP'l)jݾ$Jq&"9Ltsy[vBt<3(jQ/IB= 9[p\vaE2G*-%bu':.=3@T1ҙX)hk&*"vy.}ն3~ɰ"66`lΞ̲8+C)ª*"όNVM1=޽қ|ZĒϬ͜|\Fkʀ_OіB~ g.*f:IIΦ ;:`{O'd{L{ XF0 zIy\E'{8"=i3Ip-UsB丨ssvd'?@Enz[[ EVt!XB4lrW7g8)J8c̙Yއب E'[X+;L:74ui8-A?o2$$tRv*86|]8譂d#&ԐSknJUqwkFsraDtL4ߠQ_ùVڠquTE1lTc+ R&ih=_ߖ3Ī>Ѳ: ]nރd@{YVl+W f$ seT'/=etXktb0*>"L(\&x ddUM4C>օCn2.nn\"#qϩN3Q҇ݴ.pKyqPryH= %;vOAGiUB]C#f{] &!U]t5ZSEy2?H!=S#B(uȩMY x.Iv'!ʄIm^GM(E!aVp(HhEe7E9xmtٹ=^X+{$/ P1?ȶū(L />jwFE>Zb{$L## q,E慚PۗS͘_By~y^lFVqg %[ ~Q):U/`Ev[;jqK4<]k͸T\K! G2k]O[ǾT_՗ bF€&πSՅQU*y锍|Ҁ5dXj.@*gPbҚ[w)KJFچa_ܢʋ; 1hƓm;˷ ֢U@&Z2 B"\b'/QȝDA7q kx\x F8Oբ1]ZL@㮲^uz H,8092H=3vP>KB;*h.۠ 2L7^#`PHYui[tG8v5`l\2նTХfWŻU@l^Xp?gF JBv~$;{Ӊ:cD{^;@嘶HW&zL/tqi.dhGdlS,o[9Zs]8뢈Na5o]gbiw'g:SUr{;F2Mmtn+L>RMs!';_DP5`f¦8E˝k,QUdՔ@nbeN@)L2ڑ/)W<$aYOQ%&5478KQtD p>:];ZesmMRa7pU_iԦ~Gòt(j$c9*)|rveK& ?7-=Do!ie}ϝ`I̥d7> '#Rf3:SѠ~k,ԱʪN}2/va}U^F ;솲fU5|"[-NA&.sϣ\RlF_G둍N~Pei>dǙTXtgӉ:eh~P{Q >> eu;J7^>2|m,3T x3`Sf'㐿L1tZ0s,OZ1KI_*D hcc}biUg$b&Ps~g,1,F5}/p~wapۜݚ-|Zً:FfKvbBc/M])2d(oPЅ=cm <ϸױ{j6؃􌯧>OB±yVnΚB>kqE(Q  B(]'t2{|*lp ޺ˋC &5bЪz'GE_.U><6\"apsJC_Λ-Ovi m$ +7v HX=׋a3 pH~f ̊ɽj ES{ZQ[+Nbx4L| ۤq!IKȾ%ǨחKxߡYJ>:Co]l&"Sr}ןOwb8}-8WAK X[BgFp/"-FV“ x3JB;vpsLYsFQck23 .w_ƼaУ-YzAI_Z/#,R`KU_ 췥DG=5Nm!:߶gI" n6g=7sB0`QcBt90XV];jsy? MeufȒ}zku>"{cy>iDe2s%l-g:A&^QTJ VNv"\v5 z/-6u'0J]§ɾz%@sW 1zݮpW Rl0%Uv9Iq}DK k a p,ǡ_,q!`879 02DXox{͔KlO!)n'4`*9-)^'TS#k,v)-D g&RZH틀% mElxr \eZj __Rj 2`% Fs +^r<b[daJR;Y`q*+:dzܷtPgN8fZN25!0?`,A2FQc6!X E3U\M[2kjzYW u>nwmD2:1;J=-"dBM>awJJ<(]A{6wgi8&w4}NЛ2u·:y7|:ҚDR:a'ťq}Odq)Ƚ.IDvzae{ șDv\+xz0kSQ% h_fgbŇG| }R*j7u}eeY\/;v?677\nQ &f̾:rXmK{JrگGS $gp=͕Ӫ', .\FM>.,EјGJ^K&`_QEOڸ#toצET91}T 5lTL.c16]_핦cp f+X(hlθxgtIEEz9m@^ė9j.)f)oeky !QAifhv@ V}tTi-D?QK1B@eV苯+WxO[h$(\>>F8Ʋ|&grU;~~hN)ԺqIe[* rwܑ˾m v]az%'+{͒RGu(ݦ+k *V8.2 |~ͣR/멒>Jn8rl|oU uf7E<*ts1O Gym(ޏgAnY?@=!,`?^cFYWVgxyRy޾->"ϸɸvCUAOrX9BD<$CDD[Kpjm乣^t[AC=t ՌAX s昍k"EU,Z;2pZmGS]i:=l1JTVђػwYX?A!Pm|dU,;I~D{tm;4e\_Έ [d#h4=MpU r5xXdd}C4ߖ}f;xpe%fm\w.!p='Ђ𻎬7 x` ~;3ٺݍ:Rj,wLL.7G&2Wl@ɯo5u)5 ]>KzX>ª8ܨ>|aI>|_m{i~bFаaDF4?Չ zD[TtFy FbRbU{shmY]٭fQUlra ~gu`J5> [^6K7IKo*oGwС!Gir9*?eč~dZ$D-!Q/~bO7Tߓ#*E+2 y{z 4!ȇvbW"`+g/ȩ*JJi!lD|h ,oRW[3Q?}"s -*zMrf0C^{_1$QyZ;u>l6"KL_~nPeccTX מPF!GUW-=U0tB7E-R[l.4e^ "! [Fm/+Dр^MQݿ⇿3Ȧkq9T._aJQ&yAI^ؠ/.h}$`-<>Mv>B8G.`]&5Z˳N]t=yG8W}j  %IAG#mFN+r䝖u1xeo qLO?9C\hG(ar}Gƀzx3]#c(\YOEumɸ{JF?ϊUIr%><dخCЛ>| /Auc V(K3+j4hO ѝ4/DOR}jyCϛ  -kVnNdhNp *v 0}n?gO|:WeܧVX7mcd|"efGJ-2$KQg zB_rb# SC؝{ 4O~%pTd,-]uu6 7$^m0bѸtMEm TO=c _*ضU o:m0㎲O Vk<B)mLv³CtK^5NTO\e9&x`TP (SH=d Kpv6A盎 NO%bB>%_mƊh32TqcN-jOT[NSW ~+#>!UAPWy#@R]Or)DE1v7~OU7]+VÛˉV9&\0   Ü:.=OZ$cckTk~1"U Bb4r|%Yg!Ywo=O\-Vf687':jTgkb""/.Q*]?i_XӅ]u"@_OƔ`SdRQ**Ź *qmm(TksAE7D}H\/F 'K `/8>d@x)? qE: a$ZדusFH ͍]J i>ĭT?.|p(V`b!FELKv¯mDgdYE_#?k3O'DRnh׬G{WHEJdy7.AIwcX ?}P";旣$dI0I F@:tIAH"Y~Z!%r!rk͸n8"_ c q~M" 䘰sh]8*H<O`܂ <H\}%c޺{ȰXhjS #/j4t@ml`dQn\ ru8 7͢iW')~Q;͞ ukXt@yЭW[G^/G u7x:B%cm7P&pt  J4*ta3?_t>f{|[x;욷ãqo,dKŜ_DJkq}|Fk *.8+Ek6'7/J*wz.Q XKWPɲg5BNbprQB$ [7RIZ@xrW;?._.j Ftxjtx4nbzA1@2Ӯ4i@F@W'QV)-!Djsaϫoz;B ʄj+G>8uT kZAw_rx6]G}hG7=zWi|0zHzÝ`WY/%L]Èrj3ڜ^Eb 4MhQhن)j-Z6OaƂI׏:x_e,S Ü)p `88PRĉdBBsVfEšAP^d!] ݯ#/B}'Ӵ]xJIp@DˊiHZ Ae#l) I'fņZymגa@U4,љbChԕ՞T{@wx<  FS 1Kl)G3k2$<ϒ7t8j''5c[ODChQd7XG0邶HfH B,t2* { lN.ɾ0h4N#IwpW5 g@K#lȗ3Ww>Ȕš Y̙~[ +ӈ 1D3 }=S#&8 !*X](8Dsu>@5r65OMPXz<`G+ =KP7?S^~fZک0uq ?+ST0 Dm*lCYl]4L0fo?+}~IxJ\Iʪ:L?ez{Sue"{#T/^>sd`TF8Ŀ%r.p6)sd[_!LH a" 5>!bzpgL`~v0׼o14։Y=j`ڎINl.6MhBos-b ~sܐrk.^M?az>|q;vZs7"(8])T5Z hShe|#9K=ggCeDz;z2[~qkoE!ӯ.a, dXzffԽԥ\{N]}+}}U8QEI7Ƴ|5`LY{ʴ 2GrYr+!B>&G?Z\MX{|0B:ogDPP%~uhwd&dʋsQ\=77j)y ' \V=$M,(ZkpG5PV"Y]l]!{gեdDrl/=AYڊ=ղPɣ?zN #:.]GqS8?aFYR ?j\'t⾤^u1 H1 ܚ;NsnYb2=|/Rlnb,)^9p@2ɴ 92뻽%ʕ9 ]E"l%eԠyK_LvDWiW4sOD(0Kjp!F|.<($V2YʄM%}j$,u&@ '@,QibyƌE}ѯ$3k"H&&aiJ<*,, 5rZ%?txUTPvCshyi̬qkV*vIZެaK.) Fb+SK ex"]kͅeUa|L`irA%1kA;IfJt;JtPi>,0J}Nמ mM G6|$x[Z$>AYAL:fGz{._ dM&e+|dGNBi -ޞM& ?ǝemZvt>M]ujcZ4u?~ ;FE s1)n;T"ǡB݊TɡChAŰqؗ1w'A|y ec6R VEԘGFyx*BMEI̪$G#Mӈ: ,*1{Ia٣qLcE† =>?ɥ˶Dڷ:#AY`Ў{0x>H~pj6Kg5 ³yf2J 74l)^UIę\>5i^YG 8tOɝӗт$U~뱮$f/+wYAaYj;*&0wG% {/^ ߸f7_,,޹'#:}j"zF۴Y 42VѶfDh7C:5cMR2N UNB~WFb1>5d{'Hҏ)Bs&%,`~)dZ_B+KIO;lȷ T X^; Av߃Ɛ"2+6Dn[qA3uc[F08a;WOM*yC!8Vy2U5"3t֌kA7H)>S>}]/e-[ZmôݾK:jF yK;qc@g>B&R j HH@NcoI3{QM##^ҽyMEk? ( ffdށm^iiK&`҃ХsNyp/Ҏh$ Vo 8f4HVT E(tV]{n1.O3jo#6U[zD;t՞]#V,pVZ~75!åֺ< R%KŎ,‚a tdӿ8sLoJ1V˴*ʌ[2Z>4\9hp@h*\&@ꐳx.[Whv<u1L3:I8@^"[11ϿC.2,A{ =y XBG/} `o9o|˹GX=y Z{T琢W|] JhOv1<ބooyۑ~i)(v)wȐ2Sm)S_1۔ye#DW:q#[Y?DK],"aӟ0w*mdžmeƨ`TY0{u[- ?}Dz#Fr)$(R5N{\ɧ&kr賊)\M'Y " ѐ@'GR?EϦbgwSŬKI#IS4Ym2 /pЍ= ,!'_XuwYvUꋈV}dW}\o8-#%t#Zr@P f0gF38?% Di`yV\gc ]sfk>"ӅR93RBzN:P͓ejt&ȥ$;m1QVġܱ'Rs P|`m0 *ֵ9K^g!8I/Ѥ4G^CIDDiW]P/֑\``R9.O#*,-/Uv"r.:|OF v?oer3}sԱ9dˬ0JHzÈ e8fT\܌NR|t&/E!A˙_v^ lBd#o_;R`?\q4jb` 7i9 QBp[T͂)gp!m! Is68K__-54 3'l|/2W!q0‰u3Wm췻|<]4YG5T71u|>lgYf߽V!$˿S\\NM&a~([a\atqwBNMpPpV05|94p:L1hFoPQlH -nzdn%eŬmCytƵJ"$\'mBH֑Jqf:cM:FINRp.`1&(X T6w s|#hNs$pFĤ.ȥ-k.zƙ\*eʎ K+<;&40|e%4]zE*8R1EDJ9߁RO?/9a fg.*1߄S/O)g6%ީ4V,Ox`̗4("*trPG˜\?Mi*H vN.LKX3"$AaWgTWvUyF֞#Us}Wfj6̓Ux]eؤr 5MKX5JeGю绍 K$rz*8p)r3Azy7[T&x"x1[^V-NMYq ^ba z1YEL* 1l"v5L_P4(tolK| w.H(벗7J BZ|, .Iָ=X]#ئ{x5Ţ^"brKngxp2)}?7uxf =gP;th t?2-sMu@ʛX21ap)1+ p-񕞭$G]Ɩӿo'@1!rgtj d[GD5y֎]00x}$GfGTհ.JDW /vINQg:IP++uT(_ܪKA\\h7i#{^BӥJWȥO*5c~n♫eb1GM6VV9Uz̤M3ŵ4Pe)SgYx8F^ h*˅t~;I*žGϫԝҳs{-v5y`^f#FHK&c5ۓ[NJ9БWd!2VCjDy'ONaK+0ts<` 0kkf8Ǯhz럭guF%uM.w/rޠODqr[QycV 4 [XkWdGA|(/o|tΟnEVYSӊ.% Y&D @Q^muNh_fs^yBrCgn.t`p)B6<'3V JL>r\;Z7m׆javeB95!ĝ_yփ*%fqEB=w |hz}*;4[. h\ z8[aA}{IAB"`BRd>!'\̓}^.S~AeT]^J=`j2PO!.@qyp7F a}'C"|LFCrYs3$Hb^G%FBB-8p 6 }YA7Rљ!&Z]r!H QǜPjB̤ $y) }P9qFҞՈ!e^KYvL=.C;EBM3ӈLå)t^=j14S9Is0*o \"{Qr7O5itBj:散 P'@ĂR7 s,V.is`hEmU>SgGfu RP?ˆȌ)tBk'I+mMؤ7.g;[`KA,\eeA^,.aIvCEp.,;gU0 Ưpw GTd\ẒNl.:'KdRSx3+X`3^AﹶfA'R-]Ch>iuu#يt 8,ST>0t eW01ץ[nS=R" &),G+̟9H}A X튁z90;q7wmЁ]pSǒ:gO*q\I^[K]r /lj#eDC $LgϦ-O)} gq~~mW,gH^Ad"E] Kf& '7Ug&Αb]f R7buo ^w4˂x8f.?mV/;1zAqv]HL#?yeW7V*$Ai׹nZ 1OQupb+ bҰ9Rhq΢> 5sE3wKp/zp;*sE?pQGrF3g<}r;&,_C6&ܰ0N FP~pRLpsQB*,BN!y/Ƽ4 -=ǚo!Y T&R#ѥzJ^@7Rqԅ>:`g74@͟<ْ ,ßdysr ,!rw5Kڝfn#8#(^nO޲pGͿ[<*vT}g]HW%t^AyVy~&!ve3P Tɳs:y T ⁌f32 )ĕfkgg -Ҧz"AG:(|l1j'߄4&q{ߥ3>c++~n7t3^a;Tq;m]Z&%mPf+q4?u ~Qz<8`.@Ae֖V0J;6gi{ӓEaHy@~@+A3v{זrS({@rUTm1}go؆@E/3vr5 ޖqQoYQT Qc͙ a$^MMq+PH&[ΊW&tbp3ǹka&Rz# u+d H:)KEc 2=Q$@+3 E*Z$0^Y\WeEJSb.!HWUPa}\;6Փa(>svM)5ҷc~XY0W;elI1%GTܠXGvk74. U {#@VmX:[8XԜ`m0^3_/䆤)9#SN7F>5J"[ᢣMմL<Z+4jCxV/ j&$w 7f¾~ kVc(LiNA԰Ӳ  ʤuPZ˞—-O;,5y|.گ=Cƙ306DSLlP̐uJ -)I.IYnSx\W xݳ~OA=L#_-d{a{^A5T&Dc4v uA&$qyKl>Lyb[~~ޗy]ڛ8 5x1])4 fd@\hBx Ư:@ WhDw(|EbQ(J5E zD]@'F=UQ]|BHXG=9~5Cv&eYrSS"mV{/.`u۬5qTﮞ[5th3XG>#'d~yk~rj\)=0 V]T)])ґQ ߣG)q:^QnxƬrYH!x"%[ RU}- ڜ,ɵyv׋ -{K1)֐x=hssq iN^b^c%(;Ċs+UY #f6|gر>7~>#;gHiEӋ]ߡ񧒠D+mWO1#Ī=Be}vvo% !s+i:çfe5R ϝ^D^)P?PzEByL1TaVSH Db`$>[X,{q0gj]v;nf(J|坌MagKh;M* H[,XY9MtJ EYqBNlYy1; '-Ex/DҌT4wxmN5󉸜SҼ&!ز] >%##LϟՋ0?0v۰,dXw  ۛ Mh9њN)W -.ӟ$NvA(ĉq?% 4 ^005_ +$XOS&{L:[טln{z9;">#wY7u%kT.;3m|fZ-YA/'4+sߦ!R1R^KASG @s$%iJYg!eL|?g3k$|.G14}*S kEX&_Bi6\ Z|lݬӑdkWE=3tc.6A?SLzA,i[wW;X|MK]:ho1 lW3_V%TiQo¿{9 !?Р/UP0mv"&*"=޿P&J-yMCN:m 7 ܹmaތ>BˆQ{}>ؽؒ_Ƭ@Yt.AEl۹η^Jst[whE2TY.9 BIHw`d}_N\KۗG˴|^:6R6%KQ8NH19tL׻q j}Bϩ+p|Nhsс%ڻ;{/<6WFuw!DYQ`<*Y.bF&VW!D(p2 VG!Y d%?B2ӣ oR8h9ux\m 4ڈPSQыn' 69]cTԑ !v+.wkڜe8 &q몄H AՑ-:'>= 8X? ,}NG6)7fO9@iro䖺[etya6Frk!Ëyi ~X?c9 OAYƞ1YcqjƞhQ3U,`)}yQ)HGҚÕbt˧s=#5Nv 4M%.Ry))rH`OBS V].)tbpg[c#Xr6͛bΚ:hu-q?"ەjYqph&uY03 CA,**.GIÆD ӻW< *BQ2녢DcI6kkr''BJEL=y7et5CC+*W7Uz 1XpZ8T5E; 3e$QFunS$7#*\N, 9&KNC+=%ˈlxK-6B}:3Mr2X9,6S/ A-d( n:.*icԪ4ܽ3e'0fiO)[8fλ(;\Zg` `uncAl/LzѭlUhVyT:@rsM5vmԵ5t 5vٜ ,`|\?4Uj{ZTc31j%`_;rRvFR&e5%;z;IZZ z_ϛQZɆp #>e]h"UbQ}4! öTVE sQZpy?}2ha K)"ǏhЁ ٤.N*N4AK4/<߉B$ G`U+x>$ʸ֜Rsrמy "DIDg 37"m[/Ŀ&^#W$I<~[d\ -ֵ/|صMFܝq1MJ3J[Fcx3O%,P.+pqmp\+~!{t`Zo!%(:ukQ2.$ @r\q*?˕\jquU {9Y}?zej΃P^(2M0sCz1$P ݇U$C;Ru<-( [۲6U<4*EÉ =LТQjGU>b;Od9_ևJ#M7_:$3 Gd)ysga*X 1`t>J j .{y$Lq2|m4vc-kmn^iGUW5-!-.hG&* ;EAp{]q%o<3Ddhw&9Iheİ_v8YK'|I=@[>`Q?bQ_IK Mdxf CLGt5onGSXX]T8xЫZ7qx)_džj{7焧5G8e!!H {fG콞,cZm< p?G~ L͊A^>WЃIӗ`vH0M(aoRb y8QKǬrW(%tMJ]j DRNf}$!@3k!g糱O{r_^s! #sTHo!R&k꾬x"Oe~7D"񎒍 pp;7ʎhk+:,^`%Uaͯ2d\ 2WYBեݗQ0 քZG-~0z qH4qx֢m6^@6qII@,#53rE%r3ۦ<ww^+z g@%xe鋴&RqBiᦑ\iRHjֶ9Z8th)܀[ @\Y( 8j!j\;(<0zAz%qGgiܕ587`dh׾g쇛 sa\i)@Z3H@i*hPgeA* z ]u49i/t)Ȁ}Mh,y㘔]Y)5Ə~2@PG|w"m2uEYhbctɱ:zabim[ъ0T9 ަH]l=]gJ'sQڠ@U)McΨ9@O /^W<+(Gڿ>yjت8iQ } Z~*뱍NLɗčeV@ ):ECT'7~nk`iS&3.?ߚoЯǼ~CpjΪI>j2$!fT,]8# N0Pw>kTnC[ Wػ8ON8a]]75\ ө;v]%vv_?_̞\t3=;S,Lq8FXVkXkݷ<ʭb1q ^!5g<hcaxJl?hH6N{(Hf(\°ՠ5G j9?mҺ2|_ŭf| 07gR`F RN6-b ܃98.>Rc| }K̖wID.1cR@ :< oߩUP{s&?>Y@9 (s1!G&/1:)Lp ң4-3<^}6W>7^uzk{`Oؤ7vVv<]p$pt-s #5j "̛q^GG sM^HG zT{0-~ qCZ=H/c)Mj%h PsDVRef0cLf,po\qcX ^;=N'v'u{d~MoӋ5 6Y)>"G? 0 ݨÑ1{ 3AdG&PP 89B`>-(%J J؝Le-̿*M̳ F!ykFBN4o lĖM*"] l ;<ܔK0pkrI(AMEQ4K1E[ cQ߬DbwZAwf a#w<3gq[Z)UqG4y@%UVmH w3ʠ}ȭJ_] 9ĎV5caF댿rTsهkIKyî,* yB!O=y??U,,2Udۤq3U(g sB+BG 8٤"-h]&E>ҡӌŁӺz>Iղ@wz")|I<\يTaa5?=;I"dP\*Ծ^d)?Cdr9yZvv |AW!"8bןmq= N}\oo V9f<% ƴ?oq̅qwR&(Kn(%ʺ˼q= .}1I"oSeg 08D)W *H3)L@@#k_#_]~ceiZ(eocl_6QWLղ nvko֙;!Okbz¸{^0K_.1<=}A-[-N}wNi7=ebhkU1eivkZ1GY ItWf;[i[k9asWQᡌ!7K> 싄{$TIkVͽ4qƳAWp"Q- YW*G=}%!}p*G\vb+! D0'LMU0fY EΚ?x!m ߣfNQ1$2W>YE.Bm[PbYW _4Pt:Ɂ6ZA҄24W^Eyz+ү_oDx'yb6+|觶g/'*7-U$N179Mfju =iǜC"SX۪gMFE[y)n3HQXJ^CU%od` wY45C6lv.2.裭~XM'4h|UsOKGߧ ^gtNנz%[Ё:[rt2尊/bNEjkh"~; [v~Q%,WۦXD>&7Ff]IxLt:^yӤ7h(!' ' # .ii4@,(o?gk0la슈%?vEd&zc1ZӫrOM-UgTGRp:.ʖȆaߞΟV;f IrCfa]NBc .Kǀaz˕ӄ59j cUUQn&; jQDžAvYd,m0wӷ|K-哺*uX*ʓVD-$ȡn$>{KbeC߃K 9C9r:=K^|bV@v``PxfQްGֈм"|<\??L,Xal Ң26%1q!JY\ ":=rZ%n$_ |&;_õ1Z֣KkW>ЮZtgk> 4[?pm|N6YKDnNEW+|/%FLO hT]#Υ f5Jwm(i[ "8̘Z@I7(>& he_Z+!ǻr>kͲ4վkP"7Ǚi.RS2EYO7QK`b-pGmjҶ-^椥{(,.D&1IY<7.T-v|8r Lm<qT]xֶ;vyW C\S@k&|S PWѤljBİft>Q0*]*v%GV*_t|JR@rSŦzx^loL|@cAhGٝ6krl-ĞXW*CHv9n U0 _2+h6v-l5Da3у]#J%*|BV3!嬚c;L CyHP8qDWŚ=n_xo{$tk >_3 9T2ݤjǃ=$-PJmYsvywBҶ h,oF;BL5^ D.J;)s|v'jD+v49sh#A7x)>7,mf |@2*d~VG>0\D*]I͐4cS (Q[YZxe!,w-W'u޵LoLkvK851#Ρ#OYY!x{鸮ܲ`VsuMT^eC=*lj^`i&ZT_L 95LdKE UJS9%&e;#؜{Co1TI J?u_N ޒzK&= >a@z$ Zӻ9&>v;D꽢ysԀ i:Pg8 B80+ Vh}i$/9=mH5q_2]ƸߩA_6ǩg8f ^ʁ;Z"4_9Y KzxΒeoarרWy;$Z3'aRl:m/ &t4VM 2 RhylM/oUAKrziU0G &p_@cEM含nsƸ/R\f(O!̰4QA 5v) ]p!@-ǎ SF52,ۋ'; J[>8 ܷ1]4N=1h/q8x/<20*Z lH+TKiyE/{ZsEπj`r/A0Bx0k}6`Eۺ& !}n`KycDEfokipPR918azs*pnrkcbQě*umE|KȰ@&%Ű>D{ae͂'+ pʊxQMFP1l: bGE(TQ[g!2805~Noͣ@H#a ݗ.#ЇDa,7 j]sf4 |wCy*D _cnaHzQе@Ejڹ=DFèQ4 |6B&DJٕU~*T"h*fRw _Pu3ZX# r򖕰~lT;M'r]&ef^{AAEM\~i;WH|@k[B3mv4"̨ĔP%lQ$}YVj}fHCJ[ i|]_ݮ֍.0:a!^@@9WX5*?Q_He)WxϢYlelZU4T!O nG@<;M_ -)}ߠK?A^EP]ݠ~FT&tQ4 )w:gΔkϬtrl̸Aa/)M0SC*ҫI )D#IS_6X~Ҳ¶UM+Ip`N@T^ge(Sd"tX Bo9uVsW)`B푘ll^Y Ƭ˔j^?)mXHOWc,xBU$cB?g~ȐLr*$gprnr>Ճ i$r}7ІysjK}[݆ TJGעN vjsR   :MI{xSIkA#~ %TLjA&1RS%+b3 E-նe:H*VK2B(>7rF&qhVKp~UiVTwl0$N.aFB:*Wd߮w' 01L7S1 4j6prg?'K&i0whv-{ؔޭ~4?$-=ك4*~6ozuu/[T"ܔUJr2A[q]+2l"~Jb,oyϤd)@JJDH3pV/⩹lM@+@%3nƱ!c [i32T`rjZ YĪD|GS|Q-X44PDfi>uଛQSr73\QZVY]T&>=/njvZ$qtr|lf62* W@YR~ݯ Z;фoV\Nf{ o%&ߠ6ҦX<| %j\KYߺmW/)~rR/ea"&VaO 𛒪S@Fw"w͙QQtWzEi,;@QE~l `okDžlu!h#S ]50dJ(#2׫lT$fF^GPs8kᖛ?pOg~Vay7 `avFI5zk9'-ʎ*ٕi`PWa 15j*l?t̍ݴ!ʥ׉ux_03a!fg5R19";KʨRegA!OWB-* xRAuWZ"H1miiǦq٨_46c5ߥvDlFINdH>v"/ za)1Q{ܖsYfa^BOq ZfИUۦovQ Aeuj{zgbO&:KSh3 rxisA{ tzGhNk/;n7k4ņ21ktχ`䖼NtTuJ#Y7KQv(Ė=B.]!0>*'=F߆F{ d.Q/-TvU-p}rsj1f?iy2N yjp8P/]ۅglO\+smB\=kn"٘dɞƉg:~fiOk YMr #RHIN^u!P.A$38ǘWGo"axUu.yeQNn!7(LM@B?s:ǽ?|v5dشƁݾ!!#k }-ڮh%+- 671 nbdQ"P+)YO> (\s#DXw"mt *uOER*+`wzۡ_/m)(cbKKS 5Ra X4X`fH#dbN.`w9 ~PߘGK׻OG/4>"Ȩ}҃pk8t@JD|yۀ4 B^Iߵ?[L5HD]0K樧5l$ⷌ̓SQp݆91&ɟ}ULe6R1Nd^0᳉T [|OXfSs҄fJ_LW(\|[_dMj*&헬睶1߭IH!ZaGuT(WA\F7MwW%ӗhcDqc% M<:J K!e+<[zP *uN>!gv%d.Õ`onv֯I5~TXM}~#I¯/;B/ˆ| .u9rP =T$F%z"oQoW$w9 Ǖm[1ׂZ<|G0TTqHM}.黿%!Fvs%,O*?"-qO6`a岮A ;-՛n `+/$6i) ЦJG*q~Vq )'y}o멱 P,? ~??R)Ysdqm6X*yE"\4.3~o7=z4y#Z`x=Ȏ0!BI*m7PQš-vPlݍ iHҎAˇrR^PQߧHḱV=7m:F ɮ2Y{McG랩qa2TM2}(I(p!sujD!R>tX}H%E -kO,wCϽ& ,#D BAy< * rjWTwgpܿllPpw3IaCE'Kg "&X4Gcqx424,86=#& 6dla5B\%tx5ص5!-LL;]YB4S%,ks2C#`Coni~[npe1b: r*^cAy'QJ^A;fP``o< CݵO|ݷ쏥J.V a8š&8ȳ XM!Q"lySUIBq ^ 4TLU}q1$+ʹ?6c<(A77so=JE|۷ J A\p` >2ˈ;2.hݿAǖIq{Q->{cY|\ ^či`H830eJ\/< EzcC0+%_`aUG2Fv5Mɜ)`sHX] X퇴Ԇ*XdpZ\˟*:cm#FUf1_lA,[-R~ҖrY Cr[KucGg[MjY&ќjְVjS"mr32z9JlބKffrFRGLGWߢh Z%RIs^|bY*a^HV IH0+Xk!FGϓdZǮAW̄Ր%&H^QT3<[="#:,gbzBC9s"tTژ}9WK'iP3'y2E%!w6R3s/m;IOZ6 ?&brlP ]8T q;îuH^"g1 ^L-DTI*X(?8SSf^ZkT-NBT7QRS6{5< "š$\3oOGr#5:xfLrf`rfͳй5aNbo`Wr  /Hc,\=^=JVU>4+sį5x &|"day{Sv$wFgOCdmwuc*oNċ qxedt$ =ѿ-7fK  Pd#:Tqs ڱ_>1F2 (d @<\S@5ttn8%݀-63(\11Hȴf@QocSfիdف#k8 %&{p3/xч[ 5([H_'w糚18s64c,BO"tذtٞ"A:5feב0)'02Ѹ [Ңh=IGmגΐRM~EpEy!Jazw]Tl$P! ~\<һVj'[ENgLsY%zRG&q37,bۮȁLʍ:iD Z6ۆz?e K~ʹåA#6wYW,v{witdQ`b4fp @U4Y gRU*g nN(zc:ݙPfč5cr' L|`so xF~Ӫ42T2F}Js09m`.ٚv:еPOhTO.|\ Hemb[şnT.H*@_4iu#HSC}'l|`&P~,An!{܋Ut!+kRg_dORQxpw捀I)}TTk'j?f0}SU {tK֍Ñ,Xs xcFtj8`!ê%˘Aݘ>_{id}dUdG>AH1-Y݅2tJh*zlԲ w }{\]t2vF<# E$~C5\'0'$vy8t̘Umx9%٫ FJ05W,XaݒP}h R)xAkCs:A Q7C ">eZ_PT=dEg`:~ĹU4n;J/""9] $]4o/ RNlXv*_qcȇ{-akG1x:C+$Uז%;Pp~5l*GiaE r%)V WҜɓ ߐo<-AEYdsk`?V 0/`<$3vwA2 ZG5N4T#h\M 4918\hn6fԬbj7RE9 -^1*Pi]N܆"P#ҿԅȦa '.79[  e-sub=1$]-[5[+A4&$pˉjpS7Sr -r"L6rt@'! )s捄bDsǦr]A2_fL&Qcz$~*)uQkSx$wv y[g6O&)-qcoI*ɱTR_})\W-QɊd2Kev7ug߀hoi. W)SJ$ V!K= ]abu3SqZ|^5?_'2rzNtBLw^arԤyj ЦYZbjsv, 2$ eOK"nD{dn9x[ ~g)P㧿!ΰ!yF-fidâTTAu=nig^5QfikD\grx/_s(oi;cq o|5 B:]whTUŒݛLK*ݖM9 {@qnӜyo@23~.D$oppt[_3=jnMG)uo[C @oVi)Vkt&6̡Ne7ጳyN mV,8}ON+ñ3@BkaϨ e\%*+t?FLY ?PaS[cĪK4wPUב軝(fZKoR'7VSirG&Z 20<({-sm "͹})]}a<%z셹oj]>3o&6TU?XqDp7̃Ê)* GdH뉉!ҺzC $ 5;f\ENen_X\u$4?IQs)om(/KRW t1P ]*i x9?9;#Ӏ%9JA]nOv]kt^^ *Q;I L2OBj >\VH)o[z;,By%`Wp *d=@&z;[}r mswx[nFLXh8M> o/i _`C-̈^S]9:"ON1g躷n!(%E,:l{:`GKuv˄IohH= "u {w\]8dre#۳ ]6yYyYvp-I#hc ❦6N&3- \[M.nOf]}wX-AAUPlE8H蟈כB{ ؗڹNs3n!urjŞƆN$$_uZpoehs̾C^Q7d" *~/wǗO}VVx$L}d-= dK̈́[Hf[&8U8rcnc]% DG{~˝##k6dOa?#"=>Vv~IYoE *晾&#XOmmRCجO-y|9U-(Ł;6'<$ B#Pu([zyFkgQQ嘶nr#_e:YT z,c_d(Xf,T(qNvV `{n%qOS|"b]"XC7Cr'D8:b j]Xb~*ھ](Ŷ:]!8Jd frF__K43!·MFemG\xi;Xĩ!54_Թl9ʩMΤрD E_`O@qr^f@i"ʆ j{@g4E5Q|OCj1}%/?u-]юe3Jd(n<;)no,h;I=/oА\҂|}PDʊS|pS7 U!!wsgfٯ} NKPݷ ۢh䒪j$ti]Vcੜ TR﻾Hh:IЦ59ިs860OTwG=ݙZwAiJ0aV,. 5mFAhhWklNb"t w{&CWCP+]4gq:ėR}^^)5;v"S~/S~֎S]݌ݿn: 9&kz䍙Xe&g[B 3 T {67"WŽ_a6CAom^hth* IC`Dבk4hu*`Umݾ0_O͡\9 D94ؐ|L3UP]-J[W9AGHi{ d\ ceSgjHS,[|Cϕ<|Rh) {mYp+ݵVɼ{L& f_6:wYbsA6ZTM\w=̙R j"VQ(CHQ(O\p`ߝI fki{7A-f'So ˁǑQ_%=Lw x89{\c-A= 8p+yPuBwF\QmtnڅT# *z->5},Z@67Nؒi).2hBXxiz7yթ崝Oϳne/woJ0jveRn|\'2yqGB+|0a@ ^ӧeͿ* zxGue윁`E\(_X?vV"ڈgGZ]rn9۞ZᏝ(+!%$aMYe}K_됼Bg5ݦdpN; Ya#4%ۓќp77bX6C22NbBi`H =jHmU||pQ"-̚:B약$ZΌGk/K57X dg#jiWi 0_ˢ|N=nˍN*sDbwcmUd)9cR̸]U{~krT^TZ;+c jH.CRdx 5Qdܻ;pi&l`0K\5k}#E&?-y2w*q6ceaBAeֹ-0B12h_1ϩxNAtJ*1G %2<1e-TqG RSGAnЃ˫;diYQ&y:(հˆh}O2셷DA^f/FG7>K_>rLAyq 5څgheB+5+C |Sѳ&$M#-êw *>ڻS#NkigOد5d,Y9:Qr’=me{%/K #JzP$wX7_:罠N2x jhS˛'I‡M#硃#r-wfH$6Z ۳kD(Mqxbi֏_We4b- _^Fu3kJ)~kc #ʽn^$7Gj-~ۭI7~ <œy#6JrWP0jt&)~ Vf<4K".,8dM)Xo'PͤzkܳC bӮuNYH?HD#Py~EO ?My3.H+F͚2׽yblάScY蝡XGhR:k~yKa6Uj=e;\\ ` J"[ In֭(v O,G)32i,zI0氍iJpNxYͬ;lBZpzƊI=Jx7nK|`em_V$NR"FvXdpYVI ECb jY;o2:DQ3/ì!(d)eoQ|ۈJ 9w\u9 /pǷQs% Ul3Ϯc]m~.lmplafHC;BvLSw!6KP$#f )ZF=@90FHoqڮBQXEE$NrDyyF7C̎έ}9tgrՅdCTfσ_*Sف+t0e>QTzW0&#mh2njnӍܟC SoC t 7hթ[ΘJ x t*hދ *t|ۖenkglܚ\]?7ӈ1O]?FÔ;ԚPsQhO"4 ][J uFF58؈]l5q? ѽŘ@|ܬu؝aRO"9V_=NNs.P)q(XZ E}%^3.#"KMӋ|t#JH[B7_mq ,g4eJ&5r+06oiH lYcwi;"c2.p(4D l:YGfk$Q!C6dLyt:\Sawm,jيTRfz?2U/4Vre\ɳRvGϲCؙ6{ɩ" X 1x)R!Ptp{[~0(dr2ֆ*p5|S3p8PϳI;..om 9#yL-Z. FGg1c,UM9t̢uf`ݚ ? \$$.s ~v3!b$([%Y1v<\>e$4eX?Rx٧c~U3`'""ѧ72c Gɏ 9"?Y[k|7YV8\1uCuɀ3U:3gSqY_m]v'sX|F^|>l 6SէDOdaY dn'}&LpivU>˷<7r|rl>#r(RflŴ> e*ԋGֆU%pT༫'C\"Rx>~)r"b!g?jytr8 a˴dMXv gS@Ɠ{ R$n:FEE(H7'8[ kιԟɓ2~F݆oJD{8aX1a4lsS}UҞ)|oYlM^˂][2nܴSN>Z xbyYdF!ˍ&u,*`ΉQ*/XE;EWy% Ė,}ġm*?΀@-]\;1 luc^7GZ2x9S|-XˌyaA%Ѹ1ЋZS(d!t&iGնJ%.lq*\ }; Y7n2H>*gu,]X<60x}HenD߻iN7uhPIgL8D9 ujOI6j1PfcI1Cuu`](0] J~|rd`3;niɜ7~C6kf)4|qKK<ZEmsq\UlVF9|g,b,C,J7fkr k~a$$=Y1;[M~Gi@L `U~⏐:KjMRz{'m&: >ϮQخC;ЀeTm@iYxLP "d g)XĚtk> ܗ}ldN!Uc.RPҲ@ihF jVKL'jx4pHU_ uEN&N:;vyG]*.:r780(J,Z1>V ڏi52a93|GF/K&+ wHL}Za{*2 _}IiIT@?ݰ;mPks%[)GUOx/.Ss쏌ST^xE`ߑ}]J55idCU JgcCvDȊM p|C+hIώ8kĬNcoaL?vyMl] `8d3;4"k m>it<8O)Y $j6HًQ8KɋT#I`mF2fyo8N*)ZdK$+06J4F>ZnZk8.rIlF+[}9@qrw+-tP[B`+'괡1 DᚮֽX]t25޵($mPǥnၨ*IA9M> c:^G⧍aü|84!pOr+kd~$#x,-KSf"93uϧ Xȅ AƋu9`-j:/L:D}п+ ޛ/FBp w5fq/盟ƃoGXD2 ; C赒y# :&Wd2Gu2͝|˛U1~;aZ:ؙdVn^iOaz(1pip+9uMBU a@ h_ZzeQɆ&-t: @>SC9EkfD' C Ӷ;R`7K_Y CRDGC+ rw .bcN=*ԭ(]~xb` Jmu*rT*l,,= j^{X!R -#S%Q/y:|pP 'YcMbm]n9.Tv?V0}Q2L+!#x{\Qd^094 3T](c㮸 gW$/K?_\d}ɇP_NTb/I3EM :fΧ|ljK͊Gp&[BG&0U>y" ;L04|ܡ0E%H>_F~Ĉ&:>@c0ڝ}zȾJ9p%c s<.q= ٶƽkzU$|ꓤC֟h_` {r"Xxc^Wy:*K]fJKN\Ӌ ,K>Δ̋6tܛK) 6tel\yY|avf>c){SOc8^,l9ӇNcQިYbݿÆ-ozG#wewPāЦuM&ikϙ;82"53Vt:%E~^YvEJPZ N{a8{0P3z2FZ)2 !̵TL&i :XoiazO:d/?Jm-=[CFgKة]&M0xڙ:VvYRbBN#WR%r\fo+a,<ЅnV(qrB{(z};FYa3bMD? @*Ma KGR@}1ܬddcӥ#{zR'8>$VVE1Ȅ:\@9SY/37 ,>+N37筿"u0|@@3A!*S;8.)Oh t<$g!V<%H餬q12VphN슢^v (4QTeM8hE5gD6n?{)+cfU/j(򞔡̽گ{}K^UKi TJ\si;dGkSw&]|EJu:~ iQL;.9ƸKd(fXuf\yD #ےoӼ|X+IyͿa-{$ZGo*o˵]ȶp#/|m~&U"V !=mdjΦbxp[/Y{: '*&RaB,wIvMw?#JF dC+R ;TTGx!^M]MW5XTou̷+)8|kJd=jk帇[HXH͐ EDǑ֕]>n43bl*εuտgSٚ5S3yR̨M|} [Ol6[=Zo= \~:k/UѡP'4 0JGJd]:bOG":eZz?E8vN4E1Tl*\n-][CFݚo[;釮Xuzk5 9<'% Ȇ|E*"9O`hC+qfZ1++ÃD~Q˝))0 z,;frŶ{']DyRNe[R-բdѮt˴QGKAF$Ԉ(v% j<3㯅tdLt'閺|")SYd B<&ˋ7࿏4n;sD4S3ҹwEJ ڴ 4l]ވ埖CsvVtZbsrz].m= lPhi/>6ћ@i/"E,Z\yVH|udG Xݓ^d B|EQVwR KM~^êCDq"R68UlllfIE#E}bY`Dzܜ">s5wx u6'uzgjNw.mępgUըN6iS- >LDVf0:kdSM*/`¢OʅZ9)\-y.?$i5\A#B llKE'b8‚Ig%-qlMTUeLҾ赩-5uQ5I$f.⠎ FC%y5gt>~)ຍt9jӚ #ukk uI?&ҿ~mVfo"%PN=u|dKqs6sENHGdr #Mx ^:o՚g|J[Ě1TipTFEW{ܯ ~${Ū?O,j\j+?+,]HtoCAOxEk!$ c.*?|TQL(AX,%Y O?\S ~Q7j_LE5^M vt>eD/C&f=ŢQ3lY'j׈Adq#>}bϦ;C78''#?db:]07-Zu&Le\!eyȐfb:T @Wp\Vyqi%#yH G3" XqDO6"%~ݰSQ;ͣE|#Zn93@ veWyx p4^Ȝ"7%uSK5[{}fbDѽGH R.h٪Kb <zpNڲ9=x> רؙQJ> Ev왓%fmYc=)[eo? F\J.fDZ v)5frS'ΕrDFv6M*j9Yw;_+uc-i?<,V?fX!µ,] qQPaҭ&E*^/if_D)>*V3HQ- @0 qJ2Ygy^%;a\ie7t{@3[ZξhiyQ-ai97IE_Ck-!ޑ+]`ȭlYZ a{dm(Z uq顏4mb59wL `x%;"'=ht!w]pF|vD߾.J-X e&h$,2YC?):}I)wd9$0woq+ݴ(G,S:04k;mJ{}|LERJW:v)-\M xbEv? @mK8ߪ .p!XT{ED p;VP15,=`QܧmzU$\AO2UsGwUCݕy]B eBX;G嬮EDk^PL zqCzj*${jzJ>79WW}3 {7,2tBP!Z$( Uo qI|J0鏾zC7_-h .JS@K ZZM{cL5Ѧԯxt:7](]ZR߈ye]I;$qz})'|#<Š{se[`8kd%"!h݈z&@G6prRpc߸Y}Z1Y7kM`F”?ՃAPdQ{Hӄ oYu>$ (1CepQ,at@k_wT$ Z}:MS$iNP>@m}Qx[82Sf1Y ށ 'dR8} ?KI5 J/5u~:%Jh O1/MC9gs?2  m8Ҧ7C x>ەna'Bɲ:?y&.^0T &:ccGw,洫r.{.eE}1OH ONk^F1+0:2B䚸[\ii$˲~̀U8ِ' |^?;GFD-9G-{RfRkhA%;yߊ@s% 'KZ*';@xV=mY7P؋fq1R5;DR5+?fK7rF烰*gpyS)05f [.C Z7jXB$HF`뚐aH^ jĉQ: ю@wqٓNP?{_Is^}"O84u}'c%f7iT^1%hRfDZTJnᕕzSA\K˞W(tb>Ԃ0r;3M0>^s3Al枓_n8cCˏSf?RCOQT,.FJ?r@=KQQU^6G,4aztf-.Mjq WqHp`qIzU`+gZ^P:ic%2܀!%<i>BL.oTռga3x>vb>d~|ަ&#c`2 ;#Z[^i"]S .1IPÕ[ $wrQ)Ü}AODNl*ʧ'` :ծI*ㄠ9%j.?:mfOD\0hz~YqUg|Af4)ۚ뼰g5keꦾ^tb]*=~T>E`۪8xKɘ*!AyQ"Ȣub-Ȉx:RN~fk@2~Lb>.(IDySxPMu ӿX;.a!P$#4BQ4/UfBIVK*Ť'51h.7E~ " ?f(~X3W ԀXVZOr,JH4R,' aq^> 6B .L~4'O -d.nm>!UY?yzTo.oWF>_.mdׇ2.Bk0dҁ;"e|ys] }9IJXg6xSmfzgī0-HIJ9vP Q=qgwA};wWZoaŐ EyΡӶ&2Ϟfj /E>ij|AbLd(=u2ҿegC iqt06 h\(·:H\d #WϳDcP"†[Ufא99p[g'$'ng?MM4",#/U#@][,'q *ǜU`^kyMU|yS'&jTKjh8sPѨuuW.R2|Cx;kqiTŐʍr+^_+#B)DH*Pҕc#r.؞! +cv_D 9awxtS/ v\GalT2qKk`ē CzpQ_۩ۜL-`*> H$'bn2EG7 H SS^ mwfb;'9Se q"~]e2(8HoSDdgx^f6??Y]IȆ eY~D'WD'#zbؕ\ԒYOQH&hbN'Er x4|b+*5Ѡ%Ԡ hG8@YfO=\Ҫ7whؿ̎eyRZfgI0l1RF9Ĵs V $bhEfͺ)JScTthD,@3JL)Js+ c e'ҡO^jkNA\|wbѱ)6Q!N @Q1/PϜ(y XQ_l={G $[2bR~f/PBh#xEݸZS sx '\ȷִ"ȳhޏ@W(lZ@= l/"[nv^ %gԿ Z}~^n)8;gЈ+g3{RJ<~)=T.ǩveJ/5Io6yC~U K^3HRҬ:~9F6f0SբP`XeQ8.ܦ861=g@M.U6E#ٌՔPjao;2 M!,}|F?Vx07O8]ɴG c!unfp [9Ʉo\/TE!z*/" BJ?SEYؑT;~Â|.)-еV@ ޵gm}%ج}veg(_-eC-i]WV!\jjq6>.!HL9)SE ,۸||̶- (g 8_,R͑%/2m. E^ȗH]Wx|w\JȕZKJ^* EG򙐌‹Ѩ_}ӁDRn*:qUץ]D_InUfn ]Q>zVgh]CE@a`T.M5fDB)ÉA1i_IO[*Dߵ$PqL>zt/i-''?^Gm\u=9F C%8 U]<X(r&qHq;^y;,G;F%ߢй6-ev;)Ky'?qpNȳ/[-[*3ZBpUz"F>xwVB1 dYIE`kԔޙ)G|8G@vIxڰ9NlJRu|>=TObDsj Qox,~&0qjrLk(iʞ,Tkn9 Exl/)`(ܞ]: ;,09 }*~JA H9cE$Q0rQFmF۽ 16#v_tgtBťvdsS 8KbʆV<5;dɖC1h4oj1Q\&񜍩@ǓQIAW`7E qF[<'rXV:ggX![@jLTd%߀>~|g?g4F+NS:mPaeSBa+wZw!~fѵYOp\1"E[8n@ g*$ʄT񰶮kktI|=L-hD 0um-DRlӾPN-Q w{U;?` "]kC&Qw>h@ @C/;֕_XTG2Tc1WLhscH*k$)%&KTm=MIwWЎx80skdGzb'g,]Gq?DH^3GP3 7xxNbȽjxGĎsg쑥%uy*۠^ ypl>)j54N[*t?iN˻aH!4U+ƾ06L]:{`OZ!73IlG<~bF#p"#j/eɽq({>wd 7'0@# c'2he#4Om#{Ov ^L]C ]CfZ1+Szc:30910_(HSpLhG^uza<A7x BF}<ڤ.!]N4 )p;?Uhat}[aEky[Vl2  )c8fTS* |rmI@}lht6"MTר# ow+룲 B!?M }O_Ui>d^nG$O]ǞMVĕ_MoY|LW_qfux/lUbSNL&@1gg`9J>+)0P`ZE:I犑(;OGA wah#D*a-?oQ+vg 7Y,ތIׁfwvD4cPp\y7'Zs1ޢaߕo*~Qޣ@;~$o뚓IcU^\u~ Qt?ai%%䪠[;"N^ZE!88>ME%Ph]^N4ebavާ)9?]‹ ]ZJԤ'6|y׉ڢ-zg;GJՍQNAa d 0G3 rO1x䁥([fUՉ?.'%eZАetk`En\>'6rOhVzOmR),>*PZ\&hjg]2* څmeeAXܿϕb\Cg/ t/ s{-QfdWmfUmѷW,)E`6318[eeĔr> %BoG-M0$mu̠?N&!uBxgKTrB>V-[y%-${-t-}kg1w'+6C~ A MKkWm'Vd6Phu{)6t`y  Ҫ$H"DkqUݖ|{o?ce>HiwT`9wHSwv S]`sHvMhġfCyuS ?q2;u5uNEbYӭdcAJVO̼)&~1?1JJ#cf^(5+E"<5ⰳ'MfY{b$[$׫@]S'"FyEdx" 8/g7載3fΆVY6|Ө {kW^aIU8WF.AHGz^"z/eKZ "C:C&e.E3`H)iK`O \Xzr WQ [|wn7=,FҖ:' b_+nCYgׁUH%2js ' 4X tQǤùQFi)5l1NUi<uV0w0‚@oiX2lnM?j~u֊&j78 e#fo[O| ae'GmUEw?G5-{d.'O13jK[4ɽ_ <VFX>Xx9,)8Ae(fѪrKԉ'ܸT9__uv 0je t9\sQu@, /c|! *e1";EYi&#[uv/#o r]fc(Dkun0p$q-7߇2R $y9`<'72lNCNwud(GQ+,qٜWUL#Clh)lE:ˍ\NA'%!Glu*Vc8Fc׊gౡ? d*S%)<J Y="}t>Tp 'R5óXߏF4oUѭTx"WYRA-TaAV!,7$ <xEVyU6X7 w ɠ^M0Za]rBl\nwoͭ =G z9oH }C#E3r$I,дIv>TL +]YP^Ѻf2,(s3nbDL/S /*Elo_-PuGUapi8[NS/wa/ ~9C+ bsW|!|~ֶoj"?~]@]r_E , ~Ι y񵌺hEK;.t:4ltOqHyϯd>n#3[PjmvTMxvӪU0qQT=iKʀIРFܵbc#Z׎ 6AWqWjQ4cFH]CZ,4Oc8o+5 ,_Zo 0d{Xb܎/!u$e=s)eЕЋpKV' ӚvpطDmj{2liY31ٕ*,LW1Um*iށW4v„:lӒ4/`|%A/WߡԶ\Xڝ~ }\\WwIB@Ϣ<+N KB##ͮ12RAm{)tܶ) FJrkéݗ9r3'$< "^zQKqJr)nD78Z 9;bLu@UAx7{@5%-EU~rpɁNܦTrp1q7w 7E2Qc:o'WJl*`kgXy%vލN.#z4TF?{D9t|^'j;WJ~/qZw?;+nT{0j=̬ ] Jlϙ)3pܗoa6D Kwk"oyhQ8UFW`cN@@B= 'C4.| TS녣EcRN$t*,X EB!fDVז 61=0"+[}9-/޲>^Jă~ÊeEFח`Sҩ[Y`1{`Ee]Q԰:Ȍ%ĮgT{0%HAAa7>ᢕ$ (Эנd+Xr4~}152Cxn%k+Uo[\Rh%K;l-.@ηӛUqc^m\(u݋3XAzRC4#)?5_ii^粽e., zfJr=TegŒ]0J=$-4<${ +Ci-+IMH L)mJ1O]*+BO/x ժro`6=U Cy\(^M" ߉ih,xc:su|eH|&WXs\ d;\*G(i1r93=SD\e-~ij+D[?ʒ w[] xh&f^l[Pld2(33('A R "orU0ْ͠|j 1Ygtv?K9) s;neˮ{˭`ƊN-G-bF㓪9h.]e 8A魯?ƉULL^Mo:QE+Hf`'8pɺ?Β}V@u ̽_JImRF2q0FNrdYCۗ]dnV ޑ 4KX.֟ؤy?+Qc:Oz[3هXʔT%S^e/BPi)-*'{;l_bP]T>Knb<eDfm굕tS?r¢t颉a`oCv2SN֜bK㫎Xb1"B= CH)AD$[YlQzܶH7yy՘{Hгm%F9T71iw-lГɧ?.~l S, HGH0uQ1߆0+.MNqψ15'(ApOih{ !A e"?۽Lgᤌr=z`\hֵ"C>[&҅6 NjVzG@>) W{NӲ-_M IJ5Aguu!1oVJD|'L&k{4) L&U꒤MK,GL6P8LXH9560 ~et_  -̻ra/)k&5@SEHtHK\z:i8ozIt[]5JL- / Ze6%-7Zq&Q%qmyAY {`Ev P0/鲊Gp@G Kܰqeު%") J3&s{@ut8Bez3-u<'A52m^B!p!09 {48b+> TPۈF־- тg|:Q0z4%ѹoNZLR*ڲY`gP I5ܚ Z^ oYCك<> sXJ)J,Α8e9)9r#-6â4hMgQR̓nnϳ7jQ)A)ҧ^w\-v8cH̘!rY+O Vq[;6znr0Zژl!3 ϡ9!I~{݋)!ň2d!|Wxlύh JRnJXBN .daǮX֕El% ͐1.q)fey&Gu< <:*y>2 aAz$W|n;6z}\Nz-evI3?RVs@sNZ,{kP\L{o/U=)ZqOɈ_BeVr؛>RR:"2YAd\tMtN9Z.hfzA?=MvAX$aSˣJ}!ғu[lUI}e+f~qMD.?5!઻,% >2GMハA62pBCVШT -e$ҿrpՉ?%"a! ]bdjyLxn/z#9t`-Z@A%*154;>@1eLry WWL -EL:,wI"I'wTu08HtX,¼㪚}."؜PUQ[$-kxU˲~ L d7B%iٖ}[7aY9; 'U;Jz_WG!W@( =Jg۬dLU.tbY=Nݜ^ kVZ[Lt*"q Ϊ.{5qFff,({ð'Wr@rьڑʱm=׏s{yB 'n򣗕s~-@]ҍeI˛'v5[l Μ2J఻.]G!#]Of>;lm&5KN]T!T ?k~M(Ħ^{^G8Eӽ)ޭ;1tfpv5R{e΍wAqynNzj;9@?i i[VS]/*3D{p1p T o5(]о9ܿдx86}rν Tys3%Qevx5H6K ͚tB 0MXVRlqC6:>j4:.6xb"\Lu(6ȩ0!۴mC}YOtFJByje5LG_Q8o74evyg [gVӠVla 4w*,3hV#y81ILQ3(M:WJplu%? gSx1տ(ʜEQl$έHlelP΅B[DžJ#g|o(*v"bvԞSSLwq9'Rhǎl˝t}DdoekM7Z1VpG`fl2q׼rtTAĭp"c )}``nn9Vp _IysF9fw5 v\Ҳ$?ǙIٿ[t`j{uۃq?2V0t ?\bpjؽDᎴ cIu 0fƈ?vAV+KE}m;c?X3z}o>ѫ[1 T{-6!%cR~TXk?n )U6/6*m_ ~NczÛq{oGd;AJY2~̊n8ɒNۻI,d* u24F$"ږ/Y<X@@,(d͖¬Kr;g( (Hb#tkA Y@98*[( 3TMU}s cw:7_X߅ʗY1B"hGזM\$ Č[ @e6E= GOdasW,4ڣǑ!mЪ_EkqNGz}0a֥Ut'5zw/k.q[+}#:voJ7]ܵ)gEسC|5:"Y-AG*Ȱ;=Ę)F]2+~?&i-bd>: 爍Q"oGVph d,7/0W<%fx.bšx]Ur;g( ;y&a`M*0 rH3#kHX~kwCC8 "X|` $-#l\q}l^B!(>|S W[C}HZ faf(=V﫟vFv ñ2Ox%-j v$kl3WNng,  FnG ?vKu)I.s߿}USwK,TYŧP΢ Zb$FB8lꙦ>~G [HE,hQ~+B+6Yge7z3N͡&Uе3%9qUl/JҞ8biB Ve|ƞSܨ2iyauS ]Ps 6U)B&n; j }5̋Z\n,++Ȇ-'P;2j&߼^ZQ[ewuc&ۜyі8*sŅALMl҇O*b65YO)VGHEΎKŶfɏ^WQbuwaxh2q>]@>bv̮NȽ\+;u:be{V.ƚt$n ؆I]XӬ_ 5~NB3ޘV=o<3)TVۏ/ˇ|NXkY\Y̤34VnIbGdr)7?\VdU_#@]-11F¼:fN@i.VX$ܗ\n< .]mw@iبρ$6H& H>g!;\֤Q`L|/"l$r.iALin풴\?jȘhT\1[:pn $͇2oXS=Uy'\[>lx t6=H ̯}Ii/:Cʾgl[q&xH%}*(x Sw\jW.Cr# j9gH@ҳuP_^A߯;bn::Oz,e .U岝%FZ.TyxOJnU(s|oĠ)EfFX@&'hyF= 1 NPEm*s[1)P8TDs9%1Q e03gt(-NK-c Y?U3mrL%3'F f1RúAAڍR`4Eߖś쁆l#cMРQ c;ȣ5Pٷ@\+Bvu?*zۙ?]ª:S3$ξ/T3!P׎ SDX5Eޒ\wΌ[`+]ԾˡDjAJ Bxv@PheVfD`(#glqlD%*r\5haK*<&ɗꞃS2c{ 9(85u^{];N `$αޞ-Ih8 ~u!"Y%r~-O z>r[ИRL<ɩF>hkY{ցjQq/BU9'R~ԛ{: yVJASS?#@|:Y/LC6{ס+9(ec^pOU}>wӸ "¢r4Rdh'uf= )s\xfe]fRy&K DDZ,W ߜI?g]s{܍0:wU\p:W+7rueofaCd~i`9 9N ɀ= :UT^p ~&Э]Fwf©YU,ӛZ?T'J?1e(|Cn-Z4S̰B o[+ d"61]v3kۚuk"KL3\r-5Tu+G1 zZzS]mG+P9o| 5lj ƅny4)ɰŞLn jI{ؘ:MrbDz2M?zW ac=G]uP V!ySz LQ snHe'$` y- Q_,H93쒨<"fp6lhcuH~yjsȰ=$+6sy-1U%z(pݧ\a>p/ APYyݤ(<Ȑ _9+xπ߷}Q3 |_ *ԸQE{^B8WJh[, tk8CiͷOedD.Ont?ymGYy@<ۨev.l6/ -JvԔmǚ[1fEz +Ds\C"\<uGoIc3ȿP5' q?&\Vjw+XyA؅˘S;CDl <8"ΛyPR CX)PJ3 q/Ej"'ԺEM7YFP=O*\}GD2yR T:26k+'zZvmWMYCu1l w8Z)P4.eh%݌BRcP@q7zDy9(z'R K1lWz@Mq͎ O]y?R̓Ykͫk[PA~wʒ:TĐ}Qr6M/D鎲KRuȽ׭N*"4AmiToVPx{'0iI@FY:MD9+6"ùX^<ԡ"^<X3CL;IGNF3)^C_?l쳥\ye&THN>l(I{$g(B<ŷ2Z"Fx_^\Bq[&(aYaL5nYqc_D L18Cu2| =88QiSU_6=l^/`f|? 06zV"YG][7^?m@e{VfL_.K=͜n Υ.{ 4GG}gfcVR[tÚֲݎ fΑ?r/j{%z=sx7ewd5FTT;[WHv1!Gr)oڪNؼD0-[Sz Cw!7 ukJ&2',yؐ%خ^wLB5'hdUPDej;hzXJ^x>QgFNI6"e[Tˣ [to샿hP?ݦ0g&j( a QvHVT nE.c)׋ti˴>qBj3v^Meg)meYWy1ه/"ȝRuVFy:ٕ!Sc䗤P/NYp M)dB͟E[z03, gjKp Q|Լ:GBi*AnYWd5Xe^FEB|gP Nzkam.nf6֞N[fy!iQY'MZHP} &yI!DM.zs9ÆjfZAbEQ)Czm2ԗ=yAH'.ClOPHl?,6ẊA8n.F57%V9S䘊ew'e~mh]J5NA!}pi%Sѓ1ϑk޹qՐlD*nQ:F=j%eai]@R~/AZ@$b]6&1?OF:mZ($.8 WCĞD+ F>;ϕjWp?LLQ0ҵrO-/'‡veZ\@2=-U:<+h4gm`mL!,op5 3ੳ*d.W's8Ө7#‹c mmV6?NGFT4F#Jq'~7_KkpJLY64e*:e*dn'Ё全 ^őˆ@]Xo ʎ4T7宨8XH%|%ߏzշϯr7sRkʸeZ7< 8zG\A`၊H*ŕeaX}b˒?1~;#f@I̷$aҽM7cKյܻǗpȘx>$ )3R@u&蔆9`/zxZU<{y-LU+ IU`IIVOըa4)E^$a*Y厹\mu4=C)= xUƛ=W#|(;%O'l7"~Vuy1?LutY<}UjT6FRO(Ws q˵4MwG٣-bY(dOCN4vKKۘھuz6ol}dNW6):0FD;1~MX6I_a;RgT֜}^9p FQ;D!gݠ2I@D *#Y\]'*|`>%>H(>RC,|/P*RyOw9:nMSKBr%a©f@嚱'-;KP?ϸ[}?LX, 8p5lP(X":ʳܔ)"X6\]2RO  +.ǗiJDMWAY~+ɟt{~φY`%:o,PALlUxm(EMNyy.h,ՠ,o ώ#>&v3-'zRxoBF?ܖ]cNHș[ v`f qOКcc|?t~HP7Zs;tyHoM})^z KmN9Pʰ;ǦesX9p:xNR>xtj jDtZ ;tay՛Bьމ[{Aů##:+Q'Nqx d!P=gnSu}&Rdŧp&Zܲ&|#^3dq>[e%Jԛ+~[u~Wc:(-U6nޭ_*4A* 8x-Z/n%%y.\*9vB/\ `}';/nfC[ݧ:Y*y_.&PΏ!})vacg\ Buŗ՘֑PRy얃wUqpiR~Vdq9ۋ}H6造9-T[Itx2CzO${~+&ưx>Sޡr PƨcAzFzܦsFRO`.&Q.#Azvi$fuZm?H/= %6lp_59Q! Qs1af=lZݫsGv6 萐̎BTkh-Rνm;Ty>ɑZwtN([DRYZAeMt\> ba$zJ,ns`>:-hI?B &CTbt`!g)`wOe>_ _"ɿ?okw'y{"O@IT1D±sb'?#pq;8M5&ٺR?G8^}H$WmGe1lJEUAPt*40iIҡwONqM3UQc6>z ]p7# sQ0tG;Suolϧꯟ(fxͅ)蔍/2MMqMm`]PKĢ)A,U ((Rg|xJjFkep'ݒ9ggN=a}(zˣ 0mκɵjuJ8enV뇞ò܎a֚^Uכ|g2nl#F wPljȾ:~ ӓhy*T V]9QqFIeShpIaD&ˈK ͘ 9sː[D}x.IO AL-F7m+L= 0q}LPX5}=`LGD@sQ :j-{Ę, W x@gb6B:O{7ӐpzCPL-M;&iEy3OT<6ͩ}~ }u}JNe5L*]mU~Z[M52wUR03hKbWO2ډ9,QDOC%؂dj;b]dT!ӭ}AE=8j<#Q } 1`Mګp ϢT6q( Jmx"kkoQuĿG!SJ_%:qt8qӊJÌF;M=Kh?`!m>ǫwyvXn݌sՃg ńDeAu*h:4~Uն0xoAumOӿaQ}͋+<"d39U?tYߤj^'0NeɷxhS,1& (0bRبbT:H N)(WGqZx(ɶ+$X urhŐx%)(&5|hJN/Y5 NW)vbxE}/ ;>Yz[:mB |pUq%V?lUK@{#k!$"gBmOi@sfd^gky Ggp>ZTmコϮ!EEuVC{#|~2&c33 òP&nًPQK=O!Qa14(L yxĭ2#~?Q]W-1?Aa>#'Ε~ rqw1؏kg\ OU) 8cq%!ŕPdaW'P(<:aUTGVkmClDq 0ZgL)u)t%>ntmDf[ [jl/O>HDk^Y@5O >X qGl!2*jp N׏qX+iW7ht[s)r&'{b\n&A`HL ̈u:PVzLQ$50z `4m}G)sZ@: CKP[ԈYN5Y|;TH]s;.+WKAArst݀͡v/⃝կZ} N@u^Ի/?NaǾg E#*.|*4N 8d)L?Q#_`z6atݸHU%dx#,4K}F#u({KTf"aW\dl@II32y+&e0 5r% e9˭Q P{'0xHwj>;zVa')? E)i>3ZݾsſiϕǓ qeUt&ssv5Ɩ;G(`GOٟTԖU1噲 a"ݭH8 fsf1ݧl&^6HgtCo'NzCLnnqͻIŒ~$DD,+ju^9<[LȚL:W !b(9U\=Ҕی4Z BS3s2#-(MwY4Cӟ]D ଂb|)fJ`6Vyͥ z 6Du4hyx2 Zb%f0gI*Tcg [oIs8T)@uT#$&\->ػLJx<,n h"f/hΌhF'Me7Ǖaul.+2Uqu`АK"l>n $xҸv%'pY>goppm)$N8o)CI! P>E3cbd-R~uDP`SOB5tfUeO9kB#c=" C,#%{b.Qs['[j0ըW_ ZLSфFdGt>qwdJzhngxqm9*-{%χڂlԲ\y~/CDsD.?x2A5}̬掣\w=,HִGȾu5\׽zjt"uYXF=J>L,mQ>]=)ږD(7t=^Pjn)+A!+( $i #x*X]&G^lg{(ЌE1InYg9+ ~)llI0Ɖ:>chƷz2'~{(IJLn\vm?@I*%HhE|sM'P=_3dHF>0sRR#WB)izY(Us'a#4gD&uW~ml,6W}}]Ӏ|%+R,9&E ˰;0؏7Ct84xq1[*c,'*Q= E$=["LKVn7{ow~m8;y.%YzX~#b>ըC8/grG%&Gq2̃v\6ZCn/t_Lb}n`yb,U{0y%r`kV( Y|{S^V%A@O^_Ʒ^I ߑ7]؏ܷ_T#JV %)'_SStFdznJ)N)%^~һn%L"-.?MNi-)޾ItEY3smB#,(~ptm((3>OOs S^ONv+38AC^Av<-ԛҙ7LY5= :DwG_@ŦZ92?e,H['ݩϣn('`#?2p[1}C3d]PCg(/B&j[+ w/OS%hJ7l~m.O iNtGYϝypNL08~tW "[[Ojz9Hކ};Չ El"Dg|𡋘\ gk\ Q'^u gRGk$sN&y82D-@ [ F`Fyr2/`TХ>/ =gS2$a۶, 6Z鈘N-*BJ"{DYZMH) ^\n1|K:\WgiSWy37aYpl|BdlX˅SZJCurxeq(gl@g0)M"zW :( SsZnA2RLH͔TևF~ou5F,9#T`Ia< (5,trXQtNMiwzyE-?ƹxگ‚җ9Jպ-M9&L&b65$"2Փhm=L7rZXk`nHNbkN@. OLxX> cr=1C~ay`uXzT `Y} s~#?PN+kTMlzv+1% u$.]fRߝiJKV.>D>%kǰ(0Piĭi*/B/T X1`1`ʓ,H>{&*">КXjqSE{J #F!g>-nzuB>+K 2 'E&hl:"\G6=,қ>ElIpx8QONgc&F>ʛء2>lW5k]=*PmOl:ado;БMe`fSOl#Ca ML frbk,zX&u|W)u'?Cd((Jŝ-մQ.T;]B)Tpw{L| ^cdEoFsv =H'9t+IϬqSwՎTV7xξK6y^7!?ddArFԉju9ZN$8nPbנ,*-O";'0J$YT:z&!|³`@bMwxr,?9KmkV"ycUv[[h%%!#[Y'.4$o3W\erc&7p6_Ja[8[u sv\oY I$>GjˎZPTtg_{7{P'|$.D󗖚~20MO%/ \5C潘2d3dDHЍmڤƜa jjUrUET;cB} F4?pJ+ts4#@mMU@3N&tF>ND{O[5:ZlCOu7`bP4U2֐@[Rn Kzc tfތ]3MB[|d$;}̃y2S?9m|&37`nMO"#5c0Oj3Ǽ1ٝGc] T{1ܣcG/ɒ`@HPȦ@tû7#9`vv*Lw bY50 /A\6$) #ĭ"AgΟcDvB{Y]QyT )iv6JITUD 4Tu@ 5kBR<'X0Z H]-.1^_+&5]O:y%8Zc2҄24Y.{;%H a]4qe78Y\g2bP& ݍF NSDcԻ{PeW+ Y(4ƃ2Ucѕ&Jev!?$LFA讕WH234g0VH~Fjv_06u"M ꝄafO$90V <rc3tzQMDUp4zjL$wDs<\mZ6 6A-hbw/䤏} GR)G91W`7d{w)Rt<.~%N%L" Va Ɓ ƯA5"v7EP?wvdrnQ &Xі%%=PzB)5c4^=ݵ̹k#ϬŰk UcX| GǢC}JQWQUM _HlGӺJ鑽k1Xy2Xl ],1aAv/;K_\qw9_v¤u SV^Pys)G KvRnQóJx'KaX.3b]Pk?˪-. 7O5Z9ꆴTm xx/ØoZkܛB5Ĥ5*NDQa#w֑֑G *%&6p ]=]ρĐZւ60DX`+.yR|* FOn [㶸]:\oZ$O@qPPoU'oKzyήLI:%D=Y%{˖Oݼz*xTz ܥp;bHُٻRt|6_Ԓ8Y= UhIO+SIn %@_L\lM9 A^mS|uf).}A QCaFQii޵0IB48 >Wsvrnv.vdYPW"%0E:r+q "K^)taKr,l +(ӁeЌ|iyOokր!R '?,b_@atla8LGV,Zy5]"K=p(2w>dkOa|pq<m wfq[vaQ$ =L&J:c1 86Bq-Kt[aŻC^9}jWM)׹_lDT3'kE]ŶIF ;@hR_I(s) -t "t9][zE`Ed_4 y>#xVK)ʰ^SrGMrH]Ru`4!LtÂdNI-',w=)/5UغB}Fś6B WA> v`THSiD2,WԀZ@r@Ա qwUܘ-Z+HiPgi<1[_COnӡrMEp?O{^R aOF>iQpjxG>tw%j5ȡz+jJ.f)usq?/ז#?r[&a.̰?o23IHe>EҤמ*Ww4F]L07)ݖx nR*"u OPNe@7/Qgm *}0aR87lN&`c.~LSξϒ-(OtWArY `!@'X}N$_oDZb2^Ny>ܠotr i!W٢R sCL&d"11]$,l!o0˵)jo&}MaJJ(/Y>ys#aY]1؃n w}h!$ʈ 6 Z%Tp(ôqS5pJ:߄P!$?c{o{PatV'3'|fbB{ ^54RLq_7b<[rn~cI2K1fj?\hԡzVma03"EaSݱ*,T5Tx7g.x# u/q|gw$|70C8rb 73Rl8n~du^D'Y͘.J9(lJUXb8pKnz6#iCBf;"x5(v 8,Nߙ =HtXFOإh q*x59I,*GZjzڶJ]+!-oE%Rd۔,AXхzb dmBeh`U?LEG)W$Tᑈ1wo7;K/b<#)E66Jl^(u\S%OB.3(p'G']9 ;|;AvGg%|T[kjυAYhKg'7C 4۬;"{>]\'&p~_ ztȫFE@R˅+188Y8ײdF'{C&ºk:~P֐H#bgR@TxӺŧJ< ce stma˨U#t'"`!xe- m TQ$D9vY5 )ދֈ'?~&2[t.gbЪ#)sGZ38!8\KccplnJd( Yt-oY{T!^ZZV PFʍݱ^ƸΆ$k۲4QR?CWB`qO5}?oz;0W^YneAA'䖞LQd -E&UIcȄ![ E-j`GwShZ)<jWEt'W!qX0^R\B ϑF&~\)!ʅ*~ҷ$5fUo5i4fD%2DԮB }Ɨ%^FNy?#L&$Kգó0t3FK9@BwUm1 opN?M-eCYvcSKҬ(? ip֠62diOM OLw5F{`Yu(XYsB na5b_^WUp*e #5DfpŴJQUo}Ë Ս,3(&.\b3T"[_^CX/=-}TJĵ`@Zz~'-a+J%_sb=phmӇkZ5BԞכ)Fb ˸ڒ ?]4-p*e^5! ŸmꏙAӣ2%_ޥF4lѦyhϰ[ ޓ^]^K%p‹DAPI(>sNiۥoz 2lS3>P(Pй^uXq VK̓S;L@pCtFwIlV74D$reY#ٓ-n^/JtTb ,OV|%f^ۨ{; g7Y`hHS儿r'>u|(FGXaeSK!c*醙$(׳dpOg2~rwFNè(r}9y~< Y4 C`WВO W UXD/VyK .W-sY&׏]ڌ.τ>7f ǭ9ߙG!WFݛQBŚ/6m[U1bW^M!Jq<)'a[KȦWC"KڑMbvEWbZ|dS3+u)/ CcT=dwFqv]莲leR!1v3kz;[X'zƠ~CU#}j3xB#I-I,K_Cta!Ե=@Bgt{>@t°?doJǭ0bܧMa+[UVNɪ~5(G&pBGmaTW[ڿPc`6'긨E9'&+$VxE,)bTA(EV7BǘhcӔ >mOtvoe>%BWjRN MѺ."c k?5jr < fסZOj{ BX!lqfВ18$韁D^WrwW"hR>ԨS"܃@ݱϕ}ekacb+y"E뙀7Jzg&W@n<dC`w^5l"6D{+3pYpʂḿ%#r4HF^͡@/wy} PoV4C64SmF5 [y342Nvq-,qN:dB4N=!ؼR߻Vh?[s$BBoN1VF`) T^D.JuO2KvniUR J5Lʹ&a\ y2]7 r$* (4CPGI|:#A_YRg6J''" o$\L:xe А.Fyi4"[!sTds˜LwI }bTy#wn0 MHLC;T5^8E8(*孿Yņxul$lQ`Gg1=^ε矿N0P;O\oWV:έIɅrA|X_MN >7ucnTP@$CwZXIj}6TBbNAcqL:b,"\m>~q5?n* }l\!쳓WVͫD54(D6£96וC_R[§#ik͹))3#f>Hs9$j^t=i)\X798 &"H7yO\>+5皞,:9s3jHR}*}vYUFߧQ (*cj`hاN (rE0b8nYJ8_e珋Z~(] @1syEClU^ NdڤB-q < "| joюP1(玱 !xN{D7MM6{ XF{Gkwbx[*H #(PלȺihŽ?wUF;( O ;ՕD/b-7 #ļUGToS`#1eټʧ: ܧ9CA\WfEEeͭ<8X\ '2sxF $ M׍U6@*KAЈ縋u-Mf" n7Pĩ!5u6%ܤl}aR%}"\$ۺq[\H-A"zE3>?(RZжTF_&ִLC[s[.·){TnSVvAT Mأ12Z<_b6HYGHK$~)π'QsP)ʄsBKب3Ǻ'|/]Cɫ @s1,mZn59׊^7H,o,1/I#iv{z1`vv6* ݗҨNi*8ʼw HGy}Ǽ9x' mѸ6UofUug:(lAJE7+< wG2+pn\ݵ+lD]$}Jݞ&-^Xny,8V Iߛ@Mx1ȭEa$[WKzAE)E`Dzu?i "gI!rdt.[Dب Wo5a#-Zߛ@5* ?$תśFгJ],~'좍V((-.sH䒦YpwaI<& YtjO(:"VC߰bB/DRH4)-FK. .FM-.njy1'5y.NR(J8s*@YDY*" h$bPTuIIx,Ӕ߸h3U[qZCofXAkK4BvIw .9 f!ty 5FXH_]h&u5Z0p/!×5꾲il 쿓|1(mi" 1 4ǴQyB'3^lй1kuV=]kՖUxESTnt( Sϡ R]ɒN! $y@dˤR$ŻEY빼uM@OFW;iC8,nC~ׇ2AgNew fq佑΁rdTl|/Xm-έPS<[l7( A5A(LJ:Xq|$m0y˲LN[!nƒF99(vrv+b"$tEp ޜEHi|}Xs⾙-t+nW:s~vX[9.!bk8.#A(9*$'0+3a)X${:!^X[ r\l )0B"x)ml/"'ZR|%|ꪱ'M'VF)ti3R y7i}X!V-܈9v&uRCIÍ;(4*NDG fN8<@ BlUR֊X]ZgX\``GZ\| E>^]pjʙsxo^k*~7ʓMB2ڬf0ŋ85V20=]Yu6TI7 jzVsɈKϜArg(Z[Į\MCW`:Byt pF{)X bž& y8XK7btgFR>qў<-bL g,%(J!E}|kPMW<#n`9SdT.n?^ kH?s`[XgLs[F;8xwr4,8L92)`-£P&yl6~[G flFfzw$<%+T3bV(Cs^$c>E N0wA=_kb g De![;Wvz)۝p1+ H?gaՍmN=o;PJ!,+El|me d6g:rtDKXne] h)}&2S=2C1w;LY(Ba9Vv-e8@Lx='{zBRrhYaoo⮟@1mv0d,W۲Dmfl{ :?/S` ̥:?WN8̀DBe9P½yka6PiRM;mfA龴BHr-ꪭ׻Ph32E̊|z[&[`Fδ YH_͔N,vh=[OR[sT )ifY"xFXkͫ6ug[%F HcݲaUiw*խ]?Iس> N{: ;?FU]~<G $C3cVJxvlQW c\4SŬc*%WI?;[xi9fdL7YB:=3= nk44Y  ]k|u [m%6zu×޴##Yؼ]á6.4c(g叉4z4Vyz `#Q7,qmY36vIY`kЏ$WPMGsNg]ɭLǃF) z">鞻^Y|`!Y :?:E^/9T^sǂ7?EpZ$L܎| 85uzⷦ;W3:`1i~ ţn՚z7jºW5DI̴oJnqJE.VH]g3p/m[{0r⏁=Wh^L n} uuyAٿx&(4+.,SLhG\x@Rb/{kBo2,&bf ~G&G6˦ ~+AB9Ax k*Ƭ_;7G#j[9P_@J*4KU>~ܟ} ż~1>V'O og@pw&݄qIts{xm%;|t~ɂ bN#(QZ=oq ,jCHe&r;?ijW&:OfٌmDŽ8)Kd:HQQpPNCշ Qߢ$1:>6vXK DZ=g|4ߍhيk:M2|HC51##QV=rb!!U|6es]r#+05e2H}*V*|NjHC?&WnnpV]ܮ_VݔwX@W ݂>2NEe%w d6CHt>Vd.9-CګQ^fbK=I my{1Om!G4 ;,~-#po5 m#ʆ8ny&.Xb[Z TF~@ eI«&4w+u%8ƦIo͆<]iw6{F\hheu!vֵ- v;-`T)xyzGsk7mW9x5 :O dsV3jXDZO+$ P\kA2?aM. + [ŮqSfF}]|Q}F+R-jIGV>eI"} ^kVOO?dk>1 x#*-5wy} GZFkNFR]dD CӲ%pؗ2鿁ֻ4tjjqvK1/gG~ܑ!pρˣKuBojDyJݰre5i0&cj8ȗN%5D%RK.]+nWD\qUgWj:Di'^-$г(͏x*0Cp{Z{0fmAȑկ5.jX5Z:J19G@b U6׀cfdKs)h&r1j&tP- ($(#KFk?0XǓT35^fH2͵RM18] 7N4eFI%BUk?0[XO8ZF$PFIM<Z]L S`Y T[<4Lqu/Zk EPs@SpL|'(4n ~̦!p~-8(A']W+NtʍSŃ 4Oh9898?r]n,Ӯ7Tg"+SJS|/Y}Po?8,1ヌ!q{qߢ6ʸFu/.Q"TMNW9aMBҎU z Hr QqQ!%.Ϛg⑘Ւ'gGv Qi 2wXOm"&M6Q]b\Wۑ:ɗKk**Uvh՛~s+eoJE}Bİ#anR l]&.GwjOk:^G3O210,; fα0)XV?~ݷd2&d9qGH َҫK傋Gդ򤰬<W-?=~j0W~&0l8pE$ia*3Ɵkn8|er":,i ߃>[j+Pg=Yr㘍Opt8ιz*vV l\3 l a>ޫ/t9EH$,.اFo'-㏕R{polQ.ٶIZ>QIKj|SN[~)L2Iۙ.=fx }{.J#\B646@F䂀̇~WkY"֥,=1Iޯt3$_˚ߨQP$jTS]Aj{k`<"6Ltޞ%"!ɴi34n2LT`<IB^FdJ4լXJ2⣍tZKc$vקTbнܪ8V3R{f9wr)_3AVGS?$wmiefOgYP;|PIַe2&rܹc.ᅱEkK y&\t uqzMߪL<ł,퐡W]8kPn)]rN٩ޘ+[t3g~J&\se \㴫Zd4?MX,C <,#쮴8I.΢,NUQPrfA1CY W2glٛ5e ވqLEk*nBRrBVZ^y?_D#/+MdûV >m%x^A Wn~s,E*K!#|#)끉g,SિOIzs+0]ѽo蝬Վj'PmIМ&GZpD$gq[mt1j)[,lO⃈$DKk>]L0:f)1,|P?XT:'Ci3)agGS{.g۞H2KTeJWy8yŊFFZРF0 L5pd]rtLÊ^!AYnZ`]`^b鍧?S)P7GFm|{$g+IL080qC_q]oP4Ÿ8g+;˰Vca(T4 i\h G3ߔw=gK{f5VAtf.LX-lZl V8+ sj0k  ["X@E2XޡpOh  2>Q`要&T`A]@ w%Po$LCtTcoBh艢,P˞%wJ*2Ii`gKVMU/IRu}v.ݮ͐_\F^/\*"IXrbZ*!"1YtV;'7OHS*Yx>F3U*<+ 5&x,,`@!c2\Q:En;eKS(=W슳ЃfYOS+zP=S4ڒXYL]=!tGXQ)ݔ~}  vQ_)VH(-5џ? !E3}%&Abgfs݌ NXMq gOs{WB&0,J]I)Fԍtェ ):4RG1Y BׂE E1ۆPn^6{>D`"~r$L,!L G2< u_\#٥ZXGJ)ol"F6|h//L)L#_Coģ;d?Nr~h=.X. pqEݴ6Ft!qtrG$k)>o?YLV[=ffT-8I֠< 1"lCdR=o>`ѻnTC{}[Y(VK(>x  Aol ˈ)mBwlsGgvR~RwXɒB(v:ള >}~9Ҧ@_NûK\~U~>J mܶ*ƒI98V\pBUYѱv`4l0veiT+pʹ2r/ND~fkzxxb㰀-]bL3ͫsސe՝@YltP䦢cQ~B>61K( ̙LBԹQrorz3?Aū ?.NԗO:0V;UO$(oY3UHE80O;Sl هά`nE]h#mqPzZ:ݐ 7+I5&\buMp{s{uۦn'n@\"}|@P*݌J$SLA, ȁLxmӠ}'I*wDZlQFܴ)v,'!c֬5 H8Cl5AgtWI<|o?#5prX#k-5X)R64OXQMM :F hA}/*ij4H.E+by,AE4jGZр,te!7לxI~WϠ#j(_yz0}O=[ߤ1u쿐o5j%g_514 E| >ͦ- G@\ b֟CLtҚQdrzF m!;ߨ0aKOTS`+K;~RȘ.zHB1ZH<;eiaR5R":tFԔcOPR^М'ɷ;ٕٛ6CQN*uPg7o w7 c` eqj]Mo*K7?:]c8+hmm'x\=bFxKc1BJDsk>~xt`3%1Ok#wfBe s?oA D;ֶ6,OhagsgPL#*o!('@̯ epA@w ŖyoT װ9 LjdjY1n&+Zr7`Sz*?\m8;gi:쎄g+"p#)*O\ƈ{F H5cI+{<}ϣ QK !R1?W % lŲ$U*Wt#y'.xI;yo*׊90Ah0nIdtl":+PX'RmqN:nX+5F*hU5P¶8|7%ѵe#қi}0U_YG aD"gP#L8 E8H [C}F,*dkT6SǢM@jxOWDXPV m[ %ix/o:e̪U&)yDجgn#|6.vZb谿h{KgWIQR* Z(nHtjjWH3 \M"U`7rBKo- i}2C4I?=, j\'Цd뫓&k&Y\n h EXؾn*7|,-hZX@Lj8>+=3j޿#9Iy2Ir9j)x@K@˥jtM|ђ\A|.8M_Lc`KOg5iD'O/,__S"ip0`%]3F;Ez4E`.&:R@ͻǐ6~T 2(6BS4\Jqm3h:6Ph`޹*D_uFtێqL_eqEBjaȤPktngu5챷({0$Z.VY8i^6VzW ]K0~P5$M/-RϿ]ׁ(u׹ISi=01f"틤o~l2H}QMzךF C~&a0Xqez}Bs2){)I^]Вx>O̭x0zWN_'miV  IG~Ws\hԎm!ēmF&|g7"Bː>(=`7$B?:Qڋ/` !vBq* 3DT6cd91sR.ҒorѩjJ-AEɘ))X {$גLCǣq-oVmV0!' K, h! |v*&PgDLi1]"9_"j:^Ёtgt\UqSN<~>!5>ۍ&d2?"LҎX&X; @9E҅/?ţo,Fa#CEyv:һ^QA8񶎡XPS,gKj]g:5!ٺGU2_h&F-k _VxÉwF[_a3G&ώwㆳ`m2b*W2BV?4*TXybR!5FFdUzXTMk^cͲ3c i]"&HV}#2 <kr7S-`mpw,+Xt7MZ7$ K* DBV"x5PR7G,i.S+X/I*v#r kD \N>ɳst{vLP6zv`=`UH-Vlrb C\x2=@GtijapB#*N ~=c8o1q""ްbfυ-msG򜝑Kb|6θ=#bH8-M9xQMuk^4沱MIOU~B3(oiBP}.amOWQf oSN *IBj; b#q6c ȩ& ws/]VG~҅Υʨ9k2 e?mpȥ4}B>Ad8 Qww,9:}^46va#'f}ZC`!|wH[}Ҥ#at|uGyX_=&G&dsnQxwW(g~@Z; HۊCS_pV^$×>:`eݧiPovFzǯ-0} "ߗ~ 2aT6V4 2sVўG&Μ5Ԯ)#Mmp-]4Jܹl8DEw) '#`min= ʓI.}!%[ O@KtMvV`l*e̿'d2S.;Vy6~}q̭뽯= =F_T^ 9)D_YTyw~3.cwQ/皑?f΃xE"-=s`4rÿ]MC #!+ Z_`IB9GdpLѨh,iC!m jZFStȑyxVҨFMy1XjV#>joX$s K4,fEѻvVݎSY*moed{Ff#iٛơ&3MǪPL0m$ K* D$uF2\`{F.W/&Yc@_ޘUs>Lx)jvt֯F6zoχT\n0ʮկpYhY%p Ȫm,!911Nϔwګo pL S@z=&`ڂv s,J PR=D"LH­FL\['O00Wnzmt±ّ߮ Wm\ ,SC~p6q*(؍=8%`{/O#2:䰢5k<Nd~vGgn ve]w2XͻZ쇬|t,@Ô`; uE9qLLbK_D*k$BGA$aVz`?i@Z!Ϡ|%0C _|~gs\L˄NA+7+9"H Y6<@h}n:gHdL)}<{? qԥ'Pڝߞ*63A1.<ah/ z/m>θ6h9è~>T-<^[RZr E "3J2AۿMtϠ _v #=j/5mås4$ôld(e:k 5يs4Jn)O.R˔d3 =Rš S4Uݿ1A2ՔQ]&6'KSctāZqZ =aܓ$4{ZrΝǞuy?Iм?lϟl|}In m4K8&`z٦x4vYz};_=Nm}DXrH c Z BNGaEݚĕ^jgSB.k pH9Y:yju9`CД&ϢZ$x2$2Qia,0X;!:. h%:X=P|瘬֚,a䬤?l{O5DQi tZ|'_؛JR%{^2R.LbK}A_5]Z@83bЎ2[]]yM횏]GaD`8x-s\ܿe8QzЬa9noY%NٽOP9-s9i|+U="'soKgI",L8VzxarT_aI52""_92|D̬-ADM`뺿{:!UwwIe{1-YFhnC`k;wUn1p 1"I ;ȣY}fr[. F;g-h"c,[fTj% (=D}ƆN nە꾫,-8q@ = mh?ٌ,OwnE=YQvW9'?C)L +;wOEm C&o&̆&^3r솀r`R 'zp4A=)꡾#Vј?M0qxŌ Hik^I#]:Ia#y9{C7A7'}7D' mњݦT7nˆ=V7/Gܐ~{N'ɼs H/Z!P"^Rf,bT:S?#h gɖ721?!(\m?r7lQޢ+(M T%8&6дk*!GMn@nfZ$Ak%S4_o9!1W侭aR R[nal"gT#$2C4N8SoV5XDVsH=bMd9V !˱X/ؾ{=SgxA!s< &==:9,XEܹ kj%1KH Ņ#L* ڸzt}h'QSC tUeؤtkfp;z[(OcZpo`5mZEZk(9j*}i{cx vV/m1#;ѻ~Vc *uU` ȘN@ hrܲ lPGa{$nao<~cLG2A' G@C#hcB*sPi`xakϋ(nj B Wdž8/֍Q"pIۤɹ,=#S^+(6tɊ*Ѥy m>I?d\S ;I *ܔ[Imb¼G0ɮRX/h#"a&>鼸_^dwlHE[qd~܄Zu7ԊݤEv[K-=cfkG̥d a3(֢NMhQT,wIm I1]d4͹O1=?㑠a`1]mU UmU{zrIE\,P43n+  'h4C/'ƴŷV>]TR) Z.Ќ P˦' qZT: )K([XL`[EP!XQ6q} ,KP IU:9*_/ u`H% vZ.#w0FPqa-}AՏ,R8vM;%a Pr+VaE t H '7:ay+C[PviOV',mֆk LF*} (:!kU5.JBcT8M xbW!3!EwGJK$>ow28~BWAj u٫՜y nB{auf 6?ʅ8?$VIPs$΁G\.Mb;W+QVzKx.~-M1#h{#lL33; tH}D2 ăaO>JBQZҢp",倢?\|ԏ|T`jdTay[qU=QVZî=4>F=?eFVȽ2S$q6;U t#3WH+1fHl%wߋN JmS1y>SKIL ne@0!cK CF+wvgP^χɴB}/$;綠UsQ\Ֆ=ci Geb|Bs[ 8,oXHP@5m|!`v' Υ/MY dk] :h~.shHRB\"d>sP Na R]?`e3qOQ(O:+nXWy ;N8%<w鍍q=)afL[N7E—TƱ,pαP9HgNU&^0^D\TE2(@ŢTՖ@j^B;(L%_4݀3*ڒxL圆 KGǃDn2D_y7ZX~9c(EpKD}.G4&=f~@7<|cdh+>p*LadjjАd`BEI= yhc¹w`qm2# ̰(xR=TJǔ2J5T_Udư+cΥUK1iRSb%:*7Kcrwj39TX3*b7{߾>4%$.FAGN9o6Q LP Y-4;H2hخCzZ,tn/m86 ^CF ) y_E1n 7TҪEܱ$|V١15;z=ՏGtp'US$GFq%\_?"(̝HE%\\Rk'ouF2SSޝUBm[dY&rPtlS6B>q.r0L:mA|` YCC{Ubr-%Cx0]{v/` n 9H(>]Gqܤ{|p)=8'GWNuy`׼ 98S24^#FӯG_]\~. Z~ùPX?jĜ$ ˞ANg#5;&.GSLc%=:JC#В%׋xƓ[?QI7J8O3W3Ct=7Z䵕T.JGN cujNd$:@/'#jfeN,%ͩtW$/?Ql,R ,%@ov@~0[o/ZKtBnπ^0zTT"Ѫ-H R^PXp\d{k<`^[sv.ܜ_Wv313_ơ)j )pXۃ?}Ah%">"Gdw:%5d\便ϙspwDo7U>%EIjܦ\|eP̥Hyzא $(-;.1HGBƏykH!1_* /O @FIl/nsZ]DYEl,67"XC'L47Qt֯Vrf *́G| қ*j,]&}:02o\={["ܴȅV_$rD2UqS.ӷZީiM5[OwӚXm]{_u`~fXa-O騷GfOs3k LQdmڲ/; VcYa.F3ų2^/ۛݢ((JXQRn,VpJg$)y!Wt79l it!sÅO!jТ8w8t0 ~|(꿭{@ A2ox.ۖ<2iZ OKu]w ?I 7[CC) qUOJ/9;RrY(Z<;=F+2' 1里 _+XW՘ Dj3N$-Vj% msɜK1րaBR{Mh:dD k~"ȘWs>K|Q?LQ]8W+Uut>R)`+Y+)#c)M',GQ2}ږ3҂jm:fb@\iBEM}3ͭ@et@Nd~2"@z ЌH%lS]!ڼ?B׼:!B OsxlqM({ bi|ٶԠ߅`΃IzCINήl(cE򓋀B\ 5u3 7߂`_+ayS5g=fCcD=1vԋ":3y_d-xs0(iv2xzBx tۦmgQ-#v&\/bDB'Y's-Sbpρ]MP؜go >{p7hWZ//RI2biut^ 9{W>fI {2hVS@xQ=fM`C?}"f,<'JaI!MH!\sVlQ"x%XrqnqN¸n yr!}F> MbB&nHijJoܗH(Ӈ0Y6:DEd5%\ȼ)ϧ%43ЙҎ7WΨAvUx .I϶mU^V\x%#R*DJfL{z~ S{PBz s͡-V.3TFw ȵ"P!\+djSϥ&;]3U2oا)eIxa4:[dYx / {d[;p v*fwDp&7At'AJ q3^e~b'6-7.$s+ξK|zݠQ_GŒuci ?fk)"Ͻ#dw˦r=wPpX˾y`O=IUM6blOd~nq;>L`VWߜVl=X @;* ́!k2 qsVϞf]\I=N*\/|+Mke4TtSՌ%(IƼXO3{!- i"tU͹A X#|JًRqqv1зtQbee;QF vL;E!ALR9#閼8a=..SDI~\K#eyn q$v(6P&U!Hm !&:B̻lǔko0,=1ٜTAをu12H(žIgל`~r͘S/JHw07n5;f4ӆ;9 4O f_5rn潄j}ޏ1tXףO 6pQ}I%C}iW %Khy\_ՌX.pQ7IC3EL節86+߄uE},4^fJHUwyqչyYN%'=aKңί 5sSJA<(('=&{ X16(kIW)-'2w aаs˂,bAhqg ͧ%Gu*{l-p nf֋Y r]ajGv^vZlLP܂ ˮzګ "F5S+Rjֹ'=ߌBnYRO6cE7}#=O?j(\b13˶u6iW.USOE]-%YJIt~+r;K}͹)~ NAy[' {]yOg`y7ژ NxZ^:gkrsy5X6i> 3B \RV4d̰fjLH# `N`ءp~I.;9,W͒nċzdk@.T\u 1xsPN?Q{&pi[..}1[a蛺}o \t6.>$>4!O.4ڪ'j/R=dA\tg4YP[<<,8ɧlI)+GQ%(¹sCVdOJeOҀֲ/A \,L*v+yTN ʲil~rȯyEm7FZ vέIk5) )<yhVbВ[FeWb{3_x$jYݙߒ0pH,$GwIc6[Ktd;)5Kˬ\%-0Z M=ÿD2%=%|@X0q{DŽGYڃʲ5pdS65̣{d ] J#1ڙ\1Z'씃.9LvHO"CUR]M҉%zJoжh@Rg| EmxOoC v6X//H+4Kp3&bAE 2{vqlEǀUǩJ3Dpw|oRd.dk$H܉G`y1iyvE KOp=܋f# M7E~B[B k"D=] F-Yn S` ' GrthdYQn$]fʎ_)\$k2x%)VJ+9jsVMcCEb\l2,#b5̕u )Cq嶂9Cm"E/~CZ9S l'T ]O+[K$ ;GՊ'JA&g\AVꏋ4w:p) zn[t´۽AuG,e7!}dO5jBD|'6 N)8 B6Mi"m.{W~ˣgT6F%a!_3ѹc .i*!`Cq ]}!*SQMƃ\+˧49>a4&Q[>W—o eGCiK:cS4!e-Ӂa_b\= u\Sܶ|ь?c1n$k)y&Jrwo q'g @)Nq:Gnfci`Ι2~McJ˷,3 g{ 3$-qG`a9($V:*cB]h?>Dca@fd{JH˘}!P1wxkVԳO~.5?IƠn4$ѰS[w(yV$CQ8l-v1!m嫨TY? <)eGǦF|9/LR׵ǯKqc` x~ q{,'Pp BlT f1Z )LTOW$t#oDBeL.EWDQ6&0vs)%IDk!Sy2#sb aIk@xɜa?p{{wK 7>Jowr8vdrnO ^_'zL#,s,i)S17gȧeg߈(;$ p &> j QQM0dQ zn q)Kpz yقKWkϐT~|r:B{Ҍ[_ n%Irzw<,4.oO~-Y-rW7;iȭـcmxuwr%ѩVsгO&ѫ3qA'_P̡_ % 2QlH C=FCbǣ> FL ;'no+>=?hTEZ`XkZ!͛ZʻMh&wzb R7;BxKrz 8 {Bcr!QlD 0Bwvs ʨF?eGNduA6e٧w1?ы+g`nlʽ =-DPrܰڽ]x(MjmNM,:]eܵ}z<~Sc U…k@1gY7ҳ0,REuV!T,l=,3XL;3cO)/stꃏqSy9vD2B Yu=];ؑfԨD@Lkdp`S>Q]Za0Q{2ޘ@6O^yNqmWvjV5VHU>q7=. /n1 KWTJ -V )򾪉YSt0-?cuWSě8vg*˫>7IAʩF*y4)Ib3!F3$[ ZYQh]ѥo%'؆04q4ioU6*M={ _[wN@Q;U'񸲽+ !_}Ǩˮ)li=^م\}|k#atj>,"hU;"|EQwyO 92v~)BaUKP˂Uc .%_sL`}i?:wJ>fg&G="0G8aY6lJ $}עl>in6>Yo >sб`l;o@d EȮ ik_Z"3,wNZBz5IHGuγh6)%VVmMR1'* h\IKmilh8Q+}ɱ٨^Yn^ 類%},Sܲly)tqG4Ź~o~\$T {uؘ#bLohٿ\GCFxv]t-Gab&1+`M+9%G>쎕_ZBіAdͫM-C)dtWnvfJXH AN٣:y\>: yA'}ǝ@x tkv?.({ r 4 e9ؾ>2N}f_n2THJCDKS "2XenVE"WE+R /ҝb{Tqg ֖)գ!@]}ecJ;D悖2U7mn.;Fj"ڗpY/J߫f]jg^l}[i06uIֹ%'L]KG{C\([+E/Jm($F$63|&8s7s }foothN,9/+#CݟAM3(X f4ӽ/rMkj-D~<8(DCvOU/YzC4TjWHM5[D^< AZb C,xB,hQN~^šT/bqrIFU񉗸TiaO/2‰׈.(x"b &f"UR>)Yj`G]-^j޾(G_ =>-5dh|qVbc?O. &Y ܩHZO:5=cp5-)gyr1VVʕѕxH󊤏c5+9&/Y"qS$b|Grn\xSLzd& <дGlY|*\v]]9n‡QΩJNV7m—ϕWV$J17 G/aǑ]STY|i ɻ߇ZДqIIG+MZh4rM8^nk krmTq=hsjxi ܻ7g#}^K@P3Na: q W䫂PNguB;c(s %$:m %}XDf vOfOiR!/)Q8>bUL {Gb[3ќѴVgY;EH ī)< ߈N7_<~8 ]]M1bdfԗK^, j8K~>HЍZf#;3h4'|9 Nk!.8mcs8ZF}ϖm e,} ٫_3▝~آO7}E¯AQW * iϸIu,oUx5Ƭ7nH$DdxW((_uڂ0늗,jUpod⼹:V\M'q PB$kuH ̪ݴ|KP._^GQbJUN -ku󰥮Npo Is \,z۽eAub0@'tj rYJqMoҸ!^j9aEt^6l!Ym6+#!ϲk^e#3yv5@V,JQP/ܷjpT=Ik[;M"3:!,#ݍ^1 y0m N֏&|<ή@fn'H޺GmVdФ Gk;q-j{^ wCU-] Pឈ= +eE0ّ,dže3O"1|ֳ򝻒(?]kUoYm/6 \L]@r`/[EO ^`/@/Ნmㆮ ;Ѯ%M9ՠ snӬV #VnImclIW+;4H?QuKEo}xQ_%, c[Z+|^}9Հ\ew܅?ewK$a~pj;5v%`ٽ!yx9 yى~x@yR.O*vfC29ϿwdT_4{\b'HڃD+J9>k,PG?~kybW/¾Fɦ_-TӴn"`triƁKI..n>9!L D.߃/DX$.ՁXm* ]oY;fmH*JzT~'l+}c*ClZ׶?}"5>1zܐBI*4>hC`1iҗcLaVԥM[֞\16b.D>;'M,pO>h.rMAFP -PP`Q9p\ DH0C;4.HBmN ~e~> mA]ңS`z̵yܻSxT/(frOD 7Lm;s RDS3'!`!g'ܺ'hrO[OawsV9#;_2{Xl]!rG!7>}8oǿE(O5\)% '-umvupNCYk%hZ X4Ƞl !}qS B$ڳ&~MڀsF1V.GM\*1#H0ԭ~0T~EXA¦3ܣ|)3HQVC6n`Cf%<}/?ǔb". 8w"~vho^h§L8e C ҽ_)SDٮ9>m[Y_z*9 qĒߟT~h!p&LM5ӅR^)c~,Y Cw qLfC\(*SAXez=n2%9Rr yFbm/;nR$!cEw LK׵7 p0;ck]ЇªPfd} >|^+vVIjN'4,Sq7-?0qtLS1aݳ2YgNY4.ob#K[rnt@ƊE3)_ k{-qM,y&t)5ǽU~6-´ArHdX{#AIJ.Q-P VuS1G껲Jvt % AꞴXj.W9>,Ur#ZcdM4i[EX͂rx1zp(RN>(q7encoH H`3JƤv [12R#%N [T, B+B&\MQCUU$,.pLEJ0X7HG 6HAF&c|ɛT텡99+( vC RdIIdqF?2ZtLy@<r>SwF@>o#*wZW߆r7=ee") -z6hXjHo!Cp)!"7P[h|y;}Jh~Οⴸq-qW?at¸o,ߠ;g@^zS,WoWDb244?J?Ç%6up5d^!P Wy(rODQB*k$ܔINfOuҔڰPSZ*D'\;bVoS)_*mx`ǶqgSI6IqkC2S71BB*ԢszI,qq\j`U=Dn,M#HQ`zy`,Ss7tN ž);a1\:ŎS{[]8@R6]Ȇ|8c*:eܴ*gqN 엪>E>o)0Yp|QJA/0z9]e}u!GO$LOzrDcX-'L2|o6hkc1[h=$fAaL;U~ǮV `V$E"ƦO9x8o䅑}Q`IktDVY>HT/Gi&>bu꾜⹷fMS5,oXF-쓑Ȩfwf R{{hE!9m "{7OJ. }crxkԐz+vPM6):B}^m5 [7bC~_Gfg3,@z=1qK4""$@|4 v+aѤA ެ"K'7Êm oU8k0h+yr}M6w%d&gJ9q^CgbdBigvgy@)l \ Zʹi9@U@,8 a3s3Ad ;a,9&""td!uD%G xw=k@W +|pYH=.gٖ&}V'ȺQ GxÁ~85wxl(U-%rQR<%_QS-'v:559zA`>{ϵ-?ց=)n\?أnWj7Iф,$iB4̺EK"U[?*\В\J:{_aMNq̄y >ݒYhzFX_whu9`jЀNZYEKG 2jKֶN;3 fYTGK.z$KZyx.9}D_(7C[䋊?QOr*!?0AĤ(f$aMڲ}9`C P0?ĻIPˆaC*vMpU,/Mi”= ]ɌZ24b vG\EAtQP: J-t4 `9bOCq_FX.0wiLbb &Gƨ,"L*!L+VC(ZnCC0r {sZ>g Oó7Sb:zmK}j0?-vz~ѵI-ƷQQc Q[R7)R ǎfdҴe/N[u ĭ"ky!.,kbIfڱ-xl]#RdNz7P E<\5sWܓ m<mIG<>\LʯO_@TT(IUV~=BȒ*͛45_8t*Q;5lM.nw_cC8ƄЮ=p7tşAp< 58YmbV2a6Lvi>o@9ppT,KImYޘwgVF~;L #=P'>* 2OޱkasuN}K37z(,\KсRM_HV̧$Sj pSժyRHjCneg{GG {=4$RmE&PrjoFwbO-MN{;eL0'o! ܝv5JA@1rS:? ʉ͍]p߻-$^->|G|hX`HTsfA^v~2=8@Zp+ZO'f=B9}!c-3bGˣXj]-@|խm?)#bD0*ILM巺n9IҊcAToӬ|*v^:'IAdz߇0$wrۂos҈XڹT?7xx4t :FL{R~e׿] j͇'9 ;3a09a>yЄܒ.%0H;;s 5w3 <_y i z9F;l5э4vf7J]dT KU+ [{T˅Y֞[ m@X tTgUJX4%ʹ> OlVO>^|`.t*p A7M5p,#DKI;u[6&د֜bxMi80uRZp4 #7bbQrˑv+xevq0 6Q}n! W)K,D4Ƥ$ahAE]p'\y;fP?bcx ВYۧ,jm4P2Bm<'JPB\R?ڐ4vَFBr-= h3qhlZ`q[a\_1>>MM 0SֿUo<%כgh)̚w{t͔Grya&!5i(#}K NY>R!? hw+|諷oV?n4l}wÇ<ќâOjenaވ&)4gOg(M 4Fl}'&T+]7ppa!+L!&I2YԉUИa@,"2 As)%NqE䯌Mx[Q adTٷ0EDžL)`L Þ0 d۫gOw7lo¾2'j_5 87ښ_ĎNi^>?Җ~ \08P6:OT\jopv1;?1LՏ* , -]߷\>!jx} Hhu-6H[p;Jmeuf5}هB8uk~ ?RQV__*3**9P`Գ8ە@Oa5I!dzg8b>"K>K BW>JvB3n2'`ZSi]6 +ڐ9zD)/w5NpK_mo鈧( {(OUބ95$}T+9n-n)"| r #JUS>hjQ_\X񄾺=tz:yGW]e->6, _y׷% Q%"Ǧ*)lzA L/ip_ʚ}B,p[ї(џ~! B@9AȢX#pz$w:{K\rN4fj5jMFSifʠ~ >el|pD7WNFfl] ғY '?EF̠cm`$+.%ڲ(浱U!FXyQU^tu7<} VLsa*9b4 ߽^B cs4(]OUǂY>@+B)TRY[[NI!n(W 9T%qu[15lAcC *(|~H9~9Ʌ!t`1!ݧ04kHĠoNJ5 Zx-v4$jeҼgPOP)+0gݛO-M&Zvw)ϟΔG|:@ZqR`9j!} +z ʑWMTm dK2_qn ;{-[Y]Tofz{J:%D\w4$QU-( =7WǓ?f/4qo^F1Űʐþ6^KJܿMT.lD sO"c噼-H˥o@vMov~xh7.,Kqsߝ7>{ZcDZ%B7=%oKXgrY~o=^Lx,5M}?$U3:hk<&"lXͩK,%fɌu!}n+3IA?Kqqֻw,~m>'.tKv#ߨčMw:7t@8$iF d$9Α[ h*!OqYAbg`g{\J45}oq͓S.j b$(QGiLL[޸B4dcp/ð21n1 AE{6ށW/cbI ݪ9gIj*)\}-Nc atx%*r 8|Ãk:$fnlcmi~^ۏO0~ H-{)]]$Ҕi`v?h|@'뽭tZlAi1T7DȐ*C5Lŕ%*%eJ->qD1i)NJ~7wAi5m_u8t! \ߋZ ( 1ѥ W-pNm%;gUa\V+Ԝ$O\j>Kx"( mqTp!7S{E VvZlU 2Y[|~޾/ߎӥ惯;xdʃjH;5cGLêR#:񨂺G7?QRp; }RafI߀ѺRiizkŇi7'"*"?}m\SOȪUXjˬsY:0-JA@/ $A BVWU>ZђN;o ww)+TncPRU饐kACǽ[=SK'2E"鋸FadhK(4c]TM)&(dtz_(c1irMQJ4g{ )^fs ">:kP`&ʖma1#z#TT/ z!nܫU /bh Bt]k,G׮AumrS=|=&A#[9V؏E+be9}r+ǂI :{"!(Céh^jAGɽrqcNY@_qbq6A`~WJ^,e㠦Ɔ<+MYifL)|kFb7Ipx<6?wu,-/؈430+$7}SC=gH$NVCLvHU&D.J}Wr ^;@aOT4B;ǷMcgBRGe*:o8ޞ1 J* #d^"1R/Pe4zҲ[#.KL#3t.1h0 (vwQ+/4` z ă В_r ,=1 MδD&*?') #sq@1r3EkAo9ߓ$3W cL)m3_ao>f嗕S6[aR!1W7 lvvSq=Hc.&~ӥ̆ڎ϶ S*=WMs L`qqù/rpP?98(ֳa' T-q7;%XݐEkwgT.KaS̞~U "aħ Rbn,cy;"(kWu5j{Fi}m3X_W9ÙTR+4YÈJUzth[t}{tgILpOEaϖ;S @ߠ´1}< ֯v4ڵ/4 2NMu%[ئTmxuK+xgŭ`U? W1B|MQwNCV2 VΒ>;K3(i_͈(YyUJȱ6#SC[aReR(bq?ou'u|+"?_0uF1ەR,ÄPv,l u`\e6S#O V,xrTAau.qvW,1B ݪ GA$.z;V]~_9mV'a$tKnفrK|>@.W_d H1;v@^}\vf& UC`~M}dFL>r>_j__(RE/ c(e*<B7{hB":K:Pt&xy̍VXІᓰ R%]cj*.'3ps7Jk $1$c.yWo4\ z5NnژDrPڕ/߰鰑#BS;N 9u^Va:ߠˁD8`ӱp| J]O,B McQͲ!uj|L} wyE)Ww#Urrg'vYb= w~LAF; V Ĵ(d⇋lq{ ]Ss!9`)J8Yۏڶt'h'69w֒͗4ƷpBNN5  =oir r7IJJ]n,&oe|G%brpV`}q[bUuhՓxPhk|O'E^m/u>Vw?QzVuU#0l:GW,3UZ\ "xqz[$ng4mf'ꢐ:o~:Y^۲Uwm)Uuyv]HWh?/׫@_mۺsݪuI{"gq+4b 2r?o`(߀1-> Wx`۾]bNV08Цb8f!vٱiC#),3> U=J+M*WD9dMa?溎`AjP:8c~_5])lR8Y}x?A"s F)M4̱%s<5VB%LjFOq-8Z*b8XGr 0uQ͵Mm:Q?uIG2oЍɹhlMk m0GX?>%~p־ ڲ7$u?|JՏQ4υd?p5]U08|<NA'࿭V] "өhzb#tgD,:aK̔_ql="?kMiw TXc?OjL5kD(Ŵy@]ȞH^g&5 GfH$WI|'\|ןKZA[0>:&sz_ri/uËݡLf+jP;>K~ysGZ?6/o>KVG_IU2e5bVhE~`2 4W}Zjo%HASиŎAmFCre|/*gMGj@JNLT*RczslS051s m(v{Ku˥#dElh,{>$ TԾ޸\s~kDUYO9Gq,҄<(xF䯭lY Q!Sq<@{Y `=AhMcᾎq1b:# _\YKP7vbИ%ͣކFDz3i/ZbGho$Է4G%Q-Iፑ 76r)E¾eAW+hd]j?d|T-Zؔ>4ϢMKed̩fXRpaRQu@g`MsXX[=6 殠݈L%TZnG)6QlOꑌb]"R-C61$9Ça W}VM[TX׳8qaU]z̃(uDHf2lXbKPw#$dO<9"FZD ˇ\ K-9PtM1Rx Z&G0A\wfeXhZȨU^^?`=&Whny.UlҔh;$ܢZ0]UZCn$YPɧx\}?@wN* ~!^pҦ7eYc#9#k}#Y:qP>N289'D܏M%`x fmZ߶]J/:bU*Lrbz* BwoaE,dN譿1EÜ94{yخL;*ۇ7ߞl]zMhI w}ѱ e|'Hw^%2$/<}3eɷ[?Y t?kK٧ȑNz)S}3qmWu-C0x\{kgq1aC~/ͤת`Jw_yWCJ$FhHDꎭ6G*dPޕ[\8`wT!ܹ)t|0Ĉ0Ɉ nƙ"KjWq^ CY3K sV = '7&xZo]]<`83-2J}JͿҌ'`sE<6ϯ:Y~7!0%+ l"2 p$.}}rTh؏zA}'ԓo 0K:ٮ0Ѻ80yuÙx cBZBI.K 7w(Smח5X o;AeMi~[Ӣbx7e_[#=P}lhXt$>}q% :_q+!cE;OzT8HM@ƎVȸeNX:y}XdrޗM()k E3#[zu*I!ss~p)!,sh?~P-b%w~k0cCܥ*+ GoLLL˥橛4Rxy1U/J.̱ HΏ Ł|KBQ ݫ_B dNTQ^'Sի4Dz|k~)htbHR"蜸;41W# A]8PeLOi_Y/*K텊> 3ֈQ'0,>9b-DƲBY⃷@1{BP:Hwm ^y^QȬ]x]ih3_߿Xj4^VmQ$j罸; ǎЀDf:O-6f/Ogn~-o/mҠ?̡f"NM@Ɲ"#aD.rbAk{W6, b,6uR"p0*\mnF<8[uIX!=*uT$y[ n5}g̢Yo+m&qvẠ#5\5寗9sSMTzNV=OAl*S+ng Tv{ 2 YZ&]Xq. I,S%4Nn ͋o;EU<Rr]l딫6 NBZN{P AS 4F1!(T/w]FnKO$,x.:Ack+xD񪏍3biE}V/ S_p!)6/f yO?S + 936D0EG\gPtVԲmsI6`K(s##9pJh֣{!Al9S_W 51:\wbn hm{<AKDXD#?nf"dM@lQag|- k(V>#,5q͗1ós\%C̓I<|(?V yhӘz&5غ3~nv`1cuRVtz4J6wlX4V, { \<+ #3K퉥{Nx'2ia59_]4]Ǘg=ڍ+~\ȂJ8A!)W3xq4uUۺ#bH iFpO IĮ-ZͳFwD4͟m2 #X۵'@NQpNr"= DE^OhuHګ]Gl Lo$T{=m&O C ; VQۑ5 BÈ$DT-]"ܫ&nk;疖7& g~ZYylbQ51kULͽS a5CS3-%`ZjVbjKv䅳ߐlμi-inNXJZ R|ղ̲>&%`uEhqD*=^)EdQ{_24yk)i̩蟑l^ni]8$0~$d ~! D1DQ aJ)ahu I0 f &TQm"7P4WP3j"DlCJ24uھa'%TQ̚HngaDJU3 IbrCEL]Xl@ ׻v-BDM5H,fr7gt`w'CU.X>|荗ɧ,u bo%TOyƽ1[,JBi%t0ȡvto)H38bG.r&rJ ɥ}3)No4D tKӀo Cm5(jT_D;^!/?:/9^ޔQ3BV衊UoiʄWT -ݟR9Yd[瀚7p/FDѩvu@mlOW`I\̑oFakH%OS闯a!fT[Gtxyg*wNGDs zϻODℬj6IqQ_-` 9c̬KW- ) U*=8+ oV@B0๧xI<_ Fmٗ,t G\n?k0u .?lO$PO=*#G\I5 lƌbWV.Dapb^sDrɌϧ/,bo-ؖRZr<Ĝy]~E2oܥE/G[~Gyo$DD3r+  Ja o5j6EcqM`3g͆,4lYCJXԭL.D@F)$HP-ж.*:&/Mv sB`=}E;XsgNE"²NXDA'D]"`"څRE$Q5og44ќ7Q&d{ZN3\EcZzLK wŒܥvM[K>,EoXcXbE|̡{xxԟȖ^X78Ed5"s:= ͕qMkƱ9. Lqm{ 0[ͺb/6ժxBX0gm`O_(#MD-S3O:]h(ҺFTr lwvhk16F2nx_jlaW@qZj|ƿA:qmŧrB+%2ML:ui[eZ7.Smi S&%.Ҥ;rc 6 n4^>n7P޴ϷƵz: {?BG>/e3BoPDK'wJg+Ĉ9OuPy@ e-D,pY$mIwd[@C2fn 9T ^"}O^jweNq}3CC~Қ ڪU4qX$OśCKEn.Ǵ$K+t" {60g4I+;Jݎ̧5 Ap^ Fĸ]Rt14?+Z}?Є 2`Ły@۰k惬?YAUv)|=S>jmiJbPnfa*U+ T  bwpՅq)'( TZz)? σ$E'>1@ ӲޮQ ̲X j?FRc`RD.ƔK_-$eQ32y;>nNQ?́قѺcU,`` CCӷ&Sɴݑ=J$=XGnV')5"*Z~Aֹ⪒IB쬩6| h;EHƤK)8%RJ'O.r1C昷]dv͈.rE1 ߫@ 4&AU㴌ΓȆ *{,BRV@ mS{z<ۆ4&Sx5(]n/!%HbRA𪆛U{N 0x׮t/>MZQ&pql4ȈA|д/E ꏸV#v>)] Sܤ•AȺPt Ԡ@ ue Oyy3 dގO4%=-P~lӽ^_{J RRLIN̹1 sQFft.hq#wocplP94%3lMQ8 r7|o)㖙s]Ƀ5o/Ki !%tMreFl;e&o O@%!,䱀D SyRˀeL >p*kz[zj5 3׎k$«7uۘ[~yg[† |{o;q@"c t$7Cf^ K\b?b𗦵nZNG5`[MNYT[寮Y/ ُ4Ymo"X6/ʯαd6ybضsܢH g|ڳw~* K7r1Ǚlt@Fg.R\`y[׾\MRM*#k/X1?^o [t#n<3 9WOY+|C@8u ľʃC/!LcxrtxlxbIZ,Ȟ9μ7%A }Ia3Ze'\^lvH}R8#X3B|zW[! zY` d=?:u>|s ke",<| cݬ\IY+m[O Y]|9V끦p]Rk_6| z6fXAery]S_ZstD??tJ&H/: V%)ėܤ! ki$`ZirL۹k+QBvw͒!4fƼt Z. #/ Rc1kP5Ew`zxk{0sXaٲK(Og:fIws~&E.pS}7=\Np}CQDA]t_máHZS:4(|@E}巺#^Y/ƃ `%HC 5و v(χ- $-.El hgc[XNm&"lHpg[z]Xh8 ?7 _ @A񕚛\I]\?*oneO'J;~6ǭ4$WQ;p/ ~voFߋOp\sH>^30cӉ:j!C>J*J.Qakxɯ_8љ[*®U,fzq+ N)$Ndǐ-B΍DJoegtEY XhW񇇐ãh>Zqn(kC_/y:܀Hw<\ .: 8 -%%EFAV)D:(d򲜫yV}m#&K3ΌI,uAVp(P0?#2qSt(g o]ėB8=m(',.3 W+[Dd~~slJIMWym+tUg L:pZthB lBa$tVgXq(2ɥW$͙ ? A^w%[6CV]sr%pgT<\ ٯp":f{}r5RjyQwqP2dͯ V؋5VS.[˦0Sn5"&{t0T`GR}F[k-+WϏ&Rj|A۴l~FB~5\NɺSvaziՔiRw`ic]$g1Lۦ8S(7ѫ[Ar&]qa[ߣ7 _צհ';ROXȀ?$Sr_BTS=3fvDksGqM'WJN( z.7'"©=传[\k~L;.nf}} T||׎k[aDۣ/Y9+Ա{ l}L|&"dHAZY&[ D{>duGgGz y OEcncS `ƞOf敚Z>#Jod!Pk_=0HX.jU.7`TÝ/#ig?7]]W)sI$J1 ̒̕§>"1rXziX sZd"xҦ+Y$#G!l [dz?JsLgv  [uyu/_UHr}t*hǛR/=S(I#p̜lɪ0̂L`Y]D_pzDbV(n3&]) d*P啝f?d0H~)u!'B#c s5ð"k"ط]JF6%^xTGm* -p3UI\{B$ zS7dus$g(IrYl<^8LV6 r͖4wGaQ+W §31ixF?WÔ!b J(\0mS$Z*cNO6wڠ<'FUs+`&,1T[IOE#:bdFr$ J?h<sSQdoF,NfL@ŗ\"&fP~زH=7NRvxz+=Mda,#Mh*;:}/>|䤜sDWTG W9_A0lpcvRa0a|+%ſ#MApeҭWRm>k&f ?K+Xaa^-@#XZv!+$wޟ֣DA=9-b_paˣ6z~,MJAvQ'aml;R;t#]=qT7d貨QNo,:<êYfTvd ,/5q%F<׈lo,`AcG{>fS8mf>76quhs|9J͆ X}JRU MlisCn*JHI`WޒBBBj^*s說z? bLs㸬/,Hqǎ:U8J'Cڿ24EĭfO$7g p~D3ZK0rqE8`Pr[hfF8kS"}dL%hܭ x &4E?I DVm& Ñ~Jl>%p4UQP XD;=9+P%kR=۝t GJ"WST_֬MO`%eԎ_ܵG5ƥfs4~Wf ^̎R/ntEa+xe`&"slRI7p(۳]]C+zUk vo 0GU,ugֈCQ/V7={a};.S3CXDhx0Zk.wZ̑_ 'D=ƹFb"8=I8(-ӪC\LJfNjz4+B"fo}ص ǵ^Xuuq~SI>a4}wzQOt…!qݾ-jv疟K83ۆg Wo}hլ!#% dzN sYk3AEz<"v=E鋮ejaAzt'_$'%-k\FF°ʽJsS+@P14اܛ YǍ%*FeʅuqQx&z40|0VkK $&'!t,N*HKr+$4%A/? R̈́+XҎ?F"$E?(9& P &UjݑlRyF-t=7~-)?q yX{_4&sNVy|sOF9&@\iTՃ=95#כ&& *F̤x{O-7X“uuub' a?/õH V`}1eDuu'юkE`vh%ŕԓ"_`KT{+L&0k ͺn߱_?}h?V_zFhH ge[? Y (PxR4RE$ד"xsد^ݒG70kٕV 4jÿXJ fǃq+<0#Yn!lxSe=qVqXM,wkvqՊsJvwrF>J7ˌx h8j@=C DSjJ~\%}0T|ת۴)zpfZ* ȋe \r$NƧQy?`ZA>vCΖLpU'_WM:'z4@ཛྷ Z_Z6%Nu|jY*B"#⃑Ϳ%|o0B{`{LweAʦlY8lH_ߞEos\ˤ#eB۹}$d@gR8mW:ϷLI"4jhz7t\Fd孭+wCӅĢh ,2)\\c .uypYP IˢrQoRȷ Tcn(c'<z^@RׇϾvdc- ye1eSC~ǠM\p')Z(N=Ȗ|agۙk,ekײEexKco&˸izXA`y&Lsa>bcf!ڊڰ"|o_-D$B&_+&?3 .M}Xw{ &)AĨ** $6S09 (vY#ɲ_Ods [S츕{#)MYBkuBXII2|vmLz\͓CsGCj%^@ \kx.(ymB w)ۮx 3Bc޳U cD4+ėd ̥?7e=Kz I$qפX7z~;[d,ľ~tMz`Y٫ ?oyzQ7:kI2;޹vc28.,=)fe6q*j0;zO6㦑"zʬf ܒR}F1%pOɺ5Y^:.#ze.G9iM[W5K:A4m:w}"n,Od9]k#{n3/]{^}V! e#1syU8hAy/*d3SHPRc #k]!>T69,P9$㤮āJUAB*( {:3nRժ7/sS $0(hW B[?9́>^5*Wou+`w"mӈSq R ݆O_BXWG:X}KဦT0(Y1U qV  շ9Y >UGk!_OItYO ۹4Ԥ}G0usHD$[+dD,.u(sǰ:H!UR\\;O+E9u?[@w۱'(O`Q_ݫ'G{-E1B ];[k5P&.eٵ[4 tZA^- m<_:xo1_%-+i@3]вBj_E2x:AaZi7 qgR;pgEX3leodVܧ 2HfIv j7l7n%di'Xi:AcXmXka(A-M&"wnX]S* e8"biy3j[ԈpMCM B.t,XP-Ǚs/gZab .92Rb8w / ">tU$Dlѽ /V>J21j4vb UzL1H<_hBVL++|IK}9'p K- ߱zrLNr2 g*$ل̚6 ?PKO}%_VҴm*S1go o#`㤖 5LQGS` 0#6Jy`rFV;LV^>I!rׄQt'gAM҆lL>;Q d"-< a+ iu2VnJg<8h;<\a@.7ͳ'`tˉEqLj'_Io>s>=gm.ސÒdi?/.W.1:M/Y*\> ڌwnZzRq%X7DI1ԼI4xi71~l>8]GhT`;ʱ-%3>ÂrD,YYWt$_3 Ƕ'k̲N8""(ʂ dˬ(&j,Վ93{nG7>Ѹƚ_=(R&8' 7!mIYקo$T|QOȶy>b]YW5G>dWǚUhI~+!-b91*:)KfPvpζ,vKxU>J8:P<7m y@B9YWa؋%1SDqZ- F*oIPdzT { ,_iشkV%7k3CfF?=( M!yD

    3 _}I%cX=bh 91-Pcs#s6IawS*װc0y2NITyҖYPE>,}]2LSrpj$8r`ϙƟ{z["4K2~C81{@_Hz͉>R2%$5^r،>|U2fwg`\8T̂/TMw`f1d)%=,f:[E7e7qKa+ʧ1DWGp2ztuM4.ű'PÛ㚅g^s OH~ޕ#zs[5U~sqzGǽ \n3ւ"ٖ;Jxᔃ<Ư>,!Z5ݢ6VМ6l?W"Wnq׉qǛ)Ozӈ9 "}n(KY;uc`mwf%DFߴ; re偫֛&zs}()v%^0]fŭ[{IFCJ[|;q 9-pL듌5r;po[~\@ I͇Bs{wyACeOUNYZ2m)&$+2A,6D[t(2y8$jM ToM{H'ZV#p%逎eHD$%yؿ睉U3.5*2$ NӜ}I< g`!h3nt\!¢ԁsrYR֗ 4~hvk/0w'~K&r t|QE;k) #*PSn!`ёR='3 MlWT=ˁސx8M HjSCy Ģrq`Z\ȣe)ɔFBh.^`kIcE-\e0G>\s+{0E,6'NMP$xw−:FV9n/V1)'"=ؽ\$p)X,ʮAc?5)V \XP;۟H^XFA0ֈ<^.{\UgcID5\_@LBP~I})wuh\lG8c䗀gkK0ϹsE|nh0bɎ<~gMbB])~`[8 CY'9ȥ@Y7A1,y}2!B6CĶVx=&VH" P Axfs|ؙ m«nHBpÓOl I O,.Mxc'QFCly{wlz[k9!HN ǢsnVi록rzh6-`abḀ^jF_":wr~Slѿ RSw#0 C;Ge@E(`wi*E!4\S.HP`g'cqJON≶[<;.],6;?)V ǃF8A0–Đ9xP8lxi\*ΖI PLǣ!CCG$}@4_z$=+hfƔ򺉝ޭFbnMꘕM4zj6r>W7 v 99cK mHIjm_2)#VF lkVuOj5Gt獐f)3󞈀0qٝGȶl*lT#IJBh(On)敠mp?؍%[cjh+=[`v=}u-di &T/^XWr72c.3"vHpY.3\|r&g8qV2>@DL:؉LbCO$G?T5E 'uȥC߲#?&~DY%gVfOخ>Ux_ b֍ۛj$P)ZQ'biɦ=Wb8=>v-Ś]_kt\B$]_R-vjڲeii[{qX~_wd4 %M|r'"~5-U5[ʎ_Ŀ?o=Q= Q;l8I gZ-pHSdU(T8TX=dH8SK ʢ=\kATW)~Jq~cqwb86[ m3yd;|R @ 7) H4Ednf2^ԭc H`-[< >CIm ;گ:.h%?e}!F2"b ɉ}Thw+D:nezkxOF-Ԡ{ܪ|ߢ]Yke+ߦ 3GxCl9`hNΆ4B&ywM_ٙ}a_O(TvKbH 4K8_\-|>F-/hcKϷ53{]E*۹̥ *IS?B16A_⁴w2>{a.ya_GɣVI$o%!V> łFEU(wz0‹y3I  9aQ7ǖN)mihlh7 lhe|Eh}Xȫ֓|#T R\44pj.g9|"6S2Yuavig`*]eS? )2D(<=}Z[;ōyDGc >P| k=x%$#}MG\ sR!#8]9*_t_W+b Y$4rtm7Md~6^jGF,Ie:aJCtrrMZ_`s#*4hO|JfnXZS:mALvcW؟7R uz,$Z+{؊z|*׃xkv!r%rqǹW4EMӡeaсgP1^("*ES lKՉɶLztiL{a>ߝVHk?Ȏ%ӈ%gX;1_'V*u9x.{(F Xwuz9U{x Q5Z=wa+(qjx|g΀x }X<:ASBq,=ӏeߖНFe8j jrEVR95cFW.I!lLߡՉV!T0S/.8e~8Ζz9b}'ZGUd4$=!bK%c௼YȗQT'C(AMg=oPpw}I 5WX>c+bK+?u牽HK!n4 HN/ E#>42'Jl iW84HB9WTM:q ( ԧTS*`wbJ%u8GPL^4G/(:*BS:f35u/M=B"gLw)5Z/QIZt MӴmK%Qѣ;58.p>G"m!80T&/V.aqkY_5\1RnEO:y動\Sݶz93I+rvxfCUHA[QaG \U vl6{݊Rr&'K}Y:$8u @ـ5CQs߷kBn\rMBMp2*2 ΋-eQ={P!++lh*hf<6}!U4$l:zwͩl~4lN UѱNcW9]0-􆕱F`?Xw1upay[ y9U&@ m0͒v߻ܬc3 _Y<T=M0vZ7~S?/=f)E;(" !$¬1Փа\uL?!,٭ u3|> ^3̦T\;bpE%yVgTSĞ1Svakr/N~ +BTŏ޷ZER"61j7Bl0}"~S~bٽ:MBdBўY8^+L <m |S;PT^c-9Ojkoٟ~*jf)Kj+Xn6 hr\¼s\E;b.ɛ1f:T(\B7=C"bE`oRC|n8D6ǝ~=mi2˵ʓE[WCOW3 զ)4OPTUvaox`X'c:<}|nŧ?>ӄ.P'WYLIS9+`U|nM긧d{׏b/YɄϖ2(> /y9^3z`GiR irs+`]F@dI(ۼs_' VO*Ddbm3#. :kDz婷 a<8B絹2}w)`02'n-v>.%3.$zaɮ,di4 $ |S"o9|Vc5MulN~fc7(ɉY IZ3 \W@kP0}J㧠d WCxmǟ!:z4{J6Ew+GĩVqeĬ8@1̾EW7Vmh[]`%GˠH.whэY@_HAE׍/OOpAC>P> C]C$ho!SjjV:,$zw!$g`rѝ ,L^)ZWO  'ol+HEb~~jkU;%ȿ$"9y6RYd& '5Ѝ=FM͡y<&f^@ haԁ IʊHUKBh [;`ڸK^a+.\ ALMoE Q]!qD:S̽:0{G"8$j/QKK6ɥknБޑVV7|\oSxՈrUv`9HH//RLD_f3^HMa{8%'wi[aF؄;D+Z ǚvifUPR{J/;Fpq/^D3t^)9@V'PclGU?݋ P+#076_ka2[Wu/S4(t9]nyZ;`x2ȿ:ܼC2i)QvU #P:4<~0̲u{oIO"VӖS'.A. ڵչz.*,`M~:3Ka#y9G&fRj )Ɲ⺢>C1$h6x +nuQp!f+_Ob bƪKl#AN8\J{Q%â 0;J{+tf f .l1vh+Af;oa ھ1uk BKZ\h/"'V-. !e!,D^1)ȶo,`ouVQ'W*U5[ѴhT^M!O~M͗Rʲ k~ _FG9B؈f8ֱlUY4]>" nR?4&VEjD ;X|X&4عJ+>:v¸hf+LIZcpOsnu4_ gI)- /*՜Ss28: |TB0C1$7j  q6y|c֌;/B%|d*FcIҸn/C"lBtsu~KB=]f`u,`VDdȑA4+#^M+$:3u=_-:,9 %*B\EYiÙ#Gt ?GJ-a._)>ɲ9>% smq&U=R1pp=xDH.$yQ$Hǭ[ׯ1tE ,&)IoK< QsMs0{KyD*2F&N.a,ҁhx>+ө)hkw2Ke_-p2c=d5t RA*eZjJl—lr1eٖTuP vtF~Sll /]lFqHTfn/rpC(ToJ?|tKL3+$ v S[ 8{#Xɹ} 3 ~0h~ rZ,GAZ \&,Bgbl)*Ei,U.h5އk_Zn:_P-ߐWXDwzD|8w4"Pռ ȿ,~9Ŏ'c)-GvJN1+j|BxZNrI 2 2Կ|+ҋ$w{j\A (VqZDG x.j6Yy;=^54H̝KR!+xv4(&.xoT)e)R19!1;J2DfK?LC!H:1Nr]JH)v*lZ Ғ{ܞhD#dFu>1ήNֿ >#'uOB:F.O^f$ΞHNqr]!6V%TUeϬ8TcI"^}mRikbG7oy$Rwf%~EG\? p4w#Q["}Xˑ6`^ϠbI S1`Vq,A" ֗I3Y[2x]Q1NC؎N% vj1c1LF>HͰH!" n`~x;1uEe{Rdx覶z9בrSҾZv618Z4siùiMOEh1t&@N[qd~d`aSJ `-\xo3XR8%iL ꮖfe7,KFR\KnH"qm@P͑Nq:Kc~8ŭ*M] 3dD|+' ٿCyJR}ꤕbot6Ga-7KMGF˧t{_6OpD8K[ܻi2< $ڤkT?p ÷qz p %_;l3hٝWHXωnx<4 63(4 N+. IL6Gi'G8QFr4[OWRRkH4PVi᪠vc0'm?M9E/U`VoY~PqG9Lz20`ǖC.I3&ߚG0@:qƫ['Rl.-Q4\ȗn H[>.Nč-{گTpP3/9(ʭymS)j{ s)hc#y fTBBr&i3 Vqev]MjV#2]qf\aFe$H<o/- $61kat=Ak;;_u7#á]oH8YxYyc(fhM#6c"HsLNNk?֭9)9}NgĜY1M|.?MOicbmEՏwؚh%\?&Kkp&6YiS o}Cز3,1Ibiӗ-Ԥ#Xv/B*nqEK. Oy!`\\goKC:y_)=js1v'Tu:aV,N  I߉]({a;XgPh໣.ސS hO1:(9Y['Q ou0.O&SS=A!6N܂!mMu|/NylL1cn2?Ѵ'z)3uc3GJ'"bV ;-Nv;Õvk:NlnxGs|ha}ٓFCH/^bSi#6ɅRމԉddh (i&y} b nkJĒ m(KZKIhj2W"P\?ufhBkGjѠq%6"gzW䆞:C# -a5IɴVrN }z?6UqfQao)𲎼rݪVX&Km5zF4S;ՋM8l{ꡡC`פ?>؄Hɢ+J*UMxЁaf8>Z}UI;Qb\`-03@кn:'3S瓏 .|DoEA¶YJ/zogI/j>lLK]%p1*Dtq&b0Ђ:>H/oB^[Ka}pgty,G[d ({-2QGl0yٮ0rF( _B ~ۇ* ?CUùCCo)\wo[qa*`p2=u/yKuׄ1[F}z6<17D(L~r[ۥD3]I+Fx<" &,4:A%Fc48Nh+1Crfe_t +SV!NyJE#xP)h5ժRy2a}>TfX!<޵0<5t?ŒTT .KZ񃨄0CGjY}rQ*ݠ #zo9FTl_35Hf#{}YnpEPw/t 'akrt t5>oJҗXrU/[F9h:nSqtO# D7} FaǃP~VJ{D5PJLbN# ij~ ^8X a;(#]vɋeWl1H\r]a<,Fmrp[Յ=ʨŝو- r^r_֟;8􆺝r M- y8׫Xʲ(|Ad׺^q- GQF I{q(8jT Tٳo  PV $RF]X@̱z^)oR9M/NN!y]@K:{O(Yۇ !PhHڈgY)Lnh*!`&WSǒhT_w%D׬#GA =}D^6!+~}~5X927d$HhV!t[fV;b艇`[7-Se1X(cƹtEVQX"2} [G:Jsۆ6^xX6XF=c}i g>uH ~˯s.9U}niL!hqRBزċ !S`^m0u`yq!`sQ=q@ K?"^ޫ?S|@.;܇GOrFעm $@v4R:TmEib&N٫&CQk&㈱ɰ:}U3뵌eDHc}i юu=`B0;qÙ9lΕr[ "V@=2fRWZN 98W[ Ʀ^TJ⼧)5f&cQQ(M,Di텣z7񑱔.aW\$Ӹ"Y=i ")ƆޟZ2|:?jӾK '{~X1xf(Q~̃=TϽ[HzIE2:_pC睪,Vc-hDЅ!6gOKoX ;fȍ,O4Wi*=` )!kOKEHӃF^LCY[;m~>㝛XsKۖ*,;`Ů/ eedcvceu{<*Yl!Mvc!Nox0`]~#*$Hlþ!I(Ur%sGWUW(lbweV)[6=^h+v*SM1$G:n2Ll3I?ழ]"RϧX(xR fi8ii 6XN/q^&|GL?gH#/euƇ [~f]7QaYuICsRf>*^֑#<oO$xCM^y{v,.J& ɻ{eV)ĨL:)DQ0Ndb..wO-L.\S gUO٪h>e+c杽JRx<7Ժ ܋s_wB&\tL+J~C -:k̛Ȣbw}3SWL2M/Ù8C=Xg Z]G;>;2*H  ,ůϼJ ޶wE{Xr&1SsZBl^uND-ۈUD7;>g.DKu4{W41PQKթV'|/cpPE{<8$̺P_3: );7}| D%S1$Ɨ3Oif3#0ٖ&ÿ1]XX EK,ܭՍ_Bw"FLաQ S#Vs .*dk\G4~|Ho)(f_*tFq[W?<'WR4i% 3@ؿXO3ڟ<@,ӐhR8}3&qԽ^ aՉCk.=[E~-饆 (UQԺFdžsG;t !8#E'ǹ*! )|+V~ Agff!)sIQ"yYuc; m_)['9xq)l{@2J%[aNl0/ȳɟ',qdiPZ1 aƘa8}P& :Hg )$hwTy ߰ ĩe<_,Nt3|!SWaEKkǗ#%1>֏­Y=žYeR(Mg=(ѥi5xTҠ‘T4KY+Y-6ڪ23G@ ATgL N_@&v͠dd*f(c,+:E]gF'e۠ѽ #\|ܲ[3% y56%Yؚk$YnEF~--Tơt]xokv&*m3^<ҭͤ.&^{jsxQ=  +Mہ+<}VUh|\&^">C=}'Q5<~OGZb/)@߰ qm[wfwK]pLf]> |,Q(UA=Q E^ ;qP,[cAUim)[Xms3G~ZP?ܬ˼l9xzq#9+㷯yNƢF״=h)fYenTr7tu.- Rp Et eGmn,!~FUl< يm#4[{G"N$i4wڒ'QmV̨9CX%gNfc(3`nQ9>='$ڊߟW{qۿG--Tơt]w bH8N{u^!wN}lu|rݗ-587%o}"~v̝V'Q+D,.\H+ʜײ VżiT1ͦ[x f#+h5H#-!aSOXp9'sz[B)T}ٮ|ٓ8P;Іj/] {^^C嶘H\[=o3 ҅ǝ|3^<ҭboU=/4 ʋ|!ZÀZyz:@2@\Xg+VVQXhsjP݅U AFW|Wr"suzc;)X "\V̓6bWݿH$s`uk֪'&P]em`dVZ Е0]\ xH~UM>-|.L~|t~9,JD=Ry2k_:w>}ZU 'LݓA_ q9+,^hdX+1q%,M,4'Og-gLNF?@/ bV¨=#MKfC;t',A@etK%= ^e] ԶZ06-&ݒY^U>W/O_;|S{ʈN~{Fqn q2Gb8#OQ:ULbv asX"frh1وҊ J4𠵄OqVҿJux02dO$ O7ɖCۨs&?ZAfGЫŰz˾1H`9#uR5y,6gʼ@/U1췟4^:ʷyY ۨY^6X JdLwmFȹr3M~#]'8uoJLFif Ý䚽Wl}`ȨJY tw)8Y͋;~GMTJؼ촔x|q A+ŠV C! !+'a0~ ȿ4nƩ[ܚ|Į`1>hBO|h~Dv0%Vv`-' 2iQsz@v_{ Q+~MB8FN4R5Id0}D{:`MEd1@Z8]R/,"s_2O=^^g?b|n*Ãy/T喣&RY+%R `s+v t5ϗx&riz˳VP)ۺ#.T3^Нg&5c16%XWAVː W"3c;l;AuHl{ή`3ۨĩ7-6/$rl KyMkq?UIDYpD <&!e B"yg5oPLj:p56O8|}Yq̚ fId\;cF,AvAu!UrE w uA ,yCso 鿨yw9vHh61pHq&>ƣtEꪎȢZ״2du׏#9-)kSL;-=jbf_pEk1JkMTX:d?K'yxbł&'N_~,42̮(xJ mVk {S5EwINėl]ɋ_-gS zA-lB5E94{*G7Am'>Lc˰>xf̊|Euyek%˭~de׻-RGAa~G%^(]ܕECNcXwB4p)K˵tO&8re=\fQ`I/W=|ޫ-O:QHj 4劑21y!- 说$BGI\/. ej"mڿNAd׺Цp葮#, ы&{ȅ уMg@KE3ƀ&j8d%›w,t0>(Yl Q /^A$4tr)B)o`w'>hR a@3 :oS(~~wyf-R\ ,ʎZ nWzc%,!8f^|Y F2Eբ: e8fPG9P9R;( R: 9vb|37} JqO;ŔlYs6䑮:0WJbxes+姚k߀_ *wc)>c;b2xԑ 6`+q#:'_,Dv+/d!:7qG feR. 8#eŵNr8zlj)::EWȑH$o)\@H>(c*8KE8J ZQukU|pٝFg#/XwX"ސ˶Y0^mGTܘijE='Ќzsv9X048~{xdEW>/Ɗp7Viօ) sH1OiIiA9PiF#Hf~Vb [Zx#' I9wJ6]}ީzunoи8).##IOF"Bi8Mq 9F Uc>bC&YUz3Lpfk%:L`!2lNUTs}5һns]'&s&.E 7=/-j;čg%g^08=. ŌPE#bw۾M!N^kXY É\ 7 ,B DW,Բn5Fi`ێ S*1ܬ2Qݡ23F@f K{w+dxY7O!1Y!a&r,f#myS%ě[[ LnXbB(r(P9Oo6~ f?ftYE;UB-Xe6/gXٔѷ )YӄF'51ӯď.nf2,P^Xt_.=ߏoD2cwGXu}xg_pYr77B+@`vd)LW j[kh4i<&]+6(ָ2W\i8Y稡SEj!G+.WHÁjf0|WRC}6[* eZ!3L~<5$6xv4g tB)r2&lސ\IBsTlV*#lEW=r,:/)N2h4P6fK'}#aO(F{VjYqWJÂ/#Nd<ܾd˽ 畵%>zM ?!Ka٢^\l'9JgVz|S3P-LOd ђ0q|wGR  ./U/W"4\^S Jc- |Wg BLa4ΉQIxg؀6C2Y*;\~%բ<)a>q5FUdX8>-/ݠ DmUl>?Q_8"bK9Z}EZr[* u5>&/ԑ 3ۯrz@P_+;]M`n7[9\\}?Ղ5ž: 3n+R = )dn_hP6b=Dw!R#aGVhE;hh5a JԊeD驟2:E{9W[&۱)04.-^?b˨(bh{qKGoQ3ǂY%@T|aA`~Hx_g*m!< ڪ DJi S%}x3{Ռ-POI}jq@;5e~P%Y1/H3A=BF.yh!J>LrEIIl;1ji5_jqYG{,8P TT&|Hck`2jM =r+(\#% sqA2u#K;\j4૽E:X&Nu%t?4'9:mI#Ky?+ݮĠW&[ =u6z,8x-SZc4=Ko{~^*4Id 9@8]n+lEX6`z(%My ]P],+Τf-=s3?w }OϬ)h$=m|8>ǐh6֞Mº޻1̨܇dbٙY8ڦ}RGC y+|3#5.Xhs5#y1؅ xψwQ iOȒ9Αz+;I +-6+vl[q8] fJ˿@q2nw#y{r`v0v=|u|rS_IQ 뽬`ɥK yxlM* ڇkH{H~&I:Q6Xjx[nUK GR'|jS){,B Ϡ㇠u"1i fQ%mrh-*;IV*4%M%Wрa/a!;`+ݰBX=1ZRdamW3C MEr`lGK&>ve=7tǫrp tB* U v24]t'׮FZ,Sg%3+.{|:g2/n u"ndG#8OME2CRB$ 2GB{ar3Y2c ju];,. ޞ^n"ϧ⌑мP=oRg'Ÿ3ԧ k4 $m:.Rmo/ xWyYVp#D,hٛHIϾNWku6#*6օTx_^?S+?=*nb+bZ=2(XaYMܤ>qN,LDl?UX? kfKXM]}. ͈Tk:K|yMp/ DT߸8pBfn"ҠJU]\[{֏#Mf=AmrY},v,ѣA=Y7rH{aZ-ӖWXp%&Qr }ؼ&^כ=%!j< * ҽK[f #3=W;fnc(u5 2ܠ+bhµFjZ%F51߽ɻ|!X5fvV4:{XpGM^.̃ u"<.M?o\c$:ZAYo1g0oWk P HDzz,zCy)mϼ: K@,pntH57F0-{9q#߯v|Uńh$]LA`w SQ]LÈRAvznoPƭNS)+c HM~~Q/M/iqXpgl\DÇ@沬p<Pzd rH2?OQ< <*P؝ V"O.thׁ9!cU{U "-<`|_[N=9Rym  Z0Vk[j@j϶Ʉ7b<޽/(ä4 u g9E480Ht$ YV]H }VW oɖz7ѩ1A j`"|j{q.5.[CbdIzfR> 8M0ù4}թ8^{VOTpm}%:4b3$}5;w9{%L<)e')ʩAYzi-^Pt3csNA`M͸U O}ą-l~$##x$C4R?+TЍ P ."W+18R)L UiK?)2!y'(D_c:%hѼ2xx?)W{ ecCwxQJX{qyGP*Yؘ]\O̻іJnFK[|r_~KiD"NL5=toِ-ԏ Xܑ ˶ܟ(O_5'n71t<ʁ,n/~~H0ڍ8hAQ 64ãC3ѡoGy}4zծKA;ܼmJ X$7s7%8 ϥsPY7-MR9EΡ$ߙ[u2bErf2Š`'٘toEHE%vOuȥawgo) ÏfӝRBAYWpnq vk60D Z=@ US|rPBPD !}sxwо}#/Τ̙qo7H c|:C,CE}3FPM&",ksf4); M暾+wd7]?ekR<[{-޳cw1`N;TIBINZU((]q4Xfqy% %lO l2;t^^_'ޕA+OL<\‹Yl("Nٔ }F3ST:+(@ L m9KQSmX7ØkYrm8=}Է_L3Ɂ!V*|-#TBv@buU07$Rbxrho\]rVޝ{3L=`jNރs0j{/>Nfg/B+ͧgwh+/FR]hgB 'iy ^`{5P%MVA](:j4S30'*2^,]+AB8|X.3뫏#>Bf/0%1ѣO TՓ-Crn0ߟVlyioHN2H1M#f<(|)$7q>w~:_F:7(gT]:`b0ܝgIhp&'9B\]S_yX>0, }o/ҽC\!C'C+h'"O(QZaFn\ Md\cW#$;E>34&L\Wiݷ0‰Zlj? %H ȽӲ]2U73˺Ht(~zK ]ZO3p4δOa!?#w/~\S1fOb!4CINj= B݀ngU7',*$S'x我ѵrYl|]Ձ.z|TF~yK4@F3f+"DXL{UFW#(җ拇ЍJkX*HQRD:I]^}xsn7 b_]1a!Oo[2|8/Y:(# zK[W7Tx%:9N<d]]_s9iT#I-^JlTA$z顝Ft\=ɹ7 &kd=/ޒ+׶0QWshL)>?10EX'j(5T5CJX*mxg~k6x|i[*_&w zTi,anm1 qmG\LW 8}>"=؆9)_֋t{B⿴֎w#|)b>H|uYW{gkGy Cpӌb4YA7'D?܋~B@wxi^ݝG1\Wi)G1Pƞc7ohPQ3PM\!KER!IG=b| t)7/s_B2fo67X.4vjm5˩1оM)P3 zTe&rKX'X W&'~]$QkK;*BqϮ}J ه -F{ *?MMz-0#Y`TK噾˝TB eBeNZlQ ^f٭ {^/OP& <`avLlV8].8B*qf>D(47o^GL#y:K4tpnP-^?Bpm<'ӥB?$1[HI]J%OD2{i[&g%=%c %"74i4Dz ,gX zg3SLGb2ldֳRL>R6-P>gFUNZ9){Wq C)z#Qs޿Tx1dюUr9‹J=}ZgwQ*"(*Tےgrv.d<9x?-5&'뀣l| I->7"ъQ+@? m Et|Xe!v!x'(Q*mᢌ0. ]CG@&b >l0dMեB-7mSֹm!1$ sܰc{3a)sqt1 Ed}:cR"Py\r>q}1|'b.Ycźľ6AZiGQq hȨ9~ңzTH `zg=K6ϢdM1mz+MnwMyL&7opȈFuq+?0OӋ|ɖk˅ g` ԜPk#M^:7rpA.>btR\wXJ j{ tں(ԓO_,srƻnHxQXQr>Ti aaw-KPCm !n(֒Sc@>З>پT#.$! J64ZSE(T:̗[)9QrZ^<̩!@vǽo":-J*A{ ms/?]H+NL o'e86e9ŷ!6ml%Ptg K-%LA5d7R%Z Jɓ T'qeSƫD4̝璣X S}|jnAYХ @YPoōa_}.\.[ċ y8D;#?%e϶L@h2jSdoW6Ƶ@S}L4ļn^2Vϥ b+蓕!6N#?= 3m߰!jwKJtu^97F}҂o{F֥YU{exkhJoVbqIgY M8!d\UjkhJ<0qdmL`1րQB"Pxb aah=C!; ]oq}9qU;U%,R}> /nyYo8SZL-zCVu`b҃fJU VIs/ ['Ћzb-}uB;_oE\mgK̺iRƴϘv짻C0EtN~auRj^%PRFc,j^(נ0iDVU !(q]sU;V4b0S` F,$qnK ]۽;z!HLj OxrfYnL o!For5a"G5d?{XNXBS\odeO©6#ұJ;Lc^NE>HoW2ԫxU15H io'x5Af˺c?RQW%1<hbbxՆmh;Tn(@4:Qn%^Ʌs٤ƃ̀JSaЅqNhj纹 :C~חJ%[;"b,W|=û }6rQ_N<4Bcl+T? PvX4"w.WO.S1hr ]^)2صQs_r ]_fn/Q738sxHBЅrE-B)."Ĩ0V%Ru =+)({A/Xn]9r۞4 @ŕID_[5De42KSfa}B0λO*0ɂtl s?c-PnG.9cgY i_xí2]N$JGހI\TӒPv8 bUaN}QpcQ5F9̾т #~Iq']#bӢk!jZ>7vO>pבvL.iznŽIj5a^~pE2B[lpH^0J &UI"l!S'v պOuQ ^}˔|Bj b$r{o@_2)*ΎJ7Yt"Tk? K$L 9C x U@5IKBػp(%GׄH#Pro"` I E<'3[I6N0KFxE5'-΅ xfEh_uN*EԜ'39#JTK@ ;1{ 5l\hL\RExSä>_JYn&ō3>:)4 >ܾg~:R%T~;¶.K~:8Fk7tX!PL#AIdrmOB@!,KiYXi-U5T:=="#VHs/`. ,R?4XW4apWAx.,19( 6a|Y]U(-B~v1NR TF>a Ont_GSz (۲4:cHؠP(}-E[vaQ]u?| (kM= <屩&!S4C))>J9i#.:uuUn{Er㠊|'13s ,ӟRt мm5lJ8M ¹?ȤѪV٬̒6z4ؗ2D&x 3P:Ԅ|K<$$g[S3d;<.EyuSYʣI)\m0䊜m*ƼD"S.M"m'TQ)Bq.G8ph ):K[*kX~s5%W]oDLVˆ`\p4?#y80>w#]4}\o} o{orNg6q0ߕ5A=O$\ɔ͈57:萒k,[k=1*ðgfN5k 4cnW<|k b%YżuۚC6 =vS 0_zHA LX$q>0"Bs85RigM]f97?Ȩ\$򜴡s67D{0w/G1P xbsN,ob@gw{p*PiAFYC+f:wP!ݪvt^F"4+G^̭]JY7: LO dfcE{9ʔa"08ٳ=-gˍ%АԂqE])aJA#q1suAbwr 5޺x:FU˚μZ=GF5gLK`T;OVP)Hc 2(42XXR-cɲդ{Dl4"A ,adKt!7MJK\u!S3g ^`A0LʶX14A'Ӊ]:)Md.vZz1rV',%U~b -~FpQNvJs;RW,0k]DYw"w-EF$kv$t)B.iHf:9羪[j]roBu SPjTuzse$hj: Yo'-X]T_OMtL,/땜'm96i|&Ive}Yw%|؍ED*A_pia`?̀=fdf22u3.I[pXh‹qXLE܏hMׇG ɼUߴL+7 tʡ]-_n0hle61vlzZ id:H!!k\E8%#l`L]qu#uE:m:TW^nrb.S1Zsxtỳb.[aZs $s:g5sjM,$|ڝ]ml={;PB /yS~Ƿ1sZdp_֫v7<`cxłhtVaBNszʐ GqxT-'BnjҧKV\ GE Xȉ#Fy @p6lo!05Juni|]U gFt e< ƎaA8FlmÖzhAf؞=rSe+E̿a yX˜o_KQF˻΀(ڻܽu2G\@Bz( d<0|N6й//-"A7U¹,Z9#ɝ Tϼog:O\dNk{Z1C*;mZzp@*IGNj@I(j;y_&^/ TWI5}Z{cQesTamOዡZ㿍hwў8x$; KF:!#zArl>nfTJTE>zGZ&B.=3Qu^|H g7W/n|ńEI:er!SPI*ngtpG .wԝo}s mwn J* XOgUIM![Ol D#edp0o%+ԕw7 58rr; ?]ʍm`Kł?s88/@hQaܤa5G|R}|U 5x"oOY}1Q7b!w`rqFzraf`zByƔ,meAZZOiL3F[J=015(Qn%a?D֖K!fiڦxyx`0:#d\.+x,8tscER^HUnwsyN_JnOӊ60`!js1hAqC}*?P[(eX5/Fߵ?HZSvT6Ŋ&Ӓ P"fJГq.a 8UBIy2 yWAv u0{8h^zL@rO壷5GfPbխ[ͨJ;sF|?O DL3hwop:XPpH`훔zK@ac~:ċp> '%Ҫ" mt)yЕ>V֭]ow_kkH9 ;u:tv2fG|@GT1k6zw*z>COn\;a . X!aH1]E :S;0" R:V}_)LӣxM6#ܯ @Ή0FX1G:m9]YjLoexl<|En42=&d B}6ȚQ@PAH撽{кb %|_{ ZOHIah(nFWS3@(-s: k:=a_>$'чnPČy^\.(7/TxezWֿ[GVnc`ݱM"RFތ g}= Wչgݦ; \^:pGDYv[aV5ϒrFMѺ}1giywœD;ٿ 3꤄Z:tZGI"F%<~#"OyR0 )N +U#bդ("/UI`bWH쉀(ythߠ}x/ :MH_!iHOzj|*Kx{ك-ծelfܫGPWM1Tn,*9MYn(o߮KZ;VIsa .T; ^AϟpjiK4;9Ic'F%u_-195L Ozh5GbUKdn!;kUErx g!P33130B]2 Ibwa>ߑ{\4kr6Fγbs*C<#0;-AVzODfiu~ N ?ALTZOo }e\!~`$;PY@&co"A6Ǐ8EXЄ8z6;Moo{A4^8Lg9%XV|`OKwʾxk4]|=/-քȷ=O,J]!IOT-;$_AO6 'S o\;irm63Hv~l :WP2^d3r;/r!/aLnyf1o:U@Ugrw&Ϳ>gL ,#㎍{y,߂_),v|n-xȟbP|M\#6OA1X)zVCfz"K:]OސxwMln0r8:z#P+WI- aifJs}*Be,<X:m(+N]9rz>&  XpϦ(gN,q/Br6'j5WVn&3z.R_) M1SP9´+B?n-p?>Aujc9k+joQ,3f^#}(!S`V" $]_mg)HU<-]@WD0M:Æq 1@vkЦeN_~=x&8R6[?\!mk\ .ޒ'bvPV@3e g4&q _6ĵ )Q\Y .4%Zפ-ǃ(F};[Ҥ&YV0q L].ޛsz>0a~DN]zZ7^2_oT7e˓YvnUq-c^wG/HsI 0tmX'P#8m VUv\wćv09p P\p/@uz^T)F3<t K 8zc"g9tQ ÉIzkd@wtֱ4td2U'8+Yv|\&KMգ:;"UJ+gyWqv^?7+F8ۨ>RJ3 RNi=k6 `eIlԷuC33{CzLd?- Ɍj5ǽ-?d"!q_&qj܂1Q͉;zkPp>VܒeP?J _nF'STIz5} \mPKmKM4ɣ!f6CZrŕAߌU?Q4܎#Y, lDE?R@ Բ*!b A\IpQ簿K Wu^0=(8K~@K8 Y]s#^FC=!pد]?/ TO̧!욼R^f`/TJpSx˒bjamJT5lOM&#RȖ(36zC)q_eD͠|_}̭2`<_V9V}Kˆzi v]y'g*z'Mi_>8 7 UW6o 5ڧXIMs/I¸_s&Qp#j: :6b˵$0^d `;p6ࡦeο@Vy@rvdTI3B<Hig|GLy2N WSjXlubdpmKVU4K\}[BJ۪!Ƈ"Kޢ2Co> `Ue&/+ʚQiۍvQҢS+<|Cuqu /B#gm42Z a-YOVTFb>ޥAsē6۱)Gsa9'!F$up NvE KnCr` Ч3ݥ2w_:9w: 9+g ѵB.Ŵ$Tq)-ubygyZتaG| =/^Yu;CJi:{0Y=_E]BΝhfL^T ?!9藹 o;>yg; T`gn!ˣXcM1㽲 1b}*Go# Jņaoz6L< e˗ "Rrz|TgP(JL!xqi ?͏OBIrl4;oۼ\PC.+E-p^- g}eCq) *-d}f[TNȋV 5,@]esO_ zf"[_^~ۏ; U?/X)㰮rM-Ջ|y$zIX#~I I7j6L8jZǖ찲g ER⑉ҔICazfԺHߐGXl\Pɐ?}"R8'7C *bh@J0(߫J5F|=~xml*ϩujZ{&^2|&q5FW924TdsQf.Y+,CJ˜f HoJR٨!va'{Z]I)fc,MԬaX 'ki}xnr>1ٯ(쥙GPe/|̖7o+n_NBtۑ*1_lzvIfnPwNkufcѝ2>hWvʟ/UqH6x_#P?n|_=&q-JPje%i2Oi7ꀤ*݀Uos|ޙ N7~50Jb@7{EH4dARNLBz_֪x3ȸs\zXdpbrQn~%D9s}0gAHHdJdS[*v;dq]R|V3VmV_Cס]Dh}I`stGԁsYT+X.DTAF/v%[uY~@d%~!ǴSHgq^}*>?X?3O9N_H8Su(XQ&r~;?^B؅ۣ]`cP캳{5H-w+(qYMoչ/ཙɛTJ7U?zS{$1@RE&=z9eC$N;Zܱd U|# #n#UnEII&+˨<\}.%X(Z S5 Kj(qF0 g՛b}.הFPR@phu A${/E6?z֭&7Ldד]ڈgl= }P-YDg{)aEY^:˜`_*u-wkJB-OMTLp sߝd͞k9-Knh zs!S fE4xTE&fBm:²'@!t dg$B@Ni5ؚػ4nRb pڴݹZ:V* SZ^xRJ&a$$nV-}bNAu# $hD i'G]BٱS ˱Y~PG:Bs, W48`MjIe5g-oPգɈo_bb#E1]>U?̲RO^ZWC]=Н_U>`Qx1Z#e] @Ꭲ _-x\e6K!&ӼRr&`~YNx?e\ZmRae@:ˠ#$Mks UNoKk/##-U Yo/i0Hw)QVu?:,dRX5wf[VY{I 6yMV&,^Џ}I72~Ç0j#'SẐޮsBte>8yvCmlXG5kay$UmCYigK.~X?o)ϔ95#R28o:]TG:{Zc}{;AQ`"ś3!:oa>ɱ)|_ЯaaQ-#5fN)/5q]ɗUg)$'I8#MACPCͰA?/1p8k;vK~l*6U85fP`rL7'c=|M[|*Rw"pMsQ0Xiزq&xvv҉ѶKmdaji%~g(_ABcLxGH/^0e~A($ Ike3`0[$/|!La,k\k[8UpBH(u᝹ \K.Z0VY{[dG_b$ CmT`*hɴ]KAzkSI+VR ɴy -eU[1,uƟ0жGJg*k}xyꪽCTE sŕg4qxb<W;PvBڙA`Ir߫6hcX*@%O¨x#IVîGw᱉-Ҙ]V{0w!HtuN2O)DYg?pCQV6Q]"iy5# Nđ (\ X ~T\Pv>s@=G’S -ERDieR%kzskź$I27x@\bKilg0FyA5o];u aqײpW0F[zzk'}4HD%VOºP8r7Pi#i*t{t<_' zC K&XQcERNmayM=䬦/=/QohdA0`^mLEĞe;tO4@Ʀ+j5{F9\qC ʀ2;@!y߯ (Po/Ȓ a5$ ^a4hÅY덷S8?UqGd @qhNZZ_.L6K(?>=,>a@Ov=K5Ogr7'^1*w}6]8 }^؉rcSd8[U\fX9ImJ Zp=5t X5 ;PP?3cF^1Fuf7#tca/yc:AzӲ؈靄a/+]c_ *#QbѾ)LS';$|* 1) W;REQʷnQѶ7-4XP,/f$!~np-/V/ A3 4, 2c7n/?aá+{^t-蔵:g'[{ڈ$kvn\?񎖼mSkiD`v!Ksv 4|'@iRZ[=V :@!}'/t0LȄ yKvA 9ĐW/Le4Yb,06M|۹ZZojUb}1Jg|+F~'S ~"6}w?EcW;{|Ja8 s=XnnxyLh+CZտ[yt'},s+Aasm/ hfQؾ`[l"kE0kr;_[-jvTaFK'g&:baph| yE`[d)t(%+7& w ]Xvael'7F עd%&Čpa7{ros]wXx> q mW=ѭqf2U_M{I?b}"k$ ˄D2V\Q2*vi=S/uءq2>b;ibx6c`o:~@5&a~[e6+"6XeV E<Ȗ|r&헝wA%6"e)Y`p3{;N~k>26b61RB|yd^Lkp؆rAsV\u-6IvỠV5Darh3ۅ-kǼi;yYy,ߩMCVX'嚛wĉN+ QqU# TϷv=Njr #vB̅#}Ҽ8dvəP6EiyuL;κ;L*ezIؾBT$x+IgƜm%*f4hrX[~̮rLc]G@ 86 E<Ȗ|r>z MJVX)#\ k>26b6pA+o2ۖ ?MBB"iR|> ,[o}]9'mQ|ͯT(]2̇󘚤 2?e|5p IM_&~Iv)*oC>\M3/, cL=~c#ىRUKh'?#Z]|(73.F"VFsUǓFau"GIc>}sual>_efEjDg|,˳W`,ZpN鳟/ɴf[vky"lQ%f"(-H󳰺]f.\`qlDrzk)bAR ʪ 5c7<2ͤ`:rnW<B `wKܘbt]ަ"L؃nV2hݠ>N?+F2ZLR]y&A0GPqRaݣ 7K.ֱk3y^^wT=@+qWs[<3D8 ;XH0=_Dkvzm6HWcrD)MzKe%Br'7B`s(#z-],ˆMlBуţ!zK4h'{TlBa,*Afˣp7zdFqHe?[dQC`Ĺ-It9yX'WFr -m`_|IKW\ה_oٚd {+ ظq[{>ƚqLDQQjM ~lĄK(b.#n?>R uG+jQu6 ˊߔw-YQ;L>?&VL^90AOM]ͮxñtS:ʩbչp/X+Pu'fUeh_w'6ar! VVT^J5܈"qPͣDzhA{E )Vv/juB&iQUzZeFt|"c]/G @P.7g,@?\R8!F,ѱp2&/$B˘|sw})UDr=]_`Qey#@ CBl|",atһ n-+DXEsOR>Vq@DYc~l5ěU6[{4LƘ2gB7tʕ'ugE<=쵙fN ,0)%NmkVK{nkw%JJ4V  bQ{hIYw.z;[;xP A&"HI B`qpX%zh 2pZEjΕ0Ja& ga@+wl)AiAvy:MJqZ;bHG DđgOvAqR7'\Q*ltWWM%(:àYf$`$] RkX_N!B1̝n4մ^D&YFp\>te\pG|-\xh:ӭ[V\ĥfHRzlU:nwϲTݶ1^cJi~ O\,:XtG<ߖJ>'O6T T?-na<%ڹG$ZO\kf$!U&FC BYW.[KQH N>u/ce66:Ō O֟ ɮ;mDګn&tt"ڛl&$nZߦuOgp̷/;;g HNǪ N e=2>IW?e>( {8jEdQV_aWR)ŧ5\z~dSM2E~W8t;^_jHPh k8=4IVܚ*T Iixc3 ic5}E\1~E} emHאH/b'^dgerm27}fb*ը N _؆V`/yA@S x:yg?ia!Me1fĎU,nӸ߰%3X/hbEe{,? ,GLm ^l̗Xg5G0_9 9/ ~hnִB0*]RHX#z5d.wآ04.+a~[l`շӼ/Z]['w:xaOs<\,mc'VLާn :*C^gPv#֨cq*Ҍ!/$6nѫñ G0cԞ&iECg M~DnZܪ^1w]4I Jӹ~*8{+}wEsrۿyhָ|4npσn,϶.C(YʴحdO(== |?i|Ůe{SD|Y`G!J#-¸2 %T@%2wSoHtn+b'*MsY^LPle } c*.PwX)11DybVz`VK; :};@6\*^_ԌKhP;%1 #RaL\@*J\ dtÆ|H7Tծ/KTH-Vab(1rO\n;z"[>5שXS#*[_E^~7Z͢D sZ6+Wɴ,1&)'LNixC*lm%u!)Ǯm"-Xb6Tc%SI{}[F=eYaOʍo#܄.49Q36D8q-GW{G/u|A4Pd  ;U۾VVoi8sQ[M>AZc` ~;!Z9:W{׀X5 VqTwK"ЋKioM@,:ĕ[ݪ S~{/ع>=AKxaS58Vy:?qɛ庩&-{ZLqW`ZRh[k@6]J (0S~@ ?+G'6^)qvD`A'i> KZ/IN-UnVo:>j1e;W# #Ͻ΅[J{I=n?йˀ}@8aŖXYqmB@;9Z̥b|2mh8Y. 4K3dtge6(Ck݊ &Nx(ĝvx/"?xi楺5b[Hn(V#l^xOϽog[?ca^Jp~WyZ3Ԯl>he]3wvgڎ@5`HGǛaU]@ BU"C$X|>>b =IOl&S? |`@jGOE9_({3@V F$rE^!DOR!ru*#d:3~tjJ1:epYз8#Sژ4 gbZl "|u|{"bj^#~g<حTPRbVJL5L>-`=7n{uBƭm N*V4[%𾩯B*&OIe^rdQ8w;4dIeў DK&{!cMS" V+x!I@Q^}(faT,A9Ϫ]jRllcIķC-%\kl: bQg98~Ӊ5s P4z9\52rtj@8S*[ ӊ|:S>Vhܹ;ѢvtLM%=-z9ܜ|+KD 9<c'oJG27& tTvl{uɢ2{@4 -xx3@CY+!99AhV /7nkS[_2\䷶}eINvq{;{it h4d/hO?n=J{H r6j'7Z=߷E?_?+Ä"UktƆ&HCR`!Xe GsRkbyWt $(zSU+zڏȁYH.AhaD1_Ilb/Չ4xԅeZEG gHkn_]U+M,Z;_.Ɵgz_6@r9Wod|gy# O&]'xylV"\I&PtA@Ovd&wK"x}3v! ̣`Ȗǻ_m8mш3RLVGeŽ/Rx&+%*E1Vv"܏ü׈p9ZT}G7O!~0!x } qF!yG𭛴IҲ5_'KK0Gފ$DwTj;̀BH;U6㭆;@(Jq% PX9 6i7ClMɯKx/T>1&KĻS:sꦗ-)UL$FoמѠ.=>7XL>8r*Kaj@eʳp?>db=ǿFg*}ݕsBښT6$Z|@ִQRu\ RU8Bؽ2@+~9.ߨ9uP[OlLWo-gP@.A9$rLICz)eB! [Y(s3Ej$_$qǼ3;ӺK7s2w`[쪓 #56qiVM>«ih/\a5B5euk%OADV!Zqrdtb"#5mMG~)MצfaU5:WfO[oOܞ) 'r٫-F \ث|.#^#4Cl+idZJ -ݻCW o/X"xXdX|QZ>)f {{tU_\toP_lA_`aR&]^ޥk΃3zB%i7*S:=h#}= 8N63j0#O]T o_+UI|k7_f"hF7ĕ" eZ|ي(IgFqLXR]q+Cf./,'Ċj½dO(.#H&l#Ю#$aAa\OE;I4{J%VnfA7XRyk(T@~)z*VAt: ozH Pᆁ-f\:<:y}[{5NZߐoV*5hAf(f/mqgTTʚ๫m4?C=7osgx]k$aDa=q&=YG+iFjI}5e.17H(06oMO!"%D<͝t428YG9=z>w񚼣G`VOQnÅ3*\aksW 3X0HQOl뵻F UZَF \!5OSF38h<ǵEDn+J-,=020isJmV¹ˑӬV7a : |*b:_e)6(^Hۤd&/*{$Q tCnnޠrx1r2M.*֠h؞Yڴgǖ34L:bϐۣ 0Lv* Q̠1뙖V< WuY}\$p:hwVg.cvp!êl_[Y*?`ad ktGLG֊r;:]ϔٞ\\dY#`=vM[ngZ xַrMn:\}I_s.(Dv\9̓G vڬwڷhv4t8š]Se)L4IJQp_0Kv!A2uA/l{?C~bXyoP]N=IMޫsdfBpk}-05ϳaYm+#7nYI!u\YOEITPq-6/ի=*ˮo|~N& YRjl4KU#Wo 'S_|{kfDxG`9$$H\;lZ{%8ԳDʴOfC}_DYNYE%UDvkW 'nfzx.D`z& &5:Kr3WD38L L KuП'p?ʚY|I_XOϑ u|ޣ)tӉtQz1l7^ww;:sxMe{V&DF:;h?`I)i.i-aKӼlGA|ʋIYuO0@%yZdewIvJ69m}6=RZ*3rl 7~㯔|a3.xq&b'fԛBMsM:7vqu$BzL3} ЃI!^Vb[RoIz84M6]Bs HXpO}P0r pg*匸}('AwA.pLg\Pݥ Ԓ+f%rXv _@:zp= Z{f tN;wE/R`_/@wv#܉/.ш v\!P$L3 g( GĺeB=I즡dR]Z IyG_ 䴂} i^&=^lj` ȅqL?Y\@ZC&zD*ǰݺŁ69YnX->- KMXh@k\2 dr/.HgW2\S&1q{Xm_ό\p:뀋V稸1 ,d@tCQ>.7ra|^hBN-ہ~?&Kݷ+[ cX̞/% >M YOF+[6=U~"?jamNϗkE8ycw݀zm +6IrڞUCNFt:2ݻ崯`f8]n%76!ۙ\'` tZ 1jNsCn9P [Ǧ9mS!A(<v \TK 3N/!=+r'd/4W۞4#ZI4k/U&r㮄S*TINj96;M@gx,+RΐV`ÖV>dnDcQkh.;j!b6\1":}d~)[HO%u~,){~ݗ5C6p#alj^0w:}mpԔvx;'q5{e؊jk,2x1K2eGK</}{.r;T8BBm/}q_*1.|H8j^HyiһQ:i _hi1Ч kJ_S,ԓ [0]kq ̠mMڗ ʼ9jݥ?J_O$Ԗ~jmo)]qsZoV+ fATE>Hd$aeWIvxDbʁ>oW m̩&،Cb+Q7ufOtk 353LJ+tÂجtivԃy8A1J^\˵N½vy3V~Ã6%pA"5 l[Ah1$ln/,g6WtcXFCd&F(tB*` nOeA1'լќ, hîgT}寊CYdkb&y_0N^`S#s?τB#\,f ʄ7׌*4?0D|WB4fK_Ew| aNyۢYl{%:_|$6s_=fPޗ`yI)͹ll%:Xc~[[dU_sB{s዆\5|,V;\G<-K~N.PXj붖` F-w _nm2= ݌8*6vդn֖UFɼ4& ־gd.NMG:vjtd!lE(DGKzR2 l8FF O[1&*1v5J@tnR*&=٘+XTZ7DCrlpmj2]|L ~D]\`t<~ݘ{C])W 6ZO~{q GÖE㗎.CosS$e)¹xwX~ڊI{!!mse=k0کgn2gk}Ft% |H՟Uri5ƻVkh/ĊǴmGPąsO"O;4c.,+!hW„K9B?:ס"Nh@PaJ3@fgr8cyC˽׀fT618y5ǰSull_ qvlMM13O=kf(;jy9ߒK" >(}/3um !E>zڬ'UIאa>1Dpŧ 倆E= 5lͺ8D1{EgQS/Q'pzF]B\E|dƛ7r.#_a=ab|R` Zm٠Zbm a{Ŕ?6 JrP[Kni=*/eU(wufi[:LϽ@[kBR}r-UҸUctא-g|u1:31)2- }(ҿ: e?~lBC\`" 1֍5 Aa(&m:/ tp2>b䗖KwR I$Gx&G7L32L%*Q{Ohw bGb޾YyUxj[)M4(=聆7"`Ш׺ߒz@GyY+2̬C4b]2ӆjBA䦙pX O cט*"ٸ@<9@O輸rs;wDMA^8NxrW=ghFU Qѡ 2+,aiގWRֵzyM9Ӣ&]YEP'لk%B8(5]a4ȑ9"=o.WY`dƝv9p;SFIy"ZKDF^66/ŪV1e*,؞(8$ayFpʚ"`P=U >2^yذnS)F'nk/oV`I Q\@)Ďf&%= wGq/[rf6ijB'7p aiHCk52&RG%<Ǩu~h/R_B'ֵ T,e,R3 q\z=$ DÜc 뵆RNS”b[4 rҸ,G9'<臗@VDC%^RI_BlOrrV$"ZDk Ig`Fe%z%k+Q+Ep;`A>I2+ܭ(:>96Q災B nG^:oTIS= A!e n"aq Pģ>@n]-&cm4 \^o7)Y߄Oae!gAYK,5p4/pZBSJg 4:LU ^g\Үp|Hӂ4HXYTVu8x^aB9ܛmLlROEsZlthbӿZu`f աLn F,O_n=Hyj4 )璂{qϭskHAX>pI?IdZtH'gfU$B 7+k+/'%T_c=49:W;0B}ZE|ݛ (#-!ir@UP |CsWߛFX5+ug/ˋ<. J_߼L ȔelP"n+g']5 "&sMMq?Su;e. )2V8̶ic㕧udf Z̿Hʰ<dΐ,'Ż4Hg'Ju:T]ԸPID8|2ńIʰ4aK,{yG^-.OOvlZ<Yo JvO$ٺB5sp-A ۻ͝rSENݱ@WX1EۘxC)ܻ R\l٨h,i88 &3JgGښoF3Hih+ < R^N/?s ͠]ډFPжF%i Ɍ6"5 VyT0OI|FcuщL|wl^af)~L~T-Ǔx$+*OF.o >l? _m`1xa7l&DcJzU|Y™Z䰨p ;`xjͅnY{o: /7 yϞf [*<33Db{mұMy>4Ȍ^1`YRlhg{(`r1PPt|8NZUmU> V>Jp J.wN|JjCVW40KXLfrc=00Y$ 0or{ L#)!ˍkM2,re$h LOhd:>pƁC^<^v`1u6V&FKw s=.38fHF=?qHFg|$Ңmx?2qWX?~T9)Z ixcϞ SH1&B}h=+FC =~@/ttG Yi*g<$1 4kLMرra5:;ېhMXoڰۊ! ܲ0iu˄Mf} `T%+;nA^ynF8Qqͳ!tKv,εD{cqL'c|zWD݂ 5p_q;*c6xXA܅%tWrѳd *ed >+ EG4wh˗b4FuEF1%i*3F;[H2q0mAa{ӄ*-4N[gN"<(nY^\N*E9ơd%dWQ(_B ~H[qR6E 9%=B3nADA[ 2L2,\dW> '=U4mӪ`,w\ XL$zPpj)Y^ (:DK2n]-V-D*IROU '8'B7k0 iN2ct?c l[4.[i5qX|h6腮/ nfM_ e(?Y NR& jd'_̎$f&b^ȴ,ւ oUBbG3.Hk"'1+Ngˢ~bɠ Q$Dr-Xm_lԻeaў(7$ֈy蜭Gr֝U%dӁܥ9\ n/I;Rg ͅ$,݉'͢"L AELAj W O+i)0 5y2dO*7Ǣ eDEr# f;9 LD]7 lѲS2TP-Y\f (Cu0eqU_|!HF˩U%kzh8^iӕ_;zzE K.J>c!<آ!I8ItlE¤ :ҹ5;e]K !Hu/)BP {,ՆxdQrZwDmk 鄁2֘6½*F< we^~(_)ďҚL=<™\;Gx B;=sQ(q;U-F1`<iBק_ٶK@k='Rx E7A OiSdWiq]9܉-Bw0@կ3O- #=6#MJ|\o:=tEjq_ay:PnRsZ^?h8;٧:1J:2S(Vi9EH+;4)?' \e™&> j\8z߸5a?>pz~I˝t/,Py :%AY6F{Rao*"ҚR֭%.'0$/Tppx}ѣsI@X:f$?S{Q݆vr{4=$gĘ:/RX+ gV?FfN(Caila M VɶHBPN߻k" &OMy<6$J {F fa kx6ɳGɋh0=.vVZW+ٗCn0BQq# S Sx6B,t4zg%Z;jtᯐ58Hs FK5YN"#k'BfR ѦFJ>ZTZܢ K$uiFZLtjphEzCw Yy+!qn$+OK>j'jPKbb`6ͱ{͏ЛCYoU[-}] ~z53e(X; r*x"\A7߳J*C R$ H}OHWNA\5wRV%rAR3FTל'|(@4!dyKcv9&.{<XuqSӑs^U6YFnP'ڑ#CHJ]߈d:ayPjcc  apu,x48Qh,aÇfO}9"d2/0s| +F;[S͖ ~D@.\'PzTψp׀)9Yą0#H CЋw+0o6n.VEejhh[]u>جDׄ$[*9#%eeIx]עb64{s JO X&/7D^"p렀'ꪩC ]h71I"~S":V.O*T =gVThؓbE.INfWvMgk!6sg<`z!|_+̥\zz]B1+ "MyYSjoH`z成ڞ*$L-_sm[_L ugXj H kn#Xp>bRz,=Yev=iqG kUc^_ig:?ݥ :}R irlى:T3#`$LkQsgj(ՁTe H-*#-xd2F(#Æ +r sօw(}]{mYHJlriPyFʘŷ6qm)-sҊ(dKGÂ6ؚ 8v߸G;3LO;QST,}-Q~Kw(S.)v(V\ +:8JltH)'8}_KuR#"cVE+_ zmr o?] dPYܮg"B 5mbSpʒ||>/ch <}v{yDUr-]i#zuVW-`oW# AAܑ󎺿SѾӀ= _H>7YƺC:g{E n9Gy`pҸ u7/ ץtИzuٽ>m)d)#Wcj`< Y?(qH8%R~ܡFFOXd9ȕuYZztҋ^PqjFtqn2ʼYz?r=\!lH &-j&݅kk0"CHO JLys_dZuJ0; wPå`9SwL3xN{aUAŬn $Lp:i(Ej^>@,wSK3mp~JcK_Y  &En~;0=)PY>$ _%ʗ6ceF6n@Яb> tpJemA w1Ġ(|mbH^BMCS51pE5 ٗZwaZPn 6Iyk}fML/61 (YwU"4q_X-T,\`Zy% IHYؠm?bu^LxXOzL6zC_a~ + jT4~A%=Y }17\W6nb+?!.^݇Z4 |+_&rheLLR$ߖﵷxZP 9ў/6z "Sѓ/ba=gs?6\H!,vyBZoZu Om!Jg`& @N'o]X#TAPq>!P}iUaހ+:J\9r:s-x_oۘ^ٛK(j=HLxh!ȩr+UK5߶tUr.w/~(NxZfS)>y5{RzJ.g%78eHi1! 3(YK;[]e:>E_8>Ԇ6Q<` OpL}{/+2 ESΌTS9NR_GSI DZS7wwSִgk|05Y0֗"ֈo!#A#6r&ǨDzy)@dQb`X  iz(U6 6pۈ2+w7HX~ۿ<{p) ?0Og/:'AkuW𣒟Q ;ϷRuɔ['#@ZX킇b>uPFl921 -8ٟ&4Q&VI]a]x i4ڦMTW8bJ_zݏ )=\_͗ 䉜H|Bwܒvy,[ϔ ^JQ:sNN0⥽:&kuƥ<݇a%i&GdNɽ:Dj7#ڇ9F +*0RmR"A/ S.~Q+:{Q$Hؼr&gX'UDe5b3(oM댪=r?s1[K. pyC<4|?j e^Isn.BD3G+Še~W%ѭq"8;9u@4|` Y,JTv.nґ :2L͸҃ s_E}XެqqU3^a&hՈ׃hڨCQ,J|1Hx 7u*3-Sz@E݈L+׾TjQ/}#P3eO㝲x~iKTx ;G`Fs K{oSuݎ -L}<]6\fZ,>+OĢ@`0^23Y]%W~K&z2׫u͉mZ"T*VUo2+I'!DsYj6u37ctcD"{ *E7)EKBTI)n0"-jvWnѓuQ""}b9BrCGy2yRXvD@A[&_*a$)* 9.Q- !,H˷GC΁2i_Dž(~Y5֌p(t] V8vZRd71DZO\L~Gxrυ{$wU=G5)(' nʵԕض*6G^GPH% j0NOaet:aGH<&d]+Ә 'PY8~czr3,.eղ>ڪ%v9lG n`;vk5N4 &I\JSDxEpuSMԍ!M@0-Mg}N2`ސ2&7:0n89 L-e Jz%d]p9VL)H/pNQ~̩c-=)窄(bKE)y;z*~SpW{'r7E<x7mqDm3DMChtjfI -G躤QQF'Z/`CdlP<(ʎ]Ct 9?M( m+ޭ;iPKȨ؆ءi^xN|PVf(u;qKr|7#DXKM dEbdj~H#u%vhDv8C:{gI2Qpieck0h#֝pҚ8ٷ7_U@,R8ј_@pX:6@8AL<0FY^G;ew9 ct  _u-u6hm?.j(AؠqK|h21{tm4yNv s[bf};Y9֖ΦCg6 $ Tu m#Yyo2Xҫ.{i|K$ZZk:禃ل0v8-Lf,8[^D;[7w<)qB L.zT[\%-OK4r'}1I&hǜpZ yg™ŵ5fLbNfXUy].yL2mr:Kh˽Gxʂ0$1ll-!?aR 6w( &4ME2#Iw/EU ]̔hj]Lmn4CR1 gƼMddOlOā2R{n* žpu?Msg jDyY".xZn5'h4WHȾ@z[zŲbA^nu_AdDPߌFWjNv1qs6Au;;~P{J$qD>%Zj;Z*(^S5фGְGŷBMFE+cDiwZI@tߩ7ۻ!a}W3#ݎvCF_U'{~h"(և&0#_ztU_|Sv.qYH1/h5_PmBsOxu8}rEQQ #FhSCk>krA k^R'ؼ敿"_O9R8ZB$ÆRԗ]vJ[P tٳtN:ǾΪG+Ԥx&z/:*J)Gtߦ[LP!cA3+ǣ_.57]avݔnmS ) bߗ)m6Ѽ,ci3/t/jɌU t N%l:Pq|7 's3FYD{/X bA[ DX1dc$PmC(mmnQ*Nkons"Y::K|嬩dWNM*;7sg]l)d{$>  VٕRQwefԙJ*y|oFH8pJ$pVXHl7ܿy¢ i٠v=I͸o:'51J[Ji#Rv(ۃ1K."Y<?{Є\8 ӝ$2l\~=~J=w!xfm_th_T}.Pq̉in(]wNhgC3+lQ'}?^6kTb'9[x|l6ibqM]7TZ8izW ʙw FRIJvW8jMe/h-'"DvA"ݗ;.Yxk$^y'Eߺ΂JX/9E]kiQWnte9LsJkd4؄h,Ƃje!$?DX Q,gq)XWi>K7>)N!} d׼JF!lI^,l@2*5 Vv";2gyi$z,ϫ uH# h mjh`Yۢz`6R[z/@a&,uJ^Fqj3"?}(LJꑌ󀵶#bTb6eMb^Y:W_§|ytơcKYwO&e O Op@T7(25 D00Ëݧ)/[zt Az7kZ]s&qf0t5޽ v*eI]&MbXo0+1_e|RI;褁ǘ9p:s[4()tSKf~DGlc Ѫ2o.' [Xf]\mǙW=Yqd*sT8i›TV?fuڡ<~{.z 2o{z`o((k*/EJ~bx>.ȷ@^=g `!"LDfA"_$Q3f;baZ#c(?Dڣ}ru#)iFL苣-״;>n-ӏi 0 b},Q9yL7+$,7R!ev-XoL4/'ǹ!{pc@3йm%6'Χ(VL(rȯlw+-O|vb8x1.t滼Ώ+P#y. oHX:_&f>CG[dH4h?Q|Su47fTc$ubdkcԴpN2qaC,J&41j%$ogS\#l' Xq' HuH8M4Xviϧ_?NF1rϼ?kffI1(&|W_r=O'. fX,gS}b\'\6D "=%XwE\̔p6[&"0lлұ$W?%U #-\8aai_pt{Teeew񯒭X1fr#eQ/nA1^wBP< G J8>P~Gi,fTʳ^1ƫ`@Uޢsun_\'n^> gZFSwmC. qkq ~ V3Fr{u[+7-`"Ѿ@H(joSqe:6:P[`);vi8쪨NXb&'[}AifTp1Zl%ρ݉IN@۲l+Yv ֪^7[ d!bg]oge`s~}3׋ B j ŝ1G496pxeq/U~d7;9sDVV&}>q_\%PA>76_G"p83z zwU"d^Xе{ ޿]3{gyG>T|Ho0(oȁ3ѿ}meR'Z5d'o 영13Tݐή eеHD麻tȫ<$ U/҇RʫlwUGb a,oq}WhMb(!|Y2#G8St;owz݈s8b7QVT S|+SO" V@E Uձ>~L+DcUy3=aK&/i }?Ѧy/L^ }i:\ISOSQ2@FmBHЭe0QR__~h(­l3v(WpYeYwBJz2U0퇉037!JZ>??_j2$,|TG:N\YXCL+nRw:[EO[񹺿1lżX$|TBE&Φl#hDG8F ,OLt(qO98:Zч;Ӕ@ *Ŷ꥕Kg2)4rK*2F`7 1EXiKRQ^Ȩ@\e3w;x.:2(}kBGS9p'p%!L:SUKNZgSqzr n#IJ%L *.PU*P} b0lYp VXXT,F$` C͚?+1O&~dF嵽w+]Q=W2ls꽼"3>yB%:e| bGVφ`ڢ >ah! <6of|1El%ޞ(2^GhebH^}*GgldM DŭGm/#)Kq[1ѽt9 p0߻a>DLJܒg63ӹScҖ-@3ނ=}q]Jpua>6 剚# S ss/+aްLx#M%ѡ6."p }! `Iuk-' .%\ Vs)ڶl^Jn%νgÊ? ߳}}I͖'Lu1O6[Ob!N"צjJZz=ϪrQ]{sieHŢ;CAT˯ Pɸʡj!(b~Y!FfYlšTl 8CV#YO44!cp#&I#( -PV^xmH40!Hc#+(!~~8G˗{8o5fl5?ɻEJAuc^ }e]g.y _8VgztM1×-Մm,ͮҩIFvljߵ ,|ߑocMg*sӭG({=W+˞2K߸0=eR$I%- L ( 횊r*zncj|H=MƯ^(.wSYl?I^x}O yOo\{0-"こpbyRg͚N3dzpy2sM+xrC* :,"SXPӷ/6d$o$etfTI\VϐySqCR>u*]^`\0;gCUF.hS 2OQ'BXrCo?5-/@nTTǤ@k ^AOcH0y9p0_s sp+d/viI2TPX;?(Iq ȿ26ğHdl;Mؽzn+{֡>Z|- N4sc>'F '':"!K=Y?1D3e3[EQ.PdGPqF9ȣ`,#tEmJNmd?ZLc?f7`8Á)Q!TnbS^ K`[AuY;$ ʇQ 3G}e[wJ1?M߅r@$G0Q9@yV_ڜ,YN3sj!m'[>[.55NET@1xd;:y¦1TlsEkԲR"o7|Jn_`ґZ ki7?=4Bo[ M»UvBGrR.# E.*m5FHqi2Zlwl$r/ZC APL/D/ǘG[o-jG #x1َ+G`0mؾB;zO*C-G%3O`vqb-ٶ6o-1|Nif 80}/:=}e3L:*YI{ʼn]AđzKɶxYL1myk/: &:o% `U nu鷮pvO?آbnOdM'masܕef  pg)Be:q?7Nj|5]2gJ8]H|g|y"ߎxSiV?2ιSRr$. prLtDlJGDD>ina|N-o!_UO '{"?ĨZr[vZ 䧼k.t ChVͳHSɑ3YCOvMK9CiàZj QY~˺ގg%iIv`K#_Q A0WE޸P~7@ ttALܒ c`$ݬMޝ>_UbR@uP*}EL+L;K rA;fHEQ7ƒ 6цM3:x(?fֹ %K7uҤ[."gC_ʫk wY}k_fוq2/ O*1SR_YB $D@>ϕkfp/ R7W?!s2>IF%ó.Ԡ0Q<. fE~b ۲]JSGM)H9{|( |ƭŎ)q/tP)j2PG 6')o+Mv^ RFaAL@"#o& ='&krRo/cjW{%bxGaþ$o.ah)Hɐ?*`U`Ԧ6GNŢt|?SX5ߍ*>zsPT -{(4Ţ Q𦃜ڧB3њn^uӓ+_Pv'gl=CVJX,r/}_UwcZT #@0l]g  B;6׉LUVD `{8&°ɕlCvd|lΆa9v%#mR&tBK{xCs`H~8{`ặb  P5뮔Kvx99]߼3m|ZʪvcHӎ>%KP^℮lE2ڧWVz?hp=C H]z3v.!3Cu8K`yRGkQ*2`{Y 3a:L@"#otͧFd8 p|4 14i1/, Mq]B^ǧ,CIՈgjq+{`U`]0 5:T<(-ɉ'$&c s1,y5aYg ,>5FKأA_|"k#?]5{c6\N۲4eX#eW-FQT}a{ 䐳N[E&狌]]D"#>!2SzKʡZ=U5oIĺt[£{/)Ry(Xv20 tmuEk.ԅOPqE4hmC%s4ny.0$]?zH\X.x$!^SL99=,6FmJ/Ԯ}!&O'xYJal!^.Q dcmxM>;ˇ 2W0*y)_׎B̟̑ZTbi]g<+#͢ o͐DqPP[R.S}dî$/>, ݥB/ r=4Ur=xC`qC(8A@2|@uu/{nh0b~Wscajd0j~B)( }0e~|ˬZ9aCڷMHI .`pN҅&ZؠYr"o=Hu0cR@4#M W:Y;Z U7֚b Ȫ_ #̯G l( hDR'?ٌ@CK8ҫ,̯a"8}F>t.fUGos0/ n_!|܅PU/(3@ɯ•yw1˯icjL{lz;F-,P% E=a'Mgj;>G1:2EWa=X(4Jxg,\47o/5CvӐ?e T X 6 y'>AjDg/jr`c_3j< ̀_(w5:cցevq1|7YvɆ}D%5sv{s2~[bUVUE4CZߚH)\ 1҂“۞BƱ$#|+`e6~3Tc dz*apTy^JRNALѬs<&dk.(&NxM9m]4u h$x6Չ٬?$Pҍ=,QX?$0bhT&l ) -{Ahl[KoDc>^֌羖 Y{J wD`MS sΨ<2)yocEqsf_q]ml;{[wf*d7~' %3i؎i87rY7#7脬x !v(X8N']|;;JU"-ݩFBEgq.$UA 9Y=_%,>ހSibjY&/Ex96VQkzoH7NvChD) $M2x(ŭ?ߏ|br3m Y{T]%!ֆP崺AmD^-S D{F74"Ա`ZSc8 @M5?|[0?RH4.Ea[ $\x)DF |Y4`HE %-9zcrsLBt@FD $_'D5i׾fZ9LҸA#i3I#qK}1^PLu))%KwXdpnx4 o^T'DM.8Q0p6v BN b}3IlC|F01?})#Ѩ$ EKٕ>z lבÅ-f~2@}Y]Q+j??QL A#Ä#ý:mW~+-vQ%^ou;\̦NζDUdm+̛?%ȝBij)ȔC:CvuUpGkǜV>8*RXͬ .C׬GE@l`M00#z?q 0 .jҾŲ+n-k2%XEgU0緩 Jpo'PJ¬Z[gបLԬF;cb; !E>q;_, R0K_ ?q\;: Jd\0S`#W>G8V1=$N@7?ӵLR!V$oUA\QrDY0Eivw#Fvդԛ^ź#F }}6jc2 \yG)9'ֳv RSQ t=xKqraDh->snW"jFxT:S:J5Bm4gVt\N [}+- =!g13\S~1&rw]Ǻ*S4[lIؘD w[ZA nxQN'0㚊MxT޼kգ/\p.3Z5&_7ySZTm41eհ0j7k v*)gqP>UXPxg1e@t9S9oNo T`֟NJ4 G>%8ƥyQzF(h7 Б~;̎dwұpD28k-hTri<aXW9]`!7w@b?"VtWNc^K~s[ΓA8їۃP2'l5z/5x{rƎ\܂$[&ڸ$(m65Ć 6a?% zdBH8|[ԝW=r˷t4A8f/ȊS֑C ]ΐLF\]!3oy"PvWQ- ~=uY{C?=CYq.ecKzIrJ-AX!jD $h9 J0be$<(?9ߘDgd"JYcYK`/T^d5Mw\wݎ~%ȁ.E*0F0bA%j~pk9Uzb9uQ~GJSL8Zy%\2ŰX~L,޷knq,y+(D!SJ)= gz>Y#}ehSPqGo-w='r]1; v5ve\&hˠ>}Nx.g;{"`륑 )4er5 a`ί=SV!u<,1TLU3h*ֿ_!`I?/$NK5C)dIv(viΪΎ* t`D9kujxߒD+rL)CTZ)i)Dyjh ~iM~`\ *J`aעtkq[oWz/yFk \ UD)d]lfP]a$yb9UI%e&BvLMbJ~DAhJLD$g &SI}GVNM㭼%Sr=@ypf~O xWP$,XFo_!!&ͪkmVp@MV>Y#p(ǶD`p$~|_*纗a+/m Gv^>.=*~jG/(b1el7uwk%/' W ֒E:=OCST&nWZ4'D 2i-,iSa iPJgO+~U>H[Etb|sV7G:OLZ('~!A<"8MHBV/ )f[->aI  [2D]mkC^:'u$@ z|yXM&+oK+/̸W\Ɣb IiaBD=E[ qoaL^G- ; ,6"AO .63"aQs(xK'8ESbXgT3Ng_ųZA._ҰCb\sAEp' R5xQ3~ŷYmnWC0CuP d8m}.>ʶA438jwC\˱:ڱ,P/r!Sm@})T7=Yp&'_dx,1f1H=XȊ>&C"6 d:yݬ[Z|$PugkKeexn>?R)*739wG]@)Sծef69 )/F ӖLTOISMbё3lm(|[ņItQc|c.ʲ6'V׆׬ż:kuu "9>ЊZTKC vEWs{RFQ}:F֩Q"2d҂B#^Q05ZʇkZ~So_zɵi(IzfJvOSPTV"ӰN=%tZ)DE->A!:6;ykro6#dQ;٧>$3!-? COt -y7Dnq 1/O=͐DVwHck.cQo@=iDY'QZsIM &uTtR680=e3e#J`nnQGph! xL$;+Knɛ+z#M|L!!,{Xt.r:xer)w= 8X&;Ee|x2NaJa8ZU^X?b9)'( ѡD9)Ǻ@]υ8t-PsQ*Iٜx8ĽH=5a}hvlcrKgoX Z?իxE8ЌPn+r a Nq--)C<}KW_czaLy}vN&gxpвI0\`.a0;>ּpcs&-ؘ{+3Y+F{{ΤP\?3 lc@J#A uŜi1ǐM"=k:tc~sAؘo|bXH0oTn uhY(Hm3JJ{(7‹@wԸ^$mjsH/X.oםh$>QF y OuDZzNiYXn⛓jE҅zrZ"'G7ea.N5blC'gز[ے3o>a)eӌS@"t2*yܶN8UѝH#h:S$, KcTfrv;=|a>d6{cw KDro$ KPN 3 FKXJs[PLaH{RcPe1nU11nHٓ};dKU(~]ёBs0 F{`:|#Z:Pvݖog/*74눳Hr{]Eq\/j!krKt&2$dv!0ij؉p+WoNL{S?QZ?z̎~K ض ʺ^]tytR<>x])8&V)V691/wY^nLVśpj@ZYJG^uu<kXgQ#?.HLIJQm5*Bљ;DX1ĥ]dǵ/`nY hGHyc[L7J/+@ӯn=su֟ښf˪ᐿXjs!2D}wG.';ϔx .Ϫ~봲g`k3r"f_"f{D^3b!.Xs *# Z'an䏩/@ sZ2ۈt5PcRL{A(ӀFޟL|wHRAUƅ 1U'q UZkds+$(<*4~\09>T6E>+nM0O.Wϕg9[xѥ鷨3..)o*4nrCgf^G,ílQ}7h4ndžLGH%ɍW ~Ga_n6$\d:m'Cf=I]rx2$VS\ttgS=+5>Dë@/dw*wtVR6|V~Od"2_qP-)O ddϙ>n.hZ5n )1̧K4Ό2wh5'XgHSyXq~²J<ق͟"rdH=[qrhA]pTOMkuE~lL1)tvCSY*\'ʇ@J!VvS.9 `T##%GB }  ',?HW푓ۑ-7G|/aa E ; >[?.4-wFiQt9E|jhj <g>8$3J],x "g@{IRrtvQl:bv2f>iwl (Ex39N+6DG\nHZr=‚?dwѶ+wߵ {JkCN,?`f`0̋.cr:uzrk)VSJE#AOˤs,8({e3ҡfw ֔ '}ѹJP32*Ż(zBfsM:WmH*?ƨoLW&j?0񎇽KfsskU# u6YqU~I:Yf9ot/{hMՈ>eJ2x8;8!Wo풟\DWJz<)ߏ|btmҵow)#il񿷣ʝ=[Id]~ R6g휷;" 3 |RGCB$|ȵ{:G$Z{?9W&|_զElqAisDOF/rpu$K/D[9,1 -Š|ON`5_[`^Wa(5|-gIݒ%?\<ة#&+hG98knA͏Zh+`2ezCrNp~~ ~5#37p)2tV~J~j@^%mɃ۷̏j#E}cc{Ecիi݊$78c"['hJt?m! {L`Y P*]}PCr,Lxw˺eFrE j,)mNZ <2VIxN]frNr*]~4⩵(Wv iSqhoΐWc3a*b~9i( L"0`,2'R^3#F %ZYh[oZ4Վ֘x|ao/I8ŗ$BA:}-""CJ[;/lL(jbH!3r%Nmy;K!LTu<bȜCmuL /CtgfYRfU e62UB`?`PPl^;SdFQQ^ 4EB!Mc7p[jڵgh4td֒T!De\~)Zs¢U^(<,kLMA$[D'JA8PL ~xo3P9rq XmLᇙef2 LZQnwPdZI|NPiX"Cˣ3$\^9zVM+wRvo@iv>%m &hx`δY!,SMo +\[/[4 ] ([kbDӏa薒VEa vJVps҈ 犉h;EWn%Z3Ά<5uwҊ6!y ")MV<> uG3 0ݒ598{2]@ s=~ٷ[=4DH*KRt3}9uT_Y#Q,<1 {EcF9[WU2r@ ]:?*V8ev|@&[ i׾vO3: 0MA6q;beGXH 9\Hhs;YҿGIM.6Qg;x:0~rB^tCXLOU[K#x)C()[Y`&؞2s}sV'j}]f^~,kvdXZN4l 'C؞K`W.c SWwvn(?Dg,cEPp83w,|H^9ⳚVpAv{Xʞ-nkE "`4tB*Ƣ0ВY(VEiNV9KCz"aݎ5`H 3PzMj'cnB%YiaҪnh3m5pC73/(C,)\cek@#@/oE‚r~_S]E~&T9o]}RK|;mpk> DFk(&1]WbI,&w(cpK^u\y/W5ѧə('*ΩNu˔IٓRN#+vk`8QKDo|Ѭ_TX^9ogA[{Y)&@ca8L?y\ @'"YO1(FqՏVg7 i̞7ƞ@An_0 ia.-D_jL 2-TLh[t< ?wy ׵9Mzo2"NA8= WjƉ_:!;0亏K`BBPn,ƀ~8Ւ}3'qUy?TЀ/pi%,&Ԏu7E; LqO;OGBs$fm͚  GK8/`kD|u"6WKP1xb:x^΅Ǔ]Zsm@VgZ6hr"R$ͪr͗[Ƥ}+n?M3A.zoyrP"=( )ׇ@OaC0 #(N % 9kpV8 -r02AÇ'b<-Y .!'mp~ Ʊį"5Z Ltv, ٫lG[n|U[oV 4JO ͕,ǘLx㑘puZ?J}LH/#'{dn"\m;EPѨ(AҼp<"L.iP#iأRll2|rǏlnƸB < ;J[ܘ[R)aADq 9yj޺&,% q]`C11_߉彶DCN|]|G:C%_h\OlW?OXc}LTj/ѵW>NzĽw"ɰ{U ZKM)u,9֙&H]hC9 WZD P2iAcl#bSuBUٹ&mbIُG7}ֶNTz{SW.͵7ә619+9Ug?U(Zl~jQSu?OEV,cj>f7"@w/&lz# Ct E4y[ө}qAc}{ n+h!(OsQtVXoMaX9jrf<ױ5⅃P+CwPw%ry> _Wɬt'Fh0u VlEd0Gja[Vۧ1HoT C2Յb0 *AjAe. RdXPG BPo!B:z0* S |CLKDٚ`˕.Jԛ &dB𚙫`%gChf P,4Yyd >fz~!>~pfϽ'߾ȣ^Sn!Һ߹#V!bd=;K>a1_[JQQȾ+( Ox::f`8ΖM}ope[icl:C4Ec۰֤ш@y2(Cp4$GNsM(NgCQCR&DLtkuӜic#"v>%S}:ٙQMJL4u|"z#%A? %Uϩ+0Kc$Yb=EaqD8N3FCr灢Pơ+!"EQoK /Rfj6#aLDC :~c0p&ð6} WuѳX~$UPʐRl1%r.!ΰ۵]<]OhfjY8ݱ掃w:ϝ o.-CN<@ ; wFוE.5)QYzk?"MnOunݮTC+؃GOnbA:N$TH1&=#+d'YY^ꕆLf_R bSyVnO.BTxD*ZA<6U3ܤx 'Dkv""0|%t)fTޥ^Ig&U֫+c2,U@ůjDPz >o,Y;Q[z  Y'GO$Ar?]ŗ|w!*<"y{D R7<^Xh_ȱ~9 #rVe?hW[+EZ\-k³l[Uw|>Hs<DO9BAtlSqAd-܅̇ptb m3%4{hDޘ> w3>F_R`R8zW̙MJ qlnai&aJoHiHM';id`>5g ɿ]7pp }t]5xYwrH'&4Fך*pw)\_lU6xoxOA%Į(ߖ!]=Lvjh[D@[ͺQcXGwp0s~ ȗ<.K\+Κ2e1 o)Q@E=&ֶ AZdL>I?6(:$.9uRFHYylBa i=#{\Z D̊=G ¾GTBv'xf;wjcqO#Gݧ4.3 r;0 HM]؋XqYGVx *7SzN>ɱlcgzjg4F٧$A۶PN|c!tZѰ%Ot }SYX%6JězCή7@5Z^5A8;':Mɻ/ iUr#wX =G,Fnp<ЫFdf٘L=eWמ8 #eƅ/hdkt}EҤ/SbKƃzqəD&YX0Ҕ ' GǃI+>ʘ'${ m.ʡ汽>г:l?śO] Wl; $#(s)'`<=Mq0?4ϊсü_i|Xmf98Qub箉@D:'F܎>:џ!gI,Auy* `g_CIK9;2YމV[2֏Oȹ6XlX Մ&mt>r 3]8/H]{xD0X i4{-lQAvϢ0{Cť`6u `'}PG&)k}"yk!n!{$wiQW˺ahWrX~_ $\1w=3?zKX;T3xf4 }8.3SEEvPNF'Z薻K4q#Vt9OcĂsRo7gVAV &r<Ҫ'V{m9iK vNozrS|+X"qDJDڷtVNhW%v,}=?6GI& 1%q-7mt޴+iY79 68</&DĨc| a8ZpU>XzRo&\ΠP',tJU{k-S:7:OWO%C!u9TJi7ޣKѩ{j6F5@\jۂ#qs͖]\e-Pz!h|x<5{ez)2i)QgVKjg"r0([TGrhitJZ]uNk*,FH Lm]yv (̀\f%7-S("cZeDN*op=2-:O$vPn8^zӢ h:f'.&SAbmV}o0Ô?LsYwB<9zzZ6{%Q>Òuw)<4֞V|^''P [_ie0DO}4a@FL{,9)铙IS"IJO $GӒfF 0у*Id6hx4000>ag0$4e W{u02 de0Av>`?s*{miC;F/sx;(ƵD)l@G0'1uPPk5wJ?S9ɌL ]q2̫OD$Cqs1A<;TsW?gz`k]fUVQNT5zb7,Dh( paq%Cc=V򾖅PRU&?HZPx̾ SuZS re9v#qSOp T^saV3AU0B{g6 0L:(' cLt @HO ; 63w;2[iZYڴa /٥\=7ŦX+J>c΃% ;t^P EI/ tTϩ+h^Y<+ZhiqIʺ^t̡W3uݛr*<(]IiWτD_j >Z썆>7F. 2kIaň߽~P,$Jmh 9 !)#PpO\>?,\tC=M,f7Us/UQ/J)ZfOzJs2hR\֨L=˭| DhZ"qLct2A7fOnO=촔x;1G: "1NoLql*Xz/AN Rd!ǯ,}[reqH&0EL-X>YRW0XyvIeZ',7Շ5LE 幜^كXț/X}bObb2ecRાFNKcBω{3篊2澵 ]}cuFVxhӶ4-,?Eg $8 )\[(vMkj/`T ^n[֮4: )0KՓ;{ZIn <)b *P[H 8 ,ŃaEN[ZQAfк~1KDp+'Mk,4Z[ef%p8?yHip䪹Do p{>Dۇ ,?kyQK08w 7O}S~ 8SeqYl~GrIAZ80L_x myXQHar~QjWT/ƬC!9#MZXfMΨ _"xV \1}xg%u3Z0 j- @HO ^}g;OY]d4ň,mSa,dcVRg baxUC2NswcDNb$cgOcL:vh@+o,&7^$bP*v^#Ҋ :Zxըᦝ$nv¦Xĕ{>pS]ąer56V4!T` -^10@Q]Om*i{mߙ|<}=%7ԅAL$nQϯ }#c>F֗z)`{8,5 ]J)Pq!5L7t;ԙ|…~<86be7ĩf֬EZV>Tzհ]J;OT0Vx~smcEJxmZ`gyKNcpHK9_Iq!*]o}h%+hV4KGr׀z!~aWw#pP_^zG^ϼ& &5ŋn[-d8زsms:W KaA,I 7w(bkdɻf 2='}%{Yf\bG.8f!~ѯMA~~a"HvIcck\95Ad?Ck0 p =x6Jh ߒN+lWL>19*FaX _6XYդDtgD+A/m +aR|x_Br;J#9 ]NEi~ zc`x,o5,r}+*)S ҈n_x0:4[7ɧkiHeKh \IȻo.tAK7/3qu= mˢ 'tÚYK,ko6 ۠LHlOoVo3PJS6 Mq\ͩkNV:&@ &3y;S)gS,

    LPX}a1քc i`ZHS: jHCpvC6ّ/umÎF믦-}pVOPB,6OQ!u\`)4IVbS,}U])GEn_B}Β [>[N:ٕ[.uޟAُlb-M"}G*S[}D5ǾYm.5X$%xйxȨB_\&[N.?5-7+ q1O̧?ߍ(KMQۉ5jsW!׾G1Ph4u@+B ]\iٷ?hU8lӐn WeQkgj vK_b'-7=R88,:866&nf`q/d]5B29qUtMAzH=4RE9S N#eVԭAql0 ɬ "GrA %)`\dWs z% wɣ"l]npC!(3Z%2 3U%XbR `S/DCp|] ^ӝ{1K+fG aA4R1E $̫D;MGy>ogR5٭oWWGs ,Eٮ+=cT9 T9r˷PўcLKr>v` 'PbF_<<~DQlDkn2 O7W2By߲Ypp7Bkf˄[ѧSo,κ<-KNټ|&IJubGyx:=V[cr]zOQoʱF4Q+,Hͥc[dع.- b 2ىrKu6^ Az^55F?xd޶nIg η(:G#SRA0}.E}BąLbd8욠 q CTܕfytwMB[-^ZO:aRo  荽'cqYc/kiTڰ'c<fjD7H6 _~drB\ QG9х:~U4fxvR($I!%Ю 5:kLug㥀Nڒ{.S瓆ł1-nFPo=% 3DY>zFaj8UPPžrT(/p@$|0P^+ݝB+sԬ+N<98Rv?.@ފ ] M͈>4@T:2/h1ȍJ)dW鎥o/? J ?hI{mVjQ~=i֘;K.IcE:씡ָF)l>g{5 Øn#.oҳ0J+0AiJ>o) 07!lclqW^WvYyXA8ZK)aY}ާD{=s ގjAV 1t|o}u9-rET\(9BJۿ@@3j?U ԜP󄐈vϲ`'֞ٙPezN3?gL8rOIdzl6gFP=wo^\;rx-.jnCf5f1U;42ӟ5'G>.P-ҹoRHPG9Jvx)cUe'SEgQ_3:_>R C-_O&y >YNyx%4"O>FE6[ LɿErKđx)cXfpɓףnԭFOǿQ@KY:"b.E#ҽ;6*"|7KS9>%ߏFIqG}Rqm"zXa hcyXBJ Onvyc (N_!Wϲ&77oRÁMGV =O*v33yĩir> RIW$0b{`=z}:ތ4n%A@=ӑ:JY DG%& Fd Pxg]PV <1GfLRlsG%r- l(9xuW&6td~ nl` ?O~7&wo3fs؉CA[<(|)v;Ժ?c_euOR>(EwKB"z`LH&X {@j`$ZQ'GR0~U6\;I|W{ 뗌zlv@ӿԓQodȍv6h~>E, r2yضHM+p:1 'Z{S8H1P2{2xH4ۏw,g֥wCHZ{SE#kfŁM!]p9(:p#oΦ_[nW*A j:Hnb̦1$kpt*Og,8c-m;ȒYw9ft!)n﷢p|-xl".,M6j~j]"56#RǓ/8~G__ϘMkP]9- ? /Jp~*AIM' }۝࣭B@ĹvBcx֍Xt/;7ZP((V,nC_9f8cNc EY5X SiAegv&BS/Qn}R28ktYlvv`Aݙੑ4w('yl H@ -w7$dB(+1 ;gh30#҈[x, um,Gtܹ%L1 y_yw APJLQ馁WnIkÒسH)U3_G4͞!e*q~w(5+3^}5(`X5L,)-iٶ|ן$̯;bUYr6 pFQBRϬF&VDsBV׺RvwJ"EAN~ezO 5gʢ5p 5ݖH5`-_t%MRrTɵN'`ǣGfغf?DCWP='hz> kzp_QYq+R>f˪#$1>HCB ORi8/2]~|F.XyCQN:nT=)溚j-ߧx e6)ߎ4גٵl4`bA0QgHJ\Sa[?gqPr#ZKIմ6, e,&\Q 2_O5{q2j8CA* I*aqW-f 1Osel;O+?S ;o*!cՐZO׉]j& SuKddNzC*~pa'.rc]BEd"iE.oP9FKZ:H^wU *jZ *s8Ct)ob%av QXv^DE󟟙x0w>9{;!5xC$ݏXbuEk&"U%Ki/(a(uIl$O0J1bO@0hቘEz*: Kq%TGI=.+2/q-Mswh:Eܸ Aq: HKeo>F~ ~i)A< y>za)],#$Չf*"w:'nfb-Ơu|5૆C =A;.K4cwi..~w{%6E*nqɴfa(VtXCO×]͘op>h%& b˭{$ .)Isˆ[҉0Ɂ{o+N5j!Bw#kt Zddu4]XirV'uvvwY_m3~Ptmw 48gGYI(AoRV_^j3Ҭ&H#,~ܶ[ HT\(%2[udiwEܩz>dBviTFq$6ioC!XL'UqӜIlR۳IH:c݆¸Kku@I~_|xbpw'/,gmڎƠ1Z%ɴLp_dd 1]7:4%NW7e=Qm'i'p併zmiۄNܢ`eL` /{M;R.\'t%=X+Nz /[8g C~:l3 ,:tbﮙ~6A/WN`}5W{-1%F׫^~q5O5ŝc U*uoL~̔!dUBH9ȮbX5wvF_Gy\h 4e]}P'oltY:=}oN\ӹ}ƧZ(}!\m(\u-otP]:OrJu ~W)}n{) D:@[V{9e"E*囡 2b.X3YTnR'ixv5ay ozli`1]| f>xz 9T~٩EI] CB\ճ)̩2o N+]PyjmpBVf0`־j\`A{ڔr}:W[V,?Yypv^u,;{0G|[Ϗra) c{ܑ%Ganҍ?{Jڽ qONQRyn)llx}9 V[!{l7+(b͋?}U$FD%S!mRɶmw8|x3 >fVjɑǟ!.F%]לH`COz $q:d@d.!sYOB/BxύeY ݫ. * h/e۪Ԃ Ϙޭ>.]~ !\)c2v{ OE Ӝ7D`@c&nМ!QzO,*0$f.\=Rmޮ( 1yx4O?SN0Se}ūq5IԛKq"EZDŽ /+I.N< Ș["駛[l~F8W ujVL;}T3c(|x͐~JI'k&|\^+;<㘣&nS=9Sd6 -<̬yIm5&Vb=]#i8Y=EҝʖWuw&/Y?nb\FE)wQ}qʃq lXҞD!G=rZWM96 Zi9m8IJt1:co%/;O[ik,%jo#?D]9x|k9$Dk CRuA=°f$v> Qs=x#ﬠ. lsS6@Q$'t6iꍧ=I"!%7kөY{>:TƓpde+FA GI[ԿF4 A*] ,v*ͻ_W K Q\K3uR6]̘AsϷ6+#n"Y 3wzzz(;͜t^6g4uHOgfvraяU}[I}!W ~3. |!LWGug?6!#y-V*_+drl^]ڢ5۬ @Ü,-B|mEgeTl_'SuFH) c㏾(+IوU?0d tlVmޜgձ6%;4NW2?8 L0$*cT>;c/.Λb-m]I֒2HnrxN{(HNz{esBwZ(҆?̮G5fd N}.6ZqVD{+WeA(+٧t'l";s Tp*$K~\}%y7b(4)M*:T *:L] 1*zhGRjr6TTʁ64x-fQzfF؞gBB) ?8U HAx=-aSXFA ))nzg ?!և| 5*mJhSv ,1@n.@6&ȩuT.^+aq^=L듇Wơ8gR{* KnɣǬM*NS:V"u25xkHG:M?JǖIr0H-#ssBBw;,k ) ,3r&‡VY]xdo>*nO8c{B/4(~=Kh RaYr('thGS"I!=7oYj'93(4s_}"<,u@=,)eJox>h?e a3K3) OEi'E,Z)MP1یaEBM޳9Qrr/^>y|kHAKKNͅn" A)%wj%@e!x nDPbE&w$'lvjZ+&| XH!C#yruX|:)t1TAܢj;̓ k_w҇Jm'y_Xz6U}N-6w#СE,p$.umLI| |ؠ`jɆ *svt^{N 蛎IΠAo&ׂ5_Guy{Q[D+X%@LXBxe@o]/qC2gm-[3n"@W Gv5\|ƌ^.Ӷ E7[8Wio(j4:˗tzx8ĸ-wua Ӥ̓JZc{; _4өg*>t &ÔQxB𑽼䊬4<]d!҄2S {"5W%5`FO+ ow!ߺT 8I%}(2 /۬86Vq)Y?C W3^_־ BɶRTr5|O0./S3'rKʉ,wxBF5N^CPNJ;H+Ta.'mJ ny *"eh{W"GTnrJ}BtgiF[q^TumЏଔv'-uv +B& d;kxV ]+XfL*~?F٦'T|X:'ecGx׎4cj?mA!+gb(*Aw$@(z71}uF\)H)y0)8hjfc \w*dy!ik6 GN2n>+;>– a;5x'y$SqbOm-(5u@QM-[Va/G[e(VE8rJ[QLc+"E/Ӄ±)Q|cou ֜txP0 ʉ~źs $ҔȨG21AiLSEb"M8oA9TREHY@mMRznyrWwi0")盵^ `lq >L_N۲7FwٸE=LbW^"st8wc撵_ͳZiH7{@YoΗ^s/Aʥ\LPlopp,R3Qz[wpMFO&J^+e\ݚB:6LX#Ź@]Ԩ.k ;y73i`yO.9ǻ `+ܓdlp^"aD 95i۽'ߍ=*= |Tv1M8qPNPzSB?!/Ub9*@ӓ:S DN .`P߈N|WE2{ҟ`F+mt:t7;/2Q)[Pvb71fQ^Pj!5Untg&Z=aD2I1=lv^<D&eKWMVrrtfBV0Ѱ*+? xȲU0]=!X[k0!ݪWE t!Z.cc|LY ,roQw ]w.K6M(73jT+E'<>־_@-t- c_z#V}I9X<#Fș$A4YZiіW"ؽGQq󺳌ɂJ2rS1e=w!FJsֱ'%o{QԄG!"=m" *\dMgsaL+9ʹ:1ˁ[Zqd:|ҫ[#=#ϻS6 YR ZƓ+ Wre)UmfV J k5&L"[ !/Y U|8~7606, ?m d!(-뭩ւ}Il*:2ܩ~$֊MHօ獙iJG_mW3o{DEسQc /BA 1;/.AӻPTAi vZƓɋՓ_Q̊6«-xf6[ =+4TvOvdcW8u)&tDbYr 1[}1%7rR}<($#øV8MTeZ &Q'K"jG?mghػ=Q{!_>pj:U1RӚža1QwŚGݶ3q0190Ƞ|DKas^tdRo{9j,OcHc烩i;1YLb}ݦBFTcN`MD;2! ffv5"-}L Âڙ98I_e,lZƐy!cM ]Uf0%hL#M.\ㄳ~+EL 0-j؝[efW-okt_y͜dG!MJ"tVB.FIIjUD,w5ثG]G"3-8g6wޢ bkfhAW)57Ռ}VJ:0Qu.sg= lQՁ[eA^**2iO;˸.r\]!ؖ:5Au㠮Hh/2hkМkL&|f-D>vyUAǛT:]3{ךr֍fRX)? f.KSC c/NY~?Փ蔋~Tь5}WK֒e-\3s^I觇`So G#xaG]JT+I|`1J" d+sٽl U¢-.T b"Sjvx@{}YҢ$tkT8߹aQ$N5GHW di'iʛ]W 58@7c]Z0. Z];JуN7E!̔ vwMa)V)cú8sѸ Qi%|_)M>XL{/Ngw~j_Wd#|C=5 ~#`*!us8CX|\o`[?m.`W^^Ys<4o^ʩ{ Z0ghA6kgerVզ,%vރـ\Oj'\I!Nm.Ƨ"MjI)svW-%4!FT|!(M23|H[#>e`\/ww3@>Taoh(*liBli`\01`{%)5G293)AB}l5_HO볬K#DAP[vѝ ?ʢJ~[l 4ŻlAic?^N!= $Rgo,wVv['vlu^?3A,Y  Sk>J줜Mr-Z_ cZJ+ 9iz}dׅqAsıW ?dIyy( R3gd5JE <uN~eRVݿKȏūLq%QT |IBU+Bi5WM^laf,6qzHq.rvY>dHzzPYL>=: g ":|<AּϚ<\A@j'|n'(7?Z{*&MdXqc⠶L1۪{'1lY'6hS 7Z e?)}:^KTCd幓Rh W"nyVKyYUZsjP?l掓"_pz D>m,쯚4Py:Dѣ<̯h iU˄.CB_gIw0۰U<;aȩ p]8#`1Xbq"?m%m$7nO]޳؊5x& 4[% 8 Ll.p,iYL{L7̇PzRU|h޲Wm9q}ߚ ޛ'R֜_V)0:!Z[|Ȭtqͱ H5낡er&pn`Q I&X"y=/}N"m({;HۅBD=qY'$'1k[NkrU CwN.4;S'Ɗ0_#ӬT\ᤑHoppXDS\M1U֔9V߃\\&'@!-8g6w #Ǽe~Wuk-Bή+p%z@Q w(I@9О(3’)F =s3"[d39A_ j5Xl˒ ײ|_X8u(Q/joZ5$ eY\/W)^>U̪at%be40M YԂbYm-2 rVv^`0UV񛀏J`ͽ.(x`bٴ,`(dĒ 3iŦd^Y85f*S7H.U ̎ D:sI +Pc:m^7A8rV"T!gq⧁e֋5iD!!4ne?הA1#G}Iڋgu SY8n0^{ +]uocs-UmWJ4:Zw*e`4Cf a#p5̮l1U>Ƌ̢CnZO&hr R{1-{PREk؋C5M䥙w6>(<QW¿×' Θpʲ[|/Y++)*r^k{t aoG bc+ߞQna0N-(fX<7 4J&ik`[{%LVaM0ˊ/ؤk\.ܿ 'OkYH6/U)d |BE1{+6՟cPE!ٿ8F'C2ScUp TY+5|$u㉰aFo$ĈN? L-ТuW䶎6V.3΀m`*GV9[Yx-'iw9sU. mK?PO0ҥ.XAR5viցՖ07|Wo'1h6]t.Y4PAYe_HDR< LLP>fD%V" Xr=llr%/z͟!9(*U P3'91 $;=_<^!H39 ӱI+YV 6cEq9c!߀XTB V!L]ؼ{lh;bb\hI`^OyonrX +Ojm ]ӖI!EW8A,˵@VFV;$_x^}DONfV)UC<|T~[MBm;ck\r.UzAHIbcAf#iYEgi^8[*?N9,DS.wl1vy,0/>ʻ,4黗ٮd0{U7* CBש\zE. PoE z/OC,Pm[ܺ;FA*75QwÌR}i%k3~S H9ڱAK=y6Wu2RݺjlƍimfSE W.o,!<,Z ƛj`YZe U-ݜ^@iFƙ!.!kh]J"t Q^*J (;|]<ž7NBuզn$1kW8a9+<#t$<*T` D_sݏ0$u^ݍӴp[jW%0BQk1+;)ݻ,' ց<UVi&.}đ(Y-zY?@N-Gw3P+.!kBtj/qf = nwmX%LoVVOko6\S QTo ö`";ҝ=NsnR{di PͿ9@ȵx9m[uNRgo'*v^xyP!0%+άn6ZݞYԎˍ2WYwo^taYe <fC&T2%~wWH4t99;yj\j'L]4ofVRQ9)ga-Ma(P1E~<~Zudh+ΩŨMF#WmtzǂPY70E+ձZvCaI7"u^KN0[Ŵk3G0C$A0G\IA֍BAC)3tBiҎfw}@r/#7q>|I*~w8P6/ :CϞУy%_ Zʘ:A~[q>Sq!ht="ȅIW[W}A\\^S׃7)S2Ck侜u]EcKϏáy<ZhWC{ F`{ ОDHQό:{UЭ37Ӭ !&PW(z&ݓA]WEr;tGkMIVܩK?R{u=tCh?[؝Yy[޾{#S v F%SSHpͳm̅cU&5ۅ{ygIn@]1n6ːD]5XBqB4&{DTWqf e=vdJ5O\Myl oʖ>"fsjIm fEak( ,LMkD8bO+x~r 4jQD?,es"4݉`]hdvܹ r깾MJ||kf#T?&tfoyMT 4wF'ȷo04¸Bt.Zx|&W}Jejq1gC`b>)ziA]X0_}S٭\_ϩC.ܴ*dp~viA.Ű)9&/ tyj Fb~?RriֺZFn<6!IJtڸxc\=B&.Y~dqGM-芁ft9+24;7ڬ8({MRW {S$( צ.\ @ȥyfzv؟P5&XUwםB~$ˏF5m"^Cm}P-!"]\ZSQIe'̕r**/D:cgbM1i(4phNCLYQ1GSڏ\1H8pIϓhp&W}^ jZR&ϕǛ"JR؃0Ypv`1xM1+MUᥔQnA!4h)Ӂ5o۾}wrGH8H_Et8YUOiS|H-n8>9w}NGˉ TLIT.:O;RŰ|v dQP1Zk!p`-Y-aQTl{U)xf0ܘں JH-JvHY*D˩W\flWX?e]q$@_gބ8¤j7ܕ?]{OWR:Qu;䐭9SU`n]K-ZDrx;< _{# ;pZa`U|^)مfL$7A+,@ e^̨$).\=Qbܑ*\EBF9_Yfﴐ}wa+1ώT8kx/G7ffjѼQ :{wqh'^;ojKH~ }K[Oޝ(;.j'tc:N q-ܶكw2&~Ɛ.1X&sј[^bWIᅢ6H G?!D}AoHIH2Hૅz=0`k?%UĘ88ny :Tb OY8pjsk>Hm⥓V=Nwr^m}xIXMpi " X~ݱ %b_xHIF.{xcAjĻp6d Kh82/L}^s&Of!$i)\7j6Mr>@^et*hQ&n mw'#8Yp+QzN (+ W'W|{o֭Hu 9^%EMY]DZN;sh Cҍ9_Krj1k-| n%bǝ[cY ]5/Fr XlO("Ւ\R#GsF_ T.ʪp)mcs<$|X/c?rms:@]&m. O JU8Xk&q޷ lޕIcGQ*aOyʒ汄8yLd1%}a̜m W@LTRNՅ6fHO )uĞ+De!}@q;!I)U*A~&,u؎,fF- S&l3EDD$eJݣFdrdC̋NX l0>V"E֋d~OXwlmbrrJ®QcNNx* /Vr-v\3ȿIL~<Bol:xzB#ak#|?*of谪M✤tqKy|Tp2zo ӷ<Fkc( S;0ɋhedAU@!էɬ=c˼iG" Ttx|"_+BXKIѼ D2FX3l f$(&uemIV]Ty֫šZ>U7b-DL)U`'=,JBܤ#i20q%!-ڲR̨yI:(549';O9.bw;5GU덼y{qy<nj&+OC<i%s bZa3#lۧ?Qu [0xP+Icapո6ezGeu.#"}P㨫 ZQtq=orcLz4{ dUl(j g"|Fuֈ `rw\#|2h?/J=kPtHS wYnGO2^l适okuS8?EiIjj)98S@0Áy*0>f͞t9*h0$3|?.0"A=E|C;cT {7ߵ"N+4A7*o.F$,{nZŽ[\/mJI9bƎ|^: dЄlFS)/E@U{KTSj9+1DNSGRVa)!:Kq07p| i%g𵗞4Je883Oj3$,*E&CZmW)xD̢-!oI9uJը“] δK-d9&2t$e~:P*NiBô!OQAj':-t3JOɸ[FuI#딯d,x\!.!֮Ti3m*_^v&QEC Io.)W,;Sy? PMmJ0}:erA~&pp{?s,`kA VaԊ+9O liN1(<ͳi`U4ws-O67&=ZԭU5G oDFH^i( Efz_rb"VsT CHn\!1œuPvBل=>?tH}̑O k!ZkC$` .I޸2']0 RN= ?E?moqnTnVfZ#ݠf$YŢ\VR *#(+jƳ+.o11٣Sێ;Q].|* ke\`K9u= <Ld4I0}d/uA몐VFؑ +n>5E_>CUѲ\`xZrMV}V@uzBKvz`=߈LpWDŽX[i D.OrŨ+4<\F4ߓlơK\tydj♧OO&2yQmNk I  Yrq0K#0;NJ"='}mPez~yMCy"ʚqKBd.q!j{UO OM6`M̬\3aKĊ ⱇSvWJ]'vZG6oIi(g%jL$v<+\r@)#0/jDbYA "gvwofӉ81G&6vqA%έ|dn3kt<K([F=d,v(ž 5+TaF37[މXCXV$aXQ4^A(зsն/NT`Ujpjm,q>^4cIGO~ V'+@Y+7wz9ysuk H(at!7emEpB+桫na\".—ܻ_&S?ǛN>iNr-ki]edV<֠x΁>|kh@h{A T{z֋l-P]ؓĝ /p^.0 ^RKNod&{7+T"\ߪ`L7VNxZZ(CKg}xIz` j:3hoi0N+ZIӁ},S&R24qΔ&-3c 9:v !j)t npΪP~`l`أ~a5CtY:Z$ *ٸH{ZMc@=_0+IDHV)BL/f+O&d66P^'NCfw0.X~mth 6 kh7`befv(nݫ[\]UKfscnw:R>ީ ޏ|4]F^Îߜ:{5gj|oup63C}5Ke(l/%Ҿ!_u,RK2NƂL!scO'!iRfDH(.4s! N}+[>?%aP~Ơ>G)e>KV~Ƽe0jbYx#S7G+? l@r=fĺQ$}䔌A >sZ|7~m:ry5쯋?6~͑劍$tVdl[ n}O6wKK?wJP-.R=Xvr9 9-r wG:e [Mfw֏o2 @fl+QQv X断/#p}&?E?i{᱌Uh]3X o?K|l 7Dg(Vsp"U` *NK%YQ¶b:{8ߌg?^Zekp!H@d`7 .6l-y ~qXXC q5P*䚣#I$̖B*]uxɾ:wFJuȯkJMJH#~Mj?Ƃ`7bu_>" S nun13%抶7ltGo֗P+/ E@W}֯}DFq=OxFn8'՞i~f..4] \qrң\Dl:kC{pӅ |>OSe)|{$*E"̕-R<8vA%[QtQXA Kw-Wȭ/JFMo B zȾO%;k abYnR_^~#%49ܶ`OL c1`'͋9P9 uA콋kk{޺-`uu'8_xZ&eQtlFyob׆V*հkkj'[#ZwSLߤ6^ԃUM1K2 u,1ndM+Z:} CbW 9l_\BF|QAe/%ȠV?Z+3$7p$=;ÑBuVi/! )[򍏭F@o'/K( #JtAEW!*q?x9m\a yO]A_q'mfteKYrx ԣPv1Pj!O[_=fT 0_Qp%MLczfS`'׷vmV ܗvBǧ7)q4-Lۑip?CFGnz'jZF=\D\J%Z`8]u6y ukq_c:f9'83hI3J?ֻ!nF 䗩jpkF:˟$+B=ceȗ3DkTx#KBkH!Bykp<̺%) R©Qx`͎-gq6mxMh b&zx ΛXK .[~PYr}ZkcOej6;` 񛬝 q$˃NR2 654֚="D ~/DaAD>mGy6E_+[:CXccIKIƏK CNb8Yx-5C$|,}O <Ӫp#h' fE?W3Dv6kvDp̓FjdBU\쮘*3$h=Lc!.BYj8%'xߡۤ} 9P}p\6u*-Fۭ2,5,W~+f,oj;fmb Kb7q~5} D!ag ua"c@@gX >cZcJHb_^OOpHOfķ=wZFKꠑ\b.7jJ,I B?u)R{l^;K}4dĵUN(2P9E!/HLyFQIxi/4@n@O.Rg3AP q< BRjj+ώԆt$Bw ,Y >97nz]ZXD:" }ϔ_4t,.Z敬 } =h5ۃEю'b>DeuWC!,G#kZg%,G1Q/1$#}3VN*&3^g}u5m( 2O0!12Q1]wTTs}z`E̹Qt3"^} ^wִd- ~9#w,sV8HLߠsאk{F7 Fȧ{EH6Q:ҵTkFԝ_d7,0dU Rcvzec(cM P8F5|#nrRm.2^ A[]2XH S]_ZzEZqdAȷ"Ɍ^0nu%נAE%LyYr-J&ln[ I7\5`uSDqP?6[ ^:6wsHk/7"}}iC8sfaRI'謓]Ii]_a mJS2Ƴ59/p~?Ķ]+\H^gA asJte0Hqazk##ZceFp~>l_<ǜ2S=HN6igvjby # ޑ3cҁRKїfJ 8 "x!>b#dY\D[ GC_f[-H5mT֎qn'i:4c-26h~* A_[R&`QR|ɠd7N`KW r ("OöS_|1 w?K~i,8 8\Mp[oжUQwRK=ɰe~ 97Ls YʻR96O^]J)QoϱF1P XaƷZ^y`m"]Ǩ6ߘ>ES5{EO60XV0 D3_FS)G8{-JZ%R2GTzGSgʹzjJ ZXRp"CZ ˧)ҶBL2ޟGy"^o:9.|W:wzD>C s=̶)ـ#^hi84p {جd:;+l~:C8j7 ґ)ѐ=nR`w1)E9Ws{$tEϷVøF͝,F AKY Y8~X_,q7VPz W&Fh\2~p+h;#M$($yOXXlݼ0a@4ON/,r jʕ$8x1Z# XŽv1:@()]zļ7^Z* jnxۙ{,gXiDXkMO~c ^Z4"-x|#unSL/L^8M$hC^,!3AM<\}ԊڂGIkeؕ{Pz@{Թ FS Y`XkIQskY)k,ӱEXݞ{Jite+OFS9I.3d^ʤ/Q=&ktlyk40n=L#~b[fY-]ޚJ]XP߱m\+'>/D~? 8=B=P29e6W^_@/c 0?㻓[{bFB*`Y Pi< i `rdqER] <|=@`L kPs.rǓq!v"H/Xw:y7Cwb=t2C/ ̇7/. y:IQ)i1uˋrkalof5Y׏۵zz(/4?]U\> ,yNtx %-:{/HPl?Q ךk\,nck&Zv,Y ;2,hrcQT,yOtnL1mVﮢ)2ZXBI<$0.Rޏ@[khkx栻``,M}{oa+ ͺ@>8 ^$Ĉy`Y;ѳy/M:s D6ܺ*P'OzY/t ZԸDi䰴4=RR-99^zgyPXYYNk5e\3ozGiUI^YLL.;cmu.H/ܧדy=eJՉ(&|A$ #pk8RHj{6#MGE<|9QÔ3 ">'O,a!?=cnɈ£@w+u%t(&gE&ڙ w`ė/}uXXl2DzҨvQ`/JHY}X3 r,-\F?p$cX>%%KH'i|H`1q>nXTke .;q#[=щc\v汃yAl,1wi^V#\s$af-KG|}溛.GxLh%1)NHx9.*PAlzgSiG.#dh8Qq;x1$[PwvCՍ^5Ĉ?+]i7< x,d] c]1`>ދ0@.;\4f鼪=.SMÒl["Q}E _uH'Sf 8VƪcBG&/*rVHZ0ޘɼ,BhszE(]0"Huo@t6F e@~蔘 ;FW?+%+d:rW,,QĘxr2ʾ.`~5kVBUa۱~_؈:Q,bg/sp$t >f3Uj[+؎HG͙F>ke{ PĐNU/OBffV l)gNpзP\H([ؠvi7K+chj+t jv8)Bb?SnM m$D mMgki|J#cc1dis;fp|Cکz&Z KP[ YRGIq1e Q܎ N(f\?9}3,Gv;~:T|UXU_8Ofw:%1cu6Wf[2JãBX1+^1S扤7t~id5a_z9ףbX.fu#-nWNTTc WrCtmi ),p(By)/ۯ-p.}l~wmʅ(2 ^bږm sF=*?*V0tU2[`%kW#C yQx"$,WL19ĕ"WI^;M; ^م<9Z @%EE~#-|>}h1CU98ת  Ϗ"HXUNfko"c&}8b:\v)(W~}~?z,ySQWV\^ܦ{yęLi1!H7=Ld.%+^*g_(eRW括 ;Yд-R&]0 7 "zK1,+a~ؤ0t<<)uDv\XksGtvDj˅d Έ*cl' aА 8 )4aTwEǏρP|   ;ynK3>^O~\5@1V14Ζ1$Gw_.W6JL3캷2q%e }jC*?6=Y+0CΘȧvˏT#o>vtu~r9eNAPxeM&1Q]G@'5ن#?1GoKpd@7o܃^R#:OY(P[:]qme\Fv2ÆxSsZXN.M5}G&PUewʅh 7Hzg-t%b"[m/~Şk֊8InLA\륩kVPp#K:9#k~O^!Ve!u|9O?X ?}ǐ(l^wlAi뎦fؼd(;ȫčdȣLܯաqe*@v3-lى;"moʼn52 NSPAւUrq5Fo h!aj2~z{x g@y:n1 cxU&98kKPN@G󐿼qY(jj]ii j=sx6@cRHAL{K ; '¥SӤ-3ѵ) c>4}~ Z`qqB28jZ㺭F L2H]ݡ:mK?ݵYSWJsCRAwՄ%JYp{tJ9_mMASGx2Nr U fJO,Qn5+<Vݗ %t)zp(OoQpe}[#;8x4x`ɯrLc9lzELx4D:`yёN {F% .d +L?(/;hw5<]$-xs ;d^(Xo5ؘ߱(ȍ]ͪ<;ޚRZl2.}%usjmFPEW1")Tm Q͉6**#dX/K3߶ոIŷ b6dhBCypjƝ䗙(<>L%0ﶥvw*u V]Hl"I8Kq :AWA4EBT"\'2RVfJӬ#}Mc<"ƷUXiLI)߫jzL:jh+8`mSv/ 4g4s+@;,70v˩$Ly!l⛄גgqxV6/)"OWǭ Bԓ81<׈u)WTFv -;B gSfߚ}p/Vg(߇Ȓh @덂,M&df\|6:5y"?)&lArZ"a {hTu XI?BKj&p |M-M6͎Gw9!48c |/+?~.qջbXv38ͻ=!f+{Hd8_$h]U,1}l5(imGcYg:x68\Y e 9cDmLUֹ֭&DsY FLș.ᄓKgudpVJ 8.0.Q\. A󇥂 {&r nRqP 5c`c},5h3\g|nbl%姡dgնmoZ}wVeݹ?>߭F[?2I(_&n(is^>p0Kt Oh8U5蟣y&}3,D|$' },v+kp.+-zV_塧o( .6EaͶgNQEjynlBT 6x>ʜI}'d]3Dλ"*fs NRJ"aL%\hb0MY`BJ p ˉ8-b"j&+u5/<5zL}PF:(ps"Å9i Lai 4. ,`_W^J$E_a4\CJ2;i ҵ62GXyIDׯng[xhχ?`ugBi AĦR3ed 7M<˘Jo;Q.]W#ƴ78J+|Rk]!Hz{mB/3w(d]A8Ɋ2P!HAvs܇b*K^⭴WȧR:,/mHiPaN<#2Ib(p|&v(&7Q>LbbLQDx3]WnB)K,M=VQ&ž ANdaNL_LB5Otdr Vyp4T/YXbisHS D=l?/s1ĿG ~M#4-p!lt Ho?^N lxdPZ5U1G5LELڟ:({PO.0+pƜyGij?&u OBnhtuggZ=&. ߕ}_վGz M]9D"g-8/韛që ,4[fx>E6Q&8OZw׌]ӭTNLg3"a5*54ղHÓ% N`NS… C=%V2+e5+Sڲ0cuÌ)Y"B ,^A[.nFy=$M|g Sc_e}܋)p:eHPz6-l%bZ۸J;BxhCu<\bkA)XCvV E`/ 8st)1T XnrOTȖ !"Ѓ譸ǗCvM4q$+#j ZGCX)p."`Ve0fӱF @ +}ŨqQ;%)>Ӛq|uwM J9nà4a^mӳX(EMsHZ]͂A}LVn_4*k@?!s0ᚑ{٧v7AӸ{$t&&!z# ^XS([15]}IrJ_^*8"scɉ`\ ǮTr_a$-pÂ)q8+yCl`֞W“?486뎞i =TEeX\TϜ@Yu۳.GbM;EkG㷃9" sЏdE.fP @H /#ZwC vW9hcHw\yQJ5v"ι@eC}MGwQv cj|)xh6Wg%ar%{<aA+a>摅gJA+w,^e %auGAL!d.9]lnBO Fɲ~3s)h٠Rxfϝ 4E$Z IZ0Y%'l{08Ϝ٦Htdܪ Z(Hq+/'5'{ Nҭ$--d<=pI?qYԮֆ#g')SZ%2b8Ir2/n|]k!: IJ@%zvh8jЛ&JpZqv}+m ƐBgߎhhIaY ۔hw 0r\G9f=US剈Q4eg,f&%/ Coxb&*& {f^RWTG'!ӉPl81yǪU}vZy7īĥԂl$Nx42IeEP$jaXԣ}(eeY\;}opbtnuZ-@spG8a*Id4kPiL9KNvv:q<,pXAvFwjþoh[r0\=tGr#3T[S/smTY`R 8T[TIJ~t7B+uaD( Ȕk5:e*eyAB@'y uqKaKHsQ듟볹Ÿw6zf zaF =`ĴOޙ¢|ß*q =ⰚYP)bYÎ}72 T䎐#|MMin Ȑ.*xfofSX]5^c92.@"7#=n|]%ůŏpp"ˈ;ydXO:udMfPm^ΥNOPNa=D)=tٟ?.wXC+\KRKĀ/5oߍ)T{Ruݶ#kGԅ?>9H+B-DW#s\nd5WX',1!Kf3͉Gv{]Q0!Bˮ0c.7E6:۫<:WSR,8vbk^_X*%\G!øXݏPr?,R &vT!5f#l-50K54ji M_ru]J#m5ۧx+f+zNGDȩfywAs^tɷDž[3r~ mRbM[g+u7x,ym-X_{~u=sy =z{N| .Wq?JL\`Kv!.}XyTN] . W՛u<[9*W-:sHQ A=vmkfh=MʊxQȧgNQB\Bl$RJǭcnc|3}Z4v*o9A bQ&VfL$%\CO[ PQ1H,$? "mu"~: K+5[SMoEg>y 'k2SHm{o-r,{d=JČ)~d 't%mď*%d)#ZMUdU0{<|<:ȡm42)xm g4AHL)lǜ$h-\_o[Cf 35;dޅP~RqO+ٴM5I=+3`|b dwVԞG2 AXT  eb ItDIiMULN9h*uIvEQ0gz:.(۶%J}gaIgf 0Ebл^\dz.I@8%hBd+z\enC6*a8-m'Oӌ7(ˮʪ"pY5qBQvO&#.RۍeW:㢸[y)xaX{h:4J%5t8ӷu1 @yQQk?Vx2g>Ha-R>9a p^<F%Tv P*;sl;*_~%]f`Ev#yFIERk,6/{! lEndyfQ-Oa5֜tX"(ql( dʸwd (q.fm#N GJin{rgya ï ULTVT VfMl oMs23LZD!%95TJ?>Z*5~EXs1FC=$g<#{I)汸 p"w?AjzNe oѺwt4'fV*~|lƋxsMpC뮶J) QVuK6iǘO2tI]r4q͡0ҨȽH]Fxy2GQuөMhgFez*@bo?'Ҙ9tGӜM 8!i(^s`]1aa:9c m I ϲ9V^?A ,![דtԨgG!ܥ?A9ز-Nb̍~򧱄d:$MGharӑqb޽LmWo#_L(P׏/lO R𢎬D, FfNfj=0*Yѥ=l{M@L9 =+~Т꤉-X<Hx߀+aw8C+9AHCOi߶ 1֮iE.)HuMvw/!Ír0arŁv?/EyP`ftI^nd%d 7epGꎞL;E|@E6MjmF;2wg̝iczOmHw "螊0F OIN%qԁ*x0C ]G&ע/ttDIU:iPo g/IpS1:_Tnm\ef !`iIrS 4zܧ(,mmPߴkEѧ@r )'1SenmVt@5k_"%U?kՅ=T4\V+E$m2Garb3m9!$uֹ\L9}ՖB}bQLA(*ʋ{d_9U?-o<TPܶL,uX,e|(-;Yu7BCL+2Hl0t 0PCي{̥73hSk1m\F,sT3)[Z#E qG['81N4`cbѫй%ͦ94(<%UX-ɹ6E?eٯfUNZ6PHzJ~%D؅8\DqIgMYZw+yd#lF^ )N(B&^mCLN;pF܏U5.rFi A7EN" `/ r/%O!âl穆 v#Q|^1%M o&4<#6|̤s/tk'#BJA?UmFoJvs*M3SH~;dnP+dY!1wUtID}qzRY5,[6 d +E9B@K8TyK^|?70pNO1 U|J9REy^HJI^R~4P!/$d YOft߽t\OOWc^A {_ M晴c> (i^5q|<:K|[v'"wSd ,凈Ҥ׶QS+hwMQiܥ=GPSq?Jk e*u:[3 KF^q5z?pmp |5׀9H;T nE(V ؞I.Ԏ+e0B뛕}˦[p֙f0,g,|';=S "c:p3$4B~Ynʄu F _a(Hũ5lh-jui&cFnM`a"sM'7 36I6@.["Q:Ǟ#s1%prQ ^Z9%pzQV^.?l\n\Y5w =B>{3,sVHT?ƵEZ_V{F,e#*AՐa+SՃ`I@#kR*ͫkD3J a&{bFEl.7mT((fZzīIlII S G~IV7^r}G+okY+HS#世 f=YOٶn:[R)\&fMHz1I,\_mPz OK 0w^U%Y;8 SmGբ@~A GٹQI,h5zoot^oM "܇\,rQ!QF6 ce{eas TS#+?Y7.UD3MঔeJHl`ml( O+*itD$ uwtIxwO'.Q~_B2/p1jߢUU1dX7 xy\xrk@%Vhw+*Tk~߽1g J.fjWDWìnʄ/K\G MR#|p _D#6p3N6) m_~RVj"whj,9J`23 戼SWD}Vȵ_Gl 0E#4I9@R;j~h[tE>UxɼɭS99x g$㦌Vl?z[bR:c7"]_UiɌ`'E &Mg:Da'!X,bIq"d!-_ 5ݺ/6FB|L[-K 1H^ h:(Fе~{B] ">0Kssiۣ^zI =(QTBkk(/? *f{Kܘ9=?pw* t/FzH(؏|27IxLk$(87xJ p1 7Dq5YW5&?JZA#S`kԌߠ+e'[U^#÷ƤƸ8DU62݉SL 3QAduTV 3,Wum,.WDZ cl 79Ϭ~0>QɶfA{G$A]>|<(S0j4/ݎ};[V $d13'x+\=]C؏DV2㖒㍑Ќ^L?^`gpnU:z:Yq^cA#[U` ?(hF[Y铫̥žrLh7k$)X鼝;WZݖNkq!Fr"S@F5 ;A~ANgUyȹ}.G1qSR p}afP\]!~.[ O͏\^11rvH Uk3V#}1$?6h%b0jKnoyyޣe:ɑ;e44_=pM%KΘv.vٛAPcT4Vp\EzbImn!,TfbrLKZѧ3= X;= ??*CVvuXx+7w%6&)4Eֲw =;@M <ZGוhL,k4GSN3@avvVV:V:bS=s [I+ή?i@ H#%Yz{'qaOrg,K^3z.5c6`Oᾜ k2e]hA8ycG/]ϑ 6Wc2 (J& Ea"d΢6v[SΕ`Xd!D+')*FH^ѩo/,1citbԢ,=1@{CMK6wUc4=}"U* C"fݚr:ONTҳV;e:D~2ɄsK9]|OoF*wrfp,)(,);4n?yx3!-dw2xI=~GO07k+6 fQ:aV`H|<-.V~8WWOdB/\0FzFxq5ba OO7Db-w.3a꽕F1_]= ;7 98>ZVCfmTk CeowJˡZ=*:2ÓYUd `ukZ}Y"ATd'3oBK{"Pgi.,_ǥK.4\NpT ]KJߍ'&!'pZc!!U\ BK\A H%Z/6H= -`z/$eTְ㿒y-IfDz~%9n =PjaPOk=ѤbuɒO(o8j>h < ʌjc5;w_ ތ?"zCNȃĘQÂK}F(5r #eCw)HPW p]"Qy 1nkC 4Q:1-?6|GY!@TJ\y'G1Z1Q7)sEVw%R:Pn7r4DRJFn$Z8}ً SfT_e%+ 4{ FHg#' g2'0\ͪi%y:Teܡ[.%u hQ/aki{5Nx?@}$߳TT'fE!'_?sAL- ZՑ87*2fEçdU[J| hnœehԟQ2 $@yo#r~n-I-Ecҧؐ'$zP˜o)6K[uyS@qj=yBLJH5!ZfqkK"7g;Q¬/<a4NmfnS5Ȏ9>c-Q_H-H DnSŶ5!~嗝?߈ 2a{&% z-nϑ*)1VŗQDs9X[(`jq˄Act_=NWRd/bb^vƬUNԻree \#R *I.EȌ&͚]Z+bO9Qqde;ʢJ!q:\}F\sS5179O,Bvqg(Dt$mhNaߑ%㼸10AWJk^F'hU9ef^=g{;Za?Mczr)hMc.HOu"g߁AΰD10} fYY" 4N$rƉ`Uo'>Y_YoZ- :x81 b2\=:xJf&e6e.ut6oѹ8zH8kY(A9+IZ_6A]X\[N1-dw4_\6.wiGc~;mq&YFWw(*%3wHC|X^Jiqddkful g1ۧ^9oi3.Nֺ)~7R3Ϲے6LB@7NSԲ4FFF2>lqוlPa<"|޵t`aymv4upsAO-9èT|\$uQW3s.6ݵ\]3Zpj|< VϏGWuAHrN_o߾vAMOR3ͭ/iiS/EcX 4(,GDc񸐅B>F3LiJ͖~5׬ gm*# d ,[XQm ? #x6Qԛ&9HJӘdLcb[_X2TCY KO."{ _k >$9Jf`f]gWme'vկ r 8 ET_lM s$Y^N fFPMv-)ggWvĉ=N#\CL yB(qYDDQj+ něhgpT|zBCU6<]aJm π:.x?=Np 'A\X}wtIkY 5]%J [ sʳ8jG&R\>r3qz.&6FI4r L i| d L',˃I@,B h\h-X> gOW(r͉k;r{{n_Q+ ʀJkFm&;(t͔1Ɵysnm2L,3gBb\=1_^4T{#@{C"*wK*Ts{$3f&;z_Jv@Zۊ݊zZHGJbƛٲ^OodxAMeaCmMt6k o^ Öt%ZfcmYLR|+<$ t⼪*eDžگ)W8.: 5PQRh]v?ar^b%2 8_/!E sSw3qi` 4[O-ܝ֘>~3 f쏹4a?M<s?U)RܙrqV,>ZtXO.*;_gS1ǃ:l3D Ac(ƯNJ[*4mayPK,F 7+`geI`a2eN{a#vטk^ ?4;ɢ VhZYҮwV^."9c7-|BĀSHyLRbNAtmJ\_/΢T4PuZ`PRtPYUM8ѴrB,T0DcEiGW3-?̲cD _+raK{Tx]mG3bPՒ@MUC즇{sT;䓇;te2D{kiIpF~?/\_IiN`nS-@U= GZ!m W3#m!J|$Wpj]`[ Tsq?I塨WD:GN7Ʋs!UjQdH ?hAxf4@b0%򳺋GPL[ܞ5dd)qm"3@ecE&5mZ_ܱ's(A6:q oJwFִ/$ԤrKMTdefOFʿ@ EleT *A/^AUf#i֡ԶaE=ϕб6h23O E@,ďCeҬI~zL>X?ފzQ)쬴>?H[Fh嘅W[M> #V#l c"^ͼ*FTةZp}4\aN8HO%35KXz^M_ j8&U¿1 YhKLˣZŽަ.|-8<.HUs5:>2jhBO&|YO4'UJݹssreɻ~x2U 4Ces1gn†'Bg $q>l{`e`jQq%I.v>iB2Et#W5 Kx؍F TzΗ)5h.9<,Ԍ1,ys鏋J#H >fٕqngn^MQB{_[-@c$ V_sn=xhQd !{Oi]s dpJW_=0@C:,2OL.u+Auq73 SG~йT=`B>\V !+RE*AI"ˍ gEZ1O(Nپ!ߖ(UD꣺%mǼ dZ2@Pc3 уq'|W$^d ^u&VMQ.FNn~e6+y[g1EdZua{ldRSg_T 0|kШ&\=̔*MGWB pVuW-k4m@(j 6'"lAwT$/vA_M{Dz+*'afmˏht[bh 5eKJ q@..EL1TT"|Jŋ!T:5Xt2G=qyɊҌ1EN&-[9+sroknŁcu eےP;a~+cǸݽH@\SnD7GC<ڒj7> ؒU˛)h4BqJvþw5Mz. ytւbڛY&^Ўթ U?> ick2˟G*7+ә:*˜`e{$*_(2Of;RO"t]~;dHТ$5ݽGo:utpŃFʸ6Vݠ¯CcΝ}j#ݥY;rb$_<ܘe 9cQpD.CCfa|-?cN7'8]?S@ Wwie>0zP aZ aAnCw8+рh:c= S|~\+59b k|_{voۓL!>P0|ڱet[b G O]tiګj<52)mBŀ H'Q#29"c `$`U&@czqv!oE_貌klZ oɶ£2?{,JcB ά A#a'@Z%ߚͪz2C#(VDxyDhI>7#Ű|KH+cCqM^6XFf}1WJ66g;^pNъF_FZ`!c!^y!05|bT1L  DqFz3k/T22 ؆N]FWC ?c/fȰpIı%.)ת@iQݤaEg#bo#J@ݔѷDMa5#cuw7zP<2B!.i1lB욟+.lp4fiA_k luK@S,k@@*a-vPX k3+M(n@S-D mX"Yо,e$߮\Z$  Jz&'D]0u' 獵G"7 fB4We%&_6lkPTrGm%&vg罤% &I}ɿWqӞ_P=aXGi+TZP4613%мKa~ ~wPWR-@=S0Nu=״l%~G6M;m6Kmo maD"pɶx%A55rYb W8)l} ʉBhZt&P{ &gJ(/®=(ʠgVD7i;ˡEG"a6PnK"/X=Fؐ>!SH 8+W{Qr.?z|+_ \RxrޫmHMJ>^Gu} dEB>&+f`t+/hf{vU]D:DL~^$$46W`zg~oq{/Z5SuipPӖ#>Sy]pd/.욨_Dv+ AXe1S#6)z\[No8Sg~GU*4"fjWӲeNEzD֞5hy:m|$0@A'}M˒V# 8Ι><@X;t V)-2X,է%R4Hg:&si> gmr]\h6-FZ7﫬O[⺉D&W4wVBϞo`I_Q/(Ef t㽨9Xڄ'Rz?t*3;I-^wsj?^+Ta=ilG֙$aQ/2{0Qx\P؄i<_M =Ԝu0;~Mr_Mb@0;Y^۸zw0W_ٺZ5-' U#fo|Zgh<1WH#HmwqL3.78~v^)}T3S=֎O!̯k@T~L v/Pw2{|Gpa4_7>,O\.HO|嶐͆z…a]U[LK3^HKv)j<+I[p5Lﭹo>Y.m V-C&~VNj "(ь㳜 =sDtuF[+@e]z*%ybBo%|.»NiD0090bO,MH]e|LGGXʾXfЇlз'CDg]޹aww /ئYp [0l!Cz<E- ad V'NjFL>C3uǶj|@\n+Sxnu<*ቍxGS\"W$zT E3ɦ`C g=ǏgU\s;bA{vqiUZ7:YWSlE{;8=ӳb r(t N1}cB?Q;!#PS|ax[HNؒQNf$\_d} ;NIuE o옱 e]i!# Y=^E>ƘwHS98$#Q#&iV-LJ'#y@AՕgVȝTQO]};M,ė},{kRk<#Ckxgru@;prE0' td/!9%HAI!yGh|7wcαzIY>+#UksӐ&3j',%~#9α5FULInrJ p6Pg+]Σn9no h}0b/sN^A4WX3#ORQ uMpH,8 CZsώ>z}3{N{y턮YVd¤SI֣P?( v(qd(3y$0 )~ǛC* V*g4v)|uWy=g#aye)p|d$P0nt^Y 3gOIPmm|d`[c4#H[rڄ𶉀n9+(+5!a> )AT K7y%4ޟD2'h݉(Ԭ- ~8Wٍ>`iC7SQQF&yF[(ƔJ=JGEIVoI{O+K@CURݏ?LzN{ǺFFȈF򛽐E*Tس?aӮ;tsi>y@Z?ߺJ%>}ZWdWG~=qw!eQAU: TA ƥZ庶8ynNy1:yl9z=="/ou[f`q7/ޏEP6}[V7ȅXh$#K9DTG[IL9ɎBogA*91\,rO%fT޴7#|Ugx)C2,wV9Ɵv79 r$@H08_j޽l,@/X>:v AU_kVuwN|Kz͠6Mѫ{X_6?Ӎb3+7 _00Ե`WŒzKA}ԏXB]WC odV ߝjZAհ|(J> f 2Fs 4.Γмc]S(u̜{Pwmj l,/rY7m5V vHπ"BqKJ6'm'D W\ĝ^IZ6ie7Ѥߡi`'O)|c'GN n@PlYpnЗ 3+S`HkH?yPdWʖ{vgT[rL[(H8,.'Dz/}r)S 8Fץͷ9vU<-g=OD M!/ ,cl?FL`FYP~6}2oaKgؚŗ,.ׅLՖƀfu @uDawxNC;<"A?3+;-);&%ck K-VrÆ0?b)o9;h<tJ39w2_G0Xlu1r0L]P,8EulQ*qLBI}]}<]^3nk#23oC·qO4fZ>|$#;#7feS*e+q*Кp|Bo g NS  aD9$p3O$ڐzlw{-fV+3WۧFf}*e uIMjU|_C͇a)ݕjQ[WP7mݐ깑]}Q2˰b/!Dq,ެ$ˆ@2o,{  \L7l vAB  + G1&Z_WAhv3e%AʟDSӦi!3i-QTv7f@T[(3P˦9aDa] PBn1Vbr$-#N9]1*M nO5O1B l28fߣfz;BX;.r:U/1eH$7о]3l'˗!^HH U=늤wmbl# }`QAEPC)<$1aOM^Equ>B[Hp"ƛW :''GJJ;g7G`{&dzRC=8=Edq/n޿?D#PؼG'7q3Xˉ^ b%J z0>iRNɨsPeUoTɭ NbhM~YdB2(i$A. @6 (~THNK mtbpS8F's 5i ,!(J[Ͻ3bǑsW|EPⴹ#o枕B ~bmp4+F?vXtZ2 0JbI67Tωp~ώa Ho*U lB% ^.xEibu*@V w"Anѐ͜jU];qp3٘K+k,dYӭY8{kЏJG:#n1U 񤗮ޱAOd. vD2$-Sm]x7s3:Ɩ2FS2ΏZ u"#UI1t2-I8<3xg;v/TS81fdݝEɥoFK'zĖ'Wo'03c5>WE]0>lȖuqbMO4ƵduӔxAggUCHu]s֤ Qګ7兀&-oSW=Xrn3(s:2%wn+ "c& 'BZY\9Iy ﷶX5]W4o1sc$™a*NA<1^HNh?v$YGH-2@H2e\v;v!Bb}F~X5{Jy|RϴT=kxIVokπ"ӧI]ue'i6SİS FkZmOtL_n^3^R7yti XCByEt!*E3dWd/yl{A*a"VGDKgPwH *{OIEec(xazqX[| f^Gz^lF0c6-"0iKWU$_(r:!eX+VVBWɋ:q 9k~8SŜ;Z82Vv"$OCQd};8mpIk?t4Qsz`18a m\pd?B/ls<9hNaxLРv E&S⋦69ۼjq<)o\L m>M3m}ؽ&=>SQ6eJAB_X$5No`(u}KՃ8K& #8:X 2:E<(v+)1W)ud14nH5BJqh`*3lL*zS4ҩw*f< 5|"7 Az2KC4F~!rT,gq~ ۣ}2r/){4S)Tu0<)&] |C@wɽ#%wU异pFDe7;և tXg7W:1 ' 銦:% 9,>=1>oH1\\hBjAgDR=u^fr[) e`Lvړ m" :#Hl"qԊtD|ڒ<6}~I̩ yl0 M` N[F7-VaE0b{j{5j}{AK_!>lC؍=_ r q) Yj%>fH]*ͪH T*vEf H>E_83$ Q7A xHa|b`Ye4D~JYcxGcgSl> (> 诪V$nێA5{T:c{s5D4R,UmF\n͜OTCJfjQh]aWMx) ON,ѵ /؎O3Wu}jN gF?0Kf?ώ\mCZd:A7{U+ı@RM ZK$sGufKLԌtlzJڝ "J3 8OLR{2SX H$.{4We:!ʋ< F#L{zܳ\kdq[fY<Vodh%AbJH@j36 ȁeX8:-0E1GF%۸1Ep ӊS?rD&,JvBPQO8hET׼TI: C "_=*Mz#: c\r+3ePb7|ǻF0%`p[Rڰ|&-n.)Z)<_ :@ΐr z?}[htwM:vԼHST ?2/Д\VƓ'5cll*!$k[b 7OMI&jf.dڠ8@-H+:L kͬ.(_d]r߼ 8$qAPRe]]*Qд*\èxhK QsvX=Ԁ q5tV6NA 1pb8_OrKJe9)s{@]H+[4WB2!Zx,f@S*!a]6|) <@~c6Y8HXf=$x BR#U .dhwF<W۾GWT5Ʊ}2%Рe?X|ln3e{s{7hJXcyөUj;~D-`FR܇byt f8L2wЩ^bч;O^ AY_L& _^_{t+`),a{->J_V%$uhy ^loQ,yW6ē|hb@WxJM9Ypn\qN k~v?d/|Y~^/-%a׸ ݱ j"E:585?~V7 ;7 iC(Lq2NA,0I[YTS "J2l BQ:}E~Sx6}b,eX&i)}떦Mldp#e@ [HX. 7wv_yu#ye]w4cvAX2k_}W70ĵb1lEJGTyF&l)i` l-Īlcׁ`)L]3OumN?|TE8Ā6׿j  lmb&yD,#2^9BZu bڮb ,[aCwo#Wt1m%?b{ڽX [ -#Fu_<"R,lC93wg[=xx}w6B٥|W֔ 5}+jmIlNX3?IH!t8Mf'9bTy^5 NٷNz6$؉8]{2fH|>t^Hq ,qDA?qrw=R;"Ŵ]tsf) d )Jʥdg\w6鞭7V ;=OEѼM*mQF%W4R{Ap03]ȭ)o ;z2ڄZusBG,`= bvImf=fx(8vqZ7ƑGwepfXH]]hokgFIϽ[7OwKd}>H1}mެF"6reHNd֏EquiLx.H)+,B8Tzz/5օ'?B LG$EJ"+ QQ`=#0Ty\ "P/97w^X&Be TTxt.ДaʜtN& 9"Ȥ9U?#-apPr78Uۻzv3Bnyl$6u?Fj8=?Q.}9+;^|⺊*t4WˆXlMwTEk+mY)قf*҃hþF8# ݲ2>1zmo̰ `)?(6xZ禑q\xԠ#J-Cm@ |WZJic;TAjs` wArpiVoW0 R|<Оh.oUDgRacՄ+>&)D\ KMMA>gSu^v hϸQ. 3kk@\<AdOX@ZAS*j$\2/q6cS1wyPZ[]7I# q#p b^ȳjg oUM]Fۃz'1_qr5 Gk(lzk#32VH=OY | 7OE9 Rqe?p[U=.p-xfUǞBIv_N Ur 5g8 R>5l$-k쬳[":bsYKwF/Nl(QQK""~Nz&n N-*$Tɧ|IϘ*czno_0$x]1)jP7OKƎCþFtoDNP1a׳+FBO}]&WGt!u1`_YؿV8`"0V)k _-^R2⍪z֘@M-8UNLM"yo<ٳ[`w p2Y֔$ %hL_%brBO[K,,P,'*&'U7Ld" ަztfP4!#nY\a"X5"RH+Y ,7!g{tz&tYcFSs:p޼KAM;oCF5RxBlG$Oiy!bqZ4=0Tgh=/ȁm:ԍcF2XMi}M8ٿ 0jˋ<:pvՎ_R~iYp^[*^E1lBWԈ׊ y)ĩȚ LJ|oтI3z$䒖? ju7-˕J)I^* b+(ћ{1r |szLb7ԣ>`#~ Y&T`d2ܛ,!k3Z1N'}aoܰāQ;î-TA{kj@oؖR-A'BLaY^udy&t Nr^/Vp:=x`)5xv‘U zi#d^ȇnPUW{$#ux}‡A-3>a҈ XE}"n;-? .z̫&6n[l Bc{#D5$%~_,U9׊1egwR9A7e.X7tuwHm3'@ @2>7VȠ~sRip_h@Qg;Z7GLN$=B5+r 'uŬ\04!.VT~Np+qHbAe2\#k#a=@_X!QX$U%\ao%i 4,lM. xB?oEQhO 5 CC) .pQxƷ+ pZeשM ZqȴݨfCv8ŷ_=9 h!S$#WB[[c 9gpfa%c-B[;SҭXaFdhooH&`-\+%4+QbSֶ,44RfRi,[_b0˖WPJ i}UfQxނըy膘Wb?7dMl}OM ag"QBAnMFb=t$#GXܾSlW@:>q[amF6{p]:gzn^(tLuTwœqW{Dd!YX-P+K hR?ݼJ"#Pߗ1~t3k2fR%0C^@C' m7bee vCҏ0I\"C]{ oRXXT-0B- @]PeYmJ.JyBx\~p>*XL'Ivn9cA0Ղ&*+M>  ?Y}<U9gOYit9W,܈1Ş}ǵ}^{|  I|^րFG.#x5}d]ej©G5?stMF_FEn3Ȕʱ?%PKrZ)A[u{2R9H5%a;[=[z跋L2΃/ ifvBj@oRl/$ 4pBޮ@nV@ Y?t*++0Z}97OyQSr}S0& Fʥ"y!Idm%>wbݯ̈́{>.FCDG‡1ec̑ j{#Hdu:ҜӋ.*E w}zZ`$?&1_BQ>_kΣMT;1ֳx3BOjťgQƛ4d0w/ӍzJk城EdUBt|%^_TO]'"<{} T1"vso!9)4by߹r楎 F%l@Rd/nX'$ /OC))BkbF48SG %J%ݫ$6n{|mZ9-we5jR!bjHbɪlD7} KiZ7ǂVĎI[2]{@w3 9S@*=yysv[;̩݃fd&ƇS f"un #/9ؼƹ/ec~K\m׻;_2hn *ZТ EwW/Z`Sw d=2ibΧerr H4_[:ysFK_ÑOK/EslVI} ޞv|ľ.%-f6!Ѯi*Ķ_(/-Y6]۟v r=~፦R -[{+iѻEɄX0MMg-=$1}k.uQU֢$[.**F1x(ɱG̳dTaZNڱ d}+o0S#e|H ܀u#C+CZ@Jk%@ xJY&&{{ZAS/S8ZJLO5 fYk_&Ve%V7K04iEaʲٗ@6lw5`;^!2|ʍ3 QNadc/̵yT>xyd%3bwSĺ8OW9■Cb fS'7x9P25?Y>u/"RAF6rOxɐ9ZX粹槦~%xHF$[|b)!/q\gmwk7$7Htצ=6mY[7ΖoY7]q%U<5hû_=|fn?roVCѳr~\ PZS.ʒ s ِcA)˜F략"l#vTFk-'uU`=jՁݗl)pٹj+_Y鳞 {^f?uFP0Fvg('-賶zlfA]MEpfv_[Kyi0u`sgFay[]kVm4l"=ƶUǴ0ɤIւkApGV#'\%ޏF7(N_LR X^-ƙ˴wV" eѶ]Z>Rݎ?clfFKY8qn\=œ_Bʔs"8_Q_Fbus]mrBѠmL44i\ f ]{q<[{N Y.H~`K8kp-TyacLG%B#&5W1![h_߳Vb/~z'+) j~ʋ\{ovq?~;U`< xĀT;554W2|˜ZR'GvቑM;Q!7fl,e4C܎)UVc!yr%`,gKb*/.pu}PX|ƘЅ"4創4]Kk7fei6&]`3 suvpf0W89NC7P4P*ђj$&ݴ3p֏tVHB}`9N^gKU"*+r_F x+cbtI:Du)mD_A"W0]ae!g- ]3'pq/oˑCM*Id3iUS \`BpZN\׻[_4y1Ej ju.יtCȉ0DN i!ƺx@ďD7mYbohncZW1Pԏ|Vox z`> bPI@9%` aՊZ%pxp=){KcR,.ӢQ}75 dbzGgBc7U!7v~QeHLjpL]f헒G+:WSۿ4oHBR΍Dt̙E)j8W"WeǦyD|!LK.:ke(M R5ߒL(5XbÛuҜLss] _N#%8ļH[E _ 7Sᬻ<ɢ7@Wvwdwpc%0Wja8} 5Y(%WLr->\}4bXŸ|6b<$dP+Al!\#p.X$'.J$Upy~Hwc2`M\ cs2^mm8G\j^m~;Ӌ GZf3XSp آdHWa><.}hg4"W&n vsT s}5k=GouM^os< 1@Jv6ަ`sիFlWyi} XU!(č3FQLׄâf))NX)О51LM5v؊iBO"ޭeꨩ@5L黓x{ٗK)Քn"X6<7`ԲE ypv['XqCS DjbaV47M Dr0(2Ԛgӧh4½̢cn2 s6#Ca ;X&DZBdDL67uˁ&xk=!jGbB:㕷B(Bvg0Rgßa;z6W:%^!47-eSaNQ2s=7K!%#,P~b+^00DmT⊣wc7uӠ 傟:(aqVA Z5+ 伀i oH7=I+pc=om[SGiT{6ci(X|J4Sџ5TmY^8ſRC')1$A{)v0R #z= #)se4 $m ,=֎Co# Şs@3ɒ&C(_*2c<*ZTf!L) fR(j!om7#m^)=#>c}9 tsro0Bە d.KEV'Dз0=ƈ8"嬡X!8G A9O{._$f#H(epځ1q7u>Q4͇ej 2AS)N+m??`*ԇ:V22[Opp%t_tmŁL^ 2#͒?2=#(sM:tWdF~Ʒ*j(s'N]1Jni*NG#elf)U*=  53xRyMc=NE:ACjueW2܎v{ug2+һ[0m]D%]|f's!WJue$ZQ$0UUhxk㿯nr$I07n`0Qa»޺)m[*m͛-XlnSvf=$ω/p2wЩKizTw^o˕b=F{Au|}&*Ò˟h=5ëkkRg-~d텫EdN;bpW{) Nͅ.(SlVJCue2C0D_ɬiGhĂI^&T8^Ei?!lmrQU}Qı?&gǁ1ea>:y]UO9_@>ɱ`,ts;d1*BX ]_ICr{G1!gj|lfw>e*:2~%ѵPȭ>2Y8B8AhtOF~8 SʆBN;:e+GQ@[s HpQ"Uj 6Nv1,> ^빭r7qB<~gH av}k^KUhMٳ:,,(]59aqu;I8Kj&"rX?Id<7/ rulZ 9K}ϊ6q>xop7}'ʝ5DpFkh5"b_ ^cB\55m!jѻŮ2%9,`˿%aÌoMRv6 gJW8Y"fKGO^Ĭf/@{Ǔ S0ҬrRD1_uƘn~ذ%rJڤ!d Dw,#(&A%K)1"p}9_kkI)P0BT{nM/r񰩥ى!E:淝,R$Q&4r1БBCu]pt>{oi(ِueS"#̐@sQpEڮQ1AejrC5')0u6<;1(s qߚP8㶐i8C'ҰuO3fcWc)|FZ2[>R%LLpFy%3rPTL]J;|7)jk?BJPu5Tok/LixMK`rXt[Ly܅lxAd5Bi/݊Yt7WנB_dԧm'Gkzuɧkf%&Ėqc]eF!E^~׸YɁP"LZ8 kՂV{8HO{yL#n{ULt >a 0'+cs%.4bd`]P}.NsyDu!U⽦jd[/r~}`4KZ (X.4^vv/W6Lm <:rǴ_M;9\RCxvQ4,?;{F[7-^+ca1+=Lņl2fN<1<-u6SSTa}upJ}8̮LiQ *nwKM(zL)^.> 9؍c ࿚ LAF2 X[2GG9c+8`'Vvǁns jdOw~+f:P^`ˍ6^?yȯ}Tr$|eR}Qe~OQ.[P#q-N okCbQ0^732o۳Q['oGņ~iP=9G H#/*A$Qɸ#vǢw8 x6%Q -u$D~>me)7d\- TL'~-} J%7̱5!vka{zqM;PʬGe6Rd 'kzJѰ@C"L?+Ǻ;uJB 6$ j @`8OK[lD^pHJYЈdK[jeDC 0b;h>}k8~hq:5蟡^X->CIU}ߎd|O6K*R>+fި 4eFuS9V +w~"U @Mr@Cn[)n iPIzh4P3tL0*`GAfKn6a޲HoEfY]ADBqv3>xnk21I"E$LXyv7GBʗ`Ϻ\m.‡/% $Nz25HMcw%j|3CPg}ޙ2Zx\O"U8xYL>Cu'!{.0i4:Zf_~;9|j/ֹhQ\{Ckqu>ܼod2 ,P!׉ZW1ut[cA  &OW+lS-xJ锗L]BT;Sr7|(W]1^ھiqa42HV=q#, IPErwIpl<⫺&ԗ[0:МꩁJ%6QkaL.v2:Rjdźꔵ FE(JLR a5\b0d?ř[#c8LwplULF\ބP F[>$†njh߾(4n:s ޞW'x}8Н DqlFu O5ԓ"Ɣ1ٹŭ: bN,l5s҇WKk., rޓz 03FW3h;~BNC5 玌:m hA㵂+/¸vnwǫq~N7 gT$sYgKCB64 gL>A{w0!hl0*̩KX_KƶfLB<WQe.5x|M E3naQOnKq,!deZElg/Ty`r {iMJ r yۜ^[OfUPVթN%Ǫ/M|:X4X #> 7K[L"yi\v-,.W2&D:AV/ji+WEq?\{BH)Ԟ(o":{q? Q\B"U UxA!FdٶV#K`YS*f^ H 3tŽvAnmY.u܌쓶,33:;&4 >mqf{6@Ksȱ1^pLLQg*v*lQ\Jm-*ll-<Gk(C][|&6 0% {ٸ˒z;Lԣ#f1.cqZD )͋HZ~`W=#!GCZ&<Y{z#/_u]1jh/#Q6^!TudƟ6Glk(^5D$Ը($`"I:7?ٻ1C;NQ3%YmKl[,JhWte{λ [٘%vk T=K4Zqu[ 6'.RI_$lVJQ>> ݜDh&]';zR NI6^Ϥ𤐭І=nOmius =.Ĵ]=<8x'#&1B?BE?  eFՌwJgЦjSؾ*`ʍو@qc$*LGׅlyR ż~ tsfƗ*W,,XWUqfr\)H0.'97N`SqRm<LyA_,E. ZwA 1 3ٿ|duaaȨ5 .Ѯ(j![ÿ\ꐣrwW1*1= @{neU9G@ җ;^L'6{ 6#Vʌx [(RpӔ}#\<KC|MÝJ$ios.1@L`TUzzFka[/@x1Y^ PLbgXt󯞵~#Wd1jм7DqɎE^-{'' [MI,etTY6?1ަ[ 2YKrchM)䙓 sRl^5i p8U1؞PTWΣv4ib{%,Wexs \'3t-opLj\nIYJ_xW55M쾠3nFgpn8/# K[ VUi#kGݭz/"'"M_9zC<햼RhoR8b;y6ɦ$y.<ro` V8*zth|]?c7eN;ߥUM(9F~! hK}~0|tI5t+Z.V]V@bQy乏*beC&sTi&woTRV;*)eX3e(G0Nz? :T4+a.z8+>U QGBO i|pfyʛ,ɾFa]"=:o&'=1J]NU}"I,rn;$uΪlp |HC:xymQ2 |w} xl4:}YnqҙW&C 9f31z~eTZ'~RL_gx e$tT$CNBe嚢(*,0s (!`:RpmZggB/lݠGTn-JrqJ ckZf09"59'g ~VO|0t26zʵA~(5NҪ4sg(?y^YP J9䩎* ntr}jhÙacC'8:y@-sB0@%X ԮJz$A`늴zsP}L)PpD@~ =6N.@?AEPq~bJg/*%"Tu̒5?~}0Nz}qMU! zs"ߧMR;/$il^0MhI$oG] zlN-ո%~/L.b` QP" t={/ "ֻ\1WLt[p;{S-1)Ѡ282LXqBmBx${ 8rQ |2BjH0iw.Ta?daȌz"!p#mD9t+/2ez&0c+rܻԐyxyr:qdLR~Jr!^xd{s:s\ JL4pڿ F 'WȣlWݟ QB֕} p'2#x@:GQ?^, 䩠E.4q_  _ $#L0˛,ZƿN ԰EzcTDi?L%|ho0k)k#1,{ sk'U{%p&4#PyC{MCVO[Uc ']m="Z.Gr~Mq(5<rífƯdGju!?m G٦81qwLOˆ]X5GpZ 4YȏS%Fl/=L"@J4TNn^EC5.N辺.=m9%8V_*_Q96wϛT963g]{-=2~cZ[s_' 6W7mˆ;ETӡl1ZԄ7,u:nR?YM=~+ bV:3L?'@92"Nc!^ty4X_&bS <:ِZ>V. q0=M:acNxϤɵLZ\ оUR%e"!vvc9 4@pKm-1Z Ait8O kXo7H:@6Щ@^Ĭk_\PHU;¬'[FJzl?Ǘhp [%$Y2R,7\Uy U*~G/b 1uvf ~&;ȟtXM;8 6oe$űtIfRO!pz{T}Q{lt01:6 :RYV6څ:aX3J[bU6R&Wĸ7It[xkQc)6dwnfp'#$0^YDB_͉ʘDA{ ȢDM[Uѕ1s1!esHO\o}Dh+ۨ 1W O"w' ySn}6pp2!hr gCS:֐Jsҙd^>жn [=j+m Cqdz ` noAa?8ޓ%L!x{{ 1R:9SIؐ ة>CWFo%_Mo𝑗I4M/8W6n8IW&\:)OU((QgGЄw-|l"rl좽I"uR`T[βw\}:.Tx,sZkٺkmHXXʐ:fֿ-Ht9|' :䪙srnG51}oܶ.]jp+[Ns)@hr1N/irvLQzG=|&T]B֠F8߲Պ<5WD3}P #\U@QTyys=f[-_rJ ͟rZg֤1*% *7Hu Wۮْ!['sAEN-ʢ"`țӍ-0"QdRyW/%Soi,(>*mˆo`X烽Aˠz1^`EF٩uoMDh+T_Errm>{.y~_ar^kK?"Әr,|czNp`7ɰz;`)O&3N`?{|Uۆ|yŻ GF8zs=Aʹgٵ[-x pAv3EN5iT]56T~;ZwZI C568R37~`P"g0ˡ*2[4Ŧ&(Ljt rs\9/.܆}5_Qz}u>wc}w滅pD~Bhص 9SٌXQÅl-U~ftVGejk4Fĥj7m㫂|m.$/=dVMRt¬| 1,E"=JTS1pbv,_ "Zt7/DT-E(̣R[%)7u@X${O= Ҥ)DM+P#W>CE_A=I)C4-v"!n\| 83 vՇeAz2wPO6CQ{[L_:_[?C?="|5qj U_[6!W|.=N E9O;`3#F@gC1l>":v J8_u!n/Qz%Ԛ9MSJLK+~Ҍ):#NX!%d!֫S+W~+Z)t0h+s7՟e$`CVmCٶuH5T'Uq 4OlI&a*2)mVfRqw\eU>Dq(ې5i8uo;[( J7CA1~y\̓v]R@RK0 s'XPPojVፈc0;GuW2DQJ\c$۸J: ]E ˲^T(JDlݶgG5s]oT1;~[puۘveWHU9&f0ǫfwJLQ]xуI"GS#kJ"[TMA rfItJ䕢bzvfe?+֯N¯ L6sJRUgl 5^8e80>?vJ)n (R{+j/n?kтw̕ dZHYidV_%1/G]NwvX-vq!6='sv‡ :l +QD-,4,!\?Ȏ^+n)kFN!69n>P~8EQ|X7]/z.0\@`OV;N =Re,t{~wؚ>d&t z?)osW&RBd(XaȪ8uHO;?pSԆG'%S@"5Mor9Nb)uU(Y~W,ف+`jʐ܋B =3d67.){.UM>h=|F- >`V"ɤ ;uZZ-\X/p:JRr,m5i|FC*@g] mRl?Q5]z#cpmGEfcJ_U̗>n+SHYk4cx&ϝz%*,H+X@mS+t"_~]㓑wVhreyR63s5bѺ/*}wJ?QDiʕm)a>A[@*epSxG&DGNZ\iJٻ;̧ɔwP|brpQ@SkSymQ!UdBnSC_T<,yP?6#xDZaR_gݙھԊҖŤl);@Yok9h7Aaډ 7]40ĆZjH@!p~id-bL--gx 7?']15af.8p" 85A K*hnݝjQG&"F02tk[D [ ؜eLe]hdQer(¹V2̓U&ߝ JEKӘiƜ gίq06 S".9G4Ac7cpiP<ƄkgѶp IPE/b:~@F܃J CX_H>)&~ ?X6 v,e}K'"2*4PEJgFӔ:: 8w IԥGe!ac$#{JXMx$wh՞cΙ:`?Q5͂eB8JLфޜ~FÆh ;(ߥ`6w{u.i-`Te5:&}^߷g/l8T% OMqe ځX)1K];esW=Zw&=me$E }XP%{mbԂn%7^ v I >OoHJ}*ɪjpAG\%kS5Lp1>wpӒG@d!kJs  -:"yPXb!a͓dRBkK 5Ir˔7 `Q[N=Zu/#DbttLmǗ799t]Zj@6o ҈pI}$3 G6]ۉ 4/+AdQ2! ) 7뵂*N|)|}u-8{"&cP_liɋZ -"!s# אm4P`aB; H$ 4?M_Ɗ߭(-Z>yY3#^0S B K6ϖm1PV˾:%`G~'xy 'D Oq.<fpU%~^SJFqY9L{Y9$8PKzQYFJyUUx2e`KK&amiGjt`I=,0֢P]OҖK!qcqS7\w=pLϱl/mkjjd DGN!c4a'ep;u0 R[e\T3`$.9dvO<eεT{woT3,򫟤?HZVt3͟c)kB(C>aа.7(u*x8/Q." OYl*ةS-GO璑ru U{ qx"\M`]ܛaWX& 6u#MzԷRMWdƖQ<݊qoECjNJƱrcp( 0I1ŧSv52=pK\!4M!fAYx}[%g{EC)%nx|"%a_RL\/!ZoclB|h։J\!6AaWC=-d]jB:31aQ .&t cS{1b!;呰3q+6O]"- dYMZ/mk3ؕXnYvW#8Үo B k%T LbYtir17pb<np|l~ɱxd |$ BZUt!f$1dulJ?]&:Nh%u, ɟ"?M 2c!FB^g^_7^(ڽ?53?}⿦ =pwII#s1ʶ':@Xv6L^QSjB[|븊 !+H= =^p1ߵ*d;@I4f[l/(jM-L ɏHK=FM4[|KmT3]y)2=JbLΰeʠ>QO_Kp"YղMt[pg0Z\Hkdrݡct$71+{S:[ba 6^,Go2T^3d8cTnK m.wwe_0 &(@l@<ݴuRMgr Y#~jfv_wR~2i=ʤ.]7,(ѭڙTOtz5Ȋ];1r?t=?F{3kai?*&:#A׋a  ûNdBBM Z{ոhXѣcаPݠd,^irLU]^jKwxmk2XIN~${=̟ᙼxG۹~ JYAL&%)t`4Ʒ<~ >Tqm#GNYpt_#2u+ O"Sٺ' 6D`C95K}: ̨+BU1pԏ{WݴuV=b_8~f%߸*sզTTZ󧭁ۡ+0o]@(Js6 ˏM.").!%A b ǻ) '6PZb |wϡ,%%4ͧp:O*WF,vBxn肖15yyKgy`=Kj~@bYliF˪R Z3HxMo^ X&Z/$B^> IeV7܆G7o81<֖r-\=ѝ&N=VI->RՒi <,FHs@[ 0l:Lsa\t|U3w!=TuT`dN7';-lv=ʀV4A>{Ł/V|?9 Kf.iI.Qh ή.ߤ X,zd'U-8|euz.ʰ7WOO,:qUpeFy:gڃ5qgK\Ya1n~^Αa#jOtP{%*OQmȲ-k<*AUgL 5PǕnHŲOXPQ$$nA Z 2&c ֆK9Eo:Y #Ս-l .-恪)b> 2朳[[9PDWVfg9-/Liirb]0snVVX,.&3DV:~հ3zJFT\7\ 7DbߦxKN81ȫ PZ2 D~W9QFA8b!Р(c j#MJmJ ]GP>5r H:c.dpy0_@NfYmŋIw%,HidC N?Ѧvr@%]/AuR^-! ,FxFBH<>(NiŔٙ6Q1ԇI##hn O6cO(% **qcƽß/JBo; _EoGdxEŌ]r#_T*e*q?jVA`mjhJ],[PJn gJ S^&v-[s>X+ 8#z1G!]*BM:ң-&Bf>Ji.iߐd/6idz2ˋޗb*ȠUPRJmY/Ĩ~5Č8+TX/(TXaHu@Uph MGv0A$ Q\rYd^Ele fKkQnDS"RT}%p@h9i3OTeϨy@˥ 9xγK=RjELXgY7(R)ձV\)\G"Or;s͓D(=w~Zk -h=yq DU`b.|ci{$w73K$/ cnǂQr ‡ٔ3?|'qCGCY)z"_V!eF6}J~L[U*[_j@1B~#;!>dqHfcD4G%{$.];#z_g 8V_4['rl'8zM/٩jVǡP{[Ocߐvx?8mڙ`~%;L P22݆}[/g.2&ˌ7p"cwG{J/}7woӮ!w2uE ڠ+A$׆I}GGpO:.pSJ4^s W(tV^1Ž~4d+I}Ec{yJ-"qUyhs2G WYXm%Y*r|_zDž\,L`x΅[tuDy[^:6z;h:qn>e>Ȏysj㯂_\CxbS2'wΰ_J*#%2ap3HؽRξq/+)Ԏc9WZOA.Z49 :x孧08B4ٝqx&tqR4-podwO8,3eq1RPT!qM2I`*52Wl⤊A!_N (n K@ˉfqUMcPa`-W(B0Jx>6uk$CzJ[,Xnvu+F't9 teWsg~Ӌ>"p@"$f\wsU<;>bv+D1L7PW}[wISSv0WbE[?4#| ߛ+yr|vH+ qٸk.czI$М\tb8a̓_sŔ'^|A4B$HQ!^$lt)kb)| 7v 7{Շr]{Y͵׮5v0{Z]߃?SPizݴӴ2//>f"EV!`Gٜf[m u7w$Atp0,— Dq72m;z"1ބ$mԴ4%řoj|`kvR^5B?)y'ϺzQGJؒlE_HhaH-'Fj\!C-%leX 5K 7RRm4arETH6ُJNS$O0Rg(cG iQ 5j}0W)Y,!@4 فD1cln%-DyrnPNG1r,׷>LB˘HHI"ex^X|(JQ ]x,[F`ҹ/I_@LZDJ_g'bzCĆ4oug~? Ԗ+NL5FZ,Mk苽gG?cryU3]߼gieTRvQn I=MId򐹰ݮeV?&LԒGJ1L.gˋ,6%COGz ~camcW,I'YI 6j\"А. #zCcv ^}s}hV F3+Dh0c %=#d5TΫVR7.ɨr[\Q-yxz 5-Gg> !X^ PliØ J]/Sw6l:!><;qK.Uܠ+5c{{L-ʲ }֚5ugY,sXwb 1\I=epB&AVERgDG]{lw|FE2Wrv%ƸP,j-q~©د/ %%1 iթ[Lb?tx`t#g`v],b&Mu/15SDMj>1Eaazb$Bz!K!?=j 4jM`6z>_a brKD:na8.UCg,tvWJ\ưЇz]nGlecK:ju:U\JfaR."I9nNI,`6f2wla6N[͋4YM؏"wK Ϣ <يF䱪%%鳕M=OqB]˵H \Z?|˕۰%mي/ߘ} 6éo.@O=FQZAm=X~еLIK*jbaq0%.A:T<%p~Mk] lI|æ~@0݇c} `j >瑷2Zc+NS;'8FQC_?4Ǔy䢍DAK .xcts:?Y&Kźo XGЂ] Nɩl ևFr|tmQFNhUL*pY{\CnQP_X@x1_{lNK8JOBpAY}SknR{ , ϓ"eS_:ճ6d+USF:Ok"b1Æ~/rAw5U)[)TEsḰ2 <yqy +'<=m`K.Hoe~UqEO,Rgz~u&BS̐*1iUi*HDFDE1XM^q޽LlGIi [*, Mo6fWK)}b#,8uEC+HH:8)qe밡ҋ\pTB2\)J\/;^0wq|ɯ$;Hަ9&m1 H/Y \Fb'5,6EXƕt頟du;!NC igQ~eVuJ>)!5 jYXXSv46-miKAP >ORKLc?Y& f`}f*\ztkN2Pk#L Y$aVZ-a'.KW_sT ջD {ɥ`%@ĬUo#Uj攗LHͭh$fM6tAh>D<_c>lh索 fݳ䟿= lgŲ銤 Srv?4ɤ ә H#Ynbr Fr 1d[嚭erS76k;֘Ee%u}*m a#р ,g\yxucVt1Vœ@%ɡYg_W/'1{IZNLK`[:]wx;,5[qI8$F9Gyhj30LuvsWU%bXh]XrZjh1 [$=B&a[ssʊcw]#M8PL>|ӵx'iLG*U,:q@4VaIDju$c3o]|Fu}*aT^y'dɻ 75/e\rs@hQK0xxM->i$XMwԚ?!ѵBES($YagoA=ql'E|fYccru&v8/$\kt ( xT&PZiz' 85BvV?P\(2D, VNQ Ē2,LI4;l'nbѻ0d!r&|fLx(BW}=|Éa ڻ j~pud'Q*j$y6rǓ4a?Xm|a"v-[ m2\Xe1w UĿ&l d~}{mǍSȉ/8& wR~Bu-4h<XZ",cZ%cwHq*.+YuHьh(C 8AllY\ tY4թ/#.H@Pyk甦|`9{h]/>(B*xZhb&N~zrdq-EB5$$Uyd`Y!ΕǂC N {U4EFL5qtMT/r0@qpLݩ/-ha.+X m}dq"wg8 ҟe`eK-{ ^,o65~iSNG!ي+-\0&[b,oH\wAgիؙW2~{^l۫>TZrT+/`s H5y&a4ErP]m@8\WAAS̍1}ګY;fXaN}?7̊}~+4JToɛQ*ՈZLIt|L}/ N\Pu띐Ņy'<>+.+Či?,TVE;4#ĖKGF k '^2n?PU:bUСg½G .tZNYWQ%E<^j2M ) Y|h!(xE*zBt)o"ݱWRyy5٬K`ʲ$aGZ}\8_Jf2 M{C̍Ig#~)IuG,r=Ⱥ!.8}*8>/7Z7 bA?Po3wթ:{9goHp3Yx{+.q{uڬ MY7-~ઓb풢LVsMe/HGiˀlu2.}D$IjSdW/`ş'ӕE da B4oT={^֥*5ԦZ%SZh[˿cߩ'IuPȬSǒl!JE zmUdٵR@_2h)x/j!E4^+mɡ85P,kĄY\^PzT4-摞qsly@"ثʢ3+ZJfb,$܈  ո@\eNrD9PD->d}Ş ٤V\r}d-E;f)fȻ#rD8(|,4ס^IL]XǻcD;3g'ݞԔG.o"-73:|;O4+BuhHK!# 7'-ZsG@a{-Vr3'!$IDSC4qmᩇ-̓ ڽnl٫ -wOWʼnwHqYX\=T[ 򍫅 Gl ZY_t\"Oy%6 N\~$pT?g|~]}uvjqkTs T|mN_s?0ń_sApɃC3$x4g3/a3QoNcN*KC =d_zsjBG/ huߜg_d ,`X>:s`%^/qׄS02>I6LضyiT)\#cmf;UOgV}S1S u#l3E֋z]obf؋Cl-da09=3I&=*Zqo 0qwXk4j$kr'@Fk֜F IŒ3` X!iMsLzSc[K5afQ(ʋ'[ %l+7f (clFR)GD>c *φaKDPXbDZS*zdHd=>lL*FS2pJs"Gh1f&3>຋-ܥ гDT"15E+>MHNB/р\D$H%Zl՜>0(A W\_lt`,fڐt/s\5/y8Í-u* <#E37T/?[Y5_YfϦMԍ7[p1nN@DES\/75nxPyýګ XLгUHYw᪵ ISTrːGxUxON3Z齈B!aW$U)oJ _$FVx,iNk l"hb"ٷrN$ɒ\,/y(A+.nJw^}D+k !G]ztxR_^K,m?DeA:&4 ZaVO$kn]\̒L)Ol7!uCU~W]ӯo`\:`=,1&KrΓ)&gT:0Cؗ=QO**6.GkA ɇKgȝ́rEZޓM2 XbwsTh -q*[ )wI|Ejqy5Z)$e1$d#ec\]]Sf!M9˹v"۷,]̖L;}d@4k3SH]YտG.]M2#ϲ&A v}>C5;P %CRzx&bi{1Q4PƯ)8 2JDsvg,{jM_q!`)i,X$qc>5Oa&Pzrc7os\#Pv0`to`g^2K,7)^ڟ Sգ|t양rJ@lսDV#)CDw{=Ovǻ{3ė 3v3tɆ˅YnCf,YԜ%D8 \/MpROp-7Me:A7=ձvtTT= 21O!`cM\cV.4 ̆4\ݫ9ؼVFGb2й']N?<8w܇);2 Iw,)8jpU{zP ,J;l渎%FX_=qT\*^1Bxx" \d;mv4.[mDLbܐ_;4o)C}TMu1MͽVZ|1LUqy)1b򪃡㤷(}֧y,*$8 ͇zV( . 7(bؠsrIK|A;|;C0t uA.½P+k+sU2iݗy3{.z*3>b8M+c!B <@` ' wkE+I'~J3ICvfg՟-'N,`wyTϐ۽"\4=~ |yuυ-`RA+y(` T/#Nn߫N4 $'z'Mв]qmZAM7q "(`H#J)$XĢUX.-Z&)cV87w=UcyqQ[T$zT0I8Fk /"\_tn 8Uñ YO0V[VG)둝ăqY KGn= I3##}#D6ї]1K_.@5Mc"E'}dq^Gj} e%*)'x ܱ}{dʻ5%&j!V/?w!4ѢɽFޏdţt{ КT#n. nA~T܂rV0wWR" dިp뿥<,Dw4Lmoϲqo OmuNP+1RR({,%{=Q Z"U]7gyY;Sz 2T kQVrxs IN%6ј-{_=r3!y@<?ڴC8%ڨP"n쫠C.59T<3 59o6/yɥ&NUٖSpf9, rOqk2t+3ہ'c{eb# ]i ȶpύ}!+YD ҜZT9i.3ǵyoS MACOUԈ0^n4Nݍr[V2@M=O!7;}Vv&dX#'QiKR+Z4~[8gf<~P(RkrfzbL~H,?)GKk(WF ΪRQIonś}Qܑͫaw )״,Qkz~#wo VS#;Lkϑ> qT5N m+ *^h %-\cj](!ÝBTzʵ)Y 8SP&ƕ 3 o |tVLݥ-@N< mT𠇛GPovCƧ82ljYѲmk};!P֪ٔ㚶2/ :–[7>'a䩩PDW?l#⓷9<k=HiFϴpiC2,tfӟ$IG~X;Y!zѝf-q=g-[,ȱ 5S ׼DPw{$R?#;7;jW$S^3or*`B-FyO R yR*%QJ* C0+Cl"?aeք Lnu]w65NH!G&mͲ$L(_MWl <v%e7$bOMVx:g^Wkn&k`1fDJ6JfQ?7 (MUu9.Xbjl:hl(,p&x rw.h'>,$v5ȶT= @v[$:n?K˸~-+#倷[T-TˊG^^Q*SOK+)|xCŀˬ | ;AOsB.\ U˭\r'N0LK2o3P,ww`j%XG*񢈵:Ru9_*i=¶|Ӫ 獍V9Vkz\䢺m+'w-z]Q|>n )읳ewP6H;Tu M!:yXI+ϐ8ň!]xlKP*Н'6~:w@:"10NV|/-&O;<"aPG~ݝX5Ön$j'1ԷB\\2qt= FFcC:Xs`l9(X}GuI`R:\="dI[>XsyƗ;l!sB]01#٩<_ضnkZ@&E/h8W-;XVihR(QKeuh[.B4ו$ CI[p4D v`ҙRVVj&\d#D_栱6GT-Aւ f>\kWknC]+_yujl=x 9eIm`bh4}lqVSqzg:`򲹥!LL2 K4cL߿ )$%fH/DV:d6wa~KfRnm+CHvK)k˨92҆*=[+#vUxy\ ʣ9й_fQ30w0Q;չR@kGe^ yj&]O,Z]Ru9`ѷQ͢:ϑ+Ҙpu߰K?e 5< LF9:찄-Jѥ~{/ImwJ/m&MJͲW=|ዀtߺrB'~VˍWCd)пyQEzs&VuSz&7(a"p_{JRYُEQJ46ft~T\c{|n3ei@jYRx vC"`T Jda*#JR4xm D‡kG.4T\p`6*b .!ޯn=."f׶䘍teKjFx~E5`T"=#^NEX9m\kTQ$=t yy w 2SM4X_5J) D duARJ`H;VBd:2p] ۸Ӹ:9\溠h͂iIӅzMRٔmJOG?%|7YM0do6g l}KT fi. 6a.b#|R~栨 >VISd`m RchMF-ud#3+0!$YDzI6HnSYzg^/VKH,5#?(2LiAqd?Qt}rq::g9Ɩlb>1!@ <րz82 "}vҧӰps͍G(Z-`AHq:ڒ7IӖ)L22m%^-CAȥ\-f;we)Fe" /{!q9FU='j\| Ϥ1"CQv&SsV,iդ\QoFˊnRА.؋Ʌ-^ uN-݋)OpLܐN Z+ 'Ӷ(<^)>_-e.c2ɌNZ‡o"? tB?_ͯPqw&"bLS G`9B;/XWVwn6MYτ+ip,l'' f_IӽZ&BFN bԩvߝ / 2GrȐQؔsnwtal[?>L}$xQ˯f)eKAv%[)oA{*"B̿!pVI> Ne?iLx2Fҿ,X6ȁ"(aF BPoe|7V{,}m ZFoUx!Ù/QHZClGxKp{qF[t,EDC!2{t0Io躆&Sq7AnL5Odv6jȡY kOyn{Tdх2a2I\l'|Ыfp^vnU.`˚\x}>蟼f z'I0#.9,1vCn;7ֿ|]V ŧ_"G]T"z{'p%T*^-i{b7LF\eu"kQt'zƧ`%_h/lQ؆lkUd[> 9 o7Vi>ʦqL r @d8T-.O}5U'<5R Έ#x"H{ ."5%NKu)lYxc1UpR?1+ c=GHpV7}ưg RUއӹatk|}FLP&bah7?gNps7;KL+ғbu;I*gWw8KҎJz2^^ G p3O ˕ kiFs6[>v KHX>*0|OpػPѲ䃈m*fbJ #BMg;V]G- [ B!6j I-_F\Yo&ꜚ%sxܡ́~8:ÝV.R,k7Jn_^9l~^7T[Cfq􋘓B:d}4+R\0Ckb9?g2^b3Q` iKMs|آĩ lYE~gpE5Сu 6;4 .Ap53LT$\UJkPY썴p`c t^.56&h9hAU^Bk|Fx;Qw¸3W fsCsİAOWyGJf!ŠCDMrYf\/IZ%&j1+n㎞{֩ʼnaS_GD5Ѹ2x69W6A%+1qZ$EZ(!d"F0:~0Kً5OPudxA7|O!WX}{~h}K]T[i'K㖮q9b;**R~c$DQ1UEbqQiPw=!mG%Z x޻G1lYV^< ayCuU'.J[&(WnJ>F81M_.xbadO p>Q{ ˓Xяނ>o|D}Y NyoUGW;WMțzYy$35;W3`9L*miI۶-9JAn)%,{qsxOKx&IYMHnޟ#'mT!HEzZ pm3 $] 8釄 _-"1q0KO;XR)my}:})HLG<*X|\oyDx/J>w*QLvf׌sc^$GH30aٷݣ/tৱS3<}z9,+#=6:vE:WK ZPe/̡u%|VH  iõu@!i +E}В^qba!-=? k`B48AuqNco1EN3 )|'GӴ3)r*LEՎp/ g#ɽb.e4좱hT-!/Xȑ@ / kbmuP~!Ѿ#O jhZKlaP`fKRnA k};0HHk󃟏 e`1Hn< soI3#AhtГ{T7:a򋅰`IyW9lo0 䮫$V<|킲~N| TNPp>j,!{@K-p39Yq7C R𽊧~tc;"Hy ;^k~S|>fuq+Mj3 O*^QiM/iW;Z[n|{68xE>u*ISgf1WK%E!<5<['8$bĸ?x28RdM= fZW:Q-8OPR.S=ʽnRx//mɼ:|$h7YwiT$bRd!O]퇚6y/)q-Jz j%̝<)ي&BtPR>Jȵj2+AU {LVfL(&MRe u}P"=c&Jv# %ymb6Ո1U3~O>$s7y5K>vb{IWNA'oq{HN8~t7rkŃ?+BQVLie&aTƥ$ضfdP Wk* N(׺.&7b(D b#D;+ Z\[p15MQrF)'P=Zx pzs{ˠUu?)aXQNajWo_nJ qrο]So\PH <|=uf.]JE3c&ksN!9vJd? ~zܜ>LK_e-&DBw$=WQ[,w1b#ه$p{J*P;xIFTEA%䍟5᫓4oPLӱH؀~sW9ThSߗ+K\=D@DcQH!ߦebτ2쌪B@GMz)ק^sz0KBp ݆yZ#(1Mq nKSP%8Ȭkv .tQgh :U6, \*)!_) Mcm:"D2(@)n1M(t1 ݜ?k4SUW23:r^g ɶ[ 42?WAE-YZ{R\K %z=v,TM=huxI.?ъqGB|i%ٶ2jHFz {GK 7,xV~&}6t7us}~InJ:q? Ot@S9AHgTWE#]7ڑf+.93 @ṷsT? }$d:OeeIq%ά>K v&qNӑ99,K *~=u'u2hxՉke $n|B}R4ڏu`H]\1vt͑.:[rDp6iG~.kw4ߏ>$6>WJ49}9.q~г3M@i'Z;0ޯݒ4 *|{;7j?? e$Wֹ[D⦏(F s3I%wL@B]5@L!)bx3Y}'O"%>)޼K”v|C=@*# 5&mJz`.N.P)ozs> 5YȮ ï)*Xl61:, 4O}x"pu7ԸFw֑V0]8}*̌IҢiłƠ&ٔ`ڬ8!&pWPCp<U{I#)JKrc^N(X)'qPYnݥď({XFJ2–9B{wئLkH=s/* }m_XrU E=wgAjŠG}qoeӇ㞼*`q7TٵDE1X@uMV| >K )uYoqըԇZVvrAo0߳.&Nش3WQor|oEDrONM w8[%CWd60.3{Q@6k-(>w>A V5AK%סn ^ҚFDK'G|{ u-K.0# Hm8@(WKs> eN ~%q,Wu\p y'""~ǭUõ8!^GQ'W&Av`@"!Dp6?@5 ; TV>KYT2Jv0E+]j}-t.҆cBwK4Ҹ.}{О+lsgŽ9rtt'@(ꦊ)71FAYHGBĭVcI!}wxh;+fިxj׋Q46ЃΝHOY)RyȚO:sSI;|xB2ݑS14 _/4w6=w  /C;EXFGuwG&!s`ZG4Cu$#| #sHBCML:͖`6GRHU??fl鈨 yˣ#7G[0bR.8.$݅H{ϱm(t1;V6\ei xUMd~="aqd,qC .̔ZdVHc(>ؠoW7&O?@!ts9],e⣑0nhKi r1tڄmCQNJSXVKRJ|#1Q?p;/>߿"H{dP\"Ks|LS΢}CxU ͝29eick(%d.oX k: $fB-v;@3hŽ]1hp-hzV*_X`7 ) uIXY]w|يU-dlFv>+kZ2Ȳ1ʪ"+t\|CQZ oS5AΈ~_/CG4]ea 9@5l5WO̡e "+͆v M4'*J}@-:2ݯ}mo}<{zv PB<9!SU7<sa I}h΁s-zBֶYLoYJ~t9SZҝ4< _D0g?9e~Ra lYm#)i%s|B$.`U^$ g=d?=j2Ɵ:pxB*2Ug`m43ؼem`ʥBFȄ֘0l2<)f|nj nt2Ńt-ʂR/iEYRv"lPm *;n@ .^]`4sdAFLB`@)7BmyU#3) ^Y͒= `GA5D@\T-1MAq[tЇ/hұrT%Ƙy$Hd>kPsbH ^^E=1FyXEg!vwgZ݊g iT<|0( :Wl6JB1i*iXK*<*DK3i!P$%`ez055 O/n+Ct[h(hϻL[BsS:'2Gk(M{E Hp 2Nn}&n9 3m X)_Nn0ut&kár!V7!D5^L|pw+K}^5[Yaee5QoJ/LPd;C3U,~cho+^=t1qO?ī'b|?4 H%nkOhZ / ¿/#qu|1N2NKR [5%p&!y}%7(>a<#cU Y`N/@)FA6^{H'hN `H##yR.p٣gThhRk!.6pYLJg!ȺVďˬATb>Bolr^-(;^GФ.V`dK*jNO_!rˤ҃|@kVz+ #6iy5z\Q9=|"qsX΄ތYOlc$d2nI<Z\' unR96̄yY_Eh F׋?c7,5 b+cx9'OLʏ;XS$v7tٝP]fDH Utzlwim~̉Yz#,[`M7 {Y,?.뭥lr5Ո';H#46u`ǿj{ fS.ti.Ҕ!4%%@eݠ^e[M$8ie L-3'H7/ݧxJ6,MDW]btP,],&WT<w0Wt?plfE|g s8@4 p`etؿ;ѢX3S & kJ-1 ?1--rmI:Mw/˧Z]ݲ1fCbN %!0 _sL}cj~ikҭDjQ:і%rfS#4 7ǿs.r()O€]\7y #WCڤR 1\VfA)q?AMCX6Ӓ@=>s &=?s;'ozxNf7\A}f ClR klټPA~KVrQ4>2TxO٫4|Q2¤$nZM|ѿR{Q"C9;&}U0VcSV.r>r2rQHs%B׵bJd@1w04%hC(ﺞ&iV[]n1#lJYEXycwi1,mI!6(RۚYnbfMS͚['=\45: \ݹ7鍹>,#f =k4ơ;[a0k̨R[grx b>>T o*Ƹđ^ק.`ħ=Vn.ΒU!ϟC\K iy_ }ͥ#U,ܫQ*_S4Lݷ5 t< s|jJo_3i=ו䗛#T[b.4vǦ_x[C\ k_R%݃n!QTc5S+E S- \3C,n}|Yd,<1۔iLhZᘭ:ѺoZW$Kn?C]H>T jI>ׅM,Gp;>j>󐓳%z STA Ǧz?W|@` )>кg3482trƥn..h~TDw4T2 d'lLgf)_ hKf ybw_:1JCqѴ_my&/Lo99*2J"YҦ4y1ܕ/[Y,=1g9Vx#W' Rg%ämiIu?߇eD'sLn#qXs a!#xNXq( t&UI:4seK ;1Gw^,V(vrM~90$ ,6ءMѾOMdvc #:], L7EiC!q:ؔBqVJ(ʎZK{}G|b( xh+(J҉,GnT@Y='pjkqhX|y >05 JLACW3XJ:):`-7\nRUm&(ZV/ tQ\L ¨haf$bsf=_o` >ք8##6`l3ba,Ĉ[sALXkenxD̤)dO1 -frƜf}v%.f5V)2B 9Eb@)rIu%1I8]3c~TLB"33b2kZNR4R;@fk3pO0C1zمN)ªX0,^FR: d%QA钻D5esQFלRA\+Ԩ9!"O.?\m" it4nh!-GӰA Ò\qE>-T?V$$^!c&!<W[^d}NA !m_kL?$#({[|Tj3i-(wkC,%8:;IB6e  NRǮd;as$s~CPY 6-B9qзt}niEⳡ Qi A1኎ƜYFB!'wQ='^_>-*gggԬm_jNZ}72֖&kc|h~ U䱛/`K4pK Vsinj՞rQ4K*5e=GX?i=V1X{Q}>kiGL`ňlp170ϊTzC Jfm͚l6ˀ5Ru64^pA_z\ QK`)9 8I+ ~VhC>_g[^c?F؍+#£Ժl4 Ft :겟 eiД>p^)xE0yݻ;l,N=aq02S9*Uw1욫x9{F\D=4vu97%=.^ *J` ;XyaY:1`.lum'ovX%du]rƮg\Ib s£6nty`8VC#"?1|D*5"2Ă3@ӵUAs-<#:\Lʆtf8M0|/Ug`RLKuw ^W_,ȢOhg[}o(FJŰ16zPpn{vX{)Z!Eݲ.:*^bH,pScޓ%#P\7(F՞قj,s" o` FQc"g{;#6VDx L8-S HT +qwIΙYugDM6gT+=3:=)r-KwIB;Yw(iyh$Z)KCƣq5Հ&(U40rNy}pѿHL1ZhH cVLv ԎdpoqY.~#ȦWX[!& wEO= >0,C^`[ TzM[9 H X9gͶK"S Cg=\sX-NQ? Y43VBr Nlwco"|zJrµWz_X'6KW;=Eꝴ9b$_hFAk~ 4Q˨<Z,V VLY6GKfNta4)^˹}VZWmq'qEmz%3ٴ3;I*@aW&dg{ ?YmkHi5c.P sqG D*qUsdW;F091{"oQkPn..ȪA6셻HW!'fQ5䃑LY5M 1?_Y5yxp[-XEi,Vv^T\@x)/=dcB{ֆx&]QЀLJהTؔLqhR)0UZL0Φ? z  g 5yH- yiA)]Eibqxex< mڤń[DļO@Fniısr9Y?td8ލyT+NWFcH9s P$O N#);!oPED+b)*qr# |o\s%.wHʄ7xspVDSW`zsX)yd3m{U綾8j{m#2Kٶ-۝t%𧠷ۄO DOg[" @0Rw~5-2j< 0.JYua^/*É4U Kv uKME86Oͭ k3MWlo&?n=?jHQ SfK P;Iϟvaí`PqKd<ۚIJ+FFb`+GӲxͱ`NnjWı2UۘOKy֡!#x;qvx6(KXTNY_}Zeڂ7 k!"/8ߒH2h98{K+~bvh(H-]Y(XLA_ 쮈pèEŞi#CO~5o=.?2XηD(߸__| 滚Ϝ$ۇOZA(l.5.lD)ƌhTQ@~^7yr~ow x~*}5+:`r/2_EH&r@d, OՔޠpZ`NbO?le7{;-n:0Hǩ01 TkrtaQ ]+ro#18~on;=][s֚ ,fBG!Pw~DjyٿbF|ʪȨ1.#~z^&)H/ g4ZG l_(i '%9a4pÒ=!Cc֣߰DY1˳M]\&`a ߨ5I9QxڷX6d,2+ɑeú6xjf6#'b.M"v8|2 ?9RG쎑{4( ʔ]I^dq{A]b8Wk,3"'Q1(Vo*:1&_ gFmyi:% kAJ)jd? {Eb,KSJ&5xu]&B/- \EN/obZR4o7?W`$ { Pʆxu~%n7?M *5B3Ptc$hg!!dToKSQ+QĊםT Syley@a}Dn4PSW|iX]#=-F>wry81=YjRֈs7ӿc̤7 !+tV$^ܚ9EWY١drHQQ.ꔎ윹q. M~PZa-^-)%>[t\V݊b*WP9FބR/+qYVUv yo:: UGcL's,(!nhcGHfɒ^^ongo4Y.h旳KgM80qY˿&LyVyo,#8Ot/; c/. T. ' lyV:8A1MԾiKdT#Љccg,P.CF]s <)X Yd ڌ.C!u%Rzc2k\^Y,W&܈!CY9gu[It!AAC`3ޫ8X!sduwT]I>kr`)7MvҩWɳ4DΥ>񈅅#Rr74Tn@2/^,0+=풲&/>ddNuMEO^f) K}UE+e5e1@ Y=׭\w4mA%ʼ Ehkc܇Ԇd͙2*grB mHJ'KGR:xIr̾JvVCOVš[|Jém '.BV! .Dc{@STÍ rׇ VIZԽ1Ð6 @z_?ߜ \q[5t^y"B]L QXY" ve vUQ@]x߿uE^4"2ڏ5} hCNʁl n';14rǁ^,p >/$Swֆ0`L'}mŚK4{yH$B&IuIboq]dygޤ{`.!K/ۈ|=;2m` 0 =%Kepp8 ^~ܝ.ViV*' Q5M\¥(h]AdZ Yq0DVC5EW$ BQP^`G-ߪI"b}~ryzč;@:/o2O׸=d)wFyݟC`To .WWд0<=y/!)%##no 2pRF4Px! 8c4[-5(I_-!60Sh)_N R6?)}_YnXнZ5fwvyk bp h{T al=}E%0{>0'6 <ޓ$.{`u<)vʁ8yllù9 ljB ~U.roqFߏ-{ES!Mdzv|Rd5tuSC#Jk#ϸx}RذvN'X/IDڙ.\A+f ؚF:ҺetMe!^EϜdW%('d_x#,hճ(R/F KOB3:\PA̅6${`Vۘy.ݰoڅBHmx3\Q~G8Z!ònI(qS8{έ/!7&֌[%c21vI:YX>(@g.۟0Nq]M^` 'Ae}u l/޸SKZ`4 6t\(qGDKIͨQAr[]<]X;ҫUg;Y?ʆdi6\uBT\Ջ2w#aF;0> ș8Zl5o{FAP\pB/gHLR)>~H:XuGFoսEIUT} f+w -ӛ/dp~Ft")0鳇; 7H*CPs;r3^sYLcϐ$_r5B-/opGuuZ qK-,?0C;Ivݒ!;.vbb5#80sA*W׵Z Ⱘ>}IrNTMQ+kݍ%/[]ۤnYV *opgQo(k?CÚg0.z}Sovun|O3|լx=dy پ5TݵhlB> aь՗ėvcLBB{b҄)%j^q?el,]B[w{qiw!7?sQ]\|G˲TMW1G/-sMWe+(P8cLrt9}%M~fsqTpw4J1HeSGA1Mr<݅/Ӷ+  +lK2PHn]\߭4QA88ȴ9c\dZ TMqFE?puޛij) %Z_>#)({:Mi|Q7s@‰2u_Lg <. O^rl?mїrfͿfeCT}3e].E$zxPB+ Ѷ$6æ6JT_Wӧ NTY|1""o`kjZXUυ?XgrV!>^ Z>Al/:vU؊?*FĂ/.1%懗;V J[L,Xffh9-N#!)Ç՞'F{W`D BZL10sbY݄%P4q惏٦> t;DK9Ō(#e'a &I6@s)L۠GU( }d楢ЭAX &\Nni ;Qѐh!' < ͠i0cAJ+)ˬX ^Ov5yNś6;* ~ВLtُgO%M0EG/u0I^܆=TQ_f&;P/*̉:E6q !a9&A*VMV`0Iqg ^vۖl-Z۩weB$G SS."?sHKbᛍv]`Dv+[rKyq,7M,e 5ɺYRq Lmlrl-:9_g>X<G] {S.SH,Rojx~V\`x(4< r􄍇~NoDHWlӀP2|8>^Tb{=u)ګNW{dC-č#@ '~ p]j|4w@: F'+ĵu%HCD1&=2cFz=2HB2ڔb Z`[Dz#<5s jf}8mp2&SRI{;-j&L*B'qBUI3ɃdI68»kUMsPjỴA=2Sw h>CV?i&X; ExjIpƯk̹^Sx ޿WNSKWۗt i_M2_LOYmgυ@|[G!\^YO{gʏAA[V7|"&xW}հ3kиa$bF zA ^oT;\'ʄ8qU&8K$=|iE&u,Aϫ ;\; Z-AuxAȄ6ͺ.Iou ɝAb:I|7A"jR?52$/rыkJ|ep(f0lL>Z[]zCT2M.I,n4hS5 Jˉ + 8TCwN|7l܇]Ji&7w?>d, #\0L*F.TjvPNՃł'e#qJqŮ !85mzi<ѷ-v~kYAt6zd6}tRhW} 0_VaETkHwմڐud?U"O̦, 59)3}ˢT2AaqKGLbwIr86XJtM=\TB!V2<* 33&^E1/CwtlXғ~L6m 4:zh{se^*Sd/Hf U^GܰB0` Ia^ MOԾx&IY\26g0zvs͚KhfkR7i*4>u8S$1G5QЪb3j-"zσɻ眻HƦ Wx:ه;4%|ŀD\8T~v+XY2pł:dY(Oqg*,n&Adw74Np=ɩ{=d9`(d3t*;86ȁ&PH*eG `[W" \8s1Wۛc_ @s8"1%KBKCUC6t%-t:)$3!I ^sK= WAR'˛,/uDی9",LdX#H1kr katV/~]@Fŧsi9ŮođfTߨƒ&Do+ZQD zjYS@m]kƘYOX$jLNhs:Ut9,d^.H]滟Nˬջvi-Ѭ rN Sz&\O# 0!Tk_ݼш(\X'ƚHU܍1!ݒ U:wzțK"tZ wE{v-Ԗ]yI4$nWm=@ M_oÀ;<=fL◆ ]3jAMٹ^P" +_]uDK.AƠӇ~-w65)B v*9]R_CvBS(sXR N YSi@ ;mW.l\@59E βnGfվ4l/MJ8}E8ډ6fp FDFT &@#AyVJ'GYtq>^Ѿ*@po#ޟ`E2(3MXH} uDh0ivִ[C4A>$ $3<(ύn9F6=eW1G< KJ6*HNk[iMsM;[N 5g[547܂#J\2$v@:%`/j"¾펻PY;ª8XiP=U^yzEMʓvnfgOh+xp6*$_}եQfa6#?/b0vdސIoQ ߿T(@ K9,H!!J4\*"v`VTr 6:Q[Lxk0m+Sc3 QY3gylS0E{ ) q c3UB^^d^EpjEg"dċEg%y\_/} z%X{j7ʁ7DX\q,J4=_T=T#9^f$zQhd7K|0"ƶS\W6 ͉;\^pMjm@s};Υ;Iݹj ik-[GXCx>{Zn|偭-CP%@?Y--\˚k66aVKZBETw^uENӗ}pIu47РL04Žc'*X4&vK $mQ u¶r7KKl̺GOjRte"UݽBz R?|KSzVX|a.UKpi0\3|d̥45wr,- k ':>jw $̟*6i]/"LW$vt<}W>ͻܭx!-.#~fa[z#:>s\oһ X|6t{HH (ʼJtsBi˲)A - ̢gm)j.M?nl" Er.~Et^eN 2 ric:%dqWFtsܨ齲dd$zBEAHjV"8KGKMTv |+\&z?XmOWCq>LSz R /{_a% /B1M^An/bD->:8#mCīz2b^pcjv8A]g`(\+48{^3ƖGzbN[1.'L=Ju_¼c@9Cc^6FUðϲL``O]QG9/2ßVff&4]e/TbTӀ쿑P"+' 5ƽUiL3l.z*D]n2>.*%*/G-VA.!f p,W|&II"S&4m:TNe2;m9)Lkñz=\w/D'fZʓQ PTHWFZS-<42^ )y"weѨ`ٞ+s('m` Hg:ZtoWh$=x:A_STJxYg[p!iҁX%/7o%]ϩ<"  y/Yta?6t~K{S3l`'r¨:eH315TRkKԜ /9jN3d%bDv-N`^3=R.rW-TXA@Aw1t⦱py/PYR|cPm<=tP SV%`e%[6}Ϩ%lӈY!:N֦U--hA;l'"і"'th eJ෦?,xT~H5};Cr7~!L XI]^㫼#Ҹ%l1S6 OqS[zDj馏;DJ&xV QRTLگO?"ej,y^`ܟj)KQIToLgƞZf :}yq=rXG5EH8J! r[gƔjQ¤`]Rŀ6g@q^i-1}!d91 ]FUA HIDI.ѢөfIA\A,- Gy'_J'.^YcOjW<)} 7|iBؗN\nېsapqGԇP=Ⱦ:#|C3F۸g:T=Xa &>d/VH|,07aq]z.^.lSbfҗ%溃XRQTA0h}NW lb@4X7O!bN!υ<hvS9xb!+e*Mk߁Q+ !:DNK`#x9%38RL6paqNg# liC*+$n(WIY(T*]; "FR0!V\lcm PZ4+A]x"stR$Tnը4iƠ'98RO!W={\È%Wq&)M;z20QK;| X *_ /H4ӑB#GҴ7fF"=eOI W#yҍ82hoK 'O6 CO( A3-} bO]$$5/YfE>lC6ݮ:bf s$en0b)vnDcd,]ggj+ŨU xwȈĺA){V;4+νq≫N7+S*S8‚E vq_鶮u ZQ}E_@ԓ^׌D±?V3RȔ W5xڕfֲJ_1ykMWK3rn*)oGm,54Oچb%SLel/UQ$]P'se@<ap==m>1:"Ͻ6~h(5W(YvjOP#k 8]YC-ji:Z;a8QJ"7o?}$>|E4 ]2 PF|&mQ-ly1df6V[ x*թ2)Ֆ~[>0Y4qUOz-/E.F٭]5jaCyw><@Ncc5CrS1 7U…(ϳ @X}.WEf"ʯStPN_{Ri JNUGJ8s]{NO HM&P΄ i['3,[p<3arGxR.6)mi۸y~(ڈ Fюߙb;^$5t!ϥS9\ ~ 1guvMOƧj,@H6N8lE.ﻅaT:d/B0 $ʹecBKcn8koD PʾCҤC{GD|djgĞ?&BUj"_$eկо,LI$頣KlØ&7HVVAݲO:*tyrL@`9Z\ Tld3a}¼dҶJ>7Y\ȕNlpF3{{.] uʸ]y+ rQ2R$dː&ۈ7f{;nD%4=]& 6,!{F c`nHdwyEI8s0 #68-P @#sCzvG`ܬ*Uرדj^v,=qas%۲];__ ]('VccHHS1B BH3~{2gG'OU<4F-]0q@Ǟs̕뚷Q27Ei~;^,5+q]˺^%)fUP*vZI|ϰS #@[Q.>=-ӏ-';(Wԁ\Rv(zJDT4cg| wLVYZk[!2X=D&ӜS.fk yK]H2T۵q{HRE mf1OaȖ1iܮNu]unoС$LOET}_t1d%Y)ĄkT=T^T.]Boێ-oHt\ͭ<8.4,0}QI"@E'a8XyDUHy 0?& D:jo*v.1]^{eJ?NCw1m<݆)Mqsكg $kqf mPJ]a5be4a=͋ۍO.=J? UBN V6 E( K(Vܲvh[Շ I8o#д W~鮳 BS'Ǽ"޲`LҚйڌA3n)Fog=Ɏgls $q0bsU i ?n6U Q aYەB;z'Ý4VrĥxM4r}tdvJK)bmZz.|,`*^ŜtԪ`qAKr- U=nY4\-hXؔJ1nY83q hz,z taOFkxE`2Bšo)?]r |5n0=cz='_،>$pN} Gq]:MH狸my ~Kc?f!!A=N?uPyxq^C񸍨opc0s*xD*{A'*(##ZѡOMι:чDp}5>NK?ATq #E1ȹ1}OHBS]jTt!c:Ct|1s] ]`xeԅ5ص=bQC=;`L`(@`.3IWpq c֟ SʃW.ז]cݎIz&s#E< wb3R &]jbdwW~5c;1bvg[k[w41[7mHh!{{ 2ǫ |%|y+I8Sc+&{F=~"NEI!ȵ-ְ%%mz.F }NK1Hg=9E2CFkWH6vJG o~3;7q%cG0݅Eܧ'!wd|ҹb/e/S<8/رdT+$ļXczH4WMndKE3Ќ8 %U.9YkL]Z BC` xak%s5j'O -"`8 45f6 ٯP7;`x H߰C]ap$2Ջ* :VJgT &)ܰ|*#b$y)B?-Q,2;%*TL@${ާ4e/f$~ZKf}/:y!B sc`Rr_zFFSd8Gɾ(rkG_N{&c8l,Pԓa&N6G{rPjk=H)mCo 6gq!{&g;)20@1h;ћsJ @$`p!k?p M9&CO ҄6jLwf,va$5)R^oHi>+?rp{xGq)M1%jƫF˙6fIP<3@{?20 cadXZ-˙ox.\3k@͌fd*avr<o+4Ϛ⠜rPUw\1=(wAu h#"X$FO}7KM0gAOU\o>,\%moTK7<|+RdY3Ma4˓-A;K[.J^fzQbBnV2Htrsx, לXړw V̾Ib(>+5 GK l\%<ժ.U 9½w(PcGa%j5n wM>HjR Kw -8dԾŘxձmfªMA&7oʯir":BThIp `\vzHnPUgOuQ6rDˆXH8!yY+ \  @]^+Mͦ5iry`넭?pq~ee@F"Y?Yxa5ϫUS߻DH Ij ]Q[)*yV G^ xc̭Zjêc>p;KV4\!65fhA<_F;HvQO6:BDl8Oɫvi>nU~Y"3/[%=(=[! _~dr9KbzMRuZKD[Utc9ە676j|8J7W?*%Nh.(hseEkz9k r 0舂ZNy9YQvKuuJ[7_Qe,_cd/| Ld&`H[82kb|AQar;3*@lBn % |:I?#uʬ=@ű_[ӑ?Ҽnz,ۨ&.@9 ]t_aX1&kMvo6`OWa ݟ8{U{c$B},2›/ˍۻSra*=Y8o_l$;a"k$>wkN0 6/H%9/{-AZV{Vu^Rc6Bʏ3G\f}%OplȲE) z#"n7݉ /l]K?aXe$j;8nj`u8nȳ0'_4Ӎ;NP^jq7\VǜMp @xxsMޣQO>~i f(a-&l$!nͶh\MIU+@v?fpx"Pp[ lajyQ.`k{~4?>Ui$Ё0?, @H"50Mp <._A,7 6Nglmδld d-۹߁6)u3{-!'ގ>s 5 DH|KMՈ2/2Q ݿq5+4ϛU ?/A} є ԐmLCXp''%awTsƬPvsHW_H¡LUTf#r͒ۈ-H jE,k<`y_-8+0T"cA" WEnY$r~ûaN1՗"|tc7-8 c%ZuzE NkX3{alchݽ1? wޟ:M{ӼQT{YtT &? 5+x-xq ڏ^ #Ih*<Л.$|#d4+J`\맒ڑ⇇l{?A!l: L0pԈ!MnjNPM"hcy%uaf nډpE|zֺ8|$AVLd8|vEG˪tvv{wLS_;C7]6]50EEԊi$v=ŜPLj82-]1[JJwlG ڳBx?~>q1 /z﮽ɑ׽O6H|.:n֌.z|cIU/}%7*,a7 Z".b QDx`OlԆ:s{nRI%ǰ@^JM0qhʣ5 3<焢U 6~]Eup+-(pxEN6F#mϡN9nʼg"2\ﭕ;5}|8UޮqձDC>OQ9I NZ2X`]fv l?vroTwi E0(ע.u8RC %x7leG{~g%6p )K][{ߤ[Or,>*uCޒF?7\!MaTcV+3eAߕ V>{G_SkCl3/gsJ9tw 3 [;CڹPAJ^kp@t6dZ%X1z(llZ5'[׃!]G3E+oFmd,}8 G{PHoKB Sk^>>}S?в~&fڜ/k#O"R›me*+xJPs*%:dFNI5P6v[Nb8qY֣]NτEke|N4o"x;=4} 8Jx ;v)}0$Jx3a3{-U$b#<{f q8^cw O{q9ЧzrSH73T 'z@S$c#JO$Fim4P;4ټڋ\.G>PRwQI (7ʫKa8%B jO}LD^=eO.^)ldRRbLa:W _ތDxvl˷6n ]>ywsV}Xӹμ%tA߹Cs]8qh˓ZuŠl-aX ?gtsc#Lh>7|;8Ȋ7k%C85&[9+$tS.a/Mck)1ud|pDLLk )e'!Pb>["up"j9di.vbrn7` -;I 3[fc7y` @\٣>)Ѻq!GvkVZާݓVH{NzL A,BF\dŦ]MKD:y? E! ~{_tsH,kM`1tcLgd*,j;*d l_Ncg-e #4] u#m+.RXLutRO{Uk^7%ŷy02C5Hmaca[H_}6mHoh_W}NXjEizp({+/+JXOW,)$I܆L/}YFcCQeHlݔp͞d{3m45{؊'ȽCtyJxh0mL'W#67f!e[ڎ3R@q<ÆEk=/(؄z"`" vD[>'gcXy^F nxRW @J}VLOwH{y5uޓ P] io٣AϏ Gjq[42C,1jXmɟ! b7sͥcb8Z͢[lg0Baɓ_=,<%-ǣ/ދݽƽ, En&->eyj|]R3ؾ9g-@ (TH{yJd?/;jv/Z>zCWg:OC8&:7YSE0еW; 9[f\G7խ{&pRyWF% ZoEZǗjyWqwv Ğf. g̙ڣ2SVBL嗱&%FKLlE!om?򔛷oW|1&FjGMۀeʲ Ҩ )=(5IּoI Xh] +g*1=w^Ty=_-Ճ"{)"  8Bmi^{@Pk+5RjT qH T3T%jo5 deUV. Xv{vں|: C5J/Ekc up0ڸȀ1D}ƛ84^)v*b_L9Uc7?ˋL.Nl\6aGTG!¢&.Wt(Zv9)1U>C)6MknžopR2fS2q,U-zSBĉC[m(/llAk39+ɐhBw)rV̸ncp%A>Yyƅ命ue0e kYl©<&>$G7q [l_L/Ă_3xoR% wѠ-gxp2)l3u]QFsh5|SZT}|=V{S-ۊ T9arnX8)`ʺ_P \.A(a~ś2jxLYSޗjGFS;kW:2;09jETyD[2v#Y+ip׀^{; 5MbnJv^wѳmƿҊ[ |P ^] B5ar8 P/Y& &c{yFFK.m$4=۫IQb%S̔67z x{sj؀P4Gdw j'dh+v*_;:AQ35є GaDLV< Տ2,4)02 *Rii C9$N9"bw6[VR1֋2(wI- JL")B&7o{Oy ^3qQDu95B(VNJI{T%]r>M7 햯n+ _?dՔU^/&v ˳26j4Y ZϲCwP&@g|? =S8t%@ i WU˱֫66ݑv ܶpC I_ y,¼re`ip_|]8EѝKzBB6o퀥bLi{wDʬHT'~lO$=7CfB)!B8Y?9G} a6ٴ% ڧ0siǴ E!>#?|y?%4ͣwrgt {8)o&࢙^;> .Hꑁ>d?NP8'v,j.D~t'V8ǭJ5xb㙒j&cMe#NvTowU[\JM)*9g~꣔_a"~~1[uX ar 9N"U~gcJ3L~k @¿^,#*%0N:jZLX?QW7/mTr*ّ#N('5|GB4ӿӘ+4z9%#]$ -R8G /&+4Z, !LA4R7bnqHM}V4 P?ć B^*';컁#똳&t ) V~4X{b*R`ڲ YDbr,3lbH:;f{;*Pp}ӈ qڄr9 ycqtZzaO29" -wȊGklWk/B/ׇ_DAcNJK1ps bmXbx/+aq!1^Ww K*5XYv{2תďl ۉ2 M\#4f 4iKRzei5k|!p*4, "{!=U8hmO-edhK{C9:d|'iJYAcVbVO.0 a_t-]ZoMb~eMC!!.hZ5Ct0ѽQkuuk1dubz]3'oU0ͶxH۝ĬU#.P7`&uUIHr=t-+et{KY}?Ai ֱk\\J)V-9Vў<YUgHI3A$[E"ńEKD s*]$qB Zp ٓ([@j, 4~ei;ۇzVqJnHW h7)S ih%_Dff05#'0oBvMN*I`7ImCp ~6s8}A_m5ka(nbWE,">u٧ ܂XФ:X 0g0Mz:`H;ӵ<Y1Zmi͵"Cr]m >; 6ɬBEH+n&g@k;&bXl%Q d^*,527ޓ*KKXjφcXOSցлA`&nm7\16PIuAq6>JE9-Tyڏr91ZΏ=w.e#mlްYXVB|$l31;E(D?kes\ 㓃5?Z{Ew-mX&L2p n uH6I7pH5CMF +%m'OĎX \1FܷM]좳-4c{Ӑ@dK {ltω{ W UϖrDLEXNnDڡ4ui(zy!7%5\ ;f|3y/yy" PSbOS:+KZiK㽌ؖZʵsi23/1& [l.2|; F7C@MCȼeWPlԜ/\!Dn<7hX1Gt,Lhrȝi& Wd@<e }^ <: x$K)Ժ)qozlm -s:3ڕa rvrP>]9 <)L!؝Fon8kh#&Js-Of51>V/ gjb{ ώ511į9w(SCU$.BtaST {p>ZgAjP?z{R;-&&)2?Ik6*@ Nngך~!Ps#H' kȝXn}nGGr/f8L+5q:H>UgzvOG݆H%Ga|پ ?״ ,{l3Z[@wef̘m 59SQ<2'[+$Fك<'ԗ<~/ =|; —ayBfM i X{^C-E]5|{ԭ/>;Hz[wE9JIdy(Gou{;J q:Z>Fh'l_?kjGKa(|WEBca ŜԸ(wB>VuudǘR_LQn?j8!^mԲSfΗ+oK1=t$"qSרA8٬nkv37;WxS>(f`߿1~=K4nXDY5eZ@qj13ʃ!~'+.1#L6Frqjt[k˳ScUHD3UW|ob; =ݠ PgcJقzJG>z{7E}{~#PmO)/tržx;ᤖ3BH)r-$/u1Fob`9v싀0ȠE϶5gC:e`9}˄rjg~a@>g|t3$h % /28`>8~~N64a$T h.?4:;^͋= (˪toVo[{l. H>؈3@Mh̯QInꢻ4Nc:p_mtY$6ֽW+/xK{5fVIX ;tCߞՍvY((..E>cx-dU"ZVSz<*{8y'~䗾=}rh2G&\aJӜj;ZEl.} =,D˘o+9Ino" WmZ( 4&u0ʽ[Gm3omXe^ rHu~H ֍7UӚLII!9*T'2jǙlM8k ֗8lZlGt}Ldځ4Ұٖ|7#u|xr{זVǣK8&]|JڊfL ֙}Sm)7Y8u~+UM3 ,룪m^ *a.4['`0C""r w:!e*]hzN JrE#Vbʵ'&%V͈reБ8jQ3C#!UC`hR6mkz2$p&#È:Փ${ Q6K%2SԣU/ F>_I)Gj 4z4y>umqUcbqOc0`6p%=Yva@]@\kH oeaAg,![i]X *8\\j}fvZَ/Vˉ]y4XN#UrZۄSb~UJ8PHd'E2jŗ[G._PV4CA(@W;;W𬐗6;gXېm66_I%Nܽ˚C_TlH#=fa>[Wz&x淊S7-=~ *ÁZ1֖\I 943׬-`C^jeփ9#b ;MЎc}+XƚK9ǰ |gW2 GCWN'+eiC=W 0*3''57WR盄GMN-PJ՗#U1n E =765A}FIG'Io}eU$ ^z>pU٦> Mp*R)T9lGڹbfUǪ`:*P-%*}c%+KpVT4xrDZN Isx͍:}لWp#f2EK]FC-5?U"Ծ5LIZ쮴A /90ǃeń,(͹o0/g7r7ŅnL.[n Qۣ/dbV.* Nib\GGh~{,ҧ7nY TRBЬdf9bFjJ^dܤ f06 '=}שP^id\? 7xHбZK6b|} v\N4LW-@6bn,%bt}r,6⽠0&K컣nuX v.󚔯>죅dQ9h֟ˉg3$TI~6;CD·( _4%#|"e TGEU ݿSNv_DaLKw)cNơdct#"ECMwl x'-B~>GV= ݄wUOWKkF3õfoO %:C ̹͟İMә? Kb50aYń_F5bCZP ztk.㋨%QA둹6l,~|D@9QOͻxcwgul$w2LaWlS~bJ7.)A|4a/3ڢaS!t|># X4OG?j UNZ䵓 +zt4|TbH/t:cq0PER7\̼:6BjxN0X{7s=]2BP?uV2 gRzŅZؔkSڥEKIm.:#:$$!3-2I_(#5r鮅]. e]lDa[lDoAqI,~q/9|]HQN3wvB*}0qNz`vC*0ktlQ@֛ң(&XY#E{-ta5iŵI>fyRGi=k)[ls妑N /u%|w9pF i bn"ijj 7hi5$"A8qG7 td:0fvaC'7EIO𚀼 šxcG QqGui|Ulz F&&s-^"]/N:bjV;S,TMpTB!ChX6Ҝ]pnF坄fIekG,cl Hpd'(Wz@NE<֢}Nsh88a7#  q{^@\5jm\%Ӄ9`u3jH$̧Fn̥X`$2Gsf9hs\`:/Lvdmg٤ wHyI_Q! ZvF0@}e  Y^n&p6XO]hi_b3z4O&fL w aܐ3wҺ4jH`r..cw}b2MZX@#)E$'^8盛$LZyGBӴWeRXJo%Jc .Dn@f/0~-p^Kբs\vcEe4KAA)>Ļ'C],@gIMvKKvV$B 7};1}=Ԟ|t2$YܲCR9媅a <9?ZLc:}-0>DJ\ʦwE"cEvP(V̯Ro!,i$-fbA%]o!TМg'!/Q:. n*Ljjcۙ"^Ba_Y#p(\ =\ةhLt8oNofnlo Z;Ȁhc-tq]`*4Ƭ( .!/?MnACRj.~}m5's۵2H=JB\[o ^Vk y*?Gw\J+FQ.>T\mGRDRGsA@hѷؐ^z5l|&TGm i @8n-f|}X`+Z HD݌2!Cd:1wc:DVg8^W&÷^tW_J)n%A*K$}jT@H>hNC;HG>Do# }>:}DD.猱2r2/Bi8s@v6خpN̪tW>ʑt;PHO3X[/O8I!KJJg\W編7Qgwp񝻷.2=}n3DX`<[Ks&H  9E7⧵!vLzU` LiLrT",Y.181wE1u/v=)rA=*-4϶n`Ehw'yv]#kG0^ǦK2,۹8dz'ۣzf245_{Hk̫`ۏkObW۹Sdmŕ,O7"tI)3TK4?TTTܯ۾IzdȲoQv(PЁi+MO`ٰhF EJ@FV v8<#-X|vSne*\ḧ́e%wTZ0I#%D!τq4{&L)iran\~sHTI߁L=_b"F* ꋎ"HܹPr]쒋R~%6R߂)XI=Y ?L*8Kk%0av^d٫)lPx+,߭h5`OTtC-Uۧ~ sP$4-ހ 1|ܘey/O#Ap x e7~!X'@~3< ARm 9'`E=Jjdf44z\ƦMrڧ7ueSUW éA5?ܭ0{GL'2+_&V%6ͽgL`+y2#Nqh/6_S*;wFE afIxx")j+ūȄ9M־2c7A[)L!S~X(l&Ʌ6t?鱴|3oU"wؚ#;"z }_Q !h=>{x8]6(*qV}rnRrI"d11Eka iU?zZ7?֥*`WԫE>*z',V?CW:`@0}Erm旾TC1|KJB[*foB@+&  A;^FG; f ŜYybGRVč6Y.;њ w]g9JQqP0Kq4Pt̮aso9H<~5.t}̗P-s*l8fjGC (X\ ) wꊁmkk>ku'~O3tF;w`˅Zpp{/eC};-rUiM]"*f ߑ)+Os¼5z@)8Jm 4yP-E/0T_n%Zed@I5iҵSMpyc-5lkA=Ҟ+H%3G 40LBQU5Xs 8nbe] FNleC7B QBzkFz^|Y\d/E?`MDX ҂+dfIP"؄1Ӡg]&fwPzwޑgo7qXmy6b.>мXhoH(2tai3x+2@gFTT],+L!Gr~혚;}Web!+ӰF^V`} o(N@`]0~FӐ7[8uYxto79^btx OO'cʾHV: B5nC9Zۛh< Ax f Uտ@M/wr7{h"oy@C2V%sl)* >{G!B AeJ$hЎ"ze"F>QȃḤWMd}0 Hڒ;-)o!|ѿeqNBtk4弃d,!3;=.G6OVF1֠cjD0" %[j&~@a]eb@؜&SJ5Tt8k2')R7$Hpg$8Z\+[HNp+**J\[)lЉ(ݤl/9`KOsP>a3?/8hoA\ip_#qkAvNc^bfN}Cke%x؜̎#dr)2aj32f̀'x5`y+ءh\Jݩ,5\6G4T?Y~tL6 ò:PA{v$>_ȳHܿo Z{1Rv~;<5,W+'UlG/n`lZHj bſW>淌j)RNS麡1`0fϷI'zfmÓE/ż tї53ke PB(ʝ4S&;F a].91TKݏs<(䬎/nC #3^7%Aa "pfN@?lU#M{ ҷd&eY8/QTVǴ-́gV!O*f!R Y|,LV;&';@4j)ϫ`~t9iѺIYNMUKՙߋ%sCv::781ܞ\mc2ўIva D;f& l1Nh+?%6٥b`z̭o;z!WD D5fwQ-JmP*K ÃIgʔ׶KGس;y4kE];܈=4Į)q^Ӆ&hf?*ܖof ?k` P|~B ̣\Ê2"⥡Ҭ^M<2-$uAkŦGڂvl)}J&b h.Krgk)GCCBgpR/->jvqojkbI)cK(L@3I4|X&W)yzR)&~Lj`_ڱ=릿QRU][?ڸ g[LPVP| ܋@涻ڜN_B(]enlkUd*c{ĐI-Rf]TGyXܖ}?SN9߲-;eOM4'3VQ 8< 1@.uM=ѠW>'k! h)]2`` -GǕדڭMΙRTAP7ߪO꺢!| nn;qB6l!&mU$F{bK3̤%PAgJvh z* vSp=R;"VkZaa#ц]>p2]'/!0obP-x]?Zi/51sLz\\ѓ$6s -:% vT &"֓DXЄ~._*(IHijXi`aPfwCN[u{@]Z:AlMj[]n|ҺPԫaV/;T{@}!4^Xi$0"vu`=$%ڙn%OMڼהj%M sWNzSJa_2ݱO]az|%)W>OrػXAhYʯYh0f7hUTDed]yp}bEH\-yJUsEs1x_9BǪN]Pյ|bl#*yi&ݥt"fZ0*]MyDza3C?1[{3݉a#e3 F)\"DHʎ.PB2WI 3K-ܡ;Ї IRhdoCPT}ִ;R [)7:w@y?#1؆Hӷhs_%iq_GU23C탛YRT" S2sJQdpU `X!r T @h6{~n}?Nmd_3Ž4|0d4wzci#nX/'9ײ4 3B\_xF BO>(t "/wCk1l6"LFjvF_狉7`Qc;Z'8 4l>:pùlMdj2r z^Y[φT8ww4l veFͣ˷(~>ˋ&K'^]w bu|0 ijh)jjjy}nj>[cyӲ oc`j ZHFAԬ5<=5LR0T×Rɭsf{]Ѭ[wNLw0QAԭfuA~H,<$u+:l䗷0g3/.@yٚMWt1wz|S\(O-osV:+޶+Tz2n:Z,_&^B܇Ke9 UyrG!9US5oo%2Ot*!k}%KcMf9  ^ϊ⏎y_\ UeKnl9iDy)h<=~6h-0ͧc4J,]p\R}2PHDD柤T[2,&G4'̀:&]EY%*8i$ p}Aᯜi>S2<3M2hӜb,%>GmU׌"܎ Aeޮ` ;_BEt5z,h*œƢý`}08ˋig61iPfdCW8ϰ((+F$xF^O.Z_Nm&B~n2,eؙʫ8'.=yJpNW%p<)h-wb3Y#TTx_'°<)E7&b2ϣǡ3\#0 =*8S>xʐ\ϴ]?c'MƚSU﷑%ۭu t+&䤲#xy'O! Ke|\ m v{OqՅn*RWc`//-)JI#ќk9ǻz> =iƁTH9Ƚᥒ4fJoh![[@$;ge`P[ytˆ7CH -؟N, +=s,| B97_*';%;e?ݞaX2ED/YwmFh.rI͑$rch*t0rPP8> )T%C3{Fa$M=/څyĜ3OYuw7 sN - ]NU i$€Znjkf%Lok+YSvi!%Is—kоx|np׃)RYBvKHO KcZt[k}YiR}>_U='g{:,JU&Vן| +[:?LrW%7_V#TM$|C+یoRV1VR6.\L̈́|W[^xSUd3eSӨ)jTrPXnEBIx_MhݙOV(ۭhU;l/m¯7r߻2O\ -7o%+~>Yζ}44"-‘dǜJD0ʘx`*"xN(e=K9n47o45eWaʟol+FER1Hȭ, #LR &qCzA'դZPbs3CyFv?[{seG_UP?wWBybimئ..HiεNl 1Q];56" SݘoZ.cH'nM(BI#S _TDߔ @l5iO&Dn_<٬Y! В*=p-olEB 08kx2mdsC&M04V[QU}N1&L?7ΕNX,iB )/boͪR.ΰ`ݣZ[mw`޿xݕp+5j0ub :9!(?v #+עEIDO$۞P,7qlϼ݅sC%  DYH=&}* r  0uLu['\zltiQQC\@JC;P [w!Y})AozoIu2&hdZ{*_^ߡ|CV`PRQƏ"kzdW0Gz3 )sUJ)*2,m cs*y+[Ű4JoB/8`A0݅tq3X"7eB1,ƈ;/h(CgϽ YpT9J3*Mô~6ncڄݯsnpy48xymQm*!1pfs4=rYb&}EQBB!Hޭ3_81n<ԓ r;gThr&ob h5^~.}V_/;~w <@a`w]br[5ӟ n1!T83Ї5Pkw*˦R$-YRa_J\(k=#ۧҵmcSm3#`V L&p=AXl>?r?RSR}-qڳ1\6(yDٱnu%91myw躘)+w=,oFb 8Aq3"0ˀ1wD#cRۄm*v40wd sqny]5㖜nnu-Wufb"Ue‡_j1sdg ʙZL ɝ "eaB0.&$(s%-ؕ8l? -S'("TuMHvYy*zMMcߖX7LRjLsa2](&S?iӨ>} yR&_ a@֣ݸ^ VƩJƕI"q7{:=v #.{2?6gt*YdK7$@8+i"nP!AA5RT0̽pnX|KˤX} u[aU[$i#u0ĄqؖP,556 l6}7o z_#aB aHR[)>LJ_ {*dG~TӜRT=L)JuQVIa_E.hI K-vmٍ.JAs#m8XC9a6ckSC\ !+b7&,) >bB++^3 +ֲhE=TD(v~*pOk"5]* VM=5 )Cǡ,j\D }TF(eFeG-,D_,%[\Q,N'e&8[EŁ>m;ӝ ؀qB".U@j`ݰ} ^G`40<7 xV֍ ILXR1 츟EY PFEEIJy%n8?mVL]*f{Niy*C\{D9D;L\ \4{Z  "N}}(7&|탣(0$ d6M&3ntэ] *rRd8- άua^|{I`fMt|)MWՙ$;Lߍ_ ij3+,@%堧eEdyٕ>>w`(tHpόy+P jgW`$o trఐuN| IEV5M,Xl-Q:qvQΚX]: PsM"גx*E#rV )gw#=%=H$y!qoH%}4\wE*׭ 3;|V >{ '6Ĺ&M̳T_8$HiR70!8d˽aMz;(]pƝս\i\o;#u- H LݓHCUT?v&OɹFu};2|o ޜ07CތpoC7F P2TS1e;&xN FL5x,YYvɭg46zC4YxP߈"ܢroo2O9ܹzWB J;%"xf~m(XOɦ"*WuY*(S$ܩ8jҎyJKs&-|.+/|utUBGr-ۈ|ظDO|0o&VGf @t.V"B?P%#phd˘KO~SZ$f4ѳ f板>/H+{Y/҅ghMX?xVLӄ4/L[x y=04 -(P4:C9 }-sQb5;j%3R&px*i9Aj Ym̓8kRph@1 3 k&uwU.7LSdCװݲ6ʬb6AqxJ)BDp*G4,ʍ.c(La6//!J~gL-|æq-υ-ћ49F0 AnKW Э oZi7m7K^yfK?g|+].#@PVmxB3Zv2S<=}(EQa$3[ 8Re…Yhsn|EeҜ}z?Wzެ,jnK5A#ʨ< : Iur.3.3Q{0VI'G=@1&IxN'b(gM;aR?GeUII} ]8VMboyBkؾ);bxOhOF~g㳯 7Qp=!md\7#m~R⠨Ɖ7AiBHa"E0\hN-ѫ/42 ][OZ[]d{bhHv OP8ȲuϝTA%Q|fl̅ kɔ!\׶'b)s`#LwM&o_}PMNQH7trPr4nYYfiK$q̵͞A/4̛]uĎ1}d^|U;٫_fȝ7ɇ=D?uģ"rzd k4;$С %R[;b?>aD9fO;FY]9۬% CߪeB$#^8%nٮk1pzq=՞b2OD1 -w[}hF a~2ni!ə,X-p1 7az)Jq.ܾ^F\m@\y+4l)Poq77,{_^$Ͳ90 _D0he϶QB;3gbF)s0e}E~`u>mAՇq6gyƴ.|nkr4"(idTmOd"SltKGrmu 4ldiPk. i4x~Y/amtyit#F|+n%'Zʮq}mn_I^dC(h(n΋:۶ڙ.2#&LeRQLUAhXٟ?,WlZAGAxǟPR(AEEbhҘ3vWj)VjVS,g6!u-lLrދZmj0 B4@)(O4Nc WzYy;$3YR㬗3l!=S…5I }tZΊ%`O)X䧈 8#=$&$.>Y«b_ m5k ;Mx48wn6̐^N5Ǿ̧<x#Ns]3!Yl䶁֕XJ*9Q ލ68p(I0w`kӧNnyC;MH6ѿ?v)}qI]? *Ez&C쾺sT D=kG}olyp~CH կ чy}Y:Ȟ!U V>؇pfmdt*j݄zvj'ˍcۨįPU͆) ,<%O_ #8<!\浻&JyҳEp{Yz>>dH@^ڦgyhH|X IUuB1 x-5_U8%4Az2PI N@ nV9V`c6H~[+0%/һäQb%e!ɰ>Lrnk] %1F@buy'vOnZ͵9)+l:vُ-NC$fE y)`Wyݩ+fzSdЎP5r'_Nd4KDFJ~$wΐKUm$`Ӝm#s,a7ZT݁K)H#axUGoqy+TMGh=K3?r".olfi~&^)\zd}.xj,Z&F\|ӳwH0I |.JDXbGDpB{uNťER+>yZjEZǍj&{Ya64ęo?!M`<x fK5xi7LX@Di* jrf K$)y{ ༱m~fN>?mжLkOq"[6 \i-o4fNJĉ9)` Ɏ!)=SQPkMh '=3h@M;ܖC$^kz2s3} f (y;HDuحV0Z yၦ_0悟տĝWj, u.Gj6*>?MߙX9UsuwID5(\E>iA B (zt3Ƞ1n#xrA\!646P y:{*+GF8T|չV T~ ʇW#wVHJAix vs?JJGIB }w;^WE9nP c#|͹^ V9}7DMeJ!;ŊIĞuAy^Ya5ftGk=[ރBe7 e7`ϳFҖ(n$ 1 Н G@%/o\*:)+0+hj茴  #u/xw]t&`W"BV{N@I:>,*zVrf?X[,GȘdBItgH F`/ymxϽc\>ԍO+e,ݓIWD2SPx/ @O O$sJQ# |7n8ݎ_|H\ݴva򧁗aX"e%K߽9Fy&D bbDFHW}Z l0](\6^1lUe8/#AH_[Xsn)Nj !|WiAd=WN#a74͓aoEZ+X+> 7 M ۷Z'`∢7ARj1 tH?': gKcxD6 jpIt8Eiꩉ{?'r.rX;o:o;BEּP"j/pv|/}jËjvj؞ei0ZMQSZTB,X>3|Xھ:c8Pv oJ#7iFU-Zl0{.ufDeqp`T>t)ᮌ]\֍-Fr#fBViqަer恝b2?jݪ 47g̟ |qzgI,JpXxFfIqt؃XJI`dcSG"U0\+r5C+γ^U]X | $6N!8y^^G }gy31B)4,aG2= UѫPA1NWA%<̲(^~-~jyTv:KAǮ=u9 8aC>|CIZbِq3P)xM !qBLlŬ0lt;voS=k>i}t`} VTGG7D͹Vwe`s}1%;4SeKѕ*C2buYTc:g`*\R"i3rH<>TT=f(SAƼ7-Y@b [ T}bM+%/|ھB> 'Yxn"fH"+Zq<u9/IQ$#DOãA5"WwN0喪\M_|@+9|DHQ[T %l"y<32͓1` 7m f UbOĩIQ~;."oЛ2Pt0VE1~4|D&iΎ*{H%/s({-B_ RH[߇'$FVj%Έ&`e_ӥ.X~]FKR\`a"봝&.frHw?sJ;!ξWZu814 ED~Ť4uQs H)vG#[y-űGwZkĉ+dDhg;K&L k**.:Q\Itz[DNϑ͔pSU M9ݼxAbJr / YOW ;*UxS^g{ ^IAX7Y@Eb-Zr93.!6.o.,4G\3^$[g(>+WthFXz 9L>b1ה^a?$DA`~%3; "ٱ!P#lv YKv 'V33K;3bVΛBin |PČd6{Y+XՔӊ*x!7.yvay߬ Xȹ1K{3+8d J ]OV+M2Z+( NPUl|=Z̅xDa#i椫_;?81.W#X?}ێ` *k'l\4D3*s'xHgY@UK=Y=\0Pj/qZ  2LI}9sr+Nb&F [ >$V\7>A1SDJVݝ>:N'jI`]y+h'oR 299}R;L Dl!E4oLF] X%{`Z/Aubw^ѵKXN-ȼY!<__|tD{ɨsy51#hHZm8#.9z eVBZ}uڹg.t} av$2fR&#cl _וfE /tqלQR=gp 3eci-zfژv{9#-l^5z#"z{(@F~6oئG~K hF c-XsSjri A Y1%'2nN$?ϟҞk.Qh~p 7↗^{KCd ֮kѭo8LowGTkkP=V2K1!k.ל{d#UE,X֙()b"K7Wv j }4l-Lm&<`;c"%YTџ#6Qvr:d1xUd-+2z 37Vk.fqYvU0Ɣ?#22z鐢<蔠OJyLg$ R:zsZ&7ӧ!qkmJ#W0wج e/o&l,'_vKz"G#] KE>x,s_kJ=-@7DeO!?;%e娸^/KC\uӵd< ($ů_J*BO_8%u=nA_,dGE.[ΜIGlpy1ΚS`OC ZQT5E> xğk9 Wd] A"gz4Ĭtrf7E/e_=|k=ʎ}O/?!OR+9ʌ;h1S-c+&o!I_3_TqJk \|ic]o1mܸZlbYI`?}f2餬Um#oǀkMFg5T[^f c<_blFx9.cx'.yJ "C'mݨg Wg/ҽztbZGkc[)_X=0Z5|vؗm/hC,ZDɈZB}#:@"93-/Y{]8F.|r MڶU_"%6P"t˛Te|(M ]},UORC_GZfB &DV e.h9p!Ek4S)  tr;!M*SEXHIg/??ƒ OR{4z] \5/>A.IǤ#yFX"7M>\= t )k/`\M9M#DE Z1vmB^g 3GVsh>Q*ҍh8^"n,j1`Mv'I)o _ԋ&*xt0rTF:rq;ײa Fs>vH<4.qf8i{|9eM`1fq+o 0=:O1gU(yftcvz#'7Uq"D5K?G\h58bj*IYx2rXI!)|>}+^M@-7tmڰ:4bs}4`EJٌ Ġx<3d||U7X[cD腖`v֋tcC[(;Mu0 iNaG3 PǴ`>$G`>-LB;bS^ m8DscŃ|Foqmeͧ7y8Ҧ[v7o{%f8wЩZLa/\)> @~& :viE# W{Sf}gܕ«K>.Ҍ<9`0n 5:T~.(; 6V^eYFY7IѨ;[xfI9> 1-N:b}fh\6ܲ7`{moQwr fe\Cgg޹%jdz\`zxDQ09ƵnOL^ASYP8).̡Qp$iRwRJq+()F!ôI@ptPQw-Er!_lF)7wp;N aĴK"קcμpߗ3cfNdhN 3 Ԧβe~06| hxR0Lyivnw 蘆:g>ԊQ#]R[7^V׳y_NLS͢?20v뻕tk,鸲)Z&] TrhƔ4Xmmj5^7"z δ?(Zނez-;x~"ܹ{E 8 H.s3SR˒-_'lU4+bAg0YW~!"^cqUJ@p= :65.ʃ{'0ٿx[@ "a}bs0%j24 նtL-m,&,T ɵѐd-.>LM>LCV*ѧ:ʚ{73{Zl3tn#K-ZbkaLSAݹ'pkyq_y,MBo߁v㊑Ey]r=)uMD_:u}MYDk0Knϒ'̼>=Q.8NvF L]Hq֞`_É\pz -ycESlY􋞹xu9Pؖ7 J*+.Fډ2D,|LWG`a)i={s @T\6Zi{Q ş&~ P em9a}z4B̷HK,~ҠfX}?&>mwT;T_VʱEٜb O6QOJ{L\Jhkil d䕭zڬ=iMNբ iMscfXY!? =[WB#5 S a~oe lD-P=ƚ0[,#b}FS7 EU;EWvi.0ݛI"Fz D1e`GvcI3"ԑ&D׶I~=4hٷgLrYpJDGHI,jaSmgGXv2nk82 hSF0пBޔ #Рk~JTsq4]"ALq<: D] wH.*\߶8зW.2 )<%ZjQ TjBqI,a>-!}ѤpbD$Slfy b#moS0.@qS}o3S HkHhN|~&opp<6V*oO?mc{%B'/ x/oDD|ZBX-AUi*;kfPCރShs=!i`?Xsw_ jYB]xRauaJY*ᔲWnM/z 6=U MpzI8c(P爗x4z~"IZq wM ГS\]m +)C]**[H+qRpv)Fi*TFVI)ripmڵ䂯a.%)1O&NŠ˳cL?t2:,8r"כIonH4Ʋ1C@Ӄ.^izyY'EcJ"_̓+ ƌ=ƵQC}g|NAO0GȐkm->|s+NU*O0Cysgh¬" Φ4:l l!_K6_ue*-є 47;U5t?m4}F1c3|FI(J;4#XKCBiiZrngCy'^{śgyd)FfY3_9% I O׌8A'2cjKn(X ҿұAIj,,!Ə4QՊ+T%;-L0} IA4] G<+w^ȨCtd`WJvz V sqPn+:)c|"B-M>pGتBc◀ng1Ny[TcsJF:8e T^~yW]DjoshaK])ho9?ݠղJ`ܴ=M@ S.&;<bL*w2$P&t=jZL]"y _J#WD 'pʆG_X8WD~vF~!,Kƺ*҅[7ΈTg#d&*}; ~6fDֶgj$삵'n]7ȉJ-'!qx7IzS~Ԋ'DČ4gRL{FEH%lԦTI=H ~ٞ0fBә5?eh.Wiեlme&)Xu<׎sRD+*9+h]|a Tw/hMENI&7Jq"⫗ZMx|9o@)2u;gB ̓`(&uPU lּKxpLxR{0<&T~FzCĔE K^m#J 2HazՔJ^{LN()1mxUmhŐC^ׅ= L4ONdvr{FtW3ziQ̛Q* 1K~IF"tAT 㚨]af6` ӔMuCd×2A]u ٽ(dHfr7<r h:O^B g\|+%O+ȅS<͔(rf 0a, ̍cW|99R3#Ν+*d&y&ܱ@7) &v3^Z֋G*[C@ zOq(V)I|UUc$JȷsSV | %r2WBl@i%giEs'r(幫e/5!s W78=~^`|oitW(;_l\`jR7bˤ&R;0 [!G_^*ay>e䚆돷 T&%_c}-cn?d=el^Hz8UxpL&wAoV RZ4[\Y\ {MzM"|sI_ƚ.Ȝ6`hRɰ,g,ߘ Z3\OQa7?IX5>._#׹o*٧"N+Yv#0KO|-˧ s/&?ܒ9CN) ݞ P-:/;gK0SWCo@ m)&ѾEP bOJ<%%[es_qK&N3by!|g- ͸N w?0&=jnfA+.81C~ ax%s.f1 ӌvug],?BwS'+A;DU:*wҪN)eje ՒE+P*)y_'\@q}lqA}Z6WKJAB9`G2Eu3Ӈ%~*8QO)i +e'vMߐZB3H!QHm=WŁ)tI KX{-n%B@W} J7! _AZ~aD r +{[vR$:U& $xieDx "-?uB}-i_LÊmqXAffoGKwG x ^M_ -ЅxĚIx~&"G/vX.\s`;]]LoYnu2 R;x҉l kf=-J;/tvnO' qˌ w kb<@-n+wXZ8_;Mj, ƌ2KliW:Lb <@Рc| =&lB]YuwłMhrW;*~XK933ɩt<>kƨAH~2G(h9[;W|r.6ޗd|C- ve0fU`.@z]ub҈!׳ңzW%1(@]X ;B"e2Uwb?=zdR_Ò}1C$T/.E6~ZqYEYNuI7ymsnMUnijIOcmP&WԾiM5<湍%!WoEɶL͕sa$)BƉ!`[O{X 8WST-7F$b@b1.}kÎ+(Ie{]KɐvrF*mhĚ'j K n&V581*ĐFvq}t-n}ݰc~X 0?vTkoHGccҏK~Sl146=SJJ*cM{h(gru?.]/ͥ'9;WF`  '$8U!7ٴ]ްn=ztW~>Jh =+th,i@,vmJ.׊Jg;iZ!3Sz>(ϼ˕"w:u7YWЖq\'wd͡Բ)ժ-Uo&{U[ZBO@u襇\h9.5 dv] !|an:` Kf6J7cpWͧNu^e խ=@\K\s^ ƴژ3bQ4Bz],A h)qIl~, eq>r Z'}DGvIT+@$CsTF+GxOtDgةwʣCw]N*^컏Aݠ~>z*/|A7T=(Fo 8a+6yY"!OWY'A=dNqBHӸ\:kRV<-#m)c2(ak2*lWVVsDoMPW`1 ;͋{M]=lODTob_?t2IfII)|?I5q/P_đgFq\Q"' @8#c)OamK :٭C_)=5DI{_w3W(4^*Ӫۘ@l& ⵸b{Y&VH eò {!Ț/;:hۀZ'uv )Ƴy4cQ"p"?!Uj11kAbj7Y#{SW#j Ӌa VfR ˋD~rDp- Gdl:s wƽ~JǨ(?nSXK.U6&%d "F@C߯ݳCj c>VXD-a.#-#᣾vN*hkB۲b$'ex ^8^K")ǹg\YAm:vr4b6_Mu-gEXy;s%='qs8quъG (.,zk4q#eƟZ3&(d@ GV}ŗv`trt2"QRKs I]b,+`HcH3/Y3ȽE4R#\3jrh _oϠ%o b/gTL_7WC7'0.>MÈKfD'.1/LjkGӻc4em|*i@n,CE9Qa-1A#V:#!;YX{4n+o^iXZf:HRkbpq"$vC~7 H-pb@,"n8cIEn" 9{ "3p0liR8iOo _,cK}; V2~Q5FgNqMh̞&1 Pepy*t7KդF+pYRWA~@;48=ᢴ61[N`?/עq,I-KwEh#F*%}dtZ}ykK@CbC$v &l%*AeE]¢2H'[-V lnsALf0vؽes>c3cl[ +e5'^H%ur;*VhfO G +~R XGkQ˯b(1lu eX͹MXt,˕hs-;S-C{mxSc(c 'lj^,h{>YE/" ~#—$? 7)Mh8,|53-19fC#;J:#wT8I~$E䣷QoS s6vWy}L䨋LDp?!߻PJZ%bT%Dz"pnId_??P8ԛ5"wwIԵzIUwŸl'BEE}㳶Vq]*_gAE E6kNm^0V^p?8zb-;fk*qq`VeML>K&"~( %Y&SZؽQ$4om'7 1I`8El|bQ"7EJ^*XIEW@_ ]tӧ3 Zsٻ'2.2 `GE*E+ށx1ݟ&>O;9z:$·#Ɖ]0F/mYƦ)Qw,F$tq|7Qn_ȬIOP-Fz@UK;l&pOEJo{ª>Ck7dDniy4fWN[aDvJ}$+OܡnYd{QmFUM2Nq}ZVNXT;B_u??BFu F9d(5/8 x;6 JFsst@GS5jP3qἺQsW1rA9U6~jh+X*~u;Xءx$d~W$"Ud::*nTQOvD$եjaN%>OƎ1Tqjl:$޹M/'л;wAϊP\r2ﯜ$jM/@֌t^sCxTz *e:Qqp_ѯaܐ!*n=puv ͉]8^E .HjiW"ˎwKBjd_`fq(,91B.C kgQ]')Fb‹-g {7 OEwDmIbxktcܐցC{}ZO6~z:uKPL G_?'S?>ӣ6F|F%gsB,2wOuQEo*o#rGA(`1uڟ,}&52G)-mIGxsf 雄l(%>TgLTֻ*>uI"u|iL)8ܗ8?o؍n=HUFAgZD ik*3`8٩m a嶵lk (n{#U";i@nP,}e:~%J[ c: 8(}.DNwi+}Yk% x1¸i@1¤V`\0ދd>Z-i= d;.AܩB#; 2#Mt\0SjG?aZ? ]v_k#)PMO{WOʬ%rX]5HZgŒy$7RP>Bwژt{AGPdd]w3^ Qa8@|H~Z@8c/U~r 7X Tw6P;@SUDv1ƶⰣ5n ˖1Gf7~Zom^2+_doP@wC 's u KXi6wl5q\kJNһ%Fzr~y=Tck UKhV Mu@e Mgx_!WF/SF(aHYDsO]ыFY63"J8*c3l{r \:%LNAluK#, ܠ A@Jg6'l- ֗[ZlOH7bF3<j%MF;YCp6__@O f߈ C"(ƚ&zioCG_aź& \lVP}w_ i&[Fq拠6UsC.W]0Q'OBH8B9LNkP6Z v2ȩRӤHHa‘&{V@G24lIBhUy㇛n%10iN:9}_Rb7dR;Md[aQHa#"C耂 H qH[o ':zbbs^UWeQ+]F`~ )S^B0 2t&bKy=N<_Uswta+W`GZ :<(ě@ּGhŞd*o(-/YUSɤUo3&\ jаqE\ɗμ"^t9A.N  L| )ĺ>pp6;۴.t} ҡd|^ozybR8?$(k%wׂS}̵"ۄQ3n7H+ON'FeCoy(ÍXSzur]`H pόc<ѡ])Q65u )=c&1[b= $,Ax9uyԣ.-`Y#Th|ng=!y@aضi}Qk9]p׉~b`l\Tt zѭGXءk%q4E+D &$s+b1:o ]םKe*H>_$˙ơۮr 1@1!* s-e`P_:>}'벿R~#ޤg sp5m,*}ӵ(VzW>,$~68]~lW&!Z{8vO Fϝ<}iy;6HUfo|u9RE҅0'M6@Mנr)j`2xkG7~BبҞkbAE5*ɴBRLaTcU;oiш6:O}s8O#Jʠæ*Yʦy k3Q(;apЦpz .2g}-ş\9eռ'))Uè [ @2W@.enD @$itI[9_`0℃lǃ%h7P{Z}8OҖ{urf5ڥ<'J&=RrvW0R"M-\v}1pƾ%JG^+2 |{"^%TS'N4.f5xץlm%ҭ+U@4ggc}ݴ,_h3raеn; 5I+^cd)#2ģ pl2 ݓPd0 WA?7v+]>!sKӸziC=7|JXIS'x4vRGHVT,p'4/I=9:MMO.en4'Vŝx2Sz 7Z5'?os*ɥ߾q[Z1 dhnS9~ۏZLӮSgў܎^:LqFpn5WuTqka9j/.s$f/#/,aAĸT6p7#=&7яx5rXJ!cs[bH J-]QJ[׆&MEQ~{@cSN^Z˙ޡf' IIE,6W&_5J<gҩ()$a)a_{A|4@/-,Nn&}O/*Y(C>aR9AʐA v~nVQ/&b{XiH K{^PYMGίrtnV$$l0 Xd+y?v CIA1`q-D".x\^8AvŀCWۅjsmz(+Z&NZұd8-O ݒnG;!xa?P߆XfݳgT(::)ȆѢK{ˌ+QJ ~=i*D jPnKbc`C#ND9b a"zXxPI/6qYUQr i._[Ll7)IWvo31h`ϋc 7 9'ݻ(mv\ #O\ 7@s0!(>jreKɳD$A1}V{ >Lkba4Z+ ]k'W +l w!_,FR,ʇ)RFkYsZ] txc<ץJqlU+O hREii!UbgM7e1ʦ€ tietWTe~ͿFÈ?юɲFIm{S߬KH=ql_^k $TXUT3hkx5"Iyq*V}!Nw޵ Q _`YeOylp|FIصL&".~ /:vo?-ȏ,S% 1yRoH@{?" R2AZ^eR|# 6 ]b8eу38ԟ[qrgQ`Pi 1ڷo(wtU[rD`g7I)Es A#^νǮ+>3h9پ( DG_HhM> 3K>"-vHs ) .CVau/P n{oJc8P>}׵\Ny8`FfI$JRZ"em-"-À99PaLct4MR3Jo0+?H+<lhקw.0Qpȴ%HM?,g&ֶ)`תїsn:PYo2g6H[ȳq>V ikLOWTTȺ6A?Z퍹8XpJ #nW4Y)n~+gECsVڎLՃoW0(tr\h0-WC4`C`5blsrȸ *W^uVܙx8Lڳ5c&n$hIF}cHh<pZ']<#s0pe2Sƍ욮~R.;( 5JN"F(E@ ?+)zyaJGO0}}SEi%!ٍT1PҺ1r\p%'Bo9B8"~9 CZJȿeb tX~4ڭ2n lCv&"9F"G4SFLs'URb 6w˄xퟓ|%6rZvf4p`Q>Vo] W0s…H! *_)9`q:M=|xh$${GBK%ӟzm c%- xV5/?6O!g_[/-@a&6 H uo[EI7I*p'57L]feb\ݞA(% O;0i]ٸ6>_`?$DXHKL+/Tә{2`i3[0Fۀغc[R(?6n)ü+ek{2"'tn@_D\&QT Xa| zޞֆŶ}lhN JJ;+F!8,=G %= :sfߖ8mQNև2-,U8 17S$oTQ~% ԏ jb2tscb ׉ӧ!Ug0ƚ6xi~"pu\MݮB&վn?MYG,iy_1M Uā<>lɹ±x˪NJQ 爵 gw%^/kyh3 Ki\"C4eMꀎBXre: G=WO\z@Wi8?(9qEf8eܞ[Dsn 9`߫5Y*.^+"_2SC "$>+X- i$M6|]@IXY|Eu#s8\6B][p$gS RpOì|JEkYrI<`\A/ i3 V\m䴻`DЊ1^(*.=\%G3?f;8XB^R4N}ڧI M-USFrkvtU^q|q%OCr'>K;mft]&՜\]`G st/cpTTQ7 8tMe֘T߿yl߆U1y@*)uQ.$-7)˹sfbZlAr5Yh]Mz8 jh9]{ю4;̧ "v'"/p~ZR,, QTn˹6d2n'l #&mKsw( ZBQJ>fc5CevkIJv4ڷE311SY b(Խϱ=;Kh-J֓XH>ZU ' Ӻ>mCp%#ؐSs5Zn $UQ "iI^FWKZ'TV_ELmy~dۋl׈LG! 5v5/|vC.$tS0ձgOkwoE V/%_>@Y+z|f8:΅r7:`t4|G SK ? _+t=pdeūhtAkxHhaċt"9j5) ŢtD@)M#8vN8Axz%.0WJ+* urӔ,`? ^>fd lq.^fo㵒 K8gʺLr;OSq bP$so^?0˰[_Y4Ic6&f݂\VI:unMQ2RQ])ХAJf ?E읓ݎn| VAi]F@Ԓz5QrrNb(ATTH?m&JyYu |`_q wEE_ÌlV߁B;CR=0 \ c$ ԐI\nˈ~Y/e/.f%Z^6u Kf[ծvnVχaW)Sl&47\N#O<মІ oGUŤVwD 2knr.&B{.7I<@$*a&vm?ud*%` F8u*.\=4f/ +3./} ѓG2XeD.Ba:DynxBuFBڜoS=fz2#XΖmrj鸺)Per,M|4z_@QdBz Ϭ9ؘ! vD=L!(Zݑ<16}.V83+U/i+E3b&(BSV4lGF w ͧ fY7;A@O/i Xu:vD8_A{AaRѽM( OC0=OT;ČA>pLG:=_ȈeAH "6XW7@Byֿcc !EOt{/CD{qVX5?b$lѾOmŤad@MN%,GòɄ7y[8# 9dh{G1?:v|)M~͘9d8T2znƧ'7~'85؍j+wgu;a6$ hQ(.=Da0p` >\\zCO?ʏkF{_V=Č5.UsrDS!҇2e~|@{7QQ)y70ck>w?l_撌tI u4 L/(}1(MeY./a GCITJL3%Լޜѿ5+淃iiZ YHFT*' Uh=hM3 ,"Xec|G0O7OY/A5>CLOh(Q'AFa&M# /~+.sز(ycrZ$Wu ouslC%?)^J֦*R:hgk~A^S¨'MT*S_^chQYp0ާeI2nܵ+.׳bh"`kvdX>A Q&:w1`{f4^a})fhA݀zr!KSBY{嶰a)۸<)8azn _KM)O{g Uh~s!\~O7dwCqصz.E#]NZK?4?Q )g|&a9ìČb_5Z5QXTh%'-2Mh1ӏf$HH$\4=gԐ )П:Ya$̂B'IBEyV 't6|5Q;ixn>\ib1""ܯaI?|6Vjү-ɈPl{_Az6D-Y1vȞ/Y:d``5rNOS+D_ĽkН_g)?jbVfMC ܲ}k1"SЈUcr]v>= . l/cvP`AҼ5.p`k%kdʧV9;2KDԻ,CD9N燊֘ϴ5',XT > 1C˥mHPviz~M PDZѰh_Ӂ]4>Ip{%^=qE3Fr?F|.OUz/;Acd:pޗ'L?ql<P0j:zPhq>Ve-=`ލ<nu#ZsnxIiuA#g#dӁLQ}aܹnx[1 q93 C8Դ _Ӳ@N|ƴԸ9͕川Ku=0bI,K` XTk78Kjd873ۏ/ B1,% Iaj/Rp0iC\ZǪ©RxhR`AOc6x a˜E^#3QM2JsÉ {7gȬ{5D㺅ﺴAoSgJ+dD*f3 D?g7z~t>_3f7-AJ2{o= w|ΜEq:e Z󒝉LAR^*q4 m& Ȓ=7Z%GMlIhUqycd8f/Pu JfWw^Ew r={yjoryFQ`Z_G!XLHtPŶˡ"w;ib!Ot&z9<%ڎEw"j%sfflYT*Ӡ SVM @`;:@D;}\9̈`A yzwL3uagZ@mM7L&Y,(Rxzھ|2&0JXtr1L[aLJ=,x2KC,jh#7ڔjVJj m$$z#kM][dn2u4 /0%$&5t"B~D2=TT0?(,{XMkA4qE@*Hx& NUh/134^tJd9W\:}߀Q>~׃5vBf;ή%i) "5#J9l8(𲃠HV+I:PC< | + 2v<VdA?l^7kUm#b%kOH`Ek^C7"#LW:F>kZaa>&17Z+ZG/)m%'KҒbd E\JtNzAfhlSbFH| [=8@4*dRk$+I5 j;_Wv"Iep:lAPL=̚N:n Ls(rYΊ(mmG͝9 wa5 о,!p;ۙ6Mwԃ Hm$Z4> &[א1/s_ۜ'KQX tGaW*FEFQOr&C3s?Wx$P wQz%oXŻ p@DZکr.m oxUA4I՞9Poa|I%Y V!c_r֥U w>L\Jv+dk?甋C1ѧs[J~+um v1½Wᘿ!Qcc1KItgbMW|!>>Q6 DlNReXE6)ш# }8N;0h6G*)rq*[ AAՊR^Xv91[8Ld_9,L!BB^c%u*k8ڱ֝zV&[esXhaxk7^ F7[dc$ *c-017wlq 5Vz9HGЏ.<ݷu3s:T>Gc1@[9頓OPF3(ٜãaDPcyWIih_fs՚CAHLω>IPWZ<:5;m%n8nFy6NDU N|l|Y> @vqOżtBL-R UJܦSSUF1c=|B;|Wh)$>%|4?TB+ ym1Pu~\|(9Êhq"t+c N`ښRh 9tGZ%'\G`jq\ 0l ].to`h]zX?t2-b}fpKSt$ߟ ,83EZ3 pӧ.`vjJB~1*~x0k;?v3!4RJ G/CCRR櫙-6ol2>cDL:h LPd 6ΐ?5Zv!0,B^ faԬv|$a 9XsD@HtYjqu;^x7%<ʌ 4}ʥ-dn h'*4Y.eFW?X<2iVh4'COsi.EU4dlC|jqD^k@]ٛ %bɓf]Ձj Wt=wH5|0ޜrTJZ'z;$zalZ B)4^ªL[l$O,+&9fԫQ*ZBXҬOIHɘ۫& X*V_uFo_pA vLbk3Bƞ1y*H >:uqFBLoꗉ GM waϕ]MQ͋R-Ӣ-xb w +Se*k"LD:~ Aw/ o)jfߖ*VJs;geMPwn9/ƱMlvE.⸍Vigq~K xCY.CqV HĦ>bQζUtap\"|WOOU ?iOY턭e\c?B@W>͘o"8ˎ"D Wh 5Itc)K*@; = FAUo& u$zbZ̚W{ӈ?XH"Ex"KiPF =ےB(~kvvaӠCǩ LLK \*6⥮Muֆ ԣ@z;.Ň:Sd6722'5(hdX*W`p?M;'>4EsZBfٱ9$nm- t]V 9~>u!~{ @ =N}! +A-W#0D/]X013]?1EMl,yLkP; FY߅3}#/z;RE,31O@ pF6育y~@"=8i+Cׅ&ym#SMiI!Yb>!Ͻ3QqZع>P=Fa hV7GPqxh02*Wj'+Pm2@"»B,{ $0LW'EYxjdwr U_sD?܏uPË7$\_FZnɲDp}f!2T'%uTi x0"wMva*_MZb.7.qD(ۨ}3qjX I:Y3A˸kP4Yu(aJ$%I?~1ϓG1貰F*ow3Mv4#CEE/n?jS!rZi\j\1F'_@L,_?Tsk୸Ah=.Q3I`{RL#I9 >l嶐Kf -S}m<=(f:IVvY򭃼u[> vz)|`CECdk(Ńwy7iY$L2i=o~-j~a ѶxSmxmO5(;reAasyTrcnD5;jtQrsۍbvn%$HD>%kC@ƒ.3TYN.{ǁCg X3x St}'&uڥo֣]jg\sζEffVL3۶8~̀r&B{_PRV̭ iEAkm !^uA$] G;[+?ÀGd9/= a%dm\(~?mKw< WP!Ds~E je}k$8H ,`R%O ~\d j>OJ1d)}@=`` յGC@*hd=gB+I`5=Z%Fi5H;t@`/6,4!0f>tk=cQ`MNΧ]Ðl hB?鰱[SN;*0WS"Șd*ϟVLDKU<_CI~H}50nS?N%%ZF[b Υg]bIÔڤ&FV-dB pXZ@n`[TExYXzݧ}Y8I(s xŧ @ؐ-LI-<_k9dcQڋmN$"J{EL=* ZDفT3E7.RLSe '=*ힾ}T )Ҏ+64=goߝ9~P%likvw>F Rv<`g ,"x[18S 9 IZG=dEf^ARo}4vTDz6Կ8_G+G-p{n8yv=2t#Lƥn=\t:y5NO/^VRH"tAjsjo긓)DX=sBI gNWf~_9̠._—.ifcRb*pFbM'ne dg` jf8l_!;ZY^^p܂ y^臜itvSni ]ۗi!uY>uSfކe4]hW}<%\f: !<Y{\->sǚ{#@0p xI~`ZǑ..1ֵ,m#ZL{)ah'+AZО1J؂٣eGHU]OZ>z-gg4i`3rwjn.(VNʻJ~,v#/pUnw`D#첰%5R u 2v]J=ةY 0tvmiy F}-]91u3z3E'CnB;>QO(㉊ٝ(gj_#꤁ܩPK7*&]{ ) Z^ƀQEj2|  uykp|\n, ЪruFY&'oYZ;rYs i? ",|)H=R.>j*eag`З#E@cW X1[-Nk}&o6X+؂710(o<ptrXx[VyNI6Qg*:uer4@XC;p+VנR1u]̸- Ѩ6Ipm!@T"{M6u3MHU[F`e)Mդ@5W0^r/Ufը fUǞk (0`MǑUcyր%M LRě}Xf=XY#"γЏ i*-)/-VauVi 94YeYܺۇ%x4ϋ02=T8C & b|nDsJ1VT?4CJڗ-QSge._GM%T%`^rD==Dpk_ i7_4P,+qI9TLL]75kK " DM"rr]AT6S CX̭tx|Vrz2 ۢbC"*}d@faRDS}P%w=5INx;&~=֍؂3?t!`y : 03d+RGj|VnTbEn3u~k~Dk,l`d9\nA XԞ^/RTkK^Vy}XlJa6SJ. Nޞrhz}(b_G0/͆dAW貽Ł8&<ÙbFZCz^ 6wUrW XVfv){W$ j?`|vf[2 Vf}h-ܷkȤgwx_~>ٱ'膅p~ר`vt+ZYJ*"<wy-Ӵi\fsZآR\m4!Tr !ۛϐ4~, YE01kh=D90Ma`Qa:WJ¿jVՈf8>>ğďx7`$OWtEĦ{z0O]HwEmnoPvx+U@cWٯ* y *mᳱܚt'&} 5QJKM2fXʱ!#ua/[Ƽ(m'*Ғ3d ei(4='cBFRƻ_#=8hw4M4|I ?dHP{! AvDݯ}U" șR 2>0;NV]S\KlYw}Q6SSfl@LfhO dFoR[/SB)~f8Y2[R)Z%ݕ-6bf2s 9h:ݙQ6zy #B*딚o$,86yϝFPiA-tKvTȭㆧy`` =3yc\j쑄}Rb\g@s0HNzCIJMX63T2PӗR 9dj59VǠ{HIFyU@GrpvxIJ ՗HaznבE.[/뜃(MR7 -N>8'v|!K0ևq[NΉ @bՆ7Inu?_<0IInD *9`ՠKZz6fcxqnZ 3u?8Hh&FV}NNh@T(SS&˧0])t}Ǒ\Fk*b^n9z̓EjQWݖrly帲,+̑7Lv@lDbFbݗ뷖{}A__6U,ϖJ|$f̢1eQrKԉ'ܤ#'^kj_3!.8|J>RչqFtLp5U:e9{½eWJڔ@LB 6ߧ~fV#.%*dzI Gtmv`PRFP&9f̢6vm-rQ"V6 Ƚ'bqSϥĪ)0Kl+--%(C}zoFO/n< ttCҾzثTE;=s#ACDfA rM'lO\(K E>(RqYǒc4B^]+$,`/ (5hksR?ݩd{UMBM_kZ~R9\k9ɒ=QRmW 9JaǬ{9ZB6cֆU6mCBʜ[{pFl=xK^{!EJ7&rRsUhQ:o& XTnLjIsYӟa\c_/s:~ *4r7l۪n ǖX -up2W r1=[05Da_ ~X_i%an2ȆȲS烎 !ŝU Iq *:e8&-ZK1Zi;㩨+Lvynkɼx^ufly\ڍ3UHa +#چL 46OY^hv jL [>w=g84}mZFE =2vDD9@El^o"IQfU3 CeaAö/4 #e4 <2w}œ6^$~9ʞ2)ƹR!- bz&Dnn::YJb@vI]}ߟy*nK u3n%u_OM̊"e HNkjnR]@tD'ƾw/we%:4dծ/BPttVYćK<.#0sl _3 %gfXbj-KhB* %,h}7+Wa("d~1:c,rOs5|cgՓ̈˿'ž!Wt L G S1Yqjw>yV4VtvPHT mQsߏ8:Ԉ]wZ; piӃ k=&uWHxl}3EvGxJsIIY.Mr3N0\}h8͘m+]oE%⁥&`\B!8\$3jJU4!Uρɮ\ R,h4-&23@ WHǶPo #FT?p; npQe7o6-ɱԠrgn9aR]5_\≟St¹]ɔ0DCe|Vqb+2o@K >2NN@?ω>BTdHSTʈxӹ%~v6ɶbI支+4Y\`jrꗜILtFT+Q󬣤/}Np|23\I6Y4w0 %]lɈ{g4}Yz-V#ڢCzFgJSMDyܛ| aPlA,7O,Ȑ7񀏒c_l7뀶Q a#((Q\2/Ȃoc~e|av ?!N7#JL{G6uȉHRG% QRNuxAlfTЈ8wyy4TԢPI) 45q^C(caw)7JdB-ʡU.@Duz5ëdd.$in)TҒڈq Co?^a4Ria+l<ձGEytN ! k}PNdڧ*i?h_/$IZ[ pHo%cI7"'Ndzi8CYu6'}ݱbit)Gi,!['TE|9K%7eX%OS3Ngd&^.hgD`F'wtxlUc'C6X*7(3'ԩׄ&xkg[I~]Q#t]tei6eֺ^QN4Ah)J('K>DKG~O|lWۇ%켄>u\/Ó+ VکR1%uqqmn?KiM@ W`Z 6z3R6vGffIX_>οLg2'BK$E?:b4W\fNٹ^wu >amzw'FnN e{~4U~ǮZ|Qɹ. 8`8+CG鉉Bݰ+:ԆhzyO2~hJխ30 O^WV+Mk` 5/c8`&rB)#/uwzUP*GePO>E;ZVv/Ѷ )OBӓltwBbW-Tfk~dD !UY1P1]G%]n z/4:[D?v`iM0kV4D\n8`bVDZ­r<خSu&)A,,*2I!@yh]̎$zz/s 3i n'"S 8 AZxSn|iC:߻9D)1"ۑa5:) QAK[ʧ.~Y?8~2 .hn$`շKc`NI,J2''gfdVXU--X >C l࿸Fîe)avh .wg xed^ 'KJ=;Է XqTR܁R1:Wz6S ɘ^E9Y*_6 Ң]HdNE>{`vMy Du,폻J5M&+#2R C !3ka+j &n{/U.X:?;l x^/ *T"'tnbN]/!CkW[Z[`0I<:/+`?EqCtxm҅]eUdzT';5y ,EMw[+͛aH&1}]H1wah5/>K"Өj| e|bs/30f܁B1eM<;_#\$y΋`0iP ,6̈́ms4{iRcOEfaf#cO#^لڊe+ +*Tm]=ׂ %<W h3' B*.(rM7潠}R*pWv-Ȼ8KƼ4/RM =ㄾU?7s%'U<)3r>j I>C$i$wQ >Ηzzs 3l , BAq .$e?m/m>\#A uo[A..;=@円J'kmqx{@ Yi> 2 &P.w4Ԉ $nSV(WJTMB ve[wC‘u.⴦7a.N7x*F1a.m@dUs] 1ѬKF?#211$/sE$-Ҧ;}qQ- ds3[`D!޺-,SU ]6avۇ 8f3+4ZA* Ƒm܄=_6#:)gO%/f T`w,*;rmqnni'}%d6`aBEN6QTQYSuO'o k8*m{T{ʢrJn3ުOˀgc75p^_-޶2vP' Nph}]~d7|oy{I 2vxU~ }5,pc_Jɒُ83ҷֳ)[КZlɕY9VD`x>atd-N@6w]u5fkRG7GPuCoIj0^v۶ JI,3#s.y=&GW,;QBBl!~K(!UaƋ}Zc_1 @*54D67Ũ9,.hz\_H$fd&\,_:GGaЀлCr*&7/qR錟O`4Z`r[~g:OQ`R?Ԃe6Ju|F? P:SRGMJ{靊HuZow-.'IW7~fsܕ^W!BʬτωSǂH`rsռl'y^iqb ^P{1שa;uƔT2vϐSnKR֢+m\?oqo:iMjaCef^orA]Ag9R):[rqXF'?Ѻcy,4]vl@C+ì9 ZI} #(Ecct$6HBL !/.~.xod.}u@ޭ 2z.*Wj&˿ЫW!?2F#hySP磖&؀<v5yhL{9h&'1}'iHQ_a}~XN?*%϶x oC_H2+YFZw\r,qp /pfP5x _/d""{=D!- 11De?UjЀS ݓ3l1}JYhYmN@r<]%w\*MUEg^.E Ã=2aMAԂ.'ߝwbnZ#{IU*5A#}Yi㣙C43w$fR7k5t[}A\(eD ~[fY5E"Z&8ƃ 0x&AP(N(R렃ᛤ%;&i&2iwi!4Y\|IFa{:˭i"{uSHorDyKI4j2؀fc Tl#qAr ZD@JF~3uwPfzbmH19AO&Ťr뿕FRN/Odl>'kQSq3`s{'ujD׫|xOZxB?Em5DhHj9Rt pIVbR(+fQ4i2ßXD4{,ORyp֊=,t'¾V&w9@U!l`t =s~~ GWl +K}sY JY3wm*`pjfԖQ&ZM1'>?0#:IeKmjFELԛ[xӯ4=7Zx)x n7/yc3dU= e:HLt}3${BF2%ALKomѴx{Ns4tt7Dc5L R%cassdyu+]枹`"ms@U[3Ho&kFGx 絍A@rPi(ή?3vXY;wٺj1Y[︕>k'9G!RBѷR^\g8DVWQʰz9>k%oD7/j엒3& hbs1aלna(;Iﮭ>H?*a&O[ȞBCѭ6!ʯ=V*zCHSR:‹0T6#9oHZV0T$850(\+zljq1y]uLy(Atq, 4`vITLv39}APd|kUI(nF6t+UZA.,lW6C`} Z²9;&|M#kfō_v9 Lӭ0:7j ԄUU;N4OH"#Z$2>ξM#s1<&D_buM-r#۪@AJB2(+ϬE.\;qД3 ks ^ynz'EL9DdϚx=;q)XAfmN GWp2QvCd 6+}ۻ\cg/T7mԙn5DBVgMq0'J M:EsU[։#0퉨(䡙5*Y)H^rcvcS{`~nU-L ͑D~53x'i`XؕRx"2bTLIa~ShddgFÕghFص$f^&tSiC;5o"Q02?[r9N:p ~Q ,_^WYe%x@nt,oT Lj]߉O9N/%R/ cH J9COIIVsn S M^Ɂ9nMZ FΣWKߒZ`ԯg%VvG.Z 0{S8np"_&鷲ܵٚ>r)#g 2!A&˜X]wag˙WI{x4HA `fՀ6)9Oq}b F 1ڥJ27qS5 ȅp5f}TX$r=V V{E\'+XuDfLfM~b,:ujC8vҝJl{1Iqz}XH\uGCm[wkT^>g5Zq#1fhO(]r򳆲TX?!ZSr 35;E~4NttE*cVĈ:F|25ګC^fmie,lpfz]^ޘPu3<_s5iO)ە§Q mםtn'.+r>p:8!S§aKs덓37^W+'*I ($Tq=5h8xG5.D㕥LJUFr6a'V*O ^6&Hj*\?,č DrrF)!Xn'T N*6Hy>WOI!r27P? $P*?m"|ۥ&{DG~r X69% V[xPd}QƭB'tOdU$.]0Y14NGC5zvzjTNÕb5 (ŇGȤiz*6b%zۉsO2NΪ`m 3ֈ"IFמ?,QF4ۼN)qu6D&*x ĉt{@^O&k¨4SKj ʵ""^ UόnߥO/TB:  8QM-l_= ^\șpT<HKۡ.u%Z$\('rt4*5猎zTSu+?OhSxO/#+xo\F 2t܁-zChhx~ɀDs!~а%.HHɮ!E($)x1h _l"^^]}WocϨ3YĆubdij0ς>91]TAl5>AzuӜ,fǟ*cοwdNNV,xXv+SA6VQnhIZ )[}Tb Zqh4SdX g^WB_𚽻qPM'*6xqXq`F;Ph'. º0n !LZ6 )dtjڽ$P'ƕq9]H{i *ͷ4 Gd-2l-h>Xq_+|E-Oh"JN+cw5ypi_,W ?2=k|~@4cQ:`,~7O :hD7bHȹ>^G'i,6ԯ/-J"pfsRZJFJj "ҋ~!z&eou:D/Wc.Qw9⃶xBH'SCN$m38auB1Q,]yyi%Ei QA!ChfJ.Իop2xQZHԿrrw} $u#AG԰0 II K!32x˽4)^zVj7P{!we[ءU SyTV%\P.Tc?ي!H]WnŠO?7 2{bz"Ii:Ο$0wZ=diݠ)eF C.AoU=z3ȳEۊl~ :ޗx צ%5.^Z&K=(no!ȣ>,n, 9G< d#ݱT[dPd#>g(e}[zBtՊY5ϧhwB%$)ʂ#R_ϑS{qb(g)S}h:P[mX ijn勱'"36C$啜`qNA2MHȈPӈi! HJ@keHАbQIE'\ڗS#|+Y:0o@~ rX QxuMj̭QkЃdc|q(3/7iA<8 -$r> |McGVz<)yFL |`xj+zRs@z[ΦJ=*|uvRKD/d4->7r=-r`]|Sdʔ gᰁIg } %ɓ Hz,Kෛ?kNHOa~ \v -syNn]><|V^ڱBh8x/ݐ$H 4:D{:$B3k],bwpcR7)áBĮJdwuJd2BG[ݩpWPDtO$Qdnb,?FWc7`8$X1{rǢDt!Ǚa!_[Mpk$wB$"@(H.e_<;J#DʒbP y#{Cz0 U{(U!A]MzC1wH*ry0J\ ~h~ҤC@z:@ mek.ڤr\pG+>#O16bC!)&46CG@jҟWxLQiԤ,]:'xJiچ?R"1+*L8%UN0Ter_:q>F"R/Sze8u,<n+sIf? }Z:pd *"Z (O#I- {dA]}Gбd7T[p+w2Kh|LʘGT@V[Lo*ӱxHY쮱ZO/*רR2;43ws8$JGŤ2خϾ0j xsA7dߒ!<*W߀:`.o-b +:TFe[O_D30dtM=}@an=٦CIaXt]v}F}#}UAǓiuxk<[Jb0iXT/F-8JibBUʚ5akvfϖ(Ibaf9ɶ5Is{v5 MaFyi |l>IJ5狚H1`(ND mbyiMGJM(Ǟ_m(&C2i~cȱR tB. _ vZ]=Hljt*BJ3y|ж2HrܺQܨeoم`ML  CR mI -ɠao ˨e 0jKIqBx\go4.U IᥝETbwwziݤΙO:~q^2|xܒ-q/_Hs@!TWjsIߏ^ﵗ49C%G[T{1:p^܉KYjj5-\qgFN%לߐc7&fablx)KuKáFݽ%V#> z?!C׈3LVr#J*CR閏\0'@?WD!^"l!d*m\HG{,WQW>~HY_j~2ZE{ÜԎ'A1 Q )Xh9Ͽ1eM-LKm:|7*k͑jk9sdAϚ 7*6Ȋ ܏.Vs-GĠ{]NĉpM"Ѳ/2bo39ǥLtYnGWpir&ڥ߇#b.腙*.9}z1:iHX^|cH7%n OXҚcG^F$|C%z=1L]u(B)F"X7]4ڎhN_ֿFn1H)̅[lJN +^ktFh7Rsh@ʌ!~ͽGj2┦֥(I(Ua5X; SyĚ CI_$'¤KLe(ʧ/[ jQQ: LrɁOɾHK !Z)xGV>ypUg1b65l48HTՂP#:.إNd/a[9W/m {:jnPܵؽkӜw-t I뫔oqE ʀ{ -Zk| hۉt&eT %k^;Vx2tb?3fi1U{l(s7~,#s$x3Uggѭ诤~$ e^&b3(D^+fR&xp@,xU>~gqsB!}Kg;~gk}ݢ-tNEq^uTQWRXPZXܺ B`TuW\&1XxKVc4,7os~HSKi{3/V~g3?4ie#g bD1ۧɛѴ ]|H\.4.MkaZc%>V4;^beP|=3;UcZ3w'$#C|6Rxpꊱb8v&c[p=`sPi(gP#y? U"poө/hb9"iù_4 . W.t8ѿ-q1* a#T$S#{}6;3UOM 51%gG=AGx])&rȹD"BbAzjڞT*_S.7r5 xY퓊|WDr `$%xI46H,|`WˮUOrgA1%wS&ӪK pfA \zwHG RZV}|k1Jf厤GRA \sAIGZ z >&!U\I\m\pwAfmqwhG鸍d]dtQKӈ|d_:Y!ށd %U}Wd=;jV2 0 [M]#\z#嵴x.>cT  CQ>pC\a`6nX8GīA&Xz_3p8ѯgeo)Q!Iox@ h~W.i]] D=_]TDpY1qi(مQx55--ئ:>;s ^,#exRm6'ri[gEenvej• mb]TlUԜjw6p%QUNsy [%n/tkfK bO+dIzxuq&! 9РSe :wFk^K]ikE /*_:>{9:(m҅ Kl_$cw9 e{i[ 8~nQQeIP8+!˶kVD*t&ZҨ U/)R$hfcl'sv<GѹŞvIB~"qPW6o, dy<ԇXQPʌ.A¾Ǽ9d\-m׆@(H{yFC hl_-W;hnz<):mc;`ͥ\HO-2^ kGpμ"֊Iqa|~|+\SL& RըjqђrʆLId7yㅗ4;jI,W@= lj v7'SČ ,]ZI)xn ͑hAUub |QZ&!}(>HMN}L )jUaoq `G(;DĹ'{]<&arvnq:ů@IXepR@FMK!5S)%mR/7Yuz}zq>X𢫆n g9Dׯ<{h㦍%To[.6uaԨjpAژk|C,]piKg#a('#=GV%*{Z*$ɛ=uT.S5qH]>^DecJYĨř+2-t[WT!=h8TċO7OG@=YK8p]\YImrۘ.W9[pB7mgX1lY Mz@A!Ln].DYWb'k=ΉR-ĺcnQ + 40pl>E~m.؅`?])-HAAP2˗VM=Cj/5&1t=<f4K]>v~^LEn I.0xivD%lP %eIT&Fu-&q |k t Oau(*:r#:`^؊#kA f.g\~Ԝq4l| y?G8l9>4P"LaBOM;?Cʈ'/=vHɮ,[dle1ڠ2+DŘ<+;Vj!G*#Kpjr2ȘCC`VZ_dPBqx-:-nPpCtFOaucOQtj9V"c/ۈ<+$؉8Ma؉G}$8AfPdƙ~ r'\uZOp5XQ=ߛ4,1;j0dd?,N]4Y Cz UrTjZnE/b–o"ٷN5iEߴ>m# 8,7JP8$Cbi3F-ٴ0@u=ZxX-mȸ3V2l^ r+p^H1]Ϥ*ya=]dHe.^l5!hRAR>]ahs e;I-/Q.3}A'0I%ZJCݠ5X6^*Gez%-Cfb#"7q_PPG ^~_( {QC<Ӣ'z`y<?5bJRYSe u ڪXmTE~規.K+W'z8tmbSqӢ~&Œ3DyFKߧQ{F:rF Yԩ l /cOhprNB://Z@ݚK7iA#.[GQ]$9 b0y  p+ŶnWr֫)eayu-isCYG߱ [5󩵏T<_ZZZ8Zn2oĤF3WN{a.[CFyupuSy.]}Dxӯ'[ۯ폚ݓ~!l$A'}OMƅo+;|z)IW'.By8GN>wE-kJlMY'"HkDĮܝU!IRߜo}+Pi-9"x#f oC?(SQU,aZWn[=?'%GY*2w*Wim87^HGaryShGpCSIQ-$z6RYvcpC[}ePfO],T'Eۺ)|4L3~&O!m$0Gn0ׅ[H?m`FɈebpuø,Hݶ㏑YּA+g0!ΡAlAN-`LE|54\)٭0 P #וx; I6{PE`DsնIs.G^(A`^6f,$Dz'vvBJD3v$מ#yhEZFNuӼQ}±(2 xaߘ<{\vm}<>Z`b \Y7=E 7끎$N̦'~[3*dY=86Z`Z Ua#QU?VKp"bG+QQvwz(ocՒդ2e|>c[oQqhF 3m7Bvr~,[(˙?ޣ"}(SZ_`!Q$*,pЂQL >Ȭ/ P?f%l⣎h}z-Fem ol-Q7f.ߨ8o$4.`Z~]z8q$iř}cH ^vq U|3bby&L2%Y?j RrG({ y4_0 WMO"I)h;lNH$5kl6WX\D__AP"v\)g09}!)_D ?LBaiTl2H{x)?X\+2| ߩ%,1x+0z,]o|W,eGkDx.d73^o:nV=Cl6̲}*f1w.xVN2zm`~I2r Am;ydl:*Qdd|~}ƀlyʐikYfvRXn'=<_$B ϒ.έXFQ1Y H_?3Bql+o׮~sa.2s>N}orn›ȵ|0 Mvxa*Ɇ/4vȃo!5Xi:Y2HHʷXQuHn#2lMu>@)C=^[~uZc(@=>ow"ui%,"ϧ*0Yтrl}UnnzƇ&+Dlphi%WS_pf*;+-K NbD^<ִTg[+es4ދ' 㹑> CdQy ¶\4{}V`au~0@pb:mp]Rqr~?ѓF~!߯B̾vg+Dϟي0 ZI8;diNm%g6 ώAp(z+fU"fr@?h`ъlJ 6@iBdkS ^',jJ._ U~@s c9߻҉~g#'Z\8ljcglxBڃƀ|kyu;.<1-syNt1q~ȊKA G8WT]X_w|^?,O)^@Fls 5bŀGOn4&<),zq|3^=n`#sw Pfߛz˾QU;M=:㖵}zWbՒZOE!)PvVN" u{f??Lza$ѫ>)iONcQÉ@ zoX/`F؈|9 U0';edԵ(\ĉ)ztg8ٻѧr$X5^Ȥ/[쫢WwA(~DCL:l߱غ.bA$#M2D4=Pe9 }Jp6Qh>yi4Ӆ.(}\/WHggc||kvP r:ߩ^؉eShc+jy+חX-K"U\LmZCHa-Pf?Z #$W H}՜1pb 0ÛNXg#}~ t=Pu[o̡sUh[$4?QY ;1(|SEyCov^<$2OoOЏ+yͫsLOJ"UJO g?!e@l\y['_J4~fh~9/$w>gy焼7oל qۢUӑ4e86 x"iax ty31*7ri6y_Pdu%gdS U><9'E~/ W3 k؟l85UM\o|-%Ux}p'R@&P8ZL'9á2 a*bef'gHжDJ8>4jȕTTx)K+gxH?8+TD~$AYAo^RR ȉ~ ]0?1O@uE4W2 p%yc!7> s6v1ԠԠ(ha2g+v<_' c+y+Gk½Nܶ}7TN@_oi0 B?X06lqBcWL:exޕDZso'ʖ` uAhV"*L%;F"Q"#v8`.%iq⑌Q88QHY,!\tbzKXzsгhMj\ 41 u$ G|M\ڔp"wսi}'=D*kfx ޕm+ǦЄ2O HD 37"Ma"d ԦWV-|+A{y-gā7v׼f]aAp\bJ!Nj&!aG+mo{ɃqͧKXcƟ5=Ǎ#iX.qj6xU2A2ƗTdXGRSzq1cǬ* FI!EÑ6`\j Qlu1zԂwOOR8u hnDو:zٿ 0qs,U5D8?*z*#ˍ ]k…amUO"֞dAKץюz ~.wHPإx+n`D{e5go {@Pdbu䘨Ţ6 D,?`NZ o@zls1թUϮ$9)Xv.9|9dw& kmpV!{W5:o')y8Ͷ/!I 7umN(bG(!҉1~RB~+@Ժ<5e,%z V:7|7 e +p)6td2i~EQ1hPݍPדzL|QT 'YfW\+3 0Xs3#w[R-N>oP 1}fsթs=ϴ/qkyVr.V^. 0҉cغГT,KC am‘ rX*s;z(.A/I)}L'Km9=RK ی`V2mm0;Ot_- q*\?p!'&47j]Rgms&x/ Eİ6`u|&NKWM,af_ìPf3d2SUv唋+0jCrdl`y,*qh؏Qe0!h+k x2J{6ܤ~gJqio) *e*f9c[֥iHU֚$\K.Vz}8 [ :N#=!bk `_#+-r r5a9`yKD\)lL985m44_*gn$Ұp5aC6TaFRyjĤg0NWV.[Z*.GdX# ./I:~鴛; 96^"A2ȰVEp|=cӖ['܄;ǹQ+^wҏ}I$ܸ) (0@!P0P)&ӹ7M<Ŕ#q@ '(["#(b-nMjl6riS }C}s᣺)cJ=I j#,]B}R"|3%#xZe{ecl0$\UfyKm$ k5=ѹ7kȅWkOGp'3`І{?sfkud9d93 W\ kf܉?X"6F],7+LWva4sYvt(%Ue5>K-*<>YG[zhwǨ|s/PE4z6% t2mF$Z@bQȾ 6el+H%|UYˎ+<`=?b3wrόH(g,Au8ZT>/A: Kd&f&;(*a:# #V'"9_] lҘfer:cnul4mc@5_*uq OOxbwrI]C棉lI:ЎӨpGЖN U|]@ :+jIY1|)>h4\pP>ݔ ™\i.GaKe!SfgÅ4NNU}s!Pe*`M$937fk5*ziUI8eJZac:L˟PPdJ>0"ȫ$ ޑfZ+;_Z+]1*kLv]ybTkj"`nNBn0pe꯻b-X)~dьHtm^z U=ulf𠕭eAw h6%DW\ d] |{d,0(֖Ƃn֞S Х7b&ӨM)[ܿ帄S_BW:J膂cnR;D`ڿdM<*]! .0w dXvD3zո_sQu:1>X6 1~z:%3R3ڝs9H-\R 7kߒɂ}|b2N/]r+)& Gڀ;ڟ$5q25HV`nA 5VƦƹFyqϏQ6;Fm;f@ | 淫81_Դqc]ݨ`Ǔ{18`$aӆ:i(ݖ aTvb $Y!s1aT[VOgoV 1L%CG'ƖVpv, aZ pLKǜqu +;#y}<\Y7>p3ૹ qa/Un@}s^OlXVfJJT &Q9^R4E*)HFHw$$UTܧv1"Aq(ADDžqHfrk8-N?Ur#N[HӒnOqu㿜;ַى;Ϝ1,Amw(; $X_=}rKf慻蓕"֠+lXqF ߴ5YԜ(u}ih$핃. +`x8)Ƣ:E äGM-oKd7Oւ$-3Mi@R(:Ci+yœ(-u>i S{9z< 2q, PRT2"OG8L W'.=hk0OM>*:3.]q˪s1yW^ kU, };WRd#q}y &b—,lN=tDNqqiKޗa}4O+|e@fW|8ި4d+*\^=Na6yNNt*~ukzq Q&`$AoNNHK̙B2/!_<ᗝ.ä|#jq[_83v_ols4jwToΉv!ĕOK@a_&)b꾼,o H.8;Wm;*0 m:SW\W CCDH@6azpfjC(=hB "F݈M Y/i /L60]+,;Xg"[$x- N㙽Y86کaa^ 45'',M'Qg  )<<.ڋ?D ۖP s\VsjڤE21fO~_~p'ىE*݊I>V@q`eFBn8ELҐ.>4g%Q e/FKQ).Jtyr^ڤPV~"U'^Aص020g ?&(WH -okiRPsU `.x/yb $"rudCe!h?ӸbXEe~_57Y/ٔ꾽"Us.=no@!DCf=”DE̫jHT=g8?srHBU;CiJ6_W]-"c::x?}nN܈+^8`,Lo UG2:ՂuGk\)VOꔜ亐ès{dXrDT;a1BxY3,-3<< .kݲ B%Q,-uw?CmYNT9LDf˖7tJFVZ)KZ<}Q2yX0QCs~kgU({f<:cL2`͚ 9<=.r)"@ر,y6պ!Q +@<& l=-{c6fa{&ޞWXvIFAnO SJP9RA 2+Ne|lHDM${e; kO?Q]v-OY? eѳ=8IK>BݷW ܊Q_u44>4!sT@Rpc6rfoZQ[Lxy% qQ+VUkр$oPHP<pGc][qا!$'Թ,Jr$ uuqJI.yzCSx`_ wuRow9daRo@^b_+ߖJ;Uk٩hۉ=TQ؎ ^ *>۸pn0D\ 57]$&MTאpt͒DY.@;OWBWyPw v qy> +tN-WpK~8GֹZD'] w+4@k1W.t3% iE9D a qdSvp|vk(06Zcz*!ȣ < +ꁆ S2ɋΗ, ]["lP7UMA1#kR &y)?M?%{:9FI"xk9VNY`I p]jd󝇙#oim} viNǏEIhra&?sXEtf/7ߵt FM`o,` ngxZ1E1 hy+_խ5ehW1Br lnD84)c5`Hz4`AB:@JsK'kQ_MyQ2\'xVUxl!.uA,⧤.UI(@H4Y7;R+LnXV8@^=ʾW@HfZ;?1;zdkT, )w;5-dLxN@'ƥC2PGk"q8ZZbrF]$8<(U0[?#Veep:m (bIvr]J";wx| T8< i ? b~3ۺ LSsd@x^hsnv%||[01'&GSd-?BLw+0>_?8ͱ>dA,k% }4B5NIz}~Xswn_noruarA":iDB? X^{+u;# Cu*ge"D4g;BKˎ[|P6e#eN$t86[rBg˧anUvQkۤ'mz4ُiB.`tu%5KL+Ok^]fTQ K "FPUY@(H#6OZ1 赼k쎻}\)1.s=G*C8/X.¢AΆ5y\*KU9g8Aw Cm/bE}ȼpduJVUޜKY&do\9`׬whK_>q xevlPwOspJgjFRnc ;V+qP9mGXߡi [#f)ROST>l\";b;'78q]g|zY:s|DK1PYxuύ~I/pKL˄&j{j5uLl_׵Ԕ[#tF$:BdrR C|%62HxEkI#%;w_zpM(&Ԅ$"p6.w?6)b"f:pz6-c7<(y Qo CUhX~{tt ì+_F@OP^`ό1vӤPN-Nf#Izl1\c\]Tj9U LBt}ѤM+;#l731K:(t3Dʃ_Dǣeb%2rY}sIdk?˺wea'?F5I!t7)ih'fԿFrټ| 86`ե0&Bd'=hjYH=HS+̣/V&Xm,7R3:!z L59Z2`mؓ L>Rs>иŅUr)ހ uEg+ %y-䣿IՈR+&N[\䟋WP=).G~r w(rrY |ug6>6wƍ5ohj ǭ I rDju[8£DwBFjul E5'Kf-@MBFh$$}1S0[;TCxWѻfWVF_ s/#hF0oGIY\ۤKcնz2:UtW9=hOuĵ2?,\r, 7O2&1Xg|cYQ52#L~H؟Zs9GGmgˀ{/0TVF{O P8YT!I?erQD/~N=Cb](? T@&t _m#H&{31opFCs,P%[Q V|u>}$蝙5ej[J O ,8xX5S'pb_Wj=Zg,pQ sS0$,8|#lGXU]{Ob`8 l`}5Mْ_Aę+ .-V?K ' Ź2z&ӗZE%]X k7[;L|d!4 ^Tٚ5Wk]8zT{Bљ46LӰ@}Ms$|DUK5ߟi*EOmyGd+{:$[_SJ,} V8kش<cռE!ds:_a>=7c9nWrǛ[Y eD^AlACL5a/P",DTO͗]Զ-20nVaW~OcO;QRw\&ШKm8kgQ&8G,GW t.I;7TiaG)ZzJBk*bO]î f5\2*Md2]8daӟEB&$0Ss]̐,^rCr z ,w 8$+W|jZÍqu3躀齦="mW]I3L6iĺ!\VozZ"[@[\Z4v:lUt/px@`2W QDg?]h q6Fy݈Rx${DM{nfW?Tv,C.NpH:{Ęxo 5B]pBAvM?mRwA(5;>DﹳDq9H"o4VH~IHċܺgOܔʳGzI1t ̍ZaJi&;ɓ%N>).gRlMgUCdVeuTB5-ӪJU2r]yGo"st.xa,`:Bao?UoA/ G4dGTy(Mܺ#K:OxHb Zgu%0@0Wڻ%nj/qY P[{ 9q+|6胴}Hfs`-燫^m9. O}"" J<< /K4<K:)H$8dd0 *[ ,ㄢh4 &0kv hƷygo<'oM;(sIrab^O[ @&OmMhۍҘPa+ /)n[Ny'{DE]QPjJWʻi<.==#F2$N GR#lĊһ-²O&"ŸO?G?J10#I1a(#=:Zrc&ڐ78v,!{X~,$PuĆaQƸrvU۪)1R~Hfq3k_9DR#t@NxI׹DJjg#e3?Jn/ .IAܪթ4[q=ƈ!f)[*B&vܑ;E2 Rbj ddQE͏d,!Oi 1 |!y +!q1r*cmPUGPKbvSf :VabpQbМF+b>$8pj9e-c,Ĵ/1"KJ a6ʁX_8 ~.G[C+Oz$`\ȌHڢ&4fq(Dy~[0hB.Ҏ]{t ? [ r$0f<,wKRΫoi߯6NW F 76qn57Jn:/_^ˎP4cQ/mio8Mշ5#4IEw' `,1a R:NiHpPQ'P4ɧj(|gi7id]gS~/c}#2S4Χ +߶*wo @몐y ###I= \}9?qIhm4!KAi3}8vnGm ?5>99`0,`lݙ`؆@pVb LAG{Ro\{4>|Z]ְI:!~|Y|9BwȽЧ-ֽ3}-rβNO]4Jw o!3h0ށ@cؠAq "T@AK[==r5#3ջa,KcelX0m_"_M}txXPeC₋pk-`HjkhӨv8&r=d8{d< /ڥ5A͠-]I1ЊT~0JS}]VlRzBl15d(U0-V bE`^ɝMpit%֍?Vm`l+9H)|7Zf~[#JW-W~<)5J F}?gᓝfx \HIJ~:a1]#Rɒ.Od:!؁iRJ݅?aH$y*up%鬠 }uBD duzAd_ \_d~7S~CD?v?<#õ#[c[hT+ ==)6U{S'tBI;01Ĭ0Ev*+27sI &^3m:}SI|Hk/vi,I\y.1{BO^p8gQTR/@1SaC@N[fPʾM;|GȈ9(n1#XZt !ޢ97]%½(;7_ f3 {>cs湘yYX?fg8U>IIg(VDl/z$Ň3'; K9!]6?.fnV*Go 欇df`VIH5IOWZ(~,,h:ny>4:hV({{$H1+づ|<ẙ _r$emxDl*ɉ&,O-d$F1'R^̑m;}IoZ.%B>k ?xzYY^Нusɠ 2=,B-jQQu;ʠ9O]Ɩ.]FX͓8sbzŰ1$޺*WM{Ie*0devo&BBV`!ڰ "0:USlWxJjtMl?^i'"KM4%I`q>VqɓLqOИIΩ9o`!|#T4eyơ55Z nwOh[GA+9BqvX$mh ,@NvV G gs+` 1rnSVq#UfMu T=}ѹ{F5eOУzʐ_ Ama@ڌWU֪Etu^ä1.1WOO6!}9/vڏ݈?WOmaeme+7#}>{^>d2n`.ղZP!lʛ%Na_=i))Pj3k,09~B6ۏ f;Q,J`&9i~ʘϚ9nr!]uAMsTre `۸\ހ\Vvͥ-dw-L֣E8se V|+%j-H??pyVşM)+1l$ ] V p8aq(ĩ:b _!]8 lK*7.=#֔F_Q˰=bz@ИwV X(=:2a$ٔ8alw! 'ukvV[niI4]7\|ok$AO?H;%o6SB磿$%8-GW/t2 P\S>;-QڭvǣI) 6CM ӳY*._d#աMWz~JT97?/VBЫu׼TRI47N O)-i*[Q`l'˚Xʾ.`+f5PQz|AvUm&|DX>I.C-kc~e RL'@,9ru*hQ_{)_9UE%^cTqZ+sRJW Ԃd=>[~d9٤FSLfX125=';N|vhWAd͑FZPpEf#[)8Y8!ՋbʧAM['tLs?JAUWCQxtiaN!f 7k^^\vڅQ|*$D5:LhUOEE ǧpPcB[}t`)ȯ< 1k ˹f VYkƩ},[BSgERH| Zl>|^ӫy[@2Sjh:,".]M~hFZ*qzϪhǭ# K̭&Ÿs}jE: k)Da#5;Hek ^$K%FلRb~XGwSi^icG0ZaRӖ6rX[x""[+(B0Y,.}ǘx͌sپZ٧ڦ}m3;cN€Α0<7v8T(k2_,0"mv= 04ܟ^? H)֜ Mr^b&^ieA$/Y5V|Fca( q=If&p>Gj0B'ۊ-Rd+ozK-/^.-ϩ՝;j Vs`\3F$ kZ}eрrN}\iOȚ}b<$a =nfhN[VgEHڻw ?a.#l5)\ Sx m G (i%ZiaUzbp/1 vsM֛FZ2WNKċ9o3|f)܂+pWZ|cBr) XzgNϴ JɳQO8" WARC,UK^F!SaV8)b2~o>CsOS{(]YU¦՚W`-A lҜ)kSכ۞<юj!Ѹ-"j4@ 6&PGݪN7Ltcl>܃U[1:9Oqh?۳h܋!VPJ"ZiI$OT3܄xpQ L?;7󋼡IQ wO\:KΔ&Z" bijs!ti.쉝Fn[kXk2^^(.`)"؁inj(GHl$Ln薲8!RGiHc ̔ 6aBq~MWB]W)aԸ8<7Ž]-Tr]IwzJ%Ts,ER[}Dµ&ʖ3[#~1|%׹H[?R53d(.ŕM*F<>%\g^ߋ+d7fa )f-UMq$ZJ.Li2Bɠ Xé=7R':%T.Obu~RwzP$zEɗNWqZ!ikL&׾S@Zě<"a8@nGI)Uo>Igp=y-y袽ɑizo_8#KdR ̈Ç/a᫱r@6Y!y)S9G"Qw:m QfPpPZqT껽c TPI~`F8d)1c734vn4ֳjSE'juL̿,T{o5F3?r3QxK x${V:]{yUfػ^R_@P BA(cl2v!ibi17WPqg28 (4~Z)#+gMVu@@6+D 65FM$r/SŽk `g2eXFf2|.6G)U}XYU{?"P Z]lD($3:EU|S"XۈbIa@v .qoHuT "6nks̻\MXS %,nsIlbgHDX4^c݈H !sq\qxʥ`G݊vt)lW%aI\"* JP5{R޵JƼj"_di%WjdÔw(4J4h<3YyG,{*< 3u]DHKzah1SI1/2(H2 ׈~ _mMeQwIQf Q=mYyǿ5яgi&=r|̘cTrթ>CD! :QRtz\|؋-jXÐb#,Z2䩥v\2NN`_ԯsI)^ޏ2 +b!f~ݩ yLv K kg!9"f]ҦٞAsD.-gqY3఑bVҨl2*]>]UY:[ض&_!* xwd4kfpŁ̢n #9ݱŹ&4^dZK, A1mZG͏HM/:H|ŒUb[/O0o20𰣢[Q`VkE4wTMPS[*3P3 qh}9/0 ۈ, D1ģMw_᪖QVj=|{V$y;THy (3Gin߿c[bW݃/~nǶqBIu;k<} ׎-L?"?p 8MKGhD1IWPn'/h?/(JY8l:fx@pZgɅs4` }GW G_ F@rBԨu a"Zn; |m#]OEx(Q3Lrewe<QNJ|J -3,- rK99}S#o^)rKc>qûhS>DSjN(@q1c46~u!ym^Q7X 2iԱgKZRNP>CmQC(%;<(6,P_/@vx6sםYUMѠnLZbԇu#\ fH6֕2"f3T |c]Dԡ좔unB#DeZ}Y-&˻ Nl<{b_ܮ."Vi6WngAbr:#(KOo *T-XK~iy[ȥAعY&AEʃBj`t~\/wtZ|\iQCPg!PbT$l,\'xc  n_A9P Wֆ@<.c[L~q>wi2x2K29kot'tt{ 6%!'BBяng]50s@v}#&яep~[M4/VAe9 T*pgDX|L=nA-Y>_R}bv{fAvo0h/»!NUZQLC\\PϗdB~;O s{^|).XpC$HZ6L+cܧ;}^X., VTf Jiٵ3eVW* }uHBo4Ǐv%Gft zg֯4kuPK{i[LہP< JJMBFoV1Sz8Tv=O.(I~MoWa醑-XZ5In|_ŨtѦĐ/RwӊQ$bo&Y?whCgֹm,w][GBb>JЕEg%3b;O'_d>S"hc=ѬAT`8uTf!;50vJT?;)Nh>|4Bebb 7@sFRK VEg>Fe R>kBi4"}&[j P6Rx4`zQ$XB5!K~2RLZSģeU.(?gA;lpLv_LPړ.)gxKDa~N%W|q|Y0`mRe|/rp:zK>Edm쨤ŏ;A#j;cЬno |vpe?ENPd,$Ud#\"ʾqoڲtQa9Mu {ҳ)r47.ʷ%'XY }F@5qSC*nmOhiD׷>IxԆ\7`]v{+ӦlLsg97yA{[xjsJ' ;UQrw/f}taskQH17lOo#ܼHkk+eU qlg51LˋlE}ir-jrn&E0a&2*M*8$]RV=pd aT]qd|h]'T26ZÐ*7 ,zʯU[0>?.u -TVwp d{3CY}\7upG ݨB3U.MU8 o=:{GA'r׭jhX}"1KZ$sH֭@I,}ׯv{#ǁYV$hhpogwfס@)V y̯rO {ZDZpCjVټtYEY6FuB8 ,q?UJ`#PW3? F!qdܡ)캚)]RF1+k}<<)RO"Y t湱 O<}~iwʢVG#9NM[dB"Q(]vQ*E} V~Ww`Ubɯjݺyj( 3JQgu j UNm=`"ɺA-0^@v x R!e:H4!{XE.[4+jD31) `zAbTfDm*FF5>lFUv6n%qĊns@2?=_QH$^ڍ'A\@.mζMO+!8sͬg|.f p0z$2Vpq+/7%v#j5ud٬aO wp+9@QP8G̲^E60zts1@_ݻk ,%P om#~\ ik[F*6/zlI@קVVTݧd'sf]zJvn|(xH.Y@Fz>XpŘ>5K><' r&q׷4b~_hS(+E1Jtd}v1Wwа=_"|)~i\nVͬ!"Y:6Rqplq2_9hZv-@#4~Ѧ!`{Ngג:?W_eΚ2l=9 S[{OpLj8KSJuZz ((!|Lq0Xw=  ͧ%g-ލ }٧t,Y@ eEHԹ\$msnh 3 1Y-A`͵pyd6h~M,r{ }'=i5L_BNZvרRВI|4E4Le20Oܩy׀O96y}iVXU#-NAG1=7> U4 ;jk6]뱷>>OX' ? ScJA%Z8#!}g_Zk?sq@0oJ-~u\e-B fv"'5Mo&= b vPl ,^Dn0( {]BNqv1/t.ގu3).1rx/e-o x.4JFBZynxSG'uX)(⾖5hT`@%Y[د2muh!tl_E=7ȡVF R|-|u<>ecY1AF>$k>Y-Ĝٿ(VV]Ԙ_!ͯA:dF 3ia63(:1 czSWLDI|9Zk@ 0caA;gYL%sibQϑ#aYv`kPP&kHLKJ L£9C4Hӄz*ɛΥxj%wR.B ՗B?CA:Z6"!Wf~$WstB;y=oȵ8Pw0_CNӖߎ%3|VZ'U#q!ŠY;69}VVoCac&gN+WmF:O2ݤ,`#6gu &Mfz]wR1Ag̴Ո^P @+ dLux]hs}E"% Eb d/sҚ49'ċ geAH*RNkOIb} $bxz6n׽#~\V 5nA.\[ ¨ףw>$G$4va#5d(Ы>k0u%v\6WpXN%6|.zh#p$!pRR{B}:Ė#L:j%pi"D@ϻ/o6<{!\ijRZNL%C.E*O=ݍ%W'͕ʹPc)TQŽ?2kayȲo}k<)@ 0.r|7^|e(6#^F9K LӋ|UBzuqN:#"ð1p{:O !B f^Y (eъG[lZh4i7hJ2^2q鍈܂|1_e /vK]:FͼC6{v>vlɓe~AГZ#'b"n$gbKNUYd̾T$ Y6/g{I\INP%*TWm'*sxQ5!&fӉ[!8r\W[1[zV2,ꀸF0(`'ZĚ) z\43$ J: 4[\L#L9{W R9{ =ͰWJxV=)oZSatcZy @NCuٳ4z%R `UoM/Ɵ cy̎ {չ6f7豮r8$KyimVk{Li ׋?HR@7`ܸwc~%([$7ч En |D*Xڶ1+67h5xG;gV燘U98jn Qx:?.h[S2/Nyj%<ǹj#ƳkRMI6iПz"~S* FIx4N?S>5:j.gHCkjtuS aO)?t`E; '< ü_xwi,?lvZ=Qzqlv+G6|tfќhfgHH]]kn%2wT5#χ br+v_q wh 2C q:{3Sy>(\ԢfQ@7|cwyѶ?3>ѣ{ٕ M@ye}X95b+yvxoB}Zcteuۀ ?_JȭMŵZe-P 5/ul٢2T2C"d_ :O+E =C ;}뤘Ͳb1n5$!.rT {RXq#b1lƾÊȂ?-k"_7ՙ>CZ@;:RYauAi'N7b/_k EĚBb+_Aa/CR#赑[s;4 ݕPARu{$aVEB1e+u"sۨvZ cży>k+(.z?l"-unnsA%Bܟgkwc--ߝ@ Mj,}k8à!M?|+funTջ..MbX̼̂-x[uNG+撤"3h][a$,<3=*[,8MY ]qSӗϙoZ]yk26كɭ>_oqOTq悔 1n0O F8(˨Qу 0SC. D]#sqUkU0j!.g9c4 !g嚿Kv/G)r ,IJ$r:R~ : h-}=G ~\ D k[%xoa=8s61b:z 6s J_tJ݄ ^ڒe?RW\[}D9b|=.)}Hm]EoǕ;=5+QE#K ,v6-xk6Bbq"J<^a;̡enLc?67M^MԛJo]5praPD:7"DlV"M iK?M963A,SIl%RP{$--ؾ_8nSzjk!F۹׊K=? u>?w;< ߠp99U?):"æ>S[" `({Z<.d6DRk/~kxbC3cUE'*Zf^t#%"_r*:f T{'k.{x~?4V{vމmWC=f=TqLֹvU[V{4#~wIzS#U\OdՅ Y1y'!I&Uռ|xP`՞ՉW0N^0ѝurKpAk \ݘz=2kٵֶ 믇`)Fq ,wa3{7$i۷`usPvbF'7+06~0Wop("TqwFsM6T?HOpxC2|ʪk7|YX7a?˖b#~1FA -$oA!VR9^NV̼ufFaxl9k>W2Ln;{v"<|3<̗if9ۣtV831- s{~vJi׷8 Cuc{N E H(,Nj9h@9pSJL" HKbŐ`*mQDTb4"PfĤF"]ECX5קT2X6!QvdRV LnM]zNI=z~|_qHQ!RyA4MJ\ cy̎ {I9͜f|c_Hkq(LE>9DǰvE/7GȩP?dZgR #ܕWv47ę=?y: 5 wP= 8IYULLJNɢJ6G=`Bn\UZȊ$)܎P ^>ghԞ: j=PXhpMu=q a m>**۬ l!uɸ9C(n|{=Vbpxt=h/qG<(f4UәR\r[-QrA>ÆK1:. pU0(Q=GuJQE!4S8H*k5]ˠ"41ΓQgR1ܡ\SQl]+:.FQ([W\LjrQ>DcBᕅ+hSS~UfB1)zsܣJvOQaotSO?/ܥP"G536Yvlj"ѡ|p\>~k:‘u''1kZ׾?k&'m ?hSşK;݄kZأLL<(W#Z:tJ[3e)@YdRvS>v,_,&}XCR 1X !>:`>8)m5.j u)_ܷsn 1pKj|?@<(pZkuEJJlq;I&w|W'ڀ/ i8+n17J!MM! ɨC De]NIfMT`R)q893y `&ad:hh|t\$HkY4FB0;|T$gbɣ's9Bh_{^A7fVK~ڎEXe@G/DWk ~a8wlƅV0!DULm*uHG?N0 xs7?(Q IŦ}@X>t~xP9_˦}# e-0*47Y@/e /+vnV*kB1j14Ӕ;.$ ]8Y[q閜&iܐ{$ 嚽b)ÕL~!s*ǸIfR i8/% -zy* tI?:;XBVyt@)zK5Anafk.[+%EaqIeGՐ:1i,e<|N6N[.A^FX(ONxd#&RbL wVhc'oQB wK8'w ̽%6 ),e(`hG?[2[y0 ʫ4IdEN/(kNj% | U ],*" Djr:hq_>d|oCGC`yuW(}8=(E,o: ƻԨ9A+KuZ">,RZ̢C~~8CO f & 8e,߆(nnTV(w1SC߮8.9#CI7N@QoY w_0^ԛncAzhWKUC Y@ߺMti5H *;%-0{ڂ{t&cIRZKZTnGՓo;ĉNҢ2nW?**DCAv@qN-3u8" 1 9O(c>hT*8Eφ+ 64#m8)>(g,sAyFYxUSQ(/aRЈ;?^cg./>whY}*P o!z]RPFd3(l~qJZ"bRWh- ϓ)![g[Tk\:B]Ա"$b'UswгߡūrdrBZvVb9;ga}[H[4T0ǝӡ.!0\ ٶa1k7 0*Ϲ "С%fE֟ˈ.9/g<yZ1EM1oD'wMs~ <@ӟ]]k4׬/0%}[;H֝qEi8[Bb 0~i84ƎPR#?>^ Xӛ}E~/lɀ{SZm{4nꉨ۠D5Qur6h_Ή!^ʧ:sHuOvӖ'ݣUr|\7A@%o/S_]{ d4}܌ .T r}aGf6E)縴kr6Μl3Uj*GV8NwQ9G&g|`p.U N[<@D)=܁َx(8g(-ƦMYPV[!5 oȽ(r1 \`&a;Pkwi\>`M?i` r ?琳y?*n(pN1Fv&:̖&!(002BE֡;@~;m.T 5Ͱŭ[AmO>b~ -=ٛKy KyԑM4d;&3$S08,tƫe:ȒyC>XV:^z_Y1H[IDt ,!ONr?`xmI{ sz Mr1d,_Gl4)9P<}dzQ<ϱF _j!OK1bGdJܜc5'MQCaB%PAa_) ՐHz}0MjQ+gu)/W;EQGԺ ]1dw-u(QwF:7㫐X[{h)'TUW6(zI?#R29(ҳVwDaC n-6#'Ð2:>'| 2уNzg?_fA^ @_/m'G 9Wf+,f%SػC|G*.<-2^^ ϪIA]9x,8=z\MXIF\l_h⛫k"?e^ s(1Tqg%rՄ]9Áur\&I9s90Blhowmm8Nݖ9Tw(6,|4}}Ve*}b1ۅ'Y -n?hxW֥+c9mS(̦K[ڭ7 9FJ )&z 󚹴Љ/Fc ƀD6r|h9uTJG6R&glm<fL9reG]V+,w*bgOh 3OMmM Π+A$PTWEǯԘrU~RQZv_%T6V PǤC}thEo6Yye'TdZ8y|?B4cKէ-UHU?2t<1n[ūE}Lň^& QQm9IVWS6sZR@8&+ـ=ֹV{B][#R`'sw!a9g|/7 Q HUpDM36zUCglGޭHmEIv94"0D+gĊc,]1mzAAXB`,s}gh_v% Ȥ'g豿B( -1I$] [+Ub2B){1P_̽b!mY90G0 ,h(?%뀟og||DSoD3luN~1]ӿTހ6upxQ7`3@&W_Nn1~r3Ǒ;[E5}R-דGSmMcH%Xmr%N}1F`mݭGY֡YxG ſOB'o`ʧAc[;LJȼr~1mdäslx *t5jVN\1JG9 rb b,6*Y(/ 4Zț̺ߺ?"sDcI 4v0PQ 'Y*681:@ĶXSnmO*ۂeWbôv>"np^﫛2rXu\#Vٖj, 04=ȽKTcjkYFjѴsw8//S"e y/{忟[Jr9jy:2#)nh7}tRo%.tDIsʡ}8O"Nulp|SL9weqo3Թ":a;`{J)/gZ kO縉fEpPj>||w|{0{(08hSR嚛'l)<0QH41/%C%tPT[[DB҈.>#x1PoA'I!ؕ?T`keoK2bL9rQHE$M5 L ~M .o8^JN1g,r6z{:M$K5aUYHLܕpdzKEXQ-&C:ܚd9>t"5pu4t~Ba&C|[PKlm"NF . &oFb.KeX'?IjC{#B!B+bR֔eW]*`K )-m^F6VU~mRUW{*^e-ӓpKo-y,K1aZՊc03]:o˾Ǯ<89ݺr:.*OI ԗ.9Ý=:&ݑ"] [zڬk2]v ixGx6vA]4ǝ%$q 7gP4r%GFy.1 TDFۥ;,6QeX>v%͛{kT!&cbŪt. ?q bVJ!49c쮩wIY%bu(F^"!Sʢ}ks箫bsVD3wYvDgθxWKA7O;lGhltK2˥]7eu$f&y*xIHh~UޡZp|@!3jJρT02/A V& 7f{YHil J Τv6 3Ԉv6+thq\a'2 ?,8zr F5 MbZ0N DiQ,^ 'ciDP)w b#fX 2.,==||贾ߓ_#Y=W;)@Ikxa -$bB4@[b7Q\ 5`c6]ϴg0ɻ }"/ACvYMD/u?[K/ӟS`l!cl9߲Rk7Aw4J)<{><+i.w- h)N!kwR*%%sOPŀQ棐!}sfu.pէ%/*\W-rcڻM㼖+J\>Gտ^+Zclwe hmq%[AݭZa5iGIJϾy\+@7`*Bk6F@ -[zxB0qw\SZ\3.XV0=x!Qq4gE-K:8_*d[>OodGr dVpaTXM4)B# aebƍn3:;v3!p, ?aeg4|R^s{8OXa{>,X5p*$ e"lj %AjAlm-tLlV[TZk`z;j{*I?YtQg=jtz<V4f[3,- ؞ұZe F@MX[sf>ړHj Yi!n34IkU?Ԧn}഼LaF2o_MGO1ف"|X.eoK30Ru{J$@$D15嬜z!L/@6j×*oǦ'vQ$"=qezuo *ۤKds` I5M!Zg)JbRx)>ſ="㧋_ _&Nu>[Ti1$`G/7!mՍ4k0[h`C2G{ RMav_Gn%{*Y!@ ׸QCq 6GNI}|DMo;qx/;Z97X#G Z3Lq꛺>u ] ygʧryg++۴*Z ie9ܧ2,宩72N9,EP@Y<^ɭ [,5LpesЂO_`'x2>5ߏU`"#;[ :0۷]|/ ]ھ_;a`!yЪAy[>=EP W39?8mBnO銔n> ܁4~SY7oSgPC k ?d=ˡזv20Wiczl?Z+puz3q؄L "-ԈmxQ#dVسьEBn/\{iZ,Cl<߆-o󬼟RbوpP5 U~Gu՟ܴfSZ<ލCD,~y՘~?H~t6<{1ڛRd5҄-(}^p "!OQKv]s|S(ai(BN\֗*쎺~B2I9k]~^n"F=IgT1Gu{V^d'xSiń!S`yCR-3mtB?ݕ=FVdglA* k5T? ihrS0=}ŎPSadۭQw(>gm Q^b<&(o[Ymc$eJ΢n+@鹭+;ڃ"& :+6 k3P\ե }e3EthkuLͽ1JCY/Hp$Zng(Efbgg7h)5)4%t~)g KKJ]/7I:8mbx~:^9ɢy.w8duNǸT{{"b pZ̲tBVw L”-g5H46"AHe R5N (h&)nvxX٢a&Oˣ uNqؑ*tR_MqŻ?w2lǓIS(Ov?r >7ShYe"հ/0ks׋;i7e-=P Ob^e-Dc_t ]ٓg~glif-۽PO\27,GD4|eRN~qR*?W1FDy'1yd9aX\HpV_ )Ag{UPmj0{ ŀ)O!U9A--q}+ߞ} ֌e$38(AhB8Ze=t"W*{žȑKA͵~l5V' ('mس㉳/6ڽkGwf"/(ypjEB`P`HipߖG/HY-!Xh9 nP ot$K -KnPROl ϋ-SEǐ]'f% s"p3|P=W :GSwx=31[KH{j 2U8 c..|S\2h G[fx1R:ǔR mh:+Zy1}qA鞀]N_}hϳ.XU7iL? <\N;#' `P w4**Rx) 4 M_$p`N\ˊ3? Dȕ..KK7Fcȣ6tXH}O\?2N ]HG(߄.O҃+:qvS-Tr\~ȝ'vc뵤\(7_;8`w?g ]_$ENQF-9'_%Rc*=%.KCG W\3׿7i%صԱKBbNW;u7,- . ,XutInɩ1b& 2~LN;xY^SX&pAxl>0ϣi : ^aZJj.xjUecʝ1|:J/%ђf718^ӟLPRe$d .m> Φ ?d L>$oqr;SW2sj!W ,IR095c~%WC^P4*6XC&u@&gv6Ln JƠ ȹ oE/mbFJ`}%IӋզ{pfFmkq :&Ln#xM Tٶ9\EEl״A-$.`ájj‰T*/, K\vHZrl4߆E!% éjBDO|S1JܵzClvJDDE1$å-Y4g刺I}a-y3?X5]ʘK^5:_4W@^m I4ɟL~y/z.[ѓר*䩍e*M7 2C(:o SAPɨDk<M")*A< }睏0G'^~8s߻4l5B bBJncCu>%>ZKy>;(݀6[JJt` Po85WA\dW} yR4X$NHu04OB#d^[f[ Td?n8Kܦϱ(R\R4MC;䤏w||I!4}MogFė5JvB zABK^MeVm~P^1wXXpͭ8<*"*mLo 2Os6 %PQw ɕt;6C9V*sPRfzJ 01BPn\AP޾KcG0`*xΔ̥QZη@$|ˈ8Y~WLPnk.T ,NMȋ1 @XƆ^(Ǖnܴ`]zSaMZ4ԉ>^6:1VpCy"YwX19p\~EZύkKDH(9Jhia6EPw@Ym?ܚ썶&%=FLSc0FՏnV=Q5X3-3Zz9zgY }ljC$ >ş~-!ʹDg- #=X{غm K)I%<TYt\ g4CE.LbZ4wU؁hXӯ4xJ$&KMסX_fp/)y ]֐󘺺D QvHQb_`ti%= =-iZ%Y]c6xE'ۿ{C-e#49G?*1ІqSt2 6D)d-Hp Vq2Ҙqęv%xW] bB3f:KF*ka=s*+[2(FJfEQϒ ])tH?_7z^F;«M1t鞅Eh߾W|p8Znx_gSn/Ipl,]=jDk51sA﬈5*^ K @o4ٻ^MgFE@V!h$>M~@&4ȡpE5k{6>YYcYh;ԗWCi,{n_yAYk˹a߬7CŸ-Le?NƜ+%wFur [Tv##bj:v-qH8$Do0_cl§ :MGG!|cF=YJJmf_&:ਘ&,v@=U6_W>@߿&}_j  'SNjHOX#W:`E0E[Ğs "=>{_6`V7BVN=RwHzK_PJLw"2u ϋ-SJ8Je.ҝvԛŖȥY^FIKV[Ȳ@n %ڃ Hܡ.;.848޺ԦUziMr:NA),7?ēai=hEfy*i ҫqס`h*TP.@a2+D9ɬ:XBMME𮣳xքYzݺ=nv ˯Ɖ'1VU5nybs5s_> 2cZ+z:.۔  ]ͬc -o J赜+K\Y~ѱA컩jP'FsHB0Vk@{%=@Cf=_X$Us+vZ/=mUIaݽ]ccz68a_{~862lXz~HBT\{7ԼOؑx&)>$uhbgtB})P@^.PJYlNϠ9P2(RnB2LI8&MI e?`}"6OE^>:ў_[ ز{lBnzngy ^?Ɗ i?)e;rƧjPx;cHUp!ۋiՈ(i.Nv}(:B0/&ܿ .ߎsPENZnR*=rH^RȢ_~0w,Ń,$b7 1{RɺFz 5|vZxe%/w"@\IY-"|&/9YPD[Sf1H'8XWryz)L^Ykɭ"DYK|^#9Qf6X,+Jĝϧs*=v0 u} k;ߢbPRu\eȪAa֥>ԮD̈́Rd A-hO=Kd [OzG-[J7 \Qgi/>XF5f5^-=4{Yeܔ)PWmZc gו,9'p%Q@>NUH_>{! vFrZG]-7.ґ[ԫV5!^,:vPz&Lo=~b}{d Aa% [EiOlNiU [ίBQpބwJc(Tba+n J.ðLg廆D]l.u.7eSy(3ui%WV(t:gtP(F i}pז54{e' 3_F?@U_ JʡOx`u(F=x[Zg LE_WE`WO. )kW) )U%Hu^%\"(&[(hࢉ8+䰤p\L>P2hZ`ͩ嫔Dz*0JHs=(vpIRT au8opHRD # SeFgoUI|9_ z2ԩJҘlUCq̤RU"C8)ܔִJnϻbh/i;Sěc@>T:zo$qw{hRJ y拰`f"LxAuخDKL%QRLeV'yՅj2Ġ`f-~}}#gZP@,m\]!ݡKFүzfGnlfqUwHX@N΁?N7r9` ,L!`"DhjF#)Ƹhr,befɱ'NE}glKsS5 qTW )KTڊn^;g/N-Ѧ1Y䱏!cF¬zk_*֙z”wn*չ@. !bnE!Nu3>)?uOnxQ13A LVP7ԩa>$Mfh>-hins8Kf4Yi:,)J6 pH^AKzxIP wն䡣N26K&oNڙ~)AM> hBbb 8PDؽ#"2#&ֳDh۩ӧ/:, jV$%Ia;&" rKVxDW +Ƶs7>$pGSZTSATu\l$(zЫ]O<3eǁvYsΕ88 kNb"a4x)sCnZ!b$GetC.uΩo Mv*EfmbNwRB[CSY_^?bly5-jUų77f@+_e!8z,#;$'jyVb9rw;}6ٖ {iFV4S5F͊םqC#Hx*_ݴY&RNݴ R94\p7- I= f{j(8ޖ+>\0]8 G@6L.hj?>oӬXOK1/% YH&ɛ]Ɯ<;H]|ݨA(Dy_2su٠YWpQ+90* h5TҘ*4%rG@&CO;=#-@x!_l)\͙j: a3 S|Jen:QXZ*;yvO!]'ΞwJbQTs m$_8KmzLp}Bc⓫^èTX8o SP`؃n#ۤrɗW0#_mo50*h#di&tn߆`|{ Eyà ?\$:;?Hosm?bLү=G5]c\~oOPaZ68x,o8&1r%r/֤^uu-oTZ[Y>G ._")֠x+3*9t ѱp1V[ J<7M qC2oI@&u3}jT:W,%3fAl#(󱕬_`w@cȂ4|\ndQǘ|1g#)6td:$޸RӴ yv)`m2?Z¦CCnuR1n&/Y1;$Ż$ Sj _MThk\[8f9u 9u!@Eeaqx_0Unׯ}N ̃÷Ø vJ*|@ !2VIqy]g[/ktJ j18*9N\I7[`Es#iy]9 ۮ@ҍ|o"d ׸+>?0Y0Qd12(Ѐ/iz^OfČҍbj"J֚[s|y򲰓AbUpu\S,UL}ǭ,;cEL[Eғ0O#N !k_+rT8R},!&`h.&=c;rQ/ -MA>7Z i® W|c%@]E'rn,>el;|=4XJ\Cm ~*ŲgxhyCTx=hq!۫:S,Zbh8$v5^3k |lo6ɷeXPtQB5%< **FhXAE3Î֎J.ëg*R I7ƥ%̆ωDFj5 v_Y݇GQa լJ~݌cՒB C-/’(3r 0k%JNJ#.bO*|aZ_c@Papo C)ش-=0}(=daRAw;vP#:ܤbL oVٛ/>rB%RF\L[S+ VBw€.~ ta&VBHF5ݖ *f 1C0'JFVWdE35sY:$š0jmɗ4':E CDz3_*jJ'uf_o!,Kړ.ԔH `8i\xFdz΁ Dd^[&lq0WF-NZW"gD˶Y8X/YdH\>ͬ^hP-cަ.'(d~|ϋ(l2E-׮VIMH}^1- kh4 F-^w jfFUOo[iQ B}zӍ-.o,nF@3zH;quع4Ng'.?xD蔶y_gw]&p£#^{n\]nzuSKBW?FU;Mt9Rr?*c.ݲ%2t&&$xNmfdr(Ia8T_t7xb}7遒 2/ ;CLt c2RN7W^+NڟU\rHMiGlE2$6b$KZ^֜-RRlʤX5 gjK82+$V@ W]H|\ ypF@F7Mhstu:@rT +pK=^@H6|+0r.p2k5K,c$V@Ue@EPQ17nVR̊=.&, p$X'}>y-5x{ @U8) ]{ !ibԁI6WI#:-S%*ܲz vJM"H&P{;|E+U@3걒2S?S'9f yD6[/N*pc_tO\$S`y%/7h\n4( P{LUR/*JIB|hJ$uVm }12k7Ve!a2A8:' u QNy(׻ϓ'jDeR[}IK /^{_DG` "(1_C6 }\Zyyas1!5J@w,\U,\c,¼aVs֐abQ[>q0G1=VQ23Yn!(auԚ1^@T*ͮ騹*`@ZaŸ3R^U_JTUEb7[;s]۝Ia`Z_V*<—ۥc[.&.Y"0xnf>:;J s E Қ4ʷHvםA EHddtIlGk/o'/?ئf3tUmU_[pi}uFn}UV)?Onx*+vGdAīE(r%Iڭzccu?Nym>[=qFXcRے;rwlJВ“ްOn)}[ʧoXRR,ffSa~12y4JV#>H Q}X]GMZru5ݏwLuxxE%o ~IvFUC<_tqn?W50|\UtCó,hEf y!D}jf'b{DVr <¬&zIċI a`h?<J)]` v.%/gJ'M4$[HѾG4!N'==眯z<* p4q-Nu U.>ڃ71u<1 ^. j;tN4:78ܢGdO[ċ%;fn`@F6*5u.mOoN$E2-w*рI"ZW^ma= 4ƦƃXi>HuS4VJ [i`DQ&!)bMN"0CG]vgj]xa,*&M5/:񌈢 3ndr46G[ft[-hǾQPika DeaW%IN@sZ0%VƉ ~Ho#b%޻8u613T lJR\GVb6LFKt$M) |;Q& Sys}iHܰd߃ O"1.QpHoʹv宅8aL 4`L8 oQve0@EEQKEk9pq-Q(RŊc /#lN"MƤXKWcZIZ|{w?3ˢPkT)?u+uPsX`rdۣ6)V2_OCF@W3e)Ph",'5׏0:iŪ8r:am6Ü_mf `6ѿ6e9`ơ3@h6xߋA$y^ki1l>̕WN\BȨpUf |t@4=f)6΁&e3#06FJy‡*lsٺT~$Wߕw=LC=??%1du n#$!e`{G09dmEM]ؿ:\皼68og T]19/+sn-u/P*] oAv=@0ett!{ C_4a^y٦R| ZøHu{uA;YƖy.JPye1Ldi}@`b2QCitzճo9A`FIױ[lg UI2&H1sopGǭYAK:yR#z,H '2;s«%U1hPz]jk~߾AÆa..YMK;7* ="{ 47(X=ZvV_^#A3KQQImHKm ?LI? 1LE!)ɍ['y]ЦMd>dSSXY2A `OBS^4rS`xPAU$4HM6:?*W&8[LnBR%T_5Z]Mk"$?S2*.:g"TFyKgb$ 2z%oc*Z.[%d!$>ԁ(Ea_݂\rbմ*yރA2h/SfNhzן?F] ޲ NbnY@&Υz""ϐF_65^S(IhD#(a҇ц-6f" Aꃥ!qwjNRb_(Rgo=:nhLQU(z&$m1^R)x[k9='ob ԣkp`1=ڠ-tvx e֢_CC #Qg Tk#,sƢdj'ebp+!9_s\Sw*5+'%3 y| mӾ<}%"Q$zR bYZvRLĻ-AUC]33\}rѨYePu$> @FžVqyYLHUP'[f8 Ž7([ >=efگLJ !aHP0TÒ+_#f/58Z?^6=Kx-PSz͢u~GWCpKq^h`u4[q fXO t-iCo~-@5$t!\[4X1Ƨr,`q"َ\Uu76+rbAi4ڏ˛&8gQ+2 kpJ֯X^UOnP"䁹Ɍ7_1qA٠˗TjA<7a=M hxp+R"I3i'%9ţmEaŘ9sف.*yʭfJ;,`ߨGkDȡg Og뒯UYn!t/9SxMQfQyB\/Z(?Qz 3Y2W#I/8-fEz'zF ~DžԾ0a"2\Y# ^58T%YHiM/gRzs^_"!Q%~aS_iM9 DP kQ{_ȭN+n D Q=&C:yOT9ӅDh}@r7z;OC>a?lE`鰋l >́6u/3k&n=9SNU j NA,iD:V]_En{R8]kа`i]Zl$1{k毈ګ#Fnr?^T(G{V.YtVi!:peIIڼbxޑ,6SH6n՛Jbne{VbUS]͎1m.OrIם5Z=zqk˰ i$UYR)Sak:P]%(k fV'\+ Y%Z o#8w7ruMâz?Z1}V?;\teɲW=2 Dv&Hz׎Pcv 9%7bepq\tUM*ZO633L$n W/`:hIzg,]zdЋHxC]Ή8QZ"И %" bzO0O.iĞ9Sˏ>ERR{C%c=%ZKk[jOT*?L fԫVu!81٠"' V ը}!(ˊ= mE}^B,1A쫚(I"8@PQ:T $og1Eɢi 㢙ZQKu*׾Ig:>l2\o6uc%Zk_ gN`^}@f˭GD-lwq)a;籉ҍބd݉=\O[a.22{o3Wc8h=sy+!:9ިKBH:8ݔ<vt-J[m*Q>/$dW4y)rĻ0 I9!z.iχ-\]FdZbpg k פ`ßFOSO{N-Z}ihC([EYRuT F Zśysxzd=f$! s"oj4~5<(s2vaVM&`bRCUx[pN͍z -YL<>/ {LƫY`ls T84F$΃ov(>2yeM+@03)c[,˂ o (nRR.|O&($ h@KW<>Ŋ)]ĭt<$f׿ghޒ8 ] i$Z<_ظ#oNcfXq ]Ku7 RU $_θ^`㕸ȟu"nDFzvUiVCp{ɑf2ٳUb̗*%NeWuFqwJ_ e, /OP3] nQL[sc :pdԁ}2h1]$QīL(T0mXIvIh8nN$l+x6 Ms9'5OWmkE9΂`)"p瑾ޚW3j[`|5(ܶH6$LA櫅 !?z @F0$7^ћ(Iyv_SƋ,;j0D,.n{!()1} +ycp[ifJ v,44˙siyJw(o|cT!gM^МpMQ߰zaI_ H2h*fUά~Hp_Cm:Bg"/8eo_u\=`qSߠn ףۈ ˈ8_h݉FQb?>\C|.e8 *54}B+:CDPKVP.o^*-Tv̈́|㛥?]=K흨QUmzs>i9ߑf^GD}SA;l ~gI}pŞ*&%ꔠܒ 3Bs3@t|əAY!j5|xz-y{Ve)56Z۞.[)1{cm-tG@Rljt'?2NTH$8w lmO"An$_ z۷Em4*XyWS0^v#Kvԃ[B;\Ltb`lcAbNfavſcU+Gzþ"7 KtfE<P|߮$ӱհrW#U&Շ,*nߴQ6d 2:(;F0@W@Kl }vQk1MEMǼ=4L9m}Zz=T @Oޯbm9r@~Ji:n(}snըLnG[^K:܆ͪr y&T$|p)tTWJE%^|uzLql}J`x'*XAr3.=R[)T- [+YVl8o~?x*Zp\"6uؓTGً OWbwt]n4Fb9Vz&9”k7._%Hi=+SExzTZv痾O<;ermag/mֹ2TV7j:V-S񂷺OEDY95 +0C>Haba>xZ5T~u铷BFz,J1D*6](@.Nz[3toF9j*#9V=j9fiykp(JJLyytٮ@r%mYs$a@PDJ|!G|bufV3m3% `% ES@Y2۟S:Jul}4TV sw:ts#H53cg4$LAWӤF8ki_el^桏>QnH?- q|9'x4Jv+)^kPQ\꾐YYb`^[uC&)3J_(y!R %SH^_iq<}lO&֣x6'qhzA\4)DG瑑%x5.EdztIĘNt/AY@YiCo{2F.7gitr:~zWXq]7~Z待+L|Z$d}şGe;/F*T~vK'6v0oowMQjۄ~:ȟ}<}=v%\w"FLeWr6UpǤىC)n^kwQf8wJƩ,Y,_&ĹP5A&^11"geʍ" #p+zםBݥ?HO]:1C9yy7'g7@#[ ɽ?U"E<-'O3DG#Qke)~qT ls*7*#s+OBo̹r"/gNbqdߡ-3oez$83< S"$Hꉹ\U,`u. Y%ڡMc7=cY=r #I&DB8AeV ?cCX b 񄇫~C/qI6Bb;Jq|hE%eVj*h1*jgJL:5k[4?~=8G=G5kb)إE) d狶/fp$B8EnKAg:|bLQ ε5vFCN PhK l d)E8:^.D=$tJBߗgZN.āa7qJBC=nbޢ;0J/MxOİpb pig2C:-&R{8!}hQ3m 굾']JW$@gџܘnt2鶯,cSUpMT13(o~v $ǛM:0Wg̎.,FtY_b|EO~p:󚥺Л-VDtxc3҉H 13#6\`=gOfv`bQpMEzCL./#e>=_dU^*Νb &Rse$i6->'*AXF^4 h8* un(6ը e {ݽ({}0G4P>d`thZ4J%h oE֤Ւ%ʘ^R,zl?S2ȣ//g>H} 9K7e/P4uǘ0FN?᤺;9Dֺ41{R{V@91DV#%J6%*Fئݿ5].R+ıE! kX\/$q{T«5o!${=Ax+h,?#D8H ]r}6vG4+?e'BabAzzf'Zg zRIF. QU V:bvw*|*aӖo(/>!:Cb}oYT& D}eC^Ǔat{WP;%J2,4g":-I}G̠"TYuk۪Ȗ[ѻR"N o]4HsE.΋,GQ8gVGffSrJs6?ilt{wrL"ip.FDg Jvb1]?0b.Zʆy([\m59:y0&JOuu|2 a.WjK]#㻷7#vo"8N1BgMUv+q57b E{6;Mʛ8P3BIw"::Z+~Mldb @z!U&[l괤U]>ٺq$.)m@|<8g($=@8*b*Z̩,F , \}$絩EˋثO7:!`)F P*rc׳4BGXXwb!*ʨåtJqҺWJqK m 4lY5pj{@:! = 9z>E P#ќ&[ѥLA#N 52)ter3 $ExRF|Pm2W:TKw^&͕s6f& ]24;Bw}<I8^;6{RRFj.<]&=Fg/*zqҼPdc$ /Ff3WEn[DXzJwxbТ CBD^Q8eCgucm.D#Bw/Vzx)AR D]4bn@=݆sl?zy䉱 2'ŝ9gH|!XM&i3}lcꫮ{DE;`{=3 SQ(?ϺHܫ t^BH- d\]b{_GvA6Uqo2Lu0dۣL [.{ I A I47s00o qv`hFHnN(ᐾ6~!@Q$hGۚ⑘Q Ŭ3HՎ}]Mar2@`I N |24od]q?j|YT?MEI{Sxb7׃(/5yn|]k!Q&]]qb>A S)=.08b&1ĕM\>0A6`Z)5"C/ _#T:oxv5)]7FB-S˦Dhs>Q|ݓXKZ˭kc#9pIQڳFd~Ge궙nnѬ E{%cIB$o4N(.`f]es|XreFǽMP1Ľ1D^)O*{$M4[\8$IX*,RI=k! {Sm!Qbx8nW[TF7≠T4[pV?K#>{\!V|g=Y~@tS6BzRO| ^x䞫 O=<[LW/~ 5 kd0ͤZN##FZeAZ|}8]#{1VlP{OXO&b@OY^N*urrYHȐժ5P8ٜiK4#d`LJ4n؃e&PR Hj2d qB^9daBN^SčO=5uy(%uF3qMݣI^Ƥ#xP0 f/h[oYa'Xe>~ -sO+(uWt8uz6%'n%::$H'G鯻a48x$ Jљ2z9 w y6hd!LzƯ*zcC̞KM'Lc'q%AQ6?iV]j>I*ۜQ ehs))l{&rΙ|iaLLXnE/G{1ܓOvv;nV{>J͟=Щcx~})uQ\;eh_PW{jf_iZD4I3js)z}ss׮D`1[lCm,[*=n:G/8`3ڈL/~;@S`~)xS![yNqTLɋX/N:⛤Ek$sZqOqR8 FP=؎VoY`i=TAGUZ[xi TH N}ܢD`ur:`` =6Ʉ<6 Wx%§7 6^ǏZfkBS/'Mt7aUtC֜adb"쀐{8"[>RMx-)6s\ $O\1}Qs۳Ś]]AzMzg[cl!iʹ )tKr'a̻JToCђ͌i(p%&Eݧ0 0;HD֘I#: iVj_hP ~OH-*/]x𨕈]}߭gB( ^2ԦE[id_N:m{=" t>$ɎVm!<ƒeK"CKƂsޠaT4Ջȡ\)iXcPcqvсGfbĵUͺ~f~]ۜ4`Pҍ򡐇Ϩ[аc(~qؗZ)atڿNiL_ҤTs d{V}ݍfaZ`jҡ]<42x3i%`εQ+upGAhbݼCڕ :~`, V{C{]b yTnv!o],GY4x@zL5e {A#Gi=5U2#L_ N,`}zUg UB7n2,<&G?,Jy;{mVw>%kruɘQ p ʓ9UB J[oV>4s<Fm@j7ꢼI./IGh _~6vY,u`$nbYQAaM1`H#_DNXd>T@ x]BoA^"=[6jXfWފ$;>3N4bbՓhR)2e1I6ҹ%$|q6s !7cz}:1H BX c~* Ru @N=K<57pnY~}uDSѬkO ]%<3nhl *Y4̌д/nxӞ9yyNZXRBm>d5;߂Ђ8@+j0[>'V1ow1^}yY2tlQ%[x1ǣ5l; 4K;a qXS~H+Q5ΝъDZΞ7yj藸`,c,w I7khhk?2&%Lg`:PnQ 1o3DЃ$ɱVkNM{ g7}E bsSY_A9>)31>`= 8~ӛO'E]q@fI2+WBN+pNnI-ƫ69xart3H+uQq5SVs(' iU>IWxn.ke0-z/[IegqHXV{.g+0ܚY( ~ L.!ߓi!0,ݶ#ò %p4&`'|Uw'jOD_}>P)R%7[(B ]b`#fM?mHk(%'!Ydy\wd-6|=_39Dv#yrhZ(1tbf|{#ٍ vn݇iLweTy&oZoLq{bGDg$L eˉ<3QCw/{rRGO%<1Nf_9zRZ8;|N3*/[enokgXZ>7eeklw+s9vSsmgI32o mkd [+G1攴)1UY:>g+v1sZ/RqV4ahw;N"s#S_]+5yA{fu!HQR2\E8p>8R ~QGSfqTaA|ʨ9[~n{zCwa}YQ1b;<6gHݤ_8(ɽI9~ǗL5pچ۳Q8 wc]eL(hkv~a9&Ij ܽ.1.PF|WF|4W'F<zz݇T?& g/rm9 3#cY3#&f-,Q2huL#s>t>%K<}~?*h8; if䩸Zpr&d=2[+:Dq ~$l$9 ic3t$ݐ0tq>g!Ab3-n1Ib) 1eW P?`Lr<5'W*;_ o@rN,1`-ʙPoCFx0nѯG[kCcSt:HM&Vx+]i18)Vou1:K<<><vNtYGWQr0stwa t!w w7}Siq<́}y_oT'*5ӕ5$+Fb 8qHdNByrͷ9hX'Q1;bTiK^gyB2Y5;Ҕ~ㅝ%?ΩEv] jW+{3MkusQ2\Z.!XGJ'A5PХWd|r2GE7Yz>ΜFV3?|wSP&K-hfG_M8] ѹ#Hjmd WkQNh_~WKj2?#<;O{Ja|`itYsDmHoW? )e{5Ӎ91"[xZ9 ZךG hA=WIڎ4!o7kb*vSA>H=;Z}EJtm0f*60fї, 4KSkd9Kz]T\qL (z୎ܭz u]m{+=s"= եt=+ޗF0;TFXgVF\1 qo]Td :<[bzuqZr%mie^V nĉ1!dfR9zy@h%቏^-JDP\k%0., J89/cH^|Xh"67ƃO9PXո:}Sd?M65.n^J5 @%RH.%> >c-^ axWBa>O|+P"t& I)D[S>MΏ!lk;z]8[6dqۈ`n(hvk07bEC<糑% vL9@;iJl7¾"eJ꽨*N""fш8vwivVwfw MlƯS6Wމ17rx}g"DD2VldBއh=9>m]=k B7Iho1@@2,2F 뗟Bd4l/ԕcW'}z bGȅRAm0 UVyR9(|`=u_DZ9yLu˼?NBiqCʀi)ɾBjJ"E8QZP6de M[QXe*~la z&&,0lyjKq0W\+|:}j^4LD}Z_vt̤3le<{@ Iu2S([v!%kvOQ<~Bm~&v,dOz`\|zzƠ3%0|YNǬfqbׅeߋx GZtӊ+-t4Բ"3<4A;ux㒙Ρ|=U0={?䪎~{73wgǷxhlQƕL9i^._7;x&, iՑ'1%֋@I 6'8.s=G^[W}hwtѿ*Fs)ݑr)4 A`CG#0&fO{7. yq3 H#'.u5&ڒRjW Z(KaxZAz/T_(VEu4MZ^61^&~fO2~fCx B=u ]$"ӆDxz ËE$ Pџ?6"F\~^++cXR-؈O˿drm./NMPMXt"[.-'B _x.=~z,ClYMY VN^O|?/+iU'Y:݄]6AIA1ѧSac#QAqy2u* %W^h sĀS"ەWTՔd!YpKS=r֗ ?"Bެ2 u>}VZw[~ڱNɁ.0) I4biR8YgO$D[IBMѮW5Sp TKtR!Κt{bOCU, 0@\p :aZ5ڼVTe|, 41l<!ZH0ڌ*E߾nUzΆQgʑ[&#R3ћ@ TP "LW[jiuMwdY |T(,cIF&șFr&=4GꂁӪ+ݏG˰ʆ`GyPrrAW=M1 /<_Š[^TKfj}}@UXO4;@0pr0-b5s5oA[U}[]χ0ãNU B C#صbqYd^y$hC+Mܭm*ͶvlgAr _i0?1_=#sO œl `qUp1_:ԯ,=RagXfBqe)/L-$R7iU+ {DV"G^~ AaXaF5[b'U,)'l__e F 270f z(] q>a yvlO9؍]X3ٜPw:Iҥ'NdPK϶Cը@KxW~P.ih/!dFʎ93d]+1j]a\wV)܏AI'*ε"iȰS7v*’jA}B$ 2ˀA1ض# (of=icPo.Ŵzr%jC+1A\;{\qh=r^d/G-Ptr7E_I:kLjp9ۙ/gkŧsK2(LC qBcRU22rB`Tt\BC%#3v ?7p_MnjFVSsXe XerJ d;{HSScib#?U5~k_TaD>i{ B*#)_ߡ4iIMJl*SO\G“[S)>aO zZb@ #pvҺ&I׏ɘMK[ݟ{Lԁ4 !)Le2[hWK|Whrm,a v;BUv\[]7G~l -*ڑ|jݦAOYi"API/z;jjj[g;\fN9/ENp;B?NIssx~'GJg栠~>$:-I sDXaϷ\,]5RgjȆZ5T,yT^lV6a2j ]xg%3P_ͳ? EB~nHxS(Oц.:k 7])QI~.iɍT;,2\y48 w7abلK:M;6Yr̩{BMIޟVI|?eOIA,^ĥuٓA8W9?$ dfVqA+(nDBSKx~@Y) ;SP< E!NɆXӶ;|xv  = 3 ~uω ժӞTILrVBo݈$b>Ag{/=tj1{<`ܝ ϥbt EGYw2X;5Qڼ9b#R;D&<^ 8 CQRH-fB{1w)h2lůt`[ jٞq@{ft=_{L+OkEVSp6mgpDҩ2naOXxkZ$Zճ/z!2:I7VPԺׂҚsُTV:Om`P<~FP>% FTTam(U?/ u [%&aCa5t4K̓̉pw/NSÚA]g,qIO +J:DD[0~7 VGR : ~5eqolD}CW­6o0x3;p+kF1L 4RULŠ'Pp h؄;.X!J͟/G͔sڳrQ@B_u31/CG()yi 㩱Q2Ҷ+ 8W)I$װ'`~`^N_W"G4,O_ ta]ؼ_蛏@)6S*udxj1t@T`Px4_:NquUGfIY&N]X?~{ų gѪ%:O{I]nO"'Jn?-$}o-Q:XCB6/QV<}71h9$[Wvf;8\qr!j yMԹ*_eiم"2lR?_S~T)=ss&wU߮LKH}\42#XrgX_aR3`Sz) Kv8fj8n]TY%8dr1-`[OU/_UkbblYy R -.!%6,KXO1o| ?_=gg,˃͂TUf loǂ[P˼Bi 72^;:1M.R Q_YR%iZ3P&'h!UGgԣ ? cԢ-ӿiilj1HbJ#/j[R ͑?go)pWm:ceqW$C,OV4ȍj/:'kM4Cma18es՛Z K#g}ȮRh#b6ZW7'#pxf533W#aQS~~#=(>Z0M9h$a1f]֋5aB匯P)Ra.%zj`wGF/?z ;0ץR]9,f0T?vS7Rdü>βA cODŏ S=3W}Ɔ{gɹsP ߦ҈'/fW5"j5 5&ܞW;8گAT#.&L=%s jٴ;WB; V0|=hAƫ`PuS"ֽߎ2!:X:ɦf ,PPȑ!t< G :t,tM6jᓚ"F:\E[ee|wWx2DӲU߽Gކ efw(:9x&reXroR[Q![,&XwϞfϬƹG8sA[1oFr)em X/.6zMz FL1Mܨe@==T fuk@-@k<Ms uJHͤTT65Q7;``yG1VpUiWê:=.fsԓUABNjoV 1p\DD4Pn͆' 'zT+iQaQ/pL=v.u',g0+I;9ls%(iV4ΣcHwKq ^E|r|W{$l RAd4ï"S[~lݰ{),ooi8Wr*47?!шu߮RbUWvo߶)o~'uΑAR?6rtϬ&%(ㆼZW%}Vg>Em6{5FDxI}2;#iiqU`ܸye!x'cqRE_3"2n?CĕJ1Qpmf/ 5ٔk9V;4ŬM_Lp8ne?AwWF 7d#hU@^xѫ& $ CFexyR";/g?`"TiB>q @TN(YphlOL3S4|#/2ߧxԒ[c$6}]2D 6%#/ڠr}Mw<|$,ٹő]96wɑZa ut -e&pu$1 cm8+cC+> e21{E5na7hܕJe`u)4yڮYtS &]'٤<9@SwkLc;72HWЎ_Vbn55[AX+.&tDe0Ht{ꏤ`:bUzWZR%Y? !_jh?bdz`ݙOd kY 3I{uON]mb^#j5y$N~G(#`Y_NIl)a^yIk˞>K3no&{Scߧyj&Pu'jjU8V݈߳rvKDPK}e<{A/m%jݠI MRVaM HO] "4R"?`sܬLL8ң S 3+|D@Ѓ};7P[ڇe[{s%KD(Zb)1O0"g[uonUrX)ac_,sGࠨJ6P͟ݘ˧gi&=1X$ jcO/"qwJ9YD=V8Q{InBɒcQgr4h3=˘]-36,] Uəg`BQ  ةw'hh~T{ Tx!p.,9|Ì,p["63-?o7> ĻNzU 4lEs.̓cBSR }XaԻ`8-s㖠$w0Z<>կ;<**dv^4$ϻXIԲ $ +XXU"q| Ya_ qF 0Q.j3PI7LNMu3~Z٧銽;c5ݩh3i|Eyt1+iSa:]S#1jBbAuOƏ1 qZ7vTW(.6ȬZb-}$gmRZWu=@ñS%foD7I;S c._ϊZbpKZKҿQt"&c\VW/[ guN }\Q B`~dF!+Pm'-TaI bK 5_F'}sBYe^}s1eHBȶKS CcX+TR;رs]H_2^,{hXp7YO̅:Jȇ0uz7Qzxy Ur9̓" WE-:BsǪ[}/2/`%wۣ^_<-qD_+Y(<"v@[W&Zd O?0 6β|hNG(oRabZ <̘g:*h`F^e,`L^nы/@gxaP/hK@)9Y6>o:bM0pcwN&s锶KΨE9SmgveqΥP>E (gdݕ(54_|$i`%eqrAI/;+9KTlׯqȋ)0IʼWIԠ`- Tmr_x*ZZ"ȉο V$PxE "=936Lkl2n@C_9zH D7UZUHd?QROӦ-d Ik$[8<=jf, -qhhq$ bSCֹ@(!L^."](&ުu.К;p̪;Cf !c7B͡g.5|M-ZC22aH<M8ah8o\c2ο\ MS?ٹXsGȼÌ,oE3#+SϤ1},eVgfduA.^%3l>MښCދfa[Q,K'9Xr˩X6plhN(]~cWLKw#`*u\?C0MG c*B=W|T}‹KC4XRrq,>_xKEUV;_6iu欹Ue%ff!5yJtW?s.cDI k*T)$U DTu{0_7"ᦔSo HM\5ڐ>K%`L ?WGBc,Eo\1Y] +[ EQڻRm R0@"qu {Ej(BĊ8_[c]w{CS{Ò413vڽf9 ^{Ko((&"y8@)rwx_0d?IaX3vt! /u<!ݬ^9ْfDQS]Q"+@N44&k27{U镾>1\̱ f(:YKUc>ҏ~0_8dO Hqy7C ^.g8}EKQ>\>]Dz05f_\-SHjW"{')hvHCڴOIs5)8Yf+^EQ֛ۺ8(e 9Sf.L`a1Mn])2Օbcq\'QEs5$4 [Y6yݖI_oN6)eqiY? NS8@V-5v~թI,a,UMV u?Z|P~rnc ̐N֕u7Wb@_OqrnVGBCUOURT`/ 5buE@0nThJL/M`ܾ,t1VrQ?50*ͮQ@E`h]-fYFO|kAz~[ u&/r>TÖ 1݁7풯#句Y#XS- eגt+lNwg|O s;-eSI47Rd.64 Tl{A /(sTw6\=NbDA9-Xz9p|B6Ͼ4LS`b'ti=-E[%8cr j Xs7dc rƊ]:sJg A|HN9ᣆ &B'Ye1t;^QDZܐ{)l Zuo(r-ns i;0 )|#6-'<'*IкbO2 J)%u-S*$0Ў a &Z%N>%Df #0RU;<cK1Η-'6-3 9AXTSڰ4\ CɸɁ{cNb8բ4H#~r7ZV Vxz)ju_/Dr,P`Z\1lacb)ΝhzntL`OT`p+Szs@>Q$DDs^2j ~^2ӛӧLGͦ9хm1 S,? ǝ8Adnr6.y|ux@?E|?ugIfPcs:T@~KH鶝DrX"g3"s/.x$7)e#`Ţ<g)p樵ě ;ROrӷ5j)"?;vF\lx=_kh[w|+44OKԉ!#Q'=jK?d1Q~aЫvD4cF2FΙd@tT鳃i;Ri hҡ;8DlŰw ?@Bc#% vw=g#v_M?>z )Q8(' "ίpC"HY u~?هۄoeOȌ//g< N)/ cޒ`<^3ohL2x;~nn #<1Q`Q%y?pd $Ode[x8ixkМs4dhZR]у"CiM .+RtX{\f,Ѷ5fPe_$=d$h~UqWSKs#@#D!@8"E߼S`Teb͸a 2/ - C2`? )eK3dToֆvSɿDøLh͍ i]1ޤcQMo*$`D6$}b"exUy L0b0kT%&OhŁͅ鱕㙲|3>\lReD:hx6ǓI-ypJe-$%u^ 3[_Fjdtz#xߥu1ʖN_yy1f ː-i4; WEu܆6j!O3alB 8խXOde#~B8/o~ O,$LUc.KgU)0HZg2#,r? &5 >%d4z-VӰ4)̄p'I\@/F ;(FbRIVd 3W_OVlCBo֧NF爯kq^]]Ү[),T(=5=uyEPMdظHhkِhޢYEH ̦^KD~SI.(նK:Sa0m0F8!eNԵ, yd/]6 o>M;=xﵱefKvMGG8ʼo!cѱ);]3 ;u,)|/NEݎW7^4 3.' s 69h,$x;@t)3l29;I(C^qM0(.Sq "hB"RNZYżaIݛoxK| #s&>bDL41c!BWelZu*&` x[Y՗ 2PXn pHEsx 7)T&0StbK'`--M615$ 7zՙyu n+1WEqTOzqIb} dqp93MxHM6cs_1R4m6A~W@v3ۡMlj10SFo%[t?\v~fD KRTsiy?'!LZi0wVd^R\Mؿ{o)-}XCa0g%G,n>4AB`w)LJAPpD.QKIl&{SoUVt*ݠ3Fǐ(L%7YT3 J.7u|-Y}hבł" g6}Ĵhv'0Ȥuߵ  B%4O:fz1P v(AN j)EmENu˶'9MÉʛIpC M_eϵmNʔ TJ\rz}*PKk)7goD'D[V`j_g 0-ܶo5O|`sbpk4c.eI@fGnʏ8JJ&oLe}3Tl](xQI7]e'?UZye#5 S1⢽UORo qY*ohge@Rw Tξ~O њ^ z waJKEPL, 6"P=6,TW 7yIli誚7/sX 3 LϕBh D|4.TH _+ '<{ӧM:>P&VEי+3vlQN mmWH{L حpvm SzG0 ƎU8?25s$JsUm>E2*wSMmy5s*:A hn{KD׾ĵ Kell ߻fSWr/OHRuU,rl@?ٰA} yKOъ:6Pd)/&/v/1!![pQv8mtegA=yRM3z3kJC@VP.]([jm{_XM I,@d1ыn(\1dS1M :Tw![b'}KCd& /46yb <{OfM8Aw(t-K-~g!q7=x%rԮX\U_[|KjQNq7)H̅[%pG_wF[8aG$ͥu:S( r7ۣ}7s_@c o\Cz McYD&ng pPn`Ed!0J $h`9M@bvǙʖe#܈6譭*`A(囐b)48,yi?pz" .V2&DtU 0}>㒋SZ]C͒",=ʴ‚P7fn *{\cęM͋841!Nd"tWA鄫τQ9Ƿ~a;q2@'ym`#+|hDl܃b;$=2R% V6Jb{P#Xz<2Ќ¼c{9ۭ;7*1pyVPGOYzMŹ4i䣯JSBL&Oe7or>>PfH~]Y`3 _Nҵ8~G#CMv0y.h9xzF?L>RټqӸP+I˧%dC]Ue|hY!=1,\:Db^V'8m(:9} {#m޼`Mw]pzMo3趙F-nYD)h@W%ѝ$ lW<~(jVQdٳV!ٳȋE8lJCR7<~+o\mW N)= U"+&4% _f7ckZ#  pcaIWFsoѽ)UBd^pkiZh-?hH۳+'x2:tRj{N(o$yiaS_fae=QˮCVVtcUr2p_(?v-ष$ _>T7),e؂RUo#Da>EOOWΆ/\&~^\eK& s Rgծ q7sH-UC/FIpYm<N/%!6TgNJL(KK7[&"C6?K2QﯘrT5Gy ƵkH+) Gkgn3Z)N Kؾնj`乷4WBpqZ{B51\: /:#m;N˪GKP&SUtg-{PBαp\M|0 =qMwL\ɴZx D /M\E͊i yJ>#z!rxFMu2yA5}t6>Z@ {]W;_UUuqkx+KRDGI[Y#hU6i}s8l ^ʐè}_{+~!3-Կ7OP47WUwP4";,l1=Y0cی_ϓ̋WoC01%'b5]81NSW~a(MQ]Wh&"W\^BenvG #%{RGhuI=n'- 3=𩒤, '@X%[۬.[0UZRYе׏X_Nƪ.DmrZ_.ͽ=K gtvH">%eg}C~o֓M)@75Sg;۠ͻjRq 95|[v]&{?* QNEk/ܽlή/ L}*e< $VP ;a(=SzLng5a L<>@O̿AjN*RVO:g9ѩX#m!j2Me(tx{?7y%_y ǔrE%6V5ӭgue,D"̈Rƨ#5o9p ,.(ЋZ3UdΛWc%F.et#nںSR-D _ho]ѧP(f chf7?{|NCG-DCu&,sɉ(Œ1{)_iw"D4d}}&!Eza[vd3.˃GPzC+uj>@h-A@!#% :E'8\/?S!AXaU}3&&Hz Ru(?+4`3ro*Zk`<`]ٕلW"vC fV6brۃu|^.uD\l\`;A[ VQݪj֒ ,ݝxRˁQ[v]&{BajDN+ߊ\7 uk6Clǔ^.ܚM`ݕ!]xp!36' '0[Y~U {ShX>69<OAư&.Sɶ xWb_ov\eF/Šy9b<;y(zܓn`1?Kbϗ\Fjqȃ%{:VG< GJ#I;; 94{ =O,ʖt=#qKz]\ 0 d, gLfKb ~SO^g(Y@Y۱s.J*: ql d"e&>'2m =RWп2)\ |h{@tm-9>f䘗%pmS t8 j1%!. b{{G袞a?oR.['ڔ]oP{2d u:8o*g&_6Sɔ߹(Hq(Y/a\>DS`4Bdulam4pa_- /Yad77h(ɋ#ZC ,m$$B#-(@밶V"ZL?TPk+yDd|Uq YĜ,; i?+W!r8/AĽ[2\b Њ]7GrX聒hx4 ù`MN(ʔ5[Rvo 9q'jdΨ3[pgv^AJWAɰҫ U]e}O]}1hRx_hqzC[*`^R'$wBnQY!RuUZ2h;y&sߌSq܉ .Mĕ,]j~|}ƺ*&곬u#gB-&j7p4fl#nzqYË|0} \7utk=\*[f).=կAlb7Vh7LQ{Bghx x%DW2x?019~X)OUfǴB:}1/^cI4a%v;O;2+T!+/H1UmMU(BEd Rm6`۳27nu.E|̃nfFn%X8JӤy] o%8^Ɇs82`32_-CsEp#1*X͵%q 1OpF@_|$9e*UDpQKp"E߾ + $ ^nG !x._5kkR=f/aYb5aJ^Q hD"h* c5gbm2L [Fց?(^tOCRBؘ&TѳX;{g;Ft^RnWn䌉3]vz ĊjJVH*UrLa ˌMdL7:@%@KGsp ţ}:;|=צ2XPjԍQ|NHlҫy>(@uC"*$3N/ d]v?ͮ=}>ܱ?GL *>Wu'~"GbT˛j@K #ַvɌ( d$j\7z`'Kbʚ!:Cw t=E;BKqtAJ[Vs5o o, ⲧ/*g-0__k,җ~.yЯ"q8 :Tzr)52=E9Oj P /{-?^WyY?^g{MMUXi/T?Wlf :;L'#4Vb С;Av(F\M xDUqcFLqt#Uj^fq+/ IӍ; ' 6{ڄBt fyޝt1E I1H0KeՔX%?F~-RZ%HvpJݿyʆ௘AO^t^1Ә Ƀn'5)zW?3ߞ r'\Zeeo^D+X1_:%2\2 r3MZP~ddZb)!xkכgO[>OپYϚB,tC%Q 8r#4~ cI10܋K&&RVD+Tˆ c'yڶ& (aW;ϼc/QF6l@68-1tDAK+{߮|Hcǰ}OU^9-s]zfgi7NEAT=ųQUN՟ Ż_)_3yP ggjR &N *_`,@!=GJ9 `/WxBFlXXu6HضOŃ>"lly+I,Fs{^ zFU;_ڃי_܈[pe@]Sm\Jߞw߾W" ̌sW#Mu):dP:|K6yoX$2ߖt݋Qmaq ŧlߛ$_<}oѴN8y5ITDYie&0C/| We*:p̋? y^T4LuJcys"pKyQAb!z ad+G6=mmc_IONQ%>rpO;266YwtXsr*ͼey}٦P)?T54Ȯq-+O*w|KlK7ZB3L#? ˑA+_ YF*pw,\jCL Ѹ.:*#\T`P >oWw V {hjRAĴ*&9H?f}["\Е. [ IUҧ}wcUEFmr=Y7K/iݤGJ%WDG5)L%؁M N .IU! A`u!cm SF#(Sq&O.6ZaX$=b]@,`BJt 6NM뷨^Q+́(Eqأ=kDPD=--/<W1w)JC2<ǏSՕP~#3;O~gZppr, -Zvȭ13EQƔ^I B@KqK e;`}fݳ!W[+n@@k)w]l.[\4TƎ<6׶׵%L*xi1 4eY ѯ$83~42|j RTi[j!c8Zשc|!OnF:J#ʓyF_AICA2d!=I.2asL(o3,L_PzB-giqYnSgr]7O3npIS|CdNt|w-kx~9ȍ pb)8oѲi-f948NrإJa֠IҰI]hZt'`?Yx!N1t%(33DIu?p"}iX@N==-Apv!=| MIꟸPťQ4{fOz4?WԲ'ya4lFJ-uH}$+_8V2hO~78yAЦ:b H m5j6B-nVQ htwR%"ʺlxɿ=WbջFF۰ $t#W!*Pek=8ωsnE eCsc+,֌VOڪns8x[h Չt[*bN9X/T$E<8´!wm-%N)ѩ!n{?vb<3!uJ#n"ui*jw}X#!6nQcb)~Tÿp)gi-Ja^"BI_~ew_RR2z^Ȧ)$b{VMK EpSߖ-!{MfS52;ʨv6ws\jVEC}UF]q&үY.-t =p ]Uנ8Xu h^'9mRۊ3_+ KfџoY G=_6[̮<+ LD.}dOu1w(kOaJeOk#,"_{gk?]C'^X;+L 2\"w [Š[ʒ93n<c8b)k ԴI֒veU &cȎI| 7ȿkR]Uzǖ qɍv~ !*DFa I OZ퍿 )u(&`GpOm)C>kMPto,S BeP&cD3\NJ+?QϭSUz|CUk ND`P3B%EFǭuv/ָ֟$K)$A% ;r֓VXQЏsy-&I)hxdV_E Q,\45\N ^$k )dU,>̻> v_ 7ujG$v`d6ؑv}]`WƳT`(6><ŋЋ:QS>R˨^Uw§~Q˅@>VxXk$!G%AcdEQwX;Ȗ̼";pR3v~#ݼd8_\P F6{9Rt7 T_r#nELJ\)-\9֜7s\Sv׃$/5L5oBᆌF8/ݎ7re"ٓO0Swz)q_ 6-҅ɹ֍x޴^2iu2 ;ڠ J]#ŃԹKF.%m|0bDY툕`/dQ-'@aZ!d9+d‡ +iXc-r#ĨVAmjjŃOjl~MYw/}w-5~}!;ٟ% $nvg #aLpXݣL#sFd"fT#7#F(1!Q;iWe5Y:sCVLTfɘi['앞 QN.ec[- sndGW [E;FvV뿲00.&Hr޹A+L; 7%s/sg)v9Eo) 2̜ྒ&sH!,s'Owp _6Mv#I5G&YxHRQ gR~\-V[R^O;1PQO_X7 @W9$)}m +ZZS(F90GP9 qbsDWt|"6NCb~y ;H vǿXIG_[,9 $$,04&!Hu︅Iߐ`Q[&cT[n[J6~!9SQuNqVZ3:߿'a.>| ?Mtp$Yv&G.یpǯ۠/^:.#l|Jg>3a7:/-%db}0(v@C<\wLXiMjȢNFY%w]mA*w/G xgI Jd^I hؔ+')R o~+68&F4ȭ䂢|TD|ZmVdk'Ζ,g_ˊ ]jG4D "R6QFC%ԯQ'WcF/n nz##OɎv?۸W,J%·w::?~Dl.P"./ PR,tC]T}6r,C^HCқJ8et3mzd'Oe"<" TLhf SSボ& mf󦊬P ֊V^ :ΚF0!Q nVoT:e(rz}RȨw{^2뜒lI҉! ݥ;DϾ [3aކg$qe#w @?* SV˃l׈;2ʮNOC!`"3M61D2A?kS' p0<ϿC(N_`>"Xpsj /SO Fyݽ}~kO9Fq3ٍ.G۰uI妭E=MYsQiztzяb;WT|He58cINGw@`~J lrM:va2H}iݽuw_ Hxj >QRޘ`ڢjZZūPmY:pPRO/r$|3ڭGۣAn#;V$Sr y:wrlX)~Yeׂ@ܟIqU܁S8R\Q/_b3覿Qr|ϵb偛zUV`0a&_Vr1QvbZx+(Nq;z4'.Z"/6 N o ,(5xeO!ul_CiT˶u XڽBsA}q=7R%l m,nzc߁S-J1bz4V<(2w$[W *W5l./}7"Â'68Ԥ/~/`^]# %A=/&T--$S-I~օg;CƉ hܨ|R\:YFH׉>=!g0ᚦd O!g |nfyqdjlr~ٞ#TS͗ TC4Tќ5?"%`Ë3Rb~K,xbD:wv윣PMٹ%h:ei8o6ŖW[ ³QhHқAԻ[O8 +K\G\Cg 8T!&8) `xXqǓTQ=߶',Nʚ=RA4gΞDIӐ>X2|szg0U{eOqC eմ]um7ڴ*>q؝4*qA'hthP+s~ \v UQdH+&&[-F;LCgeFO&- /i[2aݳsY&RܙߞƤkzi=rz:?/J16Ire*ӏEDvup[0 V2Fo_="Skp}˥%*[7GK1p:R[b~*2ؖҝž4}x&"`WP_=zXZ*K,]8|KPL9v?@eINeDD0}շ ~* AZQ0gyt*l'.qB)uZyVe, JVU$f&A^u>yL3K`J*| _JgP7gF ?LV]v*HCM%b y-f8, ;s6F|ۣG cmA@/;YʓKIfFw,nK &."I'Wփـ'#ىa&B])R=  VYro׭CL>EVP1QT7?8DXx}sWmAw3oh}Aۭi1֦%̺{ b.}퓚˖v3rb'b.u/Bkea}x6^NGlۧy/]TB׾2#Z:(/0X]PNi)5r̤eN}Vi ]m1,mWr]+\I'6l@-B@$>ͨDΒ<8:8];/O;D׷sWX^bWvr7Wk&U@"dM.n7g$@v ``ϻM eﲉjcDX BfC8e2p#|6,fyKr&4OI<_AEI^Jj$mL<ܦJr2PjN9};0T#ha򅁰^ {9']йHq6\C 8r8dy>5ɾc5^W)^¬V)Y 5W#Ա}Y/!ÝݻS/.7x]x^$XR oo0MD&)Gګ+U?QL S5a=pRJo8;D!)~}'HkmTϿ ;2c)nߠ.qWmX0…7 TEqGyWN?X{G6K\`ޒhJh*wo)^06 nGk~DgK;:}GbzvD 3WR.Z ?_A,hh\n=е哟Z8NTR1yw9<~pZEnU#mM.l/=N~`OL$fL2v2I'g!tmĨZkdxwJFi20xʙPWna([E`jQ(2'8&ԋű' 8u"P( xЕeYa_cyfR IyK0&ua'_O9 \  12'>-*WI?:Q\jKIh^-<17POjٷ;; Asq?cpj0^@'W_ `kNˍ?Kc MʑbKkƚ?U+3>؁l{JX߸pEw9%hNg W4RaH\]Vv DDͬR]g3ۅ,㕋p7tUwx=enJa)g[/쀃^HxׯL#nTlY~^*东 x4:ZQHPOsbwJ ΦIɐx>_ I O 9,x}i@{(*&jKVS(dJwsN#PV4tu~(rWlsY)"C}Y2p \ R1$| b7S0?-l?7ҥ2)n{?N JN>M0pʣ*]y+$tGMUH,>a|)~׌ MW Ս[/0gwjdjgopnY( ԽEh&]t#pj2EBy\b娱}v9ĚѻNE ~(@!&)\gf ZE[lz-w_Jv.J˖(m ubBMA4~YR\1fv$N^U 'nw\c48f8hxWiJgvP6{E |^GJ?w[$~߲Tw؂}YX3Ne)Dzecnr[bg@1xEZǛ!@ >}J&h72${`آc2q¹kBIitT&)x8t#6=!J%(@?uT8MKn"TceI@ӂxeU_IU <-,''x0{0Y)#`Aq~R8sbHo^h[XxT`ݝ?AQI+EM3^4"neiY uϺ73=Ⱥ.֭}R=7BQT춄ylV,G8qՑ!ӎ-X! sX3`Si[|.6'I~| ɡGEaȸ;AAcJ:M>꣮ S9@$/CJ 2A )1Ġ@b)'ϵ1Ҹ2rsOzYdc۸]$KhbHWbW.Q^!Ts`'COTAAD R_ѝwFFx ]H1KBoqPIg|#7g~ojH ZC~<.4cQr/K{w];0v' ͜ 0*F 鵷[7bbk^ 6&RTf92]&Ÿg֜WOW,Kоexffkb<ᨧ:%-q&5' ),:k6eu ŋWEa5Shy 1Tpr7+KJLu>Lso]3@s$86wz0|W@|aFךrdYW6EiA5aRJ4\EޯljC=ڔIIh'Vщ$c b€ QM""b%ԵwpWJ^qs]0ڜRjxrhWt1Z[m־eYWٱf0zɖ/}N^r~u ]J)0d7P(%E3PaQTPgA.nјEAOٮ7&lb fד)w #U>چ:~L p2Yqb¥N+<^Sth; ڰF@I(<y|\3[{7`#v3֩40D1aZ5Q9;DO6=C#aN)8ώ-[2)=/+M±\dG_,^V!h +cfفnT|1P;l%EIӒ/w,n8i"[њ܂==qeHv=)E`Ѵ`F3 ~۰OyzJ ]u=Ni(^6/ L{^eu˼f[0UƐ1tIH5ew`miQiꛉ)?5vhC #HغRroPu;3JM0sO=W‘Gvb;T_UϷJ|s6 V X6ݸ/xӫ.P4WF>Ozɲj oSdMk> ț,j&pÍc&H"]q2dzi Lt*/YK/tslr7Gj9^f}5˜2-Mhm3NeZȵ$Fwxm¿ܸ죽 %ksֺjW6h=C).JrU`(#6(^MUdem!bO380 /,֥#ťMDgQ('Am.Tw)YUx 8ٶQa*G'A:q}' m<ױG{JaF|39N+'Z`Htfw -|ofpCoyF Z=06)p")A]1t-CH`Jrg%%mj)Q *]A tཌྷVvm;g5PX8cMRj{]kd>gkl{x$K9$΄2|OHM 2WJ A[_1_JI%Qp<|8<5 w}Q\!hdWK%c.z^ s-4ޖ&כGiE@B2{8^y) #J"Ɲ6W@ܸ\?ζXnc:xwwU)j&_ 'P:@ʶ}$P/N`g (%9zM@BPJ{Fae0J^?`Xz ,z+Q6'D3(e`VY,747hլV/ݚ&wOQA٨e?醒MTH;mU4f᜔TSAbm0J3]19c,T$+%79pg^_ V/^$wx3ߺ0IJI;Ӻ ǰPTk; gPmKEԟԥjz]dW:,-wм-9tKEFCIhىT#9䊚!|y,KP1?̜ 5MbzEH?_93 O">kXH72mC@!y[t[+g0哀@zeY3'%Ɓ7𰓢HD۸ػc۝0h:Y*v7RQN'%bx~ޯErC@\ <}i[Vdb<)"]m/x<'皜̂A洩 >XRq,jk0ggnOZaD 2SB&ThK.obQ^D=V#Zmv`s~ZWyy C S=Fo q1D G_J(1~a%E=UN).UG4u*P7|Z YR8ӋPFՔ SSܵuxۉkA)˜-jn!W5`K| ϾT.oZ:QGG?ӔjBn_D gǦ#VJBc;0Njbc=orֲr"yӈ?W}^0?%~/Y e9ȜC[lV47Z \ 3nc1؆|nxo[Cu"/LSDl n"EK"3>X5:4Zbx@/Y26{~鲾!5r 8̴z#216/=B±aVԆsSI;u9JZA{>IB7g]bA+t2Puwͨ*, 3Uzbj7=ԫ!D=WWyZ ة%Rpjxgˆ?1RX ]xa!(rsIƿ;Q@RfˋS:lUafQVi}%<<)E;iK@'\tbHljޓkx )%C̬LXcӔ#^M dnT{|ao{~(3#e5=YZKYهPԤls1uL'3; IVz;+45˜^Z,"j1Zf-neyZtJK>T(̞ʴ3&z%-* ~ozy/yn lCXV|NIw32VJ8nq!{5je'_[,'벊֋a:99_fj0\N&Q0b( ;3Wiuqc5Xxk5$j`Ċ2~z;Fq0Չd |eLHչ*Ne4)L \.1W:,-wм/Gg`]D?*A:7ozxkZy+o?zg̕'r$E2ZOh/*XTI9;Wc+,_)kHsh#|{C6<$42QԴ x5:hx w=Dge@ߒ ^$YW<YX .eNz*:fAm=$tS^e?0hkI'nAJ+6S:F R~IE|p$*]a16h-1jtIUH.dI] kiǶʞ]1ZA_gZ飀.*5&7:H(1 7p.T:*e_tSNOK3͚꭬970jgDSMyŻo:L͗=eݍx|֠kp*vQvGjZFґaO^yVUiOÃsx( SHflaˡPJlTQϡ&Nz5RVjpk*Npoo .t|ɘ,|M~Y2%*WtN.Bˇx|UGJPknbgWп5t30k֌3{Z9VY)RKX:-amb}E됆x֏HKv&KJ&jIo]TZޒZsgf>7?^K W_ Q)ϻ_b {WSM47X8ax)u33--ls(ae,R)DYcgz'}͟{eKσ0,Q#}vEmZM`I}\x!StRِ&5C-Y#=]:%^'Aw J`0N}YrvO~6roD#N7]s)bQHœ0qw(pA$D ̤2YexU0p ٶͳqvwfOa>xhVBXoUP*bX%)l)gWWSV&sBO?)0G'||CplW6`kG2".8iA#_rҕ2; Y{comի?P%q+Hm&l1ҰNo=8k(q2M|@F O')M6Ũα!6]fON{(F#gK7Usd\1{5}[*Ȍ?ĥÆ Y7s*R =xkSO8.ي i &{N1W~wdW"FN7#p,9. >% #)(3~KgHXrcOM HOR[D". /x5` OZSUh%U7ovb2Or?cJ[wK~Cp`hwogu%gMCekCMaaԌ%ƈUXvI^M.[Z&gKlDE24)2qK묯=R 4G@ߢbyO`e=,,U[dOaW>߼P, -%vz\ɞ ؟otφaPi֜^F\|=Dك`Ef˫{ ئHz/5z͢9 m;\;czhĆ$YCA lglG (6h}> G4`Xi@|`taTIEGB]Agb </b$?jOrԨc}WM%6xps%iDni69#Gʥ?AKI.x=.͘Ȩa;F*_GG6 ӌhTBoh6$ȩt<#mN>wI!ᯧG*f()#s7T@'\d}~X33vlQ Sw?t?z:7Lt*W#՘u nyd9E♈aZXEx+7AFn{?z9yd@4ehFfY܆{~ڵ^4N!/eadvLMI,|qJMغq*/g A8W4` R7GOdl?0}v3oU!kʬz.7C}FwoZ)*D$ ʨZ\WCԶ.a&[nkzV%޺+m گ.]ڞ'b6* ?;ϣ\o)d+*k(;7%èvQ] 5!bbrīȩ>OdDzrwvDWAmIw^gf~P{KPh'C+h7>tHv!r垵kcnZ6G \ߚ9é+7R5Lt(I7/x)rWjH{ KY\bMF9Kv½ ={:z&J]Hr#ᨸ\W8~JʄXL5#q*dؚ:VF߻N4sZOЂbYJݮ noCp_oSk׷f\q9-⟍t2l+WQ~=u]ﳎr!Fl/F`![{ĦZ`toQqeI2#T7ou<}a4. &cN^aO J^m:}XYۊg#?cQ~Vⱀ:dKw/ S_y{G?% &C=':;xq3&L 4/tN$^uL㹥5%Vد>=&NUg616jȖ|٠EBE%zVip[|"Aɒ{%RPDUSWϦ^p%[Z酄nη@6iBjIY;WF1@Ixuh8ia*6(Bfh>4flIfZY} fu+$[a=;0s_䀃Z- SfRܗt壤[ͣyfѩ=]yge l;`n2N@ZieUTTK^l1=8Yy{k6y &ěTʄW'dׁٿ)6^εx\۸EUW}+0a őG:t2˺bziS HGA0)~-컗1=q/V2bFlwz1["M#t8::y+Tʰ擭:륊V O {\TvSbϬ;"-x_݌.YD/D 8,g$7|'"lc.DyeFŔ'AN㍈m(0HP=KlһApZxdΒI?S'}o9cCc%n؎s㈬]T1;APgHALF%f|[0/qk2/>h X7BϮ'?~tهiQU0?V R8y!?KH6މ;@%D{,,Y̨88HyK CrBڦ]s(#өTp-`E\!$Pt晭@@d?uRSƷ@8$kĖmr-sFB9 6Zz&\NmQidmjU`o>uK?pOT."2d 3D.f)TyQ;D.ÓI>tu&*b>mO`2`hd5bQњWv]L957:XHv}}C@~AWFo,4c(!̫фQ/"EȒ1UMzOM>qE6؊vJ<=C<{1& zU'΂13. VJz 4!E4f9PܘMYԮ?`<Ttb[TF Q0+\74 T]9PDՇ9z,"IQijZj F IMQC" (N[U9)o7Kg>@X͉xB(!9gcsHv:q=`uԠg>ZdPh`-q5L K/[kb0V9iX7xp^/-N n'e`ѵ<,~ yJ=AR'ЙEO(cX.vǷ`OQ'zB1dQ69%\_] c 㚵n7bf3#2Ns{}fwWv)#F ~Ͳ\h5o.c,+"f\4JD83/ЧmGuN@qBR{:=fR5Eo^kKjo4~c&L*hpY7t> .Mmga|؈2 T{utw+a^yV ӉZZ!lyFtDI6~}-~Z@\ա|D4Xui&K_.]G]SGctKe d3e[A Ir]Uu&v* ǎ*t^pgGlΥdGCQT7>/y +$J2I5+(7i (1%Mn 19틕ma ;*$7$E|A&Mh{FQD19gc#4A1U8\$D-kTi'W,`D 4yOXYU@m%ٟ4CtݰjK(I=GJyY$!,O-{WIg@ -{F=ayl/4ov"q{%έ-@St5 fW @E#'ENgj ۼ[)J'}GAoެ~<3sgyKLwsj%'b0( ـYں[/.|rD*4QC2 7Kma2ʇ03grynYhe jxFBuƽU# R/Qg[@.%pAwM͹𚒞 \I?pnԫA@.`bBV3i&rF8,K(9WܖEtWC`h` bo-,ްT_D$laKPdV~DiTMKR*.Rt]s2\i_E*:c(LuC7QE6}aEq'lkVbgCvUT z@^0I`sy{_%*m3Gji5<)\ u8+?C_jK:)tXޝ9;J+dxWb0,2QfIUF[)[Յ*PU>a-B?gVmw,;& 1ri` & oPgtKGE\ybx@f{ɂGqkMiB;bњiѮ@q/^ :VN0NO\fmpaj|ŻIBEcBIxWj@+vCniS}Q3NKhZ#HihQڞ_ EtOXN?('εS"$¥4.Pѩf@61MѠR׭\@vꖨd|2Vh;ZH&Od:^˼s? _9\ NK5#I3 ϮW -ؙru)̹u)Xgf_KnI(ovOǤB1gu>u?lr hh)|bpY8W%֊1\qP th'w<(!n:y! #~XWh/VGP*xQICyix <£ ngytkԹG&YEROF$V^$) TpIk0Xc͒Lul Ɉ=G<=9*0D&<£%܆asOޚeXlv@ o +G^G8hfq܋G_fMA·u'`ebhp wUcE4;iU&;IV&}K)+eB" !ǧ}QRu<.| l1 olDn>1kV&޹Jwv`jI0uUQ05M,o2[ͻ#6NlNc: qM(up]RQ >י\tNھ@H-KXYvdP)/؂+]މO{n濱U:vo}ЬNNT&RtW^[C}_.9",;qV ,b6c:n( a.^dkoR4Y|*8 َ@ n.~B[er3JEErȉ&B6ʪ8~Y!WK.{(OlbRq _~-2 .qpcJC$g}%#c~{)=M I{ HVz06D:&`D[8^xa~㫴7aJ2{*Df-0JoLH nZtvGG\$iޯ|<ŴW>m䌲= CXS~f@h&B uNGaO,~{Ԭ}`d9wRF?:te[ޖ^  Oġr'9IN<~*,}{B#PיeTBgb%#5T#o# ~ȽI^CYݬWa$T0jdc&AaZCcMƲ_{VǜBbǩ<( []_Yg\Z6Ѕa-I¶Z,8|S zHS,ebM6u ڷ$y3zU?:L{>TVRn5aڶaʇ/87^/܊nr!B+bЁdweJ(ηezk!嬓$L1 !+ =U+Bq9 t ;& F(s[B VeZ^sDI$k#8#B_6xeL@CjuV HF &5Gd*O0L4iBĀ#<1+pΩ "eH9gGJrTcGlSofazP{ۈ,|кi>w^Z߈RHz|*.F?)B'nV֣: 둛 sxZBT{Vmjʣ%j;DGwba);`s__VZރK5+kvWV#NrmO*x0J,;,Ds4_&:tt92ЎXvbxOgdV7炍g| W+YS=> M~2ξSxT+ߗ'U@ӕ"$af%S!u!0,^%Ҹ>(-L  A>1.t;}#AŒ%+Ԝ‚&R?ц-4U(% 4pk'$d5d_eS7!yWC]^:gnqzJuhrg}??8Z Kz]֯X]ὢGď.9S,xUF;MV-dH) av{yKJثTiݮrPU|}fHcDE˰-WvBAȨ58ҹ1^dy95ub6q5QS}ٰ?48 18+dCPnkJC\ͧuzd0b ` 7{葋Z/$Qiw{8I%yѱc[e֗X:]{GOn!hlq|;ǚ3N]M,@[dZ)ȴkߦGsc4%?ttW/h/kޚ`ۄ@59 L YQݏYӚu#/eʝO8uK"k LN}/yL`k @v[ NX:$ 1xUp~]!  z-v0c=:vrǏqoQ h&9N_1rFr7n|pjPT(u $ɓ fK4K=&ig<Rf3Fb?ܖ2>I02ڃcE (BNc:O&_ .fAt+qha\&J, d5r BWPkIR' Mvv&|hq5d׏V\UrKEkg[u7 @Eb^9yoR%bsPo6*]`j= ܏́]~4y@|M;sb^(=ॹ呺s{v2<Mfhy̓>A\+]yiu`ӑVGm*,f0 ze"jHOt[zl C1S [&(GL_H~Ԫ0^~Z~k]M1$-)̩b2VBԀn|AQZ/+ 7xCӷ{hy *)ET,sFyX~]8͸0 OZ pźɴ%P /7A`uDh N2СjKQR; 2e,y!3cΘ(o0ː_- x8G-t@Dl.U:l\<$EܟRi*fBcZc8ҸPV 'I~GʻyD;4戙Kǀ#1ECBuRLp8 F#X7T.:!@V%$rpH^\`FDxLցlN7.*ݟYzWNʤA[g ez(W3ne|;HGۍS%(z@",Hj㠜F~#+<HR7~W@(,dʏ\ޒ-r)M^ez+׷I~/ycGU /?t-k&s66o;Hn+,DߴVD8]V|̀vx'hBt|9djL^Yu—nɠO0,ֿ3G׬Ljq]R=bd J Urc^ Ŭ dNl4Y4'0޸#ԞhUߦ1<]a -&$a!'tp+YijR{;nՠwZQف"^D঻P I).3Q;DdI_B'}zU\J'L4OnO_! q1'k9'l8^CeAmHU63:|OjHD9aTܖ҃ZڔxY!dgU.Qn 4_Q'85{V73>Τ,st}=[ ,]Ts|td#ɐkWF$1?ϡJ} >jSHutю ig|[Urojt٤/WU b4="ZuȼҨ ,'&I~.ߴgۉBڀ6o=:RrQ-76JPC o yQ fcͲLĕnǠ@^ؼ?n<QJ3?`Ԃ L,u ݑJwR9xBMANA@PϴV _QS.I{s $OX2SRSvNMj}oU |M6Yg/N(uіvak*uq'xAu2P5P=OD3W(dٔ=tu[_a̋#f*zbVWGVk ?Z,z>DۖÝ_E*lb9)%rNmH)To|zcx:M*cdHYH6R,\:_l[)ď,A,A@v+E`O{Rk1|]'n`F0;(7ϰCKEyTh\w8><>c`68/ 0Ј ~2 1]$\2fiY;Kr<*DNh6}TeϪ/IN"5p%pIH]Z /rtjNlʐDrGlvB~:-;}ſG%}vVd+I_ m;?+b*yқߪt ؍ ^\)$ȋ< OQ4v$S"Pp /|b`V΍s8wͩY46fe#TP㊵/P8߻ wW dՓ`]?PeC̀2&6.>ݯO9B $JÿmRAJ|<CeWjniCTKi~ۚic}YA xX@q#NNwVO24f14j'ÿkN[Y"u :heb"ߦԹ'5oCPiS/@C-^.6'@˞QCO_`D1zky:ù`p \UcJ,vGhGX˨INAklˬպ!Klq KgqPQyxStj׀{۝^$AlIqQW M&:'RHm. tZo 1zGN'ĵ?\fL;l:A+F{+.>WvM%4.ek y'eTG,>Rae2l&㣺6/kgǺ@+n<.~@*XohO8{ 'XO X Nϳ;TiDڭJpi9Yxe{9,IV36t\AV9/L/T+ؾ4X_^>,]Gh¿N <_Je89YMMi\3VIV x\S 0%7xG3m"#N= eV8_?577[z>ʛrtߎw[4bqknK-DϚ$NY q}&0o& 5~gV,o5Iew0Kb$̕ >FWq }F"#uG膍}شiNo`EQJ$%Ⱦc״H%9sgm{3:\P5B`?hMkR>/QiLɐ&u)-m Ѭ($XKI Ш$ ;%34wf0ϠMeNS/J% OI8,Ү` )ߝ%(\@tKVI 6`1)H#G$FTO裗q1ȬURLsa@xi>@0c{eSn,NnZ_4 tH.eYqҟȇV}#YG+Ab~+zOk4 (;E_hO #q8WՄ.o+nNg0ԜE89z c~C>85ncJқ"=!GVmfRz٭[zx:fWQ] uOU~# m[@"6)k9Qh[2`ژf"Lint/q6Sc۸M$&X[԰RAB"6ܕA)zφ^ elDC+{L3 fs+-GGN7A˾8MǾJ9Ng]9U6D. q/Gi6CAJDBǣ]Ryz "gnJ_'\A˽=M}2b#z@ck.Z5rS kSZ ȤT]}܀oCy(8ݣx1?/{LA]Yaοɀ@~QGAe[%EZ3tAf**u:xn'0Ǎ6ugi=i?B :e|H%Ω\wM ~]8(#^wZ4f1 @J$>4(,_BT85D{>$:_o"RyEC+As,Dˏ|~y푂Y55%=P~b&WkC#A4~O;av+CY]Y,ρK\g}"The6rfD#UpdV5I\؃ᐓ>0o ,K {"1Q=֍-繿KC#y3RMgG鲬7:PMim?Q4͈uPɓi[H{;Kڤ>fPz ngx)?قR8 7OK:,su'AǗGvAf] viQyu46U"muB`\_-sQdը؃/(IkIdzNƥnOjb%l=bMu *T<d4 a#rjLegWq`@&Q*.їF Y>FظJ}nj 0k;H-i/% ʖ l*6˟0%bMnpu@4h~[@Q&~v%n ePѐ}ڟشrͦÏKUݢ.ihofpz,,55\jk{~IJ6c|bITҗ~0IwA`M@K(˒dښ o!.zΞ8 -PCueFcx܆{n4[)L5t4fʲ!5wi4φ+oۗ D]>ct.*D8T\֧ WkpCڵ,TX 0lȗ[ܭHwoD$QH)rA~hg~Rb{<Ƿ+z]u{SRxHA-zQ?(c=SDpJ7%=q"P!/ ۉZ]}50yW VKpXvrȃv`hgL҇G{-djħi,dc%-r$:ޠBc"ʃyS؜uP"ɲd3EIPF՘ԕ!9X<+$L\F2]B}P$s| |.o:vF!y #hµOYebэ$Py,=ٟ!kk{a԰nCś2h=]N +yK@/16K̕7 *)k4BH?VXΊ$ҧ>:EYBG Q)TuG^Oh &](1tbTz`2 W)zKGK2[5{n޽Ļax K]Ѫ6`=dbNnCQr+5!ȁ w&}>h^˨(*L1,uj1|bɉ)'%\Vd.yϝ@n0#3rVC*V>R~5Vˈ(W[\f'k\DŽ5#WlA8̀[AU0 V41S7՝v,[qyp ޖ:QPE{8rlo8D̀۷-m XB lR\P!:H~Izfu9 ς^"-Y¤ Wq&ݟff{@P?GuWW<CV~/jnTxK$P 뽵ʼnmx œwH4nne5K99[+-Lh[,Ev\z+5]z2X".)M-e ȢUtaŭ6-75ے3-1`H} [64^{zc[{`x,읃پݴ T$ X}WF G+`嚁CNZv^GO(v{(XDú}m+0K%upbc~$N&T {L ݄5J~]LҦ%}Z4#ʰ) L5lҸ">m1R<4NͧnP\_wIUWFTCYpS;)~*Ϲb?EuYMxPӛ RJoA#Čm&14&Fa"SC1`˝%g^Hn,:KTMJɦLGzS.k3SEާeD඼kq62{}̼ƌ؟KWx,w)e@-͸p" / @"g<n?@okq;"/GT?AeJ~޺mDԩi8i cY:i?ŽUյw^t^qK͏ٖۡyF pr/6K,;Lo!i3JmtfJg@$ GBUS";.!]5u,NIb?@ w۔(h軖r'ߋaR],BlemGCjAg;\Rj9dTuNƱX j?bL/c^ڹf԰XK/`%s5ڴGL7Ae K!~;-ؓR)0KnYK,QLQ-Vd.[>JuBj Di:ʢݞeV[ ϥn'Gm"Bax38'p.ek!Г0A f2ǃGt)G{VBi ֱ[ߙv$"$)V#ufř=r SԔĬt ^1jzCJDvva8ئ+ZDPܩA tڵ7u7.0VSWkftjr6ӫ8*ˊHǒJZ@hCpgihNI3WOqnr.߄qDøyQ_ xS6 0OV8nV3^Q~"]+QCq? S<64]铅tL(}3Fvo*D2"cwsR,,z%>t:H{ekbćiRE_ܣnI0Ҙ#L,u*I~"V/KmADpl:.hIlO8n -y&};3J(u1R3|OEPOe,v[7#LWAfT)ƪ7hW$Gg] %Sd^(*9XMI W_81\yY&d @R#ӢoK?VI^Sr x'*$C Yjkⴰ_wHrtny쀫r+3B:۴:Y-w#jOM J틘yWf(L2k:_Bv.c%2!Ƿ΅KY) $5薔.Du3uGC:cF" }ػ\ z"\Z[_WL~\"2M67\V "#jCP4ѝwC h"z mxF5ׁP`-oN 0'K,Ɇ s}$YDFi$s5\чLdOX\1pyf_, I'Y^ \>3JJJkTb$D@^N*{ gJ67 }{=4WtBȸcw`[6(t%7cjEgفV-8G̽G Z(ѩg칰 DX=D 4v|v֢ lG{\lA|5CYx4}]Rw3uO$B)Ma4(Ҙ-_7bOwg&wl{=QL|&тk QKpT~GTZ{~Hx(ChO Ҿ'?xQ8yۊv11 *~vW}i3Փ?mͺX۹*1&}tW|TjlK\l&"H)r2-޴ C6b=?3ֈ$Gob4l6A2pX,,Pw?Tƀrě| MNu`p)6ӗ7']v9_;G {E$x]'uiN01>8MY1Oe(ĬΪRM,ƺ$J0ZF' 嗱j[0el@%q^!דևN;d8u],, ;N偌rHXel& 4Z6H<*ƞ ؟~f>M{OG/숯X,]zՒoO[o GJ4ɫZXiGpѕ"lk͖grџh& 5KH)^%s1"}~ 34M >FXnFf]?z4JMNo0z)G)f¨ +`,5#hkYǨdjR,1  m*̰et2Z3#ZڃTzMdVN?K\ b|Ʃ;~+vZqt&GUUS$RY"Νy|$&Xt=|D41DrQrQ_Yc:Q2oՌ |]᎓՘0@/TH-T_lag@NO6`[}u5zQ4>qAYTWhzܟJ"yNa|c^ixjkDŽ[mW9[C4T % +(xm6DKeJ&dfR3Np<&4TI* ̎Sj(l#:iQY0_3Nd4f{=Ryi uv*NWIFV e`%^+x:UQDA?hmF'aWG3t eX UTkԷ 7&c2%I؟RtFY5Fp[)΁ov ^uoqceX<"ch-r|΅ZMUq9a Zg˛J\]'Ͻ+{sA r-vR#/zǒ֝K#@#xHDq[6*K#;Er>zC2Osyuzô8Yػ:i_˥KF1k+DnCήj=E=_j͹gǫ.,`/rgHz׼azs\EN\Akpٮ_M; >?2a O ܏d-$þֲ7 f*) F"x=_؜aJn@"p_8`6mM*'5'IcbtDPEgt_XY܉Tly2C#odxr;@^sΠ@|5= ׂJY6"g0uW~U̟+QO_sz•xۧLJ/drVYn.DY[l I1~/Q:qb츽Fx |1 RM oPOgC ӾB|E _gzWuҢЦzY41RX)Iiy=<1W mX"G Tġө{XQ0L 9(TW(6{"qcUBn̠,y_š%̿&8`Zg˛J\Z&.eOrqNhhS\ jxte(eR=!;e %-\h'ߚ:RN S_`Q{K:4PVEVtHw*;9n ˽V Um03y'PRvcꣶOZsi6a5/!yÛte4emXsWpb/ 0*nxrHJ]r! >d'=\Ē5vkC..Q=`ɐ< W|B1@=nвGiHO&u%Gy& 9|g]!rɎP zz3}njXoҏ)LIU20?ɮ&MvyxH4A*9)MN91&b46:z+cR;'B t:=L­Wj'QV;v%<aJ f/ `uul*Ϲ|pݛշ=f[xe-I!Ut\Yp/K +g.TEq#\tnbD㺾JMg+)ET}jxW @䞍N-7ēOi#ިFMۙ~`07a+ O*o BOug͜F;H4eE}6ȆQ^xc0]LAM?B}4(Wkc44#`Uk r6t/p 5/ٹ9A2h JgN q\bLFv qdJG `&-IƸ7{-t*%YRv?DD*36^0 (u6xjs0d .dHUsjePK**+;sy/{R8p%ηM1)|oVE/oR4<2ԑO$\hr]Lǻv.ۿ4gu6䮞~_J/Hw I$Ki"}!㔖#W׾E5 LD"ȡT#us`3č8ĈBSH˒a8e 9jllVW + ;I0f0st?7N|ըIAIC+zqB^d!p2|ڡl?,Uէy 콵g):y;6aޣl*c6KIK¢5F+YuV0`n;D4w.LڗE+J[D; E+UՠbZ'&rYKz3J!u7}9QbȔ~9(9DzkƲ'LH1j"i# 3d!X(oʊމH)&zG ckjw?Y״e!eOPF+|<,{H70 [y(p#Z\tr2jLL,x*нۂw剞7gwad j>ո"!7`;S# 1#TzC27#OSӱ8/^zp7B>>v6VS̙C4 ǎ]oGX ] G \o.%G }[{هx)i V{-_nQ].ua\SnA_!I5%8 TZ:uBnݮl$Awiʶǭ6DH˓8ٝjmʈl>@XQ2Q{+f\w} S##kͶ)*9BSk]`!y&՜P$E6Z*yjQX9jc7n̉=O4֪OLo;T(B/M_!nhʷ$b6EN1*5$dIW'f9B<ɝOT03I7/% Ph&(׆؃ż4*+e紧I~9Ǜw*Rf N [*6-t[Uɘ\3jҗId/|qgK2n ~ۦ5`u׫8YcdaYR#ނpdB<3 Jrl3TFWIJK.pY:'#h/}S{ɩk K =-͍=gDrS[5`㝤4v$Q>1t -IhzP*P;Aqdk-%Gtѱq Xb'LSm-[Hxfy ^y8LD5/{kk M [2*6|X"YxY&(AlzQq={E"\C%V`%Ϋ Mi L/@&V{-X mtO3߷m铓ӉrZ (jT9䊽uOxꑌ^X c:w)=ewac:?sҍ΃;pxm,  $ybtYG>MH.lu Ahe=PzCA0ĊĐ9_vՏ"@W7lWkHbG}QLHES) {"v{Ia2_LKQy{am3/6b\"4~8~wpM`dOҙaJ00EIe9pU8 R% ͛TGN=+G/%,?/\T}뎮1>72]sĔz$*K&1KYl.~,^kF].Rބa Ȁ}}E|xc-)/64ϓ 3D)"dPb$B֥^|tAB }~x2-2_"]%^C^Njde;D_YrqYr:nq>`ʷuOXe^fy_=)f![l"9EVD>[PqIaTLY>mDA 6B?_[uaU Le)I9eM,+Md` QI0 זH8Ð38$$|<Ug)7$&hwUS}\ͧ&d%ƃ/ -b; YoKV@_S 15K3lKiMEGeph?r5 'PH[Uu|E/萲b2X%E3cA6mFVԚI`״Ӊ7!K&:D_W\[jޛ؁i8Lc,WvK[P+`0R`v_EDÊ/FJľ7[2M MPIde;D_YrqYr:ll 8l9i^}S{ܙ=ތ=C7o*8[Y-bWrc&"z([&JFK#NUBތ7S`slx5m0B :Ip-n WB_Q_[&6(bbΒr={@*E=33 LozX?'CrA!QgI2lC)ct͠7 .|qn@k?E/RB^FG6]2qRWgo‹1f&̯W_ήݘ|Eg-NsC7"\ =U>!:aZg@)J(95`۟+N)[+q!_WMTi<[Dq{oܼ-)v)2Q6?`:s_{ؙf$a^gܪhg\wM0k 0˰<]6h۝0lw}83'\X{BNbbH)ߖ1rг1ʅS:8[!6veȝ$h):*w)wp[lroQuXu)y]x|ṂחS //Rd`')@ëG)̓J0>LTi*H2ڋ*b' WP7\v"RszLVOwd @e敯f7d>uLl #7*_LIw#U)4Yq10\r?~$^0k9/[ soծH~O5$z,ݏ+“oܕZS>ֵ1&C\v7][`4m-rOI&K7EdtѰU@݀sy>AT_9jEܧ;;j%nJ5hBZjы+J[ź) 9O уi';%|uŤlT C\rNO ݱ| kbP8 ۪u.ڌC a#'8z55q'f'S}Y٬vs~a"Mq:ZTB !nsx^4@5rLV4ǹ,W]W{Jqf*,q8*o?×^۰%7$Xi@deϩ3\q /d+%lB"/ 6uqզ?>(ƚU=kU.c[|>-7:_y@Be7 #J0@,Q[Ul+ExX;"4Bb.)ן XBC|tu96e@$.r)~{S Fomw3s:Z4ѾCSce[ kP͵YJZl 0+͏JPtOU+yI:.wRmNa.m֣@pbs$]o=i щaApvmn=b9 r-3AcNJ6n#d=di d'2 !0Emή%nQw-UG`+AggΪD'21ʮ~胇+d^()5sMXx: p|TcRkvӋ:QtyǗ29]<7qX2U)Μ:<'SK]z$V]Qծ5gh+Wwhoij"|fdxqyrw0d˷939 VbMOr T]gڸǁD`g ڷ;ڄ[%c\l;)D(ށd{/`GKACz3 *qClTzo0*Gjts(0Glٺԯ6|fn]BםxbL=_1Hg]-ϡYŴ]\Rĕ˱m 5o'&AE-'O#QN&kc(td[`Js] G@ln|\vv3]JG>{CUeN'~իV_Z z5ЭAw0-K~̀R&$8M&ME'db?Y,>g' "}7I89P;:⧲/-ZT;^=Z0:A..X-R ݋>3X-v @G4c=F$GDM=n%1v,,Gq@yjz/Not ,f$j4=!\{%> Q7{gz<~'Z}~B0Y\qVV>J!P.~ -2 3!2P =kܯ h5č}oDWEBs-xhҽ1k>rk8dۯE/V1:noӜ<؜PvӦ3MfqPHǨôjG2+_̸c/&>5 EL셡kgxl&tXm̏4+,|_2N(zMm' Cc0(a&@Y3 4^~okc5`Ͳ>Dz~;رP,?H%`1Jx#3sF4niseq`{Bsh8|׆Q\ ׉-5M5"};;znoCgyGg^CXn( q4|Օ/\"'ShIc{y7,ͪ?c&~o>zmf$ JilRH;,#B sʳ uxtN +l)VY^s9 O6+X] |MGq t)?_=^> xo`烧GUkam9M!$kkm#e.pZR"#׊lM{1Bh $I.O/B`5 ʤ;ᘵC? àqOu5"2av ˣdoġL"52$.'F媘Z aTo,-J@1mx^1"u2ג:Q@.9YXz?ܥL`sv\.6쬸P+< FeWDڦ}%cA=q&d۠sjCƊ cÀu؊E9ts72)9 +=X̃5/i}BϚb喞jAPUl{n`"/y,at VYٯIW``NrWMNO]E@zџ 'bvve65?^J#1Q>LeG }r?IR'j Þ`s kRW:ۺB*;\t,JXp1WP] :4*5IxU%:5w4 C=tаpE*ߝ}biPn&OXemI1%F/wTDr dF,΅){14` ,e{EdQǨzWsc :)_\πw@\U nz:adwN}K酃,7bP9xGU\k/F^:=V*V8l3b je(B! 8Y[h/9\#ljD'2"6Yj_`̒d45嗦vB#ra70yr4u Fق}܈60eEa%~!4ܖ>|ZDjw?ȭqDl_CnR&7h6w g7O-v4]d,a"|B%jjɔZhGХL7z֏n tbs3bM.R1*K6̻G'֖*aoNI9 JRC]Y(R%]\Z%NӯvMU|GiP))xԗ" i٠7bNWB.;nqjV0M}WIInQ\8izͽR  pi[RE(JKFuRWT7 7Sy6QwMq!X1D_XR#͋p\!.OFosaik eIn?[Yu/7rK|c @eY0>p*: Qywq=dx?(EcN(Ȓ n(2PaB9dzxmp03϶6G꽲_?b9QQSvyCJU־%R|,54yAE ϸܥI)vF_opf'9`M>{z]Y H}4l֧+y_nns{"J%7)(QrNPWSo}N~`:""-%S!#(\#{TӦ`PYwjY֓E֡XCKwM(CgH2ƀω/fkU?w`JsȆ} K.l4wJi˃4e@00@Շz=yag6P|دE eSv]74@܉:YfK'Hu n_е?mӔgf~^~z;m%[u6OrY>T(; C[lvTװW{#OvIp怭̳ǫƒE[˜;O|^$^E@=?hj7v8'MvKj+Qd`Z*5ujZm. qV "dG/]ϭ4eK1?J|YYZ.wմcͧekqل>!T0CCI@< ?r״\H%UFN$<";fVZ,n0_Zr䠹N~fw|il6yj H:ҸhدEB^L"wEv>Xȕ#}٘Vh8"K6]P DW/|tg$CiV[@ &ϧŊcF5T{pACEl`࠮M _a<yV(%5Yr#)yv\Lg5?&۪iiVBr'yK{;y 'ה"INTYV&3J|5 ;?ci 6leӖUg"FȘ(bQQ4{tk7@#Z:)o&ѹ^ci7Ȏhtw%TErRy Ex_ !F,jXEU`&EeuB~3Lqhř&]DU|C~)ighI̕uSt09mXi+<[@&n(a~p6$!T4}pѦΞF4/Be%eTPH`Dy?dVJCzo&,~#wD1sh][VO t&I?14!KȸO$@y1V8>S0$x:GszeWDc;rP%1g#c̶ZKM a.Z?L;E2,e㱅6ʡViʻ/NްI) qLz#ύġ4'qnIJc'M]6^K_-A/rfpE{6eyrI+aܲP@!tF=,%1ilgrI>Z_AՋ2"y8}^6ӓ\/!L~փ^ ;uY7Xc*OJ*:|<"#Q} Q~>a},>mz:W 1i*JJ0ێ'a# U^-` AsʞFd*3ExQ4P1F`@r6ݗ`~ONCT{9!C^T]Ia&: isiʅYZ"/6DJeSUGq;fŮԌTSEՕ҇|j~V9c;!N?sedB`yU٢d.q/ uip% On aco+kV4Z=%R\\}k W(>mwr򑺤swWao=ouy|GҐ- 5DկmiOX,y@Xw eC]WA Ja vx:>YBj)I;&aB#m]B `hgCRֱMhM|y\=s*ĝhh?QC]ڮz.'Yx!:5CCcܡ.w]zItǘ7ͣ pĪ) Q^th =[Q/ Gq+j}M 4ܫ%GP^ >iHYP+yU\!]EQ|{cr;ų=`7ѵm=cG {Gc 󳟺/1 ZfJmLmJNG2_Ԧ|Mt4vj2:$ƸǠĦ52Yҁy_+08Ya4'hec()IEw,9n=JEnn0"D4`Zb/F Dcse ];歅H,Ctmҕ|㩌Ṏ&Z5$A!WlQ< C~a:bqo]Ȥ%q[ )-Nb:Nr*7n*QwQQcjDNZDC> CkJ*r۴B>'Dy^ǡ b@Wk1 Qm&M('%و u9ZN]t}}5)]e`;b0f+ݥ$sa7"g(nj5( lZ5W+W4Q}tu/ ,?R,V 91FڽVjJ6='t:,ƸSr\Gy>MKg%R!M^o+[1Bsm&n k3MO;!WJX|RI[!5H-LdDI8#pygv!?\0~J#(E4Sd9zdM ;0LsAiȥJr"f/'طa)jk[mQp6Z:MON|NI&6cwbC ]FvТ7WuuW-z xd۫^9e.`&:'awϢIA=NMO˶4sK8lvʗs'|@jc\O.mW! 3SJ!x!AN0F/04vFb3 W`Ƒ/ːcHҪD oZ\ɿpw|3?z5[f?nZ\?rLC7B.X$9? &y)#s;m@v6_ZgtkRfY oPf%,HϖvFmYϟ}L˰mgiT5ZSAp w{EpхA|j}!:/NX!FSta^`Y'|A[<.8@r嵨n.GC0gc)%[Cc/! <з4Hqw(YB5U$an=fR6jYn2iiwtO=`V/VCL@#e}/ yp /ΟB9}pYK"VK[6_o{~}`?VF8QϹ=x)WsuV]5OsKZˏCSPduoֻq~)#!5x)z9KvX(&nv[k &-vmL*x1cc&ٷXC^uOt]F٥,^l|0MY{˨MG Y늆)EC]lԫ뗟[*sʿOO2-mu'UyVL5Nyqg&_4ڭ;=tr ~!- sdeX_oʸ @21>($ǀ4a <4 sDW+Y})P@C aE(ÄUBǨ ȹ6XS8pJf]cc2[$U[| 6(To;Վ(^nK$5\nUˁhv${#'TH5qކ?mC9ΏHly×R<ڴEG5V69Vp&((i|4*dal0~ʨB_2VU&&ɎڰnVi ilaB\tVzswU\wkh+\?i-MEg~A:#li]1>H˷8) N@ ,SW/+ƟH@᫡_MuOWlvS.F{Lޗh'>]„E zY"<ee^^EAeT; ǢJta>CUo%~SU' ;G|~X\S+av5˾Q&,n-[7} d0 >0c}tc 31ht8֊HtdUBfkګc|jד<ʮ7A]1ɖ:7򊧰j3+Ͽ\[ %*HP?1<$n.a%-ҕy 7Zk 2m]@F{Weyܜm*-\ L2&>扊9@9gr]/Jg$\0._Q(hڸr|i. h/PlX?{bF%rԐ[M;tx}h "aBRayKhvpS <4 sd;M{c_Թz7K sRG1ViO%8A Eĵqq'Dohm=<[sK0_2@,AiL6eQcr2FMÐ*.y ttc 31ht@!z`Z޸Pt&/>_0IQoWr=w?QAi/0O+ g#_u b45]{@{m6M3+Dg6P,W<SJKl@sr-H!3?n9,{ر\ J"eeݫ1Yݚ3A7hGѡm0uح/ݏޓ`n>9A>tGVeXȣӋn9u3bwjxԊTl-!/3m/;ժ.3 * YW -6gSO:`1AH6Oe܋EKvf1(bQhX@1|Lx֚ :;7O1`ŲMϙ !C%Eû^`)Q%4آn[_]păyz)(p:H%.4U5Oi1AKU<ӲB x_ .!Lp(Du 6_Y$΁}?b9qppwXi~ }u0.M{Ǝܟ~E@}g=6m=mEM%.j74Z!?#=>㨩Lo@FtޢW C$nL݋ZɜQcoOΟqFYTfX;qߥ}Bhaaǰm9e; nHAq>-R~G$9a,S1fXb m~o˄5V}]N_N?nW[gmp `'(He*"B { 7D0Pe2^֌4q?1j|c1#FX8DU^AYl$ie(Nbm/? Wag<973l\SLo5ΒlQ>%511wQqss5I9mA<"ݚ,h2wQ5q~7Ked|e&nI#U={~){}q>H:^B"%$ۆ U%KnB7m}  AqQ^9-O#/j;}wL P8s`whR`n|A)ߥnN08,}*$<GOE Y! iVt#n3p[~t@ع}6 ykt MAR9{XMզybiA9ysu ;BH˜jL"ڴK dRm%o+ 5uu}|;֠nI~m|rp jW+ߟx[ċ羀~:+Hzdb ![p`&~tf|Ez3UT]`;_=hYJXd%0J-\h67иq&S4/LV0\2kmÕ8u$󣠾uWeԲpGV $egCk aM0eS~^V?X[[$# G5k kx/ AJd^<(%׌d];!WtcʙEWYG" ]Ey\ DZf ., h#D&N9mX$Y쩂JIǑy Ժ˵ 00?~i Śmw$wjFM 3շo hscǢ/.s\#bK.TĺK噾nay\事2e(A)EF/*ɍ1nX?~[ Y8=0Ƙi{ʲ|>죛s^V ŹrɹN4U+ͨ{a'-5'h?vx[Ab+Fo>d[]O@Ӱo z~$|E`  "4N6Dv#kUMDU~_ٲòWDQ'G0ZU|ϠTSt{y(B_&HiʲfA[`) +15&=_Vch0qv<z-/59И.i;xeN*ZRCk>}E 7QjȬи#TutVXGĸE:V2 :e mw }<ܷzia2i4xX bf{'/~K^Tc1~U{"&J۱luXH}Tf[GhsIBT;V`4w*]s C{A`8Z2߬BEG]+:]8zn5iN6,6=W&8u;K?)fv/1)T읔?w)5BI>?%(,O `|@!}$8IJ3C^Z>5E\ݿHȝ "NɈ?Ww[GǓ ?'PxJ"q&\CUdC? 2Zv2 ?ڃQr @pQ S»hGZVE<|iii]3]ư_הr4Hgu+M9Z!(GCUQWeئp1 "vAu0%BS>޹ǥ?Ar)_FP<3!\7NYliN2"M# Je&i|Y8!<ծK찥S]~ZLÑ-Ğ (um 5fm3} 浯ӐdB!ڄ]D5=J(70#ޗ|n< tIβ}Edh҄KOmk z50\{ȊT5hvm֪Sk49v-L+'PlM^d?GSeJC8Bm~4d:!${l 2utdb9L46x_- C&CD++$V88nSIѲyU%C#Hw'5\Eby !j\EO[x?ߙlȱ 㧷D{uZ-tv+ ޟ~An~ XW2j lfL&\®A懡µ$ERQҗm;#~Oq_I:RuA;|Rz2ݖą̀╟;ײ v@U\#q\%3SCv?֬ =ڜg&:LG\N3Njo_]v\H@̷@J|` &ec, }$t0G`[ xCW_S6"-F;pVoAG V]q0&YV L3_ wXKg$sr Jf5A+wmZձ@'0-1Wd9$7b/n'ٹ]3%Q#oR{<"LVs O񞡵+QY)zySw(=Uo!,{JX<ſw b@#'3I( =e:csk-=ܲS[Y͝/bKye?`WmY\{d8sѣK?|8%F?YJ"cfNL5uٓ 蕵VzhUw:"|2oKr6.#5SҧHu҈o]b*; xɗv:xĞY0?ӥwnNg;߯a FxF%Qʏ53*C'H^3QPEBkOqxc}&âq矷L)m6 {k`wfB^+)6, >p0ua۫R:&vwx gG$2x4ڽ;gռjeEWk3,M]5ǰx0,_7G dgi9E}ʄ,ȄyWCg,%UUr,r8]sX8Omǃ w^:-BAHR .t,ňA5wnE&r/-fu9Sx@AD$Q2OJ=GK:O׺=3czm$! [Ɍ됤K^}TnYhbjr{jK:=9tJ_ )AWA@H*%cAJ$'K o ⳬi1Hp,7}Զ@&Ґ mQVPuX,)Ð]K=x))L9#Mkq?x9؝9 ~++U=I4zUՙjȃΉ5qt99&|o)o0- }24t,#gUHPvf/[bq0̢[;E'Mջo%u?x~7u=NɨvxtvahD*Lh^d㕲AmԱ2sS̏չsa8SmhT.~(hn1pe)- 0))^S/""CRLt4 Ũ?Uas6QwSDBԵtfEA ]<#o뎈}v,xb/&_UUAm}zHzSxZ$ tQ5{N&Ŷg*u:Ga3˦xm8\iE`fs(L,$^XVvL2h7p˜:l5vK$|bm?:g`Ô։3ѽx4s"T:Nz)1FO?Dd^2Ҷ[X׽؇OZgzm\%=$|M.(ʄG?UL6=JZY B)!;Z^Ԗ7:jgԈ@Yħg\TɆ$jY  [m5FƢN=YI jUoybp$(tGW^g,;n;.Do)3MյdqB) |Z4q4a6+Ðki zbI=O۲MXML;쥶r*͵jd S$ؕ;5_;s`lx(c-N|^}.A$ל?)Y%2WZO}?&M9sqΛv]M9$%o-޸BtMe UJׄ[hD݊DTcd)KCrŻ^W:`&A De\yy}B'BW s]"@]*bw!:iø̅HYroeE-S.0'mƅV9v s/8*aatJ}os YT٭{*" $^m\R՘+i`])25X'ҹ&TW@@p_\e7`g|A7S/6Cu!vӥg jЪ3\*\ں 6$(Ag5 |Ͱ1&D7fh~MMS+fyb)!Kѓw谨rbLɷ:bE3Q&dvwBYY$d5B4sV}AvYd_SSLsfV'{>b[ػ2AU7B^ϱq:r6:!a# gփ}p ]rM,>+ϙ{`MYh0"աhSL 4'gpmQrzrCSLz?.piKinJ$;|+ ssŦw'wA>u9}hJ`){ʉӶ[\gڋGslR~[IUa -}S9GEm6){qRl*#qQ((R7/>x1B$ x62ԫ%](FrOo؝-p6ZKw@ﳫ!YEIuh,\svp܋q8mΖ.S,2xn[dXR..$љd#7%A rHe?rqHMIDb =4wҠI-\2"`\pqonӅ@١T=&v@r{x`->Xb笗?8UW-qw0P.ta-d]N#`m: "oŧ!wcIH-S>B5C~"+dPA,v~SrNI(ER3eVе!]\|ZL$|į`88ɶG4 ;B`wx`.uz73x 9ycX&lK(Pr}:|77:xZ0En;ӿkF\B { gPHGǵ Z!s$xϡ AGq*."?= 9,Š[tInSxNoo^Бe|0/fƢUZӬm<YH<;N\ )vTV*NIzK& հ-d % Rw_Df%}zj/ );(J~x2 =ؐ}2zwl2qd "b6N+$Na| 1Ꮄc=k^1LvqgΥ|4 W%lc#z"LmmѶ͢C}d-ܦoЍktF9ևjN^\BW¹`t3z`һb(֥=>9נ5jFh[3 e ҕ ].yɶibףsmlDC"fMgYSz*'vND%~."/>*ٛٴRK2Ltf)2}X$#o{6E6Cĕ̄@K Aʀ?wD&d-9tG`Qa4($x Q=!ONpLXSLH MDb6Y:㦞QJ>&,WRWmE(/8l$ i{hXwkPwd;${.(p퍒!:1p4A+&xQU9]͗*G!!HAѕ۟>1oGJ4օS+;upOUȩ!=cv [[77Qa0d !qQަt^̛"+~@܅n(ގo nLk7ΊŨ۪.N;EQ%χRLؙFqv4rPpg# W+,z4+3%tAm(]F!J<78b}?kaHjjҰb$0Su\#C!pIMD"u^ e5/VeQIU:2kRuCKE"qSB㘥7mr}SBL>Y Tou S`A[ dFgIZzH~䀝SШF w%4N ͷ65i[\z4 ۟ gRF{lFau }%XF3O?1덳7b5(a1M媶Ud59&vDj8m)3aŴ8$1/ 4x[U#C5#)T]8]aޟ.%A:{Ai]/t\jz6/ B5t ~_67 X3N%x棸ڮYytO:R85$vt&v,BţOgYh-ׯ-Ŕ Bcߍ~JUt(\AȁP'ÔTJ;²C]1Gs2\cD?5,DIibTSe0?:1:s #.pB?x|uO~gF4g#+!UtG\Oi 1d| bo)Bڲ )rDAW3+88\0uy[ňZ& LFJ(/ܑnF$_ria>b~u] &"4,Y nJ1dIV2^McBо- =×=h"^4Y}坛aO;ո)><|\wPo?lګae5nwc\*1HbR#'_dT.HGch/h'EFŅbf_%SYY&}=Y$Hg <- ROC44LD${Uzᰨ_ VTpC8 p|ffk499q/_2rz3RɊ7=%{ Gf7[zcHu< tn/`lYTu,=GW̾ |Pbdz εdh#uK{t4|>GEi!‰=b$J>]"8Cv@Ī>le# ;x~\u?&:^3P&5ݴ9/'UXˁZ :o@6T1`xJJA( POI1M9{{g3˷ tNРC,-ʵ"ϞvUsؽ7' v +xǂ~@hȚ(?=QUP^U'Q.!(XWuj9 L}œ.B8u)@A1Ѥr(5{YyAx!}X0,80K "ٓhauVin( D:gS|I\,4x Dluf` Ls%CyCU_|>-)O@t\mSTfAqi: Y2_fm+G|tTnfdž1h9t'jM@l΂J`F!PkzԾ,{٪e"r͹1Inl&ZDZ|r-T+ P꾛p8@ٕikXnxJb~DO%*1x 2zaY2c7bOL=Hmt9PHA}sZѕ9b{N]igoiDpBxo'qҁ]#&RC(0+\)7cHڛA lr@/}9)|Bz-p4*; ts|ǫs: 0_$ OȽ*>ɿtҁ}V-ZfmMt}!|H]YIv%!zIy_7 }9w_PСYlh"G 꺑a;B#N+J,/oALޔqr_,{sF~ /k&(`/ TAM?z -EJ(~f~pڀ.0u*(8>,'Iܙmh' YY쒐|~!8YmgN+>䰎y$S4"-L>ȼoxV<9"8 ,SdNKî a\{Xd|Oޅ$ߔҽI @B)~ K@]O]_y*e'Qd)aę2'꒠TЍ6.طzaU5S֮$D&ϳdbjX@RSÄz>C_5'yhtISNW<ւfU[DQiVqw ?m4ƱO.WJ*3WfVnpc駳\3g H=.6ێGEf30NᱴWdt?5YB8Er ]JۇG#e5~/䁎E?MJd6pI5u!-D^ޟ4yn+g:˚¶3еu_x_@EQݰ1p"5 e(T x3y9[nֶ .G<2o7x/\9ܨr/-aجd9hD v%ropƱaU^Tuqf2:5D >8~5G~ѬO@ðg/+S o_,'_$VnI:P4h7(ADw_{n`/%nˈ^x=60w0=ܷtkMyLmOpLɹAj_m LE:qY4nȢE UkDt4W:: C)f[|Z |в4ȉ`DT:!F-$[X~`zSK`H]/$􆀵cMxnGk󚣗q:} {lw^A)fP$m5ZhrG *3Cނ 77+ k,\*TK$Mao^KEP[:&ͼGJ)ʝJY?(oJaVĮ5;8g)rONf=ۊѓ O'Em3{ѥt$Ԙ^%jJ=%R:F7wy%-^Bh-6>L;n*8- e{TtoL\(VHMW-BA~ (5c=$+AjH~)UaE% ?anrh¤Xt={|Jـ(J_)Ff4Xu ,vo u`--]MjHcygy;$pް<|u$uP47jLU+#((ut_DnE:84A^1tN]wlְYB r%z̴oQɒ8JB-Օ"+X4&hf3wZ4yU*S?x>ѵfO F>QuNXO(ʋzi;.A\kxB{|KAڅ, /;YR0"2-J0{! s?+ZC: '_+r *X.!0d#ҡƩh{k3jFD%4"H=؁ϱLN9K*Ϟ] [W9fǾ%k3;LAT$ `^a#~:|7?q"hO l&BQS&O6آOAw%qj M8U -]e _|=;~*~:6nԴͅTjrCcfT( 4 EGF=}ڐ@w}?HYY^I7R(ou9i د<RvӰw!^" -\ ,@Qd']@Ϩw+F)H,U"d&эÝxَn@azBm7lDRu}6P늞.b&uG+~ͻ(SǼt7{Юt .fWέTp" IyR/- ?F-=><'3Ŕ-w|S[.>eD~6LqGK7WLwg @v0Um;`h᷐CZ)ŹeIÕe3!v5#BNea*y~sdD$1* >ED!U7{Ed;(\LDoٔ}aT,{Hgh6R["`l1"D{U920.yD"ŔD,*͛8yFC巙[nqߖPD9ȟO((^FSm,pKDKIE! OƎ?_3c`mfKM,೹خ$ǎv:WlQ{w+*pc4>|ӄAkcj5`h܂Rcu N3Q b8gAĬX~^՚],*?t*XF$4 ˿GvD9Aָ>R5D!2NOlp, \YԨOBIm4O@#Viuǭ }ĄR:)wN+Qz+>&°{z )vbD^䮈yUgB\hj4w|{_8]6Dg5cuXKrgcҬW%8~Zf@nU/fq~_ ǥy[6$~Kd"(?^ò3l3F8L40>Fbnug F ;m<<NtlZ"9Y ﺪG'L%}yݦMw(aD?^S0M~+Sj҆6R[Dr~$H]We:28%CZxQ-_,)MehTx-C>Fo3+&a66W?p0\amrS lH׏L]~0-p'{X0TQDvu%Sݤ%gpNs_cS؋m*WءU>[Vbw/u twPKpa'xO!tNA&0O} mx ڦS2gDCg|7+ %n^mh8Xe1怿 e(ew:q<`T Jg͈Vb~bN1 3`LC5> 7v-TBsD͜ 4ZH;uMꉣ*-Zt繁#x=edGVAg)HG6Kʑ6YWz ua26)WS4F[jX[[L<Ռ&;j|f>U+#r_?E"2`C5.Y^u9u+ uAmbLT-=bqϟʅ쯩cKj40Am]5KUqUnЎI@ˇ&!>rɒi0oSbwYʭ:yre*ۆ f=o d*$dW ak&u0];A0I|[clj-_h{Iכǂ /7X~+GW/LaEgቶ \r\bqu ,n|M* Ex`=|M[y&\VN\X(v! U Vbvw0MnS3BY"/M_˶*QB3s@wû$3̖M^:qVz]@rnGmJYf.{թNtȰf b`geYLqpffhm8ayWZ]ZbB"[.-{Xc' oI}֦Zo@0—PT|nDA8?oŭƁ`Z}Jу!32QkMt%SgĞ> \\쎮,|ە_PoWAI,h OVjy>͇0Z"ZHop-)7ڍWzIM慰uoG A6u'>^(E0Xrd"Տ(p];_mkbPv"(eld|׼tB2v_~smshRq“ t-LAK;Ri#A=x Vp >L汭$3(Z &/b2NkXba\ >*0U`8O͉d鍅 _)Vq3Kb!=dAuW^tDry7Z,*Uӝrl^.I%YRtkd5R0a]*ixANe\(m_ԓ{EQC ejwS,-u̲d\|V:*C<+Dr[l4:EQv '~Vu|cj\)~PIuEʗSzHJt\+%` ,ʱ_b: EK0mmA*=ZKSoQZOdXrxAھ1"; BOmYd '|9ր'RG ʬK+'&] RnL!ˣȏ6Ϗi>jhc#7J;vk-EC~PBzl7ј&GWH6W͔յ0ƶ&YP ŪY^ 7䝋GqnLbٔ Csre++Y„4vifBr)^*.Q5~˜+\{QfL2Z_Ik{:IXf @:!4Ũ&λ[UPc.|y"g\Cl" &riy=uFec,GBWZȖ.os'I,t#28K Mfp)h͹{ w-JzbzaVo_2Pܥr~Ӛ)_|4.)r[)srx7Hu^,塄#IVV$@r5nt˧0H^orR'Kyvi?1s-^&go6zW7Ҷ>dx0n#"i!fȱ2. /Au#c(0z%ٰl{L3 Wa/6-LHkrBpF>P쁶(no1Bϩ AR9*gS K,NEߟ駻)RutEqE+k|K'MOL.m6{FXUaPj,* sLȯIß("=VS^B?)_anʛc_ch C[-h2' }FgxWn0^K𿽈5L@ PEb*Æ&BbC:'*Ш75^̌=&WG^+m%mqm̤aZ:N؉+>a" bН+ȮL\" ڊ^]x"# =oNyEN4~Jca'lj`3aoԁm>ሱl:UuLNIQ#0L0熞Nr2s9T CdYBB0$mܐLlCǢ?RNI\_w)<ےӧuQ~+`AZk]^qw]w!,Q=^ uTu@~(Yf&HJx@WDZ> {ʞgtaofѼmTP:S5F1)9&,ڛ轅1_4pw!;OUe'm 8%y Oo`I^֛kQCngp.;إfrNT}ΗFY]K%R6AtMb]%G򨢟 xϫEfzl#* &ֻϻɰ  6"Er3\ٹ~ڱf0R0/+-$ԗ٘|b/LD(qcq6RƊ3U@^#9ʐ$ʰxz/}׭RQ1ϔ*+yEKsѽ4WʅGQ) I KPLYvI'ɠRo,&mG_{ ol͆N*0>ʎCx22sNo_b BPs’"_U,K}H1NѺVYŽOKPsㅥ 14_Ng :i2C7'`~.X f#7<X9kvErBÙP73͂ fŗ"S9>I~~OrD>!KK )O]&_VOKl6O"Z8jĘj͆Aߛ Ht̔2G\ozr->L$Tƶ3 nM(&( Ȓ~B2&~9?Fghk64wV#$B,黻qvlv$l1,gq{ wXW_R׈b?ɢ%)"D\;M͊Z:8ɮ5v/告СZBKYp$`yAVWCW+ Ǽ_eFzq0#*tO2~C[}!B|PUh~~‡)7(ޕ}Bm(v.9䤜^l#+'n|4cQGp9R\ߖiΎX-S8t Ҡu=Єj|旌~xLJO.׵z7˅42l'5^vu|ZQJ 3YvOwqW鞊\u'.E%X\Q|b1.v 0Yj*Ц9DȾ}|T_NOљ\]X|.s}6a#D3hR~W$wE9xel\$ <\ =f}OV%յި74?& p䭒`?喣TX&tK`Ohz4[=üuǩd=!D!Lt^;N0YS`/D558m0QX T& fp+ݷ NȎ\ƺt̂c4Kp?s__\_PGŕ~ijd9畳Ї2?/*t0j:tϲs d~?*^2NhGqt m|P* .=ϣ>Ϩ*٨Vbe,2eЅIʥ@{iyNf^0TN}B}z-epHsUՂ}j`j{4tK7Yq9+HWy'ޤʉN ʩQKLBiҥAsFTbyli=NSS۝Ă.o88[?= Jn{z!<`弮y CsV%.,\;Ʈ~z& L<Ճ N7nKƻJ:$nCGg^= \a=Y$OFP*UF#zE"!1\y!=cLh'߿[% vI;n@jةPf}kԤXJY:6JHhcvFrlapDcd ,=&r)t!WU=! @kiHG\:x|n&[by@%y@{5`4ۖk%Q +M@f 70K9љS; @?H7$ !Q5 O`j{FƖm퀞!=Isv~G4~z2ݖPBk8_77h32P"Ε86lmWu?ôNm:=) 0[j2 IR6!3jwJ)Ҥb6rf;b *w\)KopN4Y??ȴnK?RNbjmb}W_li+it`8]0E a3bY+3vt:Յx8G*HAK+ȹ[_+.᭓:}EcVywi!Rՙƈi9_lW}tA*(\IVfJ:i1/31QQ8'BI\J yoҦu(]m2..U80#Yf ?6qxoy-ItY QQX0e I86x^?{+냘`'M_h}=D6-qJ)n4~&(HUC>NHQsap&k 8"JS]2t}0NvOlF˄*|,Š&d(}#2<0A/$_ LHk. rD Y Sn b 049U@h)Su64K߸L_m .uRagiiԜ:ǛWFqGCM2sip$iɦg ۨ/LIm\ÞMY4J~ &Xم8 v;<' eOWk$7_vzmt׵>g+J @W0Kea !"7]ˊ6$/_ܿD@l {b~%{ö'잝8neu,LH*XCSgtky{wĄ=teL`i:ȉAd ,9zq,NL$|Q.U ή7{u09W[_m[}ɱ<>G2ޣ|L:a#IX@jsM!iꧢ+? #X)Åjü4/ 2yB,u!E%Lڿz TJåܨ&xe{8A(r}Z^j$q4XHYjֺ }h)7T:ȺuMu4~m*%FsD;8?h?ҡx4|u˄$|) ,L.?GѺU KSL  M!Y"}Ϭ^zஅVd.jՔzP[;j}>C)'VzLve?R "t}>|S( 3u? mk|GAX7:]M6չSCjFs%"AwBљt3XJLoOk}4)Qݤm9EYJЉasFCUH a˝=6Y y|FnnxS6e^f&0y X%D|U6_>qOJYgm,WE!Qz5B{>koY4WL+)=Sv6I#j`ȾH-o{åDVJArr1Y^ ,\|觅~\# "5(FoLxיA<>KYf:z>6ZEќ%Sm2;Su̓Hb?? Ѧ `5݃ !՜veF< ڗ3e#Ol\]|Cb8]b /۞PӺ%faC%8:Ɔ!uR&$-Nt/;Az1|1XvwFg&"҇< u$zsŕ#2 td( k;x#]צNد&=ږ60vY+EzJQS(t6TbQNշԠ+. xY_)l EIy%_EY$V>Ou`l*^ scd{EP;6WfOr|;[&evpk  Mo>o`cmT;WV]d x V^gi$6‰cHZI>r'x]{P#BCYTъ0$c 7b. =6nTԺ,v!ױ~Z4ihX(\] lval(PnNur~*iUh9.aGNgJٜ}QԾx8%M _~ͳֿ@Z%Djm Qq3/=e{F'H o!d.T\[Aј|]/zSRa~w?#3):H7{WbqhsS3 $u 8<. }4H$K~p)Yxz V ]$؞7A'/ ;pEF(;'H70Tz!b8Z?l!{Bӗ^ꋆDLcMXp(CT]f%ܐdb,XIǵ:epc<΅)T1h,v6kheNz&+yZokx\,~X=ai%+յmmX4wU@W_E1qaV$iU%r֕cwSʧ0SH[J f@Hq4En"tkoJvj )HuMb٧t2 q&d" o ~T;njSsg)Vt~q: ߣ@x ŁN$zsmd7FN&A`ׯsZ[Aa4DoQZEYyGHV.Q9VxZtNvM.Wc>B\pm g= W@y qءRok)꩟ׯ{GѲY3[AAּè{dǘ9J7Ok,4RZl̙zQKD"{[#9" ' J?nNtjKq߱QWcQXikv0tc)@KaT(MF[~{m_qeILVr.eIa!iw~^QΡFN0xқ~W+]f|la,~k~Z?x49Nn=p3ٱM {VJ<뼸JZU z-*F>a!L|_J$cY5xUW㏠9FxL)X} qqZw MDF~=(D U:\yDzjȢA "Ț-XNVU|û8Yв[9)#HU`썛cz)8ED/UQ}O! ɸ8ɤ4CC߱S尨نb1n2rKOQy%"U׻}X* jX7K=W'n29ZO2~k,~Ȫ|) .ߠt?Ew:(F;Uh Q'sN[D$,@9mykp.v[f7]f(KEb TWW06RS-p'ʸWv>29Lؾh#6~p/;d@޿tnF.#wy3G!SeHNhOP5Rޥ5vZ1r eس}*n,pɚq6hz}Z 6.\$u<iK` 1f ],.ċcYkTְYfg}u(7깝L0\q+Gz /Ł5 缛XNWnB"ep7CJ`NϸշD+ThVfx#0Dki`DNIpp^ss]ej)M/Bv!YM&Nk4hF*@I#[Q =7UF[ X]\:]bXwtG?jf4zL~ D;y DGV'exid2U$KV 6! m0 0: :I= 0@LbӨN9p"zo4 2yA) EzJuMJ9:|>uQ bx]L .$3` j{TPy*lYt& U[Jr1KFuXvp2(LL#`ZCqYgһ[N/,us<^z.ϵq?xjr݃,t $v\O*QgwuX,`j}0j! ȗ&*|9K͕ok[PHڪE?` !Ce2M9 |cG:jG!VUE `utE)* @aP(Qc^KΖeTEYl_I,s'ozF.n#H4m1#SOoFe3-'uoAe[u s O y^2T2犅ГwP&0lL\oQ̙ ̫wgr 혹6DEoTJ(_^y4ߦ%1' x ᜧH.vr&v}Ev؊ܜnri/m@)Ǒ M//QƸ._);A n?a>&GH5GqLƦLŅ1ZnlÖK#Oz0qV/j܆SđsvuM}/rӝ-,h7m% ˨VX9?8%zHp-0O׹1(mr LV&'Q wpж`[\IX&|w4ܮy -UۻY%)K8E!v$B*ZRsҋoc3ڐ׾5m+ŒSD,DKUn ޼%,]|NJG H|Zt Gҋk4fzH7;=u,4>-vdKXdn31T(m[#\m\8 !yJa8~ 1=lZXْynM2ŸzkdmOYk}[/SNQo #M=I}5CH׌n(>LN9k>{*^elI+`>X_Y``Èr#{:4G1q+_lr5)|f"aDssn!0.n)LҠmM"yS? Q9J&`"0@> Zl2ST`˙ww$NrKJ{*c5S4jSH*BGXeD-S>):&:!MU Fаqə<ß7)!k3{QLfw N]$M.KGO -f2q)_b% /}o0syΦ 0y\8,1 ;B3P&dl/_+t(@!1mŌP۷ rį6'`MU q٩[Eo=7ufqr#m2~LvfAHF:vQCW&Ӽe4@<>,_.= Mb88_n)h+l|fc;KDNJ:'Z=A #O;ukE}izNϋ WmJg Z;"bF4Mi>)= p~McI^I0r=X1NVnzu$Imt{Io9*/~vcI&Xr=,}z/IXE!ϑ/#-A^GdȀN?pH|QN|g=9>9a~݅[C=" -8^ G#@[jNlU#֚:e |"KcF{H`c.^ &vZ8ц-+euo=D<SlM$!۲ w,F4'Cgѫ)f{uc-Is[ e?zW'1ϼ4P#[тMٰ~눞Z;kw_IM%~HqymLJマ[!؋:[=""2dʆNiZSÒG14E3Jdƿ; %Z+Kk R\&H]݄k?+~3t'8! I -ቀ$?7ŽCUьat1!! &M}]mmA Ï7s̩>r9Ɗu %gp 4T.>/sn~vuqP3̳IlG2h#sğ]ln_L3譀s"( ! 0}2hI,Ǵs ,ֵP88? P4 om$F flL7Ǩ!N kԠؗ6¢̪&-K[tSD  j33 o^+m\j?ꦢacYB+cL(KS0yuJߺ dv;N8.2]U<43S#b̤qRNeO&ҩa8򽊖F C>q skpF~kkjR9=% Tv ^cN2yU _柊ӻEsN"~Bn^pazt /;3ڻ?oԁlqh3ɼ#k(ЈxzOe}@74PzM#d?Ճ\n1M%*p: 0*9nӲ&"* 35c1gA 5wSќuyvo3#nB.GwQeZ2  83l%Y+>Q1VIruM=ѾTh x`Eׁo\UÙ SF[d)Љޞt8TFLF hP%[Qqrz=՘Tc[ #B, 07w(QB-ВpD//Q|~$@i3(kc>U? ò]difx-S \HN5uJkuweb!kîRUT+Go ZylV2윙)KuO"ەotcn4a!. xD8%0'<;3qA-0(%t~qF 5p]oom~&:aS Ou3cLń^}{h\1#,#EK%1 Ҽ75qsPH/^ԶP*taBm d"+W%F9'sNazu?%Xh^Va ERM84i쇯N#dF PVպc619-nY5 &{4k=k@`|`:+@gxE\Ww5f![z^1(/2\ ?pֵi6wYD w8g$UㅞI[~6_==Ғl:E>Hpj|xQ$SmyAd0njٲ۰ vd6>6a㝢'4/(aj]w=e"3M?G/Dl]R3s ڙdj{^+dl{{$no7%CRRԔ"h/7%p&^Z ]/t\ǒQ%l9,Ҍ!&1DTF rσc\sG0Yh"?ݤȼyM ~{) WB.rAm>A5CNмuOyZDV(-0Ra}dXṚADZպ`B(&CkIjU6hŰE-uSVU<=9~ 7 u?]tJ[ZM_Aas+rKO5 lܑ8{eqf鬯Vz7F I"b%ӉૈI{[S5(p<%$Փ8uE4M^e|iZ`~' b|0~ɴW*.hy[Uc9۹#%i;+q0pn-ki!׬U7Ɩ,\~y¡5?ؘܺ4ld;$ueBVW2r Vs+w:UZtCS( jWN&{95 +Mo}z5adC͞D,pTzs1tOI_, m% ySeki- eĔ &ˏu/+p3NBJx;++ ~u:A1VaLnR𔳌D!t_MƓt `GJ6(3Vpy7WWড়f?M,PC}{AU'{kHH+º \ VZLֽAzyzLd7(7Nu^3B|1#Xm ^>7a5m<>5I&̻6ScoܮC{.'gnv KQr<4#8Ô~貚1( G{L<i'!x`7SKEDHZQqh(vtO&qNօO3#2!O`mcJEN`H, $Klh9Zآ&n=O>`YM69@c~%.I}uJ#1;)E꿨%z^=pNG6oevQ+w:"݌[\h/=nb!mRBdd&MucB]pƑ!&tq#) xKߩjk@\2tC^橲.cAo=2/.й6Wl&rm*pwv@޽FlESǚ1V}":'Hp8FWk~.˼v۝])N޲˹TmqF+}pi6lsh@OepD(QZ`wLr~-bgڑb2ZHRŬT|_'+Rv6pWK5K+aeѡ峃8 ZʘduXFѷ13Ap<Ndp3杖8sP}dYRiCiK&UrVFJp`:8[WYj\R=5 ,"rm",໙%㔻HUNX'1UC4bk1& zԤ/h\eMƦ HҶsD/:I{ڼ5k+Ǿ& v@ɐ=]K%ɠd^g9T[uLSF҄L*vy|'z:n#۔fIEn29P[dDvlYpā64W̡ԼebPk R)6*?D``)Hr*YCmʰNK]`[}&|-FL6`)1&8?M}oY7b0ȖY `3&r)|)t OX|=  {?NEIFvqk&&?A:eqWV'ؚ]zFh,AN~qOYТºmƒ#E-&N_ 䠮FDA(a6hug݈mtlcq?Zz 8h3ަ$ջQfiVyC"أ)C=ݳB̂jIy RHiXC1R>2q!c'k>ON-έ4D뾰@*MTj_zUAu>|p/b3S6,Jbw{ss1'!%f^HCx ^4ߑ`|s8K>R4xɶ7q6gja x"~Rr1$RN+ƽLUO mR&<)@=ԮŌHN:13?m6 [\ME5Uv5c,/z |d_'F+u~8ɿ~495bQܪ;h4 ~^PGH-=Aqשah#v$"sp˜HSrئ~x%f_;tN0ĩ؝ȸS9J|T/yn\hL6q&uf9AvZa<˱zshoo_|Lk ЄO2G!ƹ`K!A0< I*FPߊO4]:w,<_yHc!c14шy -L5 k=R_Mz<8qT Mx XBJƏ|J#Lp/O|LLrЉ ]u' "Â_:o<5-Lѯx O3nOc"$'.Aצx*{(ifzc.[ۦ_o ׎|@Ywwbe4 eF> M;(Q|JKBȵa]?6om¼9vf~/SpУH/ y/Ѝݴ =09я=q: f눺tĈ&)TV 7 u/LWO*܎ϥMH=:W}֌ uA&ȎC6B*]N: /\y%s..y! &# S\dA<ȉ5C>;.?wQF{v GH|㮃HnĂEZ1ôM!cӬi J/{?thUWW SZsxG?8-Tݩ_y_@v_*V(nQAINgD^Eҍ{ʸ=A>xҊ|;R\3+u^ߧWG3Bn62,W39I"H:XPϦ5;5ЮsK U^oك7u30טOa:v&p^=!/yFiGڽI u&#@<7PW_caR5'&z~zKO0J/xVx{"Ϙ,[:.l˽2孙WD:M~RFz'R7+u] Z4!b/>Z]vq "}N6#>Im0N '{6KZl-z9Y8.GoIGFۂb$֤ho"TF#C*dAO\2&90R\Ȳm>]TTuZB7H5ONǧ>^ F?w@Y^BLp܃ /{,w hk؛G5ysBJ$F%a?N>*۔uJW 1ע. Ec~q-4I?,}}E31&ٽeBV ˇV䣴L:#УՀh[tp9j!K OA Ոڀ%Ca77zK%"4 ԮyPiZb]-R@Gy3ab<8;N@`t j4;TWc+h}5NC/K9w04{GWV&ׂ}`eh\w*#8gOWB̍S^Ju7![B5yN<ȫE 3{*z<(4 ԝb(h\@Հ1g˝~GRSk`/i2\L>= G€?5cYYUK.hip@aS!+~'0vrǿR)'oqGC f SNHӄX'RU mPK=SB.38? `=]k lSS/ZXP%4ALryNF݅ɛ?QlqiX2灐S w! ,p:2`Rm#+f=hbY@kKn^fPLC-tx`=5*ZW+.&yC4 =pe+Xkpbgn\CL-BAh<"%& {d+NDV;{3&R6m͗:@_GFEʃMheQuR#QE9$=qBgb{ &QrClzxovoY$WrE7M2&͉W-0)& g 4m|kg}8cT!M| &wERr-䍮"*t;xE<ϢeԱ˓K[fxJ6Pbm)/DSӯhpk+:>b0%.)I*%хSk~ag54)V z.9:sywpKQ #@ 竉D:/RѓF-EGJߕɾH!NhR6VVCPQ0}Rz[7extB[M"DSoHEpL i=۠fhbb}Cp1!_2'_lA}@vkJ2ZU(hp)yTd Ԁƹ0=4D6ش!&D M}%<%ZBzp7]U1i%Z!:DWT$Nxg|9 s@(ZWy =K Nc=3քi s^206-cs?(^mp7TqF_-YR5jz<ˆL1$D(UtM]iB6Hs<d%DX [T/CMo V1ܕbI2aTSۜb(P#(Z{]dl˾)kFڠY6<!@,; AInf1. !`\ծo^1ViWjȩ012pj kAiTKHhq"'ws9i4 . gХ|&x~1!'J (f{/9= n kt{Qq|QknZv64]N [@)k0v|)ώHK[h;ͷen,X]H *V-Y@4H>IG9=E10>ѕaj*3\ uݥ߮FC㷆ް\v\(>%w[fA7LVwhPfd[ZwۮʢU^-prQcbُgaX.98"ƽBҸPLL7+*biLT 9C.L8=hboT*g3h]&Q0HQ7b:׉nk~&Nk0trGLt:3);~vѵosܯ"xw޻n!1IUY8׾=KfHҳ p%,c_ύskR)=?9qAk|V]%0P-uktOCSC|q됙TC;M= ,BULK*ozOȸ+Oeo%Fȣkh8yHLX La[j(ag5aOE2eǔ>sYe@rWM?]5GlkdB՛Z>q"V^%xEտU<6 '`u=̘k+W-Q0mVy;ҒM9'~$e[|-Nx"\g Kҷ}͞|+?Z.d=߹x [rOA<+[I6~(ݓcl _mP=L :6=DK.z,_Hէ<7Ҩ(Bf- ^,2\}q{l:O$f)+pUy|&R3| Q˺Ҝ!S]KEh]lel3dӢly|?'/Cڿkx[چ)h'0Ҫn٫1g!ryeC sࡲnl>M1{ yFƖKbT% δ ı\F.%Cl:_d eYkǂ8_2:IcI o\]0_!h*=&7c|r^ic^^0eved,k@hDAd1#\4QyAάj}`?<쵁;/\c%3D4^|6gvyqHY -KW5#>NFR#0-ut4 L\❖3*?K\>038}u1f%(JCN: P]ob?F|h^.+{6†mhHv xHŚ% hbuxZ2c'&v\̬! ];c9XgNxJEq+x22\;|=$Pģ$sP3p%ak!*#Z'sH!Jvv*]{2E 2O=בǢE3JG?UZ a\zl]9U\P//і(Ĩׂ+TKpFr(k%NQ"xYz;1bݺ']8DF;6RdW7\#jVu/L9scR$t[VeQɩvPPٟ/>11}#q(p# ܑ3;俎%uijD~n3ly\ah;nA>s!|#>G:oQA i|S]v-79]ó n.̌\*ڄ֜8C |{=':bWDzt{SqX6:{<2nMFKBy &Ǎ&3G~U2vâ BĪgU;TՂL!; s ᷂rt#p(N.r1b|<}t;+ew燼2|yJs@nnWEhUq1LRW۔xw5Ib jx+af?,Ѹ$` :u֊;4 RCd#΍1wJQUQԃt5况\,%'CWFp窶f ;Ш䁔$j ȆіW2P=t:\nҀZ:9I"P!*6!$Mugω2 vڝ{ ܇Gw QZjafK- -c/P*`TJ [o3QdU|!6UI _9wH8s+n!>sf~6]pD 77x,UoyV vZQڿha>jFCxC]7S6&NeQ|+T\F`4&q3E=e ;x:Irf;(ӒfUɋ>(IeA=o"{Lzy_Tm2q9 = *2>yդ~@vY%XaT<3ෝdpuFqR?mxYeFDB:=l6%xu { '[}m:[AMf3PN@:;Bsf:U֤p%gCa |*!~l^[J kh_dhe;[F twMsϕC1C6`[]V X.%U% 0*V%vڵd .˒*hkԭBt\ղ# !B%ۛj< ؈u(OH^CmE ίg{ -985fvBΓ_^51Xe _m/`%˙LO]MClbY),'"ՆQ{ y#N{[1'_3\E͒F徂[(K]fގ(XD0k D9>*mhVإe^UlPŸ4'2-%ٹwnPK,tiXh.)~7-()֐Ιʁ&.eQn\ENiJŨ"&gf+[bEbnUfk e\"IgN,Je[8SWdj|b{^3_eg0B96HӫFV%Іo7$x VWsܱ:A/Z$pZ7? FSLO?t| X1_wb!es,-94HV24P2ȾOomUu怑\ihOp;HJq6X;1LYdXݗ޶c @N,FD,v`voGƋTZ Gs&э_ `uDh^h|5%zcO$J1 Zj3IhMh-()`9mCOYc{]y:E_u- XnԬPFA%ӽ!k )),/`gN߱P fڲ.:n2XkY{چ1?0Aepqv8Bژ0 1@9=#9h̳ x L7J#dtN19sRx`!P4{[ߡlc31 Ghwܢ)dstˈM?zܘa}TJT,)eGz1`EG]#ˉQ8maofC#4NEMG3%O (QDxJoEUWĽrMUvC5gC*~g:ywgRJPpDgq6V~9yZ"b\4ptCP笮 Y:N#tOhMv̽I'χ|]!р4˶F߰>BKDyTMT, ㏝?Z- J4&HF^R޻B-1OH0\_/dPTڍcT-Kl_(EKdIV_v@,HH{6VܤZۅ BLOX;[xE_, {`;lzcrl  ˦9R:} tn3.]cfVl^.Y%nٛъZՅD!Ao}s﹭jE9mVttSY{.ߒO dz| 8ƍ\Gͩu 7ۇ0{2^W'WGJ`Ɍ[E42w~CxMz]&йz}&r|?#\jij"?JMD`GA[wcyx35Rlz Z_.@0R&ԨzgW&CX$YiR=u:IW.QCb[ kWř(U׳^-̓xVmWJwzـw#5*#do%Ȕ%>NJq&[ QwIIqn}dFfT Xڻ2QꌃbN =bNC=sog@5'%RFꇾ!WRd2.ݎb<];aN)_vFј]Նzڇ̯c<[i =LPՅ|^)@{;3CdvH2Wf~= ubrw'*Q "і'?c'7M eMXТ;iIJ~4#:YbE)YC;Fmu?d&jluV1&h8[>miy}f~J PU~W8Y'猞v樳*˘yA')V.25>A"- NmcBKsrPuSEO.?Acy92%֣c䛇JnʄuWտd*Dd_;і.,@m,q~{sU#]V4ҥmrMgJeW:p `F,uݢG{mQw>k"eok(rcVzT>6 ȋ?alf$Ä+T2n ;z@,N^J߾3g\uzr32\Ot9,ہ'3rv>uBZ9? 2nS]P*=+W.8h^B pyaT8 S^ZE\XˍGY$30[#Q?l\%/Po{}EBJ Ϟd^*,iœZʋ,dRtn/AR&&pvN$y ~/.w ۛTVw@Y+?.^feک+ qkSأ55Lt%u;3 jX9W!EtuH=g) 9@B+fx&JO'`.=Ή}a }NA UvJAzbX.p,w[Q[0!'/6UlsFKGbVf[;Vү tzܛQ W;tUB+ɸݬݒfcD\%{-%VX(fxuc+M͸j1q9iSm O2C2j"DDPƗ7] \>VNue%&q:>5}ҚĨ0"QIF𷞆h̥c q5Ió ҫg?) rڜY\zC%qVC<\C`P9 2?r_SܮRj>~CLǑ'Puɯ[AfSκ8#ؚHWKYywJەvT8;v_ư7d& TyA:)띹*z֠!²sJڀePukW+XJ1 3_wW8Iob4gܞ'd3z${5~1`M@ғ5?"q= \1PnhH馅>n`af$2O|lnG = ZVrÒ&\cy(ASNfaXsaD?)J|WΆyp؜Գ: }-H7F li}:\Cl>yJA:0^{eck$DR"V^iX58OvZpk5?7 wX WCWP`3 d߿ Ѩ -%p8tg24٣vpX9 #۞ jJgxO6AUly_Ak^9(xfܪ,ǻ70 ljv7DS+(ğ痮 W*nNw0yvzA΍7 %-b՟iד&*oM}+lbjd)A}0y ;`_ZO٭()$A)xӳܬAJBRS,'4\?H@vdcC>zw4H]NZrq3D:}j%:E+ڋoZcZsC}DPf%\@>P~CAcD{w3Ot(JqTQlݽl( 7Ί"e;IX)PisBNOLW_6@5bnY73va@T-]m}Y',\=z|eCX;)˂mKl_C'J1:ѐ[zѐ 3#lt|g?|;x?cƆa7C߳/VH;\jH׏i0*٣eTr1m|ڐ&0cB+_ƭJ/b_i&3wmm)CH*H݄B EzYZYC4NIAX؄&5Fѷ6W=jc+n 'lV:%YGNKE}DwXx_\a'^(S:ȕ{2FQu[cIH#&cܽ[S@OVBZ d鮶I ] kذxʂ7 z$֤uѬedg)1mqE2EtD~(V0k"(){ŞzB`<nK[.hPxu2PHHPLɷG5害W/#V&8* 8AL60:. ",@fyPC+b z*/cF8ҩ:eo+!e23@*b#JʨCM[6ĈEs~w&73U4fJ;맡^1XpQo66}St 8[q)4r_| Sg~[gūVTcuHu88WB 9#@i߱ M8UAX\D ?]J`y6ͭyb80ӕuW?Opy!*K9#)pؔ.z?FtzK?8ydeZIkVBzHHU )} OùDКe(u~ʪe}ʯ.WݡyZ "x D뽺_ WmjIGTgnHefTSHS Q%V@xB[]P©\EF0PEǃ :'imA /1^+B]ċY/ gTo2n`$> ?[s?YCgB*7 AaN*|I*S[akJ|j2$|htb#[IHqV҇܃xҔO L¾ܤn*٘O>=A o|ޱ2?B](eEyr׭T22vAOu4k9{^jTM/^iG*WLiuAPJ*)aM1p; --ڃ?$3/{'D_\ vevh˖*@wv#: 4kQ<3Ӱ#~ +E4jd|_8ISHgRpo}<?d p_e .YFy '-=WN8q^b<0!3e3P}Б;8eV@3:Np^ QiCNk@-1`3roSmT(wrxж%t-ٜfuv@{.vvb+2b .?PH#K I\^):P?6IGk ܒ.ݙfD+w'e}HBglRMy b78*h,pQۧ׺bף/^ֺnJN͞8rՓsZ)cݷ?j( +ObAU7ZJoʌ iVTV㡷˶i*Y(F'H^i])Q rmRB D"~uʹҳ;0dO؞̣6'ncŃ)$RF"{˟"1=C{ JrX^{/Tࢯ|Y߼;˯Ϻn&akW?QTۈL(z=j_oƍ 4X|=냭`Bt4(VcKW31 Gw s^KUa:ѩHc[~hak_W7&!P"U#Y'DO͉l W$0THI$RP1buK_9N_h96D~5}9Ї Թ}`;%SZh1z8yIr Zy)58ZuS|!&{+LiY{ܥ#kS"ymh~G\7.:Gt{w <=}4P^| `˄S9=yKԟ£ 㣤Ĵ%G_ì,O?b)x nx@A1T(PWi*p03[>]]"Jʼn)i[;v/ 8?;2kƵj :;)+`>Zc[?8UYF._Z=F'qOu]M%R)%t. 0 _>kmy3)cbsV2G`^8q/Kegqu&BC9#ۙ 9xN zW:rRUu|Ф9f~ (Ykƞ]#bKKUd~JEz AG$Ӵ+d;̈OLɠ҇ eqCYB`&s)Ň;SM߻]-$ކ~j㌫qv-B[x 7Nt+G i{>bSs%UE[2UNJ$ǘpONA)iBW;w\"b@Nw:1)kV| F̸f*}4iת)P?ٙz2lP )T}-돠k! >ķU bJMXY)%lcUcēާ7_i_X=r9l 5MN6^P {w[_YRl4܏]X;TӲ\u _bfh 4cِfuz,\/5Ĝ S?jNso?C|hǮ369녥&uƿ02:KmPI^>~2`&j҃ʯ)iǏ!dҭsAi? j_N'dQWE zXZ+څ*Dz{Zʊ5hC3 b'nFSgbzGCb9 ƽMGs [eb ^nehmhbhFs]v5uESJ:^Vİʫ{7KF{ TtcOvKdQ˪ 偈ư◓Ʋ&S#F^ܡ+W չl6ۭZᣡD6Ws f: @ɯ!lt|\by#,Spgr{$z9:DJWeы7AQ~x:CnHvigaj#@Iѡ PKe=9iЍr!8*y{g`۸ag])2(يpA(QNxי[:n3a=bF:_5֩~ l )d `DM݆l;R59:K[yWm{"f^%Ho)#Q:*-m*ؾС`eŁܦ峸 ~C_ o@Qf*.}W P0:p!IUgdMLsyh02:>2576`6*P8[cuEre ^V!9AШ6n9vķK9FjT9ӢAg} _V;ſԾ~-p>D L8I vWZw6х bAI`78E^QgQl$WPۼ cXN V?-5-jFߖ'IΘ 7IBki*B\ N>-嶻ybrI{mɄGy,1i1r a-?c\֐-w%UNxI; UP_Y3?T.d,JuOНD1z;k5)34[q.Mc hmw䟸9='JPIɌg8H|؉o=b>~~C`&/yFd"I ~!0$ԗqFIDœ)OŠ Oϟ][06jgkM8EvM%TMlؕ]vSO@kpw fHb!q VBWtOoNׁq)sUHl7K^%;!9s) ^BbP%3$?7L$ r bgpq. boͅr}> ΁jDn=ѫ[Uܲŷk{OSCj&15H6/ẉ\Fqd"W5IQ;r.R!lZmޓ^-{ጶxm <)ۤw[Ğ17%>?4or*(̧SwS!dSF)rI{l1z+A Ļ -@]Z&%RoS|mgGXpY|}8E~d3 . D)7L HE> @5=t˳aDSGqO*NJUis>9"0\[sv7KfZx J_H5  Kʇ%;BXL<^t_kLp):鲃o$ W]W OnɿЏEŌE XhcǤu;c.>ByYT{vX:D:}>Z,È[QОnL?2uA7΂+;?tڑ=Cer䆅@&ł/ Vr6 Q: #:D|l/ı9 Y&.sRlKX&Adn #:zsq4<&QaGrEIG,wk:4V7Dk 7@F*¨8MdOj*@&$ s {S1*#wi/$ eX7$ s{:nu0D:'HkTe5^̰NXzl{ lƵ7|ΟuD*qUzUui+]azMR{@ȼNu5Vkg:uׂQ0bE~9tU`bWaj!XG=Kڣ6ܞwe4^}DbEp)Jof5kuPoF MZo| ̯x.}қb\`2enNq#G,]`z/UwC*ʍu瑯 <jy dMrUw)bRZiW٬=w)%s!@/; JA7A \vHz(~8(H t?)I V"ve[Zx_-251_}%b?g?NŻhpC'?-/lWԇ=/fP#]$x .h%Z6ALcmXh4ηu /~6g,d~LCmps %2ώ= ޤAr?*+f@N2mВϔ|'!!ҌTL0d.UE |֟GLqc@.ȩE0qiu^HJ>_;܉pohű`m-*vEעNVT:@-{I;)ɹVǟ .yeE"Radu4A=L dPA#B )l7G14')M|k&*~aq)J# MY@g@Iw鯡 vJkDF_G|2 rL/Wp.@]Mfp;ƞR4fD 8fkj*ߊRɁB1|PDF!\97W]W Kc}Sj4O&ul`!CŦ{5 5qG'!?FDP|N_hM>[f-# 3IiZ^`\ՇǂgcW '`x |3hޓ@E,Yk"L"3[-w;fykDP*#HEs6@)o.5N vٮW#ױ\{1ۑ/N1ͩF70Z}yN7ȍ{< -^^0/wL׎ځA<UoS|tG'bOWhOgHvDM5f9`TVክa~صtbbsޠNz[+%bP만yRmF!拇hJPfΛmf/Qߣ^wMd|(Gܥ=eKcH٫!|o0Czg'md_N*%ǒmG9|SeH5jȪl-%#JJ q?F.5{Oz+hD:Fx5XΡ˅4SYfL@Ui^~+$^NDRvs^n& d}+gYܭVa<R;ޭN('wyU E/3ajb QD #K  K}Xyt@N֮LNE"8 3li[L]zkUT\5p#LU<7Rc0$ƧPx)dώ=qĈ]_@WomuUK(hQ-kҼC»ҚTlWDr-|UffZ$g@J\!"tzo`l\YX5w<7hN\9$;T*}w8݆n-5~8[U7VƗH]לd%bs&i4) "қdbXir1 W+={`+^I}Kb3JJfJ:KH@/\ΨP]`~vF/`6"[13dfa$v+1̣ yU̯\Xs1 FE|PVDKͨa^"qC0!°;o%Ǜj?c2hzn96*'KF٣Fdaܞo: U{^C{_'ijS%Hj _=g}˸@7]wsy[uӫ%uiZZSBS2!bK}8@ß|?y;3=-=` S7pJ&c{R-h1BnɈA"M3@5#A%Ԝa6!.&e9$9x7}^DjhŲo Q٬/  t0گ w<S*Lub K>t͸Ρ=k:+NWg\> K %/d# e=:+~Ϭ@P.{x K we2GvTjeE-*Kۆr<#mGE5 ;x%ipX@]o9i qwǵyC %"n71.e9 )'Id.]dz;عZ wf-a)OD|kqˆ嗙{c1͏K&ԓd6\u*DX pӧRo {1sr3djK29;`Lzc ȗ|pr7!vP iPZ/հO' !C 8(" =E< 9_ $ӎOxKǷ^DJr122?g";Ǹ-VKnYQ" C0):3*Vj̀\biRlh;}+m>WՀ2>o -X=]65̼_$B~7ҩjE O.sP+Gn2Qvs)uV15x\heFAA&꬧ˇׯ ^w-?3MgBORug4Q(ǹ?|JEcYPQvH?~ʅJ~"T/eTIa൶_uQi٩JzMX!$\HoN~r`opsY3Qtί@i]3`z$w HHtj[̄p9@+g/wv$]2_njؿ^Ej]a+(4[6:$q$2b%b 3o aAŰëg=% O߹ט`a6grpMX},N\ƴ80*)E<13;z"7P VAY1ӗn'Ek gOWHwqs!uj)z{F9ғET#ꑹIB:+ >@PɗQݺ uvw" 1ONdb[NłJ T+C x-3j`"W ͇m0\d%؄qF@*Bqy CAN4Ĕ*W~{s6S}Wi۩" 'd/2Sk 2it٤YOu k(h*D]3o8uQ7:`A$'r, /Ꭾ]̏Z!GyM킸9tS>'?va(UDʁ zY^GQnprp_wBLH4'$##f1 jf?Z0 4Lz7qV'ΆE˻4P)l6F>(\@x̕mm5nvk,El{GLECwD26:IL]*俛,_@hT$h0:.GY yg_k6y ē*'smG&#!sL Tqql57snP{K XSU=oqY8VyqiCD<A(~F&.O5s YK4+0e(jRkE?],z׭)5VZc:xX?1H{C^HYЛ(6cS\߳l:fcVk4JN%^bqB[V;Wͯ&/)}QeT)xX9sgr=ЩxRiQ'_]DSXtF$p]@4cu),=XV16C,&7̨Wx7x 5rgN*dnk,pkg5U$d 0]Z?4wum-_~q,'Uλs1CiLx˗0.-z6*іp:X7KyB7 _O65ö.YJѥڶm֌Al-.|B S}"e,BECx1y\uZ 8-z03~@``h5(whhK h2?Y. Gw[FYC4&}/|Ʃ9XJh[q_*:SΩ1R)$tQ֯f;V̐l$^D/G"q6{3h7`3e-o=GEXlbxdd =';Ev;T1$>~-`N㜇lJb:W EO]FdS!:>D&.Y?9n_HF{NRp9A©C ]Cu <2m]NS9!d~TtDsg!GƸ¡GXbwSJqiNG6_w{<{t%,fx&IGc]Y Zs%!ǝ70ľjѧ*Rx~8n3:KUv~_"܅?Ho! Hq* R(  ieC1Z~ÄOJܔZx? ~l;D0|7C.qc0K+n:Nu^ Sg:<045v96D4GXV BT5oݦ+2\ż0ILiΈ6x^5[u(J&h!ryWcj &CKJǨ# 1ƥAPS7ͱ-C<"dS J׼qQ^LަdGQK>uwyFF .y1)w*N:̗^ ҌaBvvw z!貽 ›㬞0DH@(b2Y^~i\=Df ugr' Ša88)/ @SoM$eg(_ow٠Mjk/CMfSpѱĉ+kP]̤kH.73cV#l[|_-G&#x&kΜ8g=hzKb%7BX¨qPYpTzd*X 1/W' Œ?ɓOn/CM"1*4>/MF?Z '7.8(Qco|i T77NVje_Ea X~'F`X2 xeIvL' $'68]f =/=iTr[Lެ!UӨi,35)q?heIFx?)]cd"xzZ_@ newLVQ-T +j>W KAjVV#D:&T(,\9PV=@%0Vom:p4}}2Rj{"V-"]_BA2'垰k(0MJr'i<5-]JԆ$܅H:ifF8/bwEZ>۳ յ $4e|&L;=lKi(>iRqRjn}NgbV̽@(w`%T~q RZֈ2>#dUK].'0g^*D͆vT`9*g,(tuhb%xN>7Mbv\xc܁ܨ܎BYiCmijxx3D_{RcL0RM3r7dkIJt>k)~m闳sM<3*!?M:JYxeC" Sx"ްً̖ED|0sl1&ȃΝ$Dɓ?1R0H=-Ci *2SC|" +gJ: P^HdU\ ȃ5Vy*>ns;π?u7 35恫z9EШnUYp;Es5"b1ZCTCkZC/"eh?A5%}]) Rg6MS{2>!LE" d|.)wKm܋~@|CvY$4!5p"׉9D *pcc hȗVHr ҄_nG0٭) R"c$6Hv6_"`˛Ï}&{Rd+ D_=㧸?üA,P8 \&ٶ~:X[T0ψmuzV HŹRHdx֓$6qioq,,.Ee`2F?pžW 8O^el2~0B,("2i [ 'Ŷ'Ӌy5jϙW7W+)bM ߔ:TׂG0(l?_0jS :R[^'=*O9SS=SäYzyT(l ̍Oÿ ;c#>j\̊1D(@JU"H[3ͮ8T\o 1O4_1NY4?1_hӻzv%!P#CpZazɮ?.h!hRʖ|H f<zmXKH+g5<f,gedsSMo P>uBL'ζZudڇU s*$>AR#~qΔJfP6GW;Mrw=+, 3 t4@w!?6d6He`u{f?_KK?Y:aD߄^b t(\3@ksmدkȃi_hqڿ/].u/of;y"e"'aFX`FV V_=UKw9.>{x}L#̈́N*6+=&?cIfbG+<8Pvу{4+2Rhh;7K H_@~b¸z{O uz: nQ7Del2ɥ$Jh Q Y`B:%&^}Ǐ8e[X`U7q,QRܯ}.6dR.A;E72!cpԁҼȓ/. v#UI2-fO9II$U6]ХO5tMAUĠ=ד.Aa 6nMF6 a2$|; [9YBߚԨҚ^%`^ߚ$.9#9ig(/g' {PDŘr% nζ=5C'4WwL[z< ilUxjh8SžR4 =Pںba7 SPU痯T׸ő6Pߋd@ޅPPP]=m;mA7^B)#rs/ɨ`x;Jt:bzWc :"N($Hv 7VbZN"Zn]:[] vkFWV®RӻrdŠh!sTج9f(9{=iĸkY5;2GJ#rY?ǛJ2a,sTpikd޴ |[ޚH;im0~OObg RKϒ4~zuyMWW{Zg;^=-S'"4|A׏ɭ]h+Ay=ဍ3=$.>ⷖ!Ueć:-=у:B}yQa"^/!qP0f%#]-h"Opkc@ TTg,򼪴/:<Eͥf9'1$LuDwg s܀TK HyFtRA”NAno"R%ɽW (I|mܑ5/A qz~`/XSG7z4ɔf#ֻ%ݠؼYmQql 6GWDZp! Y7Z"1ʡV|8=4p`d+98B &h׷9@8Pu:B-Zɀ,خIHym{8KNn!;pbF -4ئu+G,~Zl$Ha|h*oIܧxhY5 6^-t9f؃pL ݜ:7x*Y,6DN$cR(efCޡY'EO< ']2|5ژJ.D zcSwѤDWp0m S|}=ʗ_ 8wg rPOH?e1C<\?x9-W+)S t3xߛjWJii#qU2>;g+,2F ؊Jqx >:ipN}GG\f( y6 ax3z{q x!!C%}b~-Ѳ ڂI`6D5h.kZFG')Q9[ T`03;]BT9 5W9k5rD,BgM!USŐ䓏SQSVE?Qa8m_*v(gv?O5v )+OM_3U>r!1*Ws:/o8ǽ/nxK}iu{d2}&!̅HОmjgG?NAM(PuTfLR}4ayݡL0"f]seQIvN+JDz鐹]1Q:nz";=8Ln~W=WD_un+kǔDf' 妖4G2:)rm8gp=Nv}5S30qGfb'NxՀUi(Hݯ2*'%XF`ID^.4}_w jLUbVml5FE_cy,z 6.U\Ngkg;ٛӅbU?>RW˟@ӡ:cHoc\\Z,M4]_*=;G|{{cz*`xz,xDx\;5<"wz$"Vl8Z'+fUWI ;/G|]i8rH>B p47jfO: ڒ 'pKxf]G!9$ɘc\{ }]SAnj"ƀ[Phʧ~ 4q"/ &`PmnNQ#Wq;ƙհώ\٫ :dq滴Q7d m¹4IJS~^U q17e =PeIoJ CP ğ "gNu 1R nYy%3uO ܿ vc G fEZd+O7n=˾9)U|B-Ow^q3V?"'H8/CZUŠ6R;W-3bg3˱n"\/eH 35D@+וFGY+TuϨ 4EZf[ U`P0x.P{mY"4vlͿw;sBI>_1>*h"\VjF?<`!Ob—.x?F`L<B6 ϗՎɅ 'ǺNWW kUywUC8^O MVh#>t"W]Zk }çhťIP6y'H4>] ^ҿ cu˞`.SkJnC[BZB`Gjk@nH#SIzRRkV@E*2sRM“y հ8xy-q?yY,|{B3U}>nykR8&jA5'/xLfQS{Ip!4 tlB{_oADh( ͙zX2Ia,RlͺV$Su=I< !k>Ժ*ɽ2H1a:[%2}ɾ Vѡ6JT[̕ V( f |VJѣ`ag;!S w%KYk#zn=tkHv &.>":XaͶ䥮( g^%UXB`hClpKlx[rR 4r3D/ϫ$nciΔ1#uy%M)EEo5` 7Om{IF>G\!AҌ,J WR*<#k.5+(.^򜽃M^R z7 5X^CoM D)jc0V@ǖiY_B.gB:W/x% GͷHVtJ(dC]gyTgeˠn LH+O/tm1Rns&@љEfԍ7kސƵy.҅'w+njfgPtYjP`"L.*v`5VԵ`#۠汝8~_.O}jW"rC9:ۗHZReb {+O*9i5 TWi{ysUԬCBH+qj6\tb`$'. t\֕rWpٲ(Zo$\FtNuv-}_CC ӸO74PoI T;XxEW0e kr1Z^#<8kWAd'T-GVԺB~l-6Rpq>$X@^}ȱ}sN̈́P0{i3!\X"4>%up4x\`P癯a1F՚ k:ڝՖ3JWG+ACrY" ,Gg߭RGS!&tZ 5)Re3͙Fj'Їd46S9Hk dh!mƒWwBm}:3 o%geA"Vu^6 oXmǙ{k#uAK/Z4,[_c7EڃZf/>1ut `~fnf֦W{:Y /PE?Л?k""KQw^8.K<9Tx O4~R, >ep8ee`Əf b+ri8 vGD$cpˌjGrqĀI@)S _Qq3p+A@9uwU E*?pd },G{uyiw[nqZvhʟFџ%29vo2EteJ1keeET ЭI7ێAbM#:JktN}L,OUA1-knݕ%EUˎkyr.֣Jw {3%[>JJf}Aj\WDA\|Up\Pp>q߶05@kqXWY h:'TrP/)['=zG8!B%BVZ~FHPk]`A`5{N7-zwK8e}j5B *nڗ*u lPRaG2h )+sHLIyX;R2`v*gԺ}{7o:8Fx'rh @UexRTR:ĉU=/#6;d Hs"*U YZE/0[i8TYO']Ѻon=re8Ֆ9 >Ddϗ"T`v۶*s-ܯ쥅0T8A@SuN|G:Z2I,Ug5… a0(`̜2W1Ryy;k! !  $@À(gte< *x.+| G3+S W<iFFjMLcIlfr4XR?Q{+p Cʸ:BIǕwr}oAYz4vH(V{OWUHG{ҮP "#e=fߌ jUfQbWC#ixv?|sE~Ү}8[/+!/4(`ٴ-/ؕ{"'^h~PIcҾ 6\Ci5U<՝36z 'w:BkT^̆bwf{Ҭ=J ҽw<8ntzP.s>O+Sl>SΪRxX@RH]S|xO"~G ֮wZBޢ*JT8 7@C?hVהbLxbi|UDaCYBKL9'^bD.YSyXTH"1iCa_WМi&Wx =ÍfGYd ĝE1B>8U][P^c`mq1¦q [8L/?.똗tۜ=?|x"i%Tf>E68=;+YVXV+%)9b&N(J6R!_H\}[LI6Pr0||{-2,K)KJsE u "6ŵ$)@e !#_>ÕPwsR)Fȋ QW]G؄[=9pni@R{H{$@\x(DjlfQFTNq0HEcpMU:@R1ɦzp ͧ'_nrkKHT'lu[|MnS#˘GUkSұW0p">xNԼnt8iJ`Wi.?h>ܙ&iA\%ܐ#phFo#\+  vfJ5 xss[UHrԦ؀xڨJ @*XwhP3#K~ ׾8(QrͶ #ڃ]Hn3괝Nƽ!"P {̞#UAW,[ ite.b?J**n'0OdUeP K5HO <ĿI޺`Bl: ?gbzc_q:gEJR8 O?beՖ9i^8yo1 /tà pG|0vۋdZ1V>^X#Pqu@RY_O+&?5ܻ*"wX94`aX${f-qCOiYrŚyD*Aͬ"n)QKR@65D@K4ԇ>S W6I|mZX-ܷclbQ#gyٍ{-'%) [onGƮ *񈖗<] $ [Ҍ$I$'ZI]{$Az=rf0SO4󊽞 [wɟ;"fR;y6^3jJY!bXʵՒ2ZJ ĂnJvmVWlA SSL:Iٺ4%RԬa\ߞS@64C_qY&L,ɇM\e]T3`9瘃J q*8#Ý_ nQ AzuPJڱmhfM{fh qSJ~JoH9өĆaqsXKSnH6kICrmjM)\(V[1V=&h6V2$DȷxRBDlDaŞ\)M}DXڶt=\}iBdש "Zd=aaܽ6޵WAüoq}?VYbLj-}< m>B1sо|N5R*.el~-UѠnKU3ˢٛe(8zʣ j+zך|Fs%!yN~K)3Z֠\ks&3c^N~כ|ހr*kH - ;{֑׵d݃{mD) ܇ 2PI ]2Nw%i بdz u-eLY|yը ޞ<mL7.ؐȅ>S8^V>o&տ `6&(FM^kQ &( VIB iNt=Edk3ҳ):r%N=?Ȑƹ5XmP@9P!Lzb o Y Kz O]Z-l檰u,uTnKb)p"ޡT+`Ta}n ^zA&;}gbqD}.R^-^>OG(ErPVo߹xo4l#A((Ւ Q:D׌F) XHR̡~Ӎ:Zж*eCoB IJ|M-ZT;Uنjyi;wQ/g7P0b<8fǰ]\Y"@&%cgP,\ O AX/uR;O@웟 T[13b^tg! ܧ7kȽ᭜ݺ*ߦyC9Yȧ/08]v /`vL[pjţ ǵ_i+gЧ B,B ;vw9a2ly3fGSYJ'iԽ9h7b8;#.v?XG)xMZ xc6(VbW*f k]6vtG zF}a E >ջR&muɥ42ytmkbcNXE̸,HБTg9Y)KVu `# VyJ|6lus@/ Mq;ITٙŲl(9ߓCA@BwB1_- Ҩ ٵ>yn*?R^ZCx8(CxTsµe@k]ʁ\9o*V.:ҋ2"m߳u4$=QjP l+T[緢i-tkʽtt5-ɅN=Z>٤pEn'[ORm\_P9ݖĹ͌QqW9 ô+{sgH=t,$vo0$ȗ]KQBrbb7nl> 7eh:Uo٢+krse9l+ӨB񴼃;;y>1fR`x'@s]1TK@.6^4z5uWZ6ḍO@ jVg E- M(1]9דؖ:aud=* ՚Fn$-OޯWmEP+P[)+4{`} N1%ƥ0nNھ :Gvb8ڥ`SŚ?aWn["=1?$d7*$O.hɞ̬bJX-Zh<7Trdαq(7mj L%aFіf#4BU~qvS:~6_fa2O׿*@gmd3/zG\ݬA  a6d΃"V /9ĿvI*mKr:||5`yﳞ@K[0vOzgiv!I*t[<_֭U%.\ [dp7daȇ;V]ųE~ۃMBabPw(>(gXq4m;g |W,w+U6b"BmW'<8gȐ/nWc?t!=CnN)hb0voq&~X)8yNL̠E V2fHVo2Vbromx,tf>]}ү u&&Ted 5/[@AHy-^Ο])3 }fB oeߴ $a ƴ "k@Z}Wz^.H-^BL!^6F"7mruPRpb{7`5W^SԤP 32iqW_rOǂ٦ fiЌ?)ԋ˕ JeʪRAE5 t% cvǡ`~rLby6pnSU h #'Ҿ <4Z]`T X!0:^eY [>8rt yx.I?:0̉qqf1ZX<>8z(,9&2`xS}TH_JSuͨXN>e-NQh/&[<( ўnv;ha־Z99!+4B±-dyŸl@/Y\u"5jZ03FU1m"6Ev@'@q8z?־̰)?`>ț(K ZwbڊK=VROНXo7WHk& Go|V?ktȄUx:GOns.Ͽ O;oR=ŀ2Cp5`uG+ Ns, 2{r ֒ )oώ4ҢzZc6~wTeB`~sFR:? L7eFw"cy1 T6@biL9pi`~ N>iB \C|qr  `O'2@w˦+i WRI+,;wp-˘Sp6`&||k <;B~,2^%#k V%E9[[!DDI,9P7%= PTaWBU;&PF1}a29EGBn&0ڔc E 4(EASSd A @ˋrq2AJ"'gX$L_C3<$#:?9Tl@OWU*N> JQZn8x'6}'D6TF f ej\A I&c?v_{\{~~F:jOQBV[5ddq~&^yFsTDQVKOtOD8f FQ50vIz+< ̮TsՑjvc[`\|Abi KGgx陳f8~}L$M5|>Iw42|K=eka%GBsò vD lgͼoq:lZ,  #&Eə n\}u"^ɇ/V* JP 97ƞuȫ2/A|{vmkY" Fwg;8]?1w>DKИ&=akɢ{Wn#ⴎ0Y 9}UU$C0f@hW wTfh"_@ԛgb;d|=DIʩh̏UR:L~{Z.mgqQ6*I/m3߸)Ta@DR\y8Ӽf}-a.:ўƅ}iɒo%ϑKBbŘRN,NWG F=y( zîd +Ϥ jaOmJ}Bf=*9R<8E*Nlg2QdzƟ;{|+0|2:>8i G[m[7 _Fxf@>|_"CKU$?R<\fu4p ]iaT\K|VE:`muJn#1p]%Ȉ [I JN_^pJ̹h2G?/]Ti6e5vJNtH跧iӎ^;':?'I|mtDhzF$FM\p#d?Yؓ*HN$fl^gӼvge45?`G=e2)h4J_D/çX{r> 41.\R#Ą c*s-pW,3?/@t]q;z Qq<7I۝u- n#d QyN B:oXC3Ǐ/ J#u@2!IcZ0p#9]kWR._i ٩xW?e׶Ȥ>!(S 4s"tVOv;IEM%Հ7\0sZY<\˯P 1S=cm3]Oa:#0>+ꉤ7 MԫKpUl%]pK9e%nQ~,o"ya~ł~4Oy4 <횊wW{*P:6c I(NmQ]-;=w!ZYΰ.ikH߸v7bNj d-5T-6,9ru5@=mق"Xs^ &*j(hq+;U4.YUI!:(|_y̿KbbƧx!-\D %v]mV<@3i1*17ۭaCEH% k}kS- ܷ4."D()aHb26Z=`7}0:kXUF_o2yҚ:-~\tW@$OB%bD#"m״ӷdw|"ަ4Qqw:[Ӭ`Rf^Ikk1"/M.9.)]h! ,yv 0"LVuS\$O.{%_,#͓+ "(UJ7$1`pm< vu8AomCZ]gHXhs }D?3$0g/m0$$ۥQ?K%}0mzdˠp =ف>)zOD<f(п8t(Rj8.E)q?1f*gsWϺbm8Xw;.d25dGgv c)q [}aᦡkPA0ugɀ8J5..<m;/!JZ _N+!!w#*=f6Rc[Z,y^z HY&+qcAnP0u{9NX.B`Se4B#k<ɧy "⟷dP?q&Qkf-䥁u#O廔7ƙRXk^ǎtH_#g-?DqU/:MfMp$.Rx§X M [4&{ɛݭ z.5}szmLLԟ5,L+ v= Ad4#LIi5} jT%y?B2BF:01YxNJu˘*y@5+%63t"јeaʌvZmx(Y'12 ?ɨ`?K@IoT?:"T vEz!5܊B{%>n#Gq\AE]b@쎾SkpXH$Ц`mG"_8xꖪAB&r6]< ѱGR?|QLj@hI&vQ|LGəLh.G]4ʐA3&UHЦ3KaՏgJbn>U$Y&ܾLu|죨6RI%R|@WCTFO`v1MH_DZB{v 1hR+S>D~6>Υ8ѦU23Ϟ|g?D!ɵ ~`+ڛI8KlG&}z@3H~~[bdzLg3[sW$t/G|D笈4M=wJS5(9t e;,潄):TсݑMtԿU5:AK;B9'd_r ~< ж]q0h9՛i,?JŪ2VoålMۈԂmQ^!vջ(9Մ,  _|@;# F+D &՛$ގz?~GɖKAEV<pW[#WI)Tԕ62Y%F^:o$VӶѭ 'd`aDp,pRoPpxj#& Qv٨ϵB:Վ+4&+ɒś1rؔp0HxuaHHv]'t }YWR"8YKKAj#J Bd`fxY3Kn}jWFWT2W_IlAlz(r->0.m_zv|DCg*[+Sףu 'f9H8?mLɴL"<!}}f$Ľ_0ʩ[?<[NY8VN`rrױrc <䯤3wkU$u|4BuhAoq9ѬX^wRVZh<:ƴyoxH}L5I\xy0Yηݡ#5׊akdh4'%s)e>g[^"nJp7s|{rIߓ̈́'+|IaXL$f3,Fr< D޻檌il;yK6F8ÜO:E'Hյ滷)zT!? )Fo}UVMht~l:;&JJ_1Jfߜ9:kbb´`mPS-Š-Y<C9lfԈ"@0+]%ai ՃI%PmUz, hn1&m0ϠA1x^@&ḻ~(GͶ+kq?-ˡFt>7rR,S}!KT?H?媟ľQǰ1bvì$xcgUj`UVne.?j$reí:S S\m,`.zgp]N=R"R?$2TJQh[Ȃ7qTBJxvr0U+QR^Jקt_򈵇l)ˤ׏X_rmlb^FRE lR 㡅&7+=4i=MkL-ap8vw'QFqOcH&a8ĭ<+>. j/7cإ6xWWT wsߟ4MJ.BzIJ;a{?L ?^45瑸ċ.7J {|ZkSZ]6*RHӨ}d olf$!=SM!63W"mT-*sFR՘x@3ɚR_-urcN.ynBMӾPnaȪ@#S_>dݕ*,ᣕwHhŴ9V314s%?tWN ń!B@|\U'ba4$ \l؀ +wmo`\2`f-96s3텶4nN94k}hD\@FxRRz&X4]˄1N'tNEmyJfōی?M l4Һ`U4ZmZB=egcP A ﺖUnK)|KNIPuPv3Q ] C{~wglݟ'jLe,ږ+_;E ?oq`pyUhqq˽#Y.;9\m2*7>e[S<5t m s&* B0%Ζq`!P"\#QMaEYp9]7R/WK8htƊRNk|5)iCU/&j]lzsfv|yQ)"oɦp%?WcK>krnCn`t7H0ϫ^;,}1L&6c{tkiL[,JTDIBQ1 v4L5J!Cz8`1Os=h0w&]#0QaH?&"%ޑBD|n|m=ʓL..D=5ϑן6y>nB`; &4;ISe¯p(ͽh=.Q@o^;3拨-]s1t q+M^,3bʏc5@zu*M&+^CzsFK'hNakq-Xd. qRk;`NaC`-Ulũ$X*u~ 3 j%j 5(LE1Q($dQ xr"O/YG{h^./`c]eyJ^Ӑ/=y ٹAԐ:$cH`mSRDO04:=lGM-F_PBPNe3Zt€j08OaN%JXX>JqUTm7K׍Rb)P<|>QݾEȾۄ]1dZfgi(%}1ehRe6 6=8Y֪MHd9ήYC6:6q;l?>v$V{Pxp#-ۇoǑIL}0C)! ]P6 ׃FLW&H²;7S=7f'dNdKnW"6´ eD!xV4nYQ: w!y%XᱛfS~^֔Z=nգ1PPKiGL )qCB>~}lh<,˧4cC/̜'Ⱦ e<t\TvgPՄxڶz!lviyxOqf?`D)|"{7b[cA(znm軶KM0dm桾J$l}}χ qņ쬾ĵI:`9º]D-1ct϶!Aq2ۼ t).}D7ġ&OUJ<ϑcW+3I-PƝ! N9G(eYL-]6*0e<9IG>&Oz1h}(Y@ﳛRؔ\ ;;T$JWdx"naյfYD㦄8V6] wŴ,m5 3OLzS"R~ɮYZ{ wӝBbXAV}H-k;-&c!ԫplGE}qn8ruFEO^,WFguD_A&]xd}ttw>Fw;|:?(B5IL.@[TsTl5֓ ( [L 0m׬#~XFW9^CX{)zܓYЖ}Ȓ Ѣ(v, o" īovuFՖټ;Ѩ@o+_EIL'7e^+״-)gWf0Yʗ"~/)3+_s?gjSʞ[ϪL#gHɽ둷l>;+Z0N/MYVxVYqN֑KQwo݃TMQ<WoGN8hbx:3h 5U*"K(.1G8M$8r|J%ĒEK|qȤ֓&2 ]:]~R+;|37C.9Ľ Y}ɟ={;Ƹc? 6Ѩ@&e}I,A|87veĨk9Xn}C nH0n<, fiA^n- tN~6JZadnvMK1/IO@c/BT&n0<.+[:X.WҞ}+U.8 !iƵ[SOV^ĩˣ܆PFY؊x{=( t1AOiE/[:ZB~8r!cDrk.x^($mO14K5BC==-i/$qT+j,9GG?XU'751%8h,;BKęʋ6(:~o0DJʂg )b7Qְ"_n^ʦhon2D]rv4?K. `Fs㎾Em;K]oQaDW+o59hNorClap$b8kSxR@#1iץT:. L+_NOhZ b-(XҕW^9U J˓L7LޕJnk{_nB$qձX%{trhq091m$\n9{}%62mG8(b^}1]s)oL vF̭fq d[t57+Q_BWcö?{Te·Eܒ^;E] C.M1 fv+ >K&qq#n=Cz`2Vf 5ᐼ*K0jw҅O8gHca dP:- zpՄd]5Ss=._G^<ճf>tGDSWg:SРYTn9KT#n&eO" *of/Tt勴ToX/e&DBi{<93˴̡J9?l\J"H8@0C{TM|Ӡ ZKk)*|S"譧Dk%n8# ibeC7?6 J[@>\SC6G}Eňh&_2 = .iO.sM_TZG;l"Q44::=9mAn5CYM\|tW B͞"$v+ǔ2'{y"|Ȏ{ka|jo>Rla(-Iz,#Ϥr;/h*eJ-BH @|h$`@DEfpp^S`#C2 i}I*s`3,X-V#mFu!S;#kg^Sɹ3,BQODyO r%M zk{PH>9>G}L\QA]du=g `O"#H\lMdtE`6%!(%ߊ_34P%$T90 3 i  %k i[g4Bneh6+5''MM3Ndxs a1DcΎlG6[fᄎUjW%9L` 2yoZĞ3 "ǡTZ+j3 J=.beNV9@A9m cYRख़34+b;DϗIA3y.k 9_S;Vͷ$g}*uL'\5pR?OJCCr%"JSG*}r=^b`1T: Վs%$`3){#1 (NݦƞH>I}-,v(0&*Ŀa"i2փ5JH~=ɈѺA4ltuXaZ=OCiN_#/Ezv]j}.G5KV:КUI1[?p99AR[+߄2H/WI91KsOyc齞QL`hW]!)>2r-LhxW>CC*7#ȋ__c G53 %,r,81oML tyIo 82I#w3ex>Ԯ%tWAFice -U2> ~I延p/(L}ӫG]LI3D֑+2v,#SΌk -ɯF;cBͰ$`V73@EϡOLhv" 7掲UR=m[nT6qWNx%ƪoIh$LďY ӰZj Td7!?c9ͺ^l1Wj; wJ~h18[lVr7/]x-H2UE"zeDY&(8Rl!MGXrE pmۄ yP `U1h;OǣDaMs2T^Xg{8Sd #QySi*|d E:WYaR-n dʺK|YՖrD]SS>l}rTvA_JZ;eRƯ}NL&MPcPǔKDžJ-l4|w<2 Ѹu>-+Ž;`Tc^odGJ]bLpf]%4V?=`zua牌N?-Kq%I1lrE%53{v^d}?'"8z&snvQDDRȯ(,ZTt3z(|[t*aX5 -3T>7/nOcB.U9KfkBaJ(~ fe/^5%vMёuJ&0R $ΔB``/ m,;]~s_X~t39=U9~u֯+w]-/;HAG%Ybu C'U@Q- (J.kj;w[Ş!噷BRs'9=~(y&ga]?UZS,ч˲ka^z> ڷU܀<c[/ᱮ53#" ݑkN 2AJTɳ_xjB$BJV`vwfNyj;Bj )5-ot3Sui }Q]ej:aZ$oSQw쑧 g}HÎ D\?g8GUe;{$j &Wy Tbp0W(pߘַOi+ "f-AsU\$-9mC!Rc]S}I?p$bc۩;,k1ୀLAK7f;UD(z<`.2OoLjb~#"~)p-'2`*mpHq=9,*6V,Rlyv4lZpү_ݶ6$:Yx晻\ @L6mm?乹Hha:~hL$EOyG8ŧ\AoBZmYmojHr"s'57WV#YݟR3glzO5dy6Y &hqmZϪ_DҢA]kdjEt?8X,l" d &QF_i+D$xZ^XوugI@W&*Nx0N楬2^v[p:Mɢ7sjjafyS KֵFGj/'|FJH9qW'KyWaB.zHP{`_Z t ?R?K锋ы ~QntC;Iq=ZѾbz;ܨVHtkc7}"?TpRa O!Ż2#*lڋ6e1۩aQ^w,Σ&2aA?7O4eKMO[0{۴@wY%kGw+ sXaJ PK#/9Ǿdt9.;~v/'S*X^̇*і|6BH ge}믂*'㻥zqܤNȰk|菤 'ubpoP2DX:M[TҴm@שcfMk@VǽE QɃTgn__8RF^36] JZWŠ.&۪&#N|dX^{gUQT] 0DqHcUaj$MF5%BM5e=n`ZF=GH0z&0~Yп0T:p!z!rM$lWݘހT&Flҥr[g؅@tbuh'ϸg9ۣI hNz C&[W&1p"im̱ ok,3^-s:EgFkoiSTCJ]G*" wiBg|5Jc6{ Z)E3mge΂R20)yWUп\.<{؛UL5a8/γWFcWN\hr%nT/!ptzh,&;/H*Ζ41yۢ=ոۼQD@W-]Wnge_'WrD~ wԧ>2/X7\IǦ*tnWL϶A͒l'*Ft-"!xT;;JsYRh@sfa7H(轋L *h`XZvx!UFRn:إʨRZ*a ~$!(i"H(H.{ _mwev *B,&Ȕ4 ( xvʪI/SvGLW4,w]ٔ0D$M Z|+]" 9?4aA2';5µ/bqCڱf>[dvoVon:$͆ʤ#v*,zW}~SOZfau rr+M#u-`U6^9qm|^ir rnkj ك@2*Y,aJ ӌsBQS4"r(EMmlP JG&3U/Vv`W s:$JAɴ{;¥%wg3z4c= ucBn&{:l>ݢJM m K ҔFL,3-y#TkIqO`v0@⬢̬(`h29>ݑr ktSD%P( wdS{ )"uc߸BMtͺ#*:s{n=kMR;QXQ꩕&{Pz^3@*GFa;R?+e-y4T}> V.),p!,lBsĺH@5P ,KpN DtMMC.5dj} ŵvOR06VҍMSYur+&f دJ D_4 %M`sV򖬵l|rE0WIYW1]Yo{zBpBy,^lL")O|Zw;VE\C @=ۦ6XAZgqw<S4zDkBRm Զ*:f蠐L˚o@%Tu{PT D? nxnXyRg\ TG,\:t+,x+"9h^-7h/LVƣe^@mf*w>(cIz ڍ49jtx|Ԛ$ ffmiPp@d,O}8"LRs&ݻ EgEj0~ q<]4?TZ lXv&a^etjխlfwMŸ]2bFbJj]t#>hTj>^&"C= Ŝ%MXt0V9Xb51FmЩ.4#zpG!]2zeu2}UyGGXy ?RO띆0PŲ* b^oO/- =Q:ۚQE,G.AW3{T#t4v`8Fm?PVqM{X\AO`1VE镲bvC/ה/' ꨝ34 g¯5F$~5*{F~3YavxPmGꦃ] y sFI^nEnSH甤8#]=kzًZSpH0zTXKp~(2ѣu; .:Ѡ%ZOc %M ,3J:@wy~ԩuAؗ+^E%! Y7|m;ڰF 7ؤusHW[L9\P!DSwԑU@}#չDDC5?Ljx)̗me_5NBlc&=%mg>=3?}6}b[yYčc/zC5y_O7\v-=dKBi Ny6WQA%quGbR؏ƓV^lcrP;D jK8/OLMPVItF4cGׁn8Y?G{AX)zNҡ -JRHŁVK.A єu%FN<]oa*]ovq0HYfhҾɷ}ܻJWBjҠDqUr`UmN"(Q"5VcBGL?f""kB9ciH~S s@ըnÞо0Pÿ6T?7`4OUuTM4`1o-TX<7_1_L#TMU @7QoL p~߿YCx2 Ju)J"}pwRkQ PS fҁJ1/B2jhoyPI; w{;[٠HߣmLY^?uyvp3k),}FOKfiǓؕ]tLS3 uº68ǜGSi1ܵ=ʃ}y,7}%g#ucnk '+'!*%y~c }w% 0sڠ},^ _Nc܈FyVH,Oiz6h߹8WՄ~7Ÿ/a,5Y8j$d /O)L# q^j{rGa:7B C.fj:?'k9겡lgSl= ɚ"$3dp wO5|i9:k6^\iDl`M!ogf:'QF|J&OʕeClŕ ;sI5H‰$HH,#ة`'n"@&,#TY~էZ$TF8O0ZEx ̇ ]*PSgl)f6}&s.1uhq6Llyi!+'aPA"J_P \v~Ɲ YJߠg*g؏I &_k w@~iS"Ok63ao:-:E /.T ف ꩀ o?gj6+edJvă_tZUU 7#Ig'}q 7w淆V$<~v3BwI=ر!7$)ﱺC%`e0uARCXr;r&2R^ZU;*\uYlY1{wL 4!7U.m,:,KOfKy F3*D-J[XOsi2+vW!ٱfO`2b(T!K7P:vG,=ր׼#嘨:hC#[̫6Q,{FE~@Vyeel! K5%DB_ʍc&zHλLFo_1bX:9c$me+s·N'yĿ~uv,.PPΫ **'kyjs;pLeɈ-ZEh$0, ēY9DYd"b:%;>1QN9ʼ;j#yIc epZso>Z:IhdfG z>Y ;X_W#Y1U\9цRWiDj_XH U~h2ѝ1v5!?2R1"3C1z7n ZO#F2V\S-lN M4p, T=1y3(B ײ܆=0v+3F;=-("`KJ4H| =!ųteT~v>2kF<q9Ӽ:Esz6T u1K"`؃'&GZ"̕ YlV4ChZx\pw {a=rQٯDoOHVfT w>Ѓqg(hբo3?Nq723ZO*] 'DXݩ#wn\Y~`NKm>,cF ZoԂ&> vnND68"fuLY ߤfpB}qV!ۨ0SQXT2C B>O(ٗS6ǭRw:aӋ&*!6;Ŏ-DްREr  ޒ =h_G.{w q MEAe{D6?*'mس. 6{y6tw+àJl?* >gsiT>p;0\-Cd) 5Ǯײ= 9=tWU{w9CbgѦ3S j$?}<],xv,Id6fwVJXЪl7G7pB>'‰=dHMWܪBk,zl OH6 (oCh3E1KLPDњׇ0Y%K怃Ui/$2qHʳp;a 4հ"l*.O u\;d2pVZ7lQ䰆?mdEF)|hݍSr +Ckl!aݧQWH%rW%o(lhcB-:BBz)ux=ٸ']=^sQtQݓ_lTȟd8GE^{؜/gy "L30q خuHNt4U1<2F%tF F{(ӈڧlP6&tt+BԶuWmzdr}jMH[bqHe!"TS񪝱<Hd ɡ)~H-vktPz Kb9߄h$wm*ֈV5@Q@_{E d9DϬPT;-83E${L t׷VJ0Y 7H 曭@2^M;1t(Q C I}&Wg>TšnhjFnjЉAvKoh-bhT6 k}a/AwnT^s&ZGE4+VP"E q;QL~OE[BK;e^Ah-ᘭ@[<4UP0 $)=f}+6~S\S^xr$RT*!TĨwBMN(,H >%М$k 7)tw%欨.a>U}D޳B4o-d?A-)ilXt1JrM5S5ݿʰE- G7-Khe\ o_~M\8U%4y1qP@W}l4kB ==>B!2YXf ^C7nƌa.PzVH+sX]X\QAh:=Ԗۙ*I'| ^;<چ?(ēFYwd|ָHOivXK, ^袞M1/m[kWrmo)xCOKnJhI< c--%Z8χ @ٔuRViP@q'KJEV@qDq45T3 7:N,?k Slo,r|}&cp/EVXhב[Rɂ=QdX?@_UB;,ü!BS_ލ{b]zRGDT5tNyF4iE20ptu]$vkEPt3z?aJ8~f*2#,SZz~{($sӳ=jq:] @45W5 ^RJȥD%My'wZ@53P] As8dwDdMq[NLڼĶ]QsvDٺl)o CAYV).Yf?rY(y^ELZZLGZ'o"GHK43+[0G]зv5+Uƣs|fMzF"?JZZ(O*;6?5'MʸrXsi/wO2M/ 3b+V~$$0y`䰞*i9ZtZ$F2֏=gc៫! SU2wsE&} |+ 7M{CFi_}n2%Y˦a@PO ָqP*Xƫw+ʉ 1旞ˆί~iF(gKU ՂCO0Y:妤8/$Y&Ҷh6?ԋQmHy;|YAxt\SNr6%i 镍E< Z=,6;G* Ai E >5p i)ԲQ(Ic^/:SD6*0Г篶- 7I.^DN;aE!by sv)j=aQv#t/[*_n; ]-O֝xnHWaӤ:J {^ދ`:|gmL?uSƲ-Av Gk(]jN^x5W?͓tOge %M5˜6H1Mdba5}-ó1%cRcA ! ؓF7Ċ41LJQBJE2wيFo"oACe[ؚ45 Golem󦬜_NWψ* "z'!A?|_#ռoU^?[< %U5V@HtY adx]v8q.Y4=##n9YsW[K$g\J6 a+8/ OYpqx|bcI 1ujYTnģ|ZجRn1/t46ħc,i4/NDHa7b4\./%>z'юoC'#vҘ|v@7|cRk2ֲl=ȣb/w}$ <"r4Ufgj Yt;>Q׿99n6܎ D~ۊiaUvt9һQ,4I,* ]ʸ\=ZLLU3B{noQ cy$~s4+>N0ܨ2ݺHջk M bbQ]VmQ|ADE!Y!Gk0i-'LƉYUn\0܈X1k\rKv3B1V3QS{좭=aŧߪSZM?Vmt ݱC*$lۈH)DǠSC#5D74$xx.>cvkO*9 |#{ an3 SMjP#픴?gO:rf~3{=Wx&0^o@YΪ5Z:0 d㞠0cyම)uBaF N':]8]lW&Fn+)>xol[)XSNK *H %#P"cA iPjxLUP`L`4]5֝yxޡQ^$7lFEPQO˞vCAÈHv*B.$ Zא! *W!(+Kr[>R;6u#فI\ 8vL hn\y9z/cZ@ySFzi 8+䗿] {jqb{5x[Y?lP8 b΁\ UG LTo19 er{yGmrpGMr޺bjnl2S0! ~ˮwHFBQ2R" Bq TuL\E`^pgH:$EiMA/ui ?Y<5 ӝ\J*A/E=>Wd|@y"(0#zeî,[VB:CB256:6aX6G ҐE]gzϹR}_#èRMArm&)q] Dӂ{0o#~]~vBULZ7+Z, lQۈ݁2isL9+ p@-52ڼsݤ5ڔai?~\+,ՊҵܪnT` *4Z"+U 5 @(t_]"GXM#g@ջZ.9H\཮G!)-DC=ٯH#^VPHqMq! aҞW^3,όFX*|/bkuD5v ޓXŔ99: گx8q?Kvd"K'V?. 3Jܫw׮\P Q=oXS8އ﬋{Q!;? wg>F[. UI](XY2 ]z\m>}r܉{{[<ȁ`ЁP٠*ŴMg BVN˶%&S˧#S&I-:ţ߈&&Ŭ8(cImi@.h+hP+뫬ɡ2hP,3Fξ9#.+fg01ҍe$8&pl(2 BE}auVHHD_/Nd>)Gg9 evCQKlsS" h,]O6Uθ1Xh::agsëcSI\=VFU|=v8EǫT%H\.w*3 •j &kw nNx/a9]6 ZBChH^?yw>Hd; qF|RtL̻6GgB0E4ekbي N-kfGCrk7] Y)%%!Y'@?u~$1gȚTS58=-tgTuoӞM>X߫SIaU̬h@_vJZ&d%DS}'%(,e-!SJ vgIH2n?nㄹkӋ~ V ͞ϔB)^ &>j gwBx['E%Ǧoq^f|c:P}2xEEpz|{ jʐ :-yִ㯑b?ң"0 BZ67  (KHsDG{,M[W}2bk;^7 D^*,,CwzL )~RgeHYWKɐۧ&U<Qn4Ǐ3Yf1".9֨*f|C!:ʀ4 gp=48q? ?s9әe/1!"Z* OXQ&+P@&٧PwħXm L4vJtL%SnڇɜL(.knw6ȫE124R';]nc/ECz0ngpE?.5Qe3ƟYnNLq(*wsF(xNq"o8O@E&TǢXW5y"ziJky33M1"H{[nnֈ􃜖8X\=ۤx[%[F5=ȩtTmP.'SӸZ}PmD:,^`_b2·e Q\C%i^*;OkE0F[켐EP(t&>Wr{ =*=wE=0 Ц_ - }8gPKFvW[T.ΟmWPBŪ\mHpZ,,'zBI[_HԾ9$LoD y% §eAevՁ e?ohPAԄ ?fc]Ż^_`cit],cREaMj+}e|x:;ẃl3_oPyYw _h!i(gUT HV\8ԥ=UHGIfy&HnEWh9@y<1MݟB}N3b:AU.b#؀x14;t.$nhsa`յ܃D#Meܒ$ߑZsܩ>щA%Tx@y3)1ߋgbk:?}>!U*d ipT*aƭ(@tsT py> -,Z2k@H@oc_S2oORHŅ*]HS~vauok5LB1B="bjK9"ԥZ Т_m w-%[RpU#3-`Y1ڤ\s6/:H=c ȗg0F,67AO+9Rԛڄ1@S0>:Ї]Dn5? `:tڂ}ޖ<ʤWјQunIkRe5J!U˟!>-tὁgɲχy'6y-7u֑\&A V{0_5 ezQL_$MP%jk0 9q{Z:TKgmgN2|M^ܪuUlJ [E@"3Cø3$|0@-< dd2j Q"<g 3&*T4u3n>\ "a%K#V]g7޴Mq>o"88 iR_6Sr::sHJ0rn|EK {ݰ8 TAYxh02ZS;9gՃ]9* r]kbh jo蠷ax#-H9§1Qya^x?-{K=e"쁿 ȪRd&&r7I\a yE>L^fiM1!8&aPWQie-G &kpv+,_XtVYV]%7xO&q6ֻ1~mlxOJ ulChc9><}DjuHwbw\g2$Px)} AǦpO7Vv$ 8 bbc?=r'jF"Wfn$m|츈2[*sITOv> 5DqגB ,ΧMg2I3GOq*Zx: fJlݐ/B&@78x Bx܏8/'U*'[P<[ө}ΐJl[m,1QѬ<.S8BY,9]ڣ3*3' A'Bڒ${V2Q=o;\vS~nyHzFJH;o/u9=I2_l. +eŲ6i_ nf2Cl/$f..+RMl93kҢGj% mn7>!Ô|{e"s fHIc2ۦI*.$RwOYɭ- hJeuk!o\HLf8qR@zBשl1&5>|<%(׼)C'-mUX(ƒY+@MCBw "a<.:j[fB-HzD+ζUE:XGJ#wZ1mY.A!iC5Y[ 2hj=e):!UYtQt ]#EbRϐSy 2r—fa;lRZK>itnĜt _Gφq&qMv;.˅¨JU),vvSN$}2soB1T1sXnb蛾{@d.5Ҕ?Lޚ8yMֿ2痡&^'3x1^5Jҙ!jhVYR T\ANhI=#8tAB4UOYLf6\ӊ,De'ͽ*KN/T;3qԫL|&*aݺk~W+"6R{ 0y2׷q*"h83 /=Zͺٽ]u-U4iZ^cn} W{EEh{[G}1<35́&9G0J8")q#PEU$iE۞ Z_+'l7p0,mr<>AL9?;"H2}X F'X؅󼯙-wWEJv96cx{MU|9 U΍ v;Gtt$NSt/3V /_>n#ZQ'BOe?,lSBÜT1wtSdnyY2B=V(E/D?-J9iG&jWP|R~gזMV ˦; $BLZQ*rѶ߳ XTʒ> N0 n 2D7d_A 4&D hE=U(EƨxMОХ?tg1\g"[B['Nz}K~=;x,Jdf z^[82,P`жZX)pxy"/uéTJMǡ\5# jc*J$u)jڳ_2Gjٳ ;is 9%#f /-sQaB/zJJa"{4דw2(>sqhV %)'#5}'}w }gΘJ;:DUwZ_&ADZ]tU*A~K}1 K=aP)aj1:e]dO X8S$յ'xh6;v-8l,,Ƌ@ ;t!kuWy3q^҃e# =_au!2CD@u\Ja+;˓-`8o`?XiӅ!_͞|ZB  GA@5$ZM{<ߔXFl"|ԅx*XrAM:e(LVFWl$mՔއ:!ˌAøLH q%{DQu7Ybc `|z'_/bvW-8t1.A:]5ZjtȱzI[t|r՞ݾIJ3Њxshډ]7G2_)jڪ>tiϹ:FIm<8 k]ˆotL)NQ#…4Yerx[/*[TbS\Af  /-%G8J5>$}Vu qTEגNhs ZGЏ(Z1b?@mK(W؊ϴpi/T$qQPYђiܱ-^Xfcf7\nooAC K6H/٢>Qyˬdw^=t@~Dy|k}cUq^cs>Ӡx mN}$v[R7{$/o.2A!<o7FK0"&#Zyo/xV\!{DD3y:/H^8}+Bb DvU߻>xKfkM-lP"\sfGE/DŽHi. G!Xf I} / SpbM~,ni&yVJp;TR yla5V5V0LX`Õ2P+ʞj%g2熯ҭ J;Fȫ,<Ӟŋ'l{dH^_<VK/{I ύڬ!aEkLB:Rᖾ4>u,Jrj%o)/񈠴pGDE١]y^2^=vUf\02t[@f)3S>,  ig+5W̏m,\[J|6 #wⲹ~l01l*Q@@"Se9> x9b/S'b1w|P"O IPf( *3(e}ƁX'jN-z7B'䎸'k7 bkbL_{6Er!?i‹nc/'c@:"՞^n;u+!8msIOS%~ Ss|*Q|p:°oN7׿ ,ڪ͍Y#SH'PףIGA/6+,bT2?. BT6ϗ=)9*X B$ޜ fSMuvT_4z OAliZ`HVYmX9w@c40[z$_WJjTzGi]35Bd~bp /GcLw~`#႘ɘ~ +&SUCȦZ1esuIqQ[^̠܄IEސHXT%ld<"rņ52lč7h)&}2H] (l%m[r+n:š͋?P4F{6e ȝꤏQX^V ]|RDpzV̉*s76f1$I`ңe9'[̄hH䛡1+ I꫻xv6W7[di*7+x%9 *WUup UP/ ג#Bn<0H<6\FCVߜ<c_tDY)ק>v|dW[,'}. pNpW#kMa2$e8=*`QL4q8 viFRM!Up`@TNpI9,l[ob._&hc Es mٽ-iiį« SLrrY>zY8aMrے32`<M#QQ/L2ŗQܻ '`ru9%D1qcfF1=4*6wMQV0\5 ITn$,')?;_CQ^Os;:Ky6P.YbCY6[0ێsF<Şa͜!@6mĸv>}"ݩ HwҜc[8%0 \1Yl7w-1MyG{\6sLp;&5-٥T+-Lic+?T&] owUW>˚ݫPT:[fhgEty| 97=`JYl"):( 4p{JרT;f\fk}&5K>2^CrIpC3~wWq6-DM1: g椗<)o޾dݾ4hc>E^C>4=H8؁QqՆq g0^+|:5M2?(‰9w2VU~2 |T _3äWM )4y+g"wE wȽd)9uHHa,fTãWQKP 9Q O7+?+b}j[C M% _4l9Rz%Jdfa)tH>SqI9'MXacZ;– 9x<6S@=TyTj--aLl9+^> V[Qկ]$Մ١- Fau,5 yQ&6_PgWL%{z#7/zD_Ta AZfǷ*u egZl$WUyH,T77. /-Y[9adiGc}ļ^R E4m<8֣bKi-8] %3X8 D%2 Y'cTKb}t0QWUctGq蓣ǃ;ÔxnO"!^ݷ٦kud$Sg+-;CS=WSK@=Yq/ 2snxgieDR"Ub0/ʒ0NV[|v/Y mڢcvl[5sheܩYBѮ~ Ԧ c_ڗ)ICw'Rja|j409zXa W?Uap>ho8,b UuB;}XF6(mcd]xSHe:=fMS18ܮnoGF8ddžQO&:?!K'S]yOfYχ[̬Nel]mJ"'FHLy*c A@\emeջ^7<ꢙ<3 GF]nj}!*o…i~<|z;v e Q M?Ʉ ɞVjX?bP=~'{ɉ~ _΀ī%$n$s"Vj8WCwnH&ҟ*3O*^&.8.$)M2?'6*;Eo ݾWMWͳjSgsZ8TBM~wx7VeP<7\e33 Hzt .0{|h'`FG4b_"pupT~`=:f={1,v\y9q}WTR1sX@K"qSXx5h.}v0-e,JaU/cvBo~lP|Z/n,Ljvë-y1Mr:adO0g}DN%|! We2 9ED&&NՅTCL!} NbYoǪm!p|;&$^g '2C d1t՜t)}Cb;sv͞K HM0-~?ҙ¤عZuYRTӈv Q}߹>Ҩ&^V<75-&RT_:nZVNQt,|bEguaFȚAp5r"|߻`捼R3{N t.?߁HdXaS tƉIB,Ɔu *ӕЬF X*cn g::rQ,V,vUpr,Rt@|+_q .c[5˯i:LrRNwyK49߿wnܭcY?(6 Q[؄:YDJXA.aw݂[ $(:ZQ?g_v=V3`^SP ,l3\8򵩠 yנ^ь RYs密5\gPx= '{:yK4k|b:^(bSyTCUGԑlBj~LLX>6FV1ފ- T݁s:gE0)+16U.0ǕTJO&JϪ{vWR,N(VHmq#Qs`~Mi,!ccb۬-2e `˽C-.ÌG{]|{=Sq~ 3> NȕC`E|U*8D;hGxr}h:5Y.q֔WLJGz,FJY|AZ[ 窢PdzHOB@ 1jg XA|y_ &$>KKwveҹR7Ħ@-~TSYi݇İ2*d>O`e^\X"p}`OX8ͨdC @Za D) X9̯paţ[Z0?(K; 7㯢1^Y=wFdRoN#b8AVj"]a?N=zӹw4U6а%"':}J ~ziMYOD͒ DrJ;x 1]#7 D! yVMlqθ{LOw9^w<)zڀN)$.١^'uF#lYǿ_:G*hYA;Yy^zbNBm :\2 A[o{i,6 ‹3( Yr!w6 rwͽ  }^f[<|Jx}PVK좷,sB5IQMl&a c7.1"U{ة曜! ;bғz} &2<%x?SX{%l;.鑘*j1ktqٚ"vU :=zv" O9?-e#d! x7SV@WɜP  ݯQfmzp!Ig3s{yZU0!EWYDsPZH>4xEAnm]4HP\خ- fNF1RCz.y}6!bsAdw³^Ƹ~Cņ&ң} tەNn`\fJx%Tk,qSX֦\DTmHa|[&zMr. ї,<6LL ,-;G3\;@|)nJp}Rl?Jzti|\nCvɨ3)GmGܴe^c5Qז MPjsK1lI$(}],@hΑ\ܯRIo\Bt;QKJw>vz/DN43 xVi"j⥹ӥ0,8q!KK(:K/K$=ŚPe"Vtl5DD<9G9t ⾚fрΛ?* .x0e?gz4Ǝ! 6NPGz$Ac;*ێ< oS^I f4vHHlGuݖclzdPqѫg+0S5WhڗM+wtTJq>ӊk*Y5k#]=HM9)&O5̆Qis}Dm ƊV ;W~0""z=6;@ZycD%l>-rХ121MU|{vGyϑD'LƢx#D򭱈ijhV8#w>o̰@Ɯ:Tf. T1Ҡ6+Fy>b9ۋ [Z&\n!)F>tAXg0}̳]3xnQ|剉7<qs q0h95a $RQ;tzAYW)W<)#| WcN!Zݼn\Ԯ=0G"{@v)7El]*tY;[yFJhRHs \s'<Ň|Aж'@ wT聖@7MܱvBe5RXۏʝN6ǧ4 L2 'f.B HkT,j=|ԛN' ڏe&p*zt2}s$42o~ 샐TMG[ NtTJq.1-w;w|=]+xwcvY0Бߘx$5 1tj y\(4>c”;CJMwBDŽ"4uzŮAoK@a Mh}?͏m pO>1փ AZ*YE)p3ZzObz ЋGl7uD85JWtY4P/dSzk]@+ۄM"87,n{|٨$AEudBj<1/Z,1"+)v:E1pUlgU `SZۊĠRT9~,FuYϚByfO&uQn'/DPF (xwwn%6IJjt6VCrBP#eꄲJ^5(/%xWtAM8+[ƥNٛ@h ^V輽/ D| ǥڈGQ MgYPX{|qf\j3!Voy@?z=눸~3eTMh]bcY\E,V+xȶV\;,;Q7"*, ckꆪ"Q"L3S;)f r'0결;j0i?TE!Q MKis8xrY^9L3Aqj~C'{!d"Po:&(S%`gt:`9j  sPTkb*Q,WNuUTfD^1> EZq }ԋ@5b0z€5~Y9ܶh^} + ~gOJ;u<8eTTjs6yy59yFGYI8 UY?}S cNa_r ȧ']At<QC !kѺ>kyɎ)8Z (г'Nh_"Ly' ALSc}Sp`R1g#I nrh^[O:":S:"UVq]X.)ӲNl$1T]cMq+yf2%;蘌o3.6^b]ܝ$~gwVj"%ms~{W,r㭃JJxsbxWYnNIx/:ŝI:~Vn.- jp;w47].Z8i *fPBV_ ~K2ȿ( نvlⰍ>=w PhL@zY+ a*=Z}f*!lj 5%mr6*vǴ-;@9Jx^#CB;ӎ_-άf% &7qpn->dc[ [;u/țmhK. &T6^%9jOGjSn~*uKgǻ‡x,[a2|ּ|$ ++ːxMLً% ̶5;}`IlE ,2MJCn[ȒI M!)Q5~ A՛ؗoY *fDA/xYy0:'Mv-ku6(qvh^!d3*cv_عyfFR@;xT ,~20TE]E~w{L֮p&PQ%*NשG:+\fBњ<:{?1.PXdwb{P {U]y=SPI~Y> &QHH]/p0tBjO08ʊ"ro7.Ǫl9f:!Rc[ zqKƺX:2#TlД*aB78.M7KxbfT!2AgXSYCdDP\#}8Il! P}8l}v0\W:gejjeւ ^8eln--XI:nbB OiS$/q-$hvQ#]7*mRTy8wKmtrKΓd)Whvo=C=҃Ll%zޕ$b:oT.ej7TNSK E:D㫞2JAzslO %wqpޓ92) %V[&喧lHaۺL0h/R9ʖeIK,E}!X*MC tfVRc`bͭH-7MXh\ا H ڱR–1c;$,aLcJL) 7`v8acא$'ǂ1⨍Ob!+|0>AnuPh& ١!1{U7u'![~SԉN?AMsjbh/oUWJy`5]59-PaA43+l8쓻o:|)mvd̛^X:}ZXʩQKxà# !OW6[)6dזUx*5X&,<%+~vZt -R$'uGra47zCN!1t Gw*˖V3D9\AY6!*y/<9kU!h =4dۓaH&6jt7zN&6G\V?E'iO9%or JXdo/*xfәFByZBBYbPhG(_HvHX$ H΄WGj5m O,pBאKD $ŀSDxq Y.pC9@dD6z_*8V[ _B<7F^s޵Pg}SM TMM8U]gw`^<0}؈ݮ0U恖?aV>Yb!h u8MBO( {oVY(,ϻ5٥F+F0$΢n84HܢEDjQ&%tҀֺV 8,GxYBNȑvbCL#_`4yKRHTB/MscW=ʆ%8mmθ1= &,F[V &!4+M1mmV^ pTii ɫ4!N6S` `|eŧA,ȌfM'Ҵ<;&dG- 5+@̰OR[D(cgҕ,<>I4?zUٺ=7(q>tkL ~G@jC ~7]$U7g[movCO9?5odɂV`*ooW-%&!8<}N 4u~!*sq*sD{y=X9lp\JefcKƥ# @?1.R#}9zsJCNGeQ6Ɖld]`᥶U-~]^xB$s> /]W~@| >H˭GbyO&fSb%)mI(|F"I^Gi.[27I\lzCafTs $(E-֥8)Ij+C;Y'8.Sf{Ҍ1}g,V"Xg#wr#Qc/ c؜SX8zhfin}<ۼdr!j]Z3uF]كu8;G"oG|ZgL-J}b JoPZ ,zXǭ( Bfjg"bjniCUs}T̓j* VM1U'V,i!q\ٽS&T`jRx\|DpP%BɊHYIr ܈'mJ[NFEĜ[ 7c'SkgE|߸CI^="T`w!zy.R\MĮtBhv]zL( \mS\7jHr6Jޒ_u~nQ#'/?-t6lM1G=LԴVB0$` dz\Nҹ_8,ѵd{۵]O5YR(MAvTlu6>ޓ3B~EWU^Ҿy13MNTxQ{X jfxahO1Z#J"r{\j<.T.:_~PY) g̊KL O^3&CmoJziaa 2M΅PYyL! gF´' "m_h=ѕ1,2Ahd݅G˦['&KT=7f:f+=u@PT@Vv$A_{b$W1i)`IFe`G})k9{^t퟉Pcv-UKW91|o5/[^/ZTng?gj2Zvr'Rda{Yd5eIVV *g0h J))J8SD#/Y{&~me-r}QO~ewjPIs/C/tAS%/o=))>3ͦ}Tjk1Q+i%gY(&=s*WҴ(\o@ g s|Yw)BcǩLr"ټUeGM@EYpqp.Io9ՒC{gKVңU,XBPJ4~ȴMmsɐ[<)K@}q~&$l8A2h">`IliWn=F])cfiƴv^DU"y-/1]NiIe@k1TRg>vOfp[`W ڙ:Ta3KkY؞YO ޕLO,qvzsϹkj[-/DIN՞d`If/hDC LmʼyUm24=\H Y&%qУ3.,T4yAXUT7O9X;kWhR-9Z,!KtG=n(!񦳥ѭPt`792q]˰za&Qc(GwiՑ 8TV) wg0VR{{N}l83K@}dmJa.1B-c:c5C "UV%*j֛}`Woby8S_n5Nį{>`"׉Ԛ:WD 01CWýx`V_kL{t-z}+o8xg6^7(||UЙ$ Gjtnˀa{4w{Fv94W- ^1é#;RGfbr nCf۵dYN[Ojz/N?W^^ ŲM:k^yf=A ,#`` gGEq|1P8:Q&scAt"2s|| Dò`hmA x}iD+x_a*R0W~Zq|x6{mYY&Zˌ:>G [Z/p}2RV$yקJa&vRr+{Aƒ#xtm*FN5^z9a?큎-?Y׉s!Nh!-`NuBk-ʟT =fǚn_\  U@$:~V֐ͫð%?r(F{VqtD'8 ]hG(Jgq˾{$khxP#Gy "ftZ@N҆'A2c`y ީAsGT=]{SR4(-tlK .bO߷"Te&#1cnPo]W,\/geQ.>$k13,K(O(oQ6(Y 6*l['"Y02fl9ɻ6v3!֘ y/.V8F‚%z_G%Zt4u9.Z2ɮyGg pPR]źE%0K$JSeßY d W0UG9|̩9%c E%O)&B)CmI:X"j߹(,^͆ ?P#ƀe^gZ "#I@wRx*ҵرUЉsB&o%˟QQI[aՁi߿ y=Of R^`?;*z-ffyb߆M0~XdIL #Q[D ;- eiB r×+kasZW\H6<󆛀n|J@9 2/SA0Ӱ,53霾mPy6og5UJ`YULt4P\T1;W;Xd׼2S`Džii,~]Dn$$kc7_Z6ԥRKh^ ɰN#6iQG"dltW'FiJᩰVmጄWnk .KCǎBLv-wbճ8(/:3ړSQIH#£uy= y^eזd34c{5[ {%چl3%}x&@k`g43Y~ sΞtՌ|E-3T;˃`E743.YPI~ ${eSoC]C^[$; z,L2c'[TTӴ_8>S/c7N\X+j׻u6jSE}]d@6HKD8uڞaVYhr=Nj!eW(e{JkJ"/H֐>Ӕq Kr]ɟ缲Cr;5ߡ'iziA;R៿vլo[S[`*v3s1te- -&YiFtn 'gg,I=̊5 ISs܌M T%ci;o1Ž28Od5er+A[sΩI-۶?Ĩ]8UE^}2ek8lDK`eE%\.h7FvsQDWp-n QΥ#5_*&E`ڑ(`O@kvuYὥd4R43x V0NOm@i ;Nv#2ijSX<Ĩ[p&4R;*龾6BWkYrgN*h' r7{Ս&@8Kus&zM ^tFW1 I+[ZdÁG)LLu?UL@V`mSzSbf}R{īF=IHrb3yN>p>2-V Q(B5qR"b-EYd;.0VArِ&frOTD (T:@jmEm :11XDLQu$ ?d.SЭ`auh+ߋP%&|:">kT Pʜ~Pl|j];~u t U |? Ǟ6д 8Zu2";:z׶}񵿒e\sb wؐa!^ l\\O QS' q<1$2!q#(<>[a B+~bf}?sH4D!=BMyqo I2fvG 8|eEn`]BQࠑCf 5%ͪth.s遢H'=3=h'+d2bauHbѿ\gAg , ?9ǰJ4+gXVxgeB{.3RVȉzwi(ins2,"hÚ k;pwPAMՄ=^yz=k0,^K} zP.65e[ 3r/5`7vU2m ? C;EYV33Zdo;"v(7gEiaHDv:;N847Yp}J 'Z@#CzK:7ZՕ룒I xgfJ[>Ww "SdofK݋$[aWَUϴ 8Ĺ 菉>:c^ff)sQ ^;pkATVXyZX"*$Vl6֡*wBW%Z᪭i̝=b ؝8BFnKR/Xl:_pUc;mOɉTb i3/kn*%\CNYFؓ?P#hX3[wgߴe9cE HЈI q XnFL>I-K)ϋv Z-_*hI>?宅$#?t-A/5g}ԭA?N$+rSy9ġXKdte`up q|Έ_?s' waS,^n 3@U{tJ͐ 'Cq1;@}Z̽#3*URGfPx6>CxA6̀&7ƍKM"I`շB18`E%{kϝSEV+"՟j9`]=/؉q[UYpBe=T2؅OPDD=Ù*MDoN迃>OYL=(keҪWw3!cU]}0)3vB߁M|wXP 򲣳׿T,%[>Dz: `*A=(vV^`=Q<ڎQokɴ~5r?Z&燖Y/˪K~oi^廮ik@/VB*3'1O̟@ȴ*4ȯR̀Neq~00шCzl-RtxתDE*[<&qB*CI3߶@5f/"XXI LlW/?}>L!NTX9!xsP/estz&cA0n;kVlîd;ayȧB7P!HRӪ9G25\iwYpS76=0sHgϺ F%jHΚ|ݩ f8s4.3WnI3 iːa[;'}{ D6guh ]uxϫ jQ[!Qs(+Ǹ]"Yiq¬Gs]x8b^uLB]Q"cB37AbX wvM pvSz@tU8_lib䌝 x3Rjl֎THArGD(Q*CR:1MI]B&]l"B'S]XҭA( F"cRS÷I*qu;1_7)֪AU3ЃMm+;"EFW(Ι']٪E*P95&wţAy#Ci럿]9 c.$לW%ī"F%yU@'ꟙ&+oW&3uȢΚV!VlySP H#RS1\T$3I1}MBoAMS+fIx}amd Ѕ2JA`@ 03g6^v\ջɭdE-X@| R|:d(?ә%2H߯z7.Ð[ǤT`*R @!QTsq0]ox(⃌ 1tc`C:i)Љ '({iYgҧHu֡>#La/l1퓱wNlɐl2_FgYЪT;QB'gZy 䶭sd9c`?--&`{]|LLˉih8{$I1[}ɉ1J\49UStv2PSrTBҵޚq4!g[`9|w24yZL"3'QgSas"/:6"mc'VmzV.+oXnVQcae>T KDג"}j}K8M];lu8v q Aư^6O~ qqXh#oM׌kv7-jZ!6FcǗI]lthD6P63] aİH&D{['f@ "M49/5ElWOO~ގvX=% A|N Azm ~| TQ]"]aSw*>ŽeMTYR *)C_^ˢ;G*Qar^}zՊ;&>ݻbh!GUFy nPi{&2xbMvCB؆:WbFW^ 8^sG{M u:¡(xtI#"+){飏=H&Jdxx_h{Qld7`2jLçF|ogy'>cW{u5V=X:xX KG~#jhI.N.F)s̻"o+sU,w#11vN3/QffdRWWzf"ݢaZ7Fr&+ŊԘAxtXC aSQPťPyT2qU{"I,WQi°`ݞ;CശiN 4{Jnj9 zQ鲾 [%5f:5y\`oV`nK7-mH&C_ jC]|V" k<&E r-ۨóp'+>~ vb8!Rk/9F G! j4$ r Y5GX{Ѝ=j沦`Mc%89̳4#0bm`_&a"%\3RoTZ&~}&/2h[uJ9sfU~ԅ H|MAߏ.-T&tv532+[ c/|S!6(w5,Iq}f>b|@<!tB2,},AK`pEvt$nL-xY*8MIuoˤsF.P%)K,vtx g|8G$U#uv}(M(IT%.gqZ" E6u 3+6G7q6$4mA98K=#fY/Kbw`{ﰒ {kxJ 阉b&$*U:]Va3 Ts)|"?ŗ ϛmSC.#JT-Q5qўѥo䗥C}U'L՚<[.Fx9o5}'{MLo\O+@`S\_$.퍦yoEg OkOdz>(VG1ڌ$CT[ö]P JEj;ieޔxLCV"0 &Y3x <74m൒ME,z;Ksԡ D=+suEZB LD4u ,$󫶾8-@Jci4Dd;a+CAV'O?rnXiR$j#{[\?Wh1Dvf;Hw:ٛ._]{Rh+K lO[W1b_m۝gҚF3U%30=vնGS۱QlsT cEŌ1tjkʹhǗbrLWQ؉oHhS\p|o ep&򑗾hIUe Ỵ Nh*=ͣTe~6=tW~PԡnCJg洎8?K#DNi$戏֬*S*:Є+Bßť!]R!Sm;~.ˢet6t[BE@n6C߬xMS н^> &l5miO1'J}"9-GQ`L{E wk۶Ҁ.wJz3c_`F+|!( l7 &O|aYXY0m5WsNs5Z>Ň1/%B9M) CCݫ¥gκ3&D"F߻ݑ ъָ)"3`vAyW U/`̗a&/`1]VR*5K1lj~b.aױ;-rB g,)u94$tB&婱uQXeӦ G+  ehrno3,H[촡i&\AصDhܘkbS9eH}NdzBױ.%G Zي:gqEquh9K4=zEo7vbxm̥m\6 0>gztǷXA)Lc5eI8K/?&zW$`o]cq>^ R,^DNv>%|RnTp9<mUIyv4ZͶ %:̅P+U'~w~OB^۰Yq\!H&b,;xP0Hj*S1*n$ѕv%1{"1Gvn?F;e`ԍ7ocHԾp!5.7@Xr]}X+P'B@zf"xQ2TDsEtLH)>XNWM~7Mv+>r k^-awAEE$YCSN)G| KZo7wq׭ {xo#fxq)[p6k1֎ha#̺A}CnBO_ҠdsP`> SL &Ճe E_FB1W{w= ]i)\īXL,Jl&|fl.\þu%1C^/D]C+bv{_>_Flgyڧ8m1D>H*5H8 oT\06<eQ?n'#/Ta?aQ4Dl?P_QE}yk{,9eh5'}x}y7Ӧ] fZ\G\ߖϨ42~$;ΰ6IrYfZA*Rm ߝ3~9<ƅb8/S4ET"_I"{}נ3^Y}f;Q $u4B_ ~)@~1fqB.htA*Oh[i(xf&6P;qw"s|:\AtNGHdbw=5\C\0k XyBÄ N$QBBa? jOwiF> :xl|t̗j X]Uo">[s3XHn%oRDkD! Qوk{h)soL-NۀQ?LΑ02Tɫ|s`7@A1Gԗjw۫:\'!ndlqC~U# L~^욆Xģ%*>bըn8-F?==&&EhbFGX:M4,ZVW{>EM!4%;|N chz9t>Gp<: E/FѪįLqqruVVpUEe|P-m):MP,? v3l-?˟|WUk&]g&iq{,(Ja кWWׁ;-<'0MͱLj8k?X;d}U q"5s!V7/ޙ IY'^74B ܡ{V>,B#r>Κ`EhRԁ?J 枌:iM's H`1(D'yzGg@ }vq[՜e";xqG(-z5ƼϴGBṽu##.=VǠG ? C\ǘPLzDOKE@0Rnv4 f6*a-qz ~j?TyA1|V\!~I7 qq$J_s@|v%]q_GGe~]ي{eJ GHDV#Uw~-Oqχ:%ӍO3OR}r '+*`jTLΝuuEPEԐLUTΙ?Y1z{B`^Yl\;Do6px}Ԃx"DPJ];3*Yz3\yJ%ζ.g|3[PTv(f</G$I}MU,`c$xܿ#H|d}T(#͑ΊU$dB2rlf&~lJYp%Dʐ0`m|L^K\%۝ Z;bڃ _(6du,h?ݡsi`2|𣏯&Izₘe-0C[ 9@~DnOx[m/LFހsa0D7(ݬ4"TrA7I%2TF>YW2H [!*4 ? s85]NZL=~0_m / =BE4͏C,o9xM'_?P*^@{01+FqpiKd (x8@xeod@*zJ*-{[s{"^3u?;Ou4OcBd@3܇%![z>!Nfܗtɍ3}F"SPހkb|"e#<3אi˵GM$ C^m{jW<pBWգ\2BTP7T!:E_z`UtU"%pY'5>cOz\b}]BD̦NLlwddJ29GǶC ~^51vJ:BjTeN!w>E7 D]?du o'2O&48"8U9Ge!ތIשX+vR4.j~MYcl~q 9Cؕƍr0ƖDwp@|}Xpup'A4fwEtح Be-*[n^4G`?Mp㮌e``蕒`]DTt@}j'W,r\}ICB;sÒ!XM6L#H!5̕LA.ʜkW;{`z ƉVV.F1mLO鷞SsboUwԕbtIX7.JSγ@~- Kh\Y^uAnefWxmH]r<)Sr6 `IiD) 9@Mmp+]KK2p r6 +4@%WjusGF Y6v·߉H!`_CG&%"?g1'#[J+Vi4B&7.HpݬlӲYuOBz Q4&a-}mʴeKUhP+2v}(Xz+/U<uT;hZw/<U?ͥOHK/Ry{v0-hOʹ.~OC!˰7\;$aדd`l888tŢȄ%O; 0p2Gϫٻ͍H!hfJ xDqj :tCNIn]5iDq{_vgwtA(̫ZeP٨KYks+ CVyZAڙJBքKDU )oĔO8IIeͺ|ƪ.pZg %dN(Lx* ܾm>3V-\J1(T2<2?QSXBti I߂'p%%rA #)( [ $+f@Ǡ̀#du!RY.*۰LF]->j[ ͽX7N'1P+&`7<ѕX9֙~L U`A\*2b(ʠ>DW x) i78H8:SH/U**l3OsEsfl h 9vW!PU`hzjY\FjMy1dn5a,eRxf(>] Rat?HOMqeiWzI'Ek>T;L-޻$ l,aZۀ#?jG#2uOpATl-:nh窥zc1yW:Ni-OuzGFoA\aʨtēV5)/aPcpīcMThyY t I5JH>|0YBse '6dkB{WR6R?aQU88iA& 6Se0}glwpRsx;ԼR$dQ4+4S,a9_$F=?`YjQI7Gd$  gP˰|,}5"|ZQHȸs|<2XRRdŗ*Dҭj\Ζom`jY#a35:mJdNri3fSΛ|/YNHQWב:joÑz# }iUh<SbylvfMK7(QYUfЛW6iP[`6ҳ.3X 3"p4Xw^=Ի 5unH^IaXÕKoGY-I8֮DB9x$ xK3t]YO(hcP#T-pd?p?Kj)[F+P `SztQlSfg*VJ..-1W^o`';I.Q 6ǒ_Ӡz9 ]xN'Βl>x+WNsiϓ^ujv*\i!P-JbW 6uč9^@z߄#M_G)ewCUA_ LjbP%0@hb] 3me*X⧓]"Ӵ;t"Fc%lpE*=aXY*PVҔ(厵 dR$!+(y?'ǷWϗh죌X+_5:_?e`?dfRtAAUH=!HA~N>:##= ߣp3v 6D CƖgMbpu%)JÝ>J$gF.~YmߒTQ"OƪrCp# zQWA`ScsS÷%_*﷼c#:A\IF7ZvyqZd2C1>1AinI,8V.ŵo5'B^94V9 ,5dA1<\bQnPHǸa=n qgoj Gg75(JgPGQXk }${ t#MezyyO'E`*yVϤmw+WA 8c?׸`gxR)Cs.iutR:"~) 8%D+ItM]qvp9$(Ⱦ>bxa noM0Z֐ᮬx1ނœlrt@@W61t$T\qw :FK>fR_&ڴC÷ódOػoJ'[퐘d~~wm1)) ~8Nݹv9PQ%TѹڌдZZ/ʓWG@oRB,&: \>[\Wօo}NaqR:l5T5U>mg:T;1$*X[-N,F-_sV Bdw1H8mS4(E QhZExib.#Ɣ,.w>]*PH5\S*9(^.p2TK=Ǩ cʾұV-`dr7^ݦ.,xJ[T0&nlsGZ!<w63$R"9tMd{N s"+rtC~@H%Uov UK_:XGuؾfےD@2L^dy Qldz<3kxOۯܦ#]$Q9Dq`I|X ELRsq~(tBS+9rR޻q^Ύ ۽HkT[ u KRbVjGO6a`{It]SK}Qw%=.Z vʐ)ѧs^WA|Xp'QXQ8MX([N+wvqQx2G,2Cw(۞% 'D%C{Xw'Pwl՛A^( j]R5:H;!6]NIBə"rY?t%{y1چ"4~QrfqLAi]b2D\WG5EQ^ek1lղZ[JOO63z)t[Xvoa \RURFW< 6A&0A}Nv3I9|' !r:fahLv?lkRt@="O\=(fp~݅ml%3&I1)qt'~]YùNJN]lxRYb`3^pAQ0h䑧D*1Ȫ\`M iZ J>weQܹ6rl<:P0q tũ 7S{K6 s=K'xvDjP967ēVT0+&+ސoҌ5{Vk% *VSUW5{m| 1,ugQw+\j v[vEn//yTlt %1R"e(`4sM'OA{pm..[٘R.k{n_VeeqMIݞkYWH|Gg ݽzݵ-Pתmy,i40*VPVHAezw4]]LOK&s sqv%E梨ZPB aλ:3IeYJvRVEgxO4u$|12[cz0|lY3$bۦ>eڻ0[k/[,ZJ|>Q44%$DwrCCVY'Wֽz_.]Uguj0n2n8&_>_DtҏZ1w`)]jy;FsR;WO?͘O3dsJ^yڇAYܒp7Z,Զ0B+jR{e|*G`?dD 0y$,6 P#}kiD%|on<y$r"!3B+2pPdr䧃Xl0܆3(Vs@#GNMQ]L,i4,܆ yī 11e$^XHJsϗka DE -uf-jZa9S=̛0Bk]ߞ;/Zr; 3>c|ť2 o(KTBG~lpy]Y-õ(HFň癭"R /\T7ٚXғUb6;pUF`3@^2Flx: Ͻ!#j6 ԒƀqJ'EP Z xFZb^̦P@NXlUsU9ӸlɃmQu~']X5h)뇇99α!)f%eUo흛m{'fZX`!Ƽ:O/5QVm!3'1##)2pș0*SN)D*rL[ຨE0s>c 7Y`(M;32E"s kT㼭m;[߿9 p<*F-Z-ymx2HĘoV `+F0g:,CM'g \SV8<K/}ӗGFۉ}gImDez$bctfzu}b;iZS^[w^&A eyr.Ϯ{\5v1 d*iȻ<Z\iߑRȥY-9;[C/*eqB)pK)%΢+Y ̄H@:EkJ7@LE0TC ƍ C|^A@ꡭ ȋvDԱw?N`իYء*6Â=gzl/DYc0F܊ʊT*2we(Q!YU|[aE™ 9B{9oQ/T4ؽ2wS/>btThh<({Cdv3ʳO@Ť+ YFϘp-yKxxqG.&JU >7haF_P&S'[uC/T+Iev_v\¿$$Kv0$3g147_ 3\tg"kh<*Na}^&`!G QƽDK)VT)Bvk;O#/'x5XxgS2czqyn>s*;SLy7o;?=a|EEҰfU ZT`D뺔O#㢩 djWpxuM3M$S2> xV`*Q89Of@"ߩ/ p}T[1.oLbۥq([q aKˁФi|k9/K(V@%X<J RERmX_x6r NGջ5ς-R./欂RTYѵ2:%yY|AWh;&~W/b/'4@rf=B#)&nGŒ)QD%v AU"a}fLiEΌ]n0"9/Qa})=3a|NJ%w*dI[͌2@_C$2~zR1w1TJU vݕkSrjF':l8F<@Bck` Z owfɀ!V^bÍfכ>Q66ule|*W]t:BcOZ\.*3ȴN:hQ?Ml ilRQ:`0khr~>nO|P'ۜД\);ԵlEBs;)}ߐبRMv~FF3}K@Pc݈^I8pSSvfLE&A6 FsJkN7g{c¹X]}F?7ʟh/\jKoY9]O.FSf>YJ7|k@<1&'陨2bAxuPU?|ç.-b0}'Iyƴ*lZ(״E2D|yicdĻ R7y!pՓ81U(;:q~a8y :b큽۠h^Hz<;M$o4woL7I{mIT2֢-jgzh*fLsb+VL]F^GNjF:"l@rD$Q,1gZ03r~?f=|j}1E5\aNip9zU #q`+Ffcz[slL/7|}(&%腠1Q\L3V`X&D4@?Owh$ʚ LG#T-F/Xi@=<jˮTweƁPJP 2~sH0 g{.؉WW DjUVK*3!-Sq|7bŢ&_qo]n+KAWSkPT/sa۶Im8[{pQxPQ5vGP>Rbs-Rr9e5i|{neg.%DwlsIx? ꄊnzz]G:;ɓQ1^SVsa\#K*e8Ҽ6Rj1o_$p :zg%"[Ak ,P$y91k8^,]`P昮2-?/{NSubXrv cfIQlw;UX疪F^} ̭@ǯ4Vo!Rpc |%RaNBmA<ȮR14 :TL9R@R0穰ۍpzѶFC%ۀ7B8GR{Y+~aS~o18hT(v7Y5ڵQ/@1lN*TŠ_w[-ɿQ m7A4uo$. mv4BPeyF5CǗiȄ>DZlM@i nGX_b49koM1 )258`HQ$y4b)NzMAx.}pNXٶS%ɠ@Ǭ2%d%b 4nD$؏ $>$WE=:,ffwKT\oŵL(z`q~g<\$/ ;cnUG4!w jfX#$ϩl =+R>ױ9ǀ̄#۳" Y$7$0Di_)M:Ƹ,Ρ̵*tjյW`Op3_. #Lbs\| I#\\:ɀiUipE=D!0Ze/5B^!^EJl(5ucY%k>NR 7yZkˇ6zn\%ĊTʼnՍЬKJR\=YCKϏyb0}J_rf6a2{$6jʾ%eJ,eLjA,'w^;&Γ⳥KeLBi! 4ʽ')D?8~'af"!dj*H>B;Ԟ#ʁi K֐?Bd0Q]q:AO'G꛴qB.Ǽ@AY-/`ZL<=/_9BG$k's D,˼fYlPV> xco{J&HF `xqɷa?֛́$jK 8p,--6xZ36X(u}1Y}ua;p>3m!lv01pPa2ݠ=45.+ >Sy%co\V~{m{| 6!BPͭ/x;Sj %c{~ 1gڕy' XC`0z&D,":PVLѷ-bW`Pa\34 1U/&ݧ[s^goߩL0D^v9<;Yu`ULV:n0{)@megM7OH[ Ogo {(mWO6n']4+|m _9 i*zqxEc}7n.wHDo?;X—fgz?ruLt]IkZ4# w{ lgͰRvOd]3p䁬]񝫔-oSO=ڛYkmm0ɆL~TH*%;*WZ>5̒8lkxNࢇAbYU#|)9-gIbȀ%AyUAf  lR0cȶRxabV+ڜpj'txCVh #/FQଇو;n[ z0tDtO]^W Ofϔڿqi-4qAۢ:lfߵ}4[cqDmM/J HEQltrlz+'<ӺWvsȬ32wQ<Hmi:d7a.rFԥZ2BةR(5TCQBq{x[hMaUKK\2@8.|)c)9G@ż {EF7j9pz˽ʁ@҉Y{cLX~Ekl*Pv&ʨ*U+2/pG{#^FRJPa2veВ߶W i0,}\ǑaCiȐ|-[kMIHP 2ZZPP$FŻڔ'.G],3l"0cJOiOmݭ(buBXIKH)F}dd2H[L"m}FWs4 N9DDD[;D&2;uBk,BR_F@Cj\Zpqm'dP[e€ْ#MЦZ_w'*`MvS߾Z{,G}آI&*lIJoN[e"k U19_v+3y39:A@mCb[WןǨ3Sx2tqQCH1 2Z6Lt} nrJ2x#JG={bZ@fTu0ʖyzM80[0+G8D|hkDf XcsoɠKp*p$/ɜP.-o-_S@Y_mH ݋SLPhMN`ЮPCHVaާPY 6cI^P_58%A\W.8o()!O?FH0ml+-c `mSRw㖕R1&7Z ei?2wUg+,kX9S2ˍ(@RJ\q;UwoZX8b6L2Ld(5j(Y}V: JDUV#8;3S&x9 PU^XI1% ފ$PΑw[zU-,@h׊qLш5-C\~B UaU',H6YNPQHڗ: PbKUOP Vnq6'?e;\S,Rۺdpe𠋸IӀQiToi/|D,cd&>*V @t D^pv"oC{YUhDk0:Sgˤ" .a |m8;Z'{`jFOyͪYe f"yvђ1z+:~\uB?!(e^^"y,㒚, $9損[K0t 1.AmPYs8JmSi abv{^H fBȳyvN-zCI2/<m7z-aTF#"]_ds*ߔP @\L|@6(~M,) QZwјªD2I"4'l|SEWKзWd;9'*ӓK7$G>_>gǪ!&Fx{T^C!QUjyrNk%c/>t"!J]\oj$\%ٱ|6`vdhdMR#.;oWֺ 6? 4ƶ8V1tq*c+Dx_\QzA|Y4_*(2i:2S>5k{@בϝj5a|(||v2`JGa%QpȰ*ZW;/RO|(G& pS_ *yB mB @ r&W ς񀈡T`:ޘ0#$Eb҆ pUΓr ,^,<KxsG0&`z+S̋Bjw'R6l9 $RUS<:bTF=X'Eʙw Y88$#o M0o󀫧G K엎ho,$pAo|uJn<wl jdks+c! aPrHPh)1d03zMg%C닿l6tfz"d?MǷm8RςӴs hc \]׺BޝfHFTl6d#%>Ԙ>U߄p,R@E%unje5ۊyg7'ҬV=i)nz|K961zOrr deXFMfg* Lq" pv);qѴiF>T97 ps;3}c)ceOjdtw`Fu> 2A#O_W- MHT>}xctf9P~n3a| KPʚh(=X3}#qƔzJMiRYbƟP1w,U}x(NP }Hy`q#[Tg,rsݍbg)곝H_"Q< l]Bd^>܀: X.AC :]eΛ *|G?7_o#T0 U:hU-zd $F(OߋpYJz5K(ǔV׆NѝK-u=0@櫾HSE2 >17v+EM4zG]3Vh)%S(G q4fFzH}H6u!bdy%659*2] QXxcpd]y$f^:Ы ?y!iͱhjD(:9dP f1\ޠZ|"Ǎ>G8֕0:hi{wC=  wueώ}N6 Ե#2Nk1&~ ǹ 5'ЯVEЮ,8 hice72frGB/}Sεy`iVn$A#LavׄcT{8> 쑨0 .>U"%] bnrrGsM h*tFҿbmPmK^Oq9\MtcOOfCDM,v;¢S!(7}P=O(P *Wϧ,T+v fJ}QHUFૌx$}Q` WiIX6c TqOiC/i>g%J7h1A .Rc ӕyL a~ xC%UO¥vۍ8V39&Pxe8kW<p6? ?~-d*rT4ʔԿLB1 ֶK k8+F]H5B9AVؑM`}t~.jZ1?w;᪢Hp} Uw{ )8"8/[!_bC#0=irI[cE1^:Syy1)ؒHU=󏋝67 ~PA<+_j^.`y-[kiHB ֡WԓZOxDЊE'Wlh;=f[ WuNpnYoCOq*s̥arSZxWïk?}"̻0MsP9/T~7~.%c9I~0:u o~T7˜a8l@qx,/ s[9)KF;y{m9) N|mnj= CUJ^@-g1 ?(H(QB廒c_cc`ښP߁@|"ol ʟ"fW,yl?~жUYyngLr2,k(q6q_E=`4` wU!,+./STc\{;I>$sE{06ǧ*v]mLjb!CA+wRڋENAU|ix. jC4R~n"c7n 2k).`b#MAեW|k!OW@,0GH]٬Q42hʇf_GYhW?E䚭YD8 V:'<ɻmY1}n29/|yp*l{pP"F@J3h3ԃDb|'.I^{D_:m:aR4Xa׮Os%=R^Y¥9siZ5Wɝŝ^W+`sSj#9y@ĉޣFK :ʘWfciW5K3(+vIM S7=L%xƪ՟Pv"+tV5N>p8]S!`̮&?3ryCl~UkdGIYY$nM c9;*Z\0o%b,Rha?4!_?DP=9r冷 9k` Cx&GwY(2Yfy& !SA>کE}L "G3nMw6(:"TNԒs#kSD&9~7٣Hvh|c{]ޙI>ﱁ:-#{jNVͩ|Ǭ4鷝l2}㡵>loTHV++O?SW{~9y;瘢eDOF ua]i lU77zm}RCN!L>L.sz21V- JQ g+ä.sJ1!I+f<"oxutl_ &+h.ur,jc{"Wρ|i0/ı,8]mFXk꥖$ޭQfǖ SIuוM%@'( SyǑF^!p 5(7v% G<(M.RqǨg4W=U&B'{q ce\A4!Yn8دCiAn+^κCE6Hh/J;HCY&.$M0S"64d$ -~bJ4ItEF$quy"/nX$8\%/>]is#iˣ*Nͅp с~zFv-gM|Z[ׂр7JRUdQQ(ͳ[Er?!'y6$`^!v~b%6HW<}B{$llvpʳ^ *NHgG!(_We0d~cl/9Ofy\Gvuw7 9zu_ +X '})=(^L{3+a㰟=w./;d:I )Ngٍ[o> ׏IO0-{[*'7ݺRICx{s\f)~K79XLL)nlۂ4zU][4!42- kǍUlr'.5bek#,K݀%aMX6NG{D1XU_@J>dHH2D+(^R"G/Xd{Ӧ>ʯJw%V=n֛7y?+qҬG~E:(D6w ywzԬG6t44I.| Z?qѳb8Xa#'S笨m2?E]BhTYM%# @  k[W瑇پP>mKC-E5e+Ŝ 6bFdQ+^F|кh& ـ_⶛u&]ٷ&{5LzEn9"OYTB[)#lyw.O{Z/"ͭ@7ToWVm.-:{_N YRucw\^PWR履j^zॉTڴfDksupg@IOm(ҿ HeW6I_׭5IۆI{$c݄pO*`*&fbGncMY* Yy8k~F$Y?Z/?e2#4ldfֳlݣBkFMn] z7vSoP8`| Qf}ofdC8|6վv\jkƗfA04/mCB -F Y|z(6/sױ t4\q4[9/ b䈱y`*p6Q`Y\?-za H@Wijum7,,|=@6=}E5!kIH3<^QHeN),2%LـEiaT/ZhNkQ&Yb1ɒT#ipNo8ba9IrɳH0B6p*,zQ,ͥyӘDdV;uIy\*{ 3D$;=^.fF-f>Jy嶞qyntǍH ŭ&I?)% Z91!U L ) gٌl[ Ѥ3yrS%Wt`mV%܂%oΧr^^wF6^Lczh ^)uVg|fP"@t.WaZ Vu_>eE@sAO:F0Y} {9U>ow9o)y+WaSMu= @e:Q>Y\VSƌ,W\xvκ!eAIFC͍z@I6n|pw'e|9M&%s]|?V`ink2]#92@XTl?{$H(O f> .w)iZ! 4cs 8yK:Aʫ5-uuiq`V>`-:<'zJ[խFJuodGgm/oaϿx,4yP1ɩF ~\q[]G[ݢ1#&dlʿG~:"_|HNC{hܧ9TWL'@C,4p|Z.Bu8?9^ Á#t3~{Of2kZP8ޠ aJoϠ'1y*0D5QXg]=YYSr瓴0魰_l܅}3Y^;2"YekPh;uޢ+MO'`dqdFt~8}h@Eo bYڧyFE^s*}SeZh r0p*[TIQ9 Yal1ZAM#k%HJp '{H p{($iEiqAWNۺBauC Q:=v P;PʇUX>$PO5t.j0:h% ו*نxBU{fdXcE CB]:S} Ɨڍ)_ VPUvy8Pd "zR= H:7( 3Uscj*7HISaKགྷ0gQ҈bfPv]C.u͛q#1[8546En .<xlQ"ë3.a֡"I+?4Y%&UF؁l| %0@}~I|1ED2|MғB @܊|"/9'ϒK)y'i,,4TGn7R]R[ Ѭ%J}:[JXH0|m" |6 tlW˅ĨV05p("#>m~Į I1]L'1M6@i}H}]nGૉRn?QȘxm}h^Q G a˳$&] UEDg"ZU/keml:(7|&uP=G-꽕%DkCK(Ẓ<7`Lb1G|VlW&vD^G4JC:W#8S3_3hx\}هB>]r4Ɓ|2ё!v{''u3*uZ㪮Cxچ a[0Dzj(dAzbրǻ]`0͓OP,QΡsN-kU`1K[žqP*Cfo\Sy*p( <6G|5M|4B%hj kp-g 4 L. yikW C<ݜ_oʞ \h{~ Py?Ifp$B5 W 4]\a]niv [) |mN^;bf W ۋi)]`+fpub0DtO hi v1mbr;[G ]d15ri?]gl,ESH}N XM?M |ot-GZ|!x^h|ΗOVw24z+//>]vVI-qߊҭ%bgw&X}X>/?OœDz9";cNtT#W9>$X+;ܡm'|d~𖫐9!2At {$KI:K/?IU3:KPIi;g 6u+'~iCvx;6z \UHMН™ S$4<|1:rOՊgjSpcOYL5P}7;$oy#˅iVjVȸBGK8S[a( 9NtV{-~Dž=l r*"\V"{'e?>-$e;XZ sv6?WR$`t q h4O$>g7#Uĩ7zvOyEEQL^Lk;"03}Dn Q'zjSGd\!# %qdV|# /WT m•5< `~p)E+<Bd6DT<6W.IgǞy@%(K `Ay;x4޷tn1>:`qY(wG6l(HSrxEpXo"%~;BM=>8Փ(yŚEC^Rt3{fn%a2?{4@/&ϖPHgO2H^RM^!"uS^w/)UMs>E"_In5VN]琇) $a],t0yn h P֧JmH[T)bs+Mrmw (|Yrq^MN7U&w]7r#+~eP3aNM|тՇm'xbe+J۩4y]ĸN qۖ1W{`y_2;7>mg집 uyWu/p,>jT80V)pV%Ͻ S~W=+8yM n x^%2 sN MX OOHJϰgK;pVjq;#HЧI}´'=hV3_#ˤ5j6GXJ_Q{[`Tx98Lzo[=\?S8]@ ĖQsl~d-֋năG)4zR<7J!\wgkqR7QeఠrlU* \2;ҁKgbZ`kBvuCv1l%Q 929S5Pq6Da#&gz.sKj^_{gA{B06>7'h=_Dk&0D 1qImu>nú`\x _D]oy+b+T*}kZ/.MqC. %(gZt9VuΡRuݛ,N䣵&(96:J?=HQ9:Uj#Ij{ҎgP hƘc@0U)579P~ddVkv*`9n2< fgdx3̰Vx~2z=] ?#/iNTc˨RfչIfsfvt |y[z9c]cF;AaSW#ȯWHt3` 3l䄥3"m=TZa?7A`;zsx4/lc0-`GyфK 09ka8;-WNZx 9XeT&@ðʵ{Ca!s[O| LJ(QՉ/! #f|M$R=9vQ'=dڝ_r1W|r,v\1?&o/u)EG;0w13`.cr2ig\W\.NC JAaS)MSK +D1݃wE%vt :'?Hc”i-Ž7plh{xu G6 eeG(EȔY Tdž啠n^l}i6YƯ޿G]ï(uĴ/dG?_.chlK|oޯt(ΦxjӱH~M+C'K\SgX Nr#]CE?f_[rXM^SWQQ8>Ef2lyAC.Jz+Rf.Ǝ.nrRe_m#PE|Sx!|u+ҋ KPVm4q1Mo.v6E˷P0H"KRU:FɃYO+0ouP]a?s}C̆Z*ШWB #5`ݮhsc|+q[w9Rިı5tHO toc*V J2_Ch{,8%1BF-T=ݨYׄw2Zx X-\ljܫ^y"926i5bHpj8Tn|:``3  -z|zm?\ A) dsH5ׁ;4x gIfd?! c슓x$wI@6Sv*?6L| G.K%P& ( Q.lvцZ:pJlS4b$k{[XZ1`ѐpLܻtKNjldl8H_UxZOy=Py2ؔ%Fh*Mi4P~T=Į:"ee|#c2n/ +2.渫ggymx-\^Ibnn1Q4§^ȣS5.q.4ܫh<A_8sj?tuN#TT*Qv\V5G-HۄE+ջh1MpRB|da;,(XyZ3/o`J+)JUݲ%A^6>{ڀ59Ӎ{Enu gc_p/Ąݓ;F*VzWfPNf;E2/a$d&bw$G-7cj#wW{c'TOL\B %yh%ԲT㐞HRQ}?󐄚p*IAS[npb\)D\#Ic9(6{aGXr0[I9W47gv;JcsyCmx#]  _:9"mG/ry<;y0e0y}N֗+\nYO;eMSBǺ9L!`y2ڃ{iŒ{F-:iڻF+E;Ib0g?oC;a̦^<̀nG&P8MP$w8N'IW,=o7'd{>x'6x$Q'4 61 GN},PDGs 檯 $lG{+ds`l= >h`\.!3uy;fjUauoSėp0?S3xX&tc* TFRXSY qQN!qu{B6zq8 K%fwG!1\(o[^YZ.^Aʲ#>/NX7n`#NDce~mtL2J[4ԸXy0[~ӗ=y}H:x#qtfE{+) l8'sPɟzIos9 u5Z.cl!DJSzZ_ I, KJ4>4!A0ݖ}uV&Y2O>jrn&U:瀼KݪǧcWČDe:heavg2FP D=~7{O.V̯ɖGfgʀ҇Cw'i!DPƭc&Dݢ54YfAN0. @O8.&E0ë }V=ۍi7.~Őb1[#`@Q SyT-/nOKG0@exS(l]k\N[(rEpzs GD4Ƕdٖ jp7l[ ;D+C5d 3/ i*5BŎEOt97oҡ!ӲCE[ R |(QeYtV5d%j5I۰@4pBc;\kJޕᢳ<aEJҒb:E—Ʌy\f%l"rbDt^7*&2cٱ1cD+!S^{__P2,) bFR5Q)!Ju+!hECUu (Oql=ىdyg`yu[Eh mWfᝬtpHȩ%?Cxzc P?{u*|/y_c ׾H)+8Ե,AmKTLˊQV¯Nxuj#7( d~8Ba48ӗhf`=+<2p7p[?з,AWlÇ"խW8ȞoÙǾJH\+&z ~%[:(֮B"j&@T2~w//vV]?˰:;nd]>)\*[};͇w3YNt 61ak?+K*ճJ( 뿣?bQ {Z\d[YyjL`P7N3xts>h` %2pŃCy$hpX?Wc {4]r(w+s\r5kKA%k e@#U!&b0zIjrR8Dc^3V|Iե! p _Ej ߋƂIIAv8m^3= :it1YH^Ȳ] ˓ HqPnGv܃d՝MltE{/}`2mA#Qa(xi\nx@Td;i "Q`q%ThU*f&*R{ mb4iL ֔Yh6`"I6&,TIlݭN`]K N!F-=X UBLltFܫ9Eo xqӧ'L4aJмp_ 1NrJѶ=j[dLt*6?"vpI;T_*f G.,c1,\@͔$UN c\AD \q`bU/9\+>*կua3Y9\D_Osf89 wf[=Ȟ qH(W'lvtkUI$5=!Y]iL {`e|qwփ=K#7kƯb8/Tz /J)JUKlԘ0ʡv3ӐTpZgGN{oV`_Yr)y?ܡ:"3'%_pU9ٺufQ.u>7!hgxfFNT(ĻGSl|&r? #N"qAyā O2tj6M.d%g)^ | f{ kO Xk3ABa=t' Yl_y&Kv83X&ovrVBʔX`\jeBZ f[<#\)tTʼ`#a۽h.0S{/Hgf9to[22p ИK-ĭΰYqyc .FWW4#OS 0mT=}~0h>CE/ V2W$L,RNவx,B42z]9?"gN)=m% q&ִJGDQ>L?_ҠDpЫGT mM+GIhC਻Ú$^ӻQU&X2TaǓϮO^^/@2FOO^Dw" {~,1FQ4#^>c*9oE(.GP`[|o_O7Pwhpŗ0 =f&1\۸^oڏNoyX^sot9#yw}T^$*2T?Q1Z2:PQo81J,x GC#0 뻔vO^j6WZyQ{Ƿw !KNO#]Hb5̈́ On[Glړ aCw.lj9ҖHBS*W<0ЃܟQ >5c G(=O 3Yˏ/f&eYc-& db.)\]W8H)u[ ˲WUAm=hGSȃ '`.|֣x)39cj*V0CE{.3rԞ(][£BJH?m(+ b)"FU)9{O5YJyEw1k9w41ߘ: s0 Ekl8+MBa^TaEȨw@d$[qId I9p'DZ;|l1T]NG00P¤ @ܻD0moPQ*Gt N*TpeUNՇ)hMϮˠ=M.٨?%T 0I$p~啚l:.NxԐ]_)5,(;4 ˥G-y0!)WӮ;y҃e|dq.}$61vSs3U(ӖHr 45%n|9w+2>!oځ~Ygjf|x3AתωxJ.Λe +*yڟv %8:=Q pY.ɽݚ}YZ_;b~%×xtj"lz IiG9öˇ̂aa+F$kt_ 1PRRhkBW؋htVM[9Q+j;Z Χ\#fNI@-`j1f|R)cR&uf0<[a ]44~6o%?EI`8ъ FXa~mq&˜@)!.^xo*J @iWwr]:|A7bLeSzzS/9q2a#Nf9 u{"w!?<-SZԟG-B{?5>[REN)|V!G))װit܈fm''cn "Lv uѬZt+|(rϵptٵqY~lLqwΎ>0]oՌEe=.3L춎8۟7F 1)`[(}BaL+1l3}+*5_V^]FuHp0M%y8%6p17μc#эt[!Ew [=ũ3 SUK/ _!?.rU*y#hގ-c2aC{t3nԱٳ-%-(hV]NS$aVfXu{xaW+H[k3jpa31[v?I\gh1GQ~ Za+?}8? HyFuu- e'ʱrbG]֠=/?i鼲R1.An+'c6 IB*Dћ]e86·԰?tB_h\rAMU,£x~ vp PMdV$ovKotr<n!`"/j!൲9kQ}5; KTx($Di)~G*M*X0IY+$8썗("Mܾ*6_ >fq3@fMP[w2Fݲ䛯\h6Cj79+~-SV2:\9ǿqϝkuj9?V2V]Vo(1dB`,[f 4]NsTХaF>* xN:xT)&14b2Ԯ8,ɂD5o.k cz^LH )ǗڏP-[ {atBHxG~&y'=A쪬bqTnQ6xEvbci@o8pp>f tLEʆAL%8 *2" JmB].lwHJ 'EPuBټǐ.pÖ'ɼ'Ś,/@Ćݑgf2y>3q-6׫΍vGy7"Pn¨<*LtW)f+=H=IpFe k/ Z5`Bouڤj&&ݝLnt7^lh6~QSˋ 3̻s*OZaYLn4IPP?UNRꀹF);^ ~jTPIUR}CUܢ]x$yY|Hח0zdp6ŏ1aGrjH :;;A~Mf,oULRPQylQ\ aǰ !!oN*.s^Efe8n:u)nk \~# 8Iۄ%`7Xu}`<9'֧U^RŁbMG"eri ޿,lrP 2˛½1R]Z ys6Qg@;]yO' ,`3T&=xm*=wfW89/ C\6c)ch⫩Iֲhߡy{ZQ+j ,ZvqIQ. :kE:Bn Ƅy4FPw]n00oᚗ&0b:sZ(Kdh Pk up] $x8(?@0@@&Ur|#\`OѽXiʁe/c0̟MlZQ owTaE30sX (B聬IPQ.}(Ą'cCls:rD3fH=,.Y[~4P+Nڅwa@*iI]˲SAwn..5b@췍f'fԋu&^݋~!Q Mw1r1:qgz bč|$]% p#r3J#ᚡ7V1Cnh9Ia`-cY8h 16:uGH iQ:9J0E cd:qfAXA=6smYYINw:} #$~*7-bBqɳ,F؊ fv.i93`["\& `uV۝Y^l8^tv)5by Qo/FK߭v,-GXPMxXANKv->q}BuEvL[F-oh~[n{x}A6#?Vw9Ckc8zD.-êjw`L$de^D1)fp䲖O%Y+[fC-%MLDMMb 7{o(ީ࿆]z]UbZ (΅YEy&PDz5!U%kMi;dyv\Bb,B=x}tYIt"ߩ,n1j.h_PBMZE~0ۖXI$_qԾWW虿DTzg 5 IFu:X;5U[)R[J)=,lkΎ9zRo:Gw/^gKjw1̀^藾gO^v&<[G]2O`V,PZ_J]Y|E\wmp*]? eЍaK]1h+?ϕZMHןAnTfF%3׳~ ~枑4Kdjm!,;",.@ybr_Y'H9=_X;.~-=ˀf-LxOZ. o_@uw؆F#gXKW?>\6c)ch⫩IֲWqzots̚ψړPH~M ܊M+:]j~&!nlCs"`XkH]]"۞bvvxudyA"l|)E*pbKh O, i. vи8JXjD- ZT-is0m 亇*k Ӫu) \Sq&}LеCa! )}$:&Ͽ=#`U*=;HRR((2u9}fҼ}HAۘn< )0 Q" C"YWȶ4"CgI+)08(B&=V{ƎdYtU=Լe21\XDCc+߇åW8o؋oB?V뜺.\GTF ytx `J$7| : ɔM%4[@ΧFNǴ0Ui'd2_C07y?W ]'^R/~9^t lל *S'˒?>'{h-Z[ POa`FqxjVZjdm|RBe$k2Кg(N*1YЩ,\hȡ.6YuyLR@E-j:>,q\4-LFofB$9u *xu!,|0odW9~QwƬPhǃSZzLhhQڍRҬ@]5Ne]WOȲتw3@h~TZe2%}U%'w;zma Ph-Fh{QiJp0f8)hJ) gedЎ)3/ vijҷU}*PCSE5wq뢝$Qy#O U?;T1:W H^Da@ EE e,PI^SUAvKnl-̒H3LӼ3! jB1ѫo_Q'YVM{ť?/eIY6ցTm܀{=ynkv:0Di(1VC Hw6+yW,։f&@n,b;[b\^YH/d5h$m/:t% k2.Xen15v'+4qc0bSZS^Xx+ -%lĈ*V9xORB,{Ҳ@6re;%,9tgC;3bReC<6Ć͙CvTsn+>v(#I$tV CpGb9>z !P_L`TDwfF!T]hurۧ)Y9zգx0žVqLmi"`*}Pj)S'CAR]s dTvcg-zBQԘ7a(QH6Oeqcf~ssJKѓ aLͿkO@7,vxn@d1=oH[Qbƹɸo3;B0 OڴvMtߑ< ˫c"4@57rx;T ]Vao,0t45#|P o~hOz W)w߼E~h*GރUTUKga׵МsUH!jv5fTv(eh ,i.@kH_6%G4cPO, ?9Rve]Ȇ/:7h\zx)ST4Prs% LjfDu"9wmQ' ,-rs WӼ-ᙔwN環yتyٓGbZ.-b Ӊ~ťssΚ1G[Hz{ L:(V6BxDaedSPŅB\s` qvvS{S>!yʾ 8u o[pぃ=z[LJŹdvc'5DjlvjHxm]c= Q%>:!tǵ5G6ECeG&YS江|/l0+nszEuw 1YQt8>jmASnb6^s lT#NH6@fs I)9Z!w+\䘉eB-t1 I= 6^v_Na5+ 'Im%XKe_ldzK2YɣO4E0\adjL.VI+sl}\a\,b,//S;tuz6t.Z0JNFw5ՉܜZobLayKH{=@A(QP䥕ca2H~NNwW`v+ga׵H)R. OgfvMz!,h8@U<6];5/,ڙ-S+ 7JSp]g-ACJiԫ'QgV#{vYmX.hyQL7ZA%%J.D⮘ '3\TEs('DҿZL_/z #:ߥ+x) ʹikDIkcUV"s-b|BΈ_Vs#"ExNzѸ\OK>Xyxg~zMzN\ ڔ?v$s) yb w]p<Q4P_kYBB JP ?cs)u+Æ#L_S0=JqnH|2y8 rfȠN2F+fmxE[\Xꕼnl)x, j5ڒb? 2,+NSIh xh>"x R8Q0 5ҋHy30֩S_Y"g$BKL\[64{N' hce5o3I"bu Ti6'a:#7I=: ӓje#q mB.N_wk$+&Mؾ#.΅Kkvjӥ"|!?c*a;avW4h2 GC{̞|ۿxW9=Znr%@jjck+(-!}i9 Z͕*K D:vn*Sk|øYdR܎̃jלڠ~*nn>aCg!2R8+R]*uStqĕNIʃ~Vr#}_HG4Sv_ +x]N7rJڃ8b Hs;$h_f^Zg1/4$P*1uW4wHbܪ"N#Y}~6@+oؗD[rKn͈>[/8W5qwȄu4#Z`_+/\8W2n t ʨM񜈖!;g -(.2i^\8o׻DL.LfH}Ss1.8]9-h8FdX8|@|dӠN@c_bvٹ$4Gj \k?.}_P9K=(-\KmY0h%D}@;xIV[eo8Ƕi}gfo. Ѣ_>nΩcQ%CGCLSјSrUID _rL"~"?'<'20QH`vNup;{`Ķqr'z !i(<+<c[h?:ʗ%Q 7@b &и,U^9-FyV jo 486sS^mأt"iB Bz\`8z.tEC犮ky&o;;rYp*#/6?!|B$}!{T#UG\d xg7 ʿ$!7f_ bBq,)yfAzGV20'w !FrjɃfN*-bH--:8JP]d ɩH \/$C#RGΐd{=Fg󻋱L((3 C)}xOjy߰u ~Kοg;B-_$$FSv6s@jc3dž02/ź{^w~>7u/h~(%8R GJ9z]?x=|:dٱwّGMAsY7K+b@`@J4y_^y00a)' Ё\BlfzRIc޸vƤ\E|?cV{AxNb ;MH-mX p9ۯXD} xPvĥ -sAtr̷0L{+Cm2 >}I\3V+ty bB\`:A#"p )?4@oEuԤ/Asm JQ ڼytR5~7"pf߰J E L+r@\/H}=XnG'0*)1$ۑjg )p5D$ͪv^jJ} l 1ua;u˃%OJ?i#<̔Bξ""1Ɍ]ܜ .QU5 覆ը iq\'`?STep~c~'@7@ά! gЩ)7BۜteSrRs63g tHɷVWy*/aUjŒoڤ峄C܇%DxRvq΋b|@EAsDZeSUke5 *o}쭃 jޯu#U<fߜAoJ >x6d{#\tםoIQ˯11b,6N#`ie!m-%mV͜{|km&=D7bo:R7WG'w'( tP9_4] FG@5o ܛs=G*I>kYlW5(N($> eeLφA22󯼣*}df{~նMSϪtpgZjb-jn'i~A}V-R.lm/Y{Al94mk=hJ(̜q8щhTɢYJf7ǘΜTyQVGܿ(f.%VH%;|sRoOh <3]KυaSݱ;6_a o*lOJ?s2Ҷrx \A_P(7u-1LIq `]k'v8d <:]QaZ& @z7=ܻn ]hSmw~z"@WϠժ&\NR# [۸0:6u7n2B]T[6a^H|b)\ww(m8ovfe֌X:fu,fS"ڗS~Y6\c 9/ߙ~&|WVMna_EM5JbpVKVrdxTLEePb'7' jƉ3h4(WTeX4łH"__ie7s=}"}ej +]@ߎV =TC0APʯH\VNA%F% B@&/h\³4$75?v2Hq`>W8NrmPUr %ay9 lecUB - B)Ր)?zdtfD;[wqSUƣ & x#d~娔B`Djfe֌X:E!fac5|TFA\R ^ - 4{5K+OtK"w 4rxqlB!a3rlv 6߀m*QyMz\;ɦUyS}<۟% ΊWJ+4&.^rCړQȇr5rv%C:Q D+fS,̛hN}ȇ9?B]EZVcf?Ӏ-89mnS \ {rE AUz!{S&XPh,νSl:DfJ<ɦy&EH=0ۭeWhM=ĤJၰJ i-*dNYTZ;^psV+[VLXX3 ŋ99~*!!ru |#rknR]5%*,j+ץj}3N:*v:;t2SnVnݎ${Gav_sI@4UXlý]NW?w+LG2 /'M)ʩ0E>g0;c0~ sl.[PJ57ŤzY _1@((+na,`Pзu  !E"?|.܋#dL#'[jl8zh,:u{-P=1$0 rxLb-FO4?T9EN-O8{x<oXZQtM)zήMD00qJJK!0qiVM}Dm Ky{;3>^4^& W"M]hKB؊^xdqT靦i>L|bK832`S7`XEHyt.i`^V2[՗Clcucc|o|Vwg#$RDPih[ QDžς=du)pu=%_c ޹X5++au2$;f;>w@GX\+/aS9e>Ě FCAq4Ұz]htRcLNҋx)~&xA|@_=h8?}?@aj)YVVw}^Fn:eҜ^^%Com'Г])Rn`MIQ wW\>X ! ZD|FS\q ğ3ӆ.3P@([o6KJΧf#"!V>|G>lp5 'pc{+}bw["ӌQHSڇ<8ݣ\! )D};ءL4GL9Y1 }ଟeF+FZ}$ Z=~nC-~Ѯ\:{ߓs2\yB{X~'G^;~BU.rN]Q2Cc@ N͆KUy1%7F5-h{\ s֏IRR=jH0&̥?}dh1-m)I O7?bwj-?%(/Rs>4MSi:%b-10W#ME'_. D~gr5X')k>iU^qmVt)Vtfm@ !Cn\S*aAnil Zc 7 cI87x`Onu1E |= )E MI"*Ejޗ|Ysrmͦ\O@@s5+  opP~X,Ba++&dFΤ噪@數Ïԃ=1\ɳ?6OK.04ԧJ"Ae4zH;V9GL3̷Bp5ɗ^X]2PT_IH ^'(5< ,|psVɜ`pޞؒk^"!rT B~K&pLDx3./gFllzp5G~w'+zJ%W*?k,FT=>圗WCy njAF 3~7+B 6R}kr ?Qf47@#oq'GsM!2 rkY1Վ6R^0kC&w 5jjw,g^̎Gl-=uJj${(`kPkXS qs"R_xLd8r.Ϙ%z8 C$ ;x'xV+'/Q-ݹ8D!pzSJV3NxZs*7r*@?Rh^ *44A+ZLnk_'?״ڼuć )7ThM<ȱƬW~zr)%rK3RdcS)zx^j༑D2R rrhczRH`7sVTbQW<:mKځmH?R2$S 6[XuU GmI.#.2w g<162|Zsu4gfv@/ޛ}[@Q "bh/=f q=,A:6ٌsˮ؁5mI6? _:Jry)'F|8 l.K=@l~s< B <&Uu 3' E׮d )GskD!3J[ŵ"/*-r+ ]iCU_{ѾzH |yȈX__*jg%br:Ʊ퉺i[VEZQ$OV+kegXe0!Fislv@ZgHPZY0JXTM7ѻ]?e$( XCo׿ t?TNJ{+ 5t%葱P-0TdEֲnbz̕dNxi#B!.@S|2~$0]0OlRckef=M0X̾V$)γ\UTD wRsR٪q?u܏etJ>]&Gsl$m.@ @hIwZP -;&؏ FßN3 -|;3ql/~uRy:x+[%[RzL_v~ F@w IE(;($&p}_Mf:YhP{iVg4pre>' {~k(d!"C/7=c569Xa9`]:i8ɱh CmU%=Cs[yjF!9 saMSRh߉ˍ1`%өI=hJeFpw֩@*U?~?$ DQER^'Ka%Q6¢ͳNƱ]U.*Mi\/"mt326sH/M+'TsxsB. McjBzz`npm"F#9T#H` 0-P^od\,96"MfkWW l=<ǧR獭eMboU-r%Q>K\SJ ZSiDB6/vj'..] 7,7-XYayIvG;͎e˙K]d``',*,G$u5j2z,3*@*Qtmʭʏ=q3|ie VH|?.ֻ3"aoJ$ʥ(znVC̾VۼC"{H_3dea''SW!Zj64g+ؽ(!^gL!yj|Զ`#рU~ 7(? pОiQ(U8>k(Nkp "V̑OP+3&x%qgclhܖ2F zxMZ@(gq6O{9^yb0DŽ B́yJ6(2jG(  TMZs5)Dbe\#}`R^OGt~7 8X ~ߖ=FΟcNFKE)ƍGsT%el4p[&")D=FAf\(bKIQ4ߗGDWmߕ T>*eӎOVV|ub8Lr̒]68h.%,iYhYN(lD~LFGȥVJJ Jк[AiD.jCGLy T,ړ[bkaIs{5b]|!łG8CL/!*^RQ0Mae@p?P0nIR)2uku%D1w"?l^ E&2^dmIs'$tj Ń-i i+j| k[_>> U>^W [vb5mKN(q $4@Ff'`ڐ'g)7nJtNm;k06TG Q-YχS̚KQu r.;:aQUzS\ɖyū ^OTYj E/өJ!'~wِ۟l TtM<94o'@rfF+cZ}r?KY9@IS&m\Ulu53<*7 nȫjܯc\J3yk9 ]eNٜ|{w^HI^g@+D{ Sm'*IrSppAS|ZjMۡuM%Kn(PD _bHAR} ɯ2.fD{x 4Va ,I+}plMyFIu>c? [ku[ 7{|wGlT D4Yj|}9_l}9MƐ8<US+<oH=$TDZ#4ImTKt;=!Sи lJ^4Uͱܵ KtC(0 7 }rb@ı}7A?(5V I> SbHn|yB9;mڷPK")Í`M^6ۅG/B ݤWC~6[QpLh^Ѥn*rZB:UWyQL>ϵ79 "|#pC`D.8Qt{ FL]6 T UM(7(ETjpQJ곢ĉX/SfU8"ixOIQA:jD8&q:j ĈQ+,6hc*q2tqNy{,2|K?YOJ?6`.W{!#g;א_m$a+܆m/a/oSEJf# #G,!9$0P[5_PUSW/2/4E +Q֤@w*üiөcjCqe8HW;Gp?#J4o#u0[Ep/L1?'_IrCx8/t _5w S" /ěkM:ÇaP\҆N[yL9LMS"[UCjH9ի r [,Ҥ/Cc.9M2͉=7i_8RsٰyagdOF{}F.K!$vV>6E[F\ړgf!J!;A ۙFzt> B$YEA}dAOz_6.s2n*'x9GvaGswQױCbS{Tć?S@A1󃿇b/]s=b+ex ciMD R S͉$}3fc%N[p͠O4\QF}o!,: uW[VSHm8`:MF65?[چȻ+Z8 IF:KPE9Lgiر"ԡe/]d>K0s.VL>r?,՘uaFu$Su'ۜH8g1k_߮jr9c9o@S<.nQj1WrЅŖev"Š:ekrHsOIh,Ō=C{8 DʐЗAqz,|솽Oc=6.CuV(2bɢ>>*S1?Ʃj(!K2;Q5gBG4nKQ72Uz9)F8bQK1e+`kq=Zhv٬u}%K\kmCWp"n~fDU DÏ>`Ed8~*p=ߎ"<-`eǴDS5R%;{jy2q,ob5eYSk. Cfl5 ,@wa_a񗋤kSy`sn0~˸֟ /)HMwwy*"Ʃ/4{[9bL Bľo( TMr+吡jUILddLlepw#ZKqLkK,"Q] wR~a۷tIX=2~(yDve&߯%9%!|{6&SOcސkޖ^T&k ˼AcA%1u\8xRkSC %p]Q,uQcz k<(w`v5QK6ANJ[wFv̶ʢarK&ĤN]6| pL,k:펜0]̓) /k3P_NgKvZ!!^f< &QX1NdTjU97_/cƭeRnC~K}'oy}$߇G2]F-'LD/iZ]de R, ׎xp JJq̖Ē QRqU%Bwyý!tH> 3# {;`JoɏLm<=&rm#Z9Z{Go6)<;'w1gGE cGj:sk?#^1(>F0xQy̬8wq"~mJ68Y0qp#N ]9odkkzӮmxt0=}y @WɏbH92+8pVZHb)5wDbL2Dw9ca+C LglBζ;jH{ʭ )7?ܩ%īg[44'7`k#v< sߖx ]XKjw"/h&gi)ǐ5L_12 G~(om^[|c-Afp.Fq2 sM} (Aw):_V9 KQArsй 6k|Q|愃t+%OYQ,hVyk2"bs z?(G=s5^\}sh$+^pCVxVea"²xV! wJ\O(W{-wg%mׅvW?c2jV7< [JD~)OTpi_479{o-C.fޟyQM]cObퟄ;,fij~k|շ^ϭ-gOLZ8|t>83cQft۸">=:!9@mJuvOX +kBcmm9IU! ~Ա9 [)0l!V"|wS nLR4v(&!Gz(kCX Nn0A<#wXM,eꮾ;$<,3EBpS8ڢ> i1%VӹWz#-SwFW }aEt6YWȄpSgt\ 6ё@&F)sNi6dB{ڻP &R DݾfCv#e!~G)k~Nń(/׳qcH @PEgx ܼi8jD<23_ESF*~-!1fхX;Uf3g>w>[}[l]낵hp>hQkn!ABMǐ,y( 0/kv%/+ Yfn9"މ97;*t:}(B$a3u4;[3Nwe0̹b|hjJUv]g㸌[,Q ߔ]p>ucozƑul %)l&|Ѐ' 7J"ոG(v3vғW7T{"]@ԗ_fB\I|a5踭a{c O}0lotf|;VM vj4y93ҍ 4J[FP@2Rf̹q,@[@N+ub9&Q7Z~mTycB@%Lz̴OYj)BxbB%gÕ9B56s6?aroE.@$;~z60x2fZŴ^~B78I2ַ功ecVtLԏS?IBxAW7nG` LR2̻5 *t~Wp5xT)$F 6ȀG2J[/Sܣd|zM*/p4 zO򏅎O$@q#G5KWlkv3_@`ȱ}Z$I1 v;=`[+ɥrQ8-/A Q%*'oߜ cLKbG-+Y xJz໋lc<Mwu?޺u\#t`=rV^(]ʒ6¤'߯Lhwyb* &4(״d 3oaSD3%i]$vm#n8=3ݨk>yE0K2' E~cɱ;&ֱ2`=(^nibzja6)ҫw|7G+, ɾ߶ku٭B-1@ܛ+r0OOP틁L8h[:9L6엞)0\(3\'hN>xfqzՕ:ONװy#Bᄗ(@e*SHU-B-y.{Δ{Ȩߞ8}>eG> ޵ &>r\9mO^GAb$ Y9M}?M[h0=E|.]pkYIy' qZW]⼀Du{K]ٛz\&/n6܋ ˏ?{v01$B &<|'[aCqm6aɈxClHm&>Ab%t'~Z@FsDP'\:l%ߝ*ڦ[s]xTE\C_9cCC " Gƕ)G'Xx]^4g(FKZ؍}p͏'T,ˊW|JzU dlMWSBl饚LԁMڱig4vZ5.sVBY)-m5FDJ_&*Vwf;n#[ gHs#H"fH txhP+y(һ`V2yl6zx~<$cP(BvtڿnMl&/0%Bg˗{uxHCS$9IL&?N潽}JpE pl?p"hҧe AHuݷu0sL>D+ fPs$c^PmKb`"tni4wL %c.uK'j+Mf?W c",R_%̑Ͽ1ٯ ] qRԀR SsVliۼ w lC"A;ωܐxW:+$3)u0HvTȕ!²ae4Bb2 [NlHi{[tv{&W3nm!D‹B)WCy[hΒ!v[q=`[aЛvtO~Y-9owS rHj$vyv`f` Q?vd6ؼ_Z4վ:|^]CcK a1 FZX=B-w*JQBIB,({ZCs`\Ug [NMQ#P8ZE#3Swu: O2Hwoi|lϥ"=uiZ:,lyogP Xt$0ȣJB ;ě旸+9˵FV;S;sX,,6:Uv5?.6_(feJizgf18GPg=J"FUX1[k s@W?%r g#MuN4lqyL4G A2Ly܂(י gLgz#S s`Eh-`Ȩk/-QDCMTd(6w?SިJ+ř^]Ao%D$/r%^[%6z9؝t{k`f{-BޘY\:-Xu KM 3OY9%1 %5Oqq XaI>)KF{>x={8O,/_ׁ|0 b nٜz[HT x=IT]~FZeXlkMK%5㞤~+$B ƞ1W/#̲,rYW [S ^JAgNU:w#e96Mat3]vqԝ}K(_%\ ז w'5;WJưn?>7̐EQHe`4ZxclFtՍ @vBOXsߔ+gMA60 A߁ƅGM>qQx"B 2 $X8kV6DFcz c-ګD=@زVnu]$3u sKLM w_]l@4I^j>YuLO@_}Y2@S`5ҕ *A1ؽ'L0A gRD6'=+q(ۇvr2ڎTroû=C'}^+/xKoPz*?չ =TLeUASl\Ub/ .cl8 [Q_srpGo"Nv<)Xwf8shLmQ x9֑=8X~S0ܤ`zq-sŒk=bNW-F Y3EW4lDg>[`8\+s V,#ߜĩ9jIRA$Oז"\BWzh"CH+8Xf&$AFHQf$5J !}_m~Ptq`oq\6lo)M)9Dyc@ɋ.'PuiGs _.W/~b JLFGdWV($x3ҡG= B]ЊeJ٣cw\ &TcY?w0?=,{q=uTg(q4 027;iGRBVt<3^{U;_Xs( ׸\ n2HjZ5#QD.ƷʄIpض^EP7UHZPCgc G:;OFBxFm9OP7UՐٍXi-2&Yh(6nb©9KL>%'fLºյ?hu@ۀ-1M]]ZQ?$ݍ؈O[uF[ځDE@eM sf\t..Ѭs+1$8CU43QV}ZIOE<ӐN@>6Q#t+@3ь~+\p+C\s ݠWރ-||>=k2BٞI10jRG7iGbEotVf;"R F7*SF |.-|Vf\K<@gyYQ4PxnozZyރ\4x lΧ ÐİŏvkU&-*+iqMT vm{L-9Osc6"6?yj]NO6I |p\aXR0-Y(`ڧI]C蹽N9n i'J_E vkTa6E j͒U;$1WېaX|ɏTW<KzR9w6_(_7~(]J+9ޞe:^_T?#Is2#$Gک<_^qg]ՙ9N`L-^gR);PaBBMp o r2¾*xp1OPN2l z%%k^Wq2d|a*"Te+QJ}%x!8Q?CA/&Nr 62*/tE2%!d=0B8Jn-tTIU~WsLhJ`S=O1u?vu=Eg+srMт,ij_f=g(d g;g:~nc/;ٹy,h?횾kތ]%a ko-Ir«Py}KU#ɆeMzDV錑$k%D!o[O]չS,GQʝ͇'5&ilA' y@c#xK/Wy,*WSD_'Ab +DG[aCqpM6I8-qw?5_G6: ~u3}jE*5$ϧU;^aP4$ <8K S%aHsU9ū]M!a~6N4JS}vN]0-?z$ X*7laײµқp@IujWD#؄Gx$g$~#RH#ӈ sbg!!;TG26e?. 8m2p7>k<1}Sјln6e-Q#^gEYt9(q6,0婝'p 3w. RJr@@0 EW"oC]==謹z Stsk zDIe!hevCwZa2_qNS"87y{o^YFzr1K]1 2"'"c!3o'Ĵތ k_wYE3e'LDe?B ogR=OK~o덆P= +ĸ?114a-#{Ra0ֹ):Zn(͕2zr-ǀ3\Dn R a&x#d A 0F Ij{h* ǹ.(Q7[1y2+W+ FQAFE23* k\u(K+3Y¿3$hvsln Rʄ=%r{ ߠNB`4_tg,?^h<ڭPr7"1A,M}Sk-]X94}RhnFjB.f >%NVȭ~aŢjB}D敂Op1ď=fq ڬQ X.82(؍BlUh"Y$m"ߨ E>ȱ5vfjfڛBj:^"i|޳@e+w1 2J+7%L&$H>Xu Eۋ:"3tu_&Ş,RX;:8^X#o꒖%I5_Hm"uϺYHsO~'KJm5ɿE8Y,LH$B^C0C <finf 4lsK=a"IJq}rO RHx2Ths.b4cُvXX}=J2^؝6?Me]cmWIDsߛ)E 2:YXfO$*Y^Rژ>era΅*"}$Fr]Ķ3J &\n@/r1~"gr} n…m,v~C+Y06KRd*ӹK 65 _֔}^-8 3~RK_mH8ĚJ-$G̴[2d͎.?eԢ^@QNHE PjR6)ԺM2qθܟ+53FYh6F&+)\Xы/%h$74p8DԸ?gU-VyE 9e QOiv_|l'b^gI̝ky(uiO( >i[%п^+= L( ?>):ox [t^rqSẼBÒm 4(ٵgA~䉈V۠=:.gՃ?| ¨? D@3sJe ncb :8m AH!u54Rn}&6'33S.PstrbJܶDt$\)]SO !c6/{P{n)}{iZ`[q:u}[; nߥww/k%7޹s?I&Ɖ+pOHsE-F| );JBa6Ϳh $y>AKfOGk2[жPpt) 4ڸ+.Qެ3rO4z}yؙ̌aԳCmI,$*摈ekMjbDǍX`LX}$"wv)|f)`'ƌ:j U@Uc<0 u˰aV!<,U/wd Vd~_4 ^Y2M UmI>7lQD%P[h_w)URgY(/j!eNkS0Ĵ9x*{w砆wh[O0 ,55B| cLv bK@z ]p4Sd"Awbe77u">A/;ؾħ #R8Sc4 R(8(ux.`kݸU2Hv>b !u({^PM('ˢf^n³>{ 6D'%&.J&XfVOm(5}%#E|i>֌X@wXG5"[({*75Jn̷}_<|GdˢӰZȺ4ujq-"e\~1GD?C1⵿IpLC,}8Gjh0R]x߉_ C0{06 >DeLJN7Ip- |c]<#c|@/?2e^I,pohjzH p\K+_r"[EBu6~[ _<d:;Clݭ28U|KZMuz&1({$90 {W c]Y XW|.|J!' ZK * V,tNQ% 8-?v틷bjIaB^z46>R++ #IaG#k* M9~ msQWiR*@ ڑ ,yz9]Eѐ")۷֌ŇDgPI!\z*hZh2EJ C/Ҝ}yXy&Y' t2o̝>eF̖xyUG"άOGvSX+L 3xY6(& bx*nu浲ͬUMƷ,Wpz ~/4(͝4:%M(vxKΪbEi5? tT^1^4Yhyaݬ|/rGv0Vf i[٠ê`%;?5ag'SnlHM9lAfxbT'Pj-4Pӛy$\IIfs 9Vh,lX$ U/sV:^U]R辰,X@~Hcؾ<8iUF(a72&Xߝ_)H>@sx('4apFn=vk d9Pڨ`gNݦGkol'w l<40d)H1"5u0" r؜x)tLX8}(35=+/#q\F$4i .؀WVM=\1,2-͏yϾk֝4պc}3*F,SN-#Ug@M:Rt-."B#ZYaGY1FX}OCxmhs莝wч\+U}.XIyޓ(ef)w VYs[y褔Eƀ=HU! c۝ C6³"CVËlyg7+L綸5@j=X8IȤ;o*IV77}wsm ԛFPilfAژ&rp^Db۲a 6$D" k Oթwd`mHX,`%cX >gv6p0$y:bbf%E*t0BE$'DMľO_.B~DAÄz 쁞fZmxs*u/:X%xJ#xy95e "M%?D6Mͷyy @k!`0mɫأo~ҤD0=CMtyGPd9g%j6l2r+zB2϶$yn5]%ʓ?+GE2bC޾RḰve1_gF $_S*ؓwB~9$%q[?畖ơ,=R6VRN|g\l=_;#gnoMHo83UƸTQ6W@^Q*=&ȉx-HKwJnd}[}>qh :1]]W97 >:84_jC9"Fw}&gbi< .r c&pp:P )H]8JZ~#K)ƝElV/ `K_죖5jJBI틡xmz=[>FЙ?ڎפ+'CTڕLӱ]}տ0ĥ+6X:%2Sx`+ckGġX0 .ڟV{ m}0d,Q#\fV*;ɇr{*~Xf2h47m4Fٗa=wd GtwI$u3&]+MDN yrhO~ҾH @yrʾSE ˥Q켭?p! G6uu}(AK wSmRƠ=!&We{ceMkhPlyNFykYANnF]J+oU 'B"HԶDVV~ w壸,Ê,tߦ:7J-*Fx;4ѣgeY?o"X:\HBWM2uW_Ҹ؊,Traĝ6%Rsl:< ZZ@Tuh$&H Uz3.uX[N1'JB.tqyG~|[!gK XqF"ntA“:Cxo]3i=sM-e}h٫[K#/W6*7wڡ+b ~O:1v<$V_[Ciy!uQ0x*.htTpzv6,mnzpĝ*]p1qڛ"`2*1^G17}\'KrJPy?ctzU >60EbT|Ћ"D` oNiVch!L4CG Ig_Qc;rio%|4Hʌffmj6Ve>0?bN+1 ټqF4$&`KX =^MVe 8ZրŨƩs% v|  O {*̄oi`qƬ}xG$?mEOT㚾m H:yWfDvlA3_?(&Wq?7'=8ΎE#p_ a9vHo((r\v:oM5Ron״31?fO@Ԯ5„lj쾜B\޴B(YB33ag 5$Voc UÍ#nX*%ۖ$l^"ԶlBHOԸ[P]MmTUM8iAtEr]"TIli(6S^p]7ObK$rC#3fϙi*|~&MQ 0cuYv&xv*>'~wsCEׄf BZ'?"ċL4+ЎRqQN)% @@ϑ4^u.Jw =(Y0DѫJb]TlUuwj`v+;X^\耷,#?TW{f,g6;vȠ ټ6PmWPGb[+!Ze> ]4S8f/.DV9mf< \,r F L2qD|˟D/2x^2CǘNvaƋ-[=Ty~m?l7f%O#lt\]mv!qIP\7@*ʽoKda@zJ^݌ ebcc gxJg\ GO#:j!d06X0ifq؜;3%gKB[`Ԕ~24[%CkU.z#S4M 8>^#Kz.?5ӃrHoq\hrmY,Ti+vyfs"nc&R fBTxЭIݡpe0gwJ dr_;0erUC0|l՘CuiXPr/j8p;{艱/2pNU>Rs˨p)|;4get)c49Z#A`}JZݺr̞ *jIX&mkAR]zIac(ʰb"VC<4Yo-!sHA*᪔7db6G&QMx."UZ-&h( VM00R ~t+g8χQ^z=ˠJdۮG=YQCCU:>@No5Ÿ#ge 2#}52H}R##6o&F&4 q `tF>Ҿb'uY5*/vBy1Mrz/ 6 l5_BTB 8/5LH,eU70@~p Lj2BvfB$~|0ag^yy&` { ~U>i}T<{+Ȏo,wC=l%wt9Gy 5jxg7'~yZd&ꇜ A#*(f9Ro`_\_F+gSKt|޷Rs{[1tlhzR$QxR$DC)P}v9le[EM PD&KfLtkw(Z}z~iiS gSlLWD$_ F"ƛ83Ŀ)9![j_q &I 56TMaqRk|lT%>&` xC+ oPX|DosF$hT@.PӃnֽpE+8?S)HT/fʱ]+N>#7 lHB@D~X̂UZ/ jP6˴`T^Z*/=ⴠLe-@qe1.̱hlvmlvm:"7pq,p ĵ c"?qi:- tEe,z^EERL mrڥE T096Kzv xJ+7"7F~@$<]S|٥9'{ 5`o%~\{11ƞ0XPĄL"[DA:$t^YaJj=R% $8ĕ{:db&{Z1 8ԟf VhJ} Zp>*3_;UІ.W.jheo#:O՝[kuK`dl =-p: r83_@@"jK٪CtKvO߻dzfpn._v҂> v}AB_Q㑲r,}mè5#pn(cczK\h l(_lqgfy-JnJFxc%! %zݣ۴T𖒅cB[N1deGIOyn3)E>M%BtW&*@fr_gm'ö.,¶5 fJtYhow1$˒|9G)Q.'[2F#qj9rX!>w-ȜX׭dqH?.>0BJ̬IȚ׏Q%J!1ıj]ps_Ԁ0VtihbEf50G/4l]}O g`(;$.6)\- T%)"м6z[kP1bC\ u˃#12Fى)ő[ޡ>ZdjfӅDM$pgh_;?HKkQ[m?'zj}F -w77@bY C` n{cv[ /W9^&N. 5+]Drɷ.#:}ݒe@@&``Y_vxW1A^ )3M ׽6J 22Rˢ_9F ezpIjW(  6B?UζhQmj^.i#mJNT;yָ;>v 6aks?[uIo-B= 7L'& Gl4mkgL19`"q X&b5]2YrgG 3\Q,p{4%7|!Nc7> -1,_kF E ,b7K%#YOB!P'ӂiF5]rqר'„01 Zq!wOV sW9+}}CG<`HB 3Gō"fu;%!() Φu_iZQ 6z`,QT|-M9Kt|RT29ߞ,ϣ4yoA"ڠg]e1g^J h, 4-pAYǫsݹ @oOk䁗*]mi 3]`rGxf7 h*6IUaO5XbZFB|d/=6gȯq7+1{@"tHG,< BV$@.LZ9 HuCۑ2#KnC$OfaR\v‡uKGюRX*lO5zHĩ)A3ZIMtU.EM\WȻia\X)[N*FO+1Ŗ@w[R.G[gEx Ix]'j !!,(ԻL^ub=i{حڋHnFRa5xxl%~ sJφ3^]{;,}+ {v9EWÆBJʱ-ȿFb87Âk) I?r~-OyōeOb8oDc/${ *ȗegՏ.͗I5rþNfLPO_& >"9 {Y\"B&<έe&FVl|Bm}GEH*Oe% ' 1,}ngA4. )@57&@T)~b8!̓1=Y& 4Y >EK">K#m\_ABحAhҟQ!Vηp~tƕ=B#w:3[B`o{)qX{1@u{-U[X[b!3^)2%/}@ٲַQ7k p'dӔڥw&{W(g,W\ -_H3j`5+5\Ӕ0tM˜sa3ij:h8kh!붅ƏBUZMJT6"%9 m`PyJ3]&%]'Ira`F# 7_˥GA8܏LIz1Y|_C &| nH)iYqL>Tb8_bSV&#807ɘ`f]$~m,@?! .1+-^D&K-^CF>?DsAA-oFޱRd@y+IVb+t;M/ﱳtjsA(AE >n8c=%`/J+"kCOlaz/i `Pl8u;G'Nנ$J[/_`k5s@{{p1Q=ؠ)AxrgV%~Гw=^:98śXo1?*^k+zUKf9JO%:߂FhV9_Ȟ23{^㽺*:q0 n,_L%'LxVf$܏klˈ_J D[ݬP$˔:R ,Y6!_(ei[ÔȐ5QGm&p"w9B &B[yIrDs>=-dy &J$Id)i[5? 5t>h@xjoJ4v$NKϪ`zvĥ߲.DW;>O O6kh},w-5DV<>@xw\sD C+-vCٝE?Nο>LĜ8]^8l[;ITz!=hUfUksvQs\SѣM779!k@>yp?91[ 5z ^sLTA 4( lԵƸ4%|ڙdDawLv4&[Me<i]F uvu+78ߊ츯e.d@/&O$R=)4i,}VnE>z3ZUGQ,$Y #BaA =w}' _Bk=?  v#ŗ^g1CUR3 w>r }Ѹ4t2A8,芑VkZbTf 3,˔lbVd{!UR_tG8u#wq ܋3heN@y7Hb]aVA!e+b!h drf{\sZ&M)Ԍ^AD꭬Kfuiz:Nc`症3tX8wp#!Kσx[ 4&x $\SΊZM8`I[SOS&[XW*F &=|ya۴;O&Lx8%yCcjl7{1b=:®OӜ"8'z152S7Cnmll̨W;oțbP:RW @]R{7;2d6:J#}tbGF\o"f˷ e.+V$Ȏ6y6U'Rndߤ̟˳l@U^Kb!|޹=X`J>2N!HBY*203l) ,. I $ lN?4@P5s?kۨ:%.7=rAY靄;3uAJʎzS lRs3-bĤ=9>h Wݩclz N#a-}׆bp㲐q#4(3X H~(AO@;0 cи-KE&߭Q1$GGŒj3b:j& CG^P}{^Hed@\Ìg`V=E] ba ^Jk cAk a9bd%r'1sD*EMC$偼B7ĉE,9$1&!DocY:p"zhMϧ"Th:*%je&&'E N"@L~pNX>{_hY2A?'RV|ؒ\_sʢ8_EN7sߙΤɇ}ON zHj4+ɩSR'1*QoXxQm0ҲJd{qP!\XsU.!s^[έP㠢v,u j8nX9upQ˧&@) y Ѻ>nnOZfʒ0?fýg2S>ONO%{O(-gjRA6}]W"J[l \(Ƒgos|[0eͫ `L\a9GN1>/w碼[ Cx9p Gl1|=nӢXXh-R6yO_{ͳ=IgtaW5,otyUakm$޿~\_:^%_it MHysw0?*7J-$.}%B|w63:"G"VUBEB2n>.qӏ{d )ۢ قPLOI52lņD4rۘٶ1'@8-(u/o2fX23^nUZokC23Yw#e)zl*+f˺t$ "[|1_!W =@( }q H[0*Ng}>#agzmofMn:i-gFnfҍBWp23^47_bD:vDKqv.-b yKN??(m?`ʊIi\'6INR߫zud63b62XĪ{m(cg<耖3gn|CJ>她fv1yyxC͔H6Ri, @z;Ж⏒}{%ԃ&yЖ5;nwLcEIIFMݻɑ%J"P̒ mg(" @=lT}n{on@[-ۀWE a YTep\F3U\k/[}@ԖҒҎRspbVwpTXӸRvt }uۂ4{. y =8j@c` ˩,/OTtTtvӏe`o^&/T<稽_Vg0[;\Ģ..F<*^ t&IG/Cc.'!,1 #Ѱr uC}O` %6cc]/9ۊˈEq%)nt<6c{@l "{37M/GbnNJ?w\p_Ӱw&[J JlZ`2ݒGWț34hOTʌӊThzAYGL;=1O,Ld|ĽD{|H0 wMN0_+OؾO.iC`4uh4~&w+̂2?>%lmih`肛C&C^N#xSz7ISN"hޙ 37=6Qm9|O6dY^{+g%"2EcO&me{_= % *'ΙnN`DW3Wq*c8) Gfk^fwKiE”Ie+Gݶp @keawZ d g %LPt!ǭ > g׈)p~7W@+cT5 J~16 G$n]kN' &G@N֮ )F0K jTR+K+`ñHLTHE{{b7!/ 1WP'X,՘$Ĭʷv\Ƒֱǃn dirL.l*UIgXh7`qo0XK9wr.o9: }AFS\5uVdRa+;edJbxJ0%X{6uYկQ#3_O F-t^{*Go&0xhztCS a >"9|(ލѥTvǻ U&,[mw.6;#< 4_? Z^p+o{Lk}dIz,AǴslr =mNrp )"wN(o!ƍQ-6<0ίbVK{~sXC#dg~jia_!c@[R&Ki?AHxre81[wD1i1s)ukVQOp v2VAOӎk,krr !~DIwٶ2qGhNZhwY6 68@N"\5úK(6H}<*.堗 v0+F _oC,1僳UL+S]Kߛ͕ljŚ`wY3|ۻoY. rgc=Q8}UӜӫN-O><ZmZ{ q 0VՒdz ?U{q&^|M9[;G}vZoi"5$+fM2.i|vg'l7'mz,-CΚ6uy蠍 4xm:$iX{mMxGYvg8b("=)f~\NE_Isf ?+: 8Ga|oRsc) 7M.'8NU_q+ g71d34VE#vaRAzuө[8&@ /krF[+87'yO7zXa]FE1x2,U8|'"-I7{0B?^Y<}Lq-g Kۃ:*,h+ pBJ%Z0piC`aEw^jY0w@~"$i|4a`#p "<6ײSN [x i!JO%l!Ea0^_3E☡'zC2ЇP)ԑq@5~Xy,5h~01CqXT5%cǿZ ۚb sj/w&B})YeX0*g] hLS0O2Y2r_ϙCOBhf| #=jT6hϳk&ZWyn~Ntw@_1A=_{ Iy moߩgqzLo5VD[FPf"c0ryn#h$.sHr_cKɒDR*Ŕ_DYNa -.Kļ$)ȠȂie+g"f :(퀲Q5L"Y7F*z :[c)/.V98! ]ŢG>HI Piy^]=hy݈O2aZ|ّ/ ۴.nS"aw}c?QNF 37f@XOqr#)i3:$ش5T;G S4rzO]%WXBU Ӈ?#xGVuH׳dSG'`yn'!`R7 bH8Μ,iMsmYBzB՗Ŭ7I첦7zU}Q!& -WY!{C,uWfH{iJM>TF Q{6Z䣩mD 3F莯ドr/iQ6#4KjnL\NuГLSø30NQ: ٳ+]<CQj[=p~.*ahOXռ?WQI]nî~+7-iZ@3AUG˞\Ra5ڥoy&#ntrGY-ι%^oɳc3>ltLtPv ٘*¨y!S?yp8#x)xc"q8XR#j» 5=09\Y}q?TV$E )YZ"vo].#)^l%B߲(eԥ 7W`:T `|.Dثq ;kUeeOE UD'A)7U@$pȌG!E%!F"ߐǮܡ7XM}c IAGvB3ˎC}Ŧ\za Z+ØJn8x)9J1̛2pMY} a4 *=A}㷊z(yzRVvx2]FX ݥj&BDD^`Jg"9meh'oĊdZڣ$ D?Cr9{FzӦ8)*rΙ OL;mTpȇéx°+Xba A_ʼ{=ONP[û?)b2낗ZM4ZC|xׇ1Uxt{=TL(E8n.¸sJ{dž q4`_Ѿr|:c|&FY啽{:(0/ҥ8.&E0,+j|c &&kQYO݇ $- BepǤN_ D 6Hwe:,M% 5=c@?r4 /DQp(0'V `UE2ο2[vzSQ]HT -N_l[=MggTNzĈlr%ptykQqX#y[(F7FZfݚga0@Nl;}H|TlO$%Dk`^IL<Ǥ vki'e~俴CĸW&G8Ƈ~ZGx&Y93B,z'lƷA쏡y[v5[W&Vhӌ\ӟD13^ Sh`2W/%ڛ&Hyx 6zPg f)8H*u.q\,o{>X~i%f^QJB+pq:UY.zNۋk(}9n Oc 4T1p) D|ۿ!@tt'U)U"5y8kJ [fK&|G!+VEФJТH)ڌb)>Oy53:%])nފY.c*WF64:獉]>)@l@19N40Sͤ>sz8WQ"^fL)ՁZ|E'kYO{H:3g*TF[Y&~llI|0(ڐTA(n ^3P?["O"1T*R*@EoS n$L lۺگh~ ] >^ U(-87W iAlC3:u`3oc? J8.Rr9TACk8L(*Ge5x&~9Sł,jN&I@ZT$l{p~e\I.^ɭ `s΃ ':弼@8lSmy/dD5b,mܯ(?ۼЧnXG0B5 -{h.Pk_=O=AHcL);r{{YL-zbˍ+hL|+s{Q9GkUko=fGZvT Ņ;k-vk ɑ%^f%)$~3Z^P>ƧʛHWЉE~ -WYh*בP'VZj{ >["jO2TSKiLM]:x@7rk XbY)!!;ݓz+0=egcVWg[%<1|~ _Qs&n"-;j?OBW<^Gѻ.U:$_xtmm7@]0-SeOYeŒ<$o52w''?fngJR55S}dr$-49i&!׊o hGI ZQR>nS<@zjC[N8)RQt08qCL OJBe۱zcO(!alT!^x'j3ócjj00\V>co0bJ9`$@U"{abajNE}434jVeA^W]$evꎑ63(Vc+%,#HB|C#=<Ok*S>_U_k$Dy6a AKZ~**GxКXo?pGJmZ>H@g[GQ 2m^# [$7lEZ.!0wN;m@B8%xM<~t}kzJ<߁B8 {eEO?BJԁ1:)ᤅDjU,̱KFz§4G=Fc =~>hmMvH|=ԛ!1`_o!;PW@%Y p'^W6[ kiqXմ&$~ؑgE(d]$|6 KǸQ鰞6A t3Xi~=jWDൔ'ʴՉQ"Hؘ0b}'%dׁc9/ej&fxM;\=}';DN ()z;SaYʃb<qĊпRhiJUؘ]b/'&ҿ?7210~Oګn0#IMÜ,Q "@i!ѓ uΩv++`oc5rPm-pxE 7ǵ^20! F|IQO`m܏(y%w%ٯ_~杩`P*hY(FU+=eC(DE37:"Xae|=K80qMHtAZ`%`/;7|M#a9D+ 0nf-~I7b#n:J 8x˩bε~ⅉHA6W4.qW Cߕo)6$% ?_6Ic =OT,)=K ;EG{ i{ȥl8BE\>8*s =ׇa|Í"ʤ:LnJϑH? g<xxNK`TZ4~<enқpe^rFfk᫪HW*2dS[$. #g{%‹2`xlo< NTﴰ_\Wa }1W@OK:2ulB7r(8}Dh*R亍$1#|xXh*<֝܆@OnC+BYHyHl {ȠFB'0C[Xv[O^; @D)#hf[GDÍ9#Ll$]uĻ`+%P~FXa.eCZx8E.[nxf@g^гj|P C9:Zx6RM= Jn:>k Dv E< @׳I\@(7%󼞺rsX NqkT?3W{[jsJmd~+h&]n7E/Z+ -tCbs+"?m0̐ KiryXw"~ejSxε3.*isqxmnt'Ѯ a)0ct142|d}#rqШOØlA8- J2[*Vmyw&U'_`TF.װh5_4F2ߑ8[0Gpgi>Aup2)=s>v7~rN QҤ5 (jvskY53.̢Jf}zWjٸw@Vu5О'3ubhgvNjÙo5ulCӆ9( -UYWkmiJr:?'4F4x*Nk-E2d[*$> a'ғOs';ik9*{81UlGN6dJ CF#:U@,O;dr*e+/#Yl+VB,~q{%[IfUbw~ÚdGD{2,f26md[ K% Eq6+*'ܛ^rox.x,2JUFM.bzx64O>H*N[繊v东(FNlgZHE51I'Lۑ&cػEX}fΧR//RdS=;"qܜDDwSR?I:h=vMɊ33{gE>\}%*^yeq`X/ˏ`fDa \$*\'c ŸTtKiizvɀ^g֢ҖL-]nB୎sᱪ䁯6U( _p#8N[I$^rLjz/'Ŵ-?$q$)"9 $@؀ξy$h {ǯB2noj"/jBOs#)su(rKʔ B'Q4P /LbQc8è{v .HNY>dӜ-P'JTs2m! THN yK;Rd5TJ{;ED!bA~n~ $J3Kyڦg1Z#P9@SlW:/h&ҩd-\!53%z D} U=F% &,!kA`TKg?! l8| gXToc1wD=BWI)b3yKa!#L*YavfYGɷwHQvE=$R{0 l !0 -Y(tzvt=WrƔu}!T TcZ!y_oAHu/㾈~@f>&Plf{(:c7,$(:!^֏ʹʩ΋5cLlT 2]T@ DʑJ^+BK '.,L.BlIqz i~i1NlQ;62c~W_^y4ߦb.>l.('ɉ d\pXK_}EB鐂ϗ(gc0\m75r#h^zJ?L/T Z 3CETc!hEQ[X' Gp >&=KECy:\9X0i ͼ=WnI\l NvCO8E?1D]bU=XGnHrΫGݞU8Nxkt =Za;x oS|]ik$hr՞/x\ 7hШ7k8zn%v\r?1|UbGE&=\#1# c(>Y:õu+a,1 ;ȞލӖKOi(NK5FY044wl^tN#Ӆ`%WTj-?!JM#`B2G D.=0=c(v4k,a;3tLݒp颚a#hx/:k(Vs* 8u{GLDE .#]?~$^͏ښU=V)]Z&MN){!zX\L7S&juiAl ぱQ UC.kzQ;NMU.7ᬼk{W#3y$-Wm7ZLuўjF~* ^ը\2}V;0 /&?UY+̚rdK[އ<;9W#>:۰ڞ_ C5? {ޝz"AX$QX!$7&Mxohnnq7Y$41cE6=`JĹ)^CP;ʗl9p'C#즆ĩ%QJ$Ɯ8'!S\ 3F9| Sń{#z."ӒS㽗ߒ\~:x۵D>}rR8>V!WBgvj 1!iÓ7*m @őXծpX 0B-MMvWTEkbR]zU3c7ՌU v\m7 PM;H '1m&ho4+xgy7ȗgM[Xq]dl1?73Q.[W&GM\˚'}FOCUIѳR ~@|D,TN4RKqoB'v ,b`-# \i|=$[F]:Ȏ[ֲ8jU$8SVZqbcڝ Ş`o$֊@k~#⋱9/JK&MD_[> ei%U[/g69 T0E:*DK&1 "c EX# + E}BrJ} Y+ٱ"hpl2fZog(*8q{ ^NdG dv!(|>CԧngpDvHRIJmetoVȓB4}_|sPjPljVn_M`m͖,&<"aAZV8'2njK}wpBV*Ftlp@Mۦ VjSU>sБԤSp>'NT#X=E3o=‰n^I̚0kkYpPw12<<X(5ISN!~SUWB&UՒxK]fe{_b`0Ӿ*-d 5E0LZHY0 )U_t-irEzGͮ;X^A>DOJ`IЀ"v& ʯLnr,CM˼d$@ Jd}F)Su߲q02(ۇGXH!oG=ĭn@B?~a2Xol?D hf<&cC#V/fڢos"Z,S]_ #3D|n:MẉBšm97Hy( HC"!F+ކP#TPTe( HD>CdEueOF o{rX*iY޺?mjݯ[1vG|c>pu,V歏1JGbOb>nݦ6RɠoqdԮhc6ۄ9:oբd"ɪeSXM7*] _xk$|L[S ͎4 8.e;-vuA;ldlUԹv`MJulA(GJg.J.6d$"V#7"jZ4)!>hk2YE*d*SG)8t{:S/5c> Xtxd-eW5Nl7f[]xՆ[#wKwG !}m&TixMiȽbW6sK7S0UGFzIbt\2b lەOqZW>z\@Hsc\,9tsF_@So@_-{w궉ZwfAst "&JNjˎ4--O{`kvtZOHQC6$LĒw;D&q,gh{&EP RNMi4lq7:8;!h 2l)S$7֞`Ȕ~m1?pS'ג<5?m;8RwW_y.'5.F^'Ӗ ▾3 AbpA$'^ @Lίdot9Ӏ?ZgXgU{R@\sJ(4})Z Pa$II^tNNԱDl@M?cфri6W0-/MDJu;it…z[0_tռN-\.[j羚'?Z=FWG/'ѕ68܉P<zeo1>ϖ7lyMI; . ӄl7c/ƠEh/m I3k@Ҿ|DxuAfSwnw2k8M.ߟZ׋ Z0d:i[2$+E/@:-dl)bЋIw1,h\!ު =[/tB]vLY5t<}5hh}QmOl?0KtN--$Xf"fmsQ ,,.11LL}V1R̓~k/77 V qM7Cd`ü.lT/a;At}* "^JٳKy0Iق&ڴ'j8 o,DG=i X8FBq_^8(>#5XG7YfTw{r/cGzގ~+~$,+^Eha61X 9X^I#|WM|ֲYEN+5a' SzTDs ZqQ׊MfZl L5WsJWz[9{%t3^W\\2vˏy@BP_]MIޝ61T))XPrG> Zc[f#zPQKW Bn}Ѽ[=[$1- SM]h4bZٟ>6n`_fezQ MXlm#|(A'\+%dP=׻C`zs= JZYϪSmf}#AB Jh9u?`n bsyteIu:m~>e,/؃V'1 ,CjL@sl cFBPz`Hp3Wu^,$*A!*jwo%Pe_AX ubO ((Y$sI\0$}lQ#Yuq/h8ohQO +TM% AT !,r,VOSAiX, 6\˟@v3q8]8i7,|`C%B.eLTȏh:*8A` (' 6|C^1;|dg"cЛ J*D*/U SG&1! MA7}? 6R?6Oտ_rr!'jr)'"2+o]FHc /Ȯލ|tnҐjey}sPF%CVtϜm#Qc+{PH! y#|FlMr.:jBh꜃:C] MkVrWu* iI^({XO1[pOMGm ,C-1{'g?55(3ĹX,g'ST&p%|VY7@o Vyn&};Dz([@G> xvh-^6hTxDV9ci V^#cjX:M0EA(|+;X$ݝ,3s.70ǹOB<<:$l|V3TjƷdrR\!B? _%A  2 Gwʟ|Ah}ӫuE Gơhqr 4C59Ք8AǤ|z0Y'H@sEI0=uӻ匝U.9:1-ӻ:I$?xoyMGAE$oA1@cDg Mnt73 #އ8x+g}䇯S)=9簭/=r> WJ2/<9Ez*f>Ҿ(AlAe0 :|R"K*gGC5KYlioɍdj.CB=FL^XMk8<3+Q d;[] w+E b\WIzO7v/3O~jw"aCr#3vdW`5iV4rnz߹rWN;l9~sD)6&NVܽpǫeoOERݒ?0q >j`49ʀ Xvz.Z N_~c:2E L]ZRb %"NbUbgՑ8\KP6#j GkLB=pB [l&(燤xZ>u=KW X w&u WhmCR6?C_Ũ:N+w\;WQN4=X_%}ЇZyﱌ?<x)v?2yXKM3/6%n:nÆ(^/rH^1Ź7 l$᱿~ )hТd5^d0MĤ.`Xx2=,9=6j`Uh0}\hPL*,ݠ VThI /6T[ q^Uvϐ X_XK='L} Wh/xbJw ή x`O_ػ-& )g/.ŷ,jGWҐ5++7<|=['\p{ZTjΰ@ ~ ZodV 7|cGaxEj“46tk0JzGx15Aq7libf!2Byʲkocщye t==9X+ PZkfH BiJ(u2(WomF?ұ#8cwTcUM'#r6v4ǜ7z#Phd?Si6&lG J֩%%J,kM*@^ Վ˪鸜+:O N#\ו%X4sl ZڅsHs( _Ofb챏ASyp;O !_Hc=HX[b<9p`7M/ A)@ v7-V/7 u ;%30Z΢3OΞ!ѸZE~#01u0-{=|ߟԤ~>&;2?[vfaěA%H:/X\ B \d붚 â1|r$Rdc6=ݧh炚]k>*R~ x3kL[8`O'VTk]*oj@\z@H e;sֹ]/¹;luɩ+5=&ut>_P?eHѩq ЯQG{GLVK1)MT+ =ha+dH`.waK?0U"?Uxy{$ nMŮyC__>U(O,yba<FXEP"}?G'>7Mg0X.Z׋]jdž {ߵn/ {`ך#ī&zƻL~ps-P<眝U訕{?:^zDkMMph~|'Uw#G  pP碉Tr̟?!_z2^Ŷ=oYfn[w`(cͪ̐p* s+C0M5eD^Z/!kޜ5U>8:a|Õ=Ӎ^]23csn9]U^ꗥ af+:SlvjיbZx=C--W0;-3έ1 &hqoM4jLXlxק۷;?V f}!VQfp7ƣ}(wZyκ&4+X#X4S8][-@ %)I|zJ [/LG_]۰ݼ7KinB0ɽyPU-^U>+2.ߩ󓓼H_mFڭoH<  =< ,`Pw5EfR|R 0GOXړ-%FNUD^r;T=7ϽLX*WzQ$vYUXc]^~ k)&+4,ikICiN'(EY+rL5!ȟTySSb"6W vK+񌪽= qZ[ (Ӷ y"6el)W#B2LKw"̈~-@'lpn-C&>dAXW~NB~! Вk 鲻d?RH\I%W$,[1RYr  k-szCHS+/UYT&c&ݧ@}}myK9ǯi79F6>)X t_J0 "*z8vN'%W`:F*6$e CY C*{n&,LsI)f]I#C@rkyeVgAO%;:i{a%'[I aFL8% :?1iݬ `G;ɮ>?}a{vIc7O+nbۉҰE3`b'1vlsR.~CaP;U[=+ aM' Փ\$A_0ZUK\ppK*Dqp&'s]r}? %EyYE,ǪL/۝xAܗ ZK~ocޏ+87 5&X:+=bry'vd1Lq(=s;F ި٥78Ԍߴ2]Ej;YDyy\]3]7C}4V{O@Ns {*>"pH)0cOeOeޘMu"sv5Aca1i@-o|剌a.NA#2? --=Y<a!< :ɫ9W_ cCx;~JZ-CX3>]sA˒nKR||c5Y+"pqNR(Mܿ*N+Z5#8{.pU\1,O-^ynl~, N,&FJd]НƎd } ǔ(LBczdxM5;d"&ƞ^{f+U)X󞅾0(alT:5#vN:oYBqh4XC oHQ|l'9ppJބX+׵ %vsZ@@`֏ֺ[Bb_1GiGP+[Tq7KMk%B-WL΍)ۉɞf§T, (z# :>Aw;6TƦdS&/_(a%8Rײ"עF :=î20ȧERZPXH[hf7,w1Bg92yz*gxB^mZ0 P}u:J:jBI)1E-L!q?V73ƿh&mAJnr⮰v7H5MQ6Ů#%1 ~  BB&T>ftIJ7 5+g?VUEHxj2y-We>'W"2_*K_sJ)lI!H}Q*7/Y M}9 _/ۚܥQ +&uYXn=n= R>h$Ol2$* N0f875`PeYZ BaQ ˝ŗ 03SrpcWֶ咬 q{0D;?+=?f%-7L9>P߻Փ:MlX|zƖ75p2z$K ? @O$D `S5J[qn9G/KWÅD $2Y]$ź&(s`e{gotw>ͩЛGH\g^i̿3ړ[mS]1ˠ )N9nI>JY*{ [sD:Ԥ.ΙsQD<ZU8&ZP'U=cX=fʇ D>4l}8w ctv.bMgL| 1vჴ,ZhgBJÚ9DJwr aCv#*őq_{W[al ] buD_!ʁoD8YWTZw% r8yxn"7 +L"ChwR<:9VD,3y H1Fߘ]w,DZ-SSP^D04myl8#uO?/5r]jhІácdmw{?DtݒQ)x=ȖJ  +K|/MV ` Qwxݗ0@U lE\W‹Q%n40oݷp  xtftLɴYfaa-AuSBIRl8N^i-/rE&`3l -:3:_ɔE4syЁn:I(];}yM a9.T^m#KuM+Ik_3?c|TTc #"Q AMl2NHYc jqtۺm(cedځIݎ\ֳ̤&}hf6{?&dfz\I%uN=^VpY$9XC/+-df ¦X zT ʯYֽbw"Į\>:%6/[-t^l)8w8zuA1p-iͮ}"M%\P_*afM0=&Y7GЯwB1_NHZwI|1ILauaև"\%>&=Qd+.vlj͙l+MܵpUPF]fcp.wDӞǤԛq~xȒ6wu@r49eFGKW| B2 t"ӋPp^ ӺOwdPaSՆmcNM8 LmRb|EcK$!YyҴy}RNZJb$HLM-j> <) bmf2?b+[G+{#8DfiB6Ķ<9mZ9!ki|IL 5KCM"xl_>PjӺ>uHw: D#ſYW;a8BjN4:_]-`rn=fx*Goƀjjᓌh4"Dq,p'A$zfVSt7 p?Pس=Z{k6|KPv|]W-DD ‚ig_0m ^H,M }jy.DSFJڝCAb+Cɢ>4/ - fHfeE-nN} .yx`{n .ܱN@v u>W[3&; %D=]̢FE?('|IČ[m~ORRaC|vA۔}pa–DPěT|;Ym0?h4荲m1ƐH57 Gw|g2.r:i3B9;-E"HE r(,F\mO)BT"-VsR.4z ``Nո/soN{ʇ)a*J$(0a*wbG Q}C,ηAڠTp59l!/o^|#.Sq%dZ6_iQ:l>=U!Of45H<dN,Y`Hx pb⦢>#oG:9,X<c <f0͵.lZAVFxGjB2i.v )qmʃuHR<[`$*3^O-oL?I*9B,biARH%(6+Ygs,^B3E©Roo| d_]MZ؅ kʫQa GV9@V BsޤV,6IWBEa]@뛞xz@sڇ)ϬPQF13G7*p|x9M/vTIYBS䓆ǿ 7L $4EXw$av!12AI(N)`6g}۲!Zn|dkIi ڨ~Yd-"V]ݮV-f!*0tCwjFi03]juZӓk{tDԠ-G8˜AzMNݓm2϶3v%v/$K7 bh|}S*j!<(D? #~uǓKTЄ%ȡVQ|6PBd#E+^-.)%A] x4O/aq(O¶-xsF>4kOe2v+ʯ\+KuBϛm'Wۓ.89{N6}T6瀳=+oSז1gȺTa.u=\!omރd㲦VS?2z;mR}(J'|j]^ǟw\pQy ?ѿrz6,XWz(;VRxb1'낥zf >bh &fJ/zmM,(F(uHy~vH<~-J6z-ALxF+O5_&Z[< תZeO$!gԴ?eL]{k T!WV2?N,0(ŸtYgaGCݚء=Qd)^ ƴLkp v{0*erZ*Yփm]ʼn]8_y'H'pX6?XܾE&f;gV.*1Ow^rcNjؓ:bkA >PCnČGy>l\{(9+X!'ιDN ;A{$3Yފ \2f&|~5&S)!X8eHkql nZF v \H᰽8`HfQg}k9h l/֋o\#Zmp"#sy ᙋǣB{E@䳸t  .bYQ'nA5Eo022k:{ݎmA> )33T)/o+g+c~M" ꪂO;FҜp|!@G { Ր@BP<芋'=tѬꀶFb7}jgLiyh_~3SűP8Ka1 q u2/XaDRbf!։isCT24^rvF7$?!%"L^ÍhMȅ-.C+!mAQXJJR]TDgx:ڈ󜎻A\3viGzK(v:sz{;ό+ Y0V LA;H %Hjn遆[<񻺀Moɭzlgefv9Mtg`wOROR(Q4anshdE@Ս6ߨv[-y3P,ءVYg40B/nO_mbB͇Iר O0 ↛jp趰Gډ{3KHQ#p!v'ũ!F5rk˥{dc]|m.-d jŷ3c\%2o{`e*|)ǩhw5p#^bU7PxYA" I򕗰_ꅎX*X٫yg[V0춽 ,`FZR:2G8y9AH#˺^g$U_UmuRrjrhv/ݼ@~W&R2 vA^^ٻA͔E/=tg0kMm /JNr&PޢjO,C_)5B'}P-+OǶkK>^{9Y.|<pc#SY4Lew='U7)Ao{EE_j q^Cl_ɃLːHR|6gϦ6eS/5T;jokЊ#žh0Q'LXowcq 8F#@f4;PC~[5"{Emqz S :#B3p:D{%`A_S)0k "A}"tZ0Bd* B #tz] ˄s*/;&S$JZ:׸} ќMs hk:K%f^]*@|l4vm =FW!7+wmex)cawر$Ftz{'Et꽙 8.G2C=Kຓz1䢺31YA`wܖJDKEgڒɈlHq&}%' tA%N#D3UmF;;z*Ӊ1`>WrBmşRe( |X74;hVbh&Ar6~;`# w\}yĺ;K5–Up{- 2+ͺaF`_;dJ '4y'Ci OZJ"܄W),0yۂݹZ6)ep,Ž5AOV?VR!}+ww! `}֗*%lp+ϑIoIJ^r謶PH1pt1?NM D/7F>SMD{d盪>v)р{K6_UF͢Y #@f4;P֡.EoG5wJ; +CsW?[ұ vuڑNb\WhJ TL oaA̎d9Z&#f ԕO$-SYJtuF_kV`UgO u,ܜ7{ /Fa[;j:P-Zh1>IwnQ>o 10vun1'y. HЪXvXQ<yQ e-@Ti7o. |G%5rX 1!XOl9Ε g~ b *J^t8ph@S9k&\; ? WqoBoy"a8n߂8>npB{8_=Tݻ*ʢZ;C[p:C8%6Ӳg}iSŞjfk946^Z 2="q8v{N1Of.),^S2Ι5Ney"S뚙j6}K5,?0Gv6& FuUU[Qhv/ݬc ziͿ!8S"  ! 5%}h3g51$}`+Sd/4ASrl XBaczRO ˊԩsV^~GגLɣZ Z“b\iizdK,sa ʫ{jM+8X3YQJ C8'mDii73yb -; oj,Ќ!9è)o[=cLC^a#lqz yߒVQ~S p`U7r:,h&6d2wmƀuUU[Q_F1Ǎ;:Sk`$ԬDf1/|`1G](G-0~8(CG.?\u£*7Z{`@nBlUo}Uk_kqRvf9Yzĺ|*~͠ TgG?-dmĬU $TuHO)N^Vy]L3uw{AFW!7V~2圧0 *p쑐Мs;p#=lc!5%$[yNdO.9k~i_ DWs٫qU\aSr4<*uXm}Ҟi:4Ŧ/96Tyciz%!3jHB!jY9g;yM jFfjYG0U+gc`6FԍǕtUEs-+ah U[QP1i]ܧ̡c:\ áQ~zzOyŖ/g$ ݰ/D#;ZMt;?3 ) ϓȤ_G;ɕ4q{w|7ßVބ) ÁGn%UfY Tgpo~Ii}z׫Jn&[ BR֢3C0grPjY4ߏ3_޵#TB5""D-u8f- Gj*5`P@1 1aU%jսרpx~-SokQXZ;jrm3Syˆē-/0odҳ:r!ѽ|j9W<.K6rs,I "g]kD+4)Tm.AKh]!ztϮAe1- fLJN$3-pW{ʱԌ 9]jKIaYoFEz4-Rjw@­i\\zu0UY4?K6{лHACI1A*U1`IiP f^vRYMFw΢:r:}gMC*]ąHEnnooVb>6_[9-?&F*moQ}rܶs@ri4N\!U4]j'y2^V0_ <4U4:s?fOɇbs!7v$ʚ6^W; >m vG$ Oӽ!5 TQw})w|ʪɡYv`v@ݯeY=w S;51@:xZOo lx ]dЭn2-'z( $ѐܝ cӜ}T&m#?2A̡1>DZs3@d\!# %)"Ou$ byvAK]NGwl6OlT:b e ~ӉÁ@\n ׈~wFUW"Q}p/|G|r>M an-̀bj xo)4JkX^s-`gO@]!0<6"́I0_ : zA #TʖV͞#4-W`4ρ2?1]ɱd 5R"3\؁zZac5=X l~`h/A!`T2i~q̢v ޑ Ki@ *9ʀ0uHczJ!VSodwCg&D*E&_A LuzH/o .k *VV#HH]^C fJKEKR=h"C1pxdwqE"*Up2¹9o^s{->#D3UmF;;VcUmF;;%701Xo͎JkEr9HϕHQ%Ǖ\S)9IdaxI49첓p}!KB' qmUq; ߈vC%cG"ȾZyhTP=Z<3s;c h#Lܙ] e ܝ9Pѱ N0í}U<1 <\NmvoƖ#.4*ve$cD.vFZAK [0dO0Ud[:'{R.6C)^xK JEzVѠ= 8]q ͤLGۜv Uq75TD!lAEW`%<8F#@f:ڌvw㪭g~",u9螕567}1 `[)[)Xi'nxT}eDELCd80’.ӏEc!ʛp\4^S3Lȏ(k%[]^b⢖ vͨ <8V@TI>ǻð 1 etyraKt ̉d[{:/Ӈ w$S|1f΢23X]^Swzz'.Iesڲ;:5B.TD|sS/挊 Vk`hgEs2t_gH_S$qƩVR˶FE;'G*s#%8$h ۜ91 ީI;VWDWurj(YJgK. `=*p0>7I mXA8|kSN.`[Ǭj&I2$Oeld!@ Mϒ? g⶯]k~de[[ yĝD`[Ue,=h= J'Yj] xg X($?Vy;geJTe) O䠥>bd(k7=#?t>R)1=oӂ3a78ʨUmF;;V{t@,]!?睺gBpiXÄh4cC~>{(iKA>OqgEdu}["؁$6+P{ :+ &zF'UI3i<.Y+з@ Z}X!{ءCĶ/P""q`hz+&9mW)U><_J"?7:>Qg?ƧtgpoqV#P CDFB?߽?rmcj 'l?/(hEIn|YH'0T1ѥ+m!^,ֲn"3D?ѩ»@>`, (F; %@mB%<0_ 8F#@e"[QlNdL6 gMdAz-T{8\p(X9inr}(Lʧ\Lw# ),yL]q;i`t+B+5gWprii"}hԖ8s~㜴Q񂗤ElM; ,xˮ PVX6NU);T(A!!CXj~g\. pf2:B}qڶgx8;p+1U@$N'pTduж Gn }G'FNoEL0C+! F'_0b-p'c2q2ΐ$pT ҵ{n wf>G?| )>-U}eZWioǟpK%6uBZ}I D^sKad] MyJ+` a.?gVw)  >YAY'ycaoT=`Zk|f?oltz,uUӁB;V G&u|ʪɡYv`v@ݯeY=h$ G zC:7LbÝl \O8JV XJqrAܚ"^JfK]N+@nN;r֫9OOyJ";l _Tݺ-^БG6ܳ0ͳ&!xqUbyvAJJ~|t%@zgs>Jz 1$oIĚH`7|60*\̙eV WjNʠH5M Wnqw0Ɔ1^d;8rtz9ʭ(]!ɱ1TS&ƜAs{VbfA_! .0L 'Qe}M3 $(y mLxC߄|aEP@)‹Ģ)"ȝ6Զ"{+,ps0vq]Y#O*$ s֗N,E?.g^aTT/kt?73y1p&tF(9Pk*tB$0SolK-D“Q3kvKM4of8pwϦ-ill!\}֛02qgbH`J"a$)/|%^.P53|x02Jg@P͚`醐pTX6et}C`lZM&OOyQ[O~h?w 3<@]>_ؠ;]cΊXߜM%΂ az 9!QwX-} Hɺ+SggZCN`>;f5 g&zzk #p qt=xO8_j&FJHJ~BQUCAח/5/+#D0_{ ka&Bq"x>??Y~n="ꨞO݅Ex(JaҴСG&D)-cR̸aUxÑ;*e\$lyAѡ,wXfgN9&;R8㻳O݊ޜfOg4 &vaY(߈n@qړJ#e62qWHǔ9:MJs64:)AHk` 0"Ckԫ [p|1AA"%A$ѷa'|9mK  IέcW4kuTďG \G<<8ʃIΑ©چ#da@H;-Ǻl{[-0!P W pna%R=w%g9Nv/lEfa^Rd# <Yr(]$;4lH*?xfӃF…-<H=4\ƶpeďrKA'-H (oejzEߝWA8@aj|ôX] n_)Y`FƚH>3l, ou*\ral! B.o+WVY,Ǻ+/P,pa"(lAqz$8A80yF{tv'H8Fo`bNp^"P mV"fmGekܟN/WKdKIPVq䌠v(D&+Odؒ,I>i;Ï4@EP-1y%Xΐ>H?Z{./4\YͦSN?ӊz6zPygdSUVtX-3Y5-CLae4<cAQ7EOC08:ZQ2/ݝm߯LM8)$2 2+mVYCe6zzNA4>r} yZZ&<ӰY1>}(l' ->[?1y_1s G|u<)mMD`M6Cvz.sM߃dI%;6vN+5&51"YY ;C\5k8p}1\=AyCC$>W=S,DyX@B梨 $躾_:3Ms~xaM``u ,tȐ!dqHjn _ iBoK'&SLZLN`v$w[œ"z:b%r/O`\ᔼpT耖E8. G"P3uAYcaa+8uV'h,8abn %{ǰ KG)|I9W RЭx(_x!Ҷ/3,U(v[F2.5WtӶ"5b /zE3jtB`Gf禫گ& \Xb7.aQi 51Қ>ь!vq+,S@- 7- Q:fJYn%rTCefv,' Gl,TG)21^)u|!5|y8 >IAH NmSмs?Ɓrj~}k II_IQR歼%DÔĉX7U (+0L^V~?y~V:H6twL@l\Jʾoʖ3FEPa0MfMCc*3^I}"M&1zRԙMs93bTm|xf=:seukGh_a-aNp6(R"EeRiG; 3.Qp;ϳL~XuN ގ[M(DWcgU]Cp<4V)Y6D:V9>m.6}q ZvU yj 1Q31\~'19e򇪡Za MQWiH+% :[\\`s|A)i@~nX"Wp۝t!\joT7o=, XGeI[QgnsDx #Sz'CY?nODPT/fPϕO0wP1y*TwM xk: [ڵ1/m#;Eb #6X5ڻ6;]v ,l~W0P5wv:,'3ܱRfnBDE!o;jW42Oq9NI3$ؚNFZC#J ·/-*44ޱ+tv|<.XZiQU<񅔦"1 hPL Y d fN)?4W/KIЇ n1 &{H%v~jq}we;[GD ^lwXe"XXXv4&TuXlEQ|sO&юɰ2E(m(~wlhu1+0R`.Iuv, [v%qL&[r\r3Bު ~%#" gǎu/! Y~Ѧ-Zm|~֛*F'_yCm~Z@};t?Еf8 T d;2!e qܽDM$ϋ˾ӯ^zj dto6 Ŏ>z-eD'v=@G`r ,חϘ]B"fHr*2e2/cxj܉,vv.rQX هD=aGcYzi0{!˹~&XD\;|f{J7z`8=΍w) QkJS[n^njd'w}5wx P_|l&'e$^n pἁR@mřZ.t{ kVmL/Jy~2b@ d..AoQPGYO輸JwϮ”]HM/dN?h]%*o$|٠(E5#0B$F,}X 0Cj@FT'.IhZ/WA#y,2>N<ɚKK1[U/oڑ79]|ƆD6qܿ-]}=3&h=ZBVơS zXµpʸQjc`nj٦eZt:X-AG*BA5HڬM'1L|Myv@yC~=kR:.NVIfhBIfxL $֏^H.|9#_Dgnڬ9Yg/B7%NwAcbGJGªvg,P2TSJT7KO`RLm e^,_`cd>)=t%1zg MO1 0qVt , E׀*.;_zfGxqoU` 6BCW×˴0o),tZ9oǕҡXcEE.BHP@8|#W 噪`Bum)MAd_u"K+7dm#ap&;<,078Nja7?yN)Fi&, {a h_G[{'SoaiEaPp˫I\m8=c#J=n\zm͈j h/-/V*HM3 LdؚirSO:ך Id'2MH؜m;QZ7‰Qlbȩ>3k5nt-Ph|AZ?v tR ߩ&6}ly" iQ Td]Jி jp! ޯ9TiUՌ?8#vc/sR%5cu)u_3Eoy@v"fnޭdSs]XZ^lݼ҇4M^dfGky@` ѫ)U'OIYm` n鬭ⓠ:vTX(xV^y%7o]*Ù F#vP+xx #CR2̗sŊL$uΙ[cfb{*)U ʻ#+ 9o"('n|h2R6yS\xl*v | 2JLtGz7Nӎ AJ͊->t֟B 9/$eҤN243opDe>b` xs&:@&H!xu juKE7Y % cF,jڨFC6\)9\9@sb$}78:.矷0y;eʅfw)jq8UW3.Tx oԌ?@ c@ByI\)ͤ[ m%!5%g=moޓ*v^dtt v 8Jw5.̪ἒ )Ckw$TV'+t$@.*,] kP!3@KQ9´}MEb=zںtnw1n0xPo3ey ڠ2#0__{ÑRW+&wxQ.ֈgP41ӽ)ENg?H\ \ -IlF:cŸ3KPaZz%}U(~Zx UJTwWp8'{Hw>Cl7'䤙6-UO"f:WuZlurpє(\=SEJ-XL"QPn?"HM^bZ[An , RiН$i5Wec&oVr%ZԛDD^̚g_2l`,&+!끡Qi+`Qt5;ϢVo?ovK͵3K?@PulȊ%"1frE>lsKrܓeT0֒2395ǣ)3z,Ӡ+WJ?#pB.H3+om9P?6b$AI"kbF[sTj>3Q6WT|}OˮrzB:G vi], lz wlsL(i[>pE:H}^t|mj(6ghkN,u;)&e4al >Fae(N\նn yG5oQv-^녘41< 1 #5%*S/@1W?Ąt3,axƭuZ[Ƿu1. ;$FmXxׇ~^RC-iNZbMm1Z?2%S!yđxкXʓ]Z ؖ_\JYu'`HLYjdHj[OI×`X,c~_pWǥ"&wU@.i*3TtIZod dS:t=L b47(-T}b9H(K{!w/ASU%T?农EK4O5,V%WH~BA0*Țm[xW'zr5}}ۓлq cMQajRJ9Z{bCAaA*ޣ"Vb,Wb8@fɇ1ьѺNɣ:Ӊ]6}4FL~lF0^:':3bÛٲ)T*.I8]-SY+M C6j ǚrZo9D7VW_WƄ%d"Q7pͰ.3siLOMȾ/5qbW?_!)=c O\v앒a+'yA54'@Z) aQ JQ[k̕g LGJ@i4c)u.?'hx;]xG!/+3*) ZƞkI蟉ZnF D#`=1pxV26bP U J-pN@@Ekߏ^< u)l+(xIya=My*kKJ+7vo*ID>\1"d>tl&e룇x?<)7 ~DX1}ZË1:9>If$%3dq%FSyo rm7}[qc)'kg+up&רikkX)W`暍 kZL!;} k *ݽ66~~mYU!p r2EBk/8/>XO0?|Cϸmh Uj鵠} UQw _iD䪐vVC[N;q"q!OtkgQۇSLWG*~->w<S9?EtA`j.K*ď60, ia-CP﵂<3#&>A?Zrw_wb<+H7)O7Sm{AC0W*Hl?IBEƣ>rHޓu$wqTj>2(Fbhg_^eUW-~&!4񪗣) YS4Y>?Ǟz9qT~(-=y.hc^bA+Raj-B2Txu+4lL]k"֜?EXH;b'V%ڏtP t"Vʚ=QSWyw.^T_a/ B-X,D0R'{YwMUPY1yh.a8GŻ~c$dz8OM׹NRv3d|Мp{d.,ݙY+8:hf}RqZ+ 44HHjU#1}/Y7v/9#f17`&A&M|X*t"K ?f:M%Q9pxC2L{&\ZMBJ]ucvmM,XaYrK/v*(U-^W{8D!#ˆeWSfH6D2)ww 8ߕ:Ww$+uk{Cܷn?3rT|BT mnXn%p_sXMbOm{eE$ӓ2.e uI=.c_), P(ka{ s5aIH*/bl4Rs1׌kP?X<H@mʡэځ:w@֤`yOIEP `Jʍrxu6b ӟU2H.k}O z[8g[.6)%Xc4γ"UԢ VNO @h5PY]C糷r85=>S{N/QKoQtP)'f>H NR/ZXG]śv2^"֟yd˒ ?c_v:^LN(- zO*>b8ҘB46,syP@4{LT)Kd~1 >V euD{?dY 8UZK9eILEw\/VZD95*ًV ץ^d5 y0Xi mp7,#IBpi_'l*HH؊ȶv*41ZtE#x)_:$ɈQ4V&Y.ȷdZ xDg c,=ԟQD`]5մ%kk l~1; Rr")vF:M$C#Za+3vKZ"QS@:Ic5a^iaĊJpT+%{. S&&_;?H{͒gJYujc{Ll}Ί¥=ہw[ ) nT$Z02ˆzO>>rc4o,R:dQ τD9ľ3OgƜ9~;$w7\u\NiFvn+e|Bz;@(Jr;>QF٦^0 3[; >9t+~#'L})f:nkَw.wg4 uhj~viu10 W;@z8LTz$la-Ϧ)h{1OH%S64K*tz=qfaLn|4j{S(e>d&dMq#m;nI_ۄHydW `&mwM';;,DW`B5sd-wʐ" }M#~C:B}H9m!Do+=$\$UUΡb5vMv&3/No3DTJmY|XKL@_%zƟɮͽսxA N ZnL 0=%c?S \]@b^ q=a>=s(w$cB NXǏ+GK/]_nP^Fᎀ=ƫƾ IsTs&i)nm9GKrLsY=K ZNcAmCTHRW Wl{ZmwAυEVGg=(l!g^B'Q?2~,DmzwI=݇.mh1CB$#4pL3ʚ24 c^@ZO: z[IHLf.^Ԕc Pdgo{x~$t7TB~Km  zTyHL&䎓IH(.SУ47On` G{{Ofj09b|?WcͤHe84{b 'XMH},h2)72C ѯ>~mH:w‡!Wn t!W/5BX;J!??Ԏ|W82M4\Za$ SKeyZm"ssaLkڝOކzc.3rR]b;}J/ E9T]UdS}Zxs>J_1(Ŕ0~`\t:?VsqK"LN(vVhO &⾖^iRAGzҝp/݃p 71"PГƹ)'j0 ,F8 RjO88]`{H1:}l"~P #D3VI+a]!iXUYXU+u yA榚IL_fD;BzA`$o>ʻ:Ah aJI)9JYU2l3\5Hi,Nxug^ Bt,9:/(}U1-ou.+&aAÍ{}G 2[nͮCȬbU)e{uAA} ۹ 9=EV\ Zâ݇7UjBJ?I*xij :ݴǚ/cXb#]U%/IDv![td9'C5G .o08(J~Ps>Jeq@ mBO7ՐW5vH43!/|֠8I5 Gnk`䉝N;\"hX/X~N*MpF(Dfuյ)zveMR6DVA~jG w!Ck:3a72 w%0iϼp^X 9miS)ߕk {aW-IB1U1okx dH5@SlJP6ⰡFid2wmƀhv/ã>Wb(߃[<~JpiazP7&<Æq#d "[{oPw.at* l*4Tr5n--y`Rtkxs>Jy+ahúm7khz2`T{jO[r飑u׉Y3ƪI@Ƚi*]hkÊ] QBqE~YȫPS.ׯ;*M&/=Rs1/NhS]Ji;s}=XYceVTQ5}gOlS5:0?K&$[=nKV}l;9ׅ }_إDH❱ڌECm͞Rb$&;? ן*N^ atv늜ydtӐ>MM4lsVy;> dV֠V(=.o83"Or+ BT&9fksa:m8%RVCO[pafdxq)ꇹ#?2'٦k$\E~Ά\M?ۜc3c9<+W xP'r1]a?p,:5ZiU 0#Qe^mdN+FykGdHRkRh0Cmqt쬱%d)BiYmY58Q`r9;́g<՗\p-Sa|lmb x?h)LڕN2pFK !."rަn+3JX*X\=YqkJi5&M)5Ų&16Fa:}&NwSXn6ca{̗Am"ur߁g!"@F郦 "rt@|Aǔ )ϔXk<,D'mõPl?e"Z ]Dl܎&efFm;di[&s%|Ygޖq{MQӨ8_CaN?l,{}k@~8[_a=|1̅f?>MBVEJ9eAtG $3%Z #Īdcj3t+UJ {v{hyTbfrM!-@S^<;A$ǘޚ}-J\Cvہэ߀=Ց%)rަp(Xw>60f-;NF;śu:RgL5,8sOYh +f*\x]HKj"Z'Y(t՚°!嵁7`'1;/1>ɼ-x}uU^4{?%S /L][leFz'l,[ĕ̼ڋ̂|Mhs(%&%Ԩx)14=1x\M>IgdUyʚ#L10RFbt׏4r;.zOe2oQ5v׃(-`'egwX^WM}!BnkemDYȠr YԿ٤1PrL,; 5EWyүɽ?$[.:g=+4J3{,RZp4,zVe'Q힏FB{lCCb\sG&,ct4Xم]- "aj"6ESI%iFGעx7,|:AA쭼g= ^)E$hБ y[BU pzb('M鰸|Y $[n6^g^4̮6$L: ٯJC&_uIQoȴתGGC Dʹ0'fPTX48cݴ-O0ɋѓ{z|)x0tq(XPɪ Q>{HS]%o5G]MȁZϗW<:'I!є.fw0ZmQ@{M5!$iC$~o/\!8\Z@ $+>#1@r2ɾ b.f {o) 0_SV ˧QY6htk{cDJ3uW.}R$an|\}(NIm6JO̓'F"[ qs8Pڅɩ0XT mQ7CFPMfTO^'-DP/HRK֦D(Iغ#Q{K번??kZ[oCTs-7&/XLnq~ִSOb@kfLJj6Voɴt1y{)h ^$LwvvYw) `;KAH؄I 4Xq.2jG]d$$bѴ!{lۚg!.ÔHXBb<'oLeAG\AxSVYh w[TR8^B9_qCv?rCY{h⦖y#z`.Y,Zg"Dm9 {o) 0 A8e&Dw.M]I6&x݆}XrRȔE@[#'4biC7$%r _@wc&u5'Cb:9EY7?nh(l=]MdۋI*#yWXL9E+,YrV$SlDqZ,q\kUHCѺ]طJ#Lwp*oam2k.7n޲.C$ٽ-{F^RHoL ~IcK>KPrR`郜;q-.et05nDAcQlPQeDd^dم6a  ;AJ#%֮"ah'R"~u v[ ,4]h:p_{?;{ D95Z%*S=*)&etQ"a^}bWH({w֊i$7Z__c}Ϳv^8(DCTzu*J#j|]7ǯt/HLhFQtH7dp>%".n.n[c%KM}&]BBElb.f[^묟u$,iR: 13H+9gRrݏv3:(XT|E04-WGպ_$"C %#e ~ֵ68ߋܩ($/Q߮O=GsJ)Z9|FTaϱ*q3\.Oy +38#/)E6#-{69X|쐌ts-.61w_@s>kq۔lzRFe.s#OAlElb.f"8,\8Ӎs \+b,:KĈ-S6T !mGGrv Bp )r|bui5=++!?f^b ?*`PIH0,wtb.f#mDYȸ[yMHr|V6b.FBC&_u&N7"B=?[qј$nq n><*qAӢ%7MAw(U[xfߺG9q nzBӵ$.^tˮ_9IcK>-f*#\nB{ҩq+o9lkT(98΍cf7 ._ȿVuNENBB~C|ӟnZhG7|$)(cmf ;? B3dXYba ;,Elb.f#/B_jUN +\sSaH% NFsN *lWK2^ - (}(O, Ie2)B5F6@bgHMz!XT0V- yG5^&: 0ѮհK@2P2LkF!8r<[ð`sā?%pY\+#bQ#xdF4ߢ qy$y%Dz! @2߾x j"xTɋqS!DQ)1'R=`- o@+ (s6H,G ۫Eǘ㔯Sd :mjy wp!Pค=-.` X^Y̔fNW15a Hi>|}cS&PB>Kw="S[QWZ; K=OݚjƄ0ZljL: hDm9-BWcͼ,HkG# 6l "lM Vd q6h!pL8V- }ndX\[q7P2{X) S°›ϕ1wGf`qX؆5]f]|&aXϖ]Ys.('&H2kOf"O ܂)ZͿتnZя?YܳkkQI8=$`2ME^nv8Ub4rWQ{"tSO!Q03UJǮ>t;=1/-ʮqXfXbyKy>![j=ɍ@JJR3.hAf3Gg!Wv}=hlDB=~#'2XR-,6H7s02m;8WC+ROk揘6LB&L*kugTm@6ՃAWI~e.w x%ć9A!MeW7ueK{SnM&x NOi~C˗ԥ_Kp7BˎE'5V\ P[ ۍZ]n\LN0@~FPZ"x[Й?CP h:F{zOc9g}Q0 R?\gJ)o[°,%Cf^9+蓱~||:ꔸ-cҫ1:Rv{i_cohr1SmN- 3Ƿ}R\&_k"X^IdP>c\EP2 `~ XoLhL z.-Cd%nLp\!DYC* 'b V @).`K%OBƱb͜= ?&*vld Y5LEv΅Xw+=!"zeOpA }} \̖_&f5YQ!7踵46.6ϙ:dU:Řz?](< (i r8ɵ7ǐGb_#!`Rxm3#z' h16ofYVr g;"2lNT.`D!6b=Bי!ʀ9gsJB rIH :&nMA=d 13R5  aC󤵇QXyf?)${c@ E&_keIGn6"Ii팵RտK5 g1=q| C74P4Y7Do+C{SMZŊɗD_c԰բqEUlH&6gr~1}=-BCDz+ښ6t%X][oWg8I"" &ٙ,C,Y&JC-F@QmM:2gF1N7·Ey1Wv'gہaSB9ZB_6wQZڨ_0쏠uG?}I8Z]CMIą2*wD&CcQ%JPu=ɌL9QD@"9]c߇}lGkHmm_vynf$@/GYap^F W`_GZZMh;S0mkCKcd¬SŚGT>b{o}=(m)&9,VClЛw[xd(Y?W^A5~ceQ!LyƘ*@ . @a@J9-uA",%g_Gnjly'gCYEYOQ#-yIYͫNE/m~EJK"[iLc%f*wLƾIG yڼ Df!Vm"d6D-^(46 $G.fN.-߾it;+ %.7=X=+PkF?L 4Xq^xIN<>}pp*qDH :1zhnM܅eJ˱@R' KQxdyDdTpSÑYѶ 7_^UI_y#xAkWUY;rEOTqZc=ɻSt7ۿi%s܆BEոq^{"PV-RթyDKm<(>]I3Աr J^@Փ E;kPpLTהߢ׃_ĿH*NarIqfQU(i"Sc~GnV8RҝcG?P7'=_%x%cX^]țb9B!V@yPU#;:l Շ|)_QF#絖~GoX.I3it.*?gPe((95ʵm_eosǘl)sīyA,˰Y!z7sYn>23,C4ob#o7H'ޟ;Q"ǘ(uB;Rٱ!vWXe?lKءL3*?v t~j$S7ڝm$xs\CЈ]t먀2AWܣH'4|EA:E!I4`8zb F4G3dO{Iib/ShAiIͫ\QOcqǛsOrl:+q3cO^"Έk~:tӟO1آ ̨_J8Xۦ3m;Pxl**!$r] [j~9pc G_N/<;<4\Eb|XtxaQd{)Z ,F){9Ƞ.XR6h񑾈'eR@tsJ4z{R&">E@>MByb^qKE "2 %-Ӥ7a_3*Il?MCx75Z][(AtGxR"V7mSshLS WN^EPDṕ)@oi&y͗jN"COiYQv!,,!De UO‚PNac]u5E٥)aeQ G>6 ,x yCS.0On87gۍ4_.u PfK LunB62ϖ, V.Je≗eg6؇+{gSBzFumާTl]頫fi|/?KFC p$cҋκyD+-͎/$k`aS,yBlKl@n kX,%,KAat$- a,%e20߬o5*` \sa% pe]dҵPJ2YS-N{ϣ~bL4=p,xK 3P-RvuL?*#H NghV[ca+@{CzZ~hj~Oo5p| Cиen_: 97ao2~SwKKR=#8ot g%ܟCiodOV𯀾|R/4nN8tfPU,\K6FxvbfZ䱏\5I+,\E+grP[R7!-}umbm$׮.u&{?eN\#.sT2Lm0;f|sRmڴ{aisV~ ?~BW/=D!h8K`Dr>?׻=uvRE{z"C/[T9EI[Xi54طpaPt+jݱ7L3\qGXй=*!hg֭*m%v-pj+Mr^Û(Zcg?T"}Ԕ]Eyܡ)7z'N)v%?.Z6mt6aJYmukR{t?Wܡg#aZ4?."Nhf,jG UY WAc#S){4:/xc5 Sݵ2kOm}R"2S5|ԡ^2|d#o<^ϴSkmx"z1\_ImU S i@q%BȱtԄj-S$ ~MKʗ=i5!%XA;o[3&.%+ 8.&k e.Cg36Mb0+NeƏXa|sy"< ݓ0_~;cJw]Y!V RMP]G抯}\:zO=97jl-1c|xZꄊGVLh|=(~mO)j}Ⱥ 6`PIr@ n845=}l8!e1'5׸oKsUyW//Ajm3"?ϛR^qLvTJ?o]_z~v7&K9` >-1G%>ĦFY}Ӆ?HEVbZ2CP{fZLLI\ {ak"1}vɠ +ުzE14`[9 &ǘtsi{O"n[ >GsGpo0s ZŁhjV`*)HiKMC# @/ ,Ob" S`MuM-G䶷RPEF6+jn03=Vwono^b1Aն%wC}HVBjb oͭYZt>JPU)Q #ǘ]cn.6&^z)$wp+Y)}r<zA> :M"⩔t/'ʄ ۫U 2iEH(!l *EmG\USBSkAaHA3fJ?g'7cQQ 2_`B)NgzX ^Ώ{$TGHxEl{)wY#'?ԡ(> ӎ*ׄoYǫ2zHP6]C N[G7'#e*Pc!?M<4 sgޛ QcU]R%G'Nٻ2wnFJ0BczI'Y4jGqЎ%gutJ;H̻S~ מ+DцPHJKFc0br_wl(ځ%O#Bd2M%T2{m<`%fg_Y@9^GN~22}h MTِĝ!ʱUKpcDbhS̏:hEK( #p5AeW hƱ)50=%A!8s۶Nmeb uBGkj]^QD^U}ïÕ?_N~BM귿wKS$bqJ/:d T3r(b_q(4E¦x ~x2 Kc%-.A]SMC&V?{% BFtl 07Cw8=#@af&+AkLK &_Ā\D۲=I\JAhۮ+5XOgoAQuUwdF?s19hnFqB &<ὥ܀4w%+J? \c:W|(W9ɜ'ȒTjrK !=}`l4IA5+\ z1/E޵Qwy$G4!n%oRM;4FtIʉBTոϓ=Ks/fCK! E=Z$n| fdt!ȭ8BaO`Yz͹yMnt-D*awe)${/R{0p,&s"N}994J65 à˿#wx'i}\g@ˈstg,,q,.!'8ˢz*EPaBX_:) MzqzZ4H? CED;A7сLG|H.}ܬ ixdf(opX[f;InSE> 1b`:m/l:4Ogpގynh?ٵ/G˱`-< |"?@ 8W{ *3#5X؎b3Flurֱh^fj0Gl Z7ؾ$3ٱan#/tnz(mQ͙<&7Ω`35f׊Vt=֐aL8#Y<Է6ۃݶ=P{胃NQ(` aDCT)% \%.6įw 2󍝥)k?swiZ-M'57X>T \7L <@w "U~<HZc8? 尹=m=QkU:5@e?OjM%w\Ll@:l]cQ;]s0A+{|@eda?:A=0-k'cD _((ٳ]fA淾ccUB^gt+AdSP;rn!|;iR&+C[Ѿ_w,M@{M#ިh7V5A687')f< +f}v c(gn` \K}pb2<*; {(zEMc  uЪ;4]8qSkqg#/qtA F = /8䎼]jN&{+s;+g4nݖ=qkD; ױ߂^UJve}%ڐ*}CYZo1F$czbߜ|">aȈN%& 쑦hNs`0=rpByg`TwѫzڢPmqR< tLe4B0°@g,@dk2Noq~2:bF [@Wy4am9:lIvKAADm[AXo3r=?  3| #9Oc`{h.?#WKhw52:<:uTSFm\&( >xĢmlf͗5%bDtC#& $B=N0a$E(DM02ћ2xyL[Ø㐠o@)E%l'te+!8UهsR 4@'PqL CoU{GNAIq6Y7ƻ 4 A[mëFDq.l4as* 3j l$w*<Ts. VɽT8.MNUy7l3ޅuLݨj:su6Wa}W KZi9p&c_FJ4:C~BE716R )b+Eԡ,%edkSFKK*Yؗ.Ls4*EEk;rRs ҔsxYNRWX ~"GZbq)Q*k WK="P0LE6hpT$&ed-lWpb=Z {1Y]l1rL. |]Z^he{ 5As moHiDbKgSWyd$[#5Ŗ꿹|\2t^|o % B<P?oX; z{5J<䭫c/䭱%-Nrd!NZ; E|{Qҗ[(ւ[{C61Hɴ0=ǀS&ml@&x &H皀%_1EQ3[`y*\z3$}nfQ9JaFua*~L7f-g[#4Y 9g;Pq w*Zx͟J[2 zB.?ŕׯ_NE6|}^ƽO>t.^!.:K;S}l7ipy*`gî04 -Ң|guV6.m@0)o =$uHˌ0T8=;^|p U |MGL4g[0c]4AIP鱕Kʏ{UIP@b/l 3OFF&1v3ObwvOkُ(w}?n?EGat\7od2|W aDaCtR?z[ĒIj\2{^lPMu`Z3gY; C,I4iRhߕN&ĶW}f֙~Vso:O/c0emV â5-U_vk*iX|rcArߓvy9n & 㚊UXh[7w(p#E11^EҴC"v ˑ g ]`Zv`E\ [n!c~5Я>s0Uښk4|H:/"is+_aG//`w %ˌ0#6hA (N,uh:3O,JhT_PLLz].-GdS9E ?U0xݛ+\=i7sZ^w1DK"DT)Acc_Pr8sl@Y>;u`#S'Q[|TT·^Ws M&He*!GIrlm֖FTI]9Mr$-G~2wnG>tDK!1, DED\o$JxTc@[ bJͳ2ckπN5s\o֐teJE@~9#o=~VnBOĈy/X+CW˵--ϳp֬s7=!,A嘌Vi<-x7Cz2 W,h#Bm.髉ڶK,oe^?Dˠ>H`4^J=+9,zhQ [@WQ^ƽE+Π%ԫ`[t;羢VkPz%ZHShttEЙ7k(f? :E1$G{Ygă9~pOcb˂ݚã@%$})fS;N lg $^  L;t4ڍy?樋_|C>3 ;Un50bkDxJ ^BzkrcA1KP8N36$!5 Px]ki_ =l[Iw{8zU乚 vstr(1N뷓olױT*M'T9)v]Dm  MBlRAiɖ_ Pλnf G:&u>̥.1k/hɷm;c.RE^ ]v/g鮈[GA鷌-GMQ¸Պ?݉7L%u"*L.Q%=O6rZY7JKaW;1^`D7f@p7ϔ(2b^i42 ^sIqfjz*fAqNd $<}'I -f^ w2ViOU^0:@eLE]KQc|ERJ'|S;>~Ԛrˠ Bp">EXo6;I)$8OVIg&4ڎX-ť n}(,YUw qy{5NFBUYD1ZRі+Hߠi'O'> ڥ[VTW"<_(&paEʆRKz"E$k\Yiw=#ectgei\sW*u4o} Pu}2*YGOބJq*=s87Q\w{j!HB;^w'/"D_ꓖ7ίA%5@yy ХUH;`z\1A(DX+YƠev438Ӈ"KQ{PHIaJ TM߰+I-.jOmD}m^r7!`=b[YqU-Ip0@KLzBsV>?mKpqi~AqdBFw*dE^q?40r>͍م=/ oti$$i\PڅAy:Ogc/݀xzSG)oκ94͛U|`,6[>IVFvaO-<הn4KKLG%:HJeU OOR>fG I 5]=g -Zn*UjgE_@y)7=4pS< ZM2r 3`q %'w>y"? V1:Go?/c;Tʃ<PAKU.r:ó1Ex=uy+Ltg=J2 A} ԧ zOյʅ2Os1z؟:'ߴO LX0<^L{" C)~21D_ƸZu2qۢ[kg!ڏaQۜW`i(И簕?0m *$Wd7wI,|z#g汒F/j) AOm*>H{ 3oOZx}sf|“nj96bʕkEyrjU6g*v=RNͅM㰣01Q]H-+r]3"j|i3}[NKa{2RX׽̦(ƚ>E^B e`sN5 fDV%BlBqfl{nsT-/Wpk^$ݒe6 Xdy;F!?u/)0mJ?8pUR3k _XV* MvtgJo8RHBGjzV4n d]+=OcmoL𛸈͙6ۉ<41#}F=% G֜ɫr6q<7BUUGRʻ}f]aT{(L׹m\ $vkpd6 j X cVG/I5d[mCcױVqla?ҐQDނz|9]trBjiy%S d-e"pQ|4&I&sLD63CEʗ5k 6`,aa?gd/Btӻ^9\6y_z)U=+g_GnwlOȍr+,1hԉ\ܡ(X"iNFwߓAT;fvj0f$.kx6oh뢇wIC?dy85d+"s儾SD/*h6lhP.*+s.UANO-E w`t])f<Ի KS""C)\KF6V,FT]*\‘ymX(&}"^N38y,Vdr)TbH@O"3)FQ80*PX0Rd?@>hhHB%,rO-!ߧpgb@oX{G]L5?Exm~ + ]<8j+rT6+*t d`{> #GUXA5Z=.r!VyX:&|M]VF_s+sU2a?uQ;⤽O) ]N۴J~z{Zؑ7 P=u?L8y^ jZM= Kɂq aYp4}ZMQCLBi #bEJ=.Eۦ5MZك4vPCTE&C8ݠݩA03cT1DܿW)")yaPC' U A(ch Fj*no*3Hb"Hn(,?ۈLҏk7",&,%N Ȳ#@Ze>ZL='q[6A5<>޲|i,7fEBw+ y݅e<1=4ts>5{UoLX56y9OpHu{QGd@Oq:W8lErj,!\QZ*wM+z3 EtGєҋKC=.^V&yg:muWCI0@qwEظ/l#E[&g#sq.ފA0Uv'>=} 4o6U[ M+YbX!HG$3u9 dJ&U1qCpH>{zqp .~Xx.]7"z+ЬspmXR,@}ߦ '!Kl t7>e)XKqZOH ϫA"RvaZU]u9/,C}YT5K`љ]$J$'1!ChԾe;/Obfb>р26Z+pȌk>8oSI&[&h" " C`;g`)8MA]Ɗ\쾡h,\iHp^Lr_z Wy?C[@;ajg8d;^sCճրԹqHp3 #ؼ0l,(*8.C /hMkzp5KTSosPۡH |lKl?_-ƢEcǓ|%d$WaUfk|j0>%`)<2!&J'"!]0=<==`qSտ QVlOv֑3)٩MDL"#BiUVl=>j~oriKxz[ٷL;u6Pf%- M˩_>.=JN)p|9Hzhx Evh*lR+n_U8߸@asmAܜȐb2j)ؠ V%90ɶ2:ᾯ۴|tkŮK{4imQb^bn4\s)b P$* OwvJvԺ@(\( ytK6]&?NQW[Dk.-TU+0"*<K2aޅ+9V(4|v*7=̭1n 6Z`naNCM|-/ف^=VYµ5X\MOsY5VE7sU8X#5Sk0{TMuO4Xc|z_FZ XN[n'[N6x=Qj`Vgq/͋蚎Y"Yz4)JX/NPH;S>Ae6ˈ ԋ;ّ()5)JT? jX%FO;ypؾyK1"2W-QUF\ R~~e`F!i 4N եvV`w8trNT Gf\]kvkROp}5sXxobWdž* pѦփ7֒V죭(0#uDͼc`@d*4FqbQ܄FtSRuKjV cN]D/bjߣ|JeQp9.8SӴE"2=My͇-J#LHz"PD:Xj)1E+ y710lp4(bRe)*yYdLaf~Xy6eDc \ȩ kR}eG2yĄmFPVgf K}zqe;uþ3%~vn-Υ8toe #P8߈, |=N5aúv\`WLzS.8ς#ՃQu}HMNG`bg L<:_Z#)Sm-__\ndžϰ[>I,d͢yg[=H>VV}}7+Έ|D'1QjG" SL5x#C`;N['e|xTUw- >hS9fΤED(&Ra2J'3! že`)`l"p=EaK;q<0sXhhez& rb1E-Glef(kĄRjX1T֞%i[dMHjE ??apO(-mWj^/ rBp 3jKO|򮈁Ч?zBpY|6@`5_yh^a%@AYgzBl^L.:@agOfK1R0X&uCiJA7+1:mOCϵ?FkPp,L#r860 ɋA3L ~-.0FPIxCɟ_pH~|Γv2ʅ7wk0 6*ca9 b65GITZ9t,O (R#$M 1Sŧ]ſoLB.%ܦF/l4ۀ ܀AU)281Z<8< I홦MHOzg`o="o!VItsޙ,ϝ UJAnJM ]RCogE˴$7 d!)\tvPeYwՄV5JUBRv go q@Z'Z\<P1K]/b3 LoP;)HI4:KB0;Qx(3w3]kRilDQ +#G[-'Κ'6 eԷ% hRbcj8-5}BV"w:WI'˧ Fg9ݵ(V0*[FLU1!:. ʂ@p2Î|KYMP Ȩ Xc "(wĭVy='Ig6C4@<~N/e@HwyPezJbPfIT㞌#~M g>qpqG=MD¿N5Js:e!G7CٛMw<"1^Q<.&ČUIl:(֧l?߅du`o+diEӥbߤ7~_jSkaV)/d:DZ1FutC1$ϓԌա!qn^==0.v(Ctnv{:Y-&PaBmv~!yΕ:cAUE_ju{S{2cqY,h]%gqS'Iɫ 5bJ>0,ҥ؊nRbvݘP"*bϫJ><w kdD>yliO]kyl*83o]&8]mڬ%,UH,dU\3GuVq%2i剢Cb|m|r}˂{o' 7Um . xr4a7(Ơ"0кLc^w1زai_( F*9S)8s+ȸx%(WhZe{Ivݛг}dlJ 1v[S-<-ץZA#w&hZ5/ ]j{9nz8[EGiss]<"  -^ifhTϭ5$q}hzc%A tPZLZsRb^d +sRO}~L:ĵ[ykjupaP=W蝌wbgAv&rX7_^Qz]e!bb.B_ uXO,Z^KH曦cZޯW,Lux-]~6(Z&]$*88D2pG$}P3^rr'Fh &v_̀I>$PlI ̥H^[,oJC_6sH9g RFD IΜv@LW+盬ɫhޜdejý[J@a_ u Otjq}@ ENil<HU " R)ZS78ܞIw9&C j)o~n~K #aCx̝bM'nLup4)$/dI8Ҡ8Pn.O8YQ%Xwfdm>Z_Cp׏gk"R8O#M4fPcÓ/&"0j/& *ގ2Ƃ,յ>̻]˔ΜlIήF*3Myw nEнRf7ʏ1,U]]pcW[Q_0?+_yE05 9\/;\ƨ5|l|t/R&G~@% S;\k`0Kb֐&[.<'G}{oV1z5#NJrᵌm'4l΅yBt&荣Jװ3O& -ͱ%g*-˅_R괳$#İPw>PΓL~{Q44yq`:G46SU U)Q # X{)6A"H-w):}FѕG0 E\HT] ݠ2i+^i $C{kVvUF͈𓐍j4o޻aqddUσgZtCC޷%^<8Ry ԕ/Qrm̳o5eÔA^ v4 xA:JVd}Tihy0gM:D9Q,8?%gwڋRL{b[ܸ& 9tzmUgN ^jWSwS3'?;)LqB׋xaQGiE'Oڽ=y4TU"1`ݐhW&zrn1`FR\"Z?Fz' 8NXj D[롚Q-UxׅuOE〈%?'z'x>AAGISow^~fcN7y='L Y9;$텯wh1HdؚЀJv **!$p.|ե]5u0X]'CO R2EI$1AA'y.ddaIA) ر+n6`XISR$H?_QV10%?)Lp_l:*xz}B Aki6o|[d?z'柀fj[Y%~gfһAń%Y@;faNGjߒys$d?\Få-A waWgbLs޺bAD.(jj'NiBP?ce"ʧeT|KIEE! =v^lC0By kJp7d}fAkrUVb뤮UcGǦ,( Oܐ OAY[S|+h`kRۜX'ӂ&&iueǼCG !$Xֈ>@7.qm^fJ %ϔk @l~IbePUxBdʅY3$(,HA $oɠgtt6@(XqhafH;ƍPTST(E !ʿX !Gd^uXyPh鲠 ȘQ0#bIg#6\}s_Y\z6/0ёp#JDNb^d۟ñuV&? DYh&~h4Hkpy*'>xE@n`r=Ux y%?5XTSZbŎY (ݞ:m$ðbwu 2 7#rUqA)4SCp*Bd+6?S5bh'#MѯBb<=Nt0_"tc)X ]aY= [cmjr_0Uh뾛6CNtOɹe U3+ ~ݹ>μh͒]LުDNy%UCӊRՀZ|u$%æm`E'8 {>=ettZ'iQaZ@[J:}BKdsAkH״N+Q nR?4+Nr s^wJ0Eo1(2eʳ[Rro.8hs몪ۖhg̼wa+\i["X9ꝓ'{p|]#'@U % E CҝXۣ e$Í(= 'ÖLljsIL:D:NBtژG໹a&[˻H{ FJulAAp!˛R3^*YA&K#Usq?* yLiIb)I'H!F.?fh~0yT9D-B˦hܑ)p[pEB`D6/7t*]n\!6*ډ칐s%TT%YnM}rL3~O.֤ ).~: BqF**1rM^dzO#BdEV-JSM[6Rl%y?~buN<^ࡔV/vnL̛x"EWiuNG1YF& toql b3VRNfĆKx~OsW6V`B_L#)8`V%&U%$*U=!=^k,/hP08H]!Gڭr> ^t:ճ2|A%qB!c݈Q׀Ik|hLT}/nxo]W[cSf}pGM6i>`}+֝D RcO"p]:$Ns)J!ˤqYz|@Ng ca-NlҰ"EJ*?SgVC0k>04Ժ*I^!uw6VU,er^",xC2RN ,ۿi{lD탥q.EWXr+T*>}k{|8W?{5ڇw>tS3 L:BApWXd.4\⊖Bv,8Bҹ\ ,o, K`>Ң &0tJcxKlP" Skr7 &q2 AD!3#&S+;SU5r$ %ht㻽o05p*)DqB zZ^7\Gp6(3;")w=GD *pW*c~׬,2Rb<ݛ]|<4Yƿen?iSGadf&ow$k@~8[' Ao z,#;&~288|3<LoG:pcM$ Ӄ|,9gm%0$ ;*s(?B |~}!H7R=SKS`yN o5^]ΧFYe"P04k*xXaJw*̒]MiȠYN% %8|B _"67j!yԮKOnך)?{=hs1h_>fGͶO(=:ckX><{39w!4,RdfFA&βdatil9Gw!-{7G3 -#7dT61gpױЙ+2 +[NBvΩ)]HFC6^ ImC' vho"Y Vڙ*%,cb~32a-b< H6/ۜfO&R!)*"V>Q^]$PBKH;\ 1X*Ed$5{>d4&Jgx32/a˰)Q_9yw5" D7էIS&mlLoazRS͛ÃUdDUHv_pS:H KHe⧷: 'GCdl#)`>p]fm_leX`=4-mkzcX8ba'6S'tx/*8FBrYkp;uJ%7˛_S,/ُv/5 ,ܩ9$3G6w {_81]p%zFQ' t~8}kxvƀP@{E~Q8/7A71,$ qT)]SbGZXNHIr OE-֜q#p9NHn(~{X;F- \h+Ʋmuʛ)׉Ç)/j6ꐄ&SK0'Fö2oos8آ,׆V6]LЂ!M䬧8qht  [8r1#񂶖mv8}47╭bÕ2$C;ܤ)zT~fn+nr -! 49;gS`æ {FCp=NRh?vzOf@f IFОDڤX{y`7G2Deԁӡ /vK>o "AXQH|KkJF}==;u+YR*äd,fAUMY%apd+9܍X\M5"-L Ԋ)\Y  =(-uR8f[3$xp#IYwSY gAbh<_yFжm*gPa|I&>*L,1^.֥% .9SJ?kh.FؙbIE7lOdrJ2uRM|wZK>#p =hz EY`{v1OQLG}M$6fJ]3z˧29:$kh@z@- E ĬKh7ymp7)E刓zб|O\O)̔oUw598p1 q5.K#;{ΞU?sO0d7zUPbS}{DxB x7%'@}a1 iWxP⃛(K{ ᗁj*$RFda7;$g+ Q00&\08kިi;:DGɈy<3˥k$Av?e8JIfOD2@K&r0//I^[:&)gFAv}R͖/jM`;ӅaYYepzD"תMዯXGIRQK 1m;0/ҫ8b IoMYtkR&D@w6dgYa1*_⠼WU{ s`!Ag6u׿=lCL2ޛlQwMi(nm8;jOB* &`9 k Ta=2\M)]Ur>)A%JË(ťAzcKh<:٦$򉮓wˍ Xx&шM2ugcH5eFk8,U 1ִ@|m̃Q4tnTf#f׃QWF\#9 }ߠ^3]FF> ^S};_jJc-zO7mLW.jx0doW]-Vwxb/ԆEs5{Nfo?/RE&qf* ^TOؕ9&{ ,"3v\akX( ާs&Vymp70aۜ,9R'6)kfWq i|4c\z=P` -v:*Mow^s<#/Ů:.tznlH}c}IqdLAh]?1VX^WPG'=*H~ hLڙ)xoh}r0-&;ih}'͐N=g5K7iӿ{:U/TTb!]ƭ^ЕODyk>x?ҊB!;Q8t05e+pY /߮]2)-iJ hOR"53Z]=n "J]fbթӨ[mhc_O߽m~rz2^ dh>?M a.Piu>6YCWД z,O-&}Rku~+ɶ '@8rct[1]zՕF:޾R;D;f{fX.'M8D)+K] vlTnWaH4DrJ;z#?%c7 #1x֌r|tX 0x (6uyj\)Y 9KF MPqDF^CGN+v:oA| P|rc3:`Bako%Xo8B Os`]@JJ[Ed,kһ Cf$wiZWmtJ,|*-,-A ߣxG?Q X&;kqfhq:k@,>S1/c~inMo}f M*"G߆yl`a>!<3( m u I3U1W;]C>DsUK较nvYü/}#*6Z9-(1l A/0";ĉ FgmwGGEu&+v?@;ZP6 \3h`8 =y9zSQVpK$'T\ƑAT'ZeI=TZY_;l-Gx\&p฀UAl^7sޯ#o"0cK2ls*#WЛDe,"j>oK2Dq>GNMYJⶑQ lK ;S!d?1)\.!_"Bia$ |lSz PW63$ρ4) ւf|{OxL끁c(A4,!3ӥ`Yll?{<7pd4];2RY񉒏={_H`M)?ߟ3J몹O<^-&%lZXR~񏚰5>G4򳫑|&yi_y;g*XZG;ߜ)M٧cpc2vOT]`.mb^-7 S לohi-D m,Kįw(p`\I AroAj¨wFWԾVD( waLŠ[*R7d/j`:HqjnH'au!!K?8j?,]M sp*X[V ?VSu,⚄͹ [Tf: V-}^\1WfP|PHEw*>#rXt/K^ C*BJ'ʧ`Ԧ,P&/s%;ɣVZL\f<_@Q-Vi $X("Rb SpM 5^SÓ`8YKJп5Rc7763h-E(WsګyS Ï Ē1ͅm1iY]^22?cuCyXHp\/ "hJ]ޤ g-{0&mj4jq<L2Os 5!]?Iɘ9&ݧN=s|&jҀRh-~ۿ 躞PEQkJ_svHzZEƑD/Tc8{^KSSKZw'[q,`o hV'26RwQS(l$ 1XҨ1EW*+x^WE}oq`Ë+?&l`;]5ydk  %Rg |iCUTr}-OU+nus,l\l ֯Vib͜$!oLoMZ4!IkvuJ3]/ WEމMA$l' KdDI юJH ^nr0/_0F ? Q̭}vFd~ȶ2M3S`2=7 $D|Zŷ0yLu"Wp80< (n1ŠnbgS!`,kszCP>pD, b>e~l=qIn@RIRr1Lϯ<֝z[m/ajDdOA_N+_qg1=F7"K-#͎~'֐Y%wG63}=YXK[q@2}L75mKMjP/'?m[*hrNje6i{.+Y0.~t=b *u+"9u)0y ӴAa~D>ƹHUΌ,67P bܸ,N+5n޺ُŝhFkFF؍R?;2!6"8]r\$*sؑEwGxAjP"^BAqݳ艹X|ip첺4t6Vۍ6E.P7ܷ Iv{ov?)(;Pk,Sn;aGƄT5p ,bZ;h{<|[R(IǬ8$UKM/̩RuR$,ljJKLZl?-UaʺXn"  v`ܧ⩇!^ L$/^  7(-l56v.bPnVsLEbZai4`JtP5qU(V^p+#UE^jKm^4C熛_G6{&7`IbijvJR_U q[$:JKyN'Vsr($/ Th z&x(`}n xo@`$dNl9LɊ7*3S/ESȔ^1Fꉓ3IEUV.GHU2$Aư72~{fCx#Wd!Z#~ =ROhhRbaB% df{F#׉NPBϧ(cRgHQЯS vt !+u&oY\9^,tȒζR f=8` kS3D#$#=ubJeƑ|7tau lmA1'я{K=ny#Qu<ph'ͮ*7Gz;Ưn\M^r&n, e-px勀D611e7htdrYĻ(XaPݐ ؄G☟&sX R*"&.zb=h.^y#:CmcZCl/Ih.Lo;uV)<;g+&mI+W2뉮|6 0%FH rT3T`!Ǻ9# d;kHGd8%hVZ|V42VBJTg-v W^ *vY( }o?qE9C8ġˉWb}޵}p0%_s7nr<99?=ЄP&Lv͆ ezI:QxDu2y-Iɖ[ X ƣcfqWѴ7 (anɤEJd  qo']edR|=yf{s'U mŦ5MO{Z#gt;jr\foH3ȉ>D7Mʲh4)깁3 HbJ C?YA &!^')_/mp>ieuE.[[v4fߕ\6PޜGеZt>^'̓oض[6 C̴ PK"qckx%a,_X#\PlwaxfaL)ht-$sk/wr{FBMa)^Im^o ޒ[_4}wQt cP7/q3?iJlOŢ2?֓Sƈ[o.bˋ.t%EltSߑ_SQC7j ZY:_h &_Iy H^.l&z~jK{hZ\w';{2e_<$MC*kvPu4^!k=@-AS<JM0$[vi sh\;MGTh~hx†i ?!bt8 `c8ypxQo4MF-ܠp#FO-ƺ 45%sbl\VH+ŭ2*xlHߕ$a1*Qa<6&E:PSzoѡ ŽBh0-p RS\7U \hZTm.BЏj5i[PC@+&2= D!Ү&u~D &llѻŅs\' 989sѳo3KNܵ(ךs`Z6.=ކoaiV8S^oLJـlۙ3D/KaH(`#'7AHq{K@vYsB&Idxd.)'u=̳ϏsЌĺ)f7NǷ]RtHq T:(ЛGA}awLa>ټ73N}Zrڿ{)2U$W8jL_Bb(>d6Q|8Aи!2:RS-OktkA/ M r13'y,lnn#tT ESkn/"܃Y+h "뇵;lpv j/DOMg5]U3Ǽ۞GXIZLr^T2} VF+ ; sakt\nNäQm٬^n8J2_<],fu-NA:Z Ye~ヨbdeeډG\5D|s@KE<| "X-Yn]d8H'Tۛ_=/snڀVaj1ɕJDZgt.j^4G5t\W쐡3E?< yJÉV)H'ʬftw> PuE ^&t[g#YEk%rݰ ~ ?>pp8AyH4]Xp\URN/?Z pQp_t6t5;f iRNCcB=oY!= yiCy׀$Y}Ad(1`r8@KnL8\:TXW_WI*$RK^"(aY&:i4~Ӈ9nFJXg(KUkd!{eq͙7VR1H$R.7m^N[CyQ])XVXc6 f10U 3R {G/PjKwUٸuH8wbC?aBvL_u'5raؾ/K@n!DŽbGU"wB|sgTErPR+br"L|G&4 ZT9m]D*4o[{[A]Xڄgp/G7#Le}>ĝc4`cd,E?_1.mZ`>&+ $X?K.x`haC@rd PfTC1K[&˺nD+n% 2TtLlŇX$\QАwk1S~ҵ7cbİ.yUFz~ ?TՏY꛳2ǟxkh_(["] pbk-iO/qoJ=8%R$0N/Yv>U퓙vh4/죡B2^Iʡ0n!^ 2}sRh Xʇ1 e @L2xfg$ EYݞ6php/\ ٻ2.om8PGz`@ l js]Eu#J|3Ў㫁_f5 A^v;^eg7#%X;1n$_Ǘ5wexC_wrM*d7b&;P~qVjIwngM=BAh:FH~gc}bē8 %t)kl.[F,T.Lk % /ʢ UL!.E,ۤa }d=4$eomt|QmGC8w)rhdKX:@EÙA>b&Èfw5)_@yw06DzbKĂ F˚/Իɩ1 zq}ٽ񷣂 StVttۙ'.BX+QҮi;LA`z3V2sr!t\3p4}:5 Jhp"&n\|tO7X,$ ɧBkTκ_ d=,,LGuv7Ҩ4(aCp:l٦X[s̃ yǔٞ`Kʵ5'B|F#a>8}p=L4t`3_vFAtd7Z{#J=|%5%<]Vɝ)F%4TGT?ߤj_~UUҟR2%j" ˀ/y`ʒo9 ʸPJ#p%3!`ڧs&*t*<3=.+)-Gػq:7ʨ=TaW_j5q)Wp!TAUÔ3?EL{2X$(W[©rQځ|2<͝+3]3 )Y:$MNRYmSŽS"flLxYb $/TቑM]UYĢo8]]e(ʀ 9mSoCH sj"~K.LXVjK/E?tުG?ׇ 1Hx-=U*0  Ô5<ɜPhV;z>q:-aHӳ#46ͫ7NK4bQ>&rȄ$ǕT9B~s?̨RZGц[ISߴw:.1,L:""*4Ό<^!uހc_lZFG~tۅ|BD grNFLZt&tާ/>g}ߘEt=81.\ê 6o0510;'P, `R+m, ;8oTCڰz?\ȹ*:@sEee?)Lvw]R^6SvyraUZ0bl},+牥DZZWp CҥɐZkoؓ8#^# 8' W/$kb@<վҊH`ևN :W1]#Ğ5b,=-kzb!QBl/GE "z3q>~R]\HZ'x`U7*:/&OFJ*&5rWO&z(o|Ó-~(~-X^BBv61bөǂ5(U% ˬ+7Ӣ0`I[Uqw0a hrPZTʥG_fDBèqV)R" [c@ǯIqg/z˩(?ukd>d)[6H:coD'ً|(75ºzϹ:Vmwg[e[D@7IXm%j4x-/ Qr 2.njH3iu2Ԥ@HVTSsy8An.RkXSfC[#RT#әA::.q fr/ Q^j/< nl]1Yd $Y9hw-(`^$=6$ghQ 1JCt?y {Kړ2v%Aba_ 3"a<i.d]EϚ3F5?+Y6(.:]B{*6"O3emc{A{S ɶDz=nDP_%ȅꨈFnR"b T HkeZ1衇toA<PUtp/5?{i4?I{֩o<`KLL3LS {䳗ﻂmW#,TفoC)(.&-C-]%.=UXQC,Qhj!ʙjD|كR%y~ÆZakJ2iTQp߰e8}y L,;U'8lG/hy`?'I kI:8K%u jB/eD>w%Ho<0Y ΰj4М#3\b*>Ґ>dd[Ĵ>2쀵!JCar%qAYW 45%Y*4!SHnS-Q2i4,m# ec \XO]0 jruljVH[I|N4iAWkkOzcHqm?4(ƘE:uIžؑDscfφ5Y'wNNҩ _茛`F *زk W雧Mc2[嘊Rlxdp1p|m BףٚK]2Ll_C3_w讶DkDM-MA_c%΢ |Yľ QkG f^GZ i`*FG`$&!nN7&kм{πmQ3>,TQ%6 h}`n#[7Pc"qU-_FR~?Dqc. *M+]5vA.$;Ntp季$7YӒ;8%(e,c0ҥDջg!=CFY4{ܡސWϜ+ ]G :,dio 5}B EAF\EH6O8huD^kiKjt]llίJ@!*e%'3IP\aKP:a 2{[hcKx@ToWdn'&3|+(}J)V#kO^6:SVԀ`1++!1dL7![y`Σ eJC)˖qt+Θ(*{FOE>U@c­߉$KHκhNHD&"~rw8jH:ĨSZP(v?2ߛD.( "zB}-ŷQ/q Z}UĊ&8Dqǝ-U - 4z}ٹfYHr; 0:Q{EM: 9rb]MjtYysY.T_2nP&6cd15ˮid'`<->ҁkPK|B:^G}bY𙽞 %La01{1(7ap"}S=u LʆO5h;^s$ח|DFΤjޡ|=l< m4)NWMu1ѠI {8Z|L@,)4̙24pKJO vS[p }waVIZ2(ne}KRqQ.*ޫX =\תM>sQyZt}P`zg"ת>@V*zށ融_dijN qL5cuja>+Itkaj% t[ | !MTT o!݁DJxh|?|kőz#_׽qHeI㞘xZ̋i5@80T}W7h֛Z;:qpȧ1EX\5]Y)^g>67enY2x äp'](Sn\߅P@ ~ Oe:lvᢼ& 7+ ~fn}\l: 1L.H򛷪H4[\B[ JH{ lGD8 scW-TcEZ#G?Ș+C8_lU2G71IQlf#]rnzC؏@4HȠ5hIC<bm 8,GFB(9Cb43ȺƖVd!:%4~k7V8YЈ'Z~[XC CXԦ.VCDd`鹒q[v]`~:G$Oqz=xɪ(|JhƧsHҍ5iyWRۿԹE&2lYij@.[A}*OB^tv C-]i=ntfӎ^!n%SB8 '/?2o? PyB `m Pk^e7 7L1s~1yC V=7 s+voAVd蝄Gr=X >r=)m|p g)J\ )Tݨe &?5TĤI۽2;a-ĴԀ:I-R-ӓ9-9؄ã6 \2}jaHRP%9u]^lkAWٜl|tbqFiZf- (wPԣLmʅQTv}kc֌)F_}tIxN.v @(*u- MIXpǴâZ_.T5ǒ R R2ޔL(b$Č)?BaP"D/Ov@xMLxiH1C⾱J<D?N,Ҩ1yi& T(.AmHY^PF[P;ף=} /W.jʛIDQK 4|@꽘3X: vT HxH3-Nfૼқ8 ef0' ٗ9֕,IVHE" \dx`8q%.08<R[M53s2+7)0=@)@E.Ԣ*c3ʄOpwpm>Y48Me'Έh4m:@Sn$8 oī]imR_J=Tkc윘&ew:/!$Z6 n+s窄1h`֯˅-$RQbKxe1#Q` bkX"53\(:z?7(G06T6YFhH$,"Wg}1n ja{g[9)Up:Tk}mVEh4FbQB4ZsС 4E!兠 n l |BͭfOή3DsgGb' 7d1\io" @jy-$)iPC*%(0vZ\٥mz=/u;깎~I=:֋r`!*!0zS#.Et TYxgaә/ͨ%~o.qU oAy!af o)z+QHJ~Z7AavRJ>]dCcpVsffZ{$fʿXg0 AWMCj3يJ7Y=:dWB^i.5(y+rvdy︧A,Hi[va VR9l)mqeۉm̢|G.g;t| >jO_az? SST6ְ<'J ]lnmᆚi7p¤OQqFN^&=6 "B>VipWNWaÃE&GK+ (}paP>[y*Kw7m`Z[O11?G~oZN,Hr ҈Յ:؟k>t`ɶ!N ZtH k yvdWDw▖)kcK}{')Rm՜qd$ǶQ2}#L)Yok(9X8N~&hg'}I3Tηbkx9ԑ?EM2Eɗ⵿.K>&[*u"N2ڇC5AEk,4NNM4 #TGWm9>m)\COGr̀rFUUJR=ޠX 6Fw8%s;}6rŵ,!y4/8*׫m^z!vRj(΃o~Y54&;.2 0wRkJ^'\3`s [<VsEx2ެf`*,a'%Oo`Q񤧇oB?Gx@Jz32ƝKu wey$3i~?pHy(qO TyJ,tTeW'3 ȅ*A!g)IH9</=@sd#އJqݻa CQe4a[qng} O;,sIxK2)۟N|.ю?MXU?ٺڄwMʕ[ujB 3$|ءmV4cɔiQo4ZWQ? a, 6),|а_Zj-{#3<5}B d"\1=Sb \Ƣq3E?pSBauȯzSI"xpk E^!j).Aned c)I\@A7- pX2zlG#GԽm.=N%^ O`:)lq,P_g,JP!ڜRW݇wy{&jD{2|\A"#ŞCc81aέw S"rQntG8S,Ŧ^)O1S/ fYIU+O?i M}d~kD>iw?$@ ` wDz)%v©y=dqkMki l1bD=WWkV)m+D1T!л*?L˽zNR}Ks>]mOKspaDȲN ރ+phx6v*9mBr[$gSEI!xUEK S>?Ѭ‰^F[8œY\nkx,]{z}IuIdһAI?܊vOB0$n?w\xZ˓yfYǽ r-O2$H>3*ݫ(em&53J\lwJ^_ vU`M9̓)rN<܁>:˵GEVl:ISZ2ޠ2۳pSAPW؏OFt[N d琷خ򝀼wzί"v6 NF xAnK8HFF3wY..Aj1}X i`8k>q!kQv?jtӠ'(3SCs ΔhvU %O= (t Ta`N(JwzȄٽSֹ2JJ(<+Lc'3X1)6G0_jtB~{]iHl7S+ ^LGȽH ωBH6+$V9/M P (`wga(1a&Rt;rU!TFƭk5k!T}bm\;rcB -јO~f[u}fN%AHm[f I|HlL-޺K4y?ܵ-3_53C"2Xq%9h;Uea*w8h;\~i_ΜN:"v{1fN +4*4gHp;\A{8}F\MbƓS8ݟ uJ"&@gLRv|K:ވیqZ|1nO.cKm/ U\I9%nW !AR8=0wިs_k'[76na֞)eHFK)4UMl <>1!z_!w_{r(G?cu•IBQ\h1Qd u0K\Ckd ֌ AXd^UELOh=0idcsZG529>UτҴgvVS@2ؒ.W~5p$JB5 ͹|{#& scZƋ/H`r?"o,__iF \ ϵ&hL“#6f[쿸1  FP$sq]RG%ELS+}|D Q#Eu==!^>k߮ ).{> 71T{+tjaǮxȃ%T/9EC^W+F w-g? +dIHz~;3dO~U^z![fM:.m\eиgl;|h&Xq/iظ&ab ߘa-F@B1.~q @ctQ`E(f;}>vLUHiZ cnPvSc^S#v,MI$>j-&wj nmvۺ~D9q׈m+ ~kDɡġT#3nq֚-g/VMHNT ?yrvUqcl)n#?EfIjˡp g"y.7R@ .i [ ie'~&]G[ۻdC-1Ъ߀}0^A!sz(F6]%@n}V j7^V)hӟq u$+FM[Z a |4r]i꓇O/E^+zf|/\@=) SXI=dfuJ56˝P?r5Sz!"8eYDZ@{‘IvRcJi?FB6B74H3|e$JWe%3KUO/2>zT{вϏoBdJ˼ܭn3U5<Ͽevo䙔xw>G[ۻ (+^p-&\_wTGJc Qw#{1FrAP]N1Hd5&ʓ7m&QֈK㷣ߢ2{%+.oI871c9^*@&I;>럈3XO[KInIApA*=KRiEC ըT~ՈfaCTwsSKySo̗8Ux>y֝6&4F;F~|0‘}IiANPH"IInEf`< /ڜ8(Ĥ7<.!eEՇޥˠxi|U?h~I!%12{eP1p둮Ipe9aiEx11|\HQ&` 6U\3@__?ְ:4eN=2Zm'Lc_* u9G#p^f,ҡ$*F֕Ա3e=M)v&dv830`6ȇ#)Bz$l]?+o"/P''8{I NPLy*N 35|D&{2ٖBp\sYn`oX?0uvqkX85۰4d% #v1!B2sZ+1,P'U2;zN. ~PWc gezm &xoʿlT|zF0DA1`3)>[}.kHgC`3pł$#JcҗyUnkO?+wPޚ  h$Art{5' #Lߞ(9!=Psq?h0}ޏpO}d)<ϢX!QuשZU?ZO-HT8zYɇ CvM%⁙3ߏޢ\D!Zn~h"ѯ}H7ƲgKfCMwEG2)cZ!ޔhEk:D/gE~^G~}7>tu$o:thPvMD ͱXĐ-gKEq)1p.,$XbY \V-6WU$YzeOM-Nz*( ?_۹XYzKP |s:/hwzL ѰY:S#lD@gBq)egA&e^h?R7BQɺ->k~twmWf8 ˋ'ܥm~`spX[[hC w A]X|8,K?*yIukvOp#[thmE58%4JB!Qgw&{@=@"KE=T0]ezLa4]Vr!S?r 2\[47Z>`Ӥ}Cl8\ ~_z>fVӡXG3-v\fvc[Zf/`@"|[(t\qr* ~JHqZ KdW?RR" {"d{\׃~*+A9.4 Y(Jn)cz{%ds wsR{9cF,nVp!D. =$E7\L2S>h a }jG;Im, -:QXV YRN}#JJ>pQ=Pw1 @=vzXf9DŽTF;di糾9\$#^'wZ}w+dOTXr2& 8Е_Us=NC 3INp}]vn;B.9 l5Ӿ$ _En'3B $WPi׺vz1&45m6"e' 74[MF@m^a,KB9F+˚9c41sz[bSl-s[jܰflyVLGmzܯ9ePYb7Z> -~1mlE ^-~KqoəD9vcL[LB#PUE <= Ԙrې  W;zԈ '.)"cXNx0гNWM_ŷDk}a0IcxjiFr,jвn@هM-VbkѨdh#E0/yK?:7fZd'BRr97] Ouӭ\fuHe!Gx\E*cS| @b2 lB|+IڠCǂ*$KH{6_.CօX9ޞmszL#{я(*%_J "@Ny:cD|G0ٝj7 :%Tn4=,[uvd#:Q=<ݔCE\ y0ԑ(KiAiu3ݼȐY/l_9zsɟop;h`spD^eNg~o KK: g0rvH1%C D@`J_5K֟*01_4qx?'z;w/qY*Gb62Н1U|+!Ӿ c%(;ITSz_Iib#Bq)N1G9A+&ochv6_V@~iLR~R9  GvȪW.|HeYuHW7C+P2\-<.-|lcF>C }}Ʊ^D1 R&BenYbxr],4%bǏ*W(wve妎BqǦE%qf ( 2Z:HQmW e8槳N?s7.Tqt)16#LAbUkrWA5JbW*kl 3&OD,>Q*s):v.o h㒤&A_vMBvG*Bі:=CÆP (%k,X"M4Sk9 3?O)tY1ų5&]a݃d?/cfJՐd]RZ<` c:{:e"{"!CHRbkq7Y)r}Xy5?>-.xc^{ÔyaBDKSnNBLx3$ h:IЦ5ƣ|Pú%I֚6d:7^ S~ #`9 'VO`8-szMՀLFʬXNJ#PȼZB]GkN~"⺱i;PU8F8$\1SKyU/(ы94S Ex+びpqjU<4UǴ͑o%axq}ѱ+]wr6@lr4*xM3s刦:0<ܥtBL ӏk"zqPn+¾C]&:9AӨ " Hi. T`!>o9$ߺIL=<&Ț94$#TKG4tA{tb`K)a*!#+z¯F ?GH ;NOHۢ@e'L()&!7+Q4 p}oOk;~+_ĤAt, Z4骲 _o:((=yB?%fƅipef-ނpuaQ\E%GO((n"Mh˜rf Uj}!! Y"O/XʖFrbF,0n,<@yXw~ܘZÉ6&llҺ4d{=V5r5`MH°L[`)j9QES"6+`]NBk}QNHv_Д"]'~ECy),aΊ0&k[6&`ߊC,h9 Un*$p+ wWz/m}01} A !:7ǙtUjd @F H>$sxF3gH! tٶs=Bs}X^a`?+HdYGd")J+;# N5A ^<|k&M}juޛsΤXHMJO4xe;x/)Cbow>B߭B3΃ 7WWDdsVLf0{bAo+؛ˍ*#ӫ$X@<]{t?vmB~A^Bac\sDԫ͋y5Rcgweb=azu~S?f~\7 /P3KLKoԛꄒR,A,%0hr Acif EdG2%) o6T4c*)&D2FWw_boZ%wf6vgaBSj~\AvxWx^[^T>6೦[uh:}Plgz&a{٣gڇsba-tN+R}K-`\6b4-U]|q^I5&Y!FٯP-uA4`Sv9xWw<_p> &w}^ww?kq/~yUXH w* #-{ˎuNȌF@ a?^,xVŶ sNTH"+hTcU=+iPùpYP)ZK`}qvČ@ J-%/0'g6߽Ϸwnκ)ݟ0vȓ5v7C}lfq4ꇣgh {Xle{ @/*BwC%/@}~;`ASaD! kjrLk:Ek",KRu@?zTI|1,ӌyJq&3W+y 1s Bգ"}:$A"mgINnt1ryXu)UũIKϜIdҲM3ciƩ 62Z3ܡ]an _ӱ1J/8ug5 R[, +؜)I(Â1sL1ZjrNJQ]aB ;h͘LW[l}Q5 9y$bNy H#!7;ܩ`l/&B!{8Z#"]hP納%NCLskH5% ,u=)1VGǓߗDOo;qim^0H!*qS6 \o+Vf1 9d}u֜d1Ede>6nm)|ӣR/w[ JL ܠPSWc> X3ԓ\3?cVA"IRn^,UscsJ׃j)ufZOܺf@C`rz݂!`4c҆ٗZ~זmYL(L{ tQ O @Yc2LMѦ-.,*<`lǵ18%M̠ twcOɻIiˍq(+p&9]XsV]}%l+_tCcdr^jc&J14=NPMu5ЄڶR<_r,!YovGx}c֫|gȆ8MG.f^qhXnCCxQgg'|bꭢ)k`jz:`Wb ՜ a$?딙RZ4<:d9uv5FH JEt4s._Q7 G`;= ΤO͞OQfՕ( Fh ?`0 +lf+֌U. ut{UEx:1y z#]@ +ϸrTulp2)R7ݑMI3'3(ʹ¢{Iô2Q{k%14XόqCNNQ142LvU} 41 QTwU/ rtl+)KRU&26 XiǰfT׸ۋPvpYmY/{3*R-2IcPP݆bXhžc^fA.lSBD?eMRtUV>DРzFα]J'p^\W2Up<5x>:cg9j5 cz0ؤz'jr0סM@W ˹4%~D` |uK彮@[*F053(9!@E S鋭hcX jWc^zCFc%;o6K\ `.-X @J2ٮ4-eւ"KыeӢ-Sno9+)d dԿ#Sשo P]ąC D$m,yC ZEzܟq_߶y޲:DޛB-q` K65^3hB&tD "M3J5~!Hr ? #v|*k]3(9!@E GP3(ʹ¢{Iô2Q{R)$tEx)+))7߬ޕ8p8lE/KXmx&?T)E**f-EڼavRշ*B;-(*/ Uη!ZA$‡n\LƸFڳ9F\l⨛Axpbon\y;(qIٰ #WR:YYֵ sXf"azXH` Hv"'ΰ!|_(W[8ĿpSZܘWG[WXŖX۪;&.AN"\2j6Ir8"@H=|J3"LmT0[*m: "гjMgbGK7v㫿~Rvh},YTڗer' ƢE/ȕ_yjz2XAtX V*"?`tLfuQ,/KI)(iбj寍q%93*I߯L)bd4G٬f1Nq$q[ⓞuo9wɵW5 ~5jN@^cd[-hٰlKm=..' xTTaP袭j%Ȧ5̠RAKlU" 2*7r#ǡ[G IrFsJ"L %! / bL֠BPQf*7-qpxU3P̍!E-"ǥB Ƚ\WaO;\YAeGT4IiXv.)l8i3G-/4]Yc}Z (#iHRqY7 XwBQZ| * ^-B?ɱt2D,{ 6SACeQ ($QBˋpD9Qb?ɚI.YG7}(lR+dY { /"l!IP5"u!B g.ʡ 8vYM 9@#;k'ϽIϬ7 O(%'sq^:oTzv;8+I :9T"ۣg٬ d`MepGp•k!+!nZ(w<(" ɕ;L}*ĠRј ssBة= }Kq}J\gQ'6q|[ t_Xܗ>_͝*L!$WHwMIʜ.bL TAa.> |uY֬WHKL,Ow[P$-tƵ/XH]!rmaeT.AaKfGa@mªn'n^Gy ,t!_IZ`LN1M,3 .[ ʔZ 31@YϘY/kJw=RwM.)>T'9?8yQ+}B 8 ^+MNvuK@ߕQ>+MP%|iG5RjWjqp BG=&DFi=.IbV'@y1[cIiQ4KK% P>9W~jJea&NgCqoOž*$[^!NK/m̋…߆H#_x_& Jq[ y7SGR^-m3ƉIy FqO ^ +{7dgȧ`7UbBڭџBPT#l%KOsͰ>a V"X,?hBُ)l*B!ZGC>vhtuΓ[\fCM1f&5 ww}Ź..O'/{b غn ҌMND+& <mH8/\.W.75< Tf ?lSZ<a\SH@vmVdgMng ZmJ#j;rp#V6Lh P4'0Ha2d#ϡ`niiqF1eS zތ[V1H鹿O6HQIN7nc(6~Uuʂp~T4Ov ݢ}A'd&:0Lmyhơ] ^w=#/_ l(:h9l<&9]wxB<{~26_1u ?kR50BCX|D-S$jYgg ޤ*zR3i+_T2W67(#M*59μ\qB2"x->ϮQU"갔obKTK$/|ysɦ.-!b&!|䐹k8ǟM䑊uآYR*S~FF)ÄS?/馓UDD~Ŭ=g\d,\C]e%Cv{&dhc!NiF3l)PQl"$1F$Y5߃5lO&ز85w=Y]d7QgABV10e%Q rrB8zx^L˯ #{.,Zvݺ27A*ũFc8.õC P8k0`lijgL|qdob'/$2[ twGLPC aBg)_t@)Iև rS,s~_xYY`,ycG"xQVe^fM2-ոC@hH奉}m҄\%OJ4! cw< ۾a>< \{0fQrt}~[`QX#- eR} @Ay%~旹A-f\Rb?.&9ƹgXy/PBw2]OM*͋OR٬~vbGm0մ(h0x|vrދ0O0*U;b,%%lJC bmĞ8^$mbko53@ ÕXс'ajMVqa&1X*0YK5~F0go Xv"^ڕL49KazN]_RI7G~c'YQ@i HY{Ŏlyg|:j H".t:gIVXwcG2F͙ԇYWׁx alWNdžZ1GTrϟd6aRzu 9Q)> fUНVwaJ%BTnP!/NDzJA~,@^SiT[x:|; a;'}PnֹO&̭"܍ 95ɬL8ާ0a]=2(gy>۞xͮGǣk@dYg6ZAg8Y&^Ph`.K5G/1==[1rZv!.&IzIZ8uQBd^aV*8=8Zy@L]ހ\y L\ :7)61BerD;qd2W':o~/؋5) /DzUC<ΧZ2c$7y>,Z'yğ񅳵yнu<iI)خ$&azjt*r.XdcuIPu%XŠ07v' eRi=0NCy+VJCmfwBaRfj11%,hZbzs Ȉw67%z*Ke^ FP+ɔis8vIU[Cd⊥ACu,W3蕱ã-Ũ/+BCpA~Ye fgNnyPAiAFcC!E~L(q` N8D2݄l?])>q8n%(D?]Mse]^R8!5^>.\cDyt/cʫiH7d<vW+'i¤c6{p+X(fw俳 IV  yv*b:(=r8A ,̗4v閬vCa(ghǬgS8hb}M n+X,$i︒V.Ao9}EQAVNJ?'HئCy'.Q* + t _?QB ^B=l1!e42ǓRJ=Cl}u89x""~T( uͭ7E$?|~:_Ԫ^>J@h=P5(s|M 1oW|z+<1^ g+c'_5uRV f=: 5Nl8c1iԑyZ_㢒sszHRAY%' ,j.< (3K9]6$&cu>n9DZgRX9|LIu5abǿ+ZV~4(عc)LSm#V : ڝKGmL_(eJ KMosH!q"<,rn DWyNe4r'_lys.=F{41)xV:'R$(c>Mj3 ׋H w&hQϣQU1oB{Ό:8bQ<*]HS 6:E?+L߅έxEOP.`-><5aDX9e2bNw[=pbp" 1Q.o@'ۊC(oEKY I!7XhwsZ#UWx9I %,~ml]+fF`BXIs!Q4> :\+g9%9IqZ";KwVf {#ho YfU =KDhA\ V5(!RPU,J-Uw 6?`4Qb6,?f,x j&rևOoՉA)A яP*U'@I`"$2_չ! e3˯07X][[+fS7k>ZA`ѰZHɲ\|!i:R`}GvddCNz1H:Z=}=il9&2 u ,@HV@K:/<5$2Blչ Q٢6 lj~)P[[-D܋{ 4-#zk880~/S0>!Rι[)288H4R;B=5tU: ìBoRG ^BUr>wR) dA2kuIƽ+-H?o sGGx Z(YC6 A\WD!6NtqX2HRdX оiY30&SՓa4`h;PW<+*w؁-"⤞&mȯ4l;cۘujkvw9jj iA>mqUR(1lW'ɑ(-o2]Ϧ{1$pߙb(' S^O*Ax &D|m!++&:HPy*2& uJ[&TM|ܣϓmy|%Rp-]҅d~*!^ mW $):3@v>=Rµyq\S<ش%%;m>WW-Z0I5~4;Pվ0L \:JĜ$ j~ha:(x)yR ]~>K{{#Q!:9 JxJ:_=^gss^SYh8]Kcl2)dcHu}J je2)"7{-FY`/Ϝ}~/Ay%ߗ"8w]B\eoN1Gc yEH;C*!Y(GNl`\l? %?RAl?F.= fStn[D0z{HOpo%jPExl<u3AqOlC,@5%DHE 8TEU7مyȎ<:'I @MHJAmqXšHZ>B;[KwpPם 8>Vj#bWӴv(jĚӲs9 : aB/1.ּJS ȰW˙iB١Y2-"#g` ;^^؂SOK3fBND 1.&lO|tabٯ[Ķ`jOKM J8ENSJ=CkqSE Hm2^$=s Q9=OoEaM/<ڣ38bx#{cgp= ϱ&Mt|@4, upZΥ;\7ױɅ:wSŇ1Z2dbܫ:?k0{bG/KTNl&9yMiSs?,FbYNqb\B}(!#HiIcqP.^~t?*D{}ZK #Z4M=25,m4j U°E<Xhk-CJo{e 9pMrDɭCXzCjw8VND9e2 )ugq *ŌEҔ)2erqMlŧjsX`,74!KO=Ͳr aV K'β7C omv/TI1ksPpY6rm+h %.I}cKL4Kζa{Eo yڑ8$~d)zelkSX5*i-qKx_\/ ^5$d3cWqpR1o|,]>MhTHf_;~qY Ydk6, >pv8a?ak=L֚%Le (nȰ5ܑÏw]X5*ic!XR&WLN)"[UWMup+5b5-\ ! :\ӫ3;ɄUؼ,N5=_I酡ސK KQ6^>ӉI(75LHȋ:L^u-TΧ֯e= 7&eq[P12r:²#ujsX X;B2yԧw%tdBxUBl#~b"4LZ1-Y}r{̼3o;]z'`VNY ܹ [6/N~D]!i瀤Y/s Q:OFD5>Z絫Vg!Q6Ma08p% P¼)O۔܉6;an,u:# 5#:?|[s(ڈ:ᩱW( X;B2yԧw%t\Nj$!c6r DXBR V0'aVUz ǰ.v-ʱSHxk6Sea%/pZΥ;\@у-Jn{D\i{Rהj}2&D=&lD$SF]u" bEɀf}kgٝگpama} &yaJ"^QȝyFޣ".v{fז yrxZ41WL.-#ޖ?G'o2ˠ4b5pOG$ -L}?]-Ee;E`ε)^t=&R9e? 5U(midfg- rc:*R󌟾 _T^*h)Øuh2n\nUQSȐ5>6Ei2Gm T]t'2 SpuaOu3HZT/91:Vdejη>P:ըmIR&4c4J.RTU# ˜酖z"`c3cޗ Y )#Q`2NS <|&N3OXMof:y>$ǝ6jlc`JaЌu) .˽#mAOƃhGuBrd铈7I{<$ ahP4Z]-W+MퟷP"ٱ|d"hqEe|mY8dG^S?=q#_v. D xX5*i.+@LcMZ;X.|#M} 'H9pI0g?]K><=>S#BP8qD=%!܂ɻ>(d>d;g _>S/B 3Ϊ}X V${b0՜ko]l-Z(~SmIRN y>־Cۅ<0V>%he#|63?1Z&-񬾻$*5J+qjӗӟ|i),*?5#f!hQ4 x*Nd@ )6k2xYӮWwYOϕn֎>]IakB?F=b"ԕ( X [޲P/{sd>햫?b3/xCTr(k%yȽ0$5.917؜y^8VL!M+AZ/Bp)G[Q͉;I*eV|{d7M+Ҵ(n҉Ϛ㸫1]g6A6eIH8+L/䱙jHwXu쿷CIY"DP̦IJ+idP/uмFȏΒ[?.qgukk B}`e>ycRQy!l"~2n~(t S U8N`5e=c@\]&3Iqt!x;K"+Uτ4P_&l18xkor"r;L ԧXqFO@0x"J #xqN`x2zi't0 ￵lT%99&ef'Y97)dIdP-9 )BI O9J2kX#6.ۨwBX˵xLpװ㔪p#mݸSHmt 5>ыUM^( F6-'uTwBU8o@oq#E",8{Fԭ ƧEY,d={iSWo9dL?֞=ds:X WϢ{A8Y8XWm-I{ϧ [m"\Ӥ D xNmXSQ6XPJQ*"F&JHߝjXbR+dE趡^MVUp[i(5*qIDvϹa?˷iԽD~i${u[m4mn!&:6^d@SPE 9#|6ʨ^( F6-'uTg*;a;;u2 Qzj{8CYИ}))g/5S-C9zp|4{4O{x̬oHn&&A<:I%Z2oIoZhI/s Q:OFD~E݅a/_\y| X9]֓Jiº)jg.ㅕk{@mHQ^A_83 BZSl珚6 a?h^cw( P"xȝ=_IgGгk_rMAwu @nwd1RR:| `͆́ rR2App i:3 Ġe/iSie~kX\;6RQ`Y[KFUz|j}{>!M}\UIn8#2a8FCrov!'8ȍ@Emఔ欐GԄ0'ig e9^ JT7V'1^VTDT-X~﹅(yFޣ"IgGгk_yi7U+u=bLz/8\H^Fq٠y=jB&9z- ,:*ޒ~4ྂ5xvF^3z56!ӥKL#[M<'s%QKu!onq(Еc7wj8Y tE@h1~䊎IZ<;ąSanCR2ϱ= $m+2R㌶@M5>e&4c4 X7ڦ8so {wCj!m 72bqAG6Cx3/3Byn(JuX!6Xf]>ٍlgM9?&|͙~S J: sw67W:luCK?+X.)iM>}"شU@#HENKsϣigoKt9#۠`6L$4 Y>H` Հ*iLh tcu7Փw] D^IVksNRq[m4]ЈbD&9*`݇YEO]t-;{U/Cp8OObtM䒻}5JJ :SM {ֲp2Uiͩ*PsНx^CMAwuD$RL'Y&`"H+33O X{O"Kw3O7%OgĢ/F^)\DCM1N~*ZnRc@\\١v T!Ur@A1=hRuzLTĠ+ K\Tȶ.4oG~ŌE$Ѥ\''H(@T9"m {>t*6V^b":IQ@} =`F`Ja|@v ;ڠ)aMcdše{pE!>AiOl+A㲀}x Yl7AyPL-nA.\"iKi p/oyS:PjIjɻ.,DD6CȑAꇎ@C%F^}}K+wD &BQد ]~'l.wvT`]x5fi^#~#l*O?:ނs9%J:Ijɻ.'f#jMq0BDtFMC嚒wn'O\tlG]p:G+hDdqn3 Kl/(hhҎKlV<~ڼBǻk-pJ&>rĖ3.TI0gSO=Ͳl Ԃ p p9Մf2 s#M8=;Qm<˜c$8H+nti- 8k`1'kbco&Ȼu&4:IjMtYf>Ї T"XQ2eacV}wNO@'t#yyF߱K|N ;VAGHkbCV9p&qőatZ1>4W"4;`Jaoj朚HCyiAZ{ (/qX{O"Kd١vHOGe>(X?Q мqSC99n̴9> k|!b":Ijɻ."IqrC{E4!O kZ,qg&ZslCH_- $FrWS0Kd"|<|*,`W}vGT?k!! Rn>Ț%VlTak6^d<}FiɤZ6AV=XOQ,"(0܍W$bÜ2t11^ج'2 SY˳q)s{ Ϻkh^1J[Kkv !\U8&o&Ȼ^KD0,qoVZ`H3MR (]",sS20WԶKŚ*GQWhr>i-Xh /XlnhR[s\[8`ۯn?;}~cͱt>"G{iL(8秽*"~q/iu؉,'X(Ӱha`6*[0*65eTq1vǸŭHlL4ߟ꜆& A9ĬBQeWFé[։*}2#6Bȋk_]=)rkrT!vAHa#Ek'o->'ck ?}MM"DL;^Pů}^PnQ@TGm, %MO!Q{Ū`:-`R. kGvGi(pqV,b) Pt1hķe29Ԛ?&>k:'Qr,@Ԕ$lDb.f3]pN.J\#\&IX,ҳ%.8 ssjJz{}K[M<V 1$.y&@?-iW6覆̵.@ub*b1;iXY4,Njg˽#?tpװ㔪p#mݸSHyaV6[R7oؼ~̀%!UbEؖF{d/YtP!$p3$81~SX)UL=̟D}K(,҈“D~.%-Gc+Vgn sֺNlni]s/w]+D^IVz=#݂鷏=&#]4 ]&5sbzY1TDqLZ5ع˼80kIQ˾4Cb qoW\ .*_ ZnAw]#n!&:6^d:-`R.ywZ w ~R*Wc&$d +Wr&2-GEߐ(tʨ=+IPt9J/0I7EE8F]F=󌟾 _Ta lnuz{}J^$!b6.u/`XRoQ4d(y7ޫ+GI%(M/E5i~2KJLKlzrk_ٞ7迾&$vN7~E݅Q6$9f3, } j Дsʆİt3l!|~ܥ黷lK@S>uB8ڴ)n~Y"Z0iL3Ϡ*Ҥ1[є/вۜ#*⾋C jş;6 c,4B`)D9&YF~~E݅b"gt$Ϊ]e6ㄊ0r7*GE?e-|$VM[ mRMMU#ji$mքY9 b{a!q!01ArvD*~/w *o>m4XRQuڒ L:\SC凭kLHW`ʬǦ%fڲewJ;4(uY}&Nr&/(?^*=v_6c2cNtkv.M/tcWэkfNɏg2ț:x}hX@ൃ#+Jwx\w)gV`\*"CSQ4ah:֌7X.p*ANL))e{{{ Nl&9yMiSs'TřzJI4b.Ƒ>o‰c KvW8hlqH6o~t?($*e0} OFD+cHow]Q5>Ұ*+)#* XYUӡᅣ+W;Џ*{&SheqWNG=9<dzn}앧7%JW2?eO>c(p9g9("Z\rٟ2Hϯ>2?Ӊ*{<7伢z;9]Vׯp?$te<ƀ̬$@>OL (G 29,]4!kC80O|!Y: O.IOaE k>I3!UIS9T{+ .E^LI/78Bc$ʹCJITf:ݾWP *J9@s6QBGP3(jXƛ}r)HBB@s5L?GF|:*jK^N&>N,q-q}Lb!+ fp5pȜ|;w~੟cUN4$ÅMt:,0"P9wm.Vi";^zRȍBz$P55^M/bwwxSǑh#~i~Pb^f{"GV b'2PGvO##!UOZ߂ݚ%ͺў>VTj27 o/l$74àgxM} l_;>(*iCR4 xm41Bfڤ ?Ɍ ηja德' wz6u(d+7Vjm"˹mVENS[xAeF;$S6HyV ERc@\]2z.~w_: +Qmt(/qP\<ԗ)x'mocf;tW0tUV†Bt]Cc~YSN磞xj MU6A|AV(~Zz,ɜ{"‚Lh yzGda*8J&Beۖ2KB8ҊbQZzDN:PkCmLN rD~5b|>|= K| /ZxݽV1﮾tw]2]T!Ug|=lXl c #w (mK*v:VS{f\ @3(~5,![1 w܀ jș)͐8軳r)Vqޘmqt)Dڲ ?H6>{ٯQzwuݾ*+Lߊ!"Dz7O鑘ǁ$՞/¯1hw$пX ٟYk%=2U L}PV9*ПZ!gջ,Q0GZ;DfZ)xH^g` \dq$#'E@`ZsԤgeYVPݜ"pkT߬d)2a1Y2=$ Ckfhm4zߒL>S>dRI3+EE pV i^B|K>CkJ~+3[ s]s< RYT U!⇉Wd}l4ʁ3wQ,5IAV9m7 B7$#󀂙C>rTU`koqsWeN\a>ZVB]z>oLl‚{=vqz:#Rҭ89Y5"6Uh8jԩN:Gjnm&S ~d{>P%IK_+uFq䂛]Qx8|-fR0REH5CuJo]=Vd2h/ 27(߁S9.<'V}i:gVB1a(ޞ4:c테Y=%sz3*Tl|t`K0%LLH-bU} bTE 5 gj6 Bؽ(b=>5.#/Y[jb4qhe66(%yXUnE'lDFy~×'9! П猶یrTwF q%xJN_G9c iGӲ?!}nڟ(~gy\}YFae^}Z:ƒOx.Aw"Zv_U} Ԥ _&~E/+BsT!+b _~gN<_˚>"犠h|xsN]#㋚p!EP>u 4dCKT.D.uƷ$*T޶y0]*ŋew~d&6Y~ܨ-qѠt%'҆W6 <`blWܫKp0Fs Yupvղ!sc+tbWdMaa ^[\1MҰ+xI/!W`"9{^v!'V]Y- DLG %YxV?b1-,Rك& ..h$$"G*hSP]L:T& ?97ea|֍lL=jVٓ$)5]\?u(>CA]a(6d/;N`$~6PQn+rD飯bLy>*qŢ,9'pFP@}sM9hlVK7&aʕao$^7[k$ ! rJu">B >t\0ŭ@|Z$":]c`$u6g dI;c_$r? 'tڜsjz\XK#3Iɾ<x3r׉ /Z ]  pwa}vrQ}M&aSK`uxG@\U]r:YZ*Ĩj+ay'`@oRrFju\ݿm{Pm j-2iQ B{k]3[hd0ŀCil[ iR| n%ye QRX*ڞ(=U`SiޘgqSf?41φ#GH;'?,T]ݧ9lV ;[xUfFgًu+ω `dB_m1zCul=dh;Z Ӟ*_Xp%}Z2UxxT*2^ ,CPSC~Lӣ; "+냎3% PGW||ʠ H[hX$;PU+Bߛ je,72W.k͛媺i8c`F>63vȅKNlXJUP ?ƓAYfhlDؚ`Siޘ޼#94*Rc {#D"al]}3ȑp&,jyP!Za2Kٶdv+ooF$O. H* )Geԁ!q4v1_:+|s0;績pS|)DA)DU]Ob;;s߾u d Eoljtˠ{?cp2hP,rZUQ%F}C[WޞC|G(=u JM4&YӸ a@Y@9XU@,3AToЦ>H,vuѽjďd=z})삃I|ŸZV5H J릮ۦ?L TPM2a"fn Xw#`J AZ{=`#iLZg"CEijJZHt"TَCf$HړY,{mhdDvkJNVڞY/Dtg-h]" pEY":KrY֣}4ĝzWc$jeh>69k|W;z>o`2W9\56ؤ.F.= KA 'xj%_/hDVJPXJ[YkJan=n=dMyNvg_iXZyYuZxP G>=>Cq7Do?< up|jϨC։,I˽GB-Om4A~޸"3R{RweɹY|@ 1b"գ0lm)vTGF\p2c P6`J(vz7x RYIjԠ)ğ>{?`YK`-8"Vpu2XR;fm{CJ$WSyH*Rpi"&/O#=[9"V#60;@t܇Bӵ`iB{^T8 x='n3D,hpyEQ~!&e*tON;!kD$Li+.&pn} i"u*/@h U#݆U|[ W@jfs=kNF֊t{)Ջye"Ha7L z!+M7p]9W֛ػ 89k. /N.KKsZJtZH<0T3o=o4wZ #+; ɧ T!§H:~j w~&X>#S~ܠhD ^DƷ<<stw1!l"J|Fj7gu< [h(b e [ -Z>g1!ܸ q[hGHOhZaR46ISv*F @3JHF"Fuz?&Y-RAs ӷ+/4L8_>$NJҺz\"C}c'pW~cX# jTg ^'@dDsb睔5RL3MbdG=>t#dW] &k 1ǠJ4݅%QUuUFB6K Z&Q+鍇_썞]cGWaJ6h,stmz5)\l-.=|nD_<52 2YM8xJ;xJ Kwq dU=/*>C -}?B3s,CvR#&&i["5%.vYenfeyZ)n[To6 3)6.SQp#_o\En?~k>+ K+Aczb81=F!i.d;ڽ[MU?X5|ՅnX7V8$h-ImⸯdH#W*&`cPB|%LͣZSvSJ\ )gaq :&0Q'IAc+QH}O\ɮM(1m\AARݖk?0Drϐ< - X2?^1⶙-,@FF|44>Ƴ.)8PtT3];F}y9kc>;7fLԥAUr}N0"W(6^.DT%eHP6T^Ŭ>Q)o7囷lm3["tC3;IATc*淈_B$(u;s(ͬ#KY:pyv +xx5h|> zN}w(# 92tx$“I;"5ռNT_:Zv0Ŝ[>:\!(lEcye:63ѯLyX7$7+ B,\*|:FP @Ub'ȲgykH,TE̞q{%ڕ=Kqu~01<e +*3rk\kHrJwL\ptX O$+cq\ЗP9imRa}T5pg|R峵?ˣ7Vap+3H.(Xf!WTZx+-=Mb_V*HZАiHmT ~Nw-g<6T&wY}3HN}ZLW[<*γ|kZ ԞlfƹU"$pZDl2 9Npd/X&CQw/(R DfKȽRuxoSZLXga`hRJpހeB/55(E M(6[*0^c ENw[At؍d ;n=c. f̷IJȦP&q%slZ#a 8ppliJRnay6m#`JM,/3Q\|r25H.̫_D=;&W$XI;eO#oShw`G(<,x8AYIyQ4&^O~~F~j\Llp2c P9qd3s%vqaP w"HM^T[2 {  y0qUd|*lYKk>TGD#KAxM 6;`B u=)IE`ٴ~ڒIOڱ(Q كgo];ZDk1fHt{0%y5r' eWY_~+f9%TF~ۘd/|C^ $ZvOcAa" )5U.x 0 3!?0lFRX^f H*RpHan !Cdl|0-"\D O77娹*볥2!E}l|oSyّ3ȫE-&EpOHLl)IFV]4 JM,/3ڵM% @ozmx!`~c]8kzj]?dnѱ"X겯P_u,V"B(8ζi74jL3/XWCG*kXH,#Mk 7p\a&Y  J ^, >ExƝ,f$&ӽ1dzOCY#&+M"2P_7quP`*RpkQ'ktd)G sBְ}cCE~#h4 z&{W&j$\#HKJEa}aakkT&m?+:- D6koj~~8>*FIW (3  D ,b0ZLٍ6Nd/\ְY|GTƙgoVYQ]|h`BK(fOf X_Ef 0lm- }J7p\a'pWV Uk5iaڣaxYJh~Z ͸On`&S8A;fU7pgĆ̻jCSbhe̼d)JQJP߯*j:z<&8QͻkNI=U ;"W BVU;o aynGkP f_tt d;Uג*b#~,-PY?"9dM+q"h*ی$q!67&S,MkșgҴuh:ب%L+xPxܿ0lMjH6ʃ:h4MRp 'Ċ+CX }`0M[-19gtL̶yJVay!N?I9N.VEqJ2 J925k#?G9!җ~w(N>@;PHRWC )D~߉TW)`oyRBn03p 2P0FRkZ<X;s_2b/Ts򱴼 N81w;30j3!H230[?B"$[zXWllƌ-Iј_ꊠ"9ew#HL"9\ !}I xeԧ$2MlkcW?[ZK3]" ̯J\xSASjH_e;.Lct] juG24uc\[K3xIg.8B`{ DnK5P&?pwyYr]}ł7*)U^_]@:߻6,dsFDͤ~,JSHwB<4jZFyT鴔d ' ʣOte&0 3d= 1nNs,e ,d5FBL'8>;<:`VFZ.^ q%&^ߎYwK4 (K R'v;g5h/M*H$kc~كuJ2<#Mk :@WKJlDBvƿvZ$_ ,&AB.qd:veä77]2{r(Q+:FXF{"`8 jOs%7ϓK6Dsf;𲒇F]1_]$Mu e?¯pT?]M3 "0l|"Plim5#*OHHX 4~T^*H瘾erN^D;f cof H+8K˜teqĎMSSiǻpNkMLSeLQFOy>BTK(s.RaHB]2T-{>MוzL0q-3w!j0Ӈ\kA4N+"SICBi|αl8pE8 4s4C(!7 ,/B\}ټ̧$8Y4$AT0dp Gt3P= dŬ90lp+9Q9kFvgkv T=!C`;oyiG-,~*J]VU4rZ˃ ~AL9_GqI7&5>|ध٬.|G%[WiiLxw dl0+jRnTk~ sBӏeG+~{%q!66遟b{ָoy1,&Mzrc@Db5kC F ~Y|8ʹ!S\&f#< 22~eG#ӄ-R[:1L5hFd6on5I;xc;~9k|[O ޝ^i\RH/$&wN!nق+$F.s"*g#7W^-]:ZF 6bpf3 Ե]o 2eY ݽ{ } :d6>|B5}UZUTmw%(tQK\ 7Jl[ɲ5͌#E;!`;ib̦;tb1&dL.}銡Bl m;RAQxC-+J麕^= M'lJكc+8ZtIyP b:i&<3㜷#v@C G Wط4볘' bˤt"ۘ>}R6ǽ OM-rFLޱ[:"0, ҄9o?b?*hY`v'') eG譶,{sҕFB}z"y4PWJ UԤ ύ (RnWStڑMjc!Kم8uEC"&X\"pGun""<?C$0*DLA3KY^rtT}(8O >BPD (\p3XGq1zRGq;%漃X="ҫyi G{U>”C18[XiW!2{h$e'H'"$d>"|TBcIo८Eam̡v $#r@AJDZ> #2X f-:$B5X1Zp(&>㹣 Z]p2c P9qͤl)IG ;vvj!N9^`%C )LGT8 B!sj n M2WLܱ1ifL'Z-e? Ivz.kXHPͥMшbi)%?);B^po::K!Yg2WOMB!~,=%Y(WcC1a((zD|iKGƏbȎ,Ns呁Xٙgf] UM[>}R23(ZƉz\H'x,-1yyOB~IWuD>2$Υj-w!h+5 uw"-DQMk 8s6w@/Jξ-2H.ͳ\!g2vb\6;"&nɑͨV*8;"iC,{qHyHXԂa 1B Zٟ}EF;j$TMfp#Xܶy@ #J'*kXH,M LTAI9y_RU8[qՅݳZQvd48U"TK`SvYH=ٟqb^1:0~dt6 t UGczvOo8ժ:&!XH&-#,jĆgn7NٮgGzl{j\}!*] #>Cq PD-؀U> ۳{uY3ur20A4]Fur9w-JrhϵL40Vjt<$D?u/8cY"+8ttV2М]d !j?OKDj0Gt(n^K .1..~E݄Љ?g;BefG \3"I~0$vcޱ8͠#'Q7ESM5d#c Q䟤)9歎2YͫOȵqV,b) 絫 xm4>۞!<̡H$6&AsT%0^\4o g%@NȌeEF&X vӶ.yBg̕YjsXr]};.ے5Y'*ŌE RȚ{@4Q)G9nyX~ 9r&2-GEMy|sG*[ YpF}3zhe|[G9#ӝN!/Mto?@6w^%tPCIqt/#SQ5 UȀ I5ɝmk9KL4uoקj'iZaY'KzbnmsQ7=/Q8i&F.J]_A-b9_ 0!HW76VƵh&1u;yx:x}hX@ൃ#+JwxgLsw67W:lu-AxU<=1nTT/5Jx+~s썚eF/0쓎zH|Qf`T9S \9R>U ސ9SG-nPck>UX6֯5 U¯wE' ؔ 5J`#M"|vRa}NJ,;U(}Pz w襉9؇d5,LcMo*װivuXӾ]t/G2HP*m']`]Kvl}Ih|-|Yn g|(Ƨޣ"kh=(y(=UFs7ޢoۇSj, ]cڮБxO@@XCc Ҳg,ḄKIqt+|4jh`8   Ӄ[}y\WW.:c[䆧r(!l"Jex7F,KfDLt'׾;ڄRѳ?bsYI7'{믝M r|^z'qvX1..K"k}(+_4uTwBU8tԋ`Ýw"c"Q 4tP|T'^f,4^y>hg'}77% R?b}ץ[rjR'spmIR~*x{V0ba*8J=?QqF He@b*8 xGr+ɿoW;sNS>k)q3uĥA2O0+|!"Wcr1ma#6 s5DGsF? {d ("A~QMpVjZtuK'(KOqNYQ݆qAYB~9iT۳u2Wcɔ J]-8,߹7imth sK9A`鋒arN!ʘ"=gT!oZrFmpԕja2LwaJ"'z]{O x}T uSb9uIJ9L66𙲖Rhc9"S,`dbL;9]2%{.LiGX%Q%&4=Oo߹(Ĥƀ! Ľ/hK(.0C'āht Wj5 NSnKSxvV4V{"HuO_(hwL H'#|X봙?XH ~MP?7omu3B,a=DDxrb@E?aH^;2=Q=]䎶v>H eq\mXҡB=;G,º^ӻ1}b]BwI Bf\0ҏʰK|JLh >zR;s Q/Iqpꁓ@ K$FpK^RnrlH 9S":s .Ld|v)r2ڳ2^^ ƿ8;³Wr~>}IqrmS޽,DƧ䛓=΂릅[]9@}ArC<F{d/KTnu!G%&5%AqE"viZ7H8:Y6Sx0յ4qNM`>̹3Ha`!Gld٦j:3RTyR zM4 و5yaE1 s'akrҩ c1jnd\ENz$;|'n*\#Qq߆xVK'lr~Ǿ]tаZk(ϽU8-0~߳SgqV,b) Pt1hķe28o5Jx0 繓 0~|ly#[OE¾NTp<_ y"} 4D(_sϞԎ”EKRc@\\r"@r0_DC/AY!ӁEtLR&Dx׈N:~kh:u:. SbX>riz9+(lr/u=&6 ڵ^1=>޷T" U`:-`R.ݱ,Τ@j[Y>1_[ .\pqh泡~%iZ)~;#ʲ>0TK90%o؟jawf< [gv)r%Q0ҏʱ߹(Ĥƀ#0Po5&9n p.^[򘋾{ci@\dsf EKhgS.jޞ#@J!]Tw M(ˠ;Yrf>6<16O_\BF;=e` x.G[gxI+?m\yǫek&3`gٶB?Fd]f\+AԢ"`d>nG%&5%A{P(=VٕN5)6S\ީs`r 4D(q^4eް:/ xGwaJ")1..a,,TW ]+$4ziAˬ U9OfPn3PflOI!*aC)Aw̡x>0]Ƴ=O.D >70'UA /؛gy~E݄w~*=Lsm`nT?gcr"-d0fd {$I{y&qѩ"0>"Qx':H¥e'g(쮈|.nh,;N .jsU39jحġӱD'5}rfKi pfu&V CSQ/Iqs ?esx3Y-1vRs An-ޠa0HqsZy!ߏ=lq1Ӛ:`!GJ?*/$B L?{sϞԎ”EKRc@\\hay1"pZ^`x,S,ȸ'd N~V8˾w}21fxYR #4Kz]!l~0y3NSh躍شP g^檙n+ٗ&i+i3~9UHb.Eȡg$o7 I]v-$~*j%2$EDƻ0.fJ*IԐBٺ_bԝ}ttgl5Zz\D\w*sR HA!u`!GJ?*/$B)1..w<{}H)DT%&4^R{庀u -kÄtlsGn$ta^jmxSKRڳG%d(-Ƽ0CQMef& G^C}$,r?3[!a'kxyYm/y蠿DŽ~CFF%k/OyyC߅HaGcA[R$D@ {EF+|mrCVbeɚC ( >Ĥƀ#0R 3<]}1)Cl4 e0CA#¡BkϥٍI[կ(XS5(?]Bh65 22Re7ąZ/iul۾p;-BLsNE|z,> Cp9W Cg.LiGXӸ8&^[a=a ln_sϞInjWQ<+45uiqUυ$8햴$?-R2|x2֢zs/F6>Fv?PG'x˓4QV i@mTVO)DT{ܨ]YU"E[ [A^ItjxHRA/?SPF~emD byR9%%Ҽҧ8b̹3IrL vwxvij:SȗĤƀ N@;)ix9tsN*$cu"@z4)V$uR\ As2_@N~\s95-ג!GJ?*/$B L?{sϞԎ”EKRc@\\hay0͌oHB"_gB6Lǰ \ 2C (jX[=54Ʌb1&˰  #1ΤQ ߏ;L-frձ[Cb:Ojg䛓=΂릅[]9@}AlƫNS;cHKmʮx=hWyF ERH@6]Y>"[:@OGKR}:S۹19*Viur5'tϽL/4QV y"} 4GwaJ")1..w<{}H)DTs]J[ۆ&B3h6V)QP[.1GCr( ӰUMnn wLNRZM x' %+g;l ʥe+E 82~<c͢; ya)22|JjFm !eDߊ9;|=l_[ .\ppο|i),*?Or?OQ=QµRuBZ{uT$4D(iGX%Q%&4=Oo߹(ĤƀI+fD8xKRΟ V]X\O/W37Lx5G86$KyP̋٫qJǞR) veɚDޘǼ5QV)Ԛսe-($ F5ދٿ%/HBPn] Ϫɿx(~4(>eJ92B5J 'tĆuj0 zt"OњY@9zD3$NTyk}^f\0ҏʰecMg;l ʥ-[mza_a+Wr%4K ڻ82žgb44q3[_&ͅ}rѼc7(=Xjc,ab(a; lrЬ&3<DT z|JLh >jcY5mDЅ4Py 5&[2GWSF.Ȅ\h0!ˠvʔ<.|e'?C/Uݍ筎&6ݚsXt,U<>̹3Ha`ƛjc;|=w~*=Lsm`nT?ONGΒpD!Si15 $BeG-T֞{vr\|vR^h|sl%s,9D732:.V;iq"V+ٗ&i+i3~9UHbO>N=U QJ<}BS_xk˔`K6gH8G dlpCfe8A 528ݬj7!GJ?*/$BH:xmQ&&,"7cTuj?Mblʔ_n{6jJ氟օf_Yh+ze  / :vJu%J!pbwB湚P>s̰a>kMbE<ۢ=7]`)jPbuT r٩nKAi14ryOT+J+Sc ( >pE@+/$MeIHt !~U+m=`dU(?F"M4Sk(w anIB1PC@:/qQV4.N<:bʃ 4R8 lޝYrfJ?*]ƭΏ/9XrDDAy%9; a`!G>]\jFB(lr/u=&6 ڵ^1YC>,T qSRKa`!GAm~Lv1Yo{|\ȭgo1H'f7YǷ3/G <wC˕9r1>;XmY.jgGD,,L-{#& ࡔV/;XJ^$vZ/6spjV,YQY`2!~U^HL?2taalw dR0.E#~{#'|N*;q!^HC (V4V{"HuO_(hw\jn,o9xMg]X;b| "/K$; :?@w/\Ydѷ ՙz!zkWMzR5<\vf iϢIL5嵞ϘXoESݙ53QΌu ]vW/ZfzCzRgêK$ GqĂ+e'i[ -GW'&`#Wvp\2]ĄP*檙Zfj,d?WGBs"8A4=8NyN x*b5L4M} ڡLOl}KV"\h2AݖN~׸S36 nVYHAB޶"-~:<8? N pgYBkP=ՖF-Qfy6Ҝy.u`@N+s'\\bNlL*i1H9V˃UC%Bs;Pc'6e<gӴƭ?sUR\TSlLxa,H4׏}=.|E ELU2GSz&k@-02֚jX7l2uTuPӯc-T G!} HDu]3['K|ٰ e.WH&M5{8vJ4v4\s%d{qc=}cބ.~HZh4nh'zwNiaܼ"!d7{4UI,$ xnw"J?[¿0.qv9}J[:Κ`?A.CkӭrWU?U,: ǜJ̿{3*+IiQjĎX5oA!wvEmA(jM?e`Fr3xIbMg] O&X.,AC\L3TA5ޝq^F~#]&n5ʍmߠqD,Fti DO@Ū)jHiIs d$EcR6 $Awv)G΁BnN6бg}yTqb>cʶ1*28f.^O,қڗfu %R*|eeֈX_B%\yI: BjZY)D,G\T*`~W]ԃ^S A qa:3x0Ӧ|?)DYTZ̾+L+IqMzRt#!\TAUJڔqYL#ۗ73sY=Lf8Ll-*bKl6@=s X[C*|*!3$l)rJ/{߈0K?UvyJ9m/xtY(l^=)j+YQ\rèIpTeAf$0쾈c][SY 90y?p\d>r여fߔ ɺ(֎>#!i)/LA jYQ=凳ۈ‡ O4p{oxy6wNukBBZtV9wh1H3(?)G}љ|P480$ ؿE DF bsR:I.*mj6:SuF/ Fq!68Uv٨v?3W̎YoyR6 #"JMKBIrO<8f`S/IĨu2]k<.UjL"QΨ)9#HbʂnpUJ;N&8/ Q4E̸LL"—y@5.dV*N50KqAŘДqտ}Z6I\6lj$ۓ:rڂ=ӘiGzI}eUb cqfLs]찮5+[X<FCp97Si +!3yȱsBՉ2ZaAx~[8Ήׅ#Th/5J[(W t`eங=c(\p۝Z]R4I5I%X9,`bB~^Lt\<< <'fG֜0R3ok{p*W \Χbdё7 ݐ < [)_3u,0` pQN#&ē S`~;/ ȅ 0J#t Ҥb JePq>2XaE4s tD'+t`BK-qle`ߕMrND9R'ZeM)F_žzL ?n{ccOp#i &s 9/] NV+gMZmQP"m`:veXu4sĂRRRѾGCi-wx(p,cuzFJ] .,͵4ol5'"wW@͹O)Jg3H3v2wqWάH⺊.^qwNP vjM.r)$pbbl(t?{uݷFA cI5\a6Q9ѱU E(nqnysHT~0jGfYԟuCgi/hxڻ砂!}Jduͤl)IFE_e!00!zXK(E{"×tQTp[MpV0FO?C 7Q =bɱ2Xcp*Ϙr^F)jpfG"~ns_szK%b(Q ʇ"=&P[Q!נPmv':'O]SL)ۂ5 [Fb%!XGN'l `̙Jݒhװ Fǻv# #ɬfat/m( 0w65mAwb$-]d['j2V vu5n\_YSR5#+ˣ5 PER2i?G6g gУommM4}¡%wĢ ߋڧcH p׼Qh'%dVJ+ܚsA 듶,*tKu3^級ivBJ f]oxwN-@J9ANdvZH:6o- v [=?&Y>{J!eR_KOZ,g Y~lv'`^.f6M(?B2}!FKuhJIx1Y y7rQc_Coy- Y+B47N!L|]rW\ h^v!ϒdZX"=vdRW? Fr"X&үGxJOp:DY i;hP@LQ.:z< ,VqqGhBgȖ`]# _; UC f0o :9c9[[!G0JL3"z?DLKLeUKrp" U% ?YEiA*28f.ʎe465gչu i`"GhaN)gF¨e'ŞZ.-&r V_ _2k`"mQ/riA:\a$܆WLkXIOy͝s+AzDJxXs{0"8jUC~xFpP8ь6a)晴}P(R4 LyAH3:8'H U[;OTdUˆb%TR'yCf79UlX 0̴W9N^ʸ'SҔiy/;=d,AlIr"д*Cpz> 1fF 1|B`"eo Ψֱwb Pk1Ć΋\Mꜰ x?^՝C-.J|fLÞ$*.epʠЙ/+!r﹂1}bb6q$&dp-4t09ibkRa`$CqÞ70Û>lX3r r=RHUN7;.q}cGΌ7"πd_m AN1;!Z/\$C}֝"cבC4yOzb.WS2RSaX݆5붯DT7LZ7H- p ލ8?uHkc߫,QCrBwE p<){iD&J6Pt m1pvR:DaIM>N)#aa5 X*pi뾐G~ >nA~@ظ AR 2 jp@6_Cu&JI}|BRs{;UXWNc2|qW7j4Fw̿)6K66)@ADN7 .`V׵9*UI N:@_N}x,M S,^IZ1Sܒ%Z>5­3i^5VC:HnvEaciͯiFՈ͋m hc9iUH+89[hӆXBt~%}v+=0xB!rSsC<7tA=EQRbRXM&tLF˓^SQX"\ ,SX1alZ; ʣ_N.#R܇e[3N>l#}WV6jE@ `EqTP[S CsYC(#0*Lj<=PbGxP>ch.1:w__w<,'%inp)6RUE%#1&aye=F"ޖouL_9,|"/ɑ["`m>f*<."f}K b4ng2n>!W u Ri-NW\RN&R7;~(-'# EuoA ֶݨ}]Dl'0Ǜ:1e$)dݺ3GfKU+zRƛ8}> MD) TY6prLpGΊ/&Fl]J?J/4nilD~o71k~Y،31~E6be x7#:S/Th$cF`NP+.Erx[qx#E`j5fJFk*Ǚ2 4٢L/ϟv΍tj"t f //ͪ;|DPB)`-Q}]Fؖ.ݞ+^` bv^)gǖh9)[(?T+r2A=nM#|'!VsO 7LC7pКIMC\j` y<no.RK> > OD}jS<"'yO|(:\ҕFIE̾ Hh;$WFx Nt*GzI)_m.^d=觔;TyyST_$i C.iMe8&wt@,.BcK;)[jj1 <$B#A|VKY]۾V0?]}+M~w s*g%5= E׼jrf}JBo]5\RPbz3dmN(*Ii/02:wdl9Ha|HN&P[v*Jg=ig q4v/SVi9cvS)w3㏐,2mٖ'u&W s1lQXrㅓ4B^B <3:Tz(}Vհ9^\SDVQ;ia(1)f3;:(nsL}7k`*Q=1/^}~`_|mM(LEOf]Qæu7يӱR6ZrTqIU 븚ptz5R*M}CH>O؋>) V2:$@8,pR"?qANBF+H|M|!!z BZ*yJ4dzm' t+zY]%Cg9P:SWDcQoRMeL'fcHSCR {-k6Qhc$aYP,I;̖?Nb7Gul0,stj2QwIj˘sTjx4Axnne_X^vj^ GL(Bk+L0A/?e:O@Qog łmT_=X߼{ T"ۑ tN?Du2WYLyqE֍롢C1 B }kFBR##7nvwf\6~0i~( 9$ >75B339-65 .oVywjgy'.`:>lבf MFD*2@uRby0k-9pa|N2/ '&Phj7HbT 2u 0Ysv( YAZ~@!擮Uהbnq=#3.t4_.!{Wzycp*=@^(^ar;#{aa ߿oP#TQxXsY#(urEe~H.[+("fM(O] 3oa_u Lhw`4!#qizv}R%ˣO[Ȼ2\Lh'T2 ># lY2GE{.&ecVҩNMϤ(1oSSklDepW..42W(J%,ѲZvn }fI7KQEf$/[ÒsCj m#cLM++0tZ<\LDfcL#9]q k@@9Hq*OTg+C E:gjwM]@>M1hOq!;S+YNƚCx eEH|BA@Z@Bex%QJYiGs'0TIXیR1F ٬jkRDYЂ, yE8\mZ( *OuQF4$uSX BK5g=xms{ GOE1Z.onf5/e L]%8/DCSE$?gȴ!(Xc&(툺҂!VͿaqd¹tέnt8,uX+s1 ro{(UY Ak܂pmV'oFV{hY= p Ca0)Shn^Ω My0L`HW=؏-!i'KNek(̇[:  L2_8|,n9`Ѻ;psdôBӔ[Ds 'RJbnX7c.^yw}v'J5+άPZr -ʛ%H-W5CV#hZ[ܟL]MbA4vD&C7 ~ͭqRq0Z$^t(չ#d3y%GQj|:fp'VMABz+G0=$.<^Y^y7U_1E: [3/\{Yā7`p4g ]1G j/4;y++< QvP@}2 9g]7~W7qxa2m6൵ii$ܯY@k\FyN̝\ԦKn0S޺Kni/$B|Z{,H;V'ۙ5\a㔼G 7knr", |2Qd2j螠ݻTDZlArZ9H{ q_Ň=Hޔ“ ?7>zp5Y"ћ joKK/^6e[!iћA^2% $p}d\, 4&*qˊK>>g4l3|+9R$wnxТ=v=}Aݯ襟qa./SG Y Z'N֗EwW3C0y!o~0U C{"vZSqyRS۬$_,eSJFRX]R y_< -_THv#Q0ҏʱf 0~h`js40q6O?#E߂浄%Q0ҏʰK{ .5q|4-AXH, qqRYf{neExirÄъGVȄDK=*T /K;EX n_5!IOy Bhi>-p,p`U?Fj͔>V"{& TT&?C˓4QV y"}4gk|;{IE zsm/Z]]%&Pxc|'n*\A:tVւ=E5ԉAYu?js"Cڐ<7SLʬm_Kv>D9D7F6 'eLCa`!G:nT8J:]72Pn-mbvqEĂ.ʚ" 89k. /M~U^HDޘǼ5̹3HUp׾XdHa|q$pB֓"s/ W$u*BkKnYfőch$kRU,ԑ9Ξ+s"A =RMweɚC ( >vϾte`\jݒܦ] iP8+ktd)RFfSHK[6^sfB!ʔAWvi>`gZH  Pf OQ~Y`!oT"f$>cjWLㆿj2!~U^HMY0"a^L =Z ǨJ6vH#QV iEܑhޞQf ŢEؘ[:G5m Y$Lap H{+dSLʬm_Kv>D9D76T&]W.LiGX%QΛ,D+(0k+I\j0ZU=)7ֵx6w@jc,a`ƛjd]m ],M*X(.ܵhɅ1#s# P:}# RfXJ?*]1ƭص tK^?q-Уh.kXI<>̹3H{ .5q Zk<~2inph=ZQ:2bCCnṮBEh^{,51ٖ|[at1OKy:ݴ5QȪ"("d%ET8GD_Dz3xơ_Ȧ5ya* V\0ҏʰKa`> 2$ ͕n6ct \Z1Zjoʟ4U 7p^=BT9'Ʌ1# /G%| @.pQq߆xMC &@Vp&bB72d\ \{@.@WaN\bhq3xΠ`JIPYv\l|& Jxh0<6Vjy>xd2\;Sho,dHcucȻ4 ,& uAtP7wɎ 1(Q ܄dCq ݥNᩎ̱~U^HNӻ6avI FߊɇrFLCpήz x*8{1^eR?Jvճ̽a;24Dze'=fđ1B2M" 88ozsm/S< >QV iEܑhޞQf $~`qp{U6֒oo!2fB<{i'fj޷YQ;Sp%k|Jh<{} &B 3JEq2殟M2nt,* ϥJK%4E5p "}07p\a++GHxu^a{XmiXJ$?B4v`b$tsӼf&Uv>/>0`jܔ}`z_1 >lwN `l&F%y;-*|~iJW.LiGX%QAOf%eUĴPQ.~r(-]q; Nb>^_<tFt*yb nG.DF浄)um LX.+w=Q^̹3HUp׾Xy΋WMrs,]zǎ"Wx;YjmhbO:`HBJ" "<|]vza{OU)Es1F  UY-ڇ P@iGX%Q0ҏʱ yYC,:(h!-N͝DImuH/VrH?2m\%~U:Ўbȱ!Ʊx P_MMU y6CAFpxLk*0$j+kb}d \WyDȦ iqN譁$9iLK;0_pM]~ǥG6V#IB*!nG1ͤl Nm '|(F<.XmY싹"׽=|͠݋E0ujۺՃOL  y@wNapw  |/si} } zcR^hy ` =){ck\brEVXQ/4XMMIi 5x`;f ET%L=Gᩎ̱~U^HL?*EpX5L"X=ѳ~7"iic Ի%YB g{Z$&/#p<_ y"}ozaScJoMe*nZ1c"T\mt<Ůށdn'|WCƒJRnĈC䪄0 ]!5=PE( Lve4_BacQ1 L;4B. RωeteEξ }FAdnl_|Jx%; Ə%p'R 5$]'5}rfJ?*6՞,:A~9|G-.GmOy ΦvÄ8cY|WˎLa%1|.A! %_ACY,ѕ0!G^юr7A>:O ^>jNIT^Z^ u0a`!GJ?*/$B,UN'v&|eVK~׆Bb9"#xQpMI q>7IfWڤu0Xٖ;/\a|Ȍ$/cѩa#,M3˓,: HlcdB0ZKAxmxK zw:Q![~Q tAgOxKf|Ǐś/ULRrNYu\y"}=q`~h]\jVKCv?):{AB/m]-@cѳ䱌t $pxڏhD-J1ClO%}s Oj1ͤl Nm B)tk?4xqxYF72V}õR١\ut~8}j, hAx^=qhF2(>NYx^g %Y|hU&i 4D(gMW=):M{,," *h) ͪPay&# ]${\R.wU-noYg/;X1 ;[ SZDAg-e? ҏʰK{W}f/ΣHcgPFY`zJuzM|v=︮Gj= p=QN?z1)Z&jv0:D{ֺеN/$XzYx8.TְFAK݄&uBGᩎ̱~Uc~KR pVK4,_0׾4W5Hw/Nz+ svgK68k?}H,tWNgqp;MAGٗ&i 4XmYyBBLWOh7!/تA(N[[`Z!ѤǡmOVLdXaSa=vz nyE%8B`ٴ Lve4D(iGX!iB#C ]Cn6vj*$͝J3gfҨL٪t;H1(S+t'KLe yhA < (nry?ң7bCL-{#gi".(!f/KCVqЀYN:QdH9SBErTrw7Ꮃ C1( >QMN"o4zNP"/mH] dx$ CSN4-r9?W1ļ >QV y"} 4& Ar ;? gQ1r ̹3Hablo@S3l+NNa6 )Cᓥ50!)7^uy,g?L`!,58ڢv  +BtD{A~x:`:rbO& 5$DpudK16i\۴Z|'R:Oj2$w>Qq\c]&(, YWSZDAgLa2ܛ"tsIiGX%QAOWрZ}OJ˦)IFȡe!MCW! :ҽtG·] gxI׻5yX"hǒۖYdFTK5"ۥehaئRt!eɚC ( >h`D2#ZIwpѩcWqَl f\:U(5,PZ,`W|__4BdM%{䯁ӥ yuX}~򗤂tDyto>)1C/Tܻz &oko񌽽L)[$NWvW;|բ{$Yx^gFQ cb֕VB㶠j#PL:\Z]JLK*dƧ>Qup*ٷyiP?a.^GXkm?Q'bX}BC?w,ȀIa˙L?@I!mx:ٍ0]zI@`qz]Ѻ7nƤBte(d/`rhLOe6uzu}ΎgpX,*7>c[3d"炊Rwz] iZ Y,ΌF3j wsuW&rDMT0%~;Z5;֙ӝ]CkW#6 k Z4Pٗ&iozaSEƮ4`R: 9-W/V4Jpg:@ nGNq:MWɢg+U #[5簘fO4躍ҀSN 9g`,CPs~dQ:|_tپtÞo3@Of?+G3f' jDog̃z;\(D*+pj09 G^ٙ:D1v6EhzlC53Q}j8>x 1\RJхI[gKO-lr[fȴ F65lדdp=@;r€_f9Yc-n{9ӂ{ɮwۄ>x4-X|47U$XF:SПbͪ,!B r6vL/] X607Qkdm9DtbHQ5nYe^+MȂjf͑8DH52@chdf1>@ RWhX7%癈ͥ )us$aAH)2|roPzXGdaUte%h:1u}I.j/Z_5Q~k2 ^pnHlWïkgfv.<v,S3I񗮫K}/##I ϳ^^πd+E$Z9EQD}FRzyZ-R+n[.*POઊ[$]+Xrc,lȳAYJۧWnbC?`1r=!7krB^qidZ_KJ,6>G 4LԶ=4xMb|Rউ@'13BGKjB0ZK%rI9f7'Tu?y*j"N:=$㺟ЫN3vn ?LC$): %Rۚ<"#& B\7kZ"Uy4+I?M7T.kl[Dx -H=.m5U?KV KO}vY}gySYjiʶNP*aAXenjͭ6y2I !1=C\ ]ưbv{_dB#FrMEp=WꀒB-&۽@AkNU).I/7'?i&Ŏۨ Yx7zh46ĚȈ.&;v dHǂP 0"OhIeMeI;"7`NnsnU+osMڏn,#Q G ۈY{H|n!sH*uL06cn>~cd[0ڜ+&FWPT'(`1ol1\ -ͳ&H(l}$*f ǘ|U! B訛-N,EGCa||< Nkv"Cdk [G2;7>7mOmY9 cá@mחLQ "Y:Ʊ|]K}p ` 3"혽Ѓ 7V~iFFMA5حH}#0=CwPnʧ><}DB* 7dž#\nCJ_NЖoe`(E ֢>]w@¢&qNo|n}HyHZܲlwZN/ʬ-nʹ+h0AaNp$42`Kj,t 1Tu GEkunʰ(%;@ xDw79chn .ݬbe~v*<).clQ,3FCY&[josBATp}k$m Gl 5jS_8n紖y U#&)Uj}, [S__jl~ Z>UFy}YyEWI`(sr@Ϳl3l.kFП ˖%=[ DC gΚgRS ;yjhr\v5"pSIH|*Tl3•0,=svbvȳՒ]MӼP7V&,Qv$F)1+{2ߒ =Z./3S[{>$B(AGJFz vݞ''D73\v&fST X[t.ؑGVV%#.X.ܖxuW$ՐPzi9S-T]dOPj{Loc9HuEuGli\)GY;`tb5lBs@fii`vfHE;l>-l&!6WmXe<\<ۣc8"ܣNq_Q2;i;I|RX6?7~.ګ. Ρ]l`I:`+˘ &~u+A?/!ThDJEsðwpКi9+JV4.2E+u;iDֈImɇҗz?ZMt4{q$3L0/6qev8m53ȉ,`tYUΉjMT6ڊаfB GG[+S]ZAOܑ).Kp{ OScsd,Pu`ȴ¥Zx*BT\ ÔkQaH~ApDe#H]ċb.wȻ + G.:cvs/Htnj'5 ej0|cc\jﭥ}Dy9p@6QR7&rZN>$Sr}T&V mRŦB$⿈f*j &JUs[V¦Wf\_i%BQ\fZ,,/6D \3Xi<$_jF\9FD!{RMp`%Lh>P#,OP}SqW&oRb OT ()-8`@/'n(_DQe|qò[}>x+x } aE6hWi70̇No J}X} wW/Cg,D8 [ʮtLWj &{S$Jcu˽H)ے%1Z0N1GHxauZfj@HAj>;g$MlF?Co)х? ""H' EW*D>X5 ]l  Rc;je~"re된aO)㖹d8aui c>E")1v#jD?"yىLsD14Ua[EF"9,y,/-&? /2"h /*~$l_#u'lgj!ă^FS"2E%1',oi@jј~Sf3S·c@4Ì>K @p< )Ҳ36;W'v[^xN wķ̈R޿\! WmbSf >TAf۔72,b!Y;ÎDG5NQH@[aiLTō5*jF#pl\Ͳ!ݖy? *.r}_J)6Q_Z6u& lM_ijb8a/D ^Nu5 8 N5; :kH]98q9TϳS0( H[gc);D`Q̩ "9C,7+7dޓl\tî,xPLy}j~qgjI@6A(ar\1$+HLwG6b1L+d<0Dɼw^E|~E\r~j%\WSg_ߞcv 3[¦ϖdaȏQwc1#Wt'1 >5\d3^2MafaDǰaZd;}tZȏ_E.5)ˌ6;O;0%6cW}G(:}\[>,^x 7UE^(n^}5#I+f %m{WO ~]T&!ͩ,7W*Sf .LE;5,_r7Z#}6u fGֵ Eڛɳ'̂恐 0FĜ5JV!MC#!`yGݙQ*nHǖSS0 рuAt$KAUӿam6!]F;r#p'Y (ԽrzωM)>c51r< V:yE;W)dorUD7n>zPA7,4.Hr4߱ nEN, ٪ҥ,>\[rYCޭFف`!>2R6Č6jII?OA|o_v_ϒ>02E1*&b f0WB $q^ 9// ,UW+sMdlr- X7ε֬O|N?YwL%Ќ^GQ5@Wy@^*lT,|4呚%# VƊO.ltoފ\K}dw))4M7]ӊ29ʙs5/Fv>,so춟/X b 1PK 2{̓lJ4"[ >yqyUٷ< TA E\3yWrm̑X\g X|{Cl!%V:IU1P:k>HFX52ū=-^=>A55qSOSa*²=DOp6EEK4Q k=$AYu@;ڎ|c-Z$H,|kKoV* :XD %aNh\QS&$nt>A58_[6 6.OlDNjd'9 \W?r3MCz]תL'*ܨɌv9<`[ ΏЍ J$ Er, T¦egĪ|Otj>1`xvm ՕVe7zR5eOFֵ y7ʺ#SܔwN"5Ɲhd`[p6?MNvM+ӓ]Ut9'\0p,FnwC_OҜ2_FwMDnMƸD֏81G!%o.)6l7I6Ui87 hϱ#Źi9W{FeǞP\($DCVMwYojK;G*8U`q/ǣv[JKت cpkr F:s/P*v3\r^㍇Ȣb}{$pM=ZoyLSЪ &`Zr(]]+F4} MYfPSoO20k7NiSɴnb!h3<=o@ch$39i'?~u/xu]Z/#;7IwۙnNݩ :!@,FeiHh}9]A2)=JuZeh?rxq)̌V2hKQ䐘1OUCwT7M(n]; Ş 8M2DjbOWd#E"`<B hH nS}ҍ4A'Q& !9N}ằGmH3ncIot̥m:1i[ݡcW@ntA!m:<\3.+&JЅ퉼R {Jy|))}![Mpք%YDt PYXAe'MY\!B7p;$*z KoHeJqJPg umn-E|ΜYn |_~ct3О#`YDuF9凣%5tfO}3Ͽiܗ4Oקubܫ8t.%EV4Pcr{˦ j-dN-H}5͛0d㷝zAӌփC~UĮ,ҙMή, ~D8DT|>I ˆb?Qu>"7gk(>'t G f]N ۃ6ڡ%o|d"|ӧ%Gmac£5%be&^dCLi4ŷ`L]#d(Eb,,̯,2NtZ n7U~^)ٌ{f.dwɏiM<&aH^? jjo2zE%SیZZ+ xҍi0 >R %ZH[$-V(U@8p!:H+k7Ȇ>/0zRJޛFm.Y®Ne!jxL;R+E6.G#:և-;db˫PumM$}:?)C ";]LlJtv8^s)Y-"mVuxămR %[; U/MIBMpNiQ%f( $%$5QR^=C^=ޮoL\֊2vSHE۾nB!Q6w ^8 ,`0]&ᨻh{'~`vtpp,xI M>|Յ>}$̯%s?lG+=ʮ~k9#R@RN`NL$֔ kXaJzᓅ((01YCICu% ?47B>1w&DbBk*HD3\Ud>VxI8bwdɻ+ܬ3+PjVƏ/bWŲl64*ݧA@,Z@=}˩mPWM`)Fbaï̄r Ln&@{tnf顙93pN}qxFߵw'_e}VQg_DHN6Zr/r6cjrav$Б ?lW}g^}w@f"pU'&"K #~M8̱%|lЅM@Xّ^djq:i8ãuM% G)sݧf˄JK 2W*.5fՆ sˇ|ӊ}*?6ӟfXI,0`wEJ%+fT7ٜQO7 0EZ=2l?VZݦ3: )jKg66HN՘0a,FirTm'z+ܞ *5IYgg&.䘔nȟT'-MY}j]:fMG:*:PQ5YmKIP每`3m +@2Wqv&QeK ߱L˰TȣYV29 fBے\E@4o)%OaEFhqV>;~v ues`ci)8I wZud)M6p%St?K$ťI,^*_\k<&kS.SdmH<٦I4-5~9p?1K$[~_@-p^+侵x`w9.kA#T:X-~O xڻӧFY&$ TZSK.0|d4b lٲL Yl{tDoV:OഗTPk WJϽ^npbKU7%GҲ$T3`8%o jntJj6t"p:xڇ4GenyE #KtzrѻI#mY9oK8'4P್.SC`l4Jᝇ;b)4=F~7yK欻g<OŜ^?ϳ:I9%3D .b2qv"$8BSW+1Vn/TI|zTo20iZ5`V|pJy->3A^MAbQv/6LὸwLB}5z޷ ̊=kiηG;^j߰Ty&g=Ӛ'&/uvaie^1|#2.dv"FւSo"3g!h~ET0ܒ/Ѽv+{s{#^,sT'+Jޗ\mwgOj3h4/S:A 8-cRX.~9C=ڛ-$Q-~Yro:`KiڤΣ2aV䏹[1=6Z)?>g=6?5 .x-Ml,^ |M+# t6@ C-M Eȉ|˞\1mfM WfDx+H;Gbm㓲5!i ?OбNIX P09+,hq#|gKY`D]8oOQx4k6?(0h\hHWr\!JHnkWnW@I_!";t217aRX͔C7jѱbV.|3%~[(2oyf5_zf4Vt֜cKq6Y}%¡)Mdt*La6G>ȺMs `Țw\;,# aeQ ,-ajhP_j<"Gc |9 GT.8)5;씋2${Գ 9~dGZF㬷3ypmi~n ے~{#< bלK$YQ c]|=-G~A ȈËuں HC u skk.ZlBf>em iͅA-m6 !0q\,E#ÃF'铒L$t}9=LQ˸ы-q8Q ʚqͭf4t&^ ld>wBL01t041^x!D4,7Ȯ+D ~F\[M)d9Iݯ=?D [_Ȋ.C:RHe-zoV˝V.7jlty1qekVJ|6$]]=q8, PM?*ӾL*=Ђ`LK?6ܘh#]_ -GuD2?[.20$^e׻&ΐ`,wݎ39`ze67jsr0+ե D].a?åU'Qv =.0leUX_sNb]&?Pدŝ^16 &Ѹ(+g $\2T8.e.E~ݚ6$GlTA)_9\yL&Twz&ohu;OӺ:ْ*˓2~/3m{ޗdC$K ev/Ik|BSZ n7*|^}IV  D6(SQdVԋ4@#\=pd)r -#? @qBg:KmILg5'*ET W5܃mkf `:I73?&WE[;~~f7,Ir•]u],̕XBHEGL:>?AM 7[f0 $ CCMfkӂiw.oc\2F-O5Q.#;5b81v;ǰ-1; E/fR΄SNQ*`z J8Z94zGh{j3 sxѢݎ(ώІ,Mep7;_AVQdT6XrvRIt,9;*QDǿs:¼vo)l˃{0IRoB~#O'1aHoxLlJe95m1`9N;>iCMwG49bh~|‘|&+/sE,B .ĆΨS<"dS / ] iޭ40e%:]uMjCyHl<@ÜcvVEjz9 f&_+33φ!R'{h+"Mhjx}GC#(,-ך )->,^QSź`NA:;KH܃׀f[}%iyxki' (A`cGJ~A0EGcVy׮+#kx?UҚ(-dPѨ˳Ig,Jw?I/dGKO?U8HkfTmߐP~ X}w ru8o+ ZzJ lT{f cbKRhv]gKEaw?'H_ 04t=^ls~Q|39$k{9}OSg.6$LJvZɾfطJ}"Ky5Q2/X#L |Y!TE Dyy_R] Xpr u.)s~1B0ۘz)Z~Vea%jUGu0J ~kq_$ar` }87EIR%#v$xrniɚdZ1c`.OI'vVGi zgf4l>XfE"ZCP=0`8/EV"1R9!2GJ {Qn(mC!Bj/X08VՍR/GW[[6UIr3[f~z(Ju/b:] +3E~"BnLPG'_ @y41c<+9=G eF>CBH  b[ +^2r.(s 8aqu]H=aur PVkrjF s"nsDc3D€hfW _ ;̝]p5]wN΀B۾Mk"76} ㅭh2LXL5NAB>\,;)H81F?e,TR0˼Sb WB`‚VGx:2OMmS)l5U4.)aVP=u:խj:S*;IQHO[FHf"grv9+y/G瞍}8諾e`A f6MiLzh*/  B 0Z5,!܈ҋpX,CodągP}[zu'snG.6K$s@#W.̴~xo:3|-jPεyn h 2|PW("h톎D=NX΄{>s=TMۯ0oT]̄ `C{F#mm[`6V!^ӭJ_GdVG_"XNЊEUHo_,9h^2Gm n76BM%ܐʍ>|}Xz8l햴UB?fRXhOv01l5?O,rZ֕#eVe1WEsl9/(fA 0 +sU޺SuX/vFvkE?ߩCl)9 /QCg#;>#Yɓ2V ~9Kmocs)8j{{9}y[wx  69`@^ʾ?n }~ JFݰv73LbF,y(Xck(LAd7},:l#>8xmZ9 -7G6,&9*SAP4gơjaI? B-16Y(!N@65Lƈ.a+%%Am[nK@v֒.w+li` Ԣ;ǽ.lo÷S;> xhTv(fa7ӆSj~yAYu=On+&Q 6 ^,Bǿlhp4~\;SbI*؟뾊wst*v(b[zRnv)D 7ED$w]?>< _b5K G /͹0fHS#֨\CV\mJ0bv{VLQ( .62o2OA.b;^w[&Md0BNc ,'uF+̴-0?eh\yOV$Ѵh~Sݭx/Xs5^-}V|u1KIA[@N^ ?Ko\ڴ]1!x<7@C`ՠ*AXAƁL3 )dAϨBS hL[=eLxZu13Q<6А2vAG&h?MԶUv\c 9:ylŢJ/ Hm*mz!3 UmC!=w N :io3WWBqQN0H<+jܭ?耱,^['$ɢWg|z#spq{!KnV(@&Q7#vT<<8ʣN=×>/Pby1gHl8"/pRak5:{A-n f (C?M9UsQ &ޮ eQ#Y&q/űʭAmOvLg%{R3â[e ;Jz2QlՑBknZ] j+䢰yup2)@`we!XS7W>w"c '!" I;xbXPqH:Yh~éh]r} FPk&@GN a{;s:.GپgÎ6ŔJ_Eܭ8C n0ö1ɭ6"<5Ȯ?Kݖ8̳M L,@ž;yOn ! VkHl\4{b~t-C}xL76Wd!Z!k^hppÎu{ublr!ԋ?Kg5 4h;)IF1S&jwNQ x5C#e(c=NJ>&,IۇU#&R /^9sOnuYA4iNk9DD$UYax>%qg*|F$.2V7@>fKDV3ԪZJ$v⏡WN5 LWïkl6(O}Ru4Sj⸍]d$|N'cGe݇3Z+W6^iDvӯ,ty1,SF˻/'j[z V0/ކ~Tmm3YɹQ,Sd,qk<'DShRG]J1;їW0RǂM|DBQjR EMULvܸ`{U=uh%еW,cbOnprsmm4Vpc)+3SWf:'`GQoU/fe֌Xf |4ZkdQVչ^Ё5 Zs8?a6U[_jzA[6 !n_#jD֔sQ}`Oxp.QVk <΁MI,}1 Rv<[4$V & A rͮ=!$C¼pߜ^)-(mdE},Zɥ 0z`,zH:#Oǘl8%!6b ńЊgt >EkZ Z%XM|HM5_WL*lj@y鲐W9L12.l27"E~<3ţ(HpГzpk \u?E Z)nED#T:)~ޢNA95)F Yԫ W*ﱠXhD?lJ&NaJm;FֺhCG0ג4?W >_?2/g'VYp6fl$mCڌ\ڧ Y-d{:I #0|'ӉIȚf+W/Fy6}:L)*~"k~|[MSxۜo8aq8NVۊYv|L 9sx5F@PEږDF pvce>8> |l\l F< qK2l4f Pr_IpQ+o<<>oANp$4/HDX vg- RW /fe֌XetkἤLe{wnn㷯$8c1D)re"=#)-6LG| LsYΖQݟI/* @?ScΐN[ &-Zjg7vx}@ݫl^3WtKP[Eݐ,3v JOGK)%& Xt!m86Kg[9{Xa\C'fyy#tkRׂ7*(:FSf Ѵ)7Ęhs.H i?a 'FMq|9W I=HqWvDp\VgDZ4݆1w7 +u$W1P=MAԇvf̗//DL(\"nK@ve&SׄΣy< oJ|vAzƱ~he 8䌍9 W}0_pF]=@ڟp}wU뼿/umzѶO׶a7;T}{k2o` bwU|0,+¨<);Xu7%3'㪵{Kh -ы ꒺W#~pVJmq|ǝ.,3[F'v~2*?Cs*CT7`|ĵJcPعƆ7IZ(N :ݒkl>p6g,"1UX b霢ݳĜ4Fsԋ!s-zkחJ;lH]%Ƹ/V SҹH}qu<%Pyg)aƒԴL 3MJ ~O憦  MrtVa&Eg-n!B ozCk8VXNMO7!d?nQfowۄ#pSLN{?nK  &0r8'(z9,^['$ɢ_r9)f0 JH)4_TBx QujrW+XJUjї$֫9-W}QUSg|"T4 wXmUq1H7uLl[uA 4YRk Uv-J穥l5!"m]xhg_ o쐫Ё"3Xp6gՃUt25vt PӱPHbndw:@Duރ{;;X& 'e)㴫wrv.?-7Ӧ7,&хimߊկ;B`&ڲ*?'jNJ@ƽ7/]I"GoN^+=Mž{rosv^R$LHcPCJ]a{4g}פ ?oJ ٍe-g*bE)dޯQn/jSNPz?.] w~ȏN64n7em0?0QUCZW܌DNJkl}n6u ȱz lN}h_{AidĽ >Hh/.)$Y cl(_,U!<0YЁ ٟGtpq/T*҈}Ni`sF(z,peQ(7n)KuoͶTm:LnwSC RM1uԩιwьa:G7ѥ?h(0NmZo8cJlpymA26=gNª!3|^o,^ȷ!: jUlZ.dA%oa?Z JN_-P_ykHh߆kuZ$zp՝9 6C'0؈-*_ԆObZHY젔.^r+{_~V,iN'Fl[ɯf_|}vވCnEc{;`NL4JU9 2ݭ_282R`e_1\h:{pf38DzLnTw 1!y3[Hņа m.g(SX/;#U*TqR%HKoE]e&_G?~v_|I܃ؒ.o0βǛ[6^ 6&enNi򴮟uC᧎E3h2(L+ ~[G[l2nN 1Yo52[4$1kliAjyQD=- Mi>FS):b`V̘* Jߥf,㿋zr9nb =\x4I.cմkb/|JfyPCE]u!?)Ùq .1Sp _:۠MhAlRX4r?jup# tlH<%F}o^DY v9'>dMN\PQ_#RPtfKWTVsu;AkYk+Q^LjCMFwW(׸(ڡ g8: Og\NޯR,#NkI`^ CkĻX>W*5_a_;\Mhq͹'fs(gz fVċg;<6ahcfUȫ(&zI_JdDy7fqh+"rƧ!a.:AX/WeW|u95|s?oDayیjbY^%ڟ.qNF%q9y#=?PXa9YV2%Zq@F8;o49ϢtgewZڼ!(N\/=6.[bQOw>'8F _`Ȼ8mn>5r4p-4*#Ih [8zʬ\Չ3^KP%UQ>MfD!}M俘=dZFl[T>Xq W_4[I4[YɒAÍwSꩤ~w?`D3>3qD(je-V|RoIS ~ se:HA$Ȫ;YM^7mڰ q}q}&/Սn<ƍ}2yxܢ|ǼBމt: h;\*s\TRXtUP17G͹A%$k<7o>zHB^E"YwࢣMcW/6~a8=HK#3ko\JFS(>Q/~hd1eih]_u:#_4p~2y]%֨ dyoܙRVіH%}?_ UpמJ<)qU~.b^˽4u'~E{D E E㌈q>?,pXY_sp~ es}W%(zz|,vCI3wf6J^&"SkcL}Lw.{u(`e8nAԧGE`FE8aXb%(9lko>>UB'7IM- e^.ECN["Z!Ӎ49\.@#=iجQ5se}+ 4̖: ;zfl$P 'cLRp3A*oOU,1/*-?Bq!Xf I}Wع*d0mX)9 d {v8c9' 5u@:1``H֖1ij.;r'aOAsV% UQ6^M*G+t"M5#gGuwlb-99aa`+K* 7R[8c8 6쟹LW:徕Ҕɍc#-~DQLGAm_:L3[ǻ#+H^>' :Sq!{T-F>SX5+u'RYLj(4siL@a}3.1-bH^-|}#Hgx>N(p29 sx^*2mJpVQѵb*M+..X}{tZe@giOiTِLVvݪsgjѓy'{_(dadqU2B%h=䶃>;] ,f ߦǶ]vd4'%'&4ˁugh*^"%x5^O]@ +E;jqJ+3CiijBy<؍t~>g[X^Wv+ׇ/4S /(E@ b!^1LfcW:l,JOd"Z2%~R9>X( #|FG;[>*Ha{/AA WW5&z~K^S ~OJ% %[. ! Ěj?>^ʚçFT6gdgX ; }.dג)5{p+a {/ߙvYȅrgS.mj?JH}ʩ 2 h#j鬝QJ%۩EEtx ^deۭ؄ 2X㡘=ZsaHu0?+8m>n># lYN-x$ljXO˥j`E;^MZM?Pmc-n?5@.=^9HYUtN2 A? ,zI ,+4"d Tnbd>SayUH[ h:KPCN1Q=FlyJρQ2gu>`r/ArBhbgfXf(grAJ})Є{!MY^[YQIYL4jzn!8\s>GʦT~O='|l|;fW1Gކ۸b-Q% :9*x1iž/8J4%'2^IPRC΋mU>09u3)RQN8JMpL`QzG*Z)(z|#E tc |m>UO#޶8҆sN| lZlМneX,4+ˍ 嚏f0wih߬ ր0P/@Oub1Ͳ\PԨa{5Ao:L؛5Nv40?}h~:ѾejU*j;Vevzi_]0V7w]gRu"Z2i*acZIVwbxMr)"BK0ոQꇂu)*>HgzuJBOڋD!ʃ? +&Ú m¾>9{58&HRؼ\#ShB^]vf|Ij@ )F@`n]^NEW׬Р9z2ƾ'ֹ;T)%Гs|a+߂GP\~>D T[`zg$7-8 Z{z4\xhd'l*IYu\*cgx UAZP5.Km;3V܈=Fb W'ħ;b̮me˚Dz&ǩZȏ}ײ [KGp N*eq1ڹ @Մ9D_W8eKh%/M#6Ӻ,mWFn"񃆣LbdNp(EPd,Tpm˵Jֽ<Ԭt$8Fd#ܯm[Mc fR%Gze*]n_J$h} 65Ue0\+xt, SJ\wDTZ6Xj\Icpmb4vIK|*=H99>Ȏ5lNm43));9.ѤZ؊-g;JIQᚓKHX3eΜ &wlEjLifnWxB2b 6D֬Eh24|0y9,ƒf_Nڡf4; KFtyһ'oEU:&[u5uQĦ`r9yvP?Dh(̭n%R j`9{}_rdyE^G-~Hb=htklo уU%4k}'d5TInpoa\{p崷xi\N;䉽/-I:Mja2%5K !)V#VC!FohUpo+(AMoߺmw0Jw5Nv!:Huxx^p"SlMcb[Uakt{k) .ً PfNoè4dpa;rɗ}Fѭˣ.wمn>-tjs<׳b*ɛgO wtЊv].<2(ºs$J ?5Acz_ͼ,9,xu)6?VZtA]3~~IY>n4w]^߁/~XFp hļyU)b=+v|qTc|t!\<5 rlͽ3HXjq/)#Y_ʿC3!?ǐVT k9$6cmg݈ 2Ao׿^,`GWiͱ#!Vs7z:;WcdUH™5&fmY1ndsfD[N?ªn'> %OzZx!;$VegdHG/C_֥>wp]SݵzKi JUw`xn%vm5O5iKa} $8,j9PMk8^rO.*S̸wWّ(EBHnuZ=6[Pe>M l%~'{p/-7 ĥt÷TlED"_!~܍B%{"Y7v( X vT,0}ya3hE_Wf`;glY'O֕@3^jݣī@} {*FVQa6 UY.K.&\dWEok24IÑe ,+չo雾z ܩxVN9̣G}pzh0NW?ͤD]OLny 54bgFmւdvl :Ӑ5YCL5DZ܎2\fos?wjwõ}{,>! TVXؿf/Nl"5JAbdH])CsP iě1 ¢cFf3y!̺Okf fL9#\X(~Ѓ`]qw^_ݺlet$Wpq~'1xy|̪UDP!s%drmg7MP)'sB&䄘…N-IqZXE/y ;N" ,r]M/( |CW;Yʥ`d^f~0lV}޴f;"x o?muۼ:g7yE)1><;lzaHS y<=@Ë\%( Iɜs^|b;~|qbk@Tn=gѠ%HttBI,@Lr sQJ]'VzU5ݪbLBM|VFYj$gz{3XXEx`lNSP,BIm˕`Hc&rQtk8ӂQ5g}J[4}p=geR6! 'z6pQ| jnEC,w NC0UDy_'hs"['hQ|*iNO0&dvF387Bsfͻ=K*)V5/&b d$tJk&`g.E)V!O=0ؗbr~ͥzm6jh·1b 4JkYOQ8R)UZ}`Ja$6;LW2u̿)&tg)ƹ8Q!%]71ɻs"9+MŸ#¦mTI7ML;2&Z p%"!AA&D=TNZg$*NU]\z3EVBb?P:?+p? 0 5ue-b(2+ϲW!妔Ahvv'K eil /iw{>6,#]Mg+ZLE"X;6$N\AMgF~W9_:-4Z@_Lo&,mxXFN 7>>Wo[C',׃LTYW-0KXCR:S A]$9 E֝à< "n4b0ӯFNB~}Stu 1֒05gy} A $J}==Nrڕa ugW9;ĩK{,\`_珍kH]8"d2kˊbhQ H*O,T :9\e9szm {߆ S2s0 ǐLgʋOjuC2zWw]gKB K{diZ@"zW%RDUOs(d8F$Ll$C3cĒGVQRy@S(FғQ)SQ T3,?֤=y|\uY 9.`%C>wMs9_NG\߫𸇮U0owH.vx)AM ȪFOPs%66)2(ޏ[Y"YʖpTP8s6z m{$k@nrΎEK&CpB퐌h]MƽVVՔkwewA.ҢOB `͘m4ީ!O:X{Cn"3=@Jx=K˙tM\Iڦ[?)|cyx&+FPX~vMU(Z|ɞDQ?%gmL B44@ĤTxAKU@(]zEgw H#j# ]hkO_z;?$8{x1q,[ 4r0i'GUKDd.k`f?f Ka1dRsj:rVnt=]7jKT~yŞ][P:B]r7ٻg+[&hg)I奇u}sw)^BدܽR~H]"1y$>kI.GJ9r\@N:!7xGt(Դss&QpI7ioΚ>ɻ&[cQ;8Ð 4MaWżd/&?B̎D1mOsk;Y4CУz.vbgG8S"uoF܇T 4g[55v{1NʣшJ9Cm!ӔIMGjآE!)r<%c pcP'Yր,DxFgHNݟ9]uSly bܲ£U=Mazg̠Yed 1A'b2Z9sa?h'r)uLSY}S)G3ɐoSSk{tU, x 3K4`7׺dDJ"Р`С!U]|'*Hu Kky38Wm` r&mwJq8a&';8P%I>|Bp5 ׈_Jā/69 &iEG$`}1UIuy)WW٤yD;jJnESˆ~M-+a%)0E8zYuk&9:*3cv8ÜT "؊1 V8c̠)ĸѬwBS c7UeH"QL#OX i3fl)EP`"$ylֆƒfAo_'\ct$YY*`3kGyᶨeNhnp\Ӌ*G7n'`4VD[Ssl=JVo:xɑv IJ/SfH Tj$zfs 7\ Hb0ۻgZC.-xu { 0\c#]5q(W+9`1K@WIޖ)8Fm* 1~Apg[X!4W@*/B5w<¤ʂF XU=lJv娂L#*}rPySCxBjy5+N da|# +!RnFOw\#J|? g|R&iD-a\cH; x+!Tj"y2Jh ڇ ^KiJB gkW?; /C'.fx~Y0a] ZZd-^`}! ||̲-i,]9IJnM'GWAq|wL #)L5A\)hSVZݹF V%x,%)F#1_EvSb[!Mj-'/Lyhfi%Z:Nu0VPnxU)좲zsvb+0M~W@wd熤j>L()\Qu$JiK6C? 07λl\g.V>-9cPKSQz3{v@\Q#5fFGt$? &u(xY UXx}x݄6*Fː[sId P h ͛Jyv}BS?ų6h;@ux%)8,E)BN%CR!75rATKTc gv*z.Q ~JHdMG݋;ɧџlInOߥ?@Z+3s􎩪֩G(Me&L(0^sSBs0sbAE-{Q u.^HsL)\69JNzw(2شyK OOK 5o_/)b.rߗ_gFKn$@Q*&X-)(kR 4H名ΖgXqR&1) joq[tD$,P,yhBI~ceM?Rٰ2/3?K6 nhQ[:0z ( 5Pȫ9cFriDL fW06J B2_Bi!/ZsNF`_>0)L- \!Ai.?*i)oe05cGx!+ U\'c֠mnc.i)F-8<833yܽV~:*4r<]-;Ծφz)B n}+qe=WEF0>D{7 (v)!Il|9ZKfb6<졮LԻ3 _iC8cM`="YtNVR2d, rmƯBM\mj <]pȕet :Gt꙯n k,u5~j-[PGݗ%eGr2q6~ ܥ\oN|Zs RvA]49)hfx$ vZg۰`NjҖ?Ҁt zeYN5!DmRF[3ХlC\ևB"҅Gf&fv)Zg_e5pC wB忹Sw3G,~ [SC)мJNI|֊L>G09K"'s_^qEUt_iBVa x“`!?.$ksxDToabtͱ+2LOԾ}56J#KV\U BO!@JZ 25|P%$x3]F{Q3"Uimֲ_2ћY"e[ 阪=M۲6P::;MSB쬉QNPլ= +_C&kk.]x:D^T6=3@k ұZ8F"}Ne0l9IXEVHF˪;ZD9BW uTUir{/Ɵӑ.2pcX68_̗ @ 8tA/=| 2xnE~l* X\?P2'C7Ee%7hb vvtlCV]̌<dE]g劶c_!mЁzsĩAb |mS6IiVDUf?Ѷ 7M Rf,02YC։ ٛ_ 1EK-qP{->Xy!'|ގ+0g)dK?ʢ/LPlUxXRc˔rro#87[GHNqaw. ȠFz>3Q;#-{ޣsOĥ ozA:{eYTﭬ 0!1aLIstIE{l1ɯ-$qXD5y[.YC\CnGa-508H1K}@#p+l'Oߥ?@Q6AHǎp]I{eY:1Uؠ?3ʐ+W[ ܜWFM0cDG<;:o>zi٪gKirkśH)z(1A14o 5OB%A w#d}<* [4魘?Gb"l H$3nevi)֯b<Țz1))EGVc@Gl:@_') 4κEl-9]{@{s޲ j}Lb |$Sv Õ2g+{qa=lvr0?Q'F먼)X',+6 E2 s*@Q::t ɓ7i2.%0}}]荕LXDUrZ\ 'u7hDz5׊4V (M#uHHia V_dp9j2 S;]q٠/c\v*Vs9iySqLDW} %Ϥz1Ä6$Um1i*O`c&^xA:œ\nZr(*Cz,U\QFx7Wꁠ=*[)x$,R {2|mc,ƔI(ݮK?YNWs$Nn5R- ǟAQӬ3_6kvIbr!,d,UQBמ*uJN9Y怣Rmī|"yi~@ ٍ~[0O3gvVD :ֵ~rGY9a rڱ61&n1g P\(m6:#.,=݂Qv OXhzq,g8 mOtV9I8';~ɘΊR(?ŬF^l'~y|:롡Y)qNpmu@o{u-U(DwQWr%1c"e+Hucׁ oߜ 4z 6ady1τ Et6;F\NKlQoLM6_'GH OܥH:L]yUgߴwR^fA(3ܶqF͹䍓ύ#|yg rBֈ׾έr,8ư`Y 2^^=|3}@ GiS'Rcr kpǂ(Ót l8.6?'߶>J髴eH<_VZnM{gne4Ix2[<斞Uq׺i%rSgO;p`9Yn9%F969e4蓎أB'bt"BED1_}V.vX_-\ATok|c+\$/'t<: }15WN-ؕB4XϬ5!}S: ͇ tpԮq_VȔNu4]#İ&X~gɽMILԈɣpHClQ1!玞򢃕Xз~.3uxtbvF MyY+\ /dsݜ7 ɶϞe6q''@[5e0dc]7m |L=ݵcj 6|VcNJw/U66Wvwʫޙ϶gb$hi*K T>ӤO0$X+}kI\BiaZaYW.e* Q2˵ا[h_;#ncGFm/XY|jwvW l~uhӛm~ڕ  K޵~R- Du4^H9/y/ă`W~ [2@fȒVLc]$@Yís=W2@YʶR qòs֓51S}*Ũ6??Øq=8#;i.4%|Βz6\q) (oOe,w]OTWLk0FP!~5K*uSZC@u%DZԾom03: ?~k'^"v?jHD  ӻ 3(j_$B$zࢷ_U,B<=2-[s(Xs\Ԭ\ed[K|@U~T$ v1XjI*L}m;?7/A=&}H #֢I_6 j9\Z3ӌڐ D/~q"% .r/#7J{ @gvrUYٻFs'XL_56$M9^wIp+(=I DB'NW/A#x|ZhH_@\"p7KbdU ⶍY E;-+!6򋒇 zsmAwM7i7?roGЄh-of~C{lVv ht7ytҥG *XcUY.>M]۵IZhyEƜ$ 5 ~93:ʢޮˋa9{JJFU9^%Ⱦyb ՋՓ2=<\t v釮O^k"I@:= ▄wc|Jpu?2exXVbzggBRy#Px|pQtcZ~Ek eӆGۮ'gk*SeCMZՏR#?1x rfYxB DXsY.IE-#0v\0~:{eAr™R+CIK#_ BPVttLd,̟uMRZ 'lPbPG V3$G?oIE+9\@\<^.cۡP/TW#艟j#l>"l ?[8H2fϑ7_f_rŚBV Ɨ!S &ilYTp5bm>Kڦ6DTb n$8:tBCtP}mN fB١k/# c8`KN3|^bz >fi٬@|;}0awBv&ס26h`~<ӨE*Q;q3iܙgODuW0Y3RfUF;(AU@>([ '8Uwϼ*Cî-"eF  )«Yc2g2I_XND6unO#r(j wGOq``^/_k0RѿV΢Pn9-nsBDž3Z'~bmX~4߄ĸ[*mE@ sjϑxu7h>YQB΃$e$ОE o6'3zt T~@XkB?$Ԧʠ$I^UFg9iJ/Zqι^ *c65`8[*nζ݉ÏJp0-d+X=h?ӈ@*e{ syNosىJ b x˖{!toަ6T\mC?&L+N^qb9HR5ptaW?tҿ~TELpuY$bw6:? -Go ory5 ˊ }2hXXP6C.XqgAQuJ`ӊ6\ '8آ: }_y^ϡyGR_\Q(6U6]DMZFC\oڽS‰c~{oXޫMX 7@0Gh"]QȾ5@w{LîēgOEfɻ'5?M}F;G8F Mx0>A}eTݨfSn]~uLHt'mӅZ<0~> 'P.-GӱT VZckdM3aw !MDa殤`U `,[Y_r72 # Q+f^t}h'ku a؁ xx9OJl-N'HՐ/&P)zƉzg)(y#&5)iL1zAsCBkR/pB΍h3혳^E@opQ$Ù/Ʊbύ C7Bpwːv,a@AFP>cI_1;f33j $Lr{h3Ca!IAg`s+0+ |'sQ\yy]ڸOotGT`wO zr8W؁VAQ˶U}:qx[\sԯwC/ȠbJo+iH(X ǚ6l 3G*2VSCE! Jyo3\'>;V=5@'lE7W.?j?:}v8UJoھ;~==c77Z?qTzViip͙%1|Us&VjO\+h foxq{p#@gtۡO !7JVy=I;$ǪdOLlĸx5P 9n [ XOXK(װ78?S)u07r,u $2 !R|xQWx"FxqbjHqH[(eYsd?ob*- .OJʫhmWkHC[> ܯ$gj\E>g,:ʓ⋩6 |nNp}Wth Rx|Vt b>+T>J&͜ gG[e˛!@fP 6J+sAk$/H]g8jSئQ(e40+iڬ >"2o{C[4w8Lh0qEp|rqQN DC|}RORh8%'j HiH~e{ɛ GtG@q"Ntɪk3NGG.a!sg2e=UB4*,9 ¾x ˁ%81 F)}1dgT86I*o_XɂS܌ovjzrE. "-{ae͊=x(fKUu!?-}2ah)\-1"V KhyZ>rԸ d?̌?w\8[C%B'MvȒHhfpg5wCCğR+n1Iʅ&g.<c IcE"`VR85U Di*ަ:A`;SII6CbPrj9K~%Ax |J~)cnZI4t=q@\}# Lf#`#vCGqi[JO0w_ dh  p`4+9s5g͆HxK4-#?vQn Ɛ$ GNhMꮋa Yj}N_Ad=0ry] =%8.K5PViOPhGe>ٱF]`sYXQ6z[O*.+ƒ #/Hw*%%Ej0eEP t iZE쑙\ נw03/m@<7ؐOI1c'.Q2m?R">}0qaϮՄ,-Ykߞd\!# %ɊLf#`#v! ukpcmBVXv7nPCeRt$A$NEؖOίc24֥q1e+̝5z&^䰄Vk-,W#\>Pa8AĔfvh›7A48}v(\?|}VI~@%}B L!O蚡dqC77AO1S.i,6.CS[Pat{L=|q3aaU53Tg5W42KfL:,)!:}$GxXjr̺ |O_F';wKt%[Pfh>:3AMC#?;%ɾfcԲC[Y 6BFimjg}i8ͤW06eiW{ d&('Y$/KwʮǬbsx!:2J)eABڲ~A dIL" M&_=!BR®Bi3#*>O&ew-OĚ&-~"wl' xm-q5D3'[[.ĘN(GP]$R)$V x&hiTv(gg煿=Ƴ:Nu?3~kR 1T a͘f:q rɐ4e^&>JS=L*B^6he>yx8/iе;1&˓QcQ0ZOV#q^*w\*R}/W8#e)y%IYG9߄iJMZj 2e/jZ5Bİ ǪE&ֵx!l: nՠm4l~Ɉ%eĮY2Mk$l_Ǧ_18(Xe ,5"?gpV=fGL !Tjo%a-f=J۹P臈BI\D,"@k{&z{P`]o&Iv1̿,iD=c'sJ(d1ݨ ֽ&StD*vr:&Vq?!-I$oya"왦hJ44oiž#k(>"+s4;^ՑlT9DV%0; Giut;2~n{2=:nRh$ ^Yn$4$4uLGvAUut$fxE?gxhJsET ᙴ$Uo()ú:~1I)5U bzzqUM> 6aL ?֩g#VOK~&C1󺾆afQ&nŝ6ɞ{)~ip `68 y0Dqށ  /} CV}敻^Izi㊼*",E8m0Wq[+ %ܳb@8oA0_1PTc~En<ڽus7U3K)F!%ݞ1 6Z=o/WBg =Z(g`W%eӐ0)dvPNfn~[ALP, mxju.ʃR*9I;$ǪdOLl c}VLe$EM=PgB$5GdIV޵O>{aHw[I=n>_"r(KO58{QW RO{T. CĜNX-deUp D10l8TqVv;}`.{!B_]z^Ë\z <P/#ߗT墚9⥸F|, b9(Nbb~>z ǿ6eW3CZZ7k:Ep@y/ZBX \^B;n[jNpy @bnЖC$:w_|c\۬vy&Kg!`oҏރ7TO'c(a*J*Ғ(6GF쓬2 AՓ/b/ZatuÖ:Af:`eUޕ(Y-䜹mO,C7+R~IbF}wĬ١xѥN3: d&#F$JrL=)-%-ɹ\7&=?ۑr)U'م(ĦcB‘'ՓT'^*;$: :Amje#cf7'a[k<kE5,T&9R]۩fKw<.3X\/8A 5hjßogeͭRNn8F&!;l^}#g^! s' VyS z%ʉk$!Z2MLJL]甽CLXFDtm\-(bצq?leh#X;`W.5 6tA`N6ljR8]׾vB]&MЂBc0') QE1MsaQ'»܉ Hr.lWYӁ 8ЍtL$ ʯ c֕>3۩\1R4Mv2sG~[FU<, M;j#Mf!NDTǴ_K PHTê8â'Jt\xF(m |Jl1'UI06b>f py~j\ͻE|+6B ? $UFӠD6[p \ POy*% Cr,{'KZQ" q)yvV~pc:Sur㎭gNWLwFSb\WBXzea@uD 'a@OkDmӼy [2(Tu! Q<)p}͠)w|(o@X0!{z] #p<䮿ح­M))xBCd~n |pqeGZFXjQl?^>ޒVJD=`*H9TyU4BmTxrC|oRU&VNi"_-Ww`0SҦ%x$, 8n/>=acSw<ȸBGK6GFL^k{sh%q_ V,QGIKf^jC!Kе\>vhKw*诅?wQ Ge5\UX4,v+I|5}s2l5#*ZL*ǸlQ$ ;Rƥn]WPal64ڛ0 jH142߬+ Aj%6,1l0^gOh7eܲ(b@ȀdB._;9}@xP{4d#B>**TjTzdT47v=Q9QknL~SgjMęѶbt7d~,bPUHlRh"J0OYTw"++j"de xRx406ֱ^͡^FN$'% -usFC35}@ӠOݍLYM_vhXOaLz8/O[o6sazJYm9r gOkE( X 4݆|Wv]TVtU'aqGYyfN9:uCܨS,|/ԡWqz.8nfԖrٷX\L BќYj} rp"x|*0VG{YxG)z)h,~g,SJ7gxo҆Wrb"r =zAO9Zѕ˟H6s`fS䁒(:m%nU+]$3*˥ptLؐJ*΁Џׅ;bSUp^YW3H X0Ps0\/*'N=\: -Y`$p4 m7GsSobjjz|9{;jQr3ul g7`TpO! d-%e7]3VEGMFųv6vt@+"аZNc8#np % HS纮'Dy{1XxrPh*7IH(`.) _CV+@JYb {\ "n>6URF"3;a0T)l-M$Ĵ_K PHn"JOܽfi ڞVb7H^A {Oī|;kJ5y7DBkdPg3먂$8*F Kb!Bsۗ:x`C>-srP|*jye}*V Y>2r'Rm.8HS﷦ntKK]C+({/Kb?tL(7Uu4u@ ê4#x|;B#ߏaJk~YO XzO/Fs_P}=Ց҅< Os#9P^_:.葄Ц#ާAFצ¯w(l&d 8ҩ <2vaгl1pt CGI,b{f_M>}z$ Ś^GwP 6;>d2qrMMƴW5Z"T;f3įW|SƒR#[ ӴҬwA"t'JB^w $jF!mtcWZo~պD˗y@7Wn̐uN(\r6sG-r"}~ED @H޾yDPO w^8jK#mu< 8uhGsPi1F(Oz򮁠0Y*E <>;enJwkb+x'%%!HO& #)nŲX?L,Fc.N*sss7r(?ͯ *S𫀥,O vXӫc-K, Lq([r玌ARb; 3Њ[ճ:"oI; 45=|wB@VZҾscUfr0[ T.psB4)T\hV !H%*T9=`߸Wc W}ͤ`^Sy[k`uSc\bj ֈC ۗJB JTm=e,ȫ,.E?G@Nxp^V)D>Pj݄aXO].Q#9Є')8AQ8^QjG(lE~;Ef_1csQmB?UTm C05K2~_&R9ܞ6A/ ^rhZ S5A. ro~g88f;+J- ?Mh[r33+8lMjEsB"x'%글L[ Sݺ0eΏO(m",^S/_/wұ[jV']wt`RAj䛲V6Mp O~R!Ԇv H.$+2rO,m6XQ\&_̻8'5 cG[^`zCJqB J93VU-P82Ir>Y hL0}z"Ũw}mnGpvӨ_(љ!K14?4M%,$Y@|z]Fr~cQi+%~pBi.:_((0aCoK:]f, bƪ=4b@ y|a4Yr_U۝r2Oh"kgP5w5i/!KMBE''hIes}97 =$E֨NQ@^_NJmxůFx {vDiܜ>cLJ3&JF[6vL\smtsm~h4D$\M\o*chHuA\i vO!X[}W7 P+6Xc_߅PĖKhX3bzu1k2ey [[$z=_9sY T}Y݉d27 ̦GV,A|ȳt8K0)5l)[]_̅EϓcJ[O^# d aѶa=z%vp2dv9&uwzLCůN.!_K6ff2e%{A0$ǤRG3b|GۆgV􈬜*MUQϲi aw'I,փ(hӾq4Hq%COR8ԋ-9,s1? #2/,'AJN2s- FL֑HƑp!y@ Z}(Ë=z>^ï⩣Oj77uBo<S*bHKH Ȝ]7uYT3:7X(RG年aW}5ԁz+ i)}|&/U;To( = U2@UZd+T:ԥzɹ?P}V;5cOO` m^zh 0I8J3fFz#4;&?& c*6]}u᧢?gĘ;׻Wj0 BTttly̓ W2ig] vZCUmbS)9Nd;ѫE7vH){'Ga_D êrҹ ƩkIӎhZL[b6"(bAuM"- F%c#QL )fD*4 7?10@j~PNAd#DXQTSO=ui|J]ExȞJoi,wS2XB)aY=SG@y:5"E@!sSיlo-W/G#?Xk{f JCTlcXrºY6nCB$5knn$*(A8yΤ𷰋*w|c_RԤnVp32|&b 犟RϺA۪Q`a@"fcqX g:a(#l90_~O (C: n)dOJl$Y ވ*I)NL=FUV)-,p#2k$P}?v +k+ i|L,rG |ljU 4)02'f-]&Ei.T~YRxK.h'R_wCڝeugdZf mku!i{c?Pf/ً69[6)C`~3'^\G6'vXX0s'W`$*zNSf9~4nA\Gp)dYq"-EwDzz][c:J׮6"tK@rdΓvc2F!:fU.Z[?h<`br_|oDJF/w#-.SH)ʱ< f5~V70/Q %øR_ ?f0dKDq[u3n'lVAo`r,ĞWz 5@2|~S:3֜B hYXlX Qa*U#ԮG~Ed=nDj*( frYðDtp#K5R$ѯXd<8P‘};E6ZE%H`LJܪPESz^B:߭ɗТC| qnq*4_Qk3^`4qw1i֏q|(՜ ueʯRhT!KWETHt/LG@Q ^]=etRpG5' ra3}vcSoxtR~  kWE;5je͉ѫI Ih.^pNV0[DoU*eB|w*{@R4GHJTZq2R~.TLFG9yߩ/9{dnn"v35q FAxMJdJPW'Yvf.3ͬ'olGL1+hSRTQZ۱Aʰ.C?equj>1AA`v+CZ {vTkZ{ao,OǸ0 |Kmz FåUسTMN `d!$FW3ȯ > q#Uؿ ĵsH<>$U?acuOwvum9QsF?cy/Fq1!4c{~m>n%<0h7Gxs u"s[]@{gkb}Y v&j|PR 4"i?hT ܾ{1FGRVHy*mp#!jٕe;I)\mXZ # X&{Kj4,e%\Ŵ;e=%a<=L"/j>A<|^6e\t{ 7 'n]yx4a%v wlܰC+XUaJn9? KPͺakwlM1H8Ry [WX Oiu>W{%ƎORki2&y r3K'Z:ЊvQލPYrG+{g{^gyoؐyEœY2 */_,C, 1Kݱ[NU b)4Xx6rDŨHJay'y^GM,"$s;.7"%=QPzĜlH9S(vrvZIf+ܪ/ pEGd`ʨ-BŠnZpqc57ßR&I2: ߼K4ZY^> 6Lbǭ^Lhz!7 x3Mm/Z&}Zf l~i sb(Ś r$0ᅱ۹)) >$[Eco,P/G.¥';v(^X ULB4WSXsOx-ye @ꐸPYNlpYg|@xsvJ DA-Ě`!ׯ@dD2#%=]vFr:rGzxʸDd˞T*6|3MZ7Fg.xeʡb58 N+IKҼJ*$O㎈g9N&};g6mOCљԐ'n/];?:|ؔ$09fXH+H#=)fh/+{{',V<ԧx gc+Ku&\'Ĕ̃9]}BT< D[з$J`9> cОH4)yc$'$.0sܚd{R?tuQ_5؟ K} ޼ޞG#ǙV b^,M5`{nT &TUr/02)*ձ˨\S1sI?`q",/6[g(/[}QB~R#KNSeqjEOO$+>V?*(#9\y't` pdiHz-Ô2-S1&iI ]<:}gkw#o*ۑ֛+.!}V'ks>)cDOĻX*z*^4,-;Q5X/Z[t+ |T`vĂ{h`NaN.|; .H#m8|`wNTUr聅o4Q]SJH/#SGICh34m;Sg 7u Ϻ5 2V4lPbL@BxTܧ7P&.r 7=sSu2R 羷ۑ~gMեeCpe+WX.8^g (:׼9G14~ҽ>ғѥ¡\+_/sx.'$qCtQ3 Zf̂K  vukʖk_ '94Iяxw$:r/IN P7;eH 8 T2vi&1k,W2 v7ę'o$GRƪEPv-)| iM;° 3#bJ:$Cù2"zmW vbQ" &X 2w+eёh56zaMiz,O4E8XLgHYoP3*$wܡ.k`kȠ35J1/1P!%jup3ClvX] r|hKi#W&kD .Ȑ5; *G[ QvhOkEC^c% F S4>4VTr ,/-"oxEt\t4Zв"`;[ cM̊R,ejL\^C"ÝGbJ =n5EXwdepnXUl .-fXIs3- j 4e/iѾ$/3Ly9ojyUgG ][)q԰Ҏ|B@@H){~P0A@ǟ1;ēx/ƶ-ڥ8Xi_^?X(CS2Ƿ#{v*fV6fylLtZ䁊D?Ez~գq4?pRO^[+k>^W:- ̐a~#80ȑX EK4G}򫕖cqwR*B0f?rq"p ) %Q"CÑ,#/m?`brȎ =+CCwh/,Ŋ-ެ(w3ӣYdQ,]hy-'7H Gf췼~ri27}rGm{24[Lu?au*#N~|pe`tohzUFXD8+r1vg Zp>KN[ 0fCZU "t~>DU"oqnݧgcq5~PJQVǫxZaQ&icY!њV?-8֤q؝*ɗt`}?JXymFosP6|*i0"x60l ^K+"zE3 Md-#=e#!ZPxwjgh^]+%HMMOq7&wd p"TD 82QB?VRF $&03#TmW39 QVOM*:;2,MSB(ͱ & l4j ZќŶ91D 1tb`! ʡH X$_qwLj Sr!Wu]&2̚/Ge`gcόnëtBMcowX{e-B$|n8Vn@"ik5ENldm(\ʡ4*d~'AH%?[[ZlMPȳeBzWINy  ٘ KR`Kst?<L`9kein}1^P+hIw͞A=O3QцSݱ^<7.cR}=&:FTqYOhaN3ǰD7Ci! h9ZvCdqJ2۲=Nt3XR;k, ?98As,M<&Xd/js ZI5IW7iLkme8^H2;DG5gmMjkh,E#*R^? ~UTn b43j<UιI֐~)$τwu\*Y q(hl7UZ8P~p~J9BkHKТ,OAܰR++؇bp˦u]~Y +[خO"nH&bBnJoI|@ShHRk[ɭӍ=Xj'硋>7Ҝ9S"r۔HdS8hG.;xOEvyvHwE\_z=?sQ3PCjHٗ\)sSW=R1κLZaQdK?gv /QaQqz  ݓ{7vfm~ om l/i& 13t̕-m~Z v!I/KA4vP!Ȋ~< H whVKkL}CO˦" m|c-!D # Aβd8RvV>ق=:R,Nj<8]*xKM,`df9[2}4oF<9t.s; o` zaBnHٌiw(>ʢ/o%pz>q8Rm(`a1nτ G!9?GaP$O#!(n#euYChQ(aSYRQsd)y=٬KU"iH=M'0N*\1p\rzapW=;;6x^VP]vBI?̋Cc;g7z:,hCtOD_2Jn%eΖ~fS|cm,wS3Lv Wv|wrz}蘯 WR%Qukӧ+. `b $<~5^-W8i`Qa8 `_d0_Ij*7]LA1f _OP-7mÆDF ,>:ZK4u8bN gEs:6L @Q2QN-A;#䔵ݭdװiLEd֥DݔQEK8{pT!~0V*`"({n!&)E6¡5ppw1@%Nf+ר`V}]1JڕH *hqUp\/{$ĔAʍ5%}$#hA1iVXӍ=;nD['"1Ԃ?}&8-bzucR 5YJ2~Jihޞ.U>HHlRh͛]TCB@`z7i<ӋuV5EDkҲ11MofAM ؊Х$oBDt}(.S淈ʎJј *3X9Zev{T:O$odX3`E{:wnqZG[(Cx`(ΧnZVKb<s_(J3q>lQg碲_pKH´3"Z6m/6"-8VTg❷^RWt"oozCAXd_*o͊$\7ol>cRK շv_9WLJT!SJ9\C7KbE#/m*oBM8hSהg.+J~R ULٞRRxH_bJ)pXL,ʲTZ, 7JF~(jX'chSE:U}%з1.S;ԼTr|Z.7 daB9 B~|b4,xc~hqe_Dg0vBwG_!n nO[𨩘F-f#.2G3GE(u8Hh J#pF/qL`ƹc=|JÇ[2!zH$TWPR_mE-;#r#c7;`#n/P}ZR$!HTBӴrY$܂qtPtYڥ >CW>)TUyzCp\[ Mպl~άUмiG}+R ѮIr=6r'*CrB!R|C9<Zg6(N#x,w\-*jTdqnٝzX<iײd9 P@tX:\q;"T sQ+B"8Rr2t o iznlFR9FE+~R~sӹ(9ƥx{ky?j؉7uI.2 IW\x] Ldb3b%x<P @h݉4 5{*!MWG=S/3a#?AOTճ1n !(< }/-nZy+yG4-~킼;y!}+)Eޝ\ fNE] xf-XuB#h cCi*hIOX/ ILo5e1IW㺝<wʫt͚=;OX%-//m_Հ4>]3;%}=uVGuZfJ^_6JD܄@i:ͨx? :@ʐ1kAv᪎oc.SFF)qMq:#G͍Ԉ?l-1/ڀ(Ƀ%EB4I2[i#֝ތKYJ F_}9XI7YڠzVsx9dk-+°@SNSaŃuXWd?xV94мV,P>vJ֨fXQ5\9D}]'zɷkܢNQTH~[0ٜ/RC|u {ˮ-v,8L< tJm5܍N.IҲF͈7wd&0gRÉ=RW&+~y t'X{y>[qԧיz N:DIGaaowVVJJRO G43Qf1/pQ@<<<M?gwY ,=lc~%$U[tނKWl2ĘjM6`n5`ipdk; sY|Z)y<`?M Hr;˔GdlŨ& $a_"xs9=Wu>iTKQA ԰Edy#.&x!t%cvLҸ Uu+𑩏S28E: v =ġәȡV dLj4n>XJ0nF捦gϠ}U=RT{tZ?FgCR > Y?t:0(mQF;> eDzT׸LUߢ^|!E:p;dPR^jHHA .B7ϙ(( ]}w h;mhCKleoـ*LٚT7uC(}Onw^c\K\ ]k. ȹ@"]y`!,@0F_<-6̅N#] t~:2J )O[U sҟqUeBMi 2X wO. Os9_G<[Ɔg} v NT1k%6akq" ?. K46p\el_94IZ/PoE`d480߲79sQrtg{\x G\CfoR"2Pn5hT? !iO̮Z7AMk:s\|/*l>B)r /RZ- HwÉֳmlFgSR`1Q1Q'8hEWh1}Q&-FZairlKHGW0j-^{o5gm ~&p|Zmכ-auRY$.x>pzJc[C~;7ucj>N"_~tu7y h l,#p0:L=kX 1LI]۰{3>ߓq~5'w6hzoQԡO?B$&x8Ҽ!GR K/eWokP@Wo=YC(铰h{KIE|?<^arj{dVPY32s<~&unIy7;Z=zD'ukrÿV$5JJ?7OBohWN g:1E%uQ\y͏@8_)2ޞ\ώIz6Na1V<~\QtZ\F{d! `i7ݷ!t8-2i +Mk$k ۗ{|@_Č L4KV~jY{{>ETU=#Cӈ"fBhyof3^{&_ԭs;t߁6B;":b1:YQ5"ewI î2Vw|XB>ceF-.=(#+*2 {g[L~Ո^TCG1^L6R[9} _G-n R/9';Ȁ7xݍ ؕDo7#|>X#KVqCVMm)n0'FYGj[Cf_^l!6WRFըHGjsFP 0=>zf ܘBȆ2"m&m|N +i݅܃*j,maw(U 2v j30uvOL\|5k` ˈ[ Ni3H a^"0gr}kpu"X@6CLW[wE?;œG5ᬃ!3aMɅw/7e=Ԑy15ӏOt /ޚϠMkZCےL($FMIdYYV/fA'tAϼoNw,MgA!W::|-˄g_c 128q6>GjSu7kE5D=d"FQ+ku!k-}0u/|cfrZI+7P, J{ c`;p+i5,7{갡;kIBH bFᶰ.5ezONT1!buE5YR,Ǹ t;-N 1xa`T" j(A$v^y!BZ(V tLG9g%&/[!R|֊(-吙9ܢ4K]?B ϡ WDm_FuHO3]X,. '~-@G}}ɯjpYY]JΏ BZ/nj}1!+Nx0"6+Y̥ERA8Mڇ5=d3P*QbtrqRiO1P}`)#(TFP>0 Бt =H-l7oхY:9P!$ƮK^m[< lFwx7T{o`Fһt"qR(1*jphzfM9+f[bޏN}-rw@\#.'$W=k .y]GJ۽R׺ӧW N ^>EUӽF!ݡ]'HSđu@Ÿ<;9|W冎OVaHk:.rt0~z' 2<o -@!To#tjvNqny!V,n}P)!<_QEU`b ]`?&rB6?Rɺl繒4sTG.~Eׁ8^U|lAbnASW~h'tH9|v2T%kimSP[lVD L@HrÄO ?*kD ca%ɀ*pyZ7dĉkq<-vL wOUըʻY os Ov޸PoU\Ƴo&6LZ/ߋ#S1I4]XhŦrD_GF'S pkSa^LD| :^4,Ǭu7px`XHZy4bOFnZT};H4F8;)*'Bۧ:yRD*y\brb E&p(Y:,rȩ#'yP_jZ.wgpD("e ~^ ʆtsS9Ih,[g@wc@/hrk WHBQA)j X+k6PJsxU'`^sα}6f@c{P\EӵbF-S=kKִo +ۿlɗWDbzuuLb 3TL5$jb!mzr5GlA 8t@׹/`T ",p7cVu?v7CڴL7839W>E|X._\@_6M@;=4x. VS.}VϱHEylE9KD4@,^6Ӻ&CJDi<`3SѫqhK_0`t#z)6n7F,N|D@BC}M؂K_M&x:Ypڞ7bw(fs nj-y = 9ٮ0F\<~*F'T&U=$ud$xףtFxw$ wv95 ä/+' 5NiÅ B!gc`1d۬̽е]YS yjCUp2qrWTQ1]dxJb#ed7^-Cyy&ɯzRR[~:YRT֨Gх0R :JuMqǂF@V3]8PH_lu[:;Ӳ++F>s2A1 4C> ;.6$Q&X 'H\F yiʚj/CjE3vfJ1E@4!,`¨X 8y?6?VUbB~?eBH@XnNt]P.JșvdaX)#(q؎̚bx~$פMm#+})pQ aN _8{pDD 늯΄j;I1-I2@$n3%=*J%TPm\mou9Z MсE\)jqIn=7n "h>"ÿz5,iTu]X)ĭ"ػუ"= ĉ97]a\ E5m=mV6KO)3,Gt^퀪;+I&mtSyT'{!Q13 (]Oo`SO"lrȃY4bw3dkyh!TSff+GFx)n,^_n ɍFel UX#YzId Tpz#̥<Ƒ<߆18\v]MV?3P9j%ޡzJ)kKU,4;%m^.#VoõHx ~CT)8J}Kk"]BXތ<Yެa’*y-s\wOlc bgX᥼(xt'OMJ5) *ik& ޴F!Ǿq1ڌM_"8jԁYL<+sorx|bxQ`yy:&)5GlNRzh8\ID0Εz 'TOޤ̍58EKMg^ A߽x :Qtw_ʼj3?7srBڗgeުi?ntGFpuS5دp~X9}z $PcsxXM}wyAJm?ACnARN}<;8Y;O^*xˤSg.&hsez=?RmoC҉yOqgx#wxR DJzE _Tn0::zp%.23ePc=+I 񕪡$fg"f^/YB:b9`m%Q ;cբy@܍DmkJA'PF`Q|XTE5zЬv(Ё`p!8~ oͿxN?,UV]z;w3 \zԔ3?AEa=d~o}Ƌ1HK6'?2\f8Ԇ3O&->0j㓅cr ѨhFiz@1l%# c2Ǯxրi(fblN̻!ARվFw!_&+Yߴ~D3NV Q-͇҄ڄO;i_]MjJ vVS~EވKtt.If .j!}C#`9.*<Цak}n^ Mcȃ}2 NDPV#Jci<%B^";-89 "*㽠H wi}뾡*Uꪇ9=%"}:([*o敒pT'UO8٨;dREwV&GSZU*sU XIDo߰NqETvȪVh2}vg!0b7 ۗj N" cSkVPRM>* 8Ô,>7L_1:=K ?!bؕ*f6%>- fxClCmZEpJA;لt.}׊ ?Yyw}#Us:8_ut[/l3=M obFI7rr%XJ8JLKh\ 3]H8H9Jw^Ame9+>*\WEN!z.8S_ 6B+t7yXM2c@(\;\q&٩жP߮S8W#UriT7 `yL{KsGz LKh'o)'xӿ/0ɒcڞ0{nikuU&h!rzUz.%Rp`\0[YHO^K[R`BߧCq7b]}q=Rƥ@L8wno eonq)A'؍\7۶5J\AG5kU!M@lLhY3HlҼ5kyⵍ']M-r?(Ah*ĥ5Or,|UV\N q[8NsUEX`<XzSĦ;mQ|`$jw?o6Ba6@A \$Q\;j>OߧYi`n15ϗ:e*GIvzP]Wkdr)X"8E# /ZeŠQ6!=-CӹnJ91&8.: EtPyݸ2b ExV*lr"VmB)<Z ܌̩يKnG rxT`x^_,;=5rHQc+tLְVf&bG3ChQz{eFsMGCЬ{\.y7|tPA0q:).yȎ3j)2t6% n߱ lTM5_qA{5y;v#y$0n!&d iY0\c Wm\_lzz&_ubPe'6~r} "ڍ??9wS)˘k,:-J LYHMT#Cz΂%Rt'Q6ЊI<9n7{2oJmtLaJxSZwcGc>QPYݶ7#_|w&| ct\TF،2rmH|w*Z Krje}Wvhͤ) 5Xv 'd8O)K6~y`taXͻ<|®ACk\ǀc9mrIT2II]6_YreA|ob( 8ǛG4 DP=7X ¤M,QAJ ~EZYO3V;H)m-לRl# IZ} c<{3.d (UԧO-=Ko4}$ n؇ns@"8YJ%S,7GAX|pu%ke4?E|xP)9# m[>(FLIB억$k4yqS=3*oLzO 4#3X^b`G˟gfQuΙT۞h]U}f8u)9ٓ=oƪ~w%zp}XVT ZȌ%.g9e b!Kj(Vaw}G*V#t%0Fi5Ap w/\V~`iRbEkb&ro BkŸdFHyu`5&ܟI%.Vk\4]+`ĊvR=wOeVvZ Fk >F{c5EM 4oyD:UyUU;sSzUk~Ffg16' 2MYD=7xx:-] @Fø㸾Մ/Yw]:a4 rzxrL\C!SJʹ /^N&HRҧ9rU+3ܣgh`{kO,_"*w}Ves+'Vϒn3Y2|oRq8`޾B')kq տլ>1$-ԾUծpbKɦ-Jmk[$(h4N#Mv̊ vK[ag!8YWkJf Ŀ/nS[77;$H81rݧ]mկJ\탹?+Քzv X1L͹"ZЯx.uSqĚAA 8ȭ<3q]˜m屲%L-O .C&j]e;ҁ^VZ-AT?MpJy9Ed7qT?Ow ]L"F4Y *9߽wXK6F+֌#w)49ӍΈ pxYM4k'!Hca8Y/NDbGA5*I+:plSG/`kfTlrG"Th$7`vm<4rձ~!}4B`zǦ(SukWɺuEGP%~.1n/J[sV?̔Qo\-dHb6y\s4 70FH `2Q|`7RCɺdC%s;o(٣cn#$K2XԈ9ePD'MaD]CqyGJ8]smX˟;p{Fh+F! &E蛼w]N74y.z"px_#nv֒;F* yPM uQg$ŗ~|2b?u s&?(L'{k=)U3;C?VG.s؄`B éJ-}mةrK{l+rd-}M,U F@/_ܴ'rtaWj1s3}oSz:h ;IX IA>x޾(4q-{ԭvudC /`oc`"kde|V+aC%qQe5 Ɋ[ :9ɍ9K޳ʖƿhA6 p]gP^~^Ij nTKTB,U@g{/ O B[a,"<%onˁNZ7YiQ?yy6}A1SdRȟ # 3~n]Dut1@U+`TY{ `+cꑟdGz""pTr=j꛲IJi/ZԻr=.*&"f]e`9&xiAu^IsqN/x~A;2X"O#װChkRl@W9D1e΀$H_Q=c% \z(9ަgoSr3?&ԮtcV8`D4gj9(}UiB@p NjX qLJmO#KC72Fv̧ik4iTH{3BՊG'2셷;d'f?AB[1GUǎy╚]IKF븢9>3c38痲U67Shs~&v 'XDIHLdܛi1 ڑX1 Om31 'vVbܫEe7쳮Nr|f1θ\gr1!s|T] 㺵2p5I i/ 煤U;blup+ e7 ' *nꆘ]Ģ (zbW}n5o.zU {_!$!kv}.$x!x/:!5%4M/s(c~hx[o־# h!$!f>ƞ&7|fYzwYx,EU;\W@LT3&UY `=pԛob$S52([XXGo((Zm;K#D̈lMҞrGId(g)o] y pY6CVFWA#ۆ^Z|#ugzML;ܲW֧h:z^{%EVTC6pR*KԫXqO1iw3͜~t7)U AT);UПߢ8YwDA;֛%$u 1°KGH%v2&D.,< 5Hzqoݡ-CjO ͷ"@vɾiB(< W2czXj~Y:.aŦ06=~_(i%usQ&(uS cm% g,ִ$?[;S>#ϿJub.'40"xJ&F@+'؛FjhkODsuTvF >i`9x\Ud_&MZIW]2tSxTALb}!J;`uM#:jk֟rUZʡꎳ'ou@åcXsYa  qT[3ɽ p2[ `|yÜk]c8$n!KE%'$rWϯ} 8H3\nT ܺ4E}&o37zs\hȻ .C+1r6۵)ECfN˲u )Oٹ Ksָ0jZpT-;?9㆝hVv -ɩu ؕ h#"Cv"o,&Jl~[Mdp?R. z&YC{3>Soyz`9 oJX-7_f6֑ACq6HoP6{uQK5Ĵ\o,U<1Cc+vMl }%isyLxC}N?.H*/UrA!0< 7ێ3~o6߀G/WpDens9٫wh?Bn};U=<zlF*|]PM{3G$r¢Rd}\^Ń"'jI={W+L\|c %be.S"DZT''F_NV9-hFJ %|>"(p5SPZ #Ռ"n((,s!-KxH֩ nFx{~!h#if2a^ f|=elzs6~[Ӿ!I$ٵ2 F(c RQFK!Y-ж:"k,uƮol`/UT*W fQ"=iK{oqҰK*;0!5c@X3AwWf ,0:HS=uoIL $3xp@caw=`|_V2o#TgT;se'\Z$?϶GބV"ӯ8dd_܍~ @n1md_Qas $ފ%ymPr+'d4<%z'\Η/~vyoOԲXdYp#ϨZq;gH2>84xf\8 #H'UO@=2ln7P/\׃xZ+nID>HgEa1<K4ZoyU9^q>ޤ,i6x|)$KTv% `,àuqhj{І,5h͋/' b `㰎9|`/Й<ָ|P5Dm*9סηnEjt/]2KE}+$/BQ+vtT ?@ B{P>A c&PTY7Qe٤BMq-Z@ E_ gAi"ݴ ܓC!#fobnݪ$e`Wov A+@2`m*L9e]6xTѻ^E.G 43@[IkÂO~$[ #]Y\lL~sL4IP*h3W"0RB$J 1,X6xbd0u188#(FVISC!z؎[j7-X؏<rm𖈒$^QeV?1ILya*C8aG1\Fuܩsl;v _>ZD;b~[9N6x } X5d.NYW%-\02ׂbW$ ܄R=q DGh;VjL^ü m |({o!E,>A"VE5ȧQW\2C` 8@,Y !P,b:pd!K ֜/%ìŋ[& #_`!b03s~@|@t/k 1Q7Cw9@}.DC5 Ώ`>)fLO HWH±!i{̃x؋Lı'3)*UNT@N=[m`B|^jtIݠ%O%} ̥Jss]#%'Īo/ZY$aP``&[G&{:A.I̘i랴 *Mڒ&?_:x_luJ籭ă7ܪ+ !YoR4u/'bP16V8ܔI!f+jb|/x֪nw͞s[fKG@sjMG\Vldh]X&?3jtEn(4Dk 6K&9-;p>N*MB=QfO1kvУs&W"Blj"D"bt G76îT9a-l%yKQ7Sƚkʑo3 ARc!g'D9AVhWWŰn1)AH׻db44G~ j aq4;)nEL8N20,.X'O],T75Hnk,1T]m &}w?˓G;7לtG%Ohlk48g0UHm K9'~b&:s?ǀʩ[oHd2@.HbnU ';A':ήJdwh TH"yc?{M\ kр^2R;t*ڧ} ӯf wG?XHCZM@}C]*y =W☇QPGQ M2T䍬,/e!ǦUZRa(T-쌂dfk݌:$[\t"FLfd>*f:"Q6<:TP38@zWh貴+aGzj صEY\k$`i\ѺA 5v͢z0v>=At귇V`8Ž*3$Ju*m%X8(4GbQQbx0{wvp\ ɞ99(ʰd {"J&LPˉ"Г9d 9`w$R] (Ec].-}%)i|2ЃT?P9j#ɥ~,n_^Qg .d xķm-'_*s[~/5Q ՜7SRei 4OΕP<η%^0D̋<Y@R{خIc1 udVt1wDOߤjB3U(\F&ʽ%Gc1iV 5R5c3!ATGK~^xLyCڧȤ͜6l^if7F, wnJ"n2 3oL!PRfЊ6~Kޤc4⋙`E"]ܿʵx8kfat`"2obq: ' *2eXۮv>c5MUyPA\ԱvRүw0?J7D^jXr V|FC#*YQ5"Ef<<@BLUiP"{V~ MO1$OYc~g0sRpMW 2ީkSWlb̈́WlFM%Ql+^4T`[ٝ'\Uha: t`S63JVh4_FVCX>OhaNVdMdp?xq2!]D -o qkKIY~AԢoE #U )5W&ˌ$Ba zVշWcUוW>h?Oe_"c(u,dBtXosK`ᤳ`vӕxm)8 1^[pP',o{Ytx8]N7wߧ2=mđ>w.0 #AJ;x@|>ѽnT8HL~~2WdE:,;ll#zf;#f#eu߷Ł d[NF;'16 "P$ *[̮.*6s׉[GP2$W>[Q=^s|`ǗtkHgEa1<K4Zbe n /r|%l4|gn-7ipx/nLX_HVX:'݇%ga|ځ6E}̪*xz舻K%GgRyKV?;j XS&m|ɋe^Z)Y %qggM6їoe0rcޅmyՒ_bqI\ |Z]\RU xqKgI›[b]1*D oZb8L?XXΰu'-֩a{m0vČkQ\w7(Byd(]$ 5 j^8puȿKy6K{#vEa GfLgM sLݷ $]աhA۔Zj]B{p5ܸg107c;VJ}ڇđ?\6yV һ[iːLP7't/~y Z*K>a0[TbٽQ"^`oF+{1] ;cY FͬIxt䔞n1rKG%e; l/fT=/x8 &FHy5C@ RKSƐYK.UKz{k(C;Տg|Z1:驨:8lM$|<=`Z3-z'c}BGe/'<Һ @Y"?dpfJw(/:)Orq#=QpzV[e#Z]w(qz2 +&J&9@%wWܫ4[gzc3z/IM,8\Ih oZةcE9yi~Zo)L"4;GnwVV"=>?=yi ,f LL(0jiU(!(Mp6+Zs&q6/bc &v AY=9pfuUݯl_6!!3-N.'8ޒfuԧf_֝"{he:$dDl.~1Q:1k͘h#"E@_ڗwz۪g&vOc,d~-yd`QA[~t+JϳʗV>:E<x 4$?rt/D$A\LmeүX&VtE,Svh˸t?D0tRƑ,.ͧ,DnmO»HivR(=`|_V2o#T3~׉ho?$boC2?8%RkR_\^% aOxE}e݂fNb_o{n`v' <1(aLz1oՊH `^+]&(>۳%"FLfd>ϩ-1@U)m +#>K ÊCFý\Z$?϶G޻= q_ۏMgE-uZ'F6^,iylldr9֒pZ(Yq/5g0R$Nǥ<y 1(7Uث$C.ib|o3 x佨vYm&1RK 䪟8-uImѯYWH{>ґPe+?DYaCq,#Aߟ ni\s(9@ w|ٕU?f%GggLj#*3"OGrɈikJϮ+jc9%,[k"Y~u},3}ޯrV4ۺdЊ轊p;X 0>vyho}u:\̏D0:;ݔ{UM42AXRȫn!] /fB̄ʺW<:+ %%rVL+]g ?w細lчA+RtL,+ -GUGZ)"&-ѕ'R9Ѽa:Qʚ^'{Х~|3MAЬ \u74>^  ;cEFآɤIТ2ZxXfSv,A pPN4äW"'S9+/a F )ͩywH=!ˠ]4Ճ(`ŏw]c9:J?0_| |D2u Hց ύTQGiCak+1 6Ą& w )>$[k?DΧ@ݿ%A ډˁNyn.v w) E| 'zJc N7c":xy|!N]+anm1 '$b鏵>Ludv6 vN&Q6I׬YL#M%y!NE愼ԅH %*P GLFʐh6)'&@+4HViŠx {2 eEḨX L40ko"PQIp!X\\0%mԐ9\|q" 9Qe+V2Q W!3J(oJBINJj@I4 W\].?^qtO(wAđ.;1SG`Quܘ)j, ~D{4)C~C=Q'R Փ (yG6\Ox,D>%qʼcAfY/Q bC;jkrYb|7-X(;No9y!O~+~^} mNFNs=ĤǛTT vXk\+R}>=i=_{u5,2Cw/ɚըRg Sžk} m⤿Sуrk8ʃPI0%PfQI3*cA0b}UbG Yp=BZ Qsr=Vz`9"xܭ}ت޹f2aiOGU6C1: ^7g:ŅBq a0Nq u+^SWPW 7PբF:zl򊷛X_p dGm3S'9Nn JךȖMl.7Q݅#iMO >F; 7.1ris 2fl&AC0DdlSحn[I.CPMV$"Bɜ%NiEBN#_a ʔcz|>EôdGH ֽ6FV5F\4)hex%rSK[6j~µJ)O6Ƨ0BOzq=uk0¸I"\BBaB\@uZݦLr.mja 3}{oQ/5/o vs`WUd֥AəO!CK#Yͅv;>R24@j׬HU左/A40z_i^J9b\ ˚aM+*S=3\Զ6-vקq2}٧p 3V@1܄c*lmE&bSݗ{d\@pّ+mi3 ~^~ʝJ-~Q O<_uн/%^ IX[JRs*#M;2x/~]H;(Q\ zrCWO vTۄ[&)KPYrRuwsE:y/c5ۍV~Lf.YtkݝQ7 VdkNHj| K/ >w.ܲF.1o843*lt\0WЬE=sszs%p!K+hE+Y&C4mvD"?Fţ9nF0h@ˠ; 5|S=7zIT:H&v;ot346ruS F7rK/_#A',B-92~UU9ݍyUpA wFQx mnj1SY­Q?†_T9y_nou~ HAmgD6E +fu 'B ^oUri;h \M̶o 9V_i+B̭%Edv9,S>^twm!loaCOĞ9F$qI楡%Px<ފÒa4a26*j Bd|^.#"7_wC+dyq+B> [ż jjfr|N;p "Xc xX_S.] k~bdw\.`7}y\lk: !e4'3רNQ v-'N9?tP)CX}#[ii ~);zх@k <@Tb[K(!;c[M̑;a\e6`XjZn7C#{Z4u-ؓ,,9IO@ROElFC~&8St/C/ik9 s˼_5U -􌈷UNl{<[+0ȭud!/{B,+8z1=p>1P0a@Ou3)?Z j:yb귒&IY[qߩGpf9Nʷ21z3 XxhP;?7.oe-< 8R_)\=4lC[iXFEkO$e}Jk͵IuE`D*BV苫e?usξ*,Mc\][!@r57} N=oZ8)]t*Z=W~+krXyM܎i)Sb l -ks4k(inv?{DP|Z;* C0*8F1J6:7Qr/ݓXLzwvVG;_^֚kبRCkwY'`p'rwlgV:AN-};}NXj;PéG.7eݠzsʕIx@w.t׹)MhDg"Z6_Z|u'H?_q("Kx'B X/,gZqԤʅ 18LyVXons40;lzgy`U8qsJS\[O2qٺӶ;rZc;sNNanL`72 "]`[?[kX7Zty\h )tj9d_or.)\U MTPnrb2|UD67@a^3^ލShM96x(@bwNK\Q*q H4˂~5h}h<~_yLu*5eg@yBGy31>i g"1`7'G0 `1@a竬Y>VM1Ð?$epsK>k6= Û4_Ƃ,SkVʔUyT->$dD'{e*[DIW݊$\~'3B@Od,7^.H%V%eR@fM&G/h3FRp"WJl~ ;T1&r" A8];''ttmEXof!rͦ5Jh9ۆA`E' (PIPTk{S6+Mo$e# B&$hmj)IG%uKW=ESԭ/̪ޖZnR}MNzV@zxU1dEMU^`i r!)̦PrGfV=-HClM}*+isJ2zhK,)웄qJEt"I!OU)xT" n3V7CQBس-ă^`*^TV,#(-o{W<@v&Zp[kxp+!d(|^UљhT}>4Bh4pb$21:xS$ m(K7ܥz'< bW8z daOΏՏkHbXUI"L߄L&ҖQЭc02d@+8砛63 W;KЃ. `LPv2)x 2<EklzW ܺ\IEMcv7̾Slm$/pp1=jѠ(rTdpeM׺l8d{#wRY#=NrP'b39/.DukM୷AǑ o볒bN22V=i{k/SOpvJwTJlz m$85ZtFtҚ&W^ MNrldvejIثYoJk%~EsfNZV9p%c^EhtQ .4A5.}ݿȃ-aΛIʹEse-Ғ-1VpqW1HRwj2"eZpSk5B.g76} f4f4^j$drfι{x@B= XKqj٨3S@ U0 "$߃0.<4V$SH Na/_qmUC^~ !†^R741KmP@ޔ?Zɲ ]VJ;ʬ]FWksm[2o+yϕ[f~DdGgtLNNX}E X1z DɳjLhB:I$zH',S&3usZC xKV}J"^|3#58OcφXm5Xh;/TK[=2u֟c! &pq{" =i B&C'%e9~D-&^7Ŋ~_Zb9ᏊfJu*ٸT76V,<46Ñ=ZX$4<Jr<x 2յϚ&B6KFdd0?K0X =\ ^ݨUS8Zxa%햂A:e{ X3;H| 3.Iz;X#7և5G,b"L4g#*qrt&;hյ8[qdȑTIJn6PA=gU'DR=f#u&27²ZarKQ@ ԭyBH㚛y"NN55r>XJ^ok8+cf"}>\Ye݂9nUVͳs3 ˩$?Z^xVs't[RVn/Լ8 ;2jl`$4 7*M$JUVRqM&Am0uRCmơRI|F>7l"xF5:#yZxQYL ,*og"4+%`cŝzQw͐Fւ*''[YcI` 5 bkTBu UxjQozᇞ(&J7D+yP{M-݌J@]u V3fJiǦAԻx mɕd٥> GF@-VB, 0>y |QMiG +kH)}YdqdQuxofCHd[~Vk_+/᩹XQY=9}8 PB)W |F0?{wZ=XV|QY KgͩcrhB}J*Ut-yVI>W|WtLĹ&ޕxi(յspXܜ!0Qqi`ȫM`54&١yORB0- Aq}f?}!L1"8 K\냝$=xmOfxNTR,2˔][QYjgP$:|ȇ'1ge!_oFfdU[25'ڞH-}7xX(?U4[XY2U/8>f!kM"HN8vMQVթ$K8MQ(ULPDSwW)IDš+z$+Rqs]l-eF3Exhw+CqHۖۀ`B%8̠]&,JU aK0[B\7u2'ΟZ{3E/ c+~=㰻XA 1 OJ ʥ݋1D,<ΣmqP'2כ rd`J@aL&mVMU~OEWm\bitf߹s~I< g0_=' jT[ᢂT6Mc:}I ~)56 8TxB`T=u.m٦) wn8ü(&* ?X{1*r3(dZ6TXͽ̈< zy[cGb">6qv|@湣Eb ~92/ #-&UGՌk:1Z;ucGjcmY܃*aY|ɩfz8hU-0dMܮR?$"g.'Q.t&>-z`'~wy䩸2vEE~UȢ@Sb0ZjiI13)z vEqU!tɹ{g)P)gК:ӷמ +EA0yPm0ղ,/?0&U44N^FIZV ^0f$a=9pa 9oe0Sb]9͐ uc%DNQf%4fE3"ߌ%DelpΑzvq~ϙP^ܳ}omy:l9Xe{e}.d,{Hp$SQ2#Hg݈@vsܥ:м"Ϻ @_\ezvHTQ'%WQ `=i޺*`L^8 ΂٫=`J[p^Н{7:@( `)Bg%v굮G8' v΄!zޤw?dТ `Gb͞~iSSuxuY zԽAv:P0׌}nŃ-wcKIe⭣ m2Ci;C{{\eBhpŤ]\;i* |%RL*ޝs;SB a|g5AEi?W+S 6놇c͊0P]Dc,- sR=آH*[ ѯY &^~M>p)b#)>d w1ϺՎJv, a7tԚ*xmI쑙^^ZSZk%2,W_"^I;c$h'wwxߴ%aTCqgį2וΥD܄J(d,+W(kO+8IG!HVhtE c`b- M#tX`z !Jgm,]LvАhxRld Ba=2atjMtƍg9wWg7šWi )/~s&̗-Ӧ1YJjc,HZcGo#ETthwn Jdl~ _)"&Mz鳛%qvgSWn5OBb41ZTBZ@SҐBUvMB?-r+͠6sj8=>s=Ҹ7i۹nLQ > Q!k+pF?B~6%7jĀɚq:!N&'3{]1=O? :7O*PVMKlц8S_oug"ѽR{?Gc58M]\Qcǡw ȸ]7?'x|R _z$dg$D)_Tʏ3 ˺U8Q=Chb{rQZ0aoh裰UedFK><w6(O< BD(%ЬtxyJ؎'s>qF㸢|Nd-ɴ|8Kg`b"K}"B?&5ĭOȜ棬(ɧWxu-X^V"İ[j@XH BZ}_O Mndr,'s%r V|пÌUKfl|[KE0ٮ"-d&n~A;Vj/7oxRk(e_tG."*jzZ~ K ud[jH|(ɣy>'!2[3iX/ FTN>9Fb/1'\j4 IdVh=R] e)AƎZώfl?%zTQ>ϑb9+<8+udK!,d.z35w[B/+GBa&s^JIחŷ/)Kp3rˆF I@TPB`(pP"R7eqy4j"Փ{N!4`s tŲ4@=#F'~ta,!8bTd=81qgѠr/c D)h#V:l UջA8@x<݂~ws#dkЙ66Tc?qsHRICd_A=1*rlޟ(r&MRNz6L 3 ~' Ej@ka*+1_Oρ$D1aǡT%qG5DEN fz{LX9^@$Kح ]T:bb;~r[Tj?q>S5::j㗭"h{+>T!(:ta_Wc€T(nYOZxH?\C{s̛u]Y'CIc/僪qXSfxؤGqדoI斌X:$ts[GlH.ΒJN7x}ok9sA 6vG,RQΐnO h,vB@sh$);^ff]P"+"Ѳ#Frv=s+ÃudP\3v5,,|QmJ)bWZĵh,Fshoo tig%{tOZK [9&XaՕ 7Y^,}6ށB3r5G:9w*ߒ:,K# jNbI꣼ %8fImxޔ[wb˾eA61Ć3/_QbP#6&ZT!?12/SML,Qg$èfr8LJ Q)*X?ҠII?1Ni~)0[;zQTdnCkm_Db1ooC-@5 =KoyJ)dWND`V:v}71 w1*"rץxU9_fǐm7KgYsաa*ƒmuAۮ2?n'Orac{+n1LIv@{ӑšOt6Q{~@9!Oc3t Hgvug_n︖(__jǪ>5l]^K%DgeB?=} L x†^]4蓎G鷘ݳEJS󰔷`$|xs!%^5M!X׌EQµȟn:)O"[~c]ϥȞ1s/ruQ#_k# _8L[%cS=n89aN?H,ń.!Mzj@PACbBonf/J4&kwUrУjL~kn. #$B[Ee|lmN3vX;w4j5pmrrh>JcMۦs* iJsua/>]3ǡG>We $M PR>Q!ޡO!(Y.׼9^R52˺RU~%a. r-ͮ9?x]!qc(xvOδWqcc,rF5+m-d q{NŖi:Qk+[4mYd̐x1kXnD|*Qp$,p{NK$ ЪbR2QFV2GVWϡJiĐ ;Q<~R !tA6NeҋDD8cc2 >6A[nz0cJ),#,ѿj s0j\I"[zw$TOJ*N|T1'PRL|| *@I`74kMZ|,MG^.D*%x nfȥ)SAիx&Ij)QÿYXԉ|uWPOwP$Ɵ2En n# ~|"8xiH#e|9*N%F:@yWؚ0ze|mrB^ Ygi ľOx[H#,  P^B0s Wy'hR?PF/<߸k#v^wi6o C*Bym f؍d{GqѪ[ 2W;c+~ Z (Cf~0Z%+17D@5-MZXN-g?oW| iW2w~rR>$/,,vZn'$Jg:dC|2sWšMJR#|{\_B2̠z]Wƒq8SQ#s=a1VyjV%b/7VL.6|D;WX-,/-X} `2""Jr975`n&S` q"iS)ca fEL]-SQ~bd½KCL CSAّm]_(S·؋o2G^qBk]~N$j[_= ?q^ߊ-{l] 2vZG`X0НXkگf #AmR)c)nd.-0U傒OKW7ʔՊցf <d.^%{A_Yce[+So)F\W3)WH^=- (M7C u[^QW WkYAdV8&w!])L=)>eD'_MG/1"<o.(3He 1D{qfrIwȬD+vZ>[bf=p, % !@;Cq8XN^>b :ysY LuyN9)B.轑9DͫRWXy-. ?f8!ja|e7G)~]ѡ\8Iqww |iZlk :ٺB+ #d. ܶT.R}g|rN|T JVH*yÆwž!@@i"xWCbVxR ]'/rX;lRPi$\ԚEntK=L;~Rra7|R KA4LGڨo{+[883W{ؐo]%?9huUCݝpVdtQp~FRM@XE) Ow ,q}U)wo̕Yj+_):<0m[w-0.w]EXF_#E@s鑾|oNu@9[W@iml;lBBg2Wd}?Ij+”w8r][]4/Xy19' K)!JD;P94wm-zM#' *uWl21xҷX-;nM\%#^Átqo hW Ʌ1* 9yAJno?b*}2=Cl|By*2"@Z #_UPYm@]C1i6XAVөCC!tvbTSٷmNJVkΦ5jB@|S{\y XebbS!XbqDTSv3OX,Nɣ.I/ݺTn|V/[G()ĤhSvxO=G]צ"D@ΜܥT:-Qÿ҃MD!a)!GZ`[e`WiwX_wHnw4I`w+2M/̵d4ϋ췧H 쟞%'\:*GP5tcLK͕ OXH+B!c-I~9skߋ9U݆~Wm E7}͛}y\͢%w*z5:Uc) 4ŔvxG|,n(O/c JJ&P]Dyc.s{9^cM(]6dV6Ja#GN:fM˓:eAqf#zwޞJ[^_g* ;^̪g%)]w+Հ"q6O!6qkشi9 TBv))dhYewEfZ/ɣ]`\z 1\E/q#7l0\vTVA<(^y?Hω'ew# d jFYJiցu?Of"=U|.lY)xMw{z ߧr8\^LĕEnQbC=M . %>2(zh|ЮA指5dM{BAus4xm ;D3>WGq2KIjcB@uL p?V3lۡƽ=zf)co+_ o ѻOVtO~9𚆐j@Xû]sZʴ|.-Nv?-WrX@yW TsiG@:D5j`i[K}e&]%^/+h 5?{;;6oO FSׄ  qSؑ5  y78;b^o6fTy/jZ^?C<'dL61(!wK^s|7/rb|WU )D`#Ξg4{:J& 6})R-#<ȬS|@r`/ UUyx {T=j:WAr@/_#(-~nvlz_׹GCyWsq\ *~5lpg`W pfX\dnLr-vA.hd]Qm8f9f9yi@cӐ&ia<(rGWT(y=f㘥SrU zA~VKx)G|J9]QWL ::FS=^|RHjGoHjϰ }oŪУIx%a5=ȌvӯgxK`7w>V;ݘFSڴ:+tf2#?/ʍWpQVF<֔[@bjRBp[ }8Y; 6&n8eKRj!~fY" kZUrwAѾO۪ذ}[6~^PFggakkQ9'J9X.¡ɣ 6M[N}v-X溜iw!r|_d5܎InY5 s\!2`k_#N{L)XM &Hm&oE'RS R3@~ MZDo]VLeb㐹FuQJfҨvE@æ.!1hKQqˎߧ?;cuXUj!:fb  %-x!V5IVs^/u3 *63LXְGDR{2'42rx޶Nh=Z|KĀ `1>@r{U9O1Xp:I$@Lv^W&젦_MwW=fE,4'Nd;R 6@C[ RY[Rqy4'SwSU: 0 izґ4>e׼%e5C[9Z9*N_$ )QI Ԁˀ9uef*P2P͗8$/9|- V>YOcI5[ 9%nVx)SPQϹo--m]82DWdۧnY#V a&*JCsxyx/VpWݥyp n[ 0J.}4<$ !:ƂEڙ+Y^Ӏxd(-(4fP^fBO[(Kqؙ>r99"Q-g\ 0QgtNk_`d>Sg>w0D<)x+E9;E;Z!~b ;QβgʮbؔU$ܝ)GgئRhzk5C9S lIvdI䄆dAIP7~BhJ0x1!Ekf>Z:{uASh8^O;0\AK F4d/%*]R/a1a ws5(uvx,qU}:9*H0;`J2 u8s~s`bdm+:m7?IDĒn2U$Z,fgoC ف &:>|BIA 6GrL |`Qa9ex6{V>IT4DI(RYӣJo٦ D27PQn tL"vH.lxنPjEuKKg_Rfu+tco0+˲5nJV AmA$%Sݦ#5!U:󙑤^/f9w5a8ؤzْ]}C#yS+_ nPRCu7)q7g|73p;RuŗLT7.ު_m N >E 䡡an;MMEw'^JC;,ĐӇgwIU8`r!4E.RnLILK r)B>FWAR]*ōZOo)jbr?1>aoˮ~]8RC|(qAv lJCo &&&<dZE@uLh\YlINϲE!E"0qH^K3?פwWڝժi[-G7Jd$R GeI`^`$>=:f}Nk:N}@*çcOP䕤d9kG+0}p-[@!!egՉU;dH3PojXSDS*-̯DIn?UC2 ix70^P WV/[Ʀ6;2YT9pGP7{]fmۓ.r6NI`hz(hѵzz*-;|f[UN0 \CRAŔӠu!~RJg>($k?@w}l̎8ZlH!i;3e"JA ySVB/eǟ0OF[Jxஔbمݫ@ G!p8>PZQJɖkͶsVGMޭh"zCoU-mެ'Z,GBz +fyZ^rI]?ܖ&T F`Q93Mp9j{1;%Y"LR:Ӝ@R];A,=B] pP]G0~I2I<|GX;Y<8֚ږds/hu:wHR5Q*w\4&l-~Ia)xҞO`zF4RHGk9oy\B]y &*$C)jia<9>KP^;ThE:>tU/CddCjugtJ/vhm@uIF6QI>Hqz*Գ5:M݌M!2|dEwk~ cӡ+Ȧ7VU9拲YEX9z.+!lj#JIPھJLkT7¿BT\OQfPqPi20$(5(0 :lMOaDHʾɒtMZ=T2pM f+ED;~3WU#'0 [={[] %mAn-)~;׉9&GȁEHps<HaR4Ռq\v"Gg('A9u7q|>T;;nK"V!CtOx r%"j)\wM^K l5/+ȥ}uNh8GB|p,&E.vN ʨ[˴Irl+1~5GZ8ע[/)e'CQfV;v fN:$.Ol Q,r$ v`&F]Y7T0|YykЧf*/.$Fۊchu632=4>2jtMckzJwO?tSM d{6U $lWƙx,}U87 J1>d`))|M))BPnW ||9Kf&b?Ob%PQV5gޠWKB͙q+tp $ L7@n;ˌ:!$ =.ui'bֲcCK 6~ki(,nYEO!CcNx\[|fIx9fY-OtYOg85Lt =i8I5mm&r7ɕ6mPϺth<݄LH(_!z`G /pmf$|wO+%4S>"lJ q\1 /$T_ dRƒ: TĖn=y3YŲ'Ξə9ec{>$I< "A;BZ!~Vf+bH S"DpQo̕1}#l"|xuRĹѕ  /, G0]rKB-c/0IF|$z~砡 ZMYmki|vaO4}^mq.AVt$30Ew]\u_Z \ ]Y)yT&%xz hyhj-_xE*sk/c~Ɨ[ܶ0Z@(Hio0)w>Աq;+|i~}H&Fk@ J>,R_? 9:9홴YuiGv`[G'r3>?d߿ZIv=~"9~=SQ+BQM%5T#B^[7^KDAj[0e[-µbaVUSRoK6bu1A Dy-VfO|;*2.gvkj?i~ *0~b ^E_8)LRl,0 L'35L{ zۘcHwӑ3}yjfնhwph;1Q2մ :8ϥ.;J8AȨNWy qo ~P@ce UAi);̤4 \7r?VZ_ vdd#Xh濉%JT? 7ψB|_ձJ2-OOfӚ;X~+ըeB,x,jgʃd B]%Ar%}c~fg嵳}W"St2&˻m, ؋ҥ? ctVXs\6g FGFJqmx[q1o{*Z-=~ wSϙU8u>_N#·70VKLqRK&6{#mMc=2e:XVBE Xmv)<\:iHꂺ?Q _8x^Z Oa] 4RZTQ.% 2AvMʳwvUtaG%1( 0^I%ޭ[*`ScY4 䤕䃽`LRb).l6pJ7Ђ}|RΆjEfD Ŕ= H.)7U !\fX., `<86嗟3gtƈx-nRY7n76R@&S$iUǠbX?Uupe)a(t4%9[afXCgR"BF_]ћ-8D﫺)"6{L_5oo6MK3n_l=Cإ2TPL&6mרRU%SxJ8P$R{rJUSL&~q,EnC3[}qE5Vga0(DU p!aemZP1+@}8W!-/#>|VYQxtvtQQr~iL*ڢZ%.vFs?_HiOY竻n D@oH% wl$a`K%`x/f8EP> eX(tȴƱn\P"s=/>]% )(C ) ,LnlR tH6v}IȄZG+gH|?fUlI?ŴZPw)KEt?o9wxNaX6ZF`)e_'fY*$YY:L>Ul'wyf禺b-]"@dG vUi6T_,h-2jY_ @$}6|dٍ+YLn֎v6c]wY=MƑװWіnUV{[Vdl.Y0b6oUJ)x5MJ.d?(+f9оuX gCzRi]nAa;"AU]w60E-Dh ]l,,~埂3Bi:JWo䴒6t-< ؓ+iB^NS2\nB\"=f</384B C{n4;6r=u0@eٵgev-?ަ4mvpk/N8z}2a(NGmDn٘kHBcڕ>gb}(C}(9[kVmߘ0fUa4r/Ȝ]."nrG?N'^H.e~C%7e6N~ĮlMYa?!([O +`p#WnWKjgL[3^tAr?K#<˲0/aE{(LNj2w0 (fF IjN+lEBŦ^@)6lHmϲ3vSĤ@q8„Ϥ\;(wޠk U&w7yc|qVYrÈ_g}(4*\;2a*'jp`]O +AS}~R!NmQp*rq_H5wzd|ѧ'jV}bBU+oeQLltB_0 {9pA2]i/5#sMHݮ!`:l??4<\;ہV=eV/_+ kXt,+ %yg;UyZq ( W!s@ț~) a $T:fSA+ю.!1¹귩-m1an  /j{fcS:Y^QڀI7̫L|C]~˅T!-s!"qjLMW0|<8-'OfULᜀ{1 nZyS#t`IVPU3=ՠWdLJVt2be[k Ii$X.INڐe!:qXT4{V~|-yzD@e-懶_߮<ȧ9>aa=!kP>U~4@9FC|#c-8V_9𻩋?yӎn)ݐU+~Z: ?GCswϿyEq|{ & ( -sǾs ZeؖW{9OQR>I(,e!KNMJfj<ڪGEgPT#.jV~.3w؍6砲3{[FF[>;#3YP*̛}q1iLP R\TG87~TcB,}`-F*Eethn!#݁ bR){=Md_Y3pE iGVSn"_0Tc Nb5$, ӬJXrC*-ו K @3O#bѣڵ=lKDDW<;-*5H_z"K>XG,P.8D?.TlN m̊KPIZ -NRrDB#瞹=n!NR`g>0hFVdIŞ覅#HTr ך&AMA0Xkұd=Jyּ t¥p_U$+q?@~dʀkF>EB .|-ÃhťձtmRfuzJo U>P"*Bw{O㖂ѷ.GJbF4 &Fl[T6+eHRTY5nq}uDod:Y6RqJ qؠL%Uχ^]/#Xw&n$Ywi\cm,i뗏W,R67Nm:(<9{CLJ^+js,>,J(DMUǴknhÐgL_-GF=7K. Ι#;ώ^ߨ+OՙHx5yv,y8$ITmoOߒFx*G<9A(%O%W%>tjAϰ݌*w16t9&dD%: 3UnDq&(JO[Ěd‰Y;., rH܈SܪLԶ蜥1ncmGiK[%!jĸ!|'"z 6 ?ۖ 侟Q;]f~6ٿrw$(| ^,DDt \9tRIJћ*?}vf9:FQ?WJmjC='6n fŚaEwxT[UTd(]e V Yf|{1 $`0X\S5~r>>u4Qv#lZѨ6bU0F޿/kA^x.LQVT_P>bjDۛ$!h%p7iK>t%'ۄ_L&qbT*Zc2iufr[}"2rmb( d>zᚇ|.s$rn 7fO: וY~nܻK+1 f¿<" ?ܗ\= 9!unf/U'0, ,n1(hphdU6s15 )p07HloI0Ui-5ZzpLK/rk>8} ^^J<O` ۃ6_qiUoQgS ̳)P) Tv4YCZBG"n./v2ǯHgtr}B (\ K|]WhY@o>HE ޵n{j7Px=~ޣR@X *J;i ! sWn2B4r0' LK }uW-qaSRw zi6E0+=Iou1 Ycu? oxSq G߶7Ki;eg;1I;L|bTTz9KMޯx32oElThE&MD`9:aZr~* F k&Z=_z^>}3! '.u?aWhd:G< 3Hdޫ# 'ƕ)UAt*W<V?Di\A TmlFVO@!0aף6(W24ָ܂n H~`"8@KJa ,X='Uuаpf+1&>~jKN>rxG%:#ʥ,>F.'>$fs%f^]=c`I 9M&*4ҺxEtЄo2aWf\r$_VMa;; (u"ͻd6,?^'ZXэv9ِBC/qTg=ť)Jr8~#;C ]QqW@&YhXxuv[F'1k] "M_{6U e=95̑ltOExn 0{K=i1foB{/Vp ?Ē啾SI'ds3 GkZVA +82`}Io*FI#;f9)9㕮ї!1* ]"sF(k>wy Z5iU Z[䰴\H{/W?0f?j7[͓eR+p#zKi>1 Z H:Gw:`/5XA&b–!xZf o 0仒xDG2r3zxV7U.d JQ4m O#.ol1u=)5]fqe*Õ+R|Pqj)4P@&:k ;ٖm)s$\c pBy#|zO#<*(R<кtk/lhDdcNc܄)=ZFi]kp%6JV~$lw_X}Ë+,/:G36^}=tTNfZz>Q%b]' ܺR"g_HG,<Ѡ:+ݺLsVVcX")= "&v# f3b DM4ep.] 2v9c̾e'd≉ϿdG6մT$s#[7J J kIh8ꪞy%.{nߣȢs|5a&0ұ!KD?{n$ʛ7N3T8xAIŭP \ uƎiM}reO@cQaqP1V 6=2+1m~_yjYGc>% (.e! 7gf֒-.'eheȕ*su2D:8Tr PU}Bm2Ј 0zAj}lil sP ]RkH8av"d#}?jFSD6?d󲎂3s|RrSI= Ae^Q M^V-B^H" +X2N,idJ3p]S*'Ë ]j6*F ޘv'\J]=+qdleC[Hgk /f#X?/ Fi; gS=!`9Iob)0RBS> U[ Pl36wOupcRn*LOuӘ/@wsJUxv" h_疊m@( f7^N%Wß??Q/ N0H,ާ-|٤iH>kBGYO?Ӄ k=،>h~ɣ&ퟩfd* :Dݔc"nU} TlY!yP߀ɉZst"!ZJ! ";X1a/c@ՔhJS(iRwʹOg,:9I۰Q\ :w#Gu;WSO0,@R/GhhP9XW_'eGK%lL˲A{pX33Ue:_2{ƒ0xc2׎ގ 8TF1|[=f`vrbtI,UqUf4O~l/r^KGjk;p?}˨poMDKDFY,`ۚY4텼'=kfǮ@2"@A#>mH4pXXfYJ_-a6JxzYcA6b0n^Q$\ߞ]а6\H"Fba0u0 r '#-;C]v |~A] (ϟ~0zdu֚]~⏷_а>2gpK҉YEѶSBeh0]l}Il;]v/}#"-\{kOQ5Fa.ͅfB#@5psX NCY)ǴxNkĞ$TGI˙$@t0p> g:'uv-(}faajY#:]]`QKop6h6|&^L?0~ܒ/T? )xapyLo퓡- SCK٠]/Cfm)@!zT!wkf4kOJB ?R%+#U\d_ i.ēTZ1ETLMߙgY-Ì8XD4>I@4IMʋd`Ǯ덖HVg,jgKn,!ldӔ)-ʿ*V&CIAz")p,0lP:fd'T6%l#r=r܃jD_'Y^ 2pQ4Pir4xl`nEG*"v'v@R^[gH Dh ]+I4)! Ʋ IL] CWB0;Պ?`(:/W֮фp7gr~K''sLIx_RE'5c򥡰aH}?诛zxo <\:0 4 p %i7SvpȂgO];͓lzZ喰{,qnϓH:Hm:\oAiSa}f9Z~XEGUZ ^t1)@ͺytئ7Kp8 g $ c+;3 gUoײ_@Tφ[>JL#MJ7kꤘwOW.3Xd.wڤx3}r !7H:h?ot1!}F.KIUjt"rZ{U/#0|r;k9?߽&4lś]_%!5hiMP7cӳj8(9}9e[1iۚ+ڹD3{"guc ylweƺrd;Gƺ#gy#dMqj]V[K6ƽ\P)x2p^ H6$ 7h~Q dt:ރD|D19t!VpPlnZpB.=+=iSM ]KyQ~mFiH;j6bؑ*3붿^wܡ,6b@JɈn9eYQ۞ݓ8`3Ӣ2/sG]h T>gZ |GpK_g@U bns1_](Mg: GfFEdҷ kQkIi\*öDBh,kR l*զHo/ oE <2Μ4 ̒wW$;]!>^ZkImhU2w )2pXL~wA^{I9 yѿJgzE7btM(vPti]fo"3c(V]j~!kJt[Ƙ,cPȵ it8 mBۗc:\@ R.KqCQR-*!b u\s|eyV^st9r Gca}|egi~;"!a cQJ߮vyV薁Ey9ňgpXtDK ЅB4r:x/~/~QG낻Ut\y: +Ǒ!E? s1 TxIPwQq:̙uNumɮE& z|p ]{[\o8R -"M(_˵_8Ѱ_Ÿ2M>V}Io+$GGibu+pI(E4]ѼJK&tWyYUݙ-w`-枨TЪ H3/ W)HNߟjnX=#v*-8uh| G8d@gHdeuٸΤnUMגM[?dP jy01\쎰pO1N^m1 8 )+678qKH"vCn+G 2P05,^eJfLPT99yE"%fa1tX"+;rqzrѭ[wÖt0EDk3u]0/;tGbͧ~Y}1ލs=G+`?t B+&1"5Fk%V~_sjhqCBQyS2J_e,̸P][gt.5ų [2pHռ h=vQQE`Ŵ+qoh0:΄zk6^Ep8x#м\ZǙD6h5iҵ)5~t ^ߢga!)W=\~+t2oRXinwpiP)sL[KrbH|~ܑޢ{0- "1%y+jB]T9 Y%lbLJ'&üS?U*Ye˱{'CrW y?Hj> Szjp ,Hu jԧuKa3ͨ}Χ/?2X@Cd\4^A]@IEEKd^hؕN[Qe#̋zӦ {d{2(=Wш<CŪIFn} x#Od:t,;tF#C3ZN}v/oa Lb@'UP.Z >TyQ-j9* #kD&Q-EN=z ƶAoUQL(ǖVECHX,C9*܃PLNfC1zLEp3BCƖAr8Zvpg}hfyڗ;;櫓.6(Iwi@<򣬭˻@4<+[XHEp1hݔ:cO.=vnv3)͖qnoÉDŽ,;R)دфC`'e r+;>J&qSWN8EjìO2p0=Xi@%;$%~9 WAa꩞|Ͼwʷh%,{ r˅o_Cz#ٖR9?kqi xdA=nTP.3=vw|sD} X=9oAD<*l}" Ps fa#c5,Y{Ǥ4(_$ RsCU8blHlIx)PLD,ssҹ hW) 뀢Fx W߅\78/JLZ/A](q8jZ0R9`S5gRf;hb_j8*"sb] TD!Q4e9;бNı)CvF C 72`4yGV|o&6KJvY ~QK-3{,.s.`*F7l\Ҩ "u&aPeZAfcgdVie:U?mpN~|p_M3˗'<Ftz4&H+DWﵥRzg뼱-r֟$#|=U/ <#*Ztvزyʆ2ޒaLlyWi+*Fd]:R`%% wy`bdl3HY(4IRJ%iI+5b"9HqӬH5QH.BΟ-ǓIwc۞$,_Uz ˷n bSQ:hR1'YѺ%~ !,,!Ay[">%LohXjH'ܟBftlqv˫6|}5(0NEH:pO<5>OXnm )5iܳ0ctЂKI,xFrlyԯ1.BaR 99"&L|Tc!6M{F1?u!(|Ĝ %wlyM NHf}) A_ްAZص΢ť89yI"?eFd8"/jYT9k:sÄ|¹m0sfG5@#AXh,IlFBe, 26[}#ERe/9Zuk1AG5Vg+  (2ky;ݹ&P5,C*B#l^:IdgN]W9ZC v(pyzi8͌Q@?x3y %b-k\9"v!G%=p|QtJVҎ3uMbT~aU>ڒz"ȿr6U>NH3$x'n;\gG`UJ6} >>aGwD#v˼kK[3ގ\ ̒c&(E޻[+lНrL/MœEY,0U:p7@]YW#:1,Q8 6a"QtE:pǢHcؓQ莺ʭ͏+|)=d > l^; 2<~U=.mBetѩYsvMK S{{mf85{pwmf8ۈHA'u4tc,0 ~~6nxn]d%ͮ Z3kt&.PE+}Da/"Pu虷/S#%EB1qjbf4r(%_u_m0: !ۛ0i׌;ɪLJҵ(tm:Bj@A8*]{}jovɾiA!bXT[|0QΎWynQ_|wh6u(.i×@H5#vP}MߋKT+ J8!A;T6\ @@`P(q# ;&mw(m^% R/?:ًfrwM\zlZ>,2J5my`m`E8^ϯO;ęZfAN[h%y1ӃB"E %ד@+:ر ¯QjoL|>+Kk_ԟނGG|2ǎJ̶/Qԩ.pAa` in#{e],˂4ᯪy㝒!q^+kDN/4j'%5f $$vc$od3,Bѧ-9pުg$1[-+))VڟQF0EaDo?ƢDC MmG|*$ r)0ZhтwIL#T Bw ΂s.5ߤ{A՝ŶLs*qpcp@ e1qWg1u-9Hb}~(C2QO*gwyv}  Ns nGUY.0xCZ & Q_i? -F nnGN|yAW52V=v AQڂFd`q4WKw =oJY!\qoyiZqjc].{gmN戻:q0X^SMS̢M."FMN#ƆU{FeYONJlE;%VP4ɼ1_eP#5"qxMa]6PA#8IB ɏMUb\B&uĞX5&3_Y:?ʊ"ڇ&gLɟ%=CgSzx?yt{KE^$.ws' q31,+DDshĐp(lg4-Ԛ0f#^S-6ruB$-jkrY CF@6} s946,O :*9kDJ44?&z.Y'wAF/"&G k/^Ϩ%$^emW =&ʘDu2O(czh+ջæ2] ` YcNWNBЮL|g1|rl#fWbsD&6q35SǙIGNvb#XFGwN/z-6$3fD}wZTHQ<`v*`|`Y.t 9Yզu(8tp0qnU29*.}S!%#W[l$$E#6#w[\7_\]:1nc@nZbמa |үA?#'5[ ,Adj -άQjZۺYѾFEyw@0Sy>Qx+§h~o{Xq"ev#P2o=)SBËq\gկ&f½2^z4)|gE îFͥ:K);(e1U(ȼo9Y߬z*iF0k*M&z 0}Cpk}%Z0$ҏ!R ˩Y O"sH?eh!cpPbׁǮ[q^&y-,^Y^^sA[C8&#6t~O=c yod0yf5L[⚗ DkhYỊ !yXOcTi+L$0 63 {ΩtX\iY0fb3f/qdTg=0g[9\`v%R\үͿ]d &lɋ+&Q]ho/iW?:K3`pIa/*h@Z8SS%8]#iqsK,pȚJ7Ju `ޅ݂ 1q2=eݪbJdxv`םs3~XS­tH>I `#X;VyMw[SBꑔ O*L R3Wji@;o'@nds򛠢%P:-S^=?G]:wXg 64cS/(̘ye&ٗKxJpDǬC2\(:(Ym |umџ;j@MxX",TP`4?ems+]?m~ROwϻ BGW ,T"y{ 5Z 1u.|E Q\wt rw0n܁X!FpYhZ88؉#ȌJ0)7T!oO;^[,O/)ʼNc׳YΌږƚל0vɚDЦ/.LÜ`qA,hCZP;i 8Y4 +cĚ,m/v*8gd_eIx[6EjEGl^oO["~;/"-5$mUC;tnK~^Ks=]W-QqT&FPq܊jѣ#r,׷ͩiX_7ͺ1n2J= 2P*7#m0BcRlLӆz,k)ϞzTwbRY]2q2aRX,XQ ne!(qDiߥ+7ݎΘ͐l=R/x#qێ Li"餜Mφ I}ĮCIBaD{oz@EySs ٍ)H )*;YGXŞDmPNZއt9RHɼQcֳ66 ~8ș}cP_uIizVv)+;@ ߛ&G]{'<;՚_jѯ9cVV壦-<5-!V+<%++O37"'xlIkQv,P­8rMҕ><rpgV1μh[MT$roGѕnJ8Z3(mH՜uf@' FuzkL"A{{3rf*>`QI"H_<D:4yTrw!Y%w1Ug* \!nCn})+c0ӂ pU0E=eny(!vS ]3vR%{`Os [*r;onZ } ڦH-w;!KU0&sz誻d4Ʋ-iءF˿ OB"Vڋ<%9M h yHm3u)p8XMEo"p*60(Tp眘$Y3ˣvg7cHQe}cy&kqe ȓ <yz'{t>vQ8^@iP 9 nyiw܎K`TUK'i},wAv` 2L8DZc%jY7],k9;N+͔#%/m^\m@g5 /~TO{d%Q׹49i'KQ̕y|2x-H/w<҅.3^M@JzbUvC/ +u(ӶkJYBxe=rZ~a_bbeXAArC5V#5s׌iiG}YZX X\%BS6IMp,ȗMcY?>4M35TBaTpꀔHxo; \cO*'ܹWA5!U-~ |AUҲ0-SR(uXC?p/plX#(yGPHW'6KRA}0?dBH,yITDR2N|S|G{1^ʋ-^}[0OzF\q ]^Xe:|6!?a[F$b/ҧPG%J;brA`wfXY)?GB o;zV*$jYtW '/5@ Am  (R7DsɁ wX@B{[ p|]a딲 mFxnhZlT )jeh8cg{ғ5DpV%_7] kfs -?&3NG_%¿5P>o=MZгcciTT6rΘfmRUv<~"K7:bz6WNѥ#)nߦf ,P%ItoSjx~0&#m/+5zT. s:h'#NEh[}pkVۃdTV~zhAU4a0‡殪ZXU s2[n/kW?^>sduC ׬#*~"$҆͟!ᚷ( xu+*R =I%s K2/#<HF ]Ƴٷ Ulp u9'e11cVvp=RÑ")7Jb)nG_F |뙁Z{< ck#6v%󪚀:DAT?go኷U.hr,8ndҕT/9}YWo[_?5Rˮ7z&VOIH`uyq*Vʙ+* 2UD7Gɠ=LElwzY`5THytgA\lGp0kc@}j2a$Į^A-04(_[US?,Pv>Xw[Jf dq/<^ kYE@tjl0~Yه c/uri ƪus| *OD);+j~Qܧ>I<斂;ԟ ?؝C1(~SSU3';t<9`ȺJ@Otx7" mB^6A3"Z]ޕ :3U郳-΀]N^M5N%a{x>w1y8m~?*b*V h1T>@9qrt, U3RAdK[#$j >ƒTUw̞GpD`Ȧ.p ݹ͖aN_9~qUM%fRicwEˌ:m GN^FTf\?6?pq8VX__*WE%2r?[Зpm .W~ǂ]⼽k즈(`ipΙ/& QQr,=tu7aQqGC[%vB3͏/.@ ):7/{.?)Ѩ(&E6ob3SDxhAĎRâ 'an n)[r"?FDž(]##E&J$S`y(ȆqI3'h=SzU3|!Z*x&m<&@ˈzܣj< 䔧;M▷ȦX]^ƅmq>^Iaa~"_DEx"s"۷B_gLHy8r"N}Ñg-\]V?(oѤ/Հci+翟[t0h+3eFJ 0G\>0Cl9pNt|H=frB)6x]|!h:d݀NfM 3qnaޑ9SH|6X Lj`k?,O|ʷ'm?J!tֽ@W}[mLv3&afC"#<^ʹQ4tyٷuL%oA ̾1,wS.FGvNh>rEjO`N"s2*71`=0"n)&T0QRG^A5  >)bbNEUܡ!4ܒ;<+GY֜%q'?Mb;__Oe;ܤG,~▬ૺ.zt/Ջ'kBZ&u$AwiKL}䪶ɑaCӝ98gV7KPx*]6_ZiUwj+5?)rUCZ>WrϠAgG̈:^ {)X\x[ uӟE2 V}vTvҢqH3CWMUo2H֣@ӫc'lwΑ)#FRbX1`wDpޡV]pTLmt1(HO73W" aw,&K9?9m]֑4VT EjLj/B}ҕ3@WU(ZgPÏ4㫋ݜ=oz oՠOa3T5Gۡ HD{z|ՠ80|?e:G]aN:Uܷ܁#~'/c.`ȋ6s5u&j<;tÆfvAh̴oB~x^8o];ωDa*Ad[Az "p|DgePvGrL+ezetH^=``WrK9aA1'[\F#\D>bDC3lB6ojՓ|RBKSW T6|yIre95|({EZ3#oDK+q kXLp0f9 Ge[ơ,5Y BjD ,xM`ٿ222 s( X-ƙ}ISP'Ҵ&pzg&TJ5)&?o}05po81Isp[5qGDwv _JdQ| L"ӏe9FCLatr]LAaf"ԱGq"^{ nNSc N 0UhJX#Fp ; މ :5۫@IΞ:`X.o|O)C-WBNHvOm ; HTb5l sC*%>,<&ӶMͭU0ldGhKжdAe/Ч(>؎M"o!p/Gu/p+ :ؽ@~*i*"{&6- 1m ]a3RFfGf/G|]]ǾɢNqrk~rorE'7/tp} 8PXɞ1E_Wa]M_4ՄӣvtnQ<5jL]r$2ph :H'zcڪ&ɢs~!wKd 2x`}Kʻ1bAIO$h2}K^oQLN$BV p=Ir}s/ \ǚ8lwZ82N|;n>ϖYB- Vҵbl$w 0|{\ݽfyLKF]ŋ^űlu/E7J0RDV&TC5N۬fX؀?_}g`)F'>j:yt,Fո{Vn=mdtUkUщ߇~q@{Y`Dc?\l{վ&u)nH6-fCS؊)껀E<_ǃS qٻNbH@OVKQm r׽JiL4$G׫$E o:@!wf֭LguwAQv{Y%L- ;^M#B,e9gSeݥk[ ^[~LcŢ\8cEae;fJ>AJE=z[onOC&փ?i/A1tN+6O; _,XU ãx :.MV1~*99"!3,bG͖ː(Xdi;{@X|Dugѭb' rN6~-А!A{xEv?¯WsxwG_!Eqz MpC~jn|?/Ȝq JuJ0(qh9,G: rٳ2z4C FGTmPۋҜDbJ|*Ru /Ԡ y;I3. }xMm> /f_i6bGBUKvPXEVъSshi/=NUچKڔQ߻%yY(RJ=aYh/^;nK0:Y /ktӠwux$bXL~YOv޺]eu@0v6 /ĵ>S$/lڏIJ=c)JfKij-JLCkfebx/z5bdxzͷbűʄ. C? $vq Y %0>M5):(2gpW7^{Qɶ`zػU))3™/kB`ʯXV$@HðțW B}x,-/pJ;1g,`2@ȉlL.£=:*261X 8TgT=Vjͦo ][Vviؼ5 Y G;!G8ҳ͐CoB<9%k%d,c^1'"Ih%k.u%ya+SnՈ2PCEG`..5%@o -a k@np>"IA+ATMiX鑢b H9kw8дqYe-xK/SVm8dф|O\6-gUz״d] "54 )k#Qm#ΟN0~HD' wT'tl,jF'~_gՊ+`"wmʒϮP7hNp0?|mq'|0PᝅC[h z tS=3`WߖA ӹY|ʆеF69.Ϝ&i"s^t! , T4Mȃy(mLy2G[@d2l?3s=Jb0~ [ʂzgTz͂S~0=,ۓZǝST)7+2w2_i23:n2f jmPH *$yȶ"}rq: *GoFQwfcWzR+9!V[HlQ1#vz&}e_(;Rw7F5HG jj7 oŌj Jn<,,Q. vLOil u{sٜH1` 4?ơnz_#WٛLәÔ>hO5C# ?ӓjA1p_iKT0vdִ!vDM1!|`E-r^FXBo{$:r;||A̟ x1:Hi@ #!(X3퓞0 ?"7#ieix +005惷$'_xtK5.1i C0U|mRLdES8W& \R^֙$kfyŦzGO#9}h$zT;%|#*v1)dI;\M/ws27`T0rXWsMT$o{-#7Ku֝43`&;ڢC+ --iegahʥ0shlL~tm 3Nfv9e2e_.۷٘ޤ&wqgE- kF(Pp@%s1T]5R}ꦓbȄ! w$ bi>/g1ePa8Vjo4QW0KyD(E*L~u@M:B\%mZ|B0~hd1mB\&Y)W)%a r #J$~8h~hMjߗˁ]FMs.%H4.&^vi{݌Мmj}@:ZgF) \c'4B^{Ėmnf:oXf|S+X_:W =TgPټX@/ &@_2oȠP^UIL PNI-!55YՄsx8‘wz /`W(_FT9vq;RqaՋSY 9yry1rM('eF3U~ }}y5Rf-m%uTmOE=y#:1ЇZ\7l|u«R噑⬾%̤f :rؿayaͬɳq^у3'}-Ef/6F䭲#@/hiLthG!y: 9A} ;IPob/4( w~Bѻ~CӴ.qS'8ʹ;{sqCY^KNio. Ykϴ/GvecAXX}g$*(_jb#E9*i{ /%}_d[ƣB@shdG;2 FW[o IW 9ʊuM4yxƵĶ`*1CJpaz:T+; E[- pmD+)瘘-5:1z=?:Q<Ypr$KYǚ<~/˻trUiw|;g7(VM':~aB߄;?f;rb_LA;e X L0OY3mv{dRyf [m/43vہ`Y-{K`/Qov=`c 418~0mu<7t-- OqB\ن̓? ʅ[f'Se.xg$p ʕ3+#䨕"~-̇fm2]O$]#`~4TrAR$6-TU.ǹmA$&xH?Oٌ+iHNjh+!{xYV4J䑸wQ>%KI?]a Pr5bF8xi)E?e}$ƅK3F-9 )3f_ ]s1(yi0\ Ia3`c~Z+$re3u,Q҂T 0>6O>fLnTz+0y,]IDT"3Vj0C?&3} M'oA"Wm;KfDQ"1I&/|e:_ybO^PiԂMvo 0WW$-x\PJ7 |?fѴ(7}mvz kSC=p:k0K7L$D5{򒙝bj{P'&:P֗VͪZ1|х" qި3)dY<{3F=Nc(>~Xw(Jwςʯ0co}g@տC(rad}"WK2,j5fnK#y+B;7hm2x@ԹzUE g,>M'XC$?f"&^"v='j-!'S 2MA І,0. Zd9T= HYv/p6O/~}T 0.p8{LOȾӄx(.d ̍\馎`ḆI%76/p.mNH+s@ĉgv=e{^dv~s u1۔B%'T< jN4 .;zʯ?[ռذO6"%3vn#3H-uA]%n[c>(;S1ˌwaq Kx|Dۃ`'\: lΔ.M4TL]/qHB RedGF9 tSI=j/u_bae قK˝V-a4ɉ:\ 3TcmU!K:<%}pҩOi!&!HesYN%ԗQuVPC=E xN7(t>8קL9ԣA}vl&ޟE'ډIϜ.tka L$D~'V/H1XfV?'/$u$[/< Vtֻ-? Ue2*"÷>L|caIAt}ɱwbl]=/c6 QmdH3#Al7Y?V/v՛oW%75g]ӦP5VA6oSHq*e< ܓvU9{WN0NS)]1-㫹[u| <7Z~ ^ضLFH%R{FK|rde6vȽ+*,R7ZKƧuaIU_! 1ǍQH*"$t]b'sqK;28Q ~~jލ%0P,/B0+ s[D"ED@[pfc/I̸$eSi-N'#q p]-E~0JB󍴔d.?)$mR~o] 3 ހ1Gf˘Jߨ>%'wYf BxTȀ Kv  5 Lc{>Rp[ ֣vTpƔK%Lpe9ւS'd/DkWBa`8ƨ,owlRVPV 3⛽ .c[E`iM CX2k ⢹޶\~!.uÞn]1 ʾ;aKA Z\6I38QCZ;;Z1YpKӇ693 ӹ8p-Pwi (oTŀL[-* _A8wE6bsȒՁy|"ͨ-4Y=GMĬ$QjպgM-N<(Y9|*Y5fhb F CAcU2k,d$ :1\RhCȬzʬ@{ *m\O\艴LEk5ȎM6g|. oU mV8'ղyT WeB*9Ј^˦)E^9,uP72@e2,/WDʂc&j䅢wi8WڴgĽLUǎ^Nh/uZ.doDt&ȟ⚻cg1+byg2Ŵv BC=1^&Qjqr ܫ N\_=,3. Ij]KW6M(? ;;"0`"rf?F2`tXit;VC8ԑѝKڰG]!8oqcZϮR28,XLyбPM:``Wdm[6k~9]\:L!ny]CŽd?h ^JFje $FKmv>su-`I uZ2}%bۗ9`6~cʽ gc pI<bhef ^/z^ e@%Ign0 |U}4;كq e{ðϬ&j,gKE\b5sǻlδT(A{7P̳j}[OJ2ĴB3%(!69l*<. Hw*Sqa硟h)z =>UsH2'$JZQKٺjVW3rDqE5e&fvr4f֪%UDg w-m)Tyih#v^_!$":@A8MTQhZ[T~1uɞ97[ &++_]D-R9-u xݜ=Xa]tha`,ኂ{Z-j0Iŧ.PL\Z O/H:-s0o3gF0i0* !zR- 04 ^7vP.}UI`.< &ÂsE.9pu)eKYO+ց!z0}}v6D/y]E$bMN!:$nM }7V$?nV%C[@C.UxVwZx-K?h jRk0o2LeZ$Ԛ{"8Q. cq."X2` 3uœ h'~rF ӏy -q!)wʐ'e=C$wkͳ.)[34ꬽ%W2XH ;7-]k3:bTĕ'38A @T]R}NضAʐ;M{ estwXi< Bч}cքǩ#odW . h2>!F}'?ImշVt6 ; фeZs,ЛxCvHY=X|E]gWa]'&KBVYLoLS?w>>Owm@^عw|No}¶? 9$͊Ç#\ *6/D[AGD 2~Ba_)̧WhN<[RuwBo bL%F`tGnz 0("0PɐYTcUZ9K$o4w[NwŚ%gbjC,*0 $:cG'DS+/FSi?wJK?>8ʬf Wci U6ÐO9"{}3SZ-d£(S ` rgz[-P^2iBiYԺ$$Rh yB#FK:kW)bHNe/Zh z\1bY@%KXvxR,LHXNBۤJO{45>UV߬mW 9 ;%Ci/i~RI cS )CXDo Y<] p@@J7>1 ̧GPeշ@"$o-T%Κ}>{AMl&y#,| 8y&M5\ݎ[B|pBه&n#wG*Qk&rk(0kR\1uiQNծ6QLR[f̙ _1DGB͎_I˰0(CrR6<{"6h6!_ӫ 2 nc=hn JUVa q5R)M^@5 5!魸EZ ،]&_W:`ۦѥ^RU*ѽ8Q>IonC~zaWM]N.1j՚ׅ8?U s$ Р/y?{Z ߛ3# ](W:r sy*gN[l'VCrIu"(WDMvf5 1gǶC- fIb#2 zAFJ>ǿDp :uGLطt~At:w@KFfygul_YVh`H@0+ʵ1Y!"6 E0D*yӝ)@f+47 Cv=mj@ xn}i_:@)njeDo:-(Ecgw9y\Ɩtrb(&u1]CRRێ&Q'1[^J98y.z`I 3.b.tʑ3Dtrڸ>Lm0%Ð/e v xG'vD2G62j1Yj1Xta*A>~񰡠rEn3םrm}Im&0X` 3#&^CB$/e潱 tbowƠָ@xO3l\aY'$sxv4SY"[+~75bW$Y<ĝh,V3󒜠<=J=%!V3^ӾD3b&C&:L.-=xʵm":R).T8I2cRg T= 5z_O3j7kY&D;LyN@yH49:?Fg7; |vApU*>bꗣ}su)'8w;m/@uYL∺gx o`S黄[s}yMWCܶ},W=7{l!#Gıqz=$+3fb{81g&!"ѯ×B#g7_J=mfQAIume/^ ^o绖3QĉZI>|(f+9J>Gʩ 6 ?1 ap)\"˯q@}"|!ȓ0R.SjiHڸ'W7abTrUt5L$9ݼ6%oE͵dʢҏY/WT(VhY?ժjy8zJW+t>3ާ_rD1d`rG/~,Zʗt2譧aM9?Ag3 4?9c3cГAa*4{[ 9r8l|,G '0cId}"&'+W@H+]h%*&㴅]A&8[LsQ}uGy;PA?Ƶu}w喒c,h);9NG6|hЊ[TNbcؒ$W'tQ@Ƽ1{RߛB-sC؊#=eѫ?ìhA@g2VV^ShrTѕ0_mUeWb%8 <޲?-a0Ug)‚Mqpsq[e05;ǨB?Ybѹ7Hx'mBfjS.DJx 9*:3eᄃT(U!C缍:O@$O6W7q 0l%e`THx=%TRuZџX+9Dm5F:>Z&7#6^{zvtgUd4+@*~߹}-O2μl1 4Yqbb!흪k*JԮ(?-Wk4 i-De:ʂ(|rhoHU lZBLd]5L$j;4|B?yoJؿ  VZʴ_2oh/b=|5ZwTUI<,v?$rw7{H:h" NMxK0&0 he|Ul`^M _nx:F] m wҪJf5*` ,XuЗrJvLn'[T&8}9ޤ/k-at9LVňO$$(#{>3HegWlGݔfJ2_4l;c7H^sh/]:7ZQM1Vܫ')'C}vM ǐ,.94֠:H;z;~ΡR +őw5F]|JÐkLHy\L)ZǗ wcyv#{ke:D~AA!_Ώ*]&.^ Vi L[ PJ2×|rd3y9K# dup ,r\8a.Qu<*E:u#~bY֬oN K{gDC2Iܿl\N#c%TFYp͡[6qĹ F>*sPFC,# GOt˾Ghnw'%ziڢ%d8= 39?r;z<6uˡ*M\٫b`=)I6m\"3ʙ \lt4yVmJz5~x#/[$m뢻1#>}E QO|g!O"TK jnE7H`d%;a=ZLK+: ȸO#.OU0N FD䄓ħbM/# ֦T`:C;lAQ?[ˆBKzf^[vAV`NBFc͌ޢ{ b%Aɖ, /}|ƬFJieK*TSR&% 364rEJq;Qجǀ!(מw[Oz f?R ._ Rk"b!>m1z溨%9404 &hN" п&8,+Y!_0)C,e=m}K }߀NSMA_дȸTa VӃ0?f$8>kR`irn77F #8fq@p੝jR&=k pM5М"Cr4N`椈>+lf$~J{d2Ayu% N 6@`Ln^I). L̹IgPbOrŎ#US>ː[w%ɯk5.`V{KI\愞[5"$*VN+oN=eB{AyŹ9QkƆaԀsFR8L'`qk졥[_ t 06k TḬNJe[*ːCZVlm¥so0&3!c+3-6ʒ+ E5絀&]w/kU}G*p !(?J4@ ZXɥӂZ [ǭkL7=2;X-6OwϷ6&ͅP֧9yeؤeKDKQ,S/ⴕI3콐,cV|K7NL?|g?ޭ i MT*qXZ/]Ռ+(B;3ϲáknK%YNiJwX #Rxo4+TnO ޠÐĩ YuHT&t J8G=.Zz޴:Qz{)R1cŻ4ghQgGd|sG}<X䈴R+"/]*20X#V' ZJvb!x!WlMxceoCV)Q!s>$˪Y`~7^e^J)%gXj)|B|:-brߠȝH q.!,' (pgX_w~VqKSjP 'X㍀3@:d'-܃ͻZŤg1|o,u(S&/ͦ;_?N'O#TfXѺ'a\SR ldGI`=oOZÓ9K1rbjvd@Z~oTڧS>'UbBpz?u^vzS_;ύ#w,/[þ[=g/@`o|~%Z BA~D$JWGB>kx p3pjd< lFb%:@٠z|jzeeSdSEU=t*s:؟qF _d\~}*ZBGa=ܛIjֽʈ TС%YC8osub8dxCb]C R>˚D`+NY.,kMpT BQv ]^\A=1ӌ\L Lɻ76 ppz25^7Wy\hZ2R`3DT[KxXQBa꘎àpV阵c؏HHOQM01!]$%џ!%(>wz)y{7wb.Aڎ'yEr΢흧lpu W+>/Z\澛UK˶P#8q7-#e$JY!O)pR,vs`/E H{VwEZDG *)}oa dt{a.+/ߍ`PE@A\~_g>Z# &!)3M v]ۋ5{b+˦쫫sj@~`jmC)u{SL$`1FhD\8JSJܖ!BM>LTͿR_$߷[}~b_^E;лw#6o惞3]?^H;-7 {"ú َp4jD4Y٧*u)h!z t+8jH<ghPvh9U Y@'#͟F7a`LHtX0(o+@mՇaW~/5v S ` <>1tlW3SscTzU/P̵\^s7Krn.HD G j%(bTQU@=lĉf+:$73/x˩:%e Q>muR1uK7zRƊY ^.V |HԎ^KKq}7JpYt}>?LT-˜bˬ`/1\r=> Y 7Scįj޽27/13vG9z'>`AJ̔SJ֍i3HaOEPD odPV>ҿgN-L31d5vpM؉H[WV:+…ky _c&U*kNA_٧>YmzGèoZCp7?1IPD>ߵDV/+fȄ/{`;+$NaABzcf$!8V3i^j ɍ;ߗ 29Baˌ.& ֕n44:<1i,˴%ӛ~U*0u\-YD ˨ bщm"G TToW"F!Ԛ̢-`v8 krɾ#tuyx虰̅_ ·T;;q[xRX7>"ſ`&s[ l%%@2ijCN@| ~wrѪvĞ=B{w[3,%>fǮ) q*t 2 }8 MkhH o^&8vٷ\4Є\@&E I@m1Z]s#ڂHLsƲE@.>G&W W%աcy9|93^c2Q B 4{{( Zuneo+qr{%=GCRRVց mn3PgC$yYgeIUBU:=lUdk`gWްV"3["YnUR_%e[QfqIU n( &cl?O #OLUb$-iNFa%kDdBwc1j}%3r/i)֍~yI9N/;j"ni(.W>|`;RX#܂0U z{ %;{(PZU0п4ޖy6tSM̹4^}3B16\z=oTv/GĊ}Ue1|i[@T/X"?r /昣QÅI\ 9eC1^{h(֓-lr%r=SiModl(>& &hUK?P(|+omV^@y &"RZU؎0op+$(^3u`͒C8nƅ=Κz` `-I[QW A~+"yQ4A: r0gJ!rשv>1zS_EJoObc)$Maԑ~os1ecWwjmgQ͞Gkz`H(5ZEMMDXߔTaOO(nyFOtFPjz?IUm'4^;%m syHt!Ő%Ƀ<[k56]4,x#P.VW±#K<wZRm@jS*Ƥݰ*cxr/mB$"PF3'xjBJ ɯC;($U- ?GߤzIZvxo/fe]x7-y||J_!}DŽ XMB\.nmT.j̈́!SR#Bf-_p6 I±.KY-<!:YoڷM2A7P9nhi^f sϲHhUy8UĂզz K d'b>k-h3ԍ S 5.WѥZMbY[K.ul7XYΨC\qRۼj~^}QJ0N!`_t:S9+:3IJz?׷l!F{4&!+\jW[ٞdpcpd& |,[=5㺡e4)#.JyoLdh- Ow["}gډ 2_6@jT5bud!D„' U vQ,/  5i$g'ۯZsg+V krɾ#tuyx虰̅L? eɨE5u,RLė'6_}nZ0C'u\:st@"i 3>5 8Buי@[_M7O{t[.8Nz<}r{wl'̣e`C$kVIX[W3G]jpђANݱ^ŞpQ vw`?k{}[RxxK^'$_ug6jm'V:%gܷO0oafyxgPd44d v*LHC#3Yoyh<.kOp%vJGhMmyŪ{/XB; }2I0Uޝ\khQ.+~ݸxPzPZ4[l1 j}[.7D3ȃ$?wo;!ϱ1wFS5äJGۓDcf0rE%]s?x3ϒ֜$bwESc]jpMqPQk>d U; ŭV"@WAu#^L8kfZHv9FҲfgᬌ3lr,\وuq@ ;)}Mt^ؿ>z73D\kðrgq|`nרohU%>-] &&1,Xc]`ym<7JُAFk NlpvȚh:*xlxZEu 0*x6Oƨ{3Rt¦r]f~Vx ed>'26]5='EtVI3f$Rcs¿ܠs(9: 9d0TТ>՛6֥9asKiw{5a@$T ,ErQ):+gh-ipޅ܄QcZPQKkC6~冐G_ @I@s~v{F ^B̯>On*\kP&yɞ l 'xkgϙ?tʠ/v.7ˇ ND>{ᕑ;Om:3&I)Vv2Ҽk]S~sXVRx>WL8Q@QV`p$Pͪ..}غE|c 5/k4'\B8nۥ< {=@ŘTxLjo͌!Cʯ/L[؊S1Hྡxحhh9l?rdtVp`N:wrE_V`+SqkM΁S^nOg5`}~*V]97y$ +.>6u6W<\}-wWq% FYjcd㒶^Qw J" K'gC۶mImqUɥACS`?9HHDg%`A@:2gN7S|lE[, `Qv[ai ſ31:_7H.mR- v!QGqUZ+̪8u[3UE[k*ܤ&f7|Ҫ^2لiMkslc?7!L0BVRA֊#mC}5e(?1ѻ*l\]*kPd5RgXLB.`ோLmwЊ# s\=g/@`o|~%Z BA~D$JWGB>kx p3۸'I\ =4^eT*~L >:T a_merpJ%99F}s9'eFH">cJ^0l;aRI"aEuyR%|F/匎3֙I0t#ֈl$-$ i!8Ϯ7[_D]\$P[fD"\ iI o>*]Nu. FCw@$ {86z[U4@2l{ݿfՎQH*_ĔC *ąɅymowBp9".U?6ɹ\,q<ՙs+ٜP7i.-:Xn)%c:J^t(чPjN%:lH|(ΗCc}[pJgc="X$3VQLq J;ָQW&LW5i#XZ9%D$ph 0x࿓l ۇF\3*~9 &f7%7ؙ_ɾ@Fzt kWN>' R>uH_3n'9ĎK~AwkY q f?ӇoODsrj &_<6۞JRct/־329)%US7ekld^q?s_hb#re6w>Ycf +8x,-}hh֊2)j  U0)Y8"XA rlӍo Kݰp"t.Cq;mbyA\)/HKkrc*G ܩY_@c[{Oֲn>0#'Ԉ:ѭFnVmwg;d>(/}èLo:_.Υ,Xܑtn9@lxO0w趤]΀?":UO('JrF K (j;譹~{UB"D\@͒nwQ/@с5X0K^&1g2bs\yoRe1pgM H|qHfG'qXe RfǢjԙIf=4 ~-`E寍㜬{L:J5k4O~Nz7.ptg#3Ĵ,M᳘2SUBd$[R"'Ψp3%`H3֧u }")Guzv8ӠЯ*zf :xMdS#"bտ2_e]붟/;7b$,83'_ ̒)3s" r s 9]w 17iRdg!-VCPۡQwE}koI\O,,K ;anu{HR/M (+u,DO^C͡%UTK8_֋ViUYT,0bc% [JڄR__0J[X [B q>^D"u+!D dM0d/7٧O/TT^m Ln&E_ #шݩXt&9ieV)B)|>d9y!5J4N(A2o$Zm6`Z%[Or4$T ;&N,=о2|~ [m'*.zG0@pdre]sor˵B#%#X,JKaY,VOl 8|ߍ) w܁~(2Uـ@Z-u6_hl1$gqɣMxwrhP3uH#>Pm?1SnKpzp jj|{BwQ ZMfyK@!#`Ȯ[PG=鬼7-Bgb,dGPyD_IEb:S ՠX|Krk5q@:r;N%AT~#[.~x+pe #*}uǃFYE,~v  >`&nQD.2G|b׿kY6t%I^DfJZºMZɂ^ԷW6ON  E5 õo=3M|ɖFC{- o9㷥XG/"|hBQŅ]N=s8|&UīoY3eZot%ӤX2g5҈M)Wy+.5MPn 21/TGEai/<m`:'0:)$._!cۙ aP㨆ɓJrvjˡ^Ulh8\ ZTbK4i+O6dDڷoYl8=w(%n9y <[9y8әyXZ=rب } V\O:9e< ^y:bܔg< _pAQWd6ݕ $rh ;VY=hVZG{9n͉#,T2Cg>qIS_mUMP4Z!D@/zg$4g˜|yY<q9qVvѝ~ ۽T )Rsq{qTU2q$<}ӷܥ{P8TsZ[hkM'4Hm‹"!e$\pr` %,`ՐVZ 9atrˁ=<1!6lwXEHhKxWy jvl&%ifvU$GR@ZY_Ԗ5LF@5́B cz,)28!~?"ocN{T94Áh/cxSa:dJ1Zٸ?W {ܑ|Lcz [VȟU޽Ş)5]vq"@'@ pw< Xu1 4=_+4h?Loe0X6ӍuZ[z9)E Il/a"3pP^lGQb6:fHSK߬. L+O+(qwcwn2piiB@3887ưe\v5Y\^Bc jI/QPqJ/?ǵP0Qc^iK]\zOwyTJ> I D.z@ Vހk&@Ds$?Ń2S6.h6D!n An6Y ]<ʶ-b_mxb rgcu['[X28 k; T$FsF8в>WU⢖$hloCg¥`x=rNXo"KDC/J4d3pv/Ə?jښ'7B uMgKfD袊KRe3P/+YV`,&J1aw߬ԇ>P$BUH}2ѥWoA:hR۲5)Ϩ3cri[b5B[FȸoHP9P_1sp&6G׳Y;v=7< ܖ$Msezn+a 񤇥##Za_Z-4V]D'M8q!!q|.e(E7&5V(nE;Cu`,ɐǙ;Uu-O͒(4zEJݹԂ@kP (MMY3½c5 ̱ M?a5G\Nqqw=ΰ&9NTM4@>~ư}CC5!~ Q PgT+ec/B٫[5Еi=] ƑH]'W9+b%y֋5v vw ꇅXfKLuh-.&Bӑ*!eu;f#|U{re~2~B$s'ltjƲ-bd3,-ǚ z?l;R7e&??޾97f7Hve*Z_Ev)`'vA^tuu ΑD}iz*gF0+z({Q>dYay7nW8oQԱSUY79;`9M#{StHQ>.o/TIRr=G"q]FR&$WMJ1[Z6>d_ NJdT7wRSP. igg|3 pl VT4OtOhFuc^ ؖi,>b:1doJ^ro ӗR\JJ'n CAbv s+n) XUm(8fF!4>?vՖO>G$UUGz{SWlyQһLo/2(2 Ɍ< SbћgdpL.}wյ1Iɣg\;}_ufJRYe{N тIu+F#|/ OMBy弩o#Ipyj7ʿErYZZ' 'n^Ѷ`q!⬟ ;,&ǬP:2ĕ*ϰuLuo-;u+C04. , *zn3zh޻x3 U1%\Kmqs#x=տD򪞜w?"Wp(9!4*SƗ(kd:[pk{ޓbۛS|5c 9xrbZx6D;GhsH+[cw[2+1$H,gq LHOhS⪟ND1cFQ*j{\>7B; sl[D` KDV k'R 0Oj"7X9ʠwKdD07xo5VZ T1F8C3 #v~ Y:1'R8փ׈B|dc<\hCjG]Z3BxBZ#⛣]c"Ĩ@[u15Pߝ;Jp<8rAT!v}F rA]2ͻL)ˌ4ɰc[$p&l3&t֛=r9 Ո9L%$#4*Dl=Ts;IJM^GsLVB z{HPD `/$瘗r\';P{U~Sq:zĔa AlA-dΚ(}(!߾WaL9mV.zU$PãbGY:a4ࡋ9"b/uy GBQ2("_>6*Hy`Eo,l:V;Í -ܐipV}fNƕ@TPNCCV(Dn健 Y)CIm|=o`))=zI5HDlv LwfWB^ Ccv<>%y2Ӄ)nzsz{:a`B)쵱c=¨@9jA0ν vx'uMa iWby|r,pXR%gqO-> Ml]hz`ēa*>nϺhe}% "'4bpi+NBTf'܎%;Z*A0;5 {~Zi"2G|8/ROzSPzTb^  {mΊ~6ZjxDC>i`ʎ:^T.jybm+3IKpi Ln*Ң;Z扶SCz%=R4zBd#Hd vwߒkh|e*vJo:Ge -5<ӕٙl[y@G9vv|[8)'FQhäS/rB"XX{Yb~{1ߋN{6}]v n~}\{u( $gpT"5Әґ8'߈aW* M}m sPh2^*9'uį5@#bx~Slcl!﨡y/OÖ;qnĨ=bPE[G614Uuw+DWmVL#>1E4Ag$|rkE/0 A si@oBL}Bzy0DE}s+sV㲙IR~Ug@]>UTp sbӓHr{ţ#BvQSv}%>6l(=OnFl4uL,P#6<L8 廑PLjV^ЬFHMK4qP_w*iwx&P1\X8սGKsw#p#y-KN@%(?|]\Z<bFzBKfI<'AXwX6?wNL~6a^gL Q:d;x4tF @Pn678r#<^kD,r ܄]]0!(4"wy#-x)%+d1 6oC%5 2Rd%12#c9}* xWXlVt$S sibd4kPOLKN%Jh{Kb۲IE4bRnM֓_x싉^wJS: Q4 ywͷPzf `VBEYIW f8n#fSwN;l+E!?ObоdSHp=x&Wjb`eoC> 5uzg:zc%rVC;#2Ac8idRaUX`%{gIH6ˠNУ|G P͎hsr侎m=ncI+.v@q`IRsn?K=]w T5'43A(QI1|o,Ӹ7V wcT@cA,Iͥnt7A:jMz +x(m%c~-"%𿚊vEꑖ{%$*ήLt?%b Hr+Jl]qm fje4BZ('ͭ_[raτH粋fq~n810IHp ~}lkkx HpB{ڪrE^~?Ep (xTsLb#Conc~ 1V>˫m7Q-lC+2P+r6ſD23&7"AO)n?;?f\;bEvB'd.$AD+E>l!7 1T6)X3GrP™I{)]B.0iM[O&$¤|f, F ʶ=(|n%Jc~r&ΥJdᣨ僇6PD9&9hG4V{!!B5`$`䠝ONYН[enE"|?ݾV_6>#jmV7%xW'KKFǠ8u LL׻$~FÚ(lECUz&L.〈*0x W9M)܄4E7n5f(Z|'7Dy\|,jʎΐ޶2gvFk+s{a66yJ,ܹ:sH7׼Bm>}2_.яZ$~SQh3iga0<ݏKp.gE19xꫲx‘(y`9>DZp%DNot{V7 MƕV۟C8kvL(IZmn1e7཭:] %)B}O[[oZ'E,vvQ-h!i8(pǥ2W\4=IaT<S- syt˖&ahe1 .xq ##];1ݫ&F Bc!Ա Ӽt0^:orA>c*(FF=EW[a[#sv) Edކ'E!#TwŖ4Z:J,)#M`#BNcL :yKS+Hv%<7gQcua9ez_wnf۷$CaF|Jжe>n|II6kzcL*9ɧT^hxJmc>y^U%^zmL|a*^i ˲4rDz S躁oG ̯=$ϋ 6S0cNӴ9yXaO?f*+u/г 1Owd (79W>OЎ1YOV3*/*L$a6Mz?Z@X=-@w~Qut χ%S~;'בOol3z°Ԓ  4CO gٲ=GO0=Nl0(w Re9Mn['$ᘉVl~¿z?<97kJ7إizg;|{7m_ v *XX()f>:t"aN!MA;3eY6|߇z.HCSBvHЩFV,lrMUci} ]v^.׈履ۇN) љ?Jb@lE$yݖ t%ħlgD㷙 ?DMx& R8_(Yaoc;#$ 4S0;bAC T.RA?r%e|޾hV7Bv.7elBxh.pb5Un䎥(ӸEM)AR׶h#Nи$F@hJm 8ÆwBNhuC0;3)>K}z1Lj d.,>GP@ ;:{ =8AYrhZ$iN3MihHPefQcǤgFM[J̕2Q.mMo 6ofcZ!.6<;JB j1mhOTR74YN&H,0 to5 s:'anЄ'$=utqV Uwz}Ȯt/X>@VG5T/A=ISh4 ^º]fqۥHAqSvG1UeDJ_9''%L g=į,ь{_F2ֻ\|rN|91a4%%r c=8 @y1*a>.CQ#:'PJb7qk|ę'{{"b,S Zⵎo0Vsv]u~= nw]E&}̄6\hskۑD19Eݘ*?OO>pLJnTyh>nyω`3a|wRQLǵW&ehKn>KtwTn!6ػ&J̈ s[A:ऻy U(nǀEXӂZ\b#7y"1qu!iYYԌF-X'5h딑ʢ}f,w i]kp>Fo*Nry+`<`cdOo%Ç`l^;J1Թtk4-[L;=QxKDE>J4?xQhP]"kş9T>V7x+%l(|wu$ꝲ݂'փǜk:u "@G,1vVOTUWRMw<58nu5cK)2R:yyȳR TBGl+"b/+k]"oaםJN_!M[2 ձZVt@Q塀ML@Jvlfdbu确_uamУYgO;@PSx Ksɪ1^`.L F~X{_EM7>/~Nk+?wC|tT]b4~w0p8?Ӛvz}P\z}BI@^|9, v"u/j4+y1V?p"Ȳ9.."QGp4aJҽ'l_"j&c~3W@gBEu>1tFe(E p)i,8iXpN({ؑ"1ϞJ$s1UZJ3Fg[(qx8HU6x!&ǧ=,"^8$;SoE5^ߣLmS|#3.A5ۆ<_Hؗʖ38]b4:/ʑܠd5LA19@Ԭ o(l#_,QW)pOlp;T =OS) “Kt$#mT*B,UYЂ,qq_39k Y Հ5<JTl2 z dfqG nˢtl+ƃu&G7˙}u !7%|\Dֽ՜![B%W2N=(]ϰt9C?{)π=V!\;U"& ۖb^^fpof[TLoDA\)ckodb;`}xn4ټ?UqwW֨7xS AM+VK"׏o»"iRl"!RB,!ͅ1U]q_'IYȦa1*+AU5}U&7Yxkoj;#wb!&YثQ14'DaoLg7-ܶMe;3 ` rHW%|^̴{Nu -~qy.ӈu_r4fͥc {+\G)M.&(kL'jz #v5yiΧD?|bG!;0&tDn:9rpK&sx{z7 aQH%x6?WHlSPu#)AM)=!6T8 ǬG]F'OPΖ[]\ o }j(ai7l>/ xg\B?lښ!xţ|&+$N|6/"uTa'wFOx@N!LZgW/0w fd&M9VH_[ [ tȱ6Όī`m=9C bJrVAtS.? Z֑ bx8S╈=d~ ԖDt*$>SK9KIv Z[mBhqqNʼn'xFSW;!ndCPL$7N(; n)dLW݈mlX ~F" uK!rʮS2ax/ҴOsoGFw-S埼!T j{7nd"qIL~`@k_4RO4 iiiaCU8qwBםG/s'ҩB#[TvQlj"hP ڨP}$@g20`tQUa$,֥ޤN6l-tfB. M]X>j f]-I#FA+E5K耇 =`nk`Bw@j֣x*@񨺿vg^n*Rd}ʳvz. p.䪌謔/$Ȍ/J/5d0*e*Sv]uȱ_ ,c[81% F6'Ǝ/^ xE[,Ju`gd0Sq q v=STzqFj4"mO*m $%~څ*3!"eۑz[UiMZ}9> XMt ,`9 4jٺ^>%s( ,֛O\(|?K9 MDwį>MOu> Ri2SZk0fUDR0w1zT X[0C s$$HYZ] $U6ǯI) v8?1Es-Tٲ_Y͆CJi~.cѡw =;"}1:ݩ[}aHoWտD!qtC{i3]g3 Lkwo]eoE;el D-ܲjxm) 8K2󰇔!8-(JxJjf4@r; *Tɼz(ˎW ( gnN $걭 ;>.[U}g3#5a<\}f̷bf/rYjC z狊4_JE Tp3`C|<*[ [q- 2VH:dl Ϫba8X>~\4K{8|5˙@.r[lu{&5buE9, gc<] q[4@J 3P:h=ZJqPxоށD+Ts0(>34/Aۉc#ߐGu]/=4*,叓Z ضm!2B@]i.~V.lwITUiĦ5+n1kpұ ~\ko A?:A1 "Ӊ': IK2%9> sh"7n3w]N^89= /tB86Ru t;lEC5p>ybgCm[vX<˖ڭLQyQiՈɫ gl(@[0~@_;uo4abtX闕{]yp d Ci1ݷ%΀RVDL v,۝L_e)l"M1yESADᑧFo7٠=ߠ]e~r#xe>a^a%= 8NQ1o@o6R iY@pm.zљ0ڧT-=HT#H/RAᠿlaVJӧ;eu ["Gd3ۙ|}yڬT<xDu)uu\ֳ (fmvJ?[uEp:_ 2 z:J"J}; f  vA"n/'io?۴@ӐJ~ L5dzz-=z0faiQ)9 Ȓ|]L]ycBL"2僺G Xd~"Ռ ?g7M$}zS|Hݴ S]ȇir`KvA`x{5 Eɵڿ7(cKc\̖|nn0Cr"k-8~Ɗ[} ?3oߴ@E"I竣lgxg՝Cgi7'2_-CشީIJt!mB?T{ۍԖlxi~eV"[7S$cc@Oⶵ潈b? vQ}U޲vW5`e+g#b2)X)Z@%>L20!jB6W+|`4$d[Gw:!;lk+Jrc!BzY!`b: @~p0q|È< cx -5 W>zRDIEM"+8HLC3{1ġ2ELb?ˊo\aEj#`o;Q1P 11p ?0~j;O$ID1z!PK&NNwЏ!noG vb潝Pգ9 Bc ڐyL)r s&ܝ}3U[:x %`~]kh+H٩CX(uy'WCݷH iW38w". \ķ|ds0sjzp7[AOticW# fpS_⏃"`8}K% n"(ՅEݸe0M[ G=Mc(51j,)(WꑥDC< ۨ -=|T1P,zhmIZ[qJ Ϊ/T=?',U4RonҤ xm*=8Y, OIqXwo >|^4Zwʵ>Dϗ}L8Q9@"Ӿ7I D,f}ђO+Ke#gcL*W ®?F)!nTLRd3`XnvcEI[# E.4@zo8\)DC&p[j !t@ιvq'Pà!.JP"3~M;^' ;;6E\!/8a]RzU咹yKlHJB*¾+΄0{WuU`[esa4v*p㳭r^q:kL/;$F qݎG"rD@uh#g` 5lfc0]Y-f%tHOa9ys\p=`E]bP)AH8'RfFbzƾԁ5iŜX. @-[ -Rek8 WUqyej|hiQfkb3 mjSYAzZӺ> p֡V h4.<'PeSdmAUx]CG{)7!k@QhOZv>AAQG\,utԧnUMBGdpIP僸b!Y $;7ސ@Q빼MZr5^OR @/A4B߸9/+Ggdr1Ò4#SH(DYW"w%|a(~3v3Z]dY_\I }~-{5Cc3H*ӭ$=%"ΛzӛmL:`ӱ$Tʑp/RyZ K WR$ذo켕ml KD=U0Yet2G8a;񃪤Aԡ1ȻJT@e4.Km7t|rY1Ìyd[*2ZP.\{Umմ 1`P*[f[?N힆^/VQm]@ӷTH N6ĕ=T E7]wxNz#n6G*Α鯛rǪC";?ƨg*sc͉Gz 95^yRE7rfi7⦐#lI(_@92 8MRsi}4;)ZZS5HʒF(>9@9;ZړFg>9F -z/o ȵJX'ui-c<ɽh T*8IldAYc {p'O)`v['w⣊/XOSRіuͻf&l*c<Ũj؁@h\ #4.FQN:<- b}Î  !v=jZ;6,\h ?^^S!u]>nCK+l<)~5S3Cr 7ǂj-G. (>k40yk{Efrt<x-tKz;8eun2jk'#dJg"cޮG*jB?yYQ,?Wb窬 S_ mfn S b$[knM`xk3+L`/cJq3jLrs[d^ ڻ͢ }lZJ&նDLT0`F=IaY ^0s*r8y-L&x_3fG׏]QsPۢ˶wj23RVҵENJ`$HЫMԄh"-,}% iLQzˉ Š<&yi0Cj><:!2O2͊}'>;kVD#w:['eufχn쫳}=9Cmf;*3eŇdW|d^` JO/j\$mvo5p9PL_l.Rx"RxVjJԉi70"d4cgNq_.6;>`|BDHd/Uüln>] ;bbSyJ9?̓韨<Q[Π.8AӛC4󙆠9^{"ZukUisL/w-*} M قg'D \T$C:}ox0^V_9$0zk6 {a(ACGF3 Ɂva2r 3(~!^n7z =l'VzR"'."$K5BAuDŽ4,P-2gzWrwIoL19 Og/jFpMxG2{~ 0;s@7!B1at v(/#=埠ti@ Ĝw@I{!Xdnop1ni5TT gvYԹW`N,iU P R[42c/[DG %XMqòԼo['(P#Lߊ]],dG ܎vaI +]O^fFruIրp4htѹ!7Kv4`Mz /E.7ULSnh2|dk맸 ܥjRJ=|1_ iϪK|i 9\E7WRȰM'1y~lYT z藊&nPzj#a)w]P8r-! Zo)-}nLS0RqKBiy :IڹKG7S~%y^y4}&}h6ݰ~)^fQ@]8m-Ű^Q]jˆHUW% O IMU z:Ee&U//P|ӆ`\%W 6 >x_fgm_x==2upxV7,b"KԲ M5g):/Ԝm@,0?rv<TR̬Tf]w5 _XRk6&^2Cԥ!Qe%ZNʢ/B9bFR4~Z& Mm;W]A8PSy%dJMD<Ҥُ$40܊*3MO ؅y}bU _֕ t仫z3u+2ݕ*\k1H1cI7l9ӻ ϽxrLk }425v !L)e;n6p*Uۂѡ >~Y] r+cdKl1U{{,{U亸tRThFUi,nVFW_y?PLq(Lz!nI'GuP0yeXQ{:;&(#:A0\H3YiѴv vyWoル;G.m-%C-=4j|Cp[>?xe)\Wu.{r9C<ŃL|r6O[ctQW" PgSrOƯ!nFc ]|o_ZBĖ Wp?jkGxh6vYP# =x/Wʹu_v%W;u.%P裒F7^Ф#`܁c+Jif\+"R̖GG30+<)(71|h89=T _d'TMsX7*bF3 94,s6<˅]x+5/s*zAOOpMZdkNtubA)o RHsϺFE-۟'´mo>*(}7@dn}W`%yvc_؅v%4T8|15s3PF#N$thlbBOnʑIYOwu^_RgHc˵n)A|td7K&G1V5E6(<#C}ޯ Ps &5^xC̑ 1ڳԯV3CTPUGZv6`<|Moqg:a$? Ǥ{ɬA'Fpzxb|hRMhuF#Z[e_l;\VH & K~>䰮넔o f$yZ+Sְ+ʡЏ2[a!v1; `%zSv}%/^lbk~f?0Ea 9/?va˟_{U柷LOV;5X3 ˭Et}G:.\1} Y6E=LfeG W!Ydѳ c\. ؕ`bL/LW9g!^b &T-F~3ź){_Byi#wfcS&mE#*FR|c,AiVsH/Fi$>7Jۡdc~J1\> Pjx^3+Qu/IRW`r%"]GJY.8[봷5 ltH?~?'WFpÊBz4aÕ&Xbuyi9'X}] 7_{.r)N}'.vy(ai?e 6^0Lb &wiEu'z1~s#cבe*Rڐ\ͩ36p]꺚,`9ѧ+Zɮ~r;y8Œܛc6yߛ r=x@ʾ K˻^4(OgSx?ڗbSG.} + zT\, IXU`*1gkO ('w0Ŵ|5Ret@jg~Tn|Kjv^GBQۛsѡ?p@#0{v%Z?0g}-*rk~}j hCKLn-}F?Q ӪiZ)9″ d ,Ls 6{77"{ kXK~ z+eZ:NSoMe%ˆgt>Ca&-Ov ~&Iz(zA ޚ |%cZYzx9 *҇[?0nCG! A Kp?e-J$;BHk?ehF|@,rHdEQBޝȦz/s` @z XJS7Or8ӦӾ0iD&Iq:gmi\( FƃU!N!}<(c-RA'~kB W6˱ߣ)6p]`O5S_y&]M<u%ML. Hy; J,!|1kv|/|UX 썄OWNןmN&}~DBZLrJ3G|d\+= 4I`=ތ90<bZ'ɮ<*8B=m#˔`nL5N3ORIZ+Z^ gDw80mǻMq|X3 9K᭎F@ԀkLDrMA!ie,D Yl9χ̘3Zا|)I>deѹ%,Yh]op,ZQ{!(1UzH|Fr`do  =uOT)AM)=!Cb)_O#q,cm*S.ΆsԿ; >ײ%zrQD,pugA>χ5+*?bHN4f+a_S@4;ڱb~883θ%~BdT~GM+ *F__țo?3.262aZ[PZkŢFX yBaA\1iRgjktuC1"x#}/SElcQAȧD4tbYH('w`%\[z Ź08b.D}WX }geǝ}PѬPT^(vl_hUmV듛 jqpX),)t)t ,xN6btZ?":H[Ӵ\yȋ Rs$TpubЉ^,(k↫q07 8E1rh ̷ (`zn1Keo]9wr%>^={|o\j {&$wN x[η^8\f]Np7%F3 F |u#H-5 a[(EC5}bԘ(6v%'mtXQǠR 2!}E (Ax~kvnJhcʸ N9^cﹾ#dM`sF\EU*<ԄYڳ|Q@{,KґX1-TܐmpM)чd;xb[0ZB[5mBDǀ 46sE(aBȤ ǨhZw hfeU~[e ]vgNGި. _9GKg.Aj(ƭ֟˧j7&vq1ᬤ[5@5ӠW($X@)s(eRI^JPJ?F ^]u ɉU- !$΀ugyK T?+qh0`qCp_m na?Ⓝ 5~_?^6t򙲼 $g :~k<}lj_|^Ŗbkgf^H#Uè:Fbp< b2R QBHQ?Qzn~rFcҨHɖ,&p4h"P%yqPEe~+ʏ$ΠXUڝjbYhZأom@?~>>Sv3ee!&d :yMVTf@$X{KI/ 2ѕ#I< ۙ6&Sn{V݆1os ]-^2Lr>?O+Y4VT.q< S o/#_+$S3cD);D>}p'7)Z*(9oZD%ƷLH?n<:,):Lc~ʍp:8ƉQmM3B۞'J$u8%'apEG- EnN).xMmȵdStEIXBWk+ EPŋw($6lV0ryV`g((kb:|3ZDZТX̋k$+\=ncHHAzŽz51dSLiؑ:NBjExsmYUqu"nOwe #f%E4A1}i{jc'0! ^9sn1lNNHs .ҥo)'_}=\ "j-uO;#@Y ` XTQMOd:N+ȣc~}Ru { Ns{RoѬ+; _BF&ؽd }]c3i % _pa' k1X! hk "3foMW7Yy"*n V ZJwu}KjD5P1dԤgF߸.[ {΁(`P~<&νVYxٸ(H[~&EWgl<]t&*xί9p35~k)ߌ5cykRu qIV.0I?Oe!PPEkFyT TH~u+ּܜMR:&eXфT0m;%nd-/JnԚ'Xnc@wLSȺv7NA[Gm+?v{IKf/ ̩>7f3{a@ _iwr h@[c4elF||+ACh:b roAN;1jSVgb]o]q12M*g3u5\}G4<$-\9wSD! Ȃ$Ez2?bk8+ߤy#_fe~@6"óY+ec>`A{+ d/b65K]r|q8z݄􊦀B+m M^<$iU#&vm>. L<oUe)y85ڻ2>JfH  >pH/`N:jNp q@TbEAƊ% bߔѲ6Q[w~DN5K+"aeNB\1ZH,j,vGegщnD&t;gxu-|J,!pX >?> G 9PE˼(RwiiP3@^,N`fR?R2OfEL>7Xx+cdqϼL$NzUyf@)_' Yԇx[ 8)1YRm&'dfSq+7:'Lǟ^t T&%P%cQ`J;g|6.m F`xڽuPI RsRhV_u%o{ SjW';0/;h9Ҁe|&L[X3/ oh#JcěyU.cUd"^ W5ᚉlH>im@$kl?rL_+< *YLM%&RY/)!kllw$y+]D;keeWM0%`*he9l~E?ɋiRT]p`@Rs';Ϥ\c*Gg` *v&p/+%.(Kz}/diuѤ+yT1q ]$g4g`V*+l2xxT97zzYvca_.uN!9dɄ˭Rm*  9d<tR2q2KJ6FWgjg>'9OSؑw~eSt$%v܈;zlypB@\^GP ܖ͏N'L0{\r|?$^c/&y]@,\s5}yLS" a'b {- ߔ T(4nb^(H?rXdұabRPrg<_zn߅߃nI8BCjO #8hru5꺔T}ػxi=Q4)A im P'o2Ouƌt[$/}G@-:fj2vO8FE]N5A@ "  c(!)Of UjQWDcJIzMkܫ HF*$(ɕo*'kMHrr^x9 ~`Ӧ?A/[g$[I!> K|dqЭD0 8ɿEO'`ÿ[6 SueLӘ &s1p0.TF>ڲmߨfץ=,(eLAg"s6U1f{f\}c=4oi42pFSK^-Ae@hAt"@aDžΊKe Ͷ8HhVFwKK+LĐ~FOʦ~U q^ tDP 8O홗ÉɁ<5qx틎fa%dE"^_,᳈=Md䙎(~ ,fP> CriAثhdE-*|YfVw>ZR(衋_.{bdDRӆ"3 XVS5Nv@Vg J;+%'u?ҍ,'4LmȁOe,SoZ5/f|V$-t&|B<҃kdb BQrnZI_mDY%G+F+~E6_n(:Ӹbvڡ0qzk>ST#w ueԳhUm*HqZ?-(4pda%@?q5V JN,iW5IuloI֤7ҙ" 䩈CoTƱxX[0>7 ^7Y4܁lNm3 p4#[iy^rj%=͕U吝u v|!-Q5e FB+.yJTfWfۏ8zVK[!@чy!ep3_b'VvM2/clmR"4ҷJWE _>iAŒ %-h ;Od[S߸ߩ.GLvcB9bJ-WF9F"w q( RhHLDb޵4ԽL˸Cg(b3K8y%&5(+Lqt1%VB<S e,=uP/-+cqc?NPTPlm|7Eʌ#g(QV}._"{ݚA|%P:˧eBa'Z]~X66X@GUhHXo`%-Xu< 5aCk  3.ط}3$7c*XjhS7KtnR$d[k$]_{卫"C,ID@db c ծmyvLDVh\ "L %mEHnTL&^@́=Ҥ y@/`CbC:.#׾?"K^~A,a<qP8S-N^Nq,}"?Ju y(lr䁻ߴos1#͞T `B* 1qїQf;&¶ -,DS=Y &Z\"Z?/ (,ffvە3oӾԸrNoW(&I JX'F?]kWٺu ;ӒMz/'7JbNfBx9U 0 U*#J>4eWÄɎ񝍙bgci$=ZD9bU뮘DO,XiǠ3[ r6 0SdEDZrG 5Ne> '+ p bx*jLi<!,S|}TF#LtWչ'&49İjeڒ@0Ѷng"qǁpHou@d3bfM՘nҠ2j]#H`p*bvRdQ'Bm\3;dA ^XLB`R.v˥(!m{jSW\ ǟblihWi:jkY_y5O-< W4e̵l3&+U0A& L@@ n& ܟ!(& L@] i`GW-.M^)`%ۯaѓ F<?[ܛO~@'katdR'7ඦ L qގcy,f~PuA~%ִ zDXWiJ_Ԡ0%6>뭝xc:}*2Ps: \PCEݣ_i7="I҄RCnxD]fلu(j4E᭒ch,z$=U$+ۚcyy:;ϛҲc2m ɀ> `v) / .篻GCE2WϢtSa 2 _18fC~EOCdd&% ܮX_R‡NJ!_`C gb^"rkE]\Af"nšc <}vy+۬9sM8 ֛O y&g5Ŏ{n eH_i㗝k]meee`To@uR/618!8L>;QE%Uo6BGDP{B6i lG G݀ &[ɒ2)}"g6E4Tyo=I5.4%OM>j1\'W斪W%vjګ{٦tHIaF陳?FUabWo_2Ht/gh]l-mIX%H;O{k9)/V5KDL gд$d9(Pgr-+sk"UXlKJ#y ?Dp?|aYf]"0H:OwJԍV`T\ɫsa%8jnf؅EJuͭ//Kn\Ӝ9C jҘks"RxT55: !ZTa }-һ)ONAK{[I& q& b.S>xa+{)BJ AOڳ뵨d( ׽ػp-3NIg#6g~ Q* b=pS*RVyY-LtɓlAw*lّ_MI1GW;tXpt06NǕw̤_S(51})t0eK! B1-fL2yM X$"`#7x5o03/ڬXO?3zV?Fsq ln@f{^,?t XLrs*W(D"~(lIcByf%?ĸ#f,7`Ght&%o}K~4uJ@w.R|a'oش]ggJ$oZRW9c㦥QhKtJ/Nɡ}^ġe[ 2H] =P l71.A:|P* O1T(8.^ƾɿdkTR+yW֨(^)YJ2J7j|NlWp (Ctω,9E޻a. ɥؤ{Ʒn5\txP& ` W|^%7ٓ[,0jҸ ?6r?2\%)K5T`,X] RdA "ܤ:h=SV#;8dt߭=M0V qmm' ͷ!LԟYs4xX5@ 7W\Xj0_ѕYf=~C|-s&8X. p3iZ' nnD3 3}Ɖ)P:%U=5YfR|U]X;(xʣ44|jtozènSfhR[$Hߚ(6r;Cscy9,MPy>+.9W]R['NK=Nt(Z+ &sY`IG{~^^T?t4wS?U6:VܮU+tnPP6*oͺf񜰧숊A-|Ugzr+fVRe.s!Pz{B}e襽nN(_= _i&E k|X3?s/±#|}ݏ"/`,b.ݰ96 LW@T9l%_`7{W\ 8bi@ksv4S𞍭4\ D, m攄 qR_GePѪ+}x&6<9t $]ʕCt2a"ݎ,\3[azEr90SwHԣ,wn-DTci!['tP|qa"gIɥIǧGhsz#Sq*Bu9~xrb4]93nQbU[|䄠$^h7v ݟ֦a54Jg`H@uO"L:`TpѧFfM,TS&$3کE?c9=R oS9ѕLYAhu<eX'YG8 RR3ݍnR2kb[4@>ޮSR$ڊ+ZҮߝ[StB)T3&€WL}w }&5 >W0NJhXovgjoDEf2fP}vG`8J-lYB<^W<z㢒 ;e?a\iyKݨ6|%'vz`S^ WrK%&Nńf<[q Pjvۅ7^K/i^9jׅo>B[yH>7#Vg.a`b9WO_=cY֒!ΕMwW TS\/vg.锉CB>&5FJ,H!>wJj"*:k)In|'/>Z vܫ\8 6T_wfhlm~uت%//~.l/^4wHUvhI]NV[*[1:t;-Ę>?4-X(bT Ǩ6t $#gO*3aZ6Xsrf#OfpMQ)veKN \ti;-k j_5͍1teb~tvD8/JrqI$tB Eg m< ;\ [foF;r&}p/,BHL'}'lL\BWti~`#(!:ZVVoi w쥈d襱Žvi @E[o3NA2/3tD26i͞mYAw`vE`L;˄TԘ"w!)ISXT~~@l 5Q97*J_,V3>6=^qVelsw‰Jf&eK+lΉi1βovȋZGcaq*ВDZZj%_xWt V7ak=p Oe$i sҳ*;U0ٝ3S~!PFfĈ Uf; 3eJar0?Ӯ4'bE{ ^iZ&ulXXg# ֎L6j5; ; =6κynL"TYҲc\%˦㛡c X}_׺#Fhd+R8'@;5i-S7vzP A\`EyQ%LO(Wvic SkCwE{| u L)vP!8hÑ]\׎"BUfuƜ#*3D*ٓZ>ڧSmf_=9ٿ5VTwֲtהo$n:izhj~bqhNj^|OXi qJXE v50u/?'g8iqy$ )Id7 کɎΐ޶E>ovx<3m*ڇ<4%=WW{J˨,f Qͫ%*eʓA`l {t2,])`(<3ˎ0FE$UHT>-p6c]`BP8~K+b*5[\~ Ch_Z{c2 i7xJaꍊy@MEm}o_^If-^<ԍVPR^.5Srt"@ǹZ4J,|ϧr02Qx"oh9T1ub/ lP7古)/4_IBש>8iRrmLBۣ^ "ĪE2t0ax:JtwAX}E.gɣ5Ԃ8;Ja$U䜝ۢFt>˂2YMty5"lklj>C7#eEp4kv#c|`֒%-Da \E ' )|1=WR)v-:W&. j9o|]{.7%J#5Ζc#3P&J6CLw%n 晪PHۜi! U P~c(]b?-fPq[QPvJ c75R c2mFH[4+df"?3 E62OC:GT&Љ2w'v'Nkzg%PNi:t-7즖 cq\/3TWqN΂f(˱3..eQj*2QkikmoGa,dA?|Iq[WIQo.[L.}dXΪ+~񊱌k'YBsb op Dn?X3i9R'tOjv/ 46Ul. вz* ;Id$`.ﶀ5ڂM3IܯFM0-# OThB -ePfS $[& =2lS^K& #md{p;] Ikk࿧oN/hBb tdJ Z@Msү۝H blyK|a*AB_2lS&ϝroZLK0ŷeZ<[ҿ NZ.ONPurvRa8ߞew,i S 'cyMl]%z6y:&otTҿ" 3 'VMnM}#"Y襼& Y`SKo #Hs[I$N?VM%lxuw'((@AYY`^+ C*5?L5˲uQ<$&c4du,4E t:V9T*q, db`P7+Eз/lqaN:g|An2I #+$A@eS 4!D|`$h͌k< anp^9Pl\eh@Ѓ RI"@R5Q7ji4 C!(ܼ ]*"+K#dS"R)$j $ /iEm!G5/qBT8~@UdKWƞ\,57F2-jnq"i'~#>@^}HOGѝ~ ]>@^3$m%MLiH U[)Lj|O^ #;Q C[r6lc/qS@AƵ5CWɛE[ٷ'RX{/V' otj?I@YTKdoo|Y{++F-0BǘC'Iگ[vZR{n `c٢!E"{w|,?@ VrR+٤+m8Wv&|@(G0T+7c#'nK6tжú~7q^RB0fjr]pةXtQMMKؽhPuFV]t3G* n.czj! W'ne-zXe'Y>z/P 4b^]ȳX[pRDqī}o Jm1eı);3d7j>ynwϕ#ҵQ~-e;DBK'7- ?9Jo>^{ܑ+i$W?H6Uܒ<~қNvoiUXX3JŸ|Ҹ}@x@|55V)@PKQ %ʟ$pMBʌ3tGR,UHӀCt!M Tϖ^J! ƋnT2e=-aK0KvLփ V;k\BzUb&,x'7 jG, q kG;<;S*]Pf|ߌv=" 8T/!':6h-X1|AHo7ULzJ-2)zmo-#Mh> Jf\Y"O6w@ә .ՂCU>o!zQ`Nj4VY`l>8xiӆ") A \Q"#kPvFur 1t "bG;("! lg(G!QksU0<"ۅ85xoهhO٩Ԋr -j-0+O_XO^v+A޸ ½nJYG˅'cRidۨ.,w~( Ԍy4Df0>EϨtZ`4+RQV-K()8M,O#\!kP"bj羍lvY6aG/aRߊݛ ƙr 6lZwPqm5) OA[ [ħ Jz\Cu;--G[4lbr}EL:koM-P72Ԉo$£Γ@ 7k p`kk#'Fzag'& 5:)Ny_ YNLd<(~H*٨ڵbmSE2~`˘؞F,ޜn$`vl?Q}B/9U3WLR:!sde^8Yߒ(༒9Fu48Ԭ^e :^}[c't^q?ףŜM%k ŕ"`yIEER @h 4զG_6K"AOd.8(u|:L5[\k^Ksrp`PU5q5~/ښ*O{ד׊tAstᤍk0l j% +-#n #\yw:>lY X5M'Q_jveA(}gܔ>YaT/D\ukzyڅ kk5|QAA ˰ry-ڞ$v < CnϞnWRO`Ge<ݎչo-7Z)aZ3mpV.НYKؗ9fi.FU1&FY 3ƯVMf@~B+Rl)"(]oZqD)U3L~R" R>^E5Q? 'gjKSn*-~"uڌQԈ0h+Z'bd;}}|^%b7"8bIJ)8"zɊ)\6,GlBDyiEP=D\4> Jom+*;;;v3]o(, #S'scJy]- tv"u0ϖ1S}k:T :khȓ>9n5O7(` G*d2 $\fNUp=MS@[q̤4*" 6ӛRd.VRFgEK"@ߴ&z?$- YmRB:RGJ[b.Y/j lނw=,g g {f\CG_oH?s>j!#Y '6 `,zǕOg Q~ʶKgKN ݶHEsQܶ~44ᚯ6=w/#3d/v t!F' ̯_Ϟ~a堚vYKrVw@[ nXM:̜kq,CEq%wa04z y!1vsտؠD-ߪR'72*HP!< #oţ0XXoC fPTK;Ξ~,sqkJ7#ڟʁ!3^grNFi@)"0a}׶ł y] fߗ"&AW#YERb<;tC70xA(gC;)۳r,W2 =?kBi>h:pͬ0CY6{&[ xڸ;M=k8=UX"QvC 6:}d0u1f ;QVb W(]fVŸTF m;Palb3k ͬ4JbS`D}ˎ$1Bi uOi-YyY0 8_v8?lV$q߷y n-h˯-" h쉺pooW5o [;sщSB10(rFc- 4o >P+ %Ӣh %l]H⁰^ɑ%1zd.J +Z*$Ii1އULzO7/GiF/VU097/Q~NUj:${߀I84ֆ.ZbF /Ʉ(ad`'v G|s?˷Ol{;EtkN4R~pՠ`C!4!#6t.)X+Ƅ'Y6a \!%pztq/nfc~ (p6>Y >`";]ו1헟]pp5˔N1@F_[chC#C\ǬӽƋEgh`1^)D<$@B'ު_UXeD+(w:v,eM,`KFJkIqS-.3X^lť0:N,l/ 1h+r$-&aU)qD;J\}ZxoZa;(WQ&E?0(-4>(׈Q' VZ@x}b,q7V{aEoQTۺM-4/cܟxn9IJ*ٗN, w$Go)B5z ZDs@E`+[Z@o0[y i` 8Y"6J13ںԵHLqLiܗbEtWeUKopfqL0D8Pf6JA(:kO8~,d$e'Rh(:PoxkhL>(Q _¬I#5&(w+ܿ:{įO }rH 0tg˓YD1qgi@3aE TfmOukAYh?R*Lcu循Y+1[H}F[OzfU}ng!S-AbU ñXKy"ztPĚLW%-AVE&Wdb8]bn݉+^,D$E"h~DQ%4D-+:2V⻙Mk]fS,RK(0g?&/>!1'H{HX]Z [+՜wK Py)_wnG%ao]#Pyૠ_D`7뭑nȣ 1+emD'Z4"gTJi E^Tڱ%n)x9E8\t#%D}8>rew v$s<@i!@P;:X# Ss6vVo@Ͽg3IɊ e2ڦkokLl3hyR/;:@~ [1d]"/q mgɡ{cyEX+z|փJ9,حA`[H7Q]6݁w;'e,Ж-H"~ ~?& 3(v~<%eyĠ]'AzZ^a4_S+"kd\hkf*gyN#t3J埖gLvXY} J hyz[r ^j#wuhTGfW6RU˝Xpj jU'5 j` -6Cv}390~s̉嬖{,qjudRoYYߎ3$Uĥ Wb_:*EO_}G4zcjw=0uz g~%(^=2w 9l܊G{ N,a!"JPhtdesktop-1.2.17/Telegram/Resources/art/emoji_250x.webp000066400000000000000000145724621326245125100225250ustar00rootroot00000000000000RIFF*2WEBPVP8X  ALPH) '$HxkDI۶$I_3 ;8uX,H7c|cjcXb'?I?,9o\?'AxYooO|QD ~br#Yғ{\2o7yT6%KlؐRn\BcS˺R_g=o9lh9͞RR$,!XP!%XCNpSoJ4KHjeSЬ 4夓S'I'N&6E]P +ԩ/R'srɬʼ8L8u^'S'4i&NI'?3I6mj۶m۶m۶mVڴi&dョk֬׼s D$鮮&6WDD$m"V#$ݭw-BPhBKV%n͟ fwȨȰ1SpGnގ>J$5~y]lB"X!`[Gb  psf&C;C-_hP+C!mPRoG Id"+V,/ċ`2DڸSq3._p=[.' |A28z(ZD6yG`TR˗Odnotփ_ǡ @Zp/xȒܹbx2^hUn=l-WL^@~jymw,\L"\" v>qZJ sQ8^QO^sNS=2BHk =gGVhTI Շ/ݸqfnբ|FW&F.:|ɳ'O%gJd٣'O[?S<~+[񖣗r-{Vbk?fs/&q{Y]dɡԘWɎسhhdK[I`иT ̘{;։Dip Gg/~߿sJZ~Nuo]r.@,9 ۑq uZt)YR+^` Ͽuk& ><_κjWC[3KX"Ɍp~NJ9}Z=3#-ح+V q:X5}9̨ќra"PDlu|ja's.Wt&U+ZٲG?$@]ɷR88-ljp"+W_<狩*Fsmm‘j[L2u;Iޥ9Rh۟E2Ŕ%Î!dⱁyANdSn.B~3??VeV̺,-2ymV8`5-e_1ތA$;6=cQxf ɄB -1NTbnK$WsJVuf4n -"R4.hzTKOWJ 9ɧ9 }'3I7^ڞ2J 9c+V.6$k"*)/UWtz18 GRPRnc 1vV8,}f\#@7{#!JO[L {\d;%}oM%E!-_9ɻc CRsde8$/8W[G̳T~Sy0 a5P w#.UIpK80삵a +] KCdWoN)yNG]=]d!H4JW:nyfCS7oɪh'Mo2O^J 7:HYN' -DŞ#➄ћa+\ذ͉l)P1 \mqNASPrirV̂Ge1xКLeTD% Rk<5B"hFY{\ĂJP0x9+zޜI#; %Fg ()BaI9Z]dW%: 4=s֍x%NYBfnxx*pNaÃ/E7^De󞑰zTèkSeUsKc(< [$aqocnZ3=LIv0( ]daX͹" %Hv*}ծE*ܜ = }JiFrz, O-[ֿHxT_hz)%'X+0Ȯb4w%v{Xe](@+AVJ+:{.Cr7f2N K#d,5weD8N+:w&F)V7T4_]+[&uQלdq}u^"ro}]p'Qh!=WHt2T"s&iT=QFv psִ%6BU%iR;+Qj =[@`@Z1v>ehDuRg[ U](DP|CPKmM#VƀN FKxZSHg_ 267 +pJJ 3ҥbd 0O#(>% PF5w+C0^wU)B~ϥP.%u:;F*2 lpR`5!ItJr|"cbM9:]_I=FRd2e^H$w5G-!~=LnF7ôDя 2)섥`!kDY``lmΎO.J4z !m8LQ794@ GD!^O8PɱI4}ɴl> >H4JmuȲT(H*〽BeF/%*z,8^f48%پ4HȀp f*HI^FdF^ 59iE泻.i!;ITZ Y3SFߝPm2yD cQ县lG$dQ-A["=4Mf4ARlr[擻0EjC(Q鈂d i|ͫTi'Bo`9c9gDu&c.cj TjFveޢa'䨸 =GٙvDVCtCy2\ϦE&HTN;%BO9+5>bs6#yDSM蜌rzQosdG肯!BJe,@螌BL{Yt_vD9t{e'cB4LRJ%e]p;$Qr^@*  Lw>AŢlUNۜ "[*x cu?rNJT.?y~^4d,xUFJTJƴl> t{چtK\$i^m/2+ODN58.t0 Rq]"7DKrvY'+fITJ*4lu.7IT:qH\Ƃ94L.?(Yqra)&ۜ 2#<!92| 栌-vHT:W'QQ.^*7P:EVzi|rb3krDCJ 5FƂӾ^df KZ ;,zsFE0C<Ųd;5bb|JeKT+0 KjDL擻]OL_mT/hVM/2;J TR{EGslGdf3d$ت\hQ r-,H`y+Cl V+C·vzh|r'I4uT':%gi&1M/2 Iup 2Umyx*JGR!-`EAV%oVObZCrMֺTIk㰨ټ'3S෌2Y]|l> T>ͥuHSr9\q Q+\D,* bhƤ 4O>T^ 6DHMW2jI2Wd WrIYW2N9XuqN*?Kw7 fr:i,Q6(;-($#yNi}xS(Ui_SK%nxx2 4,WƃJXe WD#dz͒Q`,Yvkk|vXB)L,LPjzHrnjz($*s6-"4O0͝ՙ!BV Ki)sD52?(1ON9e H{{n4תm]y>0CIr]%LC^c^dh I*p)mΊ-#<Ռ5\Lڔ Q%[&=mځ~僻W`9$!n1eT/ܜHJ3FI2dS-vSmc'ͫb%OikYs`\ߔ(>}ErL.q :/+@Z&៵.jv 5%c̩ťWDsD>f`AdoFxx2 o?Y B)^m&{mZo$R8|'@$84W13B2񊰙 z=Se Ȉ|[Z(ZpA+ibo/sjzf W$KbY), ps6 X}<@NftW9j51=$2[[:u+2"nucqFRzPhL|,sl.dWiaSր~IzGG e_%}ۯljQTk:i- $E2{:l'V|?n̸X ңŏUow4JHWek{'He`-CkF*}GAz5[y /|.$]KJ¥EwQg߸ibߊm^(ܶLٓ>1f RME o8$5i|YG93<?8$/}2LHk#`Oe MM^ns5ϳ%}x/,^j/II?΂P%6_7g<8@Ȕ M&Å,v^$U~NL` MJ3\cA2hCg>|"#\^\g`A5zV\03$(>̔u Yj$ ~(Y?uTk{pȌ7m^< 0g%GBssdxG+=fkM ўtm/!M1;,4XWcMJE_-N Zک(M0S!M @J8;a ]pPVYr bs3e!HI*6UY0tivp([f oh|m/%p͉PA Ap|uc ϑq<{/ƴ]aBj$wE%x =19:K+ }vFi{kcb̊ξR\yeޞ&L6B*,Ң.D+),."AX=|x>u]h2mc^qsms!EL2PւTNsەw3;?m%q{QpljdJOzq} ['|:êю"3fz;#7n2lȇ'{A&6>1W6SDZF6O#$|8>ME< [{K2ëW~W?+%ƻi,#ىJ[$ϫO-yzu*z2(ϩ"22ӓC^5 My;/>vջ8yyYxan2'Ыk;epF0MB*ҲHt'}KJ\$zɻCBXd`hijwj,rݬX83(Z%wn 9ZĐJh5%j׮Y@{DncL+PT"y \GXt^yƽ}r"Hc##7?R i3vνf6<BdGuVv'eGԈSݎZ|¢ *[H~#һwm,߳Zß_|#Y1ϰ{O|c&}UUK>-c枛DӁMrL<#*|9fš WZ>ՁjaĢ'Ν9؎"q:4ԣo_7gњV+v;k^5s ȶZ9Uq"5}7sCU/1VAR@J 9*u/}9}8- clTo icl#7 s=ƽ /ry)xe^=ia5+@JsU9ΜصrvyGySo/)fL%9#ݬXd3's[ZaTQGs/r\({NEkg/ăLG1t4p0Eӵ Rq_w98_j*f{wI ||5q}|{verO9HKZ;.2).695#C?M (^j@Y"c8G516bi<@Oh&/:6%׿Kv\E9Krfd2MԹH~| fPoAO=F9->]gXD%zoRj w=|㧑 zyeUш4OI#psdU&x1z;5n@JHȼdpMgSwPJd]%:&˶(-w\^,D%7d(Yɶ0Ұ؈= eg,=/n9RjyP|`֢b҂->.+"Ʀ31GlSUs=]'|^0K8v8`5# Hy%uTcDQĐq5'1&JKSS_O`$ 0E_Ndpicl3>1=p~Cj+NI;}^ _K!"di^S+fb*Z9㽲#yD>.!N\,i?!cGӁ"~A{\c,0Zp.>ӵ]D̟=!21$lgʈ |SBTEsOڴ`LcM\iyQè3;':ʯ<2cGvyU,>0ozDn37B/^ܻ3VS,i3ل5'5F+>$mVt*(Ot\񢒉@UwC' g u>o.㙿C416bim3LAy TUB|h _]ؾnβ>, .9"b7|ں>r"aTWnR%p=Ϋ'S6S¬,CP|1R~|"Tt5EX[CbMPcN(n(`1-ggXq;dYxޱҍ>EIg;Q~K~T9"U*nUk{ sS|c\mxK=QĵC:ȔOPNh6,$ZfΌ^*; )>יAϊ416bߚ#jmHPR+I(In}-sE_+~+/Qi/Kxb |`%9 T#If3jH5181-e=N`ɡB4f -{ [rZ5By"Z1[ [9$c.+Ey+Qh6#ٵQGTqB_/ [e \Wt &S7\ LF.t);!zjK` `_U ~ZmUǚ)mIgls3b W8a)YQD旍Xbٚ3ٜ6( jѨvS)7r7eH [F iCjLLة%D0JCȣa!k@N_Pp̛&{y~|T>9âhQwN;,"v^Lǩz+ig}dkNeż"ʰNu,~[i$i>XY +b^H3 c廴Ԑ։8ݪ@n$B_Q,=Ɔ9.N!\Ic~v'XTKu`QSijNztqn["Vƃʢj@')FjhdHB"Ԅ(/ծ͒cqBV5!u!c򏝬BDc!Ѵp5 Gm?樲),Ewf %'Q0KW9"W}SWn@͉rݪ4Bt'T?IU,Z#Q eUiD 0iBRQn4repwWUX1VM{ WrF?7=pC~Ps$ 2/Gs-*@,)kG#8-NԆ!3uAD[Kn>ы]i=ٚ)M8U1=U:N /UZR,?\^!]/6%&hHξtmZNbZ0eaj@NNpLJhBbN1*$RQ9)amEu*ilqʼ"v'GӛoK3:M˯kN`qjq*%͐PW; 8 T 0E U'Z=LrZߐe-x${D`]ERcCҀ'l7MN!ӆ`4Hj$mf,Eo8|*/Jޫ%`F$7n֫@qmWIgAr,K}#rŗ\ƨ˄mnKeP"u CBc.z dN,~/;3? K|9e :={]A2^*ʜe`|b{eiѕvz 9դ$x]LjDZ$7c-"XR8k$:I8{ uC{4n^)7u_:qƒ[䋶(7!:Jh$xYeA-|{Nv*tT?w-+y nS /E+7SYԏA'-av\wYVUaJ\oX 恼ESzְ4NbXȗlWkA.lWc_-2p9Q~DE2`t9~ u4 GYOBP[da]>ҋf%kR]EPf^ßWQH:ILM#4| #oM֤$HÂL* Sj*; mBrR-, L516;^7_n}6>s'S=\,)ReEt[9uZ-22Hu:ʯ5)|eF+6I~+e:Ys,Y"3ضQ4i>'#g<.7g-^B*L;SaluҵSIYKbȇ]s{\n>Ick"Z\0Lj-b>`;X_NAIHw' 2h Z wD]t!J3!ePȉ\JaOᷟ2 {ZIf=Eʙ^t8V16m"Y%E[0oNx^x\sw|L?3g.ZP~i.ͥm*vE^iveo`Sf|׬Bˬ?֥uɤ] &}E[$ (d:\ 5z& Z"'خ,ro׫_ΫmMMXT| Q~oWf dՆ\$%uxSMJ"C]-.$wzp SIV|sLQrsR>d9= 8}IbgĵhK \sqτ^Uyp ϕ `0Yُ3F0MF6 L;%Br"t3c旔p:hUi)l51.$Vi3mX0?~3^uTom[%S">&Б)U6,2! K>9I_T|g,v3E+ :H^¿$01Ku!W=(Øs =mǀ םc@z twn1ѷ7p)G]X$%zN/p,"321B *h[PGuw=;7GJ[ѮLsGs6ٖ"_:jp0Fc!  ͯ;'E4-z͉!9\Ͽii: BLjeU^O*W`j۩~U]}ɈuiN*L .ԨEҜ/ ]k/ dD~Jܑeh@jEj>n|Jjҋ ČC=('᎞ eЮ a [-=9eO05N0_X#O)^[C&^_兔U}7?9=&ƌD9~|^Qz\P#Y=]SJD4%ui' W_\\Fn =&{r͍™"'@Z66,hDNF2 %䣦Vu63S ŊuDl51誠Խ}'m=vfN0:MFm>,ˎsfȈ!OX'.r$(Xu3Lېf;{Yr~(ϟohA!yt16.[?z)ONyﰣ*I0 6ƩBiK6ܲbƠnI8ȉqg䩧ݎ$Z!,}$$ hWZ^ ~-[iêVݴ,a M#2hGD(;ӯ I1aA/ONn- 8zϊ_E<L^ |G]czq$|z` =;ݱ!ŀع)=rثonM c-F0^ vdc8iGpKTh=r[k_SB%(wⅣiiԡ{V5[L,2f]fF&@ar6+[," V0 {]sɓC(eMi--[w wuHI,J3>99 G)GH!ds`p Io4dq Bel=XTPXuM t"#Vc$;K{V Ii{.Q^PuWf  tYS !P爴{?@nE䞣F{&l.?XQu:Wi-(!Mpտ@{Ik]$J7 ҳV|j} +@z׽_ /S\>wx#{ $|ėY+@ 6s*A@&U8k֥rXdAScҌjI`?jSS \~ŏDL t!f ىyD~L+> N,l)9!C榀g=?mBZhKt;|O!awozsfc,xV!?t_4=P$I!ߓ`AY̷&1|[n+Ԍi@DggQ2|)d ,~ƫrt4%+zPSg]ɳ6y_s KD?3ꉨ!KyQRHpll |'@A^,'?2GodI\I#O~YNKi&mƗbBd&z%f:5W:`\NAB]~LIF ~ŧVp 6*I`/,+k֨zJk"vfWf~1wol !>Fz[F,Kz>ɵ% 拴^&og/rVJnT" ]"A+OCn ;*N}%Xy2_|bB!*QPZz6Sepp>"Dfh̵7׃?ɿSiXQ [sF ctk%~vm.1]5Gre¯E~wf쥂*񓜹t+lL,:;tڳ2=uڭȇ.X>J`SdJP5;rڎy܌,G\Re<+v! L |G]Ulȶ[',fɖ K2*-Y) 74JW z,`jIߩ$ٷ#$} *r d2hr!8;0/؆S֛:[Y=h;\E&C! C'=ն28F $߫  2`| 2|Rrv'%_?[Rd|/s ]R.ҵA!OR=0&;Kq$_g F],k@b?}%hA}XhїwnlG- Sict+]"au# WJXfiŗpEcϴsŁ %Ҝ'4;M%ChMt]QS[F w 2x…+V`ZWp~nm~R3:<(!!{? /#/[5E׍G[\IjB-=n/A IbGjlV۾bR}˭x2tk~(:X/mn1l>AK@jHh2J3 NKT+EbN@E~\#`W % -ߑ;"a\m":( !oAؘ̩M_2Q]'Q(sY0 H{yޛoA˙Arfcf1MJT?-99[;u! rP$@A}) eHբlvI3g(/Oq#h؋ҠEf![!#ͩ蕦B*&߉)yެBrũ@z4]"/-$BȒqC[9*#)2mii|H"H;V/e w>mVVtƬUcu+e..BrUw֑e@n\LIQ򶧌Y* (Z/C+X'O$!&(vvc(-cY$Xfχ,l%h-*h z^@%m!|Zi57E'Q).04pE]j,CL;Fjm.2nZGXIFDE/BZ&-ZͷZɷdlSp!M]̜ƅ삆\ 1wO6|Cawbu3F|wNoF + $Y&^Y$>4ɖ/]-IV71 1ϙgvbHL^VU W36"ZctaU\LK:ҝ`q!ќ9P7Q"Ÿɚ@ ^V uf ]w1,4sCEҖq24S4h+u,Lˁ]"7y@r-.bsn RH yNG_ͥ4})Rܒh\MՋO2NLʸ9a;1@ǒ3IJ| /\ˡ4;L@'8QB,01AK9ޑSK UG%^ {$rQlFS9+^3|dޞ)wt6&r2l 8gHɱH U8v)X@ $Vp"vnm˫^o<12ReJp!Od4A$sm/$OgKT!(p7\lsn*u ?A6fI%J@=g&h!]eIQsݹZB'!$>,b573HH&=gP%)s͐HtAa$ʝ38H2F=!jqѰ> u*QghD|H 7`!iZO: 6BU}wOk7Gkւ˝+#|,P܏ʮ$OJqrQ iyJ%B6HH&e(ߙ++oQK),Š `tD$5OzGXI8jRqD\S*H 8U]X{]I?6} ]r*m ZG썿Hwt7Br QX9+J| zxLcO DmCV%0HH=>Cɨ Ѹ}/%M܋gv- V?DeQOSr&}2e,qfixvjcU‰(+tʓ@Rid_CX9mV.j=YR|y|x2>HT_תU ! tgaT̰ {QT)~*#3ũwY$.G~?G43<xzœBu4AW4LMBm hflb-%d BLWtawGeٶ?5樂9!RZ[մd]jY dy%^a23M@ISW$.`RHrH}CL4]<&B3H2Bu^f,U0vqO1mXQYD HR4 [D\J$_Ik(>Z9(0("J*Rw 2{^G-vWRJFMNRV봅zǢO4B2ٮDA*&SAaH [ma6[3 m gRy(R.匪%Tjmw&q̗6#X"ꖙ| H>@\'¬UiޥBjg[4~], EiI滑K^yeHZ9<$)5Fd n97 $_ a$O/Tx*<ewK $S(wNF ɀ6]\FiiYѳ\H~wd"mJl4&чkkZODZ/Id)a- 4#,_ie,H!H D-9 `u8Eu$ 9&afnj5 Ik9ׯ@rU+0oʔ=xm]:")Bn=5> mjy&B* U e%_)ZT=4lLC%xO&hfTZAuB{ S-FƂ/ЫQsLWYfjZod &V.REN@*⛋ HywOۍps>^ Aw2'&$ _ݜ] P!=" /Ϧe< T&? {Mp`Q -H~!G/ 41+!ϡ<@'d^l<~-4%c꽪vwDB_6asxX0w9:֭QFnX etz]lq ꐊ^Fp}V8^Rf[Ȃg4R~.WB0n}h"%> ",csw c R㨝gL ҷSG {6ㄴ~_>z Ί ~рb[s{Vj/y㷦ӕ%OUpQ x v54WhtE/Kc8*i/EWKs[vwG1 gi 1VSA)+QmBG !߇ y2fh,7ytDY_7 q^̿Y!=^g˳,TN iºx~][HgE.&: `2[ƀ1uj`f]G|q5;No!Q썜` yr  ɠ 5Ù?ƫ0s4,)b?Cfa6__G;[? "$c- woV:'a7`aucTa34+͜ízμ*2/ǬĤ]$LM"$xd?: 6ܻOgqlטGen}2'FL#!+w0jEIAea9 N G=ڼDQ+?,eoBpy33BH! Ho c9xvEW&ep Dy8]./>)? ()7oݺut.f1{vЎ2ʢ`w#oꢥV r Qxs 'ʦ SB!?\P`ObQ|; IjpA#5.E_MI;}W! n; /u6DȾ' PSt-3 ifBI"wAq@9sJlDAg6撆9 S??K!SSvRM"+'-;NL1q9!#(E urC. g!gɷgy8_ >#.yIpMf!</g[ #[F yx RE`{$$@ydYx!A߷zz!12 RtqF5/KfgOmxʯL29Y.;gzX)g9[Ke5t[EaUU*`-RR&ݬb)/JڿYd˗oT^x[N|=_L]Gve:V?@@GKk-G@8/+#9~~OLo ̫Or2~ yOnNF ot1u7Hоu n;$$/@ ~F3 &#>ĺ""#}dU45|sĄKu-γߗ'x%SB! Kሿp'G9f`z#KCakZ#^r{7oH} =jط>B2R?!9x`*JHWCyz{KƃJD`3'i#!xBd\6>HBt&q;P !W$6KA% U&D渠+Fv:L }kR.\E$q?R ,cuo*;YP΀t2oucܑ|l_t=Ä84 {1u#-P3KǢx}&mԁ5`5SG~ʇM!wx1HH r OX02HTKX '$ܖuǛ߿Z_I崞^ 鑕(Kmj`%}уK}Poڱ=[c8[O~~ D-1HG*ȳ[i 4jwq#hXy#N i98>XR,î ™2ެ I@4 ɲMN ]zuiThˎ5 ip7uקO> >I}CJip}c?b!A4SΝ;?ԳKcO:+N?U$Qp-֦oUVӥ%~ FyqLa)Ytc)H8k}?hCo$n mf_/{A )gk&9

     +1aoЭG1y䜆1,-BcPA"?t\~ Kfg+?~ɽ".NW>QRdt !O>s)3n~DL)),]nӿY>͚G,ۿwNPh=CV_ \ ԩM=qo;>Z6r Uex r4kjBJ9vhUjjY# PJZ=gXhx(I@o9DϯRA9-d]7%io}{oB2P?XV IJX9%Q*Z"W{o@)=rKLKV>A"OTE=@,3MF#+g/7 7u9zg#dkۉ< 6eH.C7eyw gx9rӒ\U{Slks/44_$8!E512HMvu;#8m_&c`>Љ6FQ܄QmwҞ-*ӐوX ƙh-+weL3#R~U}0D[>;ozhKvDJsf R%c61yqAѮWXd; t&Sq$tۻ3كiI,kd2 HҒ_B}(!!XaMP_va̘ NشmFs|\♪36s6+:s 5lrSFbVdx8x##AVX"KZ#$@֪k.L ? Z% G:sB,6;j452/VRe9 I8E>@UniY4RtuSyTO6Uэu$*/;8Fꌐ3g0 VAi O_;0:{+@/'e&h  YAi@nC߷d?y9 #WS-5uںKeʖ-K!X1BRARoWU5gtw輩_%:j%:-0(ϯZQ3 #79'nM0O-vH8sߡߌvdJA2!B3$ʸ=bT521B>~CSYܶ R-xv֕Ad;3ٍ wMFdZm'7'[K̡.Zv }o"dq$= }kTZhb 4UMjZ<$1`7{8d ENoK1TEB&'جaHtDc Fش5XRxJBa|sS^9.j1BH9=t!^~y,F}޴'-MḨA*OR!777ݸߝn\b%* 1FH gK4z3zQ˱oH7nH6>U\AB*Rג{@F.$ASmA@zm4ujDo2l0BH.xVb*wuÂ#sET"٘R))k+Ɍ5BQԺ nG{Bȣ |@!=m^NDxĕIy.B=ݥ4A2BH>xNEV[\xcOv+1BJR όS^n|ߐAw[f װhC 0-rRj/p)s-%cF _9I@ 6nDyZU2FHI aӚ }S> &X=a/Y\Dj+H!!_D l; aP{#L|.u|U! VAR!s h;L}b}<Ꮙ2RH5{*   Nq PFF0qE[fVcAEU%^T( -MhyQB"7+ l8X! qH3Äw߸d>{Ɔ" xX6N4 Plbߋgi Nz#$jkg2Bk( vH1Aڟ*exM@m y[Kʛ{lzT!fVptR`+G /OΨ ao'zYƁ CU;\a!?"2,/4{7w,):Bd&=jm2HHx !C.Ԥ$N8KX/QA +9 ?7d#^tG.U.GhffXW$^I MpR\4²Fٗu0t0f#Xg;$,S=n#dӶfwxd9F9捝0 CUlo1#)19Ri'M+ ,9c,YahIurIo\ /D )# [ܢB2fRXTP`M6Q0,&`Mw+acY5A@v},_&??:?!a-79ܥ=h*F6?d>0 !<5DM++.yΙjǖB.5.H0G0 $@F]2u7h%qK5( ]{{?,J#ʔwGvʀ 7k3A#*߮>tSn_'!<<7'aۓgŴ(Pxi-\ J O{ʽlE5EBcږTNDT`l3|4sŚ LȜ}\d[BETj?e]kwۛn B C%U^SK QeqXo yiQ?oS7ָ}FdI k}X3im'@ #C˓?$B69m)tE=WbOġhQc^,DSе%%r,B2r%0w0#C#Eňd|ׯAfDjմ/ t4BHSk!m~SfyvCg#)y( nиhDҪ7L$,a(rs/mt(MjfD ҭT.J|Bre)ub|*9CFR\ f42>Ȗ¬wPծiĄfq*$qg[YTB7A,Idlr.VI|pֱMe(2*HEnSZ mHnj"e±,rtR`\F8.2$~B o$_O7:HSu'ߊJ5,HEY >ؗ(UCclH U 9J;ʢ#X_YЪ?IP=e]IۜP8!VJ\p)e2s6v0:-IFtc42@H\,>J;x3>H7af6 5V/[;ME/H%1j:$aAZR2ڤqѪ4)Z=HrL(vK28Vf{(/B*YgKoF!CTTt(_ RɚYt{+#&U9i/A,Eu'v:4kX*LJ%$@yB8Nil]eKG۰B2[`(=0A*q>/hd|Nx Vݠb <"stk1e3"K=ð U>syj|sXA3L\o *r\ZBF3@HψyRU@ d1]B*P6ڃЀX]$6Hr"bE.!FY;]Rqqwa\ ʩb#"Lt|ȊAzq/«uuguÃt3BKWrqlq/[fNq}Jo߹{\? q9(oݺy#+k6@H\NX) 7($2E){,F TE_,h9F ü*nN;|>Zo|C\\T5hԼef0^n{WMZlިA5)&'3>H,W' 3TH53Xd"Q`O‡{=/@Gd}F ::B,mvٗ!ՒHuX>#-)PuLH]z̍y%ɀ  $G_J`4dH &M{gy{*{}RȌ*)Зh  3@H.9gȐ,xu/iOcln} p3ȢiPB3`H9^x!…81LI ޵h!k;@~w!Q/6 .w5?Cܰ[~*:dHZ)VuK{_C\ 1cb#m%ۥ̀! _?/< c_|;BueFk ΩoF)P+yicTAs{z 60?dZAz-g>: !H cŐ1y7^HjЩ=!M;<`5Hj}`!92id ɲBrhݍҘ>Ƞ<w?zL|QN`h&( 9@B2xiHܬoVie"s`!92lD=U<1Cr[r2rR>@" Q͢J><$l+0x!' |䓦Uq~XPP!j' $"AA oHC֔.B^H6'ĸb^Πx!k)ux!Qeާh=beϤmq § WÃ(XBm䁵B1Ð9vHl *)CNiI`3/|$}{< otީA$)PX K0 lPR|#Ev-1$z-5Έ!m%U^(\;"d-R<6 txtH{,)6ؓ<~O0/o3bHOPɐ!' CrdGp1ۄ!ݦ2S=y&!@M6kx'po oi`ejYN>*G1$cz3am_2$GNLVi0%OZ !C hudmH,F$ Q̯%PF)ˣU-fE3⍺'hxZڵQ&NQy4,̘MH;Q}쵫 2X))l~ǤMV1z=c@ҪJwjORR0ٞ!f`o>ڄ$0_ʻpNEiOwo!-vjLS-`Rlu~˘wC\RR(_%:I!䙞ڪP)s\JCx!RM.'/{"bޛw-$jOiYdSk $3&! ~lH(trI!55濾-Cu !=+[҆R(E&tT ^j ^A44뺹Jd`!B*JxMĻ08HK%Vv4#H߸" &f!ky^OW2;YVAxo8AZSWyE$VTCN`gHTtF$ŦJwS!d_p!Rly\--8tA:SL:y(BRtf~5@?1$,̛=S1m"jll|%t l )ot2;ƈc4^;~'5B:dR|uRW^)3B.QKJ%ݷH8$$|-ϫ&2L`qw֌1~ſ3unqb_Wwp{{l}T l )lSf) g0")܌#( 3D#R!Em^*bT' A]P"+t񲥪` 6f!ky^Svtq]ZKƌ=^ESULEL9RE i/+;,j'3PaGwS!$$7QvD͌R!Ee"gz+g%$EYH6[L!-(Hw}i| + Db5+gwآ̘!^hd jLuiW>F"bZ (HObUHW-\0Wq[qxʌRn5֡˲#(afe(HF9 l:)% O\IrMg U^|!􍕰1xw/R_gW,@ӱӅ0mm=(C4^@++ Zr@ \֡ڄDy%+HYrͬ"X_qx RWa6~\4EQsu7#[J!v>C*d 1UC e _KSzU$dy ;T np Է*e\ 僅 R EΔ~୯8no4vp#tѲJ[rJlETH'諪*dv!kz^11мj"ˉ 2 [Ul5Wjy/wem:!_:իN:2!ģrI+RR~ڎV+jAYNHwץRr l} R^4( OJi"YP=U|H,$|M+Y{n32f &7lBl2,J8M!My$UJ],">e!*q$S7EF gXt샄Ef@/y1XHrW|=>P)`fCV;@!)ڞ>\W&/ESCdL9`9~^ry ɭC6R/RLq˘:7=T l 5՝=6E>ʽ=7+EXj8aV8( QoT$c`!j;)7n@!u۶b$Us#?8@vTs(Nz);~^LfĐ5m*﹂R;GEA)ȰuuH&E&1w{/ױ ¡IOAkۮ2H  C EH,S ERC*QQVQ''H H2VW ʺ>̈!zɣU>TC?ZޡV^ ׹!'=U\ JMV$XB|ʿ>EȽ )P܅3җgeyz5Zk!Q-gN_$cGdQ=ɘCJC&L9qʱsQ9yةu(8qS!"pCz}ѶSp m&#sڧ;>^[#Pt˷% g.ڟT>da%bx-T$B0ٯ*^!CJ~$-3=夃z1^E(ͣ݉4HHrCEPH5hE'5{Ѫ 9E:;qBJm]]*μRl9{cbYg"X^ǥpZJ*X0H!%IcH ۲871'>$@Z\!@BR{S H$=e^ڻԝ7 g2HHk{qw7 @G̎(Xi짚Z D!Go^?ISqDcK(ƣR%itϷ`q~b1 8M!I j$$N {H= ;\M4H§[#WBNb_4HH|@ceJ GkD)sv`ʹB~aBayE1foG!R!'< P#@B6y˕&fos[pƚ E=m*jg埧]IzSc muXTO9?I]!dVGQ0!).LO 3AsH1Ip"~'fDn?&|ʑe5DH9ZF%:)_e,NCt`=٫7<g"ɪgJ0_t[ÄOEڭ@S!9"pCI| D~V kGە3jW&믾78?=ncJ#$:8=wsu?IV V*L.JJy{] 9݆ l Ur{ 0H/I仿3JȊ˟dHHV(!@㦘g;̆n`o+f}5Ac6 $f[ML!raN`HO!Q],$2Hpm+?  rn @]:ŒRR$]p/ r !=d10'j%\E4$B|'H!1 xj8GxU ;ZFh!7%1\HIБ&,E9`r|fB8z9G zΪ^NC"wrA.V\7a 'rH1S%_/|2/d % ik?G1s6 +~:  lG5|X.Mk@E˷3ZHYf2Rǀ!`+`^HpoW7q}A>wNC"wR{鴁X0Dڻ D rQ"R;8C~"10S,mjj6z  lGH Y@Vxg~Y k/ɓT )+_x`QẀ!1|B־[e䧢[(^C"wtzeg.& %*S/CRK !CN21d y5rWf,ukHN`#NBWV3# 6cty֧ud(te4Kf/#-?!CֆgaU] }  lIHZ((m 4>ju2IE%3bH",3d@h΀W 1f̐P4_+lĐ_R{X7=L  lKH4OBPȨȓ2,a_G`!C*ݶՐ!-%d̐ݍҌl {D6%dݺ rGWu{.y(J21C*2DzACFw|\&?-Š!N@y5$/Lh/{/#hGxM/H\+t\W&zDH5Pom*e>8#T(wVi|qQ-1d¦|PDfx9?KlHy lOyjJN{<r|]>3&l݅ZJp.|gB*6*>@D6pE1L4q9Qd|_L}YrKm=%ptRi땷i.̓LRgچoܼyٵ#º ǪhKx~uYB*s7bbllmfdnQR=3WqCz` A 7`xoIG'}wҾѰ˖/܂zKVUne TKO_yA5N2@H"ZM: żzxxJSt)4{[>O5BHzvˇgt0|H򴙼rÚi3$c 'Ig!eF ^S~;5WC"gH<ʴ޷_T 9^_Wd%۷{J$57ύI!IaQj֘h=GTTdlQ_oBz8̯ܰye/F!22w23ɐMcCJe$r@~4Sw+Yp1gUc&ܿc+5_Py þ;sn#;}6Y."V$68HSnڱP%T*̘FmkNԐuIo )UIY!uEs,3HClkjnBN_ɪ @"x'):(dlAx#TIqxʬhd4yLPMp@hfԥҡ0U/ieYvn@mOo' Ռlw8q @-}|}[؜6T:a ԁZ  $GÓT!)pM5_@m^rƱ /#tɌ@F p,ׅҽ 5XP{oş?7ԙk: l UUA E+1Crxd=F##T@7Rs~Tp@I-HTIb 4d߽~(d ȞJ_-cٖAUƋ5d o'7;bP) Gb g8Cz2J4*]!pў?"v`H9IT(/tiOmwCU/g24\G<B~k@)OmR(gǬA WǵQ #G_Yyl\ VB'&c=]ܗ6/}Y%ľZ~&')UGe8% ]}-xgBv 1PMT n.x*#ȷZu+PO#(Othm( Or A `7[/L[;y_[Wn>`ĎDhhԊ "'8M%U}2 fwO=#俽_7 }뤨'>ҡP')-6R$rTW)]iS ҕ$L(` _9Kǔ~ %:?{㳆{ML~)n„ǽ1wdʓ2gbUZw| MXSоb2BHB'A%SUy.<B ei"_wC92w"WjCtYN^IP~̔Lqn)(Z0DÄ,XsMzöaw BX\6 J-'=_߿?hfh~rf%YА {_.$I)r>5$>;-gZDx"͂} :_k1ϔi9܍&A60HSx?˼Oz,AjSΧZoV g|喰h _IrH{(tZ.dx7b2LYLUuy?ڸ/b'8M%R~_̱K7RHN (d u E WD75v#iy?ky{D:7Z  һe{rZA&0>H,I {{z.Mw ro3Z&F]gu5_cH/(Iys]jp|؉w9 lBɾ}#Qji_s#pV5"PYnin_!@FJz}D0LF榕 H:͚GM,; .QB:Gir41BH?ԛy-dYLs!IxΧkHejy$EPqŴ+] ҲmC9Ú \(77w }r鬾\g=y+4k:s2]_JK)ɜZ> ^ 9 !r /#T*-I8k60HїuKɕ|ltH"vɇ_ J]ӐN =pVGsd뷣kΆ o9@}@׶J̑nG zQh?O.+O-*=CA^umȊ],ɜVlZFj҇R%W PN)vB* r|0.Hl*R^,$LpkF {#e`0"רwxkY2p,RV隠 :. 53P1hC0dܱ. #1ĐV=Te뎍P!mX[!hDe4Ϗ n`!6 tPo N\jRX!ܠ&W?ߟ>mx00ԕSBfNاs?[Ȝf!R-1DHqHSTcѻ3?Us@0THLb$)OF YFF G*ȅ$ii-S~gXB.ΤpZ R\} We #=(4J/iDKo%%j- l~Tψ!B:dERH."su. |B s>C^ GwK/#D?pAW {@+F0[տPU#.(9Y6{Zbq)LcOdm\a%,`T(oeeUM25BHXogJC̆ )y^PJ\ %2LQIù[L&Tﮡd[(k,z:!YhN`[!8!CyAױPйފ @ W%dGB{9\{q5ȐzŝqČxev(U u: ėF2bOĊb6f w=J` :'QhF)Qt2J7 c)EC*t ϙh1 rMʶ'H!%ɂͻf(5aه^gY9t+uB+B}!Or FIM6% =5BHYіykx #XdyHƃL&l{E+e*iZ*$SPi[=&Lϸє: dHLf'umX;W/R9wԂ&. lCiP "<Ջ$fBZuZR0Ip? ?"_*U׸_<7=0'u+P!%G`"D/J!8>4嶌XJLB|3uyνv]]h( ^/ոew2BN6R!㞌oK ې[/RiqA.M!>y㘄Դ Q`W I>V3TH~$ ئ*9QzfGw |S? wA9(&&L63CF1'y{L !9 yHM ;6,H[2|(jϾG! O RI~AVsGݝ%i@''Ir {c^ IlB޷q6LR'}!xDk̺K"ZoDǐ=-Ƞ =l21:o1Df೷אas! )6#.USB2%ڜuիt}M9do[D l$ r31ddѡn+W2ȮĐfCZ śA69LAb'T#/_g"৏#ds)}6 $8S<{|B!.F wF"Q0ZHE|R q2&HEKX;˙#fϽ?F%$E~squѯ!BO 0@Hü )IE_ӗilC a/\G;^;(39N`89᡹Zuo?1cwLmbw$ %PVH#-dN=0g^~VXcE|xzũBO,DtK17[k+k;ׂ,WS_M:{ ,VeX֔ztyG%'xQaso_vtE}o+=U;c,O52! R \Nf˘ [gfd(S[=3!$k ;<}P~R }xm6XHI*];w8;vW]8aҺDNj/Hi l.ybXJu' Yc==?)P;QʟͨmHe1P@`.@20cD.V2ZH*jJKIcߗ@s]) .ʟw N`(P_ǫ[L *2M.S?5Q 2B@yW< /V CtGB#dSfWÃ60\H26}Re1m2vKŧf8d^|ɓg/cգ;==݋ڵ;%E#!]Y֢_`\cP͋}u}79҅FXq ^=̎ T G$r/$w1u̩죕P=Xl+֒;M%sdFm=IS2øF u.zBvixNChqߎԻg/!afȐ#&F&k$EcǏ(CBmWzU6mޢ?}Ӏ=?=??ss`i`?nC2H;eJOȨjbJ6k]i_؜ wP6Šhd\Hbuq T]l~^.0l77 ÇUiqqCZjܽ),r;ܡY2ItC}"n^"COo__oOEd|.YafE@$+]a ˧.(Ơ(jf!8PAA^T:wn>)o3? /)H ݢۮ|tYyO^N%ع"Bd3 z=;,BPU1.$cɑWx=1kBq+ؠ.c̎+ԾA~2IcR{ y~}ǔu<$&nuO~+sƶf)"ddt7qUM1v[I!EǮ_ݥcno~}9bPCGkQ4YX5Fl:鰬y{SWrOw$yuFZHj4 Ji(e.^[*zzk/2ʐe#] !~%Dn*H9pC!+ b&p/X+$9-=_?ҝQɵq1I6˯N9ZI5NBoc%$|xiI+R 2_Ѕң'F0ݕKx[J \Ծ_ID7VZH,[vYUo-h)9D Fӑb ;tֵ}J_t٥U# Yan2dɐ茇[.duܢAOxΧ*NU!Җ[0xsj4:&l^@A_1!]e#V_ )H ]W=2+ZRlOEOydrX ˦+C7(~!Qլ덜_BmQq\2N ܜ9ajsDa)!LyIyQ@/( OnkvM.{T.ޢ!r7ݳ!WnDZ=!$cwZz tl7wc&\\0x{߂BvnOse)gD39G8x^ޏpa@<#h Wb 뵷k&܁!^.$_ !_,q_ ~AZP~ŽQ}(%hqq03Cʵ؄Tf@S κoR`$9-˰E C'!FnJME*e m*6lӾMÊ^PKp֑y>9Ӡ`?U0{}^8@^U!B ̍,o9Q΅BW1HX֗æ ye. #` zkETſ.e?Z.u{r&ݞQ)[YjK9{G a2XHAR'!Cb>>y׏[KV`;[xˠР) ZIaj0*0V)+Јc'\ YP E! BEZyGԧ#uR[bмwtc%H=R -5q+72fM!(kvh8m@~Hxc6vcaҡ>nGk拞<3Og'h!j{o]_B6GHSwz+&`snl舶03 3=1ږy_GܛUX_cj0h_+k FZ4pb%Sxk- 4,Ojr[V$c]dNJ8 ꭝS UKګry7u {wݬl\9\lm}J59T7[q$02KrӰX!u? 7O1S5?c}?wSOCTځR?AgƍRGx'P5scĭ9Rsʣ{>\R}PWcXu'vMeKQ c#v(͠5c*6~aN Y/S!E'v^У[JwĀNnIo<׉i!o:T7j R::ld7pnN5INꫜQ(7tLJR(4 XD YD!T_=*@LYH]d`@tN#nHx$ztiq&&`Etdl )>fMtC2S;d/m=ɢI BhҢ7}L͕qՓZ+ӻ%n[vq_t  ٚDz8"R2_ #u9 b SU,eT޽nq8bd,JmpM;Z6: i!ow'AU,(2b+<yD4sU%<&iy~ڷK.5 qI ]H!H4JmMji -P_!򣄗ぺ h07 S`W $ H {3- R.:2(Ȳ Kpl{E-ੌFjjvy$!XEnpII|beF&lЖ_CWMq = K4ʬdxNAB2:^iqyI?A\ 8+Q(ÿ HToT.oClU;x99rg^F$)<#)"Q,ܠwg9(KKY@vN{n\`ybFF yCF X] NKT&F8d`{XPg75iʧ,RGt +C$ F1a1JݵqG,'i%+r[AOQIqA4~/0KЅ_7p5 dƒL4_ȁNdDnC*`gob2dv@ H^@*sK+s% NDÊ4<0jjKiR!ޔь!eTrMN&a엑f]]$-hΈ$=i,KЬ ߖ}e]9DX*eLhC &@YғҋrD#{-c4yY<{{cNQ'YVKxcT&۴q$<6~ ե2ZHIhzMx)YLNud$*yr<% dB&D*n~|bKI]#KUm׎xBYt()|EEޕ%يBwEziFG7wピe$ᵉ[ ShW}9}j-G> A'Pu_ ̷ua)jB:gpI ;^R@fNPFH[@0QB&dNH' d,!eS|~(Fnup7 S7$ %8QspG 鐨S2c8d坻L41u?TlAց2T\9tWu|1!ˣ@Q;!̔Œi,/CAxn@BJ4zibOrEe%V=I/ eUuq{ |;}~XQ_uTdgP|׮^]/,~|13H`bx,N]_Zq# ʠh`b]">$꡿R&JmFQ|7-șKvC9=)CMzLU2t3V19!B>Vi@2D ^(:M݄;?Hi*zIg]Bʊa(dqe^!X{I$Us;"#vW玒%D$y %F:Zn+!Lp+rqD}%J4$4dQ l 8MA7%*ެ9;K$DE,B*%m*!FiSY`)DƃV ~0l$-hc~!y?"`\t> s(Au Au/gKusfwo6O(ӐD.Ff\ $kssk*᥇:(˅6fF']`{!$cg=17ihh3DtQ%q-iV8AT'nX'yM[r%a}f>/a"s&i:sD߆HiH}U""BJe !B/HÜiERDzb%M+9S3U GCټ!$>mlބRM ۹@ R `P ^=x8wT nJ҅Y2Bv`0}R=8 RȔ5>I!DI!%g.R=Bȴ椐) ڄ"C])Zx#~o[UDY:D@4>TMat&*TkFz3d[Uj{TOi4 I!8R0C4Mo'jM`ژ60luF{]4W9K>Xys>SEKtJnD'dlD%{of8[ [|ir)$g0Q$?ȉ;H y#4zwo c4kAM 7}$$fA Z0|ݐ!9|it#c_bFdT-cR[xCh>FϠU+c;Q-|l%0h\ZB1+qq߫YkF3,)u ?(\N:C )_B|IkW R*ҟ.CeRv&o"B&Zk]}D" /x Tg+riGbib݂YExe[c4\%^7`HfžjU$zuKh|-*w&qYʷ+8#dz.7vE5%F k-uGUkJ^9b"dNH -$cΓ.l[|!BJ y`XR\UTtE5\&{tj;K" c1JIc?1s$A`b&/$cjGǞ.ZӢLLoUvc'L<Y)bVR_M4L ft̑V,) \3\ɢhF|plK%$pyY/ f, IrAmi{{k43`] ,zi?ƒ%2 A.Wbdn-Ťɼ2wu D+D' 4niXoC4c.,OZt30fܪu<j>z r7iJ$xBK SZ],a_`0@}ąrѫgn"jt8}v&4Ch7E.F_WP_-Cg>5d2(!e Yn !$YOMݫoYiF|KG\.M- WbT5V=c˲tTC Avh!jaz>po]SD5.akʗe03+XtCXg+_5?٢B~oW>j-y%ukm-}L]T )P)U& C畬<貓 ;d^BHcfE OY  ?P yXTF\6`Гq g 뵌ڝٌAM6:̙ÿM{NU_r;"~jW7>Z)] |wD3U, F$8dQĺco:DiA -䐼)$Vr&2DH#pzmW\6$Ec$,32)]DC{ޖGiYD Sx0y#:9Cl=c -?}ekNXa }LdM6f]ׇQ18@ʢP X[ZIw5.n7@'ct5_6&o>;|Y'`9{"Hd XWCMf(@!y/FG!BQKxn'לp˯m`-{e{Jo⤀OxނreT,6h3ĩxfF D)]AF#3ua*ٸ{HA]Tîd'?锿LbggXqw}677Bb^ {(nЕ3p@McbEWa܋4+tH!:"YL>~ dABR JgbvZZȮ Eɑ:*y 5P9_e y<Ō3C䴡AZb"u۟u4RHj K`?|kwݽy5}KLZr|e@Qࠅfׅe-mؚ U3ZƁVj_0i&VKCf"$dZ,HY߃"X !רҎAW@FSYmx%DeNشdsҟG KiJYM4 d/cÛx)$B**bJ.+P?`iDGc }4<t2pp>X@vb8,42FgIhtF@Z`H#. ^{p,N&C7|Րc7S&@Yid Wn/4mfKڣ4ǚ2"?5YW'2PAz41aE_Tb>]I0`^K]rT_XܷrLIw s:҂BŐ жG$_XAARXzGv1R7yO|pIgu!kh@o̞-J2syaQs1όaQQi0;l_^QMbH,#[0r7i0]C22Q'Í`X̝oKXi}#I!9@%~G8K̽y*, +_z5%,0@HSDu֭]m!)^ρSz~o}Oh?N nc{-<φ`ct+Ļ7ۮ> *wOB*ɠ?ٳs/*؎ng;h(yBW%GU5CڬY4B2%)sLތPܤ@' o]L݀d-N]f->aYЩ׬[n7@6@߱k Ҡ_TVbQŬ9(Feq:ˤ"#w;"ܘҭ]w3K3YW6-~djփ+}ފ'Am>}SB6+%a}ŇLDmZ>Q''<ݮrj[($x84I;|$oVvDr`$(:l(D_2(dBLo$ Eqz^|߅A7FVz/W-f /RDi) jI @Vo1[z޳yay5Uzґh^9 'I:4Y )nM@1*ڤ_̣ umMG HqTRH XCؼ ^9N\K3 DdH$d3QMS"EebT&`^ $m!y(y _Bmn`@HRY@۽ޔ<&dd)A7^X}-*X̽O3e{[v)XW%pA;[9%6U1?yWwN T2]A̬_³.NYO gK`~DŽLii ϙ }4\SHcVda6WZtKWی+fM5[K] i_.A> yH6~QN A̧g>XS }q0esf6HYWCrs{3+ Tx.w vLU%*J|jQ5׊.TΫ3PͿTSlQ>/L7%H4_da\Z+ʐLid!XW w"PP9A?\^ܣ >2c /A!ҥd^e%FwBLKsoF3t=l }H-bPf/}BE2!c!AIiW|z=ŭz)˜{#)L@pˌ ` ^/C xϹv4l.C!wdWy{06{$F=~'wܳ3L{Vx\ڎ!EGz[7O:V8d݅v?;;OTjv_I vTO B;Im#Z…식W ,G?dCK%UѢ󐌙P c^yX#l"_ ga/RrݓCBlb&lDX'Mڇvy@ÌK H[9_ȔCmDAӵ`j.|@hQF ”[xH6h%ڕUnʂY -[$ [.xϿ #n4*Vy!jU3p]BXa됲rm(B2&7NA"te 1@"ERi@#%|mZd ޽ G#^ ih':3E,TW7?@V],2R7UAfj{8&&NȜ~ YtU[įy8 6jDTߗODz)c"pnc"^$-qD!(2򘐜 f~4{Tlf6QBUr*dZwSDko/ `emM$2W |Q%/taI[bGeGo32#iZ֞2CʪB)J)$c]$B↉!Q Bgy~%!4ߝ'AQ;M/ Jx\%𺝛93aN$w*= )SS@IUdAZʣ$`ykLՕ$=jY2F3$ k5kLZlLoC2Tj@!sm*VaK[v ς];4#-Őd}ۅy\ZmDN` &N@0i`{`_̅D[/$d5ELZY aL5 "gi( DQ_.H QE!s }8$3ՉJ(T RV^\1xӾ%uɃ+'^@0:Ondb-JV~9pHKjD6T* C_Ð[ KXHiN:w4W'm2:> ɍ\5uDŅ˶ib yLhFc!#cLI+epvcN %QLdVd;aBʪ.NZ#:IK ]wcGzX Ľ+i>/e눬wk³e+Z2t:H%-02 ng UD@2TQ#Y!gJژ%Cu$NBB;Wq)`DAf:"}u!tuH!ir!UAt5nx'.7QR^he޳xxx:f& OlVK Jp  rʾT9~e I7ՐUh )-åDkpߐߥK/5HHБ. n0cK#`DEktxli'4H=b + k$H:$!e q|~ Ck]n&m;L[yCOMV% ^1𝐅yD:[% ) /]Hjl%JC !j)$}O N<':%谀$FE?]3(VȒT.3UG3%hJ?-i2!e&8˻N?WY7N=LI),f(rwp_. 5H 8 ͯĻl7 O/tHk )˛"-8L>Ѥ# XDD n76p{yCCŪbm(ޤk:sGh-)Y(k7C"4l3GÌRgBTfji9OuK7! *|aƦfZ+/Ē8Lm}P"S?mCFB5!@"S/rdQp'+5r )$lhGp,"xe:rr8dp8 y/Qր3Nyh/3NiB2`ޢ#b~a!/%"ukw$:53>H^#7 ) tqx]бt@[D%Sh'B'oX2bCIdYːk$7k:K1K i!9GIX#i-be0#-+.Y ߲͌GRQqxN1߇M0g5(^CNNB2,-ĂQ 8Y0y&6cb$Z' xπH~SeN`ΙBRCv"ܫmHY&7O$ep"}Hh1s!I ŇZĩDɌ. e0By dN i`9?Rweb()B!WIoLH LƏaJ,.wMvLd.bHD<@dHD2eѸ各N`$:ndĐJtmH'/g }ch$~HٸMzHk/ԡvE=\0%1L`ω@,,./§qND4D>z DTj:)g]TMqn_?.* I P? x&WN` HԓN1h!CZKt-4B*YZƾ^"O+@Y<Ȃ/-ޙm%epeMsJo׶\Hx񖯕uMp_#J4H5\#<: $S%j|#.^Me,.^R-TexJ.w HtXcD Xf!x+.?Fq$W ]!7Xxoų/qk`'ps ͼH羗fCm@Қ_(dM|ۓ5dK^6>HNR|Ί/bQkhP{ď,9 nAѨs l_a>Nz \h,>{鋈Y Ev1umЬҗˆғtćd-"y1pN [H4UYW%X|nC/N[|sG%Z,燂\( BW~Cb9! rU鍂yſM lzz^"ԗtRuĿ?CЯeAHĦF<)(:ҌkL2ShLa.dEQ|r"$4'><뀲 E;YƕiꫂJ7E7u7dTJL I>cU*-،*% Ќ8@J)S;M Hu_"Tw}T.lx~ADI%~ƓkZHH FSD_>gl`V. IOr|)bS0r35c r^_n+Z-Je[;eq0ڇp+: RdO!`R*NpҞB|C3nEZ:c]@~'jtC ˬ :qS4 %˭|HlO*E+?űY69BPhXcU Ό%,,FqoЩ@ܜ0\9i7Cl~t:΍mW2g3]L\RO_ƃ<ѐ"l! M%? W1?x#e@O Zȉ \`òͳ#v/MI邆kKSN#a_|z!a:/3VA$֢]!y CC`q(!9rFJ(pl;iC7 PHˁZ8LrY!S! lY*I"ԯs!KEE 4M^C~! A6/no6ԇ)o'1\o \>ꆉ] {Uz,,D9F聂{_QrC:˞h5,%heJSN]BZudHYE%&40*X' d)9zZȵ XmwtSKR 1y+&I!EKXχ _?epmLʕB+ B?4{@h!k.t:;d\Iu2ϸ߱/zbr T"\7 'B!݀MB)"߁!A1#7p!_/mVݳA" jO>־ E;3EC>)On\sF옥\H φMf@Qȼj0וE@1GD E.PI hGa2?koCT(*mxꂢ܄WC6(-E@75A_C[z;?H:K yD3(!-bzEIZ8|Pݥ3i'D2=:'sl|jL"5 4 Cx[*$sB";ã K%$6?&aȡϫ0#,B* iV|R@qLi鱈fQEx.W('d.B2d~ [JQCU'2k;b3U" EgHi@H*>d $d">E"iKatR?h&&m$w= C*ioR:iVC4\BTpw ݅DŽ@=,LQlV< uyrD! [QN`!Hݥ-9bhtn '?n9vXxcvWAGU0gP$_dQheU?K-^AUjEE T kR/M0*,1.B-Jo 2u=g>Т滎&wIj>Уu~31/JO |ۋ2j\mZDp]}TèqJ؎ks"u ͇O6X"YZ3Đ9»A_ *`,*'G߮c_!CH8 )ܭOJxg _X2["ld  i? `Ȱit 72aua@ 5)̈!Uqj˭vLl/`}Ta7Ϣ ^g幐׋3*Ϋyr|b'ei d-*kNX]JlwUՍr!omO3| 9H&<ǔ?r]1G$-EoϞq!S36E{ G W9ra6j=2_HĹx5HĹ8 Tn:.n^H kr(.M0F.CX νZ&@Uה.M}|LvV Sǰ*0cxOY $dޟ8WN` ]g=9TJ Ztd5zəiIrM<{N8:<}+q/.9J_1ME{kdjܛ-H^ YS\12Et˗>~\`N-o8s鶔Wƒ9%f}M˿M{KC(;|ԇsPa o̟.]On>^x憻 .uҵ[1ˌR!k.޸qiOb@]vn,w@nF:K{.Ȑ[zeW@nf^Uvj^;-+C]{oRmw{fzgGIC"i-H,whf=˿j XpwUW^,;G}_©Wj;/YY˺/f_dJt7M /dU<+.eIN͗}߲ـ#Ƕ̬r]$_I ,ڝ JP F@xɃdmnʢ;:c![r!o |z˳}%ͻN}m`{wx3O i//lf4/(O].FF%Wʄ3""JVQ*L i^j2!HJPF$$;-+CVװn9xK\zׯ߸~ڕ/:g 6oAn:x<}tjIG{=E̫{y4{ <iMa%J'1!.^rO[pJ(ULre˔,Z0_.BȀЈ\y RARJْ t5!9 e{},r$@uJvb n˗o#!έPv8 K&XziVB(?"H[ߚ=u)o䐆ىf{7*E˧ܵqi7b_;z08-"⛕^o- r2q߈UHYnvbאZtqC˧:yY6=t)^: 5ʛ\7"bHע B ʭΫ{ 9x`N+Arh.Ǜ@̹I^SDqudX`Fpr#^pڢrie<_ YvCg(Ct5d5@ ^w2@hؐ~lc5w%Η}[! ?R[D Yw0P4G<}LvҀ{GtZK[57@I Yzr-'<}7%6` _st(Tن;`O33$݂ `dx\SL!ǽꡰD&"jr CvFˣD UCLQ%hZ"DفBIGŒB ! |~B:v& H gRlv+HSY;E\F~&JQC!0s 9{E7ĵQsqS)d=)3')쥍j2e3- "d]S[vB:ufE8Q@ rkl̐=\̮WX/ E Q[D -K5ٶHH P~+HBo' tTIt/v!󯇩- NCuYW; Xn;Fv%˜!c|FdtҿLdJHssEd!Z&!ueĵDZVOZ ]a&'B2Ɖj0W{\nN}~B2X >2A@~ ,O]aɓGz9^9.AW;"2yh->ˊJHy!6߬\?[^V yyIg)4?7݈Mڟ|m # XFd9!a/ߜj̐EqA)Ho|EGp9 җ2ZO {+x|Ri,"Pb? yNU`K)!mu } qY !10]3J1 jJu1fZRM) RZ̃l$ ir AӮId{*΋dix Cx&LdX7|C~lh!5($]0$f"J{邁d+j0Qי `p2$CԿd[ͮAIh:"mBnAː+Mg1s;YE;|MWD4r -3" 'ɨF VQ 1ag Z>itEYҭ>؏w 0z://  r:ƍwO^'<`6!r I;:-]Bk/-A0Zj8tmd-h!I#cSc&ZH !@@~-A~ԭ L};egImq\IGx iҺI‹v|\hsGz;vsd?-śa$ }f֣cV F v "h CQ  }Hǽ.AZ)FAĝ§jʹ9 L3<) {_Ғ; o-v/-L Fh!$h8D cSp`gOur3J*< 2{B2Vn%H7p/LHxKl ]h! *ˏKD'nS6q I CΧ6 a V\ /=tA4gِXVzA@zO # ܢp_(!= yQ@6QDa.CSΦOf %wJᇬ9 wZ\HqT˴F '9o} '@N|lSa*8&vC#~[}!>\/l {-0p@RЌ.旀+P$m_뫹 Cdxː~c.[S8ܺO#{OwE$9ne }iVE>OZC C6rED0 {2"Pw( 5/2;jD Y<ĐwA]>*q3C (J +}Ic|j9 "$$ ĭ__Kje;⹷-4 [@e`JF)$ DľG9 U(s:dex ]7:X#:Cz .&7E΃Ļ 7"scsLt}yvՅԢs~ھn}`ғC>3 Hw A@2x#~ɜ @/@w5m~nrrwC2$x d?l&թf7`6!;! gGK1]؅08kjnCh_56eG{ *w|:p0ɀn2J_Ʉp;C2݀.|H;9/I>:iB8n!Cz *?yC c}ѬFbk'_ ̇L~y?ikE]c7;B')Ҏ:5Xː8%̃.ߔ@Q׏a_5!,[-pt1@]  2ir@r(E0Fjw ! 2V^Ab7M w,zVX/z U YMhUZ;M‹IL/;ͯF<+4U 2BV#WNp)qu 9j]6`!Y会b,S~e &l6?:(.saC.#4~nA~<(cYil >"4j'@rU`E$=UMK; EMb˪OA>T>N1w>v񜀐' h߆(6-&"lkEv՞ u.-$4Qn($E O t & 7̐!MMAz= \r-tnAa3XTrbk/#Z<f I}([Ɨ$q) Oq ˒Z_@f<7dmP ||D7g}L=&M@4" @zs8 .YxCPm(AV|)dAVt h#-=@ņA-,OkQq,$e~Sk3gCKoE7PV}G$E O WoQ,CVFSPH`uk L %@|!]t;]H:Hlp]mado᳂r`%Fv죅' #)-20l"܋VWgr!Hss&h.^AZ) GZ1vyI33dF[I,=vr-GCH/ I1ir;@)2ii282;t*빆7PƁ#r=Z$жc"$h!M$F2d! pD(!_=o M f_+L3}LG|V~ꃄhExH~@"06d0fNtL)`k\={8KY;H2En \(4/4umlHTL;@"EBmm -4f]Qwb۞<8JaƧ1~A~JSxe½xc(HqEkފ`ʂcq]loqgQ#uG!aՃ|HK-cS1# O@6=і>ͻL2P'Xf*\S8e9O9QV9=~o AZ.{Ҿd xaEH 5P"Eg@ Y4(T5xdN,*]˒F031.8':B&V2|V2 lwJvș&̐rE9')eXEāL_;)/ gRBs=kXƧ͟k6 G@6/}Kp:2Ul;udێ\yy@T(Ee!߬}<lV U-ŝ;Qܳ9DW/ ]EѡatagK5_EB \Hj ܔfs!`C*97Q/ iE7$ӻph LAS>X$G6}h=S?!dbk2H'Xv!?@A[Fk9crkիz6*ȴɍoа&v/TO%1sH\ayUWs7נQUvOj58Eݦr YE9!CV'5ZuR(!Y7MΌR"aF  *C2c)Zxǿki S@C2`' K<_`z_,OZ&gJaUM.snTҸ!emL_ !3PQ-dP;)RF/^Yy %08KI2|:: ɐ6I2pc nȔvHDaAϻP_͌{ d= K B$h^>7Oa'1XRN,k O G!>V[p'IHbBM49.J#txcQx﹐Hԏ+"ex3Fr<_(CZd 9Z?@ޚu\ɖ;?b!Y5ԉg摠fCgPsIt ςqW @HJytfNGm[O! !.8$By$u.YGIB:䒣WcNW )A0p QT(bȎOb޾sZVV{19ɵ8D'ǰo<}ߚx0p@{2jԿ7),Zʝo'o߿yxaMgT f5vcI5n:W({㍠u}1dɆPF  ?W`r( l>$6.BeҌ|#kiBM.CA ̰e&A9BUjT^0iawF!SV3VYԫR6lQZ4mqoF4 ¢goGeQ*_0kbA )Nc)\jO&b=~.hYI{_!?>ckB1#4Ze!x"zBb3$NHod\o'2K4\HY^Á16.*no@Vʯs`rt,6BROm ї!C+Pɯns/GON@8Gs/Fŭ;""eH7%$ߢ4 I+QqHnA )݃]/ =>1 YC«d0| p!353vLafp3F7B_2CLLMKk|ayZMz G ϰ!n;-C_;C+wI[^|l m5^,? e@5MH-Ö'տ-@gC"dgdJrmz.QpdE)WɳlcH`t܂r+'?хk`dy,ȓ?Wܢ¿,Grr}nB:.Zh^b5j $B'x! 2;ߛ^A* 7w6ۅ"!=x~`/ ["4P8*HpYs6 )[9soQWȗ p@Ο˂V]چk Ҝ7q1;G_5]#JcN&e|<0jwIOy, x_>][XXuFi(*;Yֽ G I1V3w4dlbMaȠ3?6 C(!CW@fHߛ>sOMq{v0gl)E2䇇e! ҳ';sLRdDH\@!U$\#"\3B=(BaL ț#@d/Xxbr 2yD+X0l>źw_LٲeJ#6s|P2BHCӼ0!=v340dѷS?!L+yaQ"`QSĐ BNG|!pSNf-yH%9w :%tS%dB97(G BHb,;}}chbԒܴpep!A,!ea׎m("5u;]; 0]{h̷xއ߾}hؖ HrȖB!qqwEI18 daB!ah~0C`~B:)1`=G$뀱$&Fk-".<;tJNez O( 1YgJmu!*O=Os?ʉW|韚DĀJqeƯlllY<Pi1'g"wm3y'ǖJ}@#pX1:II1 PHX&@s?Hls<|س y+x{6BH/Pqݍ|).'|#brZ u;&2 '*ldh 6LXJ NHB }t rRXRZ3{ ]B6#O!TB-"iQTZ$d.xd4r2"x^1RH، o7!+:Z?dH Y!RWI$4 í, $bx{Hxq!|PeH@eȽ`NܧCA>k gi!FGNRHxm&00pՠṮjԢ5p>$zy 5\HV~C^ *Lt&nxpUm#eIӃ!ISe=H;De :S NKAR6lf󥅄 [?xh΃ {Z0R] CC2VKB*&#_ @H& #MU`$L2y{V4}= hf% K] #p.X1t*.!=2&(FlthEr@ =\[F!,+ԌWp6$oMh!)F(h lk+ZA%Uc |_7A",j`bG-݆%w FA"ޚo2ʭ1CjB_HZ!5gʃ1 |DK{ W'GX0ɐ&3ߝM&23ѰEtd2ývƀ`$g5^]VC^= aɨ,Y5u3H ,  =VyY  UPF(K0$|Č |EODlE,W3]kW)_/d!+ЮEԐ w)P <p&BJ 9֚ r њx$)BTRhk2#7g]:9cl5An WCp"d.\/ލ׎2Zg)ː࣓dKgۆ@} /:,cѝ\Y Ӯv²M d+s6Ia\)r*X] `!5^~ L} +cl_ QϹlM;Ϋ Y}+H/O\)4.u EHȶiW <H "1$!|fO)9lң_e0\+p4~K"aX-9MNTcl-ahT Vp`<2@ds01:2|7g'ǖ^C %lt29-.z!8w1t81$Zg!e!Hi1!*̈FqE 1- ζir,2R Ce6J-۹QY?jH=z!gBD?  쉀o0߹߭DCm#bHr! \r`dVqy;.I/f>")Q[ {AgQr,. Z4!*{L y0A"zF:v2 =tBz8x>Đcl>t iAScTSpN@ {|3f,#Άt F*+0xJdH><<$(]{ n0R)anNNlz0,H?AMknց[D6Bbvy3+Ѥy 77" ck F>ƢΆNf m)6ICJr5qR*'|C#lFf@o|Hbҹ1qR))~A#Rf2&  %M Y=6s:!fq Hļzh8*A !Wp6$PFqL[ D8ue [#<r"iW=΍j|KAؚf9V+?тnHH|2ަz tW==Υc.A$OVhg 1$geb ):a$Íp^@2E̫Ap@"ِ|5u V*.U)q V.RN,X <kF- $M ܷ,<΀W D* !BV >5$Gc y酰ts*V(@ GXtِKāJ;qAt5KAzK BʇR7Ŀ,eQa LatN( @B a2=DX1,ɔ`!BZEkL"eunBރsC"2W{+ِ Z,*4"(lG?e-ȯ?O@2=fctU-FIx[O8,P+p.2}A"H+ '5 c,$CD166 jD%ٗW(7[!Q 3?RļlHƠ]:l,W`Y˥[t ,˹D|{-D)M#dAq'X ʱ䃄d?AFw4}Bpj(![|BISc;R% lKaccnMCCO[t gֿoϯq"s_O91X3KM &`V.0HSv̏e5H`,UbYjf$c[ aA3CbVW 8MԍN( @*xr/u_ٝ N@ֿ湌AMq”&q3LHm{'@rD^6S@n3x .I~Sˀ DBXPt hO.|Hz͑!LP'-$/x4 ^ 类&{1 hhKA<4mӲ'lA ^B  NfƠs30(n@B`UJy*(!.rb7}PpXH=rT. ~.Kv/% C;j<] 5y{~)jb~~HPZS.$l^ Ȟ0ܰE?+f.޳ w[Ȃ@EMh-j]T  oAe{nC uPiAuFk.cu8aBZט10"IOx}cB~.[tJN`S@#gB#a@sۇ3m1/[j4 @}+8d޽4Vepf={4@b_`Ӯ] k!ulֳ'd^%Rp@Y{fƺCÿ 'nj.C:"Cd x2Ffb2!U#Bc]gkqF!~Y%:${͖o)l:f36,[;oxSOp)cl_ _C M:!\ahnNS7] $Vu5ݫǯ_?J)sY`Sغ!M!3pVxk8:o _&D`! .a?q[1 6Ek =c4:5ʌI'~yDːGI!!o!Zw^j3fLO&UTvs )Er{߯2r8/`rX=An R)!CGd<|Ιc\XGZP^بcteYfɐNHU<'fl[('Hd#n5L4YXn5PЫG2 +OU1J{0$tBBHtI>ܣ̇i2d~J} xfH3̐A(22dh[  &4c!E05`BEA#g,dǹ>׽ne!!5&x&/$\ha ɐNH5{຋|13=| ,HohKaGX'Xc#R36>lfF-5).ɟ~)P-opㅱ1w`Y5$CʙwѐLFDZfX2'ߒi2e遦.H ^C CDRI{/8* r^clⶥ 0F[<"ur,BuuZ7 ٙTbƏm*; BF [EDvɖ҅N|=)]SA֣N Y-$?lzl)T?"bRe3Ȱws/Z{gpK۱rD!mH;$$Nj=) kP}vIz\ Yf7d`ԎԢZnD\rwJ"Q|FYDÐEi!qId8y<5.ѯn rأZ !i=V঵ 븶"ȿqk }։,Z얗p^!-4y.s];6j[Z_<-ѽJnmi!B~E ܹR([injDT%îm:y"4n+5IuU,=u3JC>Ns@概̔,ŷLS@UPͿ9-~1{`b]GOJE|Zu"%XML$̪GaWMN \ҧ°X{9ESӒ3dȏu 2)cl!O`!+?}eNюk;Ƈ<]dxD`>КO^R@;Oxx`?f՟{{!mmZ9-dKZvk-w#2d QT9׶@ )$'{ eO>J7~n&: !k( 9fWX(%2Suvz)2Ҙ|#UWs녠 GQ7#Ht@vRƻԻexhBXB֖dKȸ<> Q{m 'zf9_;T/H!g#@N:g?(49R b<ؘUz;U oy*WI`QEEҽi!ib?V\.n$e!uiR\Se)~n"L))wiQB%Dv-D Tei 3=B+om ' $[b72PH0#%ҷ,L )؂Te?PO҇!n lLLY7I9&*lI R@E ZTφCN Mj~b^Mtw.KZ|KYٟ );Vx"YWBX#Y֌>d_.x n۵ma(oVB:,ںỶpE16t˞[nqkvv?֮Ҽht1}ArJ"2,2{fSp25;hY>OTɘ!70,ge tFivp@.XLH\C3)!s/Y`.$|B(\^j%]^Fɘ%[}U-y,w!뛯kA2}":Hh~#{XD16]tVQڴ}᫪pYT-l3*ȗ{iJ*Bk7ʗ1zˎ]G ,RzwrݒI}h2RHwPXdBgԌ7% }nn,  SEga [!\[Ԣi!,xfB2H Xv#M$ lY̋d\Rf̆p iX9~d=[0!; KIzB~rm>R%H eNIj1dHIXw$ lUz_'V 䰿ѐNRϟ{S_hE) $;IŐ\d2$H߂ -R #{C ( Y4(mh42E!N`)Hb#fr bmOj4 0+A!nw "N$c(3`H.+OtRc h'nZA=3M~D---9%5]$O;|#"ْd¢@Rj@;}CW YBݝ҃r;-e#!dȆA 0$cApwJ aL|KV!1,6oDk[y y iHT\)]z--%U'CR/OEibSR_&jQ_7§C#xڅ|/@j :>ĐxJ! >׀!?y/xyB!1,67x3O@s[k%@RI!F;ާ?\Li0Dh  ? qd\!$u$ ]H{9A6M Qnփ VI  ⅉ! o1\==Ɩ%|f* po k* Y)d C@[B{P_W@/P8 @_CkҊ XH!w ɔe H'8`btjBxdC2"Xi;F!1,6`Y{ -Ӆ'&a=C<^ҒmSQ#Vȑ9t.( bm3~ئ ;)PB BWi[2;99^ ey!^2B3Hkqx[9ka{qAEh! gmH-{-K+~+G6ˀ1\Ijµɕ!&xz5ވXrh,=,kA.C=ұ u W+ Af/MZ1K8Bȧpȋu R}y# >yA^GIZ[ECNF! "DWȿ1Cp{$w+ C0Ld.dM 9D6}tB&V2HYz3)-́q]8*;=Ɩ%|JϠRÕDS@Ӣzd R<%[fР{O' Y8!O%f3ɎH@'Hc_,m`;֣\HDs{C!16 Vcla`xR[W! B[_ @bډ"țCWa^R>/IH@^Z>[]F8F0VWRA;ow>( 1M#8.'{G]YZ[VE@ߡ5$W=[ Ln^y6@ۡ+Yւ uHSɬ+vHDڇBHVKt RV pV/`w˗ٍ HěC! +:pqP&6L BpLl>'sAQCpdp~T! ę=.N( @:jwS]&2RxBf~5`h8WRyY I0 VkBvH:' $GP'k_9d0йwG(n kvDl~-`@L t2b#e1Ȩy|Q/kD-tCmvEYҡ9;|0E`SßgV+l=`x3A1b?=Ɩ|Ơeb>QצM ,nmX|v3E,s Br- iy)?p^/kB~HoHH }H] 0OC=~Bz$Y>#</x5*G&-∹9BQiLy2Rh>Eq2vUC8}ҫ;ULHԆE/&ˏM|qJ2&3 =nJ/qof\w^C5HJWҸ76q BrEkQΌE`yy6ɛykANCh݇w |r-X# EOӈ!Yp`]O0 Ghr>@'ܸ_¾{%g ڃvK6j2cܭOC'  QJ_9vC;դ >sEic,VC}\=wgod!Im~vc]i*9J:#pPPz;84GC O̅RBj"K´<ᅪO3.굥gH|HJ%A >XE$z=?aH/Ű>gEN|#CN٧ ,B~x!Y$(ŀ!r8S_dr[frw5,GH?!(]Ȃڀ̱D m: ;JI!KkTE6=dQe:svӼ/;Zi}?g0H 鋀,ͅL a ve-Y/^Brgl̦y8|ϩ9S.֭XH٢!q X(kC.Jy=Hk;\lÛp+t'6aMx ՐO(!mv 4"79H\~a˗De|`HS`/&:U ʮRh!_^ lpɎfֱ3 6<[)u 5^Hߒw(kh@>[-" 횴HHˆClJ[86,,,,3@|2#^4靯C $La1XHkf@! $"d l:h=0w1_Ұo8仯@A_ 7x 5+mMR"([Dy {: WxH.Bݗ{(^J3zBFvu[H=0B"r@wN`e1ȯ/i'F iKmϰgr[8HS'4$T9{g۹ZĐHeDavub,&21RBIp[W yC2F6 u'$n۝lQe{7x!svVxy eL>R _^ CH~7-dY ZWlHQ tm{ܖe~OÅ4u= VYgtRKA0NHwvpqB2F6݁yOBi\K I HGmQ.[a5ZH_doCgҟk>AJї'd81Qݎrr@YGdN`4 y.Aba(ZB᤟F-d$  N`;ҳ^A i9ykOY[h l:zU1=DǩR-4WU]`!b 6XH 1șoUFz*ofa%>0!ɜ3r!0IJy%HHc+e *kڔiC)sBe8#q23XHcL \X mP9̇@b3Q s_&$Pd~G%jZJ]6D]|Kw'{<_7!ÚR|<!Vy 0ImQahF-d!h!F3$c. <_>&G P ]RQU'ϥOcxeZs njW$u0"*,`Ȳ nzU?[ #j\Hl*εA*0_T*( gXyWR c[C6xm^-r:$ƶ7dHY!{?+`i_ _]- / z'_u6>^,:a-TjoX-s![ḦoGBPȖPȖPq Ȼo cH`vBWcJ1tnb1@rQmڱ EĐ_ O1$casH0̣1]\4 s45ٮ@Ha@0NXN z,;퇯uv͉ćlׯ߼y}\Vz̛71#) hg^k~ _N{|H!w4z5) {o%wBR[h6Rjb KR;0$cVȆD[ef!oj~V`B2g[&'aUa*_4@Hv jD+VX |?ICm1dEU{/$cGn]Wj}v!ba% I=[a m~D  No gDݾə>My%.({?jɢw93^H<7$e\&2IbY SUHm q^ -wXdC}[;Htze=\qM9~FDȊ{p[Dn>wK2\H0c3yHa#u9MG$?4I"j$c<HP;kSG9+ݴG"NCr ܠ&v2h RxI-BuijƎ6\HSۉ/Q8v!?6aY QV$»ŧ 50b<0+pZGu :yH'o<YhцN|,B .j2sOB.qB %2H:)N\L:qzY'b8"; rz/dpYc,UYC-Z@>Y1mʣ"e"څ|? A2P@ts q(:0-E' +Z_߹u)$o| Rӛ(dj\w_ HǡEdHY50THL2%Waڅu C Tvg` -Y' qoOZi3ҳw߉ c{HZHN>~0̟H=O*&-Pii7 1/d#;( @=gB#9@2fAUI'9{E) prj򇟱%M| FZHtEZ3F>Cd,'gCa|]B $cVDct# #++,Wv ,lob!ȑA$)=UBFP]=EJӍŽbdE\H'XDm~EO?%@>@?$ceGXhȚ&6v*dRb,X.7didM+]8c3ZHU\B&_T [4ZmOUOD,PImr|~ 'C"*q@PA'tLfXȑY܈}2Q;'V6HhN )j?9Ҧ/dZB aphq$PEjP (#HdD8CDP4DmTg$c~܃k=s=@?AW28S ?%~٧̄9A0cLQ s"AZ+§j]!"w }}o5gC2 δ2܁s1@z5M4㼄<6y-;uߵi3O;zQ`[ ķ2 ϪȎof*O0#|[ ^W*Xk\8X߅\m9" xp?VaʨїQE $zW/C6}>'AZBV3 ?9OJ r^y-rnD0y ®*n.8"+`;ZEH/t5e!HV|uI= ?< HSLA~Ly>fK߂ie=dJ{-( q|,h&NJm# P*df!^m[d4A 4 Y_[UjrH׎ I"%^p'Ey0$23$ E2k*$~d5+g_]}zA÷7!h'Oވ~{5!ڼB^ Z|8"DK\=xs,2QPȕv>8v'|U)dz}1`ع3>HPX!ˣ#%+=<]:V|iH*` \3՝~i@;R{D14It 89d<ȕvs).;*5 ͎; )2hH' cj?:JY o  Ug$!x?3 IO?y45#nKMX-x'CmQbȴEgj8"-$GS9S:U<㥁ȁ|='7rǗ/fBCa9_p,%hwn%, =Al|?)I ;'~LA&%|x1޻4dZzzrRB?I R0eaô}W_r//S *e8yÌ}D'ffݨ- a!713wd,m*EĐEmG툙Ѱ{8ZH8P0tHoÊftbC}eBrWbU.ͤss:ɜ q_c|drgwEo ,}j< $"4m9n>og_q@@S@BGCh! *p-Aju^uLЊm_%IhD"3¥<*Qd2])!Qp!-81֣l`ِ@Wvуm^EEH*gI؛4-H0i.d y%P}mhKA;npV~tB",%j5H5!+# uApGmQ5#(x 򒯡Bʭ@1 UMp)~>~wdIBb~7;h )c$zm=cћإ [PH!M$s?O+OYz;[,g@ɒ-1x*Ytad.-D iRjq 1.H׀׹A ْnWL wIwQ$Ns (Рc'0_jtV^~hTC36CrF v/Co8kA7 #|1)_Ûz+Olš~@Q5Cmѿ v>o[!ᦈERJ=aQEbCOn̛[ת;_ia6:e:|.PzI~Hk=!fӈ\aĺՉ^+Rp9Fsڮ>ާlͯփx;ud M?$d c#l ey ua.tgg9l`d5 %J >G2 C\# B4JրMF d/ GjRVPCQZd!?>9o(N? KA|9_f!sa3]/ _aof-2m{E֝ &P0-d p;")WU>w/ZHOCZGk؈.$u"4ڍojut܏Zi[z>PIcO`v$pv.,*/a-·dE.!sd8a_{w'Wp]F E iCBios۝) z lNOV Ipgt?/~cWau"pui kÍowE%`!(/dN1ڂr >1AGk0rs+'s:\E2 N3 UW ppQeHxKyQyU/ e W 6Yjļit `fHF kP}e%e5[wD=^oA—f!7 }\|H/pA\ d _ZVڛ92·,.kr{2rk6C A"1`б X^ FpȯAX|e,P[|HBʭ]țH􌽞2')M@MN`B )߃M8Nު_:QfLdZ]!AG̥w@P D4 Hd%6 ހ^H5%8bN˱lH+^)$X2 i_&L-_Dű0bV΅Sj )?څ䏭3v )*wZQ+PN` bR:E~~A "xrH-A+@Q4`FGӐĬĴHh|rՊ]`^hH050bz.lHn;9w8[L YP 82OP@n=UD<v֜H!O#PH;#BTKX4 )b C 1H7zHFQ d2 ƌc>wi4kq#'.q1ɕdR'&7@@@A K%Jtz "qIf=Z'2gCbJ(!M|/h()  lH 9s!Q8LҜBH:&iy/~Gm@9enbHmZ2җ_B\08~75i!6'xF;fOL>df 0>pn|[\Hm%DuϕYpYn  ]7y0"lHLI\E9U+|V!t.;W!&?4"քhG'A1 (b5j&e8o%wn$C l΅JR"J6:Ш@0kS #"vn!{& @*F@Ti_5?T}>m uED nj9caN /2!1rZLq2$Wx{̄hήkTF L<vm]y?u)-:OHZ SuLX\SI!jBB,LLdH؜ )$3Q[Y qmy^|1#?!ۄgEn|Öby z pGxF/@c w@}>!^D0!\ 9ՃGK\HnO o8鲼k"HƬ! ̛Or+%]+A^$CO4mECjR}gW :& Ȣ]p y/};đhWA#sY w҅DU  M 铥 onsr[|wԉ}}Dal#BH#rvI+)t d$c]bmgs&d?.K= ZHS#S Ng Ͳ.%޶@M? zz%Z.UYثCLHI82h b!@,iFO:< P^Bby`τR <ך%0f؉̰w$_k!% Π&;3ss!UJ GX_v sA$\h.an:l+W:ijEa=M65m7־WHa8y6N%oR JHzF &w@!e!YƏqIxYBҜ"/-VِnRw(\ȢH_ VbX}Lj!-10[S(BȰӤ'GɔY"<8#L;IllHC7[-dypul!|^_6% HvYuN`s&$[ dY@.QYPg&0B6<~dT1k*928+^2v?=ď[ aw{fYn͞>ԹwhU--.).܀f0HEK᪋Bkcl p!s<hkC8t(-PxkQ^9 8 al"&R>@@گfXsfw6#s!Ѿ[6i!MfA›DSVʁ񻝔v/ \;aؘ&d%L-NuH!_(dbl< I_mh - h/c:iRL Cָ'>DW_bKv;xX !zk-$"Ƒ,Άtǹ vF@;nR0z!юV8WDzg{jq,L05h!RBf)bsm;zhpVD@O;ӶL#el+"ب 9(K W2#0݂|$ /|e@Dl 1RDŽ[= Ik-$c@<,ks&<ꐋs!IHR{t |w 3nOGDxݞ"D0Ha[R ^3l|LP0 Mx#p-$|uW l*훺ݐȏΣ]s}n2/ 1+-%}|}Ͱ牼_k]\Gtܷזi3 CBJm>);pŒr YF  뼅C ӴE`@1$ ״iC` B@ l+$p@H?6DǬ q(䛬p=G2E+`+`WmAA€+ lH{RyNz!l·Rs!Mzy R?x^$l׈5m}K?1.]PT22@L&m&p|842 Bjcl TV-0\@de dR4`gG"`hIX+-$C;^4N`-BWIy~~  ;BVp2$o#81L i H8#-NH.hi$BӏOck) MJzE!@Hj?yQ0c9mBz̄mCjcln>/1%,{H!B!-i\f'~"M h)H C pt%ODWhu@y}4 v<ɐGI Hp, _fZHN@T2?$dǃ ߧf L`r?He5)ONI M@H B6TFꃖo]Cu@jcl.e0@!(+ޚـ}Po$ɖbnh YA2JJB& S\1Ic@5Qu ֓=IWe|g VYBw]9}Wِ&mhI̯ z{Aizr;(FX=,; ySA>:>Z&G)T.d#t/EWeWW-D*w !]P;R`0f@jcl,se!!!t!àv; Oj@kJF:O4%+>$>4$r IjQJ @JOs!>}> k x=XӤp*nt-8҄%ȵe?1R!sG@)=H,#ASA@~Y"_h[G[<ȕPHۉvOHt9pA2fYd}Adu _36=Ɓ:q%8d?&EAXԆeHȰb 4}w iF)"; [RGiy?!Ý @Růx~!nRWh nyv`B,6H< W|fdfLb y@ߖiB$\t#˧ozf FZчj$K-Ԑ|)CVs@YD YEz!WMX>L_ P,m@jclh#7y0L/n w&x;tv` B|_4H[%VC Ԙ?myD4 i+ai ) 2@įͼےt:$:[н;H" ruK=\L? T/Y2xa,} 2uGX޸`̅{FE-oS72o-B?ם6E >؎%?{W\[dNGv  @Ac˔"̩B L^5 t2 &?gz0F[E'Hxt_}7s=מvfӛǯegI|H&|áަqyk|+=raRz KC |Þs/E32G>dG]" 0c(ζSRB9/6-F %k@A밿`nOBqn%{(Ly>CycKę>mQbՐ?+3;tFpÅ#_vQOǶr1}%ks@w3JT#q),Y+40-JFz)^b 2[%{; H9s+}nO˵XtX4-E&O~Uc-_R>wSnb˸۶b)&O{[Wg!oK>+[jD[ua2gm`H٢{ěYv-x3UF;weϢ(qN^\%-  cY'Ẃ %꜖AbzEU->]j$s^ss؎X2$KݦmA3-ZLaQqU25dDo׎%d@дE5;OH)mĐUz¿FAV%g[/;2m1RdQ:g*,J@zEWX^QTX9 Ftے̽&̌;ll˥ E9Bげ&-{*Icn [UV)N:̏`)8R%4^6C@̐mU HF3k|//?#$BJYpa:PH8l",B0RA|2:"!,T~ܹI/e0\[zV 1_},`>OZWo< $QzB#'!BIԎ" Eoa-bՑyŖ3XHS\)%f"Hj&}B"KCD ,r;.#&'@Bf 5TҲشL 2\HSv.U!)3ĐDb q!sq>>dn-w[}V[DJRYZvh![jآ\ icl+X2rw3ZȂDVϐE@ Y0NаE%Y1VȚR5 FKaaN QD!hBβhDշ?"?icl俹&U`!w;Aͼ!Ti"IZ-R_)B^ZgRHmI2$ENF #-d [ԓ+)JhBb?jl]ق*drBb_g D -BU^i.H!ݏIhAZC ~}E( )=d-Zf5lQ /1&16ZUE̡>h!{MF}ƵlKA2p4aEoh"K62)dg62 iOlXeֺEF N& 4Ҷ؆7i,t2ZKh*z,Y)q@-;$7m]l#& /^I~F 9gV9pTmN$EKAZՒtkfmBL|+ bR%%imRy- ,#t [j6eY\Qe z[^ػ>J uѰEZd䟯Fy;5N&<5,yؿzgLyyK!C*$;M}|[0*LT%ޘqO<9ftpu ipꃼ uS^MpҽPׅ7؞:Ys, Ȣza)+uU&_]92*q5c.2!'xd6 LAoU~'.Ҟ]3l2q>!9 J;/3dY`~ͷJhFQUlS[P(kA2VxAc 1d"ȏ7HR8? 7R:  l);dK1Wiwgu1 >!U .!P!}C6f_'̚Hː5pΐԟP~Mv@ 774HRC6Y@25$.^ґ8mH=T+#j[`QXCM[eHco/`ْt6H>->q@b7&ކ4sCY"o)U m⌈Ջ~q4m!wIyIYwÀ/@_26H=>!9f\'cA(eBwGpNl#HӐ/m~H0fiȅ1@Sd[EOIZ^6:H ^Z"g6$Y!Q*:ㄴ" jW&lCx-8a&[i2-z!@"1qQ 5mV!{2r8;dp=y)6}fCr-<4%87PH@E0Dhbup!Z1b {L4Gi'\?$v)B,BR[䋜WY ls/ۇ|αCn}p+oHq1G'$jAoE՜5CVO&%EIpeH+ n3C»mY lZ\I q\w<-#}CkQ4/\8L&] 9"f@fqhQ} Д)gF?#QPGv/h`o$UXE ,k@jclE+?Bf}C zNgNAF D %HM@1#|D !뼗#p!<2@jclE{`7p,!5>!e<Ά aԴAcڹ]e{'uiig gY>CBkҍrI+AϹ#.şڅdZ}yTspзR7f%I792ϾH0 ̏g\ԧdz$dg%P7ցbHho$U |}DHus! t l~31=-%6=$c#I2qCgf{'k$j8(E! gވg$eEMB@#?*Q8M ;D}C2h$=d^} :l@bz91$KVxGG>CHYQ yN"ʿ7WD"d&!mh!lH6D3!1}!PBV?~@z ":neF?i2D"FޖHdj$UcH'X(?4=,op=_EPN7ב0Hbk1HNոR:KxًAD"{ %DH"^]Q8M k}=$cNt\%ZRpz0Hbk~6DFxSDUI|#5C ȧ}HeM$7l߇dS]Q8MO  w:$Guӡʫ6Ek~ND"2PCSB<O?i2D̪5ː3# ' $y R-oƙ K0!鲈C{#P&?eHםrbвg|(YREcX5 >!9T['@1T_q0 ṩS!|E禎OCzv`g:BN`˺bFR|b@6ZHY]qԐ[~/!'f7x#XyC>!"q@VɔP1P/`ހ2!3N M5Ku&S ZKeB"I%cyHHJȚjg8$O! `*=dQ dNn#$0^i<`n;`nkvE H-;2]RÁʐΕBN`Ht&T ?!zm&k2fA~C!Ǹ}+\7AneB@ 8͙ BIKNLt9GOgNa>Qݠ,%PFV . #Q~Bm>!;q^e HyJ]t,&WW-Y~#9aw 6@8^ af1ԏ|̔ zUs!ذYJ;sXhON.9쵹 H01`!Tc{^<(1kA׃QH0ă2}C*p[ʩ;~K[˹L_ʜ =B |<ۏ_r%QzU-|)=@&G |eE۝3R%ؒ2=$ YR/M8CTgUѵ_ߚ ^s,Za3~DSxoQ?9G*SbkvHN`;&p^AZCk2Zq&nyh93"OvޭW0wbJv! }rFe/5Wy^|EJV, /3??|9J,MS4s097ޢOk<m5GuyjmGZ@=<BpVCw>ADDXC۞`2:8vgwG.(C~۲d0韯< 3.߲$!E-޿?/%5Ǣx_Sp" M( 8@zgHRchcA ,$~jcL *% |ȴ9N9$wx\%Pem!W䟮Z[E }K#t+yGЀR.Axj"mBʯD"?!u%nMLJii/0e>N6RlJ!M-2BZ![dJ$ʔ aǵhmv=' \dPoP)$ŇAY49T"+#!i5BV2N`ka*EXJo\4:fS2@!{%lE%g֤E&mC(&lD*'QCH̛ ߷E$l @ޘ_ 3P@8KtWhhFѴE8&VoH_R;m-`Zδ*вkY:^B"l# y 6͎,'1UiD?!k(v[MZH"9m@J8if  ,Ӱ/ XeH}EoH ;_hi@ɖ-G/c\W"~}-MC8=<EY-D>!=Ʀ/hhJd#.Bipoc n9RcgÎxF:"k/YSn$z$Z4#Hz fd2 !Myƚاae}/ҽAyB<XNM qO-i+nQW/Sm!){lgvMjX+ZZJ(Synݺv"5 d T."gK Y\Y"fr:?efx.ʽ`M+Sݚ/nNlyKTQRd.:_=caoB0dArO+yv?+af2 ڣV+pgT|P[h{r+E2c^ M33(*]1Y 'o6{JB InsSCm.Fkw읶!9- usϐGgw)!jx6\½[?M@ o߿wlֆ-q>yx,IAU' B!xLs+B3>E9$ E;i^,/{{s̪aPD&C!1KƋ䎬 ik77}U}'1KC dr 0,H}z}VU,b^y_TI7j} Z4>mT.#[*ҕߖdj5~%Qsbԇ:?ShՑku$1En׬)˽|~)cSw `-ኟo:MC}[@ Yl7W"y!7A@[V '~Տ}<͖ ){]ְ QQU]7ae8 .G}i+jOD$1.h`rۼ ㇪u@rQ[}fr t+CAգ,:(4kAri$ϳg%F JgpGp%Tf{kt2'ozOEc|PPyP026HaY^t$x_c.F))Anz;J2mpON.2`_Db$V0W&ZCeX%#ryN"C?3eFٽIbf2 $,Y 57mz= ߩ>EE*=U :@X\3Hj/:5NF>>IZXXQ OUG< fع󫥶yhwΙsͧ $%LntXBZcoۺf 6< ClŐ.5!mwb/|۰3-ޏe>r8+2%slWqVAZL*NͶk/Cl : Ph6_2xxYN7!@!i2ذm`una$'Vc0yj0h޼{5( U y>)Woi-ACzVG>St]Ypdˑe@EBnNjpb ='__FX,{l2d^9x @ h %aA -rۭ{_.}3~, Yi*uo{ɛN1[YC9h3lUqV韮6őVgydʤjFbѠ`A60<:T}ߋJF6%m̙,f tqTEE ,rsrKY7Lvk>O"rZ,QZ QYc@i\Q1pÊ\ZRȵ ![B1 :-9ۮ|"aw|Be9x!َC3W.d_Bu@Bv@$l 𵨱:QK 7C;)%3(OE˕eKShX{ SL?^X"]^8$ɏU[)hFV ex{ ףz@5zd¯rΛ=W##w,µh EGTO)sZK%]$ywV&t!$fY,ps@Cjo1}:$"+-EÐ{fn1AB/t [ !}@n\om3dH싹,@>UBo~7Uo@ s f(4z=dVMs؞DC>le.oB MmQɸ'a'OC-G Fuݷ.n8R d 皏~A""rBV*FKaB*tP2W:p ůA2UB^x  5tE< l*h i*/aRL(bz E0Fj`\vhIQi5 @:T3_ڛF=yqB>.D Yعgo]I!f-HL୸3a 98E". z X$s;Ŀy2$ !=Ѯ)0Hj.ȮL nk,Et~Kn:Q[d9aw},=4iDٵyQ 5.&@ CBAv.$b D&DS2L ɴ ԧE xjq $ HAC{o0MNĴ̅ͤ7_YeDeuCBr*ɂxί9r.6j7(ƌiyaj#$005Hѥ qOf?N,H֗ԙR( (MmzL#9 PoN% [iҡ$"Sf>ͭ]Ha˔D73|\>{w?%Ia.d`FH ܢ%E&)3Q@5$ >Ce~Ő#kة%62# "zqJAt |$ )OX@!];f|?ҎLB- E9?kOT)h!e -hU&{֤;A`M?puAD8_,4Ղ,W4{"K+ol=n=(-"<Ʌ ÛσxdGjg%i6cW[xJ>X[GZj*jX@i"6)O"#`s>9A^BMUBZ_~Hi:!-Gh|q1u_}R{kRV4I ڏ\(了B;,I@`ԁJADYg-D{@!eLu0RH/ZHOZHZHwZH}-%Mץbǽ(=92a"l)ϣaF ǚ@`E8π4!)čdzрwb ʭ>WdH=TՔnj1zowGi!}g9u~. &O5xB k~dQK$*E/Sz+d x3RHy'JHwZHWgAPl5g9u'6oA {67o`>dg}m拓RTb}i3Bz ڜ"&jP=LTT 6V=?Cʪ*h\MΟ2yO‰"zH^0L, 3l^n uKxt4PH#^Z7DDIGVYy&|[8ądhHE}onq&zROj)C B. ;!;F6whI֫2&v.2 eH S9xG j vD\z@!W#OZȗi3oȞ{>钸Jo3$6.pۗc[+^IWyoB>[)JƥAD!"Tƭq*BG,v!޾&owWh!Ý`!UP.9>j$ n H?l4W(ذҏW ,$xwig̈́a\_Du 2z &a;B Fyt.l4b_u"t;/Eny̞LpF)>z #ikz#ZB )炠 ,lJl >PR dS|4~jD>?:| -Ed -Xz5 X˾lC2@hub3&p"O%i+ZkT^vH7).2M} iT+_pe`x._XbѺi ӦB(aB:TcQBBiiC$M[d "i@3 /nGRk+œ`,;*:99 T?RE6u+_e2|~.{kRV5Iy =˫*ykk:pb%T^bѥUmƁ+({M !'PdHafcɻBBzBzBb<#'㻈zj&^ؙ^x>Ɇ %\^!j[ \$$6@bFe usD,qC2Fз^m(dDgH 'XP _`m=čz` Yp.ƴbȆo2%q_($cxNjBcj"ZH7RHodp*gb̑&~a!$goo!h?%}b0͑ʁAKl͘w 4(2pD,)UΜ-b l$Z*lGF4)>/<>)E !K9RBx*33J=O︐z+ss/[OO?Ze]^@!eEP:ϴ G C M ; 'h*r{NoJ[Μ5QpvVɛɼM'^ n6|%1H"yP#E ?Fmx'o+hri!o!eyz>cJZzZ:<Ð4?/܎'% Z|Ð$@*e]2 Ŋâ>I38!Q痖Pc r%nZܑ Hl.3u[+K1]'úW5xJmQQٙ}LD#UKAEfC:$ץmy-nԌbՔEo{2 ]/@(s+ܖ7Ëid__ yqo?-Ȉ8rc >!YԿgAViU}bdEd%7(di"2U؅KHs~Ϲz)Ϲ xޏc 8Q,p RC3naE>CifS@݆ S[8&ㆠgm)W:D0CVvrc>˽Yn柧zɛB[A zfM:rG9/A2s@~uoN:s0UA0wIN5<3s?d\P:h/RHrYpA2쉽KpLxȒ> `tDw!ޛ́|'X2.А䫕`1K@ .!P&UB U?Fhgch&G;5K$Z}HJ]s߽6U&@v*r[>1|_ %ˑK$ޛشS)G;߯RTfQ[do,&zkEF Zd/lN@bݳ; Ҍӭ-[MedwHyآt`8O7-T bBZt{ɛd1jncz{ƈay78iLScB4Ǣfeȋ9^2"q7.2Sbî1TeHzݏ2#NI\f}PWd:( hcmeN۟u &Ձϋ#JٴPipvkX %,*k@`CfgAF:.7tyH;-уxɐ+ny};ât-d\ZH'?yO]>!d@$y'[HXdtLl\<bc#xJ L~E'$&&%''ſyѝ}-ަ1p yJ80Q{^:c\5^̩\7t_ JHLH}W/6kQg.^y7cccs !ۗTk s {7>)ZL!ͫ]plM |ޓA_1J{'!5-E$a!")55>J?IϾ2G]FnQdw^}枠^%%)|g^C)P%Pĩ:Ek݁, [,ZZωS Eoi](JMjfDlޣ_vEª6nތ΢Z)BF215'ȷhö{KFLifH9?o0А.~>eu3t'GڷSy UhԹ13f̞;wwh.,#,]uC+N! ٱu% K@dbekkÞ&L~zulݰZ|"Hs@dg5 }3{/$`&4d0;~aztj]tD$K=QΏmڢ%;K`W~K6Pp~>y3#E,fCzW^OZNe#+ӷLnt|6)!V lvQғwolQ(qR`ϹT8eH*ݖ\pܼ~W؝1Bv]pg%dž'v4vH5jҸVa/[I,٢ (Q,O+9 *U"_$cVϠhNNgKL8A M5 g"GR22ܿe#Iz/G"EtZEYR! 4qH٩'hz!! ~m4;W1)rm;p`Ϻ*gGu2j 8tx!%PݎcgΙ;M|q*ZÇ7iV1-BL(;Mcc-F,u%ÛE"2fV{O۹hh2n 'SP{DwZspf#hE4F-Xter)Vv.>/UdupA{: )ˣ;/Q}[Aޝ?mڑnu qhm2J^]]'Ho1$GK~HۘDcSgɑWԿcҍK:YzUNNco-<-?nwQϘescVL OIR=*M<Ǒ'„#2Ր5A_ݪKlNnuӛGfAH\e~=d5[;r#;ԹR %*O^׮^ruSvQQɽwܦ v4 zet壹 =s#qE㰤lq*?K\lde>[ isLX xFnL? } {VHi>e>ŚD416:qI Ds`azg! -ǿ&hqѬi3*]pО'_}o?^VXAr7_?ٝK?~ӛO>ywxTQa$w:yE*-;?urסZt|r7CH <֍aGW!C'Jʯk_e eRpg=r@eܺf>ޔҽSdO8NpGMҸؘ$H{ˁ&Br ɨpGi0dE;e1Ȑ5.n?E҅ tav5Z\{Y <Ŀbpz|]K~ԴFuZXruYA$}su>fe ;Oڥ vqM sv˷HLb_Ǣ,IB! :~̱zmب pSт^o,9iP~R7 :Ҵ?]_j7iwG~'$ ; ^Һ|ž;B6x eA0F!~|ܣ;S^d jb>fXXJ TR%H:U^-t=>"6IaQ93 Ts^eA0RlF qO- ޷pow8"X eʤAv+<“kJ@έiINv`zeڅמBOe˓KNQP>i16S;i2l } ls&HQ[^׻Yr:+CnTײ y(],We؟kh1bŰ[Ő<+YS|g dfJȿh #*7 A" Vkٗi YvSEEp]#}"GKCvH#tEz,Ռv1CڀTHA1B u>eqkwNCN16C.bC»E_]Zt7-zȊ_?_P4rW Yz7_<$\Uag@zFe *@.<܋e5 =1/ H )d{h2u]|~f3nl$lQ)BQC}H2fr ϙ3"qPC2ZH'XU!,5J8mf!S;284v`bE Z~'ЩЕ&AV?Lešfn- CZ˷'?>a.5f,뭃#%@ҊE 贂f(#< tedlA:L;x k/ݻאPR9 ! )cf/*~% 2{|k?1\ {q"Pi!$c>e٩>6ˏ$ħ2/RdQb8yWlĐG;L2kr!Q?;ȉ dռdl+{ ''P/AARL<?XXMBHx%dHٹrNu6d2{zKvg)Hf,UR8cd23o#bԭ- ITC CVCH$5B-x܈.P'CF@Z6MAcc_StA:.+bAb,,&:+̜ L~Sxw؟_!9UC)CE~ʨː}`16Ƭ0?_D`XFj&4  z9ڈR? I/DIAo,7X sķ06HDͿb 1$3Շֿe5._]0Y!]E'аb?i[Bp|45  4OyYHjclvdK2uC.3)LH\8G!HtJB:ηH;z aڝA@2 l8dCD.{nhrS!,>+hmE[E0(jcl%@2Y A [/QXNhNTH\jk܇T/VB`rs.k.8'ҤgIIrA@=]oZH'X!gb+HS{utU B2ݝ 5>SDIWոdB\ƀS9k鵯Eڅ̇VWmFb A2 W:'X$KDS$;4X |c!N( CZ @yoTj< z9JQ|w K=FX"ŏu 6S&pڀ5|ICcumv(廬#7V.#Fm_Cs=*۩m"&AYwpT m%ͻ?Xȥ{w5sHBz {) Je :db7lb )c s 響ЬcH?{Mւ1+ yfpDzGϲ, T(\`M5!ôYINH&)f=T)|w,lpȒ `8$,,Et+w8Co+x8 pB%$E3""®\UmꫠBf3>йR#ҺFb>?}юr7~#'m~ …[d`]$=qBN X_8"L{ .ڜB9B-?NDCFB3IUkR5f Rd. :)Mkr ]h!-B**"XiskeSI )}i$9Gq2=pFC }%Ͷ ߰%p2ѥAeM`@7H9"OX(ANHt0!rm4Wyj?Iyt۴_V1\H0)v0柼`HjclEƣI~"6!OB4A}dk;2Tց,G9s^o%U' JPu'X$GNȌ ; )T厖1=)`B>ڦ m"d7ahL6' Y^)m? :!tg ɘl]r,~$ UmD%;-^@Z:4S)Y!]0fnv~ŠLQcT $EՐfZwe.IiDKRj2wՅ Y^4sRL٦+ GmqT\&PH/Povn:BΉ-bitnxL!mSIs=tHס35~sE) ]H<) !-W#4\S_xR;k yYƤ`/|5@Xj6EIUFcRӊgő,$u*J#p416L [%l' '3]ؙ[J.-g ݂OATsEMFB2 7?rADH2%, 52>6}C8mP,WW?UȓMr&,>=΅2- ad}:!Bm~<ώg;:vIb=fcquہԺ8{#`C3CfN~tG#ܚ0~Z[M<Z-Hǯ<2~:D-Z`Zwba~nj8$d]Y8Rhe WȌ/ eD>F JG Kl^O#Uw0"ȳ*GT ='A9nlƜevu"H EA^o$3כ itƙAL$>ׂ ;Ir16o"d{H͟C)! U4Y}gZ{ή=g@(cjcl5OD7_nS;,5w{ci7a侬 Yn[H}MfGs|]8CV,n$0Dȥ.#RgM+4Fy@h y-"X |%2k[=.yٱPccnuݝʣ͵߰ۿ$,sKd HF 4K9K_XJ݃R_̭BsVJYctt0N@jp&"<9-4WpG%{5(G~Ϛicl:f hyOkdHs m ;S%eg<)b51 ;=qc>Cr`|7!J}]Ðnz (RSL[!$Ka,\216 u8JC$Q8_dI[>T!r!˷Q`6M6!ɻHB3W{"C}#^@BZU!#XIȋ!Đ5EHbh"\J4163% KhLTHST,0ןF)CSjgvw$\ڬ|_ѩ&dC&61=Ӟ݃q:)GWb%oECΥm".J5Ҥ_ >:njEaĐ:a]5yAc Bz^8 ٙHf %/z)CϚ \vʍ[7?9<\I!_>R^L|H%h-<7N/nJƍgq-&/k{? )KK ,dD"X).S;Hx5=E6N2n)-oHN\Ӽ^3{ ,!<{k~3|mu+Gbs n"o^lpMHK;vu΋ϓ28gߵMpk rʽg/f];2צqc9 ="HRT!I^hD5KJ{-cۯ1bzfB$ľ,k@zxDn\bՃ)KY_}G-2.ϟn/vVXObϑM!Y0|~3lk1HߟWbRR__ZX yf: i U鰱C>>-ؐ9ҿ+n:vZջ9*CFWm3`.[U-z=ǬnH+r ٱ@1ZCăx%2᳗mIc}91ˑ$rrF{B@N^TE s cbQdqm:تB\W N$J! eZA@eO9g^%(\\&R.[y jv@eLfx !b|$cQHj`G4kL3N)o G@Xx7lG16gXdsr֠Q#G `YKjԤI*ECf ʥ,D)(B,.9 ^u#fK9_ho7T36d}&.sKr! 3\|ir+G"$6yF=RA߽aAzwY"`VA4 r[:X\_dmD&zA*d,XgMѴ\:M[fsz@U/֨L./:\aJ9+;<~ޡeߔe-.U-?t/l @&RHWbr ̭}1E=BnE9=v" !BBȂ(Hc0m5$^;sHL;:ɐxs4 .ZD i4k]2ٖF#lc3u d_IlU13;d*HE3䥃ܮې%2cv=I=/clCw?x'Ǩ%oGc !@|;ҏTPTQ$EѠ*n.@HҤy*$3Uy.07_d$  1ժ HJH ~\bs2edv-Yr5S$i! !~!5Yϓ5{&AM=ȆO0Q![&O^17D:ib$rP~\?$~= |/(z6 GʺY`@Ү@HEeEКOib;K,}Ypwo3 @Rom~2*e  [ҿe-z )IxUHJ6ԔzB&O}9/7dAMUv ̙+g?0N\g 7HLdpأbHg@zRBf[T&nb7yق| $mkf5@8$Tj )a:ؽl`7@!-BM<2a-Ngk30 'Jn2ɓ4cW E1Ay N9$+f$4T^Q|`Qrr W77W $3gSHz#)P-%+V,W4_*\" U!7!ݳ*RlBk μIJy.Wu]ד.[! i!-~yK47q:W'_DC+Ѹޡb>?<ۡ*Q@ɐuH9~!`<|HL8%pcWxB?ţ}-xȰB:vtH Ce~ӣ 2drtPHؠ$^6B9kMh 6Ebtt@z$x^$AQ ni@i$$H{h5ض<_vڡvڜ#o2UhѸbZ$ѥdmm@nT%){;{k'/J!Q@.\ổG=qQ?$Qm;,v.{_+HےTHl*1EkýM}H~[.: S?:?~aԴ[(\lOIKNx~UB82qԝ2ə+!kOKf@ A-SW Alr Xkm'zu/[WYcY@w6 |r @BzfMH13ϵttpRY%3>,#<Bg~ٛV֝ytkߪz}aAn2l O޼~rfYqi5^%|) qut"B^B!oB{x̽$6?H;on|9&:ϙ6isK@oE7)[[` sLq#ysKS|ȭM  X{B 3bs S+ EN;}i7RȲ"i!s[čW24mc(`e@!55_RrN|- 1l6HohOљUYAVJI=|x# y cY`'';1]&+5ĕB aNF)S˪ʓZ mXdAqxmTg`H~mIb2W܄JS|F@3(C I,TCB H6mFi;Е$r -<H'!J`SI5F )[(T5cidn.(01$EY xa,B%l IR-ԛa' "]h!1[圜f9zA m-4@R!UDt6iUi HE Ĥ*h CgU~_@!cQB "P)>Z[T2J3%䥚:銄%֔P0'_ |Y"rr18VZ[)8Y e/Z|:$"|Nqdw1 ϑw[\ڛ؎il6. |$"Hrh(8!@TB!YP˓k.% 9yXq 㐒|594VHYE9NiX ‡_'ZRBSҢIZ pywHSA {UP \|-ؙ3޶@uKw v!oRH/ڼH+[DzmqjduϜv{EA~.1<-dEMBZIV G_Y噫ɒUid8 )-2@B Y5kvaNe~6хI 4|CNLki}Cf  ~AF )Z壅y.SlɴeH -d [ԜR X͕,W1~B> `e4 rYR)]!L4f݆4qd3=<&`/G H} A>f| @қ/{ )|H8$!љY}S羅 @`RN5_Vmy߆U TT?Z)!s,Mb& ݢH 9r-`RHAsՁ7 $"X!R!-mvST^M4= ͗ϳgA01SP\SpOAECఏ}IcBL&d(NZxbj0A8=$Ѽ8dE G2R5 Ka>.a51Joh!ir 5`ĹDd $xgh)d'RSFY^t1:ϯiF ϻ] 79~%a$9Ҡ qq~˓6xːtkH5eo|QRt9K@&!CҚX/IjFQai*ȴ2)dZRִ<ΗВKuD 9LÐ5lN\EV^67"OfJH23/ſȢ[CF@n⻳,*A Ie>y%CI!KkP?VNU!5VodEZHZnECo^/* i͗WKOUs=OR[v!ەw+)2'A9 ؘ !Bv % 筗MBž /(|䵄 6!8M\J^eLCF$B5dwu]^JsW?OYd8-? %o_ =AQ/4R2HePB%|2,K[ SBVJ YR<'AP3U`^6ϫ $OBB~ 3@HfVH ֑5J!}q20LHc"4dD)dh#p2VnN54H4 2Z *o 9`)̗WqMH ցQBf0d@ǂPFhtO O 8"2ȧ?$&TRgP\%I<"8iD $ޢ91I6ٞ16lqfIR)-.QJ!ZW0c.$tAm /tSNC,$6_^-KR4y<:r)}jl8^22 ^w |Յ 1p6l O9"< C6lJ NS(p5 YP>9C יd}D)d)ݯ:r[x]MaaH).RC/$9 t;\H!XI!Z9V5bY2 5l b?&EȈcø-9 DmԅX. Rs QZJMQ2xZ@dbH 2ϳILks4USh"/ڠh@xʏ-&ZH! g_3JȎ2j d5"g`٩s?ʕ% crUIb>[1 goˑBƔ,ԙ$2yA7"LkgjElQzCZH;)`'8WTcCr18R+r /<(!űUqZu&ZHF i*\ ➹bČ0J"%:i 5!C[橲g[ komU9o|y*薼--%Ǧi!U (Ie֙'ub )goFBނA>Cމ"6bbyBMȏ^J & 0H{+E*|X#H ZU\md& C '׆;'r)~s(!I?|Z RPCWPcB,?V:̅ $J P yq?M@U)T>ȗti%3)dzJHN 98}yr?ŌB |6^(Rvy5Rd*mƄVҍΖk/hc#ɸm?q!ԫp/b}$d "7Kb"C2xd3d!Hr-YJVr03LH3σ_<"CI!^RXJ'۩Oh20>!-`n lpC ]{7 gF/!  &p $ͤ佲%`qLχ_`#`ՔC(BX,  pvWa 54ϵVAU(gV^G~d!َ y;dhA#(!;2R&Rȯ!9~g((1b '%YށT/s!AC 9l~^EE,a46f&kCWr3r)~Sv0ve{uDh0dDVSh![ %4Aw 7HRx8!8AOss +~}?~0xHo d$)ᗩ-O d6Rȟ#{ 'BArr0E!reVWo!!K Z֢EΜW&c"١I`MVBZ@ޗ~g+ 2I%f>(db4#=qBZVJTZe1DHi#ҽsv$rF,U})pN5}-(A!'I!B~hW0IoU ȳo*   ɐ'C,AIWrXcl)* -GHWswDz ۧ$v~6P@K$@΁d*u.cHQi_·lEIZ^61$̧[H!YH!׻Zd.B* o-jݩ@~}S rc[&drcO!Z™APK&:[$$MV QxM yH9E٪H8r3NH76BoM0HeGARO 0c^ jW.32&j )4F JE=:pSo:Vᯐ: 7L9|0;'r8sra!7L=ǁ7gZ? CldAjϢ ""YP!Cl뇢 iy &4˒F`ҝ &!:]Q"[̏`eAb1ZәҢTZȴμʇ@]o#a@ 9Ǝ C^2P H DŽΫo8J`c]np vh2mV≭C YH!'Jx%53PȀY^1PHV[ڄ9 ɏƳ%{5\`~Ԗ dkO2U$r/GThRJ޹~ވgբ$C݇d' *7>oCn.n2THY%ܕr?OYD ә4?scnJHC(QZDQ~ WH%/21<3,Ȉ,CBN?2c.KT\ $" CIn_{~WOE%{N?lkۧ/r,{4[,Ae;G^=d@ %5+'޴@ R͘ywgNȵ 'jS;Ϧ Y[UM.ƑB\sp2ӱYq0SB&s ,aHReݘN9|n *{~tR`5NpnEPΊ H 4z{[FBBn!Ihx~2<ș32;_<9nD̏R9r{YWO߀l>f\r H!MAD&W0ye r77$,,ďY=kmᖾM(wϗTxq)HQz, nG2kmײv}*eCZ Ҍ;|u|ڧAiw5GZm^$du@޾flT*H] #I!%36Q2LMn.; i +PISuM< Vr_4GhɯCfy?G߹V+_fnS& =w%ve#w?~ߥjN7_]v] B䓻rWcz*AL!]#;wђ#ˆ_'94:sܙ#& *x3&nZ2m=iر#,aXwuH[}AܖmF!A/_Il2(A(p*t sp,*i9tֽ'Fcv95HխFLAylK~g\O)#Sl)~[aűޮ1,R@~t@tHq=,o5}%r=rdSbf|u(IKD^IK׌+CɫxU퉻]sZ{)ή`;=ELl2̼ % a6Ɯv8"#qmUg5<=nC5wD: r;1'fV?Dc ;L#B/v 2K95?b13Ge̳}Qk_+gN]ܫ*oEf.%%x[>] Z)J,ͯ`zQBZ%O\ǃ%m1H|fWD\PGPd -!> &2n^/ڸ=lV)/cqۢa h/\i-u_- H&K7to+vY2׀̪NUC(|5sk)"pHfb2V"ZAP@a26!fmcICt/_:RK|K1˵6Usy<5(Lf2rvJOlVD&^4SB>kfJwқO5тxǍ\AB@H \1.c]H!:2B;B&!jN$fH/kD{2g I8fq TK,Pjm+oSUH!+Bj_V]H iXu4(- ɤ ~. |?tp+pfK4:+s}UEud}>< at鱸E+;ZrKkՒҲU@@V$=Ii SA-(k/^$yI,y2W' `=O u Xs`B]p ߀f܈Ya yR$t&aSg0싡`q}!!**A=(ߙx :Ux377dw΅4")ҶdzFf,.v[-1&[T2B>d?pH$gCC?΄Gv#Z15vS!q.IXQ1j16LM gcV5|{|ྩ8v? v=9  jNÀt\sׅ.C+p\/9kp7&-|EV!@~JA]J𦲀f2VwT)\͊LlY!$n\{)hLicBS#T2e |]uM ,& J yEJ'$TAW@h2D/lcc}'Ъ ~@T\ȋ o #%d. 6: EROʂ s]ػ Oo$v7 Ȣl?7(u ˀ]bUe#70oڋNVBs2NS^6K6ԫv5BےBH,٭'E|)n0M? /O 5?j^˻V*"<$WhC Ol|bHm#M ɑ[p̾:? pE nE*P7̹S+ VDSj Tg%J4$\k' Ϩb "_ZaL5OcAn\&$):=܀ܦu$fC:j̘.^p+bHTC~Ahrk  jvR3!H2措,.7ىkUt\3S+tRCajFu8i9˺>}̬045Gi!9âʖR* l*N.,SlU_Ѡ;1dZQNu"o?!˗z$'͌v!bo]y > !=Qo .cuA 0 Q17/ ^b4'tGt6 N䚘da`N=,}yE/ ֐QB#+|3E@ 2V6}ɊqBgӤGb)ϻkc%K7d -d눁tSA~qWJ?ûOв\b:wrZ& ڐI3357< ,feHRQoŇԊЉ?AUpED-D~1~HLqϱ˃_"nubW| ZHN@B dYSYU֘Lqc*2~ O/14xqwE]Bk6#dԠ");-1uM^qEB 6fQco 6HC 'C16L ZHK-"N@[<>Cfl;)F]7wK6pKUFqIZR d7icP#[zL2_Ŵ/yI|"Pp(.^=ݓs ]jjԔEl\ERN eEaJWrY3 #i!sh2B6쵂olA7v,){ Zw?l9cm+Cr9?ӶU' 3վ_ѭ\N?R|o=ćDS}d?%сi~w!OÌ{B6I/t?p[DxU<6G{c0/r9sDtja~J&y|J h@50[lt | R|wi9xu}I!/e0u*"8٤Mf΂6 ˱ i.$i1W/EQR=T$՛/G&sSX$ "Z+eaҖi*OVd#)2w?6%XI ٬^̶2܈t UÐ|@Ԇ:CucOKzjp^K7F8}`ג]^_ $#%VBir .B-D?0ș2}ҞI )CJd}w +$0*`Df|[D7ȶe5g2Ho38#ݓŽ3b[ IsEl0u_S8;q, l 7qҮ]HXTbZUw"ɶIL]a>$"I1 vd@I>{c,a!4 VA' $FRXdzIWBf,?! 8rDjDoxYL_ U =)I zbwYgʌv9Lf,!>Hi/>W i_? IyN+X|t?#z;` %C2 nN'^5:琬nǂ %hCRR犁ːK,A4 )}_ Ҡ"ٛ"۞*q@Uӈ[Fg U '},:I>h$ز0h#eS Wk;)P#%rBn9;|7%WyHƂȂaQXOg"9ɏ)($sHV: f  $yS{RUrr8jbK/^n+~O/W?/h xc ?Mn`djRbTU| NHN%Wp$ƽ~_ZAɭFd=U^s81Qp HS% jt5gK \wS-$ -SAxa҂ 6TS2&ޠi(2==,gں;W;oMxëw~מ#=gYd֏.]<}x1<9^uӗ_Gѕsg۹tB^φMۏ>@:kۖ>ҧݔE+VqkoڴU 2+ں,oTxpr<`z3yF> u7#g^dxN<A*O[ $bɲ^x]HC'[5t}Bݭ'N޹iNަ n4rGN_|:vnݲyc:~ڢe^SE7ݺyݪ?~Q,Yj+<[SE6DGASUAW$ZHZH+)$G HkoغC 5-+Z,H柴:01SYW YrE`31dr 7NV)%+ WZR%[cwfߚ`&彀syk;X(+ӡ#KWٶΰ,/ sz[ ]~viɈVsB sj:.E[j(}fZǛW[;ؗL*wsfN*.rU6{7(fn|RPS8Xׅ)H<67J>2i%_([varz"EܼJ5k4_4-}GdY_-QxrXHz˳j^(ew=+MɌ?_ͅ|T2 @r^my˼>=qBznM2_͂i9bB72VHՉWaA(_;9R}),XdTɹiJOxO14SIJWG՜iS0$+<>o;(R%2}A $l^-+_y_!3PrVM\ؒn/i 6&/P5tOJ@r.àwԢ>b?,o7ro@*sttkLy` 7-Jk[ s@Mno0BEuYF j3z'sʵp5T/%J'LzȳX!-?|v)Ώ6bӴB/Fm H1n䛝 4QL">˸G#VgkCCӒACͫsyhY d2̢瀁zIt dʍ9~Йo`&)ܘ}Cd֧y-6~mYGP_YYpaur9 ҫHȿl.{`.lf1PHƂJsPqGz߾VBPT m[@ .ELdu +M5|$G @5#pDMHFXt> f`,Gj=C150H\|琙l_^ ~ʺ4ct8"8, HВ?O[ }jpFÄ 1L(!`wd $[0".igjLQ-d!hE ]bGtCb(#brҚhÃ'+ܓw:|2|7;b'yN 지vC"Pf PoBF {67Bz *6\q,I!CH Womk~=cEy5@|@{?#I+w9u'@hWRM$@D;#@zRJN)\fnf5DHÚ2BH@2yOd-$r wyBHKGydd"s^U&lC)~(S*]m#|{BFڻd;LOߟa[>Bz , A$ˋZEI$ ʄH#eQ 1dqɈ9$eSCKtoyc@?~LI(UF9] !6+TAMדkC!M廌8uꄡmKV ]G}/Ck_*°QH|7YA/}gޚH^B@^֬Ey;y Yߓe_ŀs3`9d߃ ;GTc9:C^Nr~?m*@?0m_y?P!٧ndnd[6 ΁$׉"9RHb_ڝQZ}1|`=ɾrٝCR-r f"iF,2CdyV u0S)!E7Bs@?7;IգG[aQgpBXHE9'J"$^fy qwqB*Q#e zzXtI?|/1$Yf)6/':Gwy}-4=CrIsL ${׍ &QK]naHwDŊ6Ob;vj.>=x>?HwEEs BZm[hhoHŊ;,1/W4=;M:{e#5j.9 $kt+w*N)&fR"\kQE}lZ5/"'@rLy_p#0Nf+ E-P@? )[ﯰH/!]] m{Ԩ)R)\%ju4Oţ $_&v[]O(Uh!u-oھ}ӴM3z]-ۨE:7bmVդߢ+v˦t?}M1~ӿm|$m}7qⷃŨ?͜> (LV/;<}l*Y?u"L旿q?xU񐾹3P%{]Ǽ8vd3铧a_w:{ؚUo0d7.0H6zQgk3bo<@iF<Qj(ӡa3Miq!OBJG/X/{ 2W fk$d~WJ6ܵc/k-*ٰsC\W(+Cn܃ 9zhE]E 8ېx2OWGCQk?b uVgDrRS~KF32RYsʴ̉ 'Jwi]}=1fJ>@ڌFBGKaXTA@~TR!qPHkޯWqQ]Y.jߐ}<6qZ1:A(HS>!oL\y|m剕qѽ׽XtsFd[8C/MB0bY)y{uaDmTis4"F`A䮨ĻEI)AM+3,2(w?J*uxLLb3i+KA!+ HN$,@8>.+lBV (=jd۶sB&yr9v,%$?P."=>!9:KD䨭J/-s-r@FAjCXw'͆.KBjO l62u2.X!;KW]gp㚴PMd3`yWB6@aG%́]y6%Os՜W9᳨9,gt>Wʇ쇴yPB4 E7>1 ]46.Cڄ㶨rn\ԉA%C%ڨa {`Hf54g~0DZ^&gx2IJ]~ynA^BUc`a/TA"V[/hCuPak;[-XD`~W[ׇ )䃖" 9jȹId+9):RAx:AE  dZ4L-͈-Q\i/вY-}J& *bBqM}ɿB.|=`SU2<$9B_Rޚo98Т Hae6RȍaH %MptIKΨOrJ vv8| %. } o6wHQ6^nc$|@2 Q@"1ރ P-$$r䔨S2j4iֈ*TM{ӈNe!Hu6arY:Pxtfq!V/I=0z%pB“C/ ʳGqeTQc!b9]ov =<$aE$FrX<3;!k -9Cbj=Q`hE- 0L :5 Bb,z]ǏҢ}Ő&!υYtY*c¨wKԈ!]ͽՍ /"IlQT%poA礰Wq-Ke\#<שu/_*|n%3H,uo:0-Ʋ1XHq'gJWRӟ > MT+o`]B i␨Q65)W@#~A'e~?+HKdTSS/۽S-o^RJtVk];4m{kKgآ,Z{ /%tE{Ge -pOTJP'!\ K(54H׏`hYԍcv&~Hyx Jhg!4+uH됸Irm7n[dVAȖ$<$"J"IQBʢϔpi?+ ͫ!gRa=ɥ#NXp7[ +&v7uEy.W썄ӽw9RxzjbJN#_+J?aeHt u=٪?).9) 4x`v/ ֞?BZ8$rʔ>nm~ИY^۫!_IH E4_H!ӒEP^%ԬF.G?f6:FV~1{|yUԢ\3Ɩ1!}7ǂ~?V&` qbe" »{M>JR4Mӊ\y !@=!SA UtSǩcWFLL*MPݿw`18""݀Tx)fٓ_ `~_R}>Nd+L$5瓉pj\ ^O:7Hօʪxe*7\YhxhO.OC2̸hdY=%7Vo苅Lδ '\e +ץ}Zn YsV ȵRdHDiXDP ti)a}H-*B~4Ȋ雗͖x g@##/5HTEWso)D;:RT lڗk+S'Нa-n )VgC2 [D;͹ٰ$HE'|I!OҚǶtYA7X:'0$EBGI,䈠 p{P ,X#,ӟ"lHL%NpRT,咄EV K΂"xŦq| ~ *.2P/ y0Av0S"F*NQ>Z#{T&΂VC  >$ #rXِ&V3j| +hȻ9I!OB#D y"`|<u}Y H]C)[dVYGQԑ7hx҉/J WInyh6ux+[+ )ȟ֢ CrmZ , TEq-_(=ޯnB2HqTB %H<U KC۾-lH_ [D>< g}вiQ.JIR DyDXν?[ Usqš;m蠉Jx-H!%~QF"\L 9D 9WZ94 }ef8$bӺ)#9C-HI⼝P197Z%Nql Eg|Aօh['wNQN> o%bp}Ӵן8Q\}dJ`{ =J6 pZai0LfgCZ0@O?-7)QZBYsGěG>֒7UC ,HƐ֫SIH$aQ4AxiHb{b#sV2|A:LzkP @US =0Ѩi{&`8i~&mCO1ZA,A -=,B-1v7[ڜݓIS"8Iln@24SĈrϦ9+SkRwZFOMg,3H8~hZH Su#=tO:A l /;~uE`K:pf|xR:4Y>"c%΅rEZ4?c"B]oq#p.j~J к)awIln@ʒ98._ 8YEдѧ(I`,Hwʱu X!]gI\2$W(n6aZy(d}F iӱcS 7<~(RpzH O/!|lZW -UP9j JX|"ͼ[Ҿit쥧=Nbd/;feȒ?(EcQ++B5Iqxnۅ7mqeh _+ei c֎1jƇR4/V};byۻbD3yEi ߬a/3r,'qmRk,[N$GjJHxJH3-$cJR7/º4{9dB+ \:+-E2HDca4i1OƁi*wx&⺱x]/V .<(7~Bv!%|^ E - ޓÙs&n[K/NKgXXTJU+H!Yɦ9'/: dOSG{hnfD{IeĐB!D yM+aI&ak:a4US%M2\R})! #5_3TDzTd,t_X"ׄ]aNʭ>62H8If2yQ*(pMDv5{UZZ}֔}%1uSyl箥4:"3r:Jz,;F yB yDW^FTᶖP))؝T+P枪[Ԑ!Uq7-4JJ !M]Ձ@GRgZ<ulSXVU qV<̟RDcl1Xq1SIh;~NLJD-9).(szW|VLWo ]ڲȄ|'i#=ͣaBr^Mvp4$8Ҍ U,R"-duLYwu I5)7C \M4W!4FQ;dʶ3esZpx zHL]jޓճgo\='NZ/ؼs׾l]tѶgNJ;&[+o5 & {2Za^9ph(eH!ӛZԚ%ʘ.?+ԨDq %w2dYoo7i󙔩:Z-r-L}h? IuxR].Hz:.nl%gV|hq yV#㻲(dR\z~bԫp$4NeO8 LjY$ 6eT\yi7FamnyM{Kd(ei!(!]ː^-gOcזFm"v:]IM--W׊-*l ialM^Aj/w:"lQ 8Hƨ^`Y@ %9 jR!Y-IF!/ O*I Qy?]$Wb^G$Wuyٷ"N]dHIr< &ƌ{z164I#-YYq$*eLhMհw}\I&=d~O? YdE+!SKׇF6 ?c~q'I!e !eUIH*wRIuBN)pw/iRDCNCd_>͈d Rߎ|ix>ܑ.MRf.: it()´,B y[N?ɸ: Msq4H!wA)6? @2FɔH:X0dmZ8>wcÔ]%!'PgH~yt75Hƛ[ٴ+gƛ_w Ai9J^6tŵo7)eڅL]EBHIHeI-!KAʊ8Dqf #߇&*E G,yXLq\D{lvr>pKH@BǙ_i@.AFȐhӯ#3H}-r,~s׉g'},7pȊkAod#(!Ӷi2}Eѐ; "i!fHr;xFm 8*w_%y =9_gQB|H2-}Dm #]Ҝ*4:ξ9,S譵CsaF YeR@PȒ% !2[^ 9ȂD6|JH ǣ{[4NJ$5M 8bǵ7p99&U\t <@D@jB ^nr-J}d̫nFfލP@d<s76 },3@ E$&bqh~F}iIP*$K$rSW:9MA:А 6?X&fXK܂nmnK4:Y!xoU-Y=Í ɚo3n,f1,IQzja H[ fKy'DDow;BTդQTelrzB!0ŃQJw!U&n=onu\"lmbɕhZ~H֛qTڢÁs˃&ݝs W5jWw( p&롶baȶoOE,ٻP2z16)%^t! Y=9#j"8 ہ" UsuXԨ~b0k_# ah56[4눃MnF9]OϭC#zUGVF?mO0ݾu86y$N%."18l_OiWkKj ]wM3(t9 J8ll+hǐt?0NTMFږ}BC`V{zDOE]n ts,۷(⯔xҲ'kN&&uD;n:$#bGkInGPy^ Bah! NI4C'3JcׄTUFd 2uP&9r*i 'U!m4;ZF-HKDFܩ&-95;8kdm8$"0YS)YCUD" ~ͱn$#.CZdE<6M0|f/݂p7S+8:Y° *Vh#f6Ǔy?9FH!U^v2 9<%tB ieHqCjz.Hd&ԴEH-(vDx UbB] 17]0m ԃli4e/)y?=QaNj]JBp{bN[:lV4N"G_QSHO8LMB@VV3b#l76Χ$*O - j\ƆȯEsF",H)NFYqVj;JGj2e$~_$⺕YPVīo+M>).~!gSiaFT51?:hFa&56$rLҲt_g_~,a{[- x.3 i9W!;P־933DHR'͡Wz I jQ2zp`եK~%ϴͅH_Gju4^?!QSZӐ~̹|TSݺSoo#[)ReYԧmޟ8%L\^YGӐIe;,U ~:ǟ34]nIquL6vxmm i5h9e!!</hcloC?Pˣ8!Cp 3~}AѱxG5I%q da( }'2 4Ex,:3ޔV+BWČk]dw++4MH۱-Fɔq@F#(9g^A }Dv2e`5𐞤plطߘgH[:R*Bn=X{ˈHH2+KıXQ|i{Ac#`%YBSU9MF?ؑmɩT v!˻'G)wr |}4%okAlbG!L5[PdPlЎo֒\2Â6Te_Տ鿁TEi KفI_"LE<^<͔ógH ![(mMpH"VܫzS|;wHJiH NaOcV2$/\Pdw~LEA̝!=1&H&1.Hc*F# {L vj5jz]} >$ )gN҇r8.<ΰ ؖW9C֦7rtF6BHuƋRe}i>r G gʱ2'@u*VN Q -3"A[׶L藢sWc`VBI(c#H+H36pAR⤢@=$YrA_'s`dH_>C.t/k>t<,ڋ.U׷+<[]"MI>K;e~Ȋ4gl.B8$p 2gHI!YާI`\nITCc@K +iANKyZo޶LJĪ cEճ^ ##2SeȉW{Ɯ\0aŘv*Z%.tJ}I*Z c#MYdB#֕6Ta&Mj뗿 U+\ng~rx*Đ . 5i5k}?3g]]jo+DO%/B9C`Lh嚘EN+&x֛D1X_2)dae^ 1 dG_,lI8D!]5l;-R3%RaRyuYI/Ze i3!W #VI7mBKA6KGi=AUhb |3K"(T9uP N`s$V )._->NNârrs!|l v`cǷ%NZUFq+6v49׵Uǜٯv[ͬ.:Ϻw#:dFn!妙\q.ݮ4ץ@QA}⿠tݑT1_.g_ƥ&v@nSRR٣Ԛ^\PZ֧@Y'X&J)FӐ~'4k>v"UU@ Y=)dXRڵ4E@P=af GM"!HZ_F I< et1o >8$ct[#H+l۫g[.K.^qGȞg_ㅂ,~wV16LCA@^7ӟTɣ2g=T5ZV#C2+!;▛u6(^;&=|%]M`|l:4#g(3ijRҧ Zd}enj.QAz|rPK a~eWo8Oq51ҬWuHq6!bmS3Hbʗ85fRt{*m?4Uac QBdHZzk|^+(8-QZHZ5lsU´@FYY՝|.'ίKƤ,N) Mqt -R3^>Sc FK^N1y('o~&M [O>̪ܦy=ʰ)%Fb'͢Uͼ꬜u`+{~Zzdՠ /#!fyvVd%7|?2uPUF~.wOPg"jM /)dEh-SŌ@m2eotd)#BZVg%V{)EajI<'!{l3vMj] Ź-ęn>Y 7~~2rm/DCinaEpĈǰy~,(]r^$ʔ [9*iعzʵڍ:)< zڄ/k[R'^ ihqҼR?νtyÂ-oV~ݖƚnzR e0@IdWZ曺К߉{:2u˗OQ6Up-"!Z^QU$/ԙCSz2tndd$|$k\"p&yAoZd'p˰HP ФnF`(pIQSZ_y0`b=hW(eWC=ƦpV7oLP6#7I__ I6pH:=U,~uűg419Ì χXݨHfDsq0nkF?%qp٬aȭH Y2N>WGI=Zאi/c y Ki$xVEڣtR1VkWPS$r\/P(~sfXcVW~a3c6~ڻ '>Lzh SVҜcx:?2q_[HдbT_Iƀʢdx/VS| `܈m\GQa ,:0v<Åby#0AO>]ZRoG\Zi{r jRۑF,1Ŗ2X7!esOr0ñIS ԕ3sVsGP$mMqYnw#g'2|> `g4hAHߎ3|+ӂї_(C,* QWF:Iв X>$@u Yz"'iJ[w (#ӣ` ZS&:ڥ縈U܎)$f(wNÏ^BS5Q2>!ЖQXIPȲ9˜OH[/(RUhUe3/_&l/$$m m{0Jd Iil@ydAR_. BLd[ 0QеrC O=sSƁ4oe9`|:S^B9 珏v_Rޡ?i_N;'Z(Uwatlx%"2D ; ܧr5:U^2ѝWxxfB:6+lWTڹww@Gp6̎Hlȍ?|e7ijo:"Ba{u乞@+AAҺ;v0AzaC@4k`EC KS&MHвΑ3`GRe7ȽPכpZ+DnAO̦ah]A2 ,zS n_;/g6{qiN2.>8**H=uR펟Gn5ÍR.GZ㭃g-E<`}#! Lx?k̢oV8~s@q{~0 M@,,W! \F8+Hbtz2 9:@qߋl?$`a0UtGp ۍk$Ac)/D?O#YJkk~HǶB_^Z1 RT7ד|kpvQ&ZRlvy@Z[ӓFQ#COK:\=$.1Pb.!Hfb+ˍ%,opnc= &!K5;lb=\1Cd?5_yȌ9w`~Uv)+BɅGd *a޿QS:\-f_L#bom 1,@H{>:RӅrhj⚚I`[} 4gX 2XhvRDH>R(?WIqcbܧ!z8M<@#}yI@5bxW)Y82ai Fte%iӲ:n٧綿ʂ!) %NHCޑ("eA;?:@` )Fj@w_'! jTHpz6IFp! ,]IAo]E._sV_zz y6.ݷ|=xw~?Zqv-9v̫ h(ܻ1h{'ZȲx|VPnmy=i[`[䐺\ʕ:j7 ߗzʑr0|Zo|[X:@=KKđ}1NAc0 Z2k*j,,}Ah^&/ M}vb7KHΙ2 쿼.=k8 c#ֺK>H|\Z;Wvy[@m&Az sR!Q^DI?.>"GQ=cd`c49 ~[*is ̣Y3ˊyO3/O5rgk*}QTn1Y䷪Fx8f됴"0Y| 4 DjI~>~(5c0esؘ/ HԨn~XBҲn;!,3qHھ-S8E?ц(f(b#fA {E ڏØ :O'U@@Ԗߖ1k~HKBDf7;"#Gn#,Wq r2p\Q 9h~#4%(Cn4zY^릜 6đ2E2Kr2抨f!`tֵ9GTD)s]SoQA]5݁OSXIH$Yj7x9iL j4}%PXMPiQ WنOsGh:gb-lc. -5`)C#%z57( l QC* 4HGHy/\#="ya 2i; =~d2pۿHuM~"hE<`ܰGhNUV٨u}̖!Ҙj&5(n7S;dQU~@w険\FYdν˗1\XC3zݓ ?2B!8,\s8w+PO9<)r)'k(w 0(9E@C \uS'T%ܔA$'t @ g 'QzMR#DlZj)fwM͖K9"w>]1dt#0:Eگ;V4 (^6$ v,mM :d d8n/"R4! 60nubPߓwraf.YR71=4= LB5ɦ8R!`tt8C>C"Y\?~p-H[bH Gr. ,){ø ̻T>,!@#,2ulahjP!Gjέ]D(r|# 3ߞb/pK;(EAtѓ7jk.WH(GU?[Y!d؟߷lĥU/bLR({ƨw n\rpfx 0<Xm<?u#/`~>C/7挶 EHOLoD?x$ /0[8ńPLX^vĞ?@d<R#˰ME) ضִQ:^ܠv*<6h~NĐ[z+ ?fu(yzArqד]5>Bƾ.tnƎ Bu]OS7ׇKӁzl>:c<[r?QW{/!KWwsZ>fk;Bj,#O$4gH룃XЭLP T7 D@׶[4 ;(!|8ӫAZw_RcCJ/ +' LvذtvT8)$@q/ŵC-k5_+(qBWAFdW q)/$]q$섏I9ȤQE&P5~ W5U_/-*222a»0l{˗^>NUaNxE e  bU!iD[R^o߀ȯsDF)bd|=PRUR Je tJb)eĄAiϴHGּaeo3 8ؘAIf3OQ7][\Mŵ=Y`z֠$Ƙ X鱭=1Qr˷߲z`Ta=d9ڡԒE{ζX:{2d*Z  2i{tĩ6`d5gWszY?ȃ n`e}1]MWW{TyJr iElĥ4iQQ#26 +ΩPI*"'e_TE2q%0@GWWAE vmS*/gO;\2iVmO+֭3,R+=/yұU MZ{ҹ=$-󵝱Љ݋XrlϱE/sX?YdWBLN9jf~Υ`9$@*']2y^Ԑ٪ `قɝS@,2/Eu$F4i(W -r߯=wQ@Z"S+4!h XHU/׿RKi-yX*!ꢝF YWC|-k.\u.\u,m:zWpټΣW_^ <S߶\ "Њt!(mUhf;T0) V#4cl`119$=֩!GEUC%tm'Yz#΁tk~Y;'!5m/ahr 9$-:1Ol I,N `;̽e ]s,:f]. ϕ!޺/É~r hm*qgxȃ\XHK)~{ yT 7ް;-6'c!ie_iEAlI<җyܿ]V氇8RD)fr7ٟ yW:IʍN|~S!׺YΣb3U0_@ ?]y؝.ry^I惗 #-:!Xn>W˹hhjȒŐlqَ,*n4m;1e ; c% G{PzvHy tEgYTPȪX(R%&=2;6aVK7Xye ǩ&&Qny<|]5#Dy+W %J~KFDk|&M6C7.Ҙb=!աt(&sRI+y+DUWTB,wӲG.z3zH*<;SH҅E 9] 3,u@NCb,baۨĐ)KvqG0Ÿ !_@brZ.B4{u厛e 蝽VxR`}P"[C=)4"Fqp-vWq8܀ʹ]oiv:n@^9 ZEg%f|l$_A!ʊO粞t50^c1{CZ0mOl)8N lR#Ybr i|R`Wx˗CU_qy r+.1d(d݌%!n1$CEqr%3-Z6߄rp8:G~ɔ5 S DibeKrSzC.*_j|`7J?̄@M1 ĐfZv+7җȵ^k2lDۭ3j Oz)jx2@C|f_W[s"O L« e C̑ߍZ)!nGZiO%J0l~+cj_>Ѭ?0?pC,xG8g Dqg&X3;DY@|µƒa- D6-Oi(O'H恑,`;j͇.Ʋgb䐰510LRj|\a1s  )[AMsU@Q*?58T^QiGEm?&<`NN%[ّw'oD%PJúT3Cخ!z6&ݲ!rp >羆 PhsN֛]IME$a1Hyvɻ|Őr"?XYH7+ߣ4NgHMsN Z6k$9$9$lA:0(mQ,^ AbH@~? pD( T# E%_)#Pi~ XSd=*/exИ{E(w9FH&t.H *v~&V-?n?խ5-m'NjːT_HΏ{[nZ-rHz.j|JOل)Κ3FV]Z6N>!akcħm$1dukpuw=8twܵ,[0B%Rj36茒c++ ܓ\vBӲ Ip3y" _ymL`ҲХo6!akcb=2fC}:Ma!6) 5hSLv4DķfO&!/rG ]"AY%Ѥ/ظ Tq?^i~&JDUl,%+[]psXzYJ8oddľݒPN0[UZ~\t[ߐCz%#GBײI5O&#đc$p*2-] LHJt0!akcG3( v>1$p4&S5\@HxcDi !r)-γ릩"I !2^9v 8.֣.xS<~[rrұ\N>g%*-bSKk*YXO}E+ UnVUkծEc FGFrS^#ֿG\@e_5,1E_ 9SaU>[##IQG7o2zԫZ IНC'jVu*Շ @‡ l HE{DRd un"E%l$ dC$v,{Ln%]&@Af GeJKɖU\.e,M$[`gy0ػ6Tڎ^OU?TW*L1uml)9yunѥ{<|4G|YqPk|wz%Ze>н(-ko5,(0$/>*(0M<%J] 9! ,6v\}*CqY$ÐOlju  IPHrQgl$q$qȢp`RQ3U9:;ؿPGKvJXѪ$=j>f bȸN_@Iqޛ@Vwj6doxϖPέN|y7(4 6@ORE?s5|Ser0⵼4ZoԲ@&Mz$cN22lQFD@Fp @J@iH+"/>*(M$w BYHfTW4-FFrEe )ԑi&Y'%A@Fҁi&Y]$A?:cF:24Br y5:ڲDo,үe?ص,sG$[绌ՊâM!v'Wmou`QG[kE L¦$4P9J%8y)y+aJ&~K|(H]:#q/i^7ʴBN ٙw=l 6k|`*xd3S7dvN~R2TcGd3bPi;ˇ h*F9$ΡO> &T9Аߵ%a@F&}ZWzYH6D4Մ]%[4 DstNA^&> yXٙög8oҙJRJ*koN?3yVJcl8JFGXdD*Ѽ ğU&⚳__V-b:59d>ȣGp@q*(ӎ7hT)$!wO&erБd@̾T?Ldo7򐄧=2 tu:هi`#׉CDR눆2 k~H#r͡6{jC\Oђmov*E 02{ ?'xĔ@ ٸ*J+@A%XXF (Vj%' aA.ҕaH@—F!KPcek3˙!T`J# 8Mݻ(6{s[&1u?|${o[UՍEm!3wko"Fv6l6\k Eϔ #9m075 MG ,z$2`&w; |$AwAت_ G$ʼ_jskp! ɪ/C-X^-xU^G@|#PVI&\ JVd$ bKdg?BK1$+r?6Hhu.̡{{[ڞG7e=3@ЊIK 8tmu}%`w)Xm^MfN5r.^>3G@w&E lAXRɌvc&WD kHjYةz|?!Tв'3\WH)A4IBFdRY>IU4qJp{|$aw-sd},>p"w뀹!#y@1~d]xthhcP_HZ4o"O`l[@),й{'=U4B-RZfgCQ/dnN`ѯVyB7#P~=d~#\6;A{+!$ ݌͇`@ Y:Kā9t @V]H%)Ҷk pDkuIP5o.C23H~ٰ)64kw"QEd`>6,r-`z <|$BrOz-+I 'R^94vuM:X;c(;'67C>#I0Cz7wXCX16> aUw;o/N~ۻy9~ A[+A #ԉ-I:Ow$#;Ol9'V3xg`MʚABqxkHC,8&q@ׂp'ߩwz~edCXu`Ò?r S^:KY h3&~ ԁ ۞'i, 6.Nf58z6%0DC򧻱̖SQtFHd>}ThڠMlހOxj )_X@J ОK+X>2wW!'R?GqIf44gm9_ !~q-8%a|G]~eW`[ΧK,kzاv':!߂K= dȺQ, ,lUU<Քxt>V{Jٹ;3w{.ʙIců"x*% v$ ;!ҟhJJتwf6n$|LA*ê$8ĘP%l"ia} ÁQ-W X Fmbo iWo;ALΘNvx=\ i? F9Ng?D}HȁJ+Jݚ8Y>IUTzQ( _q 16 *@sHS`$Gi-p:5@X` IHFɘF_IJlEGRq BSERGVɜu/<5`p "bʊr*YErÏyk ӆ+):ʫfD7]*5 QbmShW*5SM6gG+zXGdnF39'S;0t@G4RId k O,م[g:92X~~*R̈' N쀂BB=9*8plf'gex@}0+O|fq'ţg#̏mjF̦F|J)GB*pV8'9~SZL8@QPR%[8 .AAi^64p'rӮdC$V8A2FGU?Xx<~*tHQ"e?V19׼\8R0nR}ɖ[/d0[HF{V9[HC3~%~RoUSljUBDi S>(_EZGd$PQOQ8&B!`Ei)(p/#*F))h?xb% bS1@F@oKs F$A2N?.](Wz/`1EҠP@(H:Mze4W$=SWoƂM[/ micEֱzĖų4<)!U:؇+CTG#4=T*i%0[@' KXC3 jHq@T&8pJHY+I]4ک2[PE쵗k:W^@6LO޳jAwj߮}n|#ń8ete((}M-!H# 7w2F59f+T)NjjfYK1pv\hd*LWSa♀; n$Vk[cFFlOq#sIr?JtVl`.oFU6=ު=8bq. Vܑ3Lِȶ881&45vsj$ 8J_QP:;8l"F`VRdEr`_()*H@MOI̙XI2@g8;pQj A ?ኳ=HtQ(:wO;^  H,6aA)/*wpHS C`u?zr:۷I'ZXEÞةbDr-t@~QGբD΅'ihgMB"gdb Q1]Β1Ir(cY+XI{&X0PeX]clvvp c]p|@q:!$GI!cmjo)D67; a42Rס:hxt.vėQ-RGTu b>bxnN*7Sy[ MC:(=._ϐ h6i#|D"52XIo ##T 9PoPTBxUUi2PUڹ8[Ce/f>_hWh*kr*ьfh狗xU2+P7cw0qzapRGb1ΞZU{[KbNz9bW4IO;8_YZ2w:;B;xPdL*JXVU[Yݕ`/V2v&C r•a˰ivH9?j'oqWB6:jUbй.gjyD-bÀzpl$%g?~A3v>6yVI0WX&05J>I.2J2rFቕ3Ux`#0%d *rxOA7ۅj1MSa9MouRwhq 'pnP=N7Cy=o/1^+H>9^3{W-WO OkJ`_ZR6QTI'd<+_%vGe-: ÌmL`8dil$+`d?31x^j-}9'ɢ /_xb%>}pn gDjSԞ< {\7_ZQ4UHS$2HPЄD_-ъs؛.\:aw~l@9k q"VGoQ$%14YޮH5&S')YKYj">]йE_41߀J '8$,^閶!|'{Mb8Δ))a#c+IuG譁ۻ@M!x^)#T W} MuJpna9%O0$?3%| &<!\ӨȀ9>:P3}ift20J`*GWDr/B_r/?}@dLLtL̟?1b#)TCѕ]-Zy5_w `؄?V /30Rl$ G!^ʴ[N<RAFKNKOMV(LMU cyCŀ̬l&?h=FŽ|ӛ|Wox˛{a} |TnҔP}[l ]mmIBW^Px$Bnm2t[S޳ɨTgTleبӦM>}%dΞMoiӦޭnv0<-߽|=ѿmFȘZ2XhF6ڡ2D פ2rLK:_1鈆O,a~p3S~o!aο%?)7UUmY]/..VoxOJSQ%(EV2ҿlV!#h&e/xbUO*۲FgՆuq9)}eYCBr=Ī٨+NֵYj5kԍVgx 'YZ?%}ud¢Z!#oIĕ+@r/ѸKACڬ5Ά߿E ;u ڗw+BZMBnig`Gju]rFTq˶l۲iݪChÝl:JD'`eqtrEJ^l(UjBA6O^.B:J: MfݸNzișƝ .EJ!gR-V窫m+$Fr)([_s :luF_UgY^Υ{ڹ[Y* x M}eF9VFϞ 1wt>g^ vzjYk&w %@AbEZ1ȧR.YwUN ÷L;O1у'o\]VMwmK,Ю4(p5+'e@c㜿D\mHȗBig¨ [>o| &,1.W…<)~%$Sךw^6A\W:2 ۚU t*Eݝ}=?n쇟,-yIo}μٹ;oIXbxiVDX0F*^.oTysd\P*gE$d=՗Ԣ5]j*,=A֭j$+T3 Yݫ)f vW̠g!s ;u,~"'wI .^k2Вo.|l5nWY,n:x*.~/@ ?H]O(rOCSBUzM۞4f^X۷(WM O3f=$6Gw\y|}Q~+7߼>XY\C|e(ӎ灕TsY-pp#h}fT-&~ {UNUH Zu͓xkZy'eq~^ 6'YUR<% y ܽP ϱ[@ ƛ;DJWJT~57U)2nϋs-.}LUf}3EK3ڪl6PGk TL9΍L=c ^tݹVϨkuY4Vͦ,ֺ [}t6c8U趉Mh\*9ik?h&o._8M<,ƱU릷)yzLqt$z K:6/+x6XAbzea(< U{jlچ:9iq,CzŊT=t:$lNhm:ۢ.(0UTHh)+IKkK-Iy^ '9([L[MOkW#'֙ԚQmsX>>^.lJ=Ɩ.^^t9"MI\VNE8\A>1E,Y ()Mvu" Ak%m}0T9낝v.9tP|s^2Ƃttu* ~QxߞbiVi"ssj??'<>ՕNi6(klMә #\vb{|cngJ7]1.b$U/ΚZ^%'o-Yw/ZEՊy2%05{MVo۹ebyKofΰE5<=58e ug'3Ft7W^$G~{ȡO ON7BtLe\!R+U*S"ݒ3шu4|u-5sb ٺƛ2~oƪ^u28͜am9C/&PsXw_1$r,%T#%~Dvpשa{fE܍k.] 6m Ǵo'cvSw>w֩%S8>qS,ϊxDT'7y(ReM]+j4a\6qaO]W޼{kݻqԞ=E~OhX^zohFZwdhWƛΐ<9KS&H={JںyT䆚5AJ(M`!p}ho]鵴 xhXg4ޣARW2ًjʩR9Ph,)eNAQru4;'W78ř GO5GSaA"n( ݞN~@EaWqlږ,$)#oʛ< YR5b7p41)ˢܼ9T|\~&eЋұ cib<&K {(K X,^ai%ܟUΨ(`\/kG/[JK;1 76!fTW9v- KMQh5I,k232e)~vԮ:C ÈXp1gj@ h[q^6>zMm$1?tK*j~pN'Qb2VfG>h J3S?o a>D㜯xFz?T0oD=k%Z qTˀHYaN눺 E?+^VHJ׈W8vU1`-.<Q"鳆F.ӢE8Yp&tb"7cƸ\-)l7C)q÷,CŰd5AGH|,dSt'Eq;;(x^_,>3ݙSiEN u'k(!v=JփDlcɒqǼWEC jpҎ`?[,}zL̊j\[KM94V#Èl%#* ,%=>:6~hJKϸn̮/UO72H<%%b@+阘[+!}!Y:YXݥMMMʧ\iPRѠU2x+kbs}Ň٬ۢ<ĸKe$x4ku'{fH4|+UZ8cnUlM-OcѶ(#jƜi5?)w5%18q|DŽ8TSxFqV7BDaH@LL2iÂĖv3ĉdӧP9ru8 %I#l. Ez"nh\(_Xʵ8,)Qi''I(hʈ#ZQZ Sɩ!' wI) c)UdbƒV[Y&!L!ҳ5$ױ+`Ȑ@OaHT@:)]O2&2r2d; x0Pŧwo;\KԨ;N=zɅztݴ,\mӳaCuDa]a> ԳCCwLV,XCk6~*Ե%1bY|G:=d;M[/SFغIy}] Z[:(B!f[rVwM,bW5Za*c1vPŠg&w䵗ҋʴyqo]9u [;j'l8qm\Z[Q=,_/Oz7W9fW0/mĪXv#Q9XXlz&w4̵EĜfB!pRF%)m*(\AG斵n'`ZOx!-Ն{2q?PGbQZ!FXtKe,H1X((v2jNVɌ- `",\>mÔ\S]>bu.k Q@ al38czEW ܤК遑43u,#ȕ<ٗg$Ƽ|úIW S08aZ9Y{"FlKiMƮA5ˈv~| )x%? Mȅ`yz37 fXpvJ2D1BY@L+tߙ%j8VHVb8l07o )lU_I3#K:Ѯshv: (2Vb7^\=b|D3qq քv2pHƃR+2(z#cCg $+ q*%8 W|^Gge эƊ^fo#R_, +S{0E< y/HH?A ,1țjHPYw8ZkDN=`-T5c xbs'W kR;Fx$A׾Nzu_-o>;KгW1gݛϘ,cBr.%&UdnɌ|lrjdg2gc\N-&Mn4FueQ/0&A -S}n*|nUSMZoz\k5SwtNزʋd8ʦYt¸ N.A3=3!i@RE>)A|~8^㓢BxtQ%#t lJ霜~31wQEsk^$zQX 08QWFϬl.˞*#P k<™`x@7;qCg_Qe3ŖEr꯯1/T}z\pIa>g lK[299QFC Er:exkMi]yUD=PY=X&b:x ~C$ԯ.Zwa{.Mo0B&E O 2i#$;qXT[bm i窇PᬢB6pkQ}{j@TR~&9o`#92L-h-E:qOxڈO">sެ\`fY/V- ;(:9u Eʆ&ނhXA#1e ?-(f oܼ(YwS/8]n1!Ch| I>IliQV-iЊ `Zl.sx@@QƄ_ /j/Z53*b=frC3S(50=W=u1'YnQ`.@rW3m;@؈3m )@u*1Me2Pq ذ -$m;N6#bGǎ3} 8|ӝxH jj,lě GEEg"dž"55F_"1+:$TvHJchL;m٬䄻jt^k+8rڅ&>ػ}g[%c9LJ*5!;D*e"^}l]:q <6>+UlEQH5E4,|3.].H?M+ i7'/b!j[A01CWk۴(y{0;x>AW[O#u*C*]#cQ(G#U1;{<*卷$ϽGD׌۟C -@_rǠ6Dqo `4߂wtn;f67EL^6eLPͰJxDo1/2 }aR͇E**qUgvMGI 4AJp #C0Qpuu0bYZXcSqQ/:EdbF4ƨpP8-3h$22TՒ1px2[dgNS0&)\_j 87,P1zu$DŹ^=`ByL{l&V2j67j&?m6Q93ƚ(h8gּ:6dE&~s9Oa)/;uWt AF+?!nTաԇ7g-_rHz.8N߭&9Oul*;FƫOF#Og= I)"LbpQ;[% Hi4J8-D<&1@[ ⯁(^/ E NR*n@(bt~! ~ IWq7N<B.]Vjg/mbrXnRҟ*BƝWۇ39QA%6?B ̐^xЪgGm3.gorK ,nsj>cǒX4p.:,k2Tz4s^q`Ӄ4QüȡH5Er tZbO^ƩT ZQ쑎┦2 OFjfchJ%[[N)v^K8k֕U%F(ħ;( צ琤D4(N2͔jFjMψT6M|i$2lh#T}rj28'&Vuc@ǬYG(K !]ŕ`ҟ4w>4L'X}IV9ή1ҏgX) \q_Hq AJn:f{"̭/W4~3?onNjT9ݑ9țFUBbXR )ٓW>*hMO,ēյ0"R~Fs4*feE ǔ 2 ii‡"cU '3cc-` |M{I[TCHPnvmdbyԶTaoB B,O afat o5VqxhceN2!8? b/JzɋQ V]O6#4cAw JU&,?$]1b5T]E!`-@xTxcPZ8=\kϓK6ڊxڒLyz2 =#A[| F7 ( SJDGjbmpWmvx&GEϩ qEih7QT0_mjUMTOip gz_ѫ8r`Ah~b CRgj()td TN]w ;)DKf1~TȽ'^yrF(̝tCsZ4G$i6X\3G&1ȓ_ %a*gRmE1l];#N vز9 ?*-Xr0Tc2HCa6#suM8aKBUeuc1b,촒e8C'VZHBɏ~D p'}IyqRV'CX`w6|oZo|Š :t `(H#6[._S>s\ZÈi ta.v-?ШD2uŤ谟_=jdA Y@~0^IlQܨMXkV1N:,0u͙98P諜gh%)({;Г /%8x;HyI$Z1X1r+F&*n-<~N읱uvjaI`Rh܏XZ:D֦Q`P"aYk["׌-G׊%ni8 }h M_˷GG?"L6՟E8,跓X+P7;x9yGi'aNڡXhkEX%zʌ% ֬fO!o[3_RSȩz j}}B>{- +^Ee=S;ز4nIydo ]JV`@W&>O[>!ȼT7F`drtAĴe]P7fEϱHCz0UA26DrDLR,aWstr숷3HH-6g+(jR4KS6V)Ng#v$/&=]?Ɏ;~N\qnءo\啌AnɑڕfY !uHMPJҸ ڋOwbmSsx̃Ye0Ro:Dh]#-q7/$>RFn᝚k?ޜ4_Wa+zF 0H;ÇbfK#rh-E9(l^d*&qI.mn16M@n6h0|60|z0qvݝ~:l@eNg]f4P:$MYG&' `,7,_pU?Ʀ{(5'kKeOf$/%>Lo4^B%|~Sd2lه9;jPE}LUh< ǹA2Ln.NExMHN  ΒMc+˷:IWb *CRhP+ &TU&eXq4GKbP1PƑ# 1wSԞ2dC$vԇ[Dl+F`.x ~oQs޼/sIM S+[;.)dѷLfKBZ+h&̶UhfOGb(ZN;#6TH7W \(ۿW0cc͢5|6`2|>*8/(loLHbIg/rxHTƒk!Ϡ+ˈٔYG`o(;J;0Y%4HkA9~rRMQr+K71 ,f6"D;i>4{3_KXk3p?\.w2#֖sTpT`l*\N]X; ζ>U?Q5a8Uo+9#E1UHD4>' H`HTBF}H{Գ/hh3z8kKe/ЉΞj&cǎh*'ы&{*Suy,X8 S}/R57T_Z_vJ,]H)fGY{ 5%]ɑ7]GI F|Gnɟf#tIRj>Zndgoݻ杛,Xߑ.Ey755g#'2/@CQ.wuDwX L2i&E(fbYZ+Z; ]Y +.1>8%%sXEIpR|3{Y[~~HCCȋ3Mj{f,:(9Y lvu28 "2`6Ca\7hs )}EYpL?2DMj;BFGǧ4UF0xW{-̹kCtL?bs$[|&ӵZ)!ƈ!t2毤L#2Y qɿ\17tsFIG|wOC|a`HVۮ_pf ?劧~+XM2+e LQsѐ?`4||"*xM̺wm^]Owww&UYۍG\ơvU$7 ۙی1C+yn>}Xk߾} g[QL|uk5I;mG̎;w%IݕW53&z|\bwT55ה'E%߇m]aq u7cpW+?1׫C"ZSp`v3}@bVxo>6z/Y,ONtIˤas,{C(|yqi݀20&."2/WI<ù;o&8&A x];,̍>ZP_~pJ݇4Y@&}S)$0 MT vZih.kPf.+4˦o/Y Og9OVe'cpgHjM8%fvo+_#:%dN5P0=MY )BҒmiU h٠]sݧgERU2[U0g9y2θZ5)S?֨=Oʚ Ρ]57Jq,&^lYʇK zzY-R?"&ws!\Wxs6Sz~rq71)55ǮظiD脤Դĸ_O̩g#06۶.^4S!xb-Zl2r(~z-[lYLlV!"o(_Cjt#<`Iͭ[E?-K}@x*f{6XjR_D's]\bi^ԃTEڂ~fTmmo,xMTJfN^~A!` ͎䤆ǺUޠKf4jumw&R5S"8:AaP ݕeY\k![;Ss19-PXZȑ7/۵brw_NY ?σ؎#R/~t C樷;p3lʡgFqV t EA*۬1ʖci1Vq:մ@c%I'.`:J26'l%#eI $qt<2o yL;q&PFCaSN׽5^\ vmUpŸdYJkZ(j.!d/ktq =v5IzMn1'ov*QgrJ%ml;zҌsfϜ:nHwKgeИ6b O?sX?v#Cw$B$'cBjEyԭn߸쓚36?v=1}t>L:V\}qj_ޝR=|~1,קQJN^ww`ޝ[VPnN9]ҳĭ"2 p:)Myoܮ\4RnODĥ<_76~;ɱoo]5սlM8F ]3Gxv|Mw6bT7>wxt%6U_>`B@D+.ud|˗iP[$.O&F w 6A !GaߟZn-Yz60xNUUbqeBDnlbOZݜ>[+VT%H5#-#W/TTwZDWA(hs#<:9:3\n4-0uvjDeNYnñPxӊc F@]Fz]i!v=Smoܔy5tI+dm U4<&Sz4l 'N;j=L;sǴIVJbTYrX إO׍F_8|9äX k|( [ єF+& 'lo F́ƦRR{wE.C3KTG$ض}蜶.?FZN^joKؼmˊ7}S|BQ Ol[hg촙3&٬R1!M!_m˃0"R+S.a0yqTάes3pWe~(Պ{ueT٦QϿuB.YOJS|:!83u %7cF }>@1tᦒ:zڊTY;(7g@r[pFA}V?WJD2U2MLdzٯG ߹'0)j+Fvw0 W/ cV-@~#h N*oUC|kmzSxv:񕔠DZs )?5U)SĨ*. IPfyss- LOݘE?Jx?YY !Y^Ec#=ʀ3*RƔ40SML]h˄IO .p~@G> CVc%)v)6}`Z?FL%g K-CM]:,|,6ƥhk+F5=R_=؅INӷ.ůHM67y >̼ۯ~)W(^f^I]zXJ L> ˱)TFU4mݾCھFBg`҂E{g)P9#[wWX [ .GxWPl>5'_AޜD M>C,3b_*u?#̹O%?twWr WQ걘5<ǁԑQ;v}U}1 'cz% tf1y a#{~zXNBٳި y|m9}&o)xe>7u8%Rېf/xlvLx\$o_LTr宦d=_^K s7v=jSi=F^Mz&] k@ [J %]d8S+9Nh6,etM/,6*KsSX1̼9+ڎliJ RЍ]{)cOٲmEv\LȻ2Jb {> wϰx#P%~I0U#>tr C9&'4M!Đ[*aacVe twTdx X(~Ln0 64ӂ$~Tn7iw6NBzRw\&4J1˂]Z{#柍]\x(o5˱9_D*ցg"O#ϠmWf;l`\}_;XPwH #yI-`3dJLfH0oG[,bG*[J*B:wlwB?tZ<3(_X8fS .zo<ը2|@27 |c"[v I89Wc޾~.a.*!Rs0+sKna_I{)YiяNm̴ȋ 68Ŷ3וtDKš!wbTl;hNb͠cn!UV^Uvk]!E2 i/< ;X|BVV„/9`,`;u?Gˍ,,XZPB `M~ -|.P:;|)#a s(d  bTAAP iš̫/cWuT꧰m:ŏJx\ug݂f5YM];ݶc9ljܴy{&ܥ!}݉wqbC4'&<\bą L;}3:"X{yvi;G-s5jefe#BMRżF PJMĨ=.g)7T;}OU,4X _C5P(/ZS)ƸL+Mc ;ZĶ88.~ C$wC-οT\ayw0:"*BTu'c3pخCSܘiz.Դ5Ii oI:'\{^qa;cȐ#@̜a虼y(Z.y2u6UٟB{h&!q ʚ.p;+{{[Y{sҿ g}_sfPeaF毜uJnԓѫ-PXT' 9kxnOGm u'p0'+:7ԋf9q0xocs}~*Ro\@ zh㥇9 a(jtӠC+I @4}8lF>dڍ#gY~mzF^?1c&*2Bw3;V`эa$MdOx@ރ a`Y_ wl8,.!|疽ar asMfiEYIah\"%-"ikŽ2]:|~u7X sv,7|c.jӚ %Y+0 ~QvJ (d+xZ.xcsBiVa%/Ϲ> ucZoi  $vw]o" >;m ntti^33:,i'0odH@ _ԼvYQeT ˤprz$hk~#)`~/ Yvz9=B`PS`3G;p"sN8qWqĐQq(4~QSrӪENWq"zיÍ ao#G&qCyhKΌ;sYqʱڮaFja) N}zwo۩Iױ`lYi K|$3CIo(~p|JA_:.nW c Xb@qU^n6Hu|AYrh7R SIYX饷HUu֫UXL^DJ&1V/ϭqŏR + z[X}|H(|c?$\ ʒߜ۵uϥiD0VӚtӻi΋CuYYø &XFvY? RLOM2+(K.߀B+KsE_-OdXzn;J"0m(c#^Bmǒg^BO} 75ۊ{.T:3]+ZAtXM+R0SJ5ތ٫yYs׽v$6:>)5zޗO ,~CIX}ad`]ĔqR*,f}P`g$ `SQU{j{0&yPEԷj3Tn^enbbPoCeBkS|9t%Js"OŐu/ ADH3!6O^r|{ {ּPlژc9?&ZM};ƌpΛy魏6r^& ԑ46M™潆A \яvٴ4J $ob;DRKOD]9mx{GlˍqT\:-}Y/yy׼P0|dݏgdw؀ 5 Ll>N1Ro<2\dCxjy 'Um_ X 6idJ`ƅaPpeΡD:- $[[./+-*@I1Mo:I'zÐd b0e <]Z!\HJ3|*V!&P_rbO5 o^|:{d1_G_eg%&μɅ3NTT6ؿ<)lA}..s gY*4u1iLqY\h󴖔C8u&7?AűݤU6vɄ˔7:"wB%YQHZo2Z$>1rXGBS g%_jKmL~i1*#SU*l*^F39\*6*@u pN4&_﵊`UQIovK- VB%&m/[CS>SbDCʱszitƑ| 8 u51$Ϥkk\Vϙs-YӔQrd4 dyIqpc3kc?1wsZX/I_DML*nrfҟa 21\`b4. GMhLC>[kؖ!9 6^^O|mc)0,\kM.itذ0)n/ϔ>|x; * Wg@A3o*Rsq5j 3ná? kAf[]ݿoY{oٙ:GlWC4AuKPL(,6lRw{.IJ0lFT3ڜЏ\ʜ@dR$sEa&a G/b{iK@} 6t-q006"Gn]L@j0'r 兜ZA*\/}"~y7Y9b\d(}J6t|#,;خYZĬtYer,gN%I͹B":?@TfXU0ɱuR 4 U8wZ "4w4ro#!QHwƿܣNL?8[G=G`ȱ؊'\`V5#KRAOk)ggz@}ͤG}˗fb:dYn$r7۶s&i=3nM(:e}}zݓP`{[1e\sI˸qL)C)Y%`fEV+P1?#I2 níO@[B" \2g?iۢUђF?ui Zvs .TEv#~ 3wo87@fmrxKpփ\`lNl۰ދ_< v6v9I Y͖΋s3gg1ངm9d15̤k)? wAo|>xn(ƖN6GIiM"1ssIy_gB" 3 LTx/a8@?dZ=ݰMoQBf$l{B0K~FA?Dcg\h\(;Ar]Ĝe/J?0#- mC%txXۏw߽vw]OJM˸MEmrsִ4}]UZSE[_ީ)ay+nI:؊pnjw=)/PH`Lv&If|[`X@ŚM[0 QJְK.G+pԊkoOzXHUp@ SnV䜫Wqǡ1H(f蛋Ѱi"ZrIg1 ySUCZ3ko 9F;pFg>Ay7E}l#)X{h%FFbbLN1X@)wGvK~e3"tBѠ+){F}ʵCWeXYL և)s0r"[Y&'a_c*;yUVaj>T?Vi~ogf@F 9Vџ"rfrYtgp@`+SB361q V(~/VLwȩ ?Ho(lAɁz,mŢSsu\^_|81æ,8PC "=5̎?*/q"#(ZJSG"Eaޖz:i)|X! g*!W7Ǐt]9v.ڃ!{83^CRrp&͠漹OO &{șl51jFeb4^4K7j oL;r)j9-`qzKӫ8 "L@./KqC6"at+hg,-yxsK$lLe. pW#]t}.a{ZU3 r趃#*?~Gw  |ղ|pY^.GuץMCp݆M})!w k RU)HL0Y5u:a쵞j ;NXRgx&' )=1rmץt&+ڼ~ ^ 4O&7dγA}F->D*M)j./ܶȵǮ~ىe8sWb ҞY6]kjشMFbϫ#zζɃ;j֤E] ?{a_vKO,~z!Ibx 9~(3k6_>quJ?};ׁu[V!/Z6:>6j@'w]FKP/oV">IY)r5A-TZ}[>SB߭pJچޡ/y.]kJP-{,t S-7#bFk]J!p않B#H{=<}p~UuZ7#W- _ۭ0V@& ^Nj 70XkW>H)ݯ$:u̲ [ԑBq ~[AE6cpRۮN;=q9\7\]>kLVRb)6w5̌V5*qWs AvBUኼ %ʀ)[Ml )80Q6&U~mHjMי{K.08KT/5סs;D]-8؄,~kI)XG[B]nqnxcm}'0"o{z%oVګt V+0,!+9FN}Bd2V>;Q"0bq87޽u_^V:CSGT˧q˿q<}jҢ#@QA+ӄ_ tηZzMdbOBq܇hAj숃ȭе>H+x4WYNnuyn7.u6s%rmjMq$aCFHm/&)&HNɤR23ļkf`ӂm$CSq$p]˝ua ZyGH@Q>7R"7[Е[F|ō=-gt̙PィLL&)vuD߃Z 2qBc=o"`pi:3(tU;gpQ"I)˺# \}ɳ,W!?+ʵ1_~qg ˵ŧews 4GHCiWL ,+Qfo/,>c&ĪrA* Yx>z ڦ>sFjd'.Un[0N uXDv&0PSDT`"X +q@iՊ@hm!|;1E8+>,H14X\=n? T՗G iQ0q#AygnVžaaw7=th;k;])Td`;#ެq.gePْ0t=sٸ}_YzQтUۘ,^Q`,HtT'mAQIՖjc^34vsRZip/ %K}g:F '2y'X952-kN2]& ~M+ *=>(!)Z64acxMUn y7&Mkwqd;26?*=Egrz6!n6[ umİs:;}(zftTQE,kFb.Eii}|0P.:ʮcoO ƥ㢓$dUCBqx{G3 1&Zp"^S0矔7fC+Pq$kЫ% O {xڍGaYe83CR{kUs2S𼸌6NU4NTXLzr6fs6T%U]D3Et<dR8Cʮ~eY坘MO2i{:~u/,Bڊ pj wE}Bi '%^oSyhT뭤N-`6ڕF3*N0N՚_BnH!bų!_02/ :WrfwF|/ZNx%Ԉ7X:P̕Y`rAȕY4?$ȳMk֞$*z8짼 ^UWSLLGE?;O}! 2*%^-́RMc T"f-o]뢗qRa6q6 .bX1W錟ӟP&%{JsiDt;;RZgO# ?n*#vn6ީYSJERlݜǐ[̓mU:?BNKi^˞(fMX$,ՠ;iT`i-Ӗ͐l`.tM9F@L+dxh[5#U{VsZe5nO=ʆ;б]g:nM=(|:JGog> eAfLgCf/]z d 9w%9ɍW#4Z 3КO+6멀9IdZC M!Ep6da?zp_Bb37)r+J-N4pZ/&S` uemljlCE_D_geI5N^;@9/b"@5e D_AVI^"vK?]`h@cwwi sXEuy!DBEj8Xr@uĮ1T.D^ V $"J HHrAxQ}*N˳'e7ȱ%vd r0A#g ':AS^AP;L_KY%e@eSV{$!\e8^`wX`jHaV-,Q=K9O'V 7`Ɲ4?sd݊OpiOː~'nYqjy3yʔ)s;:9N01/2e5N'9KSK_VϧDƕ5 1ED6CVg㔎j2ݛ%yV6pHP뽲eoKBeȼ?v>r:Fzn^_.pV+&渻Qa֜ǘC1YWnO>ٰao~{tI3=F7AH\; R`dHju (t.\ Y!zqn<12p6sQy2[EhQw}۴uou>_)sWA^!pZE nHF{, T=(F `09"G4q$;5h䉥c]!HvתnۈKMC:VRAB}B81]Mה.m&xuҕVb}Aҗ&bbJH ('J1,1:y;YI)n6qcC-(2[Dv!6E:[|~ݒB4U+NIςPH7`%LT|s qq1G&>u /ע1%ТWG6t:]DZ/ȋV{ͭ֙'dnpS}ר[b%AN ysRR.f 9j+=s[s LDl&#jn2g&90}55f! aXA2hSbt&24N`H^NU}%}yCS t4tكi oIHh`j21 ӵAb `lk'Nwּ"RKwrӹq<'0BTT W~HoL8,bt$3S^gյZ 0r{:.J{Tx !6y}~Ҳ/e=F *+))2V)͖!rfܒ yE%ݧ%ОPQzHqJf7}.e\~ۃ0>AW& ,ugm;͍h_688O<"2-\QTi6_۳$"Q3 )SK.My1cꕞ&[7x> b=كl]>%M-Z{De!Ђv*Y\ Dc@bVUmAN[\@ZAFR $O0{>"PNM]l~rrAf(jI"eI7IO[X!/N&~L772֜/J5x+#Y͑ߟ^޿xT gxzѻQP?57StvfeOc'E1dq> -RX&q8t>=e4.uX($# VVLS(@Te{T)~㘶UIks FnM}*To-lG˖&.}ܭ2[;}`\ޝF48q\u3LDj0Iel2~yѕ G$th鼮鈱RIEivF/ryTijQ;nAZƲ`GT{,&"h~poe| %u!94m-[Y:_ouNmeU9f"R9Z$l8]_q82s8:&KsP)iksꓷxi`t̯yemT†; Vۘm jIziixzK$y`;7)>1~;}Il|ߌzZ#@27 ry6^Kiw=gVTh&J|V?e(A}t zq#љsSNBV8U 0UhNFm#6;ٙ8 /v!\4he.I0|[i_ 4K ᑖ|EHJq39hR)eA_g8@03b|ݻ,C܁率r0ʦ5$3i|xͤXY4;F˯(] i3i쨥b֥kzKI" ;vDR浘!v󜦫rOoR*zJNfUeOn}o уi ,p{g!շ0ffQWzJsuTH=TKT%nJ ` [{QB͗5bb]pD`q; XU/Tbtbl ZHF]4يhԐZyIty4b\'|NR'iIդ1ʵ,/3T"{TWN^hcw gdCi`cr= YFڜa)5eBH&3Bpv=|(J2bU'v@)*[E:+t;Iϸul&UcUVWD-HMg=MwR~f'd." QFӾY+ 7!nu(p霷_btMNb PXpݛ9>μmWb t2ou!F[M`.(^4@&Um<[4D, t?qZAPehߜϸsT>@&CK6!MQ>sW.{~ glSH*" ~MpyC%_jS=X^99"=ӣ~K8{*q?Rw_4I ؂b PC eo}ODE$i̝wB-_vL0e_6yЀE1\$K9jzّSPj5۱MH nӥCYsŪEŞ$CVh7F&@ ΧbZ45P;>ņ:(QkW.!JJ|G'm9ӝX=7 ǬM [kNb g1o"_] J eaO Τ_:7YYFд5wT~5]:!N z UWpt|ecYq'kmNcՊ`! @ mY yUҭzC[Ɠ}lyGSU2ꉮ7z/Ŕp!Ψɐ Ą l)} fxU`?apF]/"#4̡R_Wܪ\:qcfs UuHU#" p-hNr邁}|j:: {097 ֌ȘHמͣm>GJHw2u8 bcx1NBA&ßTH /V "rim3wP> ǬChoZہ Ug⓪YrtXVb^v7R XW'5iJ^2ԍ݂P89:By"pؤWћ}٣q^qBTF=BE__Lo5H | (RnbIdm[{%!0FgNo5f$t#0[X?e?5ϑhX2d?R6Qit2ʮ 1m*dA{ynb|oA,睹xj=Q)V:b7K\P3v6 robȥyMzq۶8:͖ڄ%\ t@`qm+ye n>E(%g[Q`Ct >$r9ic|M׻%=KfAsngY^V|l~,ؑrK\Z!2&BCU]30\U¦G:mZZw0UAMq>>wq0/#11G!Mmϱ) ă(~j) 5160{O^$QfnbZ UI |V.6Huo& 0;S|<=;&-f*LJHsN?N&1Ny#Y. GwG\3^Yo(Pgôc>ɯv_BQʙ֦x>QluŸbQN?m6MH\4-,aJNu֒ųIN }G%@"u o8U70-85HITV =+[Nqbqjq@3ɛgS P9Vk9d"Et?"qU>R˚_Hh$2$}L"\\b CAb?{I_|QV韇9_]e dEb@Er|h.pS6 PF纈uuglTYq%N,jޣ<s(W()[t;49zXΆUP2^]tl~D1yfኅ6{qn{V&(9@Fhחd_~N Tݚ"R)8zDt7 ŗe ER 4-ΡD7ɡj?wܡ ǎ_~mLDaR >(5!zJKW|Ɣ(諃DyF^+@Pj0n`J ##¾)m߈pk 1Ch(h| M4G1xk'%>_̪ Υ-ֲ<zyӶL`9<2& S]32K'cEFd ˍǯ.ѥSd EeR&# %u0[?+R n)h 3 -]Nvc@٭IƯ)m{. 5ݹP/  %#V.{qah>Z5}hn̹^Z8Mrk:6"V7vBNo\<'w[Z{tq<N/$)ХxX }OS_{4^P2A M Bvsj\ nAL~D rT} :=kq} uLIE"Hzyy&wcTz'n>x'?1oMh`S!~y (l⽠/" ,AیHEQqX#b3`?ox1^7]T^w͏Z^(#:T)GF6fn7/@-L$?hP @!l-Y=A385H!&J &1.,> ]d1ȕq?YȰцO4s"=u`cb3OIƨ/3Y+tpdj-0]3&ހvuΉh^wEMctѸO.yaMa45^OB&DuCLWn@vsu_t6yQ)|.#{L[t{yb!/y-I SLl4*mm4ZI!(<#gszHM;ԇ䜂¢<([ԅl_{Ɏ<((ϝ~` QO'J`Zs T˦U`N6Jfg=p7[=PF xSÖ U[!&]3f-e#08T"|*y@&<ŰOZ!e_\Qitl؍gлm XLS #ꖐu@؏=5~;:bo|p@zD$)J>fIu?]w@*O{R -qF5e$ş60MFʺg_{'{[NkDG̢&LܠS#T""5+jYd4BylYm>M݃u7h|ɘfB9 Ӝ[ul1PQ.XO`G N'RTWxC$  g^+%=P={FX8B I#oj5wO]w(a# LE m}K 8P;J}#zh}k[_ވBxA}B)i8OsJjY;]f~4=[mS:|Y,u"GTFJˑ-Ӓ=IñDjJ0 kYRX򽠴I^!`Aď{&i4z'㺣14WNZ!"[+,_P䤂. KpU ier~xcF@EW~j,?h~Cki\&nֳœw3m؍̢`,ueP T5 J=_Z3<*HQ^`A(Jxr]AeUy:$b/mUb<;TU$ zO~Qbkhɭi$BlʹG%,FOZl/MĐ)rVؽƮDp !`T>B0zxd.,W}Xb x-?ĨZfk~E^R>ԟ]⊥zpݼ4F 83LIq\u-S.~mG0qk#$§%;y,m1tm"/>D;7"BmZH2J*ȉJRmPLʹybZ>nx_[/ %&G0vͮ z$8Igj:#| ڽ2 bㄖZl8g v4c Bob#C'a K4:(\l<}EtDi# B-MD \ y?eu2_f魏p2of#X Xչȃ`.{+߄(+bRT|Z(,8=lV70_o Bt͘8!?-)HQйH<e AI3LP~+H Yn-\x'u<<޵\(HbKGHyϨzb!3ZI%y#^]ĥj=Sm~$:SB=amiWZxm)MAs !@LkW]8kDdJ`k A*xNxeốيfFpl`caz[r`p(Z6/j=0=;BGAa%lQE*ӳ>=VGc:ef ጔ;dƣ?GR ~wv\ouä \ Hԁ o:srQ9HW\nu 87%(=<-i r'"9ZD-~,-)$ʗ$5y?RϕLR*1a1d#&蕼}jŅuꌧ>^pY%Ō{x+/)KNϮoxI2CO)*8l@4Gȩ]y#6.P}8\[ m )%#TS] %_RgVX&C6dxt֝:0CC"4JKZ;G \ ۱? 0A%' j_crQ8zVtQ{YNgf'y8NыHCЕ%)]:@H 皘oF/F>q2Mj&jw LR}X.笺sR%SŢ֎f2#~1LڥR"Bb )GO LR'(#/]ˈ~1j7,,xq.u2ˠ/*y؀ A-UT~F!3vY-9%AOn[(fqR]cbrV_k\XB { UdwFWLSSBCֻGڵ#:e(I~շtboSóy9;!gl^-)ɗį? x4R#DU+(&!3spsji!rIjDdެK `d79Z_JrU䣭9?7T>#rPd򣏢W,ܰQc*,JH8׏Eتk+[Xs \\Ա"vͤck# bK2+BFW;uO'`)忶HF tFwhwG 6d! Sww懃fa`={=F<Yq%L8$_]a%^Oɐz= Kp6ح^%+#(b}H٥8;옭~wآrBS)Hs"?d^$ja퀧{慫n$5 @ Tr~OvUgϺϽuJ==1v[lo܈wo&_/yRm_+h01LL}O< âRƸjݪ z˕ {/v𰵙) 5fuqwioy@7rƉ)7"u}IŢV$E(**8.78?# 5W[5{&' z&{*N9Ή &l J9m O?N՗&ZG䃾J_Pu+Q^^;q:iBn=cxm~+-YM10JdBbߑ3V^gՇtm~rC{IqLL]}͘D$oE޶=bLWTA{# >kuz]I~1ѣi?5_is1 ^)}=$zAl/YG:L LX݇yogUǃG@Wm@`} <'pgbNWj 2rs؃ۤ.]<sgN>{ܥ+W]rCKS;Iy%e j1|r<+}9ir}6Cn"a&vSѴ'wsaۏ)>BCJ޷hVRA#Jiܻ&#gdt̟_o.EPiDBZ4ʂ8s=9nH,n F*F~7#VɡG zU5c`ASia:s JBIZs¨VyLNf-Ñq#FU2%Ò[w7kƒkWn Zs}_T1oOp}D ~%߯ +WgˑS@1Mۭ ـ{Du7onZI.@Жi,._LSr3iP;[T9{k0]zr%IS εgÉiա&\L a}o+K*ujI=pTL"HZ4f_SyKAfڬWN^֫S̷ӂ44v3ЗpAjdf`i׉x+ O/OOOw 80:Ow'Pbn?yb>?l_q}>%aLCjb@4}lwscEL718y`gg /l֥π^Qzw<r >{vƣtGђ,(^]]Emfs;sC<|.(]R#K 5{-{ٳ'WSlwղMMa䈛+7sL&TUH|r`\鳧^YIjxTFXLCQriP03{6ԞÈc F Ʋb[ O7 j%7݆#*^ (p,]j2F߲\vXJ,f:LN%ԔY>yɚ[mݼ~]  wkmƏ3b7ќ18ԷE˗/7Z70 K++K3At*~)9FID*Pxp{eK a{)AUCo_S퟼lF? L=dc03D݄ɔԞh$-xR..A{YFG*p0qyAΖbXβa=Ajeէl;g}+32F墁ltl=K6toRR1sN5EVev|5zp3)\k*00mk+҅cr^Yfcm#W. X`Q߬2{R9 Ē~w;drj|l}JNRӢ8!5u~-{SeޥmeXę#QG<敚thY0E+l]md` fwYOy3j,D3,5Px>[u(ҧmFH _*}-}tJƍn#xs}̞6x+LjeœsG7*Ъȗk%0(,~hFы C,k3^.QTkXكZO&6Yl"ٺi١s&}(Khs@AWȮZNX}Z {x.+G 9?QIqbb04.)@Τ+;Kg>[`]CwwdU.]u˿\몜$Z|OldQxY,?*C֮$L"Qeck0LNX"jaŋ)2#Ci|O8ԴuM¨)?EgoI&ޥ mCʺv &ytpAr!)~#*b1QAAD4p/j:",W G\RׂTy9'M1%4ŦuaA @)Qck| VtW; = =`|mƥRN:9T`oCmڮ*XMe;7)vu3>rUݮ8E&Ps*@+yBjG8#?pnq2NMzyZuO84`>Ykb7cFZADNuԟhJ$ozb577|LMq[C#ׁFw2mPb^f~Ji|*J(i6N5}:>|*]$.܇BB~Ѕv瘁.'̪F-_֬LG4> T~ ާoL2{d|np-:WBE%:T;:F׮QIb.I3>XU# }W(Wqx;O{3}ɷ5Ocz:dȝRMD263]9#˵mVMi8}(xk aQz4%vUq~ηT'3q-e8+~}1?"?\@k>8B<%-P;Q0\oy:Y#e(0zq ]{ã Ey,u hN>YlaĊ?s|؟fUoB^G('x[c\ld} 5L@&$ ZЋ /!SUa r= TB^CN(Y_ER~k%IQʳބ8j0_}=)p-`L!9z\VV…Yj'б)W}&22Q5]PqSFwiͺ}x'wm1G64[=ڡMm$}}T'xYp:_TVL{9C(b<雓*F> u#4:_pv)8B*2wn}WG>$4!\mgj`)o̥Ld*S}.貁ku@ >UsUGAZ8t * :ȍ첐z rsw[Ϫ2o"ݛ##}uNҭ t&=3 T^ǤbdP79 ' /]]e׎TrS,Oyk(AIͤi 13ʮmf5pZ;S eFS*3pR9& W_Nn) ]p?d/-+W%Xdcd=7pCaC'ݻftZ%E 3s'dX}Yͫo5jij/Uzn04?zl-M"m[򉱻g|9V`wnjfFT!'vRƌ ts.p(E-ڱ6k[rVe?a茡{@:X}X4]-%h:2V(ne}F-7p7&ȭOgWƭzºL DRJTܼY$Aˡ,@[){:Ѫ3gTk1ǷtSJHHm)~JWaxD)4eb$yJ `v}L_#c5W+ [ ǩvQ^.tzQaxQca D洉ʩgfJ(] "_wE)T*KOl6 7q׶% fR-H|M#%{ٞ|REl}& |+Äԯurn4yzK`d߇_Gkܿ$p5C]H${ZP1>&fԲ} )y aO?=\2$eUF!P0ꏈtQ~G8pn8{pIP /߯5wtwCu5Mzp|FkP]%[μ%z=^ ;[>~[Xd1(\2 dIX8`kDNT`R U!Nz)lVI0|ww \STdK`,b2;~"qG "?"6ssS #|'FbYr"jqW4q3kx+~(M&Bd,%+dR$?U _TELXd: wt m`xvubY#$M'_3⚏ș6VmǞm#֓ LYJzebs$32WQqKʇ]TcK +' tm8wQYُ[2o8! koh9eoTcA_&q)1+n<-fnCpgȁh"9k;uöcwqFR7͇l {Cixa#yMΊٗ5m%ZweoH̋wX ;5n,`X- e,Ӥ^VR˳ָԝhX(GЭ"p =Z690[=ﲪs@GRp2jؕ!e~IC+b. Х{͉{Fc9-z (qͦmh-%D!%E1GIp;׃3Qo0QNDT FeF}yl(CR{]2h u k*/%mXv ڈd_VeA1E'䵠 a._JZ`gq}nH󡲴v􋚜F< |V90@v|]pܮh^e{T_ﻷcÑr(4(3voR8{)6)4p|&?\,m[ SG]GExQFɁ%%/!:|"Dv< .ً@,+HT*Jvˢuݑ.qH|?3Ұg hQ53w5K⊞_N $k|gyO9E|71ƂKx\S/ˉtUQܽ ͼP`^SæOUݑ=`nȰô}Go4qLvL;?iՃdJRJ TØ9jHApC.5!ʃjͺ˱~F<߇9űVKQ)I M|:MIKN1]D-4g |mU9aols #eC$O)sDBS[Tݨ`qq[r.*fsLn1 Kێi|%(\vw?tKF.*`qo iA%('##ֶLsF^YJhĴO,:\Pi G+4}D=빈JlInME62gԶK9\UsLH\]Kb\hd*gwCm&$ :p=KάU!GŸ< v+orƉ%}*p͓ ͜tp_\.곷ɹ/c4syoq ֋i>u㖳wt6y0F  @B&IPt*4\UF*{Kiq;O ŘhML],-'6lLun6~QR}W9zW4>\TYl] R}INܹ&e+lԡY 8Uj;|C_ j)d߇J$6oRsi(D  BAn=D72U룫`;WՋ>Y6EuyT(kDpOYޤ m$pCoc%T}+׮_^mצSw -[7F8Ael`N1OcJ o%zfcx忛qFqo2WO?iYsjTw#N+L-(&tnB_vbTq'a I+4Qpj|mC%,zP?B-,_ӫ2(+@ٰ,1-ZM4kBY1M,,L)4mB3 FxhJR]3>g^jLDD31N !ܪTU3o±{ >+3Iii͠ʠ$)i3UT8JcJX{$a4r`az)-a W&~@^WdcgCj_*<';ǔp7h+_3/tnȭm6lXL{QPo}kprwKZ D(Sy{!s֬x O:@_@;sn! ݞL8xHti8%9W/$-u9o@6Ǩ'ASҮ{'O'0FLvcһPԫ[ٯie1l&= V!ļ_dNB7qH2}&tMM7:%1-|_ 3#VN*zJht5JJe\ѫ2UKͽ"c KD7w-agu| f"{O` q.sow$G-y~p^Ꚇoj*=;,B?F>8ߓo ~Gl˔BxЉU}w~dGU f%aXh)# y'tbt\lG+ȮR>c&ܹQtRɊ22rboB08#(Zc0q Lk0is d7S2X^:R0/ngw)7WFpj.\ּ|t琞Y^i1s=rcxy `}Ӏj5"˲w9橊[ҚBΊ|TBioI_Y&Y {:.xeY/gSc':/^9'K wtQk#<\*WmqůؑKAfgCuVk9g-/J3K#zzxC |(dyQ!ISfؑHB.'d BJ#w_'H-pLTU~DW^H4aErT|X12/QRi*g=kLmlb=~5C-d [Bwh/32$k۶&!TIs+vNK٘ 8Oeso)Iv ùhDvZN?+IT1t11ux{;M 38 N?r]?x~1*8高zi6ua( .f96ǒ ;0 Q rB5؎<#ܐ.[4oYsfML՟4I邗/>/)2 5,tOܝіYzHUyCgfa<.P+021F2#?YF{ԕ擿/B2lyk6[ޥvU cS k+G'ܖٔڰ4isH<3j/TrOϸM?.E~*Xq s*LCT0RLB2djᜳ[=al'}b0,ZaS&+<VE{2 8ĶgJ m*(W,:&܍*h(ѕoC7@3>^GQ9 IkqyKwPT_;60s=|-\<گKȬ93@5~ĸ2^/옠\ uxg*B'7нUݻr`v-Pͻ:ͧKWm\`L [b%ʎtnRiNz8FZ$ b6 5ġrV <3DmUK6n^O]1ՠV?mXFYݘ9CS`1pSzU 1?wYUux^0wquR*rv Ƀa0Jƌgə,q>Z1 nh@4=ҰxΞвOH*7)tp+ %%7ԹD&u{Lu]@es$CþMNLVZ(^򈄕n2u"mWW(H *V-1',67M<8ME-s AoDBUw>}ְ%Fz'|8!ޔs5[H,h "E~WdP/B ; C*[O%$Uވ>,mvgun4 ^0B3L94LA+ppfk=FP-u'"r.ti5m7>p/$۞Ѭo@x3X3T紈x'i+.Cèy&$7iE9} \P_q y:91j5-䂺5$PˇɂaGsCI$ZcA'sDmE$j [&l e1tg ۈ?STo#V2p%lݥ[_<_uh"VP03 rN^eVwrq9&.~cMLꈕ*kG7/'0u=E(t C18 qIϢ"&Jkcz0͌#AP ߕ܁$"zө%Vqr9}5_~<}ۗٹRɎ}shoIs"v$@eˊ/ OywdU6չGGoOkg.eM1-y:Y*. { iʑͱB6H|8;6nFGLA9w@ɧ\tfRs Q-0w,_6.@ᐸK$P/kZu:-x|ƭXѩ[VBLU+sU.}ݤlG: 31w} fHKJB0sK1=b§U\YS]ww\&,<~ޟQى },$]_PʸTF%V}#Rz-hR甝ǝmp.^=T\Qڮw.R[o\j4L{{ݺw4U$X 2KZ9y:$z[m-(B^0P ^{%eFWI"1NP b81܂`ו8`ƫWCUgco`AKuCg4 |3V? jX Ƕ;#Npjn;/YkoИTomd3`z,E1HIBpgpߙv=~2v L*O6) Yupo gi!Hk ep3g>F@m6S1DPh~[nN>4*w.xz5 _{ODgϞ\9bOʞJgXD(U$ 'k^|0 'Cy//*)Lzw$]ux/)"\5m4V(ugil@;;#6vI[.߈2ZnɄ7|<ԡY< d 3n l>Pox[o$|AmXufšu}|76dicִXǏ'HR1-zcoJ3-8Ǧ88A֜IfO\'mW]؅UlR*RX=+(ShU 0oڗ. K-~ YJd5!_(9Aópjty@.*KU'B 5X %] ?AͣeVke͝pi bHyb g,e/9|DUGh G)n65ƈhH*ud3/At,66+ͦ_w7'  +^ r(供_ChfǕzqb?eQX}yṘYTfpD%:|ڀ \͐nPNR ѤlN?Rﷂyx1FOK)@u'~'-C7E@RP]%{xɝFwi,"@V2䊒"H )-|0:TdZ:iޏ-T NkYMTLGV6uZhtN8V: lgqt䍲HE_FS5"LDoL j<4jUL [ Kʖ7 ޝTM߳ ZWD &&00FA4N91.ֶ1@ԏN;ԇHnQi#js)QY٬@TƚlPYbg1x135(6:l W7 R CV-y/.z7TDE.``V{'>XIlc@CۊF@BK-}ɿjC=PPIvОy}zqoT[ɦh/kaP)rutOsd<3Y)NVCܻ:m N} j>ELspKGܜa>&Ń?Wc܍p~MHda_b|7}(\|E{ U0nv<(ʛe ^.h -!tmqj^̰Vʾ N"x -@ŋ\ A%(;:wH(*bb) K_ c(JKNO(,- AʌVkcU ˱odahɞ4w̉\#..{rOm͈ 1@ѣ<{|}'1Vj;Dxp _Lk'IUCh$s[[,,}O _%YO2 R#,D%4ј u5G e0). i#0k~LRjN@{6Y;Feu1 JQs:_Yv/#FEuYVOtư1輅L c(tA!&&"M.lq҇ ɖ\,.0n¹,)f1 E3Fh;W-`oK VETj"U+#Yb%2y*^y@253|o/~hJPx7@B $F#6tBIљi$i!~VV@]RX~2^b4B>L꜊_Ϯ܊M N8c%fdJ &-h3g?^ђhj0#~6;"dxvЊ5iG!IjFuڐ#]T9-/S0+8G>ፉRI!^қ3j*EO*tJUKW eSyF~OLL<=ds3Xt2\2( d8  сHcH:JUK g̢XY\(oRDiaNГ< _^ŕBaY6ճd ԪP;xdӕ6M!cg*XX:X2 BCEa9*JU@YO?7⦰*H1e^f26Ɍqз٫ I'Z%#F{-fGBM,A se*ֱMCɐO<%v?1CܑF&*^ L(U OzLןi| ϠP~\>_FVVn7 IK0Jl?oEڵMɲ(l%P tVygOLT3w,KUi WI/ͅ A*,Ffflk ^`R8aWZ\\rVAaĖ1sm4O !S=Sup FlɌ7k(D@\+ٞ^)(mSkU/~cFs64$K .`:q̜>s탘:j#Chc< bv}nq\ "YQJ)oGQ͗~I ]2 6g=_m h}i'~<-[-)9JՌfazsb7!ޟKTFM GMXsVo fl~v8~}1/`lK}Ki 1 L"I"HQ8] GJAa%صZ Iũ9{ vmUTS/Lh+ۋՁȷ+M+UK7ABTȽ'SCxoxY&SJbn/gpY~/MW_QGf\ZAs !k.5OB$g{Dτ&铺1MP H%C2wI+XGUE#8HiD,b*e&AYQEa^k0+wy([dV._1 P9mh+A|ޙY^ !Ye+FnVjN ץ |>̄V\ ) ěq R#wa2 n@ŊP64Aڙ>Pf< Aӏ|'e}@ t/7 3iuGSJHk>Ϭ5BdII8| {7"0Ye6zRg6=‚׊_fk%n"+\.1h3 zP yZ,`WSzCͷ!= QhqTXJ ?3U#l=pGjtw8¨GP.1\lȵ(G_\ $?%BB}g}|ڜх3CN ڑ&V␂#ʤ&8e^SqFwtM~"K'$O[ğ2 HvFk1ɺiXķ(t%Q5;aN#RMaDB,/RPf°yl5N62C$\l$"nRk^N alDoLD.,a&ܜ1tch]c!: W; 'AD[XOش"ڿHtF9Џh^ d9Yo*[8;T}[2wS$s Zzt/ˇb.R*|*kX(./Ȇ ||v\^{p220f<3HLu*$Pl*G(zaN [ᐁғ<ǐd^0V'lY WkR*:9-&5/Z@ s[YÙ $$%KKXSG&ձKXX.5Eh%Ν5?Ԑ FSF{^FhmPן',#s \DrKO읎{7h2)ex-Uc ͻ$Kzm ҩЗ!F0&[( }EH DzYQDXlJlj-ru gFUkTQ!d/ʒ֐4zVYrUlVbq3[d[~BBi%MUUFA(-C{^o$=^#Y?cGE|9>Z ǣl #d{⎒ڌ/>҆|p?G V#.yn5o[!撏[qZAeWS8zCOPHíR 6XT갈Jt{tb:?^ ;W qm1!YO輥,ժƴ}4&ؚ] MC}_h "!hO;rx>d8U'5͠BHm~])T8k/0l}I65O)%zgc_a:N|%Tl7dFخr'Iq~1r׀@j'3+⢾rg+A*No^wჀE'u22z0rx{~O/ҴɧϪg~gk-^uuE3Ģ:(]͎v$Z $}inлw^Tr'ԐF5D3gCmɔnG-[wiG (Mw[ŬyaUG$5T0I>ILi--F+5xi3'͎Dܪkxɩ*[UV"ѵ3\fH3.rA[h8t7Fs~I"l7Q\~!NScLs>WN= {vqJ(#. MڃȮx/ 8`l%d%_̛!_,4&bNn v*1RKUi|hIa;`le>8zYnF#z !PdkM\y/;J,☫j/}mI'y1M=A5@~ 7 cEFsc(Fgyn oLWmYHy] 7.2kêv~[6f]s1@ysZkf.8yUǷmitI![bD`&J1֦OȝyiQnYj!x<6Y1 awWvsRr?9V.aȠ0E.b%GUxӈ_^:֡S|ꧏ[[]w78-M旉%JSa|0c"\4˩gE5 xLaK6-(V1Ρ{f T폊*\ma TR꙱Q)Ch~ϊ88]fV/gz+#Ti|h,MQ 1Kdc_;1Gl M-:s-_Jgϱh3&WwF0"QDF礃tdu%+i2_=^$!3=iAervZq{@p%N-:}bG&Ju4R5ƿ-IH7t`&<9DUi8|@0=]dM^葭J[t7M~>5ΥZa0ynkKd{XyMxj˛ށJB}Zϗi%%ZC 0=&-C 5C Sk]"т%\M\(?Ҽ_B9kՅ1u0xW6J -R^;\v3 e# 4Ո<0H.嶕C@jglpmZ&_@*iW د[gk%zw|b@hüԝ1l"/>+sSH3| g=p4j%nkNW}V%ohq:}^|ۺrR4i2lgY6Da{R G:7h Hm)тy`V|v{Qszڄn޿6|(f#@eJF3q )?:t{v{{`TwD_)[V?|؇qRp\$A:K}*$Cs KV052C퇕U'11#Y,5oJ&ldf_~(jڦJHsw6E?pwf5S̞\ϊ֋uWW~esToX@i 7ZK/%ŹI\̧ˑ 79xX+I`hK\[˸Ѯ}ti᏷{x-a՚@fuSqo$*5%ܪb2䟂>ZT ?>kFz,Cۛ=-Ih4ݡ$Gn\a݋{xrs/. ,EAɽc%;tKfJxj+:,`_YZ ꎏbh-,Pc0!0O(@ 73G½cMhM]OY֤"tBݐH| hԀ& e@-扬[FѳbHqܖZzt\ J5:50A ّ_*C̫RHFOt4ф(Ecӎ4,U){avɗ:u'Z6EA1 ݞP nY(T3JHOrp?>ցceT5_kFDyUu5n'ˆVQ7#x^ D'}+M}s&JO?@j"[#$*"^ ~_=G~֐j,7d@&ffkJTsx {\Rg@޾ޅߒ`yLV2x)W%qa16VZ8f2w_iɬk^xܸ* PrռH!N~?'A},#iSߌXXs T17:\2D83S]g36ϩfr"#t' +/}:@`>=d/N\U\Qw{jG⸮ 2eWe h&^\5VεDI4zɯ eGA4 v(D p7 N*%3V|һKQ6yg{Ut /.L&OkTW!-v3$ypʘ[}s>}&}Eui.h.Ìk×lgAU؊54D}5%!2^7luVuMM A$2~ڕuj!s'ʈ;h9ۥJy"jf2b#ShHSPPTr_1:7gLNC+8IV~lv;|lY~`W72攋rm4(`>WH5 @Y!Ih"\cb*}v3oԶ[UϢYBulX}@W9QLB_hQ_FjĊȒ.Yw-TlΈ pk }(F?tz4" x5(jKו=} mP6Kg9O]> VqFRMdm5|"Cg )51qu0~cA PF(`a7 ĵVg툟_<LhzBo~g'kh_woc{>ɗH!6d/&OkemlY/f"> ]27?G`,İ 54`^:_wmC_pJMliۼf cϒ׀C\\Bھ(t~m8e!ێ+ 3 *ٶb቎-l9>.R'>63#^} 1!A%OVc\~^:1"^[% fВķx}/XU1PΒ2ʻׄa*CHef^7̈́ ~0J=fe~qA(M՟1h;Q~lbπr;LnӪ{2̤e?zZ^H * 2B\(0fE g nfjw{O``4BRI˱iŕS1P:|'-ZhMH5Qs  {yq?f AsINY_3B1lhn^N%~uФ˭njPݲ # uB#S d>3ofDPz59f0 uULH^0ȯP $53o&ds oI/˵r"A9S`^vq4`A)|h"9P:+>T$Ԓ/ʈyCylٺp_+kx2ϿCCnXy'L&De(ѵ$'":.vWj3Tʡv®_n ZG?,ͷz q"W:j1 Ҟ -1p-d#pU%eںB֞E؂86q@S2LēЈn. YvWĬفzWOC3Lwg-gL;P[ֿQ1_|#p敁PRVD|[I8?N+ h`!'S Iv@gJG R^"^^~ pz,@;ʄ<פ2:{dGÄʣHy#BTv'rw/FU} _Ɣِ]bKׯ_M"lDUe2qiZ)̿2ЯnM"9(/'.{l^7߇F_. Sn3'e ,մwXC4(n-V|Yߌ<wD6kҲ]Gs@G|8512قdM(ۈQ,ځjP+\>Wx7AI[.R NʸNiλiP$Js i) x@i/FQt5 deq7̬ck:OZ|]hJATf̣9v\J?s(t&IhIYʗs,4UVdTfiSf|Ѵ6P: d tw  >J3+wI'i>vqh1:D2 (*^ᆇF/1hWQ8l~Z8#q-xǹ9 @ `l [zp:9M-]侷wDz/^~x(IryмɘX/1P .cە_|vomڮ6V#~!![F4i s2$n/l9ۯ2-$+PO+^zl& =RSa:Ȕʒ{Ixn⊞A7/7X6)~Գ,[A#cQ9ϱvkƱqɻ/nn4*5休4TSf_GssxWYXفK>7eeΡjmn>#{#qڞ4% )6pH3I[zhf:QY[UBe?}m>? jMR sh.ppb?OcW 7/ !).QOBq=:֮EquOҸ!<ө)[t-nt~^4P@;#[`Ñ/b,j`x`zAt\f^\VlG RnlYi=4 h3lkFXp(ra) Jq gU&tz%jƯln}tRͲ]S_bj5uÈ:Ib~OxPS}r&$ޯe[FQ5bdp;\tz=b@JvvwuZtwcBBjثCG_KiW˳ߎ4D1z0l{j1]ޅ$%W@,ZPZNՃ)̍c)Bs"ۥ:) @E]r[CdƝJI |{uSCBYXCyR.2A1䡜#(ٸT1/27}*`Z)k ݧ唧^5GKEpˏA?CBD&T%=$%dk?~YK%T49v@MBf3쫐@m\Y3LH41j4LX;&tz@6Js Yn}f&J4%8NE _J_vi^9c.Zru$?ptn 6ZšԫVLDW4׆U@¯{R[s}0cM9fнLZo2k}o|+ssw9mΩjwDU1sFPix[,a}\w N WoAx@1T 0q}jQ%(tBq&!SJHtpٖJMuye_32Ԁ ҝ$-5Y4; oH 7S)g_)*ѬYcfŶtQI}x:3C(6bU֍#2(&@BD8Mq!eEΘ h,\ɦeL'KtsENLkUU baeaTB$4/TIϮ1QPi> k%7Zoq.aƕǴ 55VIMg,S0ym.ܸu≃[ХmF.;<5dI[WաSv3QFmX: (>L aȋRr&͊Uy>~|?b`p%c1+{y6P,pC7V#ϔ<ui$4^pVW ض!Ax^ g ķ $? 'bJӥN) L-539'@"hhI@dLf=UJto)g~eːoW16Kcjz0%waJSߍ8|X?5s K0],P blܮx&;Q8U<2` G[#-2 _N`]݌8I9peNF;\K.>@+ǗG{V :0Ui_`0Ir/%=ЊeDhmrd8Z6}J!Y ?VPLLX@9(W|Xhhp[)YIG\Nf xcX+J+Bf}B`!`V?YMͦ!իOfj?]ɴKEʖZ]6wGoCk$$(FXyUB+S:Q_$,}@X$4)/١zuԭϠ'"OӠj<߻vҵ;O@V;}~޷u[MAC˿=]8]Pޙ7gG`zs([s| Pe`:P:/qǻ_>~z(NnN1PH +nNe<@2)rivbȄضC{U +PKDk=B3 1F=iQ!ϙKI}q5|̓VGd}zY)eyeA;YN`npn3Q$C$cKQT5fY qZ>ٓW?_1ym$a Wrv-t[!"ӬĮ^d8`h=9]Qv>(JO-R}8%[zxN=**{惗vkէ3.yZ̾k3NIKzkIVlw߼؇onks2# ~Nȃ ߋbޕd|\vT|HXf9=ea5xO|,o?Or&p K%gⅿ= 1qߜ̱ WqR:M̞1s|&2].#KO#41hspN ͪGz%S+`f#3"1nȱX 3:DzF\y3Fv 5ӂb(P1tJ y]:i_*}M{Y{v?*g-igeKsO6- ,XFu(960haUIQA=}cMX#_g.<~R_݋NnBs-ϝjH!-WI- HpV @ǖVPEuB嗱GT{fbӰ7LG$z|ZM?/lf+ s4uux'l϶ZߩkgQb=1Y=B#y&Cꋔ}Z _vsLZfVf!V4Of$Ώ؋x?}x;o߻w־}f̟6K#7!xc˦ ҉ Ĝ]Oa4ըߓz\g\ w̚_d.LbbCY߾ش̄c&;ym,^}pZ/W>2%B03^ObҲR׬Q9q?^^\Žxiڲ%[KBxc 2XdŤ)Lt8ߦF:}Ҙ UUCg -cu6Cr𰗡xyC$[1h1Noc A3bmx18O9LarSN,!i`vٔobٓ}*8l5Y/ZhE2e֒KM6zG_cvڭ{dsիw68Wn?`IGv>vZχ160zaAo0vlڠ_Ca$:q eU):[|$6w/U%aUQ'dԨTRۘ+`☂kjlUv} ]g {t5ul vңtmPM3*rV/Y/>|eop QwVK5*yP=vyTg ޞ#_ ǻ8{ɸ Qcq;ձxF5KJ%'_k}Ɲ#uiHԧ"B՚uG!YXIWpX0f@`36Ho\y?I3pV~.V k*v,dkfXs sAaנQ-v@$eƃ`z?.-SYk,`":(aP֔deڶj3ѥV5]Ob1o)J_+ Gp>3ׯ`\TP1-[:ԭjWcW4kUEp+9q1I|oޢwͬFߗfdҒUzfzp7( ooFW"O='I.[E&W:\ؓHةZtqcg 8\X5^&ԇ$ܔҐ,Ey I#Ҹ~TVF-ʎihm -:s "l*з_6GTUn`o?⑭}$n8F)Q1W&D>kfomdWX}nWw&#D>9?1!sc9D0lTǡGfF =ʧab26Ko[Azɚs?9]fkr@˛~{ai(;|gx+qr׾{(l?y{ymGhڨ}7| ;fxݍd&{)"4qe.^7qcM+dHP`֧zv=̵qИ[̤0Y:1&2J$.{A@2[H_XC 7y#΢ݨ,$=\ug]$S=Yƕ: Y[6L_wnj]{-Nb/ '[ :ɂR4 _Cy88־U=o ؊S_խ*Iho8cHX!!N&Id;a(X#Aĸ=Σ*E~ _Ag]f5~R!6%^Cet߂to*9E(^GAr/Isř֘F9v{37 |*,+n}.*>ABR8jssF1y-Cb:E ?6Iŷ{- sFdyϋNQſo!ٟ*ZwQ &E;I'S cNDPkF:^ʰr4a&Hs1*ofGMB/Uҥ<|̩RlLHŒIAxcpWo$Nd%EZ;J6*Z՘gײf7f~eq:nax#n_v8cįwWjkRccژ͂V8\m^%4FhӇ3h(> |E2d=n ?VUCEC؊OB?kN,<%}>]oR178WoB@UoFHr?UpfP]d蒺3m06ELU5]^f;-5g7wT@~ą :I_K:;Pw;0jNI.M{8zt+1FƎ@ C#zk6v˦7d*JI3_CIzu~&ϲmI[N 7dE'hնm6hF)$µ˸K{mlB:a:80[eO_NI:Noʅ=tڟLC0k0gzn~ODfж>2Cp H{̓"zKB"~BHFL2m!K㝹H{{OUs6{ͥ`mI|]?}v75CEv8i%KO7WT݂գ| < _:_}{ B| w-!ԐYۭϼ18*bd?+!MR?fbT~>jB*i㗯e~2WЯdW ׯ$X7$$ ]CIr-vm9 iZD45ZR$o%Ȫ r}]ÈpÙ,z4[\Hq!?mȎSzhJ^m(@aÙ#WڐP*f)wή brJjˑ-KaYYVÊΜ0`|@nCH/`Rd~o=*6;I^?1|Ar2mQ>@`cˈ]"h vT(*3҃0gw}%5{iO5->|78K/v`ֺL5W[!R*q"5?HEi Q'gs|&wIuxD:ҒR?h2w\p7պXLWQ8˕ 8f[</=@|ME$w8o#0PbVu~-ǯ"z*k׆f{4J!ԷtVOe?Jwr-;\# W,mfb veO*%m=Ju:&1xL@6~qM!< ]Dͭl@?~!F.ɱjϸUCapF;cQMc80g5k2㘸x %]3JvCv$'*btY#d6{*C-Eׇt|Hb$?0s3IS.H]ϑ킫?2:5\DwVhIݛ֥üC6d%#g:,@*u=S#ܵUQ9>BhaKv!;&PP?1[$FzQ7K% Փ2 68(E.ϑ^.m-h 61!#x$x 8XL狷҈P@׾7zp W0M({ewVH4sfMӖ<(ogn];0>BtGH+lQxY17D-zg ty^#orgʚ<ٵ4B5FMցJݳ7yWlL9iNŨ =ڧR W*H7i{6R5/yS|MI[ G5MJ3Er*77Ф(LQEI8}J[@& o}Ω<\A'Tg)GMWB_ k_S I I4'}++^I_H$!݁nqJ駉cC>. @ ʿh^g.,JZQi* 6!KBmh3$/l$:e73Ӷ\WJ=[EɝJx ;H )MphXkT p8Ðr/"4F%1}Xnt672E|l`6fBa!,h.R+H` c#"sbU,eLb2,pw!nrLNpH2ҒnN_#1֠ +,f?|,:>GՒHmWjߟA A֞}ً|<-6^W6X<-7>nYVd]3h[n[MMآ|jRN` +Mh}⅏!6mJۍFR舑ިZFw2:1mTu9YJ8D%e`V ;K&` 3SM[e@=5(-:B$Pa $U:P%5[u tξƛT,}hU׆4F#,v6 C)|Et1߾,So鬧,q:*fvXF ,ՓĻR)wo84llUˤQ q><X8ws`v{H+myqjkUPfR6g(6(Jwܯ÷=|P {t/-ف+VBHzq敺8 !9o@D|I|L&-} W)3*Q֎<<-Ul`PR0Rŧ )9>Լl4( 9 0yNmz/;}%U@0|%8 1ْ17%07hL p$ټ-#G ,@,tjI#gJ:xSX̐E:#i{b-s\(#2` e=cv@K[~leHb)eA'n+²_dO@k#%)4\LikȔ֗?#GhRmܢqn^֚jdC2H礀S Y'=TF֛Zˡf ?UN@ysf6 b0W>iCtt+dRd@A_SIsywanNU+/Q Qfs]5f>oޱ'g9gzbagwϒ+O+F#9FbVZ7jVrh {*Kd<&23~Ћ++K3P9m_^<ݑfgV2)"e` 1HQYd2$Ug9pDȇ$NAGF4e9?KUVv%Ìy eHlO+FVM\F iv6"DH.+sBȞ+Uf@cvX[(`gmafDc}s]1grj& Ee ~@h)%%9stxzBw]$E"4k0)^~0 Oݦu.2ӯAƵvú;G&ǿAڷDwR^_hjW~b:^mmԺmˆ~T3Ѹ;gk^L]HoRP5 } $db$/nZ &qaWPYI;m) ݍ ]r:STQ< _Rfqn(wM0!Dqӎ>H :~N`bu}M K "Gp}b97PXF6zLz%H[gaUɏ$aQy)DЎzL% )c4!DmyuX\5a}kҦ[!Y ߙ&dúһcF50df)TEoN*51_$u\1P M\XӆZWQ~~3ͧoQѿ^QؗҫҌyb^l*6T0Φ)D5>2BDN]h8P 88BˢHs~O`YX*--zI?߄0 ,& ̗1)>h]bYq:$ٙhxVx,A?˔-Gf;;= `݂TۅUo<C{WҾȪ@VZIS҅jHS;'W*koܲ|B-*NH.,mb BsqH֒ŔcGfd{TR_ pGʧ5#_`E^N@uM IQab|^QҊ)GdzD\X8qV ޣc@k|ٹ]bGU _eDHhӧK҅] %k8uo>G$S.{7/Vl[d)$q FY@WMiYĥoaHg'WM.7U 4:KTˠ^vGGG4PR6CSj2$ RX4y%6fm.9xpdu*HZ+ ~D>|QFf0:&+ح;eTVM\z@$cWL}"htbS74bR =Rz.@inVm# ՗{[Tv:W&(|+I$B7.kC!RHG ^=$Em2ICrIC)w0dvR[R  UY*Ϥ[Ϟ}Wj!R`fQ+mK<J^G,ҏ+&BMd6@$1 UA]' h!M'.f]%cĆ>k| }U=?16<Bf@tCF2>6s$9&˂cK>?E٫"az7!.hEڎfDipABg,zѤʒуHn3ޒzXb9ٮ ) m Z=^}ƈ ['ٔUi$ PfЁBBw.{ a߼'uH)Lޙ&JF)q Dȱp⤚vpC0U}V0{Gy|}A2{%X^^U_!{ZF*`~ձ4.+AYeOn֟ү#K{d͜ "Q0Ĕ'6YA!_ro꾷g}OC,v6 4ġ' O9`ޡ4J/J&y u:$[,vn\_r ?@_Q7&j%]k$ {8 -EӬ/tnEM3K\^g 㧵MakZQN@\qM"!IڮqX֠hw7iOҒ\Oz>g{d[!U0&%4h;pѧ>y$ aa9J2G/)g*%}d RDn߸r\.^ pb-3俰!d43 56,؁~pp2[ E J[wbˑea$F]Ȅ]OXDpS$$mnD?PNB0qM}֭TkhtFͨI3+ݟ<~ u3Q博 96%J0P_2%9O=^ÎS*YIrƒQ ޘIkȂ8UhGs;lQe3(N&4USC9>īuE4g-}B7|цA[$@ mZ=uj.4Sgy @۪0E@3 H%$h:F1|DHZA|csuf}})aq ꈽV)mEIoAwXX Epm~ɢd&YuʾƔ_HyRs.ENRXlI_Oϯ*[f)9,]lS䑭^PoNmt W&O1YNV+MQoxdCK2K LTDd#YY}_WDH83\+,`>&ɃA=/XHz ȯ9q~`x@U,~W}w͊y:ԅT. $VOv`hVR)3`P<&vmtD!tR].X `#DB˰9)9[T`yR]IOKl.}kf:rs`Iе{/@ EMlS($͠&\ ˶Q W DW>@Py2:c_[I@Z:+O ڱ|)7dRey bjHJf'1|@8I܁O;{c/-o>1 FVy+N4S=QPPP䴎*q2v{P7߈&^P\;5ˎw!Wڨ8[81%) " @8vL.7%6޶`2ʥR> D&a_Z3MR0&k`Zk3ɭ:$Al#ݓ؁UЄYܗ$:ˡM6_.-a,ƞ0Eşw"fꋉ+$b%yI9Kse-mBi⌼ U q54Gch%~ܪTMBhlAfm324׋}]EK46a9ߣ zBY$rWN\BZ.b_L*rUD2ٳB"4j#Q y}pTRwv!V܍\+B {5lT}?6 ˊ2dSGit < `ެ;dkŎ*AJU4RCh{7B!g[F`R2Ðڜ=*>RnS'HcЃʐ<YzAz_25Dm\ˮ"}DJPuO;&e=4էu7^K3 Voׅ71)X}KAR1n+xVj $)ѷ!ZV{k5~³p/3.l᧏Bp 3H@sMPhU]#eԁcشݪɤ ?4sywiU!O +5b*t)]dϦ݆I$d&}xxcźƧIq)tt|H]1X/,[|Ġ~,'d S)=㋾n)o1bLΌKh5AYyjC;,3${KIv.9EVuiD&^O$8A(ZU]-΍&.0ݕE )3x0O$aZYCx<A.%\1p!<>:ȼ8jT(윫PʤE}Wn[g 3d$(P2@tS:x92΅befT21PZF+,iub*#cd-ߠ|+,l,E RA;V:\eXpi]\Ҩr!HMeT+jĽ2U;fX(՞J?=)9jc{5"Oun>RywdHb%k:3L: uV'*Pc $?؟`d 6Q˱ ,9ӊ D QZ]=B&͡h`hE0fcIo Ukv ʷB.* 0ww c]@1EKeMW^Ug  Ă0fT Ri:I$jͮr [:oCM ?s'(Z#1K~֒3FC2Z+DTB/H\a=}&N#Pe .umM1 3"<@U,#;5ٔ-I  r\S D gW8ÚzT C_/fxTVH1$~ ~v^(}fܜY3vyEsA 2$^m Qph{QR`/WakFM0jS)ٹ ^Sۢ"\s*! FiR.w@8!Q1qdџȧ7(wP B`^\|{jqw OV9~"]qvb5N {P[.94 -%6=M|h!|6Vhru>d6f~n@!}qKtR3 5Dњ2rF|}Y,ZפfB/J XUҗ 7Ӡ٭XOmX8yȎFZsiu Om)`5i;NURԢz|˥r'`T nf6ºh Ih& S.hOԛ HvHv$Tɱg%G \tR  ԫʴ@nEZnk8J׊e@)0csVxM$0,b6\gO1 X;V >^.ǚ=)d5Hoj6F 2C HqT@C 鿇|сB:.Gt1~lqdr$xP ڗ~DI$,e^,[>_Vڡ)'lfԝ7ƄTq@ Q.[ w׆*74Ȕ*BtkGSzoBSc!eӄf"|&khYp7 ho=TG h6KV%Hl}F_Sh_&'o+=&e?X ;IS;x}StJzuc_&gd*v]ٍcjyTEYp[Rizʺ\:m ~wj&SG4qQZ|] ~'~D6AHcuf ~v>#`(3KWAR\;a ڱ!B^x1|6 ER[#c8- I .܌$+Dy՛ ԉbe2rv'J)V]E?m()AzM`2A5Yh0&޿%O[+WMaD@:>wKs1>X;Ic,4Zy̦sN:uĩg/\v&ٻ_ۓQF\x7$ 6<[( &#v$tZ {{N4ӊh҆Q!){JmL2$ThA/ c ASy}"~gbTI墾0 tET{XU yAҋrڢHZlB>HnDER*1W qA._g|`B2(qHbxUY[=:6+d#To|՚X-sx 1I"A8 vK9^zCآ9 Ҷfh TwZ!1[TK61+Q 4 bGa(]g"A ,N#:x6Jl 3#M~[YG~4'lO OΖj*^SMZaf%GL\M$r{{+[¿`@…o4,Ts ,ȸT-Mul !{sy04|_tBN:;2hZ$#$pU UR"F_rmY@681OEM1HP h^YnAVPξ9+Hc 6,PE(2JR=qJ}v #k鷥+fI3|{ o~Yڋl$cˤ[>nMW[YV'hQogxBlҶHO¡ɢJ)P~E8I& Qf^dPlcJa֠J솅PeվVȑ#:zy+W]q3KEbM"-R?RE[~ > @z { S5ξDS9|'A_rVyK04h?):]Fת;O<`lʝU,q^.x+VIe,ʔz V?|$`E0$}פ" $OxQZkGTS]׸WqݖʾN;\|}!i8m2^HwACee`.\MOY\n'\USO_J ohbjz+CH{JVY؁TB4Q&Ơ,J79"&V,aEm(~|D;TLudOM9PF4ӌo-߂z{4ڊ;ռeàCS܏*Yh8q`£ S wо[H1ꂸp=`@L\ՒO@F1iGoz8=đ0:5 CcE߆|$X$?B;=]TZ\$5l$(U.CtZeM<Ʌ{L2|TmsoPWG(,?H&z5Y"I%TB*A+f+U)PhM$ksko[K kj:_b`FX{5o&n4*)I r^]J2E|nfu>홐"qլ疕N B>IvM2بơ/_F.=YK~V`YPuis7JC4rw;)jE I}t)`Tvܡu Tԑ~DM~I3g7Ag<8@-`F [WW&ۚY_MOzwi5)  8ϿR4FAɍTLé 1-DZmR@ t۸sJ^gS[}| Lb"Rs Ro~<׸^̈c~ޯ#bxLuD:|dD[UA}cdg:TG@[xQL lO▕r3u"i:K75̱#=J"3oʻlBC)> PNJ}kb}~&D<]^ MaV赠~ ͨ# j#XeRkW%$4Ybv%"s6 oa< gktq+55!2Ij k𢨱ȉjrO㳘hPt & lsxV i{s2kx~>GBRϕjn(mP>8ءc+E UFHX\N:&f6i4#`-r(>}q v+l>ݣ.==#1]hd˧d]k>:WrX|Vh7r\PښInݜ)j˔5sB̠ZvϬnTL4d6KWW0ꤺJr~2Zo6]߱|A.bv+ᘦS(vƮAVkv9ARRE2AU:Z7$^S;PO^S|~?{SYц_pl% Ya)҂+d $5\!TnWH)I#=[ԝ Rk䘀G?k֐4u'Y(g^/A@@V$Fe&вg%O7={v* ۂ_HU IX,?X-Nw?ƌcg_ј%邖C5]\=A5jծUSj;], 0JVs~#} 0*Cx׫kn}m #mK2Ply-`D*qM)~^1V\rЇ/Ќn#(K%^0wt݂{6 ~&%XK&Q5˱_%PWHLk]J}jeYb61\N56N|YWHzH 7QvTx5 I[J$;nY 3a6jg?'m_ٺ;Jqe;pgN?^Eetٔ~1\PZ'ҌM9f+gOaiAwQsUԺS%uBL^^{`"@ɭynėI9(D+9vJa{;1c?~4Gۉ9'OW༄٤O=\^t([Bl @Ǹx S{2W\f+($.Ny~[~v;wԡC;n3g͚9e\kHcdin+u9@yUգS=r,Qm!^ -|>ANɧ^~ \SO*>FG5Uv]|yV~ױbgt>, VFǦFW Y.шGNbR6ה\uyrC{gЂ37Cz6HBϥmߜ/d$P$HEZJ'VϿWd\b1u?on."|ڷ4[Jq$ rOvEhni O(21P$#/|ĴSʟ{{xZ;:0F0mNI{/?>HUP!-SEݲ>gN;epQxV5<ֳ;ѬN}thVv4T_71-$ϭ(K4f |txWnED-6&s /]H4Lٶδ'ISĀi/̥XlҺw+Z؀Tr;O 4=S^i/=-CjˬFu}a%"YS@X]|7)fCh\$s3HsKRٲ}E#W:{7~n"9[KSTU8l l6_}NHz8 -9ė{?B)wV,4Νs`mT7 ,J:9:U3Nncpeep=oo3#\H]}N2307st;?##ern~bϽO{>שtVǀ{a{;/85ݽǟc5MKk9TxӿݘQj;xV$cC1ձ?l|*6-ڷmG\ $eƶL i@XbUA~U РMN#g" Ȟ \?-7lf)ʷM}(A@UUCZ{n߳w&F9i.N?~lm[f9 9dArgV]hbBz)g5pp -΀RT_TiԢE}K Zh@e:Xo[i6ՐO&a7$gf4D/\iQHG1֡eZ^Xn]2j) %$jZ ,+q*dzb-nBs 6s~e} z:(.(;6ۇ%:ڈ`s%-mr̈́J:NS0{fK:y*_Zρ5[^8wӲ#5|1lF){U:T?݋9_B <26O&Q4ZxYmn_zakdʺOa׶/>vmvY/t%hQ)yz2O/ S3(~#S**Mرgߡ;VOXϋwCW6k*!ԅa%&gxИ/\vх] WS=ӄu Wnضcۦ\ ``+ % (=B^/C tO)IzvyߏfW=Jk)lz.ߺvl3?{s3-OԨ̭Cd@,Z-SWF^[-^.VD7ØO6w_T~i? רZxOU9,̂/z|gl/ MIoae/9J5nX DԀ#4JVX=1Hɛu~%֜%ök"lO#)j²R ~y__"j)IMj֕kYz{ߞE_nx!dމ}\w:#{+"g=3Bt|CПRrߜԢ@lt-՛y!ذ?H]{ތjtڀlVͿan= *X֞)|·zDH4}yt%;oz i $Z(sfck-UU@7܇(dMMxBW hb,n8& ϾWk.r◪"om8RwT\)]FMtVNE5hPlGiGrSA!b š3NjUV9Ҧo:/[l aF#²R|~:wk^ՊcFٜwm۬Aޘpmu5nl Z5֤^L ;Z,p_Gg97w"mQ9E%N|\p똋љ޻xVV-TVQwUQ@4V>*9^6:%4+"5XtxŴ 'A.j,EԕW f;x5O (ʎ!9?p.t/ =S>,JF;L*'IX&7j@O{Wbk˺ɴpT}VƭeP45PBB4t0. WRz67P~=ZI}-(SS9@_S(8n 1aǝʑݬ>ikYd7+ԽGC[}.lli-™p5Y6~sP]|Нc|WB9 vv:X"ěr-0&ik(xnh:wθ5+ji;?Ŗ{/νQ-5$%ҩaصLю 8WO&Ca獷}񚥃\n8j{U[)ng=O_X*ŷۜxWz|Y /Y/lsSGeV%\l/ '5jX&-%5hȫN³ϰ9uC,ݘB")̫p RiL^ >&ҿFQl6zbyxhmN[A0(ENXK@Wxq5.TQ3g+pǕջVL{Z~B3Y[@ =@u(6:C߼տ"zUWs|@3F])e 492RSP6bK𞝔,{STx6 /jbYVdm3 }=64}o#:=kz&ě@YY1DVɳj*d E@DXihs?޹&ShaKEgҌtA B T޹:?LWàcFzQ[\zYoDk%;:;;9:X}<4IY?-A~ZW0 {b.+3Ɩ~b6܁VC'fum;[B[y5dyN]4O0!•y"!iC~bwC)4U#'6N)F ʏBQ;fyWJ@4)q.Bz/Y@UWb2wO9v.mn-id=>##omw]c)XWgM:b"=xJNo%zYsfrEKC\YR r.S5`|kVPmo,I@ldk u>}t>=/`4i. #ՄRF_k4 < Dr8DMS{ bU1~§.(nt%"N"\:zuwR[{[v1B0RԨ[Feg EO?AIIJO71k 3.oZ ¯-c,b/' ЕN<ۗ Cj.3_tyʤ7pƵj`׾7{&.]Qt0Q|c:`'單^-Dm&j`)y)]CJ$J0~=JJs:Y&|T&s.w(I n]W,h0F"3.?+eX<)90XVOZa_Ʈu}r6FD歌[9[IQkHS&BA*Ia$HRO,*<$c&hLHY/Vs?NSe8\^Z`lTkMf\&2Լ2?VtVW{Y{_퇪ݚJKvIMTg-E. J~qC9Jڠ)u|FQpS9VU`=쨟ƺl\<$)p9;FC= W2v9 !:MC2!Cgakםx5_46wp,IR&|_<*ƀb׵=g OIȮJqǔ/ mmQoΖv;t}!fZ|G>LYm6"4K{ *)MY)^FilNtd(^,)]Nߺ/a /"N|W dMQ4[GֳNhui, *fQH՚//(NlJ |8Xcb$IBWP0'>@dQ'#[Yg=q8z }C7e%I0ߝׂTl!Eׁy󃉒E,؏ӟhഒӢWSJ̙  }I,hlck>+l. DHw7ʺjΧƭ2O(0 d_!LF/S9 !o1{oe)'jP (CV 0Tbr^D+9L 3#KcEx_tV'DChHYfMئ@a~^3[\_ .l\[y϶` AW, C"7d׃菒y+̿_{VgBI dȩ2OY>jÝجT~F %o[&a_&~k_~`ٕz*7Y 6V6=$N;菴!٨/q/@:_gj9؟|<Ftw8f1-ѼZ% ۗr&b#)4ZCOZRj)xbJ{~x, Fz{G1? DjC=SS3giֲ͟tPFH(X׍=}m2#ՔA{epJn֮&ȥ `>,a@?#fPʐo r:nrmFA'nBOҡ4IItKEN!r!rC1י\10ޯ &}L VF&:md)~}Zx(Sz!Yy_%Ԓ;݄G{/ZhFcN>Ie;ܗ˔dsTXZ_habi_)5Se KJ[O[r b5IPĩDdmѝ[Vʇ1":uE YڜoZ9FMTtV@e e \Lђ2gy)+r߯&RAS> 4BMh"a TP8:A}`᏾Қ>jmnE6QG90,YKOEeuv\R,p.F}C$<9A>eYQz-x->Ikz='tD0<$%a[ N]*mZښ(E) 3eeM O#?@)RѝQ>1(wE:))#D˒YtXs +8d#F7G ݈Ƥ6ub=JY n z )L)Rdŋ\E S=Z5^8`TBAJԼ*~X.Q?/m^`2} ˢNJdrH`T &4cѮ'0d-e*A ΋?}p}P-4NI@ZGן+*`Wpion%AoldEi552,pLu{۫Pu]e_-p}O}]tP݄,f "]ffP}S^^RO^ oW(j1N*A$\"ӥe3c1PIFqjyReC@&.6*X| <.b]בn$8ʷ)WI 8\fGSZzŎڨMX9XT$PQ|G I( `hSbW2NkHM׽?=\==Qu$ca\g =ґ2lq{,3&W9C$n(?V0 [y3,IY i XsѨm`)>[E 5$Q?!MM݅ DE8#+: -f?P17ȥ GvaZe}=l\{,&y&OgᤁWZMݴg uG9fW$Oޫb"D A nCƽL&yv!4=F C߆ˆ)-3cIEeVa,12Edu0łkf og#siJg2G9ᮂc8)龳hlks.=C%[#HBEj[@ ̬s|a&VD^YY!E#* Bs"9ZL sׯ-)Kyh>hXFdrJzĂY87wC7k5!/:}Y1@-C]. :zuLZ<5 IQp箐qkJUڪhTB{HBQ‘0H_=UUmX6X,t| bWpߵLc_rqY|nŋ [l}BnԹAC>BsLBAɛ~$zUHpD~vVLcO25g5<,>RcbHyǀ173$-tHp8ӦyTչfuCO9T S3p)PKT\*fDMWvk<=&> Gu."j~ihnn|- ohh$+,waBWjpX6#V^]mMAdQW2' "4RSc^ҥ{yXKJ7%5)VNf Љ}fH-|謜nF30D/& 9%z;y0T8_UЬ/8llj5T7ƪY?kI1e}ku`_A DgX`)m7ٜMbeRݶ%FuǞ)lcН;{mfv a- m`I^ȣǖ9Jݮj{TG'#ڮ@FkrP:U g2vVhTm5d, 8oQj@u©ieMMEGK*~69@HZTy}pi⮄]X[#X5h+ MY~ӱ]aԢ[(%AU4e0t𙢵WX:֨EUiP|r-U[w+#$S\ZWjit^ntɌTps>t&けskԾ\` ͖ 2t4j4 H֜f=n|iIFZ}H ϛ./dYYǧ '4)TNRD]/7|][!۳a9d*J~fCYS^>ZGb[%D:dIW~PuQ$$QK+ϑ1]c 'M;j$>N#kQTå1_Gş$k:I;ew8wCUC1?%U kME IKR;)qs'2K 4f:3Z7ӳԓsO`UºZ;ם_q okHnQ3XO1>D9CF-W[ZtM,7u椔C#8Mڔ*?8HQaJo茭5NBTvsnS ٝS26#A6¡XlD2G7?;|QhECig,%ͅTz|3+屬¥q:FfǐODjj:MNr YU_zj5b6~f뎦V yvZj19J* Ien;t@TjG-`AeLYsInn5l]ZMUt`p=1ǧhai!Y~KO?w`jsBgVM̌k(I]JD!i$T X" cqJvNJz [U'<""BfmHOnK4RM*xn/N9C~@ E}ˍh90$Ξ2Gj+5Fqfdӌ|^%]YK"Bof w8"IZ)?&0Q\S"&4d7 vߘKͮ#T ȻGE_Ir~tQzl,C>͠Ew2P i8~b:9] 4rsu  ϡyQ*%̀rQ0ڈ2iA{bj~i{CԆfD!wiJeMHgh!2lvD!#:2oJ]*s7UfPդ@lǞO-]ةBify[`'NIPO
2wuwtq@d6%%b5D$גtA U/17vA>f|lq^ f sF>WCA[^}dϔ̲اzt@4P)ټx03xl~xщTϳ*9vXk5LPAc]8 1I$DRg Hm+בsNt;Q9H/"R]@ m"lIMd\EZ;f#Z 尅A-i-jw~ӬΕM=U `FݼBJ1sI;:t [m}Ҝzl#dJ8 cy |Ő,ʱ,49>Ҵ^3X(qxf9oK!aT0F1pm%;-OYzI!6pfDڠ_2J Ym)%2hkAvWdqQ$H.%!iDn]ZM6ŝ[9݀z}UslM|, `l=^be>w|{҃i% Bn?{SdE~I]bK U3aØ-i*/1`#bqTwŒזteR~4QJ6e$:١82hh CfSeQ9XB0L⍁H)"q[HXm0 _2bg,žE3l1?1c-qgqzAD-`ؖ]K0H#3xE qq0^yߊ?T1mBihŬ&4%EKX>a k`*_ ˯j\xNr?Seql?.ocҠ#!Br?!&.~ͻ~и[؞ ֪%S]wm}R :gٸJVw&W(>tw9Eetc{ j@xcU<0? ,^E͊8۸՟}jv>u? h1f#8/P-VyJ M:E|L%(w%cdP JG[ v.Z2jѽYP|ܛQJ)џv1ctv櫑pqbUwy,<2k*&T dmXPЋ~K(ڒLSj:W, QnYEH?MDO*^"W9Rȕ.vcp(.FVzxSR4ĨIp'3TKݍa X˯ 4m1s]m0$v42my2ZJ$}H_KN-PYЀjCM}ceP[EM[ɓ-_xHRHQ@'݂jl(ߺ }*WYlĤ&f^Jǎ'0q>E@f`h}ˁM`gR@BYS N*'rtDd,ٙ=%P=!AS(KFƃѪl4ʛ(aPЪ ;H[!: c:ڇECJ_xc g#:qVr.)x:p1MtTR>)R>MTudUU|\;REendᴁ]\rqV\śwc {sYTxnT*jv/Т=),!pEfǥVxmd0Z F wvI$xL"ǥ,vt538n"PU^x&f[q 4CjߨI2/m\Q+aOIF-3!wn_}"@@F/ K&A8LLQRp;K) Cu= KN=^Ru`DI9af]_3CT l3.mGT 닣=mMdy*֘3\9L+Qr>"[VV>#_~V^F$e(Sc= yu}/709_0e&So|lii鯭mAŤVkwoۻ5.Q{DF~ʶsےSMO?31ŋ`SB{N]3pJٝش Q!* ')(iY{ZAUgsPޠc2VurN^IoA{ [56.cƏe1Ʊ0e2Lz&߅bIrzlKU]Wk)ǰ:w==2Ctj/!Kfp$ST_NܓҀ)}"ںLiL5ٝDhg.%kE"5E|*´+| V9wpHi\H9Y NtCS{~8(^j1IdfǝfV|WVZx.fs'5, pØo IB|OG^X3Ef>t(}tcB2XUfPS2֎E1.FZ@w+<+gl.D*=(>ͬr4\j>h#g}j!-:˯[ c4@Fn06B+b8q Nϒ lӢX$Ь:Zg:nu2d?8?[X'im/[J&N-H5[UGy_y<8SڽN+Z7rfQ_ gq&-J6xC.z ̚5ü ,W^/kӓŽi7Sf8ooAliڂ?a:=S1f^bU+W8i#œguE艏Ϟ6Lh?֓7v~$!yIQ[^ o]Yͺ ##̸ua9|ty=^"g'_y;Zu\P3oDMFfN9c]|Oy 1qy0UgQާ.Өy #{EPPCwF%p%뀉M3T}Z5N/y2'-^/F~ ǽ7ƙ2rwij6t═[8boP31е;X.Yhr1txk0aDZ?93fT|^G@rOZG%P.כN,c uQ]Po9l ґA@t454χ4*/:`)0vMrZQ_zO?o_#4)&6._v4X)ܦv{Scc%8n33K+k[;ȝܫ;8{8FҜV &4$UֆڊޚOdaݽ=pC]͏-C$0p 1tnsj~q6٘zNj=-Fӭ7v'B?d=zk \Ja`YSfTұehx¹e|>WGJ ޻#Wv2H0p4  wV]#$c|bL=YF%:duU"0Vufn!/ecK-3E2:Y|>Al%ȝPF&RsubD(+12Q fJݢX{X\5ϥbT^Kr pGyʼnZK )*#U8i4 p{K;;˚0W͡Mq*~5ѾMV-t^b8a#S6K?6 s1E#J"۶Mk ¦–Us~zfL&/QLZ]oCaESwӳop"Nἆ2XFյ"Mg8Gn=pk2M+_5^D-rO+<1:Gkz<ŚL^;P!џm(Ƨ l֕MTlأWE $%Isy 2@δY[ٖZ%Ƣz#:`yZի=~uX)r@-)Lj-%/ZV҆"5+Zr?;3vW%PI`!v?R_Һp{q7-F#~UMlnS7Q?L'vHȪoIDo:3 V4_ q~wRR/Oh^IV5welP> C:_kyp0lvQŢU|;Չ WCλ:9TkM:o)Nlю҆'тC$yEUb,3ǷΏBc0|_Q s2%u6 c;{\7;v(e]-RFF&V 6[L M8TZ?A͚ lr^hPW " {mSLmG/ y{oطL&5t AD,$%xTC:vKC +" sřlɘ4i3L|6wd%n TÈ/Yڡ]kd,U H Q uQ%~-x}-3F$e o{UYL lWKT;,8du&[.`0{F/]֖}-JJ#]Ryi xxѵ\ 6JU,N m@4o+Pϋ6O쌳Eri lغev2N&nFYj wkVdi۵Ǐ2CVdήO5/{&̱h#.D="X /|0ƪ2^ }֯z={Uh3X?l]k ؍m.dW$;{G&0ަWK93Qq!9J >L;P'P(=Oꘄh&SD3ah;#s±A 5!i2di{|ֳZ9_FPsJ^ә)J䑳0q."mDERK>_NWĎׄj,Q3twF-ieCL  Q w3;@}/B96>75Ԃd5pӾr7$,WGȹرK xu;P[:io K9I-Al!șxۺ,ytʱF6-x5X4^ED F-!ɛ]U=oH7e`gm`|oMHYku4"+Q͛54AJNN sƳFӷ^1FC9s {.M:na4G!KbN`Rb1Qa~$ HdRZnGR sS"zZ3, ^3ti]|Ӝ9,+5^1J"bM߾a,e:D*#(O3H`-RJxɌ}٤i*χD Lt*c~ISVzSP{^&4Fbeg0J-,G<"桋t7q]=GeStiaYIZ,d*{à9. ZL*>aKoZ_(unn=VFDwTɤ>WӥeձvP_(yQ`MwsIRߛ#;!!Ŭe4 f]Ӑp79X)/ǵpC|e(dfdN }ob/5  \⫬LѢ+.ClkaԴc#9'&ƛps9:q%)3vfpx^ ^ =D)ww$O6X$- ZV Xy:ׯơ"_!f% y#T^ظt!=5e3@P(T~OXJ(jCs9[*Ec)yt!`qP+X8~Yi*t%Vq@/]k(a gqbz^krS!+_b1U'?_gR.q.#_Լfd"&Nem/L?\+B1%ywU 1_*Qʺ&m| Gs=NN\|RE;i9&.* F6'< 8{T =+ x=B{{֋zWBoʉOܾw|g5|Ciƚ$UO i(t`$&-kڶa쫿jK.\T,7Z')x}̾ ݱ9V|Jޫz!󶞼6WCI]WV>LpENO)vv%-8hף1goVǢ✞vUW,_wW/g+ >e ؋0GY t!?_HeK)z4 wv [lu*}KƑܜ(Rg)Qu]ldnK< (r DqYx[hSo χ{l0:Q(ZZ""fZ)OOv|?C;zms2b>lכ{\ a.V9 dI0{kK KNȝ (zKLHLpR܌w= !%|SF}`(a7u~D.sJ*p$^zEP*AMbͧ8Sؠ_6vt-Hy]yEo>natcoVݷq%2e/m!Z]/ln/ IfW&TAdS&͜?JIE<m5[* _qJ&27[ /{sQYc6sy4[;bI\SMn_ $${fJ2(CGOxokNeRB>}J-,c챓䛡^Ou[^:m{G|Z[%yq&["Dhz >ddQ8U;&g-!y 8KPjmwD*X5BenzczSJPTsBdѶ-V\nX3+bm(Az* y Z+bwbvt' z'kt%9^ѻf7ƱQ }YO`,g?_҃juŻjW ;/~~͂uh^-`| N#IƶnNjz>MFn=wރݺm0XFzt'bT|"0񅄃E@F$gdege$}1&5yP܍S5\~;<*5i;)0b%ʎ1NB,$uB X<"b*o? v4:ȋ߃}]2Y>+~dTWVBۙϹȳM],"3.Ƀ@DJS)ˉj$s bc_/@p@Η9)p9ޤ]E3Ce.]m_9̄2Ys#r"Sr@aFhآ涓*a7'7^*-Xo#w@jھ= ).Zk=W[wbI``b6^&g )5]"63;+˕qUf~u:X6zq{4 tto">NxY) $mUw;+oM{MԐS଎iY$fdW|<:oи 7'df%feܐdiCb3s3^=B e2U*ϧ5rkz ScVY6lܔ4wW0o̙!I)lf)cW^gn{-hzvŸډVXxj}DE0f >OʍVgLOM2p':ltM,:t<[1+MacIE'6d0X"R[ȫXnF5̭»@YqF{W٩EXkq^MygTT` m''xiPc魇7]sQ/^>6H_ەBRCtҾ>")F7}rR]GIKԨ[^ɣkh4@ugn)8tڱER j<n=;)c;|ӝ@,f9f﯌kJSɫN3珏g/ %o>%,:66:\ rn߻wʾRRe/WoϦM>xݺ9x6EObS=C7\x  ڃ8o0 (X(AGvwe"=>?Bo|ŹcL%)dѶ6GK44c@A[5jm%+V\b鬑zKɮ jGSRWL>] tέr',xv];wرe%=7QI,_ר-Ku#Iȹb Xno1z5tƎw-l#(fYUA:xks87S|VV3v*Iy]{oȰ6bmG,DHjSs.s4aczlvZr }OԠ-Ŝ%ujU+" 0xn>vk3`ҩJ&M5i\Zejn5o;cܠŧj \Y IVu.vcCx ěc5ѪuC9m XYBީj& [plP(IO<ٚZԉڇ-tN%5+m_t%1ܺ64Fge/8`z8%g<h;qW\;\_Mq!Kʦtsu4@FzKN_ʴJ/S˚؛[3*p,9 sw_`H死8rԹEAZyZXJ̻v:d[npqb _Ux ]imaHwR:+ =7OTbAoW}MNz6~Rsɓކ)ۀtęI~uVEH37rܩ~q#ִͪv6np +cߖ& oTW$`7RghTbƳw *ޭr_ 9_S)Qs0V$AAa0o| \}RXUk~8{7l SwX,D[%}fIh,JێY~솑cQY5@MT7ZP?hR%ScOakI>ڧ@5z6T<[ˈIcewE{ğ(?I=S'Tպc"egJ4 T[TEѭ[h3֒7~[Uexbgmd8NʉrPԕ^=(QALa!YSIji<+K*>?iJ}:r̄QĎHo:):H :WGz:Ot\J18L& .=X8x(Wn Uz9Kf?iӜZG ֯[2x7GԦDQ6uz|vsӃ]dr/٪[VfP?a<DN65ֳ`)@),n ufNwҜ3N=ߊ"3Ǵ+H ʟ?}zS?Ve 5qG=a'IQq…Z.k" =,%khAmX>zxԙ`Onܺsϟ|ENtBuG^m.`iLH[T1<%gmh ,ԸjuC_~ ;dݝRO11 5,ws5j(4HG% G~\OcnP*7-⹴2|<_cx;zj0Um Eݱ)7f;q\$bӠN=HW7 M:-_7R4jmt}y^hխϠZtMI>d7q{WVKk?LX:CF L–EٚGsHgsU@럩+¶pk=5e+ EE:T1.Bij5=xvbnd[CHT&ɞJx6/Gcwɧ:2 ZUԱӧEt|I{AjaQ†0̑>/3sOԓDcMQAwvS'g,+H稚坓(9CkLM)ҢwMsK~uv[ 6!o5N-umwdG-7гjz."=ՕF& A}vV)L'iʁo쥜O2_4TYTQT[JQJ.Հ> ́'YZ B|J2 \q&Uy b[ߚGV,Q/t=2nCB}uc0f ̺IwܵqP_G@[)d 86: E$)NERDC,0B]iA!N g"!Pd]jk gl'3)˹02zEfxO(!ѭg / Dڛe#Y:q칠ze0nz| u kGgYwP^(3KEO1ZgK\*tFZqnVt,A{¦U6<7Kz}٣['se syzY ͊ϗɔνn1bBNM=iI[l*gE㦇wto􏇔Pe?Yz? 8st&s)QRI*0~[jDc92b}WG$>A?·H呇sG(2e(ܻGPq`tILzGpM62[t^k;;`L1KI4{3bm"kc$6Q@y+ڬa9f[>;U38m@_;L5 #Eo+ecnXd$HWf9;f8vkWn3r^rBqNf AҿWՠ<&gٴj91-Zjњ;X^M(UUPA*PH2R(o[+>-Bpm?)N-8-yGZNO5Q Y߆R?ּ?y-Tp5@ˆJND<1$TBqpHer\QuV`ۆ:SH#)R2ZBןȤD*3DV_L2v#ڇ5S<͆gg7oJi>6 ;3|$ 2I!śYpc8ׅ 'ݑ.+-85g0xl!2Yͱ`WCF-fo毙DŽ4Uj;syvh YiK@TXz=nb>ZF&,^:O8ݝ0;ctѸc`ۉg~}[TX-ڦͫ88ްU9D'!Akդ@Ai]EɿpFdHA;iwe4 7drlS:9e]w /tXF YFk--@A<нFsvHɃjBWoOҔyS*yU7̽2XCXF6}a3jQ)u?:kC{떄Srmt&66g巪~q(GHa o6k/{Uz " jQv+sx3=%efXj3ky4Jnci╏|\oHC"Y'īU=] /n/QeezB@Dop/Is%u"cc4%$Zz`F&Rv"7/Oƣt.?vۂFռEެGB/2CWCү20SoGk7<\# >ƺšѳHr."#'l. 1gV]HďiWJ\#wC^}padU3(%J!:;ă1R1$ɗ` 6F*l3F9YF!dJrJ2ي?D{qnP x{HK8bjO5$Xڻ\~rh%K<*11H[[}]+}`U\fIy6D~|“8@?V@ַrZ N/5L1NR+ᅃX\3) m|765Ο Ě)$l -+kgp/3ZoOV Ѷfq?7s%qhApRV|3ġ6 $<὇r%`l ẜܛb)a‹"mUv2U]$yC?iɴ[1kZyN3&X&N^ 6jT?=\-%Pͦ=K )ϧHjQjmI%qt+y?\CWF-㏂T8tUhЩ!oMp#גr:E6{.8$֧6`a3v mcU"Xrt^=( i郧%M`zh0Q no{kO"3 v{o;T|ˆ: YaB 4(% Kzh1FaZP8=~RHNXm"PqxMVWJoUj91W8 fBGx<@J'`ks4VۊܜtzZT}'yd8gQøj 8ϒ1BH}aRmV )c.}4*,%_m&w rOtڜWB f"X:eA541UQ $/<}v7== g4VK;Bաf))h˱sm[obEӄu⚐RITlF *6$Hj20e  l'krU{?lT[rovd %=k G%gR@O{샤GJM~_C[<2/2)3j@ ֋!Ww 'qrDYCX~ڞcо_l@":MpCF?Ǘ{{ea(Elx:4}2<4j Qr.~/Kg%x d́%bipT0.95>n!P4" 1i6E eJ~Cy$֦dS3թ#S/ݧ%1(R :qruiJD B*h =JSWafP$&!VnAy-`e@#-|4eޫvg& ytHTemVEe`c|mOsy'|T/d|=c)Z²CԄ;M1/x p ,2ߎ GYSǡ5Z7a~=U< (s -܊%;,;vN҆](nrLeS H*gQ*No3JE!!A@U Zj1[(TaGKk< hX;YMf-΍)Q]@ρEhdU0i5ל32 .B[VkErl\ڨG&~#.N_P^K&' HRMm%?FUA4*fGyoO`&rȎlX鼪V (m-<5)\4@#5[qc$ݖv4qUg7N':"r&ِbF" VѢ>G>!@ӏm2&4fP6 W ! [spchWuSC[)` !񭁥46۩(IHF#XFr bFq)k|VO 6f xl W,Ji*H _^~2`iKg.SOVjوB >rF\~0P/\U$2|>WtlД&`cfIΎFry@2%[&v* ̀A]WLp͎&#DX&i{\BU0;,gبJóެE4#bt_{H *31v@c#"rCw33U@iqOdK%CK,R:Ih1G58zw47q4p3dV3e ~y<1+D[Y&Vd#0"y\DA2CO83̃uwp*&s&o8 icmvy&ld =׍Cc;(DId'ЊK{v<˜U\\ &?K`is@yĞ|e;+# !{rbic :I= W2S$L iMӾZn5Sb4mq$W8aGp!*ɰ'7! l@)N#a@%Ւ@jU&') `<mMUJwy 4QOEǪ9見QDF1Py1ք_lg#B@6ӌ7*VPu,feSǫ.,^-1b'z?xI,,_,8ǿ͎8O-3;i,,_UvPg!VpqVezd R$iYwI50GMMZEx1Ah P ]3Ycs1:2X*\(> at(Xy—Q$O8f5ivX Lޫl2QڮC%nIZbU`-'g7DE<~%(0/ nf0&;.|Hϊq_u_oLPO,sf"H@NLu+Qg&#hk) '|j:?y!nVږ,Qhky4bDՁIG1-p}uHn%$~46U촩_<<8 *@߶Y ?|rVf2x Ü0{U 6&Q CXN?WkntUXm&,ozIMAL᧡ -.7>qJXVQ}LԗM|E0 o XsѬFB?60JIEZwR5&\` 7h~ߴ!#pbnBO7+7ň{|&(n |d<πoj1DQMbs[BTl<us+^j3O{;޹IpQQZz 9"HBm54Ja-J_.e4}E2K6⺗|YueH'‘lX}d"y/ lCWVv`kϟTdd: LקҴH@eHn gTn@YUoQkEd%}߷Hpx.Rعc 8XQ51&+M).wx(iGI:-Ǩh~dۼf_2XAĞ3]2R<u(d׵89"'"o|)cB9f8զ$s6 t)N HeF6MHB`&An=q;q)Bȅ Crj1)cnI}Ic5bɏY߀1SN#u0!Z 1h1ۀ5b#"trM%Ҳa|/df2ULg.' AR;=T6-IJwkQ!g)͸jpua1&ewD8nԳHx6A;E 4ӛu# e:~dQlMB U_ɍo-I& ͎oS(3G?19$Zqr՗!".:L0 &9[9jÌLF\3Фk,oΊ 2QDԀWbbvK3R-NGMQDMU^gy ݊m6ߧ-a# +h*2GA gqPg3rmV|\e暾k$Ɂf!zR oгdQ[sƉ挆ت)~kl5cM)VE:5Bvya_O~s4๕ ;}Ў-&f0&R.SiY#oQkU+x+] >8UjmͷZo{۵T*PsZ4:<}bOIJF 2kG!cW<ҥ;+;M񷊔Z@U>u۱GOXCbjc~;To*2m1Z燔 F47YA|jRQB7s:Tbe؅``/ib) sa ~M\o{Nӡ6*l:dRսJɾ>~clC/Ҩq+ԍJmo4= E6,H/+#_5Ԧ}E'P?xRFո]6Zbv0LuY';d (_ ٦̘}VdžOK.'kb) :!0XAa۲x$5d&~^pƨvp4 wxaH0cDIkA5`BlFXPW璯B1 >Y#G^O ZxNi^?mp,|ҢBM<25Q& $Xx6_%:@/K [R$%4<)Yt|#RS*izJ/D6g< VOQ|21,=#٦2'J/ lJ.Eo%`zX9'}*j"6S!q)\'C5dw{0pկ+𻖄z% ;vseDPw$Zy_^4-M+md=^qKܻL~j?wӍwt&c'Ǝ+`pL xڊ Ѩ.L^:pz87ͩ#@z;ȫ"FLOSp]cI+d-xp)#;D+[ }8;VşEubZ+m/?ӟҒovvj%m7Z77"@OY@Ԣ.+$GFO!qv<!"ՑN Q<6AeJ⋫-.2DдꌏX-:] &6& dt_eWc쥨,m~՗ Iȶrꅜ-4GqK^nkM_OHzw'}H6yZ{h|4 O:fbne41MnXI;6OuK)%]}WvtCIGy:)M=C3~ ,=G]سvx2o+3j#^ڵfoLʰ\Ʉ{83B6‚m$R.w;5 99 +W H:dZiW1v=P޵IZIh`Ui.Zr–rغغM1$y'kS~cIհٜ2Ǵ;FLޟδK"w*h2aF>gװs΍l8~}e$il4מ_){^sWE U0.6(ymMM nM%V5a0G]&!CkܓY\6&XtHX[X‡ALyPֹ vN{q PnzMjv_G7"(2FN<o b_(x)R. 5iKOZ_R=f Vlm0D!n};:1>h#hc~+ĩT'񋘘1خggWNy1h('?k"A~Ӻ} }Y[% #Aoԏ' A&1t,ܚ/q0 ꝰF I ,E4B}a~ynnS&IbM,R5r.M_a5S̖#"纘 \4-jҷS&2'ZbizT_df#,#8;<'wn3y`~{Ι]ܤeR]Mu*|.6 ȽIӞ#LKSd+j2U+/ sfB&Nuuv󳉍` ?H(VĜLwxa޸}ŭ)z:eorh2^-nasI*<}a͉f,,W?[9u.^BNa?nkouLjqQ#n:X hn 8ͺ4xp-g6 #Ico#F@6a&,y1bX6[&&l>2Lir+_I9Zڂ؎'2@ ffNH 4/ǧw@Ŏ:['%QXQ ;;hAT_LݰW1жc§| x):XOvh.wݺ^ >EgE&RZs:7}i׵G@vF5DsE33vݺu6hs:XsW]Evތ?k!0b\py9 qdMA]MИWh7kσ?R@_VfFjRlkFԧ;Hx_BYaL\f%K}C`;FeR+u 9vkan_9^b:#c㯫l58>koffHy5{<ԍɈ==ʵ5r-ԛ*ef ʭ5%lD LS(V1f&^$|yF eFwӻ8=9A_Qti=Y;]Õ,#H*hwԿ)=ƌ'; )vaVE*XTGQYl͵Sh"t!FnUaU1gDa\m\sNz&3T {Iw?kٰn쵋:Hu}3ׯ/.l` XxmNpseaR~~گ+@ócF$+=/?5YW΢^RnbFjΖ״Mqlм>]l3T݌~P]X.\t?{r,j| i9u7)%EPw?7c9ޟ/Wv㟤;zDl|Zs  ޷uۼ&]2ZCf2K.O-XJS% ުy%tzƇo|E?,4_9٘765-dt2k7d*gF$?YD6 əGQdA%E=>7 HU!I o/20bʁc9anuM|9Rj՘y߈wkj\g;p[ܺyܑm6*w{ɻoVF 7XaA qqBJ:='_4gB #h F8tYU,;g*UA훿C~Bx4k<^˖(1ıcǎ=FXn5k@u!ΪZ֯_6su? ~X]B\AU6n&ZܸzveŌ˫tQsWߴy˦EnlPx\RJJR^Z4ww\\;ep`7ƹ<ޓw'!=3#%.[ӻ0UKݏOIH#[ڽ_>`XڧjAOxګH-7e`yPmOkic-}v =ςClgwueO~Txͼ{`#ok׀wZP;PA#siWK Z8zױπ]u27tJyꃧ/^<rډ]gjVVB%]ga(|Bˍ>}OiףT{ڛ/ooZ<~{)w6/*kGwzɱKa6@"2jgSzvG,Xmߡc^Ub/sgN={֥&6jӭ;H+LMXPoV& Aڧ# ׾lT;@DVMZOS[{{m^wsUz*CDE{QDoo牢*0r8vՉovl5 |ơH [N MJlKdkBЦayv.#ߟƦ`k>$*6ژu(XӁ4=eƯ˛UEӟG&]tlN8ryB|)Z_9yZc4Z*|3Ш ͊T7Igw怢>F_;tl.֝7K:=I6n'A2M[kqbA^јW#nЪ8)'o]zon͗ߣ""G5Rψϯo^hhe:FylӱgQQ_&@\Vt(I1AGggjБrWci=xCfܸݠS؋DZ+\` Ofbr)ExB-[j!N>%#&SkFz5J4G,O1\G6mG6HrrFe)E*}ʪQ4hO,VAkԎO&>mVO&PXیZ©̰KlGtQV,>🯮oT#w)Pf# J<𮰑'6E1H˩);зF]7'|OJuz vCv\V!O3O4Sj&Zj2uDZ旛Z::XνJ !3ktaOkgĭ;5r5H_JM¸+I(ѸdAh9IR[{ kK )PfSSV>|#6)Y0ṈB"SƖKhbih;6TZRc/:xbgKETAT8N9I,j8.Z槭DlYNPo†q, ɦk@n_Yy=4΀?k6kTeI?[u ,NR]x Keih%?&y҃ xh֩/*TV8vO^z 𽆍ނ;tS@ ܵhOuS ο"z_*doˇX7..z[` j &X2vk:tˋ& ].y!>ϟ߾~|I2kޕ$ˑrms)y5iTYpeŲ>1Kwdqk}xD[J]%|Q t.Gb̯Gzֳ/E]u``o Kn7~V+Yݕ&&H8_w 2T~zLu$l]_%QZ96+.~v 6,UANWuLZdN5qGuwƊ7 DNAb̤7Y\!jsPγN֊ڞj iqJ_;*;TxϹkݛ윲-up!W8װn8_I~dUjq0fE}Jۊ\i4n `ZeёGh٥Np+"eTn#Qd$Doǹ0Dt!E-R6vhuҍ5m܊TrUZ'v&Yk %((:!Þ S&ͰJrCK6-xۃ{d`Q8 NFcHDQf>u^ G+ v\9C5Ϗoa.6&ѫV*fͬX3s˽M^1=_o6yEtsEu"evR-6z9U&92aQ"c /)ދ-MG|~=]ΔUйn˱˴֡ i=wS;1 ^R߾!*ycw6yGՅ٠;L|ltV+O<2.ZEĜW2ۈ^oV kT6pxCS6mQqA4am1ڟ4\ ;E=Ԟ2+l\lDy>1yt;!g¡J)vʉ J8Uh!ሑ;"H\ $gKgج5:89CC3r)SG6?WʃJd F R| .U G_z v.B+?R,'awo&m<۽6 V*t3bQVQbaXI&:DH? xH,7c_~NGS<-(O܄7y"zi\/Q ~7f瑢L ͽ?4:ܶ3SRK _Rg:)1:č32!xD >ƽRYsWo[v7\8{|߶|]$g՛#YD鴼,Fp! )+V=^2a%FiVY s[l٬}([Hk}H$"(7|X;u=aYn?^sy]:?T. pu!!?3ۑ2]OLj׹+4*Ly`w?}.࢖LJ٢;;L[YTe.] 6Q+Fۅ'.K|}ۤX>wطؓ%i5R՗";3BԜz>mW'q76mӸa#w>Sɓ~xRP.nf YQ;>vWH#xãJ_[P0i/oNe'K[y6| |C.IO rH?1ͳsq>[4Q/ܮIu评8Y–$@9rqi_q8-FRc.s%GDJ0s͝>2<װg&~,V7f < r_jE:cxEA9/Xzs>̫?vM쭕1[IyJeߕL R &ƒ2EWvܒ5"V}B]cɾ2XFF( j>R9QhU?mm3mլ/O?] 2_f9˗X(sb7AJupaSEȡ8A(}V$:޳yZU0P ^KT^7䂐J*nزbXjR)֔!'ʓX53OQn_: UM0D͘|6Ap5Ğ J5ZVTGV=%,=?c?@̧1UNmfMRAUߎOi'KruO) ՝%WEoAnϤ{`-`pw7.WwP(\EӜӭ9bCբ0eJiDz'3x̀ {9 3VYmBa3 -,0eh\?8ca̾Po0fx=t6 Pl|jцNϾ{ϭ=<^]@K6;DRҔ Dx1G#(<a: q\߉EFUN]*4om|WkKslo9`[RBMT6L&(rǑ""tARXA\FL0{]Fog]1PQfńz곫0l^^89X)~Sw<|}k lU݇bݕ~vWEDZ2ppqvV>8))ˬsvc{f  PZrNR GŖU9.O+%OKdC Ӡ :@1P◑P$3br66ԺX^hB yih i ;)HG48R}Aew;a9UP=վ d_Z?:y{Ț1CkX)E?%KLAsw)z:[=1^ZH柬ua)OpƵ%lQtGE;v8N#z(F!Vxjk`"tgq/i3 (pZug{佶4#HshMEkt@?ƫhZ=_s?7cmJGcDM5f99;D̬, .%VAtKN}\~VF`_ D曩n{sъvSH!tFqF9"Ɯ&_' }< i p2dQ槲J $hEt9p7+-YʨǐM&kKQY^ᵠF2$ƔؽS;$X1_VФDlQ (;ۘFM;>5Y\bU18Y'xݍUiZD1zoG厞z45b0!n&π`Va|e1bFJ )JPJڨ/H)qE7˩(Nܗqo92f֣++4w"LW`ƌ3 L:ksH5 :QKlT`BH60acE]`|k`*R\ɛ@uktm}4H? yR cxbSw3IuŽg],1sqDyZzZiQ 1к>5 0C*р2LH֖N 0]Ƴ$o18j¬Jx 9g$^~䌎nMrrMӈGbnUia ܥtgKV(_R᦭5W"b9nqLr|LIʞo ].!H1~) 8J ei?ʅc_)*#?P O(VEh|bH(9QW7 |qC=ġQtdȏ~=gldԝO?d|ۗ?/^¿t*8J()?Apߠ07ΒI#|OP #Bʮ kIenIĻ)6b3mq 0WjEQ2՛Eą%E2Zήjeܝ\*:G1;+I;lzn[b3;!Y G1)\6 ;XS_rn$8OAj+5~mf(,6PZ9*4Jcԕ.G=Ѝzύ;E8ՇO__ݍe) d3)| {AtZ'K *l 2ٙR 7Y*+dxKaQ/N/< }QVрZ=1:*5) 纀36lUtz<9 q/}cƂM kfҀQ5k6adfh׿QfU z&AXPw&Ve8<.Vѥtq⯔sJ\LF8L@9U5א߽He1"u]?䛷P_rR U@;K,`flN1rP/WcDRNi: b(h|pwhս.Rȼ dpʑ@5M 1?QOvd5 7Om{y,5S8䀨{ BGqjct|T6<2Ropo {/j!ap3-{t..:֭jWoi-T+Om<6"8k9˯X$gO(ABL5{SfA5h%U{IZnB\[9n +bM рE!}} YU,;;-x%띐;J,e hOzXuK8_9< yO sz&4y AAMՓgԆ-fr3i "!lh0mv[p\|>"۝h1r'_$?v[T䚼^n k0ϧ*1ȹ758;d/wRxnpT^,ћQݴr equA3T6{;sj>yA ,\A 4īPUtݖpM36N~s@/%@ygTᆭ[.N @ƭN{|Z]@qK/Y`yND"`BDL׀-{J"3+( _&W:Rtz}ГtdM8YL`jj -:G&%GbnS:GF*1(A%26NG1-/ZbN_ɵhAVM4WR_}ą-| אLzO\/,@9쒈T+G~! OItkut K* W#tK9[E,py+b bxM j5Ps̴N<\m=Qhŭ-y%$F-b,!%,i+'j'?2)>lHp{qa 7d|ŧݔm>j-i:r80Gqt]VFY)Lw^b7s<Ԟ}lSC0p-|DP̛OGI?5ɚ%Fէ965%U<""<;+Oh.f'B'tPV'2ɉr4D($n(1b tS|ScBBK(\. c`'yf/,%7 ȱ5VZU=2/bq|$28PJ<˯9-E/K3S*}HtcFR=U l{Xt7塻; c2" @59wv{Za|)cᩩ7~ozkgY˃JlAq#a W  d$㫜/cG`֖C~^"ؕei{ܣK/PbO`/b2j65~UIfv[$k/$n#ܦ @w WĒ~vvI凹!NZs9qk4cj=imЛ~ Bpڈ[J3LNe+.7_~;$cz!b@4TӂD#hxFJ eHVqI4tOĖQ5Yy& 9`4nK+I3: 2*B1WKF 0 Q1UxVŋқ} b%N@OkiOj7!O3:od5!~S!LPثgj TagD |-9s=*>~D, q@:KE'ys(;bVA6mD)zw$8o(:|YmK@Ih~R+0RpSLYշ\swo>2=SԬ ܋ ZZm6Q3+:%`r"pJ*BR<ʿ HbX%gjO=}A ϧ U^t+>?)mΣ-0x H_ʠ18!b'r=A^,CPyX^ʋ5!;6'dD8P ɑ]ݵIh@ox'NSCIx{K>OE'z昙*c.#jˣ*]d'3dG H8 ([ƤN)UjqrC$ Q ZAާKNax`k_CsrH ( \tXO? Rkx$ yE?CvDڮEŬwRvpF`;ۡA` hc|{kT<Cq1Jژ2HwɣgWP oЈU%GCɠײ(;wq4涒&dc`V0o+$ BQ' ,'UQYn,6 1AEaƜSѬ95ED_q E1[%%K,E')i;HIgxxNg*w9[w`%ڛ~|9oŋ )V9pEO<Ů.xgGt1KBlg~A~u9#-L2·R6do$6MeH h48<dnaR˿k.)Ť1TA+BeY̩z9[fMT#q4A.EhrE4x!6o. mnudye#ZFPִɒ['#b&I냧/ SQ,z퓟_Y'&:ba wXM\iqL'ifʈkPur+eStIǫ,SyIÔ΍ǜ#"wRV .MiNUS^ROPu"3y ñcgr~ʶb{r>-D x 덴#$׏kQal) 1N"{dF_`rg$zT=6YG`v^/֖DozK*>Y$=7E.rWJHRZHsBbDnl*z_i+3&$͡yWC(SfD%IaBWe0#il?>P@T<8THN,X}4ΑZ|})A"L`miVߑނgI`)UiPv3y(GOQ1NFڜ)Cr?thTNQA à=XOEmkf^ ʔK?/#g)2x $%X4SWTV-oǯ)d阻.Zyt3u +ɨCQ&`:p~#q#Ӄĭ)`z8"Oţ]>ڦgQ;(÷`U}aHa[֋؎V"I::;ipjUvS-H.P<p؃Jg('(V-Nv~M}pg>|(]9hH f1m6J/*|fQRY6s=`[J [EPU.aG6Q0vJ,7#wh'?N㫛ߥ֙ExNTuY$}8@wC9!fP~E>~ QNy]|(CҹZb )[jB656y 10N|M'aꕬ)J7vҴ%k,M1(̱Td=)b鬠2NAKCfwS'hV:MO\P*CYҁ]w{u9seGF>7O򟰣 {1"r 0}-]]V>THg=XF Hz1NI+"nM0o TpGDQD%*IKd Y e|t.TnAsV\yojMA;zx6bbBQ3 5i 5775WS dϪ)JH˪9ʚٮ_:VHIZ@ veјLe !?9U{ya/pq!}\UM~ߺ2Z/j5nm+kGtBJ؝Q$q G!+_׉M4f(4 pVuaiT8㖽J;ѾK N15f#XSIx. Y8#n"Ŗ +bK_c_/ AR9O2}a3F=ዓ:ٴ鬱ȸ SxWܹ]}n$QBSSOitQiՕ/$]&OYl2EH dW2o*pTt6`suCɛT;:Jq0> |}לT]Zcz%f CEI}#ϫǹ;M{YP%<5r!T}ɟ(ڮ :@nw)l8] S*vȗxHޮ93[I H0tFf׊E+_6!7D큰[Nmo\xLfJ>sf$ soSe3&͑.c|sKBrrR|hsTyv-$rP۠. D8>1ӈ>/ͮl:x;?{^"[#ʚl#_^{JbcQq9|f'29WU4{E"#X*+CrEe9?7dF\r zQ* 18Zsfc')=sFXNtgJ1 X CIȢ&,Q=&w [uv t[ F(ƘyjPVL,oc<巽|^:sdVHUB_{Pk07)8lCCl3[1%WnGx*"I~_?5}b"}5r0^P_|G*vةÆiw.A!]*ڜؖNvi 즏ZX"'\V{j ANɨ, Lc_ЋFFO6~Xڮ&ƷdjP@bBV Fh 7!2RCrX;C_Cű&LI@֧[ZVT*ūtGLG|B@:l ')aRP&>F7#bGyB}2jWALoV00<[ջmt/N}Mz|~u7{ z^6}lq9C]PIWRg P% :VF| iq6J*,E L׶[z McGER~Lt3i7\ށN=ӾJ.mHLSrc$U-IY㙆84aWvS?< S5oyf (JZϚ,կN^Gx32F U?-8Ml 3X*݉I̷IJsk(<3SԔ\!D]D}XEr&5-fN[jyi3TO`%9#v8/1/g0seaArq8l]Xs$$}g`?l - A! Bz;!aiu0pFheSϦ[x){U06Y_D )8?V*TVRX=QIV1`;E_ .8;Ԑ|,jk#YP5'=QQڝLsU'q{MhɌ{Rߩ\]vO bclkP\drEȥ)JN[@iPn$z=C2{0[!/Ca׽`&IA۲P< FuVȚF1MSy([)8 iPT~d$F:9af,+e/$d&M <D ;Qr2u/lݚ^'(~RnaJEvp]˪a@iGU׆if,f1YQ^,80)|>e36G&1)# WO2"eG^9 t o %g(9^B R I>jM1 yşܙ$>= MW:S~xD"s/ZׂoR6,%KU,ѷ z p;e=ݜTҿ0C ۛ3X@p`gR;oIȃ~))MPJ`U!Gt,3 t hNtuKۢ>:w3RcĉIc r[#b @ؘIe&ca7k^?7^ycΖ6 L6*#X/dpetd ett-@)UTl`#LFqQ)X9X7W؝@ @:V" ZQ"ё+ܼ݋oirK-♘Kzf5Zcdv`PU'R[s4KG3qiyw}VP6zf?MĽ̐P8ɏd]gCc CblXлGL*hX?2`36ÌiW?)S: >-5ӧ|)A0~PVO*=^jK,v&Ji3|5ޏ3oX^B=ʫj6V]ZwgEŀpG&<:qO!j,{Q7[=M[ƛ{uK}8B }!bde +F4m9F)c ',KQqGwk9h- eRߌDf^w^/A%7@80 p4q@ NJ) :p,&("1(p ?Bu `Geb^MX%f#ϔ|L]>43+] *k|b}`:(*x1&ԗE xؚj#o2M1L\RG>*fG~?Z_Prp f,L,sP"p ^]%qK+G%Y$mI=?H!#j(Z{,Cei"4Q L`*]1 e6s}'Mu-F.ڸ}]7.Ӯr0TdgȨ ubLZL\VSᆳ}3"QyW/3k3_.VKF'_Q{5|~]efEn&t^ſ/mͽ_׏rXv+ؿХ&; !hFd>V >E1[jm21|K?/(Ue> ՚QP]}mM~?@È*eƨ t $; )=$wPE ,a{e9lKQH% b+rA jM9帟cILz9IӴA4egz;k^.DᬊU^ *Й¾ϔZ;ɕ<5s,AS/g ۊOS{u^Z¸˜m!5N8Fx tZ=  Y7޼1zx}Z_p} GO J3\#{Ɏ|2{=K ,xnuҒnς{t; zX{뢀w1SDzLwto]pnTmB]?#еC{9㢣EGG%D%'Fo KL  =tdP{A f9_< P(b;- 8E跼<4۪cZtCs阸99BMf(r-vwdvU3o;|#thXF|#X(\Nƴ,,Yeee/EŠkU9P~ׯ a?]YRc8e22\G04h N $JYԔD?@U%EE4Z}p uK22mPk_Ҳ56Qm_٘?(ُne .#`IS3AX{V"m!w*:H{&C`+\F&a%SeN-0 P &f_7~}X+v$R.o˼/Ҩe AcU~"Uh/ke/n3sp7Q[?[u3>~=EޔY|7{'d'5YyRPkosZD=gIS(6&`L{fve~+?Gq}b:q$Ж],v6w5!CΆ~%<o"ʾ} -/!vV~6 %Ob @R / d lX?~==k Ɍw;kc/R>^sǕ$niIt9i] &S6; 5i1*Q/J6V_r&RVW45֛Ow0ҷYu p\_Y2^0^E%'z{(f$nukj))fz7csVzrߟ5H9 Jޫ.f[L\|rr 9{6q;A vfR4QhZE~B\g/KR|=5'ԴĘ[=2>0ZAK3 Kϯ^L2ݙfj@aO@>5CW 7)R#Zms^ulo8&_{n?MoZܳckZӕxEPDPZd[n!Hy)gF!I$3v2O OD׼rrYxn=dAҺsOKTs'-dQr;eK9="|p Mi 1LW Iio"kYz Fjvo-#ϐ_tԟ;Ag!XZ޿ A;c:֓o?q;EG"~~{ V"n *^a}5k >_P4zSfWr9L{O胷 3Dz6|>WsT~'{3*!UE|+- sc;_LN_DAպ8;!Esb)OI^Ϝ;ot"eC'MZI_E~rx 0&nw}yIsF#پy..1Zstc;4 z5}3\xW֩Xܤ;_ثn_<[ DJ^uPRO'KyvILa5lH\VvRHs~UZ}]bK/Aif+騚dtH!gj1gWjVs{3J3)+G*ij\Vxp(0iڮ=|ZulS9V%ZCNװjg㬻ڱ?_< u^O=ҒX(t6- tςa2K`cQz?e@+WPh1+yhmWN(Q/H.ޞLoL\N#fMn}9sRq} N$H$ۊjmY Jymxs&7Ȧ+0^i k{=V3>mHK6N#{^Z/p[r Aٶ IΈvxֽEm)|7B)L8V q]ڠYD, !GKT /J E|ntDT+GƋo3Nt$CqgH>;|'1E* DMe@f7>f펣oE_U͙cKXKu\0 )G,䜈> %yIWzs˯?9IRԭbV szJ [J 0/*sRv[~.', kWo(haoɍxA!ojO,n$Yڻ/_nUTK^_8lөոZndkt򘦉I0!J׬9H~> !II!f*sתWP%&FF6p̾ O.>1Ea`o&֤=I6y{!C!r &萫oDRXd$Mac22lmIu2ߐvRC}MtӘ#En iHH׻/r}](!bc}S2fH]̄{Tc e ]'N\Ar $w]YɵOǠz՞t&4겠eY'Sh$`S@N?RX  r$!D2%$$N#2z=n)ȐWc,Fң̷ON8ɔ"GlM?;Ĝ<1V!˨!Z0jƾPi:L- Idii:~j Fru282 7n3I$25dfjCH#I&E4o|E%=Q#RSQ .7e} $4{8bz9iyMy9kW)԰+E kpR+S?̤L*Oc҃kӔ4@3# Ak5ߕ:r\\k$5$E)5__zY6PTۋ#;+B{C4/)D3Vy}5lݥA{vl^7J5{9Wxx$5DDp"T~Br"O;:[Ń(RfZdǠ@^\j5]jS>0` "4i]4U:*+J!=>>( Ef혯JM㕴~jD'WXP5k_Z m+E'~Y SCl_o9_S5(Ԇrwr;3.ݸ}wo^xԽeawa<1OOdHrHsRXuj gz]u [>I̧b'eˡW-JD6c ^wsD!Г ){T$m)UƷL;1rS(,@ot: u7MK ^VwP¾6̽cx6`wws8:L!KY]zzzUokOJ6˖ a&f—<&qR Y}BrMRW9NV]P8 b{CafO/PA50̣.A^،"o]05A)0ټҒu; B|+"FF IqI?e`c#rɇ\{/Vw`X'>YMU pEouH+ |N'M lHIs_x8PB (AmЬV T( #ΣmsBT1L1ۂnB.agB Ғ;sl&ː4dHDTÖ|6" N6Y 1.X$܌ S$M'P*ԲhL DWː3wα 4ExW,@S4ܠ=ZN<\}ECҰsEC(GO5 }'VI$ys/ tsW5c*X< Ic)ٶ¼=|l>Ww{&پd,)$ $C{N,";Pz'˝#;WO*oϒ>C>bd2_-Oz8v­NOFv?ɭ"@t4>΁F0@HevyKiҔղ2B3_&o <WvUSWP2HGJV?o\CMuuMc \h$K!!9Fzs6HaJ\ >p /a3,: bݜԒc]HRRnd~u7Vkh9Ț+X G2(ϡXı_=\ʅMȽ,$$H^OҘu3nT'|' ϴ |h 79̚|H&&u \*GO]t;w;# 9diHtʾNÐ&³}HEyy`dtݪһ4|ׄc:T< T{nD rm3`c'&V ~C"HCs)$p`#ɟ"bl]&: {x%9.avIHЊ7#BD/+5g'RЉB, E27h_+&ay<SԨl+'gRot#ԯWӢկZ}pf2){MU{Ul'5_@i"q^DtFi! Wf!z0~O ~<$|'R7UKβG3'Z9;`ZA:/f{uD^ltDխ5%!;QPwBYK9\bhN,\QޕpUɢ/(.({بVB*QtH~݀yT<7cgO>膓T՛t"IdeW /I![v ]9ܥ;5Iۂ-G阪(){ZW F4 5Y55<5gC\UΣ7_+ʯ71\7N{хzkn/hc;>`. ^' bNDpr/29@ĵ˞dg#΁l|QZm+X@n8t p[?J C io?QkD .[ WPs:`# &%?bY}Uᡈz5\sK"/搼Q}1u e}a2 ]Hr􃌐Uۍ S'(RA2FR# UU:"os&.UU%NZqOV}޻uR1S8I,.n z~~a!R6^`9UՠNKna%S1<dbJȒe R&)llRn| 4L*-<V $dǀ}y#1 *[e|Bi!-!F@jo)L0^|wnܶK 9a_ %b2M ?X-AU7?͎ӭ<8yA3[SS]-K|(@RꝟVk Q=j- f}̑ | =.J`őes/It5秊nl-s1[?grPk^yҵ> {1Ƀn9fퟱI;#)E)ͿTk|i3&:5}/.1.7)¢ +7h R[Xɖ|wf&jeI+A^9REd_.CVBV}RNvb[l6" [ iEN(UX#7O HJ9Gp 5]kkLj p 5|h“laC$8S!Jv eZL[]ɿps ܶWO\ǗH&2ղÿs*ګ|_q%B}o:}!\&U›Oo'CGjq,ЗĈ{LW.u :Nal&AO֫"#v>&GPHM<$ e,# {}\*#gUNCASah|% ./[3B໎[OwZyqJvlZ٥k5U !\fe3N^s(1PG\-AjzLW.W)'B"Y#)~ˮBM ԉ.$ۘ.2VCIgv.?8]+^9px+]3Z'gB㦮p'~p@ ={+\}> yYҘM*5.C'Ohǟ_;)5#+'MN4"?Tpo{:(42:!uհdV=]H?)Bٸ !7˽nAZ? vqCTq$?-ayG>.G8I ƌٯS?k;Gf;EJ/lcz=LTeLNmL+u> yE{_1^Ls+Qmo ;8뺍kkfWP؊D3GRU';7Wk׸Νw`7uS?h@PwdJ54$ zԢONg/\,F2.wJ(pŎg.^Hyq!'DfENѤZ8CS |*B1@_ofaVu=ܿJ\8G;5Tryngh?FE:㪳.ri} PArn8|\jffPm%k#g'H5CǢո-j70:1SQmƟnj>s gչ8$+ZGjƝ=\ Q*d@J]Hݡ]qpA~Z6j\ D>ku[5xζϲPZ_Z<~)KΘ0Y8aPQo 0']a&Hs:!q`Z?bwaϓ&?(֜nmև^j΋ζ˵VLlg`L%r΍>"(AC0"s&_܉'@bW" vOMkIO#Fm=jch~mzv5Ϥ/Cʗq̚C}u%ZXu ֻn }Li։_E=ă9·k?zW4 vVҠCef"-sJzTD%l.eAHMD2]ug2lڪ\WLaaW_ڔM۝H.y{$y/ л8D?XzB+~LŜKT04lw'>!=;Ր퇛k\{i!+CrI|'=D4GƖmax3PWm U0%l2@zh; #!9A4g^dO#Ž !9強.3Rw*CHc%#-'"d YLZ t Y~#Q}L1*UZ}2[dT A^(K&}ܣb[t &K~scy|Bu>_2]AxL蠦{ *6~KQ՗6J[dūm Q;0m q!^荾G`, cT'OHJeHNk /V1r >-#0+mKₜW40-" d.:4 MIn#Ji,ue7P,ȶCZ=9@ŹHk<\JIDI>!9fULj|΍Ȁϰ}cL-,UehHH*(QYLJV5cJ1Â(@B C9J֔ #MPi(iK%Ԝi=LI 6 8em(|9&jW0XU”~P[֠]# kr:Ŵ#=&vE kbj8iIasL+ndfN*2 ߉!I0,1fW7>D]8G-B2LQxdLDi4d_ }.ciȮ6EUMI)W@~9kÂı,H3~.HKk]eōqPYHkL^>>Q=R}7aҜ%!+'XѥP%ל ~*3n}^fOSLF8`l8{E~ly_Σ"_4#)aÈ0'~ϼ}SH̰q,Kiɢ*F=9yO;hߦL>n3 Nw $ـxػVo:; )<V gW@Ãi j##!"b|Վ Y#OBC^Td z9x}4C"HyC'a})Fsj?L6E8E s!8 8JK/Rv/IPn3I@NE45Yr`4\汚@I}|cd.Pѣ_/G-< HqH l9 b;A:mIڟ[!*:;Bk #9w?=DX 1[~ hsc^E)^?ȘHX!|S?@'N &4c>C?8<+W.È"CPyӲ%0 }xwB‡o ]zLi.g";r^h-|X?PAGK`w=jRT$ U{< ٶ=-C9a6:e1BN!sBOWN'zTʹGPk(lB]&:zs>ƻ^|^НgѮ]=q IJ"it{aHh$Jӌ*WqzȆC }C$v1wOT G3"M=uDLH\{Dyȹ, (tMR^ EQ>$vgsEV^R2%c:Nl 9 )9$%ȏuœPAb3XNS%I4R\ P/ f=P $W /_DO]xZ$y&lF@}_|U̧e}2{1|k /7N6 uB֕FF\6)| }* 7"@֚H X:~O@Yk]əDE2H4^8LZE7PzE(6z}ڷ 2>/KukA@аc4˒YjBrs)<8w1Mb.  L6L^U=ԹꢀXFdѼ:1BJKA"@-O8`e2$MpXXg!Y?*6yZ,vmS)Jʃ+d$lH}j?B=T'ew)sr4+=~ ;Le2oK3U)$̓>w5 ߀>+݃ʞu'S˘9rK~\}nL!ݙ6- *wzs@Y U ƽClv~DSqBJz(+?!x'$泿ƱusF痕KKbW1HU51Yh'dg0z:Sn=cHqyQ-X~@:AJژF2Ĺ,Сm͆m;Gōy+r#بi^R#6ʎu OgHFS?)F D#ulhm[G}&g{M zԗ"-b^T+$Gܤw76OǦ#\~;OB<(A?6z9r|ۨ6#ϋ nrs:CKH Yc/N qd jg`ֹ>8qbMfsu?ӧ3{۷#;>d轘ߔӏ?RC~<ɰH4'ek;e铇T Lfjcceee!iFݗOq76`jwV=F*zG4w|ZcWin/1sRO9_>`9TN>]3[vCS$XJ VU}on>iZ[x3 3"}A /N]0sc,BBj=K)U9[mCՇҿLo:+>hHjdNe)v Ң_,DF~F%_By{K୯:.q W Cpn 4&(AFRF{^u^SC&[]4e)~/Tʏ)Y6V~odPy6B9Oqr}Ŧ[)6!UW΀TH-LT O&-dngBAҫhy94].Z.̐$UP4*+Cw l{fc ba: aX ~˰~pQeV($g21h-2+LU, n_ ͹Y3 i!}h!E 6{  #fXl x6Dۊ)銼鎫Ki=* <$rZb{᫁^ʷÜi0)bЫfËj^A .4 2|Q,j~%D@fj2+Cp sEº y3ҡ */8;SV񘇅k-1<*$QZto>HːaTMrG,Ճ 7ج)pM7d+rށ-1Y&_<-T^puKZ|HռR#p^) U~|-_!4'0E> M cS@J Ԩ65JsmeXYh9!#IEyzO ɩTNHiCO#+Cr P J`My7% 9AI{ٜwBֶc~3â-4߇xZ7~.^h=McZ 7ج6[EPcrۇ[ î0?ToS~iEG6 b"02MڇTͫArdޫ1iɱKUQѢtQq "2~*< ,H QA@@}'_Pu^ޕ EԻv )-MmWAB&CumZbHdNHʡ  yqcLhBfī-,p ՔTuP=wϓ ?f>"^RHJ\"jU%. lr[-L إo#A1-g3d$Z(vv~>GAV}}[9WsI=Yzѱ+bٗd|~3l.sbK6 (r&du'IHGŕU/_`KrQONf@RLrȪ-Q6*ТTEAH}L )iժIM@j]OXdG%˨yXo]>d䦥E InTx\N94&7\I\]eP=x_ LPa&͝ner2_>v>I Y627ϫ"yHa9mN, 9ܶq")I̘֓˛7&6gԖ|INPE75a C;sxU.q`1^HRT]Y_& 9яx2m3N/< [KޑrZr Q6@H!H#~^ Y!LQ֥[ٳut2´B$c-rI[Nfnv{V[wʲ Ͽ2pܣ X4{ht3ROܞc%Z I#Ԕ&3 Ӻmu,7.T w/Ɍ ^M~qS߽ b|9<&G:8̭ꂗ+5j(v2yqY|S4nc_H F Y 1+'@Z̖OL&C@+Ƭr4-(mB2M h ɖpERQdZf׬W^TW5K֞-:ɣCǼ&1xkT4HwKA2Bd$Fi^b8&RB+=mpd{qx5 (BZ>˔<6OBzV x PNQÙq${*2&%9.$I9GAZv? ZDҨBG")I:30d]w .Tv UeÌ̌=$瑪 "F)N8KLUuBixyy#-¥\cт  ;W:VNxmpTanʏw9 9/H΍IrŋkwLÐM99Gr|'<_uzi;%>;fmGoFu,^yŨ% ZOEZ4'"aH]pls`u HjL~Q#.Wg<3Wu"ɵ-E섀tqdO6zXż J/{eN' /ɺHZPw~2dlOfte2s-b@w'6)r"dOښ$;ds WgM-dřW涺ʜk2BX^:xlW ejUkWmSȨh8SJNm14 ITϯ r/jd^e[vpd%]UV<P32A2P a[2$&a)]wHEݦ?JCsyIY jZ̽ #I!5z."D R/ mɑ,$b#Iòy;1h)i9N$k)Ѯ`HK|ϔMZU_k/?Rd$.Ptwy_([Xm5;x:""̫; [:]sVƽK$]RаA`x׹iS<h=rXd 6k7jwʭjA8 /FG3X+KAR+'@Z8D%ί۳gtCr:݂=LHa91t|z8˽ae'e1b,@]fWZ*~Z:>Q Bґet·hk:x>\N=rJ]hEݑŽ&oq> 2{^ֵHVu|/`agT &j>1B*B"ͫQQ gS;:;#YҭPV??;7MjI0,F$Hb \'8Ruy7>4O#JeKΥe{ЬNT695C2^HSD ieRl'Iao?{u-R~{٬?0hn!ޝDL&soM,ƬəWw nOMRj?{|!T؟;%@ aLGu ('SQFNJXgHZRrtL_kim =?NA*\kHKDm&mB"bmr%i`.Xxe}/Y|Ae? B+^tNxBBF9@R+'@:xD"題+$y|;jZH*HeTC>1hVAH Zs}^)g.G}6w 5PL5Z3AVWV$4#DFC{z9dvB\ &ׂkum*w;CajVLcXq va{WN (Qc=3TS{Yg\sI00)8Iϲ׹KyMP f#Ros|̇;SUiUvnB k"n=;Sv+T ^nd'INyu7( ~N?KNrZIYYzlbR8$54{8R@IXHpp|YdeiT/Q/J$9('.!߇JQE浔rK9)Y;" zMEQtn|;[eH_pׇxqB2%&m<c23ooWjrImqGixco[ lJA" ԔcW axs>Åّ{ r212rsmG_SDɥ]N\#/QcV_`J)wUK9dMC*K i_,j$qHy(/cBѐ'*6/8zy}ʈg?׍<& cꑍ!дY bg^i$A5$/8R=x!&w}j*jOqGs{;w<:0z6{_M1),=T<9b;#!5g .mj1T*Ӷ )6zʃt#QX!-2=Hگ:3xOwd O 8nqM#&*k*Gv_4yXK}yj_}3)Ei42z^<I=Z &v,=%d$G1MN.a"-ŕl_QH? k`J ֈe$5]A#rfwd L[ ?%:fٛy@QF܍U7k3POGCYAQU -6JԢLaQ>MLΰ8ms| C(T$ܾ Hɇr}Ve;)Ǫ_$|X7niCli q^[ߑ˞l){X".r)F<Õ ){^9K1:s4E 5_QW |0~v2O.CeNN6;D i#Ȑ7bd9~@9n-X!dy[K;ܭ)͏ @/}b>doJ]$ \WCҐ4X~!>0eHoF']]$Ͳ1"ɐ6JHNfLBH@Ad*G3!>RCIBZi B@l⼭pţ푐H:"i{ܛ-%?@BBfiHMzor-⛾}]dlJcn$H 8=Kջ5mP-'C2zXm5,ϛ17E|6 'Ȓg&tߚԆt-n$9eM;hJ5.Ve;>1ԋS 8dkÃLT>@Ő, u\8]d2WTpʨ Cw:SEdwr>kD4m>Lه<^^ HU=ݘQad.4U`p8V?'4߭4[DgnI.Athw6⧢4+ yRXH7,A;l/攆̫?& cAݮxCz dd c<0$;'87H+2<+A$\{k >v AA|Z {>.#%Uu謸_CB tEGD8 5 )[f,¸g (qu6 d :40򹣲: "!W=oW -W@ @޲⎋ o[r䔗Ȝ\)|DI.L&sj9Ff"zqKivA* =j9HA*آ/䐅 WrK2xۀ6dTq9'-BCX0?E R`Xɜ_ÐE%dޡ*̓AA6 [׏#n%'d 鈃dw#~[|;"oZ )Z)ʟqCr]KGSeI!O#AsEp<"p1} )O"A_Dy\&+=Sj)!ʞAR5Yy-S]Q8H2:!+Nܚ50M)eᛒuB!@*K9|tGp6+C椅IL}H(ā$zy?IrڟHt6n*"J ?9\WA"2MO MN|zS7Us9ջdm͐!wHj!8ӈ=ws UhvPM:*!s7 Cygd:@ll;GN\MaH*,[XcEOrqiUJ !0+S3E5lK=IWv.Aywω_53gyH dq7[Hr֜(3M…Ӑ\HI4%W0(HJ qpDmaejFҵccا쎮5%)q:j| !4g.x9ɠ1T)5 _Lf3 +EϹԅ 1X Cί Є_+0euA^ Yϝ@6V-XT8L(}Ȧr0iju &Hw'ABOᔭ:nqIz!UCdj-ߠ 5O*0n-Xw9jH2_6p.)(z# k0P~ v>䢹9G[Z[;C*6.xUH|qɵE~Q 䟎 ?S@@Vں 'h8A䩗6gRhVƧȇ'Ƥ=Da,+`?>~0)tJU|DG';}ke'쪁ȧg%d}X >@ųSP?I@Yt@^@A>qq2n?݀twE-+]=#m:kӭܛ16iMrW!d'BzhKޒyx@3TXYStѫ3jFx/ h?F|kɦ.zRH9 G4iwV"`Դ'鞇+7I+i{= -CG RxfxN ux>K1.ea5I U@vPmOSw.''\mHHb(kAVx⊙8'͆ZcR Op~oj$C1 GjF y5C8 !gKmS؍d= p#d+ RFENZmSնEs!D3Ħ A M6#\m#Ag_[ d,[2< <(䥈3EAVm.x M% ?oo5yǤ<,)qaы:@r5q]dCcO'L x:&! ;"όSn!]uX!1911C|^ Y_` @zk҇R E˚˼''Hkp7 XZ^;PҹAM\0cA=R"S/GY%gk^FvW+Kme@FrlqƣE*_1nj.MJAʵ4+w[c:7]QAIyuCچ8ɳW ꋟ[`Z˗ 5Tcl~JriY=Q]9{б'T$.pک"'8{A^]4w0Ƙ $,޷c;ae/v0)!2 qןRVKwm%,(k:1CGQz̲;g+z\T=iĤ]Kc*,$3j1WTع E?!=z^B:_ sQH@\Xe[晈IY;?Bgx Mj򮴶D(s\e7rbPMH緥~=YƾQ!NiSv)޽GzIj B{fA֠N Y AQ{~{u|⇘gx0t8æ㯿Kx`"d CG"yϘ{]C! Rnow(KϪ_ 23_z{WL* (ۭDZxTB~Y -11+9eAN1 17odHQ~̯~; ۚx%9yX^?l^72TdAdVVMwWeӀ:%~/"$p+B|iː7,vZ|;%0Emmk,ړJdƝaLugag_o峖$K@&_=+ :EM0'}'zHYv4PsGD5B6T S 9ӈ ^=\^ \4R8?mg`œm;FVa13Q݄˜O}}myDM/dJZ9mESNDa4=I!KB0dq-/!t=]BrZ]]YǢoqPAhGR٩X\jdńWE ʹ?$>8U_d ۋ##̎b62}p~.ney %]"É1"bR.e^+%S\LT!ȭ-3cTcl `N*dž&9gꏘ1r_}&uC"# OEńGƤ\X؛< }pZ&Džοd8&ئ711/ sJ:#m]{"9&6"ȕGBE'&مGAc|Rڝgou`7aH"ȞkȳײmDb xt ظАg^H9Wc&Ctc c1{gIDyPkL%$<=}E<<}N?@ OK|W18LCPW!yZZ/#xP!C`!&׼?a:#]MV{MǠ3@H]I_޿D0 5:cf»l$MeʐwCJB&K4m: ¢ djAbHp#3%P aHBb&;q͚_Yss Nq23g}?n gD=3JGjXTM"Ⱦ;@Hh"BH=:9?8TTu? C=_pNbaK/N5d -E^r'Lj2 {% YwXLLh Ge.83F֑iPg[}{nz)NTHpBj |`v_.K2c9"@?QQwe8aITb$T*T"f%5md =@*ZsQ|$N] ~R=$qp'2ͦU P__/lU ˜^^ gW6QȞGѝ@<-! UKV2݁i&|3dAEuٷo@ ݿ?2ݗof|vO v>\Ag{ݷ| {<'#= ț{盳"A>GTޟ :~gVZ5^˩ .o߼{/߿y }DO]ٔm{ӷ|4#!dhA0JVBǫ0kÑh3*A.0AB,"0o NE,N^Z[h!MhrЍ-Zm۶]={7Um=y8w~}{^jݪi# 6mٺM/:vJ5ѣg._}ٮMկ]|B2nw2 2"+r)$]ј{{n߬'ru3:n5+LhFrf_H9?,{iͪbȸI ۻ}ƎCsH`wK'H?$̝|۳nBV6%9ȨA h=Hԍp^p⣕ځكW1@Ȧi1b&s!'?yxaɺ}&0o>_0R@m8W&Ҡ`\ϲƬ*ȜšgӞ8iclM)vm]r*]=oo\O':jp~(iD$j<7رyٌp k?xI[[v};~DK9~F8]K9\q6/DShDrp!Ɩ[FkPioMd-HhX]Fj8r& MH@fS nN) uC/ޗ<*}]E iسGUvASv]BdPx|%oU';]ǡi{N +u?w>'nmA=>o˺ 1tIN]/H-Aw6 ԻFdZ$gP{vCj %XF#b Mf'@Ea\H%MFM@Zwi!^B'a̻w51fN 4hɉZn-VMV?5KP~/6W~P62n.+[WALtp~ E:0f7GH'aqꊚaǝBr@EݤGEzΞꊟm Q μ('JeϚL5A>bzﮩi'IB 5}ia_z[@rԙ-`]uIRZn#\DjIQ "H\! t^si+t궺vG&óLÐ& CՐKd:kHdg,j Q8W! 6MQA> 2ٜvMiEeyËgnJֺ1Ք,fߍpE;i$;6%% 2VlI}gkWOOW{K}Ň1T,޷>Z"&U߲)ϾԴt *>>g@vѷҒm߲? ~vו,Q5{󶂍`X]ޒ?}..\iǟ:{!z馁4XXAe__glޛ_T dRKnO H>띡C~߫[{UZltE mC:~!A.ȴ!)z=yU 3e'>7$#!Sn혬tҠ,I;ڶvhs˱֧}\AҾLM'Ns|4I;|7n\j[^z;OYdxcSA:K*2dպ}\K[B G[MZ(9n_?@k[Y99wۇ8%v弹˖[;:L~SwsAιh3L͝." TE"H~x>C,MH\ GJRz)K)3$* _\:a\i| lN]trlV 7?mN#Ħj3p#6Xxt:9p֟:֭ٴp<&z_U H[.Y'$7/E.H.|\3x]sߑK׮g_9wDK\EnW0"ȠM;|MA[1}D@궄 $KTTqɔ8z>"8JS𭷥ե,][vmۛxkxMs4mf)s@bЖ79uj+̜:q8ӿTC7ذwѴn. Ob2r.2LQa2ďOhw0/$--/-3!Ml2왃͆7!i]|pRBy"g 9m0ׇdΤqWU ɞ,%!2Mϫc6gޣgR㶮?^ -_sNRq}.Nl=pW9|?su">QZ3͸r)~r ^T\\Rgѭ襎ƸwG yL̵=y#͙۷MJZ9TcA]㏃ǯŅ)Ԝ>s޳Sw_ ?h-\7~/&w>2X&/[q1}-Iۂ,blHp Cf rgP{/==áTDjo^o_9Y ![<{<ԑWA)?d2"qG߿yhEc!f&ڛO前dK&Rt(` sεiJwݩ"řp>rnfȹqO5 !xQ~\?֝{Dc#o„+gϞ` [C}9Nf]ϹMl6}J;<ں{l>3gݗq7SO<ș˸N ι^q[V^ olh.(pi;X)&eIȍ2GZnYY#b/ tN~o(DBBC -KUq|zu`ρC8g֍/_:g#ܬF1O -լޱ~g={֍Zljcپa fjwȱqFBGbSY1b75} V7K0Yrڝ|.٠eFiii*JI)j[4qclN?#u>~S޹ N45T%5 -͍uT$\iv)hyU} ]6vh pR(Õr+,l +g c]U),q5~1'5;{vQ,_ۂG`!Ch!C5 Tr@HLfSSΗCrGn!Cz,%-u yfͷxc)1_n8{oO4Tmf59w{cg/YjcDmߟ (p.p,9ji :cf.Z7*_e:fʼA ))gU>H{7ѱxuaY}oj/2oCС"` Ѽ>E5m=Cm{%rx FaLD Y؝'o2l=vdT*pmJ0xVF%cl[2|3֕h]qԉGF57 H6\]{ɵ#,ف^Ө3O~{/~"OwGK#cy : L`镤]+]#gYx%J)V Y6-d *Z|NTfiBOw*+ueUڋu irgGɐ"gCQI.67Tj3 ߰n4/ [cp{;I9lLJ8cI뷀7]qaYms{W3Pik\El&g=Tu~x~ H5)yJ^&^kRf!]\YAYK]7ػkśc7YTa u4^Nd}zZ?M8 ;HznXl9dmħ,l4l>#nhwۏ >u'r1fF:*Jj B,"HpOw>A)Y/ٛuşg* DUHKc}]-UE04yo;-o1WBFN§5DxLm,M H%yuEdxo/<ՄY"f6vcL ՕӘf +n7jj;~)І\_Aaכ2nё\*[mSk3DӴJj10>5=E6&"0 ؜G;!1vS߻%UR?>5k!$ Y$l(džCwJAELaiQ) i=,ZtM0мM'A"oG;R69*1 4m>lYbcۅ3\6شG;)axȁS̀yUm AUi Vm|`JW -yIa:H!50=){Wk).b !$䕵tGMtuP<`vKRCWSEMIFLLP@d9 tHYq1!>HR)!&g%(&:pQCGvkg"NZ I_ M泸 H$lLS_=#ёհE2')##ƽ rU:v -Mgn8k`F t-}ݍF5Dn~6?nS**Ȇ"N.A323HV>哀PmނysΙ={N0M:Ҳ>-9%99 hHtd;|͓'ֶA9k\.7Wʓ..qV[K\ݢ3 5Syܷ0Eӕ!1y٫8p8@Zx9R ^VG9v Y-*v\ o0X=/ݫϓbe~8E@'ՀR&7 _I`t3/;a_5$SABAxX1#&qƊ+˗uڵk?Luta>hٴNUYG Ş`kL2$mkD>@E&@ }9g]ة] m~Tb!kf5~^2Er)sdI~zC Qެ<.**ٺ(=C4$]z=͟ Ya\@|!ѷAQDkf̓h ® )vinA4-=O|U5kiPcbri]-T.6PJA |RIKD(RӚ[;7,n0@w=smA_3B^`=(J7"8?kHtP9zs{kG`|-eHCJOݾ}D8~A`45:rEA.oh*=i0?3̖3]+o6UT_WR$Pp d%jڅU0%d3fG%K_p_0ڨ.*)Sm’(/KD])Ȝkӥ.'1{h,@2}Մiާ6S*H>!Z'vE]܂ Rq Q`ڸRvi(M iWY.w>AZ 57t-E PjsW}/ҾXP~d8r䏮/:yĄ{JK[@:IgORE{Q9>.SJs0Nuu$,BU')3u|!+Rc_ʲH9ZX \:1AO&l7}f ZL$$CkGQ5TJJN '(T![3;J9[ @Km94=\ 8_-gO f-+ o=~-"7MP'O#1^aeD/(kM3 YuU`U}U l*yh-}x%X|%~)-z I;c#I8~\3A.ːr]L'rn5 v8d2c!cݑzA$)G?= S{A09iEo9q{)@΍&ݵyڕDbq eZׯs^ǁU -E;D16Hȼe4 ! '#zJ~\cJou?-=!t0}?*H ko zabLV@~%L$ȬaH %dWJFojS-^Bj:M50IԐpPGH]άV7K:㹜\PSy1 jEY|+0=Q@`uq @N~݋^%D勜Ȁo>QZsqe#'Pp3$.=m(>o?w}}K;J`z?:Dw Ma-._67 k,bAYݳ7:|9ZÐdm<$Čt4xU$cͤf-IDB-:&EzhSP])ٍ̐R~?5M+{F՘P2$#k.NFe}qj3KA6?*钟)l6lH憢kEޝEҥ5JyF `O6H·:QnY5}.B%ے`Ef7]-9}Y7Po}t ˋDZOۋ 8q/N, eW^F" AF/R]Ö́KP/]Cz7e=LgO6,1ygG $983Z36UQ ̎MwrχWP!,,7DȾ-dDN<YO*o\)#l\uRdUAQJ jȐo)⎰ȏK_Z RVNJԑ9ʬaudnF/!`9Js @YTHtl!\ U4yLJ&R/!32J$zFGaLw;&o[>-Mh9ߜ/ sq !'H5ϼ{2dzu")yvD+p=wv Uuo/u,VPbXVf1Iݬ`0,}'(u$Oh?,t>ݫdy\*!MHQ H(+j|xUx[rt-埼ҪjǤӇ]W&"}!|:U-/4=h! BN&B \dGツt.\*LSN 9>_G{q8ʂHvH{L̟_\yQs1B:T4^B`X\S& $~LqΩͻU0$Mkd*v \`g$+M sV<4:CYf3]y8q:# A"]L r9?Y`7hlZ_մ;C,^%sűB"V)TMI@swmz(NO yc0xw&8Dool&׽|*iL~l⯎;3aPXWYeV=wݷ.+ |e[ t|(4|q5 ~\G=]dcˢl P0Y[i 5_(  LWhܿ PaF ;Q}Ty`w-Ⱥkqu'yO#d , .Y1BqJBW(f0yf ma3Eypkܛdᴧ+!kI\HM,?a&Լܖ{`d]KCl"""e3E,2VxhZAJ0|.։Sgi1x#w@֝Z'0WV L`=JRՈ!8팠`vbUEn@(E"آe$2%jj._MJ=HXQ"Lr-wP9%AKp1"MhVi2\:l*iqg6 0nkh 獂WěȴgT0!BsE4]AM9 '?d#7%:%j,.6]HP谏b-sE1kUX3''okzgN˘k|_53P۲5G~C`eHl\f+ᖑxaAM|X0LH3kʉ^M7|E_ 4NtR"آ4MSA^M~A:V;,?# & wg]UM 2a<.5wP(o uP %>U10қ17dHtpmP~18Y9T$m5N}w{ ϝp!',^Mآ$RI~2w,E> :'[A+  p) D{U8׬f~GS"Zƃh\F!|ӓ}a!׊yy.+8*6lòR;[.؈X *UCaEAQ R(.KotQ% i/^$Dj'uOB+f vPiF9v'va87X㙮O'+"0*`y{R[c~(s<RwjXyz8(C(.sﯦ6&ɸ55THë_Wm8^U,.k/dԁi nr?'b @bZ"ΨWKjSwLQz͐ k(eᵟL0_lCDYj/eU}K`Ɵm;K8% -Z$rcl:^s4 5gK1?w RuQc G:^tg#Z <Hh@$tGCRWH Vd@2gUV?b̨+cAr}@&htN Y'_j:-xVUc#vM?#5K6M E";8>^G&rSYF ` 5/sB\YY? 򿐉⓾1=ÈD.nYD%_EzC Qi@RȘf>-zsZg{1.?]v6ѿ[ҫYLX!7T>=NzoybE2b[3grdHw_ȏ)լ^^aOĠK% ,PK/d3D+0Ja%H yUWqd"5nv.C'$Dk&~ )s+\5eUT?6 ugy2.^%i?Trʭԙƣ2S!i`16ʛŵ͍͜!ɴiL1謁eTyށfÄdyy_UW5h&4]` 5oz C": 0~6+"X)Z28}~M*/1^K{/'W.%;aU)AB2R[\ϾW:8T]˩i꽞72t|ۥdp$ y/d. { НdRdѩX֜6u [9 AR? dDKd/ޢkUz7PL>H.n ̎" @ K6僎(̵HBVI҆M^YR,n* ^0youWGXy۸ RX8"n(Rf[`Ǖ{%$!d 8J2H*',ȿvEjŇ ýRL\lK͐Ffg.w#2wZE3}t0[ݗ-j-dt|㔹h<["'C˰n#r_JhA`EI AdXERHBecCp?`8e`=V3mu:gfDN΅}IF#T^:k增=fb=:bVjB._U*c[ _q\9[t]UxS*\%9EuB֢2{Lu™\p$rf훻 GN #_&@7il"-}Hr:_.Jj#VS=&UUX;i;%CFpgU)0y\~SrHBn T$C1*'K߰qu!u v~fKun2iY+K~tz.vrERBB50ydąE'_ gp-:W> y< ebI>rsCcB?I4ܽ!cfBAQ,+l C,-Bt$Y@z9`|x{!c>޲$!$OzG,$OWCZTN`v/r1gwnTh9[[Ǥ-?uړL G;)Z_$R(!Z$OCKM[;r4cu坠; {ʼnؓt+Xcc?GCq}O% #أCO ~m'xv7y$` XgM_7Z q֪"cxrAv y|L7<#j*'HƀkiR%4Dž|Tmfct\rjnV'M`."Hv=ib}ݬj͛CpTN`s#4xq=̔fNˡ Iϥ~`W|NG~?^6r|")DZc"sMZx0jk9Oqbs|q QGǑ=֎ ? ĸ|WqOIV1IC>PY$a%v6P9Xu fIzƤ0HJYEya*OQ-YFlk;KBd#"?pؚ~M}zj"dhHRGD9I*'㺴3E ԰c߽53J-w͞_W?LPH!&uh-z ޾mN(/ٮ٭bǶ] cGØ*E@lɰ=& w_dYVi\!YG|~v4o_:`j?z)Iȇol6Ow-[}W>gv!}ktuhB1CC-2}-{+ycA0zFi B3(>5 Ӑ QwMgyH`Aeπ*HBTjLbvnO}_].F12'}#zg%_j̨e;z:o!_5{!B6s3ܵZQ:vQ=<1\ 2a`ozr?ȝ 3_IJDŦ(Mteh jqJ ֘XGI ȱWlzw̵7b{ >gd|b|䯷G +z/15^zw,7emZcI:J]`YG\8wڛOO%71!,%]Xͯ9 }!HBr[@Ǖzm@!sUT]Y=..} ہ̨` 5̖1A,!?Iƽj__ nܯHJuݛ>#>W`Q}zœn}$L٪5 ĉYڑmJn\mƕi`WjE?ܳl톍׭Z sa[!?IN`en@u:tlQDp!/BO`|JN$[rwnKX_10#{%lDsj+4k߭Ç اK"v:I\wo%Cqvm 껽"!wᦍ7lX~r]v͚իWQ0E=VBrxi,$X:WFkRxgBvؽG  [Fr\"imՉ$yfh-rdQQ|Xxz Mm]jӲa5WV)b\p2` bެ x*kz_۹qv}UV{8Vz8x@=w)@m/C~]UT{Re*?\*4Y߽l2g~@x;٘7dzāVn-^͈u?wbVD26vn[X$8innPh'[_^.aCyJ5ҽQGnY_R7 h` o隿 N`1S f켼77u|) ئR$$yÎa_-o nYFA/G߰0+@K$G3 `);ϊ#H>FMouM3pRWk|NrwHvmI9 !9W]!qViШe(KXHYx{2e‘MK/veW(_\ e˗PBi ? ר g6Sߝh#!>m!-=W鳑r]{U9TzTQ4dl<1 5դ=ޟ7`aLA(:MmCK`dlj0(,fQj523 O.Ym6_oZfz xE]^Aȧ#^b:I9 |Ag3;9cʒUq/EWձ/jJ\-X(rK^_%N\>s؅ǎ$HzرWQ;|'Ak#Z%Vk_rŚ 0MOSHG<~%ʷoy.+~B})׀kz#N~`O+5Zu^Pv(0!-;%uɔ֩5 re斐ʹL2 4b3a}4{âZ36b ?wc*E>Ƈu# D3ga_;R_冎lK|D97(66eNqf.zZ K+, ܷڇHJC#M;a^B>S^cJh[i.li6 I7"Nyoli>;ˠ}/զ.? ,ۏxڸ)Xk7Vfp ںjA*=6 :f bZYA!upZo5p(E-4_,8Y 3Tq#JYnoс+[kt/AH PsG~MdJ[CS4VNh`kѯI `hiz3I?v,|sb[&Pg͘6X"Pbp++[Pe91g顧&˗ tʷbmڶoۦUUK`8ʠ:Qyd),?l|a:{y%PЏB'$^R44rdL+X) l(@\QsK o`hw銺PƉOQ˙ýlc;1##ҽlLr_0d%`5eK9{ʲUFwQ"VYKcYݚ; ǵj40"%I?oZ`ւnHDh*0~W.ln'OmeT!gT7,k O'=3Rs]nu@j;3Tm23YddWpFsI0*@V$UeABMl3>0Q@o""\D2l(]hSWd_wbcoU6;Ne |uЬWY3yGk{&ZE.t.8nB,:׮{4:$*QGhhI*x܏gS2@(;mۺy3xq˶+-m` xjxW^~M~ғ+̙qoUg 9 fFAtg$3Jg ;2b7I#54HSi܌HoֆM6nX=}I?;%;(R ."CZw=yD9WCsގNOgo_ù3s:/{k@87\[Å EArLVZhx-?[ l;΁2yhՎ<Ȯ͸h!(k^MU8h N} 2Ӟ|"5?x\BsqzK@z<Eq=hq|$͛.y!}\ 1G^g>rrVc &B{ X_)y/2LQS}5aF m63Z ]nH]bgi{IM6Ua@E.ɯXR{]bT[mNؘh~T&#{Y'1,$D}IVssg|U(`D ex,Մ?t: ilӼz1EMЋ]} ~@U'G .G/~&+(,ױզO_QpY[?0v8ء~1h:~ 8uƂXY𚇠zŐyrܿv\vs$L໗m.{~@ ydnj_0Q"!\<:_\q(\؜uU7,+Ҕ~!uqs\a5$Â>w,R 2'J[;B+ ?==6Xoyueᵀ{L(,MCͻY>]' VYHE~Q~$k$*OqZA0sv &jy1jhT61ԐXF-<ޣRbUj# `>kq/{Ldu=G)_ѹ}z5c61F*7BmK+#~||y}]s K&ʔb&:$tɴL &kY!եr-d$LJN5!$Rtg5ݷz(h^7r2 M6p~ A$d>Y{~G%Oۧ-dhQGM2q@ ͦ$L?_{"UTp 1a.Ӂ6M1ju]LE#%cdtt'nհ\#0@W&$cڛ>1l#IN_tQ${a 0ݒ~E+MF:f5_ABk: tA5+{ B)LLM4Z02֥Xcuqn]BLtliI}*ԤO7/;Y4gdzj$\e, :{{9s6Cڹu R΃sT(U]8ނh#\v MIYcbgCg56,WXoH5S*E>\F7dP 轡s_X]S)B#mV1pzo1Jգ'r` ӗ]~@KPA #}Oi,>q!+l^]kTS tȜFhEr>V[lT$gQ PxNjWCK#Iu!CYqt92-9ΰb 4I!/Njsq:W71mZ ,^ u9W}:HcՀ.ݹУS5K A"Zǻ*ki{JsTNe =t/\Al} @fX`)݃\| >+9Uy/ӡ%2:h.e5W(al 4}]F;T(GP99A L^&DW2h eK`Wr@>J % 9rmb⢈ Jc!!W&+gi0i]{Qy!$71˴GA2JgWh9^=$ `^9?ihP[Z`F_t=R.lөw@)[_gX2FDS_fОyO1[Z5{N H鞮ƶڔu^Z#Hʳi˜Ac t?RʝP5튇]AGAdTTDUE=dNj[ǵDսy5E  J@>Es=1~kJ!\f*XiJ%@wa_U?MΛN(+r?׌I煌2)(8'}CN1(.dkLU7DW5D 9]26ͥF[xcCu"߾_S2f݇cShCJH꼾iUޒe Yٓ/3D Nb*d u{N5Ӛrzʥ܀)  ~!F.O?KԾ'zy4,EN~AIc zQFGͰ럎rUobN~>.^'TJmpn輏W%w'aY7FN5FҢ:WO7B`@hK5mh|POe(޷dK{7|mqGխA:S<* _P]ER)Q<] 1YB (˟_[ ) E,Wy!Dcl?JēM5da`!v8B sZt |E}b=%?{ 53gHIMص]O2phk+] Wנ~YM`g?-.Wpxޙh1@&CUz0SZqē#.762y:ؚ+W=^M:ƀ.!*P?ePXy_@ \dH Kl:oW{I lo|w6[n vXYtVyp 0ȡC߳g]-n&zw?yA?7+d=JFu$+ׂBYxx*$ϛޙObkoeqKÔO =b .NVE*(6+|ό[xqA_~1:*X%Am'Z>C3+ElnVr^X`'{H:h6I8U{M:TiQM.MUK`H`cĊ7ff1#WHh|G:(Q2H겕P @5D:=D3P[ xxA iF \QIXI`" WW@P]*@2:,i*@ h. ^.C}K hkZoT~W ٜE㝺w@QԉƪC|Kʠt"Ah]'Lܗo}RϦ^(Aʼnr؏q5@?/MƃhatwP8A<ԣ掌MYا};g2;)zAh7oWe̩Ġ`4GƃR{-u֪fy7 z՚ҍ`ԭhV4 f\X ,EjhߓT(ڣ~N3_q^6$icSAցB>&) օBQٶ2@J\ Ή$'^2dGWjjH> !{+s<=:dkYVw4-$[ěfP\gଔ.MYt<CAC~@ yE]|AQ Bcx߭ojĨxXI"Y N՚!2\[<-!j/[Z8(7`lE6{HF!kO7J'@-EqZ\-b I=.i!㌖毈Qe6[d8%q Rqdl=0K;%f5c=FswI.#tIBɭS 6$ sDKT,%R\QD}D-ȼfx]&1q{}T7uŚm5zL*־+~R8B⫐挌T$|BT 8I$X#꟔SC/(܃lJ;PH(?)^m~u6%Нcgr1GM7UE]T=h)X/zq _0g W~QpBeIںo4c\14S~5#U'qŅ"x\of?`lOxs^Bٳ-<!&B. Mf[s筝!^I :]vwGq荿x\X߻eNx>V*-#6IB@gȌCƓ΋oW;_^ 1ڢGJdӵ/xtĚBѠ - x ;20ɶLi).7IUhRD9`VW&L.h(UESx 7U뀩QR^xEDF 3n8  3 ac8ҹ8cl& C`5@Ie@+B&C.re jJӃ-!dL}*@ SfZ*突#aUDwE/ [1U(Hq7)aPF8Zm֪O5_F5hjT́O:؞3ťe~%(D׀'jjdY_rJgB+'V87: A QߡLTn` 4NYL%"N"m0$Sk̦?5[*`>8[s<V{D~]6(@ԸEFefI(|RF.*kꐃȁxB>2 }kgtýl*H`PO|E0 Y.C@@nC i8P2HKJΏ-AQp3 6ϛgQU9`F,V jqF\/#bZa +hk ۿ\8y ?rqΤȏaу9[{V敱^4*CO DR_tS_Rxi\ \Jy=𻜊p}/z6f^YF/Y~ I3a43OV0t5^^Dij-f^>,#5wL5l^E^a>:M^mл̿r}e-uBf~+S$ &?*96D~[u'ZsoqjVaGmܠ^Eօek5񇮹-`Ȱ#%2dvLeC*qC .J;cAd󗪰*VEHUIQ40^anacK+R7s=GA})FZPȶ6n:v׈/iN_?5k!hEщ5eA/kK5 ! ْD!62JNLxF(uISFJ*Ӹ| DL2z$'*yv[Ƅ2eD`ڡ*ְ׀6/mQ*Y4ZtFeLXg vo \ԎWzo1E~Iޥf҆{,Y/G7 !]-ֺ'Ő !`@ˋȕoY:A)tvq2}V)G6M$^KJSFpk#?( _:|5eo BoeF߁+ny+Gy0dq/-"7ba(Dݎ|eZ@( lN3eF"#Jfxz!v?b%ފHQ]IUÊTg &ͧ屌7PEdC]NuoLj^FweDqglf>o' X;H&i%,p{ٴ Y}#UAdxGa|Fɑl5r =!suk)Ki{p|%TETEr%hG.7$ :_%WJ6rkv4ϵ>.ػ]NSj74FlK` 3ɹڍ"Υ2",vw%2&#!1$0 >Eϼt11N`SŪt؜9sCheaNZWo2 0q, a~yGyc,+&HYK/Irsś fu61~Ky hD?UUaiٞr0 dQP,#0vӿeeѡi۰^-jS< ̆ Sr' nU W]4Lkb2*l$kaI2 ,B]κxC {IdL >ߞ%2$-Azm8D.}[>oT~ˠ$:0IkMPKo;bدȘ0 m|Q.*Uj$!bM &$6';M}p/ozܸHr|kGd0nd7pM+ג(a2AD.~ fBz aA׈C8bZh8>U0SK4ZWxAl)ӎrj|)F LWI}?2/{=֏*\"Qƒ|#҇mcFˊfZV\Y~K:W@?afB0)]eă0{" ؈2Zb6 7*ɀ2ddߗޢg+$c”"$EYpq$4ļ D9Wg3ew[iG;9 닶bes@,JukNO*t{^[d 97\Ȥps"6q?z KA~^`HJת=u/!]Édg|׿ bJ(ލ+iˮb~r@Xovz 5W@Mi JMc}n Hn#'_w:́9e -s.uə> )TྔtOkbZTx)l\gig$X*m.b.IAp 3޵*qw^( i"ftet[0'֘ـqM0R]~ 7beĦkhCdL*dzىF|$5^E əlQqQ^p'Euo</Gʐ/nvwՑsK;9b'lU쫫_!H Yf204da7ޯ A*rN;9R0=(?@8)-' Y%E~ RMe>Db10}i&cq62){zY$r~ZVÅ!mk<2E`,(FAgKAPD)=(>;]Hy I>LNN\ bj.H߁o2DiZ M>z .h%i;iP`/$ kPz.cB-QI]M2&ZgP@K)oN`jr&ReL% T$<+%nʠf!'w9#(ýl0N`9n,w7|&CP(ܖtϹkYN GUr5~|CWE*R(' s&ͮ9~RXQ n,ο$ID$=eA U0Ci;>eS<|W5STXx Jr]1h"o)U}ҥ#R?YL8|/ݷWhc@q0ꐚ(BOJ~ɻ-#8a EdP 1NhՍ$˼3<= hpH+<ȘNƄm %iѮ+sV^ﰌdɘ}Ro+e|+;H39O|fP1h Hi;`DĻ-L)!eQB*<9r<^6jȮU" sRk@͗!9w' AK]Iˋr6)cDSio+BkdlNNs"@zs9&:majyA5?)Qqn_ G&_ xY#25TDGT"_PƷ&EeԹؚi Y'0؝U _F' k"fqƱ/J:,SbޓC%D~rB [˘@y=Z5 \&Q_5iT.r~f0^yr|}.qJ~>n P3YWɖ7O1ŝd ƪ۩6@SBҺMe-)_qN2N^ʫ@Tæ ]#%.r`1. T7U2qtJgQ'UPH,rYv$$4 \(I .*mM&3!Ɂ&/rL*O.$8f4r5'sXk:bTpoWuqPC vJT~*6u<(R2#7LNB٘Έ4Lgga؈mnHɆ,3>LR'IdLj%6>l9aи I_Ylƅ)8>&ƴqݤ"'6)#uᏌG:09-싖t{gl9dM&UneF*)C1ly"M}غyvccփؽl<$WIl>.yίՅS_fu:m]bTڗoQz@$F|nTrOq45fc Rr+$ȉo }ݙsщ5 sV3]:)(f vտI#x[L1!u1kZȅ^H^XR'rLu^Ғ]`Efi#sL8˾ߝ :֙S(S gC^ f(|Y{$_NrylO i_t^c{/懣.]܏7es;1[2ݸ΄f/7f>^xL"\dOyfoLl02lHqOiQ'$L;E_#BuY2i/jPNHbٵrQ!Am"L lP\!ekJ I^6r^,׻/4dÆc}MLBKDО`3N9ʷ;E)r*CM$(eJ8!~ Sz jYh.iIc]|rҠ*\d@k1NIr&.FtOxk c~3ACk7@z7QF2J| d z ;jnm,6Nʊb/͓ܚiX y%E_eV#M^ú|`HcA Ө+NhǂwYHWq 7RHs_to%̻_HEjP -<|໓qVj$C17 ٽw^Ţ;&i>׸tgx݅MXdycFL;6n֪ ]NRtwZv(ғ1o|kτѦAMmcĜk0èVHÐz6hӒcix( v\~}H,ŮYi 3{*dJΝʦyjYh˳]pucaHw58y_i}+]@Ե8i`OZ!u>+dT+E㌭35禋d ES3/mI|~Lf*LL&mPYG6m ZA4z>nS8QZWWs첦|ZZt=uqF:1+D3-YvsøCO \՘Qh!m+;5hʠ҈*3rigLA,ߋGZI23Tm1Q'3=penB]uR$YVIY=W%o\+t!kF⛣mιAJC=_!2#7>5\qw\JFIƘH5\[\^X_qۜK][N(G ؁'De3_g:x]Qͱey،z Ӕf`ZqC3TGG(Fކ#y!IQ_)!^TY96u=Oȥ5Br (qHu5yv*~gL1zĬO aV$MW-5%c :YOҢTOw{ v'B3=肛wO8q\Hi:s12.ci Eb洐RENG*W!3zٟ[=ྃחiNTʗg:7ɜIV*uY۹RI {`$g0 K>K6,v5x7$xW#B3=EJFUk<%?: ~ S3 ̐Yӟɫ#tO숖=Ʀ7;-B&>Yhb7G$,%v~eaHGUQlN}TBY#쮾؞Tw @I_/CBoe]zE׿>x_U| Ȓ;_,4k)G[͡0V8+]\]!?Cޮ%d0op\&q[ՙ:Ĥբ3zHE:$MroiepCkҿE.r׬@иE̕GIzx擧wp@[hH.!􊕫׬Yˁ5VZrմR΂Fʽ :O@ #-ERi:׹2mlqag%0Yd UB&-۴B4rCp4`oml`e+RXŋ:0x(~bZ=( GzףF79'Y@9d-r[k{NuzȫW CPx>Sg֒ɀ2;s Ne|+k~s62XXۻ-ZFsmٓWʅ0,P,-һڃP4no,"]k+@β;D>mَSw^:>/b_;g͜ =*@w[!*N*Rhw/cD-\ݲgPIDZ9%tKEWˋ `ҡj[W*bQt{zźY~ҿ0-$TYן Ο| \%ݖ%T<^vd3l>R{Τ7Ҙ?r,c~8kH K >Eӹ E~i$AcRN>]J=74hsw_'R.?KK`9$XcookeI4:xGKP%qچג#" ]AπWYwU6yݘd.14u0MټaVo:r6m2޼}@nߦ6vlW^r(Dۗg$RWHy)ظ=u[ۇWOոP&uϩl":_h<+^ri(,x /pm)?t~i͓wyK`fۿS7({.^=Nƽtb~a]}=VZcs]U;vNgΓ=~#鈵g_D%ȵ#saOݤM.+.YUeBٷ Է`s3,p|NyaPr;2rrpFCV?,XP4zjBj;.ygrK\PYW)Cq_cL9ⷐgs총8 yHWaOz}j Y}{z n2\ڢ%.%AL>uM]|T^\a3PJy=W囗 ʍo4Aaߛ+ճvr[zsr<%C>z?WsBu~S)z$g 7q]/o9 eĊ,>]itzW+;?i!'eҜ7@c&?zUx4;*k\&Uu4/'v:BNz~gKLp7Af9ls<5. q]sgQ9. 9J Q/Ч;}Od&Y @FRWg|ʧRˮ2jrJٕ f8+/dY ;rjv?3?d5 pk#_ cN >/@܇]t65i={f9+]S hx9)i׺vO7% . t]\>Y Xo,{Ƴpg#o+p4^ϊI-$#d!Ƿ/_x]}5<}0.$8́p>>kWgϟ5sB"T:Sl &ݫ(mMOjiیǛ]d@qR@.9>/2` .%2i(qFp&E4n<v򐑹[}俔uv-&P.>w {1pc*<~ fWF8o y_ctqJkl&cr?p),ayMbL9R3u lRTOށ|}'f%0G5m&S @׭_1-\71h)DLų錛9>;1MIT)>+B&+5}9(gFNmd, /?$86& BG~Md{>ߛkNM!=Sb?]ѱ4fH7xS<`3/*+3gp_xуwo_(>Jߒ}ufm fjt\/.G)kWa禩) sV0Qc+I`~SLZӡAg 8&P+EtiKg|X_%@̮E7_ zGuCl Z_DJ~-Xpņn;v0*iSIB|AּM԰ J)'O}O?Er'}{:LFcVI'sёbqr/ :ӱ1ѡPf j$yo<4wFA^ܫMk݊>xkKE@>EW=G,IP!Rz bҗs ,AX:)Dԙqnm=+ O!Nrbف QC}XVc};m$: [ɖZGB JI[5WtwQod]o\(X 3ȣZ FtGgS_npEL1~Ld)7JUw-T a9_[ dJ#+'fL$bN]Iqa|I\^h7u7ts~g=5̯wn9ꉓ*>UZy d; [GL[K?;L2AATsΤ˭I[W?UyYzGI?-( |95tE~ק߀ACnFeO6ՂPŶĤ-H@)]`@~&5ua@`UqVp* Y/b3Lbpw,q}-_$@4T?ygu|qk2aӉOմJ+p)H| 5f/.9='Jn+]TzUmÖc,LH՘M\ŒڊyVָCgÈʝ3XwW sKBP@Lg|jjVwPU 8Bg6+SOJX֦e<91Zîiju]dD.9Ơq Ph4fӲlXYJWL[!]k7n쇍#BfBRWˀDm1CÚWS>y'E|2va3=)&Urr X5s 8LЧ_SPsͱ()@!{7(/O6Z+7>{]a1/`S+d8ī.t2EH9/HK ߈Nו|by9e5OO,4q'p"@fta©^~&xƫO-Y?巤ghYOħrb?rSB}cjGizҩ4sxC Gg/]jj4`27 xcJYM=Z|pZC[N [lٸS^pXk;L8"ĥ@'M V5z(F1iESFGȨ&א2b0zQp(_77qot0NRL2kBW{o=b4QGK1I:o`m>S qҼL$Ϛۆc +Tlk<;2[bTPb3@UdHQ֡ݔ%.kTRV]vs=;&pqJ&uA=iodUV%%vSࣧ3+C~`~}BDEKV_xA6,3~JR$1˱ B7m3ΣWFkdT6HQ:p f~;hmWv9ED I"2cbU$D Lz-[~V7׃_US\?Ŧ0˳wg#sݾ1"hۮk+:JK.JUE,o؀Qڵjju wl]GhYFNTQ;{+7S4:Gh5y* ^ԻD\#$ޣ7'&6^:0u ̦( `gX5`+DH/yT'οeIVY2])+\ ޲IJl^/pF*B*ä&*2W]sc a⛫.͆݃+@7QqKa*6d4=S__ϿԈa%h Mq מp$c$x JηM((e˨εBۮSgѡfGtV,ׇWpUg0+ WC_M;3r0?uu{kG: "_l8/.T)$Xnm7{|clɀq > hˤ]T:t Ϟ9A(myHWb Oz8D!>hBPlS3'ID5JgDNUp )ƓB$=壱oZtؽ_XSڶ Il9r>Ok(`UςyE5#t>O<O)FHԥ!Ĝݩ tXV_{һ>htXLfemqs lTU܉E2{s&16 {GfV6P a.lzSoMAqVu{n8|V'!3Mkn(Z6pTOEdƳڴױtJ>LF5Ue8,&K&H@XyFlJ\0$Ze 0XJB-P"soiQEeE5MC~1Obp2'o YUJr%Au^8v{wY62 >Λf9/ڴy-[jо۵k׶D&\7VU4xpMMӋ2Е<~MT^tVֱDlBvΊdeeWMRI⓰41(Ȇ: 65,̣Ƞ%:p{7L@'Q0,W@%H )fZ1fϔ4S>;7W z@jo^yض!:Qs3%|4g/!r1OFqb Qrhʛ!j9"5i@M̎\ jIzp f*W>~{0Ɔ-Ȼٷ}e[xkYJ3B7_w|"?% UY; &*|X t;Rz,+mjϸGIG1" > S1S->ef)"v94?Ҋm*'Yb^"Ætvpj[=3t_]di\ C*мh>h\#{jæ$AG"&9;C/JIJHHkȡT b r(MXJa PgTZm >H4wFE_fhGn#幪ePX%`5lHI^~aR]'liTeUnS44@,TlbJB"@_F+b ڝZ$)ޫ'9n}2pB [8oRc}ԫh!fHDfo''GE٤3Q2zTj89JoC%zZѸ$M2[_J9 KJkH*ND7(;K5 VDBeg#.EV Ք:MzL3!4&OUkJAUBڍ;M?5OaZ~l22w/~ㅏRQRjmv3"~+V>)ԫ_ eE~c,aM/;_d^)~tbOw'T A I=V'yrm8'j TV]eVDћnGn*(@tU| ӛN E)%Jc411kUl₤B. U6ǐM74ݚC): 7@n;w`֓nf+suZGz󣯂[ݒ00N.j32D'\$/ƹ4rV=Yki[{Īwݙx%DlNk1mQ.I"\9QHcs'bЬ6cy^QiB w+ вnU2B??B1If*0Ji[am?kJg;NK=ƙ X[ʘx4)/,MO!P` yF|Z|͞}LUkZ"aO5H1?z3Wvpe5mKC?fa9w $u3t[J ](1GPHDvP |'/nGLZL+^cUZm"0jdNĥ6$\N,a![](tps7ħtF4EA$<||ݵ6 vZ5ƊL#l<݇beOu /U7455Q%B.'s+IRʚdzRm XwFe$z5 H[M:A2 w& ' Z]1wHEJ:c⤫۴kC ,K )Z0Y2r'-%x8yEH!*h;˗^!0n*SϽީ5QN F-6/h&nSb<(~E"-uJQ~Ҳ|2eVPDbԚͱ3Hwј޷Fj%ykgCqSQ/̻.Ŧ:-֧)xt:u૤27tT?n2|E{G;01cMGﹹscș{O?|x}dT'v.*>"y\`y^EOZ8QOBpqL &ưk7u56V[;z^|'Uu%S| _IӇ[7d7z_xX+HT'}#_vxɊ7Xntq+7ǔ>禁/K!_gnNVJRȺL f 7{m@0a8C_CAj,% *աPu:*j9zњJoE_޲OUj! ,IE(z9T~C=y]λ/Ώ>|-"P~Wք.^M+r- T#6l؃47c/7!cY[%66]iUR%M8-{X] Y` Aw wl]{ 5cŶ!@W"0 2['k6Vx#H\aƷ2 %|v/7Iڿ:NP#ҕ""}ypKCԼ0=xZԽQih8:6!xZ^LX`F_4-鍫90R* ,g"- *`e/"G{6!0*|q}9g1Bf6k!B)emar/5CIE?(ϲKs3P{njCĠ"2}15I^:*?QS7SW>Eϛ!}(&_Pŀjrs|u8cěL( p6|ϟ9 6R8fM fu3+yň<ce } dկATe !jBNV?zbg/t\C6?to9[fY?[pQFLg~ݪԄFK\P1x<YZ@xO^L&۔12kewEe _+}tIFҟL9KM]dHF۩EUvrBh ٙ=A7 yZ]/ݱ8#%|q^лQD.* ȝA O cUCl;!ۉ#I͒6cl+XU2tmY!l0R_ =eX\Sz!94CzgVUIg=;\4SƁ}^\u"a vcz48"~2>NʶϞs91DqL8f3`(NJXTA;3ӂ7PG*8QF՗pdbp335h'g J C1&藯̌<ڷ\{RMxCZ&I>AUKg ꡝGFsj"/4ٞ*݆x?ʫ)=ЙdUf2+㍑տ(;wSS|%'`;WmTfYLj˖,YѡK|͆j  ́ dpta<%B " ĐO6*fP7OA^4-oB>|[hg? 'tJY6adK%r'{{7ޓ8u>՛~9Yv|J(7-פ bA$Bca3dAHc$ u a鬗nf6ۢ>@n-/WZ)4%ť0{Z9 ?t6m1v] _-EMd?VTZHPU*P'0g[u9,Xj։+dL`j-TbNNb$T?ΥJ %񔿔BOTx)+R0̛nZ,z47ݭ!%}μ*F]A_YKn? tiWщɨ1۹%#*ST]hpftF)Dj([îYf%Qmi=@'L, O'~I0 <٠9 9csSB%tx8W\Dł$J r4~2/:\L 8 `nPѯ/oswp>Y1oĖ;COeuK#qH9plL\Ys䩝>N)ue/3ڵPOKG#e31d5_tp,{M9`i!sR2U;?P]\ZɨyTl넊B2"ᖸrNYG:fdP68Ū/,дG Y㩵[RfZL? #:c~ e\|J!/`BRUTۦ~ o[3~Դq:9\J#/mږRDa3T˲p  6͖^u<7_wPNUp{Tm40p" 1Kbuc3D_;Ǟܼ JbfTfUj(7={Ȓ+4@ a$ ͼAs(/!^(~-C/61޵6@Z12l(QpMeMj[2K;'DqKd.>FƔ/} @64]kf1i@퀥l WYaw-1r0oṕgT#hbI8IP/W/&ɩbKE367F)fG ^x0Ι5$V&{\\]L0L̢'9zAbnhE_9^B]S>  cZj#2%SĢzu%rSCz2)yNß<]NN/K2?3M:KT&z"ӡ7 :k%"7׵O2&"cS#n\7~keWNi$ 5˔v^wIJ=Hnî'몴ElqXfxڍ?ӯ_E[3{mq-q7g[5 *#n9V -ZcLa.<y08? ]F#4C_ED}īx(OPp҄1嚋<͑ =~veB9Tdܕw&RɊ[2a#҅M1'f _jm- z~1lrm\sZ/:g; e,8G̡ELMgI"p3*orAT RҎ_P4T\H="6DŶ+$QpT)f'Ѭ)J%{Tv[>/B>rq_8ܳbQO6vfXYʊQQ!#;rzG4": 95ꇍl?I *Y {Lݪ%Q!jn+9^an[BSԀУ1!5!Ȏ^25ͮx{+mZY-m86 o7vq_^$*iD*D7K uWPT@Y켫BD[¤kkcWPx\w(.U \sl=*jдYcb+Hy5lGp䊰_g{^AlcgHPiƑAfq+UVO|B€#uuМ:L>;╴)Ïsg^' RJB l2..k͊92or^[sOI+# __ׁ5O{\_/*#՛ U+,i7W+: 3(IKs@=OW LKzyK"@FVl6o2ԺsEmb-m.cfbĠ)BUpaԕdFl103zOxUk[+Cƃ,eʭJ',:ܬP!g9Ж 7œ̎:m=X\0 YH9DW{)Y*da9 dIZQdTb`!hTL٬nPv'pklm=A֮ۧZA$z#1$!= ! p܄hoMZF8". ?ʦG5Cn&\u_LҐN~_??^g 5a.Z-ȟ OJKv,ZURgEGRI qvE%F$xֻjGZ*v*Ggpg/̯Sq]UڶCA⣳Eڦ [lA:|~ʼn舢yJ:|&UAUt3Ŵ|"*Gn R8yhQu;BHݣ{y,+k;X5'QQf YGE%;qԄIJȹk<Ǩ)1%'EO+kAU.'6W3YizdX'v<-"G2+JdAE873=E ddh {}<ߙ.kU* ɖWtxFj:v$r1OLl|b2^2Q"ǩd7 /2j.)/lwLAPBi'jMNbZ߭)of<*($IN~5M^uߩ/>Fĥffk H `6y)I `RX޿ͫMn@HC%9xũHǑMTRтY݊*XmD8U傧rbGsWϸ F2-boutvLRQ-e0 Ñ5gzMt?G-k/D<儸2 + ڂw$I'>T3l0'_Xu}x1m]mIᗿImHiqoTx‘@.hV 9`iޮcq D\& D9@o:kr:O92FPVQAӃGxUxbl\  pSvM1>:YfxJ'ԄT.B(Imlᳱ{;Qg*T\Spq֎=Jk%!{'BQgTwj' Ύ)xjI b Y;6JI@xɤȇS$>:+; [O*{7f{c'?ҲEJ/y^fOlMzR# ꜔ $21a:#MK.ۖ/tNj;:TBStAfM|I/1u;~^mܦC۵nբy H#szliIQ/jCn 735-L<`ᒵ6y`>lE.\0ofl0Uᙥ)г/#3A"&m}!GP0?oeLq$]*6n}ݬP=5J̫{HFb˗wK+!mO|mlxE[ Yypbfua/ט;cCd5z \ٳP45#=@җMD^l{UN&dGjpJ]?{iNB(;)ͰYse_՗5 $%˜EfR^9ڶNNb^;M(~Ȁ: g'J? 3p@s8 ijgvC05w4֬_Q&u\:-\8]6sôIN{lb-o8|`5ǎ;tP ƺ #;uR^NdQ q^7 GR8T #Ω&59㧏a޾}밌/_>'yrj.7E9AZx*0)c!֋}=*E۞*d} `x͸\:9&LB!F&29+fg$E}qcz)M:*6)5 |\HJgm^:{R/+aQLhh"H" b_D]7t*[{;KtBgu(۪ۀ!CҤ+LZ{{:4H,߭0`dx$ؿsz zVM7UW 2SobJyԆڑ@/xͮ`d̜AY46vnE7'N>_ hfe{ὂP@(mw`{'XǍ>_[vspemS/x,!x $gB2k|XE}lƁ3줕a\A@Ȟj6f& b `؉ӦNi6pm o7l[xb-cQds8B@IQQQ"ca %6{ĩwiRևi>{\qVR.2Po@6^}E{[;:v*"`J:-r\M]&SԲb\Ξج-<o3c3 xYE*)WoE*5WR輫A  aA"'Ucٱ_/#'6!ǔFNtH|nuj1zk`ܨE33NR_@Wtl&ENʶ}>4V>3o[u ,+UbGa%Zt .AՂȟq3SNl!NР%?GfN~OYm.şuq96||sM{,'}<%VuDu%}酁;g;r26{򡨲O/kr5wOrz`{EkoNU$K/HOҳ̡cv>rh Ex4so3L<’ҿ]Ckii''Xi꫞GDxwRT\\H-cK ЫWo@ !siaj5o"Ukթ_6KC_ 5|mUN8=5˜zo(}8~li V÷2u{HBByt{~/ܲEЀO?<;ޡ3Ulbmv5ǻG*l5iQc,5zK{'W%F*%l zNѾ3Aǿhy<]lu :ñ 6 3;,V($4)Sϫ\pZTN(>gZj,X+d,R.A^gX9qޟgɩ3I)Cë{xmBUQ5bpW|҅},;ɅYBNt7h .# tY^_ |ֿ[W搩KX ~ݱ疠5,,-*`հ\2KJfEүc"'mɯ8AҦ2m `̢,9tLN@L[2!ϵRӚ!obLpѣ{fu,R$N-Ow]#F;::z>^>ozCu%泶h.:Awf, 76ŇOX5C *5:'M%$+M,"ֿ8 L"Kv͙QKMjt̄rIg-SVN t\m69q<_pGH0ۭ?lF)WZM{8r`9TϞ>cqeZw]x"9Y,a6/J4[ni7Bκs,x~Kug3!i#¼[ D"^7Z+Q&YG{{KΣ$+iWGj1{t/iQ=wī9Ȍ7ɹIa6e%F4:L"ae ÷`W^QN'a>Iu̯L>owGçK\7 AQ;}1Êʆ?NGr"N@BNESGW_[M0)z__? k)}E\9 ;DzG2~!O KzcoͯI4ODC!Yfsj6r\ӇKqz np٢@Ͼ<_6~kKTcΏZ#Ý4>+bYKxL@'WGxvp3&Ū,M9'6}yId)PnȊu?˩6^t%Hᢼ1;* "y# cJГf'IËb]ReS(fvFYc {Itvh 8 ft(hLǘkDLidf=X-c6.NQ!X ]yfS) E;ܺ9@̽;Uփ)9jm ׂL <؃~{I4^uymʃRt2CQeAQs0^2XF{mzwK}awmԤPhYr^(?ΉGmLk%nv¤)LL*]O4qh*M*,jr#AM> s ܿHg9S/h cl?I'}|jor:)vX }5ȩD}h%9ySN ~"gǗo ޥHF}g6p͌VVm[iun;xY6z!YҶJ^AyPcRo4QsH,3&j lUP[UBՓ-͈!iT^bd=yvǦ9Xcz+=c*pt9 EїV>>PhN=G<'E,0mCoI I#. U.SbjC"ܓxf*T,c0 +!!H9yA%;c א;b HyT[?H;UW۸%0yqs2ߔx(C&O`[ᴺ~^]g!mR38Q| 3w>;qY0;>#=ExJD(cRoq^߄`hXk3HYn3L `6K63d4HTQtQeWN') KJA[)|Д j~@\i{p+f p,W2ťo>#yoA؃7L6*$-dD`GVAqlk]戜~p>͑]RԴHEꉰs< t#JFҪ0 A%{hN# QVo,9njTG: + hyQ6;TjxmHN̞S1#BSH'- N˯7ǷwTw]t_mA ߓg /i-kTsȊ]7x[rHޕ΁CO `s l4K\y<(90r 薕q^{h ŪD0o,H;,(47@?*isVNThEjY!u2Pa2z꙲i.Rc5ժHԘ@ɯj5i,HEqJL? :,my91J(:V)A@q k 0 S5d7^yܼ|ۙS/Eǜ2H4>񁀽83(do^Ĩ|ّ.JK=0xe̼ y1[ߝo [.ZJAC+CK_Yy; ` (uʘ&謜Z, pbWȖ JƟT~zĊ hh Dԕee&ZPu4/F̎ޡ4sU?u@^LW|+BfZKRH*PםvIgGFyKzʮKW %,r #e߬Zxbwu7,-9Ӵ[Or/e9/*%v0mz fUrp@X`.=nI&42@u[ ܧd\a~5FZ7%8YLuY m%`UY&rtGI:vI'j`((B ^E@,׷a>}8j ɮx?ȫ@wѩm&qI`ӎ ġMLI0Ò/Nd'U OS(rI9dFdJBOɫ%D^_<˪>XNxws5{n dno֨.&x/gט JZHwKO"F2&&)$zvABڠ-TJ`wj"æg*3C2 Yw+ܪvZ26QnUR pPȑ_g*#ҧV6dP?1#w?HNݙ7`+._9L@ma1`A* ݇59Xo{0mKȤIqkr\YC Tu]Z{"={(v9 =DrM`Ecdx\I ^qA HuD&%Rj\icӀe5)W6/(fiG \eŇ5| .ZcbH4 v+DU#LQ_za }c!'AkC Mĸ= 5FP#"GܻV1KqAB <\߽BފۂBCb%m%eC!SJ/LG/¢ GY=g1jOHŢzՏ:*6_W)׀UTe|&,\ `=}W "初BJmI'+T_ Nߦd^X.k p:Ʒv1jC5掜GQsqB|fp$M*ݾbO0QLgFXǵ'vTt6͙IL 9bRw09-9 vU׬"np[ܺ_ t'7Ii5pHaZc[T{8hmaD6ur;RDZ*Gik^4Zi}?Tk@D ~  _9bSH=<:}1C`&4AMR;9&m+(ܸt ;OOdb4NsmӝG >MTiKY1T3EAdyO.еkz}pLuK>,HFf(*~eDk fԶ•ZgE "YI08V&@xYχU:5TPĊ@VHߦMm$qk[M{ˠhYѯyaUAelڷreIb[uh}́2 uʤ^1]1S n ZodXS,Gx6q.SSNBxs),AZo_s WvXΆ٤^^dрS/ Z mxR*oԒl@75)r8=>Ղhycbr[eф׫g'8g޿Lof#wPmllzwlEaxΓH ptȈ*. %2̋TE4͒o/N_-n|YYzzخ@qe:MY54FĸI6gUj}S,"kQ#a8g-i/96r2Z۶MJGrP.nnmrZ_T[!RjȟQHeOJjlFuA03wb[`!LƂ`e$!E*=U9?P+t e2KV7@a Pǭ){Bm4. {O\#gn}1zCU7ۗeòh>v@ o[sGEzbQMü!,rPϸȂҷ&a pS !Loz/srwKsӇ &=E,7z^ғ^|B[TUS:J)T|6ߡZb#Y'.lAo 3R-((^@2! +8+AK,Ec8Q3ȖaU&ղm[\N3Hp}x|f3|`Zυ{5Q}_VS4NxT5)h=Sj;XvC5k<G&u¹>qG:1 T`wF}BF>3 {$MVz˿|l?m|fXuSI<PS)Yi|dF.Ϛ2+C $VitJOT$&10u[Lq˽B"(U奀dƟ9CwI *8 h9Cxf@!폁|YɠXaIU w"7ǐ o-zIW=&KO+q@(4"ʑ ucKZ_ajd.ñ?7y便eCt'IF⯭G$s(E9({|oPy6檗E;9r+CT/ZIpޣK4M3֫2SbPv-.΃ĸRq}Kĵ:QTlÿäU$SU!0s*(JXڮXe)VU[p`"~Ptf.HRړSU-"[7'cadӵ#$6Um!{3?w \qY\-q\V7Dwb]0]XV&$$Ā=?$H.tM "ʽ-E( tpʝkEoh'nOMB" '`uIԟ7Ďˮ7_)taMc0 ś3O(O&QoQ,/N8Sjt .[4U9uiJʌ o4]IW>~LV*<èIN=z{3;V7Z0Ds;\hIb8_h0ݘ#e-](aeհi# r{xi;oMT;Fa~;K ")'R: N λo)txY*3SjN|3\ /g~x" VMHSˋ Ew )X!%ϞaApie&IT_fڴ-Pțԝ 95jv[O -/W{v!jB 2 V\yH9#at^17UY/"~CܡtfoVAKdԞ<_n05ԊYA"ŖA;VC"NT5큁M?ư+f*^H:|ʿi{$/# p#6Y{ ߊ8*&0޲ZuW>c?1g0*́F/ݯM[<'![NL-^Ky߈|iK) h /ʳY#2L"0? Vwy 6S#(W9b^@/#{L]HAtk) 9d{ɦo^HA2BbnirAx926$NΚB6yIT&+h֣m ʘcbgyRUؤ~DG?Ek9Ha7d>Hq%x:j7b(sfCR^hGRD6RTSL;`RXqx=2i hZ3S- bȽ -B' Ӻ3gZ_F*WKܪ룄 &b,d V{Ӏj ^yX :q bto4&kE\It|_`)FQf?+Ԇ;':EF ~=~nf3t{G@A x*cfk/U妼gw;Aa^A`ׯ lD47iERRS{$lg JD2* e$;6ctU | 9HUw)sTDN70AWE_O{/L&TL}Y &VIar8M]Kbl&2cZ'E6٫^'MqS6H2 A5YL:#(QD'mAYKm46M{$>ujٰUX/x+/ ȆCl>:6AV?B(pRU^R~A MuY*fũ EjI%:+!_P=:QH,}l?T7u8 st˯Ɇb8&҇L .;"'ʮYm%lamXr h"(u)/ "Qzlt[>Q?ccRke,04 "&ӑSaZBڳ˾qJ6ڨi~s8rք7Y΂4ʰ!#A+WT>JO#{f d {E}EH7I]WJ~ώ1C(coo)'V?<~R7\7CYO6T.ؘ"$=7΅ ETNzoGYa&BR}dOIũPWd|jTL"%V-oAr.#5%慓&=T6]Rt`v0zU{$1e>FQ/"h=?z_c^󋫆7o#t#>ۿq/s a޺9|n4~N iZ<4]/+f#m:C/B,%.ZxQ=qNe; yj0}& iXzvb9s5'¿kA2 K/}I|☇Yyɯs*x0A51tԻ~Mx`[@R>%OZq L-KždQϢL QWד jgeMlݲ%g/?{ZQY<ɓB,0׶:nd.P2Pq4䌌#}1_E!l]w7Aԣ>_[֣ 9 `Hyy4N.5>m 6|m PȟO7+},^Gl.U:/f7*JX8j$|֐pZ ؈xf$./uqog]1Q>lR 鷒Squ/=Ov0CE]#SB,9$lOٛ 6YN0z.!˨f0V\#i;li+@5s Xq.BJƛ_YC fI&qT,,r'c~؋"jD~x{K{24Q(]v(nj|=|~ڤ!XI5:@cdA#j mI}?:8lOFhk>66.VCO>Ոd%?2 Ί0rFVp1}+3Yhf/3nՖ gSɻ_c 2 ')c17@⭆+3ux{N i,-N10mԒEFR }Nm\2KM%^e)FEcYjbaim &gi IVO)-HY-#;ug:`O#' 8i!\3i`byi09dB2$"s,;$}9RD$C4f)MM{2Ɂ;cX .p 9䅠̫-d =/l)\7Ph(6 ?>&Tܢ%_]]ǐ`h"7Cŗ @̿a$&ƌDž2UF%XK'C9徇]\W{M8tIE|huTKg*\`97s@?P4:rՖ>ItU`kij gH X<c^? ,>zb6W8 }5G*Nr0WP= 8eo;9f0Jy%2 -tGwg/ƭ510͜|/L㦪6`NWL ' Kscr ,[cg X4 ѕ9K;0@ z\o¢мY,ߏj—.g)!a<і"%mG*MWD 5nb۔^yỞ3~_9bMZp>(ItZqwFJ %5Jfڶ mf hmCRMM5EЍ>X;$D4'ECV<*;m"֢&y*B-ߡi*7|N& 9>*w ZlԽ}'/;W)dc)WoqmEJߋO h}—ŸLPKbΕ'=/|CpԠ{$:nz{D}̜2&\C=/ <2}|SzTriKl!rc;o3[0Y!iv[JOCABƝ/fVASAS`x'e킦CgN=9W"|BiܑU@e~ﳫ֢u{qaƺvءMukVt01<U>حP-^F>v߈ƮFk+w'4 Y%*pϑ7l3Sxypt0~QumxMŇH0Bd~'K4/ZjE,Xa͂U6T5O1 UA~ȃj7}-$ d y%,L= z c9 E rJ&npK {w rgZȧ]p?b=3E칷3k;917 : IR/Qs: :@c܆ Z ,Is $DZk}1Y.6(9ED-D9XggEI)qOS^=OKaïbg98Cq_3Pu,I _?U56i7Gݦarsmӌ"Fx^P o+1tl `MrT̓/.'DK\^&$EF{d+&xם;h/_aؾq[ԯQ?~'[օ/GJUǬj|-S\Zݮɧk1teR0 $Dr]n<+8r0Qd"ImNPz޹*U@<aZfb]J2 gL# ~#Uv`-+loenB+ȾǾ/itlõ%_Gr3.R)-Ɔ['߾_Bk\XWO> *'9 3]D5%6+k7u£>mirtq`gyS)2=-+.ӴI|VvFM&Pɔm#v&e1lj,bH[ŁLT&G'ۂ!iZ8^<9|DS+XxH:^";YtH= F# /f*zߐiB:RN)$MgT\Jf^1)HL( Zxͮ31*"nnHڦDpQ<^=jA(yP&5ª[߾VЁh(VnlYuawt܇^2[ ̰|ˤ|?<|b(Bcf%h}$&$'|Bk,K ϟ{Ӿ_0kxĩ'Ͼ$ii?P=0{X&`xI1%ZJ4bFA{A_"AӁ004CC~sN=N05 X>VܛHbBHF~œ̎+(2H#c7s]ٯȏYWNeu !E[9Uw3ur7^G$==2"/˪٪wz1_O#F>-0Sz⇘Lwg oI]נʇMp{v-w.;~(ҳ 0$g%f{~g|}FNqNda"jo_B)MN9lwvcWc1ٟ?P\Y^8.il C<>$ģD{'l>u7޽ ypyIhQg;PT0ӫ{ QEicg)2Kfj5Qɭm=u2re[_yglJfz\d;WR*LJ;{u,$6ӽGac&M_Х[o_9cnMG/u7nc~td带Eg@3F }6/$<t~kBPδf-8'`A?T q>7+-( q +U(Ң2ё"ѫaCȾ}6~HT]TS@AXNa<[˞jRVIOp($F*V$k3xZAJDLvo?>˅lJZ^W?QQ SW8j+V^Vi#*p"wc%G=ïPŸ\z[al`ѨK>l4l۷['M2pw mˤr8}͙r7^vɭkv^3CЎRW>l0 |MV/:g2gJH3sіQ bdu jھmJ^s@+t;D7b̃x#LAͦ[|j- e=u z7~YEfH9l :~ݰaC?tpGSa#2d#a]n!,DlAҋ m `BB dOLZ9O U8B\t0rmϤ/ߥroPթai\ UnЂ,}VMjsS a㖿d##\tp2C4(y#Kzmz }Yz:jگT'i?凤~1K'Y(g܀zK*UŴ}8}ށ vRοÈe%Qm ;UEĊGԠ hXD<Ù% c"4+Ar=I4/@߁Im&ogggkoo^T8YmҨiC`^uդAnBMtƚ@#iHpb>'dR]BALdչz):r/:nʤӈ>OD" &tSq %Xz 5zPdqmSf6[,sqk4l.* i6Կ*6ؿ_f5&P]mǯk^[7Sw5:RJʅw/b(Zoz9/e3 GB{rKC`I+(lҸlǁCңaƧB{מ+5<ܝbR *ç͞'F 7M(PL_au4V CVɇ]Fן2Κ`/}̡j&:R:R q٭}SXK eBwhzC+%N<2<Ȇ;#ay>*㮞؊!`|/ע5:۱{eo-p%R\O$3,ELJ?Ku%;%cUGdxbɢM{}O f-h,ncD~Dŧ+''5m:h,/B4~]7Tg~=2@)Ik]|wCHf賭#j^ZۣV#MTf6S] Ƭ9e+TH=e^ϦSR3}T0Cq;9!`xeS#cf[&ʧ$dj᧻xH L9e#I8rR/6GB$?Qׁ  zjLA&`#!H C_ȋځLoh\^7p>`m Ds\tU1>oK8tQCÄWeLtE` '#zL(mZ /875*eeQ;(nY$|,ȖC]HvCȷ ʈxb- * %n2K6^C#< B;)w6BǺܲ@U U,S{F<#O>|KSh| LXkjV2&x?lTM׌NTs1jR߽la8AP X7jUɭ3%(*L`jI |: d  d^D(2$>꼤rf]X1lݼnvcO@K'f$3$ ?d(Iy~htDlP*g[%n3.8Cr&76nuέ7b/讀%AjW-Y2 uN*qh4_\\Ƅo- XƍR½le8Pl2JlGM:>gjkIl[*TISSv֡7L# ~qګ~\DP A7} eEz`3RaCq؞q-*BgԿ?=g3;O?x5dOqX[ԄɟG8ǒ6_K12&0VF'1 z_ G::zGJ:3U@)@+O&u4ػs? TRd̺eQ *:]Au/Q b8y@>˼>)ؚr CiEӥr6?!*qqMU,|4=Tv*-09wrå :XtNKO &^uß< ɒVqmN.X4H$d)%00%kzMb`ZJ oZco Id#~ /s$HJr'@ppssEz#v/q"Z}ω)~>=mÚ1_S6z3S~g_V(|`du^)c,A<~8w%@yG˺!nwFmHd1N:ڒ,-e '1l2)t9`͢L 7P37yiu ֿoH){%zNi(∓k89:TSUlZcL$L+LeCB 96q* X4"]ͫ-N_ڊ9CkRjz ەMm~.;\Hg]%Kk X\P[ i2Ȋ3S fCIP> renfmNh>s읣 9n ӭ G=wdk2G}nJ#=s*\a[3* Ob2R+ݽ$v/ﱉs%3**&ZeW|_3[3IpmkZz+Kbmŵkћ5fsdMRIn/Ò 9 9 |[1Mo 6DJ[u'K;$cB])NI$m͒ksGzCA- e:n-qcUF"[kRoKv3(j#Nw(ذub.`K,_%͒ 9D[Hw2ò༬J/J~Rq|'Z槁$Y@3:ZQ] y'+&.)ᡆq)da vt| #1\DԊ-SYI嗼փy`UPrۛ#ڇWT] EG?IPUGFȨ[srso?ƀ&4Li M;j(8a9 w=gj9VT^iF|%;]H PwR=XkNBR1 u* ˛^NA5eUTU'eEJ!@+&eqiADG*-'8XN/En_8JSi51s@8\qhJ]{XaX%+n>$  $4:a-2:<| or,< !{FMM2@fkyQ vYeNUl3YP')!UǕ};񊌙/_lYod+.?¦1s6("2nf7 >a8VR][USU]³ZSo2̌eH`F1[T'~^.|rhtBZj~~GfdNAVcmL5=#}u9VT'Q6$X1eĉ`&m QIcJِOX-^(s#Iޛta(GܡiY{! N ӈ~bZ{j8}T6'ZG!ј:k* `F28 )8k)ɝ*v84Uݰ#V=ZX> ?=q¸^,G2nq3]=޶KHz܏1È)4Y`y~ro0ECݡ<|ߕgԐ*@ esF>n.>w,ݩ4yMAI3pqd140!(BaRqXЊwON:PG9X#5`͙D~bPL7 PNői] ޲߄WjZ5ЛXZo@J F!U֥e,\r:4 W\M^e55w=źWw vW3cvp:xP `Z:?)QxT%a\lB, 4rТaz(7K|TQ cN ׀<{|q==5fn&={z@_%PK>wRHD=0R|OhO %xfQ*%\(I6]mDpObJ%?)Ik74TN]ZP'pRSO Jbh|TE cHO"@UpC@g)G rL7 .?:'nA zC2N@`5Ww3rGm='.\!+ {JW/9gC0wR0ո2Zc$qÆzYp6ҽDoFigzԖ\.Sr N>qK!,q[C)&}b %e/¦ 8McYP"JuR,7ӟ(h0qIPU*07ȁ7SC 8ZHʶO7rhNٟX!$Đ=F"X-< !.]bM]3wR͊br3ݲ}N!XCP&''V)G<B`Z Ώ^LSC 0mqb ީb$( wtF*"t4"!z}NFDZfȳR.mlBUIH0uxQ$E'Sq  m|cySD|bRc.`Bi1?pN̘* n Qk rhىȁGa~,?l`Wťm f9$4#D*1y84@3VH(Y5@'UZ!,D-T<l[z"i2#Ǩr͵tA` SD$ܷI=|LS8hD`)U'#Bd<dX: qC2lC _6r. ߑ6=vOw12AzbS'O+&7REB`2XG Mfsnv3!O=!Ŕ[\>(Pe~2&AgeL8eI|O&^@Zf fC]L\S:ㆷ\4ś!BX/~|ÆZXj҄yn1?L~?MB`pR_"r@N"|`Ӝ xi+ph&8 7# W0v  ;X3jH<k X-4OeLHfm~`3$(?)9<!CǓ'?Η_Xc$r@'S@@*lQ 4ëAFjdLx84q?r?A1h~" c(8\qm`SԨQ YGۃT*C{ ,%lP\{ ꖸk?w^ns7)3x..$J!ܜwxJlxņ|ݟ) j͝}'3Ki035Ii YQr#Fz*h:ط)c״*> Xv9M ɉu1dm9@g+dG4!@:&Mjq=KT[!)P3f3Z zzC 3D)Ld,ۤMyu-a ȷ\&hBNGb.`oYvxqŒ <@[e}± wte[l'?qL?x"SI%RYߞ^1)֚aW),?j ʓ-NO) wJ5$~g`y0"DЂͶ`.oGh.N=0pxei7B/A+VBԪa 6=ҙibHh!Oe3 ]ddQmEo#[W5p 0w2+Z.=_,Ve%xtz̀FU(vc/~eHftc*i_" f_$TD,UCsA |.F(U.$KL!ĞI9EÍԐ& ReNД`ye vLN %'{"AP: S⹗f0㦱/FV SOD.':8շ~TJfzg)Sb" kdLH 3V`[> zY_ifNDt.d[l , ,5n*y!#Y9Av7Km&(caOOM,I 1)N 9sPl?^ӵWp=8e-٩a/5Z|d1y3RO]z/zNfU 6ak ~8 wmȘ \`ڢ[b[r=6.r2ߖ(ITJA;/ q23}J,ǀ@Wz/d O6FZp2{ٴݨM;yH.Dݘ0i2 ISPD {Gy$˜سy*Tй$<Ei7hcPHhԭNF~R >-GU"HQ3//:"n[/ p(VsmG9?aj)b wEcGd߾߿e(.Wh?Bۑo3V`\uS;ib#:55̙s5=b|DHVW *Z$*nn E&N2Јuo3NӦyL?;&A`eGf ɼ*m5}L-sv\c֙MɄlHZ5l߉R! $@ 9Mlwxm:uҾI\~b( ,X\9@ ? G3J#ui6->9NDɣ͚ fM؃N&AGN5l^gC/N 2Zi]Yq qJ4oŻt$Y{ '2.GM ȕ[o=thA]8aTasIнw!F3N0z:4f1;"gUB @£\!3޻z|\=t>d<ػnTw6:fnߵ[v0M> عbV炀 95\4E17($- yZinGI9o4}񾭓URԁњI}-{ bsWR{󇶭=g:.j:xM{N^y.޷ya=Z*@Ҷݻ.b!v*XZ]ɥ=M[FARYxCظ/S{3`4^4.*0#eV*iZs4m ٞ^޸1cIm,tU EPN}Ѧ=}fFJĖ%3&)2ЬY:i|5]ܑu[ɥ*7 c jT[Vecs{#ibtXqÞ:ts`''<|º~ 8Tf:7ZR/;2om,QPidl|brjӞ!|`Vq1M@ՉZBN8h;ԅkPgKXsǹLuIv,cX$ zBZ jjФO)l{!YW"sdmϤ'DcJ s$ l}mJ~._M2Ć۫&<еzKP3Ž3f$ q+qҽW~jȉ |~)+yA]ٟ>mǷ]=6_6ũB>KEIo>?͡ lrZH}K>d'] '>ތZbI@oK;[kKktʲxcߪU-L`aZ #=NJ?dF.=daI_eFb~(]qy,>L;>R",(S64UF@P@>>nή6&tU4}9Սkb5 QkVGƇ 6o#QPN(1/CHcW%ꋫbv)ucҧLAP0D2آMai#0*Y i]#$xSwW[*wrk:|=M9z@obBmݍIL|R\9LLkvƮ.պCk~M+trs'e둚p# F0kw=(UvZB^mwl?jU!&*'=F0&c/l+(u(`sQ+7?eǞek]x&ؚG ѶQj`ktņ`\j$ݏ6%s믿Gĉ'Ku{ 5EVx9U8ce ΀ QZh`p\f#DT)ղ *҆z V{CY76ݺjnpn6 < ]ҜrJ&Uxm1iՌ+sArI,e,d0w_/p&ޖU]B4#TSPp{ [ Ew$'18 ^wǂAu, 4g^_H/W|&/7Zuly r^{WJ5}\w{Ӷ'gdL4:GD%=[e juvϽ@!xpL[/8zਪTVNBb q {Ģ,r=,9nU!;6tbW9/W!g;eɏYT#matĮdrD[nNbջHو]iucb$fҬuw$%b^"jّ %t d ӗ x)2{һZ42AP'ovHu`׏݇)$!'ēs-A ;1]j>a /eG1?8t}Dڔ6mҝ'.=wfҼ> /i\-:(dإE6>q9 /%4~Ѣ Se6i1Յ!{y8a⣉T#Ha[ػY7,#}#>׍8\ WTƍ밵癔G;Kjwu>Qˎ_}Ѣ4^ 7{@5E~~a_%@w:tvZKXbdCx/?/J :(P=ufkpI{/R2ڴj d_ [h*= rʗEapFU:i{n~\C԰kuqS4-[.ZSNd&,Dlm<ӂw7S]HKT5>9 8N ٲo|s'3L|LT{0|{S"~"%uZ'<1iޡ9W$0KIڶ-|ΉCFRF6@4%x`Ůu4)r3y{?L`Vy?zM…d`HF ir$|NuISŗθL{ҫh }\:]d)OGS5hq%} =|xGH'G _ρ lx C<,qNJ ڒϷ7[ A IQxU@?{ +l &Uyܽ :O O@=f\+֎KpZs7?CT^]UV eȆ%}g )nucf<8+l,."6)D={W7gN+6,:SrH-{eݑ<{y LYT|ȯՐ AL(7Т*+9P׆S/[FeKrba2Roz ᙸ)N;r5EE*XxzUt,ES6teP'L4y 0ڙ0qM]oy2&?yx*$[HB \j ޥ,W{vS MGOgo[WOn=]#bMkXoKd] < r^7Ze(P1Cq{nb6u< M|@ i* 2Oe<W Ʀח"eQAamX2Pgkqg|,.˺hzӬJCSGyf0StCj7baV{Lv=H7}bD5pBKօc&4o[\z"[<,΀GrI8xq#=L!4Sٗav4y2\C[x50ҐW8T#5"='UKK! J PfP/Nb:j8h>HKƁ?wFU<Ķ]0} Cx"c5M >zu,lt}I$!LvORP8py Qbر'T-]&GQ0X#[_傡4 h}.ۧ2"B|_FUkd/3ʳE>l~y4^{boTe|X灝9*g[SJN Ш,1wبw&"(ӛ_jKl=`siCe1`-,h<5I )xLs(iFg8a"E9ŧ{v>|ꂋuOOa.F]z]O8LSaEAZVU7m-,&bHv!׏^ RW2`giR$m~ '1\re+ zN7|/'0`,`yL×dd_d_q@xx5;4^&s6q/3A6<_S_` ]LxYl1ᕌ b-2_v sX0PL=y#&ņ;O¹wfѽx_?d 3RPb:֌5K$JÀE )F+o ;:Jaڈ#tʄ[ F>.LvTW$*Y.HN&N T>9B 3T l4fX(:ɯ㏧Ty/=Dc)~?z~@$ri4+E{2N B j!@N\3O,j#]&w_DO;L%cpW^3W$E\,UB m#B2[m}hwk.S'DF_U+>K ^IB |]o(/sE:f,'aġyw~f“skBx[~iaS{HꋬoF\4EvJc;#_7xH9ߣz!~WɗJt$ $} /3O]L$;A i?˅$$ jMJҞ[x `Ok0>h@9A69(,Xa5M:ym͌ӷW*K=hNꍴ~Vʶڤe~U@$Q#L$ 0<|N> CM} 6X}!nPDw%uĤ\g[P!a>h[AFڑӸ+3@*rb~'[OSw"tc&7u-Q`UrUS9uy^ЬV Bʥ[خNI pտ&^TcnXj%lj-C .ǴN ҏ fr"(=+_Vv[5"̗ϬwnsY05,i3Z I CBx]"Q; o~wTTO=z7G*]9W`rNە?=^֤_"H.<͘ IP-3Kƒz8jrPڳWTZjcdžz[-6z6ۃ}G2 0PY(:EN!VX=r {WPpoٛ(tvt̆b@>`QDd'd(E \?=o,+!3ȸ*)) 4T`P (wQ yi="fGrs >cͼ@˃&gu`"{keJ+2Sse0T(NAB >r(/MWovT+u8y=ϦcGYFT)WeSNCZ ˢY꺓AJqfVӜ|ɮJs^nˬ/ASo܏Gjހk9{fqfTy)^6'5]kQ^K 5Ub @5aH)֢QUeqU^WsLgwer*blj;9wŸ+4-E2K `ޖ)HŌ>ɓ䶯N \("5e΃@IRi ԨhQ`e$Œ~|7婲/51h-@OrΟ? t$\8uʕ{sEMxym3ͧc [ :eWEQDk]ѾfSb[ Hٵ{[I6_X iSǏq9P\ +q[<8B|ʯuV-AxɡZ+o@:}8xh:g^!.@O*QQ|9P m Ċ@tJp6"-Δ>G`9k!+vP] Pܢ+l>jSȵ4,]%DFź,8Vh(j{0]%8:e툦KAHmg 2@>G;SJ>@wPBHLg-@7JѯLP!q+pQ?RنЧ\XrHD Z|^|`&9;^oa ~8"^S=?&W@GJV,~8ִ W,B p$5}Ěש"ol?˙_ 7/N{9w`r2ƣg9 "5 ElԊT:jS&NLZd3WNtfX-Rۯ+"ei_LЍ;CƫfDX wl#gi2B~f.s \_Z=D Li0k Ez'=a1ѱd%^&%T~nu89; W ':׆&80큹>$G<}K$\Gnl(uk,R _d ŸC#*ī*]MZ?4τlV`8K;M$[}͗ V6<ҍCHZu#{;Ʉ9fFr9[4Ö3qA%[pF+ 6nyb݅>!HoFiDA?OOʸ QVc Jh)Fpk7:-& s5J4H! f=P j%|E}9)Z8 $?T ώYݬ)2~Sy.smhԶ<`GXQLrf "1Oܷ9 ǽa"uBi,#6N26KZL0uuJJ̉-O%4R&+voZ2Unew-iYz=h2QL.94\h_l9[ qN@I4;inH)}@m&׉y Vw* z! A W#,-;Z՚5 Z*9qVG^O MX%;Ja`9 G[>G&cn ca yrmhTD{*!*煊{"K /A3" }<$ot!iD{K1MV{kli~!fc𭥘G\1! I#IzEb,_YRv4m#Ϳ&ڴZ gɞ, (_eί<欉A u ؙ#ρ[L&7T<>6F澸1UH~+#A!w\pnThnI噍(U$)XbTk؝Zs;p~g'ո ɝe H>~2[9* XZKm<%n ,=J8d&%sIBAd#-"^QI#I;j`' Pfd >*THoA,_kooy/;Hys"1I]RRv)"FH 8my`jtiRu~R=8m[6>h&4`J~{ ݋T(xƄ#+4(XAC~!S_1XHQ oed>Vc_iQ /!۰ v}sr}%o{l\ PpDb*jql*2S'!eXV$ %pO _Tߖu2}2H+ _NF`.u* l{v_iwo;i$yDͥjlPHIl{Kʦ,[_$}-چTRbGT9A8mdCn!g¸QWG'ggׅM]\ϰ C:89tqjEߌEڤ^E=* iNpc$pJP5\_VRkUyIҀ^(f?bv>FcSp 1}FA A4 ,ni@xlo7 \pʐq\"p˯1ub|2ְ&8:4ݖxK-"xvcy$VmK e>`"zRGLnnRƈ̨ TSW{̶V]yC-;5Uzyڧ$AD^n;jKV߻c&l{ˆS 1MCV,C~w)gj{pn{qzVk5@Xb26lz(tM֮\x ]QEjZgA4q`xPu|~u"HLV`G*Ȝm֨늹@,/~ڠ4~j,ـ*[fru\_|?-g!/4_LRXno ]I+ x]xwVV<>ظBxn %G//rw܍xec#W/I.;DS=zƤH-ߝ]4I!UW۹n'b/9}/=LF]KEySayZ ls`W8RNfG/yb+{2`L  tA:{ϱ^vǀG59ױ\`cd"tSxrȽc)iv3(]y}uJSHUHnnS2rR8~uo_86YLcWBSfP8~hΗkbKxW4)ήSǩO<5uKcq**'E`z*q?Y@b[O1"c$% 7H>=vܟ|? y,7jl# Y 7jWq8 We%ͽN':3mMG4Uaf82O1ȭ 1g{j\ambb\ؾ]V-qbU% 9iȽ{)kM'P2j%QO;dAAbL0k^?r$SӠYpC"_y>V[ێg[2[eFı :ڗ)=$J"iO}~L{?%USV~_-j藤F>1!%a͐@ (@U1Ь&2o6p'A]`Z P~2|[:7”P .',- ϝ0q0qk64x?f-[MnE|'tՉ̽.Rԃ /,:N%~c / lljߡLr dڰH`PZ/Հ9# 𱶒84eUçZYs_-`T cA!v)x/.6Dvq~c.yZZJ>}HHJIus6"w.TYQv)hJN޸Rzޒ\}֣'^D*Y~ޓ'm$ EBQH7mgcRC ]+HJ^Pv?KC6& żOsɢ[ O0Hcv$݉UptA*Cdo6\tcD1\qcקIt >r"ma|~of'~=W@{Ckѳ_gb{0$)7>b ^\k grYg^?;T-:<+7'KO;w5Ԭs)EH,NL`q웗lo_9s=Tу?]r]|ta=+ B"}^ Ǩ*YZ9e>3{=2;(!pU`,VLD1r0tXzb5{].u䌑$`:]1asːP V)[A5 %au[0LH5̜k6>k R).u(! ֟6o3z9)S٭c;{YL Bfyڹ$`MN%Q{&Eb=!(˭p3##1 -*"@[3ug׍}3Oeg5eύn8$vy8)ʑprI^j6~$? :s͡FTVűPk(=DRȏg_k:/~3I 9r?]"C-LӐÊ͉{JY9MPT`ʋL Tтy |ᔣ%f(AP|9*,n?4 ̦ VW|zͻ7n"l%ԃ4.FIm0g41:Z簚`q&Yv1@YHr)ôEZS3{rczMzl$r&oVp>o Amz<Ԁ\Uy}1i5h{f'%AxCk gui_'6fqwj[~hvJX>T4Bm2FДOmoYsASn <_|iE>~޳pXWs2ᘂ*P@ P%̒EIXM$1C?$i9 8lZϋ75j~l80 ɉSˎ7GOJᓐSۦ.Fdyw%" ]Df~0f<93C+LP;}ۙOKʵO Ag?r]שR=yտGx#/F7aR$|rC6O1)Y SjBa?NQ4d\/OlX4(~N*%2(l~Q N@ )ť/959/ ^zi{U g͵˧ȦE4HIe= ϿjRK4Vj{,Ӑ#=-|"XRg*+`}Iwvix#o$q0 {B 85ˤvB&vi?9{KZ/Cpki To?ut*-y$Y&u/gnmj̥wm?+g-X/"HMm(Ǜnjk'>L\*ϚdgU*>0t#ئr-+?H@7ZEɱY[j[\3ؤޫM:Ggr+5J^J*[/=pXz$'M0LW^~mƏk`Tm)u .|1jIg~vd:Ip+m(ßL[޹ocv=;6U]$HKMNxLKRSqRA5Xq**K6\/.k85ڀf&j4+^L_h7mfl1| oRn4YɮeU'kvrV( hN,V\͞ a芤0&1Dw=xN?hpUlemzh,p Q`{8.G:uJ\)泧;lJOl5`JJ]5FZ|Ƙ)-3у2~ei[Gbj~L5)UgG?$xi$CEȄ%OppBHGv|f|`8,c)-)0y?;r`d*j"ԟtQ2W=b윿7$N9#nL6򠬑;K>8.z"s־!2|mUoO̢xPxq֮la+r{[.Uq bC;J.k K&<-*;ag_х obkL]"C+Hq5{'.;}˷o_};wܺs@ZS$E[n5חnq]l(߫BAѿ&Nj+k{!NGbQvvD]~*&DE~\$&(oT\1)!.:ww.pіtȸ?. PV}'AJ1|֑[~E%&)#2 ƍcmpk<EGjKM~ C@N+4aaE:\N q^ +.6o=|pBG5HNp>iJ~zN7s/`0s ` ]퀱wn]>V[@"-[1# n"bS3AZ&L&Ea|Ϫ RrZ 5tco^f7?.經^O0ˆI(SاP5аiХa~=. H+%4Vted~gF2:u%ITp1B5Or5ܫ?Xu`m._8Krs^oĩ<4)d#jȘq PG!@lBIk_WA\i &nYUHjZKH*t#S=o[\2b_s n0j ȼΞA޺KJ-HZ+۰C"H͞5mWw#iZ͐ Gv-Sڂs,Ui?dҬ9s̚2S.C9N:l뀺;:" /ݳ?Ѱq^b 5+x)J,@Y |<=:ٍ6T"{مwZ) 0WȞ-K\ȴ509Yc^5s9 E.Y8wDNJc-)z!ʧrk7uX&[0W-zʫ.>kMw$ ୃBfN @|Ɇ:'D{Du\+% =Pp9Xg0l0'm3!除 =xO^}.Rt/'G|[p],굶礃_eUݐ *rVb[m#yCormsڕi<Z WmӻI\R=iGγz4zGQwkNn;ycU[Y8*1FR4fD'Di =߻nL;XWRu}v*'4AZ=/f|~X'_HpT - fNm7FUw#\=J޹xτjA>i炢?,/'aw$S'kF9=an#P_1?*ft)Ez:k8h[[!j˸Luo-މ zxbiϋ"Fpj-:XT[;)/r`0^֡7j̘=݄fVp(K"0p|&:'eP{FvZ;zʭ=1g;z4{)#v 94h=zr~XAVU>ԏU:g:RV=kpiៃi|I`XO~򳘼C,t{W/<]Y ͆~Mc!G^qD'~D+^(dQiP;/ƹA* kM[^4gG9c48ae%aL^ `~/BHG$*q\n"o(/!ik>0o!,y0^Jw?LD>[?1E3C5z_6mD?TtŤ7ԉ" ؼZPd_$oh^'/gX*9D/f9ͮ(bojM9\WՒN&n,2x^]w&1 9d_dF_v1gʾ9^EۡI 3UV }D#׉{pbmbУbM3iaѶVmX{=g}xb.b۸qAF{0V{H HsdB6 P\%ޤ:[8p̀kVG+!;=agTն/§-;+1Vl?VX˪'0L:KKA hہ|w% yҪ+?& fI;ciabUǤOyz)l>3YT+%GƝᾄQ ,R8eq) {"~NV_ʝDq>G*ҸSnإ)yq$ л('&f~I j=--?/LiU*oDLo˯+)|`Q&6R&`|K%6d9h{sClzmm/!S\ѸKI Z`u$:0W',$捫 s!}f/VA=^IAvEf⓯z ss kP6i(; ohЪ&(lUacd2]`4#8ܘq pO(~޶$;-L04C۵k!{d&۽}`n<|@e ⬒H|-*RJzoVۺ8[! wֵcU,!4)t iע ~\Ko?ċ_QD|"9R;)7S]h!lR*ߨwV`{3 ̽0Fht6>ec(+ V}xH:WqKhm#ܖ{syg/ -I: 0{Zp>'">/QW2XzzfɴtiIK]K8O̭q(U/yOfį+sf!@̿`E# {us샜1y[0op3\Sظ=hѢ2;U>xt|bH*VnGVL$jFlߐYfSaIJb8#LQ_* T⋕^ zp?(vE]g*v84h^D!=s4oOgo5P: ) 'kdu,_aoܹ; TLqo]h۫VjбO=nJ!þ 7=Yk#$*P4A9xq7JXjБITr?R*WO $ MR1o+}"vK2/b nfζuGƽU{&ڛfwe.!w׸Imɀ<쌝ڲC` \;Cs,Q>^NÐio=F ȄR1# V@8\)Ym˜0U4z tʸ :EXFuUqQtM@j+)|L&gl-: D0C ٪ft ,gF-4P^վf3gۗpRVr-[c-*"N浍#`/# |-C,%PiVhVrGܞ}Q@٦SS -4lGIcRFRV:շiu6Nσ>Ӈb5A=Kg+V!hdnV4yTii+I3yQ $55Rג5(EY])F"Y׎z'(T9]mvmqj`"q ^ѵws*=Ib'XjKK)8 <[ :Tk9r~,{+/EϑƎgKy;X8hKl ߈B(M76fQ% "0^ {LXaS`5[bzTu7M[//!2s`67ȁQhIM0zMZt0ϐ]}lMdֵYyԑ*H&.USN8 PJ|>wf$lS8{F~Sbuݿ{l|+6UFgq+BU*CG/ cWm!*ExG>!')b+Gvd7Ϛ_RTC1NB*IFӄBHơ>oɛK`&P8ݝ)'AVTQz#R)eQN02kLaغKQ@W䬜UC3_MFm#xݑj•2myGK{q3(HcXqXي-Z\ { 6:d@ľ*a?cPBNI EY,T|L=p9l@I#|{E@W,,ڌ de?Y6+)؃WyY=~PU˽brʑFȮJ%구gKgUj=!6]|g@H~)hf~ִ`w9bLK+o䜌r 4o"F4Ezf`i؊|بJRN'F7SJ3'%.8Y=Ρ_]+ Aȓ[k(֨m+L$̫u4kKީ:<$Rx8J\Es+Ixv4sd#@FGAMGnxଶ|INh8:xnq0)D2߰قD\bQ< 3̧$ۦXS_P$._0s=40%<'xjaLWaF:5\ ugLJ>"U0yZH֪Ta ;J4lU7Q!aYfZ}hMc,,"Cmx'p}R_PR"&k3D7MPL䎺 ;BF0 j8) M]A TMqA-3\. @I3:GyµiOBu䚪)C$5\HBtzFH4 !^|g*kj8nă+R/vA␰hNF$>9LD+kABuS&Ƨ{4H_E |e''HcikvO?/j8+J`6<^n_ɎE-AE1'@|J/6Ⴊԋ݀musDm{(7AE2Ӓ|u5~ iJUUF( u$NF!$XGHKm8<$o)AsԒH,H )u̓bj&n 32!LU-:$+ҥR_0&c O[`)k-}+h#ffԛ ~Ä( X#k{ ?5!nJI-aUDfVu$[HaJRr,;sk:]QD[#A>=7JKBցPq'+I;@/_Bz}${I@ *1@ysHՂe Q:<dWfaʜ[wldMl e9w=oz5!]gbbLvlCt %$0YKI1ZзP%R_W5Ci %ޣgOlCqjM13){:.Q q|UΰʧN)>tvвVQO[+BZi)խl-@FB 3xf#!-{itPC'*V$>9ør!"21^Жs,ϟ,:.X%7=QKp* a@7q`!X/rp}<~^*'yv*̻KبwOֵHQU9Jb ŨK7бy"%mYCȶޭfФ>C  ~C;UrPoH^mh{&o0lR@ hrI~~ :B,J(/|2?_=/pWw;tew@G8;Oyw~ǙdN.w(m K(*uBXg)y3g)*%8%[@*\ɒS_g])_kSwGYǑyw M#% $%OĽ:"M&vn1*jĬϰ~mۏ^ 9x\0fm~@Tae+#cGħfMH@&{gu ?|Зa-:WWlӑJN/BlqsPfPU< )UhaG)VB77 jrPR> amfn[H륪Kjz0!lŶ/&F~r@6Tflܶ'ՠa7n?V?Z#Qy) Qfs,=ߢ)q*" !X }diYMVӻS.}% jUi mi^c}πhFW0&Q~t+>к!LJ( \1ZnttƄce5uh(E|ڳ9,%gag3C4Qq9 |ǶH"#HW$!$%5A %<(>|v=DRΙj'9ySn, b>8t J6_ xcxZwgqnn߳g>LlvkXqƵi9;*4)iRݫܔi`it+G"xL&2ML iʢtZqTJ쥼{3aSWtcafyf;J9m'hȚ'EGR+P+G ȡ ݂ 9%dck3>ߴ:mB MPYGicqZqdIo؀&/iI^~!2fdr#KG]/4 qn?zC}' gpoy㣱'><ٳ w=A0O !P_fa7)/W9лoF~mm_D/RuϦS:Y|҉sqQHVe\9M CXq 8A~Ir2@1\=ZZmljI8GiY TF(hSL:U@~']jzuoXjь1g&F|2 Ak|V64snp? B?%kdJdTԑ+~_$e^/ ꍂ~ w߀ow]>!hZQm iBAB+)X,$zjSͦHS$ R^2+[P .XĨKpw, ,Tu6B<'JE&mJu99C?L93_-AOFmq!1Zw?+'s[/<` Cd/JufuݡyAndt k#F*T7?__C!ΣH}!+>K\0<[+HXC4zlzh $<ͳ4d*~`Õ*]&XE |g˩lґ EStf,N,PFΙ{ l%z:ok ^fd׬G5ݼߵmZW&2_pfEXW$Y y4sYbߊUg{)aiz9dI~px#O=WoQm<=t n1~]j-XPsnlχv6! SPMZ}v!P_Mz r9 A5 pC ۫k%Ik\`QV ;ǤtғCܥm١I Ik»Y[9W)@TsM dNp^E !E,vGpT6DoRA[o5s'O5_LbG|tNBh&j FPYrA5l S;,Ӄt#䪒cmmF>b@{\ө6({5fzJ(ĂT3ĩVjگGS?i!6n&v!WSs.| 5}1B?}H42TLU^er]ːc@J0SHf6ݚڠi^RNBW&Y`uKYl4Ihܗq?#޼,)jLǿ{Whn:od!Q b.2Sqƥ^o6^G]9g&3w Z8OTnVVW@KI.x,m\=uA디3Sx6_aW/]v9!%3@H qa܁~&<{f5~U1ѯj%!16"ޞx/ vpESfxtK1r;_h]\"q0S(yK9ʊ 6xJ,hG͓Bn$Փx+B@3Qdu>4$rzPupǑs͆U֝[88TЧGJV )*58񐣢H[d-ޖ7jm<`mFk|1@U'ВT ֍/&Pfw!Hd7X&Kɘ;2I9 6?@.ɬ~ ®y#Mx6I-ATqSEX$|בcGw%7V9Wqmٽ{9|f.5@1W?;0E }T4Bq8;vNZ'W}wusvbAB$W2=5찍e-tĸGJionhijʚʈ)W;Q6c4E_+ KR*oH\ T&cx pT` .X>=?K%|5_+R)4J{|h1Eb|d xFq7THO+F~|0vp> _ sC^^2ybʀI++5m>] ,ɾo1s OcCu(C 4(W5Mq-5~vqK[ TFJ4ސ9sS%]fR#lҼ+OƐD"M b}B 0)f˥mw},Vgyy|0U:L/u n_(밤14EtbCQy&\A-Ko?DO6z\f9iA! 3u}S[ɝ']A]tڍV`tEʐa`Z~F$\Dbֶ,R!nsXY>ӗ(͢y7{Vk ˙.mʾw+>OZ4dۺG)}'->z_}X99cVq)cBrUN.0ӭAfzRDЕI%w48ΟBb33?gOL!h4iE/z?| H M>7[{:bś85W䤣L9Agw?^pFgRKgOzu!̟;ڷc!yDߋ5 7pə뵰 j6h-t˒A 3)8MSI,*vė ̥Dk7|)9.xՠ{hv\]sǻn5KJjrTzC$# qyl/x:vޭs&5Kf~j#i,A xlr<"+?"T'.67d|g+L]+ղoҮȖc\qɩ=G'I坙Oxw`7cy'/G>q̶i }6]O 7Z%tp8 XaFz[bw[hAdH0j?qDt` Q(c(Y?ټ/֮t^zOEeF'Ǻ3a?Gwc ><~&| ! R:gaIߞ?RHx1ܻqXneIPmP10-qZ>6,,5хӹ W|%]S٧Wx?ə}:JXBH3r׸ύ[!]Vs%abͣYO3 XxqZq!]p7>Vh>m nzc[!Y8w'd6?>>NvnUkwT2Ċ``DONh>Iײ w^ V骱ZtK-\[-*OZ]qb퍱l.@9@lL(v"?1jzGc1V_@>]Drx%Ȕע-z ҄1U]*\؇yuiVt*̇9 UWk4]D^/o7\8ms.XyD.eR0uvHiۭo^N_7k〹*Cc# ݿ2! M+Ee ݂) {f3˖5w= r^ekp6 jlѵ}1^u"P0ޱf!]YiR4-pn ˒U /I9tЊPم:]2Dqbs?l }86PR5_C0μ݄Gr<2Xu1>0|\njyG:]p^2 B/'n4JZV&2}7_+s'R8A 'njlca1+fXT&n%B)_lv+8V7LT#`ѵ j3)ߤ~~} ?X 09[-QO@? Zz@l2E4BxX/4 x(]3x1p 46,I@˩κWS#0-7)5lTv旣s/2m-ujHnjjlڶMM;>9 @t#ίeCR!-G_?q$thLᛋkp[ڟ~4J4:(A fVa+ʜ,4hu! 5M6 :gj y 09\8G:6nGҋ& {$P4 z yUCRĹL6Wi3|aKa/ܲVTM2RPYYYZŀl7uk*?wԂElEvo_`eYOQOޏ>YG@oCTӰF\MPc]pFbTZ! 哭l!*b1*v0(D;pnQ%LbVt4^.ԟlO3{5%d)k8k+֪ F/; OvٶǙ|UJ/,m|T X5~Dqmg/H7J M xV![Do܊fj_33>MsraBڸ䲉b(IBQطT (vTld%Y^zb_D xI,Bm g˨sD F3&4jMF̛Yq8Ђ9{oC6X$Dܢ%z2ڥ$a DHqlEȢ_ir f!,T}K( ^"gGA; ѳ8P$Hч+az/?Qʿ*~aޑSNz|qm'=r#@zb7Gh$'R 5@#@ʖt3ƌEЬ^WC'2DmJl~*R) Lԓ\X!VѳObJwҭ)0鰎_sòQ[W|W fS#MF{~`8_cy˥%t*[ G7^ ư۰q׮a:cldVv:6WТzI/n.° d?X]"6OmЋyo.j}HʳQpJ9 6 Cz*t/>2?ǥu(ǽ/kG3Miqo.xvCq6UQF|s)hXhQ`DveҋNJZN-Pd?:dfRgkO ,j;A8ٙM"2qu:g v7neV̅' P66X~7: 8r|8"Bݣ^Ȭ[g._}cbr90WF>U{7lBbل"A2<$,Lu,&!B6Xq4WNk!n_9 퐓ZN[_h|[t]!̀iْA"`[\Q`.b^ѷӵ/TԎ{%_:"n搫h\9B;Yc-R9a<%_͗@K=IZ[dµx]}4١+Q B?aTASwWx]wU_~%J+W'5Z Rv~ZcB%=]W ϮܼԜ Sl ZIbː9(>'TA?L8HWQPP%O72n_eZ )xs.7WLugᏥp;<L~\pTpZӵٞ̽8n+]Is\ H0s校9.-}-Jmg<42\Nha> *^3FttƳ $̶II\;CIӆ.f(m=;L\c7 i\K;ŕI g'{#T\ڽ(hqA<`>^bG9hJ>OlrM 4oI;E0a].W:%JPR q=&EՏS0| q4#kVrcs >W5Tsy /JI y[|C|`P=_10hl@˺  5I=b0 vba bL2DdSYpd*suZ.!-u\$/ͩJJyMb8 +8ihc_֌{'[4o 3M+t<>oׯ[lrRNgA6N4v DD6 PS8\u*4K?͒qFC}Oa49p_=D7D9l= *3;3@XrR8y3+t9YHI$N%$wpCb5cg0[Hbj׺]0/Whg Fm^T&Nׯji_#7ۭ @\^.)M{x$&ofy]α 鋦0,8o:N>׏JZ)-_vmꦤ9,mk]^x,+B99j8kAi4:mrk.]{+*6&n\sYPE֋ƒg=S1^ep+,j="_\*Q qBء?u骃`(Ⱥn칡?w/㻝]_ї0;AֳDaHQ[VFesQ,ZKzqkt2)P^,uɚԦ?H<v~Ȁ 'sv7dnSz Mӄ̟/J9+r# fuUɥqXq,AGz' Vۚ)6ń9> !4T>썘(ֵF`YVdǓ4gF=;< VUƞ"^n,ۣ%x6R΂跪rk[ w,V׍"sIe(#/qB=$ʵ˯Eŋjᙫ@06#|xkyߩӤ{P%+wG~Ҋ5R:]e&; !L-b1ڮ2__^wX11ϺHG=Ao |W#=G!`Rt TRl@򼝳V Q# 1%ρ{}"(LmNё9)јP\]'6 JcX b+54ɳ׻T' N*Yu=;G!zFa YKŸf`''j mQ{op2`ݏ)Yyc9ӡU80w=ċzљjj]&v%wiZR`Uw`m>%Ig\leuN} FOWm۶yI=].Nv`bcm7nko(ߓG4^ϏOֿRy]*ދ Ђ9&ˎێ &w`b>&thu|J66&g"[J2] {{ÿbö]{ҺQ"^RNCKܥRM{դI|wO.+ytTໟɥӣ0;ӴQ^P}KebBtih SOx=:ck$t$l۳Z?dp\w嬣,_LSy͹aM OEOZtNTv}/{3)еYAhh#Ll%rp1;;yM[^_]*M$t綅FPgyȸ ^+xq>y* O e3p]$jGJŀhohmiʶ8 e>R*- I1 =\,m&U뵍$M Xl"p5QUI,@5Co?ogPGRPTP]& myMyhnQ>n?`۹!1D|_on-3Uw]6Ebyt|u[I]҃NiX(|_2zMAGuɚEO=SE`|YDIϸL$df$Td+@2T껀$~7|=FTlA;E5 @=$gK BۏXK!$$7HK1#WUW @7<`,+0BMAtkjnϻƭ3c3֖d vƀyyD~+Q`7 oyy0:du4`(Ȗ\C 6Pl: +<ȌK?snw8Pz7 cb\.q <^#3|]]èbUW)E< ?,\Krvuf!Ym KR]38bRE7L녚Ԕ|8jG I{H Xkp V|Xp~ר^3!i?B}\)%QRSTRcV;?_r "Acai cҮMfܭUe`Lj1Ɋ[̟֥Q6[ :?PĻJ}u.L4'VlUqE)x K/ʋ >h-㰙l#c=BasRw9>7+86a)['ν9T;DyG寧X吨ͫcQhHq>E:_?.?"(4Q=Np kb:c.(fe<*(XH &#|;_^n-Jx^N{8܂۞efʱ@;iL~%rCߟ M "B !C_W$g~}@x§ԤPt$jX~N]tjxzXM?ݫ[-lCAzlH5~_#` JlGę9ʛd\rӊɁxsJDhZК-a@1k޲u#DWV=L.j.#?FŷX[pLY*(vA.nm, '. Cb /~*ݭܱ+&`I_޼ -cM Bhy| (kb硡Iݚ{ rՂ&;BP Deɲ19:S6&&ʿUAdB6 $5t{hӔ @&9OX,H^]4v}O lYth1Qn[$LjB! WwQY ))Q٬L$()jUc1Gɛyosm*J59_ex#ˤn)@?.#`ؾc"pշ8zCI;C% .'7hҵvv Ԍ8cDNGdk݅5ԗ|s\h a6 nCW#P.ZFT`Q`= yQ ^X}QV/J1j87w*}0.6~?YSCn=.#!wbsT>$]'6@'HIy;8V@I UNCirz@¨»b׹ɣ -]!$,)*!CEyI q=vU{斂|~ ɰ=@{>ͯf< `rϢKB_YkNF\߇H>h驌v9K2*Zo#Ή FwLT iv~65g% (O"'@#1%V#m3 ?Bd\ A2Ey؇es̆:ioЅBM񎳬&PW;Gw@!8H EER; IG|c;`3͌#hqʾ 3eL55I1}8gi8noϿ .F\=Tg*8EU"B4pP[ q~ncBfx/ p#N|RJ%q@hO<p$_P7띗T*{4cA]Z{dI𶽎~OU nAÈf5kڻ[x!]\f.X@QAFDG1~r)n͙Eݍg>I)k%Z _.xHp`Q,}GC_Y{^nw&{"Jx՟`koIpZKG푬LQ&QHaT")A|:Xy2iojk^rANSjhs쀿4Eܼ/8{V [ x9|I}ր`l9qc㐀Ns`C7mژv\glpMZ^{AAK6û:->sCtGHjҮ,Hͯ=81h$VMob6"Hwty ,6b8 Jb?Ur$5'|(O3JSCrd9TdjK0f1t,^ò=8(V3TKRGTbY,$i'5Q8da,#<}C\-Ȼ{jl^uUA"OIve/Mӗ/WccrЍm rf/ϗ1/aSctԕ'&,(E˻V 58 O :rHj .,_ێU{.&?L꥕ }%Tdu9 t`_a| e:_KrfrGPwבD)F81+3#4j-  N/LN}G \<Na%SHB2I9x. C$9BE W9MkPU;igd2N*5|B ~A.[IV.i =Կ$z`̶!>a]},1zz-tO%V pϮx3 đAI1һϚk?ї@%2[ ԰A@ge5ɑWa=Wķ!UڥjHvyg!jX1S<%y2%QQosgV4!^.AR{beDZha*)%xUx6Y\Z}m`P3L %F3;Vdz-X?O+{vrgSW {_*_=*d =ޞƣ28n] ۍm%+)oa($J:$zY-2 Zemr0o[aH͋=<1OǒN#f̚E$l[/241@ϱұIHJ4L*z/Ę,Ur=ALIKE7auDPf_3]ɕl$1n],$¼`-Y|#̔mL17IZ9RkVJJw(6m6^qh>v1Tr4}'[A*_ K_=c;9aFx;!o!;  :b( LƿȐUa iep.p-C4/-kH0ZypceI%q⹛ N|(XQ” %IK"A'&Qvkb͘@S&7AID6ߠH/ I(=sD/؂x`u9䇎!&j8֕xޏ<ńMRS/Oe =mG~gE7\S55ՐrL$ 0sd+^VBVA< ePqHR;o6Q00̂SbiOSMBXif*/A6Z$G`!$_dj2(\Ox aasݫ`lVnj/ft!4zֲqxoU_5~+տ=Obk#~$Y #>G2GLRAE򲯄yTAcw?m[@N&R_y!N a1mJM I؎lE-P8!Fr0!8i%B{3*ϧk r"P:vsl-: 1ѺBHn%QcmIP"21L%#$91#%WV,!"ŧ݋9xƥN85fΖ+\z!u} b&ܧ No_i F+w,MFQ_5AFa%J ׏BU)Nn\KL }GbM(2 X3؝ uj;i'ߢR\#8 QH2H ß5\SɷE%T$"y)3 ~ɘPt#gtTc5=9:Q|;-*CnX & ߦ4ɩٷ^NtHܣ'|~.7gnu/ϵP= ]- C ATgHv<{)yK'Xn LeGw7}O E+7BD&S\jp&"$_>Lmj[og7|R$sD}1?I]0>(j6$((´Q@ ufvVV< Cb+ЬQN't7H`gW0qYngpKݿ oѵskHFó@qiN`V_"^@QxYG efyVsS(lt$"m '2no2U 0?ӄV t8zЮ6t:qLa( dfqT6/N|L2L`}OVJU.'5XV _$DKHCER#nYXj=ү ,Ru4hg[|ӟgdl1ib`Tci cK{WR_SPڱv*ߒK3Ϣh_V jbtA`⽎1(rWrhd&? k(ʢra&Mdj.tb*UWA[T)CU?LhF&щ2I3U5[6N6bNQr&uґ;nizb 3?l12KbHb>GRg$`:; q3"L*Hh%DO/b/b<2opM+hy0gs}M7=db7B:8pc;8C&ʹ+p/ Tc'Vs4ye? &@Ubw ao:k1g %4=+bT`6]忳,4z7WC!l"8IZDB.y|4=9cTP6hHU?1{ (ԇ*H;"bzS${A-kٚxu=⅘&kRM=>9z<&i-%,3ڶsOZ v}ؚ~&d͜YQ=?ksURz~5 M̘2"s,r*)54 >Y̟`J!lvb}И j衟W{NXR6NyVJ*E\ bׄo񚞈 AZq?F9µS}P_ԷR&J@Qq(5i  Rfj\/Bw=Ud@ȃqѥ_qV_a;fL\'6Ec4ùB+X%41=YrsJQ!yz' `@ZnpCf-NϬžQVH 9s /D|yNG Vx \͟-.3CREeZIB;!{CIQd)zvɾ7xou)3!w uыoꯜ,DŽuApM00%"@f]oz>u1L14 ODW4&Ȍ0|Mߤ $ݍ3p%{)RUb%Ck5ykA;r}(."f$sa$` a. ~r-/Kܥx@d}jX4FoUC "TI ͸X2+2\A1QZ¿g݊)2ҶjEigE] /'Llx2IqiH7HKA$a_jb3 lgA*U 9 NƝ;ݼm"kz*;s}hбb5Ll6^m(Ee=$`R$!:PHER.u|-NMEO5oX K.s>ʩ)R[u_rf@cU,L˩Xjj^H ww0R9>U7^Afq0*N] >}Bп2ߩsȯW'WvرQ`PAn`!e)j.t5jȟs$/Fpcpgɒ0x>kDks}a3( ;*$u~b=G6BZI@M~HfHdv+$WƜlID- G`v\_AQAg6Dӕ3AI=1No{{Ic+)0o8F)zQJ8sŋW?zNҖٮARw#|ӼtБcN8y /x9޶Q2fCMî 6oٶc8p,̎-V.ђj7oT! y'6 ծQM?7tnYT'>z9_wv'G ʭVNes$Y')=@6}cV/"B-`GjJqo?ȟD,-dDd .ٶo^x{Pe;1sk\mO EJ𶫫TJ}<ǹPVYC* gt(6HEEj'sל/3ThVÈHa@0P(`$0w%!$ C-yR^F?)v)7cYr1n=4ڻN Slry##IjM6`L y:v^jN`=G9g55tذۏ\K;r[{P߿)}'C4qh r9;)|Bl FXk$m$NTv5wYkBDtq׬qDǛL4}O9Z^٨K/ܹ0WK, /H"wLe\]itq|Y=ЕUώIkkwxaDR猃ol [;nڴxeћjMɿnoG) Jy|T.;;'m7w*n-|OCԬBX'젿i.wk"bC4n@^=}`|U"0J;1A&Ve_(\dci {">6 tc@㿀nˣ4tkKtwoqax"ХRfA}Ir<`)f$>nalj Qi׈s1^.)Rx珿sbˍ?2m%Q0w wOgC2r(($LӣdMbu𬟴kDN+;Ѽ0XwoI:l/]dkmeeI8չO$`DRa95%,h™~HĞ3_rZ~G)J'm߰Fi3EةpUԶ^sby J= \Ρ hTGXO42L-d[ ՒI"2UЉ4v@ؑ" c T}]diTj g(9` %ae )KI4$֗$u hH>Akm{[{fZRۧwܿᷩS̝=; %E|{wdsf/B=ۻ]݌k[ Qiߺ-V*h"zrdiH^UzؽePb%AA*~.oe:,;˦6~k˃#E dzNƐ7~Dt_3qA,'.ޜF'n@8;hמRl=}v M'Ppp:~'q$أ0G3&T^vND:Gvp?okuVґLad迋IW+;pыR`!# I5%a0 e$>.g`l8,Cxy29FT-51 3Ol[9RVUF+00&$8 %Q0zC"yn,VsIe*II`(XbRwq8@Scp-RqNx#"2'QA[WϞ8m*:-}M!៧x@X},2@ל0)b|6}*po{h"/䢴G3ةS {J*"T8U`/nat^y_^/9&#*=ܝ훣gp%Cw0*A]H"Fd/f%s0Nc8Yi1Hh|jȉԦHI]0}o1YY>-Z{{pYp(v甑?O6!G 4=Qz81J.# /_:x>n)K)@cUWb6R΁ޣH)[_UР(ty ‚*țBwwөUʞNvV*I*PRv_Gd2pOW-ޥuj ,dQԛ7ج^"Ĭ*A4 M,fxkS҄͑TZR+-kϾ {>㶮Ѹ7Y@ #}5g_LM ;@Sg3ȗGnA{J. ޕ)붻Whw߬#uwdA},Lmu|Y!n^;wV fo HJy̨w.1[J ꭲY4nBK! 1 6*㿩7JlW;x--z} SmT ibe k3;>.//u lVJQuv0jJMHXj[u8ҥ_@d> "*tzK~Suc@UA%nmX2[)sL^1ıI%Pi[w©j }_WQζHa=h z ʬ܃7mR~>ﶟ{ePh*' 51 !h-ިl$J]MASPV+s4Y) }k ԓe#u_0Ne:lԌqHݳL꠭8g&%$$gBJhJ &y#׫[ ؠ58މbc^p  ?/fAFW+-amEO2C4l(y5>A*l!+.Vj>xZ)ިcϾYk_dw/WO>yJ2j+*YdE+@$ogb5fw"1+ӏ+Oŵ}&f/(5˛VLDyMsK`#s;ghF!=7/k@aZA0Kf?_XeBB-~xFG uw3/HHo8 2>(P_9;R'iIJ ,F>"ʾ%<"uX|g$j#o稍E* Zt%ss /,X!HesoOepq[иѮA}=Cp"h١&SӒ !9pDWh-S ۣ XRھ7IW%Jti:)+Q5 ?>Cx|R$XkgPgD}~kL8{ՇIЪ(K3M䞆`eQ[3Bb{ s=bY|WU }b[HJx`HM?mIZ6o:tgjMly[!spMmoKU50l?;un__rފe!/;NĞꨡ˼S,Kp"0 VWS[CZHf]9:c ukuIV4`ŏacD]^^Dh+o\'vըz1LE5_k7a'}=,G(Mã;:d!뗂jA佁/^>^lH9+ nIpPZZU.+ UsV0 l ],һP:T^PGQl@ZH&KrYpZ4K1h59*eqFQsyT#4K1it/~/G"]a^;,K^[ irB~5r J[/3-2vDz ' 3gPH&wB%uyP[d˙vX@>?0ł\<^j@}+B$ݸ|= {A'AvkqA! <OpT$_V)e=).ÿT(1lI̹_>N\ q(hI Ēz[h5Rvp2&)h$+9QC Jkֶlʙ~f`kMQN;Z)Q06/Csa)} j$Ծ-JOJOF]>͠f/S^n %byH'|܀4( ]E-cT9k$TlmeqPuR??R$8k&"#}<6,! Ae3YK8^]-BhR#(%{P-4R /(Q xhs*~A`1ұ2?Tug^`Q^l]~h"Zqk72K穼I͹f}QuĠ%(Wfqa _խ\P~,:RNNg>,D Y+֔peUSNi٫K%a[#4)$Ty!/_` /Xm * Y_~o 'rȻ # 'e̗ԗ0o*"G(0kZ=o 3P~i4D+ ePgrbȶw~%?-uh.@bb=PP% ̀Z?y?h`H>mFcMHW0`lWj^@rx.[5+-Ndࢆt3ոbƊPxܨ)M|πdJlUN9!EI8Ey}y Hv||.SZ/ k5!7Ԓx@d^^59Lʕ+`hzt,{;Z3OL8G oP_z@\$[h(U Qr=[Zh n 'L9AJV#- r1޴" ]PRӬ$*KRMϚ xKCW|!_e 'dv^2Lz׫_ T{5r5Gu Нp ?tYRGyZ}.n!b*Z:TҤ7p6SN(|_`P$dbHHs4e)N*\)TRM*RpO^Sp| P心>P,`YOtVÎxA1g>6}SVa&)h8_Insq(&#]+T$ej֞2 (qy~{sFDTMqY$OТt jDӥI *zPp: 5r"FsRbA8,&-0C;Y]ZbWf"UVK?t,\?jxd11ͺR? v?F zއ@=Bm߆8i/ (`*Sg7pEܸA>B8f+'rk2N\vzׅ8&UsySj3 m|R$,8ׯ@.Oo1d=T7C}ǼuRS>7n <O-f)Op `DrAIzG LF*I~ a !gl`P??W0'?Xk+uaIm#g_ }Ӝy A^ɇ{Wݠjﷂz[sݖn#Z`P1,[xaK3,i4zCKj|fg rty{2:pF9$!"b 1@Ri>ԕ>.=I`4Ut8ՍfYF7oS]*- fCv0=h" @"/A>Q[)tPT8축-b_SW8ٓ\L3tY_k{_ KjGb 'JL1!|*n="vmK'+?؛ f dR7}@q&/M 5-8 k{mv ۬M"3g e"{vuSN4p<|笊ZuIB?u>nCT*I3d MpA' \?3ae%/%R)JwW]}PkSWg8gʏ =A68q ],1&X ۸YPM}TƯPl2qHIB 5ӨK}M+v{v5χ'YhHV򮔦'<@A̟ 8Q vXJT<%1IUt{X׭G^zH\@DN&#͒3 6xPr(_O  ;I> 1^"UA_ų{_0|:%b}94BeJ|܇jƍt{MgB7m^w(B7k̃n{t7 \71f!˜.+i l!! ta|ٗ#sHnf]ǯ7vU28m 9_C b4Ȣx4&NjSRؚwA}"ZkP/Ke:g}I:`ъחe)ARoj@3(c1*N/n*'dSBk^@|Nd, s֖8+."ᬅ,F+B܅ E+ AE$A@oGu2C\99ޠ8Ṃl@WY= >eBJYB\b!} F}&u+N7 '~ 7}A ?PlF mgi Pw_6W둿bL(=LM*$~`N3H)oҺQcm7\N(;Y3iVNHgeO`EI пghћ//-M-8Mq5I5ؓ2F\BH{pS$,8+ ^dAPB3gʹ%Pi:~03 )ѴCs'?7*JR͂h&xLYZ2h )Rlس ̀pD$: Nxɔ/6LaT8%\|.iyP%q,"Jա碓,BfK!P9?VoK^ EG :8e Wg0 d7BFV?ޠFM?8cх< ZUf(O  o8V2 .Z~AP(tQ}kZLꯔ+~t*⭄)  Fj> ,[7ZASKlڞX:$Z[Iu ?aEULJh=%(oj7t*n4HQEbޅ&|z!2!.|_ VdWA|y&:+EvײVuqmZ ʂ;w״PIGM,M22[il!(! +2&R/Wϫ0.lO)a0gLC+2`vKcׂFLot7oFaze1^EϦ+~KR1oT~Gɽ2rICI@[MӪ2&DŽ&݃hkrpCu,pL:A\aۉ/fA8icrlh~Z?-ϗ 1+Tז~^i3uas aP" x7WΓ$hacGqB#S'Sf.k'Yd3N"^8;m\$-9)4o5e8(1Dr#~ ʇq8&!NXߗ@^Z)~;g,岜AS#60IE(x3”Mlᅿe*֯"o(P|o 5Ct`$ x3ye@oiI?wBYV t q9+I?|X:8`0'ceE^ۗA\֡tO-r i;c#Uf:VH0`꾊bk]G_dt( o %} Jro 8!Ll^~0,f H2)M ?Q'ivE3TzwOጅjіx:bhC5lY ?g2h|ͱ`щIj'O858u<.MUseD"P%,vyVe ѵ>1>ja{6TaЃ u"o<݁' …Ӷ۰gMGv zbN ~MyN2AMxŨH2f3TT8H2o0^&!݋^l !2wQ3y'/A!&-?J!XTIqX*%:EpӸGvd6R<ͮ<HNADGI&)>"*giC[P]kBд3ʈz1F}IHWb…Og/YN|2& :c΅Ypb|C*:ᖋٕïEڅKhչS2Ac"y hem% ̺FRg'хv s` 7R}Q0! 8m#ϑMB%\A%  Ѥ;IS Rӛ_0K b?caYBK 椶Hs TxnJzfDMA̾Y(Oo'pAWh"/D9޿BVbR+|`hC#v9l#pԵct{s%|wX_k2, .p_ۼB7^(ZCaL6=z[Ҕ+Wp \J.TfYKϢ;;[`,$U6m0FbJB9^@uPq!n\yue03`XQ|HzXe`JOgFSC4j4'nw=0{}"VwwÍ,kj ve{NH~PJDWd.B[Osn–5kOHf J%]Ba 6^\uFhszC˪[b), \&X2; M9i[kpZ̘\ W"&z9:<^~V@Z\ԉN)yBML0XZ&)'ΝHMuMAmkT$¡Io!ژ5^}ŷ1.z 1A"Pt&j5%f@Hi҉h!czA(!h**y"(E/+v/ޏ%+q$ pkGٜRXf{BŠ;u6L1x4MQ8 XoZӜd}|3{pAJ |+8UI/Ӌ&>Y%ƶ΢%$a )5vtVr=W.:s"y>a7Mk YYU(Gj3{BHJss ֡ڔXIV- ^4seՆ2ꉨCKtQ.>H"(D$U D<^E-2: ƀl P)&,o iR1ʇq_ڴ $FDaB췻IUh5Id{ ʀ6@G(BaG2){#|v\ /bV 1pV+EmM@| hCQ |GAh a `s PS0VH.8sHB7^p%w2 L b*;v<_;utS1Pyﳱi81(XI c>!^'T*?IQC:`@y3 ^P'V Eih]'  mt+`S.6^s W,RK]H}X*ߨOĄ 6VYťgӴ8 #U?L  >Y2Cu ӬvR .Dd,Q$dSbs3'ɓϳʳkյynby,{ :;wC{ūD# %Pq|LKE+WŚCJAV[QV"\$ ,2SUL#cX M#$? $U(O7"Uw?BfR)#'qM!B,4Iqp.6&Xoŀ@Ũ^exll"@f&Xxb+݆fUq~fjl^iA S6C$l*B\ y$>ǚ\,47k*v `5qӕàgaĎ_{$pk:Ҁ _}{|2OHI*o1İo&Y&H}Zmg^nd2'O9w_8TRsK;<)XO^q&k"u]I?Egz4 +km mqA‘.]?yʔ5`C) TMJr8!Aqq~B! T({xhՁKc}dno`iw}\t TE᱇ҴY}B(n}"`r=M !ۢWCc5$?t!@qUfG,#݈E/f(85rh.x]^U ĆQʸ_lX⼟y3"8{t ^u)l=<-/E>HDpnL$o^Yc`j%im(=eŋE֦mj8QS\<'d%v#!烊WDfu768F c7e5( HB 3D P/?j!HE޿ !X `!-/K/,J#T}e dt~CϜ-!}'R7[~ A%⸴@CwG)RV8HzBS]PMʬ't䉿?ʜ S. ʡe\//Xv4f^oN$mK?C`oBXJV)$f1+L6,PZ_:]C"5=CQmQ$R=qUZAPW6>SIsFOwV c%"!{Yh'!vkO hq3g{fiH~$FHRlFӬ~I|$w64G)ys)i&TO~$@SMH`khΟ$?*2D-BQS& >Hۂ%ZA:d0$T߲IԤGXU솆F 樗k}|sq(cdvl}7!mCnJI 0FXqyTo}/\r%1["AҍrN% >jq, ." 3(<7H^ݼէoKz#V>'sۈw~*cf'{4"H pY >F\ІG3.*&.ɝgd>P"q6ς 2:Y`ua3G`)V dKUէe0j \H"vb]}?⼞y%ٯIq &V*Gڶbijrt;}4IJkN&F()NG*5<̱6wW.L3g&{dv¯O&x+n-,(iPxֱ1eRJ츇&ǯ& Il7WCbM {>g>bťH+qt`N(Ul)9!6*X"ү?TJƿ EKLN% 1u6p"Ri\8VAM a8:6-z-~ @'XܪAҲ]E)p9 MyRS-@ Jr+iOK!fwY ݅N3}hupPߋϠbT񎥩[X~D)?mx k󚟶1Z4|$췿.9 LkI.!|uFt %1RNf>qִ(ӻrBE6%}9ª'we՘9\ ,LujAHf$_<b!6wrj111[m~ K,g @Pe7ͫq(oNdRˌ3Op\*d]H@}?>j2֕3v_2دؐl|0}MbknDX^[!f!Čz-PU^xNHVKQs.+;LdR6rCE!9eA`k;U%_Հh!jRڋc5sR  )SfΝ̏VT-CKLUw ؖ%D4gX/A"RM1&6]S+i߽?M_hh?wzM6Bj0?(cJ)s9R{*ujt֭{=z(ɷwe%9`(d$MYB0ǿ]˳b\8>pМʺMӵ8 @sV쟏viX\#)f\5;zWu|͇Pu#ҵ!8uBdUqܾ[PߋP5b;P / i~P6VB8U8AX,te䴥\jO` J|co p[D[-E#b14fwث(VaXc'sf뵶o^ègh-5I )mLT$&'oƄ_g*'tX$arʁРT} %O$7]י$zn3 uPsFw '0 ISz7WTRۇwHPvbV'fn2"MPbʮ_ч#ɩ{_S s7]o1[}Td6>Fnz?VYro!AHͷcLMM-:T@$ G7gGì-4k3w[*RܾrsC.Ī?Bl lΪVJ1 rygR=9 [0 WS0b}P>rFDad>{sreΖ5=N#UR@S L9.ʧ:Eӵ$IF{.1~4T4Y [N:̂dPMIڀ+wƥ.]Y?X\]M9CvbJSe@+oЮ-i٨߯άV)/B.N"HF|rѠʔ%jD/ S"SLd_27 i5)JxRw !Q -26] OO:=W#UEGh  -&ZH!5kzxXj jA_ h<%߄Zԉc FWF5S/\_i *G?"a1 d?=HOfi8`ͿzI6/i>B"K*+EX&9ѕp(N P~zU? mm9dRWc!ס[Rhh"Ta, FY~ߗ/$E >;k(K2h+9%ExIIt~0G/bo&} >x4F#7$nGzC'fRA>4iR'fpړ'%݄Ou1z˂倮H]!3gGIS=)юld}PaQuJ4 @Xcѷai茮 0GC]IQ#*/p _o6rq笚*I9|ؐ|Xva49@)pd}~U_ `f`ɮDCuh6DFRN`4 j3E7zS®r+(}+5rz@c@NrS?,!dnٿH|ǜ"xݨ;8ϿRۘ{sYZߢO>EaRWOKA޿ J1Ov^Ùy$4(fv ,*p(SNc-Sӫf+SąYQy!kN81<7|M ߆+f'dZGOٵcF.LnM]X@T:U[F lwq|db-;R.Z!m0M+_kF@AeQ0É"ȇqr9 _$u+w*U ̘*Pfn49`ړś,rl;0ܦs'uj.K%˔-i/YtBtr ]ʘ%׫-s`ZZL1c\4JԮS>?,YZoџYC UiڤR_RF^x2_"o Ek~wIo>gde t՛JaϤ.aM ]bc|ދ6Bːm.(!t3I#r1l߭oYuDe}s3rH"铳3|,%XWhW_n+cIѧ'{)YT8XaV߈_Q*,Iܧa#F PY1a[#SS}to@)ba ڲwxsg< >iէ/!#7^zc*rJ#m!+$(]'_{sB<$՗\%;pc˱Գ}F<2iwv"k4qںjM6lSHj Mx갗yOw_/&0X u"fu<{K.+T=i9vq+Ѹ߰aiRnY#`i F> Y QL"=Z[;VGK<0krgΙѿ:Mu:ddKϙ=63W;Rmx;3ֺ귪M=ٓӓ]"9z9?ZJ$,K{f9iC-;m&9Γ_EK/zbkg}I]/=w~Ca$Sb|Hg=3#cs -C.U=4|˙i`^5BB9sDd[k{e6V2 Ӧ̬?₮Hr<:֭cދ;H64Ǯ"#̧ﲥD]a۴zrCs Bo<~tdm]iGMla+ϝ;i$}ft8k$}3簬K6:&;u4ZW[rTRf֒yFav&\3Ӗ+9KG.aTC-yΒ]$4_/蚑QcUtm9~A9@MdݟͧΤи|Ѵ=o&Pι^*iM YHo6XQL>9yH̕oԓ!xYg56o̷yi+cI`AQo@رKP,LV[t!4;N+Ŀ։o{{{Rdwpߎ#Wχ?ST=u.msP~1eZC-A)+s G^|f31[BjirV*ˬxnJۙ7.<;^B\SM iHo3-r∖q75Ϛgȁ#8D?G qLFδ;-)F2@̩0wA~4y6pԶ6$3D6kh׾#? 廌9?0n6SMe"\sG0+"3Cn:27܍gg, aľI+9$:'H-C9nJk#F^ ѵMp1E:`OSELsҲmKTqՇ  ?r0`ؠ.Hw9y`в9pD|ݗDSÔHʋpmK'fR;8"_-3?<u;2~fT=ARy:iO 믲)'9WY͢b:;{ƪoY0|Դj`)8c4cKF.YrSD orxZ!/_X$hZkJ V;ܷ8$tEJ}a! #ᚊmm֦cSj=Ώտ%~ ?| 0"JB>_+6U ɒ W,Бy3;guA? Q04e="ET6t%uT^ ;S\єW-"~d?ڶ)&An#EOvo{(ày{2{`t;Iҥ77 rWThsאo?;=N>#xUX1C6FW#FLAL sdYW ]j1UJß7FrA?O8:r=j|,8ED'{l{@· 9dsCEV(=dLi|EY;GPE3XڲEs(&-^5(ƞ?M.馲\}|6U,ʼQ޸&4/F`<70_)fW,|*ԥ# `1-\ñlYV-wE'q1W(t[~aޗVDh9z6wToB1Whp p875$e-GnsҌQF2yp~C3}<xj<ʷѮzHDbw_FNce^V$(ɦm]r !J.=L9c2Ǜ cI~|O•u)8,9>yt.dAmՉE\[j73d'|KK]˷)s\a"A61rpıLXN{C5㢫-"|H-ANgtxy,=qԕX.<.2pKM[o?1!纹J)ǯ`PF9 2hX=GXʖxq!o;so!xg/t#.'pI÷?=9i8c$KWZT/q[9?h*n)/|JOovAKr&" hE,u4i-Enty^Zj,NsEq>RFNz{:H"USuyīU+(Qm0P2g!亀`@aaI b}KJDshmv|H,,r WRT4 e@$mԴvo\#fq(ZQ~$3(%խ**K#̼~%-f طyZi3c5N Q b# _z¶5=Oz01i/G49v dU2*\pE.|M>*h\<MDA3MI@XL[ ^xRliU$T<θGҌ̚p fSaR+Ϥ2(h}I`翍<>l/d+׬]SܸST nK) 3:"_Fs)I <==c15(_vdsOq%ȡ`,*1=EE XMP| jWE|ELcy9ZO&œ'^[NjJ2$}^vdYh;ɢzE~YIYA5Զػoٚ!FtypKpݙ斟n0v_~KyŲs3)k3`2,?9y$Q-|.o,7ɑ$pX>bRՏ*$nGǙ4<'4ȐؠP Y #NH;]ϊ$0PQ< ޯR +]J֮#]O'E#Òyoqij暈4 ;xz1ձي\,Ŝ}7虜56#~%-"*33wOV]YhX;h'd%mIلxO]zCyjS-$c734o%&j1OgXW\4r!{~עJP[453j ҍW$>+8qcP;)PJ_D>j)t ~Hi3l%@a>vu*YwmfQ@|~op[F1n1wΑ =&a ݐ(p ɿ(HmTY=쏅9GgzQNS{vk) _T"8*R8<[E9bZYԡXCF;ը,T=x G͔:MA2 ?6 Q8Z{:rz7 ǧ̋;!~Zfb}+2Eض/7n$r]1>w֔v5p:io7Ӈv<#:$9[?ͺt5ZDYn j~fdzT_ ?C#aXk@: m8j6/"H4ؗz^lV/ΕXNX]J5c~B6v)5ׇ@r-MT?VI\Mci5^(K%2DBO26@jwuJ>~rD5Z}8ܛQEvݷ׫EN*>){ uAgW~4/_]L.V^ )N3uO/i<䓵1T̯j-Tc&m6\D2 0@^WLljH xpQ[:]&` I:j-A-[)O?1rl}}igD:& m({᪩;!$бӈ)s+Y;hM˛Y O}h"UH(R7ԧz~N5=WJm$cp1h-e.8b̕P-ԕJ\o @ B5$7TPs C8P|]B8a .Ui_sKmIN=p^XH5>5+Exw>^Q[$:Оzd%싈/"kH;Mճ  }d FuI*]kpd&kI꼦?oW :3vLD& EK({XH,21gἳߣ{2j_3j*PcI3(ez=kzϢچRS `]~/S?S}!Nc(slDSQTKB|.&iEIa~F*༗5 ?=`!ڴ~7cVSNp5Np è- RHXDQ'xj k&zEjN2 d\BR9J(?4+jLs,ȸ}H9CE6ұ??L~Q:Mx˧ԽV.>! i(5 1ئ+fxvo^CY ӷ>0f]<|x{v ڬC{+AͽEc I#u\)lWytJ\76kn^$e_~ȑxzm[:;m1TʴR%#ѽ]sgEO f6wR<O ,֮KURLAw1g_9$L=֬]K|-z#=K # :ͷ4U&\^wAmHc[ܓSTBL։Bﮜ,%J-&EURx`=p,(v0o-Eψ\Tt)4v!V`a9oxW6mݷ=<:Op ldh RLr ^BYgC~zэ+1R; pѢZ*b҂ͪR_K@$`[ vq: 4+PMC~O9sNr^˶nvfXׅS)`o^UP!fo-GCW?m{Nc-A4~nt )eKz ؿ44+5]Ëju';v TWDZQ5+;ce2% xvCZKϩDxA~J/q:͵RxQp9!H2oUw/87t;YXcfQa?Vx`=`jj&^Bs2`.)8 r?dw!0rZ6oS8pUZ,_gS's<;'Ȭ3mtO6XW>_fJ<= k+ڍ}ӮYZ.>Z6o1Lǵy(XSU!?WcuX*Y, nx0}mHkF& L:s9f(EdZCJgq'2%h]a#Lp?,FkBD ;2҆dRw7ݬiε,*jL?5_ɩ2?]ol<8g͕^tíRZ)Y~-"1Jǵ4VijhD0wTAΣ똘W-ĤV pZ0s7\ X]רK+ew4t+p^ ' *ct$2NdP5zxb˵NVu4S5{_&y\c]_TX~N|r+v;"A:\"6T, C~xǎwXn jW]'/Zx`r&gO"~X,ش%\t}B>؛Iy$Ε$=EFrĊ "4c&tȖ]g2Ѭ/Fl2M~fr"fL6%fIL!#hX<^=Qq R'~CgU 2v0ěAgG߀|9% 5fk#iyF0w{G: *e]ز4P3ၩZSK;%݆iobS/[FcvFA8G;mhNyj$W\*9j@E䇾 ΎeSL]N7HN[+Ēgբkƀd6%y9(p W{Mn! g%\2FǵҼeE0w{1&SS][[yTUzZ:JF L:#(aP(?e?cΆI/hkeU"Jι ڦCVa(c[)he V8~MSey, !NH}O"2Qق \P3pQ]HjËNwY1xx& )-!}XRBt.@)xOaoK$4 R$*/4|sb-T}Nf41m;qvbS ьHmTD#ozA] !"e~/i0\qa@}_H Vvlhg.3[4J{_[!ň-G|ދɢa$¯c/а2Hr g0)ޒ4 3)ϋ s*Os!CزLǫ8+.xN῅EZz!)ةt#bڙ+8B\ɅaܝIE8DN6,[Ӣy>y09Xe >”ͶGdEIc}>8!.oP v,pP+?'AA__]|V,InXʊ,ׁV ApIBcVd.d{r+ C~LI||-^bTӰfpNd})fq5Z~WX-J΂)yU -Bb5 OgY餼F5sjep.ϷIzco,?-Y'ڴm4+uwpI$=*nO\ZO\|ܟ76<90_Y0Q )9q&CB隢LÐ/S<#7}y!aI/NJvJZYu"[\Rdi/?oQ mcx $ю~R]aJC5(&bs0]n!EN+z>5^UbE4wI?\*x=6M~=G  '`!ީN#f[AEneB48p.3tu$?8shfw!?wn1a챫Tj/BUg`gPv =O1kΜ<_pKK^IEa_]*brY* t)>k$Io=)\tፍiuGaP7 Y9B2RB8.b$Ե? lѯ'䓼Na I; _q %N6G%zEL)NsUф.1LLːXmo +3sQ^hf ӵ2(koi[@[dG 1gtWg§]zSzZVAGnn; N&v.HOA¦Sఌ֖Ż0)Dg sÎ = 'n09|,S#ө@Y}[gN%OKbpS! Ȕ n;'-rBoc[,,JX]` l|尕]c\- du#ⅧEbʵ]̝)̍dF]$=֗uB!0ތF85HAː;7pQ[ԫ@J+ ب;К9aت)쀉[Azߐ"ܦn7] Q=RE+n%d$𷃨G5J73&V̎%rbl=0[dݍ6,2QAE#=90"gs NT~mu8'ڱw~!Hj Bi#c 6rPlAQ"t{4D6]4E=iHxʿ 4DWf+|@F_(B  0 ]KG ӯTxM M]% 9nVΉ a8fp f|(.R=iy6?5 /z^soy~D|y%9JDKedJѰj=๪$?EOP D4eu,t(|gFw&V[9Kye #H4g˞P[#Ȕճՠ#4~CVU.+KJWOPwB '+Q#/x+MJ[ O IIp]}A-јy/0FI0D -`]A 7WpaC!P`Og.%%\Gj|v%Nf8 \ Pu_N$J=2 ;&t^=&̵&Flj[LGo=~.21flWVIM! ߤޅMM @ b)/MkhPMM!O?xN6-ѐs!Nb]!=o9PCxb1fB733iD+Zt޹aDŽ23Wd\>9 Վ5Nc&fV&''| oSƌOn3BgC U`8.@ЧEα_o2-Pfb~;LL󫧙{{`׀Ӏ}8_G]RL違z@ʿs&4K.-C~:ܰcDCGM8Mo3>^1RScA,3u*ʯ7~ ?^*mvUilInz5/2/%t}{.b:ɠ 0~ @ ;ç5Bwe{ Po?E H~@}χ bTʅ T  KW K&tQ +(VvF27 j&v6;Ӹ9QZ(,_@A6MJfiwӽeOzL2&i=Lf@A3Pw.2S{[k7\`F8gJdeOzLXl2+2xQF/s\&1qaR_oniEuF=rɟKK? {Ւ1e-eTЈY5L*8-$U3\yl$e>j-[!N [H,IϗIfj3qձ50b_?c`sx95?|3?e{ՐSEQ_U1fhQ=I7EL8U"&Up5ee~rͣK5F벿5P뛡v"8j| #;!R[6Q`VLgʶcpYHYZ#DD=OL[2f]Xʷai4S)6R#mw#Ce [36o}AHt‹kѵL5%%4L'.K (7ceq*h+LQ%#93q\,`RydJ=#fxb4 k art?i* ?, ?v8RS~EzOK6wQ(T.O"h89f24PIH?u]!gh% g/育,;f.?p ,tlQ(/#Ԯ:\I \}8 5 䩇yc0}44/L^˸v'fq SV]͕75s[^<~DzHS#ixo %ulGz=Vج2_[bi5LaE5Ѧ֫ ߽e͓Q3,YA4Z-8IPK+3/+*`i]{GR:.Ǝf#t\SENOr1+ޱuNW8uե`v gq#6@G5Z|Wkv!ۆwhڰi!y Zk:]vZK҇+b(K{84OIWk>Qnċ+/?d2_PU2e4HKٻNKuW54˦uh0K_uBYr #[uZ]#t\E~5#;սS Gw6h7:*_2xn ~Wuё yhY(0^3Pb]yL_,@ްU/H@S[jK8/4f/nxS3XJ`G U_^J8|ؑ$i{|ƛu.5TzB w2j)F6 6NT{\wTE{;k%Ş}p=B}7q#5>O ȘF6mAH+ʭq*n(+VO}33/3ξE^d;ugGN[pYJlJy9T4<7#v4e=ZKuᾎ#>ȉhȳF š#wI#\I&Xv/нnYrU?p'q#kVa|3?7GAT${# b'LKq\ItntXN@wD_;8vn&*t>m =Rhۿ Re9>~|zqxt]IT8Tm)W#i= ˗<w71u*_ }||U@>5Etܙ=fo;S!V{3e'8rɊo&V'ɟ.0RVgdXh(ƚގ"ֺM/QirHşli#^>޳M(-t\ ZͫԟY^ >W#il{ʀnXQQw;e9&P>5KH" զ͹4 6 ),Us=-IW(FO$aRdg\{ꕯ1T&?XؒJ]d[__:k@5kA!ufaoF6.eJszc}Y9\-IwetPoF[6ߊl^#W^g?6hb^l 7o1l;pusޒcO!c+"Ԫ֜403`W<3~b`ܴ4V~5@//ħgd&=;܇-!I{vvdC ~7~A\6,Z2o}LzwG)g EjL\ё~M۴оM`,쭔1P,dr tneN^rES{Vs=4n NBUe!70Bt,7gDh8>~¹{vVq#>ȿt&}l6Mcwqss,Qʰ!Ͽ >q/j?^<"Y;}8$c^![ssUI@f\nacwWՂ5o㩮4P_.z6侊>ǕmEJ)]4w'ŚX)FOnlTF*N&#"<˶i]+w>Zkg+U( \m= -R\ Uh9h7-[TT' xLJ/ːl .," x؈YLRf8 `;dyǫ9aDd/4JIaĭCb CӔ CD9$"';pAC}@P;x0,HbTzmW/6\ 5/M Qf-/S1=k/MetktVsuҶgdMÅ X{W{8ʪ7dUA/אq_xptfm!=[;}fFp Sww4#37 `1f~m^!=bgZ+j{ VťU-fg5p;"+[|Kx;_`:e_m+hY~('P f0550of}F}3M\ ?"Hj'BK~Wk&`/ c=@cM(ʺFRj y{%B$3eE/xr+@CHS@gOz_҇9L*KNV{:𩀣) CGFs0E5H%@Md 4\GeRm k½xtHZ[<㍊'t>HƘ෰G}4mtKO٭v48;Q| ذ,ka,ҫԾV<fcZW' 1rWiiﮯH麏c5fS7-  b| Jݸ%ܽSo~*T ^;z{^,Pfk6\` fw'dPl'鿆 Wg ɿl`{XP]ë:Cd.%46g0'h+&#uꃠjl/"$0uYe)?|ߧ^^nc.7SB:XFʲ (g@mg~ ߣy oi=%yw5<4S5,2 ȳ%_`d\J)f|rw eӯ(_<[p^<Po5AT^5HFBXYB4eFx nC?fV> |+EnyY aLH&TXs0OoahC+"~hVVIkYW1}j2ÑG;uq9sLn}eH6SiV~{ȻIP#*/.U4Ӎ_b {{sd1XX6PPxdˣ >R9E^A.$[m~M N]H`|RC2OfUC;컿KEG{ 1j.4K]PFԤ_3RlղcEPeYž,%9|Y9 9ѱjs*|T4_<}QOhpDQ&1vr[Gi(=ؿc8FvoFDo..2hXz%a7A.&X&:(.`O*pO;)!m{Vҷ/ʮ}*\oǬ"¼hʓ(H2)a[ZHӰyN =dftmlMFpX3BVsS I9ѝᾇq*3 *'x$킢]c:=qdӤ6Um7+ԝwo"r[qg`fěe7E=sŪB?Ws&tD ]=EGTIݬB$d0'~虈`- 1BA]@v0!7ϼywAS;Nl+Yȑ(Wi'Ib@|:F Yr.Pj%y9)>:ݟyf&0.Aչ v!g@bsŷJR@܆\Pի?.!RĨ H[^vDCuUC}*L,:Hu"wӗߟЖcZ'D6ߕv /Fհd#'k&C kQ3dV]A}9T$n\[vyWaRcP #tHqZȝW-_N;~A_6vNq }ې YIL%> N!EӒף! }e50`%6u)mQc"FOtil( E>B3=1ݱCgF]`DbzMw FLtEE%ec`;CaST 0  Aty]Q ~|۪'HB7x\S6i{EsߔG?W8bMƩ Ց&Du&]xyhԐ*6GԖVuCʂmM1[-Bty.+4%۽ZG_"HX2_:~ mD>ૂJ%b7.챐-ER(q_9jzPOg19$e`z٢jPqc=l`Jx #&A/bccb1Yo`Z$)5~+F6(DG"ZIB5@R_mSo"r h^3&<*XN@m[3o}2 ,YXy@u݃SCtn F!49oqbsa hw&* NBf ;y]I~ pֱLV# A4=K`j yqn(_aW I7 j ]%9q7s*7=_ JsAO;'Rd_5`:}⎥G1=G/!52~ F?E >m6VDM26`Ry) k:z$ `eC0=qI[.]ݚt-輙d -47{DwPQ%Ptx:++,@]|BPVSECdRzF!<:m'!] 0{IqK B6B2_u9dc)$#_IW4/|.mvL:b3!ɪ1ij,I 0Y(_ zF ^x ZB4CA]Joŭx1ݑ{}3&ݿiV |+l? ᗈH oxC4i,@LE\l)]APD lk_ u27 f&MfPB{~Mt^nS#(%P dPsۡv/7o,3QJ,t|3^\HFHE4}]TwjO  +#̂pMM~- 6Wρ 6tj$K !i5>Z6`,msA>ʻvr*UtxF q70}zx{b=\O@_ p 58IT,:n5T:ԣ9 ?S(pj&/"]!VLUkf=}%N#Sn98񉊜siN.޶SUupoQҥ)2ѵӹM( GWf'GLm'UdkAۡ6D!3:i 'O' W@>2Ynޮ){Q*"qHHj)(u*CLTR7NR\bTQT'p|$+/޿+AwО<%gpɹgÿVo#s;нgWbşD0ƚGq6Ysղ_@R+VєA7r3 /?D3D/x[}ʽQAz3isk%JjO4/- lE,Věܦ7XbJn"3VRLm%ʃ Z2,Tqxa@2J/_1M/SPԖ cc > g\Y]G31'KYM8ͪ^ޭy:4dU FEGN+7iA@hSٰd)7%sM1}R0N W(@&F3Ř]HX]̬J퉡1G:JOi+[AP˷oάe_p&: Sq'vShHM[d )7B*PMFT[f@nK(4]Kx~pD,&TLt}{㒀6^"TqtsPTWi+ru@hIi~yԐ)"O$*d)hC!9gC9Tt3>UMoN,KbmE#tֻwE(J0^P З]%ծ1vաpJnt8$f(cap2;whY GHv)1vEu8B0Qx5^Ԡiˢ?D>9)Gg-L 3xw W V+dPӨi)j>rwPq'rZO@3Qפ^ *UTd4+j4mXBM"Kdtqo%nO)s/:+cGIT9xW 0d,@8SIK9?30*#>H|cS!TPCL_b,^qp=WPy7D4ˍ$lbuVEG~Ǚ /ܦ߲TNs5de?u+sV PgjPu. dG_gq.F ඲PQ?Wěn u\_O #Fݶ^h$F9 ;? Vp&NBaKSd{J A7 MfɈ^J h֐-gg}R }h^55$cD]fCy,x@_{8 xK,: \񻀜^LR尓ࣟ.&eB>G'd'26H@if-d)=V3p}Lșgp0爟|(MMmd[ohMVB I~3&K֠W.@ {҈RmF_*?1A*''ՠ]T^vPʞlȣ;lA-?Iě躂:@ ୃI۟zJʎؖJ2~jޓhRC27EM&m$4ԈʔՆY$6|5$S: 15 K5j5STyo~vo୽*/{˩;`4<{TH6!Mj}oƎ 54rl ǻԙxE+(:u 3(}  DN"Y'k l;1ɛ%L ŖKӥb [հ*Lfףct0;Oh3ޑ@ "HXuT Zӽ# LNӋfJ>}%8(RdeQS $nOpDtˋ: fh`4x뼒uEKکR97HF:l"Ԑ %U4YA&6J|8L"W%>1~A078޽o%IHzR7 CS[ }bguQq5ndwtdk*[EĴ98=`bb?ylJ29m fAuy"$o dFkzI()A;g?BJIѷ᫣&۷DYC}%6ʰ佯_ F#)n]r^<ڇSorboD`hK yX@_FhjӕJqD4DI@tF"jH"JQ6RձC^v [ N=ovbt{BN`E;IQE,us/y66EaB~~n7XY T#EHTt5#NhBtK@PZ>Ou0iwGp*?YY^DXow1?QLm⽞*"5}}E("UkY)VKۃ RiD҉"WQjQ;?&b/Hh l [dÛ8qϕ3VAmg}o!9 EYS1R Be t pQ /\ZojUH!$d/!DʞŨV.:;R.@9qGPqYQ4F[+6g %6Flf3QXZ]L\ՔĿG?6õ0Y"( Q"kES{>rPpS*m%8H6JD(ڋJb%^J?OmTqO/@Q+ҟ ^}ksԑ7m)$g"a_uD Y#n;F#"bg/]xY|G q%ږVᔄN]z{\{'i# )}i"W/|_qP$ FЛ, q!KFǼaass"]05Qa|cHp`"yi熍kcW#@QN'B" B1~'}G,5ԧ/_Z]K$qMkP<Ƌ`@*.[n1wH&|,(.4;7${vƵqrJ37q(aP&O36YY˟шҳ9SXsN Q].^sa@XͩN+s>ףD H6!?kg)7YՓCT.8ȤiON` CwBc0~׷Fj U|!?BH< ޸(l3P 68zzpA[VC P 04~ Nvo%E?::zLtp ެ p{ }nq^kq7{"Su̢Dfp@UCz ~:GEZ [#?6vDrȱOiiqZ>^@_K&5l:0x[NlBp ]66^~m=8#AQ; }*8 `qh5oZ֭mӧh\'c|[%KAﳪ,W3*ϕb|@a&~𸟡m8D iU]g K<S b(mdQZV C} i A{}E{=h$}EN>O X\g1SW"X @pPro0E[m|o,C('3g#S/y4eF $FnYtA!e`(AQryEVguޓ{UZ퇍U_$QAnd1]&|r<<X?J.ᑬ*E|/ '֯R#a+Cb[M%-pT?YaqR@Jo8ND;TE}'lQGPZSp%lzpُGaqMBׄFfϣn] jӏ-?M2fkTҀè 2rܘN@Mlޠw|`@]|B( ]}esi,3g`DŒ`x%DOX¿@-WG0C5SD$Mܩ \ O-eCv;NKl^W׫& Lj}OhQ_ d[~^ER\:*qƜ }OeMy2$5:?ߴ9Hur}S?XoYk S.DPfi\i쇧Wneo0yZܛI6Ibbxq!L#I-! ,z+qy`BT\D07 HzI%RrK0%tuuI 54nĦEDG)U91@+=(G;&ثK 'j:iF_HΣ׌-iɓUdLOAan& vO,uXrES:pXHGm)8iO/H&/Klv,;Z/ `TB9AyDyhAs@ B>Wy~rlqk` ^/G]4X2NKG۔Rh6Fj#hQlɭ?*K{nC<1_B;PjeL8i+6{Bt9Iaדay]eW].h8ΞlerІ:I*B';AM9}&A 1x N8(+nipAh[6Y.A_{#R*ԷX@tp?K]ٵ0N~g( p_PC/! HDjr(!ZYe:mL܇v0GVgMo%4ɧ bLS 8FIt>ңr3 xɄEޚI//IB.Nz)J7 Z|']|.(os{c 3 B~;T$̀%CFC(R4v..𿟭i0E17%@P 1-''wl$7 &J(7M*Q HvNJ嶷U{ލ6#08ۼF5|`,U]}+Js][KķSϽ%dS{Q,='E{KyA0?bZ-fe_I-[hZMB k;;Lt#BCG@AioK\WcyT!L$/t9e.UxIz8RI GlFyH%9 )^ [mqdLQVw1ӜߪM&vENLjH\1c3OD&ѓ$6'gɊ̛ @ ]I5gg]|惞9$8 O;$7rkQXkS+rpCN[-G?'.vi%|,? _{ FiKw :}7GZdMDWn7CA#|aAMN! G\'QE/{ʐc`/ZƄE@NebT7Nn&=Jp`^ouh..;dU}  @qЧCn5< k2w,&?DMyBUZ093 4NiNf'Օȿ!J'Kh'h& Z/i(jx?EѰ#wL@ 6c3A&zdcGGG#3k5SrJcpIzZ,Ft$/ݐ(u"`Lţd#;X?i5$uߒ[4Prz(GAl$O#?Ԥ[8I,U9 {63@&D港h+C&!^ DWc5@`EU15X-vzr )g7r.8I6\$/=("h0'}{wC[oQNYRN& {AhxQ=?E> -n :#N1JO=vod ӶMf"k]lt ᔎu8n2$W E(Qbk_K)rNde D)ߟ8]͒^} 5ٽJK)ov? 1X^!+.n}qC,OBa.7P_$:!b94D{ZLh]$c|{@s3_HJJ0-jݮ쯲TdO=- @b3H>tԻO9*gJlOm(^,fri >\CrAFS+[ʕjFfZ{z$a"^V䦗 uޭa [Dzԣex*؆ `54CU# !}Rն S.j#eЏY+q"N( ̜5_/ZcO6@]tnSMǧQd*nÐ(0 /"qXɯ`9?ƫ{ښQ+s!jvEȌ̷E܃WzxvAX Af耷Jm,&?H@&Xu!BeHJTNjP.p(s πT#%gu?(3}e-btZl~۹,!a55[$ʞQ{\J#󆶪eCW@#`TS  L04kB Z h9'KP`RuޯozS7 8˨Df>h9Hee5ʵ;>JM25*S0\DHĶFY/KQI0o ycȏ|ttv o6ZՂ;fCPjӄقa W+W06 /,)b /uŴ?lsHJThomc6$]6 !K_h-sVy/m-sZZ5eġ ?uB^?Ґu30db^uLI$#{cd7pyB-<ݖbA)dUǢ۷$SXBA+?RȪδ~%qs;*vєɺ'2Ez@gaYlؕ#' T :"?$՗ I IQʾ(_SغeFLo(d贫rb7+-$'080L6RY So\7 gtyf_/w$ց$l,PpQ};PC6SUGQ~e[JVdII |$9Hoωb2ɦ,K; s̬l+Tu |P&oڤUmLV̓A_$lݵ?,~!$c3M=*nu4Q2Vk^䡐 Q=~HIr+~xxq[r*!A` t?g Xl1ILg /UnΥBvHH=hL)iݧI,f_<ȶ];wn;$o& ݦ 6'Po#J.ǙXRboݕ=I<#-{hdڷ_з8(FetN:tXi[@'T=Z5wA&SzCcC_K\^ $K}YSG9HD(l}̮M:Ӥ˪%jn壓 kHS]u,?YzW~.YќpsK?Y;0;*UeZcSID2p { *ر33_0;vŒCXq*Y+̀yVM!>#,ƫ䆋MsSlRV$t\qgNCzc][t'@?bB߯QgT %bm m,^>(yW2q|Ԟy'8V3*"3%b#J9ŞJִTYώ>O>V#hmZT½Pe;{Xѷ}&~fUkCRPk'D!A//bc|$}{$?o@RԼ_ u 9ql:RAK%y'3W^wҢw1HH﯉=ZDwY=(cU5`adXZD Uo72\eP /9߭Rl&\11x_ t0;t ]TKXBi"hq6 _Y)V9$c%mf-"q`Usrh8v¹3ˏ6O\ЂGZ̠U_mo1p.$#>mЭ r6+RPuLڴAΓVlSH2V ɞvP-"qr,RSSI W8Ra?i*Ӛ׈n*-X {Ƶ,a i+Oae[ۜO1)A@j rIzr IJ`#!Y5NSZ;b$`i֋2>#Y6Yj;G1P+s2z6MO}MNdfo2ϕzx<1%%=9p"($+dnNW-$OsՌ̨GL[$%.fS04r@Z ^R!M ]ÊKl~;1CagL }d7Jo,IeS\n}T}gjG\A9;r HXe5`-h6=5fW~sO~xU%1GIHMՀF[$`bMR.ִsޠrҿ6fcn&%4:F;:J4ҵ)mqfEdwIRWaٝ`d/@VU~wAo"t;JHbeU,{M ^+AsCL h`,ƛ/㒒S@)I;$'sE }n?}ràr? ?gw,rg̜5[Y?͘!قc )w?]87T÷joN_uƞ&K~n|K{u*C~z+,I>űӈv$3}bq}z;\nTjO-? yd2[-xg< rR;!ap$n⏕H *ΈIk`%[% L>бBw*<%Qn +<]aT8քc U e筴XzXEf\} wfmb+4\BJpޘGBkvD<Hj Hoh,KUlCZH'w# ~J68whIubyƫhnF Vȿv `73$[pjJt)v4p9x܈QB|3}ta$*0骬?,VJD2;~5gghH ש $BCBkʢN9?*X\]Bˇ`95VڳKXkamg0pHjMnAQK<(^T`t;m:/;AHCXӅ%h+فcVi~uvFyEc楣 [/EGuj`$_@ QH;_xX3l`WQ~n+Qb&r@@6c4&%K=g !pJ #!L~C:V#!PM2K2{׏?cxH>eTSfs&$AZvF(4Fg[ n!!Hfja_c@$CH~8п5>ԗ*DGPg^-i4w!uw֯\Œ|wI/fXQkK4wFM4lxWp&Mq0ee`(Ͱ쌩UQC ,{oHSݏZ3A,yCɏҟق\ci?b2&\/[`rleP?LFB<?H@U yUݨ,Aj8KHQBH9hnHji. IP2cۯ.2&„!$c=k倫+lykR|b{qP^4R݂9Qv-=/NS{4Y>_e& q<|ZI9`|Ř0uɭ`1oa-X v-I0ݓ1 e&41 ub ׉S2mXJo8PO$ yC/99Y#+JH䇪b!y[ɴUǜyHfm'oBjn8]FeNu5MgY2sʼ3Ny7/ٟweCd0 ^"K/ᝩ&]끯֊{T͐zyCa(‚P \]Ft&uG~"ز xٍO=T*滌 ! !9luwyHUѝ?Ag6O-<^ۍa΂,ٲIp N TtKp$jHІ0{Ir췻+A ,ޜhm'u |$r,@,P ohzB@ǃ#p%FbyYYbs>gO^}N-)[}&\Փ"Kݛ6{P?Ǥ F]v=@U Rj*QcWiZcgIB(0F7~F;G>^HQ e^C騂dI&тBPA;q,.*!dTp_9Ao,9bh!| 96S2tyNWV $6@RT)  k:KEix5Z淣[7h*s"M(0[*~>6\׷I_U(d؃#g CH$w  $Kiy6G)_"cv*怷ˡz1䩾r2QAY{Tq0##h8dȏӦ*n7:r*Z$%s'T; U`y%F{kĪ\5: 6{2q7D$7b֐bl.ܝ%̜ڄc"Eeg4FNܺ-q<}ܝ9{x8ۙ*['7oom1V`HZ*GV+ܼ\BV#o)W w.'4lռr yR foҋ QۋA [U\^'#BYSALN ?)&'. :8Ռԥ>"' PE\skbZS%'s@#}ɂ=vJ|C-ptt``knE69?1R:Ps(-e eZ ~ {pܼdX#_Kw9}4#+|*pSvkȬm[h+3~c2|yUHaUEpEEpNy,ye<#! &B /!#idJ@B8% '~Rpd5Jþz$KAgzftv');?Uv ^NЀ?XY 1=nU]PhIJi9]+ǡ$yK'Tn/0noqWNB.MF&m m]ϋ ?%G+=-_FYG $g<:B? (0i &.~-EDkP&hcf2 VN8UuPmoSB/FBjKߠz5*GMI 0Y_$Rھt$-װϿS1 'yCjJPc| )Shq4߈<-EX/\j@-}7(.i?!_$%+ね^xEyKu/%#H. 6R Дr}"Ȱ)+͍C֣ ۠ǟ>2id"nZO,$;quܳ-GRxG*&?EeN4SI&< \xc9uI()7"5YMY6 {:{Y $=dO5 Q!y:wi_wJ\򔪧lQ{Jx5ȍ)j5]`dVPHb^fэ a_eMc敹!i1O&B#^rJ 8d=J.%^9v$ɔR,yHfQ#Jr WvR[dQ/H"xӭY^dLn~@of[zN8frZ*l hNۃtcOØ/, Yj"a$*Z%`SkP:fm:S9{m%{\-ՂΠiZx ڳl|+7ƛSY/;#zX0WɘpQ7FB<&P9Qph&Wȕ'^!@J$HE iѐ)\Fn/'@ZÕ;Fw*IùcnC1k#W d3:i(mzęU$(X׮hb5RHѕZT r*#O1h80tj^Pvûb@4 '泲ooLh46OmL+kJ-"لc#3B;V-a.tBX_|#<)"oTE)t?Lɘp_7FB<ƗH:潐*\LIa&$n@Bg$I4ǁ!Q@٣+'@jN`Bg]ו:ybE= )9t]n!խbd_Eo,6 B~"9Mi(Ff 7>S A>:YLv;Fr g@}q_aϻRnò#=,2p<6>$wj>* Q/UH]H]qz" 3YܐJ|Gco^$ Ȑtw^q  92țiq6R:k&ٕN>k)-@ #i+Ǥ_'* rLXO`X0\sܕ2*#XiPDf:-^mۂ$MH\u=2o⸘;PALR(Ӫ$C)qݷ4|pG(;BC D@lVVX@pKƄ$J=2$*;$`& Sf.tRʋY g@V,)˟!kQr[Dn6r  mi[lXHV:" ɬwZĺw'Oi$kt(#(Jvik_߂*ҫ^[qvcTȐ˸ $`N@6RF!^J+0"L,n1Q%8F;%q$' T/`lRG\\LH?ۍ% e5$#!Չ1b$Sh$|/HCI]7%r!2&{Y)ԞŢ M^-Mqǽ UqwuwS>4䓠9+2d2PB]C"ͧO8}~uzǏؿWwqɌS'ljpN12pNǯ7mujHzrdnȩ\E8Ӗs|ksgS\èGaGcٷ{:y?sSN/ K% h՚a8-|McYtI JBC*Ws%^ȋ Gs#2='|par$[R5G 7v=poڙMbm_"\})D!d+⣓}ԧ מ" 6n׍@'Y, 0 z[+W©P"w@p[N ҾbL/FB<\bE"VV=`u8%FJ!}'co/N_R4S}$e6b&H&ף/564m~H~:Tc"FZ#+'87gL j/n[(򖼢݄;a_>_K r>D-5JU 5 B  =)i.7R$l5z$̀[0= } aDH![ZYNXXǚ M6y apc}μV0R>/"+h*\p{5 tHA7΁ BMkQqyhHRqo#ﱦY'x7/'a8FxFx89S02Z~PCN@F6ޔ2$Q:x1J=XJ__Ou`:oLv 5Չg-sQB"&r h lа )|C"c+ږ8H}k15V ϫ]b|Vτ$Riq9!v@ "eVc[gPoEz&|ln\E!S)W (\Y!Pj M硓2\/~oϜ.JPX᧏W# Tr/N,^NO ?֏f T EUm9 { z0s[Id($vT=Pd՛`HmI dL3)Y\89-EJf䤬MVRq[BV~vbNܿ |^acC걊ep_FWԳ wGwup}j5nODyߦƏuO&aMx#6:D @}(u+aGUSKy0Y@m·/U3r^qtJJegL? 7#=,|9cQLvno%cn d' {$T/EBJw<>*S+Rx_'L4M;:eG2۷%&_>x*FDEW9{djx\*c, (%{ħt)^܃b-'Bd &(^ݑ )6ېJJInGBW.)H*Xfx-KfՄTAG4[/VNN4\2Ck8i2>BX\kx)=*^)%QF_ ,mlG!ɣu©+D\G- b'Fj&͑z X}=߂_L+}04u7\A׀_ ~ȈG72m;~-xͿ7Euu !恿uq! :4\QD2 H>̮[+=};J0iB.xM' K'SCCMScA9/K֭jS͙ЁICӞ! \Af-aU`,̙F*Rd?ٱ1DҘv^w+13Ie:x]LrrG 79@~ThR|?'z+LWG&_ OPh_G$>6 9c \|EbaHD2~(DL~a?TqpKnl`otSxa 1!mC0%6mjJ@yj©jA䜐vL=6pg`qfxKcd駸e/ uK@ !}'CYFCfU_RƴU_9ͿG2v ,'P A#hF_{sD :"͟ɹEe.lRhk$tҖ̨zIa<#lؼJ13? `F^ApE&[ k$/J1=/<=L p3xNztSK Bj!jH, ʇK+%RYnWϭ-{4$( #m^ o bf:GX2RmNGT֡[| lG3YƂt&/A`}_ĕNxc~<"HyGB,)Q7~ Jq~3y;Kc!c/tec4)I&__ok$ca"]xS\\xʉ^QLey6z<H{}o8v#%gk\3"B~|wFA2PGԏQ4YjM%߯ `xdL"%$_fJH?vtOH%As(DǼ e&>WZ5hlZ. Z Bv=Yl˥rjEįT;{S,όh[rwp FN}jP|jᇿ|G!tpRnkǽeIGt"[M6aEc;B]Wza>Yy$ˢIXYFF;E}702&b+ \~}>>ogEJO=KZ3r51[%I }ӪFrD1+uHHV.z0kRo >|>9"{y^N"_4\bJ2ui4]>"e{YV]:iу$?:"U6O^+ s>[LBuTjMڠ6E4I"w^Sl3GH P,^_vĎ;WԖ2,}N+,̈5^By927 0v[B6-U 31@酝'u iuGBlYݣh*#؋?|}[PȪcaNQ15m"̜7A#t?!:5 1O rN~UBrjZZJۻfB> xoyȕ iTJKrFM[Kw/i*9[;Op֥gk4kW! YNRC/nA5:Iյ&SV0jXOs.0sfNX^uGB<#kxD Ypœ4gJYb`\ZgV(JB"{5)T-JJąl$C6ZʛQʨtp8i1RËeitz҈ɻ˿L!3^1wTNJ}ij$Ds8XY|unY7ضS[vL\ra2rtU@5 W@+C9`vڢ >bC#A1{U5x^i~RH_Rwд^gX68,0Y fyk pnA~ڀ,621;KOmc2E 6u}f6bBpDhEȠM=1yܙ h`XwV-3+Y i.\v/2)ƒd^-dQ,WBq/2x hyYHŲf2oRڗҍ2NҜ{PnkEޜp\ڲ5&a"zw/B{+N 9Mh4kY֒ժನ|u(=>юXi0wJerOnC(-Ѓ ?! Ek/61Ӽ4CWݻ}U5\,:COD Wew^yy!p!)! ɐo_jt6̧j|CwgDϾ hhOBD.ac}di%v!\`U碶ӢؤmW)>oTkpri|Fvih(N0.MX1ɶ@.MÐoƷ|?goƵz'ID쩯o[3mpvnخ/%IK ^0˥',^ ˘ɝ==hun|9qIT牊"2H{dxf: R9h|o*?!c k-.UCb2~^Nֱ!XwN}Hz|\Zb̓wpzirJyC_#*hFS ܤog'|0L2*u<# 6p&9ndcbk_hJVcZK>7R&Ón#@ȴ W)bʍIo@E2!O9oְEWe?'+ F(i!'H0g|V_.C 3^ˢyx$ ԥ-;Oarۑ^?yHbo0BZhQڽ bƮ ѣ`f#7\z8-J!>- Kgj zsT ʦqm/ gq,d0(_|VF˻JFՇ<ީWDwMLB>TtVp!IV6_uQwZHOR#bb*!HRДՊ*_ HzG-<2 T 4 e)0 Us?Ƙ`s>{9 "ٟ1uJjzؖNC p/Xua B @Z' $(I3VKt+,KA&YBpS1#80~^bLδ[a7;*j(·/1Dpuv\b]7#@WbF~x0O|bk;.0է*{TZw~CTkJq^bh+$V tMqԐUE~X`TB2.JelS8[&)МCJష0kT|ľzvge^)`r&B-Q ]}Lg9Ȥ+9h q 6KFz 牧+YɌ׍‚X'~͜RurIв9nS\ p3TDZ2D䐹5`Qv@ #o|·ZLZ2Ek=#D35)R䠌ˀEnů9D }К^ԃM"j~}vCVඇݕdȲ"d&>:rU8k+APM# A 4+z Hb/W}\\[Cx_'W{52b-8 Ayq"@:!OLÐ& dn_"}dsTKR2@z%v~5#L/mǽ1?lW;q;xT3&[PtIF1y G}&Ŝu`лR ߭ad._xGQo_=Ѝ{rv3.VY5XUeJx'@,tC[.(ĆL+$J]SZ$}xG=C2єNz?CZş|醡Cn9Q- AqHx^ӜdjhTiK oJ&J3ypt12Au`+Q^@~Tq[ P&WLe2.x:wiVxw: VȧJӝh"؄u=&cOKTaR$9fIw8u 1Ofp3"F8DJu<99",$IN=x H@t^ƳMzLI$Uf4eb慚O15A8<>S"ԄUM<9CfFՁA?H+>ȯSp@{c:tn55j3`iu2`cjx@{MK k[NJKZX{*Pz1*dac2dVܷks7DyPAկl e*Z3O,3DŽј2&l脚RmrP 4ꄸz:0S6i!-WH'Kdm o7Va/zAyH?.JYCI+H5Ee=Kڕ]eL:b 1i~h:0Y)_izD!]̵'+eV4gą|kF|WrA~6\q鍦R)C?BڮB@sB9jw%eIj2[HOx`RrX 7ح̄1Ě}d 4v̘ܞrDTq ğ>n'(RϮ(.{vyz [s)iͫ$$"j@ăx#nyB22F07 I6О8$pV7`;xVEd<+ 9aB2c ?_5J^;8f9$a֭.!~Q7.̟6ػ( 3O*PS#2#,vg,=W7 @*,ыMC[Z!͝?rGoz\/ZAr&C-.b՜ۛ;'6͛4j1S7ՄƯk~E0F@m5=Ҟ,/ "b]&ގ83J[~JCܛ/b I~w$ WR/oEm?eVԅ#I/{-dA'Xu} VӋL2.W]coY"*Q^m9sT0%RA&" +b-R_-˥) _S4% )|ƅxyfۤvUY츊jZk:yx"ҥ>[Y9m1ݼZ\ s˙Gb#ޭJuK3ܾ}&A0w6TkVA2MA7?$?g󜏰V0$;@~WaH2}^$9u >LEAD^~e"drEZ7` o !-ï̄+xo>t,.B9U !]x.Xdni6mz!?~~}]ʁܾ"ӌ xk2BƧff{f'ExwlL%?2Ωyѷ_?^hl֙ {qOdʛgK{h(хN2)ɹ Tg1S̍ju;k񓑌6zX Y emJdBjm=4֙Wڦ } ΂u+PFner4!:B:q {ٺ ܫU*E49]ҭ| eL:p.]r҉ӻVɥa5jܣ*i]Jۭķm$~9b4֎L:.A^G!"93;3%K u/Ԩ-!IWm#Ʉ2XEՌ{:u;C'&9Qح.7Ig !OkQR F'MC%O?̲4e|U.:Go_vO_ВT7,vБ5@JzNCB/(yo!9[]CөܺkB"j*=Zήo];4;IkˇC)bO3Q*%--޵U]CGݾc&i̫V}5i F'MC6ɜDz V~NLP:\}%q(p%!;K\E/[ NO=6*#3d PEᲢC2@L}q.| jBEKm}ER@, 8A Bzn5i\Y%v"Hp/PM(3 Rb;t={P>s2[F']`W?Ω !SPxՍ?F0:<E@Z?T:70{6蟷rK캚7% .IkN\wyz=uӶC|\K;fӁ/F'Ą |8{ [seR~#'N6}ԉt\$+$'`!) `$jStcaݦ ?i v0c ̜Գɿ@pU*MIWnي5ͷc.h3QҿsF$=TgWf v~׏ϯۣ;K+'@ޡ2to[;udrER|d5B;YoFCx䪙vע;tVvy4u͠hG/}$sڼ(?G٭>ѥ٧s6^ϫ+]p>+Ugt',ܽJnb? u1t7e+PB|mƴu>;{ *ZN6^9|Dއ_wl@rD*Pf<$ B:|@:939tmP8|, Iw鍳']}`(gѿ+'@<}o y Ȝ`Q#t^ye0"%6nHΫB!k5sAON 9ri)I@ lZH$~yq6)-E78+g/3 5^u*a^Dhu_]9cIf.ueT6|hiL:Á0lQ;vOHxf79oAGNwNAڸ2[ )ͯ2Y.?%ݳzש.!jK߹5U_ܽ}veWEZOHugE=csglZr܋v,پ|~; QqwcFv.kfs7\}wԟ?@l_6k <ްԊ^o+;,$HugE=c|n]9{/Z9aƶ\H]e+Ќ}`ztֶA5Or nѡk]5c+cH[$} )Va-׫,Xo*[7+jNo`-`CEs/Iw[9 bsdb?9 2S6 Xcl@7>5n m!GޯP!\`7Fwyұ,ZoqHˀAc+}OeSօ Xts+kLuj|ú06אnn\`~O"?ٲ2r7_n_!5:_!5:_!5:=1ߜpݲM=d[>7 lc _Es'T lc s27Ը X?ސNع}Ð]ec )s[B_G}UO!dK<׮Pl\>rWAi d[/O(_(ʎ]+WH~r~Kq]ŵLCѾ>5ːt64 3rxT*OF{V%s9~)|f<ւgt Yχ\׀h~Y&`{P ňZ69/ 0V߬[e&2E7k 닼-YX{Т5KTҢ5Azi\u\G@fo߽xGO=J֔%|ǠR%o~^.kGQZ&<3s2fKC OPZ&O* VA2u_fGB EGkbme 3ғl%gLqwZ3Jlǯ]Ds̪Wu?}fތҢ51U~,^O{ޣRQPY&&f?ʢ#6v QuC,?bSv ʢO)ιkOON|ӧ/b>Ȑ߸‘Ns_2m`an^سnO;|œߍ1 >fwDƱ\kx:w;jߥظ9 UOlO2 >܎*fo.A q tv6ꀼ}훗\;kPb H=Mޑ(eOIԹ[ q@nozM5mRb(:ImT_͕ y ✰YO^nI/yE4o~,>$oCӇֻҐS=|2p7s =}Csm0p+/2xih~e(E[{,Q1Χ NsG! ,\Ѣbr]|=&e d!}+G:.4;i,j=oV=p@*ڤ@? ix}?T@jT@jT@jT@hu|zѰtuR_qo?$oCӃu#/^yn}U.!EZMӼhkcO֝Ď@X`阪^>{ܡ-Jyd; hkԾ O7֝dCX`ri_iCY_oJGxt} F:vJUB#w "TPW}ӱˣZOU(M36g?lG̷|K7Oњ7T^ZD bGAiu{ɝ]AX/u;/Zar|U+bNMR7vS85.Hǎx^)_ՊjUԍߪE#;yEUp¼js~DH逄?qA|:hyE:zKӛ4!?58  _M?2WӀOc\Td \cZo!AƎBFxtx:v¾U+ ̪AQȈOǎ`VjEY9H7 1}~»hcZQ`VmRōbd xVjEY5(HQW_]`7MZ"qӨ#?wvlv 뒡%Ȓ5fΝ]E|uWH eV9L.q.yȿ.hĭ6-Վjlvn풡#B<򟛂ovNtM$/K,bKe]2t| [,?7(fk1dYc.  :lw풱p2}W)vVnF} +rj2W 6dAm_ܻO܈~Fz'9?71aUm{Іf.ϭ mKFs+ *l}1 i0Jlu~KF__5:Nrwlj b.ơ]]\]'^+8;Ň..͉ b.͉ b$?9YV~__b+QDT1K\]\]\/]\_VP8 )9* ><=d%_oX4 `sGP W{ oO_Jo?[_?[ߠ<KӿJiRaGyX_m$ 3oU?Pxu<#?F})4g?|y?!~[S?S=G?%gO=????C_/Y'Wc 7y;3??ٯi'/o?ڏmc_-wO __g~OĿnK_O? G??`?¿|3?`_ڿ^=S??߿o#N>W??3뿧1K?. 8g_rgo?s|s/?~ ٿ`'__?X7?W?Ot[_/_?|W/ow__?/_O?_MO?'o?G_??71?!ޏ99Y{-Z&LUH p=oc4j#Nm@#%chI5't[/p'=m4ׂgY5/Ea&{DN" /Tҗ ߊQhM>(Յy>M_hM(Z] ! M+EQMpќDc&"Uyd"{wr}TۇYS<{Co_<"ݞ֍[o`sbFu@nV*,#~Ո!;1tГ DOg[c̬f9IC$W.ql?Hk ScFE _hno}eՐ[^E$m^t/|R| z /v SDhD :د!rC0YgSơڔU4MN?Z %+¢Si4{R&qd^r.Qȕ&R7EW:Y`QCNV*|lA{H(qKh!ܗ2_, OhmUmF.zA®T9@7OC?h?ټ*ky9syrp9 ੧Կ$uw wRhy J*dk5Rφ{#3R~!S]TNVmvy"bn+r>{zb8oNfm9ӄe>qYz^ҍ"@@ Y&%q1N/+i{p}4K큞}թv2iw ->xar0)?9~:O\Q#~M#\*^*uk߳'hײQSXR%]vX/W/{zU@$5p?;۳=l<7Y_YXP3ikla렒eN+f%Is}N55ʙ$|27X$V +\upV|Vr'hsCB(GV*C( PA`7m|A<v;o]4MvuQ\^P7x| G4J$Z iןBxsv'FR Kw ڛhhDwܓ`y;WH@r|G%ro%>P,[9ljZ,g5]luIzYL(7z\:S-@~{P1`VU\`nsYګIo?z6~\v oDN{E^UN5%ǫ/jn)@@:'Z=ݗe)u^޿qg:ⳬ}2^F+M"p|j~;+=6-w(O>Zi5a-P5 t_ ^&[ >l\0 {gxyܘG\? ^ogO]>o~_ֲ|7U!*5TˮWqAtsRe!/?CūeIK7CgH|֒T雑b,cQkԂo|kWi! ^yJ]}k_Ykt x)8.Eً"d[y)nTF/ߧ'(߾@; p8X驚VApfxd9w/um5_:;IZqmu. \ .d: X֜M1=0rVAy^\VYgezP^$]?AcP(֋1_$GiN1@BzPBf҉wq>Ҳw0wFTj؏MҥS|D*@z} GdAzMвLm>B8GUy̔~@PRw;oӬWj+,C6 e;m}1C|;_i6e,pC!ל4~^(1m)/~u. _ܚ=ZqX%JI8OSpi%T4_Ʒ6ԍ*(XLu O NpŻh_NZwP{LOcsZBŔvA9显~B?7_,wԋ(æWr$k\ZP2iP*D[*?'ЃqQ Wj^?96Xxגf gr]PrLqu?Q3(WwJڱ"+T=X? @buFq5 XOXd9ƌ$6,;~]?ޢ<<388?.{ku~c/ei8v^o p]2<sR;F4OEm:f N"f5*pi(%ΠjNo/|>-$gHfܸ',3v3 -8jEya̝ xQ3,6k&c/ gp ܸVzF3'/4L5#alhfxvv@3^6`wu7Md5IuV_ 鲁(}?iID c$I5˓Kd~ghq hwgbRK\ݣIl.9fM$՗4r8DS!$'@c}۩"偈WU@+woGNd@XOGPZly%E?[(sNXel1S um1dԂ\q@͉͡6ȏ 5Qe}Hh#[ E=SZȅ2arF̈́YfhټЭw{ i٠Į"~R#!d&b@e|dvU)f aST\hLػNJVU:m¿ku>Ź[ z(&Qs7AՈk#0/ Kk6E$9'2*kq,YFzL S$J{Aϧ0)=F& =מS[% e="V6BV 3tWAC]!"v4Nh :lu+^+1 3D.[8ZTÊmݚ,񚌠0ڊ x !.K%ׯik-s4X Pjz/&ĂvlC.&;sy&# x֪ih5de>P>-^ 88<^e/kە[O Rj!Ӄq1 ZX' Z0! ]~ 8칽V2 tGdd[9N"8`+Bw6\2eԻo}JnPߪ+~S\Dlaڦ9t|Xly;"Mќzw6e' G(\*'{I ([5@#.|Ԭ>ߊxk',Z7\si?|}d)S?5š $8߬IViy䗟NH:sakcƌ6/T%-&bAPs)s>w5ZR)3V 2 "@D՜`Z 32 N+rmŚ]#_1*aR, iQ0^U[k˸^ѕ. O[Z{=~gSJ67&1fG\ *6ꙌG*iXJuPL`jB۸~pD1̊:E1u?qOyq@4OCiG3ad ॗ]s¬= ,i?lE q*JJM5N3==Mo9Y95m ァ/e)@ugd^:7N+0vk0[K= 5jl ncYhJO1MɟM⦄jM_=ɵ$\;[rGELcK'wb#/l5'P.Pc^ |.`q!f֚F^sHy/Q1J`qi?aXWc3Ԩ`vNV~I^F&ٜ͡!.{0 j;w&颒Tᡨ&k~t FN8ӂ9JuZ~i1^)SC zT/9ƷzwXWf"F]7aÅxq_*/ JVOU8چkx*ݫ ;߱o~mmcV  `;Q͂1'=޹/njU׷LDl.f~~wnϒEWrߌ·Cw )Lw;{86ڷ C6A? 9ure'_xo˻RǠB?Z2.~fdI`O 05Qw킀ƚ]nҋoiY6;-:ׂ Ѻ e#Bk *Re w|XΣ}^+bH}qc񰟝#W9Q6‰Vg۱w")cŊ^\Y?J&7YtMj(hAI4:B*9a"Rm'pgQ4"/T]#lyP<EQE #iLBOr*7+Wۅ'̑@;L(YgYi_I:~c#. d#;LMY&;VT<Y!EYm3Oup#:n^r`DӸf.,Y+Kԑ*9SI0~-Mj0W0SN[L{qn^I]7Ȓ9 p'Vo[4{KVйiKc,y؟s2v|'&5&@alaW. Cr\> q.@L+ע3ڜ`3.fTHV v4hz>dƫRu0 >;?LQ)xwМڜBB pJʊXI/aċu@W䜙.(7n`z&#ID64q=yӻ*0hmu&1+}P$ Xy  @8ָ>5 G,U@hCf5&"t_cM%Y{;m,{$r e( $sm>< p? *V83 ~PDuhBOraZؘ|5_S6A}%4@Xwy"¯lޫqnŃr.]B#!o9DwCB:>mм+/( Qz9AzFY⒙,gqLC{/l2JzSd?9UN6GO|Ll]}I%;=iT N gDUp4W?vyU9Zi]IbD|*;B ?f::^KЧ̠ 8ۅMLd{isXyrFI\e v.\ajO^DXj]E-}g[ $ x %~jqpXOw!|M O<4MO7<=QO[|,JS.IOtsSJ`8  MǙ\O\3ǟ*͸x: &IIܙq:QvF.Hm ZxCgh?鸑i8fm O-ssSywQI? UJ'rN/LԢԎ?ʮ|@%p;vx!Gʞ-4:xtWɳlKc".o?v"WiՎl]TΧne 0-Q4![F*\߳A.MLIw*fyXn,Y,C&zƷQ7OJ~1Ulߥn 1ma?ڜ);v`&s%cwSZONhk.eNv(s{>ﳻ.=O-u8K6Pc*+C{`<"J|8>_G)"8{Q T )k- oGR0^G% vL xv^I`kjpw`hSKnJ ߕV\ NV}|pc. Z Rȯ>o}mG,^v!S%١$MfW8ğ!_ɶ $P{U'G]l2j6H4@*E. [e=l`rX}0Nz{9*Fjȥ6 [/<4 oʼnhfo&Dž L!PFw^׿ 5:RLAԙwnf:[ ?љBS8=ran j"!n3#(6y 7YIgclU9^;}=LS.߄n)I^E(-h zF&1]?n \!H癪†VPz2V/1f]V",(D4Z #|}FdMPeX,3G*9u9(RdYBy~Kr-cz0jJAJ AψbrX`3ySS > v_c*Ei s?\3PBy"㥵m|i6|!}j6/+CL;׺.yowB1"[qvϔKR8(m];!kg6qqg7r)UGZm^EACf^C9>t 2@0WN%ޯ$7>Spٻ gϬfMH!̶lpFJ璿t 0Tw&вvD]U M|Gdb$̪jQj5Id04Χg`ʾ4rGf}g<1h?VT/ЇAlȼb% < l9!twldg &5L`.|ʍHM9 UA||iP%%eQQ#S[LTEo0ݻM (%D,U !n')y̌_ \K4rNS7=ukT'',C&~Xo,[ D~OŶbiYv?kޞ3?9i {OOQGM3CFNBy7 EkZ/Fy!'B INaS/J"y*$-0 Qms$CY54M1{p(4#g0 !$|| *KO0bAj- KoMRf"@V'X$):+?a-Z vCoSKdu@G7:7aw|\zϸPaT/_2fuҙcP )Z) ڑwU*xKSS{R()|,dg ( lFL(jd_,WY$kiO-M"}Gp#ypGRKbdg -J1Gﲟm2=YKr&\3vA0 Xjlν5*h(] /]tӆD-|v󽨗Lc훣dADGWµwu_ϞoS*{r2eVxyL]n a~"ʧŘU n_v$)} _VO!f|${mv;k;iɾ6[uU/t(P'hx92ZI2-/uZߥ>2 {MzFvװzrX8[sax(:\[Mت%Ux4 2l©1˰e3< J م` x;uƵ+ P17+/nL%6p{O"3<6kkBF+#d!ȄB8 C6z26*i2t*TN6{n wUoGAr`%53KM;_P x95ǻz]=Hz'ݓ\ybM-1J|Ǻݿ_ڛ>`+I,Xh!x$8O.CU3rRZJ2IDϖV&Oq=!`@~L9cv=ntDC*"ZwJ%`ᙈoi9"}Z`o_'`l8t'j-FKo}7*\e:#U7d9<5=lǜF}X{Mϫ*$mi͆_.)F)%gFϲɗ_Πo+]r h)Dү} 692 4k2ax9zooirW Wd!Exj)z *_~> ,?nϽ#(HVq4mF4%)gOY Z d,HE͋THߎJ bdT݁.'.fx&EXHpq&hwZUJnvc#qdclGs) bm\Aow䞠VxLK*:n:y:fV[)U۔b`o~ZҝUS(ӣJ8C͂VtCibUؖo,u&Vea?,3s&Jv6Q]]5ÿW2ұU2ho" eև[\H2#+R?U PsUmD,$]Xώ*:O1~TՠmQ7ꈭ>tT+$-M R!˺~0@uuj\Rsuߥ9R&~25EސRlzFY>su^]qw1[-ڛ 6.z)]֥{Ǟq[›E2vg[¯_&!şew8B`VS]zEJBIs{u; x70T؍VoZUC"b[)@|4)/-)>_wM6g e%"m5=tݺ &*e#)(a39IX!RlBswGBzVR jqRg9wp9$9OU'l}N|k=K#i'0w<ܶ*ۿtQ=E Nrb|m4]F1©7m35p/:1Lٙ?,O펊zq2{-23h SU-r^Dik)\AQ8[%*qހR= s.4tQ-EsiluY Zk1`טytxZO "ԿZ$s18m׋6^,ʐ=#Г`Bb(kkQ9b _pC_]9\MAOl_틵R蛈UΨ eE \x'eX |2s!??_zʭmI}ֳ y)Z!~y~!)*^_p) *sL盆|j9aF#ef-uŁ-[5~C. C8TvL\yd@md=gI d}3*RLTfU _*tt[c~u*W$_i$ kg_4|j9x0g\ ҎukR spbr': Wrh =A#BZRO-m,hO? Oj^' ?1ei'k"Rʻm!^)ටQg] } BX.pkNMa b;eT2l8geѢOP<~7zhI ZFţR-[ /B}7ٱf+_/97Ğގ8lpN4gn7^Ll+`/`?F(H:6[8dfM`[vRӉetbg!Ch B옖 cHnTlP_Qa so %!].H#&JizP(T "z~sobY SNC!_!{8i2K!YBxnu.@˷9Ut+fCP|x͚LtV]c=혛b @HBݱ!.Nl4!dDV/#Vyö2DJf2?k4^9Рm\G8xXN=tl##L^dŊ࿲R/@jtpCNBF%VK1i:р+qF#~ڒ6B9o6>nÌ-KvY|8Z0f.@;i1?B?g'[8k}[L'U_H3( 4Vh4 L|etOb@,z.Er$Mf?Ȫ$+ /~&t ?17WH žjP@7eO-Ѣ*j($¼9M 3R_b$.+:S\|S}1Jecfh8|9asfE@M isH%ؗ4J8.A# ||TS2™gA_Ru+YqRVIOn֓ #lA] fOj)*So8m!,4Nأgk]BUV\'1kks)o=Kc|Ixȋ5v̾c@{_%Dqulg98A IZ逺YyfiWLQU*47%QڔV$ ^gCk7@d풥¯bjӇ'lF%?5eKmRIM~P(8Dيs+9v422>%Px?_s[y7Ye5fVp5bo{j [ Ƶ\>رUםEȉ9T817PJߪt!ޚ Ϝϛ)fzI޷CW0)z"2k x1#C? %p>.-ETfdȉrQ%,con*6(XjQJ\}=<{;1hm}#U7iE]~; B?F& P֑ RiW"Z{Vqkz!R6.z z TNzPf0xs83NFBac^6/')kܹL]Y4$P'룥|z箖L7f!ouC9MV88ocv\b L}He͗AGq*rLkwMjΚfc9RGDyۧ]^op%S313lʤCĚEWg I[VY}؊;q"&k; \8KV`j34[4e(p}+!1z~+Nu&۾Ab'7SZhUf7o›r{Vlku~dž2#M$-FNJvLwj ,yѹ#$B&=1JlD "Ur %*4< g,~/ΥWxk 2Ja"N)OljMЉuݚ.$QKjAvznNP[ %Dja.җcOX!$z$9xuwp~qE;FLu]eP'or;,)IpˠnУ<-LģچD[پ& 2lIT[34'/I4ur]숁:8gj( `a/ͫH+'B|w5NjVƈ᠎pOjW =gk?]M !)C ‰pt*{ص֊^4>w]rD7_u |݋X ؊GpOyׂ]ᵣ'NJfӻ%QY/-f}i{nˠw%X]\aTy $v`UM?6;%7Kc0rHaǻs1GGnAaJSBujBу&4;Ok 4r=fJuaDWA}KW9硳_Br*k&ե̝ P*T~BO+i~aL4b_$6A>XPc(Neݮbδa(5B_ټѻ(@8K@zGʚO9k:lΥp>LxD7;`Io(dtvV6vN8'C2l]?)]gXͽ;Uw7EuF_E4jrzϔ^3לl4?W& l';ykkL! ɹr7L iNkN ULpˁ$:`@x2t2_|,j&;ƕ;@G-"N\sFƮz&X~(QQcbt#*%[h*3P^z=߀1q )@nH6 DS֘҉d_6!wb46q'hXRlU%O{RH "+I,߈̞oXҳKnAoo:c?H \}-MRv,OI,I"#í6rbHgi/cV,p]\ybǑ ; Yf>ȣ>&eH v7fDE,}P9&eO-V澏u5F\́~rw7N457΃ا"熲d@׺A~kK.R'sMF1PR6(g8gkAHWlgM 9i:tn <Lq?b,S{*[![hΏ.lCЁ7]S @Ds\Řp'&mCFK36 x~-X*ԯYMj4 1|"A9h'qήqȌ,cpıdJ'l0pؼ4W(/oVDE^ |@~N؉ G n?, s~.d'{8!]{2dXIE*jy}u@5R?a^UKYSgQw!LxS ^;.m=k fyG! b}x5 $1mBƮ'v'_Um"~ݾMT]eBSq^ &ˀ"cz U @̉Z"f%J@m$}09BfwX828kbRq/m\͒\ N2|@?T2,^o2*y= ҘZPaZJA5E}܀#5N 5β⤼:ь_.zVo u_@0̶ P4!U4s-C"FM wtwЋ: }K'T·8WWU]ÇDҋ CK~ f(Qɹ [ >L)C8 d6}$jPp!N̙@q"`j ttl¯|pZgJ-"|R6ǿ5P1̑wc{LU/_`:@[x:X]RZi*φ&VLq˺US͋rd—chh=Ǔ/j\:l_wDvVAi|}XXs$ux^LL󻥃G)Haͥv0o zX"y܉tofYc*q:>FcI{x蛏 4%m2 @@V\A0xlacGc{87Uh>,b>~Y+K)' i 2݁n\s]ݬ`.HBnYʘY#1|4?&x(,㣭U^Lb$ڻg' ⁷ M(h v< ϮP)eN^2WJ݇=摶}YjGN@~4D-]uC b@9 u ˾"<Fচ'#&9ߍWG?Bj5<2+Ìb =tNyH>yL#!?.?4c':O __pUƬ}=728|&OCnKV¶kKGWbۈYN,|yh@_H= 򦝙0Nu4(ዕ%f}Iv=8Fb@ӓlYĎvz5ܝn]@o8]2DկWjtѦX§ x aMs.*,~oV6-nMq?Ei7vnӢklB~RlHl}]e*u~(m 9׶Ij:{#h`ft?C 0Z" R!g햭K, ."|*(.vXb[ȷ6QA;A*+s9 (`&TrPiD%B~ƚFMa$M2og$,PS W~fض c1cZ1vw Xn6 j>?.܆~ւK0/^o%IA(-(Gdo\h ZĴӑpSzQM04Rѐt0)|`[|/x3FG%ROIY,{Z ,m1_j7`Yy?RMX$%10ƙ)<)Xbzexа02bl&R F1HHJ6_ ?4NP =lQJĚ.(h5h]DDQ,.5Uܭ&QY>{.MXy{ q{F\[!^Ik@dϬo8܏0gNYSLӎ}"g9Q}=0IuYa@{mE2pYWk O7{woh2Rꇨ£ )p9f8Oы5n̘d|/'Nْ2^ Rcm= tpeU:DŽ5Buz2cL Tojp;YsBvRfޗcR+p$3=5dtZ+u;97BAof b[AVetxʜ-#Z=`O,ۼM RPA[ɓS!g)aw*BrN;l8Vh_a8mPoݨ=PV퀄$|w峎u𫀋{ȶt7\9*k`ڥwQ# ^ ,ڡeu$A| w 2=Eѣz=\24?JV"t/)гJk.ݼq#nd`^WKކue*1IȡmHeI޿%3)]=H iV[ѥ/IzA%$2XDŽ)? x\fFo+ϯcǣDTx8꛿BO=[j6E>ROBѷYJXa_\|iegc'Vڿ^8=x {b ՆIwݖ_c\eC&[ڊdPj#NYFݔ_(*g̭g3mygxqFmho.,-QC/Z5 ~Nh+xT/UIcIPڔ|C^d~hh_Xc-̪և@(|̾R`qäQ:JOĒ{c'e/`t2*OOa!2$,w}EMM#>xИD¢N8'Y6 *N.YFa 57 J]^Ro" Dmv; ]lavfUY/(^P-,;ʱ r-z?SqɁ+Uh *ꪱ#8w08A .*?] ærWܥ m;J={ObP=Ƈ+ʘO{aB/Rb3AHaahQ*g)[|/+w$'<" e%ݙSFIWʮ8Uْ"zgjkHC"@{գ FheX^_3/^|/Smj>#< PNW΋T K=R~Ep6v{YFw frJ66AS.B'U .f8*5bOA [w6)A둿Z5,Ht8JkI#6 r|7C筹 ,9ʹ wh;(frA_ǰqM0^AP^zO J,sq̀c' jNc%ׇ$ǹ<+/Qee~5[9>җq`]4B}y5e+y/kPrpx8#L#ŵ4d#Z ޿m/ڢu+ 5-au7[ϣI•!hλG ;5116@Rh*{4gb!\wv4y4ߒJq ?QG\1/JtgzO(SV,4ɚҮh m_Y~S .~$~IZqxy't55+IڑgA$RNvs0sJəYˤ w;`16N[MԢN DDŽ_fP\eFЃ9ry 5+֜@z'1 Wg 9̽O|+ `khT%m;APhjƕHm`IajX{!OH HD@z7VV>OVxTi'ub`} 3żAȣ)5-`ЌΉ_?l 1 ໌^ #̃אŃ~_լpFtҗ($*Sm-QDHBw(;N ~G5gmW"ݡZ>}J%h52NWcELʞDyx[ϡ/w$aLo 8V\K[7ȶ:B 7fUG^^ɋbs9||w]Ǫ^7SB IL>EL{ IyQ%$wj}@br.=Yێ{7/kFƾ\Xy3G-r QCe"KИ9` ġ=_<JLm(2f+/h ^&>%X}KGW~pulE1(7לaH E/m2` V4EX'9Z!3eq/ a}hؤZ5:"P/NN.8z؄Qyne=)}jDO"Lzs/Z_^u).Re0f8iBkΈ%Iu> uf%0ΐ6;7{Ĝ2Of' ?sKAr&gX:zv;RK:0%,{fb^$EK\x tb&NjwȻei&c!W*ҝ$Op`7̗{}+!\tA9ru3ӔcoꐋJF5Tΰ`S*}t"u &\7oit<ΓjmWC_v-bPOO#$[BqAK诞vb QmVvH.9ڙT#sin& sd zuEY={ 0q byJvF h54+SSXY'W3:MlNsͽM#?NUӦ ϕtIw}Y.&:B.òߍj"i$ֶ OT\ݧiL}=}jvd#"2-gȷح`[K@nˬq)'w%Az]nc\,R@;3 *C}t|G[sGdr͖֡j?>8ZuIGy0Rӷח/ou?-4Yב6佀w$] A>s/˒:r[s0\I@b(qXv #8e%[ke]^Źr暑 Uo,M[LlABosNDDsY((qM^k҈*%o%~eU?ݫ:L,Ey@CDY7Jnn425( t}x:{b?fX3"‚mښ[4&ُSՖ0޵;߈oJ0(sPVʬ"X[n# rקL!6ĵ){[ճugWeo0ewc[4/};=9l_O5ţ|(d 9U OHhL勽64 ,iX7ŏK OR8PMj5c9kC{?,,u8%iXsT\$먹JSz,22zH 4.)qI8P_1y2U /2$ȱpW\țKq櫷uWU jQ Z"wH:Jr ? G^)$33Y]sOeuAGW"S r*5ϓJq`ΧcU١fֆ,y:XĶ= NȓZ JgD\)U)ˣj Z;ѤFyahPvQ˰}WAՇ~1{Vi;G*}s5XQ礚Y?.!4D6F /.:q_3dyU 7Tp^\U@G;o}3Wp hvĪ_7ns;t`a% {>i]IJ-CT(mRHR $eU_ƒɕg3Nxu5tFmP>n==Jշ:Q;腋\j/kE95, k$C!P?5M..^KERjȰ9)4:ߘ#Ѕ}J5S^[| ~vCKN]39;2*paZ224x5L F} |<BUF CiT|XJS"Uw41luƲۄnc8ߌ:6ZLkܦ}H1ל:G{C7 $ʝ']]&՜k{ EۘTUvuwzX֯t]*7k3vkqQ:(~VYN>n;O)T A[BKƝ&= ffsΊ+Zy ůY}w f5_N_FQl΁ _\>FAB㳢y!P.nRkE)?}>V h+0I!|º= ElTsJK}z۫!@;[fgw=2! <Gf9zAdҋ!ζcX>1@bԧ_2~#A:rY~iD_䘡T{ qEQE~ctm:bDY;N`+1b38nщ_ĮU18)s<ݴ-j IhA?MnZR>_|壈!*HLcxr]14' Yml߫yH 9xhaC]8K1du\Y~N>;wbS9B<<(/[ă٘)/g>N3ڥ֊' >,F~l-'pքCRT B)YxGL;1& a_ϯWbL ݡLM}$"FYbĽwP0n,ĩܱ%PG)w oݟT--dG;U]= /Oծ.8ѧ;`UMfKVCsD +DەeMS[_ yѡnֲMQ5{"g&&3cH6hNRAriœ;?mlr$=/ٵw \&$ 6t`:1% sS:79L^WEŊêM\ Ah%kF`[6$,ѡ(":~cW+o%!r3!}p:՗} Mw=kP~re2A}Xn6Y!p>_}x!hLlRŶ~:~aA£e×& tS$HP- )*%Z1ۭPod,sJ3ӥ8_ȡepRsStށqs-F{/Xb2L^+枖v$>!86&u::C_rȓi6%&KGjږ8$ rE}R7fco*PrvGܗX}# zYDP%f4?Le*f1 km-8μ@UF^7szĠ4{-]t= Js$l`a-7v_w h>_dq#qZm?YD +>W;7~8ϖXoHB*uجm_2B{GL5UGGX5"hv;oCE3^5H-Zw e3LF`"~ո=k6 R€wt"Г2J;]ؼv2J smsIPF%ɠẽ+֨5|(_淏Da;=|{?Rƺnw!B4M YLkw߹@+`d#r |`1jz_Y23P@|o?Pqg_Q]J=A, GIQχGP10 ] \pgO+1;Fqa:Q]eF0k;u=_/.X{ 42iobɷ pH,Rfn߱MZfə}=Oy+}l-H`z[诀C{wN@;$;ڃ&"wlw`"n[qn$ϛ~}FpBLAI'lX;2oj* :M. e֜cY) {s9dt_4q.PR5ZPT8u~eCm:M s ud]$@N#V3 DSdvi [8P=/#43[VEm O*9qU:8\ArNJ*qqD%[垭.kHP׊ͳM&2q|F2V'cPzR*#6gwBѪjf=K7*ROoTlb#֜Ώ^r| ./kaMGTHmSS#c|:`Vum6R` ^a3oe Μkk14:m((nkWo jն[ޛoЋ ;ߚ-/Ŝ<%;2ʒ,6ޅx~phcbV' >eI9h.n|էrt%$Oc8ۅo( Ex&ι1T w\וIkQl09+( I7 D$q_yt;CٵZ~0YI Uh)iN._DubGE<ኮO]Fg}6Y.eYZ7k I#j*IL= b؜o+_)GX0]sI tn]_do*7ʔ0 /4 U*d{7xG:y8m 2 w@vsWw 2{u0k6ȿRKc%fA:Ȓb***/beol>lj0F8Rtex@Z9%4"L$L{.S]&5xN3gSf(GFUj@X%CNs33[9~Jh7.4a@g^,"bw5P47Bn .>@ܽ@y(g?`\.:GcPj%O?4.2L~!Z7] }SI8}O2f_?ӧ<<~~apM{^@݊~RɑUdxջq'Hr/rۦ^R d{s^-'ԫͳ~a "B:/11@FGB]Q~.*Acf@ n8Ɠ.$kK"2"wB'!w҄#tlY_10}r_sWu+|G'ܵR> Y |~ı7f,fYq98Df[j*j~CJk=^A?>a( ŸɒsC;We2JVSV ҃惖R.lJޒHK16\ɟ=Gb,=/z!-o@ %ͯ׺yNΙ#`Q=J%+T ͦaTM~yzbĿbImY53U* "&Wmϫ ʜRGѫEqBu- %yz$ity@^6 ma*Qnj}PdZU𭜰daKG:,E$^נİ*5چLt>,9l쮡p7=@(y HeO P,xL>6fC`ǫ2c>wA|8rkCFh*>²5ux?R.o&e29M\jec!hi͜Wuɚb[qm9 ~H/)pjd'GpeŋXy`@gAUfi}q/jEϰL z_'tc7{,W蝮z AtI";LX4CfvifDlnϲ E7UF f5]ADŽ0H 0i^ayJ>&Q=#rHև#UA}>nj6|[x$LδeZb.?iVRKjЄ Vn4bqPa}J˗Sc ;qL&x(`*mu#k2:5յZyFydFD.0WPu` }>{QR@V1;BoUMnfO-ޯAȋfp?a[D{Ыhvwn.Y *fɅ?}z/d]g`Cv, 9usʕ-[KGZcb ysqoP8Ky&j`jt XϐSx7g;&.(B'| Bu(鋘Wd.Utbth|g\\tg+{(MlV %)U㙻{s_Zɷ 3%TsYθ6qb@_vVa}kwjzg\l6̡݀4{я&8W;^gH S#VIjqgQ0eNFWXAй9(@~rvL$wGC4~R*םr"-~WejG?"QW3a?$Pӽ>63+N=f]z %6=>~rٔN7 IZ>@ s_>+hGQm<=VriUXZMUzc6hjo>)6.0(i:{{N[lU22>*^eW.,Bxg1N?\ޑ,b묹-U#;%Dž6G}Y(gcqBc|W=MBb7pR0bTěD6'r)E'&QFt6+]*[ iAc*;~Իm/IRWiGf5gn=x/ƤbA/Ing_4c%Q;1VxAD (b:Mi{PC?3`RӒ"\U  -UR>R 1+ x]( lXX\ѭK{/7FK.e^Jajk_a9iٺà8Vt斮u7IIWizP- 3'f5>2!"luq$AhC4, Ngzvsb+: ;ɽX_ã,bD]@tҶSua`kdap%NYq( )}c5K@fCdM-v(etl}&<US&U莛(U̓nI23_4ԧ3Mk5ͦ'Z^MSv"[:ri'O,>]pZ.5Mo".w}U֐۞EGpew P-^ ]ɨfiLff[j)fwm&k /&83/7NiEfr$}V\Wj?jοضWtW0W, i[^V8$nhF]owp0|ciJ "[ ⱹne&vFOC0^Lm[A%ڂ +Gnp@iʋsbI]7[mW&DGqS#5G_x- tgjd/Gia =H<"%^;Hc ᛓ,a5gi2!yXʲS^n rT4?ujƕ10@+S=D02 VP}O#a9!zs@nTl'D*Jt{eG43?k!.r:28;8ez9}tLv+;k, xߔma DYUa ܙ&$UҢ0ANUrp4K!ur5v0E ^ĕ8ݫ^_SRzgmNn ^lbtgE|gOd{BѫM7穐pi#1!]E.kcy%6~=nώxt.!o?S7΀퐅puapP9=n`)StX5'G+Xf4q^P~xAZ2 g2]^jFE['cʵ_a]<^IwWO-vfWhr;S vybVzސNwV9Zzp+@0?(Nʾ@muytݟ2'mTe`d)M`POG[tiV8۬)ܚ׾XK>|L%;{S [:7׮ 4Q_s:}ZF `19>wJ Q{ foY1 雡,v mRΫb85߸" ;vb/&t_!ilQy?30e1dG`i2#a͟O^.yȮn PIt Sc_zird|n+@qzrf2!w?YNL( ޭ5Z$dW 񇷤Zs 1H2+c^0 sjbb&787g§ {,yQF{l> %8b?V~'m+0._Z>6Xr䇼IC~{kkuoo\\;?U@,سn%ʕG/2o6bhԤiG99/mx >ݞ/WШ #Lac?"ɉGѣGW7{[[?nu^lԽ.{F앑@kq옺܀#@D“/ؒZevV`~ ^ ܗ'm%wG@O'CFIA.j|;Ь'ûtbӴ UK^Kvs; EN2\D.xk(Uw%i C*ZiRF7qĜ,|r#~?ͼGW [~ʊ2wDeٰ.k,ڤ+BqJ}JyaΦYUκT}IY_;_\!Azf=ioz:p=GN+qXmi ;G1Ys_񻁏DQg[_p>_[o G  I[1=d,C)ӟ%Xy%tlLLJY{t3wu.5"g1>,(n?@+1]7jxnd [c:lpeJP$t ôϖjY:k ^M|m`s/&0 ^;5ԍ Z=H\.|΃>p*Y>iJnbד';]XzW|Nw|X,},BiÛQe|?~E/B&6 0ZW/{%^9C׆_Ϩٝ%e.X !. 樍gڣkdB:oX׳VuO,u э:aSQXN$ ŶPk#ZPI ĔgX` D? ' Qj")NGy%ݳ0d.ۻ wqD8^Lwh]vK &`/g1;7P[ïH\x3FGN@?I n> ޜaI٪y݀'nՓ‘//p4~SGBPPee2nOryuX6@PD5u:&I f! 0^TߺJO2L 2eaQE{r] Q2Y, Hv PB7K*zo@ē|߁)-*İPS5񰫲&;|E L7@J7#8_2{P6[n-9BErA`NYx"IGR7 |K=3ASl /.%uސʺS%<\_ɐx@/?z{ՠf`QEA{?_1>-Vx0f21F9"q֖RO7)n5:c`j{J>o  f8?":ï2;#.o^ǑQOMm3!E,Q[fS_ΎT.ӆoQиi5q_9j# <''VQQr ;ub|'r߻HE0shTQ ^PcRZ9DWl?)xHQ)Zuh2"o4q#oSvjF=٠?\bI6/Mٴ Q8E Y-(FgY L4m^knl LlvX%jqThXrlW #j!kZO'j_3:59lKǥ ywPJ:m)FCё~ϞC6wV3[zyuEdl@hOQK8klbfFĢ,jCB*]A-݋Cx+SI:!L"Yע{NǎtMЍWAsCgjjT[r]}QɮsMDv6PZ90^\+t! ;D|_eIKv6!=?4F]]"i׋y=}*[S`,w3f]7N_k~ajoW63y!TqVHZ+ң\Y6 ! ;7wΧ},&"ӿ8v[),To81ј/l7-PGy $՗kbs#Hk@nWa[U0Ta!5aֆ_鍽(y@TIpG[ugOY{F\{~2eZ kzT丈[̜ۜc!)<鈬TÈpu:Laj:Y4uv4Oi xǺ:\\QkbokclAlAQ>;_ T &fՉv埻{oX2ê[㮍E6,,[Φ00Zsءv/0[c6wS.cWnOYZ3pGg,7/#!%n\֏ ap/\ UMub &E72,p*/U׫'l@%&*Jh2iV30^l- uǗ &5jnwNis'(Dit7lÓh$ݰOB]a!#OL>A`0H הyeh'$a*Hp!}BᏃƋ^v0|b25)"QO_XjDTiRa(dVKܢIPqPXNٹIUX-V$b ׫LJW(eKz`_'Sk%O:3yGS2=lbl34ﰔf^}э˾BW{~Em1&3Sݶ8l<8ە{!mAb.9[TE|%9# E} %c(^+xFs`Fp)}RM\V ?Y"yEpճf:C-UÎůXT2[Xm4_~mn>bOoa5Kt^D,}Rcd'52[ah ' gYŦ:Sx0g$|KΨ|Ã\#Px2ixB覀æ{h~VV1vCގU H0|j>I_@ιmNAB1g45_Tup  l<)Ӛ#1y9)Ix6=黦ɿHufo$\ePMPľҴG;Կ5 @o9}ޝ%s7ˎN9&5Wz"PsApXJ֗tܰ7g*}-.=~Ц d.:UU׍\2ʪޗ[/3#h<0"z^^T~ 6qk="`xR˜\LbR`fayΣF< NTfфg[$ <%\Nu/WS$m4<^FÜed?#k`4Tr_d}fP okivXM@ndh=5B7#PrR R0NPJhC;w-%*k-+t`YP0)KGtǰ#l*K,vQ\MF#́x`o6*Dv+)9qVil$cW?FE:?9XuTLüvF'deQ]) W+3BB٩!k-u)kq} r, nUF$v;0$" f*ÊMn u ;o\kSgcK ,jj'jTG!#Q_a]ړ[2o9G41)pHG-C*ӎSm_Ԙ-D+1 OյidDђM_d9!Z҂F`+ܪ vI"rcU6.ٍ5 q)tœ#KG T זy|zwz?Wo6fjD$ (wQO֛y8>VoBBDS5q~>U2:,jq걦sIwr;27VBjm+[4GTneѬW!+_hMZ#)9&.RJcQMx,ݒ4o*5-(ct\Z%Gx Ո߮]u,P#&OHi4F'@Y-Ӫz롢O2GA+ܜO^8? O%yͳl%8*q1j.i<2Bӫ}cW=)A-㑤pc_X!=nΌKϾ&kaT: \&kq*!X(.,!܎Z$pzom(jNZؽrI@Pa萤dR3,_/o8Uxɦ~b)*z߸&V&:?1ڿ{"E+Y/{?E暎/X(⡀ڷnO3& ꯀISCQ~o{+'wJ'7da+P @k*UD]P|2Fg`*wpWpNoh!Q[o{KJaxXYA~`ڙbCG 3NÖZ 2@S4^>o124Yv|-pҤc[<5ާ {ARhY TUe_cv$ݟzd?8p*D`dAB{%L=M`Xl|mnŮ ]ك:2}N0Q}h`AqM;'yS)Ggh2=+BU8#}m+Swyl+]ŊW9/*LذV#5z%{{č$=%\WP/7L#6،J;W Ǝb@oiЁ1~J=8骪)uie.}YO:8A<gsg64s䤼_WA۳'%Qxx6l LzemK=EO՛ &_a%X[ Mu0=CȭYK" *1q횠GP>kW֩ .1a1{&ZMKqLm<弒#gKoj'%ܨ6o?%Qs`8$l/cۓ5 or#l-ߣkS@> vR&]ϸsLT!5]"Y983>{ >¤ h"<GmHXwf[d >1bSJ&G$;D@Ǐ[L{>+0HPC55Iv'~8k@)6;-Y!:S~-tH䴁h`2[a2 _DɌ5|!A--F& 3wfn[7˻iJ$"I[3+^V=# :rNJNx-B&?f"m?4 _"#լ蚁-:ErPocir@=As~Ae<_sXRk+1..V܏瞟dNS(hsyPِB_͊}9$D;18Iga0?HX~XR`_%{i ˜}x}dd"\+)Z0/؟+AGLS|?ӵJ|H؟:’q}p{P)@CnZj'y+4#3m>CPvK$`= ?e  ,>6 Wdvc28DO;]jk\OyFbU,3R} BNE]k wxT_|3VRBbe]4$78*0%x[O?da{9E:D#."){p?7s7rHn5X[S@~8;HVg@>pH k}\['@;9pƪ`P5۞@<|SY7p&Eڨ9ԻJ+p̺`%*.yqĔ7^̌;(Af!=_UZpqJ:o@+1_kȮCR"AfbZ75DVG8`5ZZ}' /q rި, \"uq1:#H]|l}KP\ ۲d(PtCՕ5(VL @9 @ͺH6>²|\Rӹĥ2i(LujI|+JE.s,T v~*4k6+V—&IwBb0mCƨOϕ(yyшsQ-mVv.z)lar!㣞0"(x{u[o-40Tѝ\Bֵ?V+#\g _~Q'Tǖ-&aBh1OtdRZ^ԢsM?֓kȽQpkm[ K zݽ4ɐu/Au!CnDrw|_R9::2I/xw`LIVm8N5^wmNŹ< ľ6.tdW/,P݂*>x!%4eWl74u4Q@m;ֿߥNEV>~*mj}%TCj B, vs?z{'Cy~6"p#qOCPBf<ȫ#3;tC昃9~Aq#[Ee)Ǩs7Rufyq^=U(bXyKrKо1 ]ў#?QmR&8ƈ7D7ZyiM+BpsX\sR1tOB6Zdgk*kL7V70F ]P!N!} sٺ*D͋kEV>-oL  ht4ͅb(GA+nQք=3 : 4+?/!dRG !. FF5V(L\]P $Wvsy xCj >(.Wr4Iabe%#)5`n4D@/VݿQU1IbZlǍDT b}*k6OX$^BIL:䤭E\#7W #q!K^:{~@OHBiD.{׽p>saQtIѕ1Q:e 9?Pd:o@`{TP7|3Fی NtЛ?PGo|bաR +еi_mUP|T`W1e`z jՋo=ypYQ:v9 -ۮi_EsISauQXɈZGnw & YW;*kHߟkÏ{8k5n+oq,j&ޫ0ٻvߊS]jg &K$:n&亶|A̶/\A@:qr>Y~hѼb")|%fɏD'|5ȃ͍EUoRlQݨṮ$ [GϞb֯&x/LiQ/!Wd@5Voҋ=F Ydg|#]G`Sz6V.%0(seV(\n{ )/fR*kTQjGX,WK^GM/>uC/dD)۔Г.1a|͂ |_p(דDOt) ]qram ?6xx[=7DU#Q8GZ3~9#lim.ќf`xUfN@?J=;0cYB' хK|Lneij]UI\OfRKL8;BTAwZϓDz{\*| 2'Z"ͫ>brh|)en͗%5ۉRt1W;].ZS=[M9%.WҐORHKYl+M:~ $eIx0C KDXoyL^z~g.i KHǔd,pYI-VzO+.<x-7K ^);^:\:#X9O̅/y/vLtGXt ~4IAð>f}R;{'Mjq1 - Ct&Co,HЖs8]u%œzV /'.1(hTqi5Э|I3 oVz|M0)<K X:! G#qYE q1$ ?D'fYvF/QB0nO4)Wj5 ,Psq8Kʵql"eE_{B"L#eF;_w=eeE4[E<Y<ûKkl[`~BCrN#g k=E [v%1*QV%͞,E)$U鳭w>ǘ'ig6iE_3{xp&L#Èh֝rnpGPoqXPA9#` +)ʬUC w;]uPj6RJi yg{ZEŒ@r99Gc}v9g{;To6G>2k&ŏ>PZ>N%3RNX^ = SXw;^+nܙSNl5(qJ}p#qRZBO"9 Q0%g شŎ Ƭ[E 09]aTl!3̂  @cy?D9E#ur;i`N6*˭(C4$ɯ06H_+}!֘^2CZ OkZX Nq ٹ> }[63)0f0D h;aL&%>&0racitsᱵI:,Cg8MǏJk0 :РARQji+(Fژ8{!q"IjObKQU!\!D qpA:ʮ}}W1`y1rJ~3D)ш6D7¨{%74)Rt%@Z\O ,yb^bZG">TMμɆs'Sg/&IbM*Q/D$ߚv6/ _RQzn-0[pHGZi oDQI3of" J`OR#f?$ \͍871Ȉط8M6B%LsB5ܴ?/Xn/ŷj~?i6ZSw0f;/L#,I6Tv4 2X{~;lqs .<09qp~wSe=w-oIw\>n1tlQ%t_лu섪'8b}Ӧ6/kAuF|-"7p @ Pmɟv=+_VGD.\+DKejh1IKh0 %{Nb(eD')a Qi7$(եl[ˊcw=5 ^?woE`~~by3!D\U@Ӛ":.cS`^;B^a,psKg"ﺘfÂLo;(Os7o8?Q2]J'GNM(|vxmV؀qARI_B NXn{L.2u؃)Vmxg*AH T KVyY.;6}rD1?u&Ϥ֘5|v8tQGdOϳ<_`9ahfShZ1`6hT p1oV:ȯFSUdu@whSRErscɿ/ ?#: QA0xغvM81cAOc ?&Ej3\Q.vI8Xzf;0O)Mt٣ Z]sG8Ƶ俯2Dd*ck3wNhhܫO> oJ~p؁{gS/QZIh"`M{Dz7/5|K^ 3tI`jS)[`f=`,_TD(>ǘXe ,lvIG͊шIp(8_zSVڃVP/Z_yƐS]T8bl LK&;GTrRqsDzeAt[`lab9jk|=dS%^ϐ0uYܿ9w݂[dwDkͥiopxdym$qTr<+~UDܳH@~8h%D^E:uՅ AG] _L}` ˆ)P6Wm&-a+ ϑ:,0h:>UGa=M!""Rg5;nq 48!kφ|GJR;J MrR #w>a ǮTqO7Dlϩc/{bNPpVQZ/~M_T~$JJ{[(uqκ#@(Kb7-˫)Wef%QWT [(񗌪"d I@ޣ f3q^|ws.R+/:f<2dJ~W0#?V*}HNKG"<d?p!XKEfCij ٿ/:zPTP fNNM. ZQ͊#Ox8J_c`5G3qʇڿ܉)KCZ&Z|° gA2!XI sŴ)QFaTU>ͽ(~a0<알ol+3v[UyHz ~Ϫ>}(ɭA@P[Z%~]Ib"-Ys@2Sd;w<|U1TG8=-͚w0JމFh0;?CEpI%n w€Nr ,iaUkݱ,l ^8?:0,KV>Rk;7 ׍+PtJmlX|޾ʷx? ^;|% J 7J,@,,b1(~?*Bt2"u̙apM+e>0p]AFqtEcȿo"[+D V+ oT"c6,A8NL괤XuH_2R@ؿmbpGViH_nж_qpG-ކ65lp^Jcsٮep,.(xʼnJ*QW3VTi"B5cu+D.eS|*Iun .ت=dU6N=-A;Ib>3{xح'p8OҔ< ]8I>:f VjgϦ_Tu* 6^W}R<ܨyo/g4ϗ!*gQ J 2;w2>Q\Jüأڒv]AEiE/)Mٜo39Ñ=ul,! ?YY 8s#t L$R#Nʂ{93Izb89cC~ T%ɐon!-GYe<3.|HG8֩Iht&u1Oxp4yyI&x*srʷn~x)ah)-A˨GL}` ¨G֯>U: EVIFtC_FdYl'p2qgt|7f~Hwu1.;/_مEm|-JF$i,v(ÿ2Dd9`&Rx07[{H7%ޥ_Sȩ@āSg*yMa*87qs]֢7TS*koheb@n8 _:֪K~%p9VY* ѡKv\qe5qcߴjV ;3{){u&m)\][0 4̗hR  }ᆑ?@|I7uK@SI=qNb+Q&׈|FfmAURmn|OJM ww~ٵ~^%j;e%RWv*:"K"MshPN0^Fli& fNwض*Kjg(揤Xʸgp,Ny *IE gS'|q]("ı=`z0ŧ/I)yˑbȢwv{p X&kє3}ף~[8:w148 ;p@}Eϕ]h|.,xZW.CO;PJHzTu*LOGF;^2ۤoؤ D+܀o5U4S#DK'D9S?e^JNid5csV2.B>MWH;Qř }XY_gLfOa02G.'nLJFdxj "׭s=6{ko W14&nܛ W 4o P;ϟCޜX.g!,U2s1m>XP fТ~yyj*aZ\G|%׿3ra!:&@p8N{q6R3'n0*aGnU:8K(#V=Owlp:'Ԓk/[?bhE$׺e.wϞ6B(pH UD,9ƚV-ϟX~U1/7 .YD{Se|pѕj5#9[C&ߌWxU׸Dw6C:9f6.VU U |pFKsRY! ^W.isʅ&i-\o'M@w~gk39A˟j1u*~<nqUҖ5 UDP^OQ#'xl 1 SΈ~U-ügb.k}ÝDaMg*If<'䱭ֽs04.L4U AP}>QrUBu8[φ [zM!k Uኳ(Y>^j!+L_]f"C+G1WЉZVXM,YŐ 2K156\Ps,30 X4( φ\D5bB! mp!>~=j pi#581-e0$8K0 /zy5a/r77l^c\{!AZc#S|Crb,NQ3 3Ѭm1YẻS!o%2%̨hLsS~{=*oASux\d*lazK|>cǏTA;p梧~+xK2mSEUg70 V+9at|SQ '`25H$>P~w./50xK: j`K+Gi;M*mgvbgZȫBAeNU{֫}gzm^'soCu -B@h?mhq*z[q Ϗz\>(3e;{2Dc"SꛐY6Z-&5W#+U^3`!`v2B> JDK6#j&W  uXJ.*;:E=^qJM槎)HǨ ffmMIătp՟UB"Y࠽2!M~>i/<:%9/?䥦D#{4eJ4yi jQǓ 5( uOFNlV;Im?$b9}&rfN ]jHF9DyQ|'˾]WyﵳI!՘ˏ1Dz_[ϋo: fpL4jt49A#/d=T+{f~]/=в !,XV|訊ǞMjm2ZWtNiCR|L yOÊsn K>SRBdk^FKPӭ00ћ.I›G&Y[7&oYĢ>X%[߽voVK&JQr+o[lpw`Ym^kObNZ}C¾dgBר.B?*p'hÝ/PfU Dz,*u5ri\Ӛ //h[l"^h{H77 W 5<šyѤ(gWV7$h`;*,H.lA./BObNC)t 0'Akpk4"Eֈ #SC䂯r qT`8Ք#1(T 1]_*Z:xHAA't&8lc91_YWJX)>7,G~ PC_E^jN+cXz8jQz>(HUY$$!L)#@|0n!U3XH牫{$Q @Lʋ&s\>Xk#,%爓?sUSOxhq?v@"ZMhJu},zыZʑ!<4mGJX+xЀ>xڹ?~vگX&;.HB`.9<%Yu zy_Y2f)ډ7 Yj< A?8B;_17Q,,OgϨKEs+A0ࣇ&*<ӄbJjDYb 6b?/a2' '5W-&,0:~?&n6.񖵿DgLL`{%_{`t#fޤWEnn ɽe䏕.*E6)MlJ$,;10&٪=ek)'LynG|Wix D6 v€{o,u7WMr]Lp|>2~A~` a̷O9}pvhI0D[w ޽IrV M7Ʈ0#J/Q`Gj#<\p[|/2#t>a̐\S(m<%'9- 27`)B֑r?]"uOՕc.ڧCT/$y;_lƵεgbgh2yybh]:z#OϰFD,-iG&nk]yAƈ׶LDvM  Y84ֵ weO(镫L Pе{&{~UhqJn4=25 ԢO(~BPhs8}+ zq'OF,,2u]7w؆'E ޼ k D$ O'g*Acl7.GX) tunP߼n,G~{֮GЪW t:Ŕ'MOEg-1Jހ!C{3ex}r -6Fk<O@scVVI4?{M,t+s Bۉ:7VOԳΉlDO%&/S'/@WL ȗ>=E9f4#ejpAPB8nܭYiy ;r g<4&S7t45j~ R`+sYԏi(2KXqɮ z xz^5i]SU:=/*9G$ՌsM WR+:(:) 4v F)GlU!z\zÃ5h)&c}g ]M&xQ/.Wk/ {x *6f5zPe}:)yӅ[vVvXtN]=gT)F܋b Q̟}|7~xv C7_ռin?/vh\vI@J_ Ŏf@=OsA:+ItRL6Qa'72/ `M,@( fY.6Mb^NH(R w>i6oy[ڑol݇@)S<i)W3BsKT̗ $P*\Em!*haDv0,0{^1%yrjœ =vG+Y n?R3vٟgAr 7sj2K9%[̠ ޝ톟$wݑȀu8&x,8y_|xv׸tHAbɔcg7r+M1ayrs{0'dAH -xRu@oj;::C$&Eڇ 2zOdRTI.}i9p-BS.S^qb½}Yy;ɵ=z~~bB\_76kGyCԢ!^r: [Dgt-~Ef@@~tUBJ F^(<9PsLP"}Wµ+gwDߝ!Aat'\4°GOj"Q[ﴜN1}e 4p!~UnsRfW9l(1(UC$7ÃZA<5֞$Ȥ4|=OzB ʼ6Y8M##.KfIc,43;ҵ{$MOsl_yjʐA%4kk4RCω i8Ơ8GƎӶb b`T.qX)N0-xM{CU=o[ L-9A6S UݵɓhD煼ۨh:HJ+"W-sg:Lh}0י_r2';ٺ|PZ V򲎕 (+iC }\9(Ғu4K#W>jq4[A|u1RÙ_\V4}ad~l\ %QWpf`۬"H X#>/Ҫ_Ѿ21vf8+|anلB똚*_QEN†ڱ{zDk7[q6LNrK|5*^ٸ#9uKZ_>8gq8Nh150w桜YԍY3cc KF%ۦzk $\H ו pgw}^ˀx27&F컗OmI`e >33S<<5wӐ@Q{I~FE "89sX}?+1W- }z̸V\R_V Q$-óFFcn6wDF/*lߘ +&j;w׺AU!H(dڟ#F܊6n?wtq'Sv T+%yӾ3̋1<7 *yɨ(8QK!JgN~լ`~nHfЁJe%)vRXUq-lZ7HG6/xK_ssWsX_-ӏM=Dm݊Nh1 púq'\*Wfw ~"ګQ[*IpEζv'l'3* +xH=YM "+hmNPFEI7X Ĵ0Q1r jTVk$7^,T[D ~V(f3# =?Z}si hSF _Of`kK*-0> (cV턆e٭VRr!1 ʙae6 \'`kj?cI05ÇDb*qo&~-uy :zo )'/m;EEϳWRWjq_㛆 f^?y31wcGZy~=P먾6ѧ+WѮĹ/Ylk8?q0 T|]M/{gX੃q׻yM54(kLfꤋs\MpK?M$:B2=Xb> olcJETv4eN?5j9JwLZ1"tNBhJߤСn_PrWY+:շФ Zg\o3*O7B͙B_$zOAb Nmpof 3m)e҆%>>_>:],4u${wO&e Tq{CoU>-z~ @hE(+;0[Vh=V$M6RݸܷCr#U{_(:Vtv<`t/կ.QF`wt~0gգu/muF$!mQ^ njP_+.G~\%lCu!ӔtSU5JWGd{=Vw"tY S_ﳊS-{!ȿa|Ʃ?ޑ y(u^ꡆ(Tk45XI>ۀ dGg s&f-}u5_kE:N(ˆeF]CUU?I@K&̅R}}Alw8E-oޞVrǝ1چM8"Hc}wtJ#e* z&G}h*5:Q9aR)<ۈXIQj "Ǹp+Y=`B>iԬ)XOD}c잤Cu8Pwj R!?3fOFEvh!ZFL#h=f@4q_UL!BsD$cIM2#Bd6k3I@]@|7?aRmhO}@2&2t#O^E."#6Md,1F*%ygTc/H7>tP{XdE4ДUkU`o޽Z1>64'ߌzj)Ii9S-z<hAD 2:ҡPowh5 4co}SǁщiV0?SVu#c7jܹFL^މPG*ؒ+QAӂبd~ߦ Q37YAlP{ EoFBqӷ瓾 .ZِX ΂Ht oh] ,tzqmJ-Z:>7TCے?!i 8PoV.t'wAȠ~^+F(Z4h/@ &`| 8E9 OtʫG5.({9K= ruo"F%52E)IE~eT\ w}/|?#M Xr<ˈ̧_VV~~W<,0aYL7Z e;1>FdwS-b=`OUw )MAH- 7fbvEG!"#B9BK CG &%3MBw7N42g$\V]*(?s :`!] ,'&t6 Rx8o^*.)];QlJDI$Xݷ+R`C٦'ݽqlo&#|:>-A4lpa'> (]Z.,\X؏N[HkI >oA]~#$1Bvb-Hf>CLAOs􃟚ivnlTRMo2\åLiVZVHݕ3&6 6 g?gUM/ lЭ9nǐUZxBO eAΚ Ji~ș풸?uMYہ zai_/5FWc ZK;,~P-ue` G(| pV%a9șȖpO9SmdNêRBO&fM<6 Q,wuvGp[o'&Dl!P4ЊdhN2ۮ!nlLJ&x OXbĺfiC÷^j>|TygՖտԧa!ػOMWөULK Z 9nG[toE=CBs:aRat:94MLN1ښ95}I!\|;"fC\"[ U,tepz-;*h]BAHSW1|Y)K4*} ʺd9ޯV O ˲C$bCY>BtlC N`pC)l\c1sb*$=aLvNi!Nn:ɀi?-37!D]!COӝn8-DG;Zw|۟_I'~ II Hr~9aj\X@UNAnF0' #tb͆ACkSן}>_J$Yp$@FwcF+lY!YIB5G2 bq~|X ٖ>mQGl?Ԫ;~;?ZUuljG~L rM\h.ZBD19{($׊&tx잒0pl[ m=e+ygbz%4Q~(M<#v_ѧxT\+S7)h48D~jq09gMkfx8UY\Y(r?͈n8@](*lr h &uo{Ny8Bd؎o 9rL _ Qnr$ҕoMoפdDȘ,kuN;~6ߏ3 0۲%PP}l5U NXnV9_A f%kOju;m1g=SК ^: ? S@ߴ rs Bܡ^ʥP[?Hr]Hhc8Y7nԥ"PY(a*v42+9O%(T k + ,X|-bOIz7=j=M|B=_Xs9Mg_vI3>ə$cw$@*AdSV6n[ok8f PI߅;ds̢FdZCzұ0njapA`ۛ-+v c6\T xƞ:zӖ<3:=oZrB߅!i_6vVUܞ55[$ɖEҞCuLxLi"ֹG]-:=ǫ!'|o.{g[ Z>rh/ۻ90`˧lb.LHMUU!#jΠ <-BdpyT 5Z#%cY's=L 9 eL<#9S9ͳϧl꾟Pίpڿ5SM)ZĤdy遣 ޾iJ>| ti 2֣mr}ZOk2@{w̳6F'T,I$ l@0o|KeMn +~ڬ9d INVPzh oɆWK V -z7B@`/[+S/?_%.+S0bR3PI@&x۠AV|;l$*i`PɎS_Ļv87儗|JR43+9:Y:v:[;)DW! p`MgcܤMcMT8_ßwg` xx/YI[+ ÷R7z2i(ikt8G=NM|Ȧ"ֹF$=UYC"PYB}^q@ތK@^pLI&ÄY8oMɇ :t v}=ySEN.k-6y?Ϙɼ45q 'jjFwtTOeb^RUL$¼w7+j5yÆ_;Kcf hk#)텔 |I ԋgHɲ~#Tp# ~@LP罂B.5' /Xfp`fbc#9z@w;OoPn [H{ǿ׻ZeeXFx$Sv>*KJ Ue~*i*\]#^泹' GN Me uU:~2s`:RaCfQ2!нYDy:u9" _4oj_At?O@R8[O0Am:2οit/Ò0 jϜ{.x5Dۜم?e*xQЕ2֨FvO1fL 3Il2u֕>&+HY ^ NZxh@ί"#7' 0" :è RB۹{kC:ຏ%~׫_횴Y&yg(ߚSAGܵ6?^=WM&+!IZmUV0bLG:rQUY']yOGy ׭~>wWLAA&^[t?Z.Mst$0Z+b?]? @uZ "P_G1G)ˊo]r!9H`C>#0T@dXaz7ⴛ 9!+6lW4ŨPrEͪ*  P[Pfͣd,L7o:?4bOj I t/}]Q\x6 mߪBm?GL\2Z}fby~9LFn%Ԙ;j`ut1|xoتHwjS0#;`o ~'K,xPgMvV{BSt9Ϟ0]䕍U+CaX_UCJ ?Vn|y; K~=F 3!exC5߃{W./?[YXOTKfgr@EOlCm&4&Rn3J:WJasΠi>>cl&esֽ씮 nh]P:h o 6ERlD֎u5IWiߧm~ YLQk:| XO [ES v2 @o@@В# ؊x;,NTr_(j!B_ ?\'͇hw觛&n8UOSA_SC^IA)|x|kD! ~T <&K\;թCۭcKőp€T?y:AvG=sW!5C<ϮշPh30  SGB fdz⛫|)Jtkz#+]-<fQ-}Fβ2T΄lCE~?O-Z{y^#gU,wû;-T-:O0!;Ll1)RB4qŲR2>Pq. [al mT-!Jr΃d^7ycCkGPt.|?+RyAAb}ۗE=۩  ,LTo#b(]Ԗ2A#N%1op 3`,Gn6`E6UzaYNh󭶑o1PF I'Ƽ~& S 1eNf|;]`]F^/%gsS~?E "4{)'a_+Ւ:Ocyð)8+m!dԫX:)c 8K$:$xn5opfq䲵ӰSr#h = ٘qC)nk6䲰4/YYn 5ex?#ʶoxWGIkH1w30eN0_K<53247 ٦_Iz#o0*CV}F+}`f6)_ <ɢ] v%FI[Gnj0"\̂$ jG5c^.M"!y^A@p7@o/=;nhbs ye\Þ6LRn,C)rTqQWs%ò;^dXl}U=hw{Φ_z=ڗo %GEm< MEBL7EW aLÄ]`Y 6G\>݄VղMV,BS1U͍!1E5 18%&pT{k_ePb/̆ȟUt kڳ{*,1ZvӤw>|ե1BfYfnsWHKܛkoςԎ99H].ggd^@`[דA{ qr (뤑?#@N] ނR&ɢ)䒛R…k1sʚ7d$,ǵh Xp*{QrQdBe0DnZCj8 җ{oq`b `l[9LEίU|i"{ 8wH?Vi{ʚUYO8k4$c7μWxryď% U/?zq O!bkVe{ql V/+6|`+V[45$Ys̼V.šP{ 0~*91B]? dmyHʰ$A W'f+ ̷ ;ܰq3C(!Ҕ[^7ʓ~-:ڵB8Ň;?̷+ۏ-l^FbM7Ɣvgv,yMmzҪh&2˒n^z$c3`R\10atSF҅&(7*@5 /uJ4g ?CbpOdՆP8vu4gn5T}m!!>?̬fے;t~9A#G`N#k$I¯C9  7ZeҞW}=9tę|P6kHu+.8s6E2o̲#QyUh\ZtHT%9IԱvYToomL*ʢᤙGÚZ?y0P3φ5r7#?`v<2,cs/kah0.֜_95[صJ8o)=3NY["C-}p:6kX&23:֡ޮd4u{͸@!~s_`hAZ]qѭP=򮿩K.F{Oz QQok^@Vwi6Lr"7g(8yO:0qXvi';(nKatl&I''SYsfFZcc:8&?MYE1{ %t/pb@nHO0gERx+ N1 |)̯:ԛ"H&_nNsh&.5XA)%P`6ထ/N6[:"LOeM~/﷊²WŒ~M2m*޺fA/Bqle^qB*^vm\qJ()#x n5!4b\&ds)ckDf~4R+ۭlkDG[rba~I۠cWqeE% M:!u%[iTTvvSI6i:!±y ‹QI3{I}'tSz jM!5,ԡXTLV<7z Pkc1ŝfUϻmZk'L.D6v|?Th9`la* tk 8 9g> R4@ޘ˘XFc6[٧AC!M-*yj[n7lNHj?J].-픩+Rf_mٙ:_SjLu8x+u+/<1f|~*& 6@Cak3?3W`Kr40cETC8'1P>u˻|?Xl)(!Ew1}`N=ъmd&_ɇݻ>'/*L-Z #o t' _-vq?vQI]:#z \53Sw7oVTS q=M"_etJnQ=,Ќ|Fʶn XC0ncGjv4 ¬^ČdrC W5ʊNkZPR{qkHZ /鱯L>4@]5R};Sv&.m+%N=nk%꘍.쓵}a?jh)zdża,JAeb75MEwaQS9 Ւ:[\@7Bc]t-ͳb/eo8 [ob iHbA!~7'zL zVgUtS0~CF*TKͻr2GqcR☩Lܿe Ĕbr!Pi~i1j؟Otb`'c kwXAa+Ğ3TF8mt\l E]I [0q_{彥Y$V8ukgqp6k.2)Dhک Y|"o[g?K'0i;|92(Gϐ'YńDZj=0ŽݍopK)p C*ge4Ee]vA y~vTw!y1ο@XJNhW'FRD"+OXv'ˀD>f9rp5SCtBXL'?Q B! TaVltj9j[z,?ʾ5 }y.e\X.!|:z~ƉOS0fw߃Ŷ_yT?WmiZj$@v*F+x9fxa_x|SOzxa wvaAm hDqt6U\ov{weGzCچr/g_*SQʔY'z?*6wfOM!m dzRQmL*w3"_g&?`Yıg>pbCarϙaԨO 3G3G5>\Uհxtyp;.NY9_F67.hr%X}٩eev&(~^>܄!-+(@J+"PUpoy0~6C!;_{3_4h7H(o;biemtڈ>Hھ>pq@yY/(%Հɱ5r0R 0å~:<=!V"j۵hC2^l/!.њ ,BH9Fp[r$eNrULfL)G\z3;6!)zd74S [[RiIQKbYWw.P >qps)0&"+ƠB/㩠Z%D'{5r[BOlZ8/a A"T@zvOhngT7 y7v2F3 OuPQv`&5|̒?0|ส.XlQ]4[԰gXb#SDA:OSw!f\490MH ѽpn0_V%w>fn .z0,ZpN*>@x<Oam U!_隱Qj+AR\UK8qwPǗ_V/0Mf0t57Eޗ$"4ܒg%WM$Y2}ul~Kfe-a+"v>gKt[b)7 >A|5 4\Do6JZ'浑!XzuڇXEM#hq#/SOK,T;-Hj7s%o|P:R%W 0?&+uJwY~6 ]q)t|AiepVI~JIc).An#ѭ^IP]'n)kE޶PS@"Tk,f(> +$t\kf k-hmqg HA{&Z W#"x]p9FoSMZ1׉ɡww⟉Oup0Dz! :6k]x]Ky zWb(XBtW&d3lCT䰘|[∂0\6G * D/p %q~-a9DvhNۋHړd8 IIփ֩]ET4?m[ڸP$w*_⯣! JgSEhajtrL,j}8NJy$.de,[.OM H\;ַ1eo1S!(+=tlSN@=^p$ǽsmZxLyy+^Ugޏj8Ipk(XjѸW?7u Ć}lr?hyꝭ0_puG8 $<f_oLMmU7 [9&xD/=\D)lrpCԨq`!&o|PǠ/gorV;T3e A \J@BUq:.ϴyvl7_舁хDǾO=\QA3ߕc0KH=].D̿tD!7[ʑԙ-D*r^/Y2;sn k+(bϾIv[Ndl  ,ow9kT%`N?1)UFX~Ow}y ;m͛Cs:;<8J7\ )(%[KG:NoA԰|;7@e%.s?\MpP߼/g=~Uct=d~Ñ|5HJO0[]$ ߖN3~qBJh_0DH;/O9[H򬲟 v\G`/P;*оe$vQxΔf0jܿg`M92Ջ'RxwpYNW;ό+p]uYK^ܭ9ʔ%p*j)[!IERafţpWnB'W]ܪ_ ^Z yZtҩ\H5(Amُ< יaY^ьH/3WnGLeN{ZjCJ=P}c #lVTF MH]JQ6Z>_A7M*IJܷ[=ܚIaitgάμ\ؐ` K-vl,[dL#}Yo1᪥/eqpnH~"W%iC/[W~kDh /K߻5%ک)4%V+Liu5 m!!Qʪ~Ew drcRaKd/EUf$/C@K5']P<|E.4!r15wU~vBrOop;LutŜ۩8b#Uu0 Sz۔{ˬQ_N:b! 2ཐJ‘y)a1Q_+}~qg*EBy *M<ĕt@H?Q`Y?.%*6+iK aPƫI`=>[Z$LxY̩j ~T;,o'C"I_G϶ԇ P]5{Jܘ| B=ɄuoT*)9DFUꊽeE@eh 1P"õYm%jcuadU隆+ xm.:6TVw˞ʋ\OM  ^`؃l! SIK|c聎y.VJak{37>6Rua;7u_N3JwpG=(vM6{t=102 3=mi&+ޝݒXryasHRqa=A⡉d¡ i隧ĺlOrZwC LR~Ա=6wX X͝'_[/jʷy6KA"| dAj,Zھ"ui@{~GtY|mhѓOb7wNquqNZ@ sd[=JW$.1ByNjG6% ʠ +֙XRDƱ֝ߛ衴 !NymدrHyEfMNmÛO9Bs0 ) 6]$v4(9R2ThY?iu'{GxxKziey<_ȑ2Mf_`f ZX1|^ٰq /\n{ɰPMVܐʔȮOUD=twȒ_9URDV.7=sķԁY)9k-z:ߓ}!3$q8@et&'lw\kb4kJŹC=bmG>3 }J~"ydg/ڶ?YjQЭ%wD4Ijk# GROc Mm;DNwQ#n/O SZ=JO pĴv=aVvfDG䁷ow tG6A&d%Pv tZ8 KiۅQ-qsYQS#0V%>nVAΊ\ةv[ DBn.[Z >Y 4|D[ &{ +oMlal{]*ɺ^3u'9i#cI8w; YaߕXOʁؼzu_+w˅6!~%M,tjeTvN]9i,:#I +dT" ww CmK-&'I{>|9贖(L M)vӃ.PC2_Lfc ʼ+i27|);t&N!(*Xp)yӢ+ޕ>:,YP ЈTo ܸ:^*q<uv"L P \rL:e{רm!~7*2hA"&nW@3tE(3TWzx tmҪ:WlH`ޢ0",X{gh̴Zu09I#:v#qmw ¥o רGHx6MePzX-bɠjTpDf7%B6@0?q!P-|ZU7,{Wv8 :Rl^ ޑܦ.;LBu7QVzbYHN5̜|`4J `-UM"C_Xr1;?P֑3D&`Gg"zLBRuHڗʰ.]8.i O8u2(/D2&)9N+tqxl g^?`@O*Uuٹ]pr(|u>NB 1@iƸS ۮ/.e1 P^;,O{*WNmK3'AK_q(┫)Mto(t2|J0k+jG`vBd+"_[z_z2 kHExGn?h5P9V1ROiJTYE[f5_͉@yC=Â;rf,aILBs_7B5TquS"e*ͣE?\g/!WeΪop!ԗ} x6PqأRf4sĈ2a ){طC# {GG 'k͡ލ)\g5)R(M0#RD=Kp93WW)hᲥ@;?4o:C0;MWS^9cY2xJDx,ZX%;âZsm?!h(<ɭd@$xصwt'/Yۍם)B.x,W*.SIA$x xh. 6/ռ*?0(ʇx(n1h*NN\\%w` ҤN,Aߛ*];̠W6ȑJy!㨂ztnǘ_|nPs)yN\\wfp \]7CEQ{˜Huƫ 3pwc=Oפk2,#ͧ >avr:&W*0F0.-֓gv1Fe'޽Bzj}R(+˺Gы|JaYàZצ1~՟u6AJ[adyA YDHѶ5I H-z2mKHG0yDb}A7A KCkŬVκr'Yo&"_aŎ\ 7hJ7fܒ7u?2 CҍyX80Vl|?ܾΊL_<]|zBT3苆t,M2 dPh~?!WN؏/Yq7vF~-o@!"*0f{4'E,M$ց\iUW?),=F,3Ⱥ4XF*׻nBXQbr,wVMnYa#zrIG01+zYĦcw7=Jca3/ }]xIibG*f&j$GXLQAX0<;nZp@2k<8JHrT= CXΝrGwY%ڤa;1(Zj׵2L%Bk%Ne>١{zK>`0ef>"<{)k^zԁ4*j+3 &`i_*)ȫ]1'^iZs7d,'^%2\~.݅!@jN.=}v]L8eF-UTw/l\ѯ(+ȿtC RGC=O;^C$"q^qi]3BL>x8LUW7g˚șhhJZţ@*"gk1oQH0A ιz|mR%UxA|QsK i"k\0$h9 <p)P]r;BWemzS6mSbqɛ<;J_k&_ ׺>U\g]աYGTVd}ن O7.d3H'? "WӑՍla?|䒝|֠E-jFL8 l-3UQAe95g՚>)lnuGIT]^7EWbO.­q _9+2p0M"|!hc@h:c[˗K/5U]۬%ppo%CVJ-o< RZ6.{g^oZϒ]x)(׵]{~Pӭƒz| [g~ͻ JNgpʄԭR5_v4 LW3^իeۯ8K3L3p阦(q~9;"uRnR8R%#SN&)l銏?h4 &Wߘ}xɏǼ 9_(B1y%WW8`hȶ",ߢ1+^$y%@*v_q /jй"E%dԳ?h4Llԯp.|鄞ÎV D!MK4t)7Ӻ?#'.YFf|J MGPWy*o&^qVт :'(8oqp_<[!,dlYκ|[z}8[(Ëy'Q)AHK)Ot'kѸ[X`gpH!?1M,$T>us*5|j_T=1W{0 b%EsplV(wԔX2KPN3.jrT$ :?ad@He Xjl&" Gj]B`L/R@]+ }Wx d&\: Jq"j@﷚%PB:/ )8暈-zynP HOx ̷Ւ .uAf.Z_@R4jgF225ƾ¿=Тͤ}z 9bKhK d/ ÷0 ~skw.IuR:eD|=Mds/2>PHsO]":z8y..d(/{Ytbnc/ʴfF'M+VBb794:#'Z+Ihfʇr_@ݸƂ;5 J}< z*J`( _ `C%[Fv0j] L #e+?veQ4d6ܤ>*GVރor= E:j!3_v.(hgrv;ĶK KJ26hǖ5mz3q@{/)d;:o. s8'ȿʠ@|A@? &pE/GX_ İԺ "Niȍd(p>@߉o؁gDq\b A@aqS@#@ پ:_Jԫ^ѵ<Cl%t+ʌ1Olu!bV,Èm8ue@Jcvc*EX8q9z4Cħn`P5~o2}7du ^w?1g(1~h,풨s3wK7>a-pi "Gct.,a!u^-=ڧ*?~nWEMr׀waVK_dWHCDO0?P[]Tsd9p<7RQhWsҳ<CErp(nKbV2e$ ~[Wұc~^t+\pgYCN~1 WZU{qS<N^MvuJ'3 Bdt^o+?s:0(#Qqarvo 5)i0XT jw2SwEwYYJ3??p@м۽>["(~U;Zp(VoΪ~VH mȺYwţRTUU-@~d4;='~A'z\>6 D'zb rډ^hy]N\og|.-UDp^w2:So ]Fo*iuR@PeXu lj([6:غv? 5E0aLA ߙ@ɈZ)8#8d{/fiFyŬ2Ď '<L/Bیo|~q;ପ`iHS$#k\UPM$>|sEWZt[1M>3Ǣonm#HpqZYZjJe-:E2.` Ɋd186@zzMY[ Hr瓞qI}fP2 ߏFsHz,9O7$_q߸|_hH٨u:4ݰ}tH.Sd#^D:/c~#"VSJw̯QShw<&_s}PҴ$д/Z b+ϝbx}͕wN}I@KbC~T{<(*@~xj,ŸS`ߞl^Ϡ .W6=d<2Sp?45Ĺc~hץkh&ᕔ"01Ɂ['W]m ֢\)\6#•V¼;~[>7*!\V;}o1޷S  o|YODB0Sˣ?3?;fXY {`drx0M =H:Zۏ\NV\(:B$ ch9MDy=ƝH#v!&b7qZ[tbN{ab2Kf>'NvA!w>WT1(&br'ͫ\Ze ގXŬDeeၬd93ЪꍊU nFݱ}ϘJJ,,)z# DapB󠶅aÚ [K$Rʅ^]h^/7#{†EplצO-oSݑT>Y?9trc*p?sOoqO8 CR)䃾 WT^;pq(GTI0ѧzL'>m2-Q݅jOKfD*6@ ҳGM9`ln 97n`<fi]I\??chtϑs%[#1bLNfDQQC\jg#$I뫑ȹE10{ .S4b/f=J#Z)N\s%Tcke0nB&1;W#@,4TH%v] TgbSt4d,wJYM$qK̺d.F '+ SzQ;e@“i%X$t-MBsO.IB_ 됨 Rl\STgoD\DM,W QC'ԸfCAYv;vAPX#49g>|eDߘ=9jeQY*mw)DFp%V[fo BiQ6ZVCS'eyvvTq3.WSg9ΐ78,B{~s> dU{ʜFtw=k}"N&zn uf.+V_ztRą#@ FՊsHn_Nm,'Wˎq}$|ȳP:*n% ^QHor}y2ۦWRQA&;I---l5ZջdL4&W̅L`]a,%ar&F^#C=^"`Fc(dmڡb\=drhA$^mo}/izRZI=@N5s%߈;YEQ  ȡ$N&>]'p8P1}b3ϗs!z+*B[cvbR8죯%CN㳶A$׊a-JƦr0W[~gIY `Gn+5sXrT= R}Ґ V7 Ģۉ̷$wAO=T+ff  5A K,qfb+֦mh{}35\/YF|//$@W &y"@L=I8{֎X qO 9eLMLf&1\{MdGٟ zK0Ɇ?Btc:ՙu<c`^CJV9 1,F, YűގKQ'wSVy쪼y~6,4w@7B@{ȩX6GD%~*(\m94S '捹3$/bjzТ/=&H::Is`2AgDJρeIsJ -SA\^WgYPyaL}/ukD{ŬSd+\N ƶ"4PzH WzRENx e>4 ;,L3C7#.uH~ Az tYg c3zGgɟ V+EZhH zwhEM<՚'x-ir C?j?h&c|1$gv^2t{h#1&Qr_MoYfy"6+,R/҉bj:oN)yuܡPܵCQ_>@\S."}[j6Hp+gcAӹBiv֓fLUԻSHIpyet+9 -a1_SQK`xW#m]8,4Z]'yk.JeVEBxd^BLݺtFC^ ou ^PN'?g->MoZ@dM(jJtBM0fM3HT$i{TZc0v*Cj gY~xπM);:i?LI H ~9jEx+/6ƻN<@]ry/l!$rR}t],~IqjRhK7a (fܟ9%Qw~;ǖ1_2DFm[%%ǃqj'pTtTo[\̠:Z*I.ԵPk`|OhQCy/"b=uO(ǣ,!{"iJpkH e iF9nuEp[$న|BRZpIu0qfIlot}F|(\+!ld|Y*ԃvg̑rgL΋7%drȶ75gY 8urbp"Bs~ٟbvO>0@ڱDK]Fo˩.ɧ`"J8y w۸Wz"Foph>rS`&oTҟo8r:Gy to9e_A*дtvEkھ+R5cwӧ,5e]&0[ێIVk{P'nc>qdpU/ |͑Fƞ=&q:TVݞ8wzY1Fm^0"\Q.jfMxw=5a4|K[;V:A)ޥ(LOXt ]M6Tsd,U& g?*IXPS+Uord* ɓQ:<듼% =a&FST!=l?Uڱe~̩d+e$=sg܎|^n yFaETjLzxy06~1v^QK,â=Rwp4l{Gsɦno:H?/K4m: S܄QLrJ3ȽyCzTvy4(ŝ k;2P'Ā'?ƹr$? ES_/K7 s]6 !yTD7\h^ ps&ӗ??'ʷ)oJO>%7E=opgvlifa-}W}cp/8z]캪̾U幐&o\Yʆ0_i&VV[3rAEDWvQ7| l6Sv݅kه(&uZ&#O5%dߙ3$#4 ϣ<>U[g @Jy+p 0Xa)ԏaEފ~VC,=sgF`iu~?󊞚bٹ5 -d1sIQ7k -i/;exG1騲*߈'ݑ7)GN1a⡷F/ea4,Dtː٢͜SBV'hbIa@kƹ.X-T'1ppe˫Jq/}}.VQѳE^쒚~;BZkRqx'Y!ft>Օ0[ɻQ GŖ8XEʎ}m>wTP5v7@Gbq˿M;SES^ 0ZRa󃜋oM ސ=$cqmw5Ih fь%U.TTKju֚/hΧ$YOڍ9SpTHZ nZ~;/ .ࣹh j *LZhGoznkP<Z NtGY&wn)7jwĺioxw ssJ ː'AbqZ^JhrE=q i3KxyR֢qPs&藬"ֆ:jK5% ] %/V#A!e:V.E=ݍoEY;J'T|BuGNQрpT] zLHi`hlivwPqYs[8{[N!gA5߼m 3@9VP&J`-٤poLJӪD}7=_3 &ŘǓY*QzJ7`$di^ŒiBVFCz!6b :]b+GHGZFk^7k,7N"֜^OF b)koG|Ip8f@$̀Fnq&laGסPAy>k&Ӝ{p:8/ hJh.1]{}V0sԵgVphҧɱI+mbEq.u@2)kBڋ̤??~sȒ# >Rmma9V$\(/^jrRp)]FnOm80:~9Van%#`l@ٖ{\>lhs02d dm2GC(Cdx=ʗ96|i~VқiHB:^??(N苻L m_ Gc/lIrGy*g0S&C5'D6bſT$ zk^v@J/;)|v/្D)Wٱse0; }[ZP˭{@ns 0(ZV;*IKڃ g)ڹz, Ux/)76˧U "4Ckg3*4HtĎfM9Xrf!њ4  y}׉'BT'# ;KyxXw>`}$~)!~4{N?3 (lgYV諟9pcbAFc;qf;| b"k}S {kS'B1ӷ#F8VF*t,)Y ~OHxCe^L%7ar1%+&/T* ZmfNI7X'3_'B4G&@(>hIA&qٻ .jĮęzJsj(9'dtA)NrjYHA;vb9l%J¯,7@[MP<4j ܶ6[?g}0B^wx'ߙКZ1%/ϵ "sA/IF,G7+ty:uRq SV6n~#]慀,qt8VTc_))b0Z\9.`28j;mNvA zwЀ5`Tt]7OlsKd䑫pb⏏mDRٰ(Dԍf;z'>GV)oŜvWLH1ۙ0=U29q u=sX9?D7,tNMh7/sHC\pƕ4o !kA,6/Jڲ8F9AuXk=a'ׯ<9H+ugԸ+x7`X _YP1@{E:XB*XΎO-MLL!yM&d%qĩAfwǛ9C-iA]별4\o犯 ML7! Me&{ƨs[I6-k8:X--p e_f3zrdWYb?yN&wB_0Ś>[n2\zNPѻۏa&|b۽-`c\80>qc4?}vM-uø wGࣄQW[]=Sx9ŚYoG6860(`B<#v:#Ry)K/>{u76R9o>,m% 1CD 7TC{ tAcV2;zI~>Mg|38 P^L,Av4ir64&-Y mKHmcW]1 $` uOqxyH]` vl#o8߳L`A[ Nzy2z#~:i651TDw++U{m:zPLQk y9*`}dł-zOTH\ɿ7C )!Y^ecĢ$c!{^':rK4;IdyPu ߔ3KV?d T Zm(M\@bi_@ROпV )|h[P'S4ϣHYnun<9bxR F;U?7IrkUϩ餧GXD3JYO0S^8qgylqa╙M0nuvohIA#Hoݎ7y:x}qf۾;*{\hMo}ڢ> xSt0UuAj<8 K‡&Kow6/dC|Dԏ}BW(c06 :w hnGejMDzuom5&ŭ@y0?PG6M",} 59ZYeT$Wksf A#YR3X Q;U՜%ԩM&5VV/SeO r$]4Qໃ1V+:6"AZw%33-RP`x1XzXQ<}(o؊ %mr \Fzb zqܼ#ոf LE}bcĎ! S[@y '?PfHvbH*pՕw {Œׅ|feJЧj/̝4&uB`/U{ gZvVC *HezQj|ۯ}$EL3!\?=C&ҡ{jܼ1Hc$>1JH9cI,G&Ve$_?)qˣEvGG~k7%L&I 0Wq7Ѷ(RvOkgUmQ6 ɨ7|TsѤ,>iyy-[u~nƪXvP::N } L毭Qpu $"lGbnJ mu' #vcTk W1[ۄ0#eC߀UЩU*8V oCN_KTgS@эbD H[i{cCQopxؕP󈬮֝t t XSN09'h$x=2rh8?*')+~W_+4it\CKk/ޕa^&#J F1TWq?*hum ԋ9!ךg4CF5jߞ+ ]j" 蛠Y}ޗ~[f_7Ytm2ã̀3[vsV=R՟YTW~Ȁd~̽ ]ʔ5`K-OGwϻb8v2%9ugϸ{4O)AIxu ="Q0V@ԇ۸g!]BG+G-{uOzr+^sP)\WQUJ\#Gpr nʻ<M)^K`X#(/C{U]m(rO4)鎑ΐWI th7af !c)I?>s3$ݴ#,Y!<?ɴ A~}Xp1\GIXj>5/{uZq*AbА-ઁiƠLo|EBjxGói9H}C j+@UC2kBP5o"ѭ%ilskS:Zomӏr=9V{%Dy/- ŸM !X:X’'>N-M\9FeS)"$άX|MT6@Hlbܺ|!fI=S}q~uiq遾> _p҉w1o>a漻5Lq}Y(gO6'il׵(PZA"**Fdl[.][ d!*\|e&Srq=sx^NYFʪEҞZJB ځ 6{vBGFN:ж_ 6 ^H$ҝ$ɸrٚ4Hq;S6łm:.{?>ȗL9\XQYRY NՔ4#vL:%zOZ\1Sm;U3]w3-P"<_}鄠/ 'ȥK±n ҘAH4ˆCݬA3D#\ l`KGem&EcUVQ6|@sQF{@|t!3J-("6箞\VY!uU6c8C{Լ|Ճ,^Ng?83"߆u܍ײWV7S `iGٰZ+AMWNphx@t90s1pOm%*ˑD[䉪LhuQA T],j[}amtx"*]SG?۹J^#~+~+I, F.dGImOWԯ/7;\\rUyiR {%6ԟ'wh#f<‘j`$!!^@uT~ ٌ;>?AG~t;E;Գ:'/z o+Xf u0]U8|=!_0M|&*'hxLu[:>N dONhXSSްc&ѻtl(=yLWځZAOH>D T{U,XMG;< Si Edebf;u<_^dsS m 4i UX5:4â5g6@K7Kc-(~> ]Sqei+r1w$͆Nw oE4|'Υ -M}&QakM 2h)hp  iÜ ƚ~?}JsɟWQ>\uP6kFq,  zKGr >Z'jW)Q>@`4 zHUӧ5WX0`Ÿ4fWחf@V8~)ku% C~TTR5gУ:* p2bӓ;Ļ@&ߗ8x9E \\6mЏ>I;‰s)IE:M-ʝO!_KQ*Kqi 5(`rGp"gce+tmW^S~xQcP>6Qw5yW֨8zI[jVZc!SkEIXw3gK*T=g;pPZ$T=&kq;5 *0wC<{VS@"] GȻNp8'R PWۣvS yBDa GΜ1˦h:*NVDѧp͵Fvۅߩ)%\)#<ڗף &f&ʦF/;]⋖$jVЂՇЌ{*m:=_|⮳`.lQ"R1K7c {uE ,D55XcKekki{DvvĩQZ@֎ *NeD^/ }UAZ?H+@R% zz!?hJ7:1@r v(o4RC61 2xY[E5l >H=2R-jz=?"Rf4^8*-7HiEkSWxe[^Hh2fN!껧kfEŘPsNI5h'Mmĉ* w;\U9^yRD\Da |Q1UǮQYS?b}9qo2vץz15$g=!&52_ )h^jBc}UCWYN ^AjUvtz~!~0Zt(|q 7ݻ-oBʘ'?|yE>:rԗdrS]I?4T{Ʋܘ[ɂZKLb6/=~J? 5"XjL-3-57lb TS b ي"׮N;d(?7k}`1.Qۑ7vuSZyu@}I]\ VA9w-)hv'I ׳M+M{?ķsK2(/&xaK;ԎX/߃ Wkv2` 4t b$x\w(e|iũ3u׶ўW *n[x3bp&]̊&g$SvqGA4ߜ's=IhFԟ~YO$´٤Ҙ\KώmbIU{Q۪[!ŚH/ ><{ hs$DU NBvgvǮj[yKkdu)K^UIm bOU%dmo=8n =>I`0&BkXLA l̃ľg ˝+Xc4`Ӷ-w}> ^1sg7OxQ?٫?Lk*m;.A* 7Zсu_",Ѻke,~+wpW78b/i͵)t; m]X4аRcmC@Q2v6nzfjgbVQθΙ\e~4UQ V#ἙX_1xFfzƓIbøVtW#&]rടGI\C7SVK񢤋=wQZӞ!A=Ϋ.+k +0PAiiF#F %ݷbˎp.MD9Bu[ ieژu/[VDYLo\G';B*,`G?fgBֆ؁6ﮣ0[ه#ͺYHxF52~2; bOh}0*B)%";pkMlPB {׳Ǐ6>(,bE?5Dp2hp݊xnLQdQwh n>MCg^A}nL9h@#_'~sk[LIa.2c#"фbqyTx16mC=Bab*Yq$uu\u%)<oѬv^Yq ((L@ i`C9/C Թ}_|]*J0)1ye{ ?$mA~Յ (8Pit\"vbi򙓇ɏ[DF^Y֣{d:^D1OZ@/~WJJ %DLѨS?ޤ lhkWg5㺣R{h7S)x3I];l \+b2rT~g,GU6fxI/x~|vѬ})kz ,GI|J!Tw? "珦x:RHYfkXe9yȁchH 2׸DKKN!^Vտ=\r{n;ڧ"CK׃{ҲF<-+VO{hsDU%ue GP&fC"CH̉ٷwW3+.,"@&΀IMvşѨ^e"På!ܤe+=khf _>XNs?Ge͇$JKtjV^gNz]ֱ9^~vs|*$N 0'?S-I.!+NZWLwsuŸ0پbcҬ tr~vWU2liP`-se*? i7\1d F)pjFCF @!IuOu~xZȽ e="B ڷkl ӯ0p1b_P%皆IYu8i>RQtfhOki1pd=%T]b5&T{Suϊph\xptΰ /󿽥2_%[!ڶQ՚=qd-$<r|&[*[ڏ^ZeQBY";lJ vL`MgV6EreԶc~M_Q8aE2/O_6$}_|#WrZyHxxQXȹ!#քJ$'"<⚈0RV. cC3gP˸u(ÿւ_ 轐YQ+`\(a2bh,a`!nu>Il NM75+c/Oi)<1 {X/VXo1Y# G\(p?46j;ی-Z{ΔDӚna?DL9(-(sqX-vZeO5<@ H alD8Ӈے RI}M_Eӆݜc~eq%DNԄ6[hl;zHϢ ^6y"HVmxtMNHD+NlAxUAK5 EFsڸZ5ʑ'u\}^ыS3skJd ~{ङ2@f'zec% Թx P+M z0{)a HmUDGlU0DL~&P8/aCl?_l׹ ɿ |pc[){$,'끅TaJxum?XӼK~ "Y_$66BIl=sG<&*0>!wV[LbGF͕OT*2M:y4ہjFqѲb<(=_GdkFVr=e&Jg}ӧClAӆ! ci0ƫEfh44MeI%~rsJ9RIg%_$TY| W*7$ԁoy-)uDݡGwͬZ >~z }Df!c$˫2UmSQ^I/wE *g \fy:c+Jo tHټ"p&9ퟍ ^vq'Rr絧 Rg^_$TY| W*I,>${UʒK=wqcsw7G tuJ G)=b.:wO'4VtJ\\I,>${UʒK=ρ*I%~rsJ9RGqɲx|jq1۩GN]&ڊ۰"Ey}VcG&)_*Qbܒ!55>컁8JG>Qm5Pv~h/{tֈ⺀W(NTb77i !s_ے ~$xZiĘ% wvV \9*9qSק)*R(P5akAVST\ȶZ=MP=A):R%߹Rζ-Т .ްa{>lT_ļiPrpw4X8fq[@:VtB,4åGb%".yqY<\V?2[ymg%Ƣ݉MU(]T $VAMynGUJ>@b$.Iyؕ?i]Q)wy6_ȗmTk0ud}g>π')BTJΜ)3BL~NlY5py";E8y bw0gngRuP>q@0f@i/oxt['TY3ʦo_9z{tw?(H8 ,@xg"?t!i,x2ЋywRr..unCgQ s6_@ȱn~ie=B(U+{jNg oUĄ(Ct\w|< XZtZ0ibRՋri fj?pxޚ6r5tPM55~KK9H@+IuZ\)koI+q-_rfX(0_"XBVSdx:l~m^ɕezr{f5eh;C|ײe2O+݀[f&0T2\w/˂܎r֣ lus%msƷ|ml^yh'e'}ffܴV [Lbx %a WUW1RD2o˯)LMҕ4ucQ9_TM\{,rR6 `҂]ŸkوmY$E< ~쓖XTӲ;Y)x;ئ4/VG 0""\n,A;ÒdJ H+\ kWIvNp;d4RLV==:tF7ǣ Ƥ04(|`ҟ|+UPg81dcǖ@QG{kMMPb!14ob B RR>`dFT<ϼCWhe0 xxwܒ⑍ANLglR iz>zLU#,-mW['nθ|[_A~wo A+.\!ñoJf2"HM<\#"²||),eU`,-cuC2q3?#ukY41 Hq.}F 2o [›#lC=jP=gɛmvH@DKv{PqØX7)9c_ bSI>8U.\Чr:ß6U 3p54汣 9MylӰcaP)F@Z{8q-`$3bq$ZepZ5ϕ2̙ruwt呾2Ո_Ҋn#tSŸg<n\"..%9{XtȿZR>zUҢli$g)B]GZI ,gN\~tP9B|_wΔ<-T/([ූG|EDgcqS&Jɪ]l (.8 +2PJsmAiH8[v|=uW[DFk;QTq)gRѾ"N)b @Md[x1`G3)݄rI!@p칆+;oR0VXҜre$Z$&#  9Dc`}h`65- V?M7[~2I*9<*imN=Bة.DdȴrZ(<Ѭ:4 )ds` a-ͤas+L*j}4O,ja&yHB%-Z̨@ ç*M%,f%7 y#=g͜ᯕ5jhfb$|Hc<3W㍕Bi.#pVIXA,_iZ9\B ".9UsxDMIuf?@CI)V@bҍxES4S<\sA?]~U3n AʽWZtmԷ"t) s8($mx rHk6lK&e 29U~,~ihZþ#"B ,CEβ[/;jkgSu֟^&bRyR!! &#|3pL ƫoƥ%lefĭh=wC:hT눰HBB|`5Ek+]B7.cM1K<٬'׿-Spq(vӢb' PtH9!؅a)/Cݍڷw |식W귵̦$ ƜkRNl5&j&%^7`LQ,Ls+naֲP1.kB`kq/ ,]" a1#R- e8SEhcdh+;Ve~%jx%i"?{0u)yԷbd}?KO* eE^.T@F+@i;_~yv8.QYi%,缷v } `iljL46WN'vde=XeY|#{WQ_1Z7PhPguQӕ,RA.+5$Ou4yQXVWndhXIi'̂:QSbW$@GS_*8l>q5kמK|Wh< Dq*EQ9.߻lK E #!۝r~A{5˺ ro|HswLb.t MvT~ R;n^Q?X')ƎSaR_L8D{0&f,4%ň6<dGEveE4Q *vlD 1I~XI#8/l={sNM (b#z:P G1t%S UyBJmDĞsŏ&Z@%m|f?N+@897Z-DF.U2804 FLcT2GDrᐇt92t l, j8]*/3z$XB?xm|-jAflxnۀهшvXC|Bi"ׂ-H* -z/555AvzG+j|pH̋a0fxjINa $S CHᵖg$Ӎ}#C~c]_ v2&e5 rae/KU"v^+jۭۛH@ThFX}"0l5R+^9Ɛ@YRȅ@g=`( +אmB@RzX~3yɃ ʌUiVMkҥYf6Snwa{<:d5oYƲ>;;5ا¨ʀ% C(?.#| K_8Hd]UX?SP^Y7s m7Mx\S̲ăp c?G'dpr-@B 7ͶqGw]$J.+.c^/"Жȭdt{KM5Q+²>7>i|<*cL,~6qlڹAR-]oO%y2WWTj۶u"r'6H*oIm!t]_?x0Nea _)*qm0 vy\hC$)!b!h7cB-܁~Kp.{]/T9+G,Tu+4@iW>7]lQZ=6gW={R)F?" tԄB͕4L>PXeo@l睢.1S$ZXaN~[yʙv G;U2VƊl3N 2^VMʈ>qTzTWEG, 58}k{"⎙U^Þ'$mvP<>WF{fnm;ײ{G6Vyytޫ;Y(.OJ:@+ASJϚIqnOyh"@:AT+ Wؙ1?dŸsA6lYVe=oc&WY:|p@:Bg$g"(K,~*v,x(;ڬofkUdVRDۅD!gډ?,766p\b%k.F =-BG\HD֥b2H}Ho|Wnq3ݑB k;/-Z勭c5)1%& ԣ1Jg"XFQJްCC˫֕ty dOvG}Tڨ촘CElc?Y=:F֭m\3 C.הE1$?+qSݎ 8 Nb*Γ>MÐ[1VAשPph-j6![4leO(,0H6pm&Y@`F.rv!M]j Im䡭P 6fp|j-WhpQ- OU@f:7=Q R%rE/ˎ ⅭcN'q%͝PVP.d2E)[MmEYACRNh%,< 0q9TN! ;,Iՙ@b 楥(F]Nb7<Q"=rNM!WEK`w됨6?>̉V"${M\ĺo]M/G*ꃡtƞC9SĕԤe H Uy\ca`Ȧ6Ӌ3ZQ5_ݙ'>%0+L0}Ji Mz_7N]yh!&kDCb->jQӮ5{*>=YF5 L?HIEcӫrjR_11?YGQذn Y^g d}'\ !J6Ǯu't"{phOonx㠎 #wmU旚p篺☍;jÏ!/Jg z9mٻvİDÐ6j)/6ˏ⟯' 欐yz=^6V~TWcebhPim9J"Jڨә®c\ekICC*A z)ÄR$!^-(v Qvs|[! kw,iirOOnXjsI |R8^޺Q2ANP΅kϓ>G0uQ[HMx5\K3@&]Z\*#pO~ mpV`*㿖"mP CW{#]l?0{N C~թ$&)OQE >c5"u_Zh\S)G/MSđ:|ckwT0 U/mHP>< 3@*in*]Jޒl,"c}o~;>2%Ѣ Rw>fJؽ+B^syןU[N! "Jˮ* sbwj$[gN!Ѣ?dlH--*:S \<[|n"@}mP /J#+k_ gN~t-*BQ„Z[iMF̷aq8` _~T%`uQ&ԚZS"&I/pѼCQNoNo;, wDg =j܆lj[OW1 "teZZۈOӚ%8ByڡQb2s|vdO:6ӡW6աM f) 4J[ν`mS/,̑6qP!m$4GXyw1)s\==uvH0-WWcիIډo\cItaL- J][h-p'䉁 ~R"{}OCKs+YpvS Zٺ01.%ª)}e|,'ς >5|+^JyS]M0}pKɊƇ4`5~ArNxXB [!&l]eА|ig,#4y$Se^ÌO_FEEFQ2,m!9KrY~F\ _'&+⅓*gMl/v~?.N=l c3VZۑvOɘ. 6{,h g،ٰI%u rQyVN& "8}pWq4]KZϪf:odLwaCi/UU{f49/] N '($}Q: de6cmɏGۺb[G-&(t=rh3,Pͺ,1zU KwMF=uЪM*G|2%:_ևT{!BqS_m}"s!YuyIWұ x)`͘LC6 TFNs|b5&!Gh_M? R]'ioz9a`ц$gAɦbo'RVɅ9hzgiMPRTС7P]YЇdf${1m8ޚ}51nl\WQE><^]ޜaf2jpwGXbD$"yv*Z+^ah6G#,WHBwjm2k=kWS8,n7 ŚB%@+U׌#hL{1nno7<2Yϝu>N ^c\3y +NWBJe\{yO܂gKWMO"?O (\HkzƁm &`DsD/멺-$۽q}lSǓt2H2ꦉ/IoAN7dBsH_ 9䆕鈱wh9S:Tw&Ů">|b - "80T$-?kam {'ߊmHhR{C)x] KkNgϤ7|#1tV(6墵Ufffw b~I (-rguϸ\DUFP"/[4 )vlz;qWw}BҤRxQsHǃIjaF%/tU6MWm_Kć,Z|k(9v [n, QQFU_L&qR)>W5ɔM@Ohqͪd{nA? B9kXoz`lφMqagʒԮPӅ8znFQݘ)pˇٞvE̟*%D.s#h{#S{NGJ_枠BAIABzY>ʓrG1De z!keHն , V=QWL Xg𴺢աt~rm `y몯,G+wG6:ttw SG4\hEb|Cz6IҨIՙ0.H~G@*"-Fֳifb:]xd6 &F؆{d>{Rm#6d␀ 3 1 nRsbƾU">)k66)ގ5}譄W|+ҦZ;fe_Ϛ@͚ˀPD(ee{kjsa{8l^A`i*w- qIr+T0b H^nRl .2P7X O(T5x{H}A%=|ѨFvͳ_ϭ΢ 8[M3Mg:ma ?WT '"kt q4b(]7ڳ2hjUp纷FkoUTN'/JrO=v +S Pzh5l>#MF`Gӭul6o>(jc Bxs5B2Qc 'J@`SoML28r`Uf~QkGA|8v}ZE:ca<\uQ0;hIa;MZaYߠ{ VVS?slP+cJ6z=rO?1'9$?@ /A>7#./L1_KT`m(UU;kм|Ì?1[tftռnL~-~Nl_/+y\ =j 2#;Dj V!sϮ6ܫ8}h5]0\'wNLu/ b*ESbmh7Uz7:Zz(9 F$ $,!}ExIn/" }o*gXi䵹S]6j,> ׬U/5"嶈 Šb:*f-ۆx8++3{aņďLฑTBǮ(SUf u-v('ܫ,3$˵Ge2CU)EE%wVge+s˶mbo}ðwVZ "DjK_ny f_/S$Y:<Ya'i0{&b#BdkV<:LkV?A'kd/&3#⑅C d8B(, gi_Zg.6|vV&?0J!aK.mZ?"+Fan`)xՈXQ09|ʄc&c+Q]s.l`I~%ݟ?RJ8tLmh,!3F6 amhDm+bm$:҈5CRg?_]z,ot8IOG= a19rrӷ?7Lmp%!=xzm V[L^d̦tiށR)EUC g^ɨ{ Fv+g[b 0Vvz8E&&bA-p7Z{·6'慵!A3|OL. HhNR܁g_ůMqQC K͛¸Qun[^yi_ў#اz6bkV"]Y!<Dg BϨ^ CA_ u$lQAo9s^^N0w8fګ> k#PS2'rM70|Akgv%Fb-|[ۥ0`vCN !oul@f!pQWm*+4Ys(%VyL&7Fx(<ń>`_08?+T#eǤg6M4CEM,ljt;Wmđs_mq}kYMW4)=ew.Hoe陋Ux?*V?Bg\@ZtK_"O468a~crlC7t}G FW[xi=Qd|qSiMv&o $CSu1;p!$e c$P~dc/(ƫAB--ٴ& [#:>*J2@o'/+1sm^T[YK"XD8O)LT雮|J#6@$Ϸ51e,d"pzɫOHs_h6^?1t4Ěgc<+ b^a^Wx+D=~h+>my e̢q9l]UUAYC'G߹B.;${6BbPkdžzT^ VKaaFfvVU063Z`ɵ4tH\thVE2wo{ÒY " .Kt x8,(4UrH%Mi!*PC>I5SQL+.ɛtTvG=ù]喈i`)DlVl^$A6Q\ԾL`dr+z\`D_O%?B947Hoɤ(8/~DSf$oHfϳR6muUlQH({$A@(}fz'nIv@{{.-b"02Ŷ!DL0}uA](C i7$tb(tL!p;`K [&(8S\GK8SM8A*@ȗ ?nȿ!J̧u}ɟW+ne,Dm,in3YB#7IZJ}ԝ/ ]GK1WkDx˩t|J6-R"ހOwunPFT_{\XS'h@r-+d,QU*O*2G)ZJR Xn2}h¾:ZT5MVr]D[gC3paaqDBȴ=ݑѓGHxi-Žy`<3}+*4tTu/<GA {dý*[Wqӱݫ8B$WR$4X r*ٿ16 Ǵ╟IiY_"5Ï^0X-8wL$-67f G/2 gǺ5I,l׸"!Hfa&IvY]qҥ0O mQtY. P͐@}6#"Ue8۳s8+PS+p 4=>=/q?΁p]#tLTb%4h^p}'qQ|6!8qGؒfO"DZ%XlO.ss,Z޻ՃrP⾐||lO9)P]m%?F96ʮzk,ȩ<)hr;t*Q*U%`Rơ[jh?eUGz|NG~thr7Dƍ 5 >l%CM3҇AmW)9BrO%wXW^Es jLI $H<(cWU%M1DE L8 mxMp#x䇎d> lj_ $)f!R/mtݜΘ^WMw$4=1fn~k= ^Tl۔kZ$N摌\o٦8jj͒AG)#K{}]Ciq1垉̶繶2V$ XTLz2!3{> ?~$K{ ÆݽFUٯ8T3g UuѶB'O^Fu3jPƦ8즮 ].إZ40&3[,FvLR2 9˸7틑&x-}C/Zhf) (Nh,ׂ)ȑI?; $j2#ᙋZelAHCBXGdbr%F֞Fph%o:NIۆ7SHr<^y3[Lc` zrr=h8!3ΠOCT]h!m]Ta#(X< Yd̻;KBqbb0kiՐwqE {'!*{/o,~g^8fR M(9vn 0HqAWz?dD_%UdKk8AF3?Z.۳B/#8$ݶj@/334A5E|zm?^LHk|(ʖ{V B5yw $ |RS>vd_ADvR&iT(\ؓFN,lf2j&J<[7Z+eRzӠl94Kcc;]A?o9oٌ/ խWjȻYTѰb{4]} ^HDnʖdB5eSuvЉw`6yLY[ܨ;"w9t+\H<%X@ҿM}ILϧzHaC uu9 ^`\?nā~⧵Trm{k{D5Ke [B).w[-& ׉sC?^ҍtܩM[GdQs@~vq\ٜ4 XhX1#G9tRW_AjpՌ},4åGb% [hYش0 ՚PF dߓ?İ[Y1 vJc6s)-Ѩ^ B'HtDEÔ/3Vo&&X  zbZf]$UD=x!)r3Ժj?&ɒu:)*v {( vH:H+^r-G(JOml%6UDrbHmcqD @^KώigIşsq'x|SZ}_dgXiU(L'Sb1xfBor?̚Zx!qc!…j/Sqt |QN#?HQ` d⛳Rx_ Smer&8ܺ%8}R-]oO%y2WWQE"PP1Q_}F@SY}C@I;-g:6?lmc\󈴥5:T K?xo 85&H D:V4xAHxLR/ueu!Z!LjCtk5ixZ4bPK=LmB,P2"[ڇĂI͋HMO©t]Aa_AS' rͶ5xARXXf75M#)קS\ϣ3aOF 3`$3bq$ZepZ5ϕ2̙ruFc0=R`A%ІW! PddZ:tx*q}E ؽK2l3ٿ*yR8=zcQcteB&O2GWqi$HA9q@ E};pU+:>j3i-*^j=2DP ;W*֞(0F(;cvGlA}m#Z.R~:Gf}',|^?u7ʄttI],Ri} /faTHjnKO+Ǡ儸4Nk J.8 oH`q `-Xo]dt +V}tP0#,贶Zs YYH}i ƕ=5SW?Ӡ*AIbc BIJ@E~;5MTOkѶRȉФ4ƖXn 졂&F>#eǰ+ |X{CWyPR?)tmÄ'r2ɢ/29Nrރ4`@ZQ@ /AVf|ZV HhNf(;2JZ-{lmU S akl%v[]3Dבa)F3Vß|njG!-:@*&\#{o_W{meߴ||fnKUK~ǑDS[>y]K.nlPGKZz=G=S[58 +z’r )"te]Ff AJI&.'ۅc+!OoY3;y-#$%"OvV| UA8삥7˽3;!=#!=,dD.sU*Ėܳy!OMoOnڈ}gg9/XO`Kh% H3JmP{uFṘH9D+\vX sj'}@|՞•vx;rpI}bs~ctTll3IG|!y?m=#ʕE09 NFQrSW0{Pg91:PFFth~'\zX'^-9ˑyrm.콚S1X%^X^Jqߟ,p,%5-ᕧ-3'*^Z;\$]MRe+̤jh2.r* DH!?fc~Q[X7œ$rJ46,͢axP5x%Q)[ͭ66A;GnV*\@QH$KCtl'DV/z&_׽ v^1PKt;0{3#"FԈꤻq/":F;K~Ƽ6$|mݦ5:JUSWWkϩ"d$~}qǽ8rDBaA6!,1Qn\2΂>ܖ^p#%#g`<7i :`L%dl8vcfC|(ɂ4ycYHgV.bؒ"$qQtf^Kl dIU|m-d-?z;6$_Ibx[V5 ԻsSxzǬmęѰccQ+ h!.I&JS4.c u?qvB7Ub9MWr1YwN3^G*MۅuD6EQ =2sOIo|&hH7E|u-VjyLPVKlW{yݽV,2z;lHama2S#_1͚4 e "&ȯQKm5yenMퟳckG8kD~ba۸hIŔQHE+Fs*t |g̮MleO`/ݼ,5h }H_=t3UϧYv|ssNBBjMB6(ݟ) <-Yts4%fl2CfA|TH@K;uN2zDSVR Gw *Śm+"TM8K,NYpBV^2߹T~~́ roT.T%$ \\NW|83EF tH5D@=T`41@rvf@ kIp`=[  XDG>ݨJ: v[UaBPm<ӹOar6>j%ziLB{) .e,Z7$xu4QlG4cSȗYrH;T1]N%L&5zkҹ|A|~Sy*dЈ[lhr 7/>IP24PxsDYd(yxG[GLȴ77 L^wNL',OsP2vO.0OL?1ifɗa៌50:<Н[XmV1}k|\@tk$,KϿrbjJuQ.J_۝k&^2.k&w*HG`<r1EfeknM, 1C4(U$ˮ]/v01 u9h9.7d7vj%>ɔYn|tqll=Y{;u:Nߙ ||3D7GvPƅj+MgD* Q`͙KƷCc*1] n}*| X\lE ]u MSײ^ҸXG| >/ȏs_4J;U N}VbXsK[א #҇7%Җ!Wf/qdxiK Q:0tui5et @VAQ)y^Zf4ىюki/)6EYE (RpT%ӐYUiL/OipU8<g&K `T%x>Ѧ@1ϢAJvŅr,=ٴP[l #x"tiH@?Pw=kH]x؄Rxskn0݄[ᇰ c Z{+`<P X%x9h{GR15vbCxʹ0ƣWu;xG uHT!xCU]:؏c3P{`LܧOY+teS/ :z s6H`Y>}{iN%VI7@Q$ 3/Qڂl;݂oK]TN :JM$BV¡),.2j[8, Cx6)w hP'Y/b-c]uDUƱKU4Ԋo|?w;BP3OoL4F%6 F}6}nVu,Ϫ0Vg73Újžz~#[<*}wl<XP=ЅM;vl҅E$VLtEo?iH;/葖H]ת2M O7ۂqRrM>9.;ipB0!6ɿ|<5w#.'-C <ό!Y2=u5W,t#5gd24 ۏ:ACdoVgW=PnYrRgx_\BVi1bYHܱh8ˏ.=Hz1DU4>wv* MƼ2绯6hpp@5|9%Eg+t@? d(IK{(;1t(.oY /MY Oք r, Eo4]ȡ7>2R E5.Eb 6Y)2])8"TϹviq^mCv"Kq \ߞA/k@~JV&K/csӮʊ}4(VT4i2/[U[xDxQ6^bG{gv|,RM=. <r+ Dj* 5!y=I 9[藳[XfC'!92F#bB/Nie Pp$+3ie_N32E ͚mFJ8aL\92f = ZWI~'8K]hҽd@I' J$h+. Ccq$*s}힭įOպ\~Zj tDW 簎Hi7|z0\,4W^*yw&#i.(V! ,Mp^o~Ue[(Mں¨p+V;Q)㐲a-oL'\ι4,s3ooȟKZ?KS"^_h.e†"C@c 5NKdLY9\Nwļ'֞c]wyU?үɬ6Ƥc򖀟'{'p8Vx ;QVJa]P$  *C4D}"JMёxhje|}͗Z"E&q7^FO/;@Dconilri.3嬐P.գ4ZiעD~&D A&^Wpbt+= MB|OjN 3}SSeF^ zzl䚩xB,2z?!n6-']˭ ]#a6{ DMb4\ddYzK^XAX=M,eg F%ʢ';s)M֊:2L5l\7rU)VB@R,p"-P.vvT5!XwV1Ư3RUʮ<`le9WtlY94tD4EVLOpct)Ʒ [/TjGUYp8J`4[g6 a9q?ok: @dw#;Dz:ŒQ~̑<|hu,o2S@݂z+AH]3 rATzIv|9zBӉ1Vn6փ"`r^zlg&kZS`r0wCd:<p AwXnV?JqbV I=w_l5*q.O兖`0P.>C&u]ʤVGFNX 蟘ʼ`6V"F$NLsU 8cJc}xk/7{,%PeȠhPĂ^ 41r;/a_Lc,3d6BΛٙg[.@^@g;#)K-E/qZd I4b$֞ى-: rD Ɲ ,G{n| a١|9 ֧D)Հ`T IAϔyijtVE&8?^)AVb@jP"gB1cӬ,3'CI~MQApmn/6WD]y0;l2:ԓZʞzQo 3'CHodSo64$g01W;m}cCĵdTwѯ2<^IQ8ukxEYo; U  W R>F=IW6VNMǝe~`'`u(K2=?,7[ hwFUFY^`cuWHqd%+Vx$LSƧvHyN[ P1>q2y lUE=1|8?@+~c_I*[woI b`&ϻ&xJOJ]FK-HN4蟏X+J$Bmaat:ҽ4CMrwf=k!ͺOk5&EڣNGRoM/aBRߧ`hR,eU :D4@HUTzF$\[t8dZ BiӚ#`%;=%(NA)UbfӃV 8rRCS2p'6YRWqkudj5/PePbS(x$Ξ琔w^G  !Z[>FBStZ/ Y|{F(~{QmlOyP kS"^nPWSR΀)E.15ve\V[WE8Hz͡t4&O<œYğceRImя89$I CkeN\~FMjS̶`,%w3_]ˏx/cOL*,Ev D/#@_XUm}\qfޚM쳡OHK?p):<ncJ(]%V4h~ma9,m{dvS X!#DL*%9Q]C4]}'ta\ko0_*_RD6-UE:hi;Tgw5ITmV,E{6SB8N#Alj =O(X V $qF]XGL7Jmtr\}w}Ln^oI1+F> ¥>? 4HVrHS3iPDlW'7VuU XY2aE⿁*;4ZH&V+`LpJ>Rlr6Vvq8ԖWaH7}u ==yx쑨` Apd6i(2(ɿYlN<]w股)?o|z>t[S4fi^(!{vw,^(7b2Y\*ʝW{Ipg1"­I3 ϗ,$Tj"\żo䫘2br[?#]~ SOI ƵaO4d(流xD:= ^Kj:F':*lhO& )| pPC-t 9"XOQi =c VovΑƆRyqf8up پ 0 qѴP@=:/.yVTR $eV_1=d"vKKAi!%1]Hh,rHvիw>`uu ҝq:4:G?:XLܓWLPA"Y&[d0Qː>b1&y2$GƑjLis[A$5R2]Ioa. BZt\}Cifa̸ >Cb0nfXLpk_4dY*bXN!3iVΦ6Hr;we&`5O"qs^h*D+e6 pزnV@ҙ* PK7 +LBMEgP`ׁy'`M @rCwk!ޙ DF9rIUnF U/NL"OzL^ͯ'S XfKlz=ԬLtEi' z|~^xx8@/6W[F#Ց]ǜقգzd$`ip Vش,njp?U*Wr[j/,\?6 '|GgҮ)d;@_Ξ\`{ # ?js̀ 4kp[eLe[b$.[EAnR:2dG4 ?vEQ:ABFrY&Ze{Q-w Cv('A~#ˉKuOW.^DYsi! jfss:><ӛt# pѤƥ )+'j isW15Z?7# q{ڃ:qɾVR)QrSehoo^ hhRaF$ s 1`a4SEI2lgv♝7̿iM<3jwBi( ~O$xF ".*莺i 'RSDS/!;To[QIQ0IhTJBK8Ͱ|VNÆ];TSGFaet[/rd9ڱ·@H{sc8īE7e ݢZzٹ' Bl/ZSttKj`ݸX{_ҩDFy%>, Csrh44< C8vkSOtVMJ-7!>;fO@"OW^=)40q׭q*&VgbD4$ LrW}+7hFw򬘺\9+-8Bغqi=4 KUbc%+ rYa6*+sa % x2Ґ ܋ua]o`^]Q1A+\ǒ1l )bis]p>UWd8gSt8u*lB{oQL/»A:5AU7Xt~\8o=!!\3|z#Ǔ=$l}mUSlJjGq8n,Y69*,@#mv {=`v *+4HU).929Ŷ_Q)$'{ AI8(SĴlj ~JR[I5]1+a9|no)V&DĨ=ybO׉wkƻ.K3霪\  >6ËʬXv>򖘭>(M8ewBŁ7֛kuj >P]B;I䕹ǎexJ~Qk *\] >:{mjY E)AH#N}?eؚP3Nqyē/>51Elc)A 1 壸 ^O._ !gDoEBX)Ra*:Z7gV =7M)]9?j&;k3Pm^}S`LehK!o1ha_o85' bޜnGoQVW[C?Ҧf.2Ļg͑ߊJg 8lwN$](bmX\[i9+33$PZ\>𧠋n?\޹!İot@x{$KZ.%q!ФT`yOoߌq;0VKN>2UErJVoKa0VSu Wc PBU.3yXxM c=h'@Kn_T"boZT7Oi;\|) 8@O oIq8vy^+|V M,{\P{¤ҡYR:IxIt57,7mB][&+yGYx ` X d[5TGHōGH!֣`@I܊X{`{x\*Z rnx{_ ԯ삸xsN^KL~S2G>l_/Z2v%m-}#Dhb=#á黄u hxz=`%kDLyy@4F d82-TǬN(Y"%N~ʥ1ތ#4,B9d{:lemʰILo{>a G_ e91t[%qyvǷsKRmiw[h1@̖zY C\'W[,)_fʖPxF4NFXbZsRZ-gqjeW-x/^e$UNI~xv8`Tǒmؗ&Բ֯dkd}Ge) {ؐ|sV,/N>ZX`8dAZ;|en9&!& ^1 `׾t-(t}KǨiXgAsnՕ%U~:ߙEde(|K@?Eo(xׂB69w+$^؃BϽݢ;X8X,I[]5ZDbF&ys~Z_+}`vUr)Hi%MbEA sq+iA_q (Zc[&ooM[ AU[/e7t'0'*pWݪ_:ڛM=toJٌ%߅)m[{E>t\XZ]+% DD|cbT> BZ ~>ose&>S]g)/R ߘȜP:zmy5\6/'msdEnz76h"1g8xWWŒ@aEw;V^e0R.*}Pg< zQfD,FBC!5j{L(]Qo\*: : VUc$uo爬m}Y#:^|5QMmP1<[Q.Z\ 8g1vd4WHe-U&zuܨ4W:=#l ښ!tBb1 ޕ4 ʖoZ=OqОGDI[ݲoIͨ`M b 2݌rq羬֜U iy/sSbn['t A=Bh$ZL6 f} yR]xtc-h\v(MD`rq51}ZLB!>/cV#6M+.bsqq&yU|uU(WoĔ؍1T"xܴEwEW- /gBo6&~jq(VZ @ա޾\3-D>o*`˞I飔Mc`h6jgӬ5Uzt4\+YtoP< Op$/x3n4Ȝz%Yu`(p.EQ&r23TQXԆ3[YqO~h'dY|rxtP&\EH5ΩGAXшR8iF1t*͂q -G9zx'[nj&c:(R*pX]ۧ ։kQ=u4z*^ X;(lDq.YW`#qG!<`Wc&^vt5:T-m+GI^G H JN>r6dRKҴf± r#v 6 8#iZ~#AXU܍:ݕC-ɝ'n$T&(h5L8s/W:la )ͳ/SȀU[69 Uh2b尾mZ5ԗYK?O0ꬆ{BЛy,G-\gF?@@P:!m?App6z%UY `nHQ )={#}O..U:SgPqHնq? .˛U*aF^X-LZBsdF^BDIZ=BxE]iU YsW,5%Qp~㢯`Ð:͐ l%:/|%֦$>ߋy˚n:Vžs$U\#Zсѓgc1c^hb\ "!#({xZٌ*TfABs )`wr.W-%w u@Ķ迃8s^p s:Fk&2[VS}>z@`|d/oixlٰ+03t#g/cʴO\Bapexr*Vwʘp|S|B1ѷ!E?/ƊT ygRmpAVk %P"qN}rTe:By)H:75$CRƶ?φҭ8 v&倴\*A?$k-xXKQ}vdtК1!0>sct:7 Xwinրk&a@+(W^VnlXW. _;D85YԐغ (D՝)..J0YK5.] ۰oC 8)b3تaA_޷ж]D~192}֤>g+O/$O}L4eLx$Dϣd e}ZMU@}nL6,`EIߤiG{B!0!(fYӽ33kqC2mf2 .?;:}J-\}W_ZdfAa<+Xw̨˝sDP)DعZZhq;!ŦlB?eb IcjPFǗ-/Zmj&F mi4v 'W?$Qj:(8!+ .&MPW[Ӈzξ\5fb"@BϋlqOb'Vx^֨-FBmIs蚹dD?`aZzӊvHܮk{XmKUeʩ/`wFg2^ИGwIf f"iÕA&*[NF[; F`qcq*-Jnz:s7T㛺/J:{rh^[DLҢė؈-uahI(,[]I] ቈSq u1Tdnỹm5C&_ݝ>=QR{Jn#r7AZA1R%c/H~+0gt9}zĕ9lwLrI pM,">l=!Y7SV޲{ERhL"X{Q(d-n5=%+;Q>h?sOQ.{d[hf'G^?Ϩh{ռHQ/KfψCB{h {nQ IlqKkLӧcI{yy#&ރ BL0~ރ/ڬi=-% _k(@wº&BtVp_ADPzYϴy3vҵtZs>x_w4?]8JezpyI"Mn cIJM#b\̢p؝S^WEzCu^mA rA$:\qN04[twd7Ӄ09oJ4 G̜-i@dؕC`~Cqf/Ys1`QUGZx)sr !kvsCzZېPCw6@(6JVXMl6Q8 q{a#S* @]"|;Z[5&(Kl%զ=w`x)y&y8ĸ XҞU 숲ܐ*PL8q >EUӆ/L@-bݳʘ;[V޽mЏIer 3>Bi&l/TzKG&n-j̴ KdXC1D%} 5f#TdtnѦ$'"*=wPnd,tvB+S@B;sS8씨+!TݷSuN"Nl/Si]D#92.XF\m*Y׆C~.+{{Uf]iҗl>ȉNFYƉ,Z.[TTroӹfV^pI_- vx˨k). YNɾ4ɜ81G* (:ȴXP p0Iw,x<_B@M! 4+c#j#,';\ C#{Ξ t̃'*i!zA,C=u<}5iɈk a<(U/+%=!x" XKs oSG#Klq@b2Y=8}zNe[~z8@䙰ԡ|鋷J_/ T L|X|_OiT1#gd<+Y[)0zkxtр+-`W\wiFnrhAv\W;y<ջe %bs?;i{a!V&(ꋬrP>|f4Y m.TvŘ߆+Z_la¯Wx9ٔD]!'cup֋vUJ$-3:S8LP-߳2G3ǹQꂂ/|c|h` \`V_f߮d B_'”&Oټ=g.l@ A8ϱm2H{^.5WI<,Ab2a35kE~&*Av~zs;z.2H.r\mg`HU&0r$%Bfއ@KZFo;6ªLN -RnK^qō6{Mur`R6i# %m/e1FmiWybKb| m~MkWv$_C]]~QOCKmD@aν 5h x63Z"?Ud * f.  }FVx.hQ2F`1ɼ69ɻC&.\jtGK R!aG2–lp%w0}g)FbaQ鑳b܊:Aڗlr~MQ/s@HFJoXۜ=|c1S tAK)<ۖqG=2Rc 1[S&ЍzR##?(FS`=3z F=ÁܽpC:QqymAZ_ޅxoϫb> scAWm7WM-zѼ2zkӝ:KpSbFTRpy,Ɏ„ k>|kAOa}?4ra#2 @3sfx\ipX>uV|1Y%;:wU+%թ I3L Q&%d@ z+sQOU'!,/}oӜ}C[b;&6ZUz~f#ߏfW_A*b,&{M V2\ 'cPQZSoǍ0Xn8qLΎg fn zZI-i^hM;I$÷׸-rқKrQ+}P O+ʻ)嚫GJɪ ۇ΋GPҢʓ98r[o|m)AUĪs#!&<^Q_{Hn5D`U@ mk6Z^=XRKkur'%K桽NvھUվ#ACFoGc"}&RkQA }iT'PT@W rf1'ٟͬg}ьW݌2c^Q 챶wf f=қyc?)rWp9>޳?_`w(,*ȢN&ܛ|c7ޫOGhxߒQȄўrS_fU,l#ȿud@d-oqɶ/֐h?Hb3/,im{Oy'㍈axzh$fe-.Ef&A!ȊOoAdWb0UNH'o”U@.[nw'u'&Wʰ䕘'C?DE}硗h fYQt NBvkr&g{*h.ZAg}CӳZ5>ۗUwߑ^ux90g c5 MN%.%[D!3-q ;CÏ%VO"|pV%7e"3X>Cu3k@yjû⑵|JOtUoӊ@m8SzS>R-R3pZ-" |&EcC`ȫ["ƥ]j!@0 $ѥO+~2 @Zh lׄCj_?[on?m{zl[-wrq 1yuM9ɕ.  "zA@l4-/6cS2p#wsRrɁa0;" QA֗m +=LcH#jSm' *a&KZjIDP4!PPH3OVVN[^аҢ4\L4ov5+Tb*bäg(@/'i׊;ҦI삆H=wC|w "6e??#98}YFp*Icm(ãuY [[{&Ih>P^VN6`hp0p1' ߓn_8(; pl2Izp\FL*nֽ{8f@mXltV֤\-/t`I5e"YxxqpVK?n^n9Nx4Т E0NIQkZPxfy뇻 ĴAyi1fQY~ѩ +"p9#"];"{/3.%ZFxpK~Y=nGz4AhCΉG44La 6ke̲`PDt*~uMק%N7 ?͜I&]?O<ޝQaw2$P xZ\.Yg=fNMBOÔM {6apJXzɉ :JCSGHqʋ˔ 5֖vξ4(Lu]E7nL[ե?~Y ul-å]ɇTլ,r29?Q̾/Y=!p:cT3uqAdW*\9Q| 4U 4x@QW+3jXfeMD.ބgà :zB ==q*Φ @6yk!|{ToeY-i:?,nv9rL8 ˱KKP^ eR0]ПC|H3* äN3VGaLZg2JO ^_vB*/OJ "Lyvwc@H?Hdni-W>CC!~fb9sAWKqĴL&Tyx‹4hDfȳV.2K-1Z@|ˈ5B эbl/ vJ>l,R3>+j"7+M(v0+E3/ 59Tb&KAl}'K`:i69{̞49*l ͖6ff9PG)iE̎&;ފ`EL?_L[n}1^Pb[k*bu"U2lIj>껣 لO}KiZ;h>,*`bZYw!?E>ȣM0!t/- L`X䯾4.Mxqyp|B@ K#t<ϒ/ӺYt[&K0|2̧PT"d[2Vp8jnk.D`:fn+16{8DW#JrcUp /0D6wfȣܿ׭s)d7xm b4 ėϕPH s}/[!$V#S IS~،ӕ-$@{6:|V@?_UgDhBTYBD1hstuNp h u-qtf&$`,;\7xH<.z"q_c1pv?Ztc:ltӹ# lZQrr5$U:53fɪ=yn&G2=%˫3 ߎ0Zkv ݎ`yp\1? ܬ9d pOк0+G;<:Y/>kon8L;BB%nS e%v]x hz!XxѬSw~2LPSU1-(fXf_J.Hw^Vknsa6r|= ՘t9ɩReF˻jѨLn( CmY ͒$=>$ [[h( =?H[k_ʊG~8=t?h8FxS Y_67HRAUyx uzM2;8 id^?b2rt'dd$b@V 324%#yW~n+"~PjX l>?t?H S|>Fzho;DFgt{`cs摺ZPrCsBU@3o̱87@o`L!\l6φ>EUD^;#8fynfAUK.81ъZx1m٪HGЊDLƋF6dVCP>cŖFH6/Ԇ-<4+oYߞ S!G&3)<GZ|;'X.<:G'6_+7 4qyrI4C Pɹt!bjx%_E)fYi Ɖ^'_Ptk9'*)/B8w<#GnS VJĴ"23ڐ0 0hFڙwu z{p6ibclRq\eF>p~򰁄6Va щlLRxy>&x Jtsnw9wTRBy("5;^*^^Q@IB[Nw*m$dB6czQ?1|L)5_kZq)z&PT4m>E3FI'K ύH}o5o6o CiY E|6Q0tfyPr[~ொOT7*UqЃPZYLɞ.C[:\sx8]|"_PMJ kʏVצQ8hz#nh\_@SԷh$tOA|̿^F1 w'"wtd_gM?G䤿hUȬ\#>%Jp֘4:s}_T<&T$ TUCNۈ{UUk-I16 ^C86K.VokKRwX.UBkHo VymX+,w2[Z<w.۷P-Ӕ& uԫ1T mX+j+,/=!7- aI@jB1Q^* eSmP\)`~/-8&lB05eϯFa~aCG /SK<5M}|i>azqBaQ#_|..< я M4`&A_eM'è(ѣ_  =`Nqhd㪂 ^N68jC884lSp\MK!7ۮnl_88 *ܗiƺ9 @^IR|J(W&/@4+sd{v[34 wD Nn7=Ym(j'!Xx Ƃ\ >^?&AcCl U!0mچ?HdPMNTNVf bm ;3#:di̺9\}oE?OO~q&Գn˭b`bdTҡc(hO9vMF$S}V:;1 ob[Ki,+Jpn\'mtڴǁ=BQ-\RMatBpz iɓi}w4tKm0!]ٌ_.m&;sXo(΋TH=#x Ǔ-]GL/ ﹭2~~Il\WL% 制ylpՑ(O)mzm'l ӯ'${o0AsUtQ޵e*պ-<;"e" ?쌵}\aϕ.M>Xn6ϿwFRB:_a)Û)=Ӵ"k߫ejWS$$ 7 ^EW]9#qQ1"ĵ=jelTMvK}/͘cc׶9YܑF!!Zd9Hv#ą{_<_j3c6͇X+FO^K?76fLKX'765o#ӧ?F4mgM(L?TU3{?:SIo4̡fRw^̞/wxO{qL? 7kJD c>^l[l}ë'u)V'TN4-MO#(&onT`EZSo8;oQ$`Ma Qtt~ܦ/AX Ey;جs2EL@vp.'7MHz"-վLthx֡ Y"u=Ws[T,PPҥ7LM-K 8[8d%6ȠD*%?<2ݼ@@oz(^ם!CxZZSm:>5m˼m˂4:Z-]2nFUf}õL,],M)f`I97O.̱XLJY|;fv1$*H>YxP-sA4Bi8 t u>JqX p%|cs~1 NI[>&Cxjf%tHFK:YR D\ӌiO+ya]:j&ՙC? qzKZks(/LԷ@ y> <|z֦fbv{M5g5#Zr%J[ rK!^jadZX9b87D(LU!p }'e!N~TOIJc@<qe6\8)fӞ:0KtJT1ck҃ґUR ύP*Cu2~|{s?S=2Сkeu2 JSxt}Y|WZ De}:H{n_uy]UϞķO rP1u=+)T;dNLT(g*W4laH"TFNs=٘Վ2WyRnA3 S!;s3bd:ڕ/d$y۶'$r'$H93F,IȜF4?%TV(!^ʩg\XwvDȻT8oJ#Ѽmy 0V{_NKuv+u_XvK1 '0|wS|*^넛$j&v4)(DHPXТ3 A_"ή]Cp (4/e+"ޘK[<Mq<ѹwQA8앧FfAן޻C_ڭs Q@oE|e(ArMRRi'nWm (*9?3q[1?xjsbAdY؟=$Nvb0ɞ([n!DoBJA &Ho^AQ?;.(,& 2'@xkC(J JЍیu^M3Jyើ`f+딢1s;VA3'/=Rk$.kV.VfF3o\Rg ෫P(A2w&X5QE-r(dؚ8z`=7JX=cK(gnl3E՞,&wBk N>jzrrgY ȩۀţC/Ɨ PeZ봋C$ZqjfyMq9yI!{|>90aiXZ]v,g}A合r8ftJ^eZ$*+\@ n#4)__M/4?*ֆ3h0G&b?ɬ  K\ư/Ȧc|xSxݻjG輴HDz֏q؁ Zyy-eY NEp,J X舞{pgivb㗴lgH~ipO&!1bd%0,ؼM;{T' p}=+TKl7|Tg߸s!Zc'Sr UnJ H4M|ҁcp77,lԔMv1qŐ_P,%>GC:?PvLLwo*! \]Eg J؅nԙrqanP͂QW`{_îQ@G.Z863"4X} +;:AǦL:G K5L0w'ż#G|(p3˧~0=')A" Ɣ~F1a Y9D?עDԓ<=LiQn{=%m&Il-.ݥXٌ=i`4d1@O2f>⷏*aO\mصd㌕|k|gf#b"_X~xEG]$F-3Q{!` l !z4 IC5W0jVryn˳iG2J m^'3ޙfvܗ]| &8JN39qY<ʼQ'X:T` ll: Fk"+aɐO ψ X:}`i1MJK^wZhF]yU(PfݐaQCkK~9$UB7 2[3G*5|n ߔm5,>/B0ޤ`dՏ,C}6'_}\7?m6LugȑW&l_٤Vߑ;gt~2Y\U(a/#}^x ViCgү@ŏ_|-$vZ@S#,5gzoPM=EJkƧ~݆d_avcL;,@:RIVp8/ Up8n]?bt8r,J$.ng7$#76Kٹq/=yve0!xn A _r͊ s_*iE?ľP\[ꬺ/1,3aRa}ljbC"r0wG j}+"C҄1%.BへV6 JT7@9eLX^p6pm,njxnxq(Y"ec:4"JThhP;m"6meefbv=J\JN߹2 !͛,VH銌9u9{_^Aقf#?ڄd?uoSJ`M{e>d?cNDef1cPt #ؘc:HAa52/vې5?( n?N⁌@{Ks9C.Y`+;R:j3ka _禯0[ *фiEcjs ьf%MIqtִmm Z0s`/ԣj #7ֳ{QW÷Bf5o(S˹#:@ڿIn.{o|):E*`{J!0sJ7ox>N^JeglFmf⽨_%d 9$H$ĘEtm29;fUtQJMe`8ģyZ+qCKS~䩐%뭌8bxSPF(E[?:o/L$>`fp:;vO˴O]r&"wO#z$l{&M1[ o}OB16%#ӫgX.H7ɪ)I|rd[ Բ^$<֔Sz-mxxŎz\o$F=gӶ(dbTkJ2| EN)݄t+>l6o&D]B".!%{;%1@KI YAL'$Ea*! 6:"VCR~{>յ~g#R[H(|R#c_ⵋ˦dk!ƝB-CRne.~R ,&5lQ%< Zm xuqb:گ?C]d>g -rϦ(1;PXx{Uwqt3>y3Gd.U֑5 #E<4!UZTo\-׫ǝ9=t~FUmY4Q2i6 q,/zєZ c1Rֱ\'~c'ݰRDJtx5!TD:5"tDXeC[ռvoe]ֲzOKi+Q1W5bK]& USI5V,iw=-PZ7+`!; ZQ4,ve+̳xܵM.`h̜Y~dG()| qɦ%btt`4*!Ypiv4Yna}/ḛJɗ|}x):~- 'ωnJz;}\vQ!Tb橩 -'k4_A۸~΂pg ޥ NlEǽGlu!Mrqlҕ,>&֜B_e2 6Yr,stqc&5KE%PT!GYIUz_8JllgCXXݍn  zVkކmɔukNj3W#d5 } ӻ\Zk*%{(!)?GXA&ÃX⍪++|RN_KcޤQt"-TsBcَYm~} Tl5M+']b08"\xO6SP(1L\ބݟz#K 3yE ^L)EpEoDUlFT4>,,'kt.ɚݩ&Z{#jgR(ot>^G_G(h'֋ߕǾ,(|ز|vNt |Yu5Ԡ0{t[vգm.HQ=ŇN{% +2V!e1` -xn?KEK,]+Le L;@PlYcr\)) [?$I @-cG{EQ 7D6yޯAӁ׳F%[@Jpc<EfwĶnO6~۸7$Ia,KKbk+逜&RtJaBxє+Rz,j O4GF"F=&Ӎ sh-=ZQy`:**-> <%Rb;Jh +2ZFDC a-]ûߪzwhwV\m@:H%IP=#L.op+Aqc0y|0a/F}.՜IUt!F"4וxW" kGR],Fa1| Uf \]ے?U펪fmRu¯-t)~$a8J 1M!5w#F+76L %r %u*0; 3yYتuYx5c!5XeG{2lۻ7 ؐq Β-t$Oz }H.xʡ_/w_B^֫R(w]F8/>s]O2 bx 7StiYFh<Ԗۇ X ·T/1SItjK{Uk2I\BnalPҌzA5ƭFӱȁaj<*;jj?*)؆I}t;&#ϦniN*[Ms\vzV8`.Ad!|!^!vL ݈,dubĩdRՀvy+"6{7=wt=Uq>8Jz$!5ۥb5H}ɼ?e^LeXm\kjiۧ`ʵZM9byoӫ(Wxf =ċ?f-ߌIgH?}|{pӮ)ٽ Mo,Q yݔKH2ěJs >>LXuQ ObΨ_%)j()4wGI6]Dq$ r2ai}?/"ɞ?[yv#3, ;פ{q`4}Fm)1:θc.Lki葂S 1dXc8ɞU@]{JT9אAMgJoYX`r l[WP_}m1si{ $ayJic$LmJii˱2߷\:ᑫE`gtUo6fs.Z18Rz0 uz/DmzCNw zPA^{8&[ȵM'PJ|ϦkE թ <@[f6К{i:1}thgb k:}P,X{g6R_eZ|Kl+(ݑ IEM7LglWuOOLZWKY4tj{A7M`w% wBq6$p<_d9 /Oԩ'o,7VPO׉ "J2ST;|z 0QCgK=[r1iWְK!aM1h3 ~*UDv.BxKC0rje '+u8L"./>@A2U{a_h5BlޣLJ.R63;AdS|Î:&9QKx۾|j>/n"Ff4H/ĔNa{Fk,M-45eXh/>X9L~SsDT)=s3H5%1}5b )!_čܻfY]D^L`aBGFt'ƇW:N-^OJy 9qKKjw}_jkw^T;=~j{ig19\ϷyلSZK!i<0R󒣂fhV44}C)n.:M&ec\Pa;N.\WRp|#2t3mFݵh]FnOnpB!2i봸ߕ6ӷo^ f? 0b0}%"&7˖uvCrL z]*L}(~ޝigP|Tv/%W> l҂o|3?ĒdBU,ϋ. քZ_e9}[L37׽$DeB䰻ץİu8iG($%cUK415T!VoE2\VfIkomu'ٿH^ҥ}+՘ee 1ږ^ʫl44kH`b$XPqWRW:kirYb'Yj Ԟ Чve/l'[+`JҴ{ Mv`d#1/ S:ɍ-''&Ӓ!q>G4W9G?Lw')B5+@j,Z_|}-}_iw_teQiR_բ^hjaC׾(jFʽ9Xn?:j AoJmqƷ^ uhC!Ȓ$4qD,O-5{Vz4@ cTLU뭁i5#Dr`GNtΉq&xrZ|\Q_H\%'Ndž6]Bj `oȜW"&`7-& pw1#p:DD:F%]' .;itTɒxt2̜c'җ$af ^foR楿(I\Ox:¡ef%sKS\]]]>~8NCb߽pS°ªȭLD#Hf3e2iCN2( t^_OXcLM@mj܌ lH)ݦқS>0(ARfpa۞c%=jis1 e;_FtJ^=>V)w)X}8`vaFE7FgXۚ0O7B(cFucl<̎ٷ9D)YOOvAUi6  PTɼkъhϪ I"Js'0ҹAS6YOE4PV  w^N6Ts#bJɊlmL~ ʳ|3^q寝$cL_aBZaz}> J,T3k@3ԥθW<]a#Dm ?+sADAn|Aa I, aPni5 "{BÏf{ i&AM'Rٷ1)҂,fK7ax&N$B"#V<=:Lx@k̥1gXla3 z}n;/zݚ_ TG!_1/[|ÇYP@{S|>F|^SR3#r^qMdE>ݽlB\D&= (I-ƀuky3GdmI,=4kJ"Rd„S$l=Ev"kNp Z\T8Tr\ك>\ pZ`8AW5e-o먚AɥW#3-2gWnkN ۝%kU-TzʬI%i,@fA͐y-3j<1am$_$:lX.rq, 0*ő@&?w1yV\Kۼ]g6IԖn8*q!TAn5!hY5ϋmst "Hl$r1h|ts~(4Uar͹/9mv*1 d Y4AYfffLܨMhgAKp#iʗ\ }|xڊ*U?fo; 'Te`3$֛sZ;YO &8[l:K_oj{O{KӐp[+.#7!AĮ:zb8ْh_/@F\)] ]7>[xJL"fJn{{wnqۿJh?e۶gcxuI-8y.Ӑeh3|ٖo`~tTZ(:`^c=:FU2|]T=e]{1D *=M0D./Qu+2,YnGϠBIDr~4iȱf96{^t Iijہf!ҩh!_*z~8@Ur _8}}R0Z<b" Tea'G ʉG>r`THOk(Gr(l>[EFP&UxC֟ER.ILaFܣ7ADaABoIs>'Qm?=% Β3vPEyOTejSbxO'{YxxZ +2ne NJ\Wd\ƒ!a%O>"ڑ?Ɣ'Yq`Gwѿk 7ӓl\˹Lj/_pIo&1.PEi8L pWP@KH,tmOuÿx@9kWbWo[$wzQ$z d'DNfBy=5l 2"h[:W$D 1fXIr_uG4'Bꗠ*er\PӖ--oa 4^,'u7堽%qའwWZCe +-f4i*1 &լ쟳܉߸l xzYA;,*Hscx,yu=ѵZIQTʻ3%´:f2Bj mGDK ]AUKQC,PUـ_g__ׁspoNOaL2|@8Q85],ԗmPCЯZBH( E G p$Yp>tvZ\.5ܤQɸX%"S؋G9et+[ X҈(WR 5~sO)fq,=JźvKbP:!D?;_;CmDkvb+^ЛKV}ڦOP :SCGNr>Qe8I8x 7W|ߋ3o'^59wcG% [>o;hܳc@4^#tfq[~.po9 o,P;UY[V7s-XDH<ݙ=Ic>'VQgyv%\_(3ӫ)x^ROck w%k@P p 8c,10D4.r/r_W'H e҆ =Jsi,XQx;I\q'=x:E!^vilJ k_x #]r(M}u Hj nQȡzmA>\8Gd{d_6g؏GQ4R,+")cE6tZ6m6;Af۰ׯ.R?gFD큥?z+&zpIKoL[ sd{'xDӫNeͨ\= Hk{?ʼ;~ %UhCY rn 2w|BU^X0lY䢃,J~]8*ÞEh^Ao'!1J:Ǭ ț0MNJ يk,Ȅp۾:nb_U!bnyjǒhUEmkH{aIaQK V7H 2ܞ1u[c } &[Ns}L$]¼߉K'Mjބ7)H7Rp7֫O8+] Dؗb -8?g0Eo+ C |d8]'G T:;!ߘlwuW{YC1`L"Rab'zvWa^Jz33o g6Gbϲ~j XҔn7*٬(httsd=ɨS@82~INw51TٟL20"ߧ3M2$-N𐀒5&PoY׃X`Ykn0-R-?@E ͎9ֻRa eɢ͕S$ XH F[NorNI ^f H ZQՖꠋy\ M)Ny`DOvdure+;f春qrP kQt$7x?-/h 5VCj `! _tue7z HO³t̃wRTUĎJ 2"fjH@F!޽EZ)pmBMbNXEmJF͒J$6Z ܊?}h[$Y]pwٲ$Ii[1ݨM`(H\z"mW&嘷f԰\ ::{xeV]Gq4dƵ%I i j$HPB>?:ddLRy 'Z]Sdf 9s"^D.'q?/}'GSO!r.vH3,I>~3$ 0R/ȴέ9ʀˇDY (K/L_?bGt02~}k zN c(K~Wāhr$n_4GU\Cքޜ-U#JMK8"F N)I?[Aҋ[fCCm4s∵-ÝY Eq`ʳxQr ačB[-goz_0njqA2x0jOpC_?1E\ICOX m8%f L_';KCn S3{ U,Ob>31p iٟM2HqyF 7`+9J%Hjy݊p4ܶnzs)'֭<A7S|j@N^*"b=vtU/fUqJ?qO@@o.XRF:0<X,*yD1zW =ڡ'ld{%u ,l/@P19W~a/ǥ*\K1w9Jؗth)p64AJnVI@T jJ ZG~~N8jvʝ^mZ '7$KagCCe& `pao+@vzs-:ƁSR> 20SB^uP`\c-3$3,=XxstA)bâ;3S=rώYo%C5-h[n_hwXKf<889s+Ό52eq 8&نj&ɞ+CfbN5QAIXuSŜNul, JDnR=N93]Eڿ}]n퇿#88i`YzK}2IP@AxAۖ`d L1EمnhiwV<=ylv.H@u i DTPV"dR/VD. Y%K|LŠцLQ5\R"Zzc QbJ YiZ*2o>Jq2 Kt;Tf5m6fȌgдHQ|ٍ$A2+ :ol4ݨU+$I+U<A:Չը̺-kUw輳|o]\Ic(Z{|1\ʼn4-"kʼn9 ~/{Ã`2y8:dIu}4Y{nR0I80bx W'SO EJt[21!I ɣ>$j?~)2T&?0h5թhs ~99w!kF󦢚i ٟj}E֒ DLyyڦmB_ =9o,qNqKVsoh1QBlP `.Sޚ %iA9*չUx ~h_odt_^]a/TRP2|\87#MZZ1}8w!2TQ&|Y|-X*[ tebYcչw;1!֦!X%,w¶@pNf]c)@Pҥ6/mxGTl^Į1B }\Pєx_sof<-Ӊ~Vb|7Wˈ P$&F,W(ߖ{4qJ{f4sƁ> SMU[ Cu8 nvjӺIC*BJ\Sv T;/XQE 6`ښwLawysْno% ǁ )G`nj֑`ѽb'U3N47O[4Rx]-d+nm]sA#˄A|s#Mb~ -5?0;Z.xX<8r?vD(w<'chӾ$URﮧbC&0( E#G J4dFR/TKB2F j)Qv#dbDrm57 b;G`=SZiAc+rk|5C>Ơ~C7#}73E>a$TwHpԗ]yC"1fKjaJ) 5@dаԑ\Wg}ՃtnT; Q|jKi?0爸v 8`>̎uu%^E(L1 RԊt$>0fC#;~W̒,'<,]#AP*Ӈ ,Qtq>Q)[@u0wi_8s ckgbx U,l=\YMٰUԹvI QXrִBFqDV|WhUnz)k֦TRRuYyFԟ"nSRQH[<I+Cć"bJ̤,PGvcJQ1H-e Ϙ$7Yf.oz^MO]Xyq %Bw J|M9 UQtUڼugBk!(_\XߣD]()jbr&f{bx1V= :h ;V¼) K$\3G8 xGD`آ9)e0Xn.4dѺ' igXJfa^MvŒ`Zefu 5F86A*DٖzN! ."aUa{73wuH*[R\b ,*Sxwa/2IvawۜA&]Oz14FU: ZbHO. %QB4pXFց=%>(пѓ1~8ne̖yATm_pbؚ)*;Up kWEe% eqPYlbzN3}F<:iJAfpynB ^&V"Be؊7[!g;L[=wmQYNZK(O1I`4c1t*K^cgrVsPqh;j]=pZW48,UǞ2&zdH{ˣ{s8[ $xwNS>]C__Rw7(iXAւw}OsMX{ͷMhv pN K/SO\9U۞PU#ʼm^/D*[{i˟'+ڛ6F<5bc(nԦ9㲯Y-50ҿ_oue5/8~N5m͓@HP&I7pQ\Lމj2.V3s) l7<7?oxLB2QƓvo6hqՙaDPn9⢅1>膬DGd ~RY+&fG:{WȆ4+y/G`ovUlqOgw4 &㬮TN~ (^ʞ#4gζrbf5cmq k?,l/Nh~w2Dj<^̟&D9ұv+:qx̘)la' (!1VWQW[|f甉\g"Q@DY]t.2#CT8o@ouA`hrȗö͉%{OT[ $#Y WpJenZoD KӭWRȕM*]u)]i/W4aSMYfJKX&}!xWL?u]G@5枍pDVJ]$͆u&!n@@S3{BYPؽg[^RPAɂ~:4OHrn / 32!h֖zF2Fpyhe[B:BG=a6aX3>Vo+JZwrcaz0ReLorÌoQ7 {y%v"S^ebqcH|Y?^иCLX@m:nF ChŽrcamyZ,jI[|6У:e5BCPXy޲D2[kENj 0]˪e! ;;;#_c[®BʪΉL;w;䟢{ˉs:\ܔ!$I4_4}GLS^mDND=TͩiI"@^fʠQ&;Z-M5tk(sO9}m@ˉ^'9+Hh\ Q=92T538=O MELVuty*ѻrrDApC7ܢIC/v#[DXDHL 4֫P kyomT n `B.8XjDCPT #B(d'V' "LOLԢO%ÝLjsA*|foaS e)o2 $LpKV| IH6sHwU$ #:^Z"}n6]ezT2OpEV>ЧrШBL/?ᛈny 0vx <|ҨwS.M$a.mMAoEsn؝[5X]..mW& 3'񅳤t|>6=_ A&u"(L0-lr0'9Ёvfbӵ ;b­r)Z74;{gE~\IPq ֕&#`Y=tw rpy{?7D'+ d4wfM,H8iAYZs+Pf00p)haئHe8@fd[AHk\hO'T$Z]`u =Ɔ[Pq^0֌YW6z mi?a@g:ryu^^}lĒ"!Q|𞴦09 R:WD[\\#= HDy5jH0/oh]ȶl=[.xA2 s@"̫i>RyNI8IVho}۶0YW\U]Ӽ~NQ}+ 4Lň柳;FuJdj9E9hFN2(W>mNzPVwy<1dFxfIɤ C䨂 \.ҏ+R,4u1CkmXgaJN;nÈŵvSx?xp I\0hEI'?<31Ckh>|GCuB{XUI-]\c >`똽8$r߹a0ߺTe;Rl5XKW;𭃭A6e"d?t7cftt ,txs}>;>wGqt#MA򂎗ۙ| T\#.)rϚǡuPlZAzцrSVm[Q2lpp3%U C R3i-|rJr^"WeSs -Z̻Ju[1SL%-:= ̰">V؈E>w=iW0pc#t\#Vk<@qfQ__#nY ʣf`yg=}u +taMU8-X+/c4L+:Al{LzbV6rŽ`iA[EHRbtTd'UCjc,dIf,Z<̘&g=c-LE2 wM=tW1ksw3[83̬ ${-;E[`6ncpfEQ {g6ּTY8of(pq^3 "jg_u_+>N(f+EcB TүERUJ) 8c-H[] ç-yv밐YOIv`p <_X(fQ<W^(^_$p.URBPQ:;MWQf )R|S8gp#%A[AWWYn̗><³Lm)+w3R~N3_!if] j)t+Hyza\D ^hmFaC[3#R*3j{H](I 5K.63Wu鍉1a1dhIs n'=xXf#"`6[+ѸKeVFᐚ-y=/R$aWO#:O'EvK?YG_N6 .fX/O-%ĨPrҙ%ZZ[Ҥʓ8VMIE5@Ǫ9`۹uȨT`>2\>|CO-bi:d/_,RXu}|҅V-}0у!tbY:g vmCNjkQc[WV&Yޓ5vbm`+IM!h۪.fN 2JJom} ,mUB]qUakNzBV%C ZgҒF/jk,de0J`&-Q훪**Ƞ193_dDyu"K֓I XAsZNny}59٬xFGV%)(1fN3+rd<<}n)cϝi"FnݪM=UGU&S+t/~K?  hPގ[G @#@߬)Bq16q^7QAv?_uމ; `<_/}p+1BMhfs@͝PFwRV׿WxVI-CW3wT:wR`.CFTX[1/.AHA,2@* +o=ks=&|4F`EͳCˡh&)^R_N BѳRۺ [2{N-sò`78j}ozA*5LhPXR_43K}_Ѱ\=80{V(,Iɶ$ZW%3|{:[)~̌3%o&q $A(h@܀?ڄQ ڇOj' x6OskaQ 77)lߊLefvÍ:nex0ӠFku g8BeI0U{3)3>8 :;h'7+PNK^[3 ҿ0 P;,&vL%UB}znl;.ҡQOYswKK7~$ XlHunm[Dyv;M1 LeXkt&`9_ҊGT4PͶ1^liĭoaӃ TA ԰ᏹp'C(Fe Nc*X> ! 5Tϧ83Z K|5o.xN4#w`V0VR ,B=whq e緽Ճv sVw򮁬ﱜTus'AfsW ̲39X0fN. OR3ŵ+j4NSA_'K "%PzRo,BeCSCޓ0ۜj5hבN=K#U6[SSMW.= M8)`Mqr+3'w90sSLD UWxE J0 gEI QX9+]w /}ӯ*v!Z(3dGYC[b9?\ar6| 0o%2ϕ,J VtK8"``Vw*YpA<oCW(WmWc{G1{T"1<9 `/"+I^e+O(΀%jW¦É3Np3\>u1YjbK7X2u3\,|"):݆C?lIn*h}X8ov1Cp'oA]'R}q9ڻȘ˿L=սnXzx}Dl"ټ E{`.6VfkMEVay7Ukx)|!ӬZf!&EqVHB:JbBKU\;*Xb? :'Ď6 tPRf;D#uRܲ9Z?:: @-T9s6 2Sx>"ۚ1W.3\VXE_0勿xDQ*8n^|fԪ|iZ0'ڞhL6b{3wrUq"R9,Cwz,#q~7 m0̏Q)Oo OI_A ΩI`u0p$dbz {De(jPu $Lp/{ha=a^] DhӀp[ԉˉh m`;d0[#j\lJ$ #e:IټO]PF*̊~KÏΉ*!i%Q1sRSA^=-q؃ z 4Uz)舲Ut2LoH6hз$WBjxF~<IN`j"Յ|q_U3VمQ?jIM ~WEǏ ¸Ɖx!ssNse?`6<"`튻Ό ^F`MBgeJzCHAn!B"qL1֊]Kj\qx dPmqXbWԱgA-aUDٴi_b/}XӂX۠vE (V EƔ |/^XY?T왳HxWoō@WᲝ6ƒw;)]Ġxs^|rM 7-1T3V`=۰DMgؠ=d<*S4^LrFnM&ntDTOm/Qj^(nTTse"&j{q5? $*saB#N0[K4"WKp"h6#= IAQ++5ĺcMVjnXR CD,<Pޱ,֗XC6FxK"if6IgTe<$k S1lK^d]))4$1{ !ϽPAd7Nu!Ҡ.lD~+Hx,FwiF&|H suTǂkA Y/} 70  ho0v3 1(0¥*xJdX y^=>6NX'T}r7)ŸIWcyڶ3XdđYLYS&e9d`(oe8'Ɗ6vܣoP_m(̋b݄9f{ۏS{Cڰe\-/Il JϨ['4 &P*>7|θ&.Ӛy构O>$L.rMַܹ|H.Ng :|{$Ɂױ,ԛ.Bb bd}{9r;]1)׌ƲGZEX,I U*ހaQ#{uv[P#|>ݒ9<Ū3" AJLtD*TOOq/Sۘ2:w_ť1#_Om@ $)Z5EuJÂ9 F3_g^%"kQqWLȦtJo9FC-J 0$m``jg͍u'oŸNۆrexiD`/7`>rWVլU MA'X):lD`o{ {Fx /z ),8OF]77m'/L+&y@/$A;iqllݓ7{V9t7SNW9NxC1 {M81DFuSoy"2i"NHExdїUQɦ[0gNPyfks^*< 䑴['VV8̌@usl8[4c!NݘP{'JnIX0HczSH:] ɒaa1gfgіju2 >VPL]RЂT5+kڬ..6M<V{VJef5M9 ;أRBb*&-($sۨёb~_~&"04'/:S:ϴx-m ְ)*YJ;EVLEw_f @۶.PsGm$^UБՓrIy xpAgzEy!TBsm!q9O7G NKF|o~f4YÝR~22ER"G/`SYp vl#H,4XeP!h8~!t5ǂW ig@8F{_~q@V ur4yo:J@vC:7uA" \2)A:eqfF^V,#Ĥ|X E!WJdM: +|IS7}{Z}Y7uұh '.;vf/9i׭$/DoB|EZX=()*sF 0ð& 3$vӚ!$mA71xNt*䙛F&M=nXBQƚNv푒MeD#=u?RB.;Emls m^Hv&.,0>9B3+Yp{(㝑*AzJ}f8{Ynjv,☸n`CI̫{QV}nTc)}b8۔Ch S",iD.iZGǬ `\(REcURY62t}5Yd P3%5/Nz FzKLLI>{5pwʃ}c}+RAɿlJs&|]K˷0M:ģ : ^xu(#.YtZ曳Ϣ;b09&bvo!OÕI* UO'zeA-VKD>jiѩq!̦w DBѡ~rcJƗ}8kP`J{;(34ԝ&vbf$2,1Z  J֌IAd꼋ᮚ~wJ/!IIovȷLFt!BSC?R@6A(F|_04s DNZk$GH*Џꮰ(J!P0&hzkI0r,ľة6PFA7/#-7 /,#V8~|4NWtg<@({Prb-+5-444|lό.9JkeͅfrCKG09ZoTptl8ǁ?MW\k $`lnS)ZoY#d%y9V&vK+yTjYM7 "SGf w'M^xЧ'_ZFѰ1w>vϦAo8.eYXШsQ!lc:✺[`7!eY3~cl+3D P l]ZAHV/b6ȉJ[8^Q/x4'.kƧJԊ|ڋg \5ٽ3&R>AC,=MrH#$XH# 1UcDtҚ,<˧^Z٠&6ϬuӍ^ b/"^ZU¥ܓϨ# &EIPx)<1/ɸs>F4>*9=9zhu1vԹV 5Fvʤ2lF`XcplMe@X:)F!jN1;+ 4cAkUsk(ӡIqWZ{4$Tt:rfAp|1]gG( kP?W;hI }~G3rj=\WAҮiJI5|r=h =}OxaODv&00^WՅwֆW਀ G#l;fmq O(숧VY+oѼR9MaQYe;gƼBy<-*!7׈&mX/\8RIT3m`] %yu~31\ Gc#Ȉa 2‰[lZx[-_3O$"wƾun `>_ɫ?Aj7a?|m)a+]Oߒx0ap,{==hu[ឦQJ4VUƔPrxx Xŵ~|.f|Y~:L*ɠV{8eyY'7yU^J!!IZL9ZL~n1.vh\ Vҁ[,`vJ'a_2-j ~$l |SFL\F*sB\b'ɗDZ?VZ=uP-MdAڂd_e?Y6,oR>Hޜuզo:PۧXd6 Z%I+73կBrGwRͫJ֬+c"eF{-yUGC2]Sմ- 3QE ^q ɏ^ET kX=-de_޿uħgE#oM˭Zs5.M6͖<O/c͹'7Gu@5\^Cx~[~WxݩA3Ր|(cݢ"ȶ1ʵ1{O3F晍|\3 53qoiмy|\Nt:y/Ph:]/?4ќ1Ԫ _ʖt<;*.7tLjU7boU2R.Hăhz_H߂4] [nQ/y=g} T-L"6WSPAI_q ||k~}qI<Hix!VyK1*dIp8f9ݑ \CU67EwEMO)fĖD0pLE^aehy1$@*>&k3D!%yza-8ײ}=A\#~1^(qi*4wG+}Z8 t[-g8ڝ 뷴Df,&W;=f^}SQ3*Yx.Pʖ$PdzPZQ e8<Gye{ai3*:>m 3< 5,@^x6YҐ.@Yw١T-kT^[CbVz`ɳ\A.ݠ3ݑX}Mi+[%΍R9ky3g]#AӲO@4C >ExhaA_uJ+Gq=S\ ʲJ[Xn7F/>,~˘ &IJ n_ɣque`rAv0?6%1 0Uj[W% h9a榧Gp?<6@j azei$H'v*L˿t:*|^P`ё^STVvNz 9,7?!j4DPp#^P-]@|:Nv7\2 Z*#U[BcMϓu6Kұi*ns'LRͲF$61\^ &pQ [%X=˛j9gQ +iJX= NIni>׮xh%8RZ]]D^Mu}~& Ou+ǑjaL͋e׏W% Ə$n-kr}9 :k& [ȒٮI#{M9|dt}r1qGFbӕo,,Rg~,d !8ǚ-y<+urWH=Ch;ϣ\Ǩ%PN≉auD(fz*q3&NPߊ &+ށ]j\gb)4ܤFxoM&D8M)ėMyUQrwSߒ 5P\$>[! Ցw< 96W%j1ԫ*3}#Gw~"@xN(6)c1(f*XױIL밫}&^Dxm|Xxm 2&Iɹy]&69p+:|uZƐhp5އܧTm 8k:HUh^ 悘9s98iw'"09AϣC\ }q7>2 Ě-U(KfN ^xRhK܍աǿf`5x"tfq&ޙb䘸1J Z $稉{B*4ǯseBkHzCgZ']rur~+]`ΟzrԒ L݅毁AŤfwu-m='w+V.RObi@v^/~z/[so][,2{;xcA PuSQ/e%_dCdLP㻷g[hQ+Ȳp NGRkaQ^_5TbqE*ntd|@uIzE폟\4G/Lf"|MuƺyGIy*#f'Qb9+ 0SAdO/Y#h^~fPurnB-ākso nӎ^t:dB4UIv+i!UQ3D\/0oO2V51ь%h헠Dxd1~S>RR99AWetnVXdu! `2Ul'ӊr4-׺7ϴ?nR^rUe2,vPrU͆2:5W$uB4=˱] [ev kS*{dy؜2]Vm<4-sYiH $yILrVZ#i j& gC( 8C_\iC$Xu!kfUQ/q ̶n3)b+2o|r(qƨzBnjbs!= /MM61iEd`f3rDf;b.P8RK63eH;P Tyma8%áyKST%XbC@m!>T${P@´\d'XE%F=x  wnRY?GH:;骜LA/1L5ѡdqo0޺zgs'bPnȿ#F֤)lׯ ϟ0Z;|qvЃ̮["/ *!]$@ER4_lm3P|;"J|R$*kk;n#P6!ܖa@L VdnZiEeҏ r-[LQ>kaVV\ |>ߨÇz* 0U[tզE>=#_-r}%y7hDTO@ӒҐ I45 |!KA41bX2gcxh"9*$D[v/գBgu!RD16jJv9t=0 BTz 5iU]~7l#Õzp:ansrὀaSa!5wQC6ݷ#k__;; dYQN0S=GsBkuŎ\'~7GُᎩi ݔo-~]d?b qfl{%}6HW?b|$c +tuޕԎLY}Ӓ;Y˛*W-ͭd!ŭ T=,LP{*6 7m;/G~PRdՔ6q7Ӗ<+ /ڣvĪMZ8;# ٌn@MK:'Uٱ%xXNAFFiKuKU־ aWÏ8(#k6Fô9Z&4evƷ^TRdd{.蓼/ *.Hcb!̠i/lB'yJ۰TAʺ0'.XLhGIaH[Eu٩xLT_f?]Ɔnk᭴8-RI,VV!m(_m9N-3W6dI[5ڄ+dDe'p!c[D*̇VmnX$&Lp!ayZEjGU ]JvƉVmK3Y-{QơwУ ]M[9lՐ۷TD_ R:ďcѢދ-ޏ2`XlRTn/R Uzn5Gi;|oπ:5󡴓N`w/RR"9v,0h08")njq"%G?kr™7?^|N AD ^aIa\:H }jĩ_{F]NP [K1L*'\"KVWe(:?LX%'TˍypOb9|t?Q 2j kkʦ(v&Y~!է.شenEJYC Pg  ߟC`+tdjg+•$t%vE?H/; MTeJoGCW7ϝ$Xמ \2&>P׆$ u)c 4h]`^(IOX,qp>F I U0!C_Hh" ܷU=O8Ujf-2Ĭl I+9. b''"ELܣÏ5ə*4-6 ".ҹՂ@ܢ7k3>ϬƧ2<4e%pAěxň"|~c]t]rZC-5 I29(2ZEu1NfN?Gy热Ub~K> ׺>C# R ~~v&49Wiq )aTB~~c⬨@k\թaUUގ30G)Y ߞ4Eu[" #ޗHÎul]O9t~5pimg٦yreqM˳` ^a~ k#x9u;qg'[arsbݟ n2)RĨɸ/BQy8u Ɨ]h FCѐN5gu=kiEi*dљ嫼;'͹+(*0:dgOYw e"9Y1YX+kq~Xm('%s}j޿\'w*H\mUZcJy9D}"'H].Nà'"\[wY .L(]{liV͛xӊP+d蔵lzf@WXBaFd=M 69StytJ@d_ӨȘu(T2 Be.Ѽ265pzj-4jC(9_8YA Wha1ᑏ cp[ClÿN.wTy(u6~߭gŽ9 %+% wrN 2imB.:{LTo `"[~&i8m$ =S1<*! fб#NjLz/V:Wr(1cDwS2j$ ܍oqX\ pkUR.Eo? ;z ʼOXgZUp0ɋ򴦏$ElxU}oaUV5үU~ȑl!G.I?'LiFm' @ -kZ Bt[ _pͼi2*J`68M(fLx/ q02 nQ#>3}mw&{gCK:5ѕDFY}704 ŜvEpBƽ!_+wE7ˊ &BY{`v l{|IH7q \M#xK.o$WpqXfo1 y KʲB򠷎M,LPiv?(}[P\Q@u}[%=ף1SuY3'X~3XWmBV-Q-w^x{ο'>Y0 fz iE0=r޺77=I Rլ$ָ ԰abc~|_u~`!j"c ҁ,X=owg>( F?YDl:>c*w& )ׂo( !D4P\4Pi֦5;!4DLA Ha!" GhfWN`k]|dt|[1o`ADdhKJ93Oe8~t>BpH`S<(2wd%5eDw1ٳ,F**sY YJ:paSݙZg4ŠolXGŖ"A[m8oY =,4e-ߟy\< S5d ;XL{|*kC=qd5kt)imP?'Zஸu@=tF^CT3kp!k0%g6m(@6uVѶ{wn<5鍡FnU7no'~ X{a(_]Եl{ ;> w|T;KZ]vVyha 9ﺉE/sRZ/{٨l@qTG^x! {w &$]>ւHP}[sՑ1t2k)4XhO]|dNk^_o#`ZziP 3zI *}'?Q]+=R S1׆Rgrk=XpiKT%P˺4sf Vj&2N_D"B%^XU GH8͢%u>G!,!ܡT,CMpj Mm6DLEA(D}ء1r/ܖiZT }EËoFJv36RӃ=T"7)֕ssZDuG MYHcu*{;|$#h< Fl0wS5 hS\uF㢣Y -mwq[{I>$sdJ~iyfe]zhBa8Ϳ϶zmVְX}f0z0* K+ӿg/+˂ze#6qݗ<" W o,aۓ]6f*E&8װS,??aK44AT~hAJ;uدg<);hZE#RUDsRNSӗ& emn#^J!9$xӪngsC/!\x;?9 _ϸknY!iq1h3k\"/kUTM䌪 SU5[dES(%QF]r]\I4x74* hvX/#z!4HUG%FHEȅqR$å v4 wC{rF<"@$ZUB1S˝ C9ObB`(#>ӈd@OPTjyni2/psGK6R /-Նy`Èf%0YS"HVFhrHpgx5~2$c;Krg:>eO ;;ڃVf4j72 _Xk1a~6cn49h 8s T(&^DBf7@]Ap}ˁLcKǣ[K$U㑱BfUve 3EE+ Xr/}]Rt .EkPi_ZDѥ*ʦ g#wQܜZ93·i=ў`w' 'sz=',NRJ'ܯhIXBR$woiedc2Z2x)"0kW. 1ӼF儧 kvV^3 1rNboX{)*}m7po:tc+͕zS i8" DrGUU|6Ϝ'̅ )H r|G]@13ߌ/`RfvF}=)ݦҳs"7cFPRֿ$g|j{&C'\{.6+qWaan(3gL=s(ID5ޭ3_1CUeCseR3)hLF߁ :1*rsXZu~)^뉶.Nܮpv_H>MD:d4^,vJD؝4Xi \N!_㔣LzLy9Po\}Oqr=sŴ<,=pF&ë"2^r9؃1cǀ .HO?Z܎:gPt!җTI6=|2wM,0X{D/H##N6\$輢@i]b9: 0iA'eU7qY1O[#@3Ve<՝z7kVGh5vbꦫ Zxg*{5{'kV̽Tۊp<NJz5v'o'ot=KMjqp(R T(uRllpULzEuCgу=,'j;otԬ_C+Zx)(qgK 2HIn@1I}s.3:tE3hL mڬjsb @5tm#Sv ,pLJW4'dsjSq=8V~\zЪю"(3Zn[ۤK6풲28g)pe@y7΅'!io+X> YVkw(_=Y߾)U'P(9 eG}b4Y5cx qyC٩VMJZ.F|pP:k֩і@\8*^c *GK:{v㱀ܒ ߆hѸNe֍nˠ/b-ܕ;̨Guٶ^tQ196|hU\[P?^ iCY}'?^V4 V*XAImg0$%um [Z(}{U˝1*;:AN e6l4f9$v] ;|SJ8.4W`^AY9\锿]]<̮1өhuCDވ{lEhkD/Al=\PVӰ*}ib54@i+hfs.[ zx \gE.Az.]HkuX!EV{g8`O!*CzRqpYU=˞uG8[5l.1}t-KsqQ$eGq>U p:~rLK>SB~-*̋Jn÷ Q+,`6]QxsEa] Pi_z]ڬԈݞ |̻OLCz3'#'}㲴/Jztsm5 b,ʗD͡?R@(#W-Y\m3[K)AyݼEL\;.~kǑeooXS}Kt6.g$;=U0H4ԆEg*: <'_Af<ȿJԝvNKKtah6j{5JA埼ѣ%OݣfVrO0] ԍ~kmq@˔a@mMTnݺ‡,AI/TUӪӾc9tΟۺPzepU"N8F C & U ̵{4D7! }IE>l.߄D{{E=?LopWzvqq֤: 8198ʡ\oH?Wk(3\I pPcM"*Ko1Zb~#Ydܭ1ˑ6MLކ舠|Ru'XnKƶwE .tSFuZCя˥*"rd}mR<ԉf5<`H=^%icQh~f|[|h;""tsԗn0]dusfN|oO$gb)cgWzP6ׯ*JFimzS:",|WxiYSQ7Ua Sͳ؎H5ʧ%-8a9K:M =`0Ag|/&u:[u+?ʯ2& h1WNhMb)Cez xD3QCOh-lސb/9uH5`s|h;1L"->dsa]R?zV[ď7_CS4+F7?" R[uicwUlv([C ;rH\OFV lInF@p;u역$d+On4!K3.8Xm1V/fcxC0݅ĨkI+Ca82uPo,L #{p.{^1ԛGj.-yęEa^{ZP( -ǗyNvɰd $O Kw4TapԌdۄruSjʙe5M\M={ +l69D0nd#$Sm `Uj6$YAay%'% F$tR@^Njw#p鷘=xٟw:KUOb58jxXlo~f9c8><ݡekY+I`@mRy@IȾOQ!Q%9&٧X?5YbAWg!paXmZIW"w0z $ޭsE 2?g5ـy@uk) ަ—u+|\֣鴶a)ER2IMڒl)% QJd'ۅٳ605C?ZrWNy | 8 io|},GJ#ujΓAI*7jyLc\(lyI.oL0h汐*0`=ˈ]sj ~9`XսFVZ,Smδl159r(Ns i,x_Z Y}/G2%/lRuJ3JqgݹM,Σ\zfҚ;GInWo]uk${ÍmɝfT&<\CC9ܵ6Ĉɮ^xiF.pDC]Zr{#[/g  L/%O!'Kn@0YQ;U]IuRav3ſx%@Ih XʎF\c7A[RչFtD]^"UQeZ[MxӄoFv#ф|DDI*>շձob  `ǽ+u}Q |j0GﻕJ);EsC4BM[s**F4g]X[ˣj&ߓܒúv-o: r$?U!N?OvHU~`yES rq!6ĺ܄DmWn:X4&>kG)ifNG<@9@ZS@kWZtdZR`˫)GZinQejDYd|EOy ;RƂq= m|Nt7g@ihLӠk͂lsά5wE9i2y+(I% 3/ "hF V)$P,z)!09FwfoJ5jPXI]ssMYnU viÔV/|ā9:\ )X3tFyRo5=LN4թ%6YaVM:=.i  FV#=RotjմbU:|`kjq{z~$?)ɚ+ 3sr#yg9 w;oCQ9ctpL9{KNiM?GouvwaTnV>I%ȯ75Q^*'.D!URvVJs;!k1Pz>Vf> ~[5̢n. v@#${ r7R?D=|y汾kOh߇ cNiTŦiTE5~ƾaˁ-m9A0vP=T$o/޷d2V̷0@oe;p7 -rg1Si(7`^ Du>-jb1R8jAʁx.q]D$|jA{\ׯR Ϫ*Ra#F,[=A7S\m& ~ٺֹBIRvC3þwZr8H~NV\pG5Z"ڿ<]m0Cum ̝eMC8D,<̮!4c9lu)bLR*6_x.\$*ڼGĈTO3߁k6ZMPd q_c;?GN^/8lWZ+rwRqnFp&bUg `zܦ 朊虵!w~/@ˣWy.=_[\h|bOƦ(B*4fZ>b{,n9Ԭ3!י Tr2`_*J |!abpD3tM*A% 6Gq PJ~ haC'/:}Bw!/yK7p,M9h47}5vY.|q jB项*(Q⓲LLQpݰ<" (ӽP}֓Mҭ\i5`„QTfGUʈ> `K`n n9O67**$sn{yB50{gnkJf U[0V6'bρɷ_ PԟT%EtKcvM zHpbCΝPgP?-c@u׊lg @/`$bb޺umŸ(DGx??#^:HjNyT9Ig\;:y]afSRzcјɮfPk$Zg@CN4$w2+3rF** U(r\桝:{XI7n0E7݃])eSe m Xe5*;h:cXa^ ғlvײVuSg? hCJ# vd8 7o{+Fِ]!HIwdLxl ~FݣUHج!.II>]"DŽbw0uE΃ک&#W /hE07r|[O8Y%?g̀d]ZzetTSوMP)'Vm2?^E<5?nʵ{u F$~QcT #\цbH}F^OGуn[ʔKP;fRk,<ʘH;F:e2w؀!]0i¨$Kw?7_ؐ~ZkFk/xA5fϱռұ@ao \lhHtz.WO/1&DliZBHzqS23Eڼمuns ztXrLA(tPlW0ӆ%i̵7yM.qgN`@3KOa"j! @JQnu}ҿ%A/=*?g#tmN"U ]^3|59'n.CI߉vE i?,s/mthfVs 냘ɽ!>q_vZ:HA$2|Q(@xƓx+RVM5 A6F:ksG&N&tɘ%ŗa1>;LϷsBxƁYqӢHi-Vraْ/.XvrDy%NLoGY@2woA FXrOSӄ2f,Ro /92]ile)aF?3- Hߥ{ŋdUw]Y0-ktUDH;diLw(qZ.~`,;1RD3/ji4jGq?Ⱥk}q"CRKM񁛍9߰3FYz)Gl#muT,G1~7BNX)1QVn2}1VE KS{Y@A}M̸o, (@zF5 OώHY-$Y}7ىEE]B29Yg_ k# ?%whRh2I=7`6x3Ę:V~;eRrCd/'Zx.JtT7&/luː@`u&L^N^050O$uj;V]TMn IW/6xDT)bx?k!fj Ϭ5S{GV1S yr8{[Ȗ6 ún>ū)W08P (@d ҷRYL%fl5a76n *#1n8{;!.mKV&.$enW&[\2XcDd:mUMIv$Ob$q$GVV\ņAdl{9l hDGzKx ֤iCS᧠^PEv >[ꕿ0a9=U[a8 4t2}Ia:(ޥuilgu79PB+&W汵3/(įpJsjugf[(t-}1uԏ0eN/; 蛻m(s/!<]>,LFq‚E&~=Ne6I0=ʷUP6m!)mwHuS;N-qV)!JN+(^t6dv{)aAI,WlXy=ja*;i$3lhdKDeEybQ5ccσgHw o<܏!ccds .h5s(;,2(sxlgr;Rϖ2ѫJJvVb C*'h%h)zAbujA!'!옋%|၄k r+Ot[p}F a-)U 4=BE ktfy2^cYϲrEqkhEmg'Ity:~/OQBapoŞꢨ0*㞇w?bƲs6)e9i}״FbM%k[ eȏeݝ{iΈ;eDad1ʥJgaGҜ[Q~9'(G^wk[vujf nmY5G"v{DKBz 3/\`\(UNK9qt̟۬7ݢJ{*@~I#FQڔh gXlzC3^Ț~:\38 Q?["dSFJ"=S%@LLj+YDGw^9YDZ!Qy;anDހL48R/fNQ.p)f  GrώB ``*EqL[&)gOLUc|gR!'vyZ3xfQ:K;!nGۧ4wyEpc! ?'T=ώ|CK웽1@dT̐DRdJ*52UOu+}‰Hn ]6ܚ+ sE08еN~CB[}h)BSs+33;a)jҺFߠC8Y7k-˚ƁZz/C+e-{Tȶst';WƗCD9 TBktc>/xb`C.6d}WLJYW@|9afU8uF_2LDJTU+&ĎpXn[8'R #O3!n FekL:6 zM SmKO337>W/uMz0zG~!ގ[LqG{इ>ѥT6(a>nǷCO<_`Ol.90?``*({JǤNu#dJ+8 :%>%|fZíP&~b₥'j gxeuKyLlm*i{-p hi{qBޅ'kM6 & e_$yUgHFK]Z=0K,ioh8wT ^H[u|+m-Q\,=XӢ̝VIh`~κz(rߠ-X7C =Ad>|"Q w͜xFi9L&qNL+BгDN037qs(W[C+~7In8 ~ɣ+Sսr,)~ =(h %KfЌ4.YvuS{Pke:~0=bu T;wvC+]]x,aA@`Q1U5WFnbbHYcoR1 zG JJhauttiԀr8曾ͤ\ntvD2ϪAn32fu]T;V/jaeTU}pM'8&wBv%JT,܅1'`\gbEe$Ƨlۊe Aa/ʲղ8AeY컃Vl-WFpdIO𜌕d=t̀ ,"`Yh_SFkCVU ^}ڼKOi,M/V{Az KN4t2-S+3Q_PB~_RWWA]AwD؉ezzqnk_DZ@o/i)Z'O橐*bf\Ir )Ҧ˭?>,p>Mɨo(\ ]6uvGO5)*LyQaʼ:Ff3d,gbE7@pK+XOb7s!ͯ;\`8 }``YL,䶙t;8yG26zkA56S8`)M\sȈYM.v~ fi]؉ϗA@<>aݴ2ˌ|WmZj,gaVN$Eش~K{ _< Ibsݲ>%47hhLFw^~MT.bX|7k+b%g!ׁ0aOb'Cެ?_Z˄]u)״}o,E9U#jm$HCTy=[A)PlHh$jbVUH 2a*yKqB Ԇ_+"!f&6kLxo5A2Ԏ9^]dYT}8hGn0 aaT"v.噒]M,ؠ% (xJzawyMBe?F"/- 'U( 9V(ͮ\h0{r!}l F!w*e]kݺ%Hт9|6oPqrŸXwrzT=L#m{'x|mp !@<$6Py0WYx@ ;YEa$R!j5G ܡhMt)'`:_K6F€Ꙇblv+h'O> գ:t;~1cj=Xnۉ F)2%86׵y2=#z[΁lNEhY$ SOѼΚC/Q)ċ+CEҦXz#k9T OHv!4VE%mQ^w`C\lAsRޜWuƄs㌳jAa.ID:Q/ nPX HIMs%OdmHu^8\PY5~9^2œЀ[O;o8ؐrP%:Y큉DPN' %sbIft}o*ԕUf@`0[E9ӆr"iJ^%g[o]cAbmEB&ĝAprѶټԵ2y?}ff r ^^)63Lʶwybz{]f-9҉ÝDS|.'Fs'B( eT y{|-" ,GXԜW 3L+ݖIZmwfB棫BPZT\zȲ?x筺AGN%k5J*nA# k1J;%-N p2A0,Gy)8y"ܳ&kŸ[8N@ R"0dH3z2GVCݸ{x7RPIM>$٩-=°'FM&e 2ZKp:A\^ Wu,ئjm@qkDMI?;KjF"F-Qy7HZXQ7&"y :B\V}'ʥ9o_tBAGPű{+@WM̺D*zb0 Selձsg,@CʛwH3Je+X}IX S:KO-#}Of1hĚWv9Sojήld/p]y1͡I z8E˭sBm9JI;"D \-J_qe1"5/nb֙Op-YCC8!Ȯ4 p4dqtE(/ ܄a&pAG3'n,:tY+oe=ϔ1ʶR=tgvLN[tC( 9w峃y-W4JPFŦ ]k42.пk ?Χ8IrxD+'rrm zy}CsJqkS3a8P\bFp4YƅX|Wmme -PCeX>E*.5v^TnOܲvFܺ1E8{ԯ ymCKO/hf2ϊ=thZHKx<.W 2Gʬ; 9K60ؗ|箲0@㒯XQ!6>|c.V KvMV0@9υx*um]#/)5m3I5yGcE/˳O@,%؞ |}޳:/ԛJ/{eeWi߂#2κ1$A;1ɤ2Qm'lHݑH2F0-̌tvg"7 /1j>}4-xQ`B=;zv2aG.NǸLuCr.e0T8ha%tLF @E`lu ~呢`x%Qzt459HP,yMW` ͌φNY;^LmMJnkqQ܌5lIK!c wbc9WgS B B9sbU'dB$OCB=#ܩB| -{i*y / 09_ H1o&nrEiz+yuyǨSXy(BlON3oo5šŊs7:{#.h/o|c~7}5K35JuC+ux.˭jqXHv6\* 4gx!eŤބ4bS*QKh"L!_B $ˏDK8Ɯ?8$TPa MWχP٦Zvmkڞ gZΔrnZW2ஹrxÍ@-U8[pǔ kS$fTh/N8  )K4L?0:,DŽć[,TgTy|rpZ:`N2%\DuVƑn/q-Y"FLh|ǽ.F.] 1Mbgi/蠗 gJadER㐧Wx$.8Mg@}VR;vj5u6Ѽ&(oZm>:qО؏*̐mEP-߷őb yj~5iѼGnkݍ"T(|GI껦rc5'm9а3|vLXnNw]ٱU)Mܓ[b0"&І= Q +l&}Krt+21_IVq׮F@-;~+,*īӽK)^pi__nx&PH'ҁͲѼv岱aRؙShVJq}*ELIhÐGh:r,몊K-&0~,W RC`?bݿҞeAwy8*Z:RFpfl:@bK;+i3 Wﺤ-ju~Q 2r.Ҹ hv vh4^ fEUឡ;Z>ˊZ]FQQ 3V2p\-qW$ \j$SҀ Y| xUrI+LLeK$s>:y ǯ'7*K0s5:ԞҎ20*,ʁOm Tfu~_~k&լdUN{?2hap3^b]TxG1W?X{X`ڎ7DvΊb1pGFNAoQۓ֧SW7"2U67fPN&M_4aX˜cab+F|, O*Nh qApq% Q3Nbbwvy6/g4n([dS5c#Vϫ]NR=$mlyTT T99+uD[ʁtpB/WO" ~HJ>yk5nOH|jx;ٟ (_5²\(6Ą0إX]#F6'„DS?5F;Blm| )(64OOLh5 z'p}WZ{Qf}9LZUx/ /TmKPŧV3$L<8:0źh$[0#ҼQ:T9 u`V[aʱf-̭p_kȳ#k<蟖!?2[iNQ0\QU{p2=yL'26 ܾ%>PH{ ᕍ3(}% A;փ6%cQ$H.ntW~j&iأ`2? Daq!+-tkyIu#bG"m?T3(#9iS11D8xG`lS'8aAǬ ܈0L]QUPyVXN2Uag_{ Dcm9Xl6^7 옳CXuخw|ذ gD#K4%&F:EGrV* 4~tۜsnBmF`k{͍hHH@ o#޴ÂQ7Y5q|3"#NVX4M ni 1V01V/9xq!@ae̅!kJ_^)X/2-ӿA>h*Cؽu/whsz9zܲ!trߋK DfH[MД,>0`}Oi[ƵEyo+ ,"_ 6fK+yȌS˖aREswiV֬ʹ> [r%0ht _'`~q5fRϷ:x}L`G"жx\ \ 礗+4ܪjf1/,_g,h]U)E梞p7%J>m1u5|S)&stWн{n~~n~1ưe2b Qrvb˻x+= j,۷o"freNgz]T~%faV 1#r;apᅯA03Nz]ނH(h=p|,0c"q)k蓋Ͳ闠gAG}geDS\4t\NqiH~O[UTEY؈u`uuGbayQ`F`w_j͢<E)!pL)Ώap%;d!1I 6^wǻ$H j7 );nb M2{{i,p%@Sz"{[)2cEPG (]z;X;2؂iUŪdog\K,LJuq'$rZVN#1jځ?Ԧ%wlmOm\2O,/6WROkD4Ut/7!*jԷ9K_J?ٴA|5+ZtE~r{c%Ҽ8֕y0ݛ ɳ;ǕWr^H0[&־' B/įNMf} 8%F9te=?ʄEa+AOBv{s dYY@9 }0\! 2Nc4{Qybr]E8 d˻Og7{ݓ(+=|_ƿkʝJ=/,H+ ,|C웒 +*E(o$YzT;Dd`j5;Ae{Eʂy$T@NwLI@T:DI skL!߭/ $ά8J)ut/}o:?j`ـ7uNϹ̑ 9|uZH 7;bc܄6կ n|e痗//؇!-M=*~H5JfЛV6Gmw;#7E,'Il6$.ZWjJ^݆ێb \2$x4~KgsZZKz嚋56vmFa{XK= m @dnЃ@yƱt(TtI|M)(rSpmBIϩlAavk=5qVeΤNk>TUr"2Z';K)5nrCtpڂvT-2 u_aJq-2L-{kM2D^?9rWI@sn:VѺ;Y_O U% ֛[I!D\;RmJQ|V) gR^.dpзK*Tnmx$,mG5vt%s/$,nN9Y:1 tr>ܗkR2=xoy穜Tҝb6IW絺n|^Ä5d(/# x/Y͋XU/=䧺/IO3%­~IJAr3j8WCw [iz mcIRa$*.͈ꋦ~ 'Q :ͮUڼ(LLuyn_W'Eh,zjDAQ#B!_`>J6 dr,* /AK8w"h_/{\Ihno3uQ ׸܈2i6X΃'L!-<x$ȞkDTbP6(D:yH[0))h+ve<dz3PIbݒSY6sXW:_uZM"zy4='հdgțT6X1ɵEK0$5ؑKnvc!TYVn=]x9`Gv匘밮3^;jvl/GC ,,fWKN{j?͆#:ՔVPvFѾ)K14Ouk"])9(?)h\`68câIWGy#='6%q1 .xV0]ڊfeD`B9[L}&I>ԭY:v(2ƙS!ڽ');w!r8TrU5&=`߄{j=cm#4JgaP6?WJm!D!@(%'<suҕӋ2N)R]_) (ot%< \6/Qq?ɚCtVF-\NQ祬mue6!}.2skyi/i4<+i*))%sllcݖ^Sq1CMSWy̽j){=|&<O$"l1ީ(}mo.›\խ,n k2kUD.Ae)RbIFxcUaRT.+\쑡 3b9w(! AJZoNa^g̦ZebY=3?oi \5[e߰sdgeA ?Q%9~~1ʰ{60%>?ϔ؎UeMU*,Q6A11p7ybY/'C`i;:eOdFD*f.~ՊϮ*̞ ڕ]4@ ;wN"FF$B1|eZM[mXï1EE_^8~UzQۿg}j\Rݼޥx渚mx OV:|J',n,XXY1*!,)tq!̙B!x5$[ks,Έ2ve[S )w }-b]BinEǴ >䟈T|>N):"W;Ӄ1DJ` |g-߇xAkgFޟ HwEɀrcrsȐKŌGw&(dSD` P Y eztkl TVA))^𫚔O3";';>mnsp! .nj,ҹn̞ ؤ/x*).<[AHCShNUd$DdtJ֙x(eI Buk.0*J?gr M44BJ*U!JWfMjst+U T6cqMڇ/?>Z$Tc[[ߠ]k|H#1aAqt =3s"!)sƒ86Њ"݄iPW"DVw e"yUB2kʀfhMB B} &0NG@P%V̧zkƹqR,:a +! `pnhuvՈ*KB{w{_n듳;59m ^niƍvDf:ôeyTWUhf~א(ԲerF e:rP_]X˳<揈!ze){~#ũ<mpo1QRzqӘx=*XDAm`%nIc!ųNZ fHmg5)j)eX]UV/3JqDSUIQ/0M 0@@mھUtvm @hュMKH.Be┤ 98 (yta}@vmav7u+ieZi? DyL H /q&(|=}>WQ)ʥ8wG#3NIܳt;3@>YI>|0\] 9a`OC<Uv-zHS_ - :O<22 PG.b-S AKOȃ&HU=/ աZ+qTn8>l>oJNjTpf!vB$Cdņ/P" .4S T}Tr,K}Dg}C֖ݷ%d0Y@%]lk3:F,Š\"ZpeԴ bZe$1 EqYo#,%X y꣋h#(5Dn_$K\J$QY ?ǢT <ߠ4]Xj{Z> dޤ\ @yY v_ ~;4,KˋkcIT |Zp xue/dm?gqFeC&#Dz9ғ%0,u~x!SX/+D+ {]ܙj/z1;ƁQwVeLxѰjZtq`5 s:_:`!Y&*ɶ6j ®pxF3>o*U47ZT zV eܒ!LCvb.YbZhЙ {VnOY*<#nkU&)N1;;V^ܥcГqn3bVxB}s~ѣXUsP*Simvuɩ2a03E5xk`w^|ȆvQA=h]D|){u$ZT"sd (L2tCU==OT*M $]5m{RǬ sqnQQ Pt+-/ޱ/.\QZkr$Y3-ØCY1`(1A\1V_1 v=vA(;z]g]0Imo`6p2S`Iwq+ή&&eb]s cc4A_9&Vkv' 3)?̝Ϻ KѼAtęNbf Vgol*B3㜰9ާFnd$*z?BƮ}p0fX\I+M;*2kE%!:&h;t3+O?x*R(zh[6cW5R& f%*U@q\jq n+3A99ΛF}:F8y7pus = YAřX T >ЗU?5,F"$W`1?VK>ZH gxYm .+W-d bb|VAvJHg@<2KsŬהZMXSqn=R.l)(p#$h]u8~a[OI8_ IRdmf! hiueJ턐sVT^ߺ4Vou7x`<%)?aӭS- h]RS|g6 dZyC#/䮔J}dV^ގ5"me${{i(<%6$,'\9q4>rSGZJ֋t2ډuJ2t!_a#fGP6Qcp1)ewnZK[6ʲcbrb?ΉfRE`t5t9v{EձpW7jqy_Ro}0EG &hiN~uzT0h(0ͫEGVNZ=L 4O.Nzf7"3섆ـ誓w-;a3s4'K.l^Q|U^n8`aNB$Ú%zH^(d*.(ghIjL[$ xM6> ę7-% F6k2H°zwoVd9ϵ5_SX;y!: Yo`#:ĔCC#ɢ{瘯-Z-}%'aeO:,_grQ`PMEBbt%0: "V8vC.Ύ$]"3LoXf{:ҕ?.@]Y1;Ӑw4K7 S;C`1(gڴ*d"0a΃mtƬ:хD]R熥Sl>IܤxXu)9}3Dn\|0s RS?,sDx\d{>oWq~Nc!ut[ذ1C,vכlG w̃Fߤ YL $R*dbjTLTy!MwI(lM|JsHBE*VqaP@*a=w.Y۱%%*G:Pms~*$` % -ar#H_4u Dy&3A|Q9/6L#ѷF`^G(tRU1LA jy,[ WmNJT# >'+2ԿhO[2*r4a5 @%` &p0-y'fjrBKq۹vգK8}ت#(]l9";ڨ\J0 36Ԍ8st:e.o+uwVGqˀsYSC,GdCMe]hUtOK^l.p!T]ңPB%#xϡvvpX럺?n;!2vaK =KjeOt[EO$caK5{aj"tsduZm/P`YzE _NTL属4 9s|Z]-C'q_l3WtF5TP[,.(+lrbiNh;ה@U hof`7Kz-{P) ƾL(a}L|mVN"v,TZK d*+bE-N֞!"!t)˸>t$)mԪW>r9d&-?ȮӀˮ ̣]Ԡ87lr 5W Qr!=&o@])M\JF]e5Iq[$8@ŷ)Ӂwh }"Fԍ&l앃CZI\xkELV5+[#G6mq)aq᯲h˵+>5r aS6wkJ(iAyc%E:5!&bOԪ H.l.X:dۣ/HgX \XlO #;`Pk^hQC;kHѡs,Ɏ5!w kwy9Zﯟv\n]ʸ4>6O fkN=WS3PǓ-;T / p_22GQ1 ϑB^閵wKAy{l *HC,3ȸpfi2{O=#vS|[Tb)M$2] \v D@~Lyc'6htP>ջl FVʵ4ˁ*ɩ/>Qe?%})&zEͦv&T^ ك:%`U-l6U~Q j]cNvb GsFIø@ͭr= Sޗo%i`h;z|8a_ʎVj(J>tB0y9(.XGvCk]N&(`+G=Ud=ńCn\T Ua e&ݷ#:vlbš Tu\U ';r=u0ItN#b;j8 ȽtB\Eƴ-Ba,(|K>b㓖'58vW 0Ų\'a2Ė%6)9Vdg_~]AH0hxgkD@<)"9/7@Gև&q*A4tOD|Gq 6j{mDzYn <[Jj[e3J ̱ dES,ہ+ Er?[hmэ} eFJ.x4G}:9c*sӆA|oPA91ĭq سQ@_ᄧn$L|` ȸ/<XFs/ *c9sT3B6L~;[@v]e9;31Al?4co-)5GVBofՌsEWxR(d/Ss=]wj*Q^_2ʭ83[aj\e4+n(ϘD7D3\m=v\sY4hòdSYL9ށWkoRb2UFõ셠k9 ﻶ|@ =2ڳT<9~V Oyn8-)b^lbh+mJ P~=2pYͳX\GvQ;$&Aqɬ$qevTnBN|&ASZ%? e,\AA?LﴐY \ q8F+Z Zm뎻"ൠ'5(+lOv77xfè +q]R*ONO@E^>P17˵WNhn##- ;\6$5naw7 ".&ţ/̗v:AH;w}մpDvMaUǸ> ЉqfzM9[؝К&8(ƻeb5aB˿M 4n3).:'ּѵCHJ&"ν~Opi|?X^-_3)#|ERbr nd#B;d4UNy+o?VIi;bQ,p`wOpN$Vgs;m@ k^<"ϳg4ei3O *nĔ;Qic)LњuB| ]N^'#}lYRD9F<k,Fh?+LFv=^3ouAhoHwzD؆8D% \ 7^- {>4?b& ~1kPm}+}}6mf;ݻRe@̶ c&ZehL,s8ܐ*ycY*:.dȯm9-Inƞ]PUyЗ-Y[ΈA|Nq_ZA9$;„[I6h6pFlj>}:ѝ.oO"pҧAo[~;$YJgޝ-pBPگyMۊq2 xuPUBh8%n|9:^a3\t`wMJ*eG~86AZk݀l%yIqN۫ee\f\w<}Sqx&Pi?)B] YƸcc5@RZ 7f&7RAp_oh:{9 E)u1p,1Dk:A񀟡jXk2Qa1!r1d"~ 89͉-zEȵ=ۭS, zH@Mua윟K` M%8 xFj;9)erT01xs(V2lh.B\bx@V)( ZdvT9KqX(` GuHȃK4滫 ,dU o{)$NnV5|UQL #Գ@~=.?15pw4k܌nD[$8%"X9 )x hF5 7 ob8% ԀEӧ Q'u"';:tD zjt1:^Ut;f@cN*Б0*gS_IہGA7RJ %F#fWydMyA$Ee߬6:O\ZJsA5 :\OWzVUg 6""? eC|b(_.g.C6 EPfSfկV0in3 cXeK죩I|"?rpKd25C8o iTnbj>c&#|e&;n'%TR~,[ۇC=(zEsCɢn蹭1}xQHxOhi9!Tzɲ?1b~GR|-dDz㼈7"pd^]iZ:&KD,أj+3})1̛*lqi*iPzO|!ll~&GNcTAG*0O`}oŇژNo4Tq…h 0d/*qt<>W_/ \]V/'Pǫ'[AHi*iqຟmnI%϶45 bC׽qXKb1B@e31ߠWb;_>+ "K_!KՌ@EGP`IIxvj=h!eZ WvfG,$< dKPΗ^jM`dj=m5RgXNk VB""݂ ?'_l[1r~z#i&1CdUHӏ(1Q ǕIe-ZO" RF0 W .iHPWT ,.Aiv'P/d3{ wH@V3TBAD2r4eZoTrk$4weպF3 1y<e헸N>estqu?B#{P l)&rc)< N?Y:`)잴e#.sx$@]kӵR#R1ScCBSV,uj>[0&QSd\QŒA8f?=8Jϗ>~  %e|7ߺVn,X#Xw-ymA*7وvca'YcCd|ux8N22;Ɗ*nql'^I@Js@ VEk jVyFgQ$0Ge}s\ܯ= 秹Y` W8/@DОT_]Lan*GI&*Yn:S`V yC]_,SE.Mv߅I*g=PD's>iZ*W$Cn}-}μZnk'V0rSt0a enمgy+P -(( 1JCÙ췂-o}N굤#ązUΥLl7kbD|9W=PmgBX[\^HwLn< @!5|Vu&`tPSu2TP]j-B4Jps =mo.Žo&} ަW Pe_<؜yX^/a7 $i[\ =:$`<"qh!0!CFuAS ܵ>T?\DҌTD6 z0`tgr+&(SIuѤmH*'2{zxw0P!._)hs,-L+^Om7*a}פE-oZLb{ο [" "8oTQ\9^)v}'Y+ #L_ 1"sSHeGA*tcnzm]b-nw )}o"19 nȄ7j@s<$$51 m21䵮c~y8:G1PWnb;jʜdvshnWMfLa]*O\777Rlߞռ}g.l#ۈ8G$l ѳ-vVLO<r@x&RZ )nva+yU ]U͘+əxZV.T'9v2䧓RJ"ЍoR$Pn&6LtTm\ T؉W@hB ~ ״v}f\@ i@ۦdI-\nI~=\'dy ղe^(,QjF5Bj1x Sߕ6JSa;n#1+l:F HhNoir#p]IofhGx6jWs}a:Є:T* m04fe]NAI+KRx}׳~,BX1/6_Ӎ*otExƋ,=4jaHǑdz!4klql,lhc99#-s5%&(X搑A~ 8QLVy$>Hnvlr#"Nj{!uv厭kvEd*lBI0g/mU8B}8;Z@[n'n1)R >kK88Xsyuup \jU[' 0e@Tf^{e:LrZc|NMSaʗ  g؟>K@uKHUS`!U氉/1OQW53*d6~`2À&b2^X`A/sjA:2Y:25 2RX(Py*f"Jx8r}RWYXTt (%1є&PBDq5Ejc3yNHb0BdצF8[tR{ZDS^i.#q!;fHV=< lxO1DQʸ:T}F oVnorVF1vw_'&ӻp&@/O x3L`?S}!:_BUDYAq_HZ @qBX[p",=cIpH]u/EE-tfcq: ] 7=֒!\ÛqF G:-'͕~oF"5KnѾ$S%O?R?32Ƈ )֪[CirљŌ1V"=f #lv%DLj"Գa'=8 pG!nY}OvVƟn bՠkK+Vmi/y: .M ŞCu w?XU}eZ@RYpB srq->:gQ) Y8*4okWϜ;&cEE|<6B_ghBNnK6p;L/@Uss:YLP4_ityԻB`Ĥ2bf Zo3 %c3oYvJGEkG-dN)蘂)NZ|)ئQM )k] w0b/؄Bfu_m@<9ȶTLPpD8;U~Sc,%լ?j M0f m#G"O!B_ạFbmgn%xt ;,"r~}*ro8?]5U?/%l){ɫ4.@ކVdE5o>3=?8@.SňB72AyVu"fU$ȕEh:Jj%QX:՛B3MHӨm-X 81 ǹ̑Vu2뎫&K˧m ^ L(4Sor"\ms@Gm#y)cL#c%B ʻ 2!C3%u%nqESayпA]W!Z>WPF0+8{ծ_!U%$&~~}<ҎX@8=N{)yFCi-k0"KQo5UH͇g*i~̞ˤhEEwE+lfgNr]@klyY^,Q&Ӥ0Ύ \59Y_KefA؈!Cas@Š/Ն RHx#Et?{U$*Mn^`Hy7vf[UK-S ՙYqA ͢A㤥fΫ'`)ϞHA?ū2&4k22s_ĐX~B*jDvVm^8ԛ~?jK\'~Ez^JHz+/i`s†7/vE7ᇤ+S(~N?^Ej1>)YxIdhmfp~IݕZd3= ]j񹃆G˜PamRQL`tmKTUe9@B%6k(YCyV /l2퓟@aɓl"ǭP7aUT r䕍ӊ~BQX<*}YeyvI_1P -\(H5c þJB y\f9V"n(P[oP^ra]_•ز nrz=?itB U!U$ )=RXQœWn1^ru[2:rL8WVO = ~-;SъI%ti*BDqQb ɝכ)s9'@x\f 0raP:prF'iGrS]u4?$jZ3ْ0B`Fx_ހa~TJ =_~'6׫ab/憃_Ùcf]쩽gNK&dN,QW>L`4o{P3DK* 4TyCҁoHמfnUP޼:E RYdG-35,Z둮T j*fNxU:~+!6*rvFI?o6xp68k:͠%y1θ4g**J'NAvHQw"FR*IV|&):uI/ހ>&zt{'^Y;@-w";*Jb |9Q̏Vlhȉ}*x|}TY@ 겭mEq\*#FkFK.~) ~h="'^ GP3(m󬼶{ *MLL5v ]v9 8Q SzRp~XI֙ɖ_)hVqnT{MN9|ɳhfaU x4,{}%?!o)!bzKWjVxܬzVmf9^b7c T[ .Y'B)A94c&,EzO+-1Z0DͶ )g:Zs\n%e'lи OY{i6YbҖ_ hR l#UɂjZkN @7,xd;P3f}o  pn7?d27҂ ` %4Ov;D̚M(*Wy<0?@ 6jMh1%'1D&rgb[X.%dTUDӬbCn=@;w ~?{o0ϔ?f}Y*oX^Pp!8tW$U/< 텖b1%+ebJH7Ja5"9ڌ< P-ťC/f$iɏy RҮ$/K(x1ϒFQj%aPPF)yת2a}g49s ٓ!s'OQƹG_VE1Wu_C> H$;M!F h9j\|Ni0 8Щ .Ɲ}q ?~1aCңQ g߫AR7K]la'~GWA5~H$v9۸H ={iVgiݽ.ʁlU6oe-)t!cjO],<"olUc ZAo|/(s.FIQ7P :]Rٶf91sU囋ZhU 1bY7_* FvadťƷk!E0¤MZ1zf?&TR|Z Γ(27ػ5J)W_{6CFn~@p6eT1:UR=]p!䟇KKVUǚign<ܧEX%}em3f6 wO@x|㺋1q?9+N>N53`ʡ)2V`dU.Gq8(& >!lU}bmAvIO#q 4J&%W? >`%m(T"x-Ywߦ{:<k ,&A6},~cFoԈ{3z[>TNKQJʡY5aְ& z ^z#@wJ+ײU,bPt N h%cyCP[xmQūBR|Wu2& XNkS\ fwJxhxEIK0Yi_J~Vxo+}!^w*@iuus|+8&cd3ľڠt8nw!_'=z5CO}!mɄB縅xda7v/FsB/?bW JC yv+r=ߺWy9bZgSWOXbq(qhNxWJ$s!$WJe2\1p,pgp nj*nMLG-;,~ ڏfua)L<vm=%(,n?n]PFq8rrX7ue͢d:BX_]&wc~8,PY^6m-,]#7+9tHHpVha@y]ӣ#|b=W66SI>0Wto=AR :V{߅C"Nut$;yk,u{K%V~ Q:IgJgG[ܾI̟C0115?8T$p`3le36?uw+`ث*P\$!!s`žfDBp[jyQfK a%dʭp*ᛆzDnJD6Q8L^<ˠe&xNQ7/fI:цa&gJpf.=KW?Ip'ۿc9v2s8+)<ɘvr50ъ ߆jNMY"k7@\|9/ >|U/X1vk{UnH[r*YkQV}JC.+j'jyf'9a*!H,}ola63S/ ,ŠGpR$?X* #3 F) MIqW^d ˭z&4W|ޜf0s\h^}V< t& |?WSQ݂e $NTRRl3@$2jU 0t,zM>߹^͹VR;[\ ?kR%Rneh RqGSz0nn _ߥ!pt):;{?BM r[=TMty]]13LҲ.zF]O+% \>I1q3J1CbHjuj؏c|2~F+ʀ.j*2Y4|A0H,re9|v5fjTMr<]yYKd:_Ǚ$7"EfzO6MMXPTtgfxg.0U<ԥݣ\Y~J3rRxppHl$R:Ȁ+~ 1%Q)7q+v{3@E.p=z.*' ]l؆  -j2M!@ݷM y՗wTX?V;䁉 n\ǂPcO[ dߩ3ݷtU⴫(9z`ˈ:AXp0*FSZ]Sn? 0+7$l/v2Zfo'2>ffN!+m[aRF!|Oc׃<T<1*>u;'r6}՗q}ğ7VSb;G HrL?Dxd=C[ʪB.A.5vNeXNg(I,ޱyn#t1 }|:d-G/. n1 v; qD0ndT^ #OZ W}C~5go~ɕU:1Hf~@tfPLeYkU7)EI;j:QJ; nҪ1zapR["xKli+v̪q?RuZ|"7PiCpL{η4a!3>iC׏TD߮kz;Q8ԑ-Mˌ)ƽl۫|F!^T'>dsltDw^ |sN֚O.!p%Պ{u jC }5wgeIL>麬jhE _t aZ0OCSFeL!Q o}aNunv]w> mJLB+ΨbeMMy B0t)ͭ=6K5>i4t1W}6sDp?4:pV%'BqDaNt;O^PDTu5N'hƻʨOh^ Nx41oE ,jmjŤH9CK]?;n/û& #Ά.PN5+'OpmXRC!yK2vO4< SRMr Ug9GhHA)JŨb_%{3I%ݩѳ[UE۩A\r~`(kj+ /i`1\!xy߼?L}]C<Op;^k@U!Yٕgkr9 cbGu-א 8pMq`@{#2G>1K.3PA1Fj9EQp>ን詂i0v7YuUjx[+&L(kWZWӱ6D8DR^\.FbB:l+f<•K⍟W)+K$fMIf癱:Pf_&VkhuR/vrՓN5Ր?Hoա=YhChG1cB/\II;pYUZFVҹ3 sKZFHr@W=(h(9o?o (<<&f9/gr0EHJUj‡g~H xBa%FQjd+kk|߲VW*^ Y,b"$zt, U1s+X0w瑱ڿTYgM8p՛\)\ T6sqJag_ܭC*]$| }N$@ u]|!Aߐ[#q(Ї,zVEUj@Uay҇;bL?C2Q?Hďԫȧ3qN.G2o% tK-f4Xr~T W tC(ycw&Psե֣ 0HǕONx WnފpKA*bUǻE#"J785TmGVKA~д /fP dQ_W wwN]SVh1yAw1oKauVaO[$ }x7C+ nDG`oЖCi6T_qH@[q⚋i;mGd Qd/c %.:Lz M׸W92B~ Sh%AIzEz+<~Iί}Mn?Z|BN˿,?j~m m-ҁoF~`3)i ޓ]2~UiQ!W+pk$;`?[ 9ZdIrS+༛ZACt| uH78O ~pپf<(ٳߟ.U?`  4ƢhUКɞCrp[jA ݽdB1:-(H huR.c@y^{=H!IT'iYdH8{%u9d?vXE)H2bW1UDQ/Cxնu Zhfj|cP+’77IG# &aT}MY\߆{#0{  , 7|aUUq7$2[v*X *Csp#dYc²cSD0MC3E 6߅|殆q.Pj2Zﵘߪ8$\*_7C' sWא0OZBʼn$%s:Ux +,)h jq¼)Q 2&@Z(˟(bwբ(4N9+;VL 2]V K,!E:o}!7eႏ"xmf_sɑ/X=筘2:Rm/B5hӨ| Cc@i-WJNT^5wƩ@%uCu| -ltU~|#bk'5ͶJz>G_jۮ:'/Wcoa<:IWbiLjeua)i,mz6l0t:+f5v)zoc:B6c z57i![꼔F MxV&0TudWt LF>f`hhtx5@N2?,*QSP^:q-c D*zeH==Mʴb/vFhُGׂ axwKKo~GEOMY&qmkf(ܝ쉅6"Ja;Qį7pni~~s {!=**GTDk?a^‰Vs$wZ Ip隝* }t́ l,j}(N4!tV X~_Bi ߙ?RB<@PM#*ux/m:*(hة11rtsEyޜZH˷1v0 LfQh+I33HXW]1c` L~FwlTM}iqpupn#t;}cpN Yj%k2:n{bm^tCyu@PbQ\PܺF,6Ҙem0eA9uhi$6 olS{(]D~cf:LU_aJb@>MX =j)In{B1 B}0i>F9)΃W}zS!$qx7Pl~y6A̾iN:y7ȢD!&P:5M%ij@MVHWbMi?Lt>Lg`>J#9(2-ɫ2I8D1cޭpuʆo~jo9y:qͶbhT0}- I 0jwO7sfmiRP- nIl}b,r/M8Q!5R5tpt}قP"ف{c?{E+g_y4;q$/l~$)Q Lx_X0 (ə1Q:/c&N_;GPz.V# . I ,GcbJ=$.C AiB ]n_@ e:?MrxborNQ֬vYB.6ܘu`Tѵ5}#_+x9H|Ē 6]jh<[mKxsAO F#H(;l:(UY-(3e4Oqiᩥq|]Z |ja}v˴$",Jۧ`-}C]}\* ?m?Lfnc㟘rb]B;RCyU6#:= tkoG#_āuCkro=w՜Jn~DŽbkE뒹F<Q dƥLx? {yRn4e v&Wu~WWŒaeɣ˗Ztdj!8Q0zzdo=PV#D-vנfۑ e~/M}1'ĺ_UX '4 K_nkx'kRNC٭ eUGt;4k!j~5mZX썢; Ld;TDB|;2S9v);R7g?'K=m5IO;_~EoճpÉGo;`;v\H; 3)q~c}1 5zӋo^Y6WȼsVdέjxM2m ~lW qb80z`N 2C.# B?HVw'w%GNb^ Ƅ[!&7_:oe\)6jC[s.0^U{X5cBRZTLu)bC&8RPE4s n_ҹ4JT`B"1*~$T2`(e{<(;%Ҕ<]&kng~"^+ùm.u;0J> w~%m9gAl0;s18+-Jlꏰl^C]G/tz')1>oeAy590e="S 2{0+5v4qR;/p:Iѳ@H#HKdW.kdZQ UnE5+`q@*M奷\όk"ή9UI+iG2AUN+d3]6z/uǜ#zTaےߖάc쎐48[e\Lz[bH-w":dΕo(zcDjxP#W.'nPMI# F~r=09t;+t$P &' zT +外p Km@[gbI`$(ᐃ5<ܪlP*/ ɏB&*$$N~9!+W(w%`&Gl#:?NB8!!>>65`"Rl~Vf ^\56-uDV&I SA98大KBߥ( d-:9)h|".kL*al6XEcc9('ٗ} q;K%/8ښƜz$\&G@b mQ&CZϚ}П("8; յ"|١R4K_,@Z` /{BgֺⵈfJ [/sʿx6KIG:6;OdCc(۸*e"Sge@8ћ肀SW=a[s̺%ݫhCYҸd^o\[pmKj{zu6)"+F@Hh ȑd^3|Ql  q1wM2 _)8w'also')^MMęZWMMQp;KGoV0Eͯ7C"9Ľ%aY7Y&^$]/ۑ6"$bukˢK'T*᎖oo0*<)+^PJ*¨:wnziHZΞk2m6$BܮZcrw `Z%&,)3F=8TY+2L PaEB^yr2Jwr| ~f?OsӪc: ]#_~ТeK~g5T,zv&!we 859:kJa+\@^)@jIT$}0>ÆVJq¨1{Ih폸) zՓZ ]}p5_C_/e_lQڤ*7g"qvx'tbGvm"_H1bs,=l;PN$y-dRzG1u=#+BRQ1ۏa8kö$Z]l⬿}kȴH9;1Era85v%ia?bxLg\&/[J.~CveѴ΅2{rm0?&~Ҍ(0D.h ( %' Ȉl,1P>\O3iE-tMg,_Y֧Ap6?}b0Eo|w`m̤qMḏVh ƭԓcL1 t+EI%aŒ0n>Ų̓&8ɶE8xZdzt>x?ٜEdhUk" oAy:H=X@cM׳=8G|J45@-pÿ֖ 꾍Gk@[2z@iy})`];q|[< ˗6d#7s09 eس%Rw}ş662o.$lڪhl}+? 8l;FʣnMg t+݈{Z/B ;>1:ƍ[Cs?fQDFVL *_%es*ַ#_iKY(Ի@@EF:̽U4fņq"SS= Q4TySe<0Rͩ$ ٬HW13F-fdAQ1xr1gf[l'3a v U7v\/E(wBVO8y{Sh63 ,B?Z(:6^F_ H K~YǢ Pm#QydSD[ (a0$ltUz(-zp#TˁX*l|*^ =iD#C[3}TgrͷK#ȕ"Fc& ] CƙzZ%8VsMcy]]'}b*ü5  JBOt<3N?R,{^"gde׮fK9k"6wX8x ݾCiKJ*r3lb*gkgnƙXM.`ۓ?䷤#&Ve~tcؤu%CT{TKLg{ $彲:ԜzϪsE`!JZ=k$~8bzw늯Wp-$8ݎB2͖.wcN(;?T\@iBߙ|TAM 'kZ]|Y}M^q 'ma .svċ]ywyW13v{dW8K$͔A4b63u)oXv(.kTĈ  $$_usHᡁm@`ʵPU?̉{I'(SB""CEUo d@ӡ\c~;nR<*mȊ`ᩔTFޓ~stqgG7xpR'}-nm$\>(RY)D)9HYH6V5ca2թǸ(/]SS@]uهIoR^=;bKO.KDx"_^|ؑO5ep9 "!0o\i6__R4h:~̰tS"Õ@Үt(j\eqA~ܔ5=#`Z<3FBb;s(7Kya'7,m~@x=zkȘ'wpMIfŹKnP¤Qѱc8m"oyX)(/Ұ3"!ӮpC܎g#ZC%6gQZ HRq 4P-R6oVex !^VJtwl_&Sx}$n ?iTΕ$m(dx*diqќH D)AH\jÚ;* ُKwCYjEJ>mBE$PʼCf</Dm`GƊyPs ~(-[7'fV$Փ|: ;:V,YNJxϮŌ\SH6Ʉt%@'rgf֙e)(KzKIsI8Z~)N⎿, ^Ȉ} u ^PW>ӗIA1O!+2 `c2E<@G n|B7l|$LլЛ\K=ۜ n%SubABmiRó~W ڦ']Ў~%Q"b{:?Sء$k9 '[Ie) /6y1sNRY⑻ߝ)Cj/c)!5~apgIA|2gJR ~ɉNHb'B@RB17@5cK6Bv$8yK "&K,M\\)fWŠS 4_+7~y8P,XП7(JB(C5[ye;H\荦$"5L@qjݰj饦:N,7 )CZxDnx 3́fׄmh@🖷bgP''Ukk}ʄS&j87Y1W̵Q KGھ!e=>*Vqm|30ƍpL>8O%S4bZOF̔qWJ_ >ptv|v7o&Fĭb==`k|VE;d7^^ ļFY1#Ipywm0]$ !ɣ=ifۖed+cTĮ5s:y3$KeRzVd&̓f+4YlMgʹ5 dpDo Zrpx$1KGL⪳pw;-o]2M*9ZCZѮsȠoixuC#NqBig},~#> ep-5|=0,;7hnn4l.h9wG f7g'әQ)6QPpfYguV1XWՍ(#b=KOë#Yℜ(̧qxwpK3ME;%kC>rٍ|mѮM@j=_:7,^Jv\!9lPPX[YFTF|5^>+.A5 06,޴p!b=$`RJ%K%rr[?؝Ĥ?y2/I/af=H}jAY^_+n 'Ws)sihAe嵞И;{)P*ۈ9SkKw#8kQ4S B[b/Zܤm9R~I[gk,$L69s*$LD AeYu+p8^=󡃓'5zd}ix] (h'(^D u>a&ԇ }pkSLO+1N@O5kQ:fSF8L”3a$ΨTT.Ǡ5Oy~KG'B ު;38A5C=v3d$\Jwza XQƾ8@ VqZ!:g9; nl/< ;9cg (#ڍ4~p{ԬZiNF~ȗ58.{2Ƕ km!ĠvY|й80jN>^5j?iKn'0'Se5CL8#_|J Չ/$r$_u"!&WȧR0(>ҧiga\+-Ϫ_qsvJ 붵 Mn1i/bxSV:_1Lfr*&mKH 6?i6+nlʠ,ASzMt&Cr@:_13WMzm$5%qq`!4v;X6NC R},|Z gd8K!~d骖2)'C%ps$Xtޝ(&i&?&sk9.bV"辵ɏBRgi"RO! xoڸ}'Ee]c K")po4p҄paw:ﴈڽN)]aP1_}>` c,8UY3cN-#Op,q@"rJ9peС"*m;ZB}XU mV$Z{cf8MWv M0 $j&X?Q#í.:ɥݳ] Of״BVDD-> *-$C1R@6э#%m{['7udWy_!R݁1'8Ֆm}'&jr:"IDE2(?U(`&dWnϒ]r2vt27ɖمi VV4F?.1]]ɯs4~7.CUgR3}+pI)<[ŏKZȰ*XM""2NJ m&.YIǀM[ lD`mԾ4I ^†ZVUcTSRܰ9sJPE{;[>E4go;8IپO f&}o!U!,d82l-D)C*β=z0E2` m$GTMcCҊ܁gWD})1Sq6ooՂ5m(5aA0`)*AdLJbsT0)wswgYKxI'%}JƸH hֹ@qr$ Ĭ\ :VBju>]c#Ǿ[AeЌȜ=o2EYԢ`XDpO鬳)w&0tC(? y?^I9 9}aZ@/PNWYy&CMpҦ.G|mWtÐ&CY*<5X ,ʄD/9Kxz 8پ ' ʞ?]`]~jX]a/JJ')%)9Q|$u_q9XV̟/H[:+o^qEl9eٛ%ű8PsD?pąDHFτ7"5s/}-.!j!n`tzLdiɾX³.` ߨ!kY=tmx`N:DMmZ8f|x5C7+5HS:7FuWеNkdq%Åخ5_?O'/xE%/6;+j២`'iUG6P%N0|##˛J&{;}2C|Qtx"Y35R hojLRdZ#de7(d=mA~g^% :e0xzBa97n*P˰Zʼn4GJfqƛw JԼ! WER&rHǙGPi) D<xdQ14 GKHvbey7?A(d*6*&_da_^чyG!Ș&4/JZ,O- H}+acZbr2вp^L&K w YT@R濇fu9T1!ۮ;QŊ3#^'8Nћ; GXZ('W7Cp?rd7{ & s Eܩ?.bAeL O&9ZHHx11L?4xwpǵM ͬuphܽsNe в;mḚlsC3D1 ^Pj@״AHW@O1`wzIzKX9Kaމ֙0Hvkb46<Njԡ7ezy>|ۅ~2bnX[Rc(D+e=dPab]6r_Փ`YO[7SKQUrp.e(,[DAN)zgn/?ʣ#A['g !Q0,f"l/wBCA?bQ)!VnytjA޻HRҦӻg=NA4+0Ԟf'{soO= ({]{g_WEj  /`0]*($Z37.sEėL7kr?RSe9NH@A# 6(j(KF'oGT՘l EDɋ/Fŵ:աL{EؤcBuL؈~}y(JֲȢVtOS_: PNj kt'cöC".'!%U!mA8~?9f)Muj~Sr3nKw/)U1iX.#v [Y p[Bu9x[)nOgn9 E{&f #BOLwRRCC;E(NRKz@"e dɮ }i]Jc6=J:L%Fbъhb34 :SfFڬr8Od=;;DsO E64hY}[Qߊ!ÆIIYc`ZJ#U^t=b |W/>LT;zĭ&u`Uae-xG/vl8CB%}ռ68rs͖\Q#n\4Kj@<R/8}B̧y_r)oM{V 3[UJTآv^ a0gFVXEfO!B)sv,mw pP>ɷ(Al”Q-\@:fRB2y(-UqHcGЯ@!դ^^ ?{W*>{jk"/I-33@^Y!{hO: e Blp~+cjHmżN}V_%G43&;ϢA,5ܫ{VN֢r$V㪖#+W`81q;2܊3/&Vbi q?N(Y052LL&$a =+"Aezm~ڭng(…S\uƃmuı߄aS)PZ՚Le$s;xPçv TFX9i=Vv @ ̦ƠtD.amy QR#?h#RlM61%ίzpנjM:&̗ͅy(JEĹdUF`ہa|;oxoziyU}6 j&zzg? 2j}2W- dq%n3oDxgdYrۗ8 fm'4T=mx; SӱKg`M~^^XS+$";8o-~NTx!\e5:gcp"N?:Ou0etd77o,٭+;]DKI)ES:kzȨDGԣ!>%|I_6 nDvHwƱ2@3 ? ӷ=b1:TnTz%3;V:kCµԍ}d7Pb- f;2:|9eõwR\~O0!﫯E=v4cE?AV^kȭaR[ dWU\(;K[@-ʌc$Liab4$Ca m6UҼ\ @ Mq |쾖ԒR$yI7XkO6P r:"L5-iRڠqѸd9c:\E \s"T7wzwޡz-'鞩]_qR!zo;P;01]!a6')S zl7Zi!0$}23Zm EQMi%z2n o" l"qۚS5fjĴlhcN}5f@?@ycK\3FN}Bw䦀^?B;@oР&ib̒YJZr,f Ck 82`C3ݔLuy`vaUaϤ^ӵFhp )ϙI1m3 t P(Cт\=/k#Y'?5#<Z%=j]+ހ!;g]Gl]P>ڤ|eꃒt#r;3GcZUy:C1{oRն۔a%8۞:Z^X`k=_mO\?w-JhK0 |u AVN~pJ i_^?'0e}8,@(@¦Cej&D:s;.#M۩V^4Ye F lS<¯cK'K+h(W벓_z?i HE~P*X$k)&+t`s86GzA҇]mv,c LV1*hzһs.BN6I\LuO^^Yƍ(pB{ņ_?Q,_fy>Sa7@ =O/X8SS_r(^fxr45n{]w޷#t>uA}m ȲZ藾XM7]Zq$A7gJMEv;bVD,ε^@ Ǝo,Z8 2(rB^]3!ڴn.8-gafНĖh*vSaɆS&{';dPRucoq!;;ZI^maJ>٢HIں[UQ7b<7m=ЫbD gh!Lr?)D1JI[ ]i'c t:SQ o+ań8ZG:>Ja  e/h>݊%4bfhN6g,/o{  +daKk`/l/o-YO+{_`8iM2^mCᔣ/ _3Xv ӫ99MU# [a EHN_θ|efD%cGnPp{V!mM{W E:sy*"R 67W"m X.ȱc'ezAj&1<:p䭕nIVWrgڧ;UI,|8|HOgt`JV ;fG).dڮr:3 j'U]kJ}]pTTElWٯCfPݵк^,ӕ__k<6ъrt;܁8})Jt" zּr׬ ;+lANH:z4O>z?|mՎvuзcR׏Tv66{0wA, u}1Xvćy}Cˡod4 eI11kQB># QoߔKh=9ƑaL6/{&'Dt/arg'ܠݚ1NFJqgUП `vS&_3# Ϭ{-j7u]MB(ip@@눨jq>vK,yYn|2z\Ӓw&Z=.ekтA4iK~0봉Q|e&zz)DUF LlٸF2JRk8#`:Keh,`r1`jC2&lGصZǣIȗV}"Td)@9 }0]"Ⱅ]k9RU$W݄ D]v#KGNO>[Ua"gyg ޘw$ .!;E`!@!q ,(ɱ8na!g8[4˾ &hy5S0zV ψS[R3`(lzbF`Z0tɩ?MJF2zTujF;7$HriSC;]Q@fN1i{l>@/eXhIw.*e]'GE2b+]Ur߷erpGyJ'8Svk7TޢVe1~/ j0 )TI0%d2>nNX0-uCoT %=dc-9W mߡnU_wFvF# niB87uYN\ :0 Obbĝ~:XIk=: yU4ۄ'$"a|!ۗwpR$8XYh B筼oZʎtKZs"zz>|QK26X ] D3yIgg@iw ,P'wv)W4&\6ZT#~6 e*¤_bR,!tRz NB {_r'jMq'ϭ@ K  5vb3H;TϚ;ZVH4s@ho'3Kckz54?+ܗӇ~D˶LtRs1W|KFwF_[)+K+R #ՙ?'Z:bSFd)0S?nGxXgrU춏]+5f7Ijk ɠT14=v9p] `+ILr+ qquIX*y288qKpZ&ؠH#!aB4@{cdsS hr;F;{I!}pD74on0l Z^h-_tpJ9RDѱTgc$\ZPcp4;Ɯz"jrelJV뿤s.A=}7[4GH\* \ݐ B7#2M{Ԧn?qU餙pX9p(o|]26=S'bzt%KuZuPg1pco mNRx*5=_(΍~uf[6h1gJ#|u_GXQnڑϤ#GSƍܛq+ R^ˀ#wSTۭWgz7G*j|`4TWY`?5.Wni*U9zۊb=]D'68CD 0,B}Ұ,ۘT*]u6b{A胧ߢH>ٷAdN7$8p-k:4~3KtCԩ [tDlf[(!:d1gqҨgns5?Bςorľfh d^I7vC `VA/::巘y$zD $¸h&Vj+gǡ5t͜^ ƱkB5 t ڟ8MĴ^u٤VpPO:nenш"Y,⤲#MClK$$Y-<}iI[l9b !@Y$0fyJAhIqQ)w(*i3ZRAvSfH_3\k9[5"vk1h Gh?rL)A }GO{R匿]V4GˈfP4ѕWq߭=gȊ(|D)KN%T ‰<ֶ'|!H^$δVpFFzi0ҒPdɓfw gn&ç@٩/$”Lw#J )<~T¶N%飨 ;E҂)X*Oɼ 爣zU,CZ_IȻEsj707& )a~ÛnbFJù\jlr'TrU-^ʔVإV|&nr/}noh3$b%k ÏZJw"uvtE}zHOV Z< PKZLo쫊r{ZrѺd(W(xArJ~h*`u7+*X}v8Mhgn&ܣ^`2ѭs僨fٕMwjAE7l_ %{VO\=JzFu\!w>;M' Κ̉L!tow4;y҇iOuE;閷O'<"N(k&3IZ$N/p7.4 y)aZ-W t/D0}쑼9ۜ,`3 uU"؁qLm OgΛKcgS"^yȂK@by(hݥ4Bϝod g8(d)HDHU^"9ib5R ImGppqVӑA`klX(um|[:,v}H ϭeOrMG< v`.CcNpJc~xgN5—Ba)TCD52.§ hGR_S@*{s "s%5A,+R#%װܳZQ|HV0Ĩ.ޠܸîRәِNs p;/ǿa#O]LŹvFu-:UJUWAS~t&A\|-YP`#Zz@kEŊ4uPSS&KT]VGHq ,(Rq|@+_N'" 01WLb1YQcR#8֯Y^IljHKD*AIJ-@|iSߐ5_YlK ̬"==$^AF[nG^S^u~6_`Ӆƌ 0$9L"ت${!f8׈K7'2;  H"8) hI/ߴ=WH-F`eJl]ut>)*H-R}KI?vXZk<'Y =Sg#J] 3+KؗtMU!7P-Ƅ;ӽQ~O/s1R(cL(2Äq_:-#)i f?.i) ̩*a/A?cr;b6%0 ߄,d.^(18"[ )A/3=ibr_r笿IT_J#[I@_2 hrXVS nBIo5#,NO(.`4&H$n_P>S "#(l@͊=+^+2i`f+u,#L 6!4o:&.\^qo }0 O1` )&Eod8OpFyHwZd@l}pjsAz!b ŚH*SCH-ߴAptam~і A|G W 02F]טFB8q@h 숳x5WDp_zv2٭_>P}O?}S#ޱ<[hcHr4nP+ jOis`jWUXSeV &~7d[pri?aW ]qrU~TP.@K*2Ӿ;>GѮd΍/ke,%S=HI - |?2dTf\a>UlBÜwW:. 3deo{JY !0\igγ"ZL6H`cJ%Ɉ"\툛-Ѯ/p$k{Fk~.&aj`Ki1WڷҸ|^*1j)q o?PBդ9yBg,Bbq .kfG\[:gΔ>{6:YT4ke$?Jq (7Ц0%QN9Ό؜WxbkX({ TM'7ZS?&S,me|s0LV{/}vm5NyTP k5iqށ>T.Dͮ Aq@6NqLĘNcpkq7AQo#ָc=+lK /vc|bŀ.G5jo>v;ց]ih}p|yg·S?hJ|;5wK~s4uف }7)vmFc~[`ViI&߸fٞh.|EE컘dY zS 3ΩVGÇ> Xjj +O`"{$ciRA *Yq)<:ti&ޒL%#vTdH8 Ѳ$&}6kvA3$I.Y-quj!q& Y,˚FֻqaԄۯ؁uFJ-y+H]j3ނ^6J7N?'ld^UGN>xo\˹Z`VЉfsN w[OKuh;I(@~鱉(_^(n`IYQ]% LYطs޲މUR+F>}q ;iGx [U:(B:v kŃq@SϽZm8Y9d.ۑ)ܶ =n Rb,!%d'k [ 6W^t\Q??X?;T:޺p`񱳟/;]6eצ2L k=HВT4}%0?Ų 0P{G/GDx t~93P&PY-؞h= d/̈́h:Pʻy`vyBG?ǔUT>=aPBw$n)cJi K><ᏂVơOױO !/NxKM{T?0Ye *!%'ªg" 3;z$B V~&ڮ]#oy(Oa~;CO2Օ|a鍊 |kUwFRuj)FVqbCl DH{3#3E|J*HC--sE)(s8j(4%s~wJ!?ex|N5(jչyqSփ:T6_( c?ɦ =AC-k8L;ftY\JQOZhE+>WM o@2FYr8Iߊ3Tו\[wd—do=B3{̿8)$I.ZfJ~lvO/3TQw4H+@Op\; 2O0LY% d6ĴS颷z0&&ib.`Z Dwoorx'E7z,G6_& bUxߵab^g8m={;Y \{E^c ȧoͼ5ۄݮr (z9wԨ*p8`&wIg7k!`3p& 6@&wY`&r9 {Ut+wBY(^Zk-C4&3}8N,/@H%Q>Hs@@\,kcR89*D~s5 {† /6YRV[rM?cw(/Ai] ҄n<| d DŽ`+:+ 7PH5NZQ<,1|]%Ux3Ei:p, E3uƿ0HRY8ŵЗ| 8.B-rHqi$VЪ}]v'tr0}wy8,*S9 k)% nF``1] Tn5D+ci~UҶ`*3{ mQͥ% VHw΁+ _'?J|m}az2vd{'T3{_H'ַڥAEbAeq~Wj=?Y ů]jPϷڦC=zx[U$ɲp|>Fг)gIYtgU4BP›GO:p]]#t*̳aq baѷ># _7O_}^Uyhxb:܎ nnElxH޸ tY`^q1t[@H``gBs6O^k}K і&uD(kA3΋6嬓.g (ue:,4'Nxa3N@=?IK5Dk8j nrWf)ү6Jt8[D5Æ4&y:8g\Mm##_OoF yr%IʦqLޮ\PϣGEꌰ#zsI#)?1vkdM8kGSPu$z)bg.Xyo53q)F1+3$t#A9hVR?;4Ҝ{+UE?BQoN'kԲocevsQ ZL}M6oG9I=ؠ#7/Z~xs/^2g]hvOab;YCd̍,.UTԯy@45UZRI/y#w@S IH`_a) e_OI95n^>oIt- # S#L6r46'}wAPoD횙A:L7xGRzDό.4L qU`Z,#5ܤE^,sTi나aQjÑLZKmbE ZM*ddfѸ'^DϜ=t"u (*wp@zD5ވ/՝,K1:'jγ]Qy5eK}~6"mdy;3S쫒<,J vz BS*% )Lg75$eRM"(Ny,\nIbk[_WY_㵈?L8DR YۂL*c~W&IyZk0ލU-MaOIcs:`ű-^1[#$B!ǝn o]XX3ɋO$P('W7l Ԍwۋ͎!Vc~U-Hcϙ굀{|R +fK-[eƳZ9OQd.:$f.L6{J.=|bP.I~BJ~zDصJOL_%šL+0N؟9L$!;w7L{b_T4dEԨqI}Pb5bTV)?L X'q{^]39tP֣?n1^P ]`ƶy[״;>rgg y$e!u2 ]PqZ)q_߅BY0ʇ$ 7GAz@'F B0XWOdob7? SG2"x0z:=ttdl1.L\3zwV\|Y\'d#5HjR[hFY)O*4bDi6&PaNegW$e}ȅ.Q(jf6WH0l2ƨ N-rrju[;BRiNj؏c䐍vƊt⺣<񵠅E~C4Xwl2X"xJpRv.2?E;9vb9dRJ[(،+2$`ƞ~ĸJ-1/$=[jI uJ BiEʋR4f?IcSYL⨥VHDKRSz1P]?)Z{2fwyؓ,Z1)(]&$*$~̼s#_xb=rH¹&ZW#-ws(,(_ˋ =s_"U |}]xO( v`;jgnut_"N$hVWw84eטL\߽ŢVOmDYp~MmXB_!h;f3U%#PZky#˦ Yf7\tUÝ9۪C[‡1{I/Ѧ(X㭼,Lt1~;"0SRlEiS C@@ɐG'z* Oȵ dO\z}7ĭ"i}Jka㾥PJ~c} ?ٷ5yjEĶkYEOQV[sxt(TyKL'[bX}}DDUȱZ p^CG!}& Oc ka!W6ɪ: |hM|Qja${wz (<`5h,vi'Kŧ^S~L劣H ]40ԻLÐ^PjPRK }R00ow8kZ#v;Cs otiN6kSø-#r \i+O% 9G=+STUZ6]CmSv<);fu^_~LAnO:m^.ˠ?2U/+gN!L!X˨6W?2!7i9pDg~I N'JDBԮzfuXf0ź,?oG;|t 2az5,HxF1ŒQUa+u^9Mj(ɂBYє<] P*OhqcV3e 96TAUnXE)8"?OĨ+,Mr PhPt_aǻ0 H=| k{֋xrg4{:3R1<ʌC,iiq[zmh XEpLxP2ﲄ,*z_8T "=rŠGӳjSW>_oZ]Q%FQf߷RAb4I~Q%|H'jT~l GJT%6~soל2khYsv8'ioYU'Eea.R{dhw@M@}ǁOjH0LX.KKqL'nkoL{U^CrU I4p8 ո7}_N*5kQexx~>TyB4;Ɗyڶ"sʱHʶ}C!s[j3Ji c L^8ka|ZFyL$ 9DUq4 姑lz[Bpn=PLGXَ6mg-٪^ &#/%=]+yHŸCFs\ۚE  82ձeT Z*@.x2E2i2VI/9d& a% 60_ ¬*L( (htF[*jL"{>^R- ~$ݴ8 M#Z J_wJ&x3j++JO r sKjcaGu Cꚋ\L/I[MFI< .1+6EG+VJNg 8M*BvidHݱpAbثtC߮ t"%eEgM8x]>{&ȣگf)ב-G'tGK" ל9;gG0nuld2ubok)ޤE1|-`BXr_ϐpLdK_w']lxZ x* @~8sgQ{N\v}Qξ6 AlSl&q ${:fch=΀JgܜP[VM2fZ~޴ˠF;.o(CuҔ4wo~.l/9nyfNcY.2.Zg#KHJgønG"A}CJ#DLcEܧt9 oLLks&š( f?PJH3sq} `tN\;QK {o=&t_K^HSr=#H|,O&aP3á;PWο*d$̿[˜>5# ufF]9 @SGj8sc~}垢tG0S& ,h`r)jUp^ A.K$DTN~~QqA>9vl6콐v/w[Y>/z ڧr6Clo_z5J~c0LVUHӸ $790rR?01i j{.?sa!ia@j*j䕮^ |GqX#Kz#ck" ٌO^me,B$5Q몌d9}oI" h>rT&G)oru/YX7Nl?6UX+& +>؝8't[4/SvC_XiUv}")ȚYVdאUjXi"brCŌllB]qqM<4|~^&sn MŕU Q^PZZ|=KyfnObŰ4o67 HQ_ DH@RI"nBXʾeH^@$l'+˨WVT&BvfC' v*|w5욈i|2ijU+/)WASB=X/T^+'"D>%Gm%JR1UgWYk>.Iޙ'C@!iB;{~1"iwJ3aaHf_׹`='uoWcݻs55_'Aɍ^")0^੾EfԜˌP9Ŵc/@˫*+)7e/MjJxMUR23{SF^OܻR"s ?V#`"oso\UJ_9(s6| - Hƪs}Dĸ,@QO5=P7aܲ' z\ Qr["1i؝H}x'|X|<ѻ)?ʎt`k N 4E^p5oPo BUż~hY^^i.a DB,#P#(LiO"a%@cp,áxp;8@bs4Sgp2Z3{vO j8=-u"=-V\b #;rO}^ ꀙDߜxt0qe/G:=QaմL] l'w"5/7 1ox茥RƖ[kZ J ǡR}_-a+NdӆTIwo}pW'OCz h|vG \ Y/OΘ>&4*y'cLW!#ֶKrnF0lwyjn2n$K$) [M.%Up\@"KM']9o 5d3j+lR9~t\}GL1%WF'Xd7Iݘ&>]rD dF(}6_#iBۥZgbN0y-.B xذbGɔHbP 6|+(TA F燈MQIi/媾5,) il1Go2PLdZ7rzC(R"5z#͸-_MեG<ԡY|ADI1F0pl뿡\3QY7ʝ_|}% Qz43~Cq h J\/oJdb. W%!9 !'nW^B<*JİXg6kIO*&2). y UH/EdJ

    !A^f V.9_Eʋ1Jw;9@a[{/E%%ǝ;ʾ=Z^Rg|4D#UP&G  tTh\/2-l4oK΢q8tV$ e#\ŸR*ݰȎ]6TЪIvAܧ&F`bP ZWORVZ)CS:ALa8y\hٔ;/e(굕AܭbQj<&sՃZnLJPLڞ7a[ӃxQC)4̀e Mi,nAr.@˺9-,3b% /7Q9Oz$aT9H)[K oB68mu0Gv7@YsRaQ:R~т-oF?":T EH3IL1 ;l E :Taۭ`vr7OnM@k}]ӊ?*_Qr{㲴a^p0u;Hڝ[|"Ud7?XKK7 .uj>`FP:cX~:$ naNC!;\x|-N{(ZNTydD yt$m;eX^̟#bȄՙH؋yn}BediI92eAya8L[:{4ZC)`}oT˃+Wzi_. Lw#B WD3NKiM=kZ&rn}q*%!&^ʹJ4EȾ^DS:/w@Wp&UxBVYRDyV)n @-Zw=d bnrQ0poNWܣLø(c+ؤ`WTrHIeY$WsVF.di5tAl>j(äD n,E,4RQ!Fj}GPp(j-/H1ƬHʰFP{rg]?ͯ lpc,4/yݿs',7r2K&!Brȧ*[?|vE]q~Ӄ>'mch>][avsPA=-ȫKUǰ$7dyΥY0ޡju6ɏR6~YO`kp I3'cy1L* hj nh:<l(<ēiׄj&\ˎ_MpyTցЛDڢ#n+ "-oo0(ߞj{ 9E{Ȟ/COy=C?.i'ez@ L?|N2-yXyvuʋJnyxtlݸ3Iǒߞ_*YcKB[^KuशQm;v|͂&g#G EÅMI:(bΫB"N&p_ԋ(Y*>R/ޞq U$Eb7W>Lb@)Ӭ&&[Qlpuj,u !nYΓCR⫂w]_z$;+v:05m)[eHO#Q.rTVnf 1{v7Omp˄XQ[dƻtwY3$`R%⯭#({髂Oo?1WY9ڇT6oWۻ,]$KR}4Gi r~"cY`\n1y KP˃t0M=y\SMW#e:CІ'UF`^#}*Խ( 3S\`t@Iiv1V*Xn6 0'8jbp *X(Kٽu?WdiDkҔ0=B(,I >#2&%]!_५Nz$6mH?s0.f1'>b2()F+މB g<9clА7H3fGyWy~nqV<iUCIQ~3#>PP>! ҃>EЎm*W'^ IDbyW ]_o$(d Γ9SbaHnbKL!+1UOpv,NG$V"Hp#UN jWJ?s"b .Agɹ*Ψ֫CY7*tN.7€d)\=f,ly-U6 8R`N)(mJK3ͬ<&R0nƋ:sa$#u6%-dz!=?YTKqɄG`er hC3?l*(wX>> iH׺BP|:Z-qZ#m0 \^[YzPd[JC3+ ̀^'FVa_J@t x9CM0.]zz9"'~/'\ʧ3ł֟`"< KT[-fLRq orA y+-ƭOO\dQ}M/C[XksR 7iR}L9xġVsa ~AS1eߠZVC]j7!=b {pigMP#?">*!s=WZ2Nuh ݥUOb#::WJB{+;'4_ϝ[Rs e+]䦅Y]`P" s."Ve39˦?漩Ym>/{<ԱEEdCT~oum(. ]t  3W)(\!%5e Us7)e1Pa2;T3}CO#.!OOD,OŬq1ޅ L <٩wڔ~*5|p'.@j=t =.LDbeyo1 Gj>7J>d êCNKgWD߶۽Ӹ9NNA9vJLKV՜ Pe~u4ax_tbu2.eKYVan0Y $*&4<&._(i?EX>PA2 0?6|$;Xf\zYdZm8eKzb:ȉZ ,\d `<5|LxXObsCvȵNT2lo~-',IWl+ZT6#LN*Raez̒.ANe)6e_mђiF8z&r!Gt4 #NtCQ#wWfbwgYʋ)7WC:YnpC8;n7q™ [Mј…m (Pδ˓XD' YC/C.v7HVlBJΏ8sO< p vDrۍ%ض=&1}{;!+NLt@b{ i*DiOQARC.n2+ΐѠy.znnMnD+D|GKAou3 @U/r)N&nָ+ >\c82z 衜ʓ{p7pQBwQ,:@뉻"?M)kUDu}9vEa\>ۦvX+9d&OBdzLb[ި%]Lw"b.7Ctpуm 0a[&oa5d5XX)0 VÍCp[J |ID>i ɢ s4X;b7Ĥ֭8H\c'b%pH`wx÷O6VY insNò5~`]ABƝoX=AcCOVʼ:@REA7yoNwLF^(GfR%>6| ;I]T`m}9>8W23i,Mw IA |rfV'C!LLhc SU _J)Pa`t82JF<g&'\hYy#XC6E$3j3JOa9]x5>MċW v 9'hr1s'SYgǙjmC> b{iV}aksps7N@E HTʇZ)gTDP-Z /zВ72ʾl՘CN suSgɳ?~Fa<^3[5jR7ЀJg;dB~lpFJu"bFA|}V{$N{cS%RAtJ} Bsf'ߡ( cvHÈE{tvwxç5Yf\%ŝ?c̻0|SAnb3wSewA"RC}^KJYSvt@DCY{? !-L4rS[ob, şQ2El<+.f'oz icHǙ*乡{w^@nѭqha~Z`7s<=DN{꽛0q<_hc2|ˀZC+nd&1''@yJ q鄹,e%:ݿw'a51͊yAioDÒoCF )ߛg6P6e*vC("Z~oMa,JFy%I%O=# SrZ"}Ks3Ѵ WlR5ݛs#&tʣJY흺S.0X3 Yn-C^w༇}1I;O)iHy,H ,OܞÒ1kwtZD8P0w9Pj<Ʋ% cۍV0FLM5\c.BFA!e*k,P|Zl0nѮjm&#{@6dw~>5\Z/.-M]s􄋥ZLO2 1ݫ+scJh> 4~17AZBxȞ>3ڦw "#EE_"SrG4Nx@ x ]m  50iGC dL"f( v4ީ⍙P@ |q)tr1T'>dτ3gz6aOޗޞmeCv|,P^2EOMPy哋5GEr k0O:lPKvtg#%-5yBu'x7;'@ wHi*4e2.|4ecT߫cyJE$kQUewr>A& L2M*w2Vt;=ǣb%Lm YwJj nˆ1u`+%Hc r[yݤ;zf}+}V!eE.TKtPZPIR;13; ו=21tSh%FJkE駕g~Lp]՗#.\v+ ot0g✊*]4F8YdBZm*˰O..=W56&?6{i-˰³7}#:L+Ʃ)24ޡU+ 0\Tfj6t-u4bo"&B>f5'+, yY$g$uӽ*HeP 1|"BaXAb,e: ]6־a̲kj|μ{mޝ<(JfKC'.hɯjG' :e?^ì$c؍;>%anֲ%NJЉ@oU:crЪ7:il2'||qhdx*Br-l[M@#G -w=g[Sws,G/u{lu*K^??%i7Q%d<.%2 [*&Of jk9E=$'b슆tUŃTiXf,ݟGGSoB) $oăJ BnX4ƲU.Kg@KQ eN&LFJIa4J'9 HeѬF|b 'D %lQSVkW-rK~dt^`K! MBǡfy$Ю#J3cǑ^Zs܄ ?\ FD>% $P`m_7' S xoNJtv\PE=erzوO2kt;WcXmC^ێ#!/Z `EUsq >aP!h(Y}t^%㛈#ƃB $_qeA1k㿿rD:G~  *,Qq@x`&hΔ챸. qf4` gtA132D!SH$zi]lґjy9†#cQ$ΠܫhR݊][p)cŭ˲ZN@xq4ˀBf3A%gbMԥzKe% e"m4E4a$|:4Wa =4,RoIoLP? ε9#鹺Y Kzdpe٬go!{!cۑXjv GTgnÁ[6(ƭC(/ZtO.f,E KÆPflA=Q}Ztwխ^mE_//|,#>sxmu0 }A'sd JA@8fg6rHQU?xuӾEF 뙪泟 .';~i辛qmjEFc]$ 8u&Ť 0swe2z%\ݑH"k&} eKS)д$]gB/0vQ2<͸Bb͌"/!-;f1KD^Ye8 cd aOl8 T݌!ٌp$5U W'R.cgQ lsJlWvB1c HihaZN W|X#e%o"d?P?jt{({[\+Cр #Ȉ:zkvyl`j9U`Ty &@dn5sC+G=#!(P 1]n$Ơ϶ 2԰v6" znxՋCְD7zc؃\d}{6%&>.q_?1bW*ώT lso%d*oֶ5 {@p“#XHbXT#?D{#KPMEef /P,ϠV-π p H)YRMDPC=}ҥ UL㏳\ M}1hGU[w-VX;a (C}6}mMo=G vDT DZ" LTTUg$FAS wp=94-A=jLq&\eT5@8cOFz]3A:$ p+Lz~$NvɠF2OtOִ4xxTv<LtfG34}A j [$^/M$r!3‰ڎrV9N6Of =/qKtrAaalyJ_0gˀbz1!Z ,:*|)26(˦=uWSHq=#&}+F++&i3ܳT79źR%˟l*i糁K|KO%P@SXfyI*(}c]T ,=vŰ8۽ӽ*zN< iaKx Gyڈ}VY܌cwW7+gaq.3WPtp&L4CJ n^WTHIlE£i gQ B<.mx>}/UJ1lQB .͚mCwJ : @_(9Ap */M\p 5/) $w5֛5wg/BU`0ɖPl!!z~Xtɥ91,h:Rh0]ÐUQ/@C{xCCR-l/6ғ]˿]53=MS$12U9կ ewlDuZl>hw2J.;x<3@0.d= czN @l)? ;i3wʆdKNP:UZ-G\\?4 c#{"m< 1磃fY&oʯ@*ŮJK_һ_#<`llhHlD'V!z^kqϘI~ѭ!"돃Ǭuc&}EɺM(>QZѕ8m &&,ozqEԋ]ץJ]xVU!-t[6DAݕB>~jdz8EeoG1{ k^!LLN).sCP] &jֿ'WT AW}R~nP!;3W1Ld]_׳p^>t7w(mu >Ʃ]?R檴Mf.DjDRx"]Ų~U}Ȟ(~eBU+)dwS)$8ժx C "Q0ѫ654``9*FQc5)P[_|O͝y.dPfn-6έDVhpYy_c4HQnm4n8pt-ifUeCUȐ['01[#~E x8Rn( njbQi3K'\)!EyF;碬*DQ ٌ32%cG[$~[m4.1xߺLlE  1giO}%rZsG5l(2B=,}ݥ8ovp.V MuC>Z(XrHJd40TQېgGp^+S_Bew2hH`:9'Uh{L Icg݅̇d J,=q3 d7M24YT9bnQ0͂{Hhu*?PXY(@ SF`rW]VHmf !v#9Z}݊.mbnzM|,v\r&|GPe,'@ ^ŤqC~C|oqqCdW|QMfyY6hTgFQ^Xrqa O ٦")dTqoIɒE[z$qEj }u]zi"\=\eXLFQ-oOPUXj+Krv;^q/wanQ{Jz<1e#fCů|q;>?'qos46+rx#]mmdQ[yQյzêN|wSpnC|'iž5VKtoؕYV'z!M +;4%[6_ Qf+;Hr[fx-ס7Bm=gJS杺v@ȏ8O#[kDZJTi*gM%;)vJs<nBI*p@Iҗ24Uk;AM?%/ &oZmMXWr ZR>~[uUN ʩ0wϻk:fφ^vZh0R7pC}"[җ)J\룰ԏ|B}U=A;c0F|\ ;)R7x|Cf^~cS*B*MyRO@51L'qc:u_cҪ@rD7?GBtHLtdJTpoI+ N:k^u%kZH/EO;OުFji{о̃KD z41 A ͡4ic)5O y{'A&1CU>~:,yWI*:lE\W\zH4U,=k+>Ck`y=?t/V>QHL+ eXm?TϷAꐫil|9 yDRwNn@OR %Uy ƣ`'!A= Rn5 XʚM:Zh0qXx4oB}?qk2cd.`q; iyOdQD]V>áH 'u7bV2R܌InCe'@7xcebGhOt0 \a a@/C!BI:( PD=:Xk&f|@0 Yj߻R1i;D8ťkT{.#lYus:9li(HLeŕ"ˀTqx^PU$ާUӱ Pj}O7Y.E!撊9>,?m\S ɜU(ctl8 xC{̭-Uѧ-ߜJgwË%k U}UǨ]M MESmg3uи>88*;ܔkm Si|PW,q` wC{npJ;rgw]&E6P5tX#Vqk|3Kb]^۫]kE}hpA7GSGIќƔG-I/fTso$.W@9]nuE? $M -+uqݜO*BuQhݿE%n#J_&"sG4^üF2 Glś_Hm^ع3'zͰ*f*\]z~0 Qo[b!Tg;u#׍O+w2~2 l`o@׉O3Y(/Ӊ$QFݟpQYmDe-\J~rDzD#w >Xn'@;%㏶Α폼)`"_ ޲t.{IFyєAǧPEz$Z%0JƮSL8z8%p[,p"c! e8@4rӪʘ68E`-἖ xDW*TU sy/k%a4#+ rlҿ1.)T" eI>Ƹ#c5wPA^HZS$4b-g5uK.#Ʈs&pG[3og~,iG ^JJ{ݷ<.L\>G_iY G5ñ)3}XM|YjvTS0:V?'! ZRC|A57jy ׈~wGnJ<-H#YkoIys^YjƔ <'Ě nGײ3߽%&>i8sJYj0REl<h m(ۼqfW໰qȊm(…r jrl)ˑmHwjZ )8`aQY J Q}d/THξ\ 6n:>xPw^ '-U-LJnÌ&5{\u7{rSQdO'W0L>i?H;D^_0{ ^tA2_4{%B.N0SgjWb" xd gt>U@-WpY)H`wS¯s̮FܿRRaEf%ic9I&g| %, ]},uDUUܝl uC)"ZfO ̝mvqkу3ѾqFrBҐ@XF% f=YYVaEȀB^ 9-0fYzN;69 j&״=.WOT a >i]v@Gsdkpʒ$CƯ,i9;IC+:7Uǣj;by \IZ҈4$3EU01n݃TWeQyhLm#2jL]: [/ualbl-P6dI?,KSw=ſtmA=[C& i|69bl,'27 qsNz:MGFK۳jt'ʸQ?{oL[1P%H&5so]pPaLBHHU|(t' !h֓qBFjA֏y-,Ƭ٫ڇ)Hk9W?+kjR9mlmej=$SWԩ\T; z]W@#0HsJLmq+Og$#Է/hb"l28'l2u em;~+^I\ z"p: .{@7VOgٿdv^՛6rYV )X Z; &PBPo;k6\@!L?"Ԋfyfs@dr݅j%,ٴ?˵6>2j eۗl&އXq̈=h)'N5/Dn5Pul(to*4"Ig?|KRQfaߵڡR9ܳ59@6ǐ[\mceY ?.hZ(\TuVȸM wy3.þR2P6 E?|H6n`5B8PL3sk:"¤g[,<.'v\L_RԠe/xJX~"&.p@{{جM0#f@ \"wEX3=z^V5@n:%vh Hd= j2Wf5X}%S60_B[W*/ez9a`|M e8%-~ӞIW}OԨWKHʟ8fewمܙI'6ɖO=bk6H)ԡ4jkYUpzy/6>:wdOnrW!ԮacےԚ\/aP*9H%4="[R |3Y[pdQ} brH/9*»ob7f& w9RVڠig\i Ayɗ۸R`dҶ ;R)9q 0D;)GJSgj=<_:b~:ŵwmHBbg',km+Y*-a:ZƓ!;sD׉Ҍ9[Ap (/ FHYw3ta}[([3N[u#15|*{qhܸ aPNkXU8G胜İZ_S6rțJaP:ڭ+}ͮ5NA99G)Βδ6!I<!K }MTղ& U&L&7*ueu$'\j7(XW"y/1xA z SbYlP(ߢ0e_&[$ȆnY_z2;y10혋ڥbax_5ye6 :<o=3\R iR!ǡJ^ :?} dؽSN}.!(0yo`N}7`cqL\5˴o[r d-PoTqanE"3nr{z%7N zXV76[Pi5 @=R3`CAmLXׄ<`XnLJĥtKQAآ$ҊrD} s?DZ :ǫjA+SB tŜQz[oVRk0l?: ՔU߮GVp4mU}Ook5t-iN102 R[Iz 9(kE?rI;zKx?ex +iC, CN7q2NK0llUk&  Ix4ޤٷLsN(2̋#NJSzU\M@NjF8%Z.ݷ(dӵvnvl VGܡљrGn;Оj/r8CϊA幽S /d0AjI8qz[Wyw\X0俨s^S}X v:]l?߅L4n^a7uN]=`t)\OYn+yxm~ܷfd,@\Q) ossx߁V8xb* ac!&݅ i>Tvh~X^*pįj AVGye?~:o[*B. l*~SaXSba8 R_Pu$F sr0h hX0+۟WUgMmH L7ÕUzY {IE!󒹑4qj a*D3MVkp\kˉ6=f xn 8X>A!~޾எG &Ye/Qfl sn0sl+C\֚#g-3hSl3)KF&O nx}y[(B'w(Q3t"ak [4zѝ-=J!:/B\\P4к ʁ5Xmt^"FxZ*wj:]a$4҈ndS/Pt&S K*v{񊙇]H__;ջtB7 FtM+wx6\J[.䉄meU4 Za\(wq1%jAB.EWasVb{h b%!tMYi݊=DZp^ej9Tf@iF؋@ib@-QTMY ]>W @^1ȫG0^$NKEvNVW¼2M͘Y}]`?ĻJy?#ؽ CߠLj2Kyeq^o䂔|"QY|z_`CWz JH̚ {k=,1MtuEsh.j|{8KNZ724ʁsW`8By/<]oǯ.zP& \2)R1 ;]c{j# b7 &YYQd> b.Ɏ`.%x.|j#"~$RΆM̙TmC)%>K>'F~GB [0u8Uc "g4 4찤mwmC{kiUY8W|޽N)+P3x;",r5gS'%`"ҫIV템PBA=6)#(FWJv71vBcT[1zl ^Jzt@n(DTpMHEYHbP1:'(Atcb{mUWP0gl􇩋xD'#tbf<;W樟a_C%X\CɀC&w9}ŰX5t›]Z<6o{sÚKs!3ޝ0%+FrM?r Ġt.KddW@&!{H-&YS#aeBbX%ypUܹ +IzpM%RA#q咖8=lnds^.痺} Te0gFp SC2̴Ą$O ʏJj }dĊa!-R߭s8( 0ҩ%.ЭR }t=PwZ=,NFsQې"0]Q-XD?2j;? >B*x[]sCPɀxwm({ eE;ܴ;X_$[7coLLYG?dE'M4~Ng8,0njŷb#! 6_%n,Ddx> [y|^p\0R`?gq"0Ȟ)(hij9ZnSG/Z8*ckSMO)GĂۀQ4~)YxXPu,Z>K⭨/:84c#GjbR/wg+8RvN,Ǿ&Ȑu˯%x)ߓcWo՘-ڣ Hjvִ4|I*s * lj7xȆb}wz OVŇBQTZp̄X8d}E4yR]e-XXL$0 ֛TKH xy,Z+Y?~h,7 !)6 D5, g},j`t:[r׵rvs z 擄\@bի7+i9fI𕮺*a'E9L-vT"+s@ЗåF.,~hr?{ [ʌܨJb)#2, ( IO]U{H -ҫ_ErÑ]df>$KNtHLAe *,əё鞬{_CӟbO9{: A-oPe7basro=c3#Y4"2O/:T7Ow  n0e jYZ;vgGͣ),*Őcr dG{byӡdƻ$kt32P >!ۜP+{`܍>B6Qz> *wr 8Z /욐B!<*6!uyGCPm)A;a?KB;h=ahiS(;բ B_#ReVR"0XѸȁ GI~&kݫ )-OJMMKMm:.‹nʍ#jV%4@cmar!ߣR+Ĭn}BaaC8J r:-ÄfJ % -9&. [7 \ߓ0-4eƚx (CGq|8L(6ЎIqw}B\ COhe )>.x4|r5";8^2+^8^D s\ul _\reF Sw1 xUL2ʁ5~ )r= T1H,:3 g/r Pu}Uc|=BHN \wE*ӚXÕx\7S@|i6of'5 f]Buz[sReځm-epR8VVcGV{vحŽܜGTQ27C,A, =/CL`I xJ }t!P=f5dv4.M/C/ +CnLr2uv^BĞw\'>|ܓ 2]w3DHp*4 gOԸN~0jk sp*dX;wDXF̌Ds!Yj, 54F”gd6I`syم~ ݏ =LBrU2I/ pu肎x.aG`: `p^}$Z"^uC!}'<#t"D/uaVBe ZDhn?ܰd%ܚƙ\>8&[6֓*#?~kq½  PJl ÊWq$=kdBl(b$YYD-7f+ADY16}iaK@#@U}o h@f* B{e' G!~C9]T%>~5C 1fIÕs:.pW};PؓW @oE{_mR{\u70֓|Pͨu$ fL??.(!$Ib\ܡ ,LR \2ӇP0W$~0yu8ySLF4UV?!*o8xeDZ! S)hkg osR-Ш%-Xu66i@Z,ZBH$8SZkBR2؟dexeטk8JF+?ũ׵5$}YMtKZӃKfU#Y4>;IG3\c7R#+JՕ r 3-ӝ-ߩ'uqmP2;8 QS_i:}pEeJN|M{W!g5Rtd|L/EcC2==^W q'&M.3ۃtb' ؔ3 >Ɂ+HCꄗ t3AbqNu$37 \Ս r@Qai :9Rf srQ($ T4D@e'hZ0,t:[DA}K1`Ë%lgPjo6+qKuf ߖR;[n9SNGᦷC4 Ul,27ҷMR@7d(2<TΓnZ,''L@m'KzG{"j?Lѕ^^0ܵ6tPW;6a/t2W:l rfz]\\~j Ltu3[\ܴcg2b` Q^UmvRWڭN DvQLu- MD|r2$XV)Ju~=0hT* -aI&{1,5WflDjO\}os#? `.{GItoD &'荖xiήsp֢Wto08C1 SCʍDbP`-g"9lP?KadžEeh\{YěsK^Yp ?:t2EY3R;zU ^`7G*pg~\s@΋߸Vh7:-h s(oyӖ f]/5@0O)#~[+"PQi*KA\g06D65rJr8PPD*_zɸR;i,R]q*fG ^ko>KE;H[8Z)NwR3:VfZ09b1ЂiB~a:?~X ʸ qv3ӝl"ˤ +˺ *4N6[Ot"h" xUTe~G߆56,L>WL*D顊=ѵc=r1bq0,Ɨ0}4"sDW. 0)^*%*3n>s4WYecH9(6v9 .:4%!Jšǐb8uZX3#-=\JliQgdn\͕ʈ?Q9tN { >|VΥqFvP[ϋ }2+(^^DŽF-a20m  ,FT2nbU"incx7Dv"n@mRړ~ e,:Ig8aSODMclV$րU@R/OThFmp9w)<)@w<%tp~|Bc^,}6h^iaL=^C{EdFHoz:)b;I[O"8)ͱVKѬ,JA T i9졋Lfٶ F'L 7t1j׉tSێp$^0I蟓?ҌSہ;lFW+K jGb'Y'֮e?#I=ՙf5wz$ zm s>&j!*,(.O7+z0idHAyQun{D91VGXBLiw 0σim&Ѧ(rU[5GpH轖d{Ƽ/Ӟ;%i-Z Pll"CJ2n @g26{EipkX1{&~yKAAvKIqyFrY [6\}kLWWDepd,ş=-ZNZ:@CG[ƆOP*$ZPGC$6 0q Q{nT QS/$#bpVI^mv dI>L:o T 8-̫BWr8i}F/ O3V#~svh_ kN ~AlBY.>J-}[V :|WϼFI h% =]?/43&(|y,n_Lc( [%}&u 3xc -޶ |Woy8{1YxM)?]C= ([1<:3~1%PƅVe8apK C2K~Z_vcP_:d*W-bV7R 17ffTu AzV(L$ /r^#},ۇ!Nބjӣ(ZU^hbU*v\ǹ= yKdsŽUC?cz -Y֥Vb+}We^IQ 9\i1dl  !ߏ#ѐEF^\KI;CÍ񹜜`Q1#-}m3r#7.|B\X(PffQ'5Zc kۄh-9Go3"h`45?gM}o.P!Tx ?v"_褓~7 =ek$FryD0Uo>Iq89ҨG.4tԘ A:E>=E;(* ! `X.,{ }| 30^M*z~l&}-  %Ahvb^ ,dsm^-M'C`g_Io-QI9AVg׺b@v±el~ӐU)Kuzf}A/5k:Wp0jttଂF|PbJr(ؘY#Z`-93#-Y#?⁷+ 0 &DLWw{sT#!^m|sJL?A2BܿL- dT J[i8 l6&`zkWA ntzUhXarSR{5cXbMդ1lyZ~a,"y#\װĀ2 p"p- @BߎbVy'*3OBZ'S9Ns $Տ24y 󁪒>iNN*SOW&7GN?f\4ns*S/@pr#Y!Kr*K3oBS`>o2#$P EtZyq+X «ϴyȈ&R R `5*vŹd-m\7 N_xc ֍bd7]?$r26++ǁm!2++^}OLE?}`om]Y31wui7L&JA&S bqEFgeZˆR >kמ~?h @r {`."axa.;*QC21/ElfzeB) ,aj9$U(d Y~=z;Q$ak1⏤8ϓy|@T!G=R*|G te0*$ujH3w釁cGfĻLw ;e1ϖ-%5f-Z|҅e(N2\,}/L/Q[8!)Hn_JԠ:U:/-;';ZTN@[w$Xiŷκ-L'P/P4k.2i& 05'|F^OI* BFF2?dѽ9|m~bT^l]j]pwY\yeԫ`CȀ2kl|kshćB@AvX\:J,>]͏i񠶳قX/%:6iX=_N|~ Qލj1WJe .4Ң[ZTΐ6OHg/0R2ECp.9~ \2mSЕ1)![RHxrDYG z9:2굛Up6DPzcd5؞sppZ2!VkZB?R =V t XK濖g ݨ:6>n D[ɫla_Vmt4#!ƅWX@#2+#>j =1aGx;G{PU4*kMw(DM&1nЛL=1cb~gFȽjDmU xtzo{+E4<bLAUFjVh|MKO){7aI]R94vYUe$$}+ j@#K܈q^xg`=/fq}eࡇUdH]hc6Ւ!ɮ.IdomVB֥+SSD ?~Q0rn@oAFɺ]GԳDwp2қ?Tm$+\u)gڀ6 RCZEÁM|N( ,f l?,}bCY7*1֥S͝YH_}kBƺ$,4$D5|-&̌ZI= t~;9]*Ei&ڶY(@NN1O ?a)yI7SLT~ZUҪ~ːὰ.SBHS軏G5avm2YI,0}<b*wٶeL0;ss*[L{-*~:8-|adfmh{3uE+N,oNMZF"RC{n$>bMNu˻LϜM&_58Xpf,ÛT?߷I]A:Q WLƌVCaI AsY^ ךI3gFЋO{IٲЂrŪ;ohҏIHBP)[ l=z dy Z  ;.;>o]0pߕ7 @?In6hF =[CE&Wzq'P ޚ bɟe3 z>P֢Ga@k2+sP*0kç`vבff0;B n{F]7JNP+DĄ^U+Bjo7O ָT(>w{&cÁ4" hאG??>%erMjvߺ{n$ k ䷄ ׬:/ywxԧZ|ޞXբg=i$f])1N::eέbP֑"Gx9Wo W{6; 0u+6R./wɾ`}qs7PPO_l8 {sPHdSj͹LÚ,;hc< `dusj+XUc5Xu<Ÿ]wo/pz*Sk ^ٙIP)qx]JdN a`0\h[<Zru C+t@kےQ2-v].~L~"HT q3e~:XanN׾5dbXS:y]0%/J L_D2 z8/J1W,RN#:N?c[ӂ#%[=fԶNJܨ6B@58g_EM\I/v nТ%c݆ X_C˄x4t6c1dT8ΙYv^R^\[2#)KuD+}k-a"y.+-OUǵpՖ ?+c%~-@ g gMH[;cR0 +q)j("ܽ%A2YxqԁJw=׀SrB37θ>!Tpvj1׍2RS\Ԑu~'[48sTo77_JcT,qElg$|)OA+S?x^4K2vԾg^*;,%]nI~bR Df,tZ{Lp*Sk!\T1X|p,?.֐|%-kWfbN~4*Bl zuRe~5)8Ƥge+f'r˲2<;]y+,:xo= vOdYUzgb5bFlMEccfI4R!7PrA0zz;MTA[U! mVi ^vp}8ڎN/PVoh :bAg EB IJpM'Ϡ2,{- 2Ԥo䭈k ߜz /jL 7}கa;~bJm gS~1cOXimDhG_SFp>VnChsejcgוNkWݶ$ܤ!c՚/ӊ H>6MB<(ڠy'z|Sȶ|*奔O̘@|Nz%ףyb6-18;? B\[sa3t09֣h}N0~ jqʘyÚi5ȼet`\L>\n3upP}1L5SWj*1QgN e{hh9IL[ۏG:֍)v\RDfƾv ~OELBy6EobtҦiC;`Tym7p t^q=hjpŮ%SgCxxg(7SS56Ȩ-yp &;hr~'m_3tuK9Ŏ}k$$eso cRd6/^T]Ъȁ!NO0t-DqpF-8|]e|q޽yf>1Ƀ`. ~ B64v.EzȤIcysU0+{5: ?IM;5"ڡ$U'%-Nx4 fC&nN*رu#Ը_ނ{o?j|FJ|sU>3܊GNdmtLKXĿ9>b5י* Ńhj>iZF}={WW?h߭KĽC'{=kȈR|z{)NRפgf4Z^в4M{9|- P{PG!x{h<`! xʼ0zLxrBۿ#lR|!i]e;e#u7:D0=q .9jZ(^>Y k{INGDIG+H)|3=Y s)NO>:<0ÄI1 ܈NSJTl1sL(Ry$֦~eU>'B1ׂD:hW9-;OMEЕxtmZ{qd^1,snDTtSf'͙BP!y'_=6&3II9ߎ\](&O C=2Ў!X"?D?ɉ3Ic6S.ӽS9ǘ̻ѝchj˜r?:j4J"Rlzi$q/Mv?\bk]V&,A%{kA+ճF(5y+?Nmbf-дvhf9brHrFOJWU?zXvet9 )^ؠɗRȨ ʞk|8E﫷\tsjTHTݜ+iʦkvI[=8c@*]Sc۩u4 -x9YI~  ğ&'$xLi ddԉ1քZ1mhkܛk z:>,N0C~qVL;}vsV;gu)馗k Zw8YPBBCT{0f//b=ɝw4#2dü(PTsN{}M=-r2P[YcwucO(ETʤ(f84WE&N(^vICu|@WUmh[v-qHO#}#fDgBzms0,) V|?I(j$%8INP1IY:.|ڿFԴ2+'F }FW_W?WY53X'&~vZ(; UFsA/:n `nJC36a՟ayQ@?C.5M覍>Jh==ԎM,: @b c?s隣OO|;݈?Mr!-I4E/vadw=sV[96HZ]@2dg+/8cĔb"[~ NnddOͶ>E TIx ؑp .&L\҂5Y ?jstGۆ\K?9E^uj);]m^I\G ud6>)"[^><gT( 4qCxAv7kKu#~yEi[{zƓ-*s޲h벒A6yNuJ B75 :CFmqG.V#bDg B ~AMa%T276[`fj JZf^M*u暧,eSSX7V^K8u4`1۟ke:H^&~n&`vfm}f߅SEC+cӧ҅*Eݻ!Fykh.2''Ur&JG=؜\wVf>ÙĪf,!F%; T:eI*юh]0 l^0u"zT$ J;fOzuZs2n}͢>~l,h|}^fZ`ӰO#ϡӤ zJ|>OAu+`41Pzn!QJud?| oQ0ɚ<Ԋ wmX/9u]5ҵT4/Ul+{!틹;_\E/鶩nw$uK\j<kIvK:g@ xoY1$UNxRYۇt=CئcVxeNZx'`vM,ɭ ;g4RlL3%_Q'.Uծqᚈ<~,Z2% ;ҟ)}ol7q( oOo,—3n _pq@J,,x09=Հ՘>or#p2D.Bzys3yO$zK- E)J.AjS!DvvbQ<<W!w0vM5ztKΔc:ufy8`P[\јN?ן1ecnIx[2KiT}23 j!KR@uAXkYN^&g},"PկF&qXFX2u)ۍ ( ~rC?33ؾ-sjb. yh;_âީ9y.`7Q 3>`dBHydYPd0MJd58 -*cϲxL˼ )}fo(5]xta94GӾ##Mo`?[= ఠ]u{!{$~\\ejlE/s?Rtj~$:oi۔, ځH>m`8St>kbĀ,T !EeU0"4Tb꘲7N<"8fC~\cAaD UIXq<= ¾O="omҶT&vRi0* 5!}kc- ^RJBDoՍc;JӚy$z۠eOwjxB*Pdj'SPfR]c>ݵY<͜ÁttipkH+ }lj4u& L=av4~A!me$x‘!ӰWw)r;&W@HWB@>|c&Wͱ]/M33h\^ԃ^/TnsfKO3b?*0GZu߿TN!tn=KxAq)qD-1IG/&MXz>-Co}SPTsʸ&I 5E|֣巪6XP}) Zkq)vrӠЂ)wəV:ps{T?[gfpuy!\-tך4>S`)I8?C\.9*9ELVzn!A|Ǯ[i J4XϷ lAjYae@cB~M2Kf _6Paheɰ G,nlNЉ^07W}-L_nݍM$/_ٔv>=r)^BesB2qe;EġɅ%igm~A:(G%{)p; i `C|<ں"Zj_0_kht+"4TK@Z|)(V;뼶 BkQC#8M)={+4F>VeXxg.>+ZTQ~,+GGhO =qùu3"<% 10G'e(BSiTh/ w7.:e z9;ϛiK؆Ti}9,{$NR~AR߬+;B5 XBNQOg#&OsP!=4#W)Œfv2xCaMc:EaQF!e{Ht<"`kUW*Ip> K)7@pWbgH`L KHUd;2JVp4~noHho~y`@/$dnȖ'n`Hjvĕ}Np#,nl-\jo]W2|Jدl11I0CLj}s]䄀0kpmO0Hݲs cVGp1 $bgoK]זmXYq 0*uC,Q1Ӎx,TC}Ra|8v ۲6]h3tzn#imÞ[S % KI /'vfyY&6`%Ł}g Z[22#\!M]6D96oJl{U;7hs]̖ri Upr)jS0x4"Mؠ$p0 0Tk=<_dLA'9lÃ/[…Ձ*@Ⱗ~(dG72s=~S('BꂖpstƒT]E9<_5"& u3ȪȲ+Q>o}q{Pk$w _&̻;Z fFnG?Dh:57پBib'L=OzrnwgX0wS\%e\f^]x\KT*x*~6{b"MN7y ~T$lN ^E;790ɩaC1*UB9=7 " Ax(1f(-;BG4Q_ -dZZ6/[vOn<-S?ҺZF^)#oi^^\Thl8u%v'C] umڎ\FаVJƮlO HMe{StzRVK~C) 0+վ̬q[X#l8FKTDc!y?&d{-hhH"m`Jv5%?Ce:|5įx$m ƭuEO! ZaxϜex Ag:f$šLs!D#$Cٖj.c9;~j&|؁*-7-4vY(8zdw7fn!ADIf ;n+7]kʴ8p?'[Q;Vis*Ykfrh2vN[hu_;coɚh1hT᠛ZܝKz]'zF8` 60!#!# ghM 9:%^oSI`z4-ok3-ڄC~3MΟ~B _q;r_UJ49+,<T$E1H#m|xLQf듸\c׼mGhl&w=u:2Nw\:uOpr)VE$i`#vGgfS~^Q³Ŝou5/r˔䅈t> QW.~_2å5xF\R{A\JC$򙎻u5ۏ n,}]2_+i(9ry5< GF{6 lN܅H򙟰}RK! x#k7+-Bfq3;s: %.$Ǧ+Fgr?4nNzͬ{^ 3Թg9EXV͍{DWk">׍6f1h .8["i|h<݅%])'[T[yI+nҷL􂬗C =t U?U1&?ު4A[@g0e pV EPX\+_raH RAIKT jaV>X䈋@zGdz~@,~@-'6LBr lAd(Y|*-.)RsaJ`%$F߲m<"9=O;\}o6@>}A]KPV3_}H choo/!n;O)H2,+l^maX[2sv֛L,H|D xe cigk/kSػ_Q 6;stŅ/Cjd[dI'o 6ԩ$$2*>zC:f'EFZO OW ʅ.Bފd3FB; ;\ >|)|M 6je?߉q:P`K#圭Hȸ똉YP6p˦vcnn@R{[@.ʇyBo>xxO6zYD*mx8 5J\ <Ě/똝9HU}Jꦛ\ *ȥHE% {Ȓ[`Be dc/#C *yHV{]_EH5 wgXsD6@zp;yҮep+B9#[Eⷩt?F~dYcO<-fоopW'@K/,"RR;|ե-E}|>7O o3(*4[>$EY5TN?BvNʕYY: 1|ҴW4!tp 1B5@O4-"/Fh41yG-|X۽z5ŧ;b@ޢMr !l5H.}u27;]rgLA o?cMʗX]?R,'k1BQ(*FM.m'>"hm6*zcR_i̒)je_aBs%_I7ғO]}/XMySZ(]9RtiH &p>i=􏁑Gbצɧ*M0dl3Ab]Kѡ4NC2cShlVfu]j}&ZEF8sZ("s@"ߴ5zǒC F8:^+R <8Qgj%$xjCP%$)#kQ`4}n}Es&erɬ`J0 ! o5 ܈qKH2~8fy}߂h|T3oΜ]QAχD81{]|X9az7Xf#B&0T.h7 |sߧgguJn0-J*r91]}Y w=tiwۛNTm1T{uI#k~&00L,? qi=p&oQҋU*Tb&^TtDğNa$wAT ޶[!? 1< Al<̎@C-LUHBQ{YICPb0Cc*'ZpGZPlb0ݸM-7z_Igc Qn /TB^ڝ-&_m29d؁{K܁_ct$.q (ǡ(@0d1zjFA}ٕ$c<- k` ƃ5 E(+l&ʹ|3cjl\iwzML'[F2#gw%C\;l ? "r"%%1e5׆ " fI m:kVgKm bE[}z8=]#2LGV )°UӸrg'tQ,4| $_I>>(3!ՙ#4Q/cy]tb>@<1!0K)=WuVoV]UkŻ"8p!7 K_}4ēlhRu 64`SMa:˝/XX:k  /[GZrU -:v6:r= _ vʍBg 73)I/aI̘zDKj=%}$):eL;0Zň;s`FZPq :=.B@|SCv{?~$/`偺GW#s=8S-ɽp,-jO6J3 "j8r|rQ04D%gc4uh׍Dp%ت;U{nO@(}!䬛Wx8(Ա&#o>t24(ҁKZߗ(FbMۥ* dю3!3h&t=k=Ɖx~qք`1 Olڱr)=R5z[o2X/`"8O%X`9elWUi.n|+B5c91\ݼhXFxRai2' bKIրW0c_Ԫ] t`lxT/N(zBg=Y T& kz9nϹׯ֤0I`H2D0oh=#ϘЯZ{ĞhhsNVOC5Sx؉,/dytD&!&A4̔$ ))a#gGu?Kin('o ܧzEXz3eWl.Whmԯb폑!bo#[Y_&,m%(PxuKD&ώq$/t &M~Vt`I5~Ihp9u|.S 7gHdoR !irEqyL漅bfʏdj4UI$ZmLZݒ;|0$LG:>$d]}o]\"١ݾZ6斓Z)E%$cϒTT}%yBw4AI~(^2)̉σA%ѠiW-0ʜ$mbXzng%NL\e/TNcE'E.IEȩŢt1$v$ ä1q R B]BǠ`OWAZng^-DXq!ޛH?J`tm$hnw\}{íh_)W295sGLJ}P R 7ݛFl`8k-CF!*hS_R` |8\s'W!Z'*;ܘY5HNKG8sZTbC%㰂\HUOmP,)Cid]ѱ5*1-`Me5%N%e}Wu%Z0v_6gK֚ t4Qګt$1{c>/`p4m >_JF!k FOLo#֩TGgpRcJUo};0-*{0+{"jpƵΕueM,ms; &Y*"B沨ˀw<|϶;BDI9)BvWh_SnV~gb+:}NYI .^"fσ0&/.N]֠P]KǸ3K־jKBj"`ԇ)j1ܟg 'DIln e!(2y=6*tK,W&CV?~?qZ`]߃ʂYY[ ̰Ip<0k|T6tt7AI=hJXfb+#1"O_?U4Pr!짔,Vix2='gMz]TV3pָ8Rs JwhrZUmbAtoEO H'+d< \`DjY _ 7:kÙ@1L7ԕ9VB[%m[ݗ]%^pm rHrCVeO3|Y*M n?zvyMo`{-ү+E}ܐ/Hi#UmofV* -u+VP-%OB\=OaM!<&J!Yr]fmNcR-+|i.` w[EjF 2Wݪ-^6xӇj]+E":tiׅF,WIt?]^fLJ1X.xȮBClW5)16. ^w(_loB8S,Ys-#9VΤtAw{JШ?W(LrσJȏ(ؘN«,Y`TmD M4܅#_5׺3|Ek2;zS^~dY Bk S=[odz"\3KQ\56(f4G0kn3 k J}N!qYKҜZQwIiHu{W~J4ä.Ȅaf};hDQr};z%@h E{u^V!>JP6+݃>ALߐ@=;1C<]%,(Smd.K2{ #B>/OOc?,Gcf ]G3 Efc0'P(ۍZl~CT c{yB;2_p{ ߬{ygQ&,o1z<|ɛڥKRl%[hx3f<}Yñap 6h#ҸY->xPVY5ܼ/РIGg #>t=I;!w7 Z"gLSxK}[,JuvouX95FB7އ"I+OI BJcee ;D ɺ+:: R1~U:i6/4nH0p#疷g6+jc;_ManbaKL,JkW׍ #8GkmKLg5m$sDt~ƶh;xLWq& [gH ݈bzG6NFOB*cj`KVz꿺 QIlQ(MCWZ5tfjq+Cjv@'N#28L6|E w5蘮P=i~ ٭Ί(񀷪Lh~H Cii,Tr?L2$ZLH@#9ZDG3ɪC}ꔆJ^M>Q\|`>4<>g5 MH, Ej@ǒl{MnCr`qˠ1ig1O?>$U#E[\Re {#[c,Є],#/3}/_"\pik3mI}*</J]=XgFiD/Y>(K7w/P5xپAk?g`tWoC{Q\&:di8;<VZ5)]x2.WU'ʞ>b;*yavJs}Ï <ǏLEILpCYYRAIFѻOB `^pufB}ڄi bUb\G*%E$E kDJCXhw&1P&/! r*'[[s&R3D"@ b:O:\i?dLBA dw SK~:P9u)7&b%6슪!$H+)a7,Vf=fm)Q`m{.rX.B-z&!ɶ4+8;UDŽg/|HȞ;۝ITk<K|tcT!)2n}SB}Y.KJA*ah:0Ͽ}ݲD[]mJRuKO$t6Է0}rQ㡝CRnݱroH@<3RA"N[AQOfUYfcpŲG 7 E&4pӹ[w,ZʇAJ}DVk-}NzC1_O‡;:3;Uq}VdžH]Ĭ氊w?h 9jJ =9R,WՖLD wpZwŚrW(tC3'O"ݨ+٧{eH$\.3\tU˻D9kk̜P~?ձ hG_:52$[뱮maa#~[<:g;38Yx{ mNAmC%AWZQ*zs}LA |o^o<1p6<ʳ.ƲJ=}:^Ek?|xUq8,R9&{UO-5K& Ȣ yldo٨?[+qw4oGFu) ~#m GOHŬ1.뙼U_1Z8'O nĉu,kj\+e?jPZXb'&׌}pC&Xq =scl5n3S!L`sbtmۤ+"BY"[2$$>L7F1,rád=].d5 Tz7^6r)^]dh^LP^_,xEѓ ~-e7Z^iH@Oj;Wcc5lj41,%!:`AY^4<u?(}hU晶-Y noIU ֕wiQKBxfM4&I`:TX%x[px %{ZrLj ؝h`M,r<ڝ4%ȽP2;YgCDHֿ;ƚ. M6x^|YgXP D>1|冨ޓ;"x\|c4Oբ`-wlađ9Z)GwfBZk9UGQ(:v8LF K9S<[+\vFݬ/2fi 7 X,ӭwG{0OD%qld(=}ǷsMk6]8yK͜N P'y,4RKw4XQxk=}6zpvKߟ];2o^=$(HLh$0ٞx~~LB$q$IZş9YJclDk\V ǙT  iNgPf٘ڑԼۤٓ:Ljse"X!\ .JVߘ2Qeu; aP&Dϻ- i5Q6qn0$C5Zv3L9O|Ҍra&t?QtB>NiGT/1_%yloV'  #z3@Ea_f( {E8%.oA<:$H{_3T=Ѿ& >(> ǝ\ g lH>܂'lm I%5%5­'{ d浼sYćhlG ٩b>[%*I49G!U d]V8)6@I=k%Bܾ9A$qM䉽s0~G;> *lML n1U,*J )‚/zbAOd*_B}ĮKjzCA03 ngR]Sh)v.ωvQzJpY*wleN`_]1k/=R&ii6A&5+рH7}ڹpe8XوGI!!e1aoX p oJ%ނU4#T@)`,@~ MKXkEt3l 6y47yfNa M)4wFOJPn4Zv ]KwunVk),'ã]? NOFTfZ 0fFu'mR063ܕ!/ƫjǭL~L5,^gB,-kv:73tA:[^*({scQ48\ =bb"ӦH1eⲻ 5d?ȥaw@g+닔]d@d#O?oSRJkA|yo*gG;bvs7^u!1V4;sxL=S5.}5ׂY.877Aܠ-:KA >cO͜i46 _+қ< Z:iFأaթa"|QH7zeN(_zd*K]]Bvvgamy,n"=k1w,(R웥Q+7'ϸ%S0rf M2lDwv".Qֆ3i>"@Θq ?fiUd3˚J+$7H*簟Ƅ|{'T#gO2k8`0G>wtE>uڧӆDU6ߓQyqأZ[qL,ƭ߮5o[=I 0%C +RFp\K?:5GY?RR25(mzRCfhoSmk!'%sg.{S8J^+N7L`AE.] hGz ךoF Y1V>\2# L2 `&K1"oեD;̗5ӏ8L&v'](P/Թ(N TF ~Gp z}YB,Hd-&S.2 wG>;3Ϣi<L`zntO]q螽G @A~m]e~"}P25x/sn€oo07tKa6iOɷ}N$`Qv^wcrmE<5S}{c"M"~o+6&7έj5pbȞ/Fً[avyn/iH* #84/Wyg`ALO+GIi*#.g !@ R0yR0Hcb/^ejoW)NMt{pRj1&GY1e7/KxhGI6MB꣖蚋t蘒:Ad{=.@+t޽J9Va,IV{Y={8ZW< ѓo&q_ŸwUm,w9ԉ"z@#oMy|)J8&, I&[ygH ;8%ЪkE6t?4|~THwT(S{z E8 k | `S]%X-J':M= 6 k%"3 )'sp}f+ehAgF62SA}M,h!^6~9"gxYAb9 Ez=),elqzԱ-a©}!ZS⃁|yҢIG~ez ]X@-BI;mɞk:Oa.:⋧=\qjBkKT#-'<2H,gr'/yXs}ĆOu2%:qJt(R9fyE=e1A%u4=2Pb jWzlnMgn23<&XpP3PZ:kW>Ww 7~lj6(KJɺ8d CRٷO~q#^EMϒz/dOa%o˸04Ϩ;UK 0sR0%V@vqQN޹P92*G PpC= T,w/ǭ`酻`ljA㡴X> /̫Tz6b|cĖd(Pe墨#JL[V?{&]1 usD+Phs>1umƸ<ˬ_g,KnL{Q]gl2;veG8 s~JIЅ/C,ds> a >nOaERx9=ZI`M,'SӅX|imhe_ؖn)0urlrDRpM 8m.R&/N5b+e+Tĺ>NRhKY8 ե*܍NHlU.wvY_I3{59G̿ mLE GlҾI_롙`aŵ}I0Ψ~|š, Uq9.oSE)6HŖ]|7 zx,w0߷,Nc9).`@=!~lγp҈(1>;{4'H]? w[ 'a~Dp fs$wZ}¯t1AdeGه6i8+!8VhCHI(np} M[ ]W S1,0#|jwfPD8Ώ3|NDλ>u2{p[;2/h[5?/G@׬ץEZ.@6@+U̖IV "hkeDt3x\¿WOYX\+(pbqC|tpսut 5$:\rK c1 g]UCĥz]rB!$B"o=ƾ*Z` ?կUث=bE-6"4 qM/V<'t*,ӱ؏&9Q3Շ`wmGs5aRUD]`BMA]&+A}XUF*@|0T9wbU]HP!:P]ÔrG!#tۊyh@Eh&a Z^W@Ao:(HҰ;߅".ĬGL$,S%balsCed4Y!.zH#QYa2vKL2$~aN]&НM&I> b ycBܟv@?؆a4A|YN8 OHSBȨۚ.|{q#U: Jۉ2P(jB4=hgYu`T[9T3<ٽM_0ˋ ̺ t+Xevf:vl"v10ťenu,dV]ylBuSuRsPTuJS[ GjR.qp=.Ŭib9p 1e [@h*`lm\+PM˜"/is8V {u~-.uH_/9 i Pa0o`.?OU>)J❚aVNj)$%g }|2_'Fqj>gΰMZa`e0gHEHS^RAY81tK(y#߹?B"6C=ط:Y^=fe趡~]k"XԄ]Яe2VˮjAL%.CW:GybstE='о` L,x]p&9-)3^t(ÙKY8e6D\ɟ;SM,3BeZư \< sv5֟5} e'FBCЇY`T4P]gRx6UwCۋI)@WT,k`[hЂR=@mZAKdOú#!ko>jtGϙ3U|Lx@=oy&&QV5 X`&rΗo t(HFI\1SAJ zLWҎ@@”z}P܎ L/M21F9tI~>iD{΄:{l1m-wJH4~`JN wXyеj|@{mqmL2<'4UN~0Ӣ)ӛ)U]Gו1f(cBp2cL4Bn\*6BJn@sΊw^XΜh!Ƕn!J b N9eh.>$oC+P_QOC12!_ *R=y\׮cgM,١x4N'hc.^(5C澸v_K{>kϜ1#_ߤ|JԇJl/ǃU)uu!: SP-#է:0H ʬDBS&PMx v5tTbZO`z6}K^>A6R]OHTVq}b~*gd?Imsy <UOVZ,Qh\={'Ta@`F4l̘Lk}ƯW^-'~3 pa>_AЍ8:OЕȬ7 688=.>]Tҭ`ݧ+'ȇk# a+a> "O~[SmUsĥơ"K{6LK;U&5F>^`:gyT캂-:(BL|\HKFb*vwd5brxiD^[W=*BYZy~{=D[0Ɍ=9Ak,Ua쮜 T#>8rR~&AzBwSKGm˱Sf]D/ $ KDd,p[ 'r&Fp!y]@Ky Q v_+kl`w,2Vŋ*a5pZ`\c]&5ll]5_!b4HF!6(/n ǝuk4M>W'p1x-U1Y-8O G cB4 $1m)Us՞Q Y~Nyp{~VIcoU 2ulH|:p>ËE֤/Q"dF:G%V*~]x'} # pHpٿj*>59upZˁ#8l!y$O( IßZ #hc JeEnICVEWӟds58yוl^#C1|@&hٮMFyj=S ،S`[;pYկ_ئ? 4J*A$ݝy[E"+bpW{wNMA= k `z  Pf#b|m5ʡuS)M'XNÅ'":vLnSlKWnӖ>:( ?G OhLdhXw=$7`/>k b2䜣p[Z}@5.J9 OCCimT9 zS/ )פrn@ҿjMyeEYhWKnۉKT|~}RY4B\a6|`wnJVeh~O `Yn&A\6;8/ C\p*SYkGyϮ> L %Zo?vT,$ZM9 M ߀XrOpJ8J~ 󏗓]vO :gLEd-"q;ozR8%DWs̥ b}W&c!5.jEK=9=CiU߮wmvsg&&T 8+pVB֌Cѽ~OWBٚ7\o:B׌UZ-*1q l/,$ [xYL Hj0lUKn,i QYKn֬$\հ1;Ol+&n3ĉJqo{p#84=سNoIIm{N%S)y}uXRÝFS^D?XUdZ1p-=^@Ǖ?L>7 ǒo JE@6}Ef➵  zXJ֠[F,~x8ZIocT 6W'ڊ!}U] %9HUym_tlM"6zÙ(e7 өC',p;38 ~ze Рn(-KyoZ?֞1ӡɇseX?T4ofZ&▔GESaǔĴ9 6GyTjn^(zm Ҫ1K+Qɝ:,;HG֔b0^+=d> qC! 4yb6zɫ &iL)XeMo7WͱD fb$64mzaZmi̲EqŔ!GT7:{ir>L8ZBO7Qڸ-P9'\'o`ҕ 7#A ٳ? :1@ܒ%XPEDE7Q:sHmѳw ^0aުWT]cC!pj"M̅JZ'Jh Dv=ZY/v^M53Z[Ũb=z,R2rlmHh^{5]n./»LGڏ:=Ȃ(PKsFP݈P,nh%NwP]1" (5SAk&.7_hB_xD9}qM,*eHYlv.7!0⻈ Ph١y ]yFl)\p Ww#lv'< X~P~X d2O}&& I"yô ı5VY +P [PYAp>"bLSL Z9;)1:$ lmj-J2m82s˅n=1_ŮemK$m.@j?Kv5l-GhH#|G gk0;egFr&qosFbs?V4Ǽ)_`ߎRڠ b/g`t1 i/fb"$3of΃A3$~Af#׷5(RkD YNMMc\@v e-i(fCjbR 9Sކ]¿dVgГ&0[Z$F5?W[*HD5"xub8m'ݙj{V;E:B4{QQ7Mc˿5!`Ht9d2|Y^#yt'o`{@#8> &uMDkݩIIMB }߮^u:W1gAnxƞ `1I;]nNxd dVx:kiF^2jTbA ޖc5+h8NO뺵 Hpys):ܯN;dOSHr30[ZT=?Lb04lOۺ==G|F+E$xLu:狃8j89#]M*hs^?L n<+q gF,{MB^5Dq8㥽tIt]@ZyX]XkUW rrR?QFlhaAfAiV.e%|m0UJqqA0 F6M1?&Avuo?1ઈs)ՠ(%o7PA8PR'N=7!&>WڈC%9B@H.Q=w]Opf*>-5c!:Y ¡t8$,ɕR "Eͻ VhEdFf0:4؀^6{b*dJ@tɶ+oٺcH3G*6ՅqktecLܒ2“9a_ee>,/;uh@Ơ %b ^qh)>#9h+'cqώLVs0S]{*>$ƚDCv),jv(ȩ.Ti>g CiEZ'XPTUs(E:r( .BMrZp%Z5@v[dr7 _"n%7s|BucijUY3%K '=^d}u,>E,X罵(ҥ{tdjՊ? j;ՀV!|*,z ܻ"g 1bb rHԁQȵ|-y?-Th^;p-~;7'L^ބе ,>ݖ3H`_eȁʣU6{t؊_fHvj"^kW\7&ڗ+mFͯGh{գ L[DFo3-TdA6s$rC4 m}HVQXc - ݏ54gPϣDՑ]dCp?uYrk;P|?CXûL%pŴ-J#F1̀z}sn1ABC7 NFm!PY 6&Hp[/Vrt?87̬f$VJyoĚPd@.оBLpT/m?TUq!ljGYL꡽]C䑣2_=2Z8{f9T!{gd(UpcUТθn*ۭ|*@;$OΙI}#w m߅ʈ'e$5@z+@KT4ȫG 7.}|Bf?O=72 Wf]-ܔ+@I ʍ6!@f(&eW!znM \91&82#B't&zYx͕ub"`MO +>5!k._ H$¤Ä+|~:A _.,R-c+o5,94G wu|K0g'V`u D_v꩒iAnnX97NKvMُA8?Ypp #D@KXI0]= @)QXb-HnO[A=lVAH|VzP秏a OX06ŏ c4;`*צ90r?՗(t}=(?m+0ݷ[mU f7'/a .5T$:˿P: ";_0 H-V) nKC=T7hGŌvuR`˨&@l$V\Ǣ9& ÚmFO7~'ƷeI-X PKomKDz|[%j閳Ղ3st%,6舃|{(;^L{t{H&ѦmJ=BRPSFdZN]h?F/b;u5GeC(򼟞l f,gxmW,1H"ďؗL!^<EiDȼ>Y  j8YY`8?q fW;9C@ZJ S\ E>:! >zG1pOLp*fqr=(VYxݖm,wnt2[Ƅ C%eLsbn[&Qf>;0h1׻k+JMғHM0*r"sŒVoQXï:& ybqv=kgJZJϸ̃\\C$ 7Ya| 7ʔ<DP6& 4|p\cZGPmV@yNvNہ NL"؞ G$d«s!-i)K:pv דG8&&hW'ZtI[ lROpԣ>Wzь%SzE5؎ie 51fgAj")\9:O}\H;[iBRR<S"a zu(mC:怴>܇}@IpuP217A|}jJlBF:[bTj><ڃZ*)!`K.5ꨟQz}x S= i:g ˃ݬ$/(@ܸAΆ5GwOiW0,푞%p@nǁ۷^qqpCDV{Pfe^`|DCT@b„ &ۢ85dZ.zR]A(x.4G!GKJzXLVrpQ8fYW~*M;q)!:ǖZL "u)g]$tynLɰhgRM(8 gセT ck9!𯀼)QIFEٛY(U)((/3j+.hS/i)#@$gQeJb 3袤tʇ'0Sfkc"i!q$nԨK@6p% $覜:XBםTM#?{Bt Dc=-&z)bZdo-G[SYA7"=Nrtƛhz,L{}a Q w>p3N툂JsP#B *_]s#˰;3pMӰƏϨt zQzlQ=~ +ӳ#Nz\9r2ѯQ%qRfcoVnAʿo0"h=nVSaM4o^:,(޴$p).O3b(%-ӆ+==NnN L o'Wq~t+DGwT0t*E3nS C{5aöJF9fФ6\L33IIF;yնL2Ek,efh, LK?u 5}Tф| qnO.mkTaf.WpP%mWo/Ͻ/kNSQX9T"M: ]j+z~uu+鞃`V:_2!B2u0~ݿR+[C?)sB}24]GP!sfa~w*!_?t <E%?v,bb\ _^ ^r?>!V\pV<zXT474 ruMŨjYx#:#H_")̥Eo[HkB9a}2dkTܱn"_6G6_qEYw [FmTC}w7ӫx sKb3-qF NPɊOIXD h0Wq"'Dϙ֎'y8wĊ3 "qOdr5|q;/4լ%"ue@~r 7"+2ɳx!&x^Hu Y30 V|~=~ǫ#-fCBm>PGgB0u8a8X$s=8R)V8w۷MF[=7cs5RQV CFcSru%B9>XQHRCnRJ;)9(nҘoIY/T*`N"TɁVyb9~ : xw G b{@ c: ;c ~39_0tMJ>y&z#yHI"Ԃf|zQJu*WDL6 iI B1[yM{jʪk*E3rS›C]b.h @ N;enS% "{ 25G~H`ejUBeSIUߦ(k$;^.7TQd"n0&h["p9IQhccXcsU4-̵& ,D b8<#026kNU%8*~֣k ОkomT ^*韩QZAjOlӌUuAk(0N==ˡw'+̤;jb :|%O"6d]?l \7-[w+IaREDœ'̨+, K 'rֵFcg_n7TE|zݕ,diQNBUśBۿZI()fh-58p5jDy$^`(4ղR?\wpqdUq%ƺz娍ro<up%.hS3N6>3q!ˌ|p2Es4+u"vZ P8DD!G f mmH](|b-i, rn|!ŪIoYwM1Z߹(Tx6<߳+M О~",GO~In ͜t`tN۱%8%!.>s~*Ec(AGUH6Lmʠٌ@>p ,)(/(T~5DR*\= Z,l]dCiUV *vR Ih>[QCޙh]h8Rj<.ٯ=z4\CЉMħ%ܪM Üu o.{g2u>Z@*\wy^WQ7_Tz3Z-h4Cs8m>ձl/9S.rJ)E)h[8gj152%PrIIi<5 Eg3}aRh'pE~38CJyyI9"(ѥ{(Gd8=Ė dN {A$^c(]?{ /G6JHN~gq}3OJZB׊D`.dr3>쉘,$_u #m,mE0\+SfX-J _ű[o-5f ;_Ko*gH4((:.6ngħrAf .IJѡzY]e&J *=J g-AQl3:hm0npq[p6"u+x]{2׹y`4ZV S@|b6cBܭEٵ9q="ArR`MS*aZj)8j,i3LM."%I8Wy Pu>k|̨Ui`K%$5ݢ}|~IF=uYWDJ!U%/EEΏoL=8fs ߬ў0w҉5MW[x1>QVxZY=b{EѴQ.LHAx?=tOr\Bz2̫dN8/H/wem.}gƖ%ES]VBu1ѝ*7ǵ1ΉIT\0HIgNz|ulw:p]C L%}מn`TX_1kNzja $fJWuCG#C?V{qA ܖ3uxyHjXtn|&zbY\!ܺ 0co6`_\(ANNÊ zƶ |r]Փ#;Jh)vSQEg(ЬS[-Bֳ9n.!|[+{PXqfB bˍx>I+Tm${0UluF "d~p&ˬ]6xnD:7[=@,!ỲatOQúcxԷ}WrEm+u1ĄB8eNߙ]f`_VTIP(;0'V&6u3] %% 1 9G(jk ?Q3KbĠ-Kx~Y]P_&`QďXܟ0e}Rک0"mX gZJɪzQۄhTq삉qՇgơ>S P8k,g3'Z0Z8&&^nwqbAU|V (} 66{=A옸U64_xjVn cO 26Զc5|:n0l*l/sڒ(An}DV.*-N,!Dή%|kH[}Р'.DjZz*i Rs!ݵ~LI w3eE yݗGUoz//U&!' 4t  1X@@#j>8jnl;z{~tGD1.+ze-}BZe0̓\ˉ@e*$:|~mX˛{^ Zj߁&)/vs1pvPgQ{EL2۟~~<Ќ3\%̞ PZ~C(0ܷS)IJnZs7ا%ݧZot#]r(29XgW՞30^n}eizDFlt\adKHvjrOL+` -.c{kFW ^&<^cجE ^vm) VdKF1ĹR<~3mh%2& ddh=Y&BE1X?J~#l<+e)6[2=qĵx;pSMR3A/oYנ3U))1 I`hIq(H Io\Yb6{zk06HiÅn5c!!6M҉$,b,@ΥS>f\lW-hX<"? %jO9tŻk!ˍz4-䦯Gb%MCXQc.';'Fn^Q胥]%|K `N ޝ l._ %`xO\\<8zs]Tccoߦ}! om@V$b3/2LȞXyCN _cGncIz-YA&5!y1E%db%ء& x }=28u]B=6A)ovLjʠq 2Qe1CR(jwNq.\yMӒD\O]Q٪xw9='?oMTZWhI3 z2Lp_ 9.k Ep$MkvI0f.ݨ +JTU3+b׼"VP\ݥ/rgj(xRGy}x͵?cdB9=Vu[;>.Axd-%O={|\l1[?wa< L03ep1FW ׾Q2>oc%1, h&i:3lD9=$/qժaEK?2ee+[7U1HG<{>͡sfQ1TqN-.i Prxm)Q:@ό0/$gq!ۓ5=%){b̌YagrsP7Cxny2HmcbP@p>ǮyquWʋd=Y"CUVQȚ+lZVӸCnYê8C`XZ{9!p7# (ƻvI d0Fk Q$/2X?]1*pb1ްqehszZdY 7Ufo \XsMbf~ cE9rMI-lVPi\u&!,Q+#2Gi_"#A{KNȘCFR@p@@m v^M2aו4mJ?qDX]Nndk^9J  ]55#+ԡȅe8'+k hvk#mה2$G 4Fn3O1*Pl?Wp)xfb`ݤ}@r6 e H }o,JfgxV]? R?)tɰcdKHBoDWF}v#aUehpfj x"zr+ixzE臚q4t9 [oM'j߸zǥsrTX@fBy4.4l]$Aw30y*EwoތdIޚըzYt~Ks݊g^~7_Mlȡ(~޶kLnZ#?_r Lnڽvb[iіa: .Y zmf@3 Qq}^6P6#Np}@O69Q 繼`302mtp<+b q*~j4PW+ſ0A^-[xDa$n(XWe5Bzq٨?b"gb7*6POV_w\h§ F9lyL!sgt@0X%j^F|c5QWDŽl |9 -Ł] z:<ÝS`F՝| ^qT5- $x]~qO$ F]UKƝ1U[D/\x?Hٷ&2D(TfezqG7Eɉa8NU#v> sYUu>ةDBO=gW= |:wՂ8#l;x$9+CN@)A;V7ƴxc%UUeXqhK}WEu 6)!3ltN3 A\gw.GT%`z54fE%0BXEaVtx@T7RwU᳈}HXxTL'E%(.Y( )-V| \ϹvTz^+R#GgOuoÑlw?>3QT)r+zIg{1wٝQADW;(9{Q(`ͼmHr1Vg1ˏ Ϗt}8٬xA bS{ O yP`C45{REKo E'0=RG )*n=} SHirKa`֎%@- T;2zg ˈZ3JۗçZa;ƞ7ZV?椨}.&B}sYA)%HcO')![k;aDY͝ɺO=]./..GEz[ɹuV 2M~Jk2J±&=zQrwI'ܿ/,y"]Y l/(mu9\5);֖ i{SH *K ɯ`s*5LVwqD#Y5<7b3d-stiEBQkN+/[SԤއZiA'7tXK9 ApRuxa/ ra?,p}wكDfH Ycl(KP)%}Dld-?,$@o0,hu0vgIX|s]DsoLXK'vAtLveAB@T lX )`3+e(q̓_{Qe(S%Z%G8 0$8{NEףCff59͕BH+lSe0 &x }j7\#ܽtDrͨHj2vȀC v*ǫmT"b0W7Y! "nWHZyuQ96,{008Q_3W"p*̿󴝬r(ǟa.y18Q|BՁG桞6#B{MV%eYf ^=$\6H ckf:۰E ͇".Ų՟ wQ""1XG3*QZ2Q r8&ya >|ڦ㺤UC̹9Íܕ86 7"5 oῴD%@Ɯs0rHu_2=gA9OG>@#aquRUhYw:ߞ٩;u诰 k8WSh|hmMe4LW*_lKZFy.X}O(E^ 2,lz-*Fnv\*-ʤc"^/ Dpp)(Ix&t/sc*+>sՀjr~aLؘOZ3_R#mS&՞zSbE&%|Y*~lWӂVR#s85!37KyJy)+t!z^Sw@>rC6IfJ}+0 5k`ȶ|5]C]hbx xQ`%ɑS!_گ+X"yRwr/܎s_p>'Jq=M'SAtx4.\}K?7#6+r<d+o68j()s %pM +%\L+lqO,9bH iO< ٭L*ǂ:)k{`/Ҵjg4?'BXLˬg(eSV; 7$ǻ f.B}RJQ9H^,')ȫe~C*Rˆa=c:P w8Wn2Kdpo(Tܼ6پ#D7h.!Vi_=wGdMظ,mE|G6ϔWJݗ'~.ə@Tdg![\#۾^8P -MxK$leeH $C˗d(8Z5%6vsT4GٺCL-Np7DCI!E햅̩@I:y\E)3"̒[v"uhbKg.߈,c\?1 &lapz廜b&3-&q;dhH< ոh"׹3tMQ$[rШ>0֊IMr/>2^{ 3:'.yۺP:xd YCRe%S:k؉45ai n"I f1{̲~Qٛ޴4# C {-I->8мv2=7} zgNHlj؟2 K!L 2\xza~J쎈Rs^tSL;zFO,qNUwp p:atŠ"G`Qku0CmY /aҪ*_bmb*ێ{=1guu̚܁Xَ$>)b*$:Q"YMkę%3S9]heO'їѾB<ۅ8jWP$Z32+ (j.nVհ۴چP /ni9 xWh7E]F/L 3ܽӧDx7uq[5}iKWJ]ԫ'.㷴![!Og"3yN.@j^}1CfN1E@mn4 u)匛; i#[-~lQ(j&)0cV[ vJe̫Epp@H[PkS1ej ,"ꑧP\ˡ^ w$vf1}Ęt]BtKcqf"s@S }ו+zs|+R{IRt 1Rz9_P`9$$yAw7Hu~Qf/_heG?[q>++1mr2ljY A@ɧ-n@f!~#+4vr6fF3k U4:>eػrv>\Bqכ,Uk=3͕Rژ.BPԋ2;07y%I4CTqǹhq־'&|27k] ۛX.!6DM)/]JF~Q7RԖ 0Q> OaOP4.aGYA}<B?"L?<*4TJ.槠 B=Ȓ%<_@?dߔcΠ֟݋9MZroǍ8N"$LgӨgwO:˃H49WOS<rc 7%6kl&;!r[;YV⋨P\$y; v_Ω$ ϳ-s@$bs2-$Ԝ>l3q1cmq$ӍF΂>WT6el̫l O?u]oIJi ĮEը- MǪmo9kt{~'DdL~J$G咮\lz7V5WMW˥䢠%xX1 Y4 ?՞ =5XQUQ jq&;e#=?Iʶ]JO,zG IAv3{t']nhƦ}!D1x@̄uiY `T̪)dHLgD%(Z32'u]h #Xv__GdS?8 2qts, nМaBgpTg wh.gM(y쿶tk>n0IIzh?Kˠffƣ'ZpS=MGB!TuA{ANR\pV;uڜXhB1)d֣Oto ʉ jv>аKPX6T cɰ772\؃#csRis+q "UxoU.N3D0JDۀ嘞 o}>R"C,:;Gf POW񛓕?'x+h)Jj6,k]h55z㩉a;8ZL`CqmPxU@dqyph v|}y(%W pHD V;a9X΢NCjHFl&YO X("G/aflЂqDRFSbiV85|ۼOlk*j%Ȭ_1b OO4( Hg}SyARٙdX ֘8zH9j^C\{=q /ϝSK*|("t"1wB9$,c:6F!_8R]Mb*1AC28= {"a}7N?:iA{2ikgC! 4K`T^y@[HŪAJ[xh>x=~wBN%UpdYX ֗Xt#k "o+؁UtA#.`4͒ɫguMA+IϏ[ٴ|wPz Ծ #KnɗNeTzjx#/DUj)neMJtL7ɤ=s9߯@9r ݊e `c ziQӧQODxL'..InYDŽsʟ{ŵlWQxk7?|M,|HRf{UG$ꖩ|1_b}_RTA3+ ,e[FGrxnUUj3z0l3: 77*࢘A&0ampL"tmVw:/xzI\!2>Ep$/Y2`i'M5 JAy1^ɍk ,4|HIb8%!uM#GΖ HiN*PC7xY˙ZJcbc@CRŻ)uc3۳\Trv"2"@1)+)FN@xa_ DeT4d/DI%$븂/iz!끘g+9I5{w+a,KyYj/JVd/8u\t+jCG'LT =U`tz\PAH/Qe]hɇ!# Gˤ+QkD cXߗӗ0WiHL{y6i}i>!YYΚds5.?m@:G'[ ϯ{' 9\ \@vy0zb PQUd *d\W:Xy:e&ai\_Vx%6.D"u3JIKZ8;9t xQ7A<#"-Rvr֜1VsɎg,$ciBItEUZnR(-T[%!9Jl+8R˂Y1[;ӈlECFkji+1@ܒ~: RV z%}_O:yZM0 @\"hJ}a5ђGP'i9`4um -uDuy_H켭EC#5OeAQU0xoaw+lJ:ߩ"c5q4'$h;鎶oh^M?C^d D'5{e9#K0!m!E݂xv۞0Fr؝"=A_P8 !r9I]'oeN8$$`v OO@mJRRRJh z7/_\7hϨώe%UV $tJzwTZhRK޽&]kŕoXU[+.@WynZAx;,,*$]'_e7?FhJLE)zdy[[wZ̘saa~jl2zWRwN\ } ő]|R|[Yh iI2Ϳ{TLಈxCvn~DpL]Y"'mPH F2u}) >e̡1Rи zMØqssk=7˨hE`cHbw%hŕ1Gll)jiYI:GQ%_PҦw>Þb+|yz5˥%Bp[8sl[nL?Sc-;a8jt }]8υ~FpYǏ G\e#2Uޙ(}Nқ{x8W"d;FacWdb10;W 7NmW/ekm⍺Anmr Д\ď7 }@?Q՝ܪzCAbQع=M!ybqkzݩiͬ2К4G-Y{3k+pÈ\mnb<@_.|Gܴ.kwŞ**PLVvYniN;~1V^wȴgU**B|D@MAX(y7젳sbb1 Cz'@1 ijFlxs֙)I(KtU36mӄZw]&-.u?4ǫ[5De^Cy@-) \Z6O9Y}cyɢ[8 ˩!ys:twȹI(0˘r .?my cB9_UvRd2 ԱE׮")!< hesnj<* p:^taq_,to~ݡl ,L$N&@4֪3P:](-8.Xt}'O&kOC]`N&?9G1du5zZ-izΨy*LpuFBC*5ǜoo0 ; 7}+"T|'1N=׏cYwDS;ԃntfs9P=߁$ n(g lz"bQ&ЙN%7LxdHF+@6_f6uYoP9؛W 8SEaB$wEvyl6)kYi[šyΘ[vi\ J$$Cly1*ˈGAzlzFNZQkG$>t rc {00ݐ\)k9'fV `&-B.BRq:zz-*INƶZ#ƬE[VhTȄURpw5-b4#sT)hC3)l8װc)?T&tCAHk^hj_Pnkd"fi,`8]\[V*.eM "툓4d:~\ڹYv 0|_)w{2)]c:OYVb P{zۓ.3g>t8 .F"Is&΍ZeOôQ=_L;pE¹QSB z=Run!)S2Zq`Z?[ŞjNeCV9/؁d܊DS?rY1J;\lC R3~9Gߓ[Y ֹ^l̘xvm6C%bN0-:]Sd<,Ǝ6j/C9H:R9 цe_u6!~~_rCBS3D$,qF!3i8<_m?n-'+ Qj'!1=7#T ›,klO~ >jύ:Krd_4ӬEBe طʀoMJlx!w5Ґ%D+J*k B౔݁wpAAPܬ4k,~xDw^B^%K ܈EW8[#9+{4ˬM KY8V(G e4zlA`rwۏG6CDoRGdH(vR]K8`Kjĵх.kH`_EoEA b“`]ʅx$^h/:# /c"D60HiEi/AN˗!ͶێtdVq 3*]{1Oó/fǿNj/|ƿc[+x Gz#rxYV^NAiAZf͛&gV#V!UFǗ77&Hc)$@Mz}zn!"🽋 iҎ0<>}H]Wh 9J*RZ L֩쎐ќoD?X[)b,„$8[ Mpr+ъ"Td?ҏ9DtbA:۶F`Д [?~uxPkUҀz76Z$ M1ePi'@6^7Hq#,dLЃEXgҼ\59H~.ւj22󠽩 Fsj67/t:N ?Ȁ ;Z (pBIWF$ wo%%,SSbZLYoM !/nɹ؉^s(MjYz>d'PgrcPchiYXPGk薩(Tj foU+|\)(&F -x@!mgzOmKq6g? >F[$sJ5yW ƑXZ6VQW}IɊ0-}H;6BA }BL'.LB}ԮLڎDEkb׉RZ_mѺ¸j0hMb+;vdNg^ԕMvDm8x rCghLiEr[5e{Eꙭ>6QW\7D^EW'SIn,Q%lY7F硭-S_~KMWU28'k@u`*A0!''ŨN5NC[F$ -b=8#VMs | ;hM8=r;+m_)` 0}~IM8]3l3_ p $w5}LR-T9vQ`b[*g.-or}c? aγ+>owÆ#@۲eE a Ch*lk-ܼM͉tteDҶ 殱(.1=\0u4k1>9ׅ!OzFloRJݔm;x9Xe6,~X@3:ú̓9WX/ŸG6.;٬|Ufrv~U%T_&;s kXAMV՝wFk, \Y=(A)cU@Ol# ;RDW8m/WSO+\ 4.*~vd~ (WIJ_fW)wDJ,cðNj'Üqp- bmO ٝD覚! E^>`yztve "t/7ToHD;nYl <%_ wMvWbih}v I!pKkbM EQ[Du07xa;yI\i-y)$.OnM^=]m:?n9gy'R %f,~zz[&m@z]Vq #=#o ?!nIog¾$Ce|3!tnyERSϥ}ϳDg&ndKJf[ PM*Lulc@+J s÷"l/7gK 9].VJ/aL?B4xB)v.!6B~*NtVM,22Aa&p -](W7Hj=8Ӹet. SQ}l@|$x\${*$|q9 >i>ݪ h A]UϾ<*R,j S34?8r?<ݵI/Ȉ31ZY27;d̅4 :,$i*wNz>ZaT Ǘ^z`HuOӜ'm@p~1,-TG0wKf([%Ei:sä8s!Kʃi20BL4$ `E vvnqtt&mg)nx}!yQ@'c.K)1?6dƯ 'ٓU u%>iqqկ# NP6EBhlSc!ƫx Y<7|ASQP)_8 oGNAjZ+b {--,eȻ{U|ee+ CVuZd8ӶՄHCpbǝOVfcl5ƶ˙o?u?_3xMOXJ}zVߪ!pp,vkryvj@6w4jB43S`)hBa uWPB |S%1jDyZ"m &˷42ş'3f td%|e0U?MI-:jtR{2gTk7*J3 |CSO f {A;k7n=ӌې@ELb47&jnEbd-PPb0uux1BHE?tܶF=龞 |~aQ:̙o0Gt_F@0Z>uF8,mB='>ݶֈNϷc GZB ޼I:?/ m)Q`IzEM<MzJ]ْ&gLMl̗srCq0Eô,><ᇹ><7jUlVhZ $*(y~ Y2ҥ!A$=e4"HZ6m]YkTk GS*z&Qn S!ml%(fv#viտ^ZGx(ɽJPuDm C-` r &cIj2O\笉Eb %Z\L+#EWL>yN,x$jJh!3@΂򉙙䕺k Tkva(ʨ"dߵ]?JD+bWRA[4,یQ8R(Z|$ړ],cB>E0 - x!BB 3Dc}' ( 4O%-(m^: ^:),ZW-gm.[x9N#I8[mĜ>Joerp)2PNT*˯/S9?M VZ5dI0O@Nϖ AOUcy@Gɕ{T~ 5\NS=ʎ9ߟvQ?]*,ch pYS7`vYݨv[MWS&BVDTnW#{ۑدIiQ_w9G&޼!TH K/eWLkj&>= ՜S:F]J,|؍!̓6F#vverRƟDfcBĿSUwTA,$#IGs:3D]E"% ;jT:[-xa6hn|?g 3NJf9lyEܔE|u/(l~Blxls'f^AڪD m[nz)Aw=`@cq^o*ӿHjRZMk245`,$~,x1d ds/̄}',\`uj\rdw(v{<:Aw(5UWJeUXY^ḦX-'D2R+?y \zB rw6 vڸ#_Uɣ㺺~䋩.%<J(Jɝv3L7<#q;nquca` 2:_ Q{GZjb[T&_x)͚ZI#[hTZ}E Dȕ A؅J  Km#gS4 wݪ bF($Z(:j?Gq4ܜE > 1"Js6u3'Ќ6RaJAЫ]:VIB N( #2}\BA=AgLu)]%KYCFAFF:ej'C dqa#G n_*:csaWtKDuzHwMx^qi!Ҿ=kB+}$%CJeXٯ _Qyښyz(BUT2K m3b>ǣG\Bzk!tغ W7Bvh͕+^(/o#&0Z)3R"$P\dR4yrx :v;MsdS"kӧ5Pi|wd$=s4@˘hf5_ .?oW)PGqҙ / *ao9jf RՌT /Z9Oz/ _a}BBwB\h+w_M97 jZ؉3c'P5JMO f]dNv0ۗVd1(JUfjo`!_ynS@\I=y4XoX|$s;H KXWxɉPrܭ\ =ڀ ti^Ԓlr,櫂;'I8Pe{h)$k@;gIRܢG,Ci)(0U gt8+D yc+ԟQܳ:#{pt"Ch1m]L0c9LǺآ$ս@[ Wd\7C`V "? 1j),/Y4t\߈ 1$|vN֪@_/$p8Bogw^%2 bOm:1 3>[#jDL'yk҉1 J@ؾxXeD/gFj|4g4 mӾ7X|ĭH^.SFt Ja&d2<Nͬb;"a3F+HN+A :‚nF.?u-֡:s.@=bNh6/jXB=EflT`oEg~ 1tNupz/;RpȚ5&KҚLQIܥ!HWjlM62ëIiBd/qFam)FKS!-~XyFp0E/,E\RbU( `ӡ銇#DSʧ&0^PWv$Nr6q|:]:wH8ˇ-#G\:,."_4I"w(\4UAT;6WO6%oa ~:C <2AO|sU5<^Kbӭ5 UQ8YA(F+E ܣ gH.߻{Vkq%++k`zxVbFmRkAd h+A0Ag&˨)|_6fuIyҰKO ] u5H:ݡF# @8pfQ+Y*Yv@U߇|!X? x4K9f&MƊN|N $L~}MlʻiY(A> <@0m~s4HrxD7o?$o,k-1N#֮#%tZyU#'5Hɱp|ГQ37Lgz[ܕvWH=QiM~a1pptt'ͻ+# *?U2(Da|doʢiN%A֒Osy,TZ4?k!n o~mwαJZg`Kܗ1)t^FS)&HXQ}I6]s6]GNb%*2SPønki^Z!l| @-q,kiߟ}(. |H~J-efjX5;H~{>KbǦz50]x=r?>4j9%y{ИRen9# Db{Zs+{KW!ק_@g{V~ Y}\y{!!R5إ;292?xqMﱰ?;:uNp Lr`h(ZVPlXlܡΤG# -2ٍ7gh ~|s[CmW>}e:h kH4 r@N .䳖|} vJ\F:&\Y<`wZ+rn)SH9qe)'7K5lBv\=,A4 C&t+fMM&ȴR}} ր??zHY? k;Nuƥȕ Z'v#s)iEk@Ej4xQ S.kI-ke>Uʍ52UR^Ѫ=O,# = ӞQdDn:*:L\Ah[8CG+R᎜"]gM2l`1vXsڹؿO^U[UEM[EY&:Fzǭ3AHG+aջ.DG)ۃ&kէKޞ|Y(\[t:&pi`OS$q8STcEmdہ0 l$w`% k~Ŷ[T) -@\ 2XZ5.wU- 4SQa43AfDyEɱ3ס[ ]~$E|,@q{*@,+2[p2IsU6 MCA)2QU;0zJW& 8_e!n&$W8 Ǚ_g~eKXGVRkQh̃t28,닪2MIM=S|̤A:7JPK7|7QahD&™QO9ds#q{Z_Aek7RDⵇ@fQ#hA⎺7z2²,HB={I" ,>v ښ|S6&vnm h9lW[/~=<h/l ME@{%'iJ©͒kw P)BPIYӽ#\(o 3V*g͂E_JwZh%,7rkAZ9M4qϸJ]*K.?Ueh∘ܳ=\;fWaa8C4z8btBeOsE 4 IRw&@9,VϚ&V//͑AEJPFPB0j!C[K(+ҕ^5 㫘MFyqp;>2 . ehb?gsu"#0sk Å./vgΟ T,xNT;;4Ij/lqGrSj⎟CPCu~fd)ʛ)6HӹMJldH./ʘ79wTL@FvĢIm z)\iXw٧7(\GD@|; ؛2W}k#;Uۚx4\}]b]g=Wb-!n JLq9(ZP;ƿa ;ܲ·GF4لS/X]ҸGM?˶Is '[hj;?T[Cl"ق+z‰['U|gzBs;~یko-AjPk0]BѶi=qd!˱&tVQC=,b|–lBށ5xieTFO`ZUt6!!;<Ā"$ӑ!aQUS{.;۩g^+_A p.ib̘\s E9z8Xu1Gߥ [b9t>In~)˰tcL U6|e)1=뼘.*<,u(@/XUɹPu6Ík%fHT5+"E-=s^[*RB!=E^xD@cJ;<U.VP=R.#w충v8NmjBS+{gT5c"މez` v *4CZej ;N}RdobI=KK*[h EK ')DxW6'WNZwޠl&@$Jl\2X ņYZ1o#! 6|4Q) ~(aq.`=\d|v Rko}p([[ɣ&1ɂ`I :H(cLϰDr'*bYeCj}}z[veE5u&{6(^82YK9jFV~ZUwp>irS"7,!-H5 TmBB@4W2}5)?"*Vnla^e~yf腝,(*B .ΩQ%Ƴ!NJ~ݑ:|fVwF-}RQ,'sVtKJ\W+x]0xhS3/g<7s2"gN `Phox_ya|| EfȺ%\`8Բ'Hf#ð~Zk!k(O7 \6A,H{m{tn 07gIKxj^CVe]~ҿWmaD|@#`ЫL3hQfP +(/j}Vs7v;B6>nF zZ_ۑ7{3XfC%ޥ2+0=n ޏ([aCÈ=1ݒ@ژ?_jvaTkBGf.Lbvsw[O@vt|S7)9-~(o%,r Xۺғx)ளKu CBk>5^Lߢ|wwospf58fuFJbN[7Y@Z(m250WGd>"#P9[KGVb y?rJ~ !sd;ؼ7\"TE[+5[e(ݦӈwH||dO^]ұk-dl(Xeb9$"Tq A&o ɞ{72)8pVhВ1rLhoy|$ >_2D#ô S<ۍ0"⽜rA_`K3eI$  Sÿ8waκSr8m~٭LRbCirs^)s6U!}DY]d/=ukȨmfvop5S=]bm;pK0JMƿa  4l*9Gc.s/#ٽ^uI=V y/wPɝ*30 "x~Vy^BW4]lxIx<l&ځ6agQw溷$b0<~\>Z oz5 IN7m~kq~gG߈73 ۋ \ OaDI^VvmFӓ S _ Ik0--/&) [_$`6 1jK09>'ĝ[/u|SS*)T ;0q?~Uٱ>b825- DY4X m{P/ ) i"g~$Ѷ z~2VOd7fZ P ,NC kn\Q3kC7ϸ=U ?p¸ pk9'j@.{S64~jJҨIj:Za{FW2dUgJآy 0TB "3)n!XĦbgԚB~_;L VgU•ںr ~;2YxAbl*jMuqkOTM5"Wyz4MOJ)c_Z14mI^Yb3^sy0}'6C5SCtBǐs}›A7ݶC/u;1tqIh'dX|YgMcRT e$r^W"C@ܡM?Su(JweRVND Yӡ\f˥pŝOUsל4%3:jH?:>v)`!b ]j4~$\Id;x+[ TDW!Z!%n4}e$ܶ8}y܀+O 58"˅|7d%Z Nl˽ὐExC_tdŇ\h=D k c? #XOCbb\Mg5ʹKedk ]DTelI6o_"\'' *R/t% Tl`&h8>)XP/0}eΙ&{f}mw r.pYUP~C- }^s(_+UMWVW=T@:Gs~R5Q[SBUID%Eםm`ἿLe3% k\A«8PJhuWbDN / -EC{i-i:>lj\|.-[ ?%Ʃm|d62wY۲y T7l>/XE|4hB4#΍&/Aګ9 (qwj rZ F{aKpm^!c;^@MGj@1J%@Jn ī36<䰰~%V kѦm|IvGf㛣w! z!SЯn5/M_;L%猻6LvM9I\<|+EH*lPvL;̗Jwrd(>]DMi[ қg;]N΂)C/BV׊UVT좶 h\ JpMǀ, G`}h3s.¡ [/H RgG/)77څ?W~wYA !>SEHɩLN2A]fOtug'[ {B \'r"X8Y Wݜ?=-ѶGa.j*|+$~ѳX0\yRl UR4Ȕ>ȖL~# Km: Ia=dI-!"}vxSvze2%c㘱^vdvUw,DEִtCa9HƗ,I^[He."5T#֯dsW@*.ܙ14NI1ާT ֠4Yi$N>bҴA-Li1mjjKLuekfхFzQ~ybbZ⼔Mt$"4.JqF%rVeFts :n̆ʟ⹛<N4ֵaapݱٖ!}_vfr ŃH#\Hq(*/Q2Ge6UG\fEb\[iS ֍@cb9.qa%j+ZHDCLk9ݰNlڽ#x74 :N qCaW"<z(%o?*UO,Xٹ I;jgN6&WHNHz5=q:ٌoH6|n P?S+k*o̅0ݵL8ENUR`݌)?Ldw? =iErP< `]KFe6:ؒ<:8Aذ -{/nq(E :L@Ŗg]]ٍyI1UX@Oi̪|,mqqO0W=A^UzÍ<MAݳ2 5WxWȃ! $uЗX: ՠP\Wíh&>'6.!*E2|-5tu. ֢uffJY0NRF{2qԴ{ @ITL*ϢSk7v([lT5!w]SQ Z4˚g![4φK{@Ny/o)!a.5[=)bԻݣRyHP{DL_M!hZ;5,0is| ((Q?5ds淩/C(:~V]Y/vF{iFcQ 5Ṹ{\fYY8;Uv4J?4"'Ф~j1BFOm`mjfUfؓh+x;R~l 0/9Dplҫ½z2X?ͯ@Ag2nBKEݶsAtǥfhqjvl "ґdJUGJi^anF܅9H/AעXE_BH@ s *w*BmcfJxkC.:\: !WM&V|3Pe>1֞gO?U!RiC\V}|/Nz2(ϭ˫ "PSFP _,iM=|?J)Jȿ#bj #Tg[ʭTyi;~o3-J/?'f:,oD+G4UFD~{`RR / DRȥY9mr}`dt2q1BW?%thZ$= 20z`* Kf&,e|řEf3 jwO쒢#¦yHRmw   Q{6݂dEí½gf++ʒS`mOz˳PV?JzwH"ȶ}i0 ±Ǖ.h~e瑡{0K6h쳖,Q0=qUMZK?2YEEa +@?a^aJ;}S%?ll.wEs N4jqmujB !DٱTlj{Ѵn^~q_]w=Jԯ ν3ZEAYD +6~kTe;`;a+hkm%I1v%W3h^RK\v-cO#hD51HZ <]R#,ȊϮK\EY|@J@*(b=ck;xD5:ZcwTkAU9\Y{ oVDqy[x.z5יC >Q eJӊ#:fb{IZܢ:/9Ahmo7GTy"`W8$&]9SБR< [Ѷ&/(&B6EJI{a0y i_4I\'LDFǃЧSGTe.)W9cLHAne1Hy27f4&Q¸#2s,z2E k Ӗ#UN.ƎMź%V糝z֪"Z ccq㑪c%=E۝v|xƀo \ћ/Eh7q6 Xiijɬ`bNL{kN%AG%N+Q*` ]UBލ:]8b;E0^ڐf5?Wf'IpA[O%ϭ!TEĀU4?cYS `DHESZrse8|mZ)8m:.jRnD".r,'fŌx޲ t'ϛT5ZK%IZq@> z$tm^u{z"bMYOjd󁦅Hg`C.h|݁Icp4>;u=H~.yGodT$(ɏ`54@/q W'b]M*pa%zq"0\{7ӛCn;9[2\/feȸ1QY dї )*`34]%\=/L\q vuGLd+PyCyFree-idtUGJ5qq)fa XCL/1Vݽކ9ǯtw kYl#OrѡHX^Oa@R^U8QkvO9׽O_ګذÞS(Z/U=,7L uLHhU  }[3Aa9k-褴R e%L9oBp.ǩR\XM3|x_!B8wC(]HWU4ǴǢYDsŌb{)pdWEognbyIUg 7QZ7. bð,tTᒯ ~:`" v)[ ]cn7DStQh_k[.KwD^<4&`7_DO[ޘO؃nf8.R?")xV lk1|W]7 Z]>; QynI7> ) կ[NDUBxWco*s޽Н;W-CNt=ǚ&|'n[F)!9-<Țc\LZjy'~&%Hn\L2wCe$/P_{3H)-EOx1Z OF1q]BBY vtx%BE ~OD!C*Y7s#'?H@#=LqʋjUœ[_r"0lNŽC3K6/u5N_Vz ;2_yd,Vg,"|>a.^ rTFx1FX1XFO#qK]2q]nP%1@kSG$k)H W7@w[lPn*R3/ݫv8` n֋i:'sّUu-"7Bɨ]\O f_`}7"jK4 H[23_GQ?i8H+.dy'\|<kxkOi)#VAL.X+^36k|,sC;ոo^"&^_di!rƱ!όFHm}R I̳qnEG'fx%rƒr)^:|00q6fe! b? tWs&˽.>MJEaYKu7h~0Njןͺܜ{²|J"攨>kJ7փ7{ՍEyQ"#1Q?+q"D? L/p7aȖ0-tͬ4lzC!x"18,dKb]j-71ҁ6V|ʦ >43vfott]&kk3x#Uteo:JٻY!@c/X GUmTVӳ|+ldNZ#Pl 0H /ٔ3Z=j} #P(S>@3[btk Tg/\f;ggS{}&:=C?_΅v-EH C *tc4 1Q?VN9B*vsf!dMQP*lp0YL "ݱy[)7GJ)ȸbi8x"wj@O-$ww#a"FIXN^2C˕phkNz$%x> 0d*U0ToS9.Aّ檊qps5#k_=.WHtj&r.q^dMc^Vܺo.j_+i|MQ&nΙӏ:ެّ ɀkH,w `꺬Bn6[s޷/ꩵ\A:>OmZDnŐYm2 zy0 P7P%f!g ;dKF-~h!_ӢKRmJ{e 9^tf}Grz#;eDQY6$@yيG_%$>; $|v;"IKQT_ߖ%ot4 c/wDuy4/yz_JznTv:@'$)`،ȰQ| {Bۼ~lԣ?"y M&w8jWX.QǵU#K$AK߁FHfGfXi8 XCXȌosu^R{£IԞtsOŚ:n]ckG[ǂw9L%r}N4JMSGjN<nV}Oʺ YY>!GsDSS,rہDeKgp{Tɮ 6yd-$y?lUA ? b9u1H3x+e~׈X[Lb\I|]&Q'6*$%Z-W`+ *n59Yb)?q3㺙,vګIņ;CiќJp| ZjtL}~*Y~5מnVpW}c3 oaG'ya㞎. Bܢ# pH8D,0 Dg;s4xMx?6;'JMW,oMk 8V0xgttC0*<ޕ= rmlͽ] KY}t3de16 ;!jX]b5QQ4a;Eu(C$!kO60T`>6;` ؚ,3' +JT-Y$|inoШ8,#g˘F ( mv׌+rj,T+f`z OQhnNM֚&u$pPMoM9(3SfqY5kBTN,,бn*s)Һ ~9(]kX^dI̳FM3^[r@Ks=dR>9],{D^-H#<&(O ֆ=& ڞF*Ƞz"@/6$H9fW}NY$& \_yB壔(=> U8Gnz"mc{"s$VI>9a6!* cQ7 {L"7];0D ݂q.L a٘Z&/pqFf8uؘ\Iq3iG}֩آooG1-$SmȣgMt5T|ScFWcfm&4$rLfyQP#P!O E^踎C/{Ii7;ՙvp8S}I9ܠJPm=k0%X4Zf@|.//T0pF.y4H58TŦ=@)b:K:grc5`wRﴗG>KB3P,p-!꣍,6Y?`oR |8^2k)]J;=dHK|R3S݂]l=z2S6=c@,*m'sQ/w+{l^¡ ܫ9ڋ9ȯyAx=2s 0:i7Θh;I{AJ6mtQԜ*ke*7?_n} 6օj(qݒnSϪ`oAw 4Il}*ʝf>dVm( 63/dbQoZ)$oXQ3rvyڨnۮYP2~])u$ Ө9!4pfq}m)Gï]‹'V*F-)lwx~"ԌA^UDzZ+ i\0Cx;5$?k Ο'2Q_ ;vEE{Ѣ CX#3Y=h|qӮ\Xv@f\򾨝nd>ɶuR=NQ*c x&1+Ջ%+nGVAe.HP`ս_VEi<[&L1$ B9X7)r@6>5V% f魶B#GنIQ~cFB \5V/H'`m*_®,1Ys fuTzpi'm0!8] J 9!r9F-}6pfGm=-ڑi  /p8UWF);gFR5R6ʸ BZ鍪NiJm  H*WWו.6S#F^ͦ-6HC z}X8~ 3~.jlac-mNOTv$.-?H. yƒ*[[aYn\ǚ dTL qeMRh>O+Ty 6)=^.g:gm-?mEgرe!6bi޿!4-a3Nn6Ame[vZCm ~nuYcY$fV:O=s8W|!m@-/}_kEN>,^Ɯ:υM0 fXS޸B%2ݽȶsq3&%iMr%߶G3ϝdzx|g7I=ʠHp]]dIb׌O#аa\hJ-E#~liv<5>y47%[¯" !իW2A_M\IEJ6:=$ 8wZdh *Pq4yyɋH (zYJ޵uTCbkWw`hZ L3yf'fZsPT̵҄`tXkekzL&̌sjps&.-.X *˺c.ؚ_6d[@8])Ot_K2$v6"J!FMYj WT3 Qqr3hZ&-Z7#j|¯JThtK1t~(uъc=[]x?4燫e'5L za°-I;첡;&lY`F"7(V6>І(Xnkt ~G_Wټ5!dmmGb mhc;Yc)ZZwHd8굔5\E @~|8 o䀝.܋sz_i|WaE(5)AG$Sh0. I&x^pR!3">DS7 쿑 {;i2PlfUS[Z,yIUW~¢FAZzݚ)i$ 8ٔ\LrH2lF1~oJ7h9XxnXA)b!hbi/mz]=:_E-))`hc Kd8_n+v %ჳX'M2=v.l8%~ A+`RSu'ܙ"\oO\< wXO6[,+df-{ Q84خulڌ`!ZD_dmվ2CVmakE >LcvA$>dC$\ j倛Pj:wC8OEp 5'KvLwv~^Hx~k"e*T?m:8kwupӶӿ|= #L&Rך鿒KJ#/y5ɞ M05rsyQk/]p9'y&-5D!]֖#n߽+Lu+3: zπ#QGk2;"A#Z|)!.,(1ELj9=`"6XIEEtXC:tSR7GEJ9tWG<{L@SVO=U sK]H9J&Q.BDETg:ko}aXB>l2O{!=$j; 3;3x0cD-kM'oM ٽUb tvd~+BG@+m#,25؀ƂOXCV\B@MPT{&U\͸]8iP|ЀL̯HCL[\E4asQ@݋^uҠϓu'E=JC]c֞ho'_9k} K (#gP7 "X8ͳ08×؋! ûD3ܺ_p2F/g0O`+لKPI`Ӷ6+,4)A 췓M5[vIsUr?g ա#YA~HA#Ws|BC5' !PSLU#F9k C~ ͟J~Zf-% w^Ns5h-`8D)2ҌF +iO,Ȝ SŊx`I,pӒbg"lLf)̵qLxcC 9ŸŽhcZs@ JM;HOX20G#ȝqh(? sGFAB$IuKYИ? hևZWWbI_i";ـݮ H?Ω{gh7}s{(ִ@rjQ?SmV?bC6όKx)N0{k>gn$!Cfs TgS|쒪YUU`(@c٬gTJRY4%.BL ݋ ]d[RV8`u&V/Naxsx>+M\Ej VCp>!>m/X};u.(!EHzēS i@>&/#}؎ 9;d.V3SdGi, ׌t.,E{SآxkujNƉBg*e =1GX26I ƝnP3qc>rH=CО qQD(H۹8s7^^eX[~Mv]wc*2ĝRoleA?:g@3d+^7iy¦36Cgj$:gZr +nd^RaD#邡Hd${nzP 2cMbh1mB$`Y8+/f%i9etʉaL {M6K9 ]=xX\n(ק{Li^'tdb2aWf"7} `HO jDg8kG:~#,YߺciL'd%d 7#H$_Y[,z4DKm!f{R]rg7]ªvAVa"mv@KܠMl`lnRV 㽴 gݖ~exFE2$hgTbfC4Zrն\#na#t |\d* [h_|1\C,Q54:N% }*m.58/X ozGv2"㛫~GH`N8Zה0 -gX |l !;VlHh:64zGLԻ.@ܖB$Vһ[߽LVC4zۛc5jKE^з"f5لC fHu^M,?3"sBBݚJlhDNc3Dx}jv@Qupt4LGU= $[SwDL3ܫO EgPpW BQ |ڌھ'13vY {\r; PE\͎ʌ9$b#*6*4QsVaA2*g{Myzmח͈S.̧1r$ntVjWE[WQR0@)UmLz[عxigMpj&eXj& 2yW3frW=*@[_(Ϡ9Q(¾0>" jt\{Hp؞WӀM19?:$yq}b*Aڨk6N+C/QGfBoMgϲjsw.^OY6@Sc%FnYU;[,t恢|EhTQ, WݞdIh0=Pߟ{ΤdBDkxWY6aB͠ "FИRxwĦ8 k 9L= .bXf_|NĚoNxߧR=O%p1W :3fCjXe43 Ԓޒ44X /lL,gգvCH;(mk7{ ZFuWr䩴Ktc[gLʑglXgvx/ ]yiY5cV3 $KbX}`)qY9#ܣaNk%0mdKoHB4+IG`>=MrmhoksV:?4 '"\j6-`#-[I>,;#Vm1`5/RF0<1P\x&\%ܦ 0Z:mvrMΟ~춊scTD|$L&,Ƿ",R> wVQIW-Eή ?Bz1^|˥HEZ\:e]kUקU#8 |;H_ًĕZrS@{HI\"O%O L9NnwƧX`ϒjjAX8K>jDs+`}%/On̷W1]ě=P+r-\bH^|/: gU;s; ]=gV3Ea_j|$še;e}%ʓ&<߂oةY0e`]!&h.U)5B&@\7ghcV% h=sm%t,XCY2?Tɹ99c & >r|i;DE CާƆ20rXv _хk3ə͝cz[q )fIH }$iA:$ŭ(ܸS̭g@UFQ%6Z[FZ'f/;W)l#?A4jձ+Ґ(+xX9_Ap}?bnէ,06OCNɫ";֛>( cց)-6狑c\1 REU桾QrJqeOdh^b'X>ݫa|Uk&4ņRE䌔?u+Mm^M=KHEv]*qoXtrfwݢ]Y'6QޓgF3hE/`m+!*}|:x] LԌ8R8H= _p>(쭠AAiǶ̣ǯĀiZ7%BWm846pWZoWx5=X] Hlϯis:LWRd׳>u{2&^'⦼f0,-.7g{TFIq_ln4&Q?:NMy#"Hi1ȇ\Rw|>*]L5 s7\nAWpEv:SmM<߲7ovBw{Pp\zɂm3 abTnQVm&V&DeA ix)W}y=4%1 X:eRR5;FY$kx'[~G*5D<~Z &K@fعG;Woƣ[Eg4zӿ'XFCryr60;)NWD7`p>˱s2N#l46lYFmD+1I[bq9|b'z!DW_iΉ[f)O*m$ݡnZEU8}.#wyҞ)qN֗'S>p f֡,3^7qi"|>,Y\6#"$3T"*|3=r 6W``[? ioR/Of~nj3 &"2ЮC5ٍ<٦Ni(DJrVA[(gD oK[!huXOR޵d[Dҩq%)&3/RL&($|L ]lL,3q}ߵ%|Mr> @P0"殬\mmf"Y94 G0c֪cyu|3c({Ģ׏Ph IֵGhd]{N +) opS\STiެy W4e]Q!e AǮozƤQC/ӂv&Á00T[}#ۄ\~M^:clO0ym\~B.7pabQG!3r۟XVn/zDs)T:iR Ƌp-:ooE yh3'hn~*B'x߅ov q98:K/MWzV8"rWl| }!g'q6L)t|~ęh^TwD.iip25 5=XVE^b2O[ \2u bJ)޿>=]Ԥ_$+X1`P!>>j6~s646hB[iwC?;s}IuI7Iͦ{{M.^lǶDFI?C;w~7WsEAI-p̓|q 1u~@RzE-Lz$qׄhϚ(hC9*ZU:+$tb}CBv!}8WrPcn Կ52]L=MD_q%q$CnF#7)~b|ߧ Z= R',r V,B%pRB=UZBܴ, W} X#m ,C_`Ԯb Ke?u;`|ҡrG5c3 Mv}1%9θWNKmte7d4N}itV]4e;pM]\#2|\Bz>  x{;J{Ϙ!K9VXdA?VKtjaxQLsq6Ԩщ|/r`uC5Gqjߵ?"{,a7eiWe>Ή0 F:`a{ALaY% ތ ʏjB'|=@l/5o cXfN*R}{7'*4< NOSy*tWiλ[p׳0h1e`c%8MrqXe '=T{X#aZ@ LpW1k1'F@Zv GaɇSf%[ $+ Z8_7 뼘kWcS^M2C787wAjCf302U`)L?{Gɔ3&gɲ918âp%ҦN$WGtVgmI5|8{joC#R7{' j]BW>>e**ȹvե+4Nܓ?[mM (Ao]\){.SL9lg&M#7TZOF ,,߰tsP J/2":qL^?QrME-0ĨJJ{ \?jȝOBs6B_/C@1IR+Af{a SRIZ ݗHwװO<J炷( FgNpIX_acr' \c/.,w١%2]] FFc9⓾8׎?Yi{,>sU|r'2v۾EP'S K\daB|̞ U`QOnM235v]s秚{'A}Y`E*/?ne'̩%5"y$$JWCYr9uƥbKKXJAF0q4\=E}x9@@L-C _t:hCqEb@߰"VyX^L 0_RMWA;yNC,\2Ir &فSD[z>GXH-3Y(a$sߗYǣK}rG0c]d(A*F)b1N  }NfTKG$^Ǚ"ם Y' Lڅ尼si#s\ VnoRpbrAp= 7͗:g%ً1!ne.f`\-SF +DĆmӱ)@z*3?T<)tWņv;p<k7+3޳#%,+R,%B6H&L2e9Rว[/k!23fV" ZbJרb({r~kã݀:I7>Tcz D Rl|y"cOD%g:غl)/6jӁtױmZ>g5Rd\1lO+<2~nYJ x;/P&WMP(Tj,q;Z6]=pX )!wVW9ii~OwF:"q_n0LMs)B m[KBy( F|;K TZtc1BPQ=5737˫SX2h\"Zh~Y`N~E_q+>BNxVm;|< n iTJpPKo(JuāwP:І"/Iƃ[<XT0뇻qσzZiW^irK/v )ͫ Ej[nSdoKAΐ8@uUK"xQM&$,I/*<@i:N`yj2/3Tˤ\:1zI!OBBF>65HHYB 2o4X۾I^Y1L򞊖,<V#UaogW^^fuAj)ޢ҃<m tL+,SgZ\/ZLyَPyL8A-d.N3W?D^|z"K fئLw+ =VNyS16Au* Uө$Qbil:.a#gI5*3|9']®1eo7k<%)9G#Ls\&8_l #" cNKꅈL1ׅ/AsOIݲ\}-W¹I^|}y\GP o(tc::kI{ߣoȷ (kRC{*I(#pץ<𱗻5䵪B{![\6\%V(rvs*BW*)V>Ҝ{@'bYnOz,*G7Dzᝰ5Xh/600V]L_n}q}0JY\2HK.Rܝ#)rq 5y&)]Or dY޷hFKSfJ08N=4Edz U5?Iec" = ++AU'4_\p O1q&<0QŸGx0pODH_Zmf2t (me9 ŽD@?ЙՔX!ߐ*BEIQY phߦQ}MEB$4l'u9^_,f?ķQ"w8'i\?;OCg[.Pږy95\ V.|\rQݖ|S4\z h} bxa]O~2~&ȆBmK>/c`myT=eԺ>(9QzqO*'mj&_UW*&bJYHFQw'%}2=;Nù"{QJ|Ty3k)yF?0Ʊ5@+:50 ;;M}6 sFe"x1~LqS> p~RPbG R?Sf$Mv%cfig)ڟZ'w3Ը^D^kNV6bRZ>97{pm}rmь趼ҞqPoMAY=pWiAH7,N=19?ne.[?luk@:L?P ntͅ,. W%*mD4K$q&Vm P8Tr0Aq(@jE '>+AGbz2[[lkGQ05)6-_LuU@a+ R)@$);$J^%G9᫷mt&(ڜݜ .1,fU_'/<=K|(_mޘhtc&oY/+QOz /ZB{ԏMJ|pMpfsA>"⮀;E%10O[qSKTΜVwjY_IPb@a^YWUuhgg)=+=#} ؔ{BBaOGOZeXi[W1oё aa强ٸk}ƩI$['١#?ƅe.DY'QR< z\rĎ|IGZDvn[7ӣC'"lC|$\3O)AeܿD#^!aih=j[fʬ[*kwsZ 00`g-6xqc'˵|2}46^Y:z)QtM<'L=`3~P)ss!J] ʁ"{uӃn؀5\ɛW*[/\b6sŀ7P_WہAUgvPT:=x[=JcV];dy% 3z3WV^+H+fVR/ƚ{Hط@23J9U XYI(1bO5̇!ׁô/{2l}`˿]UM73E(_S k:9"JSN%w ۂP7~%cgdO2281DG:+t#<6~2R'kiVSA){,IJdO(P-OF(EB:wL6BD|Qm@ff5?ͻ(*[dыex> +e?+J Cr`d\mu0ceOmLKn#&#PLMգRRSlJ.r^8+ +eiy[(\+2:*9ӖEnt,29v63zL$sC9ʕL 9N r%%2\;[,`GEI~e䆲DQrc-oV^<-@9FŤ/HB_ |~WOsдM+Ud ȏh {3LES@P[Wa,%Fy?`GYH1Y"gcDg{߼ϸ|XrS#C!,TI%i@wt_uuEt)TW^bšsT`!B 3Iڞ@av,[ 1gQL]ڹfL=gףMD,& ǫ5cA4[%';" ʈXza^;_| L*&:lrIYT/T_p/H>Ύ9iDgux ʵpDQvxЍ8#CThu!@䙈jjA qBoMұ.ՖU _BqQu:G,;|n=JE%ne 1vs-=y??߈`;ZDU/"b{W-(_;0f"i0rY?ҔW6}N+%x'ۘq]v,B-˯C׋lou&HJb;nȪ>cFZF'.4[ B+4]u$SЪyU}h"/&=.SBaiwڠ 24n:f2??~"63k5oOF[LĊm"%1d8ڢ| nr3mǫ|\$BIX&w&P/ k׸d/rנ5=7qo؅`f)L4Q*9f o<%ЋŘY 7alQH}bo ݭXJE]*QRRX͐M;siȕŰ(RY;dD`[l864?0 ̻gDR۠rk1*ACdoǘi2e\CWjkT"/Ks^ʫ&fxhLq7O@t-9 {ۺGMnJ5a?6p{ ,a\^?yv4Fh}M'Q9w sBע!091<my'lEno*`gʠgw,Kكɛz>*N?rCRB.[q8Ӷl!a$sOّ?r4B"xV&I mPK^jݸ/@ >;W,<+C|F@U []>".O۫%>r+.Do֜k97pr0ըg"1[|dYVNH11Y즾.RpbPlc~n0 կ)b$ ,4w'̪垻A]_`S& 4`@WR}/,>(웬xbBqq>3U7z@ fƓg2iYJ߾/LhblTC)?@+ZH.k~Wk=)*¤`sIlƫ#T3ThC~:43O .+ ʾCSR2I[[F^ (Ɨw":ͰTrygWPSdPTw>^yrdf#".9_Z&{Uť=RhuJ fpNɕ[6fyd/(H3auA"Ie޵~DIH\&-U̖N6btCDo X,yZ:(Xbſyzva܏l&1-yŀ_źs~]YN!|_ >h<%?]O aqA$R-}m9 {fhΝԙ }@7.3up_%KqIr dz9snr]/[I~5E P-` 1(v?o҉YIPZ)2cb\ayH#y*nU!VNp)gGUU>2K@( x-شVэf[|ӕ%43x\Z- }q`nF;xGưyʹ|fcRimBa?މnV7Hh.13}!vb;?6/4bh͓U '_+%D"W-|Y0Cb.)vI \šқ nQ\nyeݔ֥c[q9'Ӛ"P3`YG;WH?Ubѧ~AĄ㠤-4JѮ : UAR[~oI:)e%EehIɬ.g!LG썪G(}݃Yh' :1=_@ꠏT6ǯV]9\ŞٙM\딙}S>e44x -!:P=nGlv &3e:OY=-e\1p($d]=nvCz,SwnR: wYaW-'D_4՚h1Oe57-DCWdz;qQ,v@WlFϡ7 {ϧƀ&f(?A=d )$V:FĶ:rHE++yj}TGbs*@Ab+ʎ;$^> IOg[_g+ܮWˍ:{b׎h alCaQ]~%{e{*o 6ҥt.u^t;qS*/ \CcSȜaX~޼]d{?@2KA1%` 0{X"Qx6?xq6-Qvi{>8̢]$+ S֒A>,<,eQbT9 <+L{+!@T2q@ߘ/}k gU~c=k Y!+}()KG)jT_ZE۲; {ygO*{.>'=_"|NCRk\ltT$Elq~ben 6 0iEâ;5 --`ŕ*3aS74qtG8G]e ÏݙѢQim'턆_qoԍeUtp] m5hIНC$= g97lT c^dLBmCagXVL8At sTux>樞ĉJ)vcS:x,dC3IsaJ/cn5[20+  0tEώ(g{݇>̧vYawy<&y1T\EL@@9OnA w7Nݭ .vTSo%hfVnj OҕӺMQac'+&61twfwOZIX4#rG-TّETfn!gP.$BN\hT+%>U#_ɾUJwdz0Vz,ύ>g[LE{Q#rSЧ$ :\`8pȼx W"K_iЂ̫ĩl>4~Hh_McF~V뛸B)$eY$A .襢2?rN}"Ν22Qi3j$g}-R} "MJឋbgX NOa,b"M[8|s$dka*+-lr~͹[FpT\Kf'pȋODp# =}~dVȼu/T%k^˜' F`(,SP]hܝB4q-X]?Nk4=H[? 'MX738 =!+'0Jc-6_njPOU¹{/S r|ֶr>fq) ¢P{+ Vߚ2f$ ; aZ(;^dT.0zxVDS >Y2p9J3C,g6:yN躝SKJ9U'O&m+>S~,_ukbh ZZ=R96ؾL"ohj.A5~Jΰ+cD4`}*HglYnjfd,r= o8#j3ujJk:`GР-&ZghQ"9jLo׊mb0,{n42^BEK ;݂/UM LiW J ]F U8Ew~7IA9qTB% 2v,kνC>Pk܌n>fJMrMƞT` zgK| Ԋ)MBT*NiR@{CR%ڍ 9D?Foݖ٧hٽ8 džH*'d"]9j^4w:-4 f+k†g*/1}}=9"*e^ BmXmkByb72[%yk&zO}SL?쯊TK3qDŽuxLO¦Z<xqVyNR|)C}RdɥlґU], >{U]]])shaV-'/5QxePŹ?.МBkP>U~&sj~YS; |R +?Y,b4!?HF5==G֔c"nixYm xd(2z-'&n.'<[6悦ijƒLMF4!-YUӔdh[yԜ^!s)a҂qUeo]{L v-K=H#dco!i#[ʌk@&Z8M>J'bu^6~"r4YHr!m#J[0ĥřs*7\C4PаXw%>}2i ިi'0JzrѰeƺw[NS MiÑd5 V^^GSYpBHFn패fU5XJb58v٤;K$,Hh[d<{H =I@׍[7F ~W] QED!Jss:KKr;cxp%A蚓@’ WNm\J?Yᄐ/=x[~5n;[{itBZ9C |#uV79heQg< TdY;LC FLzM ԉN&E#?] z`+n:U8ل֨9q]QJ)sPYMw$ ݓB&?у knlz@މ݈:bSWp{vu忣u)#ᔕ}:ވP԰0ofY>PH9/.(hj,)vİZ4%WTqG*>;sǝ!@4G:rT{) g=Q`h;l, mܞ~/FA833)A6nUkìGm:6rj: if`/jH!^uX0Pl9jh}V ˙`һZ6AL{Y{f2aQv,ȈQX,g5$. w/LF~ o6^,KkII6grxw3czre BЛE\pAx5rkrk}(hy}LI|Vwel*L Tcsrw!W޴U5kV vnsѯԤ{j*(!wAPiЭQpHZOH٠֔ODPB{TA1؝ד4xƆgob'})wG"1an[ݫya#teƀ)C*leFE ?-(BSj,IgN,-*ZRf&!0ި:8R,V0AzQ ؆N\m~mക,S%qP!.]vOo3 cd%=d^Go!<'~58tgwW$ 'G fe>#)\;(V,=-akWS͒!kLU҈DzU6SG<⹂VwՠoεwǏe&%>BbTܽ]mYsFc HVsaA @)i+uWRzPWKϧF^C.>ryvv'q\U2>de1Ibѓ>Q#@? = )jc'g$3I7S7@`uYh;6.均{BgEioywzAi5IWL/ ;4jz|@a"Lq 8AC3/r~6UC6'g(=' {Oyo҂5x#j)dtk9W h@MrXk#&|N/4[O\UrWe{VF>޺&#SHR'&MiքV?"+Vf%7ߒLoo:`GuA5(X][E68d w~ MhL$| 8lʑ$w8 \V/ɣmA6n͖;YUX+e:Oܟc#eB%dGJ%=^| "Ϣ^/Q:j)n(I%2) g\_%o& X}·ltv|;!S B/zrfο4*OrZ:jC8"Z#x[1a  1eiH&Jle()Q'H7ḀΤso{<%MP=_@)!1'Je*Xi7Kw~30oי&#4/9zN`й:Qb . 9kKU3&bt (r0B  !llvdՀ$8<$w>(:#,R)Y0]aTQxoo훛5r4br͠""j%R' Q]bx/l])[9q#@dLV‚ ܝI.U&L̓1CsQ%ey"n N=h9sLs%qm8~_ PlVfoծY?``$#ZL<оhvђ-/L4~<[%cQ xU`2<pgQG;0EߣbY})+,8fp0|jcyP~ZtȽ;L[o=H#tT*>> /zTbCq^]"Z^)yrgѰz3mCd@gV^T]agLMC / /Q p4ea rC]?Pk)+Cxf#`f%[8W^ 6nb-W}'hX% 0f ;I"{>OٌusccNl׈ߵXCj[匳vrrHUhOhVnF#L2I¤G…p`_TbC!dO{\3w\DB )\7L]Sp /` !x,d,V- k#=Y^q8Bw:1dlIr'&tu(JU<+4/&]Tzd+t{NG%{vJw(,ω@QΖ}A Cvg<"֞VSS#'R$:_Wrrsa~g(\CHm Z)\hDYU)zEMw,CLi?lI&I:EXYߔ5 .,H7SMO-p>BsxŹ6Nhal_* VEE0`d  =ɗB>ђuaDc![76*+8֫-{l5RGϲt\Ps$5L&=,x)_,,CN,o*BxiHu!bmGC~9E["Le[2SGIR怖miq];^mOU'IDo4'mƞ PQЋ˭nAļ,W;OK(EQ$"͍5w~?t8nX] cC qd(cׯPZɾ B6aX$4C9a} 4cΧyڨ-l">$ů4kO/ lv$`+ζbUzÐP_VPoE[i9\Qy} oyWzF}(qފ vUD]T|rv c3pՌ Ko*šXQ~c0lc$oYH?j#; q<11:n.߃Z*$:S|'Ys+F*YLSS,Nk++/BS@5bSs.~\>O. (\آ bjg3?G.@ 4q͇ :CT#я ױ^Q%Hz<^2:&1sp$S^1`m;h/yW)jwIiv4iCO I&=clG„:3Dž}+{Rq  n#6+ᓈ_(>_nm>b"'Ziɝ|qe0!܉LX2 iv&wrI%u'F@鷌 Wym&Id-RWz~u5r_dx9'R 2nr]`]W`ԝS^i 1U)֡τcj׀@1s d,4([,QgJ: ި44ӆVUKjW|"P-޺M>\Sw^<ٱ2`Lz]yʓ#СUC$0|#h*ǹP8l9H4./cVAhE8Ɔlτ~Cv 9~ہܘ/VfLN])"yr7 RB. A 7 9ĥZj{ ns̴4{EyCi} b5Q/K5FR>ǘ[9T2 gX*ArV&PKk0C9? PThο3P P!\$v8tnpsT]Xϝq.XfOoSm\P1'yn15iAfII$NZ"؏fU0!>xG6tr0,+6⠈C%ݦ8S6d9lPW8l/ b.ἀtH!W',N̑l=1x 2G4ֆ}.+ P/E5bُ#C׹qk{Y%?Pc4('ù<f~_~I>dxM6!Xސy܊<\E>:XO$GۋĎϔӏmj(ܫ,L+/=4*I^ o&DлgxVt޶pwtb)3{gE!hMMTP$Xwi !ݫAA|KG9 ; C<5L9<>nD:{inW쵅;,:VlM- K9*G-MaP+q&&penv92tͿ)@ {7T&ZgӴPK(%}>M_d00OrxC]5Zّ6FUnc9zv,eqY"%2 7 }cK-oR<6T G_x=х?+D0zGyBh3]W;H+lS-r !<&b$Cb Z|zx(Tm8 ~x]fIfp oQ X˒p|X2 I܎ au<ev? 9&`tP)/\2٤fӵRlgDF^ݗVSRŋ *6ٓ:+f-}m{\wbOydx|39 -܁ZH"?yvRuk o}[wBXϗk/@p)FvP+&w&wD7{A cmRLLq`aU3[fTN Ssi#i*$0@rG]t7#5RPΓ%+3dh]@kd  ;G~cjQNScƳA$WD.tkOG(!:Y&͆wYuR#Tcܟ|ׁ")cOtɆ=9Du"FoM>n9QB]c] eE!)#S1RzIU )zCwL-v kuۼg:p8F!J Q2:\P{s1'jfU@ kC qf,=Ζ+ G`Cz}Pam37&>+G#R9]ǏzyTC;݉ zS.n+Sڭ_:XX={ZfZOG)Q,kL_i K$@n/?\]W2DqD14_X((};1aIĂޱkO-;泷&oBZq2X3!򼜟GZG{ӺՓdw],#&C`cFTJ)t[J%~|eKI):,1L B1ۼ8L1;+@Uџ2Go!Li(Vm ui}"-a=&#LNt(/h{e)wBjv2l:r)sVyEyS3qf^*1j[<`,P+]2yFurY<z`-k V@d?!c++uzFIJ)n^A_Cƃ?Mfk+dl< sԁx_~㪔CP},t`9\lN b_C'|(ǣEAYbټXfLxM!i{rg Se/`%ӆ '3mye0'[!s\v+>@f- e@H;N!8n@~ULR|)pDL[m̏<Ϩ[e~sndј7N6ZQ+/TNmr.oaBL 3xTrb%rYUET[F mhT|ao,ǸfOg}F}- _nmCqͿ3y SnB|)J/@9;1JU:ab&Ɇ-$ec' Za!JMl %e܃Cm%tSOqMSaR{桨OiXf٠sSI3k8 ੆4Whh\4x;gx>s;{yr\:6>.{Y&E!0R 8J;f:[Q衩u[ך r[t#פ lTU'&T` bxX"hp$̜J(1<⌁,0r5WVc}ܴd'}EZ ZO`dYLJ@ԥkS${6ݏbdfmBۭI**d|Ep-ڂj'#3tl}_uKJ$|p6xm&=5$f%@-Z8ֿ*%S.CHiOYۮyTgaIő]4w9WQAfI!Nޮl("3x?Vh;Z"Nk-rFnBիH5]V>nT% NxCC"PlB {帬Fv {,Wm,+tuty|NSzcb0,wd5wT0R9oB\_n<vg<=fPwP2*u}HL`fA6$AJ?KxFT Q04&zfu{%%`:cS?bx[ĠҊH9һLKR$СB4.F:SgYџڣ՜2ڗ3/߼MO=pzm /^Q=[F("45p VV,1 Pa )gt` !8rЄݢ .ʿ43z0>ls"֮ؗsӊ/Y;|kk:Z>cYIrsY1v߀'+wQ,ޗZzMܱs\2揝sQU(.=ݲtcs=X $>-wyF!Gx}W j@BɸzZ UQ*-a,r$ƃ`8VRFU)9 T+`1"ѴS%OV@kTQS|9ǹPLoΛ>"viLW`~/-mvgRi(=6m.O{ǪVq1eMBwu҃lo٤/UÇbr c{W¦.wY_x2ㄟ-˻O;M5>RPN7WPGӺ8X[efG_pc{yN^s9(e|rfؚ6#p.SqZFrLjf̲RBkJmqGp 5D7}<֔.ta̅ 8d V&'G[:= BJS=ق0L}*h9?Lً }Q͏Y P> s͖>՟զݹY_CkLt8.,TB(;M\HˮxOYN%)V蘰jP3 2y g9lRCu[`'s4-c+1_k;k3vh$zHNb=KlO96[;.lڧ`QCMEAxc{U[Xm+} ս z1 7 JOY[q07h~=(#S@"EqU5|\|3'Vbt&[Æ6ٚqV9nQ4,=+@31 pI 4ZlbgYPq@ѥ+xMS3IKGJVغƦ5+F PkR0I{^I= ^}ڳ\?mOI@r jmKޅsab&?AWJg?:Ǡ%4*JA ;糺rY= iI'Laz4[gŋ,UpC>Uqڐwk@Ӳ랸rd MNJBp9Deڂ_NJ2֖z2bSKJkxEѥvZݚ<<&|{ ܈("Xʨp{;%])B9nh{txA\)!K|D4ܩhZrk]mD|ܵ-&N4[=*:(]O#%lP1 V ?hf %<ΘK4 ^G Н۲Z+BDT ^Vx #h[(6 >t¸ > 'd&n^ @rH*K>y/G0qgE~@iQ/.D4A~=I8biI=OCO2Se]*:fD7@R]@Xۙ*΂z Y3EFc٤EɘKhAĊ.oțCzH4b_{Iw:Ѵۘˡւ;Vr5}=#2ƃA` `?JVmJ՟WIpdзw?Gco1UP uM I{@컻]zE; C9/D KxOyAy.6ʪʁa_r^a(UtWG*\TERa4kMXu ┶9:ƂT!OdqA2;,uϹFmQ{5}1`>DmX'cx:3nMf,m:T;8;J2YԺL'֧ԦKb8Fi.43cu MHJVz-^l$WtPW,Ff`s0;_$)TQK<NQLj"~9w_ߘ_;MR he7a5j.a EPO^)P|F^^nZw)5}~WG,N_1-|\)Q9dn 38UW=U{=;1ji+,2hW܄A"?{^1z}0^5ZsWsXC&0KZ}d:Oo@/ @KeogSaְ,*oS&.AciF@śN%T^{M? v*,XҾZb~+ױ"yMiꎟ2F18u @5c%u0OrJ \F,\^b́ -i0IZs&r&k10GYkTY&:_M-hu&7jBKNP<[2cf\sfgϚJ_a.p| {3>"4nLdvJ9kH_V8Y-ZrbR7۫φJm=/& h Ɂ{U\ef|bE>kٽe8sqϧd ȏ.4x^5E}P]̴ VB[IA8inYUa82ivr3At}AX 93Ip!?PI[!k . B; EsO Urϐp]-JSG>QDwuă.S73+m3߮Z o,w[綩)))Hd\]CٕRD<| ^ ބVҔݑZ(8e4X*f)DL ;%uN5vzsZ&*xVޛ?,j k/iNt"oc%48-CBo6UO>BW`{n^M jq(0g҄=:S?Guq8 lxɥ\_"6kG7hݧ\9%WH5|c9B .6}2sB(^dQkeDzaPTc~8MChZHV']DCHڊ!ݑqBDžO%^> |[e#p^>,(1 2aA_F/7,/SC28DkwG>5HI_Z5]DO$q=PtaG>C?jdu\m孕tRcȍhk[#G M >tȧJcw~+qC%',Qq"r KƄe )"$ȸ x#5qʩ3M EӡTq''H5H/B64#/Tf8:C~ hK@eLS{Om{r-)ufj::)q$ X߰`SeP{*m| ΒjZIQ+CXRQ/m5WmSFX.&/E3&H GSqCx7`Ŷ[ɨt-O iT߉U@&4$Ig+JR 0~*F22}4r=50 L# | eAFۄQE)$TT8ZL+Cp9s uL{+_Ae}<*@ PR\-ߑ.gKf7I{ 7 : Zc&#ͯwf.Dy va~zdLKG"ݾ;9O}2mW,ҲvQY C ]C`| j t.O6ی}a(?.pP{9KU,{Q2j*[[U$m:xDfzP œx*loKOqc˅ >Uia2uny>j"*0n2b)g5E.R3J K&~alv+5!i'|fid @3>L-رc:! 0%@*[ߑuB3@".wvUN7~C4?=tG_tp#(:9*]$Qg\\n/I$S{mٝ@u(Ţ[.UǚlM9=].挪1jT"%ۚo}~0AVR0fS;3~!/朞9r#b`:RwqQj~W(;EL\l8IVjYCRE&8PTY=6DQ,M:=<efdy>vqSp$wj2~~dvPtưf&l\#iB\&IO`@zX2.utWA'O\sw?A1&?M]"" ͷD|Jiɓjؿ"S&.l-}aRIl+xk S] 7\aNiۜj|JJ4Ȓ!bIH f7#@ smtiwgx'E*$%75FV|57JO49 r (&l(鲣t 4#:"[\5دHW/ch:ujW%Tt9r 7ocgW9vxnlLu)(=?:okC2Su-S;(@!fnfdSY-tv ']CkgG 玣T]nf,g~2 *ӫi-Ћÿ`IoWͰYy1];^G$][ a$3ebK+` `*z>/ᒦP0T38?3!G>pi^\4'Ǒq0ºō+{tE(sK M-&LQ'jsjE1h!H sѷmcv3Hvc}1 0g(Gg֨ifQDm (tU0nU?&Mjق^|[)O .H5pH9yq.gΑĒDjy%|KÞd$|Zȏ~V6J})L-b1@8^'E w$!PSS|Qxf`kK$=`'%{(ȥA̵x>2+y)i*:FCxX}Kٶo쫸o o4O>taV c&~AR(XXBΨY\Sϻw?0]{OC*Ea*օ$Mo 3cno M3}׼UK kˀC?~lcjhEjQ`fԍ{՟%'S0 CG/f^Mh`V+`aM[2t޺x A!gpVH ̚C`#s0 ї]@~jR0 $F܀ws#?7.B!j3"kXRZXv ܌,ֺ _C~m8&bz[Ï!Q@wȉ̚IVFRWȄ>)kGO O|SmsDw%48K$,hnR#iWMj_ 2@#xʠ n ".Lmlo# o*Φ>;ܜط-RK Dk+O_w_*c:yhD0^за)hNMx Hxb}R`^,3l2UWē*_>j\y#jT#)TS``|WggD1T1rQZ(K]dLAsbq76;h 8c5`{K~ѳTЊ_n˯ݎ\BѩGށBe@M`kxE ,ݼTY1+}H0gWy<otҔ ճ[ 11y{=Q٦ vҋ?wGdnqbMG3( bFS9ʐs^#2,I)MpCu5ˡghl2kO- GszƓet)IdEP[(Kě7Y|GM`Rob?kd ڹv9X|6'_? !vigIM'ਖ਼܋سN;RI߇`KjȄ[W1³1̠Ù70 ГLpWm[od' ۻ]ujrbi2vK} .oWpcK_Wn\h(e^}M; Aoeُ8vnMxRFꛩ]| ;OdRL=75ς iXOnsp̰(d+UN: A}9c}_Ӟ94R`h7ԢXڤko&\PĿ懹{_Q4N8lgNE(d-)+6i)GU390 4K|<+?JG"KiH̳roܫO ʬF#Dɭ]a!rLclG6dfq"(q&iU៝$P} #FC2!?!ŰrlD9n4-%s>FM= ÷tA=tygx~T 1 }[їw4 lO{E<T~ rO!q7ժ̀!rT)#9p穐v$W>1͑Q7[LgYLcԶTt֪KMSֳ4=>ܟ1〙"rY/MG;FɃ`IkRe]fW=08|r Iќjg#`0)BWpNg_xC?dTPܘBzݞ\'4k;DOc2 ei7(h.eE:6L쬰Tݟ欭 Mz}>9%ܙTfΦ_p v­01^2ʋB%z eB,lB(>bUvs֒ ĭ=]O&TFx+b/m")q]i޵"݃¦xKGA/K!]dOW)i\ٸ~]fPWPG/PςI'TÐZkFc')BP_'$6:?3  DO\ѣf^Ϻg㕕uais [cPܛ=җ oһ6x_JLOߵጭ$:M5T\i]X^c@1Ǥ\Y )mUՔ,<KJm=dN=.6cu3 ϣCPYwMݰJfGlj`K?}VJ*_4 eIкAj ?}qKG34ٝnm6fcQMC[iΰW9!kL<ҝgڑJ o#o>YT`g ٫r4Y "Q. )Vrx٢Gǩ5KJ[$c5tPϓ*j8RTZ<!ixqaڽRA QV[A>-ףkR=srҞ%GfM= mn^ ZrCxEigOs:>>zNWos9N誂sAJ7ӮX:gؔE`sRڨǞt&Ks4ʈn33寺ahbIn O7?jxI{x?f)R@A(CXœ 64Qny=ayhS DR/)yJ,i#MRR=d'ܮ{)8.LG\lUMpLt%'$ +iFS ndy]?Ê֛r4/emIeG;FHqSh ;"4cmp;m9m4ƣX Rwj7s3jNSoYȾ!093`=~;CdZ?stWok$L9ZOtT%!{F>9%ϮrŖK|Ek(8v(עȗpTA6JV4فS6pvT!?j#=T4ihnu6fxSS=ČOaVK>16 G}m_^F3.tb1X!wn; ֋+z 'G-y=bz5THĽS(KR&쪀Xrf()@w &%.G#+]+dk(jT;}; ^?B߆W2rć켞$aoG}g6-/,;!Xŀ~=waP%XE-eYt8P7$(YewhQN)2%$@Sqa}՘ I*;颐U$;uHtODew~%t~ARq7!]}_[]tC* dCze*;ϓO`K6i,o*;}PadNc|W(1D~0iG2!TqN7=hhvXn"bűtA%lrx/+ p"z󡳽O/R$Kp#21gׅ }Z7ߨ]yswāT- (Vwz=FL$S)8RN:=m55T(V*S *tx.bgemmDßd=Re)`R+߼I] VkR#g1+M5Ub{c20GQX(*bT_N@gh" G}Sloēe ޸ h7_Dx6 Uqƌ_+'&&`q2($C!lh ،*5O+l(aIt䧩+7;\ =Mo직p|ꌶ5BLȡң'9³ ڐ6w T;B4UF&g @J' <}TTZ&l+!Fk%e:f|&%߈d)\=$T@xN ;GrEp^;iu}JN[~96 ĭɒJMWFunv9&O:5Ey#d>ȯO wW*\w?w]clZ(gzx۲t~* bXAw(ƙ{u}%(g?H6Mxr a.`[dU%{o+> ~Qc F &<}9DM[aNDxv {%Ϛ[tMoaYtv3DՅt*F}ܜ[>o,8Jp߂[ 6h\60_ `Xx)RBBwDG6T,_Wk)J ކF5G{Dك_ڦP/?yW ] f։O' ΀d?7Cc+z?V42L0JY'u ~g{dh? fqY3꯻)BͺKr Kܘ~V&\D~،̛]댲+u0w=k`)a+3;=JbFGOO P$AQ|~ǜ$Ou(ICkN1+2:KHtZud)S mr^FLn++fglf 4⹡Sp8 ·̓6KmmXCw@ I~ dWacmzh g;90G-~N~8OudRӆ43 1ކsQLXl?P[d a6&v83HHc7;'IA ٧z) v5:>4Aui^&G4@gjir.-e!M l(+}HƖq³p-4)fRu,jr9@Z-]4ĂP&z>10#ōkx,;\bllGOVy)>чHh #뉿jШb QBg1)Rg6 gIq S k\0,=?6T\h,t- "̯ns2(]ei| ,Do{+oyRPHJ#Q-e?i?z@3 5IO %9:lbؓ1PHzhLՂ rBF])őn2%$ߚ$\iop>, {>3b!p̍ڐsˡXDU%>YrzaҘ ĕ`DCx/~e7 ˳몌܏Kv\Uh3v LZd< (msY- N}^,#nViey>ڧ#:X+'Z˴ ,K J]$n~˲v,_sDkk}5@ol .Nlr"^+5&G5%Q\_U˪ȷS>hځl8VZhBb־W=f;ԓPww/=ge"b}m[~aZfdĂu|n{>h.*tbru`4 1ϩ3/tўZGhL3UPouY? zdLj4?sݝF7Qu~yK˒Z?,PAsGz5%hrMP6A7g*ld56%CKK[TQD_i>|nye$Q24ukoI L _0,J٠mGQs')奔w+YE^悷YMXŢƇniPw=p>sASYf 5y4;8K0y"ʄN1{zEOx?UD ߃l}HAsYf>ɢը(@3_[՟^/\$YE KZ3ZT)k &&Ga\n߅+`1r檕 lXtK iTV?í5@BK BjXyQ XN&S֝+2$";"$)ԟ6 B)!4`шmYEQ_o ,RaG71/1TG&e4)a5JZ[++b׊KCC+y>p L3phXJSCχF* O_a$Aa;|  [@q@u׋b6D$^{~<3(g=n;-~9E[հhvRu'XO~YbC wV$\iw\!E1OL*w,M#}R 0H dSbam}h^aseAJ"6DVmG5;k G`㩽{|E`Ns $*jv pSK~'6LEzhKvX=* 1Hl/>X(8(fh:%J4[_Hk$؉xzYsS/m,N QcX6Fs ̍7rVzYz<Ҹdd| FKr8`c=X_k$pQ'8't%ЛC;K_U6Y0s?*gc_-6 "jQMEhC7PںYtyEe#ڵfζTC Pl}Ul~4{Jphk8)ܾbc,^T&mfg'wjX[e0h:}lNϢg^g~4&S11Mz>QuTj%im1R{c?!3Vi ,p_ZAK"yT^5\,n58KIDUtj#7GNC iP_ͯ΋G RgȌe"`D/SgNKkÍ!v HQ K&K5~hHq}PGoܐ @")~X/qeM?K˘2:6sso[m8"FLz p݅,5 !rUכJ u2͈AC'1)CT\WP`"J hFnNtA\g^A9G;q/lD_mvL0O|fCLF[zk(b:;J"eӑymP-Kl`%r! 8csO;}!{_b-v'0]Mmyrap%͜,{S^N?} >21B &^8_7bdז3G P<>&3@ $ -xP%6LyTao4e7PPnEY[47|*Ud5HDw$=i#T닼oJ]%)bڻʱlf+imOV23ܹ J 2! %zHAo%@4_5#鶟\.hķkݳrPZ+{%l3lHHڼ88s] xn!?zʙc5-eK պ(>}}ZTVUޚ:YKozt(˱a$ HcR:_΍t P Za5[?X:V)BM-M8^ "e{7w`bR䋈=YG9E%_ZD/ pim|Seq49d U\?DJsCkUv^9`3e6*c}5V߮lF"`"5r<33ZVHӕ@[7^j:>!HD4Nj]/K$Bq^;-)D;?LKx$*-J}kLYV&+V&=+lY3wVrfsFeR%bhx>Ml ˷—8i,<\ztrY~R;y]p'TiyKҸBl䄩|F{N.]h .e%&"E%K}%]B p"\[9Ӂ[̌Q9T,'cAvJ0'Sգ<:U~ND6Uv &>VH6Teq]vOm0IM]uKFޖZK0ciߛ;VQdUq-<'F޽1)[CWi|ozU'8NCjQT]:t[|H~?j*˔s1@K@M5[g5Ro\jR(}Uf}ꀶ†WR:^N,rhL91ɛˀaFܣ/p1BgOI,I3! 7'"GLVs 6$dRW{ ķ#o$#QDh@FH 5ߐ+rB5NfMyJdWe3GBu/N.sJ ZY7X-r!|fkv6"tۑoA=dz"X[9`X uD􇜍 jw(,IVEi$/}Q=r=l)R(/Kc^됞Ӹz-L Ff1Ҍv(H"PNfiO„W">rc/[QN\$H|u߿%?ewʁ6D_]ZY_-݆qQ* OdOBPXBD(302Bh-KF~%pQ)N (vk8LCsN"qoJQVȗ.GJGƟ rOSu -QiðVs*Fqz(= R 9 qQCC_`)*GS^:>db>%?<n= ,i6mDJZ _ xnTh=YF@\$wu^ם丵 ؗu<|5~9xo1^Sy;2k:^3gB*qƏU™ڢ/~9- mPxpXIbͽLKYJt ([S+:#'݋ߘu[Myy)PvȜS8©vM}e*Ggb+vl<6%z(?r68 @eqdsG 0UXELjGQ~&Ws(2FNP# wFdcMxX1T$CN&K!Uڸ6]%cKy=vi9}+ƕu<5ۏ^ё)DܙFr=p_Dy1H B?zk eyQA#h a/Ը(NK c3${Bܨ>y.G1qW BA0y\/Z,`97 iA x.`@*Is>e8s)Jf"*ȀW0&ty,"dǴ UݵZ*ȣ#E3T3)eKFh)SfeAD vf&>j˺dw_plmXᴇlEg<7 0Ȣ2Ob+(N)CE\1 -3 ,#` VR=xQzwp`u ڀqyKJ+d\ .fx'$V~>X![0RAghYz;L)@T{Uқal5tw<Y] 98R"rrm*tD`pm: p1dKq-/lJC[\b<ڢ٣{F&|-A8rAӚZt-D -2EfOx\SdLQu\2DD[{z)}iqf7eI;0C/cL=ڻ:`N`Q3ReL `"~@1ڣFYȤis)~ ^vZ>.))R*8Y+$5k\s`@K٫'}v4  y [I[{"m))&jac5iYMROtҔ:hlOm Q^ƶ8ýwڣgɿ,by ixd lktHݤ;k%}*+FZ!=' H|=/"l t3,':*s([akQLZ"LS2EN]z0l=ERS""2tUܫN*q҆vm[84k О)Sd]pޓT/BSïAc!Ͻ͎vfSwuҗb9e=G6 %$lLLuez:,rNr[E,?zfM|c|h9FTNٽHףk-_s{:KPYt~QzVDܲN/A^"v'gp(X*]bIJx\F2R@rPvMK%)/9zNdQu imgg|q]6e݉Xym 9s9$]'bJ_*6XҶ9Xorl&Jm :\I(RL'%#C 1Ӻ/~<DAmvlٿvA0Fb^A%3DN]j4{PCwަ14{ۋ4*u Xmͥ;+_n/s}ޫ+#@s{|lmKRIw8CNiKq 2%m*C~r >!mk1Ux{.xCLAC;i'n8GrLF,?~u{>}\n@nW]}2T1Ӊmvr9]`| !j߁ºh>/54Slھ aɃQ-cJE^9-OQ` 79>}3R%v{l6B6fnp#dʼߨ"KGsVhhM'Aߋ%\ˠQIJ0RC& VVh=3b;m[C4 [6$/tJ+P^VBxg w=̵5odI|zˈ|G`6L|ٌF;^ J;SLh DnKfȡ`qpbHcD-bN'"= UY#_ڳ.qrBr ]eͱ #./,tAe;Ԣ+/âY?r5O5k^)ZK5[)IgT0y4 ii8ރַS}}NuV4*U6BG cOZ|B5 H0;gXJhp{ޖBkoK6E!@̛l as>;QdLWGZ~#{'fŰm\%Z4ʌ dY*b|Gj>}jW;/˛BQ  T 5`/?<1=ܭCX2He7/$@D6TcWReخ=R{_p8DPw+Qqԏh+#Jp=m*EVn,95\L +N ֑>[HWM9E;OǛ/cRIe:˳Xiy/@3V,A`; n,\H(9N6+˯|xr)RdrƄjfa_|!J[l:c68X( lTs &wQ w9c[C@9Ř0W4~#Đ_|<еk loX}Th΅4Pć7)}Q7{Г)tTDKԇq]gc`38! [: [B_ϕW:hs0io\j"u'36#5b+OCܹxgX^9^1 eRU@Q*;\^$>^z !؞Wl!=,0=bcTcndW =ɶ%z3q֓io O/sEEڿ1]Yvu宬ƙ5;,eO g.9zYc+4 {͂ه,tWŝiMKO 4@#+^<h>hioh$ݧbCs4sI{Q.a(RygzU9o[ 2%Kɛs͐OF sݯ*фǦfSѱQzR*71u$atbѣcךM]`9]͎/X!mR(W+&;RHrl#}öʓ죽̣5  @~4?(ؤež>xp"GX~.}t G ®XYe=6vY=kwJL f({E4ۇ ~)Ai/`Q7z V`vjQ#F~ɟL(WLu 5|i!)bA 0Sܙ;Nsz=xn1/ 1sTSuG௏}K"=1{ЗN֬Zϔqs$K%iB,ZN(Wg0C`aH:xyi)|~ vtG/B5oL<:k6u>M'"׻Q*)-4I $^N;&$JLr(ZۺHHƁib$ ഷ;:Iǿ1TiDk#/ň+sLoӆ椝%EwJ'm"kxrÁ4n;Wۀ+jj寖4MQ^\;eѶi%Q+0Ju2]VUrcm'1=VA1.c0\dYa\"X.r<_k* mO3dUFg]:ӄ镈Bsې%at^l,hyAC]HpDYi@e,0-qFOyf"WQ Xbr H"Dm19ƀURo%_dRjj.oOyv-M ɿZ/InN CjDj՞ԎMMWv//Ey[2BV 1 )ieZe% ?Vijx!]s0M640v<-GVqH-*\{ dhVI8ݖxШW9SX)>.w{1&D쥦W&UR|ǐ~,Dv  Weq=59]-L'È nV̢M*3ԙ6gS۞T3H{Hhiajٵ@$ýD~&R|Z=RF:g#%yEg)tǸ9hR$GJB_C/PK/(P ԭ4Yg hUd ɧ Z[iJ{ma`J)tV05%ڸ!r,M4O iUuK wUggO sʄI_KQ6[#ZY}H茉 & / /Z-y8j0Jj*ocI if(Z{+fޮ}+R{ܡY 9+vm&4Ju@7Bܴ <dB}@f~Kdϟe(,;;F4FQ%,q!~b﷞d6D+Kϯ|&M׆dEZ=ik.5>Q49>QfV EUN _UeŴ?B5vSBD9 AK#.Rq-2xb Gn;JoD`6 e1 FOrbb5"P7S$NT >zUC\^wf;W,\*A io=ƳQcr}z/>BTt|p:ut7W̨\Bc>W V7y",#II*=cRv>9D[,B8e !YdƟv>0 UB*E}iG(nrFË$<|9#e&J#^l9.{wߛ{}Øf]KezN]@Dm FH+]xﴒ*FrS |S%X+Z~QqXvMCEG `psz1:L ~Kqe(bO6=lږa&Q6nU"މOw' ua?0!3.(2Gz.:MUV^I~@:t꽚 R&sɔo d0%BbPaChJd|lT-޹юLT}bBNJkv[v]dNnApUZ0ٵGxt0^Y62TQ[z, 3 ':_ p(np*07=ͣd@70I`|ihC8qƍr"i/$ 9U?Չ.dIG3.h%K^ٮbX2^}7FH:+|TT0Vy:wusD}|/C9AR+?q݃5"okbEćED-WQ`Df|/n'&J,qi}$jQ brԍ5?R[v*. Vٔ`>6Pn{.%4nݦ%&Eesa0hO L}nw1uF^.-̿*8)H_]CWR9QFG7d8F=}EZB}˙(=p*61+x$=OM='B騅"<6KNY4%(LVVhăf>ӼlivNPv F2=y9EuiD87Z4]#~Ҳ+{{X2;I͑ d@L `ov'K$tцSJF(ީ *ikZC /9QRpO! 8s`aj(EJ>Pge+ )r. >pze+ֻtjcaЭGO`M4ճr-i2]%!F&n4gh oօv^΅STsAPYQc3 IfŴ*U@\A5h_zY;i=jWvmX(챤mSvXlALV 90 n! 81"WSYnܫ1q_t4ULw)2DR{/M0Cl-n[*5~%6є ӫ;- 艮ȢxyzA>gZbC QM:3~#&<4}Ta{«_+f0'7fڥr(ݗ }_"Z4@*}sDB>r\@G]mV+a:-ia K q4M~_0HtހqK铁vţw_ ypF1L>>z'be/e@fcDqk}Q&J| M)@Uliwe 5y(hDs):47J~ vޔmX^o"M,91Z5J(ڵ "aњ3Sf[3oO yI-]gmt\~uFHmFo-n%9pWQh7rb M^[>wC2\Qȧ#IVuL,Mg=+WZHx3⏼4]0w9ZM%oNxΧtJ6S|ù$]EuǠP\S$ {G9؏?Z27 Pl=C\@yAk#cx$Y>RӺYLH]pw#{tZT zLِd:]m=Է8&"v'īٗ#.T0ӷ!2f {%;Psdl I5eo4{0ԋw)Lz~b|@:`_Ⱦ2}CxH-SiKSQGN84"|Q@6L')%(^>m/mOHN"pwG]!H0xPw YJe[sI^ZcGqQ~n仅=LXC!`qA(M djXrK;` ;w ޔ9;e %HwD`_ ZD i@1-]1w5K:#rb!\ aktz7u16>%CSSz*xfqa!zZ MWBl/lij&0@̊ cIS:)mߴYDLM&ꭩJ3dϾTHJEnb) cht|>?F &`c*Qd˧miwc̄%U-ȱF[PTҗrb3503F.l20Eue,U-ll`qll=Bry5+̵ oW-?p:܀fBq֫۷ֺa Iku53ev-5cCT'_gn7 rDݤ[ &3kWB횯ft‡˶xNbW>+¬fEphJu,'Uv{c2keҢ6Dw/Sx~ NV1 OTػ'dyb^&&gvl DJ({Bvڻ,H6 (%PGE*h߷8=w`b9LXk\ R/sl)P37sckȁ#gC98~iO<M>կy?TC͊&;޲%=x&S_-[JɑHb<(԰^Oʋ.sPH5I {-xh5Ci]P LRe~q J ?B%iL<Θ[he{KJ 9qFLyq<5\Ng:2ՀNsEL>)#[8zdrJ}a{۸Ys_ NR=`Vt9#ׇtܰZYr_l$H0[RU5b$.Vx:dRilVzˣDϪI ElM9G?tZgv&}j~<7*Ɠ J:y "91{i% 1Qiw3돟g[;Q|i*YanK1b1ĭ 1?=C6\r;WѐoNݵbtP&Ho:}I8L糉x3~XXعM 1wFQK"4N 2Kdʪ6_3'뗵p¢y{nTzd|\eKY-$Y`i8NF)ՃW..D|OՑ gw8n˿B•!hY\e/==̱Ѯ=ce1[t<7=8'Чw]p| >>_䂰=&jKE!J;ִ I hnZ/!yk$ MvVఒ'}9wP(AsLf Q}N?<@یȪf[+ͤmoQѥ)Ph8r vYqtC!\ŽQ/-Q *y~ie㞝NFz&}  t4mdM0ڕT+r6mzzل;a^+)`9ܳiM-$9XQ "W]_L~jֱǣ7ukШAПqg =8L{(xu#k48~΀j3UQ16&^oBT ai&2 sy/]];Xc < j(#zc|~9aIǶ*~@GŇ WʜY6XC̝J{2(0͊#6vI+AR ;_~^f N:%17Y3n?KzZ%l㇬0^ WF}sjeD oP ht|MYϽE{rZx ho.!~[mvx:.k$ }x/ںbj%;`PVQ/@a瀧8y~X\.[X8VP}DI1"*6+ &ћg8~bD+/֑ ⚱0T'A^ /TZIzᮡl zXX/Fq0X܄XQ] 1K=5믙[.lޘB5]4噕hL[_*`$i?4[U6L9pP S4]_&Bu$ȶꤑXBGy>\xw]tjqx/(2eVF#`^`s6NJH)+JRmD-}󴎕1"ZؖHArR+t˕__J;A$r`W )2N,7`s_y_+@x\PܾHls;΢8΁n `li^&˃jh,~mǔD8Yw2ЉV0v,@)A_?fJ* 8:]->Fx0oIzȈႰAxso =Dְo ,`4;74KvVx21O`x ܲv+iP:a^2w˩Գ6oό\C_R\>4ė'IdP@,Ae~m[=pjXkKrm 4_󭖠=\cnRQrGvܿ]:b>gw$\ R`XݬQ!iwj L ]! E'sv*Knlq fM UdL-sXґH]IL=x;G2F`DL Dt $Nz^[ڠE OL[8cT8x]b,WMax^n~9ʵB=v5GViWp>e[ɮO|#޲,`yI&ȀpBEkK;xHiЏ3Ӎ큏RHHoh_hœ % L,T{eip2\)bgȾYern9sW}`K9S-Eo3xS)dV;eg0yt4&,:\iY`Yd]j4Y#X |njqpa=nJMH`sÍ'7Ǹn/G%B%/%gy})a~-|ϖ$՝vT!ǘ?Bצ#{(<І"p*_*Tgync6AI[Ljy#^>9-4Krm0Erven F2s27`ɨ)o.}'Pax~SZOrMD : <`;5oi!G)oI~AX]ᥝKQƑh 3P/̏5ԍ1cN8Cx88s4T2k s8 iZ(:56Jr&X8,?#* [@47*@;/By y<&G~D+Sc$MQ!9/knŃoƄf[ت4Rߍ xxo0v wr3VPՎ ``D*"ݷ mO9:V _.Qa/_7~${q[8BS}g}ao%B{8L E4G}例-(BfN~.0#4qIi,T:dpؘ Ejtm WeG5I$hA 7 E$\ap='lf*ED׿ oCLֱ@F͟@ u.Y43ى>p}tڶ<UvRG|3@&Ѵ@3pK(>tDH RC&%VU>/4͇Ҭl\͵;U9u ȁ=s㳬8]b=̟I^2r$\_>Qouiks"nI03r/lm._ϥ.hqTP +/&(?U ʩt;6rx m0Mv8J%]|jo 1 l[rtKs8˨H8U?Wn8CT~j_>W pIf E:mښBF  U2G֕*t0}$n`*J,90GyԆ@丩2lZ\?-c;[ۊ'f#RFKiWa㙔bO2#-uilp,O-D,E,We箰Z\؈)A0CL.2ϛO@ex2ުquFX×ܯ,z8-Wڛ GVp4|@ޚ%-aPt*eA$)&W}]z%sTz:z=<t_iY6;L(tJȓyLްWmJqק&F%}%טak, 5J#$pyХHX4 yK_ :ÉVk7r_Ͽ Aw@]bl36wBcl&C-ù%O4Ee*N%s.7r~)IMR|:o.wENZGz K)7%w޽So(ߗ6U{^fnLŏ.FUi9Dac3}@Z*ӥ/p`Ǩ *o [Н Ĭ[%ɟWm[F\iD!c.%iz_rr v/Wbݥn(NDQm추c9f{:DCc`¸UK]M~v[7F"qylnEG|꼗,b2UG #WlE}r< >dX6n0~s7b/|l+X%%o~dbz8K$FhK~K+"tA8;[ dβ\%gzsv.5I_u5#tU:FhxȬ7l}uEۯ{#AϬ*6-zz6dz, Ow~>Wd;| xȜ,ھˎ6RC[".ə(9W}C=cu<Ӝ! pOF,odAw!}[pkcXVYuuaN9lLdx=.%4 )u>ϗX)~ć'Q,k)83r&O`$!5^ux:.:Ew\ [{]gD\yz0AZ_(G[ݨxކnq)lM\P*TmPd ',t:aF{Obmj|!C'LòԤZQHX#(<%nsDT!P+ll`^İ .kt דt*C 0чk4/CÁz9yQؗ50zjT|WUܬ)|KK@hq}[33Y=MA Ɏr[am*ĤQY$L-Q|"DеvB@Ub0Hoݟ"rf?g"8(XI U~rkyY͂&j-r-ڸuZ"Z6ȝg_HF_)LD)5&1b#72 a=p3f[Rgy3oiow/2r!G XٲJJ*ǖ}A3X^ D<u4JнB(ЫwO%\|8Ykk%_ Y_PBztr=Յ*u}YT~bUKdCV n2 0:Y M=$p>4i9eQiĚS/iKSА.ϱ[=Ge\ r|A[փ7swQH꺘]|<=9T4ewbJxD`m~q+  + zn/m :ulKPd ds\o´ i5@ݛF3LL^JH^u~;hI w]Ͽ{ӷ(;Ly.LNA۶ nC\?$X>('Z3jsy&ҒjWa9E>[ ϨHt,oJpRTM4&]+)XL(X!TP\)ìT<-E[U)`wA0/{iJMDzi%D{0MSA4D9' Hqш SSO*Z Lk׫I{J}+هE>֥J֘'u w:73?6tꢽxԅzX\=hޅG܃ROK#J='b{nM c^VQ#d4g;wncIzWr&ʮԑÀ^iШ+N]k+5Vl^ ;cbiFm32 3VNjqbԽ+dil9:p㩎Bw'Ϛub=H^)ˡۚFr^ߠ=Au IO~#jftnctC=7+q'.o-X >ID?V֛Rh0u`bhV:ڨX-dE= $7o" 'zL~( 9c3ASW 70 aS=w+hqSAml%s~@Muq6?k~u1o67i>|m6  7 `ݕb8"41-h-RX"g1&\[s^*4/"`@+劸|ӫ;o.:Iynjk '5" l>>s;o:q*{,?W_<+:}'oɡM 'y / l#G[F>SsA76Z}:آO*? Uٝ>ZsCFm Ih*bޓ. (:mxqr1Vx0uvTZP'*L$'YfĶĔȁxsSofu3ψQ'{{enb"ٵ]?k I[ʓ"|D2mvGٜ68q'wM(cE]. 6Ec UWo]:#%ZzFD9tEeo?ǥoKJuR{_@4NREb3*`'IwmT31J-FRjsmCQ ,YZ K{bVև+g҅>Cߨ}hԃ@ii1vWOC{~ @a`VKLldܽ΁]%vB'&Q Ʊف^up[)buGc{gbfW*Y,LG&P;i/XxWǬA: <[BnWOG<oHj)'Ӊ$&$IҵOu)hPñX6[:p?Zcv/wn#t':ZO V";X+C`XE bਗKO)'ѻ!otY7dφyϼS{c֘"a h mc"w||J\h"-C=* [x0D@#'ԏiիFv wH]hݞ[Q ZUpOD* Yr,sZGN¿ VkB˥oGaڬ^e+@|} Mw/i x-}ˌE䅴RFbj0o_l髟*4Gn(r#=VQU*̪ }|#P~Ofܷ(>iqQ69"t D{ѥkO4&F@=N9ZE./!e qǐjtwF+ҳE&:ڕi<$\Wg_р_ׂUZ+`:~[jvMܛԌ|Ț0 zw m]Lܮq{iwI-7_Dk)_gly5tɫb g DcWwdTq1aX2~WK2H5:/?8ɶ*aN(]F7)W.Mj [c: qsl&P~EM;2j5;3ҵ.Ӊphzh$Nף+b+ m DBLbهY.HJ=_ĴNMꊵ{~AQ}8[ИLhd(4~} ?/%56l f+$Fe|䠡{j'm' #9&U' BGc'DO pi ”ތW{Li=79WybĴ⚧l> F2z_JG5 h,B2.2!^H',utuo ~uTa@jnjC7EI]QqIH,n&Ц$Ԭ\q2x1#M~bKjXdczuJmm1|ˋi]=<#M;fDB)"nvG:l*ڒED2l9!$)`./$h7ى|Hn?holP2 81s~7g=(-+$-t"6ˁ$!Ed,?o)rg@\e1'u2'J\aN{ڪ}NruI-1^0xL }(EFK# +d3ZJW0HWSEU=5z,J.c1ڐg#6x$l6yM]9'-o-Ա#w݃ߡ"uLjy,C:;J(,}q}(0vZ);O8L嗹/G 93$OMSs{koȝ^.o3:eO3JN/NK+m+-sQ`.^}-Z);F]ӯf6c ȟ;gUi]= 'Ûg?;GdEatC^nnW=-{ kәթm* KeEZ1SYq#lC0iSDy>Ǫ21ǧ[0bbG~#asQm>G_c98ûk#@ 7(},SMEARkLe&R[H~hK#ZhBky8g DCg^9SZc 9x7z*ͦ]΅f+cU7v6*Z(6Q@4{*9^ov $mQ.!&hicD>!T!?QGۯR\IYC#Eo2ߍͺ7dW]VT-Ѥw&i}М";+0VxWG@=HI4Zc'CczV.ъEj.JAp֑EI,E80$u -l~jߟ%n' OvAH#f^У_\Q64?l*t C,&OyJOn跋z_Τ#žEx#Yn$C` 䥈BZj|ԚQ]a6A_4i-X^Kp4~Y[nZ {]M")X6. }]ҋKJi׬7tDЏ? [olj5UeߖuQ0OrQ8Yت{?Y0He(H}*).phn"&T<@Dp5cjxToYu;m;y؅}fC/߽>$bD%CSH"=, "8c^JeXњxXK4(h'Y6W dY/ÆR]\M'HiAXuI* \z:Y#Vұ,^` T@/;b7,]; *9!vJ"v[Sy,ܥ)vCcjB X oSJ{ *0x`A^xԯ̛LtK6_aԄc2tca{%Ly 6^0 {yN׎Xv-rh=ĐsLLsGbuѳ[]vJg?&|[;CUKJDÈ+P:Q.S%?~;)jC|rxg`vILmϤёqx RV])Qٍ֋/O}tkc(rDkʼnDR Qysf5Gp`~?r/{B|ܨhrzZb,'6|Xŝr8ih}E2 8'G[ an5n=٨ 5@Ae6CߧbH"Ừ߲ϼS E6CYxPl(A04Vb󰶔`*wrm-ZKo;O`_GtPܒ Ep{Io'}vh7L .kD F'DO%j{(蚾?޾qp>f|wRϑX F\*揉tSʿ]^{kE۴xB!S.\Y&_I:/l."Õ>/aUH;6Fk7 >'7ɯ 4i6GwI W NO8rkdW+Nƾ~߾${0;ͅqKN8Ho$.W4#.]]uwp%K8 f)"Bba{Ht؏3iQ&";ٳnTjxD٫r8t4Nf4L*B%$ɂndwb@`TAUo^&lyͻ0 )qg{ y>ä4ka'=SOm<`).ZL UO1Z4fAa㺷ytx !_QNj\~~3|fk,mZg$'+/-,ig' j:F篼?bsV.lٲTo#=n-pW!sٝ9 ܚ| `{y}[Q(5,dKb9bNS0 _jdH keQW\<)wྂw s9`+䞝%r!/MҮ,}Ux~~W߲h̋~]R: \nz|x$b^ A -˶HD]T5y}ؓ2. =/y۬Czm`/[dCS1>P %6imkX'"&8\Ǹ.q;M2"ϖNM})>6D dw<'=yÂm#}ܚl4 *v鄧aSe>d(Ȣ~[ߛ7*}Q;֮aHTz5 _ u(O%ޏs1gU9MKypIUbr执B{P3{M`lDN|؏P+W7tAC T$ǏkN0 ٥*v8-6O'HɕlC"OCJ.mɢt+)}Hѻ.[`^}q `Kܝj7Ӡ QLԮ?C)o_@ ]}6"SKa[>/UH[*a32tCvp70,>uB[y"SlrTLkLni֫zI|TF{y-ͪJȣܽ Ld^HhwO!OXTVj0|i|mEmZ,N,~MVr0ߐ+<(f2F7VyР?Fz $3g)̘vI~_~DL#[:o(G*8q I;#b'@bvC5s{}lp{vmP47OХ3Nd: @ c6&bR[|WjO1-$ƚ?7 ;%EoNB}{g'p,o݆G<)KNPҭ=J*"nE;q:v1RD Tt{߉oT5E, 0OJ'81JB]VyY))͘.? ML`eW6P[+dI30;ުOI[ ¿`m&J,Tkx1R]k4͉lz%[8㫮a2/,39%dB>NL{+Vcg Vyxk0$?Эӏ[{sOgM1j  A)n+RSk]JQXnPY )RS:#drɪhlYf?2AO,25_2A[C;H/PDi Q`;rW h$)E~Gm-s4ߑln^1\cXn뾝)Mfx #yxj8^LWQW=<EKrՈ}`UWOa4myºb#p@O9VY]'(hN亾`"Ӌ,5j#rLKEnf{nռނK{;#>8ٖ.enotB-qnNf@H¥";l{UY%AĽ\iJ m-ǿXl͹-*$&umIHe%V5BǸ5_I&iؘCZ5g _ÂV"KjъC E5WS6 }{8gWӺ_ɤ_a8Iy>2ws}"s^ P L?VLWR  eEL.DhP V{xmg7ܖ} !Y1׳UUv,2S/>s6/CAD-jc<[5X ewdi/ZjJ2$o2U ,ӆ+XLHl;}UPI>V?V3l< D(p~b1 @:0,-^js4Mm4unSY -%=ӷwhD}CmZDW KCCO޼kGji)bX+Z -F0QYަǣEͺ]S`V a{AFcDrF# ~IZ+2,ثVg_BgqjStj)+z[IQ,yeQCZ> -cPK%L &'R/#Q~׉p(5Lqrb֖hBl>!"(U_ȍ[D0*sUJ#It2cQԀ%lFC;rՃճ@'hE1 )(C0?.Fpջn`YI5n+G F܄ʶ.n4 vyunI,?f'"q+KP[)DR5v|W(B]&E:%+Ոru]8/I䈼}q!S8Ȝ8@\WDh<":NHњ )V6#'g*ѤuΞu_ Ճ:Ɲk&Enڜa" ~)\G˸KwQ"W!7B n4EV ;(>& ԅ}`{rAn(\/v_4[!%\I9h37[I;boB !h\]7\"ֺjlY39"g#/x HF.NXqWPvJ SZ(Z-*ԥNⲧB -cI[y1g{8iȇNH6ٻ#›0Xm5Ea~2.|O/x"|wD$d5dtr>1 wn|Ƽ_DU}K>A_VANahE733L ½*V#6:#%7k3tzkdWu I˞$7ݍJq$\-}"hFxш9iy58/XuLj.%1kg!uw亊&],Ni:tVy)$ԡ4EAL OX f^\D{M4)ƒi̒uml7:ᛣ.$t-pP;5֏xgc"{Y,$SQhwKBY(x/{j֟x% $~\ t !<}TٺAFW-YbA^ppXި( H}4T~犁Y:$eS+\-PVփ,DK&sD*>,z㓵O)77`DڟCpd2_l\iXu(`nW^4LUS ؠrw<'M"4BdS{dC>'Y}dv]#76!?$ɇUq|m{_W|Hy`* 9mJ {{Ivn/ONX;|2 kPYk腞y<|-Xbg;SښY?'2`yN[8KQgtN$.G75}yﱈ/=n9_55׎Ѻ@ڟ xd56;.pУH:!+._>TlyXb8 p,}.647zy^T%-2RD Fv!;%f mEͥ5='ym}Ͷ"C~l2Չ]KTUT%d +HsySp=De_5a3fV?w-Z.6W,j:rt^)RyF J4A$(P;u794D舥1r5F3]pk`J;0˔<g-ZH I,]^}"5f؇or~%!*buGېQJг.V΍T%>Mf¦l֭.<;!s#z 흈0=m` ~=Y!6:pAL=OsT_cWiy&fAU<Y͘\Hcm5R~Qu `܃M /.dؼ3H(ct&^؊ϩ]+ȣl-пw]}+(' x09Np/n8`_qPؙⓅj0|]6Xs)ѩL`wXD׊`8 sK]=םL5plnf,&;v'-w#dÒ$BW%G~H]̍-#j&C.pdžٞF_mgsXe<^'ԨREhFYpʽ "*qcTd G]\cZ1Wֹ`7!~6,{G7oʼnzlreGd[?xf3clGHOPqmՐЊ;/=j~ɣHQ;D| \ܻ^Y al'Mmй [H4ɯA?_ X]bƷŅ<S[V +GCƂg?mB־ ۿ(~o=;5O1}=qU}xDR }V>Wd|d`oTTrӓ?ٔ6P8i}n@)WMWyI2ؽcqe,hAIz#S*J/}u/=+:#hfY_ghVTuWy;.ߤg\f~JDk1J/y_ko`yhIzSuaHUu6cY*/XoN+/ޚj21ݢ3B pMٔ*a9寡'b5*UI ^oL ٵAH\ݴJ:$:sOK[96 bgij$3ǝ!v/x\ҌG>3!\]OPGFLO>T{T@.' n9\H͏qv|lGQ{^ ԦHǢ*i r;6Ll=,wHJd`eɇ\nv:US%ލϚ?a߿5 W>:Afysp/D<}/T5ɿ.`:~?ZO|+ QIyϥy c~vw>~~lQVX?Kq<^k"_8j d^u9YʗOX<\N#WƟI<6w3tWғ"USʧe+3ޣ9 [4$':e@uOQ[/ Y{7To>:)쇿^׫l>gya9:v}\BdODž~ء}?2cw9=XEQMTH fTz'2 _n Q^d?"שٮ ~q\xL 0W^WoWɞլ/!V>Y22wSR.@%~/t|[کqfCAڱň̠J7 #VTU] _߻ANb]rNl'!4>;|c&lF^}]n?h!O$!Z} 'Kx4) s$֏{󂙃pL z x8KRPH.9 w{=5*Lq4HJBH1J; "[WQd 3!]_*HhO E#hZMp)C<2k1P;|"7.A7x 텇ܑd (dGoujOpb 9ٜ\tV:YBB ho-/kdDTUPyAWu1QԃKF;I'0j$yHۈ(q7mW0ֿqz:k{9Kji6CٔbwtGqCY_v+ fŒ7 ;0O4qٚBQ_AN%P+/N;/SQ1ao%j(XJƠ+^Ә&PV&\?>C{$(*t>DYINR+x&#']_6>'yiI=)nߝ*t-"K2N]w6,W=ʙ6-<3f5HOOeMP2nfe9io0@%x/+(okL^>(zۢ6ntMxuM ~ۓAw#7eW#6c9o$+pvG3Z#wk\1 *oT{ =x0 N j~Y;$dv46\A!GA$PyLiZP<6s @ X}UbJZkD/Tzg# g t1C#1 Y Wzee?cGVW%QB&5 DCk[j&@=`[*dɳ؈1+:]+XX_|g;bwX^?5} Rz|lyw;ʥ#'t-1JxXxnKp4 ]|M?K=E tO'kۈ: &2 hys諦@z/vL(tjζxpt>J\T?=1Tf܅+1zqy4#~C{lq st}1z#Eu QZ ӫsG'!8R53(/GƲ?LX{)jC^a e2NmmH]7^=dhɃZaKf/tC"<uh6;bxwo ڎ4y؟ƫAMVzYѽThZmW̱rFP{i\\UaJ*4n@+٣[^\ݫy$9UHF\[^48Yԑ'_]Х %Hՠkcdw!EÍd"z,oY*j!)-',Z7㏮" Ơi' oj_rG8cTj<8 jdWc~XS}́DHZ誌.WK+fV%n4+#!}1m6_а}7>tb֢bVрSM@UcWtG>bTnCwGj~X_]q35r5ԋ#? ]{pA<kV.HbLbZ"4}lv#HZHǶt{;٪A\q#AOޕ\=Ki oV ;&N l]}ZSқ!/nYJūG1Ju嬟#cZ@< yn`ht3ԴwWnEf ,j $$tT)'!lqy|Yd:\*,\ƒC Nys,M;µO !(jOhAnӈ剢uFA_7b b9.BMrVTBM=-R׀fat/ ~Mg,D` cu= h*Kp!^<\egx`ǐᑦeƎ0ó~!E>ƅ Km4{ HDja9}3m)3`y(!-EQ{}mp{DFn'6;)a|& \V CӠ]Txus.M4YBiZvsyS$` )xn?*)$L~?O W2%ja&V>zk0ߚJ<b >OQg36@E}F=8R sPYGHǮal_ RUJ \7vXRoLt͊xWcWzcC8 ";hq>kH2 ҥI? T\JBbVFV5^AVN)`!;tUIUSDf %S*xis ge '~2uq1Si(3`8e64yh]b )kor5`I87x:2BQ6TJlDC&oB%*J_dw慳K!Y[W$)w\8"EE{^Ĉg;M.tI-LKBM[yۈYAULG0* )+\g\U|a&tNt1{YsNO`#%i2FE꾌O\KǼ o_ ޅ03]*m{W|._'(4MoBEj-ChYsejXg(DI>ߏZTXQ/<\_ʴ vEdv: ' |TS0KS). =ַI.3`.0뻿Cön6;k$gTe@os$a׿N>Ϟ~fuqU4/́sj6UpPm1~ArLh{lpH.b[;1! !59?5:%3E6XvUw~p42W Ԥ6/3x ܀xH &]?nj0{G"Sp@+p-q sat4F) ^*azE>wCߓ/ < PYzL,`cNGy>+E&xG%$$&]`̨O݋MP x7D,[QZEE/; S+VM.T%[j 08]y)X`'_m za[8y_#}cQi/+_g4 eYk`&Ӽr}J]S=BTЧտ2 F)&qǝqOsu)rY=;Q^ebY]-|:lxe';pq:&?Svw+DދrC\Wv O^=VcYS/ąQ L|(h)CM}]rw@rˎ#:!FT%۳QXJ>HeX9hד\o["r\8K`&p*C)a?0Ogyf $8ƃvs;;Zf)$,S@ŁƲߺ.d&팤lrٖosU ɂ*6O"iwڛ*pGkwQւ*11]$jHȪ#fU .]v4H^oUKZZJ+W ^6̠Z/+KyCV^$Z-YEeEEpN6 ;UX?8%GʣܱJAy<)J8)~Hֽ̪iY3,J~OW26:i4-ڽX0m#s\v(cyx=q?@)qϘ%)WjRaaֱ9<`5,b]{XCIms>HaA)UqT'9xA<& -&y1,L{[ mə] NlhR+;**7(KCR:BCn7&,2݃ v)DyKIotrKhRe\ߟS8FW飡Fly%3vHGe3O 5g38[gu /bCN;'LpYc/'qhXJ @cdJ%#AU T|Qdlvɧ^6/o81Y@/MMtN ;ԥkJԉlܑuZ ۞t=OO9Fq-|,;E̒lk\vɘyUmǕQa9#ρ:0U Sʷ~TsLSD"M^Z,I/8RF Lt*=_SDݸ-\*֡ %eʛDx/?;_f4һ4omn0b⹨b o=?Y{ûy:=ziȽXBrh-O7m LdMhݮBb;!]Y=896`[p/bhAFބTȬ>2dڂasXG%Q`iHՐP;,~,K-jSXѫ8H+s!AuG+jWh[EUŅ{&qбn(anX|/e͙UW?>[W8Uoux`Ņj6??A^kqvkC3PD(OD p BUYXDHίr62K b,&r+־ٯYl rdA6hP<%*pl,stjy͵RSc 0J5v2 d ^SkߣoRPN>>xZY`iv >!zuYǖ8|ϸw"2[".)W6s>ctShVc:E-j=m Z [PUIP&1ɕҕ%74!3dLzي@V }4%ELc`h\v e\]bxO}%MMwkć-N~(+bT~NZ֐]!0;³%-N4vi:g]V& x !MP+8ׯ?R7-L_޾TGc ndݥ%2C$!mž[JlO2[*Mg8Mʑ/?arnkzZt coWasR&gfMXzb.NQ+%=5\E"F-nnDj9bٓp`ӲQ~AqKC0T#T?9GzUC#mJXVpEvv;NC*"|6 ~x0Bzvd+58Gs_1 i kN7',*b.v/\{0s[jUK['#IchzY(UHx)τ0?)n:Jš6銔|2m.ڒq\%&7gkh9^X)+7"xa5t*-mK/MԚw't ?-Ӄp$并$VGV"3WNZc}\A'\ 4 dZ HG9.+ei ǮU1C!@` CBx2zd&|֯ ByM-&k=i'GxnܑoN}O-kAg;ƒQZtakq}=SAn* jS\I-6d3:&?k^v($H[(eI f4Pib#fi7 QoojE7LL4a'4Gne;)q*V+Ě#`O vOv)H$Y0 0nitckVyzegd.J1Ϭ0x!WU>; +Aizbt hЂa٫w?Zh:4r!)YFT䪂q;Wă2Ba.Sڜbnj3:UPI7PHxGV%SQK6Ɣ+O rW{AV&&??P⺔P,o\:~a:x=^%0>N%zN'>O>K9{#~PZTޚ(}z)0c,ѳK$}KO8>x͘u )Xb1PvWqlf7v³l Rmw*2a`V 01VwLm:Olz#0e?rb56`I>Yp&vWu1#I{73RMI@@*-5@WlU^0[CS7vMjĩyՏ~H; _ߵg&XӦ~c.،DKX*ɴAiعjyZ?iЪtȯ*%2S᪙? . 2@ʕCIlEt8hYMeyPjI{2~flK%ƽ~u .3dPO]Աt|f.V)!fkt_&-R`O!]M,2S|Iqf;6Mpy!ŋ"މ#Bߙ3(gnSA㲒To\Q8vg14&lXD$DX~'.ln!&&?)u^%_0{jJPyF7̘B؝ oRLHWs ZhDJ85?.}9KG.~`1D ~n@-E^ S5'=CU 5CQ aK pS> \}3oQn:{G$bT+Ufx}.hʃ):uDZ r#)^nAkw!笅! |aMz+x}:`P%=>RC%T~G)B_(_Ft$>mTv)u{e)^A.ݴqC7j0)FcwgS 6w{}+PShCA28yODEhqP$2;+賕 }hڡ$ksXRŶ1"ܜD,,]D˕Ma#G^\5i1Q\.>gҧW!>:IH])ruA7ߏo?rט/Ǡ-`*Z^ohGsOtb~Vy\"_LJs!7p<57ު<<]d,P rL4nZ'EPxIcG4I-!dH?A1k1R^HH$8&eaa E gkHrM*hʧn. .9ȰM"AC"WXJIͣ27pRiKVL pq?Ǜߧp\6;f~h8F tRsJ8fMO $\𢻨ˢ泫zp"]/8~r7'g џDf$gibYD3yעb8ؖ>XIDu 4rrB5oqN3-ݯ Uϻ.~)nw1EEៜ.h8_(˺ W\I qQVC9PH@᠀[uN*b.!Jʔ0Fk-7G\j[3tǍꯙţxQreh CȃFeUppzH4޹Oq9m:(4f^9&liFL9č dƢ0=vTV7G ;,Wjs.V6غ^]3m Fa8n&<nr]gҙ=-{roKOL?PU0G_ZI53tn3jdyE.%.棜o< OKm ̘?mC]ÿFS >f w( @,q>_KhoIEQ%R]h[.ޛUUM2H f25כ.`#jpVU$ WN {Tx5|h 6oS5+z҂ 8ŻF,(T}xXX=6QEiMhM%ozVX*O +NnO^ZE.y8m܈X]$>mTv)Ma ,x9s#X,Qʆ~ )K5뽭q)vtRNÊ{ DdYw?`a6 V[4;zC&uיO- )G+%TKLg/WCFGFNNu]`|WdiеeG0[;~QK4 )#5ķ~-+Бf2;TPo:kRzzlݜzS<5[v[[ ?jKkkl<J#t> c5KrRjgo/ jtJ۝ "ejS 9Gz&kc;g8U0ڊ̊^'ldmEXek3ͼN ]sIۙ,qpz.U7`j)ܘ(ۘK`Jdw;- "V"M_İd⸷a"02S15>Y:M)!'^Xicڡ44 OKk/U?\̻| 6Ǝ-KK򐹫:/ߘ@;U"L|9q.1g(gyVswBTeU2>9 vWBlI~מڔFHcFG߷([v1jII/Yoq eԑJp|2 !><po_ϩ'mo ]>AqzcmI\+E"|aFFXaׁ'"'U"Y~tC'nh\'L*TC东C [1'ճ$ J@nU#9d"b_(IxQiԀdo~iD"c_Su4 #7v[&P;`OfrHv}gRis/̊bmU*S0|Z.x)b:-bV%D@.L,Y !FZ17HҎ%}YQշF|ahpkʺs.S:^aJvC85g찗j*-kJ(ץAUS@0]1c)!Ȯ7_éx' BzEШ/7'mYjaUOQB CoP*|mC6 yt;o*͖47'zPrgދ U(GAĵhr㪧T4=ChΘ b46̩'=HkvVT 7Yç]oh&+R/W+PZx'z]H:s-pݚ4[ =ngpzh2˿w@/#x$=C|OqІZj&$C0MY9EY53'ϸ V.PF[2.Lt$72b5& ?M};o KpU^|{Z@S ) (]gm6maӆs `Y^SgKѰu9KpnőK~[_WY8A·ʆ Ʒ#tONs_{w]hFV84 c<uqGkmXSȄNq#Q9(IJ[jPؤE dԬOv%U5 2jx2؆@Y9?z61M!иvUY> C Sπ*|5tr6w6<Idr3AvɣB+6 N{nN 7f*K~b}fy4J>m 3Mo%tʣg Qo4[! r7nzuVCӽ;֖kcze 8?v)zt~ źx _$Y(DLd.z wNUF-gscP7uO=p+d5]baoS[Tش&MHÐ@-uFlJ<} e 6 L9=%D" 1%(* "e=Ϊ)1 ލt{Bi5@_!HǔRgy?{'vK"Pux=A݄ڱ{-^pZ })hwW28+w?x5?i̖W-#,MqGv9UWt2`/\62I$ /u\ԯ7.;"ixUqgl5'z|Fa>FhQӡ.9\-Z.w|efxgrSȌA SOA\:Ia=--wƈu w#?2@TǜmF}juySgLsngoﮭm]JA#^ >鼿,u[!#uV~3^7(đM V =yԤ:?ÑI$}xt(VX[^ ƽO,6WKZ|-@F7*z6T7N~ aThv.)n8(w$Y;fK]vΌ ݐ" ꧨwgcd%NV[Tb\<,h1]<6q;Cԧ;C,'A<@Hd\g AɟYn>k9֪Fc9,5yu&i5'ruS-颤h߃< ͷv)o6l7Ɯg*-d qW؇4yԢ, nx3M^l4qHXlSĀN_I0 A^@ ; v}\&ߛP㉦&7n 5#(Qȶ"3H Ŝ8 21uY6l>9Zz cfM] m#Wm4ki; %X휂'x]M+)NO^X6\ƚS}?Eփ}ʔwpUqbי*My&k]ș8EpgH}`UtӖs>H+֮W!$m&ĵWf+ .+޹ &gw"s$u="s{0ty [ XƖY>?N*ti;~a`W9j7 >UOkfX忩TG3F vӨ>p7Uq5]Aml_Yt;]\&*/g$ijHUJ<~;LmFQ"ʨ3–`ȱxjkvlGjn{VWeDWy-e1C'[K=x{\w[Yx u;!n:9xaȏ0:g!DŽ2u9>Dt_A<dH orȔg̴)*C.?5JD+aYKNoҨf1>2gj,SEbD=be;d=xX!0 ?b́ 6Pk _oG/4p0ፏVo~D! Fb1x) g֟`hkYA0ɡHVE\d&u )VOu`/!?̟]7E\Vo&РgG3E6 ln S}Kx̖FA3jFYVuFn->7NaRˍ.,sH T S_i:k/O.<: g>e/o&AX {!d^. 0nZ&!Rf_Z$ɿMӑ0aPB$ 27|J)rdӐ/Na%ρ#L~.En *#Ӕ`o(; 5JFb0K1C}Zd_>zlI ؽm~hs܊O 4B;3˧TivۨI-na[D`?Im!验jmHe%˽" & N迱[bn@/ Za1?*BQooߔc6!#-D(elo b~f6ndؠ5x3ewI s[~)xn Py?jkz>-H7꭮(bpT+銕zeP(ïdEbV2*u(U>?J2CF~#`]&b'ksdtIڇqO;OXY]kTSꌳpUt(ߚ@r E$l܆P  `Xo$'4}fy|3Ů7>Y.E3%qQ(jxR 3dqtk=ы %r]}G筓!2z:pK0d#뒬fDիqVJ;h ߳lY+.2sZu7f/Iy8k؇u0}S _`^Q=?I­O f=*,(\z86&ۋ&d4ܙNlTPndK^Sj19媞@!a<jx7bUzul.`;dnҔ IJ(Է2%hyt5)t;jS̊Pm+ 5I~g}n Q3{BѡKL |UDa]0 ˰9N-Ɨu k8f[ytY*ӹ2*ԚiۅBF/]78J!WDŽwslM}$bc\˛#["=y@؀j*X3<-+V:^ 8MlIxN!Ɂ0EQWٳ.S۾USEؗz}fMe; /Zm}}t/T^8s B+Kv5"n: Vl FENL`#P}xԗ/6jJ #̐Ξ^bF P75#e~|C_wGpwO>r}l{gŨ9vHƎw1*8=XDZlk幐htQHm.pxQx9saan#ƸTmșmL MC-*h, Iw`\mdx)k(ЀRs![{u 2J*4 c#b78u.χtnUvMAZf=J"_~Y<)>Edm5Nud(kۇueO{wWg%h8C2usF+R'v$iO$DǗ_2X:Я,P\p).?7nvxŰ+Y >PS @N(c=L̎\Ic4ԚïEHκ G>ԧa8\ _uae]paQL¯OZH.$V\e)t 9U.2ae}"ZD2s*@DL^/J n  NGoo/~@MnHQG^k&:|Y;pe:Dd#t<'5~_pa{Spyljc9IˠAE[([cעcie0f 6b%x#R&ʒ\9ա 1H-9_wVHdc& BH.yí[߻zӝrgv:goXHיaVޕZNء0z.$U^"j^xYո,LP.}gS:.cc`^Rn/B 6(GƇ%9.K'†\>ժkp"Jb* ]K#m-_g3_]ZZ %-zÛ#Oh]yٍ/:vت6WU.$w$XubW~kBzRIJ Ѯڤi l̆Lx_r.eƲez텣̇bD 8~7fCoUjw]Ԟ|P@ICfhUGDMdu nxNZ*^Ǫ׾n*9<{X5cZ;OU8=,X("/S;+츑PFmE{z?ɚ =TAb et+"P逷?BgQh| RX-Avz>n[?i/7Y31%<>|"^|`mMw֊K(\]qvPh2@S7óFcjYNE|id'zR70 @: mP6sg35Ѭsb+ת׆`z$ϻ!3PjK C[ZM igy="UDXdh~kݒxF9xu:/2FhF K iLҴ{tt mhlL:g] h 4ҕJV$x<9qOO*Uy_?U5'&%4R{iw$oOξ7xeh |NۗwJk,V{&_+ q>?ƔiW" +z\U]S{J|{#7H31s0,()+7R,d kT4 % Wqy{x (`·cWiAv)TQaHCccv̖b*vJ YIŎĈi"xzÃTce~b{KXonw*7څ 'k{h_],?qf1UOFs#ʛEüTw:2ČPh74.'vqaHoݟD jȱNq-NLIsdaJ--9_gyʵ< f53.";fb"S3`N\,^ L?pg ڵ 3 x? ;B.˶OzyCh}όq1pKŐ+ʒHܪ~ 6ړ M Yh#=.v` ';4Io c`/EXCV>U^,;Cxd |͇3cjgWAQ9n[BzP :W36~? rgE-?Na!߼J1!LO QQǑ*Z axn.( hZy*l1#asOk$M!#%x޵3AauQG5]xk*\en@+㪾4Vу*I.~X퀸v|yz ?txX ৸ڇYƇф$By 1-0z P'<D{-?8o`q-Sor\|gWöAU@dP>3|D5Y9 1KᏏ镖*ZxLªs'בD 7Qf# s$(^XͿXۧ_-z7+ x15,/ff5JpkN8nk3dHZa^ X,=i欽nֵ$*N8$RO,}4UP9se-vmo4(cI&z9..h 5d8({ѕ)c 3!syh(bMQb*F҈$, pHcl'N0@SRQ[ZvF+A /C#XZjn!bԑ ~T89^#eT[ўKb3d꠭WU32Ԟؖ 嶲sg$ k zAU$:SGCIJ\v(}з䁧>Kfh̪all1FW~oQVD{ [rHZ)rV6DŽt#^fI"ClTrn5P>q_&.gm,-Wq҆ 6wھ|ɣnʖע{vl s[&7Q,> ҺO 'h8|"sP\=( ~=wܑ< ¾TA{m)7F_[U((l|hl=PTc7kkMگ|fyRD<&`XXyPaݩ7 h0 <EHz>IhPnCz'UJY*њI.~ }z".U*͵- 7HD(6X+զ(}eE+ 񰴺\{o$'snqO?M>xZh0ދ)<ާto2@6LRe Կ޳/և֡mКe ᛨw7IH P$·|_'h5}λpuۦk!̄IVd~Hڝk4IM9 Q ᪨u`bT^9rp0ÄXuf|){}`d*A &~hjXCjBe5}i.'ᾃsAjM91E}Cn0|z˷qy`oNDCGwDxwYrtf5su#(m] { Vq p'aS_E[THmT*R fGB*jV'(ڭZJ*=nbNUtJxU++]r,/`eG+6C14_ݿm wI;B,w~pNwB*ֿմBg miI,a y P>Ugu!Q;jqCOR! (t1oF$$yv-͞Mי|zdw̽8)Ov1<O:&NN`:=˾gbHB ]MMgvň0*_npδ.7t%w4 _\rN!աuC.\&le`nOvR7[\KS9dͼB|XFi;UI8A*.I6O=׊2lXyN`%a苯b`*)ϰnp`NԮC{D J!4Oi6PBC'Ǽ+j~*GymDZz_D_>t,;lujyAUvw,0sh#LbнK ~W,4H۫1DAg]}&LF )p Q)3}%n˩9rBsJ: NF|J jo~ݸS d{"g 6ķch=A ciiᤖ)=2ҏ $r!is)~*`86Chr^oqL[M^2ȿBls^C[nCuY>'Y5!15/ 6jM}ODߒ/W0c(6zQvpn:Gt$Mqˆ-t:-^fSW1ːqW!5kdP<q_*7 n* Ȱ"a\J$L~c[Jd#m{ԝuQ]weX9f˜҃Ɔ#]t))jfɉ,B,V5S&h+W&\,4Ye2UvYp**{g֏c5*k웳Sz{c}2iP"b!7Mu#e0KõH.Uc.vca(R`gMtSCnY韺p_;hF5*nTMХ# !ۂ hȓ؜E}9JNgMXni7 \jșƯ{)4j1JY /WU%* y r}I<uBFc,8S l*j8)"X`8bQkШ oA" vg,C`jk }xw0=Xթ{zj8eYxca2(O0#m\z}-y(D7r '%Tf7:څ0QqƠZ/fg!_i6ә2c˰rydݞntlMn?J~F+z|-'D iх]E/#{îOQ2WTM| Fˮnoux#o C٫tP`29skasj4`&}' eKb8E2V?2 kg~e{%Ki0D蕲-I`Nq6 d͆Zr~><sX mԶC(*heq #}QӼ`IJ|`|?f/RHoAC2, =\gBwlG<\#םPw+h?zɞ{gC: /ү?RNA&uAҼ cf!2x}h q$nz~Cj&f"%iZ94 _N^@H8u7P~Ҭ/ѝ+|*Tp'R7h\F+6&~BVds6!g]>1("Ж(cYƒyJ:_a(2NE}1S  ebu DvN1f<OcѦ bbڭQV sS"A&-Df: KEx2rac&'K j#";^Ӡ֩Ӏd.Wr𔑒w)bAK̠{1 P]P٩kzTwDd.p~l_E$o ljΔkOt;(Ojfi? >Y`.&4)ǘvMQS`+ VUϝ£^b2-eU&zًq4a|iJ W5BtDŖ*NߗsfoS;'eWuܾM!^e(ڥy&\__^P@1Sv:*TCUR0ѷd>A@8H%y:mIɖTua?D(‰؈Љ2tJ`RY?5SFhE`AZk҅& ` Z/Ne&/:3xڼҎz=Jʖj-蛰~HaX"rw1%zS^ѨnvX Q1y)›&#ѡݕ6͹ED!~<=rUilc+#_#ϧ}օM /P cW6<)?e0B_:S++̀YSz3[Џw &ا$w7o(Ӳ?/*0,qUl *n=|,M*T ~L JT "" +z u%%濫7D4f y~.PШpqH?dP$.BX8g;yվw/důiɁ"9]0T+Xa@>.V;jpEimALI%Q$-J$s!S,IFi_Y^  h(ˮH-$ZCFE|Si_snrcs"` vY[:ڛd\٠ زi )H&[Nwv1X >j[ ԽJ`[t̰` #Vu~!]2#'nEw$ {K}r~+I|ޑ猯h{(]ZBQcGDW;LLS(#xYSk#PgKcnٹ!j? ";\E/7H"5Sv~?{DiDv: Jߜ]Wmɘ\0<ū{jj*,:%\91'd]א E.wuEM;o=8OD<&bg\*h `( jT[4X#v1|'dUF*[ʃ+Ǹµo$;߷DǞg8xu:v [Hq8+UKRI_Kt6 v)5FQq0J!PuroFIq-3-Z^Swd>vf݅(~N3$;ovha맯7`hzfIk T'To*ّ~gjNY`4D*̗dۨ657]u/d)Evf* q,cȲA/c p##M$1b~W>OU8ۢ =UV\nGtY B A Y5{'hv'\ÀNѭH oOHr]~*d93=Muۻ'痋/A aɪIN^bnh殘ć`3m2Ԗ1? v[rz&h03\OwHחׂԮ!ۧ5X_v?ȶrDAڧ__fH#%4|5VQ"ZZlPҪC 6s;T^.cD|%^Y@ /S̲«_ΆY l goz LD՚m0d%l0eeB1tw=-F-XPQD H@/j d;"mC\ܔ50,!U/l%y{ sYݵVSس(ǔWޕ3qPA Hu0cX&r6$Or "ے-S 4qP? n;J8߿i2t4tQqWq:y/GK$(=BSU2cſkȞn(V} +c<[8y0…v%sS&~HW|:xߨL\MrLD}C0⳦fco[\{L f+왽aVaknHπwSeݟ5޷%.z#xذ_Ne6hMCDFH! 6 ܯdM]™4+%I+ )VFNX ^fO؏lJP^YOe*pWA{rlfl AAHj-I"IOY-qR3+O]=j;VphQ^CR؋ 2x*ia_# nE q[@ Fu"'\FQ9?T`q<_tb1 2zzM_z>cnWG90ddRkKZK~{TΖݔ6k&FMuONϫ/Fn 7NpQ { j#ԞfRd[|}/^*Y^JYiP`Һ|/$@Q4~%$r¡Sax+,ZA D .k iM^ȁ W.80S[XP':ob dřmOX<"&fs{Y`odv}"1H@XE!F5L9%D"EBU4*˫sWz}cmZ}on. ]W~jkt5I֤3SFMUM0N9oL}O[&Owj zfm'>b>bxV[H&ev ]:́̑y(Y 0$Y3ܓu`'KD$*Ū!&jzt Ԫڢъ.{C n}90YvG "0M[QX-(PDE#rb񨺟p &r6+w2J|Hg1U:(75n/vYlmX#P1`.|Cf}7Hjs_2Yt6m7)Au9 [ E :p_ࠀvfܐ˓VӪ(ٱb7; 1'w:W~ΩZ]='^GT}-Kc}Ci7he&zX2\I1,DiH}.`]6YǸS- ^nQ=* ҧT#[nK?R1CFl+Rx"SR\DfgJ=2= o9iy}rIs#KP[gISbKlsb,[$~Vhߐ13UVc{\A愅anŪr kyoZr89/Mvh- ͹\4j(TI'ũڔgoCJ/_!TߑRUZVմhl_i$)TqW9Co}2S 8 @R7^eýĩ*F{:x %` VʨR-jU/9wȻS~U0k~jzLuC~_+ȸ2cv^ypm`ʳ9,zx&Ydp>a-4II _C3GohatAnYx.;iep[,Sv4бz] 0CJw [Bu ;H^F(F5,AI7nm=lŽLBsrc6".{hGDmm˧Ca02 û(AI,M[J="~`UȔc&g#؅$nxn_Yc$rڅ.,Yk6 lSh)65f^͂p' So`C6? P4%WewB:8N+襹<=},ԺT'98y䠺&Uг(n`l_E&<S(=u֚zf-Ǐ7߲/`iduUQtH{~cؾA_g䯢y¸[Fۆ3i[*$+~z3Şt i;!ZGGg|Š% p$k+U0Q]Ÿrm-\fS=8Ƿ-g0T#3S a0︛VJI;Q G>{lslLFG0Tu0k.vi 3۶ta)=db2<&G~R1pH~IxQW-h 2$~`ϾzI2yj}$R MBL$XsF5$KZhU%ýMS⋯\ !Vp=G&`px6#|d"<ɳwFԸ>XJHe#THP>}fdE >C1TOU/|Kv˶S=SLx҈cQ0|#Z,с|٘٘j B_1fO7Oc0> }~Xnٵӝ:FϓbA̯0F XNDG 'mrZ%w~BX,=8}L/uPX9@W}j=#?_'lF̪֬Ѝ 8E񡰀^a~j;1/S[3(BDBy'g}6U8^PrXgY\X{i)@>s+JeM~mtYaCud'zmIv\0Qp!ի =ԫ:?jwN'I#%-&l,$x<*yܸ ?ρtlnh:sA0P|vf[P06adx]4 |Ypp5}va^NI\AXvt[Ǫzu]iRc#ZS#'fίjZ$y-$Ѥf#8ۡZ 0YOgfKyΔ7_!.xDKA3f.PF|eȲzO잹Қ(%o^"m hW>m͔ 4h h^OJn06A{YE;=FOkZ@^ c- bam9 1WkIL (aeh%TL[U)4nOPa@۲_-I0 R{1(bz^,4)sȵ^86}ݙ6d^G@Cͩ?_YՠX$\b%F:!^j\:!a9<![r[A_6琟icx1א %0Յ _Kq40}xohh\c)ҶciΖ~oeLҖY6ېe4Y`t0H0Υa)A8mJ׊6kc`=ҝˌG]\#|{Gdv8qTf]Z6bM 0^s7R6QLjAM -#(̕|*<ǀA0G⿨/"+}(:kEq3~z5C T;Ĺ$Q̳KD+.HDvJ_@Du.EhدGFobоדŻb'1[K no $P* Ӧ>ӬS\(qH+t^sfޗKdڳ*eM Jc~ZxmkF8 T d{8l( w뀐b}?.ɨ Uw.8+[wc$B`Ŕ+g/ɶ( L8̮܁iSO9j˼y6UCװE} |ǴwNBX|&*!B?%`~pJO(@}pKjxhKb_Dqv&Z_iKLgG,OƋ+a h[m8u6`xƤW3m}=>@\cA ykG -Ȍڿكe,TQx ^͍haT$J<$piDCv3E%zJ#bi?P4|tV!W=S?AGf>VQml4OOo$#ǮjMiu͠g3%0ZYc[,7e+J-*ErA<$ſ05i awvYߵ1 $?u3יCr E4C߸dt+iQfBkdKԣ$?BL.S]#Ƣ=1;+H8&2PEshѮYP7dǮħ6tmF ,Kp%Wo%cosW;Zvo%-yz"cMmD)dXtqdCa*jt!qFadn"Rn'%d hg4Ye"<#vH)ǐ,JtG<'j+ 4 /p`="nFaYhC Zb I m1BQ\cFgJRZcևk,#b-1e؉AKWC[4 Oj#TEzo((nGۤ?AUqRk@6Oh)cri7Z;,4< rXG j+vsy5;]/(f TxG!`j͒=DtKCNvQXPeNtOt[TdcQ?q`~\f"_7ʘuA`t*p{&l u6k$O3$rXVM"_bF 9kռՔ@iʤJ7C'BEujg)v0|etՄr܏d1rR%\ Tem/)c@5isA}Kсv@&J 0h"TL"~QJ~::kaS:fAFf,p5_V@MA[6zH 5Gg&#J%p{oڗ?kD\^'"wFupBZY}nQ\WdHo>6"7 D\<Ј!‰y Ö&zk);5|rsmipR*ȡk)}#fv.uI<< 'Z:ˌR 0O&kP&>U5M~n;#Gc%xnL8Qb% f^N>i]+&g:4Wv7vG}722/j5G[2BL1KšQèyx⚎~6w8-]$;׭" D:S'?Ɵ|괁 ;_XUhB~猏r0xZ+d$ NL w+4u7>#2BLyoh`iNۀzZ$ tф.K^~~5WH3s7Hny_E1h]z{ 7zy ȔűK\_ z =hdߏdU|#&(*(t^>凒-{""]fD`wsfz3kOLC#B9Pq@,I(!I> šrݹ_}jOj<&zP G37nX>ڽn*OK~[[O"2Л6 ^=+:C??N/ҋɓ!+n$?+{3E=ڹ~>(5Poeq1X6[XMf&Քt5btEl5>gӌ[lf 'urks,orrϑ̵wjH&E]8eY,Lԑ}SaWƴJ\d kP/]ʖ˔W08Y*B4A6W [Iy ѽɴ^4 -@s N\{bxu׀4\%cu[^6<1[Up~9PjgWI.A5(QlLnYs`)dxfƫ07zfx-wWVFڗwD?μ}C~c =;1*j֪xc8U#Tr,8d0]rA^y qmIW|oލHrYIy@U <&>0p^s=k,h*˧yGxMclئAUی}e~h B31 .9I䴄g.1Y ;(C,@.C9?xz~~VR-H(ˈDK0lq8`ۍgIz6ē/m|D +B?,@?@S5w_~Uk ArJa9> ,|IVmll kgB>H!;? T㵣r Wop0K89Oa| ){/ߵ[t@__yZ2JFc/=/%fJ*B}"V9lܯFk9I0X< B.9&~wUAfb㌀Bg #%jYHZvW V0-ɶC1S?PӃ+#2n4pEAg?؟C< •0(twæ\!iZcfSP K[e߅tmjG<ڔ^`eT .Ij0tr߂ ] V<&uC1MrFw0uH$*qԽ;{jYIaWDJQk;݁Ҷ P-( 'P1آ;q+˄Rw1b!UOvJvv$,Tq_w(!j dPB%N3I>94E0Q| Y@K@]~\YͥLZ;'PKr?em]Z? Wg3P%& 'S5*ʋsT:Et;\/nZ`B-[04-fsvRܒS?OڎE!T2n#:TAS/8=?W4gy6MP2%B d('b宴> 1" 8G`Os\R_lX2Ԋ?)6b8n(?O[JaG]j05ǡW+tH600WbU83N#22/ bD6dƿTɼ;FcEFT{)=TlpaMejYE0HAȤg3d֊Zu%[C8|p^J+/FV_g'"=n Q8ZZRBpr}5_5$M:ɘK(<|fIOC Zr($l fPykU*c6^۝d~ȒV2e=k烡6: Fo& (!JJX*<>&ͩƟU$q,t2᤾ɨLN SG2h'LZlWEDHHjuS~~ytAnsbEPqf==E:_3~>&2 κ/O:Oi!+y{O.'[o А; ϥ246>Pryۚhwt"'fk\e,,B@ .%cD6p>m^{u0pf)w;˛3.f2.b^6OSST9wٝ N6{7F' 7,'pB3x\*[j fL9T+.l䷠Q"\]ƫ d5WxS n.rb d吚ic{zl8{Pa rNX\5u=ky@}\jstb,9N7OI-JBs8OڱSw8OUZ;Mk! QĶگBWp7ۢ$D}%^qf~Jh$G]/>u]%B&5.z+Sn{4"UIEOqKa׷QNk'Kanp5wrw~* @_Q e.$sݚr{#D^g\%#H)צh jM ,}R!0`B#؄0GV& \@L*qXF)斎IȎ X; Xᕎ ޡ$ܛx]$}CodJEƤZ rr,)h_XћScfq6~S țtAbk@OvK"/8 z/ ZߵpX'9įyzc)6!CQ9ԧ8$LÎD% }OϬ ASą:\M%y lI_p,H}pǚ 0bד%eY&Y=D06bSG7vQk Wͥ!۽7|Gmv?*iE!teR2A76ʌiXe$SDžv)S6N@= YT;KEMga4u:*`.ۖs QҮ_'u4״Xh_=zEKghA|w|އrc׮9\X@%wQk-4A*Xk* lU#]yts0hF (\lz/L`61(ZzJ Q=i( dåTԒE8%#%R0IUpA%J=4 Kݨ  G/Љ*ApAqȮT5>1; u'jLhP#a? D $FF4$?A4ֆ` l}k''aa?hϺҜٺڛ c/ s;e0gvH̿qƈ|D-Sr5[ycPk>bb71Q֪4چ]9[1I0xL㳡wTDh1nl)1u/!r&n{ h+uLt?=c4EU4%$ = IgWhL<6]'&+VvpU^ƌ~\5= ^wTHFl ==t,M2gg $?Cv`?xhBi;p2]Z#1}zQ&݂{:.u0$J ׊**9sto5KL ή)/#;J"uVcu$iIl#8:O/oEpƛpjbQ%QoI@ -2~  zwD]|"`h& X{ .^7i_SI:@lĎJ[[pᛠc#…E'A\ 2Fw71-x*#]c^Yhzu\ kMp "g O4w:% s|鏹O BjU0] q4w603zi<.TS4ѤoR[_e}+4ҊӍÖ'zڛA(`qm{:s 0!w ƫ-}g1\`(d0~ "dY  #A(jN:qQnd_7!oK~iX!m}w]yʣC>"`B:X $eWo%s%C&U!} iy%9g̋_D0 j 3:sҎ| ISEcd(c,6Twsa{EAHuɜqRgAѣ%ӆ[mK[#RL1O*]6!$7Z6P`Z8%/j?tyju%Z|vS.V_)RKD|/z+/"4DVSx2[6dp*_+A8{5b[ 0osR sa5(i3sG3]GrFu2|WjWcpl%OԀ]:aHLq ; 6ӊ[̿rIĈA7D_&]8991 tE$D 3M2gӡkqPSvX̦u9>H%8<@T54tp`׀ejcśG G}}ٴ^ֆ@)ߙ6Adk?ϠϷ)73>o\9`0(>qφIցh $(˟)6 ^Ϗ*RUIY}W¢;_ޢJ%]D.\VDFNyMeJ!^Ȭ9$Oki9%>&*eH^q7[aɻP7[G#\OƅwXp}0:&+#vΆW݁ ,P^q'TsݰaR>e _ >ô ?HŌB@#]ʢ.\bRٚ7bB-IeI!_G;Z1ɥCD{rVwk-Z!DH+EH\ѾM|Sj=x\-O=IvX8Jqx 6Wxmv k1"yN_)cDH\J:W|eHjc?f{.F@՟'&i -|Wr5!=]IPNgS jS )Hm7@t'3+"S n\ӵai R9BߥnZKJ;4_dc0Q*-:2@-B̢,ŭBcjLjiֽ&`}Q,wiNO.Z*LtcS+^PY$$=Z'h^ r]z Eyϥ>Rdj737.UznXQH< ='`fBgZk 4&z̞eжAE_A\"m=wq}o@,`Gp3uwbLpm3W =; FVBbh$  S(3&- +&=6հe')}?,5da/Lu0^ ER+oRƸ=ymZtƝƽjX5LzXXyF#q"rȾ<aJn U? ~>G_!rZ!&' %Vn qf~p )N2:滶RzbPЅK@?riam6N N;]wzoO< }7ӝTlW;XDH,3&2jR#7C4D3ItYlxaPɴ  \bvW*Av#mQRn}O&)&檩Q}=3pB/O4LJ%6x?xʮ){f9>_?"7h/[oz:kG~>C/v>Am Zeվa҅-:_=SK{0Q?[:Dtg2oNl#[&r_ySɶ."( |'~[Ӛwҟ9ЯL!N[lڂ:`Hj6#W U`C|@RHYw @j־r.~&,D^ w2}pxT IbAvi(; :<~a;7$7q X_d^ ~0b/3AV1vmQĸn{EBY.W] x^@5')qz-%%w%|,Wztb.KX#~x>s>-n:6 l=ꩀt^X 4꺄dA5i܂af h SF;[` G2 RՐh,]`KS]MZ zY q y%A5RHlȶnPI ,wyIk;Ȋfh{&w?}H``S<&!ruV&"Ebԛx'}'2|%' mx{=Pota?9YE_5pfoq.N}&ZKCgat,,|.pa;p<#rͥ!P\`rJrP h:^[D*2*iWavJ .Vǣߝ]*=+ӌIeB%;s8C/r0hQKcm~;}5  [0G*+CrwցS"q߾ }dipWsT#FM{p 21 2M͇[awkq*x[&DHBcS$<"p0شh&μXja;R.M3\.D9LJ]+S5@o#oxqyWt,`NSN<́[}?M*c'||2B(2=0Tȥlf|yù_L \8M,Vg# l@U2/<5A~Awɇ>y-#Z|"A?lv(a6Bh6E3jղ5b͔Th w\_{/ ƍ{+[~{ ʔozH^!M+j*]gtSG0Spz:>ƞ%ʇ3AH;‹ӓ;z#]רygQ04W&;]p'*Ѻ:PklUsfBe%UQB1tQ Q=܌}X kǒU@zKja0%Wd@;3kE"H"̗ n6{q>x%)4lN5QK2 -N}ղLx?~qV{KZtVjx*5ZQD/1L$J9Sv@xJR̀`|{`iAc_~ՊybU'k'd2?VŬu w⢅)ERD|C\t[5-OwP\Gi3ew;'7;@>ֺGTwZZIAkvqDXg̵ N7֐]tvjjZ_8;VG,gkcVV}{ %}9]ަg72wYO^C4r4 u/a>_#8+1T;1qaMCA~O(83o^↏Sa3|:ݖɪωLK?u~ `2LBE3m_l=0K (z+2z^_^_`ZDymhEpMRWy5?IGĴ;` ,{t Ka1/GuNkO8}Po25!􈜵w"$CpL˝+c=iub#PByQfT, !jc*O 12J]ҐGmP`W^i{ grZ즻?BE&r8yÚg$yPe [nM::}'[V^j\$ֹ67rwucb>-En^]y:rtŏjhEOP@^,!?nvʋvbf@j{٭Pzg3kbg:%C6 5fQ2HL-RE2zuyYc(/if4[=`Į1Jnz_eano<*߰+(+| h) !韇 }1N~b&1j3̊رYʷn3yo%|qraɜ]>]JOø-bSw/+6f!E yB& 5v6&W VcFZ9-Gҳޢ)] (h[kyf!м$P#n~͎K}&d&-dbFݼɹ@ E޶`Pmڙ]Z~[ɗzHm-8f_eWu~1xg2vxf^Hao\ 'Obbv4/4ZTud3EHf"&3N+ДZ Pa\Rxcz DQ)э/4wX3,O| ;nx~9A#];簝vGyvSΈ(vZõi5`n o.@X{ E3M#I4C@m-f, ed#}2}ߦrf.pʅ1%ANLY' Aztd((; F A (_tMOEƢ%6،iai>{wN;Lrp ,K}ek'=c`oU>qt4_y!qt%VqN4]`CYO=ʾQ4 'p"r-t\ DwPfZXt> u:'0DB޺"辵e>Tc1 { eWy5SkTz_J8Zfz nV@3^L.@ym6++빕'!6XqmXk ێR^"At*!TJfjYW|n\n+;z+ /e7M 4~c@ZbJvkgTA5kOǃkk, r2`ul^h ]tw3ݏ (QfVT2{PfDl D(H٩:8s6`y3Fhs}.5m4P<="6f̬mK=8AɤMW0MU6GL@1Kz)Ƶ?sFԫ"3z\٣ +4s5'~pS8׭K C%RZޜ1gω#V=P8OuA2Z( shR?E"#yậO7?3I=@/8VaG"/S廈6tlA>j0l2pQ +e ҍJVrZWOϹmr (LӺY\7]xXYYȪ."8_qxgR9#ߦB*Εvn%riHqPG=lS6EhMrN͚nRmT'>4^pwĿ}g7^YDžn =A(f3m.z]_((`:޼%Mc".1P|ٗJ3svަ";!"#JDiPk:uKR2}&3/Fjq  #vwkplQXĻ@;ōr-b 'F qc/ ;JC:*;3[^- x߬;9-bCuE5aǴ"|+~C }Y rԓŪkbT+zl^ejCy̗|P3`qv oF[y^A%PtH˵ SHKAÁIZeD+<~9ձY7hQ)VKYOKvߐMi܄ῧLgP5(#a*-=ƽl.,֜M?x0k_Dn QW":N=izgj˸8D.C-Arr+ǻ)hzfO/gpWwI١' Eڢ7DIvli /un}_ͅ&=徿؈j2H2Y?2DG4>߿-[!kW):F7o1ek7{sfal)?tsMɝWШ Ex'_{o &(q]Dk&GVy>^UFià99yQ&f )F9z1]c@ x ;î{X8f˚s*hJf6 1V Ro|"EE$[x緆;xH !7DM6-PKx0!*ƃç]S%% }/?BÓ܋3i];k"ְɄ'up)^QdqHuM \n}i]bݻv볬fZ8Y+R`)}qX ~OT9vt)Z7ZAM~B$㈴+ }dPx>iUZBVGKAPWZAC?7hG!O {}BRaB H× i^ Q[erEm/ nr֝@{i!YoctaUv f3hHON0K+}`q c6_l 5p14R8 @1OFx ,#{0$ zFqfT+b*Qg?+,ָ a؇3e3uIOUFHPi0MfC=vQ=mT,z$1.drN 0M%IճMZ7Q1}m  ڼ7imh_(4Nwܚ.irl"@rl5^ q_ObU;XMZᰪ5ꝻK1uuK/H_/akի1߀ҋ-dz7tm;4z%YS_B!S@Hߒzƃ<҆{aSS}W\|M hK~%=9PV0J_D-xYrhs/{Jt /NDWJ=8NQK)9"QrF]@ϟ]w.yQcgWm[zO5?nHctg183EM2`r:[$*p?foOU aGى`$H8:Uj ={z 3:k BV Xc{X ~pvn~Mµ:*Yк6??bNo [ bƅtc=g:\uAK0,nQmvAX'M)/`d's8b(MVxau_#rʓ7@P$E2.qv K݇ԷP&ف9-s{f6Z w01P(5҂Jw[(}Ys#e/O7Xgͫ6&@MUB-4?ۍv"U*)XW-;&8:D6 pS#lS!~ǨPo4l M$1Rq n$,[J:xbREǂV .)[ =3l98=}/^!oUˈ†]ȳ;X_ Ez젔h0̱I̟Xƾ7)ٟEvd@S>Q}ۀ:"6XWv>TuN-L dKX51Jmp͑=e?&\GKnKi2eVT+5ӝo,c/ VRҪٴsS,$ALESpl#7eCq~!Xk_'U/٦^]\ڝU=j!}<u -CcnNس*1by `(=wM5ħ$^ ܇54!+@7 E~T !|ɎVf†й)UI[p021~i&$IyHc+3Nȶ9衵+y~_)P tSb>7785qhW/QY*3AI Rf܀-=hmXc 9SCUͳ\weFPeROR(CƃxUiߚ}; ?++1[kzޚU jҁlZ[FtS%Nա-{gM _u<MZ ڣGJNNADkI)7ˆ/sSsŊ 0łaJ(7%rm>p7,1#{P;vPdUEjl7f.qfuT/O2i;H;b ܻ ,rOu%cQ,d2ȣ=_ .%ُBNqz4ыUXwhk=`3V{*'H 李<6!V9G]V%J})ft|TS\p?ŏautDȲhx5 =1IG 0eUwI\g6}u,q wX0!uWsznjO4UfI-?$)Rۊwܭ+J7tt[t.옧E봢!&\EQ'"ʹλAm0D]NByLM? fXa1_í%Io,D<CP^iwa4:u%$lR8ݫ?xYh+7]&&jĂAyJ 6=7X9+6ZzLoH\ vDtan8ӌ%oiYv[TS3h$b23)TFD"C=H2.dЍz34y|ߟX5#{'=Ȉɮndᯇ6s>BA.f+>+o>BbA/?m法r s,eHo4 *$eXa:sIy%f圃XI爚VɐZ])헗6d|>!-ʔێ{Hڞ۬N\6x_cxK3.REiS@Mq&rjzz/[D[3HeЉ ) %-]t+BeFp$!gMr3ñ>C|j0R^Iu g*P|d1%Ddٍ#(z9i9w2JU(6<a}\^uŃM=u`IBa*ۯ2q=ǨC3@=l69 q0[ж-vGi :zu=GĞ2[`wLȾgdXDGiuEN*sI`n&@qdlXPDJԎL2ޙ1eVzdjJ?6`NKUQ5o<4'298tku-Ks0 sÃW@nБ>u7ȸ炥Į@I(e- 7҄WIzLI6Jae4$js~滋5Gf:4d =J3 1{h}@>  no d9I,^pmi=[n@z'- tddO+q}(G%+? 2l;͢S3.|~ ̇c%-xGIYv%H Ѷ` b#M,ɯP=ʿ\ zpY%b+i ts[/4cǜ I# kJ ҾVί6?`U?KV^'m_0_oŒ.K k3ˌ$ %Iؑ0c{Ī>wY_4,[G< (#/GfEJ}iq6s_4(>u2:,e bZ ="Davbʱe8oiX%~W# 0`ouRU. OBg`- |qIRUYFV۲,-8O@e.T-O㥿/X_O1SduNFwl K }a٩JjѻcÜ0k8ܽ1"ڋ37jFɓHr20U)SYxgDzK0qȭngVã_p^>xaS̈4Xftx̠s8Mhժ~l%}P.1VMevQ`Z=X1a >+=!QCl_K7 ?swP\R9,RLݼT:^ (_`:bDK޹3CZE^ˋ-Ӆ cďeҴ 4-FTT/ǎGlɟ@R *rj$ֵxG5(CYAZ74!|̪k杧Q0l4ɣEj0+'RL {/R\%J/ԱHk R*/a=bjw4G R!Q/Oös tUWZ!bfǬ7B]o#H7}Pe\>5 ~ވ{w"x^ x"7_HEIL4''dǛ('xCEah \m)wծբ~Biu%Cyƺv>'`0~Ed "H;Pɂv-~G܉yq@stJJOIb0gJ6b֎509 mj/6NwB"S4aupෛe˄pu#gylTT@'dՆ-5e#`)Mp'< z׏w5X7:~LM=bkPym>o0EٯM`Ӱ^J_DCGcK=1&ŻQaofx'Tv[PԐ006P& ׭ )ܯUUwӚ& 膅L,Dl(T%gaK+5 6aV9[\n J٩qY |O^&c!RHq =Ut&I1PZlǨ"S d-|&e@z.[E՛^|~ۯrܗ %yv( Zm4g $~i.]1+뻺O8TyAȮ.$`~ud'J0ߑBkX<yUKd2mnfP<;^O͚qXmk =NC֧>)ȥKDy2 ƆcmJݕ rTBx];_7kSTtKDkG^,~i*_h}3߄sW ֨60J/!(LPnpbq>IGU5I6Ad#PeXW,ް3}`_qhIE,JO,1@@tl֓ }\ROƂuґ#wsZ%pkE9z\g9sbiaN[Σ)j)ŁB_xnZіE/+G,Ϙ9MtPLsz. jL{ 'I jm7Yu}YT{q1ȱ[L `v.0J೛h+^TϗVcsS352c% NpC bXY(`FЛkԵF7y'zDvPMoa'уp BQ~ZK6^1E4a0 :*6fqe6Z\[ ]U sHUN8a8 I4i[[ 7`@滠+q y `nԗtF."9s 0• z6`q-E!ﭳz>{*))YXt 7Jx.RsrfHC#S6^,b6dĈ7(% k,$/>c5cУqA29_ٗSdvN37;V/d3쌇-TLӜ9%_ktkM^lc;ȋ[]HIT0෋|;`?g/kY(ňmg$,ZJG66Za*ӾIZ){  ٕ?+CG?AQu͡>M•ʙ9Ki?g,)CaSʏLb$B0cf]ۅ9H֣Y%$xH*0EsWfϵ`iD^1-m`@օˇa6hkZHgNW#I[J`95X(eZ~8ey)Zy4\o!3WP&ۻ BJ\l>#n.)Or H܁)P2 " $i0}(./J"e[«eG9XKr+ wQ-HՕ՜.u Y{ԋLW16M(p2r/Fgr͚9oUF~j??"}no48<>S5m>T}P P>) 닊qnA=t oAl;t:+ڌWBd/Y1rV(J͗VN!Q(y1P$|G|/;yWB<9CRAvH -c6mN" ̰y'|H)?@ɴ5=3iM nڍ&̫3=1 L/!$1&K303vlEWy]\q2KPvJPr6 UJ߁L,^m9lU]jtP$-˽.#nڿcTz2baL󪘦K.CѾ1\bƇqq͝#Eoy7tTOkSg ro`*>܉Rc +L kq+<9 >o+؂YeڡDaoq(7O^ cc+u0؇OB2 LDc#1 kv$3^.[K|Xt]pLI(ԢfO\{ʠƊ=y:i DWXk-h@Ǝp'4բΜ4ŒTƁE8.t612WwTA$Vgn z* 1̌wA> JM3~TK*70DV h?a&.tjJkH7cFBSe(d4?{-lTNl3CHp$ +5.@Ru^?F 6QmlG]A ׋y&\ xp濫#Bquf ףi+&UU?mZf4\VW?&? ^K=+ajSbE_mw*1a=jC5~+ B97- ;y*qD5XPoe)'P]6zJ>~se:%'/PԁB!g߃ʀ⸞kۄdy,Kħ,lK Y 2 O\tўzur|EPMZ^is$[W.1i֘`PWpTy+m8xe#k].*',B8-*oH)uWy;;VsS'ל2oHǸO⻶%;Asނ˨~t")tE ]| t:wyMQ#ȕR4veps(+zWZIuqAo~.0xơu쮍U_N#7LWM-H|HUW.ħcn,:31*lʤ+0VOy;ӄC[2VF wb[R띄"w8EYcx ԭAÇG' 5k%qvkG ;;oxӟOFVoU< ?a]@Q,!| n*O5׾hr Lp|PA"}hzg7%S 1TD:\/S^*Q+F2XT7i4|u/{VcHz s l C)~B=W*xŃSYkcu^im?ig''8\s {{%a~3/dl^E -ºhm1kN٧q"tI80oLj>mQ1KG0`_GEM̖_K)!^*W:}i/nkx>b;LӁ/u $CΙ!OZ!^="TL'+1Ń)=q7(g#KpG:Ǩ_<''U7S+$o^!܂25>7@n2i,".~γ1:Y4bkmASiMճ0]WkNҖ`E ?Hڏ=&`ӈ!VL@|#X bs إ5dQ`tDԤ .kD P[j|u & =誙8‘0mWoTLьy0l?nN7%M]1nF2 Pb}.H NeTf7** olss{ LG;PQ k h&\ʺɂeNx 8ȡꙁ|aQof.V\يIȅxuL aDOW牾sXE'x|v>E1!cm=j+e k=3)b E/aV%"6xh)g 9/ ~!"d'h \M5 FVI‘qCdMBl/ pb)u3yQ.Z{][N:`kpppC۰EpO+Ab!b7\ܳL&772fQć0FNN[62j6 %*^rrB%P_FvBW )u~XTL=P d?^4(]'JVҤ2C;8`BI9 x>SZBxY&׺/x_}H9sW;Pt4Ha.ϒh( &YW`W,&%n7m?s a!"أV>7Z$zOe, #¯,,.D^dF>iWbulhKCO 쐚H^o=P#de^GHSōe !=vM_c?^Ouvǃk6wI +(o!AEA5_1?,ӥ{9?*d]'/ֵiOP _APOYuX-' :`v5|i]ӻ }[=Ɍ+Ŗ9JXj4[t\QD{kũܵ OvծpYڀY}H(j/ $+tCGa!,mf\(^07(:W+z%1y2"0ujXNI*lL(='mA -ȐXOC;ȕKjmE !-e)>< g}3޵*_`|îkSUl?*ܶ^ jBQ.{IP.ϜK c:$e~-+=%#'}]@GEU>YsGveU ~V,EPATZE`t{  Omu"jF !PWiv;zIpV$N\}Ε4F"z0ԧ̟,u̳a j1{ 27^lƶdƷo \d)[ؒ֩ͷS(L|O_FXG>~RBn<@V<( F vG`QoͰѿJ1K-{-7@T|Qѽ!d+\k Jv~E˖)H}!ZVAXFUB_)#-ZWYWH6nHHSJ N@Y1`ƈXlV7l?9> .Ι<L4-rdB4LW3rQc]ȘTU?Ll6솔C҅W (OBZ!ʚ\ ݁pMwݩP§7EЌv% wCIGNn|,ݝw4?/q1ڈM҈<܈j4uuC_U%vlwu~ oREY2Ժ0(/7f_ӨiZ$i] Wnh"Iܛvn}3~ns욕3)p|JtOs}>֥7ATE_ҰͫEڷtQ;H8:fn'|pLUD T Qv؈>LwgRoU[c+!bVjŭ=wz7gŸ2l*#%^*3l%R}Yx>v*v `O6xg~7񑝙|ﲰwzH J1MHqoj[N}v.s=?_IyVC!q`g# Gw 6h>~,;~v>L7T [7{&X#|FAx5ۏEXld"0anvzvݨmd!TP@'bZt[@`CTx2#bR_WJ49| ghY<DZ5dѳq*BfPg)Pbf$\cYdž6ksv{:Z⾚9%ֿTV3-ZFh<5dEYVW?̆S7]Ctlz*?.6Q֏8X6L w`=䪈 ys G6޻PiZ XkFsL/]9'nu`x-\ڧ`7ƖM>8,7e~Q$(L3\nTPC3V#,O2A- Ɏ3UX.Ae{d ;Ydg|3i>;Pt7vKwxE YBAȬ$?.A1u1i^Xڙ;|^0ގ3uPa&BF-*:ߎ4_5K'y%_*&GfNn8陫83)"Aey'G*+8 ]Bwnkٹ#)M΃?Q H:╭ o,BMO\q5(6֚XsWtE1^j/RuWേ[.~ ~qm/- snRfK2 ãMt)l5~R0(>|fJ7dkZ:d{|IR`]1<I-m<Ep?wMz7 &_EDX7S98twq =R׎w,-F odj%A"dYvkt ~ۨD'(Pl-S9x[s8.=MwfXsRPVRb:E+#KvlM=oP̲h/L,Wy\/X]Wq% ǭՙiPY=5pH]NWp*A4 H_ɑuIRg-JpVէp3kEڶwx T=%\CG3|m20"Q~/P1S'_WߺTv0;}S[$hw٤x.%Y FرG5+;Xߩfӻ9Fn_IƂ|ݔin'@rS0 7v(kN1?Vk`Oյ悚 *VnN \nwNcтgJIQ,\Vԃ@+.%ǹ|~ꃾX] /̸t..3{|UE7:AS \iecP /,W8c7lmK,h1ejP3pEfMlSP%4aU:g8Ki˕ЏBճe7mnuqa^uϽ ԼE*ap4յ|N_sWQsY>rq;鍂E|i*r; N1Ĩn #ew9;~pI@5۔pe~j&߸(|1uǾxtwbcC"s۝PU@2gϛ㤮P=) Nd.0Md貚Zi+8%_*G`A5i{/2~T ip+&0 9w" ϕqn;ӱ:ZcLwwt-'0'd']lRf:$۶4,Ml©}9l#WMn5 hs5iH- ԠQd6&8$qGF6){Hvo2gTWcԴjѧNPS7!T];oƈn]ԣP,;K2z=O-TL,{4-MDc,n|0@3LPB@schȊ|g}&\uW=lKJtLOK~չhu &w޸eBq͙T>6ޡSZdj[!g5WI[hQA"ouQ">'K>s r<AL-/{ɏ ', "-h"{[l^7-{^0s&b"#0rM*_[8ڳڏƽ1gؤ^s*|K׮ƽ8yOpA7Z:04)_bWXvyy!\FN墹 负=~B=t%_qodyZTBZ]|{ "Af=VβVŇsz',Oqz~ӊ5rѨ F&bݠF#L.06C_KЖﳣH^5QF_w!~{Kl!GAE]sme"EsJ]ğ<)DC}fOd/<ɓM5 DlAe'@Lq\o /U8ת!_V0dNN V ۋP,.*zɷeH&:E-abhXK99.7WDU0 AV5Ry~覵U>b9#5S`mr[{>!lQy$|yHӹӷ#h,'Z#'20@k2m};|-2_PrYғ55 9 T=kzyL7YP*y;]R>~Ga#3"U[3ӟg{pz֞ pNYI"Hj}E,.]HU/26˜!t2z3=o,71Ņ u>wp eNZx Mf{QK߯I6u8&oy@ d}7/RˮTmZO8(TUh ȖR;A$#s]soqrCe]23)P_8))C_ $g$@x;~ *ëW !#*>adąʮ0&؝ T/7l8ކd~Ae=t 4)_]͟8 -Vʕ'=w`{m2gBZ^G<f002Ü)TwZHQ34ӛ^]MCs~6A#8ms1b9&xQB+Sǧ0$\>ZJ]-'Υ .Pu &iOE KS%y 9݉6޾}x@#)JESx-S0ѣW k0EO$ϴ)0z (D&Ѳ>[u;7}:IUE(9$N:"QlFrP9@+ 86ybo5y.j!//MSN %Q8^3W(pc\8~뽭 LJw'YLk1!&y}ncdX=,>HnABb 7;!`N!Wlᆇ>F2Bt fֹWhW53 nv* 9g N zƙܘdtj,=H绍,I/F|'< 鰤z dzn%v\856dHvee&-%`T~Y$dRs~]\AP&ʺCXn'PKڗ'nȴ$ Uj*Ǹ4u8O _1;e2Eak(C[ , M/6&Wor*duE&3mWiƫ"m[ w}vB8ںfBa>ߚ.sBƾ/K÷!p;c)Z伶pۭ SnbЪL)jb I[BδXGs"먃[ Fȴlo{*قm 5h(X좞߾{0[(2̈Kٵx%sީ.IV쭇OKZmdoۆI;Id {}?r51_:N}7ÃVW&\/u{oc"XP) =˽ܐ "l8~9A_"neX#S˞+uyY QqswǤh5CpG8us)^jd}<c~Bnb!rEybØ'A NegUDMRBEN.]Y Bݟbîy8ik287L/1Av;ß2ҢU9#ll@B$,x-ƫ/( ֵu)i/:p4y/}@DmtXcn[[T3uI"'Znx]% ~"!s#V[?dj/=3{qwj!zcPvUW²R`gHA\pB1~ZeS'~`3V݋ɲ5jJE[c>= ^KlZ67t7jͧ{ m8l|OxȻrQǴ~)?ބ HnBgPY AnkmUs"’Y!\m@rhJ1m x. w,1 <4ۺ+ 1 ՟hQE,CVKZfqm R}{z/~tXi1H뉜,/p)7Zp[f K-Vr,90X)6-r0\&Sk7@@οc fMvm0U^\Ag\-R /g_PFdCUdzJko$p%2ݨA "=:Cątq9ޭ7N],ff&hP]5-9gb O,jvtD2V q- 37}W{N $2{:D=Wxm?*ܶ1a%X]Wk𓾽_^(UeRѹOn婰P2F_ZlD84<-8;}a$cR(i S 3qlw6׭?{:M7\KR} XlK}ǎ^f7 :;jxbm)̮FqyA 5rLO8?tBVߖXy4m]iaՓ͓Ql]% RΥܵƭOdDOnin~T'eeh2૦`SN%pf|mMeXSC  SgۄU+_qӖSœ1ud&V9b.![y=dB)49PjǭљBҌV2ݮ$ڼ\< @b 1 YH?vpk4"Kb}l $d zx#/֣M /Uت5FC2 yY1&$-gdfPu+@猜QPSNi - jp#ӿ))+QyឆOV:ϭuhr `[<9j7 TO֤7W D*ZǍz[',k \F!?n]=boBâyu;KHR#"!OO@ZQnAh`{;T~~r&lVLGyj;o1n$KHFP6WZ8_xX?mx8J#0]%g I w\HBw0SNch8h2jȞ.sK(m8ۆ2Ljp(sKu\UϵLai3[tYxTʄ8Җk:OxrI V"LNV[=FAYY!]F":YnhSv ?fp{ Stb'^~@`m41] fUJn(.  핊!{"rCGeEXF |ѯP&Gxߗta+k+&GN_Oqfvסz J%+\h8*Ӄzԯ0s[ep)\Or`GrgMo?k O(Xx[%M@߷4.=v3+mH0C2o+@ș%*ߧE#>m(Z2L͒zo^7 X\M}XjX6KF]xIBcLK ڦܨ;'pbeO;Llc,V:^D.U!j-Mτv@&? p{yYywyf6T}KU2yr \Lb _ + rM*/6@40\*S3n}R ̇=%/~WjNƇb)c 9 |M(CNiYVp 5:d"q O:{Z fke3b`yЮ ߡD܀pGwc*<+xSl5|X&8gd:zO~Y`{,߼E(6حiӨ6:(6an\F v1}CZ g' %Ehp뭞_C2]A~G I]+s_h?~8ܰ&B>5弹iBqlbH7}XƯ!RZh7U-&ss e1/$Xq2ŝ'/&sԋm//²h+2ƯUVǽ6ƹ6f)bi[(:>! xFu}Gl aNDQY8Z yB];*iMe`{U8  *Py`&/T&fRՃ+_s0j\Bɚ[2Dc]-zr +?yO)~V[`,O%|e%@z`Oo 5Er]+igZj-VS$5 'DU*\.<7о6"ieu<% Aw|V0C%WD B-t< BisὦUz~r^D`U(}k(͆ DaBgE꫟zdJ.?~ѭ[32NR ` ܶ-G9'g&iG0B#gL}_@_9Zxrza;s ^O+=$m'rP$h7t@n> @<]_1}-~,q4Y+sZ]Dc\` yx%7eϚ Q.o.tAe{ܾTE"D(j=J##~mL(н|pi&Rg Ħrې/ZM\e.:w}_} YpZ5HiU sy0k{k`&+Z؊?Wůf8Q.}0W o< 6XB`EĊZ>1OEC <nJkQZOluPfʹQglΥ*:Xey g [8M-.čk))' 2(c_CʤŜi.o;dk/:gWE v"0 `yHs|$%G%_\n),'qQǑcBDӸE O߆wc+l]p惵wנO!κbBE _1ST3|yJ-.\b&PI_@Esd`.?_"z~ƥ( ,%}3B={K^imvf‹T61 rs5΢nQ+UE֖'&_yDMoe.XGUp`k;x\qP2;}X5>Y5n4H*PVrXև.^,}2wrb@,n̸s֢;њg8|8Vv:ÿ(?5´8+&`\DNʃEf;HeA)4mFx'MA[=C+O?O¼˵)Gf-tՓj0w@t 3>O58"(~ݻROFg T6o҄JaH8R匇A.vPcT/ \Gm$Xmqfؓd4c5>8;i?L,:.+)*X7/^UϹNs|L 4L7 t} e)NĚ-O}SåF0Kۇ2G3JPp9m0 es>B`>"x-[I( ; -YloP?F o{$@ *$yO񎾈LBzonķObkD5x_>.r> $zKfn3.^2#6x( aj?} ɢӁ31/`I  M/EoʅCRIG39+ru`k'{Y憺5ۜ진wg̃gX ]у0VD8g3عXfs^Gi (#KQ[5(" K:AbvɟZ [5u ?[YOݶy*FwKEeJ9D)L \\2G cXl154ڡBPD- fQ}'^_Zdmid=(@-$vEM7ͺtdaVtߟ=ɮn,dO g2_yb%!RS Һ蠰CC *m3\d;"B$LB:}qF4OfL56'%F 휏Q+ ,lѽPϱ 5I;,1Erɶm*GH^_*qD D`^eWb"qSzԊclE gЉ@J6D3f{QLڞ;ܦུt"UPdM)/ߒԄJ_g ,9|B\ʄli(i.?#{'xrbD#MjOpMj5>|E݃Ad-8"OtlqNN8X3!.ϓ_/t-PM[*H,͢\ 곂s+8F7wHxʗSBxOt7aVRb諟/5`TPVIfsdVK@i݉=?߾:/䥩'\4{`]4[C 62`(в)/ y@=C  |xӌG%g3V9aSҒ}y7J)5UW!s'X~ws%XdwA+XVkP"֌ϋ 86k(T:l DTd/U B܏.~3@=_O 2yl13m je {_ںfUu)t6HEcTm,.8.UZxBXeo15!k'7kQ*,t,o-F Z~m,)7 9P5/l9kv3+'Q kwCxq&"#Ky.ܢh0#R89N$h"W |(QmG;76$#TfgW \{%uAά舏Br&=*H }p"t|cC_w-dkHWUd_Æڐ//lNtU-&/l#Q0"OgS<UH o*8rTc\':[=L~O͏c`e759b0k~Pfl3|BF +WB<(M !_ah:xg] $EA3\,Fzf21VmF0tXu/zU?=Sg}Wu(ڑ|O1V/(kKU%{$O`^:qRбţ\Dvs1Ȕ:j`LV@|eвw'ZCBH&N+aS[$6wU`y֣]eQBPȥ2jLlvu$زkaԥ/հ[-lLB1!}B@yl)%_zT #6p4ݺ_49gM0箪#bp)6|bk0‚vF_7S펭stY~-?x8{&ovT;eHwʸGێ]>6,b`Qz¡ ʸe]v6˸f9Mdj$_ B2/q1'gw*EYE}]}fo2/,_n`,`/?9̬]-ǁuh:qbL_4h8:hS` >:6]c}8"-w?O9'?8BO5IjahA@ )!^ "6))l EZ{/Ck.}8#ۀ:ٽBAVcAN\LV7lr(s7x7㣗qZ ddv6H&èe56JؘQ9|2r}NܐI]dJ7 6aO={&3GnGIj`C"P?d#њ F/" ^*FI0Y7eN:3MyepԱ{솃NσsC#^W5J'?7`+,Gܣ.b7d:وhHa Qf| whQ߁RrSKə0@*]8'F,&(ѵ6 Jd{6Vju]U=>dd m-SKw<=!ۗ*⪁=^ (tǟ |[ (BqD4dNRbA>iΐ{7ެh2𖑂E%U Pɋ8F?Ec]1pfH!-,:U!\!cOb[ 1ڨ Z^S`!b,3hů2Rĸ }uӝ;m:E vL2Ͽcv_C5ڎ{R/HC&p LSuOJ$,Ќ63'&\;-Ş^<iYÄ0Oe_~ <`YnE{{UaLjIs_P盡*_t}Ar<].)xW㻮9 _lF%$"Eo?,e#daZ ""Ҧd^tO\sP<_)/mnUY&ڨ~ D /8~_4mb>lE Ζ`(ycvxz1gMT&#ǽWbfL*H< %ɜ5Bƛ{Q>; nӕK>W@^~줱\ϧ=\(J&uR5}@ sf2׏ {!djmν1uK4gl ՎiAZQ!7ūu{Yܓp>3PXKn)Bg94ET?J5i&+ (0Y#6Բ&zA3kIϫ V`QOO]B{~Ew%,o֖uAڟ) ]aՠgU*}}%Ú\W}&7+ 3yy ύnt7>J,5ChrNs(љӥEH~#˖[AŬc z?w \2eoLpMZBwG]f$ pٷ F ʒHT3n]vY.2r1X?#G\r,aP g(Mw8@WG5>h)l=~nf{I*01It>Qf<JP 2&TWG{DU 3le/Rjd~@# [x8K3**66>/lF_Ja9t(<} kHLZ u xo T\O!,]1(CZ;6"B=#~yfēy}(/sz7?;E0Ys۳vNYࣞo{AN)T=HgmD$jsE0^Y?=zb4a^F@h%Ps]blmJH B3yY5ܩSyüHdKm7O,rKS"uY$9cOewfwȟ'+'+ƢK,ݕ: ]y^\)6ev8-f1 .{RDf H8[ꃒ7{{|."m]SA[{PAnaﱳSjJEKRi.ը/ȊFWTb:5#TY8GǝxJ=0󼫼8;aZZhԚ q#P}Dx]< ˑxsg پ(]+aQy%g~Vi>RDRWՄl9܉xrd^ܿėv}zш-%;3fKeF-!{*I5'EۊoqLv.-xw.>QD]Z2OJ2j~@hW?*B4g1WDk$s2<266ё\-)~h 'oNאܕe.u凨HexF@gA! C=c'12k 0͛Lo\X@,|Nm 7\)JвRU.1U=kDc0x ^HoHٿf^Vxm.v3)+d檋ڄ?,+ǽmH$ryɸη+Hڧg0,3GȒnUX,ܿXd 5_ # Ⱦ+cPByEwFy

    Pagr#ZN{İWd|mpTz],I64|FwR'kO?WPa j*nwd/+ƶG@!-""sXzs0Yzoazdz $l*xʥŬ\+4jZV*uM .꟧}6m3썀mE*Աx F9`ojw%l#ClGpJ_Aבr#A_opJ$kg|Ů .jX6QEg!0|Ѽ#@]{5H8>7&E/&^{V@KhPrk}~{/5\| τ LͳguQ|RoxRDu.Ic,-uн/tQpdG@>n.dᅼLYMnԹ(8.LCtӦQ_ D](i SUÔʴ[$a']g d)ĄT( ݉?QHjY %Z|%AYeHyM5|Mx(`wOcXuaR~KU'-D~3EO-]0_* !m:h%NjN3%W9îzǨZ-Ry¿b-%[~qQ=%(vI` Wݹ\3%;C:f 's׎ 7q[Q#-qfMK hyi8x lmy,VFNLB٢ƍz`N z"ڼ ͐U(TrX^߅5彀y<.j7=!|)"uSc-UU'+ĶxS>S) NIl҄ e^GCJR_tu77iqP] }!)Ɯu<<|Ǝ@Mڗs=vk<R|O%m yh8ztА.i: Hڸ1Zqw$4w!}`mZlQBܯ}pH^LuMc#}cw$BJ/4g6@5VNvoァ#%E>h/x r.6ߡ|u^/vUFDK^%.$W?\uX1vW)$]DDQdC3sGX=x@p4]F< Q8}UM/;F 1Zo2!^[mJkso/"9I9jO+}i*=̘Q>s\S!,CCA&e`9fDkmsTИb>zǟ+ӘqJEKr}\*Q @U*W^T1/\YC2;3mcʼW,_w-}t|fC3*j "=N'hݺ<I@:zt_o$:OhE>عXc9jKpWb8M0 ,r1 EdjA"끣#ʳQ9G#}RGl"E#>Ўj"S - 76\ZL+_y+*ags,~v3S7~ +r9=@Q ,<>u R?\z k JL_Op5y*wa6-)V{E/EQ4Y{ǜۙ&N(̜Fx9vo8[?$`Uճ\f [uW){x1C֙MI?y#C0 (MxAi$ų"E,ǎ3ǔ,0S{k5,%9eoЭ}N~jg YdД"3昻<\~iɟzz0/X}}j2V,)(]D$-zڱ'}# q?5JEAMG ")'7Ud|IdlDoq\c-$Ϝ.>nʣ@-ݗy]F7ke"_: ә{8bWi~&!<;˨6ܡd":=93J?gVTOx;!@6֦M0X"QȮ|mǒx!6^鋳qS%7NP^n߱<ԮߌHXĈcF6ׇ5Y^)#{sYr-׮FY_u*@Pv(`z12pt梽)8[7H84~]:=.@< 9Npr GO8oXiBQ]);+`C{Zy߿J5(n( з[G Gm33ybE- \9+=b2AoBT[W[;8 FkX5_&E4B0hjZHvs9ε؆= /4($bHD$P\x,`d51^()[Ͻ lS`a ^nbpM)>m"ql֢9`H hbX۲ܱ>r*ǡdHq_*RĽ|#L~ʤ2C2o8x@bzhNtߢ D¯s dW,/c)?nRsHjtgǷe<zq95wA*mБ,ܤkohb`:#{@WJ<=,&HmvZ2FNF%W%%EANW;X#" *-t@؍}t&ûj'^tɵ}woܔQldP%H-3YE!o#&`4MS2.]*?5bxOVqR.Fdն 2ktЯU\$J2+ff82H{^ƌ mu-WcGg;f5]{l]nJQT;(68J &. Ih`Ԑ(yOA/Ki+̖Ȓa 2/K}r2) #џ:1,̷q:NBYt9BT-͑6S!%`vUᅴ#*}EЈ a_u*}eN7dX+PݦFL\/hKSov-=+-G_g'\DSI5bXx[< eJg΋*AR.Ap4>TT~bt*6`JigDX1H@RGk0b 9XCe.Yii%4̀?d M!*" 1x.Wt((3ج6wy2*qFVix>gj31U $>q2C 5W 6%S,M@ FNamkx|_Xp{?`XQ9V_u1?;g,1f&% wG.qx%ЩLSnLk^+v =ACyA%'&}tԅl̶2 "4k0L$ǬI8tY}6`4C*@f"XK8SDRFH~N&FTr`K F|a!(7`XnsދMe_E2Myݝ?r0y@Ow;(2)*yFuE'_}9\ q|`Lf'h[(3ż~_ncrx!_S-S+oJFGE#&tY5΁Mx+TRUH@[ aBT!sRQ&|dJKlE6pD3"$S-#z0Z!hrm|EWjt"F%]u iÛmYkڔG4T)њZ;l(M-Y r(SXWnFR!*xfH YOphirưldQYj񀾔cZ-ى_O@1:{=I) 0c͂|ঝGu3]JFakNkTj|d (ي`/>".>^[BNQW掟#}(ZTVe麏?`B+$.wq,.~;H+W/gtQC!Wyji6Б(tLj}4-F>򫎍73\[IAy%8K{!F{o~_N`pM1/UĈTR 0]hqc34dmrL+BG./c4=evӇğ5݊Js԰ѩȹ/tY9K:|@ Ag1)9aIw5cUWwFЊjC6>Wǽ;KݎƓK6>j^L6+n,@(?zO0 0wRy iUAeU/kV9mPfhK|N0|~6)&QD݋3b}8^dĥ̬ mjqj(6ێ]Y}ڨSz.娒&3m󄄞y]7<(с,NLy%mq-uw8 +-^0HLD~C8,x۳GeӞq\Qy~9OfELղҥ}y:sNe;]t3c\;aao-i~BhϚ{Mf( k{2uE1W42+Wau$ŋtՈgZk =v}7o"Ir{5΄ڌ\3g}N{o't@ՒG@`%?*&\tzwYt% J<BRU%j̹f9; hm]QuZK&XH#{;t4slHwHl^\= fz=l+/]2zd:]M '?S^keE*s(A݃MZQ7n\A*pV!eH&Vcv>}{=߇ gD1\""[>V$pQ&m?|x/Yux1!k|9L^X|&dO4` Y)G ;> gt7)ٓ38TA; 1A*e4Sװ(AID.Xt 𛲌:k0<\Ω9%PC Ce\HD,UK=_Vź~VF~ǥU"Ѫ?*;K7.NFZ.)yn֒a2Ƣ>-47\zׅ\$-9dXK R*cU7ޑ4 4_]Bku!=SdTX56#_JGH!]@&wp2)KWc lkfSl3$=uË; DvMBǯ+WUPXB/% `Ixn 6{bii>SZ$.:kJ]',g_Nh<>ɉʙq%*ݷZL[bE R_ns @[YHIHZ]"VZ-r=!t#G ,_ Hthv`n 2>]V UѢ,dFOoHCsGBnM#H9 HO8s3 Cn!BL$851N;WIvBߛv@R! (eӰbQo)hϷW1$vt5^H _tnp Y$: ">Znb}//,1LfCGd,Mo$8\3=?ɕ2F-t˟ib}=oxL@lY`$:$L$dn$Lj+Uzf^_خSdL $7vz}uJNJ7{=Eq⡀N {Q~S$ L1cL©dyS0 @Q!W|HQtWCˌ;6 XP\O*TPnYF+J-X$Uh-~W/)f+$4>fTw4IE Q<1VoPxnzK%f2TOoa1+D'-Q+(:`tPloZ[lY"Wզh\^.hvJ]&e #A+o62؝P&FI+8{p`{^U#tw{WegH"cp7olrզ +ú yߘhW%խ=##]&Of[KQ-=x,  GCEe{2Cc=)RWqycϙ3ʬ0UdF ̝q{0b+k7ˉ+GLّ<Sb\ M75RR6+PôO0$\;X >6-L1Gw]*ޑ<* = Z խqJ+vlYSֲ2vTO^caJXϡ:$ "mf _IJx[EۥM+I% z V' Ro[͍bCcܩӷ]+,2A# ״KAMofWT}+dԆ3v،Pݘ&GY\׷&Uܚ n3Ī m!fS찪=|کAfqvNV~*F )۞OO9tx),9u0 ?](hg\vɑ oh+cpµ4m:I=9N=n`Œ7z`Z=&n<@E/!9TTՑݝzrF.6)ϛaQ&6Ohk kp },pہxjYQlZXB @_Ma 7鲔^g']0!++:ط$ _ RbTĔSz- g.۟Tʞ#%Φ,Xj7 X:*m X7;6`Ơ2_'#ƱSazuo.4'PE}eBFim"sP1r|K7(Ծ2OIl W(& p)fÓDUEʛW%’Qi p4?Y7en$:i[յt'!)<b+Uar&RJ|JpU'9@b0EJ_wSЦeη6VgBz$PZ qv@*`P} gWVB`eXud]Gea%5PL;C}B9ϰc]w Z_H7-g 7Wߤ\΃ vz^{j{N,I;O|cG G%ZRcQe(1oozRR' "~r HxUj!@=_oý-Qm:*o&@g0}#u*ĉ6[ӯ sNy2Rإ25m%Dz "RIG8WtTe֧m؞M4I=zoQ-\&!4PP!6T]."XpxH]PI c;\H/] -AWDь'.BSSⵝGݬQ1_yo+<ٚ1u<)#kgTcij?4;]P^/ڧl艒G޴# kIJLNtu˫@LV [4'FƸψ |G#CUr=5//" R.\%{Z+GUx]|vS|9 (J<1G(0;Tx6tLzȺyNvg0 Մ srU̾fW"Dv!lT"j<lj> ݄bjŸ1f#qUK+*Cqԗc[eVXg\(4<¡@Y<6 a:@UcQw->+Qqjsm40 'Try<_zF!UfhNh|ob+ .E ╮i0d*txt&Q?,׋ $+TmI;F vjH Z] #ZkyTLW39G`Ŝ+Lm!<#1&auI WK Op Ȇ}^d,a feչ* uM(`Uݏ0ˊYY/}\)g` W3Yz,ԟQzf{'L\SVϗUi+"_6}^8/鶫{Pa,]Od`e&~>,qOO3) I):Q!H3>a`9/"v6o=)8q<ݏj_%[5ï7uwVzCJ)R_ )$0W`ޥw[$D.TyьKy 2X\}c}k9'٘9y)uT#߆].4jknf牘tIvɵR1 !o [F_1dtlsg(V_XnpSޏȚ;Alndd_|G8Zr7}}[Xڔy&aNy&=1ķ]Ö]tN 8󲞶Tͱia a`"ȶ{If`]ڟH{Ь"\Ƃ:=|4>v_wn-Bԏ<J bH^YҫR?wG` fq#4%B/'b!s3r{NA %^MsB,L7IYz"3 J NRyUղ)d!ޫwZ/\\nԃ![&M,1iӵMȼ;L\i1oq{KT.T.w"JOc~wvu:ܼ@o2qg⣥{ 7,=>L  m'C պ($:d<,)i/ۡ2_dF u wL3teAlb=0 vٝWp=/؝ |p e\B5N}7e5e-BBCAJxu"rxnj屼ɰ1>'Z&x;y^Y7iG5E.Pͧ i1gþ )Bb64d\(Ubqo. z$AESYa3/Q +q2tw_ةOiRxR'Jqѽ4+o{&Bp"ۨQF#,S΅Xr#Vd [ 'sڨQ%اɠ,+Mh*v`eW-`| Q tCV^b630H9R_qn|Fvki#oiC,h2,n& X}B( `NkN:Kh;ѐqp2BcЦ!dت[sQeW1^J=NeF}܈Uv'rn3fɎT1G;U#D %}XߨR:fhKq˃k('wd\xH0VqJ L.3paH7.Q{^mxνnW+j;菬&JKhƔeaVtso`O3,epmյ/wD!`pv_*1)v:.(M˩f&+ΐJ;=?pT DrĒ]%>D$UĄ6Nkv?0AWysO*Х  i1Hz f 0a0VG@\Dݡ a ڌI=xVAG&y"ZF_| DQ]5/ 'ѕ&_̡ #DU8B21 §,˚DrY?GRu:%z"0ڼ2"~PAw$"CS$Gwhy? }©yǢkg;*G;O礣sLJjh_2XQ[R 96G+ӈ Vv  %ݐ;'9r$]t'qi\$|\~|2a|_%߽fYD v{Y~#gw@OzL Q![{jrǁj2R$B4$|r^!88b:y|j1 tR7.DouX5>12.C~8> tI'ghi:;@ŝ/a IT| jE@\j.)a!FZŗlAEzw.Vh3qY=t=v-Vo2W /὘VC ~d}a[noPE2 Kݬۺh0 :3jY Cfp,H#RpbE1iSAyf) 71H;,`\yCn@ob@SքN5߻~AF>\ '<"Vn3~?˪L 8WP6aV\j0&} Mm5f;>9h[YRLLU#%4 TWY+h=1UYp.=R?v7++cqVrj}R"0%aOo .* Hu*@>A`ʮ`蔶<`tb^,pn:]bCXC\Z;0)h8i%x9f)י[Hp&2(a-Áe!>V)n{׫#lGktkzӓgQamV81ν! p%$ Q XҴ8gV pq(@ . W%8oF".-?, Ix% 8 {$Oî%[  z44p@NW,7Lؓ ^! 0cڎS,0;bVe&|4f\PW<q>Ay)aEtC[J>%ԹCNʙmrx9z);k( >`;>f0"dGiE`T{hG$4+tC`'.$t1Eb4 {NpjPjGLsߋ(0DoIafI>.U2[.B'},6` ?LASoYnaeS7T|z 3i&FXI۸Ȗ{Yl;gQƞzx uq?kQF3wM`D|n'lYlEsP!{*w&wj  x]&:$)õ۬\\^˶ m1ęNXʂ6b@j²'T +EIG{*شLy~XJb}R/h0J8Q| 8^:/ ZkVUr~RB.LqY8wDPwdžB,S{zyeqaKҜ4l.jvRXJ_ d_X%+dŷ<<2bs@^"r3z' 6oAxd%D`cˇpRKyx|ؒQ>s än^.yft4&HSK. y/(S(4{'{å#ܫZyDy-^JzYO6)ʑ`VU0^*܇F뫆(Tx%Y-yvd\9m!]1uKQ$G:PbLHhFjNpMF|p= Er~ jz@pikX Nqs9$zS KʍS*mJ |j>SeB&ʖ,(ŀm4/~؝H$/}5?0w&>C|]0-vd Y XD&!_f(Q '/V֩о"[BR?^.R0_pu>ږcv(9%A "/%Sڧ(>Pb;&Ҏx$}y> 핹 wݘհϦ8(׻8}v }V͓Wo`(5ue2^'mk:nMoаZM.6KU~&%݈UTU|h]LFsWBڧں_K88lڇמ!~;(#5Н,p͎+@yqlmEV³LŇZ.o }0U5얚{sBNPCWxEMn L%0Ow1@|͝p<>$[:Oh F_xņ9MTtyyz`sqY `"dI~^*;F݂NO:GKh7*wQEɇFX,B[׺DƌƗ;Ӭ4$"3smhS{͝L d ܶc)$IA mC{R۟P$ֲ9 -o2.K~eߤ:AIuY/[͈o݄T1Lve J/i& /i%tM{֭^ŋIŸ[r7*+llE TMW܂?9zߔjwvn"-O +`2YG?/olj/8- . j5xK\ɢArtc-_VF6lB7?X&R1}pE;s8|2}%׎Vfė0(-){!AIe=.ΖɀX^5TicijT{—?# ~W̡ۍ)e2Pd}}'sa5 ƔaެhL ~8Fu4щMͩM˺iv[WhIw]ѥVgCFʤ\x ђyھ"%G˶}&|<fX~M\k ;A&m8@j j1 c~Lȱ+\~zPVr^D>D"/Z>+'|d%l[XtD4׌26XhpeցŲLrc,aCTe~BV sD~d&00T<ϔۚ@2!+o;C!|&?D<>LJ\`Y`Clxr`D5:V꥙'%@Sڐ!F͞xG$_NxȞvԜb˃?j|Z]CZ݋ V.c]|ˎj,|eĩ>5wcҏ!,U\: 3U6?uSJ/T%<ݦe9:3#W{Ѡ=B_dW[dZ/Ck[%I)T2pQxր![šm%@@ (NȯMdsYfukR /or'ǦfQ.L_.AY`Eל F6}2;`BLW,A_ӟb6Iom%,l{SߵP>kw.ID~ خ$?4%t۵ ,rɡ9~s p0y/ɴ r #?VJ-X r4Lՠ3'^p÷,:OצwHrg"KwX:Ij|˩1]Y~_bڕ9u!CʊRdZ(=Ajތk+-kUY1uXIdns\%@e(3.@\ #0>쐺my%bD)AŠr4.zf]׼}6kX MYȰ(5m`9yzRk8aqcyV]C->E[3H;&!6kAR%HwRfʠ_'rP80L{C4A<"$.)ӓN5\ NdY-r©K_UR Un2$R5y))Q=:O:PgK!PďK̉Q: 1d6xS4%yc6,Lq>aD!6~Hiǫ[! k{&Eæh J.n9qUmA~Nr^ҜAs :2M*`';: VĶ0߾YLP_x:H}N^+YfUgǦ)&:!ڴH}ӄqO%b ! ϰSg*v0v>GJYŞFL`K" $;sX'M_Jm^L Q_?X6e*A2P2j?- <:YMi'mO"$%jDY7"6E-;ؘbr<ڽaS5["HQ99B“C+ lypYP:X.LR6bXA; z"owV#K:i p^RJCG5,&a`HG/Q< $:X͉ہ(ym:" -aFY(ATp72I,Z畒 $TGi4o0$8.ł/;Aí;)*7r1T3ITzz5/Ӯ%GY{m*6Z`讖ljHdqL[".b"gA.b2$XC;^@fzj$T!| 裆qDyvwHu8&uERSa^*gEuoL(@^4h|sك g vY ϳ]T},qIS=w )Gk#|RƛZ{n)Ć  9OJ,p&m7UHϒ7pLs S>Gr#aUKT[auܑ]Pw-J21ܬ+L' xnчO F~61o݅>d@/Sj_3=YfHX/{~N7'C#M%UXM!% 2 wE(ueY>8qw26jV$;~D9ȯ0mVifU5a,ǙlIM"?Bᙴ"Y9x;%C|$veXkQ>|&a jI A`„~"xB]aˆ宅i~ Dj;KZg~rGs|oj+gx<$ѵs ܀;k7k>`܄Վ~|YO,|շ%`5Ƶ&߬_piյ"Dj\9h(/\iEeRmpi E0DL`}i2OPLҿɔlR;D:b{S=McMKseÁ JAnuӭOCyedM7 ̪* $ [ʹ6JCE2 3Nx]W Rzoqcs R."ז!]hoFfp'[[WVF+|u3Jh7(6V?4:1i$0wp`.UK"8EE%S!˜ &,}" "wr &+gUmx_55=N;jlXU+yDCezk%وCCFQ6nU7.Ztػ(hX+2#xuTiX΄TxܯPZ\tc&^Ⱦ/@߾s[eB("N)%{+TO_L>|1!!ɽ:FT=`2cX:)L_gظ,ɡ Aѳ/k6n3.kM-:VR8jIP vջt։w{>)3'G]u lwNsZoq-:v-Kg㕋&Qh;Hԣo]{pYm+/r@cE)[?,u//`pQ3 n])a.ǽ!9Ǥ d$-_#R<)ָHM]ǥ#i\my8IlRKsdKeRO6e=hW+ڊޖ%H%XH% oûׄN&I'R8 >B(4AO}ḣ'POwT0Gz#M!V,G< 솆ivSv3r#W+draex13\ρN 'GtڛR:F1uOz&I٨we){ĥ=k& mZ)ס#uz^mKY0zRP¸vBE" |~jj¹sӍٴ+w (;,{5[МEc9G%"EqXI1V'q.cLei)ڊeh=\mI,ݏ:vo[./8Ag>7 l>)7lcQ&] |65nK4ϡ=1=A0`"@y$uog5-`)Wmk@Rf`ĺæ#:Q'/R7(ͭ%% zFK /gZJNY t_`]YV) d1;bI{k$0Cpr^Yϒt-wh(`ւs2tih_; Qjuѵoխ@֭זSfoiQՍ-0dقhC =,~ԑ^§cn kиE\)wZvuIgmӕih#y$׬ϻ(C_j(pun 2\Jz&ŴA K(S\N0cBxZriޯ>' Sz#g'о0VQ$KkvUpp>'gޚx_Vw.c9AevxoA'Q2 F|1H]'%)A Ьg4CY/%Yy;enL~'[E(ߙJ|A9.$Muj{ %-=b㾣!t'/+0zۤ5RKݣxe@jekhP0t3yM'z6zMҨQ;gp\qb 0VXKhFUǛ2Frx؉>'ۈprQ#An&)qHз|8ZbC:&E D 0XGXUf󢀆ӳPr5%#ipuYǠcPNB~WWE`/aP{B]k8'o58kBj\Ig U"x_)hp.A{A1e?bt.dLQ<Kx2%3x`0벉}Vnw z4kIY~Opc)D]} $[L(\u`iڅXEޅ]@s.M8o;_JG:/Pl :'g;}̩{7n ]]u.6(΀-Jѯ|ghX_R)1yi*m@$sAm%?=K2qe _ .{}(,bEiAܸ ye jk'#LD"?`dwT޴-p\D^TȥnDuo]&![2ܮ*#7䡒Dʖٵ#cA2*,Q 8+ӗS}[JaJQ桉0 Yt=UW5ˆh Fj0F/v@RZ˟N!ogZ$5q۫{ZL)N-UieZT%cXZ v+M݋3na(`zR"Y[E2!u}.噡Ts6/:w[oRw:K\JY^. $m9:_.l6AEpZX 1MQG K9͖nE>.I Z;\Zn| k?p_\q1WTFņܝW8H::6uױz7)X ,ή6g,ªq8U?#7k4P>sw)uL`.Fן]n?Kkf3aH@֊Y~$&SH*K5Q˲m ;A|#QgU@ndZ^?%ygY4=_x"]+'332\v+m'BWLOhh JmlcWy&oaA4҉uR@Is~XFFIH]qԕa԰ӏ>)A4sϊ7 ^Z\RVڻ<~˼@)#$Gujc6a>G!;dsK=lǍypXKgeӽ/Yyn~XUa{l))ƺA`(WoLʷ0_|e;(>! ɲN)}U'lMZJ+y8򛽩tsWQg]vGaOp424xB^^w lUheJ|g/(N~FTc_`3 <<kΣH˖ղ%449pJ2D0Kd}/Ĥ)oB@^,)p@I`l/ٷ zo լS&]!v%8L)+2% Nd^a 1y&`,ŊEB`[2Q^+%_:}Ʀ ,5!vY5*bʳ)V?g=MXcdl}X4tԈ!c /;S0ϟ=dRπ`ؙJ1ͦl4b@x5zx1ͧXΏ%[`ocSe$gF}mC,iധh D"aB:hF dh!te$egwث} knmT3v+K ״62]wN k(^'r\g+!-F5F }NLpxg x)OfXi2UFX+rqj)D1H1r3m}Kꅕo* i}8[#N.yZA!ě<~<&dyoBDյ']eC%@Ԓ|Cl h~ c҂D26a^'6)T8m^A GK?/UOR=ʨUb0$I5.#2p-RǼsu% ̲\O)+xxR>]T%QϺL7EHm*'Oy(\g2q\w#n5($P/qτ']6 Ԣa :ʔbD8r(Hl%橻\t6[u/hn YE9DH'3XhfQ qr3iPZr~R 3?ÞʪY\,CCW<.ǭ 궙#mЖ7.+ ]@p#S s/;l^5V.z 2A}G4s/QKrڼ @TTaIؕ1uWE jZ㳤r[,U^(bOT$X}:c1JZO^Ƴ\zIzH5@gu@>xzEY iaWfSnX+]0Y`zhMv18r2wC=Ԗ ɛrLtEm$nIR.B3pGf8^m#~-6* LvڊU 0?\r-1j ÂFMo bE/ (yʆ4Ea$Frѣ\ ?ײCw|TЯD'"ܲQ/)R ysP21͏M&ݜIBU-\~9BPud)#PXp( ? z?Y-uzM ;cק-X?5iW]k# CS*|]*kQ' *`m*'KaUXt|g}ݬu.iy;Lj$I~+*}Zi0?o66W/ozq]"Zvb/_/0qq6R5&n"D\m6[e"LrҭehTV?ގ/FS7[r\YNJ=B5>bh3/H"咔 I=kM\,EߊdN%:$oW5<, +a=aN=OŶtxXÐ.:ׅ o:}@UJWC u/: K'ٷ?ま%!dx%#F޽?(;} uic2iT>M~TܹCq({* [A‹8ѕƄLk`gn$1~M߉TnF} =HM/zDO!|.0_b3MuꙿWsF~D9}Q(dUW~?wOa%^0w_"g׵BEe(6Q 梛DQ-w'ik~WHChX^_Or8ﶟVI~ȡ}(tsu Wma4|3M8t "d|O=b|i{p019f|Ze(H8,zޫ.y|:scp,3:NI6Kn]qRxDPA94pC {VY(?OphrgqNȾPWޓp*Me0OfK+ZKJPsB?Ă oųy0%nU?}-q`05az[.g=B^D^osQ%;Og3@+߳ڮ?47E(Eߜv_O􌪭>` SL-&L!}$L|]RB53}?[oߕwr/]G~;T%tйy2o25>đtzsԬ#6lWr1o)Β- >qkND"r$3L; .x A5qlw1>]6bxnJdds[2X5pVx(}X@",eϦ %X}ݽ}Hu!, t rnrʪ譫g@$Qx|ϤKԂ|JΔǘȡ:A8kȢ;ӹ84מ[;RY3JT{ҚoVՖK?o\!m@l޻WD<{j}VdZ$Y2.3ܢIk]2* [v&~Y_.dw}u '^8~W9}VMqI<RPeH|ĥqӳ7_*tDfQhfPKe? Mp}C[/JR,w(3ِ1#RN>9ӚrtG x2.g+:UFפ6+71~Mo֐2jݴ¹m~޽*;b0hw3VlP@yH*7@ݷ ӁpN<>9&64/ se(Z<U]ՙQl'C?Τ]w  }ra-Ї%lQN'u,s*Fs'<2=(Wˡ @[xM}"g UAU,~h‡[LS1GFu(!K ,*[t9vT<\6ce>P`!Β6c6E*W Lqk$.ȏB(,d~2(wE4Q D?oU uЙ4Vy"~N!xtVLo;f=W4 ֗c=qa߿]{y( = FY; ĥg\< ~&;g *G`(VgR;Ի]Suzާ]fY?|tuKϾmto :nH%-vMM~;,L#ˠ5Nquzl A{ǨpNwTY} }[zYgmS`ZPf"!12\M8EjNX{5oIHKd8B=i/?qKi ىEKj1DZǡb)aɺ6sG,"~oUe.Q'0ѵf-[~\ Maw(f ty)5-Z@?wYi4`{wCSZq8{q7zǶWCPN4@_BRXBFFqJ9nC)MGX MXԜc!z]`D/Y2tOI TsDگ &BHiua,͊ LVMXc=!ط&ZB;MB0hof42 m8' \|2)=2kЙ5wrCj [*)qB|imǦs}E)\) \Vr4pUwBd[d28S8^|`%}-x$]AlIPl+d8FS7jRW%p8Z:UsdT(ttr}֫d꺹coq#Ě)s籸ypWO S W\d`^FÊF֜hhBizԖa0Xm (x88JI㧛m2e% ړ_2#+̂P52$.|} 9;@'S, i~PH[*E>9פL Sڜ1݊⠞M,n:\F6kSⰼ&t&ա\#2*xjIMީbT*.+ :dph _zIu*SbjMHAzceTvg%phچ8_b(* dp}'~Mêo0l>|qqȐ1UTV{!ې0_syz†s6 (\˞[|%gG8۵'xԽ80 '7RKi~@iR$!AKKvyVA '9s])Qޛnt ,e`ۛ^2ҝ&$iO#לal^kn RM[DΎoIwQ jkTԖ"ex˹cڛv%hY{Px$Nw):7"YLHÙ2Xz/{\0jުi=p&=΋ 瑔CCZ?uԉ[[.ralje'7\ 2H()ݦO_#ttſLГ{̥hR{dI0/Y[QDt>le@Qmf(>L7]f;UExYQe}w :Zv&or$?2IA$9s;qZbTLr;H6筹0x^Lr#͉W&-n-\=Kl~V`_LS|0H~VD\%3;\:8~$)|k.!j(:8μN qzi' cS"iƈ*}[R  ērl\#N12Sno ~bYxR*ƽf5ݾ;Otr>BuPEO9NmoDݺ9);:iob';z-?[@.Krz7AiJ?&iۇ{GŻsxTO'[x @io-*--]jb[QGv|7IJ{?rC jZ#VFޫnwyZz`(=HL^S+R(ļ"/0xF̌v^@u{_: TժY݁e+̒3#)|-S6 wA.S~5*`GMH&03 1嶨oXG/)@HwB( o~!I?w׿;b}=eÿGx# P~ ؏xYU6e[(7fU\l97&$s;㧄xr>=j@$w&saKq[WO!7J/өT^{z0Seg# 2@Hd|R汚#䍨b3 ;B8v c(1fRW2%6RܺJiG|T@, kh\At;sM2N_{|4RQ[_<UPm_֑slBpPJįy+&Z:7 \ޮսm-dK1j ;@D軹}ⶠ+뻜*+R(j9#qʢwV/i7}Py\Zp$Q~(x'{,K\ ;;pCso_YcKSuFy"JT{X2s @`Shvl=at!Xm6R}b67IQo!AJIhsi:$~R璬ZQUDF';c}k?8:P|Z,1sR\UeOnΑr\6;:o C)S 4-F -Z: ^c*HVzl0AuRNu8H(F ڍli9WAߺQlsiMEKH fK7rkEI5x޽j|5hxlAg`~'%5Uj-cD·h3WcK0]|G}n6u b/3$EQ]>p?ӧ== 񩒏 V񪃙xesWHXkfHbLwetK2$tn K "5yoܺb\ʺnQRbd. zv!av~M_w&Y]j3X&x ߛ\X~j-b ךL4w)[cB`+*wXM {*ѳ1S2 _biEMYHG`ut1/fm  wx&_fxT 笅G|İ9/ >{s {ZYdQoVE\ Q5xJs7`k\>#Ѿ5hrwT"y::z% :]jܫyS+ZlE;_hӼ$kT9d;rϢj5|lQMzD8,W[,L8xעp$ FT7!ʄ|۷/BzƖLl!Y~w[Gox2;Z׊X7\> PSI@6 M L;3N4 :9WVr eDDaɐ!%,ntuP)], S,R2ek:aAXNTs()Z4iOOqw^*DUw[f9$E$q י.!i8vKǧ"nx J2B"G (ral6%eLfOk|D|VRVt$ 4'0WW Ow.qcU4xm woȵE.y2]mCaxVi5H͇&WzN>=_Y x녹٭ZK9< akA+h\Q…AF]ޑ5ndj{/K_LC\jw"X&)n̅0DgUjA!iu0ܦ 鷡DX{#k2<0/xNNSU^dUpҚJ>!}f\s?p"j1x0;޴/dSH Syq iOAR[49C:X+T}ẁdӨWY>hnґ!${u0e8LnI@' ۙN09g7jeX6 WQ$j& H#ƹqC;PieG].ixPDH{25r:*6շ^DjD>‡)8,v^$ZbB$ˢ(-=P>Cf:Of BZ`,2diu.! <;$[>.! -bHXϕeDmOa/ᓈcΤw(qҳ0c]X Sq{,9T}`'A}:Cټd_:@[Gwz,|ٸ&,7\_"$PF)eXwk \ h0"Qh:$VRk)ՠ^y8}ݦES9K@Ց7R-f0gANg(@AQLl$֕lnttm_R/$os.<0]koq]'[JR[8nOb1l~I,J`ewT%VQΎ8o֔1;8;e+qoPIp\ٮH&;`-v味,AG6  C!"8IBNֱEw%@Sj:FMꨇ\6p 6濴n^H WӀiD}X"^T5bu\:ƌ1yުy"ֺ6<=il+EE_x4ug]ڃ_-4N9.b}ȱPiҖQ`i*B"㲯8FED?!?KG9_7?-njVA~ hb0Ir)vOل;dsvi3K$/ d 0G8yzE=E5Q?e{vVL6Ҵ%} ߺo{EWcPIQO*SIrsw"K:|  Ga;Ʉ^ Lbf 6}Ѐ|(f2e =Qc8Yfz]'@ tnD*`G}$6whQ:gQ2bumL8V a%S> ~gז2zCj3$d{en<&q*eHV vPWk)W}0˃ՑVleluJaîBܑ>hbx@.3`-pN# 9 `Q8WfH|@xe+qw!f+̠ҖPfEAI2 }b;Z}YοPax ]ĠЌp˟G{sjwGWG&,Z$HYE=,_]ѯteҜSPFw;mMQ^ !ϢP:! ]D{nHlҋ}9OFO!:D[]P#H#SDqcZQ/ؐtCsB ,r-"3bq"Eя>;r< |Q3w8-QFWO춿\. ~A5kvIn =>ǐc%fND伿1nX3F1`ACѬb]O: NaVp2L ď׮IbBCqmOeEX{s :m/TzJ0ZFji4[Vvv{*ajl55>ovi!2D =yn_NebywS@;N|Ύ՗ԓ=| ZB~__5aGp([#[S ۜ8?2d2#7$bktC>}qRZ 5bϡea!ȒAL&$=t.v k^C\&o]̂^ҴB `ɓ7\N'S_eitPmd} %bDiO@|8TQBZ-!8#;gtń y!ʑgjDv.1*r[ntBS'AcRYRo=KYD}4e<${Hs΁KLjol{-wT)ն"-*[1!ke<OKbf~UG2͚QdUydX( ^ N qV2\ezzoZ('2T3BI3X d=:/>$v%?—&"IK\Ӥs\(ܧYbN$pM>ICfӓٷ*%r5gqN)16A)h{HPo pkB bfLxČN5u> pmP)Qw [hJr /i/u2t4WS),{'XT MXA'GD~l_O@&c3Rי>IJ@^E/rm;?\q:glB\pm~Sil}Z#-g_ A -qn yWT!C@$ D&/=Kq aB>TUh=PSGs†n1| ^_d5br_ˀrf_=z8E bW9}OCh+1+Qr\6ǹx\TG E3@$426: DŽX#gSqk/U ErTm`.4 Ȅ\I jfv {ox;4M6jVűF*"/)df1JqJHrhR FBc=&J&*Y[βP8G  w0)\?/tEƙLN>j_qJ: u&cs+y5kZntCH"ړѐTGk퀐=JΪްVU_sb>bQ?m&N0'Q j&Z/~xvw9CvM=6sHmu\r2|<sOsa^#$Hi˛?UZBR}?cI*sҽ5E+ Ĕ߽?W12xVu,44g}}[*>{Pa?Щk@uD5̎y9 ='0 o5 4W"҂_.^|^>#(3қX5Pyb?qj11!*=6J[ο~_O j<6Tn Vp.ܬ-I,n 2qk]ڪ@&/  o fYrT⼉]dC}Iu~  /4t"v)C[*G`хUX <"MZFUO(VeFT^:_:`ef +(cAQ7A#-wfFiԟouM^~"B;Đxq#. 1ND/ʪAٻ?5?Q4: Ġ^AMa >_H˸ AZl[k]'{,;o/Y'?f> Usⰱ:Rs잕5c[ :c3L3De{p"R왷%}аN!d ΀̇32sP3#ue>=u[Hh#e+/Fm@̿Q!leW_Z Epޖ!U+6oOŦ橸6׹v[CC0d< F0 kObd)rn&x{S#ff`]}2*atG+)cۈ*@0_3y3uLN652 8 cP[w&wϞp5_͍0h2>yr; :ꅚz3J 'xe0Jt|.2B`RgtHEk;sznE/5$0#=G&@93}A_ ui/҄Ao7"˩T{B@O&+63PT9#OkjLC+M^w PAy6"uu#= 8%`N+ )F A)+Oj)Wzɵ?9}˰B >2%n%#'WSe1f _MK`- "7^!nsQ段G ],-_i6" (}٥ᑞ7=zGMYn>˗BH(6 ~-PPU:Ը0x:} =}y!#>nhPzR Xu[ƮP9,Y$t1 *pA^gۏwsGciaǑ&8?-6=KVX0 feTyA-%#Ipn=A }*Lg}xRs.kGԨ[Hu-} wU8j{{̀{읝yy/74ة꭫-BNSv.&;&Sjǰ 0Z1IXz2XLۃ* g ?B"kEirpo 9ȞTi9X(3{o $,j \w/|߂ \?k&)n883h"QZ&f3)sDmk[o(B :Og 7 }2E(VC(üF3P׃*q0䵷#8բ)sl<];9/qC+kd>?-pxEvW2YUA͊SX\=ؿ'{9L> S%G˔آFuƣؽ2%9}V"buP~=vym/C,ĥl'JI9x60{7D՚ކ\[*`1窨I&:U(iD-8 *S <_kzr h$Ȓ4!~JD2.-1 Aiy_m5dA,f^I_lZӦ p.L ܉ v=,{){{s 7|W->\|<"2GF'VQD;=ft `1>P}XL&ctun)ely3wQS_wz^}j`V u1W4܂4Il/"Q+oȥgH}`lqbn%OeL'…AUav?Wn&)Icu˒Q/oTyӉ~.j'ΡqxK\&ΑWtWxY}C9b !yx[gs0XKh@y[ XvyDܢޯ>6ahUlA. ( kq NDg7G^e|eґu2mC+V2?u3酆Œk/ݐ%W F1-qӘU=v k?Y7KhO0%x 3iZ)_L[b7X*fu?HK{j5EΐwPXXO&Z~C38(8/4,֒lS/mվ#y9&j~>J@zcqiղIg[FSs 7CRVeH=2prXuPT;IBXD$<ҝN85Fy:i-[XPl@[Q83Q6d$5sIjF|kfTpըM d~IB-nDE;q?Es-C;3)^+%!oRW{PY2|T:A 9Ν%QB8AC)K*җTuhsD<|k\ }XDci8dE}ÿnKς|~?n]m @}I3L)i mR풢LeW64`=fz!aRMbX5 ˥kꅫګץ=c\P=H[{俳H g{yY8@_E+.1ϢK=:' |S݀Hwu*]υkz* 21YAH~{="`W? Y^a̯lr t)^YO;.(}3c\9'5Gbsl8}oS~⋠FqXl~\ -ry]&+'ܵW͡58vZEw&eÏ(b4DmФSu$$V9v!4߾HR2'6xkX"7,՞8$q@qn[ ᣱP:(u1;˭.X*/qnỏPƇ6zr*FgMtY_bCKoruXI9._/z[e~9((L0&p3mynƁ;DQcd;6s}+yjlʀX#~ Ϲ0%zU:_Lg}Otll_P瞔(ҿ2C*9V{>䤻M'[>b8N8!؆ \tQiB,t^oa@i`dh; $6fJVސ([3 LidDrUPXK͵֬HYMbPHB<ՀH) @:]M+4!iUh`etQ3 Sio(I 'l٬@4KՑMuɤ9=kTЊ(]38nA*op:տ}2D-ұH*c '(.!{]BMpY-tҌ8Wↁ'%^3/E ®_ d"i*@:=Q$Uw.y=-FE#^.46'3nJDB~0嗞&C$OE+v"v]i"AzcAztdBݖmTcVF9Tst#.b}'i{PEk2bNuL2Aymi`-qI}Ǟ~Qev']>Gv%ŷ ! 6(74t@x%'xb?$H^;xr &V5 ȼ UqN=d4g:a_N,WF`JAv7ȡl(놨Ю =qYQ Wd5cm*i5EiO3m؇\}T;.2}rhRPפL@Y\#"~c%6*H9^ !dߢ+2]v OBN{ TkUGNJ6Z-*3fnRTCѴ>ƟUj#F~> ^M@pl==>"eG<~7 B6ir>1ql/*e #I.Z7."*x.Yw܅Yy)̈́M1#-&9-|cMO]][e4*Ms:W9xxq?0ri6?z""Ƌ~ހỳzdfLg"qw@d"zt4@Hcϐpm6̟55 G9>DLLtuL% #dm2_ekV&w~)Ju 4b4Wwo N[@{;`06{ SҞv35J@[R-ePc x/۝ h]uF,PNq*TCExw'xbw<@)E(k r9@Og@bvDߠZyODh$K\^v׼5;_Cz(cᡫ볊 Y Bgw \IrYcnrydpЗƭy?[Q1_3 S~uRY7F+GNiϵ8S |5~e""/Xmׂx1뾰,>/tJ6[Ff}} j.0At^ͺdLZ- zÓp u\.!g8ERg"a+sWD9 *OLZFji9*픣=[m!G)$;x_ѱq&@R Q39գ0K.vI0a\J~lY:D`6{F8OVex.2KRMZ\hW;zR=ݫO5Is7pik bA/ȲGְ * Xc,l'QLWb=%|ʢgD݁<22mc3s'73ئBW90#?`a㏱cgjv>凿! qv8M2b _e?fճѽsG][.L6@pa$ȺY,A%l{|VĸukT(<jædn3F;\$jIC. ̸7|5@H/1 dt.]TtԌ@- ͂xrXsv kT5S;0>ЦSNU۴xiyPs'MXTDoDvyljwd4P$[5e]E H#8m e{a` v%Kp0xu K^Wc8G)R>hc_""`JT ƭ۔]c% c9,7|lq~!P(RƱ% ǣӘ;.r%':e qnTsCu/"%DZ<zM<|5~N_/a/ qFmnA@3Ņ5x-JPćk,u J첇z/3UI6._:\->aIá8Ɵ뼮V1 ?`:Zϱ|6~ C٘8zeW3yiمU +%iXdkr8g=L(\1ƀ9^=r*RMz}[ '% %]cHW q0=cV[ nH>XLG衕G@"~D¨ bd1L $;Vv=gۥ5z"%?'ߢLMf]w3e\ip}Q^zx8_1ױ22tDt oWVTUNIpxEJ{zFU&BkRt8P~[rSbYv0y,copw7FM&U@s<[8:# pg_zA w|Ȳ+  M -DԔG NK0K43SiԎ2x%HɖCJqRo|@Xަ(Pځ,GhCږJp:K6\G>X^k}@ #]OtI t :F\-Ed1zRMy5E]jB[@VORf`*o?3IwtS{,@@^[yuz!QdLLPb+TJu|n/sA[k \J皍UHH&`źT8'a!pǪ_ty 5ZPUb-d{GZf$h,-BtnJfy|R*P?hmq`QCDJ Az[ƫy#)YǏ2/@yC36)嬪&y%F[M'\pu `\oYNfo 'Z%XsuH'9ȤdTq=Bu⣨#,|ό D"Mt )OCާW"Lnـc1u B!-H/9MM6/yRfQQ^n9qGh>r68:-?_/85J6[75Ci)iV  ڝoNm^++|W]g=D3t'uWU?ӬXeqSIDLw{ʼM⦀OgU1*ϧmPmI'5ъGmOgszʫMdUØJ"WMШ˨OW?vM5GI,X6h~NwN=m}!98U̅K O0 ;uDT;۪X.6%57?=[RrPtF>EkhU~LhԐ,Y.s4c;X"NvS*R<:fש/%]4B|sKF61Ѹ bğ<_THG+(_[/ـufeȻaǦj$TNgjx:}C}|#p' P{9d\#!Eaki;3\&o[$b'N!݄\.3bh/26(/â^RwCkʷɼ.IoN]NG 2W0e=#} 3f ^uQ5z;G=4A ӛ%tfט =V`3!H 4M 0,=Z\eZGE)F!pR?--GEXZHNp$fl-r]O; 䜙/~Z('} {ϦwX0KME@"Ft@OdzC9:+6/X{52ĭ̇0p\ rC}W`kd&#/F84T6N06D9͖{7:+s%椛8j7H z_wdV\Zād̲g~oUY9Y^ɝYl7vLdF0=eoMvxw$@$1:#ܲ"9P 8/`sDh.X JDF0B*Smp5bT>=.a)Hԍc\/Rq0"Ɋ)VURoFU뜶&bغcrÆ4G!@F̸,kbU A I %@oc}`/6`ItM4'eH`SٻHt槵E2#߽\Js?@םfnsZǿ˂KG گ}X"_KJO mG!Bz!Pn+ $s]0ۈ*֌!@v^x`q+_"04uLJ^<0hEm/yIyW׿ $`mteFDg:. 4?0LV?6Dw eMÙ: #:t ή s_N*q1 Qڹדc1DPI)?&[pSRmxzJ X=ض4~*1zIrr=39*9{ȎԄy29_@%Vs8M7ߟWnG 2򡑧ɵ$LI?7-.8-DCĂG{F)b$~zWJ>4݀υ//:aJ(Ytc2I6>q4|VRy4Yt1Ro;c˸[8q*=RtQ,6n~S5/5G)\SCQs6K$5۲A7砰Q7f&j8 [t.zo_ң|ꑬZ UxNee\Qp9اN;*5O N}Ƀ 3k}WfY ~Wڑ,n\&C,}H[}u|<_'6So"^@H-%$A "j|S7_>"86c`~4p6lV[s7h꫾@n۵nWO#M{Þ."#=҆&:/X߃}/DǫJ(z.H.7 y,>Ћ1MzD*h+r~plR^ i+%rQlu4҃EQ3OK{s&e`׀'9qgI?ӧ9+5z 22|=}EF}ja$!z-\'Q`ϦTg }@;+?/KaPi@ɑa4yc-S]Cj娗 G6!FuY!up MmHAKji}iz7 gYqM.FiUL9o<;9^pe8?gڒ{{yAdA=Ux/7L$07 0=P!Ls?R[dM}+cNi"lt1.>YJKMxȄhp e6 :X`?s);#Bÿs@!'z\PYM[gķA ;षט<ܔ] 4}TpVH'R~^$v1Z[BSkBtPhVϱ<&XBJ1;?/J9~{9Ԝ&xI'QdW-YPoڕ/Ix_𶍻heՆ6Tc&ճa:U:즟S/`)mwu9k"@- V-Np'g|hӍ2K]!.[fY6x5 b_` āAei %(d{26:856{y﯃ԁO4} LA]שn ׂOLd;{(.yߏDZu.NZ4u|;Nf(8fEWҀj; 9] (\(; I!%_WbBKfhm1OLjuD'2ދc\oq(A^4=)aGlGsJզ3EL E7b$;T%NJtTaN#@*HQ1P*T( pw2hh/Faj :.:S};/2YqG/tˉP6!Fģyi$Zb; 1߬EB!a'0cWFu{Y.9$xӾDz+ \Qz*t [%4SyY\v>v "/_ߧIqOj =g'`F2 먢2;t]7yOXe ;I m(ͥJMk0x@J\AO(̔S:`s8Zs^pl,`Q^U;W8GE<\U,bKLp)FA4C'?ɒܽrAkQn0&~ r$Lnz ?abDUUPߘ]1Z&79-xޤKHơ($&[&$;& ȿm2Ҽ~I]fʒ'ٱg!>HtYQ3"]AH}}Yɜ16 ]Tt_NVjM߱@R\sSDבm3P]+ )}OxIӢ 9sZ!1*04|'.Ud:'Ye4!t72z/P`-Z:FYt'= m"hy*Z>6!`5j烱¡$W^O94U==/brwI6k̥O<^Szb+ܭ+_)wTa/vC>oZY|Qi5@|9/GƿΩh2J|-  M(QgϢݖ`P3wYr?b;)U ' r |$V5MíJ34#׍}O붬 <51:Hpq(Ӟ/$Ye%D8Nl Li2 D ׎(kqL.* 0KXwY݄\,ړ4}.$)]=ĩ)yJ}o5A> CCk`w}C; oYO65Ҋe/6cf-yOc% 5`1ۛ ySpkwi0 T|/`j.u${8UwfǑk8\`Euyפ1;!bӗJv#XLWL[}CqӄW 믽nB{ݲ0Q_'l'&4 WS"I$6$PI6[ ,Q5z2ڂC3N?CQ5lWV7շ; 3N+xc0kH* MgWA7/HQ.:Cw-X^|!hx^cGI4Rv)${˲Lr-JѬ } dQjIU7lT7 ]y@~3c E.M#R rԗv䇷ZG&үƶ`nq h58V<\H̓q3.XF1Ǡ poYS1];LƶCxq+XtحKaUQ0xӎLO:seA{Qy<8i[]aJ=tottx8 ܸ:QVcAAYA(1I>Dc>eo{nb<0t| 1ĭgb.z"pIN/QrԼSe  LP[Gޏo]JzO V.N{DTMD1.8$.#q fB,'oe'#TP#hgтL8XV4:!L93~`<-FFp>_=^TyY}`yŒnSk.٢MǙf}MGO;sgR%=s0o32 .@F*~ԑ 9ga'S >xMs- =@lNE/@!c`J0fr/`VsJ//ӑ_w6YC@ozLe~"4d^> L. FCg^x4ldP\>^+TPWHg$Ef;BJuʡͼ!ONF L?\\%L<0*Ig^'O)"Tk0SmtA2k5=Jt`γPf1#ߕl q #`D&V\^wQPmCܢ9kxI+QKm /Tآnr_,5ڥN92{rOVvGC"[<߶IojRNџϿ)rC2 jC<A[.( ]ց/tO8 z0M-Q݄b1LĜʼ0 ,R|=JK 4 h`OϱQMK-_ HK#B1fu'o謭݊>~V)%͸R1)p%:šrݢ"o܀EfkVeD(n*[w./*](1\|ƕ~0,m FHdMyY Bb2a𠡽~YN ]ij-oO=dN<~>k̂C pVy:ѦTa J,/>jri5A?=\!UB LԊQuSxqoUbMhk]@Y(WYx0ϫfDJ9ՏuT=+HX3d(|4}fiwP*EqU!)wxCq毱^c\ tIϪd3ߊj\[ѕd:u>,K"# 3[\4:9`t`nɠ韱@?} AOr ױY:8<ȚqDt R04a`Ɣfy&SWfqz%&d'eojڳr+=ΏcrW 7¤]-5DCu +sH,cCc$iFuXQ_bcD]yg=OAzΩˏ4* Є=Jk p.s>A-Əb|Lpܧ1m0O>bQ/y8$K6VY5@gYFd6iрPmn =vr( g 2=1iߞ`'Y1d bloX;#aa"okWz(74JK>SCm VSuX7&'E.ͻ|40DBz; 9QfSVhAxzlc[HD_ _RLVq ݷqCv?ۆ>SLFaj܊; Ak筋 ?]$z߸Zxc:uP[ă#[馬 >8"+v~5Q[IY{ A=e<|tZ϶< ->\noۚ`L ŵ&[ɇ㹤;jp|N:j֫,T LiW@eCh[9cǛ 8(p9h{v4jPXMA.NV0 0l7'A \gxQ pY/kl 42G2; -Qhc~PE~ {4 Ty}<@eA'a<jSW%t,0iqiXSwK-z6ҕIlr9TpeN:$]*n߀eq2р]}*lK/[g`Y!ŇmS@ mL/&,iٛʂH¿ʘ0qZMFN̑0 WZO3h]i+= `͠L^HFKhU'y& I? ; mG>ߪ6 l 7޳جe9i7_~xJ>uhKAkQC݂×{!@[u(Fo ~@)<4d m_vlíDSIL|dshAzC~y8PŽ̀B}?O`<@uR%{?x./mw2!OrG"yP_&Ճjz$n A8ٰnR^A{0K1|%y")M-ST6 ŀ.ÏYw(yVv '=Yn4W*t4"+W ԉd'a ~]Qmtq :M B֗qmdI號{ &9n!#$5T-yJv%uj r{IZOUk" o(2|?f/ 3BT%a@Z83R+rzeAgy[s]6u㒗4x%ܙ!6\#ujQNT\q,'n$?aI<(ه< p(O+K˲>.AxLs- ݹe5tgi;n3ٱ^#X.k@ڕ'@_۫dz{$H}f|^fF,`rՙ̈^oI<̹sMj5tS8Jmğ]|ۧ3ѻH)UL6SƏ,QL`c=1kH~-BYeD(V44g;OZkgqڂMLfG(6g*l6K^i&D+cH3Nʆ$_ ;^@&E?9&nJHxwplq&;`̐|u_@>Н;*ğZ}1x"dXDvi%q$ty)c>% > oX_[0RPmUt!u1^(#VZ-` OB$2z;`W(]*k#^bWػ̏f/z=&Cxm]!b`WTRN=);ȥBbگ!9q.Z"LHݝ8Τ ꐛ݊_,gͤaE۷m\&*Z^cVʬTzܭe :u  6 IBBjGkfM"u`A}M11qS~/Pƨؑn+:oP0! 3*NѤʝH`fխÖb9>eh=(5F2/_57]>=f~~"3JWDar CFkOZ.|-#"XW| i /WD@q7(a"}FB8ڍ;-bu鷇JQsQFAj 9 $,SX!q8f{YݿP<5yv;s>O`¸ \$b 0ũ) ׀@ >=%Z˺V[t] OD@!,$\y'(9Ξ(-P%zOr^qAe-W"~$S;F? cG5(44>l}m6ȏmjLgѓ+Y}涵&ny855pc]wZKT UQO|Mz=:4LLCɻںбqWc&v\zj"`6Brp :hbîAvmi/T֨]E^j?(i\&PHE.6p0Wü>7uTؿC6=ǘvCv8ˈпgi7ip{l/3 XFSV'rk: uH/xt BQ˷I$NT}#g@xNδ=Kj.D!MXs]`[tT}[RVt{U^#*F%$ѬQ{DY04OLw|My'O }Rܱz*Yh{$`J=?;]TIQ< 5>Uo,5R [ӑi+L>F DoR #v}4]$b5*ti'RDM18IxϪG5_E[m&Tkr^y2=W& 4mXJ653(pr@V(6|v @Q9V(|~{kYп5I%YK9uk#Q3Z^,wd٘lf\yV1Ont~&mzq <!yӉ%h0*"O걘7dW:*uowzCX.(NF_@[н)Y,?Z;ftk%>{5/Th,o{Q駖]b}&Ili(^WX[ etdbpplU%CZ*ׄ3\*')uEE̽Roٍ:Z"]@j.SwdQbp`4݂rQSMusbhfVsܐ@&f\/Em"=ƈl&c gO#Kc J` 50y$7<5_͈";"M0Gx՚09!%Fr6}dok=$g;2Ŭy8) s(A@{2f$"c|W'ͺp:+^SH+oPME3y+;lX&3ȢєEKw,,foj`nS,@ۆWߌ!6Ǯ hu8A,41" =Na]٨6ۺCLHYD'D5xPo?*u63`xxe&0Q*3Bﳱp əvd6ɞw=iMNAt>onYe7Ֆ:5k&,]<J^=q);]Og,mi| &k&ƸPGFt=cp| 1t``)J$p+qŗ@q:ӈo~`؝m6&k_kb- N -9 m/zN+>[K >3 u',@^.8І+,™ Z}{ /~`z̟G.PE0C^5?D?cIV~ĕ:ϓV^"72BtGX}{ acݠ0tnًGQt$TUw1*7/- _Â- I1~o1LN4KVr?pI2PPÂ(pĬXH6oJk>`SO_ē?ٽam<+5ܠb=Sx ’:%\Mʃ æI4YN->M,ީ8Q?\t}GV2'>FN I{XiiB& F2uvL_@ǗFRژ:urP kBz"<"W׏ P!|tI{82Ҏ\ [ Em ㏄T=xP_u.o pgRh6OZ& Y.QTn %~q|f (Qy(&xdY&>-U4߁&tUd%u=$JNMG 3s?\8x֡( aM*v53e 3k\ZH T:kXIhIv*9?8j=Smta2{9[R2_Lr~֍"ޭp5+"RSF@sZCDe { 0~VJQ ;] &PQrLJ`-2/=^ZvB$*Z{ĜN~[cGk4'|j7dJ=a}KqXR%̃:7']h[Cx٨YѓI1U nGKs1ԛQ ϙ*YxMNQN@(B pzpRYaU+πhdR;tW^,m!w7UVz*qB7ެ/£<6OOVia„ٵR=Zv2F=zwY*ȯyo<Ԟ`.j2&4Ic(EהEM8{`-+ޏV7W䍯פaywRȷ؂l0dLFA*>UN / :j qYuk,ko^4B[:奴n3ŗbڷw*g?,xg_9cpoJmVU m NIJP Q'HMy@TpycòZ,8D% }yRPv̑ߚ-T*ds4(/ LzoAtS>+(9B]-%)l;:җEylpl=Co7HHÉ.{ (<``goQ#E–Vn=[n^^k?N6m9 A}RѴIx~D?D|rOq1RIniˋ^K!`68GJ,S`&c>>og'~<Ԟ{'Qp2f֙S!׬r_LZc1(l;kq .w3hREE ^׌/|$2̧Pas ub)& ݬkNARƳĴjza 5'-χwWY\T#eO½ -@GjR#FIO9 ,:iU{v@]0)SnFuT+hWSHEClLw0%Z:P1TCf`⏃etE[^iR$+ tHsp tƿ~X4M < Y8|b,I <{!> [iLi糕ISe$xăߡYIJ0OUZBe\NZ*hISRA_ ,=->Re ~4~?)?l}Ǣ]P{۩}-Slc|E)dQCе37 Z5IarqPsjZi f*SO+(?N\2{4N0^UҾ@ *AR]:F7kO$h=p_W7цI6K~7M<7̓SϣӢu1qbm+bTm>~21F@mT{("IPKgs.һ 52@&w@XJ+JRu7O-j @/3r 6 jNSͯ&p)"պ1襢PL̃?#?BtlN1V9@ã04P[Ԋ3-z W` f6깨r;5vGWW/B/*[~)M=rl.^lkWmj+UNU QP Y{wdU'x1!, }wTYc8Ԓ78aOkzUYș}2ܛ>!Fo2QhL9YraK<@V6$N5ֆ E(9 ʕ@;so8W$k<KܬzrG<->:v-w$EѠ$UQWeƉ6-ECWI6 !bi;؜s';QLnW,/D{1TB>U'vUP+teHO#dzja /߆j|"txt0,br8zϡ/O{Bzx<Q3R(  rRpX󟶈"ʮ4ٸ$]1 Ou$D-?XZ/TdmP(vftSY1z%K~ќ(|-_6NCê4o~lAͲWhäv>:8B$4i=$QbFڿK{i569D;lrQkBgTl:f^l,-m dwuU:ms_åP|{.[U?W%Jbo `J9: p#kW\gq.C}OAƬ+͠ t1ۮW >TPubENG|Hri5VƘL[*VpZxNÛ"A'Af.)#PhHӁ$qX')1SApc/&ŵQVNsVG> KP-+Qk@4wIld+2HC; BCnWXۻWm[hWIQe 0uĶ.sQ9a "+aOGl)m(Akw%TUGߥ4 U v;?s#ߪy-K:}m6=+ ?2bdx/^m!fYr\SC$~?N^`s bÊP*e,-6*+ ̍ZCF}KɋR"ǰI vO\V ,G)㐆h܅-u[%z^ey++XMc֪gYʁ T#w34 ~"rzDgώ QE CoO8؇U0="ee&ݱ`^|xs)wc_wX'a4P_?z9/P!2ƹ+fX P G>b >/:A|u"RzJ!b97}[] dOwiB';t949.GC&%rBG嵂d"&jB]86;fayJdX˰>SC*'= *}=F% [pp@btD:P(n().p0pD{'K'V܆Xw@MACY<>1aV߬|) '|I%l0,m0&ffF*0|g0|I@j0|=  bZڸ0`n\掣T͘|O$(ncj|Rn|ɳ~oxRnf}3e .Ai:")[Q[H@U ӞĸeCE>% ;mmz4k`aZ)i2ifhB3z>M@yr0hicő0Woz m gX8@Zq=<Ѹ 8i,;ZƠ0jvÛwâԍދQ7m|[ fۓ EwjEf}^ "C8.BJme6"ܲo'_j=qhM&RȔ=oRzGcV%-4o:|EZL:P?ȾCD Ջfi _RQCOu`V*̃@Pb1CjN*Y}y'ǘ9BQzB41EKkDoGmmlD8<ԅU8J'dgw`2"ݍP3&m:Sg6!A,~@#f5z zzH"=q;Vnu&,Bh[@V2UDLIȼz緹ߘ( hsBK{$ZNgjB)6^2p'\NTEE #^K$]Q_QwQ-Ti@8QFRmK9tR䀰n|vǩۼDܚ:O=o~z{uW-!"<j7Ve{|+vVTVofOVHo50`=JYF9BI٦Mo[n*Wz8IX>\c:8SQPy:uP_ݽ%`A%ީ[jH7gpJA7q)d΄˚Ddܑ k~Kʼnb Eg " .6zo!B  "Ͽ8[ %:'$] UK:d!M#GQ. "O-:y4G"KEr`s4t6A E7'3s:=j|}\n<#o30lAtǾE4o`Xw_b-,O<)ܧ*x<]a9۵p?{*ej_RšZ'Dw-LLmAKP66:]| =sPw/V&m ;&ɨu# մy[ [5.ĔsMQ|y'#jT]gg _@ LGj>渞a6$U_ñrb؂,2Sժ3 $(0f yϜg"0PݘEN5ǟXa"00(HRW+h^K:5FU6ɱ@ \ aRZCt4cRy?ߺ+EMt2Ϗt4 \C4Au%g05]Z@7zA^4-izg !>(ԧfK 1}eFwˣ9v jUeL+:j=AV2Y(NDGd- zw}4eMT{:+^?a'PSf2ŬJ=, D@.FX+8G`1čh.QjȂQN(w5oO;eH)/33 V 0L"0d*s2}(]1FCʛ0N4D&MID~Lq, . ƀL$-q"v^|_\"%,,N{Un]^8S2)RP vj[љl I XݺgoQ'ߏIYЩ:M7QZfe=kq+Kj)NJuuor'Y&~.@^q@8LsWyc}md` /iI)'W[>Ĭ!C /}R̘o%| eZR@p*ɤ;R$z &0E,u Y'hSf(дg"{a ߐ=+(m4tq=>6 z ρ 'yU"c xw9Nޟk=I ċIqDJG($"GyoǣСbͫzi M`7DI7IO2lL?1V6O?!ԃgj[adh7>?š:с>߫}m݋ZjPF{Jj4K)Ay^ZP㙟ANPBɂ㏘urʷJbKv%◭>Vkى +hgɄiJ0vBܲ"rCr&2(4n }?CJbB{ @{DZ޻c:?A&Hk;RK#naJ1OWk̟W0_Ȁ C `Y3 "VȄ20uߌ-^-3ʝ`##T$]PB\~*u' 4eW~2jYDN hֵ3ADb_B(Kv5u]-6󒂜P>y4}O{9YOZfm|T7| Q5|kez㘲i0 `j\QoCR_ C{RN|1=u} OMOO%EQu&т}%8Pף'fv{5H$+$&;%j y֖hHccPBY2c'km~֍(P27X.Tx*zR٭vqɣ7Q}rGkQW_*/$h2n%>.~V<;qtAPnBqo{! %P7ό'vP8&ֹnJr1Vrc9oWY;ñD ᩃICsT*5 Iڏ3+yC?^ʭ$Sv7lsr1+ϔk׬HFwLrχ/ vFi?}+!>Ī#X>̖(,6Q;t'm}G8"3 vU,u VKop)O oYW_s#IIzqgU҆Q=1IwBlV Fs5PyaOȫ ?FnəMpL %4U}2:g *(86b|9`H&ZrPQ>;ېBLk>/ME3X  < ,C`X0k]jhł&A$J>,rN/64{N?`Y5njx279w_X4sER< s5G>_*@K>rWG3Vf׻K)SF}II [ X׈W OT2ݥ}H~,kh?Z_֥ 4'lA ,hxc]\+znЃg&s}sJ&2&n3!ߣO*3$7%i.8-5h(i Um9tO:8 ׊fI+6.d⪝㡙O1KZ,%&O&a{ oK6({Cn9F&YX{h!cdA(ټuUz˻laHIHF\ o4|3Amkc&~7\i8"a{䅨KH }IAwN:}wo̞OxOGfP [qQIGiDh/r~EH ][m CmsΆ[dv}{bQ#nk\E*`2КBRŵ&Pzͧ}i[mdW>Ѻ,;!6H[혿 tȇ ݇7r!JS`vK=s+C?n#dXq-HxPEKJMm߶~odjqeآ!tZN2"F,/gCF"wgvv_8&0x+֏܈Wy Bc\I+peY&i=4)//R2,[e7%? ^ 5ŧIvSpLԨLTw'0s[Fy }o7txGs{Y7~D[H~pQ[#F<>Uzq 3B_ւT># TJaLFl>O{:S*OvuV:2Js+YGL_O fŞMg-۹e~!%*q̌pFb!3 J^u[9=jy"L@NU=77W><ݧ"/GlGFK Ek(`c9(Gby%p z+mnӶ{H7|;.H=p-u\e>B06K$3@ _!~Of%ϻ Is(`3⚤/P5-\׸1r:@CF)LtpV-"@\av?}6[/<+al :psĈ%A[J8k碆!N-Rn;|Xk{zuaGĺ؏i0Ԙ ͡A;GPEWqc)lDlc5:vÃ{i@'\d̖5j"JǙ)_! 1nML*;~*YP3*[{Ն_ʵwg浐<)IN˔g;$?|9yo~u# wB:G]gyl)t4%ƙc=$44֔=v@uD2Ċ%1a)PxJ5BL- YQ*QߕW.pi@)(HZ&}@9ۇEWA̒;#%<5Y3|#snZPͺg!jesL+ vL TԮ5#U0ph "jŢdG\g4[6#,XBg2IP xϾQ<|Fcm016%İK\>TUxh^ hPͨETdy^w{(VNk:ԛn?y֬δz2ARn6`baieGR[M|0-kDƮGFEGCItv}Qs!4PEXgcp P$j(m^]kE.-)hvGAF(}h Wf5/%]I{/WdaJ5JTJɯ~; 0@lgm"CLzgLju̞.-ίw(#AeУƛB hg>Q=;6u^w-x 8INߑTV!hmFtўGyEΎ*R|Wͬ!;`]U cWi=*;x'xK5ЧS(3 ?a?YcVռqU[a0+QCFXD^W l `鳌ϳXi)ÿ@kӨdOZB?%/-4=VCYɬB29S(1:9[㉲%h﵌a7 H&vyk$FY,V[?#54 Ƃm%lŎ@h /زҳCҘ=}JV[ocQ'k5uTk|%vx_Vھn1@T#4x|FBAm$wB>=f{@{r[@^dw RwswO,/ >.hZ ? uJ}vݜ4aCpǗp]q)hͦĝS\kҩ}6TO͘ 1Mk;KƴE (D/ -_O ^kqPFB{9eb@+P*aZGe`_.&fYgȆX㓡} Hh-Al(VVJ"?8ۨK+2he-܂JXc|{"dO=a}M3gs1pKGM5YmfY4sCgem"&OP:Ɖ k'k}4][Cӹ)[~s-$/rc, 6'f{a66)Yp#/` Ų"qa5Htw gm uYV`tЩ}1,oZNȮS L}1ꝶ(cQ8XTluoIdġj9JBuA\*ʒМz/Wve̘_yX/!ث<8j?%x%ؗ(bB~(Q"-FXEiCKVsPɊUwDF [ Ӳ4+混d8RZ#ІX1rd3P~#^窜u 5iQaݘ9CLOq BŸ#pê׫f cR7Q%y 7{ [Xh+_SdzcBeiluď;w_`Sɣ3%&xzUϵeoLŊ鮙^X(525Xw:[yMryr _EXTZiAlV-*F*9W+1í2uXkEDQE>ZB!pWehY,+07>O#z(w~ԶI2B5_E}c$̀>#}oBWj'(J<Q<J2i+Ig,FH:vv\:қ=G]_G&FقZ*^8z%0;pz'#ߧ&ȩ#t:E\M^q`F^JV:Ԍ]%[n0b"rq7X\>3%Uήt%^ %g0y0 N:'́ g6E:ZSxZ3e?4|7GOgȫRpf wC6xIL+r _iRs z6(z^.gf֤\Rz *OCcrή?-H1PeAi<{ZE_& <U8dc?,A4mεsqM<Ł T Gs=N`MF3野 $e`V)/ G?zAhZ,a뻌).դ˶X"NC{TI C$FxL\,}a!=xޣm19Sv5(JF'KgCldS,7~:2ECs .?8UAs^.{"E00DE w]1⤁j4"EV\e@P NhX>! u~eOrf.A;]i厧N+96La`{u{q*ЈSI<{&h8C*gs2+jxˢTfM/B3BɀL N̲7l6OABؖ*yZO|)}@ҥjRPh\j3ḫ`b$@c | b6/8ζ, }*^Peȣ5Ɓ<59a ,jG=zc6U(SC) /(m?x(E{tmՓp"S`xOÄ0[dTw;MUx>wK+沱Oɴ.Es ɋm:xȮI 7"ʢ5┅3M5bv)3@!*C&u^\d6tk07]P9 MT?t*8݊QfRu9 i&mm! 4rk|\?#?H2H *v+X }0bFz#)uΌ}oΡB5@JO{g_P%3X>;uw=&CU'O|Ld%ʦ\3AĬ,5'=RGR|%C-wO7~4o609Eyvجs<'+8d@x[S>Jmdp<0Ѹki<{=Mr˯v49d5C'|3jwڡɡ)"4s GrsC%'n _ 8~MkjfoLWIHc%<-u`*훳 :*|Gg!~N{}4߈t^~(7gbQW?"ax'7wҋ<;T' Љgj@H%6LmGRviprVX%gG\v8f>Dg?@+- 44 ջv0UXU,i;ʙ}c)r4u"Hږg>ﬔ`p/9 h2C-f P's֜Q18xsܝlq?s;BFCZ.ϙq: i?coL1ԚOsaJpqPQrXUIjV_ѐ!X:u b> FeAEQHի:lQ("MZ̰֨{Pyv?K¥H2fsIj6ml5$j™aRK u]1o!"x 12XƇyR!C 7@6-"0ӴAU%FVR|OWO o\W8zB_N$n_$TF B$'X<Hh:.QL3l aH}τћfڰHˠzAMDًn12oʢakq%j̗9s3KזStcF! [㯚2 dߡ@UxANX* ]}Hri<77r@m{@H\]H*(%4zc YWP0.6#{ c&y$SC !LK`݇aKhRL<HrRMfZxL-+o̓qדkk[ŕOIe =:׾/niS{+P1@]zasRQ%Ëև&r9)9ڴbR 9w& >mY#]> ]e֪T5D\_b;|W?ye^w^CXy~Wl⾪ Vkގ5HcQ-:- "dAmZB^֭{>M_DWP ! '*)uJTLKdzT%OVikN॰ %.^0O:ayb"l`O~.G|͕H) nj@f|y639SZhf d+(Co'KPt 1V{JF bnXNUl kVnY-7"l nP yyU9Ce4ٽ K)a5kWгM^lMa3ثs޿Yl[yK)5Z dsvP?tr{dAo ߣS͑O& >\sseky˧ޕE /{[2:ДGPehQ&Gaӆҹ":7'Ab*'E&x[01\-oH6 \Z!~́HwޟH6GYl**ښ)pLN~RӷD!}0VROux.:.3 R3L`O+;D/k5)Ϥ?ȺŵLjUy&p{l7F#KQ @o @7sĔ5c7Unh4V IlŽ5Kp(Xh@D &E0fXS$rν6ϠPy@C| 6Ѷ[oSoe _3I5y@^Ys.5n|"D1;zaǨz֒組Q!#\oO5KP*Sqi'8",#g@UY  Hkv ~ʁim_xqx  M3z~FBg}7ڲuqMs[sw7S`:>\w]}}2#49F@8p7[-HUY$TO_v-f) ˹7[Cq\Db:M"I.D_(JthGUrUU:t_EtPtn)Êjo(VRE}]Es6ť˳ϒ֢r0-?edl抑k>\HuflN `v[n{G˺֣uOa|U\3U"Iͨֆ|+F*-/؜pH*`q#c>#d5Dn' 'lfSv+ lPqd8TU ovdD5{PRx]Zi V3G-U~kߒVE01u R'GB y7hV3pY@Q(\g<Sz`T#{<% +nX7<qmUWFw۔JQ 2'zi;|D-nvNP$է>3:-g;Vu` M0 y:y3٭VrGvPEp[}@/yΤǟ&,y*)|Y2Ε~li !I3lO1l4-\$Ę_ 8֒IUIB-Ĵ]g 8zvcu[͓P3g"3Ӊ&ҚI>rƂolk ~wbth'7 IfY@*u}a0v)E2-F 3E8ܩ'l9|+${A7%e&d5$~ٵgxdBoxgiZN+0?WgՠuMҘ|Oxg6KwlbPOٻNDsϳO2A-GQF9~գWj{T YIi[$I'86 3Nqꉺz9aJ*Z>algp`8yˋKY2f2jCzNe'OA6g9 T/sr>9Rܠrl_K 6K=QWMr:=E̵7N!oQ>229Pi٘ A !us1xO/&l14)  "LdEЃii ::II#ӼwAA *vAm2T8TVL%ՏsA5΍a;5ȇچ*'CkޫD.EvdbD'1Ckne:\m|w)HVU²"Qi^Ё-dIM[!T%3IXDоQe֨49=V%x3 H&ύ˄WrL={hKyxQ]Rd)`PU[|C1~zp~o6?no.isO/|j.))x ni Y'oa#Z=;"."1F,"ꞏXEr(Doxe"`+uLnnnI!hC a9h7AknH&:'S(T|Lnv<a4,xW,1f!kvC2X |pzyn{kxE-Z'" ~f X@}ӃR2Za ӑzׁ`'4i&Ci3p*VU81VO@caK=P 1֓] &]oo3{ IȻvdST3.Wz71JUgݲi'ӷm6~8Nqk1sx' ~?zWػNwaV\@*T:^75Iv7pv'Q$5WoQ~\nv6ԢԞʉ~F7q'`}uNes1I~xɀp0&<[spK$ M_] \$qdWȇ^g۞3R;`+Hg{yH[ކ(4͍$\GK*{*zV+])8qR/Vbk ?<&&\hB=T7͟\]0 I}bZIN3. [ikVT:Dl9#v%h6VNEDaԶ!ov<-] ϓp=SD.vn%'^>xYv%MDSˀaq-L4ZXԱCVI% iUUs=d^b<;e2et%xc@3NC7WEqTf+d2L ?jDa0!EB|*4#{ͷ~=UA)SejX srI8F _ 6ht`Od14owW"Dgwe]n YF6~dv޹kDq(EXb NlᐱNqDJO/Cc2þ7[tnO 0,?,Q!aIrJd] {W]K7'2Ή5X^Q_ np^k!]LGMry M釥p@l+jzp^w]Vm q IR> \orI%+"U-Zx[!7f#H~"oq$0Nt(k 2WZϸL`QJvy_o?dY)xguo-V@y(T x-d[hFҥť4tU; N犉j Bd-5quXDe96n)Q iܫԚPLhe\az=ԕ?#MUߧ h 9ӛmC Ub1TVU[a*@uW?/8;Ve?FMHh<t;VcuBE[/5klW3lv1x8x|YI7ҍU$MEQKdVwSG G\I4[S1¡r1wv%Q9lCS}7$YTM PQ@9&c3J mhOWHBX:޻P.]lI0caTgGW,!ci[u @ MZv?-$#=0 ?bMN.C O}z/Jf^'b{?>qn&JD}kYFV Xy) يא-S ɋTl@0٠*N"'@5`%B$d* 7WfyxUqt҇4^nSlHc٘Y#rowaZVо*"X2Wuݙ9F0:,cdžV;eI4r&OsPfMK'AakXeÞb;@Xv1&ol7()=_J|kT =Shj2C2'Zg9DwRp E~#Ի<քP_ q:JoP3׸6ixFi9iA4|: 4׎6[Ea ;$o8\!q[+;:,IwZ53jVL&VI>@tOf_dU T=RgG nR|UY(V_R|e!MK> #R0sz1 :7fP-{6qXE.H1m:V$f$d*h|lzQ|8CGZVc2"Zf$ْbS S~ŵ X*f̴ <'[QXūUh kM4ˋ,5E-p*0^N)e_׳Y^۽B1(}FJl1lT zc{5xkX-B Rc3Au2#10i+[ȭ:'cTHюKiKcA}}zjP yNI&06 Q路ջPXh0:WERTJ>vpmMϤ@b; ~ kry.+!k(-8b{nl6gnR̵CBNH-b3ܢT[5ZflY~Q Xw,J'}{c܁@A-Mh&]Nfo/zUDh*l E5.Y %7frʽp!c+ ,8Wjwglrc:?C>~}3Ge̺5WѶ%kFr"+̯"fF'm>jAd@Y']Xg\p/s֙WQ8%'/$Mz̀ڭAXcIx[^DteLp {XZ,2Lrcc Lf:ae2b (`zգBSq#; cT~<ݠW{!,Ujefz͕=#Vz}3eAmp%`Ӭ|mM-"#wDo%5gD)fO_Ew.0@ݼ2V +'xH?$ NpWgU*tǷ갬y19G)!dәNFg1E!t |OnrݶxQb>XOԵڼ8:QVC-nk37+E©C4P Gz)Wy8#o獲vKxUF'5NG+.&ÒX. uǴ.WC'q>5bz#V q| OdىZXJAXV E]F'j6uO'@\"n._ iqo)k m h_T=-6ؓTٽRtJ e#d݉*ZjNxAB* JN9qdK3*X!w91MR{FlrMgyS.>m oa"?J8hcJF}1쪵7Ƅ+4MЁ%f ѯ*RV! g+ ^$Y>s5C(*˨ɶrVW n#]&ziNc+VRzm ق LۚJ6.Fjg$lkLSuA+Sr!G 9Ӎa[WRw}vkcOуSK?fVeO+,{Ήf4 6򥍡=S|KTW/D7J5n_d28>0s87l_M=CBBVjש7q)|7532U(eLJpѝ[ IsegsseYز);M\Wvʙ<5Ū12[@_G&k n $89:8 VǡPb*IyN^MqJJ(g{{.l>HM5MO gZ2ڂ:yz:Uΐ=^&ǭ]&64QUdxjm eNR,=Lupn4 f1Hrh;J<We\s%ߝ=&Wgvnr8僿xM_F疣Jfh9r7PWx#!ô ;w7%>t }%d3\αva(C\n=suXt_v҆<q!nN ʹ{FP6Kp_]* ΊD߲=lG8nčnDè07}Jy h?w3gG)k2,FRrECW!.E:.ü Om7u)ԕ:M#۟&(^|Xia_5Wlp58ўpѳ$d(gU(%Y.bB|x+= E?DP8 >I{p)a$ }< Km/t f HS?\ ڑ|ul6?(jrpfpEDN|pol, 9~g7ne txk+df=#{.)|!j_iW#eP%Dmn[|$xSVL%P8ycݫb / q6練oK!.LE.qygXFoz(*Z7梡b,cG8z1j4 PunN #]uO!D }QwpO-h ѣ+7N=v)-Nwn=r*MF\)m! <%X;dΌJ|R 4i/ 8*Hntlc}wbҴHl zEQ,iרcg6Ip4bˁyFQ*tL}RJrYW{[E8C8ƲS0=`yCJ޼=\y1yZ,;L r*N/}P*9rx?FVc;eR#wv|`Hq#صu E!|u#lXYeGm֙/2HãZ߳s;|`sbH>$K' S˧?aANX뀴g=&ܲ9Rӿ8IQ=J/M8/ɂR # 3=` Z4byNJ HHR*;@xxi=Ѭ2%ӆ@cRӊ, p V ]sm]a>VIabC{8LZ\@Ƴ:!q*9 m+vug0Ϛn[fqeSSP|xYr1) (.ŨG3`Sꙧqj)K+"61T%Dv5U`pE>cOuLPUKtԃ_W"H&  ϸöl;kBSW5u?(`wG_z2f6C^Фa%5G:3[X b"q_{iFJFG.We`&KُP| ):vEqsfUT&Yf4T_ݕ\A7>b(Z2ƥ^,b[J#-x4y AG9?/hvqxfa{VJ"m![؈q0ށ#gֈ 0J02LVrg t+VvoX'#^)ыv$>DQI 'P\[5~^۷*+(t]ispY,ӷH17*Bq$Ѭk+{Bⲝ.IK *?Gʇ-0 >FSG3zhbnb3rۊ}p>d(ɡuV1hwX_t"KZCs3P(Fה J{n|iNoުgɭ[Z,bhʬ!~c@;J7D|t]CdS:҆~bh;v8p1n CdPݧgq(C. )H Xњ@wKBѴJ^9GV̝gMu5bs>ٺۥy6#ܜ0RFK-}A!a <#nQȴEB+걹e WopJO(]؊6S{W G&Ԏ̕᫵R"q LrpCꠤ16~D4|Cl{̼xT\Bj,#΀fPUm12YbMxϯ0N$if*i;/_u}($wU% 2y`b}cu e)8%sjphΝ=x vQDݍ1a[%[G:"Xo@4Ӊp}Jr$̸ɋw [ȆqL߬i3+}7-}ܒb dEyjx_&q+_o J֋ ,'$4RU agTshVMTot(:=(,8-̞ p i 63ڻYcSp"!c*B ڎ{R;LZ?cߠ0#' ]&D'&$4]T P~v),U`-:16Js"ԐŐ!/O3 Zt[Xz]i4;vygR3WJgtA,?L]H7dŠ]5|eÑ'v<5ԟvt Z{aDFl)Δ[LVQY+@")l׀;pigH-$즃.ysKzz#a8ݢ?6Ĭ -0 dRm]j2WTu>!a5H/"'6[Fˈb}quGPOM30-|+H' :jI~Ǥ 􅶟2 Æ2 VGPZ˿(n`3rtS@ QV Wd|1#;?mCF["/dYEvTH`&FLX7.n}6]/#ԽT)h8T+% RHea梛w>o'3Ւoe@C;qm| ],0_oua{9Z FوԮG$7|gIiDᧅޓ^4 FE%FNIۓET1sjHe4֪Ċ"[!o!"PMlx\lZK  zWz=UGUzC(A<5Bǒb;%Pd '!rdLi<`n藀zQ'DB=Q};` 7!{c[:I{tD3Z-0ϡNpXB[n8H,tVf 0Lb,Q=jWT޲!.<:\מHa6]/_wYJp3ǬȚBaeQYm2MbrɷD9>kUk,á8|b479pL.%/j0 %F#pe,&~ЮpJF<"Ɠ KonaɍPQn6F6ڶ>%9l@M eҤɔH ,VЃ*F6RNޜ,%\,U62f2+ iGoJ:12 @i3z} z4FJi%/6i\W -'qa,?Κ_|D&/R7'e8!w p]v- I|0ٛ'UXi4T kqo)'? !( )&# 6[j/մ8e`vKп'!5d$=%zzM3D$ѿcqyg9`Vk޴ sc4IcC-'f[TE:޽?>4%tbfUB@>bѯVP O_l?dVo`OESG-w0'8p#o~oBίFUM)o5/¸/GLp1$d1Z71VJVya`ÀƱG>"/?:7^}Km !_> ' R{KklIM;VMLwsA2]<Khпk͸Ex8E Nc׊*ݠ$nD?}M=$G^&QGO(Y pUF%oɮn6:آÜd?^7&M[1>.m_]a$VwO`t_L4R rƎ- X0m~TdE\} ˄՘USbV%6Mz؊KZ RsS2G\<+Jl7L)'? ^eW7]Oύ ˁSs`Y(3w@ X2xthZwj9IQaTfה!L47HzbwJJ`7l;A$k^e_7ݔUyr t6uBް2 K 5K VȼV &`Fa|ñ6$7$5,XC+珱 xERW'3@xg x996;.w] ^ ohCDnȫHTS_w2\6PfAF& ߼rn%lf%@qƙ%P4;u: C#)A/_P# J53f!9t Bn당}(eTZxS, gweR#$! 0 hH5 zUy" A n4)¬>|yWGQG}&gKG|i\D^Ю(W2dg&@ƣ4rNIquѱ GN RLJNSDubiC)`GA>2yBBm{ɟX<3di{vY֪Os #bkQ":M|,yf8ސ+*BLP^;tT㽪7Tq?yt9cmM 70#bqmמÿOXA BvE:)a㥪tp؅L9&EQeC^C#:5)zK> RP KhGbv"=tcbfpT=lH=\HX|[;_xFH8vDZV[ZG0P]g>˛bcaя^6.d+׹?"=`IV"^rYp(bJҕTkAI(:w`CU&} pͭkhЄBUB,DxCBWK:C:.o'7o[utޥ{o@?0DQ؃tnSCYd'3Uui .3|5GcAj1AΦ|R DE7G!)3#޼ob}xj.';J=rXTq|)fM=*B6 {f$n* ' (x%&a}-Xͧq"lNqn-K,F4. ?T<=f ǩ-PON7a^c1쀱k5V=`eɷ"--5i*bNOp3F;|S$@OYZ!<0 Jo>/Ӵ!UFU8Knj::}R0ja*^~hjku~R0AOvpcN1[^%?mݡo{0#{4q(zS&\n׎Uu>Rrѿ̃#\ V`dYI;hN) =WbȔۥ`xs僌jKL&R3sM(lY~" }G3A2RAl4IK'WɭN'J)Wtf#(bE:xR̢v y?zTK> @Z(NM*'UPQ *~Q$-83,b24q_i~8Wb?(gU`<{%R:5]`6ݱ Bv^2dE+)Kz fey@7$nWL?T/7-|jyoV\V5%e_Ԩk,P@55e3t^l5y ڷC~I G6PV9pB+]Wϊx&v7.EjQs̠I3*h(ƽ~CkTKfsJoeW,޸ypSHN/)l>U"0("Ϲxv40~E#yCjrA+Ojgrg'R?|E,Jpzq#y2*0ĕG_C~+C&"<;5W22(Tgtrp.uXtyv8§k.iQb4Fj/;1եc);)9(xt*1 3q2UޝV ]aۊV*2r#$9 ȜYݱ30A{P@W]-]|ɬ<[+66t +t>Xs \- <M6<L9ՠ؜ _9 ?Ky y -}+UtU>|.hL w }˱@4$_C70P~JR+Cl;@S~Af%a;n ŏ F:=<X}Vu"`\ g ]ess@ Zs dο4DD}d3%Yx1n}:-ʾ6SsyA5rρަshoDBJ KϘ9ur]#CH7\ZP@1_TvP.kL]2Cis)+<)f0Ԋ7eTa|AYL0hR'oYyh߉-Rt5O᭍}>)NH(^Ͼ~ `*VBLF]Sy{;=j1y,OKi| C򱻙2` Ə3qC`LtKph cO2Y7(oKLb`:8:eC&]3q"JY $K@d&^!6Mޥ:dYZ>G,P>@-y2\WAWg!@BvqF; cf[qEs,Wh7i u,)ar#P)5/`!¢+TCJf~@]ƟNFTdpMl(a]掋[ALj.PJrXXGƙBL sb bPٵ;h~tH ߥ̍$qTHEM,_,\NفS_,ꃄ}cpdniߤ,SJ[/;^]10Y6 #!=֝vF0 V*EhI>kL|#}N։5&CMٌM`b J{稿X]1iOO;X΢e8" .3)N#B%='|H;8c(-:Ja:pg@|fGKlBZ8~3Zapc|SBҌ1V1S1M&*)Jw8v6KP;Md BPVI%2{"9r(tϟu>?]]Uzb vg"j xj*!UȎ-.^ 麙[xXYguc5ͤo]6e Ggn.HDqz^(GIɈۓti1 VMaוoP.Z[\?A!.{%y@ZExך×T*1p?N}iZB4Yg^_?5- `X* `\;}kGU}@H:nLGr-J6QAJ jlt%I:By.q)oC',~DKU؂ܠ_t3zUr C`dpLY )CG0c:NOC9*Fr6"'T}v),0 +v\v~i ۽s(?R0/kԖ%xrug9ROI}B3SLS||"&, MqE%U;e[j2#"1&#Gn=Ku9/ k M)ٓWbxvpv؋d! wVg0LӼ [cȼ}M⪉?`!sJ t=vgE TcͥjwC ?M#}tbh]D$ 12MʨMA1 МFgדahNn7%ڞ/~ϛ~іPe)Wڼ#&~k8fXyæl&>U2)=*G54n, 5X}g+ enVS RRBS.fz֝ W=f L{jOE1uҗ߼tÅMu;YDUpp݅oK~VmQf\Z'3|7O Iރ3@Jyʂ|kYX-Y ?83nkԵ7L>)?1YJZҌnnךTI:1LÎFuO.ĉX{~wN#/HEjF!LTq)Wⁱu4O^NqNWZBSӣ:|rEspAFӹ#\֛Z$ 0x`q j,9<^(h҆(`Ns9tfj5N$Z; !>>=-B.Ǻ p>S!/.yT CMm`HBe !aJQAW(XGY`V~/ I^5*&/"FW #- AA=gN̰JQëD'Q˒1[/WF ng[ǏT0)vQ1"UfE#`۝Ae)ؿeƙ/xf>ʿDZ"? M].?wsvr +o_I۲b,fuDm4ձmC]M[fiFmK'XK Q}*?SaE`nh2`K[8X?A1AM&`7xw%ʩ}ٯLs_ycP1ׄ,G+s~TZ{sع&+(T|PBm0.vΙ?3鴣x`Q'ۈ+EO?s!#adXLPX$$5Ͻ&> =lGy>)8-&-]o\P[E4 tNӸs!E=t Z_E0*ƜcEtEKT10x䟦JS*]z4\H[S3fnu~h0!3b8nv8_M`4As4y;59̀֎ SGl?# eX] gD Y},.[TDX;AZC1-!X7!}gXIpa FL^dQ<gD(Dǡ.RFTsdCAB*HTTЂ Ι~M({k${p6mCvyճr={m^3}چ|a t^#ESsrp8MH:jG/LcdL_~o8xX{v/^7?:l6؅Jntj x1rөKҊB 9e=Pcj0%ugBJKㆨY)'#CٸT4趁Y ~3qh6\;/>jᆏeu2u ,CWׁזwiHU;Ա͋_ |xۏ=Er[?f!ŤV{#}mk_Y͒ 7H9Ό`7#? ?KtFp2w6.nMS5|I7"L)>Ǘ 3v⺗,dHh,Ysn*IT ]Erh&|~xߣvhz; G\5)b߇WYc$Ui0[+sʱb#N,+(ŧxgUOvv9=͖q~F t`0 )bky|+Dq S+O9RoN_c0e{b1FsDZ cvǗR&Oظ2lFqK4Xn5>i/;w,@ڄRl}Py,1[*=@~huLxE|WEܲ7T;rJvD\ޚRȠ2Le> 7\"_GCuӇ?k(:(S?:"/#du՘~͋uQU q}јE*9FMLݚ cglTR<NOiBN*QgA҇)^ #=ߟٺ xZEw`(fθEL1kaFEFP6P)`edz4+>s"'  AςQ=lTI9BDB*jK& D2rN˜Dq pXցNy:4ΔHZ㚉Vh?ws(A6c%ƹas+Z'|}QF0YHq/T .Tj6KIn55 vYƦa VZؚh%+.7'iڶn4IKC=:46J$iAw+zBU|b*M=#O4έ'|ko2]:ӋG=6p%e|^+ZOm.9$1k> ɐ*Q9[ ["y8Zŧu/P+ҳJEXh8| $R΍ܲ/SGhK՝2on"GN8wK >()U?aǛQS@WE2H$m>BjbLj),Y B~kތ%? duٮݵtmÝqX&UWiNgl:WI`@xKB:A]ϡфc -Prփ[^)=+uz/*$Adqͣ\ڠ=cA>;x?U278lWvlxRn!Lxϗ-dt+.^H,Jvhbp:zkq <_=z92ڪg É + ;a+y @e0UCiEp^J2Y{6";p.!JҦ^# c;uꪀ۸MkĐ㦿\ 62DTSx&kI9(JpWpM=6&^s.N.bu^DూX|\)ug֦9|&k;&u|f]G)wZ m=#ϊmN59Χ!jb Q\Z-aҬ I*#͙jR bYZśvX.2ίb˨%k@|.a(E:s-)΃G_%"Mq;'[* =ǵOC tV/F%Av@g5P^tmtZG=wX%V?/ک9I][W&6Gr3JRr_q nUuVNAC4G@E;-t/8" >p5`@[r9Za8&LYzrB t(e/#9#]:- lȰ\?m(JO@ڞ% ō}_J!Air jmls,k 8M5x!5;t+!8[ kdA2~/Z'FS]:K{4Gof[d~sc5n)_as?kI磷mvm$'/oel.P#t70~z|?(fL2_.At<G .nieqYL2b;GgD77<$rЇ!l*^t:)d<Ŷ,ۣ%.r;8ڵZH:He%MZuŏq,zU(("8) |#9 㙕m Ѐ`FѣY!B[.A izT#yVIڤ"0*!-K",vln]$͎ByxEW3é82Uhw WLJ;X:&< x;^_JPZ'mHcjgN0%!97? gçOJV90ݒjymbjPr U2Fy@3̃RI}jeT=Gr^c(F:E9맷:29HZD5!l+rY$K$j K$Û˙7^N}iÅrtfi{o&F0OĴѯ|VYT3 331hŻwv$dUծy 9zFɭgVjzHꃒhm|JdsQF/frYh|ƌuFy<;9TeL. >A*$h6jJ0$/H_J`&G4G&X!kc"નU+>bwr1k8τF2RC5Rߞ;Z=;[f=w&l!rO-b;ZokRhcZ/TCVFD~)L%W Zrf.8)ژ%ܲ29WzI ĩjO-Dh~vf8>s4)_)്L5X̷%Bs eUJY~,ll5qQZ~zUd>gPQ REA4pVHoY,o5)_~@SiA [oKn P1zc=~8k-(dno'u1AMngB@]L 66k%S=¯>身C*RGSQZ($t9Ɇ]\2xM& dk**r >hZwk,T($jKm'1J -6REXn3خ]"#Dz3[v˸W87>~Bܟdu9CrolIy$feNMr|S_:BQvw;rٍGTP6HqN<%3Z@0v3Ut3#:tN!, ;*7QA9h {`nS\yub{sزItwi`a($AvGى#4< nG1p,hQ3-Cohqh.9S2g5NdɝPuE+й}8UCD}EA@ZIo0U\9W>  Qܥ> >  ߵcDcIMKCТ ʫ lҠlEŦI>>g[/$9t n(gW%Ǐ"&}֚(qAK!.=s + {GTja#(l1RV=TC D`8<iqU a -Ft ߲Nd]o\<̤[Ջ-%!g)r)sCa]pJ30"#/lmw.r PFvwI]3J6,KU1ֳ6ez2,>;kyCbeCVΤXQ^JSxMFvڮ""qic@@dNc=RgvDR+П0&PD!QD_,p}88' ]^GC I=针&tdh3[P Ek#WS'`@gӳ|[۠W<^`$+,B-BeԞR-@=U1rzW%:BFo f]vzkҁzu ?r R=LOu m+2K # AQӥUi}\Hg}nީ,)qWuNjsV3Z d&i;N%lp!W.&e|gIX&/BψŃq[-Ïڋ`aqM@"k Dz呖ma+%j> uj06ڼ%c~'/z‰W L }!kMF*Z=4? k,*{ k M¢=$viEɓ~"#7EYWkoOIld//戟ژ0#!h Z/ !"-b$KOO=ﶵq%wFZ:9Of Wa8Qھ) @ѺV@?4d2lM's΁A☜!kfq 6e^eW|#M-qL2XAnoUuQ$"(D{sI_ڤ> g|,U^;a;,sM;76hrk+r?|O>N{3[]5>թqTG+xHpcia{M/odjiAo,*5kt+gAK}ր1xm:@il٨]XcQUh'3!h{LrW|S#Q{i5`0aRC0D4HxL M$)yZ}~T G/"o`BoTGb W ,t5Mx?Nj1';<gmEWqHU/+NиoZl+=jf{M<ɯ% =ȿz޹<49`WDvXNeǐe;3 f1MUiigEzŞLn:^? ԟk(jR]b Chbi]MDI?h:ęLK2 dY$?0gDOҥGZr'^ݐZkj3z~5`VA88vlKF"qڔ*fGėF܍lS8iR]n>ŷ_=,.0N, Kq/qwOϜ5pULy8mu'ƭkx;[fly$SY!PT#kBD?̎Iy{X#Ks8VE1GMO\86zNQP4JGbRރ,xî“KAv"9wXd ŝIPUj,n坯';CYoU&)(X'7ͭ;AF3^ȺI&u <)Vwn;gb^ƒ_-z0mI40T ?$!khS u ^PۃÕe;@ E{,hGQf afu`nq .N混8ADICew|-;bUV?~WN2B'9l3)W NbqDݥSb~9Q\=̈́!\{N(ZpR"/)Q+dK"`/r]PD sJPW7 !#[j \.} ;hcX9&Z)l\STe?,S=qD;*$T}a{L =񰽶?g2d8|3n網0i4ZMHOgLuv}D9=@Ŧ1N7GsVj덊bd#QɊNvQeP,ْgDƨ1}knWGDI; Dq[  \AS]$2s|q<4oD1 {N;@ȡL.Hc2 ~qܑ}d`RϹO{b&޽XIΰT 4w5/:O} d =5 k;E-*G$g!^״X{f3hچ?<8Má+*ZWQڝz=r;r;8T=ٺiG8_B > @VǞMrz'4!d=vc0?srwavx޳Kz|TwrOQDÀ! R鄿(k<1QIZ)l0's7":;'er_=Y*G->Q$1>V/*)$T_2kV S pbrXCS5 O; ,mMӡ/@DJ:| U0 SM̎t*A+X/%mf}ٚg I=CN4?Bn,,йseT[ eI;AZ̬*kanm FGݔ=+t4?^\W=H>Q"%˓S)/T BB" +/R0REV+me|}9TQ:-{dGMI7~Zr FN混:"|C[e]ѝ!B(4`[1 U݀mk%|\:WXybEsPt" 1h\E!|[DU ki0\#Ŋ Lp>h5\IEꆇU 8m/t`<q玗2gZW5|G~ K"'DYfrERfbk}<n8c`!mi.9b2K'u3MmÛ %s “ْzקodu0J;o995ܽ8>SFcW ljfip|e: 2p]7M'8SevnhQ KJQyi֢bi֮:1XG'?T_V18]Hv͖ժxCksƸ3i16G%P#{.;=đDZhŬ(^G¥?rE}/h[|#,9:w0-6MCfb䀎l,qnJ.m;%k}&Y_h Hb_:ÚGGZF)D[P1-P"YU&l7UG&I@chC“Pz.*Y!x}݅Ȣ篹?W%,7̹"ϰ0I 4Խo"kdf X%]]$PggWȦIYua('D9zi%;l^]!6,*Z6;yQHē 1/">3mc!_c$e`AUw6/# tU/}:/ ..z_XJyOy+{={YC-o/ }sr]z0L/^>2 @ZQL*PA] y13‚ʇ[rg-q7ƭSjòIgnr[S[`m :ij PfӱAzUs:5pxlMyL hK 6ӓ3b)Ew` ]cd3lmbqQ;1< ^30)34K_Lbw"򔆭/78Ew(srb#Y@z=$لj*Wg[oV.k{s4_cۂhGqQn)C{Mѣa\H|*)OD7KQY> I˫%;t Q(t~uuJֵ9OZ6>R>yVۚRqiZ 1"li{q6Eg꣌j)Z.$&.yM; <7)vJHZ%).>[#vZ}v.>E}(Bf <ӓ`6љ_j:~pIq9f<527 1K4MtVg;_[ p*-㈴p| oWyĘS,!_ t:a&pO%eW:I6B1V[۳Wv'A,Au(4vH2Jw2oḺYa:$?NM"N?{\*ڔ-YRCD$AIFFY_<0&vC.wȅ:_>jt/-#ޗXֻ/ʟ/,̫gìfL*,|c}L`Keehe@QaÞ3V>]kK) py%9Ci۠尉ã | X 逸E%>+dQM9FXi5eN 7Z5Em7ꉽň9H6À5]۵ώ1,2q(YaQل],L*(9ޙtPߛiU:R57b7N/4Ӯ8Ƴ*.,.A-> \HR/?wx:鉈b־'RC~;'4B{gRwhW-B4̚ϵ֡3]rZn>m$ h848Tވ0 sm} 8U)60qZg! _"fJ$Ubjٜu'Q8TWܞa&CZ_Et8W&OgMܪtj`A0, _cq{18/2,9W|+EejӢԙH_˚1#8@.lF*3Eq(\h]BJީQt೴ZFAۺJOoQ@R2 PΫGGIX/Q4?轻"A Koj27z`~լ:m)^3'/v8imae7K:pU((aImW$ȦDWH q:;/u^Ef!m;hrsb1H->kvbE(l:3E , B Veom4$De;`%#e"v^y A"Xa{|aҙ J\n;E9SE8yjߪÕiTI]gdQ̮Oy#kp/=69䍕cB%"r[Y{\_62wSN]09EG`0"* vv]QR[јC)v'I{vUi`-@xY׎i> c*nXȍ+C.͋(t*%F;2QՋc{v5I&ےRd^>vv&_gɉb06<\iIt})I,|ZO'/φ;#pW.Z ;#BxϹ&*4twuq^]g_КEs 7bXW*wQ f'MÌD=M;p /đTgf= Ȟ\v1OyUu"]"bzLØ:ux=-4Ԣr#i 2; ¶5f*ȓ>@):oEw-s:įsŰMvxL_;h'`N3[ģZpf?45Z;. FJO.'/ɡޗ̨#,b|g\^яa!r+*'u:kF\L5<pd:yS(5pS=> ӻbMh7mSkR1N V fm}SuK :_+k{I u:@/UZSD)[AҒVM%t Ar+Vw3:a4'P(&}{VyA6Ly3R" 8*ĪRh-#^QQ |B,^ZKԬu.w!>ÿj=G'lqŀI3f8n,PkO#-tӊr5]b".05ꎮ,yMIȀЛmJEIņ$Gb$Wc6XV!H`QCpr$vS.EpQԨ{rI iv.dWAg;ͥ܀m;<@k ͥݤ UE!2 #桌5㝪 M*x;wV>!>TWY>hU5t 272>" >[@ol)bٜM Vl5]M:c[+_ɔ@nZ8ȱiNN/,wjٔU׾?NV\ qUiLοM 0o~aC*Oe(D>jV'ۆ'@ŧAj$Qq0hl!FLKNqh\,"HgqkVS:̧uDyS+zaVNb+P&2g* 8Iym̛ +ssnWqBvmoÙ81qwHMqa[ixQw! m{&=n@\+v$n<(JM`3a}$]K256Yk QN#V'x(K2v×8nⳆwӥA*!2PaGseqHwcCgFgseM/Ǘ~rBŨ9O}ׇ:+cd;O4rpxğ^n6 =`89;س6 ESѴI;wR C02c[E1rEi(wʇдB]#c~V5Z~!1eq[e,/w}geKQ61,Ƒ!!RwHfaŲL+D!%{wh5$6|f5:&sހM4G0:g l#à$KW?|J$h"G t1$5!-wo2.6P:Tv[!~kv-gOIP7gWd t#N cmFb"R?7J[ \I+l5TF|<'3Ij{A{O ֏3P[d\;O/f 7fh'vEa! wxf@`-^I$'nQMA!My?WU5).2Ć^wANycRZ. X1+6 #XP;yoe~ѳ.KL8=UNΊEǠ_x~vap1bE>F:7A b)xދ-R ar3}.``nKj-qԙ9ǜwdtW`] Pp 3(7 9[W P}LSNSi1x1>-`=_NɐQv vIzXkE*bތi*upq[8&]J[m ?oݨ @8QaX 30@2_]`ms)S 0T$_r8^u7ylK)-(T6@ Ec۲6hri{1`5tϱ@=}aEՂ[\¹WyzM-v.?<LI7g(كn`Y”.G}EM6lW'/l( M3 /! ~!qC>f'{\xr]=b"(un> xҶe\ l,\j啘V/Gq[a,U(-7 ~' gh݇*XߥAG+K$K'8SJ|b57X. EoC`,"e:͉r;B~/v^P ƚ/#M YERgqWFLm51 n3á遅(i6sYفUue(SmQיr8@cDNtV7ЩG$w%W^KB->FJʫYvoƛdgRBYLT`_˪xN޻rT J$ihR/` f[V&f ' ́MqrxMڟxW+ҦU'L9 48m0aFkD3`="2aYHQZ;S4s|~48XH/h>c'"JԺa?駖7.L?t|-\֕c RueкOR!:_U)hxa;#B?;Uȏ Z*>XtX6j01J(DSҮ`%PȬX BeiPKHMlLN>M*^ί}j'QIM&Y+z*;؉A<(dH0 tupGKTjk@TnԝVvOOШKCn V?a ^.;7M@ 6cǬ@m0;I4O\)O_6"}ԲVRtwPwy4"NOl)wB' a '@eHȁ .!њhpuL#[aq*FMd77elm2[R 8bKAL˽lr/}Ѓ|Cs=&G I-4l:4xeͨ'V^͞sxFFnDr UeTRҭ.c k*$ğ7FpZ 6XS}\ 0&솀&ֵýؽK(-l#8VeB~5"o=";KaK/9,T1q,JDq񲎭:JK1·侹M ZQM)'`BB8hL .\OmMf oX811rn; sJRC¬ zHR70AcjM!za~jL>~'jY@8AD 6Sla{K=60h('K<|G_K+h5^Y&i8a /<掸 ~3sɥ:TdSo%I~^‰s[&4ߢ\@yH@߱'AV<3KX]O _~",~u qe2}IPN1V4]nW9xsd]ֆGʮwSx_/CܡۓеZ\FM%X}mCR)5Jsu%NyK}L=h%t}^:"ײ֧?f ^ ww[g$U1XV#lF[';HCt6Ovi"Xr0E=v?SZCy}wQ!(~wmqV<:ӹD  ~K|vw&Vlmi'^8,)VJ| gif< I5;NpFLvi,7TיqNM&%ⶕF@2KR*\?~tG}'멈'=E_Hjg[a?o 0ޥ% k!p-wuRFTއZKRyn ڬRGk m^c^6\-^ W h ^yţVFKXNr-w"[e4/F?9phj3=e=<@fMFz%xOhyKcUa`1 3M9 #8tf䘘cm|>Cnv]TqƜBd#Tm53rNASҬst1]FioHes}1T-dĘ}uFγZͿRX@$"~w.|Ȅj\sϝdghlq( x\'(YF(O_\a ?!5ft$& F5$}{̓K;bU}07nP77 kGhcWֶSɢuHY`1y><-S~`cť%Ćƭi)M{YFﳬ+]bm\u ?k'~(yDAh54:(|K."咙c"A2  3ӴI ^d_L$*6߃M@ vޡY#)G=PeQWF7 [ɛ| 4K @PCr' Aגm75FB%ܵOE1 K̦l\$JB|) }U\{2չ{ퟞ ,0ϑ:oMT}׍# =51+)$/`{& =@N ~|=ߍ&RNG.ScjK^R'WM>fVˤ2BW O@+FieWkBJ6n^:Gh`<ȿal~~.BU-L—*Udbv4< n `|_ .#ڽsp3rgU"3,I2%&\t@gݖ3&$`ޅ;PV;N>[5xR|ũveLTFeKOjs =c5&rM&9=p &7>C鴧o>I("P;[oځ_ey X,kfp o1-)EhKVɋxvd(cw͑&0&8ƛ^kEfʐ!_>uwuVJukoq'z4~T٬07. 6q#\JYޜes@E8|fsh,&<X2Ͽb4eX=$!2R?|7WH# YT^|lO`*AWXhrvI+\ZISK!a?h}}Wu;!7mL?g\]J9Jr8$eQAB eP4L \' r"} Ctrzv\!Pи0}74MFF|KބJ{mwz9EͤbCI\L>o`K` {P8׳ZB^뚃/-zgaFX+Y g<ꪅu!%8wmY{p+;:Hq8Ҳ8§4^ױs jQ*[ g1/ LϰL$hU# QX&XJ&̤K=!W %/r{450&IԹEDqr%kӋZ,o0V= DsnuTiXu~ka~4TQGTyfq~uk +°N\D\C<FhpW5`KUf9UyOIeggܮI{x TM.֭1oA]B&ܱs`u>TJΡωA*"Awk.kŠtt;𾅾rܟ)Hr*3 )>e{izNJzOfH(L1[?ç{cP%TH#A0a^ k4WG5 f&ֶF|Y4eܞ2@,TT) w!_//uΩco~oU)O U429vBLe_GagwyeՉ (YtG̒ ~z.)ڷ|J ֱUkVװaϦ2Jfc:>.;Vjkr{34l.A"zl Q[#Q'{'tw,`w6@0l^sAg]};^ :,d.DR\2)$M/fwW$T~pZ) Dz njp%.4;i9LVoNdL; 7e%P7_ݲjs?J# ɄOAiY]x%Ľ~IG\,2K]ɕ\E W_´o]|Pubi1u.;#z=:YHvәU9Xz i%~gH"){JcKAta]R?qhM従-j M\v*}|8U Q$hk(>$|wsxtְ۴‰RCZ{_c$o%Vf$1f#^+ uUM h:EX/[oN =fӎ}bWΰkgK~;t.c̀U/tҧxbFN3/aPi>ύP"8X;91"-5>] +}g~~?wI0!Uh7!{:!4'3P^1mYG>p{@K7B -ƔԾVU/IÝv42q*6 8;]2<+R 6Ҧy0󀎱tz|\5Z5m9Vn\Q} [Pf`ZS3ᅰ1jJoj"DktƂSI!n@T&K Sq_A#O P <`{h V29RҢ%jnׅq@jv*er4Âo2A$P&p}o]{Yexc ,14+cI+NyHePN멽z=ur[ R1!.nmwiJP^/?L.'FYN=MTEp`^0c갭:i(31e=/Şgҍ[;?ojy)j/D2tV5D<F69p[016Kv6`㋛[-*OiGORCfTL x0.ES^k]u(Nva@@Ik3V`ù^L׃j:ZACFIu5XBN'oms,MĕlëLo`Z3*OfJ J8#Њcg)3Njӈ/w njO"CspGqʖX1dibt\/oV^ YQʅIL\IRpR\)R B&8NEэj+>,9I5)DGb5x˝K( Fב߫[:P0 7r+sQ\k@yAu3!YP%u@9 g7OU=]osځ\k(L^o اv26`ljQHnZ:G@hb-6J M̗HvCN65j*qiYr.Gx9@ ݼJ|Cbz]YXYq)XO⤰0j?pj#2i 81 |64/V(ϦZn'Q.o띥&Pivk57RGBӻMyDEbYzdBpص[Mź5w@cDTΓAKFR^Ӈe7j/Punf`ApMl~Hr c<ʛZHLrGuKW} gCOS-\"$gQb !o̬8KAAշ9U=Lz-Z|' &pO5 g&]ԋwXpbie1 B+ob+mcڕf7w-T{n T2'Ĭ_!x/ɍ 8>b":WF.P|ě~-WĹ}hb/ekmmW9+,}a7I6J"8 :p( !hb1z1KSGGhuܧ{;F2hkP2P5{bh$I<*F($N=l n@qRFǘsV'QwOD\;Q>0f*F#rWwu.D%zLDҵUm'_Aj~m', .ca6mzj`T3"C=]#=_r wKoaY iNമ;:bhIti1$-Xq".)\Wa%.* /THP_vחFc z0m?73\u9uϱ&w|Ď!4 Y5eE޾A` q8;1bpx?.2OVrpe%aá5h-y-ѯO bDGcօWtf#9O$;{-#C`|7eP.ng\wG".p$5Bf&4/ݥB)97[TO;o#eMl*)Bh{79 =Q^Fq5hZg4+'j0* 4nV> >uoѵV|+Os{^ZH7JwKI&Qm|8͔NI؅0[=Lht.~ ~[q*wQA3kz8apb?㻭Gioe(WtpgG5i@Z,-\ Zq@3ݑGPfz( 5UlhitWWu=V5Q.Ώ[&EGT1cY)D6:(Y%4:(X**<+u7? /'v]Ի:fJwRbk='?ZTM8uP{8m H+v?| 2Hv ښz;`$T2f>ӃX6 U18;to8Kh$( Rn\CHS:'dTH@UʵqwQXН) $w;CYgJ/djcJ^ёΫ}νb)%7i[;DjoB[P~~ kY=;}CUaH oªV.RH#VpUs|S/Ȣ2DzT>06[kgv4\ Vq{.8w14U2_k,v؆#$=)hS`Atq\T> JKSʯczPox O٢YfmNo5?1V9Qz߈ ˥q, ǝjг;d tn0] 飷:EߐdWr?(nA6 G E2A}xJ cM)R>iCz)Hahvo0i]QFHcoy+slID ov6 ɚ` ώIۚ>Iq[w9[ K'Kz5M2ͱy`ӻ%ݳ eϨ䚭qP+hm˘h3,XpԵĞ)qƬcW,^J Ddo gԷF[=<88i*S 2~O]0 g;E@,gy2y/+A8J#ٗn}]G%hTI6޵: 5z)T/+[D  Ÿ|I]JHy͸:uCsu-d^l+0lP%|$Gi`48\74NI]#KɔF28$ iDŽ}#E Zc=`rZ.)UQFBG/?c#G2y[ 2}rk$ 3m 4%Cx2q )c]h$z@ a6\{e}R?~E5iI+j?҄L^V` \0;WdN /#zFC|/G&"W(=jU^<:ZRpk4S ߱2j "5DUohc7£ZJ^r}Zʦ=v>rl(/, O~ƎzEef*&yϦ>^|[k VS{e=llFJ!+hriV2oooN=OM(b\)&•4 @M랴hoTMܷ#o̼cO9(ZV|q -BZ_5A}u./g;Q?OWg;ܿhw&Ti ]] fQ˪-5aQA~V+HÛ_ўݾ}I?/uMKC ye9YJ pTRg1ԗ W&y*MO8W%tf,!TB)+_YbحW/8aF,y]h)GsloypĕU-;s_ 6)o+}f|!lx4]1%jûTcJ)gwZ7ۧ_5 Iv1Z l ©7oYSDoPk|½F>=M~x x<9?}Fh*`3}83fs9I%g*k|%z}!R/ʿܕ!:V'f"yGsxAmxnU^xWo+t,M KzceۇkOG̏7Paʫf9\R0 A&CF0}$1h6ֳ"eŸ /G +0&=us< 7Q:$ >('s4șH'CdvfH{BtTv&6lBPh-uNc{} ş%?pw=xdݿTZuE5^b&FN%tYifpxK{n\rn>1/) ,\7%ʲ!-Jo}T.]qUPXg#+Lojfi@}a0p>~Ѷ M*M񲫆bʖָ5F*|@7֣fݒ @ :D"Qt-|mҬ-TYӫJ;r-W- ㋇mv=WGsh7†mM劭Vbt{Ҫ䋽(S%tۮ(@~9P5n󼦋GګZ姪"rV$ ]`_Ń}?䑫Ujtx t,3ݷgVmL?jjr91i&kEu 3%N{K݌TY["$" +Q3o% ^RSviA " wpĹu=,]/h83SN#7ˬOU40o>Ċ:SwF1*Ao1D X'unDlrhḑ!_j@'5ԡcLɦax6Cb`//6Ψ;q0voV3oyҸEݲPli#E?7] o Ե_!p!-Ug~_Y[&5H^,a85 BʅƠ%O7{Fp;%v0^ւX4 i`87)f§Wt+1#$S,KZg.$1y 8phȳq\` yCY Kو j7NH$y7 Pz -T:hIe Qy^6h_`l~j(\X~d?7x< Ď_Jo߿)[m*%t>P+q"h)%e/Lp1x=&Nv=>iOɔ$ZD[EHm0Ch5IaΈT W4k.L=7cEx}hfW|{TⴎpD;+AETmO~ (9p 5iU @]Ac' Iy_AIzB>^1gXs"$Cw84;R.B;Oc9!! +=}6dI͠Ò]vaGZQC8f zJ#O 9ɃXL9,a-.EDjvx{<Ϊ>Tyh,Jf~Ra}:5=ωk*лøW8U:$ƒ;N;-{/c Betd՜,cvJfnJ؞|+ezhB"X_D_#6G9u8ub\IQ71+u3#UVgXyk FGҧe#{Ŧ6LN?S/O#7hQb3V %y].T>ş0O@s`7SLwI%efq"ƣgP70tZgc:;'6u/$ UCX"hbhv8sEŸ,9;3"Xɯh ^=MY% ,Eu"$K1\vsƼ6U=ۦrD{p,Y5 zSx~ P>cB?xjlpG]w|M:tάZ`"+6}Osj7# @p݃,hVN_^Tn =<^@t8H$;DPN}8i6 vozjN^פh:J:xC5kerޘD]_/gt,(,o$ׯZbK4{/:]q6_Q5 { O:u І? qnNjl\ٙ▕ cGiIL&gilY 6ójL :h[VH}^!:q>-kC eWBA~\k  uhC뻄fuQg߁ԜܦX @=BTPyiE(= $µ8 neFJ^ H͉4G&%뮷˜"$W#.bGbGPw;p{▅5,^WT/BƮv *+ajO=^%.z7 69<-*u7V[b-<߻$kШ$Ywr vΧ׎*zV&KZ(* jN6*?%t*G#j b>NWXPtu_4b|Afj*޹+}g/BCYjT fso)i'W NqaZNz3nOQ__z٠{,?]J4Q̔:aNl;eaO$f#:I&t;KSՀ3 O=OAdD=I̯ZeFӁiӾ xkqCLz*b7r%be8|@%_T5)pHG3gF"DG4q\}cҳm|hgd?8dc [lmaܲ5O~4H0 A]UKHyL,]^ԒӇU{YbX?En_kM}nܝ^^/E&sA4_" EƑ0Uwݣ:hŚ#ZH_.c&DN\0VHD&bsA YnB++'YThEA"Pj7PgŋWls<5@p ֐Y9ب9,,a=0~Wjn,2&r,HÎ\&D$2|w͙uXFN ttVZE& nYLƥ gv5銋'Cʢ^`S80\ Q%9 tR+6ϭٌ˫iWnk':[\ ן&. ^[2N{9pQg;)H}droe q ,|E*ߩd?*6<ɨ9MC'0śp6n՚atުf$'g%]=_wɇ%K*J]"+@/7:vE Q5jp$s{ȭg̈]E%8hi] ǰَ~py܁ kah@Dό\)/BDt _y#q*v-LÈHA^B|ݵJ_IICcvD8xwUM}*&7[sm녭Ȧ{e]HEkB,i+ſMؚ1g|}Ө@ۓW0 b.ס`[9 9bpt@ n'5%T` n %3Xg&ʰlV4]t),VߓCj,Lhh@n\K/svu߈*L!&_s ^}Ly\F-:I;m f !z]5Rڥ\LJ6ߗZu O>*0k byK{}rk§8­q$qWm|rlNgԕ%-Ğl-9ӷ2Zv)W, Zvzo ݜil でnFjV1vF;gx "z4z'A/.8ǔX{ U a̿n^dO>7z $ rAD~QVv7",\;wF ,8HI1(PƷ =Ȃ?&|E'a*j }TE ` נyYeN9F.Osm5)/ T cj.T{Ǧu <-g+JiK!יjg8і/7l\? m;}&- S7B"$.>VWAG~Eh22VN?1ǕoQ8ct_e& | v:ԂܕN^fާ$^ H ķV~\(>˭qͺevN q٭*GP T.!#%-6Y`lĀ&>@Tܪ2;;۪`3 GsHyKy 6s'O,@Z"ȑp d$ aXZbbv֭TsJq0O80{wI[0y6QR׼mk@bm'?W=㤣')/^}HO6uʥR!pW(c}R}wyP?nGjUG(cl,]"Y.T|5đFa**2?0(vdw '{c4]l̘?ʼn e_l{K;SZP j*^VeG/Oݳ-R֖dwomΝw7ѴAFpr#CM{վ`P$%<dy.)枼G _;0!"gaCɸǪ;ᰏlDC?vKuYID"*=UqQ5p%^sDfAgim4s/QD;8R1 :vK2(|{n$5*wv|bHBbcͳY"`3t_LMs} ^ͪXoHh8)Zo)QYҁAbwPmn)eAk_`5Xf扡2gJq%H;н}]WZS4F1sÔF#! |[˼jM7s!)1ֿ^(sڟĊTsIڌcOC}{ վM|ևP2%*<=~JG|_a+rO~n7WV@o,Cl!'uLo'Fn&@HGYiĽ+-F<u%pM("6 C&.@c 0UuOHf g+D,lO!"}^o'CEV*õա+7Jɼےh׽'L2$n@(L,1fD1̓5w5 oe )ķ]eϥ^, `L `Y цA=6*T|Sa0zܶ@_׳J0`8z_g`snCĐו̷h| /9;ާT,BP;BlB%}VdD GOحFN5>98,xdE^j"oX*~<@ #3>a~-#"qsDv$7Ʈ=ҖAu!Qܟ'HGH<4r%U;0)o/#ux# uga*8Wc.hT?cC@E5noTs'`f4"smR -1!}; Q ( Ǡ;ub⏤"i~n, I TNH.UdႻs]O\qX̨k," I5\HmaS (-ⵁD36juϼykzXYÛ|:PBv9޿Zל zwL!;Sɽ!UiN*2~aJn qNNnbhǷxvꓖH&l.%Ԥ87^1`kJ(HтB2;[1U _ʂ^N6B0v1رee@{5i/,WV[4D螽Z+1b|С8S |穝 rh-Ơ<u(htdh_HU *:oP:0Rա$;rR+ryO( yG%֐fM^@ Í\bDD3O8_R6+8 pPL{٬yEFW*(1\Kv΄Lb o b*sAJ y! [ݙ2C0ٔW|nnk8s0z"4Eݖ (2AөC8HKR~ $8t`%%4r3R_ x-m!n?Y86Twmۏn1w-Eʚ" Q2X.$`Rtl|;w0IIJd:^*!XR^UG[O|sθo]*Y*3 ig,s]߾JZPsj Z~Y:s쟣OIoYC^Gv&/ xHR/@[6Xs}exy jiwQ3/ReҪ̡~Uhu F#ƑVG4e2 I@X' boSQDzq+`bI`N8ֵu~&ZOS{i*:H 3F raU[Oua"k6RܼS*mAߓ8E+{b$`Vxp^+`ୀ6c͹(~+j,&4q>N L=_g$$X̢% +}ꓦܷ~ )p@mI1<<6xߩKy_$vO  @QD ?c?Mx䳿"ܿС RG\W!NJ CpY|rc>+͉}p*>!`m ,,UKۑ_c2~r(p+uf>7n^YoJ )ifmW Y`umbC\D]_fGIlak] ˠ;H,gtD'ġVPz,;vf+?^;_<*6ނU allou, ZsчUhANłaHm:Tb Σv7, MoynS.j}M<)%s. Ʀr!)L`o8Yqc~~% \3⅚ G@D{Sq$dąElybt~Jv|_{{_!BHL`bMfG,s1KfAv`(e]ЬHY 8G.9'rźbff ژrZ rwm,/$OmOZ-ؒ)Flڦ%uKA-_?3Iy?xkf"鲫F-ٕO`FR5'^tJU@нGl SaF봎. z|"C !$A\!Y%O0}vMQ&GZg(");!J.{8(B%^/_V]=!yF}"yު >hiܔ"j{8C9{hGfs)r=.ƚa J;K.+ Ym/[]s4YW~<]2)45*#6ji[swi/(ŽJԖ'wp_qCvΊ1 G*f-:GTt"1 @s&eo.gP4@ iea"1 1mdocP&L&*Lmh4ɻ6XT*JeevkfAEi\՟\}E_[~UЄXcBI$+VfRBM3 i>LeWZOw(x<=S%}`=)2ETF8E rgfAmC%r)$ U) !!X1kaQ?m٣VrΏ־ ŞRw2m:Pu 9FBdzB<ʲ=(# L(u AÜ>;f+6 JH mÇoh$OM6Q¼*^ t_ۖ͟8_/W)pT,ư*?wrO;tYRFsWIp mY;Vl To U^s4gܪSN >o*A [-wpUuaJ.A! U"6iS+ZXfHpv.”}%-9tdRW٧;8t‘ۏ<@= qrsk9А0`Y^F D0^,[ q~ & /i&*-A Ru&}VZӾCAʶ0$ŔVn7Aҧ dy7[#o& p q2>X`]jmEfm)nL8*O:$cg0D[C]#zZF,:}''l+NTAQ|,'JO~9b|kVa3W#KvءDi<ɬKm>͍b7K7"K?]e)eghP`9Kݲ^"1!) 6c)rEPjՐqK(D<_(+_ƿ~"^MPKPrޑ}p"߯q]lYr412t_ 9l\WqH>}DJGHbAUomnMWFs1Q- i4\ߩN{Rn;TަJ=۵+`8-$gXaw>SgGm&lbpE78[D u2Op/o+pٛmZd6,}ƴkuX 'N\ GDvq/Up5?6eKGFec. IGE4Zq:Ⱋ]FD#c2\VMQI]3 y-,NǮȁ}29Ou b BCj0}4U:cL@fxT#:H ɋ'o+8|2M#'9oߑ)?Zu]ț鹻! }t|N2LV i驭]NZ@\_qო-)\c<N_; N OLJ҄.-ih0n=YLS܀Z{oj:SݡR"w~IU7JrkF Ӟ- ԨB̻Dc|m!Rw ,u.V]54ӃGv/훟;bg8+O2;F (:cϗqɫw)H1(ĻW&1R̉ c-oJ MuOgphKia]~g*J[Yx2) H)8s I;T-12ϕw' ( m6T4NLgxEye"]ҠŢ%w |ί!] vJvHMr+l_ćuƇ["E]T1ZtuD&煄U['xYG}vCb(Ve(_W[JIh;9cI%)/s6Ɋ$~Qls qMtub.ˇ[D"(g +6q.[ClqoUF睱|2[XgɋYZ܏g) Aqrv't/u1ִ NQa!lqKd@n7 gd̳ gg!ZSw&횺'\thh(뉴G]GCHͮ*c@%%, _"6M$|{x S! pl ґyd= <};+e2j;O>E:_`Yŏ|}IKX޹$WArt R oʼ̆qFQߪF Yfwj;W*Uׅ||CZˑ+̆̽zQ+yI[Gy+oXtj o GIYp} {>7VDG"qcq:MD0fo98 [镱4@յ&=HMI@w~!<|҄y}!  бK)m@NKTIX,' 9wbY Y4Ub?(6,PLfE.RTCMV%I9h'ѐW)z ͧH* Z t Pr5<,C^5gxJ:A ($/9OZ!$g+=.Azk|R6: ;cb/ـ~ gV:sCZbGL3\4bkܱENh|?KvikMFp8PsIKm vXL dسXEdu)cSIZ>J/4ov4տꇈ^ Kp E"EnZ᭕'1>K$A:3,VM~ +0Ί#:)5R@¤ؗ_$A,M`\:ϩxvi0FFS; uM+938d,1,|?+v!Lm] zOjBɚ@+:b> ;'Q;EGU Ȼ}I3JLPJ]m@+R$L;!19ypMwǡ 6${v~[|KD%/IO٨ qTz5 '#(BMD%Jb02NUCv pISEV4A Geg18b+͒ Y{&"y#ز[rnl3_E|Y Ab^:bRۡM~WO_USLr ]q}@yTʨzpR ^lf4d?+ \伅դTrݍ/)ĐsaUI"e[;ѡe6 zQS){,g1\&;І>EnՏx7]ZdlREz~ <9L{{Gx9g5.SF[vj%ɣI1E^llϦW;k4[ i'K4^TqìPij'ۖrWv/kxϹw(L=syKDŽ 1-5 QN~[ V[7IO=#GrbO<.h89v0A?b/xV }3ӮCKE܆ }3˦Z@\0(<S̟"Q.G٫^SƲz2yJ'(s|Co dG^5^=/M hqTnbaҫQTMQDm O&sOGZqdnv)0b`EC${iK+0c~o y&ch~ IKr 5"_!vc&q+"RзaBiIp'?)&<6RM,AX8-*m_Jonj~ wc!x|kWmkH'J$8YU뗄TAƼeM8k!dl()Q {1P؊8: 8䟭Y*z d18dl^ޞ("i4I3o&shҊUpfj\,|j3(Եq0+PA S޿ZԳH ,t!50U4G0'ܿ:Eyg]01f$lMpOеHuaf.LʐK2o"E99pP{ `^Z]Hf#Hd\Cp<>դacw\@'i愳ك0}2 ʼn in9 IQ [}CIFL<j:L~!<=hiPG;F٦* ZG%c 7~f~!pW] ?U~f-I>lKgP;, ;ٸաK_\ 6! O*vv#׉a}吃a q:>\ϤhF{;dtަ1yI"gd]>P"d0Ou[um|QFS5ʆY'ʚóYRҽ_K5 `I7f%qbNz)Ik~fr)3bY򔇘sst[f7Mu{CaѺ-ՙD,3gYD|Ho^DJJ +~u۟+}-nyVMe MݭvQikHOFjBLJ%mm Ǻi,uRÛ\h2q{Ag܏iJcT*p9. :OO8B_#zz64t,٣W%`(Kx$U$;{A\tsNh!zw'цhc[#1_C\0oEoB iy2GОa"a+2?80:ǎBZXm:\H-w8|!Ȓ(|rq' <- U*B-jÕf=o Ħt)ԑJTO9 c eΰ<$n _v,#5Jt%,-9FxmHŤAٱ0kdY.k#\#I#f߲Hzӳ+:bI-yL (~W K%h9 ˃oн ˚Ҙn V`RiWEZ^ 3ʑ]W|$9Bf^"ɀ 5n8&9e7\h"Th |4A֙*M ڤ_wƁ~M^qgߓ4(EBaHdGN9&Yi&{ q۴Rf l ;r+0!fOa8oQ㋎ &ս` Z^4a_ZX5Fl^Z N}T _̨ǒf Z;U? t7pRhTXV?D[p91 KfZ(șivc+tx?S2St1‹Y˲[/:sኊw B*#d q)2'._oXَP6 &5Gݖ5ayYdm*P]L4z(E%,fDUDOˆS< 3}MqΩB0o41$xK=o`!yoiaMh+;La)uL,ǁZY(xTMV'ޓC0e'89b0@C ZΟRۻQ~bx͡(G ?A#f6֫9m1rx͖ܦfC-nQLEs<~ մnn7Nev}!YUC=2!Л]QNB:۶{ +iW\mԫIi4d)?QDhubH{O4W&+LiYT/#YWpHϤ]ƚQ^ @͝!W ȰdTiNzCO9:UzfZʴ'x7 +*܈g&*\FoQa.ɍUB׮63/_ ָQũ&]#j8? HЈ>0KW8G9yӧsq Yg+O`?\E1#+xK riAXa' !gNh߭p݌]FR#҆Mn K H,X#?{ y1a1#vR_:"ђ͝nxy@΃:f,ii{R.wr,6{\g_j+EmaC/7UZۃ:yJԓ6>f"`sL[Ss370fnﯖHQO"}Nt ?őcg_7zZ/ɬ/MTK_͏};jm,-vc2#ڀղ{u-WgbkaZFNcS{ʋv/dFD~rgx[E]9[$tk ha:ܥ4 &J$ EݬniZNgfk~S OHBMX.zAɫvt϶L KZG},3-j! sDxO$u~ۜ$K$ɑ.Ȥ~lq7O#\B+jT md! S)w#Jd 2+` t-hhgڢ LۘoV0yu! 0X(*8It[5v2x']|*%߯M(4r\;>Vf `m5࢞luZ0g%ֲ@;N Ei$[$aXs#oO{d 5lE b( pmY)8J.By5om?d*@i))TdecI.PQB3eץE){tu*]o<6yT0~Mܣkwy"f5qyUf2Eq%}f{O |XvJKn=E)uj{EFWMRvhrp)DsqǗ;0\ƞ/})I{Ɉ:"~BWOdA)PlhL=|/gU: AyE/3H5)|eW]d`ifd|Q_Hiٓ { emU$jq6J&,xpل+SBz.: I#QQKfWݕ@rZ" , $^6DǶ)4Ⱦ ~i8a'`EEk Q@&YkECgVT1۱6#>߆4RչjQ_hmu瑕\fh5B,1Ok]3 C xf k fY4 QnLۿO [?4AwADa]r ө|׷͒^?UY(eʎuԘɓ&D0}@/놨sDUKDZt޺9 jgԱ`PYvW[ZN2`g..1^*E!own-}  JmH3]Y渷ǡ MNqh+\&9r?MU M"6馟Ms`bB໊%ʭNgP|{7^1ePW/t<f5A|l W9,g$nƘ%'F^3&ЬΘpK{d黒^?CEFr ;Ճ4FQV&nuѤh ꕀ+{ɗiIQp3^͂CI@"2zfݳek& 8׶*T%hE;;)оG>_q { c;/ZJL>.'g =ULj39AUtΟ|Qp%aV_Vq&Nը E I NDN gƟc7 KS!޶V2у7+wٵn؍\*vG h<釠:l}da]zu$ģb&_10W.Cec)$F Ꮀ, 9fZZgo/@=\>@բABJ]vW Ys>PdwZKµ9"kp1 zF~EFw= <%˽>5D)%@-u?!>M7 P(J"~\F1[re*@_O:UqlCNY ve?z{!->Ӫ4,*R[áY\JMλhRs'x>;r F+rڿr|$*T=p#{b_;uApwPE'%Wq}'qB);d n쀸c+Iѐ{/^愘필MU TtÖLnᵺ IlCw(AK\N!~-"'`(ugvfi[7[<ҋUrd/g|goJ`BΠ]T).\@| tdsGu-z{IqF^[.}xu@o[Wspeg;)n.-o^S ')Hcʂt0?l`faZkM+h&Y`iLLl=¼s˒A4W?U_U:Ӛr Jжq߫KNsX2ĽZ )Lsy~$ ޙ6 ĦÅoSϙJS0jH?a_H-*vQ4.r,֩gfc8wGs뚧lZ;l+g8gC[^0Sjϼ98ӁNs>}`UgPhdP2w{[E.ŏDP~o96 HJ!s*1ixS|"B?Q >}*`^ŀ5yL_0f1"Z Yn_1GbaA96}6=eS|~M, .׫)&)]+GcĦExuAÀVP@]m -<(l70@m9 n.S!ܱ6U(<{b#Mqx?{4Yk&V`žz'$(rjSڑZEpֽJ7,eXC!=576&zdS1/ O.%l%q]fZ9v F2jp^+p"IKIzιZuT9ývWVTDi% `>@^.9Y.Wz;dy)kLQd(L=LCK1(5jqI50$Ƴʛ%(CP<=SUE0R|E\>6;>735!6,baz}BW`W=o}I9>sTiΡwE+62*>JY_s}UWYtvk>v|lwؐqBʉ:AtJZe^d;=I0O#~׹ԡ{%\T^e!:88*B! b;Jm28gJbv>s<~L5_h}w+3:|itOœ 3)!`ږxF@!re" ?ٗ?GiҖ?RWY FMA"԰TY 8dLEwќe8UE{f׍.PՌ\amkbc>4]AUڔO5`-Ifm}ݮߛ(1~oS1pV+fu6؝((;%lxj8'OF\3lxNdjAUNu|:K)lݘ9P2 ,("Blۚ{ԥ+eͧiì|_ꒅL'`<".ж69LH}nu <7t#YaybY4uiy*;5ઋp5e;|$\a/^,طc|F]1&$y9!&4& yP֘/V; }t-3u~iӧ1B=U<~D1v sH 51 H_ؿsWtyyZֽ1Q[GzoC LghL>(*.ՠKaĿGny=)KjsЙkW@_kAa.;K7.$sY <P#GCNZqD>M -WEB~C">\MsX>\{V^=c='-VuxqCk &*rAXtBxvľQ@Z`Z0(rTZ<[I]$"e5O"_ʫ|d,5Ÿ !TvNRlɧ^!|,<'Vm;c9l8*,@ ^cOEdYF J@*.b>nmx~j/'!zPz!ٞnRЫ/E&Q\(:IjRooT{ RZAW$S ATyw_-`V)U4j2mmr'3kE ur jfX]h>?TD9ZM7h9๋qtcq`aۃ-udkJf@b4^*.or=qT;x]+/ˁ@s} V tc!;ª _tj#uss7Gl^n!0;9TM+ywcbyoZ_;#3 Px"VT nY#-Ҥ<(Diu[{ĐYtXl9\V1tu# (^{ eD9eV,6FF(ɿ|gV}%q7PLp{]R/\ClçM%4e\&Ye{2(G]6vy;EFFM8I͂-h$d5!8v"F{ic[P5H[Q)V ctIE8Od Ski\>Z0nznN' gxgpo T d{e;12X[=j=Sݿ?zOI{uS}Q8Raue,ۄpk)kQVqXN9_; Gj={=1vV0+Ys >m{a]hi4nq t>k-_[-UjfdP;d!k>k9ٲv^l隙^k.RA|631 u5^ܴ@<\Hjgy"ۥYB\1tuRDYݲBdmAx8>7[u)B't=gMSTY{'Iu:.jE˅O ZcBh~lWAmcp9@ K!P6?l_ybY`GpPR+3P\_Y̱lX^jWI埣Rv&|Мs0Dv .Zde9'w! )Jux?gt2QјQG&: 1ޣnAAhdF4$9o\|ZJ4HbN}7[6=.p8ԋD'LB5p e/cK=Mw#C@c  鏶8c&)rE=_tOGl24sJXWY2 e[uO΁|׏3GqL`J"Wz2 Rw>m{E`Y1lLZdDoazղh.R!,ש1v22r p*,FU+]ᄌHHrI, ɯն+7;E֥LP+~X#}wD1mWQRD)`cR1\uT'CY~>.ύJ3Opk\R<hf IfĊτ(W*5uYܙ-{릷z ЧҠ˙oR;mtBC7@$M.'|aG͝''H"ޡ^~ǪB̵Ra՟WV&Dn'GÈn7Ud&O"WrGo󷩈09{dPK9 ?:|z lo"fҽlٽR* `Mj#B~Kr˷7Uq L]$ddt `_M|Pd9s"i䶹K[@ǿ <čh)_u9d9*3Ƥ/9g 9ƞ("gAa'0 Ԭ.P\KYJ,*DRب.g?z$+vI-4xͱN3 ]xbA?]j7W8OfP#FH+P<$bb<4]L1#C1X`&D:8#;'Hk<&ʉrʕ +-ι;dN1LORѸ(|m̧O问 S:"AQC=A3JFիY ⯁?*}{wt3 *-8KV@d\)oSXjm!v(XQ'7lNS1"7-rKxGGMU$SvjLEci\3ث٠cA>c+9Aveu+^Drbф?kDh!ZL|b` 'e"'~;TI"&b«D:>Š&I/~T psuTJlQ!i*mA\&Z $QP63KUbnA&|'tirT 4W#ݸSE<%7>?/hdn(^w C1k"'&(@{pӹz$Sj&0\:H TT8:0Oa2.sɓ۝#=wsC1W 0,.Օ@˫G)/Z="u+m\6F # UnTv\⡁ blUM}1G7(}7/QBHX~R-eY ukj F׫ϳ ]di$vC k-|㺔n0_툋(p=ܪ Ub_CvO-4-Uh@eQnŅָ˰ >6}赶G*a5ӃKvdnĝ47HjT8v4Ԓ>*g1B06y&J_74%ƮtKF;>qSA|ܛb{QtSJ6N߹<{:`K9F.&L͈4SuGmpދckp q>hfIΖQ.#,Pl׎Lyewx-CIXܒLC2oG2̆a l8o ΑDٕrABjw^!މ;gAYlkD].fT ^ۍl^Zyaa$a>T\K,Փ Y(*vxjdҒeƀeMQU'=o=C8+(jIJ>|鰖x.gwK1% KOP y1Y<3$QqJwfԓS6agc@( d}KJDv G\11M,U,<ӊ(۫}BLkA#\oz0riOUΕ\>}C!%t~=}^bxDI_7hӔ`fv%ؔɐJyFML_*zWƨ =ΔLj)4$VE >5L<ša/vԼ dC:'cU0:n\ۛe@ϋ&#Lw~7:dltljY #Cd h6z-Ez, Q۫'Uk8^gi˿H'ݥu@0Hxp k{uq#Ž(VmPy /"aFK ѡ;?o2@*۩IvunWIIG;i\?} m LO5Ю%`)Gĩ+'JEu .e}2DIu@$&ٛCiOQoS딩~,w _ go]jXsSӎ O56y]A]s„ѓ.?4;Qij;?l4WQ(٩C6ٵn؛-'L :r n&DW8΃Lvk1s'[iG27gpO0L4!&79w*/e`CL7Yjfo@{x>vl\v[-aE2)/ ,`/uO )}fmETR|wB^9 ʆ1_wjk#v/fWrTK+4*G5qb>C+pBg6Y'[ڮS]r{LbUG6{߭P Kcnt?~@N29Ͼr}ex +0UO[ZY&Es1 ,CcE/wݍjfkk aw6BP*eբ>*j/7]ـ;fc717J-2mX4UtPzCDL7ج>zub& 0,4,Ib[o0C6G |w t߰7y8}nV&uq+:lςl+%4VZ8k-Ϯ@QN'ю0d}󥧝ଢǨ\Yz6t &uYt{q)FK\NDdעYx۟FI"lh[8i"VQm1[;w1 r2Yek)쳅)?A%E'"dm|p!ȥ*Yy}\ro ܒO(s7[~t_z}͝|CUf Ux]dCȒT{Jlv) }Y=y^c݅()0 ZU6W6Ӑ*ۀҤ#=4 l@Dxoi iEY@ [ 4h܍P4r:E݃Q0ו~Q^|2`j̀!\w?V*gabvB$ m0sE#R v*uz0q/>&̦պ6m\>j-G(x(0A0z l#n"y(Yem\@k9A:93aPX`3ծ/uK: nuGU]\Pq"f}\uƹZYIZ * @-cô$RzZlo핽! GN>z'RXFC \dhtՒ](4w"G"Fe2{dS3ԢhJ%~aDG;҂5S)ዋĨ9]n烰=9x^''>9(XN8~%M (] #[z|5e`CwmB"Wީ4SGnf8?<4heGbډӘcp9s>?pv}؁ʥ%/Z>S&XϲقtIQ:.{TZT(NJ+X|Ec% sӜa@&lUH rS[5NEYRw^b'MU17pG bcLR }.!wad6=Va<"fH ܖNj^dܝUw:Ij BoM73pu1D:Ѳ|s}o8Zpp2Ikice;u'HJqb 8!P[w+AТԱlOیFH?tл6l֓WP0`z;9SgP֮u<;Jx\uj`AbNi/mOɝ]oGa!TeҚC;ڽ# xco+AgrؖF^NZ 翠۬B _)pb"aP)U4XwkPxYS#mžgv5ߙ>nxt<~"jQϰT:PgT:p ]eܱ[x#Fzɸ:?fL3a) كQ  yF+&3?uYJaسquchīe&뙭,Az]foD˗`f)pΙ UקPآ]}]"C }jF ^=5z*nM[r3ͧłyJjP ,n8%N"/rUi8ӌ-ȴKDk " >8m?!oBξ_N4 }rhLYq&2"4ArƑ&Z^TRG}.iD|xK*,Ż 4C(s{nI asBQfhi4T#'Ȉ@^!҇_ۆ)a OTvL4bZu}-LF &i2B*(/t%Z# 96-,ٴ[+" {ו. J+4|yQTs-锐bLQ(^Ey)Lܟtq%mHQ8% wS\ےVᖱ x#sq%6gxFŦ ʊ>Z14ey(={!ŪSR", <(o閒bSsH U@h vl*%neU iu@yiDuf0d Olk#Ex)JŻTmnd* muSo<#Zapғ ʇ{MnԻ[0,[o2L35f/Wa /0(q45d92/Ҷ%ÎNF:ݑap&+`(:gwѰP SUoM.]'F kqǮ-ۙGT =qT'Va_z:*!{蛸TO7!w<ֻPʗ-kB*bnK+]ྑq/'U5^^:Mh/S,w0}m;7)h*f)I8f NXSd](4L#@,DB+3lQxo_c0D|2ӱ_Ip!$ot:Bh& azꤐ]Yɗٶ|k-/KBT޶E3[bM7Ng:i@@Se mX=^ΊI|?&<'{~=CԽ^?$Jd#n6˸ҭu>(,E7> η.lZ*Qz&\xTPaOSUDDxȔtn{fSkdzMߛ 5{og햢oI*J49O]3;+,?F90iT9Cg A&n ] ԈTu.s#ĠZ-p NzIcj7[5h}{fƻ@M 1C2Ef9RVkaI P I/͖#:#X3I@EQW,og9գ]gd0gj#ZaIN ]WZ i%_Ӳ]Oc'k׀k\j}ڸ5=>,j" 6H dK{yۀNk4&=/6. wsAW%:e XMLv߿fpIh1x隂 e4/T2Z%i=DrXB`4r!kz0+ZD/ {/i.уnŲm~&~(B9miSU6 `k+9t&U[*6+]_`=P'o=O7qSOt楌ݭ. aOWle6ri }EUH+-l;IaZ(-Ћ8I#)rS4.b@ X_"\fؿfɨ+6mXK祖xh'ǁK$3@MpÚz -$nTo⨰ NE0:Tnپ攎HUYy 0J}4M YK$?Sš 鷺/(UDUi%`6󼿃X|.  F42n8tslq+z#0s@!P{UApNj_%;BccDG`w)NdP0&AѬ\%{$Ƒ @^'oc)gc4ƹ rOm 0vl5~vљqݲ{8[.%&lxZu9(ek-À[tƕ&b;LoU7ڊ세d""ʈ-=U y[QwYѦ&d/xvnm{q|sfWĎ2klkh=K,hQCa%*ncgN"Y>N޽u]!_}Cv3*zhhd T88Ty=%c,\k3-DJ(: s""|5;`yxhQ88՞Yl7I爛&IG^Բ/ew ̂6lK*;'iyM)xy&m{;`WnQöƅ DkJh B '6t\Ad&_񫴩H)$j"vNHgS#@"^֤gg-y5!]JʤLu: R`<-IOh' 䟢D({+؇4+XmSg0aC;9VZpmgM%*0.)tfaVYE+qkqL4,$M#0kTr__轲gEªpRBnt8F<]7kawaf7XxNEI¥g_[m;r4]\ؖ+TYBV-jC򺅀MöU:1@P EXC)̓,P5\s‚뫳 SPxU| tv( n|>CR%P}jh &4?]ĠO31%:eB( o1ʴaDу?X;ON-i'`ξQyܐ kx7JL|*{ k=,PVxNBuTP#e50%%jS4` ҡ#J$w|м6&]\87kчVo%Q~AI$4.ΗոYڊ TGfǘ.4bneA&~l3_q^E/.j`nr!!VT 9wv#=0vI\$m?Wn;kS+ r=څ/9'ە3;8{<֟@+F+L|5$ Sp4izBU'eū$FVRJ\ЇPbENL͇L,zFiC5 =}t+h~EhS&S!QxDhOuj vr"'1g!XU8$U;ʝI|?•ꕻ)XLCIU)(LhZ_sD_LE.{4dwaQ|udمx O%<8ۍRyrTz̫-z5V6dT7\w߇u4x: iV-qp QO/-^Mb+ ,&N7R"vdc!>x` /'-x͒. =LAZp.{æ ߡ3462K+^>'e aʗX.GFDb[y+)T3C3ږmB:3w XO^n+W *qWRWudA ,yZN!ȑ1aL` mc#uv*sI-!#٬Ew[+'7XؙMqRDI҇#btp@M4DX-c^Q;}FivO7^EC~E_l TȟH@Yw~NkglYttvv9(zZX(ޢZn풿?HW5K:%)Cw7^}DUk-л-jtޣQLxd94Y؟$Q_ ;3pekXV ./u\=v&fA_}Х÷^"_Q|e2vD֗%zU}LM⃩{+rqʞGWt E| "zL%j5u j![VqK 8RH_R^k#$>d&Ӣ99@ŬZ SہfzcDXbIS$lZD fKʔh<& N\P#qo| HQ'qSnK(d^|r޿ݢd9`k0] Q "86L6/7&c>3p"8o> {)WYaZF4l꿲FH˿mA ?nGCR:6nlf_I܅adEHWU-YY3{aiw0O-oGAH=,ӗMn ϫ V|<0Ͻ`)BMS?Kp)ln6. ?}Udf({ Ȼ\7TR4.I܌󃗛&?PRjLaLQ^WXm+b0 LGtnO_jl܆k'u4AP,n-la*61ރ ieurvjdJ9&Pѯ?9pfiu0QrfTKh]r\Lu^@JFyA>PZIGw̉[=|qM,3H)bM xr 9(DϷ|7}+^9+&TNʬ:V-RZQ؝{desԲ篽NyK'v|BJDeё7BQp[Wx;doh697ũ&F><-O])4 Cm u@Ӌe8Ybr's[P\ra{t.$Lg(ZkģmƿzIv֣Q"iLDN9<8+gh0gHy,zp‰dl%(V+eAhXƖ_-Xx?a d[h\A4u(BfWjdN$s)N5HJڹ'B7ve>`Nɶ-}?of8E9ZFG降6.S$2T/ˠ_Vv,qmVcf A?tpvvgwD Wِe{zXES/> #^uoPm.PK Uw׹$f<4ȴzTMT/aT.AΏ28g'>),I'LBZbE_P oSI(I94' 6SE]PL~7O 6=֤qQ- /f_, $+4VĠjؗ` 8:)z _%S8(dԁԦ7a<Ԋ)n$Onn&*=m 5gIX'=p ^©yoO nv{9\#r K1p8E'H[f(⃯QB<-#>\ X(cn}S|nԡ,~I&O6bލce9;,._76B:Ǹ4y]AWD۟j5YkT7؀5[VObX~d+:da_va%UfSvJ3vo %P"DAb(E~JLAY}Be˪KmZ]ݽH+/tƞÉNʆq"Uh檜"u "ת*l+,[@.{:HҚމ9 CHV cV'? g9(8z%V(X㿎ҶSbg07m[^҆gܬ B%-6{@sL8ť[cja.2{201=N^7AY!*3 qQUznKLϔ6ZԳ#[UN]f8Mfrc-By=J)+#hfOBV^ΫLU}&c~;C1UT@g|tˬVə-ٸ-LƢe' Iϩ̵l5G. mzhglGB?wLrrߩu샰) Լ!7CLW$)8s x&|CJKlm/JQ=ڽ]+2rp`u7-S_H>p:סCD fU^"c̭´|PۍzPx"ګ?gDԈ0kp3:Y'+c`\%|iwUoELpcH7:f*#RWϜW gi;5TzvLܣ'B2*s q(aP?f5{NeiHh>2灇RY]Ήc]St_-9/UNi2T} 5<"̴(9+ B+; GGs0b_/yOMN{Y'4Vоtg#Hx ŷ<ߺW5zِk)d`[iR"0g0SmJkmD{qS(sQmڝW @r"sڊ2E"%qDĉ6KbD)P|-s+ZUUνvf(%+V"ɛsLvk~3spwߏRwחR -X6, UX [2IIh Nmsr|# |$ǛB1էR$˖BhWL- SrTx#E+-MPE "T, +/o U\5$ƕX@_ɞ˗F'f [`Ƅ%psQJ~ 9\._S#WG1G%gDX̊M~Ei͏xrT7K~MݺqCv`rTDS`Sg(s((J(y}n/3 wÈl J%8mZ񓐪UI8wK]OOWOdg`!'"3I ]îk+A̮O꽖/WO=3 uEo}[tcB.E Ez*[ ;'V\u7rr>w ǵp{hX!1L幄m.h Е+ݎi6=H6WEQz76mYqXZ;ըF׻PQ3BC%JDVڸ .Se@3 ;|;SZ~o{0Lw&ZXJ>H7{Z0vt!!v'7dufѺY-^p<.Jr޾~ݵc3H3ͷ AQ}C]/epTwRIzA]飜#&-/pT3pS%QPDa%mعxE,xQ170S_e=w$/K"w Ej`N\QC ;LQwrY^dЈKq"K*V 9ퟝjUR,"PYPX] IT˩l|tq~ h.념LG~SJESգy;!o-xu9Ǡs6,#,$\Uu6,OvU]R"u!ݬ\zygV2Hƌ_2CDf~J!-3Jq*dza %Fny>%Jǔ'[~|ɩXś؞Lsy:d`]7i`̸y]EXSVΜpyh!NяƊ8:Bsqbnrӻj!:rыJZVHm=r ;0ɋxpjv>1bL FB -BsBW'3v DԄaNI]m x::7̘mޏ;53QaLAG13E XR OO\j#SϽ1 b {##ڎ. J^0"zGÄSLC;]/lM&aT;Ze ENͥv>h|׶J:CX(i;9G;";Qw >O19ILql]¦M7uI٢ߪztac5|iD $UGyz>@:~mtz>Y?JђEҸLS8 _5 FViɂ浓AdT#x=`H+jS&ƖKۚ(F+ř6ޮ;v/mPU]?%4|J9woܤV^6q4&@'8k&Up%`4DފO`N@¡G.9k,d;i*ѓG/˗/SszR.a;cC,5/Sf.RR:㞨6[(&wcjhw.Շ ؆\~b6͚ r1,>R,\JVO0@ln5%?|6T(D$k")I2dcBR3xJ}1Z*GaQ;ړ ǜ*i 1\1nt`=nV_6 9?00b6Lƴ湗,4##e$0_>ϲh்A@ԐY|Ҕr(ٍ?V^oB/LN 긂05U؋[)kcwf'g ?Jv0u ,juGA1޺=`RO-P`HҸ{oRJҮFQ""ձ[ͨCxxOK)F~\X3I; oAL\ c;8cH9> Z> }ZF-=(J\Z6j(Iו &TjRvse5gj TmZ#jXѶG('Xg^kpCV#h@94FLka{te^QlЅj^D)ƀ.Աt1ْ֦`wxPLnܧ'i}.3$k=[9/JYMM#e+Ѣ]ЌzE_ErCrtX6\Pow̎p8Ĝ H ڼHVd`RHH82Tړ*jC) MRx++c~B%3]5> *x3K!ŏ4 uN<7U sΡW^|\CG1^ăΨb ’醃x2$WӴf&ErM˰]پ"vqu8̱XZ ε*t¼&rwIm}$gKg`a&tZkHޙhK!q0ՇusZ~#V)ѡB΂U'&@=R}S$"\Zon$漌Ld {@u o_vYY\\KOU. _(Ş[$ԯU撣.hCJDvvw\; Wv-+tam{Z4ʏs^Zwl/]fMikjE7+D% w??Pyc@X%i^ 3;Υ Pna҈` OlLS(XNHc_.Յq8JL5圝` k(uL9kףHO6Qʺv[}TY l*_6JI]Z)曟L=LoOl=sz9MTC _Rs&]K61e+K 9߻zFRi`p>JWC7{VLG4vЀZY'5Ԛݦ& h0$VW"yVQ2k/0n*{/gm z~22l#j6 u5Q2}oKvu%kݾZ /OH|[)>;kg/5A-Ǧެ#EOs5FA:9k.%Bu8|kc '6 ,5w>S.tsk$][Fva?<I[Z*kYF[cT'/.gp?JS `4;O >=[̒_˖Zl vXn+ L<%fVUN{!a.)bI|`$͡ԥobjWF?+Rde\mdBQiEY9"E%U*'ӗ%4 NW1SYMW!!21jׅ~t'2xT5,?6Wkw~es& #ga"ϾÂQVGrӶ.% $shOlm5S DE_*.+Y XL~Y8Oz`?iI&%F]d<~ҦF#0WJ#AkyŎ)Y T{ޠg*~"X+t" inrA {RXg0Sh1ڀѸ~c׻Mۤ'V~ar|5rkSH7z"C%ff:N B* 1ku`fst1QqN /`U1cs&ՋQ^JhGu~Jg=gA=s"q1[ʦ ' NEkb(I\M@0>4͢YgZŊ?'Ib74v{k?1m0R\<kFcR꓀ccÒ-_21S%6FQk+ ug*!yۍy>~l ee%CpZVz$,/Ai-(YhPuޔ[? BHZsJ!ɺ٥E;4 Oh\Ls#x$k'~a-zpdgɵ,읝}}F{֭~)ayM{.9v^kDk/ҷ+/7i0kK`tu?܍!AF絪OxlO6N3fE9{Dd-!7OEuR,]Bֽc `"zLI"˳"K&GFR|.+ձݹB\K"-XԄO"B yx|'%?zGf:EgzR(IRÂv{<m_bc|53`v `}V'LИj1vN1Hnx#%xLkRٵjxjo?76j4ZIfmhx'Pgפk]T':!In}^@08BMu58 [fШtd0*f^H#N͌'&Å"R~ZU-8QҤD5!&D]fu2f Br-쌳=1Ykk.A`vqBEȄWE8;.ZG?֡ @Mm˄$) ȏPZp]{đpΥꠦn$ zMLx6Wڼ㻡עE8֛ %iUܬViRh(2rg/x{ӵrYu A>v˂)+$#f18+FRk0TPi̕h3e+}GC/Ơ#L!u6Duo^usc#ւvיRDT1-G+O;%wnP~p}}rt >r: Dl -al׮n'H]@Dgv![;r&-?kX<0̞e \_~7KC^؁4ItxKgԯv1͎O 7HC?s~ ` R7P+ r6_0ou2wr٬f6Q,ZPs|n>_ l rG|ާY0W,0B|L#6S$N.Jҟ5@zO McxUGX͝Z˕*OSp#x5J 6[Q7R _!|TŝrnOa6Ζ辕\ ف5Hdsjy~+0NVG L%4F.ս49Y9 U"x?pHq%H|슏*p;$aZ>snwdm7䭁/Dhgcf En]qL-cum `NÏ/V?!ݏC7A6>_!!I7Z8= `c˦)|F`MȖEJ+e^ל x*bFѽ+0@L'=: mSpM~\]*gEn{m(TqUɌ{Lbr<ƣ tJ߇.+oTE ?V Biy֖v"Ah'7b5G Zܡl<ٓb6Oў;v"?WiM ‘c봖/9,Wj5ٷN1reZs쯟j {&fXMǍQGJ<.BWuU2$^O|Uw@-1]ύ[^̐8S:sKj6P6ϟ{c@jaosͱ)0:=$u1~JS7,hntl:\#{:%|٘׶ 2HVZ[X%̫o_߆n;uUjnNy4r`*H4h)p j?r԰cStI;171H?Ȁ4OD: Q!,?AŇq,ՔzCӝ (z%f0˗o %3MN٢lPMLrHϩ@-P ;gG-A\I"aG2I ND}K8dOos);:=et4CA]8,_8"x#_p*]/:㗌Wum۔R3T3)ű qEK9f_.M3TPpYPf_K[[E(dɿ h5{%@L>#w;܀]oq\ysK~zh&Pu*Jh$O9@~ՑIî3BYͷX)Al`/> 'kCc5ZJĝRi|zـf<::?Cw؏w\mf6 :'4XYH4t P_35x#cœ*vl>?ٻS8z^ϭ*"_bZ~ee$_5Sz9 d' ՛P0^jױiF1D3N3N&:JB}s+̨qhf~kbcAR|<ll8rFrgSC/tέDAEՈ=/QHa$$? UE'x=.bMF vdI;ꥈ^2s>O^!8P֭a'Pitr֦-"q`#+nu/ļ qi ⩵i5Υk(3Tk%0VD,:3{:#(/;X/3i RLC-# elPnR;1N?Jbvm\nqI@ Bd71}VђflF©x< 4Gn YhwO9U (0Կzmר[D ~c#OftiS}/r|T/GbVd}PB'NPU]@镟^4{H\'v*-yO7րTB'/:'&u/'t)mB[dQ_Su1F)xVh.ŅM#w]Տ()*G;ֶ{g:iԽUA[I!ӹ|~V~F#Ț 54R0C8ņ%K^́>ACSXr5`\̩pX䬽u^Ͼ^3*-)iAX}{ \Ċ@kx;7շ?γ!Z}p'q೧9rp_utl7 w{%BxCxBn*sAgӊA͘:rrΊTH[gWC bUiVF<랝.Ki|%5xSZɊIM\gVV,lT)y ]D<"B>2ʴT)9I)M͙$a@y*Nnt{}cǸjHtwYv'[Rg|ڔD"i6r5:*c_fO&)u37E[ ウ*)ګ3c7< 8.^ h~VBS᧔w)&oO]eȠ$oLikjgYx i)TUc`ѹۜBB p?@j_M|fNVZA.fُNވfMpu`礦jx٦V}iQu`+'LϢ.eCKb'oPצȔ#F+zfnm~KGX^UgLGT}w*=)gP$de󥟏^`?O4/$,^V/ mFln؍ѣ ۝J+Ѥ[pܗp"'2X /Xջh.<(>Jpߞ&iM{*z$.}qڦt|U"(SO.C *z >|d;hUx-9u;kX]2M|F܂R&N8'_RL{M(&=y81 ^Bx%;}f,<=Czroۖ["}hXzg^!(rl>fXH[\?4 кqEHR/Х's q[.bABC^e8H^P %Qߙ&ba#v+8'+6.D׌ /3U&'HF:_]Kv=~>NzW l&ݰ&4;s<a~~ߢ:Oi/=gud6 Jb@ycR԰KH[= spo쾂o{7"x13EXǻVqk3zj粇F$ v[Z[C@b?ɉ:i=~=EcC/!{G Pbl%NTST89l\h)׶xz&IQ+:Ͷ %e-9۩Xw$.bɾ|;h(=&ޢCαR'[ojUey@qmaVnX+nq6oJ~ {l:9c>H$Vc5[O2V(l=pqA}Qr` s5*wPeP1dı79E.T/m?ކ_>C2!'ުg5 5#!BC4 E:nSxh=u6$ơ N; g@pE.dmdS=2 H0x.bOqH? 2y)Q.Aw  rF-VVޕ)K1f3e3lÅ[*<ŅqHh ck㿗Zq3=PM5Eyg!,0rx)L2,zvcr{^vU1%#7KOHF{[RZ;Gj \X N) d4'2\Y|=Ug*wYmu'A!(ljZk?pA(}K{g]4оÎIMi6} _6y̋+'dtѵQ|4[ ]0!}>7A/sVץi;-k`G=Osh~]cHTb%Y+PP kUNvUChJ/_>ʪ@YqQ/\N`itgp8:a7d#20K GHB?t9ZMLKT oIB V*Wk+(mwM@\Co j(5XU[AQzh )/~#h -֥FULTU+F[Пd[I=ɿ%]ȃ*3rFsYM֙|crG7[V∃xnR7oT[xE1e;s^@~ olbK{gZ:+ "R_!ge#Lxu/`?{#3cͩj'e_5Y ocw;HDJpoЧvP6q|>?ȍs pጪEgﭠ̹XYdLl9>KkbfegRZq邞67ݜ[6!w=$3 9-L@>{"Rg"[0(d<b(nO,#疗jHA5H|.b[o13V~Z9G"BU`/dIRi #'cxo! Ȑv7ԁ?g^XCŮopoP2ݭq`q:&RnQyQs¾=+mE苾/9 #1@ EYٳkmm=]izSgcɲٜ77IxUGf<d޼z %{Z I u3̯kXy?6TDDWgz>FQ^Z7/$e7R:ᢎ` cV7.7D?(P<4&NL䣽& //XC<يRәZg-^W*C5nb82/oi% TL` gjVG/GEj\sb/I_Mt/mHMԽa8K&'I *{ٔ\]T>eki`>-jG0};-[m>jDrkKG< գ~hʤ&DUňh毨h|W!"u[V[=nP '^t!ɔHk/wi'#n_rhSg򨐩.gZO^Q[\>xM˜Y&\Gu :"9}ߩ?3임)QKtzN`Q۽E _2yg!Ἒ : yWoko SK~^uxO)H~\Vf΁MpStCtg(Ό)Rf@ +aHFCbliVQ;rO.eo$u6-CmH.`No·2SMsa%Y,(F5M2j촋. 1*(lq[U؜OΜ_OSU!\+"g\Őie[KsTy!Z_ ތ,,"1GMOsD_DZÅ YHb83*lmY?†lI$-8^XoKܦPMuu0//TOC ߽&ix'|*уu{ !f ^aT#w}ƷAUYtƝdJQ}J|@ 2V=P z=_*5QRk|1U;ƄYPx jpހa&=J[}9b)L3+HkҚuec(8v\. D?!\=ʵTA- گ'{T\`#ա5q Tڹ Ǿ3.+)ʿq2BE[4?'ZH2i >.9~&!*f43tepj*]NP<A{buټk8%@Iv"1Hl^$isK:=Mq0 ^kZE#M>ln+~/ 3g&r0دїTE Oy+Wqcna0Js秿4un_-K\E'ưMHk؍jDۘwS,?EU?ΐS :  BVX.V#1` k\/0%h1P}3׆ 9ոTEa+Xgod {F۩X@vX7nb^[]W쾰!=@ENn-ġ?CIzgF[SW.ݿoN=b`b%rF sUZ&ʽ8rm DhyDtr,PjطZP)V#_4d+dirm1SLR+#ʝ}Z QՁlD?_>&>ܗ,` Ƽz\ۤN*?3rYN6~;?D-dw*;q,Xu@_V/<+q#v=_l87SɔiB° aٌٓ-n fܢ[$'2vxniiKP:7Qg@䘋Wgk_'}ỦsR&fSҦԗhMJ@A{lY©bT%LG˵J^ꄥKԋ$c` s]d;nϣKe3H\`CO9U X$1ךAkSkW0. QAz;bOʨ~uuܺNrԲFiQ;|DV[4M*il]G$=e;)o6]8r9WRr'6|#%xHb[y{x}۞^A,w @]"^zwR&|2 w6Ќu-Գ^A$uBWߊjt֎ӳ][.eKQV ߣ-;8Giw|"LԂ[kGjCѼ2CkuMPĭRfDgw"b]eXYjC^u X_846i3(+:i}hi?(Mcpu~ ~k/檓aybZx -b܄d[yM83s9sw ?Q[.G'_jJV}c]X~8װPڄjCok*Ú{}1הOa¡O>TTo~ю#ӠF}^>'cΜdp+vɀ=xW(Qi~K~B岳:[ŠmK~Wک*.C:AS)TƼOl%Bw f"ozs{COQYݚi .fB1~eu'`̣3ALDX:q$&ڂzyU𧲀VHѐKҢyЉf+CjQHpm^IRW^K6Rע0`k`f5T*W0Ն#{f|!{)Oa|-e}\fK R_޹/#H15?ٜe۸"*#S(n:̐QZrApUE.GzBI|NFj3D g04VWHʅ΂ryIz P$>\6> lZѿ2x ϟoI˫sQ.R/v#7dd_e7pd:qϨiN:>N; z~y^-ІA)ȧ0jҢ,3xo2aDJ56a!-Y=xo.5KyVa 7QҤ]u*Z~=!i `J}'ub)R6OT%窬퇇-/)D$gARv F[ sQ@Ҳ!<1l4{+m2p,a4BhL(]P.HH[sbZǔdN ɣ?uM-i^yVwj49tC7 < T^NĴkAH?U x47dfkHnUuu"jhx?a78 v;6Zȫ݇D>qPa/𱯞.k}KvsYݭmNǬa53-ww Nn {{^3!E%PF( EJc5Fjs;QfxuCsh9Vt '4Z=cKMr⾜YUkGQMC:HX ٙËC7+kF+=ꝋɘݚ6NE4!Fd(ns^‹.2]{3=g-i+-6d__wEi"*G+C%/eefXi;Gly"rL.8/G:U?]LrwjW?v4Iq[<RkC ť23b`˙ Aĝֆ4[GYLt; Hs\mOY|~s+cjaV5l3Uo??awO4/D`zU&`>-fo%ap);RC;RJI,Z,+曹J7DhV63&v~Bx]Pgŵ FpO똔a?5gD?eakF'h?2) q a뽀zN ջl`Q !R\?,{NjgY1Wli2^u|$˫ ")V5_X]Ұj\0@q:[CwX&jb!ԏ4shSA?J(ASU]̾0ęS);3лR&XwI X*o姺c'NJW=˟<:.ݓhE8~dL2-c%-4N9or@7Q^+ pCJ_K6`Og<7[C>ZŠsN쫏wFĖsiB$yj8Ԁ"o&NsٙtO}m!8Bn@zC %Q~׿T}}+; ^fg4,!ۍ(Z΄Gθ^h ęּ9(&H3tS|1D5װ6ȹ znt /fi>2 f/lKzR7p4XgM/mt|s? q*iN3ׇP-%;Ƃj%CLpxknN ut%D-} <^+!n+7H/#찤$x'\u-:%dD$#4·l8`Hqΰ) (זF:x+T^/e?&` aX[ԲjsuLP~L.|s+8' +ama(!Yi_xࣧ|+³yro/zEԡJJWxO"(1\'9&Ū8NCӻe##u8)_efF~׽gԸOO*[JBҋD'mLd (a<;ܠpuqQ40Y@ Kb*.7k$38-Ք<PU(\+m/n/Imh[y̎R Aᕪ8fΙF470%H#2~.zӮp1l6 -DVm KZ 6XBX! KJneDRW;;aU u9KPwO`I Q +ojbQP/8KEA3.ܐP'Rmf3J$'%OV~L 0'z;a7&:$\>ςHPL&IibS?2?lӀ˔Fsx SB3e:dk0n*$&*9K.PU:?9p!w0 #HH>kaҐ FM֐wp g9O^E#:a3[iw6'L_L- V;Y([ ⇙1\gA1RWyy~; ޢ)۳u22CTj(ڝn[Pv9޿ЛQ|ףlEϺD Z3il]+gAJ 괘b|5/<>N|:CJ wBq5ctؘҞQjT'R\nPa0Dał(͎u }VxqGz| `{y!} ZD8 lɋTR"R* "Eq((\S6h=,dKך0 xoe{?ݒ98)7 1s(1!&:a㺼x)T8D0e|\]FvAȂk?ꌀQسB{Ok˕[?WG-7<)"VԜؔ E56Z Bƚ˂G'G.5Ź:2aɹz0xdOPd;  xXbW0#-Xt b12^.M<Եqa69k=Sv:3RUR [4jp[Qt><ҶAL[@Dh)ڢ' Z,̓^\ҥ\'z:2%oH02U3ӘEªp"ih:#'Y >Kmnw}ז}ĉl1Na~8_a_ad̈́)>0'n렳E0*5 Zvf\8f}#]Adžx8-¼$F%zѵT϶Ɏ:ls]-msݧ4iHd+h3cΚ},Kb 2X)d,tf~/^iޅE)4(CeJ`Fŧ vO(KpP2œZ][v!Ы8*&'z 9"v3%A%MzrhS}7^bŒd;-)ҋ/V3OkLa(??`pYα;qVuoOB#E:K%>( rEv#|?'wrviѼB0⁘ Q`pl|:7˂noTqfSn=cV&9q;xs2 N Ww g'=aij JŴ Q >׽mh,`uy 8j\LH `)Ȱ'L\~Ч<炽ohN |4%:Ūҟ˜%} LP.+*RlAu&٦Q0,""ƨ{mF}iWN(dKij iZ"Vy\ )\O Zq!EoI/ M MI+`przVy)xJ+8+b_\HUNiqwQU?#~3a8u1_'>2ꈢt+Sxۜ/D=!_RGEBdH?̇KӷLQVjO,s` L|l,x+Z 8WqOhq￰JSSZL+dBH%b)NTےH̶FR͠QPvyi47.jLARփƸiH+*o AA/8#ncWjYOZ[잁̐6Tv/T%=>*vNTRܥ-*4ԅ^/(Lruf۶zE*xJeQxk+c#Չ^#YW?Wvq^qz%z&>^2cE&`dxKX#}{mO^]~禓Q]R%z4gR 2?[ _5(_{_dA5>I wȶ5깋-׫&s-Vn\m0 YS8#o>/xUq_EkET MdZJH=:#Ψ;sߚ/NGIK|`, YH]VaP F7+Oj ^*y΍[]ܟ? gw )ZcfhqzbdeՍ|eK湤AL+vb1{eMĐx0F,ơD2jQwɭM|U$$YY6.FfrK^2r\嘮n|6 Ӓ'ETES:$)|*8o =& gU$ 6j7/E;L0STϐߊcN9qlFf|@J%z!]1c冲eCej^9Ze9-]V;!hMuD( & H#[f&MFM#'E N0LZwjl4z@dBX(cmi`Mڪ' {;p18BBҐl G=Ci '׮U D,'@ǩyA2y>A);I+[) Ł<̤H~b9x4ȡ;-k8M5wy2xØ_dBȶm_t>RI\%12zN w1 {7n4"08 EX3A^ RTCPK%6܎`Q.+t,h(Q04*{T뼕&m -vtp̌\k@Q9c9{FDk'"ف,< {m67h_W+l8 Koz(a7z9pXϸ2AXmZ%Zki'Y8-tT\~+kRD Br+'7|@- d8.xR)(S2tx'P~y-(Ļ`AnY^9ZDWojO/'@ 2+_cf@\E.ץxyRkbCj 5QL?"nnɆTY;5x2xKU*l܋ʯa]k}>EZ~LOSۢ$vk1[FHT#:o,$p4fW4eM6@,vP+B%**{ :>*ʕ}d 4]'*?v6n)>#7+S!J.> `z뮬7GSݙR˺A,{˿F4j=_͋rCnI?:G0k~_ac'Vdj]H4 褴"W)aH ТC%KUr$6!M=Y8. ՃxBiLb*}85#SbYDr۱J T+.ZWlIwҲ+s9AurcImK ~]C)xY]C~{拏Dre 6uhw;OkPd/J+PPsd尺dzkt66-りs 裎F -LM^AЬ2>i^HK'UA ijmEW?2%]D6Ǜu"LW pM6VHvIߤLޕ&J]K/|/<%5g_os`'2dO&*MȆܣ k#`ˑ8F~Z_ۉ ʧlj%ϼe/D,Sc>LȄO]Q\96Fv6=(7A'~ئ\VRk_ʢ><35_ L.-Ykй#Y ̨mlF(Gz6fvJ  /9W8 g/uB;>92R5A~ݚu~3|z.nMc[XL ̾9 4JZuCp`Ùqy -;p}MDuZ˘WzF~O=Tie(vL&=ZN$s;w\ϋ٩IikQc/AnԒJ:ISm>߶L|4eRvv`?߁Rzq%eg^<YkmxX<jL\r%}ѥ H-ͷW11ʷ|(|r!< k [4&ծ9AItS'գH8#tB.]\`qgq{5`ƙ]b՗n@OяXݬ]ZfS)M=6MLi;,^ЙW>K_3 Ϡ.V{$h&ǧ?t=*u<!}Fyuj-;GDytsS+tet3@ xΉntE>y۞9)j~HeMU3aUkOpְ!>Y .ԩJAT#\ rx$~3^5; yBQz_oU!t'*_cm_D_dQ!GY֝j,gFKk%WLRf[]4;6LY!"c3蔶{ѧyg[:ͽ"E 5 jTT c"͵bӗTW;$ue‡45 E;z5sf3]L;l0\2ڴsoNdڊ[RMڵ._hP!m'R,"@wު <5&Ԧ]l$V(`~U-E:Y86&=qVx),`CTJAߖu,GT➤PdX֦Ny_ri8^$钿?W%FZ/7G̟񆒩RM{s@s7pJZЪ7\.Bs6 3!VmE:ḑiǝimna锌=\:3SR yF+䨗>\U~!rzʙ*՟4K)t]b)l`~2Z4A}"o .܉=5 Y9ZDv(IUu_#oft*̞ɧK*yZmmDٸ%J Kk4.9`?u6Mi^CʠXS۳)f[Uv#lby oN$=1DX"ع6s'#~٩iNwfR/}&> s"#^46 i$;SFhG)R=ܠ$C&9A:2C|ud97׳j5[%b3 F D'eE_~Ofoٙj,yBF:ގ*r2Ǭ ; 0HE4#ٓf6FPIYQ[W:JHaKH1\?djXqޓ`ׇmsB#sR]Ji\)S2>LD6)aG;Zmg>w532黁Sd GKL*|%}2fԴB؀2ușj0 e,V;vJ'yuY b9a|vN+iR^=+iAO` * Hɲ) bd)j': ^U'}^HJn򐡦ؼ#gY3 c'[XڄUI~`N-h̟g,6Bs] k,/L8(mݮ(sUn+q5C97.%2?6z*X2 23ӿFm1 Hfoq9_ҧb|1r6E^*e2]ȸ|z^)1ƺ/]oՐzf8@Ѽ!nJ&|]ZK#wO#9(l7hu8Fn4^-#m_%0o"j`9C- *r3L)-$"3ʊ3N}$2_m?yR 9=Պ@|ݔI_O.*,N4e-]pF3J;uit m/%?"Ɗg]Lfx뻷la; K5xܪ%S{] Q~[m惠ԅ)dݬXG-t/Ĉ1Kbp2TB3%V`j"/D$$!&?W#jMIL$"$W2FRqUV*$ }ҍG@ [ke|{֚xK^ᤓW="`h[lhHv|+I:|"A6.a=ޓp˘Yy?dMXZThc {K| _O1u Wd%k'Re?n{/(8 @9D".5!zqL|)AtW~)dAoA#1JjFpI(ĭN^Y0읣 Fbn{<%0k٨3 cOY`M KB'kn}@Yћ|T10TO79K.׿b5lQGHEg̅-+6Nq:AIiVHN ѯ;.9y%hkDc;.1,^)T޶Q9Rs[,`6x@u ?I(,2;f x :Z'`>QJ s%K]eJ;е>pgx_L3ьE樈+'OQN;= LѰҿ;4<?5ȥ1:6*rx80AHuҍ1B[$_GIv$cx~@3hl>rT7 Y%fX1 U-GXwoqr{@Xu04X4}{"VɋC͏J]#[*>d {B9/IxSf tfpw&AyU[ :u"d` rA} OcjG1`;{0dhfz7↞pw= sݤ ~oTZUsLˋV݌B@bxݎNv( ̆grg#<ȟw$S6GO3h'p h߃ӳk7Jڄw>8fLup$=me=G lW`Rb䣸iWǣ7ya\`q(S@UU[l#oBl oR7 %XN[nP H*ffSۙ3f0ֶJqс답iĪ#aI*j(f [^0Vl^"o-Moդ?gGkzg,b1FѦ.5_<$p.{T" [a-ӯ.#e@y4V8gmCtb%R!V'-{Nf.z0#wFأ)ԒiO>2dsWf9\MvPN?K>).`qw-o,!顡q]p!!Ӛ *=z t^Wd׸ἴ=;dj>(u> :p6RrIN/ne"E>nr% /J3$0iޚu6_WV Y"+A-CwEeMҢr-%ߗ_B ,C|L1^"5KrM.vX(iG)ğf`̞}g'jnJX6S,0PC- E9B-sU*Kebn+9.8+_Aa^"x⏼SmK0! ivJJ(sT˰"&ګYT"U}?DFy-V)ޙ%hjݛZMź9m;hed C"o0^۴!to I<)H3O90e}< K~zT{U:]FdQ40nM4 &(]3;>@˿2FeWI >x^@h2:e7Ri)WQNPK3!"IEA)ι.W)xt61gj_ $G[d&`UeQKGFv iqVvA n|h剉 %U)#Ǒ,5w,:+Tzn/^nEd7MCLxr86b9h[ԗI~;1.%iK*,9+|f:y:P(:fsiWZUrp0i.8,GYNEjݻ |q s#6Tw1Tn0̹0ԫ끬ۖH0@>D,F5cЫ8!rP#@POnaGT*VNMR$ݓCrgb0Q!ViPG2ȐiJtefРBG`lOd/dЌ#r5NedC`SqC==ʂHZ%$i :zA L):]'L7q˃  )gz6Dui [DpxR[nޘLY}lLFmc2~:& Q 5{ׅ7\CU$;s=!cghZM/1lu74U+O7|_gE,(.@Zj*_epZ-u>dOTcOz1bJY=-k?,fZѢUdcJw,+~[=>MqN&p'o\n6oEb2U-(vݮo90$r!*DGD,w݌N e(mwy'7v`D"DUsx,.m>2ed+b,vW裛.8A otf\A}5ۭcD j$b=u}W'a Be(~MpV;/CImYId׍ ^YhuB> .Up?H"Ցs_t=͚q8F$eňzQe.BltlϦ}dvR&ó/{gDΒɓYʶ0}?,հ%/V3oiu_?0-G)B;3sFW~J.;2z!VI0mY>ժڧ$B 3`& S >SƛƁ' 0n!GO3; EMT,JP2=kTL0a"ĴEE XLPʵʏL Dg=E=:W5X$*88Ns Җ3|6pƛ6f t\Kr`e v:P9WF`p6}b0H;pG}ݛT.EpGo rҦtS4+ >Opu*'ctk6>Y [zjǾ[C @旳 pD)bmKC leլ ۖn̾* A';St8ľE_DiO{ c7gSVZ 1@-n:A_+P R$P=ǙFtx{ݟ)+ߣ   CLʔfmܪv ZK=#ȼ3q^D/gHi"w幝xK?)yّr|,',*ȏ^O3j@/}r'J7d Iѱ7;? m(%\O*ِ̳j3WV&J۟@9cZ7sÃ5?{ @79S J KVo(D,wfi[{Vp y)i0N?}ZV36r]mW?rGʬNJHT ɹ鮽}.-4ї|/aAOe(jJn>곈ؗUbm kh8#X 1?C ݌팈u ]3CYZnԳml[*_XR/[٣N*}ǵ&3-w{$Y  |y+]|anouV-%}s@Qז( XaKzT&(m"-Ng-R=2 * 9RFSnumD„4d̀7]riT~,-GOE3`42&9aZ+?INW\֡;3пvv9ėy՜T &q>A2h_&c5Y ܘxT$j("yv% ;~[DyMfq #ֈ:霾n;1. г?MLE:JMeNƅ0^>Lo\[c5I stqU4S 5̋G{4F3> d,o6-hbз`9\4׶;e.=h/xÖ0;ړ`bz7q,B5υo?@nݬsN3 AN~!hkiMaSJn#l!m) l"xԶPCBC0۟\>!朻nGYnc5e5HQvWfpN8ݨڳ/5 ƂJ~X3/k@/GۙR&N ]n43U{]BfyG0yf!v/P[b%t~pӼjTKY{ѳr*Ŋ *]г.sǩjk88ExQKj[pwȿ\3=1:岆YCQ=_5~5CDS+Nh0'u103 кMe=NCL/{rcD4@〫dNӱ! }ɬT2%zK k?'P ^l{e=KUNC"|]M+cٽr G{Amv.vݴR^kU;'Zc%$V=1T0Zz`]I}UBPx'/-ݐg22^bDR DxTeWctHt15\λ) sXLt<+]P'iqZƽ7pL9tol;w4hx/VH+>6J(!msw%,P/;nYΈ"=$F¤=FY*_^X?GB18V,"GX(FQBz ]xNPAߐk"݆)SdVD IJ-X^xn3%b|nTʬ9 =CزxDw8L1on=N6iM.'*UOb~otXJKy1ɾ$@wbWAZ_~Ц3]DYHKӟ{/ؽs3KXek~昤 V2hBeNa^{emJ@h~w%40?mgY@;BBۙ6lSid`Ū5d!Ec" Ei_fDk@:Iq :IКgC}W Ğ*@b*fX}Ӊ2qz@F=3~|뇞#K8ɲBp%C&[Y}&RS8C jqZl3u'莐Iq2sf}-.VjpGZ#+-+DO h1bw$xӂ(- }mE>[)NT6@^\8/cdt NÝV{M͍Eg- eJ|e*A2 ` 8Ko 0M&]\7f3]=pz>gvc=+^7,/@^ݢ@CF]G쟎Lu>pd-rSy+\0uw`Tѽ:""zRld~b `4g5N6Z7Vx4( +=&ۭݖH_ 97UdꆤdX'-ܜ0lh}q]I tL|60Ck/v$R_G:249EEE A< Sh9Ee ѲQT9%]%Vr7%U>@Pp|ze?WBOHNg]ՈS&kZK.7/WzoNOlP4uc;/=m0 RY#@ +tRly IJ^lz&ߛfzW'點C{e %BZh)_>LИ@tSuym`; 6OT V{lH y*=U}Q-ӥ iaēϥ7y&vԹxY4'?R뻙? =DE` k^pXd7َuȜw\44D:јsTDGK@nZ d9n-sUFGe܍κ~^}bOa8Q}sQQ oq-Ŝ}Ti._ܱt-MN"b 1 ղߪy Ou2;0\m ܆LlOUKɣNbcL&B>-N:=mb`MV|qe֕WMy=p*F ]Zچ~ $.lG“"m _s?m \AV1t)pe|ipĸb'`>aKx$l?y2ǎb,Rwd+ e`ڟ&:^=h*ťKI!\=O?àhvz+su[ԜS Cu3 Zxþ+<+/xzfswN"ݳg0rF _3gNkn䳈|P@ iOROc/]&ksq >$c4mYpOj)&!䖺%lB$e7x:))hyQ_HO:Ly^I"i8x|;LLD:XSe@_L_{si_s+୺Q߹xܤ^] lM=.S,^`io^j~kƁu5h~6M&Zt~/VVDķK/:m$8'4)^Mr3ZK%}8!re=(v˿,ZK _CH/S@;5=8.E_@۹~UL2(:ML8|OșU2TkkOTTEeW~t]С~&wPEunC+,Na>D#ݍ 3i!D_OLˋh\KD@^\ϣ#Le8+G^b\ 2vEW'e&Nj#<}6"kn(W?*.5$0FbV;T&-Ow|o6ȹAM\H+ExDZ:b`p$mP ~bk6`(YDOHӾ0"e",,Oi㙖% $εΕ./ F46&a38lfc۸3/WD)~MmtC aaWdc/ TפPXa^,ۆS9(F.*:ĎLސhm>G/|Bg )|9A{GyJ߳jYkaڸID^~ҥ8o{~smi)_,(W\6! z_JSx`QC4Ӏ"uJ:ѦD1yfFʶ8z5vjƼzW .KɁDZ-T3Ml&hdu3IhUhR^Fԭooq7Z%fp*3н78<k!9kE4j%$SCZ~RS($aEvKeJ6ɿ}0Iۡ2b6 w]_*eT`[ [SUdJBBy^]7nbRU)ߖ 옻z3~>3 gQb)#`e$#n?ȑ3L:mʝ|R}YOR!HӋ~(<ÌT-0/EEV_URQMkя9'{ߺC!8.爿- jb|eZV7W3G2Eq 'Qvv.蹥5k 蘧_jU S=Q<"8i'Q? }mW5-W *c˄:cm?؄'XMijv @=o9/쟈NCdhA\fWL@=HQ2qA9luسlױnL!&|Zưȥ/HZ_2Ϲ㬲9ƧFK(Id1V&ȣa]C3QӨhᱛ֬,i?՝mp^Sbyf3(8::Z\FwjP?<&+ӏ}67<"^Vjo>p3'`rv(/Po&igfy8nQBg KS;xGOqDV苡ϑ.)T䒑/Uhj)*YW Ctf cT裔RDRrWa'Ƭ&T/U2,YgX/6|7DeoPY@4+kб^;,aMkL"*0_R6)L-]ɠ-F 6cC+GMm9%k=UWgCꩃ&|дw '@\7,3k޿WCǚ3[G,_<C: bFQhU0d:X2  A ̯E#iv*_ LŘ{;U`s>#$ߔE FuŸ<3AP;&sA?M0\'ώ-ٛ/G7@ qMNȳdL-ntEB<;D{'m;52-q?e^~L9(qWEGi,lʀ3. @_ YAТh8<>.!y\)n`IjZ8MF'[JżK2rD!AoxX rhڔm|3r^/5<.9|/Ӎ6'w;k[2rv*/#͍v5@5_KY5JK{0j%wP?(%&Z%o&%.gW1׎*~C⠬ Q KgI_Jަ) hq?jXw 8 ?$>xjb5~[ /T'K"Fާ,Uk4g哤P{Jm;Ǝ޿x+!B\%^-£rSԫ@ݵ6δTٝ V ^QIyep7fX(DD3Y®Fxui +fd!/L,<@}+-@ 2#vcRF[1P3p8RN{ÛZKf$Q4rk-(L n/#&[eJg2T=!JWeոnwOݸOLV5Ap(7^|d<{=XaJZ*&v6I3D?m5-GCaV Y>s ;-ɋ mĚaO A[IPD'4{yaQ :K|ó' GJT*MG.Y`MZf02*2""UUָj'7NfJro yHh^܎2A`U88ށb#j>/+!;q<]jT+y-X(;G.s+McIO fML mgWճP^oODu-A n[DŨlYtGrVR> {I?6CNl[`D*",w<̭D)55(nhM]R pz㡶,S YAWtM; Xʏ塝=a,A4&\03݋B}Z-LKk:FL 6I54ߡ/v[e@&E[ w˺-=ojOrh+pjmNEN5ȲXQw+ E.QbwމmG}aС]&%Ciչ`01v[X"9u~KF˪.}Q8h( e,]v1Hf>i/GjiC8!+D$g]Xqs4Ъi'Xژ^ ԰ÒC3էl dc-+Mrұ;pCYN&^IF7 '4 G=MRl;0, Arq~g%aI"-(!s^5xڨ͋FbH{MZ"8>#j}V|_oW,Mr|wt Wm$IjPm qA}ZgDQ)svR-11Eh:h>a֝zax_Ԝ;7z"h8Koח]jmGC<%3Q]AI",z:nQLq6X.'YwÞ5NP.'HmՍscrLJd@vIr]jL%(qzJB6AɁw0 jci]8po#0:\I 18;F!I+?U@h2ꍗ$(MwlN u؃8p`4¡cдh,՜d8hGn"@h5ZFk<$2fGz=t0%VJ@f%Y,󜟌t <2W}'C߻jk(8v^&_+ӘD9V; Q :O@0XNN?w= ގ+"˷צ/~sIR\^d>d`s>~۬qY39lnГZTt6y]H@<#(ւσGBA$`daӘG sX3>̓dmɠ~Ԅ{F S) Xq J#:OvF?o/]PCUvRGg״cmN' )dl|Kʴʿ|S\,yj`چIbVU>ԗhc^.$*+DUɓcc {WXt%s;{5sa{LH3_M՝0An/;-w/UCD笇Ǻޖ}FBi85߿_NcӯWJ`.9L p}I(e.j͑.]\=|\s.3G8Y>oe/l|pZ٢(}G Ewo$ggLJ3_W{/؋rRQ40cY0MxCq*0sqc 쪔lӍ1e^ᰘd҈l *\jzMzT/Aiu,D)xtDAqМ /Z3ket7⡝fLz i-1Ѽ~M}h%ʗ%F!3͘7!(L"\:Q~܁}ֆ(MlJÓUUE0Tdu|$Eew8y=eVȺC2lCt[QP.&Ҁ"|$o*"k)9᜼L?RCT#  Lml$_,kyѦ1o;~/i@IRhI^-NlJ%h!4޴ywyUZs 8t:XqM*DϻȵBTxvq}`X1c'M7;4􎈡2ذOM[Y<&,MiE29ۥPJs۠U9JyN) bkhP q-r@A;0QV j0O`#(խ||R;NOzv*g[̯ 7 H' xvO>:{ r5l{wʵ6 `0aʟCbȵ{y_TY65l$y Չr ރL=k,XKv*=P!Ys*>pxi}p 0hgܲtkPb_9stM68B=Gޏ;7%v5jJ/ԋ,vjģxo(u-iJu!g;LȬan𼝩* bT<݉k,:Su.m9J>  .|Uϋّ|g \n6 FAAڈF G4XAV`/V>*,%/lg`iKlU۟ЖeW}Mus-k k&{m`2>.Bb012r\FPyHKȬ$pgE_o1eKE֑Ÿݍ![eNp]O:JJC nlA _R'IarvrZi÷`ǹ)8:(;(:vby~99VE$+=K1i?-WSGw`)U+}Pis׳T|h[{Ix?O$Sڽt26)A R"xRkӍӍ_S)Ao1#el:vW ;:]hSae:'Vޏ'UNmBꁽP t>0$kxI98O!=gKJ 0hQKa)YMO7uIuoxx#K5-H'o?|[~*gN[}6q^ *r㹽x]X џ &@m#W5j*EԽ 2qV3>K| >EaBME},2SF>=og,% 'fg׫=`V [F !FtՔRg(̗ef,b$s ` BPp%-Fi`Ghcwfޏ >NιY6m9 zE<ܓ5kpTY j-^B5-GE,vIaz'1Oo3f o.kK}{(r]̺L!_d?u U6"Jf|JClBWoGLMЃn䃥 )<%4g9r in1,㵰k_U|mZۦЮHVďo*<@Z'{hDzjS.9tWvLB.%<#81DѬT#Ӹ\ h>G*0ڥDB]w)8c?@Fr o'QN-t͓N4ُcu" tRWlIXs%pgϡ0hrEC"̃'9IScoEH Zg3gx=Z6DYyf66Cd3qQ&?!JJ#B}gBA@@f%[tM"o?ِA2|z*$]3.gkݸ?깟\Iɬ v +pkz,`{Vgoڍz/,OBK}5b6r^k-k\ "ߢl6&qح4|VFf2Ö"_k&Yo 1]c,cfxF2IJюna#xPiHbd?n*xL>"s+'jY)n~$MV|wf;xȃF;'@e҃J-)4wm`CAĸi,32,_M:%/t BgjZOa?xZt?KsTJ*p+j Ӧ._w ˨V|3 *žjH9^ D3&ZqGH #^NȌwʅ 'L5KEEfM4nж=ݍ},xg\U}ݏ ffilkQqx; Xm$MltjȄ(T<K ̿Lehyamw5 L(O.vshCc&՗rOsz_ڄ2vdPh'@G0$ڔNؗ 39򉄋˚o5vmuU8hcR<'\&T6d ԑu7RhhBv펑 DXn=Rc̥^ ?m;w_mS7_o*ʎ_IRhXĝd ֐*e,hњv6l Ȳ\ RJt!*Q3O/yZ1h GPIoGOY؎t/_]^VM@ٍXQ3 rC<`Wt&O|6־Eǽ)ҲmVN%Q`xN!@  $d|x)Ztrdg{q pi1(5Js,D]}>{5ݷ|h/FeD|u5 %P0o)@裚"xG8\WT^ǥ`#;bmhDT)*YVKq\^> ORiܵؔ_8ZC;GrY!'06~n@1RkP_PX>O^ht9W.rJ&,Frgw-" B+< XyȩN1d[\h RVQe @)HcB}to ҅?bxCs-X!:!Ls.Ub廥}ơq,h\#rV Q-؅1`XL@ "}!D535hfkxv4PL'!rKON5 YIFou_^{<}%mÉ/W)#wJيl&`"wRf889'^kSd[M/?nBϤ k0[@5A||f5L?M|5+V92bX}h$0ͳ:_a*~ LlYj 턙sg+$yІ4-fvDQ)ue#\~#ԃ̛l$508{VIzdS;jl\:^ 82bo?Y[($=uȽq5ǃW.`BbG_PK/z2i:i( QAS`Iɀj&JZ >TDvp+dAظonVz&_o (|.\W/Rs(T n#Rc)Qld[V]߮RTp hִBխD3oL1p98y$*kU8G|ohdbE3b驎{A pL]ᝇ@*f(l]Sx&UV4 a ߩ&@c, "#l+"_iH-6ZOVʭ6wJňuY8kYDPuN ̹:= 6&UJ\tHLT۲ iDM֋~op1M&qeH6UI0;Ό_T&v ؛ zg :nJ i՟ &1;^ȹӃ;iʹxCE:cEHC%LM%E+L4zd>"1J/ ^aKJSQgJݝQ$̷Qo$MXq3 |CẎ{n,ҙkR"[8\p&͖pe{yGbZJBƽQA1o}!ztr8ȧ٪)x!S "98S lO5z9&åoRyT~h +}做T<55euģQjμFFr}  c.4T('pȚ̆xQrf U{ڹ˂vP>b@r $- tq*3P5&I4  3u7JsN8h~:K6:5u,4'opCӉcrO'tKNP^1'G̥NZte:& oͩ)"y+=G% af)s}+#YUrCY.bxӪ7ylCIk([RTa yKz)2=Xv$0ӒT!dTX+Z%Wg$Sx,v*} yD }VDCmew9gv2KJ9O•)[VxCć}Hq?3AFqmq*qhX[y)%Us~~Tٽ#zzP)ܴv*a9QUz8xBΈXpz9#.$}#%)aD(A$TFD!6.l{a~Yz7͏$a ٭Ѿ9g 0[{p쬻'j=Im؃-qU]|V>.b-t!0WM8pW콄wN(IAR;Y@s4 V{Tģ( q]).dv1=r3wX!>H,t;d03`Iкyyw]s .vb%_@)#e?-_"ZBT pF!&n7/D}"(lN i>ⵌgnLtLTA 5)]yXhM8! .Vw&NKo^iW&1o*L fΧ=$5mTe~ v" YmަH@rgJa|o,\_(1m?^X@(ؠq}*55Lw4OJ^ $n7v12SJ8>bej'鹡ک*o==ΎCe+iٲt߂8w6MY1'fCk]@qg8mr0zdGWsZ*1ाiax'a V%d׃$r`H~9yr!qYv[%+3=B+TH\Pox)R8n9G GȐIֻRW_X(RbK9J=Hޅ jXz'G*vj(`ٖeܾC:E>s[ߪzzX&4ˊB·uZ\vФ#KNԞŎZ 5[ǽ(f}d]wYiH/Om@:Nx$!VF>5R; Rcҏ~D"Ш\̪`\ԵlUEOA®0\_ JRBjIVewIPvX^맦} Hiی1QHr%, e@ttyP69ڜD\p u!O>R3TJJY>X&N?,\W-¼ňw>oJ<(;/P\Y|oT[Lsf?`'ĉ޼[p"hA1sT?&6}1/~"ﹷ`z%JQ/O>AceHŐl<'`46ʫҽJeOJ3Ml _71A+Ypǃ-X{h4F}y}i 4=2%M=3 Fʂ|Wryލ\кN8 DY<  kIWR_ g4㿚ȝG.bdi$:;Xڊ&)6PB4''y7ɍ0vIiPljZ/G2V)*Ac?d+2>x ?FtG#^6x ^tu 5,o CZ'xUجaò=q:gV}LUX2!,oPfb) tI>tJV0QK|źU~^mcDs[}ttS[7ٳWLELFGVP#p62+g&sKy>(7[紌\"]vi xq& $-MCm hÜ> tB ?BpwDA}l$;ƕc߰fFʡ>`}. 'HTYU懥򴪡5ԿA Bɕe~^".U* ACuȘ/h1%Jτ1N8.kR lL4? tITVpe0-ֺ0B"@,/rn4ο^*Cbӏ t҈}um޺iz۴o5C|n1wkEY&]a?YγY\VɻzHE17w̓E0gP&i^"E+<O-3r\Fy@d:LV0:hC(j!H8`꫍ȏC7R cz,+bmgM^$,M&(_tͿ`g܅г:~ ۶}mdYb@i$}.*,lT7VS*Gl;αYdEl\야g;wW3*؇oq1$:i9ET8Ľh[ODuXP~x#x3ԑ]?̌amp¢ Lc_y RLGcݖЩk?#s#˨ʩ}[f0n.g55Ƥ,l]NV~-"}=&j-m4ᒡeM#LYrRQѢsVZ&a<Gӏ|ٻq8qzY)A"b? sBIՕOZ'|5SNHY V8N7nJ9'At}nY[Y߾eH "Bo\09WcA=-z SaW|LU]IDYS %Ֆ_4"ُm&_;.̆={ lIPBO Q (zvwQ LףKs"ÞkXP2lNœt*5'O2"ܐe*)0i߹?gL9ey&nqa5]P[ Z]ډWb J:qiL#PCXh\ hp5PUaasDQsxk~H/`M6Tq[)g5pqUwr$y|91.Ύ#dzrAH-~9zAʈy;AO9`ֈi)蒮œc#" prpכ h! _3 Kh"VF_!{+vR;(5@VȪK nT",W"] We2a.Z>MZ#\23~BY)|wމ!5S>2n8rr/o{",v>JobQe yGVsT,f˯N[\M4=Yr<V盒^?k/<_tn+8un}k>/f;FwT̤Erݫl3/W5@I(=f) 4pMcC×\}G6ݬ!eR)[%$[bVƶG]K|2_IB3 ސpє`zHH3fuj E׫U) ev Y8+G}ԪDlc ̸f3! .<< r(W+^Ox3lRJcPQB)|)3;Y̴L᥉PKVzA2 Tin-)sHNl+ut8Xup$hf 3Qw68χT=j_.d9)  EzdZ]5)SJFb,a㠴+d8V?VPrtU]OdKr^ZC;q)xѽ~wiC4>c<_@@]ElY(g^x^O{>ꯏMTp J!1!V9^YI;U Nt+x5Z<~c[}hl}sGYss[B*Z,6txaC "am,ht9EрsWO)ܟ3gWL%%Z L@: iҤSk&8QsYYL4-St" wǐs  F5E]6> RS-=AJ#A)5ab)]8B-.M;k؊qgЈbIu(7q l Nq.oaՃZO)%#l{KЧ[}[p0 }*)2^źP\%;b(dc_umcg!%螯9"µB 2 sfiB]äqS֘ARBbFF`h<:QZN; #FJ&ڮQAYƖlEWˆ"B'K࢓(Gh\Z f\Mh0Y>x؎]czvjN8ޙ0|2.g1u(UzM4K1u xgzJV,%!WBr % lj hO8횆H[EjQRmp5謗Mn\~4;O- dɣa +4ҋ\|a{n`Y߇aIkrDOx i\Rw#(sKߢ( OPMDY1;lŠJ!ř}xggt]x/DwGQy*xM,5~YlS)8GO;xi+G Kȧ9 %qAf6]qx_V豪4'F'd Ȫ%pa!REh}EsjrHxoFx"|j ḙ'&b%.۷Jg!+&o'Ǫ`@Cm\1WX_i-*'&.Ǜ:T: 2~+:aG\P00חc1B*EM"TSCnap߇Ma32 %Uر KH {8gG |=.W, M jIv.C厺 [6ѵV?0@ 8QwzS6r>!R7)jx+jp:0 OcނPSQߝJNxod`te,x(C-?D'FS3ㄈ^+2$$$nzmaM}Z/@vꯝ%U >/4cհ5P_ox럈 ʦz WݤNȚAGcVs|G:[?Y5#?BTN1!T#rʂj @vA1B*k;?$Ax|w 5l S#Oy40 v9}4"[z#.4N Ol4{LY5>ApT9RS`Kmb:/@#*RM3 נT c>7d{׫isEOsVT;fA5 *]H0|3L)}G}bi"7]H)nJdVi oKg<)Q hmw.r{x/δځHE= 򰞏ƇQr qJыB+f* a"xbBВ~"{;շi(ѺΒ-ak,f0_"MGS>=NyC`f}]GfX5Ш5dflئ=|+4Gw;Gg( 6Ou'-޼oLU8: Hc_DW*4lNYQ? (.7nmT(86\sǜF ݈w%?~v3>/ҒUfmoCG +bx*7HtD:G/ےu !H\Q̂{' 0x>lkB'Y5uX@SA 3/tUiQ|g y4Sc: cx̕( ]& 'J1` ISykUz?!qBu )˦npSa)@{f,4""(kr<_ n@@<]N%O@w=\O0w8AJ̃V I0wG6 ?ghm:bD |.0Ũ$R7aYS+'7_ q̄5;Yi"V%̛E ֮hGG>C,ᴔ l7Eu s+{sqM0#qN'3-,yKh,9ȓ<C*Z6w6+xY SFyC#tDΊ{ Qv׌2VtX.R>T٬ݷdO@ fsx%擎9?F-RkБ$ńec{9QQdI{f/&=,D09W:H|klTPt,D)bxS~- H^!UFc.>k}>])"|EOID聆23?ХO&}Ɖy9uYd\և;Y6@s|XmfyDM@sbJX"ÐE.ly0M-1 }QqqyՎg|9,Qk3{E|\hlx<lٍ,3 gtw,_Od) $\9KwZ!hrꕛGl&W4ݝ*3 ΛA .qFPa h `vc~O9#HhTc˩Ϧis 2eq=f3ڔoAA!o+^ZЍ.X% ;6gXw񥘽) ٢=POj 4GX'YS[حiB8OQzmHiao}pI`f^\IH:JmV.̄dB7|2ҷ'NJ26ZZ馍-0=t-Q=.B Hav,U-M<_hKŚ$tR]Fbr4#v99g&0jLKp)2ZN]UO>21I0G4y'q.Q:7+ P݂gdGIXrqOhE!έ7CSv=UCD` 6-+ ڊo Ju+7>[|ZAn .o} h-^heO4㤦3u#VWJ\(%($ 켸p؅5cߣ I=J=Wj{H[ 7:4{GOxI jWY , 70vxY .tkDŠKNE. +tكLэa%;)dcفT3 T0ґ(^)톤iz!)ߟ%̶\K+M@Y`!~o:S LK'u,c_z5{2CrblK.Ԣ%H.iAv[l&)oQkkbWRXOsJPq>lTF~PAIC m(A {6-e~|aX*6r.5^neI7uZɵ_~ xDJxTo17=?ebAoh* !@ɩxnSw9#֘] 4D M]y[&^h Ezs ".}$v+qɁ, Po*YWF<r[M}&͊j#T?-On,u"cCnH)W1JLK4{e4Bl|;΄|9`U+BGN#vÙsLU{h2bhpo6bܱ;+Hּ |mdOns. %ar612plR-5z=s0M,) M+k Ϡ֠smID= 3oB 'n6̘u4naE]bX j, 1 ԙ6ٍKLPǛ<'bktRnf#vŊ[ X:4W< ?" @_ru'HBp]&3 -Eމ/eR0Amek/U_AHvuGe!-̜)_Tu e"iF@aԝCk9F>JtYV~Ⱦ}5-wG ֺ ::+e|#7 Pq7(3wҬ: :Do"o1k 2?68g<9ė/HZ~Su%_ Gq~=9kOOA^x> TH rg'b;YQ {k FB..H/[۵鳅!<gR M[c6?$*(c`_y_lj`:~Sy_ߴO\4U; fzqKiT8bK#;jL081MV5/;=\4ѥ-T1Ň`갤h'[ɲ]R$B?GgN8q+8!Q]bAr"{tɃRا kz\`* 5 ;k2+mfٻtcJ45:>R/'oW_y-"cl5'uV ZP 6v^Z"ӆf/YŎ!p7ѩ<$2bz2FbNw;EcoNsHz&J@=JYP~;.M:Cs_%9tW<9(Du"&9IͰu=YgI~p!qM&!_ pza9tybĿr3f}k ώͻcL8c$=S6ra͇xgEnjݪPӇ DeUŏ₀w.=/u!:^ZY{$HML_5e),vأ | IJ&2ڪEܽ~\ |489ә(^xxCvefyQze~k)ȵ #.el5Mq3&Xvh0N\&LRĴgg:ь(P< %\oHl{Iq/Fjn{n$)*z&s@f rA8jd=rgS7^b8أHW!N<\_L2Ay`JFR# i)'D~w1?(JOz: 'ˮtd]kE `FsOF#E”u{z%W_gH]lޑ`Z@Nz\R#"f 042b>:Fvn_+s I, )m|ZDK.Szӑ5ʚdŠ}"rض{x1rӵJƂㅵT 3^v:ퟓ|]޿p8D9gW#:9 n<}*zLyR j8;u_+%L= D A=GD =0>bnK5_ܒ:J!%RkZR1 ++Z-?qs8_\*v>g$I,^Cd^Y<" q@l쫁.ɴxlZ:\2։:gJ9Vm{gr`r\;,hTg@2}^Ɛ|c W/inKJi9sQXNl< =Șt`=k9VW1Q8*R.hF7X1ˠ5D}٢zr\󷨎hIzA(hm[VGQV>Ԛ-f7`HQS]d+w0}KX'dɜF^Tbja* `mA7̶e†9t`IbͫHE_SUqժ[J`<6!ےC^j9ϸm(%H,Hd#Fnn">@npC+3]PCZ~ 5Sh{BRJME hkkm+GšHҘ0>2Gg^Wh- m4!g@Ԭ&~VhйeiEcA*Wa'752)cM+Po%]Ilx&M5绥Z> $@ $Qr&.+}yٗ9F TJ3V 9CMqFG, C5P$OPk$_'ڝ<:\iVeo!*lR7!!&9R*vPiYL:uQFa"ԓaf0DQ2N͠`5 \aEsL>qӌ,/DXFc :}<*u吥.΢%&ѦJMovs7 w?iL)**hA՞VY< AXUCk"7z_nVRǂ}+. |]4׾H!d2R=zއ'm5o){kaGKNLUnJK !ʽ/22͔1[3O$E&+2NC{A/l^FSf$f]=^:x]JJf$\Q"98+v\!1B)?{|v9l`AO9^hɑ CnH q)2gKIEv 1ǙV|ҳg<Ċk:Y^QML؛ô mcSn/0_,Xj2<1<z7kL߀q¥HUt :p%Mgg㕱 A@+ivf,DC 見wjN]厌Q%껉+.#\GE/hPWL~aOlqoIm] Ǟ2㛄\ 'EʔTF{GHiԻ5 y b^C(/>'{5}#_]2qyI[#5 _% Z'aburԍZKpE5g#슡|̘{ 1puFO }B,!nqCruTm/P_28J[HA),E}NЧH/y,x lwQ#"0Ou=_OFSG"oafdIѯb[dF2qz>Jg Gݤr,WgLHogo+mW7e.)AVJ*!* fS>'KaLqZE4{Rl!Vh0` ,۠vV_ٺ-DZ!T=`D|*@٧vqWf!1,Q9·=*mġ9akbq,;)xqm7 .|x {eNOmfnb XeݟW|NA߄ѿ;->|QuT3Z1XY-y9aՅ0ARq0abE]V&2[^`1Ӊ/?dʶ:+oIpMzlK,lKXX? dj62{4?s;@}^&Z8XD\Sr̋|e=TQmՠ6It8R$ALUL<!RNrw8V>J"~`'BX̺oo2M5Яφpx?Yb4g6f4>YupAù !NǛjD…sGKJ%>H}ai&E x Ou[i ,0;En#1َ"vAK%BUAe쳩ΊuEڧaW~NDg) f}\']m馍sw+1InUͶb?uj`3TIhUtqٟns~@)X q <k|=Ci7 :6 GnK%~:d׆[~f.+6ś;Dt:ݰ FBCCTj^qsS|Mw6oDѭW[^mś͉A7A`©SK?/*Y75;9+ A9MP % Ѯ2OPoKT[DSS m ?gH 14w}aAz'[ E!rA~9v0SNMnePw+ԈyD#I*a.@āgGD,ڑZ'Eldէm*K0r\JpU*KBP 1}EԵy+={y#VɭG䘵ҙv(ɷŬgzzTIn?5Dvi,Y.,$gЁ"8Gu {n+(:. %҂Ϣ{BK[zy(ݚܻ y"ҙDo2P55w:!GUֿFpPWDEyH}#]g'M/F:uIUb'4qnggr[Pb-ބǿ&S.v5Z:}aw]qOogj%9'aꡨ|RO] HL[:ݼxM-7ji:?=Z% P{QLþG??}|8[ s 樠W"H2;}L4-cGVKc۬Ȉ-9jgҎ0~98J%j4WYWMB3@Rw;+ .l'wSbHwB.LʰO2(w|-BZfa_6M8s0#If bŶǽKYZLDQp(鸟Lк E vzDobr.srb|&&m sK!f!;sVζ,(L^3 ! Kƶdj[W(x{C^+j,H!Yؒ;A)N\ GO)Уѭ7 [˘ƀEfb݇ E9wN{$MRwfNLFRnÎ!|z5xO43 L--sǍ'XQN1]4Ț}Bv|,n,HV[''ҍLBqЙ^ȇte@z@!!-e3;W !WG1)8aƔϨrmܨ|>m!/<ٶ?z!قK6IS!m@[~0ӥ RMu$yw'$co>2 W*R*MpYQ!UIdŞֽ3JF5EE*[@my&wVzpm4'W/ :Bdw2ep ڀ£+L6CKSa5m8"םAǑhZ.cQފ0$w#ƾ2(a,%6\5y ̲W k[kF u_..-gA>€n2s/Hp%$WE%R+Z5rkNuM]!Ej$ MfX{!IepNd>{sܯ5?΃!ll;ݻЕJ+y/rNDYvk=gniVsf|P!. pz/G|Pj,)N'&xHY͘-?h @񌣽I8{tWe MnF|24ʞuOͰXDpGX_u8('O3cRa{` X lpgߢ(%/ʲBmiF.YWoXU9LF|Clvz}K$hV^>e}j# nˍZiŪ76R"$˜O#xLݳhoƅqW4w4r|vTf-b Je]O怂l'seWGxSYEi]X2H *c| CRQM|W0Knr_fED[x=kZ=Fi3 zPc#VE%H?(|G1spW7/#Ak="!"FW{v>k)j"}JGr%pAǁǍК dF>^.=i~EMQ,M NυGHuDd[{m ,rIZwv{ᓺuJT=1֐3ZGhafD "fF6%5zp+ :_lg~gFHm<9(]ruuN8f0^پ]_B@uY4Fr{U{:Iz8٥ F֫@de[ExPRvP~eVjJ TDaNBq[L_lj A VTȇE)Gu~;+,\)O_%dt7dꪌK~C4+sI4bݱ-2[A FEy\c0$\NzAD; PlylNA=ٳ8~wo Sxv ekA1I_*lay S-2)6"qIlQı:8o8LgB1>Ɂ&BqNB9 AN$U$Q$tOlOWv5_}aɌI|rsD?+7-9tRn$vij#.Hدkmϯ\pق%b\{g݇y4ɐ27I8kR9]r|/#e + P3&NysE@x4'@hEvmΪ|/$Mo<mC,/lŒ_'^Ͳ$B4v ڳ7N<,qSxd 24oT WDgZ,U (嘎yQН|&-UiB"idh(Ύ‰MeLt~׌t[F??Ş$5v~HfLyJwj-ZK$OY$Jþ@R|pr4̕β瘷7(a7]SȖ^RVb ݗ'k;JV7f`ofI/7!a:!",uktp8bVEF0i}RNgk\ʾ i=% ȝ,>,K6ny[AO87_GW^.]ԧ y;6efu \*\ߌFOc-3@h DףM$ dqfdC ]joMefMn<,fd#c>T_UA;f5m%!p5[7Ih{$a%(rB&0۹u<)gx>e,IO(hKdThg晒*j#IZN"e=͞ mcA:ٞn}kJ1HP3|MEoSbov D. e?ˆŌ-ʗɋRL.UyjN8R]BwcqgM:DƤ#b+ D&=fGNMkn`B+x=WVVB"EG[=p' T!a T/P}YB,gBU#egY"j#$&\WAj Qm26bS&M;E~*(4"jQFY7{QDt%Mg3YNAFytC[D뒠Hq,+{*P}͵._ä0g` LO}yHw&hkOOehY "e4ʞ{_A!a]#'yyn >ލ5թy*wIfo6P]$a~iH:{w{2+-/IK4(5Z jKѬ!"LM6M)]xnU>֞NNS a(mġ| !|&qG S̎-^[V/Z_kt?g/އ %טڛ#Lw1RjhJ{责=+ (:e{gG^/j# Mzl$3ڷΣJnF&qj!N">xb$dwȓo52zH1Hf@ߑK#?= N {|or*}h6o5.2p؃Dv=KL: SF:&\'Oz>iu 0(nB#kiʋ[ZGnnjUY7*  1#T'qHcUēEM'&x:@zƝcOw~]_h9{H6r|$oaKSG9DQJ5iV;GKᲐyhF (8.<6 C'/[9a"JۆM6CV,JW3wIf ]&<;Jew7" _A&axlHdElgMbhFfGyU\}hFX2g,ysu'= R.<=M P}oQˋ,cra`G7:hͫqbm3+ /4dB[MÓAt,|wU?hPoר<[h:q p{ lK&X9,!]6xd:a#l f|S|zM瀉k DLl兴!&/e. $Z(ZL ާJ+zm'C""6|;6Y[hiv7͊bAw'4vŢ ΪC; Xz .EC|PXl/d~] 87EO׭3/)Cx " 91wqԲWһ: ṕwHH4R(ls}i/c/x {j.8T3"ٓ2<%Ԭy_*PWM}:ܥm}C4oش_gN.ZFK =WHY"24Ap7[nՙ!*pEsL`~f)<@0׭EtXK/4)'Y9:33adGlbcL\mQڒ7u*.I1O(.2=jL6@,Z=|)'.MVEՏ%{4ZAKo1iK-\CK-pI,e)Vfgἱ^Y OD}8EL)5n|8#tr"Yp&KٍzIz׼MM|h^g8l!؋#aa܂5 Л.5A ,׼O{ )zEWy.[+a212XD*))-pã$H 4n2" KQԛ:{F.K~Vx+'o@os,4u2 י.vz3H=n~V7eXqyb+㤱މ8Xt :Azujq,A3}®INuC)n+ShQ`=v GX/I 4 l6$32Rٜ#wX|>2]睙QNlp ǀv! }XPV|0} S9A>"# DnMl.gYO>Ӣ Eά(:%ƄoD B3c¸u :gWw ]ND#Ƽ\;(Z G,uFW:niBr\lPI8;kZ}^A?NیղV ī0#.0ے"ʽIߕ.IdMvQ1ڏHq?fzˋ 5ˢu{%r&B:oگ× bCyWNjEى'P怦ee+0Bt 6ቊf+`P`-2MQS|!-L%-u9MjQ]2Z!3/=DG+acp HҚ3͉J8ZEß0ՖYcD-`PoXc,,aλ"qGӜGBaK04 K~(QXC(-QuFLks,u58 ᎌf_!I`e.y['3ǔ,b~q 3BKMBoʠk^C@]G<֑\ .>wlQU>X,4WL>6 Dv ׶ ArfQM>k` Ofo*cX6g^pF aagX_$qo0kLτXq]+{[!++DW:/pmD ,w}M (M8[IM3v8yό=Fj0>R>30iQH7gxv^)NR }8'~ r^xz0oK^(DlTέ {,jh8AW@*s#JBQ&]Zጢ!0}B m0+tzPqD M:(@ẍ́Ӹt6PByfE8Àh{ckvqDWRf?D&)ۂHycuݱQڿI;+MΓOL1'5mˈ52] imC=̗\  #W.IXٍЮe+1Yܗ}\Z GwXf7BQrY*v/6OϓB@o U5 8^qHwKX+<\hdʫ0__ 9c F6=f]boens,܌ PkalLiw9͂|Hu q ?b%=)JTui)&@ 6 3URqu{c^TW J!y,Wnm@/!GhH]Ӟu) xz퀣q{B0'XeP7̎Em G`,Kkm^#+dGЂPv{6{jT˸PSGy݆L ƝKo5ā,ƻ0})%2) iߘ}peHw#>P%Cۆe[i)})p0FB1k:*N%G\R4:ӹ%L+j6B0ɖfy #o}&7&̰wń tG9O <bĂ~;3F’2|w'|\%_~q&)߰5c~0 ]䤢EoI!y&dtl3J+zϙ3f-'A zb_WPvsR\r#O[Oc#PSp.uOx4si*Y:S:5dQSSI:>X ,[;'pcaAHkhg#>en#bOTIO)֫w{%B/ˁ{9MR)" ζ]+XG!𰮨M9tMvMtW}tntTR]8Zܬnɶ$^y#iޥ$9ձP4ͱ>ЛNc jT_*B6 8b SҾ痏*A?VLyl8m_BK6X?H6,5iVa:h7Ql dT[-J֬×b1κ wu]{<7D1<&^0աT87X˲hXOxi-)*D-45tF͘b w?ӅMm, @I+6}-f/h9!K46.6ILQn b:`DTkQ; a[Wt|ίLbAɼbZM>X-?qh6q1`ڤZp6#_ܙe}az<jLt.d~i"%=qUˢمs 8OsҐEs4vf`"?FŤW*C1|:V~vU|f)g4?>Zm|}!KKk<P/y9] KN! ڍhj}Uެ{T v?!?ﮃJL O6}VxBoM np|մECUP͠K)y:kt6G8^Ob@[.`.k+Fӝ,/>ےd{u+YEl6a6+ةuoqB})љriǵf]:A\ QB|"z1eꁜ\71(_;og\2d3N)3׬_#R BH"_$ʼntt*t? (f̳l4͝_xjB53w `ǚhT8^MX+]<LMOsф}_jri`w`sMSWA.QF-w]Egv L:)E[{Wˑ/: Ҟ AlL XV5=C,Jm>ٶlmSu WSo] VY(1/+`q>.Akydg3 (>ZEtXlt(3Bš*ovJOu\i2 .ql}2G*=g Mޭ 4۾rP<(:Ŀ) ŭm_,$ M`32G:gw{1+p}cVz]+yc%엻PtjZɞYږ$єd4@IP`=yH!d@x={ x;ǭ G}ҕ -!$%)$8V/8Yn (ަQΛMRdfK6곰]bߘ`#p*CHn0=I|h̅LllC'TQZڞWyf±q\9 O"Qf- @k?Lܾf.4,8M|?q"<7qуs! O-ZIa{ #؈&Ы#s9#K='efWtMgC:Rgh-:yYpO~,PX@~“_r}Ygv?,k|Liۆ Vyc=ud;{i94*XS"1؋tX #<}j-q\`tc1T$J}9dP7oUH8CDWC|@k(UO̡hO1FNGԆx\1OTK[Kdm+CCV+0o&nsڠldK!EhxsUSs[,:34k6I:e@~Uu#ɖX&bqګ6ơYլ\Z5TЃ@-O>9lK9nf*& a(igSls2e3Axtf ]}O5:/gUo$' tvi]^ -[KqjXy/jJn.;\BPwppR ]8v=C[`.&Y!7=926r*(iቌ3eN D6ʐl crtɸ*{p!?l?Jat(9W7F <#]ln!fJ@+  NKz4ϧ2*Ը _!PN'~r=WeGNrMKс l]we!S~*G*ql*X4FaՉWPK묋kߵʢ ^]ݲ fT#__ȼY9 +ezjlUXy2;{K蕾euXQg uvFwS ]<[})5Nēβxi_Qm渊Y4Qֳو8Jc2 "dMq8O/|R^0ɤt*O7cakQLX _u:QX`OJ~YFwpwU MH+ĵF TЏhjw"pXwkopPo y#Q6;I2ť//{G㳒s)(x-|~]v&OOE+Ww[p"ZZsE*쾙?Cj8,qg%:{FI>gƽ]X (g~.ēR IrLd<-Y~McaD:C`tQ|V_Ig6u=[E|DK;IΆu}g5bnxo^{w?E{}5lZObAਚPB2ĕ6ʒ6$@huᐰd&B{h0_ʽu3/zsO%z9"RMم5 oSXR-"D<N {.YTS} \m?AX `PKQH^U&H?SBl6FpfTQ=u2|.|i<iIq_eK HDg)cӫ_X;N7"T$䩉ug`z։- \g,VݼGuMs,b`g$!7Z!7 =v"˙@Қrޮ)fo,yl9&]uU[7QQ$8mLjl}*E{ÜL8b$)*r'EaS w՚؟HvғsQ6j+v|ԛ 2[ƁRtoOvq=ifY,ty(mVU."'ˬ\/XY~&K{јƍ}F"ǥ^U=5b#5EbzLbƔ8^^0 \+/b  9a>Qr@rsmj|uԠNxJBfRR0VUD֠wn<0o(̴۬Z=GC{YHV푩X/-A]㾲$'ӳ;~엹[:UJtl;Ӳ=!O-TCT=w`xbK㜶 a)St\<1 do2F 6<x#o޽$}gPGXM|Hyȧ 6O1mv>%Q.\.d|G"X&+S6}>&QY|*~xHB_P#Uf{M[ [&$z]#͑)5ysf`zXYxnJKF@hnڵL/1} duO3e0V?{JS&[XCKj^M$3B0Nhp)yB\$zVG@Lq׼ q\-IZ^*N֓2[Ҹ4,72'z΂9 `WvrcRbF$)oD YGoq1"WgCSL>?tNMxB&ⵦP)@ XLYY6wI?+%^«-v3~>G舃DHAZӇ5;jKT i^w{+~RzU ]$[ҍz;OCsI.^ [^A2W;c?e(+UP^R_euH.zN\oO, Mٝ#8]ק^4Yz46 P}ږkHhweo %fP2ǨtС2n_Yrܺ<A C2 /0v4U#wArp@eogvۅ5 g۴ 27vWC?/k&uUg&T́9`vN"C<GqNɼxC pf-(>5niImiG7f)4l~¨.鈴 4  Q]5bHgU,WQbaLx:$5u>(So;^x|]7Ygƚy'@_ HneswJ'/O:;ӆo#˨FB(lcf|ovl; -IUu܌^ }ChK;uhE+h_i f=!Xx2`~s#*Lz}30w~㩼U/2^lAcBQDJ9a&I84ʥ7$B}C(Fխ W`|gHfRt9䔓lt`% \Y(xMeA(`Np}Ҵ *Ad>FhVccwI{X/pwYf*)Y 2N~C-2wEH;7 oqUC QIZ3zv !\ Ңl[2z :!ksG\U\Q_CXm`A&*[d^4 v@{Qc45u{qWKX@j21!zw"g6=/"U덹Q8r[]~@]X8=^i4yI_*@#E[ <Ƚ1\jǎ2xTP7eX"kHK^@pzvr3ہG͈f+6,&)%>.|L>YDY@0>UerZc&7/ɮש!.ڳCAĀ^nZCHu56HH\Ўoo k B(tq&03 }[W kOjm8`wg*u ӈ V=BK4P5xp(r%L,{I^yu$0Й>F2="Hy;G$l:DbRw5tClvly%x| RCqAp1^5]ZUHe~P x;a0OwK|51ҙH.SUd,Q2wr1o~HIwCRWj $`#DߊQ3%RvX7_ G^B'Zc71DG1ے:$=,!TYa`mLe'h9/v ;os'ep!ѭN9Ⱦ7q)Y~E3cs.e4_{[z=z-Q[-X0)MPi>Lcb1ׁk>.A[$d}6tyL2`rް=3i&+I%^wGDzS`.a|]+ 4E MhZ;hq`\6::MBU)٪1QtmtN- @5tU^y&,vԚfBsza1c{·z+Rr)y@2>T~u/Lyl$(L"_ܳZ`ǤF]NJ(PvL=, l%3ċ;|ŕs{4CF1]D׸G?$bqLaZZLR{% *50flK*.ݳ@? ST@*TNF{ϸTcGЊFؘWb1`nOW^NvP󓓚H]l; 4v% B_f̐τyWbw8!_x҆3fxp+񽻻+%&z y^FUxqUA;ȷB˵yuOHa&2faIyl&W 6 an^=kM"΁݉ܬߌBЖ^ ϞZ)Mסܨ{gFk 3/yX V- (%FWn_$-wx%n4+0fN1P?Y\;N ˯Im\ןb">0S/NO:&JV% ^)ڢ6*UشBL NnKTˬ>LUz[|ezU㕪]Z&yV2P^ޤ8EI=OjxUus^9dSI ,S0`)şdm1 /Fw#@sa-h*Jn%2ѿ i>xXHhD p. )5NoXh~|Sޞ,M^6N϶ۻgV׿ բ5xQgIW^%ܤ,e. Oǹk^b Bk`d ǚBg7vx{q͸RnȾٱFV)*-#30&3j]n;(eF ;ᜢ-pp Cf+Oz:ZWhxEV3exDwkYw%_ n]Mv[$wdӇR]ɟV?7 'wl2M$:/) y11%[@Ha |s9DO\\\f?L7yq&Y^G;edxꔨiOCn\bݍA1#{ vY;ڢj->0e%6t8VN $#3Sa3 4Lr?&GՕ=qSk$[ַ׶jwvO};w2(C=t xTKݩh=@=m)q`$Xeh#|x`tѡ)M29É-lFZʭ ]-o& 8=YhF(m|RדctVH퉶aE'_h ê0pÛlA#̅)7F)"/z!CDNk4D e;Iƍa LtI0#n+:B[M]dLk%z̰vR:#6AY"\rIȈDw2`)ZXWQ=jQF [Yq #L;yU2M 3ul Jr]@m]\`ӹA}=*řAm޿"T,~2hFS;3YZ9{fbdhRJIdˣuX|+p##l"lR@T6à ?p >q޳h:B'na F?*;yE6Qi%*E30zDqxEjrG97KĠ(P *mE) ՅRl%݆OTF㫱LQ9q;K~ 5`ҮW3J|ǸrY V}HʼQy$rgLd6#8Wi2J*UaxZϋU5|5:|)٩CӖθE|eU'7!h,< mOKroOZzӚC'i빁< ĝ$v ~SNoiB!YP*d;tKxi1RֻaF$?Um \C`5O $=lҫa8m_{ /(62/{94d\yRR?mlhw1/X8z@ٛ2wÕQGPe#zֶu!)AepQFc&Sڴ.;YQCCe*0 Ld {選LrO;}H}XFcW13C\U=(n-'[ ы!cbCqmyOA_V6nަGV]cXdAB5n;,U(# |dm'sgԞ3mSt.ڲrE?T&\\oR? Ͻ"s¯{|(N#cǮ ݡڠLR iu}R7FF|{&|ja!5GHZnoC0MZ|v`żZ;~U?12 As|5N}pH?BȖ ^vʄ #j5Y#dH  g M۳ )ĭ)JRv< 4Mm懑;v 5>Z׏SPF0QV9NL+/8O>NgrrDkurWk78!Lv5O`)x$u sh2[}~TICj-I)T3s}2ɦRٚNrwMe|HW$^{糍1l.JKMR 8{eQ\L׍M p7`u;з< UK`qؑ6 c\cğtO!G3šn6OL9&,`yH<Z:gzL iOVOTOٕ^KH:uMB?YZDUHO d"S&`,\-mO2$&vszn. \Y5j#7ނ,,N6O@VwTfrCj[o`HC e4!!k>qi~;} W*&zuɸh` ұiՇCPS7OX5$b/E=&c΍Oq'ʋ(0VsoX<YfMr̹g$jዿB[?4]hT4ŤnϏqB!1#(@-:@YA,\8ޅhvcy8Dx-|Y}KV"sKP#wíRMt&)=c*=Sv% N`΄WzZ;!b*+"Vh:[?g0dk[ML@dAry-@ v >dngy#L1Ewz}d2pZ%2(:6Ts@KYiJ+U2g޿*vIQ2sFڰtL\|XMNjtbjT1c zs7XKt;֎w(~P%zR#$Z0睭B֖zR%\!a.K}'D'ޅʗ_XT׍[4(9)} ?z O0eEt/*3: ywP1 cݡ\%ՁT$F, Fg (cq$)HH45)`:n5dYo$t{/=3&yk3MuњD{:oR>[2짶ՠFiv%'' B@DDX-TVHR(e ol'PG?Z)lGfծ]ŠZ0-߁ͳɸW7^@ml3}utT52,x8\1^k,X5DJ_N|?4 qy1vRbirb7Kb|u̲d }b79EڬS>:Y * 2%3)E^O U,E5 /*^] R:ͽJ`Tڲor("$;bBegÆ $$NK:iԭ!4qFNЀO BkiJjΠ㬲򄄟0 P [P,Z{N2#nWITڿ 0A IaOiQ?! C2O<-ږx@z$Ym]2+~(d=>ĄXӞ'͵LML>㳕!2c.5<5~oh7K@*hcw( T]ם?O?FA|LI>xb$-UMtܺ[/q[r7uTcq)HelxJG |`hrhvgLƪᅪ?^v~_1H,, Fig{A+3**GŽz f>V,2Մ/X5{^Op#Jaȧ.B H0Q{g-K[P[cqk`T2 ayR. bhiqGe3gqķi+цtcQ`#Dsbh)YHKά/ajJjbSW% ! mҴ[OZs7`Tt y۪ͨ . OItLx@P:%k<xj43t&ő k=YÒB5]M^ ĝςFtBRhO+ZR5,_%s nWQFN_;"jTNL[;4 S`"t\>$>0c Cۡ<'dݔM'wa8} =!]WA%ʸ; KQ7K6t3O!QS$$P?f IgF}ڸ\P&ѽuV0w>2Wկʌb`]%9-IŠֶaFݐ =jC%VuqdE'|_Ư:j0(IOHKt[+\l;_kJ9vշ~kZ,ҌIӲHiyen$+ր_i屙#Tϒ i;";ʥMw)xh ^'4\?-†{-Is AP<>JޘxB+!y9 ܬFmB&;0c`lpVy\?3E/t-I2ey^\TgW>L_†0|XՐ@lݺS(D oE&q3Irsb{9rA ;E*s;tTG~- %Ì[zu\zp! ᆎwJQNt\朗V{Ndͻh,pOͣ!}b-`_ @B/(,q=/2Zw=Z6%u ho#?DbV0s᫅F B8Fl{SxV"{܀NEJ[R^ӛ%\hRxfE T$A);qDAUT^`Phqa!8!@) (4J{ 㡫z.W:cK a_q>Z(bgw#/\}#FڀYcMzKĩ?:ںhSU& 4d @Q#Θ %01hGrU)M幡yDqVzP’'iYc0*YXKLگ`gmqԟ[3:S$ (`ݫ E'ތ/ ^|U;Xs+m\$?^pXW *>%iBmԁ=O6QNK"-f'Š,gH,7bC;0=EP|R:݀+6 @ll`JP?Ö 1Gz)ק4!Y}F3DxX)uәaEX2|[{TSOА,~ja}pɾS??dΪj7˵}ԁTE2Ɏq'X, H8*{%B*eZ.FÙ̥u. f5 w`Ob&]T&KHEEcwE3BD*?{B8@}-`9+YI+ɬ|g-n7$%,"0D@ps=@ HM8r4'*U1Uycaf .>2yym"D鶲u-'E|ТTJFsJ{+K%q؂N|cwYm'vVԬ*{Zf%F(vT._e{0g2l>UQ5kz7]1%NQDg(B-C'H,-HkW˒gqϏc` j/^|Ur?Qc5t#asiq.M-?h(DLx<&E ]I+[!Ȭ`mJ~ԒW,|̒Z}p _BC79h=JlXl*y vSjw,OwKoF8ĽѺb1u<f;uť3kPUw:JU貔?7([rvO&IMfbEx'pߒlE t-6Crysq#;JwOl=^hlxN3kNly41&R E D,։&<wze``>HIoJ㲙<& M,Cy0^YxFuAV/zy@H m;;]w1"= ׎RKcg=fQ;Zn0?Q#`704]$C~mD?xk|R4.&ge%0c ϒ#dfa^^cC8\~j`IrQt%kњYtl@DNab69Xf#јV7Io"56Wkk<Mw0֚>iރĦQŦ78If)Zhݻgdu>EPs}ӑ%,*|LxD!+MMwMK)%ţMD=h~3ҟC0mHm o(zXָ0v L]$2BlVdu|W}xg>31Mc, JV닱|%t$,mtyJHV]v*5`=VwgXB 1 !k:KSHgi&Ւ>uf_pQ~Oj)E~ՑiBM%lq"J@ʻ aa/ >/Bc_c,3hm/ay=I*('U GOM.O T*| Lm8dMbl[qɖt{'Kpq߼d/!x =BunBvb8 fa~ȁjޕheHe!lmS9Y2XDδHyDID_1x&2B`ӨV[m~h5םHE] F!ҦM4|HrUPI QTK ;7.Af&@|C(t '`" 8=c<ry7=kBJ}F2\ A£Cv%IHir{}1 8=1i;"ZO^:@Bx$,랆BeV @Vx fXlxTUHm~ZKZ6QP| jU-R˲. pGF$-Pv,bM̡XT.</TJM&FKmJ?oCA'o9r̩1߆q m)#F`Y_]N 2'kTځ) !! ' A/z+:#[ng";!%%cD|%/3/{mdEj2nx?**+\3Iw^(|m+%˚~%|gKy]7!` G( xf| _Zl؟S,EJG>)yI[`,9eG$tݑZ]M}a@ayEYrᮬ[$}+ Me6]vTVW ԪLmqB<Wuabۡ$pg~6ЋɍPaA!JF$+EbUaϷ&+YW owh3>?埐"of>ډ%ۥcopA O^5QMoˑH3&Wa vU(*IO `|NҞ"n s뢠iʷLs#wByn0PՔ9˭!sQ%m <TuKhäDHiY~ !"^O$jz E&r&clFۄpizAlh% n}]5oܙ9ܢZ Q nt spvBsGOeuA3ƒ.Ǣk 2cIKggN ȯ ()}_uhe:g g1g@$>\[\jbI\zQ 3gWYwDrVxǙ[Ĉȋ1 T@ Sj|ד[B_ZYT;+NGO'c eD J_(W}4{²/B$nQՕ%91dIoH?\r/i̾LL`VnbKCx.LW~ai \#d 5ÛSQif&z_)OzȸNھJ͛ y f.3gB]C㮓cV`43"<,9<ޖ2|sk/޾ӈ|$bgk'9H~- RkN=g ;;؜SOv~A KO= &9ofL!#B (=m)O1~iVU pc ]Bʈ!Tz4x>/io΋96r :+Gę4,p{`]4:p~ߠF7Nv휩'J iJErobܼ]#/uح P^{>ӥ@ymΥ  ;^=}qʮWфB}>Ӕ\Щ!sH+8%<T)x:%9+h+ |"$wM8"̓4g@"&,ujowH*f&4M:BÚzhUSJ8HJ GuXGS%"t6mxQ^4~!d}XOAw Gbˠ[\V,fџE/rZ(W t NǞKv8E8UΉÿk(Pʖ!'E/QjDZlM?d Fā`'xƝ7RK]%AeZmŹ'}ɦe EW虥 Xܤ KBJʽ>Ӕ|7 __3(v˗=>4 BIsqFV\* j!4b. * H|` Pi> )[%%k&]^.|yyOCs{us=j!&߾ <]4`6.䦂+i֌-5dYm'o(yt=(޲,uiye5JSyѦ/Bk<katgtG x*rsgyBis@*p1m˜kXG@>`Y*sTL}N@M.e-j)ݴZ*(뎟|-b-*Ui̭n/iZ_5rQ;mv52p#- x`H<#.[ 34PQjd [;!:2!ŏUn#=!}/߱ `Qrޕ%YW;ksDlـg].1;uQrb<@!M-8nHU[JGlv.7}2%(|Ï򬾨(iel!L.?Pf3}ӿaE#E˱!]aů#T׃XKiKFlJZmj?za3#ʴ6jd~g nKܼ5 'Jѥ̞^W,9>&S9|ŚAT8dX9-KfF1Vn5a7maTc'bxm=n6¤6DdwIqYΣhdaJ =kCcJ F c͢;]?Ya| ث1*jMaoiO\1Jf-~kBWa!*ya-:*P0r˻LV3+ g̒Ha//MW/ A HͪP>Z_z*N.4H!M"}69udFR V$J%VM!orZ!g"@~ 2|OM,9,Khd$7 P-x/+ވ|{5,>S(BpTZ1s sFYAr+N\%t K\,8$Й3:d'k89bq5^˟/,S%C,MB~ y9_{1s@}F/΍gXLfJi0,-5[FFQ]kٲXf.EBD,u $]tGƌf-Ʈq\ ~[.rWlKk׽\)Ȧt뿧\Kh$AJHL}ij6Pb!Jl{^t)SrK"N뭆p:|OAL 9gUgUB !=Ҩg0Lp!'׺ idQ_W2L 6zؗz^48?FoeE GhZ=X $ Bfd0cJlN@h}S4.;mESEGǒȿ_c^~Kʄr $֖Iѡ՝Px,3#WPʁ$Dqp\1@Nߠg{K ݓV ]oրM~3$Nns\}ɦ ᅨ6wQɥ,}$fb&s1s\4ɼ;(5-V33__[BW)yr":/ѐWURFsۀCHK.bGߍ\4W(D0xdP!jz\rbeiz, >uч/X4Ћ'mwdZZ=B{$vZ*W5l{%k)K#ףz 9XeթRj`*EQU (I[<8OڨfyU`_9=0e 2kuLTe,kE )1 BH./L>WwW̏Q5ԓxi:6јl5,/w@i| :~AA k?[lc/ p4~@SLd]x%[/,Fqx5FF(a 7V]Cmx$8!S0;"d5J36lRȨSN 4RDj ٵ vw}7e3qC1H yB:~ux) 8$a+~ G %yLv|(I]bHoTp],G njRӒ9\ೡ:B7o-Z֙41c7`oѨU$>S#;/;EO']Z_ة[$+tߊV]ϙY!;EmMG/pt^FF(@yif &';)pbyԦU;蓺=wӦMe6zZId%h/W)) ZBćz&42e4NV4)1iu%ːqV^ֈWKb&o1u'{c:o .K+eNf'i SC>fAWSej5-82ﲳ ̻f=wsN_|ϫκs=Wf$UUz (њ71l~p+_[ٝS-X'@6CD9̝H8^PqBGFEVLmTo䰕ڝ/_rg͑ܮ~QueB5)f7FWrN5ɕoԪȝ|/ (#QEKlgI/ !lpdy=w=ݽŨSV,@#ZSK<(t!C*LIL:OF,P gCwM!WT\2cpc{`AJb =7mwKf+~ļZ*Q0vr Ɏ`/"MH1~ "9ɺoSmGGMik$r5.Tw1Dnz3E!Fάy.* hh-"~cQZUB^oψ84I.Hk7uq[٣v%[3uuğ=A^دY9EDg<1?aH^ZeEN$0-u%<\Xy=*@e1:R̽gTz¾İ3K]j$V ;Wi,0c~O}Vu|k?RJntHBxx٨Ċ)UE!yё|x^FL#5Gz)9+u. /a,x{"E_B:Vi]ϋ<zQn37T:.:{YDܞcM$>7\yɭ > `8%D6!'v$R?6,It$VgX=1G73kz=f86 ! XڵM_Ih EB]W$b@"q4n_^x?85ZY}QC@8TQ}7|oޡft:FϰI_Rh3 8fԙӨBxa60N( _kIPWW'1RF#dˤ>o A5 LS5mLȺPt1xA_Y`4L~8]jFf77E9!XrXkRO:d"lVFKX I}6{zw#I ,%i˦qdObE>:To6 |]D6퍙ZRMu5(VC'z8h@ )rf}~t]c[`_ ɵmAX^*׉F9J },wpS1.KΥ֨$+]߶9UD ]Yid4+ruFFâz\x\/ xpLO,OԌS".&.]ʶ=~N%B2PI6:Frp>|*/-oVA&XjAwt;n 7(6k..Ht%YEyMO7y}TP~6 88yHyqXn8 =2,?@$xsE0 oǺ μ~aMz20x-$A mjZ\EtiY)au sgU*qSщL X 2ye'+/ [Q?FovG ;C/ђ:ϴDIA5?^!JBx8D!Nu?Ef&Lp(t)Mcp,LTl {Vׅv wǰТhOhAt LFAl2L5W}WPZ%ḍL3<<>MuI 9Tõ`wXR.r!p/fѤ׺zڕ~G%^KjbokfRdP6?BuV#7² }h,գՑV x88z 5Mkw#u'!:##DɊc(AS;Eln79LߘUry.dF@0M$yݙ07ssOp;.{U5g ""w;FRxI{[AYxv8e\oO`]M&%i sY"7kJ#QoEX*0%Ll7bRv>^ `>)IW_/$cGUPsŔ9806ݳ#Jƥk78{m@2%xv&j _? 㴺 +9-"sU8-[$\Ĝnp*A+~hGBX(PtW|T:3 >ئ|LsA6crYy8זl\ W;}`շH+AOAHtV09& \#nX+(O'R;Sqv,SGtYfӍB; m޷@]Nq{n2#0´✇4MHUL ٝlgP'{ Ŧc>A@j{jrhNT Xx/ q?򚤕L?$NPbfy6o7SOQ/ .P)^'_pJVkclFDZh<:.RW173@citZͽ⋥DQ"dվS/(lTu8]:/S7-TaAUEFFљTw6\90 dz U@$%1w`[B?duND1-XvguMX@&Z,Y~xKsD̮9yZ@^6m{L54;X\9*٘I$0^G}):RUh?F7Gx BY5?pY?+tS~ zѢ3R zs@+fIӓPOn/[)G"#DFk}%?rM R;l^Kc"m^K$eU gdX"L8Oᇶv,%)N7_?U%sK 3Ob`#ٮ't-kAHήͩR 0(QG NcZ\MV|9}׌#Ye(Btn tvQN:D>d_ y]A_l'{$HܸS4B-eB u*4kDxw!ߜv-]Ð.ͨ9rOFm-GU^'b2rn*%3-74췍k{/oloX)Xi[!46̴u{nXu79o}J5Ix_kzG߯E)n'@ͱ6[#?B'ꆬXq?Mc.x88xeJ)]z *CVWV'cu62-\f`OmZ [n:=vhQݝo͞~&cj>u& ~̤z5UZʑaw%;"f4=S*1>j0ܼigy#Yev]pʭIozsj \؞t4ɐ:"ܗV3hɦLMk<7\> ]:Ɲ:o"-~ju䍽y: g)%M}~!I,gj昣~t/ >TP LPSdrBf8_OdYVYYg˾(  kjL *SvmDplyi4OjaӢ+$梕D_l^\%>24%Dz0wӺɀ29'bm&{!e7}/17A{>+|S'X<%<2S" 9\h6Wf61| :ͣR8޽YiAr0 UiWS¼BqO^-o$!K?ejtR'[__L|UNjV==sǵ[On.) UGzf9/kW1Vk p=r嫹G~DnAHĨaW G=Xkw3#arFU5|7J*T,̀8wD%{ze\Ψ t~z)Eu]I ,}}IJ~\!f Zb֭ Z/;w" f}M_I _t0u˸֗{a/џrႊ'2qyB+Ҡ@M`j7kXEQ)Q,U t䊂Z; ;+8WfZ5?_+D&N0 M\Q!aq|&'Dt. >p.[qd-GmK֡lK31jU7KH-[6YH5̍|hj;O"&Q0Vy]yı,l3pP̫cK^ҘFDhu[-fM|*s/ w̽!~.vFX#uG{0|&}`¡xR m{wt?z8QOMPFnPFhro,XfqQW4Z7|'F5e6ʽR #i~yBto v7I(Zz <(7P9$v$b GWJ Ť1~y&2.2͡Tʭd&J7u!6gWPƊQj=kDfB`u2LVෂ? (0;Js(T-{sӡٿZݹ^)` ޟJ>XliBNbj%xLvO0 8顎+Gl~=XK!h/?x1rxj^Bᥢ|mFuBW[iwDvfx bPPA5kSEJ=IIײ ީS2 CC`6R_.|a$"Qoq_E.}~yKMȠ]!0i' K4]"uGzys "D(X2i-pRv>=+=B=j-Z>C5^h(YkZ&&G㻣$oiYR,ӸhGB  [)LX9,+ ~cDz`:0!%N^:nM|Hj$_h2tHUb7Bx9I]lhqO//qvvKk<ͲEw5Rަjd MDmy HY<謴?Aq{IJNyf]Ve&Qm'}4 R7K]}$~4C@}’SݹI0FoYFCƌg5NIx9ΕRs<]PrgՃDcEf[Q ><δ:;%XŽ3juz&m;>^_@5zSxzlDfiDk,hL}4 Z.v(kӺ[-}RlͰ=e}acrTqi/r&h>N\[ߋ/(i6Dw"p<zždֶ݇0dU1pnꢫ0u-עo} 8QSRG%v+6#Zi3QB: )%5ow17S0kش @]ƏDHBC\c8qJQ=a?y/j&߂Ϩ.Uy[QuMђkA f.h\gZӅٖE$^ߚk8R$>Ҍ<-c0 bt]u A&ַ z2]Mϵ΍9+ (Be%`Aݍ_l_n:Dn%C=ir^9.xAYڅIƒ7]-zܨKeO}X:6* |[c"b]̜LOLr7BҢ-NJxMYPY4rlExى nښje$0LIaQ"~/v`n=B.,cWRVUq sKꤒh%-JgX #{5|W[iKQHn^1ܟ\iaќX2Y7Go s6i( [r+Iy^@q)rlA3=؄j=ڪ6YX櫪em"s>NĝvwuCw]Wn;s$gk>Rևj4;8wffS^>enzr\ܯTp%IDNZlQ~xr2NiKu?lT ބ<^ Hz~UOR@y˨Q<-Imc8壓tPHu7wv,b:n-^Od {>!yT -R4Y^!o~WTlo"XzK(OP:}Cʪ{)LXB<:v"W_ɇ7P"Ĥ]L]Z&˗JɊCv~U "sy0KB4lj,ԣhKig(3; uqT_VMì|ѫ[z|/\衋5wJSN&C܊C/kLg AC0р[ @ <"o۴6G - d b5;B[髤$b: ޕ^2zƣJώ Qy_ٻW ˉ ɼF}ѲI޹NUOJQ!&)p@+; ߣ#=}K3@s`=G!GaKZU1Pfu\O.3rO}Xu 0@P'~ N'3wGsQ]ǴB@osw9aFeua@ ?ŔլA7by`M24glVn;bذ I@ p,Py Bh`kۥ~Цc'wHR/g XX# =SEBoLf^Xȇ-'%ʐ6쵪GC@~9vDȣzaͰ)œ{5k;E m_<&/h9{x w޶ Ŕ%9FeM#Bw% En[&j"ǎȡwW9*\}} l"r@8?._B p N0\Ӱ4BI{ CE@Rna,%~NdE+XOUW~dͣZ^;-/~ؤ8O\Wѯ}o ]{'[| Zt&aߟtm,ausI{[ҌDn0BrZ7\"ژM#tzVhjgDT`O(,[׀jTz)4#w7!ʚk>ع~MNweZhefq<)cDĽL^MeK~\ zc3B-W0%?Ôi/`Vy͡g;ڸ1Ce+3 kpU6EB;jͱNL >՗!YTN*3HX׾nǮsլwf1?03u5/Di{I& 0r9x] Nt(LO9}5)!DPnu$>vuڮz$XU<36-NtFnnQWQ15ea3o[hMtW/,?c]RGe/';DC@GjcܻPqPj^)#$Lٚ&|7S7b~rk'\bfs}+k8b?7s.2/3C4x 5q여PBS? Kmƭr{T+Q稯1dsQ0݊%p] _ȕ\+Sd'{m.n")gF>ر /6| C%.*7|!5)Jfd4I cqd2hjx?~cʥ}Vt$yڜw-Q`a}n;5M8fy#9bX@#iC+$<0C {,T#c. !OΣMf4Vlŷ9_Xt R3n/R\3 wR+>ʋ3 h2 kz$L_D|_ڱ [ [\ Bad{ /bS+gRD51*ڒJc?rV3|᷾ F WHkJKve@%P 3^T *꾪], ,YG0cw8L @2 6=rZ3cT\/@{:2Y9ް&q,~"L}dcoF$K)dNS*06+뷢s x+\4` -Md*2PE cKT7jnɶDdyqs$ v6T5?rDM騵 \yi`sܡ^&#:;oGAѯ]lnh7q=R`iȆ KBFѦJzYZg{_E+zdѐYy3aĥk/VNY}5[(ŸX;f`sN{2I|g M9xʏ #~Ÿ&gu%;$VGK ݚR?^ĊHe|fE$[\#O+"3c. "r~53,%;ut*6v|d>*aЎݕ\M֔v_CoT"+j8o'oLQO ˋ*Q^9@dE Cx.0F)^䏝N8YIM&BQdEL(~IhMg7Bvc0X»nέ4,Vfœ:Y 7W^5\9+sr~a0Yfe&x0iĘno0›-j0DX{G>Rg ? DMYFzQN6P}i̲?$.7ฮ;b1 /жE*bӿ͕:M0{›I7(pjc&ɾĝ_a?MwZ6Ɋ̀F GيȃEO_DZ}R ))lAw;7';A \؞;=l%)$M9 4RKզ9EOF噿X?U O<2n REXѬl/(3 "f]]ݠc L`#Gy)c2k˝VkBPf zdEg)߻ڤb7ْxx2ﴜqZMڅ-*iXzJΒisU84PSOF9 m!LT&@(ǵMВf ?򎸭c'n9gWk9*=c oŊ'lZJf.T#_Ԑ|S|YHwΤ˘AuS2遬n ކ8$()ݺ5v Jus-D %&e9okD\#W.TQys꺫CNk5c]kmlHe,K怭N٦6?A&Ufză8P3J(֠ε; 1vhrCUCGL99v$..WbM0?{U5+-EEO[^ 9y(Zvgx3LSw$eo,DEgA?9%+<`glVCv5G['rIX^M2 V^ rϮ@Y^=,I3x/>j1a-Cм>GC X>[,KP8edi_%6m[G[&xo Ԋ  tocB;_MJrwK=/=;^6Կ5!# L*+՗YsU_M__ RgAh6]r肨"Fl.-3;ḎYYcWqmolA i`6w`ctzVϼG߈8B,5[qGwd~?q_uZ\rۻOi1XxtړqHEғv[YF@7mG ]S~ȌX30, ll yrA@TߨΜ9"y!Dy3#ᖒ~sޗA|iV0 ږ2m!ymԴqERrkfJ} eW_ uƔ=B_-һĎuO~ 1/W|lԯb|;p1ۋ EVܯ<<8Jy7x9lmu@g\9>s^TٲEOy~6&زg v[,#wmVDndfq߻)kP> 7<4G0WS%F7O_/47AM5DaT5wpm%x+L^of)VW;dxWE;@`s*vAOebV!dw飹77Z:j~^goQ2&nΎfcE&ĢN8o*~IN>CٳXz NXqҽUǷr 56k^2$8ūyj NCC+%/]qݭKD?yY!sҢQMGٺ%+^]8}7YJ\/]!ݼ;hl>mw6)e.BEc0Y rd'"L Gh̝sKYlm;VśS]QcJ%W13e9`Hmw436$ )P)魎y&YU#EF"htT0x:l 6=~n#=6>Lb׀>!h`U=@iZm"Da ^ *1,n :TeNΒp攑<j;= 3>!.>X'$-ߡRM Kad-jFk{(-@{A-5#gyC(Ŝ6ŸPn? ^Dz6'nؽr4b-.Ev=2'.]_M9Yd)b?DE_97dQ6mf[^ ֫9/1fF͘`a)rh\A;̑z J%S ş TKCZ?sTE)OsJiɂ\f:ou9ZBas=CpKVAa5LKmՑ=ќp]QzBZ{j@~#_˕HB,Te^RyI5,v۽VOGeD$u֎_ l:reй Λ~;y}谌Κy{dۚKCe\ؒ|g/Yֈe{-|пXT.jB ><OaIz xx+8 ӻܓY=CEMX6LK"=&rr鋇}S~gؓi3 Exht2XtB URUE)4ßmbHcbehfeF`P(l4vws!]weH\Hnvw }={.К |RX/~!\np@c|#jEOBh 8u&Wz=y1Bo@=RiU&2z`3Mx+_%)$>\xǢr@s@jeb?|@wߞ+eUoOf!70<$c ,j 0x|e0QVJqI܂πWH Q-LRW;:}hpQ>cQ$.e>בTOD汛x>/?IZPom|aFՌ1elf /zTSa&徟 ~KSt8ϢNcZcJ3FnZxr5-Yw*{*0`r-7h?F(e9]W/;pT0 Tsw|ea;|y{\#we PizVfv)^7PLuG6 +&oN;= ?\@Tێ;~>s,ڬznfRwt ϗ76 b;6y7UNqX<; ,)[ KM>ɝy ZǑw4zo`/8RU֛jۙ3@?sBCIRyK{߻e}svRn X7MOИo#3ڿ&DZny!Ķ3Ăd>\fRf!E[X9dlgoOqYn+m 2XZ8Hu1A%6DE Ԝ3hGU)cS^ߢ=qb5hz$K7o9kMpGBז61}Nb6z=}8`Kǹh)0Uƃ%IȜũ!wT?L],zi@-g/AIɸ+86 uXU4CC̔dBѢYr oʭ\aF{zJ'<ַ*p=/jvWWie`DGZ9wѿ!+>P neD/H]ϽiR-m֘8y^Gp$nnk6G'1anYZ}b"uA|WBbu^.`_w qi P:N}-垪Șm 2Qod}dj`mbuz rM)EQ8Vy%/pG͔ר1d 7O bB@ s9e hE[(6v8jxgՙE4bV13!,OSϞZunj`LᰜdSߎP5/#LM˲w|·\ĩIX7]À{/ I$ؿ*|bA؍8s )"`Z 2)\l) \Nl#Y8GXzK L~J{̴L)e$$ah hH\F&U qwFR!H E/ n k =|v =fy$DoTW#r[ki*b쮏{?U?3 )/y ] -:iJncGsR ƝfhD2?8aOo:Eԓ Jl4{!d} hAU*1P#VƧs$q/yQ1Ґ FT7?Zn7?;Tm]B< YrN0˖cOtd]?g 6@rkj-ೇL,oMF;"Nx9E>%89xz}5c_| AkXQ62֤%k%+z͹-^+^ s1#_7ڛ(I@-Ā` k.[[-^Krb$ӁK-.g=6ykZ2`@6#X%hN׀8(Iz~Y>#9i d 'fႠIi͒ Bȝ.c򦹁TFWmIeL*RN} hqݣJ¦ąX)SIpS, ,:c!!)w1S`xq i/7lU6P]Mk:DFKtY:8GR(׋:&`MQ׶>K2Fhuj%ǜ)˳U]k͔_Rk>)m}D(AhnEߧ3+>\kۼe|y?0܋,?^vR(z5q0^ѬDhTi%Sԣ,Uֱl>֫&bs#}"MVdߋL\5Vޤ|.3!8q i 15mJ?tɜJEo#8^4r.%wI҇x2֦  "բ۩7:LZ4+1`{p,FN|h!_4yMtC=_oZMq}cǸ?HTXX˵96pBnWz'>&훗mt"'@h/NdmO-9ם=`VReIɏo&[0^;<Yf9hS#RKa&'>qQo!}qj/r?Y.#>@Ӱ_KáԎڛYT%HK(P$$o{H4>h19\'^6rC _zmh)Kk/q\u^y3Fjtnb9 5?Jjd_Rs0[8[NZ w^cd.V!;ֱf>nNF4e+Vk'NiJv [ aC% A|}T7żϥsƤh;x촎 ѕ~lf'6\n{a7Wvߠeo u/5m-haԂosxn(3?VD+P(H){M* oU4]Fg]!cֱBsz;ER/x_ے0OaQIPrj$ލi,`ZyP:6׹BbEW/S:~H?D;8⏮MFDH2Acnƨ]0pF%Nkk6d<9R{sUU\N5lhvֿRP ,L}$Y,Tz5o4-tGȝzwu"J՟{D2V(GJ Z ))7٥8:Ϲ=o\gf@ ι+)cs[;:"bA-fڛB:8۔L\e{~kzXd;v&,3ٺގz D53mk5S4;:'0G3);=qqK Y3e>W,㕈! ;6L+ ڞwd#䨧4]1hVx8FKbl٫G7@6P'2= ͕d^xC3Ns?&>J6";5_CHbm4pvCԘ#5_y'5g:.w ;5/X兺4LC? :ڲ:cΡ̆& Uk"FR-Ӈ;rRz 5цXO)J x 63%Cʧ`0gV! ԷP8be YބL:g-Kh_ĉw~c{z yod#%mXG/* ZS%b(]O 9椛xLқVL٥#@t#I]~s"7M-8cմiycQjSj!c-Ѳ1SЇҁSQ ,eKr5&?yȥBe6-g kP%ka}=#(Øg ʸa'C0;BMPEb̕ 4yV?~'ԪAZeG`lj6g/S$Vobz,,s?gЂ3}yrv.OZ,pӉ*bE'r⾩n>(e٘JbZ5XH!)`L^XX<)R-Ybuc?.;q$P+hP%wsC6qTRpF+~gM]{wy!6v~ f_64beLO\RVNg`.v+̴LHVs[QGU!igw=GoμDIi!)y@qM|<5ͱeB4n;f$ C F?7o5) TF{)n⇲~4A Fi8Xh {s=F ӖOI?c֬-#YcL@ÚU fTT]뎥"i͜RLh* JJ$izcaF Ie]y׺l.3-c8K2y^{ܔV VŮ׈.FҍhXs Q3FS(Ch 88$QfS؊x` ҫӘ:#8{ҝN1C.ne\Ļ٘:xjFBMuﲜ/:jr Kc'\ɥN::MJNNP֢̑%0,#1nxg$y -M:2IY0=EhَŨ܉PBfaoJcf2;"'U\|CPX8i󻕐T_9=]c:Y7[sj4&/[>"'~h4;uՄ@ex\CE/MȤKq]"8z܁Fa/'8ȧ, 34s#ՒRR`+/ ;mG[ITA'Wn,?]#=G\_KfÊ|9ǜ{ɕuN؟#Y. N8袉lM 7hi&[9,ڔN6׳AVqV"B]1QV)ax)Qħ3/|Tׇ≉x^C@Fe1@Ӎ#oN $վetʊ ZL&d>K]Fh8Zˊ˃%-#jΪ㍏t-71;TN%Uvgw'mnw?|w:kq}*N)nK:fv{3 _Z49[i: !>IvWPj'*Z骢Bj֒ S9{YxL3 jPZFxmV|a$fwP+$آYXdK*)%p62-]\:=1 vDE[vv =.OA['h=mu #BN!P 9MᩬH30- c66 7_SeDI׃_϶#0G܈₃ݝufIzhP&X~K uv˼Zק75[wKz:'1E($vYSg l6LvB[r.@Zҡzl؏x]:1Sx!P?Jfs':h q19W'&^׭7Md zÎ=Nȶf6ݽqHx.(O@Io3*n,)%[P\lxTrHPĻd$3{Pi5X,N-f_Ug:Qs[)%p-КnFߚ7$zVC{os\a__>C$yrdž3}ŔA@Z x`ݧh뷩l,(vԲ&}y`FTTt8#Q\_L(܆r6R5d@=#{/"1&{G[u9O?=^{g_Is %+Yٮ![o"AFljxsMďAKbFN,HOޑ{TU.a'_c#̮x >G?%غ%L9pqGW L_9w ?˻1fha7D|@3aWkz* +1G@4hr+9vB_+aoS=6b9'gL3x*X?kGRZ[!EݡQl zy2.P3J5B{maVbw()W6b[ mp;9/AtFH:,:,@q@AMC͌;z1F=7]~l$Ղ]&°In\-UkT|Z$vFLQo.\b&t-ox\<-,{Ge\"9ah_+܁3\Љ>^>بe߿c5I{Lo99qN-`X)oKONv1U-3؎k+t;K:DL&S4W@aѧFP=7_7u vu}4hY rۿcV5S"b P8m+J|D?6~˼;MBe|||pUvr1&&&)L|bWOzhOܧJ>' sXϘuƉ[ p/@(`8RQb~a:Uei!x+n/ 6xQ=iQSׯy^i-=$KM \w"mA : K(o&_;ML%`SHo>ĸ$UJ8 IECv1mRxL=zPxA/ТDcVvA Vѽ] q bAJ rgJx5I>y$,4+Pm㫁\ί3:]I0~v+ưuRkA&܉\tcu#v7 moNxdGƙۀ㯛ύY:qgLF!KnBv+`)?;l*fv\82QCd¿#V<)SSe+I\BTe[. ^;Jk(Mb {5` +xP~l`l;,ޔ(@XX6 $<6w9- f6h7r))0!8 GgJHu 1Nl ؾ4JSXο)H*D ZUOP%J۵08v)ɛ4u~ KXUP aͧ~ 2&O& Pq] 5G!{&es^R+5VgB$dLMɵ-1j&h؊IIC- : wPy|~DRړ-W xhEKq7 *v( پs@cG;4 f~QU3sOODYϙTHSN.sqVq[n){H%UrXEX˂0 \kFK(QnGQQ䆈W-ve.nvt㎮eM{ȁ"!V( 3*W&QJxbOVq3` %[Xņ] 1BCt{w\HIE8tqB7ۀ<=OWw锰`W9F̲ M 7ƴ2-b \q5޽M܉'_KǿYXIY{f[ 3-bI }sSC_ed f/PN(b =V/ ̴^*.#]Yb $Go/o<Ҥ;7,dT9qWXП!w_0˻٠ұe~eIKE:)#xqEhrwDK1Ds^ot Wtho>ZL]^n4n edp2'8$Cu٥m* 0A%[Xy] ,+צs!f}=`W'&.svZbpX:C~ݘJ,|txlkI]\qYc ֌]Hҷv82R6 TOqhgjn>:0G8nŠZ;iLPE~ȅ`V6k=ڈZ ]j ؠ6M?cD ucu*WO$>ś쌬vϗ\R@ájKeC;C)ۣЊaReF%'O&?{/r&9Rq0\á[i+UtİT;mEȒE~-9M*ͼ 6p- 5#v% ~Al"%jS; ?jICqlKpyhU?=ySGc9ʭe)<2T4 nأ.FbB~_=?肓`yŞ+.#n|i˓@JeEYvC̫G-p2xv1GypK߄&2;4rSPD;^ N yMXcU-L 󉀂?s'pzմP iED&XS+G*!"Ou#+N۫YVd }`9zjqB/[;l gBm.kb7~w#p V ~x%lYnOq e j{jƦٍmsu٣GOf0o+BW)=HEãsM:o;[[|I`Qm'FhL:kzX+xWj pj0I%j' 4fFpN 0֬r ')gQOG|F+ <{PSAyΣHp aT(grrfHfut׬JlbrfEυ░Bqrl+M'!*Leo<>:Ґ×ʭ W2h/U{5|*l8Ytw.(bO㕶!ڒT C$n2JL7b?z N?noDw2r\ p8#r>ᭇOZLT8o ȩy*ϡjxނzB'Y4P!)䖁d0ϼ'&PjP)L 34pt#UiyCuFG聖lªJLXA`Ɔ!MC-aC i/ژ.筐3PM[Y2n`ir71#-A ݲUnn/`XT0@,L\zi2u&Z/ ɡYccs} (:_c<mNL{RxFwA sPT-dd?NUL.ZNF#-WD` utAv6o3،渉hK`KD lG_Rr bI!|XaX3 8Қ1| ;qC̆wrӈ?G }:cp?6FקӤ~mm :(#T|` \2]O[D…-}u*a&$Ap6иMZPgژ$$?HS0ҽ~ ,"h^04#>;6M;U^fSQq %.qPM.IƒIˇ=Zf,tk#,MY.IToU? ۜs0\S뾫x!IE\$ o?pL@#2pW^ҏC(yW*ào; VV^`IOuГZwZRHInĄ&0,t=MK]ه y eL<ÅFu,nfqDThoK^̫F-U/:rkܘҟra0.F/|yU:Ht$i 5.`Ab-1-P5[]ll0ZmT;~6f_o)pbB{ ,r.*t&/m^i-#+k<|'r`~>Bf&afY`|XڀGW[>#| 43o2OD 9Gfb[ʺlUKL5^w*X_3Xh.Y,2I⺡mROV]_k ͼqj.}sY@ 2nk<8Ć+}-p(5Gaߑ(7&Ó ?My8j"Ij#"a7׌ې&*7_4->_FYuN>>O7TtdN8!/آp6.m, 4jηNqW'PHKE:* SIc*Pa\ iFЉ'=3盧8)r{:?KtyBª}3o^MFtWZ$ߨܡB#`KZb=*Š`).:%6py kDd@$HmQmJ`SȁP[,0'OlÍMV:c#/TQdfu~e?50C4+Gf;*U=:øWרvqdMض$ q5XZf[nysZL' wSLާlkZ>K#^dwmg/e3WJ&`enDn_.Ե9pz g\ںfpeRd\LUDJeGK K`YG!2U[H.QY2[4ϩBu?B0Xf#H/7(5D#ĕ%p] *Aȯ7 V[ߛRX1_=;ko"j(b)+q*]jH0'{}q+/xvD/l|DV!W2~L귢.f*Kz-p>j`O xlѓ]GK&)w˞gpy 4OBmMuzr{n,#/A3Ol\dPiNo?|?o~r4$Tƒa\2pDj"ձʑ?ՙi5>5RđN[El2ECjH}д#^C ;XCG_+ t6]$s kBY0b^X'rj1&[w@NJ ƩY1N&- `'WF!>O3ޝB9 dY@ :M{?I}XuefҌ)cQC)tDNNIYQFR v$ XpǪic4湿 [ U9⾒)-8ߌ:_t $? RJRz)rV39)bQp""2f\ ̀fmDGӛ3Ou95ٗhvkB^먿E|JYɧ6 FGOc#!T ,JnM\[/|[\AutC&Zyu㡔W°:T#,]fkN6zm]$L%{ \J!A&B:p+$c5d@ 5 Ѣ:tFzcܔBT{~ ^Ȟpo_4l(~m9Vð6=^ȱ@n,/{@SZjp=ZQ3ox@̜SG@~*pv#MR's-h-I!NE6yR.\} X fD*#Eur:U`#ezOObA^T+VIT }z<qM`ڷ6bܑElUE fî[!R,^X|J,{j|^8~4k55^G&ao<x)]%X`@z@o:rԥ4WSgpT5_"EwŗػIJ&+̹)u[~t,Tnnf&UGz&5.vUb 뷥QTڈ{}P*`H/0Z /sT+(U>|= ao0NZ0W:PT3 (1áՆ/b?ヌ%W1w YO8Coq4-7LdVC/w_t'QucKZBH6 g)r{:@**^O&^P<1L sv~j)%#2oX}׌9aphC,@cLm@9z % xEUibb#O"CE׮c|P]jn*oK)pLp$@wIߧ 0ǏNg~Mx ? |{9]%V* ,߽XTj[GNG~_8krMZr35ni6'}V'~GեY8T~'YN<|b6+!-o᭧OsQI#-4]bhAiD%W :PgSwAKKVĆŒJ m9c*sh!A?2FsHFr9X`Z%qw `Lbj>T5Q͓2I0^zPtCR˖MǨyƳF(\VȁOM%]Z+-: e2. I;w"iQ!bhlLۼC ]"_3F[!ەӕX@k'M?jή2ЄcH,bu^6`P%/9a旹H0̰ )*:$*}QmMVkO\>K;[W1]%kF`?0I @#fAbGnȇЏ'>3⋘IA{>A|:4w/;mʇ]U~v^^OhH:=&ҨS7 wg$2ٷ( 5nWs]=ZIL' 0Gf~4xsh7~y-pa(4Vs3wVoڑPE.-% FuvANJ>BPmke.[@ٷ69M?ZmjLłV|-gG'hPc5r/Jnw^2Xuf%prt3ڛ4 kRJ EZ.羦QSD5/ k6fzk*jaNZfԙEhV-$2|ߪNt۞!'!=U!Vah_#l*PX%{WO{@`*Z It$"ʃgýӶuUL^?= &*@Ү #57RtjMx~*[٪CZA6EcyAY;Rbgfj|T kSdJ5b$y2*n[%j(W2tMUHr"-s&b}A5*B|VGcژUdpoOUvxt[C5mOC/_ Y+҃I`4q{ʫԉǥvvB|T^aVG@l>^i1JPYwHI Sfh7IZ[7nO/H(DJp<^wFFsT>f+Z?zr_ꀪjn%lی/fxB7~EaVn]hA/+2'r# +T*ЬMvD}.uPҢ: 9!&Hcl2x^u Jna! DȔ+'#8 =<MQُN(Lc\ 93m(7!$xj "7608D|l]dzh<9#j3@Vv ^SZ=8ӡ7&i*v&~R4W63v}({l^bBh:&xxS 4Vj .:lox施fko'qxOrc҈֒HH$8zgNFOs72Oڱ"(N@ypNzbI{tLG?d٩9Gˡ| L~rQ74k^g9,hW.mZ7MQhN*/ׯ#X]=]8ݵꖞ)[ųDT>]~c0 7s9M_l"@*PIjtK_!q0 ǡ }QV1 O`rfR~$Pk`iw' J2nyw> ܥ-nٕߟAԨZDc%UtC88I\/h~X .C7L^?irh4!G6칕BHm-7r6gm 1/ZϐWnv_ @)mLs{#h6Lij]uv5EK^m$dURB|@nUlf^yMa3:] Ryő7D1> + _ېak3!oe4 rk,KE'3kb@9)WTf}2:3 j'nuiIf ywj "P/4'6ìaY3xiN>nTJ^eS=x\88u&w~DêDseZ/*r  !EbUͿ|'{6)OlʱQLv?FwQQC2[uAm_kЍ5ne\{^a9 ŎIr͵etlFWGr" %VtY\Tk|Qy?WAe& qqq>,R5pH*fܺMĸ{*{ptiYϽUYsvҜ~c5ÆYSw%h>oE= 8^HJDTIR(aRΝ@w4Kkb_K5kq\WjX$}^P~3BLx)}Z?.oL-Ep τ@!߄{dh(3yy}v(iF/փN¡ơUDNc li.W4_߭WMsi2b{ mT@ ]!#uÉ%丙665{fǏ`=ЫcNo#xFnNFҴڻwpB 9aau͇ae& \#λvkՃzmZwc/{0gJga`/~6nB`RHa( UA H*5p{(z@n` ̕Byf**`4sަ(q214}hGqN~5?D nlR[,,8kx"H^N+t,?R$Va~RdWg0iUcfbbZur}GG R8{KzarރO`D+U'_z7Y9*mbKBR;@ 8#Ɔc.xGUGm}?J30*)]xN?]qyѹ{[u4,uktI(@!7tNg@;U ]ںɲ$"ӶrIe$#;nH'$YEY9yxn_wfSe'eh)>)kA޸ϓ7bӮe*hJhJWߙ9FL5A[(b] ~^vaA>> V,"`ub&u0HtG| CSrjDVF)4tkxY)&.wgL#h4~MV@> k0n 5fz{t>.~ydb[H}*7Q|DN6ah1BhP5% ڊGObuIGeb]՛qF;k0Z\m#V[#5 =2w@fxZA>wbGm^ɼ_>ZM+v%ǹzCKZr 5&We/CӍ4_0o1e A9qNIN^DVKyБ1+b2w:@V<boȴ(F_qղԲeI/ah RFM(@uTz?K?TϾvБlp}O*C2g5kTD1ݰVπ|i4DơʒFK2#_ `b,˰ލV6f -d2yB2s3yoA7OH.gbq qOȕ߉ԳO^ڽ6!iQuݩ"hjB%”`,6:"Uw!Hf3p@JhRp<%1td@Fňv)3xva<#1kxB:Th qp-](ՄC6)-O0&a=΃u@'%5!f':,3cx>s\*ݸjoSD$J6[^Kʗiȶ'>21<`ڄ!#0Pj{o3)c sdzΦ0zM.SK>~U唻a?16i@ {xUD?〄=zV-{j@+OZv}}1iuo VZtFXK 2qy?Q?=AyQ?WE>殥.hY{ZP]pSAX '"3fm+4CF=X>$ck 5|zL=YŬjX5%(s)Z& EF{UЩ fʃ ގ x*1T Kl.Z /kMc |6,Ombk pT(\3X\XL* E|#"ү#Ps*l~-i2 l}fMasάp[Ӑ ;|M?K >Z~度pMDXԏCea gOrC"2p[6%.4Sh\DymSDOE_HʂpA?D~i _Iݲ 吃Nepj\as ҷnwƋ13$.bYxatAױhilnR@|/>|@XA{}I^)yDozg`:bm$ 袘] "L:fb Yr{Z}W4/!vYHuNA9o'qvN[|"3b4aT/t#h|H[{~*%`םe0NG᭪Qnd$#5Qy3#*W<3k?;ajpZYO'0A5Rxk~Ջe f&S= pWYYa$kvf~Fn+m渁zHϒ\GzzCkIགVЊt27%L| MU WiqIg s+  ,!©bc,C__w-bhU<:JH--o@54w ҝ 7 G"A*g牥wHմu~oy70$!VǙP}t,Y5ZA1w֭/ w# `f1 juō,^ꬪ[e66.ݓ25PXa٥+ $48!qRBa~EwԿq}Ҧ+:\n\ _2G;pΐljd0Z7;KNET 6%p,Z~H܂R`Oxʓx@},#!uz/'z:ULq" }&H.r:'N :*;Y-.BPZ6K5I6n R r=aPniQ2iKQ4\) E(,*f94SHPHe5=78YȓN wZ}s@@3FiPdI},..]wozNteB*e;G=w"'f, n!;=Д_yj"Ryt`\Q(-Eu&-?akVl@kns:U=Bc H`bFߦ+^$/ ͼi\xjbfZ 춍c0}дOT3]e#/)7Y#\Ү82 o ٴRR6j0aݒFac;<=jphU&|檺š@:wy>LDTuB?33fR6J X<2 _Ӂ)GSq=3?kTduhQdE@UVe1p)n'=&KNjlXi9V 4gS :7)+VBG^h{ ta:Q_^uLDvr ; |Ty^^šQRe1 ngͤr5?~qCߺuO@E7!VX̹tL† Pp4v.dv6ᮒ:o -<DNw8Ixa"rDVp7X\2Lߒ_M9tmUEI`ՌkzV\pxUk痌1iŃק< ˄f ʛZ~)^9Vg閛UFy!`mcNF3b϶ 6^/;_ "#.&D >}e@p(Awo(t߹e=Ts[ER޳ ;oc Hq3Lg$obg H^5J9Ď.Ag r,!)IŪS4y9H`J9*l3'xupȰCAB t><)=27 uOVpܛb݊sΈ^kʬLqjyW۠%t87Hv3 7w TZXd9U n Kde/Iݡп>=x^Xȇ3;n$;hD\#wB硖eDOv(!O_HT'| `wKCWs#m> gr%KXT}Ş6b+%VCnN1ZX'1|,# #{^4sw{c0X萳T1j"0p5S^mQcOl8NY夵%#'I38?yS.+9[iڤA y5fzqՖ=z[ [8N 3K%0*U/s0T$d<Q"qh5l3=oށP\m#%laΖye@OiW]Ŝztn, ڷ}C̑Z!g5 4,9鯪ec?'-"P8&W2FJSyNs=xi^,v.Ml6лX&3tQAzqm Ds,t7TrIbp4jdf,{}!I0ؖVY%4*LaEk0;`7qL+ lU05 0sg iC n.L2^X$ U@FU.v 0\^D<,:Gw ^?oG[x e6 vF;EF|hJxʡ"a#ƅ}nIM;˱d:i4B{>q? 8(ڇQ=jGG\tTyen*p{HZ*&G-y*I2k\ cU@r} ;m%ʰp*UTF6ekSl+H5N V\%lk7AymN @c(xLp^bf[CGyCq;S0}sb4v#4}0gk RC*_:<>k3y.5$MBR<@T_^=!)ZWNMd&n ťnE7Ն`i*2^s%búq묈;.қta)9 y9.4/#Ma|թFk ZI1WC hg%PpupRXP"8ش./Bߏs_þrLCH;ȏ]6Z-챊lҦP+̺+%^{.^N,Zb1̬3sL nM(ҩP`6E.\d6o*9Lu\fMnSʌݜiTwEZrQ". [#JY?3:}'ڙ+CG3jNhb _4;=!W!^SV͂6/2*:m:$~q2m t11:#]WC䞘d.$#F?+_,B#Y^H&"e*CĊ%¬f“ 1D hqmCpNȤ #ka.2K#VB/qm"4Ы;_ Q\&.jzuAl(ָy-dӯ1I@3 I91^ :@f帋F-$#}&S,iz-o;?e7`50%}0;JFg;%NH_dLB)S-1Ͽ?`ai\D,)ʠj~)+=A>En~E%f+-(φHWa 7vcwR(W?NSR} 5bӵ: Ea |,d er16L*DLFwk д Q +%V_c+\!Q'DG[}B(;t%/Qvr6LgPse0Ii9C"G]_Sbdt`85P$&|EZ])7zejU2Uٚ*#kw|8E M@}*רYɆ|!݇yR<;^ ΢-I{Z:1H%Fx8 [Ԃt)GRט*E6Zo,Bܣr:c%8j+Qm)N )}ϓrKO7k_R|FD 0̽&-T :գyS)h;ugazo iRp^[5?esX-*,-zSN<6X sXS<"-V/>6z`V[^y\FϝMc@ cG9%E>f. =;pRm(;(ifܞPʖ8W-d;^DII5h9`<(dwc=hO^=c ]Vf{uDg^q;Zl #uf%r"U7ѥc)6ʱ.+F/0Lyؠ7p /@;*Wv)([SFjD{8)r1]hMwӏ,+Sgr*!E>r~l@G.i| Ͳ:@u5]`;l 6]mK^tq. Xw,uuZ b7=:(]*8LUhV-KI]3Usr M$ TdBoT <ڱLnUaR=$1pfANPHKmZsM4/r2 c!huPҌVp3^Nge*O&NCY3ikX&7))nl$e܀6XzRLg8 34#ist^j$1c/\ȨOb^:R-j}OzY&?P%,6KO-X*-vv⼓)sm:ř(3`A~itoz94:n'ϫ/]s9/ [[Tw V@.CZL99thK{޵K Ł=:КL%mT*~sDE+U^n{Q.cBI >lO"E@</и\e7I,s8Ќ>wp4 mx"y\"^3}P)ɷJ$fVc%  oeP1:yYs#N_*O$͹y vpt ʱu ΁jr!k)a&. }(3-vCrnl3kzj`NԂ~OTP'&F5]د{FvJŏ[z56 Oa>ʮe0p`..d8-SHO}nZ% )Jئ_ʟE:]CjbtI؈{7鵄Q #ȝQh^F+u /lfaJ<p`'OcfY{[Wfǁ > t\`D&᷆tC^F! p@c\"c㘷UƾZ4@ZyJ{e=/}M ^C1 2Y%ΣLV! Vhd+{(~j[y%^|Ds0tn k#(H_ʷ%bpƆS{I3pʿou{"D'gG4lwί锖v>K4\\ n'Ҏ6Bh~M<{~K ^zg^Գ(Q݂| 5?29֨uaXIZ-8pR >ߓ0$N;,I1*]#D6#=V_XoÉyv? ٥xtQ鎼]ӊQ9mVV-ً@'~? IrUuX?N^ya0?;/6 eQS h1dfuM:08O.rQҺCwI%9@\S UlH_&dBQzQ?osz(9 H|SVX]3^vv(ݟMc6ZiI 6mO-{Z*Zj!F{ *.b6x/J/ׯ0q.ިNq6j-ώ$d& MclgoIJٓa j?CboxYw<-;L0%:A_P /?WDRBdts]W\&] Ŵ'TRhCv043p!Eft;٥u|Y)g>u!EV0y!O1FHNHv6r4o%^WZ pE }|k 9aw D/]x]nx*uL},Xx8+J]PN ~1|YX$>8b?}C*X 9ܞQZl󄞓0@{nNx!w;뺪 'J%h"Ÿjd)% L5M  v^r{! 9!tOD;l=}I|/LnnzT`tU1?}?s98u=vuvo;YEMid`<f%τ-K :V U昍 zx⼬ {{yep˸W^7$qSy՘l~Vh2U8V#YeB4;?[)Y*9=PWC̫z$ Y PU}2Z9_VSlo8\b' 4u :=l XQ r`ON\"]ZUt.z_Ȏ (ԕD{Ͳ}iO D,#٣~G CӇ=l8x5aՁT L=doqltZ#3bӸ'.DqY^kK L1-H "bwۆymɛ=Ainx%ogyu/c>;gܡ2$QAD%=f켹;G&r7u$ AK.MP0驤F^rV10Y\QeGv>}=$\, XT2?CxIJ "{7}II%Yb9ޑfoA,K.TKb #3'{~c]>X@=I>IIVZ=h~5$784L8f>^‘OcRT0Ld~[1wRȪ^ #AŐHOZ<4W9YSJJL6-xW$&̬Pa}Duh\HTD>^ 0tgWIv5:8w|b}~ sA Voe2/^< 2Ryt3eIQA4Z}m{A~dɑT=%FNok@u5 tdp3&wk.SMVi lj%SA)u~ôg+kdm\ZܕÁ_!Y$VTub=vm߸Հ}-&Q+a끏+Tr~Pdi'aK;7S;uoYS?pY$eVDGa$C O`I3/-X񆐩mq gz"~`3gw٩؃H5gpAN$ʇ{j@/EaCCi Uu7p:\w^QlsWlf|7B]ǨFWI==YlB=(N~=>֒eƙFF'C+WdLfeXT!.iyİ?JmSkT͔ˆ- (TWc <)hnzx6AOP4+\$Fu`^5}*E[?Yu+F3RuIx}\;.i 6Kk J|j'qpD擪4e`kW5O8@aُb+ѱ¢k∈Lَ<,23mȢOQԯ"8t{qð0Hg9F;ur/Qgkymy/(KC)(\Nݻq=2 $gf\׎޲e LJƺE([FİS_ŧ,b?QIdK$\0x7+\ QE91aY(Yٗ캀Ky #j!:of+$BY4L?!!tbhVL5"EEg{d%$I2 #ןeIN~2pAY`A/j"ׯmix&qnBc֛]pw^VV8ŰuFQlWgXC&.ɜ""uKL/+ #8 2)mD앐#`Dee L!BY`(s 1d.krgn~Win=qTPCFӧdʰGr}aVܴwJ&ޘyR5M?$h>2q,, ܁Q׾)xe2lo[p$]hC5D΋yM]rF?!!z(H~ݞ0k"Bpw4fRAsMOŲ_}>IMob50wq|#QX>M{uU0 `*l4{GKIs20<^껈Yl cM}7 "{)9332 ;QVT⫵B`+c H4؅ߋ fh1X3q4if(ӱ\eUCx4 og;@)uـθjB Z{tIi)^dYʈ{r2( [Zs uXvY%ǞmI>͏GE>b"ImL2uf\GהCOfSd1#D%SXnzRT풅]y=5B#xOK滗iؠ`|PrXv&gm(OUb'GTCD_,J*W2Nl`hs3&*T+`3j@8,3 I)U8V \)US ,Bw`O1;}NM^Gn.M5L۔Yjԁ[o`/'I0Q&n5/{ 9h`Z@&Y1I}1R=ѰvZ#h~ + VNe43n¯|aE,f{JoPw )ٶ( K5"M**QA[@(TBYQTkgɻlOwz@Խ)xl! +fPc ^bÍ|2p!jJUR)X&,5q.8㌯<;R+'[D'{$4z4lb%=8t%yǫ?>6F!,Q?:4bj2䯧gܫc 4us\D(ZRZa "VC\XJA41x""]iMҵ6GE|9 aIo V[~PJDž/\ DjfHTrwTGQ&p9 shbH ;dqIBALB׺Ck|1ĭGiԬMYɀ[V рp*o(|k!傃Eo[4CyH;l ػrfR m>OA<+\־^hu-8f4T˜Gi#/^f>QԓékD~ do3Mkfktdf(*]mCI/bP?ھNM9-'n*vSvyAf8 }Wu<rht"\/UH6(gbRHˁ돭XI8Ҳ ^HDa` EDK@D|(az5 \;B衇RPsEUW`Qq0ԥؤZ‡ѻD:QMx8Hy|\Lb =7ɲɫ4_6Hx!c%ف\xqЫnYYȘ/ߥPg!M!paʮvBbsb𷛰`u,aX|àMAD(7ѝ1h=ԕ@}K>.1%tBkZ[:hCQV[4hXZXH >"Wc0ٛ~<]:7=*PTqaH6=Кcs 3Vћfн:.6};V{2mItgfZ`n!Nm OS.FWˆ.9"Y ς*S:1+Oj1?:Ɔ^{լZ_3I2ZʦH$YjH:ݩjV'T%ڈƘ&Gyk[}+&$z}, {Ky-r^Βhbɮ}N Qo*E/&^'_/ϬQsQBE^8 vKHy:?=4U!ͳ̼8m;8ִX_dt-lHH}#b*'Pԣ bHN 7N81dΑimC%ZYX"7/ܪ w:Dj/Hwy7}N 4v7#?&+zH a)yJ`LHhVjErbr _kazN-:7/q#v8XR'ģ} '6 G=E ?ŽѳTZ+~=|'T4~0M2m5ڿ,0"7/lwE..B_µ*k6d.PB0|GJl ԛy9`7I63^s_TL^su {h^zΧs: =GZSJpVS2" O[(6b;aHnފдUY`Pk~_"L=.a<ǡHɦy,,54ņA)r-ۡv\mD4 R:'q~0Q[i:'0Oi>D ~LA.\hküq3?؉/h,Z5,͘} #̕:t N6nNڪE6^J3_'hrI5+&?>#akGVfAmX"~x)}u*145v]lq i+GI\kkL]1'1KЋGvh!!͵ãHigPR}l\WJqoU֩U=fRwOl˶^tQL0; 1&{Mz_-}:ltq\,xx0-J"G=LLZQ伏\S1JԤڈ%ڋq}@6 ;]R4'EYvZƃg΋AsposPW:9xC'@9b ~ټvrPROA;J 9 a!$j,@ع3LP?WqZNۇ_u!6xZHZ﹧VaU,LIPONd*tCozi+52](OCB'IPB֞akol7褾b* 0w Wޮ+gx0u0߸5?^PcKpm- !KjTk"Si3ƴH'XʍP4 o1XWԋ`Dn#sq}4E+L|Ij)Oy6d٣X}lL#Тfsqt/$Szg^0rOq;ekoR3ծ$ךuΉL~,SpnJݒ6:>vʱYo""ks,0~v2fz'XpH cJ+c%:>r4L|bu/%WB'/2f-n7pґzQ{t3ZGԗ!. X3_r)L5T}f9`ae B{ 3ob#lAҙH |^૦ 3B{4Si雏i%ViD-$n7FVq 92㡤6$9-gش6X]̒v8JW->xε=tACIzZ_ mQߙǘ@Ls|5C"ExQ^|ʼ@&/tD hu(*mf!)7?PBʉlCY_5y'd1;%Q5!3loz8jC|f*y.94Wl+u4=ǀFf,rta_~r=#l1!Pׅe-INscrW_nXKCW҂$%|מ|8;Jh|Z?g&eܾtnyS#0-–ߧwsH쇰 ԦA3_˖6:q@C^2mA ?32&gk#Oң{!JV:+3*nMofyS)V#E3,G>wp @3NemfuN[ĉB]4sJ2ca֓h9Kא R bxg]ll55L BݎŁ\m~0$@:AVCge7Dd뇲4Tv޸ }#ޕ,T:gѹagZ/')\_j tu{nOb3] սJ',$Kbkժq$¯vIدYlae- ;L9 iIŧyZEAr|a_lE}]|>K47 4KWBIsc܂u#?.i4mBͮ%6j=O)O<8 ? tX1s.%<.b6E*D#^ׅհH#!OmFb/c9kL$2ٽȮ\YhT }-{arhΥeAslCë|v؛Fc?>璵a:=a3ȈھǗQ64]ͦ>B@"Ay&I=X|4#-PFD6I(,&Y "EHǂ_ɛ,\ N͕={սNڷG^'n0e0۞$xX?v D^K䜓l~(]*ډdlms/R}HRBZ:bQɈr =6Kcb4M3s)wvͶ,oTb84@)Z)Qç|u^ůzekOgA-!=qɱ7 >T5c:Ac)C7+81qk+y7Ep,</Rd8*c8+{A~*N<:j+CA`%~ƱY$jjl4>wlvbǥBn #JS1Lko)5cޅ} 1/Dٖ66bvE`_ͯi+MZl grqAUO{Iĕ0Ȁ/l2443|cMjwiyFYGCvψΟłN4W&-IuoBc?[\xp[zlJ]U7en7~Lr+q5JcP0\,F"o;`{͈irY3ʝ եS8 })uyʂLHe9C+H  w[5f5KPKjvo'R"e h˵0^RyOaf`pbPu ^!bd'"Saf|뽂\-tXͿH)%YGfl*W?,V+zotn3foF':K-w3LI ':g_gz/4υY+F'8au#Bro"`# 7E6y21|)˸;T#rIxUo(FkZDvK,)Od=0㫊CWnwu?Q)m,F,{QQk+L-ɀbX'}(/qT=~%y p[%&5!VWs_L9WFINI$QJ : ՏYov,ϊxcIמ :mOW\d>KePT?C"IaF`( 0ÇMƔZ4IUn̋|P_' O#IpEJZAc슴/clz`ŀ}yضGg/0"4aZY%)@_nʐ@#ͷ+qk-!#I&qR:fAI#4NJE|tr>zhu$xs]h$ҳ;j_4.7dNS Y&NFhqz{8"zK;y4HmVQIvB]W zc̢XFdQ:.˅-|t9a+U0NTAn^yJ#P_kq.Y|DiS2FY9\&k֏'mn{nRR"1 Ԟ*yp.UU1y6_mFzj:-rsA+.OG˖VHd.aWǂn@HD ЪvɎC=Js"N5Х j | dӧ#(B+ LBۥ mn(WxV=G =ZV+\]nJI"1 umˤF+ZA\g,PO3xǾm)Mfb 0Wџ.1U[(E'䦧i7t[t??EٞAo%8ymDZJwpL [?us{nqThn6Ci^/L`$KW•K7꽯o_|gj 'g\X I,y6tvkzY4H:z{>Q&1"f'Zݰf ѝf) ֔Vrwń-YMΐ@ Γg*mO/K%qz0ܚ;܇OfrܾWsxޜSBƣp檛D  83i ^?ƌ90ӡoFune(8:"pNsӧ BY,}j_$BnľB ncf &&,5/ktmJni5܄S?IȈЭs~^yxVBYNhOX".<Ջӟkns($^j&|JrT) ÊD,Z*zFlݽ59xtcve TGHVβY_5 M \WcȿUD5F^n͍ቅP\ ;1hRM~=a*̇9CVv{ȟ|BDZڡ'VEVmնjHŘw\։XgSU 6v#kKȨ֍_wUw>P&E#KȧR8!EL%IPZV ?z4 #g%Q=L βɏ=!J޺ix 8.L5ʷH?_x u-IvQ'a6/uj pAZ.\G+r|a^AR̡cm#k= s!!wiCO49<yW3E[rDoڅIڅY"|)/ jL'J4 ({VWob}- -.N_h6aG xAS1^S3bL:ZYTŐ]fBT^VcJ󨆻Z0&t7~q$1TI![l1"k G.BJcoc R5/ OueEap8vB HD>:EoD*}R{"msȮy-oM ,jmbDK{})C1v4eFg1;嫮!21ܒrVUlDm3Z Cߨk_Ѷ/Th6jSW.u`0}J͉!߅cJDd̡Bj>WMy0Fuӯ6CBדe_!AJ+LнwhCo^3]Ȯ|hnwLLͩՠD8,m nWe` l#x8Qj@Hcaם"z9(r#ҥ#*6.ʅ"(]1b ˜tP9X)ӋdnպѰC R/ljM .% G G^u5TQ^͹6/xa!OO밻 #t 8nb%\7]+9tQу.^K|SZ"JBkӁX[Zﲖn8MLm)׹KêvF<~j<݉qLzJBFL{q5Lx|B>2q/T2U(1MxL:A=.?{d #ϰõw= !gLrΖyNVC/ҏF͓ӀXIֵ$ xI7F'W\ΠXGtJ n,6Jk"+TiE]!dJUu7'++]z?DUD!z-iH'&UPvӖ5F3ܽBwQx0R{WٱEr |ӻ)YʑCn;-;EY>J$;ڹ vDCR^TPhG 3Y1=VV YL #a=R'-nLA~4QuML5rők[Bkn7JsQa)8m+]FHf}=^HE|<8% Q0DʫNs^pUUCɵ¢nv@`W'} '$xG^9!d5Zq[o+,]{omnTPflyP|!#_+0Z!Y59a+1Mpku~%7Z]zo\(H K`Lk *[q֊Us[xigK3R6y& _ ZTzaNe&g֜9j]  5cS C (av  9-`-<#N<%ݱ?CF'XZ t,K(4>}3Aze_݋iߙ)ЃϘ|/hi Y]2 \XLlIplVRKZ+ZZn u `~I**IHSp~Fs[I-$໺,C*kB1?PnHeGTR+A{:)EqkN+B$)[ BXM:D"íj/I+dोPB[ %!C( DZtiUf _?ZrVLz()iY^o-P9Lkmiإ ʇOHѝ|k~si[OQ/3~[_[JCuZ QnSCx&[PN.`+{yҿCˆlMmPx9[P} PCo*{z6N;IFKq%%џw|w+V %J5(<Pb*2I ?bfĺwTp!]0X^Hr? {٬$јH}Xl-H=&I\arjj, ZAY tэl@֬!$l_\e?Ի ʜFmyf((ĭugKY=BXSEuo#_/e/x]m T_c ߞZdbvŒ]C(~H JݏDP]wx%KjL5lHl%[s[cCk*e>U{3ZK{A}}3"4=<F]m_}apk`D#\Iۄ3t¦+l=~!? ~+JUHk|H%Jݔ+aP $,׀LaTPlä,l^"ۑ96p.dZ0 -Oݍ/YQ8+~:쮀WP?6 3O`znҩGʕQ˯ @)V64CrNc 3Wuybroawt)Ϩa2;*\B/D)Y얩\mXW%,"s>Iק]ѰCEL=qj˕pRC}yЛ$o-^Ap[L|Ns6 Ĥ| 1Rh~lE##Ӷq 3$)i[,/m&,xi_x۷I-$ f\*B>,3CEVT#>gcmI'=6A[_Q)Wm#A/Fȗ- ANm36Ø}g|hDIY`?Iq@2UnVZ)VC-Bx^uEEG]),3x5P|݀xU^M6mVa* j6KRlC#>8u5{*N/{lT>JG( vy/$Z峓k.{#] mO.-l~;9|DϛePWWN;U-`aY+*7/֌ˈ\w<RG*f` ԚS`.˴l|]q 1P_ٓbl1M|jUTNә{B)ב`V̗24d$? ȆDV99mlLf4LGdΈm1\.$(̺͆ץ~*--Β K)rNi!+ '&ؑM)#oMep!|IzrZY6Ww8|hE|1,,` ] w1? x sd{0,>mRy,)CVbz_"Si_gKw[]fBKL?\fyy5z#\ >;ЇgXjJlk' 9Z  0OlH6 Bl!*na3Ej D~J@-jZ:BaN֨3Z.cSsІ *}M|AS04߂! jLMXcI_WG\`sA#seLɨ`}ey9?ߖFuqDɚphzB|>X [VgzĈo<ޚ `χ$D_s?]țpC/ _` %~_wÆ&?T_1+zi_Hk^iܻtQ@꾂+Sڕ }\^+4"5i-vCC|}:;2d xŹo9ŧ]ظ`PAaѱG)-OE[R3)bLh7Դv8Z_0a@Л1&"/"'OQnW/l'5L &Ȃ\R->`Rh/-")oJ;<,]9XԷ: 3d o0e2,P䅪s mMG7TFN:<7.oF X%,+&Vn [BLQg_f++ɫ.KD~)^ը?p@v:so1nw}̂#d4xU19T@Cn>[h*KqkZ NǍ"" s"Z/[\_2OD{vӁI@ "nf>2YJ(k Siu *6$^3Ne9hmm [d/}&^  G~R K8aq/Z0v>tz\Eh0^5?ީb>g… ; Sڊ۰{b]v[Q~~BȦYW}+3 4ZD)@$/݄(6(pN"pUq$ sPo ^A dsV^9b<8O<5dK%n!pWsd,d7S?rF;oX $C;O~{RoV6/L! ,Ccbm2`٤z JAf%9yhwiB %g ]&&\j =RxיtfF 4Q) (:z)8llMncZLFkRR" ϘJg@ 3RL^ףK+1AIu0nY\i B1ASAQV ndLgYWi81g pL_)gKMO_!-IPo,U| 䥼 ;v\~ ^gr$~'%#vϙE۽&Ym 3V:U˄NK|Q"2XPTÉ";xnɋIy[`{ܩE蜫!Í[I֤ )`T~cL?Yɿ覽3VE$7.T GRc(9OVu07d~8!1X7rp: WWqLJ"i5y|=3/""v4$0fv!lti _Ґ~f% d~(~0H.#.dYQ O/E s\"%Vݍ Sh zlT`e7ÿmAMT?ҕS9'? U`QZ#`edOIl 풰t=E|dn I|Ί.R2(t'O4T(QktZfL6OmQPW;_3>[{gȶSB:_V^v`$ =cCi\! Kyo]6@)ƽBE4_e{\ܰ 3suf ̔lֿ! =@1s8P|fPFԇڡjYP# #)O[av=gƨ6V>6C?]5$%gA QxMuaB=93flw"[dD3_ӳG6y,UT.ԴPu+χok-tt jM8˂(IڹL?v"X2pI/ WW_OYN.MhT55鶹5;%",:c9ϔ뤔|{8Q(xz s/oqVx$PN(dibz_qm-ެúO -X G|3no^5m$3c'TC )nJ3O, VZ(#X"Eڍt:hzMe⃲vmvf9I{Ŝ˃ktK}[&[<zԍY,;jV)@']U|\S=fKʉ/q na/چ6|H< P?Q5 1ȃ_$CfJy[aMC/dAVWSR@p&)DlN h"&Hx%mK\:z+Gڎ=Ӷ5AH-kMSńhj<;MIlqDEW6d/쪊 4lap|eel-M!24f[u!LkDwg[:ˎe>j J?aO\l`Wqg}V„&_*N޵-v1'.@X x(ۆkF/sRGq*&P""N^̭Ā..ֱ>Q2K e tӈRH8MoaIHsξU@$; oo7g0U({bﷁ=owZ?֊i9/a|XwB5R]At,Euy+0 ;d5 巵mVSc,icvI4 R`iUG+[L> Iۢ^|9a)nhnIH 19 ..Rёgvx3Ce~ `fK1ͮ~pmxUL˽%{GaزkeYzuU/qkorb+ֳtąODB3ew1!tgWzͷo[E_#)n7&PIqV jc:E.]2jmBfx,;YD.P\BV;C:e%St!ݢl]l9./&h0fL5-:X혛{}bF8m%5k+mUFd=VMyZZ4ք͟uq+<ͩl+Λ`Y$Te";-榏({|D0e3Lw/BAXZCZdF{ >UD#i!nﺵ>=w|S>8v5<.q:?Pg}Pt?Kf]<:|k;djַB;/ ^GkLBׂa{g&e|& x۹|5~-N2X'Y` KH+>(#*~1(Zfs_:}$viGx<#*vo#V"ww)14+vЋ"7?bǎc嵧yNF@+XГp_ĝ5T`C3L CJQQxVkY/4}Po8}D%]%ռzpԊQ}HuGZ:sv(>uw.{숇TթpxLw$+2vϜ::S*2}uۊ?]=D g(00FZza`,~Da7p' vIDU9`J_9VFߦGOmG}:ˊnKᦅpGfn%=:4YtEypu3yH\A."O!AͶEuo:w֑ n.1߶Ch0\Č ^>y9't+%?VR/,;$sװ0]ʬ w^HFC(~?H"f]uI^c60)=$(@"@maUhjXL@4pzE[Ht!2kͨEЌ2 2 (A @"؏VʤuQZQfh!&?Ղ}\XW&D*g J߰=$kkBIql%!hcZnÚJyC`kE %g5N- c3m'G$ A(6@T(169ɾܛ6VjȨ%ޒ^rU6!R9kNߐZ0( DF et'}px>2DЪx ΡbM ./I~2 H@G0m*uHS.A?W:AK볙xzWe Lۏ!o-qIZFu8;oedXMarHbE0[RCG ^Zl/LA U'~ }Tb"97/DC5V8mwXmy y% k Tq(-a*({cϬi)I\9{,X6vN o 2yKX v#G1<djtxdXP1pUa&ʂu[Ź82?)ᣔx>*B~I,7-_nxRx GduNo0Ŋ_'?JW;$eO%FxLk@"7.J ƒ?lnOOF|J.yȢo `NY >ea~q-NrĔ56#(yW_uW"2()[|1i> c#F *` ǔXOE͹`YkQV .J>MtuX!Ix>J9hu9UɈq'_!]Tc{|TZQ 烟tAXƗ J5فbD!|)5|/bt婻 \g+GZSak%ٟ ABVI:5,?:Q=`p_&5mrW, rv)Y]%,Lx6\Tinj$$bse 8<4v t8;@*j7_-Գv{:`Օ$n`zR$.hxv}NO)G_Dnaa1U 7u+IO9w5(^{I%=f\{R)u^6$~ He(焞{_ˌJg۳it`}#יEܷ%kN\1-頸.kP]4vIZ=&y)!_k/&ޚ#dnN DYxL%ʠg+2?+A,0z^'o*IXL3kay W4wX*_ldW'Z NL8mqethzcU$C Ѫђ(W)(6A  ?/Z>1''yL}xźOmYT~IF=?/D?E~n0MG>"aۭ#Sbap8xWsG嵘0;)ƒ* zqpM^eW`-+ GQKլoğ;&>[d L ۄmE4;-00uvoq tb !mB~[;YUD)Y!XJa ,Df6#d\j^FOu_nhzG*Q%=^珗jJ D=YqE6(ĄΗ5C!R6z4_4w+UHj !I:6ΗrDUEԗF.<9$#Hh? {f3*@PGD!#03Iƿs&,_u6 PMĬ9!5~Wɰ{JLVjA*t\fI_aI?UpvJN7|Ѣc-ZG_N =^qY-N~{SV)u H[!@%V@P {pU^i|cW X=)͙@FE kQnZ#xNq}KU&4W2zgk@B_V7b?. FmbsѦb5 T;!Ș#j/CYeE:}o3Aa@)//){)jw<.ck~`J]$>?c:څjCFUM$'sdei;<LD}dV羕͢Њ'b}"(u' JأRF.>H+擇dYc;J4yV9_(g$PyVgL$q9k٠oIdAFV\vKc[o@6 {w*!hpբ!tXLPH9-]JX?BV?qsQHf6*%o0Hs_^]%A?]w LçOk} e1wGH&2{T` HѸejrc.!@Df1ے~E!$ FͭP&X݀ڸm;uZ}ݺ-ZsŹzW#HPEp4X)h6|uv7 ?AլFf_"Xaŗvt 6݅ŻԂp֚åBW'bq:jZ]%L">ot_bhen0ʀH/Q4LHFcTY9^͸NaDy SiH̯R`eKL2;b}5Q;v-%h 56>'3s7hB}Ž̯ėi÷ Qۧ͑SpEM9wjMX·zSʠORa4]^2Tc-EzҺۋjr,{+uҢx[77*596?zǸt~GrU:xBXĐAϞ0a(M3tGkH < yvaU;kdJkRnhb\o]|ehytaGג+%ĤRHTyfIﹾ梵k蟑}jq4zv7jSc`Aw΃Zסlx6ֱltA9<,̹*^h =A|jF}Q^[lz.Kzqǭ*hmboP0q5W&}^#يI7{=ܫг",O#w;]`҅>A=R`3hT<*b4]L2V cWǓآ= as(7u[vc9]QDGZV l)lߘ;WgERdn$֬ J1:󊢄,oLf0 y.Y6|%$ǽ8(mkU9Y K!ãAwt ҦنGh$ufY;p-V'|Xn-ܑ:7GWvւqY m>OMm'\{ܫrYҫT:/;=YS!z؀|N$BXAH^q'q$׶|sr)<s/dy.t+1O0&`uP4k磑ܚI,YaFpZ! [ |}^"^y7'7PJ+.=y|/j9I^/cou.6cu'\i+ O!-/6 fkVk '{e];𢏘.ZMF-'d2U+BY֠EUXsr.AY\>!]߸畫>Oi% 6U `?AhU_D޶+\Z߬j>NOka؎M-2MumN*EPɔyfvěaj+7ɥ@YE~yGw{KNp HY0`8\נ-z(y5VCya f^E}PyDx#=!K>HSn~3H1r*UbcML'L z.19s_O ǒMYH$q+?SW1mo8H2q q1~~0,*]Hrpl$}R l%/ᑀjUJo H);xUȿWz x$+yֻWY)j<m#\UIs\OBWNmu2KWv^E_1 ћ4+C'/>S_%(lN@S膅YBx#=fIx͠K+W)z-9*+{Ү"l3,S3* bTs5"|~<"5p`y 2i &gjhCd|&neF ?xLxb[D{|J~`!&DGxF+\UhvZawryl|z.%,SOPΛ#F Vџ1LfA ؖU"2]pX_waMfn< nkCk.#\WZ~%4¥**QA(ȼzK/{3WL.JƣjQCsk1%kӛ4D{E K_DC`#{8Ɠ-]-˛N#(V\~S={BLÄ魦~ w@4y~#f#(ۆ&jYWGf#S:v7V<~Ep[mfVS~U  );%Op=:dPF89t0CWw>RmB?Qw1X(EAGͤ05!2 R]0ombF)X ޣR*xnXi *qMF~{6\Ј1Ɔf^r{-EKw?23/vy+sl4bR b*|e/9S%;ۻeY3$eWUaa Y%>m"RK&)*_߇'ooP~jRD @0 SCvȨ,Ę6#( \sxn%̃dXܠ$SUF,xCʹkeA"渿7 '/W3s!50\r(iPѾ+H<3z+36l2Ӿ0{')'VYxvI=R2G#**+ Vq%#8urah8IaGƂMKjkl:zMRd\HkO6OɂQy&||uzӉ_ldq%= 2(J)A#w7-;`7~~vjpaq$M5GKX .l?.jĴ1@1qni:()ǍdY$ EVL)gbF /a( E*p%`G -H(lh2O=B=5BIGΪbBùmTLlT0jnkZ-6-2pEa$%ua4⁚kަMT3mgxH.D|]XGC\aţpeF#V:SADL˫7Hⶺͽ4 [R"Đ[B؅~$qZ:HNG8X`834~OS)&3!'x/y;8NR-$ZPvN#"H %?š 碎[u ]+K~OX5*%n]%prJV@S%lB'tOjН̮"xWD.!NȨX]zy8 Cnq?.F R(C\Z]cnؑ';KVV{rɄ81cGHDMB('s9ɹGy[z+îRN|#lJƃBËZĤL~ү>f~ZSDeƄM *htWɶ!.őK -+)֗wOdXnBg'-zδDgűL 6uqMUNU6S;n<+?6tC2v'WZ .Gd`5妠U*T90ܔ7<62+)iP2XK" Ӥ,;,/1{_ oȾ_{9Tc7ף }F0 ,Q>9LթI&J.y!8Q>Za#r_!ʲ>@YOHWv/8 Mzb DI"z(|P7{wU,T+P/VwNx6v91gW a 'YJf`uRAkdoɿ[,G{sfG5\r>4==Jh#, r2%i'" nD]܁c, i~PA4 U<ĜLO5${gȟ:ן'WQcҒ_$e\J9 N&[? 喟ubu":mv 7ØSIk7BZ[`!5 ,ˤ4Nia$4L\<#Ȗ>L #0.ەĝ,N-S/y$H0C_C zY.֬bhe"{y}W]K9lKΣ"x,Nkiuߴh+1 7Nѩu O*x@ 0(j 0bf:5S4p#8h$H M : 5L  8&*,tri[&z9Q̋DkSQ|SӢzDVҖ i]{YmxbBp0<-aRNT{)LT%̍2Pw{>:$,޲IKaxWSJR@V/$(~&IJ%?T=`BjI%%W6in|8UEQ d3+3ty'0YPvl%5)UIo *YQћi?4ߞRLO4s $ǿShc4Y#ߩrb%_Sh KF)'Mpr)xJf.Pȴ@#B\"+wz%<ReoJ8 \7m|SjXvJi&<"DO)Bgq[uF0ڤD !KUܒ:RHQ4^N7|sJʢ{@(ò B#@m@(\JolT֗TZzсu_f:/5̕k{ *]O8((.79u50[ѩY۾kۚl,"2K$?^ %"W`3޻0J@ڷKw>5Md=da ÿ.42xCPݩUttlt9KcdBv}LK/:gҥQ[ψf:C_G^U^z)FDHknvsTJ;XRQ~}KgX [& hRj8KR挀&u+Qt6G{6UmG)"ɵKaͅ556_ H6|S;~݆tW~$lNpr ݣix0*yz17M}-XUn)uV(Ej*MZo$:)p쳈t,G̈́`$J%ȴqF;AXϪ( ԓX\5PBXcd;o)OΜ>9Lb0'*W?:"ث9MF!}o7b~YAjr4x F{8I:\N EWdcO\aR_ro_C۪z<\V0H$$j J,p܄ĖM65ȵeb-fPF-ck]oga>MB!EGB\C&ܱͪZq~cnzH}̮wr3 xCV{QByOEٯ S}RxH@Dc~Gl3K7OO'*Gkk?I$Q@@9'OC|F)[S@F`yܿiPyW~F6Ða)Pc[*W+ʗ%AcyNlNݟiJt*=7v"6Z "I j|;UiB4R茀~AB~{./rd_R7~\&}2 'H.c"=, ;/`K%LaaOX#Y>湄3#Llƺ2/!9*-WNd bHgm>6 yegՙaT_l׌sh/d[UzI 1\khRbpX;Ơ+_.}Z{sǜOe +uЭf?B}BTlΚ,ToN{1q*~Tá|66HFVڔb2d=t\@}Awh_ =z IzY& Z|%(,,]!YiuZ @3+C?G}YWfݬoi@#S9Og2£ LmVyҺ K9Muoǒu3]Qm:xC=&(jŵ[eWjaKW2vWaLJՕNU'Zɗ^O(A=Ɋ}ѫEs]}`ܕ5x(σ??)*\t:,š;znJ˜x`9z: .8Oڣ}X 72˖xug7G^NŬhɼoeMsR&>6.BOxtsjmQ\>0WmY?[e.HϼoJQ%Yti@담v1:bX/~<5ݶX !@B b\gAdb5k%D/'k$& #3H,QEq,*DA,27.984lq SvQQm "ϳGnI}(X0 ;7Ebޔgٹ`coG{ R4l / :kЉJ bv?I{Zg)C!|t~ta|B tD1C08T/[ J~2)@' 1Y3ެMʞVՀ^lPt,5;!=>h'N~iֳցNgYV3fiΟ؅7 xA\8J-+Y9\:1lIJbb6#k_-RnIT}x,x c0R +#ςvT}\}>=o<CL.%)xpt>뽂k< ԞXdhzIy,BRoڴ%>T#vz&ެb-P7gYI@}8Dr2Եi\-]Fq:g!c^CeZw4%h̴-N#[':O#n{ z):Zq2!chy"ėBhtt۶v"*n9=Ar48e |I-oRduF6J_wS'UG(B Ϳ_|EP egd fF<]u|, DT[ NhDG}5!jMfgB#HȜ.Z <ܥ 7JvMuWێD.%3U >ֻ84Fݛd#1R1BUxﮖӌJc 36g[&'}0Z Q'*A X1=BKd9 |THA5 f](R;d?jYQخBXs*Ճѳ*RcVr^9y,«~(8osBeTGc 2xS@q?J;&R($QBu30 3h;v#k>=I='9Ў~seVMp򔿔S7R3󇉧8a8MUY:NHf42<[q/Ke vO(_u l5im yf \>Q?_)ޭݣrx&eJBؿmǣ#G뿏f 9=ZN 7s$b+}Xmm#&p1}&jo6X8h!6xã.jdvOBsi8p8iX\5q.xp ;/g0);lkJ6٧N S͉nf\k)]zTVG""D 03' J1Nf"W3nÛ%8\-6ut~òTtfxCc&diMr EN4j;4:.! 4`HBYqǘ1=[50j426pitz:.:`'"!{J5 QI_pI/4$UpFC˻"Al])cf8OpA)b )L.NcNpFeie)E>w!.&qf9>o Y⮘nRݣg5既z6yM/lih%Cm%Y, JA$ߝ[HV -(3r,Yn2Uu$q/.-q\gݱbF3jHt:.r3EdX4o\ .l^"Ukdj4֊T@>ŎvH7wlގDm3[Q5JK)u+ 2jld-NBFƏ;g c\vpŗ6jb-Wrͩҥ??\.ݫ.Xs@!Xu疅b\Nqlfr{T,p(I| bӣ\L7v#P+fRJqEU y,8i&bl1Mrz}B%?p6XVR'eNq.Q]I{/юo_ o`:Dz)# Ͼr.~Psrj$d#_cLB IlcY"" 爡yn]Iݮ6-G n6$G֨{vԘ-y3nRׂ{;Z={?Rm`zj'uQkWhRh` :kJ;dx9 }TUWwu ك*(Jnd$W<$s{XE"g,Aƨl%+Y?-@3qaT hNc`YIV*DwLYNaםwEIZd@KvJQw4+vL=w*A:aMڵ8l!\GB~e"]4Ӄmn"4zب0BີOy3?Z vC,`j;{FJʸZ}AlQ@َ1k `[hKwt]t,cOFqN^! su*pg*p*?kGPۓ_ZM/w1 _UJ`q[& Ԉ&ʗB˕Iy5&/wRr<Ԓ1GavUFP?]Ep6"-Ω8G#*é {ރ'C O !Ow(ٶ>HBP2?lJ-4m-e27͙cɦamj0jN}$^JDv;V=d^2NgO3MZfY 9'4͉j]+B9BLI)@}oEZ[sj[GoyW}s=2?7RhTLy5bf N4#Uq"CegadURg56,e{LlZ;]Ȝ%\v,!Cִ-ݔvPrxuZR~ 5_[ſX&8djbhigx^z!ryhRk@* KeRKeY;Ϡ,$<2zg8%{AēOjIΗ "o%@M!C\u/F)3CXHqƓZk&dMrdzG hgrBqxe!wcoy:YQ κ'G^s'/}< PtNG-J)RLAI6 -8z ^\Lj-rν؏߱?6L}A+:`]8f rw|9syqHWlނ쯪 L?e TC`PS7h'j [aS@;dz^ .KEtLKj@_?/ĪZfɵvr,݈]FWB.+U+4 Ĺ{8Ffde`4( y&;-CQw@2Յϗ7U>,,U=(h)vl'+hQp ;ZaG-;{;~=Qk;ߦr6joZ3Ɲp4z9‰XT2O)Hh3x@s%Rsi DMk"quI=9T+?I eӕ\5k QQś^ (I#ܩn«kzpNOBCvo+tmEp?` f%19 kz8G_KT!3 H.Ctk)he #v|AUƘ܃Ax(\^& ضjC pXۇE_O{nX2'CZ!xU޾wz{ZN* ޹˒P0l= EB_5`s<6dFBlr\2/ۣM(YyH,\ UW&vBZE3OB:8^>.܉2!t4}=*9M&-&8"} P;9z(/rb l3ŘHц7Y,EmFTÞynU70a+!hz5׶j3\%Gq0͂]+cAJl+>“B(X $S}jn@-+ʌyEFh7PFcb9 S)!+&vDZODlCrxR.~+sжX"~Jt{gc zeG$1l;ྐྵ~>hoN)wHMފvb[]\Gt?`fi.H\DMoJ[ OqJ?Gƒ9f{A}xV%P$(>Fys6Q m0ϑ3.Mf! }٩2QҎwpl)͍z\s h↣MM.drfE,@,U:= *u8Ӂ4?iFg1e.#LS\ eU_neG+%(D&t1 ؀ѐ+8la >nJ'a-Hc޼9vʌل:xmkkaX].3m7dH.Ei{~u9"zDɹ nR}`37lzt] +յWq5'ڍ$ᥴmY𒔺^3 @HOt5oM2m5#qp,J} n-!-lv z%D8wWjgre:u9yKerIe䣧oy߅hCpXO]Idt,6h\lEoìՍNZ7/Ti^P'Ѕ^xQP qq,@6_5 ҙ7RҊR@L 6 #9`hmfue֡D _k8XA( iyBV“cbL =gmFt(qKފ]YԻ[)EpZzhvd `p۽%T~j?#3}wʼrA$?GZ$]`q`+g%0aHo-mOBajvyF%S3&^F۱W%[^O/ȡbVzKXG]r76h R ?¶2\zc"vCB&/"Sڮ؞fE5Ue{!M)r8 s+=>]8]fLy<=~K吰ey~=`v77 @X6Fj^[6)(Rd# ڊO fPG/hb5h-Q%bٕZ 9} qR/Izmu^:C!gra!o<5 mvHBl_/.@s*!'Df(BЦ] K ZXh.rfʮW8ުJp)PuPF`'|(+;4h@B,>_x@ K8gy{ۤLcWUY∽yMCVٶeQ"Ρ h秉 rY@2_,W iI 3τ57L%bZS{D>=l$Eg XOE~S̉͢jdu©j/DC][aG-UYa}Qn<] 0uPk#xmD#z\CJ}e>a$D B = MGe7q=$iJ]|LސD4|ln5'U='`6PT_7"!01“"Yf:=?=m9e!2+)Di)$ztbsB kVQ(N/nuDaF}fӔ˝^7(E~eIrj*ZU[k,@(ؒ8!Vqѱ|-a#ϡLD57o nu{ɹˠ ~(FJL Ez ڲ6ݾ\2vI[2 b%\8&vImc!a/xHJ7T KpXr6{2q׾}۳N%JMqt#Ÿ?Ob@rx~Edgx~eka9X\DqY)^I]b@dJk!geRg*Uq%V^`aI)J0a m~ [9\Լq`q7~@p,kMVX敤x_jBqXo\ycqyjAwX5)]+[S7C0?x)\1{D v&iR^G&WH'-mi]͑`x ޻>ܾݛt&֪T4R]gzb WP9>5&v+,&- )$(Xkb)?nxY#Tن}XLgKছ}u;I>NV[ՀtV#vI%xOs/Gj':C'k&O֏$ =.| ʕ=V x +{];R_\Y D.˜-skqSc"kGBdn^6~Տk`*qizbFĠn>88j(@q{j [ڐ.f40^2^f¤2}?$ C=+ 19|c*ݿS֏aьZ`NK±OF3 xQ <4Vׂ֞n m8 v 8Kߏxt^dofz)A?7"%-hd?GȤ< ?5g3#D4sC6)ci2iTr%jc >y(}Rʃ<{age㹳}MɇL7ĴȻ V nRp錭%x.lobn} 4=de>F.>Y,`Oϱ4сZDN;Q~x/g71]_N.nq[yzV'}-2DߴLBek1([bl\{fD),+ڊt/8;МhӬddF:v-ݸO f*3v hyݯdgsK i>Wuޓ/a50;/-"ը%(x'*x&OtdBBT-?"-VMV&w)W Fb[ S42=~ nJ$Uyz,B(Q2L$;82bI#mu$礚L]7[m*XR HLgtp$ k0h_/p俰6O-j>o߅vX YZfMAm#QVfQ0tuГ@/ ({s ej q,t0rH`UN}7(#k BL&!_ 쳶?>܄x;%f S_ʚ8CXJ1J|ln5CaO4pC1qGO/-(,pC>T E ݤrɘ^PbTr[Lp޹%#BEMg0WN tӨv|6s\s=-x.=^{aNugFv 6DjY8{5HOd?;ZaGͭ4 `mm&dNMv8}.TIRTI+LZ:7hl2uޙ L t}tiO .e ~b!Ecd—+0:)2CElOhb&G2fwHg>.d k^S9üy:`!"JfjfD㉊/  ?a+dg@;H*OU. ʾeS>aeilt5{ g(Por_$V̂v#yқ2L QYc]:pMJ1YI/%#[q^Yr޹m "Y(yv/i:%OyMuqc%`%(yڴLqEܶL2~ WiUQ%gUnZ33 xS 8YR Is.h =yK =Evɇ'5ҡhEz~iA#ջ`_ \6*`­buiLe|bԘfui K .t{(aN~{ز;o!ŃKEG }P+S5 k̙'p( BZ8n(4,oD/!2r|DB, SDq} 4 ޶IƾUC ;Y,X!:OD8`qm,]aJ,kxc)~)7'׳܊]D8OSn+X?=>lz=gY4,Bej9)dpS(R|+p( ;xbIs(G F+})ovP2*qQRz#4M '_(48P;0ud4XstK*E=B I ?ں'_i_|\F3@zrrt"o!%bI ltf񸖵ADeku`xyco >&7'./Ɩ03ǭ'#g'oU,lza8o#94,dq\ ;G3BY.f ]Ul9F |R%B+!JHwB2nƣ{8n4}S RD-HmS4L ]HVm-%eV rk.V VUD!Uk9SG8jclf]4 {` 8 A:k:l?]Vqks_ela0/b{a^Fާw٬ݴB|1i= $ڝn?JJ`&Z #$}HՎfmΒ^Q6toR˜okL}Ԑf!B\ow2?)l-UJ{l|=ADZBSz _ v}QXDO"4ij]D eaBcGH-Nѕ82doo?FH*w]FeSɠ&{̅K+C$v;1< t|7rbŁ8 >ζqf7 /H, i1Hn[jyNx&u&: joVȧy qk^˼I] ù,`8CnYĒ?RF+AJiLLJ D҄ZQN֨4a0Pdɓ[| (&(žuZ)<,dCb-wve;}7v$#mܝq!Kw+ "o $~ݯ { U֖~W d5 mc_,x2od>|P46yqNc*^uXHeCFSw\IEdh^qJ0x3;/]ҽAZAcaxn~6П%Ӡe6 3zŔķ%R8,Ime!4%x#QHt)pU'v+>D4Qz' ^&k(it;IKH3s{mIb"Bez5 "*{:8ޞ/C̊]uQD/yT^C@ ?:5Dx~ 8Zwk01zoT6j8/c ц*ӊO(JDLs]Ր\ϰ8Q%}qYmCQGl(^G{I1 ^d GVJ%ky S^Z[S*ax{ &Qyj>^zaJdaH3TLt-Yp6sѮ9;)jlA\Qz71e6}E 1b9^^bMR[n>fxu(t]`}kl'kQoR3Cp -^lO(] GPBԮ#XNK%a2 g%QBcz\I| r9Ke>wDzפ5gD;5>2Da磕P;RP.Wc"#||>?0iLse3AŠ/I [56o K DY+ ~ /^j?ŕ1dd3zJGUq^SiQ ϢX!^/ [yFW'FckߓaSLt`?#ɣljQDSYX_8S!mAigM25l}@$\K0cfONdf,XwGxԇo^cXNPg'sIjUjӇ.-%GK3? mBc,k+u([1d]W,+HdG^s~FZd.ְX.sDX!^Ҕ. lL.Q8:5 LKljϾEcEHLT|  kc'k)3 vExv\њD"Hqp_~ a;o7s| 3jNsw!b\Q^mvGpLhuF Ds0γ\,00 N<هjJ>BI~u#SQ.oPrff:tXE4^ *,u:̴_:NTMdTږWbab@W&T֎*=L/{ۧg7ƕĝՌ:V5ziRs35zZLh'+ Z3%*A9U}[>\캘L< n$Mb#TzY/aP#W|5:|8ZFVM/a4\? ^nH)r+ zFg-a%X٬Rc)! vr}4TΥ=z'.xԃ(9O_( P cIg \Ǖϥ=ѶnB4Yu 9%@0@0S MPct+a .T (h̵O荅VG[Pj$d:`a*l= SQ KgwЁش,M"YIHSo}k< n2]Ηe1=)sU OzkYf#hL@kI @ |*9*,vJaAeq͒ ^*l>C!Xu(׎hf59 P9J7(k~I͘RiY3gI ~wX9, 7mLS_e{;8a^ -m:˫ Wvn!i'%Do Riy Zŀ[w%2L:cZ|>=$bȭt\K|b袋ͦm_k3xh^m#Α b/9RŒ ѧ}?*nѦo|vTazoFRrP~uMMѠ2 eY7DAcanjt@3d 2EZЎ=9vW-GLe)yZ }Y좏"ޏ"-6$Q lo:hOƺx,\<uu=+C{?F5p'DivcȀts  vFy.܌t!kM ^7Dy n@J2/oPànqn9H&v{%It7_[UlZ׺0e\ |;0"hCrI#[\Xa}J'ISHsFy,C];[+Τ?Uwju #<'] ֞$ۃL,2UtK͉c 6kS5L8:O1ը+4&jisrYmƎw['V_e¸y[_b@bm)fg3bv`)eƀ̕hK8:aTrEx՟φ{$òMjS0POekϤ?c ,XCjju݋փs&zSI5U&BN dw©y>p%,ś٬/ņ$r Hk(N,띤m|Sف.NdxGu],;ZIVwۧ?/TR쳳RCרi(9gNl1L_tB{\N!㜬 :kbjFGюa/e>~Ȏ1S%OMyx9_a}c|OV 6rMCKYb畗2 >%i(,|`IТ(p_iJ %# rί U'_EKbeeY5|5I+uE3P}h:2$M!6t9~U~>=r ¼7[z N9S`iq\b3`vq ^j_8a&1(PiZh{B i=]/)ͱLUc22wj@`Q7C%z7[h'0Xι!\1KZnT{<佻-]sOHi*zaj L"uj@Gzѿ9yaLqUT|Aʷ^^Ow٩H(20X|)OL'W>0ZP gt- O-\y[ J&׃j ]y8 ~쁽J5`I,$e84;J [qnq+7Un/_EuSfSFQEC1"dNJ1Ockw E_|$87o/8ޯPIq4K0]W对Nenp(53wD]k5^QkL(YpR7H/"V|a mǟ'"-&gSb=9Cj%Ŵffl==fyv44p<Ȧ24SʯJ&`Dƀx`͢g} n {1-r> lad9 Ҧސ:Te${9@; m\A5^PhWLIo8HNC?êjvaw8/}B 4912f.RZ!Aƌ_ʫˬEqwY83Nof$9iACs Nχ{ !r*dNEG0@㩅 Ofȣف)?qLqj1)oy4נ dن{=E/[9'ڝ r{ްe3-騕5`^yDh'ĕm:c?Px05KSYMKpbӖi7Bռǝ>@P3G33R# !y)ç9 M-ʫ׉NXH>/G+/u-md/ :ײltfie_`/m,--izG?h#@rEYTZh-J?wB+ 9&{4rWe9V!tu<k z&Vf_]~fe|-| 8?"nFX-9b-2lm*.¾oX$+t#lGϷ:ʮ(XUjyq>8x8W;΃d0x2+i=̞U y7+ J",&(vRS'D[.eg )::Vs.5i]zmV@F^5~I|R&^ĚIh< bVNB"5No!Nqza+ܓpRInZ!C6~:`cn4grm2bW*9%F d(0,ebc'&v"#x4Fp(5 3Kb^U3ϽۯٞAG9`\λqԏw)զrEya}z(2e>eJi>$?ToIƍ+)aXLLc ݋*(߲mX$ ‚zSȯ~ZKiꖃQ 5$Pu=K,ʽc2#X9K\V^#YË˒Uwp& wZީ&u̓b[U_*Љ&?l-)tG"l@x&5?*T6ĺ|Rv6y8^JCp#+Տb~}Tԁ@d'w\-v0Q4bV1P*J܎:>+iE#{4_̾ `mcڧa<ʥKrFzd0VnsB3os`k 0jԧLAa~so"Ԡ1 .{ΨSHkcwԥwxIf.Qp:w:Iz)xDP 7V_bRoqPsrɷK=$\SpM5/:\(>P Ws9lwS^0f{omŏtbQ{ EՇ{m'i*":M_ /sc'Ǹ}~JlAp{32.mHeRC3mm'`+Koۘ`!H?th2'faG3v$G"E*} d]V{*bޓ$ttQ;*mܨu˪g?l֏ FϺzzN\? Y3c=)RF[\W yY\"3 { @%r1vesssܳI<p՛rL!h phB̆hc%*z\]5-9%/ }MڡP-6>0W=bsﺱӕa8^vey쉙}i=Ǽ ]@8uV O C'O//3U~Q&?t5ςō:cW29M(+y EN/Ͳ\).%syұFa~7,a0>O//;~VFF>jIË\ At#5|z 55ˈ =39<#>>>dI9ev&Gu%gŭ1la*˪BbUAnV_Oׯi û K (=LY X5byΓ-Iな|L>\j*n @O Vɧnvfq[Ktn=%o쏳/);vZSfD`Ue>ޤFqf7d&џi<{#jUAR&Dh_8TW/&atp1ׯW(QMkff p|X۶(yAOݟUQfUrT 3 Ly!yTB۲=Hlnېd&9﻽Bt:kNZFU|M>5z=Ԑ}2kLQ[io/WnPњ@䵿VdQEe;MnƲbnq  åA 'IL$_&x/1lЂ]bAU})bI/Cvl%"|{Om6 #b,-m6@Ȳ^T/Kx Zr`XQ֩_v#<5(KO³"iF7()  M!X`^gl=7a/keE|i>>&~*nّHGLw\$Siw&:]p&ch%V||]_.36nycJPP.@!`)wW>$ǫ YO&}mAxΐ$FNqg&-gj1ȯ%Ӡu" F+1ȱ Qjyb}˵V=#7p*ӠJMӖz&&2bWF" C| r{4Y6@"@L nRkLj]&Px2 +ft !<~L(d(}hCc -<!YuGbfy0O0+0+3ELS>ljV;]\<W7ns{z@Kaa ؑAqSH|\3= :hLUPШy}2ikbb{X+sI~$cd DFud{hTKD˼!iraԹFC\)Ԅ p\kd"" ?C!ȼ)kœ-r|cϢǥ0*jtDThIXkIE)ŅyHٗNvAĈ@_kl>gPtn ӽKbw⫘M7x}f^&:h; ߊaxYF-zr+jWW rLF-23aA[[ZǃT 猵nwAfTZ(jR.a- "x@+k5Qb*@8ʽ^2(TS))>Z2˾ia7t\n S';.'eIՋxyy؞XneLD5Ѿ=A1ǥ@@t8+y%شr}Ӟ:LQ.!X5#.178zPihj.=DI$A>^(ymc"íNSy6zb77>eip pIY"~vlnf)Jӏ0Ox  eu: Oq%>)c4qon<*ғ}tHtlY96l"Xy7t2fv9، Șv!> C @b2ji8]T Ҩd<Ĉ3[ϑRK!z MiXIu+тVn5Nfj}.n}h,]CǢ)]; M8%$=:DD0lZ0R.\ZJ[6K1Nwa`}"YHR9tz3&*JlY9m >D!h'/R`/}֘$>pRF:?z{'~ρ]֘Vr uvtRE@vJ^5s2bnI-vE̍tEw.$9DgY}E/M㣏 8}4ѹE+V"]4dP5=t26'u7836FR\ },f$ۑ3FGc툈y4=x`(61bv>ͽ%Mz^F"NdP?.$Nz7vJTt.j>"pqZ H,(a$RI忁wޥ#Nb1Joz{}CG-.l-/G 0VZoW~y6`Bԋ9L k,F (1t_ }lV#u]>! 6ӦitQy羳طch\h*kHu;ص6iot]˯U4Y`̜s(*hwu7` L@W<,v41[ Hz=ս7lO;`Ĉ**bSbގabף vYSn z>h+nuhzڹIUeVify#1TX6U0a{]CnX7/V*YV[)P ~T+p͖zpF3Y}|`}.Y&j鐈'yصf>q)ykܣUE=Su܇Z>?Rp|bɳB\T9HނLBe ś5e o]cҁ;Ag |Ar/(p눶O{PB/M𝣇!sopvR#啡 IM+XZ tٹaOg 7%0ȍ Q40׉3|a+Iw7K" f5#@BXǿ{>ZLHc{)'09dnbNy)őV򬈅)|[CORJћZcfK>voYzAN10_uoNw h$d =Rtu @80M)AKWYRhsvSH%#':GWr\G  caK㚌qR`33XwH&%@f^=E߹8?sŚ,%RY 1\)OlZ A;Kd<Bxp-'2o[`F-Zxhç\ $G\uLH\Nc#ϱ끹.9 KOFz2=YY1<ʦHtbr{\h b'T)ۘ#DzY5 c9qY@5PW}hqZH]i"»Hoɳ4=A-xj)`Sk>AGQoD1!d̫[ّ  &w0޷b"*$=65R1 Xsm#9SV:T86,b5 @'۶~gɵ'yڝ\!xsl:]7S%K^t#mS`L%3NccN@bM,B{1S>C26ZBi›ƈI-cX?/:&`@M7=+=ꣾed*|H]~Q>W`wWQ`K b/Q8@6佬<܁3( J\>9;Lƽ[h|Z4{h5٭ū}Fޡ[WZnjJ&\ıB\ 'uLg RU:szed0ɩ#phRWE%V屖.dDU|@ìf-W?W-HviY烱 S}?N<#o嵕R-_>!DxLf;~I']OgTWt[S4q!uSxhz|Znyz 0C3NN[@{hύjUn)}ֵgy? vd5S퉏8CSLKkmԝ;WP(\ Qo+OcH-+跄yԲ_ lrhʱI[_Z:WEYAe6HBNEm؉NJzm0Mƺd EB^CD\mnfRxܖ /dk$0(Մ.S2A)@V (415 ++ˌaH}ЫQk u nudۍזSYpK4:4:ť)8 }kZ>zA']mظp]o~xC `&nHо&=]s3P7BHHF?6'ȇ 1X\p&n7iB϶]lߐ9ɽFwd0n|G}5FXIֺ-eHμfBG Vv@'|+y(cѡZ LMs U2J#4iǮQ#ra"Q)tI[9R^HEѷP vB3^k7vS{tf4T=豌ӋΟ%W*9Uav46zpf~~M]6ِ5M1,_kIi: u燒3t'o>Rl GU!w:!:F&$/,0HZ@nmvo? k?VVȫQ$5vp園0 o!H/Fvֽ^1~l# *Ɯx9 9;O^ů@ ^OnǝS3kUD1];tpQ_2 Lߒ+bHl 2D@p2c 5Tlx| `LDZDMGR=|K`?М[ʽo҆UiA5zi4SK jKD4ѳQY/`Ig97]}3IC{yZc9o;h .cmXY#0cvCQU\ݞ7V\wT!Fy <;29PN<0Ip %}92n͵Wds&*ri ҧLQoK/!*LεE}$H^U$a$!!*O`p <2?GS+B+j.mpى>FjAZM~Mo Q1*]Oc[b<} iC|m}$0ϾU!B8#.iyD#8Kۜ)'O3Bœ t |,$wK /PĖ5[/KЕӏL<֯xW[R->` ̉g\4>+ʃ DM hсۦmPk$x(w,b!6ub@)&)C٠\hHi5`aw`\teRg~ @ -e?mƁsխ 1ksl{#7o QRiuK߰wq*m^Mo>|"EW'P\'p0;83>H@>{ʞ3:'ӼDKQRqԂ.9Ldfa0XPj@]Ld z {Z'cS,uqhD5@!!񚭅գvM8'BUb MaI|c?'ܜ=NHVE໒OZǬX fÒ3^7lzKiYdMh>h5ҳk֘[sƩsh~#S뷖fD}Z&!W9:{"e75Hh£W!U`CUm jkXN 3*.|5W$̹oBTEBf?USppͰBn@&sJU +5wA!bHCGS==5Oț=kϢ haY%;A{T6U׈ť)iwMrZA|%~{q8?߸ZǏq.GJMQ ®EXj p#;+r?f$%f}l>4l%Q 1a `:d&My6Y5 nZ @ aحΔk6p^?& τӎz4~_yt5oU8. Xn.Z2f"6(Θݪs 84q(b%R],;De51[Db˵Ϋ$Ia˜'zŞE3o'l hW-p~HgvNERV)ߔ7mCV }tӱI z7FI8{gS..7F]| nǎ '{XKq=7Q֨TO>ym]šOR䩯 8kR[BV9X^ůu7.sNjrVI g]nI߈O;!`>ZiL)P:AM@nlFX|ō7~MbCU "`ڤo[Q_Hl^=(3+Lxna/LQg "rAB85R'{hG|e6{awͣȰisĦ_% Kӛ!>aZ |j3M+!*Wg}@n]uiyZJTe'$ -ENé@h$j*p9UpVB-+ned(3<(N5洫W3a'uXf@fK7 Kufe5 5 풆VOjsXWNɴ_L>^McnwV#zN%ڏz)!%7![k&gsẒ;rDK>Yj ,dJMou+#ŴdlGw6! |}Kʬ׌}DU[ )-5>G zG2_W q z{H3f&ӟ`>zwE?\v2)Ә> ΩW6$TFЏR' h0˽ mrpe WC_V{#f/.U_ƪbp* q♕\ug QRIfnd{&W7ʳ܄pO eP@@^t*F?Њ%_+֤'Q)O:Qٽz!I-(#GLQqiY7hiJ T6||` cx}zjl6R͒EUEͯl4,%x( @dEQJܾʄy22C3`֋pJR}X'l{c|>ܰN*#WtGE AQmNoZ8ynx,foc}pݽ8!QRijVkRm&(ES p8W9io 2&>{9ߙǠ1ڟ EEn!p-G\1)CE%\$R=O>t`nOZu&?@^+ Ds =_N*΃\; ]~P?j1SC6laZ"๩^r546iaIO{F N$^[8T6!̵YxV 2Fʖ )@.).hK$$OB/Q0d(\:>AOnzP FIߐF?“/C}i+,(,C<[ZXCfPWAaj2,&%q(y byY恮 8F9`+]gj{S@lDU-A$]m {Yf "Њ5NT5}瞐e د!:kiB @Ǖc%z=xmPj}ԌD{2Qy.hCnjXstѴ umTdr93R!,#X׿&ȎGGlGxAs*ӵ ϲ> iaGa%c;HTr1W\JsVʶ(\`^\WNZ%YP*eEiQ_.GmC\zwqsCԘ >LѭmKtx}nT5P4OG͂22HA.8gaA!ҐkGlz1{9A + p&]*g#ОrC[YݍLQmA{dG|vmO0_x}gZ>cqɳK\١zb bK m2XT2>{ϓ9 8-<Xv/33 h55f8ϟn 򟈨윪u*u#Ey3DeɈ( JF@ϟDnAF$8դ~$(L91|.Ҽ%bX&/}a;ٱHmC,gDzƦa/|Q#yi{6Ky`drJAKcua|6v#=*^Q0dK'WsǪ|佲fԷd-h2%s_N' K!2pܑuLRk!O'%@wVBspά$XMso 3 Qϟׄj!.lmX=d.>C"ՄZ\L5pK,6&T!3VBh`QI 'a.N9eS >8""@z1 Fo6`VKs"Mş7t%@ҴHK2 U0LN(kYwf#'c'U =L/dMũl.vǫDyhUĥoV_WVv!)$VIT^4DWT]+i Rı\zW|aw%cV98ل7C `45k>y>EeZ 'e 5 Ư@r[~`{D농 0MFcO3p€(S#L=5*Mu!2`aC׵DlU8#ukR`!%Ⱦ*;TfV i9 }P؋J3͜ÒᏨwbǚ/'YݜV*k54쩣<3?bh$OB}0"jSc RbsQЧxS|p#јfΩGE-eo?ť3lƁu*yjr~c=: c/PKEUWjss ,\K/6mۇ]4 >]c_aJ=>X*/*u}s Jk }}^/PAZWK3{h,>8iT#bGE֡1N¡aHXHȯ I^wbF%| #>H1"jzn{sd.=0^Q #$}ΪUSo5-Me|nD!S)KN ى[8(n1c}+uɫq)n٣/'g X,ǐE2ؒMXySW (1Wh1ԧQPFڙUTM}89Y{$vOɣ޷"&?xqOU.(X\pRO8@?R>`pםD~$isEl4a4,/{~ C D'*DmM1n̖V-]+"B'xP.얙'89 +(>0E.N[E5 wʵalO~]M{Vڹ>#2-OL0;bH~\:3YkO6.r}k,jYx` R8 91D`( ]T`F 7n.9 VRپALvsoAA$ݟ\t0U-a]> gW&b/++PPh9!MT{I{!gMfSSnQ! RTrU&$¦G1K0oLދs7c/وgsO3J(Cs0?֓Rp9(lA_ ЧIS-ωnfuO;6?)r-e5K$teD?0nspP2FQpvRiYHFRfOюÏ$&t,S˜`thmF* FQ\fC(lﻣƔRrt񞗨0$X즂tbL;h` ]եRc8'Y?vX';{݈\z(AD=%{5: xԫ,AazRInoypkp6_Wܯ sF׈J,ZEuf/o%溾4 gTx9WGKjDlJ@T\7};1F~/;Z&'k4wgYNyIa@f?=m)B93I\@U,-m䃘̲'*^\hV}vpKq ko|V#п?NgUsad%P,Аikߊ (hgn6L$@PW3] QC6d CHp~A$  5]C:jcJ+:1]7"1[AFg"M{VB6 gןɣ21A6EDrFɑL:GK3҇D%]c V0=Mh{ӠjcYי`EϟxXwc|y74|mVuch H`?j wΞ$Y0~9# 7oPb8VHavQ̢u5CDA@^@ٲyV7kcVL I1f VO 0֕]98KE҉ RP2fq%*&5(_ϓv▗1'PХH8N.+FM#PB$4 ~3jndH~!. 16BbZE0~z🅆N2ɺ >z؊ydklo煢" ѵܬ\.Q4z|l浵iIpyx(y+l] JsBMBcDXgȏ YzG2MM-Ĉ4&0yz6X3z6.}4q3%ZSD:6BG<')o :D1No ]S7c4=CPz>)oc 8Tё( "pu&OЭ(޻ xx-]AWvE/èUg#BTϸ}d%:=aH|::kE(#E;g ;RlpDŀ,b\R>[! 9J1^``"Ry53;W< /JI~rWEbؖ@<=Z0N#@Ce )pG9( .tڋ`ɷpf}lv0Bۺƍ{DʉeR0dpN jHN/z_;&Gp4( /!# Ot@{ _ιeI'~3!dq=@ii ɖf2kOżco4廜k"lvD#8;"cLi/U]u\qP5;ĝ8 =0D'\`:(_D7^ S),s.h&ew$m82m>2adb*#n^SR$HM Tc,MKfUzg,!Si*)Zi܃kf *+DW4r9oٓO&ѻoL8vU 9:~;-i{e(OV8G+amBB[9&Q rAum@o+[cP9|Bx1XQ5-H1Sb뱧fv{()a!JVFb~]-3s;Nq1)cFC%(gLaC2-_-ԞH?d$M:0 f W(]?%R41㳿 qi' u2a.qi #j~S8& wH> dDZhlh1 P+2 JqM5k!{uV{R#zcn1vY؉x9`5WqF0=QA}-9pq:Z1 =VV2C;)"Ai2c Oӈ[pw;D-tT4RU?0H d:eڴ8iaFje&Z~с9sc_ۅ)(\#o-Q%*; \:.7Zj"șDpݵ氊B1Z!ԛ9q0رtCdEXz .BǴKg#Cjrzb+SHC L5%cڣ_ e(_iO]l(k"A^ ?ҡ|tjZO`ȯC鹤";R_U;u__xXICdp6 }ϟ,Uw (U)lӷzLا*l֢Au]v)*zGfw  =aw ):ҤWU7Q$<"!N90?Mvz ]L {$#BfHrևi9{1d4 pa`v HȦx!h (9܈&dz4OywK R&Ҝn3xDߣF|p^*.MZZ=dZ"iyTn#(F/|/ДOieUUP!f5LtnNy^sՠl=Qa;F€rݕlZgBiS,~AUMT= To{ݻ9{e x:{fy ~Gɪ|M{<Х1BItdaZ E`HUj2HKVܴi}eHĈ` 4dnP(\&Έu"o({p\4瑳0bGf¼Ӓ.Dzd"*!}{€NXdN٩֚y",gRVՠ9?X>  Ė\3Om{ |BR/Q=5<1Zh%# 6WnN]HIgoHn^ tTkiVǏҩ-c: :#3< l e<>V4P/pruw/9ȽXiێ G|*OcDz0zRkнqP*>4y`o:ϡ|uAPbqRHٰ1s_6Zv#b8lyS Ќѫ̾s(MN FoGqfrda9WКu_aO3CR-Q2Ԁ,%UZ*vLeS$\pGXTUtH=H|Q}`~9r@z~z$O8E+ɹ ׻`χEV-Ȍ_PxTm%h 2Yɷ`ZI.k~T#Yxۧt%clJ4bu&T6$N1Iȅy*.L%_ x4ZJ qt#SvcN͞ey.+ĺmwJ]ἔ|'QD\O=-#|R;LQhuo祭rFxnB)P9vZW)G3 džk]M= &FFu)j "xuB:;$h!B'G'Eqsy^|!tĿppژy?T9⃮5~kn%/$h:YƇcj+!]?Q;np zM: a^ i!NjC}?`<.'+s{Jj~;k?= 6(z B`L]x? IK^n|+ȇ b{:3Mݨdheh(Gzkb-Vnvg@`{wqھ`}HC<&{D^S{9I-R"\h7ߤxB㶅x7i&]n1ig/#s/j+c@@ˢ]`ԅëClF_v%T<^̚ExN b6H;u^#4D  umnba r]氮SqPo\R9 wyQ3K/xĐO71Vxw(HQfDw hFâ;g:tjK(˳=WN$q (vt4$m nâ}^ƅx`hpFJ%?oڵy[q;V5> fcnsC\y^cZ-0P;5GZf 1#^ Lg)3I[L Y7;9v٤;iȉӹZܣ4sWDG_ .EPM^.eq{]wB Ǽq3@~gO P.9)1KQ`krF@+]-C2rR*(V\b^va4lf#󕪈?KѰYZn/JM+:Q!TLX;v TQv)bˌx%VPRȺ aoa ~AtIP|7L eNZYc^[T$:0e]p0ec-'Um&fs?L65sil(t?מ#n ZLCk3/>,0,DF0 fZ,DGEZ۞yp*G%ȎPԵQ |{!܊WiZ^'1(Z.fDY+g?>&UcLG~=$`r1K'41mfv'q5z?hAN^608立 /wp>@u0LY󑣶Oo y YQ"N@H:/" B{ `3m"$=~+QP*Hf38NI{I{ a,c2ӥ<q7m8 ^y.{C^- ]-M/~-.kD\D.R{7?:մYʑĿ=+erQ @yGBA^xlwD9eh>dU'EXeMޙ,i//u6F*5cbo##l(%Q+3-Vy1W@le!d]6lZ;PP\z?GYXt#LbPrPLhח'(\)g4x!TΣkmIM%c[&ӻ7Aç`Ϊ@=먅I4sSdRJJ+?XSCκvp"69Jc:wK_Kn&HYqjNauPKWoMF`^XHuOB p0-$EOD9CkHׅT<"pdGe.:A4'g?Lbȝf"ClǴIt.}IWަSNEN5) ?ׂrew!Kq,ª͇TM *KTXeݨ 9@Dk* mZ_IdX½(c1qKI߯bAV^ mi'm޾а|Fq4`T`"p>j=Yp37J->-TT54X*TʨpZo/KZ;c>}d񶀅zzU/ؐVsH`wDj%oGOZqZnO|"'=z?񵥶4"\5φNrc cD9 \*oJO+ z3"tE^;|-0I-Z623c^%:w1BFͦS)dnyJ=sˊeGc(XѕJd OA%͚la˦PqmCHYZK]!ֈ>c| \3ֳ6Jfr 2=O^>WP!r/z/Ѳ@);w~#\rSKK#~twtQ$Q [5It*6LB=q',\F>+OҧJvKO~z?!JQԢpv }Qy~B?p˕ ZF# 'o/VÅEa ]^ye+]Lk/dh*1>o;գRRݱ)i 7=>܁ v7Uvs.=p Vm֥'e-fvt h"p ~=2^Y bΙik^0ΌDv|t-szʪI6: ;YV%e4r V;!l@/~$Z}K |&4QI2N3苔%q(9c,E}x!Z&q&D$R{ -}XLwƶ1J2MXGU#@?&5Rs Oh~F)3d / '(At/z$5>k޷o9z%LVQ^˅'MXrv< q:?S2ҁ\$ЀY=@V4Ͼhٝ;܋M=x}o S d_|q1-Ip @GׅbK^FKM>RŘ-H`3,WDǠ[j[UwZ䖛hZwݷ:8tв'HlڞreF{#`oTK2'VG6ȨBsLB|" e;^`T0UY{EY/hizSZFTo^ptƤ|~ v.AnjT߂NR#D "q𿐛>ox*1)rfY}m51a9B K\9+2*Q|XЉa)8\aqxBL 9#Lnb\ *ێe:} cț[ 0Z#Y*V5AjN8R@W%7R{)Czj'j`c . 짾׸ϙ<v8:ˆɄZ}zbV{DyMQUoFKb8C0h`2`;h^Ga Q $E^`LM 9O9Fb z&A}mE9@qReNn-I/>Bq;xf$e5]M[HbBIoF7 ӹ󾖫8j&APFLX /_k^ L%8j9lz$5[/Ζq,4l90@sI_ZRi[VAZ<}~qYu79:8 ލۤa (u* DDr*b:,WQ')n5zkqᣪ)}yR:?Fu҈}^8a(%- +]Ik.J>@_Dz+DFEXc l.,FǪ_DbMh8rp=.^@$-tJuXWseD-\LySf&LњVCJtMQa6o\No"u1r./H+\N]G u%O]B\KTn|{4-\ÌŸ[3t#4riw?_S~%zRNWtӥ" [Pfqlt8QMp4d c1d˹4O]MWJ*|[VFt(z5Osao_mu2$ɔPwT4DZD}^eèχC]2XxM"'W*k}~ f~ǭ$L}K_-n ]୙ڜ3&qzJxRU m%pUd8OQ:dS JvY~iZ*6Luem'*4@֖'<7Uً.vOV:3F9r;\ .ޙ\5o.ey qхjj Pìopo5")aʱR̀6֩[է)M߯9e0 a n #g0S3aj6ƴ4h+}RƄI \2GE1|1kqc)m|^Ɂ_b6 \94@AGPۺg*3-ΗXNWr#Mrn>/|X1|ݽ@  bG;˯"dv>lr\ 54.JT$.ЉԒVj_U߱?5e}ŭ/vaTfվvLk\j/n01?N;a\{o)o^vhRMHP '̎ RRwW>]|a+@%OT.+z5#mq22~u3o.XU}S[EJSV*!RYُ7k2?aJn.QFx&t8¼v֎%uQ&FG5`(S-jv]Jڄ\jU~ʢ%n!F~'Q{@,.KKp@>gEnpd20B=c!2쩱?aPu˰LX+ ?!DX Q\H X/X֜T(p/H=6pGm*PGxboHj%*v7p喀2,練\@q~AsTSCq'͍KkcY 5.] ī%wY4hg7Փae #{MrL.#z^:[H |F6;5a8NUSS ` ܕ:bTpy2`~A>Ī@c#ץ;9`c"-L`ginzFR(Js +;䎵 | W=~UaμX@O"?i{l񡑢Lq'Mm>#zuDXn{ |ȯn*\tni71#SPJU8P6aL&Fπ*^F6n5Peޞ>Y)F%-dp~}ZI v!ft [NR2&KPL%](r&E*L~HYM^0I1aPz2΄+qlg}7}d#dէ%L޿t _ɖC.l˫nM%ѯS1R908<#2maU ccҒm]JX,g~BoOL8ļR^+ȣX6GK^x{+JVgE'kBg-rqfRǡF<F/VM%{D]|B9PR(q.^K SxXV> rwײxc`{s)-٭X/̟Yu+ŀl\A`n{wDf\,z?MbװPx8GЀDE_J8A %_Kؒqw$hir8@ ɻ4&,S@wrJY%. oj:lnRDU9] [ob,7O9u\Ae$**cʞom6.۶`#yڄxqs$,_3qH{|ZY|= 1]_h*>Oh5 B 85_agZ9_*@۬(ULP-c_c 9+YӧLt2ךb_G#cT [G PK& +md+ bJ?F!"]`-HɁ(lo$.vuJR9NP\jf+_zQ!7",4,LW\廮?>4׾?dSo"%̡FkK77OCÇ|f T&,*8wR)lFtrNM+H3Ex˲0^B#rwEu1?ace Kz!҇=o!xV AM*c!:'"Wp)n^5Ҿ fe:]#6-Y,K3B-ZR%zoTZ,a|%L%MMYS*6"6׵7TDi $4!sJEp[bQt&'L>0廍„3EDžG³-Lm(tn(1Swq;}U$!BANDǠfȶ] _N8qWg!z($b'дi/5^&@-ȾeH)ՠB&ZA~^f mYbX/wدk/^"Pbb`%J*}cނQ=ٴ~wk{+d#i4ЂcJ{uN 8KQ(O6Є` Aa3̅D`0vz?#6UWGw X0IΗy[9\ #`yT" cQ&W˯O|F4 %+G(]i׎*(A?B7Tg~.&Nj<~*P@l<#2- 8P!#^Vij].dg] I _\ܩ%"~% 3T$P`V9HS e 0ݗR-ԸKK+Sbe]αt1AmnjݪanҤd!,"Y\(Lxz6I - חKx船(7T2 [R.j=K#kDB=Zk hbnsZ[LIԈ/Y4BuВ`&q,_.HN3_ g,_1$%(/Qɳ%dX FEP~Et2ĜWu0܅}ZΎfg3fTm֪<gtsGG%x~qmj'wX#yO]ܹ3Yp|Mg!cvVDcK&,y$ 'Β܄|.Ay)&kpa#$V:^sծqGvCũZN؟;t >IȗZ[䍂&@xջQgRN&0V\=u-λڠD|r8 dxCb;{`\[|Cb@ *{cHwrԙ:g!v,3U _%uP{U0( y6%uF0rgqd$IP/= h$ 6N~!:'AO*0 mpqy 9͍V^܌ׇ K&Jjty\j4 u.@<Ja{DTEݛ(G{je3_5mH7}؊mXG-!`abkƷ͘zꦲpI]Eۦ-P 7l)-%I#TCӜb1Qya*RrM%=N'Ld;e=LY-ۧ`qHT]m9sGDm|߲(Ajk^NA\<+%LY 3_ +$1 '1 UJo CJ~;=Rdc޿(\0pY ט8^:vB7ɡOq] nyFx溼_z9W{^uY C9[bsoI2ťn[fa}^xS.j@ktCܽb UnAcA?ԣXstm?VsJ́b ;8p%Ҕ?ZiEzfyx65 kQXQ^j%&<*I]Ct쁠4_]b?RM9-E5ؗrm=z1p7q᎟׵l !s?$sv$T8HsF+Dʞ5_?lJq;Gi$hmjHb:Ro0fe$Bthх>&>9ĭ vk@/Q,ג,|g] }$lA3&%`ɏ3'3| }+ XX*c7ċ{"7_G),s[mͱt6jT.omΈiEe] Q.Y6ЋA OIbpĝQgWl -ep/wHrB)^,je9M Z1UW>kGĸvPG1c&Wܹ|e: JxVEӹz W J"PQV)XyXS'ac2r"sur[Oڞ 01`HSZ`kA6N+)WuAj-VivxR{J'09(?nK׏|x#gi{(Hâ(u _^^>2Ąrir9;dC5N>T'(xo0] /9#7I99Iq!'fnDV[m+D42-esԞۓ§? 1-%+WR'DDS!|0g}9l{F,}IM/t"*ϒJC"@3 !,z!(h+L%l=0whҩ>џ燜pp La|F,IgEɚUv#=πB7^ĒH,x:~4W#aL!77MBoy!uθH]/qɬw.~  1+#=@Ȃ_.T0[ mϫtsp(?2[Rοi C0#Yثg (,~ 8ߧr; c5dfy8x SzjoNtX Ⱥ B_9wSU~$~ 囖O1!wPѪ@3BJ4;W"\һNvp@z,HZ8%4O] OC0& s~8yI3Y‚;D+J1!NDì=\]!ڌPGF TavR 60t'ƕ$z%(4_թfpx$cܛn)k3N FA6܋DjvH .԰6WU6t8 -u:˯USA-a`TJYUv/{VAA#uv eԌԦQ#(<`.*iD>Đm0Os ѬJ8vͳYCC1IIUL`{nsC6<|Q;.! 131W Sl;( /M]WWzkYB‚ nN32Xu>Օ08}2^( [YwU{*uXt(jY+Zq mb]kS(a䥹σ>`v ~̌TC78cr.Dko 3)o5a(\cB‹g pKK]3KL>XbjC;|M/l$oB, L4ŸJL<:Ḽ) ;l0k|z-#ZCBSj ,<Gr=$,IήN{Zh5ba_ե8߄.Se tт)(S>}JY&l jUTc1RZi)WRط&P,B+ =6U Z5I'- fJΏAU*7ify5q?Z1$D$07usGub8zxPz93ǚcaoD%ԣl.U`싊YqqV[=tڤftGۡGwWF%ܮdX?I!5Y>E>SHJg1pjʹ7=';'yUkg,|RJK3ݎe>:(֢0Po^q5L'Xm1V n0q6dw5AUx fFLE,:{vFCY^үq^Ck/ި29e\q "vd$Vغ'ɂJ+EL3c]z!)Ց (bu!|@kxzirDה ZAՉQ H p瑧{ωͽp4:_1BR gߤ@[ג'bX=CKmBYى%˭-qѐ/ҸZnN `{ycRdgJGG$6nPUT@*LD"nî] SufTR=wx*ɝtPS"Qqi@|\ ނ ijk5o@iy>5 ` t \Bv<rRUloQg֧MY Ϛ|F}9{4G5#Bd|>˻lK^>fL1lNOA=^i.w1=W0У!<:"0f˕rŸRer4ϋ5>](}\a447@mOjژ'⏩}W.ҾjG2Q4d~ kQБŊy7a9ܵxuž"/#L5i>75a0uird^,ND 3L9F䋷F\1|/R>x|Ġ7vs54t-F^g1{Qm\&瘚)u,-I8x mC$6ge 24M8q_#"Ǜp|x˜Ol/%UY<* e}CnBCZEfjX'vҡZ%*BӢyz"9{͒6V˽sCdg:MU$ ji~3y7c@+ 9^.^BK>&UI2 o* nxR.:[x+`SAM!# t'Vv\o]=*}~]Z8{DՆRTzeSbZ/T$'}\&ܾ^(28O_AԮOngJp}4?rCݮi )/RkCak' KNG5EEԿWIΆ-:&|˔adw\oyJ i8rb-'n33:x2'Ɏi|t(Uz[ޔЌǻt(.(ySmW*jwgXƠ`UrȔ+^-1\!|P!k~}ph)Km`/sBW߫ace/3Y>%Qh: 8{<S:Pv2Xv`==D p;ml* 4+}'uytTgeFTZa^2Ϸg<2 4 mTR>AWApQ"S$יe,_<<@ePQ&CCϙ>|^5lbJ)` o34|_$Y8]yxY#( ܈u\P%<$s7dV? yk^G@v!yP;I/i-zaAg1{sr=-t/f~{mk_j_.hNZv+n.=r fKfPґ[8F"[pE&eP bK#⩺]L0Bhjebm@УF(U({%UJ5SW}b 䀧9#/lQR!ZvACM\CG~ČSfG8p:엗5HEd vQ햿?B#!Qx$$Sށ}걋 S+eɐ*.=r/RY鮍N͸Po% JßryghλGMʮ'ϊ}}EP=aӰ2TvajcGz-vrfEp6٪Ԇ~E`G(7W:~FW%ɵs}éf,d_KKG+r^2H+Z:DVW>Is!r Y*FMzUJڋ +nl_B{wʖg$O Bj!wݽdy";BI:x7: J wjZ[SC T=ɡ>bF4纞0`TQ⭘BD';@Lqv ^Pl.Ȍ]5p]W$w6 (}A͙ǁm3d\/[);ωJAV:')0ڲt0|oxDLF\hJw1Z^S;QBXnGG5T|kˍЃ.t 9W&O/)e$ QQ1 NI*ɣuFa9.,|}!FFs`P} ŵC?7|ڻ21tFE &Uߑy%R -!fGHS#0"km*}k0ZO!3<Mqg|k-|f`W%K.n5u9l9zer-0'pѝ&4ۊ%svBKB4b9V.޼hÙZ|y&UsrKLy3 ^Fds}mBV͌|\" ËwGa` B/8Zs,bh!c-9NlnvPk&':&VtM fV%SӦ1S'>eFh9j`:ͶjԣCozHhSȓB[ds(jSJ-C ; *h_.O(\Khz+WߠW9Cg߄ _]j_}` S23NѢM4G/4aX -&EvwVڿO$ff p/|)^2ouRmGA6!fWO1H;!a;|tީJf+ܥ>sf3ɼ : %"PX_4TX7'Q.5z gXT>EwQַ!DzQ\C(TLR ܜna}}PKG(1Ie n9=fE1+In~$i` :FXIDƽZG\2 :'+1! arNy_/>ѧJwL_Eʜ m8CobDH%`@Ntr{Nk_~q C _S BYWgs" %O 9_QVȦCp)ۛGp‰CJ#'ٶ:Q*YWK7<~EB(I.d̓YC3fh 壠qdfTXt]%gD"pF@U=I&J3'7ꙝfE-ox ӗ 'RHKCBc|1[sUҍD? (e:PUV׽e fq\A'B IӟݒW[q w{겵1 فV_cIN꬏EU #bW=x%qf1"jB 1ۄڼN,/G $SO>PK \C ӀLۭk*a_ޥ`1nGFHNk] *ڥbBIM?4f\&~W/Aq,Eo ֹ>Pn'a frl}IJ}^޻>3y@16/ve߁g!U<_jP:fV8-@Jk\C~5V1=2z%_8h+XL,K0𺰕" %Eo',u?"Oh0 74 tF-JᅕT TuL{CzJ`B*iQun\v<~HI ᕖD1p=hI:|^S/4Vr2$[YAkfCT bDPliȠT,qYag}Go4Ϳ|ߓeECcxwar;\>kGډs "هfv>.;xC7 ^H5UD\ 1NN1# &o.O;fXC4"mCÏz4!5=TWA^G:Ap ],:9GvfB)nwMDiPIĎg*Jj@P7ݱ~FV*Ƒܶ"]s`ze,ǂI4QҌЈ 1)[WƗ=4<ڠR*/E+[20ۮDz86-rW}^nɰqXXLZZWaOh$+[w4!R9^Co+uP؛L"F)`4X|2zhey/>K=NN)ًZlX1Sf$P}e:CêVP;A߇, OYz;mlMVVKW $ ~ȍ#AfB4G'QဌfmPoo.u%R/h-֐c ,weLXߪO{6?pBBOv (Ymˤx scw8 t)C XR)cJC@0[č*b@V]%-8" Nǰ_+Yw W͵80[,l" =Pl,9;=%>3S0N;ڦFJ#:yS.CXz;X$)w;T画1}@ֲ +h#8[159XțhĂ Cwla?U(KN h?|^YVC}zV!OW{[8U[}Ա[fpAQRֶy]jh ]ԭA:@HqLt{,f++k'C`rV^"_yvF5-|Ue.N:HNg״RsWBmTyF5 i93NB|Y*(aysSkVBXzAO8^Z:L:MtKH{Bs&g^tlޒ,wBA?'x+Nx<6p.(d]UJB 8β? .+ӻSIb0+#vH$16僶3?IۏDASݪ(m WmƟj|$xcXEe@&zkK?vj&ӬL;Kepo؄YxQlʛ`aԐ<9u oc|K)KkoW #m<={O0x*&v9Kt)F9XCKD{ IiQ2EVt$wQټtsh`aʠQ\VvVX`ݹ[8{&3?D,vcgtbp& Y#^)|T= 2248y˖)M2C pq:_N˦eiל?w[.; I6/&n u)?aH`g9c{Z^|Z;xD=T DF\Kxk亾\$CNG?e|3!4p)/\zy*!#L {`ؘ`s`*C&KS;[E_حCJXϓ5Å/7%z̶Tdj /FVu}Hh-y A,Jsh-+ˡ|D`Βn~'#55?3,zUgfM`OF~R똹KpK7G"?n|C|?]ل@a5 68ц/f ם9iH;fIkճZYيGð|փ)y'cU<[LyWhǣŇRnsb3 !'pށU/Ź٦n5Or$BĤ&= ͮh{I{ie]#G̮,O5z (DOL {m&]fE:~v^ZYz15'q? QZJz-cݨCjyٖsw2Sp)"/Bz,S2(g(kIs>Ȥx;Ë4Ʌn1QaZؿ='6/d x@O$5jZ1¿mihH);''$)0S8+KSjIhD#ZKfi0lF9Fbat2s6<$[o KC-pw9٨Eоa2.4"fzwB@Aae]Һ}(h{4`U2n[ӑ]_cQXO.,Ba:j 'vO3{Z7w]l.mET^ۨ^ @ #pNGʡ:E(8k_h~6vh6 ]|qB<ŻlQi-dUSv|M|?;`.\rsd^Pf" fs`4Ij?S0U&@:TsކT\?=O51"k, \,&#^"ߊپ6 T6nw ̖@`AyjzE$˄#f|+NM)fH2_VqjEeb.@GNK9s`"lΑ&E@i"iD#X_!Jކ_:XXuz$|Ź4jxxjh0J7? 3/RUBjN)ýž 77d%AgpS`2YBFz2}HVjſ}hccdĵ-~DmU˜4y<g0G`/bMN g/>}{6dAegWl E[oN@|OmoF=͕aÍ>_g9z=%| 0ew83a2Q@Sؗ75^ی2efw(sKl6Rgp{Sʁ8ѩ=ϼri CAyi| ~~^wSڵv?+4sA;.F?x˞A37KMMj=hyI&Zl^gK8zTb9e"/7Mcې#mcnLomI%܇)(jG#Teƣ4Npz^ M3 jPp7vŔJɵ;:63 0eDl]!o`bޟ g^;-HNs{%_(#m.Axu󮣨ke6. =Ș (cKߑƭЩ[hL;҃TNi8e(MpTtDnڀVk( .@Gyd<5ȉA XG)#a檎,8V)GyBixk{h" ,SI O54cI}!15t+Jj#F$,t80ԏh$"5v nfƟ;y s|fdJ8?wՔB@x,gK<Y$Hxɿ&ϒ 0.T流M~4pA"$0N HVk ,?:> T Dj[mYS$S #)C@&`u oLeH͗RD#Q'9L4L4 <%)DN#/=y]ZjSFODL=kŊzNbRNL0Ng,p-HE`J3;Ր5{p(Jl督czTY!PeW z脙Z3U/ȴͨc{nVN9gHX⁏4'&B-n1}/V ! Fhe&!`;=wl]@MD+<m9&ℸ OMQ}9'!gd.V,84⑭f`(/:jh˜̙UKi7+Uk ݲ5Snu~}vjq@TXȊ5ͽwwQ v8)RUy;90{0*p ݿn[b^-u:,JbaH+0_oK/WYs;1&z$]=h9Xѣ?=P9gn@b_Z} JQ:uk'.e"@%K0޵?m:p1Vft{~mAt:DhLwpw`è-D% /M]/UkN* }Wv i "u^Q=y3RI=j0Zue.M4L7ṹ7ׁzb==xnK.Y/UtP)oMZ}]N8)Nw^sUIJ:1"~I:KN_,2i17Jrxenp`vT 3!48~M?͝Ŏ8|UsYX})tN”XO1Yq%I)17aWa`MN4edK r I|j7JxrGYCe蝒2:iv5 |O*wu*u~xߧ[j7U 9YsDP`m4Xڋpdi|4V5#>y*Oq\AI F=^|xx@yNY~1R:?kl,}wFn<2:|g,5 ScuP)m񫂗 e;ƜtbͮW59 \wm( F{yV4H,M .r"R f^_w }p#0洬1}$"Z Ѿm\SeH_nh{R=kבkJqb"JJNV&<&bѷa -/SffbN-J=u/Vn<}ŽqY r@tn_<)LWڶ9Qf6ZbxO=7amPn.jc{4r=eBT4R) rEav<̻‰ ˟'[ץ Z>QK_ ݈ PtBzI+&1Ę1'ۼUGC} 6vsO0 aL(>-~(һ*鸼=! 'Jցm&bP*Vל~Z+?-w垶CB"HiVo>xyLl**:Yc&)rdcenaJM^,p,HלO82,{̊o'RPj ̛=k7shΑPiAMŴχCC~Q^s!Zsn Vpb*R=K L 3} G{Ӵ咤΁s z6 I@&ﳫ,(zX;nU!bMeTC1n#bYgvw+'g\,o{ji'--:w Avwph$MF=4b1֖w13\tJV6=S#4)<(l3Um\.ٱ@a-ƗgN~Ԫmx}xRFwm}*pP[mmFgal G4H>?KO$C.S1d&#U^P`n[S ӇX˜5͞'n4R|UErXa[#Ŷo J"xr<3u$ǒ^l{!٧p@ZNR MR{'[ _ ʧ S\7ֆy- #aLoB]8Q\IvCYUI:w[Iʨ}MY σ"n8WءdpOT٨$)bPuDmHjGt w8]b,q"lLcgN~v XO%*،7"̅f9/@n.W%z:HtܺxB. Yqp;fNI^zFܿB<鿶 vvKYp@ѳh@9lyr 6{CQκ<ge[y̘UͷgLdP:H϶k4+&Xȝgmx'ANo썺KqEʯ 84Q 7Vn{QU_LF{j{3G\ȬѶY4aFɆG5WAg &ك'c%s˳G皷8 ]mcvXiG>*e=͍luu*-K<K-e2k;\} %ĞsvtDU瞥S밁DEvWJ:>}'*#io1tT#]¡':iI͋o0Jv~잲WMQx>=]n{6Gi "&#}L-:2%Ɍ|{kjJ$ЕӞ55@Sy5 7s#~䝆~tz ͗WAzin&Gap:)1}u`֣I|,M: D韅+H؊^L & ]-9_ /ם)װ N'& |j#ֲQQA1HE 𰝗Kݮp'E% Yi*TAEad8wʱ.5{b1`Њ}HZsp%Jx/@`XRf$q/+ HiNλ3Wq&+Ct,P֯kgK3?)W#5 VTVQJ6[5Hyϰ7XkG,0-(>v*! o_=^ T&IdQ_7QިF={qWn<~XGN*m+$Ij1uo+ BT`}CXb; S/q.@=9.y)h!A?W cUv.7;CFhh~OPhxY4X|GcG}1S'e\9Jh^~[ԡO=(D | qq>aeGLv7ۋCe|O~s"3"ޱ> >\i o|w?FF0 ά;`СcEKT.J:TVRb(v"Qc}(X?K?yDTc̯.͉p9QΎj,]rE"tT_`2Y,/LG࢚IaJȒR# >3?=sjG4>BDg)UJۢ%,;}|[P?;ʇ*Ui|vcZzć"9_ɉRm]{EsO̠Y|#!G잓OR\IlAd7'~ZQ¦.HAd4K K{pkn-uh3ҳH# S9h;.3bcl+PJn1:-ϥGvZ)30e꠬JבuiF_?GW{?y}۴)>` (BG̖h(F~yX E4 >0`]%b]69)8:oݪG+P\ba5{nQ'wYA Bv|Lq}Vu6\|@X{+ЯK.kCH^;-?-5!+8ԁj#/pe ȱ= {`Lb3t"H`TZXCF[|779&]P3M1?qpctMs047ΐ+4pNi%r9~J*gxZ#BUB5f&cQűPRFtf (h:2oM2& D2v0(tQE _F!Ǿ` gS(VKJ\<%Q#P/gh$f lecmX2˄D`Q2xNk@.ZӉ@ˀW(]XH@65v/Į;E\U$;;lic&'3ط}>OcS /5剕1_o;j`~ [һZw⑇v<+*;]V(X~g1CF^O  3+ї`]ة ~F,PN7KRFf5q 8eȠimj_Y"mfzG3}gIC&r_oL`*=&s0olڲn{=a-n &4̪r1@DK(z#y[IdO}TWLLJmwY*xlHْY_͝,~ /;bՀj@B3eMr9A8k6PF4 Ly ~X'f%{K9*w9 昑{v_Su3t 9:.&M 1XA.}/O*SNRYnO4y7C3Iɗ”trSid9B= NZJ < @`꣸)(3)qXVA%@lS3Y=g"4󯽫7Lqž~ݰ|YΜBWD=x/9*߰\eU=M-8c̹;{:-NJ5Z(#FޥٔIL)FQ>R+$*{;ZsdꬱT&+HhY[~ccі⮸dd>1 1]/2(JVٿC~1dSyg&ٖd;; y);6 %an` 2i{(_h&ܑ|_`?+X+c>A ΧПHi dž<&+rWXp &Nū><M=!q:1Of9tSP s>>1!d8<ԧËGba*t 1$UkG!<&GFSϖҢ> `-FO:L y\8mJtln]c3im9`e8 wyYGF'Ua{*CE{>ĸyqW 6k0}vdz>avM iGѝ3мTuXﴄbi@NaT]/M雐v甚wB;m1b;<\ܪ=%)2 lq̀z&9Inz v^[>#Qns%ٺѩH/@q;/]:qM'vs|êq"5jJJ))DΠ8&;lटԸ+y7Wg';@H=8ij'a3߆Gu:h1ޝ%XDG I1M)-JpVLFz;c$X8)BЫKхXhޯɇC3u>\.Rum,-[=鋡N }9hzGHK]Q\œh!%Lo6fS29mc^fڑ$퐢Hѩʼn@}< m2Oj=r{YD H+Ms9scuVGI~U+4U^zB+wQ,=z:.xKtfK̶ X? ^JglNJNi~}N +emGQ :3 dkV:SٽI0t.j50f xh͏p IhZ<"G_y`$0HUǟjAP:ibe;`TɷκַtʟeZOVp#~D,%g$,Ӭ(q{Z>Gr-[l)drלab}BΓ~g,܄K249ɬFLބ,p׬`ఈ6Jrr׵T&l~cSL RAHP Qdf75gs7W7W 2r-''Q8n pL?-m˄w(|Wy+We<,%ő2,ŖZ?żoPG)tA *D*OEyWE P9W'>U O=+hv3M򾺻 h>k,MC`8,aH;QB>=Km4X])em4Zw{Dqhhä"]xr$ˢou(6ɘ!I7'p=iQi$`13LV![YȖhyޅLjKXHs^t YD\9^;]#X h¶bͥ9TǬ~T8 q'0RF<>:`ꑓx(f~vh<;mX~] \w*-".Wj rӹBkGH$- G=\ V mK%="x%$+*'w@|Pn=)KPQ'̽ d|Q}V8k cdn^J'mo~?K3y(ywJ;X/x`EaJm)Ua.Ȑz:s&=9PΏv\%!,\wH"#5DO"}|L!o+$' iAX/ BI$⊎GEƾnj8j]íD`U+Y4"O+T$hLȂ(c+1<^ɵq*yx~ZaUQ;FE49^6-Ӈ)*IT#Sۂϧ4]( >`"hG#/'U_r~DLa*%ft,<[4_迲%EQ2FѤyo lmBV?%b [݇^4} JiJ~}ĐE4xiwN @?@m_= z#Si?􌽢:$^{'$7&:g KfR^-:f|ζVj8~N}} .@TnA=nxtyL?W(k TcHJ:t>ȟWqxu1L @CX?g=-jsR`G)+/ef淠{I}Vu:|kHwԢ eczލ\3qˑ5mx>n_+."s qus/*m`|Uf ֒{`s,{MdDŝ6< zJ/}z,bz)deNQeҌש82twUFS;C]E?[@Ma'Eҝ: חCsCMys4 MP\S=ǖtĽqW@ꋄIȒo0?qG&B[h3:7 `wι ĸIZsF#WIWK7l6?3bvU] UQDwxd 'LZA:q{vڃU/e#XrbW`+"4إl4~22;&HnֻaX,TlL}E:d5^Q>Xmp\d2@e鋠eW&`{=o;6\v疣=&+(~Dh֖KiSB*7#o;s$46'!}p% -wTfd8֔UY-F:݌nnp30&/Eͮ,op(?P(,iz&Ie4N}SsafL#%]ܡkX>+Dߤxh=zuyӤqssG%QeFX)5Jl/ʈbxxq'kVR-ArruGH7E1i!TJI\O#2]9l"wP Q=Y h\!飓k?OF@~*婂mAlPLI 6뚯-K)7~@s{3̝PL@PJr_ !islyH.T+*4TudGC'ˡsqVVx^߷) ^ivgZxҿR.J'!ߞW Epzts4[ҀA.!^f JtLѲyB&'gd+ȳcZ0 |u iaO) yQ%{@ 4QǠݒluT|p1 \^U ͗2W40 lU1|'>2o놭Q(4E9|uaa@+> iІ+ SʯN왩YS(?ng3[mӳ- \gouIȞ<+EFf@]_7b ύkC"fWЕI>0叨ikY] JtQUJU1uHuaUr6Ɏl@EkOݑ`aE!C+ 4*ķ}V2y_] LڶTb1*LWUՒU=AgRX O4Tr~ f@M`҈TFcIj>Qap.g#4-Ē\nȍkTފ";%!Fvuɦ^- sa}Ry䢇xT ;\:c1q)5Z HB:qEZ\N*SCߌ8<UBU^DӉk *K{"by}mqlEy+7<yI,M\ܲc'1b)jHKSM?ߴVe 3ʢ^i8?D/%תRf=JB[_s+\ @ˍcI0j3R[NsA\HB$b)8<֛Nn*vwH3;95 v pLjۜKV쩚#mETь :v4붑dm,C8t3im;㬹R.޳Ul2ag4Ba,N6Pऑ+w./0m T&pbRMA_c@nGCF4_)ʆ EGJWZ֩ps<'y:VĻwViّuvdq-toU (oS`PCdC6U<$9p5+Խ2)SڪͥRad2ǙWNl3sI#=ڼ#~}N/(/ 3()/jS>6lv_ۡILtC',E0^XH!cE6)gFSrT[1 Hj PN;G% g|߻(I^zjwȈѫGq̂M FmUE=ᑗA "M)idpQ'EiZ=VFK/.=tXr & z6ګB\GVo;%qUQ]Pߪk=p i E5g;g*(Q',þG1YVVw\﩯:jBC$`W.rtѳ/J3!~BpO0_6C;13l<Ǡ%?GOQE@0qe~X8`ؙo>3 YFo d qH[ɂF+W#1O"ڸ q<EEQY:d|5|j;CWqFhB}[EMq(cz0_^,**'Kf+u}լ /u˟\w;VUW+̡yQ<zhhKǜG삂ΛM \WΊe^ #GBq?SSkVI HH䶁wd7%wn;LjXSrde܇ʞx]?ChP-MYkS0wlw6ݖz yW<lX!Bto0 %'=SJb!O (_Ol0-!L(j[Pݮ/'e,Kκ A&HiO{c(JtBR_ժp5b 'OpzafPzcl/6Pœ:b@O@.Wd#naRٵTuw u*ρ."njƞ2$*oƩD@'?MBS&*G [bj|U]/WYEՓi'd鮓0~⍉gp,oՍsn9ȍ8?@?oGDFn??h%pЫ9qEKE T)Cah BAjC_oժ%l/h:Ȕ'ER Cɳ4 <`{tPVvV$],FJ ߅{3X 8_ ۪?>ETD_ph ;s'Y![?L]|3E~#YLpWS,n*6HW2E >jeA*e/naœЃ7zOS+΢=$viFS8Rϋ#9P@C`ujVwk9BW7vT]ɬv3ᩜȉ˾ k_&vCdyh%W7q|I;Ws{ &ǩ^%#r:ԨD|mPD$gVY4j7:o&3*Q2.:1A{9u6Vښ0%e]Rk=`7madt SlkNRP7Kř@7gfO5 ?`ϦerB1[Dh2hbvr?yy`s )sQdċEi5׋C5Z {~P(ݙu\| !a$B 02d\:wwEjS ڀ twLJggQ`+v֋.z~rۗo wRS*+gn?~Icq4]ȸs wE.]gm391z0O"moBVƺSBg o70D08n $CKz9^C~O0Yk_&a%z:q?PfVwZk /J% %zMA ]pXdoճM!%SA# pM!Uoj4YJn׊ YYb8-`(M׍NR|K,88>fё1axAH+b#\+рYN^VuRd$=DB vK$Y)NB"6C|a/}L,Mfx\n{rg#2&>T*ԕaFLJJrRYK:.G䜸(SʗhwɊ%/Z{2؄4">*oŌm ݎ:[Eӗ?8CKC׏?ŒV*H.2ՋIjm 7 29)eN?$UJ +'; N7%/}("_gI \yaY /h=zE4{BוJ .ws$EKk-WH ItsJSSxu]LR~)8?馭K4 6L'+CV_K^U5D,}~G&z=f+]BtąPY),?fqT 7qdboc b4 }`/?vx{%̇آTϬmec\[X 0.*> I4n_}Ge&, j4*Qi5 Z}:w Ơ[g%D֣lmYlޠ*f/7D_ k| 㱏h?"H7MV#GC `XAvu]s/sHH0/Ϲ R(xaW?2--K"ޫzv!]+eqs͍hu-FQܐH)QXbS2U<=u*֑$?&KM7Uz,NڧsՊu3)3, xP2$#ԘvAcHGP9\ʼ8>5pUxͻߓHNJuDn6I 9rj5M[cgݜ EABal ؔ`:VISj#3ц[bQ|⢵RݒU[k>b >9"oja\(q,՟ OBoT0Oenƞ潆H#aM.+&nϕ&;ˏй dNQe6Q>¶kPi iPf= ۢ2YU2j/|h .z0dJz~& zr F]u?UQ-6#~k"}tCwjʘ?]*;( &\?4y_#TYH&dmxkm_po\נ `f}6ҸgId\;5Qb{ܡ%EKܑ>aZ'3bQ4o.2By7b\%URzͣNҷbt "̜ .hp 2s}ya̔?K•%vh a^Vt2n,\̒802yUYjyl^|ݥO; u*'?"D|?VZs݃DtK+&k1AvZc0.߈M*匦I("DA:N* y=[]4@\C8:n G+\Q)9fg5{Cj=$f|w|69Y#s@ek%d'D6~3]sEc8NɎ]sES-wrgP4CZzJU>L ˮ~LzSiۏJt `YXUy 7C&=ټ1Z?ų*6ew u?ha:pdSb"{m8T.i} v8)h=%`Bd 6Z̠ȡY"`.Ӏ%=ĽVi9dlD%qcS[*Q!_DR昽68 bOևqcĒy|yn#?]ah_YdŞҐ&l|"i-ځ"{rAckfAbfb<$"k9kޕDXmïxF=_p8h#F%Sre5(Ӱ"Ip_㳙#3y|PI“ UV¾_> 7xM 5P8z،R.]:7sPD~ŀ hVᅳ )PR*™|F SǦn7((S=MP(zco/xar\ :6y{@WN3T^V5#L90I_ԆwKV5K_5h[֓o*NE _Q5 __  - Lb╬vاH|oّ U獾H-SؐBQyҎh'gr\"VW4i<R:M$#dka_L+ .F$SYu$t9y>3X #$J[,שcSf/q8 =Kb Ы3bm1CP""{0F E- W,V<͌6Bb/j]9!OiD9)$;WVJ^n1=T_Ja7`z+E4MӐGc6֍jcd< T6QfYL"#eHh<ک&;ס `0iU#ݪqDoFzO*Bf{y|5}M1@D 1}cҤ ~(tzyAǓS|"/Vq8Fbx4gԖp+?r whLQ||ˆk*7$nMn]%`^3Y"䍑Z}- AD.PR]B⭷LpaP\C&G}:rc(xAMBÙA?OjZg*;BO}M3 G 51) h3"uVa,R䥜f, *:/Mn ?.>"U5!HI jR;MLI첩W?|IG`:=dѲ|:%<}J3-;(4/XgL_ 5Q)6WW l ( R6ENChW1Ҩ~fM%l]WYivh h{hКȴjFcNG|6g$/S}*q:N-f˨I f ш{+MJJ-|#ݏpkF#qz_`gU}_Dh>ACK"몸+Txw}01Wg;e`[ >a3_fl;X &֡XQS9}Ï$ÑK⎊=ӌ$4/ݩcJ-?b hJXOl'pQ̠41.ڤ$Ҹ_37]5&1sw]#^\ nF&+d7Vs9=3cTH)v'3c pM?0f2YN.]ݟ,th+8qWIosM^)_hZaz)!-HbN)H{'w ۃ.hEH~rJxqiYGFFVhdzACQES ee-񆇼1(~sm "6wt;ݐtUmЯ.__ͽ+`rYnشXCJF:~1{͞ɯ >'vIar2 R*}ADizO1a~׬}ߤ˔7bfr<ώDd+e .X9^'z\=SYMkZ3ԦjrCWf;h]M>FE[24i+!tV*#N]Gk(*3/RXG- HΠFczH ж"f &x+n-#Z+C e̐% Q# פ&ݎA#JR}-_LP{tfZ)uz EQUnH8Cf2wqďw8y"Cyiz^珻ky*9Dmӹ&h~OJ0ⷜZu {& %#3E(KP)5PE7 11)B;"$mhGhwuIhV]WO-u_ooʱZ!Vxd>7j)4Wn+WZa#Et-@+R<=OGӏg`mOxoR7 _0D6]3 ; ꣍8BcYoզ8ʃ\Q]q<ϙE?P:Jaӧ* UT(fRv։"7N,ϦWdzlDyf>ʪ**v5^M+W*:R6YVgJ;B|Pė 5nM’yEc EI DZO@h{ͱ{&cHPoZFһP=ni_Ol2p]ׄ eTڑz+yD HA8 ;̢ n2~, u>\@ [0̬i3KIBJȸk9uq~3csߡ;ũJ‰xԽQjcA~sWf䧜7:.$2wF"FGI0U4̽&䇍_nk]tU-ʴ ~b:2 Vh 9~WeILӦNAI*N(J$voTpcфl0e51Z hAuLH$o _$g-N%ZW9Ui דi{bBڈR;if9Ekyٔyuv*sҲh =j-eU0%r>ZH>q C_떼FJo6r3xnVoG&X̞YcywjPPl?hbK#Hwz40#6,^N7RQ(ĻI~mt a8 ibpluLRuyDP -*W !ɇV0|^=y]6UɷY'6*k`ٳI ށ0rz[ K"Ei8r@Rqzn[qM+M$ vr-!ݦ,bsï<}+v; %1Q_>M1UaX>BsUK4\e JYJVܸ#}1(/4jDS(Cdj-Rzw@%4тnR,0M#+dJDw *ly  $+XnWMXQ׷[1k=Jv4+B#zD!((3j; %F=Z\:GϭBhMBn-Gb(pϥ))[ wJ-ΉY UT+*.mF"b!Iʖ+ I/DMlYx7uV[sFe;.Ғ/]XRt{l'W m#V2dilqw[[Sq+M ֐zi\6VWof(Kt+ Uմ$&̔ĈLC`AGlpsb@8F b'cn%x~І\Tv@kmh&Pg%*݀aQR9P#ux+‰AypTv)pu;*<|:$J9ICk]6̸]VcЇo O៰lSw fzHi@QchkkչңGKSfBym[OV q'c2B s_Čw<ͼ`tNVXlJ:#"Z\|[bhKP[W ~9J jkB Dvo6Jt[+Qīԇu O 1Q-F0ik ƟnZ`>fY_)ɠbivꞛt?,pLם;vx%Xon$b_WqR W$O|? xﴤrN>y܉0`Y Hu5 +K܊(aCVAnNzE1Vmgyl&@k}Sɋ֩XB~ԏh7QyaziCSMxc$uNaZ"YfY vU; ZiU0;BA9+(PC86ۆ˩DpҐI@ކE) 2jDMxe?"^7WU*Z7@髥 \=ԪNRvQ@ nlQR0Uw?e|bDS'IgM#݋[ 2.c|IO*_/%6&tɸ>MtţH%}[/|Is$|r(9'd}~ d'p,}z_l(L"D=0kDEBDxͦďd%RZ@ڔ:!׈~][Oay v:A%tBq'_\Ge_kÁEfwzL}IhxUQOw0WI{6s ~d_3w:yzI0yvIa ,}OC]Dre5̮R'pI'BOP}(8]mG':`y5j -~ZBJ^9S ڽf&ZFp'||sis҆T`/\iALyۛ wKQQ~P׬ݜɻ;ٻUM@+S3KgU 79c}Y2bfL$)0+ve5Eow -QդJ@69A=)Ċ_pw*0`a@ou7z"VIL$Puaz@Ӌ{t#yQ1 3۸ ,WͅuU^Bzn5zKE3 #lN¢*KYdZ>8I!Q9 6\%(ɘ+/eLšC!l86Gj%rdOiq<=`0jRۚӫG$BM4ݬN3Vrlo\`eDuK @\c¿ŋ`F D˹2ftnT?[?K- #]*]7k*';}ܐd,!Rs;Η9 IatNk=?Iz+$6w2X>FiHV`zuGgigEpqXP+Z3o0̃ c 1FQV]|ڳ2_)$^o֒%sVW2ȡq:q1P1q>k ]khTN0U!ĩ1F\C (v%@MG۳ z|bɕzj*e$W}V<U[r8/&_'Ivl]=qhME BK@A{0(f&|׹1>%<7+EO6JZH1vd 4nfᝠ9jxHUVOtK.TcYr8ƕі2׹)ř4,̒XOS;8̘NqX!YdX@ yT KNac bRzO,^ht9c4$mqOMlT<;Q `LN;1pNvܼS[r6VFKt,7'*"t,ЄJp:W#X6)I[7ug99PۘwR |W\NKN9нiLz"lv ܛ-Ŀ]IwiZ3c-愝U;F7K|KvMOFr6׆En?t&Niŏ.{Ng,-%+\H1*V49ҦtH^"8R?$3GB8eBvqNG|tc(@Vs6ZLT{7Nn:(3GyUm)$qV̦Cebf'/vU6LJ巧;KFۺ5@;C@o]$P&;RקTI#ρl+@}VJ (NscRE r.!DʶaN T㙀7rc=-ZI%=L2NY7p!7x39dIF YsIp(8)dzk^l&!~p-kFK=л=,LV~ff~ɱ݊R40{rXZIOG~UQTG+}-?)RJOk J @NGf5\x7& hy{}.p@QHX1fYLh}.ħrUH1qJF,Wd},n!,`H*tO+JYvG6C`4:tD3$ΉҭiAw@NjEaJ;dZIypHAV4YS} /~ˑ47n[qPLRpm!z?,E=>,6M? 5!q8=hWUfOwЧK a; Np+(LjNwZ >WH=/gl L*Hzn}@aYuVFDE K@ItaAUN} WVj^x9ryM!6.Jw{uSw/fM\~Uq mHE9u4"sFR67r_y%N2 \Q!꾹nù!Q˷HK;=\Q3վ¾H|YHr:֡3md{ Îz?ae)iy|Q 2gwd("!MEm;H;ĻQ]{=i@Ԫ-%=-:8aS򄂓 4"9}{PJp&U77 _~h`O~'q4*!4җڻ=ַLug3--I)\LOpiٲb:Mu$YB0f! py4uUγyy2EesTbHwBx.6 ǒѯbEv3 -m@ǜZFՁO4q >l|p*)r`{ jc)a/~+Zo̩k#.X Bn2\l C$p^1R@QdAݕa9a~bv!v牀[Պܦ!Q2/Z݊HT>![M^R3K)־!l8#^b^p"nJ1}-vs- {v2rrÚyKxװ 騹Xׅp)p&?mUkוхWOV.٥Npuzo2JZG{r5u%8I+"Fabnɭ7a55N_Et<|6 w"Pa}n#ⴖ( )CؽҿfbOMK~ Y9^G\d+砌ZȈ_[o7X\՟(x> eY6E+镼JX^slԎ93o1GЂi vArW=4ᘵ]:||.JI +%54=%hdwP=)֪L` y5| oQrb7O_Q@s8$^# b= 8=IP(9.OP;2l0/ ,T-:/)=^ &;Ev!ʘJAq@\<[_dgN4x^w#=8iC× ^|{$"܈;p"D5﨔e%A=rhGrѬ0O80"<g6L9 H?ciǯB?%

    EB, TT}OiX<;9D;,RկC97I2/y{9cL.rS-_ ǃFMH+dݚAnkj0l {gؠd\kKgUr)O9__F.8xjJDp[mu@2shb2ڞb/R.MthQxaIja {L7D/ʚ?=l#F<HՊ|biP%\\hZl%?b-Mb3f;:?Dy{:s-_ti#wx_A7)#BfB拨:xkґT|zO^sMCSl6.% o6D:(]0S?:8 MLWSw^!S(Jq^5>^jE>.uW#)-raFdq㑙3-}vY"qc @cepΔ~Z./\D9/"߉f &%:]_v$@22OvBq8 bJ3r1L?&7^63Gy!6\0qf(<5xWn滇y|+L"vN  anIɝg ێTZ5+T^TPL!d?$ }iF%цܟV~S(grxCImr8 ::'M'}zle^9fFB3d9&Ñ&$zogHwKM#A1Z}&32QX"ʧGN݉35󜎘)d}[m9(ju ou:I"NbXi2.}-S(y+H ?a^C)ry܅1$xuJ ==-=OO*".;>=11ZaO|Yvp;€(HǜQe ?z_* j!5VN9+otĨ?N"q.!Z% F`D/)`֘?Zk 6b)2cĆs0FMQ{<:)ܚg Mq`.&Q JO3PT̖y-8[M`"Me*ۣI%F0pLu\oD< uF6"l¿&4@rd/HD\C?utMos\VS %N F.L;CBUؠM{YO5,Ch=Tp_Qn1;t D7ESV?+C>4qvr9ֈg}y56]@C.J!$2 B<=AF11^0ED߅y^nZ4\b~} 䠑8ᔵi ;#8UͰVIpB{o9Y[m3EFtwO!t`]@v1L ^K kN >aQ/0.;?etvߪy:2xU!$Lz5?ئXkC "5qy*־Rc0cC`\s1un0 VXGNWIx>qQLWpuǦ,Jz5궆c7n7~m.PaE j>ݳ꥗);t6kd (7S2EB/ii a<=K–hX3_+*IxϩT o.c߾ yA-AD#S7ܕ'}N0( %Cc<.P[T~&BDi췦26ݹ yHwJH3Օęd !!MHU bb *\ >&l+Uf3ΌY<;TSU!N5戩v aW hd<롘6'ɾ܂_>* &n>3gƳ ;)t#tz/E0^ʿhh8Ȃs9liFS^\`! 4pF-TG+B{ZV.*vp@޴;O NEܼMS(`4a5azxPYi8άQ-}xIsXABMqs\u&#L8k4-3k"rzhBb(tCʻ>hIne; ڏ~z@g4O+i@"WEDGR2Al*@jp9M*6VQ }7׬wߺT-ퟶ9 5A哃fǓ Z-tܯ9NyZ5'OsRޚfBZ[3͓@"B Qڪhj\„ru%G)[?*x.%XrrG ['; *۩ kEUX ҀdIْ݅x5čW[ 0ǎ&CI# ih-~H.+Yl1駽TҚF^ȓC,ݕ?;0l0.BmBJQ8v`A"78يkkc_a{ CLi.\]׭NΦVO^t/eo{B;zHqC}Ix>nO}imۏ= hg9K\z>о"&Q\Iش )VKXܝxB&xtOZtۛC,fM]̲坦2?walHhescٍj&bOKXr7wFc FlSɽ*UuI=dիIźڂHƺiv51꼈q%@>L:VI[BX·N$_ 4ѽ:|I4e]`rOLܰxƏ.MJnzsGBڜF( ]٥P)٪amHZ^ eD-mL7EVnB /0%`1JM:O "Ll$ΊeE:<1ɁOӑ.Biduδz$ܨ&!^la=ced17$8;`GE)ZׂCژ7U+?ҙ m!T!{lO{{szҮ}fIp]l7u+N -B_q<_uDCtH444؂J=7FSWKT6ޙe >ЌtQ6{ws*xf|yj~ ~$걇cpiK߽EKZM(Sgh\}Fd]`7˚h?H2~q&`eXǜxDkRl}TA46мV_đ հwMa"|~?aHSДT_O5uP^m@[wy`gU=menr kQt3+SS"Nb`l%$VPҚ{;%ĕ-h Yɟ2(&@j QO(c]^ Rѭ|/mj{.eaZ[Ж)v@ziqt<@!+>dIê㻎G=h7Ĺ7e#[FF826_3-'Rmaxǹ!*Og (>.ɧŋ &[R.AW hByTGUWmIY?c &ʜps ~:Űq4oHN]V5E迪Nj)r$KqrhYl`I,V?mS?K^pco njC'Nk0dfqʧ"yOLSʣfʂkO చHfSpFQX4#0 ̗ʼn8JCW!wn^EMH9w@ )vlWY6r9V:l]|7 DP BgMF r2 ud&R|R֣+sny`>J=I@rCDo6dЌMrFlpԯtbnt36ET42 TwAJ]yN8mIՁrĮ8Dwܧ Cl2I&N]~yA\5DmLZ3sw[;n-ڽZU 3gG$1w+op麕 h^4 f.i ߣ=xCmkCflc^>%T IPjP%#.SF `;%˽R#u`:aB2Z{=*6hܓlQJ06⯻>$ft 3V+Q?׽el5\eXHY-zOdLo4_PՈnsgJ5G8 WL>\bH+j_"2gmz.KEW,:ԄXZC qV`c$:Ƌ?ɱ廞\+'h ikOmHt_Qz.n czي7Wq+h;'~sB i}~*@k7&ÁO`N?a&a kbEےRr7yBnO\5WB@Dߡ}{¢쐛W'w j% i/C7G RȎV$ lH2"@-h:,LVL$oO1%ذ8ReWcĨj?PU{Y<٤ nmq+o1SH{ ycoTz~\rV9<2u gqOA`UfuBɋ.GI1(]|1Rx u$}pauB^o8fU{Q4;F M my"c2ll't֪~M@PP{آVm!ieVvoN T_a+R YjcޖQ /J$P[ˮ]p?ѡӵ5~7 T-{,D"aMéx )`'C:A:xJt=>{#~y6e]i w=6f:SǁNh r_I:$m|T^ǂ ^b2tkxW޵X}H', љ{=HcbpTU<98 RٿR fo_~ٌ0TjoD=]>(PB%iTk9Nd24R3TX G>oB#.D l'!gA m .dv8V"s/'7tգtXXeГju'R ~nM׳}t䏒9"muگl:48oh##2Vx0u6Ẕ<bT6eyA5}uJ& v6[ fI>fn{uǃd-eG[|C 1}2A_=q쫖wq6#eU G^*jaP ~o>u£N>?:ũݫfQ[~wnW|<n2rb8.0cτAjQ5--m8= %x^Rعq?kC #Ҵ+"7OAX`8SpL"ڰc">oعh$jj5}6L=ע=o a%Pպqd |ӰkoO"OJz H'YմwՆ2O:IvVMG otV:={ӯt0.#8<,< DC 68M3%5sjTdMJK ԰"ιhDYyH62(@Fт_Yi5_B0Nhp)yBb /^Pߤ+ p.ZjȰ,T_/[v@4M#%MW5eY顰U*׭*jw74t{oՑ"q)b's9 V 钰òNdm)P ɾˤR St)ǺϻG%zisL !C2]jt>)Sd̗x) 0[r]dq N?Ir[toyguc z0ufZtq9)>+Gm{J{h3ςA l7? SyZ|'J 7/j-F3YaJ^;J hBhtX?9ş K&k>=#wirW lRG\LRϰ&LF&1f~MٵX6זZaEn)/b c=;Aѓ.J[4ZHlM`| E5ѤvXPjdqB'no3s\"plWE<=nʿn? #HbVKZ[ŻF I T [}Oa"See")]P|PxLW]GmAb–vtKuurճ9"ʬk8T%JTo\ߵ'XƘčupN r͑Fٵ֗'xW}fFo+O9L- eJ//kO.XΝUQӳ=uFiBWy@ߜj)QH6u9LT\/Ҋ@p]b)s7ɥפ~m2= xO4-&+U6mK PMQuPTs NC"󊥾w"*޹E-Ұ#RKT^Qo#v";'C ?Js1-j}M3Q (rJ}㨤#DPZ5ׄC7D/L}F/$֘N+ jm,0A-qjkBj*'O}a**2l( ωE#Ghǰk, )ŏh9j0e/za`ܨYp7~1"7Nw6"Qf7n pƼa6$npަ.xP͊jNRo;*]8L',n4 ;{>mR 9{E.sHԖyڦeJ{#BgUxt*gWӣ)gy6wv 99yxxM_ɱN y}?z1u0[I2UF84gݯhb=T̀e娊p{j޸%QPEʛffWPLb[Y<Ns9D̼}8c6s&EÍϹJ"@"\ 1X9BFhJe;jGts89Tn[B-abF|1'% kXz9•"mB^eAJ+QC'|(Z#Ǭj^A"cM2<xL/?[;wQD( ]k#mU裎|eIC' }u怓n= gݖ㻊R CkrgyeIIBM#4dǜٔbv:qP* 5AX74rd,Ƚ%V75?K~1/n}@v|W%02e`O'Z`ֺo5NJ:^Р [Vu&g]={?oɽjJa$sūG8Yi+RL L1@W¡蛃 oq mSG%ݗ<͛`?_8ݤ 5k`eU]|]+D#[2&KV+ nMDv=@VVs߿wux./k,K7R',j?+Ł؝{uv]GጯkXC%O( WiM}kuGbl):ڊ xHb_4;0cWnuXhdqԫiH[IT-)x 6(ɓ2Ue/ɵ⫕~ ߳GVO눣wP8TUjXʐ}dNAb\A D~IF2PҀ1;,e0N,3@$p銫X!QяF?L 1i^]:wiOaE/#FdNAlM%ΝͳraO27 'C}<)d cv t6kz<hgl5ǃ)KG:~I߾j$$zZjd M|Bǹ> #e͸1G<F yGbqh{kTN'K5y-(TJ,Dw~@Z*Fv8lJZ ɡh-'WgKEِ?>`!UCCI ro/ s1t^QT*ɐ^Oq]c͖PR4;=yu(:"4Ī_m|\GrpU `K?Ԧ;GKv+ f̳BiU eE7ن0c2w!&tǻ1L%J;*;>G|IJB )GC,B8g#ǡ 5mF令~S"YVٹ={e*7sxra<.!b鮺`6̻N_ j?Q a|3phԷ[AS=Q(1BR?L?KpK?ǨxNj\*Ȕ" 2֦yh=) h7qbqFa@Cx,y zmv͂ЛIܛ3!`ǜ*Ww?`!> *ҋ,Au.5N @5>z3<UwZvtRnIQn/ݾ7$B3gq >JO"Sܞ:ntE.[{0rsvܡ`+Y(?{ď]Z[&u /fUI|ޮ heѣl c3Xt8^]oM?P:O FLlI0v*ٱ840Q&k@h@BV\I<<ۈp}g/y7VFM]8cMy5Z ΓK(*z)^qI&T`VY`Io<=Ck;t="vu̩L9+"񎰅D!$+$cJ"L FXdI]fSp; [a*]_,':x\kKr|7Y?cqvh嬩ۙ͋7ƔqK 9cUb-`BJT[4SKs  %G[Khn[P³ 8E .2gy4 U=,D8QcVspDkWr2scNE1g.ܻ~A 2oF"9l' I*堙^dܶ" KJcE!yuʧKM:Qȡ4>z6#ő^<>tJ멁PER *dah@j\Fjv\iCn5[`u6!1NK Ha@З~.z^=kt)zJ,B1ϡ!Ig p% Ŏq"Lc‰i8K84nSZ 52=)ŅR2h 찬XoS߮1N0W`drTIa[:<[&f"f+ )M|,h|h{$Tی޼]lS^tOf厯g̓})[L3&{>?n%gZR v#07ήs7c۞zݴb B'/sӭj@=\3 k:JO)y]22/ѼėC%pGo\lA~!>Q[&?sOzvH}z+{e#jj;ȅ2ZA䲎8+(-̿|Sf ^THiGoKD8plMv{K %QFf(@WLge>;K[{>c_wnQL|LŽOt4_#|vr<{l9PF))"&&h.K`O:^.YYBIVs36)㪎JR%ta/}0ҟF[,h4TQ壃W|U!/H{=Re$9K2);ז:^I k+rчʹX-҂Z4op3}%Y?*w/iPm1c^{N-̡bLZP3ȍ'T'n`lUTo +fW[d.Gbq +Q(0 M>voͲȗE 3Md;)꼓Ijp& Xb@TMy—d;AD4 ů؈mMj.h@D-0Ms㭣}5\u} 7yfaSuNMvh|2dlo~% 'NdF-8/+bDg( ʫ_G))A(pȀT3zBJ}m&E;G ×`0X4@ȣ/mޯvr\5 ci$,4] =-cKt1X)-wX‡"En"?S_3 T@渟zjJ0vtgoN$MǦWWb)eN1`CQ".VտK`nRtf~u_dJlx\ 8f ۣYilVꩊ.{πYy$ 6NhL=/ਹ)~7~ )K؅]AlچDT)dyy8YLs2ffd7*1~{B/i.=$Wa56r/[%?#?.gzp(|x8bG7GU|_ =AtcgmgH6l ɐHqjׯH6\o=wTڼp7PɵXWXUj^qaYUEݵ:7V%~[zcɔ k7bvKY-Q/G7VƠ\wy͐J$ɠ,Hg[;o2t8F@A(Lg̒shPa4|;[H)YDi1W7jY!X+1|ͯ }Rޤ}O'Xs!GyrMQ'owY߯THR1cX^#C;hcӚȃ%@3qDGوvdS6?Mʄ&5#7p颼F&>7&sdߍ%&xG /)C8s-$!c02&j 2 nCVN9#:5Ԟq/*K+cH jQ΁2=5UD"Gَ?VImAU+uɿX*Ls+wb&p& WY&laPPu8fB-{νUemrīMCX{G{ A=ijz"bgvfcH%S-I9腚K8QE^/dMDjIY a:J1a's*&2Lx;]Lz7'Eৡ⢕ǘ,j)zAX"a25(deȖh =C[W JG+Rk7n ТO( Ut-VF)U0)f 46 Wqc)a+gn~q.r5.kDLMv3tJk賻  rLh:VX俁_AgY1b2כXY$ \iKq=3Eh8nRWx TBvEnl'y7u`=A+ %kpceq^.tWhW7ݒ^.ɂg P^.+m;ž+/ XM, yioVҹpVFEHfpcղ^.TEӏ\nyl}_,kjՍ!{Dlm^<+n.VC\WcQϯhȄ@("d` <3hZ'u֪B^k_ h ܞf(aK3p!Q1V;aɃV 9g9~}n4'^Duh<Ɇu0M\Vꌹ(X `Z}1U  /Ь]CdovYX3:G*-(j7g'-!2QO&A6*>|駡limvY̺-fBNVo"͋jNvBc,?h431o!PiΙ/e='mn |SK(`oI@\xWh@E)࿛~_&0]w-zSLhJTWfcO Xa^vUv 3b^K!=ŭIĤOfWE);Y}$]T7E>yؕ"eϹ"'I~'ڦs;|`΃2w;(~tuB֑|5A9M2^ID5c )ΙMn1fЂ,갚b:u['"IcBhDlņTh/G25jtyJlN]qIa- /0o lǢj>rḺgim.833^2<3О5GdV`ӊndL2; 6lٺ_X)(E7M41ކ4gW%F2szBMW#7aN<-ڮ}"BЌvdU)gW Q~f>}.lȽtݪ V}'>_mN&wҕW~SÁw*(C.88EMVJ_")+=ysusDS{Ъ1#A9AL?bQ_0qhJLZ h쾒6P)NUVDOfSėˍH!:A50:PU !գ< =stFLr2:81mc+vLn\|hBR9 Ɩ;l ֋{gJzMDlTJtV=7X*9f[Qr~_~;btV@ ,ɸjh\&SI'].Q; B'B襥=!G{Y(WUa:(|[~V4gse?%<)-\l>?['1uN:TAH@Ш}iᄿ5|'e(Oxd}𸡢9-oC^hFgd 3թ(6-x9xyQ j"E xUrA!0lCFԯP\];ziկ9QahsL=WJy@$5 w:uRݕG2bζ{  yd~M>3XyWSDaP8dx9`#S?z> ጙ2ٮALV}|1Nð@lEG?P_bݮ;4>^k0u1%`41a9 xF' oP?IaFaQdi}.cpHcDY('Awڡ[l_xVfiFP!#)Ȃݽi\oC?Vd ίnmk4oy< F5ߚwq¤$5a/5<=$rhI:CC*[)<˅aq8`Ih9 %GoQ72Ynǃ:NOPj4": 2])S&`%2<3B޾qu>z. \A {揍]䗊~b %^ǒ"ׅ"f7 XwA{;VP{R=7iwΫCp kI'0ܝѕaN 7aOϣ: ͩ@s&\S}| ]9 JW[~$<~@|$0H3A~K[$Cm%n ?u_7+ͩaN2 Mt4IZF(.|n.͟[ +դAR--&WpW}7wgy|p1>/N-;fKsCSVS {pb槆N1L%y&T-E}LĮ޵3XİiR|vm~חG<{|äGM0CZFHWοR3K>)F@T5l=-||ńBuJ W(g%EfS4O+_:-PDTwz'0QHF|.|Մvtcʱf%YOc'f)bsqp`T_ H&!YRtKh߯#CD0 n&̢'@4F׉L x}4%L L!gͼҽgx`ZHnۭp8z=Q_*(U269H C0/'#L% {5B96ZBm@lLȈ|sTDDhBsYj+r_bO"nj9a¦ދK =3@͉;VF7Kۏ{N]/+\#Aɢ-ͅT߁'/"GK}* 5jϫP~G))kTdJ,ȫ =:8 "o qͅZy<ٓv#2ޭW&"6ݍd7C@^K[s@pS-ߚ66|hWIO-O&52Yg/9015'w2j !&vJ\I_i,œ?C 3b9Hr;hzOL__3=ճ& ] ?=`WA}PBJ2di`T)i@Yfbz9tQtzQ*OeԩjsD_cZdK[G8jGT(lW%8Nh+!Uj||U7G;k1,@c7?=8H20?6\*#uO+iTC-@ }oLf成Axڅ^e0g!e)ٕg@hcpڱl$9L:I8N2 Y\"x"`p)YIwP{VwOJ.=8Z ~'=ӿXx`JK/z<`L!Dk5uͨZ^: VmZ%}{2ߕ5o-ԝFfhreqH ]ԥϊPavl[e=Fd7DPyAUPKtOuy(Q ;bK5mliǓUkA$e/%@p;)492^.Fu) q|Uh±8-βCS_7`~)ғj8c Iܼa&|;,BrP_ۗe3nb@eP@qsn$Cex?nwc>B֏1I(;R8Hˠ6"yR= i^cK4dfd4b5n&{K@`ׄ-u:Izu'ӁDžmA=t6?@kkx-/Niވ@33Ђ-N4o*|pOT+gj& ^oDݼhVI3Mi> q{v P Td4tEb;sx, cxdl.cv̛m1a: p[]RID Y\>WES*W$xBAV:iz{=')ưP+fx+[RI6rsԾ~8kh§[3 4>L!E~0ԍY VBjOt]A| fڞ:(P:t9j1{E ` Gݑ> iNU/ċ_2l5n>o?J'̼]ܥ}ʑwZ9% s͇&1 PŲ磿9!={&S47)v1xgwFz`8Q;yuTid?:dwnqo/%<%{$\yR|`ltG3bkWE!+1 KmYp¿ KJ,Ie|e6:{' FUzpk^FU `Ƃ(n`7q*{_C{_s[7gஶhEMd*1)楞꾋E\)Oc,; h{s-oZiDw|&d*͉>_kb\猶)>|Y'Y {2EWohbuQu9p7K W,6V|wxK8f3y;|˩j"7B{{ijD/pb-ţ4z[ST]9@'mByӌ={|=#H}I*STG5 $ TuC ނ0!>Ǧ! c $;ҝ%z뚍/id+6*̸zϞ. t`ۡnLxF20H6(Ih Jd H/Ѭy5~L|MAz?HfIS!~2% /vHʖ (ʖm'JNiq?``[tG|ٓx_FMՊ9nqg=eG(&xeS炀tP|/5fgii 0K98eu~E$1Dz؇ IYr'aXaFDf D|R;^7,}軃Ry0&#oj]W(l4vսTɱdAD:/QuZ+ (i./%Xsr[7rZ|DWvJcY:%IW)\R@i *޾S6tبaK1(q95p^-HSCQ3IH)=a+^}ڴNn76" Ύ&`rLK~^Z SJO.kfee8t@*  E9>u̬B(Lʑ]1C~8mImE?8&rz:1/{ċ@ws~0^@o k8Ngx>L8vӼϜ Z퇭9=&x^hhJ^#; )Eh}ltsCWQ#-VB_wSɞBiP L)YRW+b펐n҂ۘQ%gM~4 H-,B7ت5mrT6^M6F+LW<" |(Molے6L&39o9.V^=qDU/4o1{vI"Gh-i*;v+.7 c֔x,|Z|2G}S<:fc 'Gσ{u m-PJ NV#1&$u[P[!#>[뽟\+(,Y2+M6\p1U rx1is_#)]OwUAi 5qkhAhs.! JŅpG1'W/q^䪤=鋁 }ꎖٻlr+R`kʿ5S m A )E>uim6.upE>Cm!N6 Ժt#+ 7*cܟLj#1?KW~+<"A"Z+4kt=<`GkRy5)8_C|'|(v}?J\F K7čB;7;1F&IGm2aj =A׼<*Iwf\!"O^j5!?c<2̅e#Q)bTC<l/ Bap*.I͢*j#%rUsap03]b/>`ʈu#9bwh\YdDjrVeRICa}{LsjM.f) )[@C{JkN GťT#[:Ovlh=H}!QidŬA U }kH QB52IZ^0%?!س`Ǭ23gC)!~1ɜqCܘD2d6_%N5R(!T?+whL ]o%vĩ Ǹ%Ű.Or~{ CfF;V=z XO,a,] E#Q!Iδ d2XDXг $ZJϑhϑ;s꼖@Şd'^AQt5C5s}l9ăQ#< ۅGxXo"00oA _eK|o./0?Nv9ҟi_?cp/}b]Oݻ`.>kF] },NRJPY6܉F\`6K/~:A_!O,oltɺ8Gm. r4>Rc /Of75q |=>wrݳNDBELQUT`wk[HA&M6Q/Q4agssH|nG]nh}OHʛO":=ֵ-?Q "'J̶Ok) +4 g flC1-r%] ‘U>k~5 )fc <PXS·?K8X2nv%(m@Ahp p≁E8yHj*#k׊=;ねi+ѿ±6vCf;0e Qm"y$ש(qivy2n)~yAԓꛯnrWCvI, m1搒(|}ih!op7\w)6Tcƿr\7AyE\㈮'Ao|&g@‡cPY&{ TlN!'08ټܺl8^ۥȡ GwGC>7lu^`E~3k,H0Ά\ݦQ5`n ?q%!+4"1&ZiDַgr@1e 0p@ Cc`kI3mb0DҬ؏I'!N! `y%"K%p'?Kmz%U!Aq䦖+"mZbjL ) δ^; #[zsnȪ=N\D[J7*C͍osQKfY?%se􅳼_3# ɍǸHrg<5~ IᑺnzY;,.V|CLXkv=c]eȻŅ"G6Lܶ*o b(XѻiK*(h6.C/Y8lGM(Xq1 l>VPnɯcZkpAV bkG0C\zԷ*6\1~-^ b8=;u5#j$њTû_KK!5a9<՜-,@W-kR[2! #,WȐZė|"lޣnAk]9nĹa+`4D)%XaH䝼$ JC!bi}l`IDp adwl]'8Ĝ*u TTقףʻyZ9{v/'CVmM_4q]ᑈV_złϗ]:U fJMva ocoZ~|ۋ۰Mwsqi}-G 5V "tTg/c"C.[NL,ozc]0Y60IOYG;d' [{Tq+ `0C "C/ǐ 2]|ckPC؀3JlR>y @qt`8>F.,-'tx$%aW^>8OHMEP`r0[<#lV#cbVKBz~ݱ˰g`Wk2. q%`u&zؾJؑظ/WЃP~GֵjsZ_M뚱 `m4!"ޫehc# RbhZ7T&Ð^]U)k_YqybyU$"nxc(޻`G$M22/yDqlZ`Ml;g]7V츉E%P7G&5tLhvڅqh+@ ݰ7yB՘-.gTLbwn8lή@&+-#UAKl䙂P-l􌴘رYDNjj3OZ@ 3k96L{aw&);gxǘJ~K1:rMُY x tф^'׋ƹ_+uf>}hdkT%`HesƏ{$ɥZ;5/a|+"{5uD9͟甬If.zCCYڥб$tώH.YMnp5֘l b%eeD"׃ÉC\o],Z0됓hzlG"(6) r4FYyLN5+N8tX`p-Be {h{m:ݚZxƳR7RW?XrV}wÇOÒ4D=*^yxS/۪2^ HcGY)ڛ7bAdǼ*/`J|:ZqeV8zlܜɀߐ2Eu]V'E~j =廰UX[<z0~L l{僁"ڇVL)Q.RHGG$`r!PKr:2&iRP"UA?p!'.vX{^y%g8WbW֑:f VӨP.*cNJрwJ\9 ԰s|㐠}{j- vP:dpc  VY ԩ+P%=Q{B2\4U߷c!2Xץa4biT)OSOWrԷIgGTV ||,ũSZ{Pdgp\ _z]1ydN`)s?lqE`uF|y5{v ] 둫1ٝoKc5p'BG 9 =xOTvаx[c;ʧU]ȥ%aڂ_Ϸeٻ̩I"*%UCln/B{(MSte[41 Z} i\5i])œ*_rRYj&#-}?:rM=TJf\u6HB?'Hg)A kly$g؈3덲@b~ &PqtRbMmA6Ìh$Ǖ:zZBz1jX^Szb\g&ўQ'_Sñr_>$nePtW 1{o,AkхrќG4? "Jz!{7YoV$otХܭuRFg!sL+LS?ܰ~M"Z[4hzQ#"mjM_W̗nCab/{Dw_Ԉ=Li6 Z DTF.]QKp#lmp׋p6MnCTCb0%[}|&V8I c`a|"4Ka?ZHNJ0UzUoi*l0?^mDﹾJQYcHDx;29/V6-M9RfJs[eFq! Fz r\ rU)X.dČ6LJNNPC㚊aW&wnw$7Z7(<ؗi2A\lj($2V'(}W~}/V, %uV7 l]Fo^>zY! W(%Xr|hG)5by Tz}YG%##xHB a]A, 9u',$dau"C'"\9#@ȑ+C^+ץ?6qDL(ĮJAdQ d H;£[S-Nrta b;Ը= .rmϿbu1Noey[Ή66ҭLœt.Cx !<v8urWwDj2g@45ȯ[x>oMATԏQݪw˝5H294zO\f 3i~3ZszK<$"rl$W6{r+g v&t[ uqCB)Lwגltd_.1{.G},J}Α ?<16v7zQm TI<!٧s~a4g5[z65+'zj-lLilQc~0DzZ `0x͜^zoG^iqx7~k-W{E4-lx?HY!6|_(:!Gǹ=ܥ3JCF0!;w' oOaHhaMr7"T#qq$Qa2Դ)=Ռȭ8D3cZO愯M.4nZC:L> ws'U΄HtaQ̓*``ܾa9ܐA95+ `*Bj<97~;Vl 2_Q/pT̡9E]x_ :趯I*OU'߽m_R]^).E8 {dIaOǽ .&B`'WF=/pjeF TZ+{0wK /3Pw%k BXw,eѤ.$nMqPkb,[w6Ϣ^ %h݇4"+sb;[9NÉ $4/y i`{F7g{UJpU;U-/f;jeF-c"0ŕ?o^F,=C%ΊzEiA0"e.SxK\ Xu??#T >kC8Ce`JM9-[|85Tp"b#NFq8b8 M1X~3Qw"ӲghicruK$cxgYНWk#nK`0j}5$ ~`r\_]A;= +H}qXb@jYڠaD80N/5+Lw[|Չ׭~͜ўl >шAJ5 XTV+Y<7W H)n BNbwʎK$17 S(^#Eûn鿻S"1k]G 6/D.D[HUZ5J^¤ǠmID`L/ajRxto5W7;~ dq'{)T.Iu _ nJ/e6LKߟFD7'51 n 2w=+m(slUhI-WmW 5pM:c :Xϭ[6وX?-=E8GPxSY"g>UZc: \&;βՐ_n}_~Nb.93Uy{McB Y*.gŵry糃lpJFTWpuO8\a˧DZ bhAuՄO-qV^JJׅRZCK*,ntJ1?K#Kd%%EUUx[;psMv"Bh֧'#CwcC`"<Q|W.o.VIԻSJ^h;pƧ[ANs{ZJYVCNUl G)/Z=p8Ԥ&')v[H1 woܸPD̀Oo-YnJ|uKh ;IiyD闽Y,~#ΟV/" ]Ԭ/ KWmslVKJ\q *R1HM ztLURt{kJ^x~H {.9Qn @)q N5.G7_܅B>]U/mbl?P3u.5^rT 47{| RixdFajEB0U:8"Wְvjlia"Tٻj]2THD-VBzL l!O?f*t6XFOk۪ɤѥohd[%BHcUY9`qަqoȹ9%&3 s!p B_Wφ܅k]BRo)#&P zOu?4 1@&(6|SA2}(j-(TNtyg3 ̷Dk 6ADV3:W~ 5|GcgFWH tէk;8_XWvZ<;wR8,6ak!=ڙT0 /9c}۠G2+c$\)^.0o)DB_5X@Ԥr}eAa*6sjͩGtÙ߬9jDLcFʻHLQ14*Fsי'e"#WbVş't,.|$5ax&?L".ovC(sF#.(fVp[0^=g&2F sv}Npa#*G8c';kޗHoqu1 Ud= ^^>jg)-YGҕ\Fo=^s]1$׉u\RnLagݬEB鐸3-o!oXu:`f(6D1+MޣysAr>𸓞wt&Ɉ7m 91u' +A'$y]@#qxםe@4x\*Q$I]7wSWIe0o=A9AY%A7f\xyT/;xٺlw{-xJ,.ndQmc~͹F;GnT}&įttd,('I$m)% t?c3$`yJ-DSKFXofL{T݄!gBŸ~[`}E@_ٳR"WTitZY8(>Ղ:I!;n%nƆjjecz6ra`+ sns Ǟ׌{_\!g&Gb{h7'K.*iV8UL,0dVJ>`5V[Vwxf$:|yRu'R"^JXYqqPDxp oAgà~;^ 3D<CS@ 6 ~v"[HC[z"3W 2:OVXz>fƆwb-{܍[{nY!CNsU§F7|E\JF PjI.9 h^, Q?Pf5PC;F]+&&P"/`l,0y}qU6ǝHId"c8Uc尡!T J7>cATywHc}I94Sm[` 䚫s~VYq',D#x@5L0{JFE-J.H~S8DWͤ 4*"aX׀1 I@z$}|@F1[N6cJU;^7e2]1s7NbK Wt' ?[u5ƂpnPNc',\ ,^٣a@"Rz;W{"9(oTz,%UG}_ϞvDsU{k>,U9yH 9v1|ĄY+Śg &neء f?w!4f <ܫ= yւ}inCTm6\Set&^Ѯ P[.il*Q&I&6yap=%ޢh"ѿdbGhKDK*`WemkDOՑ0mr0\s[6(+m׊~[7dAsxˁI8??ESq-4=l)DBRY$eTQ(ԭk*e瓟7sPrđLDi׾P"Q]CAzen5y2~  IW0lGn@_7%BHcUY9`/Q>Xti8X \s/mBoSuki4f282),u.  J˧@JAK?ڳG * ~1jpm"V_普EHvpװTxXR3ml%K57-F5ŕh3?RM) _!k#}HҢ(ϕ%F;Wx#3"m}X|k)C%ؿ]~sd^tl&фYUhy^j$LʾtVmy+*X[JД6Xd37/.TM[rf~D.fU&x95G.yD Lp PQe2Bk۫׶:׍{,14``̿,/*5idcmjg\f'bhWs?% z [Wu~(Z 0)W~06:p[Y?8kɌ(hv3!Xhh`i?A;)2 jgKxgՀ$gur{F#|ff-1c,isW]|6}6| @2?|D@߬\rrz sS@ZKad=;@Qe2Yb 7/h+HM6U$Cd[9F\&瘷b~G)IW!Ǿa B\Jq5ęm]OXsl1Kـ|:v$-ݶ75j~Q-tM<\U?tفil;d\ch&Ti Ú %X_G2 DEI*.Is3RȆP)lR\yZ!Q W9xz`B>Pi AzlݰӔ6Cj9No^;ghrӟ*r3ʜ 7XslŭF~vmY ;JVuiY/1`gOmр[:cY9(Wu_}hΔY-K3:)8oL~:F`} _=\ lk}^Zsz!r5QdAEDYvEB$%d֔tпa8L(9%$<~o C,oX13N xQGk9w5?ֆȚ0oXh5ιZIUM4_wEE*t5,dt5d 喎V/^9qY-J_3/"NUڍº_(l <չ[z6)c%A'e:?FnMhσvk Ny}xC郍^M_Җ0{eKW, !|&V̴ W!Eve`WHM|>ЉWΖĖ㆞YR:<8!+tG2Fζ`ZsW pheoʾa ;GH0[ 4P@gvdD81Z?#l#&kޮV/ $'MM{ #/4OX#2џ&;EB'%} 2{Ӡ䠖{*omWV_RpT׬4'3\VM]c{VGSN G/k:?Xcq g`ץVyLvDMMa[^^2I|L(oI%r8C. |.NFZnL4qؐ٪")Ph^1Um*yu[}S(7b3,̴/Ƹf24g&߯\{.+01zff{9=hc-/Bx|I[堲]тg!hोq@mh1 ?h=]G`u?w!ci^=Gcv&p] m1->4kdb:jJ/`IV=C̋qX ƪ+R |kMХi.\EAwϚ1 F@ٵJVV3=5|cV8` ԏꕼAx?RC<4'|aU-j/M)5 8 axLǃloIZU+st/Ñ &vra1fQQ4s|;"uvDfFy46DMR['u+m?Ys;AMӶH\M\avs\[Y(]Uux_qh{Qџ"'wYR$r ikɮ]\0ZlNȈ=>iYCtF9/ꔔ; RR"ɮpӺ'vԼ2tiy|xuJVMo12Cy}N,'*mXdgX==l)Zz0276W1;f}Hu@1˒o;am>ҷ:9y 2OJ f&у OzGؠ-mE-2[oŭ'̊ hpnwE[Km,cZ-Z')= "V4OZ=[sسA6!DYr4K6T=[p}͙Pߎu!ո՗YQd@ƘCt޻YbcxĎL2=d$ri4-bXacم{_ԯR5TNWDB~u#m*F#1^s9=~wI%Do SFJbqN5f xR[Ww}@Uί%,͸K )T 0VX,/Ѱ&.Y ,2QW_\u[J*sH^~ίsr ϳIJ<=ښ8ϛ~xNy paj$VbndkK[e]{[/7b8@>~dW ѹ!eKIX.Xj PW,>'wuԓ1^ަc I̷Jkqg6hЈKE: ~+ŷzX"!ՈeW1q(Zs"qZkSa:PH(%L}t@`BѓÔՅjϪ*|ɀ| C1vZoofԡHShı.P{)BJ%ݴ+|0 ȕdz;/lI3NW0.Zo!ѹ/d\3tSl!k+E"Hn~~SkIez;/P!i1k1dt٧n\,hۣtuq01plvSydet_+L}!50ܦ؋vƗ Tp@`ΡUE(XxSfI`#M_O$<ʼG`Dr{<dLpY#di֠ZΫ6^kѻ5MkMj ;W=smxEY.'BvF(ՙsyUL7/w^"?Iɭ5mbhxOkD6%\B #Ne4Ř4=(u QOW? ,HYA!OYvi]%K{!\fQцAѲ0Z 1@oԙf1]_=g50qA܆!XY hXhW M ե+٠ ;Bld;hֺ|{&KT!jX"eOaÇL"$.}j`nBzb *[NI n !L*b2,)J 6 [`\JЩhUqcJ||PN Wng aMy w$kh/Zx F`"$jߢ#;ڷ{JL £܂]F)܃kHLW-b<Ԁ|) ;Gm*XࣃCHEqUtMp'=j~ERjiI߉y(MpFw+(\nO Q-F .oQxQN7#XKGMi-uߋǐ?|AojJ+Fe48-QII?HSd?dz.D Ff .-ςVRf&4ҺQQ|)QܫL*mڛLI5:< W)vkoBq_ h9Bnerڰ0#STgtg!9㌎<pwPԟt@k8"==@eQޠ%79ayz %4$gQ&~G>--LsK3 vtUf;nd UC&~|1%JY@%TăFzuE0Q?zrx)!]@7rT3Ѻׄ&$Ay9dPu}B :ʌ ߵG#3ݒV"D3QBhOX<:|(X+c[fߧ l\LP6| *R8vp'Rg!rTc" rt' k;HT;9q˩pzn-=ǔS!.BIS2,s1-I ѿ}}]F;FClC|s_dkZ 7軫%wLCBO/D-;5&!"G1*@Z^S8mHok/i=v"-9yYPjr{vxVיy6ږ2vފ῎SDD=T,sD B&!6}7f,x?gά_1 ɸ4&\VmTiG(|ܔwX;N}RN6~o3Pa鷾JϭΖ4=6LDj>`dܜt\ }IƱ`Jl f<‹n/Ib,!%nꑇWTc;3zHWOOݖ&%\{LQ0}g}%@z$ү_Bp@ jAU383#@ge,[t~vzmҳ;ZMՎ]_|yZMұ=B4d#R76DDonY.F*|}eUZ=:~(Ycl(TXJWc-E2Joj }}BP̢4 nOzfkl(?Ɔ0#(4jx5y:־r? 9{`~A.rz~.:6 4`Ea"Fu%Y#b#|M8#%(qw^ HZvM0Nwajs; [N432QSO UPL#ԑA&ᶜAy% d]aDR," 0?^-USYWyzߒ {@Ju:-qsryO9Y7/8FX^q/csCi㺬\M9>$Ӈ\%Y=xƳESRū% zITҬێ7&7o %p%lTwi<ӛ3 *1)OV-,byN=h4@Ёzb*-#mZE[Bla*jڗ5ȡ Q?&oFP'js1KA377f3Q]]0XݖQaZOx?wbb{[75-<._n:D,(6/ׯE1uƞLd?i=lRpmٵ6wsH8>j׺1@GM&%en~57V=(VjσZӽO:&el[`&ĤPh,2p i_+G TL\+@l\J׹f|&MUusurCZ)΀򔠳-8)W{gP!fԽ>RVNl|GQG|3 k`Ny T#MݫBU^" |vc:f(L:'5>Sc{㿥DoP;F&ٖEaXbP߸g%|"fн̞ڑGMu lq`(Ǹŋ3UA18Ǥ1Un=Mh5`&q=/^b35KoO4`X-ɳp"̼q$ /u0 0? tqV4sx|_mH%|<&}lΪȩh/O(fNf m.QΫa]n6TCsm Z͌ɲ/UUpIKcZ1_[6GJ5hX;t) ]ߒe~rTy1>tOxs`|w![0u~q,YJt3!xK(?Z8&`  54~456h=; M'}uHHlPݬL>šhzKy)59bn f&{rZz0#tE3h #pL',R[C!&K.SF4Jr(ǮˑEx^2+5UE^Xѳ%a05g7ń k*{kN#rJiS^~Ϲ}"P1TFզm>4YZguJsx^NL9`g6Hؚ}cP4 (I3t_ _ݸ6Ӷ?`~5&by[Kx{7wsl}3 M=Է.Ea\"'_뷖ITn(k2H2C-~m~t8*/x:3d!~zبe=3'x߆tFeѦ;߶qfy9kUl!rcEy2z0ȻdeR:S]Lߘ Z K ڈV1itzW[+ၹh.Pi/0n(;%۟ܗ'& t6/Pߴ_ z1EbH fVM6SS&\1wB=0%vq]9`bFpYushh5B 7B+J}^jַ)K_'>5ۓaA+vUKUSVҥű\#z$C (_KȯpIB`m5r(('&$L(I̎4jCJun<۟]WVT{O `6""Eӧz<#YllխM= r"-lߵg=;@t X o*R!N/ yȈXKlgR - gQC $h4|{^!NIh\0wO gb- @t ^!R8z5m'fzmξCl8Aa!n)5['t0D4p3߳Q5ON+Ԍ翮Lo_' ΀7Ӳ Srkׇ{68TbOMʈXg^C lUd;6hYj[$i<)uԄ,^_u*8Un/ t3ܧb҈kr/X5N5w&Eg͌ r'-(}&L Q(m w/Z_ cHrZ_.xU9S{Ƿ ]-0Wy d0R@(pIO ϕP+*0K֟$?(Ѓǿ߀z1m!@mOQa^s0Ď0xΣ&xUq@jS u3fJ!QUs=,pųbr;! e6ĊC"2HjHK9*މHpaW$xh1trP_ڟ:l =Tבly')&{ҮuI\îKIvƼJX=jx.P3:ef\xE?|649Qt*CRvE|IeA,ܤL1{00ÂmAH9]3$Q;k 8ˑcY0x')e\RlM)h 45ɵ`>zUVWi*{1UMpŌ4/7 -LWT^D}r&@!fz"v!C1,D؊28 x_픓 ⴲK:"Ň/nS`i*B/Wbt a w:ażX\Y"p◊23h 伭riytiXdDh5wvqǺ1Xwy<=s̍=69qs7n(L@lm4kՑj4NK7O*c6޻8bJM8gU d kߦ{w}>09n=gLm,v]ŢoD ${j92[an{% Db"T~@}Co.񎿫kT•ͅleFgx1-'QPN_Ht()"F.qpI&q1ULOo5۽[΃ }}5B TzDgV0Ɖ%"=At+MA%F}DVQ >Kb7[PoƆ$*ˠH1ESƏΎjZó*{b'T|F~63^~K  !/TÙsUֆ2hsUN584aL$ dNLQdUnNkg1 eʜD%S>ybKB.ܩ#jѬթM垑"}ck.ba5h'ETJڔ`$Y T9Gs9rB>gNjk~3>X@Aa!Yo o">jKz~)6az8QEO- J?Iۤ٦M%7ooc-Ԟt Z}y܍hP#BwF߈„ZK-Jr Lg<#S9WDHOI.0m4 0ؙM4MWR3*y0t%>YRאFuR4lkHma_{3lj5u24 ?tنտ` gz2\"P Gk^3W5?E>m&8imM#d}jg1<_h}hcߪRt/L; * u2 k|% zFY/A݁S,,M9rŦ Y\15( Ljju0 vy0_ TWב՘nɾ¯)0TVb-_3tx*xnl}Q^G *J^#Ps*# 2 ȅ(^q?9V%=B !kae4Egw F<~ՔH ю``\Up}uWRț8oK`? &{ƒCEqI*3S!=w½z&Jt86 !hX|%:- 0CsjdK:s|V3?Y)WVwJYoA}-Pxa[gP{{0su$`KwS^~lyWA(GP,!GpCAp!_ W i>?Jy߫-y(疮x?ID, !5 om"kE&.U3D$Z 04|m{Ɣv[A4fU͹9-1zbb=[Հ0B9j2<쨠Rς}ߒ,=4P8~Fge9VH#ӎBbPC!IgM0} c`A}pX+6-f/Dxl\啸ԁG-֞.{9 tyOPm>FF!]D)K}+}m?lw[`0WTWBlś.Ew6Q3f aL6wZC볋.+? !Ac!OZ}X]\{_f _.ƱCm70̩9 Cj`LqG0-.8[-.esx@؝q3$jp9+јk ȇv\p\6s6/ dVu< N CDТ3<{Z !z ^' ~m9sKO%U h^*7%܍4|/OiMf>(k9TY3! |W2R.l.=؂]<0?S~Ww^-ᙕ5+?Gf.7F(ns.wY8IyS-IM{{>Q#n~`,:5wXZB,'lsD,}DS+-~yv0f{iyֻu? 5ޙRK~KGeN@(9hloK&HJ;? NuiX0# zS1{T7Bj?;isW1e@CV*fe2 K. ?oo\ԱNqS lT͜k *[u(VCTNbvki .=9Ae5Z9ۓN/jZ7@,uY{RL$M1Tv?ֽ̤z^Y\IW[OuՌ6vH#7{\"ZQKB6K}xsS@-3U2 y6Z~Z16﨟W(k8+/b,=Ec7:rF~,gn#npJ"VLM6N Ϊd`AԆ }/fՈғkwi$ߺcyJ(3k!M4Bm[p;Ut@:>bVCzfC)ѡ2j:.=W1vLt1ۖtRygZSYk!j~Mf |.Lr1!]>/yZ D$| eAthZI]MQ7[A՛H!IXuȭ,t#S-̴qTjfKI [9x,h'ǙuTdI8@QǶ {d#^q z`tWlxV9_qVtp L=_2n ߇Ag:WeCys^W+~T< sj/ m]l#[xoAzI{b)nI.$AQQ\HI s[ޑGpڭ'4=FUjaU,;j3R?}5·g"ew,Jvru*䲃2#:I01ϴ7Ԅ[nج5>Es i8<'.:CH.pb҉k8 [PC5O-}J%0 3GUܠ] 21BjcvޟvjjqLcd p-Ĥifw 17 gl&!yc㺕7OÆ:]D?;XCN}BOnʴ;Hg퍹*u t4dǥ{^~^=EZ2w#ϕ (Xbpqb8o>nf.'ۍǪc5H1X\b*/Si$`:,cGx'weA(殺r=ۡ[:.x޹L8Ӕ(1xߕay3^.32)| Xb9=f+ϛ}-|y-ߢ!y|ך ȣ ̳ގ nޠH׏zqnuzj";1gXO"Rӓn uxg{8ԡylI9|Hݙpp~eOܻ3J7x=N?ܻv'rM_X@RGD0L't´ui%9/mԉ[bHPQ/=d]WUri6F}Ca&\xvր`k0cnV$Ae.vK!,.itz(%7Ko˅3ViC'L9vWl'xWƩvX(lBܗIE9Aۯ")kGGiۺ&*$* {[V&a9,Y%J$tB=04uïY KBʃH@ʹ`yT0I4 bk7d/kf$ W7ӼvU69+[K%@ Rklp$%M:oL n5Ì;::a.꜈ﶧߢBHnDTcό3 /ifS$BQF-:L7Ua%b泠rDQy=X5xA:!\D#(Sflaf( VsM09Ƣg`OqFJu:Pni7αET +*_Uafz3~D|Cqq )QS[k'v#64[#=s^Z{1T!S[ڡқv*0We00=9f[! Zu$ta*9ɗi S%=GSd a$Mm*C5my3*.[#3D`~'G&m@ݕ`4@/Тܤ!+aV p37sĠ Z@¯e'Ձ5DL*fʜ#C xzyG{ln+_9T'Sב#wu`ne?k .oQ$#?{ZnG N{PK1FgGϺxYu O~L=Es1K[d6\s$sxoRޞGGO>_$4$ZMi=NPoG0g#9|K :ts"5F ]X 5j #r8O ,3pEBTH}b1+{|Lǘ(NbTh'l2VLUMͿ3uLvw80'-q`h~xF,M]ssKz{:nSQwSYRX@nQïV4FHQ6٧=G^Zl=͟]O NxGrޡ&tVuPEɖҖvuf.S2ݱ{ kI.# ѐ6Z™Jz zKoTy0%w]nP:+rGw?WF|n2u~vAoRꖸ:Z%oaӷHFt9 K,?UL -77sQ{mT`v"T$pxDE0I m>*ؐiz\zU=Mq"(u^~6xQ'iG Nt&DB)ulj? EjCZT`fm#4qk.ihz XLwr#Ni⃮$ w>Cs IBhh BY݌ RʟJD6Esy. "cìF}\0{* RVD/nk׊%BCh,McpO_HQU*L&G )~N8Rt)U#) x]-er7J~:ڇky˚|CţZV~C6gՆHpV]øb.fj|<YRW˶_vJ 7Ib@{/0tBl9?Z83?jz<L~򃴅#g}7OxD>F/7a`t ߾ky;9 6ؓP>'Yb+moER̾mz;SZ lKSAӭgI#aZf2_YvF:Bg+Mlj$W;t7E UE܉ x hw]f1B 1ޣCGsfto?BaF?sR_En4 y|@o8ϴ}Gqw`W2,4k3端^Qv $?h94N=ln0_%ɢ .!mw ٖ2s<14 / v^j%# N 낰UuZB&ّU#r Y'S"B6"榪;qi,m#J'ѫfQ欙6f;ښ92f"jaa7 $~/-$Uyoy="k7[񚸢^>[ p#-+ڕ^u-!M@U&|_{*1Fk&ʛ.> q3[h Si=q:(Ls8ٽl"lfhS}qJCeb!/ x/T5ohՑ0s_wE o dǚ nEk~wgVh<2?Ly6DqB?8g{{*/_نMgTV8V!H>3&˄сهb^ [K)(h'>XffƊK FP,D58`,{VI0sM "8 a ?hUo"Sͭ%*칊dNϗқ[H(HtB'D(AlgiI9|: m I"q\S W6[;$z)1s;G{xR@좽jmM߼WF/V͸ \$v 4H@9tBbyN6JLrbTi MdѤД _|5aeTZƬHBwA2T2gp X0A ]2|G&Q15T>VYp(?f|iROt1H %t*f۱O2(Kʭ4`EZ/O3'bQ6]{32Zk?u*2 R~)r#}$bZW=.*4W^LzfƎcˍ)o}f(jD/MWOK4)آiBlVcȷY͹D UU-1 )~i)W&\IfZBpI0>)PMhXa&;b_fɅQ]N,JɅ'kX_O gm4]*)b:N[KL?`_^[yYy!vT[M0iD}3,wA%ٖB׺il AyK';~k|JP=u=zdQ*l:;@х{`hu^(b?4Xji蓴0u)p)lOB6fu_}[A 0u6 2΅yGQ*#4+8B8fe*5:_H4{CPmVZOKZJьtxOWyzJʏ7M|J`5,¦In -mx[ ]*3lǚǒ1!t}7>GE+Uy#f?e*iExiH[&fxg4EIVmP֧"2!p(D9 @LC6ڰr_w5RWxui#XSE}aab[ 3m~{zHOl$+d7 atV7&g(֋Ӓ׬砑b= FVc>)TDј8<tV>ww&D_Cvdİ)_NZL =ݔ}Ist3J/g.>c?k(KYJqV[ö:N>zD>"UܕHp2ϟ&ՒŶ]ۿCHM~p2:zu7{A#VE9*~NYԇH3FUQ=Ԝ8g\,ˏq,L6v1<7xk]\45E';P 8u!_.s ;XTT%AQ]?291C2y@lqƩ4|xƏ}HfxME(3d8T6CƐ5'OQׇ"4jh.ءzN1_)I*.y޾+ s_E`ϚZR+=ˈSŴˠ|)4*Q**dx|f_wTHC 5P݃Mw\XZ2[B.QS<1;bgl>/-h{z_]H$ Gu@*#уh.ycX:Ԛʘ OZ0a1W{w]K6? # Y8RSrCA,!w&^]`v"fBI :`3Ap!1\C5ݟЮ5?} N &VDCDMXfVXEbد:z`gl-gm`(Lİau"-ZW.KRq&Fs+@k}K]v C}=s:h-L!GG%WevwA|WMC&Cҳa.*u̙4GeF[P7zQ.78D}o~nZW̷b^c2&48˂35^"RC} 8UNCyOtOٗ/,%VuDY"ѻv|2!d~!%D݆P&`bl_ rorؐ!vJ+{({ ii=mB_S =`"քa!y D !>XN@oC`skᘫv:&pS8ˢ1x)e΂CKՕ9NaգYM,e0o Hvq`h +$ pYQ6iDZ=7mBɌ ExMD 2.t'+c :}j ѩdxT%$:efz6=}~ &@_05M+Jqt0Y=./{V-=zVOV_)3*, LWoĜzX(@+,`JR=/3ץ^( kAcHAOTJv$||r}n 57bjv[mωCxR5;}`£hwkGbS 4%Wp FEA{bw锊e ym*)_69:NJ `/ u& UP+mZu9g|xR o֜E: @6K(YXzpZM?AEʚ@ .W30 MJlT)87-ġV[U1StS}K9e{HVk#SDO>.ɏz 7Mvm +d*7EF*͔X#ש!uzt,&?`X)߼}.;b{:=T#7-NŴFXi5 @dz"IYVxRdG3]&>K/Fދ!4qT VC0nzu;(ynHe=e"{ tr@D)Ǝj_,бmESEo+&=bx mY(u5l 4^YĖ@n lX)Yn\#^[!ULE;}~UM6rE, pjj_wjy6FN LMuƷu+ͨ܈lh4$39;,e8Dɕ0FnbPfA gؾdŤ&l@cĴ*9{u܅ l!xz*ȫS{ARlCpÏ3C.h<ԡNXp 7.9 sqz5CY7xZe E,~00G,#cjts^ ֘0jt;}.|,Nx,zO^~? #alin:*!]g/vT,5Qng.%hZg͖C 6Qܐ0^)ɬ kn"XIm<8ȃw(Q6:RbP;Ҟ3$z$]Tk%3x1{~ num@` 2-$-XCtMc$ حj$E7aE# FwGHSӞ9ac?P˫[{r=ؕm'Rې@қbIh7ra&~fB!x'(HXcu9Y2"&l;;eIlܷ:WB(OOu Q(@w;aVof˟/~s0:F3:p(gL B%bfP^Ǹ4Nrn 8FoN47J-͒w7?PBN B(/aU`Т ̄qGlXC!wy?P; I%( ZE,A#`;BX1fAy׉M!U6dX94uCs75Y|6f/5RfrƫOnޛ(D{E{hd[n%8V |coRQji8sA?BPnӀy&q Yrb0jAW.8palq5S}הՌpk4L*h%.9+~nn l} Mҷ74{̯0&Yߟҁ%}ȥ5AI1QomKϓG I-7mB>ߟ+bpn^+Ϯ<|.hNjw{D?6r$)YAnrɵ%jS09&-f"kWuy$dnI(n,cC/FYES6-bBo*&pA kC>V*Sk[[s ڤtgf_g&xn{[hS@h7ً̄XA-|*Rpi:Nuhi4UwٔAfu9X%Yq'7ěl\P}V\LcL򔘰,| J[HP}\h(O?]KU=ŎK|r+eA^4j_7B,F`_xU{t1\-`PJ#it\S!o7hG[0cc}˳Hx#䔧b䇓`GcfZࡲ6[DVKC( -MxtUAȡ D졟_\/{g7Aedp Y'5MIF6wuhm3-MUJAͻC#fpiS5$BMMˊQ=ø-wdDVqV|:82R/?YX13HzOjXhS DlzfT(߄+LqU^LG SQ6DD#0p=6ǕпB"UILgXBd a %34Wm|~E h&Y`F:7dJm{c%_c8ݝ9(Th#[pȝd ލ2&:Z;SR>mKo7(<1s‘dimB~-1KI7R%/%w];k ~, f] j&rߢa'|^ ^ϻDGtagRו5u/9DRa_rJ:fqxg=* ! Rڹ=1*7awu$Saz[cDއ~(g۸$' Ej_x19:/y=uY9x_MEdǹ*URi0zPLA[xno/Π!Q!CGM9Qo|ڄsRB3mK ;FUINjƋl0Sng^[$j"5ZKkItQ+օ,J< K`Li{ތ?Ꜣ5/&L򦁗4 =W~:7|1.s+aT+a}vn%S Xe(XHA3|U&4h.z3XmnۡgFh2F5:ZWf*KCZv13*%(D(u&%<VN`FPsv=vTb!بd$8T& Ϗ=?() D5Qd]PsF'kfWcR~fK/]#AD@5XΛ04RQlX#z6KVa @3o$Ⱦ( [_We`C -o߼kpPuODp]+6z-{为p);6lq\ԧ:ÄMk_ |ys-p'p5v-pj)\pNF~j3 Ե>!r6:^o^aa3]$Oأr{nA^j+\faD6 ֆok(|X҈qCrR޿ɽߞĪk71zuA@4}w_'Pf޲u#izng967\%I&u`&Wݴ3 :(9re~\.'h(UR~' dN 59\B˜n+6A~'wZ_)8#E:Rat :q$A9U쓴"5RT8jv_2#p8pt*%0dpL.~_jة@2n 8k2>9~_Yfۜ Q60Pe!4Ry\\S):#I@cTI(wDzI != 7fG! k>s>u)8s]=x/ur^4OP.Xv[ϺdL!ۊD^ fqNDqb4"9>c39ȑZ4Xkb?OG 8s%ҌN5#k2beupN ;z&yP{I ,\`6) lW{B@LB>kf5uwk@?+ C)ղǡTDo]_v?K=k ̏W&$0McMT}Qu(|JixSӨ 1%F|_85ߎv~F(4sU2 +"Wv>l%Yjy΀c;7dJyl9􀨄v_Fg0Rjpf}աpK2~pc0M:X4%X ¡uY2*3q2&5TţF*z>'֗i~asdC6T5ԇ2Q?l|hLoJ _w t`I>;.FMƀU igc: aT%,X*83HmxΎ%}eupDWAڤ{\g;|&H)B8W-B $ Z=1PgI.{ibxV$BYcy\ٔw)[#Lbx}z}#Lʙr{ѣ0E倹^]JJC]XDc ; {%^h@T9UOP(“}m Y@qpƬ뜮 Z.1Ph]zݬ"0҃1 C^>Nf OD u!Lt6W  B '& Z,5,~G0뗊] *©!zXfL]>vN% 4V!_ skgo IQ>&&Ejf ?s e跜}[ݢw>@ɪ|#vԥІ`\`ι~+"}'kbTuF1&Ϩ! agrFVFVXI9os{XIA>r˸ سr3+%Þ6E[=j3Awݮz-eR"ؘz+0s4{E;R} ɳӋAv;-D  T}9za&AhU6-ap}~V% -4?ì.no%(z@}R;،1tUԸa0O\11.S7X04?a]ٵBkʎ!NZذ~%4LK1ʫSs"<PҸ6gYfk82zsqǏ*}dyBy1+؃Uy1U4( c'>,Meg+CӣQx B fH;ndhuj͈/&F4~N-{L *d@J{ 7=FByx,Ce=UF#VoS8q;iZ)w>5YeI:j1 Md˱P4;Xs;p'޹2U$vv%6S.+f."4?$Qiclųp\Wrߍ tC⽼!)!*`Y-W5e@IVn[%^I+{z1"a(`qn-{]վG$Χ]1"ʾhڣ*NHlI'ۡ vVVɔrfc S3-Ճ,kۺ]L2={ i&L)qS 7)=mo_=:xj[)zfD¿Y"y'vq ):36rKq㚜Â4l,QDS j43Jt?{oU͉@731y}AqQ5!#rPRJ#ۤtN[uvj]D0$0O4k۬ډ6lmzvoM<M5Z2X9*WKn*8.6Z Y7p4Za$<E嗎/hZÚ{>M'&ED?Mc) , c`3 e?H l(@Ke %Ѕ8H[Ô>GԅF/%H9/br7x'EoZbjFT =||U{y%Tf߳QlrlK81ʀ(QٌongaR{pXp\bQh/`|"lFHmqa18XrZ\ eg\mdn1\SzS~ 0 yIz8'M \˟'msk;q cT^A{vs)7 =@WW̔'1 $)(-=`|d ̤ԧhq}S'!\`IO oVU- ^tgÜz"SJYn EK t^!߂a!q&3{(7 YAaXQLkGUr:}㉿IWߧ$]#k RΠђ×z=D)On`Flpҋ枹N T"4 3lYrŏ;Mٕն9cc@~]!-fĴ\ZρARbݻq_1MK1wr#Lj"Pj/E]T^hgb16<ޠ,#K*$J-29p -IUWöA1$I(@1},M31_]R{tfۘJrh ǽ-a>Ԯ gI6ή#D6OOM2+(:2s/E<ʵb+:՚Y|m[Z|9LCCDi'n'l*Aڄ |ՇQ*rL6|+*5YP~3ZUqSJlBx_% Yڝd"k@=r"ŞA+hD1$bVu[tXUs8k#P(qr<퍸VnSEô6e(}߆FT,^^+єfNk]EP7u5Z6w T'w@]=Um'"4)9|2.oUU{+۪#dn+,+]6Aq/R}cF`e7INt| zȫ ѣnu[@j=B}B?Fc=Hb3.zXJ{  NyL;[mxU7q7cS3:sJ`?/O*1n˲6_28ݗhN_;Ju:"s5^q^*p%l=ei ْ5o4~K, vJ?C|!gEFFP.o e3$D"ZsTL}E!j==h7Ƹdv^8\zn1A?.eѭ,`p,}]g5eb' yHY/Ay# Ҭd!I7Il`Szg'cnFGV4ґ|k1'fedX4K=g  i۽Z#.JYl%aV:C%H⥄_3MNgE 655~k"`6?^Q xOZ>= xR&ytmHx٘+DWHHjٿxX)8$r&?XtsΚq|xWOrs6ʵi.F"0$[7 b8ÂvR 3}l)㨈e!0t00e.QS'iwEs-|V z/~lMimڞYa()-2I^: =;'m1s/S7J`ux%~^3gtC !.{\d΁+6 ĥ|,+h ;=rن gQZ`/Z"]Loq~!09R(C0r$IO`ĭ,>sZtqCD%k'TH"QvqTz%KKe٠oק@cqK' d:O! i^VN%!xb'>T{(ÊG\׋?AAyX2A9r%$oO3r`OHANS#4etw/o׋ⴉ|xr?||U/xXy~SԮ]=_*D\8*_<­nUnߋ2YOVl~jf# Ӏsz&C+s”g4c_2nhnʓ5[-/=@7ާNY DR%fbl^vۘٚ!Tpt7<yWnόph1О$=i;_%<A[)֡$!ݦxr ʿv˥[cpl2'kb. "M޵7O_3?`aY%䳛Nm]^$/ = ;'GKMZWU9/JZ-qk*2!{/'Ĭl.+{q%F9ozTOUR@mF)צ % )vi8qE~tvV, A\ńcA ohL-*alaߩ@EJv?HF[t4SL&e3Iq)A~2~[KS7r,2#eb Ifpi`Ypn*>b\:]!lٰJԇC,>\@ֻxΩQ"@]mIeϥ$/*PL U!hӔv5:F65@K7~E+HդcWҭt{ RgѺ|nj~tK9TPD?ji ^;M sv_1$t}|u 됱μ֥%>*{bMS\>3K-2%zy^+',g}ՠ3Ʊڧ6ޱم%HF |0U"KnPVT<PVezu9oDžx7{^ofAqE\;|Plgi@ewMuR Vj UZ\:}o]=4Pd`Cvk;pe.OXL>h3/OU.P:a4 g/su<_.c"ډq30`: f|z.]It9iZ9Хi_5=P9hYq8l<7]T7';!Ad,o`j 3#)9=NOc9uʨ_̍QA=A2B|sh:`ZDPjVG?Apu&X7.dvR yqgqј{53C:'(d^njB򧚸38o!ovgv |VbA(F_znFݻ~{Wv•v?Ooͭunnrf<`XFt1bo[Q<}~dP_TT!F/ew~-b\=KOӛ]Ud$<2pa`[zfҲ)ճ$,䌖1iy[%vUZZ9, ).*:Cyt2!h^s()E9F;jg@eAuGodD|'N$56 +dWiQ]>%d)IMd-V^_r.48WhRP &F'-ZAnYA2cp\1^)pt]]珗K k .pt\^ c &G%垫:@.ҿxD0a6'#H3`|15Uj:ք3P1:J2v`sbRu `زY7u_rQes(WnWo kg+ۜj |۬KPA/T8++&p1|`a do#Hx3y=N@V^Y(ӍP@O 4\b׬_N 4$#Ԏvl}:aJ@HD:Bb}f$,BdlWaa _iB @w^ƥ.hhMZe?bZvzlͥo}u0.vq-؟⬂)y}`%AȂ전 S;=gJ+tD* Ǡam)$}$&$R--̑ E)C7BXaEy҈20pRb0Ԗґ3N8Í"ߘ3AvLlm`KEX)aԟw?/U }0\A?= Jepr6"@@uI$Kx*椱 A77kX~yG"З^4KkC l))4*'T(s vDYC߶..VϳekQ<m3a &]CyC&<9*prǑaC( h(Hsj[],ڹuFi9b|`E"qs놪&yyL_/cCe br,bʫFe_C+1,ɬ@ 9~i$% ?j6@@vRw4N*]9-ruWrъ7oԫQX~ZHkmN>n`T˒B |ͺfdؿ$|Vir;ZrZLT>8.jPQoq^$0ӝ4AF"P.de4K sBT~+  7E}qtK.cVzo5FduKyf~@g}Et9 {ǛMC|׈ u9d-?><8;׻Âb`>̇aP07/,G/%җ x&m7ꞁk[A"-Wt yNg`7@7e?V+lO5PY-SNO&3RH˧l a݀B'D_d!#qtzX섡AКi&P2g\pj6ITc2xWO" ؞Q$ \_LfPc+K74B`Zp@? bUYyVp3Tߟ827jHh\隝rF۠J4NBrf p {L+:hɠ_t׶h/Bs9D~y~&$vk࠻5쀍pL\4}33k*NXxcS{p#<]VsRTtrn۩r!~BDpc]~ r;W %6]7=r0MH3i\L 6|0 j-joAMzbc-ݘc ދƂ|'9{D_ ࡬R||k1Ć.x3u3;qid,jY~Y,;5BVAAsm/AUSa`ޘh msQ.( W'gecT<aܛ.K^5<7i.SYS17&Q"bE TC;Q9Wclsƾ%(}P-ﯢ*H5YPwV 3;PaNpt G &pA$Ș`j2kr0QSp3DQow%0 #RdlEii9ݛqI»zRu̟ipkdiujr&ꚿtN^xœ7?ٸBEOXĞ31Bl0((  d3%|ΐz2Y͸I4V:p",zfq<AΡ.uS[bx%M!Bt(cp9|*p`XB8SV\Uo e}bmb{O}f)\ Pte*8,0FHR07f2IOݲۿ n0O4_9i/KznNy!(e UU<+#/+fS=F;bK3X>NmZy}D8NCoT q01XUڗb\ry;л-íҝ)R0d ="g^ɽLׇ0B;;rdͻ"MbՆ̉?f7J2.W͓PxTn*Ԣ?'!Ye{.%Cg1O^V%^aQ2wO! Mah|RU@n2?RYphuZYnWX3$j/=E﮸v&FM;AvE﷗er__sE3;gQ B_*oPW '\'CPأXq8P̶aWOO3Jh=R dH=C WbP=Э:<7J6:2|]U)[Gd saUHV "1:\`p<8N)-Kܺ~M[!Ư__hANPfx\+q4m;&k<;3XSiyOggbaUi`@=@M$+b2fE(z!Jg5vbo jQ9bѸz77{ Ief&"•ܬYBJUjW$NBJ$e^[jxpd?.Ą\u-?jc)Cʌ/Vl 9 :LhcbV" Zy@΄vF.1{@3"Izu3$!L$K$sb%W1 ޼mጺmf 1_mm^\+3ba4qYH!ql"ѡ(pgb1=*cCp6X% }A@-$hIs6͑oEA) $V9I>&7IZ]8Æ/WɜBסMVY4#OhQ9}|U{J5]-R{#4ׁHMn2;87O3MF}@2om}twXi6CH> nbr5ȟ uy虊.@q:A3:ɮ tЫ^)օ3='|4&gERyD2CڢZbAc_W~efvR&P,oQ[hEIzy)EI* 24RiQNkB8zS@7Y.x%tW+?l/ co{f8Kc+Bl׊Rf2}A&AQ6dHT&\ ,:W P>7:PQlҾ -71+4.H"qb+C=J>dZh,1r~搋rL-*o))ڂ^ſU6&hAC۱ ʄo:Mj\6[`atT&K llj7̇rH3s490<׮U඙h)Mr;~",f%9InhN:#9/ * vikOLQ#Y_ "0EH'PEOOu=qGg!8RQxoc:L[Xhtj-#r佣)&XeKzg|Ss)#Uot 2p&k^R+;Ҋ)}Xx?ʶPFGx (L)B 7lJ.fEK4sC u K3>xܵd?P0.D0gmPNHnA3JgD-VE_l2?i'3R4SKl }0(0b*GXug|]OՌ$ICpԷ䁆M-H_qnRѓ::ix^5 "_/HaF Eq_#jM4RY4/!f7:'2w,26/@ZU|C^45Ɍ1Tj$\8:"K@,@zN֞SeksSϾpvگ}cp#r0u]0\YN9Ig#J67޷r%v(MԼr,ONs9 )6PO{!şD ]9 H} A>P,fwOKP% qڮXB*X|7٧5%5S~8֩X@WMOЉ=R@BKw2ߛ2 52>L. & 9=oG[PJX)rϝ:uͫ^lALJ9U&E`5 ܛ0P>K~1_y3:_9eQ[vD9BSqMR\g&FԦ&FZjhƧ#^ JD -F\U:wCb&7ֳ){(y\ڞUB]P5ug@PnPtxʖn'cחBSDhhfHn%Æ'EZYS` WZYF`EhE5C'LTi'5^G7}Iqs|Ƕ!pLN(ٷǷA߬Z~]$v/KȈO΁bBsp.:kjXAHȒΓ/gOzM*/ɵ#8{y7dYFKᷝ "Y̓L~Oz;j^nl\QR  LQ_IIB'$4w7UTœ\tܱ1I)Z9΢׌Ǻk$}kу.FM;(K=mU38ҟDrH1Si&+R{ի[ņB_IcSMXrC}MRz w e"7y0Sp 1HAC) Aarԃ-0x,m7{D@Fq K7O۶ר$||겧濾ܫV;*33lt"!bS֪qq$)UWbui;{ G[@\+4D)53Q.h2EnFbCLkSx^N]/hjJ O,X\YrӉԔkԔZ01أ+IPXwA.T{9*Ϫ.R})Pxk4Z b{#抅'0ˍl D2cy&Lm'xeId6[p13oJ6(FiV|d5uRbs+%jqR`C8'LE4r :fZqN [{t$\dF# AQbP&F'²cy}i(u]0#Lxp \oYI5ǙK^#~n N?K><6LuXۤS!"JAcȘpGL%<%_55PT6g||:M.gRڧL[j hUv6ZՂ䩲k6^AnBb"z 0\ITn~Dѧno:Zl|]y40|ӊ2|Z.|&Ӱzlp'ʬ64&t98|hpgV:<]7C*F(;H,Q0#V63nVtئ2ut߽^GOG2"=JϾG4BGm+!Ŭ2x$˦1Nw vtklqFLR·J!5~Nզ#0]f;EO[sb*(U!W| |EdKq0oQTHȖ[DԌ^!fs%ǰ 3֮HsGyv}%&VjXi -.=y=(K' z2K톕j+, 1uwcI-)Tl۴W\|08r.С@G .JPtsjR$W\a%ޭ &0RgLf34 (Ŗ/)4eDD%bfM%l&+iV NAWifDMCy1t§SEG=d{QQyt(lɑk,KcwK^ 7۽)5#ȩ))wO9Z&{"ACةm{^uMbAnۈdߌ`s8 QpCܥ $fz,/~JπGlLGurNG. 69d1+P]E%gV>:X]ۛ!L1tX/蔽encF{8De6=5Ϣݰ6׷UdK~Q}qyL-175^ J?wC%]O# qkTOc}d'.\7Ƃoq:78Lm,;g3"[+֙+)-$-sy| {LVDge`af~v޽l}"!"(-i Sj3e_U+Lx zyhiڊ9B`Q("Y3ɿY>tV0b7I>VT=C ShP*,\pW(xEq)A{cҗϴ?Oi;87ɵ`xZFRTfB(XBTAh8Re2xט<;QLgz*Fz`񗩃w;TwoTh4+ PT|JTq4;B޸&|ʠ܂U:;|Qͧ.tfއGrpQV^hl}h*c([xGG@n>@3a:t2/.$wږ Unɉ S,% >>KZ3L;CI72tАvǫNaGfH.Li<mӂwh~Lv' >L:bҔo:,&ԐO Z[k#t"!N`m($$_@06PLMIz֑|gX7#!'kȻ<c &fՍlz&h H +4kDhwV$"N W:;qhzےeV&¸VVYNhR7^e:I9g7gc x=#3$H:TAʾG,ӃE_#Ikq-`Om㬒Ήגz9=Y͎sE`8ۋsf#Ie7C%GJwC)Pɵ$`ijQy|w5КrͺRʈM*M0Q>7%<I;Ն5eRcmyVǡqxϱoJl۰ɗJ` IX斊#Wp9w =qkFQE(4AYM ќb}D{0l$kڝɎBx&򸲇e0fgA s$ wW@og%|.;F=* Py/ :;юf ? HOʒ-AUIu:QfH݋f,#XLK:/=giÞ-%V8T=Z h!tQr.uI*9t>t@0CB*# L`v[C\o-'19\HvbUel65I>O #@vf1GsMV70W!(%21agbrvq.:B$,8*1HV*] ׅHv0' ٩ )r+CU=gʹrV%Nhx&4G_mGԥ{kGDj=uÞ/hƿ QPahPD$=Hqtx8t*]@]•fu:gYGGw{4bYF8h(WZgO vr#8H; }KI9<RR3286-CGWF)w U}n :Pl!d*uv-x M~< @WAw N2[&ݹdή&ëul o]Jb?nJ]&X}:r\.ߔ53ٻ]Du{]H]VW̻_"jVO4MW<;_Uʊ2ej8Mę"039v}:aRAzI`hx&Nmn" QGn#mаʂDHC%uxB.+zr7w?D |_H!!y5 eG'a~Xx>U5Hrd(}dT Ha⢐q˕4u hxE46/{8BpbJ\Dsv)>Sdtsɝj7^*;6լ "݀e=塚I:=ѬEegbobr=>0 ׾{<6Cl'xf%L# >::?v#s XDٺ@B|Y"44xf{/Gі 9{WNޤcaiVI L,;nɕ`D @ל;5ǟt!H愅pfd:г+ztt%9 _H|El?q6Q5o BFM~-S}FQ *ER2MM#&lȎYp(U .-kCd'TVoё =1 , +/8žlu%V÷># OlІ${峾:ux|Otpff(Si`lpUBЦؖ j:8ShuE mjQhfuU0R+*HQ_HwկI<{8-SOpoEN0떴m=;ޱ}@A/toAI_F1Kg!{HA 'f>E߶Zk+<)5oC$tžZ=Mrz3OK42{û0`CCd_FuI f]e<0BHAs0Sed[X2kZʚw:kg::*}H8H=zF(tUe4n&J=@Vŋ|cg-$"IҞlsn^l@*OCN,+>}X f)Y7JQw O 6#P@(sP9W/ݣV^ЈlɼTN#npS3q:x W'􀗽-@倷9<u[v9oHW ?p[I+v!]|?Rxw@, *%zd3r&b27lp1"{HjGJGg ׍U 7iҸUy3#ތ^r-WR!6Sij2m<;e s ϭ%#G% c6ŋZβ`OA~-Q_n>>ma[V4h1JŴ\Z^wzW-ڝ[cky?\ Ps@:kq<|'Va /HZZǙޔ4dHkZ}sR8rXD||v1H3%[XiSS" M ѨC8G;9g!tG: 5pʷS(n>^W+%V+2~ƆҍLL/TCDq#W bqF T'{itt>iS `f)R#l<1JyC;}cfeCAf} &72gL%y,8}X"Ly-OJT`kKKc*G+40o'V`J bXD+%D nhҽ>4L8Ij}g!@8"TSYMYr0jy77D ?מ C !'T6)k:+(iXlxmO v)G~D3kN ;8Ϙ3j ;~\aU|)^r<(z‘^&;E)cfjCʑ]/|b~_! n7A7 T۷u&F"i Er&3lc=Q+y"V5~ op\4CM ^TPzkhKjv+R[IYӶ}K"*$j*8cCFu*4s!넱 Z}O-TJ8w$X:`-WM8}:J% i= ۈcQ_T\l; ֩S:*O}Ǣ:Y_@&i-I-,k9OZs$Eu@Jًܠx)ؕ;TC,h."gBSoxΕL̺^w X6DeJ خjޟ|ߡpWn!=o[_rH4"ԗU4wbl {E`BMLڼ|.;ǡ7߳v-mD߽ɑbX< ^z8J\0K3m,^Og]H7oڈe DˆR"<9-Q"1EB#)Ni $TX%X}<<9'܃nfq^g1QH |;\fgk,W/.N~;р=lG (6^-'Ž" 5}_I =WCh@[ks>L-&c.+ȹa܊e@ு6ZUy(>} H9ʥ{&|& y<c4G`$SzeʲUאV9CLX~m5ZU? G-Ovl}R)= hcFFϢIT.LJ& 0Cg8v$tm?IZ(z(("<=Ł0=7jo8e&y,S){bA6zl ;@m.{&v=+/K{ĺ|6֨y[U-={ޠdߦsG0<(RVBu`4ljYtߒJL陵Xn{ɨJcFeav{@VX6s}mʕ rbY!w=KHt,vwmTS)9-w@ln!_usl&)`E꾂|~غ%1D#86YCN+]!4]*kVWsj+vNX=ïHu<3KwP*"4Od+m,_G_YG.f$r4yVNlvKbG?9> 9OH|T>JgNM~'w=?WqZEBwtfRD~D`1[ڲ)Ir`3`% go;LA6Ϻpf*+].5BS 8_H;@?$g&@EjЅ<Sڼ~yEN:z[,D~@nb12rrRke1.Bl$ z;(uζ7},ߝlvKf.?<Лk@Mh>7#$4s'x9k N8kߘ,7V_6м*jntqiM hsj\#K5s)Z=;wV@ͥ6aS#" v􆎉# Oͨ.T0[xN1msNpmѲ@yZc#xEãuE"т0PVgGU\j4,bQ"3}ҥ&t'z.CqqXW:/yeOey.kn.Gl9v'Z'h1F~R- .MkpLS}y{>&}(jUA#h}\pK#4U6"JnwɦH6@Z / %}.$HjzI8DX$>򜈞-='QT[a]jqnͪtV9LbBKJwHG}5ŜLx$*K@6We8WHO|1c@ 6O: )prA{9<e73X_zrC:AUF Iu* uG Oj5hI0"t"@ќRpqT `Jfz2O@J`!rU&H{[H! ,! p7/ު(?%l aޯg}f0`ֈt{׌,uv/,ƙs`?4F͞9bY]ǜdjr^/Ag3pInpV?HhO9.F7&ȡl^+Z=v}8$-j5OR[A[RD`#KGfY#t&zӬΜ 4+=`CefY *So =cx ksȷi[9I 5?\5~h9L>r%/ g`Lw/ bͰ~OP멨 [i=z(V;"{W.r2,);YV!5. :-CIvG]L'ͤ?YqyYR&g,%h~B88pȔͯw7gʦ@vH yw!$?` iG[o}H2$tM)Qz ,xߝ"SíwpzYxqSyu@/RZf"hU7 zzsL!+#x@uAn:a]eБԕf!I8i=ai[z[bh}*n a_ m&pI4J!V641Nu퇂g:fN@-zt|OB^e&']w^Ӌhl3uۓx}yNEsT%w;QBK.M~[syqaC@'_F)ʷ5hj?l0%恩"Q9-؝<UU2XPI9\ Kgp{>Wf|90pTs{F+"靯M+K&"e3 3ZLrv Dj 5gp-^&~; !-OvaؽV]7[W!-!\]>d(P"0<4_Jy`żtlB|_ЫVcҋSlJvMw +D $qAPr!|MJzC v4P ,:JYiM17fyC -nP>"ۊfxbV=B~J\L6]0mf_SQ7XWPg'|d;ga!~Zd-K_ ZiBp~naxgؠOfkhz]âJODGl0@9K;\Z ʗͮKe$i=Za D? vbʯ^cu.YR$G!V"g'4zh ]bHث.Íz²$|F`5v@s_j$dgӵz%x;n( cܹ*N^JD$$ۇh/g Y}wI~r z"NɫjD ö#x~=WjGluXAIvc>* dEI(~ORwfjuoYA"TJ$p ݀*)ʇ+dǯ :;j#̡ wL^Zg17CǛ2u#@^caT*CۜqZL];!_25@l~ _t ' cԿb[Z[|FChrg]1+7&I&mS=#rv0QK{rzYwNLRFػx 3"KZ%P`bpt֔pGh#$^t˕ B$EW| y_LZF;{Y{I@U5DeLG!c]d-q<8KNRa0vÏ)b/dK;!Ld@,Ƙ.T':awh/(P?G=߳_I ;X9O^SNڔcUc ܺ)Hz7% Zxi%k&cF" #kC@{w'/.^q.~gdrW${nvr(]u$9b@/t _:z40|Sܗ#G%V]ٞm' FFyrredtt'RoL?h:R2(V]exL$7̃{_CYx3x]Xqwy&gYU9BW?nL#Qd ӸB-ڂτX1w9y}y3Du}uJ<˝qHx RbQl~r13Oh6_o+w_IyX;8J$ ))37Ze'bizq9bȚAIKhD_,̀~1MEoM-NW`]tP$@Q{2Yā|66[NnMØ{%2\ VB(',],Uds+M v堵[YNxhи4hl%׿ZgȭT]ckL,Ehw0S6*}܃w R=tF A=nXrVsHx0G 43O6fDTCGF2)n'uw%efK b-x>0M۱x!3YjPL&[h ekP6gO€!#ǟo[筡\/"_Wԋ{krz9B?Xgu?~H`.0QCh ;foOZj`7L/s2eSCEǿ)CwzWܥ&Ւ:e _#*9KG6 #[5,Ub{rzu~sXd&Km S ]3r9.~&*O4rD ? uk|n<3Z:jaI2ϯWεYH6hжVw7Rs7YrϤ܆Ȓ\5H&v WfyPXݻ+-*QJp=>;^&O oeUE5e>#[E $syQ/@K0!a\oSg.s\SȶtvP .8>i:-ή:(8}ʐU|щQ8wLqL,el׼u/_7_T .E$^Ty+([GߝWdT1V@[R' :4ge6:?0W+ź6{i/@G Վ+V}RvϷeѴ85m)s] uSMQEK{z+8@MȤkwf|[PA2kMB#HU*3qr`6 k^uq@a^?wՓ2r ՞w#cD1u(wМ^MO7u|t>]aJ\E1(AmBpC-uHt'qAFAr}Y SET|=:"3:J ?(|$=sqGVg3F)d2m>zX/,QߥuJn;Rr_QQpL\.YW~>%5D `O|QHsRl}UrPr72ݩh-cY吝Uy^/h2Oakxm&5PfH@(:Wų߹?kOȻUFBCEaϑń)>3d ttT^& Iʈ68;iBHJi&㽍zSj[=GrRɢ<[rirW3V5刞 Z2ma %x"Q޳Q'HfX򃄌u:ވû sL[fzSbz^>8)}yQ)==jhya\ V[" t;˔g$/g4'A^'#xQ0nR $`>ssS<  vf^ǘ)Cc-Ls}8b;72Fye-&wSv;|KA57~k =zJoP}7J?DI!@k _eBQ{O] X/rxYhH^cg mEJjvѴײnrvBMY}f$|bͭÛhX6!Lt,GuGxY{DyȫDl5z ~1 O8c|E$_XAp#X0:NH@;g?-B^"ܜR%z̀'GDzP1GǠT"TٯЛw rS.tחH{?R&1w = ԪbCv#9"Wa=X,/8mb rǺk (ew[rkBN,3^yJkC3A@wJ3tUteh/,SW4kl `zVw 2{w Xɮ4p-K:AC {1C+*-Q$ ZepD@9A' fI\)ּGMcF3=lxq!moalJۀ7ޢGZjg2c_h=6ϖ_|v 5FJ] MKsĄ0"|;/݄7">f%`^xڸ l۶:P榩7%/b !sU+."|bp:(X (7|]O%1r=NQC 箊r1m(KЁ扰9`&/RbЙ$mJGIrdrWC %Z(PdkoyxRt^X];ixy'$@pZ|<yU捵):/'ܚ)?ſ#4DY] oNhQ0sMp&m^ ;TphI'?{LJWm~ DW\G{MbUq:h |Gcl 8bB\HôOH0xR_u|IFd^1h/XF[m :gcX8 s}#Rʰ|:aOO> `2 T`ge3YhKxߍ|<,V>INDjZLcx{Ir266ož{)N 7lU'>gp;GԐmFH`C.j'!="3=Џ_P.v^8U|>H]+5a\IsC]cY`1Zr'b5ߦo"=Ra%kH/1m뎂2[T́!lC^ kBPTctQ@N[9.DjIXF2x-,}P%LG~'rS~u(VUeVk2HZAkM}"iS_}$3jL{sa X}xE*^Hp;V߰]zG:`9~OSƦaZ%͵lh_7./R ]8XA\`zKo[Cjieq6+/禩{4j(46ph4rQNv'Ь'XPze)?Wκ'̴+ \pEp&}yF{<` tr57%C!юiڱd*rj$l`H{wh"'3ϔ%̐]_4S޻?Zu$\0+h&_\WdS4I=Y3w-]C' װ|* 'cɘJl֎OwVVm7>w?)_)$*nz%g^_P" 3mmMgŐEnM׎:Kt ߁Yf^mHLݲqx}vhtL*֤L/2m_oի. 5˫(+]-fnEP{‚;Ҟ\o!`qidC0Zy<~d²#Bل5UTL5P\,ECI?@t `t5^T^sG!pVc& 9'#(2]iRiA>Yp9y,K#el~1]3;e:m&<)]8œb(kMv?fdʨ]e2+q NJ~'D" C"$]rJ[zT׶E/IJ [%Dӗ"zl2j+QeׁY. ש"ٯ'd&ob]1z*11<')ҋٶNI᫐ ưP^?!){ jJ4YC9kA(hZ# RLa$=-Z&o4Mny֪TKג3'KTX^}˖E8BYZs@VS!b Mf^2n+L@~PE@p bb LZB73~Ք8. `5l Z3șnnGR_jzhvN{ӒV]fa1/##Mi~x`UmjM+UfbpǬk犾55{(VfrJ#ڛѮA]NoC(bQ !3;,,c0S|i"fE,tp3!qN<6QB. $Y`Cf.% FX8kB|MImìON4N&&Amj$Y} ȭqmTDs֏R.e5$H_O_2Ɠk]?,Ag[rtqpى^]\x7ʉ…BY $ hiFEs r|ϘDTy]fsOf ө)" < @6ejo9ธxD~|ke IMDR4 Mw%79,+uz]K ʧNr|tF7)`L }-hDj!xQF){^X6?=m>!ˆ㼐l`VҠ0{x¿vg++"segyu;e4Ogq_!v|MM𿧡̀8:<{)xzDΉhFz9yx>V6*'ݯg+D0LLWCg^e_´!+-ƬߩۀA.tP܈3Rܹ,g2B/dSeh>շflnۺ׌z/Γ$W jX5#r}TxdJM9.*s@'՗wD7דu: ܆Q 0I&Gcn~EW/"DaC!z.%=>tj2-PEa,\Prr :?ƲǂN4{"Wcw q{2!0a˴У'NJS i+>#Yw@NRܣ/,n1~W=oYOݼA%u448@Ř>qZ^;%iDGՁԘf.KRǺyAAp:8qp g͐auKG[}.g,nhV{+$Gm̅3SCѺIDr q\6(IKsZ #HN }<ݖa#_kPI%`Mt6I؄'l˖a->E>rBAWt""qI&u"[ a2Č#ROn$0bu˔28IxF㓸m٠iPcLz=dxݘdRw&n:>g&&7^i$Uv zjvV0:Cv>Y:Ʋ ,s0 ֌2 ( 3vBw낄1)u/^t|g8MUa 9вUOݖ~DߛԍT8@y:[gVɩ]زE\`O"WфV=ӎRE4JvuEXpT)@(ĴߚFY-(`XB5Я]vA@DgPv/Ovq!5qm]N¶x4(LC3c|k%fpNWes؅Ve*r='I^׻ǹz8DtblQ㔉ۤg| k @n:i0u>k|}{8>xƿH:ͭf9av6Jy\,MYVᵩfQM <^);?i.3zO^o)PA(._R)zkퟘ:quQnm(]ز,6[c DZ}^p= p`#JiuŔ$&E-]g%P̣ثk 4WoȒ+ߦA;+hB!s#|և?Cܭu ˟"h>'%riD|n%o9K+ֶWު5~惌boubX-/H/K=Q|g%{5>Xom01Lk;(KK Q%o4mE }Eq/d}\L6s(0a5 +cXcC7DZ1Z̙8`m~y7S{sew4; ܂YyQTKMz=` DZ.*ӹ@~**$WGZzu^uIJc]*ʹ Pz(e({fd *WWCv<^@k!p̫/{Pa;/ @}UJjؙykmnvTxx=C.??DUX֊8G+ sKe@HIK1 ݉VܡR ASF 8ζ>SDmP^h?`r:j{_ܹ-dzd- O6"Q)1!T2Eר= paVS)S@| A@mGM_Mlhʑ+ %vlY]fB9d}d%8n}{&J׬(`xa)Z\5uv#ڨݛp3ʛۑEL8o msV,X2N*S2#'7N..[sJ}fEihTƲDaOYK5䫱ENwsAID]D&^g7Z8L AUPÞ.^ Ap=I:rffB^F1u~_jSži4' T&y-^Tpd^iirȀV^W[_ҎZHHMz Jg CnFܖǃm6`t5˿SgMI>m'w|#r3f-8nVڛ %},=ױ5sbg9Ǎv FGB(A|RJ?djw,jz~`AT9 I4,lxr2~VpS$ #^DHMX%Ha+Eޑ_2KJxS:zrwn@v^l錱7J4=z򼋳#ͨCdt 301鏃ֳ8C佐aKGG<9A8_h?uߵlų8`:;д3<#{SLq=@} eԙh38wXH!9f?^MAD\yUOnAƊװV༜ܮ!*ىJ@SS+蕵 Uw2=Dzey*$2wz+5&9O8 1C _xʷ Г@4Gure*ɖ$+df`5Ҝ2(ay\ػ_8%GPpB:á?r9` 5HPbygF7q|Lt:*PG=&/׵!xT3%Kg;:'[X;9t??z5(= fOER}28(a]ix;W_nYmo5N;b'!}57C&50̛4l7,%0rKC(u,xYI\w&qN ⭑< ~ɗc'gKՌ!latE"T‚S)Z3EP`Ea%3rtE<ޑZE[L R\u73dkNZ8$]webÎ3w39@5٧+@km'ې%@ܙ&Ƃ]]NC ?(|M(TUZi93y*f gO&NV{@!],Aώqn!f fBĐaQ }X# j#YĒ&?*Gd7Ci_6\o50 C)Y'U-\Đs* ;]Zv"^?gD=`=Kʒw;6Vﴚdh}́\9}Q82As?^|]K+(_~+]C| zϬE W ;*`0qڝ8^D|q`h +!stҡ1;VD3 k8n!oy$>ρ_1(S6$3alz.n"+tJYȁY}B8{,w#ɦQ(R+ƎK1uLQ?ߔҩwO"箍RAbd;Dm4m_t]yyDH&'& Xhp3~"sP(ؠ 4=bcI]-wx.)5lJI#L;Ү"-qDX~{CvZ */u_II[<#ffl>0iqvx]w8tI[` muGAC_;`B8Mo܁Thۘגvxw̙[`NyHlXm̢?t#]8T&O;L^$R2GKl߶7{Ȕe[%.;¥XpA {5?|J B@ð2 b_?rUu $3FژlrAܩ+;,!0ÓNJh)dQ] ECaB斚(DוalbiiD[I82ؽR7cӼX붘>rtۘj4,&JeuuVKjg}Ϊ~T3yGXɅ,ԉiwgQsJ} 2 mא{3|uomOIm8/<\bNS/Hp0 2".9?f %(GHEEܳa$w0߉") >cߋirw3&*Ul^׹ HqLÐt5cj @JpXmv-t_rbc5d Ny1 WaMoq 'd !P_(JD==UL`#URTU)4T+n* K^?J̿|mnd1i+^a@ˆ( C\}sp9,p)' u%iy!qyjV֧K>#~lC&ЀdEWDfђ4h݆? pgX2ɶA л$c'xaˤscws]PgdنqJ'3b4Q#xeh ֙2rmsurt.o6eUpI?$_F󧏤5@D}CCF svuoxY#_ݘ]!rΜ2qΓle(%t-B|Z\=kb<77yY;DT'ij bP3hrwAE8:,y"#Bߐbs޼I,e Y3hu+R;35fE *-&u1C64|?;t c!_x;o ,l4pa_%-MICX#d >:2MO.+҅U\p)v~+=*^ \_KŸ/ CYWL*j eiKqus !{ S殌M.5f[VM\_ѯ֤ܫcYx Z&ᙏmFΜH\Wwy>F,~q+&ĉߣb6p2UI1`4(ݧ̉T:"LWZCЀӖxMck'bނd ۲s$$^:gmԛin6$!kMxf(w@+DBU/U"oцhcYT)_`,e[N667QnI0yn6|5WY{$Yb1ns"S̼ɉֲٍuASKWm8*ή= ;~P+LkJA^4M~'nr{mL.j枞1weGq:_x_V`k=%L BWMe!c,wxsUiil~{XBz\xh5w7h|շBs*1py^u]؂zU Fw*D'nWxZ[>rV,f㖧u&GL\3|J:CL ݎT:YI 2ONguj%M{>'q=s|ؖwswƵ6dX,]NA)eGY0F;#9 3v5sƦ F;/+{.?`72QJTLs4*Ox ,󖼻.Lyl2z!OXbꐍE-}յ' `S2O3ݯ3?C,xo`~Œ 8 mt(duFCl!dMuDew?~t/.'faXoőM:˹AOXa X/1Uid;bϻ8eH| h՜\{|gggw:pH%{FfZjFZD#INNzcJ-l"mcVyGw "&T-9SIHdA$Mk>$ ርjǂUK/({b]4H!}8jdlTƬ93(c5Yrg"{Iw&U:gH"PA þTt2-g ^;mH d`g#c8uP HhcYb4`!A.'Wzb뙢]gg76+m8\ wE#F 0 U=A0|ۇʡ-.&,UvV>q[J]tkźߓspC ^Φ&~Py/q,M `mhGؘ5kCvz&iLҍTP'YLjX{urRF'jYTl=Lm,ʶ%L5b@Qf7*C#ެ8W!fЬ E„{=Egs NMNPv.oO .p77{s[B9`>ok݉ʃ6ބBw@ָ#GHQMR|Xt^9Uir QKU SJf~x+$K$lw@|Uh,.#~M }xhl%TVgǦVηS[uiUk"9~`-JgiNSEBC֚ ?CJ=A.6;Q8l\9< Xvj2bc7Ф9(Fimp ]PЏB˄sҞVp bK#p+Tsl 1!4 ;)ˎB~|l8 *T͏{.9 vC3zkKxF %AY9~@Ma^xy>ܶ_6hڥf~`gc]̂Мx7*lnu9CH<ϊ: iC>;ϰϵ@W__B4vWfd Vi1F!ӫh͑.IS?iksv&:n:$s.}- <ᄶs}Wo@fH^4УµpѢQb'!>R^Po4R.~q0sh'+R~ꛑgwzR;5 [hzNrsgkLkmz=ͨ2r-alTmVu{-,1\u\8'1ETRwi^P#]L/?I0p3DNE6a_SFHbqq^)Hַΐ}tXU3Cq/^r7Jsim'fX0";CUp4zO{G{:v^tY0sMlUaJʊ^=9/6hIgU=Iѡt,i.p[YgJph-)KAd%Qn;ah!B9uxAfȽSN^<Q5k^c]yY2F*B2U[ c93̝: Rd쪩U.\ (6+Fe:}fnjQ`|F&]3 9"f?s(ѵl ~yp=mn#wU_I{)zN t-Mgp^ZtBfUϛmZ_ |YV m\˺P[_9Q~V.RW%(6ZHA綽`U72iφqiީVV)tyJyxhTKz =(͹Ƚ#Mkd^ >w3ln-2J[7 R+xx#dP`2 ?Ki+e =D&Pxw!Vt 9{?k{,K:Yf3OD"d}(J~5, k䧽E[|tvl.*aYqN\l Ymsd#x]k/E'RTM;XݿQiBOGxR-šЎy'NvE-)O˹+t,h-*6f})32vR/!(f-]M P1d  ,'}-F-`jo$@NI3nӡn91*6c*ǠZM`h&Y, Mr Znr:hjCș1Mƕ4ن']ɏ-z6g0m~>'K|9rM!@% U#:elŊs#qZVɺIi]舧"ڽ_Ɩ0ľ5^=R ׄ>F6ڶ b ]ef[nTL3a_:[>SS;Mc&XEMwMҾ p>ki#dPR4p5U]MxQaJZKG++8UjڼKhM=*R vMuY=Q;TI!vzl5yL1~4Ϋ #&A qPuK[9P;#Y!$EϤ/NvŔXPʡ'DUUcX&4t5hrp}I8ͺ0Rq~,{Y|q-V nWp,e+\P5׬ wSal͌IsfV5Bf` '}NxǙHB6_dBLM=RkGjIfwFg}-g]w󑓎}v^Rh>os1dT3}l ;v y&X{OX?T?|o"3殍gTɡ2 jOi1o@Ⱥ1^}[LNU2'4ӝgD6^@cG@lf3z FwIn]J-B"?G=E.6&ie$f|Ѡ|Pt"65fj^9&L .Xݶ+^g*hxgg!αț$Wzk>::gqf% ":MrO6t8Ei-')~+*.N{=ȭn22ϾVL+Z`񕏙4Eb,Uy'eva7#t[[];(hK\듌% 'w{kgxCdybăy/"yvot{L*rSEYH u, ~^^!ߏL*lR!+F}PiM>;&~l, #]Mwj/O7wa鬣O\O\_QMnMtX.̙@^hrj7)f/K(Yp768 9Ǡ.} ?~1>Ê AczV<޼‰bfmlʞQL̕v C,`|ƘiTAju)n^#\df;núûPVYs|۟uzܳ NYLfԱDAS;sSƥ :9is* @]8ED-;qX^UoBs~Hv gzWq>*^|+.Ju@m0)X]+ JBA'p~6QC8v:W_n $9응2Yu[_2]Hv@7'Kb>wsWh|GtSh?XuMKz ߎf[QXVTX.1j\Q:ƣ3i u8 S!1ɴQ:F֒z. [қɯICs ֕{@U.i^~*mȴݔI^ɨ'I#c*3#=m<{6awMyyfȋǡRDF 50#ЫwO1d4F<'8~*Ebq/Vza'Y ¥70Η15E}baFvsHIN/LqY6J(?!g3ܿ`7Ϝ] 91yxz) ^b!2ZC8ºI9'?ï{tf-UϽu$>;^[yQ'_}8 "SDrѷ6$rd 7QP jr(U979?Ҧ/p7gjU1k{609 4T?u}w,jr'97,9}Lg1P"6OkwT3+ Nrԡסrm4ek|Ls֚ё)7rGڅHx"e>nG{BY,xʤ]IJuP >ŊlomNrFYDW!jǝpks$b%^2%]X~Mߦ6-UX~9~nc:QV/L70uTC*ـ#Q?N곟P Uuðil%J`>p1xjxб Epѐ:d+FG+Q\uύ t,}0XADPxvy '[.#g!j/OhAf)Qhlv[WB7W./sT5&6\9Ff[博ݕ*Q̞>t: e^3]'U?ܝu2'I.pj @{ީGQRJVtH}t,2 QN8*+pu1Ճ$6RR X Pd3JZijh@gov4AxY?ҽLKSbHR˃- (:}a :uevkXKp_|0${}Gm# b]noJ88L4h$f) _s։M0qN<2DȨZ,NwQW z87)KAƊE/ۅ@H)@b#sߢYY4i TԲ}hy(Ql׉C yvaIJft̛34fZ%ps7p~xv}<Ӎ&nu {/Y+kK\ բBIߙw#VC\ %f$"!9қ kRo/&Zt|i߅j u+d{↧4(us7RZlZ8 Zjhg5S,iˬЊ93h@Ί7,; 8!~!k+MeꢉNx ҅}zkz<@hw,uI>dsй>:}QtWpH%Uf0H*fb9PMNn mL`<+F[E-M d|QO3^]FQ6e%GM? 0'ə۩ػ~rZֿ`00p`4LPMyz!hq_#"_h,WWi vE? gR JP9-k#J`ka̓SA%j< L{W͙˧%~i $#|uO|,'_{As~<U wx+pPppa<7l/f%v[m>XH[vJtBsbWiu>184r^w iŞl:?3D@uuO΂4ѝk$j(.gS\Js{)ʹ,p=h,QF0lU-N)H i>yL\/\omDGF7\fYWiƬz/9=rףsMv|QN|B3to~e{Z,6/M䣍(z`8#?%-vܘ|лN&˂"No+D!8˜-hM6F2 BtWX ;iŠ}Z\d))ͽ2KSC$綄}x1N>f(RaV)jT3؏hֈP ގƖ%S]*=@xsXjw%s}]߻UV(k0ZwSG益^6sl%0HTқ!?׆f^:s8iY(1dGc2H/Д bx(*zM*Q }Y`ސ4")Ohe @.^t)jpx^ 汵 ݐa\^H&a%*ÅY]Df봘yBvi)S˲)Qlref۰M)S4ZYM1:L-xzO-b-(PVRZ;v  |/t)"q.O e P0#*GqYwf<>qdT*K-?Ф<{+AFGׇ_8YES8 `_}ꥄ4_Sx>b0~Zp&<Ǐp[}>30Lyɬr<VIZm&7<1@X!y@ Tl4l&c1Z8@m48JAŔS~!~P,s.R!Xw¡&[MY}G7pZ6;7Q|]om5+ {E_qAjs豭,M&PVO(^&E=wN*/k3W,+r#Ag8#j=i/|n< tLPurl/s_dp==|oo{q\i_q"^Bl2 +tןC lSKRJ֫]A<,u9_$F+̓ه)OϨn\+Φ?nJ1pBkUhxԐo8i.* Y)̙0`ݿ;»H $Fo{Ua1LbWt Pȫi31@݅9lDf#]HL>6?5kO_&e_0]Mk{oZ2 Ϲu ZmE>A>/JPBdsHXϴs 1DŽog?ےI~itҺHJX]mn5G='ؘo(XB` ҃g°!Zx@CQ'ɯ";k`sGW IWKg ؟B;[MWks ҆7S EVfϮhQjE` !q]*E?5 ROh@&!k܉_Jw}_ik4eoDle 'N +ć#$Νu.^$`D8HQT/KOZ6rDRqF74";TAe=v/]oϾSd1}mY:-{mg== v"НmHFmçYSv9{/ Cv-vb  0rR3]*#t?Z2j{*@Ԙc NW]Rۥ| 5rm_(FhBLajaM bQuhQXQzmfwzhy%:&o榰 WGAJ x22eN JbH ߂zIIa7An ԃYY[2f))N%\ ŖFع3W9$ oZO0iF *vGE|vjWـ!S"*|Dp yOcYR[ʿݖ D,`Bzv.s0 >[Ʊp0.|]36gݖ9!ǵ;#K+)%x5B[m~̤XX EsVkQ *0w\04%!+?AF&өjKtQQvW="+~ K]F2߷/fNTq; x9]?w5Snwg4NxyoOC;΍z*[*e)Vo2Yug'M{;'t5c|+*w\,vHM[OO0ep5ċN$v-ޜ=PHS wnT᰾fe)V}'|C̩M&{YUmQXǴ!;97w=Q|j]wJ$E+^ o[ |z7ߥvH\izbRNKf_-5$˺`3M^AбCs9q1fM:'3W ~̩6gITH3g;8g?eP_y[?6 h#[| oqՃx,k?©d AγVNf~/Sx}n! U?_Ҳ\nz,74yґ2[]?OޭD|s%7SA|P$f^gG2W&j-(I˙!5okȎ[Ȑ)S+E!"q>.+9V_C۩1N=UDٕ:RʽF**ɺI!j6'C yC}Oى^o*ia=ٗ '<ǻ)Tҗt} p5¹`Uk-4' ~)af4T4P~%xѰ4.}N>%U%qoOֆ þa;* 3y(~<ܻgOo\:2rc9ѫɣEB;zBգ,T.=3G9JZw2%}q\`C=z{u ²w~7/t2J%⅀?'S7V=42U[5 #u5.s?BX/F.T7Y].HALtͩӄCvR$ 3%!$l2DQFu80d-Hbbù] d BCM]鿇UO͏C1ʭUCu)6מw<P[2y< 6ԕX5c3 Tn}27j&˄2+LՎcݙjY׳jмK=ZD~XsPEC;)SY݆LVkp~lxiLվ}*cղE6BW՚g] rH/J>Yj|I*pJ˜wON*1(+.8wz6mbh[}w-D 2\o݀MVݼ@)fI$y * A0RY^p:O+ek*l/M{m1tpW@:Pb7#ɍPz+ {7LBvQsus|†Q!JG )P/ xUXa)˥Yc}qE/=(O缙\Fx p+%=LȑQ|_%%[/_:)z#Uouty:saOp!}V\]c(Y` )p%LC-SU|L3ې넓٣Hk0ψޅi~Е`m +M:K.zZjE2?)HE4\lºe"y^dWg5(~dՕO+F~$tq.1q%w !.w~zPe# r c_dI~:?LBIK:Śc3,EFŸ[oXSYom6%Uz[=ޖ;Gf.LZ1T ,0IF/%@+иG8k) cϵՆR+]& SOrS8IFD`GҨ'v^bi[%~)Gr[I:R\z 1me GQ3NR]-E׎:uw-R*GBOڻ G6DZiŰk;/ AUh7IsBYt ZVEb?gks+C,t/H| ^$Y%aYkVՍ.ص̝v)eD*&'CPtl(UgaW{E(9%>$TRن:9?+qH`m>'xz't{xʟo;) '[#V*@T$v}+:0 Ƨ p1,KBˆbn0#x>4rĥX?*v(QIfZI povlnxլ2,冝! LUy16n LEX8<?xx/ bSMf fsI@]Lj%ᥤIYCA>I.!5L`y RڷU@r1^|0kik{}vs/{6ӣ.R, &0w`>b%dDNkZT+́+h >iɎkUZoVq^ Jٰ*A\}0WͣKϋv;ju[G7KٺJs4NC']kw|4@NjZGR:c6^;!]Y@Jx(g*ƝSnLg^ !@]':\^ ҅:N9f9=KfZ\ h7ɳL86p>V&1J~4ܑWo_aȇ]t˅7;*{gS l$ACQg0?YI]1RGKt9jZ&Ý̷qO4Lئ ]of[]99_)"x1@>N;~-Y@i(n5=M- 琎d0.ZGu;e;/O%8uH\垬WoiGퟌLT7ݙvhIel.zM`s(:٧>QyDLDyt=5@42W0,J7;-Z歏eȡJý څ)& 9;tձiߐ>P8S542Rp7ߴBPz5l=TpփzRwM̃ 80hG,;ڃHݢvŠvT?|`a{%yn_4W=ME2׶ZQAc+2?c^&=RjA6Hhx' LG rO,i_3b4嚋na4R@s (8_EmC*U/` h. 9`{+/=)z@5H4t}FTuX%y5"Ќtus_y&pݙ5)@#Nqnb'Y6e FThM\tGѦ%SXj'h׏zIakp(T}M(2Y}FtD# $ jR,Ϲ e!|\dm- L=+\`e`y ~FzlO߾oE7V=J88ֶlRΰ/(w)սYڊ9|6m%.{_|=>9*f(jM_zGwsN 'nWY-CÔ{e8UMfZ߀◐a*}^ZYEeíIKv$C6[SkM{043F ˽_dLj<&7@-rc(;^OB0ԿTل]* ;aJ`=J<%`sAL#<6RpyidƆ@mJ5,C7#bH7Ws7kO;JYAF4R.Oxޡfd%5+ *ٯ sAk"eTϥs:eSPgl:|>z| . $َ[tx*0F;WllUbc³?r֎dY=k`k.D^"~G+C!ȕ0S@aVEwGɓ;Vr8,vMB'u0^+<.~dYHC aB P1[3^r?G4Jy*~n6g9+5[Q$n3l2[P ׵d6%yl.89,~yd2KUr[ӄ[2@|Wٌ{5!&j*wǺWr8g҇uKc';u%$HCǘj}&g΁m0tS[I~|1 ݕO΋8c%HKg!pq$,F~#Հ|Kێ2lT8Kt. FOy$[ E\^|lZ+\Hz:F Aރ̼=VQŎ (Gܬ9~ίOLdŕ%wp9ii^(7NjIO c9./KFGtXy0)XL5͒cwd iQT(0 #ޮ1Q^/d\> nЌ 8<˳ )mP_m)rzwZ$y<\+Vo;ŏj훙YX~z 7.CVFFeǨ-_]ޑ{}.]|SEZ)TM/FPҁW7+X]N~Wv[?Nv/~4}u+Լv^k>*Is$[aamGS@=qU{o|*LuW]elCr`US&$hj-0ԥ&cTb[W}G6[Pb+su7+?q?쿀Ǧq Y+;Xp\]$&,e^sǣMlSSh%66ޖC VV] A5+-%E0!]X R1h*dtPI:8;L/:߷O6A^E1􅖔]'.FЧG 9[_mLO8G%EZH}s|p!|݂S!$Yo^ zWɪ_A .}3gzaaHQq{:W"‹8EC{М)>c-pKY7Vy!݀JiH"KgmN/ky̓wbWF H%mP716ސ=@qv)PI(ḩ'FS/jF&^PXg}^ArN5@%uj3  \HQlB~[o,3V|`dGqM@ns,'̔ ]LT 8s.Ȃ @kxFJH?C]%J~qCViZwW#٥㉐PN).UDͦaDDo.88UТf>HP?SnHsriOc᷆l"O,!H5]@7?xanA=:h!LbMCQr.)Yڶ1| 1I:z/^}pAI߰rԡ٥2!' zx\@-f.. "TJn@+ τ5sZ6JF{^gCN6QȤڙ؊p BRf[q5=ŜvZME3ɭlWOr؝푢Dy67"9ǢU^6TD/_rXor "1̳U*@O_-u. AOB K/CZ弸*^uE w< F׽=DVnWf,Co^%m|($+@X  F/lz@m+7Dl5UCakgȴSfY%eFCh^wb;Y%G:aw*QI>{=Lj'}H)+FmB0|N"='Ct[k^s镋,2?m)P_d,ϣ'Ns=:%? a)l3S㈍EbFòUe=r}Ovwg }k=hXbTC,"y `EŧW&-D"F?I}o?iDS SgԼm>7-beo>k#&7$DԆ y0§rQ3d5!'(6)(F́}\ybeCA5NP a}х +vV8ki T7,`97lkGfEHcL!kJwȳVēW駚 !K; / $r e:c 㿙FQQ=Gvn{N`W ٢NQMo3 ֻH_7L[AE5)!Fx̾w Bfޖt^84ء3w7ܤ&_EY /jGC!0+A"\ba=Q UƘ-CsU)"/rdGh_vۢq1C|ٷ(ܦ/oYzO9)?8 M&09l bm fhR ˯/) 8ɠ/ٱT~oO9(iPR券/+CbD=w>G1kvi=Qu4ꚾeWV5 SwgX{z?#ٗqmM5KT>϶OP%6R $>-##]aG W58[ n8؍a7X]Re]m#3_Ť~(6jc+8A:$|BxRkD?PW?;>jtYz2\ѲyDk^~[]MyG|J A3F;Iq+hDBjטW8N%M.ѕ/&< D~gO 7vv^X_j}_QX8g:R&FI SoMP3&G"-Ӓ}1UwG [:D,JpK5ËHb"h#?tME;U(Uk<#bKlȞ`geKixm6ˠĽ&q4|L a-P<^yρ ?8^A0ѳp4¤/O31ySFjlr pAc3-vy]bV2);PVg>Z6}+ӟt۳ Wr֟}<,m-(,k[+O5V=dHWelÏ^Byc/A/> #ڟΪS8az#c~FWq\6qyfh$7jZVߎ'3gg:g.-x$[yNSp)oF4[,aZ󃥟zpPD\mʂUZZ#o+^`MK)!/+ڥńa22wSsw3k-M2kv<#vkT(\>T8FSnEuj2(*Pl-Lxv@d_nyB0.§IZgZSTҏZdP 4Vqehler(21xW oe#C \-,Bsj!n3̻$žllm޺ {QɊ~DҨ {sہ77tDW%=)k% k$ud<ȁ}"߫M`DJbZHqu)8LU}tCt8p4HD[7h{ Z(C`n5Sqykn=9Ѵwc~|AR .Gc|KI^bI}gq :f;BK*= h2EX#k—0u\+2 8 /*~'+#S shg!7dAE藟TƶZ۔yghgؙ ʓIBXR+?'EH2Že}-C&\TpDF~d p*Rt`6r6b "^AdykNR]īDZsŗDX1$UpZzfY4]g^#!JS'MU *lp 9TyR*, 冃0#uy&,ƌ(ɍ8DG/bӻT($~QD26XFR T1$[ T*7"&1[KA$r Qp ( S䧛4(ZTH3$gJϤyG b߬āһMiwk7 2܇yXfh'lC(CbX&,4zO<:j?uBwlk UufZU~m%#.hM>~m? H!Xnm5ezdg}gZrI@tW:hV.l6PpVԍt}%BrCurKƒu]`ԏD`oem &@pAF&(E<0DwE aROMO̚A*1'у-Ek/^0XZo}At>ZB(ls Mo(fv?E " ~EIX-j,.P-+_®ˆ(9h8*-V`vW_:QV77t8ݬ0;v$z@$@Gǂzb|g۸DE} _z)ڜGgЩ|`ʪ80>SJ@~~ײP i̱zBqPU2kUG-SOԻwZVZS#YZ>Z5Ԡ֩}vis<:FvR`\ҟS7eDfup!g=i9я H[=J6a9!&x,Q^$=eHEꊻ_4t;\s?Pb yq 5Cªl$T( Iyg4M|Ykc V;wQҁnSE.4$sV~:M;w/I#S~@fTsTJu_i0]|=ˊB;WحtO UAE.sȑ9r5 UVߛnan*uÌ`Πd"ipD:_86 N80}g0Hfnr ~ܰ."vgy咲.)m'zSEYDyTY-%@ c'+U 2t\'])xOj-烮mWLq\~3Rh-6J@eQ@R66_8~bq6%I}HIsb,>AXc?ǂS0$*/󱗛76cW5+ haV)xrK=M^Wk.|})>:R {= 6uLFcI)מ PG,JȔ\[ KC =ђ"M<&=@o‡!4;MZJ]]^hEf+ Md$",Bk(S ۸eX[&LO<7=,)[J8%f*Z3)LWXU`?6n+7>Iǚ'jZc \L>CY kK7cŁ:"%b1J[ӳIQ/:6cD_rf$ˉItpC"W}Kbmy&!~\X`n;Ω5.DF-EM. 4|߉ٴ:Gy؏f2vҤfF8Anʙ7#jt_/J ꗐi8t@0Omb9[B! tj8ӗt-> Eê'Us{9?UQ^މZ_/}r9\%ԇ4٫Al6@7w^TPeDO\Փeec|W9{k $?cX8P]X ,Th%|*ٴ.5[;knO\PB{HZjG#YK^jQ><,xG[6@d2h a>'fT&=R*q[ȊIGe@Q,#Wm̄#xR*P*wF=!,_F aO}-dnWn!|WöAsj)E7\KKiPz> % \*w)t,=dy"KI۽fSIh!S~ 2W\Uml{ǗOeI"MIKfDi5 7 |Z@mibewdMQ<~@3wq)1~=9A@063R҆I}G*VΦi3㜈,J[N\JgzURO!&NLNTTtΥ4 Dͼ'C<Ns",$шEkRQ[|: \u)Lԛn!XJ<3 Z@\[$w>^SMk֥1yw FǩSH:/gK­Olf 1.dw -NϪ^H)CZ%H\gų$Y83I_j[~ɱHRHȑ-.GMG+qAF,s^l0@Uy=.R9gQ4JR6'_sNF,C`Gm`u,Ϯ(1>(8d|[~"[EXl="?D)ahksX_zvP]A{uT2{BIk#dk:6d\w/Oo&2D~xG yAjd%Q[m,JY'!#б O\L}12V9B:}`j[k9[IPKTOT]*sը 217v8u++O*x?Jf} )\a{)}OQ1yȉ2W!۵_?wdïm[願-8 ih!y!_3ݿ:JKYQ"nG+F"?Ap"zjkb5 vP /"+ⷴl{!$_YxcS k\IN&ZQ[ѯO*'Z`8jިzH{=(jʈ\nCEBDԟlJե,ii˂[8$| G[TRV|V9(27 Ur:ϩ=Nk{,3- IQ&rzFK0N]*tѰ6{tJPj\ggQ/&6~)7\@]LnfwQ+:>[QEhgÿ8tiqx%?ˠ!gnsT)'2])Ao%]mL2mkGx|rN^mv¸{&y=e G㘺t|{ phb%ƏbvG:luS◕y=PVTΈ9B,.&n兂~eYWnˀӾnS/;@֮+D}~N$I^7qL_^mη׹E1F.Y{+U/  Fh6`<uWx'!pďJPͤ,}܄wI?kx*m݁L9XWb!UR(T^V)zJp8|1sړ0(s:L|Ku8 bwLKW?K c? p93u>7OQrj _:Kl#咶هR9K#Ĩ^ դaPRRUUogH3ݭ[4r*>\hM%u(Ed,lT^&RV7e%#`v"̏:ͧs4ǒEI)\:7&ɻEiv4%>IwM'صVya?N J̨ הѝ.gCV:$ 쫉7eua_H" +ٺd"4=|.(ZyOvf:A`,00Pĺj*ګMIǴu#.3TL?b[-Zpn!f0Fu? X.?uh"xC񎇆c [A=)h°1?lsĪ⤝d39vQ'h=A;  JD,˂x9ҿ} r!| Y+" y$21J~==[:!ڟʙ$lD裟&M-,p]SkxCXCku$HMflV7/Ԗ7|,*1$<)bxrv􂻸;9Z'UmDv4$(mPf'XCcs'{QF-R1uytEZT[HԷebU-- h¿#3X --a :[8DH߫&[ ŞP'5Cj`LrE-r]PgWsDtRKFƏ3jG(iʄ}MĠQ @/W%>},Ğ+veőlh!*YL.6x!(~\ZfdZn6U`DON9(7B7 jZ,TOka:ӹ:[RWaR3~īZ m]/B}dE= /36,X׸KA|Azr IwFWlTIוH-;4g y@"5`С홟^lR*@CYMlʙ߾@go9֍2UonmJS2"Rn*ԙ ;n^_.̞D܎4a̦ ,aqQ^4KI& )0-]{ q7Q>`o4 pNx ;Tɒ {tl#]bPPWp5`nM2J?}hΖF#V^+eqşɇfm ͖aþH/(Y2g__Ї4;j$$8%XtϞܐy) >)>bܠmPwf Hk[ʿSamz͈ K rg[4@ ϟgR7z[=n%`pu0QMPo%vk6~Eyӎs8ƀg'x, >ϧ[V̷UXKᎸΫVJQ<*a]@Nk8Ǽ_-XxYmnѢ| 9U}`x~dΣ ͕?I]3qE\@qWYLU+v/3qR^!O[Jvwwpp@&wo,:Sok9"~qI <5q ~4bZؙ}?1AT궁{ hi߯(eI_mRSYp vkE6E&[MjЩe;e\Nײc0fD?m51`Wrg4Y2ʛ#ʴ8|ߏCasяg-&i9[uB)Wt|\îJ >WXvu'sÉ"g̳wDM_.D"ߘh#|f_ MCUŹa X7\u5ZFa[P_T J NԌj2H]rXTd]?NׅI1C,3R& ]95:C^vr ձ% #CE~T=yd\™=gm 3B3$S `aɆtv&b}V7>!$裂{_kĢNg+9u誔 z/zx?X3U,UU>nWSߌ`v5|W燓 bmVˢ}s+RUlƯ4,ۤ`=-g_gNZK]O;eP![U`*z-$M*`O˚(hJJІт@v'ƽ5*;hyޙ ̮d rߺe:# 9%y)YE96N MRzuO \4!Rj]FlÃvlgp-MՐRYGkΫ^rC+wF@x_(N򂮀YӈTP`]_9λhJbcKfF gXO#G#fŌ?hLM\GuңΔP@ӫF#E]q`׋TEXo0ghƑF]ސؒuQk@`DwWD:3܇p|LgVlI* JΆ]d2)Ff@Ux0-;,wG_l$>~`3dȀK'EpM!y_Fpz$ؖsls?"&C 1 QfүK|6ul]a `УeK1x3]sVǞf0Ő ƱCִYM "fP7})?,q*[^3q?] py>7H-oQӀ(QoWT<4׫WʉuT˰eDDh? C<;Rgj<"Ykt5C~񥗳*/ٸ`͚$H|: 8:)>nyv`F:gQNis|?IFҁD|qK^Z#ѣDžgLtW`$Hh8]Å&K"AQIAD~ \ +[r8CLPdxPYj<繤V /sdu@3/&vO(v/{~\[9'Zvg2CC%JЇ=jy; .6bQ {W' #s:~E`dZ7m/)yzz _fu3)f_ L%yW[/$+<>E2AHr}MjGφlgALrudc0ZL.xZMpu;wA9\o d$C96߹<|͍Urw1Fm@lD5&w.n,ɡQX_Ԯ|1=N VZ߿Le_iڇAaZZ=ڼȑ2lCUq!Jz1Vv52IkO0`dK4na_$ =lej69#)\w] xnZtDr h0m)"Q~%%%¤SQ ̝$:6?sV4SNp׮d@'mF|\GAvTOY 8粍t+^#]HsO#0UÓ5;h%,}=؃5O}ёKO ,BBbO6, zrN\ DuCƘО#x85@Dp$w#n^\Dl:-RQ9/=R.p5BoX]ODA,ǟn43+i{ H;wo%d_tGOԅBa&Gj.Y[jG*#lm7l[Yop+pkI izMkE>'i՚%*2J\*a`cnJ~LhP]62*"@};GEٽٯe*{w L%W}V'ec=i ,py.5&Ƴ Y)If¦AM6215ruc8lSyԅo e" slM-~ߘ#AD [cX 4k2m\T.Gfp u8Oax=ׅ﹎VԗN>R5~h3s3DF%aGL>[}|Oj VY_BV-Ro/e^ᇣJZ;v:t лl$*A.zX*dzveNZO7[beCH{`H j㨇&^]mɆvm}?@?F f Y%]%cc&GQ!QN[D,L^%oz Ϯk A<&Th_MVH zBQj~#VoqSjtr#I,T:f:8 @>eV9c+cيKeZ]nq/]\Mn\Unc[øt+`#FD:1> ; &m#HBDjSz+uIvMy؆4{ 0R ''H4@!%A@lfAɑ3JR,%Ng~T=' zk }ʚ!,ʯg%!)P|wKh>b$+е:BHɁ o,o Vf +KJF&{G*Zzb mtf5ƣr)Qn>Y`dH T%K T- 2>Or&a31W5FHZ?`-aNTM?=%yMf 9#tYwBO[R^B-L,h5Ă*wF=(us)aAUHfS 6-ӿ5Z'TI8KFAvӧimt-%wTFK!;J2_:x,c*pm=\2ɺ/ZS jRJk [{?Gshg8.#լ!Aop߽A),G"|loK͝I1|x2XG $v̚o9D O2Y_˄YxCvZm6[3 e'iVX WZAl'_Y|SzUg׿%ނMwŅ lC.oYjԹ>lݓ Uh{[G妗գwEX[I@HϢg{3sq1wH~۫fj1IJXXuN_ei vrULnK l8ML񟍛tf!AB UĄ }3\s>*nԟBkZcS 6dWYX(V\NBxe̒S~;Am%I Z.T"|=zjohfґ̲o6!RlҔwXGWn &[(HT!_`=y5Oeާa\\M"D5َ)_'?cm|U=F5y[c7|PgìsYа^ˊD}3>ZR=vI6T_C=9ٺ$Gn=gd|:WǕc L`{nŔb(UV?I@E&o3de>uz? 8)4nf"4L b/2E[T/ >KPŒ'\h,e%Qǜ௖WM'oF\wzx!mQͅ<9R l@N]QVrWr:OI& 5T@d;k*UW%aDs\)Blr} 8r5Ti;;uw[0Q!mQl亻1Bϵ<:Z3(ty96l,5Y#76fI\c͞hݲ]XM:utDؘotZ ^ջD!+J5tB. Rܦ#U{_˯bًYCV},(>]hzfi. 3@E j7/A̵iZ.#1T7ކ=~q1EPR@A=s> i/H3*6f+I}1 q֋`@u}# <+/@ :7`*׌<}ϲ&1bԭ- o8];ij:w1 *MS&I Xϥ#w0?TgEAi(:$!/9!uSn0B̘nn_ XkRϰWv" QxȢe2w/ `id7,ĊY{(OӦ_1f@Fajs}Y}TDْ6ݧ=u.>B8>@ GO@9ћ]U_+{dE}d#!ֿ2JIYo/DŽ~Pw*4ԨO|äLRT0*sxk2 R53SPTE3LM_JŹ1Hv[64Ĥ8̠ Jn 'Uxj)d:(:/n^2cjR6jX?dm={5ahgTذUPd~E r*Pvi7y[Fd2CJV͏{jc ٜ]ZScQ\uƷf]'De.gCRnx-<3]c[&kD+ "4T\2@(Ulͭ>OL;EyYp, .Dɏ-{ڃmzH4[|cBhvx6Amt-և]]cZA =CicnH\a(&ػM\|_$j-r)!i+ĢN1Q?sו~M%AO%^}ay3}ņzHTJD5^I[zS0[X?Q,:| !,sB]O(]-8,ʖݳr|BF7'ֽSf7r6K`EKB8?](\~m&?q<2@-(w9ZQ?pͺ_t8MZ v. p6 $kj3OB5]^4z`kb!5:MYtI洠  1MkLUNE()? pg /Oۂ+ ;?kZ# Qf-)hnR %2I]J+*(Ep$ n^\YeoE0H3bA :e$6`?E6(?~ @V9#M kWn(>:`RI_}ZXul c؀*V~UOWk?U|d+lԷZ֝G}؂o/hfy\&j>[/(b[_=ܼ`\XPe V^F1Jv)=?evViVOc#]6s; 8 VTuhwSFJȎv@ŏ@%wXLNN4Waix\ ܺK.t L+gY[d<,Փu,@,f=c(MNEVRl)-] uſz:zYPnä1i0qƙ ˙yN=Ϗ3椒@y6 p?I3GK=iA%wDmȓ<mP4/ftyD;%&ډBa 0 m{H}0']]4^ u˨tr$UL=qn(4I4;A"-2̅\!Xm 6(2Iz;*_i3C ސNSC gI =6}tŔr8砢Ps b1ːOX~aEJPթ@^!@xYqwcyߍR?q^GrH>2 <R`>XW tJ~ pu"fZϏ!ܠ,rq+DV1L)B,tD2a~Rq5](=a7: [?aNWnxR"{ ([@:ը> $dԜ̘=)RT/ ( &&؆-14Vt;Ҙ0z/J jaBTGloʞ Zp5o+aC?>y3-si @D)9*j+p߃Zܸn\mV÷#nE@=O7M^C-s:vF .s,֔H'z-sQ]0/? ܦVmΝ[IEb-WmR:ԉBI )tFz fiwMWAK'enEgpdC$ORj .?pY\*( sl]Jƭ(2WSTFCL1ݗ&ϓ0koMQZ-2%(ַ\?MS bffƕ 1 ~86! Hʺ8?`RaPoeoZz1T<3XxZ7#z5~ 2(RS_,7:gH2SKbVqu å\Uy=b }z^Sueט"^Qcޚ̱gv 𭣔 Lk5K}ƞ,w;":L=6 B׾ՃJxAUvv s ^M噐c2QfP[&:C8Vu21&8\5)>&%BIJP(mA;C,Y;`yrĀi猟0>#y/+߄@Ɲi Gm #ɮ\X4'VmVIrZSOßcS:DXeUV!mGN{+V׆?^g>[JƢ6HXu<Bj9 G>wLd8U#SIy,?p$ .q(cۏ$\F$܁\K$0!lzZ"Di@#'_f5x@;iYWwkY5c:t&!ioĖe;l0mUR)3I^Hk EBXCr;;T~T^Lp趟Ř=`+8%Kt0T#Wa,t'|VjI\1?UjYrimy Rp΄'3r; ޵כr=dIfzFY;N:)~@E-VCQOΡj'0CKEQ/圳 g"4 /i!s z"VF]ԅ%B9in)CXH)=m+E|a+k:]b&#o5k F=  m1J9GU!(?d嚽qꁊc[WHzW4~ED?J7B9'Hj1ɴ'NTo ;|6mİaЯ#f% К-i6Azu6E>𐫞%AU6 u̘cNl.Y`1›Z.Q:#rG%~DMo։!n4uZEJ5@ ՝=8fioQdWYjV@;K`\#ѷ|c6 eiJ8f&;7+*&i9nguOp4_ڍ$/!;~_L-I#ħ ݍ Lw [Dy '*.:e0Dj'зbmA&z-ӭiuӃN6q8XoĀa^; OHk/;F1B ?1䷆'VEF` ZmTQ)O7v>so(2 P)(AрztqdPdEV^=~q twkM-߫ o9*wXԆrϛL"q~[]D#/DBp@WR}|iu' :yQH;;M9(,_76B=` ۚtJ Np/w0ڔb4혎r`yvN5=J ?EA1<)cQ;:l:c,GnzsӶƝ@ζȉ۱֌&A kUԅp O*?w̶}TsdJB >ZPW1,+b[G-bhYi{~5^gt?|)|J]*eIbİn>1Ew^˞xEU /cm&;,%taMy~6Dy:kty=;(E|zLaJάQ6+o@惶 y4`'9\׃(iGJ4 1Ҷ*@G#q~$AƓ} =5MA۬]zy^95z܈@v\ D8yB_hFAP+$wߋy -:t @|^tΝHC,^G:'❚?|&˱sےԆʊҿd'SQ m&~_%+1 wOIϪ|5S%p(nZ~jf@CiKBRQ5 %īiVBQ{]:uoټ(/E=pTM@kIM{^kO b鑿1T`J%XFmYk'ОvO9ܽD&"ei\D1~ك2s}ѽt!(7՗W iW+q78]DfKQ]#Wh k/d4,;:~k-XbZ7#fxD9Zɕ-cph- a@/%u}lZ16ETr8uI@mP2|_| ,S7X(Q5oTMjz9SṚuzZLdK0BY{ą7/9Φ@S'.tݸ*Ħg>gBʄoB'-IΓHН'gpFoN')d4~RJ,4Ƭ:ڒ" 8{@z#j+nMؓxxV):~FeV6,So F!A[)BihRUg'VhMP ;p]ZGYJ[@#⃲'#KGFN6jR8BKrLP8"i޵v,Y 5$1s1)}g Bq ZۛWn)kAZNF;esiyB*&S[0&89UdxTbh*V6ГW[]aT:b[zlj Ɛo\c9%7TX@ѷ^*ʇu_3㨓5{N R2Y elz.&21gIosJZ\O`!V6J -1ڣSo}X| s _Ki$q 8/6gGd&ATj;b9BeWto5DNr:LϾ(%q Fis ;%F4뽺xu[tWB/+g#Y#Bk84JC( ,t+mb"|2hBv6hXUl["Z&rl[fNxќ|$:@Tú'+oUYlw<0:`_'rXD[~X쮠-C D|S We}Tnn!"x9ƒ": :a9D_.8Ne6GFЂod=y>\kZ00 v 8Wv+*1T~:ACKغDôxق2d +5,C me^ZB`p;Sc:q iWDsb2NmW ڟOj1TNvc)^j NvtſĆD E臓hӶ5^~$4ƣ!-5&':22o8AH~Ţ ی$:rvܒ@2sB e/V\N. >R6; &ݻCPCZfC@4I{f)X|;oxø |)VG֪ox+LC[ǹ@&1][ԃ}p{AA@AGO 6+!7e%V[j(7NתydњWa!)ja,FFd91"sɗsr :l4p{ot$x:H(@)FUNVAy+8sÝ1KC wA9MJZ`%X5#cNM8"( qU!Nr#WXRM+VI,jH\h  's36wdaS {9Cg_8UDh $W:se ^ȺN: xiiغT%knA-;蔦usкnmU(Fds;s>%hXc=R*fJp$)I^ǐ5ne~ /H^DI{9.Elyv oO 4`Uo&T-2uvE)Թ☵gX {;A' ?Eֶx:~'!= r:-yL<٨rU6{\/q-~Ӎ=⒤y[!jE;1&2`7ZPHz/kYB^9O{tu A(u&i^୕N%V%GK:|ָ{E9(擵Z >zk?o *7ݢ tuXNJ zʒȯ_ViN_Zv띄8}g9Z]=4B.N}|;,,O˕4O847;Y̮9k̯ }* nxC*B8^*ILRE0F;I(ʍXy}A'U[- gQC"cїrIjeÐmbM4/ɇB hת%2}Ż9kʎnY?U/SI3cᴸ)-㬥ji0x˶ackwwG_Rd2/|GCt=YKlP5%oXfUq ; (JF&1ňyPG򎬕+AnUjݍx4XPY(G$61Wm5> &h@&8yDgq"⸪i>°V0̡"\Oqwf|BCÆǩew~*S` 2um6_ +l~*e45͓o\;o;|=NxH>쇾y`K {xey&@1DZ q?o]0 j HrkNȾcg})NBT*gJDtD?ۂcwjߚHg^D^ 伃c#&nT疨3Q'e|ťc*wzAa#یA˾4- ˁCȀ3Dv| t oy".$j?5nt̝ɉs 2:@ @ xκ0qjDGk]3.jVuS (2 i4x6˅> f**}ꏎ5УMEUOH:W;`=` 8`!ґ.2Y;dϾ@ivԱukJqL4_V~nͫu⚉/a^0_t._;2N^Eg 8:3fVy N U YnqoS~1[ݙ}2av`CN@VH-b$Voֵ3ryrhxnaإx4yᧆK-¹v*HA5qWi#+,)5l-%oÁl`}`'kV=a=Â+@ o tw`z!e 6Ɉ _r%{&p,{vLJ2yNgL?ɋ?l{_Hly?0@;ϳ uK}t&g=nH5bX)T[%oOﰋ[ tסķsx+ƈk['B#:5_naYAy_$5v˝4-fNrb"@e;`&tDD&ߣ)b4gʼnC-a7/xֈP,)b6gvx  Ѕۥngg^3%jI Ҩ?`;Xe \r} IE1A9.PKBAH.c_x 5rmwۙib*(Bdf)plIiz'> _-.m u\z^o_e}ozAR_C/➡Kv 4aK{ȠRUg,ZU0}P=ݚc_&E* =LfO![luWi6ɧld婭PgIl4N*x L% wBpư@R7X0Ю\J' C?9}r7v#ssH3XWA9B)5hCzlO{?wqcw+r}X+LڂZӂ#9|s6T F-0G ,^]FDq(4F J P]՞3z 1r=( W9$S n6ذl3Hj؟SMT԰=I_3Vz9^IxZ: QDxL X72wy/[Y_ OQHA؄;xN)挿0E .?t;G7 "VC?W!fbA{ŪNzKzcMkKR:q#>pZg\Y\R5W-3ρw] RU{ gr~MjrU5Q} ˮ'SBm k0HIX,C#r74vU9(f:m nY6L_1|M%!* K!%yj{388 ءcZuT>)׺e&M5cXDX\đf_>CrJ|tOʷA;/yB4niͬQnxξėsdb [˲D39f[#. .~V^SWJpO cӕ (_9p5UZ\a yRs8y !~rPSE@K"nP*A+`;_;o[{?M'a7歶}p6S8k}%*^>BX]7 ;&1ӡHGC9Ԁ=!p +{xpaMfY$mh]`gF[xl^ƍLX;IJm @5(ziM!=Zx`FGّf Tu#6M؋oVb,g:ޢ{RI"J[D k"-C6Ia\ T V|^i$0HLy4E bgClLS q'R-M$UX+hZ+3B)@T "' vj!'v}b/<(u)>N Az;MzSE8ˆ=B)^mQC=up#ě@>V O6tJ-C?>;8zfn wSH(O}j+}G /h.(;̲G\ӥh0^wt{W(rstES遽^tYN1:&DjT.aνǾJ38&F61`]Ϊ`7tѢb}'c/XYoW Qk&1&j6Iƞi${pKYGE8rﰜf^nLy\"9Ar%іd7-^)n5-E{ T>Y`]Q-#J'.M1c.Lي>ٕc42PB⌊L܍gvNS cۛvrN_(1.}a":+J6&]Zy׊<fvL Jfr&;Kk(aU 5p ˼AM9'4xۨh7ؒZ$)\,Ѯ`q|s2_!t $'hbf 0j%kuZ[wFl81ua\ IN|_mˏfԭ_RZ?fL=P&>k߈׋0SQ%]I?AShYn> WCmB4 & 1;;O$qw{ %1WKQˡVfz%:@羷hKuNEZTXG T"&y<=irΞlcZ$񃲩T^?l~+G[.*o>9$Jsed>D-ȉý8]ӡt<2 @'ȼ̍j,^Rk6ʼHQRqzUm^L^{`h2~/ dz0B+3pn/{`bcMnQzt8jREV$|Q9M)~L"mVxDj#{o9X{<Ծ`^dŚ"56JVfrX)]9hMY`7t>g~|_bA^6\T}.-6O?GbhVMJn>\g]I(|Y:q͓_BJj̴k4es^\Z:,~01):ȩeWF_zԖV9,5\´wy'xE^^IpBװms=f&JiȸJHT6naGSϩ.-pM"O [8[rO4R>odGk;}Ӄ؇*5cQ"pqsqwJ 8tkƒ4jx.Sͥao,LCfʹ,;Wa;z32q]Jd9=ul/Gθ=z{d\L >I9?^zKCycy rĪ#-~z)0C*w:!tѷ-%gHvIi ܕ,G4}Yw{?}1.Ѝ3Kʎ{ +v' #T 7"!](KmmYnt2( ر\W͞fʭo@v-4#aP" of.j BoX@bֈ59H*ujq0 +Rm7 6K_c\EcӂE!+Sv'iz%@B~]z̧FR|~x0h($]+GYݚ︶Tȿ%׬uQJ4Ma -.s@É+͊x˳.pn7Jd%e7X&ˋhYn m[Os+"ѯ j$cj15Xj|c9l\`@E ,Ў#cDFŨsmf7ww̚ zA 2TGO3sXxe*c"ry~{p&u)󚥇hxԙLh8|o]:a1 Z^@tC^>'L -Y|D-@nRwSaU=n;n! aslv@t٫ck<Uj~1S[tCe5_9 jrIRj(/I&Y />?`@L=;š@@GiO Lngn6̗ՙ@{~$}9 Uw7yg_ Od$pHy}:[*iv+m7u8B!Exiruh 3Ұ,MKLDQNOtO2 kH/6 DK[2Fy8̚2l,^Q \Ehc&&(+=kR,pT- ?fӣ4i'ZvI ƣ( BSٷ~Dm95X T0}GC]Jcڦr6Esmդt3@eg~i%-BP]'rbl-JqzR句K}-ڸ2\b|1Vz<HܼVǗW,ZP|ޤ"YBQCG5 ")Џg@]Ί8HG2k`RT2GP!mGEgu*V @E{oI6 -(W_Y4e"2n?Nwi/Ìn{Z`Ly c2|#uP!%#"wa4M;t%vAcB6=[Y$;xAlRx4BFiwjʑf]z<D22Oӷ+-^B ӝy)c*7xQȨ1^i(/Mٻ * Yښt(j \7ץCH%὎>D6{')լMsa.Ū#U?xY \13քtO6Q6Zd ߩE-87cDQU÷U ߶0={mT le Ȑ,"qD OmXmLm#} gVr? ]8imns <^qh̀ x"ޫ<뭈("G 99 YySvX!aGHIa4[gZFݧ䫗b4GՄq~fpv3gV`z{a10'jXLu7HJM_<{ƅ5PODΟ_؍=*aR G0cTH7~&.s7G .Ͱ=S>q:ΩMP:@ vL,H%*>L\~5!޺:%f1Ka, RP5AA3/t]Y.5Hp9=^-"FЊZ#Eވ]gsj'H߂i%L]e>cX807*U~Y:FGyPnC)ug0G=0W0Vxq/Q īDDb*k\u ntfXޤWk5*Do[Ts{tD;F̯KT>O KAHn~ӿǴu ~ ")N/yݦ ؄ @pXꔩ67%ϼdwf%d#'4éYT ŞZջ06e֪wSD 'cbA˭ g0?KAH3ki6.)f~vE Gm1wߦؙ\*S}avg<}&\8FD붮Lwн1Nx,?Nޢ~Sr)~t}Par3xɔI,(D;2*YПKE< ֆGbC;!|LCcejvKOEq\B 혤Jk+&R}KD)gf H# 3OR[:![;U3 @n;MٷDģS_-++73m)zbɦۗOh2)ꦎF# xcRuc11+Nd^w.QyEy9RN(,V}k9R"/11/rJ=)GPe1e$Yafo[Dүζde=u YM#A>z}l}nj@?8 m !­ɴ*?M0|`hhY]jejCLgk ,ca+zq?>~$9b:ma]bܭJD-7A|i=\mIJ<\B_AZAK+n17I "CqeXZ#Odo$jIm&.4Թ|8~=?^c=eIgwX{8`9nDbCWJ{Ud:ĂMcQp>Dï@?I/HW5-$Ē 9wzT,0zsI>P~;ZGg D lz6擈 sxW]M'IPƴ܆p\ۈX>RYH,|ؔ ϒ1ƚьdx2)p~=Γ_4LvN]`,ίN5dn*bE&YJ瓁 w[RoX՛TLg{K ܿn\)2 zKKUQd`CtGBTN2)W>rZ}lO!ԴS\%e{.CQN~ ě+v ϓA^EA8e.gnS|H%N.wx,2 ~x[-'S0+Y^e;,Xn^^DrFI?WyכhN{[,~U 4C}RYn\1+x[~?7wkt|l'#O~j{sPV}I?.F`̐![Ti&EĽ0! 5 NV&\qp0w?uT,+pߜ Ԃ]{.W!'%qoN]+=0)"9>;dX~749)tyDOA^Qm0;u!䴬B,(t셄 SlכO "ms|8" ֖Vr2@FO!J 8 6ߣ}2CG~KhZa-T5djDj*9jNB3$k3T&Zlhσpv;!:w'6mN=?njtx>Gg{HGэOko*|oI^61A3'#&qN= p[,0TgQK]`j#u0+^n=$s8=(`ʗ^}=k2Oy^Qx%PjU[H6a^C G FI?Şߓ}u6,5M-۽̉qTK.BZ]WpBcC_pIۃ6L$݆V uD43'`3Ϭ j #OzW'9e1ڹ]Rxp7>1~r<ڙ ]ocdl}9;b7mI?#]1OhrŠȨޛY͝G=':n$™M#fWa3 ǁHtp'.&'/핞̆y=ZD/4nk]4963^_)jL=(c)I3iOgsLira%KdiTr mp[7)V\IIr{&`lhOuLYchӵf?Р=m]Tsܓ qp7>%zEYH߃B]zkx~Oc,`GTML_ۘGS ]N&{b:#hVpQvxn}LQ ǒ*tK$]>b>nĚ\PIUmV 6AF0v)faf ᗚ Rt7 u tBwcoϥ3Y#n㤦Iu?*hTX(س52 J?x<:>n# i mnV 7\שv f؄M"pJsIXC:;W}PwCRL C[!';{84 ~VNe+a=  2-"B 6ad`_L ytR{1gjBH|8o$ `%3lsSջڟ< 9'b4OI]kA뉌%7ֺr]ff] ^4Z "x768o}E'VNyo&S0A9)%*$*2!M*Bhiq rWniU #l4PxGԽ; dZtgYus.p%N~;ŭWz-*$1v>K5MISm'UI)<~Mm%d31w-!!2C8tn1{KpTNf ck̀y(hoūbe~%]7A7Wyt=8m(] $!X%B|;HPlHggS?_/"Q?]"@{ FxfT`wQ'=*`35RL4IWvA䌪[ Mo3dX8r CUd;/;#@i~.yr@jΈCsh zF偤ҝ ڑG"HrBaG`;ANUksB ^{p.Plhr2qqWJҠCn+v`nkG҃m26Z]PǹZH3]!PtѺ0U9;4&:]EcU!h3@E<9,45H9M\&JvғlP'^7FO$jA |ӿ->b .h}p>n(٪C ~A~ yx=ީЄ}Js16rfx"a1`S>MOIYÖ#4qی;㄄˚] NQugs@[C0eفu:g 2Kquh [dw{xT2.F :E`*^1ͱ cTp&XB&`_\k  qyPHV5Y浌": xD|~"BqL4֞C`HMy{p1Var+FN\8sFȺ%CqZ_?@ \ǃF|nhc8s 򻠬&(: pz߆ H&G0PaQZ)mMC7Kps6@hߢ򽽫0#~k=8#+ X760~WŴJzpxyBQǀU<0G,/e&ey \ !Y 7tS(4 b,@(жODvmX^tg@}k1]=cx%3yaL)w7VSS e.2ǰejYbeXtv1T[>h:ȫK+L^'` [gWCFSDTY9i8z(֩ pJEG^ {w] $j|vdxU@"4Hpyu|8U[Yî <$k'[ETٵxTZ<&ΉMY i9OS;w;Z4b*FB^wkfa48[&ɪw( Kd=Ce~53Pp$@V PaO)]'{wv熗~u)^$1i.bq;̉]m:>K1K|g}!!^e%tc!Id]$e/;Q!M(hqV_(tE]@rq`L;T 2mReǚpl#%XCw5.)w^Ѹ@K;RAEdid^_ %Ls@.{ρK/|IFypq}4k$c{ +VBT[K^ytu"؞84[zL1\grO,XE*{a&zٳ˩Q GnrKqnv#fu;74fd[`KPl(RתG&'ZQ&3%' s5)+j!z s.A197d<Lf4K|a4x 5-s̿g=~Nw5~B;ƨIV `G]9\~g\xiIcI1_FerӶ)3p:5vVpYw|:[ba&–Թ1MF8@Ӛ$1ztiNj$#:v#4Yzv/`Pv APf3@s]| 2IXrx901ѡW{({} [ Xm*NA硙ԟ,S UJ]9XM>㵨Z:]#;lkG'6=:D"v"VZA-s%/`G`= anI|1y܊a~ sRQ!| r>*-kv.s `hAQ^ `5 @7Zc5aa"WQmm6I3iUdہWF.'N f&x×68֨cv $Q۾'Naĸ9WhϤIǑa'6`Ġ6b=dt3}LUO^Gy,q%*ˣF$"zY8;*]ꁋ"NbIqBfT"`u5 N6 -4e Y՞Io rʨ6m߿-s9f%Qa.,䄆@Ct}BB+2*q wgk3ZrW7%P}s͍cgvTܶKmL}d̓ZsZ_J;[qy|6#åDES .Yo3p˶/Q^$PGC'. jUKF,12ogJ []iɘVԕj[lpS<#Y ,. tQ&(=ɿ-ܓ!aD=y%E䎊4R~vViY`])6> (Zt KQ@gi~хiuknaCR`^i.@OY eq>]<`ޟ}a%MGD7$R!&tQS_CfQѴLd,K )1퀳+gK1)Gs5U6-L[* /n ?45L]S$jLYJZ_K@è4# y-:B F >60߮!W _`[ q鋶k1#WKкJWcyP|*p JrP"9\|d{t7<%ᮇ$h(Ra^Woo~ mRϿ{|yNP tCNڡ=yoQqBqk4@ S8j[O.o7XتX '_vrdJHoXē.q1˳xby2?6`S*l6$TܱsP'm[rK Q*$  6̾ zD".qHG@&RzNQGNQ]MXNȽ(DpDp*dbpғK"rtK]Ui1k-6U fCosWUWa47XiNXwȹp-&buNE^&ȣ(Ql dPslSƩҨ(,jJ 3V!?.q=r.nLvJX7^($iWT@UgZ3`3&?= QH׵<;  &K̆ÜV;R einY+wqq%%+~:th^E\5ΓNdu>4iSj>L6s␧6_<%O*ao waZ ҄~Mv$5i I+=N+6RӼ|)pHÌtjlȵ&~lE6I`-,qt~w.ڇۜ/wj~+y+(QґH5KjSk1bri A~nfxtDi]SƦ8 DS> 0I`$ @u oT2A5QUl5 S!:jW>zW;D*]0p٢GX `Qr7Ea‘RA/L;y ǯ49>7CNMVDn7#j n íL=OK03*蜜fF709_:/&N='}6G9\l!ZtuẢ;~<[lE\džB'Gog.k6xZoᑙE.|x E ?Il߹cZM zSd7IVs(T*5)V4ƣ6$b}<0ƥ8I vwM *@"hòK0RlV Pl=.N€" >DY4-'իmdZy)WmYqlݪ@M*@Uʤ&dCG:z?`T*B{)Nk44BR VM<P~,]n .O{ gr۞9lO*8nl^dIDkPqG\D+ `ٕ:C\vl=BTrȵdOGŏwf>)i#-+ 'Ub.SvQāTʙ2(;Ölc3_$kjHWl] o`VeLj `@{Ž8ʯ5j1mN+Wc3Lg&^|pjs%>I"'Zqjxnj [+yֈ'Kg0nKؿ3 fSPwxT| Ie\u i J{Mm=[qQ< wGsVP}uJS.D*\ɬ2QPHS/,?Gפpةf[(j3  `DٰfkհH6+.1ꋦ{ tAuq;Qi0У"BlV晦KD]2vVJS-sdbHظQ+&W:vIk3# OW8f=~4׶Ayʝ>ݪ39eI5bO [/)F д/k`!e?BjEbbbbiAdD1GH}|%QGB%|!L #9[oWL]ޏyCuܓ2\7` ?EaA v+_q2\zfO %y)Bn*8=47htÆa+,`ȅ!τxpykq{meб8=ԋt44 *m!!_3e/;10XГ*JMW((qvXrf˒՞2"gȍ׺2P;R㶧FAPP{Dr? l)KrØpEeDjiG%hq{ՏԽ@kM*`~b%Ϸi ߋ>떜'/IܹI'.6Ќp op4m;rTcaM]9d ϕϜ \Մ{NR~r;kH'jx7$pk "EQ"^} Ԥ,1x͑t1b~e"WZ>쟪=7 ^ECy+DYIq̬sy ѭnRh1F>kɢ`##w`Ah%x'AB^𾶗6ӳ0̲[݇Q0A\Մ$-n"we¿Xjr#dтa&7ϫ zp}A}7l_M)8]V9@>HMM^n}>ԻEQdC1oBFw %ːT(_^|πOT^JqA:CuM-\Pf\on:^Tr\u{M'ړ$v4lbLnEy1c[7:OVELcwc9IS7k|^H/ٵŌHyclI( ו=/vxvL3P8!k^f5x^O\0lJ~ 1֦e"Υ<#:Ӎg&v[WEZ?d{˵~[=5a@}kN `=ŒUuFR‹S<҉BB|Z1;~{%OqZ|Z?h5͑`KRM!6q ōfEp;A"=OBPq_o`ڸ?44Z&RkUl-7;!Td]*;FfײMgE`O|.<z_Z E!L)ŃEC|ǙM=.^FJu)ʄoUS1ljo\$)oW5űu^&ߵ3҆^E"ىw0"W@ w]q"Տ*qa/zߑ_qIQ(D6eTģbݳ'Dx9`΂iCTOeT 'O`x3y*&-v퐘LdK{:UK3xo'PL#N4;=\}.r[ 1.Y^q](N wsDE1͸YYTZ{_Ͻ  O5?:Y%%T>&_"8/"A7t0,JQ^*_ ITŏ9L`|V i˖Iv-*7@; {A}iqgR%2󵎰J\2,Q>~of,=֜=vdƣy5<&XUR 9`4_nWoOb銆vX00?!VhpoDNϟgMO@U V|_v :]RXH/>6NHA-zLT&'ˠrq Vj mQӜ[g,OYyi5ڿ/E 5 G-NI\?^V4a"d{K̫M҄jYxɵm?{tBEb1jOΦ0ਈb' oM%/bno6n%bj16P0v 9Q l+gӢَZ}䀲_uJ.آƧ<:;QOWߍHbhkGK~N,zeT22SBs>qU㝝 mL rkZU˓,ʚ3ZAtYt 2Z?9evfN|?Y*0_o `.t`kx1ɟ`19ȑZ䎛J9j|5@:;v,裗(P;EQw؉8คKf"@PUf֗@ *~򖢄zL9+\rbY~"c#Wg`%Y.*\7vv.B-՘A~h \]֗Hkuͪ')'&ܡR-{&x;83 WY%}i+hc`vF$bM4Pti-&yϨvmGhί{g]}%{8i? d]!}ik<5'i@K]< 0`hDA׃"Z mK\6s3Koi26OcayH}U2]`s1'ubܟ14V57qx#G0);דe,عs>x@Q C(F?^+vKz?3/$<CYTԵ7 ?FZ]P?o(A5;7R0׹ a{"DR^Tпd{D.ղ"}[,5+t ՙ7dX|8@/jb!=@{|{ IkǙ_Q12xnWfKWRA+{Pf LH3VHͨ'yq0?iu:sת{x'of/-G{[mV?ޑIPKc;35<ylk47?26z:OR4>y1;;r1 y0feVk7g|c5Bq(pZJF!KuWBdIz.G@ea:U7oKm +N"q3$`B}/[_2|wR䲈Z\W$%eǰDIG1( <6Ғ/6:n@2Zh]8 4+V"&_R;yO ~hx;I$u~]KU &Ojt̠ʂd>$qƘP" u1U*:k%聀qecӆڛ.ػV,P/i& I6~.6 Osjdt!Jur" 0$/Alq{Fw+ i$ˤTIŠ}rXg7Ƙ˼ RJ Ń+.v{Eʤ^v%fE]-C~"xmϯQ/ٴFĖQVj:WS8.J\ȳ'W?:G&ME{^#.^tŔG a.8Lٵm&s6tH( $^p`l]r!'@ Y60D[04 r `?mJD$jw#:kZAK@rSp@aDGEq̻CKd͊l2w):vX>VĠ'-@܀G[5ދkNҲlhx]q%fɬ aٽ Bo?S[٪4xe"۱R47=6[CL>}T-]p|i^Rhr~ph6Zeً:e&3 D"*4rXPNX#֏Bsv>c,ULew|ux~Pκm A{s B4nb f1X Dϒs_&),"%W1-..Cp˰:*Z0sDq(q [>|Φssr rAs 5ah0z=R00^z ʵ j[?{2 T\ta0t'KWDy~kS0۞ p1+RLf!&ޓEs܍ kq,$ȇ_&Ypx,T)_h.iyr>2F 6 --@j~ qn~_ӗNyրk*4)iWG[~9 7|fQ8@"$*=>GCLqqzR|@N_8HYVL{zz0KA:E;S'$eN]{bVß_Ϙ cwR)x~UxJ.dgg둈EUV2*hy _ ZY ׀`ckNd#V`.IĞ&yG._C29 K;,/ap;$d,K lL3R"0ݴ\M{Xp⥓h^~mD{5],[ ;c]8^l3," ̗rt | NEtז[3pt FcNhߴq;렇 lbZgKl!p amĨ)f! |קEy9cs8.}gБt s K]\ot)dm@uݱLHڴE:L!&?WM&JGKeie>^0毠9(}Je.XqXb~偘t[,QHk4KҊZ9 y̳aאeWʷ 0U1 ,I`1,gTEʴEp2{ta{_m^pE{v`_? QTT .;eҴ.1k#=RqIDOxϴMp8F.8–2b/X =Ǥu<dBMSW:mB[')_'Y+{MHǒ{Mozi䤴:ʱcH!O#q˵p[BX/l4Ў3 -(D ?*'BL:48=} rXaN㜩"lZVa+.2A"@c9{1W8{2P%Kh6Exx%t g%K&*+P @0h>i ncN2 @G|(#]!zc򀝺Tכl^?KdJ$ ѣc4&w5<-sM:AO )Yw1Tbs B(7؇.Fa_p glY0+oٝGӮ }M,0_O;JeVo˝Q7IZ6Q2-M_{ҡ>0?0=``*~siz CHL`(؛/.]++Bܽ=Sẍ{|pac2֊:Rdz:5+yhfHGl=R\II) l>즼SYV,pY F5߁y xKwJ?2uel˟p q]Ĭ}}.-[ NoUg՚.w,Dlumݼl^"=MP4d)b{~liq~014bL1BKPH|8#YU#KFqLVk =ς(+)OXWq@(eh9g룿іk/~= "0Z[L6 m0dK#Z]˰k$`x\j[i0y.pY*lBXd}x@V- wtyWFښoXa'֝=NHӾ] zĥ d4T4J%u"2xlpw.oZdV6e=)&C_-C׬|MXN7cgH/s7>*5FUNC3N0:VVq%u&z_"zA%5PIKMutZ ;9׋" I/=&2+{=yG"%6*4Ͳc3T8ja^.0!$ISju ԡZ>2o-?l)q6b' |j~T=鄗&Pќ `<cWRF~H%-  gY}#HQ}0³Ts*ݨ&On)C]4{ fҐbV8RomQ{D-AܺNuF"sW.⚂u*$Lj)L<ǜp5L,N1?Lzs\gP-Ci 0+߇"}vCcQ ."|ffm׹D{VJʽ4D-{СW^GI:6c lւVtma{t+睑N*wqcBW%:ءӺ| ly.29n[w ^o: '?΢))fwj+]TA&{B P&JAVٿ)^rrP/]8XJK۸q$ l8{H b OcUXbd76 flž{JFsqm02#4sQ\sQitΑUNȂib@*P?3n.7TZ-^2znH̿j}lrXzO(CQ!?a{^\<X5iL.W9]5Hl0XڧbԦ-2FboD(#yoH[6Cx:ςc%}!$a҉6j:##ޜ=h6gij{MJ:t/7O6r59L NLR_$75O9{¦XD'$sFջ3gGO(NT6=_K`I4:QcEɶ@VVK"DZGN45-hⷡG5Xt-X/ɬ}XSkl w DQ8j6~Hkw9 &vG{bsl( +BzǼh>? p]7 -ӭN1(c(}4^A7=is}<+UXD[̹kHWBm9Q} vGEi׹7{e߿rC<,`#=ߟ=բVQ"V؋ޜ>h¾soXˋ\gvigsL$J. =(;$IcPq'#03;o|c[$V{& "Gx`vU'r\S-p9~(ֵLNve%~ps?SLЀbx Ӧ|J |ᎏTV و ], 'hћfF,X%F(ixjZڴʭ%FK]E\x5č[ ːsV-I}?VMb_WĎB0%4̈́>r!+Nj[|KfUT )&dg2\JXm'3dԞo̦Fpg 32۴\"[O%BP'=uj|h|6]2ѿ }v{ !Y= 6qLH 6FP?NӢ^]b~LumH=m2"]i\M\YsG:X$ j҆pDRysFT0yKH/)ȧqwu֔L֖>^Uhe:e66a@[1AahYD{*'Ԡb,K^oiw3P:#i^lnz b2C(y=Idc(w;)tCbbjuCnP6ğ}nr=C~ΚE h \gJҥ6"m e\=}n{ブӲODuPG.۵L]%1?Tv\8 Tnm%/g1VV 9w ԶPh@PvAWxeJXvssōV-Pr/mLsU kpm+FR?$!3>D zvָf|:69S`Pq5SڱS:8 41tGJI(ǶdB?SQp3xdlz_Xm_F+vY44Ax 7vYe5=%hqo)VwA XoTӮO?j;vV٠M& YGb'ʯO c^é)L& YN3E,z*ZY53c >Ji3uK0Sǣ!MZN*8viV)Wt=e&&5yׄ.*6^+"jp}Wd%1YкFz<iCO?Je en}70 |~KD1h琶g$tVNV)IKa]G9R/J)E%Dk%t73lsŒp@b< ^S QzYepb>UVT/c,TFE.*6]nw?_K[1dPxIϪ5X_ZH (L濣 e礗#{t6nأ&._W: $6J]J5&aLC͖՟+ {~޸%))[t7NuΣGpLDM;_9bjmK]b\nx㑸LhR:GajQbZvU, 'dr)A=Zϰ)œݖ,b!BiPwe*RY$c%ޣ5Qh*"'[ ;\Hh0amU0$VYSȠxLDZc]UW`#d?s9v[#gOҴ}20_v[6 \.A"N5k<;s`FXVK{ KŶ4\pĀ@#N4M%|Qd5ZRpYKv~{6WcdγO͗vY+0PEccy;9^uh4NGjj2 b^UocrV(1I,P(/wgRa\6V3[*/4gIu䳱ޘu:H[-*,N-+}!e&f1\d\p'ܣBʋQ;Ls ?e "B{׊k7f>V,2^ Iz7z<9po^+7I&&X?%I] oH2Ҟ%d`TNofh%KLf+RūEr6XK(_ObԷ9rAjOFRFH͉;iqY mݐIy J16b^QA*b0Cؘ[7Ya̡/<sU~?˨tEcP 5[/q)v@pLv6KVQY?գ.SJo< k|LKgyTU/y4qu$#{xÍ;74|F8;oVoV7:0ER֕!n cAMPXTTI%z"ɄhKllZ huMנ[8fڲE݊B&G *tY=a%_!C|NH7ŋ䥦M:'ʦ3{"2?]##';yvNcql3Vd+E\X3Ƞe"_] ?{ jd "S^r+w LR]F%'Ub qY%L4Vh8={@c '$W~ƕcf7V@sݐwUjK*T*l\Иc\`3 ,Gh #𵅬rJǖ˞p/SBi.)fX)'tXJIQi&R2vWiy&  (NҊ$Q$w>KhHNߤ J M ZzYORΌ0_%Y?D=Z3T6;JmYSJ#㏏bI;^K[6LӉH5$4sA7 Kzqt4/(ژw4U,? cnROѢ +BbGtPLՁsPPg/X$^3B(ʮƎ-'W'/ٖ_:=:whǟW|\vѯ%6GhJ4A3Y澁?Eɀ.Vx.Zw O=TƻnF9oD4yV Ksǽ<]**&sɦKBPQnLCn*ܫ9N`K_ E _4/|#-BY @hn2&]dԬޥx'l·m #tYFVߒ[JtuJ`=d~/aFrk5p)\YU/R}wQJ{(y~xp+ 䧆P<}p[O븛]XlSFcX–u!5b iELHe nQpgԼ#mXG#z+Qn X]ۨEouKl(TP%}ŧS}o2X3uSE'0'4IaN@=8VŃ:v2m {ηjOܪG=KŒ J4#{g0'#r!>`45gDS[Qgr )4}yOGvʹ>~ŏ*cf:+eW{@@5_Nm u6:O Ee5wb8Aߖ =Ai |͉UY3N$0sԙ-IGc93|ڴN9jD^%Pu@P4{Ȯ[~xCpÚ2<S"A%\>CY"己嶄ߣ[UqzhS%⋱?p_ oAV$D_+Ƽd Fkk-~qhrd-dVڝ_ҩ-?hL41Qah\snܳŖXinyJe0|Z-Ti#ZԑW $n:· Ԟ^ښb7ژTsY6k~VF GdBmsjx4eĈі}8N2]'Nd;gwg<+n 'T~ S|Z}$^8_ύH8f'($,Sl*uT13{I & Lmhvmݜ4w̨u**S@-C7t}KsgQg4tgڹO7P鹋d u/\>k$X飢ʨx$36,]\8%-E~Ro m]< ͝~ '߾ek\ptRǴ7C5˿xd<Q?Dvs-b""˫P8Аf*B;aC@螂1"VE#>=z\ꙟ/.Up Pp Q_\BUG/xy/5pfVa5؟bZURH9\rS.x;e~vE!G%@f# {EVc _f&j%hK"b;Wdfq9QsNE:u|`v~0G )HArk/fC~x(X'ZzlSJÏn 1DF|PI>Ju(_?Lh5Dhoy2T?؞tN:>'}{bQ~K+$u{iY k'OG#3 +g_HFgٽ%Cx pM'yV,YD>vЪp>BnCUs^`wstS7uzgR_rM\-+8F1NS1hI?K0,2|EXbpUd1":JDmI=O9)Y_R'l1Y :+ILb2@Vf 9U@ۯt=$ċ o~XN'֨`=~ ?Q||&}ֲ#[p0xjy(zuRoGɿHLH)D(+c@ WʨV|Y)T{,ٔezzƯtLE2])pCk@v Ɵ)J#`Ϙ&6Qr}ǐ!~*l3$L]+nGb),lG y8GLm=&;$Mj0z#ڸkL oZI1OG;vR -8\KHDž_vi*], F`΄[@3:|&2߳$ˡ5@3սPG9W 2F3LhtqyAZBD(Ԍ[]({0m(woͅȔ:Q|ea#a,wdGe%d[?ˈ'q4Y{%}~=ZMB p:;?Ef Ә!?rH|M t1M xM5P*( fɏUn?}5Ott *1 !]:xZ 6]o`RdlxdxѐQ#V HZ2o&'S;$+OuTceWr[bRvۆ#8xl{ }CT.xixr|͋x:y%&&'>[H+{^S&sh;ƕЎWۀp$#U٘_zEd GrՋYW aIbZF.Tuc:fFMio܃;bӇG7Y$ 5KrnT 0 #c\zsʂϔ|*Jf MDzC> JП1 3RqʬVкj)/N7$]t_8櫁<_oL𿃉Y=#ۗ`-`3 縻0Cr@NPqHڄέtw/zH ժGjBI+ C{Wx7rV>^է_ ~ۄ$]T_]u0SsгPņg {#%wygEk׎JtrAC?'hD7)[GE1c&k~t&3\؃&Kㄲ*ۙq auqqV/JVvh) A8x|:?QCSDT;3ِfÞ+%cD}֑[& }̍&>AN0E |.,䋼|e#v%he7{B +g~CK Pcf PDO)U~wZ1>MG߮7$}eCRs?%*}i4uޔ5a$C$J @6w<pSS q;H-b)_K'Rog,fUXUVx8U/p9 ޑxťK0#b) 牢ZajU=nRa;K/<+}]gk9d:"t:3jKȁYOMZ~q uR@tHnNRlkFMN.O[$1( `(-.C1)S\XE~$Hp ˭i~Uk[1Kٗz!DO_|Si<&4Мj7X6@$_|>8bҞe<\cP x3 p 7<9" 44" wGZv>yBM-3 EYG%6z=rC/ХP#-*_O -NRr#NGn DMDC!iUR ~ @C?MX*sWCe (ê^^L +s\K{JvpǨJ \7Z =̣)PrrH UY6Wc5[KzJp 0V,XnbW!\.a+͹wD:Q',ya(*;^ ]-pw5Q&/y mJ{1#)rnd"x`4T t͸y_ :KIGpUM!xwv$5Fa7ޤ9wUbR&9%-#}膝4ƫRYVABN(cnTN^~ $ WSeYHXZ%QfFX_Vr@76D d躆/JDoX<~\pFe\Ϙ%)b"m)Bui=q9@-N]3dC)<)3@=8o >~:uk:X64*)$ׇ\;wޫWO& 3W(Hz)R'SaއihC괣ks)Ҥ 1.ǘcrup|DF"a!;K`ЩA Ձ;LJEިCD\ 8= a~(V[q0Sɉ_V5Oލ77/ĆZȧXJy2 Ap;*[zYw k'S+䫶bU{rW ]xdBzJ 8 ~]DM艢Z8VH*}]p960G#dp0 4)M0Y͜X_V[FgL[t%R=Il>N\+t-*ZLOAub.8GⰖ݈u/%I:}f&^G-m oUZ6eaiR%ǐs+`}%/OnLPDqOL,9'b|sڸYtOI2g <`VqSq)IA Ul!Pkx%- u (͑=~*x!W놣׉}˱N0yTXK U9N%0PCBƊEŘ:K:m 2=ii 2q)pgo^b0R! %Q(T{]=DYjV^#Rw\t7`T8dΆQs(bw Æ\UX̃BD!~_Δ9!o0>ص?<c"s%+X٥MTFyX(OY!kwWx`<f]S1} "vh>~bCUsnRZ A3e.2QXunV8k4m2[ aHMuKvc688'1lNumն[`r^mgyCF̬d !TvU!]fuF6})o5^Tu'ud 9$[ç*nk@+ 4qw l_P:9rHp|?8u~D!àZ2T?ŢPςx$Q+t^7EzX&&=8j=q Ud\t BZ%TB/}N`=6mΡtطrv]t XX mxZ2otk __TZïQV BbP%E<+Ǫ^jgM8gޮ!F._My:N6K4 G>Nߝ\,1ֆ!#\jD%יJXjW(c&GrVSlN̂/T99b%< V(K?ޯSZJ]RvM*\%1^a{$ ~7 ~DB41FN(Zt:Κ'Ah7 h?ַҙxE!:͖`OLX01h^JJggzt GѓEŎ,2/˔"Օ6 u$QZ{Rr,mAΪ.5D9غn`fpi4Y[L0 bs.MD^,n4h6??ty'i&7i?Lt֕94Ǥ9CamC\/*Ł\X$p^qƻ;6bpۦiE9o3 :; zcEsM ˋhִ"__7o2Mmbݳ3rG\ H[l "\0xJil43ŹdjYFLKGqb}Q gFh`~8ZI.+ 0mn3Y Y/:6 ZUӹd]AIo ,n0EYţ;п9D;bF0BQ. ^j\A%Rbw`qzmJeK(Y^9J魌zFҠWqdR֩Y%p棪naMG2¯V\~DMufN$%C'ysY OF2|EeQpH|~,$lOck\ʫ8}iTϪlLP ѨFE{n8{22兾~U&fGCȟJ2H1!V=y_F ʐ .m)9ـn{;by w"R}g\c C&F7r*HIh;ӦP;N tMWE Z%0‘/kE{%I=>XG+"u*&u53YO OËbC=BA,~!vp h~w j9DOF֍ypZ`!&l6 ~NXāQS6Q^-º; I! Aޖl B}t–MM۟?i+} 0G1vc-q@_+ 71YUpڴ× ҝPdVɃ%ȒnTLHA8q,ƎZ ;se 5WudnOz.] kq5l0n'ˬwÊ؄`2G)im Y}QA|" ,/\f0qJe F( /6 i'lw~5fnVa>x~,7 I;+>M݁('59 41 vw#`/¡DXO{ E,:weXTrj @>x\ʸ?s 3R2,;@%I_T6٭/|G婗Ԭx$pgNM̚x׽۲hJ`H ['G6cr4 4κnׯےѐHqjHOw@2 Cc v\242Iz֝ƹdAe+%cFm܏bF~S؆eȪ/bUvN_C~#TK*eZWٺ烡H*Yyt~ vbϜlFa4zZkPFДŃ2t:>SuË[ʁZc+F5ԯrJBYOCR2pμZCJFj8ト4?}~ 5\:A1K/j^@1SA5L9*" ³d}[;EM"0? ~ݎ6?8ҩ&?7g/=-c6`F8,AI M)ё j>Kh\ݩq^1AsKm܎/#m}y<mqZ~52V~x);zn9v9r$DzroqlPͩ{po# j(SCGz<ɷ;p j;T_4!QgpzCEH>&.<>;U~4DKr0OCB)U2*0]Ո$j_\җ1?E! S h7ژϤs]Xvu7l_')^ʅqJrv˾)xO'gs ;&ȉ hvশu姕>W2Q `NLf c l:Q#E(gE3DG-7{E{o?pշ{clKֺ@v tr qhMM1R ˼2#[?xQr^WrDho(162sTUZuS xWRs 8ZsjSpqku7fOlˢ1NaQ6Sf\eYj9W.(֗ub+,͍ɞˌkZe 0,\hcQp6\Re ڧ1AE*hp,ur*m[]}6cqV<2L29L'.nBFvȒIq>qUVT@оm)]᥸PΣLdG*V!^ˑǩSjvgK;yF֐`=wu B <-a 9J㭇4ԓ!zcƋ2_58W=Dտ=.0\񰛀+3 8YkUUr/DWN&틵YeBE`禚q MkZ$B<4QV&qR"++:m2mD->Ŝ ҦXzڡy:F2`'pl"$% s_k1e~Vc8\ 2NwsՆ.$,/sc~5QQZ>GszeU=Y ;qM$a7|-wGvǺv0*XnGШij`n)^O,|N$X̳N-:ךf]ʅyJg҆&M*uil"/cCݎ^t0_eFE9u%=}`{"ت3f-0f}cN]'}ANGj/e xP"N)i u+.)Լ6zTP9v ¾Qj79bcߣƺA:,ƲS:qz޽jZCn(u5R@ة謤hvvm S>w֒HpJelmJYYLI?qު{y`'ְLML[mg7\NEuOT@b ~+Էtq[A_,<4=d$n~e&a܉YD,u[QbLP?-XqDbvT>EB )%`o GlϊlS(uLƷq|tz4Sgހ?S8+^Q l+w( kFE,Eh"M/o/WqʱzvBg,fjz\R ]+nfלh_*؀:nDuDc񹇗 l/qO~xܭlq@3el TO+d>,Pg#e [ֹymV}cN MbcF%rEV9"ld*,rkn5)af Ve籺.T:➊"|y )+߭sZCEɥȎU)y4A_tfr`6@0t!O2dPk e6$ןZl}zQ0oF ]`0?{r[Q' 4\PH 9{`'6H{'SMj2/b 6@kb)M@iǏ3=CD+( #kP&P% Vt\Bv *30sU7ush>#ZJ kfR95׃Wo&%nf|Zckf'\, wZ2 Un s>vZ d_͉Z͜y _xg;N^̖Њ=ܵ3NNbOW|mPv= ZxwpsHل\ wS|ыcrplC?2! ?@ĝ=KT6#_t!R!˧ӝQ 8 N8\5Q%vb}t\ F?H -.Yn݉'b *C*RlU4]#@:hA_6)x@IîX ũY<5B? ]ҽ!]xPYp%o#Ax-Z޻#?"v8օ~ ,ʉr f-[3k#:ОHRԓX֪9,]p(c+@Qsԃ؄ZܠK*8 9 *u1Dxts@ىv0)*ƭY[i;C`;>BFM2R:V)U 2L}8HnJt[ˑӡ~Գ t6 4-sgA$BdHǸ.]p|MZV;&^4㣍G:!m{hDʐ3!L$.j?z\G9dGWQeb]aAjRvgNGI %@T{pOʨ0)ǺoKfaޢ EWlX dؔHdv(%cpl P>s.Ѫu! mX6HcQ'a{hl&LS+▟hF:Ĕu0H=N@kX7j>S,QA8#1dW MyVwZM7O0uBc&ܜ>Z;"daJ)pZg G#Ky{<;Far *?$ n>R*{Y1 FQ!%5|NΒ 7npʣi*1TA"9O%~,"3GR& 4)xZ~-ic( ^yϨb\༏N-3!~5ߔ^'$B,B-&33KrҠGSq'X~lZdul~K̘/uۤ5 /؆j$d/:B n2g8q]IVX޷O$ҦxVF+%EЈAH5s7LK[%?6{gkQ:]+IڍRZ~0x$*KL,DR9H/ 1C0[vI*y9v{8iĜcfd8BFsJ@aѤ$_^y2d{ _,Ltjg$U5{V{L͠\& WA)jW'p,Rn"$涩ᓤxTi/-Vdj㙶TVn#y3;4:qk;(Uw"Gy%G?jNM榧^u&Ɋes=v#e!? q[ځ[0{8Ru܅A?`į ]Vn?+˷ѻ ecȺP=n3L;B#Q ֮C5Cd zL5(qN'Ӧy38MҲd#yJn|RI>rywW`r}@UU! ΣM#/j. 7OQ`=<+dYK -?W9 VOJȎt0s9KCOQ.vIPAw]LB$$z q [͡[^,RO/VX|N ~s*/͋a;U]ePPyg9$?m ٚON\:Gj_T;!e5YNk}> ͓ҥ.!F>=#)s+?$߯wMiٶ[!ݛmʿS-edO4Tj;䘃Ƌ0n^!*rч;)W GyRs솃W_w3Fx9e`?A^z906E_!l+!EpژZPi)M9Q!=j]v.K_ r$hìu7z'9ls5cq5핲U&{]o|3kn΃@,rwxdfTݷw!cw<0X9IC+L}L.3(k0>.BY _(-bI[A\%ZU"ԅ칣\ZIOZ!DķxZe|6^3?XqO+;gIjzYMMg[-hqFf63cɸvoe֥~?KDZ\H ˱zNUM=L`+(Թٯ0͗O}7o.{] 93nht mD/DNtUt[0;vPtsuXMATdTQi)- ë$zhb^ܤ\[4۰BAH<>`9sHؔ}/Bmof5#f-c nt:jvB_g˱Dr%B.9s1LrŠ:VJOs '2r:5` 7lK^)tqnX6zÃ_9cM8l "͇%1|e6 Y^.`H FX,x]&)gtr? ʸcB\SsXf-;v NF!YtCtw[^9;]3-(3+6ބ8Hש4'3O`MפѸe3Cz,<:xl OTIÉFlQR'Ȭ#̴!)qYj-l{ ~\M1r4smKOqO#TW.q;v qMg+M{;[_4qOYZJi41X `wҨ&yi_-6i_y/?4=k^ժgr%!g YZsAI_m #ɞCZ(I^Lyp+&}}u{HQ9"֮k Wciއ5ˮ=fEQYrhl9'n\w.*$܋iOQйC5Jqj3 MLM*63bɋ $+O3GڿnsK/'Hqu DC\J|Ui[`Wt{³Y줍=84.4$6ЏWVg߃^*7MoVlfV%_ٿf2p>e| 8hJObWDpjJ"v"YeSҚwQ╓ qn#Uɑ]eТjןZmy 5;]bILO3&) 3H3MMX]bx Ds; %Fmr*l^e,h򶇐V`.hHv5c?m[@$K42u1lv+w(]kB,ǣ>h`X ulD. 'R+b=^L_68z݊$jTLR{4j>qmB)î_!u1ˁA7i%=K1 ez}'VxJEiu[t3ag wpUn6'DM#o8B6Ͷ>\jVm9O_bFʈRgɘ:`lֿz_ՔMϓmd 2ݦ^L6)X_I+Hj!U>fK"n{}e(;sհ%:3wR2)C.~hbө5m.Z[n%m/+0kƅ䂒瘄L-5MF#?ƌ?|w1uEgo vcrl0OCcLh9 ?Z" LՋ}K>K@2˩2hՁ[k^mvʮ2_[`^baqػz,giYn1xC%7b>~cǓWIޟa_(!/7bvd s6KQ2}\9]V5|ivRxܠ{.KW:0p*m}1 -^W_Drp#f9SD8x)"oR)(JQxqN'sG*Z <Ց )+1s3Z~x~|aPmg^(iQRߍā(7h团'QExZk:ym U릸"yFrq]{VT* jjrZ4斘$^ ftgaHm*cO͔#98[񛸢$%EI$lKJMs;NkWoB9^ES!v]ܹĬ>N`\I/6K(3]; r7Mޙ,чLArQSAWXQBͷJ Η҉`e!w)CW[t( \<[Hqd/u t=?4-Xn曵3py]_E9s.v8M9r/e%xkWWܖE@oܪ@~_,ͼ9.&8ـa奱;ũesD)8҆`AKfAr lJe.`aVSDžQ=C!%3"We% @b .B^t`93zIbM9xt~ңI?JS_g q[!1W odJ>vs/ӹLqre2|D * $}Ds챃vErF?9q`!s2[J/ʹ .x ?l¡ִHtϼʾ}R 8㛀sE~K3p!5l g$h:XTm1Ɍ)<[;XoqF`yBo[sLQ&z^b+}~spuP W8&kd@VN1- o DZ&@YN y!^ᑗCZ%@ayab6%ꁽ@ cmm_ŘC3-1ŒR{ GA+ʔ_?x/3̫m02RkؕUPS?z ^U7F[Z3r"z٢MMoARf ρ\ƅ斡X5q/Gubksa!O2ȅ{K?1dwZT2S(Fhe?x]U5ct [w} ɭ~}Km\Eܑmngʻ?2cxjBR~8c:*0wgnqo`x@ v!6oPlL7 Ye4= 85u{ $ F =-N`^փ5YjabrWc5DlN[ *i'{sBtS[T~UCe4YrLSCt/t3xcm-}Yd Eq_-֍6`@(T/'" حό*AQX Ŵb(]C1Ӽc{,s%µa(}rp'׋{ϳ ^0 YK :Ҏ Ķe`PC%$lj ՜X%vψ=j7ARҟ*J&iRs%2UH,ԓ>Z:G 09JNXCЇķ:>Jr ݣ K"J&hE,0lpJ;#z]+P.!60䘴h@+C DǢDbz.9ẃ2׿~}%;xiO<}pe5\qޛ6Wf?N5GeKdʜ+9  g-2:]:Z=j  ANU 84|F5)YD2x ݁|IY}j&"%7}{A zw8 c_eY>R/nmkFZDJaa ÚGWӑiVeiNﱖ͗%gD$XaZ9#ωq(땦R嶦s۹ Ma`&wU/ hdaᲧ3L*ֳ]/wQUz}^h iqGLuMabBSSHv APxD_dޝ pҭxBn&OhiJ3J.ê!)x-IY 8%u0Xgh/&`\S@}AWז˺a̽f sc꣊H92* k{MR|" bi);hd/ A^;e0 B+v76[xEsrkXY]a Ÿ_aOn~UM~ ]|Cĺiˢ!W/`,X1ۣKqXM#P^ͅKA9xUAwg=S尾<n͍ ?+"T% \8,<+]dzCyfm1֌[S+ϊ;Mn^= &P<]*z9R@эyoKߋR`4(Z__V_p>EI[zE%Rj Hަ^#WڦDzwW`Qqbsq< Wx˜ӽf HX15uGamX˦v# m K:"*MiNÄGp<}GJy2!yXEGhaH?8U.N8NTK!g!3!N>^ #y#6~BHƀpA;ٹS+$|f%.3kFg0|ƣc8?Fz'E9@ɡ|C ۈ M@%.w-;m!Lo$ :.UFN;vWB rAƫ zĦ߷I·1PgUc~}+Lṫ6@QCATdG0=|ZK !3V|"ZZAn9"^!O ɮFere&R,$֢ê .㕄d2 cMNtqӴw:T&0:V OSZ6fq-rBh`c8͆Ktm__87HoXҦبQ<6iG̴!Z /a$nI%K zN[ֵL|˘?_ITF1[{[ǿ!37)=\A#Nqo\$ȟGWiM(<_\t`wΔb̔%q[ݡ π^ !W@)(ua+:YZiC;i)MఢdzG^L_eG%Gg;䊁SXcJaޠqǎaR Gc IsLEvIMߓ(2gڌt̄}υ~KSkwfϿeyk*j-8\,d[!kٮ(`BK݀3$`!;L( ±Z$޺k-ںvqh9O>@{+E><:`?p2#@68S7㴔q<$q'9"W 5[:ij;LC du[V͜I-hUo䧷,Ђ\xDőjlK 9T )Q^ᤖ&;YWJCvVoͅ|0o5g b5Y7_S&K &΀!ub^mas`xɫ7(gGlaUmTY۪YmWe3LN;b.j&􆱱 mG(9 qY5L10߱]D{S9¥ Y4aWP|A1x300Ȗ٠1jZp? Mw)jw}taCJڜ é|$ZqeH)zXE~삻8޺iyEHqXX.>UFzx; %J5;)e#[Y:B'Q_yA(Gض2PP⎬KtO?w -d'ĤW|šɓ#%b 4sst w҃Z榀]fmOΜ.Z2=ͺfBxYó ޡ#t~cx..?Y [r>`d={t/z)/=;8cCwfP8\~`dMP#Lf7íA, [&RohFJ ',KEׅ[%5-LC>vy$tl?>: Q=䃅ﴯdB@d"rJ\|!LAY9hݔzYh9;bs h rdec&4L$:h.#_`/J50xҷLOnt(gb~>n#%^8,0EIvOIil*VBy)JK{<A?ݪIa /am%NWwmD[=Km5s/kty65R2sT.e eƏG\J-/ \Q&vѡU'wf+DƫtX|}4~\C֣ eu5WwEɑ 0EbEK*߉lR@Q&\izG߄W\)_8%iչw *|ܔ 5!)RW؉ f$+5W`Ѻ8}@!q6g=8Ǥ}J@x5İ*Ax;>fdÌCSs}J!aM1./OC,tSD ZqOIvz-~#5M7y) V#t=фvWT_qT7d%Thr}+4ʲMk젨tIV0T5njۂx´^Jșk)T&VJ픪HDF z pd3 O hIG b[ƒP`w&إLLzfliVpMI2hC?!ĭ1ŏ ~yxwP+8@e"Wyԇ %vkYbW/>%Up ]dk1zRQ3b! "y&K0C!0}9aD͙DŽLXٌ׈ @!.`s7YD#4Z.IF7 rtNjfH-fEr3RQgAvRqSN* 0YX#& 8pw3goI+?r*.,[(iJi0xBuZԭQڌuu~D)7)J) /C&K3fK+tϫ@S3Y|Q A^_Y30`'m҄H83ɒ%{~Ns80@8O7|k%ҟ4NlI=G>ݙGѱ=["Y r0uA 2yYwWT;(BJQ.u޼s,c$߶9f(;YiY'm軞_q;?g5 Pebӌm'+Q'˓hMJ>I;k0c./)A;# GMY@y܏]2BCiBipJH˞tܮ yxh$vh:zZb݂K18 EaN6 ֬e6YNZ%O*SQ]#G`bCR3p#6bS_<(}mʟ.)w53^8h7599DWoKPΪx@i\]B, g+7GV,rx2_Gyh9][h܆̭Ǘxͥ7޽)DL7ks 9Qb.})wv{J]vCBIdR%zbJsn˨9E=ʜ*OZ׉h}mWDZfl5wD1<訣пypE$[ h!\4^ѩ\=2rɑp:Fj`1\{^8WǞ~!M^m2vzZٚѡ}A)~akyJ[ {L<F9~HI#긊5~x 'Rh0+>v\1,,o>Ui&|x~gH֗Y;'[gȰՍB*́BӺ Yd?[G|0,cWbT#)(QlKg筝wtPS4,'{e[CG%P9I\,5 .EHQMLDv6ƷNh8ZBsvɏGI{9?EG۵tyB Sc,!5j*w|F`7&0[SK- \Pu{\?-( n_PRg7|X'^?qTE#Q}V:]nrWٖ['?aǠ+[#(:|MGeԐevjZ^G̔#B)v! IHsxOU-MX(û䀫8A_Da?n!9P0-ձNωkqNI<= .9i[JVDTtEk5k} ^랡!X"oEXSwFbZ&-8۷<-3+ip؎@pBV Zb V)`wx(umo ˻rfi@r+8λNW_P&u!|'նn@+NqQfh\spky&t:Ksʘvu]#@r;<~ƛa^%B`^ES _@`N^3[ `H׭,ۍGYN sġ_'H| W֪;m鳷Q9/l1; c`t\TGE$sR`$1O\5XV1TtAX\?Y4Z̅Q!9 Snk3Lc2Oz $tjq֫t-(KÈQ8?Ǯ\'rqtzF Gډ.k>Rd+xf.=rl@s5B5!QmQGQ[p- ƒ?'=MQE4Vwd^j$HydPZe9FQi6AЅ=/{h%%v!mjL~/ QZTd2OHm\7{-^]lʂlضW6*[^ ']] 7Z;@“t|h0&DsY͇7Gh8>5ms%7֪%K$^4q㕝Ԗ֞ڠf~rݨI-s3R@v;ps^ x|nh Ed?Lm,<:xymkE/w[ @.56D6y7EC\ PV4:6RzëZ&ƒNN N".x>y[*hQ"6 8;lݰ 9c4{Y>dz-]?鴵3<9oV%WqK_QHnPKB=ܹ\8BWH~uGAr-/+EAî ŲZl_e9uҕбGHJfʁuWESJR|݄"~y>{ "F+p#jJ)ǂڲu²QI3֏en0Ci|VcxR+(X,(lyGgԟǕV㢰4Lb }Z I%1m09R`l V! In /x 2w/RoG0ϲxJ([`ZYPZx@+cv .3% W9;0(5#04wL&*zD8-}=~5&M%9q#nӴ!sE+` 7!mfT9!REw珝V w.jjx!tN׫ػ=i?X9$hT@ й Z 6ZD$$BCpU'*b\&`aW+{'eU bfa/M1:4q> y_]_DWVC/tL^qi-'Ҟp6 ,Htc D5(h: d^Y1LB%fr\EP[{/rAU&Kɘ)š%%2>zs{äH8<#"hr4m!o|O|iN!jTn"?8YIObn4m6ahe =ގ~D};q`$_mWVD"[,䧿W֐ i5e鴌X'<#qw7wINYf|f\w~^,*E">lgDݥg*˨%b}`ǟ=,N[~:, 8aqWJW $ζ1CXGЊat(Żgɍ8rKg׶0`k{(^pֽmc:f]"l T\#eRʍ3*E2-\#_=Uw@:'n}~hMMS%-D.麤D_Cb%H|4JL6?zƕe[o#PqlDs`b"sTUKچ:ѱxi-o_tMCʔٱBpޣĶEwۈ{HB LTM"1Ƃrn%u=U/>$Ҿ)P#"iCۑ%F5KySYh|-ugr^mo9<yT"}±Z.S'TY"-چ= ,޷2W]169鶆z&(@&'ꄘYL[Op}'GoXPWjuv7 `m8*L;uw]-ٱ]p> ;}t%3 tUhʹy/;qs"8 6yWq4 oR7ٝȓW63EOuE::kP@d+# LB?5ǟm*5+O iʹWS 74BS*Yc3>;I1.:!q8oQ V%Q`zUfU','nX+]' $L+E}(2׉K%Ab|ʝh A EptxOG3I򋫃X7Hװ @sӵD/=Gj-C~ 5hM=ʀuհC wܨa۳x_oQw?RK$D )&0?矲MzмhBfpzDK+A@ǁOH_Ӡ/c_HF lcg]#y_yY6XHE؜.~Wc'sS7뒴!1wI C7}C!pv46`sUqxZW+0)Ȧ($Dz39*\c}(=lšܾYŇ#ȓw9zZ[Jp 9" j@z[J?Gces-.2 \ 6,I50 ,ڬ){|lϱL/ûu@L"+y.{cDO/OMतG Qf(i4i=V|ց'w[wԳ\$?TbD3P,nBڭ` @$ߏ7Gӂ?k`~S[J[ pսqty+fc2ՠ@蹼#vluFμ+?z&~2HD⇮0bfGYic-R7EUA>@EJ$i)(A[{kv|!z.dn`Ģiwmct[֫bR s1_岴!kYKHϑH'}L!tܽc 0&j9h{LlT)]D'BIw#)/f %$Æ_{Y jh7p zM'^H>O @.87-gHdkZ5wݎ>@AţeJ)mק6&0U9BzPd:>ju> nÖ Ȫ`>l{;x=%1yB@- #<|존mG{?8hfHj|T~o瘺kTحjd0aUK1|AQȿ!ė!c.$+HyӾk^Iٷ: beՄ?N; }q&ش1SRxQGQd_6by9=ĕje$/o1DH=%{]8Іz&y\rA^CcAF?{MF1j71tokV2;~CF3k=Ua~Z0L`6ZM>Ծ=a2/ |Pob?*]O4HmX6 oTSת/]fKjvFg5K. =v4p(-RGhbggߺS2 *7qK /gViUV$ ]Ys]hvVrΫgE$@3DΘ6(_;)1,`)!;|K|St+0A;]G="0cAP1r~F7Wv B^;Q;џh=HZ( Q"iB5Ql"5BmsM|PP"x 9r!^+mE}xMYm2U7v.Xw̒r1Za cA&DN[hR:aO&[ڕ(D q خFZaAd&}F^2{؊YǢ8cv #"Ja3ݺyvhl1z>)ړzA< ^-IX+ؔ?=o;ĎaxGȑb [G驔6%_tÛNX<71bFq-C=@%DZLdE'9!Gtx4ɤ޼xQw#6ra̎k|KMНvtdMf9x-|(MM$NfJe r[ъD[WhIlb{ ˩kq4L e)5[9褪SA; (N@= XnRʃ){T)ā*Ɓ=GR*tm8'b-G0+0y1 {\L rݣ o Rj0C0,qOvY)uh5uHp@DGĄ) [ ާNaeĶ]='V84`ǯ SFo^ۂ=.>a4 r+Hf^V~/9NP4.[KH]Fq(vw%K!\s;qxra\:(OJ>  ?,[#WO6P\fJ7?MWg*-&~F gVvw-(QO-MOFJ{"U'מw:K}M[F>_',<8m&)ӯق$*i zLmS3-U)H܆^t+IևˋKv@3H&Qw„P]ATOub:& :: yti|+sx4_ J~x'CBW+ž qD 3;`jFzbl˟o_M!Y UQFJfp]Մ.A{|x$ #% ϊI FAJ,ϠutO03SQu*-LeN\w0[ML`省QLiU*\pItj i[i. 5ESnX1FtlvZeTd;>'tpZzÕ ^&l)AW~EW0 .BXM&T FiJh`T+t<.#kƥ,C=ti٦G2@sbbp^~-bW)]=!rj:WS/W<gFd(:4Ż/PyzCۨ;1[,dF̀g:EuM𽀵i(6 D要awd%qb3 gky g0a %wb*ǖ?͐ d|#%) %iN bӐ ]ԞĿ'C:` 8\Fq*6wZu$(L}{9Ay gH_Q$vp$4(heI1P;ۗKK̙ }K8qYY,?)MAvK$aWҌ&NjTTĒV(Z!G]!N~rrmik` /pM;ŝ }t`\-~G HR^T=RGG;BKp2z&/\#XI%8w& d!W^WNѠJ7̆,F]l5wt;Df2!VGёZh ] UԶ i'; Fڥt/ӸL~(%O"iJYʜ+dJO//_М3SIsfZISbf3,T~qZp7v{& +)e)0:/ Er{lZdXRxU˒pI?옵F &\>t [v.4./6qZdl C<ꃮ]h)^7">8Ү.w)>gVyyav:f7_na6>9QT)q4)jی_g.Hc P]ɜgF\'"Fu]녧1zJ ӂ5{D7ԣq6R/pU Af ܵD , )YՉF6CIMPY(xիw5yaU *w+@D+8kT;bo8 #S)j3M1dvJBA7+$Z:_fo2g55’hӁG,WWm706;| m(v.M85 , ڵ\Sg0BчaX#GQ &ߎɥξ̲`s+v1qu15j[+g0A)2h\@p4C |-pE;%;M>1R[O+CU}:%a(`ae3 qܚuD&/%Zx/Hzfci:b? Q ֩qUܞR ωbY$(U*txQS ꎆmD7\ut21`Iu/+x =Oj=Bl@vnU圉6#tq cmMU:Eȑ 8Er|v#Oj^;"|,TěӸ1Q-|Tyc%9ueV;驧|)D曠X @K[Oؖ8E~Q R$_IOd)l>ĉm?7[jѿ0[uW@h}RMJÞc{lTDgJL%wq"liG]?iIZl|&5fUslM?Pmfm=:ɑ(J,L#IoFu|! ImAj 2mwf<,UR ^13#,6T IcyN:/̍ױ! mKh$'A՘,qUK'/vr ͺj-Bc#roVhC+nG_fJ' P RN fulmG/0/C5b;V'eMLTaC,Ιh7Ԧ3I{ a/>HlSMd)0?72qt:@L w|LNSq!?+Mf#*/}nVK/r˯kNsAP;:3-y\Ա&P-po""'}!wU2EϘK3kSY]ۈ2l [5biSm. =۫U<3v;%`e4x 2?^kf$Xrn YL.}{iЦ.˓4t\ nA'*q0*&<gd'!SU' e PE;,XAϐH}*Ov(*#;`Ha[ӚiR8:c\?}6V-vNZˁFcm[o>.AhZQT$˅bl}o\ߙ*iPd<~a^g{5e/Cno$ Hu73{z [3LJOmK

    ӁQU]Zwu/n@h9!GYC,{j[W%sa!hqHlWfpPEnJ< ULp֯vC2ef!? ogF&ں@R?T01XOJ&gtvtrP{ u'B>s~_3ECL%q˿~k40m] [Ruض/XrR~,3178FǤEū{4fKddob,BGZpu'ir voPGKET7üM27!~pm+T="m!su~vFڅsEs)*U,Ƞ~m"B@GA& <08Hr+;6;~V4}[aA*ø糶Q-uW|@<ťC QD\Wb7cgRW7}\l2f7tLE="OdDˢB#%*b4gl&d"=6;vg!R?,,"uݎ͑2|è7sԈ߻6 w茐.$_"(S7clemaZXvهU)u&sk n2Pcs%ߥC{hd^O$t Q 4X4ArH_4/d1C?,j/-ط2Er;=lrfRt)fe WżVpJJBM@5;/ lcxԬS9f<s(OJTz:sN2?p۬M;-R̒ E#Z!<6Iog"?3xjvZPY'cwʁw fX5 xDרvGtohKʎ+:'52Y8jFMd[>z]`:퉞⎨8—Ewa1nK޸H/4Eπo bϭ]Id2Y'V d4}7P:d*<Њ\6+̣̚~[E,fCgTyB؝IJ$ZjάUU4;E44eWQ ~@=Uuh&hpdhV{'lKLя1%Iu2yT|-3^c2; "6 s_x,,)Hht#;S Z#|r)QpQ߷qzJutayngZCt}^voۅj늂 H;1 gϩot-H}rUjQȪqk}.ѳ]V|9LςU#+Lk7N(ފ2L>G8A OQlLZa3tk\SqCuJ9$.wxx*LуM7Z2\zĸ*յ읩W+ӣ+Tt5,;p <@%4,7G8)ܨ `~jIJlpZiF.{4zz[!YJIR -l&^@]OD(P2swhV=W9z[Z],`V-a+o*GAڄ}w)L.EN#/tO\NdbW3@Z2oD:Ӻa=MF!N۾Í򑚻C]qKN[/^)0Ҏ_cC͏>XvP@$ijME肪J3j!a6r 5n@uƢ]b6{Q,{ ($/ W\ے<"TY95P(m2 !@-$/ _YfЫumnފ :C}= b(YDAV/S1#XXNMoRbѸǃn\Z.ΏKfzOUo6kfH֬~oᒆ^zOMdf>E:vj GT0/.5؉ߤ`~ rJQBᧈ갷% Z b/܌$DħY1Y[n=9E:Rq+H*KÇ YX#6 8L\yVT"* rӮMd#']@7rrbnZt1VZ8NM-neĉ&u|^̫&?Ł\ |ugww.[ >H_l4\eVv]ݝy1Ez~.რ%PSM2;߻顕N[U=9ŶL*/QTz%4,4TFvAzmM;M&,cmFOxɭ~GM0 Tmb:.l!m)X9%>McTa g7vyO9;2vo@ZӶD{&}0+FEkFR҃Yd SfgFpƍHZ~ r%Pq|l$gsıharĂ?8,Tc~Ϥ7&u(i ;8pKt&ƞ*Zշ߬6z|>l_ͥ(DjR3IÜҨ&خB6 F0e KTz~FUSծܣjx)LG`QW6T# LFA]ɝDjJ=BH3a$$!Z@]a\H"M_HwBnPF/8 +| N%JpiasVx=(kY6c_3ݏc%h̔oB `qJrO٤Tfӎb]:Gn !Fi=!enf_FN "7S'+JVzꀕJ;;ox@.HeMIzϵVc#f!!& I.JB/qq'k?D?d#BˮEh aT#ODA/-8Y,H+iXU@,V D)s:6 xn fΌ%ܻrbKQ.Ol[[zze;!yp/1Ar]`:p1f۝/͐7gאK ro $ʣ9v~L)R<Ί M}桽A7)^+/: ;/OS `Bg:+蜔p& q>euA93Tv7`ٯهeYV}5#q3Bj:8i_Cg`g˲d^rFهĀ/3;{pv %K6p$= E5EXnDoشq@jti.&bż\{bA.A|dцNFp&)&XQ/ WZ\~ q[1nDTlиa!ژ~-/])5x3_<0Q/alcY&NоOP!CɸguUǯovwV`}ʤ9i uG"j)&vV†!&na *H{;!W!G##MJHEoBe2 J;Ҷ}I ~xkFr5y+%CиΉY uArmԈ:_VUtɶ75⌵K-|]rŔF'WI?Y/Ye 9WFld['}DgҰ],E)L;7+|vi!?o#'GR"}bXO#{}6cXgDQV0)Hh%H̽^W&9U%8fZUUcjJ';O-2Pfx Z(_]yk5¿Bs38)AƸsrp[D7FkDB<>k!D\ϥșpvҴ1^}X3$b7DQgL{e qJOiY7IHRTicĨ%89r'.DF"sR.{ñuM"('{&VFA\ν\u%)0`x8q:qv65؃ cmU^L,؏,%t%yvzPVj+#b.l:#HE+VTsZ^Io`o jdlx`DbPJ{%5~* 8xK6'HNdZpBה @Yݫ8p diyDWuʹRQe[Zzh0'&<0v_iGp$ ݯ;1. [o" hdJΦYBq߸ESq;NtQmʲBi**e9̐wk\Rt1RwFffFanڨ_*Xe@dui>b\QgSGku~<0LbR>6JV )ZFrVzv 똸P:w/wΆ)> F8V`qG%vy\@x@m@OʏRƟ39HՆZ4SL2mt떖ke,iu#"s$*~jAR6d:;TC3dgۆi|_VpEvݻǮtձKgS,+RxNPZM~yN(-?2Ƽ 'uG,At{܈VKN1T?8%#Fj7OᮍdOGא } rw K<+A[+#@?-99H8^U{\T pFi!\2d(|[9ֶY#cCX <'4mq D0Wސ yW*NM=gm/c!eC0HPi}:!;P*ra(S_wK0v;$-DK_>n`@a LZ8yYX8 J7(J7u)=-cG]ر LB1zL cį*tؘ3A}GkPG8z^^ěbzrL9>a+v#(<_T]{9 䄿i,DE,/+`x~C2=T Fт=NjC$_&U?}Hn+&%KޱQ4׼-"ZP\Bm4-iA]ςT15wcsA S{8ݨ͌y hG/\w:)dd|7Ymq>>K0X4 6N8ph;q뜬If>D'DRհjUks3Va \!;4|pjUx@EM )nnW6Y:Z%"]QHA6buFET'5CRq3rs;C}g8BA}W$ZH9>{paw9abԇ-%2wJ~fKB[&soB lb8nN宧3@8#n :1|! o`.# + a/bQ9z~ՠ}۳d'nRrӃ^ nPXIsm3(XÎ>uTѤ{SFc]VOpnT֫݃9 gNmH fJ.ZgƂG8Ofwfz$@'%fҜTA:oU`\* 3 H敲E2{soEX S/qذJ^hI|'z${'-+eU`ݡ^kf ?oǣf\AI:d|xh,;豠ogf[RO 3eM+Y%keyH!؊]N e *.D{ IM.X1Iic]sO hrߘu62 5l-? }CÍlYh(\T#{`(]qT;BP3![x~emDpjkR֦;fzU ^l=DY @ݸ|ɅU~z7?V3? e5҉.t@*ֿܝT 5m|OQ !/+J V6BHp|  ՟1.鍑kHPJ l ՙ$ L|TRD~03B8@uP΅aI%ް8 ɫKhht?u\$9ӠJy7,HYRFb@cqĔ flQ-qC圷LBXUEAE1^e XBSϓcZTq:mS68)_־"p'܋4 x7)]4z%ڪif^ .`'&Br7QRr!pvJԔ-.#F*}2G KBMNSR%`y~ypvTܜyBȓ#SI OC(=&DekJD4赬 ;}Ta-#˓C"XiW2#Ǵ'GǴݙ 02U3wxp$R \)C,'`3xGo wuV')'&MN,E3"_YT&ÿOAHUt(Ǔ4C]uځk}ԯ GІ8L## G^ӒQQD MnHjV(a[xm [RHl>Z>dG.s Zki֮'U~àew@AI*&GPwNzTSn&[dɬF ;k`=|-=vRf9E,akUh6hޏ! )Frԡ8Z(g$f`t-v!40&_C(ws-2l)Kpp1@y8<5흫kwzDCv '#Fl50@聆E^FލP]Ksz@ 7oQtF FKdݴxrtEb@,.ЛbV ی`ڀѴ/(mUyAЫ 6Yr#DNUaJBOӯ?}&jX޺q~'g:Y\ԀnnĆU(k<1$1. ʞ\Cn7%g\u3eu}2z{vws˫l2 Æ44益Jg`7AgQbM2[ Rߚ+*߻™m81JZ7瘮MJ &rHmhfA1Ű'Jtk BJ< Jzo0͇~=ū؍xR 4`:Yx< %|؎7-cL!htl. |>?v@O0ݑe1,bH#Ph5:it3ә%ymMdOWZʎ#'M.l}=KL#" 6몟2K~0M1Xx[J27dPކ??X=ߘ㟁4C_==Լ_-~Z ʒ5Udɏao'~<eG@6xb۾ŚK_Hn]Vd5A?E%'ǮGNMT?' Tz#` j[-r={a09CQ?2s d\#Ҹ)>'vL6*,%I!;DRpvH'DAJ ]yϒ^n-Vi*|M* ~ OrᎋuWxL -4Mο 6Vb1\2ȅ6dgԢ _3k4*"yX9²Zߒ?hbj $rbC갲gztҹ+O];"sJyC7jY.=|([v&^GI69`h z 7@Z;Ήr[ =HŜk~Fq7jڼr,S3zmr6+tj,Qi6mҷßi#ٽoo&d[dFWAc2S &5K5JId|7Tr30hJ.͢f|L6u!d5 3><ca͊F*YKkpp0uSY"'M D*䏴4dD%4`f6n5V0Pbc9[sT&s=ԙN\#soʱ2nz9s[SWhp8YƯ_ˬnyy2֡dzIGcP sM(YYZslqAG *9QsC}ڱ@πiۺGh:"D9tO Y[z\eО^† xgI,y.:Gof!_&/A ʏ\ޛ3\Z:کu =v}ßHCtrIܤ R7ՙ\opϡ@N%3gZNgf,tޏлK}VsxS Ѝ^eҀ4M|սb~h&+S SCN[B)\GcdM6?S3a-DPOY_@a/*yUQ^zuP×vj u οdMX{&(GC$4vH3%]Y+×#ӣ8C3ݥ@4%zCDWKɪ{:i5qzy2|Yo_\>]0a^Ċ^'ȹI|A6[5[@cizgm;FKdD36[E1)]/4L2`$B2i/3Kez/vD1/Mԧ zPQV(ț-fBñ摞{b @Kee$|`y5gH]/ZdTYR|>VcX3ke\?2ᢃ#4^Etݙ˺ T*lG6秔(R," FBp1hZ8?A\;㰬*pEFhY:7'o6ܙ"z3:V, +EOnU1 a$۞Ż> zbmNnm2Ķ H@+!Y+B5a-eAm&=JgU[)=12م'\X.< V޵bP4֪ݾ喋DkXVs'H)I[ғx>evAaKb }zĺ /nALr/c>#waK\uPVW*ˑ)i"o;$Ҩ235R:mEhd mpSwSHLe j"cj 3wMvzLRDÊjY8OOQ2ih \ש^UPW/`6҆zOb YRm'Òq:(6@p5;$pa#\\mMk8p轪sedݘPhns+ć_97TByo02>|2OO.{p2Q*NRȡ[ V Y+g)n43jdp)e7:SСbyTLb9V;YJnì6]c95iy64yL'2$x^& vD7qwD{/?=/5v@</Qb" 5_" Z]ptj׌ Q^ϊኝ+<6q"Tg`wODF$KzXyk65K=êw1E-?#S_$zއY\B@iJ2ՅfGע@VW$9":{L0$Tk7<+¶b@.cL@dYիgX0u,%c&P90AcHZԴoؗق龏-%VPG C0aZEBmEa\--G`A'AL wdXc&uQ0CEN0MNRKM /{ &Hr8 8!;㎒9-lF^M4wTޱreBU.4nޕK#f;wL)Cg$+fTuX PFB4qdU_,&pF9eEg@*β,0uZtnEVE"9o1dY<{D{/G@>ޓSE:DuAZH|dk-%\ ,9F@lfNt5OͽwGl'qLGo RNyd60ҚpJ&W{ɲkio&=2w__ZxLŬëHe?o;wxbdf`Yϩ| )"9`Eu}-c}'ڌ0g{uĔ`e{anUq;l .' ({bͺ;57,j,&qZ43d1: l#k(kr$"szM Fv8m.s׺+k:,h^Oόym'6PPN:ef"R%_?8YwvHH SP$b+ư: 7$C ٮ e9֘vMpsJXK_f AsZL _x"ɕvpzgZ e,o'%O(GV)V=+)R+Yg3n4VMO&ؔRz Ru2FLrbXj7We~|&Nn>E+J,YՙSަO*v6aq?afiM sgQ - fy"G TsL"`eWU)#\0b\(]$EpO-CȐl,7Cr KG֬ EE,zfLvĴ-L1G6b~آ얯_upCћWH~ s7)N{}o ڝ8k@JhE5O^Bv=9 |lT2o)u'G~G?J.A{&rL)r1)8h*,G!j5d!)͆ZĨ ɢ7ZNJW.Z:^N 5fN5rg8=U J=<%^pn6BPnuy"B8VyTMqeRH?V'V2)-C#Ǣ)ڝ=lmyuݹb+":,6 yI$ uY534c\0pQpNz`^GѾT\t|=1Ubr&L:=N㡧xU*^V?$"tk먏 Ἀ6;2L̕ʥ\)keFs䂻{mk#KJ=o\+QKM02j_L0MjFSJş4AC8rLwZ|!3|7c͛$5Hc4 pVI +DBCzJ-܆,YO#ٝ8gv013Mޅ1xޅ N&ihͱM0R˰vL֋oZ(H>PJ%plm,U.G8ԉ׬ѩ>.C:ѲRjc1X'2٥\4+j}FRs7^^a>ţrܿ@Uv[T '>j_7 ĽUgbӃB#943/0<' lYvE!ܮ]GYKg1m,l͜ "F\ JH#tg'Zl;ēS}d!>EU]Vyl>DTԬ5i0 ̆#Ƿ eK2Y}#9GM4@ n2|ПcDҹ? ˾647+TQ1OUR(N9+ QGO"gv:ak.~Wn}T2Ϫ&) sVGWȀ9G6D"1^,21Z0F(Wͣ<br ʝ߳ӃŶd>L(/eDq~7JESvǐ=; :$ʛ{TE9B1>#A*uX 7:N /Z /Dv^*EZoJ̄nh' +1dms*W9ִagX+ m $.f¦)5V=z$噤}1;DțTg|5c C{Ueml죳R9)) oNQHދtT29u jIn%xV4f*,+}H GPM@&JR9yfϻR'+@Ov=Y"_$yG{#*(_$T HJC0NcKӼ2BZ0' 8Q : Rj2 }d:ͭw5&ȶ#3opֆ"Ȕ9 _yJHNv3|\ވ9e/YEHwFg]hp(zxb9+4*sEHח$ j FB̈ XGuWiT uF!(۾A[r]?iH@t1,갎5-q_Nߢv*#R˪9k/1ߦGiqgWӠ c{Fh~:r)}ۢ#PT)0NƠEr?d揆.8Dޖ=10-ݒ@=~ |Xp-<;2.y;Ο.IZ^r) 3{\"W(T]Q$:X68$8v|_uڃ&o]yԸ,PmpEuCΌ .E^*vm5r]*6fdNM[GzNmokqYش\5x]wH6 a-a0m`ԾU5vʍB"6Ef&mq|ᏨϛWQ"oϮgdȧ#P)(!kW>:+* ;Vϥ$ h[6oԵw )}VH:̷ʶ@Xomւʛ]VJ`s. X4cYA%3ΓoN ^D٨mwrցj1Wұd{w¤#jT‚MIj!گ-Ƭ ,A tOcȐTHJ2i# e^ȡR\O*{GH`HKo<>8{!#xlR4N01Նc<; QyK0b9A(~#iVeƊ>"= JАWBݘUϟ*8e{IDoȖP*i|4$/yfݪBÍ'iƦf"An(@ tBk7nNfM4O:-jp^[ˮ*w5q)^칊XD%68 ? N[gBeWs%b .Yf7~йt& :$c\`.i#{FzO & `J*iƿGMB!mݼ#7DQQqx) 8-%;ʭ^z$YEMowL`vĨ4D Q;Mօ?n@BO@HFwmka"%VrxP/xQx?i5.:Kw^ 'v  Q@7s.rIc'xa 5_τ`"Oݽ΃'Orcœ f:HAɴ?+ޫa1Z4_W̝Z#:#!DLGׯ7S,ksȤ6/J}i%SLtE 'SuOQ8qWZF\4V!ܘc 57<-kͪv31'X6i!eN33AO;) jXٴڈ#UWҥ HVCKʑIML%zɫ_ɾ? }0{i5(_-:&q9d{f`ϏpP_Q@ǜEv-TEOǓ:xb{WaU8ù)tr1cNDG\^[Q.?_g bFA/ixDj͢ÁM:4bJij7n[:r%i7UL`8F U {*Fiǥxzv2Zg:+juYO G: sJ"e`.@K|`ʰG8[Vzd?mº/Q.Ii^+ut2(+OœЂE)=$XkEDYYw%xHanyk[ T ;L{6EH՟.DZ0g̪&^lQj:FX/c0BزEUzVU.Zd8 v#ܝ҃pJ2 7$U}N֮v.鸺)YI͈[ߓEƎ}pɝcD3,uo Hó{[ EQU:VuԥyC u!BgkiZ qū`յ|3уA5GРfv" +-C50lJչSq#19AܙFR{YyȾ[xnoh*V`5WW1PԷk+&o٣L ˕UͺuxndvӠ _M|"`_ YsZ"ZB~s5A"0wK-֥=ghy8%gs'~ѐA2zl+lF^*'֗Cn2kҚB2=Sִ;$pW&]]XUV'`Cdʪ&kdm(Ni4nѧYh,i2B9Qũ BE0YcNFh,L6&vptR(k}ȕD5,?Be㏍ ]ʢ kݗǒ #b0GgS Fy ye8t؈ǭGoZL2dQ[ y×Gdر1U×GFw9nkBIs8N>/[bz| Z6!q̦b-yqGpS 5X'Bl*͗Õ[IRm_$DRh C:. Jt ^"@p43c4g4gM ,㵩NUzs5%׋iw\=E P )❂'['JM#<)G+!^ˁ*RhEăAoHY5f3ܒ O>`gIz6\CGӠ?$y%}xO}0qH9hڑQJ~>@w\h}0qz:Xɭj6sQ?Q|5?Zy# t #Bvڑ Wf5 B(|Wv>bFt 4I9y' չqibMs&CZ4E3c\x*()1{#r NztCk{kT,Jby *PCd#Բ@oeN4.X3ƸJR5|_k*iOO}#ԛg`h:(96$ G@LFծp2F1 6#O{:&!J15<׬Jy`%d==MBc4K^ACayt\717܆ӱaC%X>4335r qwcI ԫ ѡe# ?Cc@wUɾ;" WCJ'!b+UDY@%DWSόĩU*e\yovQDW$(~6$>ϙѩ}*5ǐJV݀ԳK$fvI\]y|v@mb. 6꽕̐?}m\sm/G 'z$nx)3&L`"aړf|9C֮v#5NH1oxwT@8C$h3 l -҃)t?Yf.TF@Z+[v!p,*~\/zˤ͡ YW9qi)$HFpey(;L;ƿdȈKk5O-_0XwPr3욅Snٛmy2|XOחbqT;ɂB &55(LaXLNa/犿ys)ZPXsF}8(kSb(A(-^ajؼ ʇ$±(oJ[fc+n\^?{LVxj֠sb) x#+R:s\҂`  < e"cpY=Ǔ<-֮=]KVm!RJYy3VPʜ"–0i[rev` ֙L{Z-RK=DHک3a2fBAzQ*J9'VIBkҜq0 |p pwGrg^>;66{DY<79]o.PN8؁p˲j0{U4pǖMTJ>9`/V*~Hu-7xj USKjE+El{Iz_*y COljc4O2R?Gu٧#V#BX#Λ ^XI)*c*˩ߚv@phЮMĿi`N]Riiݵn$!bcs { "Hd[14%S4\3(sߒ!%yx5|*cICu}_ϤQ>]ΑEd,A{a|勠đYUWKCkތmIJOAˇpɧ[IJ7fqYO4u Bܿmcє)wֻH>W*W,fBF+s'([%bdN~wXv;ϙs+C7~4)W:b48V;sC¡4 ao%LlV lಒʲ˝:&+aak3-)p$s)\[l݊CFrPc([mP m=:LmKf8w `Ïwߥ^D (sl2^yZrpVΉ<35P!6"_t;KX]򽼉x&XF9hY K5x4epV1X9ۛrSpm{p[[󓹰,oa&-Gi(2@ f<-r$if8팀ol VRrЬ7)a$M :3J0n' ]B)oqLJU#=:~T+վh"GCX`/J#nRmHҵ+~DUPpz,R&]ȯo e ؙI`@?ntvXPݲaMB;-d}y xia}_GjPcc';8 | "oDyre}F&խ(|8)S$>0/l714JJ a@.`#rB1ekf?!LTN `??{ۉ0W|" QŅ*\R=۰D-\޲I]N2D!i'gM/Q74-AZ Iq~ :|HfMTCRjc H2{f;`Z_))4fFT_< cd%Ru90.1q4h#ZQ>G_!Xemk}\,EIorO;c7k2DѽCKڮ4irˌ3<'1[0zY843 #/onhhAӬB#.}яPd+Rɸ!@ؾΆ9~a0ygKCs``4/H'3hֶ!uSe'R#MdGTn:i3>YYXthӎj$ )f4JI#e| /<'E 0&7d|-MYB:3hǑ#2=5۸ׄq{uƚ#[YdYg;  M$upC瑱?$~2x{TB)z 5Ra^ٟhߺq1 h9`pЯzacew4.4~Oyzn $. 6 2O:h.6ۅv.A , U[S#r6䤆BWCw7!IpUҤP"_[!d! >z֚-,3ɢm[(4-}_^YWa=M8ӽPxߎ6grǧjwN&ʨxJFLU\ .#'B뿖lIJ(INs(Af<>N;Y{aDqO팊w?,PNݯf$>ywb'{,|% 2".YU EG]ctN6&_U t\B S _K-_]4 W EN[#fnD|1F*VJ[8a\^{=КmR;q?(dkUMbcƪ+tEML:_B"tLxmR](j%zm-$pv.+_)ո0g+8[ۤ9H͛mrX4ۂjRo~$cP|yѹ0k¼inScH)XL[دnN↍]ݷڱa?^ ` ;R:16C3E%L|eo54X(wUkc%}3C#ٚe+2MNpN.wTbjPZ.yݥ:GA@܎c'7>sw:G l6+3ŔVKzBNbȅ Uot|//._/^Ƌ56͛Loz2+P6(ƌqGWOq9a ^IjГ ~~X6;vxD yAv{ O.5<\6X:kUƙ{3 sD1lPCt- ]fV~4IBJRb)zcէK(|ޏtZW_T[(Q~Ksi0Umk(}q?nW4#Xdy[y?Pu aY[@:<YDUToz ؊[L^Sa7_e3U #w7jаR20D^ϔb̕34xPIeH XC(o\ u{ePW_:aJ[89brQ[§_)5,GElҪQvcr0[\CU]&bpen ؕcu/[m#ZBܶ"XktS`5y[h6`) t>ԹX7hk_ECg†2yV 0aSv3ڷO9*Ł^!y ,":ep'0W,$<)*ZgK};_6lvŮ GNYڈHn&4 @>1|Pg]AI,W9w4o^,>4زM|+=FBY-G@ٙwгd]ۄՕc:P9ryB8-awoP%.OH`| dh-cr,%UXJ:AE)&BcJFv_A6n?3Aڂ*!ы9$ӊC[1T@*̗fN6j`\(RőF$"ЕMalIX=73fxֈzGkAko1N j &軖!eΙz,wq`D?㣎'^=B-ÝlxQ#?ڵ :4.a(+ 0E̘x=)OKy(:H@@%wfQ"9Ɋ[>0\@bHsZ:Qox~W9kPtJc>i3!ie BU-ƅyE6#`֧Uи|N0U֕+x/}G^$6CD U+W:bFi\{>&ހFE}~`K5c755bF*f<גo9#M m(!KO>Մ:.L=_KU[cBᆯ\W>ԆZ3H#[ qկʻP(2g>#TZRp4x+j$O&t|ԜZ,Z+9*[!bfF+سC>2#N(zY%?Vg[w9/}CS%S0pTr1849 D"M5=yqF? y.n\U萑}j!.V}V㮜RE6?Q,7 ;~]o @J9WYI`LM߶]G{ m@R_=O[aؓwPP>@l$&  eǤFˌ˯GSiR'~P F\48C @8B޿gYTM'o>Se g￞Ns T(!o Ŵ ήO_=krhq 5ss]w߮©4Z}ܰk }K/0M&n}2AYXH62*XCk!L,lFfКIɅ ˞VBFUp#-jNw;H%sIuZ4Tn0/0Vd #w= _TofgYz#r &S["ԖM̻- v7Ŭځ'[y[5\-#u#^R'$O`&O~TM"ԸˊUmc'Qyٹ3KSaVMU;mGOWPW݇g$4k1u^P~ Ls*4MAѷ4cXRXz5?rUG;jCʥ`B܁R 2 yLܯú!"|4Cͣq:*czdsQ7*؅Qk(X. wFJ`3S֨j_oD$e( &?漤+!s^mY&/=~/ʏNwd՞f7,O`=7s m-\ e4w#KDq [4Nӛ>MN]GK>C;/eͷg%[wk1gINaM]Qi,'f\]T]].teɝH.zъnK:$F0cxD/݄zgI98D B*kEh!Ix^%܇_I )TWi#VD$O{=fhuvbkunhctO/!t!FY2R#^ gP>PȨ& -#شKf侵&.Hո Bq=2}>pY;AEE\+g4 ʣBh<Ƃ#| :,} Bs:; [ e#.-U-)jdDL}:rDDb.lu\olO"pNQmsMݞ6 Bz+<~Xv^@M8j[hӉ-:Fd ɶ9z"U5m +9 !x|RZ۩^ي&ER$;9XKhQPhޏh@U N7Cy4G|+9eMZStjgKp9/PO4 y_.QC,uǙXvɺR#< +_45wNLj=Pu Dž9T3;&)Uo4~M`+Ou3fsaĄ2_2IețmmfS+.u0D]G?s2PK>B&Ő Bt[G0[QE}/js?YT A<9"D>ʫ8mU j$R`Rs8>IuWg-?Rޢ{ #bhy}vF}ckWJ(0tsw'91 5 g?:p{rtMyYL>Fp$K:C'B@s[~&\JEvDkX-+1UpYMsYOS,<tK PV]K8'Ki7X$ktWa 7O.XfgVuw!u 99o|\tuwaӃ9(UK$/hj̒|Sal ZU, I *ZdEY.iIP_AUF)9x8?M |7&5BsIsಙ&DpKa2WERS7Yq$$sVx Bܳ7#iW5 y}E bܯRfrwL*ɏ.- _{_Jy Boo4(Cp<4[:m bc7h*N!G8H,I6ݑH Kf;`'Eڰz|1G+&yb88BLw52X[=;cʶAmѫ5xFH3 HB(>@\ 溙6F'R {G[Jf /6zd郺Wszygnz@^k'Y]{B`i2N!/ɑM>'OWq cbuF :Ə*F }gitp7|d߮ V6)wKGbvPW|,Ƨ*~mjU 0>ƫ@ bA4:c Cҋ>)p/`U i 7TlBS8QKNYv?Ɛg0K|,;Af798_̥- zo-B{俑k|UQvs1w>IO(*K~sm!DL>I5: 7s 7ջyBcE3'G r$J/g"/=C!OZٝ[2t"lNG$u27/z_ހtTm]|5 Q`v'wEϝvR'0aSq2 k'`K?x%=i~,fII"%ѩ׏׶Apbصmi^| AtJ͏y*sTAJb a{D +3v ѡ"[],˺`t 㠼|28>Aٮ2ɛOZXJgcLePKKa|Bi\Ҙpb]!Y2d:!<&7c 4_j1@%0>+[sPTSoncH=aL,#:7Fؒ}q| @Q1>]$5&'=Vepg ^w1w72/^6j\J4q˖ִ<%7YyZy RL/X!ub` 杰(_u3C7|Mmeٰ `V=RiD4n_-$=<-U+H𸿤eL_PeL,_̵or "O-w}Ei8yjx6>[Ȫ DvkA$2'#s7| [W, !{ 8 Ѓ0FlKP{j1Tҿ=,u3&L}un% St5Jaԇkh}-mbbPLSٟ$gfqC`+M>zjЊ^eǵ&u|[ TobA9k8@cv |bwJK jƕFr2qž`쳃4cs l@Vgvrrs)%dDK ɮ%8/5$d~G߈xwr~kg>Zo!0, dIщqgn~U90Pk7O<$|=jT_uԏɟ5Fb9OP;Eȃq9q59<+"w]ӆLnRJVvv1/Uӛ{wOIs IYެ ԛ) 2 KY*mYtFOS"w/J7+yXPú3Y9a5`byp%FٶBY8Y*y)J!JgD5]?)t'ҝrقK߃ﲘD1qW8pLhN* KtF Xl,r+xݚ*oP i V $6w|!/満 ٙQG<ð~iRY+h* NZ#[p<D,8o@^Q⍓c m0uq!䦺;,V?)Ña筛,r3mrIeI:kl`c)胥tu.GT r|v§?}Dv9l۸`Pq謞R{na}.m2w7뾑{S %AupP}vG3'X$O\?̉!yq`f\bu-_d!qJRwT>R}We|F^ܙ`RS)8J0`0%^ RyצHl4uiYw.z2ASЅbSq &fEX8Ĕ y[=wJjع!eߵJvAJ; JD '*A:8/MFI g )1n9~~~hY-WZw^t ƴer fIv?)騟i$2F2Jno|(x5&lEZd۰NcfU꾾)-s/g7D%/3qcb0-a76oZ x2+]{nr)UI{dMxw¤׮'$=\ 3۪!Ȅ iV,+M U#ue~!x;R+;3$Q,7I59t ǎABIO{n=zzYj3k<%]Ywh&cppX rwvo΋}əݤ7Y\zwVK\('Oy5IHR: 1qBtO\d2*ósct<*=čgLK/\ndF^ƨ0Z $]֥软 AU{<3|m<0>j\SZQѵH<`4c[YHFy_TI]i8#k &[&Q ˑ Bs>q_.lkԬ\i|p ԿvA37{S@l΍MB- BHp7*X5&{q/)"djvaJ+3&v&4&A.ۅNȾO?[}*1ky(ޗ=7qû dcrCPo`T}6t-Q6 ˓QY8g1ɗhS')HW)atء{˽#Q@L%lm"ă<,(2GX:&lK ;!k:j!8ibVZA6@99|̏,Cv4p!}l\~U`X65 YkWM$qDZ^Ya8SJn#2 =~':fU)HzD ԟHW]Z,[ˏd6 7r8kC3Pg@Ek%\Bn?jKX86-Lj1!ixJ_s8Yw@  $Ji,gk02XվG[ڭ}ꪈv7=R5#5SLpfr~ E>Caԋt5`G3Vy5KAWicE4[^˃p]r\:xB7%pX˚{7\ !4᠐lčI57TVcH;=kY[#Aۯ~/D[OthF}f4igjF-z>TZW 2(̻ Jm7gcM-7r+0c)te+da[??j}ٯ=t2"wr$VYe?Iܞ=Jx.}I#R%qdEdK۴eũ=8>*QUj$*(U{a~y9Fw1s-/qV0T71eO@CA"dFDlFmȵ=&b֗dj6Wk(/?k|v.%!dX6$J#@e@hrpd \#R"}$M7LjCCT%;>M p@i1K93 D)tӜLjuE #2 ?ܤqȘ-&{OֲM`Z<45ds$n/&ͲVqdة-Onq)N\\{fНPkSs9BI9"1t3 I1VVe6 Vz_.Zιg-vwԶ`b}n!Q:#!UO&WvPua ކcFs{x%@9_7=al=9Uƻ5Zr)XV3S3yw?1<Gxo0'+*:if6>E}=,rB$i9} BD7V,EP8gLw$]i1tĽo)>D˔7߄9n*˖b2^LqϝL1 lsjv.HYvoOu%Վn9H#b\+F yr=C^Kf4%48Ipڶ1I1H^7I9OR<;^[<0RTrNU|ZEkmK{#P"&_j@@أXe vnV&M`Y"t!bQKr[ϻpKP(YFXZ F;I7+ @y>Pg:DV^DZfʩ0T389=44gy:76y#![k7u2!.$Gz6#gyz M91B|JY:h x! 8S! OQs:bs5 ZK=?Uʇ?Nr2[Ǜ_WC^Nyq/*Q0w'5 Ik2w.q)8BX9 w}2Su)L>2n0Ga(-0Ay|xDy˪;Z+9{i+O$J@9Ôpg+ V"_mxP[I[ȰY*؍FzE3i$xeJPf(V3rajysqU$קzG30>觽T fH#,;s`c,7vtle˵yHyKi$z Ùz/^Ih<5 gNrΔx lSF7ɹ4³~$!'c_ ͹˙#.]5j/)1A̝S*-s#qxTx|0I߅;XJ8` ͮKI-Wj@TCcW}yV@iA5ƧWF/@ŰfH~PR>+Rp2[⟯mt7RN_3S*ZX~ DH/$ K 9r KN_ӵXp"Ru|zb%BSZ!" b>ɪ$0zw>e'Ii4SE_HZsa!6rArƑD&|;}cȉ1+p1IlySLEVQTSBc588'%l2Կ{q_;2U銤UՖĐ"6 k][uԸ"9D^Mʐ2\N>;$dZGŲBL.|rO%7Td5,7zڐ"6 dהL3Dзdhyucu3pF79.O%wbMs)]%8uh0s`D B90pL >ꗜ"WjQ@Jw+%=2Tgr74Hu0nK~6}*Fwr_V,m>#Xm|N6'89҅krKnEI y uǩdXk/nzr7 ȂE!%3oUmq^ M7r=jÅZᵔ@&je7AU?4aSy/nsZS]k#w W \vs5?IN&m lquhѰBWvJ*R\I6'u*? TÚ/#-V љĀHՑc'D[l"z pf݌wE"4xeV'zGu2L9Q^oL_/ÆEa+*K=aK~DR_-Qw*h! Vu;VVvw;vHqH} E?[-ney 3O ]Dg|] KA~7ASn尃vН򷕝9Mb߅w^q)"|.J/ )_ ?v~SZejoIܰ:u\5䦡?o8^sH8y5. ^R=J3W-`N?z%BpAIHx&ghF$ NH@st?5m{XGFI8 ZxŬE[z$G/(3䞣F9E5yUK_=I0iۭ̅~>~0D AyָXȼ\"nQhy'i~5܇:==5VKZi sG`T 4U -,765O N :>Ȕ.9bom"> Ԑ|8h1}Im w!ZeUQ+rJBrC3G0}eޔKHޘ\44?z̧$b|p {[{>ǽJ8Y"mZnO8 4և%z KRi ɴ!N:Pfn=IxW e7.YA(0%΁¡<ЗojĎGu/`йZC7ӇNNs6QYzL|;^gT]1NL)]wmEkbMmĥjtWI?)ZܻE+TVm`)V~&^'fTj̸ {o+F>NLR-G&LUC6+d@džNoOv@X#w=$'KO b%?طܕ%5=sEU{MQ,JM◃Neׯ,]p 6F>$9^9Uj ܸx!A5@j΢=nDzF& <=Ѱ1(Yzҵһ̈~ف.b9FbфgG[XYzAOLRs/@! [A7#@a^18|M#c0*gLGВLeqK5<C@zȧts}U`iv7oQH:K4"(0Q*\q~et- uyU D 4%+!cn\2ew )p7^'`NaYcjSlr;/WrYu"MF3,spS[""_iǞƗV0m+2ѫIaH)a<\|kS[aTX~ ڂe3១!h?yz*GODnoͳ$X]GB-,}y%hDNWUg{1z9Z! 8D˗s'S ^Dw#s`^6 ?)WqhqCP\>3/,?uzL*Lua  CS_.mS2 f> QJ"!!Z&lpP_Z[k}φpr]zu褁AGV#~(}pG&f}oU  @Nl7P8MOj԰lgd߇4~'¹r`Lred/J(FZ?h,CrɧFXOZ2 Ey'=r 79ԕdU"R`w).WOyǢ IX$Nx uѯJ3Y8In0̆oQn B)_e:pJiqpP#nJ +R<7L5 gS:2Upa ŏ`0jԙں6tQ:c@w~YK#*![jv qH-zE{U%. ~/۾VG?3(G+@yU#$%*h|לʰE ӃsǡDN~JA0Qt3<[a6̙"G$v^4颸fz !8f$ҺW&"} ,kfzf x[iOfx1í/g/Tgzq*o2/2Y Rcv`qDU}a";X<4%]fx٫F߾2lL3}0 0[+hHz~5 Q(6-# ">{2#L e5 +kYPWx*K6}K ښy'jB ښ `dԥ|\P׻ѺhnC|eMb1Ѱic``u(?BqR}uVKֳhi 511 ij22Q#$ 0ʹ+/n4 M-GkI '"u+ Tj,;M~I -XKU Ypu =ݞ J{#V™YmYbl+yTˍy'MnوZam^ U勳T'\/(44*R#ةg$mh"]E\]<LPe7#x32ȏ֎S. {E}i=ǮrǭHG|+9V yZO+FW|[ҝp3WS L< iH=?d_.WQL+9A]?Jc9#P`f'Зtv$!ITNE9gyf#;XQG$֭ԯa^<.:HyvonWZz?nyys*s r_ F puu\h`)sݫH9 4EHC4Y=Ma#<"b;…|%7Qpᏼ#Y8n9+dḨ-_{eǪCa Q@Ob?uL9}2M,h.]dԔ8>Hv/ Γr$V %pS*~-psYyLQ1>_\p\ET'W%R=$}~1B"T . 5ȿkJCHg媚CDwB蠣aSyw#Nob}*K(*&e[t3'#-]-q8{}>U5Ř-\ u-PQgU~Hi~vB?HsEYudSeؒbz]Ӗ|!l/ 0;N)O4KYnڰV&jڽ\}OÔ /udid|!|d%od8\([][?Kyִ, |mEH 6w| ]/T߰dc7ňNt_s:> WoѶ~i}L.'78]u3eӹpER aDU~xB" 3I/vKǥ`U3Wk2PD\W~ῨNTfsyoGG E^KvݶSyŰ/hϡ־X@:>\26nbo7*.Er0=ݻ-3I*IOˠ:SFMxT'{qQ _̰L3zkữ:H6* (qkmC/9r!_u_y0[{nBLGu;2Bw ˢ)}0Q%Fe[[:u.(<\8 \ Xt_.ǝP]gTH6EY@F:#!+F̆}u`0ܐEI3oKΑY>To/n>>|g$;dWEBs>,*?#W\%x9OPgd K<8ʦ2$ Ծ Ӗ^J`4zvwK0bNgUȐw"ߗ("f͐*nC?`\b"}#Bu { z骚heR+`f='3 ab"_\G3.j+V_Q/=j\[c`a|G t^ˋH2VO̅ڪ&SH 04m&h7 /fj儜 \$u(1L^5m:EYd4[-> Dq)HH}5!0pk(m "V{չ,fK 6/ ʶ+X/EM15El9DW!C^]v @PzKgW|#5cU? ? 1Jv:,98\]yrLz:UtonbEƒ4 MeZ3i`"j_$HRQZL[OW:f=«N4oۤrݒI,?@d 0= ٵV=lIq$a6cT5m4#oO1kBoB5Ke­Fs 9,M% cVKahm`IY[u#đ_Zϒ)Y5LW0%>iOWvr+'9/V&P%m"7[vv| f^51ҙal*{4NAd7HBgV[S90 ]M>sogy{٭oz y}JiI*ڢ XL_q= 8mLsj4]<_idMX >ɤEi:C zZqYlF8ZI nP_tvR1:`"[8ғ@i?F MlR2?vn|B|$R:ģZb2cIu`u}q㓷ˀH{J US*PU~P*lW+HS 9L}mChĝ#V 6"bVbX`CF:@բa!d{B)3mMwY[Ԟ>Ҥ.nWn(CZ%7{nƷCA6W @fp.ÎƹoB&^3%ݠ,KKJמ=OM*b;;k vUYc0 )jZ3\jBIsEKĤW#%9 r%^))_m @Ȍ,h$F"A0`~8PTl3W[SXt]H#FUPJ@C ƠM`al9{,o&R&#}Qr 5C> UY#sӎUBBtKu`] 47Ҫ"/-+ kwM7pk*Gؔ7]$m`%Y\,PMA#swR\>KOl YyZ$h>B&G5+I8ͷ~Q7v`G%oB/KV;*j !N .Iq XG{ P{Ѕs1'nal_vzScuTBCW',gcRjD+B9K3Lh=K{NP@I K:Kgy)ڬXJ9ipKsd SRi %s DsSJ"82g(~y ڢ/Fkb7 n6 d61иr8Lw%]_`Lpd3qU5K .ѻWA1㢖{WըdLb.1hڇM60Xj,"{WUqSk$]mfUPup1 ?bv: rl_ZXdodx*x&+jXrY%aΧInFK"0iZ$4j3 'Рt1VΥ[#;A3 6 e$,.ܔ}dsu2M KYNG Q-O e $+L̀]BsH&YQI՛$1b܎L3:ضzmn,CRJv̚dH $LfMY:}ѭ8흟n @mұ4X*IX(F)v H7ƕ510n1xa~htH67M{&Yp댁ϡOcr nʍ(,3}Ax1ϠW!z+e('n3 䏻h/ @ʜ8mRө)$UU@}t煮ߐp-ÈCHtKYȖvW"q!FZпl/,l1#\~o"]` ?cZdڲ,D/GpzQ)C* %:ZPH݄Q啚 )DDf"&tP[6͔xY +Vh"!Fj/z ~x%fqsuy AR2ר58Q\]BB7K/[ilI$.!V Y4ڋ0dfrV?2㘵㨎G"Xgh0z}X|6iKX䇡Ⱥkc0[,ܟmMݦ \y?d\7e?aS'A-'+Ԍ.ߏX ~j1NaӐA?P$WS-.oQD)MB(d҃0;/qѡ[ HX6]]\zor-T>}б({7z"EDDu-٪dώdTS Nre~oX~LxfX Hw z\ x ZM./x{gĐ673DΪ_,lQ41] 9hdvkLp 7}k#QLݱNE: t! R/le2Fw;S8y f5M)|'f+T蔪0ڔg75cK|p#σ>5ϯM2ք1H0Yյ ͢yޗ56[w:'`ks{~OsZvcmKҟzO4:FbvGYZAHrU$¼q]:R)b<pUpFν3hmn 5OG%uƋ),j{VJRƗFuFb7tKc&s.'w1s:oh)M=0_}V g%Nr>8?8cjr7-7E*;1ugay_ L`M@TRԷR𵧶WCbr*L?"՚2{z1q z1]~&''O~.q[3 YgCLA]KiJJ0A/a*UߍYm\;7ivx,Ӳ ^s}_no/ ]A 57zWVRLKߌC?v_/O+WΆ+sW4Lú]a+A.E+lO`.lhdje u8[Dʡ_omi|oZد`Vo1UHe,Hqb1zYsigYC&up \cg/P]ЬjTP<@WĵE_" ` !]szg4Mţ }22B|ITmuGC&.2(# aTW ^DzN.2'u|Q4sϫpwr w47Rw-z:!<6$tUGj5PW'5[*#Mx͖=bX(成U%$fu4mU6&ZoŚA{dnM ,]adņKlˋ6]cT+,fuqsx[Ǣ ̋sEP1P? L |hNQ|z:C?!H Gy|}NJd( ks,ӾϽA 孚MHMhHH-4т=~ZaJ\OBE4>8US~{FIϦl)T_37j!#Y97E2ߪ $+g(ܫsHfq=lgPX2\NldB)2 uj>#a*ϋLڴZpBra Xnt_|@]b6f&D<~СWT# Y#u%mؾl2gsJ'V*S=!rX(HVkc>M+s8tĭxus!}aC6u$l}woC2\Dk#M 8vp1UKd<I_Ղ:08I͇tð- A@yoWvBΫif/OkQprZ]$.(CŝBoHKcli`^!5V1Zv ^߀VH+ߔY*vr,G+p/ث {EPo{ 5|V["}8X#Pɹ#?g;2{`iK"IwmWIY|ՅH.9qW{NcR ? ,q%V#a1#vcP՝l{ݝ=ԯ0kFNa=VG̈́&+(vvc-AڔB -JcdUBO7%aˮy|4YP ょ25%)Ari$M 2t.qs⇐% (]dcYS0kLݎ7ix\Q,U(4QW5>1`'maa cƷ*x+_A#'.lBo> $QQc?$~tHFxzRj9"`d_%k:WRǁiцiifg/ oO u>NnІg=&R- mE)tPXɆ^֬kGz᠛ @D@uo3^1\SRA]kxnKyMU;Լwҿ/w$uY5UJQ F?n"• ]$Y+j;i:r3)x ؀ jȃ omQV +'HV%)b~΂X9ߴe>> e'8K2hm &u`9 צ_E=Kˑ1DeӈB\u)3*}76/S)XbFUF|Tϻ#˒XceB+; |.Qm,q 9Al䉂B}r e:}y1WrofOY0\k}' {EBe*q XM0[Nonz8^. (E1">Qc`"*͡^5H$s~CU#>T@5V7uwsuD2q94$P>/֡}*eQBYi&U@o_ĝiVlO~T7kv9-r[8Ŷ=Uq.ST{e"' 5XfIX?_c}Tܽ^ G댇lN Nړ;Wgp*e ֆOA =Y~:L}pߥe مHpï5z"eo Aw$|"[w$uҝ&8_a$pC. >IUy7uNCiSSXmX}\xP="0 uK5 tSF?h*w@IBDןxg!bg 8k~1T/CY-<6b (X/E%"8nv2H[E}ySWiq.]O{Hi=,P2K8O _ZAxGpf-9Cqr|o>? ?D}l_~%ցnoo2v%=Ba}v'uH}aأ1aNԺYi2PڝܘgmeEi.K?k^\r`VJ P*Sqq}yMZƂ!Q!}aJYD[.@<"ҷGn`i#߹z+r?Q$QŦO'#W^w+ .:yYӒr%V'%P3Vkn&l[(,ӭ~0G]IIX\/l%~Nil2ZDؿggiX]TdMf`/ mhթUg_AuJQjMs "Կ7|{YHm{B45ՉN50:iĦydpZ+Qa3> gۗ#zTs-B΁{DLyMi>z9 3O2j[Ώ5'YQi<8upZ=Hf}DX{1W;)wGFa!'x(r\#{݋YZ8up6̢ܖ_56QOxZ>!fVStf<=pӓ%x  D}O <;jG6Ui52i,3GA*<$4fdaP03fՏva9F\(s%a4w@V!Cn5g'C.)is@Jp%gQ fH85/ re/5ۆȍ `9wuWнkDC?̮ Lv$3B.Ѷ+?\ȥF[kZ`#{>x61iӵ6ZO|ӡ\<(R9p9t婢HQ>|Rܤ JRE/M`tٞRh\{r~Q*4N?id!'KiT+h|̩)W8 &M_NA>6s̈-3E)D)<bB2@ TIGʮ}OPɵ6B1t_7Oá~'⺎E}U٧_Ψ΋.QYSM<5e%\‚g[(.X.s$R(^GR"iyZ?%6K=vȖm|]51A)mF=1XƱ{StVaq$H5rXNr`<^Cx~YKFU&(δr{ x({w]\dH 3[kF=էf}.QU?(S2bu5ȃ]]!#'4ds*[A:a9BFT(i(Q< !{rFkL4ycH5/&޼xDQ-8krFȈD_kmCL4Ӏ}tb] W 3lb*o:Pa')OG.B-mf4@喽 e92S]y.}5mzkч(t:Xǻ6{" <,ge^Be,9Ja*EXPgn|O+EAY\ĜtLU'#=;93x5@st"@6jF?^rwXQKߜjt#l~n~I\!cG :n_Θxs,eپw^(B,+^4sPFB Q?,x,c2yuj\S55z6 d;"J6ȼjNHOGjZ8 0 Ci% kT&hD%L}1bü^ʴ6[y0LD> uٛWA^0]$pԲy-p<*+4 6H7ȢtN$ (Q7IC0A;4vF?(8 KXq)Zfv<̞dn#B9 xׅN!;^ IϳNr7ڕJM1_8 l*7 S7G<Қ联%U @͵-"Uɒ+YE/W&F`yGܬtQuԖM1\j ^֌y JPv\o? |.=w`| ZGPBZd[{͗v-~o2Rlr@Oɳ xb|7v=HJNth~kv?Ё3m<^|i$Q*o2^:;'2Vp+zuZSFRt*s:6t ޵q{.6:Oj(Lhۿ@ȯd@Z' -')< Ceɳ9xvY $lZלW.>ʾu-{%ajN9ru4M^& c׶O9zU's q]f9|1  ( tхhJ͉*˧P_fu(*n) [|4򌓄&hxxƑ71T(Tir6]:_Gw<$fXEqzõcv& ?^y#J˜$ye- 5^W.Yγ2fp NĊ.3WmG{뚅4w/Al®.0w PAm .FK_x7.c.s $pkLc#1J, fϚ2>rҩ%zlǬj _%&]>u#'Ja%|kJ/-Wa^/& ޑT=hM-vQ6$&58- !㳉ט":kI 0g1^Nv;us]Z6 I}Ěg ml׿x`~ {/ju8=Xn.>18[JIDmݣ@|A"U*]F-5\t=;9PXG>F:C  3Y]-dVxvR4?Ϧ,uD ߹dֿT71M>ۃ+RQ^U]΅qP5]梅>"gKO9T"eՕQ@t}R?$1t|<dZn'7_<{f6n DB_] |rd4 X;zWY sQzXU̯TEI"ѩSڴ!4ַ&.nZC/>3~ ')M p_[j|+}-X̶ ޘ">?A(37! Oʁ*V#Qy+70]ߙ@e&f?q&D$O"7ưS mfS[Vd bdeU@W!gKX\*a)3Lm Ep^j3lApq8ס ƭT"V"}Zm} vc|T-\G&u 1t΄507|.kn8 fߍ:CO~-ҕSaE*؜z3JAn,%Zϯ'i&L zQ̭^[H"D?;@gz ¾gE]zxy[] )`dxVKD!ym8%TF,{\v&L)&/#asqcq.źnW؄JC%A*JqN]9_euDƢ58l˿ u-17t;e89qX$).1cckݲ%6dչZM_Z aL5\UXoر @V&0+HV5kIc / 4nHE:WKN&iUX;3 ԋ#>. iUyUؼ2a.}߬'ﷁF esėpDErh6xP3P4aC[ i7m:EBMKC #*-fxpfo 6 p}4}J&#(Vro3 <a ukhE@vOs;dх*ͷsJťv0xiGP8ȭ|K7益y M8%^*h`!<:ϙ"sP㧹DvUnPH'7s)ET-pF1\trP˹nq,/T3(d΍>Ӹ9jX]Ʈ5n* mAW>&Mw#qz&(;H{xW|erQQ? 9%XM1_mvѫ@Q4˩X'G-Ot0@Ĺ受HKK0z:&uXJ rw-欆|Vf.~:Ta[08ZWJ| (ҕc%JӶEvO:u=1Aǫt=OsN> #pf ~:,> `SzP7lbB[`0oZeVviY" hDj\ olX}{c)NS)F5 ,&Cmғ7A7j6V6ߡYΰpjLUI@CPuDux,{VTG]2`-U`]lmGҷfh*)^)f$1͹IPAWunS`>? Y%l2 Fo : ݙ6^6XT#/ jVǜCmNj2#Hqźrn$OL{}LоXd}RXi1$Fcb}U:g@Z<׌V&Gs2A?'(F h_3,?#J7a / -Tvd? } /@~ZoRE%&kS`'xJzĘְ-ZӎB"H1'bv̏ CEoxW[AYd>o.Ӏ6/+Fc>뼭ߌ q罹m5>3oLLϥʐMJpO&];#s׬(z=ٓ?s>E.ލuTkb+A!66s諌U`.h9OwsU9R7?Ywi[7S^ZiECAA?dc-O'Pˤj ^/y(P0Y9rUp"RK7ݐA*C˱af~ȷI1z47OYgwBanX٠}bai 37[+DYfqUբآ+ϼ1:Y+RoאDP!_wU IUBs.zJSl=J_4rLo2>,sRi UFcp!O炓 WeNqpvSĐ"Vg^:eihfMPMs(vo&#S70*fx zo^rd>Ó~ZvuaƯL  NXV9tr!ـ!CT5ćX*DioZLjj _ H= cK#dtI.`-^Ƅb hKPy'qv xb ?i/^i D?ذ'@qJ7̦E k[sY0 v|l0KHjVdt*c&?|( &- 06݊xl30̷AbQfxk%JϜQ,1q IVx?4劏# 4Ⱦ|ӲSnڂvS>~\0 [ZV0Y L` ϜL*$Um2pFFzi2i9o SCp>)B~dLڊy?Y:0v#6-{ݹ܏tQj@1̕?I8n`@'_3*rݚ\Il > z0<-βjJZvg R? 6M%:=v! }' xd\+׫CZ uorp8SG}¶pϗXǠ%{⬴љ-d.mM륯=v"+0gN= ]@}r/Hew؏x۟xj8 33H]xmfh`጖>t|eQp4d~Vk,Վ RMB T?"r\BS1  )9@2荺j$|cn:2AL6J+S&ɓ 6|&!&qCh'N9̂'{) ؏(s;2ū|b\٥/oN٘ je!J\}ͧ|PW^I͋ĦP?%I.T=/6L3]>Jeo ,X#{_X9n%E0ߛp$0rBU@=>r+ƷcC+1:Lc!%Hb"`5Qlren1du瀲ÍiQ15D&6f{O4njC2/f?"Zm,Y'%$!Fw@8*鲍|dS ktbt\ODGS=a{Ln0KW29>[ePǏL>=պ;V":Ӕva3Zs o \ț߫bo`r̗KHjU p=N˵,̎b$ʎh'xE5*(AbәNb!0Mx 5ަY8AǛ=⻎7^vw N.@%;"-orw.4(hqaC0@+ RuӥTk?IH5%#BPu(f6ߗUq`I HԳOu2'ovcLk="9`&mLCVӰ" Xy%p#Q&.n)tSv  Vi4xge3ӡ{؜fbpgso[5tITKxN=BiPiO#Fѥȗx-IsQw <`; ox$n8v9g0ć?PZ) HO "-Cd7ež V]tU=I'8I1ZJUqܮ@0|6 3n䯆+BC:qwn`k5 㲚*~º5nrbh &NVC7/C^^v4>Tu\YY4+$i nD=8l"1g9H`*%_o$!zl^QCݟ}mEP57(6zO#$W;{,"z^E3!tt2O5 AFfLGDZL88޸^&@QAz"_u:$֭G56.ܬtoSHt cМ-̙&vӱ_M:E}Zxz[ʫlG\]~"K1aD;+]N2ڡA* &I҅⊦ LVlףj(n ð64X vSq(]؛TJ7EVޗp`6w[qO97gw\tY`J=zQvOi v}1(,DP$T|#fXgLk*9D$ʸzZNPm )%3fɹ`VO١ɥI:/o\j1}9{+*9aO&qCNH9jqF2orNẈ]I<8GPpvYϔy߼$3qRH 'DUj׀$!eت9"]&xG4Zj ~4JwTPbqsHp b/.UJEQZ#vT٫У8_Tf~&|oHFqv͊P/L ORqu U0L C-B;3S  -`;_|׾39H,du \2ov#ggzˑ`[<%/Ώ0T.ttfsAeEG6ÐI`do7[ *^@ue:3H =Rvї2Nr p~ge*!ϡ%6+qvsLOx6y,Lu]b~P"ɑ Y7U7&_n)ՃW..D|Q`Yvpï)̜Z{vql*"6P2Fe&MYl,M@A*`dHf}Ӱ5;!C Y"MM) 7<ުvϢg,|锂ML@/ɿscGN^ib ٵxSzNɫ8P|5JY23LqPvf~-cS88thTEH_Gp\DȂ{=.j7 ]~`4teIX y*vz#Z |mGr`R;9=da jemU/O}BJhe4W<ߖ&,Xez{+Q0!'*Tq+ ^J=vg~ƃw D@^k-CN4&j#o~^O|&/b1XM`ۗnUb4e#*3 0M[>yL톔Rb%LA;ӥW73MРD{hgpcW>ik(sg]B,Vܳ0cCo|?jsG z M-Uxo`Gn{uƺx>`^]O o"G9& H{)2EܘkT9=^S7@]J46X#_g2EahrH~ ]TI$Vǀ2dL4l& ^YS{מ $65wq;&`1JB#S!!,r(;`8x_&" (\N_FVcb☢h2Pr7  Ŵ{sӖݪ3oXңۋ[Ux(MH]Q W=P }ͽit@{VAgl=;oeط5o:$3 v95i)Sʥ͋h9 /!K1\dke]`nIZX< QzpΧ[!aUDR y@$2%HawWq? 2 >j4q-ۦ7(coؕ[SҾ{UO4ÜbJn2A!48e75*b\@e_PUi?<":9> {Z2Ư{m 8h'q]=>2x! *Eq袮?*ERN'aW$+k؜IY:PP8ݮM+F۟'Z1uJ9ɋegߤ~JzYbN [˱¤82z\b,0o6 h~̩Е7ޱ́}L}hK&{ň|XVxB# (?=EIZkFr:r.da "QjMQ6R;ibe& {z*@$+oMF@xN vv߅tqA8n|a~w):929LćrII`#=Ki٨+w7C B+&(}3=/`:h:xf SY«X]B\YCRCơL@7RKiffndnY%}GntgNrk,}pBtaO)( ,Z[ufgXnf:FAdqq.QQc.S _Ia:!qI@jm-8U@߱c \wU"ڕ$ySYp#m6hvؗjī)UM^$Cݲ`rMctQRyWȌ|b³ e}yZI r-I>o}acKĈOK Uι,1_0jYnt-󰣯^o˖ֳFebEP lNVQ"ֻh.)tj[Q_?i}t Ev9r'P`nm4zr]AQ9IHbB69=)A@K.JZW ZU$+4r0&+:kۇkdc5(2ߐ&NNz__WrDH.w0TarF(IȨ_(}Uh"՜v>"6(Q=IKuO虹j=E%m,`WQ3/ A]+SOl04-il|짋>R컮<.qWD?݌V^\O,{ T0=loЇPypcuɴԯA3aŁde:YgvU> ӨDox̉<+fl*sO"?19## 1 cK:v\8]nG`*^!Oc0 $Zw5M쪛)MMh2H$!EÍ/|}#oE H{&'"sWMu Lri+j! G w\)7RF<׊+5h8b$ 8ѯ1̼M<q1rȄ(ωx0S2(Tvs %hM{:WܡP=陇*]6faoy`jqqicqf|K} @b1k\N1XwPE (dϒ GUلLc縸 Dw,JK&1LZ%pw<kxd,hCAb\̋]y/PCEu)IzR\fg5[(W2}qP1'.#,&;&VS=B`~;/4GbwP vô.,OHC蜅DZL J0A͒ꬑ ļˌzjVȉIJOmyQy;9zJ~QW@oaH46!8UOc›r^1ݍRov iߒ4%m%&FEã*C%_e:V=-P=@x3yn`=u]Hê(Oaѩ5eKzՆY*@Jf-x?LB<94y>e@{#V1%j85Ty|7>)8f801b$sKcUV!EKJc:rCȗ F`I"H4m٫|$jBe͋! Sh Fu[X[ns.C1A1b%' "87iF2tjVbFeKTYbA@@,Yol%mLl]u³4:bǥƨ7%Wbʝ}!/CXy`}%&0>#8}B.y:?13-k׾ k {RmПOn}k-iN~.jo ]`EҎ )CʃI8bR2]68S^(ԑWNgN_]g -G% ǣ;ƜrC.X?4lGOa}>YᬽK(ro鯶 $-d+ W-*0$U~Rv!`ՆT5gCOK ɰҡhiGWP$I6ܐ69WD9`z28NCJd@JP=D̒fepL+6E:VS~.bwY.1ZlopW[2$SygDm/\o L8-ֽEb z5-*eX3ajH֨be]@{8IYrRbamSwgTMU`NuWDz_݆cv kGTY䤌9iz8_v'>0kU]2o_&`bAq*TkZ-VLz% -.H[GȞ܀BX%: *Fՠnk%p4ڄ)XTr%l'uw>Sػ]j U $Zk (sQjO7d)܏k|j t!jM0|UGW##fWc6WvDՎNjy9U&\,|OOI߳3GrĜ즱U{m֓}tOfZ; $-躱v'\\-d HAVZ=)hgRDy"@0ոg! dKأsw-J0Lh zԖiujT Estx|׊ǽo`iLԚ_rzMhN%t )$gEC]s5T>{Rc#G3D t_>k]m욱#XtKe>/H&ڤ~)1ÙvYgGzسX=^ 7  6_,np'u}liV^n~. m vdˋUWk}1meD}5S]i x>]'ȟ~|[o }iYj_W>J)Ҧ&WYˮ elb%.4L yg;RNuib;v/d3("GcYPa㱯gW*VII%^&kP4g p_94^.,ӗj9ǩ]*w፮ES\p$uw7.qZO&LeVlD#d[]:y]U \1 `&[rr~+=VQR˕oz؃bxm 8ƈ 3/s` rmxP'T 냘^T7j~,sP7T]dtmVdHhG'k W5؝QĶ3~ayRH:1]("Am?Έc۪ -RڅT[+$ ծ4܊|2{%5i0G . cv>)ApK7;8 xIe;LD_dെ)kd+V`w͐a אՖsjf iBЭIp-"/iar}Dr"{ H7 J{н^`[RBpۛEf¢,q+56fcd7<yZ*"8 Bpk= ٫H 2QyFgѷ{B 5vU\/Eo%94eE34|'~^:pnέnΌD#s!tKkO[ᢾP4Ňt@绠aao:+zKg oG*[+TomfΫ"YcpMAZ(X8$D"~T81+%e/LfmZ.70c][w|Y_(H #"SzӗnvP:6wA,\+38 .m.b&ja,MӚ dx\ "3WwUnj42&!}:B^X̃!qӊ;E48eB@-v5bpIH6U CN0B_5)%𤤖mٌݾ$Cx½hصTxNp&=XI<kHSVJ j;C,1ZSlGZO+X2jŹ@C3. ~1 !W 1b UdcTyx_ nLЈzjv.,pt.#8%M84mxX0Ny' !IJs9X}ucex +(e )@tn '|xP ؏o,$|P,"OCld0v9vlx =f/d\bJ#Mh״%)X0իX9~L({x39޾CWJD)LQ@bJ 3^5|`}hm-+=zI.f+ǛM ~WkCjKUa$I(k+$aIbSeϤ@.) khER<Ɗx_5.&ё/!ٽmkbB]!IBs4PM|Tէ9!Ae4h] a[AD+`0  ۥ$_. F Ù9Jh3ɋVyH:AݤumD[9zb8xy< H-<=r-S9HU'|%CXn)K!+:RmliZ7a@fro Dyo>O}yu]K}8DbS WK!`5R*u\,=L;n1ya )Q*QՎdxA9\Wح^ pf 2i8C$U;9w{% 9}Q78!:sE¶S0%¬i -ӵ4Kt2-x2eYSj8Ӛnv"QxW9hIG&Z+V)uH]cCVd_Hk=m,@?&19`GoBģtpmk2lh^66i>jZC:E zс:Q9'0o΃KjX-y#=n~lE°RDr%G; @~(vvB)Y>t\(ik,*oņ`,fq%Y9~!Ew-CT!טCDUnJ["%)ۈgw=RpWp`aj2ζa޼GivJɫS Sul&4OH^J j*oZT6gy,/2! Fhv^{KSZ,*;&ހTMeg!"aO2i HxʥgY񨑷d}dzduZ^WpuUP 59/ARH *(+5_R`g5q,|WQa:A帓nkbԑN))A+я  hR+q;ϠX5Nw{1wMx s`5fťKvkl>L:`ȯv_j/u0Mgv$WmbS]eW0$l$k*hcfUi ء@NDܔTCHqE~v#" fV$s[Kr?87GgC 2e9M,[;Tb1 Q.+<n5-9&x g?vכsuYm*BMjm~E05&8AKs"Y7?yD}&Tϩ9Ff\k~{faK"0bdqfF٫=g4mxE\pxS3aħU>HB7' o7&&haL/1R id'W 5AB0;_i8 ̻ػ]ø1!Fhr\'T`Y}#)& &C\g,;8e[03\wvP}h6 !Ϊ.\;/syjd(g@8ƶ0A!`p[bHZ^lӵ3Ғ&5M4ālJ i&X^cz 18Pay#*VZ&yjȘ(z?ߏn˳wKcCԹ:nS٩ޜ \$A݄ n=} ZW.ʎGo@!)W(p+1iz xۙUiNBmڦDLq7?` njv+ 5b^D3 X7N0* sk{eqwK>UCl j.);`٧WP N$dx4m.㺒 G bK5}?jk[L t:j_P"$tƆJ~DFEg9~wk7ȌR']lt>O,Ytdop˃(u+ZL\}D~};a> BBOn2j VJ)?7J糢a7Y[*KЊ%>3d\Bk>zu,Dewnif#V=4 L2Et۫WxJYBuw `v%{`SQ%~6yhYHyhr'DjE1J0yj>(=FZWh$WxX>۶4>K3fw_Ia~6,QqՂ!vv/nnNJsg?kg[h„YF vy>U[2WE@&2S?b7GXGR>#bHcO`Ձbi%nh}`#*6; .i"tTWxFBGq$sg͟u03)T~zG>7P"3Y /6u.TT7媿KrfD8+kVţqF=j m~ەMG;'|ϢaRkyX|Jo8x:|^T6'ȥ&.wm1KNCĒ>eh("q}$0ym1/g:Yǿ"L3oLh(Vb[N=(fB!ݿM8T'bu`8}fYČ8ICMn[Z}& |yAݏMx(K-[ 9Q+ ^L,,~[~cq!֨kk=9jXdo&iGm.n$ELviF~s`o}6?cO0ߋte@dL̪R Sm9} ɩr7M: T[fqI^K3))0<FC~$w0epZ+pZiQ#lc/~IS=/׺Rͣ_RݶDj=յFovlo!fޙ_ mq.XRMEUhS8:х<2}$ּd„dsχ084f︸C'{^OMkP6M_i @#R^xu"d!]䁗IW b<5 eTjI/Ol{n)9:~5?u'$wg\jE &z>,g#^u+)V7&h nF_Wf[٥{c_CRP^D5[X" iU ſ`ٖ>%P`Ҍԥt 5摞GX,Tu.Ο[DJ,|J/vKm6zBDMzDOU:K&ʺc2Xuoqg:3<6K0ra6+icrCK+ڐ2(\/F41]g6|$f@;_ ]ha~9/gj;17GUmgW rdCP?4:iswPpcp]W$}tYn j"+=jM%™@sq{OU5דs2)vb.ԥvi&;9esy{#A{$9݇Ϸ8P={ 6YUgX%Np0_g?Ax_ʫyqA*j>YAuH<.JXwB(΃ b#߈x<܈d:#ݠxFJTj.{3UjV/|HsN~У$)E$ºo74on߲W\yKkB0JlF>Ũ'ݖoI_8zY-/4oKuEzRe:ש,@4 ]W*1m- h=gEY$MKxpT*π=Ϡ̋SF˂$ 4h6,9\tLӭ$2u4J̍s`6}~;xg#ƻ/ԏ`,I2i74@Fy:Sp%Rgh*uc[Zyn мĠJOl'4wXσJ՞j=^ =>>Eӻ)?ԫCJȴ =myݖ&95[YJ,7]#g ǏΨc| @S֑t"Ek,r=zUUw_]Ё!ZX)Xms`&\um+!'BH^Bd,h:TY(eZ;(ƒOfQ՘ m@B̮6r&I~+Ч<qW?QL ͠!́dM8+b_v`l[8LI;9:UW:#$18]L'X}+œ~]l\P Swy'-u`"8%A(չ$b'fF[^E0tT 8PT%'IGfm2.&OJp4_ Q)<n"ˮd+Y;d߫FpUhωMe\'̙7l^@dGP80/G{FCY (z7tA9!y9I;Itm)>wjv8$1G,,_S¸*7d "hRVv#q0jdRZcC:<0قJT_We ?OZQlJouY˛E't+4ѭ4=X,!0{  w7T]3vlaªe^?dfa;]H,DQfrI 6λndiEΜp8Zk\BNtiq){_ѿlhbvEGD;>+q(z ١_Sol6TS '&%!s~OiWa^M]f'-F׃tc`m휑jJ~1N5(!] ~N);׌u峗*o5{*d>E>e$e~rGlZ#ܸ c^Iդc)=ky?-pEx-Dbk`+Lde {Na f߼qOEQ+Oޓ{Iޘ,uGQr%ZV 锭>R2Zdek)0?{aSCNKY *ب]Y3iΩM*-O-LCAEVvzƗ\1`@ \;U?R^O!9GNCuPϕ xXs9i [GG%ISe *i[7@AWn7W-UMڝ>$UrR^͝ +V) `^WqI>@Ƹ Dz6ۊ9 +wDm6؍ yR@4' G[5 Aka"lݫFiEɋ4;ξԤwY(HW8-lH_lBe9@=0s`C[rDm /zL'G9.ُVh9R~Y:IrW'BD,?q52v{/g"/Ϳnb:Jl{:!Yѭr['{,> M1O 06|-ɋI5R0Ɉh)TL_f@5E-Y3?>ȏ9"3|`nb8d c$rG_j;|fdsL" 5SrO2Y˨; x_ 1/=YtrYRKI<6j$&]tuޙ ɺ*1naIM,e*QMMb.qVjj=$zQ|9V6'N_k/RyǞ D)iȆh$ (?q{:Fդ9d(P4ЖoVKyyF#Q}&iq]x,@t8%Ab˧Od'uFc`~2x'EdHW!eS4q 65T$ƵHf^-,sx|a)љtJ 6 ~O zmL&[kT$iu{V^Gv%>{u2~US<PP2FJdkr #+)vxIQe8t;aly։,f$iFg2q6<^Lu0Ā|t/tWʍ1#UF촇 W&JqM:}GBO(8-`sњ$Lg@rCH.Bfv7!_Q ѾJOH^x".ɡ\5 d6[l}=D#,D4g$8 KkTĒ?Jh9Y%O1"L jxgRk05Ҏi-*M~>-/U <+ŖCl}iGb#PIVaEG'UPS$gط7 ꤀}l퍯+SX@z~qEn36N&ugEf% ;OQ9BZWyxe=~pkl|*i!X Zo*zc W/GlY#] pjݢfq1*gV{ q&bmMm($ ڀ,Ĕ=)[ܦ& OG)uNMU!PaNp.KGG1v!'d+ȘsVlsTDhkCzlzoTaI%*K#VW_*,\؇0 2pɲM-$(VXֆf ׹%w:U-cTHQ1\XB:lF:d-J);XئcoSD6#oa+{Jl4s{giÑZg6i;+!> .DKݴ&UbPqMa@Gk0Zh<;LV#e|73_gce!Ncfx| DaխU\ifКBt(d#'mvoW :g]50S?kёQ4dRohVzQ]jcf)pq[" t_ 3^:+Q]*ݧݒQl)nfsጶ.)֬B͈?m{yRN[ ; ^DÅ2GkM;g'XRA!HyBV~w s/ȔtޥAay-6s,x i@'%@Z#ax ܶ8!>?R鶔OHGɴP4=9 [bg l)E E¿|񂆢M6yXla-lN6O"H[,]r(./YC Nk'Uy#GAXZn Xkmʺ W뻗d#Ih*_ UҐ'Rl*.@W*\}`F<5ֲE JD61=ධ#ÿWRM V3Yc"bјDET%F` 6{ާ@e{ F I8fkdK+;W3~-%6 &,⛣ޣCiMҰARP6:s7,"Nk/_Y)7=}?`On%P oKJbg^wp-,)i)8%~DTP޴W N?õn[WA[RK} Y>3(rwv9C<4e Ru9G޶D0X{S zʹ.yjA~|\Ԉ];۔q]̂9 Vv!?<6 z% z&ңRN*`yimScjqT$_檵&]Q)WXLG, 4B̷TXpK׈F]ZXyLd&Gn+}~,aΡ:jZUf-ר3V_*]1q6Q{/ b~c9&zR'"l,`;kWtl|TT`F[ R;)bu2gpk$pa.F TNfo/f"d`y0,i.8} ?d8V 'X)DNDD}@y U)r'uK1vaƵO8vY)}nRQkyԗR%^zD.̒u*AȍD\VѮSM͸5Iy zs9Ir.B/\ .~C=/zuۘJASI[|e>~[z9%jnSlKW{_1E/] v!ˀ5EGm8SyKtT*E]a@wOET*_^bo!>6$Ǭy$*"iUnc5z/E{њ݌81`$7{>C":#:_nzD /_H%(V~9gzdn%5#1 bjEn([Y]bu[k3Ƥ~+$L Nj|bۦ" -ro ):1;si63hrsr8gݘg/c3'!Eg40,rDaܫo#G${oO:q~0̷M 8-_tδ//\=UOcYe}WyiD,47[H Klk DҴ Õ[BþskưR*)f#AFQDq0_ }4ED!mf׉x*>f!9ɘ MeLnuV3%baORշ,tϵB,{CP^dz>%3,o0 =~R/#JHR%ovw+]zh0jO/䮯`ǷΠ&‹fHaW8gX?d4KU[Eľr-Ij!d,O~4HH ݶ *V*Hbk=^>z.a&۶d҃Jl~e8|F%vzn;}A;u'M?5Xnsog8}B+XKSQklE}7']hQ>0lg2*a˦Dg^G\!3rEΞn*:%RMug}Gr'wqI)^x\]%H}RMPɲU9: "#?C7D'rid.=湠Vh M\._6I $_7JĨ*.CKZ~Me9p25YPL¾~xD8A ax\+1SRWTqoJ]cʇiS7IYPw*tzpjB̞f?D ..\*`(vIGsfU:)*0K ]U?1.'*Po @՛h9m/a%pIN Zn*c]do+#GmiYyC 9_u"Nq b :my16^nYuGN8!r0=js|~_ޡHݠu]@0 zcƒ t,]\|HO|T܀T HB̻R2Yopn"ǤCt[Lޒw6xO'xsbTLa:݂Jõ?rt qq[l< ޥ=r̾e 87g;|_p0fAX  B}|Y%,gc]Е]:1=ZO8O0ƫvigbp(=8 Rcf{t:}`(k^tt&+ ZddSڴ&Z˼ 1DҐ'F/kO瞅k?Ĉ.HɴinCyٖq;⚖2 lFo~;:./-*j]]v܉n܀u1`؃N8ӻFjN.0V^9^dQuS0@+┯'&rF,d/b?u>$sAA8D5 Tg(mzH1NCI cќ3)AV*iqXe~b %H% 7od_vY!͟¹e[7>y4&'i9Dh(bLjӘ<=#[7-`ˌ]&ܨ8TMspM~hTp)Y2_'Cfh=_:s!S-hd)kMڨJy(:>w'nHAr3i0Bc۟Y%[MFJtܺޕFqW Ziu$7s:n,ӺY\hģui41fhK9R0D) _}Vdl7˞E2>8SdևLԍ⹩]Eӟ-~#;"SN"GD63_f!P e,o:;oX9|F]4{ԁ:#+ÐPYӓaDH}3ɔH|.w,$L]PwL&Q&V-E`ɶwD'~>(mXcg Ժ?Fϻui$(4(`_dRA1lUԀ 8M_Uxl5cKou4@}>vX+~f1B ،VZ;m>~fo>l79Ta*a.ify1͖":vKfMZ|Fq ܰ6%Pu V҇',9 jV)nLNbI(]^n~AvG)ϺzAFmBM`@` 6i·]knˡ t8ے>)]K`fWp(XM1vuW+VoUBxy?g=hW[!)/ŮX`jhgKİX>B~`3uʔLQɹ1بɈȍf"7ViBfc2+oQGgz`  _B*jz:Tҟ ] E ;Y͖Tn =V(VA}r,)>t'!}Kzк="=Ӈ]GY~VBLPb.F(bXKfU"V`ZP&Y6A=ڟPѽ^D/S_IBRK~ ?)9w9/LpNp؅@C]DlOj*0YwoX>k6f]K; ~@%H 5\b8202B*AM9;'8K5mV&u M{h z6xM׻9e5 ON"_^}h_+Q+P]J %=JR (?́U譕_?=O!8琮o++/y~g op@` SiVlm1Z~ٛǡh@x9$kon/牧BS t>ڿ-q5PlC2̳^p¼YϏqbCġyN1+E#ߊ@ xV Ȕ]#O߻3gޘG]uʼnj>=dƾHt~ȅWRd(K ^4'ۭkZz-1|`.7af$mRƽ {/񰁶T=XPM~-p =HAm2'C1`ң _K. .3W̭'s.jh[D]p] ŘôZ`x#NtM03<Hle&2eaHJ~f@ȷ*$vxr+[2 4Mjﭑ#8T<涡s0'QuF7Ӥ̽xœP [$nWTԠc˿xsh!f崳ݞmh5=28^ oS*þ/"a?niX~u[#ʎ!9 bFU,%-}\n?hy-PZN+gKe$\SQWćA0;V8(DsjQ-4;&x8} PjE] DG'l5\ؐdL;`o#tRqVR= u+m6㐯O4DA6O\)Ǫ$8zfr̒e ۸'Vm2 ̥UjKaq Zޣ%G.f:"f<1QyQ=狸+q| &JAC)CgLӌ-g~=f `ds'yߩl(zT-_:Okt]IP1։^W,& ?!J {5Hj/*2EUgȏ*]uYblj/~hi, $*olEߍM4k` xƉF  Hx"^8:bЉ^nn}vj4{!P(xS\x/>s:fw2%U-X`faV 3K^g~ӺHFmBOÀ{ phC1rH^Qr{Ѝ ޚ\y[tv㶾*:, X잖QkX$RqȵLO,ؚP"|{ 73N ?-,ɴ, v ?pwz|= ,ѵ+;2Ii_J>*W '*B-8ri@mC0;X>rQd7Vc~S4Z<%wSw H-}PثmT3bJ!# m5T&^%ߠX -.h|g?'m0 .R? `7 cW*}ϲeA#~7GPaI=.yk3bױSTU,^6N')WR S$;d-.ǰ fMO=;eZd헂4{QˌeSsU mN<2u6l:GjnߍXQ9 IPXjɵ' ./y_mO6kD\idx F4cuMYMv5Suv "D:(s_47+~ M+y"H) 4E?w sx[S/ٌQѺ(,.Oo?rQ P[t2adoyP~݂^y2qw)R9V6.s\]fBzٚP`\o !59+άK [̭i-* 0|4q/1T;@[Ocn{bbhZ 2}|+'Lb,1fuūDU>1q=OgDdm`[pleN?|DZ+W +9Z\`5.,wB:; zCC?.2EXO̜m$`~#fO[DL.[{7J^  CTXa:Q@ d~ [5k{>UΗ2Ð_ٴYז?ĩz0L*>h[V73}t䠛C!v#Ɇ ZWP0:V)̼Fː;Q8^w.oQ{2]ynqf'}s/x`c`sk({ 9[zoM%ef0w;0#1kzG`^rw* r˜HQm24ZJkz tU|W,z~=bE.#3B ;s434M5>ZAt[ҴlfL7A^-c_ i^/*1_7gUc(+sؕllQաث@& Cy4%4Oy`ej WV$22Eh-cZp]aTӺa񼷯(HaH\?j! |oQ^.+*J˅u (CޠZUJkju2tk~ pP}-7N"qJ⡞e,3mS M qzۈI>nnĆU%EHD`_pkO6#=y ;GjSn5w9f 4?`6G@{F°V&Fνa0x9zu`PAt75p͐hnMzpz 67843x,Sw% kUc=?3 c_7u'dD"-;m7O7-;f֠bhE4X?e7 F+ƵUR i3zbaFwR!6u_iQWCͰ\@] qa+EzCs#ܢ/ :3*l軽 `6GrIa`GNcbSre5nzuqB)z3WKۖǓYQUC%ؾ/jUvvк bw`&p3jkhQvLCuPq*atC8' '_Mî~?*Lqh.$³ #tP1PU[{5@ju?J, yk@_ŃR>}t".p0^۾(D9?c ǛŨ6(F:/1]6Yn՝Hҗcb3Ӷ[ߎ*INZuY|\" m],59=_eνs %|v]owd 8) '~CqD欫caI UX^o+rs8N} v&z# :6 ul̡ CۺzbQ Ҹ fJ+e{>mLjP`-㈱얥R魔Mkq$VLqKP[u m¯F94lS HNh} 9L1N%w.:ma'])E^2NJZDI-%7@0DIE M!@ck,~3'TeVˈ43}JTU S;.TyQ%#t<-Vš?t9Yxhߦ VO㻩T2}y)pIw+`Ĝqb-A8(q}Co"3NAqioCIFB6[% : /٠Z_T.˳} 0lov:[2)ۇ)/0۶MvddtAg)Y=̻J|ORJA$]hI#Rd0vjVj>y+K[`3YP]RB]+6Lf{ndaR͵7)]ڪj3ePձb<; z~yo3#8 XFa`hѯng•s^S{ n@PO@LNu jTa ;{ ~k!DJΓw9 LzXu+ͅe qV`XUFG(ש@N""v?/,/fUrg}0u6;/|׏wt8O.0?xBQhlIk4;@ }o@I[_j,#ux-ьVdE CMJ 2yMˑ3Zq53;56 MmdD45<fsG)חs/Ve1Ei+ cKYLrtΛ3USdvη!6UI&g1W*)=;5[@GnQ@ yBZ`BͳRK0l u,ŻlWg,{/)D0nSmc m xśᅪPKzhʚ_Af ߴ9Y^u鱡"hP3"s0pϵp : Lc-psBlGTvR# f@`D9~p3>-X '',|r'tH*&dq0 zd-QX.T;qe%XT {ѿ!yyi;̵(J*g.MiS nޘL`.-n!¬YU gi*(1XF6(AiO`@7N _3h}8|Us߱ s^o V)i]#F^ w~H s=蚑d9h#% 4v 'Z` ZQ.j#ZdɎn*`}s5xJՊX,3 g\Xgxw#tnj k,{tD*1}msa%0lY $W&}ER=[V>i0<'aH~3I,L<#Yac_R|p>ֲ+ȟ>Êm?Z܏x,a(¹kX9'Bj' ʌ69Ťz ۦK[Sڠ@hO",3gJ%ĺ!uqU-o♞\a'^LxPwefVcDVټG*74b*I .ψ:9<|F'k$\9.O(׍k\WSa%\ּdNsR"Q$kdy;YޗȔѮ )}vsT@'n *k !lbAWehK Ok KFTXUPҏwZ1o>?%)C.wrq?蜫dO-Kuq3Th59GfP'eτU8l ({v铲]sMݟk˓=aguA<:54 \`Hg@F1?NM޿1 BMl ]R" ͶlwC H⼜.>8Q]wAP7pHG;+؆+2tJyi?4vf8mRgl*W^6" ",!a_eHtXg<}5NkH"FZ,y7{ЦZsp0+Q3Sٳ&\ /qLS'p|II| px.怺5!5RN_srthtJ.ŒG;0`0ΣMsZ*"*#&ɘ4z6(Irtϫ \!1g[R!F@S"z$GV^%|XjjX(T\'Kq\ ߴzG "˜,}nT?27'-c$14>Pr)a:l "J&phݛC$6y!kHm ق.EUڼgWMEe I}G粯v -@lQNWj>58o0ZR;lIY*aA;J g%"1JQk9 dcʯs8mA f5tuLf>$aEѦ `i3sIŻVJMt9$TR]h!n*Z ӄGCBȎ Dۤ[ZC&+N} &ANX c>GOu7=1آΥf>\x}.aŦ[엧i3|7n?@wP>yQevsIμD2i,R~sczU8Ŭ>K6=mN.Lj4[QY&+`dLn腶uCRcoH7Hc1iDyHLg"Kml'P8 G+@ 9%xP,paAW:53x.`f@Z9m{St \;J.2<-b̂?3Jmt>h4(NS ž0p(dɅub3whn"i P^˳a`ڶc<ړxz#5 /T/46 a37N(Qof8+*.0*?`:^9.l8xce!.@ xgX {I[#Zn{:nƉ-$j}2-BUE -niKUIUT)eR旐Dg\t6߫V)U$:!OY-5nWdݞ$:S5x!"V-D^cL"*/x+i`x od}o5$a6n:6F3H0,񟊞;|gyҙt\sH:_q 'd`W&&#h5љY[=Ѥyϣ[W4ss* wHux>CY8ǩRΔa؞mz46BXjhk~(O$0wz]xpJ ģE2b))B}xD~z Cs3s%:;,2Iv̹w3VO15LΘ8rݜdTj:lG^GcsQKD⑮tb ఉb4F)O+^ĄNm48JB'neS7))S%6]+]uvnsW+m5} =u PF{T_M?3>3O 3T1àmQYs" Ô-w6W/vObSjd<祫왿`,ƩhegI0@=].-EaXaebk Cl%#6uK5k|< /^4N=ȝYc֓*_Z7׸km2ǑSmD Cdtmd*ܦ?MxjHyeTW~5NHNo]2\YAаSq6_Yz-`m[C&9`nķ&"D01*uiFOe,wP:FeA!xx2>F}ٸ#*JRiH-/J>zcP*d%Y:p2I j @^)HNs9"o 6x`٧|cYyNH /Pv4}Q.H پC,#fQ@-D慇? UR$iEQXnb"|߈ܠ{Th]51My¹[ePF"{΂i،)""^zH3 >JdM)T@%aeK)vd7S@}L9)P#*`F׌D7-Pri,V7EAʑ4:^74~/PYHM@ue2pk?#?n,ԭ) 1a6f0{"_+ZX8nxULW<|MsTEw3'ǂ3 ,A>M j7U^WqccpTp9Bwð'p!՚b'o!Vչ{zhUeDžᢇxlxVso)C,ń) ળ0s&IqO?T_y< n5\ 9e2.+p:[Aӽ]펐kAs8䱃e𴥞͚q$ ~*QU}c@= 5kM/i4RΆJK #3%~)TU);xbLT ǩk+r.hDjA*A3O:7׿?bϦ~Ѱγ % u~RQH[d/]fRחfϼ#*xߩ!Dn&_J??MՕ}(s;G豌m}.\P{@tl,Kf(.W\%ͿK+fDS5 R$Wo.e fD47뜻[mleDxh3t]Q#UwJ@(EB,adV[ZFHD56ѭ]»O,J]//{ѓ% 6jNsH F=P*W" zR?wfgх^ }VTolda8h ^2K  xUᑾu+ 0J٣Z FC Ѡ_=CUFDv] uเXü]| {lʍ_70Jdw!NZNҨm? ٠uFfVەV= 3^ȟdu늜d~1L$ntK 8h_GeRl0d&q9Էy2irֈA#qAy*+Bb֩]{G+ 쉰a?PnYuɇ9P¤Ac.:W>ѕ{2ݻ_sB*%I?Pzm$4.b}PʑZl@oR67Ѵg<j: OSRbCq^i(/oX@ei(u:3sdP$N0ÊoȄ%jR}* m8O y;zŸ8)ó\Ϣfiwp4 -VTާ Y)8*;0,P Mᰰ V> -BKtXi";nCLZn  C, B#ԠA1!X# Kuvk*АGj2uh(?$Fص;rC! ;2<_я`e^QX,UHk`gS6 VC T4=3}_>3g ޵8c jQ}3 /cv}fK7+GRGڷw_FZW{8etL.+p]1-RM9 189(~8ji8+^لi[6Hș&]% X^GHF-G ?jǰM7jRؐ؍ &,@A[>z|݄>D12oY `sREiPJn +8?!%H0)ɬhA2c0q•fu|ƅޖnnsBAЊ:,~lH)͓ OR=7*QsJ)WՍw$ frɯ76B╲ ST:gi7jݓFw&3SI- bN q !U>=(iP7U)jmj\gc"&֨+R Gԃ蛛(?M$Jptl!?G|hԎ|4,V$ مY\z=O|-b pH4Wka(2KOۑSPYk $%WWBUkky暃\y47I:Ȉ20=E6G2qRRE2T+`k1gIn<4a݈m_G{ۻ8t"C z yN0#G֍tCOG'FSIЯHHZPv˲T}dl01^8:OUQAXa.Uq lt>˶ b9ыGM͔B:@`+BLK(~y3YU©f1W_Mq`MQxXh"[fgiMKSIe. ViC؟>R/OS~㐣BnѬGj}(Sgt'tD(Īyޮ5bE~d¬ .szV/I;tΙu'5pi~])N!9i:2)RrzZ.Tx5U:R{wjʲswGO!dEhk\ż? m!ѹ' k d OE Q k-ܿtD`=¨bi[\Xl{p$8\ t p5G"`]rL_Op`{d}platXi6XΟ4lkx#(q@I4۩?W"Ϩg]0^LBR_% (?E}@8>S?-,,K:LvI]QHxV'B=-PЅE8N}ʢ;Uн Yr,J-{c\Ӕ*"뷵vtK⴬<ո.~oawHn1Fpž2NC37vqT5AKV+k^V@tN`@ xvE\ Grh]+z&S͸:{ CS,NCG-At"&Hz$NRleH]a3. ΖFGa͸`-`Xjyl{aes[.)t駭r}Iێ#2L[䔴Lhȇ[#*ƾ\:I+ :h"!Fp X`yI%hgNii ݧ r>,SFP1 &yIY.nj "]RKw1x/¤:h9]d] JLn Ԯ## cȵGMGi70QșI3v)XzmmʼnAEڝi4T9ѹ2 x4@+05xp=BZ Zp:y"u{u}n2=D)gkT5!'}8bR;>@ xgYc'u->\ ŅRm3j4]T# m]],.ڛT];jS~ w=h2= sR~71F$W{*~$Gi\VZY`X_^BaV7x5&O8hu\jB5k&t< <|M<2x.͍uA9; ;_ǿ:qXe-(/8 +"S{?<3oӥGVlw`m_ż ,>t{b|4nlY(dn Xk&-@^k3QْI[q(`ro߼ci0FU=oTQNzG?$_? ;KZtL.glX|_঴Dn$K4W|~yVq]rGI)پEsGJѺdtd< 2;ۅn/Sa&;ga>'t$2Up ߺk.G86M.BzʼAH3&_-&wὅ})'t#"Uک0 BkG17`B>(p Qid<M͇bTRwoKs%5Tm~'.u f6͔ۼhD9#cM'Og1{J?p: J?Nѯ% Eʗ 1:Up(H[HOiS6`˭QUvDό|@мVuH66}=r SſZK6c]qμ%+)vex ]F <->B[}U mwS=x/!"̅wcJ[p5.*܆tVV3DEᦊwFC>C>[L۩'B(Z~eer [kOϬ_&yB)fP't3%=;`y6Xn>Ovҭa*crek&>kfc<! }zOޯ^ <:ψG(xhuO榛`9vl`|w/Yp9`ێo, wBݠBOߗwDGBPH|f0U & E;a0lE}oUãDlU/Sp [OA掹i5!Ern t #( :/_Cҋfi!S\f=~Mcigθ_VO怍K)CUmh$sS?=(U%?%{_}.w]WSo?nso&@ɱޞy0Qo{l sVkV2Ɨބ袡i='š}z녖YSJ z[ >f;;M>uɽH"@Mro{Lц@fU+uƤ2 &Vlwٕ#+8OX6-1p64䅾6'rf.#8p Ia~I>rJ_q#~JEcFw%`e)/94f"pc64^7lg!^y+}ay!{̢kx&2&9eWΠ ='5}p.ҫZ2Mx;z˅Mma[P$̰$0 ZU m*5D7Oq,[Y4^a[Zb 90jфuDWdtThPT},wfךafBtԶqcꞳ۬B[sȾvʨn];g i0?QŞf: iH@)EgL0'#uw4>ۃ[8ګ )qyD ` s4ϟLn:#j [۔Xwy,pY)@J=`C3ga5Ar?|v$^m.67FrkBSz\1|0`$P:09U816I/ŠS%wXㅃzsj~AC\dV !بAmdI"H!u+)_7eL\8ڑ Zֳӊ[/5 D1kؠQ7%bkGw#"xI` bBOzDKN6BG EG$~3b Sv}=qs(nyqq਽C-5Vm3GCX'z5GnRҶWv/v"~8߄7taҫ])EY4^Ԫ]cwv=N/= 䠳‡Td!LAG'f "61xl >n'(Hf2E*k ㍵DrKG6xÞ ^Rj'"2PfJ\ X[T}لgVTt2^-EXB6j7Nfi:|>^=AdT2iFaw@B_ ]7x`pЉ;khY$V?m3ARipF/X|T2xNDg h@ą%vi,oq*(iLX 4q AH/LF^hbR-ױˍ!.ЎEx;a$ C*ž`'4,,O%։A]pz2G~ڔCg րƿL:cV˰Sr/a󕝔j֬sl"ё2LE\IS1wڔFtn)戤ͤ6=hJG 5 Dd ~D|6%/ank_豪 ]B$M%u1)~"ȵeofWGl/ți ؝˷,eEqJ#xu)p K1av(*[ٌfFۦRfX)hwhel?7A,Z&60`lV`D3Si["9qJ0<1(& +XS Vb'#`]V'25T G\Q*`MK"r>Xcd.2/vsllj_l.7c]PcQSݔf- ^oģ7q'a 8>y{꨺sxخؖ=|ƭi5|tLG&M6)S;nw'=1b12z Nn@P Kyxo1{M$` Ylx9$Pڥt_FGo)E?$d< ȓd?ry X<+"ۭ>C8n&޾M^-YN?;bpX(&*^Rch$s!qe{I-YǰI+yuxR3LdDo{ 'ujbxL*j|FLLER E)Wd"nO%2Iy H+nFO#ɉW-auA.uWBm1na;:*>aL޽ec:AzY޳AW(x"la?-fAb|JsA']T=Qk1JKˌG;0%r6LhfQ*Sr8"—tɰ!grwoTbr[Lɭ-+"QSF>ZD' >8Ezo7YFXO$Tr%)['֘^oP]xw\#  6}r):=UೣvB\){vKMI-yAd:wOdnc/^0jPm3iF^#oE9uKCiOi~Ω#iqNr"\]/2C״ƪ5 lkV }.W:}P`TRpO. l@z&35nSL I#0 dt[TGrUG*|$wH'V~!d_)~u!UR*ܞ"U7gtk:叫Dulwe;@ԐG)@ ߜPmAIi zY%f˵yeYFvijѺ#;tNLbzcsWm `NVjQr2DUq`Y챤9m:!r͑p5NY4Hm[w=eT@^enxO &҉rh.[5uwb}_8y; ˢK/b^lή 7w2^5Qؘ[oj 3$l}YNYY^s%tc 0Ja<%jf , ĦM߉ofCtȜnY[4."*b8`lNWO9RfN[+\RҩzL*10?)~oT#&g(q߰@?hz7vI"%tr'oiw|d_K5( sʾ͍Pv%]Pt`Pm>F*~!V/xݺDIl0Q[*t]Չ=;>e&-d3φ[-ݐCIE.S) h6L5At@Xq|ӳtPBk1ys4<i5A[2!]%xG7 ϻQ,dƏ́"H&gA[rf&(c1 U[ dC6]XTsiFM̜CϰL*& \sdOM.Gd+A)` }ң%_~Sဣo̰Ɵ&VeGMX8 -z` Vc);f:o==K| xY-08@i3B'rUo|WT$яVf7_hkU.=еk+BL 2Zei`ysЁ=3@DnPX[׭ g^y`P`vkpl8w}nW?TjXr~}xtYYŖ: -͎g$I| _Oe/~W[ⶃ@%h6pEsȏ B/+,&ϋņE3uVt `۴`<(iCyJ+ܥg^'? 8¹@;>A0? ֯vxG0-+|_Pk,,>D3ҧ ڐa"m_9L@wlMi6 Av^0>PQχ PV{}DNa˝#!.屘K2h`JbfX7$}7ޱUs$F` 4@=e Xg寗=T ->_p!r|>8? 'V΄3B@ DƸ6n?ٰ0"T̳.gJp۴%VtJlNؠͥ[\/2zg Ӥ3; ٻS#M |fϰ:]uY*!@/|L^ms%U_˼Rǂ Q*.@?l2ƌ4ôl/l]q>- {Dsb#_ؠU$tO4@>]ׄD. 0 q1ʭz~2WrQxM"٠/~yW]њ g>DUèY/bCmIT= MB)v|ɯЙzc;zN;x\XoC}~NEhr~Փc҄bQK)*#Ɯ'``5ᗞ<&P g `BnfϏ^|ePb p*6hix\홝 ئRׂP@i!Obs|mR(?蠏1G=P%AaT$@:"tuZ#u4J/ k`zz)%BMt?\6-ćw,u`V 39/k(@7}r%tpܬ:{({@Z |:Q=#<``C$lk**R4 \/~yȞ=@X SP~tл5F u\zJ[?}Slx_z8($HTOduN"ҔJ`ډxc<;6"YLr@t@iT*RgLqxj?, jm=DEU7=4*"G*0ž RM3;%ṼFŦ%,USWvUX@1pu69rEea~]9)X8j 7trb&y%% c򼒌*+<5#lbgeENy #tWY"d~lm$S#i¨uwK !oTgаf<Ǻ"!ztlh=mZ r'kds|͵= Jx]v/@zIƪREmnLH DXyjm15cHR RZx_]y-򦽚@XoI7 (70Ǐ Xz],Vxl>i*g.|L֝H(#z۷T/Y6+N7S %[ZnTL'8f!֌m6x_Pqm鬢d1MXDzoGUۖ~/ BiYk2ELO l梉M&nW<5ؚQB=+"{>gIbE,@^nA{5{=oW}ƋOP;-sL[[+,?Yo/wE%jL%8g١g"m w,UR[vT\9 ŲvwZ{,~x[r#_(y{d[4ңvj0YuCzWK_11kGdPҮ"~yB~DYk91o!dÄkh܁/rLZ :1񢴊zhKƲkۛՕ̌G!Tcm 5Hꃁ&5%Sej T\ d*tvu4\.R*Ɉ^Hϰa2n>Z=]K1˘34pءˍ>xift,{;wh2U"` VKR4o6 a?D2- S)4>UFyjsQҿg)-h6~=S8XqӉK޾ 7fcCbIٜR?xdڱ#%MeWdL:)}`K8oM!̊swQ&,Zd:6oK \SΞW@b|ւ/Їm9>ڤ ;=G=ݲ01biVHxʜk9[D!O=F3=`e #N/ 89#W~mc4{ZStb-z$/m=oS6 Wqfh-* 0U9) {tP^+- ryp')Wh/7̄]6C}˻VH=)Xܭ R|zcVo!;luζ0)h7\[ב@-H%CTD`&Llp( mvZNֶn3|dIt}'X}qfi)lg98+*UVj3RՖ&lqCLOL)Ҫ;WZ<|Xft l̇ 6ʬ:shgp)3FDB73;ĨI\'m[+=>(^cH~l깼t 3ZHSk68D!rm|.NţhTrИ#KJlsXl{Ip#J5xKR~^գ10ۮ:*Oc_J-+[L9r@I>: :Ti:`pGfKYՎu B2ڶM(^gɊX8J6j^Hqj<[]ZиLhn\˾mvnh5۶qʍd`x|]@8#fH iŠvm DGm;b8%w 0 ~FĶ8xTn`{ N &t& db`굎g^/k>4:KVk n(w-Ȃ~|rusɲgp}8BLw!nG2ǐyWp~Rd %ڹ}zb3$C3gf/q6iA&aKJu t!zb oT JR }G&2CIԣ?MOSgbx޻d\@GѱaH`9$eEBp z!x`i u2{ԝw؃@9G&L?Q7ٗkno 62"=cs {}0a\GYl-HPܑoRVhliy}Nm"o] mg#?ʭN餀j=Q߆\|U9`:&c411e/{EV +C#M2';jT䴁UZ3ߗ&d,6U.CI=齔kP&\/ QbrnBd1$4%v|T}l僷'P>sT8ZV*D⾧ P:aVzOujCUB-/ w|(fLBJO2u@ِ){ xkj0 LulǪBϏ߲H AM 0=0֧u$`UrQ1jcH3~@'Nap.]+\ܐ .jc(M^Iy =Mf%ku#-8z^On&t-->B> LYՇ_rt tgfdJv+2А1q2އQL剮.7W4x.,/ Wqn4.Z6(4(۹m?"}u˯U#ԚE8"{{SX$I¬tqDڱ>n4 ) Z'+) \֚̍!/+/K 8*fB8`a Mu׀o[] [q+JnB{'L|lL9,` x@EܼQyPr]L< Mmpf>33k>Z|s}$j*hVW7 t5 Ip·#bg`dU*պf:@A.  KdMqHP1oFfUJp_EaWdKKD\hKH,V7qe‘ ө#Mh 19Y`wGYy4}Tgy[%tp/ g_j|K0T_+[Iq8R Bem=.L7UvLD3/''2#歒}3&h39dٚBD s݉%'SLI3vΨ7G *eʉ[ɉnKIHuChf _tADыmRxd95 >}2,g6p"fC&+G:NaD1==SRt7yB#l7!_yNno] "doufT4ϏNd ^ i Y0;3ƇҮVC;ԯR t8rHKY@f_ͣ-l nNx[w5S2'L*BS!>qXQIw%'Kr]ȩG\\~E[>;xlG%X/κ] };bϐhqՓ8!>F3 cZ{YV[P]@Zז$Rh~'$l.2~>b A MΚt; .jAx4C:_+ _9AIt 3i;L~Ї *8iO17ȩlB+[% pE3tl1U) |-i;VW9YC7};+֬Ș)c6K+嬐Ou35 Nj{1m̤!KlhX)a"ѡ^cпG5!1 F44zDaĆmvKr$+le^铱CM^*mpYr,H0ee|ưv~1UqxKyw)D cEBi qgYC9g;:B*BsjQ-`t_N4 טl߳ʦ! D~<*TK 2gB-Dpa 0qn/FQ77m LڤKDk@zO~)-"Dx 5U/W`l\dHR$\εǼQ`7uu+\nӍ0FfBTRc'$ps_x_!^5A`RU,{7MnTHU J5a~1Ui3Ql?Jjq#*L^#/R4j~A7iX xc\B7;M3ªKp`_BeIZKGUK{} 4y#i}$)$w0+N,}$GK/o.=\h2 A В='ELqtSZtӖJ7*>Зh~??;H-yLRϿhM~MOǏugvy1\{vA$@8=Q C`(~oY?s=8XEeG%-0l(]%&.agxYw|@Oȷ ^.-yeWx+ P傸ՅKGϭdeL NR|Bg,|DQ ,w r -Kb7h C4eH`B߱> ' ې(^ά(k&[:zҨ !2 Q3)STx ,R%]ԗ*.,\43_cdA7T;]` wz2F+(vjxEl:F/} fx-ߤޮ$Bg =_i/gC@1)ȏ / 3`#dPSP? Έ<7tɋtWܓ{s*t"![qdQ6=%xD>L)Sp&e a EȦ.&Z2'C]_Iѝ}TsJ㜾B%Ya 9ACn܁?siTW$Q77 nYeX+ܽE^= #oդK)&{;BT 821ON<EF^v 0 aU|0ɏ) nRUӦj_L9zY襥9xB|RF듏-*  КI;5iygmKTAx7 14"|Jw+ؗ5 ~ct!ǰETJ-@]5Dک5a 8q!(Yۘ8dU5ZW/,mBiRoZ"JDΜvH$l4\Z[ө8>a/$0Qa[OĉRe:Hdq$J4ހz?8.]q?gzZKҳ3"˘eu5"P Y;MiA܂t8$UcPePWɔvyԯ\ kt)!"l ԖhɖwTPq n Ѣ}7F7 ܸZ u7b] a㮕HYŬ=ҋhXוK<}kSt5P6ѯkFc}\*Q9Zy3@џf3zJ?@B@@lmCtQ|wvȺHz]PefK*0&JhMϖ}ȠC n^L R[gGk -W*#<-nd/Tp٨>X#Q* b|]~%SB ڌ% e_ ތY1@xޥXзvy /{㩍 o:PP_Kv[\](d0]%jc%=im +4cAAaZ6Dˮx$N(؋d qe)0ضXNA~Ety?‘לy7HCܔ:O'bdg\Z/wd]!3VEzJ-`s(SqN0[H@œ7(Z yXm:=xrak$?Y5_+awJn#T5{ˉa /X8嘞? wj['ywGIJ.\^}jhi-T}7p>d4.5ZK#)"FٷZwS ;MQ 6O%πP?,h}oS >1F8؍rů\6-,¯&?$nFJlS e)iSLG 9KUJS31&End~/AǨ_]W'?€b_SXS\' 9_qTl)C?ǿ^jAs̬\Yl(!f6Œ=lHRG"  m)䂧B!qj QgCi_*TU$缤L 90,Dʾ~bZ' 鑥+ʙqM<ޛ]o4ѥ$M/}f왡4/N?Es֜Rӧa޹~W~ w~wX}S/:U.QPy%h-gE:W-8\hP(Ĥz57 C "f jʰz3YK+P$qoyzPTGN=Rh\aҊ]>!F᥁67+4νٟDڒmya_>Nvhs Y7)D yEHu7 ^[Oq7XigH #ԣQ8JE^ |X&Y#-[3?g?,wH+V pd)rCcr~WEݏ)` n.H7oZu퉓WdkOOw$[^jRi%#q>u2 4u3z eĚT!Cv&5Hb=uzL]eÎr'R*;&=Ʋ ZL㖍!>g4k\6ZaG+$SP m|n{sعhY87S T\%)@/f\ϙ%D*z):5>'4^ w rN`qca8(ڝ.&) ;–*o'f/.C?v^ArqJlh  ^oPJb!М-hSm`J^dE>!KjZUă2It˝x Y :경~aI rW"}:T%>W]{Y[^n^bCђ^{;6i&Ha,Eb3QcէUr ۭ҃PJ\ ܥOW$'R=) 5XSlOiOީF~(`u/{X=8".v$- >cRpwR/q<` |@@ ^uU)0a܃ PT8~e  Ar7cyv@Bsqj&Z@~E]q#U߾̤wL: aU89S^| ][@aoհnJ4?\?p,JuֿSBܤeڿUh~) ZC;P3"YŶk0oW ``o7I@+W|jGN&?I;k:nE䏲n`Q`5g?l'lLdz4指ldX7cI>[%SLaP8fm_߁J"L2:+DͶNhH7Ʊ$NQe|@\;zsͬnά! *`+Zs¨T\K)YG 2d|^YF!1'ե_9 )$"U̓- U>;#L^p7̪( ;::EO6r4dqT}pP> 8':Fe}]ֳ77@&kbgWσZwZ0$?޿ǾW- <Zkk}$~CAԮ9O$DCK@c$9<"uܩz!THE•G,jO\nvjU"oIݐ\ $*>U0Zӏ&Ia] ݁*ψi ØЭmBccmb:lVpݑryq8<*I\c ]oC5[UA/8Dצ0i1'0\GyɲunJ96?ݱGLr;͈BmLj 6:BxP+ X% v1Fx{*eOp[?g~_ *3.[JIWHEH+_c2An)Lj?#xodla )u nҖՏ)Xf!}pQ R~ݦP UMa$ A ;5}jZuU{KEm.= ̅bsJ-̟p!M]'q[.:[\&G&&K>kD֚u-YnY<T B{;,ַWj=Ce2 5%kR|hQ_ (Cbj3BY Gݵ˹jPEl`g`KUo&;&m+{ۣ4EDM޼> '2/pAE푯_Y?ŬTh>,@1.%%`&t ʱc$[-\8 7'cfOݷgC=>i+bT`UtAXKY)ABxy>M=G]==1?\Za/&{N}3ҫyzhrXkbx^1 WQSYc9,K'K|eƅAh&Z^\hװGiZ<Α/w黳 fCrb p]K 2po`ph(92Ͷw0&0/gLjLEӰ=Q[okA+'BHd*+pG)3rܾe<\;E|_R)|HV^IOls|q .W_">QAM|*~-#ǧЎ*Q? 5o!s}qD+;Bk=aXqXM̱WOڦvՊ kCNßg YB_-~bes/#babǚo JbSgG0e4t{( ALwxQbrjvTY#Ea@)Y*fC bC vѺ"H,;]#!Ř G[j:6VsA5b[G)eI)y |p `ڐ&;[mlδXz[D [y{W ó9#rKq*uu1O{f̶S yP1Z(h剫=ϴa+ɛӉ=n=嵀}v !eXvL+q,)ph : 5rgd3ՍF`G^;Cd°ӟ +t13hהhGB M{Pv>.2^XXlT^1Tߡoܰ=0&ԗQ~`G,"otb}"#;0f\$sb KDzz#a(I82yUMNRcd~oB5KPO_@"PGU8zCD4+ C&Ƒ71 a=$& tin٘ GMR48zBbe_}"&yGP:%4p[13 # ^;C_byiNnƾikx9Y6epHz*Fr<j2_V46Dnixȅ 5#5{5&lP:?%0|f6eЂT9&y7\PViR ݃sw.8uq ?IߋH 뭢:T5*0|"Я$Y(dLv34應$MJfgjSң.vg!W .,I9uQ\(eaxi @HC)`Tis>KQ/Ém)òx#p%d w/U Ayl5:>SxK5c/a"~Ipd%XK$g2_ /vXBQahA Ku8uL"Щu, £,ϰaКd<*JiMB&icKkz#>cjR%uSڵ(R cYl%M 豓ފ=?@BE݌OfrlKشGw jٍe31 }`m au长礒{N4I0puOA)cYuPLԭ.e,IBID\ k-,+?m2\tX&jy/?\f n^X$FA cHޞAbşJ*㚻!.A`l#?"{}!qQV@a7L_rTJ *A*5=/V=Z-ɉF=Ak]SQR0oJSjmihorLfy"fL^t?ċ\ٴ'i$ F/jlcZ偋h<Ϻ~h?r%uXx?V0fn}={K <Mvao4] rf%.jAW׵n*W [U$!*d2)QvzZZ$H0JI06PV7t:+Xur)l iVI,dLc2. E2:!tRso0[4-XoP|]|Sep`Z[7]m@*0bRt?gGGJ]-k,Qx$CW-xAEO<nIz tf"?ec'3)\YhEPLT~߉M#{DM;__0 ĕ6͋CKse=}|&rµ[a9kx@fF=# S-uК,$ qdr?kΕ.B;G*U :-rR Wp;,-f]G\:Gl VƒAP˞"0&_/^-?h)7~@bFj/C] Ï&%wZ*F\h:1Z7sZӷqbCཔ(զ(v\u0#-Q a%߇5Izr+J&QʦJɻQ/j)SKx%L1 UAAQE,6/1ET1eS4G #YN\@Ou ܸN͋#XjDdJE8R qKkj ޔiQŸ\? *n:jq0r0W;E󼫸/B"_,v]틃ٳP>OGdL!vAä1hNJ޷'hkZhކT̼ʑb)f^z !j< wL*zA^OqhhT옯 + #6QGI'UDw* +#m,;MѶ#,p`5#OqSrQBIG냘a,Eƒ旌X9{d{<e-6RV Ată{a4* n-3U<4$4UD- )u cn:ֶbE^45t!e/u0ߕ&LbEAPBϺJvH1/I1_&PQTHʶ97oY%EK;HܯD(q0]ˈ̉vgW=pGrspО(]C&)0r"Ic敾Q )ʸh'z>Ű[홳e⌵{L3 أyZڶl; ^>]fxr , dZݲ5'rEHq)gԇr ow-X3| jF~O\=a~F:d'aڱ==n={u]f~B F# k"MJ~Й3A 5>sBeACzq(D=9 ІqE,_ %agtk71_StX(3g:/H佲D²x7TxuOs OJ0[IɻKmE&C4tؙvyֺ!|':kdAdφ{)T1[{=`T!+Tefs 6ROaOIv "{˨2_ $Eh ovm:ЇܪOM] `39%+u+K Aw!Z}wf@2!>-hSxFT>S"jɱ7}1^H?uޅ&w:ԂGAa^8I;[QQ{-Z&O"GOpW'B:jy`^cN#jA״aK8cdQaG>L@`DGx!RulX'E޳EJ 5"k?yCѲrA3 Np ;X jb֜8zIswa jhP}p7O2К^W 8+~Y A8eIUxs+L(L%**l8yke(lgڰ+gdy0baJ5ц{ 0TU|}vzSh`O{u#d`~{ Y"qƍ(ujZ/q%ݏ,%⡌(j7d0@)]vx۴M Q>h)8UvyFLVPL`v?k'љqRܴ0N'BvdmW@IxEJ!Y390M^YR.61L~m4߱BJ}c&hcX N~R^9hO*qF JR&;ϭ@ $4&?]gwK:ݒwª6i⡍cRa;tN^M~pG"}[wXփ#;dJs UnW{ 3q}<=sZ-Ÿ0E3 K^E}@T"xT&DWh&R]Fiҙy[5횱ϿֈpϦ{\e%]aXZu ΤWGzftJ-A|(\h|pdG/_K,w3;_'j${>V!(H]ݺM}oll`h>$p' |;X~9 |TI' &" dĎOI`E9=t.A9eѕtW?^w'bTH4WB }Q|Ipj_l-dJ@ÅS)7xsl\ɦrB"XXN1qu tB9ßC0o%:,ʼByq!&7AܱѦhh^ KqS$8h%ֈm}_<)P$';Hlr.5UV 1zHbOz'f[V-D#SVYBNTv*nS 4H!2>2>f %M,' 3^(\rB) yEq@k ;;̣~m0E\^jx&gD:=6sxe*T$cbHEc/ۼ$㕁O+*#+if1=gCSڪTHs |qC$q-?\z(SŜ0#m\iS2:$s=7"J 23ld"d?0Q[4%Y߂S.\-'Q$Z 5LqgP4*Ov1=Wǥ^oQo,kP41 f{7Uso|,xg.}|, ,/ Ufƌ`214] Z7Qyׄ1qƉ¢f~ɥWs;xZ3PIVN*}CUtgh} ^0b5tKxqVYd&J:apD/=ZFrAZi T41DRի'9Nf'L{uG:_fS'lF'ީޮ/`O=m se )΍_Rø<$X^ y+Ml.z46[kJb$+q$5Ouɮ[6i@j 8o{;W9X{): h b_;al*$"MQp3U>W#[A} `u)jxe&>5Kkq޷ZGJBGI uRb<̔vxy!.?䣖̯iq;[V1&pEM;s^t䪃m7SD~ʊťZs~sRh_謟$U#Z-Q4*\[jXd3TƄA/\h+u^ɻмíC5nF#zGHjT:Ei I,XHV  k to0ֶ+yezت0¾rpgH~;|ԐT5 +5%%NC_xOiKɬM >dANpMڣ bpR 5-TnJ'Hϵ}$XLv7:t5{-P iHT-]UOV_s u+SL\즗e؀tTHr_(Kct3 ΒCnwmfL5BOq?fCA FAcewb8^j܅(^P7PUĵj VOLV&Ȯ$͹vq|g9~و_JxHA]=׽ޮ;ȥ ed}0^pmh>eFT}rF.1j\U{[ӈeo%$E.qeKЕizSfܮ4%>A4~EiSdY&kqmLR?ۑBlL9T˰KؚKiaVz ?ǖ1>J^ZB۩?ICɀ 0RA},bs]S>ש-@{[nG_kFJ+x@m(߄ۭ? t0sn}wpQ?V\V^n Tu8ٺzFSe*)dzڌצ ֈ%w1Ԁ$8SxPx帣u)Jyr0lB~_/0<ds2 I*})u}i?OrC-l>t_s5.sK+r'3=SX~xքZ#] dY]a9P}J(Jn*!E2`օᎣNgWlrsiKg-M֨GA®[hBcOwh[n'U&Η f:|HSN2[H$>JiV2UDGl'ιcjZO8w>2\M#UP#xl6"[^)}Q/6 5DT.S1AݼkrViJ= ;7dIo4AsX U:2+eB,p,;9FC+0j*\xʊ;o{Xʧ8}[/H4(/eGNtLII/ߔVx^@InmLO^dQڲL6}c[`P;DE9s4_eHw*{&XMwgqڑr*})D؞^nFLmeJx.q$bQXR@_1vg&QFmkA$E *HZE|Yb{ߜ>q¬}}eX4#W:D>_ .zDشtݠBΛ ?̄rU:H\Ͳo;5+ܣtN!Li7bt /o ShȺ\ ^T2E'`CBC%_WQ"XЭ 0<.WRci '>Xn'R"I #_1.u08oz[g=: :ً%}zυ}1ŢEU/].)xH,?>qdg *p_A^|ߌ\>uVTS} EIr^ƕ^û.Ȇv>:x,˨3hFS> (@SK8y?z*b;хa?P?10TaNWmȸv|qOL hMk]W/ĝ9jO.WYc8) J4VzE`?)ޛ!(k:6:bm&Yior3NkcV`]4WЫ@1) CEw]8=|=2&t쨡ys{зG+:$rňG ,u|˾ǡV#-аO ;d\s>)i׷o8% 3 *`%Ի: Y`|BܛiOTl5xI"3[H}=V}VOҪ@cov,B]Kk ~YŁ:APU) <ʕڛ)E+a,B,޹:Ux`.u> G nY>ՓS%$F*Ju{}l繂%ц#a>8>۱/RWte S`mW$;xhU)=.),:de#WV^N,KQ|_t!_˄>y1YIֵO6d0)-"٨;ͬ.fhĞ@VQl3Ӓ,w= ]VV` jYY'8"ϲLM8\Ӯ_Y 59qh[w~kekOL.̸f1Yޡ:-󅿗PTNN 8"/S'uOϭ%@ߛلɴ1\w}P?WT$/~tT""cҮM}BuӍ eo CMl[QI9`> 4L좪I3N`$p-wtk ݿXOSrX!F:WCwZ?D &x‹Q>Fy`:T4T*hj]ёuFy=adf__TQGg`c;*2L[ ~62NQ $ɷDb.o'2N"Cnsw-Rܳw|@#l=^Lt`<NnJ %Y0=4!"?%jumhP>}JM}i#? bp̑K:uhAֱ7ada ñmBWl8GVAB_Y_bD Vv[n2HjaI| hR_PvL=^٢ikE;_ jIfL;݉H(  - O:(;/)<p[сh6+!^7 A6Ϊ!N-68s7T$]^TD117>"` d/GFC hʕpNf՘fzGB-ynox: +gDCp}knRA -J \b,kx?X) <ƥ9Uy2Vk80 Uͼ^ɧe yYk[P^qK<#]pC2ǡszJKxq/mZ(Ogv}; "ߏ0^Su2"ld,dP8 UA;ׇ̦9_wލ%x5Jq hy4C+8}POuJlF12k'b.̟SYŷanY'O~"MIְ 01GςeB}[},{Α_ w4TiwXfcEHfP͝6a+ǧ_*NSޜI_a5)Z\=& %,ISF鮙\i zqjVQiƤq>~GYV"QQnHn6Gz;+<9z0b`9졔wT8]Xj_GzmRJvM8E`XGPOٍQ_|Oj .BL;[ -Mx7ah1gڲ6xRvUA8B0b|ai)@)ӕ?0ZЧt'0t/}ȼ'_,_8 yXX!|l (U &N[k K@Eav[ȚWhZQ@EBAE5yl+#zCb[lƾ&m#ejEXhm}O7bU< ONdg=Yw?,f6 &NtVAl:6͝t 1q^Ax(['3eX4|F,EUB7: |%f1>fswع+V߆fռɕSia#J{KLw>6RmCD75m/ev<yRo 8:+S x>ĺ{?lB~)kl+ı R*XκG)qs$=쓓 JJvHU,!/Lg8"D> .꣋s`ݢrEBB3EǿQW+ZFxm@`Nce!^Z}M(D4(w.0P-EFd(y+6p{8ԠpXAhpȑS7ۥ?kId@M ˩8^̾C:8ɋ[@Ԝr B? }w2QJV*хP׋AC*;im^u"ܬ, 5 tF pטWtKKH)YI" Aᵷ?6 rm%; $~=۲m(UwעΩpp 1Y^BQk^{-EpT _1y Qy͚S Ϸg8 ]uҺ.Km/AY'QXeTOq(pHh-*x+hi:w^&W92bX>e爘}P~0wTxX"'64\&%VBPA{\ڰY߸T0Yf.Uu9|q4I&P㼴!pKOH_hpv&r,U/2 q#mlk@˜:wz݉//C; f $4i]FEx5f8\>lěϳ:7N| Xɷ}mcǵ+ssp̣(f&mmx'~cmS.Kvܪoٺ\FhBi>v.\ҡ#0?lyFu#{/_%G]Ul4h%CKv䖒7=[@' f% q8*ܭOɕN ѵmJ6 ǧBlF$&$A(F 0Q7yYـ/~hǘûྰR5wڷU\2I33oAy}b-j)$n)-? ,P1b*StalY#=rnP鋖 H^_PJ)ԝ q"_}3}3ns`1;,}˄PizɘNG2#@EV[ԎO$!uEq)ˑԿT"ԬVx6Uq.~@7ٿOd9{]<䉊GzNjC̀V"{2ԠtaE L"?6TLt A*ض@+l,s 8{K0ҕѣfKot9sE~,4C#d*ާVR9z }6p*b73TX\%%e{24ykׄFE1k^tuY 7@gQP%]f˞џwաt&%ݩ i,[o.$+JH$݂GNP2tW?,-^_Z 'k)niFd TDA|"ĘN[ȹ,'O}y'!&* mHVpBnL$;t8%!]DD`(@PUUBuZ$y c-HS/-X6t&e&udbVkZyV;ά0{7$3sd8 ğ<&J|:'fp 2!YXaDuXϞ~OjhY1&q[q{T =c=WCOE<:ަaI J#pN #=mtN9b.hmV1d EffA]w+ӥuop2Lcw\r8@lk.Mc˃ׯ9ۚ6gXlXI,,˯M@?`i3keYyN []2_"}NjN~Q&Ҵ֕%Ը]\Ǿ& bVV+LNnw=Xy.@|ڊYI;>lg?#Ҁ/0u&gWx v#?^vӃjM&<_XǍoft\ ZV±ծLOeFnݹ{|I٭A3ҕ$*60^5E-*E[/y.7w6 M3@qߏի=tE턙V|b@LPLU\E[R}i\]XJ3i LJDZ3{g=67 b5[t=F/zÿ[WH+6Ah0]+btB xpasC龍3n2 8Wlgє{= wʴ<:SyD(1[T pn%=M:{B'%PPMZ{Aioo]oF6BuP&|.نvۍ9-_|;M/P39tA+yX?kީ%tF춾W/aR7˽V gXDq6?T)gSICjs kY[/w}\ǒuul?Vy[* .<\)LXX[ hIhc&rdH`cf; 5ּ{;ZƲRPoi)(~cqR/&x葲q s]Xg/9|%#nMA:'~lyT/y @]e~xz14[q0-ڕÿԻUD5S\uz:?̓zfbh:ϟUN6.E RqǙqObw0*]O]o0>)6^UCѼ+@p(IMfEä)=R^$<G]ykȌa,e7 -z&[TJ<&z*.kcd3$ΙI[0 zKݬz;?$'ŁC53ǏTA?-OczX3~wEJP߈>#hqu,\ 4MLq9hDqsv 9nt=ٛ {i$功 :pQӪUr\R%TwHG@'=# VvArǾjH pڸ9u HJp>ib/N5.)6!zIn6oKRŷfz{@ܷ)Va$ os9el//լdGQ1 S,DZ0r-[Hd}.ӶA_P j=Ǹ~oe>sdW `+yQwS+" mXF^32..&N45M:}t>(isRH FdΩιF 04Lbp6l/XD/DB@Qhkf o2b`'._[U>}IzV謹rZ[+@TAޒẓۦk$VɍQ'hRRYs')~e}{nb$CcI XÛ/(*J?x6]һ?f GQ΅j!1Ҏ-rPYړMKw% KL!_//tfRb>MwmKy$7yW+6푲g'$osub#8f駴lՅ+"՘3DE?bD5וv0'cL0c5_?.ޥPBߦkV_ݛ2# PK^Ւ'X_4Honc™eKSAeGHb+}#,7"}ԘIs͞X`'*~np.*c<6S!.n*TŴnQg. J(",yQ836B~ߘ-):j-雥҆*EN4$dyS6ECn?gq0e'G#cnMV_r | *z?Uu9+Ne]E|4J|!|go\9L!2:3avgRnr!X)9--B2E&tmRy?ovSdY A0^PZF5͖ >=,5glCt_Em1h0Ftd0:㷜S$b!(r;= ik)-(+ ۅFXo 7cCEt(um w-B>U6_Ɖb5Qͦ|~<ʍ]loD3SrQ`dXi̅ Hm61Db%O ޖe!j@{?CO~DՌo]V6]Nx;2s"&JBmV{=.ܪyA$Px v?^,=5/tUJ۟jY*pIZēisEY_Ogtw׆ڋ!.Cw F(Fsq])X]|cpu>75^Bm2GFknBe.|c&*s(?PiS݊C@ZWv_62'[:ro(J;|ez.R 4W)<ͯݼz}nuCU ͫQϓ["J&`xe$ "B~>mXkSA,ObLr`8]fds\p7d 퓮>9{3)8A(BUnJ&z/63e(@, ne~|yK=;/zqx3l {7ֈR g;i⨲@)G:W0º駫5FP?v-x{d d*y loӺQ|9ބ 0rbh9j^ v< C9Qta|w$O2]oL&))b6cS5r柵UBM,]5clS?n-oB]{yvk,Dhl+,po#u LY\A/ۏV?ZqhgcG>Fע{ͤP}g)R601bǭIK?,^tcl{NmH{!ցθ]\4s`u+ye0" lگ<'(nF2`L5骼@Ack{020=S)vL6W3j%;v\ x'.dEO-DzaÏ4ZdI &H }n3A뷘bsM8) #8^fwwxJR&,$U!ѕsGq֤|l' {ve(_)-. Y @k6S^~5O z2iӲm^_z%;pmvgHJoAN͡}N0S/lԤnB:܇3'[AKv怨d6hYCʑO32J ~-L!^rc4Q"HsoXkloN)>77MHUcwmibC*Ql\,9P>}KǷmڼDRXlqk?n=;G+LTwk[64@5+! X?e>]X ?qv(Ċ/ _1ҖR(ڱ._9Bt®9 ˑߧh"m%n&@a1_-W879VBWfu€6g@j[DE~iӾi wlj:2@)ʆP@lRҰڽ^7^OS(ܓ :hP-ݘh>Bjbҟ(SqGSySvՂN9c}"! ܛ'E gM3_K3A 3i{O )+~QzD^I%3,,9k8mIȌԥvăh\dkb.^f5BdYu4u䚉MWVX71/WL8V+3Ftƹ*ɫG^BCOD({*Ck:]m CoLx4_En4HGV` $B#.9MCcz>!FFы텞(fz#B;JF )'SpX;kq׏`LY >*;CiY/3y{W&^p\j#vg,VJh #)S0rJ^ť V-O'9/?@Y\莣eDR f+ui T i80lh "ZViB #vxvg?vV+94iq^d CR3eli?8OM!qetN_`$+Tŧi"t6z`Buki9CM3z୮T`POhrvoni 0*ޚO*^v a©x-,NW@qE6:-\x~] Ik1c*B0I M2KvD34_]E뛣c5ՁDH%t&dW69(!;?ЍR8PRh} g|O={õ^:q-bKdߚ7 i4ČWf)-je$%eis0㴛O"_t+y -ʼb =;J(0gHUيwA xs"W渖c/'B/q+f.5x(~.4=5 ?bD)IcYZi|Qj\ä½@c\^n}ޡPifS{^lFqPcmeHqY #LS Iuy)M{K`sΆ_J((dN- ŏ61,:QNrMm&A7`^L`EVpGDqUo7ř ڥRԸȥBO}-,,BP<ڱV^: H:zn6xEW⡢|%" 0\ cn\:TͶmz`agPniw! Ow7'|%L3 ok \>Z2I@ ɜ FSCwM] 4^ agFԀHg ;N!N dcpi. CF$S(uk[B%1>}ܝ2/>(I+IDri/CqWhZIrK _Wg)tLH ~"IEmBg<z"Di˷apNs*%TxxGA0 O>d9Ը)!ޠAyBZ{nMB*6>̋ʴpQ _@YNʝc!L  x7}})K~ÎRN$J -Y~ŵC.Ow^vC| Ҵ0y>a݅ޤeq:XsAI5/]0{%*#? U( ^Mmĉ7LƳ)5F;4RɰEqGX7=lzv<*JCЌQS+Hva _$쩪E^ ?;9{"L:~ʂG1Yw)~!M{mn}8B32x+; xtyZAKQQ+s9)훟\qN*QF1&?wYV2!ߔ0\;e1DPp9D!18l K` y!Nh7n;!;lR {kO:2Wa<6R4X pؽm-omjR3rlC֏En/T^Q쩏e#䱡79ït7yg9/WtV"O۬9iʯo(2nrbs-{ քP㡂tx.i"vcޅ'Nz_5B QDTV 5sVm;^-)2^Y .M ]{ƛ ioYUkn  |٫\U{D68m4|0[ӛVF^ s>k,w vGɳx}rl[ }i.-kZaGKw}O)/x0pzlM-ȻwA}qBGu+gx) :Un"t w)›;7"a7^ЖOR:4`s:,CxUKS*oՂ%Fv<m7[G&\0#@GxZ4-TĆuxyce!evv_sҏ:;\5 ź}!l5"G 6U{og= FoDx>- O>QSkL L;&\>7`@c/alZT\%vW$ztl73@pq',K+2]'Bvi/rl[ My.O]U]2LܰO+ԎCH5vV~ճ6^j+cekC޷H\ C%o2EGgi(Fzp#ь02q$CoŹZO%'[Pnk5mǜFgZgb1lV&x&lh܆]Q&YMU˫f/ɯISYgƴ¬ۜ|g X89YM)a;nlv6g}m /(姞)l?ڧuY-{L8q^avwhj"8DCcq9{ ?T;(~֎魂+\=LA@7C-D\LZQsԅE_8j+_ !B*RC>HR2x %i/_I L#u)ĆK" ށm{#}q a! TXI[~V\2'ԻBMlzz ݥ 27\\(}8S%l?ݧ"(z gy}-ޕ#h&Ruۍ%)TV"g([`0Eխr{`Z\$;1K8u2bN ]+$ ptbPah)tzC]+?0p¡ >;zCe_$5g:?쑌e]2+kMAt`+WF&"%]lr%` $k8KlmʬVUE /)[ OߪjlO5Mbl,seZml,n *BnC,O/}鿘V85G2sUb \jFj$zeS!}xte^qd ҞbRuiҢɩ͕4Gz  >p&}`Mbj%g783D|2[o8Cr=0Fѡ[-FmVL1xQ znr_a|qSWgڀs?__?14__8ktnpPT`ytD3o)QLy@D墚ڟcqt ix=JNHcS٩b`3ȸ;N&g{zndPi}\`* ?:|թO]HJ^t 2r>dh>Ưm(W?-mAb@ n|ޘz]D8Ԯ 𭑜5&"ʚ$T.T$ BgpY^O`ýd<6:/M>Px#jnDtg_n4 ll$PpD'*ݽPING[[鿟( X\gtrxXκr ZBa߸(8_٭"(zǺ]NYw8W:-ْz LˈP2zn*4Zb~j-!R5˓Fgt9br]p=).0>FVHib*:%. 3N4 YLZ8FnXqt%Yc6쀧&N5I&ۤvvIMFߝp^W0 "ĵMvYWI`qű˼RZYEǠhHBa? {9`>0βR.;1(mbke%c%ٲĥ8:|K񯲃}44xdaI\LK :}Iy"^VDMVMD#x|ݓΣuAMZplX~vN+$7Yx?Q~ϓp)"5luz780m}:S=V'3sxM><0n;H-{]-ZT,e%//έLP\cJp04ezm(1 h 4Cr?mnf{8:,zLEIHbܳGDe8'Fp +qRRV*o0j\7m}Wzq߇, ؛Y臅*rcɜBb|lOh"fYp@>.} r4D܄ϧ)Z9[oszQp8wٸi? Hq2.qٸO[.W%V ` ǵn4GgjbLKi0n\i"}<\nCR"@CLs19!r0`[/p)oM/ǤI"di`,8Y:<%Q) \Gd'pl7mg#m}<ʟg^/eH5]Z}wyCA=.K&LNiKn,gߺ% Y Y#U=h}J3;U?Jْ=)2y=Źjo*o7zߥE)cBdSP)wDZ{ctV4,&:=>!Cf``Jc,?aa^YM#7dPt$N0QBW[ޠ@Z\HrI>-zPWUPYͳp;>$up]-hS\_P'L$o<F1Qzu*P\RT%y8 ;5IPςĈpZyii]y1|^~# @rA$h&x/K6"C}۹p ̫Z==+YTDxzY^jǾ)!jDgFiQu>\Br׿_xD4(E7zm2|^-0pN'C *˅L@!szXs7}d/1pwI,w/tPMC/kU%%< -…'a|IQCg GAbvI(9v,/I'K7$WU'g2/]:D&b,_&!t0$^3Venf;ISc(3Ǽ@p! $S8b&ųP `M)7Tx] $׌2Z;]oWeܬsGۆ,Zq|]uk]ql׆^mOc5`y}=@zY@G)/D'UxځKI؜Dm^>d[KO$5f2ZͥC]I^>L1g$ԣgy-1˩ôc!AL1o2aYI̍2; mI&rºgF5]H0H|y=H⮟?i !|Ӊ!ENv˭l1[Jg SDPB-sHgauw1B >. )8*mPs?C`8*c䉶~ !yL 0Q2PB4*M4&L_"'%i׎rߍz>?[e#*JNt޿Ux0Yc^ǝlJ+ОF7.UX,ku{Y.mU85L>i_}B?lfG&cժ:Fן>qj.腣jch́E'3X[{^@y8q]funI1^_fV,13P]f~rpH{zA"iܴ[plhp8Cq PhM]Hes\r{ȗEMȷQL"6Օ"0:Dh2[Rf3[ۀM mG'ov "< xgX50ґ7#fiSJuG)Yԫnbnfo,|otzI \A\ 0*lrd;zKIvۓÛw~<@it=4e"pte=`N]k1NT5jzqʳIqwV|DY>q;z@69F_d\ ~3p~S|F8Ik)g kht´Bڎ.v@k1Q{tgTE7(Wm( CXSrQǺ3p6X3;UT$_;*Ih}q!ͩ㺔IP_?#sX_@&|B=F+TW@[*!p\iٟ~j6jDH]N:L[);E8ƉL-1Uc?3+:Έ3GR֋$ T[t_E1 _}^9? UAJ^^"5eMIPJ\;:YPcEC_`;Q(bi;19>ޞӽe O i%?Ig''|0ls4hCURjd3kوv]IbwJBѻi٫w]vة7ȳP fY!Yg} @:[PyK@G]I>cÙiFgX!K{3xR 4E`M4"! ;T3+` ^6t!@-DBaj>B֨.h+g)9+0BЙTڕevgFMTxaq\k}Np|^薶P*wNWeOjI y~ՙ+@>U?/}Ԥ(}}KFJ2ɬ6AI ~s;3jC W΄p:ɍIʪMب0M|lܺm54f\iUcR7uLC lpN=#1IW<+sԑggiY!t1 "vr!D ZB hr7{dзTxF&UG~cC͞3cގ.m40#O43*TtZe^PP _:1dvKQw3EQ:̧C饣`ۑܽKמ^G&d. )jf4xd-O)1m4^c')$~"Em rtDLF\/APWuK .Ni@IZE!}V1/kuŸmf0?U NteTPsl9x[7XଈU;2GG̛ 5U1%ϼфAG~fNV̳ aNJzi5kL&۝^{PcR#H5aw`3Rdke mk$fu0 ?"H#Di&|]aL ȫJXKmd$Gcީ b)>%X;2V^`bw0AamIIYXM!Jū&Z#|qQ+j=6*)НG ^}~f od*e zI R{?W^$LMt*|WTJ{ksGz:NLe,ǿxT(ʙ (0oί{FEHJn𰙀C Mc_'P%%n n[كrMlH!DD'ʮY~,s'ܪ\Wg=kiUS#æƒN3EЖ߆֨^qo ?s!G^\xcXĤZNhCqn0_Y +m_QM ! Px{MRl)~qAf?Clf}$2!:J6:Y,t7#z͉Eq'CM|^Ǵ\ Re r ZǕ:a %4 dvGO }u\jӹr h0H35AqCQ%%+ UӶVO 4%Wgo@;[ٶUk>>z++mBHZS,~Mq[N L覷Ǿov)'m9#6jՐrPV̒tn<~pz ֝qcF*dK _J%D }@p\¬BӖaaQ=LZ{.kyya K'54W1TgBO vDIAT7'rUH(Bb6s|ET sol3AR'|Z6pz\p2|]܄FrQ$*7}Z/4,U0$X4 WFn}(c%"h8Iujv$W PWYӱ8i`떚eb ÷u5U739LQ}͔;<.pqpƴɡT=*^ͳ)]\w S-Y6Ҭ% LP~94B( f$цNS KQ+Ut[7 MХT]Y{M [{Ux:2f5HxgqU(~Um;>fhk5!BNatPn1 [ZaoW .rӇ掯]+da7f0]}0? 9 xPƉ޼yPƈqW(Ch뿔Et|[|5j}֑f{I(.+T j&0Tq\%֣l_,^ )݂Q<o,neH"fJs`< ˖\#B ]m\~iILtŦltW8@18brR'̘~S#UU0X88x+իCl~In@biGt,ml1DG6Be#N&Hx0nYU>Wċ81gt~@7;!c]fTP9z'K*0ɱ.<=>_#$f[?{?ʀXܕN9Px*~ѬhT68VJ̩aZ+goSLKLa)TwOvCsobӨgVqv~'09z=G O^Kuǁ܄vcnΆ]ͿЈbp\u|5{ylmXW{0N vإpDok@+c&Gpς <*l*WwqW+:9MNrj9cEe]CmfEvqaÖ u '7x6a442vɇ_oj!< kפ9'sVy±I4K;^sUT)&"HEYU7kY ϫTGN-ulq >eFx'k 4UJdH 'FZF%t@}j55HttלUجB7B XQrG wA0fÞE;%+D'~Ʉa"`2KMݟx}qBQVǖ}CRZcQڷ;ךB{ϙZE鶦;ԩ?6a9! W:D++o:yotGFbz.GJCǷTއNq"X4kw+pϠ>b'ֿ.)+(4%Z?pMH7uCcL2!"f1e߄i InL~Ŵm;&`2-3f~[2Ξ#>A6^QPWs3s.7S]etWSO*SCMXiP=G9#`{X"V0 >?wlY8x }5 qVGD]I^|ua*E _*a1Dٵj1h^̫_ hkp+(A>IUJe/`u[t *^ԩxnvF`⍍+ ߭ihWs"͌e|I>[--:RY ^[ 'SAC 01^sHX*5*s5_8OѺLEG"=*-"fr١ )wV3wՍ-( .٭L Jŝ9zgZGqu$ LtӬ'cze+?@Y &Y_4|tCF٬pZo\@S}HaY>Arwq44Y2[/Ѯrobؿwp.1#.+RtpP,ĆX(/!B0O:Qz~H?@Ժyz0r< nfX@4$l%$.yVBT> Ϻ뫑$Q̈:7gt@6Zp7ݯQetY.J"ӡ /J^ lB >5 ڣr)]oX= ?wTNtP݋3c:Ui]yf-ȼ^8@A lN:cS+ԗi"=KT +RE4j߫F5Z6;cqsxw$\+RKatAe*g)|iD̘Đ__$L+ ͔Q)X$CNO'Wc[MM4> Sx ^UNW T [U82ZkPI'5)C/xkg%Ga›}vBs"8ў[E/~2.qַ'^Lډ7ݶXз7AF`wޟvgNeeWb,@ Q)DX! yk@|t#G8%qܭŚQ.}j%î Bܷ7 H3ª1֫(56s>*C6cC<'ns:X1XNxxF6EW0e[z8HSZ)2ֺ?j9/R"snV8N)S_ܚ0O[?]Z(8 ^J,uv%g ,h3oB,1jnr\Aèy{?E-4)i1kknX:p5ǀO4f@fM,bKktꕙk*MWScGzk/_#J3-.ko&<|+ZCi| !|H=]<3Kdfʴ1%Qa?|B]sX_8,tԂp6)ԢpP($#}mlVH,i酴}] #1kZ8ӥ!)Qn ?I]4K#s@xQn#y'Bd肁܂]ؠkVh:ZeG{ ~$BqAȄb{ aG] i ء((c0dG2X5. C}BGM2^@2ʅ>+^n!:#b 'b ďbx:)\.3I|  +w6RFˎ!s&9~ 8#u.[ޙѝBCū-yQ;^Q"۳d.|\Zw#u֔ o'b$:~@]yU[%9G=ڇ9IJWK]p."UaUD 3GqݳB,1QUP(LJ[\.}ھބKU Lj@Ky")QG``פ @@E`12zWP`fŗeBv!#;RenX&u,S@N#5wf`K <.Y_Af= @F_:ռ u+kڝUZ&>zM@ZzȀ7;N0Th&iIaq]tM#Bz6 ع(=7aa gS`٥q4FNm߀7;sR+F"}1<0}C5SR|> O$K2催C,񕗟&B=aXcvkX56U0qxZaۢPXd<agsw/GfO`MdDzN&4>jcPh9#m-{(I/#TӀo*8D@u.?r Z/ $"xd9 _FE w9Ś.CÂ^+E^:zTp8[]}gv{7wĉ!fHb}N؝\cYZu"D;vZGVwo8}1H$9tSoƪ+g¸!ُv2T;4c q!#AhG2qN[/)P [$~Eq\ݯWmފØhOX ntmvŘuNeYNJzu4 MX]#9ϒB`u#^-8pƗvtZ4><4 \rz^Ey^z9t:cB^onX>^:TG4m 2{X%^IS{;;dUBgxvxtxh,gYkOTpQ~ QP6bO䢝곮G+wt2K+/ E!=Q~ a@eCV*>PZùw.ݾ>0|r۫py&kO/ =O#F**^՘4w^+K"̖ OعNMJt$k6~oծ]?$u e}vl2bI ˱\. _ (O<G%;zQٺ/6_ȀF1. hXb⯅dTbK]j^hm^ ; e‹" K{ 7A]cΌ$RŞ ΍ +FMLˢtL:pCW( . Ϋ倆}jhS /.ƣtHTDh8氚QP$O?0A/dg['C .(/=W 䄘Y mCPjDKnՇ$~z7Ja6[Ͱ7GM~V-?"-XCvc_#ϦڄW!N~E D P?&d ^{D34ujyL{ydU un4cFfEdME,X@ p^{"_֧c|%*+%ѡF!HM4Ʋ%^6&Ϯ%m%MVxi>sԤNy;ӫz3_`qB&IwK$1oR﾿䗶{*@tc~h2U 1 7 f!wfR&9@B*BqI<;F=2yɉYrp>: ,if`6%QtwHwt |9NHq.TuGYB8aCw٬#AթKPU!-Jy5Q*_`p0䧀O{/׭JYͫl EUT9k(@ t!ٖ6)u#K 4TCG jrBBxED(GgKwj#D}aF"mOb;DYcz|ެQ2=&_xɤz]HF Πhـ``T,X8?X8^HMi-@ŝLw<#^Mx}vldBW lx% JuLХћQDV_/:Oqjح*^HS+0ٳlfe=zL@Q7" 2Z+&QQFVQxc Wv8w&ɲdꐗHFgnoԣ+VCV1cI>^PKAjFRCu4!B]B[bCcf`T⊙Q#:w8ds ^9rtutjS}WC:;sz>֢-\Y7ԲjdUPAl<ĝw+-{Y9- -uGr* ٮR] -.w:+ȐK [k@$v|²A`ⴒN鑇z|x6oT}I. 3K#itvm7|[8I bbK9ahNgyJgI60>oEG,N}-y_wvi' a2˅JEL5'tqj2QNu+1M+[p3$&J5 UyeW`AHL;6/DԭdIO̥͊) '7*҇{AnZqw%:0 I-6(:]=(#83Ϸ/G;J}Cjs/!qpb9ghynJH wPg٤/E[gTX81;׋4e@q#Ҳ~(e#\xzFje㰑h4_k]Z_gzw dB (t_. ;]Z#=6CE}"ʳT DNܓsFAtZZbBKWuJ'%nD-H56-NEdUC({WYZgO#}Og}% K 5G frf3$W:& S2N<3 ;?=nsөbw= ?!:4=D>1@JN 0hoL&5vNV0D0R- H,/R#ۍqIhJ^2B|' [`@b Mm4WԈDpޚ0/hްuPViIԮF:EЏU$.,uz][I.,Yp҉j_E\=:f72M)k K&alE@ !PG547DiJLm=w;Ua]S"^[L85kW]%V l1,܈ؿDQ>1R[f꼲U&1p) a)k{ |B5W#C AaK!uH_>m2_i^Mag 9FIoChdeQđq3[5 s` _>A[og1T!' 4bDbzޑmqp%Y|+7 6zڴyt|dPaQ<|W8H4 :D+v|Q #S!ziJõ/?.mKRzF*~ɳue5zےRmi|"71mKzZ O)ōW#[b .pWցG|Š \hVrdUo#7\7=<Зݪvl[$r+ eJ Aa;fއ_Y3Л)h0)/{jiN'k5y{|+%/q ^cB-޼T`[ z#А,Tg dfrJр8eʹ]t:d;ɇ͔_0OGJ'ΪuX~x1p ]wȬ,rDmYf T~&0oi32DwBCYɠccne齀+,LN<1MEAVћж~%aw~˙,YUc6rChFBYiSZ\c7*'fSc.Ӑ>> C+!7:Lg4Od6$[ݸ앾lsRi /SᢑmqX{C-"^+l&÷p8peHx)|$2l'лڜ黎+K2ֽ'ȯVYj7\6eQ—sմf5 r&!hh'BW##tj||]!7mJEnD:6rƨjzn=\yc!/W~̫.Z,a`6cHedWQR7+՛WbݣO21(curkRCHٝ |ú:nRtSQiypf"B~X"%UWw3#8-UZt"Tx\u{<`-Vo=y[9^rM~rKE,;S2TPk:ΦZp] rdL YQF:Ȭ!8c7\TO];5 vsE&#%ne>ơYQ-5s3d1Su&j(p .6”RUB VĊ"NAEC<JkT?R/U-jvkƁ?(0wC MP@xY#)=1[|]J$K rbn`[jad]9\ MZ2? ytg E$Ns\%8:hlXwVeK^r)"?@6 =k#{WPL`'*YvX#O]Ap{px0rֿڛGBP] Lx N\wvecؗFr :k^;f-vS$Kwտ`}w`0iQMHۉ 2FtϏy1:Yі6\V 4׮fאf @C _oἢ̓7}h閲a߸k>'?R R]ޛ$Q,!C<0^-.ϒ2Xc 8>:_Wcz(52>HٹR~pqTݲ]eMs۫;rKIS\5sc-o'}gZM¾,j:mՄ됄{W-:p!uf. *#(] %>{E+V0å$h.Zde] rC{ @ԿFx[мʧ9=ɱ FsQ&<Q䰴_?>eNDe r&ɑ,TY^=u5XJYS>Ifp^R_s""'fTmywG_zcDCZDRi=yٝl+E+\znќ1oQ*<>E&R4-}$;˯rM]B.f‡RA8 mdiyJ܍,CEFn3T7HnCMGW3i:$GD9ũ ޡI*Pm$GC4 cޙ܎kxfg5&KggG|\I zB_"wDbYiq ҇g`wrL IY|1:ph5")&.phhÖ9ߏ;Tz4c8Oےj_*ȖXVS)*Ճ"zvIrH/sN3˝)@fßh.kPO M@{.acUC|qY KϜ̢BUp)W!kQȍ})i cu=X6粲j1AҖ6&Zpc;v* +sx__6g_M*ڪs }_k#D{Q0"S  f P@hkcg|lJGt t3{V_!$*:sjte>8 I7M&Xu+)Be@-@nLzI8uKͬ*kdШߖ %%SpӐg+O봶5wՑ6F).'qsI;(IvN )/5OmS zRA[+ZuCTBSuXM!0 W~ü6 :1G!-`/Yk\CP?r^%c"mcGEh`Kz>~O90zlBc!aosVƕXR ʉyֺˮVy֔:c^Go貔!9)_`s+f3UA%.DGύ^(hN]O[aČRX*t)fPj`zӑc^/c?Wx~{ME\  tEG!1^~4ָSp/cq #]TtKdRc ވT ^uVn~ͯ7y]x9P ^Uru{W<McBDj-qk8 P}n^GJvv`Ǯ#z8W݉,UؠW*,㺶~y2J;*+'b,%3uAA>E%M:'h ۽'\;PJDb/4 ΃ڋV!EtZ"t&TMg / ePUըs9o>3*Q4o.;:ܔE :"''ƠC,VPؕl}UE|̭,5A\&M`e[N9gu-@e%Ɩ]U%(qtHřT w40%'a%ƒry\@̳TYF~-En0ヹ{cјzyQٻGvxZhss +䣧ג+g^oDF..7 u\ֻZ#Y]uV S?* jP}*uN:EB0;K]VƔM L{^s4|9~QF4/vnsiW0LXH+$?/qDQq.]M3^J#R/Q-y\y]3?+;av5`C0&0Y,ј.K_Aa` \$QPf@2?|`lBRQ N%t9o{Rȡ.}wpkHmZ)EDg"1 ZXjuj4_Fȏs "*+6-׈Xqfa CH|f n5:%;L{]#!ׯ9ƙj ,CS:VF-UіY2&2KҠ/h2)*-1`EI4$"1wozˆ C%,ƍb "CڭꍤM!J.V #N1rj*s&`c-ޗum׊/v}Sx.[RmkSyB;AXw_~18K7}>U_nf>*v@U[YUnun^&˨AMyM y3!>pϼT&{v4flpFRywR esI1b]fPl5BvNt<#$ e]ls,Sv(ƧY# I#)$Yf t%V{ի"]o$ty~`rvp3;ڲFL$o AWthכӌVRplaaXJӁ{Ce(PHS9Mb==U3GD/Q^,i\[4a[@S W9v/I%]LgBlrHo Y3NÕ(Ұ,"vzKo3FW͢:%>q=: ]%c2^{PTIr' v!+U{%( 3wjqZtW̠ANb$}I)%[~1$ A/2F`:զev&cv$:^6!v<> eO`q?~zۈN p}Ei7DL:%w.kFCY2]|.I3 ؋4ĎI-(qx_?6!2Χ}ca-6?Ikᣳl\}éQ؟T~؎P v&G@?!q!tLdg:;}M+)oα7rČ!?YHs+YAmDGsd݀k_Ts\"wTtIIkOq `o4m{1hXEn]k~A3>I긧뫹MzcA;$Fx=Uh-~X1S'si7랁7#V:+(-U|tv^#6ƥ3>FwN:߰Dk_qQ+WTt r}.C!`7@Y8 t%r~ 4ifnr^PXC+9NL's||S#+?W wִaH6֛Մߒ0E&c h4 PcLz;""/; fdh&O|I?-Eg#CQ,Q)qp -/n'ӓ[@ߋ?!TDStg Rm@@T&4M͜?k3$gn(x !O73'&q1ږ. ^M!ge׌{LVU=q3™f|NEog}+YTq܀KT_MH\ZR7C&Y2d(lRˈb)!u^˨4:{CbdL/%J*ݸ/L$'" i ζzyFZ!uNcm 4wOx]Ho3΁-R9C{O{{A=5L2ȜW?8&Z"dDߤ"&14'\d#@Ou9 sN9MMJȌ?:Cu-N~_ƟxxATobV u6Me(b4[ $m`\. k33[:@)XGzBb~qI2D+:7yO\;_Ŏ/͡Mw#.A+u>؊<uBz@w@w mDf-C$J0Z2vx,a!,҆R^V]V?':}P8.4qsV 3 _J S߸xC{ʨ,pDDm,mކ`nvV;8p}-Jq)Shhu|7ɝG똯SpH$0Vͱ L@ة]/L:=GgG4&>N^lkZUXFK+p%@~Xv3B  9ngv"Uf_D:UŚ*S0*v:JhzC%:,pADWzb=$E±F]=/ 2E:`(Fég%(YUmnU1LViݗ%>L!} 7A7YԜ.r&8no4H7ВjC-ڈu<5?yW@᯴Mor_UFc5~a\Ь>H<ҳu%G~->)>Xza ~A!@{IǨgW^}+QS޴rGx҈XW&S5 i.f BhLpIUKwf KuoF.mu0A'JhU2õ䋞r2ncX3t]?D,/qֈ_{P;R$Bז5:U{O@f T쏩's>rfXs%2Ή=f;+ui[+d΃߹0=&N3x<Д"9q8d4UqFh=2&4VܟuY_f-RiG/dGh)-{PF4"Ҝ%H1 JZf-*ň}(w ,JMVt`4_l` |vz@JM?P  'k3.i/8ց"i\̔G2p"Gp׭D&.%IV^ۗ0H_79㏩G[Chi~#W͎$d &= _& 1b>2Cg7sGItW-ouJQW 6 &LP߽jݣn #F b{؊cV^W޳Cʦ?2!O\|ZqX2tm^mn#)͐|9jOkEtk?%?]!`IXmbӟ8v`}pl}h~K:~we.RW.>g3L>PØkexl( 94كGJ@7oz~I_`;*OF`q6'̎L^cY2iAAǞLǺ 1& 0dJn+ֻV-OU=Q8Y^~ko'A,b.(Lš :瑘;pvW}z'$BYa.N@H6Yu?^e-zAx='VP5 [3LkKiT: *L$<(H&6ĥ]D9PcQqQ ' HU&iI[?¬=v@}638E S1IC1ť+Wч&W \b(K2|*]4'M;YgUgnLxIIW͚I w怒E7z͠d "\l^-eҷI]CsIˎUGꉵ-MFD_NY0NR̍]9p {Gj2/4B{%xM)|[WgX/c*ŵ`Z41OVQbP=WMZ ˃A%,f_jހy}x63IR%gUy.C[0>Awl&S y>N?Xcn 5c:COk:2:Ǜrnrjl9 \Ck@jf0Gri\Y'\&13pC(aˠ!/9CF^( 3+i& BfXM e3' %_Ԁ:Xhcȼ))H~Ɂ jĜj?E8wD:Uk8uZ3Xs\4ȷ5疲>NU]( ~Xl~DҴX9ɪ`/A ^i5|_k񬜔a۟THDŽ Q#$1i'tW Q 72 XIQ ~+',~i*iߔ|~ ~Ї|>amozj[Bp׍pX,x&+y7 pU0FD-d񍯿Zpad#0T- XtCx+%妯^Se{)߇I8NnPnܴnSZc+-λu&X#5JV_=5 JbB*i8B~$8JksóZbğo%?{lZ4pAl#B-y|>R%`=FyUB\KN<&-!=uțh"{_7uAXI!z6>Cn):(mU %};i ` < wNy㒟[gA! ֆ\NKus#.SEsgV**iZ8b@ >?_,G0?'J9;-|< ޕlA-{f}y= $_\qS]-O{k }sC%[ KɎbNNO&4?_]fbOdrwr2f91PL7G؊Ԓ"ǒ&5u`U0*^" m.o<ҢZ 6oU/ѱK8mK}6!/IJx簐Ú2D$ro17;Mʦ#4 {q.xN3yorƖzJ۶F./6nL0aa extܽ; Ac,cp7@{5B6Uk"#ҀOӾv4RnM REmQ̌\um/+Z͇jtlYMnzs Yfz_"U]7] z0BeVQutg%ؚα!NWl]McBj]|Qٝ*Pҁ.lHoP(oz Q$!sy3Y7ӭpT(~ϲ13e'arFq2F݊ cSDGSc+UjKYARocڀʹd2eNkU܅A 0qC1rj oca]f YN='KJ0,7 ҞiunfGO<(GMq,a@MmNZA"Hǥ9~vubQi,hqv1LjݔI%;6> E:IIxp]sPaVpHk''s~l% '*F9șo&O$O_,2:Od-ȰRU(wjmysz<}^o~D)Z's0];:0!3L=;4s Ko$-3j27q0xb /_MM 6#bKw."3G\"L-Ud]P y^poHGV =! *ݹ2BQFItR/c;0f vVr9"qdѩ94fr %<Jl\*3x_ )d0QNa0U I"800ł$l }h`1UjQ0gw ŏ'$0eɆx!aYSaraD>4ϻ,6To6!9ڞ~# ۨEv'a-!}iۇfQ =|9/|zcW͈%yXȾD&c.~]kG_mu`!o\%lDo"!kiYZvew[B&;aؗ5śm'Xc5 ŷe[R(Lˤr-tzLHgr en0UJ_{ K1Bue^T ^rg qGwmFiVgTKW:t8y6gɲw*G]?H^19QłϾGaM3J$F~|J.2?)Й.Q Ř pqC!b9[}OOwwӀW}ٿTxJOaTg#K9۶X=I)Q>A7,R7*2ӡלzityzqҷSU55g%HI`kHnq96LHNht+5#|}}7lLY7#ڑblQLl֧ i5Q#i<4G g- `Fڒn;$Fh+muSw=jO\!ρڛvhWG&0xz]g}ۂvv?K\z^c,|-=g߲CV O+PR}M;O$K~ly 3Q1t}wxӠtn$4lu:Vg41FvtwlԂDg ԌڃrTj|X^v,]}|!R~M-{{~Z! J>{L$ ~D-%OX]YïrC]h1AZ_CHm;־E|Fr;> rLk`Tj46N<2x$x :1i/m]uʥl=ͼ3ImhBeh`"Ǻy_¢{ ʖ8hip~ɃADeetg&>ݥ/I=X .`68x2k}=ԕcoh?qkH͸]c#`YG,I`Tc_FfuLOmom8{Cq 6HwΜΡrvQ-ŒHM.W-J/7(vknx}:3e}^l#k@ύ. VZnH/z4%t&mGoK.AC՛;XB6zŀцu(cM6^&[k^V@i28Qyl{R@ZEL\!9%(=`,,(9KÛr:\2*wF⦔ܹ|A+*~'hsHoi=VLRzp|ӏGE79_pFTôES!N e=Lq)W8دv(MX{M"P^hoǐeE;٤i*NSHvW(Q[$[@]~ .!_}JFkG0 Ч@ǒXiQZ6.J/M$fޯ maxEtBwe&Pŧ ƕco]HU}H{2#۸5rR8^ PaXۖ,@H#WAlAv@,RxsHTZ|i qAY^Y&kX? DsV u:`D>aeg P{7L`(Jh'̰EW*S_.H(} /c"T%n ]8cJ-)|TWb"f}BFd"<^[n}e>&AprKmrPqK}O 5 xtNB*2J,l*t>\Vz,{QVEjbZi= P2ُaҟ܍ghD PjEWE \3`'JFdZX s$pj1 {Z)nncI[{6N |{@y:`K;G(C1e8)]@}-߅R9W e+[09Ϊ:lGa ( o8KEN*,+BHr&Ȭ &+59Ţ %Hv \)&bpaKd3wt Ζqp ɒgi+33;So ڐ5ϴЮ ^:Mćeӷy~瞿>$5-M] 35NٝQ]-ZdMe%δPS{p kw[EsjW{TA@KJTD`Jyj5`+?NrfqWGF!J+d?D}ɈeQ: fmB`Cyy|!i|t8d^g'Cx:;YY{Hӊ.8qәM,^-I;V򘆡㺯i@eҟoJ~M} [Պ3DK!-MD:\yvxDԯoy}-h=c v/Q>%}l4_=w=!7)׎y nޠt5OAG:H+jܒ@ OH,Gq2[W?2=[z;b:XRs2 -= %B4W['{5lGM[WDp3yr=nPZrtjPy(+Iu~ISΎs$UeP<+|u,r )JQm4r22a $ѰCYX]Yu wgIb6}>,-S2ݠ'qfe3Pmتnc6t')0l#RΈnΉ50Q-FÑfi|꨸B (` Ǎg1'BN\f9*0\bXZ^s+~Aw?!R3p] $>i@V=)Ow 4~'?E2܈'xE2kq,)?Q)5ԧQxVAkqCե//fwS+1øqGHv8Ew0NŢT DTL\HT(!|kxs =X$vPP,ӥWv;tK b8!hy'5H*%)7ܙ NZ㓕GHRkpHh! I f-ėv$5"#ǜ"'zVSWa^ps$SC}A.ax@80b{=5 6|~(!VȚa&QoD XBZ/+:Y@d Ul,bofpy`ҽ.xj $cE=-f(WnXTqoUaTItQaSc-bCkGݼ6Y^f«uO= #-D^Mx~cIQڙCt::77J X5g۔2s^JXt tqk##-q#Vw9h kً#4$@ʨ ϭjʃف$]@Xyv)ҨHq¢./-R5U,%ټ%" G@F=EkAl p=F`hϩRau9|buW8|[m<6KHhwN-L\+eD#@&v6TZKNH|qE|]K蜌 kdTJl<]Ge @q AdRxQJ!R麈nf gȟ*9v*=LY(o]EEU<0X 7:c4{Bl*T~,z&v9`;f,]ИmĨj?CZΩ_TH);ʹ`" 2Av/KBƺ +]Ύ`" u4 ^/sż3\֎B`v!A>YRf*;W &3] %ZzFMwVg5|3p7{ FvAW//?DĞ4A4Ӝόa/H(  20 p֋!>ZE"<'R{7+7*"ިFTN3aO#)Xk9 N&&iYd4^PǺ~ã>G ¶lPZ rPivH {hpUċ jċGT]#*0P>j'do+q d+_ws^53y)Cg)]Ef[Q$e 60JypH=+-[ZYߊ0zr^SnEl)l3$&FXZ;4uS}x4Ö<2\oz#ގܡ-atB.w又t wj4A](MEz8-\S-C%Vd=jmEuyTYok;azVv.ܩո02lc& .ƕXm`u^M~nRPLH7Qb` XM.ߋBh'u+)R-|t4 _U-vg*TVGO `z;10q+TXɥ5epe-^pA=êI*Wz:>Zg uƯE٠̧T0. @Skv J"xozz'K ؓ/q+S6/27M7=M+cmz.-jj9(Fmx&M5<6K7^W !V,d+>)wZPY^|Y}(7Ic_Z"׍H!h.Z}w,K54'R ӗpTs4TAxPE%x47OU=[BQ $nht㝆 (;gUqF8^Up%kt:+ޢPZRrU21fծr&>%,T@*kW"yP3;.kKy٨LA_hq3"DBdxo> meқ;6 n'aRWIcyi޵t|JPпcK6 /Ī ѿj:ӢDQ#!Ap;RcqW Uݽ6{B-fߐC:DcE̼b>%|yw^vĔм&2yD&72MԞƲ~\<îP>3yC @ac<(`pՈ#X\;IMOKnEI*3ív**+ӎ<9}@G"ȮvapagE)-J:#y1Yy hUy GΒ?nWbQm`c9Жpy!ֶEh9?XKpn?U/5 Ш0͆*F5FшeeMR[/"Ej; k!BZv59_y %<6Ae?[Kʍ }'7:yYZc۫(oM.)嘬zR8ٟ"㗯؇ҝohp̎IJ*_Ebcgv3żǙ.u֎ct ;h,s<|@Ƃ7 w;[KxPEL;5)T2YAs?8VqONP%kޑH)g:JyOkň@qT ?'[& [=>hcL85f+%2|Q.Ch9C[2!.0'nW ##lt N˶]LAfi8uPu<_~jQɯu.q&Ph"<^ b O{y714~Q ָ Ch+8dZ>[FX&?eT={-@^MsR4RxCa;[7wu??; :RBG>}BGAt2+%S.隓Pf4ae+}Cs"Xr,ũrX~Y``x:8znIcez;%t5Ō$:}=DXxg ;D=xX;FDP SƘ{&E_|sNw5D_{b nC %S6jdL9`XLYӬGmqy|#|ہ[Ãm,#ƽR 4|%j8kc*\aG ƫFWa(I}5RhN9;r+[_yE6;gs}i{\rk;1[J?7-2> ȌY-q1¾~a:īIfowQ`M]b/̂6˄e98K's52(NY!zF #t4XOQq%Qs6Xz)^Eœ)ȈAT;YE,><`r|ve %HOT'V2ތ2׌8M&4N^"%'@5*1=$FTkdT{fˁXuE'DVLS$ϻl˜݄1slTltXosJ.gL5P)Gjۧ )GnY|i)" =&FMVs㴾`p~c4q◜!!)Bi7ylY6Y`NN%jcB_<vwvP T$ү)1.w:OAȡ j>G_bEcA hZ!Grf1ޓ!.$Tʱb_khNBẌ́C9erR"mV,޳78fqX*,33t_Zk ;eA nTEV=%#:T]⌣,thsS9q|Tj#[t*OAQ,=!pI}eEOwcz )۵}SL3|-[)j֥ \*Pؙs-ZUMgazk]YwYq)$\ٶ$+/HA08Dz-Myb[9}M* Z?n:W&~J/q91*6wfGmL2 jڿDU7#0"cCM*v!x_m+5I![iU>8x27hC"֜t#~@PRa.2'֤ Ij5Rͨ,<}<~]7Þg.ǯEe}U{X^,ɚ`O+ jF0saA)g]1쬦ڪGnp`}\/_9@tndZi71^‚8&ٚ|S?O_ 4zwG66)-=A=O T 5i!865~퍣Kei-\_-}>+1xs$D,ڦ3EPaF{V.5D<0&a_NW)BN(| 4aF55|3x}8O҉DrdnsDTbx JpVT%6Ȕ<0x2|AYxnH-Mcrp֛d46kw[(MIKTx_ C1@"2ڂ5轤7r*`٦qʃn7&Wi9?YSA lV-i` y@\|* ǽBR=*Q"4n;I8gDއh/*Y HT9uDnbcH2gJJz(ϡ}@7.N) Pzzvt/d8`F!6e+`ls-ðwBKrR[>륨Ho{ 1h|5px~ҟC{T4]xmϱĽM{=ҕU>D, UE4 13|V;ј-'xp@Ơ/Uյk҂dFNymZIluVh̄PoB"f0>t9jR4X+kj` ow:u" 7(Ju㻦n݋Խ.V.NX2MclmsH5bѠ|ǎ K K kǿѵ,i׉l%[׷ R_6c!W^\Wz3oن5B_y~jbیO/ϕUDx*~q*Ɇg&"%Ja|mZk.X4'ω%[OvxOS1:V\nǭ/MvJA_[ h+} 85H[/'ܑ+Ab+|)AБ||nx̧y Q ~ hV>-lXtMrAoa;j/b|>KfD.2Y kx5~Sen+n#@"P I<86* 5IWY}19 ~ʐߴ^J 4<{7RS$E8)R @u(u{zA\?DdQ~>[+lkP$ľkT<G^aП=иC|Y,`9)z$ߣ1shp+nwm!J ^N vO H1½ק[+co9^' 30hWÌ#73Q Q{,w}"> o 6SV@"ͅ' y"u᠍Ll@?oqmPHcpD਺|P)$;_ޛ3DZ;O%ҕbdGg|Ul"o#vU2HFKB]ت:Qc8ZJ`buu }"tCt僀4E;D1Ǥ o_ؤ0BAt!(Ư9X z9MD|VW⨥[Ō + Cg*C@N*A"DD}=1GeiH !9ڐfS_ 1쯖rpݹu{hU`rje*.&и-i1ư!Tb*T0f5'b-m1E0qL%4<>RߞVO|dͮڠbh5*x/cF:o~Gp7DD`3y:~>4OK߫$wb^MtMӶ$96۽uڝ:#M'gUs塱;gA)jf?&~ւ!+dL֡enۋT ɣ Nl OVJXhȤ!tۛ/;ܘʃ\W0ZD#nN,2} nG@"hxV bB^PiV'U'ʼ%E~tLdF]t5tF`sZF^-_*G;Eǽiq'RuTb)]<F3k=Ua~ ~ Y"#HC ֳ~`ဥlȡzYw~ҁ>,IsJgOpc|/3&#GϊfdWIf dUYW qޕǃU*;k2bg,hȁ嬰REFls߀@, -Fv^zR؉ާS ޛ7PT'dQF7$'/ٟ֩0YEٛ+]RQ".;w@g~"i)7j+7 ձ p w BAo_|ɕL*?bXo`>[ff;CAnW{QvA+<#M CO)z $FT0<&2FaQ‰ (iÝR~epom ',?r5og s(U]wJ ~Jd~4hjsдp*cƴwpZӏ:]1(d@߸[J XaϿnр$%8hJ1m]brS9cCa*mEo(yܠt.H N\*eB ,L  Lz_ŖYl+\ubLي s'Dݚ0#RtrKF*}ڣ_TCKț-};<@lBhw<㇁,*"h6 vN[sɌl.f], s-w!_UmRX&"ϝ$fe׻^ v8;y[5Hl<);5srcA 4r`8y{]\ B[8)"a@R5T3{HD5 4sn=FHy6ux _x\dݑ]6 'C:cX|*;1siô,|'$v48X Y"E[&8 j&I݌^%vŘ3ӤA=9{T[b_"xDЕ]B2_`Z@ó)*@ "z0e} 3U?~X<@t풳߿|׬\‰!EMWsrB2\Q{Uy;؇VӎϷIM9ݺdRrA]nj7XhnD4P-D‚y7%BP2?+x(|7ե;^/ћ\n 6M#xrO p9v6rȂ*-*+{ Fɠ oG@1,vxzaexA2l(@N^SI2pulM96{ھk9t#S4#l {Cn 'l., "um3[yIxy Lb~EQN 1 p ey=TqqTM%}s\sXhe&sȧI }-Pn߄CƎsM9]1! V{6ի 7csDx]|m 5j/^(4Y 'PC7V-uLqS(ܫzz0pi.5}Kz6cO<{{wdH?c PAk#._ F)/KJ279F&'偆{?7Eڣ֜%9X%x%xTā}@ϐV?ۦcRK&ڀpyyUڮkq;(`qBdu #H;xuY-z\H]4Qzd6ox.5Ze߽#:!وF}2\6ZнɵS8= f}mI>;q\ms< ـvP4?qWV|Cͦ|  Oex f!+-*昿 w4Fʇ%T]9 2 d%V`>HR?J+JX) W"`*~;֤le'ԓi%ƽ`E.ISuXeą&3 JMC4NdGynr3CcB`ϲ& ?'$6G:U$YKW_OG-}s{X!?*9D4"GO˅sk뇢 X lвZKpKSIn`8Y?8Ajf$|P&=>Hp=~!G-Pcl]֞w4_4C1v МТcC 2\gOO^‰3ǷG  KqUr{O+ 7̟{ T\ )O:Tny-!?\Ur5&nuuθ~RݼӴvOf{NT[lZO+[N] .׎xNr&i/q1e ZǪsY8?4W&K,#F"L80&x ֞-I_+ض';v&zGrtNq>$) - q\]2OC Y,+Ҧmw{ls@Зk6˹8!m&ӂB_QԸxtE9 N^uHiJ+v9K(@2NKڿrJlT4Ѭj/IHt;t䉏V*R (976C l$Ŏ7,F5oWuwfy] 6l@3p|A  2]7ucPb)9Ka &IgK"xZUwwFO,[7PPw]E(ԋ2JhPSaf(|gJ:SrvxD2/ u<-[z a͎xG ź'ֻT-t:P@Wm^. 2],拿Z2+2{?ɴD+ryE̠=IXsJq7s€Kaܟ7^G2ZBÈ֡yQ5698a6IIR,Davn)U4e} NOrE|%vpe9[ˢOC.]IK4Z( ;,!2T%%Fn3wxou{,z!VӊMx<^_ImpAJ@&.ɅD:M! $\-ߘhhiji S7L-S'bi :ghC;Yp?f rP Nhx{i>"x}:|MkSv0kygyH ̽ sP.iT)1 ]aԻJ[^I)ΙNtC3Mbᑦ*}uO<ʃ)Pu4^ΏI`>CvOZQ@^{+5tKѷՇl߲YuRqÚW^H2q!+"¿£5- Uw a,-|Q,G)Ii"X4V"gx5 #EϽkLׄpG/Xt276hJ7ٽ~BpLؠɦۙY cU-w ?kd#ϒ{@H1퀵)IJJazS:w![CO`i0j5<7UX9ԉT#zĪ˾.&”E.d*~BY6Gf(T6jޒ8LM3 dt&L}gOǐ;6fI'E{ޜGӢ gupiYf]sq_4 HIgST "~9D&zq^QBSJF=w1-Bj@|û8AfFa3Q=_,fB쬔L,}nzi[&%U`#6}#̚tjW!»:8{ppJOA,U_Dt?Z8hqWycUۋsG|g͌:e2)eІm- 8W>l1gdQ{e˘2 PaZڴՉz.Wgk=W7 &2_E&6 Z2("16$ |l.R3?-,+Pqf( %͇/<]$_i1Z"yT-oDs 6I1k0OKJBbL3(חJɡDPX.dE2G?ah@0ӬH\`;q)7+\|BQ#į#Z-*.K9xah- p;l Џ%g6.4TPɖVoL8P LC*'MkB["ǩy% @^;ɸ0mȚYB8n(}8Hkl>ZZf_tkC}<'10-^Huv9V/K].ؓ~?gT+K ~=etkU7%Y> sXi~ܰݏPδ ̈N?KY-Ž ErɜnZk>g8-Ro*uZ  ͞cS ?xeo1N-bpzPA2(}_~._-Bg8"/~t6NZvdJ2$ʔzA4$I얺>)X%ˌ% v'XU}K0BtC*&p ˆ\:JjP晆Kֳܤ_= oC`ڼ`2ڎ5YSQyM('5 Eӛ'x*L-FwԽB&~K@Me obFnEh"R<rQwØ&"8(a:OL8mZCOvn" L}N]t|!g?2Z8I \tTbm Q $()gt`UKTց, Ś#Xu.}c&Ry5%TAS1ssںNtU# =4 bXYx\΅EPZWM- ݑ[i=Zpf씘[{AgG܃UaR<zr{Kə<Q_GS`ק\lB݃!)}ے<?]eWf!);ГO([/t,|q \z3]p"YB*=Ͷ??QiujM“fg68»WA?Eyv%5dXfkGqAZ fn U5%>"F%y r$]5ot˥|v ן Byp:OH*R(PE$I If#P @g/et,O٣!U"%̎Btlس2&a5lgzfPjb~0z&nHKbP? NBP~[0 -Zΐm|a/E8M So?sv Bl(bm'8[AD}(/̈́pUEE#ڬ;?(M0ħ dWf߰.$ĝ(Zۧ+ߩL>^FM \Kspt ڤq<*^{VQNm~ܹ7g9a86kXbLN/1l RCͱ㡑'&0y$K^NQ_;!ǜTVǫ 0 l4~j(UOC2"PNb0z"}9YU MQ,C*#kw+]6Fx"|R@n9czC"G3T,!?Ȭ5 `qdK2!gHƛ1j2S0ԧbT9>ZG&Bn7ɫy+Fj oXFHg`wʦۈL( e|6-cy"; PR~RV68]:ʓr8wVz\镱3yN̍)6~τ;dDku>ɿ[Mo012O @f15Gc#6RX($V`#>wM!b6A^f)!@愁t+p1\!6-jF0&B1ZBj]\cH,#KJŢ&.N s0~mBl;xd8|<``%(;$^;XwP0_YR ˮ:5DsX'D!ڣdUb/Ϧq`bD0lKb[*v3PK%[zD04nDrd絋:2yk8:,K'2ɈDָuOLȭN Ke-,ttAgmtaB#AR "윱_;2OAh=݋xU]7^@:45 4Lڔ},U6C=R]VHH)1Rp,BR>6F0G]Kŏ4I³Ӥ86RdMd4LvlmoО\{R.ɓC3;^W9QJž!CD4j g*"$xJ6o۬[(բIIF}` ǖ)d2Yl9tA>.%WtLCz%lLBYUJ49VS`xa9p$ɝ5@qoW d5{q4T׳cRD݉8fcTk߲Kut.@}ɀVUtd(H?buK һU!;+ ?3'Kd7^ݏȈN4r湛cpUe+iL+|i:c}Mte\/b6q)纷E /h̯UylxJˎrRTW]v\S,ܦfX? ,Ɛ[iJ374sT8jiGQMɍwlWu<\ dibos`EZ;z 278/2SΤeULԖ0tbd$mO\9ƨ3-1ʶ'qJ#r'0q{aUC'fS #xFJV5BLFl@HQ˹L<{W>ꪱ~*C d|'uF*?=9\?~HU\"mlR)8 -EC^L;>U[ #ݸ-$UY4Rb';x_Cƾ9=cz<%kpa_Ya6.?-|P$O(LEk0OF|ųp?#*IO>mrX&]C&!"ctǒZS ᩼se0t灚iwYy('u g'ՇGv✨:60kn]RwjfC %'S힣' "\r62>QAggtԋ=^V/ͅb)gd]J D@wxfr S#% uQ̾xܦ/#/W _p)Bprw}HtUcV\i}kn09WfP :|YH-!eM[]v#ؠ' ML~}A‹>|WW*YMOW*]a6a-BK'ة~U @4yVE ~rBD/҇|dWuRo q0$"bh3PIf)}W7`=5r$ZSD%p]ݼ +{him8߽u{)Z3IJE?Sh+Hb-t%jI'vCO?AZ<#A 5(Tl,=N6Kâbx_1zL co-Շpr3Qz?A¤N !-pC1ÕvYBf0?u"fۍYeYY4èO3.[mظI `ߝ_ Uc DX5m𩰒MEvhjd=R ON+fߠ_HGp߆.oWX-J~&!5_8KAv x "~}#eҹDfw'㟅[3IlPBRyNRjE@l$'^"1zΰ6F:S *Da:]wL q $l>xWJ>sz 4'W4A^?m.53^pdq:}e&d833^;X>yޓ>Ct^iGO|z5Rf3Tg֚Jߓ`<\LR 8H 6!:M"vJ)" 6Vʞr )4e~<bp=jxFLoRW~"9oĸ)ԮQԪvK6f+Sƭ31QR!G1#D8~ܹ >bJ'j-guYwqֳ|W1+U,+3>h !wj6۾#R󥰿7tfPDmY`/y}!6reR'jZF bIUEmd f'RU(Be\kj?G:β.@'WFV%}$v,65>x#Zwto :<+?*$EW2 꼞DE L+zO@?Hfw* jAOM?ۥC¶e#%/[S#eSnN{ xgSGK; ?^7O9C3T?0N3h>ʊrG+uG eh$y⍅B F'3@ܦ7RqmzFfRͶ }h}\Jj2GQO% +X]#'Ǔs䋞ch#4,"z'F|~L86ԗ YaGƩ͡2rv*:prv 8>.ˎxTFby0RK,C67^}P?QzTTf ץsN^}6#ǴFOV<LJ;*}Bk˴w]aĝ>51Rf1t. SFD}Яϱ$OQ+n&ved|܌0Fh]K3 ѣBb^ M8Rp1 l% 5Lӧ_Oa*F8:6RWZM:/R@} qݦ$i ١x9j*7Gf`nyb2_1e9#~)g*ǔ'V0%`zbR4eH|7EHd*ճg}U,zFGK|%5q ll,ϺKF עT̠/u{2qy7F3]Vr`< fjx|q]-_[oSjl h] ycٵ4IҴ4NRXjomP%{5cNMQZxAgQRɯ[vImĽmp^s%c<(IgRO0va ?}Tzћ/s(Uhy;oqbhZ|O^ڠc9+9's}qŒwʇӔ>G,6-9O. ^`zW Onuw*AJ?SN 88aGJiKϣ- 咠()1 dٯNՖf Rf9r6i-}pƔ%S:{WRfA!Q`^p,O?]+ Ma@Fў{/reVLl<4үYT7 sҗ l1yL$e 0jА'8afpW}W oCXGfqjB0Vk UH$0$lmwK 1Cp KiOވ`S)(D*C}ӕR<^]1b"k} "Co"a\ݵIkNFܨ%a!:}n$lx3 ŹUr \w#S2 F~ױ_٤J>҇/XW}lH[# ֯Uk O'K DZBPCe%Nɉ=J&l8g'Ʌf&@V\u5.pGJrW[[Jy%Fn7@ ^9 /yZahԍ*Nn6q[E7@t`sg_ +7fx&3'_&L\.]" d_ufY 5.scKG s!{":)ȋ+2KtFg}a"6IؓaLp]H_(W !&!}ct+^`GM=ۨ2}ߡ`'& W^a<,m5>FAMy0yP49ۍ&:҉NeHEՉUEED(fw2Xe o\Aa5H $x_ݴAj_ ^ebjIsQΆ/wQ!cE_:+l#w<@;DZUB=;/B\aLnRfh}A@QٖV&yio*[FT.GDXCbQys]8H`㖲}l/h<J<Sh"H- DEU y.5&RAsGR Zl-tڳOPWޑjEF_q&+mBMyXQBgYQo'a=STeߘpUs/w\=扴Çz6#VQ1쀺Tt ' m % w:̒&*m"57Zm8({9\8r]D5.8b2w[p&2ʚlJ~{?'|' - !YdP3՛/P݂V~KNw^sMq_0(}2=&IY_ӎLh=fhs4JKrb5܆-.5'Ogl=(S:4D; Dd[jDТ-ĕdKgDΤvXX(YrD PM| ~/Cb(N\^'>f<"8?|\`  _oh]rP;ʞC iG ƨKoD~8t%|;Mf'!eܷ+>YgDm`Z ߏek̔ cR Fv-ULy[!Le0E=q+XG( ϴvl"2-\sXGǙjjs1z}pIYDbi1A"ZD,-@ t!{CO?bcm5qk/f[PFZ{ͦA0)KY9xpA?Um.߅ql, o7_EYKvny:Ń_yjۛ`㨥.X:G<ŵ:ӈ. H (bbK5Qref4mF[M2q϶sA6m0V9uhKyX+^λQn>q薳y Ւ0gs;|Twr<{5iX#` sY&t32M&T"wL+{ ++i \JC}پ,wXhb$ꊣQΡS@@TFY_B K`JїgN0$Y/ /k6…R<%'c̗56@ߑ@vq ܙQ`/4scCEM,y,rBcxBWݧĀe*V7"1_z,(Ϙlyz q܎׋3uǢoâ3MT=ƱJ=UBu4V9Fa:aQvtSjZ}~Wo9jm/ Q%ѭ/gCw8KYCSPb.*WiOA(RXPDlހpĉGhR֩M<|sK=V{fqm/!X%^nSܺas&m5,59ȓ~Q@+?D#5QH}es#^La^67r8\5UH!1ia[_OryMX8 6&RR 8+ܣp*ڪ9EL_s\p [  01L5glI,/fJJG2qp љ\bS̮ qGUi͚)|jXL-o@6pb~9c']C >)NRq pwZo֋$P̈Nvp- ̠[nsC8Ŕ.aG} >Iy;=2b1+Uz+}]S.J>-`m bre* ~hgO̢s Zzq쨺XZO9_ iے֢ca拞M%m 40 To7lQ)pST!t6N` aIa*Y丞,H d7~( rP'NhuV3*,djYbQpGp_u@0$i = ׽_i-r3R)qn'݇aH.N'e2]iqc'Hiw%zh'7Xhe8)S[?3>$Ho͌u6cG}ߊ*Où|ƃcNG:A[0c4z,:zgkhHvm1:mo6]%&jh{ GDNufu5U߄2u.{=a/9-0h%f׋".92k9T, q\YP0i.zo~ʀD'Xו{#3R2dt*ƎyM1P'|Yfl7\ u{/ v|/ \;w\ gYBR*~hdQHZxVPr*9 ,Tˀs=ZZꞩ~5=pCyle -g:7o`I{lB5PUKdXvʄf?7(8 +0\=dOd|~ _RnLGXM ?tgQH^w鞗/IqNȮ lHyPR"M;AdaZqM_|ڡ*7T3]Ԣ T鳀i{JtrL8WmuW袤 J:n w'ţ]KXPdN?YfELRx|s^vJ֭2`!fhjZ."w&e|[nh"=) q;jaTm"LC?H1/ce nCBn§̨@/cxj/6RE@ᣢH~gQ\- ^$sӾ#k}Kn|BG~]7QuiU]0摢%Cl|Y׫: K91 ОwlΪ` |S_>Uaj;` ?/ oIzWŞrW@v|fhk%s,GIǵPX ve)fݒ)+HFk0K];2Hwȇ%(MGJ0rp唪~cMD$j{S,U ҌMlLY2z`x pZb#A%= ]ӒGB[/1܆ڵbΒ.  @!RB3)"޹icc%=ۻA53fG"n.Qg&W56#U EBެѴj܋{ñgWN&82}6пS ?ׄ+/:PHx[a7%*FTO5K`kޠ0@~NgbC@pe~V6nB_5bApu>5*˱Q%zmh|RR I޶/}/YL@R'׹O6+y 7hnuIkD{ Wp-ʝe ^}l VGD׏zG~o{SJVךlB"QG{_IMvyT`Zx_SRSЦ^$='XU. =S>CMhR әUF[C#Fm_*ЬCCʃ0ꯊ ][#!ثÿ~)>HVֵ,T\v_k;R/Tz%q L=KN%c@ڄSFYz =U:sAe %{[Xk*C{Hq ;>s洮3?;,7}[Mm;Ր\a |:g2IVŚ?<eQ n3p5%0%5;zTfg\l;Ć(Ʊ;^mQD1TE,!"v~6on&T\A[EƱ{*wU: [jh3)xc dv)heuT'H('ͱkʰ$hVmY{HJ.4/m PEҶ)\,haE qsx*$;JUǟAݯB }o;#5z}[4u9]t2ԒI-o3WZ| bşV"s _{o(8;fkD1G4ZīOlIDF$.waa+ۍ0LGJ _x89GgQ%SS>ZOrQqYpJpn5## Hʗ̥.~Y2awl#Hr>@5$gγi#lVLF䑾 oԵm>q_}Up]u[.+&rmsTDk.V`=`QĪ/hܹEǷgD^L[c_;dj[>o3B^3, N b`{ (Sζy\s$5u\** K}3ܫ8#TF@PVGIsL]ɑmN 9XLBSLD>kG7AA*o.*̈]xrM>/떦BhF2 #,RM:t>v!Æ8 y*zHdvoә'..WmAKR:b` "PnB%!ϫ"2lZ}y E$koy2~z-瘲}@6}jC&GL;0hoKfV 8:cV.c< `_5 2r#ȥ []OU 0v;۹ ) N;_=ZM(ɝB³@΂Xi*"{Rh39o kBUQ6sDPm2Tl*'Fa:GxO/x[V݇VFJTw&0[A-¢Fr5k6AE iuJ)_txþu|M)iOM] }[m5BB?_ei$iNKʺx25o.ƚb.yCrb||̐e O\ke396 ETN_Ovdg[h6{M"8K_BthD< _AP;.''x͘p}6!G@PqEԞZu-I r G~݊ię <8$NX˞XZ\ؐ|41TKlZ DYoܪ- KbOY$Orv{{љz.;pxo#|hl=_yBFՙԕ+}Qs޽3gF>]ĊR^y L'#@YΟs^v ,v4Yj٦;FQUI~58m>/;'/C`졞8TV$*v뉟^S&XiӛEsv"#@;;ymV+4pJ4 +ȢXm4˪w3M%JVjgC:0.1Th(!^Y}\sSFʰqD]*8o쭩t7H:ˈ#,bɔ3^ݘ݇UNhi)2N#K%L ߸@1vO^*s@p{xru4y_qëuQFF̍m;}} iBY`^!rX$4xM>̊G$ci^ӇXeyhubƖ0:Aןm^FmEm[Hn`%smWSʗf5E&L$*|#Nӹ1r[*GO6 Є.[~NϷ&\8 T^ [6~/R}=C?4xU!J=s9{xh,E_>ݎz6/x}V!ɷ~re q"婔ϋ& [)vڬݮcEWީ x3R-I^Q<ㄆxs~@|-T|2fDG%$zG&+S.PV"mbkRZ0p+D!ZҡU:' a 4"`% 2FtSE^@x}5%e[E 0..vk4JkTNƼp%@ t,Cj݌|/on,~XyI'nԓzG؂7^7seD=7Z X#rc5O\'XOݔa?Z;Z  n JGy]J-Kvvqoiu1v>>]A;HGIganzgYMq˭CȦacEх.N<eww[tu+:69!%BEC Лè*Ov>f?Nb36YSo}lMxmۋkXǨCu. D#/:}|L`חeO ipCFb@ӗ͹J /ok(eFF&#v=шD!ujX$FL@G!ėYlc$PkvDchŕ],M.!z7RDh^+ QFۦ}L72UaۋoCyOR ?ش$+}zmRSիmсb.͑'Se l[DN\'Kio'6}ӳ7T۬8i4 _?H=K1wj2oV ~zv>~Qlu͕ e~8j术/Z"OYM3ģ%NvzT[u_hԩO]45BB%Mb! p-ZHÔ ps,.GS<^'((g- jL ӺY<~8Y0'g¡dgHdq/Ex<:i_JTWj9DEtS#Weԝmy91iJ=CXΉ,գ,_`L[SJNA$`5-bU)-Z_?lx`&Y)+WnPE͍Cٰ3}7i%38оAHnbޟ%.! DƒL X*Q ' ;hM88:۝c +G`EI:gkAxBqܾ;Cm.GS4F;ٍH$:8mu Y%$YO}C{Ply Ef~eiGdAJjsޖܛSу*wJa̒tD86zAz1`QÅ!\y2 L0ZGxp*@Aj߭^fJ\`[+e]&#F{FIYqIYmىh5'9hsi"΃4a14gO(imN%9羯}pW2u?qN E@}li_(5, GxJ/^ 0gMkҦX$GJJ~p'w_ǟ%Z'2SnTZBKTOԭ^`(pj:OSTY{ŷ+H/#=<ǵNwxX9 *7"I :"=7gkۥR6ߟ=lv8DMAs>p9̆:B`Ek̃7uK>b:x }eY^U䀒\J>^48xO[Q|U?уGQaAmL`f*qɻxˑ157$7:8DZ|c̝Uu/:AОOWVKQ#n)8ƀbSb;l LŅ`kUdwV4Q8GVo,H6.WW&zj@e%R=nكiu^;[Yg@5ƶ'8z}kh)}PL<>,s~TkM p7,Q뛆`W쨝,ȇi6Z{yJxo׭s:a u`+n(:W%4,R"Q08/Qz3*C yb(4r a?X o +Z=L/driVqzpɁsjz+?1^ͫ.zwSu0A1|ʹ^.lp_G:笌J;z3tc.WMCs& '&q}bx`v}l6Ô8(Y1q8>ac#TaggP H3A#>ҚH˳,"q55w\)+l5β/LM1p@EJK&? mX7>(hxϖ&&]FG7=VB&lA5.eeơRB G[5[د{$w=a YN׎X!H*ȠT'ECoC/ucV'wжomH'%".Yr36t(ﰝ:mt*4rQebrV{uTڰ0qkВ X)נ(%,1 AiM:0 >DCׂP֓벌LyF\f0\YjeʞᐬZkI dE T#qQbȂf{7Sc%p#Eh0\!M~׾ WmZpozuK%p2*Vc,zynʺbMjZt[k~3imYjAw?[,ELu*Faf.lc3xPljٝP5Cu4(zhZvؽW;}O1ٻMR@[f98 ş bj+=dPŕ{ۊ̐y5 QI䕏}gcQ= iY,y2*{V[>D+x~HkKUl6!YoK˻ŸF[Q*v;kIIN+?_lz 4slJDd>@k-P[-yBj=bվ1uL 0 |&RqOa:$g$KPa1HLR:sC\njY721m zAhW-ۭ݊*^Omym:;mlia2MPC3rP;qSe< )EI[%e,ʋAXױxw(Pq=iFշkz-vt#m-bvctislm,=o͠glɽl?z$05s@WZ\ݦgKZXynCb%o5}|ipLunzjeݷGar)NH2l+!p'#U}o\BAv 1WƂ }œ޶6/PXgq+ǝ>P=1Bp0eI# ܑu˄^j V6P~ZGDdY-1 D@@m?Yrq*E65yW7CVms.,?Nny~;3Hr~~FJ?nݮA0N u4 Kp3N &\MK$n71Q$Vvy$\ 1gxu[OFwA;Ԣu"vqn0?;"Yʵٕ_%!D܎.R8s]:9(E*c&bIpҊ삡Mvn1yI%'QVn!?ObZgi=Af#݊/ҡ27ӹy_uBA>zL7vG@Vw#K+}{TJWa*DB!C1B yX.ҿ]4Ӳ5Bq *Ll[ c&A󘟏NOadZ/7ba24Tκ\PoN{6c9fTN'LJU+"?Ukf@N qBu-[Ѻrh)z,$[3u$pY |.mtltQl[(QEqe4B@;D? UQpQz$Ռ9L0ΑN/8DgLYˎ?$<22G76U(,a ٘nG;LM>ŢW #7gYP@ @V1́DPzN~CBċ=R'1\BbTP7fj;toWM V-Hk̾[Bg6 wqb%Ƙܝ@;Dkǻ.Z̅uV-Z䫦aq^:ϑQE'+OF&JiU#Jnxԋ\1Y%ۢ D )FK_WuK#2JorZe'UسS20=U&'uߥ!v> 'Dx2@tyEX0wX0Dd _嶊觝N0b{YIYuo^χ\}4e/hu,dt I!mutYvqi&љl||1섞5e#U8MZ4 Q ?甉)ձ`@ʺBn ^V7v /)Jy~ڕ [&T@Zdn5հ`b@5OkgJ_c36\EN}UK^%*1xdF$%'rPr)e 9ίXRY¿V2K{ݜS0oKR+ފO`Zh8JݽZ!7es_x\t 8򾓘gs,N'ZDآ"pmtAZd /SɒN{Ե,.fH %_Ħ` jx1 ӛ-GYۉfw.,,IV Œ.A=3R)5}C$vr5R‘el":n:Jb_T+P˭=/t1gFw}I:HI-_O[Ō.l P ]8I9XQr{ĞC@k>z˱Xsr[jyaj]e ?/H$I.^=Ԭc装^HR܀>d| | !澊&o85^oks{zW?J,y"ٔ9PR"3}c-g1@cY+Cˆ ;JgW K>oI/_(|3S&j%?㊲"]#.mz&VՃ\65H9dLxr>s&ĵs!s>ckQֱeȨwcURܤg%$; cbJSfC=DZQ)P_/T71LqPI0;A0{WށKuHT o{!2zǫm oZל:i8 9Np}hvXA^&ve˱̥' 8izYf0t-LiP>.`덁kyۢ\!ʧ|=#l6T\S7%SezNR+> /(- rn>e 2Wv S2;ǩr?ʁ李o #s%5Þ$u`<κb>fzl8U:x/W9jBHEZegTVTXGed;IA#7|xC~ B(˅ <יՎm@(A^1=ʋ#c S$OVH<@& )`"[wLqcs1eJQ" 1e2cARh5{?5iCYyG Q fp~qHf lVw| BE Dly= O`rpUbXeHzq͉zH'_9"k>N/S6U0 zXYxTyfDa)C`C-.46fgd&  uH,H49ZX/yo1Ch$n4FWgo#&[so뎥cٸX~%Ir!4p?rXDESKU YڀGКy|KfVyx<-<*]sy8uTXT-0f^IQo4jhyi$3L{wKHܡϿ},q%KhK_674 W`a4{6u-_f3熈{!3[4@ F%5F2RXq;́CxNDG[NeiU4d@=&d,Ʃg)CrCe[]ԅ HH}:qA:]上%|谀mN齂Qt@gSW`[6_(Lߴڨ̦,QMdj"@:9z4jDx,V%wFH!b(Le1N y*HZ. s&ʟ /$uqY^Z)Fg+;is4_12ILgbeoWTXg.,pDX^lf6(LI {\vv?OidҸzPe`Whvu=#bku1S&D! >(4w"͢PMRDž6cXƨ*Θf+M-~3g|Fo7ŜVwY1& U2sioU`MOgc7AoLQ8|yHbtpSNUD H&S IMpffSV`"&n,jjY؈-JMG`}.~uY14@C3q5x6}zvmQ4Tɭ4qgHIex{7܏>[K>li ź"yעU"}J+73}zlWaS6x}S#w?Ӻ2&Kiv\fw jڬP>uBs+uN%@BTw*Ƹ$%=H[VHiRJ^BY,EIƮ^;aծb |&5t-E14YNsF3ecNY|̒g.9Cf #U)m|*?\~;?Q-_T5X4 ^H"4L#~ɋn+C~L ,lKCd-jOA5-}]IGP!N/YȎk ;V<*&SX u2?ICqð}\e!J =)qٞ[ B3hl$ &ܢ.;7cNwK;o+}e l:j*<7wD*au:kEB/R=ܗB-`4#@4Y?uF_:+FiJ`mjQ%w3 HƷҁM~_}4qI'zcHaKCQ?Pݏ|$d!RI,~4~]  a>pE{Xy"9izqz|ۓSBOh&A0B&5oXORȐѦ乶$d ػh(t 3͈u_'36,yP$KyTt,bDUtXܛA}"Zi 7GQ~|Gp5sh@uM:mpXPzvP^:}>q#6)"<ܜnGsd_qA]֘(t5Ker՜؋`Epɯ^&?;Vv%%m-8ʍ+>ݥZ f 5' ܲO{Lc25 Bt4F]6+i%8:!60kt'Oҗ8Y-esiapp6:MJ:ۄ[TP@`7:9aڊqb)u{,Q50DDž17SD`(ZF)+_;_ o9(s)s(WҞUZɫ=Ѓ}l9lWƟ5zAF8_MuޖC^aFqe$E-LuoVg_./yx|u1,nZ1E#Df1T VQU#غfFIP17k@ :g%6uM0X<tHY]nyGS:FJQ.xJ-Kg*`!!n,s!S(EA˸)g  41Gl8}M];+@f )`/`OF*->Մ.}f=d"IVbQ 6Rb)"`(^` Z;z7jϧgh&8+7o53~mV)lCk0!4u ׫Qn')@i#. P `}0QҚK;0):+?%hl^tl:* M)3Y?n"|<\BxXm )}iS>T.qX,ݢ0W,C;Іθb ӆC˷/X56Tn}.9[dJYj~F"aU]\t)@UYEtmhܭhˡYk~Epʒi %\&!S QZ*t%rL](-ڌ7K"fw}UHv9ݦ@3LRcOEx7lUL`õ<]S !L]Ѵ^*yI2mHc|bx$'WbҐa#eJMLjpoh!Hϯܟ^qr]u,״JS.aR1*2ZD~Z\gXRI'̛] 3;'UZY^Ёd^c M23/X֝  1N:8bԮ?¤yz-8yd>^dtyujQoȟV7*tS Qή,ְ#C ~#},akP24kEfXﴍN>fo`'A.\@e t|킩$\!ʚ3m=cu--7 3կ&*uv*J3c8٧ N<n+%9@=QTw? Eq`o8bU\"0 l87̑Qow}=zml ?Ӽ ~YkμNE9>Mt_JiShRGeRq;rGd3;I $>31!m@|9.#qL~NNCTD؄뀽je(,pދNS~esc_f Jxx¨ m`*PJT Ky %  ^7mž#] m4:.r39+蜐w௢ =iOsЕ):5WTsRdƤ0+``;Ea62n3^]ht9}΃7kvg*.|$!g{ԏ'V ˸IL ɊauNu-l.u~ >-u a:M4&H:KO ӽ5Lm)1reHwu0][ ӕKUPM]XW-OQV½86 zL):p)1$,058haA/dgR w\u(?GX JV&dzݣbɢqJld mMuZPFgr۾X)5`dZPNᝁ"ܚƼ,ea< =&& ׅ-qWYi ߞ;UeJl)hEy&k qxk5'Y[lOEE*uۅ-C>@#'M]\ulII|Kp3sV @q* {OKBYylQ.MkiܾŁѿ̳_6ت7~AL /rK2칓!^㼚k~+Mqc!8J9/؉I_/KHK]!~Zwد-cYܢNە ʹ""ew-cweh, 8?Ӟ՜`tTJV rT=[G4zfg'l>{(PBFLɨ6.]F] @# G/:T n({Un6XtuņHG㵊Z^b>QH̴p =e\9_G:] 4u琾quU//LCjeecvo>[0Y  ͢2+FUUȟgp{Zckqރ mo^{-ө07lo]GZG.ES'CUtm3L|tK@MshN>wt5u3 ]KmW Ώ2f ۨe; =æmyiCf!Y(j!Cl d̥yIc^9i,1+LPte/sBQ<\8]*v-0[=?x M9vHCr$'LQL\9A h+nXX j_}ͳaԀ{FQՙڋ]}="p- 4Eퟢ.:d2t4CntE& r,xлOA*+Y%ub+&,k63ꙺH$#ĈT$xbDK]JZ{UM%P}yw -,lr; 1w&/ΜdwXmŀT ?*ۿ1k~>n)*zvu)+W͙p%$b%6V'"^d7فOv M;oHJbԋGL$KNfAWt 4/ǾU6(F~FiĉSY#vZ+hT;C_-@]y1t++Cs~Ն,}ݙi W.MUI_-mK$(~ƙҗK5c=il:]~ŞPd(U8݃A X0]м5cQ|:|j8`7OJ5`'TX>J9@zlݏ("^b9] }*(1gT8s,_:k1e 94kz*?4lթ|ٓוA!De`FIURꃍrruc6Xզ0&?h$:Q'*GxҰkN:gO4 E7^vؗ< cO%&h܁<|AxuRHt^TJɒ0:\+d5G8*Q&̓[Z3|_BH|Q%9屢$xȑgi~µ&f@#U9pEM(Y }7%M8΋WR 1~z  !/N 4LRt?pF|wq',(nQ yŢ}ڪZ7R`GUSpeo}"٫qcjقJyNM.kil!5|浧 6hoZ'x|VK-x'&'VT]!},[ϟu/ڮ%spgT{:JD#&U n|xm*I9/2 PsZ{Dh@s,C@WYқwM+-z~l9`_7,(8o`)g7zMu壓7G7i{|7L9Ҳ9KMaHdtR8b*Jۊ/1*LaDIexf er9-=bas"aS^  J$HK=ChwIЯ+VX7/asʽ;Yz2-#'w{M:U>0ѐEcIaښfmPgEĒd tp?%a*Aޒs1ի1 N4UJ,{DOO !8Ԥ# tQ >sI07;uJ@r89 ?Ie%.mt~#`FcIVP n[0~cBB,aB;sƧGE&C+ ϲruOP1 &.M.ME6-3Q8)E }ڲ4 >ҋ ۪KIu8wk? X4])I9b'h(eS YljGL62;u\˓DmKs,8rdCucDnU7d#ʴ҂cл_8(R3ͱ`VL\{i$h7>7/5ݲ]0Q)QUxFle{럪h{O vBW2½wwo9gT 䄸Sc#1mS7XXim]Tq<֋pУ8YA:g851W<dr&RDC`2ev>Y,RH_:Wo; 72{3L"q8 w{Aru%/ʗ@|`S,RAň t7υ?M{_TDG(o\B_5*â)_ Mmމ^;b/1Ζ^ñxg= 1 bL0,{nIލd]/;dЂ=M 5- fK"M14iL4hr3 {xH:D <:'=JFQ\?` ؏]sPCНA 0?#voL[s%[bGoSV n'l:$h?*1%KD@8Ю 323N1b "܋O֥2_:6!;ACǧwѼN10 zw 8B͞FWx7T1^F _Ư5{mV)r3"i?P*_Fu0,DteG >=u޷QRU˭PK&]_w~Sݴ@Hp2Sf'ykvBv'rԟAJdI6WuHrnnT67#Z?ua\cċz1 |a Ղvo [+zZvFSh %oo> 5rmܶ R4,q,jAh׵ ZT#GC-gƺTg}A-'H_oT Ef| ë msS005bʠnsfG.hH$B[YLr}Aԙio48xYy$ \j43 68=[]SM%:GVo,{ ;7 tĥ:u;5gho3Z'w0>f:HLc߉tO>/4|)1 ]zk\G[{21(8ͮH&_CtNg3K}Bӕ@ Bv:9gsm3BhY#\Ż# ոw]RYr:9Dy'?t)<1;Y ߢ!ZZl_ kw@4DOfU?,8 kg?@Hu3 ǎxWS=TQ=nHɲZkBS C6LtHоo]ǎ7,~u^|j,^4K5[%nY7¶P 3bKysTaϨp7ʔ1ZhL_9~F]|yOS6t "퓭JcM]ei~{Y)>.A\[Xtesku*{E7S8B:$2gSBu 5wd+7/E^;Bk5\X0Gw\{ju#հTQb%y7t#˴\0""~C=Pl.YLu$ǟB# 6a@m466h;I^=*g1 $C Qݙ0A YwcF2%&3 cwD:]xrJ_I0ղs~ 8y*y-v$xBLjbEv[qG!E{|5Jf1(;)w}`Q4u !0Aw8$OΓ4*`g!iSLgo\+=NqBFJzh7:G&Sb萤C\%b UHZ侐 ijֈ9j =Z4VmI &,'㆔6aݫ &a?bMCW6EHѻzdf:m~co8 (U:ݨ/#KCdeKyo& FmwV [gpKx,H> o}zƢ19ͮQ?:-u^UXUb[3%/M8J3<6GT֟ۚz}eW7o 12v_Z&`Tջ o <7x~GT0:n%TkG稔rӨpZVS)$e>"A7j0CviFG0w$OuAfZ/9yA$YPXȁt7Fe M72Պۨ=)WBvHa|LQWSXvg,ΏJo$|oI)½[͈ldƌ]G]s%3Uv*#l9$wWYWR 75%CiRiڇ#?J@S#BcB~4,JGbؑ#"В7Mgj 2TU:..z #*ɴ#5\2G|#Oe9*oS[yt ܆wJ55ֳKj3Q1*@^`C4%Mj:tP=n=vS"2X*'x)SvjLp>ס=͡*vM.{8ܮާ3#N7(MC(od{ߑX *4!,ɔ!g!Ds/!UsΦm7s(ԃ(Nϴ׫#s su\bS`G,W x9磊[Q]&DdYry! z燵)pNh~iMM-H>9P&y_9%qA[[TDfiL'~Pw7,5>WG{| bQ8SĆ|} ۺd'IRb/۞cyhO 0'@Azs63c=rR i)nTѨ/N1]u=8Ӥ6u _B/_-5"h,ǁІ ؒaJ(0 k.)Rtf#jf@BT*H0au$;:wYT{(ơkƂcy]d>6tFFS5;NA? &KRӧ &y↷ꦠ̘ӻd^B4W&kLoAu6 /1|R`N)v.ȻY>?۞u^Qǐp?SUBbpmlHoX >*'J!<<)L쏟wp9Tk͋1FSm9GpȽ AEXMm 6̟##^R'$:XP%(P_:*n?@1xhfF:2݉cС9A+P='J*eҦ˓[핦?I®[{V} )G:z&-~[[J=Tr5JnHJ^5![yk_> +l_h ]"@SVYlK2ƈwvuxBhi􍏿LH~Wl=Lzd&*tdӌ'-w@"؞:@OO#@"v޸<@he0՜ hKZ hUa5-Iyby. *|`i/;hnV!nu0λ6>,xщYpT7*f_M=1㟯Gqe JǏwX",x<ɏ*a#FޟsX{񔥭XG? ` J|a^\lvv ! VjPFoR-\> 38eȳse#d1,9Pl PEH4KuRQxL5G5 (AipZ'(-*|PPSzkaˤ5*  6c0~_JHtHp#.T@;Hx7wy _5ˌ3"(F٠{:k-.ny굍&a eu4I45pX;R<^.Ǔ vP#rFp7ɝ&m~ ig^\pH2HgT5akxwSeҬů_r̰Olo^)ް_YZ\v /e5`fsY _4 \j{q5]I\?fR7@'\`tEn=Q+HX&hoېMg3lNcaɛzkY|_<*G.|D N?[.!U+Θ?ϩ2}h- o((m./;Rp{nugcfQ/8@t!8iVqBN\ }b('Eş &HXE[L2%YkKɐ{@uU~!bYV0l6TjxPv$V"i:7xZƒ$gg o xl5 =>=Q[5L%nJHF9/^4y H 9LtP}3X'`Lg`' dD0% 0Z|Ͽ+VpIhL֘ރtHu?88 Z!tHy$b+^<8AwfyM*S /RSw3#u mg;Ј.ɦy jk]ӏ %SL4ױ[? ]VKn,= 8~'c/W)frso(h aiC<'VE'? cfZm/9 prS0l`$?^]WBAű̓ԟ$TN3 U,yFU<ZeEA(QCVr1LQ#_&ۜ&7PhqDi%-?]މZlKcSF8`d(_+bR{vFbAX ݾi8)F͊D5 ^sƟ0 q3)}hM|Jbrϵ8hkSYPEhl|^&?BDdZ~V '皋3tJLa1됁C$KMG@Z C#N68oJ]𷱘M'$HR*pOtܭڞq<3#Ī4C@=y`q9ܬ!~l<1Jl\h'mChȗ58A>r5EjẀElId;d#1ĻMjԓR4Zs {Cx%H^J(Ob{4};ڀ&o4<Pn2@& 1=z d2m1kUvoRP=8U[Ik2̾DOjǙp$+, p,9d9>/񍩷An0HӻvyziE@{!C7ȽC ަA͚ L[L*,en96;!,.$:wFhM{mz_ .p.W[P ߀j 2g-~y7]& ?@ifsi1HI:?]LJGX{xAb\C\+eҢx$(WRt&w]40Qq% qo#Y5 0hSf1/C]:"m9<(uIAJ> $AVEag[~$}FkuoCo7]8B؍L) bq&[lتPrEѱf+#t_Rj!H{I=][dPsK?!Lg|Ќ~qNQ To܎f.]q [+@Kbn_^Ӆe?m/!'$[~i1hDb-z.[dd`ݧ~xRr!&cN>3i9}f`#]'!1>۰0@ZpDr6H"t&DXtV0:Ou\LSKNxloFݨyg !e%DUm,VQÏ3J5]%1mYYKki4r4J{v4LxW ,5j^t&b_U-3) GErZ" X/ J 'aWLu2Թ9:+ބےcfS], `G6U\[RҴ=E kݎN'5voc)>ԛ㫋~Rg2>ہD$RbŃD"yA&.AJ)KmQ0啥1pgj3i]5}JbI$cf2~:GéÊkcT rc!9(\&oG=L|J~e`GR ˛hdsEH= s؀zQͤy՗~י~H0lwkjps0heAB:bn駾?u=$dtg1CqC\a@\bWd&GC#R `i;v5ָm0Tp+o1{" }wuTk0@"- $\H(~+6DbL|)jc΍TEʹ3-+8`Y>Ҝ Q=CQ|-̶']-׳r_![o{1g2)@R%PW;ǜv^ʋNXس?źA0V. H]A,X7Zp 05 ڦ"ԤicjkF? Q>BA#amƘKp0 ,?{/Ye rSHTXQz|uRez9/^:gt/vR"_KY76jQI0oОpsV=MΞtt{O o̐GWॅ,8v/jKjު&(pL^aTh?nbpt:GeF-r/}VJ[MFq\A{S\3j 7C J3ʬ#Ո؈EIx:_%;e]?PW F)dAϺa}CƖ:uI^ ^JdӰ"QwQyw8W?C5|ґI}iI^e'fn:l)!h]gș=^:-o!=v? ~"<=q3V*+&6ˋ8#7tNHj4uz]>{7*/5S9~iP@tmh=eS}.q3GK 8eV&7B{إw@=EB_8A^mLJ[uF?E{Ӓ4<SLhu4ěC(t3b ,(3D`IŚ}챕x* ۨ[ß*> iy[#_KׇQVyՅz5~f]36Sm;pj)26fFiܱ0q9U*Bܻc<2O1(yY2$ J 5) $׌'M0ǯi4JxgJ?E[ Ŝ[e Ƒ_/~SKjzDVt;\#s0ETs- E.I9#^dڷDjaO1pyhM74c%Zww78w`uQ^#(6Dz>i:>'k-JbH $g"<aʺu%`Rvz,P&5s!HP|+2/ Y0 %?J՚37=V$7[o,](]owZx]M RhP]9BY?B-X펰 aެ]D7M;>rE|~]Q,G\g*Э\j8FCEVQP0\)z^?z}b)Q)l ȂK2H) ;Ca*~%pD^muI5Tq. B@,DH|kS_W KSeO>_q"S|T~*Y88Hڒ #8>z*33&J|K_ jn"@r2cC*vtTq&D2yED>[V/bQ2BC[#*rF߹gUL#5tR.tD~9[b099=&)Q:UeHǩSt(ƦrZENt:H)4D!Tn=O|*B RYEVIŨ3 oTd"MLU|Xmox=I,)R<(w;-U'#A&KnK*rH;h~:ƭIאLB1mGbx2A9HM1TS *x{#'Ѓ3YnJRc9Dv. :́M<²&πf2ܵIrDQȌQq#~\q^0;_i-Y8h0X(Hf(ە~j55|2;EĦOh5:M8-d Wl\nyr4DºnmjzN`쩍Bf ə{#L t|}dG `J ߟݦNq^$gvC)mѢ<¦?FT\QF@",ٯ_e_JYJd$8;|R G4 "yӽ }L".J c4} bn w>1yRGiX#UWPɅ*Y9`6Ԃ~۝uLH[ eς䴜[d4#3}H0ƒ >pX}(G \zI~DS_~M3- }tD0!Dߟ#`Wvkpn'E<'IͪF1Fhq @cy_dcEn vz0 1ҷE($<3:C9bl8rc:YU 0g|m ) eݾ v{yq&%Tp78pOcse5?HG piӅّ6XraMIRv_ B,q):vyBC+ĭuK +0еg(̻mð&a6a($D&~{5@s0|}:2~VMЦ\+|8bŐfNە&|ASj-3ҹ>eZ`ߐ0œtpZNf*"dgš$JͳP:.H{㫂jZV=>e$E]j X߁e_42prvd?6T'@a ycoԮf.xI2-,lgi%[hl^o=5|F?l5bTsgSnDrN+G@`uSJEJh1q OItXs?t `j$%raiZ3X$>YW?L25;)Sp_k@Wh$H>7Z!J9W4Ģ6twG1$x.49s(?[]a9!$Qvgsa/Ms4Yh߱h M~,k.B2+M=?g+KkP#T Rw{bG-ӟo<&5%dVs ,۽N' u=Z"^n??5'qslHo 2yt\G%;xu-ϙp/N'w/klrֹ7Qj:@'^lZ|LZUe!/tKRBFcENaMɎBpU`)s\9K tZ䛹'$%@8m _#]5~e(WWA(#!{3mWNAE ''N I=|}o<&TAPY#_by\5<:Y^h?hhJW: qk4/62͇9~?&޽zL0ek_Dڭ%~#'B;΍U&F*ӓ!cm43(h<:T*dv޹#Fm#iJ:٩_\}#Zq4o)ʀE#_iwžX?}Bx4:Y3nvkj\y_]Sv## S[c#Ӳ 3} qݢ(ns_)a, 33Ak_w2|IN(mb=OUK6/,"dK}^˃ hg?t֡|HdN\Lu?fz`-lG4DR&V`\mݿ .ڎtfG.^oDk) 1pwN9a_YؠЈ2H2QqD?%Κ\=c!kӯ8Z~cX rmwtx貎4=ip g+U اkҋшUWRv#S) zc?ƱHq zFl02"] g:{+63xo|Qz6vO}ESjU@H}PvVo jMG^sD݊mG3d7sKj`PY;ꦘq@>&ãM?:6_" idk* '7>FRW|Z4H#9+t!W pp;d[Kb6- =.oi]D@Y3;tIVթiD ~Rٶ2{b iI#.(C>;an.7uusjdgSA2~m,MdܞX>ķ/¿m9$~ы'zIxy0@~I"0L,eaٯvc[?lb0d RE+'ЭroL@-`( x!: ^t:eFaUrslCAT}x;ڪxT}[&iW P{1XxI+NT N+[~U W߇`%+_)KEB|.'ڇn+OE.O;ۏ=쒯7%I%p6.;@scW䅴#4rF("`ZI@}i#MZI1ZG1KK#gOI]orJ V#!*h׺Ie x? 9<^"}.MĹJH h r[ ypv})<Һg.Ћt6( t׷o\n[$OT5)yn~[ lO>GIP pՅXXm" YX2ȱ n^] e CB|)灉9Zn`k`J6W;PZ9,ߧ1sI̞iRSp.9ٙ*J)NY g[ 5v({N$' ķcnѼ1]|'ET"Ͽ g.ycV%)dK+Qn5Uj>wa 'xʤY,wHrY͆'F͢ba~}6ܼjvޞ'+;xN漓}0t@>3r3ڻԽ* y<>`T]LcVo/S v.fVMT{2FgM\iH<آG{y=/BT|!sI^'7Of; PP?ZoKlU*sՍ+\܄+?U3II˟YRv|pR:"d*Cb.bnA1GҎNf[2T\g J`DNCz8ȺSar6rCE%5GmUxN`Y)'x+Ko:f2[Al"9FځC{ :HhW1^fz%-܊9KSϲn )P/)\=6JA'k;$l`i 2MHеc84nd~}hT֖/5i۪0PqktWh^xwOM4"O.t!RrӖ?9zQ'z$@xtiĥ_Xo"\D^J={LѻBcZȾ83UOph[͊,qWؕa #LYjlG-:ھ#tHu50y2>`c8MNepJɊ (܍$Vn 郊^zg^j=nYml8KsB'3 [*kRϑV W4Z^Ӳq"Gd'fė j@S?z6kxcqR7\r.}ίs ˭y<+ń꣑Nt5dSLD췣ШhaeCGrZ/safXA`Q߬ 4Lba1h[y;'V5]f?4%`DZɗسyB`#6\ڞlcqZL#DhʭLq@׽1q#XE\]ETruASb\$4N^KU4O}9ٕbiH;{dVm{ L֣D* u &B}O޸/h@ٜZFBR ͟Ko[b'3n|}'NG՚s,H0k^N~^,qX0a[|A3)Ͱ h֏7!0˿)-cU" RXrB:6s0CD~h7J4klՂ'»?qdv Rn;T6 KQ|2/CRID1ȼ͏Dxgh(<4ɦXQnFlvvkyiuZ`Թٵ&k$bur2 I=F,^Sk3Yj]b eMp}f4ͥb1/5^BfAm@*z59]0#'rJ֝1"@U:?t*_ @ l|"Ǭ`PڇШlɊ]$fQ4=l בញ=\;b׬#GEyxh] 08kW3у1n\:ذ̢#D u6/T) #@GSB>cˏ 0yA#69ф{?&+ Xce%l[v& 4%:@]m#:\VڸK-g=2Xh'Z>Y6ϲä,A)1H 4qZӊ Ʌq9sv?aTDJnx'iJRbޝWgPK3L*[Z x#%N4XYhwJc1* %R%Y!mB[H/ܔFCgkmT$Q,TҺ7b4qQH5t#(6qY*wߚ11'LlhIO'z!Ӯ\q4ڠkKld x\u*3Fu mj"W>!—~o6(5i^nobW|>;EWnC&}GdDS9 v@@*Ek5mMeAS^:#i,Y6/ͼ@H󦁷Á6 }<HWҴ80Ca BZI ŷv% vIܓzx ׶D%Zr/&]ó {IS@Aah< CE]z2!XR\~L X֎焪K Q[W֛HA+yNY>ʛ[h5A8F5&ih)0;Lv˚/ oǟG"Ug 'VK/7v Yíp?a͊~<Ҳ0l u`)x9Xf|adG!sSn3uLj5k8/>V|2mFbP{؇ ;]S2U0Vrc{$ s':cR | /[g4(.yӱsS=j%G0|et(Ŀ\ B3MRܺNѲ8f΀LQ54޹|ɘހ~)3KAYfO.S( 5j[I>ci W:WZF3) Kz9w<_~Jn8Wm`!K@d:n !MG^|> B֮/)n]cܝy !x HgF~Z26}HJ mܧLkḔ]ɕH2 sbCw{a#K:&/@/Ѐ3aec3>xݸp] <;Wjg2lO>^%IZE%Gbu Sߠ3nq?הP_){yi쵴SJ-h65g"trc;&=5-IĪ: XH^a3Ap>;F ߊ [&-0:sӓDzT'%owJ7sFSв2ŪnHRT;?)|[IH4CsXoٻv~kŽ({1؂U> ERnY8rmXVM.3_h Sn)vyo贼{SŪ)ۺ*r^-B:K4֪pVny8T)êD_~B,f$8 O&'x[zIk^ ` p0/CkkbC 8^NuV*J:>]rc+>[Iub ߲vtCxoN >+oR^/SJw*|Ez5sȌ"j!sY? tjz&w2"\ߔ#ҦǮK/WLh)W_i{ cleNlf&CNfJ@uC2w TeNsf2b\Pޓ~h=B3TAn:X0Q}&(簭 c[ouǬGpݗ8vAQvoJ" C'?3Mq$HZD-p,^QTۋF/Q?JX̳`3/ 6pQG%՜)!fz"KI@ˏN(/ɿP"9|Wy LH!3GNާ_ Oٰ͙8Ϧ&gk`p!l|y\E2'XnCC&@!x-N9*=kd4ZAVt) zLR4P(؄DJ!*d80v)#kA,>/ԤXY?t:IoMB*0*8TGiF ra\z>w8V1 f>d{5+uxCywۣu1T\ePL{Hz5D+x >:0AN*qaM2*< =97 WT9GWۗ~"e?tZq$J뛲ˡfyJf )T{PȪB_~j#K@/`Lޙ&{H-H'd b:[ H${!MduK摿]+O.qL"Pqoʛ5%74h(Veχ@d8%+26CKJEp&X4{4suڞ g?+ꥧ`YӓiDSI1xouys|.J)NJZLoy^i$W YBjß^`ŹG;D)f؏sonl<6~n ChMո7in@ |@G}$qOEC$'M[7I"n ? Ai2h4+/2c u)OMc08_8kF÷eIUͦiPh d K!irةho;[R3bh5*g(AQD@ 鶉d&*tį#>aK17IK[Jw:8}-H?F(Wrs&9Vm%T%Aiq" ȫ+nr:7}k90-hBb]{n<\ZWljRW>WT0ږqs$Áf/ n$%Sj&Q'(8ٴJ"+-W;0M5i:,U+tj0کV\<]~NɢXgp&C}WLA;ceJ=Bܒں7pGs*N_<&7sP%.PҠ SڟvGɵɐ cOUN|E#4;ҕ2M4 ]kث=i.mil<=z՚}gtsfi ͫ< }р18$~,KXޭ^tjePl 0C}fn]Tyc 84 ]k:{SkΔM=#NSG*دٓ~Ǩ$<)ua# aǿ4#6]=Γ *~n; n4Dv`D{*Q;ˠ-1- "&^Rn{\ UVs40cvM<1f# 0p#4_.=ԁVI|-߹CuzI^ -q?lWH!4O'&g$m?6&J7pPdz{ϼY0}}M9-t{^OQmx^svƝ: tPNrQ&m!zEN4_0MPߏo&l+q\5'6HVm;)0nѐ+|(cW(oLp:24h-ќҍUUۺl3aX0\,{ RzN,|!~Ę"bVg.8AKZydz =큒ICiRAr8Q]P.9 ϭm pl|rd#-,EMigF~k41K1 ^MXBʧeC`n&)ߡV/hQ{L0Y]P4TgvƷ tI׌GFI h]}3gQn-h>" N7VɚV!ҠA'?~ۢcOlb:1uT`1Ra Y8,JJy{rf: ڎ/ Ce95+5=b[ļ@x(.VbWR Z%vөt )6}s%wQHEn@>%zGȻTKu-|[1{I﫴،EWoJ[ 6=lQ=Q.px 5 8 p 59^6{:ߐ'WM$Ͷ؇POo:bLSխ|]>NK\#`ڬ:",i`,>qqw.:cR젏~իrI[3p3>$l̹bv$^YJ =gd߇LssY"سyq$H8Zݱrͬd̻7 >]5@.))rn܋I JdDń$ Puݩn`T Wε!@:-Ro.J8<!}zF/7qfA0 N!-}QkP}Vͮ5tnn˴)*ZyG@ĥgO"T ;M_RP([^1Z &fl^"}ˊL܊ϸcQƞEmrWZʟ;X%q ]0ڕD=S zMIR;em+,BI"ZM:#S}n|#B^Jep` cZ񘔡 ( ʩ|Ĩ""jt|p/\N6kkگQ[FjDH~ 7MzlAo0hz#ě(ˈM)QBA-&63XJ2C2iFlH(RΜuV 6؎WwUL0ʠSg [`UTԵ&$hMrВ\oJ󆺊Q"W~"V*4V|ξ\a{Y #K*w~ 8HO)MI1Al! @O厬^=gxs.fS<;)(*"lF؂H22.t߭=qhZE4X LZZkQ*[jCsx${8i%!y|L 3˹x$Ԩp(>UC>_> eϚۜh໼wEo`Xl}s 37@*Rִ e-a 6ܶ)d2zbAW[!Cb:s*%{D %8)ݲɸ$Cnґ*;#$-'[F.veٯ6?Hr  䯔֕~rB'r&Q=8I*MjEeZu#Dst1{3gK.X\.)HU@T?6 IbT$̜D8n5$j3-s>)~Oꭿ_"2S 2֬Iɨ ˅㹌MhʉS:=!ib`'4 -jW+m4Ĵcه :e}[,eĂs.͉H&n\a63oP#8/_fV->KȗXb49)<]a.* #~nl ^K6U0^T!oylZwC܇yqnP ePD}X`w,Bdʲ[ĄvuJ|eON(;| Ǜ3w,[O }s*)@Ө }dw}ѫnu;28Ე <${Kr8 WId+^ p/R{e#foBvZMuT] ,IC-k6ӑY^TJ8qq{O4,NvYەͺ) KFb"-G Tl|X^$v&wR5ǡuN~&?vspyvn(d[-~3&c-<'tv|J/Mu:vO-~u`>Q?BXdEoMà~g&#J ڡI6Sy6:h6y"1˘l$53vS bpE;atphyRgS58٢$$b`tG!j У#9KAf1CaBT [Stp8oi苀:+b$Ȓ .;٩d,pH͛HZ5kƏʙ\<" %ĺ$n.3"(U[`W|?`*f2.1? )!hbԕ6 8~Ѻ DrZ BvHQanR vC8>^QږT#\HO#iaq**%]X#gc!$\4ࢣq,l`K,*eHd/)tRvq_;9Qa_N|Ru*/yI9 U^6 nCR] f]3G^74Nu(ke[CnPwyOl5G_4VMl${x~ow&-βS[ .YFB$x5(}(-0 "v;f0Hh ϸ=3"tw߉]4uޟpk7hWA8 u?ZQF4y"}3K!zU v24qH4E•Ub3lBPPL#pV,4yW#Vս!׽2`$Lވi~mc{4bf_bα!۰xJJ6u+g{GP[293C_rTrx] ]Lʖewb{:D]7J|c,(H,1IxClNϋFP`+o1`s}Y4fGSGWC@B[; v33m. b_ZEm#N3ncPu C/"7fv  ^ZCi<=Yl! EbV^!n%xL|L=K8~Ҝru^LAQ p;O`pz/$$+[ͳl慎ޥ|6ҮS*@ 1S7^T#Adh\e?ȣ/~p=[ZKV梗,ؚe_$ ;镘#ߓ[\0Efd\Q5ހ=S%2I. aj'h6BHyƁ[PE D|oF5yta0AtY{=WE~xz8@-#f),v7l )Է.'. g'2DR5ꤎ'Kvd_!LAV+ʟ[I?H41[XTPkY <+'@j o>; b@0toۙO> 誑r_*+rDO$'s|],^  -.|F{Nz~tpܞ{lSDUɨi=Ny?&F[IS%]2  ݒT  )$iŌllQر;S-VnV^HU>Nh{Pn t%lN@*5Ư _Ž Cgtzt+G I'; ͽQB9a|^xOt׏$uO=<!e̵x_fVھs3U?[*65 jRU@CN~RG7Yct` 8ȝ{BGiѽzO" I׮,Mz$toa5B2.4PA<;| *"&:O6C!nzf>PA@Zz]|Rn=WJ%5Ofdrkk}lj fc~('Z*[r#&iOsv:f/0>=)cIiď3uq4pr?0PŸw zj+Hs(%XAwsGժ.1l?Y0G7`3?z 1.G|LjAY 2圡 OvaG"yWYY\Ayogtw}t݀Mˍ} x_g@\=aGt)X'Babƫ379߇Bۦ*ZMꕭJe*8vq6LjVp'NfQ+'S*ހHl| h! @0nh4{"{jL%)Hi¤ jȿV4TqkFX(($]׻Y7 ;GQ?yy"$8lNsaj˴kOuߥw?IQ_ʿ-+lH}hK9 YHZ@Elkqp+Cm\rILl0X=1Qų|)HM^t wWXiuNHDϔ qcJsNGVR AS)?߰C5h/\&/VZlWkrM>4VF) ;1j7L"9G(˛̯5 ]ouHB4*Be~kyXقzQSkpX9WО۽t s;X p |6Ɲc[X3hkj)7F^-ƏBf;@qPTngOCbG6ȋ^W5{8, ;#7PղvCjNRm5`Rp?mÂ[WE~tךSF`t1LdO `.4d߰h!F1k KF)  `&yᓱ3w\]7o~u7zF9PsInI/ iol |H]w12M*`Auw4%M9 FeAR 1'3)tݿz . 6$L/ŵ_#7QGsG"#`.fVT?<{s6͚2kc0jHBtTߴT4M\wͶ*㩳 ^;|oOQPdx.Op(Ұ!ʓ7i Va'F{+Cթ޸h3}ƃlhF٠>ޭb R$qӀxE&]fMV^,Pv#mPǟp\#epnz%YV GOkjLLlQj}~BuVެɝf:Xb;\D9@X:@+oFylVP ELQ`y[X^['A$w-~[":3@`EBqYzV;H{A)plǴLjT*D5Mٍi'u{CޞQ=챖0L!_]1FUC7$^XqfUOr?@߁h3ۮۀ^|=t)pX ezÅkp!jY?*h!ZpY[쁈Ruk( cylŸyMn-Rۣs6lɉ.ѾAS䶲1ie˜6hEɽND!Bk13̥A#k_dˤin<. H)6*!V@[ *6xuW,MVT\%L09Y-'= \nÍ*x),- #{8ts3(A-wj_.~^/EM3OUmؽpFYł4˜ >c uĨ$_C7~UR.ulLY9ϋák=SGjQyGMF@E9{ u쎼gR٩d:/lÉ!Sb6Ý}y[s4}oaS 쪵ؿ~Ujϣږ sC >o=-A+ Ċ?{ʽe=ex6O x]]8WL+6\XdQg!Y.Y~iWuz?\&&FĐRHFg4`K ӧ:͜3R1JC# hAh3{_7ql >MaͅiRx21w'뵚dU3p.T=5G\w#d 2y##33xdhȗ 1g Q 8sTgwۇ9s؜ yݲUI &Pq&vWHv?[*y@Rg2xuvPҲ!"XOc>uB"`>\@}F oK0FǤܚlon$ vt!n1zQ?g4.6;krR:- = ׾gt`UM A5pl3]gt`;F&7Lԋ dzp2mx+p@8^h5;CfrQӂxP'tp^6A, "M AxữO m'+Ņ}?UY}#dj&d 25;$ҍԝ XwC1V1JD6ݲ8کZe7ע0Mݻۻl5Xs'͑D^"PKf#^]B X5@t 6~RwȻ^4~0^ QM\gٰtjʛȷXX4X5AO1w146 ?谖]C8T S'd5]R$ ϳM:dXx{v.r)u-9h϶qz=wfZ6Pl!]myg-Z6q WuCE*솽/;C e DwFq5If545ı71b͌2>_ }_J.~~;u/{5(p(1?ELB: w yÝ1ڡ|TO*MZ\tZXr2HVw"l[#%+;:~BI'U"/k @yf=?lИ8%'Bw؅,\fuu:o~n\4 )1OhD1yPd|%#,t$iUY-B^R-A)(0$Pؿ<ƠѬv4T~D'&w%b[I1or-t}6bP`  <` "ɭ縝CH)W}$JI)Yfh\V,a5y @ JtSudY@`x^\fSx;s] cˮ hIaT/N42:ۖi띎9q(Ķ_qg-${1z^,3}Ѷ-;'})$@P,9loJDŽ("l^ D05U,`,(H;8Yɞp(W}eyVk AoV6\.WLjqxHE%"<~ߺNy&`wV`h=@.^K". UѩƂqGw& ?_Bshh@޵ef341H1AC bBmtAGR.3pǦ;pq%siߦ~#yK6ȗMw-\/7;ݡ\8H-r7q)FRboqC^i-](Lf3vYVO,U_E3vMLX +k49֣.dGB-,k( ,TtaA~ lqT󅼬7ّ{֏ FifRTa#/]RG ͢1~.`@l_ Gm8kk:HI\#L;W2~h2#k8>X+ ns+OT(ǫqsA忳0*Rld-1Ϊ[W׶b.anYW0Ӄt3 * -{IX{L[1^^F_ZP(ae u;"Rh#qCr1v$f=-u_ Z;R{˰V Q 'a@\ ^% 3ӴHrlùvJL(yN)agsN]WUVm'd6;:Ri w. / UyQ8Xߛ㫬$w I.D=sɄ!獗 GiK2Gr;K 'F =V٠Ҟ:ib2I$1l)b f1ih 㭮AI-x,sܾJ~ -4\m>4˛{ܔڙd36"Y+Si b;4E,踖Ns]TW|1.䬂4Ac`E>w繢X1^&1RL[Mݍͼ= ueO6d]3 #hZٔA{xmig 2˙]:3]/ىj-=ԸLlshCŽӤc!&FECOWEH/kpqΤ%qbrM nWYCOOͲ9(yj|3I`١a 8LaR Djtp[FjNr٭a^sCQt/g7I؈pSh#edAE:dnzuF΍qh>S wr)Ju3Γ X?Lj <0t0W/]FILtr%<-r( a?RZ٠O`zp6Nnz35 .1J:L ÃCdJ,\^umo@Cc q:thW0;{ 7&И!471'ې&Fv􈥥A !JZS UMXI&ob1 ]퉨M 1UccA'MarKө꥾&8sg~0Ц'e곩iޛW8#F5#ƩP"RIqE$ÀҴ/#ͧy`-4]m?@r[gU?1lWX N꺭wp`{XCWT֍u73zN.(bũP]wQ]Xv[`೨ط[u#6{~;q}s_f~hWb`rmT%" W>vr' y@/wqۦI*OHJoe*4yM؄₥/ sV5P` :,!뱐.xC-}nlV=>Aj1ثb0n0 'A/o&Myw2/I= Y'2Կ@7cfVP ЃePO {Nc8Q#pjС @ '-0|&{s*ͺ輞ǖHN`qN!NeB|6ќl rF"$YA!m a@_.=> wA3\ԯK1G%WGOIb\Cܵ3M_ό-2fl$@ vMG{8Pv"^# \S[$8&;z("MS5S!'~~^r_ߚ9 [J@X~S7dKFAٿ( ӋE>N+dF½[|n#G!_J]"keM+sx+hu?v#=XQG9(ЂUڲiQ۵˽Ywjpک9Қuҏ A :x4SWj@23)b6%l e9TsU*{Qvd>䉊Cfmsow+&Y|9պI*rn|+ASl!xhV'$='^R[,zq^jҾiql\+"C|c*@5%g kO?\x!Zs1u]Yrd2UTf3TOEX]\j?jfBCrWa#ϫḏ%)KOU_ߺ ͙"~:vWkP)V{CڱGookR>4n٢K#F\mbi07W U3R ) ] Oyz% VƹRINnf XM*s6݆Z4VNg%7% F+W!j:ׄ:Zb!(:&m/x\9Tp,rf)fchRN&`3Qx ˻vKtwg?uY?QD rZρFN =1n@5<GT@`s+bYD/+6 MʝCn}jpW=t9a;(4m:}Uj"W-nEB_UQdx3YWxf>ޥBМL;U]G8 J|,~7 +mfbX 8?r]sB]F{LK޹{tMpսOU$uOa.$c5R' EFXɨ8)ڹI~HPTb5ul3}i%|B`9f'X4czOہrNZѩ'-SB]#?ʝa\ƊȈǻr?&SqJDYQa#f`yµSE;QT&<28"R5_]ٓ񀤽 t6eɶt'=]rO^D)UW0P$L! o x]im)~G&SMVN_Li+%.'-m|&Gˇ/QqedksnM m 3ׂίǥTLSa4s3Vy QL&Z@,yD aky@N#49b,v5qXKyׅ(y٦HD!6wPa\"]J!f4y.Rƫ#L~l,k/u:ZH -d}9,sΰ_5dhD;9?!OS= HWB1 ԓy/bH3Kj=Gy'\ HK2b)!u^TnbFP Gt0)2l@. ̋y@X>:9i䅩5 dk_؍Љj2%7 ua D_zccH?ZP,s>4ٙ;ziES7jq<%=)GpF*nt*Bd0+D16`ɥ津e!Ti瘴ˉO^`I6JGv1p'ԒihxwJ2@YG0ha9s*U ˼+"<.b;DΔ#=JfyDD?)iq6 V/VxOy s#NVQZh#1gowVLVz JϝÏ}NZ K')o$Bh?H i^IV&xja5wAnzWܵ-JT # pToXSY3y9/qHpN8^"-vtgacve%:A)qsHi.GlXFѯsIyL]w\ś\YN+Wh| CPX`~s) eF&=A/hqt{=ѢE99JZ^XRM{Ic 1Z*nr֝TaO4b`9y>j'fHE3fWr_7Ϲ?r,_כ$a}!Ul=t 2: O?KΡ9(@ԓҫ 6x}r(Suj#0ń ֮o'J<'hR830һ3kQy2 D!3JBy?P|X?'HMU+m6D˗ks @` ul? BI7jT-lOP\<;mvYySgWoZG' ߞJ /`@8-} $oX[m"o6=Pl8MSGmy^<H]pu9sAaVKP"/tTw^;nOk1+`pDoÃK9Iy b/Ȉ#l~*{kƁwgd4ջ%K93V)Ъ 8˸Ps\q:EAyjd$#|O=>xqu9JF4-cQqN.Eȭ]-%LoӟzcϹ>u,5zhż`5UJ9ݔFq:j7I5~`p64ۥݠR~xW5biM4l z^>}+ "섙eO| Hg+FۢI) n*UCyi[}5ߩII_wUY#+?eA/zŰDǃj"@N@2V ?2<9b[XO pz5,vxCu?6K&8矓0|?>l.2wM/(H!K?!o':㬶sO~x`ĬYtO:çC.7ی rCgA+<\J؏^WB |ajjO}E}23Rچnԗ+%=|:m^ql%r]'J$N=2$k4.v5:0]d~|ԍzK3qV'zlN^lBmWJT+.{a]xM6Oh҃ta/uMz6S3+˽Cos*9q[k}bqRx3,pr$QV%l< sl*cs Y% j##cOJ!2oFԎ^%F%hRw7#ZOj.ވmzI2W .ʝqG!_NN]Euz*NR<4g;LgF*/mݶȍ^ t*d\[%ϛeI8d;zP԰JZlFȧqmh[Fw@\mjw Ț{\齅y\ۺ K.QBALZXۙi N~XѻĂު3}!4Z1:eݨTTi8j)ni%&d-Ϋړf| M6AeV@QڋI 6k&Wc_]zĠ"#Ȑ.71IKMvUtdQg Ԗl"1A׳WcjNcBo!pc c!7c\>F'l_.і^8UuYYG:s Gf54]|F5hM8#~+ksu}J؂?m5K5tT pw`>@)z KAs˕n{`T%C$ +pse&0r-`M#̼ӇJ]:C30^=k1S`XvP0|G}dҊlԞ\8[Ո0Q`Q[@niwa1YoecL&YxC?Oyc>nWq(a}ekL|UV4+aPnf 鏩Ͱ*] *H 24_߮G ?*@vzg&KH9~v.{lx?EV,]}f`[v8T:࢛eQ2zw 쭤PS?V-S4+iAZ&c{PB(BA0C Ȗ/'8OO:'EmH5lH}-C{R#0 rZ1)LyJyn]W |1"r, \:K.gr1^EXxoSk=uSM3YM̨VyX^W!\OQo/_#nQuLcm60Ԁ 7dJ w6m3:F&}:i=Dh"P"(GαX&ǁ2_zK̚wU*sspQen/'gD#5zmat[XvL\x 4~/ʜo-/IS` ,ǷWn멵dKj6_.8FKN+ނYiJY!lB60OIWf{=r+n"'{5'ނÀc}c#0/|zDjPpZٱ5ѠOmJhM2[参cG z{"Jj \zJGjہ7l0ӨyX|lx->"4cN?"[6g<"`\Ӻ/2!>.qf.%".Jo)n_p1nG0:=/2A581=C(í6]䳽  4ה'6*ԒJEI|pguX7[u|8/sNScrWkN񆠩v7>df7" XC5cYeK+TVnZ ` \tvщ9kqa5Ƥ ]98_6XOIV/3B~fԠB71~v3]F2uNpi"#=XX*[rŽ_tTG1mz#cTv**}7h?: HaXRVi,W#OG;]&Ƴ8PNֺ8[roI&L\5?KkHB@-O-[ԡ3|L`rxY}/sCo/)al| jA#rFh '$;s+=""4"!7^Uau;+cSd],^ǣb&6jzB#vK ` uPՏi}hnACl M壔A[b<'4Oppl5\Ȕv,WR#] ؇+ qqfrR?WG=*UINl>iʢiKʳ7ʉB1&=@EDQTA`^o w~S&o V$b]>Erq8&bz{?M!-VAK\W Khn׌Wp^`6x9r 0~'j+8)s*6>yd*=4L =$r@KS '1ELIn+Ɯ|a^ ZJ[G(1hϘfh$^8_QF6/`9\#֘^ {3[}BUÖ 4qtuICdz ~]3wӼ*ЬP.ns3q9ٚ.%=p6- Ŕ46 Dn`Cmi%eEEC%6PT3ֶҳdU#Xn+|t"O6PL|}w^]+5` w~רџ7)g>*@vs>W+/o$N7[*s=UDRksa!5 .H" iG5a-31]B+%50V@GaB#|hyH&;J"˃. 1ݫdX{sY Q́}kQMWì Tn}Spg]e@j~sJ⇮4!~{JD4#X Ne߭Cg|+ףs H{n/{Ce*WJCr87DomDr*y"}' A$cO{<Iq{`f H=w/k!U+$ {V' Lt:mx4iX&*n4`ZTX2L*Bk.8\pS[РJ UN bcӓ W_cnۋf:@ Tԋ Н)[4hY84aztL;~a—mɮ} j@ ̄$veQAk$k(KuI۵\J[Z J20`"RZm^h)˅*585/ܿ-x2oOCJE ]KCfB6δW7[2N`*;#:w90p]1Z%a暵mU&TtD@aw|^ #L Oa@_D[ZF|Y@ jv ' nU2X\T Kyty"):iiYOAEA_(XPeؕ}_G:XEɄQrGhKa n Q:hr@@'u}ur= mJm})m#u@ԬI.a+>@ܰƟI[V t1kz7܅o1"O.!8|z䧫V$ѐgrX 1Id hS6a|'B1̵ MQdŠ){cž DճĜ~gIV~7aY 4ܝHl"/MņSH.Ǹ'B7T|E2TUb1d7`@|Ej#O'Mv9>X,ڂvo[YTH@^O~be Ñ 7nBqQ2^*>vGHI  ްGa-C^:zrA0H33*N;x>lA9YY: ;puo V־TB8ͥ+Yͥ\pQ6:𲅻 }'L NsPhЙrjR[D`'f9zڧ\N)E)%SFPQ~;d+ܞ:IzCV`-W&QW נa/Q&h܈X b X6F"n`#C[/lc\Џ1r]yq`wxYs"HQKI&xSቸSOTqK/}Qw}y\qk-DgH] %v>Q\h^s:vZ*'IQ]7px c-pSkƀl1I"ziWU/ܑ1!h9JƎN2/]kAW0@k2-/QmGweZ=A:lj=i: 6(-#e#<6+ۅΕdfCEErb2¸>%WFDLVk^pb*kNpzAV$Vw 2(^0kKoTed筼$1ZDxm(m"JC@W.|}}BR8Tl}e^L3`͓ pJH5N7hg#ڲTE\uZі\e~r=ZFF=R#8K Qf%3DU] ~5RS7$}ACtP [׻1[s̎4CMZԫYRа3Ԗi4P3'!` Ra{F@Ckn%DR@{5kOZ5rЇ*e.'mmё-^[A5M\M=ӗk^E$[ʶVMO4E܌UnTBS3Ƿ,F s$Mܜ,PfF4r{i 2E*]֌\iYeՎRF :oKhEv'm26c~f7hAl[%Dh9$rK_Q4bK߶w5Z(64ZЪ@63"Z`Fg]ŇO֏ƨs:a6|k+PkXR 7<HќdAw+8`+bw: t(8zx8Zr&a!@0ܩ`=ͫjd3̋S}> w_b>B ?Uw25`4?șeKZ}۔:z䞞&y:8dcە#0ƕSH5iBpy{[-K]$GTtKUC7_i}\O%ҕE mzڰ#A Ɖ"Q`*MIK,-oй5/ݕDM-E0 $/tʛo8i69I=` ])>F0җy}z#[9;wLɓG1G)44f.Cڙ,L*g4ĪIp3?:Uɖ!Z?DDOQdX1Nn2CT4t.Sw}yDQl$.S-;{!qpS}Lղ7㳚)RZѷ*=ƞ<n c5CpP=uWRĭcl;nŝ>(l„O!0K)˗&qߢ]!~FܖO"EZ׍^6d̋" tǾ{3ha×gf : 'tΨ@xaΒ5y*60'`8av?@M`h)8.STpi s~~8h oA{)2/=vr |~NDi*9 k>m2`y 94tsNb߿N1)CzԒ&6l(9!iux(.>uI{ʔ2m.zUPgAY?_2ȯ>N3\K>Aΐ$,Aא4n h*nqcR4SGjtّFF_Di%1|HG(Ba0GIlNr kRR5#bx Q3?{M"qdxIGE6PH6*\d*E.]Ao{dӠ I'蠑˺e o'E+2 Z-BPa I|]m +UwIfE;1 sPlOP$a($!汤2. ~UQ-9H߫pj'?dKH~:ve+TRR2J;ٯfFZbBHg'm߲j7fWRVe(.۪8h+ѡؾX:jh/␿D}`X_l0;7FuȦ+`^VG*aLT480 oOڲEƢwbb}bD\8GT-xtn%]<戊j9&Sicgj눥%in|!ϸ72%P9o8erA{fʿ%jRrfq躏++1߿o/=ԊtUDț|'(w0ms8u#ݽg\OZD,u gPMx>W(='?#h*7X Wɡ'JzlgSQSʩ/Ƃ}/ O\Ɣuԗg̶*f8K8{PmAWfRӷjd4^(L>yi6Z~ܱ=;҄-f7*]{j0rP:_PR[IHW\/Vd1sT OueKL#;٘A""nQ`{m>UGP1U0 % WdNSc ,p9ee2.UųGKXW`{YW8oIQ*KL'9~v,Q (UEa3&^ѪIG;߂EʬsRk\Ӏ\sSˮ+@xbW tSK`j M/oӓ_5HT!膧/*!8Im8On;oa>[lnV5s$bhjוSnw~vW5SAfcp%kmBx֊TkFÅrw.Fi4F"6>qo²A}/gM<8N=@g Kf2zy+}&p]b Ȁ۳#;@]8SNrS%ej{5|b|r{oG0+|+J\{GpʦZ4lOvOH0.44Sfi ~5`6xE晀 bq9X! 9@jgЫ$!e_jNqJ/#F?]-ŪcQ dZɬkWFؼZ/n~Zɳ2̱3ㆆyrQ_#E GuIO`e}m&h)P-5I/ۨ|iGX'o6F^n9H낞ݹ 1dE|}D4 $=Oһµn~2cU4[)Aj"YI\6][P"]`<`TJWMbY}E-b^z^AśCY7!w nINNZ)@Oi0߶jav^e/>T| 䩡M25A!1J@j9ŕ:RAōVfR; I 8r\F fZ^i7x8iqoC;Oܤ\逳\j2t?֢Y Q-@J󿃿OLmK= ^hGlƖL~s0z(}/$h0IB@|X3'>|߷p:wlArJ_qL~cV,5R{Sr٩EP{GҪ<*S`JB@p8I$b=a}O11_d$jRo+Rn@"#}a/MXk?ZXKo;C߾&KngŒ1jqo &{%Ndƣ8#TG#-L߅_ F؁bC/ig %UݔEu8RqA"ޗR J$zY?&ݯgeބrT#dܸ UL~'J1rE7y~c~ `>\ uMp}i'X,]wAPS"Td=m'K1[?r:񇁊3v-ʔzgN{aP:.h忟 μu3'@[2 0L$H<f7&׵~jdg}T1ށQ4a#&2%J7nջ}rf-e#"+{-?%'e^ )N#N{H…@=RduCXt|9@'""Cai<}aE;=SL$FLj4-<\kq)J[&-b]쾁 SO+z(x? (9qʗM,hXmd?8Ld†MMJ깶 w:65ko~4o[ !By:;2D%94,KL +'4B DbyBfʅc [-#W2;2v́Lb_dB+ c|UTxf@>oa?=-p$߭kǞC]8+z7`Y!S#(l*i9%|Jl2~I~ٰ䠿ĩ\ ޜPIX2,mjJ ?2vD<-f:}-zvLR$Mti5u65Po^!` Dn<Ъ-L/RJ3B/|Su9ȤF\G'Ysck 5r )ԧ܍L4CVԐ>yt,j%dZ rNioI[wOQf QLtFLK)yWP9iz(K05^Wq,n KUOܙvcT Bfؽ؞r;ҭ]zO(@4 :?!F*5TFjIG:A%-B|1 n!)gIdx{Cs}݄ f:]N'!N/ZdXgg/Omf33LĔ82Ȯ-_ߑv@A3.uQqXhfmrEX-Ы "%uQ vXiC[ѣЗ=e>8FN'@o&jȏiM^)n7$x"ŭC wVvLBIiwXUalI0ξ /16'YsC"=XS{4זRfFW oẸsv96p2MqI>zjXAtf 4xNk]bgݢKWQ`"Z>@&y Սhߞٷ.=b*ep]u^@'1%].W3V଼vM)UEoSJ(P[&npx͎(N^ThV>𬯐VjD^EL8m 뤽fsB1G6PA9vhf/7)GW۱3riɿ {ŷ47@pމcy9ҽR*p\#Ej׏J54J]BVI5}\PT#J+¸s\~HdƔ2dH++.ŖpB$EN?.z0Pro|T #LyqƬuSTl->a2 n%)XN|{^ZfPx7SFw"\U)r)4^Y󧭒l bA1U`юP9],M#ɾ1;z '޹hxfz$Z `Xjpr5ґcI-ʼRdBg/=^} &c wEx'Qz.Pe4ߖԻ5.!9\+b H {fFL L+x~%l3E3Gn}bR ##SjhcXU<)Yџ#4",(JC0pxٹFUܾBҟ?iոg0uNGU4s\t̚W?sUޯ3ґSDRN1#ʹp{Z($ 1 re/m^R"@INQnl) UtyKrdӲ2kaX8e%l=+k̫6.m n{Z 3.۴Y|]TZ[Nr\`*FoF#$mk25nPvLlJ2\I9aUۺ\tR^$_ZԹ+|gLMeǔۭ+Y}>[̒QQQ ӭ#ΪjAq(ޏF讻SJuig@&Eݼ-G`hyE"QD؆9Q߆#YsX)y5A諛2Dب6sm7塊CMF½*ՂrOA:TNJI\"]bmoU™(.SqlMOrs`[i#e^^;rP/O)9 iݠuE kٽ+u8ƚp.\l,Ըz.REr]C>::0[JK)3i3g3X턟f'^i'QdTZԴ5ȡ@ʵG e%6[`Կ>7Ր4%B U8u=| i8_19|s٬AmK= ˨~oIL٩5G/%A0aֆ躪tJ%Y|q[m, g/A4=b:=r$Ol&!ph#BZ'$pGNEK|ygk]zVe{Duf`^N9ּ♗G02a)Sb+NBҵ(hĻ0lsS8#=5 壢W1[\#J5޵1T' & oQ~"sGe fGaDw]y)[`#5ٔ{z+k4a#.|$),Ł qi馋o/K+)n0PjY)#k|u-b rM.烡m g'C::TO8ZAg\EM#?\ׇuAy̻x1py|NByGB>!{1Tf\& [+v893~/, &htT|:z4IpGoތRH&c䘆5ѻ/]%kj hgLC N{_ŐK/587"L&`K.EF0%|^J=J[jFX5[ò}ނƅBXfqF K无kyx-,MP]uDdISHK.eqOw"߿FQzNX =m>jtbf7aBťؤe,N̘61?bŅ5)ݞ*[j@cUTpa58;>B4tKa HM-;%{uʡ;ӒsMEb P26,Μl~i;|PqPO_O'Ѽe]s2 ?2$)NdTѰg@.% 3ӵ)֎E#Q% 7V͚:6w.=yܭGY؟bwUz Ő$ҏI QaJ|X2IU!Mk6 G*"m 7o]pXvpZϿdd\Ϡd_Ly9[{ 96xHlx4N?ZeCT96o:2"^*g^zsJCר/qXk:˘dO' Z {eדXtasSϰ0k!ĢrD $^]ꯣ;+} S]+Ng Ӭ z&J}i7d<]WFDĞ e163Wg>U[SXDaІc~gbSzLz1> IqW*>YY5y=a-SE𔝮! nqQPd3I 'e 7.yG^Ԡ tȒP8.p~WqYs:_QPٴxVQ!=W1|#w&T<<|0#ʥuЋj[˱Ml -%4t\{ WTPi6h]#Z?q.=bZ>Qw(KXI<蒱T4?h"|*jb8z4&xo`{@EB6%O/9y{cUK\g~y$*Ž;%a`G<=/UuZ(Ixc+F[XyCͦR'mVS/8V)4$;9 ɤ $S>-(cG!AsQ s+B`pa+}O9#^1iiCupEcY$ *┧y_ Dh1+*mDBmńXqCvC k'I:9a<&)چ3"07 laMO,lYJ+hQ)bnZ1ʽU$:E. טx? %MV }3s£sP?TY[J,JΆ% aUƠDڒgiyrCCS O;$~!;<3Alϐdy 힛OsBO0 E ' _GJĆhj K--!9#Zz{ni˜$J6o^|j 3c7u=, d^YCU.1uxX]H}VGVs!ad ͩI/`1#\2O=]M2c _TItMs@07;a@^ԥ: وic<:sN;la9JCEQ DA"c )^Rq Y]x>xXkFTW[_sk=ît)) ˔JO HxCUa8 lAb{7%{֘>Cf &3L3pd?0   (ѫEW aj +h֤ds|c5fPЩZTފ'7U,g2JtUB)^}?"Oj,r`]QL}[TtB 4%i:!De;lc}zEd'l%vXUcl]Jc^1y~-8A~MУP1_kHc|ޮ0$,PuK}6d2P]S)tYkq[7AtgA%-紨k7MvgZ GS-c'(Vo%X5iHB2q칈mKW}|iZ_Pa[[`kOPpLن˯ZRHHѾ㋸?*^ "9fމUu7PPyY#P\JqZY? "gY+V qub٭+緝EL%;5=^>=ZymIB yP (bx.Idp)M(3&՟ /_0'Yu\ xtÎ^P}uؕNjqm}=EAKU仃Ի<@ZN׫ .JA#/A\^JbxH j'=bα+,ɒXD?XP|'|T s~>C|8LZJR3#.҃$-(i{V}2}y }+ǵvPf70,y#C~H:G0v[GܩRpNy"ЎՅF_J=Z\K]$ n(~Ty!CXBg+O:a;";Yrf$ig1`yc&%X6^yptttv :” ,%/ξOO9B+TJMc: ?dX73wamf~GX߄&]XKr&3漍O>كS {.ǚqDesbkCէaj4s,{5z׌ŎXZF6}fTT'sN%$$BYgT<<~mǍz}2~ў-a `+>Ԡ4}ni_(~ c,mxBUn(2kVTi¹K͎d"R+Pk_Ks3'ZNa4>ڋ` t}'^@%u, 8.RtN F|C)xCuԸ(.08 Dgּ2uG]je_YdvNVԾ%BCzn2/iKR>7r8}__Py@_JB>cZ>ጃu,;mU! ʆ[m*HK/Z~3 !w*K:AV ي#6뱐%(>5=s)^]45 $S*sȔj(E'{te 6 ?,ę6L>aC$?\V~B& =8w`7s;Hgtl!ߔ70Xn|U&b vn}AV076vm+D0m& պ32#]>w/שe;O-Ļ)U* |>0K9w vD]11 x0$ߞU9B3/MUt"臨UL{uBQ4X[(:6h= &1@Q˰xˀh"{ sBwPǢf^*H *}qА?"̰U\(8DD\bU&z`j(<$\񵤒agCYRp:-n!(ڙ%?:EzC+?u1^kQPYr C>7=gjŘ NKЧunQ9$k??.[:[^e\ m'f#Fnv"7qG-Yˇ9W)~zn%?)I,=w.3Q:' 2-. ٺKB)4GϤvUV,gư*v9S׋ϱ-%&t=K֘H"%>?D%HqMAjyw+x!Ua{* 1jY1O*2kzN!ɿպ]ׄmlw"j&r\Xv *qCq3)z4ۼx%Vx*D}ڗ쓎XTGПUJevQ>הvȍXo}G7Xy)*}4_~tQw 0 (@WowisT$I-05|{H`y9TmzZudj~52u c{·x*?A{[[j3pxpDޭMP{=*\u4'd\,^h"/%ZVh<@DWp? !uOnqs०u}H5mcFkrégsoLGh9$҇(_=!_1 VI*/gc=Rd[ŕ: ֹq 'OPK.'ND*}n~L$^* nNES`& e+ ^VDHehf/b|(R s\l&ȣu0$ͨ@> *@fArE`G/ H;`CcCg/6q &zڇaI4 ( 3[r4KXJAs/& Q*((xa>$Q*[Bk=AyVvb\J:R::53fy#77~$@,0MS ~eI)2~zo@L;:Dܴwtuױ 6;hnC-sQɒT4bE8e>TWW[,{nszY9{;Ĵ9bx-L2e3O:N{e'.Lf R绕ʚ@z5-Kk*$1"XiڃBk.훁[Zޮ`H\b ![:}rg.@4@~ N_;h nxQ/B Ze0-!u $ڞ/z_^b=j&siz8͉;ؔOS_sE/%"}9xv$=xݽ T\y+26\ YHoo 4J419CUFaɈozye(q8P}r{&]/^↧P> 4Q~S%$ϏRsItBz_jcjY5ݘ{ÉS/9 X4-NVvsW LAgZ5YIچ! BDzA6%* snL[qpFA 0͏t\$ C9ݨqAJҷ68 hf9jGA2sU Hj :&JE`Z_ JXш0P;k,hЍ/wQhdiGM$t7գk$`ֵ#o(IC$6W  2Cz _PyťNz짘}=s[UVMڗ%Υݠ:~nWaR9=͵IsҼ ̐^(fFP6wku"ֈ #@υsF&=IT'By0ywb8ӒxVhCPwjGUeDN+4WTzK8k3TB(v6\rv#GXg|kFA _' 7R550d,Aog D/-?/̔e|,z6F6:k08 1]. E1f_(:fW0uMpFl;w5Y"&aWd]L=V9"U`:6?g0xH[D^,I63TgJ.Dw7+d|ј0^N|yaj-!JQi8fˡ4̀ЭcvC2^F?$ ӆagߢF>|2hޯnwx5 d8pʹR3w9|y7BLl^E_X O35 XfA~kn(BjSy+KӬs B8N&h]J@ )--j+VX[5m=;~J87 z}yѸ|%z/N|*u~!9r {՚)wovQ?c.uaX۴|Ȑ,ɡW8A4rxt&$QE/T~I5߰pwHޅue~XԵ1Z 4@bd?iCc9D=Aq2D]n _TLRc&,_L%S7J\$ kO)j>kޙ3yِζVk-2 rchX:HeN}ݜ!=da"$ S\ؾ&7ؿL'Y4T6vH59P5q~SJ=#,pC~'\Ŵ nDQBEB#j[f@r 8iJ+~mQZR!xfo--!MT;g|J*d0o0 <#č1-tpu"ͱ"x`P}dKn*)FXHmFrkv]xi5[}Kf[q}M[X;[܍]t`,!aȓ|/p aJ`U~bD1?, g"A ?2Ƭ:UTřSf֯ YTlue g[wz}岷d|\3~e3*Sy w8f㹖U${Ua !c^ ˂HD^}$/ZN I٘'d+.Á~{`uEJ }µkǯw&ٷ4o &ToyW,2BZ0dR@A{tzMǺϞ>%O)V98v~>#0rF3ti(,ٮ^i}tgmK!4vjOZ;ɔ0B@Y7xF\P;G{Wxu7v\JR="y_<4a6 hY /s Fw694:u6y˃O3^"?c7sB<'w0v`1V?jć`,؟9XrDyYUwC[?-4$Sjj(mh.;S}cl׳| *n()_XhY?[ %.N'bd@q7YƸ 5_L(7V%i>hX!t\X=w%sK=hM{q^aspbq8Ϋoˆw\A0M(SbuN9ۦdwzC>>׻\+=9)"ɨ4N6Qa+<(&X=0qN%Xp1x O_ګb E&9cbco]Gl;㴰߳%c!<5[7t?/eL $>VY/z_/k@|x>'f{gB"TJFCI21QXu9:0ou[g_L;R}ٿV+Eٺ-~[U4'vtFp o"[vWWQ>b"Tng-KjyK5Tc ]AX9Lm"?wp Wys|VV Ȅ;}q'DŽuDJ{\E|ޠWpq;g-8wME lBe ~:B0iB12AzڜɄU}*|B f}fDSGxm dF*,zۜMPԐxw1.A >q:Zт%bi_KS]o_ !^¶R&kspvR5;J;0U:7Am-lF_>Ϫɍ(Ft*, EKרLsPޥHd&(STD^m}/LQ hEǞ ۔;:Ѱ2WějHcBO3/5mpAD"#=iy 'sOk--1؉Wpã8 D>R!Tq:ȴNc< \N[={(ț~eMy9YGكN2-}`|.B1ۚQt_w_75T @XMÄBvs;"^֘Kqqo͕ N1:A.Xv%9shWF=h cSUp] ϒT(sFqhɤMptRݖ ߎ>>o}O: >w1d4Ki NN 7\ûfH-AiN UY\v<y9fqGlM%l9*)׸~{.٠SuPNost@@\'C :S،,"7F8y) $Cu87!/蝖Sm;vC2+SF&[2-"@*( SA1E[A1zOx^*d`Qf"UyĨߎ, =eLLtRb{:#-*f?=G, r=gw$DTF#Cyy`%q̺xDTwU4b,8E= K&QÃ7pEB=l8vDf`=Ȉk#Jp6tu8btYͥ \rCN5AÞ+b0 b1.c':Y쀪xQn}P_iA*i,?Q xmE1yϑo'&dK!b6kPC`u(-|syDK]6@zB(}X>zymLviU|?UX4onʹ[/ kKELEƺ 5n" 0ʇI"A.n Fi#dF'N^f\r`S7+cCUN'L|d?_r ReⅸH_L!pəRG~$i96 6c=_:S6~[kJΆRi^(ɮLcw86$3ꐫ/,Cvfi:ʢ%OKASdoPA,:?Ǐys-N:n>v>@1[un ֚ҷbf _3-e@[V b,w9/H :Y5gBW׃R٣iEEI#) +]mL ?\ZL=-I[63=r}կ̙+ Y5 z;Yxӛ? -@8lu_H%MSMy?'sW$_.*T=}Fi*pZP,}3$ cg7H+fNvB*ߣDdsFs?2:i^C幽%W '+>'\K?ȇrpu{ҡ]ߥ3kBL{[n)lr/& ~:/[il20N>I}r֝1e\G Ƴ}k\ҕ%XǨxiX8 ],L(@a\hbS ڍ5@XexN69=gԇη( '̞ <(mSu8>6r/y9|GCͿ\ؚfouxo7&.ݲ5יfmT7+U#wtSP!̓naY'1 9L rl9if\k}G9}ą1j] õE)6%-<5W͊NnH(p.m"i4hju)vqiAA@v2D`ku4Q}&_w >.axvFt8F >ңXtv4ȱ$)jrq4_@bzQXq QEQ E_KtΈwc| ًpÒivI7`v# 7 'G~5%ofW@xEd0sVnXIx}6'!TєY[P:&< -?6|YmEDmG%d.u!qŔݸt$>/腀=yNIB‹zV3U F#=eS|>g |G vd.72)PFWus緕2P{+Qha}Cutj6M>,ٸvL{2Jϰ!y񤝔7I (RI9Fc CU!¨%ƓMs_ ;jevu] Jz {LeEƀ8<&4H5Rhe{^ ;]E2Tvuy.jv$>Vj PODX Tp_Ӡ͕.n*: ,.PJ˾[VsFu!%~aja{2V)t4Mn6==3YDV@?7{4{y6DJ\ ]0+aCoDڢ %Yt IJK4 {a{1K;pDwqv]R }\@b\(*+ n`Hr (|4dpPZ;ͭ :R^~G\>^}qVqV{ҾR!޴:8WsTx@4j%hwHtpf*tarfӶxz53uݫq*䋦;FHu!c%>s2SL'5?umԝOT$YߣFT^9' JM5,kRVL49f6&:T-&N6|==dgTuۢx5xVD{3s H -À&3\1ر\tĠ.auw1랉mH6Xj=4(hJW^a P=+]wUG.Ǫ,?fBbÅtA7ET_ zZ4΂ʓBjO=:ˡY]iz>D*rXz',Lv1M%K32#A8t͒>}\Pn]%׈ OR-Y|;ST/r_~7x!ߞ.zzf{64iLud!se~o1`' @8<,RyXc \Qg>t>*'b'AVƩ0LC9X %ddkNǐz\m毧_6A*fbh&.֥n;rs.٥ Gٳ$LULRRkW).!=o26+^|YIş =Ccj}Zupo./Z]WZoȾKb-aS8ñARaCuZ~)o*v#V#2gHZS8"x`OWM.%T/vn^OO,"y/v@{>)4kv5] Eɦ 1jl#\TWG_+ٮҤ|{6QUO{UI9GQ.[}6@nz+FxLJ]c8,*hd \9(x[ {k".b< m_Knqۏr ( WB2QԕD5`8Mp3QP%pVZ|M8M:aV6\r^ r?aa2L/gq{@t~~]PG>&lMv2j<<nY9_<ٕB`ֿ* <2]ԯSXO'դ(<ܸW&p5U4yT @mPg ] f6?_$1BvΦ(3x2i/rrG:%khJk@֠|ꁔ|{c֪c0\\ ?k E ڀ2yanQlۀZ+X[^DK)oDVfyP^ef$ciW94 >m#ZAGw 0WRu'ϗ{,cpr=sX9hXP-MwK3eiOAv͜}Q"Z;2 dshln&CPg MwViΧv_!{[VŌ9l }l[sRskRSEg`6L % iָRshl3lT_:ֱA✎ËuB%2-0>:LDVv.7@fw.ވ:M~ /͇0ye ]i *8յ6[]*J0vU}Ҿ8K͛|ӻd%rRƱ"QKտp}z$r%܁] =tpk]֓4nG$">iW·p{w/{֙p8U10[opK#'tZT$b+_5pS=Wm ʑNVVЌM a o30h}),_mXmj:G*MiKڀ36SMl^K|E\"PWp[?s'P~=߳tBRȇȪZOr[ATBr-)T~u֣۫ԟ9E8\>Vx'dF}&)@,A$>]>}RKoC~ў"FuM F߭^=f(ĴZ#RP ̏eq6T>/,z{9F[Ey  YjS˙HHBV)ZA0*΢.]w?澘R.HR?87)6,G -"m҅_!*º4i LVPV-]/58qYhC `Qm"~QA5KX&5nn2h91WY/Q_ `JGl^A'Fb|CF~dmQx }'ȴWYCdVbś'Xy6n҈(= l{(eseYzͮHYX00jԻ2ݬ>oP Y>ڱЌtx<>R|9S68TwXy:޶8KrᵹU%4iĴ+/:NmpO>įzx:P{MwGyJsIz\G(Uݽq:h"4 }EַxSv;kJy5ï6 g"yAۙ\4 2jQϊAx`/ W=y 'C[ø% C)_cz,,d*6v 0f,.aіͩff ?dfq|H#U[DCiʁ'}]w\$y^r/c5\+串D$Ri?|9 X72ιA7ɝ֊$ !9 Lȭu"7 ҏ U'4+$w"a0Jua%-w5U牛TΊjˡJL>G!=yD4n h S?z!b}z4ֲ)""[gEmӸu4Wl KX V |Y2u sM_=;_)bP`0*G;H+ mo?b[(Q:P.n7 zjhF54t,=;S8_{<ÎDf>؂|9/jWٟ?`剹hVUρة-@Iq<^$8KSw辞j)xۭC<;믱:.| hAX ݶPӋ,(^u*u^z+~G 櫜Y iyj OL4?ÉL(!{wTQ].-%lS6ρޔKAM0 w[W$niytOV=2hfr_'^Sx1.~6"J]_Cl;dN˾ŸP-VHo[oX)ȵC֔k~X-S[ƺk#7p58-UK!C̠e[¬܊-\ 6G}]iC2r1׵l~ y[dh腦K="Y<68 w%Mݗft P[pGprY8jq;i-",ODNXj'錙2 W:_#d3 AZ# ״{6nZ-o=A2Zot/prœZ(' ݮjݪ^;E,д\-FpѣJ:$t{Gspmhd!ƆyG;aNl$#91w#"鈥o6cL>ǡNWy̓8fѫc:!`?ʺKTÍC"k)ƓAw ƒMRV~CʾiJZ._A߅d\q!YM=+*juVxe'$2N<D>5maq{b:=y!2ں"+3AE|K9X fj#N =bonR;;0ؑ8ɋ!-yK "یt_M(R ֩୆|q) (r| ĸJr޼x ޼K6ēU3'nx^̯F`\;V{JzR-Jo2p.nnFsIN1djk9gNuJ7܁ ;ȢEBO9X 7$U83G`k/5z.ynWViY>YB+Թ' ![k81lַ̝JNrcU=ګ/\%Wq?+XtL g?FӈL0 Ns%JF7Ӳ@V]額94Ŏ,Mgolc)8XVG C5/ %I-Rňw> Ip} i䄖 Xy*e& cR40d Ajx^%j\ L3$&j`SOGʀvYYڝ~~z܉5NX^(6 ltWbvhE# bhPJ8iۭ벵C$+%ϫ!K >/z=/׎fIw.iVvF)r4t2D" EUX9!n9x2yX<IȰ|[/9Ȁ;w#`|Ay%f7*xo.#'WF~yfk>g!";gY ɶTJC#Az2PNX׮ rYүc uMOx9a~ CʺOd#;4p`6Ҵnv"zGI88ٽN%eNzKLDy"Dv0BDÆ)56΁ Ds"LAbXPv`L"k*Lcf3o]?ە4|W/mF-XD`eJLGPxsV_{' w[&I#]#!\=WeԭġXH=O2|xI Y;]bqyW:Qud%/7@m6@N[')Fx@l gԥZ0++\1L̏?!s\O^'۬uhpIHǻipJ9-5386l&/V:F=YO~jH KkQ~ٌ׎MʶͽO?y5b~p.8k롚f)sq;)N=oBnYIjOWd_sʋ+&MsKAO7Oin^o5^NOMjUFv F[| -{V1mlYws SQ:ہ5p3oҬފ&lځ+5UcHd8M?y'tux4EWO*P}~%į_wi eJY>-\6ӷ1µLLMAC6y9bJӻ@)(6K "#֟{wD>D8%7Ь>p@ 3'WB$' Ul@U={rخ4vңebzwjh8?jgKo[eCR5kK䨓 ^ NY a$nĩw8|+MNQ2kߴSӞe҆U_  Sb%W%HJ\,j_oѶcLM $zpIڜ0^fe&"oBn} ƔJb?؅Y (eLB# '/{~!" fT+`*=sL:V-N˼ȻT`+gv˄5ڗ %j#M"/FK1?*FN %[4T\=!{am>UG-;Ѝ|WpW.ǔh\+_j  TX3No%V p\iK9!,yeHN$I8@Vn?'?{fpK 䳓ALd%Eua+=)KZs)m-: 3ldI Jx#HwIx m*OgWNi$!{jݮf󜵾tPI=/Z`B|Be-vssT:8ͪ'L%ĞMUpnϧ;[LвZoup,{׌&L=kJ|H"oMH/>& 2Q >1Ĵ [)Nm >o3{b3Xz>94Ҕxreg%&C~nyƞ>{NK败mb=#ZWhN+!L'2B ZUR?ٵ0l+Ax^-rϤ,`#`D;@oݫlarƉ Om{ H\CYӅ@z/d3%rR v:%cd_= I Eu#IzE`FٯXP$l/o{qdGƂ V'[8,-b,m^i6$[!hN!ʲp|:/9s $64'*&D#oEsmy יq?0OtEj&x|Q "2|a6wxa=Ւ>ly_cLl1bSmhG $#AAXjULD*ximCIh3ᱸxKy89wh 47*9΍9sjY7K%n 2Cćd2ܡ_@tȼF CzT7#M&̴=YZƕ$F ,Ox_WnahG*R'1ҩw-hz} : [)TΞ7wc0O?GLXu=:d M:.IYC<8FTqLY|SїiuHW}':S"S%)sL M{5UI{[*. boT ,*Z菟 G)JkhaOT$|[R|PT3s6jm)dFR.\AoHs`a tZ 8?ҥBd 3enhL,6T϶&Y #p%d wT;{9 Qڹ#线UdrʿlB m=_S:/+ӁW7S嗄V"nhq(ô>)z8܁_i(;U_.噕u-ZO ӓe[5l{F]}Q-ܔ,u`Gt뙂 OgQIVgkMu9|4b9&y L,ԠwZʑyA'VӞi&^ ,dϕE# 4(!bN(dv7F,Rx%k%'Ga9o dA]1;u:f6[En ם!×b眪jA ;6]gMbKs!Q~.3`iKpq®7 ò*ϳm(޴NjOU,F*EM*=0VjQ׿w"1`wP){Q# kѸCȚ}DhaBD=|=S.`nr|@bh6|@oJ rXD{wm85wťlA I:=$yjDy)㥁Vr{`9ܲfBx3^yIi7s$% V(9 pIGGwأGJLL2ߺ#-zgRnn\j8yQ82 @%ңM[댐v1/y2.?jk0;D֣RF6䓯]i3bSm`hJKjtP7 syRX)XŘc.@8Tj_҇?IY3Ϡ5 q1 ^2#f{!@Ͷn$3 $Ͽ3UHogh+»4[[H\])TJ1vtΑ5Ll@YqX^dRyu#^!:(@ )pNHOۢ$I"%Q&+7-ZUP5ЩTqU79dudr}wgλQۨ85=XCmB+9)SfULT霹GA$rT9ݢaw./3!ZMX`I2C\_ =0YiRڠm:zԂb4$88]!a#eR&z^$bK"I|k@7<,n_Dg/M܁ӠF-+fW%/zY~b#Ox!mk[ŭHOPeBm-MwmW_i(Q "1"~SvIc(cw":b㤻qiS xB?&؝vKP}LW{/P,]EN2ĹOjv(C (%^{iM*U /ᅨ UONuA\kX=Z6+v%Ov-l(W4V+3O9J-%8~RʹNu ΁ ok 2>CP1;7OwL5@8ȧ ]$ y:q 9]KǛ8xmc{{-s /Z( ًhj]ľE8-l\]:s:-`0$IXQ|ϱ3N'ܲ fg ^^IȐ#J }5[̒PFd#HX|"t@$AәUD㯙g AvTvjhK;cA{=6i}ެBU2&ѵXYf2Ъt32r:!qwқP D31Cض§M}s%׃#:'< ;8~?.D;mW-A%97ޚ:TJe(]KB#!nPwX $CIm#LD\ tLkGQ A^^@ГR?]ˆGU=yy4sG-8 er8lWoч%Jqb\_eOM;zʩمqzGTXQW1mL?&$f{BRʼn%&Ar7~[WR&Gcq^4 6i} ԛJ*7i*h@x6H҂D0SzU@ry-2/&ْfEUl6Chm@s m.i9?)M{aȟ~:-w4D@u;>HkO5y/N ~<|%$pShxcvmnڋk EagH LuO &! "r; PG͞>?C\hY;@#ECSNH2U= mwve.<y?֮v yN__`6Khjt }>* ;ϨrmZIimť yOV#/ֿߵ_R=9;(Qx%k&Ҷ#"8$HdAT qLhl ٢ AM-)N 7?h?A3VO˪A=u/BR|פc/0ΜDyvY;M=_Оmlڂ"hR#bIa)T -p7 QW7NN#6BL:q{`uBHB%w1"_4i(vkYyyoya%POE-Z@?|o*kĽUt|L{"x&>*[ĵZ" :TL[\HTYܗ@(t5V~}fy%dȝJ$HgζGn*m2ARѐ %HdAa4GAgi4 25|9ohaVae^--9$]OQUƒ>TKx|F=zCũDDqwEﳸdB)5sY6P-5<vBQ#a+E-DܦXIcܳRO  [zMvhDꎉ ~H&g?I3 ၎M SPWˑtx-^qN\e_++{R;<5x1٬1%zKNV %7)kW$e/-\U[8e ґS1? mBjtMۖ146D ڬo߂>T hxEkǂ@!fzs>}(8|>'fI(i%uLIy[p5x)C^M2~]|=0f: 8K$1 V xHv0I@T*]%T0О$aT#BK\zגۈ7ʔUH,`lQq aimydSY^^JJ"kw}"0e!մfV~ x["Q lp "I:-Ȕ(:HMe.oEV248R kߐ RCZ}P/I?hY0KyS4̂I4zL$8x-?vFTFCfF)aH 39_)UC$6(\zˊV*Fg祵Y_8M; `im' aUP:03lMT55M4IRS..- 87='=j%$U%ynAʰn ;&-LB~,ǒ8";͟7*Qk7x/ap;Gnvzz]QJ7M!Q`_#bN{^{7KP']QU("2 7Zq+7q})ĜGbL'e'ȹb Re+q7@URpvKvWK|b/0cl: q429̘X`MRZ(#Nrl.Rх޺?rZa2]gD3WsTQ9PVޝȅG2MTD}^g{ ɭ@~/Nϊd`JL Ol LE >%AeoRB{ RA$F`O4 J@D!0q,;#p#dQ/[T8 9s(0'L#n`^\fR}_!a+cNiSkVY@$'iTg[Rmʛ'V?^KDNw:K0NbӞRQ.C\R>e痛#EygQ;`Zfd;<_ iBg/vqߑu&vP꼌b!l79N6f P2h1ksZ[\dFxj a/>zUp3wrLwk2VfV 0f_!zDz\blxQtjq0s\`ُkscq2~ $>7ڬP@\.=]IPr^2YUSLh(m j´eY&*tX'utW_\s@VPl`P@AՉx{ݡ}x׍b} 'N*r[ Ƨ rp[SDwm6S]o:4T`(S+p#/ rC$w>p9ߤՖ_w%ǩG{)\KHlnA| ]O8XzBA_$ "Wk92kQT4%b@R/ HƓb՗hidM%ч(6sA?u5Og\<[PσH$ Mj r<[ܔP<р3of~҂q?_Xu6i=uo z'fhC+Ap i^~nbYXdwih tׯwq/6Gp5}veo'iEK?D\júO@|hnRŝYr$5f,-D4\J4vq'T$w-`&p_L~d. ^/}P턕*xb}> Ñf3c՞VA;D6^Q`L0Hkt݅}3l1z+ -bmhlP//>$QCEj*}D'KrXY5q+Էxyx\fܯ~AWX}\^PovKް3|t]F9oלTZTM@UA$Cjc{l-&Ql8*O/qbl ) ,\>B4xzAt_9I;ҳ;K!yfLw9&یZ u $kz)4ڌEɭ]!fA-+3 (מ3Ke>F>"ª*jZTVzz-P({8":@ϛ;0Q=k ZR$␽̼K"oӫҮ-נdPδ砞Z0Jm{9yxt 1YājdhGAv{ѺK^[Vκ.`^6YRZ\Luq6ʺepdoW CӴ;ƐEsv jQ|AW,J&?|]ORi- يtd5*0Ժ>PdY!Ӽ>(UO,(jDעO87Go5)\9'Hf,`)B6e1 d4sj3%&iIżp n[9MB#9s 4\˜.P?,yı5(}Fp/_oC  h$w|YGaoڐDUzYY') qV:;vƚ8}qO$2u7ٞTތ%v4-B ns?xH#12ďhɯj$CX4:j_Lv:Pi7.3]gU/}X'aI3ǥ)SO뙭?KhVH~F]ئ3(vH}8[D\ [>y dAR+t7nTgk85U8ֱ=&eOsN#Kj˂[V+U3ؤFu6+Mo{}yYfA"0_7[sƔґFҪ¶;jLIFi 4P+caU%M$6v v| NPV,q"x}w1D_9EI}x&f7Wo6u)Yfb#I1܍]K)^oOb0Jw_ j)}˰#mԷn,JpoƂTjRoSAg$.[hI8|\&B-`˴a.k 6fܟ֋f[2Y^#riMMI7۷Q08`@'G'x_Fj6=㽥9,'J T m-GsN/#Cgwri 4R~S8G1z ,ӧbHI<xE+RZ-MgTP:b.t3/ 8Xy"F8n|UA8O\HimM2ushҐf'V\~י4SXxnF+r/8U&#|^,t"0hY@b1qPt8yj,Ã_Vreb12^slѳ}]g3@!~Y&5FvivzIF{e [Ӑ:d$]G'be!^'3{B"͡?/2m@'򴺢\3/Sz"엍=Ov! a@3/tnLz9BOl,nؒu}9@Fm}I]FaEe[4ڛRX?#]PLWZ8*60za PJ%5eԵ[o%g w `ĺd?YjV(l~6i+d⽑f`K*$$ENE2[z:d"8̬=U؎OT0@c < (&Uk[{Mm!7=TD`/ k8VA#gbmޚG4/!KoX-%\@U+c4Czߐa/myVWM+dyayg.5EPѐe5r8B>Ŭۭ6GlBKtM{x$\Gڈ86OIJ /$4u>vkdv#.m_]ՌZ>x uDMrkž =FRqԒ%X!OC4*7@#>_ňGIuNalI1[E@3H/ߌXɇʼnzw?^˨x'~`XbVt.)cQ'(Ɵ {2L7jj s|L=h\2[ٜ~jf̰1l雛qY9j[J|QKksf,4+cدNз$%t ȜN͇ƁH+W)ˀ Z>qb3~Sѝ 0fɤEe{2g}o|LGíS'%Pb92eKʽEu@㜾CZdc D&5!v^dnC Zc$ 0mXE#P6w;۟p4wmYx֦Scl]JB{/kXƬJ)D㞰0 6?g-U%'z,>i xv_]eWw3VƛALOۓC3l#G*Q/WZ佻2āRkv]ÀC:[h ԖL߱4Đ('+LMHF166 wٓi֤|H):Ɨ4@Ӡ5 ,k]@yDLUwqFCݨ72gY>X3h)L]#ϕTjM?K˘GC}P>Dk.XzZ/7 ?ņGvp s*րܹJMbVܡE1åJYARVJpMo*ZH/Yrq/%2e!&`q}'r86b;h*%ItNiDsD5ɝ)ށ _XklG*|ڬp*ÏgC1}C&*lN(2Џ3Ԋ>Α4fb~qcieAÿ Lw,QiS\_G%6H&13+Z]sg|կ'皙eGs)N* ~`2%YU c&v?a*9Mpc).ށE05j,% T:.Y㧚u БFzH*賃# 7߄ΪAJ['f'AX.'! k&g%$.d6NޛW&_"i0J|F^~2 ʨ h7ҋ6u?$2xPL,4~%_hT+Ru[]#=J[^i,b-:L|` <pGsD$@C8[è_3 x ñi3_ Ġ!oI?ߔ3!](ЎuQι T7{ 4ۭU&^czЪxKFE2jd۱B?du^pL 4H3^=2yW=+  hvØ8QLWm;YQw`PƣpZm~XZ+Bxcπ(gErn`/yU8t)R0a .>Ջ@x͠~ƃs1 ea< f,'ElvHح$~N#dDA2y!5Y_7N2eg>l|^C[5y;)%Ǜ48LaTEso, nRJ2y^"9kzp8î)Zknx:y~fRR r|,9>ix2/KwG}%bUi;SI1 N,|J<~QJO: Z:a0촣 R[1Z{tSELSP xuV >g^IcGj[\NȏDTgc(*r{ SgV nw][R8zfY3zu ztq*r{%ZA ב/k.wzV8{j-,F)"!w_HGs\DoZ)Vv-'Hjm^;uB;=o̍oOñ43lf㐰1:V|QJЊ,"g7otOLZ½_8.9ۗ>ҥivpq'vN\+=i <Âq4~&U/~?5#7YnK| ߞXƓI*8[lϜ0`ͪ X~Hzpk$D,%5xf ܏صi `!$`Ot[ϝSdՕikP NN/jQM\ãf@A7ecЩ,X!!GBCc'&QcIi%=#n._AJ 8)3#mh'd3&|A}h-m!#f[HvЦ1_3rw ȑ` '-jr6S;{rUjE5,BeYDp(t{YU(\ @ ĭk+!rA[6d-p] aL7|eO~e~>D#1< *Qߘ{j"3߄VI04W0R~>PvفVYYHIT"F'!0 t),+?+d!s'.UZtִ(iH`mPc{'lo%0rV^:WOqNY LUnrR_>Ac' J{uraf(C1VƔ˖QŝT^jMjHj(A* ˬNҹeu$OcG5jK:/ [;Ij)v@qn9 ˢ FW}R8$I]d; ٍz W3 .iFHFF t\ 4/"xY<7=Z";V)YG-Ge<2 l:M4]EX"%̢;K3b3;GC~z=j3)1u7HTn=&m {{jM'o>_>zl.]?.IFwJ ơʍWNUb!Ύ~jDkuoDY)V^_'T YSzNt+qL@= >-򚏝;ﳆ(\fK; v<r &Ru%IM~D}d#ϵIeD4CQ ᤏ)-vɯ [6QfA`>{oJzVwjzUEs൰ԪXqa tNSH,0S$jT{N2#"T6&uibAxWQCkPmT>#C3 ijuLKGNK2:P+uWGعY,/7\[]kv ձ486D%}UcTzKN Ac*Yvmjf87%a^ZK? æⒾ|>٪ ]$\ D(N& jwV5^;Jn-.´Cxr濌M5q`Bb#j7\8)nz!*VL\R<)01#hBة|7`o{YFfo$G uIAEN9M,]%hgVk[5*U&T9;z˹3!liS`xajDnՕeˠfnAJPDKQ)g@?0: \mhG-=d%DB.Zfs Mإw@8RM`]&suO!p Ir- `@A4CH4;f/l8G6t:Rt8bDƶ[pew;_{CR)v92Hyx.RW'ƿk@2V>(!)3p^öjeg}vVn.>0:G2U$o`aӕߔo}&B"M-,PI/KqQhQQdl9f q(!F`W+r>-0&k`IľB3Lѻb ad/ƻ!_O݆1/! c2hUSݹga\ߪK Sd_K U  )nN %!d=@|WUS˥p٧&z9 ^*oau{LtmE.ګ?Hh-`c3.O(oGG;c3c|-, `_2P!&~M](xPTSU"Al-"O_قAI2|@agLC]]n??&51ƥ?2@)ZG g>o Bk.C4՞+u|EVY1":V ݆eBA_BUyc[Q.xO3 0/rGMK ph/2u Zl-;w>U9TԾDz[ =/ǹ JT.Nh@tA 'W!-[˓:= Nַ4!Ї`k;~>&9&{,(’L%HMA֊,|mq!9; `p=Z3ZDi?t;e`jIV٨9Xɓ[kY ^WLCsYeOr^zt>"'Ӗ ݩeqӯso4)\rN8n}h:OEÐ)#Qo+A2E&{R ֘+YM}VF;耿> I[=5ĝ&=S})u`M7B˗l>#XpOvfk=ˎZSy 8 %\ٽ ~K+e"HD1PP yP'g}ji?"2} nRNkԈݻ{5Gju&mܑ@WWP."#.׮rPAn-$ˠ]щuKE|)#V`(_n4NlM XG:ڡ &O J?ApQrY#6cXH z8QD#aȟ}Q+Vk'h8wEkp4pynb_AڣZ$t`u6$IZK71 [ NL}6_Y ckck @ŘX/ X22ηPUз8Z~ead9ٕ r͗Q[ÂɣrUSBP 7: S7M wUw[^,^P'J}g~B=$CT1ʔ&f^c'*~X<%1̝GneopLcm0,31[ˉ/(Do>=%4I`gtd#ȶ[i&p_;N9j"{H!x/m}$*S![^F"s25Xk7b|R^Z?1ܐ`PziN=hz$ 8ne`1&BfmJj,]\.X K>/!^k`,yLILeҞB5åǨڶӍcF .\Twd-ѱBE# 2Us5ot̆؃A)W~J{UT_#D&1lQW1! ;[zK<6}1z:bn g븺-,֓":}pBgdזDΚf  %[0pOֵ0$lpd`PziN=hz+z@&eF0{TÓ(x[.2J, 5~lV*w2NV%2EN2cQ! //ixCCnlid|# =4d1Q559f 3Z*R d4Z!Z Kq+n41n/+ )$&UDA>Y~хkI-ϵH 5K[ uVn8a3$XYzӜ{JNA8] ]ufù2^q1zfޛL&VcK)@8O5jLRiX>ϟm8S16:f]uRn?nSL-$Idr%tK-PIA 3PxӁEQDKۈk,<l?bXBNӕ Enha͋e R|LlE y$Q2}ǹc`K>ϼY9'-ԪsĆ_?a30KVNr>'Jb2L^P 頺*ft25a,7ࠏcҴ¢+ɣWk0)ʣ3Ye0=?i7LPèc)rV6(+tY\+VÍeM=R#[#ۧ]\uԗd&je5U!:`Vہoc /3iAi ֛ې(Rdx餃[;켜øti@|b[s:Sr.A$ $cI+!oKiú$ͻh'[pC3ŚʭM$O4i"RDrՄQi]lGnlX.AWkWE߷ջ "c.>mگhaa3NSD0^l\)~?Z%!ƋēsaJ,M!L<\CדiQ=tV'n{eܹi{#FMi| 5g'84ΔiRj `77[ ;TвZ˞ M10u]eF)_.9\®Fc F[M,q9> βcʘR?'^gV!XɔPcJmw lڙ||cUŁ53rBsf8Z_b!X{Mv0  uP2'5Bf|ى׮aTƼs$?4hr Ie)2DTWqvyTZ)N^R IfiJng! h7ѩm 7xIU+Ry(WǐlhgPdtyA!DJW+\l$$o|GuD]{9΋\}׷Hq 8j\Ù1;bE fʧ,s3uũv Ar(գp۪skhٌkpi[/ahDu>rB %)v0 P糖%U0V+G*QqǷ%J?k'xѣ,>ڇ9i6Aa&f iUV:Z;_ A,Zpב锋IkdۯFN}Ed7Xt$哮瓮%5lRjM^6h V~Mb](0vac.gW (|A~<&^]w3ˆ"i ?JZa0Z(!bp4ogC`b</Ux+) ^h#~Z)n"Lg9i:Lȍp?wR-fUE$l>oԆq)p23ES&ena0%7r:r.370 k]UG p^whjGkU=I4'vsy@J&rߧ~)٢hԛXq`j?? 7s@K3ayUsaςMǞ/Wr8AD0'ûZ,dGacйŝP(o7{5d@B+D(h*4UFD aSKgISQڊ{R?=AB?H`Vâ ZPRKCץZK. E!% Z JmڴEElBK9e4B^MS|tB*5J/["t%;~\\Xp)BwijD=3+Md"37!1mpq;3Je$"Ȉ/(]tޮ%h*m}3 a1 1Cc1jGA*U8/:Eᇘ",:7,*4]3Jw]i 6hP]bi_"͉%6(&M"o du~)4 3goJ7GX7=,!Py? _:3#Xwyu鈈C>v}̺oO \a)D z6E&ђ1J@\ xT٥!9*!]KV`ؑ5ZVKoə#g.'0KaK%\mHՌ%DHG]Ctd$`[C_Ы:suCGT;Ĝf@NS[ {qc}`[KX`hȖ8! ".nڌϕz7:T9I!&w;4JF엄]">RI}fAxbD66Јj`8-Q@W>"-x7ھSxiV/֫)COA[2 2U1k6")E IMp8IzH=v2'_pOwV_+VA};4fL$Ē/ ~eiV4ۭ[OڑaZcJ7L7"<?+ixW[Q&~JI╸aVd)=<vo"4Ʈ(Nt>bG&,3_ޓ CK)+uTGEģEv-A  <9_GZKZv NbO_`Y%dmTn¥rdQF(r nکAUR]C|m^|:D+$ }!?' sL T`k)^ڣB0TѦ A<˰!jN+{[<,Rdn90ҏ.dEcA.גq)9uY 0SFh?3/aIgDIvtbyA=9"Ool[Phel1gժfBwMǙJ(éc &Ѧ~K>1fp Igݎ}j!'=:O{%ٳpȗDC&:vD[QOl2eÇgd˗LPUnh;hYʠ$E. T|~K|H glݕl([ϖRٷIl@ %je$_Nڟf8kfR/\[6)Nwwb5a-Za57|Iџ?袠&Y=y}͂@O'YEn/l4AlEM`lJ_&UR=]l̖b]x0 cTZLֆWWdcnw1t} 5QL(S(<|}]s|8<(Rw*ie jW0zPd} za`!PmSṒ{}, |XݶM_䬭L崅 k BH- ޟ1,0/HNEX ac~$)B&;MO&DQCQЯ/wϻ,rgVULXHNJ4F1ɟsy dEx:L"W2jCUk9S͚Twk)DģVw "N$ĚR/&^۽QG`Sh=@޵E>ƒ@z[ep8x8v$jGEwf {wj "qysUBgwl<y*Y% RgjOMjn&tyOGI|i{9_jfyI7Dx! #U7IhƃqϛPjghq`wjо}V ofTZ%;?,5cWċk5 _Z {l.t vգ9J׾A@RlJ70+@V(atHdd]]{zaD(QAi77T*g70CJ/ay&.Ʊ0JJp?],o6?*{u2F:"Ye`7gDe{iE ^nךoхZ9`nL:Q ^R6"KGj<;*mk&[fd4%r-yey6b5kP." G4d(ޛ#ҭ X?[MUԾVBtܙ|신h\%ūbYEMyje/X׿&ds:R(Ǫ]'>R-i+ƪ*.5F>"9q,r<ЋH3؊L,_F/4h^㞾bL1rNgύA=;o)oVO;ZEcu8e3]64MLm(I*ؘ!ͺ$slHUJ|*XMF:H'|PS"lYNxkp"[;%Xþ.D~c۷ 9cj3=7NO f/~.$sD6x_ UA߿ ɅFf&{edzyR3 "ADf3 {9m ;v?&q€ݱOw\˳,Wi15gMd"KkM?иI;̒4T/2E>iƖvQ4nWֱpzˁBZd;fŖA&eY z{oF5PJ ^'uCl~YڙV F5goZ;[eBph y,O*$C:G:PDR )}p} @|$zЅ Ts#nry-'F @SNѼ- vu*hhڼWn \q\Uoޭ:pf>Rަ'%+^y)"Mv+eڸрC%4kɓF=JD"}$Ӵb[ ƣ\ ]M$Qriq4F"qwt]lPʆNXk@K@!ODŕfb#"E\Gޥ4UeRw"ϤL`g")\罐`oɓV2yryGFNM>̀ߥ5qvJ_l$aChto AxW-{MԍA<;%`7{$]ެzzũd?WAv78j45xX:2)S$%p%J'7D;<)O2z8հ‹.ϚϐD[?)S>fܨ'eWm)kJpF c:3ԥbV9|ZW-Œ"H{elwnzk 81{P+={H *+Chucr WCKz>w>XN))7W4)oHJNr Lf9O~*LxI]ċk0a&AP> KC-bվuZ7WG6Q%> ~I—:$#eUsr|d@Cü=_N:oN2c,Z~hi'l7f*瘎@{h*f/ߞ~fdnbu|J0 vm&8+MJ>>3(V~f =_ua嵬a:D|~2&ªߓ-d6!6F颶t" NvԔ]Gȹ\ioбn V##&z)" OйdweCZDi0"szy A_bF$wu vTaH6/_e3ŪOP\d K/W$NpaH!j@ӍJS&H̠ˢ`}BG;x6P {91ڠӵ^P]ʕ' о;\z̄dӏ^cꚫEs vv@g@;ᛗRnVA/Qp=oTΌKG"%C\j",.Z/$ n=fq7+fN?(?޺҉/#DhBǓ 57LzERaO#+vFhG6O*ǑZ=d{I[~8Kd{DŽ$UjMAJ4U!.nWyBs?^|4IA9{jUVI\ǭH_\Iv{B $~P Wxn26nQ70α;Kp1AvvpN`#<֪w%UWjz{N<>VB7L3DԇNrzCVwc=Ɛ@osyn^掌qqM@2QlZ+yOEț{#RtR][(_߷; /!׊4rnヺ-U ԢgF WzD?w&x9dS[$6g~a>!wS~#e7cJfcG<ǯt,<6[{HXꠅZ=HsOBD@fI3 v9P]Iӕq׺Z،qi;{{<-]I ə?e4VQO[D7:j֌Tl ,J\EjB(3IT.AÇp۫ skV<G_i}^BNX)971?>TQ7`vLw^ɈW}U|y$$%๙w{N|[S"k td{߂sE}-U*J>!c6xp+rK}{P2Ei;OxND}<cAHC!-*°LG7 ̫!âӠKgeHB TlEœhώ .E& Ojiߍj%uDU߱E\̍kՙ;rR^3T*ݐLUq"p/cpekIN0MՇOjy/iC2m=% >Z12\-V[lu|^i7"`!ɑ.4ARDwijgTgdJX,pQlr):8votD ) ͕ а<@?({ҡxj26Z^e/E7g!{2IVnD ËN/߭085Px˝e k"ޤ6C!dþl-Oi,llvE OXcj@(-|!"1{BxAJB7/W?[QhN+ k CI}Zo NZuU)>\9ghwn[}bstR_'9oi-' IOK3/7{>8Qѿ7#'ڷJ^D_D[!$V{XZtUo(hnmPZ$IaJvJ!.ƘW=e0 ^;v큛1=d'}7M=9@t8*L$'6ʒexVirsƻqfmĻ]D)M%#& +!pi 1u(^19sU08}8HPhOT;840XmLnkDΉ=£b+h0ӢGDŽw3ˈΨ~VV";.q?Ăj|/KQuh\.?G?܇ }03x@$w~^{VݟءG.#idp( TlδiHӺ܂v*T~E%a^$%W3Up5<*ջ%[o([ $ d䨀GM q@),tإKE/^̅Ț;%Ѝ Dcka Uq>jf>cw}1Wύ~ʄ.EDG`LjȤKsSyn (WjD Z F \:, x7R6!<z#eE; kt3C$ \\ȇTOH ŧW7a0 RaROD?u]<'3ܾ<,zX$M`Hȿe@oyTi,HU.U.hө^GpʠR2Z9TH~$4ԯ_ב,|VZe $?G, Bc[:WMd 7e)RZ=# ,}u걲 h8~N_]U{?/  Bś*v̑ҹOO %\ @V*ꚟ>PCz{<is9 ^(~al){,;8#@T^ >ֳEV$<)+n-}V\Urп-70E*W\cgJ.GTJ&? #tvZNQn{'fcQməxEզadb$EA;ߑL+`!' }%E(TΥ]+`9$9fE + } d6=拉+$lС?I;`ZBhs yT ʡ(SWui/QP֩P Y[G6rP{[F uRH0̲a-?>6<^*0[)sP7vĴrE",a6fC'y.Tz7{m>˼M\{;UbkOs #S'.jXL{3֣6=[W|}(T|惝!ܝ*9֍w-\iv9$(r}7:Ԣ>/\N"yAm˕oB<ǼYEp0>s}A.4ր)k, U yUMz) %bu ^k~;NGfo=opt g$]ഽA;oHpIp4 Gf 'k;XK@;&2*;n7_tNF|5Us`7~E^Oz`AC33_oGnSwdU'xcByZ\G)*o/;6|0c6C-V?ۉ9I"RYeZņ2 O< Їiǡ/XSE⛏7bD[W6t n@棳l2%1_b\C(ڪ-ؽ?^eU@ivԨ8p hvDLKUjʇ lL‘hTمInq--?KuT؁B0$yŜ93U:flOqoRRCvqa#`ZG?RbuTFؗf2ʓmKtwE85ey>39&[=| VIoZՊj|,i̓ [ݴa׮6lYXd+{%Fblͩ(0Xi ˿HE=pb 2q*n-Zx"tL+Ue2 1tX̉][c{Q{Wy]<տz{-U269Dt A}asi7xMsJ*ظk9s)ⷐȘgڅ@OUq~ 20:=A[dWHq(g;ëDUsvND<-} {եcD dbE=T{J4c)N8Rrby|¬ҔqeLW**0I+EO_,?,pMM d*0Hu*zDoE'Ɂ8?xl$m:[1SU>uh]Sc7 /2RѯHt!4dl;g7UfSǮJSPc,8?NV2⇦u^a8tHr&c30>MV^ЫUe!3$%5 "%5(d o ~@ML|w ?Y+H8V^Ƣpk1!pүbd̰bpiV`o4C`@/kd9o$9BChQ?%<T+*GFv_T%cjާ]x(û, o&u]*žri#q-;LvTCķK@ci>ۗɸ֟w_A&8WPOߡ=76T̶9%zUU U٣#ν닮=LA@5NV~#cXnhL a&ߊvZ6Hq6 R~Kjx]!W:wGKڄLHhgfvK_GN |mѱbC:Ja/0߱4VV98Q%E 3܈m,YT]anIetqڶmQ'j uoG eT/3b4+iE\bUI=QPZ|FGbc a[}UMW6c15/)&1kijwGrE.2,.<; R7R"4 l {$uЏ,7]U(DZ7rM-V3$0D/.Ѭ*i=pݪTȄ5Y? yx8jL2pGC{ҿ-Un?LoՊ&4өc@8C^irtG.Tj`D*|.B+6"YȐ2p!E?˝;7JPFܬ8;a,O] Ujp#M gL= XMJƑLiγ|i !/ir]i ݂}C~~K RtB.OؗqJG}$<j0E6bk*!$kZݤw%F#Uk6ɖ)QZI˾.z,ֲƈ:1U'%L[O;V,4 P'Y?`Sjnkk~ ZF?bF)ݭ̵8)?=s 96kb3[Lb,2U98 m1fKE@*hbtOv7\]_^ːk־76$adlU{φsëL`T؋ 3w%-4+n<>_Znq&A&bI-D  )i„aQˊ?l(Rxb,γ- NAE+Z!-E趞m̘Qm֥`PEZtRyۮGb|Chn3څdjp( $zJY b0X|w;ZTVq[CHՁǖy> hJMls@\+=0$a3t"21 \y߶/cPK̏aAw hh%jsM.){AO`/S2/?2,3 q%k ;o5VOGǧjms@cO vhƊ;jj\bw;ř^( JV*pN$6Xi;5?;n x.S g tNYcB5 +fGuB𧻓vC;mMc< |cTvm'Fț J6YwF\(õeVyʻ  jG0*[+tVV~ɧѱ 01sϹ[K@xSe"JbSs4heqB䗞HHyW?$k 2 P-z>DӋ ;[XB#-JlsxPu[oůi;$^??Aކncm;m7GƂ_ ܯ7hwKf١ BLh9uWIXpnҗq;7gBֶ6rå+|dؘ;CS* !7mr0MEU֟STDtAՔSw A)K%o5:򮓔2SyX\ ,"S/ԑBM=Lmq/(K> ۩#xAT-~/S-u gv D|IjxO1~_ Nn{Q3/~r>m7q{]Ͼ4n wR&~jԱkw)ĭUqr5Dxq1!UxZìX@n $Eo3v`7D0FsĘ2dl_'>tm){-Χߐ |[Uٍؾ1s^!0^(FƊ~P,h(HG*5G!t0#$\ {O%RdaS9 Ћp,Y潋;Hb >BwtkL÷[@ms-6ś,6d٩H̒MG |dW!.J Fb{SP&b;'y+:E[ s"{MPTΑH^8B90AUz-n\ s0;粿/t2p=Y)?]SC yu KE`-Y E:X` _`+s;:$Xwĺ+Ъ}q-/ݼebb Qc9I ˏoHMq'MP;(36w΍(]=RH &!|^7HyA8rC0Z 3t-10 2M-b[ _q 4puˠB!dS\ʈ-BsuexaC3CNۺPi+;O")jIu s5plN>ALzrg_17^`L;]Lt? Ro2 w=X70m3l۝va-ZD?sf<i;hıȢ=V՚~!]qxA)%-Jɴ 1KQLw"YI\:Pa^a&%Y0O-1Vߔb0f{iBB3}}o_:ƹ% Btgl)xu+aUCG"=$T%O*g> H2$Kf"۾#LJɰi&B],4Obݐ (>-3K(Kd+&Tlhr'v߱w L}qKbk猄}e6"{< pOUW⇟BԿq`i+Õ)ZʁiGrhœl?&$Ă75~Y|K5BWdIBRugܱ`8֊6h^O& ɂ1Yb|1=-$J5&Ձ=cEoz(.t1<'( Jt.čNzgQKF>q,"}TIVg$xn eLB%114Jk. ;ǭAtq>ywjdVƑU?:*XTgm4&PBZ%S{\(]%8ig֩*X"ȧ%+s:l: ݷm[QsiQc# =̝d

    Ysl%x딐!Xb%oaAuZGv~(/E+ge[52"y~M_YF_xHq k-;I1>6Uqj2JGW"ɁƖnW\S8Fa\gf6btjW}Ec#E$$i %gB7Zn|jJy;, _Pe;B.dBm_K8=.Q8.G,oݾRi/0 v1Zw:.G&}JxCpn1;SB3j*8}uZ4|ܹp`'ΰWxSK7^sUvt;"2G﫮U>e--Jnw$Tű@٥kc@B yM;Z6wsT ]5[&t-T|#J59 1U*ˢ<_aa@`/i<1PS2ytd2x` 'BsX-0k0erD.Ɋ R35^{xmp9͉[ҟY46sL,p I bZOtN(`]OW\<y#To }!?hBtYeH0+:WefZ%uW:ҒJ5P]=z;0Q xu ,g۰vOh<"&]_rߪӆ P?&!WQØn<̢؈AMVE<2?;-ISu8 V8q>됑RnWW2OVJ"fCkpr= <`lPEoI5]jVWiR8٧$lZm1KZ+PaoB{v$VGIN2%[OsY5/&PlcOxd˼2 L`%M ѥf qe\tXXC)+;$/'tRHcZH 1N. i5AN;]|䫲;мR;Cgz;%⭤? !3L. u۹:E/xeB ^>WTuݐB/ThK7K93>+<( vzsk-PE K3@&%>x㕫 ,*MRavb|芼r93^R~qnY{DM6.&OR/}DMC@ގjq/vtQR=EƲ6Dd Y.-#ҩo⋳4rPkqi0C8`d٣5l|A&n`Mƕ>щrV1\x( @oJItU1RF{̭xݼ&2&?@GYuMƋmSgn!!;ҳ<(Xg3#1hb}, 4G7|#} "sj@)LڌQOOJ?Jj#|[yяgԩsp x1r5irЋoSto7RrTS3u\.W\:>c ^b|k$,k#D*^VFC[;3?7DkO_3(of}zPpiR`ek]s͉Vt/kqS<ᘍ]a6ZGd7O }0noE4 w`6tbW}ِ 3Z i`E']K7@QP"~XzHKX/5@G(>V݄1 i*j`:WpY,W-[_6 ~ Ae " jK/n-Ğ%!eoa8na>]͏F^ `C0St:Ax}GE&bb K?';e]wHZU/ZZ{Ӓ>2nSޗaZroѤ4( ȴŧ')õ=PҿO`aʒ*l,ۇ,#1Ѯ=f2B2cx*'y'2!961 ߸z:fpצ?+ |4DeO62ἜihϧN+CO>yxꝞ"P{JVYִ寲{nĎ? X6“II7s\yM݌ж&fn֖w8ƌob;*r8$bW8Z"k^Q|*.6YA Ѓ_RTuvпX@SrQ;O+Kj{$ % 8 GD5B:%@;9sgQ\`4}r YU614g-2O6A#vuCNR8Dwu;ɫxU|^>Z ;)Vςz^+Dj*eV¨t3z}Wl|O5WpO  1 (d"] tZ,:xII 2uU#4~Zlt)X_:L-A Xӳ!!(SdnCsJwHbF`(>M${RB -3:9pO)pҲyNП[[Dn_5԰nNGl'0Z(Tlik(8.L r@%ZXY䩺=l+5G2I-)FIʙhύ'MT^1\r?|,ĝ4A:ahe H"  g6%|`YME+,"*;4]"`+׆YZљDfp;f[er9."? 1pv!TWֹafV,enFq$;:[8xM2 &lNu|!Xg^׿Ǝ'l@,aBݓ'\{r޴Vq5at`/"yqП5'|DݫE]`goc]ݗYzAi1`<L,-{.)5g/Zۅq2:ClÛ糉ɔo7w +ЌoP1WeѷFT4fDY/>+z?7ARB+B `  oj#}Y_5/e9RkLW<5hsy~(ɫ!TJ}#Pq*a"|5 jEi Y/ON14C^ZpBgҚ3vj L'\%Y7Xe%d;8 Ezܑqf'u?1|yW[녅yX}|1XX*+ݕ\ }VIaX јRAg*#˟z-clsCM lp\Mi87Y-msYD TLY'zp30R> uLO׌θ/[8JL cC{H9OD'xF58j-~O;f{]odL),Tΐa{[&,%2AҶ?/|y&Fמ~+9,A>;1 ?<~KWn(Ta5yS[G (:4˷c>v}6fq[.S˼Њ7BHXSJ N{'c4>}v\߭#1Y8(m0L ]A1'lԾia_?߶na&@9՛p`Ug'b}s:fBfY@S4ĸ E Z$!!\& Ĝ[6"8m͟0t‰p|Jjv*[` _΍Ե%7jK˘LDo, m#%_6lQ !]h!^[J&]e)+i͋Q:r/p"iOM%9ThϾ?ΣZ-j[Ji֗,S6ß#%O(I@\>;u6yh"_z[P5ЩjȬiV&#``! Yё1px7g`( OC >O3mӋoy)t%1_sLvMQ\RL%0dA c۪:dJ [gGNڔ$a?r1 [T'qVgqKV*MG]旕4Yb^(fJ)?s]>_4Py +3Vo99D/> _uxcʌ6C@ w'9N#1_L0CHi%_Elz%k^!i2PQt(yU:4Ȟ?n +ٔ2Z1v3 ? tp5ʆpjG 7;Z?ܠ{ݜ:y(Ai:3Lk;7W{6^H#@(=~0-Ô{>&}`}f=#|{ XS6UT #>H\.g׌jMaD^f#ެ)zD)d#RSj.߱tA2sL)#Ué}QcG-)$PPTVa+#J^eRM)rn"=R"*Is8}v֮B\1KxZKqFtg7?C?03"[0[Dv/ zgtl I&\#oD4YG$-:B|`McLV|12Yİw̱nĠ-DG+,8`4YQFXo$eC56vEUP_a( 揃{Ću/sxX/.{5BԀ!UmzWG6`oeP2ֽH3WI(~;h8-{S5 i+&?/wnHBPE\YA o2OI{2cdSrU(u-r -Xg4dW:#uQcV[׀DIqH0+h=m#_hd Oo'䩃I )q d %4v^f ɘ]BM,zk9: Smꭢ|&Qv DGfI>pnC sO0Zd_5ݯX OAl$1vEY=G.oa”L b LTVpXTJ˂u(xzW1<+n81e. '{m .ъXDvŃ)_TfC^45t VHи7Xslm~n A 0d U74 Q W,XČ˻XƼ Icuv Am#Q* r_NC:{uJI^ĝ@m'թF} ]GC%/n=k'zGxi8YeNؐslOt sci!8? ByB,-!~2N;EC;W65ċ)BFhJ, xur֍6J-aɞh 64 !XDrs>pQ>@} ␚2kSm vs sj* 87ċ3a!zaH(n c,1 ƪQٖCz9oo ڕ6,[DA6:FS}bЇ&S vN?Q8#<@[vCy,d^=liRrݦDoŮmy*4dOvݵ,$t޵[e[<Х LIY7AG!@捱1>=,ёg rkZtk{X> MD di CÒx תH`{q^ V 9GG0U{pO@Qw7M7&ZzNѝ=$Y5b̫,A ly-1f7pئ؋#Xp'E#KW3v &Ӥʢ54KὮ* r#~2чjyHqeJ`8l>;}03|A44_C/zC{>q„$ KۦfzE l}?X\d9Wd'g7lU*.X2<pI P+_lQis gD̕%M+f$81{VuCrFB!(;UaVqi`dlkز8.%#DC([BfH$4̳7M"6S)s;f&|NdW}ƽ{ 3N>щI5>0sl.hhOl*ǿ.(7m*9C,ĻN\@xJxlA0RW[ά|OoL00`3]Ac|7naw#Ը {YITМjk^ JKEsXMe b q9QZ6!s[g$F[VsT{TZ SG&1lv8`^gv S.e86߃"0x@A_Lcbdz su[c6zG0؊ᧈ\ \sV,Fk=W DQwWe_~v-Y@T޴Wlے#Չ#Ɂ%a?Y[o.V٤9ȏ5I=@-/["H`=h5вc*06$Pҵ Y3ְ1Cs<mq+;<#E}Hz􎭶29M 5^ہ&xq<yY *C`@ iY;0`3&!. Оm=VxB&bbgv?a#d9^` :vT q=*].m`<,<% ^g[,g az/h YPq-?s9t^ƨTو>~T܁Ab8lgI)k"׫ML%̪`j0gT:- 1|x_id/ROsM T׮oQ3㟿ZaZ+J=ͭriS05 ;e%[r1e$_b4d'v\0\񈸓2 GXD~/1ݎaH%>Θnu: qd ,74)AaF[+nY̚^%tww`4QV`+]]ljb+).4u&ԑr2;ŲԆ׈a?Fm6KkW>MܑSXIi2z5H9_8XTz_kD8 ^%UFw"]F剆G6.[9`ROCwzl}t0VDty VxVL ifg=/s_:,bW 0T,@OjrS2i+IP]]d6ti@?, wPǀ٩׽.6eĔs4xVx\aM!\d'CTZq?`Sdy#gdq6x =qv˴ :P8'BM XbYhHd0 KD_^?a3Ϡ/+!da~h*wgvex">& ~Aj&5ǎRwa f]Ƙiնҥ?9f1I)е,f41`'u>?3 I8h%n>@<\! `N8ʰxC*!@' 7NM-*U `-MZ|*bqX !I1E2z>> Z b f3 A߁7~e'-^>-s+L[(b|00nw]E.%*f4o'z(@!Y},m0LG^@j&w0JQH/Y_{?\&ᛠ!z7s&<Nr9m^6Ca;m3Sj4MR}/KrN@" (F5H9"=&faP{9W[a!6#Ābz[ ȐͷjDPU%˨իzuDef:s=di~ \ۮHFZG\Eu?.GĖ+xsD8Už7 .R O ӳ097fXI(/>'3&ٚƎ9{_Gͯ] Is*>1ho,VJ_{B`y0bQi_V6RJ1ls^6Vb5?bJ-"U_YonT:&NܩP zLsYB"i&)n8QkilۓlsNdXKAؠ{SU!B0چnKz'~-6C(nIʯ㓵@rX5t9` +ɍrlI?Y6ޛsK{z ,%3sT{%8**5NUZwLP}utuUb2NY44'jԅDމ9M0\i@@E"ЏalEIJ[<@qB7"^an+Db#%[d)4':{ >bvLWʍc$X!dEAxgƠa;i`Kv% ^䄍Y8j[PaOGPFѲXb}$[ѹv:?mrˤ%x<$9w×wjW+gnUIaP,#b^1ۆqi.qIͱ%wWX)}Ԏ1ghu]U/td#ɄPyU:,iR >R?Ό27şp9r1%G˲sTr|AhAh?Y*cml4͉>Im1ǵ=E!\@{^$LH8i)ŎPD,#?F<S R\2M+_H;H}{N+Sw 9^>65aq mo%$W:6EŦķdj~:S^|zy^/ V=L$;$2̫XS]`l:Sd[LNc>f jcv*"JͼaX{ׅ[;- -np!H:Iꭈ߁)7!;RˊLwjUZqsMl" G Cn iqVujP10A>Nb5 0nQuMm:IE8Uw( vtBlwo:cOkYj9hΝrQΝ'"/4hz|>WYyڿDEE"tmEy(I]! (fe[uus)sQUenܦl :ى#^h4t*t|`c/ M+hH2ʕy`7CKJTZ7_[rpC\sZµoMsC^ u"8^JGgUB\g+R1kv0d9EC k|1.n%!lS; iHV*[Kj mKL )f#飃 k\R)F9!*XeIMQ4ڴN9CւgFĠ Jη _oԀ"tHqnt hRs]I˃ /͞Y("(i>gcUޟ< T cu]2P`G o kwk <1 -:~(ˣ!qĖIdmZ`TWî` iOiCNE$y^xO=u?:>ݰzcR活>#&ݏGwSؑl]k{UPh20H13  հH;Y;;4TʄhJNIU_?n׶?J&Bq;lM1B13dHpeGzE ,5hկ yOĥsR&?|,{._wGW1*jCx&VoװLwDḬCvEnwg&5ijVW+ ߍ$c' "Ś򺽣 Bc|KAtPL5-Mg^jdvsZd8~3[#׭ԿC0VKH?>AN4+|BZGZkۡbGוY0}mEdEB6YU_*_@<>t&V?NU8 ^g8m5feEBjlrZZf:]&1o̽*u[QwQ(V;ӯbtJ*^` 4sOO*[_ܶtJ3MYeo5n_vyG%8XYu8*)m]´LL,(\H&RKUo wGW¡ԡ 8JG }:y_*hw/'XSwf aCmsֺl(c9[q>TZ/f Evc/d Sqyй\>a-E/Ǯ=fqJ "L/$O?s ="K9f++Ö"jؤ |/ԈGJqY*Ժnad|b(ǴI)ĵj= R)dsl66W#w_|;+YVy4\,MI2emxsMS)?r$s$X`RdIi>:KyMtRư?yK#7wHc,j1Rko}8$D#|MfV!\m]͆3%h ]id)-C 8NB>MEIgaw'ɿ?\z8 |&9lo_l 7_/:F4O暣23Ek Rb p :ُǢ8F_ŗ.p|U).IԹfnz:VɍLYڴ$*00 1J{d ?n&K3ׇ^G>}S6b4cRP(:@faڳvZOx:?=3R>~K-)ɟژ:5m=p'.Nkn)dLdESiwjs+q񏻀؛6ioM冿gtGyrKC.||7)uijQ ^N[7M%eC6.:cc߰=R?0>rf77O XQ%Bs<֙Mirܡ7/̭I,TӫgSց]O.H o PQKt8ҮzHRd0.A0I긽dx&3V' q~'߹Ӥלż= I+UuOJf/#|2 K -a:.+w&losXv,~(^ LyYR~ t!HWRV'{Fh!pqIhl/؃oMu6浥t}sW䨸GTsAXApx` 4k˲pt+Y (?bgׅՖ`|\Ce2Tӹj瀫k <_[Ry8K ,+=~a`:VøOGsu Zݓ|ǻKwdKrܕU 3;pW--NNs2%i+?7b>)ɔـ HY쳕t`Ho5y3Do|c&3]w1{ڎ]b9BCfO./uLHq]tHH;zf9K!롌L|k@79燑A-B"C\&B2Rҫ< Zv)) ؑ_C&a )Բ9s߾QyŮ2uxzљJ@쐖.Brx.vEѸ%kY6'1cЙ fP}46眨cYԮؙYI$])? :j=Ms5"7Mn\1OA<.BC:z: 4tO%ӼQ?/ iS>o&@%*ksyEUB  JW=pg~N^/J$:> g#p(ͣr>oEޣ^+A}/Hh5P4 o*ԕҬa%(6yN S{m !!.=FwrY2U',QNXN` 4 Wd[Pu隮\SK \!rM{~AF4rF5>QnbHNW,y%υ˕/)^(Jhj2F/[=!Jy҈N`&1Bb!,!*S E*@9ROԘ\A&*mݢ 43Ϝ LGHIKl=s⥊@+ɶ y_斄+8eyּա4KY Ҙ2$`KWc'C $uI7cA7~KuoءZ@!|>'pP1X NjH؛ҧ(W+֔$x?x¸Hl">sќp]v13`/Npɦ[fs_vp3K[ꓤԟc Zz%E~o\}\Sk?v5 ""7ك-˘D5^*Ԭט/fu^ <e&5I_ߑ|%]hQPTw:J.4LUԡXQwȏ6oOT]c5i[eAֹ贎F-9/C"$iRIj' 0P ֤&kln$-ċf8NɀQAm1[ ɗZ[V}`FpZ=V NH 2|0:"6s>T?{ق[g rh"3?2=IG qcy2I]&S.yBq;Y޻EdvːfŹv-7BO;Z'`O5,:?ڵN| LJA廎]aM:h\7pl$,~x̅tζκظw|h>Xhwn}3]y~? q CN .kã ,MA{Vh;I|c);Z$-ns+$vI:NͰ%oJKaE<3ˡ~p;7>SCػb0%'<$Hi)3l H(+8PK7CSU؁M'[vlVaRЗHnф &;|cdV#W;ak;PX/x>mK^r,:r#}cSַ}$'Օլ!]\&F npj:V)>#%駅d2(Ày*}Uk \ E}i^ZsƲDd<ŒH?"b/Ӽ9;Yx5&$I'a%wK9/c@>gZ͖<d_9݋9r%uV2[Ą4I`KܦF=ELF\X,GnxB %Q C%bS6g-$ "n+P18 B<57h3Oz`aH7xe~oA椡?i5K]8͓7 H;Cg%["!+Xߚ-D:_P~ORs⸲7TgzrKha_PzY?7tOD T$I°LR)Cq>`\ʻ!ڎS)Aߛ&'IusٹcAB!mޥ}2xʘ{ 쀞D ęW^(4x{:ڳ6>C9]}jV [Gl"M;d7YJya7DECfA}$wpuԂf+SXPJ_4Yl6C q0m9t1,,kꃬ~uo6, u ׮Mp%zܪ.jY3?Rgftié&b Y dzAb9 QHSndp|嗥~)l$0 R(S+Ɂ,qvݚ){To>Vh[i':R9l9ɞ#"'_̮w+[.};UXY\I`tHWzRkQdy;:DgMu Ebb$*W^mc=gv" &ŕl+wo'F0%^ݮ.Ϟ|R?5F9#ɪdcgvÞT;$eQ,4l8rGGAJN f_Ԟ=iZx8Ю[$T&(kpX;Lvg;)n.-j 37暔s_Tbgcր[dzI԰3U/AhѬȺ7ƉL-Zg~ ,F0m%+B q%GN(&* ()] jnfy "yIl"eSEZlxڔFN TcէL Vcqf: %o,?OvbPzv@SK7 +R妑a+d9 Pjp@;#!  ξbo& eM)~}3la8 & z׹$ )hIz1Y ^-"iTk%s Hl$kM?S&-zCv db;޶}[>ckݼʰ]Wu͎C/µa;zC} Y^Eu3a{X1nbM!4M(wd]}A3!-PYh s?E. qMՉ >y4dY@Y61\0,C&N*@bY E4 #zȓv{j߀ ?$P,%DW@^ Bt(`@Fyxt?m.7 MJ /4OvOb! lx"5q(3%Kj !H >x#P۸V* YSN˫a9.]gG(cZ d_ʐMQ:tmt5:uzꐠ$(|C{89-a7 ,K8d])Qަ5Ƌ#g\lgqL|U\r_qk}3%\%3weU洇~LħBKuu2Yuw1GUƣ.:i}hlxM+(,&^êG4>76Wަ!SdsIIѹt"݃4@-"> &WXDQ ߗ14h"c&؝1MvT>H#tlBC|o/;-O!::vUe7^/)pzk5<1>sO {*tSbAR-=u-ag)}g @q,֊9*e#3;}xZYRf jj)>^].Of`딼g[š_җZ6儰tsݗ2.GX&sIzi)0}X1`PTzL<"+̛6$õo(f!';A D%<دӴ#xnys;Z7#*ю]羋}̩)O4|.G3VG1Wꋼ2}!DP,/bƪ߮,n3|<$̯ӖL9u6+]_ità=A.MR}e |4Z?t%=qq3B]d, D-p. q!7pN =Iexp ADďK* ,<غح$v`VIO7|ѳ)ɂ!IT]&ͩ0|_/Z kPãVA&Ke9 wlХo 9eeſN~qJm~j/Д_*h"W'%%-aQ@hxp(ZFDž4>-1eVeHD/]Y]$n.5Ɗ'v #.Dav6(F|ٽ&T@ܡm (zkjl}g:#9TvNg༨d0;}xm!vR\rkऺ yy݋:}@T0S `U f ~_$~w*H\Z[J^)n)_j.TZJu.okTʿSUV.o"N>;j1H'Eد*KK26ՠcRFV.}#u- ޞruoUq~9[/LQf$Jz{l+^Z>co>` R߶%RSH2/J `66_#fO Th5DyyN Q6DߛdKC;ށsá&щ?:U{ZKK0aLReF xoT7Yϸ]8w%!B= B$'-'[5!e&G1& ^tz =aw7W}4]芆v. -¹FGusϸ'{jccP&[ux^sRRA*$JN 0y@5乾/x,ybk+2f9N 7ou#쁙lo;yy༎}iJXNn^z.S_6:jɋXB4ua\Kb<=ӶBMwQj^-dBKM#1o,UPK˺:PIq3 k*M6ʉT3XS7!YNы㥸p,|gtk1.ҎLl1gQF;'o衂_(\?u+Xe^mxq٠|hJ?bt,s#I6*36-#XoI{za-i@qLg3DEGGjh~1tkD}݋w|I0+w`+X0yA\ B Ah2ht2.jSL#sS"?&3qla8VU"ÈwZluHbʪpIQP$yFgY[_,0v;2('@  &StF,"̧?p,hqƑmK>R^ `3;Xau>== TK֗АQx9@bv?$l kFA+\Ԟ8A[0QYA\h-]j v} tl7nċE]ԨC97iwj.JkI`&lM'`߯~ 0˄ٞGX=ơ8: &K.gEwp.d:FްLFPo2۵Q4c3 `;Ll n^=@8iYL&w~'{lmI*iq Py=ʺ\[G~vnB6c[^~;٤r+GʸzH$0(4E L/薆6keN sKh4L}V(0C7mH;_S`Z`G-c0)+}P:, %..hy(n@~e1hkIʝ2ȹ#Y0i #%w!9fX ;;8 t78s#JNba1օ %yZn},hHY&%hy2_6I`ڗ% jh㻰ہ\?ΰ7u."tsܟWY)K]Pn}H;.snro"j <  y.|us.=PSdb8D9zHi mJ <c?qDw+WKऀuDsCɨT1>|-QO9qK)̺WJr{ RGdim4?Ft=b V3}͏l1ʌBB iD l^qH:^+Y3f,Yű0D69P6!_=u[M~c=n ɴ,,L;>D\F:6+s1P,ߞP'7[ɰ8=em:c`+PkK+|g6ڱ].Zت Rm44bQBzL&q3HzGw f #v5=tMnJpÐ{Veb2  vXdsKՆ;*aI-zrErmR9:eg@+ $*?%IO DrCuot<Ӎ(SF7el_P xWL xxrE~U {™ĒP* {dg7Dii_n\ݸaE7~C ˥݉^x7Ru&οEâPyr%ZkV>["H{Ҽ8o`Vq9r57|JA&0EJW7(80Pig5H}ns5pdm]+bjx5.30FbU8߿I@% ,!|1i&84Tw}͢TC@]D g yއ) Q tyu<$.~1Vva:eDŽMC'ntfLT~ oY#fCqQVg/EN{w*a{^'D@Ԇ]=r$Ɖ&-5$d/#BdJ$k6|_:G}-\S3|Ivj0'nWgר>fV8un)G̕lɹu^^]T0s%!ܲ>ԑ4Dz2ᯯrU/1xSDEr_EMoMѵY-)5zƱإ@ٱtWCSsN: FsRLӝ~x9_h E^#0S;פ%u<&]0 D̏.oJN0V9qWo>0P+cyOaGY:mE2 $͓\_>H#` )See G5̀ѝ0,N7 K(OTO^SmVد6ĊW-Wi*8F49kRɬ-c KSΞAq 71;>ZGHC<޻ӽ/h.iɪWet@HD<t81Q/6p'm[x$(*狒ŔMA=FpO_80%Q. LI/C=Ii8fLAM'1bN<5ekU`*sBL S׹&Wg`buj&dh%;ZAcLi9w$4gjnMH숿yL3q>0Y|10PԙbZ_| +-t H# ǽOۘzZe^KMw;.ء"e$3 |cVnU C=~KIjHHVT5-U1uer?o}l}Prz]ɮtҧey?g5E<3ۑ?5?kv˯3lAJF] ҧzvkA~1 ;n%)lȯMH3rr͇]o>7 ˳m|9|g4߀2sy\nI0  _cߴn,2}͘@PW+-8,+.@cFe0qtHv&C+ا#<.oUB34Z rѯ znN(R04p{ Ce˙ݟ3ilU3_ [9b6_؛[!l:v/ lZ6^wScvե2r_?r#a U#Z96پD RkOؼZ*qxلF'ne {3o"cOqo}!5t {kpu=$*AG`$k8fWt!FsNiA,&E:_8eSDI1^VA1|.c|HgRvgwVJQ9cj,nOD:\p"W0[|T*sm'QE [c=a䍪~|G#|/؋3;s,꭬.iivRb̲I60hf^IJ0)}Djz-GWj[1ۨׯl"TDx6< 80\d 2|y{q|N6dvM AC+YV<@ !v ݭ,k$ 6jtte)ZkMQ_Opq@Elꗫv{0S-OYg;T|ZTk.6@s]NG؜גviv|[":ŵCCj{ +Qen)~KoAΔUW/hO5YsQKV zԢ~hO !ݕƞ'})O24~!$\ZQg<!Y*q &dO5PȘeGp)D(rh>Eu=AP A);0.=%%O`I94+Ie -VOغ,9nt5W-9i9Y|g7NW4p ٵ~rk^5GLfπvamlXE Szy?XI Q '4V#KNDөovˮZ3wEG79[*z-]*j{ϣ{dF*۵t!v.fCiYVMr >'Ѽf C[m(CE+uɌWwg콋jS+|S%e˧b։J1r[m=y謑wF~IXވ`.†vj]AֈsMtIY,]C S> ['?PAAW̵tF'XWF SEa@E.-,<nNg5 # âE"&MF፨7,И4e8-SKSV&.`8b)D! YWYkѼ+Q{{Ĩ`R: _W}ǟ>\*M<եFp3VOqc&̗yjA"]^cc28L&)6z$N]U2:`ZA?#76MZ-MR?8mr2)/_CvxUT®N>(P9^\le;>U(tS<)njI{|[Blgi~j' %)ᇲ׌xu/+9Ll( McݳKo}ri Ce2A.[kŎ}C =Nⱒh%yMW^pȋxϜ  sjd4(B܆+v& @~CDб4soLn!#\ʠeyl{C0Jk☰G.O=ҔE9]ךc-Ɵh,_W^G(3MOb+&-"v#p 'Wړu/iQTd0-)A_0{!aP6G_g >P]r?TyI|~E P&(THܧe纮JL'8;o-R,7(LL4`3"O܋7=O/ܔ[Ks52_]rwZ a eU1ƥ!3.J G%." yos־H @" i+D'\ S'璒%B|ۂwlZk5^0GqtNlpZgESG`!BWжM9=܆!Moz ऍGpQ7DVFcbrV&bu)T0hj( #>Km1zR{]Lߵ;v`knB|L4 b&tݩM4fDw!LY;B 2by[s^)a۲m17lE_ NgjF1M{KQYR*[25/op{BNRqKlذ`9p;MWu#2h>/R41T6*؅JT`$ 93B:ˆWI F%*&y2ou\(TqQȤҝX̳qTDɄٌIBTYŕ`S#^}s`F[-C+c [4ucQFn ;tӝud:.@1#i܏L&/rΧD:]QX|P0LU):33-[je<˺Qj$s9'GY^C6qF r`%OvCkm 瑓!x !06-C]~Ă8J H@0B;lC}1 1tKfݺIw7gp?Z9`ojw`sm/~@ ^ ? f3B؈g۹vu"4v_C ?G&ګ)&?%og9}16oB2-u/dBr aqW*K('*C9h2L5O#A]|T71g8_8[TqTpm[mƍ8,&LZSϽ lrנ1|ZÜbIsOlq)R!RS/u16)m#lTNewG5T=[Tyc6Ew*NC|l>^_g/_?I\էezvZ+Fds^4pgI|C94PF@ӖZss N9]Jm ^W_v=Q'8>yV/N6RaQ 61oY> ți2iT Tw L 5UR2ttO~TnGQ6^| Ns,uLLxs61t ub݁"S`,ZXmpA6*^+E)#uf="d C /y η-0^ 8NjH) L] Qf[h*w'Xgߌ#.vW]lTL[7ZJbSk<5UZQ@:/8fMDtW4F- &,s~[[sb5v#ܲAm w|ڲoDǽ}A`9 ’a4M^;6e[m%3 C[#H >0fb]VuO]c %g6<,GumebE-cS9X$p9PC4-ZqZw0UsYɍy3E9??~MjbUb2GؠBc 4WD]n<5w򝃭>3 a lfKB82bc68tpaxnJuvZCWk&m6s=M+Ng8u۳޽OfN5RGP|MҖr"+[ᑅ$vukaDV}ן&)ROzaE*C -NɟhՁq&\r}dg~Y:g ÒRDŞtN0_ڿ:&&ycϹ $TSAzF*Ca!Ǧ.E #)CČL@8&Qr <뺜1%qm/og3hYfus}l"oiy0&\LaH vsA6@dj5z4TqD4]N1o1y1m;BI8P1! pGX>]%á|l ̼1EspTBY̲aPŜMՁ~ `Q7e +$Xǩ6[F4'Ir:vJ0Q>FMYJe؈M)֕ӸuވF2r8ɤ2ŨK)w=Cq0#,z%7fP/UfuuzET`%h4{&;PzLII|)r^P*&iY} iT?h;Nn l<6^ݔFxiUتWXS0u-sBX[׿Qa_顤\GļQ8V@.}^ ,-z FD9z˴ou%^,e?k!ST"T[Y!mD6=7|WjaAB5"^~!506p}mEc<"=>Ӄ(9% kVR<-[8fH< i* nz*/k!DzѠxF.Q`Vl1jx90׹  0J3Ⱦ_(6)ۿ2b'X IQh.|+H 4!,V>&r8NqVyJT ׿bŰTJ HzӏΚ> ,/򃫘BifeU;8:okp-%1M^M%HW2ZoYUDDŽW׮}' j*Lp ͒ V]Ek}t yTHE}Dhbd˟>nY qUϜX(}rڮ4Hm4L-*j?C:f , JB0tSvQ/5"60*m5LH6EF651B.<yUSoY&BF 3O03Z8YXLBqlq5c"ąWc*DݦZ;xb`[>K픔sREqxEA/dž8}۠ X(L]W^aI"HK`(+Vt".E]&GDgNv_#8U!FGL;}mLh]\zkʼ)k5n 8KWj!'QGo :mɜ[-8ܟ;lb#bgJAY k 4f}d8)00y(/dk5-ϨusG8F44]t.ѩ wwkOtJmPJm3i_y3k Cn- %/ `(/IK8\XZcc1'," !q$QE'LʁahR|ݓAE(eR6/ɯgnR(8oLP'f[!+S˶*;eO@裂JDG܇cMC'4OuxeOZ0 tYNX|oSs_\P~M @5n3GSr_z3PY{]s~nUѻ|S #z\U_-=GJ%yZ7*3 R^`#>H/(";[(80sD__6eUXH^鋠,| }q "$ZL&o VɆ37βlALyT #tJ/ S'/bZ^!}Өnq y.AnШDc>ߒȝs!4 zj-Td`op7u%!B#5 F aŴ[R&pt r6P׵|=GwA4!Z393dg1KmKYzgeӘR$bc&0r@SYmƴPEu[#/1vpָ݄OA~xMHI8䋞{|vm;SF"[nɹҋz' KD"zS-1c Nriu6bkcMP KDwKnyvvN 1ݵ@Jט!5zmzJ%Yt@AF:rY~I ?LCw3vm˼+ȫ}bۇrҴ7Jb3\ϵ({H\/1x18|4vڵdz^ dɑIk~@5;vs1s r0" ]YQR'[ݭػ|"6%/IoKű?/u=eN/+rhı?m9q&Bvd0LOe!` 7VImGѺg7GwH!USdnAX"7y[g ˾ KIg HxqtK %jc XEo=H}F@Fjӥ6(RTJ|V,.S*}QhNj 4:8N3!LVT#tgp!c KlT3l1ƍcVEU$ueL$;qTNG(nHzzأCtU,*CʞL-Z=|\aQMlMwBLxnLB/wY$3zU"ԝ&atWH0Xej/k6eÞ|3@Ko}' :l,$4Ui5}R_nK^MR]{BfM&&HM #gKq,gQvgS\Jg֚{qۙ ΄,R|J)wxP1ݖr4mb|zZ6-w&:~zkq߶66GYwL[pRF#΢bd@/~B |鮅tcTKLg_dcDzShB'|qa*/ (TD/kI &`=m \ *)V܌>>p#J3/E qrEH&{Xa5#!yìZ/5Ō4/26!VHb]enhЬdRb<͍LcDmI'|]u Srs"}Z,/͂O24J. LIvVxz,8# 6XR1z,+,ngҬלI~T79'`s`mcT3 P%x '|t}"&w1;ɽ,RdmT4q%@a@~OS-@C#Y.1mU=S×QD)C9iʆ0)ڍsw3_SQ?#ԩ $5m fu e+K$Ai3I'qq*j mhm*V~FÆX5fjUJ-FG}~i; ң&e0tAK2Fˊ$Rm,"hǑCvS+4w2iLߌOd-sX(=F6"3⮉|/MAWvr۝QB:C#rJA(AՂWܣUA9K3B̭81Mi{)I،}y3)f,Do(Ei(#q#vUFaw(s _8/kSN*dAKsz _9b|#S F#ĽȾκH`am{ZN=Oؿ05G_>})i_%o}}o43+Ho{akS=UٮpJ"j_jDO:iB'S'ev ot6?F^\1kBX*̿[.R`t@B5 à%0ZQq `EFEY{#IMGVFwwO <%n!쮻3Ezk**֘S'gdQj2`VAcq$ȯ95R`N\U^}"Ii+7"ԕ}‘*;boIT–wi.$f`TN5\!oF;c]|[Ї{] wB&$cĖfm*;T09J* R9k-p7QάV]듧^VT&us{9 8⸧HZon"t?b/;2ƞ020лLABEewŃGcYc{fE@ɆK2Bu׿QQ"j4 HiHG;1w=buSM>J[xK_!2ddWєsG;`5dޟ a6r$!xtFsafmFfҜe@"^gjöS[ɵHƩy:pw+>&1é(-0SJʋ :3\VrX#f8[-[TM@q9<4kU>N/`L2ԾO1|/vOdMx y܌@WtW'!35U<Ɯܔ:{%,JRؔ ʲ5wo6dh0$i7SF${-U$ЀS/g>AX"Bj; lvD;bKK˽y eI`m:yxj au uG9G&o;^-c?Pֈn,m'(\k$ I*\>5j0)33`&hba Z1-[IN/' mq-ƦG aRH8XrK4Ly" 4ݡ_,'eC*p}KeP3 4qn pеwLE{ Q5buZ eiD mJ{4kOHo86CC?ЃEML4F2; QM,-@ʑ Gh֦]klpќ|K aQ3rr6WC8B|vZA6g(0]5|Lp<#ޞyk?^*lu޽7!y%qSf js" PA|Yqu4L8#Ce#=4ZPjv hڨgp%3_eS5jUAuR `o>d1Nׅd3oZhTi)mf#%Lh~F=6P+A(: )wɦyQ}9Ka_t]Vkݬ)'BHk n<.I4vnq*/tWKLT67(:Ne[hۣ˲ZQ(O w{E>Z^S.ͷʃ}M8131AK+DTWSS<`K12xyz!lLl&NH437[8EFRjЌL0^2D}<W16.]DylKeO~eB25&i]_bt\ @ݭ:F%N0b:ENZڎ}"D!'&SFbO^NPemzA>*U67i٩-~X({ ]&&1nI'L΄sIj(ԃKz DmR=2E)9pa*ưbeLo4A5 Ql[^{5/$GFgz~kLlAEV9YjxR_s2 :& ٷludVSpG)mcsA1{&fv 2ERm[:mekI5g*` \Ҳ>`!y ʰfձGxN( *Jr`OlcD^N0Zr۸xֻBNfro4"V5Zd" pϩB:Lu5yϕ֠&bVwΐva M|dcD?h'~uS_˱<ڈ`A @fK]CtjB;ݼ]1kΡ0WқGt׳mC PU똮ҍ%VCX{)z9q8~,K3OQ_IKӠ Z':130Z|<V_i3ݴh5_0*R~̭ƂiK{gU8@  ]rh@K%ֆsuaͬ2ahn6Ϣv~xV"FgB/2,A# G,ijo<#o)ʼjck'^:*{ZU7qZ'OEbK-!h;/q{A1-۠FkRFNʴ-C"Qږz Zeȱ/lfDe /2u|M w ^ ;.upυņVT-v }kA"󬁇*)IU׿m 2Z9 ڇ`14J$5 >tk lNeVi8'AGЅ.7YO 9M,n v6Xilo[2!4;ɥ$84/J m5_H3 <b&!o":g;oG]&ÂftѓqBGL4*ew-7r;W#)28! ;K{aJd+HP.2c]"M'. :Trߣu "^z!̀IXф4/{t$R3zA|[{ gJ+rg A'_i@'W1.ԎCՠfOZ`iN`ݲ[[25y-ƺ}Fcjt#q t/,*.rU(R,*Y#gHAڮt/gd8~)k ?=,jOszj*ODG2$,ܮBrƑR "uÝofZ)aIKA[ʊ@jS@X>/Gl:撮C+%J6k1PVRvp$|`8:p4)p쁕 ۉyO+j )} AS˟-oXtl+k퇎ٞة؄I5-Q5烦¢Y4 4ȳe gǧ~%u2„ICYKT귝W*9N+I2ۭf}m@22V["2yonN)\;h43Gn!P9Ƌ^4 ?IfYOѯQŵS)0]a %u==unHA_m s[.ݐ8@JM_'LPӓ4'4ﰷxjb{wk @1*kHy$30/ùB3ܭsOcRxĖ 7 ^_I F4r& qيj_LF/Fb|؉6$c!`ߢf87~h  xS<2|Iw [2}Dž6 N'6s'lh-4)9YP׳׹YQB,{bp64KXܗy*jK$!!yG#_u CKL 8)2xf\;Mqb( &ɕB4cLn^ݼR<&:5i1nm2W:&? ʈdO;V!ey0WZ+Ody\K 3yT~X0T|*sL56=*;h뎧$O-w5uDe5icN[ bs#VZ`Ex / z8ܞcJyWs7P E8#[V =4{݄+XEqv²YJ'TT2J,.mީ3!K9P9L2̾ÄeՔ#nGgz;ېLtI9׀,!|-~\ Q@2Wg * a||< K}ܲG=ZF뾙;a(g [UgRj(lla*1p^лeXS\AzD6iH}kJA!z>K,t*ց^p\u"v&l A-UƼ*Sp%Z'K-!̊w$$@f1|fq`\viP-5"=n{d*rTޥv`9 _EՖ8Uy4ROLDjz#)3?ppUEӕ񒄎q[26f3р!/&^b5DgB{l@ /fia\ ۤ%l$ƻļ+'Cbob %6R:)Jͦ@$h؁$l# f`%P,s{)J+載N{ο99>k(lIWQ+FIVH0YNE"1>랴f z>I RXBߊ҉wSxÁf; gUl̀c^sh w2J|\nI@p *i2dIB{L%鑢KmmrpN~&&-Pa_I.',fgq#Le@8cS4hKvtz Kͦ/3ߒeo,dXVNr de\Vb}E&!ZP]2vi_= ϜD}7~r t&y_WNo <>Jʫ֕ٔۘh3Tla19;OKur-y}n洉-y HSCYgn:еrksQ" jW6Y^X*Bwˍk<覑ada\79,1DL? ingJ /|t05]oضjB-2xʈ<$vFx'I fk&l>THUћ di-p…W-( +hZca&JR_pޚm9*{f`, Cl cLPG{^^ҫ"wIL҅SE1\mQxLfP̄5t&V qJ5@M|4/ϲ0d@GE{[rlrƮȍ`1\Q^+RQ!a{s\k8DzeuONpõ,M+6aEOOEoJṎ A-\ur]#p3De>i*r؍4a O%}S?Z~!AQHQ|x&dR4/WRf(ap3Nd>vs/QEudrn%`-%e=NnR WB1[co3yЄѿIQyb#6_]k|G(4\v#o6AO9x庸bK 4f:Ñp|(UńLu`#IZYR[_/⁰[m53#]D8;BC!a εO2S^+Fm OVlV{r_%]f/Ѽ8nr!S * y ]e ,~ d7Zxj>5<weW%I\f0@A;X}QfTؕpNI/`?]6.9CJlX(XɳqVz(EV,cI B{=o)Ir|XaYdRak aNQ (J fKiWu>,ڛVaMc?AOkzm.=]$#΂<'M?7}jL׈J*xth,Ԋm#k\*oxF2 {L !^gXu]@54$z"PJWwI3<@FB\+dbq GvQOgb 6M_I pBlсߏ@>uU_wFUh&el<'{|?U {TzƑ5PR.S]I>LZ0;#@A\/;-f1-"Z={: /Z)oWb@Dqk@zjd{\{qdb:e8ky4.wӲ>,RhyW>bR'taԀk=%[]CȮu7ܤ ~\&Gu!}ppŊ 4f٣-N 0-lKDžp+c! bk($>¡F3xe f>$;akkbFm7p;brxǥe*'*Ogk۩ nl5!-yJ5)0CE܎W:65gCƀ z<=JDWȗ;d̗4"'||\drv_0(14[Tf}I>F!HF& к,w8JBt@lpr[ަCz~FU7>(=։h#ΉJ^kD7Y )49MJe|f[0Y?[53"0u|(d P 2#?]ݎT2+[ sY"QA:'GuOFrԤΕBCPJ0pD A|N&Y.єHwh"θom_Ikr}!B wF5LױEL j}R +55 {s}ҳ{$}`['6xԫ* |..DՊ'^w7(@LH[}Wc`Ш!XV)d\ e2fH?:nCبsu/d`_|I|-#m [v $7!j<٦7%UV̼_z9/.bR0}w; ՘BJKBϣ)q!%9jhF0{Yqoڃt)ռMƽ^%|!2uD@qq\̞ (UN Nzę@Jb:aå\:O#e]|2.(ys"PVƎ}"jKzGGwF޺KU!ޒ־zN6O H+{gRt|iGïOj&AM W@:@sgp}|O 9HTXL G5A6HXWV,s5r+65Q5j`f"YΊ6؇c@{S49n ,9ưW?,̣7tiPqC!W\l/H@ਫΟE< FHL Xfj c8HZ ^J,_GN#>xQrd*(SO+}RK&LTs[ZA~o/^R Eu[R\qϜiW?[3$7hEW9/΢؋|{)&nWZaaTߗ|h!G?G{93=LtbyXG_U³oCcQćTm0 XʜWj*֨Ԙ^4wk}LW͚HG4)$4 N׸͂c-\;%qjUoޛ)4_0d esZQWaͻޡӝHE ew ;w1GnzFO>d'49KHĴ)\B;J.Xo>Y 'd3F ͒ D/'`b|dG+v1!R_H)}DޝZpa[oT.k++@@KOw]i"t*w46n'putmcj'?_D(DW">p|_ɈAE!@ʡte[U[NJumDV:'uVl^+X_NIjs퐢IR7隌֥ʙBS?9=Qs>s Rk\ ,ٮԡ)נ;2H27Ԉr,zkWz(m@x݃HD-RyL6N(RP^7M #F$Ȇ5k诧*z]ڙ@uU?P9vT IH :N3MP N-P"(0+lUZGcnl9:p+nhI}eg 53$ |kS;^ʥfRo/'y'\.H!9fi N|x#4Hjd!.iilBS!|*<ه*D*Yg p=eGUzɓPܛ  @~]@sƇ_(}`h %q߄-W{Hi :ӾkeMI{AW9VA"pYz)p/aƖw^͉cԊB%Ȩ`eYfMjj/!<"3|.åK\sF-u8*'2@ςIwtĩ>o2J*5_W7Z\c pt1 naU"'eȨHSCS (QD VOh(G#zo$R斌WI/h L=n^+ >t_M|9tJ1k0tv[иQRI%*"#(uxR|HXfW|g"@I?HUx̗ ,kkjpԴ4JAZu-pG?O-5JR+mWS<7 h\xD|I415l3*y :hsʺkH? /l&5N~{-RpM!qj&O|JШޣmk'*BES+1aTwֿhsG5z'Ȫ͹٢z?4]n#]EKcKp,s-6e&J/YÂn@ַnke|EGމi?bIӟ?A}2JjZڰRŎr$$ML9F'suM JzHD0jL V?Yk«ZVF,JS]X貫6T~cyv\ɇVjNS-C}ESԝMTJ\|WN+q?Uf_^c:?@I}ѭEܚh !m4vt,WL15w"L?<7ɃD_EOӽO|jd0 [h22<4;&ǝ[՟Oz{Q3~趯ql"gEVmtѲ9>|V<J#f*ܰY,!cAQ`dæK*J8ۓDULƘM(kFho>eAɣy&]jpMnfd58!8g y| =y;K6X:e$`+T@7u,z6QGlhF0~mSЙ$" ]7嗡2d˴vzmF~dk> ^SVkD 蠎0, 6Sto( w88%\1}%D0<)4!oeuSH\n 86 =Y佽W=ն;.c"X h`Ӭ/;E'g˳`ȝ>@ .*F?ܳHLjL2A4&D8kɛaڷEQm=V$?P[S_{aՍ;RLg`WgVDH igEY 'b4v4"u?ʀ30[Y@rI!'zN  uhŵZЄu]Vi:0c%/ 1K/0o:X!RZb|2_SB<҆l#˔} l8I.crnmi%7,`"[cgJy)rywyHR-0/>sd,cryK>U9!9"]F*&{_c#XeWU<0 MIkW17VbJ^׼C<ܚ@axt3@ XJ5KΥZ猼@gfl3&_ pVSx/>)[7ᔋޛdgRҢrBZ^ލxj15\:Sg+_nɠZ_tD]8!zOVg!)Ҽx`iXګYfA*mAG-̊"*kt ڑ xxOa hSCP@VގqH.@a"4),\MYS.?!6)j朾!.P4]訆ԬsQv6FǑBpeO$-n6҆.x :,߁{~M@/}5*_燡3N7H)t%@džXKqͮӻFy茍=y`@9}mrJNe"CAPc1| 6)0WW^|X|@W i+먡 n-Ayk-E }=mי2,?3^,P8#CySOFc8c8ai^"!obMMWL_͑v %-gePS_os}:,HsSK$hivL(; eTlމ6}>UIuPzq +zGhO(8. *{80TKc91KꪆB \o7g%6FD "[̉UҬHr+8,\G\ݰ n+"{80>5xPq]fXaqPC-ۀd [s&/xtfC.[Wh˥yZUP@+4^/fm nHM{b]1?a,4Uޜ{B;?BXJY2!3mϬ *Jj'l ة:+yZjuiK=ҳJsznW{9 |Ŷ8Z[k3!^G1BN(pxH%i "L8I2lZٞ(CsKr9 :^QdE^:Z\sM/0t5>Jߪ?g#bTݣ7dsk6_U?HDvӫ;hYgVY>|-'`;dĒ s62 {Țll0e-):>~9uoܴUx @1Dmn o%W_ Օm"D(.zKnԀ/FB8fzlba_/5}m#[%A_: j&VcI)#ۚ zgMqwg/3]@W]$Q2^"ۭV:-Ug-E==ھ&&hbi.I 3}J ~{vW)-hymzxSJƔootl{#$Pz$^|dS&o4:B!v!HNupN$Z#G)n O 8A  4H&![Z?Й@eB,u8tC)z~"Ł7mtjΊYäB[ϼq6㮫 rJngAD)8.gw{_Z6jEAD˧lؾq^W)DY7`~Bj@:֚`!BRǤag\\'OM1vKņL~P92-M~D<Qм3$O I]B'(cmGؠ̤̯nQi๏0j'1jA5m YWp3l /q_F|ҲY4vvh$"̌O#4 7>]9"wE#W46} Եv⌽}Aޒ=(=Gi[]pO&TȆ=Xd{s^7? }y> -5q>'[q)ExU z4jLvd?:A1ti0#1n@DS,HOt^ROLt:yj u( f|^AZLȚ~q*j)` 2􎈫yP]n@)_J_xsӈ l+/XɍaMXJ>Ք:X>'= =it@w*/N(T<R)/ܗ˭%g-/b-\NIZyJEIq{Bq,xOS !S3PC6'an27굜~`:+\2./c#99s?Jl}}.l2:3`29P~;!S6KX\Ek#H&gwO˅ ]m? *rlWD6c[w; 3}e ".D"J\Ү?ZZΆr03="5ŶDeӉ/Ϭzu׸y<T 2Jw&\ icO;kD84ȬiEUY>vs̷~T(-~%-hjb+c=q*Q/wJƖnEoTqV\_;Dj/<(.jcZ3(M P,F!? :¹ (ҍ")ZBJ}?b5%2+hmCZ1|kEi$*U0U58!s!jd1V,DǞ%GY"Olr;R/wN*+H}q',%[.Sm]ӓ?WH6{׀;5?Yv՜V'Td=@m= 0EJDQz.r ㄫ_UUzU**JC.@#:N:W o`G4=?v0X؆X08|)??๖&,A2ۣ#{G}H}0H>qY*n K :T:f0HXUp gtDzKjI{n8^{϶f"2+Aro#pĢI.EOGWlXzjJ| GS:y[_;q隤68rI0_[I4"S]TX=4#,s~kemmf|Mİ Km?8i6*B2a.4&6R"gۑSo\cAv%PsAm:9Di/4 hueyq#'9DpiQ QJ4 j}ML[.75Or[/H:d6V1nݚ κ_] qOk.Eߏ)`"Wuqv_;,r"V?*y3 f5 VKE*Q,.Uu&??Mټx‹}mEм"#K+>?)^Wɖ֙WU64pf=r+=JE,24R+=K)lNۛӶE~1|~J؂OD(H mbm?*i(^nƗd~hTډχΪWT0<7Y&*.lz؁C:X kU5EsLҋ䥨-g{ixk“2uȒ5}qp`h. '*=^\-".<  T9gIg{t ,z|P~1FXጽP~.g*!}+6~,jZwsqP~;sP6V#n\7 g^>hu$:@\Cnfl;iju 㲂AbD3%j^͕d_Po4=dz3B$i/ h1xF1IK\]Y'@t\e ; 6) ujB'O5.QM;վy DS$ Q=<%?bfϚZ^Մ,BX D&FcrC wӑ bCXlA/f.}H?;v&1CRI `pND= (HuzquU4H%VE Bh֭W5vH,wZ, cMR{⯉okT 5$=raI{~T]#1 gOag%mUt̸*6Īc> lXj_w$WN5Y ig71.Г()3ɴWd=pN*'}uV XWrNr4DIC[<1< Ez19Dos'xϞqL?ޯꖒc v`lQ>a_s [*ɧ"L$^ 6Y7e0~pv#3^k=K(c;JM4 D9k6M7Kv90$ v?P\n)Nkq'RM\Vxm&8 ]]GoO{빃rR."^ 2XLZHyK]B8+'epO(gӍվ(5W Fp?0J)Np[;N2>=} 'c)f^c AHT.B ZFJ'fP\n.,&QwgڳF$[<=YAmO4^ ?7ZnVQW>ܷkBhdev -H*`€3t{9b#5iEJ+)BD64/4ak &^ Y}p ~#<3'vN#٘spK\btKm 2F1nj5g ?jbiϑ);.Aod4lbۃ)C 8g+m()=/}:JJ)A2i5e6#hT[R8܈H)u%z^*=Ъ>xzO@RKmXk~4h˫}Onyx Tp}*T72ipd^:H{A{n;/:ѹ%PP 8CYte(^[! 030]Df _ **8b+Kp6`:w~L8l2dJFva4Q"$@ާ6Wpf9 c'o+,l^ {}XN/U:k+]ҕQ{vcuϹ"yRcPʋ/y̷1Tzr}Op5B0u8y$mܠP͍->Uَߘ]&q?rvۅY&[2ڿFc99F e?E[| hцXFC>]wqOz\+)!cňH%#ZYF^Pq#[:{! UOgIL/8#w.ݡ  Ex5 PMZ!*e ܙ^Ht*8bm$<=ⲏ(~!9k~H2K4P<>UA|vt+KLMf@!,Sm2H,G8u$R"Ijdl[rsU(8qP[ |*1v(@-T)'?*yG(q 8:OѹYO1mIz|?|Oexsʷn;凍SW!L_ZNINP4*l=șZ7 KIfRw/t%~pğz'ݤĮMH5ˢZl~Eo#QMa9'dQ>y(τSo>{EdkH,5(P9ׄ?qj4BAՙvR%&Doq1bQ,8BMf+=s~E@ȭ1~tlWX sz?פuNj%qs8(shǰ:"pMM[Jһ(ꯚ/ZMB/SuDK@"d,Q eͥD G$> 8.'w)S*?='- ۖᄒUr ߹kpIv6[D *z|n̓=t<،$~ 6FO/n; ƿMǧ4vf[ŭR K^X~ll|ΩuVYxX1VBRIRԊ?;@`[ a+at}5BuCk;{wAd@ĭqQ{,G;#b5=V̛Ej%xy"a uGnm^&Fh[ ّ32Q|iCZf\D.T"Q4sH( *>AGX wOu\ˋ{,LtnP0m$P?6M έo 4t_E6D]v3@靖˴c8Ĕ0%ǁ A[%TD c4;HcY) C% XH˔ ,G,jxҌ7> DuGl3Fk93 YoX' 1΁J_1uW !8-'/R$P]hKXNr~o'5a >sV%iF(>5ժ߀1ۥ~ܙZP4d >ӸidO~)-S?]J'bb̲ g^|PT49(^"59%mKK .m R["Up6TctS<=_baPܪ]]"۰hR0.&odXȡ SPT05NĊl;5Y.+M_Sx6r:k܄A`==01ypE<''DS BPxT{]'D2/"[j jG-/Vy4,Aʸ U1t/Y 义%)w67ZV^i]<`cxg0`8W֛֍ yeH##V塀*D[4KR2Є((tWa0L vlg„wȋ\3L,0*Q=.3 H޽ ?d;|´oؔ;W˂N jY ~tOc3Rma,MSa;u/ (̌H܀[G%){B?]_¬/v Ub=1~դ. \`v_+6ʱ:a|wRA*`w8[7ݰn1VY Y uAu!bJfsv4mAŶi˄՝׻ Żl>P]T:SK.&{.G)T@+(<Hl&5"C:Ķ:M8JX r/4s=m#W-S SvXFȱ42UWtIie# J|PU};f }x#Jr[^fzx*)TD1G!aydAi.ma)-P6u.+OrpA0P%ӺnXCzN;zQLѯL9@G_rf&5 Mx2t(v u⩼2 g2&`!MdW2.f"3엚o2P)bJzhT_3=PoGaq)~"!n5ǐPfw1H>hjMe<p&oį'w qP3[i}L#_վ:m=?m-Y2Zߌ%DkO!%id))~8Ԓ5MOk#z}/Wh JB7LoE;u,Hf\T[ID$8y.*~EC'֌В@ՊyZiQ[!k 9'j:#m]-Ꚅ&|52/ ;:nk<ΰ誗&w6oTPBݗM QjI > ^yZWJKX֟mtYs*ƨ_W8;ن?ɗp/Z̳آDBTj _vM2V|a;%tf#}sb,L0J0xx!py@Um (Dq%ݴ@c*D7#MF8h#M񁞕 j0~l }+ǤA,lZKކD] W^ sDU>b zM08@Z{ymtL20}⺻nz.8>*;.y/wCmpӇ:슼]8&) 4f(('"tզ{ẅ́8H-j;$aDQOdGD^s)~@F}_lSHݓWFQ"| Veҕ]+IbZO/R0Ӵ pO_')^?`&|7ȡZ S!Ҝ`e,5GɄW fz.O| Y)u_*̕_ /u=ZkM"P!W7ʞ}^&d<Uqѩ%deNz&ٕ`\iryGXGapCv rQFؔ&PބI$C6jWs{%Y?2ճ7b_ao8_f"R8#k`q)ƓkRzB#aE^|3HY(lTm$gm_0QߎXW)ROUHDow$W.^e&ŵJ&#]\UnRXPʒFS9/i.7[#0ƖNj60u˞;a9iL@0TkγQ\6ӄC˲{ccw]k|.qf}i^h!s;xYU,.D +*P2:P&#gRm3lr)RZ,ՕܕT~.{)M'Hlwq¤$5.Ɲ-0'No0b6<0 S m)E _QE@ֿd7>J$cǞXd4v`q)=OHzHyrh&grH #mY`X )PQPvuw ^W8HhHCܜ RzsJm X\ Ig𯾧gwl.%EdQo׻4{4r BGGG;(7n)B%}pi-u't~53_GyGpV<(t }1Wmb{Qr_kߣ5/eH̴V}ASyF/Ψ S(<)i|&@r5K޶f j?@i &S$#H=A)ڷ19+\}z:'>:AvٶZcϰ{PtEfQV8#ϝ9a8$S,?SPBx ~EbE'-te~R |o37֠䫿'bpmͿe r־Bee ]ԔCX@ ԛm.[(ݗ_~e0HcVxOz*Pa|7}3>5݌Ӿd+}\Fhlk|>%e@xXVZSy@QOP͒#ԙŒt~ŵt! N2" ǗID qpb@8:uW&sʡ5#&!i.>Lz)v'C NT6GvUvc2gY;+0#k Q~5z%g {]427rN8N$;} InY^L'ROݦgJfH<?2?;|fw| ?ˡ,EaRG-MY:(|D| ~KʹZ B:sT{R[/އB@h6 jKzuȾw׸xEU M;NAc=\9oSOjG:|4^, apzu Iz[4(, xy Yu6-*hB^Qh|#pb}앟 N~{Vl:~1)94"ɋMStτ K蘠=ZB0]^J;@t姅W8Kb=xCJKNyJ%Ǚ MaWSr5"z8NإVzƔ/7e0Hr4-.φt R*4e5yqfg*|5f"S-l*9Cs-E=Ե@H_Le9/SSxfS~a1lp.gy௜$NL_x80Xu7k2sݫcpD7@ 7aLҽQ|"Р u5_1bZ :3@5N%j󳫼%px_Wl|/h5ohF~a Qc"5=h_CN cqpֱu2 S#/D|O<32-yFN.oO4TS@g)fs;yw!ۇvJ/9DL@8yv##`۩=K٦ S/RԒn9ܒ_cAHȯ$e3PHFvF-gu#4_[3}!&8Wʰ'9mcGy>VRgvJ v%đ7(g?lSk|$ʁs̯.m5;J`) tqiab fjٍ9ړDQvx$Kw8:hJĘN$J:}q,J %+5VzSҨL'E&pqrdՂ$/Uu =PKSH#:,{Iأɸ.j;)SBFTI3/OSxCa+ڌOTS*@h(DoU:S&.)>ёԎMa/ ݗ:_J{\u zxz<1^:LQX)tD| [ u7]6 ];325 :&G 竈 F0z #7]}ڪcff(c6J'{w'ۇj$:Inon^%Ի?qA_Mmv?AjxA *5XQG7¥cc).2Jh$mf BE D_jAQ`"&=Xrޔ! yKP6s= ;HRjyJhwg؆җ[YN!xh~ Pܨ*veaP} 'tQ"i,EXg~InCi 8 @C(3'ȶSaҍ`CB[VձJE4)F+4a&Wm ^# i:U8dX_B~?t׋}KfvX8qw <>⹱n;knC/ߍ^0Gu?H=1inhg nu-i,n 8+]Мb  آ-yZIb_%S3)z4@ȡQP[a/'N]*L^?6wxT!T+k\i!y%( =Rnְ_R\)Fy;Lx\x,jt9UO#tKJ?@W$ Bw&qQt 8@rUZ- B}p Ỹ`ɲ"\ǚBN؝̽DPVwiC\̂)^M".}s7X(lc:7W-y`Da-kd khhyxR*ntg=玶*:}{jHQzۅC*KĢ!qS|c SۑEo<{9:.y3. {)*ls~z Ww-&kcEPe6]wmރG;*YÎF-\6mWVM a3aiBNGol0Vq|Vr|nR>4~cpu$ͨH(;>lZ䈘;U˙# Hٻw =/ |iG>9K!+~_ţHߚ ".BjӪL4DzNHxT" h'fش9[z "!q-7*&v58;SiX.36bK ߁?t->Pº0"HGD( t4>:ɟM?gO--8`' ͑C,罙TF6aĨ.F'ԩ x} կCHjzWqrK쩾setIWYm=D&i& 1s糑,dH/m 2݂Ctz͎܆7laʐd},?*FHVE׊wHmX[GfkOcc)`8:+* nZۤsy+3EJhl>Zcmq!gi%ADo=Q6 E2G"h(F9+ă_W2/U] m+, Ma9Mm#>UMWL䁍 7s@BʬC/^Yh l3-T1*_o0z遲/#tPQPηk+y3 -2&(ڬc{,l2G|ʼuUJ-/vj\lEʙau7H1bocZfJZ oJWۑER<WQfQ*RÚ@Cz3(b7iyX+cJ5_YK]e]'*"92 TlD',Ejh!r< 2lW#Ȗ]pK0":kC ٷWrXbU o#z\e12IBw@QI~dxآGiUIgTK_`:VezkJ`+Is3{c qdYJ#"5SO\ZЛ`tyý~vo;uw6cD)$bK P} R^/z"X+XSz-q":ֿN#$ !)ϛN*lKD-)CV\Czψޥ^w’{C8jЫ[~ 't҄Y: mkrNqV L90IycRCX0Pgo0 /.MtW`P\!J6Sq^u~1yFZ(+Jige5\H:5iO0ѢIӐgtz,5ϧޠ 8Ob\ ˪.OvJ86?-;2*Ԇ`BݸHʋ^6>頱rn<4߮TkMT8 ΓOB'VR;6O:Ӊѝ$T6!8 :[.U {YյtO K G; $:5@>phuX^ tf!!;KdL]C#/aBI+&F`k .yQ A[o 8FJ>Q3{foEcbm"<3|))-AQ~j~5 RE|#b")Y@u5,$^ogp)p_F9,.!j`v8ݕFu7flˡTAXLxDv']eRoUmON5ݜYAj6EmAJ$ )qS:_7R*gt{j??wkrr+gyl/i' H7jk*{~넮DACq[WU5 ~~w1QT)x9GsB{qR%KGcNU{|B*Nj ǷfkZJcs70K>uiLob؇8' ٿ2V<1SU8Yc]=gd aKVf4cr X;f.MG6x\k쭸'e ][o O(ܶn%PQ)`ݘ3wwyt{fk:~ B2Qc4ֱ|y7,B{l  B=u ǵ.C cߪb(}:6,aM`A$nrO5i1¬NgxΈM_E@hU9|!iӵzqc[>Ջ_/yc6vl 5Xe@F/N>1 MkGcX>:X%m *[A;m!y7,TX Qi@&5MU52L 9Q-QkoEGK$\Oz{έ  l9oAI[\#{I3ZQz^;gq_%$xskK\=x< vk7݀n0Ĵ55} "JP?cq=g}/ zrc!n~֙d?\~/W˸~TOKEn: bUžs"N@.b I"BM$)QhzdȨiH F3`9 =N(yD&3 2(:C)%^P`ە`34Gjlvd:pB>fAk}CLFrpw3=|Ķ>)weHLT :̑.}`A [<.#ZvyDSӨ.MuJ*!rz)\iZ_)4G&&a 2SqU @p]a[Q oG"B4^kUsY l)p+$ yHc$#S(Ш:mdr䝜?=QQ|(]$ QX3 .Jf5.mɻEѥha"dDO> <.$]DCK'dKdӗ>Ifp.Z{ށ[mN@QM.6[Wݘ-H1bg!(W9nϝuUh,Ew vIX/U0+,DGˌh|ǂn6щú$r[>]}Bg_gVsaq[Ŗmma+\fBdP̳n/;NyXb OZ?fV[FeBeȊ,`1zbM91pǮNjDZy<کe`9mKilץFW4 AvѨIuR+eSx[|p=LK@2"8f@^=ӏ e8|MFi|c1$eU= z #JE|yYlӪze ZYNx_fdi+J2ouotabB!x"`A6F;e *d:Pfgl*k«\ M74l %} n2f3:6ƌ[F~C\4[0@?@̬A]淨 D[QH) lWTT'6#@T>guBaX8nk,)=T\2o}n a0J(L: #=*Y*-߉U;4D#w0 EK^oq FkXξ%[k0u::E}H %-ۢQL,?B2rE:ǟosF ɣ.ԥZ@Ұl1k>&! Auqg3orPR"!Dv!HD ~ gևu ^r]:b{`qm7._+!tk-2HvHy6 3o[k'~J*,XYCA$,2%)e7CEif4[3<ʩhr`#~oA˧GHi~VyӦx] :O")b\,̛sc&B,-n:2F^ko^`*3{^ wYrH:;S6gfn߫xOaԸ18;')5k黾>0A 7r;@"C̴L92]D{4S@<2S&t$o$  Kyǣ=@wx_Ԗ~#ҫK9~pwțNE/-oZ2.U~$Mڹ rɔ6}py=ύBLK=`RFo"c0CU=*i kB{vJ%#^`\ ؗa[hd0"zss߸ ~/f/W ~"ɘ~Qo I9Av1WRݠ&`b5S+TG%Q(&mJɣ*vnGqw 7p+h+=2[7Q*Hvb mrw ~)"!9۞̟ptrKI4@슎X_p .sfЌ5aRYE!^8-ikԐ=Q(|g*u6 6V((ƢR}^?(&&  p[Th)߉9RL'<9NԼ9"lp7;çRN&̥7b&o;tۙjMS\%T5f+h 2> q>׋ܫ g5pH}eMRk?.H>1yq q=éY(w2ŀIs eRNïK[ 9Ն6W|;n_ :f_%U|{&΋+}|']qėBN[{5/kJ 6QZ 6jEŞ[q9ppk_xU%00Q"[PRѻ̏61EkKD;0=UWttªqqV۱1]Nmu$`U}"!@epe7 ٓbBhS[Ea7r\1 ~(%GvsJ5$zתG\-~Ek,ްyo/zVq8^37֯|-D~"O@BiKI)݃OΦ8^ye:ej^t].7>6 (K!D@axrlQc ikJQOESEo9ftb2THXReՉ4T }sT?=9-d+xtg7Ɋȇ#F4!BcKwUs`TyIj<*E0q'H|;?#UܴS -l^w骻u~ &~Jy&a3h1֝]GPX3|L:*Jukٰ9|7Do~?1tI7#PHD0\F@tIY@a׭K6 ʃKؓ-z VD1 ɒnG;봵<;Ó:x_so%kXk/69(2R)L.б.2 So޹YzBٍȷ5X?k3\6=Vo{lERe!#XpT[L3Z: q4!2qq4ʆt[&؄ /A1Ruy! Zh"g(x+bTf?-r1<Ҽ_ekѪR3M:knơ4Ҙ HT{ACnxxS,659A5 ͵S,uQszP6,A_3Ò% tD4761Z2&00H~2OS=ĒkBಶstD"7ߚCٟ}q0;Hwbɋ.~qt톞j\^T[A7U!dX*vpƧ7DVPc+A^OuCHk8L_r{v?9).kUwmos D4 '\4ԽߙKߥ@Mir:1$ȥ%I=(U|H$:$!C903aSp\'B *z0Ϭ}酓S$E΍T'm>W(bjzDȈtfkMVg5ԥqbΒ}tKt9?w$L$>@*Se؅G]jL DtT!jX{ei0"kűujF"f'ob&*aD, f.8>xs2WrJpp/ۜVg)W'"Dyj5-?DkRLOau~=2h篡IJ !0Wcy.\B~ ev"" 3OBSG'T}tWꇌu W=I^M熒v=g^XOЮBx^=׀dJ\/{~gnMSşP>W]^_pv:%@lzQm =+!Q2l?tϏ[*>-b}L B{MS_j`[t5tF֧ $@)m;\îRoV暅M).WӅ=ՁfKS;qF <'YbVNWJ#a Hˉ`xv$dN1>`f(+8򤁎=`w€[_f17GGF!.bڽx&V# \ؤgDj` J4{g\:',o;iM=9XV˓(f#_EZ@Qroj5 ;ot^sVAY|LГ<4/=$ UŦXu(:H/cAkpO8U^?~D#]qwDaxܖ=Sahg1U_U4i^EtݢqG5}i8V ?T{Tkmiol҈36Dn: Ll$s n2s 3U][f7}b-"3UPOJhP˙*H*+;u! IsuoYv@&lG1!NyZ'"۟#vEs["G{CvKH\=\dVODxXs;Jk-cAMḳsU 0d%Oѥ7K'bMB'~tG[KD^ /\?YDIJվΙs^eJ0+S`VY@\@yԟ ,M<#WHuF$N:pמOBË}ۅ&' 3s|pO )E_#] a-Mo4p) <COigyC׼5 !DNlp݊Rq[}ݔ[sW#@)t}V<>+՟mmb#D⾠tͱ0&8dдa#Jt@ƒ[.-O񅿘6LIX~*~X_ 'A42#Lg>@L) Q?t} b3NfQ^L5D+{Oa T̓^Yf(m eLd+ugkv?n)(`yPF4 Q{palvCZuq~c"~XʰXFFf:[~ ځνѨĭZ[3@QEh4ݽ}{ g{RN\GlMuU ])isbVKBa>)>n Dq/zڛR_Z@3 #8,ѯQ),l4͝.A4%l3TX돾Vbrvm|ڎn| pԘ>u"o+2, spX/_W?L'5K ["OiRP >$ret`'uЃ~BDw9( lƨCCu)Kqifa()3FqAPn8r8Y9A@KPUQ񅢇waxb,onvKl BUoi ikB a#𛧧q`>%pԼ%M~vpS=lZ $Pžk}A5 =t]8/uiIC14 +/N'CkEЭuhQWNch>G.7:SC_,Ν?LiS~9 ڤį\Z"Rs;P$52gRk~mj;_Y|OD"t g MLgcY>a>Um B 7Vk8m9]"}<4U%Isy`IR.UxKU||?ʌ7i * 5rkE[țCt ?4;`[YCBO^ѯ_r5!}8!qC4bLK_/;-lBwYYz1 E^%m8SbqW`c3Og}(O)UQ2#zV{)(XZ-CXUIaKONMgZh'6䷁ڽkEE)'"nVpN$ n<Mzz90ޏ/^jť-p%SνIaAL_o''a'Q{R^ډ԰aؙ9عL:cRfc޾?)s-NYZxsC Ъaљ9~I]W<6Q1P,8@Q%QâmY-/[D6g.ٽS 4`=>H_yV}Hl$^}\jU#>R#.3Ab}Nw&)űm+E~U$k v3,oY*`QE.#m#t/߀VvsM^Y8su݄eai4jK*h)T)B\o{#A4F߿ˑJDNԶ>MQ5!VSnp#ҸG;>peݵ}?AѝS5w6 :+r5+"{'o t { 4cťo{Ҟ *ԑb,=Z+'0هkB>oț4`8M&ݎ)VѷM?SIfaήҥ&JtĿmtưk{ Aѣ|rr|M4%JPu@5}Vl4#9A9P 2Fb(2&ya=.9QӊPg-c(p]ջ Dz3xU#p[M<>Ú.kFBRQ&bIl`#+4-9fML̅u6ӊF|`#T|q@ҩ XLI]NjDQOp`giKD_stJ8BUG̋=1gO@&c{OT`~u(X[\k.d/; |e=PXM{aQ @Hlo6џ^Tt{r/j,BSolq*) aWC+Dx(\5 ܖPpxվ%tk2pJ#l#T1R\'nʌ"^K 6h_sd"*ΏeX O ܘ'qQn 6vC +"uI~UIf.DZAy㴢u27SKѡ\oyqa~ʳ*)5*}ZQuЖ\\;c1Wg AWĊa+|܊Nj ?ӂvl=V ё3,\tjitw|-u˩FW}3'YVdG :&]zY-P Մ96qmx],B㹾O^rѰou6,1kƹajmP^==L0Y6|^&G}S/DZ O 9Q8k<Ɣ-)S,t٠9i xAOvkjikPFH.M5S.{4{4i),Ck@}@Y~CHz68VMJ綥P>)9+2ӝR{=y4fׄ_'e5c N3>d$05 _^F|y\橛[-霛oxi(bGNU~Ts p P+# mVRB6')c%ab)6ßrw gwY`;k#ߤk>"lVJNEEt I-QH~ d}Z *4dL: 3=9 ?Ej)=x QJaР9G",TW)7%S3ճ54pԣB.ӏM71fzZ=Fz#}*WO)  6 Zz*%Qݎ lx;FK|UWҽ~^t8r"IsԔ:ed u9 P̑j]̙zhU8ٶQ0.7w7NggYFA4Nu-Ҁ2 AdBp_2pd!|6ERާ2& lS1~erZ[`Ml]@[3q&n2(:֦¸NM?^ŋohLI}DD1Қ+eFq|,n?ݡŭ۴%X-wp|T E`zZ 3} Ӯ}`Z-gCyf o)C:]NaJ7Nl lEO=ʴrZ%+$P<BIsܓ1 mGu$GIfRųmXUKL[YuS̆dž^&[Y/]͛3X :E."(wbV92Cf3Q櫀ujb(B3eTWU w Ħ\;Q6LX"Yûdڴln/))GEa믞5w rtzaW--WRs]q󛋸ߧ9X#*z!pw{C0& ;~ꇡ008~_k pTEqjhq6&=x{s%~|+9O O[&D"κDjPmnNMa<3qM& u޷ ^ Tu8JK( "S|u$,TǸ%m~p1EAI>$Q& =K<~h2*e#x.5<ws<` H *lCwpV82Q:-iT7;B;\^JpAY'=]'Tgܩ-7l& 9 allU2uڱ5d)TAXXAe:HZYt⋨"6`Ѡ`0T7Vz bece'^UpfMGi˅4%eBoê-uZvuP`:b_zcjDAƎW2QHP/zBua鮌zEȬs=-F8=.e$>n#KF_42  Iil!W')E#aNJt+MƎdxh<T"a)N9&}/.d<NfFL싣ԷkuH^ÓM<Vw J،Ʋ_U_@X7:F d|a층[%o"A]LWE~? 6դMrR!/S~ά-'D>[chD9 0t23@N#Ɔlem8okS`s7D7XS(g9 O]I]A{Ҳ{ 4PyOM -s,s!̛m,`v%̲ 9{fv^ET-!V zufr/ɥ 3"%)+d"đcW"~tH4GyR@E5B6I@) Ϲ!#i"y˘{/QF*.@=aDHIܚdџ6;Oצw6Aweeq\1IѻF #^mچioL15T6EMk 7hSZ&XD˾ID Ҁ-?NBo *OW[&{'z"4JJ)hM]Լ%ݕ}/hgpї ~mP"<+5oKڋ)⪜u$h*5su'8P&kq}jw8 )׳U/\HpTJUEX{oB"`OuȪnʉ>awm^%3%MY~YdNjM.IcwmSn<Ӯ/ȜZ`# qĢGɁ{r{4lWeN}r7.`%eFs.cY (N\DŹ/t {]t9kOF)DG 6[3 \ޑG]pVs3Bc|81!; [O@aإWte BF?O0HGqLsq--.,^8d"juK]~Ie o#ydY}zķ3Q8c@1MD 'wdo_S$fr3K4ys#E#T]K0{Z .k ߕ05{?=A CGEڝ鯙 rⰭjy 0Br n9ukt93 S,4'HQnWEj|Ӷ HЍ!VBXN=훲#"ET|_]PznL$!m߯Vy1}o&:DP~#E˵|CeN8Em{&l7^vHsEOg{4=[,d ^FvԘ̰X='VP:''@>WuV$hhz^ǯPy`ejNJ^3!b$W<B: .eV'd'2]#0qu;itRpQoJ^@da,0xqx$yYg[p:6LOKJDG-xS`{V⯥Ny-*9r ,"f [ai$Z`lW@ $; BֲQd#U&{{~sjU9oLj8e3-Y{^fI%kCc}d#"-$ uZbţQqrb/$3:S1*XU}N =Wrg6d{g;ǑMҠ܍I\EzVByҔ(n3SeְW3zBvB.م.x5%>Gk_;n#$־%B?x/\6*n1 ֡͗+y^n:ߞ_aٚ[ q̴݀!6ꐲUU9_+C؊r8o[9-/ǖ׺%M :QwT9~Jw3ʮ-w_ Va碂f$v(rmzЊ9K)/2/wP+QMp'ۏA8B*ha7a *tW ѶtzE0¸SS+Ý]w0n2!"H%aF@Up .vYd/앎15䯭IWoVvّ<#O8 9eT6PLTdqh@yT74qе/"uM+^B%)đbl#$'>8K'hw7Mϝ$˄9MׇIo k QFso9R{ 8ơӋ@3`Y*ݪʦ?4AÐsDut\\t}t1vٖS+τ7 4wkѡv1{v2/˱ 6}I󬜄/sp6%y7ێ}z7Ff!$go9Ã4RyT3o%YpZDc /n,jgC:O0ϥUBl{r==a90^$HJMdOCӡC"oE.6^啳 }!&Z[Y]Gdbr.tDX$`u"]}hp\&IJMk(_0NX~E-EQ[:La{±.~q,S lep!N؃ ^L+r2،M[7obtp]IƆ"%ʚj=H|tk:(>$Ӓ>VgPEt,R+46ƘiCyI$噃@{o9U-B'>Y_(*6zxbe AݟEnRg}E ؄KKsY>[6N"jnMF$?̯Vfj.Ό02#)p ķx5#}B]SL&,8iVY RVdoĘ7ȴBc @dNSF?Mf x3jE)2Wi'zq^EE~vu$@UrS<Њ,I>q%n0u5sL^ aE37X89h.DWp̯h\t]㇪dF SRZ=-4>AL0F^3{ lPzq\T [uޑ$LPUJf$gUuNуhpBKb afAmF2eY/&YWкȷ^ 8HۜLkiEOSB"_iy CkK5HQ?E3'W߹ұ~rL@m;N9J"SLuM P؄.w_k0dېAb$ Um2N( j6q)؆v8:OjWg*qʜVn:|pWʲIZ ~`L"24cZ&[O=Xdhkװ$K{6[dM:B /8+!fd(cqdƶ3ETrsK*˿["N͂kJc_U8gTlzm0pIG^AinTXꡱϾI2A p}Y<fg$ 㝍ΝC |Q^y$T &VwI6I1&"r*,?帱m5KL1HD 1s1kI(NojؗaN{Iۻ--Mv[FЊ}sr6۲EY^I~`%t:|U5u; [Y;21a{xU{}YC_opo#!VY.fv>5M3R+Ih-{h^_ F2i7~;7h{}T1pRA^syE) M)G +MΒ4ĭzdK?=)>7ߢ '^kM+fU?e̽Ehݳn?[{kN`Y[tV_KߩQQO)/xQWO2<%Zy&3\%2\djf5p +6.=JMLp}-xtd d+;͓;eK˧9ҕ,GB`4)9uw^ٓM*% Z~2;IpP_%SR^w?rW}CIC/=E2 |:~@Fw vTgQco(k?QuGE\'29<0Ez[Sb,kzR3=jǚ5^ 9s@f=?ut(^EgYh&Tq 2OhP 7 % XC]Z"ϩZ \5zř9 B8S?k+$MjisVk\$A,YP6!"lT~Ij)&vQډVDRlu]bB61+ \.X4`@Fwf.v)E"ӞVlm ^dB qcp YF[[R/eD|H~les M}ƙB`NvGK6O7X}ӊ鋥уl.L KuƽFwEd~ RgYaj/_A`!>L6\^*>0T$xJF#N;7&Ȭp,QUFW~Fv`I3tK']4KG}q ˮpNuS!3rDKZj L=VpT>^[;;Qhr7!o?̹еdeṟi">b'Ze7\ FLׯ末'۞&Yd;5FuЦO>5 'wCacOUdu nBaGMiBMm^Dnm>dgihd:*+-u&!^GTZdj5ӎ+$4tE9_&h$ eÿޖeIq|n9uz_b^S7Cuv̋gRȿ AIň/$gu3Tτ &a ǂT` ߅2pu R9'5R4٦@%S>{ D:%94L" рKqjgk { ‹`gyZo1.a( ihO֮翽K;َg$CLtii&zZ1ԗASz7WP̿!%["jL ntmv$3mdg'ktIܾRRI ]z$gR*YfXXNZ%DK{l%ۢKEAD-1{? WEdE0:הiÞ ii|.N\2K)~&ۇ4eȒJk~;i?9uKuCPWR 39iz\#DH..T]Tz\%B 0(à%t*9j^Tu^_ X[>3pdʃB`8&&4ֵU HvcIbzwV:("^ SbW^%c~&6UdWq"x3h 8;@v8k+j>°'M rt`v, dQ}bi?&]I2Z^ņ(Ϧj<:pu )KU(F54 αޓەF$b..6wn!W]ܹթP s5~ e/ Qê*d3]̷<׃](dGg@h6قTC(!wE~zA- 4*1-u:}O{ʷ/P53<˨P `9ע BoUئ&C4:nYO$$A:;QMZegser;cǟ6Z2bZ0b%Q3Ba|Ӓ<>I|ʒL wh$}7%x#)}a3HQ|[ !c)pB1(Dѩsq.dy,.+zMP Ҁ(Q!mhh1>@Iwڃ퉰u4joR/„Ђ~ ZG% R_kpG'OZc WIOKھY7윖 =ETtqRި,>gJ[5J ܨ]|⚫@K׵|/V1| N0ގ$h$y7.³  n[C\Fko۲QR07 ĴS|ã[,Yˬx3K3XAF.F`HVn dٕ;:\,8_x@l3]0)ixZv S{k\Y ~nl|9bb$=q cܓ0,j$3s/;tf2 V*:*Xjgպȏ #%cJQ$MޡA jfO`r BX# ;ܶTJwU-߰([h>O?(:$FI) -p8-39߄(uHs y0^'kg.EE% ,!%:ܬ5_R/ txa?$dBqFX4+t^9*Z V_H*j D%)r_taԕ$P'Xaզi:ﲍ ^4,lo?6eBae: Q]~UTOC%/?fXHMEbw|pLג 1ګ(O03뗀hN>BS2ºFu4 4Qğ`rǀm2JLuxnh`\j9Tb 6AaVMh"/{w-d$]PS=s.έ䕭ϮVHM!]eb~'M`,j8m&00[9^iۇuw|-0U$R?`LW}cwT#XB==h/M뵥VY ͫKÄ-0񵊆QUNA'N80;{w+"0,nbT0|psJ@̯_ .Ķ6qO3K3.\ө̩)8X-ya|jkP)$O]vS^aMuLj,?LS{xyK((߰\;W9#tb,XsB] A H9^h:6NP7zGgS%/(g'79ANĞt)UC[Vց?>N?շMxjk5 cÎM$cvLfvkʛ(la+z;v|WuGúuKD&4(FmJ4f djV.!zQ{D#o\yq-7&J-:zd9}Z1 t!/_: ~ޱs їKwGC{WܓSmgM нy 6\:~/җ-*UJFV=kyz(e3 Ԭݳ!?qY:ьڗň*PT5aN EH&d -I%Mj.X@R^^00nfh$\O.&]Mx @.Ϡ@œIbzZ re{@ mx p HVpS}^ԾqĘzf;ۧ^yLa|AЖ}gkZX4Y@OH' C?æAFь,F X9]_v]9=BZ\Pb ȅcg-¾רL3pHm_ zfhhΉKر9ꬪǦ@/ە3B-56l8#WK)l6+>@U&8eCM;)Μbۓ«E-/YW `۳ai>|]ba/n:8Odž'I"n :}|zĘ0U\eWį Y΀y!eg>njF:` 4^BХ 82N|o~%t'3E_uz c1(~xOCe}퀠Hyѓ05*i6xV`j%ck7٬mB9MHMi= TRpwƳ.dzs2c«M1zCma!olk,x}E<_WoʃR+rM\魃Lbc݊š7"eFSr038B֩ې;i&ہ3^G3TpAͿw!9t>/ԭa; 1)I44nNBOxJ5شN,' 5";+9]ˬTBm1[g| 2bN4eT%$m?W]C4MX{o"ru/sMAJU) ?dSfTT=,N9.T} (F Jc&)-&q3$U|UCC $<3 Zr44JPoW~=?1M; ZyiE1lg q1%yfcGiMM\wqfY/F |D hڎ*R &G-6XMQȎ"qṕa*^:(Hr@ӵ*:p{th%$|ʙGGu;(Ъ ZL#AZcdpk¯,_bZ 99κ~uOF^ k0z-e7ܼxJG=)~xSN M{VP:T67iku$%,6;’z񶠅ͼQyV"6`V6 <>ERv_^K@;6j|,5.NdlM_bo󲀊__-ìЕ̬4 PHjzMߘxEnBgXޔ[wzw  ,|"s7?6]T|y×V(ps'wx*0?.1d=aP=ƪr _(oɣU+^i,H1Z]1L'i8ׄkq<0阯+|G> zEڵMD, aOOO.&ծyF ɁXC>ف/duzw̮_R{ɀ,Ptb+F2&@=F#=~ $W~hb̩0E˃;3|ʝgWߋcP59XPA»]R⥛K" \ u*qİ v}qhBq/R'b LPZI3hnryUUB'y xHb42#Z ֓{&bKE-8Г\b!D"%a)mqӝp3|QGdZGN}ָǕ w |}Y:#Kik@T7ճImlp೨8}cZdєX5q$$\Thny@jA\Qoh`O&țb6jSND[xo1 UCm]l@.6 V*YF$njL:|V?+4dLG򇿄IA 6J^.5!Qgu)KCf5aU@&Ez{.:۳&y7lw\FWA\A}{K.bܱU UeaD+ɤ7*d[dF.3&4 Yo.; ao*U5^IZhҥ\-d^{UJOj&R&!VƂ7 ǮWO6k?Pp6}SoZ8b,vˍ[L"_g|'[* ݝ54|,?}fӬʦwTL1DkJA|Ӕwa,nry۝y5R%o Yi`Gt"imeKq^p@>pb&A3elN:8.s(q[kIY ]-q+tj"foo([[n.)$kt%WOE!4k 5xa q9F|Yѻ 8:LJ0"WfYA#yd7[C\ }Kr?"MsZڶe6 ?"Uэ,\8:Qr .p/ZDjEިi]w_w AO i%ܐq3tӔTd3Д1eBijw/վIJ_ V>ZМ+_vd/ze]REB=kNx͝I옷V"ex2}S)'%`ޣa]TшKL</u >GvE"gŮ^d . NwCY8ەăj9I!(Wg3_WF\84n\˳T03N]vd;hB YY&L.@kK" K)R_5DƓ:^_~yۂI˔C\WNe9$KiR-DDAʪg  7$0XG!]'򈻉RƵQs&ids5VH哼i gإX=DMXPɣ˙/z:7Kѵd.קy?hw6cu)LsO_& GPӧ_A_qrRћUzw2d4^swFHZ#aŭ^w$;\8"-ּ 5H']:ιcDn!>*E}I kcm̒7[֦5.c%ܙфoI%BE{m UF@p'^㶚qt\3zYiP E Jry-mϢPuK8xo,RY rSk-_w7C.MO? ϕx>awPP?ưO*|Q{wX(WFCL&xpÈ.4q!ڕE(~'pb5yjdۇ(4v&NrtuWmcMbD)L VQg?\mtKxV "oP4z%mhϭ{IcPxx*&")sU,wYU#\*IwebQ l\ jN?:+;J˨,;@T_vw/m  SG3n7Gd(CFć dU.LUˏ> "@JRPD j|wtF=~jmS9/Ia~9Դ]r!i;咖iW"~9Rx Xi\r^sO ,Q#9.N#"·ɧsvu)QAQLDču^S&AƬY(QG {5υ'u*R6{֫XFƎfwb;oE;K jUO W}E ?&U T{!r%9pן>*YwAR7憩2|DŽ눦%o.VUgYΉ YOBr(q6z=;FclI@N :l;3H+](7NTiƏӍgqT:x6me3ƖIK U̸+qo#A o4ppحS\AxFogKd\džX%xm8nʞ*]N8ԁ 8 O;KǺ}*iZ+8ys~hAbCQJ(~('pq^omkWs,h-YF'j62iBe)SDA_osx|Yi0IHTL MZ[fϺs2Y~/A&D 7dTV 0[N\n=堎|Pp&( "4۩Y(i[k)Ƅ&j<.oƂp3r1xn<73 wJoC."*5Nnj;m N91>@6/hOH9/- j4Q~Bdn,Szc!`<[HRw]VYal`JOC5\@6fxDbu3I@3<~~.ND9MiÙ艼IPV&),=8U_Ž|\`mAimS(ߍHYzhIsgx 0 cшbr7ܛNM\C;oOS5 _?0#[#RH+CT=p`N-:amqkmz>VR|M?[=Wk>[̀3#$pFnlmdMJhb_7W_=\[ {ا~ dp1u2MK iLDΨTX+GRi=j/[3eDjFSr#'OїOp3Y脏 ]e08'\6M ,F BBժ/$?~'' T3_Y.`. V`DxfZFY@)+_kn82n/eܜGbهz N)D1򪵦BN~m*l/[J S;tWE4N +nҎ  vseA:OTRsu2BX?>I:dw[5@E~k7xd3&\h$th|`OߕjoE<, NX1S{&1,1hf`OZԆ7G~bteZ#J ŌX2m1^DJ0C ~D[V\+<.oKLkBg)05iN`#pnvwDZ>EK,V9kXҸ87r,_cFgy=ĞGW*ޟexƞ;:짆9rziCr C,;z L0*Kuoj~kJ\&?hBu6íltHт)AMx5g"y G._ 7!_dl$d>h)C4>e@!%REJI!b/g %9@823tĕ@x< |IDm0fF^,1+^ !'I"k0$A%m)ca%sƁhvAxܾ=dtΑ2 Gع OX^ uCJ2ेWy&{M4Sbld^׾+.I,A G ȉLM:yf'w 〓_Ξ{'xnpg!Mۉڵ(] Z)>QwUL=v u#ZQ.Ȏg۳-C5 ;}SJ-aj*Q*Uil%Ųyq֤P)qI݅Q+bD  @&bSh?ZʺbH,~΢ KsgOlH5iwbq `O#4o0[h0ED6Yb†E߫pKx-qA8"*$(\g:1!Q!nCsr^w0ot=dêX2wK;:g{P{I]? &NB?jq6JX@hr]e iv" &2|I:)#JE\L;;De~*U\&F(2^pTS!cw5#rK]HCo#bMr*&'5k:CgPW) 6FZSKR.-k;K~8o}_*P$2$t}œ}N{@ Ɣ7:{vAiQ4vQNs,?Id2`z);Px!T̴LQmnfn#f<hmD:M%b /=5m}5 5Yz=;迺{IWw's‹%s,QVsYPc)8R&k>L?Q <i9Ver| 'Vn$H\=Iz;{`Jd5[ʳ&x6?*P?t"X<CT᪰f\4]U%N_ߛ\:bL &ת t,) H`K2YPVT?6F1]NBGb 85F&Sop?GcCⵐ0h4K9><-0s7l9qٕ. ~{++ujF$АB{&kUU'YrH7#"O}zj0BE@Cf`eua`އQʤ "%jֱMWgf u4z轐`UXJ f0Sb2yFi'5b47 '-Ҙa# (|Ze\ٯ`} ՈU 0!N7?'w-Wv!X#v.hŨS-dW&4@m7jss##&@%+mL[ԃA? m#y׈A}L/%o?'7v*=FvHnH[^˼?9Wfj84<)W#whɚ`΢~:#j6/n(u7}eƪ[MYE?'ΙCF!HW<1ss`֓Pj=qrD.O1)Օak>tGlv0g'H#hB6%C=$FqZ:kÂ/֟O=0h:BH){B~L; h M6>ڻ+U#PX\0H^8WdcZm}2XG%$m*pLGX-s+F 4`__V8t i] B0O(#"kc` =mg6qWyR$sߘi1IIʬ?M~"'r߫VC`|u5[T'?t=#@ҕ8 d}4hߛj|y.0;QJ$qKTJ|K|)7@o>.^__=_}۾45z#% ?nU$jsI:p :ʉG(AB BO6 f<{aCj/lsN1` $LT7]ԤH-jà <"Yi?X֛-+DQ$~T@lv䆓5)jpZtJivÒܷ!H،3:Xȉsv3^FK븄~ZnC b(< 4d>;$㭎I J%v_5xf% q#`+L5ǍլЮXİ|4*'W^ ;dHJr04X})G׉*0Oa\gpSǑ?M;m#w:dv@<^)D,4<%  ߌW,;ez?Vd$1 i me qr[r!Vs]GCoeq|S7O6Ѯй9h+K c餛@'ck_LLhbҵ(Bޘ0=)x].M=-55SWgjd(֌EUmΜl8mA/:oF IiX%k8zAF˄{b[n$ȶŸK头%*#mW';J8"&@HQf5~T+Y(ͅAC.> U;u=Is/ ={yVUn)QFⳫULHŽ0`|^Qk ,F1>2ûs.ۋjan_7e SxngϤgXx3Jc5((s2xKT#[ƞ) WJ%G]lNoxC^8wgyelSԆN\ɤDtr昙-ד|GaL"Kw KUW wBmD18!Z}C/m&J*g)1a'B, :@ A6ڰͳ?)ܓZsL+ mvOR,.tӌ7\k 1 #g:Fj |Ί m1Ĩ\,NW'Ѝ+X^69s 0̡ j:6kX}N#(= 𪸌`;4WZTft<Ӂ8<2%QczseAtW͸nj$R~D-@%``="!&bx2A!V)A&_<~:Z,Yj7$[z)̒G1Lݣ ~e^7z{AW1z8 JȲ~R!>4b8/pݘ-ac$KU`{_XP)?:W,)!{i TJeMzڥtg[+&^+C8R{};!9L3ZmvUɧ/O"8]&غ*.໣k!Pt)U􁂶 i>hr@JXˋכhk" _Y(j~_vbl#"{m-(#Kf@H;%S2F돟t *wx_VΔk ULV09#w[a~?^ so$_KpVMr୰;b"<Hjop68<E|93-&uW޿^HAX,#jvTWr2`rOg?KjS㥭X=P9?'to'K">U)6#pb4ѕ=Q 9HW}[qov~_uNXq،UzNlU#Sӵ7GL:*0@Vp .%ķ>P=Μ6_d+\21]_ ͲNOXWA "(hh\7\@5BTa3feh<݆.9,zV?Ji腓vnmGE>riO*FYmB,%Ѕ~}Tf14*v=s$-8#0`6Yc\I9/H+(e#E W ӛw&3+;@R)#'~NhzST[3Lk$aYۑxO(}/58Q&q~m6F [dZNWŒe_0eRwHOC?$).T" |%l W|_b]GRNyC~sutƅW8! o )YHÆ?).B{C^|W>ˌrbfG <{{Iğ9wsR[7mqN0 oD>?2 WL+RX*&T/_@ܭJPo\vM#c>"VRb=޳7фq!&?%NVZYBi^KA7c&ΙLoeoA h3~|5 㣟UTWRыYe#EɎU<e{h)P7i 2*5@’u g/ߜ,"իz_ڈU}5U)hM"`;wQமX2MwJ$΢?md$v!ag#)1yDƞHt/fjd޶ON\3w865c+R6Bʐ!ZZX :ߤ9J FVw8Eӊss] eSCgU$̌S>)K$pc$ESxTJٱ\E >+\:Mr`tZj>" 'a&ט xZZ1hВ&."y)Y1x֪qL,llb|TlPս I_~!iex~Pap/i)~wG/#tmn]Ak@ {Tk+0óΰvjVBZMz, 1Q΁jBZ8_ X0"eu )P$hs#$k=k.e5pFƹ78IN>Xd-t_Ljs'Ƹ $O߷(41'HMZ@ؘZ֯r@('4AQ% li]b/Ǭ`2u-ّ۾ у?#51wogt5?dBI ĹV`f3٬YSސ BXkx+jG=^?-!P+h9'NnkhnNL&]쾝I (uH}=!VW}pvm4ω^L{G͇'0鱦EyUko4 NFl f_ t, 02n>vy5Grw!\`g̼QυR&|71"juڨʎgS,=!"c\kP U CdJL5xᏏLA dMB!1C,ь "6lWW۫h\LRUPd/3N̲e!LbҊSrJө[nUHYrraoP1rS_RܮԦ tT½b=k(kսZ' W!*^ T!v`m  ׼X8r5A&,(&z')/"Ҝ†~LusVavM!JP4wf|h6cq>ӧy+iy vfl#YL\f\EQý9hފZbt/+7FYA9iw܂Z{"}]c{e:EZ& a ګ~ #v>pN%Hnt'K [0}Utjf_C;t[|^N"eMu iXU05ف\˄F< dA$ocM*L l\vGb.;]D\$[U4e3k0hE9J?>GXD7dOwznE~< $1x#Du J%o3'b'c_-Y;)aBP®?ay;ph :%{ږ7K+mwvl"rj^V n;I[)z'yҹyxs^&:o ؕrbt I_̓eN`~stQ%~Dz#v3'C p=AJ!Y6uISҰyYS GI*81C@WD1Dg TkN<g"\Lk_$#-Hv{qs@՝ k] br([З4h=#7s e&n&c{\ P]U?0HmIG&~Vŷz}[?[csEHWgdԷ6@>q%-Αm\po`f4eRG%~M~f A ]+sz%8@Bikֵ=cRT۵+4ysi㝹=p=39ȀT 9A6>ʪ;q + oohG;#.4mxu"f!~z>3\^Rxp1|D`?iPk9wY~͟5]s7!X=8Qx@J5YB=ki+H8Ydl3̻GS axI/UkqV1y'0su$T"_@ū?zF\'UVTs]Wb \ AeK9prɞ_'ut|.ch)ʉ<9Sِwm7g@/MͰQϔ7%d#ҭWQ%jM+sF8c+d~R[5*.x} aRKYxef?:;A> J.[JiBpdr]^gO95nlT=!nġn"w8ja/*Y_b=LCu;]&qVLM"wb: tH ܎ -LߤU,ўQda*0h-9p7-zCΗ|dE2-zeO((L-\UWJSu S@0,Y`Noy H;oy8DQ0lC>U3?wHf^fa^ ^Fp͋Ort¦qx{!t3īZ{Q_D4m(0;ܥgI'̀/(@ִ'fî \ c21D dbƶА 2iTRI??oe& $C-17枆4s`CN0\b(CȢ;‰Dsӄ5 ơ?{@:̢c "ZucWphJ]~ҝpX\_+ e+9~;Xjt;eݱlrW<1qHTQ&'4;Y3a>vj|.9Xq(=OSPKT݆n ZڶMw31={"O[\VX3E`=tؠ5lAV/W<=ƞ ֠VKhIcCpn0WA  Uy>u1nBrmrm/YTCJ6LRdWm jg9y hlZT< 4UʅWYЅf"-,a9|bQÙF^,bn(iQ!0Pߋ3Y7vg\|`{p SP&-T+gTyh}tkCpz=i&^bJOddy 1ߵ:דWdtUjuҶ&wHEn4l.Ѩnk-c|dRW)Sx=y[5-@[fn;O!|N@[Pi''}VP$ 2Ge^CO);T6;KyƺBLTe"i0*/vbm:<2Ph̠0;G]4{шlu"2|t>XG4J0 -z71PrT;n8bN6L:4 H7R6]_ mqP?bېܙ?@,?=9zG}Cyg|鰭ߑf$f>,GrO έ2bj`۸j!FO "iTxv,*wBc1~1}'Е}%#C$PkE>$䚓3g0L\[gQ 7poKOLF=rGli4I&DufءJyP\o4ռt>W|6SXAIװhLRۨKli2iƕSC徉X-L@-I]%8[xvuJ6LJWZp**Fzˌn^j ,w2퀦B/Y謫} ciÕ"ɦ37(UzR'`4՛hU:)ű)@:cz9T6:L!O%05HC QLPFqԥ jUlIRj02K^^9nL,zrde/ID%7#7urۇ$ *ɹI< j';7ZU8S.~ xn]Va\J :鮌(v3F{$2O[PK"Q0.i^ gG+/r.M( (S]c.R 6XU׌ڱ͠jV졽Bֱxĵ eW2^Q޿L2z!US?A (}MÃr{4&eJퟫKk 4dt /e*l}(LG{ R#ҲNЖ//75ѝY%VUf@ƷlxuPpoq'Xa؅TF!wCd*26atOR}}Y"(K;?rn,J2đ%ĭJOUZ?Rjȕ1盧DZZ+2rDU^rag̟>`mfV8@X_ٔ +d`335qkh ):͘e׬`\9J\qE/Y lĩ%[Iɂ& dGHW0K)jyO+}&5>ϢD<?#q'Vc 1e$qz k"r? >Ȓ.´cXɃ9]کӗXe?~SšQ5T͠Y෻xY2h|KU-gP|YzE;z9=Z,f4OI+qG#=5_Rrcn,\Uo[pT`2P$|;0e{b/եU.4OZwm+pZ~/[Pfw22<iR4e_$v؝.@ļstq\'3]VTJ/'ťkPXUd)fUkJ*}x,Fزbn+lQ*ƸiXJO^ϪDS8=V|~|qb;*y" /[HKTAڅRM("N -~24E\BoKW..\1r XLI頣HQwЕnjUE[ۯXv|H^PFu]>P^R8]O6 ^϶z7c=Fjοuy ;6 H3}HA&[]|D!9*ZF{Ώ ;$޹Yy&rv)s}BOv>U)Vul{bFr QYC͝ 0Z>ly |1R.`"l5ǝAk}?D7_̑IMzSm ݾy >фgt.v:BH ѭ?Q;hTBA!YƤ{aPZ<2bf|Tr'ӧ+A'8!;#=3Nއn 0 v3hs')0Э'j{8=^\~ޯ膾{*i377mnD.m`=U*8wm A@%?ʡtgc]豲"k{t~gMicm:8&bR3eA6X16#OO+`pJf*̈́c&):@i_IuFT9W?V0%,?nx"#Zëg?8nu7MVʕH%(Xo |[D Z=8If*JbĹYybAU6+jעEgS`茒^S/|s;)%[FmkS$8i)%N氌ڡ\D^>U/"z9R C3QL{,IUz925-* \gxXzi=ԁt" *|,IVnjjҩ"{ rs0t R J0)ǩq1t82]UԞ1>;Tr1I16\c]] `y^18jvƹ eJSZ3I?`8sl^B<5[IBaFPv߷ϴE$ʀOҽr->B9.JߪڋDUNv0eA}굚+*bX痚B\u$A?M{Mץ[yɓ(;'|I_b2+;Rgb$CX8CwվR549+\n_._b ïS}T[!dfyg>EOقb}ȱXq  UIHs'9X@ x/zmVAd{6%([eO+۷egMTlLd.RȿK:l."ƼN7 D,LC8|g~_פU w},p]ts(3-Xj_z/U!i7qdpx_"H ]۝W~toTy-&W*l{;ڨ- Vȿa4^~OwHcv+ H?C"b:>gbEr^'~ "I< B!b|:m:q(NCx%7(b$9\|+F/< 郜˄)+2TmL)yV/u]r>iZq{{-I8$:+xj I-<yIwx՘&~A 9Ù#<ׅ/=Pc4C8!֛>7iPά5-/`+Bm?O [B4zD%?ˎwhb9:CQ-m?Q,_on}^XoKDM\wpM쏔 >Es#+`,t=wMI8tYn#l@d9 ^Gtf/ x; ## Oչ3ʯ9fdjkf]$ ۵ Ĺ3Yg\)1puXi.&1-JBz4dssm %Ob&Ƿ4L1O\0bLqwNVd8TM֘X)@DCb4kv{bT@dr`d?6̞]}ƀGrC4}Zᚚ݌ޖK [lPt]H%n$e{c.({ z5[WɏކO;2s_Jb3v єS^B/.4??63)ҞkiA^Kc2R){d_DK4/;oFOYWvN!I^NT_*_%c~# Y(A\CLri6kr_ֱ?&?eHp1*a{J?-?$6lwΗDzbu80*lSnνS)S7~=KA` 1lQ>cιq!m( s)4I[-Dž/W,pU&Vc$\Rs{Ju۠&l Bs^I*ǔbF#^~Ysq(ѵ߹=Pw#L*9kW@#kN/zM+uϤS yBcB7ҐxM#J֊ՐX:ICw2ާ5w.A;;? -LcA`n3nj35Q+C퇉 U aEyEb-c4`VΨc:vB/S||v ]@Oa/s4Hq,"XYN4UN$nT-oPj26VZj[S.՟w J;?RVaݯ^]#oI,_H=o*'hlh! b{ UHąf1W n}gg#z $`0gN_^01ܫ'kR]B5XYڧDTyC`Ce3T/QShZe =%؅DT'{DYC) ֆL س#[>Nw yBZ~=GFz}{#FZ%lP-;f"sl͙eZrX.{G^3/1|FNag6v4iůlp촟r 1s`\XBPmhٿedWN|jVlH6,GLv0bQ@\Y/畍0! c|mScҞz1e[k6@ AG2^fƶ-A{gGS`D5JZC!s|jNUEЕ:!nW "bRẔZXs1IXSvHۢj%X_4,qzU餄ֽ>/>Q9ǚ.k` 73>F " E jmWu9ZP]ܬAZ|NVԣƤG0%?/q? 3;!,C"aK['4URp 9Õ`߉dNJx ԏ5G#>lĖGͦ 4;@U Fk$JBZ?-ќÖ*9xZ'p*t4u N9$%=ճ"q;StH֧&,0Iڲupkl#xZB#,@@T,?}Dy玱&qƁA՚y/`}- ڄVw .9J}G8ֹ/Z+ڂOZufl J`;J~&Z̓?H}F[l_\A>.ޫ$Y2ru-aKd3=(?P++h 2A\;^h/) chGJ(yN$R9ۡyχrMy:J|i5貽S4ES0&èW!f1ȅ{lGo”1or7bw@NN4K6 Ys(Br&z9Z R}؈"[ k/Z 85F2~YKS"zG*QlAj&,9vЂ5xHoUksۓ =ߨIOi&U&Dc{@k<,dz:Q qfVR 2iaDXV.OHG.01>(cna۽-EQҳ"sqzW wAF08aSn=[7A+"^3.S+ Ɍ;6&cH!::=ُU,?0 A/e[M79KdptmMNv*7$> Iĩ.rk\@j _z-%6o9^Pzy$=_B 8g*#uqz|=O䀛%w^պrj gY| =^pmqҕ=lcayD]{UFJ$^]~DZ"b/'iD\/-┒Еd }?ČfkObAS\4=_{sewpO {\VjchawB[~O>:,B휮M`Qa>ѯcƎxȠo2Cie”H$iǂpKx *1@9M8s8_т\e7p"%N5~O%e9TV왦r6AawNpYl (crg;6CW;K_hn}wp0"u1ÀM.? +2-@p5M-L%0EVe $OEj)(m@% )J?rT+ 䟭m=סS/t nAA_hg7ڴ /P> r6@0Fm2քaA LM5oЊES)o&u.+\L"0 Omp4sL̓17wL5uj1M/j(b=Iz)ƾƹnHOj+FJN՘~FfpN@2?CØߡmS[xf '<\?Y:gdȒ~Z8jûX[ΡmĉQ/o;Pۖ@4;<'Q_O60@kcc 3 !ıVhQ^Wg7wy3"D+=iz1keځ>/t#E0]6H}8(\mN6݌B5{7hתe/e:c+A7;1[33@&`M m+놭I0؃˲Y#oFNoc"5I%&V%:S#lXI_e,ث)Bbb${`;6fqs9JK)a+l bscRfVcT]^D|JWLp$F&k|p︸bS嫶c vc6iY.zY_(p˦ֻՐ9z/&`" ^R(>K n0cRwMz{!<*|8wˤt=ˎzC\ezgLx/U>lZ{@7T0p`zV:rc(8…i#;$p|[ q ,l?}*],E+椽;_rnهgKAoz4i9M٥EyQ.V{1/@ Y 9}+'5[X/{B`( )|]:6:idVl1{W@[Q\1AS8k DgUހ=YHI}ڛL6af@SU׈~1 `|Bȱ%yԯ1_\Uirٛ&ʵ4QGWt& rYkȲgb7lw.aa"=)4<<;>"b1勯rҊ3s of t!A.߇B'\vwXGKjVّ-.[twq[Xa*m kL vIW~k省k2G/# qFhr3,K%'xwxw[k FfqcU%|iA,ͫs[<DzD>0!KoNqK%\GL?ۧ1y< t0ksوԠ HeQ0&0޻I%Ee-UW5*%ZB8 "i~W2Nkhzvծ3`~ڃ>uxp2JL(=$%rzC1n+kdmJŽhlYu[hmka-ڜ9MbK- HB`UCߪ08o:Oyr&ĥ"~n]o u- g6fyNLʎNzS#X{GuX|C"U^Yܕ5"IL8ZOq9;$8.LtE}?Q"z0@ct؏Fڇ{LX뛄obITz}':eQ+ƕ[|eOEV13`|ڤOyG@ ZNsΣU}Q|9fy$!$@|Int g~,~g]A>K1 ry8ވօbA\$>k~NUqf<>};7=ʫ|yeVspgS;/"x@̥ie7ĩdm1.yG:-G=}-S:+Vsi*(l;|8Y/ V߿0w݆drG' az:"gYҔc;yܖw>kK'Flт&0& 3#؏WāG_4ϻc mYC"7<잨y#:2~R=A'atQC˧3I mnxw׍ˬ&%5l9ݺX;=>jC!%j3sV'Gj 6Й#`ͼ$uƥ2c FM,z!*F7\nHg/ -6<Qz/b}cfCkǾ%,wuٜLp'2fq쇍n.\v8R Ώfw=5"M ' qR"˷xm5IƾCH7Q{Dȇ{@;q]@hUpUxD ;z2]3e%+yPX*vT1RoPEb=OyÀj P:%=٪&DaDyH߈2rXh{ .X+EQcT-/I o!%̵ZQT"PEQ"VH#1Vξ;׌++pYpmrcF;#yOLRbcZŇvP;xF_@mE\lV@bAJ9CH4|4){`@rZ!B{'KTmP4殇P|cN2GNO䧻#}`9)vV`ͱFjW=O{[ny?z1nMk&8-eYv:cB3bkwʜӞ%iҔ (0l}~AdtԖ0檮}Ii*ޅ1ʐD&Gf!:@|)&eуڳMلA["U[ ̄q'*.^NzKKoAZ+sBg|~9#32aV'\}rxW4bQF̧\waoHJ7bcy\t"K稑MoC8(7Ύc!`rC{lȄ.m(t{+EUɢvc%d}sɢs*[MCժ5Ec{}8Dd.*'2)W5ΣYItxsTN`cQAeɬx 4 *%_!,҆JQ WFN5NTtb^n>Uߎn]ƍN.6ϗt+,c R| x\в69"jR`Z߲G6zHkAoE6V`, PG_/eNj^֤Ǫf)}ӓ7YXIv _`B"ޕ:+2@ۘмe#ks̚~ ^gխH_wvފ0K6J(֠Ra3h<ّ5zC|SN-MP;mZQ]CɎ5O˛(ͺ*H3&ĆD^s"mZlNnSCPS9`=w#|CFm~ՊP!`c-ddKR]Mq#SIK#`&M~D ķWnpB(ᑀ$9d\Pӕtţ=z[qӥéX26ǞUgt-o (6yT _jӼ-Ej@Z-$+?j&rWYjRFګ:ј*uX\yŷR?׻&zYO,hmH@ |eA֪Sa$zz ÷Ô]nD5Ļ]}"Vf\a[ɢWiwH^"z=8n+Gc.4 jX]b+{Kdp sd~[*26tڛTZSD[E"=7ؖ~oNz~:!8+i;ib3k ʦH@rdN{^N8jxT ImLfov gJ1I6.+:GQ3f2DߌS,̶ s:N7ó -Ms07a^M5H x6e!<#&՘#R@b90+qdr)ړ8Kڌ@VTHjV̙L8\GC9+,aV n(䉰v"clhM.ɠ+o3c X{! uaW[D# PwH@ Doy-MS =4}G UH†ű[ Rg\>Yi,:N,xU4Ɲ֤;:jL(C,{sPMP_#e2,Gɨx'{}ڭ,Vg j_ǟژ$:a c}d5n:< }G>6Ƶry[B'Zm#58O'$CTzX Y^r\=ÍЦSSAKCb23r< D\vEթ  N'u6tjzCk3 4]o=((C6 媩TEl)6> FY7OmHl@Eeft[܂u9"4zCݹpo:9~ En{/ԀÒp ̾JJV[T#УPPk۵o?b{uR}Rrd@maҲo-R|׻N^VC05qr1wWE΁j7[?@BY+~?~AzQ6eA0JR21d€ܕM(N̖q*5g-20ysQm9Lxr5`C)3@^9@L=\T+}$!M|YҿLHmøXG~ط({;.M.p~0h:ikpXϏzw1F7hv膡PG};46K Bd/a~ﻌDgWy*ZGx6^ &hdr&4=D$t/yU Ԅ]B Nm b,GQY1 @i.mPD̩v g \։vtMou'/:ŽJ`('T5<["ʒgoQK &PBfd~lNW u\Ȝ"YO{9>^՚MTZlEW$'uص=dhk%zKy6u+VXk >[ *џz˛3=ioZ3HZ'T(T]r9[IhW\!U[QHlVn͸ _:@ձo) [+ff]S>9BV⹬ (E7/X! u1;t#%ޟ ᾑϹGyʇ==j)`[@8aI=70d%Q|Mɗ lHB|.0NĢAg5$,)$ 2QXǽ}u u/YNɑP@{gGFlg3֭03/3y85Lp9;Iћs/)]q_HUÜ~|d ZUf+;ޕF";ae2|Cfcs1vE, 9,ef81{BHIOMcs?8 !=]u&ܩM–^,YGnX3Cdd,Rdx\ B* J@q^V:x>D*:hbqx._,iߘP5 g0y"a;"+!JWLPw5hp9'ύ-I~ܾ[ӃBW;Zpz F:9/%iu΂NwO^H',X&FؤW: S;S[_TY\j5S`K^$[Nv #InzbDdd77bp~~@7f:n}*GPCò5e⣂(τgmë'@,sJsJGCtR.$hW\>(`#S^slc%0vOC׆l~tmw~yA;hF4$ &RBwDk>h,pR vl`ˁHrg4pEbb,Z=i82p~d`%}Fcl%J UuWpR@TS_?XY;`: sh%_Bd栩2<ig0ohJW>:n\9%p q!l[1av0写 ÓtY)|{[b0jEɶj;a 2+rV"1NORE'&18teIXpH/i+Oi֏ ;J `p02es1x wwt`xy>4t-do-77h?k'*4ZgU 6twc%o*qB.}ԑ&,~ b ׶xԀ_Xޱ^k=˪""KXq9u'7aDB'^3 ͮVy.n5m;p`t**##-GYa$\J@4X^ BCHwڵֲI_kYB4?ƆA\n8 燱H5ՄB!o%X 0xnOaA~2w$#O}g6$R"\û`ݲ] đ& 2j7x7t̆7 C* M`ڿۄu8BRm9kql2: )-}XD\}p<3.yD*g7mV>R?@Y[а6#~_n" 9XŋG#Qِ?ox|uy'=N0Lv'=ſT2Y~#~~ 9h8};^(ɪ}N 3^н]>vhy53u7z #_"&.ZE(Դ狰8VIe@K2cڬqsJGGB/rFPό=pٍsn2=]M.&Ff2g 'ǢxQ~ ħdzF!8dLXo(塕06u*@Z*d:OB`ϫD[k3K@CQ/?-h\Z*G^˟껒~-}R=žJqf*GQTg|h0)!:K2۰V%-%2HRiVJr\ Cѣjٹc%Y/U? _ ȓB?o0˚} qԿ!rcgV\7~/K%Cy'ԣ<';B'tsc 0tۃJe֡p Rb0#t۪4#j'ƻ볭5\f Nrk9dӤT+M+{2eI}3-tBM' ͑冲m-TAa.ʫ=Gf`2MuYp ]bQc;V|ʊHbt'~ul HE!񳰸fTJL-^f\r!Ө"s8L6$L _$.濮@&Ӝ[ p(>XY9"̍y7.zf4Oz 3Iad#{^Yʽ/f7N Kؾ ^t|Y=ٯ|ӦWWrUH1:m)|L䈿 E{'1 sQpk"o٥9%9!8 y:ʜ3&P k|±K |?pϽD}z.st/7Tl ;,#-z@FO>fnfZyLQ~^"؎>^ury~u%;3#w #s5 #WkomO<IBJGV%:&!mrGo] oʠdcC:4( fT!N1oՠ)nŞ$jExΦU A1IFM&>eU@Jc@_ JV]sՋfyn3;^z&j!q_Օ¦j9"h:_PYƍt< NX{xpXLPAWIC"k+deާ+g Fu 0/&J;xp|4OGR!aa!HUO0 9+xj(8G?U A/rgm_G*9=o?Ej':6pRJ=숟JPf0W^tVl2jA%^f"Z5ӌHu6\ k~Ls,,9 }zeh]54uQ1cB /xf{ۛQ8)ucW՛/EMD=Xq9.]OF0^S} zmנb36@UGIaL"qԽd/s=F VY4ϚI?8ӠPHQ1Tf|ג?D6\wDiNgb_P3܇z".l<+N1ϼ҄Dz?wA'Ӛm0nˋ[t/%L >h FDj"R UTw0Yڠ>HI]@N1ְA:n # KʷӐb"3#1&81]W~%$[^gys47yQC.wG]ǀbyqJ'!}G%NqCM}|aE¾>gOyƺ345y+WӹjwZRQ*Pʾyt/$B$nppCw]a:w> S՛}M 'k@~ssn,sbjmѝ7 z4gpUe;Pt+q*gqbo `Vq}{ l,g%'2ҖȞ5sw>nUh Ս! ۱4f><XEK+F˃%T7s0CuN-~24%d鑵M}k\GlD)^SoUń7 +xVA;7zR{ph\lf@qbY{`vlI'Z2^ r"uteq rȾ͏ƴ/\0BB`D{M䐐0 `1Ca^WHOZm\)KVg?KL@r#ǰ%uinLVZ·Jsv/]7Q0h3zL 43=b"/wJąjp[ 9#v4 ;۫!2ufKH2A b \Ty̭ld ZȀhL>t(zI gO߹tmk+8A-~paX'rr=Vmْ%AʈeUR"dMX*A20݃GV̞ЮuKa*<v"]}}x&Tqj#E)0#uPaZ/H e Y<׼`h _<ŵz6+#n1+B(8T"J0*C.A0#-lax;۝qQy |xh*j8-z x;ةKSx[Qq@"aP [= OUK׭Tӱ'9r' c{ChAgd,k^pMbfٜYeVHWbvbOυ <Fb^Vr r޶7`1IU= ADd?= ]!2A1,wpT; cE O}Lt\NqoUmaX)/$`S[U@$qe.zs$Չz-eqes{Mk;H 'IXqthy8E//VPʛmA\K!ņKV$ҪS0q>Yp!.ː s?Yȇ;sr^(Ă~e7,<:}cm3tKo}%8V^\^&<(OG@S/ٳ@p0ݾY>X]֋ʪq:)N..x%>TwyYdoDo@Mڨ^_am_̽7C1\#a{~/3 W! 98v3"`Q \6nl `pN?iC[L2nq 5X%\ <<7"R APo-*fHB piG %l~*q }!lEsaWr4Zt2dSxȢD&ߖqBlI~J{q/c3:i$&:wv 6\^w w%^Be׃}:6i\$g'ߝ5P&nƻ=ʔku[NAYo2hk H~(\͓ZgՔSErTFsԮ-\b{{bs۱ohY z3ށ[F*%Rx pR?2tDOW7bR3%>kc\$>LA~@|)v:꒤mJZp,leJ'ij˳N@@HQ̡D3Iܒl[t)X$^1~;&(j+Vƭғ,U3ަǮ¥U;0(.J:Lly'O7{ܖ.*lmd̳9)<:pW\ UmLod=/d_tՌ Yx!dt|2fULeI%Ƽд Y S_ɝjZGH@_ms@R)ʃkU?6OEWIC/o9P#z wA-˷RGa Gv"F {a >q XL?-lڙ5UY3?և5]}t`Քv"ł2R7uʱk4!~ƭs`"NЫh sre |k츝lkeNo\PX[g'͹.FWk|+/A*/Rc^Z7_PtC>caV/Krw@~ 'zIb85iMBWkS DD >EO0WOT#S_1S!mt+Z:g{:=l@#T:dKPz}*gyȕb+A-Tk:`g.k5qu%ӒkOD0Wf죧_q;!&ZQOURj{].W0Vƺ~*I! /&6؜XA9yXvMxX@H뵁uy@V+O:Q.g\lFP&PPyT55ϣƓKAHQD138~3dHTp"a&=_޽"M2 2SRūQ,%勭gFT$/_B->KX-g!1gWuxӪ-~k>"~r5z߸fv;Q2˗A2t{%>%2+m;d@l9^,f9ФLtݬ"7S'ݩ}WM:_* <#/(B |^PYB),7tt`S˘Tk "')uHxc S~@萖6.Jii8#!&V׸AnH^ E;SZ/sL i0e5w .ѼbǡKnwVki5vbop%$PH]PoHl|?1ey4"#} [q'j#=tvD~oVE47kE++y E/h%־6Ywz fJpXXӄA x&}m7B({_u-%8B3 f$1rU=2QӀT6رx4tU)DrMGL(!R6GJxM*Z cD}Zߊn8kЛ(e{ avC_PP6)@T %5 .oMڽ&0jQZEvPQThKqv]xYԼ,@{wv]TIk/BX|+LHf5&:1ěIz_כKWdF<ђ27UPUthl>We]xa pt˶(/a֖.hۦZFr'_mw?G_VlΟ?G;$LӘ"3ZD?e(ǀA,Vvc/Y5 fIhnXa\ :=J0:y2խ&*|tov*!HBN`}ha;b=dPvxүWKjnio[t @t-U1amBb~:/e6Y.TnrenNxnX- #r[o2 T1+[3ۮ|SΣk=#Uie/p1|ye>W{{Oj$X_ʇwˉ4#}DB^uXd آ~yS!ˉzaQ-k\~25c4Pfr`iJ)J^Px492뱆6#V jW!2|U\c|W@DssmoM^9;ZfEY͂  *[կg /jc 5?)fQO2 -lw}X1fn$8A.)eZF<1J.]G(UGs|Ʈn$ys"+x 7N,`"]hA`Gs(v'd`W CKyե/G#v~#~hv7cqro$3'W~eS5n,{ kr0&̺Q3.Tw$FyR%DYA7/o 2^v VC4U6NK^QGgxMaqqMjP^-^T"1^![ou Ɛm2jCHy [cuJ j#7+ɐ 4'vK>?SV9ٚ9\ngl^D茖-\#-b(WF8pCOh&pB;=>܀+ Xl?Z bR_.W #f-C3CajerZ%ώMLL&̈M`#zl|O!#^֑0bf]j)?ZBH]; ACd!%CF6uMZ )1U>k-!n ` #,s-mf(El]uϛj|~3@,šCNQ s<>_nqFOM0|uw893!{x?N/Er [t By'# #5n$Jo ަ8= )"-K G)ȩAAb=5jHP+>KPx1&sQsQÍRU``+bVI˨ ·zhcL*9:N)~7\֞]?v|$M~$Y<-r0je<|'aW`n4NMK9>O5ݍ֎kh,ę 1, }Mxy'Me'z(K3 iJZgP3j[F)K쥑f1$3dw16Ar-9 fJ0α٫B[)0?ˉPFh7t)uxR/ &&OCZV#;jVYÝsxFd(uu.uԿ YWFnkSYqK #_Xi(ڨp$XќoTOT Y8\ hWo Mu_c]y^ 5;<~V 8yK)>_U#I%Xښ`m-T| Q71(h8rA76Dbito}/Xv\gʬs/, jbn'z1fgbm̨zryڵo!F} +-TjAyt v4 തsE:F#ܪx\9.^,LLĶTh}֐#JȨ^'/o(*EJqaʾeB7õM,OsH zhz,g (Syq\̝ST/AQ. tQf7%L#TZt!.e^ z3Wl6NLɗ_ʿbWJJZʅu֟]6Ŝ&pNKL6o?^01ԱBբdFm6Dk3ϐ F?})=:ZVCyȚa.!qׇIY-dDEs?< Rl`܄jaT;mY6 ybhŲ Lnh.\e2(^Mكǐ9p gPa£ [M& cG. . $d<"`ٟ~*zO LYJy.=KFfBmrurXwŶʟar /$E]~gVa'xRD^[\BO*p) %+goe/1ɏ\qw= onK7coYK]:XqYNȯ/CE|gv-` Od8'^!(m7]Ϩ4&Htd9%-o =Odҕ4>l ~rf҂(_xE~B#m~gjhQiY͝@TK0AArovmN֔d\sp[`k"T~RPR%"C(Uj;**CL|G ?@Sf1{0p7-~kpsCO[qAZ4̪Wf0pFaJY'BnRuzgV/xm,4&|8s :WM$%a#=r.:j3dS}cQJϾZ&ƔϊNnkx y8{^Zolܵ@»ER@.9!AJ^:#l6ʬ$]}vf6bHK!E_sE;׳Ƌfw؍\wx۷/FY"#5fPE6ɔ648 5 B[ w}ZW4yoλԌ.=Qn^|:] $.Ƥ~}*@wr|Eg%`6~,0> -#n꽣LΕ]ɪ+Yz.Ԩufڒ+â^`t6cS`*:>.|tЧК<̥bnM>1GUy8 |ҭ3@vk|fBNUGELl[`_Y)GgQ j_,1TI[KJ=-V&0_Nγ;c*>3q(]=W7GݚHVBhZ ک󖇺u}:G2-EDXYhs}mw_5]] totg$yIZ;үܗZh# `[/+qhd X׾!\$w8c{ЂCs1'Q>xE~2FMmRGP/:YAS4&5՝~3+CJgp T}88')eAn8-"gq ӿ]D%jBD N˿)EFc6@h^$G+O_^EgoМvש8:duy`[@0IJšgG\pP4K05)=ۚf1P|ߍu984&(h2@WhU'N\Z,`^lgش{߳Mfٍs])GP.1֝ǥ"4sjU -Q2Ji{v|vj˥թϾ^ld2p|mN;fBluTyړBfj˙yc 1fqZ˕mʽBBi !PéHL@?$l(!,t+RG~P4FT p%jLLy_D UO6?1'"lR:yWAM>F82Ȝp Y]*000EAu4nP.#ؤJN#r-x=}@TJӗW2w ĥ[%D7ƾ@7[Uy9b_7`$>_>V Λ=K'CSX-xg)0|A;߄b= Ƣ9Ț~GLj(zi9T)=gV!1|lYtTYf3?;4;D _M_Q8^c(L\]  Cj/O$϶3\?noytsƳ •͝/AC`צFY]JZyظl8q·a]ZĪ!y|^GR!w~.ycfiu`CfA$U[m!mϴGo&W#HnRxnҥom3In2|$8k/X$ }MulǦQWDhܬ'*h%O#䀵٤"Ŝ;T7+1 )e_:jF=-&2qP{X?@S%T P[ U"HYI&ܶB(D":wLJ,mK֒uj%)FYuj d FloQ R z5 =2"T*ig-xmqhɢd,ЋG/җRx,BҨd>T$xO0-ۛAtAd7܅P.݆:ss^XfTx\6j20u=:AA#HtOViKZq-jU)hXcӒH,-4 YQG ^.1Q F ^LXW/F.0GTN ^\2A7VV~P;}UvO52Nf`b&wR-" m_~*qӄ W ?c%G,HxZ+ru+J *YqXXտ /g+Џqz0}f#|![:\Xn!5fe~ʢPR`طx[ z{8dJ4AdWIOQUi9 I`UGǫBY{'z@7 zvwD^sE4yv!W#/j59&ڕ_=p"8X^|Yin\M'B&) ;1=V5PxFɘە#k`obWKh$l\L**q5#Tܙ3w}6 {Wh~skm~E3ɘFܩ@;WuB`T|LN6ģuAƭSf* r豅0s IH$?L7w<7o` ExKlc[u_Kexx^++G"6*l)ow|hShEiWJny)NŲؚ⟁g(,GPtU `c4wC1;@ o$o_j\JFnzJ 'eOm410PBzW`(=ҒF D5AB݆US^qdjTw;s045ط!'>Sy?dzJі8~pS +Z-Ř% R/i0"^EBfR=p* [IFB=}o*`0) 7ľ{QHnWTG.F2ra;π?O"W1kkjK3|נ}-{cfeA"N`ϠfX(8o+ B~~Gy;G1cA@M(<'OFhrj} 䡽|3Ԍotkl)Eq1" ˋ"Z99*RTlțYF]Nڠq3!fS ̿x[# +,@dNWDz<$!: #d[b6‰v@nCpn3Fo ݟUtSqop6g}Fޑ`793awY>)%$,j"bc7v3'^,f`{ .9h&V̱q!ob-Uc@uF Fw ܽ1yOr/'F:yN3:EOʴq>Co.`gny%4/l[ˈ8I]VCI)*l&M=iU:5mߌ_acutO+q(O>Y6.#>g_Hdh\yM 0wU}kMUS.>o7##PPtY3ָuGyh}q-@<+^{>i]L6PЏ:tJ!i-iPKR {MB0c4QgroEyy4\ "rЕJ>) V4z4`"d8jzvL,Ͻ~ .tbȢޚ 6cQ{F'^kʦ_d4h4nW,h voI/( 5cbX=GlVyc{x~V(O0vpn `!Qi!-Gb!5݉fNh{l oJyqz4 U6ޓ< Cqѐe /p,H@$W 3~!;Cw61[FX,&y#$(Ha%vo00GŐOXH":'aY i8*e_L*7Õͪ8(d.ɓcnŅ+}/KV 8BFu.Z|'4x8ʐ4#\`_92ZO1yפ I'G|7_ƃ N(=ro:䯇FÅL~|./0 gVS{RLݡO+Va{3b,C\T:P;Q+BĻt`D;=Xwᥳ25;cg*?1h2MEaASu]V'[z,iE֧='m6T=oJ]K %>&F*_ X~ƶwAmM.`/;)Z?p/1NF* Uz6t`[z|o+wl.yAR6G+`E BIe)0}U3⎷ʹ4=q@;acB<ғa'd-w+|&wr{؛"O9ufc <S~=-lXNĵox$7⬩BNM@\{>VFF~"TaulO`^|N7Dխ>%qFZAy4m0 V ›qˮ#ѧd4zm_j15:8{Rԑ)mΪٛǖi/w!6-)MbhV:dfJ99w:IG˥"ՠ߬(//d/x-e^fl߰@YC fakWK3za UfYDFw6 uD4Z+1{U)BX$L .n3rãe~2?ՠm 5ҮzU<L w| ؝m>w0.C2"ڡiQꁔPKtZ#$wQܹ|"oH6hwuXS]iA':Y*%+ >X.kGߟ%ݜ,O]+'@ J+UJN oKꐟtufxh\Әo̔wO[cygs.D4=9.GWd*#~>/d$1=Oen =R [v -BGh[@w5+,ɆrzNYvϴ^>\ׅ ׌>UM*L3>:\N-R}8|Pw-*i0|;n d0 Rwj{F)8~ƾoQ㭒,(;q&. 72H>RaLqǬVgBZ\ٍa#m()؄EeEN7ٛ)Ml [NZ>VGm#1\Yrb=lS-HAa>bBWBW?_#j#j؁+C(77sаqW3"$y!LmCMjxR dF x"v@Qz#F0Mbwq;S!U!`w3hDo9 `YdXň|X 5E1_Z7К5Gm׺ՆѕDVKi`#wW P[V@'?eitղsxDI r*S%4Z#9e<,65:3|s׳ZJb.1=!wbi>tM ؉R D,/_rDyZpl 방As 8Kq:s!W0,f%y( ݛi!a.hb?kO6xƚl Ab 8J, jr?CH(;/53!@˱S dacʟٝ3KbEԻ ^Qe5'd1ƚ e U ΀@ H a7>WIm#ߟKګ7#[V_~V@E,4!#nq`bStNn X!&2u|"tŎfZ5ݐ[I=5,^:^[B Eh#/^ר~*fBæ?Қ+0~&<-Y%K>E  YEtwkaզu^W:ZMx 7(>Ō-KDOn<M峫kfBNӚǨQJwd Mtte3ع6ƽ[P V/A2 iѿ ܎֨I]6J> Nz"#n[9819͘u|(lbO=ݛ mXHa!0<ߕ\Xk.mhm@yڹ\կ_Pr5C?]- r`wsKgttt|/YVM <#`cI1Npނ) Hsc z?@8c1cXR|B5rظmfi#" D$qf[2+ η9&5EsF@`Q Q#` Qwh5DBJKp5‘O.40=k"!G0 GB;4 G`1$]U̟ 8ݐ䑡6d>ŧUF( NsO oFb_bw`X$D?<@Rj\./z> u:Dv@ g $euᇻ:lrg`A< 'V*& @P>"EL]Ehî VWTO)^ݣ1hs+=M|?{c̕:V3D@q4j kLO9]բG\Fg, # F'd0,5tOo@N}eVUku*>u2$v4NKl_"E}kID/b_wjVu PΘy]r%7bv\M5FifgޓTʧshڋ7 o Iwz襓9c'x}m ­ Rg'bUv{ +^rv_I'&AY+AM.yk3!Ǝ15kF;ULM#nH|e+^jt>vñ$k* ~a,g &,f:|m҉Y ZZc9*,cU  cPdpLR5Y֟Ss" t57ѨK-e|k ;aIJ{ǚڣst- 1 Yˉ(JN^<cлUJէFtd 7y=hվBp*Sȫ9kV& 8֍^o3 H>Kt }S?cjyR>sqÎo"E ʆծ9k{Tz/P7:Dp%1\M(֪wh$ۨ#$ ӆD@CgMU)I? 5%9l|cEF OXU9_Y>$`, Ƿ]Q4':yjHqa; peɄ\S0չO.@_ýV`\Ҁ]-HxjW1*åsj{VM<6zJ'aUt ߉f4jqbY̏H;{5:GK[Y!?mD(RyfFB̆].X%1,dNpwPb beRDKFܙ,nt9=a콂*N5Оx 5yu";R̸ktZG}@6_Pok}6 g3rY!RJݓ: .oK9P_EՊN)S}HK4d"WM;A,Et+) X&ډP8J]RmWT t-*~.롺aube=il 5 UsYvKҠzdrY`E`ʕ1`ryShM̈́voյPnB=ðxYidnebL0~:,4(ۓEg/4o4iDIT%i2v&pC4!E&ZLs,z e{c,Ꙕ<˜/5pDFxhf?Ǟly0_KC,jȩw47t#OtZ9mPSլkP U -mjUߊ/T#{W@Q>{ [,9p2:oENQ"O_A,aVrZ_34Gi(qb?sq"Ր/;#>E#<sFSb<>̚ap{<vҭ춞l(CeCأB;fA M5tfA]!!{8b3aXGQH A.=/[_墶ݯ˂…& &`~Dc$j)ŀ: $F. 1 DCʮC+£3򃾋kx^zoMIC6nJx87 !$@+=#ٻH) XuL 򋾖zS:w~46bgSJ% *zMm|:8Xlp RQE`=.w~w~!eGI%|&>j?qE\\ n,]i/f4qg 옩 Zwnv(? qcAˎ/j~\-4([Cϊù5s-JJ섞kgvڲbBͻ5ęxC6cǍ[-WYng@Ǐ@)p=Z!ڸ= R5$5h/ o*lZ{AOFEdx8T ͳ,Pm8u DM־ vѵu܊1y?,y_`摲UZ Vێnj\vzTSGւXdg }pa f"-]j9dRlaVϥm2ayz_.edy&}?u*[>/5@n *~ J;Ҧ]_;q[a3E|jiGWs]ˁ|n&:L` SK,ڃTj ZK[MZ%'`y?aEqO&4$]"C5aj5z=%UH&bl# NHdDJm`JvldžҀi*"}wpGT(X4ښ궏Ʋ:ڥ| r5b|' ƥd|ہH> 00ts3O8S J^//Q?m0Ng.SrkY:/{CM:^*",ِb.\5w\m;޷/[Np!%Yo@Tf )ж='aGhUh2ۚ%賠(Kneë^W["#Rڑct\<3ݗlFocv7yAr},ЩŊ݄uo)ဃaޗl_رDnc gۤ~$Мc={iw.&p;wS#"~Z* xSF ܭr&؜=z?67Ku%|,8vJ .eIkFQCs>__!Tg*^Aj~̆ڼoW|XLpw!G^0%sS_XAg ۄ7RVE)o:U)KgmO IOC9VbYZ$=6q6On%>db"u °ҷB9?A7,jz]Y>:ZR\q ~f h?L,?|4n*Cbإ2 _S+ ϭYaDugCFa{" vmƒ_m"ꢻ4q iZpc].δl-4mۊ/$i'p'8Kyv%UWKj vc :ыGN&&0ȰEi3Jݞ Tn lNLh {mD qzd.q4+zcY$)^-,[VI$vzmlT2R̺[gs|9os7űap0$ o@5lwbrmJTWjbn [_Jq"O+׃ʯydnD~rJ8Pd!ܐ0GeȵDeuF6,2 }x u>M)v#M,YP$VĪk`uVC5=*MTdOʆ$J'U%~8R4kU`;Rj %˜:bmWC# *H qUcONR^'{WJ1ګD)S/l̞EbqP?gdYF} Mcy/虏zh-RRwaUMY)tL*E,aTJa6HICZ+f} M4gdY/H XP^@ -P:=/̻ml*d"!*QܱiH憉8^7FgGaq㙁%:8qؾw F6ŚN^-K-YpE't0sLjwlyc^9@ 6Rڰ, Q-Iz >CE(֍B(7Z I_"+ Irݕ 6w$>n%|TV+.!>v83Q* c}Bz9a\ 7{w,1w:`[{9T>q 3MBl2 L ]gIapFoFP@֠Q۔iu d7˃Щ\Jrh"6x Q(<КJ}^֬FpKNssKث -8H(N$qwrVHd.2%:*$!u(iVfz M5JM MK:-fVxtWXL؉SB, 0SI>d=YӺ6Ŧf}*/o0TdNKzj#K0B:m߹mWkC>9aܪzJBU4cd[F\v^TÊF҈i~2W5k=Fm$GW"8/|43A9grsnڻ$䦗Rj{ޜn ։8qjF}vCQ\&ipMӐh >&劶OpmPDSN?q*q.Jd}];TФUp\9KxI9](t.@ pѵ R:㸋4}*,(1æfK ͉& 5gC47Mם0h2Ǔiy0(HXM[<+`YdQW_i8^OpLGܐ:}ԒHdTLRkXMYT 2nj` Wc|ouA77"ڟ|g(\FXY v#v,a=$}k}.Km{x4/AZ'M,7QYL10Hm73fUouU37S:H3~$?8Py$4ѼGl *Q1jW} Eh#\ Q!e×Y"?}F#;ΰ.ΠY(7FhtԱ|kmoV@# X¼xT X0З(Pi[x}0p [I˾UbK;6C(s/mVK `{<' AVŬ f-HUyt8zfL`lv &,I'Qp aiXUQU {[1{} & ͵0弸nQMzg=P \M߭M]-stJQp<# FP @޿?cUjālp%u)1W{6caYm Ww vRVաBEכFhI}Gm3L%>%^Ӆ~%k]1:a5' /alAmf]^ѠC&@:ȶ\F?WoiɋYb<<2E)V5貉,Z¬8!\n9۬:^x7xFg*Za}~b*]n"՟XٷPU;ܕ8S{@/[]*Rx[\ gX>C3tj="um]'5"ap['X-qAt.ܴyvZ$-y!5}pJnKXbds5hז^-x r"fPPNMP7diC5:іCPc2G& 6Zv< G,Y🲬fK㥴g "[[R߲,d(I]EOP:ϱˢOsmG<9]t65`Ƣ>9^v<@0ؠ[ W,C\kY&vk?[kL+ʠ,.y8>''r~U[漛Ь05O S.|uGJ"t(Իk#< bdO-wYȇT`U\PX&-2zi\m?7JUphJҺi 1{`pJz.^,P/Jl LP%%!l^G V$'nLv]i^?^r=t|K,Ԗ|q5Iӷ>*ϝsymt}>0K`?Ն*nԄ2I;VVQ%EӐ;lh(vZh=\ZLiCCQL:VD%5!`KY+*_שK+`-I!yL+Bz̄dCvD.ۑ9ArNW Vj󘾙(z$z\!JPm=8к^BuŇ~dvYBQex6n>SvB xLP 4q1@WNZ ض}YsBԜ l5O9- YgAL]Lj/rb)aG}C}]>X 43ه99?.h $|=USlC0\Mvx[5'2N7 \daZ0-n#*2l`ޗ^aӋS 3Ζ)rJ妯A7@jh%Σ05jPYY`;)h-5nD2@v[lʵ'oTgiVJ2+*f FۊoW9OQde kKASIAaTjIC3vu4!$5e>X\4cb %c>ur J rc}z%&> 1L PrO RW8"OV6\kP]𞳹ɒcC){S|PIN 6Up8UW$lՃYJ izsݤh[`kdOWnF( 6uܕjLH .'#ޕbzWF:@0= gSZ JWpS.98e46ͩ6KD3*ٟAn`BѴ5tԐireBȱ}w*yQ@j|> 6g& ʱrOmMPX r_myO)yŨ#8<oe`琬N~_Z*{Cluac>ѥn6\OEw Kc)>-/k|͓%ӵGޔ/6p7kI@3TeW  gP`u G3ɥ8 rdk'5&sS] qn@U 2 h9Nu: ")\T61!PAg:c[(6nmca3 o E#pО|!UmLX$p91JƤr uȫ#uewa$Qo3/Y< [H.OJ1eNnnw_lUS*"A\y$WIV·"atlW hGv \nE@^3K38tB`|o8 \\CQsTsYM7X5-YT@$xl&f7 ;c[&i{2u f.SL@<>} ԝB#b".< 崟0zЬ1pZ.3t Jøy 5֏O\1k9ANjE4)6 $hTh9Txpg%`9gBo>hv/*VGCGw} DZ3g;Rf}aSY:DCTZn5 C?n.>}EPp&j|/8퐞U$|Bn .jsb|Da`OD5RԌ:6C:jqW[w0C0K QebZh`ԉG$]Qq yH8ds- o8~ڤg\ f,Վ5ÞqJHXO(RǎkF2M@D f5Xwo-RL|u֦,p񣣡>od>{l+hB8/4$(X49LթkY/ VG椖ڜ=B,u ɫ=zG(!R4iQ"γǀҘprhB(̶rvmG# Et),x|o~%?$ˮ44EA؄EE;أThTZ(iϜvU0?"T;~3CuQqɧHj? EXZͭ_;[Lk?'HGW|J +AS-C41FB(j,ށ,F93 /= OY4up1ډӥTQLx˷LKytZ.uo gUdI!Ze UvS8D;1ύD1{YT`B9)3ٲDX>w"<0aj&(=&oVMb1pƟ?k]s|~*׻ y*j)z=9 QԆazS-EEĆv ݽ-)fX8 b ]txl&=Ê ق.I!=ɑkq[Ktl)MQ&?nClh,1m'n!B>Hl̓z36^ l|I3C-໵B:~sI_FZZQkࣹ#a!K`6<_~`O͛^*./[ϴx$ :'-r񧾆GL|[\/ imcK"sdvт2+\aaIW@mo=]P 9wC|{ӗ*I#F?O`(D]4}zM j81ͺc-:оt) )t%4.1 nd n3s|sYk̞#*$|3%rqTE`SƴyY#RD^{ %m:e?9g&.ݔ3\Wo:alFZm` `fP"I?|#ee nOR1um(F!UEFW;ِg#p` TI+Ovԥ2whHWF4#Bj[) OLE{v0EO/W:t3|(|h[l\]FU DTZ;l;*9t9}@+ۅXx7@lBƜᱼ]QLLĊML{f*5OHoX1ƑL?$fwA{JHkw#<^ۼI׉3%HL\^Xk/(`1W--BqH>uiWl<@Q6gդ:UjbudemSD!H,ك89;KvgcHnoNVtPZ@ {/25tbvMTR _ Mvq sI*˸Ea+܆~3&r ;Vd YҖR[}r`FCl_擔<hũR܍F>jWX=Fw@!JŁh}n' G׀ܮ7{q D{)Q2k ~Qi/Πk?=NMS ~]soa^όYHQwpOP;biQ .;{1)S$܁'2tEʏw8R{]ϩ, !ptKJs5GB Ol>L5ĺIMhWGhٳL$~< I~ `%w" q(# =Wa)UN3ƺ.T e~j:k4ƧMsq1ϴtԢ4/aKhZ`)_u_ 1dTgE?==)ڢ?+ 6Fzw} %$ DBNM1mSdO3YJUͣ\ZSP_}5~TVoTw[b'sHkKn.JJB_ݞ8~\OL.Ea: ~![\ڥkUp8 \:}x+xatSk`j/'eWyKk!D1[(}/ˤt?1LGvZÐDlnUJdy"r\WpjBϚ°'c{;\6JzDh΅>:*cgCr&;0( #tWB+&ţ|?n,˲y@ah$òP:۩ b?aZ旸4D۽qb|js3e;(/Qy;1X%i5ЀZTҏssJ>V׺`ura4\a}BTb͠mtpߙ{| G=PV՜\)Xxb㗽*)~mP^k-!"'`#띟'j@IWP>ZS5gc!W`> 'kt/ex1Ka3\,5MNȶőΝH;֑ixsɲV@~n"a]bMʚw38/*=Xu)s@ǕArxn^4Tø?' )Ļמc4&c3'g!{?o&M-8VTmț$ a&gUnj6G4\6`J8Nzx ;֥؄a tבYobY6:$ƳDldx""ox[l.diS8t0 :wuI1bni6rhc5]w:kt) =\ bV̽N\d3kuS&zm܋ZO%e{#_gN{At`Wibk|.j1Oe#B^LhBkFWAgby&M{(h)EWbaz?s _Ol.oZOJ<\pdJTsIm|x |Rezͥ[ }KD-]ƷA"Ns/&~,0f\@p$fB0QrY޸ssbvo'Oo>!B5ǷpιcGk,YK Ӿ|u(]:;]v0,[myp>N"l|rΜk9<$PqYF?7zR @;"džu:2a:0y=Xx8!V ym`, v'|N[ϬhJt4 &l H6Χl3F~$yj5 !%V!٢)_>^^dk8MzkEd1;;uz,*Bjad=ɰi>LT߅*a5D&Ģ qe?DrEޏ#+8=GɈ3T@v0 Bf_!}R\Zoo&DtO7<-tw ')v>eIQ8ְԭe ~ ,FX$gMX7Fq KB<†_)D}R5)rvpۿҴͥt A>vBNE:܈[ȗA/*^,R2 g^,V8AO%Ix!8Dyw_> ,0>$< kGtөZEPmrJ b 7n4O,D% tiRv7D {vP`YN4pڅ#d%NRrƕ< ڮ· -Hv ­ =[!Oh2h]'r&0AגR/71rkO"c۶.plnRo5bL\~ʷĈYiD}O7&S[͖)Ij;7ь4QgT l$7X'neTpҞQ) Ӓ T3I05L*<ے6.8(DKeKuĸ8VYS z7=m7h7;^u:Ozz:F`F/{zy ZLbƋ-"Ud )qAJ;U{vړ";4YQ$ҡx_f{è\U^tjH!9 vzBl{ l QQ <߀q%YM"N| t-װYBcqXY0\ST?,#jf(w[ڣKS?ոuM*̾3 LGxE{Ї;4 91كrA'KӰ0+^/x099>?q%\\ z,`|A?1KQm;s#^bFތٔ4(0]SKWZ3JfUn(=!`pMآ 6š5Е+?qQi)7GZ @\2WCίAkAj8wّK8 a}~J^#;TDr#gmZ /f1԰>gdىЕ`҈΍^v𼶆6UVU 5cYtSH7H#?zn jA~QN:V:5 Uެ4M Nj([ӋKʐ)KXVcs=fO n?z LN (x|8%[ɴ%||zu_'´V'X8M,׭kT쳶f+nj `Un ԧXnj~Ҹk72k0-xeuP:E7Tec ȹGDOX*,𐈖2ѸV)ņ$'s[I+;*>bٓKLۄ!R:BO%J|!ՖP=qRG_ÐD/^BpTy ;mh;|Ӳ׵Ҥ+09+MxaVW2 %1%Y4+&cDI $SkDoAHYK^8P02WuU&;]{z.YGLFdJQ#*"E;|ljڃV##B`+[dGw͝8g)U:`&Id<^:dCCnIG%@!^0Qa Hd,50,`>AglַP7S5(# S6 oz}Z>?&3e͡* j6 ò8qh߼:Bk\`Fⴺ?kge@M&Ek4E.8%N tLMonR.lBrmԀ.Dd{MH%=kOSWZ^Ȋ:Z*N?NtzւGd ]&NQIhHq9߬ >{Lv[Ta.iӏX1F-?$MAkʥ̻Qv3kY"|rRM0ͬ q1P=}||!fVETTW?r Rqr}cy%~nO~pخ9 ްA)2hf=eL3yhb FGjtq( /B]P#KhX0(Qwr!'AG|wZf5쏉Z"A||NέrI )\쌄tH"Ӳ>9P㘆)DY5vLMHisĒחS]Ue%c D4%"ʱ ?]$$ z KWwEt$fu87AshfnOQ5yvĀZ?$'8KQ rxS1k^|@'PNn+wV7$)\9W/5TXQ yjJ9{^N̙ O"~s~5 chJS Yb_'$'_$Uϳ"[/X]^kN[i*8d :%`QA]woN7:^=2S̅vsڷcKUA) CT\p돯ߩbzVxVٵoISQ3 daV\ǏY܂Q\yaYҜMVclZZ/jVQ2}jғ[~0T)D6iG3A&GUrWŮ~99|f#|XHPŅuΓwFh=l ~BZ̴(D5/DBD&!Ih.A<6~9FV ֌wUI'F'%dƘ0AY5?k+(jSI=${-MYNiew S٭l=vJUw}g춁`-58] u(K P|ET*KMORXM ,T5}8"J2t15_\8M {MNkNX13F=<[kgxaÞtP[yM%V9bӷ/#5 H'Pu>6lIy-+J!#?^lW^lλc]L^( )NR+sܮވʱ љiz;2Q*IMم)z_fLgZw)޼R$GN]<34c_O&.R Ir׷ܑvud.{J"M="[|owj96U0=FU*_!EeRO1un 6l,^%RZe.]UYcjERi\n8vPQYxބn%+{ӬNuHWP'%1.u.?ʈa{"/qo륃 OnKiRAN^J1xo5LǫУV?\=e.vgZH{1!u4 5o]c_e׿qWv+WSt +Mܙ7hNO/6 ؠg[of_S@p~ojZhVL9+(8]6P. O]Mtg NTl&L+@7$Ն3Eo, 7I@ZBCoÏb gtJc_vr zv]|MBu`xC(fMЕAQ2Nm̭}LҚ0/יzL( 35hX״`;̺?Q-|V fL)\Hyw /`Yö)cF ٦2ITĘ mVRˆJ[ OlBY.r,,V^]|_n(mDz]%¢YlR~ (=nN}Q68_=*14_PF ,jdBu#V3RPk%xrHm5(rц- r`^ZWI&+;;,0ꓽ֗ |J9anD.('*g?E ~= /_3Kň%$TMϪP?HGN2Le?a.!zp/R8k_ 5R Ӗ|AB4\4idɧ5+UX)qqGC,쥮ȼn-{CK!/q[yH*+"e&ˈs2I-gDMhCʥ}PR_ tbeWϤAV 2c"^р.A46f1%oBY8"/dT0 7$(L;v(R-b<4՞0غ}-ȸg )`(si-h Di.Gk犥$`*5ʨ DAg)Q\K۰ȼu߬NEG2Uleu:(?v" ;LfJ_\!MȩqfK0k;9s"GYe:Ӥ#q.YXqR[h!@/j/(um%`W.]9ukc/Խ&4'-t]@z*|֮AW ÄrdWbW ^2a{ :-^Bɲ:K4 a( cO:`?o1 )/O9@k=@hQM FkߓU|Ua!5GʋlȓWXb=vDo(dDhcBU^h"PUrov})- (hi̬kYZ(M+`Uf7MckD[/LjbvN͹9IUmFe,ɡ!%w }n1=$eII}F=MWT"wl=bVTͲF8T~ot=Inj1^f!KBb՚i=1+8~.md'}t60'ǧJC; miG㋧BAeLdAn )wYF^Ea;wH|">Hk8x⃌W&Q~Np "*#*=c`x'˝4"`|^BUz=}2tdeKZgLdu:87gfJlK׍凼+]d,}t$t0v!> -]ǪX0"d7 (pOEHAW70XVMܻEx[f>5Y[9ė|59'H2q~>TĹP=SF\`2ÎbC:p]BA">Eg=["hPchER1q9}! ~"".V|b=1Vu+p9 P-{f2*A!oj$kO_zk<6rS^#.wp 5X&āCʹS+1Ptrڄ-W)/ܴO!oďUݢdNُV&(i6 Im\C>sf;ĺ ׺!4>ĐE瘙14"JX|0񘍴Չ瘛窫{j>7*nx֐R)|*D]9nqݜ=4$hÑLFBW AdS\f3qaʺNٹFm@ز yn^ G:ŨRh`d/7+лtw+WsyZU"ڬUPk[8K!w%T=FnHi6& @w 2_"7¸]Z،ӺRL\૤EqR"7!u~=6^%sDUi`l'SJm[@j,5'>+lPqlOgvZp4v?gc7IĖܝ+tXWfN$^JF4+eMY$/~<;Mj$_įv8m6ڜ3t0dbIUfS\clF3bZ$L2G%# DIlU˸ҿL~CM 8MՌ*{j!!p8Am/z))zzM#% 85[],h< !EXk@3«6]578+8vO8򀖊X%^־KuuC Z`]v)E#KĞ T0KVB˪u"uo"WԬdK+ɕR[ZFކ^\Wv)l]W{Χ&Ӯ u B/ѣkq1B,cU6LuB}5h[p%uH71돔+sUۊ*X$YpG H=;0qC}6>ZTQicŞ Rr)mP++MIW S/:of-T"H&W\:b8 ^ _[@4.Dz66&6CYq lwd8R8|SEZANj 8s99~d #x.P2f`¨PlZ;+K4˗ܗ5\tFH`ek0?5dc$޿ǭw sC$l7; uy灗e֜̃L;}˭JH~0`>OMݰtf~k&@9կTi3g*.\5jƴ+,֕(*aVV8ezub< 泊ҍlŻa"d2 ᔁSIqxxg26 Ui K*X+YI CeX:N ,KJ+"~{_g/j>jJPbÿh ,&n =ࡽФJcHɹ0&}qV'h|wY}*2Uᅢh9~|?P ~°wYZ3-!N Y|ò+0\}U,ل>rv- ciތ'L2Xβ|%YQZ#X0'o1rM-TѰgl(F=Q+cKnmOo1_F l6*cbSIϡTzd2T-ys{z0V YP=h3XӋm sJ\ =yTcO܇^Bu}4թ0Gz*C Ա 4?"%@OL<#BtzzL : wL]B]w% PݰJ>@Ѧ BTۦX n>mYZx72W(~97rmW äm̛B7Rc{=[1> Az!7Wq$ME /,%!|g h, cӎmE6-_5)pg]M# KnCCǞ pY[T}ER\Hovp)a3x^jA m=ҭ<w_qCI>Ұ +)RW S9@JD&χVD6XNj—>B"*XĂ#;6pnG ب_vgj܇ SoQ]L%j˴_EHOȾ0rIV'Mh4р[@¯' pSk|bTY}çʬ[A'8"d ;ř= RfE )۾s6pPpȯtPg?pZZ[ '޷Mn#)To'"Ї!ZMJ~]7 H鄮bg;䰵 ;w`? o# .eK@Q`s %}fBUeY]qvśD?QGeBЗU^GXqkSщLHQIRezor%WtV̈́j2\dͥ-)1Fn+j۽nT{,Pt!9 ۤWM "~M&Gs;^wyf7&*a9 7|?x롽ՃYaR}ԠNL|86zqA8_PY%!jA+nj)YW_jj|+ J:Q[zM5Gl ZX#MMa0!aG` uNFSO%-;eu2OM3hcp㯔-P߃ZɇǏ`PÓ>Pݸ*o/1 ܅l߯ rH H7"$_*Bwʌҟҥ1NB9]XoStbG:,DJB*faxunaz%gj rYZ$ELŗ>Uo7wn3L#$We~ gձcb|,VJ&[ʚaC^f+~w2a6x`"bӖT3ٺ!FnߠA?C`zap:C؃}bLmYnMC~ б@a1`49FIt2p}:hp=v}MAj1-KN[Rcrw+AffKZ"g(lMqQ;O߹ڞ'4wTWzr2M/g.֯f^=܌ȌeIN7?P˴ Wlw~Z>hbg?UY &=,X}U$bz-TZpplOsljډ۶)DD+̟sry䟒8?$j!C|/h*7gwۆͱBT0U-e)sw\*W@"[8y|20PZR! hvQƽUw|'6]FJ$+ u^,Zm2a? m2|˧`cǶmۚwmm8O+62憌W|Mbr;2So: H%;x)dI0.73FKn5gs]/L>:lJ$@ѢH%{R8(FP/:uFµCОa&- 쾧WĆH(A;¸wTLw;!z˅> &߼VGc" xFp"kr3z}ϗNOZh %6Wu[TAhRTPnDh;RJQ\#ϗe[n]̨E)tN@n i/`Cd鵯 DLMt:ӉEs fD2_Aٴ{m :'ՋST<zk85vnWpK7%ؚpF]vf(x^&g *jRtCTl7r?>ྒ>r11 g"{%E#9@4J>.Ue}(Nji;,zP}V.ZX&>ip$UP/KqmO/RU8zR[w$21,&$."%PNBa [0c>!L*Srus w,YF }ȼ |*~DGZ؜S*EWe>(y["$x9Dܩ|S P3/Ē.~/e)e%3OwUлA/7=]03oWz-xuzž/M"Zr5};l)]b ɬ.n~qZf12bk1槗ʶ '&9 S& 1UhM ay1 2kR;yB_Bů[As-#bJZ*ָG+YZ4`ݜ,p/)F)|u!0cRLuRSlb'! D;5àfBzm/}#xaױvI{ek8t|hKI*GwNjL?pIEuwga9{jW`Xx]2Nq5M2{ Sh,A aoX*2]m:!Sp$s ܾ\l XP¤IllM!ȅ4f@=K6 _)[ՔL.+8 \&wy`7:dJbpe$_WP[Vc3Sp(Yng _2# "" EZ3cV6 Pƹ-N e{usڡ0+. g,v&y{Muqr$ޥmڅ7+%tVRb"[wcyMl+9!NVs8Nc=P 5lB:Cv= J8e)ٖcnYxˌ˼P/ʇ\JK̈v⺃eHLyPycAx˯Zr[yP2lO`sVT_`GхrAas>cqzI+*| Q+iWsۻG3|>2M4:#hң=LۤTj(tRXK-y_#.KjcLytEO"Բy ]ى9"> ,]%`.ȺSڊqxШbmJҏAɕTw¥m?K#BLT\C"VF6c3/JYݾh-Lk@ [Թt]]tm-lEQwMZ,͝'*FqG3yE87[HS*{tM'O-jN9LAlMTth&͕ǦLD\+w|qe^ ADkre'j~9>o.D-?k<\gWŤQxp\蔭eM ٍ0`ͩaW3j R1r㌍9igkI  ˎJHSLz[4G/kFr o-[ u?֤usv(-ٜNUO!HK)^R!#ƭ$oE{8x96 `]kSb$uiZ dN2 S<ĪlkRDRegً؎jjj7Ӧ]j7=wX#p%a[_\=j6F.(<u;x.䧷gD/ns#yq>P} G ri&E& }7.',Ƞ>Rse[ s)2gr#;ź0i/&wO^H, CX噱d.[8jԄp5Nev=@{RZnXRRrd?Bc0v)&;/@  BPEv<_,韎YV) KLn-:qsVң󵖢kDwI.B VNIDaw%kC0·.ΒH+e{14I3`bj\|(u-Dk!¿^x*h߀?c ݥ[U8ᵋ/O׻Z͑AMz$-i+!#,A M(Զ{FdI3OM*9a5`9?>t4d]Doj!n?X9ʴo4ap'<#V嵌l_$08P#u*og⺒qSUoFF X1Fךuݏ % `8Aiqb"\@(dR#;]jReN% mnh৹}xB ۄ1*h$e׉V[}Գ5塙liY>f_#T)EoI nKT οEZm4}Vz 85hKxMV{#7lN0/|6"X}TWn3B1V^B|pZs<=8;0kɸZ>e(9 kHc^X}ŽyG 8f-#i;Y }4<^ň'ceYPԢu,c0ZM'ߤ1 #(]Ԍm@=dC*YXhgms*j ]&ѓB8DWM?QV(@[7d:'@7|s)A7F̌[zN#\AxVfL8&DtxwLGc&On3Ggr˽̘euH7X bjmJDg9 B!vlȀ*y-u)u XTɂC ON,zacccx(FF3T2$MoEnQ'@Ƃ+Nm9Lm1ȹ Y\R͆8yҹٹ yBc}UDc٦+̪$p([ۆ%\mu5qSUN Ol3Uh~IvbD4\LXp,H_$A8P^q*]@*EH v@WP] u9X".zPR]B:Dbw `;#Irڰb7s:/A|I*Z])`DX^窪t&S8ؐ\NpBcHlO23ŗぢuz8Kuc ak33iy~6!njST!{@5#I- ruV&m^0.IG}Br(!$U-ּ+Z4;sTY=>HZW@AAN'#U?amLU@Kc!bm3ky::m sIT:z̳Z81x | C>[ ⷙQ7ȇt\W\ddo;A.  M>MM/eY_aF|5N0C$QSnXb yl 77 8ګ(L*p4 nCE/> ;68h^ئg^ʵ/PܺllPs`G?rǛ_dEѠsl6'Yr;82˰K)\L?9o;pgK R>p'8z?zb=Z XK+FE4,|*H֟M2KLѸ\hpU>עUieVJ6 )N/#܏DLuQj.MήFkbBvx qQo]oT8/"\m(Z,X֫Ҽp "xÈs<Ƒ]#Gas$>#~o*ɠP?3'"njn|ևl$G[uF~vs74 1ɽ;;_:WCiFp!bP*7/2TžORz, sɜA=Z{嗊r,8,HI*dYo:GUCPp٤u}Vr\Xԡ&=PB ַ$4Ȗpp5XbnܝHgi!3x~ ,5')2d\~@L}*֮E]^Ҽ>v[%]@mٓNlr܃.xvH.,]2?Z˼8wsDx#ٮʲ ӪVsSR!+(-(?R-i'#7v(hO8aa:긖Z)A^[hƲ,hKp(`{ F-h 4(Y{ X Eo7 Ŝ>gu~!IoE$IJDZez'4 }_0F -S {TsD&̍h;+bɢ "o,cY'5Uᐚ3I=댔il৥h$0 qo:􊢻Ldxꆂ|S:[/ln~kU-2;|{Z;pZy٢qH9~(8GQMZۭ͓&USUjzGG[ -Yl2r &4[$òƵTbe*]CGL1e{&&Kn6iyR!E!@G%70T߼bd vXh$` &(=+p;D{W>usʵ=͘ ܿ,c%z7݁fάN0bqGNeev(Wk L㷴2뒇&mJ%cIJzyd. ŐTLXjAH_-̼vIɬ4ǴPF,|1JG?'mWq\ H^#|Yވ h "1H5;B:gQE:BGD0`qH>~79H2J!lw7/6r_e22Ǧcv}+JY%.t(?r6 ,Z>RfpP<5Y#Sdza~G.SI;/G7N]I_B|$^^=Y.(B`xAY悱̵&Mt$>dT`VV0p/:Oc0| %,P R[:Ɲۅ)A\R,b2~Xs&3hxrMBk8B:lf4Is}P#$3{#{ ^V H]Gma3:U#CLjLP臁Amo-QH ;}_}ŻPHވ*CX/C0z"49N׃ɝ_}T(;u+Pi^9cOȸر #= rq[xj FuS$!Hޠ*: (S-(k|'si>Cjh(Phkx%YS04Ab8D6VTdh{d'[_ޭ8ڨ+VֺUq񽜇N֠`4=Y-OeQj'XhQOݺ18 1f\m8oȁC%A8cqkNkj;jc燸&Y7[}oVe'?5a%连Y\x3_@դ`guP>r ",e^Zf`^c+h$Q=o9K6G֭0T+<#6;ZEԷ~1FWn>q 8=Rjv%_(c;ٸvXK:gy9*?"JB*ՃZl(`qkܑxzk6^#S"PU 6Ơ"(q~vvku vi\7U*{)w*"q d?za{b ?Z$ԭhcNg~4|@ „5^ j[1y*VeRɮBx.zh/\Prpv&YE~S0q $ưt{ C)M`EEd|e_={Ц!a|)G{M(CC7s Cv 3j` oH騒aCx]+*TO/E} +L^ӍFBp̑aljØd)K[S-&E=մmhg%ؖn;6't{C si33DAdS͹!hń8"G7m(kˆNQڏ3|)+d4IT { & (֡H?78L=Mt:j>g>({ HR~F.)E9& hIoqWJDb:NCʌc0E-=xuR!aRw0@gֺ7 gMSI9+75b6 ]ɼč* 0jv>`o#^F$Umͥ*i5s|{zF5. oy-8gﯢ+y'ַ#VwS%Jj!( J-\5Vm}V̟LWE fDzsKqM .yqBQ QKϣ'5I R ^e$W[2goXIm0K^!wA<̲ !^@+(~@pm[u8d!6sb ϦR5nN@!t$ͼ`opT PK 9_7yS;aDj z]HQٝͧ PSaIqpc{p0oM]PQ=gsH*[όL;dKޱ'ryș`X=}r<Ԥ- FuJ{.|}C w~k*:yבPK^8 F3~FaTMĀ>hG3&KV@wa/b{My{VHbM4 ]hG|;#/U ;rd4ZWm;r8 ʑLc ;n p1J# iq+MCzX]t:<3ujT_f-jRFb>&MA#_սĂ}UO*VQ=G@Euބ*d \LZ=M$qP[;lǶ'9rѾ`+8 \akEJJ}\QjqA. ^` йݰGF}-7&8; Exq~qO܆i)IxoZffA7,ƥqX\rVhr/.arD RmѨFĐ_gz z)]VV/'' b鼱I!8/n[JXFV1d bx+*y)wBE=Qp)/DT^{.it3$RuWjiR*򷭢MhYT-"RXW <2 9bEwH' un_fւ6n]gbꄖhfWОzOtm (  h,"űY]}/!:_2jڼ]A bBc)̖7xLs?~K2 pem%S[TҊmĭ/c?oK፬bȇOֶneJmi}?#ȭi/Wu(nKP#v-PI|!V-D!b>8D*eо4.mg7h>O6W+v`13 }"4Xc@}u^@ <-X/t19퉗0`8C+ĕ A%GdBFn$JR,o:6зS7e yg >{rKQ2rmciYUkafSr1YHFNO~j}#\t{jrY(>=5Sju%B^3QY2 !QoĽAƺ:jsQO[[4)FWr#jAFN]/KX胾Et1L覣b2ƶ鲽څ)y^mT<=DlHK[9 +zT0-lN OL:gh W?߈U{XC*XD>\,Ekp8|$6%ucVH^jp ,1C|ưUC\ltPeŨ:勛/N( Hn;7! !e pp[ec kpLO5Ju,!GIֿ=Y-Xp<׃:s߇,>W>.g0y\]nu`MOG_R̂hLu0xcxtXq+uUd}r~SOL#ijQNmٔ3'xBedv0P [I>+/AT> ޝauٳвHVW>q2>Z&F(I`E(ZSK; ({0q<7vkZBLn7С%qQ)2bGfs2?<vd(,A%u|Qף'(Ǭ_4կn(RǩǛtcAt$_R] cr/LAVS0{؞B^,]a΍4cSO}Sj" 9(4%w:, wT05PP|+k3g:ApIo,s 8[|Panx*R6mPk݆f̪N0ľVct5V<=C({ފ=[h %qeh*qQU(5Ta?y>AI&DuˍJ`Xt6G 5å&מƬ¼@-0o?Y{X$ W]0.#v)^PMi[egekѴU#t[lZ/f*X_v6Q>MQ%0VivckP|TRam&"ROFqQQ}Vy>MBRJ}}v3NF 3bx|mR_4L}bt{YEpnI 5METuC @B 쓮ݨ%N@ }*լOg5ZݪչVT) -Lӂ4C(~vPEg:#3~XPPCG_5'C*ƍ|7f Q[B~Ai,.@ׇVKZO'+Us]пqc걪1~j{Tpmn9rӞ@OTY GQ9~U}Cbթ\ONCKDхl/!gQwtu!|'NKXJ^k゚ܗ|ɿ2C`).4eTq b T<Ļxlte jY$s'&L5s B6fmk_(jSP<6w=~{ r&ם| C6_e@A U/aeiW"¬džrW$@.gLΚ zhϲGMxXwCX`T[jI_2b7Bk^?Q)հ 7,!0u#g"`V?ҝXd0s~ }͸wgXn+ :()sn,B|[_`lomBd&  {氄O.728[É^l8,]X5\bᝒ"[2 pE$Ce!޽3 imdoM9G}s5S4`-,(Ġ9-TQ tl kQ6h([gcV>Ffr9}BC0R_7?Rr i:;)qϕ5|?a" ᑞq{SHF GI!lRݵZ/*V J"ŏ/tEHt%!Ӽ*F.~GU6x4V𠍂9R?5<(VkyxR@ no"k8(&[Hn6`BږR'g"y0g]dJG`vWXD&e:fY"hJ ߡ HR6\p![DPZ&:Ř⊰xm`Wy[[ M;BRIn%?ݣǨWJAT \;b\g]G ?5fM!D t.=λK_ ѵ2uJ!OxEm&wbE ߠ"ŲaM ڟtxXw<.vzg ֜^F;r$OYBtX;[$j^Hp_*p_FnjQjZh)*n9{ݭ>*c4:i;1)`IW}M6$+ƔϢl\b 6Q6qqdwBx44_A l@-Z]Uc%,RNO pW@/Y5brÚX2Ba9*^AiܛfaDDί~8Hs2=IJQgDpoDA'!P- K؇#+0^X|(>{uba ?]JĤAcG]) kќ؏+sb|Qq";:S1'fF-*}ӟ˹QF)C:x6J||K[=JWsd<'P0_yG@+hP~&Q5bxQ^rxU&dc^Z0daM"o h+1od;JY3ZsMK`҃VA] (U;`dc#qCĞl6^W) tLz2P27yթwbU20a DS>c+[EeLm$7 w ](5.YxWEe]mD"Z#Jy%y0MdY ˊw5.;B>Ұn/Lp]L20]n" !<7( \D^z"w/E;ވS22 %Py>5.@_M0u(T!#'n ,()tgɚ͊h3ZYk' C]Ew}Ԗaی8O0/[ ҧ=siAq {j*$1{e[r,> \ի[>/e?e#5x{utA FWWbyP=a4 $s1=nk;yU7KaB|  C=pJ(j$+hd!GnIaYV2ܡXLfd}0u (@^).ݯ*C-~"JKOh . WX1l\,g Ѝ5Z iob!B͙Kt/,2̅ogN&0{^0˥hhh;`AkVm-/+ Be~pIN-%G~L틥囇`-%jۯ!j1EQ7%z=7ʔ.83 =?D0w'Ї7ß˖NZyp"}'wa;^cЦG>jw7ց4 nm*ߨ&aP1+pk@ʟ‘ >,ٳJ-d'{d'>; (uCN έElqOh#~CD:xg*mYW} +m:=/X üζX-î6kRu'ه:h7~t.\~dfO%&гFqÝdr zxy0͔kFm03+1}(kS;NIFn֞X싵 m/jeyR w.<lpȲx<@ѥaڹ3(qҥ׋ݳ; E2',yܯ\`~jWFj衚zQZHuKs'8gq,s\z7Y >q|7-瑱jˬ˶sMM1o5YH:F#G`X,)yƀ.KQ{4 f&g*7l\t Зw4 Ͳר4;: ;ku/ ɮ6-t !rn፡Y3~.qtqg 5dxuwe=kc=p&=3,+_޷15͔#3?hVr%b .=7KL,w9JL0eIf{~tA}h7*ٽh"1'Mj\EK{NQDŽXHPph8CWˁt—xwvQuzU<(*_SoˣI ?f|TI6&Rbt]BxDZ[!X 1s`q6?lkVb1hM&BB7|'Ԡ 'z 9G6WzF1E.@f'5n:Ո0IWs-FgɄ4•U@@ A3IwK ;j:7tK9}m(eMܪ'0VvAHQٱI}ԺU(.6 V@Ƽ`Prֽ'~#U )Lԏ)X21G4^̭u0sAweBsYUW~8eץ#HCWxBU\wJIϤPIpᥣ%Xc&l.e1Hf*6ut6&l%m_ݔ|ZqfFi̯\ɚd :wi*$&aۼBqDm3rxpGdf7"i4]<_IxSђ Q=D".ߏ{JX%InHML<F3~s\9?qEgڄ $U/%zgb19;>8nћ>y$8ڱ"g~[/״: `*ÞU7+H4 ^e({qI Wnp/?Q;]5x57HRWkmf)/ ? )A帨4 D.A{@6BbQ.h*@C$zd'7m@ڵ)++m |Y11XxgnY'7GTưkiL҅^A-XBl^cn`tQ.dM"Y%ܲ2/7. ]? օn {'$.B^}Wxx'y Dq>t.09`Pi[[, VT ҠcLDՂPAvcեA7f`Hpω>YԤJ.A\/?E?̽C:,)GC_5Aw7PKvph*w{73s=M-D U^-)n%Ks$"3߾ݰ{KV-dOj ?BȈRlv~ee,dO]Q);9ƚtQ5]:zr;%lCX{4e:k`&|lɤ؏7VXmdn_# mȰj.A>xDX/Zrf^ SScrvGP'FU"/{?S$e OYe 퉲`T}mȖQX˚ (aX@ p : DJ>oü{ˍ! cѵ(#MVM3}͓'ПMaq/θ"a9zP@ vY<~~y㋚Z?1J5anP&[b@*Ba ]AՂir3(u:|\A#AnDA?DwoK=3#vAg,mR)ǟ RƗ]{'122|*Oc^^/ 'k߿R.&(Hx*dC5f'=JlIg{Kw_̈uJ9Eؽ;*F&wL9DrU)Y\o+:`ni.`I-cIp3 -8 aWYn]?*j_Y4-#mMUG^vv{@ex~ _nFOE}_kVhRڵa3C([t)~tVhq|-6j$-j$$*XfcЗZx's1 8q.JLsǭtB'L3%_raxb6рm`JOlJ8rlsc sm[h)v$j݆f!.#pB"9&`*cwc p|GK7n9ݻT&@.hT][DЅȅDil\zh=Zzi$y&Ъ_w K5$O#ZLtL]8tjeO NE,JNρ`uHj9P$ig:Vy0L5G[_D(Eڴ[Rj]!7Q $KHE?ܱu1î4вfԯH@/5q |ƉpĶ5LC=4x `wsLRwGtOG3?H+ T5rvWZ9|(m>wou}?[6Vm KXtC^زfo} ڝ0Q U E juz/qAW / ڦ׎~lZoGke\l-ϟ[2DrzkRdv҉)!I|:%2zJ?G"Uu M=6#{x﹚lȈ}R >}FѼD hM !FoT:/mP똼f@Sb)ZŖ(vS֣GATDV \W*ّnj}.pڶơdin58,\WrCZL*UsMv>צϓ5WYhe+ɏ#ew'v7Itv?w}Ry3_3N N]܊+/vM T߷LHvǀD):B)`%3E!HkZв Ÿ#'_t+yj>vZ³76UT`&tkznٞP\+^w77:nW~.줲 }$6JhR AB@_;W%L#F0'p:x֦E*%mN \^,&|wf|,"iz!^uK~dnL#]bҖX*[JG 7!7PT39s2"PVXɝQv~3"tS'wOM[J;Qu k8ѬڑHt_U @MȴyMy f|OK1z^ӘNʹNmTimןO"&>uy & < ss;w13qƵ0׃jR\B˓-|K˨iƄ'#APPiceBtV$*>/Zy0򫨚2d#d|C9tv.Z>Qbלgii}XHm<9vQ ýOx{_ _ԐN ,ڌb*i@7<tkn;ZAf*z:6]DeN??pݘ D˻)&<;sڂa᏾gG@@&*2HeGUASU o^g׶_Qs\Z\aC沗%JPڼB:PRKM7mzIFlHf}O/.?]Mx:ؓ*J?1K:^dd;:Z3D9xMZ,l@BfeGg-Oe)4-~?[%Riumm 8J}D؄EY% 8ԕΎHRAЦ?AP0-\H  ^_xXIN݌z%*"h͠ qA : :īG(m>H/G$3JFKDaV ?m!JYw S.P KO=$E,X$-;Rߦ<@ TLfZL{>*CiZ.4(ThK%="Z˷&ه)8lDQPX' l ](+q'Ϲ64xX#Q~u5 V8vH:<6Mg*40;HHmW/.MuDsS(kC+lYԒ)^eJcөA$Ԅ.dM_=6c]jH%;T S!-r3ync\L&|oWEKq(CNl6A6NJ>8C8H /XdoDK'ݻ)R`+8Fɼ-t]0P$YzS@it**=x-ьWζ)&U g2,31E?=ć|K,kxk(g5ʶ(-UoFr4BK1EFzb Aq:Cnz6T$Ú@BȜ\POs}]~qɉ].3WdRE}2݄W8[q3˔#a+*ʪx39nȘcSpjp$p*|YS0GτxDVQR 9K`[S,c* d/zszOf`s!:xQCw- ]LM9+}\ AP!6nїsey&<^%9Ao"BփPhGwnAތ}c2+_ Q%)uN*<7@Sɻ`\Z̗ "u|!1EH򔋚 nM ;Lj _WKg*y Z %Ͱ( Id4ﭾ izr͎C|g4Yv|lom1$!*/ZFx$RjO]vdS;裯$(XZݸ0>.=p4+>/tkH/2ZoP O4ƽT @ T\du@Z!w:՛)Lo&i_+0f"b>, ǟ BCJo:~ `* tDSR|iセ13xΫ74q7cڳ4'DGy2e"9;wQlI-J3FkQa6A+ c̣FX롹I(t,nOɁ=?oR_Gtv82.ƨfǧ}wϕJC-E48J})fB(i9LzJ[mKKA.> +IKOs@#I"*PBTE/cKB# 3j!+wR5,1 ~JhBڗԚNZ%S V 8.E.b9%O4^+JǦ 4B*u ͌˶"¹Rbh? _KF27<>aB|;RHjvL[Ph;Ġ'B+2V~O\V=fz<=1"02grG5񷚤ݥ6L?ieŗcjTuDU-o~vOHzsqLD &?,x9PkL@kt GLM*0Ņ7wmS v0Na4%]YDTFأ>G.5vO=,gT7] ] |g`F{ޱvJL_,%ZGCd!f-&DH\'5S_r?* |ʼn ё t9Bjyma<>+3{7}@$Dv7  γz|S,YZ0H?_{E[ jJH^<=-ip?6`c]{fkօUIa7 Nb;V]U %/(g(/VV/ŠM gv*7Ralr_Խ"pϞnBq4M눙Jd. Dky|f7;DE]K\ &=h<䍩.<\2ɴ[ykEo\ ǞC5#AUޗ@%@L ]<5:#w۱]ч; bг%X d+띞47X{xc`{Ftݞa ,%]."Fn]8B ],Rm },tnQ"͓hPol#vMe{\svI$j:e:#"6 nVKzh͌ZV9gD9 EݔRjYfGShF$,_Vկ5 <,3۫;^r_sLli-+zjStTߒ8X$(ɺh[՗质;}x\Of} bvm"U;"Kڡz/]*@AsAz~ڎO (QqEgǡܜuGƂrOu`9c$^y=S8uŋLf;z_Fe)F"q=pmo&bd\L!q1ny/P0g04NcYDtfl|N=&^$u _ V*W8D<$ѳiY9a ,M%u]8A l5j)qN_g xfAPcN[DhLP)[LwöxhLj뗖Y8C ;!? IӺe=I lVi/-O qm.w߱@@tԱmXU.m/\֚fq$Y*_+ $( ѫ CTloo1>q&i\XER%Yςg=}NJS.-RġyjD,/rQ:əbNȦY?-0벃CW j7(+;Жy'nd^4(;m@`mX pFY# @eJ'Oݗǡ?🝺3~NAORWI h7~T+0X:`gygyR v_H˦|J4Ieθ+)䃊3-RklDhƼ+,/PQ_E;8x#]}vQ:34T "_iͭ^O̞=a&ͬ//d^Q936,xE~=6}ʘˏ7^}Xv<)ގ͒Y8 9 t <. yw`b'p?A&Z򛦸- bߧZC T"%-*zGS- 1?x ԨG[4Fy`吏xYA" k*dnTŧvxx>4(. =p1";`=S|۱<ӡj9)ȣdBSG՜%ZVo%_$\|/ 1;R~EѴ룁-#EA2[m䖽Zs{] g-}X^i׺,UW'6-fm˛4nx 5Q%&z_xb%eTfDuK頴q3B1cp5O/AĂ0`Ed bxYO*ӌ >M8e( Å|w .d#-KWfy;:b8($i*vUP'1(sk~ O W?Yj͹ w1@erH^>rg;]=ޮ;o"|'D sAB$m=X>S V\g 38n؋7$"ҿTt7DD"8&jиbX C8сjy4+W<$pR^l'QP6|Ɯrŕ{waLO>yR}ֵ0:b]`_BvH$y,cn鲤p l1  1FUsmrߪuQMƲтn QZ@ғ!@dW(Ҋ?RxYF9qZ4[bzU rPYR>:rYl,kK'FP{ 7JtJq6~1 yӖ @+q1J=xE(ȿݿ!kfT/@+`5CwN3f#-9dI“}f,ByV%rxFIӺ6=%# E)ϳe*e(ƎՍS[^Iu@_St\MPhpE] w][}i匦 Ihm^xl>]~XNG Ȭxm:񹟮ips§+gУr- ZgN=3z^Uź?^̍xm8<P7 sN<-KSQm`dQc-:^Cn#+2b}(@ tY[#(3cVI35ŞUM46m>NG`߰3gq{}{Ll?]>B u\#&A>̦/ksQ꾸`뫋4AYA,.:Po55j4?վIH]7v ;R %__4:1[ph-zg}OΈUrE9;I`Јɼ|T!yэz;)Sn[;M/iW֟(f_ N84Ynn`zϚ;3eFA`9k\WEv:ƍCm(<))՗>C@`9۳fЯ~IGТu?Jƞ@eQ $hERhXAft2jV#GGݽ*UZHEZuyWJ'şMc'C!@Ci!%cOƽ\|Ip4]A6GcJo;)]!QA<VpUO> 0ДҴmah./- j[/DjaH-ܽBUs1I Gk&{D?=sY/qI*Ugw,zc,BJT#t|ʅQuPc_}]7;ސ~Cb.t:~No zP\9s]f*H߹‘ה|N}S17 ĖzS eUs"zpDmY@~4_bU Y[({ B9 ܪ>8NJB2Z; 6c2|HAZsBBY@q~Hvt6ށ+kw [d7\h$'|mh*jsS>RcFm[-2bP֯pk [ŠͲ{npukxJ -@:-АI.pD{buIil>~qғ{27Nnje ӱ/Y~tx_Wڂ*@=Ffj/̰{ꨵ{W,U:oq ~K>B;U>rH<@cB׃g402~%2k\18Z` }!׏:/|خܖī -2Akł^flky EhuR?ʡۮvX-0'#\BǧcIhlge"! DǢ#3 3)[ͭ66Au6< tc3EO Ȃ&I|p! !_sILi3FajsÎ|IcOU.-3QL-z5 :=cfFیg̋S066_R3ЉKh[QdĤdO$dIXqSۢ_2#22 ӗRkKt;Rr ى[a#"d|6nEKM'mR6#bmߴ.{0BS~)h9Q$hpQ ]llĿs[" t-*KLFw߼œiI5Һ\m﫝 -^z ek5[(Gt:bɋ5'zU]ZEvY2XYv.H\p,Q:jU.X a`JPi%G}fև.}w<7XۻS nHV-r˿!XÊ?+$JS <]es6%ꗉf)s25:$jA[ Sh6JZ|1_QDdbLb|Ai.|~zkaK ^JWCYyc[7|*,!t ~_ib7LΓ3;my_{r>WfkYdZm[ҫ.7`*'tDZuCz ֠5;ԥ#7cܻt{j o;ijL t\+Ȁҗ2-҈d5.!bQj,Yy zeK0*zLFdGUG*HS1"u6M(| EjA;^"$UI^-;&@VzD+ڞyHiBVs{ֶ,lƀi}2}8ڊaPm<-79}),+^@Pk~0o9CYx[E"@3_KY?ԋv4>4'vݣAEsh\8b ~4b~Q ,8Kͭ~7kaGNjo9vJfQ9+`AU&(3MSJnk+*"( _K$!2+.g0=B.G4a@,? /$ ^|$x4|9뙡`R0B]B\VkJ# ;|ڔ_Z-b[̴+59+?de a}@s- zCX,nF4m)` 0R;GΆ,8V&O&xp:z0ظN~*!gblu2JZ" z%709TL?icz"$sOH!)6I_=OuI%/]*;&PUm= #ˡM(D֌DZZd!H04=f12 $ɧf&6uXÏ![y6w#.B ~ rjM ;; Oy'O9-ǗneK*)0lcQfg ÑHx&jydc^ݙw%QԚ0VIA|֦Mbe544q/!6'1>phd5`ʤYƪs{R5aQ9 ±za/Ȫ=8^kk,8.t*h]>tDtQmÛүO07$业#vBOњ낙PɩI̻(նd2E#ܖtbI,9Nnbm]b }dۙ0}ޓ W}A3QNAaBDp` q*!NYL;j2>hč(NzvoP+?[v+ލ&xw:ӵ`Sȧ^QރD}P ͮwV0wC]ݎ̇`1 ÕG][o2bS);#X2اAo3*}SyDrKPa)CbwX!8C+?a52D>.<4)tj)Ľ5G~Ir4j{]gT%0i&lNjW]dJT؛ ]n*SL`2뿎kacq 6,Et8;,K-F ]\8P6 HU5 lkt-Ix Hf^5]*DGwLU-Ua(Č\6C ۿ ܟaFCWa TJg+}h'0bq}ZP*Y'VkK'- '_*և L#G=˜ktF`-a4&@{940{O/V=ߛ;Tc0m;Cʨ+ XŹDA5KLj*=ID|e5V)sLKLx8&Rh2,:t|HOUZ $끸4{w+9GB:YiǕE7<#+`?A6N=XwP31 <2nTE ~ x)[Rg/9p.VLʊ`8^;6hi' mu9~t[P$6XT[61pBU;ao+<ݵSp NՐ\h䥣6vcm'6h52\Hm^IB1P-Fw-؝@BܨSB('0H>0mj]"mACXhڌ6@zh4DXsothuX<ubC_ɯ'YoE-B# BtAM ]6ͅ6:TIÐx,UEoq_[xvH֒U(ϝ:ZdfK/RR:LCXv ]mF! q:}3hݻIKj 0eREyG \u x<d> 1 i2Rߵý-V# l)ѱ9Q}ޤhuSzWJ8~%hBԪJZNn3rJ o~5($RM-=^î׼٧yMA֯gÐ1CNT:^t~_7?:Ē MIHJT=NŸ7IlMBB"r8N 8hik<'_ 7ɵ.P>/?4\7tD{e1Wϥ`VLGZ-Zo4GxB>&7ȆjT\fj'Je<]&fN;6S٦=Zvn["Wi k,]{#]_.}!'߆NWwD?HGFc>vAlg܋(A|!CV"6Њ(0=!QV܆{И/Ldv4@n(]!m6@X23Bb+: X~YR˶fۗLRUi`8H˽ʧ`IDan!_8U8ƶO=m ¿w%Bo˻7]Tɦ$Y'raNY!B U,>X˺g\e T[n#/G[ɴ_ΞWDF>nѷ[R]La=hՂs"|1$)>+vv*۫_&ejM4yHyRA#rНj$Z=077W9GqΜn[>eeb N)jGcVAAD[;(MJwZ@>N=h^: m[C:^מG數8i>@BĮVMp |~dn) CܤaFl&6?% JoZ\g.[XH=/@?Iya:e5E E~FZE}tbGmJL֬)2<*oŪx[:TD^#H(V|ATh/k8{M*2pVJʒΒ,fAU'?-V/*HB%Vg3x_=އ UnZBJ8 MX%F9+C< $_ b ع'$? a,u63r݃:bT#eOR/Y~UCjb]gghu.8%K#\J*W6fƓwy;mBW?`}2BͨTw ;ř>tPgINQy`Z vI4|apI[Guȑ`f ;ּf@IVZ$aqiɒP瘕[l]Ǻ6#8H8{.0@S/Y*T ?uf @@/H xc|C\G;HpZh.3k{ $npq6{K*^8rm>@YCvxv6kbjEX>T["QcL YLͯ&0GYQpI rL0`tIrƳ$뎈)ܒL*PIoB!j}H_PE wBaq m#orB4'ۇTR ^~AYG394lKxgetyb/6u.G0Ң)+Xb|K<fd)}K4퍛aƝY vW/hIky@X@CR@@+\Zťs`T㙜7m8&ءK%q'd1f:ü3d)tfxJ􁥿a#&+Z̾6%^4D.7@1Xjy7I:j\ 8P n1[H;1h[]ȼq*"BhDHB͎πW!4Јg]@_-4wQAfZ`׷n\v@ /;oӽ"S!7|\0ɏܞ[=ox* @W-۞,uiac#2ŁS'-rc[e \Ew]8i%=O՚Sf w=ka3'p'zlP?!Xf%4 ;8t$|gTBO\gӟKXKØP`NnST)7 g[Gw7Н+ډzPSXVK0 ſ95"]6 ?Ԫdu!=eEbӉi65A;]QĘ\tXH/.Kl0c;?2K+S2#Vw[r1Im_M݅J]X1,;Ac gGF%~ d02DJyb*?Ԏ0YŽQutB$ .oZ`gw;1˛X-HPS(W>q)ߚKr$+||P yxc3h! 3w7#X57Js>2;`{xpOۊ&C.t$(TK/*o ȢH|}|~<o+Fߜ8X+DEԱaD.۹V?/O|B d99fij+`"{Fu# Ӻxɑ6><`ai)kI` h0 '=VZ_'?cs>E6 V ȭ?s?_&+6S &SٛdalYT[ַ ohi^BQf >fվ°;&qw&A;=5:\ im)$[״PbT$_O`Uܺ(g~bڂ=Lk'_|kTSq0(~ tDշ9 MW/<;c6.‰Cgy|!<β} A/BBz~?>S*EVC]1ѣTtړ&-ǧf;>H-z"VnMN#=% Z7[ x0ND&lg'N[:0#5 'CuD4u oyA|j 6(:ki "@!?-aH}Ңp;5+#ꭷM83WT}adfIw_w'z⊯xGIK ]8)4 z&tOD;KHTǬ4hPqi;䉰rz)O6FGq[%Dj|? 6}qJy~H %X^> w弻8P+ؼVu, Tbl T^f45jG7C̔dT Ujܹ Qz ͅa #ajTf٩EuRyPftłp:i$f\ыY>Uq|0BC&BH=1uXV e$EV&9>LG%}R3}͜nVTru=̇}=A .AD\@8ca%dEJa椸.T0v;!eT9vX "2XG'1,Ꭽ&M3_z8bxW>PGy~nHw0eǂ`݊ۋ2YЫ`f%.iiU@R*H͑XK[y/crT=44Ɉ"1ы}a}zSzUGIj&#+o*K)p&lwٹV?YسF0xK0o|p'IwK)X+&V̐5#c}D喈kICK 'NxfqC@Hbp?@5~I8hoҥ_Czm,JOq"ȏY쭫R i\{"t;wmh}('Mȟu4r3vBB*b|-S>HsSNLFO]y#렞-y~փE4Uғ620mnbQ*di jOb9BkffVUzAcg8|sXzߣ\hUrYҰ~h;ÜǞ 6m]T0xw>lQ15'{w#^83y{3n !MNJ(1h~0Ң݋;Ͱ+\0caC⸩-PJ!UL|j19 AC!zj,QqUP* L4%_pq7 ?YaZp~y LJ^It綝n ^Ufmz}8hՐv ^$_lN'~l`nt Jvڍp.;C۹Fܾwbk[\=M&;^YsaaI> @`x2^|sQ6H_괦Itnq9:f%d3 u,RؽW[rM֒sBw[&"Tro8ahf%GoFBz@qDpkލO#Wo-2/k pAT!D5 xgxM4Vt-nvU0SurӃKǏW`ַD3wRplWT 58lu/뇆_S2Ds}tj [cg aт@(A5XY_i M+H8Wnn>U_>PLS>4ObYm&'ߎEmwq%X暚<9dV;@YibqX(  Kq5i"ыߐ q%T; +3V] $FTVK:ʧ-k&Fx|ڦI1C d =P@L&Ugz fG tҤ&:}_{l?$t\Q%CF3A+ LjD(A,GϽՁ 3DX)*(zQ}z1yލ5K"geĢcC$'`*J #u}RǑH|S&81=q> 36VdNy͸T=ڛw7=Otq㣼Z .|)_SRb2hH?/FfP3BNqaa j"ζ}6k+5]V3j"8!fs=#l p?Zw߃C#HC3!^!=N G]6E@UeͱqI۶Zo*`(J}N\ 观QW3uH=r):T׌4DeASۭs [% 28"GAwM"̴Ϥ$R fym-+`OHNvq(5w{푑vz [BN v Z ga< lXll H$ՃfGLFU˝db5(l-<4 GO($ ׬x|Oa£U]3ʷ;JYBKZ`AF'vuDf5D.yOq ll1HqUND< y#BjLB֦*yfʍ|̍C-kx^:ax vv8at:uަ[5}OnkxFu*>R@RDlE ˭{GFH5?)8O`nGbԻPyB唥d#Ks 379#0)6Z2(lE ]fY*A@_0_}y7ѽ|񛊹38XXe-lZgB1]'"<\WVJͦk! "{)pfboޛFQX <ӊb h UPTř5T)bJzrR]-Rq'}JuiCTNϵ-P#ِa~ XGl"7WjNb`qnE/2 ֈ^ج9% Rt˧kT#W` QF JB?;@wT>IV_Gkn{ 岓ft8Һވ5g%AtNcG 5=TdGNΌΚo@ iܤEHK#̉Εg岻߂rG\>NÕWcj"eUW+>/MAE[1*ZqL2mG:_ͶBM. .~bĻmS8A5q8 pw vN-oOQ~U{qjTxce47[j>K,ShSK+UD'mUvrӎ@ܝtyBmcu0.&K?> Yq<7QcŽ}Σ6A\ BU$0$OK,oxA=w7ۮ3VԦJBƽƭU|1k ܂ȳJC O_KƘ[Q'EUҰA7j: p}}N'(vr6?z,aB-M?q6R,| x89Sv u$XO S}I!)g8وzQckq}3M1YϭD&{x֟DQǚG;nnA4V\>jn9) ]3 !{.ƃ ؋WTCy\%E3E[݆L@`M2 =? ރ/i2KS&~8.IX.ٰFԬ& 6؍)X Fk# M % ޡ`dE wN=&:%!3 Y[4XhJ93CTB2Y&JK Ͳ#s,Bea'$p(J Ip$9oP]q >E^@o+$vMAEj'c:F/*,&>$9ܦP䶢L_Ub+[*}libmm=>=_P;T"|딧{{jRjG-D->*He{-̊~yYeR.vy eEb>qI7Kw<@t뫹4pl z/055SEf~ck2Wcn1%T9O5s /uxXIWep8T8oN`Y1]c>]UMseP?ߥ-w7JZvi0R_P.*n̐_"}1[\zs\*;(HDҊ?88~tʭ9֏սL_f$DJj|\HR:8WXX=q3E4z{h=,Y:afpC͢ U-Ja-H qII$҈' Xq<ZctQ.&x,/_XYhgK}B7Q,l\_֭Mdt-0Cp3Q̍Hbk>>%/Ҩ{+#TȴHl||"ܩpsc~Rjz&Cbmj~@h>\.gȼzq-}MDHcq%QJ="]ҝ~=VVך>KJ)>Tm86 g<=Oܷ%u7P\FAP݋ÞWԨQeR =|%44F8)1$BJ#1.Q>r\&G#>`MX\К^X `Gн}>ז;WˀrOaX^q̘F-h g1sqpE)mnLDĩ}f`nRn=`<=c0oH5p܍[f>itrswzbώ6r߱}#GLwq㲸R$ދ yki˿wbO..SqwS~>lеg߼pϒߵ8E#(Ħ*9!qjToM>9߆nU4kL5bR Ch:PyINNذFP^)eoɍZHIhn ºgZohTAk_g)?"˓acI6d$4e{j&ClY Fհ:yw76ʒ'I'#Y|Tkpy dQՠK+hwcՂ~-@7u9suӒaG%#\SI|1R C(5E:]Jo 1z c?QL|PAdϷJٙ|g:hर\ql|hMբs+꽝zI.Yao=>OUU6PO&D{x ܢImnl@(ÜU, y' ʖzUr3YO9g?`BG@ _Iy !:AlۡIiX x+Da&HXwG$_J@o`;z-2\# ^ + oE82~8 W%IZ')'5 %͂H&l纣?J`+T7.heT"R@6\ڸ[ծt6dh6f2Bv!MpiH )$TD R44ɣAܒ䓏*$ÓV2y}Qz潿Ԧ33֙yGq[uW?xn+u^ZB`Q~՘@? .j.ӵ-@8٥+qZkH*) c>vuyUAL-؆t/k^nwC1fRϳup (jA4)8uCTvZsh!pv]*NCQ(Y2vvEMU0B^ {oL\M5 vn$*BKxvxb' Xk\d,90 2=i3q$E'i{H r=t]jC\V})v񽷱{ӻdI|-uTW[g%.^vrss"m&F>ƽ3R@-z91~{u=& Ķ1"3}ctnvv[[?>䢺i ~M48X_^1Ngx+\4s?:69q֚TаœupqT {P(]B0+tw-G1# B$7N#7-\cdž]x>e~Q S ZtZbb ]DU?Z0u;uc)(F.%mdZRi>c;EqSY-P^htCM֘ J)[9;ծB;0Y‘WHQ`yGDr uIwEE\bRE=W~|&>B3m &*`Ɇl %Ӄ0 f Jܱ7Og<4Hݸܣ<}U}xga4C_,í*iw 5 N)ȇv8o y):kLYd`2igJ~l:o'0|0+Ecڅ l+*?+4jW: ^BL5&&֭o_.w&qYL!0Z6VZi;*M`eSuQ uiuCvͦ(6`8A<3s̶IUF]Bi?O3@Uyz$? 3H ov54zbt:6KAVoǔ!|$/6?ͪ;n78) WW[w'׀WN.4 Xpup1/g)bu(Ը~im`^2{ n: Q71+́2m')G@$DV@(Cb S\mO[j`zQ_RKNIۊʀr0! .8D )Da8}Y8 Y瘴ƚKlI5aD!vƝ>9R{#4DȨ-Whӵ˩.֘WJu8?gƩK| /ݗ>C|H:jW ҃*^o9ae+`d@A4C$dߨwsí x¨ _nzA*aҕ ?8sm!]7DM xvߡ|PE|ٙ5ڍ|v 6)KQWLK=8k"=͹vnji_EֻM@vHLq3WC 25T̀mN_R}0wKk5b'~8*6l+i88%L @R9&}CYe?pFwa{|  G_8y!|g5jSHvJ2p+F"I{(j;3%WAI)U2A4=qn#"˒3bfzƆùVb?wo;䓒fXs^x%G2_13_奄B`NH5PؔbܣL?z7C%!MI6iDrt󧄛N/ar){B$o."z3iOx&rgk,A(ZdJI_tJMmZw( RǭgBdXLAJ:S xϫ^WZrw,ɹ`eoKԫ ̲Gj]-45E7N0@7#_272_E")CćnpA2SB?eorf-#ńlf 6LpP$̀7S< lU0*l|&0b` eDϸZhbMi(bgY|DY('8g;EVMwOcBblV߲7㴺q_dgesdW)nD:zNv.l$òDuŐ4q9ynSEop>:|'W?7stR91wʀǏ)d\ hR=EbeB )L89ވ[P .7&O#Bt~sJ!{cZ13xIs6L,<`eE4pp@ ~@(9)]M6?Ra:5n`i2l6ҎʖTJq.<6ݵu3dPƑ HraLC jX4=kc#+Ef߉xGS',prvU']eքy<fV@TDžCDʵN:ś͖`gir6`1Ze*6 K0TQ9͚ 9DR%Pida=!xeǬ|F0 ؋a1l#c)vwmiBIu[@Ò0=9 3[94ڐ'0Lq"? Rfn5'ʺ:#q4w<ʃ<ܠ~B ]<϶b9әA·B Hv[$Z`˚N嗀Y8uX*_~u>#fdJƌj诐:A"&yLPu!,Dc;ENKHD{-'Tpl!b+~0 D)OJS֫0)͋E+|Z̚zO=U\PahDTOvU%6z^D:Wp=eħNѡRڒ01!3Ԧky8#GqAzuY/l!6\VrK̬s3-`~ږ*e7~]j%͠ZIך4{ᲰMXAmx nu,Q|: s#2fBP1Yj/vOP\dsev@0JOwI YL^R>F6 E3 X0YFIW6۽#[\ }pZzƍ"Nu-TbAX-fn bcuK B d3N8$(+ 5t\ϗsb0  / , Žh ߮ƹA+ZT7!#]%9Гd⥛U9?#\ǖKW(FKo~K@TY }oFouai .i)a %5\y8̸lf':(4[zc(*\L!nA>_&olbO55r<@D$dWR6gg?[@g?[ i2ýt1b :}`]"8  ;]xa4مu/gMexvu1Rj ̱hkmpby@w/v@ Т% r#xe{bW >V2hEC2V̱]cC~ocZ)s2y2«2 nőո!p,'+=TW}gL@h^h$VD6g>=3j)S ,x'f'C=Q}.^4!]s`htX5O|J ˾$&w* 1`f5_DͷI8$jy9V-+3]YR䐊1Qg }ՅXSkMn C~UAH[Rǀ@ZBILJ TYΎXϘtSwi:7p+6L7e[>@t[C&\$/9ׯv{`.nS4#u8phS㘇f*,8`9}:QrbgA*ozR 7:JU2ZX9|oc k WǝrsJ//iΎ^? &Wg <$wX; 9d؍'BޤBˤGx"&sw&qnt^O.(sw"Js|~ ͷBuXn. emq_'9DH^.u9[LPKWڟ SX^#q>Rn66 ܜVã¢R1֗_"6y]U?քːzEDumɾt:UQDٸGUh:RDCZ9j۹ϩ=M'];[gp(3A wZW")0Uߓ;17a]۽AJ5#/:^):mT#J@k1"ndя p)s,"dY;zv1LL6+EA/ (i+hEra{krPUA];m֐qu ˈW4+g4z*Ix4䌠+ |`Eb՛f yLa?v{6Ԫn[5@z4ࡅ#v kD v],?A<& k-:ȣElZ1[?? *drT4'CZs .`\e3"+ɞ#kCEs{k6fߵW8DJcGK+UxՊ rT5`rOڪpt'`=XӨwI}]yP _ ȉdJhУbm.4 K ι#0J~9 Vׄ\ӌZP2 sd0.֑5+|r9`8>r;B6F˔Q_-% R/9cBվ4ngXԟ4wPڌkEh ~-1Z ¿LZxoQ&0-߈x/YI˼Sǝ e^SNmtI^U DKǐiq EG5S>P< S{5NSG6e¡R' b{{Ҥ71':{tl.Sn3VI.(鱥OHHtBĥ5du"ȭ!ccbV6RGG$SIĬ?n#.M 2=Rp>>*"57գg(GRJ&:DXOեD6I?9"Q3v[L2oyEq Wє*䁫(:#lʛ%ER\>j;I/G׵UDZ f dpTgEΡq`ą/°Idl-z(3hÕnFhJ/ThPX =KD3F^_WZEk uqZZ s>Ҷ hzz{?>7~}IhE3m'URw̬k!ީZ1>@>F074F_)Dd+vruZ)6+v<#RHp[JV-  _"mYW[\b[oaJ"~“Dhek#{p:Y,R` aLrL+&ĩ_S20W!2Xr:5m q(U:^UF71<4/bSq gBC2́//lp52}Έ,odf2h" zT'}4*ik t ߉a쑰qD88Fgƌ@۔&Tk"IfJ,?D9 +*kfג"+-8w*oŔ5&L1/ldM^abD: rm$A~&;zCYHvBX#.Y D[^Lxex~J1vdUX࣋^eSu [Eay)Rl; >x8d8mC!i>mgpiM(CO(fVؒ.IQjti+Q8\AjK소)NosÓjyc2%$W@:m- <~45e >ɇ#,aDOŴF+yuYJP修/8iUHT]cr굷$NUvK޾Ge \y}q'ʺe)i^"] >hZ^w? &;Uz9Do$zE hjRtQ46Ze6{Q7IJ^ Y4uHh_y+$lU5:ː `}}| WWI3>ʤ﷯SQP_T^/awk|S죃:- ih@ѥDg$M8 km䨨n񓙉YsYw:͎HIK.԰ %u 5P~1O 6̟?c'nL-_J)Nt~{- OH°g2a[4 Jhpy !X";Wx Ū 7 cSӬiOJC*цDŽXޙ֣]n}[KΕ1OB,lj *zJT';QPPL'oF,L`e\H\T+Aٿ`&pIS -' Z9 *Fn GIWnmfww-9-$*Ѩw,nvpA KZKઢ NYR(hu5lmScYjُ.`N{I"j N_˰ԥZ?'[s%nWt0'x*t ,w.8DW7*7b)._)2ݸL5? s>Ej|~R&HȻ˜`r-F.!cJd$ݮ]}O6cz([)c<Z6'FISA6= o:<b{dylM UnI`jdzF7\~ZRv2E/+)J^^\5q1E 1qgxliӋ ꡜNI%c4 ^/+(i)y*=3LcV] Ao h 8͢"•UNv-jP)CEDoV0KꯑX{K0 $Y=I7i_KPG?;l7% jTVMtC/F\.z VU%!k/׼?tB_*XL%fsdW|IW>񯐽˂ tՂDy9p<~^k hF ܻ/ǘ8I\.0xL;ڭ<+VE?xa']1i6O0`!jaq!u̱CO'd~g"w;I~wҰ'E}7fā<~ 4M?Ft hwAB7 ;Wnkrɐ"4)w s#aCql8y=IZ'6#DgT+ߢVwVs㉗Sp{R3k~q,gJ>{1TM 17 -a67Cg JF8X!jeUNL ȁkiEE`v#;B7GNWKvUmB֎ʄ-_wrIכ"(9ؔQ1溁`E390+9Sv(ߐxʳkey)AꂔbxA uMGV:>p~ٮލO1q{H $2_]ì6c}{>Y W!u$J~ 劧cZmh?1c6>f.B:8#U@X[c 157{"KL+70\ [^j6[&n'<^mz|m]ug宓 K"/U?DK7YSZ;Fɏ+`&`VBVI w̑ژROʕ. 6L wA,piXdmnfν<vovZ keAv> WG&09.aw뗰Z w 9P1@Z!ng5t1b7yY+;I9vJ^撳Cs"ow(M|ޚVŎ1wܧ޾-"d~5ڒ7o'R,U`i|<r-paLPk]ѩW2M8q:69v.P"QQ3~wkO#0UVkx"a_^v0/AF!袁 xQ|fLg:8Cl]Sܘ%BG`;xdπ.ⶴB vsF} pޏ}@v>-`tf}(?&GWq \DL,j}*MV`'Dxlh'; gCSSC#Sv;ϼr~*s2<6>5Ք'9ak`_qꆫB6DăHɣ ic$Xld-h?;(Λmq}39@Qh,Zf  B˵ڧ^*{#y_U7M!bS(T``~$wѬpTF6mSb=x3UD6sCR0':EیKp@H\bPYtn?3&N-pF/aTC\踄DM}}iAFv]r`樬Z pjnc 'ņ?C'UMIhNU m&~sn_a8M4) Yѷ_e:) ĤMUM7:#$h[~Ӑt 6˨56 %(bf~aWwL~G~ L_fƐ48_s9M]+Z2w!$}Mn1x\ k9cq׵Շrh0~ |ʻ>oLˤ٬4=pjbJg+ 墝ЏM0]@f6gW b\ۦ< 0gH uqWDK k&\< ]!>3jo \@jb߂4K 0oN<o!3I6].H0*W"ke% {o x3﯅AvN%H/]++O罓uY'wqsLqYY~ܔSEycA6GL=N]OX6N L|Q ~ji2aC]O]bњcឫC%F4Fu]|&Ja&7^b^z] Tph☦8=pz#f'XYTAGrȈ^Zr'D5;RS )^NM#6eq b ?4=Hta?ߍXaFOOwU2]:`io_T%wi7?<}m׍hxSрoe"8ٕurid[ 4kh†zqSxjYR" 9}RӔi3)Sf!Qa tU,'kg]V}{5hZ=y7b0-l#6@}좍4^c7^H(v)x&C(nHurM)]20pYoR4"ٿ?H7un䇎Zk:ELFiޑʰcvly>l~T[{k1uNÙ[l]L :IfNKT^H h9ȍU[^RHz-)i|V%>5D})ferdnm IJet%}ױT\kWU!WI;햝j^!%KŷVԤ=%oMNn[8GxF5WOJ4 5'1vs[!BFohN^4\goTZnj"nEzC_2MKֆ.~DΧ`e-١cq> S}jN)MTeiŒ5ܘe?z:q6Ӕ"s%@KꀊV%΋ʗj¹ke4[H PBe2~YEԞUO9М'X`*7jptw .O δl KV(tfs 26$aENkQW3I^>dW!͞I!`Bw5|}t—wzqfa-%`>-cBLw<+ב%AH&q:3y? jiX3,qM7"\yvmFX&m ͡S?$b~B䑲q6jUߠ8r}0_@ Yk+Q sҼ!~3ΊH7jg8c$P[2fw PZ:$:xAAYwfgX$TEB$)|Ԃ1LujhNf~kt`hڪۢg:: رrWK 4_ZxS۝Fg.Ϳ}Ji : cTD2x́‡)?8 :c0_G~g(gA3G6 ̀=7nF_6X M(Vߙ\DL{e8J$5_ ȥ̂Šת-aҟڽL] Bn>I:jTRg *Z;1Yo셋!9|w?KcA;rlfۉl]u*ͰOhGQj1 c=T:1!%m($Ү6Aңq>J/Gn'hU~4eٛzNf>4 QpLssK/"<60'Dzn+ <|0 vjO't|s{sV0X]5} `JX׮^^G\@ mG%+*R~t).zBK8q|;GQY6@`pu=uZݻ$I#VG#+375t{R~Iv d+*GNḽׁt ✟uL[ST(\]vNB9~MQ/G6__ΒH[IGӝfh PYSO>.>_9̮N @&\6-hɥz^ra"FM"M@CrZz}HvxG2[2\5y5Ph^1r#q,=-b.{Q_N%ON%ţO_#nF 6:SJ8nNH+QfG_CGMb6-(3 DgCr%0duhQR4'~iB`=ܼ"C}ݣ;uɯ6FZbk((+FG<5}BТ똟3i7{~­z@`g) s^~=Q%q9%$Z u(ǃ@V)&P[(:$8O6''˓GNL`E*Y@4f_6E2uk v&>m~o1I9եsĥ91˽ك3t0.൑{ӎtCǭeld={Ԙr}U>_naFy|͑bFұ`t׿w;Xfz)M)rp) M (VPP+|`sGC41fɌno`VɮY2RN#ɞ\hy~.Mwژi =Ӵ8wjY}@'|ç 5R  @ -Y>00%g7>T'^dܿ˭\ 7ݦף4w;ᲩC@AD60­HBYaT"~ۮ*M>0w^st8Mhc1UUNsشl9jlLL}K+ŮǠUc>:Qnf<s]>0* g=q=`Ö|'r=R|?) ǐ-ބ<11 mPߔOPn c=ͮ``4I^`m7iM +W;V n!{;Wp1 #z]>jopvKi&7PؖKf" X g'w!MPS_t#X]?V`3V, ᕒV,,8C]f,ESBLbwi!Ș$}[ 32m@`Q SHr@jwN)d.Cr4+u]JZL]jSj'0{#Oc$L_ sHӓ't `S%O| ч%79E  ժKe?u|FvJ6(]<,a* ߪyTy&0Y\Y$4ʁ"8l%w_q'UPUlFr/ç'4L;J$@C&Lb߉`!D &udDo?ߍvzbޟ{q'4zu mJTg(ýǒĄjJB7ܲt~=}[FWeM# !(}LM9eY_kB0y|F}<3o5;ic%?BNJ'O`k{=7Bw q;`[E_Ė/KU]KsSK|_m' HoԆl?2FKgT.\{ᄍf (hX3bz`(_*AF IqƗ0 m|D!k$a*4Zs6jL[Zu4&8,X=Y#ɻzܫϯ#]z(wPS|f!GkwgSyDZhM'UAe$:k4m W3$ϓˢgeքvr쁅Z<{| ɮz~J1ϴޗEg/,6FSaf.ʷB~J Ȉ#F; ޽JR'@b RW+߬]ΕG}3 ~Yu{-.s]j|XKtj&Cƀth13v|Yni3q%^0Q@#zb>b_Pkt>`bݴRXX#¿AEB't_2>xQˋH8z O:K Л dtㄤa;q\ƷX mw||7(p|4tdEi%&1'|{5x |KCShܹOߍr ͇_@N_ M#,(Y^BB}Yo-X7© 4Ur>]! xh#q==qU3ۢOSDD%3*XB9C$jdvA &= Z Z*|OOMw&%4HgǠBeuZ!x\1Z<=@"_ic Ppkʉ +x:ák9Fpˆ<gnxkCq+/PHM4o圩rbSKU+V_n%!bsw%~&/Cuo#+Ya\־U)2'z6Ygb^߿& \ N,R=?2w(~՛lGZ߸DB|׏C@c>V])?4/6LX.n}\}Fϰ[E{]DPr{9*=Hhl=ς@moClʭ b. OW+/13Thg':L6ʜ _ O6aŲJC$#h$Ow磡I\)/Op;vT*m%1qVn1_1!5z#'bA'[wc&/$}Dq# 6*hN%sSRvS2X4(+&AqۻI-kJ h:;鹿 }B7\ce36F8oMiNbSkőWvb{M$eU 􃶇yoYD*Qƈɧ_e/}J@^‰Rn1엘CLhŝGLǐ׭>UٲfoS7*g%oJ"=b::I|&Bh R[l7MvG(,5oLXw#E>l2@OtRٽ΁B[y.OfmS-udZnYi- ,BW)9GWL>~CIF *}9Mټ ˲rŗ6Y}-Ӆu; {g|uXŕ:⹻5A颌{;6ayj##uUvj[{>. =2JːO!98\67#A+Ѡf~'gvHƫ7NL<5/T\-:UIz7l`Y՚8zsv:apAAcRb 2bQ`s[KmK//㈴g: ,TqbkUL;_Jzm\]\.̘TYI@a7(ڔheqSw {+^$'v qxC?4^~$2>_ H9g$SOɺ2_ƪFvP4j}6ڲsW/d!`[y&覐U.'=hpR{.vEֵ,@ c}YּYELh&lԋpSATc`L ·"]U铝K ̽Y}Zu~}žҘ%cix`> qMb5dAvd9jTLJl$UqzAf%"_O۸˝ 9am\:: kb̢4=|D`ɒ?SQQ=[P%@&ek y>TAκI4(f5ۙ³=#(BN091t}~v?nct ]!1I2yFqDtyI]3$TZdyc,yg/UEw0e3He2jRW ?Qa(t4egm1 uQN[FkIt*gMĸ9Nψ]:4b4K{2qc8" \6TBw% h2XT:+gzFUAxm&&H^003$c{70k@W{J.zuc:ȁ{cqe2ok@>/"0hGI u6`$*uB~Wla:A6R?:J_n=8unit8o *~,Vh[^Ϗ<=@9qB_żP>oIM)(b$_vS^\@n0;}:1_F2tsڽ" 5X|UY]Wc]\73E"waI|(Z$!Y x/f{7^'DC˸ui?+!Нn`?sN;y}nlkyjx=j#.w6 1uL2"h6?r#µ:@S2qT'o>'j̹Y围xlmYBKÿ}:8)OF.>y!1*h.KM8MX+TouaUb, +voFF)%xh 2H30T(B=ޚE/ߧQS> 1ʨũD+Y)ޮAUU$`YKki.NKC 5oӮS1ZcTiţ5{;2Cǐ>Odpkj[F J)BhOMeZipC4̞k-f DFm F*d56"d+I240 *iUX*##tn(A,&Ԁ^̹nD [3jщZ5Qv%Ƈ!.πh}vܟMKæ:m}Ҭ\oZ;żWsb_V4}E 𫝵94+0]08V/3^%ЫY84+T~$$ PҌ'ymdœUT~ __dҞNH<q3JYs ?\ys':nśrX?!k!"DOr|y/4k#ں;+rΐd'+ vjn]v|CFN[WPTӎ &]BOܳ,z*qdjorhKt|ZK 7k3,,F-r~ b} m&3ZU>rank@` Yf\F#5ӯYԾu9MvqQ6CZ?T+Gk]>W+ƺ$$uHevĉNp-h"{J * <Ɲ`ƪZa2-vQ :))D3+0,.q K8_NԚA>!bY.UWyuŰfz~oOU71Fq)d 1 z\,b!W&M|ke&c)]XXky.ʣu% Ld1?|S+_MSǃ·kyMl$A)G{jS|NRNTZItÚ K0mB=e[;҉[\jOR$ mdf0V]z e(g9S>0pkz̺qP% k63f?3&HG;i/^;=;^ySg,i¸!n/`2``Iql^:h9),kwg#BݗNͽ4l}LX/y6F 6c% }P:XRg6\2gHl-:xzY3hxhլmQ {Ȏ8U5pXZ&4˨*]0MlּFAJ6 j$h2#I.(=ZIAe13Jf6tZ @o\ILY{3D (/x0/t۪~2|F:5" 7*9ըcTx"[ rɇ);(y y_PwXC:_t\&m_jk=U6 憘O?X]0{wO}KF)e5N)43RSfAX)~b$CDa39=+Xnzs}8&Ty{o OV":ҷ.4,E>WUHwԒ܆P:W/Ld',wR^\!P?oе\2)(o2JaI.~Qg}H~Bz:5m}*ܥr?DG>Xg?i$#lwaʤFh"fY JpF+J̓DQs͑aU"cA-)TߞP4sg2[X\Pي.9gO ԟ!A+?Z;(wWTT>m`iN oWm$Oڲ `YyDt+@ 0CGL{e]K~Wgqښk3g5OP7ȲE>ةb^Rc&\< GB-YfwFIza c =_e<ҋ#,@N|kWQJ#0Iu[sbLmօf]dH|yJNy{m+v!eHK&UNbj;ƊpPL6Z#qY YG"]t>H= iD=괽5I'rgz`㱘5zO͵|q79[dH#]| EϮM@=4'^{AK^t YU #C@!B2[vк 4UdQKY9ľd߭8Z%srOF,24hSrnlx"AjiXԃūiAi޳XQzb6b|*sX22ܑ*Ȕ>m8Gʙ@Xg5Qukc DᦇKɆF_yE}82RqBBEAMnPtV\#`BD_L3Ez+ 'CrmNؓG78xӪo۔\Z=RnƯ p"g9('UV$2Y-.F[3:$(tpT>D*˶:-R,l~2A<~lvu*?n#E@O]Q#g$lNQn{߰ә;I&TxϢNg =/{sn39V6R_DQ "Whb7HGktsF`%Qk%ޮ>}D#ȕO#.EqV t0Aq:ڗ%i.pȋbC!t+  /?9:> 6/ܽhq.6y+x:A: q xc>zA%BQ8*:DἜcچF Vb|XKgiS7b؄m-eowF-/fG/H0Sɧ (v~&ۛ佞 ʂe:e6st/^EEn `պ/V:RӸK[im+w4Qk >bmwd-_ 7^칎n0h-{,U.$1q-| + *IYY0ΐs~ܿ5& 7c&L$ձ}"}[L~!I5n=e'.N2Me57XwI(b=u~z}<=:l2o 3uQ5LA;9ȭS(WWX7D DHuP %I~~[*i˄> H=ٕXgv 1"j͒㭒d4fAxBvQ:.GyMHEO>sO ^wU%?D{ITc{*Ϯ8#zDPgNj0˧eLb! ?%8ol.^qZ.D} ㈊؇'Qa X1 `3Hd `** bwG7֨Or2 L\ʎ:x^7>H,WJ8{~~S#;O;V96[.HC%=* @|̓y6`! T|_wu#)ȉ{͊tG3w.ܮXpeaZ^ {=7~š;bݎA`\oօmt3@e1Jm}7nu,^{c{Gy.WA\ z(T♳X.נYJ ĭfcBa(\GsRP1וp7ǥM^Ȝ66om'59x擾s!TdF7 ߜ*uehRE"FHZ Uiv{f3iH6wF~ie_z[VrU/սl, *M?{\Dw*w^hg6gAhif^|{TUm I֜gzĊC;oJNzĝyxmVi5"?/B(7&Sl3͹ S pvUH̳+?8SR1 IJo>@Z?~mvآj37"P{,t, Fl elB7) cGhl\e/ Rn"B[ŭmUhXU)Q ߣX : Z( 8]6lwtU?O2C jb%Rxd޸jF-va{۴:Novw4YKU0F(l<6:uHp Qa aiuHZ^Ћ=@w:̯vɮ76>Bu~xf~WvVznfG:@ @''%.ޔi WCȟ FG7Kf gpu=7NR|_!1Nk6=x@pUQUMJ! SXAK'BB(]2.} -㫺T( r͑Ĝ3a*Üީ=1GeOK bAeM 9 g}"|%dC1}-l nhڛ~r(}oR%=qAnǒ ؊QiՀ&'YWHJJ4LϛЀ4y'ЦAƵhR<Ͽq |l_%<ZDj+&]_F!JߔA$N˹\-D\kzx+Ƕl vŀY+r$ Te8Ym)îBGda9򁺳*l R['O,~"h[*:=*{w57 ,YQ,r`7'ÓBzɊz #tN~;^Ҵ9վFZ{+bqr`Ll]H$[k|-_)a848Bp5U{Pev(qֱ* Ą\AHɛ^%ct=t{$/?qV2vZnLe~ҥvVt䉕%f(sf,߯4>@;_.,s$' Է8frrv|Q"\cs_* y[ڵGՄvVksЂleؠ{b#i+}uzRPY,F%MbmEװ!s uYWa}Y#r(>T/]L+m&/g.!2k3Ha"]C6-k`H=&Z PU{ig[gcl tfpŸwo,H⨧IzhL'YţxѮ $\0.hjY˨xS@%Xeso:,3cQT}vu"l}W)?pe6 ^<k{4ʣy(QcKlZ,_C:-P8]YXX؍E@UI!U,{}6Lq]m <ӹ%CκXļ|#VdYL$gICjW.ےG? G0Fd~D4$oa<pEOk(* Ođu9'^ r)3" UR nv3>0CDJnRQ} Ԗ~GY.ƃ*qJ@dfug33HX2 ("bͤH#B`@}'&e$_=ju9R@+%=Qx}F#ʃyC |챮䵣%@LVP#^oJ#i|X'd4X&ۇw?hi8hU^wmR6D.a辺 o'F&-X2TZ/ SHa&[?ԢpL)NGtKDL ::qR^mFEok 9ե'd ݃f%v{ ID#ۜ}L:ioKߟ+3=-+S"GFE1X5K^n V4Az2c/.Cc?4M8'&Mv>1<)-H(}M[.? w 1DU/B0 ?+i{os;x? ܫA 0-+~l#(ޗkAC#̛HZ/*}:+SkirJg=UI s;sXVh?&1z[y@l$ H^Cl(T[XTϙ3ܜ rY>&!@9V#bޅg^D.DD ][ !vDŽfUfqX1rиc59 [;Vlc۳iz3?m] q(x,f.Z$]O%ihvX+Y]9S0UO@e" HPYٙ Nȍ>Vt,גEi4n_+33uo0]$Nvv1G4o=741(% 7V[9˞FhˀTNѡC1W͜wC2Uڸ S @ :惾{گa,r^4!w_0dЁQ`@z! t;sN6@P7Ea(]Bw<ŲǾş븆;ȶ3zuͺRiyfѥ*֫+lGt.&~'3,\NpjbO1$Ma WqRGAU)wh8o /,Z} _$j5D @Zss$(=/s}ᬛQyoL LY i9 {}zD]H@f9,-b}wJ ƙ%Qaaٲ &-Y/=;gaډյ˾S:( o=p(w q'a%ĝ'eUB2Aܨ ͗. 3 W1$.A|C: Ro_y?GMxZ ag@jp< CNVWGkv96la\d%ލ˜R![5 "B9B5.)|+Ut U ꤓh/bz_'Ub,Pl2":gLPGu! 0FI?&߂^\LۺŤ+De>>ei{3pʠV KV`J Hq1-&G9sfXdORAhE gdIS#1Ow!xv5gՊ,?Cx}ڎv Za}^sb*W[0ɴV<)dt:\<Ip>Ek` w3cTL$}ors\UAiŜA&\nlќgX/c@?jTfiYothʨ,p2ܳ0Dt9 zwER[ޟ9QIϙAd jFeDNG Sᚔ1E1W赢{n.=0eggppc#8<%KnkE߬Pwf`A`\ޮJ'phX";>+*14 :NG kP@HaԻHy4S+d+5G7TWjJUW>'ow}Xd{f+$ fh&ɥPLVQ"֢yFYLۍ&`(aV[vi@8+,j3PA/=ra^/cՌݲ(W}ȤU04etk6VRaqHڲO6dcK:9x_$C~G:mx[t+٢azgdnG5)ڔH3`Vk?:l[,~K!D ,' eLTZ0RsZ_Z$RLi[)jwL r*Ѩc[\̔2H.}#w@5ϞG [pjDk/u¼sҕZ"^٠+??6X1g0'-~W~l;ٞ.5 /EQXܝY vW/}?!5 uJGpu !'1&j2\jNjL";TBiƆd_w!A} hEvZ0@('?iW_uzͰ= W c'5[X wfȪ}n^l0Q 44ڶ6E$[s;%Ѧ ݋S̠t:2'quѸeJgwOzC3Kֻ0ߓmLƍ"E~ϳ!۾++J9/Bx!Џ5V Ҕv~8Kyc  ݸw)JKN~jȖ Q6Z}PE @6T0 ^iQ-؍Yu%&w46]*Oys_K'zű{GC8O<]TXc)Iljev95z0& Tolgɡ?eߕP(},:^p)ެGx=Ғ{ì~r !6@VUхaˎ)1ت`;PNfcV\8a(" (nURp GvЀR[5c%V;Smw>cCGe5j흠#d*gUѹqx~kEAoɩO>p x 5X X(7bhj7VN'45y 2YXq+:V~5}1[}g= _w|JʖRHZ:8GIKXCEgEJh7Gٻuھ@Ot_Vʇ)B!BR@FVT [;FȵH;fXM%QيHlq|bLg oŒR U Y$8A$ZY<XMV~>'.ᙴHj="〟&K!ҤۿɇʑD[ "*'/~AXFtXcD_䌥iY`;ZSLxR%~9` ]u79,poA9,@x祪 jm,.Ǫ&= yz< ~,?dj 4BL[uoNCׇ;!L'#c.f[i:Wt "-,$.զtуl.iqp@pڻjww$ɶ[(Z#tTHG'/6 5wݡf/0m96Ы!(m,XMś%i8>vÈB CjZ#c=Dܨ~E(7ѫ hbW ľ~K]{x~V3VCr__Ӏܚ6|iQQuU  cxp ܰOؼ}ΊΓ?ǃ`۩x鿎N^ҹ"!qdErM?6m+\ ̀Ǽ kV.qJoĭjk<[[ -ƊK}`U3R(wc#$1y 9!X1bĢ) {L]OѭS]k>?rQrg2]Mʿ)oh њ/+qQU 9tpiuGPԽ[oI&1Uo᫒Hi ; [-{"8s/}w( 8 7~fQoh={VC,G& &?{0Q#Q. ܌6!9S;^²XH,:kŀ)c5Իx<\cͤqC3ϙ@lor<[3n$>#:a^Բ|TdYR;qoN>wkZmv9 iHPw;}M퀐d?@7~R)~;q 0 4$dry67c.ghGf7Q G8S}R,y]dKJ;ҸHVUs/n`ʹ'(cBVz eC|$(i; ؤh".@y_Mjax+'E]iel7$U᪴K!1X#xܠdS"IoN0grb @Ȝa//ކ:\/'4|#؄Ɨ|#h%fu<_Reؚ&1ɲ8T} KG]*r5v朳}^9<j(Nj]etJ F\tYfmQ@v8(5O$YWg0'JF3W8 €h*kovu$e;qsYff|!Ik$$*j;u[ƞYHA26m0skUp^?*R ],fNS$p.]sΏte4:>xÃR?q_vY͝{9|WvƄ4v[*[C\Ԉ_z|EvϠ hM%tJ,.!DKױ @ѤBaRdQ!gsk0,s  &ݥspaFn:l(_LF`%Zu>7k1!7&G<KNPcEOz?] U (G=Ȉ$ZF ?J0ݤ$oievp)㺆~?\`Ш-2iˠ!xtPV$z5+rF#Ld}ƿB΃)j8P$0ƿJEJٚ_RO9bLdU?M w/ٶw.M{Ngr J,U`wE'ܸμ}Ȼ\H pz{׫{deFL6s] '6wǓ${aOr;x'wY31ynadH΂4% Գ!RI.X$%G-|RZ`Bit#`ٙ Yղ% ?o2mS ?d!-7S|2#l NZ *fC EEɫ!nͫˊE_yD(|n~ҳF2s#>EY}CwdbCəNe3[Rܫa3QMŽǠȽ[?7CB=̪rl.[+̇}kF+O<d'5r#4ΝWp탻$0Jz aK80iY6қP8n%hnu)K T(z7<rHzJ2%֏r({@AY383 |KA"{iVkȉх&f=vَRΙQ頿 +=8AؠV|j`N?INלDI!tm6ɷq2,kj0qV#0j10v3ʼnm]#M*ٹ,M+R HױSiu^$+ ITIϮ)01QGğ n=4M<`을|~„( K&i0M!-*7KgP+`[ 4@+QOWSt# o W=xfB}S "?]~_EFFUy Z on SXix ŵ }*O' .K:ߌ0АͰToJ7&4eGw[C!̈hy20ďPDdߊQu w!{$ʞqrcEHeA|2=Y γ.}90u5$!l#Mi9e_4qdFSf[f^- tEODb~{R'qS4< S%}W!Y1 yMYj}LRY8n6eBDCa)NRA=.݇Ȧ6!+:J{t+Y?]Wfxt:3#R>M}BX+YE9+Rs0FxŵȺ?W\I[WaJ1䁷ƨqDk#|"R%n $vW̦*'vA_ny5pp_;cQ1wjV&՜}Ysgzt%|hRYmfi@ fՒp3c-%QrI4> [Tԕ s,8' d nѢ9xSS5:w2~&{;hv$Js?GAU:UGG4*2O'Sd`#p|TZ*w V҇$[U@[ȑ|.GV'8%uЮto/|5 `Xi[*>4ҭv۝>B$(w/.kOkrn6\}(L<( x[8'-Y:<?7|m d25a}R`+UX v0,= XHʥVh6`m/UӪv}>^w zI3`V;i2U Voyވ ~5|4]Wиšuh]T8w17 &,K}Ƞ.ː,[2t~&Zuĝ.j`MoZ#ՆmuA-* $]o+ ! niPoT[kH@2O~iJk(H;I|Gg0GgV/ kpG2{p][yoǪ7Rt;Ee y#P6+޳`J lp{\D:r9zr])< Q:DgpO@(-,w`$ Su9d;X:(8He$cYH[ (X?CL~?TƟf/˹(qQg<Lsa8{OďĪ&#}VSz)&4U4uӡ)-p#^pDQ >b1Ctl)!2AN2f582$W>(`D8#P#6aH<Nwkú2z"R31œphiU1Z& \EBwלH|Bƞo}.Ј:Nl)_ cFAA9х}]>qgE韯%bWKoȃf9RP` 0\oF65D.p "{Uu?#+5X;r-8Btl6AU3 S$8՝n!@tTJtD\bVtacZ8CVpֲ7Ⱦp:5LE_HuMV g:f;5'vQ50 4s/ij 4&%Һu*I>^srZ<Qm+ut$`-zdr¦/*J EYS [߆f|vì3J]ÞS;FmnI/GP&(JuzBNL XR;0;(ըVK9Q7Ӧ~ZQigdpgh=рYjgƖɕQg;]#(=$?U85f9f hƛEi[-*t:45jt4E@z߳UF=n!H} R[۟=##6WJ>4V4Z,ْPlA@_|p=T%&$d=.>0]rW w.tX@v6Y JORftCR4,%69`Q:gZ<  @jTcޟsn#Nh}KVBj[fǸ%}rAGlDVP" #ҥAVUݹ]?W׉n`Qk0CB1ƍP\JPi[Y%r&ϱ"hZَI |4(=)9$ԭ\G3&iy* bg|cDƍp%Adql0DB"2٧j@<òBy/n1%@e磴ρ#ϖ"5wF&Ef𞶟^;!h+,||3FD(*Oj)n:K^/kM 'osU+QoiFVvcwMXt|n>q.aSB)x>L j[!0^k;8N.¿q('nt,@& 2A`{:fVg+ė3ZO: c\Փñ$R_(dۘz ۬Be9t1MhX1; `@>qvG+_{ZP; Njܚ'37 sS|$/YB2;:a"7{>gh y@eyUjNE{5Ϛ~FEZ{ Q1[DT+b&zkuiee'/;Q(20i{D =Ѯ*z~Ad e}4^D*wIOYdcIkUOSj^BTUG Åt"?I%h0bf_ 1-fe_ :We0u :[T{>g ٧ď~8^?뫨YlԒN6!2F +oԢIg\=1W4opgqW'˖I⡆G7ip? S+ WUHj$2ARѬ u#jߧSn!1tZI+!^PD)@!:$~1ߋ1-2ӞLL`z:8%K*$;<W֠aѻ\+]ƨ I|;4T*/^cI@FTmN"v$-1 / \&'ca.a=_/aF[`-CPi:(όc>w#+ߐⒸZJG}2Q?M̚*y^ &?5]gIL.?L_MgYP5mC>|Ph>A ers2#].W5xpL >UڷFa|.ebJ#7P 6k bߩaI+` 7YR.F;ssD3Z.v1@w[򴀘[,C7X[3*'ӨiHV_X}~[!r2]nG#Sˉgt0UWJ`OjMR@k.sLZnif::>۝8@sx,j֣ m2{q/B7E&g82Kxm߆sauwJa}T7b5װw ahSۦ7qċߚ!Hqvdo,YNa^zܚgnM?]aD+ {/";eSۏD~m15OE yd(OWU]SIТ<z㰫r&Mҥ…c1&Cs#Z}|gjF-\xg_PݙAZ\Yb'Og{b/$~w:EYc/ZSFP?4S$VṔ;ܜAӬ0T0}dkiw~/=BjF ,5 wƿ.\ O烈Q9ӻ]rk[ܤ*+Uqһ`KofVu˜=;#D[V,l U/].GB ,@"ǹޠ9kltSI)8%GOt=k`q#axoF-wFc*c쉚˒2 bgtg>%!9r'Y<w7cPSGzHuZQs<$G lPKez3 3ȩ_-fʞ\xݥX1sSBp9|Ic'#S*ɍn0dDhO;rm3Lh߀ٺxH9ST3ڡtdqrcb Lu΋"j=~A.pNs:kXոZJ(KB_o7f]LǏN}7G3H >p #}]LfDȺBI$YQW:bx M^4=;T giZ>׮+;iEטaew/P"L^,M*$si[Z r6xt&&}BkR'Y]NGf!xDD6iU_s0dK49Ϗ'ݮMzb_H7UT}#jTd{&ߚ'oaZP)&oYGNsw@fL3=`*oߥ؈bKprʢ`olLi{PEp3󟫟dXZ9;1HekPA"NcjuRDhAG6a &;EbHHlE>;4!lpʸA&.aC3z VGr2YaD`BKr"ƣQ|P7.αRhEh,jX2#X;|/b]=TOѝ|VB!ِ;fZ^ IV(1q$ICwҍ>F0f`k Lqyu#8UgZ0zl#Do(Z$p%ouJ>%p̀Բ~Upn8bF !BŬ̞J ;w%gHscd+ ˜2 OÜ[(3 VMvKlcS?I;_Lj/v H%B)fl,|.Rp\qEs'OѢ}.bǡ׽gaA"kl\jvRS/gwze7xRu,+ %]Ł7;m~!^W5(hﬠ**f_ylE+oK#eQ= gIo4ØD0$m~L㩏s0u\7kg`QS~hbgHgFoTrCywHL}Ϫ49< WBP^9 dd/#`d #o Lz~y/qO4.@{1<{<<˪Xe穾7;̚`]n22v?ISmǰC`_A1F[o R<1R&t\Q^p8OXOX+ҽM: 4戩DZuo4F|#( ei0ݲ+Y9G~TP⛔*2ݕxo2tuPWXQ*imC 6rMR*ِf߫-A E K Òh,7?߇ //#^YșF~m.Hkf"'>݂`'DMbx#ajpqsƨ 3L55w㎸e?4d:OP T*1ަq%*15Ͼ&Zr>˵h1!C2:R`*>H? d ,&8k}}`09IIQ̹JhX܂X<+Id˘cW(Ti98`TJҾqdXeOIlȮdAsEj2 篅&In0 g6[숙sbgخ:6- *ɒ}3rq2~ HIrB#-<"6G\1^sXLq_8ay$df_6ܬ y!g;Dy."PD[ \9lV*6KS=QMhٽ(|r;%dA ;My?axȓz^f{j/-fG<y|ݺ Z,%~!TʢJ_ ǽ{.ny2f.:.G 4jRjss67 Pzh"œF~YQf] 31ߘT7P-fWWϺlsSR.lhMb ^4,+7Oh|ND$x:`=e5|N +\R= =wAңF+av&H|*/XB@q.ѼȾ_JQE1SZ7sWO̕}Fֽ${$r@gl'u( ޯ+}%3]jWG2n #U&w ѫyی fO fVPl>^]V+DwDXQb{{m=G+Sj5?jOj@sDUf18MpTjXa-1r ǧuڜ^H*01E<"h,C*A+o=LͷK_u:]vha9rڅ֣(bx{y)V)r,z9deg;rU˾@lD`Gj#h`_E8wyuO+m {3X *ʎmmi5$ؾ An'26wRJb2͗^Zx7> wї?{e'M\/E*J=Ps7O=N޽(WػHD'Tm"7qeSy"n/ެdͧÉzۚﭲ-@P.14nQs?9_* Ec5r :;Vc]Z?HKFeo]K++'OZN=1{&2T y^c%Yz5:x ģWeA%.q\OZg~˲-)_{@罄l%i(7P X`uM8_)G56?77zMɩWZuP+gEDzv1{Y:{߁ Urz//-.ی5{yǀPҟ`\Y[5K'R$0s_&_RO1~)n~Ҽ8R4snh4 ;`ia cEMU1| O(O Gx9s5k3@ ' Lje.Ԛ5TPjWوJ]$?i])t8lqL.U٭b8oL\:?<^1ԬUE}|sLF59,p_ޱoiROqB; =Jz$)sDTog G5 v %?#FZVd%=zbLEvKh()&H>l- \eJۺHWXn 7k м;-K I]ÓNQ gq㬛ژC0V5QTxXe/M Yт 1u9t}Z7$Ԭ}t7Mo#Z#<9OYWvj܇ssXm&l`̇L~E/P=벅*L!Ch:XC~` Ï!MDg>v0W&|Jzj2ҧI<{4ȶnZVlgIMe'05o4QWFYPЃ~JN8ېPZ> bAK/C܂$0f٬;ҷ)'J5>y0X:"jݻ0-E\7= fۺ[bΨ8?|}‿0i=cL)Z?#?)?_ bHt6 m[+sZSϼ?k[X7F;cU V`y@",-B׋0 |*'L_LIXDOً;~tw]k)߽݊ ,?3Hg@֚x(`d2xa#c3,"|oao5C >rq^ws#oh }.m4V5Fh~3Y612"o;(e6s`l<TcFY_{q<3X I`k[M+EF}]&DNJfnPDfiQl ӾfИgw7l&(6e{ӓ\]koc)YA-N.I|PeM}EP*z aUGpDƽ%a3VFk19-%wRȴ D9ݒQX(WBwwt(&lR%oC)@X Z4Hm:7;}b@Ƭ{C)\n]PX`rA$1Ӽ%px)%RWSz1lLwDf.AGlx.Lj{}aK%Kܽp-Lz2:kH%\:w9ϵXR -yWM7l1f5%XCC:R#>gkuw!,%c1]8ܪ*1?]ΐI5x >4iP"8Cst۾_w^ö6vdtw#WEb%zdFVC) I(|/CNqsS58@J-r ҆&]pc6@5<;E6Ţ{His aHD0!/[Ive%֔Oa "L(Wb\T"N=lee$z|+{`,h# ]KTVK]^)5D-tc^;YK$ƲvOkhfoyP]nh (Y| NfgyT_g[98om&t=5y z 05J"e3uWucCdQuwӨv>R"m95Z8ln ;LN}Kآ,esi'5YR-O;x@a] -.L&WktJnsEoYrnISܐ<5y[x/F{\"ء$@ aeʼnQ[0lH< 1sad鲨5v"%rVYp fp])\ץT61ԣFMaGG?ŧ GfKmJGt&2:|]R3@3dzf.i@ ijQThP6hu2Nel?qXP\f| dnNYx^o50bhU.Z}z{@q@ / Ԥ)d C>' R?)+@SUI&XMOj|ePClt6fv@Rc$xVLH*!%`ynN]f0H%f#ݮGU-s Uv1 rjK !%H@?x}@/)ŁaJ{j/o#垤2$m; i9L;hPngLz,N 1f[ @hV Bmzܘ_*|1vMOk@ $ W6pVzϏV5|N\eFJ}c U1ҳ" ݵj$U (1E^|wd]L,^8t_LvD.pH? *8 Q,.&'#6mdX TC߇V3aM1>KRFt{OQnYYD $ < Y yP#$]6 ]|gY _'PUswN EG4[VMJ%O0ah2; ǒi &YR}NgǘC3gOtjj\VP%H}쿳::gCtgt@]}ܼ 'yJ!>ΠŌJ;P6q,|RrDs hJSO9z/CpEdK0SzY{R7)90n^gDzCb,]b &/_26a~ͅbӱ\ kYHʃ t=! <]d=>]|\o·10o_͒"1DZ&m2p:!SV0g:q5 [Xg Ÿ-%ѣGw|HIᐣք 8yM32AnoWb\@~|0.N;d}޹y3cnDЗ -կn4Q&-;6kҀC jz,IA9L'-E:Y8Rx qs:~/9iwٛYzOvRkͤHҐTA&<1^{:CN%O I%5 ՞8x_{隲 ,chk*Ho@6s*+\}П ;uq5"Ƞ^ak<>8aelIw$djwѶ*̈J`fV7!%^_?'>h/md%OnU x7Z+`DeCfTqA¨{{DX}#Jg;xݡy[Z]#(S~;9$6PoYQ(`lv6IhSq'6uϳYlG?jD8j$ }qbq>br4 c+i@{CʼJ߇V*}2Ws ԔZ6%ps nJpzjϾTPA=9s ` cwFd0`r!3'Α$h2zI#@q3ss?5ԁT!wd<ɷwMS}3 T2uTW7z.e11HG6F|'H67cF@5t? !|%$d`mg'%a1e$*F"܄MD 5X$_Aُ\ٗl h>bYC A2崿^*֓4 ];کl;PVDEΕUSI@.[eR'K~ ,|\8OuɏIWP$_ W} &1 {;4]QsVrM sU݂/ 1.Wrܒew:*B,>~֟rog. {14VKȵz\D>b $|U1^3}@ݤdplê[ ͎2UǢOmy_V8NCSh))!*L<^b0)z@Z:PąpJ-~a  fǁ%5oG딄jܴ!t> ht!ArHDC \wٰҸs \&Y ra97#w&qgMǝ-MGi)vP7M[XTYVa), /1t!by49ħXs (NY]޴:#|.nk0fr3E߈[^"b1"[+iPR}䧿7FPf5[$s "FWz-{cуўmnMg ><Dsicstqr +:?{!ɳUOpʞ`nzs+uUw[ֿG*"q2h6AnP]Y :h(!Mny9-*Sy/-XX1y8n`2cId%ZW|шeo} 2g^SC"fhڝ#BC-p?ݦ X/Im!I|=a7Ĩ44xob@3V :)X鎭Z[턙FWh%N3ItSG,i =|NJFO3Lk l)K(vf|-pVq&\%x`U_9?+#$Ȧf]\c -!mULͫ+%?D?zxdQ'#ȍ*(g䫢7fWyW!ս3^<{n>u]~L.6aqmearzTv8>L]@'3Nx TX6%Q_qKQLUj7CJq>zXb[{XKEF2*A!ҒAcB@ѪRM:i )1{:i\5&qxUI6 /ž@~6NptZo iiA;$面IRWOr(4E^)wh^B2[DO^*[4M|%WU^u7> VwX0`["X >f%W: ;tatӟ֧Ce.dk5lǿȥxV(8:6 ̮$g}rVoC1 |XfVg)>Kyu''T[x|sgyL*h|i e\!h}zYT !$<)US7tO=*snwFLj0ֺc,>Ė5GuJ1>a}Ŏ` Zl Zjg \ J=|w# DHM,8y-OpWwW zD{u}Xkkϯg31Ԩd'Vt z&膑M tTdlx*.wW}0NϺQu1=l}tu'04<9:; y9*z f? =Tl`k?4i#TתH \?B9T>vLP+V_ _FRGh8_#]Ghz{p8'6'XycP gi^u;g$1T 0 ^sv [6uI"R [S8aN>t+~ŒimJ%{aM%iҌul;R[kUzT^-Oe_f=pccn 9Zf[ƅs٠zuv_꟮ q<Ay"&~QfPOmq5[TA8;[aΖN5=^q )< ;SK6g#zScG km%qqe#4P[}uQ'PK=㫛K>c>,~C$lXVƅ=lW۵B6lg}4aM}*zI%<SҜmjs5΃O;SGp)QK#MY;7K#^oDMݚ$|5]Cu\~ԢmHwf9"!rxų,ͨ̾9nJʓut鸝s]< ƦA}u2dMp?MAV؏;cg $zp>'c/Ң ĦVK0B *m4*$l2 \k][$Sh/#/eqrέ4,M"2qkfw:N+7;υ85xg5hɕa%t;@+#7*A5Ks;R|C󺃛EFԬ7dfI_ZAhxZ:m˿`# 9g$U&%Vc.;u3|RʗI*0HEKk[$}0/rBi b'XMg_L%G wc"D[I32^XQm!:~62 ѝMJ'M|[Z 8s"d)K?wgdX[ BnNK'HU}gk _yV'Kl9:]ok/\UtMI$ePc)]y[m淾xt5z@ώs=<NAZ{<+񦹥_/ Fv=c2DzAU7UJ(,,݊EVbT=JBɽe بqYָ@C1"j%S,(hQ8|Puib[/؀SOP,E{m'  -߻g<>\ݣe:ד,3v]1H-Z\pV@ L8ESaHQdmqt?)2ݕ9HUIGc񋁿${k@cMa.SdBi O3US*ƝSaCdS c}LEF3{!Ja%pӪ܋Ms* Tq[w{Rlտ."hF>r`8(ʴ#o!xH-˓w=vy#&QYXP;tQ[2]SmdtCHNJ67*=,6ίATNG4OyjczN<.6fi@e6)?.cB=} eY7NhFcN~PB#>h'{\%۪ieH7-c?a9̢M xLB) ðI" 숩IR]QFV0zI3([6QLQC~0Qqh(~Bt_8#+O(&!;eN84i1ӳ^퉿:CGfv%77 M赩Y _s3lZ뛧v5Y9% S\? ^0͙Mq2+ f| }Lq z|XUUmˎqKQX΂˭-Uˌftocֆ.AK #ͩdL ɎXRZR&usOUg2(^J^zo^r93= <|` >ܠ\i1 |sV?+}ķ,v8d-LZ,Gzs}$˹ʐ]F gr,D'ൌzY1ȡ?-ޔnp ge%m+_0@AʪqŰ8E+6:CJ;*5%l:x4}{lde?mr);,8𾇰"r KZ|h ҇/bklۄ*C'ָ7ݸ۪ x jŅ;; ׳?/IѴbopi- 0N]"qBrR=0Sп_8ΐvʸTBaDY(_3d6d T$[Ĕ^P}K܍ =2 mN/F|>q09n(Xɉ]:Dw[Ӣ6<j pX$)HK4R('i+oVwmVI!jd:M4,'r.0o# ">REv*U ҝ79BD=xU:wz{Fn#E2!k)XKjL;I'̊,Āg9ª 7:0V:#,8VQjT&x]b"؋de8䊵^8lGz95\i#wQG[o`[ܩpZiTގ߀>r_׼ '. ".r ±};^#A+G\"+7ɟ!V*SW F<,KdD@,]gL*>\mKy.H+O*ktpk@C]n[<.}p$IN?ٙ"a6{6o G.۶Q3 Co*Qأv&XKͿ%%cvw_|>P3q\^Ix?oogW .N&Hqv!LT%m@4jz pAC8E¯њ2U1'2V@e0Mn{굺C;exפ&ˠa$?MKǯ}߮b8nꕼ׶B!z*"HMĒ${H(G5>UNוf6zENl]Gv_flջB81, 6wMZʫB E,sYCY֫ g–\z_4j@όpO*.zV I6JX#!p)T:oý1ҿ}7n*N[R ԑaSf,B#g"ӑ*3m0Cn=4p&"{0ޮcg Ohx`iRcM\/$"Cב5J k)gJnWEfb,wқWs1+4ĒqE ʍOr9J\}pW5)k|x=%qԩfPN12{c|J 3 ¬GoeT Uc)[Ȫ7@2n6K. I|^lYmzdUpH)fB_KnЅ$j#1vXT>a K)*,]")t ͩ~UVX{.qnl+St+8zڐɏp76Co>MH!`#*;hZ Ej2FgM\iH g:(';~ 1))"_er}' ZSp=fTXc |>)=M |/#[ǕtUaeG)SUϑ#06l=q t%,&oJ IKZ!8ݿ5F }3ZyYp^GH6o3 NugWeiE |l:&dSK"{_i}PP1U:#103$!nKo U7ϜfDLsRkb֢|[yz=|b>R pJw *`gYvq&:Y>#ks{j}055jSٚx0y}%X136$%DLWч&#A䪯]iFQWe&#f8\e`+FJ;ӿQ}FZ<4|sh 2s~lyvDl6jN!e_*(J3j+Nu7[BNd[ 2LCH7<:'t9VV0O$^ Xf.PVލSꊰ"Dlk76{Čqg:IQ}=/z?`f{9|aSmxsN"ȸ N`eW|w|jmx4ڱP3ų|6 xp{: ȴ3JYuwZGh5_ߡԶa[Vşyo&6Mh>Jvj?(2h1^]k[GU]x.x֡yOuXkqb?D2֯wfdmD(jQCxHpո'7+{[]CV;o5 KNU캀Ć4=J/Jȧh~kgE!hXlҨP}y.kz.6:;Q<]GTgg:)!i&cن^\G@ ''*(F͋?A ̒FD?AQiyE š"a!#Uӗ$rGVW%tx+47ν!?ҡm>GIK(S04i39M*7P+eJ'?圮b%!ǙK?_9T-rۻP#"4S:lmfLP [g? nPxϤJjp>.1cVk6]ST} |,՟@\oN- ! pf:+Kd˽+E.[} זwb;K4&oxV|JtҙѺa`:@ ںUj^X3"$$a*oMLOB&:ȗ0¶#;Ek@/?;^g %2z dn)Vٗ/))_+ĥL]( ~"p#=\W6[ci錢 3)~rH z*D-gP7 fA !3( W7SA[$R%.mk_{Aqp[KziPHu9k*7ѰϢy1Cy~_,E3C!H5OJ#F\YM~)+5Mnz]]gڳ Žڶhos7 QpHW)_W8Cc}=aG8f!,cX0Hp^|F=Mw sPM쯺 #Inוf+= })|}}AbI}|ݪOd#u-Bs5U1==W(YؐQ*KE=CoRȈ.;̾OB8_U'ʤwY2Vd V3Iwr*]peַA )36.r7H׋㚗@ )50HQQd97fM"uKFyKBk~d`Oƽ?Df6-M ",3ԕ|Ũ(w;|"/|= _է}!tOg t`ŗSh#&ent! 7}Ak "opeS~-L;wŦGsV 짏hYGF0;n1G03 (VT@}'?/e}rJH\~#jtaD!j^J#~<ΑPL`=wZJ7=Ȝ~R,-}7/9Q<8 H6d̈i"Z6pyK k]9f8 :z umH^v$ЦqGY{Cl eݘ}ZM{m ]TS"()yB?Qny1\*ykXy:,9,_FW2;; (nQ:ehaPZRrFihI}چ-d+$\6,Oȑ!Xoґd*#YlɎoNe X>I&n~gZ;o(v 6#+YvM67x \)˅73ྎ{=M9R&IלY0^[y`"~ۂDl9(en˨{N's(5MxR@9$qOƛ8T&t 1lQ d EDSMVtA%MD~T16PAݸWCN El? \k֞،n:^,Lm')s) fAEuD=+ TTAnwxoAla{ĂJҹ1% AS`ɱџ`. .<|`.kRL^b^&y92P&_&rk|Js7Eq"u/]9LIǻ1L0Jj?5mb$[x}l9 Eͅej8@ E=vdk>[AIoEVbpќWRFaەUoQ̨Ya=Eo&!eNC}N:IbJ %YZ"[W;Rx8RtNva_$|DSS'`T)pLI 솘ț8%z8Z ^\ Er ~j)YvdQ"(֢_B#ccA<} ַCdQo]A\*EŽQ 9s~Rh b#I_ǀ4 A 3]torA B`&XQEjj!߶Au@Ulb ='SV—?ü@2*C4[ OWd}v AV:Kx%c0ŦNv*'ZK?`>97}E&fZXOJK Z Bbh%u&G)xOM_Ɏ|u`$QVSt ( nɆpErX{k~pQojI !_]o9ژd1ݗh4 nQl:NˤxT&+œ>4 m ^^`QP \8e-ə !'1AiotPEZ7 MLaDH Yƃ,i{΅Α!) :i4GCvr]*w5m(Yﶖ%: ?I5( >" ,|)֢5C);Y&d1sEٝvM(Eu)Ʈz7q>KR$YDr)}]d6^3\v} ⼊*h>B`wF9d3 g..Ux`y:Ҥ~ >E>E4< DfYEBī}Tۆވ>RANE0c/*1_[PL2U1X,w])YNJ"p[UBm)p-H<>:W0:]Fbd] hsx3$t,fTżv_#ps1軅Szz8i zX 96)z݁A~u92LIލܧ׵\%KP, KR?)0B#!5MkݠMiӖzYYh;?e5 usD;ZtƢ鴩M@ZK /J"۞jnLࢴ/sBrom^DY^Ӑ&r5"#S1Q!#׶mvTH&DA1(ݮ KgfFva1Cm ҨĞ=tHx7EiT@g{%,^ln@/bPlHHk]"X1(xW#EէPfJ#@*Ӊ;C?*T.zi0ͨQ KnC"\$B}cD n`gj6X(NcT,^%K 3q[bϭi?GgpYP7&#@if VhT#ɾEm.]r{x^2ZW#D%B8$XCBY8gf4䓹ެ Wxь,ꘒM"CM)ƴ$|J/0VS`{D71OGc-߳ω)W}Cw34)VF7SFܮ ,u& = .w(^е79EP+T{k̇< "O]w s Szgt\󤬚or{P8Z;.?m$ *v=*h@e;e M6]R2z*>6c2e;^](^(%NziRNꨦemo Biw~DH3y,}P04W`{N|R -ϕIAv~w"9_(aSV oEiSۉH-- /9o|k뵳r V٨}9IqsHMd7(9^{H=5HV:$ @ v01S@xaGj\@`Jw( =6!I^ 7/zWR?n!^_mDs G6†/XuOmSa[/6fVa*2RZl f zpyVe*8{hAX ^{㛆!tv^[@ WzGa)|ժ=\QZe:>ؠ@0zotmz gDz O:\ o#嗦[|C+-%VelW»Gw:Zp1@CIG?T|"*65mv nZeHkt"(7-";]Zt4de(J0/3ښy[Now{T[B(_~`Z|["Ȓ#} V0}Œ9y} cO z}`b Q^[ X{dCvAdA/ \@n{~/?=dC7V[DL3t)EԼHf@y2ș^ A6J >zB#D1!-0鵏ЧL\*vG ,[oqH8usM-mr+_h {uH PXĖ־й^xB _НP21=[k噲Gc;\((pܴE!]6ȋ2#,g^!r 2Y@"6@95s Z~~@Z_A|P2*qǗf[{ {'W3د4 Ti5Hao'p۫&Q0[ў̔ۆ6J&5R74E{^_ʕ.ҤMJ;oBtI ,L#</18V-@WoRnCɕ2hwEv6ٕ'#,k#A'=nõn&ʥq7/^5u0z8l}uUyʷ& Ra"bguR@ۣP 6I&Ce@ Emo*d+(/)dcΤ50{p `N\9&`)ȊiܒG@0uؙsUvn[E(*1Nі nN:{RȀaNo\\G^7g)Π'a 4(l6EfIkF UX܍!5ɐm3F6jtJbm(դ'Y5,<7~+1fJYL~ERC[e{jp= N2k@b]rؼi0f$:2e3o(EJtmUXh-eF\z _"3ۺkbt@Q@ X (o:t3 F5_Ļ6^ [jg7Z]y^`MF #Mk_oNgTމp.P7dUǁZ~ />*-7QMUdg&jCTμ)T,*.-e}^iGyKp M/тNWf#vf%I>.}7k=]}%.F>U=DT>^V1kwvXp08,oJ{a&xxWV_`(W ޿0=$?Kqpk/NI-'Y$ml:.rEdmWP UK_Pr0sV-CFIQPA6BH3 ?ɏӒQ–Y<0nYeG8=(@*u7ΫV8 c̗ͯS?t -mKL_l['Ycܡ(?I@$s.q[nRǖ۞q,a욃 *\큀\/ƋmgVL2.k<0v>1~ݟߒN%k cȾ|#Ԫ׉kVf &N#Ў5;X3;T${?~ 0>lذ׋/5$,92y%"LǔTN2ϵ8:*5CYDV _T[Je~~xP.t*IZY j޼8:1d ,T r&RlǩŹ+SO~u` ]*cӲ1~HIzBH)#h nZ+ (z], 3EΗZ5,b/h6 yi[k&؅rfJ p9hZ R# T oR45ie>"܎;F r ;w;WJ:1ʖ}wᎿ`>ǦBxUD&~1/"=6eGԒW>/"6|cUsKD } Nu7#zw Q0Q'JJVbو*܊ 3e]i hJAb7b439%uDƺ[ݥuXt4TT yĎJrr.vp+RwB ,q] TŅL(qa"S]lҁ1(/KW~<'0))Җ%a`MNKQQfx7'܃v6Gƪ2ҏ.q{IKᙂWl7LT+|yKߥ͏j<v D<-٨YO %j;'srt;0"SrJ9]M+;=wE;f;@KLy}f+z]vZz1v sZ?>'NDPD$!0;_#Ugxx"̸6^X{cڞ8~gT@("O} SY!jQ8@`ī+*YՀj͹M@'y_NnΉ5 v{Di˟uowCV+2rGƜ~3ta50I8leǒBn/_mݜ&a|F[f)&> Ŀu4|e"ɦ4/ş)LI -|D&UJx HG<%ml9)cP'kwr7]"?e#GڇɰvDP`.à5rYL>(c 'xQ3c$LG#hۇeo}g7wŜwY0co+x{׻2PՑc yoۏ'TQzsbeK: \Fvbqo^8;aP9ܪ9~}Q7ڦ!~LQlsf`K"_dvwmhy@< -RctCj.NZsYHkJ }3 ͮUVĪp.úBצk BRqwKǹft -A @Znl K]>5[6-GRx`/v@|ċG5kA7־eM"3|X 8um/9l\NlV'=W Zѭ9;|qf ~r,R[ & 0'tCU+H;V7 sY+M*]'s*J8+H BiiǺs|ҾL@Z4]{8'^l;!+XW<[I*u9L< rSc+_pI2#zy΄["t0aKFI!49uMU:ӏ'^_ጾ4 f]EZ!{n s%>ޤD\/6D*3*/E2 ƂNRdp1 />}qp٣W38 JΦh*@nP5p٬ptzC02= <1dd1u*q.HmAlC1M=`9RZC;NLM<)8:=| Gm3 J`,P_4Ul'\M߈/X[gZ|*Y;<>q5nQV7K| /C2m ,|$8v~g!e"RXGY*e{day4~pm:':֞s'5%24PWڽZқ\2@[Q SԒ$Be¥{qxA8qs w~@8syV) sj_R_-mH$.*Ge_ق;`;'TN8dpӉKMV4ɼe>I`] O*p9Un?\^)yXDf?<7Ի-[N@ܬo56a%L& Խv@yJF ӷY _¥'%CE|DLi %YBb 5|)bWEw/ۂչa't= (v4 b쬫b\?;alu . [ a~?>)_i+?Ϲna1)PwɏDekǣa4٠-_!Sl;v ǰX`C( |ƥ[njrL Τ6!:g<ˮJF!}t ZK֧Kpm?z۶ AIҤOz,{ܶ)U`Hț+'2ɏŴ(J4N r29;4ٟx*+x/гr<[m4g<7_ T%c1m̷m@xQ'iEZ]7,}*Z z`p$kM;x@07dp{.Y+Ng0"xNrmb̬aj*)y}' 'ܩ5'ZU6df:8g_ MIzD{ꊏ+~<*3Ul 6EtdЫMSD,,Es$|}La̧$yUuct X#0t1B=5W,٥Drڒ"){;;Mkװ[?vnKf_4:kY3U—Vp,&.vv$nyɰ\9f4>M9: _4G~|m} Bَ̄m;`xJ".>H5vY'5SI#m\ܓeXf>xr֍2[ʴgP9f-S8ўh/=v*o*ǨD*NrWGDs{j ]S=L fr z~@+N'@ЊVqL6,30:-_ _¸ |bO=;8dFoXdl5&Q 坔:^QYYQ7<+WB0sM~0=y]{^9J(Tv6 G\)%k)UKGNq9al9HIjl٦ř@ weRz-`+)#ɨbnX(Z@higc3pp+.PAvsQ'GW~u"G~=]!t8n=2mUIgq̩ZLZG"1T4GŘY ҙ|^=z(1 h|g 箚2klLWP+f'黴@o>8U0ϪNT΁B*|U *Z=DŽU煓fZ".nBqyjݵHFVpicr#37$\j i貳%]L'.po|_W-*bM&0 w0qb\&mymJ==43[Xd6f~+:'6-$&9a03-ƞ}D/2T]zs̔ArKMA[C̣Hߨ盶@Tٟa+ )M_RKjH9uq(ɹxć<bX>dB(2!_.jb O/jC oMm?Iݾjw6&9scI(.Nk%E^(t=1{!rDV9lۚ TtWZg˺@U^[VW{X&%ky !LK؆Z'۫)\b9oRKwy٪]~⠀AMiw4DDCxsqn=ȵ )Tyc"&L!?8ɗg?ո5t^P&LJ_cBt#LQsK(&@ZB@MW=f "D.0"I|͝bd"}Y/鎡n%>$l~>Y|z {s&G`^^"~G3#ܦ%%t!'?)un!j-MPufj7aVfMj.bζ0}"fg 0o Ol~>Y|z  (QBZ\y yƚ Rp9NTX}DVl|En0a/ d/ۆ F7IDcnrUaf9;T #x|ͳc'@@ͺ_YM6C]sZ/,y0E(5?I\3^ Vۀ(]^0#N²szƈUat.43&|xu'™17xLW}EyϲK~֬4C6 C_Kdu'Yi/Dx9-Rq#e˚5wͯU@&9nUh#ꎞ:@ee{LͯE+*6[*bdEYTB)Q!h;iD yiJEg/W`[kÙy'fv Q+7/>-i!yḻX~nOMk.1 Wǩ*GǞU;E["H74M-gřcbo GgG|1cT3%nWѹֽ'VީY^HKp|z `ÿ[i/>9(d(sx ݡBVݫMˀ*tzS 5@vJBp?X.=ϴ}Sﲅ1T,9ߖc ͐l%82d{@*zr#X8&Oy,vH EfϠgZr!UʤO*eƭ71Q6b;EwCzy"[0S}NVS@^ @Z8c(*ŏ(n0amS׬s|yḻV5r L2f+]ah~JVNH Ӳ̈Cu(ϔ8D/;o_N6 i.'t5J_bC#K0DgY l~>Y|z 3@7Ҹ:G+Ry+FFjI:$F}n'm >? Qv7'Sb'VVML8ۭڇe6cntDjdUY7L @b+T]CV6ޘi#dl!6 P$[pvH^872 o`pN(3uDc+QbG[6%j7PWLIcNCі1`ŭW)H鎸s]mk1_mj4<2vz}?q^Qj&5 JWt/!6V`?: CWm3>_oIxJ=w)A|A nҵVh}scI !wLț+Y/̐f g&m4Pbdy[~#i{6r˳o$F{{vU|,(khj\h0Dkᚫm۾sm!_6cEZPDVqyw zd^Af2`MTo3>yEߗw 9yZdaV_ 4@ѶXdf$sx[%g 1O9y@Tފ-r^ &LPnN_x7{_5<Me]4 I|(\U(ᆿ@b2ON׹qѷ5cFT42_yMؿ&pT>\p#*p2!HV'7\i]M3"B_jj9#sG3}?Q|3&&x9*dlU Ls׽67uQ\|zmp[BY>K@'͕_P>i.K,[)l g[g#$ qlYeֳ ޵NDT/X|*XJO* ixU%T@9HxrfDt $sg/NK 4eRu1r>'q"łkNֽO2VP,}evW \bTkf&I\2^Pbt ;w¨h/3^]o3t?GISL@#wfaXȌsxT~UIpw`vc4ظ c ԙ4KTIh QO̠JM4Xan꼸CAw#Aޝu YtHֆs S[,;&ЕKt]s!ܱ O~"ܓODɱvUܫjSyt`~TpyȒ:hAB )fA)0C aRӼMY?ݱw XTsAFwlLpSɂ6a.`8.̠qkoڈMGǗ8* Jؓl}Үm2JDA-FŎW)AWB :;vy5 K`m|՗.0HwnٕVAUU  zf+R/'M;pY..xa?LP/WBvYmK>{=_>$烘JD7n3Ĭ:}=Zg6GxpwW X\+"aykǨðjXk~rҴ݋S?)5Ml a9Fad[`-c!]F>"Y53G%ՔX#\Z_(hg<^tiE.po,T r޼ZBCH丬 gZ$Z_+s>#0 ]}>vDrepලXhb(s*Sar]&˃`r"#p[YkOX 6&q1 ʘr/ #&==v1P* 쾏jSذ9jVisSX\ z"ݮƆCoD54JTY߂H3^-.D1Aa ]Do%Ca ~~x#RA$_X~ FD+?ym QUP BrM(k5C~4.(&4kGqJb$cn-Le"gtm@h〖1e4&Dwd浒u_Б:[쾿 xH>c +`G=8|j>s]~"[72ZG`O tlMGO*)oeO)ô)#6zky l\n2~sNh'm",LyU=x$tp̱ L@s^gF'3|rE {}wǗR,NT9*:gQ<Eln%Dl^.#6٠ŴEy1BG L.0xץ8Xk)PK5=>tRMbOQ )QI˨#;< t$Jm%J~Qiו/0}Ńh/;=z/=gW3tLi`D]#=6%YSQ}߈G67[)A#7!? J6݁/筄Yl=VoyY*gE~H7vW bq !ީ|{/tne:̠()>ʌ%b ̡X,nOz0xZu<abTme T: n#rnF=ڀ_;J=uc-F1 3iRϗ,g6sM J!zIeC|]N EK_Ƶzwm͊?V9oGħՎp&=HZEm"[!G!P 6FGTvjۅT2w{AUw/$#fAfL"]8+듕n}(HBm6R CcHo%#!w\"t !WcUa3G6O[ɯ{[f\7~WgˆNA=pQ4#&)[StΚ^Tḓ f~R7PD0`@˲-[Fv V[0ȄZel >רɯOB $ÎAZP9m /ߥX6Y[ut>c*8Úv*NNoalD[|LE ?AjDTg7o^ޭioNH2M2zs \A5~L-5Q_RF#ʿ.x(]j3G.4ބ1p3k.5y1ÔU `a3hf4Uҏqi}ǨCݦ5+X',ؕ0AOaĪлn2gpTBۜD 7;3G]3D 򼀸r"oU}U[j1A<%u(M Bg>'@T(FOA^E|KGerܯMymڕU'T PNf4FEN⟻ <ي`^i%2ZfwTr̽HZ G2.Hj!j`K/+ߜ:xFQ<|(D«wvGAgsvm\.|2=E^ `F:X<=wpYȀ?H"Jl~ =`2+`!6+}6TDt!!م5[@{[1[a`dbq4H͏KZ ~:ހ)4rvḯ10EoyTV1y'&"@#b'*S"'33YW)nڄ 3bcŠ4r}Z.덜V["Qubzq'9fi",lIH^ w<&G`1 w#&x6;H//t &V\>͇D߬~zMc FƟJP}Lpn6^`e;*^q9&D81k_O y{SF((Yٗuɞ-,Ѳ_`jHxZKʓ+GVB&֐eE0\s@Fް Y'ipIEC0O>2eJ' KLv oxUdjV܇.;)(-"#kRJ8>['/3 C4QƷ3UA j(7njn:#CvfhlbњHe#օUtqbS\G&[UbQU~ɭ{"(,]?N4v߯u]&pʫY7/I-mjH^U9N~}A[& F.5?zEnb WorYLhDȶ \ U<#MqYZ8)sH teﰊRm'kqNx)d-MyDs+Ia<[t`N5*f(T`3m'S5dR/q%F\X; m4z1hȴۚ2ξʼ'UQ0T%y3=8GxQh@WB^>L2j@2㎰ 6E%`gVx9F{uCoEe# aK3)FO"d'ͻ!z=ȷSx6}Scl`<"J>:SkXZz+qx>ƚwB^J 7 MXzpsL*_QWDbB`F  y0h1uטc@Rs9,FVRzvvDŽSPRL-0rxRV>r}jnA=G@PWdB 7 xgp]~>^wkV@sGM)FWcƖѧA?.=DAA5H\v~{nx{v֔A˅n'%@.i8?121N ]%,LnI:aW<, dQ_VsHc@mTU.v# t?&K^eZd,>oZv:EfbJU]7s 7A%V(xMY]èf 38#UZ4ݹ=Ê'|[vPNZbϓ8%oSW>!T(H} !G}Qh{ D+42(ftʇ }Y;78IMi@d$O AS{RgVRs=i4[/36K Hu9ݣ]z));n_Vjr(De1p8:1+x]]v'@)7E*f耂0ݦH[wNع$ 2+<Okw\'P3EB8 L?f cpX mEX=mKy6y@p[`DY }GL 4`td42HByf6VH?c.)*!߭OZd:ѯn_AM1Xݠب0n<@)$Ĵ T D=K$ "o ^Oļ^ t+~Cg^93*t51Y\_w8nfx=C tBM/1(˧lOkVDzMLCq | YrO2/I4@Z)nu>cq|4u^_(,s5@E&fM=`m"pgbz%R#13)#vZ~? \vi%mrLb D/o^nS`cX8NFg /d T5BI޽@Y(OP^qcIqrS }5M% gWd*GoqzK W*ч\'p>"Jj@pB Sl_wgtt{6YnQ;zڛUt8~M*e8s8U.i۲&Vڅ%fT>Jf #yz=Gpug _>Mͼ1F)t93DqN5DM"?3Xv˛P-Az3*5.6E!HPpz(uƒTeJjMtyUsxkp*Sy qyօL8EgOb)s߭&(  *aԸV(wK{1 P׶3'cG2uU\+\ܾg*3 DLi+IpUmW^H_E7Sq\3$bWBI|| b.߽=6.]Z@ /Lw~lw[г֝O>b ʨviiB(`ZXmhI $ 1W[cy̟iPw2j[tW])O6M@V,lѳP\_|t\UgYl>N91֡ξcܺl}Osp.52O4NINDj3 V[l1Fá A̫=Ej\i3# L `sD:5'*)0u5 xi伴 4ZpNnzi'ԕx>8f"%Ut<3&#}.F B_z`.%;o˶:nEh868(v (ŨHPB:iU۾L?ƿxmW)\-aweHX'rTdP͹G悶K軉~)aPXi iZ^ZiHgR9 H LύJ_ll[ 2&uck\׾u V1ؘ>xJ}w\dٺ%`oVM pCL\A:0nq+1XHQ@*k.32׾|$'k&WeQ!V鏢ϱsrҙws,μkB3r]8enwS,׊ϱ1ihU9 h\/֪ݸy? sp{R3Hmz1h'G:78jEH^GhƱ .RֲYzf 9U!tiW*0Upx|1ϲb[٦++:DFK6;A'(T2J4^4wɰ1\J4FJ݋yNAsPvVb,n_\q:UqB/TKU%!N)]/!訠*pZRhPě "B%\G[cGo%R淸0ⷴS* F9j P 4Q7\4:5MVޡO~gMZ=%49LiW%']#\W% _+Fc_yA;ĥKg*#Sn)zҫlW qy~iF0{0(ّ W}IPKq|-|r轄tz9IzA,3xgbGnXIhd+QL+>SpAH{W_Vv4*k&Goz56'4(’^L+}90yVw" ~:ckN$CBRFqlRףd򸲘Q'#as͆(I4#]jaY(kS}0V07D0qeT5?7̱hv"S/oɡsS4̦'p> Awoc8X}cX5A!=I&+*q.fբŊ!+s 6[gOJ]֥NiQL: pݦ=2XOX?M,:7&~#/Xiv~(X:2?ceόnV+ɸ8}h}ZiڛGPL-sʄ6s}F𼷄 {qP12\] cLcuZ]ȅy"긻^Z]7 ߯`|Y8 )(֮5oN&)"ՎĹDwe} f#~n4W7]2˚[ o(N{q,X9GOUgř +[a.z>MwjZ^H@O0wl .|v3 K:{Ρ2VV!K1|+$q*rJvBSe S[_J=L@yĘ}%Ax6M|ςKxȐ~|d*Z} IZ(lwWW.MmZiRM+*oP7cǓg-/e0 :kKT8Z ss+OU_rOxAt" VZOejX~N`"L t57.#-G%qJ.[˜9x2CW*XKtuoA۞hq2m@(j{TV-dT,W}-pݲUNj^G?,[ilƋԤ $ف@j2MRYjmg]t>jGSǵ v4F?xdJ"^oQ^rꡬe4Mf{) /o 4XGB(Z53e|&QrJ^"`ߛLLБE ahɐN$o\. Y_]4G+2i~mW>;"#+lK"<_K+mfd0:zjRAv/;X=e X"9 FVO.2@x՚;T;boB UcJIk+d蝛]Y\Um>?+s0'M9RK&O9_} YbOm?DƨK7 7O!NҫYA$p,߼e웗c=Z袓$L)#,겒mys>$<кHx/Je m0{5,7ݝah>-P?7 sm6$o{e9׃hܙ;8y3=Oy9^GЪM +h݁hf#{h>偅:$&T?A`W>gU-lωshB\x64Rv!sϕեmzE2d]m7m륙*@$t-yhoSiN+j?u naq$6EA.UGb h'G'LZw>&&I&S59:/LK^J.QfPĥ?Ƥ7{o=354z9;*!Frlư r5/REW~(#yC0l!(E_.Y D~"e(nz8}uMSs;(J3/y!p(=T/2hTcO q|.?L%!]/Clp)DyC7A-m\LL̄dT$ڶ"?6Ʋ SH} tcmc|G_SbzY%tS$e !#q <Jm#[]o}g^sC% ;+'NZH?D$۸~k"-8 ?!1E4](P65{ko<0ѤY^$\SV^fS]MdqER9U͒)?tt7f&txt (ίx[>,x@5kNpK !6r+DJ`pToX{zbO;}uDq4Jі6-DZ1Iͱz C2y!舥j|w޹5?:继F3s?nD])}HםJ owwUJ\o_t8L0r(H;^u2$N4rp6I5NnZ8&eb~ۻE \ۮV>zu6%p*%)L41aB.ƒU ^ RXwo[,Ha9%xVvÈb썱H_j4 2,8Hmm`Њo/:syvE-hu~,R!lˏF Ɂh#BI,+ܸD-lĒEGY%)x%*R(BVDΠtCaaGHsCeH5ʦ5^Ǩj$8~$OslK̛rLU%~NA%\8.'Xi܅0H+P:xSV𚻫q,Aj釳Z9{Z׼0zX0Ci7]w]-@T%s=<)S 'T6eӞXOmTTZa3Xnĝ<0`?%[@4AHcNW߄;"+Cr2!ݿܧlC_Cv5xYWݱҥ)|,sE"u^#+;+^`і!Q;uזNNo+g)iVOȂA\.N(}+9RlQxrsi"sJ¥+w!EVAA΍6$6tƠͱ-Zu3S QlUßy4zϪ2\yȟNalȕ,6VmVC9|"yCL JFmgI%`䠪k:mU\ L!$^D :|`X\2fR\EmրЧSO .JkFD [ /l"X?B$K~Z/BBZZ6ٓN $m~z`]c;T9sXcO ctr-tI2dg2FwnЧ/摳l8avOM w!sZh1Ơ)SVligl.u$T[YӂqNC@vC9ۯL;}- 0, ؅O=2E|UhBXK!L? -VmrHqv!˄ϚLc0$'w|." ^hi G p?&r#~bSni6֟:R:5uC1.1qR"L OdYT}@ad>8 >9mPkȂ4rY E.2rz#ENOOc):Fِ]xWI{IJVD|O٠Tq[p! @J Lu,؅,M_4:6Pd3EhPպ`/Gt!4<0-jGO@'V9_a3 µG)ԙO#~rNYrUXYBՂv#Db@-\gV:mV'sWf X|0J"P *wQJ !؅I?oP9Ǥ#LRݛPMI2֫]̎E'[ KY߄_ԏZ7 /Ѥ o"*<Ƕ gAL{sDB|NfG$px4il%7kmgr$ǔ4FS̗r(`".$09t+!P?QRK*!3ڛ׍Lb/B"EW5>gd`T4d_ 6\x6#SR`nite8dij]9C@ *!74svD*:M[/gg ɨĠy^f)P$e@.*yMuth7lGN+ƱU}CW8iJB9y:uσF)Q3Ubdvꯑh{19rb 7tJj%#2ƻg<<06jf(=>5a;ҤbhG,Cf֭cX)x;׬z1}~gQ;}Fm+%ηBp&Խ9yE$Þ|}KVg_OZ?C뵝`?\n2!hzDAqz eٳ7Jtў'LD 7;Bv*O^ǪǑ:Tq%@V3:yյ 7? MI|W`s'rӶ=$=l~~h^*[ϛPi͆jO EaTtB3Ӽ'hS}i^ DS|h m ZYNmzxe 3(EEQ^:ys"vQxe)bC+ >B 0hA-jAJCG>O@%Vqn4ͨ 'L>8ڋ PH=;D^t!'J½Q!]qL t V;+1#ϚOmf#n,:d[QmVHBAJ↔ A 7lz0vM}gFq⾃Uw-m\832i\7H]m1 VzjkTQ\uhi69][?HBMqycr Y6b@OVo^Cl4>>Lb#zE2y='jox`p`bݗYlcNvnRiGk pg~>t;@ It-Pȇp2DF4>׏xuk2TX&4'Jggя P}6 wARE#_F2B1X~ֽc.b734PhCO9OAόpoD}kx|BX0~QM,S)"a'G}J<ܝ},q?B`Nv =tF=G?BVoK*&k"T4(㵫k*OďYqq;ۥH3Xv?&OQxߥ-Y3%|9h̜f'##E۸Q\o(Bv 4qoj`y=T/dPwaWI$@K҅ξ9P#xv|Z; ŭ>)hosն=ca#;>byDG*'_+䗥E8I^hʹ++J/c@ö\WeUӬR^%kJwR{ZDjR+WT)@+f2Znڨ\+$ 搝 A`۬K-}B9dih(JƯ""{O-w2 eg䃙(4`g`jXVp6ֶyS]XQgCz7yM@E!E4e.݀0ORj|BY-rAchuJ6.p0B'@`aݏfΓdoL@b#S4lȐrVL3MDR!BF8@44 mlnܻq%আّ/A]&})~R-2 4N[WGؓ=9n-x8: /pN[m$5t]TQޖw @{AT?2;a1P&P=ՍGʫq~?2w]3.a!Y> $+V+I 47Wȼ8?!Hљ@ݲJ>223)^vDE3+}/u쁄J<^HA`x[^Y%[% z`(ǢgyLKm sP(}eg3algXwx÷u_h b w6N)%?T~oȂ=wB1pNjbG> +FmxM ;$A3P5SyUo:;HAnAUk\UHC2@yY(kȀ¦th͛I2r}AFp8F__tG)cq&Ob6GZaAc'Y(n5tvu ܸnC*3, Hy56z3Є胘tDNǣz[9t$ge[ԥ⑷; :MT.f1_h[!e|4+|smѳIK*5"Oԧ1[6Qr CD9Jqi8kБT~3ǹ pOM+%Fk9!K*C0 6R5l₂UB0jK x1ar[g'Ma91`ƒ)T/9q a ]p0 .o/ZW9:L[  E 35P$Xg1paeb3b֜og,#|+dd]k^,E rXtv^^'eS%+^Qy|9 !&Rm˝'P{+洢I'0r'[~0` (q'.Y*NqT)ý#) )DNބ(DdF5ֵ p-̗ qو}-B3+.$ǎ)@8hQp:.-H7/+tDc6oW3X:^ϰ5&ʳv&n+l~%Dt?㳈6?50Ymw5dL讛.j/Hy2%>L)ff~TgI50eIQvةTƠ.Ӫ?h V0B}0F6Dg?@cHEkأNt> G[i>Ʉa jSe1v;ڕeh* }ђ]>rkaܼ*K+پf(9i.p(|qȩn"CN !8eI2Id!pN "G#'7xK~5fè2Uޕ13}hR({1߁izYU'F݅%aêͮb =å`XM$fѥsZ{iJc .A֮&O|)% z@n^sVcžk,A i؏Y╓pc]X$/Z ̜CV%|1|*tVGsmo3BQ3$yScz"2m̠Lq5sP|=\ O;%U9$hM^rۃ߬,^=]Rkf-aNޯa. 8"_VR濾4 hC$:e\e@9T? S/mυiu|"XtKQzT-% 980Ҳj/P$% <}1p%_K܏sp]=w5yڵpM&}TKhGJV 0t,0mfV#|.Ot0X́v@az> Ӵf>͡:m۪ߨ_FR4'wʟ5Er|N|!ۭ\RX2YgP9XJ6E" H6ȇ c{U^?OЪ>F_۰;IՃҹœbٯ2}ɪa,.*y;r*S8\H"?pQd#Ishmh^!^ f{)eb0<Ԃ?}2(lmTVН'AW-H)!5T1OV-}P'Ti{{&p"2lž)Ab]&ƞ*ZYhATlgma$C%zAMW_)E)ޕ'IH*WL k` (.fz;_M) R=[` 4Ai8v9Zyʴ!*+7cu&)=&)O]TF^C8F"o8%8{N2c[NY#ס(.eI-В&,$C!E5܏Kb 8lĺ%*rPLELǯx]{,wlp&Vk4P xOVS_(쌸?Lzte"Y&W;fr +PPi <E7wL &MUAŎ_(4~[Vf6§и_t14< p-Q=-R!0ffH_HC0`qcL$)_`=%D&Mq:7#~bNHT$Hn,JsY7@!Di~;LjyHB+U=}USL42 t,MGhf"ȝEz5ۣF+ M=$?ƒ1#\PU_F_ƕkڵ)HКrjZj$Cw-I~9 jX4,|qPLO4ifBR7Z̔ޖs/ti Z2ݯVzK8fBPy_k7cqo0ɏ0xVHT]nw^AA\K0tsq{(TC#j ,Fs's3@rَ Qk}u'i_-Dv$SCR]XZk'.23y3M7*J5*RpQ ^Wj&2.V`D={@a]B?mQ_YE,#e0ӂqI y=<2G$Ѻyi}2i6 }X C]M[/`M`1k$AF^q"w iB-ToWe(1 "/h>n a."v? @b~|wh[,2V*RQxAe8+o/FӖnF:$]EH.0Y7d L~drEHyv}nwTiMQevA= DZ 3o/`M]lOg:49E!fқt,Ekwb^ ~.i*4(̠`z5[]Yex<E{ 4W!rA<@%%S3s^4B|g[.Ks]\CG,E,`W]W|,x=_ }{*C_ncd+[:fglM|i& #V[i! O\vMٯ4sn+)Gr3T€U%|J51R]-y0*K0_7YKUۥҔ)w{uHpM-TJ O4EjH/ BjSkmGJ~z]9'硨(#^[(@ex0DRl|QV79Y< #J݋VP= Ղp4R<l!RYKf(ʥ]4iTR nFzuQөBf/csQ"a3?^uo3_IbLGM>qHX$&Di +8FHpͦa/(m#O%Zjk2`\*+ezZYCj} X?:l|v$ i{qF5 µ3V  z֊>YBMGk:U?Ş]9œR;xM&.ԃW 0shGQU}؟iQpPdf"$ߴN'*;LCp7@R/!Az3Fɏ|ݶL#͂~ ] cL1M>PDYikk%z+w~k Ƃ%p`N|,NL+btivKDtA\B}4Hуמ`Sfb*?5NLżq"1q)O J܉:=/#"e;+B{G:9er,|*.΋!״)h W\7–7E~%ěPԸ#]I weZP@W=*zI"?u O0@n =%Y,7U-+DS&^;jc4}Ѝ0 )rRD*%lvhFkp1@@Υ[S5k0W7֐}-Re{ĚCRc7;#8rN2#)" @RORtB{W8;Oπ֒ [>YǹdĀ]$І+{#ڴl(qx 01&t>|DvZ@-luFOd Vܣ>ˆ2qq=Se.{"0QHiol=$|JǭN{%yŃ9H= }95B3sN;0#)L{4T"qYW<Ӊ~ kc.u1I zfgN9^~K^g+gW1αo SPdYuX ^8s a2uTCbld_Qx(fjYKi~9&hg]bNwA1ZZWї V?Xj#aZ?M%7WĚ|Y)iA$խ+\؏Nzn$E#%2/F3Xn6GX۔"*0"ܣK7Iw{(_&q9&'  Cؠ&WJW5@#TpEgRX*~ʶ+͝ǣ|^&(%b3n-ؕ~$XEݓ`JW GW|1*?`1$H^?ƃWQ}"b1ؾBOkTI_ݒH~N!\p]w?˻lﮔcX갴CXԈ$6 .v46,>kG!\s6A~/ q脦\wz$ Ӊr4ˣNNLb51b u-C;o֥God +j () KKujKgqSxۨk";YhVԼ4,NA1)S3\Rwe'a(tʧwI^̷AP7<ʜ`)^*x_ۢE(lˍG|Xd hܘ$%h`Ȃ„02O(}[;&$L.D 4U̠^2ɮ.͉(WE14IX)%QaWVqÑ%8rri%mg9ߺ7Vʎzم%CˢtP%ng(z!ZlAtj%jM-"=J?kFTd>;-דvz֯Kv!U@x;f@ Γ 5*O!emg9e8N~(j1G%MB_j)-7lZ؄^i;6w~rܴH E4c0a&LWːjsPt2x\5S$ ەw<Ȣ "J̓Щk8vSZᠣ1YII1mסuH2+g:Kkhiװ9=,xMtzvTWx{gRI{lsRC. ?26no'T00ިS\w, i { z1_5`XׄtjSpfyaa1EC)b8;G*z@phaj8zZg 2=*}W{3P]Dt 1ՈrDc<K;5_žoRevPKqSU,[Nw% *|7m ld޸ty̪g -&\ ,F6Z7Nl1薲h+dY"j1I8UE-Iin9!c6Sl\]9**0$Ur{+# qGY i2)-(9SUJ0t<}__|mdVzKai 1 VRp{r?z2ب{`2$/N]+Bw$dQDmEL 6!;oþkn2c_^>gCY('!P&*Bh+Cteu[|AH͙-m1XDlQ6J4n9[ɵWsN!j̛X)4YT"͂ -_vRXsMh$ MF.Swi$B5ڌCR eϯ!eF-)2Ӷ.N ndv7hpTkB,/$&`٘ Z Sf4V̌T!2\„Q5A!E$B(Mwd+qZE&,]rV/' vȦRu6uk"\ӓ3y. eQ\׏.n 3"Œzɠ@]/ridu,.ݭl}RH␚ɾW@0G!Jvp7mF^= pK7~/;)lf|'Wܵ SF[n+JA}3} KXH|nwu)nhQin^xL&%mXjBJm@QiQV6Ey5*}DSD$ Bav:-Nc ܎_kHXc|gicxy[L9_ в1h@'-5Zw!bW]24Wf TАw x 15rE|W8J%c0uXz "Zw8# TvJCMq'OL h͇N~BxlR Dg(^-1EB͌ ܚxLBW+'{(,ȕ0z`ǭ}9iYAg^B7(;ϺyԢ#yK=+lJ\QM֊+s.$G322LJ&F/&;~Xd4C$>2+E1 +Zd+2|pwᾊi $xވx3 3pp0r"nsP=堮1>T.Q~4.帖lƋ і^3+\)HYb.Ho`Ŭsl{N9*ZZ%50vO[ Nߓ|!]tBëd~q>pJִn4$K:J#lJ%;bfsFEc5omU;ћ#V_\tPIP$w0G"['[ӑb mRNjH`i46aqJ#f^Ze!^ _2܇ފFs_X[ N_6?#CET GLʤ#g+h#ܠ ,ڴC} ЗS Q$x߃y70OY0Y\O,:-y;Mjo aN 5P;E3.%P v(eKNjO5݋WfFax/BɖLR-]4Av8sgE%BҶ~&TgA-bȿw=q$鵒{/P=~ y-el `B)J8<_OcׇMTk+yx\5AotSƪ܉W @(@[]4m*W1[䆢;K5//9юBx:u6rriBDjg IR3viZ|0݋Ks ^7-`NĶvy'׫4z}p;p\cWuA/62@dQKT|0ZjEm ]HJmi/"}WJe~~ >1|L# o2 ұ"mFCT`K²GQ zN໨IQ u Qݚu j{6ht>D^ =F_>f`Nfi$HԮ ѡy ?g=i >٭XIv%Z`c0P#l/}P V_HtY*2tjOG#5Ό) xKE+.3EOG99fLfG3"L4uf$#jM[]N0`a2~i;liɬ[)l ԒVzvFL:d DнWVFP/g0P:mZhz6TL`}_$-?] xuAqP;.iNI wEqri3fJ8ŠU#i9 |d;%}pwu:S{V sGIe7MnL y3'b U DCUT,i &/,._/7cuv6PBH_O 0B[pT`McxEMtT2O"{۫Ik>_yh̓${{F/-Aut:I<_`z;(WAgNIwJ_#1[EG`gkP(aď| DG{X֬_m~bZ2~ f.XIADń"ufe/\xd&^;˓lZHOjΏ!OFpfo^w'5r`BύL~XzȾ{FdbV!ޫ~/(ܰ<]!ՃP6Z0YTI 7 ,T07sFFQ9Olz^[kg9 {i}+ej1eΠ eC(5&y&ZH&cfn 2G\ðX5L>A:KgKpqS-o|ymA% +7?i'r"zU'v Cu츆'3}-ڒ:H}K vֵz5'z/$A=/yt$|5.j?sc:*ufIt@F. ϥLF`WgGBSkron(L!laS< dUE0mX&a#I4tldX{z:BC\qPXV+RszM@BhO5dueuFzSK ŐO^np"m̦qdrOc)x*Ϛi]3SPy9\+eCAg%%%ʠpfYko fyLɩ< jd@wpvTF>zWI"~[QOXO$cQַV}#/0j ).H 4O9$;*m2N%1kdb S;p\; 'K^ `KVx t6(vlrx2+j ITܺq%,6A K>POR62 ?N$Bb !OfJǼ[𼎆8Ș^DF A1yVr}L%mڀfeI:jkqG䇂r xj^~Ѷ5X"lō1ArXHNST2~ke-O'b̽Eγ]̭D)fkDBB&Zln}aE?cf!kl=#ѳƗdcB/H/ M{G >Rh6{Ioc':Aj@xBm~pn `gS!n8'f8&~(jsHRHx1Գj:Ӷv,.K#b.B}ƞrt>LOY% N,LPNiJqTWRAı6&v/tDCjZm}9V%s]MCZ[EBNT!7&P!ЇѤbHJnr_:Յfk7weOaҾs&odDlJne PJ5VOI$2lޫFlL#ֶ57` N~8Z̅)B0JVeIR֕'.vfeuG ;Dn<,*h">YMUr>ܤ rWydmHat].Fxz3wTꪻh-.`T=G.̫ 4g0E>BR}q؇TmurRS'GVwX<^=p. BEIZ;a!va6+8ׇnԆWj&zEPE٢#1Bgz**FM1^#Gӻ] #Aԏ>Y!2S*Cs1DҹS|'ׇw?8.7c;LI5n8P_n%z-o40;Һ\.;/ )FFlx퇭wzSjI$m\"zUV7uO ^z{:dض2d',̳')KSmN[|}5c_|F}tjF!;3jQrؠͧ+1n/"n?hO(tm4;+\ώl{r;G'U"uQ>}\8`.\W831jϓL5 Wd(T(?J̢,Y˜ vՔjXS̎o#G3#l}۝Z%\>ou|W;X!%BUgC}ln`t6u/i=P״Ҽ.[0{9tHQrzEohL~pjgĆ}$pԿE].%$"pbu ~iTv0Qq_ϽFsv 59:[ آళn &a&Lk@q71;#ո(|"ծS/՗^:XCwђ冦YЩzlPX!-AMJɁ*ZHh읿Krh)r.>F+2$NK!7`OJWK45=zklpm .Aale]?VQ!9w s'vUb{K) u1+vrfW46@qm2Hl ےC|ʛ2#}_ǔF% NjZnI}+}5a| $=we?>wH6}<&{還 QnL )?UJ% CT8hw*DD/,6|w͝<x,x^\d.Y*7"g:5g?Kd?>#J푡\IR'ޛ %΍C[-U}dߓ[r34[*zN^ʏ:P37216}.`:e7OIlmp=۵NUY'kvF0';6P @xǽcizPlJyŘ|pV0a%!9MN6?_ XU~iϐlckack/zܟޯfU >٠:Wv]G[ϯS*wv%|H`zD%ߤ[%2s ۃXjtG4m Ȼi?~MOapS j+T5)C IX=Y5;4?kOw7DTׄKXD0޻_ԧ i*V s==qTcP c ,dWNr]J,zƑ p.-mA6\hÐj[*t*Xz]/]ې_t?akoM]h}^Z#>DԉkA^nk~8{ρMU$}F U1}}M"q]/>/ߖi3O`SRAt!΃.pw'xMe%Z{՜p1Al#QAT ht6DmǗ Y[;GMeK<]A˔`R!m><\i*7ukJeƀn >*#JX^6j4 :U:ns '|VpiUl"`ىs^k_OyU_{pҘxs^pQ0ro!_=qVRRR9RQ3<"l~8(ZhZ =q~$߰##gǖ$sc&lr/xJM- k``FCa# T6xuwn͇ >2G"tG _cU5}负M$WS hg|@]s9&/HHX5g]wi`g0p PpM}XŞt =Q"oJ#4a*,~rhS˯+<ư+A3"xNҝt}') $ۉȣT@% ] f(X;)Z.:H t򿔝k\A 7M%|wq] XA"jkRz#$xxZs SC]Wiԭb*9IjsXMa.Ň5NJ%"W4RmD}8H _wӹ`@k+hFG?#fa4>Gͯi/aO Q\@Y8zhj#c463AH2%`f5ض{ e?gaM 7O1hdTG?i%Z9xF*ĴaHM~T° z\/6ԏc~% @-1Z })|0,ay獦bI({'0Kz"!OʒBZ~ƜqH}u BY'Ac2-ďM,ŽPG>nIJo5Cܺz " } 1&6:'`XPGH~]ɓHELf@L>@ p)}#;..ueRhQ Cڑu&+* :[Xh7Q%W.|&HFAv+ pƣ .?J.VZ]>UAL<3."cWh^"aiJZC6@T]6t'İo%Sܫޑ1/p7dv;絙G=6+#&T12?8o6[e4*#̽O`i4݊uWTpN6?֊پJv2؁sHB)YL|yCO'왜ͤt%s;;ŐMu.nX82K6 9_b.?E);.2{:ݓ5*7 eQ܋}φ8T2|35P=l|TЁu{mB ٹ(л`5Z6T]FX=)DE!De/yA<51_)9CWfW&s/F6Gmysk<Ϻ]S3^s5R!zZ'-بSq˴;w,ҕVJRM^Mwv#B/XO hF3~ZqIgQ3bT)gw`Q!&Iry?PM9FC3{[Ǭ]#^v~[P130L<-rXg&l! RiKy">R'XZqA,dMsxTW$dպ4铺#A>e C%yMИ,ƚ㈡b*Fȶ`HoD m(Q 5'^f*6R`58i]vvc$콕LHzrfkǰϢ̧e D(QlƵK3(ko~]Ξ[5s]aub:i"l{tm]`7$?` X %¾e/'<9/|ׂ-HQCCҐUf:D=D?bOVi*># SSU|lρalN'_U1f]dIPDOVRA3xP %h"TB5͖]'m+D}EGlGvT c ՖH:8jC6b4$QU-Ӿ6sOD:cN>MΘu(8M702[az1X XV \J{U2T!9g\u&4CaZF5α!5׊#:(XrU86bOD.qL0Ƶ3x&+#qbsK)͓8viMF%xV3mS;&lV;sўNr3M}i._s){ŮShePzKlk0ϭ/,@5ʯ ȭ`EwwH>wAcg39Yd,'Gr0Ud1sW|vxw!ycY9 LuiTtT՝`޿GrvIG#Dy GKL U3?ޫĦ$?L |0{e=Vq ^fN՟!0Z*劋Ȫo W%LAi_6?BiJ|bjsjI^nOvTIakќ _l7us="0̠Vz=.%Eڝ]m)ҕ7\KzO94 z_ 4o~8;ʳQ8:EDg5 NEG总ͩ򺣃Y[BJ|kn b/"O[uf# .?~##Ɋc_111ei;wcSB@zA ` QS`=u5q]uJ<^V -_bwƟb\]g-KךO7oW࠿8`F;d}@P 49 ur{t.vI*u0lPW6Z.9SVZ)*V~&\\N)v몲.#˪"G7#Uv Ƌj(!FC*nsmMXzKcMژ$ZG~^+1LGްPϚvtZ&6~_ExjHQnxlUH2 /|S/vCѵBC^HuUDH{K˖8|IgXߪ=Q0oay;zeY0JVS z[KeékW:9Urj;#u8  (\ո{jԍk,c,,[. 3 %JJWSls$> a:oL@ڪy 2ڇڷC"$?d隔xhL.$"`:/RMmb̡*W!eթpJ+4\I_X\2ᄉ~pڻ7xt: ue*Ix di>@g5ՒUd G<\u]cQъmx c HrqT&Nrn=GbP;ﭿ0/#1yauDm}c z7ά95W{#dk5rhQv{_Dpj5zWź銟bU[BHlwˁҘY&]}6Wn2#n?mBm^>t%%GID̪7fV/OZa8L5[Qc`7ro֡_ 9ұ|yӬ1WǍr'' /z^:jFG'ߤ/K\*yܾH!Y;-VD.9t=@PjĠ 4B#zq;M7,xV]4 %:#b'~|*:M L[Ep:(XYNOr!Ri8]̒e!,A,aU"izi5MJ}$|iI,'}6q@]%AU?kHՆxByj<}@H465}Dp8`Uf qAX>38'Xܣj~@n@MkEixx?Ћ2~:|(S[Qc rJ{b.D<KohjfX ot6w*8߼K4Y&$P,Y׃8&YOچ+8YS%y޷MsGEѨ";! 7R[eD/mdNf9\rmNvz0Y' *M}n؏3h\@;f93qE@!q{`Oc}ނq"(h_9kRvq_]rR;K;TИ6ȶ୩LA_}W*X +4*}E^}jP_u#e)aƠNwn*-\ְxCZh9}uGmh 1V|(%= Md;)#RHV@(Z *XEZXoqh邼ӛC"~U9%"oLI!:6X$I<= B#4g aAldf+{^|:3AD ׬*:a&o-LBll}"F@nՁ/Gg>]|}5ʟG`׫Z|K#*\yOJ z7?l%0a *>~mb8]{i*^>Jr-+V6 $~w%! H'(?BrC'"5hhҋS"GO I3JkB]El?k~+t]Hk'w8o zb`x=A1 c26 3 _U*vAfJ^m%Zz-JFx=%< 6Znj0(nqi"ƹ_mҼwLYDz IhLqlw3IÐ '.f~i3y7@fv!G]%VX"b&Bp YgEA!RNN~Jd@gY(ӇZojے[Bȯ.*$ qJF/>m *8ZIWk:QA*R)BZUmM:e]4a]9KDAU}~c {v IUgAg! zʆüUDa"YA b50[2*0Cl fK[XI(suV ĿKY5lE)ofFA%67m[#)5V$ 5HH:U a2.VpU%\o .U5TIUV<-Y4V G"ɻjxFLFIK;@{am`fT ! i@ɮ8.&۰3[Q8Br.x+w5՗)_Ca^Ԋ+E %[: Kaq\d'HZ3XXOy1H~=V\zTHetN9|h(~5H(rcd|g Ǻp'B-+XCY*m\.:dKj@U`-0H֔֠@9l7(`VN{Rҷ/I ϻ1/ WV] +r쿊X5x6a&uBPa.3|vH55.QSyb+;U= {Vbѳ$Ҽ灣`k?%?;% `κ['Y6:N7 bSb?ŀOb (;*EE :[9c+"9-aj H|;D⭄EnjiY2>NhXY yڧ[j"${[`g:SDQC:c?H3Zű|Н{mJk҄o;P|6jQw JJYxh&!j.|np q2mp6y^zVp'ۢW= Z-$] Ή6pv8=T-V%J:9Z6uk I<\'t *ӱtP/R]v)54>.ʾNi<"\%gD6I"!u*{8㊕ޥWIo+;;Eu SR4???DIpv`֕EH™qYy\x3TdCU!:p3A-=;;Eq;r=xbܙb@ie ǽTLJH-r=AӾ:GkSFh퉶dHs{%)I@WO$9̐slrc)1mJ0yAN.w"rqMWLj Xr<*+J!(#j5БݳIǁ E5(x!Mߝ.-qґ[D27!hºFq4tE Z%5#): AEÏuA,bca-M gۄ#~,l!+KJleoк׋Ƞ&T\)RDA1qbS4HĽ'q*K]Õ&1c+lwT!Q }$][^(o$n,|hy2xsIi G Se L,6Hp,5/BOGIUf1u|>A™4~c{q&SI\_hf`qim#zG<2 Ǵ,tp7~10%,tסM!rV q We--M Sfuhc[5cDF}Z\EJF5Xs%Yu`N2-v䕂NS5 G vdn 3vmZ-‘ϩG3L*xA-`1RuxLo{B{V`ږ}";ue9X hM^xWHcg_Z-Wb:lY-7PAȲnlj@aȼq̪>C3JiJ-@-CYyD7.ɦ3P#sN(MaQI20Iyf ȣ2$ʺ -E |k]el(žH ҝ̚m8U{3s?.0lxiҵ±ǀ=dEl*[y6së;鳐R>?+1 )1X&FF@*N_)"KV]sg6 M+6v:ƃ=+cd-r#ҡDI)j \KeR;{(1~+%n_Sk#br e'Յ. `1/xYݏ3N91QȬ)c_NTB07;@-d'y@ y s :ѹ$P넺pB"nD5_DNuV+fN[.DtvozFv3i=2J1rK)TX8&쟗y!vO!XM|";LZ0> JޕL6biG=M9)NBzʑ.=ne_顃}+8Y4$'.#ݚ5n'ů[3#l_6*Xop%oVY<ֳ/[(z0nsePcH9^UGslPaw)vZADZ|ՏtL9p:Y<~='C3D*'>O)?d$] SisgBb5f@no< (ji{S| l}sӂ$AATAŴYÇ7d|GQd}`XOŦy룆X\wh["NnA_Pw1*,7{},us9%TF8яE[XcG8zWClE4j\;q1SH; I?IGwbQ»6hRӊC&BEI\Ы~ޏ+X+7#n;ulS,[Hl .ekUU$s|SM4B>D_Dtfó:in ~ng]]D޳64B5m\QG0 #iԎ9Ǚ]Ea VhL>-Ҕ?a ec78{I4SDX,ln(8,/LlCrx".L}VUuV5򔕬 o=Fa|h;&?8D`͟䓆p8us[|(& Ȏ2lm %ݲbH^"f0e)j#q`|@ h{u]DZ$O%W&D:~S ^oC6z7([:%MWk;ގnhSkU[U{Z"$41ychWo9 CMV>W)Y[N{8`W ?ηUoSjWL\j71k&͵Ӏc\q\ut&h(?(yzuB*bSS5Dd7a;h&\1o>*sm~cq8nr]޷vl*m^{j.D֭,eZ\uN3b=;D*=<%t W;~Ũb$-01QW"6a1#+hw}TP2O4ngnMeZ"w?4ub&?;F,an> FE~7\K$8(2QRɊ qo;c4u#1pxI~(LlTFvƩm>!C3e$ͤ[%lxnܠ ނ2`N)Q9{bNJT\#$dAR'5fN{D٬2#bͥaEW'gHn{u1a:)„~GWpu.b \49!܈\σHaƫ5+Yͦ_k'Dz ]v#t͜2ĕ"6_X戝Vn`nɩ 2ZۈG{ sf&26Wx xE?jr8te5밾nv>z7t` ?k+Dz=2Z7`HkMt? =cblFQ>˗حsbER>S 6ЖJʬe"dyfw54lLKж3鑋LOٗS* Kt=#Nںowӗ¤*)\a$fheA{%#a9caohAM̠ݯLs]fIOv'Jw Hn`u$mꃕvKΆ{Ÿ,LauMUݨs׋AoB,7Mhi xJIȫtWB,(+> S]y-;j6-Ć̱AnsD̊ mzcMj5YDB*誟YhDǝ.Ouフπ%&@ȨjA{sfdxvg3 5S(NﭾcXdH%Kf[c2V'Z [Jx1 Uw竡72Uh7Igfn_bPXXt!j¯׫a"̽p.NϩLT#gWʭW~E2BblۺzȘAa[<@ŢV=&eD u鹪:|GF""CLBuU:]sa[AC8 |*ot*qO d3&~3!_?B;z@%8 k]"W܎tseTS@'yH`G;6}c-|e'=%4j,knLeEXMٱ%ʨ^$F26u{d 3i9G'4OfvwYqT 4cnmr_hSCي#Nv2키1jϿxȜ1^iQv/'1qxl98pacnyJ 4_vC]O` `; H'ɩUK~hpS̴%B9׬I"81RKlJ[0[.g*re[TDm >& H1<܁YQ-4ۺ)) Rkb/=Xxt)@'pٌXR2T7~1Vm ެ{vþu$᎛,AP'K䵐J[L=Kܯ b`~"In9h *Vî)^F$d[zh?%l3꣆kau<؟pq)@\d] S'$gM+{;K &'O7 +8+lJ;0 vjJ̩8Jbi[y] `e*(:8n(,ܨ" ߄6uq ia>%wdϺQ$, (s":+`J0c&<%8jo PJFŻjwy(z;[ ӄ:AhlOf~T[;6]kQuߚ AqݫtK1化܌43A>$Ke@b@q~ŔH+y.t4@"yjQZnRK8T=ec9Uܩ=Nz6aD&C&pSDQf=qQu+CUiU4kY5"QFBer F ȯe0xE6g\N(aw]W@a1zBT7~rɸ ^<{nbWr*NhE: F@ӜFT9'e%[+hi"L̔B%)_5W~FdsȀqZ3Qd-u7?^T>'&?:_m+걉,zk;e̾W%28ER gilL@cICB+*?0d\vW[z$:ϐZ#+mdUu!K]Gyv[¼Ĭ1r)P2{M-{Չ@k'۳Eo֤rTA*)@]9çsxDk*~U'ɣ9DH(yJ}-c`KUN"Wk[kۖahfHt'~8ߛ0VW1C5 ȃ{9@IID*U[qIcI$e\޷45J-ش3!9 H3t9mq$]I=9G''"³pdq:ޡćJB3Ri~ e/L<˸nj-waJg@Vq] ȶ|-|[g?679&ViAu}=``HB5%m=J,OtZiȝlZoGTA ^E<7qYpAJG71[~EU[ǧGb7ڟr0;&īd'Q*p7v6 s3?e* ]Ū#k5:9Jo׸M_R_GTv)'19`󅪮v)^b\d.;gXHtg&m U3;9rmū x.UZpXPyS=6󂸙"}kWXL뭐[w CqsFQQޞPS7+H&/^Zᦡ$.HuYCE i5#pΫ7L-n;V#JsF0 |x ג~!xJݳq;ّ4^maUim*Y(ǿs_aFYjXi5,w$& 0df}>I:m>mdtj%`)',%֗962J;öVqh딥Seai@%kB[ %汭 T5U-t:ܜj_UJc- n=[?'Zm/:S%VIPJ..?Il74j5}rأeA:6zU<=S ?H@-߀>j3exi{[]p)Z.fVDZ7!pONݎQD8\x:t!|-DEH7zG -02X4hu, R1X|W83^=5<jDzQ*pkeF/vooX.Y,*bK:ϤA+7mA ш1E T iR p2ZdFUTv(=2鎰#&DS[|Q%r4 {)Ŵ̴9Tp˥P.х]\(fm, ʀu!Ou 5-Uj,X 8`B _TZtuU=u:@OEW1WUӅ.PD:0JIh1K-핇Ta&aP[ue'vCsqB:P c 9:IɶxÀ ya6aəWnTiDZҏ2QAdf"iI;;prI֎Of@UC;.SA;׀}c-\N.KǂXy \lԲY'  i-xCX)pyUl&#^&\hRa{~۟xt<[D/hGuG,GH+Ӭ(9cgq;r]TPB:P܉h6ojҍXyaؒe[ĔK18ydt#r͡C}2~׌5|IԈt7K`AJ~A>YN~g/%}VCXi|șecbJMp8Bm3⾞mږ޺ o;>sɗnKy\q?#2CG63\'?d{v{ʴ67!֌xc(C8GgÚY@]|$V2&l^_ߜO2&B[骿 EmMc靤 ܜmb!a9:(Z9q+ƿ{dl.G,Jk/exöPOrBnVmEW(JM\@>h$E,'(\taQx@WsڐCeo'bM6ETe.Eu*j+)̺j@kjfjoZig r]l-p&dZU:Zڋ_7b:ISe=($24( "\ Ɖ/磭^0ZmǚXfTRl f(7f ")rƤEaXM. +ze*^i(0O+wq˻2H tP_9;F30MjTx GCZ:;'Ő(Wkfy7 PI4v3hdŢRrM.Q!9,w(aͮY!5#7KGS ۪H~oS< )zLl&+ih;0o3ܙ-хw|r@5Z5[*T)*D~l'_ԥscčEv#WRy%}bo\Du|N9$iKu0DHBf36oo'׫Ed ZQ)>9:ČG l+(ov$ G2i职x= 9OhY RC#e#kg0KR2lJ42B9|/++g 3ӓj͹̛U_PH&OyD㳨tx鰗UQuP7xcSܥϚ|6:(ϕr%RFwgPP>▗pc6qlm kt:o<$U`:h+a~oF|-HRK?gigI!&W!V1')\]X%1ϤSBWb|n08ehb ^̺ZP߷9Lm̯o|*ڈAsn*-Nii> Iax.2|_NcZG;GKUR'[ᄮ4VhsQ6 z'dO9\tόH!HFh~ _V:'Rzmqu#ɆBYTq#'/Xu6 ȡ{<(T|OY.\#SQAT=ed9Gvf&# 6Z".5Q(Rha@$$W!h!7BZߟR3t:Dv8ЇNrħF[STSa7^"s49Z~ ][3)՘s@(Hd\D\iLU:i+6ZNk|n> 8ńEҴV0mN[Ɩkc2$!(  3D0wh |s_qCd VizbWh-؝e{<KM^CfuLU!K- 2q2ޙ_O,'d}9Jjq=.7F_CTEq\x?2-DҸ̼;kͶ^ukVw 9;*c4)MEPer +)d3AF*/_1TwmfGpÊ~>mjIQ ,HV*\~^(=!nnH`Tʚ.acl3`0{يʗ,#Eu C7DHЎ^wGn;jh4Wڬ99d } t ~DWXjhTgd7H!k>MQ# b9@j$o檫RrĢ~, /+$|/ᡪZH FҽyS>v+^`'):h&=&]~,G"=#(ZQ]sߗYə_НE1][o!!a]C#&#@ƅR`/;HriٝgL0GRi:3/ԛYqžXY@ Y4'd24x҂hZܙ5@A5(P'V~8 {?b4}F^驎HQx!<0,.] iSȈdR(Ďz{M&;*m3srq+MX0 Q% gj_ @x&9M̏jKOrGelَTғשGٿp=,F;d(;D|`ՁL{`r q^H[fF? L1c& ]%eͼ#'i|f骽$EAXhNX?+{ŗ"d^jq0Tqi~%WJȭMFhEgBk!SC4N;sb^:lk8PnjWS;o RNJB*Oܑ-ɴ) E1ռ`Kp׬ٟjQgk7`|H\:WİkNf\],w[|8{h>4z{YJHG/6aIHUe0s\ (֒uB#gÏv!×'K\zCH:&z E5.8TNbu9%͊H 0>+^N3OI7_#dtWɎQt1U/u!Q<3NN|yUJt -/&H\Ͼ+Puє^/ qx6ewǩ=n+U.ZM ү;|.!m ( C3JtM~յ7PGT%iUWP&_vH'v&Y#ҹ}O@9gPFf2an.slsm~Ɖk9yHezsiƎC98"ɍI؀w@>z7%/?n>j D ̼눉}qˈFy '͉.Ҟh%Y# :G=䂍3#_ Pyr&prgY.ܬ btȵtN _>]eIC{Xn%=FV[;ڿe!zeN2z볣}'zs32Mw|4}_sɍs] v 5;C{:7<^Pbjvc3thxch"gWA%'<ȜemBjt"nH)W3g>~)X$ʜtFY*`8)kQ2faV5 &I/ Gj/@*.)oAx]E~2BOkQ\wƹV[a|N؋M;t+`T -7/L`fȆ =kyvAY?Lf&qA"q*곇Rp^ݚ?1c!"K4uчg׺ZO._Kj^,|出PnL;nw^niQ!4_(Db* ʩ)AWʚƄ+ʟ|BڰV1Z[&oE$IgD s_ԘX A}T+U憽Κʠ1W+@ mteB4'o|ݾ|'SxUʋxH[XngχvBIZ-v[珖"|UT nz{8qΣQL)xgft;PR Gy*ΆGpEIaS6DfhVX:̿rV^aO#Bl eQ1'uf5R`t]jsYy$TNxVpF9l,mxyPI5O;( X &Aظ@"a˫m}W1H[3VA;39*`bSj]zr_}jAI٣l0]_AYm9>oW""qAu%%}IL0~׶U'/HR9GS6H!(0jJ_>Gk@t'pm2W21,y&OGς9>n؛RĀ,3!extCF(9M yNeTrh0c7/ j0Q7D4r7fB#NWNÖݎx00E8 fUs%@dQ\;_؊ϟSH.t /Rv1FpP9ܧ,6.(Q/q}9Ywq9, %2I6(gd*()FI%)AϺ>.SeƱW-ݐi!!RDaa< Jywk(38!*>Ҹ+#?3/҇xᑑjo9#bY,`%(:\@}*7!͖@sc1G>ETԣzbRǠggG¥i"UY剠Wfma*BkU]|9R[cD#u( zxWn 1qF_X5U !N@ ^uF8uUge\H,TOJ9&N5ld G4TJZҫ:€t*? !ۆa2M*?5d%HP qM̯>Jۘr܆sx +\he.;DQYDEW*,J= ]YVjtOHUD؎4ϴZ$lYxw^5D&,b8 >q^{]5:&h_7%\6jv&yYY b`4yIH$R`o(ΕP;TE+Z!w^#ꄪ n$ 995,vp) Zΐ&p)ǃi/۲]s7kuFG܂ &B0{͏e62񠉥7t[6¹{{''[ЉV CXFRqԤ+ 65H(]6"XKzc_V!K#[OXKxn6ů%b,~$ƿ.:BΔXl@$YS4̚"I\ozbb=5ٜGƑ+rOjFqEqiKY,ħ^|P}*NٺoH:H3f@s ~L+5p=*rx'mn`Mb629j2U;L6?AlH afkF]IC"ߝ 󏠿VX<8f0W *C$,ԲM4Q,VR7ǒ7-TA4ph\܏D<j;Vt{8DWg6%gC ['EʖQ”߫Bi0_-GOtyavIyb3@M\$VJ` joי#dF57yd?yyu68qܶ)j6 p&6GsU{n1!V .NFC[q.lz!SmӚ$QQ[b}듂2!G 'g)`RSk =%LEpV\"q%wH>~ڈ_@T7RoFܮ݈t3hbBJ=C!L[ *a¼+-t2N:$Nl/pCnJ4 })ǝM]ʏ]M#=[.xX"Pw̓.xR^i{b:NjN׫G0+r& \4`䊡6f(E&ֻ!0?P>PVfQS*{.DmIowpD}8:)waF:gčp@3|xR ͜Ԩ~4Nder>"4HIk8 ϭh?ţbQPW=*$L:W*.nJb AF~1*y0֏bS/eAOz u侻WorE?:+_ C'}XM?K̽.{iؙ`S [R+C[ 0dUQյ?aP 9Z+p@nǭ՜W_gX@ld`ܨ3}d@%?/ ui>1φZE`74ɲmo'eZrdmM,;PӖ0 kP2rHW_9 *S{ //YBw7wbW㞨3a<9V|8&徒v1ڛ VO!G%Ow^ʿ"Sk:PsՁV9f5i #ϪyWNBJ\Z=(Oqd.NA̓G}onij5~/5t?5Y {Oܚkg㈺(Z Bٷlb 9ʊsM3'aajK)Rl2 ƪjE>m}t{K8ͧ/ñ&޵6Vl&~ o(VO_!:wrYo_aL|EDә9*uo1"x{ű~Yȁdcͬ7#D%oxtLlZ@~dKj}RhgwY~'C<@*pgs$OO8(4`[G}U\\ډZأ[p9+hwrh^aG$^{rK*_ؼSW^.W3ۓ_4-'@e2*y#R 5!QΪ{1M fetFj/[/oԕ%+5B^U4-8rz" ɵke\!:ĦMepT_ [TxUFӥ ki1OTTtR <ܞ2ӭ:zX@XKt3ם9T":⼵-Z n'f$,Cx8gO{`a.zrH\ZۏҺQ[*P/!|SA?Wc5w1}l'tU7kSlN*(fbC=I˸Ag85 i$t%j $,֝˜/k[A4^rIsECm l!:ۭzJS!Vw}*=?k.K)K[AXŸk-@gww( Txb i}Bݞ*B:(EZ?lPoJH#hb?S<4ZbIվ/䒎&xdb{)Q9hrZ6J0;S 4m!l~lmztƤևNXʦP74/NC@ǨIIj68J "Cpl<.AfJ_a3Շ|RywU& PHeѕ>_wzV<Ɖ*! VȻB1Hxfe>́[^_W-7e$0v=׭qe5<Ozb]|+`p$ bEss_0գ)D&U@:P2fR'D?";3[C2?\ǣ_\a׽QJ+#j!OG3 [} X'4TmmXqVV3z$|htYJA/fIò!&nGXh0[1VӟdnWۖoe GIU&J" Ⱥ~PUc2# BK4$Ɣ.{v)Mq"/|9=w nfא@l\t"\Z8W]߻4p:/OldުiP%O,Um_5n]tvŢx'=~(6+yB))$!0%U+>xl{0e ޅAlO_Q(KK+Y.:մq3j~GSG`D PIxWĕ|_/g% )ywP_l ]k0m/E-Ц,ܙX YeZBcj3l’<|Vpڈ[Ǘn6l(nAymw"$ â7-.C:I÷zngcL †ȳd7/{"LA 5`3MuxpԄH4˶wʐ L:ʠuJ&gEwzG*tXz7;a7Ac xeX}DǰơR@~2""5 >yWB NxKX{|&'RN}JC!O?{XԕPW (!dFR(s t~O|_:m[{no.j`I®\9:>2L˰GÛ@1TX#KE VykhI:FS!g<1}MVsv'Jf,y7$d:ODڭLM!<nSPZi)ݰ08d8Kh^FF5^簍|Dr+6i ?> J9rl .Pxǹu)Rv>C mUk4syO6%jF/Ca$YpXR#ku<?Xxr^H:GQ?wpCްg@JNC18yJi;r3.hH`IqM,m|)~xeG{0׆ }7cBn퓗픣]C`f83SOYCqA߭_w p'2#.=bK5꣇A4!UPd89\粹AuZ_WtV̋t~y3wdDtb_@x"RDehX9=:JlnTP_u[H, r`d"ڧufh-Ա"j9Oզ17e!QYmj S4Ehl1K ŬJm.!d~H%L,mةRR!\KAuD8u"_w1IsnY _k|?_.7C? ,Vq; ȱ}אJR}MZ|1캜iܲ 0"=Zvi_:1SPqk{=U.@N0@}f:0,%5|aȿh57K^J.o!T ¬po8{Yo!Gܚ"rfW;9 :;Dkgu*(h>]I0`d|"](А@G.{W'GGޥ7?V/{(H猃lsK8,]cԓMvBv.=C<`F{|`oTJo2rm.+!E.:폦keC6 H%IQ3=2ch E^ȢHo~oOi">)כ6+{Өf Rd!D[JWu%DmH_DTvt흀g@Őʡ]agco?jsпhkN}Re_#-<~Ί{Gsy[CN/ge}\)_.3<n< ad0>kMs"bLHof>^S?if|ceuYb+T68u_VjPX/JaO Z٪dٺ,#qqoG %㶪#H(WAQtGElPAܶY#p-mp1oAk*W9S>$hÞ.~\ƹP,̱[~k0Z4-̬'\摭b:kᤤnϻg}:g;36vrqI犮)8mDCW`,쯜iX@(tdz THɾU*&1Hcfwߡu0Q>⠉R<^ֳcxQ@K`DP ֭۠pL{ U44|hȿsSBeG<!uE6:RTNsJ}z_~%{T?<0 FɊwff"#Ck0Q;a⎃jA'|7*zS,Wo-+Rx<\*(x{LĝHR) gS"v(>C:`v:B31h6tǬn?dN\%X'{!=rXeF+>ˈx?{QRNh0Ի',[klAXܯ{{rCT d)Bى#Ck1ʜ /mKxiԒY WA 4ұ~0@g&PRS&J3>ؚ܈sX?!r59_\_eoX.p:42"6X>>Ji^dMAʟjw퉠HdkH[@oadOZ{0P/葮5hq $GT"[QȠ 9QBd]IT34Wo_Xvc #7s+e;T-1Qf^ߏ@C<^h$ݯ?-cɡ % @h1=F UUɘ|3n9u&[3|+ ' sK+f* fĄ ?q) f[\U]7Z/IAB:oZUH›wQ .B&PSxxբ_ڎ ?Jvi|X(syHGm+as!eiȉPsH>8=g>a@$>},NeawfHl9u%hQBhx #VK D$} D8 B+6S'板}}yݲM; TYCM۔CC7]Ѭj Ibf?D%F'\5 _]CZsF."FӷFdpNejq)n!7ݷA @h5A`GbdO,&=9m봬5g0^zMULZ2zD"KM,rwja(m>"磺+$}I  {: {ki4}eIp/6sF?]th0CFc6?.r=$<,;^Ji5ky\JV |%A畃ZMS|Jz2vWrwAj"]q%eta'}dSm )˴j!yܾb`Nˇ6aPi^k9\tQ'HA]2/rDK N^n0x8~kgXFGkƬe[Nxe(IҜ45x,iw{Dqj}&rĨ:]3sj.FJ~2:>Sr8QLQq9 nVu NNEfj 5K7E!KOc(g)3( mF4f<̀څCf W5kw/0%Lv|g\;DAJڭ©cFVNn34ڠz#[Oʿb0F1k:w~1i ߅Gk#lөCNZFB_]UKUOm%tTBm,UjpVqQ6'3`<-4#u4n" @;Cv#E>䧳<]60#mc>϶W)Ʉ~H.nL4x4\,Y_VO (R O *Sӳf~]dɑi\z|cQ-[^)?icx/A쥡mr6kYB7>M=U~ :YfJ9laXlbzcl'-q\wTQLDtzql6i{!0FRezkL+.#o,״<m"&1hON5pq00r4 L{3l=Xr3 ~@miAKMB4puZRB'e# yKjMh0J`5LlE,NVOiA\#bd}#kdk@U/l"5QvKNj/|[a^ETҳDy^*ҏ#D#6y'EsҶ\Z;k%''e8JԟG.zvAbZG `&uݢ (|bA.#24X%vwZw9WY=FA[nyU -גPy [׹B6aM]lu]sYHѼN0j\$40`2/e<Њu2KsR}elг*pDF?&.ބC-.VWIYj3%)_v3-sκݺҦZA@gmBIElى!nKt'̤_k3r!7@p6Yg5lu?'#ÕMύp,sg|;ݷ<+gn)X4hPBQ=%GspD,e_PӎH xUq3<1.4(g*kl8aHDa!XȡEfiWt?jZD|ӣկrN ׏VmI c՚o 5חf{K>9U.4֔yG>9U.6V?o%1;|f9EvXt9dIts.VKeѓ|O=3mLȔ}v ,{+ɍZ%A;67E$RQr6gGvCֽ8[rޝ=ʬ ʏf9 BY(`Âbez79s_k'l<=E`& d3<'_^^<88yh`Q#ժ%8BZ Hb[;2?Y/v_kE!_ܚ,`Oji?M&mj7| .gӉlObM9s⺆}u``PG,,?y}oORLs\]̩V{ےE 7AM :\I,#%x?ƽf՗F=IͧSXi~@^{9[;@zq7HX B[pAQJRL%Ŧ{ƞ`.q1_}L ɩbU/Pr+dM{^/&0j !d Lv X?D"D.]Ѵ-=#m 0kgU--DZ0b•} 4*q(j-_4PH zOG˘.P| ju&,EHxݢ.[>I@%IQP[jhZUD$S@V\% lWPiq,:A  Ҿ55/_XzPo[HmOTvR9lJlptL(t01sKa/!/忈T,4sRCmM{f~P胿m~/z7j dVblaswjԕ+ vC5nAXk; )% w#v)~BQCsA "3Ro{χY'EE#H%5Q.aMs%wT?\xyaGhvҔRY߂J/[9H%=tAX;!s8`pw$k`)I(ہ:$QK錊Pk"}_yW*9#}e!= c~ ZQ_JdzQdv+ڽ>Ӣk|.~W1dUTQŽ %z#쐺SsSzIFI`2嚰V! i I ƭy^zU2hkjIe2V"WM'M._gc"P~;Q}bR 6,[ Ѥ.eКuh|J7>Ä9&0.otSPuCA3=].,2-LWx5F P)kh@8AA)wUzgnG?T jka'&tN؟9⪦-=gi5,d?`ʫ/CjilַDs8F_x ßt~O95șK^@dႠJy`M5B'pK3\GhMbf\C w#!/$mDŽT΁&U75ـ #&ޜiK9vvCA2F݄l3Ce`Y {V(EG!_ySfK[eX8G=يi)!! $4@dW&[OB盏!zp[Y= C m~>+zo(EM,Ulvvpѐ <y#MM\>3@:,p>~5H46P "+רqfb%j/;)YoU} J%%\B$uÝ˱e8>]S6V(kw/r7õp]qRCxKƿ,fbf(r(9p9E? \DSHxT$0Xf C3/p7hp*\Uw^Q`a+~{2-avղ=m[ ix K-EQ@F[mF:c`NŽ#z'?X`'m w hsUkvG͸~Tb?ۉT9}dB,9wDAiY\HQ'w9ʸQLI 69hq`D5gu  +)AƝMX(~.VfFڽfȒd_e)vvtJ lTK Id^BD,o/J` mػǛBm,Vv WZ}R&;tey ݢ=QHu˘ߨ gh$m?Ek)Rx6.Sˮ'+K?yn&󜑟{I@o/U?kϙ@ 4hDGMLhtkx$Yz,d82O.1o1@| [~m@G"0cv§)0M˥f1~4-A@նzʘ' Uj3Xsv]B>qz2Xv*^jMZ|rȸGŀ;EYΫߛۂ.#k͋1\\Ț>8=TmNiש_zɟvj鈨Fs D&|RH~g߱KipS"z) Jx雩1\ P gzx*ȜD&vNӶgm͌ʋ*oxTSI~(U/26귂|gqzpmzf{5kTLR +xY+ּr %Li {JUm'<"!>nWlùJ `+#yH ]<h2v֊(@ghjfFj\v1JA2x/<0X /?!=zB* q$1c)y._t"vuOy3u1@$i'*bS[ pbżWX~K{pZqd3k%w 4IIz !Ub艨1aBW4S/Hp WHׅ\ yKOo },´ZVn&N@ `{`RAf8@'t#|GҸ]X ;r 8'c3uE3Z\'=^&錮`Y 8S-SGїn ?`-`0LP~!(ɓi cWӥlÃ"DZ3Dg<̠QT}6u(Brrk4[.x [rh٧U\)Wt^ ]@m7nVoM1, =R)7"Yľz҂x? Utl=u PU3\ u慈-ŕV S[+/QһoHWސ`5f/~]Ǹp2 hiQҀR%( !&fbK9UV[˚!F=5|"QUb KQEP "ֻa6KkZ390$@]v^ 壙kXȵ-g{qm|Y,<>RZ Ф'[NigEcj\S{?-. C=iPG*顐$יAv%[>GAD$qo7 R]>0$1?꺗H"!.\*e–*'D ބWSatP0g-8IὰFg˳0 tξ)1CCO@z_ˮ4)z6q捓m>o}[1ԉAr*(}њ#-uDs![lw/ me* Ԩi=}<,v`jܑ> bj$V`BHõ 2f^5}$/Ҩ oԒKi]guCV""o !P+RE2Ȳx.tYۗOFS4 Zȧ Sz wvRm,ZIĐ'=|%ӫI 7*'Yeʐ#6]OQרji8w; XN40I-jEa، 1FJda,eU%L,4}!oM jK+ɭ s0S nQoG׏mBTLr4!GǓ,'ޞ+RtF{fWgt.u`52fTPתro|=\ߌUo'1AzC5{aǢ\x9X1dM%@]o3`,=[PlAߡ{iQb5mOhcA^p"'*l$z#]FͥΈi3jNvB)h|lsȱ`3)E9UVO=5wk8ht}X.\flE焀G/9/^\!2GzTBm \dvЋuPV)3LεѼBy0GS;)1 0aՠj W4U+{a:A`uj(dGgd]5l1$o$ec.LgI.gIvK\XYq:='؈`g 4Yyg賙O,{RA`eM }/o)B$#%TSnnX`p -6yW Q>^ˈ_]6W.J ׌~4JLIJs&?W<1:@HbI{Y(^x!~¤j(n{r9T^ԗ).YWTEeD.Z;JBн,4K_/pɻ*<2(s1`t螘P1ݱ(A$: Z{_K攖pOkE& a-rX>:=R6|S~ԹSE'f6YM䈆 E)6o3XU'$;D*>9n0f<[CąR4oDؘSIfrxEvE:qj E{CKM3mˉ7ss໚K^\ffe3cW6$ڣ#T <^Ub-$[FXZTY|hpn@sdwW)Ii Z.ȏx_^d#iտRFcWX; Ŀ>4"ITYKA&',FĶNSB[Z v dK<M{ߒ%]uX{''TZsOG}߫pc[D` En?[% )m1+՘3nDtxuvk͡w h8I jQܴ90gh& dZf2.NYE*OQ A.H!] 5-oM49! h`wFv~t<#zppknr;s\ЛP Ezpְnm8C>ثv_ծLNwNp oXl3<95 w*#րj(ܷ௞@Ϛi>aOPT2X7J29FNM؅kGUUo]/mqn9 BF++soݫ9NU]>$j6ɏ֗qx?Zd0Ij $pX.<[?)]3 .e\r?h1nrN#R! FvA`qʥͶc('@.?z?<{,@װXxDTe_ a"2nkm?z}19n tQq4̋OpZ9c\.j?xI2wޥv'Ca]Ab.\nţxڄп6yZG92l6%oRĘ#l .q+fLm5%دE"UEñbЊ5:NBrD^<^Tm& ʸꍼzMg& 馯$ \ F#!kǟ6cǴ?eՁD`p7eX>;>-ӅTEu{hҞWh&*zq3ۓl~ o#UڢCPe~Y D ^RK+.Fr὚)?s. ?anzvm11(Jt<| Ȧŏ7ڏKmiP`0%-q?T ꡬ[lH+^)D=\ ZxA:`ASt.3o21vDJ0קOeZdzYV-CzȹDW3c 5d71v: nw`AzUB/^쌟p_$ H=bvoI a}M=Hw+tD)L` 7]&j?g[u=D}6Mg zc($yP `6mDCC+,|RS9B#n@WNճFy.rDqc>mx`6ݸ"ÙG]%y;:#[H*&ZLU0(0*3[7M^qʞiE*Ze9Ww`X}a힪J&]*L""DD248A;L/a ݰs=fBh+|닇j =Ducm<싕^ cMN$I,~Y1(%*?Z\XT04+X\ghaY ƿofP }X(\3 (KvE$.QV"Wz um(Ztn4$X9EW[mQ%Udp PHbAȋ3 ZEtH@U398SoIRAB]9InE@@Třꥃ:*.(h[%ogc[s *xJi#tʼnalՕ4= TDM>NoL {;iqj25 (̪`xborQ# 3XRq?!{70ETR˾`@eCA]x-zSCЕ3[yD#X-7 S" F@(7yQ$pd9[Kpn^am>lN^9[В#,Mִ[ñ:NR{<7MR]]_1;]i MzUd*oΈj&,gfà[7Q" vʗ03T'2YG^YrAntX=g {7޶?-5؄|َ#7W"40zR\IL֞ZDyVT/eO8 e{4#_$[y^t5)Bk]qeij,"Z xp z:=" 2[$LXQ4򘇚t` s5S rTE/ϐ>5?I(E~bO5U]qx:U{: bGk%ld١3egK?IҲA^kw5V-7aw G;%1(:"}Ob硄u9,Lʥ7txX*ni pLx _m@ϋ!-~VQ|\|d*qY@FUi=/뻤.j'OمӚ@t%@LM]7DJQ*f%A2çdߘ%PwxZjW^cF H`B D*.5 n]ywoX[6GQI#4etZWmZ >zlVy>&&5{p}.~"eP^*լ^ ,%<Θz''TȳV! \\r [eLAazȄqn:w9;2P5¼ Ȓ>b3?(ƌޫ ܲm;z~L3Mj<{5:!=(aj$vM!y02ܡ۳rB.僬&8!--23̭5 Ak n ^]n֧T VTt6dHҐD=Q\QIuDArp 78cU#i^"`ͮ#BIU 0Ɛ`riOȇ z#m=M'VBwm[ᬍ ~ubFG t(U56 m5'M[Ʉ'k;Z ŧbc5l:K yxfO}Յd`|5|9J^@E8M˼'23ʵBPzkg9p:iԆ$nS)]_ex%jW'57\WCmGV|x.Q!Ύ : +L 9@jUWvpoΚ [/Z,3PQ4@ǃB:^LG%q)gbllY~b73^1U{nƥw4rF#7f![[Nܱ=l%U9Zu^ t; yuyCo E21}^ih8IDbq6m9Q?'t#qÐ=.3f-ugͅmo_S9 YCNDpW"̉0Jpu$ϢLk,Q.;KUO&5ģSd@r\k[ ;C ]_*ߖOq5/B }YKyVb)wlO_i-i g.a =&x,R.n"oסik g\pq]jv4!UW挸# ؖEb rMKJRk$mgXHH`@| ̮q5Rl] ]V8kw"jTdG-G(=MpK2bm,kU6&z1%xCIJjitE6b/A^lPz+1PT˟Y9uN:)I!…k8foFPb@\ Ha@Wx."u"^o!2\a ihE-QYA=ش2*ٝPTy{l3N$ nHwMG98O9$e䇌E8i9{\TA4|B"k/=&Ui$h4ꏋ{oCKO.v9L,%̚Cso9`(tA0zQ=m yb>&ي:Dɮ "峑f!hʍ_?tavմg恙}!UqÐ=*t^L좈I$i<2I ^{&2,'x#Y8΃u[`? y(AC!lv%:#<ܱb wEl%u|OҘ=,o$}A{1#uo`߆|_5ׁki<0zl z8 XyاQƙu轗l䂢yHjJJaؒ@%-lwiHHW]M|)EmBҸ7q*+OQDl{WfWwCy{ lINˢ(4>>Yr+$arygV8w#B[LY7w {9r|c1 8)`J - yQ"Cny*xl|ˣ3yjá؂*Ҧ[;[mݺcL̫ưR7ĘIm>_¯Vi(6. tnh+(3\NR!r2fx>Ny[_0\VCyYY::Ƣ,$a4%mCAa1" [9!z3Z!^;pBєM3%+ecFk6Hs{# t$\4zcRuB8jOD*!!-[R7C(%轶L>!/JEtЌ>B] 4מ6dh("Pe-^pJI=H[eh2}M L.UEvx#F(2r΅) RB.,BQkсMjTU0KiK[UF#k8A g]H"V_tĝ %i/OSb_Qp| wg7pX>"߈w O |ɰa=G+WtyQ"Cnz^79}!7<4NB <ԀK.XyD8D$E2~ķkXH1rNd2Fu3O߁PT,m/=&瞤O/I-cf "N,G{EVT@RO$~aʇճcAo{ )cLϖڮ$Zuva6R]=O4ۅ2pn ^HV'zRi"^T3b^ ~Bŵj⾋.Dwfx"50imr/O#3=3>'ør71gqJ6 ywЍ`( " PkZU;2..[v1 it n/=&UqÔH|m%Un]vW~ tG &Vk̦g!%L?KݖfΗ0 JfAvR{5nJ 7,&6ӧ?h`%A*LF|2Mr}bcHt+5q();0҈%"I}Ox/pRZcn>peQun2JqrK<˻tP$tIٙfea4)Ui-;S|<-?LSG { =;56mMsm1ct+9g5P^%)4X"W-xdfz '`w/E}gW>aX"9PjW_HzMЍt-Yd-\v<ļBX~3]g_Xu|MU7M9 Q_ux(H۲\qߛ Bx~5ʉT}#J#o$kȚ( _!{R{PU1T)Vr?{K a,P78 $_HzMH/KtRRXrһ?}߁.Mݎ 6E32 l&-"jU(5 >΋arW{#yN,94o o5(pnU&ym<:wGb^]e"j2b1硣 SІ:{G#Q%58ֆ}e$BHo0_D IȦپ;k$}"Ή8(Ycb2v3yn {;2pa:W)!i =SkIMb1f_ĄmBF+h ^8Lts(ݢ 5f`O h^= Uz'߇[/pr@ SuJS+0,Qz86>"r[r<-$|np4&EWvhH9eSRo7gݗ W:\a[)ПaPNep @f+dtcɜů^y˞^u+mW-94Ic)4I aI?P)* Ht(Jv!rhm`>@o#_ǣ(yn"Fvqy^mF 4.$e ٽAY"Z3-(w:41f쐬oIUxp*n8rsC~j7pxx{ͶwR&Yץ/|ߜ . ۉwZa;(,/7!S NMIMىS"#rl91 z(uJKk>T 2PђCb9c>gMd^[6,Y,臇R*{*6$1ʢdUmT,c#oO7jCOiTsF0š0l$`4>;TpBtI88,bH#ʀl;O꠰.9|#Ϭfɲ5G8[E&d{p3ü˵:${-a ^q1#I>˯g @ش"^Iש8'.O+O{%^dc A_FY :e6شp Vjw m侮A`/Rp*UD;R[泸$1.HP:gWQ~Iwk`.xBt[b޾yd'Nyқj#/fehドqS)T.Q< /ޛ]-΢,yt`MQw? OT ޒLI5`ZƄ5/nv;/oz2^2Nvs;xx4b)[{ ;D#mLȡfDۗ6_G%<jQ7nb oCpVN}kkl% @.נ?@7dxT)S"9Qz~3+Fl -gwzm5-ˆ^75s(*] [{ez Zt̀1< l _HzMB/=&3)G~;?h:eUuVy.4w_fu9e٨& ͺw[7Y<,$ə=a1KLjM4 A-e]Oli)t~ʷp? BP-؏ 2byP\lnvhs#'wlcʊ3n {;d vf=VvKUW mD/lk&NʃYk!z9vF;ߤþx^-Aũ0U@& iX42k߯b0.DB *$2A:sx)Mqcg3a*\\{$o2*07X\\@v#o4>w]ŋfVs:yRZK؃s657'A18,lwx]Y" E;Q|Ëgg*Ⱦu7ULEe?)tSJKE΋q"<ŃJ\1+lv~ D SuCzNu$f <-/.XUm ޢrˏB!mlv,nC 4G!782WC_$QKcv3tDȝi(K}BUk#^$A\ʼnYt@avq!m#Or\'DT BQ}PN@o&Ag&Ce(! ?k#<ٙ<--Qǟ Hg>HD$OxޝOeo6hOv,!nlM3M+_ZuB+ Bs=.7z61<ָUfʰNDL;B)U~k l*>IL)Jql:G!Oeopb$kczLbߓn WTG¶d@D^D hIe/<yc8B$bL{+\籛c Lto-[<,b\J:i}}:LSǃ*n]0K -@tӮ-z0oft' TIwpk& |!vm `|7 d pw 5AFvaK3BԨr:,/ ,اG+l4J;bpEbeSR֘~^bנ % ; ?,v%j禘,Z:].0%ZG9TL ju88f<0`C:B"z#9_bD)n`oƗ8 K!B]mm U\ZY|}e8Aɍ d7c㰱@ N֜ fsI0[c 3߻Æ㙲dך Kf8SJЅ M_':$`,)hW O v{dw{, #cA {k^D>k`Q^ 3.T@ }w/Wnm\:~53m4drknK^A]B( 4 71A(^mL#J' \}ðANJSX|"g~ j|HӵȖ霱x)LL:0L-C~.ק@Y>,!=Y#,wh,&Ɣñ%w\ksߜtV`Y$pqv%uN%RhzcW.5艮MUҧP:86Scd&VF TZh#0?"3es$;$&7i0#V?Iq<Đ @UKU4arh&-^Qg4?9jXF3A9>S L-@+OkBp`uaEe5鉞x?.4&8-i!.ס? t/Ux]L*(wR)3__d?.|=sAGyZo+[}ESk+rJ_: Խpze1S~/v}}>Miz5BLz{C)?0۵vF*#~+>vYWMuH1^Ȋs:5GQP ғ-9M򕅘հ{M9BuʪGDc F-b#udmMWCqh#!y%3 gA3歁AhϡS)TXW!{)lX&lUpAx1cɾ2,gra? {?'TVzi-A)GpJQ6i1ZW Qdz!HnҠ!f{VR3'<.$t%")/%8tq].G[ܳ/;H#8, 1sCDaY,Ni =<#6@"zyGC a-@cdYfbZwrJhOc{`rC_Cx[B$cA(T=,Jf!M."YҢ`NE0rs -2{LYgJ+EP`2bt-mI|\Rv90]VӔʖ=F}ohF>^6.tc]J4mu (EnTI6P;N-;BI$ȇ%Ze]fyw=5y-: J~]WvQ<6$v#e$Gq!|[#'TĖWhSt`喷 L$ 9HOHLH wm?gڬzu`wAP753a4X҈C{ )Le;\*%ш6yDnY>q5nչdPQB'GsԈ1yٟSVńɪ[Ȑ3[-E`pLpBwƢ2TpB(t5x ډG0!FChcU-ϑ#PoYIpX:݅6/@ n|ZZ*zvig5v+K Y䪆ejNwI7< }*^.HCw5@i_]+ jjZŭBwaGvz~fK1j (VT؉wO 0X\GǾZId+u id$L᲏eTC%bz -C@dLb 7̭[U?PnD&̈YSUx ~ FPVMx] @Lw ̌P%~zA>BsHpE F:?ٸܔtր?n[_>U%.΅zoh8GfMEo벝| xAN9i`ߓ ihyv+OpS.uBv˥>M48`34+$Yq؛ܗ{sf5b zjḶ5ւYd@_Ft+rlf٥575'G-JwÌD1p/ @\ ׮On-HłzaCu“[HP1/{0=4s~zoHЗJQ?uM)PurDݝBJ>.) ycJ\,/y  m}wK:N9+6zCP ^m<} UaMpXahr x񤪉Omӯn#NyQ=!bxk$Cp-QlA+doاBYTr}QUzZJV2&ϹwخH'u(R0WZshYORez|kW׉oHQmӂ q]oJO |WCD("_TC?D\P͠O+KvjqF^N}8#[|,RG (՝j):y5M0zU>Exw]d<u~z%;|zM9T@@H;&N o/29 L7e2C {6MC'{h._O"X&P`KpgJL\K8É֘Cg{ua*W̌ښ3x%|K m0P7wż|`hl_1 }+ȴYYd; "b?JOJhzN Su`b.`%VqMsr Ro4*deH/E, K=4m|a DTAbdqjBPٹ1,0́&!`Ѥ?f s #Rە{P ߬͜E(gVCg8**k[%&fo.m^yF>:dU,2*X@^*P+ JOIj)`+CSŒ5_PAr ;8m}៱?aʌ]YMr)fшr->:?u-GМ.!AcE';ˣle+8я!֔e;)b^*l! HN{)/CsUexxHr:%(QfYw$/*E^سUzSJoB\,pa}\tS2#I}M{ Ɂh{h}UKVJ_ !Êm*Lj*AخD\wMҮ(PΡb*33erb*R}JP,rkG@2vէ[wi*SPT!29EcKlQgѾ۾.[m7aW8u.D5y;,nJ .+Iَ4,ǀZԶ3m?t|&ncKw5hAFJkR;p_!t]Jt_/K^;+hA,_U#$AgY:X D[#~=pߑVPnP1-NH!6 T |_$\alǖC wqĉ$/C?ϑZn'yE[9)3jWkuG.Ft r| 84Ԋ,r䴝0#f1Ch kC^6>*ઋ8Fh(SJZP X*KSK ~G^> I|C ^]UV4FcTΈ0'A9EYuBrwvg'anI%fBx:  }iq)U'e:lBYq{_LZY8*UŊwA ">z% r:&Hƨ'[c*ëaqɡ557bp)m)wuRy¬u C~C϶ͺŃ#]`e\_oYΊZQ!?&$pgּ._i}y& &[:s5W18[ h:~ٝ4jٜFxDwwbyF\8Aza4~^U~b%́h^kg:_GP5z&Ƶ@cM8#ե $F8V-[}*AzgWyd5uy-],`7p%9eFoK pwE<"¼׾$B}tŐE<{Χd'ݙ;PdVʓd= 8b@|J]QQooXG@&6UE`,D*tY{NB<{;allC_,}hj dSdIj@?k\- _+O"N_5 =I|jQL0^f~\k^kx1W-ޮ#&`NrԶ%YcKLNo}Jeneϔ;fQfxR/SΙ-\X$&.h/NV +b!oDQ]*ܶX(jUU(M/\$TWzDHBR0 5A,BW/N_3ߑx#FsXף[ԿqCA -k3 O^X\NXvo[{d̉ 05I,#ڦKYK4g:5!{漛h }uaf]b%nV_^K`169K t\<RfF #A_}]\I> M.oe~hBC 8ޡ!ϦNm[h-}8+L^-#l &!i29 .J\EBl6ї4Я%5%d-.il,\>(̸غgTF(WL:6f#?>M4h"aQ'+A%@l`,)lLlg/-ð/ڣ4nxmΫh<|֚~E¾ڍw1(#m<{ |3m>9fco$D!$G6g#Q%k \ [ :AI5a !ruxR'3A@G[ZF] ī^r'cHr@@R\Va-OEEJGmE#S$cb6l-;Eb~&96\^1]v}`,hEۍlo\F#*]0wI.9PGmHQ6QjUϊj>ˎ,ct]Ewʅ솷>KsӒ£t$v]3XW%la+vc.ExW#uH>A[cnkW\2C2davs4I&ۍ |'%qzR}9+Lhô e $v_q*-&B並[ʿMbt%BD(..fnHpMq$GfP5a>pIg}T!D+j~6%Ѵa&le8)w2\Qfm'|Nt$üZ8eUd=e- Tz7oJ gVzΚLߩ.ms;\&)W[,~4'&"x/.7ٸѽ$ 0M }e-]0Iʥ+Y֯,qh/^ -9'ղ)O 4+z?pHhO{mXlV"jW_\ :Ȫ'`,NjNCT}Te)E)$ M-z@'g(PA.5]tj^'}0bQnZrs:%2[T19]5W//Xj5ӃVw>KH$yqg::kO޺-}I$ u շ TX^(l\hk/*r.7m'.EW )Kt zW/gGvq6rKgVPy Nú-Q0:ria ڹ CkFaQl"&忶<Hs4ly>^ m͘ms2a:v=m8 w_R |$O䝲9պ4d$(/-cYu? Ghd~͎* ɯp0*2|# *쨃kΞO31^{ zG5?%'9in˻ݥtx' HgӀ7yC0~ uRLԵgcQ~+Uko}|sV>r$ dw`:Vױ۞hNE$6Qs*L,zs¸J\-*(ʶ}+ j-Ÿml4=T]oc2:A,w;x@m q9͐L `l/^q rHQYy dR6۳7kWNvJU#g/ڀXTɴ}:>bV H] nt/ _4^9$܃si/(;ĸr B.lɢ~E8joPPSy4>Zu4fy|#Yg}1 ;-]XfA5ՂZupaw -4ͬzw|O/fT,!B)~ߵ]xFQ͑AٹtGK"Ŧi/|dvI,Ajg9S+ >']M&,bŠvZő`ۣD}S 6ݳcg@2ߕh P7g{ݎ!NC>&na c( -dE+ɬY$gb59*)?cBʄɢLɗ#_4oT<@P*%e"XNhQStc«KNvP}x5Zg:sظ݈nհ:s]M_ݰ }v _[F?᪙ #fjK/q QWf4]8N*mY*b,WD¡\'r4Hx>ەBE FDb9uYm,|ļRoD4/eGd6vSSe"0o2bKOIBp`}@k8`2fR[1B0#nY^m7.0V',;Z#1p4${S~>kK_0!3KiW6)[jiq"L|wsmcG< 3LZ"*ڸ,Q{XnNbiLx/}6LJ"Bz+/R.kϥq ù=0æfǺ"1^P,GF7CСߵJx~%@$/|V8[KU@IA@E4r,[JBaf4Pj'~"7muQ>琝[P@K^e6U}LD3|g tce O$*mP@4Ss1iJ c'`J*"DGr3I MGF"0gr&#*я0?|+T D0OR8 ,.ant I6sXA&I% f,Y"F<, @AOAj$>Q}YP8(&Ra|/'y6eL/K_@3QE^wWӜ{q1ΖOd0z!CθED$I|]p})vb<#Qz] pHM/(k(i'aӒ}@YS-r?k_4H˖E,ѹ@Eع!p5W2F&C(E/r&=uM(2˛[ѱOr"SQ>\,w3m=I[Fyj Jd)_V`6-|QqyO6#mSwh:3m_j<IKPB1GE?xvxUiM n(Wh;ƪC> OCti3F|N|rkNz2fo^62byuzGe$3GA0N5Ew3ospWRjt|  ۑQ8 D*cr>lfz}]'ZʙLL$<_ՋxSڢVy%tY*vOHu$`y'$gGDueZ^],%S1jwTSlMQhp%2v7lt4/Zpƿ{- >Rn R9 TYԣ~%t`Ko(y$}XN`o~H]}"u. |]Eˋ+V.>1e/4˧5#1cCvVwҲCkjM[9wOVP(OhO=ߋ=>ĥ[9&pNn^dq]XmrGqttX@>-,WP c.J )1Px!`%AJdzo+ϧ W S.*ʅQl\qi,k=p6,ݐᆄKHm*G>1t}fLo sY2x(Awݱ^Ff8q'\#.V`w}7]$XdGGלn:L^Hg1~՞ZN=`.:3}QЩ ClZ}&t:V5;upԤMhʦfab\n*^hNCd4:A;]7>u:+;CjD[?R>deLe5(R+*.i(})XWĺo4hdR*wSuq~hBZbs˯Ȁɥ6:9zpB"iŀ1Pp vmЋζ !&1 LsR (PDa4/ݍEXT|#Li {iV\6gD 5CKW +'P;XsUP< #V$a(DLxFc2\ +^6}Ro6]a\WDSEFMY{] L!¬;r"$ }wR0yu3vV|, Ǻ/.q_Hn5!O Y`0ƹEA8u@Y kռQ;\lRu(w-:z }͎i#Q'YZ 8H[OBNcEݒf\\#P9Z sl*Do7HWdJ˂zu@!xwٝ40<2*o;vW67Qr?R^B& hxNuZj}OfaUCy-pM-p"X+l\D?颅y.C9Zk[ʱۓ+:N>ZDj6Car{!AwRۑHnjϧ2X5Rx'D Kc[G 9q';We(h*@oveO E5.e$`9f, 'ßǶ7:`r&Z 6F R.Lirc|1_Sx:1v} \e3vH =t 8aހeUs !I?m0k*?5$\$N~`2IC4rM#%C?{y qb`V^hgBjUZq|GZRC[E’FGܔ )“]?'p\[*8FcYCZ~݋ Sڶ(;8ϹJ|3S^V#9ك\ZiiTs0`)}O!].ە~ropK*+Pg%mˤ?M!{c+˻GP v#],&YwX7ᦦ7&sf;O;O0 M8UϽcf7֎@qKP_N_ޞj LzvټVM*2k!ARTu.9^2 ou(9/hb9g"tnlޫ>C89S>(n'S6Rm\U9K Q8iF m:Dc̫c/p*va?T'^PcU[68&n+atr[|7908 Ae&]B7CT̰%;؀PhuJŗpNUbÃNVjS%5r 5 z3BψrB_{G-h5xb^f-+A)2Yi4)af5wRj!3tbeEJEt<ԹB>4SAD9)N^ё>;J~-u<=pJk.Ry}5,%k̆89,(1;n&,*kTv}"d"v.*aɂVþ7Z~8\¦OIK|!we鴰/^D wasQtdFE8HzgY1&Cr\̶;yLeeNdu [;6(- ILHD`9%eZ8#7g9cg̵ ,;(&̽bl~1%{$#©#fe>cgΧ-M"ǜaҬf((ECS('HTR\fh+!n?&F~k"ex:Bc H5kgOrZn9hϠY@,UPfCZ2s{vShQ($.']_4 'h6ͧb3@N~`Ɲor80*1V6/v=*NtT8P6JCnr--U‡R׺V+N%ec*N $y4bX ߥo{g =d2'|=lSy#&3g'9sto υROmŰQ͆NKJ~Juon٨5.UzU$LV*h.ޘCG;g4Jm-O pOEjd QB SïOiU94&[ ֶiMdERQ+QU+rE?t9E3 ޟn;&ߖ.z~U#²9]h!|V>c佉IH\vG/4}( lS0oTct ӼHbLeٺ b܌qߨ^!6Of 4,(bڐA35͌uYqf^1wl͞8 )K! T/hK@ۙts-Agge)) 9˾`}TACZ8tI'=9 kuN U'c%?;g5SRKtk"LA׎3]|-4ftdM+&kD BKSΘ.E+SMֆOޒ8L%dU&s^~x!HeDbEQqF0Jo[)ѼN p?uq&#[O*r_=^-7VMvpY?VT@or.6O wm)obrߘ ל>΀C yJq[BG, =޳o?.7uTBXhޯɅ灬 s&u~(qw[!)OAF*:߸*)w6FG mZT DͰ84Mg8&;gr41AQLkE,>"ʨ2mxAfg7e`bxl!gKz˷9߂{@p.~L7Ǝb6pP7ƢdGA*ž^ 45\v )5-zxon.%VsټK "^5 DQDPvY:_5gl1%NލudEu˲pZ8OZ' }nĈFT-~npV-y}E\1b6rG{H540FTƑ N\Fv3Z*r劗h0e6aS.}U3Cx[)4; Gͱ,יUj̠ceHahT~3Sm.JDn/Xqc >gF3[@?f*W|eE xs=35$}ɝ $,2`khWN8Yp )BtwB̖p'ZF$FͳƇNÏk4״]lQUFo mpFsx=S^ECۑEґEֿC{ hX&w|x ƱZ}{ zGynu߳f3ή 2 W^:K|0{#eSH1Ep{ؕ#SѼQLmfQE$kjE?/>dQd]No3,lk`>)bJ{҈ ڗ<'E #1.0EBgXd8Ҏy[~"Ad\87f#ל+vEVH-=|VHrIIl/(H{1 ])x7{JO| %sWb'`e&zCAl^z\/c3>7OfVR[V;vK=3[$~@C!l7s [G;f[?r}~ugm&/7'hi^ɯCb,1%Ksu`[ܗĢ}9g^B.€CR%(@`E9ڀ[Iیޘ: d gvSk,6{l@o']焾?p~yd9OQ/ӕKw]`_VW^eWS|hW5.!І/Qy)Kql;5IQgN!uc9+$(7MW ҅%9!a`Mp㼻M$T"e_c0&-o~A%H8)M4CE/ K8 ne'VWYQw4. [xpcUGLm^(Ls^GmtaI  hS6Ȯ˾€UNC]R;2I]K0-er%];Ԁ 9?FyZB ;CoُYOZkX_&~E&dϊʂ"Ʃ{CcZʊZ?Lzd A$#ODz(B4dӆj.ʶށ $Ͷ:GR:EalB]Pv>ёH Q U+&B)ӮW%YyAu^iM3@R7$#u[+,<1&s]$E3'tO2j\&%QBiR'酓h$U^іd%*R/A]!v MԑL}gєʮ݋Z#׎xY說+&y*;lZ˱/b Tr[iuqcu|MCa`tYC2;KhqزT7^v8t GxҸ?z2"atvkW(MVJy1BF?e" J3YȰF%wG[H@mygu;qd;y41]:,/Q,!eE$15VUҡǪ-NhWQ+vPFQAvĨ1R h/.aVrB1lNy7~ЃN[0-ڒdL ,g(љucn2c0^ Ǧɩě''1"WKWQI@jh 4ո!(988u ҌHYzMa7Q#CA}ѫJ޴ilNw`RAD&S{vSv2k ș^oXᮑ7]{6@x:Okr ,mՐ`ƃ&v- zMB"?*-MaC~Ĩ$Ii~?W/yf( *AӃ:rfҾڰ h\ؤoGXl\lD_;P[0$I :Ga͔V4GА̴Ѣ⡤eM\A,TRؓ1.JF!(Pq5_tQd40"0w $-lRfJE+ˆ{!hu`zjwHR7O@MS$UPBJ*zCgoipԕ.sSښhn2؟5Ei#D;~zΘn_0#AE-945vwk^0H\`xwrwk lA dL 4[Vpq4?g~ goM)rJIC&#ӡR∝eOzpEhfJ@eM%mRCfh0u{ < U ܪ\¬a*-]QUΥ6`.+lT}+z-(cj[\Ѱekx0IPpf F6 A՘_n9)o[lazA\t%hk?yEuB" yăP:j(BzZ ="o}Z(o:WbPEc^&7$I"\)6•Tmf$R%1T^uR3r|~ʡi3C\Fy}ZE.XNXqnǏ&v[Hv9aej#bl b}A ˯<_dَOxsJ3|##/p5LJAKSvm7H&+gChAPF 弁 kD^n{}DD?uн{?&uX4j"+8/fS}E79깍@d\NGlVMcmF6-]<-حBG3$jƩLJ_{wF*|^kx EvS[iX=2!gӎ pю"?%"+c|kGUm6ꈉ;DPQԎO1%]|%xle`)NdE{$j|ˏR_$iHvfXWJ3S_7ΜV@\#"K nIEBs@&PDqE=R_ƾIy{*FBf7eyabN NRWi!zv?^kiٷnTg k1Jb2:f\xi2x)81gi^>*X2Aewm1{̟U[s~=~Pb~JY.9!3T)y0dJ1r$hc'Wcl]8HbGk;vdx]'S!Pv`o'pv9;}A {G#^/?]l1r]l|0ҋFBR 5'˱_v+գ?^(~f- ._aG'Lj$*#Z'^c4ǺrV?Ho ^Ri U-Km,Дq Z J;TjRϬ6{V '.>l91_Φ9'r˙Ԫ7$1NZ,DG?AsP*,?Ȱ#$A\ ņmA HD0X7q`Chk  ۲ W늛HȔ/ Hsp5^'] iD5B? >GcY~xg2G/^9Ro`~m$fcŁz7X|2V\7Vji6Dr+< Rٴ eӟ(` v& b3Efd YPTkz92 5Zo( #;f' B Cm ,ƴa TGxTubd6Z$7ZԍAݴM+Dބ1ٻ#y *w%$FTS-OMa@ѣIY->(WQk]@&?s_z1`1O/_ڄÙkih;xNX]0 L0iz5< $ 4\*{V4He6E;4кdof7A75d @"F4`, ᑎ@m)զñ!W 'S+_-Beʉ|.tٗ>+ɞH{zhoip7$b߱E D)䯉t=Q2$ ځ C2,u'\V0YoskN?kɖ *?lQ?Bi)MgZ9\3™İʼn2SS{P!*.`[NR4b@\ 0?&q$U]' -9HVXhQs\]էD8&ZtUg$I-<kAi`'Iթ($yVq ٹfǓ^v 9}Vq]+nMBa쒷@ļFٺp+K;?8 Ol֤EqHce.Xb/Ag\{)LiG,BwJDXq%ں8\ųhX%޵( lQNk5#^ !hӶPj\xdYYVLR?IT3&l?V p'@ɅtO$漎ӄ}lU0 YeP'P|\v^dc']^70B%eСq >!݉?Zr .f\*+*kRAyC-6Bq /nBn=AUK9׫3֐gpl5|qXș;mwj'[IS]oc},L?ta R:zΌ*E0f\uW4 i<1B,-\}G%%%s/7A v&q$QIW_4Qq3'*`W6In} [>p[0$i07dJ%U7gs;/vt",]* 'Y.1tߑ6TǑPBy^Ko'N1Zi)|$qfg詅q"dL5X@;R1[`$AfxWF%vQ KF)oYW8X<Ըu@"døclr_bzohuZh$qQaGxz qX*#x\jr7uIUILA >ay@&_y2U"mn$a3x.RA DD"TbabA/\[n6?l+I̐A/ZNQ໱<ѻ/,[,ɚC,2 $yHya ?vb4$5SqU0hh110q0㎣@aw@z/ƽ%:qWCl[Ӥ5p}[bkW?ȡjUxRVa;Y+9_BC/}E>8Cֺ̦c^n00!Pq^)%K"cX_ݬsLqJ=h;U- "| H{l <ާ/nŝpB{3h/R [AIo8C d΁R% ڄ]W*+aDL #"Ć ]=wz&Dj 2,1X l==E5ys3|U#BKt&5SA@zDÓ*3lR)z7Mf hk{IkQ2$g&&j'Dҏ;:2kWz{ˤ3i-.;js4Z>DZUQi_]OpVs;L9̅ɫu E aO? vZ_ꖅ\w3?ixJ+{\ }.52M̗wK,A_Ag'VmNv'm ,حݟj Nx ./J7x`T˹ӴǑ  ꦛ~G u%TGa 7hT2M#gJD;ԮzGɱN2DJB-TQP-̬Z;{Xx 췬y~ǼwL jJqOxU6?xXD(SKx\wX3M(7`3&ѳoL*D!C&*ժ~C V1´+ ܫ$&nYgQjcmW l2KzqWJ{NKj!*B/upN^$(vRAXݜnIUlgib5P%Mo/*B^UDP9yU 8vÖb^ueSxrKՎjbBP[0rgL:KǦY!K|uK5׋l/"^|6`x@ȸ Q!pÇwSV[|xIh 2[D{d(1a]qkk? mHfMA}F 833T¸rڅK5yzR?Kym-;4uGs5l} \ ޼TC-//$[>Vŷ:'369+'*4ڐ6N[#Nc )ޏUhpO]|O LaW|"LbJUW rǣnPreziٞz:Z&Kq1V\B R~^׏ `JšU uF#Tl~zwm^xľF`jk9ERNn ݄>q;q& \~ ͜ >t;?%34ۿv_ݽU,/~+7R5W#a!KuEQԪ6&`BϙJtXOʄ:uPedt0GBR$-7woօ +*u|4 Za4_Zy50_ghפ!e-jG",vva&;@ :Qvd{Q}7* L1Jտldoa]1Gȁ:D<=F;4gܡ,pm4=&u6E}Rg(ϓ)U w`Na":uU昃 Z~-(1!5ޙZҞK1ϳcqNwDE G0,9 WC_G9c|X__A9]%}j Ciw뒳s5hӖ.j=!kՕ D9E-kP&Pnn8ggstӒaԚ>G+F‹Y/G2Sgbr(K! HϾ5#~1[vETHKMS>{vݡQ)g?#AVܥbM4 t.q6ʢ2tg$ 1~_(nѐFká㻦i1sZ)r}7@, QYWR=ݗ*u͠Br%JE/aj8Nf) ϣ6SIRɀ!Jצ%2P9i%ڹWO<] OE<4AںD;t+">R!g]&>k0hXTs?٧ŋ0hXD `-^!"b^h?C[ []r\jɧYDi1O ] %×<=lܚJx`1kBzʼ D~R-IFN]"]=c OTE6[(Ld> vB*qeH9r{ el}KPze{ڕ8 ]t3VMX-Xjq[>ZiqQ$\^4)bCZ[G`b,Xg4s KWMMjL5}Dv?Xaz)u]WNzP+򝮹ۘts%>L#^:"4GCĊWT[$,m3>+G$I^<ʋ-h,gtO4#.MFk*˾nMtWh`KJ}yƾ3Q51VG=[sj؋3We!}Dp0Y3VꠘR4M$joئ{1!Ls*m;{0P7=oôw󺆽t =pB&YsDuj ˾r},?ؠ7@d[Å)-m'`N[&v(K^oSÔLEmV2dQ3"mbs0t-LOؑ(qM]ȝh)'Xz&hYvVDhyomC1pAh1UkT3gIj ʝY OM#nHYu5/L$sT4s K 35gꐯiiXR3ݶD{"FrH2(@;-,j%jĝaJҁA6&G+i9vz-/q68^!4F RX$|X"UnPbOaic|X*,sh{?rznO3ɦ8l]mlR`O. (l~з<>(P=h2^Ȼ*q޸$.lEeצ8/JOl\!obsKNk^~ s\h bӈezJ&Db|nj@]e1ϼ:8\9-!WZcc --]8WIH-7 uʽƁ>"住is}YsƝnrיִeosq"P'D`Yѻ-/t4iދP! 'Xbdy(d3%xu^ iE/G2؟3^_uE@䒠6@li*epIK}}&%%ɇ wcR7eQm2 *0H4S MQPrE]9 2_hGPAGR}@sMY|KԐID./[_TӃ򂹭Ωxzy&PgCc>%;Oe$VGWx2o"^Zog44Vw,ƆiCh>L %=',B &>2WsKQ_M@%`P2P>2Jg?9 ?Mx7]֧4MrihP2*uu% Lrگ}QڊHIًԍ ʗX;O[LI n;Oиu <u@ S;w^8Vіp1FP]@VB=nXgߤ>6[|em-UT{TW*$/y@eOKK$h%KOA@%^g1EH(̟kE-r448C-EY~~ hOjMZU&3䃴'D+eՃ?msָSR~x 0i5b!_k^U[ͱUVs7tTӷq Zs$Xyc +,N% 6P1o A*`]^OA!~Ѱ'~_)n%ԬIH")C98wefdK"%f^]_ҍw03zY ve^PO- @d ґ-KKQietPjTkF[AW  ;U1i!4i*AM+rJˀW)eΉDMd)09zݵф6^w.{)Yvif'qsm8xynzv.E n/TZۋ6Jv7sh|P`\{:!H2ȟ/!~aM:%KmhA<)}q)$"_V'fzz/QcYJj]>slx„})~FlP]P&jؖʊoD}~ ZZ_Ǡ !Y4Tؽ"8q&rP6]-F֘Ф b !їDWl ~8/m8;mVߜybVFIZ]V|i*(biǕ~!D&mgKo8q<+8sXVQo]Ndr"DS .>nX=}p]s#b t)C~ RrP<` * ?dwDc4]X mEgIN)/a웹ov*| 4Ɉد'χafK8CNkkRuM$]ʊ'4A}2~}ߥХMGY*Gts g2lV7e'Ln6.%xM~ #ct^zE/\p2? z:WWobջлs:k)IjRyt1j:Naֳuɘ<mғ"fu/Cၳ_-X<&qd+0fkҲ)j, \Gʤ4E%mgL7(1eM~}wbum_SqQEJحx5<"zK8r:oY-tp.]J+LJl3|Y>L kBU.$46^~<ݽWgdVM4x;/-;ĉ*9nL+ WOzm]kns 網&-u9ToP:BKz_;#ȱch3-$pU@^Ņ|1NR(w=I%Hc5'\miȫqw?*?S=Iۋپ, IvFGbH(-BK\l޼ѓښŹ~ͨg< 5TB?$Gz-ٔ7UNÚY%W> /%$P/+h8uq_\ h4 `<&le M4gvS$Ga"@z?%+kEVt)mq躏v)\-nլ  ^Vcjb,"@ESPߌQղ%fj[N4#&`e S2HT`VS$A 4427tl}괇NQ` /y7qS-HF4\N CJ4fmlG]D >~} 42؊MS^tTV d4wiE3StAP*2 3$\3.]wy>|ݜd A0dsAGd[+,lލeL1Ukigb!-r 6HS+iK3F8}ܶ\ Dw\EsV@ Hy+7mjΜfbӗnyz4Rղ|$]M`~ fьmkl/ƪH2_`|9T1zۤ~\]D c-Ux:#xD C`i˖o:11]X?8Fyu[fLSfٙZ]tRU\CN=f b57Caϡ&BSNZbgoRP ~DE]Df ǷYO41C(XksFP>h`*RMq\ݒZƼ{z/ xxovc.\t`$1zy|TzIsm?%Ah+ >$@z{2 {tc2%|vJl;e| ON"xFwqj÷۩U =BTuݛ"}>g`0ɲq'pD0ƴRB%]vHg:L(p~ `q ix+˔k[T%53A]Vt)mq躐S!g`\8v $wCvKK \ўnRx_h& &H:C0Ya'eFXŠ[>rʇO4<,1 c˯H6ޒnLʟ T[41Pts#Qlž-h+wP`а1Ǹ8Z< Ԏ={GΘFr]MSl%~"+w*0m`ȀM@5/ƏjްG?P` 2)Ѣv!O֒z:hj2eP !AH!_OiA}ztJB~DH0AD|#իœQ˽mkK$C+{%؎?J+ʁ5g^27>4TAۦb"{sǢBFʔpy͂A}Oڛk}9eZqLTY 8pS L.aeA)vVP:5ó$Z  OÛymúY*`?7F0݂[몟, YZh4qQpv2O&O{8^Cq{7"}\ѕ9آ8+ G r5bMZhw؍Ԑ$#_ͩP*t qn?#?yE|W15!Hm3:N[ў]wq9%8+xZ IRS_loi/I=0Fۤ'"ȼ]1r՝7-hubb=(~qeùJMp.1A 635{yTsH"m W YTnkN,16ia4g Ug S~VdJ6ܵ f#?@~5[qbPF[BL|&V3vX "Q+W̧7{v`9j,.S*`hl3cgҒPP|~^^\ɇГXZ~$bzy4uM{_[tWT CFcacU>`5oqr|TB=kBi2]H*&^Yد]^zKgp)HQפ k YqBD\+RKW[o,ZP;{톶? RP)U?ĝ5}>n>4Dh?ѫ]hQVҗPy{c0 vY.BF*\ӫ?LqR^0!ӻ-Xp9b]bJ ڳŅ{L>" 22Ve;,7v&,yi_qV!V-ձXes&:ְ"/Mϯz1v{(*(ȡhtJ*rE:Q3 SQ˖³DYeqBpLK)RSKjVMV`dq x8bErJhAh.cSUJ@!i]Ayur7it|v3za+D>Jz~D`Nf{/z0Ǽb`P`̬M~o\X=|\e.t<JY О\شFpYx!ȃS~"dGa]_m}`㓅nehzØ)c&t?=)tKZT̡b TNi'Lô(*$MI"F4 "Aڂ|!z"bVq+xTb԰&**a)i1vj;6L2.^sݕNO !pMYuNV@yW@e܃+ѡ?F6ܮ;q3"/ Xg 9Oh\s!jz3>=%]DDK[l z} 2&?w;@ Z1Z} 6f]݃.`jŸ ]Bμ}kxBO9T䧽5]c >#4Cno9gG,l*w4uK؁xjI F[2<}%Uϗ&9~2#7JTfrfد:&00F[ 4b.V5_ A(.t9ǩXT#3 !@1GJ~m"Cj̖@n$)c{`~!%6jƿjl"S>s1_; @hȝwQ 7sQ}3l(׉ .¡8 [U\8n6EO¶ZbcnW!c6(?ֆsNgI)\̤EJtUx$~ ln}4  )оe?$Mt:΍uy7h_v'`p]xihE$&뜃,jxm.{6M*ZBhqҐ[x<x^jQ s^PY B !lyEcp|iEuFb7Ɇ3Z0j}0]1FT> 5Nicd44%MHwb<58;.o =ufdLrY37\o!й:'AtKslGs{I1=ULTe ҈l5[マ3srQPY0[}tItk`mY8m~̭E1u44&̆L(9_Rm8_*tq4rz,wa&|C : Y`R|`L3~>VKJ=(#wMS`p)osQe!ڤ MYg\UNqcłb8.CG(Y0=fa#±Efmnt AםgZW)xoKndPn[;.39 ]fd M;'{{+剽^YUzhEo!T,p1E,ZtlmZ _M!A_lYapqVpE,&CM&nmJ[.uE~a gW 0V>od C漿 _?Z:2%b F~I2e%H{+~gN +Bߨ_9 Agg9[ʨ]@z}N~jlZ9_#X˝$"08 | n3ks;Q Ln;Q1O)a<؏y<6cOГ^ߝ]0G"R/ KRgVg=3+Qz!H+2 !sqB~;|}UwYmsn|aDkVO{bMHް;x;H#ARܦu  [Tɋʰ4(NNy CMeP]$vFyyX.CRn2|ٵ7U}"\xR]J N:-KPGX$ $6J$ rkV. ;x=Oʌz9lp8받vخ}k],=ذ҃l6;F-^o2L{cP>&dx7kg^~{å` mx5}Zvqʻ#VM3Q5O %ޜ%w` m2\T犙-yo/ЖgFӕ{L [e06_}'UXM~e9-GߏCIE;E0&L1m7`+ԙ^N(aVy }14ذ;my;MY({Nyc_z \+NaxD/Rz$̄h:Z҉YL/lc> cIj|p%(b\/j+` f%{eFt ">h1"uD!\n^o7-AC'c@'Hw][ZYlO9.?_uqJ߂S9K[s HtvCW uhmQ*?}&nK tG3-& jIJ³/$xX f|Кo ?0;2M4k>d&v=C[af96sm!o"Pi.av So?=ӓз'1h =|G_,/d7 Kdv~, -!eFxm~ (;*(d[ 9ez Zp-Xn.ELN_{;ԗ,9Ju9MF2Gݨ'w>-텽ȫԎQi&&T17Jb)龙zR1a^"О WdgaGfY.a _$nFIw*${ڳej'H٥O#.S)9v ෮E+eNPt`(,z&'&dDذW%,0uGaLJ4.%,hp_Rݩ+a 2nn.),>ưԌ7j4~R٩[r銻^݌ rX\пwKr{;{uGM .!Z{[ mQP<&c˴zN4ҫT*8"yM $0xUN T]%߾QxmugK: <~dJb8, 唢CR[MVOK dQ f1 -8y Z~M\Gqk>TZemtkDGpeiDg~<^# 7?:b ʼYp ~"G D(`e)($.ҝo?D҂PSJ0Q#-s#3mTQ$m=ltruUouDC6h殭;#E0uLǨ"s)uKz䱅?rzyHnA"k3nC436lK'$ a<q9ձ0`3gl0"aԴ)RXorB|_yGzyN+&n$-XD騲d<7؁tƾds^<'W r)e{*:0lɿˌbΩsTV,C˻ Woaҝ1̧9I0C^+:zۀS׭X:[8R,*M5j$Yն3DG6COAj]:_a9glB7 >qVSIӱTZDdr+\q_ >~y2ғTOb\B?M]fbxjQr!BHu\Lcx:2# ۏ<' >󃋟п)JMDŽA+Vn[gَ7'!ƿMiGjb>oyp qT8J}]GsT"HL :vW>W' E%י!,Z"*;ͩ Si]G3n$HYJ!m4eZ=,*VFN+~D{;f!݉bIWui:] 9k-} gCc={fh:xPʿl(sX 1-OUdw`{6Nvp"uvչ5ب2r-VTm-(j ϋ{aoBpAI 0{)掹;/֯Z Y3CYABd^ju âUitv,|+m +be ۲BVeRIG-Ծ~ᔝ0UT}0yc&f-rzӇ{xX 85UED/bt?"NWcp!l XupS3F:&m̆I0mhb3iYdI8߀0Z6/mrba9A8MK1_ `,vsr \bwoZZS_en~KAy@l) #m+sG0?֥RBZl0l76nKZXS]o5J]F 7hZ /vDZ,PhnC g!QBݛ4q)Ҳ{;jk/mzė#T>3d8-~] iKk Ϙ2Yge{`(r.6_?feb]\3ݫ8, vp윳Ӊ2}~DB߶k=!DrejR/°ז>®'VQPۇkr8v 2ajHD(a.'Ϸ*^󂔱 -+?/Ǐ={L~Ac"c,4 >|ȸ@Ypr®fGJ"{ߑ㈋ae@ jh:)UkM|A$J:b!@iE9koDLw#t|I̴Fu菎yQD=(54\$F;˟`Ήkm."}ǁչ$\<QV\wL |)2_%+JyxllF؎LU{X]hQj4~D|pa8DTxse5&ݩ iْ11!֎$ [UޭovrКpSwYMp.fץ!۷a|ۦ!7= Jnx^SX/O̅d=ʼnNe}Jߣn+L,#8``:v뵅 T?qzɗ =ĢI6jlFcDc7u^C/nF.2{58gJjv7"mpEW5vqV3Hpjs>=םF@ *VWxY=̒n7X$ѹLKT^`'rGt7zZVydp.aj4N}ZL#n*:|dŭ-OhSY1eZ6p$upF@7@_}c B8n :i ktא!pK{:C zL*Rc E(j7aDs F$kP4%bz;kYh*"I$iT|}[r?0ڧ 1/r!ȅyV!쯾9~ ߄XGo-B*= >ԠRMY5^‰%emm~B^f*t,;2ЇGbx&s&(d˾K ʆn[z5̐[gϨoARV$iI"<)\ 8ӕd'\E}g>pGb+Ep"*V>ݦ&.KCmqC"*+z"Eoh('ܥY;}@_FO)OIXp -og)K6 vK e%;Ş"dl|]iFMRutncaw'P|^PkKpb $=l^k]{rn=£fbQ'`5.lڮ$C^-1bOhߘ!hЫ3 rxxH ~ҕPrGiaIYvpDUOj{cU2túrt0yHlÞUiAAcVhi0ko8Erc,|O#^6kY4"+t:;zE`/#xq!QHRnG>?5@Lrwy8kQ)/j!m zҋ'PS肮Ҁeenoak-`l2/(]ݰ?WOl 'Xz3 6'K3LGcH/Ե6@-s|-'tM_flNʓo/C^X ZĭI oVclgYn+LqgP1hܓ> 덤WQ3\6|]tuo('ϱZoiT W~sO+_ԫ|nO{љ6"𜣄y4B6iBï}fʆl`G'T~-*2yz _=9_(;J"OHԱo(5ڻaț(YAv;*5)2j'Lw=  v-8'w޻HeTr il|4޶+AKӟSYӰWW\6vˁn`3my7F$@ӍV|Sx8j1]Zx; &%g|oLSTgHPʁ u#{u*BL䆋H㉶6Ss%cpTl(IˊgG=CЊ3!Χnĥ-YgN0W2Ou*Nh?N'HgvArz9>FC=*[Y[Lվl Tс6@\Zpg6G:=%fff] ofTz{҂l+4qUK";VY\v  ޱ톄'>WB/jwmud#p Lɻ"s:5l l&uE%R^dRߚEdwhFQ߭I7ׇ5\NCՔuWJ.Szֵ~3g-;E#, >p2[Ѓ&w2Tb$`bHxiڳDMU%O$9{s#v*)0(W~\Ѱl"hS V'k v۞#cP>{x̓IPOQs*o6ٰͯRaWR"Nb+v}aZvs\ :|vRՂ Ȥ.L-1LfMr+?QD8REaSDG:0)C0dI6n0hH8rK< AjxC(FY_9YP5yFS7/F7 X,I3֙1eHH9sSHDk@nZc:sˢSZ`v> }SpertdiSk"tDvv4ed2L#\oNռe?@H+{ !0FLS| .`z) ^o.X$ 73]nW(-F6]?kB?X]*"{ i "5!zg6(vQ3SJ g~Bppy:; 8a0:u/)|(3D&)Co f{&di/ww: Ve?LC'z 2lf&~ h=#aa],9D7(B'In\UsvZsv8buƥ`b(V6Dߗ7 ّ^aO_HD#̾@#!#\@MbV SI,N1Y<1Z(h?76 Q,;R\qNl0ddlt?f HףX 0_c[F\o>v],udb}q2_uP{qD9tMqg;)cX㬹☞0*/ OV87.0{X B_ttȘ[Z5;Z;ՇZ^Kbv&cW"CN&a\({uт&Ud+p]sY7=0zn"T+_4^9$٘7pxE*#yS} 5r oh{svm*u?ʐHJqZ9ݬٻ\#1l/\v]:+<@ʪ*%՜ ['ŢFYiuWiZSG6sް"$;j1i a ?\;~ІDG?gxӹ=?|0V}j+hf4p|)E Ü0Og$\R#ʱ{pAQJUE 2Hw΁n<8ZooQSkcgJBxjv)M`rQ.oXG WLp>n(t!IdҨ`>H^ #r<3elj*4mySE4v qsD[D}`=a+wT*Cȁ6/[F!ٜHc/]jAźz'z9KP)BǸ[os;1hM 늳':KܕZ=$?~cP?t#AX +pC(:[x@:݉l>/-P`Ka؞;s>0q7 1LOg׭?N{qnf";{â_ q.^mrŠJt]ʎMB!w=Д[=qTNH/-^օ9fGWPh ֱV?RN2>yk# UZF R$cHhg[#c}˝ ׳*!.ƄidE_Ͼ;igſjIۙ~eUH4wGR^'EfX+~g\?Vќ2>/xIʱ;<OWu˨s-#,rC0*^rl)d12*-S#cGL.P\!2 \"r5yq&uPMz 4nj(0h[, J0@Pф;U9a|i~ͳT!ML y\>E:[$Ȩ6yC6z(5yPB,D0"ȣ,܇EIGAYO)9.yv ˞܈ʞ\jbמY>pu]s|~vFRH9Fc;c2G>kse-wU')bWaxv Py3u 5E TS9J}ʽZ vh8'{g%.z>倱ni=,&rGn?Wgyԩȥ-*GJCNobV1N +S$դ+GWWP6QԖ((msy*znǍaS\yv3os- ﰷqmg]AՖS}>ז[ێq"Sў}U9 ,ȟלEo刏ۢpXH*hc8t4X+]𲥷OI2FjK\uwgAZYǬxj'GdΔ@ /JٔhE~Pfj*@y`=/dYw Wx\Zذp I8J\o?(ÙhNf!5VQsUuLwz>Ѿtk׮N^F%MDg*_ci4g qT}NpX:.Om o&?R(N ?X.Xn4j /fT>F_ y۞R{tS٩1bafy=fi[ SԷig::LXp[wSay5D=_#[LsG đ_D83:ܫ NoOfQ/%L9}_ļ vHׂYYFӎ^ǟ όXrLYE| M\d4WL:n Hb(߉n1:?pL@bC92c2Swn^󽮛Z.;{ԣTДWJfs~՞x³Pjd,RuD@bKדbl#eҏzz ( `B9BW#uJ-nq}=cji8?R7!IqT,["O%rTAPp|/5  fU x5&G=KEʫ,fdnJ=Y^: ,ǘǝ>I)V{éj+j\|=N{<#pщV4vD- U2D*'lh1̖z'aV.D8滞\(EWo(U)3]?#rtsG$$SɌ$9!/(5{+#y0ec$rGV5Ӂr)ӎ%bEDf#(hdy]yI "qhCO=RV L*SAzy*tZlgx[l-juځ?|_a"%[\),s^W΄$|^a7<}]5yzqxЎ۴&klXPBq>FqC_m7Ws+)xz2ٳ7bG=3†20K꺻8- )*kĖ)Pu i1M(Es-?R8x$Oo`k w u4Iʡo c:k13(}=ɗ-NcJmP vPu6Tg_AX`礆6c?0j&H5ۖEA{c?[ZW7۪blW./փ2\2B^pDKml;`SX:Q&Y.Рky}u7U^j*"o ƀE4QC󝴛)!;)Xׇ ӺI9fpIa^ZpplDu>)U&8>.{cq$wVQ&*>RA6^>"K*H:恘Y}zpgs]J^CbTz/:Ii@AϾl!"@&jʤ+`LFL bU&G0r#m!q$=HHAZt;Wnt9 ĊhS?Ur8y)ԹfPt{c{=X}N\8#H'_]\7&$C$Ix<1t>)Ō?) P56 %Ej RzC3aG( p Jm}>Bί9:r x3zZ^Wu%˥!STXd ooFot"o/'t N86i;)'iʗ}amċM~2t($1w/-ghP,>S++z` ' #gF/} ZFHP{i3|M%cIjl>.w}ץA떯5¤{9ғS;՟(\\ S0i+ץ XKkW ,XaCPF;?_^H4yuٱitz䭆x؀=cx-72qaZ *xdҫP̫ξTh]g|amIrUryt}HLB,(01+PwV{3x<߫F\Є"I0ω|g‹ Qb{YHk|ћ[UB? żovP'5iJjs6g_>{NWK Μ_Cɀ_,س`rt|@#s=S28Ѹ'e ؙ2 -Q; E \SUQ vDyxvպxÞ X72%ՂnXCOMYHkG]eE,s3[w*捝<{[ ށ*p} Ȳ[V]zw: f-ÁbtDo.B L5fBL<)h2J)h%yX~ݸMyZGaʀGF|>2灋aGQҕ~E.qR CorVrMn)S]SBST kNߠ))!Qtu;E*I|o+t3y'–vb7zz :Hr!tRcaJfj N=:cFlL)BvP1WfpO:cwLrubȷԠww~L|s|sݡ(ُf.] 9dum۟86\73Z⎲_{Q. $ p͖wi_܂(>'P-%X|>G;U-$”,ۤ񍧮ڍIʃ|qݧXDu)Bml'Gh \޵5̪C"QNHܹӯ1)/gAf #(c(9fK4r`hx%%z<~MĜg)&ϑ"6;K.@rVDL @zQ bǫxl @HND3tU qzkζwoB^Kɍ.Zɧ$st=)QFL$"HmPq-P &XvS#à҂eVh2cwCh/j1čw # qiCĨWLPgt (ڹOOдy!Eݚ힮0\P?Ҏ`2?Eٵ?ncߌqi޾#2 kwmb_ubי%L zt/ߧ.J:]/ܖIE!5Hw\VQ2T6^zHe4SoM%vLvU_1vua7f_ \2 T?b%Cz/ө`ӡʸ[,0ϑhRsLEvG+D@<ٴK +B^&}ⓘ&$1fw7/S0xv(sW$OәTWt㏆?Rn?W -R e%#Rl385)psK=cK8u'FBW,)} 8N\3ٍ| O6%Tܱ,])t=OįDzW9Z`~; cg?UQQa-1JP \́c>o~uo`2U?x|MtRx~hqec,>/c'x>cvttPm-M<cyxSSZ[4팷9Am@kzzz-YdzPQo48q@2ϱo|X~I2D%:UPY0cb̩=Kel"D5~ .Z CRia b aA1ioGI6J >Doy)үƜY&5A~N6z,Z׾v[RR2CAM3=@"r>C@84,fttQZ:PME=lm8~G^;6Ɗ]4l=~0m" N<`΃9Ֆ "4Ӊ X &n$ZK2[G*T%h #o8ºo fJjV6wu?-_4 4vB& 6F([YWG+0_*(?He$mӢLbUKXX^ 0=Y'DGN-ҙ}CqU?3q~Zȗ|ѳE6\=}mϳ9[&<^zל>]QI$__Á<;`Fv|1h uDž f>+GO{ϩΡ|;#͢XD6?"?+ۑ{W5=-_D8GVǷIUGZM]_ lS>uٽ ;қ?UQaA%l_ 5!K[E,~M0o<ʏbe÷2eH6py *SEUe|PJ8q᭎9PX߫= '?Wӵ]jV-NLww; O%:r&+e]QI*]ؼgc?y1L6mb|.zf k.CaM ͟.zӵ]5b``~AMTL zT;.,NS)xR0@>S81YO%>l.0;ẞONj;:M|pHǘy?]) IZy10#ZҀݲ ̑ >os^JV8fi@!AQ9-U'D:Qjog8Α$&85CGm[żاF_J ;hKrc=.h/=?8q&m€ “t\]lCpB% ^ T?r?]b͓:>0'a%9bm䚙& -ܹ 0"e謩vRр'; x[ͼgЖ>j nvLFƜ oUs*pD)aVޫ-T5cQRǠda~+ Ӽ8RISʚɭEعو GEMkiHNjY}LvO̡*J)G^VXTsuXnST傘'iE9e%4Zv<#O;&B5xؓjRڃHmByK\L 쾶X?&*ǯK.Ha>.Tݢ joe+*FmLUwi -8a}L!Q= :Νlt> EVW%{_6@{|ToUmѤ{\GGȁ~s8DE W8s^\0LHۿ1Զa"oHevh/%`ϭ/@nVQUIpȚ93nH(}~,iAIYDpяxmHGDzQ [\$|r|#DX[(i%?!\YSqG2;˯?`r X?vc<7/Ίv5SXds_$ƻJبRs* jMM\g;0ܴ$&Dzbl82N(4yh= TY\;0E.mPZ 4_p03]K!PXOBs$u˒PnjEUm,/DXEv7O$s_ŠX ?j/"_r vbid%G+Y9掫0Ir9uis }m_/EҼ8D"}=:\~ԵA紿R#ئ=PJuu}ɱsK -GG8#[l{H0#r~F"/MJ}DNjYd-Ti lKZVtKB2̘Qr q!Y5_crWDIǝ~͝-dDmdt.+p"U '!~aUK-0OGP9co6;\O0gX2h7ݔDͺF]QVc?Lqjt甂G k9&. б{Ah$TѰІ͊mF4'BsXFZtt#( 2igT 9G>$+$ΩƳtG&v}dAJv˯^9B#׬FeɢC1ӻ}R=KÙ(Y]ԏa_5 ) +Oq8 #vWFQgx'=_vb'w's##)[-v> gܓWӌDmF ԹAa. b5teB XSo\k)c./F_ǠaBۄ"{ @\MkBu;؄π=w/0*Q$$~OUW(>0xMQ*:U֨wӎ'h j R#AB<2dG /EABf\Lr@l I1q$g$1/Y0c*?y1[lZ: Xȏў Ah߬C7OC^y"<K5; 2^ɐ$Arל~jKx}!~>I.旜b*.\Ò%KV'#P|%J dUVc/dzlw4Eأ'nrPkdWXp"w苨bٚ6xi#~܍/PQ؏k3}V*Ři^LY"WRG>rO䅹ǝ*[;SKz8cF0-ӯg5~EO\#٘S7jCCBzϬe-n9>f-%S )CT#%b9T$30bzՅ0?VM^m(BZWRX!F灨 y'ۖoPa4U1-Pi>򠾓#(KvOvu-rt D#)ٕ6pzŶ~7O%g2ث3luM`|]Xv6ZK"HCʐ R_{h$O\>H i.jQ\T^vouw%LOM'6}{RP(ۉ𡔍R[EPL}Ps3}f60w > "٢Vau9!?hm.TxB[4<0.P7huNJU#!3l$~& d 큣EѶ=t SJؙ/e ” /S>F^Psݾm`T&fvڐ^Z1r5JԵ<~@(7AN=>yI`A:sHQH~!<zNͲ/K[u@w-|N5HHm!2SԣOתt i"dr|[:zo QV.rt Jp'MDTJnSoi^5ӓA1Tì q; {c$ľm[1K` "=gQ,[a=yy]'jx!\BdFF5P ~x">T:.Qi˱M(C  4yѪ<ɄL#p3Q6J0^gD5e=k1a"(M[1Q,Up}0ô@ ')٤KUaOP^:+]xFB")qҩгG~|XN&25ɖV O'AawVk¾ 􂟲lҍ.(h i,|ܸW",#s^6=dMM:賟\!1f8|[xȟoI$sڧ<L`|ӇA&%TON15J OE*j.QkSS)o z*:,.LWx{P&F,kN.s;:Nb:qq#aAqe`2COyUe>JMv /Q,{j|v,Y3O3s/*| hk.UhJk}ϲ7ڐ}#,w2?(+~J 2K,ڢg$s5F>M]rYND:QpؖuDQXV,`)䏄"u"F|EʹG%a/GZhhS(g4BVj7rltP`uU.]|w~ on!TcIBpAUe) jd Qq9ЕSfIo^sD*YʦFCE؇1ص%~V0ë!WˀUFyp,oTaoAQg[.V$0js5 Ȋ " HfEz,&o&MdT$^YtSKI tH|Fo"ACozNد 9h!XLAǍf,ۙ?aejdbQvJ UxR%@F quQ'OHɼؕ $XbpGw^۵i2WKE-~ވY÷xgSw[;PzJ&ʱQ͔8_k}^f!HSwM?mیxyj3iudqmrujax3lvՆv8ٟSk6M;bvdIcWXcONBw`*،ҁKggm^̍4T.gJ/+B=H}aolQSzJ%RhȡafzQ|ke%DޫhƝn@ES >l?cblkw(9> ܳ-1 86 Dqߥ6no#7oIoMt?.}Qzw:9K1hG_L xx%+&tXAZyM쎀bҾd-S" _tČ6+r9p7޹b?B|X fv ȑұ28N 5{'@Yh*RlPC:t|2mMjIAn.R^B(٢'UURXvy6¶Yc@\+}eJŜ.N`oAvdOd"0;]$E3{\^(T!msa%¾_p8H,R+!`gCE 0t#{R!=ؗyWmRض]LϤ8@rB<6xe0K$X \(W^*|qC!>!IbvcsA~S ګ^vmsLgcp9t3ʸ~*9=j#Ocq 9<-?Aj;l>^>Dׄ~)2S8ӧ(:Ona& JKcɶoeML&йuAm0YH_ۉW<߳R".NJ@K;N5ei̠f~Vf$=]FHX%c闛~Hstt5.4ٓV8B|iMQB9`-,i,q4*2Tsrt$r=+!{[ RŗNfzFAktOs#@?{cm&߀WF:YDƍQt"k^]VuN%܂l]o]t2Fkl&[_"$dVMN-6 9.'M;SzXv$`!wrJQz@zn`D3NԠ,'EȫrH;Ȅ BS.%dЩLCq¨v?yR r6Պ S5 ">:` U{K6Qgt={¶DjrG*Q;yfg۶Eϰ(BID.+-j{坛Uo '`|4H-A@<>.E[Cٰ+ohXsmjUD,n?l5$Hi'\`Z(״+%P'zjzYr2rH&•u>4N-'}\Ú " kzM=.y1HWŘ8yP|R$C0ěH ƠISaRثN\bB7[,74#!a {u1pjDƫw޲T=Yӝw}u[DjC7_9ȌXc6g# ?y7^oYp\4Iβ{5L8TOcF┍F^:-`O TWk䕬2՚71b}G"kχHD';Ejg7JC{.-~3Xr\%XO3c_$ё@-1EBwsfw43SlvƝk.V.I^~5NJ^ &6kAify3.V/IH9*"zt0qq3H'K!"Rxk{ʼ$&%I.m{)0gY~ +wVEKae6C=HE_;jRAmRAPiUfM; XL݁*z{dS$vYٚ%qGGtA I6un狢iڂϯCiFZLKl4dKh7@_ QXe.Z-;27A ;3gI,g˙fdly5:2j#bc8`mk_l"pp77dZI1[Tc!͋Ul67w?UJ*I~1Wq_6AB ͖|HJAqV<9Tm.Fbg88j`Pvn}5 (嘏&)Iljo9'@-k}cɾ_14We&ft)y&D} rL (<25ojW, r"LRΕlF7k gĈt3,{)X83Q[I_*܅vVF#cIi9a灾zds(GfV`e:9̋%RU\>x_;όTuIb~AN7*+ŭtloo [6<&Ba' >o\T^3+MH><Pjl@=l=Ӧ 1qx_3*gDd,pΝ>,ϖdKez`}L/fa __Vfسw8xSgK6˔%l\bgv6IP66[y7X.!@jÍP߽WdNBf˴VwKx?$SݙXV9ߒه9V6 %h~xԿ;`\ /E͚BocǑo7|SV C0XR A3yxXo*Lj )t?;›=W:X\(ҽ_ 2<żII|_ZdO]岅w֙?v ,B6#Eg"XVy9({fb$sv񁥚\ l`Қ5Y~q EH<ڏtLl;;,(NcuLZ߶N7CT p̯1ޫ8:"7GG#:6JQRa;aAuk AO~捷, B;n#y%I",;›=W:XzBp|tH[BXŹ`e^X$JwdžE̢$Fe䡩-Nqg39K@uVBڧ/N[b[)'뻕@܂k+|*%PKOrgNhfM%A$lk{s,:df˔%VWsve_5ۡI⭁L@$22ӝ|#:E?1"JT5dzPsc@ #'\ M_ Nj '#ۭ̈́nKf&y瞘Kh܇vg34&9Cc1|uhs.L爖$/“QF)u!zDO%8VM;wh2x>Zs~)9czgx1ЏzU( z ɺnqyĆ f2kB |\ XEh`=!a@#Lw@q-w^ ©Q4 ~@krHM,LIoOkY&q|x8ԗ[zܗ))]oI2А~M`؈&\`p4mE}eH: 01Ufpz4*Zx5, Jmm&n+o:&4΢S8d>r$Z gi$ 21kbh-A<]KN3dl:,ux ayvƏ q|u߃zkK8SL_LW @jHQWq֪wjuh"M*А{Vđt/S:x̐2{<||9e/My!'9lxA[gD dN"UqrA̓@z1XuDraWE0JQCBpS vyd aE %G@~$wDSWGH~1'PeQuN xSGpIm 쀢7Ղ9%k̀`>#gB}I Օ_~`*iE(OD 9"̂UEꞱ8sk ?KG8:p;pT7?[vYtB4Ѫ==/P\c#lE_ hs♝!L>+s@7[e-$Ɖ:y9Ko9i__kvnźT28^o~^r%.u<;Ai%:Ba#d'LZJ~i]hوEO22KD.Gnoc/d ɴ0ũslD7x!8ƳQ%S ӶO@Nϡn)6WX)o; _f7ziM 793س ^Yķ=X$DZk9W9dGzضv9d`c<U)p<5%C8Y͕Tx-L[8BVX',CJ/͍ctzE;lj6Uê 9PhO٫h˒^hUT=T+CndN\Qψ" kmkYKe*&k $BM#KOa'T};EXx^Cv5).Գ T΁84)+W@K8o#u`";%MF?Au,[j3s~P_(q d2eqtuT;}tٔEGe9*G"<*ݥ pyd)ʔr¸,:< 1ˡ+2Y?]11qC{ 0CRwqfs_ # .ਖn-k. L!` q/TX\ײNŎPcs=qLnz2-&"҅v/1^:;(̉">;m:ۘ2]ږGYfyB4~4q /^>߱VPޘ^YP@1k?@7)%D#vr\58zs*4d-}z =%'̛a 㸨 %i$ v< ++U CY*f~uukm34zh)@;+T1S%.9Xۖ.L @(E&UA 'ԥ9oi.`|E!Θ9п~Q9镁rC'$6eq`cWAڄ7k1Zeie}_`ۚ (Dsx9 iBAuEٛ[neJv\HKpu`.`SM7K[hS̗*VC,+ tW޿J=>p|rl @dחZ#J6i#^͹vy5SŃ,ۧGxc6۟9T%a2ú2MSFÇq}@:J_h)n(%&S{ҖT$tMs`EU6i͓Cx'qwT;>\TUT\^P+G qζ~p`'-RVk;LvDJ4 W!=?.ڪ# j,U}%x%+3]U-uMu?ež(I&{L^* ԨoBl+ƕnU&@X -eue)c¥-9y,,W霣,Y~ޕ ZE;=p?QI]$3a#ˇ h{?ɛsN}gtu4T`R ߶ddHU-xA>kh| uBn׽N)9I0#[i1ĬK a[CdTweAo.PAx.q3DxXU #Wr.F\0 ٯjj]PL6$1ϯxS\fЌG ́R >: -O,Ӝ|G9yedFMچ,ukp=r#~uz_'>S3Hh)@ OWLȎ)4pvrc$<[yu}Ձ)a)uV S裙_领&U*ѳc1%Cx.*mkȩacl(۸3YY q)2() b1閫XZ\`=01dpj>nNQ yA]uM?#rgu}ĉ ?)Mp[դ&Z7yٍogYҌ@<8OϺ1u/'T9gMq=&RQԞju3ϐDIZU=e=v v2ʒ*U疢1;-y+u#UsT+([jHk$8u$&SD26ʏ+}(a{o-$Ddt>Un[UK̙JXv9ȃuHU8Vzt@2խRzP֝#NC~>TC;!\¯M@uk.?c-lp8cr>s֛@t}R$&#AA%,Ֆ%p|;:oIdxpVz@#dhaaxr>2J fM7Eqg*;mtt?MAA j `| qOU7E`5/wǶ=:lȸΘ p,fTpߓ Y]w'Xs;װ,YqO)݋-SL4nMXzwTÂUTNTgaۂ?ݗf-s~`d{h`Bt\93 vwY|AP҄NTgbq#,z%L1˜e< L`G# Y7g.7b]MGZJ94ur#)> Ěk!YW.yt~0|N|n_%#Uet._oߔO}$"*w'!C8()+(CBg$d\ ~u}$iqY%L(XEK bMhl>Ia<2kLޡL2{fs^uz?Lʀ ՟G}ℌ#ffaAԏrmOXΝoq!yTX̐;]@*Af,,@r.-Au:ڐ9X{e@cIxnhl{8̕GW|}) ol,jET=hxN5UyqDkέ8 *aCr8eMԗ2>||턘q?yLD1TjTN9ⴹ\J)C%<߁ ʆy@auTiڶֺ=`N12%Y%AK"-oys(^S/i0(>o[Q0g8e*DtN 6Kg@ X}CƠkPiU޵G3O;!,eJiӺX#c+{O/YlFn(JhD E#) rcIZ9S&=2فuI,\w$In֣{Gv-3 262pޏJY q=W76)u}KY5 x+ g#ԨV[ZmAϋVۉFy}A>$KjkUja1NLבCY fEb 5,9Pڰ:TϪ82a ܓmOǓ14գ0#Hᢁ z^W0Rgp YG}FxAvH$(F%-No<$G7kz?9 G-qZZ:Sܐw/r_da7 2EEmN*~ E[40vdMTIB=3JÏ3h8"/*+0 iu56X-P4Ph̉P77=Bb p`]OA$v`茢Yo巾7(M4?f&]_l >)~W{*،oMDI{DG zt7bu긩bX>Xk18A,B vEI큮Cu̍8tW&$)&&!\Kbzʨw&C.=tyJ aδ-EvZ,_ GAVq{OB\Z)gm`-4h܍:7wpL#RwtOZ皸o맕I[ݻȷ܉pyӤz7WQT H$`nC+hnJX)IgquJzDBj!oSƜZc/nS1Z\УՆ\4/!Ãp9,n,W+vnʲ|ə {Si6+*O-aq7~} MPNgn3 zjXV\Ea^^|4؄M^^[.`I !J})Zŀ`,AD]VT.*\vkN>oo)RIbB$!cΩ#YO87G?&7\%J$)^)U򋗥e,}Z;|օЃQFcV!U}x`(}r)攺t@M.fߜ{jU  JrFJ]tL*s%b-%t|uIJ-nr|/SտH5IH(/8?n\ToolkTQR%Ʉ^!<[" 3߀Atcxe!W%ƗP8 ,j;.ʀ%ÄUP&3;e+2 jV_/x,57tk|CK=fg!_1 !alcZB;@oָ4|~ߺ̶;ˤ/Qh m\, TBJ-m|=3{o\X l2kw.dT1EF_CJHO#N=pMUZ/?U"nಐ+l dTgP1ڂ x>0x bcaFdljmkp\D{n}p8oVa۩]NRWJ+C? aH(P2" ȇ 6m>W)o6K+ GZ2+UTAE8ӎ#"_nȏwR'Q `XdAqp7@p?kUO"~jqao;h*t{~[/e>Yv^LHUp;Z&s/&"mt>ؼo*~0A x(p@Oy pR\Lp4953 )@Ʋ{2TK4xp- @: }bUY@rjg9'OQY{I&aM/_Vp`jP3|¯>r6 4`nD@ȣHxȔxlV}W>V18'^ op5|6dzW^8oha|󭄧BQjp}t?֓1Vf|<޹bu! tC8ϕ]hގDdyM( $= } i/8mZm Ob6兊鯸47\pK|Po.{Z0b@4 '.|}K "_"4㤻l ~[E>NG2(K.Aa!;V }RCT6`|:>;My(lʡg1vND+xGD9W]{'һ^Ip[$ą.Y(?y9쵣U Wu pgl|Yu8Slad#Aؾ]BǏkRwɅ<i.8 ÚֶçvT]JnE?kgKn 7m^rlІGG &kֺ;S'rWGQGdj÷ޣǙ|BT%0yY4zdJQvD+3qS?9 6׽`žLdGz(Yd!֝˼=:ب/ANg\t}v5bKZV uhP@&׵JR[Cvm-Wmv.ILe'|S<o6 i[ @pwڨ~wI<TSV:1*NJɼkk)p2}@T9*zx5܀19DYkQtΑA4A˓8;{lb_B `mÁ-uў}`4N!b5$d. `Qؙ#`Y[6=\?C4֩x vwMrjHB-##_ȋ*g@Ps excYQ=؟^EY%s^r~ mNvb~Fa^kWRYjR]EgbSf[4”}ʧ{!L~Or8X ?! z\i4H{XimǣJ trD\`$ΦG+K:Ipm,zPK4¼W>Ul ޲ފb*Lm;,Ra^rD\x 0T-hPS=}Q?r뿔: M_wjp,Q`cvrT#nf=/dg,èӟz),]n.12O8;(/n(I0>饑+kGP/1L,/\XA,+E&ɇP _r`<ݪK/W8[dRBe[ܔ cY.:IQ^+/RTA˵W^ #895'.un„:h SН9-Y3( NbkIa}x92xk~mꮣ0x3#-b35(cH-Vɹa.Qigg׃[Ԟ=*;n93N WRkrW49)OrxM޶"ڂG # !:4p !`Ep鉍gbLQj.~@K|. oMNhr:m$ۂh;}@* tn(sVy6q{TCw_{=:2/~\LOZšV/}2^w)Utvm\ƖپQBӵs}=~M@/"_X$#vCqЖ js+0ƹqo~)R^dls; xۦ0"#22{Q(AU Sq}'3M͔싱`)\k-_Vn}I59 Rpz.b(:)jۍ#(\WnreMХ[ 9=x*Gݍd$c+nMGI7vUlnp` _jS {p et3Sx)I mِhtM"-tUCi_ez%+yjYfWh;dQ\%p4|/v -yXY"yBJQ6r1od,#|xYqMXT^sƔK͋ǁYO+F?r#OD=?DUBtT ⅖$cG-Lm5ݬaAE1R 1 YG}u`ٽW\;!a >}M&In, ЦHrCe3%C脾+n# RFMljNU\jgvK 7γ3QJNTs "?2}O [\ܘ',-FxmM)ʽ֬j$kzF@'[V\/ЮO-,փ -|%_VA}Px4nCլF] *hZ9@3!]i.`#S&wKF,@_ ZL}ҧ5\nU‾}Ec3&F< P  ;8t47JhX[368ڗMԦA͌$GXc6|jǘ I0lƎ!؆CW3&˜aA$–ەbcCI=$@/HzXzLu QE=ώSw됐3KAoÃ# }߰-4 ~Bؓ5YPgٴWxE< $˻FT2H }cs5t6w!w.O~oH@=kqWf3R]`(ja-ˎobsxӪpd8lI hxM ">qva:Td+E#+ET5&V-u}{}QqհzKv=|TIM]<orZe] Ο4ImY|΢H4d4u4 %L_25 5oTG"f&jxkD!m1eLm_"@xP"pۼ8\~a,3U֛:nN L%GG{KwLxO5;@de'xg,TaM~2KHo= P|vH@. ˭oC]XкԶ,7u52OjXDgutF|˥~˭1'= eOZc1 I"ܹ)eS| 6#F1GXsYWRB4B2PDQ-AkH_?+D0 ~oҴb>rh1h4x4~Gc 0{(jk;oE^o=k~Pk~j8]~tHFؘGێy'wjѓ8ZR+,׺3+hzU|sy-F>zfɳ;ßrg$$Qc(v^;X6}J;r!sMϑE4m &d?-%DS f"g$\S(p | xYG CdפX2z5U XJQn!e^A""j!^RrIKOZB$ cRƮkhmA ޷qp<e>@kmX)9ʨI2x'#3c`4K]. -#RtXz*eֿ{L}zTTM ڴ(O)3UFgpB_JTQܩ\E1.X쳒KhftǣΕcK>rnaj޼GH#,Vst1 s$}AZ|4e@6+(8E@mNڭAQun'.^ru< <^(GIɈeSy)y~c{|A)&6:yp8.\ Ye3=W'7*]i Fe>|0fWl3+CCm֣Q=6`$P\v:mZ4f_ՕzcUU\L(-w*O(x[-h恬tDEtPJzhyg<2MɝxY*0,*n3,|}Wl2GYQ7OT Bo캣4;d%bn~yo+!2EzE⻈yh3YЍPcyE;Yzg)=y7*5 8:YxɄlO& gLcnv]?!>AD,N:'""y(Zb6yeBT |&+L<0BUQ犚Sd)o©؆1\D>bȮKLQ@߄LX3X>Wdc>>\Uj"hKc!i[ұ p#{,Gjg{1κ-QY9^2RF$M Vn@)u" ]Y镦ڭm7wՠ[*L%ȤOz-̦-?_+QFe_ (8zV!nbb0=tXzdsKS"iRYG</cs&] q V~ĥa q=+)Y>͟.H)gCu'L}.A :9`C'-Bw-\U=AFZ3k Rͳ,[r0Ы܅ m0z(.i97vK$x>?6ܷ^2i6ADNѱۢɮVXV4]tKDX }dvx{-}hO1\;Mtw Ӫ`|w(x2l0vVE.`\ՕLiB=F1~ga&ULf?AO*?ΎSV6e@z9bWSS }]~BX4UB`v` 9$NRE:*RUCQ!T RZ'!$]҂T7nܣL63@8xr%>ug~jޏ*I1jz{gyu ㎤g+<X<DWL/D[Ny)<gME7'38viߤ &bF?A+X/|AxnHb3 SaWc^dՃM6kMܿǥT-Lɢ /ɶU4#Hz1H~FSANA%Wp3tAMxem;H+B26qhZw忶=z>!M睦݁ DuOPHLPtGz5"UH5/0 ܜq; P/F/PC5fQ~PqRڠ%`jvD؝!|?CU-h&byv[_Acpn2Kֈpz?GW$k.$4FF$h-O"O jb[rB2=&.!bNѱۢɯA܄ۣMwʯ& #E$IyYd97B9ӭ՝azVa1*d8Lwl)h Hm*Жryq,~B׍b$ D/Bw7GI9 `f;GcLȟ\1 k|Rgn~m{+QӛEx$J^ϭ$JT=v,#hy&P_z:oW]/}JFo# \*j#>~ab/i  #KwqIWRI託iA\1mfRr(puT݁SUZe ?+Q=c [e^Y{uM3" Idjpn׋F9Alk2Y#Ò`0/a+Lce^VH06zIuwWuR GpTvA1 مr0T[;HR [w]mJ; UeFVd V*?/m|焵E~S-iXX+tvYZ8xj]s6LtٶMf2T(!J 7N-{p-_;6^g]5I% B,}}D=Λ V[ыZdf䊕/>(aɨ \Q?m#qztcs!o -63!̸%OPt4;MU kr> z&+AM2B㯓F2lBY"|h=8iC#Al&VȨcوvő ϞxSB9/EtEγCN6. eBU8ތ:2Bm" NڶcAt %t#>% Fm euf\>M1e'5piƀ,}3'p12R,\$ulHNrVe>RMS M-s⚙WjjqDV8~%̵jt1@< 2rjr h Z!*@5RݐB8,^ }(?)sSR[8?5^G9%EB UD8Uđ1>T7n%|nBq;ZӚ4?S[S.\d6̍;aJk |ZZN1~ I WI]qO="ֿg:dk°JKݗn/5k@Xl2f|ͦ8%7sueԹ`/vc&aQ] [Mh;mH>xr]n陌's5`oq53ír+aӗjpqy𿍎# X8^OQ˘d2fD'7&4*=(]E)z$aVY_M1﵌4Wi7e}e"cKXd6Bd5[ߗGJp*=\!G!D"] a4Ko'ٷk"RNI[jWᴵ Gs "|d(Ld+lɄ6=XMJJiڃSE컃3S#KIAfʍERvZ+UwSv΂3_9u}aM&%e'!i:\xӁ/Ri0XLr.ߊTVQaڄg#z'H8Sχsș坾+àX^د5 BOR{'APN#wCzer9_5kcYmR#\̈́^%I <--S<, ;dԮOAY= lkC.:s_ ^Cs3̇79=I& b@ J  % T28mueD z:9V/ƁMrI۷maQrx& Ə?.©fdV/R&/^blO YyyQZt_?N˿XTفH>\^k6S?cj%9,׻-Dm]mA.h+gbߠ$u 5x.fvDf^bG\NjkOw6 N\k&An3}ɐX63X\2ӏ_ە7*$ QRssfh_Iyl[hOqԐ1 V|2!Vs^j7SNȨCQi,:cJY'u ~g{== {fܔ!iTN@X"@D6`G:'\c x7p颕 Hи[Alov B4Vqv"{*Ϯ3@w@6 |B]cK`rk9>ʆoY@1bn߀{S\׻WLhФspehFf[n~=MӒ__Ӝ=FVy], 9:1`n #wDVvJE󐊓0L?a}vOixVY _>n?x2S$B,)؝˱b38յ5|Fdg<0&ol5-^`Եo;u3B3#_VH`_SuP[Nr"E2RkIQ;bar \r+u$Zy]"+7Ldtؾe#ԥ%Sڰh|WRWE1QXCOos+v`a9:5QD%NQK5lAn~XwX9TS?]52DV;ySt&@'5{| '(&eJީd2gTH=݂!D>) +wV:FLi cÉݖ>%*5)zDgpnb@՟#+F~٥_8'@08XWuD )k;}Iif(tDh ,ALWۙōS"zY3}m4DDŽYGYc4G%yi_$YCKb-x!B O'jڮ@Z!I!G/=6Eض q&f3i9aAcU@ hB ;I3Fx|cݨMu!βq0Q\aS`,qrØ*n!ԃ,7JUN\:/3Ƈ]!IZ #fNdcD/&m|4õ2LfK44!5gzo]M(m`TYWӚ,׎Đ 1G7ͽ)e8)6]Kn9-*D\YUSei٣~QPEbO.!4D&`zKIc(h->qwxM NZՌF4}D6&c3 uD6)y\ -/?tIJT 7%upaOAQ~ 'y ^lTԌy_-BciVos Z%~+l:pxꝦ ?QcpZ@;Epwg%%i,ŌHXi;ڿP\;lyx#Ciç]~},Ė)ukw S33\sW'q{Q?^Z.Swnyu# tI;)HUpsڥŘoXyrY&7~L\V4>лmI#L]FVp=sh,IV {E_̹}ʕ'oY(C- jBJ._ &KK%"(3AiOA㕈ip"Ǟ-rN7Yâ$KM|L{9CδbCy-%bd䲴ڙ̈}8e{F(Z2uNkVFu8p1Z r||7!ho?2D Dܻ(#6DOQ ܉!]|YXr刕E\ƼnP%QV#?sN+ed -%;'yvI-n<ˆDs39 UNvSlq*S 6ך_i]_SDQiOs#+[*XἮQntob|Mr`{(Ty C4&֢7pߏ_^.wRwd5]46W$wMIB]1?xv.<Ӏӈ;]@ھ5)|1Z)j)PsTk@J b~E9:Yp=p SܒbgM׊#R6LQ͍8A.z9p΀748򅽪qyMåU{~ވ%SM>E%?)H90vSOv\mDy|VKLMx˼_;g6D5Fͻ}'.>IQBJ[YgC6q-C>WҾ%DT?,iL,}uPMp|ޛF Qy8c^z~PMz:hU1%bA׫O_1RѲtFuIt]#<D{(D-9&J olDi_$¡ݣhjgX17$R.%_'&(ʿَ^~7.IaJ\Dp}2wlzN|LX|D)M18yGuLråI"nD wݖ 5|8JL(QT fOW_\ә"r:֩OwBFz3mC[Ǯ8# &HPP](ĆWAݛI29_B?kT_a;Ec+=bحtWAAsh{˸zrom xk"LRLD:GFEbeoO :E~EvrpY7BvzɯŃΨ%۷-.zC~sj(aY. 11`vl1g%`%F±ynj_bap[ @/Q%T,48FaI W6 úm@:YinΠq熄;w`B}IqFPkN'UREN/,P'3vxѷJ.bCet"vLƙ}B)ǥ\Vo]R=դ}"(lEV{t ”T}ɮlQ8|7A6w8yc3Ə`\1])v&W@?"@Y)#kvKVAY siR,8 s`?@9%p:sZ9b\dtծ d}5gd[5/n&-8zz!p6q 9A4k1L,C͎l*T5|">v Gzc 䐩|x%[Uh)N+g9&Ko``tT*} =#|NwR,9st8,Ce[\! 9ÓZPY>kt[H8J8xiן%z"hnǭAKJq৩: -UI)̙ oK_L]Zigf\e? YL~cHg w Z7L:}FBxEM2к9>i0Iz`:3x} ÷-l e8R ;?CkwDWߟA]|nwd&`8n֎yL[(0eS3 ,5Դ&U gwH}TiuPa6o]}'WwR[Sr=g_sܙF \L xɐ`WB٣ơtAQ汳첬'`ɼ)׻V? Z{Qo@ ?')1iW}! ~3p0INvxu,s>ѐ4֊5xR+24W&).zєea"rz:h v\W!R .DLacۆhR$NޱfLʯ`˜RL+Յh]Bog# ϻc[<8`uΉ :np~JI9:M1Ƹvo~_ S'QNWQ'`_',C/V~f𢋨v9n0B(sٞ$8ZG(b!eZYb 1> u6NuwQhcdUO2ٮFOH/{.5_K=vKa2.UKgAn eoMBn]-BIKB˝v#"Ÿvcb[GvlhJ8^bmLxW/ם1gnD2yٍS""<9S>⸙yCxxndɫ0]ߡzET7Y!::_7‹Lkpt~b2!_<)GK3\660Pû5YKS3EM%ħ{88yۨ$+_e62L6G5$#4cCÑX j{ OrndC?! +;.AKr/%R&q#Gq'mC6 *uĈ$o׻h22<}oPa1Y7_Gm\;(UD2:±QH[1>V:;nScZ)T&wLuk(8,bɔZ^ v׾~DbnNzT#Y }~.!-Aaݼ'Dʴ&㑽Q[ է/{3  ,SK->@ā=tng6D xߪCTJ>2/oЈ*rlXD|HTvhCN) bRiSuaF4tGk7|}G٢ƴJ,f@'.iWkpW@KƓ^0ԽJ.rZޘ>Zm32vI7NLUaJX [CFt~ r5(kvW hS۟[{`l ylfYbl.M@s׽s/?[lz:s:I (ЇZ+:=ر+B@fQi8A2 IwŞr_U@;r#Ly=ظيR5nƛl$t|*`W 54E"L|܉Ibl_Shd's]б;nǚ>B9|A*iaʀl f'=xsq2n O␧3a۬R-Yo0sG$,7Ru3@naOi 3(g'_X6[$\ՓaLaA{uިZj k;cԣ;Ÿ*y(>MmQ02~UQ&̙8ytDL"o*.je=SͶOşl+`NMN3b;'ۆiH1k 2dsP>̭sJڑNymȆ=>qR ٕ슎u0q>.6bu~}bRt~͞d3 -U4*&e'e' \5?`(\W$]z/Er"q(<_ic#1 y_-?E.~ lh6m}^-MgsOl5rObl]q́dX `!VoDf.{ݜn/sPl1ՎFU_ K v^0Ol ,H}rbIէdE!Mu.IP>T%fuXt,3ְpE.trnj7.آHq)>Z!(Fy)D8Z$FUN?%4Ҥb9͝UFyz-,ɨ!M T:邕 ҨX4LAxn?@oD   ܢ&V?SFRxHeh`tk.)%l9o: >볺R:OX~ba>rk ykƞQ i"GdqY$gu0z1`gu| T4ӈmo:=f3 3' v-珲i]=V:s>E P2#VyA} g&C]V2L?$SǔIxAr~NnW>LϪJ`Ĵĉ6[@vLh6EW^zɢ_ vfr5vkMDROeq;"׍ -Fɹ[0 ^ B&l[,f@ .#˥AjXkBWZò)alϜ?.,li5eLR{UHh>Y;Rv(Y<1UPblLK)G8dw?=KDgCNoVMƆbh"^I#/wm( c("dV sF%9iז hJO>gTJDsuo<ŞHvBvL=wAʬOٱ$e8֔dco!{Hgzwf~5f?1Y_E 1&AkOqoOdH d /6#Pq|Z*&^=ohґ|<)۟pdϹ b h0{MyU<Y}9پ, hcF=4!V (f'23 儂2dK 89p]wa#Fi|(f7BL AX&SD/`2 xK,+$j.9}L5bCt{u}$k'@=sA^_"ёS1M6 9>WeZ\_7[ΰq|zFsԍ[MNΑIC)X+raDCt3`JM6u?$:E4xtq:E*9!86q)@&^Pk!N]d:`cA<14uФQ[JTC[/M8UAp;iD8?$ Vg7Vڡ-W(HImխG Nk!#n1y Y㋀A7*O|ӂz$`j6?H:rk[I[{f;E;T/U%U?a[j\?hnXW>CmD7[4 /rM)4QdPni3JXP+Yȯ ס.𲋌>ҍzfI|x=#rae͍<4P3Ĵބ=1A4qs:{bRzCCGT ~اR`u,zhAT1V#Z{z̦DV|\`4-3N"l\koU&k=^,uݮ'.qV* R\oam{^ɸf&NZT5ڠkpl:mCuK%݀WLn3OsYc[q'e>V88-"ZQ YЁƺG'F6?GC ڑLfyHcS4YQ-W3zigYG,NaWO 5ã>E· Mb[ ~g& &n)pheI $~)8ȶS!/:w(LnW7}Z3'ς;`3pQ}o\sLm![(I݋xםp-XQAFdA|rf2aMm2bXG9_F/ت|} vqϰ.>y&q>o*r@8U9 v;NUNHkoe'mp h#p"!jD~;b 1O}o >,lG?Z6j"(T) SvDUߟ@,,/JA*CUP`H)4gX\Ա)-7ğxjKYpAbDsȠp}2~L2mʌg{[4(aٹIHn*h+^;+f_M .{ċ21P]iiF:T: oj&ziW%~Rn5aZxevD[H+ p X2ւ_nLk̼UXTb rIl,8h->$X[/e!{¸y1T9Gow=aI1I8?7L-bnn13xlNUcd]'Yj33Crg9hjZ=@+k+m0ZCk΄bN0O_KfPnAlݖjoAaFV>GҜ`,c8 9g! 9MQ(j>j˾$=ioae` PFIRjTJ!<ēF{ѨedknULt.6:fYn;&h'¦k˸aJԉ3KzO ,sn ~|9S6XIe3Ci.`G !hrZ5 XǾA2i{'SX'S=e;#krtLP$..6p;ou WШl)x4KsUhyAjoBXgPb>`/oF|hV!P-R;AugT[ʯngĉgRNn !I VDz9!β!jbbA.BN1&ۼ}*-> -@G((hewKhfwڮuFoI[rx-(q&^+ltFJ}Ql$C Ơ?9h%b#_%Lr\[f+Bmb 6=`"<3wxr0JXqʻ:q^ꈁc%.M) E.JO[m({ƙBCr˞tܓk?͓3 z|m[:2_jt'\K*\M5Jݸ*@WOrE^8竩TQ?l@A -_}w@N>9OdyR>pC[( bi$zI5bxKV HIzHr♔^^R`7XM%G c6ebw%-r_z CN+xH\M/ 6~Y+6%,kW ­{ynuE"fI 31]; `нJuy{%&!E_HMF6W=:jF:M9coJ6%SXIU: G'0)ye}CՉTxފR}i$-ƃ2a`i*B?eUq]b0U]PUK/bZ~4b|\_&9%#Ljt$~ 4QdM(f_0[CZ򌯡koe{ &0'==$E#3rS~5 h2-{K^VXau?IN3ʥ.w18NPO,q)wp? ?c'P" -L2I 7&pk:c_{TW Xء]U/9`9sl[ϕp> OʄbE.P%VNUZDU#7iƔeB&# =i!W̭>ވ9!@G\=Q\78uTP`To,NT|Uxm'Je]&DΕj.icO.1Ŭo6% j,2sC( i*:e[ mV?ΰJ6 /NbxHO4 wVL Dbe -vb 4!s/C̏4Rey*̲;Xd "ZCv+tcd "Zzg lg~ڪ>lͯ> wӝ󑱆=~zK¿U :F-#Õ9D@d"yIxY%h,0?f{%y;¡tkƩYoNq_?d,-M>_3->2h'+R z'45o0`b``3^2t-g[sl& B$$6Nt!Z}Y+3H­C0$ i@CE _hem2{bʳ,42 TDJe>MDD-r.Hw'y}o$U4`\" 77qhWL6cU.g[ X=\50N细 [ MhơPb/lYVeQ j, &4JD)Vl,@gaqFmGZy#=bn௭{5yj<}`Rn<h:^xd`>^:Xt񬙤9Ɇ[ f7ٽ`&W}!fie} 76\Eܬڌ W;_1%R9׎oxXFiG܌Ev,_m"#:zv2]7JȈZoO9Aϋ'UqK3ee'|4 vj2\}IaROO2bçͩ(ש(D`M )C|m{[.5jaJF*UZ8E=If5Mv]d(= TstʩAW/ZJ(fߝn4&gJx>5U& Ґ\+ܫWs2I^@BDgeΏL[e$iOw؈po20>OG{75$r^Kq@u^X=O!FZGqe2DtpݜH\yS1_% '8#z!=@2?PH9!GJ({bʲx;bG/@Wf, 76+d7o%JT'5nn?b@=9hfU[>_N[N ë$LvsftFGYl<Dz2ư(1%(w7tqWPH(! Ī Tz+ [rVqGhe/PG1><*D` 1/S;482-o+[Y-giۼXݣ<_'VVɁ Lx A!F8+ myFj;IgWI r[IcsY&)P{i"oh>:dRŕfYnhe"2״[iP5dHX/`mC RH/}MSV37.`z"߫VndW'_1*y+mDuA=:6!e>dRD;;u\]jsẗq2)H{"$Q(+Em:3hFJ޳[aRUPS'A`e|8-sƎC]ҫܱN 0'_xJ÷`ۡDpnhkpgM@a4ٟ|{"L LNG[8J -Vzo._'ΖĎ;:trbS!Tv\A B!XHDkV58&v嶓ʼn}0||ܣB0F-l_EZ. iZ22JP^t>Nw)?2:Yb4@j?RlW?t16/UfQvq4hZmԯE\ D4?onZԴDwSh^cxP[ׇ<σ+BRƻCsdh:QMER_g0dB1i hK+ ]1 ֏zj `ЍweG|p5:ŅIV &C#¯fXՇ>X PBAԢF}bN=< 'M$)Ts<:Ί/H>iGZ<-q5E @]Ĺ Yʘ@wOX{~z+nN5żwxօC >^!Fށrqjde)mZeomTg~YCp!FKpcbR/(Ģ ,eg-_ހahة!V;m?`/Ȓ؂A2$)(v:7톼 I&9,|U' ³p͵ʢy90I~O$2D!ީ:/ŷA _d[?ERlmBmCxt~moU>B7 L}ɀd_P^&TRtajfnCAkẄV;tBH?eDL.S۶,0á$=Q^8c+GR]Om OIDi;2YsedyG3{>ݻ*K2\SU"^Mw`j[0% }K1.6_=ґKLHxy_(IevZn;6 Vkr|pkNwR$j`&e*u2HGJ2d)Jd:W  $:أObe`$۫1Us=WoAA b35E^B+ 'k6צƓ\wTrd4i) њX6;'=fN%;B;Rۺ89Xe`zvaZ/ GkD s0^«p%Nwe|!Jrɫ)6od|ÅIJ ae/)& pQp ϙml퍨Q"b Mqe60z҂nrRkVw_^t8 B%6M(ÁU>pG NCM4Bykh6aK w/T4w̲>5)9n",Ed kҨb߀Lo-[Bu7e!>=ڙzw5 6O/wXAƲ=w :tUSؼ-`B;z opQrA@ָ6V4xm`γ&|SxjubmFҾ NO 4ncS 57o1//yTKCu/9$h7-NԣmJ{LhRV> mX!9ll&,PJ~ֹ1^9$G/X#e\āU3]Vve+k\`Uh7{WoyEpd HYV=dNL6q\vyuSj*.:7?&lU5y$.-_GwC(pαyU%b|G9/ h<[ +b \9_m߄ΰZ'13֝ XJu.*Dknfp9&m{!y^ۄ\jH t݈xd2e볔g7,)Z0D&] 燡Gw$ n0wRbU۩XٛԪLb _2yh_5|(qyr'oBoDĝO'gI—n5cT},*{V4eM`Vr#_?d79-|Xf7{WjDfkU<SJ1GWV G3Kio޶ 枱/:ߋ.Anyj|:X"mۈ FM}$жJR!)!RK?~_2$Ugv`bTшʼnFZv#'oBSG-MG< O\uLM)k|zUv½v4sC7C'9``w"K[WaxEv#1@ڲmc~*d4WqPG$BTnIs&[C4HX{΋}E`s qx{V͟*!8k9QLgQ5Bz32caImwN9״-- eڡ =DrȍL!~qe;cCJ(^;%un^xYl=Y,%ȢTc#>$@kd~]uu0 ?C !#>GTUmhpw8Zz^fvHvPe/|RdVs54w>ic_Ӛ4I>_W ڝ) 䟪Y$#Ϫ4{!#SC̿+RbԷq̾4Uy~`Qxivkc`Chg)zCF)@#T10^>6V,cb:YKHχ!cvDcOvIWS~@`gIA'x?o3YJ2K!0dSC_"ma f; 0ڰ֖EG+ט[AdZ{܂Ԙwd+kk3\a3z)*`0zuWo{$oBc* ЄL3]#D1,'$6 @e v)6G!݆yA2+{:QހstGvA^|O3HHp3Y}7CV::2\VVN/Aq՛4dp߷(?DpX~LOh#;sjM! J _K-FvxoglƱ>' e(NzSܗzϜ{`+&)y!^Xds@Zʴ7Ja0q1 =yiQ~\㨟KwhM"7;ip$*vЉQ+? Dv42r}~;XX^p6vښ2^s ToȲJwy(J_a qP6Ԓ#aH".<]VҼ8&CqSJ}khg43HCL1# < C82by(uۙe; 0݂ @>&tb{T繚V aQʢxNFysuKO͖zpT8jXyX^\r=}zі1wI~ZOz1(?<*=]{d<~hDm T=+6y_K e8F־[諸Bd%qRV;^Zȝ"Ǔ&~~ <Mkp+TNcZ$MtV6-'gD%CQ  }` sOYzBՎ 6Z=Iu/V=֠GoC"ŨP~Xrߍhh*I*V>gZ=7i-sqCKoSӂXh5Fᱠ\! v$~)Բ< ՜`~*}{^Uej&|f :3jqaT w8~Oʣk(dM+:a";p~f!-!ߟZ_u!_;Q??Fmg*H }b. R.6;`71)6:9WMjo¾HR3XCzbh3{.웈·5w {'N3e,Kz͸$ʰ@h4P6@ƗЪX/K>X?8wNo ~+&C@\ׄto駂mHw2}XN:|N]β)Ko`-Ћ|6r'[[ޝ=f7iP께!8 0 pb9#B ;!gч~PC—T]in-wI-`c ~I˔쨡7߃`ߙV2ƠoAwf/L}ėKW벯]Y;yvb%tJxSa52$԰j:}g@n*^ń'(<{"ێ !`l8F$(ՋK{ǧsEs.mTx=˝q8n>V㓽}͖:ldGCNWMi#F ̻5*+)[+2Q}g4v F;FxAhqE*hS(8%BfAlbM5v];O K Ih|hRPu2&k5=".v%տB 7wp5)Z!ɿУ8)~;TsͷwbR2udk( OmaeHn76t;:]ob )sA`}'h?Tho8>h'۔kܯ:|Zp! 58G9vkJ3fxt?q54E[F FW_i{Ibi{<- EU\WoO ?/ׯ2&;X.x{䴈V\65ZS2~Y {I>,xx炝M#hAѸqDUQJcʠ2O瓞ʁLvVIU[Þ&Py&bv>h{WיmsN]O} oEej$a՘r@vY;DzI+] {)HS4 ;"xCnE\{-2ܢ&AH&Eꙶ0?~Po#{FJ⌍(żPzB%ڱ%gdntY鲹 43h_%:#H+{r56[z}*F>J3ur %/ƛWšg(u,<؀DC}☔H/0,$'\ LJ}Lɯn&C*,Ps@?*nn2y=-k¿S@zò#kp\m6v%{qJZOt6~i^G-TZWUx B&!ZJol4܌:t2Tn6x%2uLv.:ҟ_GԸ2 93F ][? w3¶qAPot:- 'FgU%}-0`,GVoGjc#q`^uyvé5j]PJH߱dœkPU]AMB'ڃbH6d~_ȝ3ma6oV6f4YSim)lb}}.]Q nYB|%^ǘ'nU䎣̒VSJ+%YD#Fm'e g!Ϭ [@+=`3/r wS&c& 'b7vmڋo R][{#1z7Z9R]#»n(Ɵtd em`#Tno'Vq3)1e)I> ?"anȈuMn2쫲gBU{!GO֝mE@aSi (\)k 5 Hx_,4ˡi7v>i<;xM[9?5O/bӃ'Yu_OD7Vo|nDhT;F޳ӟ3Q&|?˘ Fp=e/xWrYZoW!'5$y&wW[併'j)Mn3s&Ēb:Ϯ3I_3N_g}=.%bePz՜' ^ؔqg}w\]~nM͋ZZ]i^Nb0j:8"0ͲN0rC4=jk.a̓zY WZ?kU5/.="ifRhSPBx;/y2TF\r ҾYuk&enr4FzA::up }J K4͞sag18aFRg$@=r1HwOKe3s2W]s6ܴ$,ߕX EmX)Tdl _eHG h+ '7 _3(%xz>@4ϲ~q叒] poV(C0u2Ѕw,#5T\HZS[ T<Ҳ"owE7'噵H1)IPFs<>hmHc۟1#B[:~>'3-n9>0ĩtP.hyJMVGq@kGYuR, znnO^xOt=VLRf ^C qREA*tIm31Qƛ &+à,vkXtI/RN!!UW 19f!=y+@! U4- Ck7 Udwwp~Wܩ6)/MEs*aJ:TwF մ,>Pga0 mƐm=CoUey3q';60Dwu/A/30>I-B95ZxltqC: _U_J+k* oTcgY 7k2gR~ٵ*yBhFq"xb3\DdvMC JOs_5z6sY0hQZ0@:91zb #N@5BϞqXm\ub3A@->@򞉩:D阔`n`u%M^u@3荜$vDZ:)`&7d-cXOD+#Ȅ $7(Y9OGnVp6#-&<(Yoq6q_=nGϱp44]m{mǴvӋ{S9bE9s;+W+>-m*?a%8L0',s\Y=HdxKa 1mHTv!u^:n0E+.׺&TAtw)woְ:Tq2m!ovd|xh.[qEj  :͐BDHzB=]9mu3% , [ԟydlSPl!݈\1*ehW-ĉa,% X,%#Ʀ 懚 hy^'@NLtTb)pMn'f4Jd~mG5S W)P3K:<1ƲR*u]M ߥnyQmOފO [{(Xv&N冼m#9am dKe%P "g۸5sv(%l 3ž1/>u~܃FNJ*|NAc8*| YY{y6Q?zH*UpSwl !KQR>nCSBbtjMmViΜoC'=<2J!+~G92مY1Ti8lAPĴh9C% LjکU&a$=y3ƸC!oQu/!UŪg<|!G?%$@FQRܰ*T[@I08tW?i/HoAzX&<]AqLn-@=sy$=9rc ;k: dGp.f[4i j?u'Tү8$+`ՙ{qxWubAMN8#ڸ9VXQ^'L &옚=V8;N36hMlLR#ѥM[mwaO7BeV KAK}X~u,.z7\TZԙ$(>nt ?[n֊E&ZR^ܮ}vS/ybz=kE&gNd~x2y`6!eJ/{ hx{{ˮގR&ىPMYeHr$g",s>1#wd&QW$nN(c hxHa7v]a) 4'pXGk2 ʸOB R{^ V+]Jf'tm oŞ JF/F錇x;3WuӗW4@48uvF/2lLhAZ]1\^-C^mbC?9yRlE4Ȇ V-U%v˰lh-"\^fz5zL&貦p*~X.W#( ,ux;]cWxsZBvSU]]uB\R< qCѠ-OT$<̛C|dГP,s-K\U`jMR\ɲG {܉?ilM=&U7_Uo607dUIrl zdTl[vܳt0TO(;JljpBvGT҇:j[TPV^-A.uD(56S7Jn@*NtHg΁s۰2{ģإkI~Ҙ87##n|.naTl0ܵ_ӽ+xށ.,)ۯz\ 8|(;󃭾fkq,r_s}#d>Dž݂6 TGCٱ)MM;0E3 thM86^prXWOHK/H*zBtH=xǣi5I=–j=0@'ɔLlvG'Ѭl*m5= Em#Dxg G vGֿԕ&VjD]yi>(&Sog>v6'.3Gԓ9>bChLEv)zӭ'p-bldm_#~أWvdɣQǻ|9%(?>-mIKL%aoKF4[ϪAZMpo;+n93f%FN@N98,>Y/t㷻O4=(B o!s}xVel:cW ,4ۃ  o_/Fnan.צW kvڗ1vPht" xF-w*Į8FꔸaLpks"uPeݞ^{ :`APx.)-.KcYDc .~wB~.﵏Fp1ܠUOʪ HS=Rl7t!w'%M_aLw5P*ms苍+Tw Ug{cFm2_CvǼ{v%)ݾ># 9{KُiIȱWU_xɤ-B]4HlzYIgMȚdB6p3Lz kLW-u=xwB~Z6TPHhs ZM WpxZɮYAL}ԏpUuG6]L1b%`bV#;s 5OK:\2#Ao(r'Y A-{1T2@=1޳2A E,ffY4D1r".=ey2@_cz[~h&7]bvYM[)ṃ/;/"F԰1\?]Z b`C:f0K\ P "K3*tG+dgJ5]ze;kKT0"#c ,A@ݼ0l{jw$C Db#%errL$ԏaL4޷xVgUK6pM .}QCt/̥ɩEbxnڨysm! s:RfNF:<)| Oҿ׺^jO=L4j6q.oNk'zK/$iLJyͶj #"EQ;0\NXmoD/ gIy#%U(61/HQ~ ؉zC4ôWPM}/ǞXsxzΏckar#bI׳(Sy%'y <"_+nqvcL*PHƪsZK/tDC.7 F):%a%A2VٰfJRM3]?J``{&dם]#b ߎ/NvW.ΠyHI8އ)y&pPdsmUN:d[$gVaKr,Z:XzM,oU2'*I3^\9՟]6-+1j1+DC&$kjΏӋ\rfڥ>AYYfF F,׎b̻7]2&@\1v5]2wl8Lz"t# Z?3] +}B E;3ks"W"",V!e&%pìﺎLmy:1ƾΫh)Z'JkCYpʾҞ͌yjwH,3{IM"OOA+e?d:"aT4'_@jۭ>9_,e1z{Hxi^G+$DeaG-;VHqZƫ-\m= 1F\TX#ŕF:4{s^;aS7jcEǀ |~ibץ6p!?˫Vbi*~}7LkTPǎ__g`LˣܿK>qHʜ9C koI*EazyvRxݙPKyEɘn#Аżzh'RƵn"U)Xv طoVJR\! w[}@|>I۩|l#(:y5FO$}!]˾V}'ZE;;H ^X6d㾺mBāJxqZX6y% @&T =$ }7PU'̱9Y#<2+ȋ#(Jwjک#EnC BP K BcaQ(buBR tS#:Zǝ$ QO@z7O`|>PСW_i`ڱogm%v.gEo`er@/ұ9Q(&0SfZbV;4`GkTo 6vHh5>1-g({=/}dCݩ2j K<[ `eJ7N Y(9`*g- *[A]T`삸"a&U*i T[k8 qwءZ13iڒI%!).\.B}?|hM|ڒkE (1C2}EȉA+da@e.i[8J=YBtb𭐟ꛭ:xk^Oj5!{E8Z/pGeݱ74AI32A! J "s\.ۋ_3#B6#f!D89q6/|m`*D^W6m[ёx: 1v '(*u? PL3\5f yƿһyZ0D*eԴ8Vz̵JU wIymub#&{cI}捿Bѩ&1P w"4- o#'r=Fcmn,&MO%C!!]j 5cOô R.AxڮȐWh{ ιg{eK6 KJa+Cc!Q߁0)tHkGhXb`@c'D59!m؈ET 46J0beRbC{V>˒닋M VAwؤ3Uq{^k&q4eStgp"΀3.cU=_#kFSyX,tGuڠ."x8oϓYh/ ӫ'[>kD6#{P B9jrK[qO, ivQJ q-X^2'U=ۙik ЧP+A>,^/b30ƞh~U֎qH. .q8Cԫ7x{X1+kkPZf_/ ms(B'=6)h=T+h$qR޷2!M;m0jlɁ~6\p@1?):^jc)rͨXC8>+j?Qtκ`J^@wƿ@^ q8tCQu٢$Rk=n˺A qK3ө6pF%R.~H4%[H8 *UmD2xT*,pm \2\&IB[|5309<{MG~&;3+Y>4] #xC'DҒ,= Q$V8ïlL,2+SྞJtw#`p3em8iAn6+K4W)؁( ߶Ik9k~ 3"Ʌ*_$gemz@F$-N?"'do!7o>BF+^Ļ72ر]5#Kh6 ~[>֪k19) &,4B/Ta= DIZJl˰t}쐭pY88rC(9T7%zT"DDʛ.׏F!E*VcMYĢN(a^@b3w?z B ҂`De9bDRV'P,i}BeH-#Vsy+(goY+dq>~$ 1dL: xAwU 8?JAq1vihJsXήr_2+Is,WfEU+ǍǐF>;7S,q.p>ZXu' K1Z"_*2;Hg9Y VlD(20%k,d\w#YmqwwD՞UcA)ys}TAFV"uxL`|>I=>y@`<1)0?ջVSz3@ZԁJ( רt?9(LH-. Cvnҳ,/ UX2hl9 ]\x'˘{)a?BOwmp+]wDm뻗fhv{uwpa86G6?Ds<&l t`0/5˩+&y5Yؒw"ӽ:pzbU)G>&(? й2nqAB 5Ȃٯ_4MsmٯSuc{|Ĵib+pA@VWR! M^fwb.E  5v=d֊æXBy7VR̍^[Pz3ھKb`%B֡e}uߠ1U5A~nT` /Vw /.Ow9/*BNZ$vKh- =RwSU_$B!Tf9+|WNCQXj~JwjbVC Y%CV]ޛ$^+Pa h)O`čBf*'C >Z@u E$&ߊ1-ߖUҷMRKU%Y9-Ɛ Ue,Wߍyvq!/ >1+Pd3}ٜ{30_2!()y9N?Z'}-h1\Sgg?ypJ.京'#+8 SL\ot|j=Z5z9 #V{03ſ?B`Φ;Ң7YCm2+*wq>[1+:wѐ LM*R*C<(ˢ&#XU1K30>*QHw4z$"f2#es]d#5q)k(iׁxe"̾J_L|cޔӬs0;-adB5NJvO{i"j>[Աż+P8ja%B< gxox }w~-Xn^>:(8}x!5T:xpAn/9kiC"1k<VFEX9;doٶ]zV3<QZ$$)Xt5eE[Y* P|tVPVa bLc D`: .@D]ftSO2w4,_ăVIS&}LIJE.sJZgrwS*Yޠ$&o#3[L0a_W]."O&+[Y#t"(RLAa_vژ͉6ԯ)@GF=w}['shq,q[j&y΅,(P"k nb ՊjЋ'ipH$&b"IqP͊8ΈG+*v_y@/ S@JO=؈c~DB5 ?kRaMZ+n7hm4I#ZlIP 7`2 sCu(W0w3:EDܽgBn4~NG>/~f טRyŨW[[6f dFϼ?h"Lhɸc Y:p_KXD )I"VJRyZCG1Fn+ :R^|v3;Ͼ>Jf@24]iU(zAv}d;~P,99.Y9VIzDX`&0{DySE&\ 6d,v@W9Il7՛ hslpVpQaU7: U[t R> -o(L f<^Z fCBSmC,a.PY c?e'ӨP-}m%!a8.Q"n:*w$r\L,*p y] V*ezN}TJx.j$C+J:߭Dn9#7֫Eڨ1q3ǁGA'iVo5.n$k@:\jagrV@(r0&eū_~=G(&Ŧ`yFb6ww5ǒ.nZQ׾kȹAAzE[XW;5h֑ +ȃ#K YtkUT]ȦXksc16,ITY>)ReUa,#+5OJklo(H]64ѣ3 1ҤinﵢOF}wBnMPgڜO_S`EWr߰,Kޤ_iȹAfPѡj iJO5a4D<3/] /Cf>Sc r ? ] 4;p3q}FE,{I%ݕ JOӅTǛW=F:>{MiF^ꬦ4y$0=eoo與EDX {dE:.RF~Iֶ^.uIsMmb{tW /$uq\U|jmDbkͶInE+vyJ|*XOhhlQrXD )ZQ׾hL$ , Y$FPѡK Xf/hO/[btu*uh˄̖%נJI87gۄmg&X@J֏Hwe 8٪טsT}ٚhaTG{_lF0a?$j6d&ܠtjB YsĐkC!ꋇ r1*id.J ܆[gIY(W^v҄Z䦉1hh4H-4(Ҡ9;E-DjD-hUu;^u,SqciG^6";۪.JHĤ#DsM*m5S=-*>g[C N[qg~e?21$Mo㤽<)a(A $.rV?q%Nhהh'2OZp b\_a73S˖Kuͧ]H)奝ap=YɅ m>ٺ;RDJn (EOYu3/-CxQL*h rkڿXMK>>G%q(R3c55tjdii ! {d '0֓ce[T48o%Xw`Q]^Nԛ$YDhm+O ay'D_X,a4L/_qgn6(z'#2C,>ȻVOㅥpo V&C_xLN"UB'y2\LP㿸cq/&!N9 {&ǛӍ,~TO-8B͉PHVC6v{T71}5It.h.K Mxl6@(h"9i\_6U(^\wFB<&qrj$C)<Մ@ҖS_|CP ?So^x?`H>L{Y &DiH߱3c2M.{On6.Gߩl, F#Ooa2/.j$C+J:߭}dU*9E;>CmaIw7O௘$Abu!}*@)'Uī;-A/pV9tkdޠ/A)GH*j=Gk^L H{- f@؇V͜j uhwy_MF#\6>k$~7vxj&ohj p@qctW>lv DL1{QlJHɫT6 yk׻цȥw i/NɁ4\> .1Sz_Ipe wVaM^i׾kȹAE#VIŔJWͷvg -r'Z}z'};H ky*rO9_\fˀmE@~Y%J44ͣr¶'Ms3HFQc̳? ) XƋ#R*>^ ‡{tpp$>ƕ(ʘCsG+ ^LrkP@08tNnmFu2,ulk|+sC㚂ƗҶY.e[+S*y) Q+@cwt;3N=Y/d$;H. Sn@>܇75 (~F\5/)Qo/D/BPgj7בr?a0듙vm'9Jbx!M s lVYxPf\ S4'D[d@nޜwXūRD2F]aN7'MY+Kb,yvŅ>hL+@?ii[({6IbAp71mِ` }+l J=A;vX7kOm4NLW'DYN}KKnw P$:^]@3LDǽl>"## NvLB/3*$EL&'] p1c0%I8àNi J=Rע&PL$e)IIBnQrXD )I"VJ>jʭl~. M%JSڿ|@)FҀiSKu1h#Qty cik:kRd$Nݡ7UA q,c.tC7D4h`̀@gDh9jsXjsZREIat@ՋN&~ͻ*{%I"STXlt=g xIRq 7Lߘѵ#<ƝnhXbZ(Ƒ]];GBeǹ]ed>se+E}2ơDBt{n ɥ#>N_*EG<#$7@o?,D[Q*Z@IO8+(cG $Rb~ꆧ]w$K&~L|/(nbb Qv_L]MmBzgLåNlw,g,(DKMYy 1 ^/a9qvrR&@#ܔէ[S/yxjb  ;:)'#dT'ekUxtmt B +Mstғ^(NlWݭX WʠEEa՘Z-+!^f>f=6* rM_Q o 7y+L﹇aeQK_dD]X y0T(ѿ:Qvr+Fdݑ w)ci Ez;ZFwuP`aCi?WiW9+mеWA-!ڠpk\[+O_;쯁nշd9!:W !\N?wlxW;E`5r2` 2P پ>] {WOiv>%O`,Wㆁ5t$uZߣxC-%~Y Ʃyw%nGLe.0&C4,Mh, đn'<5+Y3}έ^C+K4Jos3%`[>2sڡyY/(@5a4LtEbL1:ogL3(DaCn챩`0bW9,7.$aœC슭zzL@$.0 Qy hbj f@kc嶬9SʍHz_3N|=;}50k PuҞôW/~ds:E$uU|U(~+"7d2)le:KI:  /*0 U) v0>I 1 "oSh۠Mc '*źo v@7ܘ_Cy pZlTMm<$ǩ$R`.m._2!{ OuHHmBt3H&MգmCˡ\gCSj2 Bȷ+<5CgAZAԤ5^WdEWuBlFb! վv\',1A; :8jC>hŔJV>E *(rJ~i#Ϳ.[YӣXp'3r28} r:6gL/ҧnC3sD_F a[l{H3(~Lo9;(hޣ|" ; t NoB.=K&4'\fḠ0OR;榡%49*嫁={~ mQG;k %ꋔj iJO5a4'Nt>Aꨍ)~WSXD] VsX!ulB`S:eo$ӪN4r8yz F(}\(Ivډ$*隔\|9ٔS[+#cӎHQfC8{* ngpt[a$I/+Y" 9NDJ§!٣ɦ \ȯ`Z5ͮwˇOÂZڡ=4&*=zC5@c+@O!xcjkHV/Q"J 65F d_>ENEu@8 /mл!H?@q?pkp ]mu,nbF\uDGݡjhZ\K˴=aU% % zى )==d AJvInFwg9יQ sĺ?760LEKdU{'ەQPV=벎~e3skS7 Ǜo(|8޽ P͘wx\Vx7`=wP7mQcT;K%(t9Jj[C˔!uytmxB[/uTAʰ8lݧPz(!\vW4tC21)/(c^8'Jt-otaso慎&0($XDe[2 _ϴ~x[F ;%]C147&L@TNdnc__yIRJ&6X7 %ygZ|}[/ a BDXUIoh%ICF?MclDe8W`hD$Vug啠 5.͢ eI\AFIY[l\5*))e|CtM@p9,b ~E"yI|ga3*9>x Ll?a?r9٥*ౢG0S bFHPd)K&cHȾziV*kC{5m6"'<vQ7ft+X d*;$ZΘbht,_bd#5Bɔ 'cXI۳$YsQ4he jzFBM?ԓ%%֧`(9; wHOCݨGoOls漃Ev㻾}62*JyJ%A*og0zX&-t=mr+FYD2JMEM) M Ty.ltN{{?lwCSaƍ|maSbgR*O{.H&,oŚæ(7z[ >hX1+ !(ΩŀtO<4?M٪'QVDO\ydRfJ~'C%d1)F]$0SvaAů+EvLcpHb&c؉"j*J4/9!$XUIohDBӔt>P&3]+a2;ƺ)H.1T2 =-6joxcoйQ4i?"fiKz;ZG3$.ȪO+X&-.{eh-:E4V)w@(/8;˃){a8wTsE8SK;En4e% Ŗ(54#W(@xcGI!+v2KrbO2Q fNyl\SoyqW%,p B!}.I7P4䣲4Z%Ӂ  W^WHmuI>$v< \D= #¯2ҪW&J!4/1 yCǺpzaB/.Hπyҟѭ'LT@NM ` ^<ca !Bi0meamc^,DYyHmj;IoRՖVq-C8}*"Ɍk@z2Dp[CgF 4X*Vd ") أum18gWI.0=ej6\Ovv'ׯw5s,G_ )r5)azPzA;w' R׏xu>n>7+kHml>~%\:Ijxc;?/~!ϵwXXƉ?l.x)ѯŹͻ.ulT OčU7L Iչg1CbJFd vXUÕ0ˈܭ[gi-0w2oMC\CR>T.3bώ]àcn3?h\$gڠnz3,*pfȴ+w~}g4ԝ\,S/4,}E_d;eȹAd%ꋔ#?q5׍tJxVJ 1(#H);Gf`|VmUFXy̯΄bz|Z̫&|*R:(\"?ba ˖_e\fTTLGdMVӲ#PqdNeOYk7㜀;aـjLjgĶq>&g{˩[A@v38p%\賈am1*Ӈ~$JmY$h}1Q-*]ˉʊzˬ8=H+K3Kt1Wˑz D(S\0}8}Er2%;l@C3G_UM1߹.~ 32:5z-|p*p͸[zmuȫ6[Qv wA:AQt#M }Ӵq@U 2HWzbBq0+>rځҗ #яO/; Eq"l]_h]<jEtVI,}?'u1 㽰׳;IqEmtr6~ĕR ꊡh{dT/f$,&A+ Q5Zw_Ahp nKDZp-EMt}˗n5a (d>E )Q>hYr!x֒Cؘ>u;m񒰪hyEeɸ0SSД͖[7eJGbxv,?!}mF@n@g\w"ȽAk'dw\&EMe)Lg3Nk*=+H5}VV+.(tApPbZ4hH`ނ`#.mxoqj_J`5p ْDaA^w'l&%TTmP]1[SVh`jJۺѣ_C3FR%@2KP2dY0-$lϗRE-GدI}(±|_ʰwꣿپ/߶?<ƞ&gD9T^ѝ:*GKeHʙg-.h![L@Q16#2 {{uEO5a4'Cʑ5(o;.\%1 NK`Zh11Qe?+D܊<`'u$cMj(` {LB?lq=Qknz:vt lS51i6:Rr&KɺBڨ w 9ca׊2VaYS#>gG1 B\%h !{PE0ή5㏤zI.-a"=Xf/NjU:_%q"p, S ʣ-ivKA箻vgb~)<Z Ʃa v5t $:Ӳ_}%weCb:p/Ys;p ݭtڸ{ԓ3:]%KrgzT} M  KI jrg75 (~F\)K9ŔU`uYϔ33ZJl6FDG~쎨RLN%q =ys"ȸ{,{;@3?5)%N(鵠 sj][uNu  7G36,++{ C{uE#[X]}/J㤾0XDJw7&ks֯24 |b?IO%^7,d tE +e9&d2ȸDR)VT;Z*;qFV{$",JmYN`FrIԇ8ݭ2 @Vnz.ێzñGZ!n!Xoʃ9zO%rp' 3&RveCdMXD (dtJuwj1%[TkŤf?AZaը3b ?)hQD}5OB|]SfMGgi6-1#A'y# Jp.-x/,{0ap5{FB㢋 SEZ^Q, T ryl}&&^m .cϖ{p]Ȥ :^]%C}Q% dtJ+9ȯDvݿI*1`'%M6cKT_tb>$3?U&#[ImO1*q7nD%g-M6:zACd"݂w, +l*|hui_l/jAڒ6 m4 KswQz۫Ugbj_'LOԉ-'Du"I3JIuߨƅLʔ?336bzUr (Tkb:'fC`6R̦ Eb\2:^]:n6EJV {0^mQ`E j(wC9Qdɂ]2bB=5,'k~aJC\NX2@ڪhuhO>ϕBgƢZƪMj1aBpliTX)g1#W('BК`DL+ md '+JbIŚh;4QŌsAQj(wx)NYCRKSflRW eB{'BV Xo*kiYI m1.q(D ,b)]7Ak.h_$|^/|lX\FvٵC Jt q?dy-U?s$̎]$n53﷐vX"df#lb;' mcR))iF@hK?k;jjn/VTJK-N%![\6Df2I߄ܽ5KzG՛P>f8nxsHa:Ȫ!Z…y=KWߓҼi{d6_CP85LZXنq֙U&qJ@Wֈ]7 A{ UNJ^= آ+Os#=** bبy۽d,GC^N%tЛb.wk/qŢ'k0yF6'%]4%YOTjBK %+,TuS>Qye"&SRs:Nwޮ)qDE5\2 VS 5g# 2y3v]9a1 =#wӀ;(J^ F蔆z%QZz6n)@yO=BGx\WF \.=Δye%ͦnZ\E#XsVG[iؠ,2;GLb8'5A//9xGAd"8"yO=yigf:L_Ϫx)o> ࠭?㿾sؽ ,ĕ`ty`k׸[ԉƸ,ٌݹIRd@n6b؟i))\pr,;5jnb>1}#%{ܸs`mɣcRk*Un15.dWG.9]@LK6?^ r6>NŮ b݌~o  HwZc#BA_Op X_#F+-GKCVNpv S"!yOp a1ۯ8M,a'G4 [ʜgLQH֌Nq*yʵ5&p:;] v3bPU3 c/+~Y6C>E˕[%A9vh. "uRwZ{pȐLm2UkH <]/IN co=BG|-<1Fyo TӃha|x:o(q0KcbAZĹMnX["O_PClHV8dPd'ȡkӪNC9fYJ{3aځ#퇏 ½ =t`蜋 :ʳqG޹GWtN1 z=ƲP M)Xv99=&G<> G\ "O}f2 ]Drd'DGPl^adD]؂"Wl;{D,Pڻd8"˧ۘP-!8 {[qniQSfo\obxϔsS 8j=ǻz #R`ϐ4[SpioZ74t=`S]'`X0~4gz:W,s *kcЍubҋ ʩah]Ȁ:=C]jߦXsg˒?b_bhfBu" 'c2*J{:>=Apx1xMx5/\EJ G~~ZSө|tͧƄWhϧuL\nq"(QL.c8S q AAzJbq9^)1U*e!<67b=A=@0vy i'L &-nc%ʼnF1bQ1fւhB{Fq~3C[Ao ǃ[](n;[2 ~Pd7CO#Ta-?ḧ́ be*}s s%NW=}>r|f.`UČ<.AH@{rܤt5n {RY<=B)D7TҢ2qΔ#XZ?pŸבΈ0[7uǰ8. Hң&^3꽓QCm$ JE jxV/amˮdV:h.d름:In\!bKS]C6L> {pm8oiO星ޕ;vDly|FZ!nM(zF~# 1|S|X.VNs+.&d6}a4:P~W2k[zB^†nH@{r01]2FD0R,3i--G3rSi }PըL:s;=(hXFS?`IƑHQRc[LJ)dޒt,&"pݡwt5jTر|񚭾 yB{%ޓK|E#,7VO<4X?Ow6a8oԹALZiKHđBd&vKЪ*Chz {?6#D! ܷ.8\9n!4i "YۼUSwePF8Uqb\kXEWCzѧҲF40)TR[ f-E :# H XB .$1LZKcc7-=v>ZNv9UXJc/JUw2ADb&@nB2 ن<=O"Grw%m~W-l5 ÐӀP<-~ųITWue4xGLn.}CfAտ%ot\ے,8:)k6n64 *;UlbcBﴂUyq˼3t5J71ee:E.1dHHpgkLvN `0H7Pct{ O '3G8&/CՐ-I_I˵Ƈg$]Q1-}=GMbh#%KK3,kb !br4 kaȥ;Pp yRlޤBqei>#Ƅ %_)JN07)ۓJc4 Xw%o%B)Tj8yoStԷ?K 6(?] =Qm6!GxM5!8k~Tdܘ.l2d2̡~(;(gc޿x=*t36QdV [x"D*5vn3oӧu!Šf4,j`MԸ5Pˬ9h!ERWa0QE&/ Dd$.@Dp8,q:)*TAS61>~g-<~kh&1eW}PULS'41AX܀@Zߏ X%q\dGL1.E,pF* mDUՍ0N\潙/I <܄2+)UotzMdd%lC0^S`z?iB #0:x@&vpB!lSBE5ojijϭf7 42u74epx!fՇ*(mJ6 N͂ώrf㶲?N=i O0p>9̜oɢЙDUHu1cONg Ϙ>N.!']q ydPjP?IԂ_|ˮs"Ƕ=ISm.7AWXAۧq=U5V-[OFU!:cNqC~e/&FfTx]vsB( n7@LI5*B5Puin?}b9AvY?(#~_S-@3*9;\`d㭨ͯA@'z)@RzH94th;%h$TDjbkX8&N%.2É~B/+bË*M6Xe{<8B=8vmkS(duiwMr}ӁAq4=/J^y=8"a}u:`ZN ]}-H@{rܤt(G*k([\v%ೳԛQlaՂB8r6bV簩:V/Tu R橃xuI=.\Բw-AxƊR\܅z̄G`+YP7I- B (,J71`π޷㙘\[9Z{QØu^՗"p]m@h Vџ )ʦBAz&`ů7k<7q$c=Hם>py ::x/&_Lab".:%6G7yU 5!c8X̠XаcS\" '! 0sFQ ;8|oDs(,J71`puwdBzZܙt{dh,\;3j*<ǺY4mwf^5 13ߚ}sDj6G⢽ ykU*ιmAUB٪0<41Sc(UxMKʝ//hs.MeX7m`8-9ij7+`(8i|nn?TH||[f'i] B{ bZuaUߠ!pt~(`M<ú>Biª[KPWJ`aCyOρt 4?l~RjxqI',bģs̅ B昮Ic5_NH'Q+/ôH73N: {EB<]W2)ͱ-hi.=43N&[m^7-ܤN%>HIx m7Ӥ?* j= .ҭ.o5l.ݞG1\[c}[r&qj.!iTGx1\^V1%j> ~1a9ґ}Mӗ;P#h',Pц`KbylQ$gMdeCU?q֋zZ3 Au=VT)T9U+Qc|tʊn5TZ)AfpO)TĹ,kܜ [f7AOF)ǺKOΒ5>F&0WX;RT{z,uߨp̧٭5<:AFQ:,=Pmmv NzվNQx<̲$`}T4t)G@{lj1_$Dz8S̄z\ ʠ.H4G}@вSFc qᄊp-;( L4d1S,y[ѓUt;j 9[-q`n2OVoht D*s-7" 1+.紕",zkc׉D`rܤt)76]_drC=/"9c֥fw\0bs26ojULofv/0c+Q<lg=1A3? \1$8ʞLŒ;Ɵ[@uZ4wU?#؏y(#lOhS}4*]92WɗAOvÜX 5:|_gCJ uޓbkX8&N$E[݌;Z%R#VPqc_2i7 NXY_vO^Xj},]5"Dxi>ACC06I_~^͇o&:ə{ l!A))+h؍Ks׸ҮP EXc얓bkU_ NHRXQSs#iT~0.ЙG>rdЬK"V 'RrN \JδQ_D|$c=HYUʄ@-1/']7'1F[&fAg3"nG_esqؽ399[GJFD2;n]3Pte@P,J71a[(/t:l5oaNYk :uEwHלCq#q CeT2sSHZ mMILS\f_cFjf0hRjf2mW]q"XAh;߬i<0-8ӓz"=PVԚ>el{& ),-qDc`ͩ8PAn]pR(7Ԉ *{7 >FnR:c=̅KYa2 ~kVЉHwZv=Y$pNp>7 -QP~)a#׽aDeCΒ<-Z Pڪt''&YсM98?URM!5PYE Z|Gu"щVܩ*gcqCtƸw!;c9#E$* QS ҳVe;MbWыA Ftœ [q,MtK]'IĚKؖk5baNaꪷTϹhKްhB. J&#vf=J4ܬ;1J[$+!]S3})iܳxPh}P~pEZP׫Iq6=L # H X(9vhQ}L%B9`N5\} g)A3$F "zXt֩`JߧoPUm ӶlO[=HKGKJ E^e:Q]. |[Wx Wgίh#B-n 8~b<9 m_ K6^ELpIKVy]䱁> #d/; fmn`xĥ rN~n/Լ-?2*yQ=b[I[d%%bd$lg뺞ɲ\*Bana 83K(%tSB$c=HnR:fݮeކacCL+f(.~S& dΦ`HblݫDaCU d#M)nRGKܖ2w/J|jiտιN20aɑ B+87TAk"'pܶ©p j)-ǽHk!!w`8m#=+sG*Sk5@l_bZxWD?2ʩ[ĭ> \2qC ԿMK׬`)'x惜ɻ,[c>T0-(X0s o5M6JPWDHॳc[J|B>Ǧʎ.Es+<.o?ۆ;M:|XOܙVnc">Uhe8Wa@ Z>RYȤd3PA@/:I܎m_lDj NrA4?\](az^7R(;X/qf C- ƁHi]5Iuy3O>Zl73;ͧ-yKvfG=IE6p]ija,^]hDo@?C$?fB 7|OyrmkӁe>A4 P4vpCըjy\d3MuoĚE4t)dF-c*HmQwD̟Y2-ũ2ş1iB+]CQQtKa)glww"}/d\]i)Wr46"3!-ш gF;Ug MF I/D' j bNX1WJQ+5=$'+opŸבΉċ5,~moDKP*%^=@⚟b**$_zd)ΫE[Ѳs\>NA} J5^kZֱp=NzI-{D6չt Yz F dpT*IJlʼnZϱ:>7?]߳UԾpvIF:۔_3R>YWvZ FQndWV cDg1~)-4Ǝk*B6.jT~KI1h<6kZlnd "8TEE[ׇK !xȦSQ@Wq,o 2m!Z]b8 jy8Gax -=O\ <-C}W/UƵg y r,JҐNfgF*ca9Qiwo_ScڜQz%EFWϢ ߢ4{DK H\S'z\SrQKH@{rܤt) ÿ ^娵qe\Ⰼ88J+(Tux`E.6Of~}.+9Z2% s( +زewJ$F(D% &ͅ& Gޔ_3 Q'z8< #҇j޻ĎRxEPYy7mF[$}0Yny<,J71bğK6KeG (w< ZB4Ea Mpkݏ;m>BJr*[|.8\TWCΒ5ȞUVXlنW8t'mrG]| {,i^r4aVYuT6P|$\.zQQmCcY [X5<43+AdT@\-,YKQrgE57cysu$\c'4s3I,F,O g*]b9\w%Np W Euم|F, 8xdbV1qCl6e zl2fKٙwW %xCcc: X}"*/Eńr%ffJc8#A\a(@ڪwnCo6k-ݮ"F1qu|>ͳ6{'ey rKԝɏXG~,*f `wrYѨAsD/a*kŲ@a=QԷ7)h|Q! yQ"L 9}!BBMU< ҇im,oسcqQ po E5ȩ':>{| 5E%휀GfF*z-yi(_'`֩K%GsR sq*FND\6HVlh>[_Z wmSى5ƈ \h@Q~IY| G`:ap ILfba)*3Pg9x1-*;T a\]&BD( rs'$ґAl|R֓v=W!7(Ev^\:fIS;PmPZ6!Qϸ.C҂h8&rLCmlG}z$A4&W(7\JKBt  , ""^Hxx~=WIK >."1SVuJҸH%Ck1CJNQ+D ~ĔX%VBUIQZ857/"XiP~TZb.^4K>pj;cm\S~@+ o qJ1^M_2h9qO-8RE' 7`#qeŨrQ_y61u>m>bI"?៱|V"W(Dyc;_7 ^/Hy348XL 6g5~dxZ&Z;av(30>B9~m'U$s<0]1C>SJuЖfFh5 PWSC*°`@ Z: 4!c3{J}L &S/1O ٷ'u!^͑r{]I-λ;Y.k|{0}@f5OJWGwv:-3?0u4Fip#wF{PGM{szGEQ/*6my;ϏLC s"I JUW\=7*H1z RT X 5S@+Daՠ!54ocO ,~ 5db$B`(Kc!` DK%>iphm.c~Npi |bc?n84CeA mӋ mVfwH5A[892<+T`ミiΐXveomYC,v cV{9`#/ٴm7"ѠJ~ o5K\*ܧ?ϤF6DVj^ \9Dze,Qi3z ao]T',25}smjLn0-Rd!:FЧݑ' => ;b3rTf 2 wtΊ3[nj#}Z:IBuQfHw:F71ƐkÕ+\m9@*KdV&={s!*XW y'guKO#TچLK%{e}1?8WgCVΦtiM s@B dI$m&S'S?TR10FHR `;o`>S1QЈ$bH/\gt=Q5pdL?%a #w1~ѵa4/f=HqO S=3_/{YhTEZT4w<< |]H0è<|rĎZ] Ri1NVUInMgyz r1VxY'Y>Ռfcі3N$3S,I`E=pݼw(o"bxhvB}X(Bv\sP\_l,bmkML:BΗ(Yt;oCCptZ܇E֖@Hՙ͋s .S{>؂& Kh&},y /R ﵺly+eÑXuFw3b+CCi}xէ=OYg/%=Ti\+`*sYϜ -C9Ow}$='S(f- \ꂁ>Ե1D *g1?ܝp٧%|mm/j]i3 `nmٸۖvb JyQ6Ba5'$z8R\LB]%d;fP>%7jldP (yX4h@=.gV18K;*LOl7T]}izHA{3DDaG(r˴RƘOVgF@ĸ BRaIyxMt8grf,*CZ,1tnl~=A_y ދ~U,j+p*dr&{aBow[$vpal. Ք40 $.HXO^XsS!{XQa"Se\mi&.ު-;@=ӈ|Nst{/}_ȫOA8~Iҳfc^ F0W8'QvU ୫S? ̅J{cp\2`[ܣvsYHD#+k1ގ1X9} l DסgK~m;YH)I-?7Q%p}Z60R3^eU4f`'ZNP1jͥ߰}df3{uZl8Dh5dϋbսA<"Rۋ-;xb iD07Ye|e "/)X93F4;B̵td͑/c5qЕ8Yfi;=zp"w @=jWP3D7bcUh%8g^?Cc 5 #<#]K8L?HJxӟ<xR2\BeN(G">)U #v.'1Ix0!“T+ٺHV9jTFRl.@%kww"}cӤP03u z'6 seQyBs'?o. [Bs'?sy^fn$PP>dGLroCG kf_`K5<3SmdLN(]hjy9Js0ss}^͡xv^ˑJe)rVEL[Z5fтHl# UA`0+[Tpj`iy>VkT/VLB41J$8;L#/|CS&9 أ6pLحUҹʨl '@sXT'M ݥs }5k1~#|KV0rDfG̏T벂4,rsU@ޜ,J-qG}l@:Aۊ1h$:ΣV0 lq-ɼ'jk5Qלχ 1Kz֔Ӕb QOB +Յu W90xDe=R?^o{3y"[U7կԳPxPL.gw!Y)U\g+)$X NSJ8 yl*n@ւ <혢Dž5BC EXZ"92WmvDe!x6 a\#Ibx[p֓H]uʰ_XwȄACRoГ 1yiy_*Fc#*hv9o>-0u jzxh(Z}~A͠ ɣʈcw3npO6OL\yebÄD/ROf,D$ СKoQ7ѰZ5=Sz#vaNvD?b`P.SwSM)HiQ&Δ^fFŀ٧v`'*U(! YZ"H1}ۦI`}r)Z+Vux&k e%R x\-EԫNlT]bAJs?4`;u7sv U/pX؉Jfdڜ9:';P!]ù2u͙ ! w{޷x[Ч&3)/Yo;sYy=VO$93ڬ]1v0:xM NPD9aL$udjv ?d+q5(QXQrnSyB7ojq;ed>P$m%Bs#k\S]U T} [aA75`Q+Y&NլZ"]\xM](4lbC.b0jT"4lgך+2$/#ÖXeݮVB<򤏽Y~ϡZ+6U|Ǫ.w 1 WqSF@XPwO CYY8ShHڨ_`azx G2h41FtsQϙ@6fVH+|hIa?$y-w8!tx-b~'2U|'Kp>WݗFR>YGlX>r N;0 E; z,]g,'>7 Ye.7͆#kB!mIlR,J"?7x]!X%0)T\$.$\,Vnzר ݐD[/~ 2 rH8I1:|[7Ϡ vfV6^ѽTFLL{a Dg+^{r/R —O_bE Ƃ[|9+}̘鏞ԫ!8ϫ"IuzɛV˒9M$cw]ӂ=$knO&H޵=a}HPy›CI]Q~)rgcvD}ۉُ"t45e3*~ 3'"8D^VJn'S5[,|ҤwLjs@Yr# fk%%w8/L<͐"nM,,TM\{pKT~pF&M]U&<1lw9DSp38$owZatTͨ/EMs{m%6s,s0ip<A",Qd?&VzեwdEЖ-6ry,s`s/"{-/1),<1WS~rFn$$LL;x.۫]c߁^> NmSUㅂ]Boe|9X8$/|:n70f}3 b"j3t1~8Cj½AثL`^=ΐ Rg'~bX;Xw&C^Wt*Lbt`Sp8Up|%,#qej"C2, <2<ƿ[hGwmwQ:Ϥ_jƢD;d>5oEßqYu"d3.6a 4 2O B ~UVާJ451Vҙ=PYWvR.)8*f_&-j8rb_ė{ׁVnm 1|۫90d\x}G~~)U\g+ 7)g OjJ xz6&;UD]7CtUrR;Ei [108 88oUs|HcU]B +gυ՟Smv y_ {0ڢo~%#]<$2n!=`IoՀk,3[Y4xBM6I_!ŞpBU/d3T5[l҄m:4AyCc^mTLwh0#s&sޗ3dX&[n~'_ˠj$9J.a"qFvK(wA 6@~D.X8>{XLj M.S[fKA^}_gվnZo jv{*Xy>r2\,g(+WDK|=At4n٣bh0|!}> #۰/l ]h3Z 6 #{Hb˞}5ois3Wny_͖/ZvAGW_9SxF)bL0LF1+|K"| ea7Y*IHg _Fl $k`Vh;x$d^G{d)Tv q }f&3v;[sh"-;ȫ{9SHK㽶nj":K,n`B YyXl'KCoѽK2I PΟn/8I'b"oN#af/_vԐfG;0ś2\ , a8:.4s# Ѩ`>}*PD@\/vy7[_R}[cc<ĝΐ [0(h0OC C.ԩ;os$#oT2Zۮ#bI((wm/ʹ%n9*J&{qz[TMG&`i[!<_̅EngBgtiuMz;!7^ q,+eۆfcuo QV|TQs0Yfoi6s~㕘"KkǠTdLjW1· _}%ƕ:ĹNU-\ߎu,HeR^sF 2 0VfT\!fкKE\AfAJTe-NTsQxOσZf{# xʹq%ˮ "st;r:r7֫rkC)T Hܶ*aE"#-z|v}WҸJI)WPTl:!3IV4/AM KS.0_NƥA;4i%!K}W7V>% ĀETƙfU%m$"sPbE'@d!#Tb"'"AAf>3)5<8<Ok{} >1kЃB\sv]=WC,9:BHT \@Z'/mX *#R. bvf'!0?NO%UzR}tQI;1$MC˩[*5~_Nf#q=K}2]&ض'Va{0tSz4~kݲ2Z{)ia]>2Ye+yE/-O5w\IkGedOZX$ ANY +~,׽_ƞR[S-ݰF$bבRVtp}Jk{ʕCT Jm@0mK&Kkm٩B#Jx6[2[6tPI'f/`gф$#Z.bTJnv ~@g wYhhDVfc@x roxgN!F(+DU{h75n,kz B ,V(ANo/?z\, M"^? `M.^zWsUQr,l?nVԊY.\LR8w_%xx^+c2xq[#K_0ͻ 9DR+wT͐&=eO; Ui9q|J2ѪNg٬g!!{hYI,$oe1od Nm(Ѯh]n3iЏ~?2M@>ڻ ][ 5<3S522d G~[LITbK);+EpXdo]Pt1bgij Bb| Wv".0 z=':åVN.Pѥ,Sp;|\ޮNW1 źwDvu]%M'yMUױMXs)|{Xyy[Hy&0 L\Z`}#CyǯiG+DmK= '6ѐW#cYFتLc:lN?8p_ϵcmA >$|e0)Ib*e*o٩@"K=Z~e mz1_GR<$>}_N$6jd_i'a~$3SɊAW2%Ѕyۦ Set;> z`\!L }2g(2E_aᛡGo;h L` 3SqA1h"7T"}vetã)$cP_0hi[j4(%ئEJ Uue*ZvA:h 7YaVxGE H9qoٙ]ՙ6xi-m|ִuHZ~H(+;A٭$k0;.6s hv|RyobN/|biPs`L$ejc㦽 DIF4Hy9%zQ>sTQtjv,v (_*}ꔐn h\]1<-P>+ bIq=zZXab#cCU(g#ă}ݺ VSz] \y ^ɦPN h]1|l$tSU ps}SV)0YJ6af ߲9 {CތA=7 wpl;@oRZNd zxgϙH-LF%AuSzy9lP\ĺ™HFr.US ўXʉŅ쿄W!]/J_]B~΀#e9(ZA '@z@3M {bb >l[(jbPAJ'CGrl9I{y_~$~3/ɥJ0rs)x]")UP\KݴFTGr7ke.6D]a  ~sz%t^Wϗjf O!(ݮ 1iVsIV`'[cҳ nEv}c=/o~mC:j!Qlwi `~?WJЂ\’T˸5t)D CUQK9X;p1n2C5wMyH2{śOJ~yЉ 7={cF@("ͅȃm͎O@D[ vd8f6ðQln簠/ˊ͛X8pUJ"TumiuD6nrD(M_8&3GE9-㐟T]\RDҰ^WÂazqO=mjI$:z H~J@q4._\Umy\Do&P#⢝.jV$l˨ylYY vrQvB){mKXWY1q_@.8v3!: 8.ƑPAѺRYp F+vgil|*ygCZbjVm.3yAmǶ\B}4:2 ;/mD1OgJsobfKYCR/: ~Pr+l%jT iSpyGx1;b^;Ė"M;P6 Ư%gA> c (f}5W@CdTE6O/[b)E>ȃҏ8`"(uu' (%7%•{f$.ALZTR\(O綶cϜTW(YϏ%nj$TNU:UZ|W!g8]*2;omؽqRedsF.~7neFtIhLrQHhsqs`f-ӎ9!hIs;D ?Es(|֪Bcnv``NCCL2QyRV-$£͙`7px?V6#+kaB<3誕2'BA[ҠKXbGT2OjEC< FO#bB2[CfAJbV<q\XQX3- ϥG,kx,)YYoys})cs8x:&4=anCF`+ [Q؃@̾ĝ>XS1T7Dl/5ĝq5R^&yb>DRz[B SAn`4BaLSYO&˸VM@QO HNq=-Mc_J$69:e(J/.on@M)Ev1~4)@S6Rt% \ =쑟`I%r B3Zhaev܄2T/A_ )3ɁC<c9zG6ps#q62u[ m-ƍZ5PIפ_ϣBУ/<0Z!+jmgQ*c' !qy<[;`Um#w]ys"tPr!d[Ƞ;'ēv|(ε꘻w<)`$c#1"^3ay~6|%JC.=F9CJ8x6:A8bS/qۗ00, Ԭ1wz@& hC CI.*ғlmQƯZ҄Lj`˙o (qnZ t"@n@ECGҕ6+'G2#:'es/SNo4`搒3ҵbތν*CjtlYޝ,8n!ϦvQ]\_+H ٦iǯmۏXT@ M/M5qOgB2U\0rjTL$m)ZTAHBi-.F;~-.9,[VfamK<|H'yĝɸIsWR[H̢& Pӄ1n[?w'k/^H4rAtMMr]g,O]vcKrOkI0j[0``ϥՋ |sgxjy Ĵ`f2veDїl1~vbp P#@|z<XbOy͋[Ҫ}y;œP߶\)*!=`{` ΎͰ]IZJV :aMs%z$|rxA_qڒ}*Bʂr5&#EKX^)`+!׆ںƒmɶIKiZ7ֈ0 $ҸaZmUģ)'0=6U_戋&0#n7R(D u\kZN}hzP`Ŋ 62@ `- {%HWkw&~تo>}^=~H\8 *w考$:[ci)CT@5Cw&Gަ]w|0RY)M $BJ̼k̴{kN;gUuXy9#GAH cM:n" #Nvւ0&.  u\\֚Ue\8޳)f? ;CleWG1o %YeYbFq DVmpBt W0@n i}Z]} z>UN?Dob&#7 M*VU`<˿Ջbn MTU@ZCB-B9prgOSki 4RR_&NxVqieqYzHIGMwGPL sz zjZTquBO0?]78[ Xgy@myVE,X]/\_Cx I Ė_t'9U _G4Yf^b"/+o; SȎ\ROJ{2-/rđ,@QEѩ.0w^N2@.MK^tWuXMUtޤIĺ~EZx|!x cFi_cu$@ˬ5e ql^$?\ňN09+ǯ=8aLv"\ X= #W24(kpG#2.ui,! BEM #ِE0D8Zu :6&\u'GUz~ƛ127d2^GȄ<^F*&v\%#,KIKa/gu !S4 A̽ iZ\Djۘ`RuBsĴsv˜z⍍?͢$vxXbz/?m3&'f ;<<k%)[ DK٭b)q.fbn^ F[%~Fw~vtP.t۷2f= N5W|G1 NU9:N%s CPtJnHܙ>ǻ60viXB4KJL y$UHs]4S &;O!8u!aɖP:.)?;j=+9&͋ +}SAF4]4ƨG!T'euh71_H r_HU7j bQw Py: Ď\muUvrsxgPͧ"X^ߢT,XPHlwl+#iȡES1WO |Sn+#C9!s2Kֳh?~0ʦUzK:SUQ?Z??D[N c.S {s zIY DϵEUPw%Mě6'96"N#ĽʧvPk@K߂mZ]Eݽ&ב[(ջ&k$)3=|ۨRc[i0wjUR}hȌGSOTI}- MʘSr/SA7BO=E}/- '/E4)JN%N '՘lv& )= jp. Ȏd.x 3m^}L{ij2+); ˿1=wr]Bץ^f2Ҙ(]Xz3p*1wݩ%V(ĝfn }H XXXD\P$2dxk\ CǸ1ȧpM5Oϸ;(SGi\. yj%ՆOw 8fpL>N?u55wߊx2PY]{;ށ컌M/)Neӥtq\/j¯I(mXUMn,l~4)JN%N 1DDᰵ!2wOEEe|=,]|3I\dgPZiB:$\4\jFJ?kM0H}|>p3?&M>c)".|(n<Dy$}er\l4׉@SR GPmKsv{0WNX#QPj?Jk>{R9Z#O5>A:Dn,q Ƃ2mAFB]oO<Mpb)1#Y{ U;kϱ ڎ>X4yq_ {l7^b٣خ _4w͍!q{Hc^?mXU=E}/- $ZxshRJ0m̍M$hQ#N`cAMʘS~ۖ^S,iլ{&.o(*"Գkgۀ1ZJ2,6t1nUEc8JRf8^ Hhj*,vQs2%~nY"#S6(YQd_]I2Wp4q.Lk ZxZokcWggbǑAS[K6("t_[VzOQD_nOޒX< {&a\KB*!}U~'1f*tFwM:QCr &q%$GnJ-2w;)pLEGd2 .K w _ K,Z„dbi#d=L>0X|df],"FG7ՉtkFkB1tM7~|Rd-5Xko)c0â6T'7mݞfrnTƂ1L5[ms΋[VzOQD_nwj¯I5V'\'q K!2 ؠlڧ(ؑEFƺQpiR7~6; ,RAhfϜGFEs΋[VzSn \x73sʐ_36w R:=@l&?5n|t.P˒HA/C.u[NĔdž7z#L@?{Bp@ݎrцrhpH +@];[YB4}?.6Ͻ5@ LuФ.@oUU1.0Aw*-G ?Q1ܦqN>eרÖ#qS=E o>nz1ϰ-¡!zE^r:q6J&b[a.)Ow ]KO-ffEq5]yKɋajC)-1Lg㙅p=mTE}/'< hQ=&~tP YG@{#= x׳vh@$_7YGEWxG%0kFuytm^|KT@I)aM=\SclEB%ܽGn%,,d:ztq`S_OF"$ʶ("t_[VzX=fubꀭ=h8VeI]KzguHbs,մkx::ł4֧הBP1?'x`2 n؜h:Üga9z1'c/K z'u;@-|4ƣYrs 34D!Oa%vu3،3T>yjoFBƬ 9*GDƞ ;qJ"\Q7QA>j _A4Sr4ܩ O˰9u mywj¯I(*`]^1:@͓=[tYC&j0o6[@hm64=!q{Hc^Յ^Qۢ]ڰҜv/ZJZ4)JN&u] + *7FqNP Ƌ{Wq j9tj~=|ٙG990Z0tKp+F)`a{铽.ϰL67,ѿ~)M]ې' j)7~&lr`a0c-$$HAі\BPEzY9C* f<22>?>R&3*[@h{h- G& uQD_nwj¯I(|X;)IĩAc}"4.C5 V*li {C%UU \. y|VI#Q#B(dșU;b'x^HlRXl&κs(#E7@#]=5[CQ3#z+RHVb8?;@k }tft6\.BR ܼ)TI*gP'A-}P.^; .繁a[Cr xteJ%I)a lZ`X0N񁋐Gc@t3J|jL}sν¯s[@hm`MAhZ=:vՅ^Qۢ]ڰz"t_۝BT q%qEERF}cuc.6˧KS1-GE@QF-&GNjp`QKF@_59P ro¸|4 "RmWL(r M2HcS;‘"9p.7y7bFy1.yH>)[_*AejӀ]q05G%D˸GN# ꆷffP,lԏ kt&v FV* 13gpȈpS,hw{2V(enY:)XV][;Յ^xNp&.χ Vk qzjieE^tjE[@h1/i {Cw/- '/EaWOeJ"'IĔ DB$ȟ۾PTs-b$GU_|̂u ϶Ԛx/G-kfX~9.|kҍ /3 IiL. :"!H,"5GLY%b e&_T0s~ ߅CyF>2;Յ^Qֶۢ5\Xk5TZx:ę. J `V*[@h1/i {C둅 _'Klt.5?cՅ^!v1&!莀U 6J>4g sPNm# #XnҾ)1=klx l!B(?~$_ne - {f\C60tF7-y]jG\P 1G/R g KuvkXLԶzF JT.JRq2? Ak{1T,q? h ^c A>q$f yQ; BT6"/h,Yl5?WueS&50Y^:Y#{u$8=1Y=j? -?AѴخ3mY/_b8 V]:ȃ WSFX.㟠kvC P,(y\t9w?v$gTU5H3.z+(1? *PC|!V* gSc, =S nTʴ%k#ajC)-e[,[Bˬ@VՍټO~RpXPv&0I3eveyl%g<7 P3ad۵N,3D^,:(2YZAxu+Euo(ȤCTS:r|"aum3谻7!_?~\71V4Mk p+3*L"LZ[Hc^,=NfSn0ܑ _d4Pg+V]vtʏ&:jJu9;zn 9Я&0 6}8hU Vd|x o¡`@ |xVMo:r[O({]' ؼ%$Cns3QZ3啅ɮ}\2쯆g$vED 5~ xђrSһ V*li {C*kve!h~8ڥĜe4Ъ)8Uy_a6>u,?'zRz@bM6 7DU^MM =|Կzgpjkߧ ZcK.-tƇ@LY8KU#I(ܔ[C9w;J _+ϑ"qhǡYY}}]FRGjt'ZM@&6r(8ݼ&|(nރ9В!$bG4Q7PT0s~ W&,lvJlG&J=TO(#(bDm_HbNCJ2kPr-h!{c >Is9H6`L~jlZBKߨQC" A n%A&a}X"E7;+ G9 Ȏd.ܛ/}]o"&eùh- -1jYsA 62Yf,6GjriPM;͝I t:ϡr;`ůQv3Dd%"IZ-@*li {CǸ28Y,*~F6RC&eCM]>͌QV*Ln ׻+F#.-zϦ΄$Q嬇lUR8Hek=f#fEn4- ;z!BҋhN4;3 V/On>Q%H*IS $”g(ۡPm6լƟDW28qT*e~qq\^Y6Q`Wr $QdFФtzm^ ZcDM]'^%eFÆsExb`_TS.Sb1PH0"e5Pף(uJNV~`u,H9+)a䡨ko[G& {h-HCaPեkn_h̦e'Üгf':[AZWfb2ijuD3dQnB{ohS?0>D+T96ӄdƏ}Q &^f_% ΗWgK3Z'<$π |MɁHgޅz)y[@h1/i {Cb@d'I\PCfV^TitƏf޼sG2G\H_օ|Hdu zw'4INήbBRf,SԨs?)8RHQk-+ `I+ I=)9XA?9 o¡`x|.; ][ZQs^Xeh$Ͱ=`gYW np}t..6˓aݷi "!] T4,m>.8Aٚn[N3(LK DQyQ0ܞw8x`?F4! $LͱRq6b:pj`,1Y eA~ri~ԿJC@"Ka*a:т؋<'yE `1 V3cHc^=!įJ00eU555g2;Cꮂ@}Cq]:\mvj:)u]4]/'ghrR$3\5Rt3UMҜ6\`eyc0TY؃R[` 8ͪn>@QF-&GNjp`2<9oT@JŬ48cX'85ZG} 8rxe=GΔ4Rc#T^ ?t..6˧Kl-ʭ_ߨQImxY Jfz;J\!p1I`>Pυ!|gK3^kctf%!\VwF})IyB߅="^GR8>W&Rfkׯ%j`G۱ wJqhqur+n Aѡ)KʣNV&x2Af3( Šΐ UOs4m_,"9we3(5H,k/?'@s`%y \ Q>Ri;t xօ0Qd |OGSw2? <Ͳ.1ulMVZ &w̹wÝɼrkV؃e&_T0s~ ߅CA+6 >}V\ר&Hs՞@ 姽;rM H ?'Klt.Liet-ޗVB\ר'3kr*T9$cX텤Am:,x+?քC9ŕCHAC66O>N,IrZAʄwv7Kzv2o`_-w&qIg=&J7ls R\"\N.PFdU\~FKTE:w鑼4Opꬋ1`Mcn \-RmaB`swcwZw&4$;r?tҿc o] gGvjҦ$M+ڸ^q[2<~iFU&5, P7r}pU> *KZ 'b?!<:~\U拫+.v@!օfQ~ejT.hSEւ!Mx9{YT|DGaْfSIA?I۰C坓_ikϵ񸁱{ 卭bZx^E.sy ѥaLez1h]jI&>nKF/),gMѢ0"HiWңi+rXE7%djDQƬؘ Eu,B=m F;9Z@so,mk /Od ?97As3Xub:>5EcLk0x*^nw² ^tL74 ]pDd二fd9'̑{+qt1:SUAh^=<۔ϟ9 riT,yKZM>c{CGN&'M)eh8}Fj i2zx(oڎ$(l;Qk,Ɗ>}5 @Ǭ|t8Ac 4pT\o ;O}uS2*7:UXKcH]CS^Y82 $Y,mCYBC)t%\0#.W%=0{vb$53W+H{ 卭bZx\Sw`] bM1 @B6+HdGR_Ȅu"qr1!Oؘ҈K;9rUÁA'%8Z+ H(pe^%-{GV6UL̎zY0SL[ݒF#%wa,`ڀ(>2AΰTi5H8c Iu{sex /roLTIWیɿ?ohHUϢZt?pG8EUX-+мOJɅDϯxXeE&3tR8@?~F-@Jw9}"!"O$Zx :L5*aHecA~'ԎUp@ݓ(bz x @P77MF˓u3lQ[@\X~LJb%Tl1Klc'nDIs8fz{_~ŷDN鳠YfS i?@Rn^.Xe^*JWOusQ4E5B98N"9И(53 "g@K1x_N<j=7^~\|b`a&cXKIzB; [mSx,3\xi.mtIW5 VN xÀ4 KpPbf]u(JPZ?|y^s;PZB|Ε7x24gLA$%% &01b:lb#y]$@ l6Oner'(#GA_H "%ȳ0s݉(b$6CY &3sbW>&FY)QkZl5%Ʌ|%N'{+*0.?Ni@1SxxsD0AIŕk@\簠> Rl oW,HIaU(RNF+me+34s Fv_uJXۻ󟅓| _b5 řt s9 gG/[Ţ'&ǢV`@V)$C ˮp9z[dkux T SG]A}:#'Q("i.;ph^L=dGdN ֱz-sv+GQ[<U?0:MjF0OSW9Qݣ|J'I#L:[t8AcWW`iE %aK b6Z8%SCl5AOx\"AC>R^b]]Y Lc/n5r4tv,0njUǎ#D*g%Lm!@H^ R)Q]Mv`r]aY:132Sv-͂$=ь){²Nx4@P@C2趇5Yp?@!օBMplUSX,+FyG(dRlZs{,-!oRNE^ G|SiuNa/mu8Trs_ Fj@^xc㵓oNlc]>;IF`D]{"@9FcSbb3.c;(Nepc^k :[*H|b[@=f/08lwQJ̽`ƯLg^fcǤʂ uR0MTu>4\ENj?g#Z}L^ERν)M*a ^`H<\RvQorMfь(7R+LqvB ilJ?j-ݞ6ʘ~[9*EȘ`[12Xb ӄV0G ӄzg=k2>rASobAj~i@\9L+;t/{A8|98)5- 1F,A 0muvqnU5*XΣJއM"\ 1x/^ΨBI2MƊ' IKo ș 1֞1C[pobvR 7!ŊWzN%3Tֱ}JL(w &6$@rpA6K~3&}:Poӎ* ݜ:B^4p7FKSIPnÆm[NH{co;Y3z<%>) *s:2, wV0W?@*Ex Op-a+2:7EB#(v{vɊPaG~UM%`JX!k<~I4x}  gê{7gJ em/^B D2KBiX+6w!;U ,y"U4Ɗ4قG`}YE WJǡH<,|tBF"\%/*9B DRFtk !މ1nEU˂&z DB16ʘF),mZ0!+ۖxK'dq\}F;q]@I1e h3iKWbM چ.]1H/MP*˹pu.UH+ eL_z8 ?BC aZ1d f `Y°kmƶyF>L!e +C7/GSQ^)ع3ga r@{Kݡҩ8'!cנ$w}v0U@L訬XU|LJlph8C;7JFl=SUa TsA}@5<w&*EzI:D|.*"R^TI 6dr? KTY*\`3 y'L ILJ/0ކܳa@!d5Q:UdVwK*Sr1'?ٚbt#{A"! ]Hs49,gh)1u" Skݶʭ'=tc ?,5/G``^>D* (MvhN۽H#I>Y70?U%*ʟ4Oˊdc ">YcvR'֞p}'Ù& +V3riF#-VHT"+mߺ6%jAH[H!^8L'~8ngQr.l$|ayQ[PDB!-6ZAY?>U+IM+$n/~ IEHMcJ3AD$y[#aTܤKF`<`.nI"F rP,3zi!cV1qlj}~-zkٜۜym)r!hF iGsw`{"gl+L>+)fpbvשNQ:|Y#z?Z)g-N s $qРFwLAIɊ tڥ@5˙/!ucl,r ēCd !=M\}Eꗫ# {!A^ٍ9rh⪟m"+#1(C/wO,>=>:-Jq1N:;a3'6lMN c Otҟ2:s̨媶uk ZAnTʣqd))7VЏ$쇚Xԓ#b\.wy,ͭct&vHQȣR\c@IcxeڥOv9SFWTh#x8x c!%J2HQ9(/z# (Ykc96q8u,mkg ;DkP%7A-!0Hd~^ 1v7 #]{< 1R*dӣ@h?>X׷:AYckXn($a9kz!.)SI] tU%V⩫|EY=v\"Jw?y(C]ȉ@Ӟ%EF0bA4j=XQ`GEBBOׂ'ngL&ʂ&u L D%V@@l[u8S(eґ z ӣ*Jl024'<<'fVI]D( bN7VY,&}i{>1 gecLJ]i0TU3Gry["ʱJb#I̎iÃI)֫( Ԙ'y0A._G*Νԍý @U ڼVؚ(`Q3f<Ѻ^FF䵒ۖ3S~ʋR;Lķp(J3+]v!QvZvV[YE{KQP8[&Gk;:Ay &;q7FE"Im A^(d( Tn@%;&fB­A2 ORe&"+W/eA11 |rKǠ03O* Lo2p"DǖL2n 8~T[ib%]ev]^*.= w4EA9gU=c=NiZTZsQ^`jZā` \?rf,N:mHHe-$Tfs?GMxŦ]TzN\imh` /)bÒ:UI˗̪TY?{->(|ff5=Kq9^No0MBJ/ C-rI%dIckXn( )zy9߅Ew!seK?vT#뱄2vrm1D&.H0;h>N! ^o%Nq\}Cr=TR[rSRʠ1M]z]=Iۦ 1 ifxAR+Y^H=ߜ!k,|8R<8``m;_=s5M<<RTuY+ri˱zX%Z\lڋVhZE)vH[En1N )My ;9u dq+r\!s JP˄ K "E&=]06^G.uD4m  \8!׬kǾnt;wԲ?TRbjƊ9Q" Wd)ȀYBJ4g'.!3zˊK4^9>C@T-zI)g4(hiD'ov`2csP9G2UQFNee<cW̴.`S4pXHKf.ܤ # kNC śΉ%%S[m UwTη3QGEةV |ůA GDRAbҪ Ȃ2Tqrd"DT0p#?*sk~嚛AM m&``Z6jAfm=:QK +7z.WuL<ͬ-V vYYCTx1(}s@POwH,3CTڏ/u7l|Wpm EP̓^ҢNs.6O@#Z.WC*E[ #ܗC-h|bEU!%1iIF^>QWsƄ]`_x`c(k).@@*~;U o!N{fI*xoMM$d>gz$kKx4#h;ta@)v 6lqT]WnND_Fns,S x"uI{2|iNbD|=N/@vL.Z^4-B[N1kK_U?2ip  (ߧi?{2f:26]:%c㶐w]vB]IUV>'BT?KI6wҒ}O۳P6@=cHm\ymiR䦲>GyMp* ?#|7(un{Z!x s /1( ĺo?Ȧa{:8ZIV\]XڱA}3%{I:l1NCJCt`B2ڻj/>d YqJOR<.O~[~x$cpuD~Cyooa Pbۉplbdܧ;967Pʟ4Oˊ^7w ;V)AެMdq;qcwjlY\]']Я1M:pbb(320#h 0W^|MBA.#]:Sa ewxͣ >U ;z均xoPȾ8ɏ|(9G$:y3RjC`N@Muap/RoGtOPiPZ<3ӡZ??7?Gg|=x:+qK#[MK{u;V `}xoOԍ vu=x,"RŹހ=:6xѣKlcj_m+Wvd&,0 LjXB2H;!ߗ6V m*4?R CvfVi= S3ߥA%Q_631Zwji©! C!M)%(׻}UwE˓1θ7#W9jfoV&7 /p`"C{SYáG =EC"Q2ZDFOZum Z4cD>l"+]8E>fkT,^4\BP9㍺tir} I *rQjdEC>b'dg)9cUQ8j} 2-zfnvMP "(~닥f[e ɱjʙp챵_f*u: ϙZ^H=.)r1ͭ)pOd\yh(X1WlAUA>dh^AuG[$@6ľz̷:x6Ӵ?5GXdC!󃛬$Zj(@ծNGEТBT`P猲B8O , Nl:ZdȻ5d\/`t -Aa&ܮjF*#V*?\}guGpuwj5(j[:Tܴ,~D8o|c''&/5‡앻eK .%  qkg[G>Wʱ- tOA<~ (-I6c悟FϕYUpceRkBDE]ٽɽ;_x?=m0#u7jW dϏ̍1 53A|\*:Nݪ "e}Ju5iuҡЀ?2Rc0cZo%U_NS#$DE?79kBAQf^pt"Putgd^1?kY"m G7n W=JOo6 2ohc!J2H{WbN AYFrNBna))[BL߁awg"QJN)V34^DvU!3'TQZ>x6@l08b(C#2# x[SbEO꭮B4f jch8'Nc$:˂/Rήdh^j_ R/c'*Ε*P7ƋqU%wPPҦaY3>IWT1 đշc]:ry9BԷ,k"u5.rV{:+*J8w@ERQz@F$\G"@dt\FRHpN϶Bh; !}K8%ލ$Ly3BWmP|DPVm,L^r% =U7FPTgfҨI ޤ5TV63 }#ʆ"QSz[!DE!y*}[ QyCe19ɝA[|:/`/Th '.qc yƞY4$tyUpхA >9|>YKTw t6s:4pa)*`:o`{J1ьM(H0 9{ϡiHEtŐ-jy6,jk.AWD>~[`Zo^{§f `+X+\vPQ]`=*: aѨ EӑF7 W.xPMF|=yJWSW"LY= ţn í54?W hph~8@r휤W siC9q;?P++#-dr5ŸEZf 3<С3֘-RjoðH$]܉D~лRu#*Sscܕxc8-(i,npG͹D f\ƁKLe\]Xb}>bO/IjmT{ ^(j?XAZNEV<:Mb h^3%B$uZPJ~L/7OO2%A?'?U5S?2Gvac='p _;THqÑlpK|KikkyYIYŴjTC$C%.ny>! :>֙v>9kX_$ù(x1,xF13p\$Oj~]>1_ԣoe6dƽq>d؁P<Ж 6@71}M Ah!XxZ nIU4(yfJN$8t&竐7aLcʡ{u˼.~xm/ѲxM8[RskSxNSη4~Z1a]ʌbMt!qN a(0 1QEc[,/w/y=A̪ыXY1ρ x:t+EZBF9T9ڄy 5@Rt|<@t(jg'GaoZuUgcuR)!ڲ03`?%2W^eaV -enGk9laGX_+ l,*jΪ9oˉB ";u_=[ҿFy$^jWдAڦD/ rc)y&wn'82a-@grLEyIWs|` (з3}[=7Mj4W&G<%`>f\PH77-9oۼPeK;\,w"f?tȕATHt٨5k2bTnI3ؚ̡;wFX2mK4w? YrSL푠I=< yڈ.\3"9Vb&__Ł Ql ZlJT+N3a)>vNP& bmB/a7՛S}~f-ugj΍\Z; &!]NW1}N3Ҿ~f+ufm҆L X :a?fy0A8;5 uFJxNf@E#@p.X)P+;e% h}uxf s^V@,2ᎂU¦̊-wBTiQ*vO+oC_hV#ke Ԩ{bt g{a5lRi+y lJ=4|ړJxG7+c.}c/!"Py.~e/SPj+ꋅ$W4Qinj(O;["uZTh3! b%F:wKKLSO (rtex•&8"'X p\[зi':F7;E0; h+{py a1*7 ǤZ-@ P [~+i4>IO+N)"3 [OV{l k$V0LYdEy $ O_YU=woa)8=1a2vݎ'%^VdMd6mW4ETrY`9s7vNZ _#={xzdȢOZ3wV5i6×> JGy eq3I?;„Z;R>{qaEa_S2K hfTbR=}_ާQK(K\2#F40jkf'ֈmNV JK~1Fb3Vmx+2A(vE 'F:\#rXdVsO6tX:^Hh Sk;-L*HHWB Pd[ z7N*Id/ u*31EdVD\4;pN-AYҫ]WS # q0tcRHR^a]#j6I$L=' tXCpRإіRNwVvDVr1mjY{`Gfd^ɸ2"ſDK6! 1F<+`2d>dOw*Nb]jkfjӛk4Y1 sx#;mc ڹaNS=O2 -Ɉ˃C= c: `ϹEϓ\4ȗ/SV`XYf/ټi (:zGbHhN]P 0 NƼX3rH[5KChH%TɼLw|D҉emć`uOQ$4 cU>տ)/fVzɄ ~GFת4G2U;΋Ⓤ+N S;wT1rljfy-F$+LFlj_4+D;ŗS' с]XOTP@ET UwŽ|~&\]yiO,{)M8;$:&`{)DT%^Νoԁ >נѝsw.wc^@~wަ_5YHa* Y8[g81 [ &:zcоVxQ?﯂j|p}.hnxP+*)4Z_ MJ&h{ܘ1O7Qm u*@/_vX^njS,uI=hMHxnwBw /zՓ9AMzruH⁡YiFlrvA]Kw *Q! ^g7ץs+"J[=z?Js'.0W'loQfs>. AjAoDn'L-~Aj\W CZP暴Je'LM% >HTN'VCHX 8Ĺtq7}dD᪑݅lHM,51j1PrD#` z}X|qB `Y<),mihBƬ]/O?8A|Aϫi|6%e}-ѽ~=[A 9]X = 4X`ó ϛ.5oBԟ<+fMILM(irw?!$ /S MPi]#15ht 2n'x4BӢ^]i/ $?P/?-})5)k*RKcׂl<=]a6C K+4lc!xl]Xb=fSsMo9>uT}^G 35w۳%*lxm!kp7YXoo1 \7 x#%]A+$RBE|2i y0A }SRzudךa%ȨA[! hsZoo dz[Z]:Qpl G㗿tG^- sÇVk*O[u褧گz?qޖz^xbZHo?6 ı2^ӕ Dmü(0$:1`O}zF([T˃Yu8AsjtCoV}gxܴ^UzVB[Rհo7cАk$¿sKFԊ PJGD}8URDJ':O 2ݝCđVeo?(UsAms/.H.ߤ)4d 5uCSn 2AV/Pt-+:UjxA?fshL{!|nrceم+yf(5)YoW+M ZNn xk_8P dU_#={x# ԛ(eMبbaf{uZ1fL'wE9SV;!u61f HA&`iZ'4_7\c'j(5:>C$Bb~ƈBoᛲG,7Y P/Fݽ/dlv͎QA;Em %bS`&0ӯU{nG\p5y TwlˮTҺtNrNhy_CTWp1XOi9Ҕ =GX5/*U/PO:n/Ȥ<+'- S(ϫ%M)iTr\:Gt^vȳM@bhM=.G;k ȍ5}4<(Un}" O5_7{W [nߌq4Ҹ98k5 %N(f*< &+&[vBXzVZb7> DR)-ZKer3'ƨ9B;%o&CH)$)dFW'jD8 -[7Axʜz]JcbsM$ |,|^|SzCJhH'@X g$<75ȾdVͯ TV(7e>O)X8,J1b< ?H& p :Uj QxH P( 2{GlAkr7rJm!hlǦm eA& %9dGCN<(A]UKgzB =~d4lEqƢz7)b/?a7 %cwHFJ7#[YS~))Ņ3K ,mN"̶?GbvY:,22LG[JcJRMDSYm Vx,h4ŦAyP9 :g|}Bn^fy2IG=P/:d)u{}A;lǥ$ nFa`Мau 6 -]ɑ i@ ԥ4ch Vĭ'E8J0`3pyo$ 惐'*,1YŹd X V9;$, 9J{squL L+KNOtnF[b-!vO ,(r /2I#}Cv~qITB&"vZSg2șĂx`?zAocq㿰1W]|$=*R?^yDk.f7=vSFrmjklrLW-Dģ% #]"e ئi)&'z쎇@Ь7b|m$=J|POEU\p,Ein!>Z.iAHk%gy2TeBp̉F hA [:qΑto#3i,@MHw}vʀR}HMB?4bu.rs'3Nm==eJ>d@E74̏|wN<]`$ Py5ŽjZv井jOa% U~7tjh;[ש:FZ¼> ׭j6y'~!&>+dxE'ٹ[#xGS#~a-ڴ"l&GbK(pӥAxдUg{ 8,KFftE"B4jb9b0iz=IǗ>2(HږenrjeQd`gߦ,_|LZЄdYuIVH91|<兝}!$]ēB;Nyz"FMLT qmFr aZxclKؐ& p'5eDܳbo4q|0Nca͏_Æ $G< m ѬÐB.24ejJ1 k!F.L@~/ޓOWh(*-S]m@@ϫXJhUÞDhMIOty"0`cqժ CR{MWn(bN#3"Eq yB ms. WiM*Y1:&B Ϟ2R#*yc2g2Ȕ gnYW9o{-%xeWK^S?r ?u P+MZDӟS0N ẑL(UULBrg IF!(J ,+byVyJ-vV\h![mv[Uеout9["^5<g e>VC ?`\zNu*F}8  t񆓯F1g<%Q /kz1[}@f3~Pq `7BpMΩi˾XǤWVT>[8'-^^)7&!P[0( ^Z.H<7Fl 8)ΆBVgZR"I7>unxcCfH؜)2W-KPjqGrqİ RarGM%Vɚ٧ ES7Nn*|';5}taT^6E*wynڻUmtꙒR}cR/ K),7僷`{6r#|yV7PV wY&_EЍj?*z{C9 X{+H9D{0M4vShHށ(ifQo*.PnZZ}GN;fH|RGv>8}p9P2GﳞfMa|unyYm)Z+BXK6p">d)$4D_IVȀVyABC,+9Cb! PF -7CVs4D7oE`tCHϭK(5sXޘznӘL'NOwX&@A4JDnsBA'I^' Ā3~}Y5=x^ŴQz M:V("Ic<wCuPbЕW-U)yJ梖>!. DG=*TN_j/` oR&G[:}]),p6v$ȡl"_!_t%1hm4Wqu$ٓuqk*5Ydfzo;o4$kS[i~@^ڴl%;bB<=8 +^숇Qn7)g" V0K\p(y9a+'Z۹.bܘ^$EeNNkqM5IRBTTyd5>F'T,s0!ͭlJN.^/C݆<ǫSC-q|#VAϭDxa!lp#ck,8~(فv>UFeb:Gџ+U9ZEF)K6XLfra񭘙5λ]ޛPR_qI_SǕ:1Ȇ^`Srz:.Ϳ,I80pBPh Ae $EP&+.؋4Cp`tuskz{&U^8S|e#Z`>ft/g"uφN @e¾$7nf;-jSz@a#n*S[A.MjB6P| bPf6fPKIHyl*vڞ=>]T[С;^8TzR\w’224FuB~wp'd Fxn+|Ⱥ:i| Iը #4Jbۆf1^bdjŷDE6=G+cUv^9E 8R0ЋK1[FD9vLmvlA8o[FaG8=d8S{CLO/OwJM.MK{̙q p(NN@K"ܦpZ(ClMl \S&j NZY#ᇓȍ}dn'ʋoZx7N$MSbf{vΗ#f𬏕Cl!p//sNM~fg6T:a ʋؘ^doղI4u`-q I*hXJv"8D!~"J/|ۏ.^8"([lw>8SAǸ͘_Ov_z%ӑX+T肮hBuxP/?K&^sT:CaK>t:@f gEckV[t*KK+=[HoյFC(d,gㅖ1Cg ҝe4'Q WcPMc΃/6Jw1UQWBE \1[b'~ 6Vg qJnف%oxWmV [:0j<}]_Zsݤ˚"kX$eQG:qݕ/Z\Hΐ/EFƇ@&P ;mbA|X 0Jv(yD.B=_}f{z}:7ȯgܹe4Vއ+C|lBZu+aHېL8N7#\$DZtÕ`l]XD!ZWiWz%zߧdzƺRT,/tEZAmɛ |ul)g·Q–f/0}6 g4ґn q]1 v.п hū;MNΉ ֌Oa 4v*/9taw)ߘ MywJث񸂠 o<4Ju.~xtzJOV$[lmZƐ[wvxq%zߧiWz%zߧj$Cr<0z|%g̣Z}8LN5\C \P$ $.[zЄ\G6}e*ķ43daP5L3co@Ö|lo[;j>flAS6VI8R\ %\Ϲǃ[e~rzE(0#{s~kH59ȽAݞ;#~χh-y~c-GK1st*.p=!Ѡ2g2d@86r_7*vxÄYztӑiCU^Adso,YWOfhZjhENV)CCM @Pn2@OdFר,XiImݳa:"]yĮ p]2Qb Nn u64t%џzTu?>nC~&y#2L 8ο0,쥫55UG``zy ӝonu ̤,Zۻ7Ev Xl .WG^K4N+ֱ6Dw)&ҹ.,ЅǛ#fp.P*K=iN~8Yc6~])S@|.Y0p OZGL%-[Tz:`g]C^̧Mۃޖ_S F[>MP[ːv-{]̚{h|()BTI-\o(V)l L׳+]ݳj mb4GTU-:KWHԀM&beg=[*BD!ؔwd;2C)&JPƸ|: @;ZGʿ%jA}N+v =fʅtX0#/?܈!{=]߿-3EN@56] ){F>ߘ1^Ā\} {OVVJώAuaIFhjM˒@ @yz22/3e"O"O R+}2USunVky?D81Kbt3hи]:ڑQ[NovSO󬬣c1(KH_$_!2uWe}0}U4 &@\QFC‡YfJ.ac:h~;7zm_=|8/M Г'-w2wgr13"Ohp=ޑa˳%~pМRrYԝdyk.=|2̅"}Xa' A> +Cr9.(q?ɴQ.jƑ4r{lP+|!JA=@>~VZ|pul@Xt5Lņ IbP*ɓZD2a . ٪,` ;40XXъsdR!&`]0u +W69U=n"ۭ;BNO=^ʕY0H*\㤭E/P!Z>rPjU5M,T蓫Kgd-r݃]{%!Z^kNA/ށ:vA ^\1dʰ`GE?z+B' S3Fkk?c_f6Ԇ)E][,:Nee( 46״K){8_BG'nn4kZņǖ WAK2gv=R.ڡ݄_tPS1 #lk]4 ㌑![.fP)ccφ[\:Y{1RM,a24c46U r]f?q?|'@aj2:8~{e;Zڭ";iV†?is옶)_9BRuqy6iwheIs| hv'qs_)g-y[n.}Sane!R}ZBٞ_ƪ4)~ $WK5u;JAьbJ퉸7:Ւܯ]BX E^ #gR6# KJ~=iN~8YLQ2WSpGZ*0;ZX燐a7?'ȇ*-})b,b;zs]U.9_y~,h:Q͖6mo:K $DbŴ:~dFYe&yD 8DʈQ-@}S'Оx m?CZҜ.4w A3[ \]A6:k~! @$.0\RsL77?hSXGU 0\dS)4$5Sdž"O| ьUK_,#2׍U6<~dN0oy%h(0m=w:D[ w&uvBRJ$wY165挂k{nduZbKK+=[Hoմ+=[H5@8\(E@pi[mƉaDN^ޝLOHjr%P Om 2ଛ  z̦gzc#)rpM7a*]؈'(R=|g >>l C`dOhLy7CS0@?Ndpy/oY,8dUܠQ"Ywa3^tP@a]sj'=\%ĕ~U'Vukf'GwH ̄<걥0lp_m2!5Ed, rXA^}ck u֦6rX]+=[Hoմ+=[Qtd<Y ~u-mp^KAb3,=rYXNi(xV@9k> UY_g"م͒YFA2&X- vY6_!RD6Y,^"2 )5W{c`Ëjv'Zx9ѷCU*vk%I~(J~_zW0DiwgE:D[ ?qMn^e3qy_8 X=ŕ塿t=MkNvkaEqdo6QsGL+*0D 4bګggs !#7dydK|m P3`MGC(„хRh.^~R~v)m<K;t ҽ[Hoմ N"xSze|#@N6$,Oo}^ |H-wOJ4Kө 0. ~?{֤{y/q(9sKNzd_ɤ9EBkP#3y\')AF&ޛ%QwAYL E]X\Y;Ztaw)ߘ M4S-aD]>0oB%rQPZۻdU>;ϝ-,"Tka' ,W25.2Bfv{@H}j&KfITphN :UsBqPxW'Ь^~5)S6w4G3ԡ15DJ:b'vgw?yj]'sO2m>@ËWshIΎƎP; 5 ]x'FL "}rX׹>O&lңY>) 3dZ³(쓓\o_#ak&* FzQ7w23aꗉ%_Zʟ_[SVo/ʄ,ϱ+qudqh7}1B沿kPoрP2(o+ 7ByUr}+c֐&?6e$ TC 3Htۑ>Q&_:::s r-Bc|$3dU>;ϝ-,"R~T/lR2\M9+U}wZ3&3AZصgy)`T9?Vpщ!.E"N,.0A?W 3n=[c^7 `ɒg]n!ڄtEZAm}K%ĕ~I^D~- \a Pth?HO:D[ YE:D[ fAK LrU ,+V`Y7-]=pA`*6Ga7YN˭ -僱xKKʲopŇ^QM[]><˰ ʴq+-A#6[wvx쎡OvWirq%zߧiWzC9wB T69ri,N$D[ YEGTe̚724!2Lrw8=—"IP+U-KIWyd݁7o&ל9rSɛ JhULroT@ VCdxAKUgƪGKԱ#`igΡdep ֗,Wz%zߧd=sk#+f͒,fpElٲX6K8/r?>a  Ztn^.Y.Re-2 \p1w;IΟ4cٵsk# do>dw%6mUv.ƒA:y=qAt#1 Ĺi`]ck} v!va6KmV9mP蓣Lhŏv~I/V\`~H *\{zfOoGu7(j#~4 p8LOi-+մW̴nnmG͎XTO4mbeZ/9oS^v=äƈLW֌>ل`̡]zC,Xuݣ1T{]#?]v^NM"_M山p ֗,Wz6+Egq&yʹ1PPƕz,3z18Mu:Y-('D-5]# bq<֋>-lSLSB9Vv:s7:::s<=gJ ˞|')0\}[hfPTؕfksvM\/6Ӟ&Fm_Sd&)4$!,'mo-vVE<|mG[uiKոf9VYP,><;ʣtNp7XV.f7 Pun\S*ٷbn wvzMgZZC;crYxnw $˗,_RI j֮[&X_ T9JI8loJ'f$uQh "PV!ZU7m!Yn;zHd+TD4#Yd>}Z8cKR(ߐ^NH밶k_QP^rYrdw $~ [N5dWcY Fi!j#Ih5sNN|g 83rOz;T-znDK6e.G Pqv/q;^|&X\[YbO%xʎS9+M?sZLv 4ugoQ7xfOP@ GlЂ3?]n'"a5[u-WkLVH8v WՁ?j/G*6BY%}bac80ԣ^f4n|[kYY$$ܩaU@V+0qmV=~67qiL UP4 @#87kcKg<'!zNfץʝQs_;^@F|DBJUlKd%qYg1,d T߼QGFU u~II րBǾC [Xq۶{i D"uVzADbImE7hY>"yi4,(jK~'PL4@z5156@pۺ>tQι-6O0OI6=03{?|BQ΁kD %(pomcZҗOZi3f{I[@ouRpX؟v.hx,-oA*k~5!7{iMeYè1㚞G4wwSd@7TM C: 3VI xܬ>zdp65zl {)p5kduq0eV8E>c9reI, bY, Ԫ}kAxvЦh.ƀ.^_t-J/piKPgUJ7* BIb.>nzp4 T@r g-K6s|<d3uf&=@W}ik75#$` Q&:chjL7P K|F@Xhq|VJi&+~bt tٰT[qgJI&?($b2 .n \vo*gdC<ꑡJ "<5=IG:.$txC]ˢu!A2/S8noD5leoٕ{msw%Bp[ b(aPW)!wXhtb3`F H`(H@̸Æ|8CV+ ZxC{& mCuJSd &n5>a02pфp@;]aeD5wL[qU8R#$Σh[+-? =2t(l3"#n\-GTYY50k)lj LKrZN =N"t.#[˵&JvP "֒B:ʔ&6t[)6Z\$ t6rsWD_3HҽCa\`|҃jl܈|;z-P5{p=g\g~G]r__H~ou^e&z 튽."(9b#hK^J0 ҀwM5ΞL}osΤ "ܝbpiAIgY-M[,B &Gq9#W6"dv]H\PT9O y7']ѓCW LV3 rC#g㙷|=d yהuTЌy0ǴW–(Xza;TN!y[$9 sok$xyLI ZJڝ}kVWS?wEAc\onuFcI ^_Mܘ̮Չֹg1=FB+bu|uQFpwsQUޖI/wF6 &!"(zOw"cȪCmf9QhEl e0)N1Ϊp㧮$!T4T50 ̎eY*ɖ}i5S .q JA6 sIǞS"ACKUM үM@$^Zyt+?B1oD}ԺH7y/$<.zMH"¸o"u{e\;"a#[THQxˉ) 1gS~s#hn6{,!f똚xh\E ;-LEJJpJνUϠA3傒'89,eMqzMԨ*MEis:St %'/厯z^bm ߬ȡ*$ C?YWZzX /(TNWqb!?=&Cʷk(apH;R,8+Pf,rz%ᑴ &VIE)F%oPaۃ4 @W48^˳ {XU$! !nWz 6{=4  !!Nq-kGlȅFV w`.wW\E3dTq,KTE?:9wyТrdmLߌ&@Te`}'(;[47 lt^| <(]=f]bBSg EeCX*4bSfqBg8<3n |}⟰P"b asr=[, we2J E.;ub,ù,ov,K-{hX~.':8F#w!gPgZMJ%Yu(=] TFy|ڕS{8$l# <'l,|6A֡ij7֛R,f!Ӯdݵzh=@Ưf%P]?L6F1ݮRhMm%t9®fVkBvL#ʄuy8H9/0t;=|lzfs'9wBVTmvO=g`l yQ#r_io=/N[!5bdZ`˸]{$S mDبߘ)-M3IO" KT5^O3ˬ>b#">.,N\a;z;=hg>}ޝQ7"De.0Eh$AVzTEphP"!At&RYltTu9b@*6)'={MbqI1?k Т 40FLE ~2nHOxug[>b^ŎY*׺bA6\  8|_>p՝KH2WK&oKJ>m#PqeG4(@Fe$>_ אxP̓tbdW "謗CN"C[4f/E}U#$ R[AemWhh!G P!~!ӝH* iNQoM2$<͝~)VL{p$% s&em(Ҝo)~'~D'\Zm CLYJLsjXL\ӂϊk:Z,IҔ$AuHp#:j8JKYj)t6 QnfXB )X|1L/@:.-H7"H7"H6mwuF_mnfwTԘX°Bق<<>|cg85U5@26y`Ix64ġ[h(H D).ArZdn>15A";EO,&;Fev9)t( 5eJB6c)@0gz|(L9p@6Jx8ܶ ; 2+% xB'^Dqῧki5b^o2ksHP1%\kXW/UƵqbo1{$;qF?EacQ= 'a \TWCAKFʆkx^ GURcw`B8'*"01`(% ԃ*-(Dn $Vȹ'h.^ֆKFipl6fFD0489tmf=!$#\b4m n}gD0^j]rjK6di4F߆-yJ'X1ZnߡǚZʧ8EHϞBy2|">1/)_#AUDWR[x11*g!#a4[* T+/@*2+YN-Y :x` 9 wީlg((CE4g|YĨR=gse]FwI FA.u]{pfGm ?@trD!UNH='>|5ػUa B4B^E6Gg+>%uZM~Ůc0XYg'l.*"!$TJ^uJZgѳQ)' u>/anńR)h=!{$"θ 'ܳdYa }Zf7 \!Mz! mj*9Ljh $tY&QjH<4TҫۧIT P{ D,ӎ7Kژ`u.oN1$[;3SXe.th@|![>ţ!hʞ#u:Tj 6#|!,RC! eWdQ79/vIf4|^, -FUG!ӧEЅLa/ O_DQ$u2t0цSg蘽F.1[#0){U8&Z乸@8_xEZyn145ZExLLhZu7(jUl7-<Ndu_l~«Ɓ?.u=rE|H- #Sn!9FfofQ w2elX& ͎/G5Tӈ z W$z7ܥϪ.%oFfue**Q=i^:[}NAEQsd?'*j|`4QBh_e‚Tj+ɖغ'QUuFvudm00ѝe"9fԦҌ!q"LZJ]h$!$/eoύ"V罾y_@23B2~6ᐩY[&[ՂYekIOPE^AR׏V%{Qdψ ؜YYjsN$0M(.ڴuUsVWЧ9PkJ |JpXh6W gMU='Z.`|&+9y<+^:VE|VM 5:vf8CCًf.=m|xRFmm"$TĘx(yGMe(E>6RaYQJ {dyJB#DA'"$0fHgcX`XI^Gm.lrO=ۓ{dm6ƈ'lhvIA%<Y.Fl#u籀 CV숬rqoV.IYb:*{&wd(=Bz2}^W1M(g۾&Ԅi 8jC}TxSrqг) g\%Y. LhV_n0/Ýj9>OAF¬6YN_[pVɜ)S2O=u0d^xhy|z6u̙^EqKlkXh0q^n2?l+GpNTHQp A9xb/Bd{eq4F0C3O|.V- "f`90>))EG#XRVmg8Sd)mV^RkzoPxG Ŧ\:A/|}n'E4o@9̏9_>z1mp#y}`˿㢍8bHCgo FK/W&c8,sզ _ayt zsAp;'A֩"΢kZTDX!NԴ$" VDc2\ ӠdJD(K8Fp.]D'"u!8)Nu%S}/5.9;Zaq1u/Isk?v[Oc,>*Nߑb,9O4shF{&$w=[Ah ȑNGFCpkwt$u7q)e<XaFxw5eΰK"aqzRpP,^V?C#hM%^NьZ[,dJ֤LHC4ZSWAs({+oj!Ș;2V$ v8 WLgb՜b-A9L oљj/k[4buS^ _Enpĕڿl3_¬''Mp`cZ 1 󆻃1x{LP-8ͺ RN^bz;y|^gep*O٣l)Tˬx^|N?Q,L# gcƏ,w[ ?*[ܴ8̬e9kxS ɀ,w' f#Yh !B6&V>J;fS%dnoIy ?VQ3)wE5Z1yLe6kgĶBՓ^b[GZ?*/K#  44{vmO TB2Hm5; FjH{1wVj=h@^Ekj+_0Ѹ ۦ[kdZ;u( kO wW:aT)^YUi_Eo-.Q}ᡢ9Jq&s_*j<+9X_{>_"ٶ&5wXdlQ~= 1rʝ)d y>wJ:.9`HP A,#$XT4>x+^?"9|?3?/Wˤ굣\1kX,g3yϬ˔o< Nzppyq+3^͒K2nE#DNa\>k뒕8?83ǿxGd EZes N[jfoBPԡ0> > ~G3R[ws sRV/\ D|].i}WV|[efNPȢ /W*9Rm**)dEhҫ6E*Eh=:+%WNuWPAn.<(ʓgWy;/"dXfSM瘞V=1>f;Gy~o?ĩO9l&5 ޛv3%O`P=X3F.;oS|c4{N#m̖d{>gOY9D'$dAzp2c|sjU q:6Y4\RmQ=^y 6fR8 ZM܄CbP-ƇM 9mEL!RJ-'RFf2tBV/ZkĖM@$?mBaozRT梶W*Vdu ۃI3Y_.NMʈ;ܧ\]d4;TΧZn=+G1kw"X!hJ8͟bJ dWl"a /2^Cު sρNIC݀<†~:_o}uj,S^:AjQĩgtT/rvoJ#%\dj6on`gz#'Ii2?\@E/!5ku3ޜ]S2Q xsʡ4fe瑋Z^ārFD95A:՛@\媈(>3Q#:Kw3F?%miS"uC$2TE61ye&e}kh?&ɐ GQ.9@-JYv Ԃ/t5-T_jl &|VSZw 8A1^d12Pnv p/72:; 9zaf`E&Jz| ^ӒDW?t?Z>atCmXN˅;UՋU+F[oJ KIm(22}g8"p-%\rVGMxWj ]=vJÜReŃCsN\ʀǒ*ڊcw/,F<"K62Z9%FspġYi!~Fj|4(I(p@0XC1>uҪG_4 AFת,LiYW;i*jL뱶 Lf`X )ǑޱX{ؿt_5 E9:QV#.b贉OE$<+7~n_DՈ^:yxۼWj^J"~0>zq CSyH/:O̱ %z brXh﷣ZGBUӃ/YkPRRU,\&ֺ:hovMNrt6},*N-4&]@-ӜUdaZE ߉K{H@GցƜCԕdg˸97'<24/ճaH9 HK.}}3J,4r}jHʛiޥb>tg$BaZUz*%_rpj(N͙g_*/gpg$Mp)ZyV#ˬ Y;WoMZSa0OP@%:|z会0&Itxabyi[iЎl>Nt]~n4L{K_lfP(Slbɝt 0Q.ݜz5î\/EA,w'2S,Oض;W5Oc6Q&J̽pQz.J/ar茗 exeDA>-TNX",r veQĕG`0ip =`K:e3DIJ.թ_( jO ]v;]iS fIUq{D^J 48s4-V:p+Ǒ/s_Z`U GNؖs;Z{ M-s(}c|Sb36yTZL)[\'+XTc-=kn2͙2I_^P6jDj{y1<1?NA6c%޻^ yDnVlaDƬiZt6f@޷,ه%Nqz' Y,؍nEo B.: n41^~{IŐw-2{3^YBm_}2`LK ǯ^|aV!2͒,ʅlW>L RVt/d1wCwa/7*MŎ3}Bʄȍ!OD/چ}&Y$gq'~B'uDa`"Ϡ:LG=mXV:# I-bPh-ރ9vFj#MdƢH]I K8+s .ie㥤y՛)nQ=V*dgCLO)x^#I߹4}ϓ15~qN={"&-4-, .h9)^4Rr)&&ҷ < 7^\}}Ql߾~cF0 ,*7{t03JGrm{cE~"ٺAgSWziIq)qw۱V kW 񵘊Dn3Mc}5V ;gѼE2Oi Y jRμXCXٍ!.:5I0QIxCB)%ֿ )yiK}/ڗgf l^6:5A<'sWUD %[^ΡO;ruS{Ʉ!}).wU"﨑sqO\YqKim<䩾P\C<-l]>Go+oom_g{}ffg*dEs3|@Gs Iї@OJqT1`5F?s*Q̨aG+I;?>(d%^l7n.jPND-2M~@-@HcJATXX9@`a>r?("-OZ/d fŋA; WM֊5"B]cwByYC>[K^+˩UY͸'!ƈ&I`FXˡ94[kqgUDhxQ >W?L&DٴG,K {&x_$dD9EYȣ5+xڛt9KfcAKH}@WD>Yq.L1T6Ln`N/41&0ߠ&vG1= qD (ɬK|tND ^4Y%bu}Z|GSP0D23|}TlA\_9,QYK6G F+HfdqSXۥ_i3=O`LRAH B4*3sePpT_: ^cD[oӄ; Rvmŭkޙ&KP 8B@kDm ~]}&SVITdᱦ'|/ [Uۛ'*7D#F句^4+ZW>R]`KCS%`d߭T:OͰIɀQ=ͦAwXaxr;S!yr3]3g0.X< O"Ϩks/&eǶi$)0OhӮPZc:32^v~'E9w .{A!=j Q4o1u#F;f&ev-0񁴆>F-t 4+)S UNִv]v Ь:}/' uG;z fn Ȯ{P8 dᬇ|zy}i&`bEθ(("1ciecij%4YZ޺IN81[mSiŧrQ6+jj/K0Q7*n<=褦L孰1G]A-)p:sFJPݯ̛ui\HPv{9Vd!'`tw%'jM*;)%11"1[ MqVj+7>-j.Pϟr^rz-G(!;DZg*:(YH|*vhіG8Ha"|tuHA.γ,x{QF@>{ksR0~ŶELiMH!!|z$ڙύ{w̜>;#!Ff:FM#0q۪"+hS736 U=M` T08 *NݥN=l!{sl}E-k *_ "X A 2:msҥV); !;HwU[X@T1;L[|&ݛ"'Tp ݑ?/حLwt#{ޱW^˲鿏Nlmω#5Uůּ|F+:ʩ nl8 :;3#.8ͷ̂|\#/,Ը QN<5~A8N6w)D1'ZwV T4F)^_4ߙWp5-_Ϙu{p5`f,}-tSƘed CJW4!PN$jQ仾[C$zT7C/@7U ,{0xK1̉zlTw=ryiJot"&En Ȣ$dkKbh_<›%diQgnIMͪL şpi/N_AfY4N%jsDeXDA+Go脈I-zӶwg(*"?%bP GM+`NS b. +=~S?z$s[-bI{lpA[ےk7bGN_غx #W$S#Aqp%>5a{q;3 Z=,eZ3r'kT?E;#4ޟkO&(qb+zB~KuǺBsSsl'W`1Yz_Z',v i]E#7]1x'3CMAl`$n:P'(פ}YVaJ{}w'D䌒hhK.VfBdJѢU5`J SXGfzzMHG#/ @#`=`FFcB/q&m OU0)a EV 'N!L~F5-5_n:TԳBYde "l8O19AA N|ygQJ{߄t>JkaҌ_o1IFP-\Agtb?Ey+ !aBHv6)'5ҽ⠘I1JKيFL%!MF8+S _|Ǯ>in1!m@LjXR)Qw0C ;޷taPd zդIfe(x-g[΀ 7Sd?:*&.X  Jx j(imsb*TgQW5*"dvAT (с|4Lj -М5ܡk8/ l[}Z^cS1ʑsVBmfQW=߆J |g$Ot?O7EQ,ca(֦F[&lSɈ_ (Ƈʢljն}@?RSӁcEVs]Mɺ1"I,9 /H!FB}a$2FEI?% M/IM=ݜ uU.U9܌K& bIz8/ڌ(xx:/{v^N&ٛ(+$2(q^/0Ϊ=-9l4,z*V#z:C'ٸ9? {fzEItD-rvZ/b'dqeLY.KWԟmoKxH\"# fp04؏]e@א8묂IZ, s)*w3;0_PR-ZIWOqt*X]PaƸ ?khwSc/g+jG'}\hۮvdAT{{mZo![gg&k⛙9V{qr?yKkF!vr! -|0KK82f)MYz{۴YޱۊQg8.)#`g!P-؝(Tt fkøxPq:Dw_9dd |Qqu!;]w":%gu/9+pO&ᒎyiۇIbSMIHM 7YjP0GWm2{D ÊH0H88ڎΖ@Z6cC8l9 چ?|Ps q`oS[RQM"do@A\ >k\ /q䉥\N}aLs%=\~87oG&1PL[#TS[G^j?H*ɺ}n{۸nU3#xD +MĂ1|]-:{eWI7w\L*VL3[g+FuC7Β0 я7F!6ҁ#%:wn{mT@TVф:z o[XscK=ͨ۳u>C.&-;)^7~9I7{=矰`^qrtI5w*Cq35@$ LdhUe uUw\(Յma}p^Iʇ|S{eYd8B7IאSC* sŪ -v'UM;9l8;PYBυ@7S,qcgK^m;D@{;զ8@ƼVKQaF.ߪ>= fX 걏u Ey.JC2O3`Ց\9diiJ}Nn"**xFdz!_%Ђ٘j|[=$^=gИ UMǵNHUZ$1IIZ,oغWg~`Nm)>6VZ^-68ܷD{m6%Poן5^35CohpvsߠjBqAY礋8ZP9rdƍ L]!iZHqt6uQPXe2<_5TYi:qC0G3Ohwixv8daDo7[Zz#,sPact}(&zِn5T|$V)}qc/g;] 7ȴKk##V*. fsQS],P{#_4SjP낟^{ZN4jr䟕8˓O`a| )X)u XEը\x![5wN0 _֢pY*!L1RB!I~$N;x%,MЭmK&הjɠn'a/ղζvjx;шC{rsՙ`GTHr{ܚz83dNX`";kѥ To)zmf>mB;ЂE4-y2wߐK+f #Yr!tT5:6Ƀ;"}F.5%GUrk"8!rQh`_l9E+92p)=G!]X ]ʸ 63,7KK ё)qIV7Hᴐ[&[ OOeN(q[̓u5hWZd5wi2d9S81i9'4]ǛZ poD59 oHSv%XkKH”4ݥ_`iTMN̈9R 6c焛ʳD?!~O 5 ҖW84Yzo3BܒQ^#(Påڪ}7<3k^PApcI ǁ~h3@כ@N ҘU󦘸@ dۥcn黧= +=ɐLOzM~*C,(Ux#IX v7ӊn ~ȯV; (iۇ/׽gi/- }h`e9˙ޗ/AJ$ET|%jO;mÙyo|Ƚzݕ h(x&<tQ1/w A.fz2׬Sp1yIӽl$";Lgnǁ7g1UKT bkt40˾AO4 kf bzImU0>:?: gb0AlJ is%)|9\/p-sXxu4WnFvon vei\:ml[ 2Q\WcC@DB4!κ]KphޣU֘@P$d8"3LּvͷE4ip39Vy(M| fI8UM]tVFҶ tbh\YaO,n0Utwf>j7+B$-Su% jczZrz>80BrZ`6LR\n3Yʓʪf{arxc."Y UE]oTm }#0-?Kt168 ^Y5ehNF]8~|ġ'xZ,jx|3viR()SȠO`ǥ=W/"W>~JE[ֻAjf_sl)p:Ң/y@1 K%I\nX>{2~iBi i+!h6b绨o\*(os%N6QJ>8UϹJ얬㥙SeEО$i4஬?V<P߄NOV/Qo7z 6b+Ok!;=pW;V3_[T8$lɷcE+N5Yx$<2dslk!l*SU, 7&֠Ӊ4wRqQ35l>N nNۍ5{UՈ L:zX[ޅˀ,~NfkGҼ,8g7Zg7a߇;ni =huH[ّyx,7=̭3ܯTAOZI@Le{ Գ^:a`%IuҒwoˆx%GsQ/VU6D2&y Ak.bcz5<qG1({0MUce5R"G0YxH4}k3Yl|jOH͠Fu$u}Xq(qT8|HCPwFj: 60:~F6!@XWzΊxs-ԡ?hEٲR9$Jn6(3Hw'iRS&|iD5C ZArR 8ۋJ)ZuҾHhtJN<7X~cATNi:X/y(x pKCX$'o7r| nEuD]?98_ 08(.l>!`F1( MPv܆sUZ5wĄ)x5^0^5ʆQ}4LCI:e Hh@7cdb>;KLr6̧f&5N=CLޯ^dN>ZNY3#p8< 1w[0!*}>;TtlRBjʗ4*gF0JrguF3LdoMGֵזQ{.u/Zlώs/$&ڋ-Y$Rx룦 nc5OIbإl#H<5׽hDy槃趿/VY_jh!"\P<cshnT XӆVę9g*%Xjc~͖4*0QtRIH(DYfJ P=|;FQ\do^e'x@Iq ղ[LΥ 9O H`RPR?O3>9ދ's'>iDGPAcB 5?01y,`s еGuDnǥYf4CF\brk"A4TPZP;CǠz}<&,hŖ%0 &<^hbjgˮ\筄pcK:TWsIJv,;C${QQ'Ɔ!w>K#i=]9 0x #(Ҹ:75I8b3(EݪΨ>NS:rALs d*ڥWs{.?wIqC{=w d6qWNt0a sy\ N|{}f\RjqGQ8iX@8<(̖EP(Z¼i2uz?ՅxWM)HK:-d#{`% _3qm^/IRmIٻn\*TS2T H4g}L^Kills7_ȷ_^V;cl5KÀKa&= +w 5Xe@F/,v e\]b^> wJ(ݻ(΅ԱO6;,W?TV/X<"okɪ$:=!?(s)Q♹-*|RK7Rb`D'շv^Q#su5іj}?1Dm3-eKYCHBz^tqs&}k$W(O^Rq< % fB<t/xHLVoa&e\Vh&F BX/.FG6M$Jlkjq Ba؃sV[Bjk\CyF*~5DoE9[p;n:Bc= ?V"Cp CkԽ 2Z_y'u]qش~ 1Eȭp6fäH><:6;t99aؘWek3"ORTcjfu1,28 ^ 1kYu KQP)=&c} ҩcgW6"xmQ4\O^f!^GI&sGA @!NId_+\0Hpl8 qK?a'ɮLw=& Z%gh,,Bmy`+XF%g!!I|@)Ȓ]a,qk~9"}wļr =E뷬߹v6[{6|I dw$ a"cNPM℃uYeW+`<ʏ!6骩n $NbAweV_EbV7޽U-#tyesMr\h /S@l~ſhu^l8X Tv䱋+Ș~+Z]0%L#0tUh_#˱go" eQ+9dk1F Hp,W)VL}APG](Hsyi/df{롷 HJ;u!`o -^yџF9^9GSPK`rGȨE52O5m鱸j+MմBb[s mʆ& 7\. gPe{!_w,p;oA[UI际-:D bKB[J.c~4! {>ҩct jC qUTrExmEA:iv3yMbM={ϊD: iak%P `,mgj4p%l)˹ 3Y ZI.Տ' TQ^I nyQGقKb,"BEXy򎏯UlCf<1n<*.#Z !Z#h4ú H[- ^XÃf4F~Z쥂WEҗM͛WEt%,\j91)4忯z 6DޔGK+fPFn۟PStwz$k]@M m m=$gz9Y9}?zxy㠆P '#N)׀1C!v)9oFT^u 5<^P2jGAqHbfQR>WbpJY&z2C4Imi1GCX]rTȱ#n*$u~Iu!,Ц։wݳoO[c\4&1BWxc,aGs@ \$MۡW30 ȼ*sшEs,GҪ`pl$-- ގt#D8zk]Ʊ~Feуh;yYSu4_vi\F&uY(x|eTCojqGntۨe GOdXfOzvVyR 0/|ֽ@@ۏ@уB 5<&:qvp.ZE< ק5]\w,9 N^p%'w>Ze9$JDȢfiJ[J5(QP[չ s8O)V"%|XRH}u2&n-4xb#(>ʆd)"fhm'xjdISX I)gYE|$*~/wca^Z~87:xA*F|^4@rPƫǮ'B_7nÕ݉(<,ޙVyj/UkҰ.Z|d'ᚊUoQl?YZUq j^9>hF~ԝfMk'w2c[Ba/_̬yp:RVy MфÖ,-f1&lߒñdm;LE$0?(t9~;9P!G6eO23;#,94? vrVy'=!ȅ{1]3r_z\ :Rb^qnx@Zo*֚I0If0 8+l nnu0#6sˡ`l!K3/T Tr6x&Hf3J\JgnT2d*dTC8cu9SA~8}jOk rEgš'G@ 3J(teLAaI32,w1jY/ jv%LP: W}e}&ZV$SM-Kq3,: {2PlQfPxXS@\ 3|?ذ-@z̓X, [_*Kr69k5l7Ba/ੁƩoPz_4 *>v`{Sed OȲvUbk{^eJ$"ЉD7FXǾq9T`2 ORU&]ڪnd% ?_ _m.} +ԼL&a?[pw@E/TUL\i:m]3pAG#0MK "~4(Ot8ɝIz#bS>n+@1зjcE<}c(M+ |^ot,=-~K8oA{d{<- IJ_șlyPi (DDmP}Υd6SL`'4X4R&Z)pZ92|5nDr+YBB'vO $ x.ʆU&8?9E. P_ 4=^XZ"K·VsDž$8՝L99b zM͢R^kxmuo4߲qMcD | ֈD DEF-5OP^=2wvD> BD xQyJ*8%+тfCWHeac=boW^uJv϶< >XZB(wXF.;nͨ>k䏋MI j;뾳bL޻uL"?2@D3w; 獱N LXmT CpxL ¬2Ķ8"ٛj QdŪ>_aXUt|?.{ɵ& Q{añ:U GDպȎgi_3iT#-x1 ٷfaS'p,C5TѶDHdT)gT!)oP0x}1N1Xpy"6\v|ErU"# 9Jw (l9%Zn28X|a; sv G_4?+e|F fa鴢Qj]|qtmse"~"9qCѥQ7O _Oׅ>mot{0,F90 "ZUJF8Z Rpb :Y_@Qv+$Ρ&c&8=tˀx(mȂrEdCpy7 PMG&/!͔rq;RSqÛ6;hފءD7 9;M?0ws ?cІUBEͭ+v}VjRS6+TOWc^hI?\;@ݚ2"OwY{i=g`ۍ@R)a@b 9f0v 'qh#ӋeqsTB6ێ𻴥$<d#FdNALsMv N3x ̽Br.K=]-%K|珒Nep6OeCQ֡9*ܪih'=n51c6uK۟* E;̅+п}: % 'svq"T^㣽п[U ޠ3$λs6AKMylIP`Pq[4Q J:78u j̡Ŭq=6#ɷ$'$p3㞀rmlt"7" 鰘K8HЯ}G!1$H~GLBnq-~C@>; w|7Eax3khݷ27ʔ}zS&(WgG%vJ<jZ?:;4Z_IeuQW NZ]):C`,#Лa!x Vg*i+3bꕨ2#߀n亙Q+Eꧪ_vn͘)^dU*pWzw7E[.y[G`z@'mCdލİhJ[^פ2D;g}%pp6 31b~y>hV'kז ո| b+_u~s@oqpջ:ճB~d5 3[788xK@?A؞@WOӾB4֏ Mܕ5 ln=XtǪ.;xdAֆqAÇy>t!Q X 5Uwsbīg{`bl6g?D E,Ѫ}Wu{og;HMI{k\ȦIH/[Y`I4!ƅ7y"n}*bcSwCeuP;iٛWB9FOq{@ YIZk{RVYv6ʒ[uJ09F"޲~3:E; ,֟J{x_zmT e\' jQbuO, M'bi?=<1=3x Й$2~w=hKG es ry?\9bߟ.p4ޒVF_V{g?}8YE4/O3mzN ߫BZN]$i?5( L5Z, w_yg7՚N:]bi V>}:b]4DPCH ?o SL P.dU,vϏ^W滀ej]M냪+TIde?c1֝b͉mwڨ|.Hw?VϚoH I_gU`UVѴVڿȼ9~<"^aV Ȍs; ^eY"Bڟ;m;yaDB+,ؕ*3ɲu tClÌ 6swlo)L@~w/fM@;ˉ3F__q81cb /0($g֚u6'a+W[Wp^8%>{Dл$Nr_y&HgerrBbmO-K6ϣϷcV2墜؅&w$Yh.ORN9eq >yQm 6rqm& +3Wڲ1N.z01/پO?v̟`1z$ #.;O3Čbo ^>_^s My4s37 ҁUǧd/9>h x'dOx=Bcm!R=eoI'a௄ &J@Y. , [KyRώ硟Ь3QJj:d|q>= a<.YDm{,t"WBmVo> zOf+0R5ōeN[<ςfFCᆸ"D\e62D3 X b$g7l?aFC/sCƦ'+@nƌn~Gsx+ k D.O>̰H0ba>$P!D ]Zdz6PmQɖ-'([6ԑs0Gu/=0ͷe{ {63?kpn3?"Xѳۑ׫]|ȃhnjB4ܶQY |[h %tch\񅃆fi0Yݑ/&waAXw$e(|Ǭ58Xo~&Zzj! ~ nc;Q5OL~(x(:3(?_*0E.TH @wCw<}x~:5Ǽg1)92ar}qÚnJ_4oyQd#Ѳ6ÍcOp 2]9兓ZtC,sþBB|ns!+YlX gHtRT?DyI0gW?A*J eR#akՕD0ūQ$l>὎`жT. ̒)a?;Jk]78vV0)S+%#Z EE0s@!vp/:(X  Zk&?ѻ rхbK* r͈|os鷀0)aerҾ'CJ 'nz =6ܜ5Y}_l4OWţYL7zjTlk)Rb/_&3X[.݋'V%zw{fS4%lq?װWg/tt@eLJYJF6@W쀙|:D wihd IJ|+˝( Vgk$p)Kx} 5[Bi-"4NjnL,`~ga׼KA]TEgtbNoX)n-7S_RyS9$.(o_lq\gȺ~ntdSZ-A'7/*َW =2s_ekYiζ6DS^L @uc@.tѯUXE%9e-@%'9qy !jșAw L 6ۯ>U%M[gT *[@bm_,g":K๚>ʈb ˈ INZqW6M`J\ikr<! LQ(fNkП [(h<19ۢoF:!;'؟cWSh\]x=b5 }xU ߐ~?fgԮbpP`n͋ ؞A?<ɺi 'K>XhdWV6/9kGjCMcRJn{cE-˛jd<@6c(*s^`l1&5`65MfI&1z2 n\Aqr~_9Μنy0*&R- EY+ekɜv&S^ßV(Iy7/~R+ v o=^*\?2`jf* xHTD!鶡 & QY2k%c#4FrPs]c n4KR[yA;M(4uA?Cs;6}1 *c/LQk=\A!M D.x?, xP9 . mɖe 5|RpUiJP;0fTt5Wb_N||:dxr w /ꇟ0 i$D>ӅI rF t^?,m(,J$l6\{fߓIn|:~<{+62:]%h:*rܒ&U6mue[ZYP{P!,?(&iqӦ*%-Ycff,w$hGZ.G'v z %==FZ<Ø#TWwKh sc*SR&Ym*)ob.GJmY?kB "iv/f,h"~qdvI([h `p͐XC*)ŅAӢ^! t´>F>;S7gC~lX L#ݭ,ÁyXĥ̒裧xCs#aO+UCb-J[yDsag^rLrPT _*& K&f & s8^%wb'OͿUrETϚg`mɖHҶ$?ѯ!@J&V17N"27ugbfVBz -|Z)i< @Fy K?HA#M7*k= coS/A@QLC "Z-OͰ1TU\8p>k@aVxW"Ȓ>)$1^%ˌ+pe`vK ړ1؁:)S_{bDVġZ*&|v,(1Η ޡA͋1xd1 "o\kmEL@6JH2T>Z mk[R[=wrѫUڤ:_:c@n¦3 ۣ$LY\j;5{毗D!OLH{;&0v`G;Mpu]߹]tujenw^wܚԘrD_^1óCzhdsuyUJDru`-cx d[*4Q]{Ɓd;)<332X Kq+~0tݯq9R?GY<4#ݩmţ]ˡ4tNnΤ]/A4 Hr w[? ˨a/\V)tcMWj ZST#|@$b;]F;7U+xLA{b>@ Cјjz[+$Sn$1ʯ%Dc+Ff7y8:Rtim&!}qPҔ,m^?罂d lav=o:SG bR8 x_RYàNÞE x&q5\IL>븰 m 0+J>H3ڌU AmE\ibh8t=P`X?z߯ט܊38F3I8rm-V}VEamٽvkK ߚ?EWgqs:`(bu͠bPMsk魲ggqbBel*Isl-R͐[\C[7Ǐvüs4$&3q{TY3dC~ ն*Bp5O΍zdBt3 1+XoU㼙jqnYB'zL&>bTʾV+{nGLP,h=3$) T}Bg~Rӻ>ۓv s^z^}\wש6/$̭Jzu\n AQ&>µrw8Etrҥ ?1%zǂlsE~1(,lwzO`/;}f8{ $!WS衧&\?xm4CZpq/a6s<}&t3ggGQ:= 5G{ $P`Cuyzl#[LY.sgg|%-,>QpE+$E{EVӺjh̀k_EMÈPnI6`u?8_l@O"6.#(p` 0TߕKHws_ѾwKڦßt,PрM#OavY Y ۩6jڜ $5NX-)A5recvL"yl"dN  qB/nH/*" X(Zݍ\Gj8T/Hd 'WE2+W^ xY+W^ԾM> 5<1[5*Z] d NLW$=֞CiEd)n_|o5F1fbWz6oc%Y_z ~שR-Lw- +z3NV8V9|=iWJ eg:Φ'FF(,KTx[-.X`_\X% e m9֘#AjPWPOhD%Ht~El%*jl J"Fv" tx]ՂqʺdE}X!pVsZv M_6- '=Ha:/EuĪiD39j2 m?~hb?s?lG9L/e?K8@_&F2 >xIwMRR'hħxAD") ;ύ~#"XNY0P"+;BazAv= -],]M|&QAtę Ec{:|Xz'ߙ樔k'ǫ[GWa ̳Pۋ%@.u IGc(2"́~J`FUH(GAp|{՗m//%]r=x%Q>aM=\* r3Ecl.=l`)XdL5򢗫},D'oaRoG AxDxͳl3N.֞Ӭn=h}v-:U_;J&%!jZ*"ǢN}T%2]!KlS|v<ۏC,yG 4^Q>J7%3&=y {1]]U넎M+`)㪽I*'+x9. Na.a?G+/@'kY#vz۰ о((s&?o\GE~l{{xS8Rr,{)vKPrXϠ]e5{9vV1z-NK-(bȦ[Em#Kݧ *ڑЏ(T]9|X}A,TI*y4;`u.}ىDz^;{͵6N\??hd/)l?W!1_m$CX&bNFإwIVq rh70="O3)_z,}.Vٱ 3p|呀gEql`/jP2(K<$+n~A`օR?\`W[ԅOUK~'sRYa%5&FMuiSv(R^ >/43&ܯ95얛*P9mC5bnjW׳Uzji|/B4dbr= )~1GHh6IyFӚ/@;-#EQ/UPQlCx#AXǐmzs-jfsVqY\)Q)+'Y! )>傦?ڕ[j*oN6"&3~xD8da[ C1Q- ҟRm0+&tcH3\һ?qwN1#crʒ6-h8̥yg"HD& 8:8!]Qׇɂv)drHWZ;; j|k;*o)I'=F/sP[-]Q<ƫٟq$ArO:o緭95$fe+j%R'fFtecEoj@7jj~uF[ ltRE0ö*(zSnE,PGRwܮtUC[2%߅)t[/Cu6֨Ӡ[7~79Bk'a[l4ʘvbnk`$7Ζ[69pp.~xQepc55BiRAQo-c2寴/r?\"Bd@L$扃=E4*0{۸cES~~o/%-ZyYT3C|KbM|D^ªKXyÕVaHLOJ7_NAh6O ߔu:sKtepsb!40t^Y|"L[h׀Ɲ'B.U'Tp"R:H$춌^C>A@U\"븻w4AyfL]K⪵#@}4 ~<Aႁzݱ ̗Y}\Z~BǃPP̕qeikV{jn<ksjHd] ~-@xsT{3bZu!1LI[v:Hg탷j[5d, ah?M%V@M qr2AĜ'0Xޅ(- USZme5۪3TзlX)x$xy IJСwCxbԯl_CQ7U`Г{ Q 99Zt. ] h!r̿P>uKχ*d!t>NB;`#fJtʯ:C τ|CP]>n%6'uUhO5.X+ a(GR8dos 'V̈́l oW\B5ബh`>z/*^H۪_&/$΂\HĻSkX2!AH_!_`ıgF`V)@*~X|Q $׭Fɇ9sz>C'qs\?mȃDZ=[~BШg#E2X4C^HNT1kA픁jmֹ[oLD1>g%ҞTV3Kl\+prL+.ퟒb#%ڨ dB·5jSǝ k7V΄Xp Xq?^,a&&Hi$c)ѕq,vaveZj&yBTY ^g *J? AM >&&"?NRbf58DHPcÿxOi1X~S G=iI\:|>*ܓzI㬉vg.y,K,+w6mĬ'C; 0#`ݢXVGU7.ܝ-6@=S~ȊLgE}E31Ni+^ ğKT1d15;9~98n#<b4+1>NPLG"<1<%q4]slngÞ@rHwiq>.}~kO#wJSJ}Dž=t`8()wޣe5U&+w&ѩKo6NXőmy*PB@΄17Jq''/tZwQ3쏮7wPzw46sXR"&grԞ D ˜Pk˘ kl)T'FCQ} 8=0da})aNsB2_W{^#^C?5K싛KUۗ@T8q{Ԉ4mS},Vqn-shzMvO9A}pSeY/+:nFy,H`är}O} gX{/8P&N':d=UR=sq}6D[zNNY^i( zQvfPHj-HϢèOmWIEPUo{r0O#\*fh9FrFkAt҉}:a>JlI `,0KUOiyZk߭Weiurg^s9j-19z)7˒ !o  [pp|n^h/1 /&P! *5vpL˪7n`_ @u& (ȑqUR9CwBl#1jK|ª[Zr+B$|Q7f!r ;a1xm?blZ /dA#0᣺JFz#//tPOڷ Ԙ?k1٥[,YK.R#HnQ3#?WL;.ܖuZBᠣlxL}Cm$`9Mifr;dFK,Tc?^nȍ}K˵ c~Ͻ`{Z,7y jͲ%X&1ցOXף!M; Ikut ^5!L?RVr3s'^^◳B-0>rb"=v  /sTtXJrdVKUyeq 0V8y8K&pl+**);0mXv9@2mM -[izcbOb-ՙed̠W*HYdhƌ3k'{WV .\G%ջ)!Huj`2a7&*\MT^=.LVS4FkDGM<ɏouPo6a4Iտ>ߥ}02z\bh͐=vZ"Pe kB2J  go:_:C3  kdf0a^rhk5A9↓`F^Y3HqT>rr_Qԇ{\6{5Qi]ApkD$/ajL0\Yx,DڒPn#LkIMf-BDd0ɜ논]'sp-J^t b/+#pC ^j&HS;VRQT ,EaӇ2<@hl BTE_4o8Ǭ٧=M :6LcDdq v eZ*w|Nw1;@N^W{7' C>¢!JCu'-/Rmk>ut.)S9PB6܏/XF/-P"dKVk]qU'3JoUoK> A& %j$rE|XR&r}x; c)s_r类 3l/L̮K*K͇C)k04Z8]|8^ FWg iƈ{cH4\9Swgb/kqvEg,mD6Xp): |w}@O1BY@F$Q}JkCUh)V_P̹"oP(2GblôS0(yOL}@}ۜd,? !{^& -Q^+9P$q:mNOqGJf\Nf-Hƪ7Q4۷INݥ'" fLg!a)ߝ m22P֫u\U)x&Qq(@3`bT%=#jg`'jkw6{_KA5WQ,C+7\l?q{yïF8 + ?r<̀.݉9Eߙe6(L41רPĥKۅntoѶ_D,e[DhdcZ\t62NfWto~{/o G퓯`IsQ!#*coG$!8Yf`ǘ]3h^4THkL"T)\+9OX ~H_Nz@р$RXq/Z|@_N 3BcBD51_x`ֆ,NC+Z?NJX<? o ,2?$a0@ͮhP75*YOSs~[{Mc1 7{ΞѪ Cϒ+bk Ny<ZE7TB = t9tOQ 6)|7WTS_^a,.[h\"_D-N6cÏ9*(|vVB}ooU]J#z1#T^Wr+|k/KyaJ:ƭu5ue6c=Ve.(+1}~&v50SI[ެO-Cq-s9̃<668ĉ`rb#Ъ{b0 e|^)޷܁uݍR@-[=ʌHq +keCY8ù)ξWvR}P{X"Z$ Y_:^Ru]W!boW'" %%=E)7A.-ƏiXaL:aNCnxg] 2wkLw3^ :8R„0+˚xG #Az̳Lһ= oX~]1/U_Yk-][8!;wf˨ԅ2%pB*m@_w)}9lwjez&۠Lb LhGss:GR׆H5Rex(\Dy) [EUx勸4Su$ ˖JID8!9`kzȇJD-p0 kfs&qԣ_o)D2M.p0i)9ReĘIϾtEל)ڏG2wt-}זGsˠms2k>SLHE:5 R2 ŕ@\&&}~h.Rp 1 ~%"\x{kD 7LAvtUc-^Yn(##BsLa$[ߔ_^YTGsp5"-<*OtlI!JF"x/U ac:?J*6~*%_5ȿ]ϩ&D-#[MMDTgBZO}Fzh(EPBq.޴wIzg"+z*-b(!`W,h|䧳tzfs ~7X+Y@$'p!;jDZUZ#Y3Py#ȺʴUu:88<}Mq24ݼX൭;EXzRd"䘔B\_nu~n 9IH&@&}J+Nv;vpQlhWWteVŮ.\Nm 쏮7yKiZg웁qU>]s-'Ai*WWc `LmxHk9G"蛞N/5o M}URcn(kXڰFc9#7 0'X4pT%bJl,e3Mb|?7(unl&OUTA3)O7&mi;fsx]$5(j/DR8 7CѴ4~iw;׆ )f&IFT0DJ HfjTmV箯l'9!$[" 5PB GB}$LEf.:j:TsNG .kÚAGw4x)Vj/ Ta ҠKЩ}f38/m5h?f$Mzr,k>: R$>qkA&̚6QH-E2X8S/: |qE#̫_+uc~'cldTMRՐ>"΢~*C}A+~n>JKp}TgŽudf.b'p/ uY$B2KmbN #"K:~4V1 lXGWf3?[{\7PM,[G qb]k(^Nvw[υ,bWEz[.Q ˱ݝc:!V蚙m6.L:̓ Ks۷/q߮,w0*֕~͘"յ2Ҡ(4aAsv͘D[_ttry}d9WY؏VAF\ou`AZ)YQtspCU0n{O^Ԁtv^ tLpl5'Ұѿ3XC-D;.ɏ.|P0)KA'SBTED1Bo' *zNbɲQRj['y3 G)0Gupw_<6,9ݨ5t;0xbh P41.-ʳَ߃f X2#LK$֔ŧaA-T.s(a\wz1GVB M@׹0[AuN7gOD,lig}ֶ#~FRQ$SLJעe6+"Lxy}&&'7:^ qw;l j X?OACP;FB NT~VAR/_h`Φ{cnAzy9Qiަh=zPV5K3"Pʒq3M o!xjnt$*dOa%9˭Tvx'r }C9T},$Ğ^FH9AR}gC"߁YUlNz7Fp:뼯I(] JL~؜2܋A8oM]!c- Uq8@r^m/&%C \2SHP=RO]Y:<G 4:G!Gh5P旺iքܠu8w]Ц~g48OHrTNlm1K\~8'{HPQ#z ܼ]\ͻ{ NQ,5M-.[rgi$|袲(.'6O u $*>34{p( .4/7K¬_;c"ÿ񺕩! j%+Z&D(N]f-j.fg݄۲w z8r}rvJJ6{h B9[~~p؍xUУ_)ϜP79j:aU@pQ)O Q O[ 3o,on?AFyba$m&}V m8yM$*ij S8EF};8ŘhLмRPI 8x/ePR*[Ó@xT+_wԩ8Tj'Nd2֧zFZ2\fo#} Un e~?\W7-2JÅd iʽ|xX$P!._WVwLp3TNH>R$3ɯwƵ+ѻd92/Ph/玄8 q%G, yP~wz+E@=}KOxA,`NQ`jCcR3w +a+*Hux8Vȓ=ӺjG(TQwo}D&g];VbMj\qG1޿NWOq#:bz^oV L)^' W[Op%I%)^%i'cTm{WǣVRh9ܭ)j4o’܏(NV>ƏC][~ry (ɇ~-|N `6=cv{#:YT+czia-'AnYge|w>yYEK1) -7i!h/C+ۅ5.F{0w_РLYWМ ɍ+0嚬_I) KֵNOWMK &LqhC.nƯ" 7b=w=H*\a7aoɻlb4cx0GTP:>' 8+@dA3uޏ/;i!cIzS/qO`44t@fpn&,5tkྎ0^Eo+!Faj8%OѺ )nSRvm?quLfvgK *_02jI`w5T_Q07ʰ1%`c Tm i:=p 3 mvB+7'V0Mx OtXO^EhuD^Szqh}0v)_}>$y@.R4 &@GwtYEP0yy-~{+ ^\ 2Wǡ?%T?ChUOZ&==V*OH܁%esx}׺ :ktճUj00:Nhl:hdS8,]nT rwVl@Dt5~[S^}YjyL6o ox,W8rw;nYF񕉿|ʋhReeE5"kV-lt+@ݳ][J?AJ}/szRcKWѐx/:풦.3/xnKD;sEOKZ,@_B&.݃-\=6xSAtKHV7lAB&z!!k;2WnY9`$ Rrx8=/oY7l~TGҖHCJ`d(ݚ!kg@[ո :#NC5m /߄yH(Sͦ 'DO68ҹĎEwt~G0 -*y&^`1Gy)-B:x%TH.CFʒXK1! X(KOHn.ezJ&+;śI52PkؒǍ?jqՙV_-MD ;3 yE f&X/Rkk2xKn ~ܱ+[?/$BDרPΟɍeM$ᤔGjX*q'OfzlJ %%+HO2'h_ռĥ`vɖ|ƢCgYߑ#YDRO juj  LԹ$\VImc3l0á/gMH yh:=%r&0m4tcVxG!3oEZ;$?^D'6xuM]Y3,M͋5 @)V z~d@]-p0 ;Y![falʴP2lN_jvAύإᲇ ʀC1R{r& s>&oRI$.o{%{&@WP†#.˧R9hxocKrmK[[A&8m}_`Js+H.]\ znY \lkfʽagv&@HnU8t iyi:I$r2^O0Uv0|{"т#|+ H(( Ȱ]9œLģe'݄@@Qu=$hK;A Ү w>!# ,!bߙkg_*ɫ\] ucjLIV^i~4ifTғ3ry ۮS"pY doP=Ͷ=s ]Ijf z7?|Y ;)aYjKŲ.%~`=PA|c]փ!yFd_)y7; Rd5鞵¦/_h{Ҿ~H*7Sq.(T)w3s ɡvu,JOk~k'=ӷ\)WOdtxhb+Km1Z}™̃n]b.Dަ e]-(oR级EZ}!-IR_fɴbezW|Bgx@(!,/>ӣh8+뼮KF^RtŎI\0x$a6+/T%>wp*fnqѰf0Lѝo|qAz Ё{c|w/9Ҹ=d TP)RT~:]U:H$V'[*6V"U4 YPGK}~=PTl8Xw0}?\ѭ; 0 Q[ڢ'1yʌ;"-g3336Z0Mڊtq]_k)JIQ3Q3zX+{qߜb(dk^VO1+7n6T<8,x%<+ӒB~_+ea6&%Mb*0cQd&ݷ~{Yz݆ 3;4"k|2krBoa2Uyt09Lm)"G%]ECLIx@Fg&]0 P:![…>|kWf!vU S^]iPv|:uQH@hhCsBvR ZzaBDdW7LV_meɜ->4A(yvOw'.7!U_rtI[ldz?7wvٖ2 cjD-Kl8[!bTr Ksl7#ZQ"j,`N){U~Wce"y|0}).c(mE,bF@[j}RsQvn"z) Y\F$zCQ1J7eu6QJCXoV}4ⰵnI AFNB@ 5u.9SZĮ tDGd)?ޙSѴw/麣hF`g Sl~2{6x)=hO^$?txyiQe;eC/K E~曹䡡b/-RPk|G2ty/*W9?~սY.Gz]Q`"8KZMN)HU>լ lhPY i w("SPQ2;oooy=k]39`>s;yI%bc=\FTS{7tB6hW(J(1B׫+'q'"nܐbhߞL< _Kۡ k o'^)EuB"^dNoJ2#hWdt@ɮuxt|d~: 8D#gi5k.(8. !)RQ0`/ VHp?U${!TC$#gc\Pag|(.fM9uZ)V= Ҩ*:橹"'=ӷ ܛ@ϳ K4sBW׹zDO"9^.+_rrʆ~h \ h:DV3DJIN]ۂ:SL)}~VjyeAj K99[7c2]ghY|.FY}p8aSIK;IGYK{+ۊʨFun}Hţvn3s蟥̷-oXOo5.y1:[MxρN6j|Ygj>8V>SwI"JuCa /\ "ֿmlx_ژU ЬT#-K:lL*̫ȗhmsŶ_(鋿vV5YuUs>T?FFp^z c9i\D͸d"-~gzߟ2Kk,PON˴csy ܥ%"U0ѭ`Z5_Ve^n(~,e]enƨAYhᣡϰTG(ʻ_3Qӭ"X 8VzĿR)ZGem3{!N@ÂUNaeZQ1p 4 /=]?MDF!#T'G :01j* Yt#o }?|λ+ը>Rs٥ʞZÃOH?_ ʭWf=5 ]ӈK/, *2_QJV ,ćk%6B,2;bon)[/#{I-v_~+CH+bQ C 4JZDqDv^WXbu|! 'b]E-?¢^ѿ)>ߐl&tv8xjؠwzi ($DD_9+O" z/"P5kOEj;!WoA_ 7`x[cTX 8wqt>1$@|:rV@S]b55P LMb2j~hpb+083WnmQaw/08\m 91is?9y/0pg~n;`l뎁EC=j>ÉWI4Uݼ<z#_h?bfGq#MZE,uc>B P:&r}18Sevx/S$ /b_jvAfXC Zl@y VvyH)I08ȴM #AnӪ xmSq.{h !ߡ4j{!yTrm.ݯdm(tW6{ϢB[H}:ӻzc\8SťԛUCb&hӄ*x1Yr߄dPg;`Fj? ?pz,/we€XEc~ADGGN.kLSYyކέ{Q&aL ~$i2-lp*S[㞽W@k FvrڊiQ$A/LZIA<]hVM7cp~x)v Q9-DjͣwRn,bͲgzIӳUӢZў)$KT OR\$eJ … ]?$^RvE~0DC0i~'ѹW-/D0k79f@)Ӷi(,AϽyYJ2ą 1U&w=(Cds] Ͼ’W#6a)"ND/:a< N6"kY)%4B"3 Lw%I*AXPg?H.JlҔAsJYS6yyӂ#mLI(ۚ]I{N-eM&Y!|JfOi:^~;aI-ƕ?7xڜ^_^o`LG JJZ~vmyؖ-`e[Y-t9/W 20⺿oUWZ 1Y632d- TNB3*%㫾t\́-ջ=wgR/P&ȶcщ~roU׆~>^8E3`*;.Imd 8%{;|ps e FbEwDn>\-fr2>֭})2iJWnq r\7Jn)OaʥC¯A’Pcfꊙ-ˠl*D߬_n><`kY1xU=9}R.%9? m)/9 49K^0^Phא#~ʞT鱻i5;=0=ZdUf\8T7 (xguPAbIo-p+Յ1OlF.ׇЍ1 oQaGFx' 6IHw UlۜXDy s9&)R-Ao(}S){fߛyx|+3N,UNĞ d>k]B!9lFDm~v٭4$ígT#pV͸ B7!+m vArv:،\۲1c{Ä5+YPgSDԒ4;aNb%O9dčK,ۨ7WT/%ǢTc}7MVv{wZWnTN}وVC+9*3jhJA{{>Tc9OoeC}A ':Xj@)nMgi)2An,_!2 Dz}_ߺǩmR/S+WAEPY֩+5W."|CGe֗~nhꜩ]Ru;ޓxYXmC!vyOJqI\ih zFY ;{a5*M^wt͞} 8SKnzVXSJվX䓆X}έLufYfd6h3V'_Tjݠ}^<[;4 9̃&(T8׹ .P+:+~)4ܗ=O~2Z_W4o+Epv`a\]IY}4 ZK@ih&M!]Frv!աgAd1]|W^31tP5m ٌeJfrx>pڄlIh\gAV{_hYy׹eopxGy~WO7"x$M5|hofZhʢjxF =0Yo",ۯklBLa9$SZnYA#6ʛVK w"`Z]U^YnۮMdԎϗfMhȌƋԸ_V0P-VY]k.}1Ҳ[\WǷLDZ|_+nL.|nM/BPA$ Ad~,O/r\g|pH\J[ufJGر@󃯃/{ C'5Ֆ9u{&<. Qt'"=V7 ^HpYi7 m+;7i ±gH _z0,ݓe U>Q/*> &^Q/s[Z(872[l}>74 ^"'eRn-yBqKV#ekdbC) y=*Cp'> MX-?{Jk}p(<9+j7 " O kS5)oSml-/EBJ+aotz*05 CX<(X +rfHpyw^EoV;"OF %֊y`f!Uܥ2xCtBq|'*|{R%S e4[֗./+> z}lu" yYVf?qX߬$= )ot? \Fjv@!ȵ{%HaLVAF=aT#y<[c5TkpSXZm˕%N*&pz ,$?J/]X,As! ؀fqs ) Q?X;ϲc,J9J=QʼSϪ>]VZAO2.hz- ҁt؜π<4yZ[\Y,0IȠ4t6|ZZ7d0ͦ;l,%2~qzҡFwExU͛,F},ٸ$5 UHNC6IcSQ/3j~.9peؠDYLj=v:P yRs3C:)I#oN ËaEJJi0"[m[`6zii}%#1} RWB} m<XU0hr\:Zk4+c3H[jz*IUA&/ArŇAc ](yoP\Nlo~U#l&ނXp 8 %4iSpze?h?Ȕ୔V5ƲvY" bw0V QG5\n9BYϢ7@=4Lgy% Èܻ. ub Β:9@8 )X2nZZ =`6sC`殧dHuPm:KYE4|&O';wy z&%hl08o&I1#@C:A!5Ä.070=KkBUm1`&OAԗJד9p0 ãz e 8؀Y܆ee&ge/-+M|)pr^F2~)k{*a;=&*; $ `wOЮ9X閠^8=\1rdSP9ݽ[>>VrNjڞ)&ȓuqoNrjT֤My; ۡkaW$A`]kec q.*mn樧GI i^=.C 4XV"CB'̶ P+dhlryQG~YЏ$,^zsa!my *a\_;0JNȓ|pX;YLq5Q~hCeܷcf;7Nx0VzP `]6cP&*JU)X[t#{wwM7".Z= x'7a K;03 o/ PK?Z]G-y1Ә ԁV0ZoQ-RnlZ$dDmBoe~Ĺ7tuX}2hg է߫1ݏe0q`$(7#*sw\[`OA]2lGA@QrB1r <9hU]/~3E5sOnAj^w1KD)e<ΠhDj}{1(!T&~CI(<ӄLdb:VAAN4#Uq#B{Bk|6jr$EZ [VʹM!%! /j01*oqNm t?L2 Mqc3i2.m\oBfFszcƸ/>vfA,zTx4 vYN'l%j>C>f;^EiRz?u-=hEu7#\;#t $Q`BdS0]K.rp GHCʰQegi Eӝپ=)Ar._Ӯpw<χu<Vp$9^?+*8":hó,DfǭskiM=j YA-I䕐QƳ1Q]88׸LT'&S{"K`BeXnF,KjM#k}2?мp e=̊,[|vrdt~*($PXrg/i Dpk,.\@KQG0qW <71~eX9ʗ;̏BGƺ]gp mixF֚<5; Ae}jbv0 {:Nl}NF1&x&0#F1e>!9F?Ɛ1[%M09#˰"{IHfrh]2XiVׄiR4N}"GG[#Tz)Yru 2UCG.V 8>Saw,Q+Z'.BQ8OMSş=נd{tRG S r@& &AG Dsp>6&uс":OhrԐh@F^ЃF-  Sf5z69 M [ٶZwVGB]]tc~JgNr<$v(#ß}S 3w\b>54 ~b#gtpnB?`s>+ǃ7զR0域qbIsTsYS). ? F+;[o3wvNJXkq9?Ohf`׃|Ʉ+90}rR'[(SY?v,(e`@(=htmLEBOr岙ZW1; lO ,YTumg2~rV/A!YAu4?wrV3!e7r'u\ ߃V7[Fv@uGjnju51LgN#~\@㙉"BbMroe[{Xm Wx&]fչ㷺`.:4{xrGwvORK$娟P:'z/:|e-|hL Y<~jG/j-͚aHDQ?YZ'?<"kA)UL@"$QOVK/ @0\k+W"Q^"WgRdn=KBp7?%-׎9w\gWeF6]-+d ՎVdR㍪s&^ t#f) -LjS(!EpkNswT>FVq&0Jw􈲴$< 7\fڴ2BWeT_[eeLC]z%P5h?(VX= PbNӶ+2͕(Վ&SYW)JZ4 3lWחAp;R\sy4.!ҧ^4NCy[ [ڪ#l7D "nQwG&Q7O3P*lxtn(=mG=JKH&f1Z&E ν3Sѱ K~/2<..^!6LS|0uRE\D/J* is;pʏ^z_RxV͞GcRqaF RYҢ=,8V~bzt~G>g hO"*X3v"}?iV>EmBj.Â--F"}cWZ=A.dOdvRmƁTb NYi1=;9CMyzʭӷpC)t.9fO+-,jѻPE~TIgPr ͵g4S y~L (ǾCIT:H34)&]b1N ΣQBE;g*߀ $i˂Hs+:٥]u2^)?Ze'BE;4ӝ8^ |m'cQOȻw-mݶ[ #׫JMШ}1u**TVE.<5C E@{Ƈ! x@V S{t4J|U!*6/o5Nx)f*Yi.*SE~r0 icb21KS ~ɺ$OܸvTq3q+%$7TM)`ʳ>B@p0Uum,-hy)\ﴲ]qAaG Z9ݎ{\5aR b|MR%n H%!EWJ/9xgaLkdVQ&ò/O~=v &'=dMn?Ai OѵPV,̔X+e\ L&1"oVnPټ[qhN&Oiۅ.P8"Mn 7WN>>c-7X\$K9^4bey;A62|b\LEJZK]O=e~a%ym󌥢$F<FIcCwM)w :$c:Gmgַj1Fn0$gו iJ0G8R)_"bj^ұ2V,t/N l.ZTr#6zZb[8z]ȿM Ms,!^EMD+֟@WLBsDO.93`f<;#wqdR,k&&{z3ZcyV|[GKZ1L޷zmܒ;ŦL7C nkx3I#wF.7TCU6d ]m(fV0#새 X >h_c[ 鵙K9(xw6!3ge[PRe ?vUeG4[ aF56[N(yp7'}$R$UX Tm 2w vAvA9o~ceRiHCppOhe>T,p.6v1k>KRhcm0Dg.`؇38%Zu  8 3> M} $!%[+%C|Q{_~'ر,˵}T48`ֵ܍TH)nDFeƱ5w)"_@b̶\>}vLS1[*7lpKGC- `IyN4D'6J 6L#kD5<<8ߜؖ}ɑ(ć"&"dNp*{-46t ƭ$w-y Ckd~ۉv#Bxؿij;JEd( 2 tKD}riPqzņ|F%,K6.SRpA-BS=7x`T|l CPޙ,RoAy{N'9H{QqWqͥTui AZ^A+δ0csCȅaa@㲃Psc-ԏN/?I5V3-wʗɮ]&LȁnUvA*CGe%D^Fj)iu}Aue89Vv[&,ڃs s$@mtJPt8+bG}Q V:1C$/'%iӬWDCy—Hz8(vW9>~T)99ʨ1P(Dk>Y!SrD5bu[gco鋁iѭ1dAgsvY}biJ'VhIjɤ1vd'!1 ;IX_1P(aXmT?]6lvap6O^4,(9ЙK鯗X 7P٧ OJ 4W'"#Nڠ@aı:f?E.3\8&}`q {^KqS->#3TGʭJ)Z,O2 qaL^Ͱ$/x~=1hwO@xĘE \*`g0޺ƴFP뿃PǮXF P}RNhK]ATF!u ir=uh**{p DT9̈́{^=Cn[燝qW= M{qG.S)alp8 Tez!pߌj @st|dAxr8։V%'EA6%RsJќNwU?7;(LɮUy3ֿİmZ`uDr,Ya> Q^6z>V{8V\5sE@4eԵLT WNC}]\ޘiw:,r{]`618)KEFV"bPZ:%:݃@&*a&TǛ~$qUf'>?ƒ(l*  y?=s]x%HOls2ZA2bt㬬 QU6Sba=Xx&(GӖzD/+Ѫg9 ~F?ž{`"/.wiDh \zT&Ahy?bB&P՝SՆ,SdLGea<k!hZU&[M}~aT E14pVOWװLALFl%UfG7>s oAs&:l uR?V|QЀ?!/TIO'<dK>WmdB@O oeWhm0ͧh-7#7Ɛio Ú 5OAeׯ1cZj`pJp'Fa5}ٓ 4Њn v0^ɐ UM$bpGdJ C&-=h S#R*d 0E.we/Y"}p8+tdDIEg̠D‡_kDigeg\l|:?gei7NHmH깆Ɯ6|p`Ƽ߹a:<L4Q]"S# -Ԍ;1Ǒ!؁`;=+V7,ƾڳpt @`Ni Y#^4y)o_zx"oUka#J֙1dF&y|0T(47-=xy.$"OeRlYC+D)$㰏ņWݾM?a[քn yC xz] rrZ&dX54Zph7l= ݺ64*жN_ͭn_be~f3C0Ϋl SV|$l$|T)PECu3c]{P 2m JsCN ̛q̸;Ўְ O +6A%l& _$e#saIPv8xTmvBD祢$I;@;t ˠ]߲M]*B vRyTzLgO \H?/.秼6ӐO;e^NJbH3Q]mV@F'0"bNJ8yԴ3{\`iп>[ZpPpgf%ӲnPLKq/ /p[yb LAivmUƴʒ!:*C!tIn*a6z"'uqu3QETGatF\SރD/1xbG.~Riw茆+=_,DiYTpmuZ9'+bh11ZxTW䤌`,e3îl4lMYmv󢵾0oE**4it& -+n<唒Y۠q4`"`Ώy\O yU Da1*I-J\"F5<ڧIe(KV-'?yA'nnmZ i|b*2{X5:+#Muox=8ڔMCy (C= SCA r)I=g\@L|'*7D^y3!8: R9 1Ɓv;>lϚn`k vi/\f6;mWfү^˖LlGMym*b_K*;1V{'iӛm bc{;u><15!7,NFMXDևCqNgpwMI!$ob} Xl_b -(hPGtaT.cQW3oaNō !:G=hz6`MtaHj]]+Z yDe0ª1nXcg`F^Qo$J` #Hr/oưs+"TC6/Fe}bwY9/?w*ckMD*_3-.ա4[*elr5Xr' &'/srׄT- օa5ޮ6\5'!l􃔏/ϼ.Յ, gpmٜ5Gs,+("z †?־L|ȚڡenII=/ʽ9ꝁqݿ^C׋.2G-#1;q_ߑɢxǯPCLy%(ha.i㾮8%3!#@[߄Sy(KLIȳ =qjx!í(e|𮰅'?$vsHB\7t?9 'ī h~.1^dlXW[^԰=vO(pըn$uK~m|n*K]ۢd%DeuqeQfI ŘrNf}|zi[~'iY<D [bj[H!0na47.~U15@Di^D2b!POE3(bW_m/2rHroC3k<#3h# ҼrO*˃b)L>y?^ω۾P[]O_8~;UW>3$ضIzNӏFy ^5Jz KRYs#[+i׭7"`C43^" I$S!5J\HdTFT|ُ,D+-)-%(Z@%}{H2m؂ B5#45W6L1ς:Ӆ;t:F{ $ўSf>ѹΌY.+ 1wmp]/ԣ>HWOݞ^v;tsYDfjLe`a&4emIPTrKݺCŶ\:lXnھQd)l<}ֺ|W>uHw&F?w*ۄ""9"?ߤKXxd,y6Yi d.1l! +Pm!Q| Yoeƹ}LpMT6 wPi,}m 7V9$tf8UJ"j$?Y=>l5(Ugflb$+ b!gײOg92k0k)|N\>&  <-/ˎ =̫zۉ7G78@wQ٫7:fl#>KD.AۮOCKw/DRI$֨Y¿_ =+WR0Uy.nta _/ b 7e{rlK$ %iPDh+aiV҈DI 1p_hߑzA xvziC%-ˣ\_>FY98!vyeo^]*LIrr< F#MM>><Qn /PE|t۾P똥=<#dqj*ps OvFK$潖!vg\LH1Jo\fkEa9乨syʑz4T_zp4wփE&`7-^{V>s\Ap+&,Kg~I4GTp]Om̔`j# e_wcdVij Ԭ0@2L&@c>6=f,:p! zf :/vC7ˏ_hSܭ Au_\nI;[dෘ_,8UcW=f$¡`Ѿ~r2W*S.džթN'Y>z*3hKU x+!ُ.5 xRIQeNH:z_M}%wfʟD7/_t/ Le41l SS~3c_*`0RF[|#R>NITD_O8@Jw^IBrMU KQ~z;N4DGvo`,xv}׻t8R,n%%1´fQjߣAˉ Th]UYNH1^ 1\;gʽ~OwcgjϚ>qd'O>{ H^mJ! '7'imTj1PlVR`+8*r// 9QPQU߼2R\2l-p*1hG2lC[׌WGvQN1ř&`0h[2E BTQݯ]9UKcg_8^mLslBwKa/->ה"&wHf hcv-rL% >mе@J%b_ZD} - Ȫ$^UOBd+fAwTAq%нi;uR ;S Za|xnVDnF[;ibeqT1pXv}s H*|Z!=,{<}+cM,%yV6#~?I<`>u8 .NBXy >;'pH67jT~U e],f7ʵ(X$dD]hO6$N &azqe`WNpys|!0_JX#Qֆ5X oa`xOSU8j;kklGF(З$NuC|`PScJTY&AJvu`*3'Re%hJ|ㅟrH" zvzԫɃ`?&C2jMbe)+ Mh4k#H[T}"CElWF !NxE ͅr[--Ns lp ¡ sS*3{Q#Ž6w&[s&ҕ4% D#8֊?|a6_vΌsb{t:PMG%F{ }@AUMWZ`{h04"1)kZ؆j*-0I"8v`l54C"ɋL/CƁ ^[֑YZqw}yC|`W5lo@D-)EYֵ2} q$'՗5R^H1 v2]/*ut4\uDE~4ts0 FC!SF\ 98ut{#%O9 ^drۖws!wǑ"]d6(ʣvԝT Rpŋ$A0|]%*fr_JO s1vQ+sC+#1\܉B}x?nxWn?{QM[ 0.M5d/xh`w+n_Y+,8~ E%AG>nw9)^ GҁQ<ޭ gJ&+%V9 m=.]A"T)Bą}d$0TdzuKa{z-|VDlPT1;L,|y|g궺s6s{vYc6Ќw\n`w}O@[:(*CcbGI1I3MT1|}|d;Lh JQ؇b;wam~|09lodsA|iz%ɗaktEMHG 5ˏIJMnVA@^f ygw׊E*A=لy *A?䳴|K qa߈=&;r#_{F Xfٶ!yF([!H拡rwc/L Ra(B\;WZy(X3HgJ̹ awID?MJl ̭Y8GlQ^LRJoLS 6Dޠ?+~W%H{%g?,x^242i׫`Rpv̴iJDTH |*oҡ71^*k"_$±[ s1cz1Z`!s(`˴ L??y0}N.pqӂu#$P,(_aܘHĊĂ5˲-†1:Vh18\ԟyWU K q5Zp87K(=A3G * زQ|@6^A\=x7TRx0/kc#q54lT{?MXpPỹͤ;OLc"{s|9ȿVm ARXERNGiu8E {gDiLYY=+1! T_у9h_d"d> ]R5R/쾖L6^-["S٩[K(CnW~ 恔KꉬhtCT4'gS[>6:Fo;Yq;C1iL@xw%5pC1 4C6d]z?+T]yXM{.'ߠR P\ۄktGM뗶=Ѭx=~ -{lH yob}[#,:ĻlM*lyB?@AVo]Qֱ[a[z2_:)ZN}ma=jvLOнިVRGP+U"U.c %;0U( /[Ѹ\{w"D^'2'TFhcƬ7UOGB*azi<8H~x|!\6ѶʒeQ_,6:a:Sw!+\eBpjf Iq.۳3~yYjua"4&B!bK4Y_e{#B{GeCUu=:ʾ*NJy}ȣF#YP=.3˂LJJV:բrU%EƛّB KVE^3K84Q,0W`UW*)_r&v!3\1鐊zϧNl;PGc] 9 7ʀD1'Hj N0o0Hp.qTIq]5k!I@D "K=WlQMa̽KxH^|"*xI yzw;vUNkĎ}W _ڕ2C ]끯QZMqkV}fU5uVaO@l'P gQ&pKb]HLbi leyD$k矠?ݟ#]RMͤ}[=+ch4G<ɺ4AcI$";X}!7 S]O\p6/и/!; X4R^&d~< 33#̺e fE?Lp:Dren{ gm?48!ػz"%R`?| FR,}$Ph=2$C1= zmFAQ{^ d??ʑtR?sa}0i-bq"kJ#`sKCc)ѵYzV…kBQ4nj豃N=p\ q{xb/6$ -OtYC<|zRK >Ǻf2U-!DpT&0픴:f[gԿOr S+6+扏>#kї|,PyNhif}'<VScæ?Y>sPXs1VW"T.P<02 >5|d+02 < Jfraȹ(p(2a>NTH 8;BdlK޹ûVmJ$)R~R[^ ȥINY2YҎU0IKf6-^Y"0M .J-AS8?@3^P#{QeN`lQEMQonquǟt(M GKKs3W*f\ Aѕ#E"+پyybś/+.՟4tͺ%9Se+\Av\MT-ʲm !h%rO0򠏒Կ@ϙrvs_!K&xŐ<@p'+ct oYB yM#Oz & $CpէZsq|T#l073^XDByQ6??-BOj\=U0mʫMԲ4i)m RSP-v/i]|ƬH홚oN*Utw/:m;_G:߾JmN7ѴpSh'' H$,4CFqGt80W|S<'_}ToL*cq;1 X [JzĿV|-W˞eܰ"D腛ɏ'o PslhaWNG"?5+)|9au<4.y⛐dR YζDtk^>5[bgN2L#$YCÂnk%s,Xޱ gwKHK@ <%F;Ҫ:cEzJ W@yyXmƦ$ 'evߏy "2; {Mq8NW@[y6 8^At؉?.JoR"T`*E♽ґި&HBU<ĸ3XܮCL7X2ef+G:z/\ |j=׼$O>;ageg*"ocQ:?f)Rٜ=]NAm5;~ݔlb):!´K};PȐJ,d&?bEwJj.͢E7ABf] &^˶K3Np:Q۟Y_]U*<`bT MThlM udxN TFGaʫ}'$#ބVb(%ϓgC(jsPoU 7XKIwevO{W ΛU~TAyxk:?ET춍Ъū)҂e`oFTO9K1v[L``)QVŴZ Ws :N Xa ɷ袭T5ËRw[5 buFLsk7݉D gyt tOgfB :E^oxt><6U6 &o/Fd1Zir; #Hv8ZN(9K-cf~IT%ҝ G}^mҹ "(աle_wEفKe'- \HO}˜Z;A}dY4!t@UX?ߩ #@1V_0X?OQ7ϙ[TLsd6Y/:|)AvĦ&y; -,{%JB%.zS\n*sC_9TZ[Uou3y,/`Cؿ""7"M~)A x_nr|OWlZ[:uZb`qOjC%Tx[&Bj+<fiZsO%W N *?k+#)ÅD6/jER*G2%^Xh9oglФ$W1-(!އZ:oSAQBM"疨lsh b7EGE\rdخ,s^oD_]T̜4N$$dHhmF iS̱zI4@ -AO2 >Gv!` 3ƩO^]n<3ceHh~Er8omɪBm#7C";K/8To 9t+1-ģX =,~ LF4)%H[90hd'inBl|D?Lew8Z|nC 0hV.-u5gCځo:Vc_2 x}۾R_ 8ٍaPz>Q%Jx> Qu餾74tu/rzU|H2pO4mvf|kKU |ӿKE!@b(;@kRe~05 LY,i{bP5*Q$2A.%eW©Gd7}TUy'i| 뉃Rc¹vDѯǑ!I*<u~OO<`y9m@k$b)Ahdڢu+Gg5yz!..dӾ225I?3=$5aDK jsg(1'󀉲!znd# X B_ YM`%i 1^xtOYbP|X~O4oȖ*u Жߣ7/t!yi8=Lxy.@`x~uGdknCItф  + Q"YD66wv=b%yɪ]Q%*xzX\ah33xæU~pA?hC>a3vǭ i?BE]cR8BO7L+GBAYK;@ ocs1aV$bFH"PUU7 zl *3@"f_W*X+d(7zL 'p4Eזz5SRN,oݑYI;;*ЉҐlʏ/qe/k ӫ]V-QU} I_SOƹ5{@8=aHg9 \gR=rLB427n3"(ݙH ỷpz )^&dL  0SAa.</n?֏zn5I;Em2~eP'Te-M0*{.I4E,Ԝk)?_3+ÆE~7}*^|t m/#?혾V;~0LιM)%ѥ˄)麰Lx>U۫ !#Ftx& мilNUFv&e\[cu«έq -ުS|!Rs!p:&#*& 2NQT~~L3 [>eyڻگGLRB_ݖf@ it2Ca֥3-VtS~3K/ JmF!+b:c_ 1F>s^ k"[B/`iye^t?#,JAy<|:UqO 2ZoχY<-:: 2"[$Xxh{%hn9ܭ|^?$vNpPtŇ[o{9PfCt.Q 2S}[nXx2HEڌjb${{3% q,KoB2#atFdvWFQ[ /z@9%zEp S$ދm|6,ҥO{A+ Q\w  PL1V=^Հu5eHua}j"-7ЗB](W:(2_ڛ BR^wI&@RȻ,DZi,윷e'*`@6#yl@V% $»_ |.6 Q~ZꌨZ(_ܯYkö%>^JJ>O>A˗ܯ]O5c;hNE|(+`իWSMJu~3ŷ ױJȾ8Qo7Z̾uHI8upKe6!^zΑowN\/!>B}(07>.b6M)UR7ىI"x]ol;nκNA3:߇ 7?)YғLz;lad-=i* D=&">Ft99 uz 7"H: P@3(@xj;x+- sh*BYd'SJxȋ"O+׳px{Д<;qK$U0GN/D':t$cMB ]P0_h0NS !AF"fW?Vs(ȵ[>,KK2clPwx V't.v*̩pպ|{G^]] 0GlH/r0 -BToU `@,IoWWN V:_VDC.B~z۶$Fׁ`7 )PiR f|6B%Ap@{U3Nei|> L 1b hs}OWWۡ[[n5F~R&8e,sꤘȳ:ZZKG!ENOp)$,h$֒vòm hAarz#lGHa}uE /ŵׁk2hX%)1Ƀ([ٵH0Rm޼?& (y6[Q<&mS)#"ZHT~3UY ZQ8Ib䃧3E02_Y9$|TfhMa/ Tq /+<;ݣMF`,Ɣ8P%UpWoOy:>jRDm4DǏ[1xj8;8LWMJ~ `(,a-3֋`e8u(zrr H   f= X[mꅬ9"H8%?Y|g,y&QT|iANl},Dv&@G6ޜk!%^RYm ҾA,$8_^qЏh'5㡒aW qdj٩ot{Q+4@AZB_MWR֒CǀRN+f5f{7ÉTp2R}I=_+!82UyAޢz4{{"o5ky1&|W}&3;eY2I3f" VBǏOG;I0+\Fwf\kHT^Ti >vI#Q^P2u]˙A(.oOYcl9p TF'$ZcL^6NvQԊ;0]dC*:3_`<'Y&^ܴkF\s&Ӱzm3LoGJ6*,W4hj I_efիCaJsuԤٞvun=M[Ō!]O?A*NսU\QWߝ% a @1?Rd.|;߄zqz6hLc{m:r!~/oS}( '(E?sNS$~ l19!nV}UJ'Ll3<C$oN3xƵin3XX.a3AM_|9S $ )YR XyBe.%ȟAVCMI'p-grƷ!c3te+h|#znFyy$CIs~)ls7W˫d'cRs)ݶSR7&F{Yŝ?uJ+2maF ;4΋c)$]Ie~BÉR 0s9j\!" -fY;5mĎn7u}BuK5OI9)AS17]zQ\j[\>Iî /Gm Z h)0' 0 #IgDWqh^5_V ꩈ(m坴R$.Nպ{+*NS+_y(ġѬaN] 16!Lhv]{Ɓ+V6/oI5_??M!Gv)啮BKGB|?Q ?/mI>ldդ*qn1mcܚ6hN0#ac4/CN3T?%][~}е.vP͜L{!2 [~Bj[(~n} \j93v!yy"T1~27nMS8ej"Paz_zWy%`l/B X;MSOxLvk/ʿ9^ AjrGAp3#QU=x.D>ey8,6oH|K@EZM#R,]\0{`hu9!Z+X'#QoFÔ(Yu/W5GW+C#F4te nBa]/`XSRAUW t=޲Uy!#i l7M9:0UR^ =k]t7 ?cXp= '~bB"B} _nAU~fWdsk'eߐHIb|ާKLL`,ZW$Y!sBtQER:b|$dS[ *# OG†&=zEVEۅ鄅x=rS9Y[%3)?]oI(si|pPv0(-!A*ndظS-4tĆҽ48A ak>1Gޜ~S!ΎF |82ZGP}?Yͪ uqɺjHP[waHݥo@2I[A'EM) KwgLV%`q!=P*t-]ݘ -.6PJHb-Etz6TQW{ OFj2-_JJxW|Q&^#nmVܬwl^2CIU׏ScCe4%޶ȸF*"<1vgTkWzSz|Fm9GdnIo$BY@Cn1 يSt{,\=w#$Sp˽죁vS^agx[-*3X s獓Qyj`c Ɛ=zUKs^p: X"CrmDꦚc"+^]; ㈮,dor(ʅ!FCk !ȊiPt*[G~@{#T>­JCN!Nc@K2SL ygM85Nց{{ZUb3(11 o@p dLط%ELM=t#tHr4I,_}j- Z/\sɫ%+*dEa7}'#VXԹJ`bFzn+lΞH;a/Z)]EE+%4)(Ru(a-u^5\v;@\cdqIޖcA"?mǺCV=<Cs]BXYx@)J?Gt\Q cBSWT;=ZpbgOu]3ab<\,J:"508N\qLO,O ʭ f(nfѕ'jA6F­A$:ǞUM2D6Lkhdpfp^ἤhdSs ʾm<5]js$DQ]-Syz yÖ$T9Ɓ' 3q)bFY3`9(ȗiX@t3Lf= 3~.KX&]C*}N`-;5 {WpXD)7ң|Ir]ܣ+xfg.#"NK U?x$ȈgS*\ep"~\3z|ʐ+1d8>ѝO[6|Ou_DZ֩ǂrT#YmB!O_78 - i>!ZV{%o8'hO򹎖7|OW-L'_l. / vR8$ДP;1k+4o"Ln)R՛IT~f>Gae6=>9t/;,FԬrYŇ9#Nxp#LbX:2+SK3Ig6+F;ުΩ=S}QOxـmXTs%OF*/$h]~D?pɴ4?_~mRk%E1hBM8.@lwIǗc+jaAdf'߇<I 7LX߈ UM**̾dN%mj+mi߳7BR F=%m*j,c1$̢v䟀5?Luu(/ɮAaf/i^[eb-C[e YS.VUٴEWXVZYpSwI%$_653n. ِ=_Q8hjz9 ^GdˤnY55-(IAjc>jvޞ!PSwtsѲƒKhRBnٍqF +ka4O:zE ;P.c3EJ]q2=3X,: ەH;~a̎[ UwhGsR"iz03>{y &(<0 ,ӍYpJ.4Q,' }l=o[%e3-|783*ru!ƻ/^;0n?3.E/Xi;m-P$r-h;Yb S0Ϡ@"|v$$r\Z v|a7;%xS }R\LXZߗy8-4&XxCc t .0Ek=6`>ͤ9W4 2@+Pq!g.ΐ^h%J1q@%-&G`׆R=ײ89).- t=rif(11Tm݆ϛWPf4 ^EZi;];5J^_;6|ԇːoEckb{6m%CǗW{;ނ.V=Po/ܰQ%"ЉRUyUōRqhd_+ws@5~[bH}{DnF\&@n/?8QƜxawnAMGd9/LP9 PU̚ܮ͸ObZKv;tIq4՞Ƀsm#˳%bhIC*?[$*.[Sn"?^ {zLYiM*~,:czV.؅Vh oO'N`J)_ڢ_x-]ar~/arP_)5p\"8l-2fn^.Z8_1XqlOE3OJ¥bvBxɂYPdG.¸A׃g39\6[Vb r/D`1+ǚ6^Br7G3\Ç&bx=?t;u"&L$p娸AmǂUqv?<Ŧ.S53H|l^<̷'x: j=C5ƿeM/NFT-461MNd}:1_}ak- q 53 gIH >Un0'? !8s<΁氰zRLR8YHHa;Ru"2[羨GIxġ/%JG.GTM <@fe M'~d\v*m3إ!L)%OɜMG9̌0m(3~S v5(& Mв `YFS>ꚽ|L '+y(taf WlҞf[mK tԮ Q~+/%ϒ=5(;9)!WL2`>v%&۵$ dj Kpb(QJp;j<==1" _x`zm.^?d8n' X򞆧/٧s?tV ֞{71)ߓm3LW1MW:r#-ܟ*Guyő~8ąj°UM<AiddY\V +An3 /14䦋!;9 Z[z؆}޻0Cq^?v'̛J .@HU X,Z-!~lev0u*A Eҍj B2eSS[?J&svُ]hU̯<ų',9,"1croㄩpZ18<1.68 jȼV\߂Q|D'(&@&EE"Rc*0JctN.Q ,jPZQ|-|TjS}7.xy l1h%mu&}ʄ]x]!ʼniIR!lj}ћW5ásZaLh R7JxnOWNuD=3A"GD"zjlÖfҺGN@՘`M-Wnvḡ5l=vF.Ċ[E_Zavb%: T[t|U)T X4C V5E.xoEL$|gTeP(JSYKu@N 쎋L@q6\iY+[ʔ 1BXV%8rMv-[7K3WWAh'JPVOKB-0Z 2#'M| yrAYPҟp L i;Ŗa\ٔ<7(xE[nM+|˫>_?E]X ,(Ꝕj<+/a(8@-;ɼsw_r̊3pi?|i8P4DS|z3mPܺ5ue: 3l$ i_h"W3 :;Xً͆YlF8ۜu;zS 4`{͖6Wv;9/@pB>hb~m$\tn!D@ @$U$:LpxW|zvMoOG $Vq?J{[!ɶnk'9YLve"cI08]W#x=/<95=n@11`%ެvtKY0 ŠOܼ!`4܉N( MȐP cGuskJ. te57>|G'H0`c[eD,JW($xՑPt=7vUBx\(ծN~3lpF)݉_ }1_VJg{X@?Gk<#7@-}x}kQYwL [W#rAp՗ZH|+KΛ̔0N֕@(|LW~y cִC:c;၇"}+ Ы͌HPzf@NfϣPPA/Cul)! @J.OH?ϸsRQ9W7 ԐX\ANkh([lڄW9x˃ Kc_c S,YjC2㲂41 Y$߱<">٘+z1 W #:ëYBKlaYK *ګ$ϒc >/GN:6DAm ap&uCT> {36?˹j2Lڼ1#@^W}mui^N|B-GT+fZbJޑBCZA`DVU\g%0$FdJV[HKw@lS׹u#]sդJ̩|YVVau8y"hD%O= W$~b{[carny*.i8n^rGWn%\Mё`jEB72'TխvA N0ݑBb( 90j ?exf?195Y=*^uI=5tQT]uL<3jԬvS({">ǹH;>(,%*-" ʁ;Mv 0M`s亷U5_YIYֹ9*̶tRӢr>Kќۍ5)N3A)=)"\v+_n^÷ K s77$N0݊%:k11߲8؋Q`JU'+\O+>z53Ccjު̻c+/\jVUsi\2mO<т]4U0Oz'q+6ty\`ՔZ 8({[dmMwyWG%WK= b҅CEQg2j1{ Brʞl#@r.[݄牳EcŖ)I•^wcD7'P*q,+nrHK7dHtpn_LDnFMG)j.@ViS͏* 7"LTpm4Gmo C0!jᆳNC屓awbͱl5@7P AAp$㘀9\&?HX}0uO~;7-%x2tNϩ1FZfS"RDW63Ie v@(U^7IdZi"&@9FXy~ʪIqX ަv_rKI_'XR7<ѓbJkv"hs") N<&YC (ϡ%?6D7Zږh+59z[1s?/ Ek(U%(UD.k@[^NqFȲx!V1.om/3>z+!ll#3k}1c6k2 ['nފ{a˫ޙ$=5M;k).r(jeQr8 5HvL)`2=guE<)ƊAۊ~uOpz=hƢZ_5K%CSH*>݋ف7|Jٻª^;Wgȇ!>M#-[mA)m *_j9;}xA-\D8%RDPmҨא6F~u Dɨ%E+/敡:DjT6UMIjF@){d.0>W^E'jx$?hVDtE#gRкT;P)R*35Ao<9%Ɖ 4.0l^u0HK%$ao"o( m1~V%ҙa <`@IV%)d/` ;b7J3Tu vd5x܊"ϊ^:ZJL ygASw-:z8>[2[)Xqn Z߆j\$e>-#1 o"+7Bj~N >בKa1  r/}z#0Aec4;c5@^RV3+C^z!{4dl~be}2{7W Bߡg48w`9jg2ԂBxٹfxK~CB OIuG;JdmܿO9#m?5C3LD8X(M%Ke5kk>LI(V#)75 _P, 3B:]HPEUӆ{Ӎ Dչ@# cp9W"ͨeLcLliU>HOLwoٹD[$!an)|>m!'K~ 81o48tꦮ{К@BGތO#sЕП:1j^#(` ʴ'#vx'Yga-gApL$-t9SiHPcߖ}Bg`OݵtYG%\Pѝb ]O n.Wm.g eT:^}?G1Cp g%yڡ_Lk%0 ؞=, oR;!p`T$b<[[и6Ltu!P&C[ !]gQ&yW)Fntwf%<}*p휯?gҭ7@(9gM{ףMگD\apHrP~ZP^фTA)w3W Uo{m!VEl*c;]}I99kB3ؘ.o&o1UOr*},k[&Zm+[mрlNH吣FAz.'٤Zf 6CϚb=wwxkrsuI,vWƮ{Kަ-C(7^zXcdyP0J**`q0zuP'v>ո\toQMNh\=}wTЎ׌*UB@&e6Re*:j}5LnCc | d&I,fiE/u"uFJ\d;Дue7cȭ%S/SN+'Ì&#uSO౩" y9V)_5Cg0f-JGm CEFpI Ub$vx/Q tNu=uH7vgI87 =Ig]w}ΔEw|H]5݄bB.ckcDh{_dU͓N(8“m214;tߕbq3vTĞ`h*jR );ot{́] C ܅W"Gj !nڢlt굩)=<(i#$ ʋZԏrwhXPjN.hwֳHI["o%Do■Y..]qSn`r|ڻDAZ)Ҏv4~XI ]j<)8wàWqV/3PϊL;b?ʯrR<_^?Br)9UYe u:.VEu!wtwOҗz&R-J'h(9fEeˍbrI@ƻ0n r-w%TFԜGcQ׭}ФGdXr,G{ 1\<440BxƅyG,Gh|lAUo^| 櫯|##y )j~PNGZw!vX }$qnUt͚fqhq t^aۀ2_l x?G| s`* Qul  5/<]9xX Z )&A?aCfpM44-FލȚS6ByBl2`ZYWqs7 I_y q}7;k*f3,xMa-:- eKwbx%FD zҴ;`6H͈Dmrrsa5R/!5|Uϒ%c!yL`;[#|o((c;>b];T%en#e"D;y ?&oppP-̾}7dSABH 1ݎڋK|i8cOc٥{I%Hk_^0[K [­$v,h'S;ùTр)%oa!Bc@\fG܌4k9^*>_; ȕ^W8RxpS dzڲS4!m XӸ~ljuW%퍕jb+JѦ\T5cܭyFύlZ';,8d4A4:lMQ.K/>3ChLX?:\HwH!OO8/EjbgF vu qPisu߰@\c5Q_󇖺a+9mt0/H1@##bNA1 R5.6҃fD|=c25/SY2Ɨg~L}D&bb/Q T-'G?RH3ҞeC ^*unbQ?(nWRO@rBtnHMݖ6mvAPڈXܮ߆Fdhfdœm]nSGT Nb.ӇŐ&6y;r7f+vH:K#_.)R^$m]h z5Xb&@Nɡ,Ch9lM,&5nh|c޴=d h0ƺRlAŠNkwBAܸ3xTE,ꦤdr } Z2vO"[yN3~ –O5/_gr$.POߤ"2X׼?EN X_kX[E5$fkM}9#w ms{{/A/rJh W xɸ eGX/  *ݻ%?n^qWؘg=^]D]` a> ೺%8IJ'7|C"j91~խe>ݡyLBn-5<'ϥSՇ63R֯rqKN~_r.v:/G:aW\ eb좖@s٩:m4_3S흺kqI2*[)p(Bػ%;lT`I),_Waw $J qiVC[%֗mZrLBVSb ߾Pݪt4䱵 =lewmC Dφa/64wSYI{;.|f7,O'1@ @VKTjrށf#JGXFk`bPx<9_\#Z-<6w$YE+Ζ60kh^; pZGpG6v 2A `@('o"XZeg3R2< sAȕ j;4ՖwiSYeO})_LSDzп Y$aByIa M,s<Ҁ.8XNȗzܮs`}*ny79m-p:oR')ڍ)8g3p~rjU2XMΑ2 K;rU-ql>][j@,| B0]3=:M%?btLam]l<$ED1h \2͘0t+ $vƭ=WT'bfYN谌"O%4!#7솎7D6yD_Fm߽:D' bjI¥#>6\rK"搵 ވAhHngi'aI/M1aBclqFd/a̺YDӕ3kFl!NhBzsYqsN?0&2R e υ=u7]H}LZgѡ zMfxbIP67\cІ!']\R;أ:D0/Vu^e% 5 5D&"UgZJ- M?ixx+O>b4 t]sj =*{~ʝZU#S0RMGax?1קA|o[FCFԀ}̔@a/S dJ]Ȟ~ s<ˋn A%՘_M:c-u]kiI<I`oR_^/a늡~8Cd`؝3z1^TEb (H ^]`7@ݷ6jRyy_]"3PL1!eĿ1~z NP_Z}/DV]E%y#LiCnR~o&NIˏs t9%ֱn䊞͏sE0Esymrn_ jQ+PY!'\@z Cj_!t Üэ%h`7.nRQТ4s*E+fPq|oEo2k\hX>^9m[_'?58 h*g#V5P6LkeA pf)9/;sA/橂mb *.6$Pך·\Y:z0o|nAtu"HkozDOۺْ-ㆫ.f {xwfQ7Ltxq*-JQO\ïj y=V~$CӺV^x/7}j>c 2@&t|b8kVneA"z#ߜÅqIvl{ë$ OttKUec9%:fPĪwf m15N^~ts%(@:MD1ihF~eҟd)LwL3vo+GѸ$UjDCѼ)1C= B hx[o-.Ŕy92i]\cϗHTwLE-fD`\w taD 9,-^ǯ?F#K#V۷x>T})-_ԝ86}6hӇ0& `S3W`Wn~qWAiyށ{[芩K/)ո}` \; Z˝+mF<#`x/öo.Y<mfn?{u~ŮPo>:=x5?coxƱ"y]KRp;uO˜t"DymO5A\k&%؀3pn 7xgt6d :BFuwu&0ThDJ2t"@$A wvN:ұ-&7+c4۪ezu5cE$"z3`D+$Iso^9i&YWE%Da >XWw:_o]TW3]Q~%YMp ,QKc!5Fd׹`+?Qړz%L9b Ԡ=p/^c08&XzVe֥ j\dZΉw%gԳn|MGWqsIuN]['5 D( {dr9݀=۪HJs]o/g>{ B>&\9ICB3s(؅M7ݽ$췤OFN-23 6x)=D\f [fX b PxU;}AO'V{Nk,^R-u>\2*ޣ} [Җn\ oZA4O+T$:!3C mƀ0SWk&<ن ن `If]' jw >)(h>xǗ̟_)W&|p^m N\p5/6SN_|\Qkx>2T{0Pɱul9L \y^1 Z;k̰V1 ,Xz0 JOAm}>m=h3۽/Y$E qY] 06aYqkEJxy׆)G}i(%(IU+Yhںڽ-GZEDM?D7T|Z1wfeU.(e|`QKtDְ[9`co2.Z^9"*W8fy]0qx(5_Bك~ڠ ?ʘ{uww44y}2p%d]t$KqV_<{Wv&iJKj*nm 5+ֲ~ LËT1PP#_]gi-'d7dvּYǮ|c &f«/ĉiSjuוo} vL>4ޱ%=8im(T:CkhCJSaN)7Db'Й]+sBvƜ&#խ O)dOBCҹ0{Y@] $ |9M=T]7HڷAh4Ee][I+N \0#fR{/RI񓴢'k^ &Iwh\4(O,hZ- =cGKS!Iq^6/Ϣ0hEE({4GʒđiO#ْvʒˤW9"7LjԿʧjܴ#uyn0 \ j߁=3"ˑu. #6 ~w5hKU]r(ymr C"9{\kALq.Dԗ3HȄ&ȋ!pJc?f͜>X@d(LA[WB\AXKHkntތ\NO B{pV.~.xf_He׉T)?#ze-ZbZVxCSښXcٝs[MG3+)+ A@h;{eS_)岭"S1Y]!""~C4^FӱChi6M|EUd>mwRzm]i k՛ 4\ (F"=(̓fEgSidU+QK]4PqCFl[A 7ARKV g3]0QBwȌF SBn̙._સIUij°-;2e8vM لG"T:7njop{ܰA <#FV8aA#E{ߓ&H BKu'džƷ]ѿ2~(0+;ρf& S{2mp }D=~ݢCb8zTkmT^d8k?ޗ ^ XuW_z|kH9_gƓEU 1~We/]P"RL!p<ÝxeH?i{mAs$Zwq.`idO6cZzh)Tq+C^݋Jn95G>lez_Fn ~s*!2ڪQ4\cXk' f yK٩7`HW%53s\mP^um2.O mc<(%RF`wk:$g|L76vPgw [?9WAxKEv[T'Zgmq3t-R߽YHGj9kB @Dg{kjy(ԣM=eʹ /3%K~g$S%37.{ʏݯRsMAsmhpxB|}M(g$j"C(`_fE/WyZz˻ frǀKά qO TG7ͺ5EXHVeE R#]"!H9bI6R7Wy#N/"d43ЃDMQDc5գN%.tsgT1Za"eV ,m74Z`l sYpJ!I9qqx"5B7qeD@vMf 5l <hwq^ЍQw gW> ʩ:n۰ڥaA¾.bE3zX7f[)XXO&=030]!صsRmr%lgHX!n~`8 M>@u}mz[*gWqr T:m*7A^Qe8Xj;yuՃ9(D5 TI- L*a{j#]W}lgZ6,K9gBx},]ҡ_|6dc8-R>T#>Wٺ FS.~J@S%[X f} X"^YԷwL8ҵ=̐yY-bpDpR1Fjbơ [L oI?.ŒE$yοbR6w:Әˢ?~sʬ`ّp K$1@Mѣz8,` ݴIʰ\;P˺ "$.Z59CZH7Au lg؅陓[h1;]5>|D6RۂrBW,$+ݣYq? SIdP *b٠fREvu!$x vXږMi_H^3.JX+hS~hKV4RnBxIqUhӎ4U3nυK8p|:+ 2Cn8W ruhZ9; -WRJ haN.CjxPJ5,omZRܫo15)#]?yfF5ЍS.7~bBo}yIXΨcӕDxzѯ):6Ь =!4{J1U6dd֧D?\dDBU! Ӎc ry'T6 -jl 䤴o']V9]ELTSvte,}f%1jrg욗`ԗͯc«}'pIxR/YZ0`< g&COG: >rtD;m0Y9M_^ a D'S}Yt4:ZZ'asX\YqˇDaC ACo<}sO?sت8xkE*e1>~N@¦܏N@M.K `"Pi&|f4rUl7& mYީr1o6͏6ڒR+#g 97ey58@IW97c-鄔=V}x5~Yv@w" j3}YY%F_X-xX6X3r0۟:O}.y }.'v*O_cZs\MLԲ})vE!lBt$[ <q6fOB!@v2_ۛg3˃Lj,ɓ78īg{`bI[=EL ǪiC*0$vOPOǰ\$19 cf_4ݸpnX̾LQyQ&a*,WNN8 i|L40py( p pyi=њ{Â5惀U&.45N atؚ)TEdj tMX::Pĺ?ˉyѬ\ı%d0%ЄDèB/XZsʖv#2RU]ģ֗L{b`!Wv7ΉD#&ְJoC>D-XN.&u͵5w2'CkG03Uq!M YBiEm3ildM[E/}`.a?tQ^e2ɘ;7`]_ǭ6pҢn_y邆[oeK8)gVWJب@E_[}aB8"=U%|۳Qk&%&/׷O18R^#yS7.y^zr[*@щ6"L 3"Gj烌aXǁzci2\ۛC'~%0?T{ 2r쇖mBv,!7K'9%ґ&D<Ο Z Gs|6x#$ H=0"]9!, F*ωjH/,ɚOKї\Ut>f/vkP2JiQކ0o!IJ9Qk@Qn_#zҳv9E5 :j99_YFOSJEO |vS8F)0d~ -]};v?pvqa_#1iߕdɂMհT( Yt!*55!9-O8BgV*EVCeLҕ G:KPkZKnE(z8ww;e1-RKFf5#%|B*6ҁE d tn,\A~,ul]Ho,C0e}-L"a{0k(6#,U’7 I`b 0wqk!xMH-]a"+@8!5vD8jo p%:֥}E}qm벉ㆆCڽ mӸW@@@?]J; 3ͺXrdĞpS:C_ §PZ| j0JUZV<ݽ㿠E˾ 52/XK.`xEc^\̾TnU~'',X݈FtΰY*P$spPC)icvTe SDr$]qi6Ma} <2ďkߑO Ŭ Ԓ$Vzfvpӧ4)8CK+-p9Ehl^kœb{Ls5z?65_O_N KLϔ_0Czy}_ )FGdՖFZkzfUYfJ_͸V XajqFKEЪ%D4XӎքH놽 =V^@/谇qgѭ7H@BV-!2ȋ=u0!*!J!¯7YST j kk-&$xda;['Lޗ Vw_5!b/LE(o_n0!%L-:KJOXDJ8qi:<8gWMd=eljzFFZr蹋y)tdkip#Idje E9.Xֳ= >{6 `Z݃'4â`~qSXξ\y 1>h6~9ӣ{&{6&! "!znXϛodrSb ([r1aĎtb osم~"x]Hdw0X#@QẄH׊{G17b 3ZS$oAcH$(ɶ;jH >Jpg?^>U寬evoHeP/ɯ h!݄ 1">v!WJcnL#=x* %`Tl crTTk 5Gh]$scu}z[fn9&l]HI ?Fۧ_h4qP2f^E@+:}h&oŒO!XKAcv_ut3)EymUрm8MX1b7F;6su*YhT.ݵ|Y)ʏb5U}`eH.PHExpZf O5YO Aji#TT`NoUddCڹ]f#%;) Uzju]L'!h&%It`1i$zN"*k#C;zYɲ C۳#[1\*#휹jNAd&\ Nѐd:@٦…0Tk!RF諏wYQBem61M:띆lEq~'mZʖD%A5^AsvU7Wv:Gkm7EG:YX=9Y+'qA@EO& D}e[;=LJ/E RXWae8d!1KT/[Z=o*<\#wzCZլWkXgLkYdspK,u'P(/PX=x5o=0ͦƙV>PG3Ujd9lY#ݰu#|JE>є3H% wVxO8\{SM?|%v50ԨQOQQUDMr&^<#Gp40.6wGXVwhD7r3}SM&>vمFWVG].i@r~VD/Zp_Q@Ҝ́LΆ-3NU7ͯro@UJjpܮ-BO>"R3:>"Pxǥ#yws]7 ,+1*ZW|_6O?vVJLΌ;,j=u^xC-Y4!qOU|JFc=IC@aBrלmEy]&D|e&>ktP]uidB\ECxoϧo~)Eys MlNás)[k %<ˀbKk;xqhJ %NK2۵mq5uc^R ¡cEa7V9:tŌJsV4Ъ>[q4͠\*֠9܈oȪp f I_" Geab\ӼPs@0/DRH Ea)cԺf\0!;-& q|s2> 8~s0C'hSF<᎓qw+/݇?MR: Kkmr*ts Mz=Yls5E嚝xUYu[c*˄s賙UNZ '?i_*D:A-x^UMbęGA a8"z~u:4s֡|hSO1IP/~L 0DAq+Gnȯ+kda)cW9͉_K8IDyjwjC4W+ @,. /Wӎ2`|\zUC _PxF#OrYE YҎm\YmL[ݔR*P'E]+1NR@㾢>'4z݆cit;|?/_$VgJdzf xd0S{an_r\jM&1?+h؉A:E"[v@4o-]z #d{NX&Դ=@H5X  ϊm>#W\_cQ^%ɫr \Ii'MZvt=+vP`;r% ! GGs8p\k ٍoɚ>u0𐴂RZ1# aF?o:/ Ρ;iXkwU'1jzf5 UYf"d^¹U9-P_lcsA\yxNK;GW"6YȸBhmR ҉3u%ϪO`:$2LiLetZJe'n]d7$#z4sE`tNꉍbt%||egJob?ۃ{%syے5;f9}/w$zltNKZ8Oİz?r0ō?߽zw.ˇìPZ ےēq GSHZbw5`Ei?DچeuZpѲ6V6^c81LrѝJèAo*IBߌ ɤ4=k~1vO1˫8ÑCU*2{Yp湩Ȧ7UÑ! ȂTQ9I)O\riA5Mb_~R* ZU I";T_)T4G+˶q(o +3rmt󘀪f(XJ=E,N}/@e6-"C{g4:DŔ{y>kz  'b ,ޚ?SWI[#wC?xtp)?DIF92RfȾvY9V~>˭oW}SC/A.^E:FRjKIS^47Ï;-迹"]j sB(OىN m}?+OciB\;<[ 5Xri[gz&iЕOxk/ajEmNԞ%#xU Ys 2  Cڵ ?YOQ'#{!̙ϋԕzD<^QKqgB /dy؇''Mϧ nLPw7̪, sQ Tɦ?,XYa!g1Q*Ybi6?cLB=K*MNI;J_dT%=v?/:WiWFȊ0)B)b/ Jod^K&d !zYȹ(`yD*g\7BU:wJ谫u 0m3e(P"t锜O%cZQLI{osMYaNJR{|S\:++m(dcsn@Tn#kfywLÝWph(F.Ι+i0ٖm&\}\t B2?OMyؒXHrcI"@a.l'"FOO.8aXfvC-!K7fӧȥ+'k 4|Oф#C^#|5ڮ'ae;VU^j8ZgATϫ)Yk/Ai6Ne[;"iF<ʨҍjH\@Jfrns-oD(=d!9XΣ UC؟E|C(4&`[]L#^σEK5%܋^>eX XU2݃Cw d@z>nh)V{{ :{O賒 oOuF] _] an 2b56Y\Xzh(o# 0c?O'KE \K0g |V@V_D(iÈU(sRd_K{XZig9DwBMÑwegK9M.$69d j з ʰN-ߩR;07i%#:HhB 0γK{H$C</cɿMRP]hhJXixA^V 斫lZ >~Czo߸^tPW(92J= G0u !kc s Ovu̢qS5,P;[nvYڠ`#nҌvHK m@DzJ {cLx@y{r ߺmF>?ل6m/;:x"0 ܚGȨe8`5J2􎍴tuw.3.'  s̳n~iwq}/^UwdË0GT0 j R3ANp/)BC1Q._I=FmSo=N-n~ء8Mtyr7{3C!oeH0SrTz~*biU h4XVW]BR, FN[ eF[*s xz_  v%ޡoĵX/2S ͂OjzYpGڼd'}]84H+Zad;?jt+ 0VqY'q^ռO&F46m4]hKɡ1 l~ g؆OrOFBL8AҊ`dn'SJOB,*wCo]y͐%Xu"v,ԟDb5LÐ%V`)&qz4omPGALj#J,(| -ل%-b5evҌ0uOқT>glcJ1<)bAc ӓtYM# H(H-U~*ͻ,:Hڭ|fC\S+T RU,zaqrotƭTW VS4|%?NW^uoK?8T$~h2&>j{hTs 2FN-yQ Lp60W5 2Ò!Ą<9]6Fe4'c#jI# Cw鉬$N TߴJ`,eB:n`A͵=jZĂskp@ QP83{ٞ9@qOUR9~SVd~άhg ӫr+iS f(L.f3y#L֔aBR=b1}!°j%jk8 ^mu8Cdܡb3n>JBֿY~n%uN#8($QnBRhS4r5K-a}] O.¬%ںIQְq`Sw7Ux4Z>ɡ FV(qwLע$ -^_ͧ2ӨY-b#v #XA#V3me2;e`j}UKs% JZ+{hnY1zp0W at[A9rfv\VTO e r'ntҶŏnamh#d64+'3d2PXIyμ m+8[ 8{%M*K"Ro!N/t%VmlCΒg AU;Y2i{ ~e$z ~uMvֵ CЩpI:01_* M$\hJ62c8P.B%겁!ц>m9B~fXN[#[ee B,A>e?8L[W{4s֠[wF9 yKDBo ʘ=>rN{YǴs8WQ~84]^S(#gPG'Ub>bNt(p7btc3 $r+uW&P\9o([VD|Pv;bn\RHzo1$Y:ΐ:՞E ;O6eRuLKiFlHtI0|CߵB ξkbFV?mh.\gxF#j;o6F:y҅3e9A/-V)/R)"X92vUv "6@[=I$QD0ԯp̍9Jϴ&! ;Xhbiv4%ɆrL2 nfM+,}EBװ@R;2/8yl)C B@!~ilK-j[?d%[zf_/~ƽ j~Īک0kLX*f5p7q$LH|tԏ$l?>?ZjPvwH`Q %dYvh3ԵPS,wy؇(4 ZiyS ѯr 2Xj-4fe~%dE:2w Ȏw }&*u E\盅? (KtΧd UYq%4E uo yo}*^W }20!*y㷏.{i Gv460*I63Yͱ%gknOŧIX!@b<'EcV." 3{rA7Hi"xxm]55sX; *u㿸vv}ٲp?^K)4L1Lǩu\|u`%&Lu}CSlD~ˑ{'}#! !fd7'Wlq3J64Jс7Xi{˱{p7ԅYII1!x]!!5E*!+YOppkbe":Zΰ mgO6X[WZ&--˦qMv2X9 bG^ht=F~ -MhzUk=Ue3Ceo˚xU)OzCU`ĸ}ZQ|k^+]ʣ,J[*$™Aj%pmu!Hdo>u؆&hcx%]+jyRKfѢ,Iʃ?m0i-œS3R6v64B4lJ`JLV⮫k8h;nJn> XI`?؅O+ ?4:<(<0[605Ɣ_}@P$U@'@SHQ%uPe 8KW:+dg y"Ƿn+E/n%u;;p)uN7RYXZ7BSݎ¼0t\}6!a7}Z, [r,A\#|CPNR^q8ڦLpXҳI'kqvvt3:S9k{s=!VO()"m I# P K5QLI?*^3mdE׉Ŀ,iXPr?L᳻Dx @%\#n#͗e,9 7jjX"R6?:G"*<$~嶐~ IƲiPTd&n9ط/:xDTt-/Brr5\Ng_>NnouIՒNcCwT"۾ ]$m4~nn!҉;5=:d̵-++ JM2T^ *) %:0q?.; ۼÙӜEN)ΪS.]" RKzOC" ᆊ!m6!'Ðr(è3G{팫%FƏHȹh3^/px* Ѷ]囌4?IQn7Cfl;GDQU Eݔ)8 cZfÎ>'5!'e؅zgFRq=mAL'H098غz&]G)ӨF[f9'eF.0a{*op?B%oT0iU4љCS3mS@$rCCO+s q+bkk0Cs+f(?5IGo6$Vۄ֧ܵY??&]qΟ"dOl1`i6hv g1S}Lw:DX́zu7r+&j}-Y>#{9=`  Sb+IPRA;-[ʂ`硤n~5i}F+ <AM (@DȆOdoB*nҨh˶rO®ɤqV>7X-=Vdg8qHnhɬ#PHn(GtJVK,hܢ+g5sA4RvcP(uz)F) iڎ#w( KHaf]`5#6m'2гKJ6UNħo Wl(&|5HRs)?zN7vs~APSltʿlW FġDVX+/4l qpR]4g(́ I^Բ^+f8 8WlU,7N 큗H@ aKs{!pE]zd/eI(rUkej`޺BtNEmu*:\ǵTqZ6C>6SRoV|K6-zB{$+)ެ KCGF /眲y.ZF^p%EL.TOvUjoWaɜ\ b޳݉С03E@FX9@^`$XͱXU aM䗢"O8~X@NN'lda<ʃ*tQ]X)YojyVzMR<"{$O&/fѤo/}1&lb"D'R^I.KvZ.YUP9:Yãa20fGKNxi_Jg'4:гTBOCux5gAtSl^FmA)oA@7Y\URӓXrq5;&foX $Imm9:=z@- CdfI#:JA4g=QR& K2X_-4wKG7^h/s0Nx)Re(OYX~( OENfFn:VƈtPko(:M r{{#ljr2(5D|е_j..5bIKXlpMbf%73w mGi>4Wq0"3vAi{.Q t'8-ZA/VYY-Oq=qXnlg7E|UÎxO-4SĒ 3!^!huq1J1<%IC s3)dYlh é7Pa׉`ɑ +ں-4L,5vEU䎰UEY-ld1r>Z_d,A)Hg%0(hOgz)ZmB#;j،?CKJHx]d#a'NKߍQ1kynT=0uXl \tZ됚I|6ET!¤b}(.g֡#է׶քnwCÇSe %n͑R4{?h&em2` )um;= ga3^x(Iz@[HČ $˲ LknP]Z *v6%o$&jU3LrPbΔrelM WFwˆېV  QfzWM,:X[q G,DW ȷ(5GTq-eS)FPrrGE8˶C2Q0]yvyiaC"^Q_PY/x}| =jۼ0JV@.R ǁz;O~rca02}Ga-jԅjV.C@65}\0JCORB[):/ q=^I 7kFODw=^̬a8G#mU5;U x3}dتգE&̤eqJ#U9 XWwQ1YP*Ҿ8?N& ]У:^|Ʋ^IAF/ ^Qy%@H\_'|3hvͫn֗uOEF0 +g?Z`=/= 爽 KqXJ ]?3@q:+BTRS9vqhXzeBV#nj{q֭}>(0}W!AӅL-S ۅٸB_vRi@uib *CgV12kGX9*SvBb~#M9}?Bs\Ae&Ȉ, ظ|<-݈Kir}&~`+ _E$aBeš`Y;5n3Gkpt9?xD}u !m`|o'7M =9l"yvOǁ+p<;Űq/sTɩ҅J}6O,b =Ae/`%v\ٗ_/IJYrAoq`rUfOί%6IO"DvgǠ@,^H?οg ذh~B,_\T$,OFs1 |a4ʮ[ Qbr DDH$#q?Ej~xF'Yim|D%ّ ~a<,xq牜PD\^FTYy xA]qف$cs.{&i*_*v WиN9 cKL#wqh yKF/?(D(pz9gM˶f^˗sقin=IIkӔi-d^Up߸; r &kwYB4j`YjV.؄bx]N!!lP!iɅnzC!FЩkpc?U,~ey g# M,O@yUDAs@KSgetlM8FA]w)+]GJe8s~0{,,:n#hE!u?C E+4$NEdpkD>z~۫efcUV 4o&!نcbsVɝvgj_w[Xef!ެưܛ %[S&4)r$xlElq`f&s{֒~I?7=!HhnO/D# K_R?FL,jd_͚}EfAx" ^C?E =^2X*xl01RYQlB0/nEiar*`^+j*'[@Rn13"vϦGΤLSf,w2JeR,]SD):I K)9DVtcha`o˸68.B C S {vn :r CvNoDxs?|/x lT )=D&0> ꤚ%H(s6Ɔh}= mhoWPGq_잹Eb{ #{H Op| ZPDs~(i#9vh1LQp@RIuɧt'\rLbh5#+hK"_xJw( PU!e>*e_cޭe^j)aw(Ht+Q}z@]߲WX8̼P3^.|dT-;溷 lH"c5gehm`. m4R( N>JQm翲P5Y *L1bUG1VR(6c" cS% ]s$,9~my5 MíͽBWrz G7Fycf疑 ١:e'&Iˇ8Q0pOaɀa6MG>c*O:6j&ټv"g{s&WɼzUY-^άy"mfi9[勧.p5k#Dkr/ iaqh np0&n1I f2jL">qϦ^I;D%>U:B ъMIL~זϣ5TIInG\wo nFQ:z5I([ kFvh<Tǚi>)t@(X! ?k& Ohȓ1qriO "O y+Rb-GꗥX{/@{)6Av{m)4j, a3vTaF<(ȁCqt?6kd=8mmsLZ&2O,}R(|5dKۆqa.iɐܞC;|v4<>vBt;z6㬒! I9u@?}3X>NB."5 XȂʏڠQWx.]wr-^>/]lE1s5cGCH^\*0jƼ tPZ-]&[q^= jPZ~*FKf 9Z*W1 cBYE䳋ywUgEJ< , _; N~M(D<2ш k0tSl%zQܲaAD}a=9iKN~1_/p3 d61=zeBp:GYcXVt~EλX/M3 $V-{.4|<ʅEtUT@ ,.rk?R*/@m%rh}I<~w >茽 0X@Cހ cCZ󧕉V}hPrkvn\Sµ^#N^LڸY*Ջ 9Sԍp@SrzcǾR-WS ,MϹ@ E0N#/`_|"$aB'LkS@:% s˰T+?,SQǠ1FkbqoQg3K UGpPUa\N'LsH/ (I!w/@wjvn,TB ?$sœom>+ N,-PF*le~>YHs7(tmCshryVyJ*A{\yw ,w2 ucMuHIUl}'ɥ/ S!% w[HHʚ[`DX<?Ews}'%c&v r.~,YN`$?fvN.VR1Wt:d<'![?M%[a p{ {MtP8+ibP,d:-e r%Ć*}Dvj7r-I0F3+43VPS'6W=iSLq1y2n5^Ou}/h)$m m+6QM#?jARzwUɡKȢFk'U5ڽDwx <zLyk}澞'%MF(кٱߩ ۼ@IN oWeE Fp !PݪLם0S"\t :b%>~XNpfK_ҽ?oeŖ>FBs̗CgZ+sqژ-hdG$=Vtg3ܳ$= Ryޭ@ fTZede"+^-1SBHm璡꬏4%KM;Rl4HC2(?Lj.L@1t1g;(w!LW:rI~lU`%sk2m35 9 qh?ATԫRx-6 EG]k1etY{HDgjliGm869ޢT >DiK*ՆlUxeD5x^s̈́\{]*Zb6eXގ쏾A!e@rS0.l/H"օ6qƭ@o mްrgckg #5쏍>׈Gg+IR&/Q5oqiFj %ڦJϟ6$!GH|ѷ[ Tt9O3(,a)>yV=)(Cyi%|:pp`+#/UciRIVfHHeZjOuwXj̍`c ~䅕IPMѝjQnE'獈1[a_UIͭ+C:KfAo=x2 Fu*{lզNl.8CuoA2=ߙɐ4x LHKZhRK܎)2oemq? 6 \[dy8pM842)ĩUZv4?fE]b 秝5l_1g"=K2d@ފ?D BaęPخD K/Li`c1E_vL2H3pqwP)Ln:H? 8̏NݗIy,nKV{N2wLcua*hW{L_5)'daEHBuS ~{EYp2;r64Ou'q[4e~HW̡}2bZRh!_YL $^mi8r䏨PC%%.JB_֮xD%htQK]q?|&n \(PSmܿ(߯s׈T?.W|V ؄g*:m:)êi[4)2C Q]qbk &MCEXuUMyMMj/g%hNA0-|\o\+T~a$&m aԙ֮E!p.@qwQ:7=3 =5q=]X%#e8ǨDcK˲YU8qjLj9xcjd'ٞQ7K•"ic\'DK֙t ) ;VLh^xړ|•nwhO~@s0hd͘';Ta1c:P&Ѻk1G +*QU3&H t>t+ śpx mc6N,|R>И}<Qy{&`4@[*;;'9`j0v]/g pܩ)^ Xc* 5 `-"=gbѠ(Ly1Yjq?ڸbh(`C0 T]%+.\j#Fǹe A3|DY-%d"m8NB/ T>^e)њrI9ޅDfz|fHc}euuYtDWŻd&HsCtv FwgA}OY7Q*+ Lm:'NB΃R۱/]wџ(b+5^~=)Ps?`%ͻ@aW;e݁{n% ͫ7IM~u-]sfPQ.Rȝ׋s |A͞,PoLLO @/hM9vKd!j0i +jUvJVc.cw Xm!w[*|Nbel,Σ+|0;Y0wŜl^MlTYܙqY{ SEvKM y6BF4-@KA/d=an4݆s|x?͑,E"r /ظ168@c dad^{N:B!.yXJxoiR ]u-q5հjh,)Ҁ,)(_&lh=vɢYS,z9wBNZiFx;$(Uyz~g!H 9D2VC$ nYWm!%N,GRᐭZkȾ*oIoym]CrI/R"P@rƛ>;^JG7 vj7#)_+n.;W!oOڛDo"ev|2_6P[ص2pp+Ed8Nm>m~* PE2 t sqPٶJ3ȟ]SI>*k[(Xx(W 9Hb\A56cBwJ#GjZ<z];ºSO$؁f#guD7JWj4(/?5$TY Xn1pxQ`#{%B'؞LSw?|dw۽1 76 7S jZ4"vYV?GA@wΎGP,^Nї)cG=4_`Pnm9 |SK\Х@FS:>Mrlh=^ yJ`MkR=:UX0;Z0i>7~m)emNT&k2ۦPQRr v݄B:4Tೀ97R}yQ$BCBj7f`i&'zJT%OTm|y }PB~blTw2턇iܛjv$@oicǕtJV1LhsIUO8Q}N>cn=|e4r~7oѵ.`yvdZwj{t遵?~鶛>*Qah0'RiXQ)W ӽh| 2  nV"GHRPfH)[-JY%t+ūZi;w,oZKUġ\+HZ}%?eMT*34v] V]=8Vb|jVQ&lzGal?Zō ߩP:*;U(ޠIMf+NVRrͬ68̤/ Oȩ"ھ(qW ZzB#Ukodp"M@:74@^+^]ϸB^QtN Qk/:/14 PGhsb|Rq%ט>1 6lLzT$%LtBcTQ8CF6,3XA[G(7'x;ȱ #],3FۉF6AӨZ6UVҨ([ʉɫfs}:@Dqp}}S?cM ql݊D< RZIn6Ql;F%((?=kpxOfO47@\TlW?~#j3439*#( 2YY _ѕvr$UzRo1ß6CaӖ@[rڇ̞|srgON Ѫ'*dN@ۑkLKXĒqF1E9[YfY\+#,#@i/i%/}oe"]~tAw|Nk]"b! !L鬻,˫U kFD`n\oumky>S%yRg;U?'6}Hݱ?ţ>]qtZ2ŃVT>Ӟh.^\m%9;ߎB{fpERﵷ!f^\G(IW8ݱ<^4E_ n,k :DX /yQ+.{ˠKԅvhRzsrjpg@rp"(\6ƙ%gM!0 x6(n6 mM~Wsh ݎ<ȩ ,(ca }%5 2*gm, Q5K`!a[rM{qҔYgIP_9Lf+,\Ozfns3,νTR!^,K =ۑ^ =a-IJR, qE+]iEN8O;LIݖԌV;cl5LM6Qg)g,ˍJ,|5(ҢACztM(X9pgrhJcGnl滞x/ؕv) g*% ep-%[;suSzD:7XMkN7v_CVE)~%u(Bzsr&Y YOĈ]PbJA}cj 1נPEjGԕ$$Onؘf턺#Q 5] } ڽYW6 XXv^5}\~U!ԸW)O^=DlZ%;v =% i;_e0fhy.罟@kЅp\ьKjI6v;ѱ5 j8oSjz:Ou KE^A 6UgvMW0ypa.xDp es^{?v魰4@lpo>|Pu\RK>ؠjF]Bd>4Lunjw& %'kt[%,-߬NhK<ЮtllV8 ぶ{2y\yߝ"͋CÀ9u.ᑛ|"U} IȟEFJco9,=n|Z3ylkg*^ i;qLWd$eE.ES GDjӑěa_ITNͿ[ogaN$$IFFѾB@euا Ǫi2,݈`CK+ ė;i W+e[gLfOv)ɒظ ]K}8(鵐aZ $^R%aL#2nCc1Xȭ9^n,Rq:F4}lfiTu -f_OҊ&VR d4G<`َ+tqgpsK #*M Wg|g찒>)19f̞*| /ld҂"Q U=7}_{2>iCHtcN ]˭Ej1#+VL Gg|Em6vK'5V4,e8HTC?a]TyH!g봥w_}u/VlNmdӜNr|~ӑg_.lvc#u!p:`XmR\xPTa؊LH^7-OF9)[X\ {q UƲ&sЇsg7E8=`U/)7)2zfי3BCƣ|U^ H"To4'LXQ $xr\|<4:`j\X0DdQn|6c,J m|++נ̏`kFGeZ܀zfC1UR5q٥u 1p:a MÆC@9C ] Xo*2+w27rBσŭ0lctoqX=B@Urbz-zT^83Tڿ%rFqQKiP7ޘ9FHY[(9 Tn&͂dlQvRVJ=vB_f"My~]Xt N?_TEŽV^Wi]ў϶e J,'=n:lAXO binnA4ny-Au3OjGe/b0Sz3 /g Wteir7Ze q>6?4Fэ؟+"叴gغx@Z 6CrEMk*M\<{_E5(xe 0p%u+y3|jJ 7t_&W_C K2}#?c0}HDfK jkLN݁k/nQ-pL['D\^'켏c#&R]^[aYKط;El*2O%e썢1_/MbAti/E- &$Ǜ* [B+ɶ7F1e%tm AUt 3k> s^ٚx yeʼ/7Pw8b٣6Q=q|I BƧJ0v]֗[jCA |An]^ B?kwa'0ߗH T2j y][&٣KC7i7i~ EũIN,w>s}yTa)yXoW&MMÄnMWꚕͧI¾/36y-ϤV e9Wz0[ )yݧ蕳5M/Ƶ.+;D kXiʟ*r${wj#7_qB<5rce1 KN'M"(DZjR߂_b{ŭD' 쫋c֐ o? 0-qz sy/Z4{^s0z4[{8 ILv{T4rm/-ʴ,p> SjÓz)x^:\%tz*<5B Do. tBܭ4Xս_Pa}h6J###)6C~&K, +?)i3v9 %eqB4iw3T͍{gC䂓{\[-1-?]pzgN#&2 -P* 'RtSv© >55#DwG~o2{)۫u(>:>g)DthX{u,Aj',],7}Mj5b|WD[j) 5LZiFw;ktLI$L7 yW6BtJ|ϫm]@,;爌PiE<'XxBr e~VkΨ.DCMgvqix".۴a0FYӾR$K؂3 NG mI3ٚ^_^ U˼OYBZV;tAKttpfmvbO&x!/O&T-<,TVGo c]nL.x)Dh@') 1-)O@z(MZ>ssTd< C56"6a-ךðK=`F>?t ž: kLac X'η9{0)!S<{%F3!O,wrlRvJYʵ:(7C=i[cA@%55="%)ag)!W>!G\;F ǐ_nk,<ɐ N%R>y4NASc9e\vbFxmo٠SP`Sԓx 5 U~.+o?JZKNh'r؞ʅl| 0MXrєNxOoAÁ'OtVG<4ajK Ir{nSEՠnQyV@qʼnWɻMsA{r2 C{uέd8w:{id߯=Ҧtr/~ȍ 8ϖ,r x 7wftbV@D2V ymO|Mkv'eT)2 1u#TY\3|)I_]I eyaQGLl\1/> q-܆pEtfd ,ª-I3,p1@<+c3%&8팜?sY qO3ka}bk$Gy -#['GT:SuWA3[2u?J\ۑXSG: zfAŒ^߯4F]=VGL,0TŸ#VI1yDSP8u'pU A\;$ 7̑تEVÊy\,~pLuN\-pm H?Aɗ%=+e񤛥.&Z{)}\mO SBa>ZiJ{:K}>ڣ4%Up&$D(kCGC UCC lt Qu% x3/{ }M`"rRdZC%ʢkyy2yo 6^Vj,Zm{cݹGxhy)\ȏ-NvR<{- (C c!H]Dh(S& 6\ 28='n|!J4oXwkC\&V~i2 ;Qb;C$Lg.Qa+wˈ@bGH$G zmf<|Ye+iĢ"FFQ! f>A;r ZE6sbIܭQId~.4 '|Й/@8շ+svWR2 %`wB E>~e4eURdWog0~ ԊUj_q-,IY: J,WIb5~J]"D81K#s̔ Ǥ6.WSa9SIC⺯#4nuykb>o@]ep{m?Jao.Y@Ļ g|KUѨ@C9˗e6U\}n-/A9m.7sX\hWę\[{K|_⇤/˼LS:8Nx 14)ZMȩ=Y/ ZLkKwtCJrb w s7UR˙#+0Qh?a/6[~D6J٬^ ft*T !F[~-i Ls#>f>N~b0 ͍t4znHuáցuh`[6uM}j=:f5KXϙLr[鼌p,uTIV3]K`))WXyU~<_jiwHsp#fa6S';NKnYbB@$CI]kkRtI0?'ymu\ #&?REWNNYNs0-)y@(<B$ rVf[9ڀ(ZZ\4;Nv~eIX؈M璌-w ͵_O! Ds|߱aiFl-\儗>y[-JlΪﻇvwc;vQ̛> =9N ?q6$ R.ɲ-]>J)i\AЛZΰ_2a([bɾ\'e30:ff+Ƙ29h "5|D>%O?0B BDdih;=[Um)$7^Rqsb`ih:SM/_Bgckՙ,1 wތ%"4iR"gC#d]̒:BOU08#o2C"I}Wx}orS6g{I͠<)Í!GmEG7dz].m6bIs6)R ( #P.gi!u^ !<-%|0ӽ| ?N!sSe-2QPu 6F+1Een:=]pmD)CBrDy! }@%V/!'X W5.rH.,fڼ 6jL;%" K{+=iW$OW0@LďZn~#vn27X}'&`잸o7p9w2_秝lȷB6xj᫤!VUb(pnK7JYԉ| IfymݲK.k \0ם!6QkD d/3f9ʚ5[]Q&ק%~UewjbL۾Yh/AM> ̈d87o2Tu`m"nڛU_!f0Ѕ(B+= ?1;޸i:ݬڣC$v쫂\p:2߂C֭b6ό`HQ)g,C*گ~"#9,B5 9 $rᜀOŘÜPY(@?ӿ6.x. *d^%l#r _U4P_)r@.^Ӌ]B1 ȡ3›WY)Rs iLMW_M57m)4okgn &5̧ғAf.qVVIҌ} ;̊rQW5{ Ik*brlZC*ߌa Vwy-3(9%sE@ᬗm(n5Ц"#"2:̃bp+qпn!\h:/{j_}*[AAAld'Zntc;],M7*`O^p%Ϟ6N`n}b;U*X1jSŕcq(G*f˷s0/HZ?2m~^6Ϊ_ '$US|9-$xť%E,\0]Fȅi-E+M-ǮcUMR9B?RKs7p5daʘVjTfhOx[huܰ<[aHgq=qЁaΞtd-bɯSÉV8`jP)#26ًz@Y2+}^_/z6(3Hw\ǑE'bYVz#u1q: v:y;,<[]ht"1BiO+o6U[,kJ*+f@onurȝkj]9FSlqkiՌT|\e7 b ⫯Lh{D_UaHrɝ{ /@LGɅrWrg49Nj둕9 1/] eO,: fRdyU(E*w$*%oͩn "?uyP>!'gH)?`+_@܄g 2b#0e2υ3sL^4y$ ℄<d|8Y0^}==4(-Xr4$ 锸⸄dp$!b'ο\nYAN[hQD:tz A.g- q dY6^wkaY֭^LU-e7w <÷e"ȿqtF5 qs8s2zk= QEsC꧎6";Wh"8RgлT Zu К^"V-v,ʿ/rjGkX:ΩH.v!}cw{q76l6T1Qgg cd`N֗B~qa*)Mv{+g=Nsxpk ㅷ)7'N[ڰBk Gq%3VT #hv큸"I&ТK!IʺhnR-ӚrHİ2`Qű(IT >K1n)B ӈ.8,ㆨK6]5Sv T$aXB).c2s}IBQ ?!qvg<yu7"ZJAz+G] ,(`.-@M簖ydDwm_b(&V;=&'b0v3E\ON(ҫ?<Ĺ%f?T !nrjC}&9^xMVz nz;#haI8Y5pZqUkhasXy|/_Rg+}MsGs{ՄfnЫ<!Һ흞ؐY)/w= r+p09Ƃ('֫:Kk|yi8h^jzǼ\2pG$Oy•DNv 00z3݊Is!A~~_7!L3}QkMTv޵8Ȑ+$7o_Zyڣ$mc!>:o<zL"M?G CՊ }UR13Un^SS?1I_aY;a=Q80-FbW*`pGJ7atOSUWSU+Y{Ocy=Gㆩ˕aG@>f֣`d," TY0! uŷS%7x}{Z`VHT#V.(ʚx\b](4@`a}caoW B+Ӑf:k $=I/CBid,K\y%+t^ ]FR .8!zd2:\hmCֲ>Hh?tx1njb-2Lbٳg6r1*krÄ~#Y1yCSjMw WYiގYA>}ά +xee  rA#rhUFɯ7bS ߂Pz-4[rgQ3 >6=@f\ulˑgH aetkXăy_S* j|cdCps&[g/a=W]QCv'ܤ%寍[X);!f/֯Q B0$Y=^e,bFǫWK km"ZzYb+~DRXRSǴܲEa)"f9eijQ`m8mFx$~n5VeCK*NB pS q3z 5Բq1JC7I`䢵sg ZdjRCH )ͦ霝3,[60_ȐضNPEæЧ=YhQ5bhi1UTYCz@yZd <=qڀUL .C[;K S[~*E\e$J|R,G0Mc;L:lFeaNRF律Cxt*KKgϦuX5Jtr2l LyR*`M|BԴa.7aTk6 $Cn9ouZgFBC%<9fJ/abkWc} -QjS:\=O5,uNsaAP83rjd@ ajOe-.֩VK\ӱ_bOK箋_5XagFIbe] C5+'z9 Ŏ(\L$Ix^s34C}uL3& jHgng9RyTcl)cB2 8`,Fͻ4qO`rPaTDC_T_97҉R At»{\b ,q&B>ZFı>7ZYhVdfs WV R=&h[q0C M Sܸݖ3-/*v `PhoXp]uȑm5``58jZꌝz # Ah)==>fcN{cik`OJfV/e`P&xѐ'U?Oo Q6͔4_ 삣*Wm3VjKOpia3?Sv'sCyS=:hGb`v܊ %x瀹0z$&CIh3g}W^̌*/6&IMJr.,19 1`ǓUq ]Btu6F3@{il/ۇU_7Yڗќ"mӰ"޷x:k!HmiyA`[Fm0Mx u<5J_I(I/eeGs.f䓝Sa||~{R1CjtS+~SoI]'숫Giwy[@G N]{&r`uV)LŌBO}uNd[9W(*"| `mp z"IwI2@\vꮘ6P_qґ8k),[K>h&:﫸74 5hf9[JVHݔ+['Da,M ! 󳎟ZxE J Xq*yӤzHYf=Zu(a}|@%$~P^&a4Im[|7b@}͓zkCS9RO]'QYP6, L&[$LX&Eꕪ&^R1U*;8f RAiPvB[ ?ͅhAiC6{;b9=?޷~H[` x" j*րkUз_%lq Ljqg,*WmD^J\eY0O4$d VQ-_X|'+ۻbb}*7▆sH99ЎI0ub! ~ф-rJ4O)pp@wJؓQY\QxxoP%cWX{YJHPki:h4ܒ7]L̤n\9ea>!XQ+3uM\{uKE#5̏H{_J,}sa@4aBdOx?BRHnqTXo잩C3O鍱z/$IFL7PHA*{UWRDu }Ω+6lղm|-FgP%_{;B1,9 mZ+>JK0jUP/ :.".93Q~nkD.N]*#,Tεߒzjdaބgk ::;ze@oo_u!Z|9 ֳ"3ݜɲ>?c;0#ZOH/% N: Em n5W(k3K|hrn䄝-^c#gsvnqrxP Ds۬(pV9+R儛{]~z'.NvxZ.y$9`ԅ0>Jd<6Frjߘ|E]_x~Y)9p4 g$m&hzv:nH⍦*5N0 Jڞqgi-ϊ-y@ؙā "~f|F29X_\Q ll%9ڊީ\@{ҕ lTyeg<G#aH wXg柾J ҐtTK:P|.¾cCNMe{|6Sm-Q@x9:&A]6l7ED Zmћmzd\T0\2>w!UI1#x5U_ БsSS0NsJ׿K訉QD 8鶶0,I8st%ҲE@vaQMҚ#B!;Xz^Xi==s_e.\?Fh ޿jgeכ8j:jcZeּ֢u ݂=/Ჳ'z+lt,NIfZva'Nv[]`I$ZX=YyT_~k8S(#Jqp-OAYNSBN;:x̻gW_U|xQ3Iat"rDa0XwAiX|ּm'XوVF _0$c0RZDw`c2]k'zn=|O?]o~\!]}ZU0ǺIN'2Ȏ\khQ`ҁ]ZL[NgTcEh_%h>#)`pOöޓHRmȬz Cq(u5&[dRao:d6`} brrV؝0h 7ޗD.lDmOeD:A24@-o*< JZ&hm1Rg,Qh%eVC-x4ZSI3KZyd!կNk꼤ZEĵO1Ԯ\NMMB3ru%\};`@#BȕyBLOY."k`^Yu̽v4h^ԫ> MLh 3997CԹ cS8[&=u"B 6M3Ich;qL9xTNGM# q{e~a-0 ȍ$_T8}pG"ǺQR,ZSϷcˎr`~<s5ƾ#^hCAr-_Im,_soi3`i[TDIl4"p,}JRUHҶ1s ČaNW̞n'2HzJ: .UE쪗o(x.E严Flb&-tQI2{*\ue vHA߻3qUB~_PJ\;?ь`=P>7v'a]Bȯhm9c;TEz @Ƈ*j5J]>܃gtQͰvL~hP^]|ީVpHP̦UT]؃,5fWM+H92aW8}BOdtAo`%NjnɲPd߾8]myu\2}{#:e98/Hʧ2(Ϊ1@N>g_f~jUOtF~ޱnwJ\Kf!;s41r{<h1<ϋrxf 'd\ ^vZF P|GܱRH#b}zIf|tм- Uc0Tg4'/:T@PUƦξ 7zIٿrN]ߘ'w[EayBuS@±0K0E[j*+Ppu }/Hl]wAdHa&N\֮+|d;OKn$[UWʹ]Wי)*Vˡw|5lϩc؟gxH76g:a5!n|BKƽCqIBR@F;eN[{*o^q&@հ0zR>| 8VER* DҖD1C4^M ?2r|$ # A(c=Q ҈.]B6h7 GvRU;kɝݾ>ʞ heAy8^JgfsfݕH oxUb%8wړ<3C!D#|QdppӦQ dgZqܯi-9 ~,"pѨ` [7R<@@#r^{Y>x-}R`lIX w<]C dj]\WKlX4n 8%} L\ψJgQE2ZAaÁȨ`3-Ϧ98zǶ%!E pvATe @r.Cڂ24TgU2⾸+dƌD4 X<όN(_šMɚ# 6$rqiS?Ȯ'gw |ձ,qU>o36nM=~m0t^,wȔE&Oc/x7U0½ jy,MfH{Dt` 0%q3`U;G-D 4_hUu]8Oq/p6Eso[P^ XN)]ȱfD2Xך4՜PQ~2KfW`SEv#7|jJZu6J.*Y9 ǡif:Җi|Hr e +:@yR'. n(؍k 1S-t~aLO,fa=)9@{17 \$0FJrT13\Ot.(?k ׹3vCBׯ'&9]9lgENn6\*ò$aP#n*2ݐձǣvz<ϗ@"m$PkUVLv[06)TS#ڎ&%J8jL5mcWCs;傦-Φ7v%ݖпEFӱ8SF+ʁڦ]mC| #k-3H4 z-m +i}xfǘrsiݰ4oR`־~Im6x2,*=]Rǿt# .'ƠK[Ɍc͐F*(4*5{Q@9׬&mJVQ3LŠYw&nV+RfvlJoYl= ipa%YrXJ?e4ȇ`&{iW ^t GW@[tgoVKJóbuV|, DÅ |07@Ib̪̃ƪ(%j%{8#k}VL~,a:eE&Q6,XHG ZezD#勹Bm^DJWRnS; AK+" zgD65a({wE·UBQ- {jbQ~vtHLۼy0r6,R( 8E /ds:H)6 Bc?~n4mu%Fv㹇Yƥ%"W2p?'9S.ӰYd/_38ش=Ŀ`m@Pࡻ>eߓyMV-0a~]BLjleTs5mZ<<_UB yf=6f ]: s{? M!TnwdIQ&SJ@au .9THyqzC"a~hJ]6CF2>LވUa78A~.ӡmaظˉ~\9j>AH+b2 ݙC:2DK(>=[–t` ])>ANjr̐62νwH]T[z{Mu dp[KYs(/Θx&5& h `Ӣn66x<q^bG.?s4{iVzp~ٳq*? 2rPsdZڻ6&oW~eb*I54Nګ;(x@Y,16U-QxdpdiQ凫Ff\W.lze.B25G#6qOA>GPSqk:98,oqđR]Pɲ/9˥Q1 UG*ӡU:3Mz8B65nF>peaU|r .)m> L^cQD䠪ĸD2ЖSm_uiIpG6v ʣC40஼ypSO߈9Ӽ9sT1T% ?|N=pEiKimrnP(Hz:V 8Ցk4G0v J^T" ?gCb=H1c&F߱L %+C0Z-=ͣ=n[0y|ͣ#Zьc fR#C]rix<8CiQˆ>hgz@~E*x@# (lU+%hd`2Hb'iJ J\W8`ȿIt%=/FV7"en͕D'8̳YAU|U !⪽_jduȍ^ǥx=(P HQk |uNJCҸ+c*虒z. pj= G$yOݞuK55>^la@~A]CփmOȏ@?L1rձ4 Q#2ݣ7؊ຽcYHfhj..6l0%J#˔�㣵X枂Bl{0HpC/Gd3b')mԘjz {(JA:>=09T1K$nK\H"O{. ?aO[p\HWOS⊭Ƌ;,-G_T8AϚb`\4[ &g2iA`w׉kV?wN\ ~89|ßmA6RlyNʜJh{Vvrb,G0݁X6e9a[}KH^Dcހp>I3ٮ=aBQ;A]c~sDJb@ǐCN-;v#&E %][?A.wfO7(8O-o F 4q>ωq/,/(Kq) D$iJAt=haAcZۥ"+HGQg3K.Ns[ șbq,=oEюFkܸ[lwV 6)RvqALO+D Cd` jf+ΥWD";>zā8zDr'H:߈X~¹XƏ#O\sn(M&y )yE `yZ~ye#9O= ;ێ5I *~ص6<xF~,#$a4YyÍ2)\?Na(_)@9>iUK9@n wȏVKj Yh}揑@uX` 6e5. "p]w7/4h8랲nTN4et!Mbtx{S)u Ny9G0l55]~4 wR`Z=-PV-;֨[jDܐ+u%PN h/[31RJf#yR$l/Xvv2*)hCXiDf)#)a/⿰qCXg{WA0hQz) j))c"v˝ LKb/ )_<];b36{x3̲tR2SAV=аam"|)Z3kڝ Jo+sȯQ)/lU%.@yO n`~8Zb` _ x;\<:|% U3TN ~QT?: Hyg6nUlLwQVZAII<0,@&/ xf"-nS\tmwNvxO-գB( K v/ v+6Kw5Pw) S;Q'$?:ĵdv2=h'=T0BFq@1ֹXROkǽw&S|Ϯ-g?1o|@d+iLaLl'S+n^X\{hR$IP0/Pȥ Nں~1rp0`r3ټj s i>o%x  B }-*mcn.eب,'B0<\hJIɜ HJ|9t 3P$eoZ&qZa 0)FÂ}cz VϪ!Rw*9"߽0J6y.ƣ>K2Nsmi Y6%o]tӅ;{iqs f U\^Kj$GvyYKN Q mܸH,`K{}ԾM6w 2aݮAsHD4j2'J?Y*Du63! iҞN`laQ_ hR!Kh=b^>”FU~fIK5*W-h  53(%n؛X>/~C;=C?FoK_$/zCvXY<+Rb>*hԝD.$Ĺ+#f+MOG{2򨺜r0$6ynv,ҭ7ԳVQg^p͆߂`x?eϧw39L _䪊eY4FZ nb:=&^stqNEn{h*uyKnMZ${z{pβ_leZDE%)F'J6j|Kdg:k]Ɛh.&u.V} LYƷXxOw+ClIS :Gi|C%%&^}RGX6׽%߆?[Ǫڝ޿5zLT},_}ΘHVR|zPK}C؍ԄƸbJޚ9`6M*+Ff7dB7).âki$ \:1w ?DtDۛ,?CTSEUu"bXM6?ӿ shuq!DY%n g0 uHl ׺glf W-:<0~@GIV7j$1|C#7dJJ.bka%@2kGǎE_GNùQk3=#nNt_;/wY۟ R,]b j.j[Y4ԡ:K!d6T=o9 I](>VɑZI)wr`;,&R|_ĂPҹ ˢyLƉn e) ^2:wkja& Gʯ)U՚`᫦ZJt˘K!?ĵM~wuzj@3qxFr!Xa\`HLH ]x|ý'6Xw$@Ц(UT_s%=Ge']xLcorRV@5r!VX`JY -/佲v×'m8t"hbfsa75-Śѯ׫3b1fu&bn3,& Yze/Mr 2"NVU}a6ċ˟vrz%C[C$E`wQaxv!/nS04=s~Nٵۣ/wo PO1A~G)+frB}Dsb+^M3i|7+1T8gz䝵u%щb]CZV$\Y8:cTP,+PkwJQ)]\ؒs,x{ycxyV6g!K4ey6굻u14-,M[>& vݕqvV X-&;nhh)] Ce%DyQZe`e!R)}T뽙uwL0]>|@B4۩k)rR 7wPkγcz; YC}mBTPzڰe|70ͳ]BTS( mgx`'']6]8Oc~C 'hU]䲖b^p]g]EW䓬jCbU޲3h!( xmkY*X3*, `u?PO{oq=TKɉ6fKw*@qJ%,:h1{'9Ag5mRc18OK=̎NBI.? |\ J;m5caraL3j4/jnBn~hi*[ Ƙrz xADppշ3թ4v=SXsTG4[L MsAz^dJ 1?~`tQ4q)G禾eOS mr .;-3 I2ńKU;SV*i}Aϰ0Vƃ-o*";&V\jܻo, ?g'nS'kLAjLUުYv6 nrV%U…p>L4[ҝzzHVSHّ @k~G~0SfR$jk+[^ltAYQI0!́34<}6s+zRr(9 , 8Ż2$j-RnQW, Z1đY|ơ*T6P[㦩SgsNH.k 0-JHǏ[:SEJ ZI39HK']_t1 }cY:Wе ]T4vTiӽ%ҋ 3\)P%.lKrɽ%khDP"? XiB .;1Ⅶ3,R;eM+3Ab #2儬zEn]Q GpĽՆR~KpD[xR(7!Py7mbeDKO!3]2:r˲ηlh'@'E z'ô06[V.k*zP ~n{Y"Z>c,nA\bhx֙ uF3|@8ٹ-8&!W zv^(!Rs*adߔZ(.*Ij.MyͶŃaom/zr9T3\S"5*yҡǚ_ߖ,t fC;bc7u LRIO#HIqM 4WDݢt_dm`❷xG_1"ਧԇ^a3d*s6":yZI4S1|t!Ճ-Tl{N/%?/4)Vv5դO%%B}?HsfN=RApB2:3NRn"}T< >!-o6?uV?w>'\E<]TF$_kkvǫjl&(W8c,NFMvV>"6fxKf ckU*x}/ݥBmpQ{@ ,]{(il<\S#M-{pƗ@dSkF/X||}pLHS\մIRxΔ_/f`"=w2B~8ȡ[{jHk΁Tճ(tVxX)Y.3:G{-iGc'Q:VXe5~k! i,oۯ3At8-`,VU0#~eKx]saӼKXdJpx^,-/ 5s*LF 0) Ȁ5=s.EnTMkHr5CƋ{~ ⍁!;uGVGU:ca:}[.]]|O,=#|h:3/h+E5I&^ҐBjorE qwW_ ]mU (/Ҡ 8WH#0է 9*{YSoB8S0ݽ2n6n)c'V C<$uAx*&\[YrW38Qzr*FXԳ#P*j~|W*uX.,㭼NP NC ޞ•8Ͳ\؂YA*6bV{|Awd|:o3d5 |94}9'_ ;,se%467E:ie0a JԜ8usb7zywvၛ|=.a147I E4=FP|mڳրf5 3bbMvCw, +&xp?]@J恗,&}vdoy^V# WcE=>)Y~S5Pbr"ߖbKman6{Br5鰭ƝweEzan0y?3nl7SKҷGa ]cцI|9$*<Ֆd㺈4* e!Hb\86@*3+7Wm)AKcWFg˫~ ;[{E쪷N 9 i<7w#꡴VBcWj'Ϙv9J>1oq^ 3MT5=V|#vU#(Y4'jq: i`P5и~ToJ4{]p XAgΗUx 0g}pGvyިזRA׻@:tǡ -= "$צ%waИP~C@٘KW&qCF$ȚbUd@y9XxnPM>0`.ayf@y 8pvE_6208%&u cq:14 0Ul UaƸMR"><.IrX%eƳInqC?c2oTL&B(1Mwy>.Ԫ(y>tjAI&faYYہfPj J@U ,m".2#+syi% dߍKA$E@g跡<3uW@*YKa3 =U?%jYL'wOCN$ u+X-Vfȡtlc/j6h|}Fa9 }F &qi#eP' ` +zaZ& /Ŵ1k c-b>ߥev ju_^?3mGɆ kf  UϪ(ݒэ+i7|Q\kl.9|NE6Т Q9jHnW{866.K:%DPZ}l#ܸ䈊r,vPjp_|`@g[[4b-0V7f%sfB ^>; >򈗨Ois)"51_U|iA1 <\!ҶcnpQ5˜s/ocYXX.U]xمNt ɢu%Q?߿'Ql)9p!i[3poaA=ӚYA%ʹ0b$S%YNP;ܛZl&r*\0p?Lwi0Hu>nw4|d=36=]z.̨eDUF2UǸΉ 5 +ptgPyPR2@cЃ|]cX0`@s?<'([J'/njM6ܮk2!|뼾'.{>ƻ#W,w-˘! cR$E9~ M/k>JpN;s>%{m%\-Y(ұȦ:j:T0Ho?0Lt=lI czkd IB΂T{.z`E`m Oz`le4hGԜ~b9.=hEY883"j{8q9ߋ3o ;x8ǀ݅1:bv7H u:z6pM؉,N\98DbXxwL{a,6R 6a;.ʇeEG7)ke x#fpIMo7 ^[7w_Su0 Ok,cҥgJ .18!YKv5!WJp{F>k+4MEU+v\Y;1?roC8Yl u>޳I|^|2 wPK2sUj*kN f]ѡ&gR2k^d)!S7Ӳ1iHHHkd 8*_䦧7)w> a|#џ ug ށىZ?.Sf|홙*!L`̏I S;w}cg Z`>kίLN;R12Y娟CS?<оC2QTvv@g+*'oqɓ`:_W(KHGSRgB;̬5" y+ϥl-/676j :η(șzA3էL kmN Ͱ%[)ζ3 JRZ0a٪#Q c/{BJ;]ųyrR`MZ["rn;,6;Ld&>J,Q7>|pbGM^B5^2y~!-в3""v-@-ò `RƬŒ12.f*//\oo3}jz|s@-/v$on$qN&ot Z8Zl ^1o'&ysR6 9\=W܉$(P qb8~ټupv|_7HOyJ߱Ӊz HkJ-pHĨGhQ ՙ"bItc~1zVryx6>@!*Se3=\A!hM}܉Y\FnK<=6ɐ Icj}BNG>ZaUqkfׁHE66pS~\JR#s=nbNw(7"luj@}#.9އ֞Q!* O^6钣oQtCF*x8K0k?ݫض^ |&1{jQhY lj">(lCل{1bQ_*܂% H,U0 O=.ζLi:< _hWuDŽm;A$2?6n*EnRh:|X6sեK1Dh5B[>;+0-!qCm$o\W1V~HcmE5yG5 tA L I6"і]ӆybqX'aq@IMή=p邺Ӿh| ;,6GP |H9sz3Wj3?R a-iouX/R2pYLZy@XègjſoŠ5=[Up:㬙A2Om1d+IsjX]kt^DrB ${CA\қ@|Ll]~^n9lDGO* =v`k$.Y'N73 RBңQe;?cO2zqn^YdENa5.R8Ct=ir LzA W-AfjH >m@3ךH."@LXNwHS^5YmPn*.ݷa<׍R*T)JжWBQZ b8Up@+%u=8r \uXߦ?.p=7;h#_o[S)ZG5I)aDcV?i/~#}[B[$ø!Yp;hWdJ;}"[ɰH*:~xݵ?'ZiE1 $BFL#K h\}YvH$$Q?9['|Z/cc_H&)KxH$hRrsE*N(C󱄴s@oɒ٭Ya%11,4T $^<)p5枀sya,u`f0|F Σz6̞`A֎ U&&jץw>=CwPV@y4T:ow+hI<:VkdVEp ȪW%eֶMܙ޺]Q ՗@6|3Ix|5ݣBkύʄ{=Y\2Ƈ(&۹KD/솎 ->W"pmjnS\*xNnXhZyZHpf^JvXEP7#"WbM8[mVX6Qp<\QoEX7=k@ <=QEfR/? {/=5 }&K6P%ݙܯ=sXESWF46343MߥpvO;fU q3|uVZggufg>N"EaNjh5BEnijX,>ՌUlJVڃg{;%O*.zya@lyۯϧStɑ]<2wm,Hj7pq(^/h[3Kyq?ӝ:s\yG}XWP;JjED9OiV0ʟOQH Gr[y즚0 FpJ4_v+K#BASuk)RYڧ, ]O ֗\F,Y!qϊ.bH*nEFUlHX\wGE8iJa.SexvDo9V- _bڄt{\LOWRWx#tSsPoftXch%޴.#2KdٳZo1%/CcOإ4,WХB;8ff\tJljC!Ͻm`#f_ӓ>&2lc(^жB77_U5tėgn: Ԏiv#t;sy_v^_{Nu({-[.:n 'z*_s-(Ő5(^8/I!woi WvnT l]u\.TNl.ڷ|vbݱY>u>`t}_->N1B*biZ:u n':U_tu;H(1O)+>9~Ӓ!,PUx]qɑjY71;@4,],S=:2xO7","_S4XkWbyeP~`x@hBtۥ]֨!fTK[5 sĒ{w-w! 6 S&':1*+]50LwWlꧾsQ!=_*t\=ian/Ptԧ|'h&%_j9^oz;lgЧZء`=nETSsy#5 }Ik<[ƎqveSŲrJ2D$6|YCڴRz}dP*9Ϸ|E ! GQ% H#lY{ψ,2:FndHa0fu s''>A["}L3?%N{F Q(a2"PO`ͤC:mzڎܑEW>UɱOTkcgCŕd(W5nũYr{ 93t]o캏I;;df g'iS&M䞃ğˍsFUݍ]]Y_Dr 9ܿE~Bm5Dluj1,*a n3ʮ( [̲(J urF{?4k3qtBxްT5-"cmLs>Q'TfFPwxb6>j5٪Υ|68F@3]*jͯ KܩysPlVκ-q!PD`.=J׿Y >Nϐ5&0 ȶJKu̞FQH'Ġ n4 Y"D]tQDevlaN5N/}_[-:I~yQ-/4'>fc 1za&UŃz_F4؟տ+8I-`fMv?w:w糃)qbzDj%5:mL /3j:IG,/Jڨ|IE?DZmr 4EٷJhtѣE\⨛jfq¨px?Jo8YP?fGc+Iy83p"N:x$F1c v]z'\M]z^8ȭ!G3O72v)J(42dUGdw KA{z~dJ>Wt\4b3-1i|$1C*}RǾtEh)21QnGBWC8)w%w]!PNoI- eS[q:_Ex.fqEH)vP<5BB7HC =$ub"t9bcNmwkK:(|4pZszo!32o2{={,bS ַ"22dA^+|94cdw5jW(B,uR95ôx)1Q 1m?4+( =g$ C{A@#2d'XhԽטc<4YÌz\#q i92u)gzfJ"qߨK?_ȥBVRP+prFVEgpyaI|:)a<1w p}I%3o|M_ڮ$eC-y`Mv*AyHSO||K67c:N/ T;D$'~ky`lz} ><SYM:XNs ZQY B]&53qqP7hƮā3lc睷K}m7oY\_cJO< P:WSs a❽AGrXX&N$ h+W` >=LR'7&!39q7$h-ax[TVp3<$`fl;Kʿ)>cUSmwW?-̧QNĕ;-uG~gE7uf=rW9cxopE PeV&8ǀd,1WXiDZʾA‹#fݠܩئHaNMuH5L@6 '{ʃCb&J`&OKTqSsYko0v*6s+ŀN4GtC4ѐ4sqN@dℤ}J̾ cvekCk]Ŀ! "^.fku4*_j[%U7(ˋ.A^hb5 hͲ|]#LE țs`oWSJE@ vF9~D d&R#*|e!E79 s,}m2擐ZT&B|tt0gn R~ n23HˮC  ՏPezqoN6@+8{yS3޸ĥSCfEaU0ES NF601ƼT*E\5 P7Llw$ hoV7 hE_c?ft&-m>`6Ȃ,|BNjr >Z{I`jr5j*('-aAPaWg4K۠\Sa@HCJlm&\l>:̗8++ij8T(̡thxnMEv964j oBM3c'ξqVj7Z0)G h`ʢ&:+3ya ljR6>xZGV*&"%o; \w~|:@@_"/>ڎ-BI#pssk `diz#*Y? T(҇`Sv:cttd|1MnU- 㜹 h Rz_$ H7aۅ!&gyYR[%Q7ս8iM>J XBA?zУWhݣT1EKc"md(+ "asKfXr|HTI!nb(D3$3XcbGW_Jq'ٓ%-}h|Xi[As]%uc*ޒo ,S(8Hյkz; ŬLni9\~hϞ(q5ef%R@.g 9 ?)ohU5qɘ7,]{!;)yU.s*0KWp,70I1/:F1xs'vf}L[?4FkuV:T,:+~S4eS{hwx U+|Q!} sA?t槿%UkxLVpMG1]T$`Q;=ҎQ։%U? W!療{O,-\ pyyndF۟uDk?$9:u&Nc~,Kp#|zoؐS O΍k+鱥agSssJ=o^}TUTg`/%Ei&ݘsgdtO{AlH]}EӹpulJ4(2DX)? "aIz N~Wdt9'WwY02hB7?\7An {e8R]CJpcPעHD(%w8oFU2vZ]O* g qd?Qv -$C(93LףzȚ>| M:tnȿC>}!ON4J߬qL+Ls5 E/O':1*',jQQ%' nmi tr;i;S'7R6ݰ_F1T@TxGM ;gR?'d/6ehԎ zԣJ=Ow|n{\Ph wBxA-5Ng6Aܵcet,p Ð/3q3kNf#kYJE}BC𖼱-Xa`(%qJ,IJ R.n*ͅ-S0Dۅ C$H 7&H(k)G)P"L,d@d!}qZy#w`󧵑UrABOϛ?Tr \T+߀N`- OozQfʛ_5C^Ѭડ,'&J&Kj4ҖyYFt [YQ|Z:(&T|;g: "ԻB_?S>Zm5",RiWIk[:hp󪇏 sT\];VEH+<-xܞ[lop}X'9M'!%MЧI#K>mt_kM"Fho+jA"ʇNVk1-*:TGQjR;KeL5gw)NzT d>m>';NЂZ[=w|3?T =7RR"6'J'b^s* wJɨkս[o2Lj0)-HYj3Q_6`!O;FkU%T@8N"+ K8BE.F1=r`QD(쓙_Sqo6g)χHJbxn44 '1 Xg#9~22=/5ZaK| iGKzG˙\}ry' h&_9yeVynʮu O[ ~/VN$L8 C~_9~c CCuHϣN{t֬.^iә oOT7$硩L~C$4RŶ^;ֆ7lO3*lKrhYvzYg}w2 ٶ>xl>&z%-y{rc~A?sm5(ګ+壦!|[RH֨܄ c1 -!\kpl/Zl~rҾkPjfF*@Kqpdנ;UfP=؈E ]/V!6І NZ]cZV9.j3_k#/IGbDL@r՘,wLu#69 NDcp)ilδ@rZz啾0O%ڢ;=vqkVٹ(0&ZXV/ʞv6>4)=CyW 4-^aWj ]h :$z@=Y{qfd\@BFnJbOOe/l  KN ٘ iik?HN1LP~m+ͮ7ia*TOfuhtbPHh;Uѵ0&vJ3xĺ1TGpo4tjiz/ iBd~':?habAC q N0_dX{?0導6ς߲%H- eۖ\4%!r[PP׎&'PAo|FS!mM<2C@8Dž#Éjvdӏe'L՞/A"5s\)>§Wdu-|CHg`,)lGEn!}Jԩs3D>iς݋a Wa ".De(zR/NnS7Y-$K'VٜbpK2<3DCcVJl&geEfFN\#xTc O{SyMg`xm82j;Qk  C!bx;_1)e/WI2G$>HպAËXO̿Stus".D+r[{\N 2ah.Ԋk[ ^oCԨ xYӰ hA g/zC-Γ*AC͔ t4o&PypTIMwxr  IVp+',W@,S=<ϰF *:ZoQA"2 90ALl%^ꂞ:TkT{W@)M_11{Xז:+{(g]R. {kd\'H6WIWh%;t~r i ZtF >VTIUʺ*Bc ܨUl4*zTK9wՎ%*#y3b|Y!*T=΍R4Yߗr*äх<9NV68=- HBQSnEH@ł5P M &%(6N3"1\ ُ{Lc{wW\n0\W}=q7VRkMY\cmg"SLJ):ٖJ{>?N:h9E3 ҩ߯Yҙpk+s6:҆DPgya:6kqț0yu0Pp=E*0$>Gz49Sch%!BER:@69Ʈ8Qė>wfGgey_H#ݴJǐ eth3^s¬(0PWՙ`"֓T-' ?jU#1Ѣ:=,ʳ QpR֭4G1[ f[oۂMJl~j.d^}yNkFp# O  /N&("EA`ĸ!֙;k#>~!Y*[Ћ& vME]vWٗwzSm$.2:OXZ@2Wv:,fd'.`1a9:_H[;TY:\$KG,T vUzifp /s]s5=vB)*-JrȩQ?c?k%vɂtOy鳧,d D8J̍Gp\5µ|>Quҵc-N{ $V@t`l$$hu_?Z<{'(Eڍ=[E"}d8߅@bc1lyݻƵ}f]H ů˦1$6^r:Bu4ospՋ]iO\"jvGr?Zq4bD1˘+빐N~aPneh?y 9Zn휧Z+dtA-V{8<:YҰhXf^HO`n*A62;$QsGԙMJ/HJ}Z\rݝN zTD({3wjn滮)R98RX G's!?0_0.}"`haƫ+yxEۯl" &n;}ېRw W";$`FK )7PL W ˆo.JG"yH5Y;$6!pnn-OHߕ]*7o;Om=kݷ"1 .-%T=r<&f{+F,21^$2f2zvTxʉOSV-4c&ēBPI~4q5hM Z|Ua,NN?W%!=2/߆i r1![[46;-S#S~")dŨh&?*%ij70%rf^ON}uW mp~7 }X%t_Veu^GĬo^B%-8S9) V"]UQd'3)Pׅ.TQFcy|q(|6&;@ I 6ۿR$YoV%@ncLwa^ej BE/cy Ũ%ɧ>H|!o[ -q < Ɖ5γ$> Q!M CqњmkLݥlJ.J[B SEZ3>/M) DU+%^ת^Eh49 m(>jOS7'ҕ̥ݮwUtx%ˬ85ut؂:6.e`ɡjif֌NX$1e|j,fZ>[QRJ.wΥe x|5ȩMe׬z1nV#'R/kk9ȕ^PdGTsZK"B Ew מ*/pP3ߏD f# q΄T=5"^?crǃ46_,a^~3z,ŪQld7hz5VJMV%Ͻg IgWߞ @CFTJ@(f.yd|5)2 汄ue22Tld=W#nS-uU~vGei: KrbXk1]5x )~Y:t<~H`B^Yk!hgc2!K`L&W;_+`<]/<{yؠE$HWk*hVV<LèM)WW'nrwǾ[q4 q``:JPR ?xkZ -^r~)ۅg‹X[2$֥Vh87I"n|OɄJ!2RY"8~[8x.OScx͋<`pdaOY ZgzT"˼AHkWxj-|jbK)ϱSN:Lb֕e 6ӺrWnsI j__ 17w;}8h{Kyu,`-: ^d>^9(|⻷iV[Va9@أn֨[]@vnږqu@xDRWs6'iEfaR6 1 +o@AXS D?"(2 .ڵ2#KaBlRݮ<䬔fkC&^r{wY|Wr^TX!!0V  l9D<ӳWe њ%]FC_ٯw`4]!Aݛʅٱ~Fne2V1*G" e m0TA2.#+ f.M\H(g#"65n.2 edQh!ryya0͒=ϥnvRݲCRkb[DZX̚bcQE96 ЇkD,N+vxiexOQVY`b*l2Gw9drL[a]\ݎƖN\g{Z̬,_fH%܀@ ^=ѝkmfP^wy dz/NL9V b^8#&}53_0u"u*Dm)j|]|0s=~m ȧ(f `6ET̉5U`Q@CWHڶ++<iAVdkhZ.^uS\:_~:^ 3إ$v$ 6k+o+pfÑ Ud@^^L=偨#}2m/ _[Vu͢ TL)OЈ!*>so,>~ RvonX0~/+"X!GKU^ֶl4'V иLfV]8Z[}Q+_9$ ܐKNDw)yKWI6, aĥmt}7Ҝ5h/Ov _'YvdkJ4_8H 7aE˅y׃,lku׍҉|{(,:K`'xQ},HoYėrn,PHv*^oCp]6tFi=W5&`v"qQ1r  B> Y T=SoFɬL}^4oރI&l'=$ cYOo{iG-Lfťp G|G)GSQ>N)- ?jhklOx"LP~Q`ӆZhqhK h/nD~j^ag"A\PTM_s btFk~-kb^48*2EQ zEr~KΚ8i Wu G1;X|sޯOAr^J,G#' Ar `j`de>&#G4$O0%ylq%s$1 7` _.B5"dƪ7Ap\7PI_tЭ }J[KdhP!#l/BpU-&L0֌e&QeRo>rI8AچvRx&0 )X+(@$쩀VvK# ?.򾂘Zxt3} JKzYٴRLE̦.JN:ZX ?#@5H y$o]S,T'f]bF$FzTk'P> ʏbl'{f^X+̲[Z qȓ\e1i#6ic~e8<&Kj8LجZ1 8/g3RbE\M/~cEn1ǴSmɾs9 n?r((|:l3.˴_}5 !~?gVLqiL  icq?Q ةקEX_ݯ Tvj1{N߫9*Q6bcf|`7s,[7Er!d>,JK`89?1) 1!!*IPz* ޠxHis m̤T@ 48C~Pz73ccNoKh.)/ (cV/Q%æ <µEM{I_i*LZ=#o9kFUt6UZtAp d|^_ 8~Aw( u $r 'f_X _jl–!zmg@Npڱ~"PGn^>j(PAU"V eVXq@We)? /p0=zD/tԞV9Îv_6pO+mIK/ur[@l+i pXüL8ېEr K M{Yw_I6pKRA J>hgr 7wr 6i\|>4W9% Du6 !onZ[ޘ)mZHiN\'*:2Cs29y()*ґ0s/'.6.7|w3 {`32R(zCjMjJ aFD) (ww/gגp Y!aP[t+t5d枚> #?NJƑZCB>b~IM_j;(狁w4d5w"G޳dx&׵ 6ʭoƺXlCqGJ|35X%-45ϺvL,pJqMK~7F|e0j]Ԟ9 ~ )Qx^28<β>#FNH'ͻ[inMEpŨ|#?,9]* Tï!~qu ůw 4wERb-%q'n`dW`Xc6IK$B䖻 żfrXkb(%lODg!⮢vI=qcי`6?I9tQH5/t-x~M( Q;.LKQy @ev)r#1ML P?DeBnZf_)O퓡grq|*iުOJh9f`fܟ>5 u(T9ӂ16tf2yTG&ȩpC7_N;WFAv 6J{OȲ]Oe뜌~oUנ )ЗMQ^e2< [luMZRϖFm ®͠bo0Ƹ9zmT-Ը{&rIT>4bx&lQb洤z3=g[szh|Dži!Q7??Ϥ[ ˆbn}bk\.wvP|JUls 8|;RN2^yNdwZ*ϟN'2W~dr暂fkZ8MHFnoҸ֢hO ]~>}2Cur LtZn5̺+JIq3Amu [*!}}QI20zύQ@'H,f mD SGVWχ5 'Kge2@s?gC?1n3Hq_e vyH7Vj &%3$??#hZƼT@ 1B[ҕ[;} Gcߓd%O˪v$@YrZJ`BxB6l䋞}CORij<3bgI~ 08NlHs;wOv@`>~O SbPa~M[OYQ8Ul?[]u )*<}2+X,ORQz'2aMX$ c%LfBJ ^iI?WO|£aL "OKFX!}J, Pm Y%ɅIi+ bL>`J4Cɫ篛.LFb=3aY: 'qRO325DMA [PTцB+5-2(̸حv b=8Szy}RPҏIOG`|ڑ׍< ⧏Cupnqȹ-!\c@IH8\2VMO@ Ro%^yl 8= sՁL~IݕDϳ2y% +/sy )OGTn \I;P|>f D̠?̫Z_Yڹ$$>g.굜PA:%/kS.!͟* }M2f>Ȱ|4\shh)bcB]vW2m9$yT9\4 4/7M,v5@sT& s>I)ɣm>u!EIpurSk o=QÊjFRP(5|[^ PzooZLOxz]bfࡅj d>͗ZFu} SE $nf59=fP*7>އj"q{p}?Zi7m=iMxwb0Z^QεO ^1n dB`0/nYDI\R*'ϾŔPXLmq65;'$;:*B xW`Z͏hȪΌ`bKS+̭g07鄅o^c~]i?8*rM_סb6-mUXߓfY\k3!(guמ\rQX8Qv۫J/XdS/k`5'Ȏ"*4fZҽNh *wKWY Oo(̵UuSm~3ڐO$be|__{DyO}8zv9Bkl 3pI6i 1nWǽV|ebG*yQZGES,X$Fsd*m1k(0T[ph- Xv-Mˋr' Y# m6ȫ#8׹ AKUfq$'}aݺ$y]*GVԿ(V 8pXDe[f!I6%PK|J+S[5 g{)fC]Ow A4x]'SMy^\kQvkH pIj<ł|+pGM׬{f lm~ r79c6][*7g +QtW-oi4y^N㇫`Lf7xvi醴o9DQb}aP0:5k;0w|=Mn?V7C)q ?×hQc[WM[Y{y1g O KRZ~9Ņ^tUP#J ѷx@s6f@䭝,nʋ3Kwy440wwZܞٽa!' Dn!edEk8ǦL;/S*R&qhpu涧+ Mх1ۑ;Sڡ@ӈK:8rece:-C*>p^ݫ 힅>m?#JpUK活N[!os' ~"[@^;\3-}|@Mp+)s$LHNL ;X &SGt!ٵ}إR2B߹)GJSo luUOr(@wO-)  F;?B<`7fߐ  JjI8c9 QC xFefkP+IE_3 `gi_ W%ԛ87, 즮=]&KIT )|ӭo~jcP®M!r)r+Zd+bf PA3h9-z\"SF1%~oY3S8^ ׫߀ʗGM@\)vIϴ1kT.ͨ7m^)BAyǾ<M~{tDB"ɯg4fβ*Zae4 9WW)r^Cν YпlI,";Kk/\vL<i`a]R":ÁIWw" _cw ;ǟ'pʀ XKc9ơ-f6@ +7Bꁚ IBI)y+KDT(cW4yAO,5ϊd> d>8 y%LkQ o^ .k7eD˛l>,@4fy;l1yBE@*9N}(ſq7!1oѷ)&_3`S,*W{k^-!3ZEqeC/: ftgz]ʞ?HF~YT>A# Ʃy]ڳ5<AYf=we4[+["{?'R㘤$!!m'=qb'X݌ml<1L[8PDJC P RBN5ҋָdU$Gyp ?2; h'#9$Æ[SY4Cԡe >/h힒0GpT& . aPoHobb=bêZI{)54?N0}}ITC?wPz ~:EҳUAŸ^ls Kg>F)a aV% @_Et, ]C7Cr7)d-sqR[odA.URkR;{YtC[ v˔jfOZ} oVC06HM@z5XRDk6-(d}zrg/g/`WtORS:i9@{!5j|LAg7&Y b0XH#2pBWs0xN[CgU8u) XQ'Uwl*-yB'XkK c9&ТHxpKm*_}H(EPb!IsW:àB[/X\?zFW_V4*؇*$)^&2RAhI`WA/2BQ^<98#=G,B.qn ")k[2 7Whb|}#s|tr7L cd)O}k(=2o#0H%p"jșSGx$_]ZY8;otHD҂M*u.{XK76#$r"#Dll?SFc ke:݀EYԛtT7A.rkNe5&EK͍fӴNqzǵ {id۹;zxJIBC% uA0ĮڣѾIyjݨ|{"ᓯ[s?y,Z k<ȝ#yRiddȭ҃H@9؎; e\{>sa%*Qq.p3g^7;ٲGU~~o@YP 6N"ʋh4?ӑwք`}҇ti0fgA*dwZ`2/.N,]4o{Jšf`DۖJpjCLmmmDTv/qH k`[OY@B7@] zaj/֋Prh+\>fן{#Z$ׂZ^4jG&\]RC"dxA'Uq_ V˪:ŹhbtK~u$gEK?(~FChey^p$ggxfꚑӦpbݐ5 S{}1+cSkcme F,o Y I5?[l7ob; 1 })f!L%:O\>nÈXڸ3 jk$xrpzيmiQ{\B=E_ 6sr_Cb/eSߵk\3{X1.pn i m% DrjLC6f+WqF.*1 F:-g)@\F6Q>!w m)] < L˅ iDG$NRt*9 4}go̮24#Y!MezO􈴷A>TH=(ꑜ]m#oFG:`(m~z+k,L-L/S!Zޝa7XP*A4Lh4\v I]}5aG@Jj/Z5/W7wHӨ囵IWRbE%?BRI~ @-lW>*DZϫ zz1Yp_&PE7:w G5y;T5@ y?YSWkprI-R_rF3[ 7B!8 }NPYt8 X*ɭ2bdYp)g_"` x"1um_эfͬ2Wsmd{#k+#ϗE>{r>ҴAT'1)ϕp /4z ovdtۮߴ69R?;A`&/Yrd%״̯-`TjGDX!(mbhUDߗS6X&PP[щ깓jJ,g U2f\Il㐧tF$PW# 1P|ǒ-x F?3^s7!GHvimJp{zWR %^}Z0:OudT&zie  hտX3afW# 1RK)"#-ۉA[EqS Vщ ;F jO fP3vb;- U&u!;2Į1KKuC+HgwxjyV:ݨj* miEG svBs5~zC $Uv.nh,q>H01tZ`]u,?E } Ջ5*}t7~&ae`psaI%cѭ:<H\l} ­@Dbܓi=yl1yV!QٳҟJq9dlQ(_N㧈h1Y+u{7`&_(9SЁJRjtp˴/׷6緳,:שqyx] k0{Si!6g$^jy2s(͕a/FZױ@4^ Ke+G JP`Unp~tuf|оF P|7:R:B y[P{c׉|C#ˋ Hr+CO6USjp5o މ"ѻ\T>'+Vhk&m$1o,?jGyOy pM*gMYuLSuc6bZn|(2C//I!]pamK4&GX0/mt@ Ǩ>E;glNu TFCϾg*=uB~&_ʈ>*pSȨ!+qDpAeɶ*^%cj"kS&HR_;E|<,()TŋR6L^$@@\c֥d-Wt YAQm sBA׋- ~FF"GwJ[Q 2YzqiM-3k0o`{S;w@#R jQf.ՈRwՌrGl;!N,F&mr/xD/{B!UE^j h/ q 4D<(7QĩG$ 0L^&XfO:28 g*1PE52s>O6ܭMw x&IY[\S(iT7_qMU7V 8q[=*Q[yu=ҬN`!$]YJ4UM3279}^:KQ|CEv#wWm O0ȖKwǛhO)-yY@,x>[L1Dhc2||J=lӌ 3&\~G?BȤ07\DjڏMY6:~2h6 N1QQvo1Jߓ_T_sgWBI\Y>!(`4VJL̽νPǎؔ(rM53^r BKɯbօOz2V]Z |59~ȏ?Ҷƅyj_ӿ$gyGSy OBQ0"{(:Jeiя󂚧@4JۋELMyZ|w^ 4hc3 S(Ny~X;dW,^y02lo~ &?cJ/ >|eQtEhڿFB7m=w.2uf ej\,QnG*.È_J<ק bYX-^. 4M Q fLlҷM#xcY4y/&{ rUl-L&:SQQ*]쮮re> އ5՟[ڍ>0Oe`2cwŎMO!ѠAr/bKPkIi*5?ftbrd%~*IhJŰ j0,ܝ ~Bl(yĂ9 "wbeZZf'ph7=UFj (N(JW'6M؜͖ <x9FcyБ<.ôiTKSZȢb/[Woyte(*VƄ 2=TA] \:8biUQY㘄4K@WO ?2JՕ]O v"*{" t~=XVF̃iil;7[[:/YD}6Ď,9UУl ʮbx-d]vI4?'9 氾[c:@ٿ&p ȟ!&5L.&D s zUS4 ]v:bm&$ߒl? ?C<5jmNࡡ.^YcZaә1\/7EZ=5Rô8jYgYF-SUnvMQ_ou6hN:HXO,AҜwPe Pc$Ώ䥆M+`{ 2yvq4{*-Z¢ ua ȌXcX̪Tvb6uѢXT{0gX4hW,]ܻU`YN/+qWuz?N 5$7ܦә5@/PRezEQJ?-Mӻ JjQ@!?L/T Mڗ=޶(Φ=|ʨ/L]cCaO>;wYCl,{(g]DGNSI|+@|2˝h@ϩZ7)b'F˚HS]R1>Hq X_t6~G B0hw]T#q}x1ԹQIY4,Z[oϮH+fi7Se2y ȏԡ4`XjJןK)1@; bÓo_k&zA |N!A§vYu@#E5VqcmTp#R][{eᡓ75IԷ&6"M_%Hgl=g.8[(]u ~o3:gE ob[) }|Q]H}~L>HVn 7P:[ʙX`pqd)7wQi8!V*춑MY@;]Q/gW ay:213aP):jxyhʁ AC Y$hn[|+ ݏh`NQFgpo}:Kř3l_/xb`6nNF_rDP/87]@DE,4OKih} \ymKfIn^.y[_JƮoEg2DAJNwbQ.Cy;EBxQF-Sx T+HH[GLB鞣P'r*TiٗOd@m~_vH'jnߛC-O182: xu/W NVI`й×W,l0">Z&G2rRu8X%2c&YcWڂzwÑndaɞ+C"PNnr"O|c,aLl_ jIjcPKP2J6]Bp tiusv= 7 -g?Kwo%꒡kcxPRJzaJY> cʪ[Yə%qqh^O YE>W2|8qiX(| CUv4A{Qc_XpCג`Zk2^׷rN͹cCV3&Pmy@`U #^lQ 3gك~6@_,2b=#D?gmv˼tP+rUpM\2Y4#} g[)DHQ78I2k1rO"F&IgiAmLC=bk*xs ߃ ɻb!u RT㔠iA ^Ѿ^KaY]UI, K@hĐM[bE/3,܀) ^:z pYTX^m-3Ejq|6mxRg8Da8%q:#\4GK+@IZ^Rj?kqlF ܰ$Cv\`iB}Z jJzi&fz9zۺh*23lYp߳jJo޹|W3<<8ueG$/w8˓~46(gM\Z"~nSvՀ%Y1Kdqa_/&LÛeBƲ0ug'XvB-Ç2fmbL,f U^l]~xn.//Nd~nh-N^j8Gg"A  UIkI*E,tGa!UD|O'if&T0qn#quW#<)V^H/~beϪi DdPVĽT?$/LG$ Q;pEuM,OPoe9gwg[d'P]\ ނь` |>ȥޚ:J쏞EK7pq 9K`9%^6=i,5%Հfu.[5Bt2 O9Brޚy7 pO< P -::8.v zjzո 0W UƼRG;Fgsw0|!aF_r'7hqH6X@e-OLZH*g+4l&>Wwd kQJ9%rpjNbkWtG=CK0JrN+kʤiWr-Blt *'mζ_.NAY?٭_-sS %i>Jhq 5'Q>fO.[*CJ\0Lijj:C*vESh 11٬#=F޴}eMnxlTiߋJGTCF8Mu-LzԫcG L.9F l /HtNȅ\0Z 爁>r^f8W|+ƌf861Ü+֚0i~QԵrMy=1{+mR6C|CdJ藵c0JuK_֋`~မBTA„~:Z ݃ ci ;C,/"B&l ״ k9.8ƣA@{b; ґjX4T+;˙}CG\'o`(p\9&1fr뀞^5xG8'a4.1E3pſiEԸa*ͻYBa 9|/@\]7L(1?~l(>E7bMHz ;t~n5?d^ 8 Kg"O:4-lga979.ҥY,ZwV;\&ٝ&ws^ ,YZ]( <υ,nGҹlny[r@`tjYaGdj0^>8=iwg]lr\ ȈH ib( uϢMG׺/^Jtk;SަρzD %LIPD}jTP`B6ʊ_6jxhф@ZdFmONp>gfr[BCM9^mM= )xeV Ϝ.7sS(cRkJ=Z$ O~řnY hJhF ӰwuO)m#3 CY)-Ps,o7akEuu ~=PyIz.Z/)=N}uq.;xu 7_ N_,X=lcJ\ *5V<"M3`*@TkLE4e>t",I4Rw&^.4B|5]u>D]tѦtRgp}ۦFݾ5(p7 C)띤cBc~pSƃ>Ric4W-72/Tﮝuլ.O~KC9O_t,ȏL'O;=WXt7\AelI|iY$} | WO jc12*z]|ͼ(l9 (xCxFPIDƫR"8Qz=+q<%$Վ_?p%7a0+|dڝX=M*{2"|z-XVb)MZ B@*Phk? Þ}]dzC[0itImGx |U'G~[aQ@?MVxJU% ,:%y2(35T5 &7.,Hu\^%I+Iz"VEBb0.?p7qk yVvq/ol3Qȼ1m=f1EEj5sűUɳQ5S'; A;1(Yb 7AE%hJO)).G?LIlI_ Z&ajgT!55:YtwFUEu2i`rI ށM ۾O*,2l2BL\sy>oE:Vgcnp}Ȫ+ MfQ&+#9>ս3 S8sV1瓥QEOlPI:2UJ'joJ>8F,np *U-!{xd*C lb"w"4/fÈk~Pхa2\d_uh:.O-鱬RdɄaqH64j6c٪?d:A4|YQhg&"#W dA-Ι4VduYYf6? 0AQ,W:b!`琋up&9EKj`= G UZHײB$c8Gd(3͆$m" αl'1_޶$(֒yPs`&å- >= RnڶJ}4 bx)e-ŔyEO5yk WZW#7e+Kvm4qsгbdl wl9{zVK_8kRw m4-kM~G 7ѡEXR27U'f VG ~"Ro.Yo/+eۮ.iP-GN_nh*7cI|zSa:VkR2s0.h#ڳW @{pSyr'uI گH[v񎈁*֖IV~HI M="XIG(z[Ox׈ f7?,q0Ӹ@tN6#xrTRJYݼsydRεЇ:M<,NtH YZ36 ͠6.}|{E_@.j :LhNf7Y3n}L$,&9 oeu&+-& ŌCUj~NѡAZFC>wx6rjGG슯kŶ"Pb$]~}QLKs.?CzrR/*Wʂe֬}s$f %F%<(54G4~,M|~d#Q,jOh[ ad(飢e0T`' Ű7 ">Yj~$!`-a`{f{,m!܇Eox.r]yf4(GЎBO"45 uFAesWa<P9ųnNYJRXl?XcH넎W.[ā$0ZH =y1A rkLY:;JP\Xhp" DS!o'bf`ELLC7aЄ0*i:sIE\p0)!$G̩F2ja:`Ĩ| B}/kI'x.oyXIN:=[,^$ގ%p2pWϰksM0)ȝ+'7o gɱGo~iȼ*j}ma"X]^ac?l#8狾~NG_5 S%[zS%/)RxPWIŞp_s)= [KJ_tQLM6/yfYisJ^kw> L`& c&ˬDCL-ŶR~}{Js2t]Ei D(kQ̶T뻏K$mu Y+ !ZwjcU쀧0[j{~E.>P : G A,LYa˟vflo.}فNд"BE#=(c ?#'y](OD\$0BhjKl2t8{-0k]#Q V&yNXJ@^@\ˊnN2VtGuKJʌOP؂F?1̹C/hgƤ.Uk>y- ' yKzI<_ͪy137Ewb6A9y^7dRH|.{w+|mG7dAn-sITx[5b<cᨩn7 Stة>c9]Ӡdq0[)gY~NcqQ >[Tm$ pdTg s;Aߓ{"g^E2,wqt>'RA`9oo>eBL DީG 7t9HS;A9Cc++[30=Jq,ռ&풮e]_DU"'5pynݣOKzar>m! bUU}m8w\Օ_ 5Xzꝍ;unw"cæ vrE[W=,R/; 5I.ĆfeaԹ{Ut sZK&PZF8wJtg&[wý{ٙWnT0\eMNZb؂q#O DhuW59jb5b6!,:JW| H'0g%n hVv*s0ɆfƂO6 kgep\$$!h33|tFk~H]V/shR1GY?z'_ T+Wb"Dg7u^v;aQy.z N[* (BRZh&&vn+,&t5Cڑg9D;Y$11Mڥ x,m%]@ᆯN+Tsn %6 dEi)} yr$$S+]`Hoi-Es6 "vW򤉯ПPJ&uAY_)սU/9-W)V@/Ɉ Tfˌ:E% ^ O^ pz3eM&\w]|B2 AƩ*rpVqmL+oӖU,jf|ȓ>)yՠ EĚlTI-E֢WN>Mlƫ9~mRm&:ݥ8J޷>PW,Ԕsog]@>K{v?x eok;* gU` bcBٿ5)~<,a nt(Fꌒ $ǹvY-  L?y=37#fG'm֏ /,>˔Mӊ.^0ηlc\jQW|mQ /&&46vcB@ u4?ܳuvm MłLYt{[X}kU#<~ȗcz DVZGJɘ]2!O[S77:O*2e@ΰ- _K2e,uKC.Ц۔`& Q0ua$+$\I-x>>E# d^g{_gRжJL &? J.hC3i7@2P~Wh-2_#~\ge!ҷp?`ov;6ϓ*r m*"0ѡ,[')?Nz"{| '2tC!DYBaҌBk_kE||#}FQ,r1Ҵb*SW!tըe\Ը &z(+a ¿?eO%#P3m'9ڲ;UIPyRz܁,%佟9OrtFaaNx{Gy,><~+'#fщ@u@ bfYWUfћ,\ 2*; Sn$&Y39wg~JOIuWib] bO{5Isd$s#?:*`9eM됈O=KRȦ1{vz^險 >S & 0k0 'lK ɗ }Z 9YQ;lySwl-qe=s!.ݮBq)tck@e=IoX}lXM¸g !ܑ`gB{Xj!;PֱETi|-Sbk^ KLK2^prh\[T"|)d(APlb_2{C 1JfJm6^pЁ f};Ӧ2mi2YrH_ZX 1a^vxeW{DoU{mh/NlS;p?\%kN?,2“!w*YFU!ѯ BuiTtt v[4#+:^ޱ܁9͓ZIR6*941^@Ԣ5NjU!cy/CZ9#?>Dv5H]5gDyiŧu29Z[rNx;?&ԅ'9YWBo:΄B~A;}{¢ϕPB͇?=_g54 Ъ>E; 8-BERfTgXq8mKzRg-xp5FΓkƢڲ(S meC$P@37#]^*_:E(SLiݾrF5bB©]u깁4ezSfiwa*Ф!?0ʋ0G1\"3?ō5 I񖚴f9,O_O}̏SZ.9bOO77CdLy􉄷72Bul L:G9uRC||ʐ( Dľ L!/t3,x:Qt}s:uak{61)NYwu$t&d^y|ȂU|R!g=IZE^T%ȋ%bCS=G̻-U{e1_#@D}V*CHf"q v& 98~kkdE`m_FE7P="xw`fnIZLK 4._䢑lV崦|;"BF ? g IgִpERb7b ]/0\/^}q/… %~vϓmD覸e5iʠEcH 69ߞsșԒ DjQC0E]ijW*6UI,?W/e*a5rbKe4~ӂrxV_wD=W"톢0BђXۓqLP!\( .YBuG&ڐJ{ԇQu&m!M0i{ӷSa݃ :$$[0QR>'4X_ܺuAm%I@ McjXasKOJ ~,QY|oC8!Ic_F$X[oC). zfb;癞8=;3XHqPI63drrFg-#(BڅqTsFq} ),gf=`߰,(}iJyY'I)ncʔa=[=ݒA[uJa>R<۔~~7EO}c0t+mE @" Tz|F㪠󱺩- U%ا!D&N 785 C,VxζU)rz.քipܥ+1}d)*\3ALoŠq<2̶ aoH/шI_(0NhɿNp fNJ;Uor8^A@NִoD. 7㊝SF7l$>C}aQ*L6G2s+*rLgCZ;("in^Ӭ쓃YiYDQ?/}:R\bq2fwmB:TV~AbAHFZT`TP)-Yj2K*#^S8? 4p4#nP8߻mV\bU\DY շmdHkּ=ho/ߊ36F5[x3zRw$Mn!p3q8|3o,Et@'/THj ō+D~(Es]Oд˨&H3y7v_O"^E!gP!Q4O]:,de7xW; =s\+4e3l12S|펢`0'6ib|vߌ[$&zAOrLU$w7*c uU4z"L[ wo`j?~",.ײM|Y-)XgZAJ"4yK8vqhpsġ1-^??@1z;]IF˼K\e$&&&-AdQ:"^/埴yX\N;cU^ӱVω8lR(vSĶqu0s T݋z5`CU ?dVKRd)I1^2!|aݶ? )t-ɠHP↎n]l:ÀWHlWM"=Ex>u͋fhw= sD}PڑD>425H0+ |1gZ%)"F5iQSD!Nyx@ .[{!0"&&ҨX]ڄ$ͱHDl%WCۚ%Y-27< T7᭄IO.Ns\dgT ;K#I9yՃѠ~K CrʂXY @l JuYf=<d>7p9;mL>&tfd';1ReHӺ8w4u@$[X n%Gyݟ.I5a'~"34Qx?&3ֈ@D;~kZLcDe*~#hKQPky-kqY& E0mwWSiF W?$6Ώ!mRe3;1IT0d+&D)اIR^#8U1f'Zj 2s%/ L im.~_qDR36hn3iȭukg(gXfrCiDR#;%ɗr,k XGOuۻx DS;XAw&L_Sbm~AU p2ͱh 4O\#l 2M?0;M/P3ƴ b `>--=(R~lKјk`~⺛bhV=1h& _DtKJ菻\jMJ(s'_Y[BB&'xUk F3o)xCgx)?So:m̋tN͘ 7c>Fj^ը$x[ vr{aQSeK,Λp;9|d߭KM^v!ȕ=Cqo# wP]q3MÈwfفDwР?M=s}kM}K9U3қփp̥rPf6|TƞђO9[:CN-I8q/@fZ_m՞Y79?f |Ջeߔe{%1 HK" }.HAƹ.rM>!-W$W{u%kT-6+uz窢E4Ft4qy<&0*$`k°oY=(֏ta~;BjCm%И^Y<_=-[ЦP΂~WmB':Y4jcc)0D1R )e'͌F 6 Vo.h|i9OI1 A-фGt˺fCzFݮ\\wO/θ*uV,R'+邙k`;9=Xf[{l%51ڳgcᙨL4۔>fK. 9OzSҝ=Ő4J*TׁnL햂83X Nmksղ4RPX7HZ+pKq5K)kԆU}qA?Jɖkrn椂.V g9G 8Lذ?W̖Z3zt+.#D6nJR>*>L?xe|q"?R׎ԽXZs`1t[\ QBL!34kWY X+ȒGP2yD ٵe~г/ZcBܙa.9OS,hmStyHg3\rgDGamڗp߲ڦE +LS׎zn_wsP;|6[@WUbHb[v`#!h5V72΍wδHoX Iҧ"]T1.jiy@zBje+zg//Ta }2EO"]1_`uxʐ$0UVDej̜&eJ O'#e?j!a#u3C&䦑E!aY:b zkv⣌/Æl|q=i2ˏ 6QrX@B !ΣuDF\tp"(n~\0@5}Ī+!J5FQ~M5foGJö_ yjE'!Er2ÕHQWtn$#$/qrP74`"7b&YzrC6)?i B <ɒf.8A2@B;<7e1d߀xx5nFm4;wrq=Võѧ#,?jjj?:I!GEщ%Cڧb^HmlKq¿LJh{v֯spT6ERnݺ/e*>)< Wa ӥ"ɽ#ꑑ[paS!BW4^!?AbLry9V)}^#(sPt*u}WdUDfu Rc; h%AhTmS5ْ0P+ε'rm\u(Vm5yT#ξ.T4MvVbĽTx[aUާ._[?픳t&iJk|&4smGXe$r]WFgH:]#Voxq18.}#-0[nJzt6ifA <*^L"/.j7TJp`ZWZ-ږzӅ)ڳe-(ދoM[-NJdPP%̜"d919ARZmT$3pn*UߢZ|Gn&6at}ܹE{ 6`Bf)ҠXz Z>qjpH 7S|NYZP 6F7 opT%' AMs3 on ^6AXiÑx#̷;dD `_JٽFkK=+c* =GߨM2{ 4ˇ+EL5=ρ%e9X%Ӡk?-MIBEleXvfSBZ*By_ٛXPTi PkpP%s߷=KҤ"l0fY%X7lG ΓsihA5G'c%AB"__C G]3ae_߂n9WI/r7mF.iy6l {1\`J,t'kVkUB~ {τ.ϕUOqE 4^"veكY|peAB6azBӤLV}q9FJ~?z A}~k'AլS d`&;uuaG,\OܓjF+αk1TJuļs6n5RM Q>ئ@6e=x:ZUnE dÝSMM(e27ү PYU:8FMxrl/SG |oW$U-BɐouL\YpX|0#ߔ۾Sa|.yH Ì dv,':'"H˕Wꓒ=cר2 h /D\:B[s6{@տ$_$N v1f`E;?)ޕMhk/U5ے)8 =-+(oJ&.҉v'KE<E'ɴ>"0y.1<(y 5x1β O'+?5s<2V~o;L.,$uP9C(!n2M\ 5њν4gڱNfm1z:g6_1Dۺ.X=(!! 'b%'ЫfW5,X}+Xb]<ɁsWc-~N=U0CBYe(UH'qtMs"[q +S5pQ_w/5p,N{tY=kX;+i&#/r_bokS\yk-c)oX]  x'?OX<ͪ1版g㍢/cĹ\cn#MLSAH UpfaȂv9W2`4 5VrD'D92lj7y?bk]zӐb}llu T4Ϯ?q>v9:MfSByZq5yVBug gOBty1S$Q ֠<6V4Bedjywq0-mq0P”Ww L׳!EsdX?2MDqmg}v)} Z?.^H2sw+jw]bp(?,o h%sz+Iψ3/!9а"zj 4M҆k\õ wܷNerd CB=b ϞhG /ML2BMHnqePqZpD%ؽuL=nH\F*k@e;ʲ ڶ I,zU5 RpmCsM<ʽJzզN2IgL슃gbB< \d3@S;JL' ScՅO2)4Fzus QD|'C_E. KˈO8R8z,6!q[C51??d$]b(5)'\ӲHw ,ptӠ܋2%Q³n]_D<;d [{TJ^[<`j[d(f x[έAȜ( 87c,IN?t`z.SQ(Qr{#Rh}/jL'wβ\"՘K}ڒ(]kţAqX8; 8,>y&+bLi|*|=Hps9*"ѝF׫yd_qĦLRG!.WYζphq7AF]# 1~ZD@<9gdR[ۼ&(ZůwpX5%Y%L̖|\-'+fӒOG8m,T`R2dn#w-Dӹ A}|=7'x#%{$(Ӳ lբ8a2밠<GjSrn M:)$0M%,[U€YkohV៹OzzMx-mQ 9F|Ml%#W ūL3`ՐcXJ1j fb@BvhĬ1* w|6(/Gt% MwȵLtf^؝+% ,%Gn-cZdS5*rc'<_l%5c j ubsCM*{UC}@*[". %26(#M2󾷋O%KSTxײZyXيp!c?,n *'WA}/> ] kz8V|&9Qu ϔ紒kٕ탭LMhD$(f0^GK+,UҦ[-POٚ(%(OmjvoF1M&Ke[F(rȏ|l{oH OtCj81ݗneT"ii:(DQEā`(RD+}Or [y@PPOk*.{:zNy :E K/pʘD݅ZX mq=7??fBe)R*xBdH_먌)T>@,@TNS5OQQ*ʇkJ﬚ӱHl@~I(BC{N5)x[Z,]B+; ((#_9w僯znWPv|>󋦰Fl -N"+v2jPNA`kV^@p#R>~ X[⮧1AJ0n,(7N`IbBX3EI&ZNe3%ʝwMWKߪMP {c6qѦAK#ܙ~@~ E T <]!n؂qQnbTtȴP l  ,h[-*N>fv|RݳO wGm0:!LtqҲ*{ ꌛG"-R-6p'H\GPU^ 9.4 {n1nCB'(2>ǘlTokHE}3e5_ 0*k̈x )?`9]zHW2͸#r^go>!wl3C_0:Eynj,VNgl+h@kr u0T'HN dU$:]?4D`JVӃ'7)%~WWzt(W ͓*KN"<6ҮDk c͈-rVqņX mp .Th ^J蛲ڰ2x2& 09I\D3`ؗm̧jv7+}1*؀iQ {h^ {6[cO90 _THTy "5 ^}TGyisi1Ѷ o-QaP&C-s c_*ZT AKA a6!є2Tn՛ajÏG H|y߹r ] t_I,uXTLzT [`xR˩:8j qp1+kad?q%de?1\kIs-Z)fWuln0[yz#\m8P G8.QIO*81F[l-oi '3 BɊΑ`%&i i9 y! Q Ffv ű#1SSRW -!S+Ǘ'I(k {m2m2 9 ua? zGS"isVUd@WjHH*"(y5&Wbx̢zօ&,b%HY@/ԜSIXa li`C8ΕF[e_5o:hukF^= k}!c%W^W耶 t_rH(ǁ¡oӼufE?Ubd!ޅ4ļ7;'p6k=W޵գήN &*ؿJYsz`wqqeaNQPAܕ:^>)]vh1]p.xzQW6K'A La:ZQc踴$+n4&7dY*J;W*=PjDUɛ R5΂nq_HX^^RCբ(f-)5>jreAzӌI/q6apqI٘ #6 `LZ򕲌r>v 1@ǮgE6,A?aKU]\hϸ#_L3mހjD}2V E4o(P5bP;ART+ `lsORDNvo9!&`B}z:P7cr[04+aVy.oN,4#9 WdW! tL 'S*a(^uZ@j.-P &^ݗ|GPzʓ b)k$ @0wDj]AW+VEoH} y4ݵ Ty~U-#^!(_ա{&s"WO"wMi]{GL׈RK,_T9?ւhN^:¶$d.A@0Շ"]"DuX 2k2{|x8t¬]p]PFYu|)JUcaQ2ϛʪv6ˀ!{) %F>Ʉ:o6>51_q)ᕀWgc<ͮ964 ?@L8[+k@4`>+9[+xXXN㭕mn|;ރNG1sXNS l$?t.4R 4(dD$瞥(}!G(ƬOx c 쾂~9g31!)'3NFK^b0kE֖7r$8>c(3ST+57{jqL ]I;glD.KOh<\57DׅdL%8A$?kGAZv׉5rHz?ԘC!̖ ]Pz Z+ߓiyB@)X~33*cU,3,O"ě5k(ot+nd )CAr:-0F(`>eAqxz\:ȫ@b|^nD7E7iW 3V#. gY̥9q(H_xx $6 "HMBpgǕ!vzzMF-' D4Dj{K6$> W!?>bƳLڐKQ)wY<"Di#Z2s@"KZ5F}oWL𙘎H}G1ڥCץ;0hGk:ʼnZB.[jz̀:PBNŽ8EʊV&iC8K jT\g T+](GwOܤ:(T^XKr X< (cOy>>/Ս3y=]HBD{ G/ u5帲xp+| ,!s~. N9FbAHP͍v;E \Cܐ>|-ܙz2 |E!:]!kSnaRKD {%gBZn}]ˑ飃RNF#777_0@Ǹajsv` L%;7d CrEx*:`)j4ċb-3ۛoӋ[PW4)JuYw8`&xHϯs 3etef Rl5xGԎ)g:-8l7'Ip#s (]9 ֻ~r\ +SXCnwfAQSȱW1m^F/ͪrS?&$bwOѶG3q Fۘ1O)bkc@>>=$BGCUIKIIKwc??0-o+)7|S?!`1>IQMx6TwoiܵaRcϾ,rLz#!o C\ctlO$?#ryo),DMo(~kwg;Y |74\p]i-ԍAJ Ep'/ M&Ȁ{ 1KsU^k%]Әv|UXt p҂X( {) % \͇^sh ())[ytJlRVBٰ=6 $!L .ם6o˔t,/'H(Rtyʕm̬7W`* wh{}m~a@ϭ*1gˇ0/5t8 Z cBL0`}+uELuSTGSn|7Y"/cҶv8VCiTp@D9l5q`i{Fj̍6*LLZYT^لՔļk )&(00 ' ,E a38"تd Vjht?HWמo cxD8WG8qV٬ tEaesC[a=xr&G LZD.d/}1vجk3Wܯ{DAH3}*KCE!7o]U"j92b {`.ϤTFINuP20H~T,A,gFFH#;C^N@D)d.KY$Sod`#U`w4PDclǥU' “Yȉ=q0Y< 5i]y99K^]D; Jw^b<2u=QK3$wAD3*IRvI\= ' q3ױb}p鲠5QAz`*~Iue1fezDԹf\)ui=m89%![^"E~Tݴc859[\'cިTE!s;Ū`n=kӗ搤Zִ+u0/SݎK>CjX%R#`6-nv;y/IT8=N;h_8k3cW|c]è{1a{*6uꗊƁu ZR./ Y̗ZVͳF B-d,-,'_W ?R'HJz?/Sܾw1QL7SScܘE53qb :p N4yG qRe)q3Ru< |˥&*']Jǿ (:R$=)̧HJ-ŀP5n/kv$~$z-P^ckMMj> %y?*pD&ǧGp0<_Q3;:|YƠCrH*4٩cM-)} IK\ӞXW~ݎn@+O&: 81K"IAy"-tZxG? !*\–˯><'gcDV%Vn $+Z'`&Io0HsfrUD\2K}7l5snXX1RI iL>ѩwL#U<W3{4y dϫ*[v =Lچ{@d!&ˆmJ[jc &I̺90T/ +] >"=FUhg'k}fo-U dpQ9P:*Ί {ޞ++[kj&mz(0R %Z6{|3;u,>پiO!4ĵkqYl凬ѯbVN)NvN!, (*?tƝv0ǃϡ@@so43L$NL&uqW.1W B' ]͓`ݽL@aO7Y@-Ct²Jjt.*> 9'v ͎d%tB,7Gbhv,'[Y,0sYKy\bMm z(jSdDM*rD{:h Mӊ'r<$2xv}Fkmf:{U`o?dK{}MhP1.)1Xo =g#6eC OyaW9QV_inG<(y eAU2:tb?FI52Iȩ1G OJd/b'̘?!Uv/y7f%6_TO_ + LP*Geb7I!ׅUR]x -j vv࿈2*؄=K- 2亶c ^JGENWL_$*3=t$S}ZX8F gb|{ƻ3+Av0UyexքuLCHMI}G&^m.pqgS]w ĵLWV-WogHh o~D9F`8H& a.G_p(=I!017vB4(]ꔋ. Xs)h*R+U7CUk 3'kW!T)vLׁeh,~ӓU>LU)8'RŃN^MHdC6k)ϽxPM[qIݥT$99HY->{om gRc5_sInP)"Pޢ*؄GP?~}oCDYwzԳA\vG#ݕ` ?MŸF71#& *"$㭭x? O. (ep&@@nzu=<7Ǿ"o?<6y_YP#rߴ"R_l x?G{OX>9-{H{xE2Fk˅_n3EVVR^ؑ Kb{0H=?ٰ'<9|d!+igsT́ |N.PŠ^kS/V[oӮтL<1Id LMb>LZF2kl1oV[*ʳ3PBȰ+7ln_DWy3 ;a㼰;H8E{ڌ'Ok3۸lޘE9D} uK0yul-\e9}-kN٨6:A 8W{Zb,‒?-?$p8@)K 㢥ytIA>eέ;eb2j:0MJDg4}vWYGqܭLLf>on_".)r禳zTlo׷ NǙ ث|:`~$; T7P tBMyt 7 )}SEto5wf! jlNӋ7ncmDai(*fk+.t ƨ6\Tq_ I8َYM>tLyK+{-T_۪<c/ m㮦*Ks3)"@-A(6vAKsX@ ¦хHapv]Wpu1kYhXUN7M+noG1ᦓ6>FJj1I3)wa'6=iBs bie ,\2*]us@y\ctF*ܽ]J!,D0TӍpRN 'UJ1d 'Wiq/-R)h\bW?ׄDhcGExÇs c4x#XWIM=P5_ v_E 7-Y818XXo˴WYw&iF=z 80YϟI54s[:>)vIYK +c>ǛM>HmPVD4twi0܃:>&ZUno4=E1}(mNn(H)Wmts j/.>t2`De\xoR 㭕/5> g\,?nk ig2 Qa{W56EX?av֋wn&b:`2{;#gLv:Fk%%bʢzL7R>{瑻e(<):8|@[jꈩcŧmX}-~X_-)4f73Dc{̀s=0K蘎`#O"tqqU8ЭEJY2a(AUط09Gvj{H:|xrLGHp Mc~IDpChNƀ*~  z CJd)}~Q™fhȧr!ŔcD@Ju GTT]z/ř/]q"o%NIQi&m'Q̀S|-&]$TWϤ}ek&Uœ~o׽ݺl.i}p -S7jUȐ26TNjԋdlVX4li*κc8DO>ҿ!H2QuYP@&4sϩ2<_k}fwsyuԾ␫*,D^a I%%;*3IdMa;/ڋysW[*7ԮW7XH 5>{)€*zהsPW<}e5BQ1Jl.t6 {J7z hlHafJ _Odw`6H ^l?hH`[*ippHE3@"u_-f܆Y컘_<ݥp/%1k$O!Y(3K]h@[fٖ@c9M, $|vUz ƭ#pآ&|]vD~y0٧g=-OdJ}Ghlr՟HEBF~",SFI䘟Iai(p%ycY ~)}VSnU{啒r:?^sMgL9GT>1z?bLؖqIfW3s+`E]QIT>*dpO5vΝ6ڌ׺dTBR&UElȬT{,I#]ɥ%IvÒd 3MT+'<؞t`Cts5bOC|P[Fjϻou]: Y<vJg~$=ڼ^`I8i_Yc%ϵP^mr}4,שb5V?%-p<k$ dXceZUl1C5+[s͆3yܢv3LSg_edZ \%s+ G9&`NU%0BЕDX%NY* 'aˎijp|1|ce V\㨙K=~nd].K+}x;0V4ҏ^>|.4}Ҳqf&U`6_Q+UNP=+nA Rùˋŕ$&n0ր`Mׅ [S͔Uc+l]D3^*@#yӷNz4ёvE ?%ۨUB`s@ilR4@ !`Zk=97/F܎*t}to:m0Y[CT#uz'`laX'0jY#O6O ۑQȣGZzZK/t=o<6&3 zxN0kUL/[?([ޙy~"aLnN͟?dxX]g,Bw"svxOZBrHD/j]oSmI=n#u50đwtW`UY}a>}juAkt!G@'`DD$Z*T"m?K]&!nHC[ R/EB`[o!^-ՑṵwlcdX s<=x4y]2pϜgxzAژS; 9APO`B6!搹Z|$rg#燲eiOϼd2 ~:/xY'N&~OhT1g?P& <7 * eFW!'zqEu6םa6 [(0"qjwr#i(b'dfbH@n 4WKz7 j<]h!!ijL_Cbۑ|÷IwocWEZk1wf`tחF rcbQT$ˣ3SXT^y {vd4g {6SP%5⾾? ׌ 7k+ꗻ>%g/uD@nDysă1WN q8M̫e#_jJ4Gl ' ݽ6ٵ?#s^ۮID 鴂)Qy)48qq\Eyac=ƃA8 J(~D2sL7G 5|44ڽa';#(f p]gncS^Y.{_-R0Zzy*|eZunVB8OV8VGlў''@|.jy< $j+ޘBϑ&qsӀs{ȹa9dDL"uI=)?ł0℡0Ra( ѱ˞?n w< ~9>)?`"{{R!e= ,Rf-]8G @m! O$E ʃv5B'G7FMl,XiuDD\ ~i"1`3Kުӳ Gt_$8̠ slU tv>Y_` z8ٌF:vf3'dC}%c5ad+uêLKdRXH?M UpP_Q )!"nJ nޡwJqOo|NŇH{vɠI>LU߽ ev[Ą-0c3Obakԋi]!B|Iwd~Ԙ]ߡ}Ry侺!?z}c1l,Ѣ"y1бCt~ $"GzhO둙8c3k]f|=$s7bþOXc6BTv~-R밼a=TPĦS4igr`KHiܗ`}|<c KfD`+)p¡sNpwOadڤ[=޻qS>T :B'.ꍃW¬c8V"kӪRlb|0H,hq; ZKoJPW r]x?HTq]ڑ 8Siu&Lv*H"XzS ?1ZGЋZd&<2=,Կ6J=5NW[޼({^?GgDL_Vxtv9Wϒ8oYwڥҢuW 2PF+ÓZ̉A$$c @9r‰%b5Vߑ(ǓouŬ,G Ļ<ꧫ!dDb,&ҤPU0z VMx o,,Ix R UuڽR$Y,`dž4=﷑ Q0ԡ۹p!1wr)lܥXE2Z]]W/Y'Ӷs,L+@׾'RhFYZ ǟ4}r:ǞűpRZ՗z48a9oMenJ1 +}wV=!v_ipm\ֈi_R>:;\$ق`BߛGxq'w)7뎀Ljɞ_w{O/F&c5LYhJ,]*ݝw:8uOĊۼf{"PU\~ƍUex颮NwyςQ$yGYSŵ.ħaf+{bO]S83ߞՄړogi',"#C 4b \RPwY9d/=1 a,"Fp(hZHZm||MD&~&op=?XVyERh+zQ{I~+ I_Jǜ ,>O?ކZ!|3C3F7Q'(rPD l8h$EhDi\% %!n!(dDkɆvF;jb־x B o&CTL\4(l4)$buziH2[Ś4+XƉBF.0۞&n z}o%[]nHLҖx z(ib>f[ <ίv>X^ OA$e;'D t?+mQ.a6*Ƙ}x9K@Ͷ%as<*]1p50@[shh5E4gC\30ߣ מٱKvdL1^$F/L7$Ȱ!y/Jx^$ `k@U#4ـ{M)_$Ur>Z#b߲Nʂd&2 }ơ*zhz\Uw=#rdf\3>kr<ۯxiٿkq-tY6}\Jp&ohˬWk\ p;§QQe=kb$,~u8KbC]{UԻw{_G;ѥfwo u׆&3D$n )M#BeLk ;֤0]ѿΑMr#_nOοpsesj#2kא3z 1ZPjV-$VoX&`EapŠ!.WN'(B𿔵_ v!}%/Lu&b(gUi>4'<ʠl1Zj2r]$Žs(֒Ooba3KL29IOH͑ J]`ڿxxh@˼,*z@S)Ǐ!|qW"d,g%'+MT<9׏W|*.HDr⊎@_t $>-X wL:ĜR$X79vxn39yFs]eWu /v'7֐yy% .){&n ]<#ϖΔ*e:ZZ=ӌk%vFmKs{{0GQ>K\=ˆȔbf4na~*kGV`9rP9@<e:3m$'|X z{eAG?~UfVĺ đ>0E}y_bff`P霱jhKϞUEY-90;3?uh@` ]a @}]s&KCδ}Bm_&Gט {^,Hè_@ml/Ҟ!->mwϛ@2í=1[ns|){L?03&IS+tqR.L ;x븍x)q`$݇iA> sY&7:0ߦ(ߑ2p73vzp,wb(5}lhZ;X)(,T(#I9ǡ1fѕeKKC;2}}A軀5Q]Sc6vnm|7&,+&DžJƷ䰤0?sQ+=aŌnGX,1G8 &/~.=_]G& QU 9+y^Nx:GI!0);<\)le:Vs/)F#n ϖǽ e`vm]vr~T$"NroF*=ocH^&Mf Ejb5K נ)J3?@M k϶c Uڙ.ѧ(pJ+N*- pDR 6P;[urެkuw x֊$Ktɻ.H4یĀ"+zO("o;rO:X|;̨c_Mb¿AA?c>:yDVΰN(iV<3 TI,3s1O+0cr .  ZglE # _p<.l =m+ϯ۸Z:]-B b@)6 NGx٬3C`" M6b:͇a)SI0n0x+`eg+*c!0jڙԏE3Q|)>>)>kDd\(.UamK ,A`c0df3 FbKgk{/Qj`B\P ذ9".[~R^vҖO)²]Ӯ [iAqhPT!1~EpC?Dt/yr3d 1dh ќM W &玐 ,B9S/;^0Ѣ7QPKӍ]9Ho}q'\N86x^C: Y *뒚 ?"~dbD|E <{\W`$}czPQp}9Îg0i!`eykуKQOԔ+I㻁"c$>^~# l[4[ғF#TAc$U!ع PiX C*N7 $&y/|u17 :VOi`jt&3 %cS`~u5zb#j/׫{ BE=)(DPO۱F?Dު%!;QY/ R"u 0V.w"&9`tz,qY1_U;#G}M(,N w )# cI8_ذxF0j?]7rq?c}*Wdz$965r a)̽W{X{'Niî(FAqZ&sHTy?z0彋6aL]$YKWZ}jDOL#A{ӠgX}0jQTVSau 7. Քܧ3PXs0 CԶ畲ڭb;m2-}xvf"qBh@*(u@xܻR4ˤ S8 .=$oJ?Vf;Jz@dϋ+2ظƦZpPjOFfѥDu "8k r ]QddL3pZdTWDc"2APAխ':8%=]64mjkK 9z!V`c ?ޢ)dz>'HI@|ʹSbcj`\t T1p1ӴKxClwD1\ M7zܪv~XrNGfӛnD|7<88<Z* vU:4=Cge/%*"-ɪfZ$" L3kT:qK$%'\TL?<-iHeku:/l]iׅM =rO^:(4 S0A\t{3Ϛ ֆ>$A7%ٷ̂=8anQSG x£m ]>Up(7`"k i5͟ ?3pO5{e*q3EU/N߅:U`A1p"@^%Alio>e*빸4%M_K6 Q^zY<Ь__5"!(6\P"Z G[h yű[juĮvk@)ɶ@좑 .A mU864A*4G~7S tGl5PR7{/tuX- `8*rC{`.pfa٫ ~{ZiBwF^}'xBj3>*7j7lZ{-D)\nTdVeQd]>p2w7횉R2ޏ#ȰcW_,!\ێ$h~(r8ozp"]yS _8-b4̢y",]D3{99qW&eO:)LBZ}vΧuE n݇(kogW{w޵E@–UB }=0Jti NkcA5a7s2c[5}$dœJ ,B';Cu^Hx#(cLz*w+<++p[! yWslͬqG4.3K%3K"3!vE<\-Orj f][fB Q+8!-U4hi7Pu Nb0sQrbl=Va<|يp-(=[-]v}V։ֵtY^}ݥ"J|Hy-/<-I5&x3?3 /6y!3:ER@G4{L Mn Mtp88Q至Z jU]0PV")YnjQP:tBS+jJƛl[.wFX2oz@X}gVݢwk<6`;`QX!FIUVv33jVD_˰$pLrZ&l|*|o/Y f,Ŀ^3Rx9vĸO)G _}% ;W@r ԇ(F(+`ʁ m[F"hHvGM w_/~,^ҷ99rxp6mĢVPrS+VXPRnfScQU|YÝNo?2@ yP$x0Ub8FqދﮍWĆ#Dj6v1g6ac> i)|~d Ow0ċsg@#Ek쑥--[VGy^#OCԪOT_߆>G݀\7|ɳ</:by U!:OXo1U։\ltoi !T&̣&u͍Ɓ=9 ŶYkÒ[ ]gPod{-Z;7t gj2F:xbw ELt4E֤\6>RC\N43驹@Cb`j06/94Ň#CjHeqh-8=/WԧlT Rw ٔ#qB B^z@Htiz3ITTڏ'JFD6AIulN<$~rhiRoЦ"eY|.^Sr>JS~wG`m"${lYUmF!v||Y:)qX VeCK:D>6;3~o>[JJCcC ^M!|v /ݞxi$5fߙ@n愔xZj|Y1a)=V Kyuow &"Ous_Y6e=9C~X,7 s憗qfD~nóTMuւ9JK ox]TˢR*ǐ_SY?`ϳ k(}fy` 8t/CgKpz~⊥+k<(s8W<!9;]Ϙ 䄬nxFKlUT jrlxfg_6HFhW Gy2v**`J)`؉A=kB ]+S: mݺ]#R0vv$_iST˫jlٌp}Y>JTG[~6\BKK}:~зvԵjΤ? VYIT 9r27[ShzAbHj% 6 UGT KV;8:mZYu7Yp7F^M- NWDlani_S"Ejk2h4IzMKb°Z"."i~'܆zU0fP(~PuB !UYr'KP&6T BgƊz/B ./+_VVrG)r޿AOGE :l [N]nMȆ t-x,pdʛs2Yn'HޖG[n9-c iZ.7{InP hk6*uG#A\]bϫ`z*`9%9k"xM$HbiV& ޿Wʷ.SRAՔ 7&S]jlP]h3`!^ s|O]1PnL⎞#:N|o(1 mdz}';"d28?dnaa(eT);cg]jʘ3~"ѦOMT9TҳGWaxeǶHA9i8x-33< j; DVYh!f1DadvH+dyc}` +mꊞ' v rc4@L9\Q-9& 3@eV\}(Zzq=Qbn3}L1EOeP2͢z#q-r(bVe/ bG"8{yn_p%U%m7-d!zYuCۚwߚu{V\ `:`91T93;_ S\I-벍|sTȻ!+GE# 6η 3K׻KpNEu'(UMbվ7jCn'vrx;R #PY#zFq@VQH'#jQ Q\vmP5yZa@;SuYXVw~XtSRcf.lA?pI?>z0u -iF3sOkdRi1& SlVݎF ȵ:f0Q_!螤# *NɇxFY[y6Vo>z OO9-H!*%9QKidB]5#=B < E ߄-q #ɡjB0CQ"u#V` (:~u$R;ۭNfSK(P\po>dVww \= ءajʲ?믌>ArGsV {Bdr9/]tp$~J[}g?ׯŐ92娏-Zh' e"v-ѥ౽H@(;0-'/uu]`lN>j9.!0S#m;\m^#gUpN($",W%)s~{@|(+:P~ vFxӮ))nJY>S܉!F9)W*^9 Lf_ )YtǕl~h < TiRt .C!e9㣦f8G̢zmyAST#?{KݨPΛ,o((#GTI~ 0&7Degv2eX H2z2i8jP|a[c#q.D)q<wt\V11~L^A :Sygs=Lɍɮ䥇.sļ 51GHD䡭$o WȳZ;ʢiGDhc)OWOt,-#Nš,y?5TҭF05ʘ.{EňCdOK (v9ARCSX-\F]em8犡m9#obp# (0Q-o=X$^<8 !6?"n캪gS2(A=WK,3GxTOtpsoTAQ>L`50LI/8|gOec~CK/49aACke*v#x5':T%9Tݵ#,{;D\a+)u[!cfKzOʏ8C׍Mp.?Sڣ]T( .=Cq` 61+z]&6AR;)ϟ$6 7 blncQJ3k}>D2] ("I&l.KcWYM:~aCR֖x@z!x4hc $um)0Uu&_4, `et40nG|XKG[C}Eߛ qLjqmުҜeLyJkxOܽioRC[ν$gkjM4rUǣ,: [W> iH7Uk zaNǘŴD*-kVd)a,ã.\\kC[h8P}I z|A_̣>Ԃy z`8CErJr7Z d hOd#/u׸㧿ݗƇ 3l @!k3B|¨Vf9a-l_<߳З:>߭ [z'+6B>nJ.GCz d^+bؕ N 5P/(%+Fq¤R?A~v8ĵoS d2ڍ{̧|z\LX)oc')1E=wp U: y\TS/V &x2:Ǻ^i屋&X9'x!$DЈJb,Bf'&۹0r3{c3B+e#vϊy2s`^$;_u;7 P+)0s_ƽbi譥XS\~\,iJa7陖;%Tlck7/ԓ[ ƦV=b|je盞NLܨ/ݐ)eE]_,l߱CڡSi,mph*Cb@qxa HFkh$vAI4n‹Qw>4v4p8hV1ǻe1[p53ŠQ_W" f8-S.B84C<êB-5j鑃.>OLY$^#][6_ۺo]3!''ٙ߫TQ\7#'և x;Yڌ́K0kMeP_1BahAW7pA d̽gTsb ,'qewyw̘ wPra9置DE!cK j-})ag`~ӽ>L[jQP&/`'x|Y-;YCzz|5RB89kV mvN1Ë|瑋oa8Dnq/0  f*]~X}v̦g‰OBBV^)6%#о:Oyi$EL>Q2  gH 77 "Nc+pGdT̔Cf쇝ϰoഓiG=a#dgR !R1#Dtmc}t L94SBWfZqg5͋R`c׼N%xRS s<1t1 bqHݒs$!oL=mTW%γQ^mTc3HjGltvۄW"{ńvߵNKFyd-av 6„?CG{|O ib)}8gr>uO`N'{n: KB~x3"ڧYɝ6^E)>e}.QZ6zz33**e{q'._m/ݽTuܞku,zxO?#|7'g^P]Ȉ;Ð1( ,-us@ݚNF]JPX xҿ1uBikQz2:'heL'ٿUCr2F!U.?cC͊lDP.C=Y:އ>f+5VP倎u@4@0hxXN~߮`Du mm̧fzrfK|+BDo >\7ڸlƨշ+ A䔱rbz% {39&r}DډU.Pn-~ <@bu=A1`̑#Z#r PJ'zNvDԹ}pM"bfЏ[,E.$XWӌ>PVPc p" .ށ@psXWII+|$ :RݪPֳW)?k;F{U rN@4ڌfGfB7Qh+V' C9[w.ShH&{QXuӓ[>?).|uǂz'3f or񚂋1&<ʇ,X1t>Qa{&D^ep7qp=|r3/y 5sCk5jLvq Fys_|}miADhK-/ ,/WNZ@^e<&dCNYlR9&{MU>E6se]3'Er.?mEGh4Xy&l!Wˆ Q;r. Z-?2S%D/Mt\ @ꁤv2nUJ|xO 1{n8!> R e *sZ`K!K{*pc2"gn Le 5as~g%>PpHNNC9{,?r( L23(DoR6L8oY't/P4H7ro`a+qABᚺ'`l;wX96qpt Sy 詵98r[b9-Jlu3P┌S&f^)@8xlqc,"{4 p4 rlvun+sw?cp.g^wi^!C2`Ra'eO9 ϝ$v%{"*x*4[Cbúfl ԝ 8wg" hy"U\3Qh1^c' ců}\F;843Ni<Vjj)?-,B5Lo2ZY[a5&c B=vobRWuZ°څ@mXg9;0u v%+@ʒñ*0 ym?3R,_DFa*( n b +xUY7E>FSc1'{$WWją_q?+Bٍx'Nh 80D2;;(l6s~צAy /6; vIA9j(/}D'N)ۍ9ú yR\P8&d+a4VIsHɺ9;)6ui-рHUt2VoyZ1XZ  /UWxPvp~WP1)˼ Wp_d-mǯ_U-^+a%$ⶖ2E BOpF4\#\6+Q+RR pWeI*}@5dH1V>G[=4ȹxaGri`NSṕV@@ijxFSᗘ:`> R'zG [:hs-f|9䇣RTj,fz/@Q8†~PfdS4Ӑ/^KVIƘ:eFfAca "Ilh)>:/ƅL$d?t-g{Y|-\ g4g59MdرnvPHoIH.@@"(\@TBt?jC i =h wKhkgUOo~_ ̊!`RŅ8IC=abC=«nXxԠ}O&cPoRI*Kh 6CYA0m2>y?0S_7{|\ +Bmea=zKѐ(پi)ܯ}t; p7 gz r/%[#Kg n|V#R "TVB*؟nݫu)w-j=s`]|,%\AB)DŝILp$ o@x>[p/htF?<*ϙ̦+Hvv!nLrdҋw;sDQt2zr+}gLeEJ3,si\ .k9"ml:Iv1z:c Y\k"}&N=RrD0#_jѱ]̝٨t00JP&^.+_ Ł"B]FAflJvf (#.5m#mvp4GRj6d8zT›)xt& kuh-AQh]:_`|5AJ:yWW#y\gW7@]ʼn~&%~,vUk%A(1O׷ZYk3( gH+{N`W.շ<$5t켏$NC8 F| MkJ/iنy"hfdwh4~v;U.ʅ1%H!xђ4YU#dڃ-4m˘P^9ZdOvi q&{az/di樰A]=Pv$OFoU۞5:24oq؉u2"~ZA *7_aʣF39k~!"Ab) fE~xU}>{ߛs.,Ρx\V /t0rw-:RLV^b1NWC<},I.^ĴR GuJⴽ 1f('!)-U&ql#u옖C,[HG ޗ%ң|Ҥ9D͝;vqc;hY'fe/R45jLa%DQhV˟ߕ3 O v`P8!XÒ5_V$( A,1ZRمz&m5/ȧ|YFtXYORAΣ+VŀMS'e  r~ٜbr[D/|zǽS|Gs{cA"ao]ɬ .ȯ9lr Ng ;& 7Y)M1ӵKL>oqFD3GKUiY/)=m4m /@^pZG$Uۣ]$4y#C$6AҨ/Mn0ب/m8N`̉rD@*v~Q^_os@Bt[?; $:Gv<KADb3= ?l_<ѥ!v~)Z#]Nb$e1NŒ9elVY[,B*RaBpf (!ZY(̓ENLFzh|I1U[{pC<O#q&D6-ŭ"(O gOne,Z`[b7ҙR`P !^B=Yj(Kr{uCgC] #o#7E+ 9-W[փZy]s9c!P##jmMF1ݯDgI)V鳡:PY@lOR(>= ]z֔A 7^euaKh+ !Vdac:afR0[OShsCY 7o"is~*Ṛ՚ox( IȿqV*͡*YxbJ=iLaC2"ަf͟d)My|0V{*gﴡl=#D/̥RJ B'$[~qM,e9jw+];qQe%ub;ʷN,Kks]T4EoTHvHKh@O¬h38JfQd6m{=: bya#x>R'Q PXąQIkM3Hta}s_]^ >J#F2tِ+T\DAD|1{ Z';.RJ53%b;;]gGԬϬWF柵_h!wGNѶ XԍxE~.]zQg7c͜}k'a+:.9ܔsј5TbY:r;"RHš>c> u@V ,7/1F)d .>U;^ ໏sVhq^ȶJL?Tg"`j:uvkbw}^LbNUhS!DfMw!#9,8d`D?=I'@ 3B+[ c($padzw5ǯ;/8%xK{_T3[42\3Zd)]5Qaei>{}XZ%?fXe!Hj4Y )^#Jwb*k]zG[l^U#ԙl]g(ԙ߷e!3'բ;]|i/lINTtE&pbs0pY4ڠJnֻF=4 _jRڻE1v{?}2PXm@{|,$n8l9y/(i#0IK#s.Ց]@xA7 kCTX :T3 fW/LG]4k&Ze{iDZF|)d+ނW +[ ,JXH_8E' ORl2՗[UGmAO7j״'md`Q'?pL[ iȨLJWT0OStVA',|KfA1Uu8kW 5T|G(PE'W?u[ QSRJQ.#%o~`Y2<@'`O^3͓^Q)Vl`bZ\u_Z1;X}@*tH,TҺj_iO;n}H&+-^A,Y_qٮ}Bfcr̈́ۍ67;ƀRx9%߸!ԲBU{$#8BZ@bF V=zCt4g VNf{& jŔ(o;G&`Q &V樱<1;OL  ZB[%,v#wx=ͯ񜁜ݦp1ҝrJn M`R(ۻ7GR]+iLSb9yИb{~3' Kf֥#8*Z#Mt bmA)с;h|L!Ȩw*@@q[)f cuпK#S(B#-;C =+@,Z,HVZPǸ+* $7' " 4otnbdNx0 ~F͚ŵM##d5Pw{>W gm&1C;0a)79j&[7g4Bk߲@  W }p-ڠ{+1QWi]̽Ԃ?k[ڈp`aIg윿JlҎ,kmcN/@p{_0==J*zbuWd4rNh|_qX]7v*fKWP]wfhSDC^ = _ug,ܥ-uA7,v+Xg @v>{{a)RSSW:Q!hDz֝@6k 1VdđI9O0 %OPf贄p!^ErG&yI}ϓCcF hBb@y(2]8{˫@;zQy0Fa7jDK/9ʝͅhR)F{K,~3dk:z cqMN@1@Rx\@pzضTS!!mA}4g各OX*B\ܧRJĺui(ODMNࠈ:忥᳁@c6K>~m6OSxUa¦'ߦ[dzꡥE /Ww;`0~ {dr}.VA3QdȸM/ޟŧQ[b|Ov?n0D](ޜB\2re'e3($)V0BSz:(9 D=#4 y|5x* ,?f -R߭u7Ubx?nYˍ.'9xSxI)||p]hȁ Hd&ܡ<\Vp"VHH^D>radj 1S񷌬OԹ?Sodv@6l+bDfOǚYBoU=8*6󍓎[ [=:nҌ_1Kt1QuPL+UȫoN4$7FѤ"!@wdC_8y%Lդq׍jmĈi|FBŜ7g{^ZFJ^ sr)Vﹲz3HIF`#zwOJ,_wΥihjlKLnn%_G[I5B)ηΧu9wcN"^Gl L uUa~r҃;#¥49T q۹4ɘŠ]6]۽*tV踨;dB9|D-Q.X. :RN3}tłR~Gq=9"_F["zN|.9/ML:,%~/&C˔ی JLZ"jT%JlP,PՋ2&rj=44m[9纳5#)YW{.z'iwOe/0f0L!r=Tq=9DwmŗV9u@S̚"_h<'nd eSk-^ Ld|Ă)DN`C>s$Hy3-,uAd"C{hp$-AOYN22%.B*'FD)3eEx-aĽ$ ǹ r&JltxUrE9]gRL߆`RT/L#X+saQRLFEEs<.%"DhPUhۚff9o7DWA0Ww]Ȱ"&PmuΓC9`g76hM黟S1hS*xY s'VUT&7}B %E>>lTlEY}/NBUE;zpcmz%XNpc6j73Nh O3Y\㷠zm@x-q ܇hB4}?/do3?IW_*k< K\ͽadv41r 7&u|5LV QQizIZmOL?-Yoi>Kր-2Kuk)C8g : mbUC֜y,zL8!36+H\\tH )eMBIR&/5YZZv(gwvN0.-˓~U?P|V!ZS^oFn؀9"2e27Il؜ zt %jlӷ _0SUW jI"̕ܝ˗BCLϥ.4#@u}lߟrnڍ[  .JmnXW-{E4EVl]oQQ6 f #GFl$b O˕&de~HbQ H Ws7u20VvO׆ A9K{<>;y,AIj {ZOd?a,Bxh+=w1Sm 8;c33r];Mk y ST7n<hñ#%^/ nncSMěh CF1T5i%ΰX Y qD]k2J3qCM0;jF9-I_4;J{r!{q61y]wM>h2Vz\OCPl mhRU^o9>EVU0LOpʵ+a v:Ȇ wn2$s E㱃S ONV"DEMv!%AnG4mܻ{hHfMAa0ծj7խ7u)0d4 vvśع/ ?=;XbVM[8'5"Rg(/włO SA#b]Ȗ~P ~JV1/II"4.HNݚt ֯1WٛM`Ph+eZcO^DW/.\b{,|L5:n-Ƨtkbho:J9 -04T4[n ͐Yš2>V4ttSҡ-|k>Wf۪hu 9AC`y=5 Mx}{k1fHGo1#2vo< <@rFX(4ttz7$@7+S:hFWھ :h榛o s H|y'CxN뚥P~5#vmA+'=gOjѰ:6bxi.`ij;k=| T)=xp8ІIQjfACQR p8|wZS =Ŋz'KRL/f7Ş7G,.2iX#´Sa)od:If貼E7fKI] #sB!?%+4^!Io|ÊƩ"+6/|[ȇHY\5C$436͇2}E4߫I+6f1"\HEccS\4t߱H$c@`L\PȰ6FbQܧashC\7 <؄.ܴ.~DHZxXǥ G _U,h]cg 50MW(Zl˩ݍ gR.5<8eYHz?HUAMĔt|c-`p2X͢}V0nO64[uL<9.)\iΔҦ;MhB򅊠@\v訽fl%7ijB'YȞ /M1* mS,=#䁈,Y70!6+h k}9thL-u=k#y dSvWma_ps3"qZ4g'^C*E%Jk+?Ղ2JW>F yQ77 K@yVӽɣ)cS6v/y(r^ x6 8\fqED}KӘqևp44v˽9CIMRWk@/D-^-g5]/ɇ??kx|⎩׃yi~b ݣm*\?4l2_/!x=,])k"JvhtLrZ6^U4g8uIl!TH茘0x-.Q8-֫,`Ab -:JNv qK%wPѭyU¥?]$FKa.Aa8PBj3@.#ѱELpcшZ 1*ڨQuA(+CaUB58}|\=вc @=.S$$D ^ـ3te 6W,i+ѣDdR|PF@)3IJ߭"%?SFݪl.g=^v@貌x*+*(MZ$s\y Z_&o=~::snHu@š>].ls0ZLGn[DWi{EgZ;ma~PC@ d%s#= .8mM'RˌTNsVe$OY3r8Ջri{6Pkƍ &\{mn Q#%kpSl&1 ek˸kZ\ 2PT`> 4f)'݌uMHShPzg9[R@zAh^޻R/]O "]ܜx{X*?+B膼@!(pH!S*DGq2Sۀ]@o%Vc$RsvH4i/IW.LWzcYilϣ߶/KӶZ\`*`tc\qh K5#)[D_lzӯ(<[ He`lT"Xxɑ+x(S>C.dA,Wo[cÈivBi Z̺8() I?Yɥcـ6MOZ1ޕM+`|yoQ A2 v1;"v1^Yv_W>[8OrrIJP8tGt՘/=P*͎En٩x0 3Ѣwh׀$R"@faB0{+mY=@Y2+\6h_oIhHژ7+ T ];I{pz˗vuVc^\j9ECst*+\ %TF۲7U\iqwzujhhNEەu@?dΨjh0)FKC<8Qyߒ=2nBe5C\'IpfBy]Wuk@뾓z[P4 ;GŘO~Nkæ^]:(DKUn|6)b0(ڢ x6pRRPMOzdh2IJQ1\8㓭tf5Ґ`~&u $?C8,aDVvŘ3hG8%cAۄժynI;# uCWYًFcݺPivm/\CȊh龜bU׭?PYaC籍2sĸ!y J : ? f}Z~s&8l׋_QP%؞۱بY[8Xⴗ5 #V{@e*4+jή2$\hP==!EE(ŠH(':v!u]K$RO gV(AVE9[N:f #oFUYU6II?pZxRf6tC`Tudp^z6?U$j#65tN5 If1Is)Pkd d)BI9Y쏻[I^y@B#1X"(d*Oc{$T!]EfT?ӪׁHPl=0 ZO]&Tٽ,(UEx(rP{7VP˴M܌^2,wXZDƈ;?Rєj*)\Ii#iBZH3*)۬%{C)XB(16]sR j44+Taɵ1EgNR Wif J'xQr4y,:76y >Z[,g:c#K -jTRavc?{)vm M`)}lw|/!^]o] 9{<|0Ի7VũD֝\qic% ų:P+<69+y孢aWt܂<PI-$ 1:p]AG}z.*yF?ZoE\h==(KhD*ÖeP\ FV z M^rL>R$%JlE :McdmZ#1Ԏ5ILȧOR*LM(Bj9itguv5#ni)G<GV3'q ?, aދIƜًd-Ad0gԀ#ݮnb >/zf3¹@&M!Uv"K4Vwpu $a |RHl( h{Ƨ UVܲ84<^ްNA=!t ꃚZmG1L*`M'v۟b\ 3h|U,khMly]shz 1oH+[=l11[/ϣ}*pUdc,[̊S4[u-eȹ<{ݪ3A)o{~}.)^b j+I \ ijR[%3YlH(j5Jsjq Lї[r1{GUMggJxށeƾU*!TU{A3'dx?IX6w,2L! Ŧ'%[vEy`̯-;-~޹'?oV@u68pc9. ! ԰.C\ .skz.%Dkؐ;lm ٪C?H kNW8xuᩞW\rnbnOuj`QŅeilrl I(T;2jNԊ$T ]5}VAyg^jX m3䨽njrj~mXߞ9JGJ&Yp`P7U-? h6e^wګ:Z-EaGqZɏ9RVvahc4VA#q'zUpE ODyv \&ߧ62lk՞5B-STk WzhF" 潁='i<Wo߅ex7DrcC+3B_dlw[|{0|vBLV*ٓϝSƮL 9iR6Ak/ Zp?B6pk~ Ui6^~Ny,E{:gGM5L*!lr0xeM@&ڊ)*V+ "īT r*G5 jcŮoCS6dtּZ4QVOeZ(RI@>vy`J;t|e-w }M5tWfP#N,WMGaiRjYUϺAhe0NߩL͞M1bA04)FYL;SytEq@ $h}`,dӽvG't ʆ1PWTpנ=MuV+_,Lʾ Iv\g6hpt =^^I.&Rx;$; Àܻ*x{Z*\ x%JXnS-aq`McV բ>)2Kq/bo&w%AFK"PEOFgQ.U#oiڗd\Kfa#%nz4H+M5xV7Eh}4xlG狝hlmы*˱,TJ)92hsIK*[;j3HEj]fzߑ5!#=5̻xEttVK!mPgbDR>Ir< ;o}(W"o57^y(`s-]vJR( (K>ΕۨkO1)*Qu jS,ZNeW0dsj*B;ӛ(hZd}XҿaD!;((< <ʎ;^YqF7E0ܪ\1@ >\{e zङj#vק~{?`[y6q;'@V{ok}Vsؒ{rÀe`ΖNUގ$qf,0jDj(AWcyM O[ GIqКm;73_4%FP{@w(ce7,`229r:PhVkk8|&(2/m?^wf$mj5F^ea(vڈ6_ӎ&]gwQp-uR¹# AU`oNܩ$c Q{^tGP\1:_.@XY{FL[Zi;a.0:8قi=IwBzph$?t Doرsks 3.fڊhgl߼M+yA[Am~ԝATD}`iMCZbNo-͸[ҏ^j;ϊux(A(L,el >dl5O yڐ+5$ągO6E=P5̊*( FpY+GqI(ṇ*yX:қ~QZLr9mE2Ƃ d2kPl8Z5M'+Wg㸲7N1Yȼp)3k̰3ɷEk`]wXM$eB$/$&O|;0{RlPZ q"S.94!>1Rgƨa?`Vjp'TOSZB嚐GkvrU`6-"Tռ▁NRܜ 7Rib|.h3xnd2NfY%?i?Np`qVb g jB_fړQAZ$*m??`}cp3qCdR;R^)Qa&y\kP\kA1xC ܤ&dߧT%?H-yV`:@q0,~OaDlf0?!_a8VpE_Rt:XKQ:DEdRAoT,D_q[3->ȂzQ9y^$ @Nf)CN^Sյd,EXZMt-)Bз4H!)eZzUWY.*yxj'x6jf(ȳT!'0h}+HyzV؏v{2>(JBCwJx(x~I}_kD 6qץcY^GԚT(L$Ntp$~ B񪄗?0W>]eU>?Ak ]=4X*tOd8?T\0R|eu>?q0K0.!> xdZws:qM;`FĊS/vEUT"jXeKF8VK_踜u9 Fd>T(l3إ!q_S"<\ޣDOy(tcE/8'W/BH#ȺY`1Ν.Q5lOwd._0q<֩w#K,D/Hwk?//-rW^ӈ3J#%e~&dxՅΏ$r+;ߌ Ӕ5Cz7܌>f"MzBv,t<6>I~}3g@!+( fY+nqަ=d27רQ <:VO?^\W@Vu#p(Z4:YЈqI7m-c\tp*1ʾlM buMBhp>B}Q=Xr4Y0-yI`,eJv*&t٥TZ1%s3b}tM:\mhO6mMhN!y^;R)+s 7Tʏc +y_rqXb^ fLef"/jTmm)Y t\r.A|hX¼\4u* HV_T nKԀKY%;Oհ<_"uQos H>í>,7΋Q0hz-h2CK0?2XTlמ޼q_AXa3; 0= |9 Nazk^:W{-EyHSc`pWEX8~OpyR뤝/I;غXX·s܉k)Pu H0ٸHǼbK\e~ҥ"R?`ꀕ&5C ^mp7qN}XjwhJzzAwΝBR'E]w^eń>+Cc>>R 9 7pQBw G@nuh"Ye%i"- ?aAXF#WC%JZ,0Zݠ)*t]ď'-,,,,,,,,-Ȓ'$ ɓ)0//////0̕*-! }$cӛ-2332Ԝ-ٮ%g K8%cGcIN:'( @  1&A&A3 +V;^çr|  |s_Z=1+qN| Ň … … Ƈ ~ wQ1H2o{ Lj ƈ } P7yhHlj ȉ rNkJʌ  ˌvQP8wˍ }   | ̍[? 3ĉ || ~ |ֱhƓ+ ÈÈ)?] ΐ‡ÈÈÈÈȆ ѭkGx} x+ײ†‡Èȇ̏e ';ȌŊĉĉĉĉĉÈȑ#ϲ¥ÚQvͥW۶ˆĉĉĉŊ̏2#IsQ ӔƋƋƋƋƋƋć ҥOվͧƑ(ÉĊƋƋƋғY xΑȍȍȍȍȍȍƉ ݽʭѥO ĉŊƋȌȍ͐~)Ǎ̐ʏʏʏʏɍLj Ɏϯ޿ ĉŋnjȍʎˏ̐&56&IԖ͑̑̑ˎɋϘ+ڳdװտٳĉƋǍɎʏ̐֘B/WG2 ]ؚΓΓ͐գ>ׯԽŎ#ÈƋǍɏː̑ٚS; kJ5 aڜЕЕϒ٩H͞AƋȍɏːΓܝ W> oA/ S۝!Җ Җ җ єҕܭQМױh ŋǍɎΓӗ ݞ"N8 a+7؛"ՙ!Ԙ!Ԙ!Ԙ"Ԙ Ӕғ֝,k޺ő ċƌΓՙ!ՙ!ܞ#6' C Ə!ڝ$֚#֚#֚#֚#֚#֚#֙ ԕ՗ܨBɊٷ‰Γכ#֛#ٜ$Δ"p'؜%؜%؜%؜%؜%؜%؜%؜%؜%י֖؝'`٬͒ٝ&؜%؜%'yQ;a)ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڝ%ؙؚݥ8yբ>ڜ"ڞ'ڞ'ڞ'*_Eq  $*ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)۞$ښ۞$Sƀߥ3ܟ'ܠ)ܠ)*ʓ& V?e-ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޡ)ܞ#ܜݡ)ޢ+ޢ*ݡ*-dIsw /ߢ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,."Ƒ(/------------------/Ϙ*!$)˕*1////////////////1Қ+-! 3&3100000000000003'#'rT}ۡ/421111111124ߤ0{Z  {[Ǔ+35555553˕,݁_ #/# 3[Ca{[j k }\^Ee2% 5 tdesktop-1.2.17/Telegram/Resources/art/icon128.png000066400000000000000000000221461326245125100216360ustar00rootroot00000000000000PNG  IHDR>a IDATx}yxW[{6˒,˲=^Bp}ql$d|yo3,f`f 3c HuӊqpLh0ȏ횿] mպY?db$ÃqoqȑZ7+Qdxw,;4=R @2/tbK$AҪ9w\xX_n~Y' &4;w>;'kٞjƫޮk2s"I,$/j#8/Yg]?q&Q$؊pbnV/4S*\9V5U@ox[hO*zwŅ)#7)LE0lB@縨Ճ[qMB$$ҥ#aţ@C]z2 ¶ [Bn௾l?fTʳz_ۖ M0p-"`83pgM. ք=bhhLjی2*n֮.i#@`H%,W3aw$9#a\>E))^]-yr +u^@N IsԖA?--h1#:9RL@&t/zcAD+Rc K\3n. TD86B$eAi#LVd[{kRFbpc'dhXY 2ϽJ;tM^͋Bui.+ syT9oH.xl00``EjʶtqbKw YvҤ9P HIaqHqKW]u.08 >ZO4h|sh0M 3+4y"py^3}Xr :kOp3SWwqh'`UUk:XԳv?2 gN?TN_*B߅Mp$,Ih,Sf 02m3JJXF 36x]s6ze߿6IԸ5mI]7\Qif\hI89@°9)AGs@I-D7Zlcs/1R%$Gu!4y5RYhi7$t$ 8GGsu>״%?3It܄XŤqJy @\ـKTԓ/" %q&4l*/?0{<nj.-+ b ~`CM&= :glܲqb)  u,j¥l[h,Seldy,Iyb*֔>Ic")nҰp|rhdB8‘(ڍTWGGs<3[8<.X."$9{\> h>*V$O ռ Dz] H'?6upmWk#vAsSL8=KRz A:IBG80F,_={S9$YgYddw )}SK`3la~pW?V7qzlZҀU2+g0-cҡ"BcЃ&?>Q ΁ߕ)X`3>2_(Od| ygw4L hz"p*epy8Wml&YRWF/&0F9[/JC)¼:/Z} "XEXƻ>G c$29H~vyۙDJv O )3b)}:`p :wq]W#.ZB[ n R`(K- >P~J(g@”Hfhvk/.fN;ЁAg%?SKd(Py&Nx@2}*>b.{IŔzxZ|Ec@0`+/6 qCbFA<' O;9ɟlXqg*}!36hL.eo({qr0]UƀXÜcxv% ,?6+y11ޞkSU)ޑZE0zFBӦav dk3 $NfQcYIH0eYztHK7/Y1;rHOUFW#j.EᕾDQ*t_~8Ɩ}'Iz8gNx} GYVzQ ,OYOQ3aܹ}#YGv"I0^U.w) v@"ƜG p4^?x$LtS(Zg Qd?OU6̞G E1!ȑ3oX{_檹ht3 ˜sKJl9^A>)B 6˓Q^xJ>9voLW5vҟYLti|R*Қb$,颥]'#Y^;J}v=fz=Li.XolIņƵ2==NQ`!4ʞcg3vő2 7[1Sp{ eu$ \>]kNFCp1ӆ( I~S~s?'IMZolrlH 8všȼ[pD 8zЅ*'mB/LJ؆Qz^6 I8u\ gΡl$ʞwRn=w.p6h"G^YKJ$q2f;OgȀqsŘ}&6 p ##)fi4xuyISM{~;8WQzVv_sy`ߺ=_,}mLU&zR88J97Ƿ_ш#s+^]ðIc{_w#aN0*d4*F|0;7hL:'3K;,5m|wqD?` \^X=/H>IH?0G:}uws_ЇJb8ñS# gX1FDO?X$*%v➝3_ Y0gL:eRjT kj~c¾p -&n'*1L['.8FL!fXU8Z.` kme~p+M깟|ezrnWzcֹn5T 3h.8P" 0V3eE4` /+5kw\ZQ*]:Tt0?FFy1M,G ly ۄLwL\#rc [ h`JHʆWyT,Ư G⿾c_0%[<EÇ!&+/5/"drjkK b!'k/$P0򞴏yM[jѶtlո٩q&4do\0: lԼ3@sF~NJŋOX&_%ڇ_ݯ6 x ,cLpf(#̬1C43D|.|o?'{>|s HRpn&3>`1pm8yHw/k?Ajf2w%ɘ#&v㚆"ʼn_ /}fo֎ǟI=?0ngrQ.><ڽW V08TF~x֋0]xh$y@/ lJJC;vjmGDc0QQ!`vj#{?15rETPVrK  g0 +0EW #LO2;zj$2wa m4 2cW'c*}2}_<~Z|qb[M퍳)Ggᡃ!K;Hק @ ן*W][>c-?H!IDATW6@L8y?!@W O0S'gc m-mcV gzUdY _(C}'u=K: U31҃ EX$1LB~>C]Fy<5/I}ϢJj?35ԇDmϨpoB[b Nh SwEIx?3SM3=+N*{teE &a(<w?ꗬ*NaӨgSlS֮Йӟ`p7+6@v?\/}B!8euj7߫#_[0;)I7|{_@*UvJ} Içמz\xU㳦w*J| i>ӿKٳpvo2-:+o7}beCLͺqJנz=f˟-@XVɗ]x!fv?<#\@~W|>G ΞM 3a&/_|95 ̶q = iB_yA!p&l`62@Fh_*rûĚcMM/.N,(2=[_;X4^8Yб w\wXuftŬ01c2 t@)PL)qL:utVecx u֥kkyҕ}'臦#;ES*A0OP{d:}p Rŗ8F#nA0-,؊PS+ 4gFx 70$4`$F)>F,|):xCa["Rp|*je\b8ߣ߳HHIp\9as09̡B;N}wIENDB`tdesktop-1.2.17/Telegram/Resources/art/icon128@2x.png000066400000000000000000001054551326245125100222150ustar00rootroot00000000000000PNG  IHDR\rf pHYs%%IR$;tiTXtXML:com.adobe.xmp xmp.did:1ACBF4C4D24FE4119247E59A0A0429A0 adobe:docid:photoshop:167a618f-0639-117a-8f65-8cab695dcc76 xmp.iid:ef977eab-e8ee-43be-9712-dae3b7cac539 xmp.iid:1BCBF4C4D24FE4119247E59A0A0429A0 xmp.did:1ACBF4C4D24FE4119247E59A0A0429A0 saved xmp.iid:013ff985-91ab-474d-807d-b60bda015c0b 2016-12-19T19:15:42+03:00 Adobe Photoshop CC 2017 (Macintosh) / saved xmp.iid:ef977eab-e8ee-43be-9712-dae3b7cac539 2016-12-19T19:19:58+03:00 Adobe Photoshop CC 2017 (Macintosh) / Adobe Photoshop CC 2017 (Macintosh) 2016-12-19T18:26:51+03:00 2016-12-19T19:19:58+03:00 2016-12-19T19:19:58+03:00 image/png 3 1 1440000/10000 1440000/10000 2 65535 256 256 D cHRMz%u0`:o_FO3IDATx}wdU*wd`vM+u b\]%($ L`r+t«Uy ={%!H27K E)RH"E)RH"E)RH"E)RH"EJZH^*%FD*ܛ8B-'4 ,B)W7-`.0oY3 [Cju-%MĔF 󣤨! ({<@8aBNICB>{;ӱ[;dѹL2#Fu (Lki [,yˏ .XZo^L/Pc j!R41W9t[8 R0`g cA8t:3Ӷk[zF=fo[;7Ӧ{HD^ .>zUd՚ëN=3XC+i:; ;NON.H0D ) F '98`wܾ%{nwWBWt\سΎs9%ǝ5[Zp{D٫{( HA -CC;_M||s&wndut˻/`NJpʥ;'vEE:ij$6]}jTh kPz^{걡O<޷eplG VBK[]{KjOQc 2 {V)؀4p #spdžMO<0Ï$$lӒ+F@K`ug\~YY׾5jJ&&l2\^ @ LAné?|?fy$TDjO† =ιLil neν *S^h.hL2_玁'PP*_|Ƌ7g݁N$!͌Go?4ÿ7?8*Fj\|Nep)Es#ad X ne/=/nxt<-i媏c3/%EeHAd]|)S@LAWzoG+~ 7\7\^\pnTb@ Ava{wt?xwůPJip7N!MU⭁BHߺjw >ښ%X__[?B 2 R%Hn[}Ir>ߠյ,p U^JU`¶2;uK7]ȍK)ȡkcenU qe?ix:!ٴ#elJ>tXs@zUԟ75 /e\CAĬ~m >1]Lܓ2ް)`rO_I'B ")>=/WtŇ?dR %"ӏ?~@`/ZsouK4a̎=[|NyJ@Z-^?-r9!bL:)`졞z'+ \r+?+N\R H B8uJnoIkV~7՚t#2'e=ym>基wZ]"nH2 P@ /^i P^x1kZ~~;RL9u͟4{߇P㪉ERf=y筕 ̶H;篔/@q{gy@5P ֥K_cT`LB~Qse< ƕQqiTBiTkquj3Ef  ˮ5]&Y' |0= m/r}@XXsRTn5/^_~|Gg*`x[>o o;lRuDj0wT@xW];]-T~)'Nrugz_qVi[|+xhLRfLбD}Kkc*cNN&I^p}LRbX/?DpA =_x{^}i@SOw;GDl@8:_zx.Q#GyAg/<1gW|c3Ef --92gghy'>[~,f`t ǜuc-ܮtl(5s(ӭ\GU"¶ "L7^G0J 2O~Jom"VNY Ti. <|]FN<L#5Ø2i"hy@)>A#?j}_vM /"l jӢMW0Mi2RȲ/SrCVIRR 癝L8P!A@kz[GCr>yȣ!J8aniڦ_Ȕ)t`sGT4 I7`ɘ⠩'fFR)(2@PT@U;WFp6^j/`*cT-=ZqiL\ c q\<DT`8;Lܻ;%2TW@L\~ٹﵩSi"rirЇ#w2☆qNk ",p1>k?IPuK__1E O@,;wr7_)uMGt Θc8a-\[F3]նD4VuߟNj{/?؍q8238/| )rk3kg.ێAC!c4D2plh k` P}T= -jpQW-B:!q&D5tiC]iw\oT/&91 ֥GNZJz.> 0\4?GqZ#s->֏˅& m0 [\e ,£<:lsv%ZrKo)>bs73± \4˗Eqt#ϭ/@Ck+T-Q}(A݅| T,@7Sȴp8DU\0WpFks7'd`6R}݈7AуUH#t K?5sJYǟwa`eO`9#P30pG5?k?Y/8H @@PL9-o`R j͹o~H.~ w]+EqlCBKEmG`_ #s{kU3iw߹g]IǟwM߭şVq5/]Oӳ.# fl@!/ HnmjhU2[2|y&6<|'&)8Y\zRTSqsDLŏj kp.Y}Hӯd*zR6L\ uЂ᲋ff\PsֵoMlxwp @ vocKVǥKrY '6۞zI ]qbVR@Z:@@; jm2{{w%Q_qLh嚵gv[@sH%a\,!< Q$va9@@|lp\E!#±k xCndur.5D䤋.aڰ `+; ؞bTMA\8iR!)\ dIW Bi sF0,m})&2p]aIӳP>blMi둘JB!%tڳ2{_ x$@>̵1![+B 虎@}PfE Әʠ/v0#j:ZW8~ 4a8h V9 9BG=O7`W֭nNcby /*;B{sqᠭ/ "p@,ayk 4m c mРQ( $|Nf#pۄC@, ZcOfy<5t`F( ZEǜ$l3'"e:,|vfsE4\׬¨ 9{a@G iwrpuĠxϛ Q-]01R sAC-dz8bʌ{±Ե,>*s_ZK? E43:oX0nskFx$i pXK I zH ~~ߢm$pf)5h`@XvIH jX4VpRs!`3Wv8RLs1l6EMيU"ݼbS(D+Ost =8DPOX H?U~v6*.Z\kW܅aUـ1.)04q/a1g #vMp#>eJ%S~o}gJ%с_±Xt ju¶z1%/88a[Obd㛂rY .[rċWHޟBo< V,j6ΧfOFicw_:S!+!"#v6簒Ih0HQg aR~XUmZL8TIt G 1-585d* '[ӛ0ޗDrS{GUbhLD ΔH +4y&åP!0u@sP6._aw4Kh],:rƏ Ο.Z6ܙ7*_a堭?141,k.3ť*c88Av?Ka_/+>bG?kLꌀ*EG1a}U$lKj8y=ndTFXUKcry 5P [ ^M8̛5F@ ͋?°{f* ՛p)^ +RBwcdA`,:Om' 6oQnD_9:]ְVDp͊ZFCPn5#Uقa/F<MDꔺyZ;zS^_Z\ sC# @9/qɤA HU86+A,ֺ|p+Crsg̋5diKWL@{:)t pip@Ųueڔn#75\oAF>'OQ)#}|Pa'  hӢܱ4_:IN T-4_$#0 9Vp(\^C{.~%̌RhHòh 6ςToP~Oa߇ߣ$/U/4SBpp]R?>0=Xc+$=Rni WŹ h}ܬ|IahHgOk#,iBelR `R|G || >C)p <(L RӴj߽~Z@m߬Dk]@ٝpB*Ú0\QuKbXQ#7W.~bs i?WCY7ƂX4ݽ)M! ؾ\GWAI#0L4m)*ye0J,k&5s˜3?Q`0  ,%Q\kZCj]]F0l)!Fh aQԔMx;7@750scy/%5Dz! p5[LҚ̃1aYfe [ż M%} l~o< $^iͶ$A9ۀa.Jq~ʵr{A0{rǥl@@pe|OǕX<ȷJ8)˗džrU}V6)iO"cfS{45,mASpG_%]: 4R>'?~_sO/>q\8`L=  EH40%P /9TpbKo9-6\7g_9G׫g&cqWVaO_ry}xzq@F!aT8clS` "md`eJ {m lJY}geXE3Z;֝94>P` |7_L{Q<7 _O<SxeP~pI ?g̏@!X71Btї`|#w x,SM #ؐS8V#01}T'dDmֶ( T㿓ّݕU[(-JuQ oq0VPxiTOڠA{ߧL'k!S1aߠQQ /vFl$Ƹ znP(jBz)?؜cKW#/F9{ b9xBqM?(ȋ;.qCA|T_i̙M;``i])/PAn:R9ok%1DpO5]'F)8ς%M90'~GOsX%B8\p峖 @G>[氆TG8Ha09"]⩽tTp/^ފ$ ~-4/Cs!_pn$Y)|(I[(;mbܛt'}nn;FJxl .<ATW~qU`݈܂=L޹#z;zRaHKE|Gceۡ(q@ebB\ǘ 6aJv#W"pZ\kPTwG =)F@ Cv@,aqSyuE1tmLQܷ?b t*97ق<>qs@šB/pܿ*z,Յc[ԔSa3ԛR篬h10a_ĎNleudENl6znn5FPNzUڈEMQ /-W ֮ z(.!64">A%JII͍ ln$)U;;aV(=) PYvgyC<@E'il_1㶑f:3k9-lkpM,T=U>v61\/MDlZ1=}C#*)k a^[gIvTe JY=.s{ƌƑQ(/|?ϧ 1ylv$ M!8ǫ]ilhKগㄖ.^р cuC}҆cnJX>ЬP;03mD/`ΟTO rӨ8]~>!@P2v_[Pyx#(`C q -0m>\?GC\FM#@!(ޕ^yC6r\@9Ok: F^y?51߿cpNGD< sxAJ!"3!H P. XsGvᅩo Τ6vSCeE?23 `w$rqa*ccwݝBR8,W{XQ9o=e{ aKW+yS7sAqr~#0>p' q6Av6(y@^Jv&o+-^5xk @kN0/pobx@s |gQ;R!bw`b5=;$pREc ; 'MF;B.МÖpkw *cwNc"K~~*ңa)`1{:C(R$M=iGWAS]sүRP<((?ơq)?PA "쎽'(ұ=;4n˝ $.EMQ4ĂSc]I eE[䅓 D,A`鉉8mNo@:<< M!lϠ'eM(H ˆ#8ܹs?jGSF*F$_>A\?G|dvǮauN8aܺǐ! &FSz:26<%xѩ.HVS)W~~#X)w 2ցmHOm{O(,@a}gjXslӉ{:a;<Q+a`w*SF-*cqa>R:}m</M7)3powN2Paso{S6AUu mїHl=EWvГPiNM%=~QPP 0}? >7`D@W_HM/q( Lޗk `__oÞw[9@ Fx#T7Ͼih}9W5^޶-H)dzlLbo0+;a:ZrfM;?{^y @|?0B\[^54±]djOC]s\:" `N\ނI[X1TFK[ޛRcJ+yqh0%P~`[0wS op!MjRK'mw{ߡ* ^?GSp{RhPrhF{RX~;֡׷A2_Ys_sp˝IG?<[: }]xyWLˁ̞,0֑@.b{kȉ^b-!|{ߐ=.,SwڻiHgE:a@acw)m(l^KOz{CLAS̆6vć7{D /,9SpGԈspkvm6q}heU!6;ecȦa0ca[wc#*H> H>5ǚ7rz[Oy_4~c3_)N/#JWjSlHƆȘVYqh&R̃b⒀R|||AQ3:RuX& q_D&L'8/w&a4f z/փHAeJt&7>Q>r F>S~O>7gG piᝯm;f 87ܙVB) IK;chAWgK7`c{ܵevTX=csBGqK`SW (ܜ9Mև?ӈԊVacKw6 (?P oaݴ9H Y`g6=(O'LAL~7#lJmTp7TsW< :ބ 2`@fd0(/h'&3ߟ<ܻM%pؽڻet9-߯; 'JO v=qאTqˇC>zKALj a[WBO)>Q ( 18gn}a{2^v0hlx~2sAJ>%6`:x;9jsБ@uu{MHsc ؎޵3zz^ir7;  0+J I _xod Y O>zT>Ϝ)/ lLsBn཯wۺb BC(ğwtݿ|ށh@ML&^Ik<*O>:B"yY} E`>'"-({˰?.oTx5Fv`j+`0{6:C^U>zMr@֭ j2=7ΌM* 3g@+7aG6k_jȼv7&8:29m뭝_$5 y96t&J i r$2t$ pA֓i!V7 =xϯ?nmGDWgC*4ߠӋ~ƞE>3vkȃ*B eZUgܓFo*J 8({)p!2A _ B@`0Gq8vyH[6u &PSnу0^yڝJl0б>}x.Ru L;ї pp("!aCG?y0z2* !j*Ň܀=iBh`OF{>?]D*ndzݞOZo*` <{P4ȊGd>6c1/ľ~ܲ I`Mxif<;$JWM#K-{J&)`LrtBMB~Wp qw޽w5xTUsb Q\(3Lǚ;±n ~wo:Lf Pa]}8?W\Oz !?` H <{oHA~*R0 |**Cwď6Ʈ$TM|)?U~(z or|°RaQrhlW,MO{z4eٔ{t:v[eH0ߥvx BEx!a@?X{2Tud8s"ןP|OHLPtc=v`ᕶ!y TⳒ﷤{ *\ݚ aN_YT!!҉}Pu9ۺ12k ;LP7 QԎ\=+ Ҍt ~P( mc!4m?6 O:[+٠]wyl=> bE=b?SzHsl)0𳍇AP=V\fč(AHSmOʢ wEѵhs>7ޓ#-7xviihs@xizs܅@D6l@z \A*=W)j|;GEӮpb67@HLqǦøxjZ~2XQ{OZ֞W^3_m՟L@;]NBp`%)OG9WR5֘np Bv';7ىh@EDWRš<s~e҉J ±./7mlOW p"kF6`,l N&]'VR9ɟ~n2?`[3q`6ՅpQ8gi#icKW҉])u?1gf@2O}=ܼ|2"e4@6 X!rO_sD&!3cc PK0*89 IS;v8H H/ fbrIM{7לBz8TQ1J0'.o%~r@D %yT]=AcP o4}(R{}_V+7KcHK_'@ X0 Nˤr*rPKBFxJ}ӱP~ n_ S\Wi/ҙ0;_m2+PE (%\V~BՅg ӿ"9;kg]n#kDUfʹY bßC(q [PT{Qt"cxb܀<_;^|0gȥ?dͭ#T@Vq,1%/JF (0%P=~ET}ka;^x9}[ərGnȇsDF } w=6\҂oCc0 VcXՀ@oG=nʬ͎y#z1ufY7PLG*?c b"u7zSW"d@@mJuXJ7Sտ`R~qX*h02f{fLHP}Ɵ~92(Xbtɞ"dpO(Zրȵ`Ƈ3\ޟx])$(S,[7t\x@@u'0 ǁ۰U%1N%YBǪF# @zk۬/*(` /^B0!Ю5'n 1DA!x%&NE:׳[,@)` hñ~fֺb9̴T.FdžplDe)?Sr~CQNnO8{^gWSOAH ]ݐEǏ ݔE*. ♻eg+ÍWLЯZ HH}/OH%-UO~? i;֫)n_E.JkG;P4s[Bo[jxB!۷_Oc( 㡛n2zڕ)3 e2w}rRǻFSUSO7c?*RH5hQ63WqTVlѤ 8M,FQ|1(4,r>?Xcv""0.}ͳU 0+ LQog e\`FcYo*yʶ^ˏ`wc_ЇxU| ZQ+ 37B1 Ex@I*ZjuO~q~n fw1jBh'{o7|49Wp 9JҎaO7Ɵ~uWv(zyo -m@N?#-Y!0Mc|@)\\QVzy]_okhfFT~_Q4HwCsrA  :7|,]LRD@p?jW~-XF:s׿j;দ+67W tQk=175":=).W9@|˸+_pƞl"s +KZx|N;˅rhJ98rO([^7gy g؅5 T]1%' '!"b}~/ooƽ/z% /8pov $ c _?#@xV?;GTmun A.?3O}VYv".2AO0`wovҨQ^ ВU7E86`[\` QbW7Wylϩ% @沷W'(X/T:3U/8S-?cw}^}J@a\ Q 5-95p'>Ɏ9g-4јQ'W:xy?H?U-|_D(fw_B((0R Q@\+`}q=Um$N B%W'<]XdW(A5Y~b`sA|'{0h@ۥ/Y}KmGNQsI˷n^ox9WF~ *Dֳ17K*[=K5x0ڠ ݽ={z>?{tSor z牵eS[Zj%YIQ/ܘI'5?}V6t8ΡZwoj_j\^$lnU;t xmp.did:1ACBF4C4D24FE4119247E59A0A0429A0 adobe:docid:photoshop:3ecbf9ca-0639-117a-8f65-8cab695dcc76 xmp.iid:271b741f-ca3e-4132-9d99-b5c2a2056dd2 xmp.iid:1BCBF4C4D24FE4119247E59A0A0429A0 xmp.did:1ACBF4C4D24FE4119247E59A0A0429A0 saved xmp.iid:013ff985-91ab-474d-807d-b60bda015c0b 2016-12-19T19:15:42+03:00 Adobe Photoshop CC 2017 (Macintosh) / saved xmp.iid:271b741f-ca3e-4132-9d99-b5c2a2056dd2 2016-12-19T19:21:09+03:00 Adobe Photoshop CC 2017 (Macintosh) / Adobe Photoshop CC 2017 (Macintosh) 2016-12-19T18:26:51+03:00 2016-12-19T19:21:09+03:00 2016-12-19T19:21:09+03:00 image/png 3 1 1440000/10000 1440000/10000 2 65535 32 32 hy cHRMz%u0`:o_F'IDATxėTW?fvfggge,WPX[HhԶԦUhMc%HZmZӀPicBV d ݲ ;Ώ{e@Yd澜{9߫DsJ)=\/?l7OC)¡|!8;3ǎتOşM3f5ܲ5j/(bAA[ .ߵ烾kJDz֯={}oZ[ |$hS zDh®?/A| \@}B-˿`+EĄ"xl uuN@rFǧ]ɛ: Nez5G^kixr?3̳~1F0_|joM/g#b퍙Rm־i%&eh⏆;=RtՊ4ErfP Á"D!j?s"Lgݻ'&_'Fa?"4[ތ@ 5PF]N"!aze,k;r,mMSԄʡPDl:ȗ Қ(GN$Φֹ 8vVA+Ų4׳|V3"^1"Vmh#2JؙR0dž!(Ya"""sGUr0z:7KO0\(U}j?c5IrbߙA]1)pɇ$8_aסWԄN˜[3ݲO^c$0>.5Ji|#%]_|oHwsd 5pWs|}c&9! An|BƋA,j5JKQyHU\j_Z-K\1QtowJQm?船*։X~#QQtUAwO'}`dzD:eY޺=>먖:'vTZB".d\`<ٹEYUm.5`"D̤Jip<.Ȓl Ӓ9QWkigo[f-$Xl -nyEzޞp&WiUT2](Vtȹ28v|׊IENDB`tdesktop-1.2.17/Telegram/Resources/art/icon256.ico000066400000000000000000013226261326245125100216350ustar00rootroot00000000000000 hf 00 %v@@ (B; (F} ( n(  @oLpMn3y|~~|yn5y  y 1!|   } z;1| ܾȕ0{?ŊŊŊȑ$ǩ̩hܻڶp‡ĉŊa Ǎɍɍɍի\ֿѥˆŋȍȍg +͑͑եDʔ‰ƌɎ̐//ѕѕܯUڲɖ3ƌʐДŠ3}ԙ"ՙ"ՙ"ՙ"٣8Ƅӫ]Ǎӗ!ՙ"՚%ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&W֦֚$ٝ&՚%՛'Wܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ޤ2Aܠ)ܠ)֛'[ݡ+,,,,,,,,,,ݡ+Ϙ* .////////.И* %/e011110/g&ڠ/'1K1Kڡ/'( @  X<!~V3W3Z=! pM!n{uóz~~~~{uŢo}sO%|U#t~           ~ t~W'm k~   ~ n ou   v 1!x  ~  y u u } ~ v }h /~ | } ~ ԭaÎ#j 5‡‡‡‡‡‡‡‡ѭl<| } ~*شݾ‡‡‡‡o9ĉĉĉĉĉĉĉĉƍβվ™M}̣Tڵ‡Èĉĉĉĉr?ƋƋƋƋƋƋƋƋѤOӻվͦËÈĉŊƋƋƋˆȌȌȌȌȌȌȌȌݽֿƨУL‡ÉŊƋnjȌȌÉh#ʏʎʎʎʎʎʎʎʐӷݽ~ˆÉŊƌȍɎɎʏp+K̐̐̐̐̐ː̓#ڲbխ֯ˆÉŋnjȍʏːːS_ΒΒΒΒԠ:ҥˆÉŋnjɎʏ̐͒icДДДД٨Iʘ8ÉŋǍɎː͒ϔkUҖҖҖҖҖҖ ܭQҠծd‰ŋǍɎ͒ҖҖ‹]7Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!֜*oĎĊƌ͒Ԙ!Ԙ!Ԙ!?rR ԙ#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#ۧ@ˏٶÊ֚͒#֚#֚#ՙ#cї$؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؝'`گ͒ ؜%؜%؜%؜%ї$͕$cڞ&ڞ&ڞ&ڞ&ڞ&ڞ&ڞ&ڞ&ڞ&ڞ&ڞ&ڞ&ڞ&ڞ&ڞ&ܤ5Й,ڞ&ڞ&ڞ&ڞ&ڞ&Ε$ks ٝ(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(Mǃܡ+۟(۟(۟(۟(ٞ(zҙ(iݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ә(qqR۟*ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+۠*ɏhȓ(ޣ,--------------------ޣ,ʔ(Ϙ*+....................И*/Ϙ+!//////////////////Й+%%ݢ/00000000000000ޣ/& ̖+ߤ01111111111ߤ0̖+O:Қ-1ݣ0iߤ011ߤ0ޣ0iӛ-3[D(0` %G0+uPCWOWOwQCJ2+*XOksϳz~~~~zsўlYQ/ [?%ky~ ~ ~~ ~ ~~~~~~~ylaC)C.m}                   } nL4]Q{ 㺀   { `Y" m   o ,  t    v 1!/  v  ~   w <)  s  } ~  u j c~ } } ~  m msP %} | | } ~  ƕ1}V +{Š#z { | } ~ 'Ԭݾ| e ?‡‡‡‡‡‡‡‡‡‡‡‡‡ضzԻ6{ | } ~ˠL‡‡‡‡‡‡‡‡i K~ÉÉÉÉÉÉÉÉÉÉÉÉĊѴսF|}ڼƓ-‡ÈÉÉÉÉÉÉ Z 'ĉŊŊŊŊŊŊŊŊŊŊŊŊΞBֿĤտվŞW+״ԭ^‡ÈÉĉŊŊŊŊŊĉa1{}ƋƋƋƋƋƋƋƋƋƋƋƋƋڷyֿǩ־վγǒˆÈĉŊŊƋƋƋƋƋ}njnjnjnjnjnjnjnjnjnjnjnjǍϮ׿ʭˆÉĊŊƋnjnjnjnjnjE0njȍɍɍɍɍɍɍɍɍɍɍɍϝ=ֿҹƐ'ÈĉŊŋƋnjȍȍɍȍɍaE qAʏʏʏʏʏʏʏʏʏʏʏʏʏڵsҨVÈĉŊƋnjȍȍɎʏʏʏvMiːːːːːːːːːːʏΚ2ݻw‰ĊŋƌnjȍɎɏʏːːu͑̑̑̑͑̑̑̑Ι/ݹrܹڶ‰ĊŋƌǍȎɎʏː̑̑ΒΒΒΒΒΒЖ#׮ˆˆĉŋƌǍɎʏˏ̐̑Βϔϔϔϔϔϔқ,˛=ˆÊŋƌǍɎʏː̑ΓϔŠѕѕѕѕѕѕѕҙ&k߼ײjˆÉŋƌǍɎʏːϔѕѕË{Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ إ?ȌǔÉĊƌǍɎʏЕҖ Җ Җ ‹[Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!ԙ"߲Zתݿ‰ĊŋǍɏі Ә!Ә!Ә!Ә!gu/֚"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ؠ2yÊŋǍҗ!ՙ"ՙ"ՙ"ՙ"ՙ"~;/!җ#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚$ݬJЙǖ5‰ƌӘ"֚#֚#֚#֚#֚#ԙ#O9 ͔"כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$؝)j޹ѨZċԙ#כ$כ$כ$כ$כ$כ$͔#Ď"]ٝ&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؜%ݧ=ɉۼÊ֚$؝&؝&؝&؝&؝&؝&ٝ&ǐ"ihל&ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڟ)W֦Ѥכ%ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'؝&pϖ%۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(ܢ/s޸m۟(۟(۟(۟(۟(۟(۟(۟(ϗ&} ڟ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)۟)!#З'yޡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ј'qRڞ*ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ڟ*׏h Ž&+ޢ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ޢ,ŏ&3͖)O--------------------------------ϗ)YϘ*_..............................И*kҙ+W.//////////////////////////.Қ+a͖+9/0//0//0//0///////////////Ϙ+?%ۡ.0000000000000000000000ܢ.&͗+9ޤ/111111111111111111ޤ/Ϙ,?˕,3ݢ012222222222221ݢ0͖,9aGO:˖,;ۡ/wޣ0ߤ01111ߤ0ޣ0ۡ/yΘ,=[D  (@ B ;(3jIO}UaWiWi~ValJO?+5   .'Ykjrճz~~~~{sٜj[o5$) [mp÷}~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~qLj]s+ey                      zg7%#`y{                           | c dD;u ϻ   w lJC `s~     c  m    p t    v 1"v  ~   x , s ~ } ~   v  k  ~ } ~ ~   n [ Q~ | } } ~   ` ]A-} { | } ~ ~   V;#t| { { | } ~ ~  !˜կdwvR 9‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡č ͬt#z { | } } ~ɝF‡‡‡‡‡‡‡‡‡‡Z GzˆÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈ״tƨӹ/{ | }} ~ظ{ʚ;‡ÈÈÈÈÈÈÈȈ}xT 9ĉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉͬɬ־ռ>| }}~$ԯٶq‡ÈÉÉÉÉÉÉÉÉĉ\ Gzĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉ˘6ֿֿֿ̰վ™O|}ȞKѤ‡ˆÈĉĉĉĉĉĉĉĉĉ|4$ŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊײnֿϵֿֿֿֿǢaغ‡ˆÈÉĉŊŊŊŊŊŊŊŊĉQ9oaNjƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋʥֿҹվֿֿĤʙ8‡ˆÉĉĊŊŊƋƋƋƋƋƋƋtqnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnj˗0׿ս־־βشlˆÈÉĊŊŋƋƋnjnjnjnjnjnj ȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍׯg͞ˆÈĉĊŊƋƋnjnjȍȍȍȍȍĊnM -ˏɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɍǞˆÉĉŊŋƋnjnjȍȍɎɎɎɎˏ`?t_ʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏ͖*Ǔ,ˆÉĊŊŋƌnjǍȍɎɎʏʏʏʏym~ːːːːːːːːːːːːːːːɎѡD˜Ԭ^ˆÉĊŊƋƌnjȍȎɎʏʏːːː̑̑̑̑̑̑̑̑̑̑̑̑ʏП>‡ŎˆÉĊŋƋnjǍȍɎɏʏːː̑̑͒͒͒͒͒͒͒͒͒͑Н9޾ˆÉĊŋƋnjǍȎɎʏʏː̑̑͒ΓΓΓΓΓΓΓΓΒ͗ŠˆÉĊŊƋnjȍȎɎʏːː̑͒Βϔϔϔϔϔϔϔϔϓ̝BÉĊŋƋnjȍȎɏʏː̑͒ϓϔ‹ЕЕЕЕЕЕЕЕЕӚ*Ăٵo‰ÊĊƋƌǍȎɏʏː͒ДЕЕË‹іііііііііііі ݱY֫˛ˆÉĊŋƌȍɎɏʐ͒ѕіііČҗ җ җ җ җ җ җ җ җ җ җ җ җ Җ՝.xˆÉĊŋƌǍȎɏ̑Җ җ җ җ җ ‹Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!ӗ۩F͕ÉĊŋƌǍȎ̑ӗ Ә!Ә!Ә!Ә!Ә!Wՙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"Ԙ!՚%d۴ʚ<‰ÊċŌǍ̑Ԙ!ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"gzX%כ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ"٢5ƃկfÉċŋ̒ՙ"ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#כ#m5 Δ"֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚#֚#߰Rդ͉Ê֚̒#֚$֚$֚$֚$֚$֚$֚$ї#" Ȑ"כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$ٟ,s׳‰̒כ$כ$כ$כ$כ$כ$כ$כ$כ$ɑ" Sٝ%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜$ݪC͓͓ ؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%!eH4 ՚%ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ'`۱"Γ!ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ל&lNʓ$ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ&ܤ4~٨Hڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'̔$t'۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۞&Kљݤ5۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(ܠ({5Ζ&ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ(ܟ(ܟ(ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)З&z%ܠ)ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*!3Ζ'ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ϖ'qR ٞ*ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+۟*h%7ޢ+ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,%Cȓ(k--------------------------------------------̕({љ*------------------------------------------ԛ*)՜+........................................؞,O92% ՜,//////////////////////////////////////؞,_Fљ+////////////////////////////////////Ԝ+." ɓ*O/00000000000000000000000000000000/˕*[%ڠ.100000000000000000000000000000ܢ.&#ʔ+S/111111111111111111111111110Η,]YBқ-cߤ021111111111111111111120ӛ-kkN ( ő+Eۡ/122222222222222221ۡ/ɔ+K<, O: ő*C؟.ݢ0ޣ023222232ߤ0ݣ0٠/ȓ+G[D F3n!%Đ+7Ð*AÏ*Aő+7q!'L8(    % 7.OQ7imKX^bccccb_XoLU:k3#Q 9'  !;R8eYgq{~~~~~~~~|shņ[X<m?#  -H1]\mӶ|~~~~~~~~~~~~~~~~~~~~~~}oی_R7e 1 'L3_bu齁  ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ xeX<i-  !?Xtὂ                                       v].G  <)Qe}                                           iK4]  @+Si                                                 mQ7_ -Ce                                                     j>*O +Z~ a3U:_v ߽   y fFo  +`     g3D.Mv ۽      y V<]oLy  ~W!_     h  )   +m       t '7  "3u ӽ    z 9'A  *7x ݽ  ~    | C.G  *7y ߾ ~ ~ ~ ~    } C-G  !1y ۾  ~ ~    } ;(?  'u Ͽ ~ ~ ~ ~ ~    { '3 n ~ } } ~ ~ ~     v  %`  } } ~ ~ ~ ~    k  qMo† } } } } ~ ~ ~ ~    †Z  B.C| | } } } } ~ ~ ~    ]@Y  !x | | } } } } ~ ~ ~    ~-b ‡ { { | | } } } } ~ ~ ~    m  U;Q‡{ | | | | } } ~ ~ ~   ĉoM i yz { { { | | | } } ~ ~ ~  )őݼҩX ) \ ĉz z z { { | | | } } } ~ ~ ~   Ψ[̟FÈh &/‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡•?=y z { { { | | } } } ~~œծ ‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡ÇI2Ej ȇ‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡̢Qδֿƣezz { { { | | } } } ~~2‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡s:(;ȈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÊбγսֿ˫u}{ { | |}}}~ ~~ ϪaΡI‡‡ÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈȈƊ\AS nĈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈ̝DѸս־־ϲ{ { | |}}}~~~ș޿‡‡‡ÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈv4$7ÈÈÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÈڹ־Իս־־־ҷ#| | | }}}~~~~5ܻ ˆˆ‡ˆÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉNjW=O kŊĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĊв־սֿ־־־־Ի1{ |}}}~~|ЬgŒ‡‡ˆÈÈÈĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉs %ĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉ̛;־־ֿֿֿֿֿվ@{ |}}~~̟ΡH‡‡ˆÈÈÈÈĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉŊ7'9] wɌŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊʼnصxֿ־ֿĤֿֿֿֿֿֿֿ™P|}}| :ݾ‡‡‡ˆˆÈÈÉĉĉĉŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊNjk zŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊֿֿͭƨֿֿֿֿֿֿֿƢb}{ѯm۸ ‡‡ˆˆÈÈÉĉĉĉĉŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊ D0?ɍƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋ˘5ֿֿֿɬֿֿֿֿֿֿֿֿֿ̬tͣˇˆˆÈÈÉÉĉĉĊŊŊŊƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋˎiJ [ kȌƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƊֱoֿֿֿ̱վֿֿֿֿֿֿվȫ̞C‡ˆˆÈÈÉĉĉĉŊŊŊŊƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋs ~njnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjƋ˦ֿ׿׿׿жվֿֿֿֿֿվֽۻzˆˆˆÉÉĉĉĊĊŊŊŋƋƋƋnjnjnjnjnjnjnjnjnjnjnjnjnjnj =*9ʎnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjʖ.׿׿׿׿Ӻվֿֿֿ־ױˆˆˆÉÉĉĉĊĊŊŊŋƋƋƋnjnjnjnjnjnjnjnjnjnjnjnjnj̏dG Ue}ˏȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȌծf׿׿׿׿ս׿׿׿־ʭ‰ˆˆÈÉÉĉĊŊŊŋŋƋƋƌnjnjnjȍȍȍȍȍȍȍȍȍȍȍȍɍq  xȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍNjȠֿ׿־ʙ:ˆˆÈÉÉĉĊŊŊŋŋƋƋƌnjnjnjȍȍȍȍȍȍȍȍȍȍȍȍ~ ɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍ˔(¢¢شm‡‡ˆÈÈĉĉĉŊŊƊƋƋƋNjnjnjȌȌȍɍɍɍɍɍɍɍɍɍɍɍɍ&+G2?ΑɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎԪ]ѷХˆˆˆÉÉĉĊĊŊŋƋƋƋƌnjnjǍȍȍȍɎɎɎɎɎɎɎɎɎɎΒpO [`qΑʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎɌĘ׿‡‡ˆÈÉĉĉĊŊŊƋƋƋNjnjnjȌȍȍɍɍɎʎʎʎʎʎʎʎʎʎˏpuˏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏ˓$վ׿ǔ.ˆˆ‰ÉÉĊĊĊŋŋƋƌƌnjnjǍȍȍȍɎɎɎʏʏʏʏʏʏʏʏʏz |ˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏԧTӪ[‡‡ÈÈÉĉĉŊŊŊƋƋnjnjnjȌȍȍɍɍɎɎʎʎʏˏˏˏˏˏˏˏːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːʏnjŌėƑˆˆÉÉÉĊĊŋŋŋƌƌnjnjǍȍȍȎɎɎɎɏʏʏʐːːːːːːȎ!#ʏ̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐ːȍƌϢIɘˆˆÈÉĉĊĊŊƋƋƋnjnjȌȍȍɍɎɎʎʎʏʏˏˏː̐̐̐̐̐єF2;C/9є̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑ːȎƌΟCƑČ ˆ‰ÉÊĊĊċŋƋƌƌǍǍȍȎȎɎɎɏʏʏʏʐːːˑ̑̑̑̑ҕqPUfH Oӕ͑͑͑͑͑͑͑͑͑͑͑͑͑͑͑͑͑͑͑͑͑͑͑̑ʎƌ͝>É͟EˆˆÉÉĉĊŊŊŋƋƋnjnjȍȍɍɎɎʎʏʏˏˏːː̐̐̑͑͑͑єdo^eҕ͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒ʐnj̛9޿ڹwˆ‰ÉÊĊĊŋŋŋƌƌǍǍȍȎɎɏɏʏʏʏːːːˑ̑̑̒͒͒ϓrjwѕΒΒΒΒΒΒΒΒΒΒΒΒΒΒΒΒΒΒΒ̘̐5ݼ|ԬˆÈÉĉĊŊŊŋƋƌnjnjȍȍɍɎɎʏʏˏˏː̐̐̑̑͑͑͒ΒϓwsДΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓЗ%ڳˆ‰ÉÊĊĊŋŋŌƌnjǍǍȍȎɎɏɏʐʐːːː̑̑̑̒͒͒ΓΓ{sєϓϓϓϓϓϓϓϓϓϓϓϓϓϓϓϓϓΑ͘ǒ,ˆÈÉÉĊĊŊƋƋƌnjǍȍȍɎɎʎʏʏːːː̐̑͑͑͑Βϓϓϓzsѕϔϔϔϔϔϔϔϔϔϔϔϔϔϔϔϔϔ͑ݺѧU‰ÉÉÊĊċŋƌƌƌǍǍȎȎɎɏʏʐʐːˑˑ̑̑͒ΓϔϔϔϔztҕДДДДДДДДДДДДДДДДДϓ߸iˆÉÉĉĊŊŋƋƌnjnjȍȍɎɎɏʏˏːː̐̑̑͑ΓДДДДДzuҖЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕϓڬPٶˆ‰ÊÊĊċŋŌƌƍǍǍȎȎɏɏʏʐːːˑ̑̑ϔЕЕЕЕЕЕ|uӖѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕєГ٦AМˆˆˆÉĊĊĊŋƋƌnjǍȍȍɎɎɏʏʏːː̐̑Гѕѕѕѕѕѕѕ~ns՘ііііііііііііііііііііііДӚ&jǔ0‰ÉÊĊċŋŌƌƍǍǍȎȎɏɏʏʐːˑˑЕіііііііҗzc_י ҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҕєأ;ɏѧWˆ‰ÉĊĊŋŋƌƌǍȍȍɎɎʏʏʏː̐ѕҖҖҖҖҖҖҖҖԗ vjLI؛!җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ ѕӗ!ݱWخݿ‰‰ÉÊĊċŌƌƌǍǍȎȎɏɏʐʐˑіҗ җ җ җ җ җ җ җ җ ך jiC0 1ך!ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗҕ֝-vشˆÉÉĊĊŋƋƌnjǍȍȎɎʏʏːі ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ٛ!sRMі Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!җ ҖڨCΖ‰‰ÊĊċŋŌƌƍǍȎȎȏɏːҗ Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!؛!H3 1 ČԘ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ӗՙ#^ܶŐ)‰ÉĊĊŋŋƌnjǍȍȎɎːӗ Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Е Ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"Ԙ"Ԙ!Ӗء3~ΡK‰‰ÊĊċŋŌƌǍǍȍʐӘ!ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"‹ ֚"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"Ԙ ԗݫJҡڷvˆ‰ÉĊŊŋŋƌǍǍːԘ!ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"jcٝ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#Ԙ֜(iϤ‰ÉÊĊŋŋƌƌʐԙ"ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#כ#{I4 /ڝ#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֙"՗ڥ;ʌ‰ÉÊĊŋŋŋː֚"֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#ܞ$xVK  ɑ!֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚#֚#֙!֚#߲W۰‰ÉÊċċː֚#֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$ԙ#%כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ#֘٠1yǖ6‰ÉÊÊˑכ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$ ri۞%כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ#֙ ݫHҝѩ[‰‰‰ˑכ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$ٜ$A/ %ڝ%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%ך!؞(fݾ‰͒؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%ޠ&nO?Œ!؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜$ך ۥ:ɉԯ͓ ؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%̔#  ~}۟&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&؜#؜$TثΓ!ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ڝ& K6 '۞&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ؚ!ڠ.rϔ!ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ޡ'xVA"ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ٝ&ٛ!ުBΓǖ7Ж"ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ʒ$ o]ߡ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ٜ$ٞ'[ܲЧXі"ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ܠ'} ї%۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۞'ڜ"ܤ3y۞&۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'ڞ'@/  ݡ(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(ڞ&ڝ#FИ^۟'۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(ܟ(">- ٝ'ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(۞%ܟ(_޷̎۞'ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ߡ)hK/"ޡ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ(۞$ܡ+ܠ(۞&ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)$H4 ٞ(ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ߢ)rR1"*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ޢ*$2$ ՛(ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ܠ*ZB!v_+ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ**"ʓ'ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ҙ( tT'ޡ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ++iA~"q,ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+,$ ̔(,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,Ӛ)& \Cڟ+,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ߣ,`+pA------------------------------------------------------------------------------------------y!]~"o.---------------------------------------------------------------------------------------%&--------------------------------------------------------------------------------------˕(  ˕)....................................................................................՛+0# ) И*..................................................................................؝+Q;1$ Қ*................................................................................ٟ,]D2% љ+//////////////////////////////////////////////////////////////////////////////؞-_F) ͖*////////////////////////////////////////////////////////////////////////////֜,P; Ž(//////////////////////////////////////////////////////////////////////////͖*." %m000000000000000000000000000000000000000000000000000000000000000000000000' u"?ߣ.00000000000000000000000000000000000000000000000000000000000000000000/{#UdIњ+000000000000000000000000000000000000000000000000000000000000000000מ-˃`% %o0000000000000000000000000000000000000000000000000000000000000000(!~\%ӛ-211111111111111111111111111111111111111111111111111111111111؞.͓l3%[ޣ/111111111111111111111111111111111111111111111111111111110'o;, Ð)011111111111111111111111111111111111111111111111111111ȓ*YAYBȓ*02111111111111111111111111111111111111111111111111˖+kN!P;ő+}ݢ0322222222222222222222222222222222222222222230ʔ+aG( ~%Uљ-2222222222222222222222222222222222222223ӛ-Ŷ'c<, YB!Đ*sқ-2322222222222222222222222222222222՝.Ȕ+iM)O:'aΘ,ۡ/33222222222222222222222223ޤ0Θ,)i\D' W@%%Y˕,Θ-ٟ/2444333333334442ڠ0Ϙ-͖-'_bH+'R<'~]A|%U*eȒ,uĐ+**Đ+Ǔ,wÐ+g|%WaCYB)-!  (    !!!########!!!  !#%'-19A I Q[&c*i,o.s/ u/ w0!w/ w. u-o*k'e!] S KC;3-)%#!  #'-7 GY-oH1Y=pMZŏbՠnsv|}xsofׅ[wQ\?N62"u!] K;/'#!  !%-=U2"qT:tPaѦq{…Ć…~~~~~~~…Ć …}se|U[>:({[C1'#  !'1 E(gP7xRjݱyÆ…~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~…Æ|nXZ=3#q M5)#  #+?%cR8Yq绀Æ…~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Æv``A0!m G/%  #/ K<)yqMlĆ„~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Ćr}UH1U3%!  #- KC.}Ut ą  ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ Å Å z^Q7W3%  !) C;(wyRuÅ Å ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ „ Ć { ^I2 M-#  #3$_gFp Æ                                                                           … Æ wvQ4#o;%  '??+{^ǹ ć                                                                                 Ć iS9 K)! !) MX<n… …                                                                                      ć vkI![/! !+UhHwŇ                                                                                           ć ~ }V,g1! !+WoL{ Ň                                                                                               Æ [/!k1! 'SoL| Ň                                                                                                   Æ [,g/!  % IfFzƇ                                                                                                       Ć }U#[)  #;S9t Ƈ                                                                                                           ņ } lJ K% !-8&qjƈ ƈ w Q89! %Q}V … ň g/ i)  !7S9z ň Æ oL E# %$]bň   Ɖ r =*u+ 5Y=~ ć    … vQ E!  #U`Lj        Lj q 6%o'+F0z Ň          … eE7   =tPć lj d S!  #!Yj Ȋ  ň y =*u'  '@,y| ć    bD1/aC…      lj Z A ;~Wȉ         ȉ l S! Kf Ȋ           ƈ x 2"i#  $[s lj  ~          Æ D.}%  !2#i{ Ĉ     † U:'  !<*s †       ʼn aC+  #D/}         Ȋ jI-  #H1… ~ ~ ~        ɋ pM-  #J3Æ ~ ~ ~ ~ ~         ʋ rN- !G1† ~ ~ ~ ~ ~ ~ ~        ɋ oL+ !C.{† ~ ~ ~        ɋjJ) ;(o ~ ~ ~ ~ ~        ȋbC% .c Ç ~ ~ ~ ~ ~ ~ ~        ȊT:! Sz ň } } ~ ~ ~ ~ ~ ~ ~        Ç B.y  Cr lj } } } } ~ ~ ~ ~ ~ ~ ~        † 1"e 3e Ɍ  } ~ ~ ~ ~ ~ ~ ~       ň{ O )yT ˍ } } ~ ~ ~ ~ ~ ~ ~       ȋn ; Y>ʌ } } } } ~ ~ ~ ~ ~ ~ ~       ˌ\ + 9'mdž } } } } } } ~ ~ ~ ~ ~ ~ ~       ˌcD! K{ ň~ | | } } } } } } ~ ~ ~ ~ ~ ~ ~       Ɖ=*q 1g Ɍ~ | | | } } } } } } } ~ ~ ~ ~ ~ ~ ~       Ĉ}M !kK̎~ | } } } } } } ~ ~ ~ ~ ~ ~ ~      Ɍh 1<)oƊ~ | | } } } } } } } ~ ~ ~ ~ ~ ~ ~      ̎hH CzƉ} | | | | } } } } } } } ~ ~ ~ ~ ~ ~ ~      †Ɖ6&i '\ ˍ| z | | | | | | } } } } } } ~ ~ ~ ~ ~ ~ ~      NJv  = L5ˌ| { { { | | | | | } } } } } } } ~ ~ ~ ~ ~ ~ ~      ̎zU ! Iʼn| { | | | | | | } } } } } ~ ~ ~ ~ ~ ~     Ȍ=*o %^ ̎‡| { { | | | | | | | } } } } } ~ ~ ~ ~ ~ ~    } } Njx ; D/wˍ†‡{ { { { { | | | | | | | } } } } } ~ ~ ~ ~ ~ ~   z#԰hϢ֮ѥܻzŐ(}ΏsP  ;yNJ‡z z z { { { { { | | | | | | } } } } } ~ ~ ~ ~ ~ ~   } طyծbÇʼn/ _ pN Ώ‡z z z z z { { { { | | | | | | | } } } } } ~ ~ ~ ~ ~ ~   z’4ٶ͡M~ʌm - (YÈÈz z z { { { { { | | | | | | } } } } }~~~~~  { ϩ^ڸ ͏T; %e ̎‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡zv z { z { { { { { | | | | | } } } } } ~~~~~| ÎΣP‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡NJ~ ? @-q̎LJ‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡ÈȞOѵгSyy { z { { { { { | | | | | } } } } } ~~~~~yÔ8ۺɗ‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡ϐqO /rɌ‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡È ˣVĦçȥizy { z { { { { { | | | | | | } } } } }~~~~~~ { Ыd‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡ĉ"Q T:ΐ‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡ˆ Ð-ӹãս־¥˫u~x { z { { { { { | | | | | } } } } } ~~~~~{ ƔʜA ‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡͏[  7zȌˆÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÉ ع׿Ŧս־־¥ϲ!x |{ { | | | |}}}}}}}~~ ~~~yÕ<ݾصq‡‡‡‡‡‡ÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈȈĉƋ,[ ]AБÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÇǔ0ռȩս־־־¤ҷ,x |{ { | | | |}}}}}}}~~~~~~ { ҮhϢ ‡‡‡‡‡‡‡ÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈ͏b ! 7|ȋÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈĈ„ Ѫb־ʮս־־־־Ի9w || { | | | |}}}}}}}~~~~~z Ș‹‡‡‡‡‡‡‡ÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈĉȌ/!_ ]@БÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈĉ‡•־Ͳս־־־־־ֿGx {| { | | | ||}}}}}}~~~~~xė@˝C ‡‡‡‡‡‡‡ÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈ͏b ! 5{ȌÈÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÈĈǒ+ӹ־еս־־־־־־׿ÛUy { | | | | } |}}}~~}~~~~} { Ӱmضrˆˆˆˆˆ‡ˆÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĉNj+Y T:БĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĊÆ ѧZ־־׿Ҹ־־־־־־־־ֿ¤ǣd{{ }| | } }}}}~~~~~~z!˝ϣ ‡‡‡‡ˆˆÈÈÈÈÈÈÈĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉϑ\  -uˎĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉŊÇܿ־־׿Ի־־־־־־־־־å˪r~z }||}}}}}~~~~~~~wřDŒ‡‡‡‡ˆˆÈÈÈÈÈÈÈĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉƋÈ!MA.oΐĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉňƐ&Ѵ־־־սս־־־־־־־־־¥ΰ y }||}}}}}~~~~~~{ zӲr˝A ‡‡‡‡ˆˆÈÈÈÈÈÈÈĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉВrO   !g͏ĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉÆ ϤR־־־־׾ĤվֿֿֿֿֿֿֿֿֿֿåѶ*y }||}}}}}~~~x$΢صp‡‡‡‡ˆˆÈÈÈÈÈÈÈĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉĉȌ ;)UȌƋŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊƋÇ ۻ־־־־׿Ƨվֿֿֿֿֿֿֿֿֿֿֿ£Ի7x ~}}}}~~~~~wśHΠ ‡‡‡‡ˆˆˆˆÈÈÈÈÉÉĉĉĉĉĉĉĉŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊѓW=  vS ђŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊƊƏ"ϰ־־־־ɫվֿֿֿֿֿֿֿֿֿֿֿֿֿDx }~}}}~~z {յwЇ‡‡‡‡ˆˆˆˆÈÈÈÈÉÉĉĉĉĉĉĉĉŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊ̏r' 7ɍŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊć ΠKֿֿֿֿֿ̰վֿֿֿֿֿֿֿֿֿֿֿֿֿ›Sy|~}}~w&Цʛ> ‡‡‡‡‡ˆˆˆˆÈÈÈÈÉÉĉĉĉĉĉĉĉŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊƋ̏4%_O7{ђŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊƋÇ ٷ~ֿֿֿֿֿγվֿֿֿֿֿֿֿֿֿֿֿֿֿֿäǢbz|~~|vƝLֲk‡‡‡‡ˆˆˆˆÈÈÈÈÉÉĉĉĉĉĉĉĉŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊŊВ~Y   jΑŊƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋŊƋƍֿֿֿֿֿ̫ѷվֿֿֿֿֿֿֿֿֿֿֿֿֿֿֿֿæʩp~|z {ַ|̜ †‡‡ˆˆˆÇÈÈɉÉĉĉĈĉÊĊŊŊŊŊŊĉŊƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋɍ 9#KƋȌƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋʼn͞CֿֿֿֿֿӺֿֿֿֿֿֿֿֿֿֿֿֿֿֿֿֿֿֿæΰ}~'ҫЇ‡‡ˆˆˆˆÈÈÈÉÉÉÉĉĉĉĉĊĊŊŊŊŊŊŊŊƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋғO7{ eG ҔƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋnjć شvֿֿֿֿֿֿԼֿֿֿֿֿֿֿֿֿֿֿֿֿֿֿֿֿվֿÜʚ: ‡‡‡ˆˆˆˆÈÈÈÉÉÉÉĉĉĉĉĊĊŊŊŊŊŊŊŊƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋВg  %s͐ƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋNjŌʥֿֿֿֿֿֿֿֿ¢վֿֿֿֿֿֿֿֿֿֿֿֿֿֿֿս־կe ‡‡ˆˆˆˆÈÈÈÉÉÉÉĉĉĉĉĊĊŊŊŊŊŊŊŊƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋȍˆE)QɍnjƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƊ˚<ֿֿֿֿֿֿֿĥվֿֿֿֿֿֿֿֿֿֿֿֿֿֿԼĥȕ ‡‡‡ˆˆˆˆÈÈÈÉÉÉĉĉĉĉĉĊŊŊŊŊŊŊŊƊƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋƋҔW=  fH ӕnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjȌň ֱnֿ׿׿׿׿׿׿׿ǩվֿֿֿֿֿֿֿֿֿֿֿֿվԼϳ‡ˆˆˆˆˆÈÉÉÉÉĉĉĉĊĊĊĊŊŊŊŊŋŋƋƋƋƋƋƋƋnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjВi  #rϑnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjȍƋǟ׿׿׿׿׿׿׿׿ʭվֿֿֿֿֿֿֿֿֿֿֿսֿȖ3ˆˆˆˆˆˆÈÉÉÉÉĉĉĉĊĊĊĊŊŊŊŊŋŋƋƋƋƋƋƋƋnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjʎA  "InjɍnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjNj˘6ֿ׿׿׿׿׿׿׿׿ͱվֿֿֿֿֿֿֿֿֿֿԼƧӫ\ ˆˆˆˆˆÈÉÉÉÉĉĉĉĊĊĊĊŊŊŊŊŋŋƋƋƋƋƋƋƋnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjҔN7 wY> ӕnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjňԭf׿׿׿׿׿׿׿׿׿ϵվֿֿֿֿֿֿֿֿ־ԼеÌ ‡ˆˆˆˆˆÈÉÉÉÉĉĉĉĊĊĊĊŊŊŊŊŋŋƋƋƋƋƋƋƋnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjnjғ`  fєȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍɍƋĘ׿׿׿׿׿׿׿׿׿Ҹ׾׿׿׿׿׿׿׿ֽ׿۹‡ˆˆˆˆˆÈÉÉÉÉĉĉĉĊĊŊŊŊŋŋŋŋƋƋƋƋƌƌnjnjnjnjnjnjnjȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍ̐ 1   5̐ȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȌʖ0ռԻ׿׿׿׿׿׿ռƨƓ,ˆˆˆˆÈÈÉÉÉÉĉĉĊĊĊŊŊŊŋŋŋŋƋƋƋƋƌƌnjnjnjnjnjnjnjȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍɍϒ5%[  9(_ВȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍƊӪ^վ׿׿׿׿־ռѷЦS ˆˆˆˆˆÈÉÉÉÉĉĉĊĊĊŊŊŊŋŋŋŋƋƋƋƋƌƌnjnjnjnjnjnjnjȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍԖiJ kK ՖȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍɎƊä־׿׿ֽݾ ˆˆˆˆÈÈÉÉÉÉĉĉĉĊĊŊŊŊŋŋŋŋƋƋƋƋƌƌnjnjnjnjnjnjnjȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍȍѓl  lғɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɌʔ*Ӹŧ־׿ռǪ֯‡‡‡‡‡‡ÈÈÈÈÈĈĉĉĉĉʼnʼnŊŊŊƊƊƊƋƋƋƋNjNjNjnjnjnjȌȌȌȌȌȌȍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍɍ̏ 7   5͑ɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎȋҧVȫսռҹď$†ˆˆˆˆˆÈÉÉÉĉĉĊĊĊĊŊŊŋŋŋƋƋƋƋƋƌƌnjnjnjnjǍǍȍȍȍȍȍȍȍɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎʎГ9(_  5&[ϓʎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎʏNjܾɬ׿͠G ˆˆˆˆˆÈÉÉÉÉĉĊĊĊĊŊŊŋŋŋƋƋƋƋƋƌƌnjnjnjnjǍǍȍȍȍȍȍȍȍɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎ֗eG  _C ֗ɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎʎɒ&гӻٶt ˆˆˆˆˆÈÉÉÉĉĉĊĊĊĊŊŊŊŋŋƋƋƋƋƋƌƌnjnjnjnjǍǍȍȍȍȍȍȍȍɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎɎӕe  `ՖʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎɋѣO׿΢‡‡‡‡ˆÈÈÈÈÉĉĉĉĉĉŊŊŊŊŊƊƋƋƋƋƋNjNjnjnjnjnjȌȌȍȍȍɍɍɍɍɍɍɎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎʎϒ})  #yДʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏˏȋۺֿֿˇˆˆˆˆ‰ÉÉÉÉÉĊĊĊĊĊŊŋŋŋŋƋƌƌƌƌƌnjnjnjǍǍǍȍȍȍȍȍɎɎɎɎɎɎɎʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏ̐Ɏ"E  ;Ê͑ʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏˏɑ"έ׿־ʚ:ˆˆˆ‰ÉÉÉÉÉĊĊĊĊĊŊŋŋŋŋƋƌƌƌƌƌnjnjnjǍǍǍȍȍȍȍȍɎɎɎɎɎɎɎʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏӖC0i  6&[єˏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏɍСHվԯd ˆˆˆˆ‰ÉÉÉÉÉĊĊĊĊĊŊŋŋŋŋƋƋƌƌƌƌnjnjnjǍǍǍȍȍȍȍȍɎɎɎɎɎɎɎʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏʏטgI  Y? }טˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏːɋڷzսő ˆ‡‡‡ˆÈÈÈÉĉĉĉĉĉŊŊŊŊŊƊƋƋƋƋNjnjnjnjnjnjȌȌȌȍȍȍɍɍɍɍɍɎɎʎʎʎʎʎʏʏˏˏˏˏˏˏˏˏˏˏˏˏˏˏˏ՗bzVטːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːː͐ʐ˨վܼ‡ˆˆˆˆÉÉÉÉÉĊĊĊĊĊŋŋŋŋŋƋƌƌƌƌnjnjnjǍǍǍȍȍȍȎȎȎɎɎɎɎɎɏɏʏʏʏʏʏʐʐːːːːːːːːːːːːːːҕv j՗ːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːɏĈ˚=տƒ,ˆˆˆˆ‰ÉÉÉÉĊĊĊĊĊŋŋŋŋŋƋƌƌƌƌnjnjǍǍǍǍȍȍȍȎȎȎɎɎɎɎɎɏɏʏʏʏʏʏʐʐːːːːːːːːːːːːːϓ 1  #|єːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːːː̐ːɏƌǓ-œտվϤP ˆˆˆˆÉÉÉÉÉĊĊĊĊĊŋŋŋŋŋƋƌƌƌƌnjnjnjǍǍǍȍȍȍȎȎȎɎɎɎɎɎɏɏʏʏʏʏʏʐʐːːːːːːːːːːːː͑ː&E   3Г̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐̐ʏnj†Ǔ.֯eөտտۻ} ‡‡‡ˆˆˆÈÈÉÉĉĉĉĊĊŊŊŊƋƋƋƋƋNjnjnjnjnjȌȌȌȍȍȍɍɍɍɎɎɎʎʎʎʎʎʏʏˏˏˏˏˏːː̐̐̐̐̐̐̐̐̐̐̐͐Ӗ:)_  +IΒ͒̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑ːȍȃ)խ_УտֿҪˆˆˆ‰‰‰ÉÉÉÊĊĊĊĊċŋŋŋƋƋƌƌƌnjǍǍǍǍȍȍȍȎȎȎɎɎɎɏɏɏʏʏʏʏʏʐʐːːːːːˑˑؙ̑̑̑̑̑̑̑̑̑̑̑Z@ y  7']Ӗ͑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑ːȍÈŐ&ӪZֿֿ͝Ì ‡ˆˆ‰‰‰ÉÉÊÊĊĊĊċċŋŋŋƋƌƌƌƌnjǍǍǍǍȍȍȎȎȎȎɎɎɎɏɏɏʏʏʏʏʏʐʐːːːːːˑˑ̑̑̑̑̑̑̑̑̑̑ٚiJ  V= ȗؙ̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑͑̑ːȎÉŏ#ҧTʗվ˛>ˆˆ‰‰‰ÉÉÊÊĊĊĊċċŋŋŋƋƌƌƌƌnjǍǍǍǍȍȍȎȎȎȎɎɎɎɏɏɏʏʏʏʏʏʐʐːːːːːˑˑ̑̑̑̑̑̑̑̑̑יa bF ښ͑͑͑͑͑͑͑͑͑͑͑͑͑͑͑͑͑͑͑͑͑͑͑͑͑͑͑͑͑͑͑͑͑͑͑͑͑͑͑͑͑͑͑͑͑͑͑͑͑͑͑̑Ɏʼnč!ѥOǑְֿh ‡‡ˆˆˆÈÉÉÉĉĉĉĊŊŊŊŋŋƋƋƋƋnjnjnjnjnjȍȍȍȍɍɍɍɎɎɎʎʎʎʏʏʏˏˏˏˏˏːː̐̐̐̐̐̑̑͑͑͑͑͑͑͑͑טi xUٚ͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒̑ʏŊ†ČϢJċվֿȔ ˆˆˆ‰ÉÉÉÊĊĉĊĊŋŋŊŋŋƌƌƋƋnjǍǍnjǍȍȍȎȎɎɍɎɏɏɏʏʎʏʏʏʐːːːˏːˑˑ̑̑̑̐̑̒̒͒͒͒͒͒͒͒՗vfי͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒ʐƋ‡ËΠDտտݽˆˆˆ‰‰ÉÉÊÊĊĊĊĊŋŋŋŋŋƌƌƌƌǍǍǍǍǍȍȎȎȎɎɎɏɏɏɏʏʏʏʏʏʐːːːːːˑˑ̑̑̑̑̑̒̒͒͒͒͒͒͒ҕ %jי͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒͒ːƌ‡Ê̝?޾վվƒ,ˆˆ‰‰ÉÉÊÊĊĊĊĊŋŋŋŋŋƌƌƌƌǍǍǍǍǍȍȎȎȎɎɎɏɏɏɏʏʏʏʏʏʐːːːːːˑˑ̑̑̑̑̑̒̒͒͒͒͒͒ѕ -q֘ΒΒΒΒΒΒΒΒΒΒΒΒΒΒΒΒΒΒΒΒΒΒΒΒΒΒΒΒΒΒΒΒΒΒΒΒΒΒΒΒΒȌÇÉ̛<ܻyݼֿϤPˆˆˆÈÈÉÉĉĉĊĊŊŊŊŊŋƋƋƋƋnjnjnjnjȍȍȍȍȍɍɍɎɎɎɎʎʏʏʏˏˏˏˏˏː̐̐̐̐̐̑̑͑͑͑͑͑͒͒ΒΒΒΒєÊ5ԗΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓ̏ɍ˙8ڷsڶ׿ֿںz ˆ‰‰ÈÈÉÊĉĉĊĊŋŋŊŋŌƌƌƋƌǍǍnjnjȍȍȎȎȎɍɎɏɏɏʎʏʐʐʐːːːˏːˑ͓͓̑̑̑̐̑̒̒͒͒͒͑͒ΓΓΓДΓ)A %ӗΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓˎդBزֿХˆˆ‰‰ÉÉÊÊĊĊĊĊŋŋŋŋŌƌƌƌƌǍǍǍǍȍȍȎȎȎɎɏɏɏɏɏʐʐʐʐːːːːːˑ͓̑̑̑̑̑̒̒͒͒͒͒͒ΓΓΓϔҖ/"K )ӖΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓΓ͏ڭTЇˆˆ‰ÉÉÉÊÊĊĊĊĊŋŋŋŋŌƌƌƌnjǍǍǍǍȍȍȎȎɎɎɏɏɏɏʏʐʐʐʐːːːːː̑̑̑̑̑̑̒̒͒͒͒ΓΓΓΓΓϔԗ2#Q -ӖϓϓϓϓϓϓϓϓϓϓϓϓϓϓϓϓϓϓϓϓϓϓϓϓϓϓϓϓϓϓϓϓϓϓϓϓΑҙ+ֿɗ6ˆˆÈÈÉÉÉĉĊĊĊŊŊŊƋƋƋƋƌnjnjnjnjȍȍȍȍɍɍɎɎɎʎʎʏʏʏˏˏːːːː̐̐̐̐̑͑͑͑͑͑͒͒ΒϓϓϓϓϓϓГ՘3%W /Ӗϓϓϓϓϓϓϓϓϓϓϓϓϓϓϓϓϓϓϓϓϓϓϓϓϓϓϓϓϓϓϓϓϓϓϓϓ͎߷hӪ\ ˆˆÈÉÉÉÉĉĊĊĊŊŊŋƋƋƋƌƌnjnjnjǍȍȍȍȍɍɎɎɎɎʎʏʏʏʏˏːːːːː̐̐̐̑̑͑͑͑͑͑͒ΓϓϓϓϓϓϓГ֘4%[ 1ӗϔϔϔϔϔϔϔϔϔϔϔϔϔϔϔϔϔϔϔϔϔϔϔϔϔϔϔϔϔϔϔϔϔϔϔϔ̎ƈֿ ˆ‰‰‰ÉÉÊÊĊĊċċŋŋŋƌƌƌƌƌǍǍǍǍǎȎȎȎɎɎɏɏɏʏʐʐʐʐʐːːˑˑˑ̑̑̑̑̒͒͒͒͒ΔϔϔϔϔϔϔϔД֙5&] 3ӗϔϔϔϔϔϔϔϔϔϔϔϔϔϔϔϔϔϔϔϔϔϔϔϔϔϔϔϔϔϔϔϔϔϔϔϔ͎oհˆ‰‰ÉÉÉÊÊĊĊċċŋŋŋƌƌƌƌƌǍǍǍǍȎȎȎȎɎɎɏɏʏʏʐʐʐʐːːːˑˑˑ̑̑̑̑͒͒͒ϓϔϔϔϔϔϔϔϔД֙5&_ 1ԗДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДϒա8ֿÌ!ˆˆˆÈÉÉÉĉĊĊĊŊŋŋƋƋƋƌƌnjnjnjǍȍȍȍɍɎɎɎɎɎʏʏʏˏˏːːː̐̐̐̑̑̑͑͑͑͑͒ϓДДДДДДДДДєי6&_ 1ԗДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДΐr˚<ˆˆˆÉÉÉÉĉĊĊĊŊŋŋƋƋƌƌƌnjnjnjǍȍȍȍɎɎɎɎɎɏʏʏʏˏːːːː̐̐̐̑̑̑͑͑͑ϔДДДДДДДДДДєי5&[ -ԘЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕΐۮSֿԭ` ‰‰‰ÊÊÊÊĊĊċċŋŋŌƌƌƌƍƍǍǍǍǎȎȎȎȏɏɏɏɏɏʐʐʐːːːˑˑˑ̑̑̒̒̒͒ϕЕЕЕЕЕЕЕЕЕЕЕѕך5&Y +ԘЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕЕΐДݲZ۴Š ˆˆ‰‰‰ÊÊÊĊĊĊċċŋŋŌƌƌƌƍǍǍǍǍǎȎȎȎɏɏɏɏɏʏʐʐːːːːˑˑ̑̑̑̑̒ДЕЕЕЕЕЕЕЕЕЕЕЕіי3%U '֘ѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕєϐГڨEΖ׳ˆˆˆˆÉÉÉĉĊĊĊĊŊŋŋƋƋƌƌnjnjǍǍȍȍȍȎɎɎɎɎɏʏʏʏʏˏːːː̐̐̑̑͑͑ΒДѕѕѕѕѕѕѕѕѕѕѕѕѕҖ֙2$M !֙ ѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕѕВА՝.pč#‡ˆˆˆÉÉÉĊĊĊĊĊŊŋŋƋƋƌƌnjnjǍǍȍȍȍȎɎɎɎɏɏʏʏʏʏːːːː̐̐̑̑͒єѕѕѕѕѕѕѕѕѕѕѕѕѕѕҖԘ /"Eך ііііііііііііііііііііііііііііііііііііііііііііієϑѕڪJ͔ʜ?‰‰‰ÉÊÊÊĊċċċŋŌŌƌƌƍƍǍǍǍǍȎȎȎȎɏɏɏɏɏʐʐʐʐːˑˑˑˑ͓̑ЖіііііііііііііііӗЕ(;sۜ ҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖііііҖГБ՛*fڳԭdˆˆ‰‰ÉÊĊÊĊĊŋċŋŋƌŌƌƌǍƍǍȍǍȍȎɎȎɎɏʏʏɏʏʐːʐː̑̑ˑˑ͒іҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖՙ ƍ1lܝ ҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҕёѓ٥=ń‹ ˆˆˆ‰ÉÉĉĊĊĊĊŋŋŋƋƌƌƌnjǍǍȍȍȍȍɎɎɎɎʏʏʏʏʏːːːː̐̑ΓҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖҖ֙  ' hܝ"Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ єБӘ#ݰWӣֲˆˆˆˆ‰ÉÉĊĊĊĊĊŋŋŋƋƌƌƌnjǍǍȍȍȍȍɎɎɎɎʏʏʏʏʏːːːːΒҖ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ Җ ך! wUޠ"җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ ҖГѓ֠2q߼Ì!‰‰‰‰ÊÊÊĊċċċŋŌŌƌƌƍƍǍǍǎȎȎȎȎȏɏɏɏʐʐʐʐʐˑˑ͓җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ җ ڝ!xdH"җ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ җ җ җ ӗ ҕђҖۨEɌɘ9‰‰É‰ÉÊÊĊĊŋċŋƌŌƌƌnjƍǍǍȍǎȎȎɎȎɏʏɏʏʐʐːʐʐΓҗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ݞ"m S; iޟ"ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗҔђ՚&߳\֨ҩ[ˆˆ‰ÉÉÉĊĊĊĊŋŋŋƋƌƌƌnjǍǍȍȍȎȎȎɎɎʏʏʏʏʐːːΓӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ӗ ߟ"` 5&Qڜ"ԗ ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ ӗ ӗ ӗ!Ӗѓғء5wݽ ˆˆ‰ÉÉÉĊĊĊĊŋŋŋƋƌƌƌnjǍǍȍȍȎȎȎɎɎʏʏʏʏʏΓӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!ӗ!#hJ(=Ә!ՙ!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!ҖѓӗܫJ͓Ҫ‰‰‰ÊÊÊĊċċċŋŌŌƌƌƍƍǍǍǍȎȎȎȎȏɏɏɏʐʐΔӘ!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!Ә!ߠ#X?m 'Č؛"Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ә!Ә!Ә!Ә ҕғ֜)b٭Љ‰ÉÉÊĊĊċŋċŋŋŌƌƌnjƍǍȍǍȎȎȎɎȏɏʏɏɏϔԘ!Ԙ!Ә!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!۝"7'Q۝"Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!ԗғӕڤ:}ǔ2‰‰ÉÉÉĊĊĊŋŋŋŋŋƌƌnjnjǍǍȍȍȎȎɎɎɏɏʏД՘!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!֚!ѕ #7 mޟ#Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!ӖғԘ ޭOϙϤQˆ‰ÉÉÉĊĊĊŊŋŋŋŋƌƌnjnjǍǍȍȍȎȎɎɎɎЕ՘!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!Ԙ!؛" #wV$Ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"Ԙ"Ԙ"ԙ"Ԙ!ӕҔמ.iܵٷv‰‰ÊÉÊÊċĊċŋŌŋŌƌƍƌǍǎǎȍȍȎȎȎЕ!ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ԙ"ܞ$yV>m$ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ԙ"ԙ"ԙ"ԘӔԖۧ?Ƅʜ‰‰‰‰ÊÊÊċŋċŋŋƌŌƌƍƍǍǍǎȎȎȎȏі ՚"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ԙ"$d2%Iڝ#֚"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ԖӔ֚#VӢ‰‰‰ÉÉÊĊĊŋŋŋŋŋƌƌƌǍǍǍǍȎȎɎҖ ֚"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"$eH}+ʑ ؜#ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"՘ ԕӔ١2rď'ˆ‰‰ÉÉÊĊĊŊŋŋŋŋƌƌƌǍǍǍƌɎҖ ֚"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"ՙ"֙"ޟ#>, S}ܟ$ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ԘӔԗܫGː˜A‰‰‰ÉÊÊĊĊċŋŋŋŌƌƌƌǍƍȎҗ"֚#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#ՙ#؛#ϕ"1c%՚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#՚#՚#՚"ԗӕם)aٮԮd‰‰ÉÊÊĊċċŋŌŌƌŌƌǍǍƌȏә"֛#՚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#ܟ$\Bs%֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֙!ԕԖۥ:~ ‰‰ÉÉÊĊĊċŋŋŌŌƌƌƌƌɎԙ"כ#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#%g0"Eڝ$כ#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#՘ԕ֚"߰QҝԮ‰‰ÉÉÊĊĊċŋŋŋŌƌŋɏՙ"כ#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#֚#%aFw Œ ۞%֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚#֚#֚$֙"Ԗԕ٠0n‰‰‰ÉÊÊĊċċŋŌċɏԙ#כ$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$֚$כ$۞$3%G p&֛$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$֛$֙!Ԗ՘ݪEˍƓ0‰‰‰ŠÊÊÊċċċŋċɏ֚#ל$֛$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$۞%Ŏ! !hK}&כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ#֘Օ؝)_ث͢L‰‰‰ÉÊÊÊĊċċĊʏ֛$؜$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$&p 2$Eܞ%؜$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$ך"Ֆ՗ۥ9|Կֳo‰‰‰ÉÊÊÊĊÊʐכ$؜$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$&eIy  ܟ%כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$֙Ֆך"߯PњƔ‰‰‰ÉÊʉʐכ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$כ$؜$۞%.!Ah'ל%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%כ$֘Ֆڡ0k޸ٷ‰‰‰ÊÊʉˑ؜%؜%ל%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%ݠ& T=g'؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%ך"֗יݪCɉŠ ‰‰‰Ê‰̒ٝ%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%'b-җ$۞%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜$י֖؝'[קǕ5‰‰͒ ٝ%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%'H4 [ w'؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؛#֘֗ۤ7wΣQ͒ ٝ&؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%؜%ܟ&˓#%bGu(؜%؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝%؝&؜%ך!֗כ!߮MϖصsΔ!ڞ&؝&؜%؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؝&؜%'n"3ל&۟'ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ%ؚחڠ.g۳ȗϔ!ڞ'ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&(R<c y(ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&؜#טיݨ>ƃٺЕ"ڞ'ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ݠ'Ε$%_Do(ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ؚ חٜ%TҞ Ж"ڞ'ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&(k)Ҙ%ܟ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٜ$ؙטۢ2n߹Ɣ4җ"۞'ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&ٝ&(G3 Wm)ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ٜ#ؙؚ߫Dʊ΢OӘ$ܟ(ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'(Í# F2 U)ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ&ٛ!ؘڞ(Zֳ֥oԙ$ܟ(ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ')|Z "(ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڝ%ؙؚܥ6tő ԙ%ܟ(ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'۟'ܠ'+;qR)ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ٜ#ؙٛ!Ȉղ՚$ܟ(ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ'ڞ')x -ל&ݡ(۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۞&ڛ ٙ۠*^ة֛'ܟ&۞'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟'۟')N9]k*۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۞&ٛٚަ9xݢ.۞%۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟()# 7' E)ܠ(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(ڝ#ٚڜ"KΒڜ!۟'۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(+lN{ |*۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟'ڜ!ٙܠ,a٬{ٚ۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(۟(ߢ)ј&%I5 W*ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(۞&ڛښߧ:z٬۟(۞&ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(ܟ(۟(+\!*ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(۞$ښ۝#Mњk۝#ܞ%ܠ)ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ܠ(ߢ)ٝ( -S<_+ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ(۝!ڛܡ+ަ7ޤ2۝"ڛܟ(ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)+d#+ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܟ'۞%۟&ܠ(ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ޢ*ڟ)$1S<_+ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)ܠ)+c !+ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)ݠ)*؝( +H5 S,ޡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ),\{ ,ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*+З(!4& ?+ߢ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*ݡ*-hLsg,ݡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ݡ*,$ #Ԛ(+ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ޡ*ߢ*,F3 OfKq-ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*ޢ*-r #-ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+,؝*!)6' ?,+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+ޢ+.iLs^.ޢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ޢ+-"  Í%-ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+ߢ+,+." 5=- E-,ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+ߣ+.qSy}[/ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,.#   %.ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,ߣ,-ڟ+&-, 1ݡ+-,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,/ZBa^Ee/,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,/kj/,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.&  &/ߤ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------.ݢ,, 1(+ם+.---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------/S<YJ6M/.-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------0|ZoQs0------------------------------------------------------------------------------------------------------------------------------------------------------------------------------0v f0----------------------------------------------------------------------------------------------------------------------------------------------------------------------------/%  |"0--------------------------------------------------------------------------------------------------------------------------------------------------------------------------/И*## &0......................................................................................................................................................................../۠-0# 5̕)0......................................................................................................................................................................//F3E)'ҙ+0....................................................................................................................................................................//Q;Q-! 1՜+0..................................................................................................................................................................//W?]1$ 7ٟ,0................................................................................................................................................................/0]Dc7( 9ٟ,0............................................................................................................................................................../0bHe2% 5՝,1////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////01_Fc." 1Ӛ,1//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////00W?[)'͖+1////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////0/P;M '2//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////1ٟ-C1? ~$2////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////1Θ+." -i2//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////2%rTo11/00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000/02r  O9Kܡ.20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012yYw+ ))30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003۠.P:K  n310000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003''%dIa/200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012f  0$ -(300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003؟-S=O  a1200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013{#!E2?ȓ*410111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111013ޣ/gKc  d2311111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111124}$'#?. 9)421111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111114՜._FYpRkݢ/411111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111132j"p!231111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111134';+ 5;, 7&33111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111124ʕ+YASQ<K)4311111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111124ӛ-nQi  ]EWő*431111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111124؟.yYs ^EW*44222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222235՜.yYsT>O(4532222222222222222222222222222222222222222222222222222222222222222222222222222222222222222245̖,oQiA0;v#153222222222222222222222222222222222222222222222222222222222222222222222222222222222222353(W@Q  ( #^yΗ-453222222222222222222222222222222222222222222222222222222222222222222222222222222245ۢ0l <, 5  Q<Kz$15422222222222222222222222222222222222222222222222222222222222222222222222222453(eK_"iNc'25432222222222222222222222222222222222222222222222222222222222222222222453ő+~]w1$ -* %nQi'15532222222222222222222222222222222222222222222222222222222222223452*Ł`{:+ 5#_FYu"қ.454322222222222222222222222222222222222222222222222222223454ڡ/㮀&pSk/# -  =- 7uVq~%՜.3554322222222222222222222222222222222222222222233454ݣ0縇(bK7E 8) 3hMcq!*ܢ04565433333333333333333333333333333333455652ʕ,ϣx$tUoD2?:+ 9eK]c}&ǒ,؟0456666554444444444444455566654ۡ1Θ.ѯ'k nQgD2A#  2% +<, AcHYsUue{%}&(Ϙ.ӛ/՜/֝/֞/֞/מ/מ/֞/֞/֝/՝/Ԝ/њ.Ͻ*Ū}&|%m!sU{kO_C1G8) 1                  tdesktop-1.2.17/Telegram/Resources/art/icon256.png000066400000000000000000000511461326245125100216420ustar00rootroot00000000000000PNG  IHDR\rf IDATxwu'}Uݓg6 cwA$F09lK?~eg$+X>?s:9gS&%HZ$e@$rZ8:U=ӳݝݙYNWWW{R|Ç>|Ç>|Ç>|Ç>|Ç |,=x8cT"Rp ſ`I)M0LJc<@A-, ߲[֩-]΍*5Ĉ+a (@-I HRf2XLOK)&PߨԳ3' ZԈ6|bXV6sp @33;ܽ Bk97б[Uma*MB@@syI00n&?K i[ɉi+ԆOk#gϜwH; =/CQ ZC=g%s.*aŁu=JRX ),@X9^.i7q1A MVjzJ˞:7uto9>vzxB H[c\}M ozڶBܞWɉqs84 lSwҋûvC7/ymwR H2!M+qR BZ&LߞWXHS[ol};U4!, ] ?D63PT6uOoLc#5 V o^uk~ιгLP A\19?7JyZmgU@tQ-BB #FLxGO}適W +zl ,L -׼{>[-WN\aBKCE|K"@82W??C4G'j=F  nwwߍl~3g^@j,6t_'ecbhj$ BPVvji_ E =hNAAh{Ww>r >/lF6wh?S_Jk=zJA9^w՚Kn BMR`02'gr'? /k<\ӻa[IQ"Y9Rg HQ|$]_iX 7?ҹhdP4<xP/_|5/˿EDqg|k},%iR*W_zϼʦGwCIl@hs|/|?"یV2H z퉇OdO6`eS xˎ{^%}WDǵ8"l_~3\R@"=^r5&'g}(}$]XKu~2L*Jg<_?5_@ʈԵZGNJm;w/W_2XL`%3 |]?ke>|x@Y^#eO8T1-%V2@5/>\ݳMd4up8ג{^xcZ*X6|ogVȥ`Ko=f[>KT^l_ O C2k=. )! .,zUW}+5 b+Q}֞?|ߛVjڷX8}c_{8V62V@d{Cǿic%AΖXh3 C/rΟ ĕPz>r/sbo^hH4* 2zeW~mB> S[~sjt7Ѡ/_#2qu|Lij_'4hPH)k`țЀ/a1 #\zU|Tw4s`gbWu11؟9vIlK{?cjs:;IgC=o+ Bt#'gNݍz)Z7:/HXUܣݛ=u`?pF7 2еic'sٴ]>Q'|wgy]mLzVb|5fbd}VdS5K iౖ E z<]tm>?>',W|XrH@pÖ^gS?}5ԔH|?ו.>*X&"[vçQ#UV*@N›^d쁴L`$w"G-$;ߖnXгJxC|XvHSGhݹ_F THO}:{rKvݿmʯ[ٙeއ<Q[;;_ޣX-ǖ]PWݼ?Eb<a{Bm>x *@?)]]ԗk>RRP?i `rIv>aͯ|葉qG2ڴLNe ,@D,$)oBK#{\@D;]|"}0 (״ [D^r\gMm]ޏclMXnª{~}|8l`q'DZAK-b{0%IΠy{Kp9`Ѵwz/.tBZ3€GL-! H!ͱU|¦%ӥt50RXL}Z0D֔rUA'kX,CS:=gs:㐆<;ӇT, w޿]Q?9}4(Ș%6q|v6XF{#0486pOO ~ܟ+RG4?yKd X@,?zͅ|}nDruq(:v,nI A'/+Ukj J'F>~Q,X* @F\usHCk00=mAʶ|Vlm" bnP8~ 8k8J9=6>X [vK EeЖ + BY6ܭq}pEg% y)1G khy/< qTY X  ծMۢ.c s".$Z 7ޞn\EWTɉ͗~C4qXwޖإݕx{_E W؁?׽x5.-}xYK"qǦaKk K}''SB[W5وPK<䠹Quk~>,!178 #`6I2cTi"=1hk;x pL@hqchӶ˲'TͶnycpݹ[_aIhDSa Q@)x±4ce!=1p[;FcRて9Pj3w|Y>pn-mA(` `Z'"!Ce`Գ_~cߊlj֞ٶv+AaXlߎ_QpoNĿzuM!)lIr/#LgMNdlaq۠6uuȶ7zaTX-`~onodQs"~Leر:{zucuS /?)D06gcDDvjhnށF~{gv=؆a @0~]2Ԥz%X|F@Oso8.!LsE3?ER7b"+P"$ H=koQ;ѣhZ @޼}2!a%mc4ڵE "~FB"d9rDO% D {& -JEߐx;Q5 ne戕N8FDGͭ,B={}S 4S"]wA$YX h3$H v4b=R6ewܛx;_PsoS~9#q8r7Uܴ!zpYguLQQxl" h},]I:$m& )$Y4{N޴j?ھns+oT8}͹6qD@^ %0Пr.@r`:@9wRrj@G+y#mo \ÛښD`|k Mqg*\vFB )\?1$$x2CnyoZT F~XyG=CJ{7DWT"xWo;D0sk*j27js©`6, (`dҀ1x[cH#+vR .bZ4`#ػ}0ìSFΠQ. 8nCwm˚e0QnV09kZ#T*0pt< Ò|Pr(OYU`d3w i@ش9 }o/2pV}&Bڄ 65p㒎*nJ9#h4g,gժÔ `K;uE% ;c,@r =ݒh q\>zp}n%l2XFY L8#XR)@WkB. 6;:EΟBu2@,& [v!:Dx1 νlW_z K;pWOwvDZ-N>{5 3N %KH*z*ł3T@95(G*CIBA,/ ] .a"8vp3; -> kf XXUM&\:Ll ~BH M14i - 3W (0̭s Q@_z4$,ӀL%DA\ ҹ6wXh;a2fJy^Tejs(65 3 Ed٫,!acG DT_NLf-TPe~PA89K O 4SP#1: %X DghX( v_x1LfˁAnM1afg@9;6w5=qW]""@+ ]q%XfWeH@ց$ =_ @`bv2{.n ^'Zyum!7mh»67utFyV[NH!`̞C uQiW-a3VJskEğ{R`gK@p IQ[i-Kx<4| `WC wtqnk=u3n N1@LJo9 !fOFщLOn}(<(|-"$ T j8JM$iPl⭫ךۺ2}үYuX Qۄ+"h 2hDzg4 N5 8 "lh=8iɂxQ r -1L<SjEc^s4P xZ<6D>ήKj1!kXLarFu9!g 1D8Xa ̘Xzu~݃ȋs` f6&L@[<,t! sH 44qrXȚ #⸻'m!B֔H"˯7`h* %įr&*epd<0;qRl!ωל=KXZ@ TK iןw>lZlP=Rcę]Fq_o3v-gUǁ|@OJ318tF-D%/A ѠbRӹui%9%F@=T8w @p MAV6 @RlU cfBy.d @P镖*g(m­bؔK5ELcd*Kxtw6!F{xHrN>`#;\N4;B[kN;iW!,m: ljYHQmz)?9D`I@3zuS w4ᢎ0"9>330B*[%$baݝMP9[6l&55qb*8/*s}.wOI\<[S H)"-uu<:1@Wugp=Vpۦ8܌FQ`|:`XSi%JzܰDs41(U5(00 &g|\]t={Cȭq@@CP$ěG_ bʪ5i,fq=_8$ ;H 5?%D|K ll 1ی]aTnyiah2|%$:XjiB{+#hV;r:z! PAizPY g+;BeᏳgeL` @mY R!W+U;, 1ܷ ;ǰ:zSkǓY Nyx)s|ִB{kn7;89 =5 ŋ}\²UeDL@ s7~@!\Ɲ=ٰ\|r=]{YDS)7$$֭ڻߙ!5pl2\4™'p9.)(e 2!;N4#xm5 *Ƃ(KPKUܸ!{{qYWM;,V;Gbd* kω bvhGUCI C3w(&y9q şm~Eğ_dD,|V& ). HiEWXRdsBZKHׅJ9q8܌ǰ =/g|@O2c*"~!%86vB{7N`Znl IDAT}  ۧ(l];#0Y&-!͜$P%XfEϭ BJ5x%KpM΍m}p O1|y)pMq4պ!~VΠ]rn{B2IF m"W ƫj{B$ZD?TZ茨Ż67c(B tKn} ɌRg3GSsbp ~lG²WJV& AJ Bj0y&7 "%M"D|̐pyWlnm'tkŘ6Y.gd*Ds!D(3P%ɲj|Ói(Hn/9 ?\]|^ܖa؞a _Z05Hj037;Sk*nϜӂKˆ|):je=)dkѐMq ]WOFai d Sws=^v=S$+*9% ƃ@RS@ᄋ:#xy- l ao!0<[3ܰC><F86dքʙ3CO/"1@bBU={YG`3 KX`*1CN+H-O pGo~6\.xkǓY OkzܐX3t!r <;碩;Y&]C9K糧\p٦OBEH{|z6OHCƍq]rMlIws`D i{ gBJa ֭1xr}s_\ݳ=Trw.q6G1yO C$[֪w#k Aa!~#zF7*!DWK[#u!82J@N=']; ]w $~*]Ⱦg9m1W~e"̵ܪfЅ >c $P޾Ղ_Hc kB>wM[-"}ci >@h1"w\,=ˢ8;91J@eر6hTV.%uu[)TF86Ɣf"y}tNK$ ~:~ RdOpF\<ݗ/*PX=.YHu-.D!14.|c(쮯J68>YsKFV0EQ]򼖇"}*"Hiգ @8>յz`)L$y!6t4)l;Ӭ^^F.L(D_lO(suTRH] ˲5ANxd}S6Q"p`rFTڱ_;∆FzNY`1ѽNu^^:B[Bː\JKHa1W5kp mQ6 CSiLƎҬN8=@R "OHwn}򤟯gn_:xJ>`Cw>m E8B{Xs&tVOƯ|::i!_\#;4g\o #ewFU,Dso}T. xt7 _u,{K!9ZļWJaz*ۣ|#1\nn5qXNQomOk&H6X KH*6Ǡp!;(w$ iᎾCA]V:fN-s;)v9wY;Dr]%׊[i^W¼Ui蚕O+k6׵ 3O80z{¸᢮(CJn7Q7F2MdaU)XJr{Eg c A@(SM(uzH)2)J'$ M2R*"Rֱ|A*zpsO+uF r{f[Қ50<z-Wcv ֬+9# @a)wM/O]6ٲRݭ;sɴg\OZVy`JӘoGƠPA@X?~]q~G7Vli"d #X3.* "{u*p|2SxϞz)gpwWVCʥey4ɡ -hH05ȡB v ƅQۆzZqn[Af%})o&:'hk>w KtF^ 6"*U\.[/x ]qNEʨ ^Y?ǻ8Dr"!$˚ңT`A ˲`/'^9WqKoԂ޶0Tf3+?t35c欟mQ5_R{fr'"KSh(- -{^<{*K@ "99 !+b! "33"e}E߀+P9G@ /'܉)\:[7all Aa0*$0P3F2m1 ]*XpE?# Y7*d* 6"UED?=tF&G`o 2/B4GN K(XV r"+p$%ҖgNLItD\ n^k64c}SHc!غfX N@޵h ӳ?鬝ƹJ,W,f wl}KN_*@T1'`o 2/\0*M($) B 'M#芩j]3n܆뚱&n'dZkY-r\C{ַ W TN8Z ΢2-_D-nSGf!Jt~M2`?`A*L(92H"0EEHQ)0YQ<k|5EJ *XC@YOtSbۺ +W~nE }:C6#H=k`7>RzҜ\A>2] Afӆ9w4W2aV<)-cPc$t N%$%%/AS L4\Ӊ0*-0F88svOਸ& ݕ@%{Y$2L_M a*~ԾݤhjeC!锉#^g P8C2qtprQ;+TFHc0;hW0W}9ɡa\IM` J^y),7SXH>8s|onXPᝡ aRTtwcu=!߸d,Kw۽Kf\R?23c˃˃sF3HD4pz,cl: exk({".FР3l`g+sAs~(E6r0‰Y+a&㕃qx`|Ґ-- MAe@F"",<3`ʉ_m}/'L3{BLj^1=CPH[oxZJaZC`ND(vDVYcz F81ABe#8߳)Cu*gH 8Mkbˣ2 >cMXvo e:%/ O}02x4 #AR g+俭L :=Eu! x.u  c?'TjBc ~A@mpfCqT`Ҟeev\%7pjҪ_|Yt"j1œG݁eA$ #is%Xss&7:xp1FfbhtojhY%5oXA,G&3̌.1u%?1®RAH{sjnU,>/_)>4xPڄY2ƿye3hoyj1@XCG} 5PvVAJphRL  @Xaxu`4Rp:C R[`*kI{CQ/{Z3K!&&}?]T+D%4/V& !eXm Dnb&I_}f0 }``>$.߰ ] : #pɂ%͓^Ώ?zg C{j vE =?yZL ׿YHWNi>8٫=0o`v!?80XP]ML F@ʰL! (!~J%~=* @0EjγOb+ *g?H.UgX 3`J='N)MQ2/B:~]eJ퓼*a0Ed6 Y)SG) d6gҲ~1gM Fy{$"8NOC ( $XWUH;_5L!Tj i/1q]/f"BX`rX vؔڳj{,?W@ !\t,,KTl) 3>iBUՂc%όh?:05al[l wS2  ^O P$7ףf~W S?zv@Ud6 *}ߨYgPÞ 3F;N da!px<y':TEE %BJoz},_U$%2CIM,$~A{ؑU݅g)|@&3GH .A΀ጅCiQ~VA@ 2J8eXJ EU>ûpl|fYUY f{?E%h|c餖飏&ffc9XtiCup#^10%PJx ?{|_=0no_4lRB0H$NL|@$5XVPÁB/]u% p[Mg)M5zb4p-Q9a.!!cXiCl F4Lg1|({h8\ VKI\i{3+-<Y;6!L*m헎r4 N J=ӣ`UϹT @`04x%kR0"$u ;"X) n`&{2$wy|+T+G1;GD&0nr|'qoXL k 0,'{_Z@=y*TWL~=9 /*4 5aL @3 i<r 1VM>tl(۶^95kݧVմrFLj82aN,+6ہ?1 o H'/> RH(HIݚM{RF:׮~5K=?wVnh0Ĥ.ᅢ?}r4S VO3&&MZď;æ%K чRa$€^FXK+ Ǧ2'ql* UQ e~N\u\mZ`: 'l/Át8zҹk-K725+>{b y,uget4|[BܰAcrVNՏ綛fBC 4.˭nWSS)EO KG8 b +x$^:>;]48'§:cawy? !`b([n p>hK7q8gco/!;o @UU 'xb 2@ϪڣA!*XZǟ?{AXuogVr?ma͌˝Ŷ ڌLgqo9sHˀw<ބLğCQxoOɩ t I843ų&Lk? Ǡo?}ğEy\9h߁"35J2aeӘMUu߻|.7fڻ#nI- mb?@ @NӯOlE*Ծ%KHWC$\:x*"yxS_%3 'yq?,'󜅏!}~=V OC/D2@ Lff&ezT/GY0 e$$r]>(^[QHDc~?&DTkٳo8>8#HQe$gb Yy51r?Ч6`KW]PN}H;)g1 J;h h?11~\ {?jV5?;!qmH¬O(}~[%Gݗ/\u&~WE?lGScs0ι~t? R8_w55ȫ\<@M=KAR- $u=7y_@?/WRu~@Q_{#$DaϚ pD}^:7"' H! s@%LT\;+#bi ]jPoPɉԗ?25u޿V7 !,{z-wR8VˆV?<)~28oh@g :i U9>n1A\ɹs6QZ̿,OXQdz~3G0i믶]QRMgxל?͠qIe7 ?ܣnh }@Bv FRg4s#Se.uΗ) g+ib_h> `g'& =0!~qS3| ;AD$0OP 8 nUP柁+ #@3(/e#1f6?`~ÉN42 6'#ᛯ;@>=K%&7%~5z&_; ~^ht$ș!sme]ݫp XߕGYb`bzd$Ч>nx1|440+`MM<"5wF>*Ƭ$P(q_Tuj M}|wnQwΜsjm0IL Zi(jRTjoUUDUm!Pꀹppv\|ڻ2Ӈ/ƀ^~iww~Bl~-~x M@sO qfVfBn^>=ٷ-9vK8|y\(&Bt^k[Z-Prpn0/iܐcobOQ(>|p@ uym% 5(98'@ cCo;g4$tKPRFnǟHYhhZd7saxX7!}̾=oy.G2E9,V|WO7U,8(%~ YaL8{7sPDQ&Η_WNUWh/쁌@xFv3{Zfv]H1%D)] dRrC"q˟.,/BpǓ!o  ɞCD0}H;.x]/^j*|D`87샿~{8WuȟK)FJN qc,GQ"Psv޿vI(/Y!.  @U.ӟ_'!t0,0+e_k?%x݊bUP1(7߶ M뤓(P÷}={ڽPgP(d A6Zjm} s~̪žIP 0)ӏ9/?eJB0 D(W5~h뼼z$#_Sw2n뾿;_^8$TOI,d bPFa˚47}'dc@(ӭnu% 2F FbzEfWPVY' 5X Wwlu}mo*͝~z5(dA Y#`_3F˯D]%\t30Uwg/|3DYyU.sWYmjEOX!0!cG H${/C%."f `h#9(0Rm;-?xԂHRQM!><{Ϋޥ#.?*'z^0ԧq#B5f/uk/4^Vݰ Uƕ]gώ[\8)BeP= 2'GXPTDDX(b-[Y Pp: :)qvq)91 o J6TO ӇBb,^7/|k_qKTjd`257txmgn5T/}=/ؼ@0?p@' #K@yCFv7ʍ_gU+q̠XkpC\r$7Ί rjށg@A0言C BPf2V5YWnhf7ϱ5K(\;Ďq%tTa@N ˁq3w~6-z]ązc&3 ,1cK5~:Vn[[\LKX`c`jϊ-%@ouU"=rbF$׆;{z3 `@Ԧ`Aڜč[T]*W5kXͪUbYKհXyB0LKR(2LYz&*H~8W=V\J'ɱq9vw@ܹ!@oyM$doхZm׽<~ tt AGW±+de5UԲDm5+ZHbVVQ+gF(PĄa`"9p(6 )8ұpI95:蘜'nݓScͤL 'A'Fz`N ^`X'B0lц0#̰"-2+7C<M0fAe@x6\;-'ǧNCii=ŬCज़ xmp.did:1ACBF4C4D24FE4119247E59A0A0429A0 adobe:docid:photoshop:e8fe49e2-0638-117a-8f65-8cab695dcc76 xmp.iid:085ff37e-a1ac-4208-a119-0498a9c35b14 xmp.iid:1BCBF4C4D24FE4119247E59A0A0429A0 xmp.did:1ACBF4C4D24FE4119247E59A0A0429A0 saved xmp.iid:013ff985-91ab-474d-807d-b60bda015c0b 2016-12-19T19:15:42+03:00 Adobe Photoshop CC 2017 (Macintosh) / saved xmp.iid:085ff37e-a1ac-4208-a119-0498a9c35b14 2016-12-19T19:18:32+03:00 Adobe Photoshop CC 2017 (Macintosh) / Adobe Photoshop CC 2017 (Macintosh) 2016-12-19T18:26:51+03:00 2016-12-19T19:18:32+03:00 2016-12-19T19:18:32+03:00 image/png 3 1 1440000/10000 1440000/10000 2 65535 512 512 }K cHRMz%u0`:o_FIDATx}w$WuyUU ;3gwViPN(D913p"c36I`!!BB(ksyrT]鞮sװU{߽2@ ,.p:@ @@  @ $@ @@  @ $@ @@  @ $@ @@  @ $@ aQBeё%fPjn/=)dUUދy=U$Œ 3"xV'[ꅸb\cL`-?V(@)$\@I(R:u:S*   aёdͿ!Xx8f$ι]od΄h筌)緄M0#B(<#`,0̻5q0S_)ۀrPʄ2Qv.ǒ$с1䨒rDrC[weA)SNNZd"* |/Ns]T@Xd_U!p,0]\pR}j&Ls\Zn%eࢋkFH, sM7H\ThS*1 q0|b_RId&i;(;\OfS'x8vBIW'Ar,5I0 &a0d?AC10K{MWx婧3Y{vw􋺙Z4[kjz( c AOsW0g|B80qR#7;vs8368p;ܘM'‚D҅ Œ>O(.01F֞4~\|˙Zlm9EK'8S㑼PnC87́=קM;{no}f_d HH ~Y硈!bm~ֲ o>Xsk=CZi>ٻtDvQ``e#'ٔMlI{I$|'|CѰdyGE7Z}Y%],Tk[i,ӏN0GSnjd[zƧһx:spS/Ɠt|CA7 3Ɍڙ޷摝 Ĥs$MH_D<ҵ3.ۮ4Zo[qy|׏ `|B8cC#nrM>9'd S-CL Pz@I-?[οbkSŭvt7҉Cν-@ڣ}Wm~e.ct1! !}YhiKon~ z3BZO/t0# '94bض?6=LgJ @/GA&=g,ܶo5ry7m](Bʈp <4ikcOG3owf:]B Ȃ$H]otjW_FOpڱF9F6\F'}_rvK+D!uH s__^x ^s޾L:D\xf e?gܟ:ޠ%N" (Os='ν-'+0,>a3Mglo;o?{:9-IY a>WU/oy7v\C̙Pҡ=}HS4 ɭ#ѧ*-3GY h|o! _/kkg(FR>^ceͣ~30;ԏ Œ)V } 5:Wu_;o|kkκkzX(Ok=һ}9ЏV:h{@@_揭Λ^nѱeN4+` |GgCLEO6 [/UmƗh|ea;Bß\7G<8gѻ~iok ,Gⱎk_~M׆75rʹg\ {` >?Lzr?iHOkhUuEߨ\I;Gi~0|_7:>}?շ3uKMB@Ј(WcՒ[^7FP2NߟF_` $H1E$ z^yʕ2gBXL}/?>Ha#~fzH59fi0gWG2'G}?{9>2$s|OAʶnt+3.y-(;GG@+Cbp#g{YGBA$ϼe/?o?cq3i!j(r wL1@a?B{:eɵ/}Ļ]3M>B]nٿ? ?rףpo P}  nCZ׭o~ݗH3CzX0yv_~䝟\zSm ! @kba݅^q+@+Apӣ~?8O~M @$HD婝+_j%gB["sY:JB_BGď>C׵[ $HƉ7dumx uu_"siG 49]_:y?ٿ[ ¤_oθUЇZ.g2J|3HH޻Ͻw'ʱ\( ~/բfY.vHm~.L-  EsY=sg] ~ld6uӉ;>=i_T@@ӓ]nDu֏H[i:B¢DCjc)@@G.vegv??;㢗J3 %& <43G?޿_@$H4%-_r韼c"X+s:Jb] |KGw[hz3)@@s O=+_я^|[e.C{[(1='B| pp:oy+_m]g!Ph <~cObg[dB@o]_Ai!'AD=yx@Ş @/~Y|2}歮}!b?G윃A @_]yp{ß~ip!Ƈ>ٿ)o%L[$H%/qݫ> siF~a6с-Gabm QD瞵=(O l 0G~_S#<HX7yo{gEe%s 8FGGv vtACH) i7sTO WD=~xwr#( @ ë~G$'5F2=vGx_O.:& iKa9Ϲu+%ZG$B7V}#_(wP] FS oz7D8FZI @ CD}o6wV@Bav``\~w|\v B=@D0ϾOWHfL~<7w$"Io?wm8IPq B@&pw}泉s|IuD sCc? &4!# [=K\K~1!s푯KPOŁ$Ӡ?quo|SΞ(#zW"ol3IP&tO:m_FdstBj "/i?q(@P/Wxڨҟ@ 42x$쾍w;es$Hc͏pVco]wFYfXI@h|=O͙}k ?~'u۟K Hj#M$B}Oߘnx&H`Q?c\5w$yFNjdOml` x_3?.Uwctf0p#7r-w3Darl/8Қ?h_?@ wڮrLx$jEFX[/DO "t͟^z,5 )wgL3'گj@(p!(<~ozur %hȟ y+?@ T 8;fW7?Kh 3_rëGO Dź7vs3$H4L/m_ͯ{' k=m?/>Р "Pw䟏57[;o{˿Y'3 u"=箄W玸pN-{_zk2 CT?@q,ȟ@ f ܽωhK`ͥL ?~';~wSTPmJtta.D@6׼]w&4^tYE?2v%}rittadh%r3c'~CI)0 @)#Vұ_HWD0Pp3ItlxGވ Q.H$-~#F>Yckd.KG@ M(H3-轟jN$HԄ5q.W{+d&E (@o PMz|%vfNh8_`|?}n6ɨן@ j a8+_jvsSR qwk dhۗ_ݟ&(FEn瞷y:1H'4ӈ~ɟz~qw $@sNBu/~G⫕c!Mb7P@ܨ"O Mwl@ уxKƞaͶ4Mjj/<)Uo%.@O Do]b abpu(u/!':vcw~"bb #<kBB]53z+o YAB Q E=vէDNP+87+?EP x?Bz˿]dYD, eWͧbg^~&.@c^_pdK  "_ՊVnˆi]YZh)`3G) o4\XG[G QD[OHȟ@ Bu| o|@LsW_d7Ӿ?@ 40Mm+>iG'>׽?d.!?n(M _g4Vm}䟷E{}ǕcB@2vW#W@]$ 4u޾tr,bD@7^po,fPhkw]H\r\%UvV,VP.ZE/2e'&&\u`|gk`>יfhO'z"sBYϽe7>4U0wWK_򗯏#  q_+0- ȟEOh}LK@ D$_C) X ef/C_"k~z ÄK,&U3/4Ӡ?@ ,2(JǴe((Db(Jמs?uEK?@ ,VH_}@Ywf,ԿW_D;?@ ,r گ{^s Ab^|K˥NftbtֲӍH_hw{ۗu.}|T9N@"rvɟM͑A ܮ;e=* Ap,: 9x lNl"6Hx@ Gđ%9g3З6D@=-~57wڮyٟDϾEO !6R/6imfdf{;km:^)@295mA`3f ܮ=ll@ TNXt=s9E6l6ES=~\ј_@ k]>́3q gl^mY@ U5hO=0Tm$zxY_ !47­K_WgB4UG@3 4z8@Q@ e?ڗ\k,@SM lP]/{̙taYHw{h fy(^򼷿OJ[(j#,Z&Bwc5/0ͼ/@CL3d M^y{k3@ ʶYoZD!0>OD[Z:n{_("@ ̍swyy_~% i /6Vv1a.!mKn}˟>r&|ϼѷ?ږv.( 0Yǂ|y7ᥘ@,ދo|5g+Ǧ3@ s/l7Z[RLԟ5l-@# ZkWW ~('|el]kju/j,@ ׼H_RO `HJ Rj7Lk]]\ԐYFKGgy;FҶH! Ɓ#q$\R /\ҵYƠpZ{#3TO RTBgX`Ú(ZmF5C֕8vs£3,Fr."`t Dk lo妆F @CI4+y=39#)K\lF4$?o: !67 V7?|rC9|o(ڗDU X0H'w y)%P3/h  o~Wq): B]G9W4\2zlY-aHrdBWqg<'u։^$s I0Ǎp׿E:6D@ cO/bCO׭;lW_0]ׯ!w8.t}B_z0|!H@翛ׄמ}eL$A \s pzem g- \93U…xyKϏAυ밶^_9 θoz훠@ ,4)~`yTU+'˗GКO^.omɴn^w 7\1/~7I=95(eΏvi?@~AtY z~Uk:~!rE 58 8 ڮ{k}w ȏ`Du|C!P@r?9imnZ-kb8#㟿pZm,lD+9sREhu oz-E5\2zx0"gF*QC*ePCBD^g7c׻hPh]XRK [ R ʕQlXcXۢqQ0mQ#A .\+ŷ\__,-DD{WZ&Y̥a]cذ6t9\WO/FLT{֔' ޶tm˕/zO?,@]Ի`i7蝫ϔft0Wq4x4*pn[+#XѠGY:ZڗJCʢbޯ@2i4=p#@qꗼa6K*)VDp8b],IH #$"Hkd'Fhi/鲱~[_%@OF_soEg@1\s4j(n]ǹa,?mtpx4 MXQ v( 0r^6D*n,xP׼̈D*1Wjذ֫bE:NWX ,4X$"$AΦDږxJD@턧E_<}[$^1=)\,PmH%aVqkO׬bY[rlh\th8 W͌qfjdѶ}yn8=[9 rgמsr@ ,HG4s;øeM 76`޸]G6mM,LG"!$na` pLD: P3q!='y )(xpW<$u |o ʘc'E78gKv?F` %Ol+5ݔz2v֞'sTO ,uT*BH0_b lXÙK{Nˁ3 c8j sd:yP/|Я#(iQ osr"}ԗE5\*%pZdŻ.pb4Q'I&m xq<$S8B ;4s~`뢎Mֵ,6(;Gg싥ߌ'ap\,[&pcw k:cQBɁ#d &&HS< ;:Hk;"0oJVG[7l߸%mz7~Mkmz@hh_t7yMqo^gaq*C#Yd]"jZW uFA"`ބC[ngl+b@7J-+BsFB3aCO[+=yJق%2Ojz'〔0Gni sݵaXwIdegf>4&IL?;V~ !N-#ذ&pJkLޫ L[H#@jBpPtVHP"?2[yy?"&sEkJ?ȐkaMcmM/m{%`?39pD\k+7~/);D7e B1 ŒdYA 4Ԃ*}[bܖ\*eQ (P?#pbİi309YB7)=ø\FW#Ozh˖N:iYEB]GU*g?,㖵qܲ&3Ca "H06PD|I_MNܟKJAӯ5, Eιև6[ku~LϻRkU F<+b:]í= \<֐RMn˻88A֖R+)(O+R쀹Nxr4d,vB woĞ9Z1I`RiJ80"N'?v: ( = 0u/]sQx{J`uϸj2!8CO&p߳8,Wt\Jo2YhBfAo;eaI̅іhˮyj#@%ob]}٩$:6 RhiE[+X=(Zb@vphĄ|bH\DSlJF,Nফ~oX]5L\Y4@Xhߑ 9_6:g/c8> 9 ?@)PKFALXZ@@xݹ=ڒA:t5x8nVtI}u#D,~ْߝ\ `؀y5]PRbG/ȧ?7r%hKTf{@up}W\ߖWymqVGag++sНq8nurr(&8i81P2"XH6Zc#Ñ,Frʏe3$V q`D@P `[ݥC|)氀u )"@ϼ!h/0cWzm֯fu ,>L]pΦ)5QQÁ,7R'6:u`c pD@ .]h1,`B 3E] 3#}w CxzXBH0Bfa#Lʼn4F29/S.勵jIQnE,LG!y?R D/ǡ\vD@ tv{9cy [L$::%+/ҙA $~GJ:^= \<_Cx8cJw4#8,Iyg Xݙ@gKɟ1oLȷu,AIvz z4$*2ƌ] ^Ll, P>ZKRIBh?_9"zaM kZtpƐs$Rg?LHV٨cX4%PF@v8zy`,IT3%3)hn. `=ڋ؝N(YG -lEF#BU nu FLȹ 9G'/Z 'G3uUYAXە@k~3qlTQwT,e{Rዀt e:ږ'Z::Y,tPk/TEBAnu \*e1 ,wqGNja4mpig"t z]?hHȀM]?z!Y I B(+zUg=ra,6B J!bhY@$=ƀCdl^1GY;V3d"2%:;W_"=Ek Pa?>dz]qMwk#hQA_7Iz>Yˁ[;JTHDt,M ,+ eaKP",`ep*(ϓ[ڥ 肏;}1āLdTќ?O~'0$ !hKPt~,@B1.‘xHHʓqV7u D5&): M{sfs(ZCy[V"``Hem iZ?n(*pIhc-("j ɧ&dR=mlJ<$2_1Vεsq9L7Zfih-gq~e]!ϡu \:Uqgۛ$~p8 cif*5)UCwG3QDt_r*>wٔY2,0 "JGHյsIԸ7uze(!4 [ *v7M FDz% t/{o>,8u[vƱ$Qlۈǝ3``S eXiWdfbǨ 98Htъb%%x|IGI=qԸ`!ZX 2"4;M* X^sWD),33Xɑ f־b}t&V־0j88b ^4't3FJ)w']^mKxJ-T Jk/hltKGpkO7ME`]*2S顸s)4@>N(N"_;RaIX՞-իXӡ_G=3 }FӰ|k_K+a=]oe8  -!)ׁcBa0DәΥQ:Z K1OdD҇߳*5tq8nYim!T?5>e3)Ek 8Hf00fB* ~(51Mp m=Ie JUi7}@_M1Pck hb8_ xrjRP"@e 3k3pӚ6M0bz?й ,Їaê:ᆷ  t0&n?~{u}E`G" pm:&fti]kZzь[L#uTE+&aóp4ʞn]ׂ+VDS= }N1i>K*Mptw 5opA,G8졦O7# \+ h"W "z]mݵ M ($VQaįH+9.^ņ ܓ@O3oK(U*,F29'ğ'CXG"l,*3bt Y %}~)rV0\"@)@hGPN>h z%_ Iwg7cCO/"sX“s鼪EtCʴqb(igú5KހC0L#mu 79 c_ݯl2RsP~Uϧ'UbM` Ѵx8%5r~* T)~[*8RaID QܶW-BEuz2q?AO] ti{3QFSɿȟ j#p_J7H59~tF V6M +֥t1d|?1Ɔu-im`gQ?C2kP))2• -k&`hɟ3p`%yVfaAI=+A⓯A_ 8J<*5u}9 z%h3Mj(P-1^.&_:aqlXׂ}[^UKjێDH,}k_)o/t 4@O2/WxDW?/Z*|?W3FqV@ MPl]q3d-o{XU ܺvDZ*]d˻D s89A֚O9Hojkߠؘ=>9EzZ[cW* !օ:^\ChzLy+zRES% q& pKO nI`0,., qvqr8TG "X$rC:yW(]wWV<<Й$XXQ4I|26]4Ϲqtqۺ.]E{Dvl)cA8Ld }zR PPX$mQ(("7F֑ Qɫ9'$䟇tƭ P <1=̽Oo??x =.{P~*._ǭ;Չ[^lytDk_6pcϬU1rI]mEcb@vw0;18 lɿ5W~uJO%P҅t6Y8 ǖbj+`mLc4/!)Cim଎&tV,{Jd}Y؎t3`uGK!H?CvgTo*Cj_]Vc%ĵ>~ŗUVe" ʿt: B荥,zjP-0#neޜğO+c:Zí=-lEa n~>:4d`Hgm 78tGxƥ?iD2aA/AtE L:RU6nT/@8>)7j 3x8Q&%> UqlX kXOˑ-o*e00Լ _oKX_t־A idl OMejfҏab+i r2> T QCÚºX=˜#w03 f; M_RpOzJVplg \C(0 Tq(ni= k5sgR>Nd`ZG7)63C#;ݟy`l5-,OUPg"իU3 I3 Ǖz)m OE NbهoZp(Z oz}t3# -0_8 !kN82jNTUWGFXnS /&꒏ cLWM4 @Ah|3C+}]׭I`ULMk$d`;r^ }ʑD;d|12H9cyw|x#j^ZGW4 qMKHL4-Yr@ Mlbm:g8ͳГD4mO`,kgj)Z"X$Z@^`DBV`}IAM Tʐ 6Li_Uq!x$%+"؞ !@Ε1~ ݺK_2ΈWH但]%7bo, ו5#~Z}Yg E־^+g xu o=Hf>D%rJawJ!3lBgDV~0Ч8.\ŭZqZO̳CF IZLЧf䯼hqE{ K[#PJ} R]i0I# la13{(Yg"@h$b~,6m? J'xhY-~Az€ЀQo7Xr }J?cʎ:־doᬍC#&4cevԞ$䯦!3}a`: d%]Y%?v<P5s=Y00&#ED-yi1DG}Cl }&C*olb!rB,8 f̹`L;t 7P_)1J/p&@14=׆( "B/} *8RaYL kxxy `mEHéTZ_Aݝq$"lbΰk  yUTd|oT% @UNWC*o+|Ox)S"[5qTS"@pR0/W*#%!<6<@{+ 's g{> '&+YId3R :( w0~5/GAɿ9JJ-vb `"e883V)_Khõqt5Xf" 2GШ]r{ ]orW>6]|J<cT~UYcPo+U2k*?uJ._CEڠ c([Jz: ma +\V)opf7-2JxXǚ8Y!zS6d<2d6݄U9f A&4GlSDG%v䧧~%/D@;*ug#8^yV;^N튀3+& 0c:89A2k-Hԟ'֨84A־ssi$mist{WJ~LpWi-}ϊY?&&k\PXP7K}P#W.Ǖi1n3XcY8Z0ϓx:}^0L#| W+mw&GP`6o /[ߎ^ K2C*cH> BgK+Gi/s @KF Z24B)ﯤSȵq2s$wZUk4w f }J^J-)|@2kd9?zaәF*}U]J ĭDgǖaJ)0oj,ef[*xߥLȿMYI#k57yЊw^!p"mh`%81xڿ$iVzLPjL2?ݶG@n-Bn.O0^(Q`xAQ`F\+Wqj[9ȿ٢~7I0)E+&k4R 01n3`fH+9W"V~.½wnɿLi_J*Ͽy]d3; \24 ־,,g\Vu}sy>a@i["'P C_eo~v;Ʀ1Kמ0(H@2nrU/R!8# '?_Â#lAHcY e3=kUqNsn·-{ =Y+ 0UKЫԣ*$*r} ZT" {;vV\u2t1H7Eh+"ϓ twd[+Ҷ]iZ=Ri Y.JfAH݆*TUcURY*[-M!Op钟MǠqyoO4;E\XCR=k߈A k?Tȿ^ԔQ(IjsZQ[%q"mce€CJ~cY e**XHǚN]ŸhI|rͅ*GzyLIWOolVTx$4UL#,xk|/oJ9TP}9xx,i?;A&-pk_)oXN־ #î42D,, RǨ !V>1}9B>V OUDU)5 PJ$c^#oC"~d #-bQpl0}Id,[?pB[45 h/H.v Ko iYȿֿӿBLД&T8{mn 5m~].\Ƌ¨:ce~o ,:7R!GHOm}v%v\d:6izr5l1(+c;'ő\;E3@1T LIyb@R)5wqܱ$z#--⥈-W#-| ézGmZ=sg훈X.xQOj3hG_ %˩ġ]8g> U3\b(Y^#jF >>.f:ix>>BxnZ)2` v'qiu=m8gi -! Tŕ(MEHyC/ )Zc!ApO P_c{{;`_NW2a,سKFEo6TnJ !@CK\9֘w3PLR]讄!<ٟƖ)|Xu=뻢HT]v7b3?[FThOڗ.8v \MK|^jI}ςjl=Rq? `Pe&8찒7*V 0.̀IPRaKo ϜH]1\ӆk׵ↀ*8]ޑ,RY[&k%:[X$` ;R8 1s&"IT\7Wq*榆6ވ=!D q%:6ǏOù⸮g YۊuK"+auDzqUC8+Q,mxD !8KM\4?pSʵIڐ`ٴ"1ҥՠHŀPP\S\34r$pxA{D+~];Zӊmat#/YdL"`:Z^&q4C*`G+RJs <*8T~\ tWJD5(|\ g݅Ȅ؞(U鹵t{UU@Њ& {@ Y LjNN\v6(>tg@A3L J"%['BbMiūZY{ڼH2 ,ocG!M`uWFߠ`HlKA`q `0-S2PY]䯪|\G_s9&ps`-D9sMZZ0J"1d|wnŚְgEӎ -G͹`Xg4d4RVw4HȿQ!З(MΜ+,ɿ|_%Τog&xMan&iڇTZ &(79OKA A@ A(:pxĞq^EpV\׳.-͙1g R) e10f7iڗЀ@pHc̒LN俠{`edz4?@ηE` @VR̙})&%tb@ap;R +m8{i qb1oIr7ElC/ZVu9 M3K!cKDCdD俘?m, dJq\& ``R)08Tr2y8z=Kӷ"v 1Ra0P2 WrHD+ۣ"Lyn%S2 boxLU_"i4Ϊ~\w0&bS_G4)g 舡5:ן4!mySݪ8II~ӓU/O- Iּ@Mx Z>ȓ qEWbJ!5JMlxR)+3C0` m$ dJl#Lɟcg`5'KOy]j p:[6-Ѳ7Wqa3  2ircgc9 6QPvf}3l;/?Acbwl('*P(QKqZ՝AR– xaCCwWQCKM Vȿz%Ma%LAB'䧎 {~N5jWXo<ꦇx"Lq]C\nlx. KۢhP3bk_ (VE?7삑44 hF#xCOW͢L\62c ʓ` 6ykoqp2á:/D#x{>[*1jF,3oFInjxοPqPʱ-{ffe2Ř`w־+;bhO!%N`ޗDgV(5dɿ:JWCeLж=>r ѱCf@ Ysq"maa: úJ3\C"bu)OŰ7 ӑ T Aj"A͐U'}|_=G B䕍%sY@ЂIqguM}i\<`و.bI w?2^ !c;ޗ8))Ɗ*(΂^,/+55 ޥklR\O_K's0kSUrۗ)ERBu'S~14il:gu- D\fT!0{ ]0=[lz++ U(Ub[6gY~ک|OʗToO`~bN4M`,#nrh9fr#IGs3=LJq_/WmtHat<GVrltui5g\[#};领sj-d‘ʱ֞'w0AjIϸ /Ǖ8;wHJ[ޛB)T`/X]; ԷP]#_p>gh @a7C;&cNS56i&028;8^t` 0mSޞ9 @^*xX^27}AmɿiL~njx.Ebu¶qG@2=nDMf80fh2S"ڝK+]02qfwºCCr? ~B~ @͒|sy"Rj~#BE6 6T=3sjv. {Me40 rmJj匁sC}cx|1 &iK`1A 8!* `S?QXD%MAojٹ,7m0,P= kƱ/ӭ6vwkc4mDZ0m , c[o3SH`;˥|Vcg*̫>er,P P-Bαer "̌' M}iXIP)וKc}N  3HzDziĪgկ,rlRv` ԎT5bؼ 40 E 9@Ɔ@@CJm쾓0mBq1AC0K!i?@] ?/0:y#&kN % e[7o@_W C9mςuRTUc BpH?^#"`[_ 3C9Ǫ()9qiuJ]SM2=B LW(}iܰut̶48me;NY\@#3 mڛ&xXKm-:Ͽ,tާU=_ UfsBA&m03\,[;ٽ'alh2a)36 fi97Rh}h>;(P=` Z;{Qa"sĎ,t>SWͺ`8>c;o$Q}`C;13/ٞ0)drhVPE!&<'ڊ&f¦TXxn$&8Ҧ'wC~pm)d%R5re v֐BcܾC}7."?f~l?+!ZQ3; e QAD瘗muipRyd#,YۅXؘl` PE#6? ٖy4 nNT9eI|}5k)#'!e p %rQ|i:(YRɴY󴧮< 6U_ȿ 4RPfڶo|g(v=h0a`J`cozޒ i#ydJі@ua@{IMf"Ɲ翰/mܼݤJ0kcOpDa lO{,sL<>:w@ڴK )lR`JMM&jD7̉b΍`j {wp*G^Tq4e }m!o^| [#^oDـ`:.&'y")WuǙH־gD |ͧՓM\(4=mU iܗ܏`pTHMl3{N`~H&fe3lKDAt1| :XLJJvTi_;7S$pGZ;[o`c&Yf~Jy/ƾ 2;XslO:d6manB0Π7mNkȟQ { Փ(8ch  @IU88ÑttwuSi< $!8brsm)$-՚(JCicUDU @!}[`|Je:c?yΥM1afƖ6qfG;w׳+e]}'02q%ЄZ 5[Wb$\U* UjyWSx59<{xNIVUȿ1js~M6߫ld {Ӱ;M\Wc OǑ5sl Rޗ%p4bCv=L@:޾9ff !kDƚ.~W,OeºX&i:9ιJ[LAJN8ٟQ5<%;p(mtn"\`aȒڟ"L0t`:wwl?޻ NsN&s? *'5c_?":~񇍨 n?&FUmV6fwy#Ba8Nc:gg?|"fC֛hNc%J8걀~ ! T[>?kT 9) ݾeT Hq Ѕ,ά qո p qMZDZc C0H(.392 |0-98&s e9*`bL~CYxoPsS'yhuwU\no7ιP@ 5si\ Yg瓣'4|I f,8hZ07GG@r$6jb -'SЄT-p)i) ϛ5I&\iPh#;o̺S| 0kpN$Ola)`S_zV[~8Zў8>ol< ]p'%  hBAOÈ40?eK.͠12E #= ɳ\뷶=!:&61j:u(x- qƠq{[za.ɑߗ,9! ?xy tƝVk {S5$??T`Vuˌ @dS Q@&-ϱ+t3_yfJfk#]; z)5EVK.p9GH6mϷ`(c!jP-HY<>st.{S\{e#px,ӗDaUy͏C#kg)  /GތH+3+A "Uj!ҏ8Boc|観-,1 e,' |AU|cA8[MCA~Jl|4:Qp?9Q2=lwA(?cH9 {S&Ly?c~g? os2uP(Ί].B ql1O3s|S^D8̍˫廬ỳ}pc7WS7=,Sα]Nc p})dmx}SJqռl I3خ޽ ϞO(*+&iӮr k?/<g?B fAA [N!mKTw6ȿ2?Rf1u_n$T3 @Y[u!w$) ZnHPIŷ#GF EޥOG &SU%,؍ݵ FM m2-)0|)_ lƜN_ѿۻ鷏P"Je{If`a̖ܗ΃MnSrL‚7mǦc4?6A3Vz['<!jh3ot$EU77%4[2C~Ⱥ?c WO枹nF8FN JB8 ggaHaC2/ZqdK1Lbbi'T &02$.DM A4LNuLm ? @Q;PpXXǕsZ|ԛ6G_~ @='p%H<kd= |u ^^?LEL!'E\ m-4uQjM({TcsdF-C]x}z4\f1YŖI3)5IpfL~q7=:bΟʡN6P  GnKz%tԛ`įi\ߪr~o? ە/h+0/E .ɽӕ{\SBe GGx]h-F6 Qpv՗NL:tUh=Ϳ{pߤ_Aat:Z{bn{C1=B#݂uw6i{fB?ч{{漜QJy6ٴ2h|{"-g\xY CJދY nhMQǞ]͐g(3LEѺP/d]_($ rR5яU.CI79L0 !2l> A+Wۮ?6-M[r|[~4qKgr2% +`10Kf`a3°?km@~v Ρ۬mnj0Dlڊaco Rɲ_)Gμ $82699`P4)| G0"#j/ބ|`,W"-qLŖމYt 2M߿ oF=,ߡ,Ӥ,a&B@ d0sJno=ͰqpW9݃i *'O LsWIdV8Yip]s>6bO@c20 gllMMSD9l(WF.Ș99e_SP/cc`6:\Zo6?-})|٣82}dtDc9yKq!厧qk%soJ?m3g>; PɿQP\ em( @ɂhTYs\ %U%38xttc qG3aJ@o L KoƿvLG"7&_u71Eܼ c `@? y2#Bg)HOxD{JH%_ق3p߾A| \"WrfP!]?w߳ٗD?.a]5^ 0pC;Ɲ# >7_8FvFШtƝI pܞxBWLK~ml9Q*iR]0dm;>18Jl#LSůJgҥBP% @To \ơ54 dr=wih n]=BgM [4߽6V|VjP@eƲ1ynֿfLk\PF`Y? 8ѱ;P*q':cxtΠBGJ*?TV L+>Uƍmɼl '?~N!PO3l.WUJh6"X$̯rO Lu{=}LOy.lzJo˧8N#&;M8нv;?Y\CxGg.O&5~6Dn\L ~G0i@.PC=@,yIKOE2@ RN1ac@_y(f\ΌW`,~n5?Uz+2VU  H&54d-މdEtcZї&x~6&?N=F'~CwCLkfgl9tȏi!:{ O$` f姏•޾ʢŘ3 lKpֆ.m~T?NAh&v|JadӠ O~ 6t7x{~"%M8v8T6kryn{?sO4bB4OJ!CG?18tG7Gvi~DJ_0%yl<6|#.jLqw(;7 LL6NcnM|~6 Э~ONjO/0$`BC]|w c1{3s86u?~&W@1w<{;z-qiw;2 Ẇ4' c`0#_l2o~]ٹLde39Lv:̯udLRe+(& 4]ׅJ|_ΌWjMA rЋ%4 'G?o?yg0qK ?;{ue({[[z ߳XpRig) A,t zHRhPx$ &_lm޼/^dD,@YUcYVK 8#ewC܊1?]Ʊ?q#=ΫJG]s}*C|CMF87u,ڲ C< ([7tYc5g+,p|Xj6ɇN2'B0o>})|3q%s%qm>i𸲪|b7=?pގ|,ekQ^X~7"K<7? <4i)dSLL < ?mq,g U 䃚Ln/y} x]k;zx==~b"֓5}M~*ê~]qd_L'&oƍ@&3g&K4 "U-`t&{֬r_ef`'<ͱV`06./?Cܘg!8,ŧޏN3ɞP_W?*{7RC'1E4U1|-@5\*$̑x8#593V%o !K?zv3wW/g*!Ŀn80m$"?"PGo?h$>.=0  @eiZB(-^b^J|ƣ\p"gKq)xݸt$\U<ƙQ`>6098&&Y?C9;cHknVnp_D|]X869ћJ,딈r a$J!Hu˺ʵX8\/wY[bG wn=1ED G1dOILE@^ !0eg9kf&D?+[[Pȹ!.\نӺk@v1qw0m) aGwnߍ@+GoϹV@Sfʱȿ8q-W)XT wTA _ȿ9_ fgϓ ?/cn٫J A\_L x#) @;p,FVt*a{f ͩ*yBӔ>raUsB14xb]`=eW>WsC,Fy LYh¿$ V@=|[_f^pэnsҤ ktd qJ_9ǹr߅Ur5 7'(g⎈ U?{v};EE9y8GrD(J9gq'dKE!P(vPG~S{#P¼zLF_&sL3W<L87;s(1w*=ڏECMGis-GN@"!̓0|I=)̀X B t!}(ݏ?¿%05 pܾC_~sk.H¼-@Xf srC[RU._V̽YONB_ 2ȟȟ0'T69듟R9R{n ( |Rv/|ݿ!h'4 <-/TM~Y/̀o|=} (K[W 'F 0S/#Vet(OȪxlCMߘơ#G@T m"eٻ?iSYS`(@J|&|4cBSǂq*&@b3XWy5'}Cꌁ}+nI`/|AWk_rEM Atq4T҅s<_ӂȿU{af P ~}OϢ8XچjM3J_2r 0!B=5_s?;{?~=_?$B܃}?zBmJa`5Ϝgr BdjOȟ )`K sٻ?9y|VfDHjpp#pNOOx̙f+sl| ^V7│|`pGWyZ-h\7 {(};D_?ITD^!8̟|ߙ55&?3dJTyQ"&J !Pxܨ :u"b:%ޫrjma>l J ϡE頇OH}!I? FEz ׼ߵTH* 0.(ȟc꓿ /cK$f-[y`DI'?$j.X?[) o.`;ٟgD?2o!0+E@@NeƆw|crA@B-Ey4'"r~?'4оj!y@םq䍟4B6869~jNu VC7F_cseShK{C2,=we,U 2?|M " R ~-E^QJdLE@5)tVELq( g3~T̈Oo~*\8ݙp015wQRg.Ul-K/H#bMK ށ`Ww{'{,wng=GP [o~aY{RbW \=.X`}>irb%ep,HYxnub3~8w-ÿx)/Z4*[{F殔 'p)&)z'mD d'WL:[+_J=spHeOٻ4VSHGZ@H.]{+;@:!W9Oc OpZlX~#p IwH|'o\~a2wXw.fD MWH '+F RQēX~枟8WYvr^S[&\0'wHEqB*}m ;!gz"VK3 .> V}g4~3k(bS)#9ldxA) /2)݄e >O就_QSgL#ƃҪ H#c~;ڴ [$ Z15F R5֊K~u U?aH / ,5?w7K @ҍсEf`fOq|j6JYKƼ߆IwtX*۷uc:Tß@ڎDmŁBtQEK(5H$wÿfٴ|+JnN  rnӦVG&(N"R 2Z~~gWSH'H@90y_ JI6> `a(Vu̳=V?dJ?tr7@Guq>kUg/tpX~!)$ 80XP?5eZ&ǝ?S#T+1bc=[k#OAߠZA+VQx]@ ]ēC̫kE7@) I'JO7]o@ܗypm|M[\G>/;A7 @g^wgu8|, >݄~o{h:o\Gt% !Od~۷s.4๼r8:W6Enjj{#_EWs@A>/ X L qM}f)n7߳~  xjxA];>p @x]@ Hm i3@8 `5,QVP?[ @kt/b\zŵ7@L^9CWq/h!XȾ]֋}f?3h- I@0%^ tbtK y(x.tNÿUZ<򸪯>O#w?)^& ErPS7UzhzRNÿxH q#vZCލ"o-|Pz[0hڔ9s̅߼E:= NRNÿUp?k}R5֜Dn{_~A2)^tH (3kX4{0  G=ЎUy}V?}v~鋪Q͖Tvȏ)f [ GE@@Ȥ6S|<{\x 9w8X[ {3ʃ~-VGoPHD :-$ _p~u׉Iu(Ig[Y``[_Ϫ@ "i="bAd63WOΊ FHi[{Zge^wۺ3)%݀@ CY\Xt1a ;߳~a2mۋy~ ]dHcGsEĈ SklUH 0b@!^ycrr{4ër@ .@t`Nb%gi3U\9zL(~C1S5uM/>ع'V){D1HB˱W@3%4q2o49c_-+VzGUSs??@z,B&)3ٟ6ye"10 oAL;Cz :z`_W9:u ~x?B~V " |@ `ʑS&j>}>}R> Ho%߷ӮwM֗|u3tr-~?> -h0\?˴YW_)FN 8onN"`gac1|y ;îWtBP'p]ٰuJ?>/vDets6 !:@BSa!(HT>g6b6u1t4 vT|[Moߦލc~-|GC?` !@Ed ,:rQSig_,ϝ7[ ?] LuwB ]x-6NC~B h@HhV |7B ]M ?[NuRq,4gAew?UB}lu[ݷe7RG*}jCcz'B+P3ܙЈH:V>|9qy|t ) H]GIҪ~o^mU݉y ~ p' F!:O O_% N'''Κ*FOE鐳uɞ9)+|)R@.jOGyo[}G3~xP[w#>[@ =Cin|e@)"V:H9A w?s:f2C#^b H(7]"}n_H(4ԩޡ﫣wvQ'><;]ywP>'_{O(d 5!{ 3Q*GL,L13&!P2h ̸!ry~fP :^ٟ{-+[J~WUn߯N}ϫ^kO ~G' q;!)Z RG@B7JaKFʳΞ c+EbJbBӳozҏEm| kvEQJêJUsV@*h)]4ߛ0 @h>UPhAX 0&MOB7C䈉g#IEɠ' VfH!5(nA \|.腦g#X Fi8jVyB}uwT՞#*X;݀ca(ĝ>pm!@ H=݁B-D xo2QMOҲ|p!b|pY6j(eˆ!VR3nB%!*, ɮNTNk={Rc[62S5ȤNޑ}QwJS'>8 N4*zмh3 @HBoAnATSЀ!4r蘡H ,`AуaE<9FaƠAJ !Eѳ GU( *'s= ;SʱdIթTMw'j`[5*Pj<+f(XB؋Tn /h+m}Br&oA)<<(A:x^L̀:Erp\ 8+!%0 h)4݄ 5R ]Գ6 Л @)ʵZs, ;VF4֦c)8x p`u:FChzK|t}w+|B%R #roaTD o!, <џQU{W(ܲVgO(D%%}A'elsي@( .BR fh{~FBn O  _k)E-t /$B^m_h}x+p !Bz+ -ޒ\ ZcK_!P݂|!ך Tɷ36HB2 B!b vB(.߶!% !ҳBB!B!P!B BB!B!P!B BB!B!P!B BB!B!P!B BBB!@! !B ?n$G IENDB`tdesktop-1.2.17/Telegram/Resources/art/icon32.png000066400000000000000000000035711326245125100215510ustar00rootroot00000000000000PNG  IHDR szz@IDATXŖmU{mwtm*6҈ jhCH &D D ?LI0iIHEVMj+X>navfwvwfgޯ{?~@KM8w9{E]ҟ]/XsMf=C^[b"-#7]=q } \{oqޕu2 JX@k>́gߵ`bt%zt.!\`B:&͗N=-;~ST+ kƛlxIR>⫣|E$]G?bhVKYı߽zw}ɥVX\;t06~/~*̿{B=-WoXg*R@553֚t|׻qeV -Xpu-'U_<};7~d&gϽ]1 QpɑBU\יO/S Eqq2?Ϭ10к;2wNo,"Зxe wqÒaSk+{oM_qFЖڗa7Йv yHdF|>~ 3E5LPE.`jŪ$]WjQG+CLV̥ihFUJf@)T$Zu-gc.?8jf,B,’˦9jg}O&P, ALgq@B iZkЉ$3׳|ui QH{`7fشZyԞ#Kُ*hVk1xPA)]3&jmNGϲfD:gE?O,[YՑB)h0VU,VhoIߕEP@9 OUpEmEc T"k1[Оtx8Z VBƋT4-ƕ\xg՚z)TXc&\,8Fl Ww} 6,ma"rY4 gƄRRF$]AP(5rZG iW19)GN 3,[UZJ9¸e39m 0v ǏG4R?:YOQ } UHz#5Z^=Y(4Dq&&^Z{^kyybkrI8ZR@)|RxG^+?w_޹4lufBяIOgk<CҞ#N:# ;"8?ֆf7sS%+ntQpNS@)4d˞cv%xcĞ)VweYOS$]Pm@2Mxx?Fn.6l}Q^c8[0>.5Ji|#&]Z{xgѢo{Nd~f\*{ۈCDd*sA Jṵ,9"klf[p"+7\z]ИtZJt5ZoT*Kۃ>#/?#Y\`u gZp֫hi} l_};^QL{S|LښҐ Ւ|p&`Ǐ+>)nSVt[O&`P7^ +qڮ3G|\̭:z?WnӝV-VNmYsE[{{sx{^,Ie{TK{Jf%auVg&N~({T$ޥIENDB`tdesktop-1.2.17/Telegram/Resources/art/icon32@2x.png000066400000000000000000000457121326245125100221260ustar00rootroot00000000000000PNG  IHDR@@iq pHYs%%IR$;riTXtXML:com.adobe.xmp xmp.did:1ACBF4C4D24FE4119247E59A0A0429A0 adobe:docid:photoshop:340e2f69-0639-117a-8f65-8cab695dcc76 xmp.iid:6ba59e2f-132e-443f-84e0-f4ff492393d6 xmp.iid:1BCBF4C4D24FE4119247E59A0A0429A0 xmp.did:1ACBF4C4D24FE4119247E59A0A0429A0 saved xmp.iid:013ff985-91ab-474d-807d-b60bda015c0b 2016-12-19T19:15:42+03:00 Adobe Photoshop CC 2017 (Macintosh) / saved xmp.iid:6ba59e2f-132e-443f-84e0-f4ff492393d6 2016-12-19T19:20:46+03:00 Adobe Photoshop CC 2017 (Macintosh) / Adobe Photoshop CC 2017 (Macintosh) 2016-12-19T18:26:51+03:00 2016-12-19T19:20:46+03:00 2016-12-19T19:20:46+03:00 image/png 3 1 1440000/10000 1440000/10000 2 65535 64 64  cHRMz%u0`:o_FIDATx[yUՙ}孽B@D ۔!QJDDjR1nj5$N1f*cʑhjtNQfԨ8(: " Mot^vsz7tׯ{|}o}$/O2JߍFZ&+e"&!cvt1 *ۓaJIyOM='yƅ'{alfӧi2$Ӷl(Uݝn=}l~cSA焝I RZtI͢J/rL 5|VC" 2 ik|OW~kqR"QVnM6 sA`DKY1Ρw<>|@L)U\~woKqEУ2iL ;x꾟uoXG YSp7/\fhxR,dX{Zfe@y_Zz=cv*dC&l~{c4ns:Y0,a_z][twc d=|Z|7@݊WG*3{J*guo^֑!~.f\}e)WSU/77+c< ho|]c_B>)Ym9_' f"5V)Y1-:g ?&s/ r93* O1VKciSH"&(4|MP ) ^'@’psYfԘڪUĂ˖ fl޹kߝr|2y8.+榱dns+,h4PW)u]&k`oø_, 0c16nfDVZ3 q)PLP* /_Ck+o۳st2{Ç@r;D^᭎zY\9lC]1>RD>Р(w[{+ )ń_`[{ 8kH'Hw|_˘}w׫ZWNpŇ}.if@kw:eDq蟅wt`HQ&^7Ꞙ€k{ۣ>> 1y3 _cKghJ צ!{~NH"2$ˑ_=aOwYCU┴`=3laa@4 3CcKGbHl!\s7Յ;)xHZr3]ytC~hi`keZcΫ~=Zs^H}O-1݉~u  - ?yyV[:sbenM4_ၭ⇽؜ qpoýB Y2 ^Y+x3`[OSsܥYŠI`I׍_n9-y qToL^jƵoCGA6`K]9y(RX]c@9NL _wd? fHahIMP,Րogw)cQ8[2UQX0(Иhp3- \gź}$.<~Mxnz;a**xXVAR_C dFAa/wO@{nI H@ۓ?)  @ H9r.%Pa9Xs Rp6<[Oɐ#v*S" |00#BS)TVSx})꟪1yo=ԔF1!C,׷Uc ? tRVV ]NnJgUxR?L&R1x {6;?HO71/5<ѹR]D՞/X }yo!:'ԙ,!),Vc$*c`yhis"1c@[!wⷛ Me7+矿}PiCNc4@VЮ}sݪdFq荕+n67wR};?'7=>Y5&|qoЌ935c&0,@Lkgp6cM)lyfjq 4Î _0F 0LF4esSjdž\=% uU/g.ϾE3(JôÓ-3*\ '#mc.}pWVS:u2K#U=UuHqJ9ps\?\ zOf> rb3j|I]o];\IENDB`tdesktop-1.2.17/Telegram/Resources/art/icon48.png000066400000000000000000000057711326245125100215640ustar00rootroot00000000000000PNG  IHDR00W IDATh{Wu?o3^kUH8TQ-b'@(DR R JmQ*R(qpcڎއwvvwǽvf_c8^U\f~=s,OmuV=ˤH#& ?t9Ƙ%3D2zm[ߓZlGp)xհ41=P=`׫o?lھ)[o=[KXbkg I 57ww|~,_5nmxRX 38XxLz<ʧQ,7D@&O|ɞG2TVw$B I<|XoyzZ_.~[ap@%Ԇ ^|t=LS(mȆ/:ۻ_v,EZ62.6MIR B, k{rd.X))\)z()AHxn*Tj&"ttb}V=+ B wٺMMJiK&oeGyߪ4+.ڂ AJ ՀKcElXN2MJ BB5$h[&*x!g[!z7^eno6b,TC8ou96wqn+)JDdLF̑zƆpjo,}*BPn,XqVHDfahc;<+R.!(\))B:1fQR6NӰZmȴC= $@ZZ$Q WJ #2X: nɀInBÉ29g0:jkAPNAZс3/OkkrqWJ@xcdD+ҝK`66%c*@Rh1XkB g'ʾ`c[umgY\Ӗ2Z0Y(ݖdyg c5AJpnh9U3UCS}73*SׅFۦ9 W N;x_֎_cv!Zu%۱PZhp5/4x@k!, _2 xՉ36]spn'k*1U-]R `ygL!zF f!ȅS}`mi8B+GJUO\P)rzgi6)8QҤbs͜_-ɑ{?iDpmk>8R Q. |/SLWf@zdfQL `.WE /^xR8tnB7BDHU9W+soS{o~kNj]D_Vn_V;] xmp.did:1ACBF4C4D24FE4119247E59A0A0429A0 adobe:docid:photoshop:2b033d6b-0639-117a-8f65-8cab695dcc76 xmp.iid:08cbf5ce-ba78-46cc-be68-6c6f784fe5aa xmp.iid:1BCBF4C4D24FE4119247E59A0A0429A0 xmp.did:1ACBF4C4D24FE4119247E59A0A0429A0 saved xmp.iid:013ff985-91ab-474d-807d-b60bda015c0b 2016-12-19T19:15:42+03:00 Adobe Photoshop CC 2017 (Macintosh) / saved xmp.iid:08cbf5ce-ba78-46cc-be68-6c6f784fe5aa 2016-12-19T19:20:32+03:00 Adobe Photoshop CC 2017 (Macintosh) / Adobe Photoshop CC 2017 (Macintosh) 2016-12-19T18:26:51+03:00 2016-12-19T19:20:32+03:00 2016-12-19T19:20:32+03:00 image/png 3 1 1440000/10000 1440000/10000 2 65535 96 96 I cHRMz%u0`:o_FrIDATx]wt\*,ƖdƘbִPR(79)f6'pH6!g7$X&6W$7U4o4QרX:{S޻Ǧ q'xSu"LJ8"-y칦oL0|U:/VOYu^SXxJVQXM,X&*˅` vMzn_Gz?io;*ih\x>Qyn0ktmβQA*d@ru=;3M;)7O}lP!iNO0")S;yw?v7 _Z?ETieҟ'X@a[ۿٿ7B3B}k>E*)@SOJ( XnS7}i\wҿ3/<73`ʼn0(8̣{o @R퇾-91ZI/Kf=J|'sW_ZiDPeHzC?-e o7ol 7;"km嗿ի.9QUi!k7&_|y@d9o_~뽿S˪J;7f7=߿:DV}~Wx\UTJ 2oAB_ݐ|7%+OkO5}S?U6~?֜.yZidvJyR("FBQ~?7qRy#f5+}_e K)|}Z⼫`Л3-;7Jb橪ڋ߳wonH? ݏfhقxycSczQVjfٳcVl@nVnͩ'B{xob.(miDϺ6Ō_0"ǓAf 9W\;wf|Ⱥ˓.X0^YyM1*˿8BøyM9\hӻOi#P" ډ=+k޶޷h?c$VWli 摒auELqV|̄ݳ@iXZ7<;@ 'r53`ꈊKpu}o !n(š2LC \ e|Os'N$S#2yr+LjhԄq]cAm\}  & /G:gT^.ƒ_tyzS@|P*2;pqB㸲>3)"D" %-)@J_RK͞s¨OQ﬉ _Fy8P1X^Ӄ"T`ر^}JOO ci8W:ּU?d#Ϋ.یSbr}Id1`y I"s.X\YRwJDּ2 EyEt)ϐ %4\tJ׭H0 xB\/%LGPG^z2p%#&"8fP"ђ3wO/|'~3l0.[U q\, ?ZJ9MG aem٘>Dח8t, -R<$]k/ ͈k&hOs\< MVTxocp X#qu3ZH54$<(W2'-("$-54P*jipZlZu*|<p'U^^Wܚ0 %ZGqm2㠥'*B}U$hT4]NZP 0#khȤUD& Q-!TaQM[ø /ayF ){bwPI'NY%< bJRH U EH@z.\3 54SKO[7iUןͲAL_BXQn`8m,Ú!5=)6zR@zkˣXRuA8o!ID4A / sYjqi%a]}oVO J°+qUC.bAX+r&ۊ ,;n.<)'f%"τ?Ga< ϶FPI"CX\Ǿ7$$ªEa>w-O gj6G3o/ ϗyI8eaU0IF{s' o)TIHmc"Ă F$!Br%@"T,lH"COB塽/ ىe 㨈_a+ Mb1Yw(> H BOH3C]ptEHۗq mq1 @P1ړY7!d*bHDQTx* hK9L;P1_9+} R5!HbbT]g)ӟd]e ׯ5+˰<UVQ 85eh k{ ( (#bS.(B`wW:hsf.G9&}(#m"@Pz 4@yո}K`ϗhOfѓ !DpdE teJ_({ANP(rm'?4iB `AR$I[Z˗ߴ5ӓ}%ya٢tU|$ 8P jH `) ?YVGS( zL "$JoGg2k*JUšQ2 Hug)4ݥ`#$8~DbeL МpcH.bn2(:ї2ʢ% rԡ ' F1y1O}%29 rmSڦ%Hxn&-lnG]\Kb ,-ÂɀIc|ٗ@ɩ * ɌŕQl`zH9>š *V?^ rDwL ce63TQ c m?&}Kh8I WY1TE0\ܕoe&¨=83L li/y(6{A#UͨNJ|Th3^eѲS؟h f$v'>, q8!, 4VuyUeaHfTAݕ a^ sgO94xؕ4N6#Hf* @@KVW{pϮ.4$tt"[FGzιQ,HJ6cH҂&F;1\4Vǐ)zC ȁc:7hƛWJ@W5pk=pe(5Rnf*!xgS=Y" +J t? :t| `\Eh#?*Вuўv8E @WTF i%`+iXDLWQA=62#|]Iݣ{LK,ߖxD]'A < qחڏ"7 s(od ^huμJzuX4u:Z Y t"а ^ CUfvMA~,T! 8j%OO.P]Lq5 ~kʤ-{_b5љu6f֓[Й "m)iE}qp_{2nFaW`$ܸ/TBre_􀈦Y)W;|n AGR\ HO ,9[=Sw2x`nlӻ.:P^F &ni9woxvpjV=nIg}r&AAĀ"Kh@Go/m$š -ugQB2$~^{S{p)@;~$UC.Ş^zNVN$b?5`/70T1%Y L(Jq)jr]CIdhp<|`̣~OKW`gw0[:2O9"m+O4W۟mMlCkNZ5hin qcTWxi]3lm}\bٯ<Tc֥@#,l/@eb v  Mↆz_mƓ:(4z f:)8;kʝsif%A2{fX1}V0B|%~%)JQ 8;p!9PI2mДFa NWe 5-#/;|ZdVm1p|kOA%B>Ï0T’aBܱ nze"#Hd@8SHs<{Bpwpv?d{20YAZShIYi) ?ڂ=f~Da'q$>x6<qC3hޮ znA:=x8rJMH`;f }/UCte=|oK+66uG[[p4?LEh/FTWЙ?>>3BR={7Hbn{&gן\AhXiOThfymWMNXP4X) ,?.u<\XWoT^&ډLJZ(iy>|wt{{XF{n~bYJ"79#79E QRq*TDt~_7:vqNcT a=}=֌Eom嗰iCi()`R3ԙ|෽ޜ=8YM|U MJ(LaQis Q] f(Ps4)ڄ4wǗ#~F(?ܚ}[kux$I@@snK**'2BkaOyǃ<}/m;) 0SsrAfp P( iۮ{-f`~{_y>mbL|# u% لGnae\ͪb7Ň4a#] :LJ@.j{omϑ,쪁wDc4U)MQ΃Ͼv$`sZճZ;= |iiYP }_XZc6aBP/#칀(3$@Fڿ/:g!>ao5/Èjp9tx(b))O 穟]=EIԮVooW֮mC۸=|%E2GzG 8瑹|7h}| FD>ބK'n} 53'#MOصj*B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B @VVF!BSiYv=WSE.2D!S^u03440 yb}~(@PJ)PR:J8B giϥ&!BfD|a/~g 0Ƽ`ý3tKnhdY4GaOkO ׌83}`<308%6l(eAJBɤ2+;5ĆGb/?6"N+%JJǏ<}:q,(TII+) O+x] _ !2}rb BNƽc ^٬kd45tsF uA {n`\cc`cR9fs:OV00t$ҒI AJ8'ebȳ;n~ℲF[/ CrղOL!3Bd}cmMf, _tO7.= c NA '̹m"cK602nxҗqF"1|9j>:]ok"9}?6T<2)Br( dj q=7:ӍsaChE n,>6g.4}il^ Xc ۸Nx:-!3`z+3";"񗇟a/'|וXakGDDBH Xc {|_ࢍKPݲڽPB`PEsy:8;rl+ecڃz"X2ّ &igjHCtz~Udfk*=Ya@AZI@tc_F.ca&mY2bɻJ?DǞD|d(}ܕRx+{ֆȚ=K<E H-)˗$ -vο(vF=ܰΈ4s.Stc_[s Äĝc<`~򞇒?VNAn@dR( .pOhP0p}O`ڭ6-=[hs7tڂ8pt=Crl d& Zj9sz1EwWܦGjp03Ԏ}? S%z垡o𓿹7q٣2Ox2!T>Yp}dkvu- 4i% >kn^8#z;ΑyI&cqLq" HՠT7[c5{6Ebm-'E:Ŭo>0/~9}~E2`@LHETܥ]@zNllon|Eu/?o>=Э;2Ui郲ԩ7zۆ#)di5HJQ8/H-]t]o;`5/R¡FL[=m_2_U‘p7ZbȮ$)R( .-߶neWzsӔF,t<;7?~S9+R~( (+oKv5\wz_PPҡ9}28=`CӡG)Ш)sr2nn3nߊ͗3fzqOtpX/]ÁWB@E( VۗP]۽x? L?)s&=x١qo~'~Z Ghz H,\7q{n8xоJ٩t$elTJ?~_?I 1M̶†fiD_ݰׅWJd*nCHc04]%x3w%|$ܩL+dVQ@fK2>ִ>s F L7=^'|r F>4=@fd&} >д|] w)0ihԌL]}k;~xyDGz[jP @fdWq{.kuݍ9-*9'i%{#x, HdP@[6M] Ma׵iZ+c~{ O!?c#O'o7cPC_2]2*hдw4&sM'Lqp"6}?:y71>ȴ\qYz1|lwfclF? \MgJG_~( Ӏ|~ 4}ߡC7}fJB rs{p;w߿gx|L ȹs63{rU3@L0ã7K[.VOs:| nz׷_a @R(,;{'L1 zW[??сQд$cp>mW4^=BBY1M3}Hү7?-kl @jviuio!luIOHP ʱ`D7_noעO'PǏ6X_Te47i%(ɏ K0sB3H@Mv}{ow|Kv(%Rmަ} /67KOYy}>mOkf-&Z)ӍHx«v.JS7 h4PP2LomX8cJݶ+?d{DH5SJ@̖%u[LDFFtn 5ꖻOvǮ3iH&c')Z8bۃK%>4h4fPPrmܕ=7MT6!)e[YQw/b/<ӅBTܹ~iKzn} !ccȆ,Oy!grc7+A@4:G}7}j/3MkeBH-xɎԱWIPE@(LKl^:;jErTuQ-rF11=I PPr7aDۻ>~M׍'uBFZ`p9#O?SJ8*/oߨo v}JFm!) md2Vڣ#T) [.[?31#MT$ *OnZMW`BfRPv k.ٖ8ɣo ?/T *G&ol۹o~nە.1'8XЃκ͗M ZA6T *GׯK,{R-Bjn#k^nԵ:Ï@:JW ߸|]/jX&SB,QJ8Zp+c/>zL ?/NLe򖻾qGsS۵I|?!{=k/=v}Ӡ򕷋}4xTܤ~BHP#4'О:v 2F@)Lޮ E7c}!F hoS9{MV6N`sn|Q!Ii=o7Bÿa@qPr`Yd(:Ths>u2/B( %\m ?v={n2B@yv@GϚ78%T>{>s߯ebB:ԇG\^jct^WMkVmCLN۳duhţ=kg(((V2? }C;>5A%!\ X0E^{_LJ-LJ]|^Zqv#T #u_|7O(9 J#wΟ1}铍DbC!(@D:".[ 7^P0znn}EB64_)о'NyT0d(]y[rg~kEl T;%h_]t'_[/ Jٓ|AO-\+orB J 0xh'G{ 40(0跾t"6\C#٧t3Yu'G{* f^a跾^DBH L'_2ڳ/f 3+}[_ O?!`Ꮼ۷/ˏ'x%P0+(9{n"5C 0Dۛ|Ro|8 fFR?#_Lݖ+o9B)B 0?7~Grf/4w~lB`7pɞo:: o/c\u} lQO!S 4l8#ÿOq (FL-}7|DbćF!CIݷy~eG6`@`ZP0=r Mn{r΄i ޵v~ {4eZgnyG*Sqv# =T\yc/Ji `Pp2n;w|G9vB0J0{w =xN 4pd7MkT)BHuPoefKOd%L7 _&  w^e[%>4B.ҶjmF=P.'mN}'=u%>4BNʱXbo>Gv@";"@szh欼D&%>4Br%Nx< ' M^8]lz 2>!!'r mzPy`rW"1B>!)=pxu#OЩsFe2 vÞj_V¤B?2˄=Tj?MٖH_C')`jrM˻>05BܕBLP OkB~_A)=sUIBPpv}HRZG! k61}GM JlgA4]׾蟿JQ?!7^xRlR @`r2G_u!|(𪝛Fv{ Q0 &VKۮK2D!eF hHygK v -/}_qޠRcl鞯G1N'R? MZ.hl_TBdc?F|!O!H@ TMdćF!dJ7n?7O"QPo7]궿#"1JBʱym]/葦 yt߅Ta(ZNJ!D F}[m҇}xr5~`6T!J 5&^MP[񁿻5zм?!T: 7n=sv~ЦAc(_m@շ~^h?!T<)C_? EfNOd ۵f $mC!@ y{4fZ2:u/ΊJ!L'?o͆+cuP^؍oBO$FK|XBiHS+xS#C?o͂?adm~!)@kj;w g4lRRܥצz^F O@.W{ 7]5BH q~򯍆&djnFr៿fq>-HjR#inՂQ5wF Z;w7`B-J8Y& x0jp*2ɻ6^VB(%zt@gɜ?-0DhBj0Zڹ/ywm#jfU@d<4O|L u)!HQꛃ˷ 51 P @p[^=2ABH͓Lmj`tQRyWɽO!pyʶƞݣȮ x*G2C:?cw!r+C7}۽x<(@ՎT@^/+x6#r>Fp9( n P<0gi^> o !PL" AzU: P# _}>)| !L麧sAoJؙ%JMwɗν6J#2%M]JGP dpD˻oPhkU*^"RwْGI_Zu&1oyW e!LЃuuF.d+UTc`:`[npB!@$c\t 6@B`U- ohծ nC!%Mo?izf5@U@ci6]y-s B92Gpo|pGjj r+n8$S! 3~TYP-I?{|>9-\KBJ80So<|ඛU"Fr3z{nSVćE!)@6k8xro *-F2Ñ?E S_B!B /=8Ł*zVpk"[!2ïstsH( [*p+"JB67W?*.^8tNdo7Z{YFE IDAT!C* Thjǖv?DM4ux58s|ފwL tPBt57?CȎTTP!wٟ}МgFse[BjJ'ΰ jbOw{BX\og7a=~(<۟¿02PPP2/cM?s)TLPĭ?nj'%>,BT@JS[8Ć^HjlMY9Ӛph p,PP"0a;rXldJM>ZjBʋJ'_ + jAs"&8l8SHSpo'$0Ě&/z}1KBtJ&k%<<*0L ]~BYI  !eAMc _gڅQ|l]޿[lo) t ho`Pf`I0ƙ^;4TRغ5nuC)^!3!eV-~ acTHwx[_:i[3vE篑? PS;I-+}V6G) 69ַ7~t ims9Rѯas[A)(Ƙ~uoAȮGtfǣJ#̞J(a^v Sg½^L gQqDIl>䛻zi?(@'VJ0{:V\%>$BHW@_v􆰩ՋO#{3 T a~C*PR/}ݕW~$vU`lQg 쯈r6!̜!ݏ} 'lqQHڥ;+apKsV2-kǑm(@B]#dOB*no }< i10aKUYgJhsOx@vs=Ȕ8ፗ4;hnc fC8E>t(6bg0ߏp]}8e%|~T< (\ @zdWA!\L6ˏ , GGcEҧG:G6et[\{?Uk(%de޾;m!L(sވ]]~ by2 c T8:iF(xu>0-w? ;Sd(4*W0STrC ~z,|h$8-qt0]("S,rkS?2|I`%hhk s>&BH]08ֵ']ELh,Jjsq1r@)ر@I c4e:F"[޶e"\̰@ܡG[zi!lƀ be~CH;=_4pcn8 EHYB^q_LGP?PnjЦK/K}L2%kPհ3!lk%RBVloു1㰓 3G(Eq` عvB/ , ܕ =K+!YC>cy{ ]cBґu֝F &t$BG~$/i 5-Х#vw(@YdNjDr((#ߤBh ֶ49dzi=Cuxm0 mWW t.YJ!|AsɣoL1ǽ=TPB'BȬQNז QMm b{gA@ O1-pd scbcv F,@ 5vobv(ir r+]k͖2E"!B1,7'},wws|"qK`} 1g ¶wK/%-aer k㼌^/B s컍z_֎ŇWL$=/hP CIP,`jM :hIA RO-޾im4@?L6m٧T2Ôt1ɱŏ=! ;d1φs` Gi}XcAbH4CAL ۓ}l~k Iˁ QtcCx5Sx9Y eA  4F΁Zioƹ:lDp7z:W]!z Mx􆰻;ވ1$Icpl % RHœpYrs Y $k#w'l'ϩPnұaņa <~ӵdc@v@pJ%e/ТMra2ln9c ā^w&YB욹q-pl GiF6~Jk1L9>+<γggiǮ q`fFt H+>HB JdZM̹Z]oK:Y:hلVz1((N@-9T6?ë $ mP>0 z ktF S0%ܷN549C'D Jh8tŎumt>Ʊ> Oo RhY ]cjEHPH'vu/^ PH9*{8#qr(!Ԅ ?74"ܺ;C6NIU 3*x K&>Ş#FIA=ڼB 748( 2ҋW+*57HnI[^ ;8֎Z:,Q۽ l(fᭁbI6CCosAQ֍?`)6yxmxa g=6^$ PRB 76nY4|D 8p_^o[$m!JjW椪O: ^aq{CF%pz8 mp9G/tGܖ+ąp<+%>g0vCf0s;P#@ٶZM9Ԟ>-A0ˏ}aoC }Ōq $,Zzid ! }[:hݎzeRc(]g*~R9CgCѮ΁3q 'FmhA7TP $?dmsşèffAIT!~B }>aE{¼:Ɛl£0_ ~M8cRp'rï(>@AITksg9 |s|#|hks[J@AAzi6ٹp 2;*M ,ւL!2$\qf Ho³ŏn~Kd'o4a8FqTҾFP*JB&)Oe)gY^+8?zq./O1+@Bܦ(;߂Ka>ڰxFp"0NR9 ꥰmXR"q+ aGgA[wԒqX38‰N % Ԓ'ݶu6AJUѯ#W9uT~ 0tzIg/r2@X4_JB/vj0GoJgWXDNjD2LGI`pu&􄰷'% ^tw~HB}܋,L}Z6kocҾs|&kG1I_> W,q"W~EnrGۂT _مRR|6u>t3X8ΌLOLie^w8,#IpO $6AsR0?Q0|~}'uR8p  P hZy!{xVV?:] JUh`KXv61w]J;$x['-9K_>uR)xt =M!2} 4-sZ?.G`ܯMee{'NRA|F)ms#Z"ld6wyZ ,>=!+ʭ9CO 􅰲ч&݄ڳbp8b)n5u53 JV{apWθ _ٍg8P*c. ʶv>3[*{5tn%`@tB?oy}bҾs?%mJQEHr3}V˔p$=^b|LCN0ywΪ9TNH'yue^ boOx4w?nSRXAx[q 'έO!!">Mni_,I`1$lW5*O|;qϹC JBx™$xӷ%c7e0f9JX7l'=ck/ bo[5( }jg B)PBI=!t7q6 Ġk9mAaJg[ܞϑL3W+wepRH@ R}s=rfly kAR - JgB!0w4 OFe4aᭁ8F|7|+[΀31 G,pѿ8y3M?JOh{a7ک<+@AIY)?Gsư3fQ>6 gCqNB* -s?:Sڷ9G{]`]x? vz:1:!1+2(P^Ai oD2 IT1ǁoe7n3+y}dp0I` o`k{#GW#!~J^ق>)})VUh%Sڷе.<5 MLX`sNaE܈Ɛ &`$aX I'tB*}&C05^?gnġ쾈y} sAv$S' T\P>IqEB FJ::[nfkS٘`\c@/Hg3tLl b__key-`)KfHR]ҾBҾ]AvR^:^7Ac>Q?1-&om& &>LߑOpWG$M_W1a6)ƩVXYnY:-! :N@,q$ /S !/U]w4pgΉ@lzu$=V\[c&NL,0vIwʹJ͜ @}o;g,pV  X{r~R(x5 coo wKPYޒ8C8>Gh >%Z>ܳ02 t׼+ϻK_0Q_ق)n1%qN`bthP}331$!ۓWžw,?C Q9J{|ے|ѧ?g& /[^n^hw)kQYRqƐH986p-5]K/b>fJNc@xL"q mrѴ7gG8)/() VЧJ)0Mh77QO5KaO?3ğyo~{B;S1ү?g B*IP#}) AԇG@]NJEQ T&/}: K#}=!;^%pj8C fd?C*CsKF͕*Ccx{ɒucsg !)(aci ƆBml(BҊJʀbF b@ 0~wo˧9gWeuwGT̓'2;< USV+ؿy隺ccC;0>0@݅c'XM1C!|KJ ϥ=`/'Y#hֿ]pY(/Z2)ͳ'gNi߸pD^ 3p-4<]LNv׷icXv޷-sZ'k1`gc8{ @IOC;d%ԸkW;5g{cO\9\~*̩Y&RwaZ+4Um `~sDN;z3ﭝ`%C#,a-;M,qOBxtO7$@ }/kLHúSOE~#G{^%]ᬿ,nȻH}Z!0iI}cC+y<*5|G]>N38D8׷wjWwDS#4H̊HU}~cPI $g0,*zGҚ9=˯<7?3˙dqBO7}iXfx Ji߸p9Ϡ)":b-]boOEo{ff3F(=a(O8<_?.~CJ  eyY~$N";n}eōEw0c9udǧj+A8"LfRֹ._n8V̿c!4Zm~_KZ^{c?AkD`AMN,,NNb=SI3㧧99p!7VP"ְYbyH%VX(M}{^`ZDS &oWy2ob+` *=S'zUoD)IFAogt/<>/=wO?&9JBY?vJ%/,+># ccۑi~֌G&g;Vvv$51I$oh7:r6ŧ|(/>>őJBAC$購U`m鳠O#"i'f9I&J`qoRO?i[_׿)4ӂdHGO} ͮNۻD[xF!'@R-:`EdEm}_3)~|9FR`&E]oU}iOfn:V^+-zZo.32oEm̿l`@ `ADDdeb Q'7O[쬧#a ,<> W?pxbT"<ý{R,mȕ*E(x G W}y,Ӊ! 7 յRZAVP'&eR?n;4i\=!XcoH+']*PN?a lTJgf/hO>=É $!݀l?ɴ'gH;I>@DȻ7Vt}5#v_[vIplC?mXFKy긽vujo@e+9C(UMg "*65AO0R-s,͇矜fR:+[Eu>~cH̩Dw,Eap}TODw?(ŭHPJv"~0wXd8sbiky@@[&:@9*H<avcp"A\eq8pAV0rO#%?N#m47|>6KoѶ M[̩Y]'}O؊0QE׳ "IʾW?8?& |߰U`=[O#"iSs GKؠAV@~;9{VȰMO<5&( $(%A\ ӈH382HVZ˓s#q `-/@Rp7or=G}&FS J}`C zŒVӧgp\ÀO =gK,oq+>r-™Tt>9İRy0ѕ(U.ݗcm~Ǧ|}tiKMԶ, nP+d4?|b?Z'BHŲfǰ}I91Fi ({j&+Ctﯜe }z.Ҷ27mw~;^Oc;&{uT(_̤&t9Zl3~?h>9L&H"B󹾖't)rwߺte9඗_ڷ Fw dZ,`uaQi_챴Y$WO#LBit?Hh%l<.~ؚU$߮MfZ۴aZ:A{&a!qz6Xc?0nY.X;F2IDwH s.*w[oӒ_m90Hmh:Ik[S1˾>6"`Ȗ}ޞOLrD4\6 >2ҾFrd2S'fH%ҾCJkBf!ؚmmѭmCuصjӾM=9. 'HڊK#ArWlY^";BAˣJԎEZ+{A,^G޵ߒjFTgZ;_pxt+Ю9"ЦmnBs73jSۥV/km5_4&;|2J`&qT{YHe_2-8$CGt\_)P7/5)ftkS+^̿qz;{{@ùv ћ-TAP˗!Zkl`hL8b~k#N֊oBXV?<}|cDD yjT"'PU64f!#b ǽdY~1~#͠PN}E/,mɗ=FIЧֆ OT$2aqc+-j7ݢB׶(Fn栾a:ovJiIZ(k|O̤駎駎LA(FXgX.-c c-ix 3z]CGJ (HeL[)꾺6ߩM!jk]ɿ-ո_- Jv=m8w~`\M}{U G(0-ZVxe/_[̑ (p?qzicDlei@ FNЧZ2)ON'Ҿ#V\]ɓ&Ҫ1o{k38ރ6͑o,^p<Yd+qp4ZaJʶJptڔGhl)4m:T̿u;o}N}m|{״&Y꤀.vx63"A/" : XSI H J9><:1ϟ='CMk1>xl ,9XƀHHŒf\eKjak,'Npn*זs|tI)!I^rq (ﮃoP |}>gpBC@!# ~`x6o<3o?⣏gs̤n`xk+%emv?$ZǎM$. ڭxhW"ml8/xnnbޓ4f K'bX2Ƹx&&S|Y>1~<;7A&(?2F?mu֟/z,m(|Dc|'OsD9}pՕ|ؔU!yy]7_׺8qޮN|'Cנ],oyhu{{Eځ;P*Q^)&gh"IT 1e&+@ usgSi^<(O e䥈#A"ƎϬ"i_)}GCO%sZ t:2tlBs3}n6N^ݠ~߷JA" '|{ AZ Jeo<6S)'GP8Zo;ﲼY4>}H'ŕ9/ N\۱̿v_㖸>v1t?"cM7 `1Aٺ%&6,,(<S_[OnlE1H l]Y?~)Gq xBT&BʏqN{ۼ5&6ON̿{lܱ:z\ ZS.5\X+۫+2F}Z!V1aJ\Hf3s|?q'*Rľ1܄oRJc-EVK;>Zf&R91$`jmנEm5'߱ÃأuߊcB%+&%6"&TkcHb Xno?^XJ?;Ǐ<6ъqƀBY,/O+rd*͙(% .(xlPB;<v+X Du=: e|? bG}P4FEl@Z i%kb}o:1=·NVE}PZzH`Xe7lǦd ][T@s ndWi6-ko$:>A[yڵ޾ch{| ^&j bPI[L$b@Ue-e ieHk!0 YޝYK1>xq4ϕ\7]?5#<>7&C% Sw޻Wc'ӯD~ڞQlvq!:{ b6 Ž#\ʚRnUOX0ˆ)bepmxgުH?wd:(7nȕҾ1&~K({jnSGI>8"@֣*{Id%tqy7bnSڣy0*X[b&!Q΀(&`- zoS)> CGX*Qr,|bnG'#"`Ҋ9rO: muXYSMע޶z FοJtmɿO`uKjNX T&{BWͣz`Q" 'iN!J3RL57oo[ǘ`+/O XbI X, D cdz;6F}je ^|nq j]VD;"i( |$ҾiN5'80P"0h,S̕{\/B/ۿLgR>1Mu3q#CQrMo-]k4Ҿiվq^2-o,ޯ7#`P9J ۫wQQ AJkX+G 4O!D (%8P%k0ߦ v9u>g>r!#"X5&~r!Q! MOB*`]@WV gFL'O-,wJq,H6uR+Rt$8u']]!mv ofݧj*;WD)g+N3i*F "ă&= :&yv?BQ-f6p@FAF%Xmb`D=6%6h.,#T}Ns A`ósNpguXO 36eɿ7#GYXKA~c]M;̊ R]##],Mqd?P"܀K9iڮ GqlNQ<(V:t}Lޭw޷&(2@ l``Ŭ-l/*$ a/{A5}8^%RJX̕Y"j `gRC:_I{q:r qcP Y4`fo$DOG\\-]v =}bcT&Upe9K֭Ya-MI H!Xcl>1:(-n{oq= DƱ|͛P z(.6K %8k-h%>>d `!Vuumn ۸ﴍAvލknH|#m=_!% ȲIkMhdL*8'ΥL֨\Y(CJV mس^Cֽ]{:.1rW_UD<9`@[o߷ٵ5sqki%X\X͓RJt.14Scږ@B 1: 6făTeݛo]$& *C@!\Xq> k&o\f.\u܆G),5 .IvU_WD?iЄJaroRoD_ NRj{W1$cORsy^eZdr4o\&J j{++0ns}bRo6:^2{q}D6n  U9 S6J#ABk͝m2MuiÅ;lJ|SL4~ro(se%.DhCi=fwZ p/jm#Vw{7_C~ƔIf/J3 Z(z`;KAv$+&a&{ZJ)-oGea{Es9u_3?և yc!x]|Krډ(-( aUG+%޼1H8Hi\(ʿ$Fy_g|i~+}]1Ϙ?Kwe aX!0ZW.M~U&H"ݔ,83!ܠ" +Ob2$!hۢݽbsGZ{Kp뷯`{%6㟱g4!Jd?bHP]h[%3AGY ly+y @ZExegOq1Cϥ)uy4n_PGjvD;&M]p-=eTv9ϸIݻx&CJ> `G7MvN0PݠnP_v Bk-A`bъB7o,p{a! 4h%,dtx֝?1ۨ3R3»{zԄ cnzw.'Y(.i^d:a ޹Rc|wn/R|])..tu9G *;3ivj"M%΅sT'P's^8, `% W k_OV 8ךDZp5Y*Bduk//*//(4bmV? Jl,ݨG 0s"iֻM~< `!ֆ+U[ Y5 78Z-y<}矜C) z1%w.-p GqkmȿUhr7z0 ƽ{6𶨏VTlxg>X `75EaR>kwkRq}aqs|]?ܐ[sZ{!$H֙40 kI5{w%HKDORJpuHJwU>?{CZׯͳwP֊kY6K~0(p@kLv}ӽ%"GqŰ ډ, "쮭5L\]lv_hE֍n<\{"cRBúo3m[+*ֹv9`ޅ`ca9R8Pk7^b6Wdi?J0ְaJ>)E_6Oh%Xk`|}bC (Vu@|dHymjԡc1:o|qP98)W^} crT1w<̀cc ۫k%%oϐVżϭ">-6-z9^ō="l|[pPVnx\R/-=soPI#ڲ{IJW [͙OpъB \{IH`T(j#MX!^?l;nxw/ܮli&(m{wLn# vjh.CQ}[*"@WFX _r*DJ+.-yE! b\H$?txxzM+Q0vd;E+5!# :J0qgDje%6*\E8 #(WO3~ד/L{7|F {:Q+`mt{YOv:^`+Wϝ9uX` yQI /(}J0 3Gm!R5O*bkGg&̤vr߸F9@k]:`.}OfA׾g7GR P0T!v^,߻T$=њۛ%.> @a hlXxCl!C:J<.c.KL% &某aq/ !G?yUwI%%"z,Wr>!Q({,r21I@֊Y s"=x?q6 !C9E?2BDyE"˯b aD8 ' 0#7)D7x`Wn7Q’zw9] Ox/`"Y.r/wI% %B#uoᤆo&%KDXw V @0J ox |}Kï]&WLBa1[B3ۨMúo7N_^9FQ!c4a5@鯊Y AC/0Liӳ{ZXP|r%wrߐzw/.r~)dJz6Ǐ)|cGccL4߻ƗM$3LR~k_t/|eF,?(yȎ$X{k$& j)Vdض[R G0湺#*tZu~o{'U3,dк{"VV.)v7G%?Pº%|F?z&2_&{e 2*Pб ?6K7s]7vBHWW |K20JcDa+טVPp}yclxG'Sx#R8NP"|!y? 3X29xŸƚ2P&{0jӀD@+o wM{kr~OO%( Kޜͳ^H9@4*)_4vH)?}7y,ʒ +y[y]& t{x&Hxd0z@)lg2ýcGk.*; pÝQo..QDa*ԥ" J1qho޼KJ dQد#\YʱU2 ᤊS!-c1hBiLv}ݽW960jԫz-7l& v[ kX*\/ޭfXז1ƠuH;Kƣ+e@iLk%o|T:uCȹ¥m!hm0@R[o/X}ZHh߿8I2`ݠ2HP R~׽Xkw4P,7nk٨ ;åK>o[g&9I C$̥éxM `J?ڟPȈb j._*i]9jm7@G;8nVl}~?xEZ (M MEé*4̤5_zz C DX+x\|F +?<'p{w+"#`#S·^ #IL#BxC)Üϝ"]~`&ńXȖ{\ZʒтJ̟ӑFzSBC`2)+/_`5_f: 4!V*+vGqCHȿ%,׿j#7+`/ LY\RO?OGO%<>g{*T"<| }Z߹l=aQ)( W;i>xr&DJˋrotiʷG&PSȮ/-lo7F @S~+_l1IL;o<7fOJ@$7{jLZG/۹h5f@pj"b&xkqgϢ2H1߹< wE~3X}xjUFat=)f{u+u><'@ zCTDȕ=~YNM bE|٥4 %BJ){/\^e~FTX' H_Ұ=T*¾RJ^ztoD4G E} Ae2l- _ѦA+D!x/a$EFlG1Z/[zK_L |˅Ll-}.B3 U~A'4@_11 |EUhL8/_z_r{FGw`CJbEG8F~OwB㥆 IDAT8i^nsZ.`ݫ?~NR2 v R[QLZؾD|G ^`•%vk-LњqGҖGZDy{'Si(`Lj{@8JM31"; ƴ ) c4g?T./% v[SZskBʼnY `a'ipmng/.MRPHcL<[W%q4n~yUb:?WWu+)6v?|pҔڗlN8Q<#M0%X,/I>@]V0@ݒ B` :Fϼۛ&(ٯ:"ZV t;y?<ǿ~&Z&x@Z+nX-x O !`Ko˕-&q0O> ~ů*j *^dƚ7!$K9~<2?jk kQ1vB* {,A2#.`KY ^ HBR%#({Fv( TΪO9 6) LR),VBJ ח|NfCCc>fTʉa=׆cBҘR~3aȗb EЀc6/_3,)0gNHZ"B/{JP"(-TWky?uUƹNIk;MF7/Q />u G8pYW}>Ϣ5z:uBC'q?,?"F/8"yͭo:O" 9Z05,gGyrvό$+Ҏ6/]\Q= -^]^g#C@KWr!N;$[Yhpe%OC%yZ(x߽w(Jȿޮ87'TJm]Қ;7/W.28c^%(b[ t^ $={՗׮RqcMKu}e0K'{e *gsK9]e7hj7=m2Z~=~oWt%?ےAYh*Y*ta"c(ya-/q?DaR^Z76@@̋WqoI0,H%@I gqB[".d&ݭs~E2LIkHf;Kj@p`\\#OI}7Ty..1Z'?tHfⷿZ>OHW ,ƿ{{{/'^=! \Vl0ֆ0n 0h..{Yȕ*f57ڦ(tZKK L8H)<+MHP@+KKBh@>|}zr gP R-{UiG񃇛|<%G2?` Ch2l߼N7LhbUA&FB.>QX4b2 %6x.\p/5IO'R歅,+i*&E5>wyrUCm@@ yoF %oq?ǵ,3gUͭ:tױ;tTbF^[l)'. 7jQ@#uoS$={[_^{/ Q*f{u]|qG>j}wȗ`l `+\O9tJ E ]DRZ(_^8:L2 Jքjk toӸ/Qѱn v6T>7o.^xcdp4xR? twݴABc nq;mn}Ș7bʵ^( (}?~67Pz Fm Vezk~Vj:'fb&)%XTEݯhKp-cv)6& tmbkr4ZϾuqyq>h%\?nݢ7B‚&p/ X#p:\^LāT%pg#GNNl& d .nL8%~< d k#h3k1H22ۙwnƻ݅kE!i 6 fR|# -0wr{U${ wK_la{?'NHc?a@u@*X(iLbI0@DĮPtnsL~[A` Gqe5g).bwh8k i܉l+7h:qvmKh$A(>߼J! 8g7%l]s.f$!d&)uB1Xc,NJ?%H b@B+-dK=9%g %0Ҝ_;ɖ}RZ7mn6mvvuw?Fƺ/aEaQ8>>|Y=>o: GփM~"xbS76Ia-g k ,|=q 3VL Y(Rrkȣl@p-2mE ^ֲ!!V׈E!in!Ï>b8talv lHj˟op_;`@ir4WB!c/C^/qqi mrn@3_˼ro?(vvoк? B ./d /XDȻkV2蝲⮙zvlhBR+LD?A1j};RϿ$F@ )Ųίxooq~ib( []7!ݐuwh>mƃMܩ;>=DJ ~GNeh/ip !f''2r_ Y((Al"#sQZxJۺ׎v&.W7VB2 Kٍe2c2B(^|%?!L]~˿[`J?, @5!0mӏ9g?q@l薎1!0R}|vå-%6M}:]7#Ƶt kF C/۫<*98[yƋ麣e&Q"!s_n.El \{}d&g" ֬6[, C|bNZj3W3oC106['g L8;rorcll,!цd(uWsV8@;mm~g.J'X`h,ϥuس㻺ks--H\3 tnOMH@voXъ*eLi..n߾Fl2?pՇ/_-Fe 5:) ?v7C?:IB`BF *%8$ڍw]|BW򏝫Pmo+yH/TfOހV~:W1mK.KHQ_ρpG8@`B_e$!0!J#ځk[ qȿv8Cm()|:'f2|4N gEOs?Dʩ:vKuG»7o~w`DZ!/Y>6<%Q*4L@T '6m$E5d4ʼ|}ˋY&SQ\ߓP VlIyk|O+ J &qX1] pcę8яPPtPG2?mTYKY?`5R50m$Շ2ȚNϵCS>O#K)$LR5ga4,ſ ? G0_URC..S@)a]9blxs"P^T P Hթ!YYll!m$Ǭ׍۾*Edm~: DHФ 4]i~ߕ noX(s "%vm{ {+Ue⟗Z0`nfb]n> ' 4d(c?Ӑ7a RrV7>Cj>$헻G "W(H5{ h3 7Fx7$H<z%?~ 8B?@{*?`wm56|Zȹ3R&ca ̔.5]@5\J$9.RDElzc'q 8mmP*aܲi3l"Zj8@k(Wf0J#0N >G@政OPԬ\8W[f eGHMbXEq&ͺx"@2}S~/ h AJ-SN=3HBӠ (E/m"Al? yw9G -[(4q\ojd,n]uq8~pA{iզ/X@R)0;v@z!_H f=n'qE'9jHƓֹ#G57)Sr BL@?h gg~v+2qN @~]/<}` p=jBr"KW)YNJg=myҼY l͊\i@rWb=3 ҟ;ߝG @xoMѮۍ!83T !$_b XJ/~<3`dc0x?2+Q 4'Vh9,kиͰyޑʑ69ιRſ؞A-)UzDV}n?Jx@QӗqI%qLHEf}_sAoـc$# {rW[!3[ @d&@N;"͛-Zw Ę@R{Q@|+o|úxVc(R7 j nY]5H%01CT,1w#,f"(lw ˴̳Kn]+g̟T ̵ ! 6= L69O,q[y\Rw!ARӧi"=ݏ%B02Ts uA}])f1'T!D &a"ׇ>h} /|߂`#/N|܋oI%C0:t7[on8mM$B@ V1O`/VKſ#xW#p ; `n阱Mkj&@9V,1R6Sg׋_"s?4v,!|ݾp줱M74J"pPHW^OЏ7=B0x ݾx┱MòSD*v,TsO#|X8(:dw]l_9sV_s`D"4xL@OG2T0?QiDmS{B? @0.Wδk6 Ra jp]G8d8[$̸;mIDAT)eK h_>uZ_}߽"H 1l!?,"҈'oom{I$}p_ߟ l:/_.?|2d?$_mSz[_&h*C P]W.Wo("bA1 !ЭU89 :İ&PWqPz$-ғ?e(rv!La@RqprM Y_ "NWO~1MkWi+UL1&T!SX RRk)#E|O g?T `.ڒ[isW$B KjP[6Rf&I#G04c t5o5+ܼ\&4d5R"ŷ']'hſBA&@@:kq׏sC0x܋@yb S[z۝B(Tϔ I"qFxSǞp=kcƷ >pwk`m1`  XgV}״w =b RQp6w8Ȟ!Hvǟjk +%}C0>H_nkٗ7WNV})DHBxOѬ s#$D_xIy NO+\oA0>O }quv'ڒ[Wsfq (B@ 4HJ~_&^X_4Nrr%!@*ʐ AW#4J!cd'O%?|8Hg~( IH@:.Qh7 P )3쀌HxkOlRPTy ~?7' 39жռ{?W݆P8@R1DԲ_CDa='߶/7Gf_Pu?`\A0~ 4'ewOe^nb`jvſt@'ߏtu?7ӟ14iN^@ו6y>9kMrEZBʏ"xoߟy:ο63'$4 O>]2QrѺ R)3:Cem<@ZGc6d?o?C0x7 ۲S{ [׉ bA ԀP̦ގ]7x?{ ?903/YUK̛qHT K)# f4oI#w@L"\"f̟AR1cۊ;k'V]ሿ7яV!4Շ8 BkSS+@D7?~MXgȿN< ujA6o1uL( -ޓa*a}Oh9DhjK!: *T7.]Hh nzHg4Qˊ ~-/?WzU @mpݼD2No_:qLb6o6g rK W#V򳉗~42{n+Lbh\ m? 9*CN !e`4At TCzH}w[ϟT)jy !Vz-墵C"Wܵ f\Sd4 t09sT2 cnvw?&j\#F!eqWIL= != ։|j_9s'{471FjFp_ACH{!Fܩy^<4?``x4k+x?\r]0#p`O͝[_U=+">}APshwX.(ƿ~ML8m1ƈJ:n;u pWJ~Id&xMx!RL_oWP?y t:?I~Az>'<xq`s܀02#M_N[sCt8Rn!ϙyakrJͻ8V%tnrM.@ر dqtMktc~sˤ \Ƭc~vi7DfߝOB@ ?,nڝ_zPKV8F V~#$)v?Y'w0wo{nm:=WȬO'y᝘l4nƯ|UYB7a~q]8?) R aoYawnnƯ|EYp#0R0Z'wo}5/ެ~^󓒠 #oa`ghܬm/6@09} {g{c}x3 ?Ɉ AF1E[e~J.ZZHŒ6(jF8|4nvGK ҽyo?=~' MdF@m ڝ_Deƹ1.0 q:;?};ڐ{]gR3#N=Bre_v˃6w>,Ytd'c}>߲1.8"n"B@ʅw@6#]}0'r Ĥsuܧωy+6@f N_3NԞ'oc<S~7G'eHx©6fLVݳAsi܄iS`% z-s'Gw|n\szL#Tl0RĬE[6k>E^!3*@!݀:Tgrpմ=ywDwܽ>~RvhXܸF[sڊŔa+Jp BRCPAӾrutLJ։]{Up^!-R^yB@eA7O_ap 8祷>'+hx& KNub֒{75+. "BL8 iHI@Jg\ a;ռgռ(]H $ޱ}&q Oϣwx h(ܼT[mզEE)!2{jm>ouju)|nh?{q jnF1I_X[v]MwYK֊uL 犾f:`gwL t_##~>R'w;<F-P{k$9{MrɕosG-Fj!tg $Uw{}va±֙'̞۳}2O54d"T\((2Lh6 \'&Ϝ%߽Z.^{\rz1yBi0e {e2qI hSǶڅ} Mw#Sw{޹ q}Ʉw62 : Pdj\p:5rh>K!1 N־*%틩-vv۩&Iw:l{ޞ>ELHhH57Ȁx@irmkW Vq aDL62!})R@/zo\/kVWNZۑowʞ   6A刽f= "8E.ٰTZ|\apF>$ ,OE1|!u(30ӧU-' GՍWro/  SI@@&3^cyu59+W`ڢDŨ2!ňf+Cb9f+SvuԢحMjfe|>JhH&N- BTݼ@B7aDM7-. -/BݤiׇC)O씬԰P_󷲁dBoPEԵVj}dڅD@\o!3O蓪B`PA^S}O97@5 9nSً9)ip}!Ԡl+5F \Lh Mw# $ U|h/lCW~I5_Q~$cp޻(E=zo^PE>>9hH#l?o C3uq$9wl4N# e3i.tRʲ5 ӮQ5"!:%{C锲SL$Ld 5i_9s }7:Tlk넙"T=too}퟇=E$4d4wz(Ð2$Ν$g.E82F1c1ydi#= 4F( ͐t RJ)=oEХ$~cA|  !m27=>)X CH]4LI3D]c=BuuЌ4= 2 9( ;n@{x<lۄRpJ(L2Hc*>0ިs5 3R&l; e%anvwd62g{B#~`5 "̅;$7{򛓠qt/x!{O>>{ !#{ MA6s.}c}N'A4>H̳_ >!%C@Hyy![>'Zg dAb~{BFB*_^"/~^zkB)46Ŵ/lœMEEhXdKhNa'd@@Hu3B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!BeAPfIENDB`tdesktop-1.2.17/Telegram/Resources/art/icon512@2x.png000066400000000000000000002761711326245125100222160ustar00rootroot00000000000000PNG  IHDR+ pHYs%%IR$;viTXtXML:com.adobe.xmp xmp.did:1ACBF4C4D24FE4119247E59A0A0429A0 adobe:docid:photoshop:743a6e8e-0638-117a-8f65-8cab695dcc76 xmp.iid:8f964db6-7fb0-4916-8d8b-8a96ff3cd37a xmp.iid:1BCBF4C4D24FE4119247E59A0A0429A0 xmp.did:1ACBF4C4D24FE4119247E59A0A0429A0 saved xmp.iid:013ff985-91ab-474d-807d-b60bda015c0b 2016-12-19T19:15:42+03:00 Adobe Photoshop CC 2017 (Macintosh) / saved xmp.iid:8f964db6-7fb0-4916-8d8b-8a96ff3cd37a 2016-12-19T19:15:42+03:00 Adobe Photoshop CC 2017 (Macintosh) / Adobe Photoshop CC 2017 (Macintosh) 2016-12-19T18:26:51+03:00 2016-12-19T19:15:42+03:00 2016-12-19T19:15:42+03:00 image/png 3 1 1440000/10000 1440000/10000 2 65535 1024 1024  cHRMz%u0`:o_F@}IDATxwxdg}9L/e[+.w{48;@$@$cmZ4sh43FҔjU9)<fP@P@ ( (@P@P( ( (@P@iq5jv{\ҿ5:Ldh7{:Л|)ϲR^?@&hܬ -$="w ^P_Ph7[1 ^[f@9Or@5y*poB߿ԿZXn[{=ۿ Nr _VxZ0vԙ5ƍ1;[Hj l'n,+(ce%ٲI2dd$[1aIo17.y)Z$^G7lC#/pcUASLћx096ԁ'wOγ0@Q(PUa?WԞr [`xd,{#ڡs;daoj~w|*PhQ@(_H؟{CQun9ƎmBb,{ֹ2𤛘ߝ]rw}3mB@Ls=g"N#ؑQ?tXSd^wrɑLwO|G%يRE( ؁ЧV?~q`*c>*(0幉{ɉ&Gw~^Lwg>2 (9H7Bַ7ċNrjO,Ңa71دo~'>}o@!y(Cl!?huvb'cٽ8&M:91ݏ㟹垱?ݗԁ P@~Km;=:~>rʘ(-'9&& br?wޟOh~J  P%mr3j6n#YDU-_#?x绿oPgB(PeSC~g\/5מzYp3S| j#9Ahm`] S^2ϧ'>W!^ Xj  P!?}M姏 tmcg[.ɟ'Gob@(@p)䧿m9sH+:Kƪr.{܉$oܝ*(@X_)twol? 3o - Tn9KN玏dr?~⮿@1 `BR\Z{ ,,,v'#9|G2: +,P7?7DŽ:/1-\#g)P@֡O}zd5WӴ /11Z*l݉:O~?:ohyS(@ j ͗T|ً.vWX2&DXb;9p?~PZ ]P@@BC\;xSt+c,*L?I]_R @SyVoB][=ĹgX2&B(b;1O') .nz(P >CQ{_}PJVPw 96O_&džI! J_*g/}]mcO7AZ@7x7{5?5  J"2.?Ӷ?É_c1()$~:?ן (zKN V;g K=7M'_|mBKѪMc?ܿ?O~lJVb (|%n.i%{玌ysp2V#- fwwrtkCGp@aB!@~ 5g KoXUW|q|'Ѣg)`j◉C~g0 RGP @y@ioƱgO1QZݓN}mZ+P@0KK=4=8hM(r:1?S{zӯd\G)(P`Y~st)/CkdLծѡ/ _{/xDG0=  Z/eƶZZ8UVڿ>y'yDK! oM=F;Zc٭&J% ;9|wҝ(PZ07ݽ-rĉ73dLRxᯍZ'P@@ymܸ?xUY p'}|z] t)(P?mo^oxq|ӒIS7mO{(H,P@@iP@za+;d'xx6&((PPߎn>5ʏ5Xv- ȫ&?~e?/ǵp@  bO K^7u_i,,dbpɿBk(jx @IM|K<Ʋ7В?QH-7)פ@@|WkN0qӻn "` }䉿s#K2@!@x)ϻyNM+eЊ5:OWwwG -P(PhqW}WXʹ"` {~9 L-P(PXN`/sE[I-][|qeӊҨ$wN z#_=ӋiRT:[A+JL<4OzM;6P@ gڳn~ &eQHL.7O H*   _2_<9wܛ-_ੴ"S?=x-;'F2((P2g[?}mm;MV |IPp52VVT4=Q (%3+럯7XFZP]u}O.F;>@$>mI}pqw"-B@bף+>8oJ  Lh`~ꝪLTH7-ot⻍;`ߎߒ4욍׿-v$~.@p#?6wo *Kmgy.um}vZ'urhHl[R  5] ׼v;{V2$GB@BÿY";㋃k,nr·_w*0(P4ko<W+}@rdž;yӞox (lymnDPݯ̣rS޹3L F|>ŏ?GP(}4p$2D(mu[7~Wz z`yUյ}km7ܺ1G`A"P6SR~ 9G.um~h<7_-H@ ʵz S!n헾ПofBGMf,1}3$/_aJP3# 7G՞z';KL_Ir?;}Ǐ `wli?{VjD R5( %9OޏXQZmZ?rgXSM)@iU?KL:VO/,{u槌r`yģCG
 .pܗu{Pٚo𗲯͎?.U&&^uҊI%B(9ssv追@1&o|{G'\SScJ@ߑzG}]dcr5=^#pq@;0FX[L` 6K VSb(;mgN SbJֶOz>Sd V/ܸ)tԧ~wG@1V4~߽3}TXӣG,*¿7>#X)+86on|Z<% ?ĺb̦n JMM}EkV/K/XoEdC u۽/?_ }@ZWk@=RmzZ追w@s| m>c@(e%6W}|;v8lZTEEyͧu @/}-q|G5sgp @Ʌ\|[?[ c-*?[?_/HG,PJ9)'Ե5|g}2O.G} um kH-PVpڱ 9 ߑiL/mj}B,Nb~-%]XN~վv9d/Kۅ?ӂfukNvzh9,)x9K)$R 7K㫺nw|S/ 7c;ݵ^Mʼ&Se(Zo~cػeZ *U=-sq@`5ÿe$lj7}o7wW8@!E;޻SS pzk /_Si;?ir+N40`YKWkPYߤ/|P_q{wtjt;_ƺ?=υ7~q|h91o[_8>CU ;߿(vY6BB[bG#`JΖy?'c"jVL$s][>ǔ}`EߖX'~fK{fn<X}>[@دkPr`33)7[twocj#9:?׌1I a;nvg `aO?;W{5G_ /;{f۾^Q#ɧ̣fUa-WeK@ |rիZ}:E3/xw<`|h5a|c[v>e @e%¿-~ͧO+lPE;`Gj}7;dʇ3~]b̛;楼ػh ![3Il/PHڂkMNFP@5m`| mHͳi5**cWo~Ϗ>?>@(o\_C[wGp왴@`t{ߛp!-\"*Zկp kTssLJzOX@us'Fw\?vT ]`ǰ&@%UڬR;6?C$Y~}N9w@4!G<9{Wi5+n;=J2c=Yy2~g?HP @+=P;c@(?{qvX}wPJZ (8?z"E̔^dS|qr/ߑXoˇ>;RsH85蔤[R;楼/>j%+VkCf*+E@1ؑgm }}N p=#@X?7~{.uC @\OoGuMbsb/_IOy P 4S=]2o_m~5-m~e ¿yG_7j7w볕y=+Jÿ6{u6+Z`ml㟮HfR`ߤ[oəkumD[yNjT GP`vwtgeV3t5&@(#+CO]zͧl=2VVpdiOkl8l=+ԬrQ+uLOmI/xg J&?oץ k|G>a|i5l?Tؐ@P}IeoKr,ȷ\HSU0r~Ai񢀋Tjgoz]E\U[5=]T`֚S k_9¿s|e>("@mˎ|Ox5Ol13]:F V#/7wN\;o/ESd~HP@YYwG~p@C]>sߟd/P?לgvO 2VV@3&vg6\kX ;xÅ7VZ S얆W"({i_]kTΰܷ}WPl (o9V_Po i1Tl F?x2OXP`VKo>oXz%9GͿ_JgGsOfI#s.Dq1S$֯ob?T S[ jJ6goKr_M#Oj"v؏vc۔{WCPn?}iK[j|UX}iK[xEP@\ΔcPѡ^ h*Tmu5_O}:;JOڢVw$9ݯ?t:-cЩ=|,+(P@)+{ox.-Lu=="@z!"(dLsZ{=wtjHE(;gZo5kO?1Qg~7k(LkP94Gr+/yge@Ne4\O{^o&!5 ,oζ߂U{n,xZ X"w)z)v3 f?J ȿki1 ?v#_RuQ@ =}ӗN7w-WokQ@յԢÛO$m_x)luǢ]ʼ;Ʋi1`5nh Q# X`[yD+ kI=oV- X3vEL]a46îi|Q;?z?hᶀqƒJctǑ#o:_w um}2OHxP@;XSI+FS[31gŕ}1@,= |i|J5k>NI>e^`(PrqbR\`ӻvZ X]vg;?Prw:_cmLl载U?VG0YU.l=NG?q|L2!X+&;OÿA-K Э]0,+Kr6]wKk\p|=_;S$3ӿ( cO=62WgbOP>Pds nN X_;zB3?ص5T{Ό{3>ZTP?wQyݯ-?x,-H 8w1߮0ݩ ?+ƛi187kgPc&`W o7/y\,χ_^qoxT TPyNO-f CƲi1D+xK>h>-0mkQ@`ѰIN;p ?J<O7+l HMUgvK޷z (7}[Rʰ+-EU"eֹ`)}Pi1L9;65? cQPl[_k|h1j Y@MP]S|/ԵwT YV D񁙿(OvtpAMz_L2mHr_E8mʾ+SS U5@W[߼Ȋ=*{V۾Ϗμ)qh6{*8hwU?Gp󛃽Ǽ[*Jw5\͏H`${@geoKr6DXvS^yo h*E s^YV;pR Lv8vim]`W*QkT">'v$~z[ݹ}iRަssz$!T FTv>n@yZ lƲZymg*s @ewfؑ7.S(23[o9g^]w`Eu5FZ .ƶ[{_iT#P@1֯oGjN3Ɏ=_=W  Pن xqm;Ų@53? kWh%@TFTV;pl5wbl`m7*R;c?uof IN|$-^0=[JAU}Os IVkl懴"$FP@齞[9[pDRǿ_I o XoKo\NSi-7YoJ)d ( X?+cԟw;d0- #c{;xA,Hy oV(z> g8 F/Q SZ0QxqM먷Z).U`mG"vZ @>mlxߐ%) 8^>iGk87\O}$3oL|J7 #}oA'z9 D~;pܯSp(`o;Yq4e_|Ԝ!s_,INW6 9zMP>~ 2] 5Ͼ9koy]/&XQ[嵇[jZ߽2UUe=OHnzV'xx m7gkjPQ72 geO21vKo7O4Yr (`%{V0 @QCc |J[~Uj R@eh;|cskԂ(i,X>U+u?wV +oX0b[-mϿ;pA?2XU%-77ouj_@kXMNm Zoxk> P@Vsoug=M2OXUkϼM$)π9ŴӭP Puo Ȃ9ύqd,`ME2mgKr^nKX涛-^ 5ǀgۿK_kp3`=[onV- Hve7?+BXb//xKE R?߆߱ى_Kk(vm/x- ( "\5_'cl@icOZ< JS p-Iv+>v .8fR ;엒)9ƛy3tߍ}?i#JQU%9:Q=P9f&8ʼ+\YF)Ch:_3`4Z @I`7|l- `tRSs[PJoI/Ox({ÛHjz*=ܙIATm-勗Zб4Q]}( -E߉7]P][_9Swl}u:_wՏ/izw b䜵up{o{57:ع%ţ0@E3i'‚Ɨ}9L*8/sM5SJ|- M'F|M]/#_SKBNd) ( "}~m/cM4 ;;s@e[%5uLk(gCG}7FP 9uƶi"ezlE^Pmy9:k~1`- U'7Yƶ[h"~l}]JgʖRʺcKRZ @%5w8sR;Q*Q${>le}r S(XB<P|/ vl +ȡ Ds_mL9l䖐ƧQG~[ai"ixBN象If~gL "n<o39My$͞@HVj],^7|]7qC(3tYwT;bZ:6FQWo/)m=cΣCџG4w, M/oԁ')ț9=iz$Ps#6c;k6KRwܧ"|XApip`s|m_+{뎯QZ&S7/9.G.hGoL4b~K/V뎨?pPwA3=$6&r #ҵʣeY 6#ux~I .$$pQxS`u1)-!ڭ3sxJ`4/oz]@[b-$Ozw_jW9dkF}gٷ`/yV'KR{̧=Q]ӆ|w-mQx{GxP=ǼXҿd)xO`zRg/>r± P[3+:%T-;hV٧>:ċf''?b&NpS@xJ`I[O{)P?=?}Q]!yw-=Y<4{&xPBGRInZ ,2<_4ۏsi-ٷO/=^߻K?.뎕d|3[ /$zGd_BM ڱ/z1mUgi{_LMѩ[0ԵG}E՟ұ.~Tҕ;ÎmH'4Ӛ/WuOi %29^V @#W4>핝iE#FP({Uzg=_h";RG7]g.lӎޘ¾ajbOvԞi((¿%ɊԘSt =|{Wvԫ#ZYO.95MDzjL( |ʸ_uoV̈́/SNk֩&o$a`ٱ|rR$lob>_sǍ4 45h{oLW*)Rjj'9(P|7Z]PR[W# mԅ+Fl|G1mtiOT?we^zL:7KR[qqE<`~$V+ |Tch1AΈ~Cٖ[=(z7HL~J)oRJ+Ǖw2lhvAkj oMaGVol]1J;Y!IV(zzso;Rw` 1+@_UeQflm;40{ x z}L5&Vzݡ /y*$kXZ Tj};1[Omҿ#ҕ1]Uȡ[n002.FF^&دp},n?͎Mr@B,lM2?07e:N  7=o[Cw]QO+dR'Q]i4**i-m/~Y> ``@NX޽z`KG?ˈrFG7 80*ד<#Ksi1,<훍?:CR7O҇ݠ H0?ޒG]?kr&GhhT۶沌3b@w+͎}֕41ΨQ똦 E&|hl&,u`g?vrdX)8ϺJҽ2ZLZPԱM;h";Ҏ*`yl`Tk4݃|Jay jhxT;x;6u"@L ez`E98VZ }z}N}6]@gW?W4<*Z7Ӵpvf@޿`瘫h*䖰ҏBMO;p c`6P]ۮSe V<lrbuDT~藤cEQ J 3;XRr5sbb\c-ŀ7eb e^"@,%jɲcs o 9z:}s{>wqєna6Y 瑉P q+|[K F7Xrg{2:=qWQ~:ɩIq]SX%:0;,:(U ĔP^xG##Ggb@^8Sd]$c/es ۺ}N}iud FeM#?DBs0( lh~'4 ҙzMoa?%~N!11XC.*Lhlf$eۼ@(K֞+$}K ̎%/ǝWcbgLO藤_;zc7Fz,IP^͹cbG;fuJ 0^4yze$^(cMwF}s[;muߓ<\WML" o_K/ʔ& K'flIIg{)!6=Q)&Reg?hxPV|-ݗJLnK*20+/9J.KRkاǴ7 =Q*48ɤ73K[+/j;_|ءi3ir 7oK9f(-юNn PI`Yo3 fzt V#C,Wׯj4-iU誾eހuQ!i#va?S-uA]%1ضe}w_c$19l`?{| jel.ue 9-}Ep4k]ӕ}q24Fgo9}eɡRTrPp'G\0m:_gl1Pa跌maΈCqs`$Áiz=O gryr!^D 4\sx ~-F,lkE4 EYrHW̯uyomP?=LA'ZY#CmG^ V @]j7}1G}4摁y27<,?MLibLEǺb'_V??ߣJL>MsiO;[LhTsϒ'ѹQ=/[YY?@Z(' M `QžU{5C`Ty 4=TeRԎ.)єT?zeɤƇ)H%y弪)8 m~;;P5?Kۺ'7ըORTJf^26;`NC[ 1+Sr1j,d?|ݛOeG9m-ctz[X;z:#,?ceL1 ϹsgyCGlcXv7#2*] Ȍŀ@泫xg@,iM/Btځzt`T,cyLz?wd ޒk6i?X!#ynRCP tn9Wϔy T͋f` *&gO3]Օqb[wSv>W@D_~_Q'M q&c]y桟|kb;,J’d:<M_";tƈV-ד j|Mi4<>=RC`7:? -[xKƍ )kM88f|gJj #Hi&Ke^URX0_6(/IaGbW{y?щ$ L<?W.0X;>VO[Ti_4߿/h'rz-/,#Єv u0+є7 Vb?p`XI }'0 @^}$%QUjtiOLq5hF̘rFƧ5#j9Ζv3!Wz<պZ^sғ{O2dAE_y XпD['qmg,dyҾCc{hLW>1RGcLk_l[2R?Wu7dslcKe/㳇Dɿ)1}<oMjrdHH"V{SY¿ R|屒&Wn/m)bjXǵ\wu:(p@]MVsj iC]_E'3[|R,B7y~~~߯8yE牧-)`X݉pXU@9Gj}V0|<3w>F냶.eA?xB;F4>Xﳡ. ?"#3][hϛE|oV +|,iVXF 9֮i%˵@Ū&bM3^U4*yΈM݃?4Ǻ:RZmIWe 1)EBaY^PU>j]oe_ }"2-VkۉP?_t|zZ\YuxtJ;5 [QWSL0!i-<:0I3;+3_`O/#P@$:KNh=8ctAC%vĪ/2i+xV?"/~QQ9lEp}7Ob@5m^ݱPX K1MA">C OhׁQ%\wտϲWBikZ78>:W]㣒B}~B ǎQ]*~!jY`sj?T`3m]׎t:&\<05~VOK\ Yka+q#9JI'Z ploww)-Tjd#'\t>TE?Xҙ#FgYƓиvM2w^*%\`bzAMG);ww+"bEe q#BkG_\WTǜhɤ?Ě}hЧl'z4?}AZbmY{(G7Nh iG_\t6Fzb`X~hPvXg'K^jf`"Ԥ$ON0@h+v܉Ie)e$Y;^~!˜氭5zڦڣ)Ay%VWkpT{7łjpW+iL>F c]o JZH鰂=͝J?;&ϯWp 8Ԙ+_($1NiB''^ePqЩk9J0%w'mS]@Woѥ=qlu7N!(+ OSd\/{?+j?tx^gɄfFNRp]˱mA/(npR 5[ĴF[ >X?z?QgcT5a?/D 1L&vАLhj|T`"揘`t nbE:B~^#ѕ5:#"s*&\<0u1j+r8J̃Lh\[^f@8@MFRwIM e(@ ;uIe2 1>wXsOc>m}1mpϲW(`󂔠dX$c%W1}|kTܤc#3EA1KowUbմ%;E G/tAWTWu|KP&Ƨ3m[=-q|Rоf(%D1[Դῐ'*S\WSc0}زMySkd|kq*YئuIwLJy38cA稧%.MI=qe<2\??w_:,Bxcra{O5kʏ7[,+a aGƵ?8 M#L$|if˒62e/#:#~yJSvV8YU``Ej|?V#9љ#&zzGglbEɀ_y3d,ب@ 8>KL 5J_qKnꖱ; sw6/{b vX%褞<0)]6PGctf8M{p$#/uGB-T45>&'eS΍%]ʽ`ER9z3G8#~.kGSwEJzy`X&KiV)#7=[O)LOɲ*;a{!(TƮke?R跌tJkX;:3מQ%ϵֆ”M09%H"@5q7l+u EO%Yv8(8u|W+zcш(y W;521U2][}T QF˔WCGdd2o7WL_?W E*bGj*B' kY20 @^?wW+ӎNn ʂ'i1=4V@U=:c F9zƦ\ɶ%c3_;%&xY> UΉv`@aM"@v9`y_m i{_\uedB;F4>(6F]M1Er $}#O]l9ru#ў%r%kZ_VI&zGph g$5]׎ 'G95rw;5!yl?tT2_fɓ{Vj?Wi;^߽kS]@;jtYOLuA?ByJvщ>[=qlEϧE[ĽRX' yhÿVY\ 1h(E5s=PloqX_@&`뒞hk}FDY;8:'*%>u7Ge[J02ԮeB)\?E`}@(>oCgF:mfGT~M%]dI|_]M̧FIxpLXN. [:n"!yOC9%^"'\To]*.^;8=5~mq5ʰh\{uKF_ !*eyh<, }@o+ ΞgRX09_[A:n")CUƮxqȯdJ)zoI2? ytlb~KuǵFG7YAJjF'%36ĂjbUk~|3AaO 8+ϟx}I%W9"Ǟ7VQEWl4N ;g,#֎SsyӾCcYw\+-!5ׄy*00z$WJ{ݤrA'*u oe?r ^#5q]_  32΁aML%Klk!ʈJjf `G\*/zMHfA7Y> Hj"@Ű=Z<\i(rg|IΘvְX#@Ioij{_.+0pFƦl9GC#9\@ZPGwKzT@Ok[:9/!}5 А' i1y^y8c&7ְΘ6+z=8ቲٛjBj "V{`f@8i>^"pg3B/{X~_QȓNMJɲW}=aŽ;1jȜL(>3"c";FR{̯+jtE_\">UvQ"ZQc,ȋX&p`LMxX@?_gՎZ[i}hOwxLʳ -uZ7߰g2B1[/V# LwȬ dR7)GXȆ\9J^X +a~ĖƠҞ 2Lj&e;|zZٜXȕྑԸtOܤSJ d"%|VfU~O|u!GueX v&秅7pU3ϒx֥qmֆ ȤQ"R !*XN',MhmclzTP:͝JoYFFgwDˊ!R&v hhl"~ƚ6Ԇ90G'<ϣAU}*-̾8vP5~}q5j`h\u+#,o 1gǦ̙"f @`5Ta˽i[ @ԏ-]׎DGX)WO kt"Q!(;{ d?"/c;FfxIҳ's`Ͳf[Oe~cNSYY& yҾcwhl!^X s kS.Ԕ,"Z3ySJ0.t2=--yFWŵ!1Hh&;ٖQgsLSP-ݛ%2W`.k{=xR)sRI,J |]Mίʝ)Xk/^\眐k(1(п|.iG_o 6?`Y=Fu`h~/ǶSgs} Yta}+SBfYwB<7Ey3y~=wjJ㓱(z7*.>4oJ]vΘ>n@1 MinE^~VwsL~kDB;K'J'~+$(MR+"LXpf:ZC 5}5W{OcEH=8t|.8s҇{ ְjuvm/yp2/@O`320mg6Bi*&/it^gL:5"( UҭqO]MQ\8B ޝfϕ D].ls#y3C&-i%lb߱(/I4OQW\Q?m&v hx|"Xȯƈ,VF<zO'_/$dْ)ۦ&I+eypfZJ {yrtY_vը!j+pJ0ϦTK{j0XMi1?<.GǵԆ񂣨7"דdiɧzY+Opʝ;U.sonpV+  ~[uE_6iH` L$&{M>h/OYfo,td a]uD`M$]OjpxOƨ!0`u?3?/YdPmhGz%M^?Sqr]yTgyY˶S[f1k9\"= ZؾG+omΘvur+ km*:<6Y1RWSL!?]a$)דK`ysɒ;RYP1U A;-maA?`yhLcېQwsL6 Kd?zvs UKٲ⪠X:}M/u9qLk==]WÂ~@ LjׁO&wlȢӊ,gRS^rg.e 2¿W`YϑW_F~_[n 뭴s~'Ʉ,ۦm Lzx|,Z6 LU#Im<RO8ቄd;7Ko}}?,ϓ""ҁ-u'Jޙ;_&}Azkt %'zzrpDG&wo 9 w=uӂ K%{z:EtH TW_~KFmhGJYЏ3qhtRO*two!+iῈO_?"ɒIU#LD9LQ񡿀Kjou %m*jਆǪ奔1Ɔj~s`xfl!&K4Pdh gC1b~/s:c!".@Cr껑Yƨ)h@Jz5]02m[?h Z?}R4! KY03-|]/l[ưԢ/]*RXLzu6rbCθ_WQ}5C )WOTUeWos0`ipTIs), ,e, ,/?Ǘ)֓EEdR #қn:_,Tw:0)*bSYFt#juR~%ʓcU߂c)ZpϞ!))G'WA@/,Kb$@)X'~AGO~bt2'516lu5shtܿgxqT#+,f hY-),FŞAIy~|x=t`h!wmDi^0G 2Η&Z*a o;Auf֕i ujOr3<6'G4p"A:2K'ݿoXǩe/KZ*zkM?*loY_~u¿5[o|V/ Efru*%7i9@نEuʻNlu:3&>3P^"jQjo4Jң#r%  >ϴ_XTEZ%lmD8}c]N}u (CF&{pDIiuрZ뙲uޡ!O?W\dܩN$,J`f (gqqdU! ]F52SIj\ =SCN?O'WBP$7YE+K˰ :Wd|¿2ְAo@y$8<Ʀ;LPk]X 77ӓ2d%/]f9냙? JE/Le\?Ma~To;ݗIrm5U8#*%rߒѹ]1xT5AdRht2Ac7GU(y CGfKPxYOM^߬Oܗur2QI>_lWb 6&U9.AJ`*c;F[s53N(giߡqYƨ1hGc,a) h ?Ὶ=s`lgv[ (Yz֖z]^ !DBF4H3l˨)pk}{CYf$+G]Q6g*ꡁѬ?Gz*)w4:/a JI6FX '਒.|hfRGF5t%kkl$ ?K&0u/0*^+RXi< T2#Θ^qR `r*]F521Ecd1 w)e?S¿W.!WR? ¿W_쬐~Vs|"%w)| >37A!ZP{O:04}䲷_FuрZ#Ku\&Lq+z&7E}\aOE ŎDE|9Rr'EtQdRF4>1hR!PC3wbO" z\qkȲXm o[z:9DʔywpLCZjjiTɤGƖy*`1(uAҶtg,PFzr`D$ZQM-MQ97DҕlO'/}*@d%AHS;b9<LO9XhccT;ܷwX1 ֑2Z~'y/Z?s%2 J+^~$#ϛ`wx ,!"-rxtRG!+2h)` *0Y*<߬4a/^O|1je QAEϡɁ" ="DՓ#ck%; Φ~@EdҞx.# +E馤Lg-al({hTIv)~VWST> i"ꑁyY/$n"n-C-㢑XfE_c,^["5[ydʽP?<#^ѱMa369ԮQN?Φ|Ecb=D M<=p")S~Uy_Ϣ𿲯Q_gI'O)mqJIi^)1*uMP}x>4Wz  (+X آ v hb*Ac)icCT2T Oc% PP;"ˊb@ <=44Fj#~m]3ƽ{Y< .  ,xnEu&*q;GP2ǧQM%4FAօiTѩ{_F'Y<DלTQДix*IW{$Q暰AU徽Cr=ie-H'=Wư,<^F)W{FĨY`}ރJ.Q`_nC]Ds e9[ W."@Yϊ{d%#ְV&ӱ!. X/݃gkF|40$yfmG`W]zx׻T1{+y&?xˈ&(j LGg\5 */+"LUҵӊW(thZ:04G&/ou6dj/4p.+;uɿYV h/A?_tz%5/YIF>'=t`\t7Xe[M߲nΦ~@ԓCO'jO-tț7^uGa{Gt1% ڹMoCeٖ =|s Ja-e @i]F`͍'\!K0X aa?<ƶpluZ} O'W=ϝ}*kJ[\wXOcjX (G&hu4d["% {fF Ȅ?g21­ cC:l o#ܓ:-^X4su5z}r,]WO~2d"F521EcAS<ƚ d*>8L9+H6֕>6.'#Uӓ"+:^9J*,~ڦi,3G ]Qc~r׾cry_~Vs]Dd-g[I'K0uMNTB/vLdl|MskC)tܷ_5nT_m@H`dROT(F?HmQl?l;xy|& SE=2|dbUu2/@se]#z?=&<\ozk"fn:` dҕ9 >yS̽Eo|t(7(b@lwt0BPUyOKN=F]yRwIu׆ݵA eepxB{j7FeP{v$͞;R+RVR=\EE?)+zV9{YW:oO`ʠ`K+㥌=4>vpLN}W;uDCXzm7JTRG5:!ic}tM=*o< DzbbR_I)'y|;UÓ2 R0'yanq(XeIIw2ziI)<40F_>mQ]^֩9–_(ٓƵи:j#ևY'X4/d?&1s(C,Nr͵$y`4em^H;=#ݳwXg! u~_jmu16ԓ#bk&DC+3 fD tQ)vQ`_Q@T%rdI;WM]N<ɕ_:_:wO:= unO~s=O0[ڰȟkplJU?RLJ˝l; OnrXv?1h D{Vt>a-u*-%CDTr1@;Sȿ04wܧ>OuAE :-& ݃1Vl[h*:@L$\=tz : :b 4^8gQ#%'5 _@)2))}㾽}{z]^[l1&И\Y-ϱ{5tYN_?_-?KrdR T$;&b7ޑI}V[,tQ65i*69ԓF561@悔)"u:wP_' U%yCZ<]9KNpCUmls`a`(TO'Wc\eɝe ^ʫ#qCuL?gztl1 衁Q}O訖.o}uj iJ8^\O{ipxXCZ"4F'<˖ǂ?rg)qa`w$Wy UB `l|sc1$r%J(PB9զٜsC TwWuWtJ33syiny1i+uq3 dZ1#g,w_Ze.Liگ>ir1Pcvfv)6:ֆG#@lV_l^5vJ)Q 0R&ϑGDgopN~1y'ɻ_JzɲfMm;22h3XV-@ `G#;]XA1gs=\<3@!r -ϜJZ,[Ӳ yW;_-o9w,ꢪy1ITTlZDXTNZJ @><'du R*je?W:M|^/VWA2#o5AUX^k/"bќ6 ڈ;-Qd@SH FNѮ%㇧S+$o9wUgZ5&f;2<H~PJz,[-)/2Q\/&"Z;UDhU^է̝"Y[Lt^QڱfT{5? %Pe˃'Kv VZ.!} 'd|:&NPreOJtDA?ߵ~rm[^BcQx9&52ҭ%0}cr19yq]W\  Ӗ7&2SIo7u1͎ٔhG#a"((>VK pET`LLe׈ܲkDN]#8.cLer6Ny ٧,.?@#68qC[`? /"2hΖ5- fh31̀HRn>"7l3/wpUx[2<i3 '$Y&!^4WZ#Ŀ׌{uVlxuqbt"2J%:]JRJB3rO ̓rA9 V_JYA˴ ؎LG FY%gY&!:b&aɱh#Ja 7K/[[ ֺjᯧ)om}NU oZ j95& /3" 2lj sY,2_Rm<3 ő\1&WlY"`Ur^ۃ)#Qlh wV/" elꖓs- Xdh:j_U.o NtvD$ ÚP3@)jOGD0t yiwYB?uvdx**s1X#^*HƳ}hN?S":D7W%_Wsb.9HC/-pf'=t3o6^ZD5rl@3U%ZltE;FdHX~挓/\-o?dYZD2:!ݩѬZ+n@ep&}H nP 8_5@_sC/-b[FĿ_˦S@Αؿax{; @*&0o6DlM:I}jy'˲lJ4NkfeEhl-k$SrG#;Q$zsD47̉D0r+hv3 T_3 8=>#=!Cp y_)ɹLѨFDUKe(MšDM[B#ph`߬ @7Hջ  PD+-CN2u6[MIҶ#Sđ)Y7?g$=-ܒ-n2<D]Jl4ۇDN5GZZ¿%%@ fbEnݤ h<3@1ӒNC'dyOHUםBB-rrDL\&0z,b0T/"ĈpnY%NƆENNf@ HHp3`.i{crq篖W\5. dd**esB4M3dșkɢФl:G#;]D4c8@ @ϝXx%@;zY3;(mLLuרܺkTN]#<SdMGe&of!Y&]Y)g⢍Pꜰ?'-mSbA4Vоf@ab)[:[@Ph$!oʻ/X-`\1-fIe;MDOwH>etV2G#)eS7Ȟ$ìtQEh Jl\<(WoN^,JXTtJ8n6{5$lRWr%d_c3)YQeĿ5xUUg'K4`ݯ}ɣt0̀#SQbcL//^TUWzڧ# X6Ku,CY^+:/4Xgob?8#Nәl("UdPNc u-9㢵ψ2z:/(LEmosT"sry,k=i2<X"IKzUK-$8*42wus׽?vC7d<""qI3}I>sLsNm jhrt2>@+@D6JheMwÈLdN"ۇgeg,?`y'ˊESE&qfӿ)YWN[h!E$f9 :?gf&u2n6pm}61=Y(QJ,x#JP&9rǑY?}T~ V[=Y/>IK4oV֬X,/b Zl[кw??Ț1;!iQ =a,;"x,E2D+~PjH`OZgOɳǧː7Ru*y'Ko(U5Zfb2N4)JDN=y\J@ks❞[^ QikUW*2G@**:Kky/U&ӗK o$S],TJl7+ׂ2QnIˑǏL'eqwHW&Seb..EkJU @uh&-jUu2 L/4mWk+aF.ox;4Z#6C6f@Hb."c3o&zCr)˥+`0&ߢG#Kİ1%oSYڨ`;~.yt2J ;m GZ˶$, 9.`Ȯ2<u d/".МM oV@,|#t"R# m]"nFHDiM’.9{r )` #֜F#8qN/oSehq#PNxJRQJFr4@/,VlMZ!1`v[GJb ^F@Fup R)3 nk1eereh?v [# [P|0GD4oKU7B@#X}"9mA'[3~Wֈ?giG~gĶf$E#nL V,}#a>M h8e9yY|О2N!$]G#y8L|S*&/-1(Uޛy8QcIV ڙKزo2&/Y1J~R9iIȢ zόPJ@( TySWK _7l%+)x-IGcsM@h* @ .76` zT]eZܻ2| ;;҅(_gF1NZ5%d(9etQ|*5#Ŀ?iv1ĿߎhknPɨ`rJwYrt& Հ!g\ :p?у>mZO7$?tÉo7|3J-R#rEGYkIw`0:}ca1mGt97_U5P +X눔q=LUzk@/Q_3N|#nn+%?F'VrS3P, ٧ :ó"F^E/*oT藽l}g"ڽ@{ÜP);92I2HU`Ioft3sHR!ȮCR'F@&qƒ0 dn9k21 ;Ȏ"bGG#>ZNi0973y 1> `9s-h$)"*guDR!Ǟ/-۪ `;J.ֲ<}uoZ)1q[vueE @uhVR\@([Do,odD'GE; NqhP.X64[W.[@Wv-IF[7?)M&K'b:H^i՛H(kyvƪjy/uh6=3J̽?|j':kJa[ {ꎴܨED;EK_ʩS=1,}^*Kuө$D4US%-+?'Eȥj[:0o=59QR!#y4̠!d(9etQ7""sxVE"94^<-Ol+@}PY? o!g\P(ٚnS_6G#<{[/ ЮEB7ёn/ZP)$mAʣ+$gLzC -7j?iڊ>}~/cwW [g5T*Q\?rIشG]v5N8G.{?0q@&*Z-l wȊbd#qZT3oXeZ@W)o-KzArٯ}Aϯ,kcG"O^DrũІ&ˉKNzg>4Hχ*%}sW*MGې3V-_F KFlZWU|k )f’*(Ҟ5㈈'ws@]'$]8CiD%Gg9. ˖Gd1q(>lSK#( W!? GOX:l w8ض-""'乽Py/G`ޙ'۾`;# :ķ=7EV4MÝiX<Ks{N ó2G#Mom)3rB;b}h31̩u %d(AGdǑQm$fڲ<#tG2>og _{F8]ʝaic~mp2,p,ɉGk\:(ȴ;#gjs?s;K?5) Csꘑ(~yk5'qt(ۇfSRj^k=?.h^SE@{ FKU!ݓ'J-Hc$mujVZ$d*nuXp]eR:-CS^bMW>Vm>_3Qbj ylg~ґmEwwёG+FXZw:iMEd&8g:[C##_G#b& ptu%T#: h-#ay+Zmk~xT&%g`!PK2< \q+u5ń႞_ן,:M&ki[Mk<ա_K@[Dm]?ZeM(%Zlkyi{s2Iȫ^p,]y P# ̦Sk6>ETo=s`uR嵘r:eVUS.G4lֲ}Yv1mGvGGŦK@:85,4j}@g#Z= k1<9' \J:8/G#O?+#=Lr @sJDM\)&n_(e˞b GC5|EG#c;p8[Pp; s8Q bR)SJíucx% icۆR?q/}NwI1<ſP̙_P@cf4BYrd&.ce$MRDD κ淀JGC_gO{?? sҘw?e On4x""2>u{ej.N7GGZ:oG#^s|]  `}ʵ/KBː@sJ(4`UKn7-t`@?}w2:QY"F(ƒ],=ſ^:ga$\VqgnbqҚ0TИ)*xZD) o2"I[N 5_;hC2+=Ez-Cguh&ФNE(Syn$r#W=#u܄ F$q]3NxaMQO3mHeS>;|xxRJ"i1 ֌24e5P?tNxIi˝PI˖͇d r϶ٔWE':?8N\nPy@l+|9sh'={/!5NY3 r.h$.nc, Zn5*OJ WVKi)!ٔB'Hvxݵe0)P;Me.agĢ]?I*>'f?Kn44 ߤ,[~uHK/D\*9rST* D#7nru`&;'eN]Gx<0'ThZ̑{ :Y1iqh(SqK.#SmĨ|)[|)m~e;'g*31 JL<̍@#{v  =^OGgw1rP__xj~qx!m,`8) h$)S(sO]V-*0oh̡qցi.,$"[g_YLGCG'S Uy汝w0\)K(%[҈,N?0RmTJDWME”Ṹ|췛嚍DŽ X(&23GMZW GmQM ;[yZ$m7mйÒjܣ_U拾)v`;\m[d:䚂285#S*ۚ?AcTX`;@#m988:p.UJuH4\nEÓr˞QN/:nſCq?aΏt$2_IuG'o NLj<8:U.qYͫ` :j!?jZcף?sTF8~A{r0XR?5 |#~N4zҙ4Q׀v>l-r˞1yhn؞/{CO-+Uw~G)qP6*![ mol_;"%@2> O?,p_{[~?v1G(@v٧hRN+;( n9r!59^qF(5LD@HE8ڑOܺE&H e4L2t.c^:Khگ`N*uh uqv- i0̓r`2Z 57@KF|J7^/MrAI#A!C.@WC%oS'g5 YihVKN6;'C27e^lT6g_wRJ~l0++Yh\>uן+} $Ă|<0=2m'E1?,_ݰ`kUƲ?Z n۾Gc^3zT1t,u>̩ Mp$)/oZ\cX](35%ſ -29F@5r$K"cMG;uGdK|O^..[lɦd{w.G|8ڮB^﫺sP;h?R =xN ') _Y}D; 61,nu*h;- WۆR_gC7 e@I}je20%sy80NHLeRXUCR ]8W&r3A["b6M0rdrpllǏNͻGvtnΎs_aJM(+[@gL,)s䩃mo@p̤ՏGC'aO >b4)hta1< :]͇ttܪ8mהX;"OeζRXM# U %Z]P# tT>rfd8:gMj?iT\&0?}A'ϏLZw@n^iifi-[Fj`љ\7(q5ķw/}nIZt=U) c#wo j"bÇ.h`HD5ڥwi3'f]#bj'c ,hZ]MhWW2csێǛd rd27?sR 8<l {[D;uR;1ۑc[4_k{˽Ƴmt6%P8* ?6'&y`e?f>*qJ4KDև.ϙid`mXN[gÖ6Ŀtwnw}LJ"QӒ/?KYHHJ? gjև0I-F@|m9E~~\uPvE?s*gS w wɇnC.6"/)G#Dǟz?@sRP CZ 3MZ2eDW+LLo@=6-%?%@O rx",pSܵ{Hu:c87gv|]vk@Kᅳ{~S\NO&o[DĖ_ֿ}# ~az~cNtjIqhN^fiUe}l\>,ᤕLпJW(ui1 WR∑Ў hZIܴ핍ǧ x,qݶ([gR3Yk׹V_,K_zu" GP;_%"vKv 1!>7 V}q 5vG-):o_ZEλKru Yn71:*}_]⿮ ^k=oHRX_e]' `}@f?%#1yU{fOX;0#'x9GUٽ`Q^ϬЕkyZV0 ZH)ZTD>|&[_(3P[-miW/z)}>\ ב&ݺHDTU{Z0 :DnZfj?Wjm44[_׬EhB7/'t]|PqovR@UR dZm-c H.l’'VSv'%I:bRZDg3<T[{\MVW=ι7 uG9n2񏬂:br8%+C!^kX~mG=7D)KP8Q wc-2-UZſ@WMmTÕ>{"ptł e D7ɍ[Y69grZA=7zLF~$w?? @czۯ@dױp: j?+ϕTi.`*fʥ}rx:V L;) VIۑ>_>wMX\j D XvޠcDO?@kW +rVry2/RD6T@3%2IĿΊK1Jj2<4.wFQppjڈ?JGnzx?-+ {qfD@]D)TLbIɯSaQmdT21|T?A7B秇g# RRo\L巛M'j" ΈW?@!ֱ78#1)w?`Q~'ܭ8u(84i={\mH-GuĿ<Ժ0 7% J333wn阹@ˊǦi?X~Xɉo}nG ?? k8#Qj@'eV"a40@Ç'ֽbk'`͟.h~\몬 aJGzWZkyȄo6ցx>z#-u| HG{@kQF@wi+9@EJLD%`k-GN_vo/b"BRoX8.zj1ql’#DD[-߹+g^L vFJPѲyxaQSqˑ IЬKHon6FwqܱM&I:up&q#iot&"G_w|߽S[I 5AL Il-η%mQlULߜCT@hYlR>x&#grhU Sdo!vNh]i^Fg7ɡ}|7QPsџ(*UphD;8p8)55ſKu ȉpEOD۷뎈.li~+;hycnrk1`]* wm5)1i:e&%+&O4|<o0)$'s6·|vn?@ 0 <P_O@͛,^mCm VZ osբe) eqRjEN?^uC.kkpGdݱM߼] j5!4mMwjoЎKn u"z!ZMFyAn`VNPIۑ_o CwGy %@GhѲmpZ>tC}JO}-B~8BX2wU=zDžPIPgZkQJD %h-xفήA pҖ \<؝+@gx~J=X}O\X ^}m`WFhGkGDb#uVi%lݳo| B*e8ZAEUZl_*氶[yHyW:Nv5^|3dPdvgu]p4K6(q-Fy^B_BZ>Hw ȽɦRYh'#"TʦTU:&k+xA9M|Cy}*S«@~GfrcW2zPuFN/*3twv#1tSLB2-ݏmh|h=2+&yAlNG#HnJqlSwݺ) 2_In@e.yٛe'S̛ !"Jֲw<"f<PaylS8:Л\.܋F/`1ʎ/.q[@~QD뜔e#Z(1Ju- Zd.n%wΔO|7)&"*[pC{*%+iG.AɪvruL++H&7jy)Q""lpx?JOw%6.kU x sBGdny]Qn߼YNLytf҆͵ѨqIlE?.X̌ev&@ci$d:aʵ>B#k{`lޔG#] qTȕs}L %<0]@#SpfǞ=tv V/ &&( (ԍW6#5&&JJD+yȸ?1Y\sH֞K6_ !H^@w$ QiIvw/ aZo`:m̩:7NY>h;MKGr# N\mwpMX{!{ؽ RC7P"Q1mbˀ sGW#a,7ɱ(Cy7GC'aO?C\0:(nXѻ~r86 +_ZĐ>wTfa0R%]|SyVwJ xX>w0SwGCG8ؽJ wsmC}]a&.42EttĒ=Rme(E-[6(r#^G/PNܔK 0 RIK^{%f'"Tj\]SGCa QqmX zh0EjLM6T(7L-?w*.zLǓq'\y@ qWO#]OGWOG)Zܻ{X>tct`kO{ֺF Vr2o]/ ɮ)^ mHܷ j;-&L45oI93)Cr?V2YB#1p"F `H(ve[{^gaz2Db'b"#sEg8^G7ROvghD)%hh-*mh91O>Ιm?l<>%Ѥ !83ckwt&! \pGLc2PK`ޝ`JKQBb.ΦdL~ZnZ..G7JMs/'% ] U"u6'mG 풹s={s!nj?ze>īv0ZF@bӽC2dP7 PXZ曔PiwE !$s~/ fc]cdwf2'O$uD#}GܙxQ)/Vo@oً.o]+9@Mb_)CU1]],*+؟*,WLL F@s31t4Go"7noTqa1eNEnUe:s={*hIʴ1U >gcwnk9,i#+ 0_90} }}hx4**Jo2q?}?:'|ݗws?S9t|n ~[~x|-@퍀:(/,GDl'guJ$nCDtn nH%( _n&s^sNK.Q-ۑk6H/F qtSZLq8vRS&D4!+SQW-|͊?z.g~F6(8[iXTU6?e/mEdbascmǑ=,qv^Ӟl\( ivV8)^߻_Mw*%^]~=S @Dm?|ه& u M%bǢ*/_RE!eWPOWItRfz U o>?#!Qhɴ LE ;:)w&T^sʖj#I[._wT!ګ,uRmF2hCq_)D4)+`K֑mͿb9M5 ZXV_*Vj&-L{L2 R۷/7_n8&SX*$Ng>@o:7.;:s=a WЫ-)"V/ޥ 4#EK P`̷ tҵrS\|m2M6uw,,7n@#o|G֐zkPrjdJ&koh0lЬ&݃Gw>O0R]tްIjӴ{$8A?:֟d,)unxD#E#i.om?M%913U 0Iܹ].غ9Ó#_|pI"c?򫝒UNm0: i9Go\Ck,omg]dN .rcwoM6Zth$)_y<|xUVr"z󷮔yԥ@S0(`~[@:8c~FFԳHňks[^l@gJ|wl#suc-NL%h%O?- o_jC«-h|k@31u?6NZ 4 #DDoy6kѢDTI`*ZTH߆Sמ-:cElB~mP7.QF#q&6DoRX/H̷5CxDx"bOq"4 GB#m!3k23n| _Do@ff-~@& v3\Ooߩ-}Co(Q?-ĿHT%6 J L*DFX$!k[[#UShb:XN=3MXo<"fehX8v@#ȯ?g>wwsנ8PRX PC#[~-5$xI:@#-/y@VZTZk'SZD\uODe r jI,E!1G$m-Ѥ#3qS"#Hŝhsu~㑄xsGWq ^ݩY# ȯwwN=>hz 1\n@D^NgR ׈ h9#z7sGfL6B?(W0L>{Hf=ԕY\+e(w~L|Mր۾H{ `,Z,N"&ڶ$-K=Tջ'W|_mU͵6CCa[{os=aJ/ha7wfX,@zU@#?)9?Us#ul ("uxxr?bMn?G؏C}RX< oO*S$31-{S@#?/ñ[xOQ?( kk31(@K=?G_R' h5/e؃M@{ |s G#hs؃n\O p6ÇncQ=b"?:{{~Qks=a@3$3\+uw:_rCwsKȦ蹉h_Gc2|&@"?GC3د̌E;ʡ\O4H󌀜ܠ37lQ7B]b.F#?=֑mW%iW?4 P(`ޤ&0&@HE?mᩭ>{/' h9!{8v2z#9@˛F(E G4!9{NJ,( T|#+ In}"5 P@#D'>|o݈xoY¿V!M[/!uaI9b}/'t9o(1zzIE.cq+\Aa9ł?\k}?8NUumjU{a@{c7v^yI"bEi cPU^Cb;ƿw@gI5x+ds `@Y(0h_|C'H@], ?h='pG". zV7:x18d[w ct$@G#Z s'qC (*1u_Ǚ|2zEGkxM4ԘTG7t26H@;FO G#MNϏR<>IvtrMؓMuȔtPHO"Dz]ѣ3ǯF>S1.%h@.HvŒ*u/ |E⾍LchLX?LG?v$p?h 33#)(_dga}T޵Cgnz["QsC-gU=K-yU BVWqT]W=L7zH5$V♛nyfHU/ CAMP $joŌ$5* ]W::;MoʼnG~= -s=T(}ngؽ$ \&G=vo/#?5į` P`F~uxjCoT&h?@,#;"~kg) 2 h]QF(O#@G52 G#б???`@mLD_ T{/8v7zzDT-;/[N˿̚ڽ"\O1f} b4] `? |o$7{ص>. h&̆6te !t G G#: _Q +745ߓ./ݬXmsy/# mOwivAF7\k?@K =!s=@=MoS8QhwTW%)@@3tRU;?k/T13>z'/s ~EP>@UL/ 뤓x~S#GM@ZSvGwWMtGFod<yEF@zD\~Q$ c?@cS۾Eg茔S 0y(U7`ⱫD{@ ԕ6?@[8_hyvPJ+ל7`@璎3E$i/3M?Vhm%cvq= /NhK7ZP&:Uf ֮'R~?0%MqMbAc즯?dv eta >M_C{Eڈ2u( `H2O=v/ cT@?в8ӿvI/-К&aQ&]h@FqG4-zft}4O~C<Jn=\aA4N_~::a7?@.x7u2ɼW^'ħ(WMTgj(_# d݈?26_v,߫_E h+W:)"I趱=?1(t FwOqG#Hnby ] X}LGܗߡ<Zcjђ/@B]U @DzwW_C+ c 8&@Hn4nΛ>00EČODH>r.a?>WkڟoRڿ^? Q^&@I6vo# ww?>؇2vW-> Ţ &Us# h(VG#,7I oTa@/5pɈ@D?`8Ç^[J&_:@H3r.qFLj@GG#j!G=cw*@@$7j~=KWNF.8'bDLP G#,O<$vvzV,tup6 GWޑY92׊HY%c?\C@GFhmh]^@sAmʿY73#X#AɁǢ~EĿ#?^,:mHzr3d\E/,_'fF:PhqzWUok&.xB[ )Д&̯ 'Y:O.ȷgQ4e ݈ڠGIGUw"0acW۷:F:oT@#:@ό^։hLK#`&@~:2^o蹉M(` _?7)zǾ#"߫~??`,d>h$}Mkzv :-@#:BE/ׯ2QLٞEώ~zfl= @#:@ώm^ _H|€й&@93c~:< !&@k.o:SCO1y& h !E/_h2,j$%3 ?@L ?c("ͼu%0}LK>}g*` $']O*! وhn32ы?#g}*t c^OxHC5ۚ&#:w7)vl_~c?7?# e!qDf_7}m,=K?~8v &KI.؉ĞU<@Z*U_o~{n^џ! )"fO>Ja:p?@[~⓿*L"=Mw=Dޑ]Zh0Б&QmG4JkObWyb?? B@{7~>sMF:0<o>)a0|LIs-cY@#Zǎvaa0p\WtăoH>q͌,` -mO\nXsh#1<#O\3q%%fbL?1S.L 65܅% wqgt26@gA ?@cP>c@#LR֎Oįut# gL?A?:{ /} )o!T4@>},vcdh?79v}dۨV/Uh K fo&DL|ՙN4a M3x|ř蟑,?@ΡukJ&kpt᯾SVLp]"Jv=#o#Գ#?p̗_F:v?a{oSv+q΍51,ZH6؉oŢ@GTq@?hX^Wc'߫:C8:_{DGR95ǩs_zǻ_'#1a#' h;nn{Vwvm?@ "#M<1_ЯM`~>YBD{l_~N,sN3 k4 jeN%'SY#bMWߙYQ92[Ynm>{g?z.T@9?@]hyfP wCmw8?t(DFwL &!f޵+?zfl# h U3cW|?= w3??\ 3BZV ";? ]mZ>A`5^f7$3]tsw@#ڶ$v~.f".&W? P8Wr-_ 3Q6@&mnJUj%^PF_:@B䃗oL0 ??:NL>xF)Zj&l~%ڂÉk?:<3Y?:^Gw%X}P˽C`7fo^]㱋?g.t `?t:رb=ĿWGhׅR).ÜKRU0D$ؕKD]GE}ំ^a4G4~bж}`|NPoLGCqnl O?(ү= n'UhMIon|A# 2 f>Yٛ͗J^9;o]r"F:kDd2G~$@ ^wz_Ԓ/ftSZY{ :`$7OK{3%_Ccv?uB w[&@&gJOEbE 6# j mP/b?wȜ * C@.u_|/e0i&ͱYٛ+%"$?JMgx97#OnF:(Б=/D_?@ӘJN@ h0 SZ:xgg9O;Bޞ@X82]22C@t]~-aB±ֶG~'p#%ED4,BY@q@_7c#0="-&JqA;RV??@sR(:]8'e#M?@x4v>mm}xt? Ŀ 0Zp?_㘌4 NqLP߯b?؇Z/'H K stO^Ώ|V-^FڒzaiV,||Y9,mBh h$R,O u⭾{EtFE="Q_;N[?6sP>F@' 5DO (jL4O|ןdlL?'ذį?'3!\O"ޭ<m$Jm]O8Gd=PuD7\O eR|d] H}î{KWJFڊji)X洵偟&sRnX*?-S=?4pC\Ф&U]1B.#; 4_ uodxL =8wN]'bot1DWD&:˜v>\>4t N,&LK=Sy*!K+ 1$ 0 ?v&ݽm(#펙==?]++&1&LW>jr;[@Ghs&o+]/_ 6D,U y_{,:5Θ1ヒ\C[,׿B\OPk ? 2Ļ6e9{y҇S}o.@Fh\;GOcR/_f?Mbx K 2 /zZ\F@CQk-O]Kb!ez z"\..@W"FZ\&!Vp}t ɛ.M'b2 s=@Uyeo~^ՊS_h@{h3yflS.w?=XJ2v&HŌT[^U8@0֮'Lqѓίƻh&#`!FqO/|8Q$Nh#'o5hF@$X4_"zi_Ւ05كs\j>}Hp\v ?```t PH!D=?z>$݋0Qqk3$qюAC3T??@F_Z@1Kfq__ʿk)#mcEnOxL=Цb _b_+H~ 㬗;-B<vF_T@~D@HDҕ$tޫW2ls>Vܣ#q)/㟿Hp?``t @UL?.P>@jũKS =^?6ܧgF#$Կy_nj@0P'Yz_W/];5JLGwfxT w ?ԿP#>B"B"RV-O7t> P%Ĥ}tm=?%;uG```pNJ R#xOW@{W.]?z@' b9^m`Pu`~j@t?E.@0lͼGt,0߫#000(75 TXlQ~ꏌ ^ջ,\'"[{~򸎇%-7P#000PwW퍡%+]=]{Kދ oqh~# Hrv/}@U:Tύow~~?=J&@z퟾3欷2yGU;p]֦{ !_!y .G@DBY/=~q֋+]xG+1 |'=D__;& DxjѲw _^W4xku<(o#@$xr5D$zݟ8圷Z̻Pc;x׽V}G||1_C````䯂4J~Q>OEzwی=jTyQ;/"^;C```@FPR (jH EW=ƚ,Fh @8v\O zSz[|Tn[G```@13(f` By;SV uJL:#G6⿃_&+Z  \->_v׈R!X6F{GGĪ_RAwD? `tZ*X Qs^qrכY/#hٹ;1?6{91kpxbTnnC```@=ex ̏ 0ߐizퟞw87KSx0ѧm=fmHџo8o!jU9u 0<e/7^Ʒkz.@XQ=9*Y/_*Ŀt~? $=V@C+*Pt_:5oQ'zZ @Koyz꺍zv<& A+p?ì|Q+O_}|:WR]S<_[zvlsb='#R_mџ/ 6ܫbJEqy˻8V򻢌-wzftsbZk#❓_  ?4PNT@" `/~߼8oP+O=1Byf8vLOlvXk>u&==v (vE"P7`$b2 ϫeuo8UN>Z; VDO orl]k=>)+&KUo~?PnT@~@Hw0|>hYw-yj~O;1czr>u [ul6!2 GvSk9͛e00lx32̀zퟞz_N=uj(w\KVGGfѣ=Oo7sDK~G*/ӏ hZ3@0 Ps8%+C5שZ%scEVgp&{]|Pbs~ *p@o,) "Pa =W4תU{Z{틎Α-gCf_l7!?j00jn,fǂ8wV~ソk~^%]=+yZ+9G;؛>!avń]D w/F5000]PN' f@*O VOG@ / Pn@Czx+_VrZ̻ ;S.؎{tGZ/z?000jh*j]y K^z,>wذ<'9'v6}H1 d?%  1ύ_2􊷽X~^x]D @gX19GOsN* rD~vf@1#)Pi@Q&D+z1k~,_}Z Bۡ27q?9eb[nНb-bPn@)s TBF5E˺W㜗@>lyjsb::wT'C])yzP_yJ;>B}fHTrR5ʉ 0<_2Bt\֜uҚϗUPĤӳGΡCD;._I~9b]~D?``` i1h**) i- Qu稥'/?S{p@ =5|\>n_w\ ./%T'?s 3ȳh3ehNp|>P%|P;92-";rS4?8[qjige'-;C*J8CgfȰO#z1{ߺzXDwˋ+g ]~~6 1(@C @=v̴Ǝ͈Ȯڳ+ן3;C dhwinΖv)؈ttfXώ驡a=>aT1+/^tǿ_i>v:RHtr >Vx0 ^8eS'F-[uZ|,ZFzz#3qN%xxL&txrTO ;'v:7v)/%>VN>:k=750Z !Pn@P h"CoT8kStʩjj5һdt{WH{gRm&fLLI":cs:2=!cرQϸ9J bN[ ,"8AE~gQCQ$~T+O_VL-_X-]TX-^Tz,UݽE.ճh(Ţn uJ}{Ibz (*XQ.lѬl̨hǎm%D;Xɘhmd<"ڱL6L$$\D1ḏ-UNAB{5?@e뙚=76.!~)P0/0rL(b~xzz8g}b؂4%Vh9>W"X?!Me;G!Q  &Q (߿|)C J1 ; IiÇ)P黈 j T )_/uRB P/L%A~)SR+׿UTb c|祌RU[~ PJ} UK@)\x FiTjTs~ +Wj " __IAJ]cA?) r=B0h (&b|rDu BDWK2 >`Ac ,T[P K .žtWPc-b H@A)_W[ DzF@9_+<xP 7SE*zkTu]GAQ3\bVm=v^Ty0"q|{ՀIP ~_S^ 0ښjb4 Jk`````````````````````("DIENDB`tdesktop-1.2.17/Telegram/Resources/art/icon64.png000066400000000000000000000102161326245125100215500ustar00rootroot00000000000000PNG  IHDR@@iqUIDATx[ypu-ojɲeK,m͖Cl̔$44-IKNh4$M3aa',@j0/b,=-o{O$Ͳat{=w=oAWWkj-HT.Ã25d%EB2"x׬&Oʵt e̡moNTB"-Kl'A0Yz3#$f"~ō7t8<^q #nB[+'&K]|'ǕS~~'n/cIy9"˘5p4CEH4rvo-UEk[{뗾UΊ炙?G1RFIUMuW,F G1)f$3r՘TSk[<6wc؅>+4O~kkT?5bVLf.zcfؒ)Ɯ bXך0ºR ! <ȩ([gP$jeg bޣ©sʥ$3Շ3!Fb$j+"o< p,! #@|rm]G<9 Ҧ`Vx'-Y+.?C#N?hd HaACr/uSH/ܳu~J|0u35C}e0hG9!8-;QVa:N?i=@HHu`/zl6C"S׈1vw+D.}˳ ᧃ gOkV-@N HGu,15–ºGs"qMSI,GƩA+Btfwͽ5zQ{ygbS$A锍({x|os4t!DtxS!Y?y}8O)dlHKU) gP$@_trv )l` Po_1Sq/=և9jb!< 6HD!Q5bpv/?!+ |ky\$-M6Hg}Y#VFL  ! [hLEup&/R<CsRƊ?K9݋Q~:C>2EhB@)hDмO4$ dw)OЄWd`ac Y:=h6@W̩{&"3Ҭ-%/Yh៨J@y#`9q1ݏ{eM9;4ǔIU W%EUж*g7R$-t<:ۥzWڷS[Nօ`,%(o@z.ŪWGKiNrv,NwB]Lƒ `z;/Gt^1e#XIENDB`tdesktop-1.2.17/Telegram/Resources/art/icon64@2x.png000066400000000000000000000221461326245125100221270ustar00rootroot00000000000000PNG  IHDR>a IDATx}yxW[{6˒,˲=^Bp}ql$d|yo3,f`f 3c HuӊqpLh0ȏ횿] mպY?db$ÃqoqȑZ7+Qdxw,;4=R @2/tbK$AҪ9w\xX_n~Y' &4;w>;'kٞjƫޮk2s"I,$/j#8/Yg]?q&Q$؊pbnV/4S*\9V5U@ox[hO*zwŅ)#7)LE0lB@縨Ճ[qMB$$ҥ#aţ@C]z2 ¶ [Bn௾l?fTʳz_ۖ M0p-"`83pgM. ք=bhhLjی2*n֮.i#@`H%,W3aw$9#a\>E))^]-yr +u^@N IsԖA?--h1#:9RL@&t/zcAD+Rc K\3n. TD86B$eAi#LVd[{kRFbpc'dhXY 2ϽJ;tM^͋Bui.+ syT9oH.xl00``EjʶtqbKw YvҤ9P HIaqHqKW]u.08 >ZO4h|sh0M 3+4y"py^3}Xr :kOp3SWwqh'`UUk:XԳv?2 gN?TN_*B߅Mp$,Ih,Sf 02m3JJXF 36x]s6ze߿6IԸ5mI]7\Qif\hI89@°9)AGs@I-D7Zlcs/1R%$Gu!4y5RYhi7$t$ 8GGsu>״%?3It܄XŤqJy @\ـKTԓ/" %q&4l*/?0{<nj.-+ b ~`CM&= :glܲqb)  u,j¥l[h,Seldy,Iyb*֔>Ic")nҰp|rhdB8‘(ڍTWGGs<3[8<.X."$9{\> h>*V$O ռ Dz] H'?6upmWk#vAsSL8=KRz A:IBG80F,_={S9$YgYddw )}SK`3la~pW?V7qzlZҀU2+g0-cҡ"BcЃ&?>Q ΁ߕ)X`3>2_(Od| ygw4L hz"p*epy8Wml&YRWF/&0F9[/JC)¼:/Z} "XEXƻ>G c$29H~vyۙDJv O )3b)}:`p :wq]W#.ZB[ n R`(K- >P~J(g@”Hfhvk/.fN;ЁAg%?SKd(Py&Nx@2}*>b.{IŔzxZ|Ec@0`+/6 qCbFA<' O;9ɟlXqg*}!36hL.eo({qr0]UƀXÜcxv% ,?6+y11ޞkSU)ޑZE0zFBӦav dk3 $NfQcYIH0eYztHK7/Y1;rHOUFW#j.EᕾDQ*t_~8Ɩ}'Iz8gNx} GYVzQ ,OYOQ3aܹ}#YGv"I0^U.w) v@"ƜG p4^?x$LtS(Zg Qd?OU6̞G E1!ȑ3oX{_檹ht3 ˜sKJl9^A>)B 6˓Q^xJ>9voLW5vҟYLti|R*Қb$,颥]'#Y^;J}v=fz=Li.XolIņƵ2==NQ`!4ʞcg3vő2 7[1Sp{ eu$ \>]kNFCp1ӆ( I~S~s?'IMZolrlH 8všȼ[pD 8zЅ*'mB/LJ؆Qz^6 I8u\ gΡl$ʞwRn=w.p6h"G^YKJ$q2f;OgȀqsŘ}&6 p ##)fi4xuyISM{~;8WQzVv_sy`ߺ=_,}mLU&zR88J97Ƿ_ш#s+^]ðIc{_w#aN0*d4*F|0;7hL:'3K;,5m|wqD?` \^X=/H>IH?0G:}uws_ЇJb8ñS# gX1FDO?X$*%v➝3_ Y0gL:eRjT kj~c¾p -&n'*1L['.8FL!fXU8Z.` kme~p+M깟|ezrnWzcֹn5T 3h.8P" 0V3eE4` /+5kw\ZQ*]:Tt0?FFy1M,G ly ۄLwL\#rc [ h`JHʆWyT,Ư G⿾c_0%[<EÇ!&+/5/"drjkK b!'k/$P0򞴏yM[jѶtlո٩q&4do\0: lԼ3@sF~NJŋOX&_%ڇ_ݯ6 x ,cLpf(#̬1C43D|.|o?'{>|s HRpn&3>`1pm8yHw/k?Ajf2w%ɘ#&v㚆"ʼn_ /}fo֎ǟI=?0ngrQ.><ڽW V08TF~x֋0]xh$y@/ lJJC;vjmGDc0QQ!`vj#{?15rETPVrK  g0 +0EW #LO2;zj$2wa m4 2cW'c*}2}_<~Z|qb[M퍳)Ggᡃ!K;Hק @ ן*W][>c-?H!IDATW6@L8y?!@W O0S'gc m-mcV gzUdY _(C}'u=K: U31҃ EX$1LB~>C]Fy<5/I}ϢJj?35ԇDmϨpoB[b Nh SwEIx?3SM3=+N*{teE &a(<w?ꗬ*NaӨgSlS֮Йӟ`p7+6@v?\/}B!8euj7߫#_[0;)I7|{_@*UvJ} Içמz\xU㳦w*J| i>ӿKٳpvo2-:+o7}beCLͺqJנz=f˟-@XVɗ]x!fv?<#\@~W|>G ΞM 3a&/_|95 ̶q = iB_yA!p&l`62@Fh_*rûĚcMM/.N,(2=[_;X4^8Yб w\wXuftŬ01c2 t@)PL)qL:utVecx u֥kkyҕ}'臦#;ES*A0OP{d:}p Rŗ8F#nA0-,؊PS+ 4gFx 70$4`$F)>F,|):xCa["Rp|*je\b8ߣ߳HHIp\9as09̡B;N}wIENDB`tdesktop-1.2.17/Telegram/Resources/art/icon_green.png000066400000000000000000002502051326245125100225620ustar00rootroot00000000000000PNG  IHDR+ IDATxw|[>C˖=cg8;p $!BB)atR6JbuRBMK%kYё>KQ.![@j) HTyy%n9 ;V'26'R_!a)@kic}@dicG;G`[7HH hxDC_8_Hd%@6Oc|MOϥ@\?N'K$5[nK@{s|_}}>Wc<;῎{|C 8p25[nKe}(M|nPU3UY[-xng yuMyD)f(Rʥ4V򉈮ED4M%^;^UyD9!" u~.ALD@`O` '6 [woԺ}wG/D>7?s*!=E;#F?yOQ )ksK\ݡVZkJS)|є_DiSW'>cE wE> > Z>joihc{i|{^wvDxWH0!V8@0@ZJlSWC)zVynBwh tUiZhH))R't 墫r%"Z@l\]2< ʇ~ ?lo ֳ#ß( i6)Iy]k%??ҙ6]0*5]Uh6L4U:FƂALہۭhO=vKSk@8֯%s&$swy'i=TTXW8,ǘ}ʰ (gcGh6R׵JspxEWt]sp_jW5K j{{@[൶W۟Oysϻ{$HHwZ@*IB禬5g8O{eMmk#E*wRJ񆮏.)|` F=j{km/W Ů" $KXCQDiU1.3pctSʡ@@{sm/x>ҿ0P 9cBپd mjK0kDA3לl8MPJ Ahkg>gB>>'Q?& V:dtcSys8|r3]> i N)Kw`0'Wks<'?~ؔ@)F Yn}e9f'Jr 1Cwh5MoO7E/r:%+kGgyc]>~}wK B/z  ;1,_U19)snsfhRIV mG[Za#'ҳl`0 @@ .F1|qQ󆹧4>=ts5Cr'[Z0h u#7=򯻞GlD!$ l3Q7铐D}k!: 0M?!lbgto8mToib\ZRʕ@[潭rEWz]6 !G`[њMb4 O^QUW H)@ښhןzw%v9@3a[" W]~dtc y.ckR=D0o?wo|a2@vl@Gl]+9rá/RJR=H/`pO[K{[{c: ^( Q>&MIy]k>ئyM[qV"6z`{}-=nHd€0@ZvI{m!D oU/-1]*ʔ|2B0|u?wC7>t?|K@cy6A` `̿_6,"ޔ*(4~pӟ%r*" C%_.p.=~mNX牍G [叛%D@@" 51['_:HW #A`*NL*Spm""斦_{&W~O8k WxK='ꦾHl^ `{kC/|>ijď!2MHw?.;sK6yը H`{}}~&''"6A ~4ͬQ_qm!J|), A{ڿ^".!Al@4 4Qv9 ') O@x Bd ?|G^\_=pKF!lki?300clH~\o,Y[9UU]*k@{_7xަOJ @ [#  ϯ9W>B)JWcSY3>?j_}ƞf!2Mi^6ȵه{'+MUf}7}?_O}{@l.Q'ZZt<)JS9;>mͧO@#  ;O y/̣\~ɚ% u;w랼$}&g?f_=ܳGv6ewo?~{{85HS6Ad4#Tz5(qi{MȍO"  iC}i1<)JT H0| q6 4@`@8OD4_ic tmXk @{୽5]zE|{{[$ M7tA-]+9SӵD/yoڟ&@Z" l\֐?wfhu). ɛ{."=- R&r_C$֝hVqM 00TZ<.C=D" I=述w·0W1|B4;3Ƃұ}Ӂ?]uƼZ1Ͻ'ta!: ۼ0C?wEDeRфr8"2I[ޖ[+o٩GWM+2֋{H`[s={ݝї%EX HO"@oᛵ4gJ)35``owk{%kt-Vm\c.K4Ħ2]^{lˮLK~,@?捂  /"گ-Q6f^2A{[ٿ{I@>11D/o2d_94 nh3'߻fnT*"(0t@d7&ԉs?"w:p鎜BYJS)/  ?Q%w盚v7J6 a*'n ߽h뾾hfKuCz&m3,  XđKH]S֏-qO, mj粀M^;Hz. `YlF}]:W T D{{/O/EM"7 a`XwR?_#4U/˽⨛s٩'H$@4@t0Qws>s).T+{ZQI Ju_.h<r3}}_2Wz si <$p_0?:sG a!vhr{ ym`Y /]w̓/Ybqu:sG ؉qฺ7~sJ=%i K9@㮿."U53Lql?Z~mϟ{^X:=)id&d>> 17^|͜Svz ?@9=kt9 :4l 1?qESwmb@47]/_P@b@ZCY[&=L@sMLz8HJǜ ~1) u[]'OZ[s26X`myw|4q?4lw$0`X]oak#}76^|U^Ee NcέY~gnh7\u )zj3F]pl݅N]lo$Uy_Os^yDMz&ڥ7Alò{=Gε뾾hc.4rz×|~_}}\7@Cko6ALz{s'Z׵ Ҟk'k8`#!lHJE#wϛ0lJɏtCbnK]аg?~9k@ yj ץa +|>_VՕ޷Gk;{ |iii{5k+_WRNj)weת_o!@kB HS165`|5MXLvT֖|YOz:! !$HGLi(ogш[iPuhDޭ_s N  0z%@bH# nI܆/Ϊ9jЦXM< ['减,=]+a@ 1#ю3E8{+;~*M5w}^r\HQ]}@!@#cV~s4kI ki+h< NiMDyޤC&$uD]׏]W9v_NcU>zzұOHHJ=ys\RO\^1i=T,raw_ͻV^9nMFYR0@MFUN){UGHƀq\H1crJI_+RiI1(v\e^IpI!z@clq+Owcn^D8<͹ዳIi)r]#B*+M%g !ڤQs`O﷝n{t""rGIQw*WpQ0 4nr~{c | @#,z#TtqÝn{.(y1)U$ z}!(zL\Ys0f[U/P2+k[E;`_ E$r_;][v:dLmJSE $JSEc yל$=!@8*H`%޿h E9;_}oIJ)6)tq+Ⱦ@C(\vxm biis^p|Ha_ ! NZ]ԧ[R,EtCd:]k_B`C o޵ EkVjUVR*,S-V+`s@`~PuE(w;wŎsG+H/J)[ׯMHY]w""r6S^0t HMmE~۝cŋ??VeJҗnjS,l?jeJ:%")1O|^1i=T~9" b IDATuhi72kY76ҒbҜ<#GǟG)wMt""ݩ`&~`hoعad}hummعA 9`uC&ZudQMV `'E5nٵHIts@B a@BG"b}Nמ-|GS*􇦔-|gkwy\ @H Їo+?غM"dM^^:-_흿'@ bc#z;;zj`hN}vSrI3@g^#"b>s~ݸ#~$KPMtW9N"C Ev?袆uԒb2k:袆U ahCw7lQE#nҔʱX,)S42C/[y\I0@]D ݩ;]kҜo i/͹tەkѝC" Э. Q^7)[iBR^s}1]D E:X,fWߴ9[7'o~S?nllLk&D7GΩt[s5@D1]?9/"DwL@?Ǘ-:iSoR2zâf9e{9!ЉY-N㦍87ORDcԺ~> @tb2d{o'EXvI~q@ZtmEտq:٣v~=Kq@DD`j+;GOcY( iV:|F]Ϛ_/b L  DiCs75TRi*ߒb/JSyſ\sq^z@Bddq% KܢʱX R*&.iX/!wB ;$"b ]rXR,E)(w![]:&< h~"bn|I:?e'Ir%*h 4+מ[ {S9olr; @V"@JםӬ;굗N=tc@Bdی9{ޤnIy]Xw%;^RWhk*@*nQ/>?n^+dx>iؘ*L $"bNa?X]gc5 9C/[K (}4]^b#fM66mk= #Nu_߹eWnPk* ~ -wK= 3555gͿtXS)ҁWڂc?x}Q$qwczP{ ^|Ϛ_GөXs΂^wz&`sDq9kYp2VYS)ґԗ9gs՟;:ǦS_jMHgS_+sn`ϱkhԗk `;6k4jk*.c])p O۵ҥW6nt`}4]oΆtnn@k*9憍ZzYړ[]@^oR^n. }7DН+1XS)2n.^_S=?u"=uI{ƤPai_嫾1RdGu.՝)לg&ho޵Wc% #rGl޵B!lvw?1^Lyu% Sc^li3=߼kQⵦR`)qO*~{m_H $U"BaSK}WU5COitŠY\] їp< R)pNynh,H 9bSOp< RIg^! cT $2x}"%Qvou16}wnDkRǙ88QDLx@XC.*4uߚs,?d"Ϲ뾱h}< ``H/㦍)WxRbZS)` &%'Ϝ$O`S@ 9 w7&SZ[=ǔJ!C-E#uJ7XJ7up#p2D;"b,?}CgM@g)6Đ#%o/;sJ r;6~{;D00(aoB 1V>W{%iW{3ΖIe  XMqaKa RbV_=ca{ ڞ0,ۋp{ҚRo D*zԵ=;(J#rm`#bnkgnuT ؇nu+Ϛ-@ϦG0#@لoI֦.?֕bA-r[6}w^gD~ y6}JrnM}Jr%M 6,Wx`+zơ*%MD a!cn-&ZiȺJ{S*JKzOO #@Blk;T dݡ\}s$r)b@t HMN8.)In<7፿"bn`zoiy(0$ >zzK/:?u,`ǪE߿|!iؘ*L ?ؿs2`B %U0yN#!}9@;_/XjQ1 ),brOboȦbL^/"f). m5UO7K0bX~mOv  {zCjCٰ͑cnD 8rCV6w=w]K≷h8eTwY%{A)J) &D Y^#QⶮR (qUM/rrS$_cq.t5ʚ*j]"@-u^d3<Ă3x%K@? \usR_H~GO@ "/kT]!Jr(ʂj[,@"Fb_q3 >ՂD=KB%~,@ yv;XK~ T w,X&RBfX]V'oSV22 ~HCxt8vӿ~-r:Џ;~Mb7y_Wݜ~JSE JSEF)>pSي K$ؿ^i,D`:^~*@CD"d mP_xb_I|%9_]xb (XW!DBFcWFή%". _Α+/ϯyg j@!2ac_ ւ fhV|eI рY ;Ž˳s 'YXANZK@1Uuw=ŻJv/,ZY"]_wk-(uzuߐا V`.!߽-\5f&@r[8o>{E$ JO|Ldv6֚M: +G_*2JH&U*=SJDTg߀ FGEĘq7-(@)MO=h%y,`RBFb/"ƺ68< DfE%~ %2X@ǯQRP*BAo%E{  C1=Pi%JS glSX 29SS‚M9GN>KY p$K2@fpnsH#nsgJ䆀2@w[q\rI@DD-$㒐Az"")#,ksS_sfh,@ m3/ #m.}9ZW%t-9m։å'){#(E4"b_6BQⱠDv=a!`R61y_D[t, .c麝Kl*D_=_0 ˊ`+վFWI!SD`CQFC|T%!M#j*ћga)V`.aM8wl.c{én\DMD{@~^]GuS3M-zemȥO)}E}1&9wagtkMD _J_{za1w { m3L()8@I-"D 46C`Q7DDwԉNpI>:QBa l 6Q_qRl`h(% tS3%flh#o~ 7N}@S_KH!lhKi.*}ZW%L=rj_ \srT6)U:)v}H=wt$%i 4vQ17^?󰉣YH3I6PX@ H3Q6 & ~ź*d3EW&JdX@ @v."㦞*8K)U2ȽB4ȱW*)t`] S>vmMp,mXkڵ5'"9""kj,GFl@&oSƻ|-U =\^ǡs2A)@~C4m;?t]44df&u4&)?9Mt$igWH#ʯ=c-" v>$YEgz)iؘ*Lhŧ ?:C tE,v?Ȍ)^`8U N}Y,C _K4uS3JaE}⚼3tS @D߽3T >vsIiL D|Ngpߗ`4!0`,K(|V>{k-Z͊goL:Nh's:}N1`sjWs@sNͯ%r ^G3j m#'M?Bӵ ~tbIwH b9R :S+/[E%z>W<&?GH-넯>/s1SWJ[Y Rh1S>Gz?4 H>k"zNDjr%iL5b?naJ+RJ/8nva mH"w Gs-*T{X b_pBVTPQJ?na3`!Ib_xK='YQ$sbH_'SG:]w542Rt u[% $Y+b"[9)'SfX]@u_}g*>t9dT8bOmpI{MmYnpO^Eiiot m)J4}z!MD_Y-G^w=ns|2@ttԲz^1G*բTAYMԝ6s$Bx,@]^%y˳=)~=Ovi((RPc AJR %'Oߠt5 8.)dVBqt)/5eݸ؛^_6E@FPXsÿ,AwuuS3, +4.-KfH]ΫmkdNu[\ݟ)&U(?7ʳ?HW#'ȟ7 ]@V(כםrИ!Ͱ፿)"/Lϯ69@r,$ IDATrnV^/k"lc(hJI%ȻoZZ `5M*}{wޖf? Q#SCw!}PmMD'YY@f/-JL늤)]6N:B>5,U:$y\znvMt-` o?bΑ9DzOBn~CMQE0HC+M"$G&N$SdB$ X]`MMzD&@rDlg 3}eO#Y] `"۸]z0@WtO,%U[W%:M͔I%Ӥb)Q.$@Lԙ+x?e]o`h/wX^̨+%e-)=hJ2yskVX.w^b/迈ggU J=M闺2|YXSQ[%OK,gywԎ{<+O` `NԝED^z~d SeZy.#}RJ҅^ẓ7 쩺BA"(wC"^TU97XW%@21?m-V ϹaK_~wfKs5$iRW>~Rdo ݔ&Psq^@?C'bߝr,e(COs| EB6w늦OI2 8M[ZuU J_L+#ft>@;osDD4]+YtR?\J@hő_0v~ ;P1+uxeJ,Q9WJr*R\U{ox7Wn{$HA` ~z̽}7}Qۘk]}!'˴zW4I-,ywEHGncjs/G)ym ź_髖i#H~ m+ {d=5bV'NH!E0p1΍V.Zψu0-[Ed9=W6Z@]sJ"""VV?GdқHǯPywr 'GĒQyP6?_~'t Xf,?7a}e wBUt*nvZc6>n1Q6h:e,Q Ol8g5@Yy%?fqDDDvCc8)qZ)@(υM1r$fcl %?`?~'=2_oBHX:3[BUԏ RB fD.{LF Y?8`%/ZUBX_uJh/p>Ӭ @$"@zYo{U_ au/0^sU4Lrln4L1 UcP?daO vhV_Wu_\Zz7>{s-iJNOFh $,ooN86zg;l[nGDDDdwu7`i!-2&~ꋕ/ /S0;,?maoYN!""`7!Gd)3[Hǯ*4r;m:ZZ$PȝB0l|Ʒ?7hP#jUDDDN_QܚM[|Ua8v!c\i,N|p`B&Yqv96>3ͺ gg;=UIDDDnv!Fz c`xs=^pzR@0&P?s2ltgy?5h,ƯSawkݻn}dP)Wz/Y򉹫ɟǰ^ #l`n?%COY O e:қx@HT߻m@B4߮V($ x৒rs|@(6,Ц ٯ=ݫ-'I߅W74Pf/"&y@n}}l`=+r&X( n&.>cݻIDc Fͨ CQEMrz @caURRkXYv!zz kc+! gO17BhM&=̬.%CΆ6P~O@ RDDDԦSa' @#x? .f\1RQoZu B\M4_I?^Lp?; a#kHο.9&^=jFPzә)!7|?E$=ܛh2am2}2oz*߹udƧ9̜_MqoqMܘI{!%q~ݿ"EK:{Zz|O߇K)  <损.`ʛS83`9 "Y~iq}t:yOx)BZx l'rϜ4R=iXZ`yDt)S3j,\OFKnGDDW݆@\ b@ &M`Tjt>(yio-wx^BAܽ <|5U^mG}|?9CTHYAD Wÿ翇\%L{ur]Cz `X_B4uuGgC< Y-5!ῡZ4f/% ,?%ӿbmDDDЯ**n/T4k Y%/3XOobu"zt UMw;!`:eBcGj p44QMI?wW߇( ` vK*KwfaDDDxFf WG_3L{w4(JlIsК8 :  "KBlpiC "xۅaZoGQ3pzRD8rM υ 7`Ԛwgʝ.GDD^Lx+/\xa Uy˻S-"=LD1_7V4!WS8`Z~\h\{2;DDDӧЯ:/=n~D5IooGi( @B@OOw ]0 fVЗ\hz 8a=2o+~lD&-7ӓ""0Rk~9o%P ?u8""Q%Q$oiT@/W>:@|PJ %"!4~x%Z^rSw46goiҥ%Etڇ_8+XlQj!-h P)W)Kԍ֞&Vnf4z7u]%<),?CXpljT!Jqhۜ/`&23M+P Q=O7hf9`RhQ.?Z2OF[yQ ,ͤ r~K(ݖg?W lh\m#N8uN[=7S7 #""rm<|KOrB?T+c/jyhB`yf |@a@ jShs h8?^zzk ͍0~XͬCӑl?c9?d'{W8Dg{Z@ @ӂkUXMt_ys@-X"қl!ԟ# mivPP9z JSbm<-x' vs4.v/DP$aBF7yhfuDDt`6?}L~lEW IDATP Qi =trntݾ)|4lP1}6'`:?u-YM0X UNGCTGZ~ Q{w]yҿ E;#7g_@ BQ{hҞAx\4o`5Cb۷ ED]&[* ->Ih ak)B<~]ͨb/mUn۶oXF0&9%~~~y~} fVC>0j#ݏ3+x80 G鄋 m۱T,#&aJ|wa}mU7 #""Y -o`= h ~~'K4zxۋ ߧ߬\,#@S92zJ aEkO*\b~%yMhl&-]x h$NqxrhA gV)~=ělo!1/l6Zԓ[/֓c_5ᷣeBp1Kl [MQ(xif@eljX-"[88ῷ +7TU@J΢U'._"?7'phi#W""Ѱ8* j!`!$>z(2m6zQ !r5 H("goo_㒀?@N@" x]qw1V6q{>1+ }ac\lٱm ayvn(~ݧ:va_J A2f*~F9 =v0""xN/3Dt5P(\Bbpҕ}E~ !p 81` T&] =v8.6|ï 9ZFa[Eg$œgXlR1UFtq-LTr`t&{(WKhdqΐf Fͬ @a.9<*7b nU]dΪc#mdy-aUP]ٿ˳+aL7L!@}wMs8\o>~滴oh0 R>yngCb<~k7hE ̄.GG8*d]9"5U-&_2 mz}r`( 0@7o &횇+C޼E?gVZSXOoMPjuK5SU 4t/&[HHa=Btp96aYpPWFq_Ҕ @f>6l' ',ȯ=2sM<^~rUD1M, G֡gP:Dq^"EDJJ&!<F 'N힋N?N VWc#hjUX)b/5j@( dZ = )4jf@kw1E~Fkh`( gpʠGDCfNpR̻] Rÿd i/95Rit7]g!"S?{ қXK@4AUD;) 4.P K%:NC,%P)Wu6ݦ@O_o5:O ~jFsWw^sX]y2+#h8j˧nHDX/]9}]Oh,m7b8Mps>Dh`?l;!Ff V9c9Mc_fnAbV`'5WJU!*S%< w1`9o sQE ̄FPpUjP.Bbp2!S9zo'Ss\lL`T|q_o(Z(p/-\ 8:=Q!;1g 5h0vԇ=k =ǧR-WU48ˉVpkkbBBQĒ+M~\ZfR!WE4:j {=TK9' .yNM@? 3HoVMSSM,EK֖f~{tΕ=_3.toXB]DDDhOVa# }I:JdGJ!rnBQ,ϦT] X`t20KF2wyM4wl%^IDD[=T*BŇXOoңmyDZ‡{K5ST4'&y:mlêOaBGuX5:'`q믷E4}ѨSB 1:QCl1t <~{ێ%@DSB`+Kc=sD #4BR.5+3i &*<70ή*8.z㶭neF [gyә_+nɆ !톋E??GdӛH'019)di]@ˉe1Z W 7a~o&M9&ՙԠR?_@}gpw4/o4 ^~ۥX #H%R }4v+gA,`|7؎a@&A:~z5KKsB3ED4*mѿ㛅h gC #1';^4bbsi-ٲ%hB\Wƨ^JS$wKcN n0L*A'B7}  NgZg'ƣ gp9quDu_Ũ`/Jv) nA#}1 $gn ?7VۑD\?t42K7|;X_ Fͬfl0DaK@ml\z{KO+=,7/hugi@BWq%Btȋ"r&riB`yf!=C{}1_( VjlxYn̔M7(?4M=id)Zg$%6ڧ_/>`lyܵ'DDt 3x a-<҈&Nͨa?Rv)=T +iv)4F;GolguIt isגW>] / ۑwJ"iZ92[HVFP:>=Q!뉳ԃH%(G~QpXط tIpNOCeD4~[ ҧibTqCZv!~~i]cs9$I Ռwm 3v2T6&@tꇿ X_i^n؝o|,ܘDԗ~C@ :3:{s4yi\pbnB}niX?3fRBո|Z1s凿٧V4>H/7y?9Wgob/"qQCL齳P\B$qo< v VL7M f`ӣ D sm"A $I_zzsN FԬf԰CRt(B zԢ@K0`QЪ?ԥ]v>?CMtx H@4 x?`}- B=D ~MQqw13[p4y G<be6 ].&+HH۷ i{ :I)})qh] Yt?2C :W4<~yYug`Z9î(&#GRU r{Tn7]ձ2]}} GˋAjM O Nz@WeJD~@if#VWcKbȎ's=_=z+MQ!zCYr0:̠|~YUI=g{>[)& ZЬ5Ͻy ,Y熈Pg+Cgpg!S=Mb v) H%6ayWg U_o6@Y{ZMjpBzЌԽED4T<V p/nLNJyKX$Rb:>z}<17>4M *D' ^0g`ЕY~0DS`#x -]oaۥ ,c!voKK߸;K='t}>M{2- ~c={*g=5}˧n2psySOf_C &`8߸;ÿ)91ۢKe߇ώ++J A/DSla-@bإ^Cp٥ '.<⸘I)w>9^?l(4/V0?#D>AR>A-+OaF4f=%K ⡸ۥ;>`wg垍&d1|=7 bouɵh` >{-6& .!FCRj8@띚?vbm 5uy:{S0 :GP>Ee4*LD Z 'pykM\ALiy,LUT,Ϭ .5ŐNP(]階 4;䖷W}ON!M Qsy; `ab௫:Vč;O4dR'YگY0rbP.~0ohLh! y9ӃD.lkXlSTNE4lU>JۥD8R|ំb'yڎ9N2֙{f@T(lD"[zArD`bS[}O$aK,FR)hIH9<P Ey} 2,Z͞?pvҀ?`3tM:U}45``$r B'$+_>3ݥl}PŨ`?rv)#'0! Oլ}nx&9;1>ı50 Eȧa:˕C=;nB @4rO%2xysh#"Op|z|qF0? 62nD4L@&h!ur+ ?5λ['~j9қ$:"jVQs#J] Ў2ni/b`8Ԁj'+VdsoXf@p{!ܘg?Gи7Xۥ EĂ1K!z} P9oLBl6iquZ!ZgQ|C*viDԣY~~JRBWu,Ϭ@S~B^lbnFJ 0Yl#t 6HB;xzSNG2 cMYA$Wx;0zoq;F@w r8[ _KAD4Mk%&pB?IQ6R]؄,ŗiWhymQDph'0B8O]~~ҡS{>궙N $ 5gD:JE=3 V]$x}o:/ &)Md e`%>@faoꫮa> !a#ޡw;y-^'dnp%OjuPb~+XOoI9'# j=ԌۥU<B|2h ߣX9P۾1C}gےR¨P8Y·W\o@K&r @:f^٥O9ф1}N.efv!~q:6 nay/&@o‰`?̋ G46hj{_[ qi wA=Q{MBÓv)c7G"$r͛!p&-,?ж-_^ȟ7 & sqlcB NG4jf '8-] ,$ j>rכ#οÿ[Xދ(2jUQzXk'06lZP{BNx[Up?O3GDC+pt)Bbr a󏐻Lib;RXp101q:ᆝ{ {^^Îwݟ:m|b@6.P'm3x>Pu%b=DC,GҧX˼@4+c&YŨJR\eۥa?j &Frʸ¿Su 3w{л9 v=[l<["#Z ]QEd܅ߦ{'xv3ܘ m"B㩽]UTfPnBx}92O3o_4%h!_zAr /$%\]ijKb=ɳDQpGը]k4U 4#o b\3otiJ(lStO ^:˗᝕h'?VTcOt0PAD"4Wg>~"9 ?K5 XJx)MSx}Ւy$eͺ/L8K@#+:>[sfy,yמzۦ)믷]| "{z 2pyUyĵ[+3"/1L'( nH0RgFFvoVaq#kɤ~6i=aI=pjr~)%`bBzWE$-o5 l^2RK#!8)00K☏OS? m=߿]M{@}9Ojy2Bo %<%l\~A-D^T3k?G\t$1s d_a m|;7g'rh!EQN@^xn~36{ _p`/ț$9Rsy$ m>k  j& z]{p]v=e(ɪ}A+ZmYVabP#<y`GB|l@~ ~OR򪻅@*F}+%KB,$ ƺߘeY[H!?|1yeK Vr&a 1ґ?-`pt)2ҵc>vYD4)%YNy(XL,!2y mt A !|\ 733 !Tؼ|zk8$J{ժn21TER2~c Ke cc eSk{hpP.H:!Tb/+7D^gJٓC9K(aifA5v)D=, ?t { s H)}9@eRӄ߸ X] i}ԌۥLMӱ2MM7Y?F@GHM&AR"%I_ÝŇnBDC`NQ(.e PKS3k=~{(tFz|Pht/zPTD>W ͺ]  AGi] X/1g;Fͨ\k`w_]ڋ v)(<cI'C3m %E]s8>=in38cg{G&X&G{ܴU^¿z Vv(q/9MKuB,FSB28óaDVUCZq$1s xs}?ÿs C+|l< W#Wh3OQibnK~yll3e Eը]ng, 18'M@o "W!p9ygX- jv)Kb!?v0 (aTawh8׀/M?SƗ"TY|X !Kh`1H v)DCUyl=g&|8o"[og^u @3FqHqZi?=W؀SMj@ /P"BÓCԌۥL4UQ\BH(H"/Z(;3kh'lƋ6\ ݋Nu>)e}_$^oM2棋JۥxB@`)Pk ӀPCn0Gn5bTyqS>hxՉ)fCs#F4N_BKPkBz'3ZƜݜMw@4 ] q*8R]gD,(F}^͍w76 nA#yh˜,r9KX(hj>BHXl4ÿۛk յkp@rMb}j\ωD$ؼeMVƇpgw{sc-yc̄7|u@Xf j`RaȞtv)3Ld20 +zn3߼m @}m v;1Koo o?".|0<$2J^6?Oo?f?dmОvZjL,]0y}_ױ;2=4G(V nB4Urv; }Pejoj ==:k/ƄͯeX|m tO#<$Io#[<@4D#V5\-]'id -v)Dɗr 3x07LĀț^piJ R"W:›/P58ѨH)-ds'MՐYfGf aI `LcjT7{?@t<T-a'i:gW8W 7|.!}y} ORBl;)%^gǯ0hLi⨐EwO A,%SPxuΡׄlGݪR'4Ο)ARc9F*[XzR0ȥ F_$so DQ qq0@mu1ވ.KkXX^`pM{s ӺW_sK65;V]qDkڅ^y烦tq+ٛ?AեBDceH=|8?h("?QO.|2X6vf<|vrf/p!~7K'e@3O y)M|!g ƧP>A$ ôZq㘋ͻ]yYcJqvG\Ntes]F^9<>04 0k9t5 ăIhՈFfpprRg!Ld2&ҫ >~40:/?")2k `QTri owVN? @D$$} ]ĨQV.XH, ] Ī5Lz d&y{J?/~_3Om!PSkHPU:ü"Gp`ߦ2b;ſMOgߵ77?W2pB߉ip:k$fo9~ >r8t\)L;ԑ.Qv.EgvfN^̜ߦ[nG7(M/PlzR>Qs^+u]խv`^Qs2D_L;M\wx3O㧞'BT/!]LCӸ5?W!5 })KſGL7BoKiMG oRU 6h: H)v5} gKӗѓPub^s`.Y%5UDa1 =Ͼkon\߼WJ]iv2΀:c+j~̶?@J  ]N˸p\t W/]Åh,!S)e8|0Ν8:C<t԰#~58v7=VI}4he, C #+kUwX AuXid #H0w^a_ǫq3z8sl$OꚂt1j #0UYM. iMwΈſ'y^CKzQ)=<Ӭ0E4:rxs3KǾSONBܠK|%l9;ءc8{l OLhȩ[ wz-SUc93AstK,얙@0}h"OGOGq޳/ǾZBl5 I()S#'q(~ C<S( `w|-5eaz`GX@F>".[]2ZRrrg_5\ UE>8ZEqt;qGm+JvXPO8kI(5T87 ;v/Lk0C "*Qܻ0vlY抦ۺG/_:f^8b*뭎!"8Btql9m}2whs7i0D8 'Lu]kF7>[&~ tM: jk] Y8 " Z9-dsP 7nZŝpg;=4^.|p 1z RmT3qpߦ25&WCfp 9w9iWm`v.Anݶks8,';XXtfes=:\ ]Ӌ].Pt٭侧b 4W~3qE)m|}W/_~gT 7Ԑ.P6eꙝŅpPt5Cny]~d9{>%@SW:-8`Bۛ/u@ [g`qH)m0_Z_çǾ/~bgBFZ@F?qOgn ;84]bo˴{(GſWifD/5EOǩI/g@# Wݕk8s\/]#'/$h*R$jJoSBx"9xzz.ɻ ^5>]h9tJ[ J h}0jMK"B|`Xg9@Hxs /W.=s.龜99BCspIx(RQsa@t*RPf:4Z2~C_9/ag@y?z3`Wxe\t W/]é#gp6ZB u)8wf2լcuwh@Uo.,nX046^Rc ØmEZr?gVv۹ kطp)@FKc1:%jb+SsaP/+ItѝANᵂB& "Rzc)AHg Xp٧&ِQPUHP4oSB#'q) dYKnj\8[)GſWwJM;̞V^F1Qa!ڜ3Mg@i.+hk.M."ZsOycWphXΉ r&Z IDAT.udJEM Ɖ'6P^n RsaPJVVHo~aARιbk`KعP#dlخ$ $Z˿ ہESpwg+^KK$D5)TM px1 ]ꈧ^Rc䎝ź߷ ~|fOg] x[2sml츍#$Ș5;Ƈ8rfXJ2 TM۔Ps;~u-x2 @63բXT|ٲpNe_#|&!Rb, U+. 0èd{R"BB17h پ VO̮c3;\| x[8qXgѤL1R)c8wg (~$wwT=riSng{5M0I 47?qPȔ`k̡)[|Sgvk AtGdq=V!o-gl>%zB_OEOGg'#\}طpd8Ni(JȔ2toSBω'pi 4& ۇjs?[ =h~썄]^kO'+$2;__0B>nRʖׯ Xrr}c>×.| W._Wt/KoSBgq o3L-(j b= Q{"QN-p1TQQSݙ/8B{ڜ3΀pMhv ! m;88s_y ^/^*f#AT1R`s8r |'L:F0ZgC@?h? X@W=: vi473pgCg뗞7.=,&l9b8pA-qpߦBlSwC@?n8Awt7 )NZ5&rY g@E)m|c+3OBL3T)eߦ&38m|oQvAP#Wqi 8}fB`+Z-hwCގna!$x&D@DBDcc9Z$wXz[ &""p?!P!SJ6UaܫArC?  /}hZ΀y h 4'RG4߉DDO^C~Bx2x`pB 7/Xtҭ o]?Kv? cJ?l7?ȵv!7BpXD+:0tXjiWS`nߦBd5KSIv}e#+1(lH?8P7Ad'T*ڶoB3`?Uh|mku &VfpEm !֒QH f6jpPԋKˤ2#480zszMgBhF4tk)+fG0:UWYE$bV'6jpPg?5}_7;>%zEaBiČ@dFX`284wN^DDG1ʮCX <>9r`J2 05j^b!$eQf< M5,=xO^'dʈ'W:S?>9&Ղ D?U@4mʪvBH +XgDB&x*j?a}8r(3n% e!  u7@!"ܯ U 'cgʑ !!`5(ߥ >9[T S,`* 9-=d&sbF:BHh1V_|79 =FN;㇎c ׄ "3T9B4T#5۹NTLKG 7zU]u#lݼ+j۹u- g2 v EAc7㶍^6`{_`m^ h0{اg^UpVtA/GV5EfjcAs'~B1tu@=ōYg'$GEg7_WyاB,@j]/`Ŀ +_b0R""'/aM!T _;ڜ}4}OT|i2G݄_hDCISLdO]ā~B`= !a\NG7^eIcZZ?k膾Y2on~vî@{mT5p^J=k{Ogsn?/94vx6?I,n-4EΞX4UOf!$\eX[BBjrCIxP$wa `v%/GSdb!C"Э@FjG!%_@ۤA\8y3xǰ΋ro?h5m3N?Ўip890V!W7X_Gra?qv %䊳XbHjo?0ԊoMFiqZ 12>:"QEk9X_Gp1?q⟐.da~s.y Y"@%t2/^i,$Բ)lT/c>Y\N>GOm!G)cu4G (̂Nkt`p[v<¾d.?ҝt4'.[R5_,#&*SĒ6NjA-yh?YB[OC{w;nT8jhTBa7>mެq[h?Om!dXM,}Zr/ڣo.VcJYUЙL7?ʍC3_JBH萐7{̭pp?!?Cs:"VN~)ތ(hA7PO"bw[Q izjϵ>J!B:4SLFDDpy;) !BJi7{|NE{ مRfj<3M;Z 0Vd-!dtILdN]YFHB1 >^Gߠ]('Zpt[3LI~Z}bWxn-+.ȜߦB&Xr =B?Haa S)t=f'v5E g-!d0R C"2t]2B$QWm8 tſsSZ6wdLjL^A%BHs@آtY' I܋EVEI$AvaÍ)FHzI14.hvʙC>G #m#U&˿aG1_U]xO]UIc?˒]zU? rprMJ]n6sVpC+]GFC%[{vo`0U4FX5?1F!7f6Jm^ۋaek)F@ٮ}T]ſP spvõi^xњol:kX;:tkc^꒱i4dxT]ST1{(U#2iH':v)1z.ysL ݰujUSz!!G[b!rh]jQUxvYp%$,lf֡h})WTi?t(eRV:L.c@4B[CU&?uNc [悫BH'M6i\Q@J*ڧ,h9Lh5@gOl!8"CjfoS0>]~32Q@Zr;w :g:њۡЗonܗc"!$XLhvTnf#!F<=x~WǶүKѻC^x;{4ޜ77q %׷.!'ivmi|V/3VgbP5e)u[%$87v(B&l)\%;227hwh~^G`wP֫iKyBRU狻2=M"1 ;1BP:nȖ Jv !K6ҡdom^  QK7#V%K/_զQ 1=G<]lbɥpXJ⸣6ZpghW", {oT2Nypm])tBG< MNoQKu,lCeJ!f5z?S>p@<?F16kvN3`=SjE3VgUPU+ʮנ1rwW?Ei?Pr-(=֕ IDATݑ@('.%8pU~?!Ą@kDt+`~!RE !jr9hs7=tGl/ь}(aq@Ϯ?6BiC41!MOzC* BvASH? OvCCL@/qGJY- T/`0k܏{ɾ|9V _ܸqs[M@tG|t)k7N?DfеrZU]L$ bMi6+a~suU !G5ctVH? ^T>dj5B{SJU?BB` )V 1 4]9[-<\/B&MPi(?8SU#_} 0m1 G@j9_9h!$HlDl%L%ӇaHW7"Gɣ6bb5lr6~v"x\;Śuх4ߟtƸᅬ^|ud{yy`1@[|e}^2?~ !av!D$$Vci) Xۋ3% b7cQW#9Zࠫzje6pW@]B&c1muƱ`?dw`>jL w$$b6@R;6~ס`o7n?4@x:ZaoBM[D(֧'@I n(JŌߦjv(J-F:T? 'm8``mN_mӑЬ iDLB ;4cqkk{qL b%βw9FwlC3@Oȵ aq؅vئl}UkZ+ !cM-`kmMpzoS 2(uP s7PPkZl܅AV֘@.i5T/fyͤ;\+~.҅ߦ*b%ES;6~ס`o'M]k? l][LBŒ]l%V˾5 @/4cy{124LsA2LZ6aE zwi:5B0>a^\sW/"q/ !09 /'ר!t=pwڽ z8|2D4=5pS=7/2ld&b発z+B!‹kw.BCE"\j%^/*PBH[ldVhx M3=VU ~\b4o !AC=&a#?LYHOO|X+)u8P``9n?BH;B4VT]A߬Bp}6Qa\:yh#!$HC6]GU) )C[  > 80wo+/~ҢRJOty?vv=Ӧ_0i !F+FݾMw$\ ; 5ash @"XRRHSjw"_=KYo~7XrUh4=n~[G{jJn(յ/A7;MTsHGJ!QyѣX %3恐l.hje0jo,rn/2HH&WJ B4ʌX_5,bp}w,u_"5/ƭEFС>]`~):}'4BD!!d,mJt No)8jީ2KDZ3 a/{⡆ g_#T<;_=nZ^[KjM[JBHhi)39hĒ~刦0k(^[xu.{?FXo%~ǔ2ۿ@ߣ7OO&-\B@@wm+/g!41noBXI.jMW6!pŻ+AOK7h8_.vs>YN3`=LHRhS Y~Mb iNȖ?ͱD\5=&4}&Tt)˵pgW D)$#`7]|O.S1(~:F{p?f6`1R-%7]Z%{*᭥WQmC'3d3:yh:4Mų VvơGwRذ^EMA~a? !DZ[W 07voB Jld5fz!7ٯ lKfjۓ!QSqmڥQ0⟄A:W7GpjM^>K!:ֳc=K{M5E9 @ $=;$Xj<"({@OBJ[}gg)sHIG-Q#f8XKG0H)qg|.ti^(m/[ɭҁF*n;d#%-?iE 2*t#\0ſ $֋/F}C;r<!uYJBi"Z+y?Mk}>,'lĿ-  0"ĽO kgD;v*GǙ&P#uYYxq&KC P.K>K!!OG}ZG{8)U⩕e`(ohK Սb t\Xb?UyQ0⟄Rz3U*=Etl~oB>+${pVͅowT7^_")6G~Vu; ĵx#A>Xwg{B{R:xQ@BBr۬۸es2)oK(Ё}J@}eȶBORh̿ }@@=yuɼoVBBO؜(%lnGwa%7_)W NXH́ݰH4ckO~x@"Xj9 ۣ`(mM' 5kp =?~|$>/e,i*L[nS*|}m$FB  @4$p` >wɱTPWjco1Ĩo?|Α*ϯ| @)MEg4Os8sF FH~z!>#+Rc_ Y .uo}RL91(w&#%}FJɶ|9?{xu\֑I,p49-pS{|$#!ۻ:\/~?sZ6 `n^T>\BG4Bݱvk52bRJY:#5CA@:^yk?@,7t1l%cſLJO98ltP?݊ @M~旑pbm@s`&j@^5?VB_ek`˻  M44)=>|Α>I~y go銞JBASԡ1H&Ǜ"J7<\9 `r?($.YVv`цuK0-MZ!T X/#tL ]%hw6zUwf7vDsC?{JȰ芞lMt:G@#@jv9%mQ@\53v[yT%8[|0s|,ڴT_%H uEɭ!_5EG,o+onM'̏`v9,?t Z|yR*M4Y&ƓӝK.jJf ſxlC`3YZ5RΥј-^7xyOɴ+,&ZCps-w3] T!H]_^)O\D/R5Kӵ>K! 9B0+E߀7*wK$_eC }?Rk\+cɳеIDzODSnR.c6o<^{o'[`'Ry ;.%>X}w7>jF4ψߎlτdonzV o>?X|ߢ{_ii@;;owdi=}'0.<:շvc߬$4Vl Uu _CU6~ЧG5UB0`\k`-[Ja'#~gL?[;>O?$t @4(rKBE7?4yRa{j[ױ[.%$kƯjF:|;}#2 $p*9$ 9g~g펄RiB$V6}n!6qԵ'\|5W>L) dASsF4MJ_wHBJedW?Xwv8H8潟D߁}?:[[TMٵO}2Bta5<5U|JN/1US6q|uf…Xb ܯiD;;ow$E$t7mkUD^o?_5dr%a}QO^;>ʙڭ;MtsA$` #jrɶJUܚgV)hN՛&Лϛh7~9.3M_LpݑP"!5S?goHX}kgJBHx1+H!QQʮyMR<^} ۄ#/`F?SJgH MW@s9@oAuԔJ Sh_cXncm踤$ǫn62룶I[?;( .JIڧh$g=&.vW89Խ^v&B[j{\8g-O˵_oc]Ty}KNt, :.` `$Z44E@b/?;Wk_'#0Xr/k:u׈OaQ2 ?Ic/ݯnoN>TB†i4f\y# ;&ħk) "{ը| {7JٌhU4M79jJuFϨJls?=['Dj=}+>`DWt(.~53!Jrғt]nCt+o`-5B/[ۜX񬻌M+HHD tcg׿Orr+i+fìRᶋ1nQo4ZzǬC!وk-7ń JVҞ W^_xV7чP;7.k .$o.Yxm"KdhnYQ>.| g2hwhFug=ѭ@bWZ-/7+ !nh* @'+T+Շ#Qض>mrsJ@ >1JcV-?g$)dUgC8D8w}AW?BHm ޕļ'mORīGהw1\zq;*xowO|ΤQSOPtEOItꡮ:FKbj^ċ ^i;-j4m2έ*6o]xa1%_4L RotB?ASċ?ftVaOF#& `oF$bkŃ4I[7jJ3|_7 ^? Ywمfב`*7?CMpֿGZl+zOϣ?b=ZNj^f!d|GUأg͏ېMG9N S.F%XRd3zE6`.o!XM5NL~5|-OjGkU2ſ]?t>ZN?>+z !as|s]CUnuRx?o}b)''mſݬ?@oŨO |9?%8mts'>+z̳n`<84Q뛕p`̵֤XrqujZo,ܰRXj(b\yl݁cN xo!x$-V-QOPg륿k%XX=fozZ΀{ƞݛŸ !mB@ :@5GQ=**^}v PŘ|QO͔ŝ+ʮ7KZw$ڿ32ײSLIp7fD'`4 zEhJVʮ@sںQ(2Pۚƍ!WIۊP߅9  L-+9W> RROo\O{dȬlj:ƃ(?d!dt ΛNESlq7PQJә?\>nu,hI hM%k?@-e e'TeO?}#tK2 ZL@kcr&+Pex⿏.&@s@H#%R2Rbyo?xXy豵?Sx-?}qyUuQdIH'<ٛpvf_ܳf$KgV%Q#+Z(Y%Q$3r@#4:W t5+v}?jV|ba$,c @`oI"f3*.xZ[[uvKGWP [4Er׺cmu[m4G@U(-˿dyseۗ i:.O{,1%tQቴroÉᧄ5S~( JAIi~MqRީ9ھ2sc8P?s_v.~;/<8y:CDMߴu̒cN$m.F =μXH/ P)0.]66OY.sV74TcL?d)=>>@WZHi`tUrGw!-y} W=g\D+d. eZK~dio.OGަT6QK}[PJƆ}N{2+_pœp ":䢫B:kW(u+M Vx͡ySg-^5G++/P\]ۑ}:6p$Y,#UK)8){5?C/fcss,_1⵿hheҁ|W~/M G߻6 ET*KINyӧ%\B @DXS\]/[2bt;lk]h𯽢k!5hN+B ~D2ct0=O)|0)a 9Cww`Xdfu+Uz,>PtWuMfI>mXs:?U] .CcJ(=>:=nKlOX]~4v|/R>Pt ~296^/~;uK5 "O=T0C}NIW~ʬ\~X:(KǞ7ODI ԩ*,7 ^ICN=I+C[-)O̤PN W߉Q!B_z擱BRߤ*Wȉ:. j__o%.UT @?SzZ^#L8'ʿ;twwHe}:O_;: {Ю ȯR.7B 8R/.5Iْ*W1 `2L.w]<!!ۻ'88G+ \O*m~]ZC ߁;J0}Ju/O(zRrE1[IQ[Mzt9zUѕ,}һ3K{igoވvK!!C=OWL]*L(e_fgHuÿwWq< 2 @""W_rҌ(PM 87;^d67DR$q:>-bE3)Lƕ;  @W 1F2L](Ob~#Eyܫ`$Y̥f8Z Ӊ ziꝾTcoh!5W<?cd& 5mdH3OѣJmiUDvdNyG(5߷Lr6 qFۿ"^7VG{]v+ 崃Hu 􅅑wF|{?}5^ DIk}|m;$|T1☦!x}h4>@8\6l(GH&f7}I]~̰ iLy]ʪm2* Kn!~ю ^Lp #FNa؎2vo(H? {? Sq@ Hχc;nY?PtĈ# =QFJdrĬH1*$0+@5 į昜/xFb˳??vީ.Z s!ۻCwwPy׻#'x?n>9|"?!f8`&xL( VP L*l"eƀSV|Bw`N޹:A`(=FDIOJ/}ҹtm Ymءз &zǓ)hIBXT  PZX DG5ѕ.%u[2N a: _$:顏a{2]8ZNh#_ƒәf܃,|D xU6^xAق-0QfE-+LiNWTa/ faCa *L2dUc,$bO:& %QW?pk-~#Ep~Exs?"*EZ] 0@b*,ōWX[ǔ@qDFĜD$ഺAMb~[p%_h6QA) |[_]N|4cEۭ5>EC^_#&VIשo-](t$D ?Uỷx@giD*N]h![>󽟖ESo(pxɓF@(W [:z[%.BO_R Ш2lOE ۲@M'>eVv!Ӝ*aJJW14qw/ Pu xzi&F,ۿ_Ն7yRBJtI?꿻cq8U$ujmmud}g#ۭE <%N+<1(?iwrv1RKsE*ݥ'1I&/FH~l ~aDj[k*#Up+zOtjE(]I{(-C~kOZ_'^}QRXY4y-k\lK$m.C7&%7ąt,ELT#6EpC5ڸrru_Vusa(j紐d>8ۑG(Mvj5Ys,IDz{?8G3NOU }k}ȴ %&ӗv|u˟ϭq8H'&;3qj7? Yv'7Z2771նկ(6bUe=pXFG%ɖwy>&|gI,~qZ#a_K5 ɲϸ\8(E"| W&E,.yſ^=>K.~@r>o/¿ 5gc}928jF- (|;bZe8Q@¿-c#d{C,;(/g5YsR~CA^.f(z9 %7IsGrf@3 x ch4#F3=GRϔd^:4L@Ϛ`#6|t!9'+n.(N/uhI@"OO̤DD(P[ Q{4C8 nj-g's︺QOlr V>k@%3wO+z'۠Vο2G7(/X} ӮҎ"3Zp^zࣟPELjQc3heAu ODLU^7*T2~1/#^8yW֬o$(JBV,U@Vd1F,_kӗw}qWKlֱH9g?mM9ƅ/҉Lg/O@ @T|_=Oٸu7q(oxo@TwNs4ßӾ йJf"Ys8|Ϩ|W DNLL+_ P ʄJ BDz᷿m( !W3Fg{+0:1Je5[Y=`)H}}N7 ۿDWlɲL2IEe$Y"t.I㋣48Kc Ô.W¿ 5bF8l+__z?8?'iS fg\{·dG8Z@BV4 *=cs~ken.&S&h2>F/G>J멹a-5FxbL&Q)#f(KRV̐$UE7T|1#eI?7߾8\@p'. g}eӻ5aM԰o3<_-olZMV% "4&|j%2?KW8\{w y{r*yg=$ȳ:J@{o3vZ5Z ϜyI2_NU(TMHL&O?IE8jhZ- @ ¿M;5AeznJn_%7i-2z^ն,:7rJkIv7P:4PPBC pMg~?AeB x5ߖM$,/ܫڬSgbsT:1޸mp%PmOݡ^$s6D*/߾M$!W',d,׆DTeN@2 ^Pw(_<* ҌC!B|ŏ7 ۷鐄;# _Er?'CN2ѓS7m1"Q"E ۷鐄; JP''ݳKzbE"Efjn99mY{U=mm,=H-i?}d{#S]d~_p85BNgU\ey\fpA!BIGG4'#{6k-V(* C2)o_;tTPDŽܫd.FS=$ HC"QJRÁ8!#zARSi@}]ϼ?P@ti@m/L|6ݲ̒^IqῦTp ;DY 2_/n@2*hEsSgA^ap@$!^S@i+=8CaSg1>7H=rW p8 P4z>mdH׆-Or.F#iXYῦTp ;fy* ".+rOD&q8 P|"n䞞xv! E[!!C)==OUp %|zCnbZJ}'=i5"d8@M¿!ld-}1-I缛J78BJPZl^Ƙv7_a?wkiJ[O?PCthe›SX_|pQ_a;Aq8B0Tf>upW}'2n7["鿍p:7"".5Ms{C(:@⸕B']x9ڶ} UzsUr DD#lH<2O'ʿkq<\rlۉD±ma ,ν_3섀D?yv{Q *(| _o;JYvpFo{сgIsM_{Ho?'ЁX*[P;'@D™MdQ5x!mǖ_) Ch$3ϼ. 7 d"@IGs p GVW@ێ-R¿H I\=3v+&P{(=B@.)ο2x$6mUc˯@4~ O{yP;6k_tTL3v.H\yo(v\gdz۱WBP  Fɞ~0?#%sjyr#vU1Po(@I`3##DyMz۱WBP  F3 ٸm" PJ',tm`}g / 6ޠ[~,Έş`'4_q1帒<܌[e^M7mT#f܉'wڤ8 (@L 7h$ș\n:' 5T_kֆVߝ3 3!#~>I3s>LK'UBla2)ұׇIy>. ;3f.>OəU6_?NPn&4 0{ii?2 < CɌeLxo6 NP#6z|65zlPX)+_0e~_aG5p`Uwx?N`Ve9P `}/(8s$WW@3rl/z?AGǠӬ YPxs<྅l7"Z C%fw</X?N`31VPrT5*xÙ L {.;xTE ])(ÿR rޟRb Q CRbwӽ?9KZT|7xNQ `8 =M}ibNt9""¿Asٗ~&Z V&Wa"lp 8"|z"-%f$s.73\`W!{NXc_JRiW_7xZ[[;;](4xps{d:!e3]mh1 mq[ٖ7/W1ݟ k io? q7/ %Km;H8mΟ|U27HM8zlw .7q`&0zl'VwWnz1p *"vemd8ࡩsf~=а_UGh9ҟ_3#'Pizs xĩ}  5 J~0~JMO@*QOT/9̅Ň9"?T^ZzÇlzWp |/" o^`a 񴻭^E@ξ4>X<xƠ.(y=LGz8BJD/@j-^=Ld_\<xJPnb@Jkɞәm=83/  @әw~4y%K(*`@8إǒm= Plf.=F+TMnK(/Dkjɨ*S#Jev?()  @Rs}ɔ%῰b? oBR] S_b.7s"A'*ZՍnvmUͭ\wrߕcكmu$M,J"j<`r gBS๕sq#JoXM7cWW6;+N OO`D`I,I3Oܗ ]n>xoaS]{gƅ'}d\")TE妝\RL=uOvY83]E pڳ=[vY8Xݹ"7v?- rdPhe]8>MŞ7f"]?I/d?qx#T@)]=3eǺ+7mG@C"R}#9sSs>+ [ؔxמR $+瓖#CPbRЛpH$șvKr6{FE pڳ=[*9p׽e¿ބJ  QNY#HL?pxO)P~ TN<3I3!V:wpdwS93s[oк&{Z 3*s ga1/SN<0(K5۹ضc۶ z@`U1@rmu?4|g=㈯`u@/@ ZMrѮGHFpO|ۊD'{qOXUWEb O|2>Tѧ=Us(oY-lkdɮ> "?cLЩ_;}m/.'=@EEy胉}1'.  C1ƄSϿ<^+SI7GF,@ !2f|TۿrnOxc&EV,зwm?2:O_a$88Q9ф"?Ե}XS-݆OyoJ@ !$A^p>O'^Wrͺz/5wRVtI}t  !줬4y魑=>Ket?? P*([! 7}va {]}>>,  C ) 7}vaw׎GPAZEJqva(1dgBO8P IDAT|U@/@ƅSOPbJg/7? P*@ub &Xv#~tIN_x,59tK?LDzKdԟv? Ցc#u<8evhԮܛ>E&r5l_+@:%Sm nsw!MGdeh dECmsɱm'_< Bb{ 舝mѼ|ٚs4Y+1Xc21\7?P .Ӯ;+(>>l;O[ sӮ[? `ˌ{d#C E4ZF?gDDqYdY;5T_s_ApE0O՛yC @J!h#gK#ɅTZo4m-9Y@/ @8iq#01?@W)>/׫[6<^e;~rK8Tw%Sf-ev+>d. m> q{J_Ap. m'InwIz@Y PNCDLKxg[nXnMkM668n%3z { 'Ro=s!)&QGa @Ez\%1D{ M_88"c$ź~y\˿:{q*pD(䩊Vhbt ߽xю6AEr?&\zk~c(@@nr@^DDloy:6Zw+P_A'fq T+s4^@dĀD21rp*(pdls=,wyx=_.!^88 z z(=. ىSGܸ~S㺆i% əޓOܟIۿ@v@߮;!"%?E&mkZ\{ٚrE5pNamO !aI=' z9?Tzfj??B S0[&P;)`IO"b39;ʵS%1G>7I?d/,GP+خ̗ $~wq01di>Kn2hʿ2[oSB Pifz \z-7mkc|,"ԿT,sԞ9* FOn>.U^W{Is'_6Ѽae{z1Ƥg:}JHi2@@~a29ِم>_\[9k~Mٻ/9zV.d`+h7]=`a 1&8O!fx^v|C(ߕP7<^}b* &ɹ́Oݳ8͂2ٟD( 1 11#'Nݶf岦qR5f &3ac{>ğvyrKiG(@PhyO B{R;6\r39 |pa!椉}?p_W?fsp z& + 셥kqmt VBy}!,sCW'>N֯OLw|(SkPc; Oht_Xτ$;|ٚM7V zVpa GN?RT@@af0$@}Y_ ,dO}h֡j PO$AMv~~q!IƁ_o? 'Sp { uルA_|mzyoD/UekӝkUxM˺uΈ ଼c%g2}~3 T~FITz@@@PLJ+=R0;dltCpYGz šOf{B6Su'ӝ_ | @@_!yEsm_6۴&0w^Gc2.&^x  +ZޏU"=cezX#$&Ou.\Jd>]+M=ǩ??]f)w>:$f6Q _'B@@ P(rHfc掬0/ٹgZ $g|lv U" kl 2xU-kZc@w0Xr:wU"hߢh%vu{F~s7|d澋o/$$_ 9Z \'B { 0;wvϨ T}$uDun :Q>5|7mޭLb/>W:̫kՁ_-Wtr$V]|vımONN:mz@T@J$^Xuڵ -k9z:ә^g9&׎ݿDXa (B0\BM:ČֺȚ=!:[. ѽ"I􂿶mc`( jcc65N6oYsV Fs ~O|,5C_X%k?Tῢ"C(اLo"k]:pR|hGl4Fo}1qy"֙gݓ 1- gv6omh\̳bx?`T͖Ss՟F Q޺ѡQ?pTyZ=PBJ gmu.Z78|MH='c<0uV>3ճ诽ɪ{ Du=Jz(_7?d'$8|v7nCO $A9S@bVþzF &#B(9wvofG d~xF]P9ZRDi$4Q"YſuEgĒÉ.9f6.j`v\r߹$.>;ض'''۶]sX;Uvdj OŅu1@)h _X?⿬]3 FRb:㣩yZ zA_;j$590)2a/D۩!ܾ͑ԃknf6Ik{C0ٸh?K^޴9pu kxeB'Q~zM?S`@@4{uO Gx֖mlm ~e؉#\$ӆ"?Q@u [:Դ'!f`DĖs4=k*oviJH>?pr'3P??@E ?Lzh' PzM+ B5W_M+v3ťXQ}ګ_w.(&=s?eMhe[o/sH#<)'_zkc)'ȼ%3;7V`@^zmFDtg?itB۽L)g4G@jŋ|=t_^gw 6: 1-,{1fcWe3k! s۷arI,͜]\rٚĚֆ]59dQ^\_>>F,2__٫DJ p,ҡ֬oX{ß_vK3dA^XJٸҫvW_wgZ`R> 0> bRЛ@;QdzŀHӕq׺6y~q0fϻotqjI {C #tUdސ9Y95uj~oe5#Zg;I_<g7ohqDN@*3,ҁڡf=VzDo+ֿ4[~P$A[_~񽹤W%2ϯ^O] ^' Й?s 0o(B@C"D"M7on_EW: Č8>ߗxCbFʐy׆~+] D:pV X\s]eTl ruD]P(Iu,`"bF.6!>OnZ7Du@@)+z1-Oy;@SF(m_Wk7LzdB7M0_i4?| {%P,W:K+w~s_6onfRTcRj>wt-^Z Vkr~zss‹UzWLV Ю`r@رTpFs=SXjy2a?}bo|,5K+a^_}_}3OSz7{GirvѦ]1;rM~Tʿw^'ս7(@g {=.:Fd^(rѼǹ({;f}$&,u;Ѧ3xr ڞkx@ L `v޺ek)ԂLHŲGOޙh Pqk x@ ܪ,hDUFJDYMO_O~s*! B|,㣩y* F]?UF7[( @Ts!^hSd͵{[nhu 33p>ֻaŌ%kҫD:^ QPmFDtGgz%*4PCČ$v=vƖ];O[6\#O ̑'NI?ț~+wr?=Pe1X5@=i O=Cz}!HƖqƫZamtcO 88h`t, orj?O?Q#=PP_Gz1@D/ݼ[6sMN=_zdIŲN^;zt.ЯQiw' Y<(z[J(㛷4;Z7:|ꍐGO}.SqW}p_~uN' <(@L& R PI1\m>op),؁΅2_M[O/kb?#9{'B@yʭ `@amqם۾;lh:q61ƄB=ֳ|`ɓȌ&+w~O3w {@Y9hueJ "Y+ D(;~>pu˦`W9z#Oؿ8<8rhdzJհ_nl~.W? 0o%v|m44@o@mVro:,@w޺e׶7޶n{DЯ-Ȫrνj?QP?Q/=v℁,%hwQ۶/~{5GVg9RCSLd~OOu`@ P'@Zt+^|)x۾?lM,)Ad, OOgBnZ9S0+eJ **I`(oԸ۷}k-np qx<IDAT`%L|$cX.InZDzb@*PBNzsj Wnr<TJHѣmL;-OO W  0+lA¿mԸ? W^Ưq< "b2Kr2s2PүI^'9;& 9F}0"#?:< +l%oYȉo"#Mܶkvnorő? X2;8|tG.!foY/7}]WC`R **"8ҟ+0(7}m4\oٴ{ݷ67~-_n_Ꙕg3󹎅D'BJQiP7o*Qj?Q{3C U [E@] 0+M(X&Q;oݼ/m˛nmh^OI BRMd..M~7rh,fWl@ , Z- 0*p?>;>ODr+hyc-\[kI,^u&'b'㣩Mge>O{~ڡɷ^VJ&RGDqo.ʆ-hxsCsj*BZN. $GΞ3@ơj~-]+o9@PYdDfC&ɼ` @瞗r0p`Pnٲ뛿ng-MnF,2Y3DddzhrǸ+@a4D0boT<Ӌɏ;kJ8-{&! ,HMǛO4SS5UTTe$0޷^tH/_UNwsXK_`Ϛ*-Se~Gk?tt{JZ0ׅv /𗽃NfU@" 0Y!`TqoƽESׅOo6\T6R?x8nso߻Kۧ7ti[op ˀd!\PF@7F)u 3nL/7*n+KU&+H;ޞwhٽݟ~OC.gg w( Iy*d `R]1cp➼*܆S37WVZed~%b絃}u~}_~8((Qi7,RdÇ _VDˮX{ߚP]roD~( w? Vֻ+Ͽ] hx~o2o:_z~nB%}\.`R h_n?/sFcvwZWkwjsەY%) Vֻ~_$,g0 8 ˀVWJ+ˀ{wֻ;"Q+-jn\ީqqpkۃn~q(oI웆}B?gHM`})Wi5* ckG_תּ?DD95۽ڼQu;RgE=:8kւ~:n'o9Hz 3ʠ ɿ`IA?}G"OQ^;{uG[inߴ]Ҙ?!0Џ^dp<_mn=8ؐX3iKǙ &a, 84e [)[iJ㱨OкRkߙ^n^-J˽kk-<ZgrE? y?(?/)[B?\0p$XA_w߀H5]%Vd0)Sjof@DSKJ˹֝%UJ)ϊs.0㭠i{dgh=HlyZ E}{ZO ι n,Xۚt+Tw{wE]ӋR}Tr*M]}EY- h3^'x݇G[כ|%eo,?\`pL!0N1PvLYB/^^J ko|F%֥fo4߫Uܖ6yf/XTeZ'/~0w"? vvv;H\h06 20BӒde@Xg{'[Wjͩ덅bm:. {m8 v՚5g֌X3:ʈ0(AtwViw]/Bv־i/2_(E +ps3VWdlAU ->kߧ^s`UmO]ϵ*L8 {کvŚ+VrԔUvUKwA~Џq >zAnx£?`tyhr qB}VϻI_ݾO$QR\.0z>GǢ8zʈB {vo-cR*ʴ[4[niij9VrTMYʱ+VSDlQue)W٪jY&J˱#-ʒ;R82""qD]% b? ➈u( GAE~<w {^'xG~^)[] E}Mf~I#rQd"C*83D|*(GwHQ$g8S35vô(qC.(PJ%i@6H+S &%@rXcc]+g"8/fnY #7 0I5KKK# H"Jθn,k<.ԧi[q )3oDŽ}8c66665r)S u獮X~9I9KЯSO8鱼'pҒGg=iҾXj)L'~y9;&u GJs,cb y̒q¾~ެVOнnטKbwpZ"66Ny0ζXr8Lm^Pf@m_s&8u(gIjXV1/;62zN^՝77/26!k_7FYY犄2orlz.+R+2F8]>cIeC$Λ Exy5yO. EfZdKn7IEvֹ+ ʾ/z&TW$L2ܿBL.SB  p4ƐcYC3ST2ibb&E=o& bqC5藻X-"H鳰pH^Q mM K^qT:$ +qv U]#|a0 Ӥ,[IL#jZDso'S7l,ZbJ-/ mG{B; kw$$`P ӤӰ@膌p@Hp `)E&[He߈M$ߌJ8xya,Z62V@2'{tbm 3aRyb!dn.c se,@&3Ldoxw\eD}C'z8QЙU3a P:]}#CK L/݊6 ǧ=Il8w*\IdyϹ^c=i oYȧ շ8B Dxd [= #5|ȹ H&r l 9 # KЙ?ַ֕ VJ@?h"*th9܎!R  5o>~,'&zx.X%=CTgW`,cy;bSOH_ ⥯ϾV2sꃷF:g |1I ((ŵ&>wѫmzl4`/|ܡ6L5i -Mc ģ- /;S̹ .pRg?tg=ɜMXx J)G( 7=ck {Ç#J5S.oWPB<eӵ߻W~3Ƿzl[r>VGzI[|1 ˠ8%m 0ʐh`=wI"ugz|[L&S[%߹74z珟~Cr1 DaYސ,az,3_ԭ).; xǏOgy^JsL5p0fQhAً)=5U(1=gOw\^S]wOjɭFb71Ҕ>t~~nR)d<ǹ<!ry~xOj}vK.;M pO?KŹ4(eYEٴA#w`[WiVok@h00߸N;d:l"wz.qj":yлH|2J|*ueG8;БG?߸籎m)PeQi vOؽ~X'xh놝J~{'O &%b A>ä xgߩ;'^ws8=gpE#ś+hAl+>=% bEA D0gScѱcہ|zF ȡlhmرwR-: /Y'\$~.!&"עWS鷶z\EyBJG?;]H%~OOhP5?{zLNh9 8}r@b@eG#hi yϿu]}.vOÝ1}})ҁR _㑃9?bH?hEHZ}_Htb} b4½}6{񹳿مEFv(]cLV܀]g>{гb qDtJ̽}fG2 @ћ^̹%\xp=9Tz>7ch/ÿ~ף/|~ZZxo+Sܵc2 B$.vGe :؅\p<vg.aT InsmH./wGC\ÿ zG:?y2͏\<_ey]XmT?ÿ^\K|ۉtZ[Vp0X~iD2SX"gBbC{_>zw'&^ !-#xp. z΢ߏTjCmmfמ0r`Eofv.HA̙Y$8$^ž~=037ARK (V } @r.}w \΅tkn`Op9bc5- O uGPb&5`2 F;nHh?8ƒ#۷* (|A|zoVzzJчGNL]l((RjY=6'D9vOWcfi8GOxr1t6G\a;z7N$g2WV{51s}=<I ?.7Hy{Bq N_Hh_IMXj8 ^q[Q<9&[eG٧ʏ0ڏgpu,5,JǬHe`٦/!j=#?Wo|]9V$س$T]4 p0,I5ǽD-K%_/<e%A`2G(ws2+=ŪKxSm=]l@BZM'q$Ns5!Bfò(ID6e2=ß40z1K.vT3 Ž{ߋS?|oEF#J)IJ x ZB]A؆Lhg{VҰo3]gSY; sQ!TLao.Hix%ۊ[`!5hv vXH3m3 {1Co~cwVr蝃?$dyWwQzi- !컀}X*kHo1E^C*6N,f3p"mKu@7,fY%]pL| Q Αqfn:VgR :Vt< -1d4`W^<v Υ"=tE @vds+mp@ȡӁCwd=P`n%W0Χ#Fk0,<^emAjPx^/]BKf1KPʼ32u܆}p0Ϧf*W"b$`aYn]WM y/]fY Nt_.ƿ]>d&z8.콀=J~u;߬xZ-%#j 9H`K% 0 $L!M3G8Pj!ơZ*žN1r/_2a[ ltCT%8>sr1P@i*v uEbX\Buum˝3HiI(8w//0hwdj7iL^E-PjA.4US? P@a<xJQ&!y:> p._)vG8覆E- pi]8 G{NfEH^D"nև ~sxTKKFR;x7pE[ Cwשg1C;[hb^ZK"g8 >6O;BX MlwY6?ki}.\`j ׍g/Hi(E|e/2> pZsY4$^, "/bo>x%T)\j'~^ P\J 1AJKA;pzv濥 âXL/6lK+Y{dAf>Hr)|/K`U &RLD6ǯ$(Co?>Ѱ߲ <:*ͅ ґPc`?8wg.PRPAA,,AEG"l"x;0o}ӽ/B3uo$9]S꙾dge*2Jr˴˨pE P::F`9 u sƺRțy)m8;pN\ɾ*!e0ۼ5oϕ@tL'FJY^ȣӆ=]{sb+ ,|:_G5 diwbHagTx|@w{8ԐxBP6=2j98{gpYm|ȗ%l bf IJ1j1i/ᥙ1+hn ٫KgWl8jQ2yx`f!Ch@Ǟog⢰5Wc0-^;0N_9ۯmXLj <B8(lrL _6V)TQzHwʆrŽd!pUAGBH8.X2_T Xۀ-SP4zQўSv}߹2VQl":N,̧1aE-j }"Hr i#2J5x%T 6̟ma-orC 4?Ɓ$^ܿۻTM5KJj / 2,tl"8:8ag/J.6\|zy(RU}TXc_-ydE G[otʂc +Dhٜ2 )-87t'N \ĉ޳y&53n1~2l"Xګfy#]{}Z\4rz!o 7geS?Wal-Vo6&B_Ơ:"L`%_go+=aNp~.*Q|!rFi-1 gT/Ȃ PO#Eőv `sjl] Hr RM1;VVM4_HV# fm7 P")|m H0Op]_CR2`?݅qLP 艦Ofz IDAT1;@35x%tȋжKa(X+S^[Lg(2S3Ao:`< @z` ZdPF֓yeN ث  !=l )5/)EK]n' a>5L˄$H u}v u 0̭ Hz Y(|P N*@)*YF"T J `ʋ<6C-Qw#aF@'vy.pnHjqXׅw>q<ygmZ0/@d 撳HS8l֯n1c&)$ =m+!~Tg{PZ:keZ%==WRXfz^Ƀcqg)ҕ36eՏ#XL/T4"~0,n;2aB{[>$`ZSl0Ui/+ZNNVREj4Iv b_uDGJT$qB0ԾKHeLYN LucWI)#v U"񛖉~{J}3Ɋa4–@H^.W_aT_kiĎ K~ŝH@`1{={4 }]-*Yv-(2.YT`ιa@ :]ЍV$0Hr P+~#+/] *E5*' b aoi#ENgd-!έ'| $Aā8g./hƷ(}0^pHdO!!x_pI@[:0tU r"Re"PDSyٔմ\JR]Q>_Չ;GG2u`hi_5VS{Ù;pdIW9X}v*YTy̤q<m%oE̦E/PN3rC?n_FH)i3 2ph ]^`Y4E)^K"o>p: RașCE+bj y^ӌHakJf ,N`' `2.z[̓(:DAm&] vVSre~=N8*-sh%ZLj2H~*%-Y=OН0N;[2l! (Oc!i/!219P<(J 0$8~_obL6VyeْZyXR5GTSGo}N{ikHk2u_Ղ/T)dlUo ^Ň0x^f˘K "ȕ~|8 n8aEXd(\Ux V_V(i)N1A Usd -O95cRH11P0H@$5U[%R۱9=>pcJ'W/g}'/ Oa.1[ EqQLؠ OoW6곬Bs߷ Y'e **Z.@k ĩO὇? H*M+gg횏1"/)-NR eg"AM'nImٱrKu_`IgwtۗqRk@y}e2p)H0 .X>fOc0Ʊu{V!v'E0fD&4SċตBJv' 4`nB(H0$xT@^ZHJO0\O*>Uoi-I2gq(E. PvL"C=Y= BVmDb&>D6V5iZ&,jcNR\v/c&9Hj`{9WY(iA b6%~2)Ux.J}7Փ|q~v k{443I' @U.D$9x3w +M!T&ld;pYKme(;c===(eʴ 픩20B?.HۅدSBl"X:fW ;$J ԇ\j وVDuS"s"N3KuԵ_1@MFG ZxX֏t:3x{u:#'q|4z+fZڸE=st.|!Wu='hPx(׷5XPkS{}T_Q0T3f`me1y{|!7o>=8hT3KZIӋ׊bS2BO5a 7i9NLrʕvg8HjX'ISbN@f>ӳ&8em  E^G<Ɨ1ܱN>\by=k\rXH#fb&5֋Πڻ}`2>|ƾf*נNtm;JE#ٷi}5)#NUO2MDS |/a_ ±8}m])g +Gyh: Z:lRR t wSZ,;aU:if5c0T3Z63v*aS݁/PI2m B`&+:<=+7`RÀR^V`! :6!=Ahdۡy e`"vDac>XZwуfP$řKm'p0 Ԅ6>lپ hf_Ss8=k ;$9q& H:0#0L;+S/+W{h)8}R @$rI,[^ܐbnQ1GR*#HKRxcc7".iɶPo Ěq.74H3o.8wbƬU(c0"P7sxaYbzr_^Jh\Kaojeى1aOa' nҲ(s+$ u0;+y#]D2,l]35xe/:!"T'*DTr{QroݠMުTX|j pP4Eإ*S:0B~r7.,a$]It|/a69cCCuS_c(< 2l?b*>xƒ (R1RSue{TWs&$&iH]T`Gwhczv׋2=NX[ ʫۆ8Bm 0,eVT|_c s*wWYWa(la#/|m \n"sn20ǯb5ikcߎBВݳ$&L'{Hż_^P9#w( ?G}ecX`jҳX)WF7Uk*e~<XHU楯Ū@'aZfY݇"ފެnV'rwR;Q*#-;lAM̯fEٕ"xVF!hq>h6[Bw[/zCJR3_TD{@[={@3˒!F8V/ 9}d]X;PPŮ]XF}}w@7u.,(LjTi[mZjq.XCPZ@]Zjo50Læ*P0 ɾih}S],~7.@U'BEP/}!+OxL'TSh+Mۺ;vIU=l0Kegp+~~9rN2Boy}{WnZ \y> tY_]lfU׫Z_"jP$ne^/\1ZZ6#뢷B.*a]-sXޞ|I?DNp%<`R @e(| -~X{#X_y=+(Z7r"\\C|);MȢQxs@ȼ-,d W{jFl->[+@e@|,b>vʻ,cѫx+%VB=,',. (~;eIހaQ jٯtj"w:~sV%ԋͽMK^a lecAyŮ!%!p+WKWP<ޚz +{v(%FYJ}ZW'Ndԉev*{[1flAK^O=_bݺ~Mso78Wb:5-xeTX,Hxk-OL<]% SS1ɅhR]G61;B\X̔1=,qͧ' ;K%-[2YW}&?d(A ד/ie˺a̾{RKe7r(O3v'bJC?Lj/!p<;H%$ `ĿvR[E! aXe Prr2Xrsm0=g T&Rw#g/J _6Gk3W&pdQ Z ;#<&b7r4u˗宽89*f_}дlef$V73Z_$ jx. hN&^51<'hj{LŦ}8ѭ6zPe޲}5d~FW/CGku}`!%,^K?@2@9Ԁȕz@ǢWaRb~ī9F@$&rRހۆ nUe_a[-]1 z̽gfk-r|<ԌM^ w#dQ­ ̧")F`J1(ETp}*^ LD*XMNc&y PU88Cs_5a5A%QhjjIf+\Yh <A47 %]1v1uZʛR Tt^"=a6Xtn$N­da)Qd7 e1VP('4£Nj#q+bWa{ b˻Cцlr_㋸?o.8¡W >3^Em/+ȯkzuNV[ :V,\c{;\fw1Q1$Y" ZkUSq<:={x.ζ^}IGovl8C5U/ttRT靭{3vn ZGj:kM=(l^+ԜG$7E^늲.jاM ׅo]?W0# 4-nBYw&ܪ?\jq; R|Z/|3wF "Ğ0s+eq?&'3_yn!(ƢWs̓V,j#y&W_DQ(%@ (K*A 1vW0C,"y:ܾFeZ7O~67=+gԬ&7,&a}/4WȩB`svoQ|K{4b/?sy=f/ڼm8:p #gA0׾t`W.GJA7h+V6Vw_׿4a^d2KѨy&*{,r슯Sq@ 4Rah^1þ.d ?ۑEKW+3TϿ25m"<L <j hh1/ua+Gb2cL^^;"Xa 6_p[1g SJYe7ݘKGS[k&YXV>1Zocޞ}]H˲6=P<C4-X]Ӄ_1oŁt޹sĬu 6 Eb:18|ʒE)˹x£ߍѫ\AkDs 1ϣ;܎$ox/ӎ_Mn89vvi$"xqۑ*4*Co G:u[3)wVFBu(إ 1 ^J(D^DׅW?P<@W=iGPK]# o~OC3 ^BFqA -^ǡTKa_qfJ2ܺWQCc_+/do(AE96.x`ox?,j° 5$sʫjRhoϊ l288%Y׿?E K%K7xWD9ǃ"סH NG e6 )x=r|Zb+S? ^/&]r3_r P<K6ɵ45r^ms-y_~<;4|ĂN^ď% $Oc֣BO`an3k,5]M6_Q~&~2\WMuke&O]vmMƢW[8 6J_=c0T3}FU>OP !0>PO]W$Ln/d<'@t-^\ aL=p%>lMC~PƾtV Rr`A\[xh`YVarx`263ήvo8qV!D=<~?/<9)!t׍ icWuo{zgر16flcؐ ` eB E%@R!eb`-āccgzqӫZmkՒZU~*UHO{"`:<ꔢbֿoVhm+8 %6SV< +` ^DL4u [G,EHy_nLO=Hr~d!gξ\M?NތY WOc`wm{7|MHq֋YARj5z] TY*?bS[Ux-[V FMwғHj =ï'K-s! ɒǃ~m,եd??K-w<8é.}eJ<&3둖e}- BX}-hB%( `xS7in8Sy 㷗}UV3beKy3G NBOmPW >9O=Z␾x|huzpӄ@)ۗ04td S 8!I%=*ΐg7q{;ޅ&_ f ;AoO]1͟(y~Mb/HX8O&kpV@EGomPV_g(Pd00݇+܄EM .0.Kj.SDŵ8sp[PJGzBx78=p >ZUυR@?_SRN~bH{9!X(af0"nK),JP/&[qvڡρJZo)`iiiY$0>Ǜ諾en8]Xt8vsIa dꭦ3_1(:j[q߮p[Ȣ 612;W_SC)^Ȃ0WجO(v ~F:_[qcccJ@?󢑰`%A6`LD`U[䥜%-)D/pE!^_~YÉ9x|B`W%2[b`q뼙wxdx![ ɶH:5,R. ,4}!p(C=A$)@%6 #'{.x t+te> XjzhcZZUl$ȳ:VVOCQ Z܊_ǁ焅c")S%_)>uϬ}FSy՜o}Gl Q"咶A!yTj1(vRNUA5^ 3Ja-{P31w>ܲύ , "Siƿh/{fGK B(m*N^ON`X <:kU@^ lZY/`˘k6hϻ WU1P(2S YXFri 0E{uTjd!66&P&K cً8orq.-&0.ެFeˋ|j,FC^!"{}Yӯ'& ߈v=N]\ʄL1l8nc̅ z WJe%9nQ߂UC~~6c6bW ? 115ݩG\rqr<ǢzL{jyso0J(/ӆy'LZՐRlʏ:E|'ÉJ,$Kܸꍟ 'Bx|3V>,_WCX!@6?k;n/S$uՑ<Ӭzpv_oͶ(˜٢ɋ3'~k:f|P@ЈYs {#-X:"ˆ7ؙ:ߦKՌT5,!?J lxzlPF%ױw)4[G(]PS`A<9l-HN.weDSlP6 ':qF*+J Q/o۾]Y&G08$vzq`= W8(6 ?'/<8?Ŗw(@>R`JH~r㓃G'.i"+%WZh G ᘿj՜@IA|2:˟U*Ql2: ,4=OLvGmnxpŤ/ eDWq;, V1}q(88NA 7HMUSs{<ϳ7IΪ=Ey F/ P(52xǑu}::) X*m@TQtz,6[⺓['G^I/98Xy6-0XU9qMA WouaFTY~˥`3bֱ#Ov=ѓLJpDx(-.Ȋ0fᶛJOP8+_<a}P)Clp `vm>{E#jU)hNnEc5_J(/W:~15d&TW ,`*lֿv=Sf'Z.=\EjYq鞯_?PJ88=GLi-Ϲ=I*jpz+4~͑E Fq򙞯_lyRTR"R8"RvȩM{ClTfy66J{)s\|%CjS(u2xljo]٧Z9˯IvkANLYwkA}\ 8'ȉr9% *QBk6?׊jcm{X_dEÕWEm*ⓩıN8E?PdD`z~O@|w.+e6I4tOΝSïmԊ@o\qgQ!BTP$/mv}۽wSv^0ͥ|WO(xG]'}q['0*57ؒIL_tl4aSa,WV#Uv`6k/^_bup*j 3whˑ=T>g,(z-˜ߦPD^k~!ޥ  oT:>y8Ц`A,9Rpe q0Ю">ygn^? ߏff abѡSڽJ~RP6y + Юa>H ^q}K`t0U֧z^6 =5z=& \~jS_ >GKLrߪ)@&kSOM({o6:m؀X>(TיhHE~O}"g_!6 `d`Kw̘)<2bSxz/q٫O U_~3 1=0y9r5ԡ]s?d6A\r)+Sq2I*̸m]\}iĢ#`,*t6r dY|z<2yۃdo !w5jSJY垐 փSG>73噘`!UȄ uz޹S=sW&uuĦuw}ͪQB!<*3zgo?X) 6O`tsJD 5zLؤn^˙ vʥ|NEkR *"~]\x s"2e˿kiiYp`i~0K϶(IVnjqfy<~ ݑޫG{/"3{elllVdLo *]5ux&M[!+x\kzS wqB {a$, 6"*kfj @qDvO@o?z|MfLEI+7UzXÓ? _ )`|IߦX@ß_`h6a^<~OLXKCoM<:|O[\(G`6=maڏ]:̤5 A I$ؙFNM:9c{1| [W #p`;6PУJRӭT!O/[y6|`$W~/+X6)ukC;|E#fˆe˗$E' RHrj;عGeu]%q`u@I?L![6z[NaDM W W/9'Sz?vbĢ=`f nꗋ+ PE:[o>X'`ݭ&o 6b-?R N )"dJ)S1{=vfgdw1/;}~W+k3 ΅gbSFXv]uᖁLue D$r`:Խ5;} hW(Q@- +iYU'pd"y6Dفbk`Uw>L'ש  d01nj $zv6moC}Md@L LlZf|^b xP`kn8ѭϙR(:2{W6G 01p xkZeElzI!zj6I@L6| ]׊sx@9 0MNI} 6b㩫f0 fb:8 dѯ!T2 _IY[56ۡfOHjSBr$EKs7oX HoE|s7lJ ۢ a&츙SacƈZÉ)}26nħ>bd ظK/uϽ wLPd B(8-%~KJ 2_+ibxQBrPT ~Q4'jyx98RJAY#A5Mu+IƜ$FmD3Y=jNsfI`3SYɆkN9\B1>!`8X` 2dM mNRz8e/N-wD _.} @ex6rG Plt_d#β:<<Ѭ.U+ՇcZUG g7߱s>w ť';;IENDB`tdesktop-1.2.17/Telegram/Resources/art/logo_256.png000066400000000000000000000511461326245125100220110ustar00rootroot00000000000000PNG  IHDR\rf IDATxwu'}Uݓg6 cwA$F09lK?~eg$+X>?s:9gS&%HZ$e@$rZ8:U=ӳݝݙYNWWW{R|Ç>|Ç>|Ç>|Ç>|Ç |,=x8cT"Rp ſ`I)M0LJc<@A-, ߲[֩-]΍*5Ĉ+a (@-I HRf2XLOK)&PߨԳ3' ZԈ6|bXV6sp @33;ܽ Bk97б[Uma*MB@@syI00n&?K i[ɉi+ԆOk#gϜwH; =/CQ ZC=g%s.*aŁu=JRX ),@X9^.i7q1A MVjzJ˞:7uto9>vzxB H[c\}M ozڶBܞWɉqs84 lSwҋûvC7/ymwR H2!M+qR BZ&LߞWXHS[ol};U4!, ] ?D63PT6uOoLc#5 V o^uk~ιгLP A\19?7JyZmgU@tQ-BB #FLxGO}適W +zl ,L -׼{>[-WN\aBKCE|K"@82W??C4G'j=F  nwwߍl~3g^@j,6t_'ecbhj$ BPVvji_ E =hNAAh{Ww>r >/lF6wh?S_Jk=zJA9^w՚Kn BMR`02'gr'? /k<\ӻa[IQ"Y9Rg HQ|$]_iX 7?ҹhdP4<xP/_|5/˿EDqg|k},%iR*W_zϼʦGwCIl@hs|/|?"یV2H z퉇OdO6`eS xˎ{^%}WDǵ8"l_~3\R@"=^r5&'g}(}$]XKu~2L*Jg<_?5_@ʈԵZGNJm;w/W_2XL`%3 |]?ke>|x@Y^#eO8T1-%V2@5/>\ݳMd4up8ג{^xcZ*X6|ogVȥ`Ko=f[>KT^l_ O C2k=. )! .,zUW}+5 b+Q}֞?|ߛVjڷX8}c_{8V62V@d{Cǿic%AΖXh3 C/rΟ ĕPz>r/sbo^hH4* 2zeW~mB> S[~sjt7Ѡ/_#2qu|Lij_'4hPH)k`țЀ/a1 #\zU|Tw4s`gbWu11؟9vIlK{?cjs:;IgC=o+ Bt#'gNݍz)Z7:/HXUܣݛ=u`?pF7 2еic'sٴ]>Q'|wgy]mLzVb|5fbd}VdS5K iౖ E z<]tm>?>',W|XrH@pÖ^gS?}5ԔH|?ו.>*X&"[vçQ#UV*@N›^d쁴L`$w"G-$;ߖnXгJxC|XvHSGhݹ_F THO}:{rKvݿmʯ[ٙeއ<Q[;;_ޣX-ǖ]PWݼ?Eb<a{Bm>x *@?)]]ԗk>RRP?i `rIv>aͯ|葉qG2ڴLNe ,@D,$)oBK#{\@D;]|"}0 (״ [D^r\gMm]ޏclMXnª{~}|8l`q'DZAK-b{0%IΠy{Kp9`Ѵwz/.tBZ3€GL-! H!ͱU|¦%ӥt50RXL}Z0D֔rUA'kX,CS:=gs:㐆<;ӇT, w޿]Q?9}4(Ș%6q|v6XF{#0486pOO ~ܟ+RG4?yKd X@,?zͅ|}nDruq(:v,nI A'/+Ukj J'F>~Q,X* @F\usHCk00=mAʶ|Vlm" bnP8~ 8k8J9=6>X [vK EeЖ + BY6ܭq}pEg% y)1G khy/< qTY X  ծMۢ.c s".$Z 7ޞn\EWTɉ͗~C4qXwޖإݕx{_E W؁?׽x5.-}xYK"qǦaKk K}''SB[W5وPK<䠹Quk~>,!178 #`6I2cTi"=1hk;x pL@hqchӶ˲'TͶnycpݹ[_aIhDSa Q@)x±4ce!=1p[;FcRて9Pj3w|Y>pn-mA(` `Z'"!Ce`Գ_~cߊlj֞ٶv+AaXlߎ_QpoNĿzuM!)lIr/#LgMNdlaq۠6uuȶ7zaTX-`~onodQs"~Leر:{zucuS /?)D06gcDDvjhnށF~{gv=؆a @0~]2Ԥz%X|F@Oso8.!LsE3?ER7b"+P"$ H=koQ;ѣhZ @޼}2!a%mc4ڵE "~FB"d9rDO% D {& -JEߐx;Q5 ne戕N8FDGͭ,B={}S 4S"]wA$YX h3$H v4b=R6ewܛx;_PsoS~9#q8r7Uܴ!zpYguLQQxl" h},]I:$m& )$Y4{N޴j?ھns+oT8}͹6qD@^ %0Пr.@r`:@9wRrj@G+y#mo \ÛښD`|k Mqg*\vFB )\?1$$x2CnyoZT F~XyG=CJ{7DWT"xWo;D0sk*j27js©`6, (`dҀ1x[cH#+vR .bZ4`#ػ}0ìSFΠQ. 8nCwm˚e0QnV09kZ#T*0pt< Ò|Pr(OYU`d3w i@ش9 }o/2pV}&Bڄ 65p㒎*nJ9#h4g,gժÔ `K;uE% ;c,@r =ݒh q\>zp}n%l2XFY L8#XR)@WkB. 6;:EΟBu2@,& [v!:Dx1 νlW_z K;pWOwvDZ-N>{5 3N %KH*z*ł3T@95(G*CIBA,/ ] .a"8vp3; -> kf XXUM&\:Ll ~BH M14i - 3W (0̭s Q@_z4$,ӀL%DA\ ҹ6wXh;a2fJy^Tejs(65 3 Ed٫,!acG DT_NLf-TPe~PA89K O 4SP#1: %X DghX( v_x1LfˁAnM1afg@9;6w5=qW]""@+ ]q%XfWeH@ց$ =_ @`bv2{.n ^'Zyum!7mh»67utFyV[NH!`̞C uQiW-a3VJskEğ{R`gK@p IQ[i-Kx<4| `WC wtqnk=u3n N1@LJo9 !fOFщLOn}(<(|-"$ T j8JM$iPl⭫ךۺ2}үYuX Qۄ+"h 2hDzg4 N5 8 "lh=8iɂxQ r -1L<SjEc^s4P xZ<6D>ήKj1!kXLarFu9!g 1D8Xa ̘Xzu~݃ȋs` f6&L@[<,t! sH 44qrXȚ #⸻'m!B֔H"˯7`h* %įr&*epd<0;qRl!ωל=KXZ@ TK iןw>lZlP=Rcę]Fq_o3v-gUǁ|@OJ318tF-D%/A ѠbRӹui%9%F@=T8w @p MAV6 @RlU cfBy.d @P镖*g(m­bؔK5ELcd*Kxtw6!F{xHrN>`#;\N4;B[kN;iW!,m: ljYHQmz)?9D`I@3zuS w4ᢎ0"9>330B*[%$baݝMP9[6l&55qb*8/*s}.wOI\<[S H)"-uu<:1@Wugp=Vpۦ8܌FQ`|:`XSi%JzܰDs41(U5(00 &g|\]t={Cȭq@@CP$ěG_ bʪ5i,fq=_8$ ;H 5?%D|K ll 1ی]aTnyiah2|%$:XjiB{+#hV;r:z! PAizPY g+;BeᏳgeL` @mY R!W+U;, 1ܷ ;ǰ:zSkǓY Nyx)s|ִB{kn7;89 =5 ŋ}\²UeDL@ s7~@!\Ɲ=ٰ\|r=]{YDS)7$$֭ڻߙ!5pl2\4™'p9.)(e 2!;N4#xm5 *Ƃ(KPKUܸ!{{qYWM;,V;Gbd* kω bvhGUCI C3w(&y9q şm~Eğ_dD,|V& ). HiEWXRdsBZKHׅJ9q8܌ǰ =/g|@O2c*"~!%86vB{7N`Znl IDAT}  ۧ(l];#0Y&-!͜$P%XfEϭ BJ5x%KpM΍m}p O1|y)pMq4պ!~VΠ]rn{B2IF m"W ƫj{B$ZD?TZ茨Ż67c(B tKn} ɌRg3GSsbp ~lG²WJV& AJ Bj0y&7 "%M"D|̐pyWlnm'tkŘ6Y.gd*Ds!D(3P%ɲj|Ói(Hn/9 ?\]|^ܖa؞a _Z05Hj037;Sk*nϜӂKˆ|):je=)dkѐMq ]WOFai d Sws=^v=S$+*9% ƃ@RS@ᄋ:#xy- l ao!0<[3ܰC><F86dքʙ3CO/"1@bBU={YG`3 KX`*1CN+H-O pGo~6\.xkǓY OkzܐX3t!r <;碩;Y&]C9K糧\p٦OBEH{|z6OHCƍq]rMlIws`D i{ gBJa ֭1xr}s_\ݳ=Trw.q6G1yO C$[֪w#k Aa!~#zF7*!DWK[#u!82J@N=']; ]w $~*]Ⱦg9m1W~e"̵ܪfЅ >c $P޾Ղ_Hc kB>wM[-"}ci >@h1"w\,=ˢ8;91J@eر6hTV.%uu[)TF86Ɣf"y}tNK$ ~:~ RdOpF\<ݗ/*PX=.YHu-.D!14.|c(쮯J68>YsKFV0EQ]򼖇"}*"Hiգ @8>յz`)L$y!6t4)l;Ӭ^^F.L(D_lO(suTRH] ˲5ANxd}S6Q"p`rFTڱ_;∆FzNY`1ѽNu^^:B[Bː\JKHa1W5kp mQ6 CSiLƎҬN8=@R "OHwn}򤟯gn_:xJ>`Cw>m E8B{Xs&tVOƯ|::i!_\#;4g\o #ewFU,Dso}T. xt7 _u,{K!9ZļWJaz*ۣ|#1\nn5qXNQomOk&H6X KH*6Ǡp!;(w$ iᎾCA]V:fN-s;)v9wY;Dr]%׊[i^W¼Ui蚕O+k6׵ 3O80z{¸᢮(CJn7Q7F2MdaU)XJr{Eg c A@(SM(uzH)2)J'$ M2R*"Rֱ|A*zpsO+uF r{f[Қ50<z-Wcv ֬+9# @a)wM/O]6ٲRݭ;sɴg\OZVy`JӘoGƠPA@X?~]q~G7Vli"d #X3.* "{u*p|2SxϞz)gpwWVCʥey4ɡ -hH05ȡB v ƅQۆzZqn[Af%})o&:'hk>w KtF^ 6"*U\.[/x ]qNEʨ ^Y?ǻ8Dr"!$˚ңT`A ˲`/'^9WqKoԂ޶0Tf3+?t35c欟mQ5_R{fr'"KSh(- -{^<{*K@ "99 !+b! "33"e}E߀+P9G@ /'܉)\:[7all Aa0*$0P3F2m1 ]*XpE?# Y7*d* 6"UED?=tF&G`o 2/B4GN K(XV r"+p$%ҖgNLItD\ n^k64c}SHc!غfX N@޵h ӳ?鬝ƹJ,W,f wl}KN_*@T1'`o 2/\0*M($) B 'M#芩j]3n܆뚱&n'dZkY-r\C{ַ W TN8Z ΢2-_D-nSGf!Jt~M2`?`A*L(92H"0EEHQ)0YQ<k|5EJ *XC@YOtSbۺ +W~nE }:C6#H=k`7>RzҜ\A>2] Afӆ9w4W2aV<)-cPc$t N%$%%/AS L4\Ӊ0*-0F88svOਸ& ݕ@%{Y$2L_M a*~ԾݤhjeC!锉#^g P8C2qtprQ;+TFHc0;hW0W}9ɡa\IM` J^y),7SXH>8s|onXPᝡ aRTtwcu=!߸d,Kw۽Kf\R?23c˃˃sF3HD4pz,cl: exk({".FР3l`g+sAs~(E6r0‰Y+a&㕃qx`|Ґ-- MAe@F"",<3`ʉ_m}/'L3{BLj^1=CPH[oxZJaZC`ND(vDVYcz F81ABe#8߳)Cu*gH 8Mkbˣ2 >cMXvo e:%/ O}02x4 #AR g+俭L :=Eu! x.u  c?'TjBc ~A@mpfCqT`Ҟeev\%7pjҪ_|Yt"j1œG݁eA$ #is%Xss&7:xp1FfbhtojhY%5oXA,G&3̌.1u%?1®RAH{sjnU,>/_)>4xPڄY2ƿye3hoyj1@XCG} 5PvVAJphRL  @Xaxu`4Rp:C R[`*kI{CQ/{Z3K!&&}?]T+D%4/V& !eXm Dnb&I_}f0 }``>$.߰ ] : #pɂ%͓^Ώ?zg C{j vE =?yZL ׿YHWNi>8٫=0o`v!?80XP]ML F@ʰL! (!~J%~=* @0EjγOb+ *g?H.UgX 3`J='N)MQ2/B:~]eJ퓼*a0Ed6 Y)SG) d6gҲ~1gM Fy{$"8NOC ( $XWUH;_5L!Tj i/1q]/f"BX`rX vؔڳj{,?W@ !\t,,KTl) 3>iBUՂc%όh?:05al[l wS2  ^O P$7ףf~W S?zv@Ud6 *}ߨYgPÞ 3F;N da!px<y':TEE %BJoz},_U$%2CIM,$~A{ؑU݅g)|@&3GH .A΀ጅCiQ~VA@ 2J8eXJ EU>ûpl|fYUY f{?E%h|c餖飏&ffc9XtiCup#^10%PJx ?{|_=0no_4lRB0H$NL|@$5XVPÁB/]u% p[Mg)M5zb4p-Q9a.!!cXiCl F4Lg1|({h8\ VKI\i{3+-<Y;6!L*m헎r4 N J=ӣ`UϹT @`04x%kR0"$u ;"X) n`&{2$wy|+T+G1;GD&0nr|'qoXL k 0,'{_Z@=y*TWL~=9 /*4 5aL @3 i<r 1VM>tl(۶^95kݧVմrFLj82aN,+6ہ?1 o H'/> RH(HIݚM{RF:׮~5K=?wVnh0Ĥ.ᅢ?}r4S VO3&&MZď;æ%K чRa$€^FXK+ Ǧ2'ql* UQ e~N\u\mZ`: 'l/Át8zҹk-K725+>{b y,uget4|[BܰAcrVNՏ綛fBC 4.˭nWSS)EO KG8 b +x$^:>;]48'§:cawy? !`b([n p>hK7q8gco/!;o @UU 'xb 2@ϪڣA!*XZǟ?{AXuogVr?ma͌˝Ŷ ڌLgqo9sHˀw<ބLğCQxoOɩ t I843ų&Lk? Ǡo?}ğEy\9h߁"35J2aeӘMUu߻|.7fڻ#nI- mb?@ @NӯOlE*Ծ%KHWC$\:x*"yxS_%3 'yq?,'󜅏!}~=V OC/D2@ Lff&ezT/GY0 e$$r]>(^[QHDc~?&DTkٳo8>8#HQe$gb Yy51r?Ч6`KW]PN}H;)g1 J;h h?11~\ {?jV5?;!qmH¬O(}~[%Gݗ/\u&~WE?lGScs0ι~t? R8_w55ȫ\<@M=KAR- $u=7y_@?/WRu~@Q_{#$DaϚ pD}^:7"' H! s@%LT\;+#bi ]jPoPɉԗ?25u޿V7 !,{z-wR8VˆV?<)~28oh@g :i U9>n1A\ɹs6QZ̿,OXQdz~3G0i믶]QRMgxל?͠qIe7 ?ܣnh }@Bv FRg4s#Se.uΗ) g+ib_h> `g'& =0!~qS3| ;AD$0OP 8 nUP柁+ #@3(/e#1f6?`~ÉN42 6'#ᛯ;@>=K%&7%~5z&_; ~^ht$ș!sme]ݫp XߕGYb`bzd$Ч>nx1|440+`MM<"5wF>*Ƭ$P(q_Tuj M}|wnQwΜsjm0IL Zi(jRTjoUUDUm!Pꀹppv\|ڻ2Ӈ/ƀ^~iww~Bl~-~x M@sO qfVfBn^>=ٷ-9vK8|y\(&Bt^k[Z-Prpn0/iܐcobOQ(>|p@ uym% 5(98'@ cCo;g4$tKPRFnǟHYhhZd7saxX7!}̾=oy.G2E9,V|WO7U,8(%~ YaL8{7sPDQ&Η_WNUWh/쁌@xFv3{Zfv]H1%D)] dRrC"q˟.,/BpǓ!o  ɞCD0}H;.x]/^j*|D`87샿~{8WuȟK)FJN qc,GQ"Psv޿vI(/Y!.  @U.ӟ_'!t0,0+e_k?%x݊bUP1(7߶ M뤓(P÷}={ڽPgP(d A6Zjm} s~̪žIP 0)ӏ9/?eJB0 D(W5~h뼼z$#_Sw2n뾿;_^8$TOI,d bPFa˚47}'dc@(ӭnu% 2F FbzEfWPVY' 5X Wwlu}mo*͝~z5(dA Y#`_3F˯D]%\t30Uwg/|3DYyU.sWYmjEOX!0!cG H${/C%."f `h#9(0Rm;-?xԂHRQM!><{Ϋޥ#.?*'z^0ԧq#B5f/uk/4^Vݰ Uƕ]gώ[\8)BeP= 2'GXPTDDX(b-[Y Pp: :)qvq)91 o J6TO ӇBb,^7/|k_qKTjd`257txmgn5T/}=/ؼ@0?p@' #K@yCFv7ʍ_gU+q̠XkpC\r$7Ί rjށg@A0言C BPf2V5YWnhf7ϱ5K(\;Ďq%tTa@N ˁq3w~6-z]ązc&3 ,1cK5~:Vn[[\LKX`c`jϊ-%@ouU"=rbF$׆;{z3 `@Ԧ`Aڜč[T]*W5kXͪUbYKհXyB0LKR(2LYz&*H~8W=V\J'ɱq9vw@ܹ!@oyM$doхZm׽<~ tt AGW±+de5UԲDm5+ZHbVVQ+gF(PĄa`"9p(6 )8ұpI95:蘜'nݓScͤL 'A'Fz`N ^`X'B0lц0#̰"-2+7C<M0fAe@x6\;-'ǧNCii=ŬCज़#SUzzz 29wMԉ/dY^_hQtE@h:ίo ެ ĞagH\^:t j8iA~|6xđOO>mN׺W<p;zk;^sǝWkWf8Zw@#]x\ȏ0w;?Ȟ?yqy%c-rmM{{ i+1&/3,-At3ߝy_zofϟ[nnT\o!Bwdn݊d;9+AJ@1Ggg_oM=Lf&<5aB`oyϻ[n#=o:YĔG˂ J E93xno}e/ic/u7Qyh}ӻqct sZbNӌudEZ0=M<ޚˀi5:3k?G6$Ʀ3),\C:@ BzǾƿ7=ZwocKoS_ۦ݇[YIȏ q# )h70'rى'ٿH}ZwocoM]/u_w[yqW5t@vR2ۑşϾkݷf7z灯m㯶~Xɂコft_w+N~Ϳ'>1zQ<H=z~_P+9:tÇ' }o?MZ*l~/77ƚ@GHv%- )GO>nN\Z5 Ե@w߽w]Hˈe_D9o_BƜ)Ex{]YEӳZp쒸qm}O8Y ]ps=ކm??ۓ? ٥1q% ǿ~cZD3#8otH܃⃱ߜofnJRJ.~oG3TJ͍B J`o쇜\f,yֺ{WԈA/~O7M@y=¿b!muF|?d[~].l_7~峟 uuv&]{!hFn\/ׅ m"T_7oh[gf8{CA4'P| ly?x$3kܻUņ[&ʜN%<,E%܅O~r74Ѩ:w7۾'}j,]a^`OvE:{fֺg$_?~G~՜ʓBhwzm@@_?N#ˏ]~$6@oj[ޔ>3 &xnd-ǦFO~=?5w_n6 uD}]O }Vebsb eSN&ǓGzn ?G gΝU`5`'BWvL??rh[/_x 'f{X=HA}wnba;=eGPz;|ߗ>5K{˃] ԉӇ_Ώ;ZTac[7 ^L>ki[m#?6>~}[sֺOKzbz6uaM!- _6@~㞷Ͼ @[l <4matu;~돮u@AO6o uW`'Q4?k'2gZ {j{i=sf|P,g2_nz@3gXK<{#~ ($Rt-ڻ}Gq wŚL`f-64`NhכcԱgZ)x|gzN-?1~=;VYSY _wW[i/+J}Q-޹s _k?GoLMCwysjFϣƢ??Br&2w{3s#`[^@W$P0\m7"ܑ8^egZsӞO?#s4kv ኆD;]"ĪЫ"ƭW]8k߃B&wOĞM#OxTU̝|uo:{ÃKB3곦GWWw|ui=xT5;?կlmBs6oߔby"}35˞=bg v3;x 1]3?V#Wi5=-yѵt]c=u4v/z`玘95̂MT+BH!12Ձ}h6sOZn=Gnݡo5'.zaP4MLk;}|^>tMa%ɘNC/֗ "Se. zR$_cJc.lA>qm ms/\GHMó;l؎DSU#|佣_߱S˩f ߷~k~,Y5kŗel26o s϶0mKe3sMϒNg Ez6Z){|7˩c >Grҟ%HrŔn p a᦮@lrʑkCSΟ=(c׭*X3)w& ,C.~96EC%)7ܷ=~K-9D+AjA?>OU7-sZX99DhˏRX{|aᚈ36]!EIbM=h>\L co|O;?yX]ɼp]\;",[iaT]A5l$1zHf4_̯r/VB!?6DU#[乥mH7`t9s}tI ;o3WM EH"1ZՁUf[cSR QWMz|$F(mZiZ[;ƿg"cڦgBPp¯+aT\o&WNOf@/_&HPMzr )pǴpכWUALC_]cKaF;7g 3zXk&y[ǻnh.筂_l Eٙ ~CB"J^)E!=5tl.ƻ99Mݷ/f1,w\w>kzl=sDb*c}[ܳ=–[Tަ|VlvNs1#p=JRJ|ҶMCkNL4pby;6TbBw׻5tޮ 10a6m)KnM8IU+;oG z*R>ǁ$)L&dhd-O窞8"#S.$ru@4LmA(*ұW+;9w_oM euƖ][ÊB`ڒI&5C׶@]吿L"~9]S0Td*S>]%I, ]꭭D:ԩ4Iyt/\ 4 +I|8BQ L9&Hib|%3ݢ:oAL,>7txpg{wDe0S1m .NqIЧˏ9O꭭}*}<9KYQܑE.&ġ[zeUajXf93/CY/io>?oɅ%oB(Bs+Z>{Ǒ\[yhg;{årivNk ]AWc9F$2y|xE,hpV ]!WEoœiPtT>#T4`DcIl;5Y0ۮNzhX$6aw(lt?8d2 >UIJt85 +m%IF>9f6m&_~%+Kq) IDATr+d)@$3A"Pu2) Ucm[f4l.DDnQYf?ۄ̾UCSlLQ\#CCEٻO#~H?c}>;Aʬrà@J̀d>ޱx[ߐ-]}oAp+mIkE0 "~:7_ 4EdsI29Yt"4ۂ+ڻXhF" :{L_v/*uT`ʍeς(RzQifQ㝽@ 1 `tm"s:W6sShH8pߎQĴ%ٲ}N"(XK0Vp z,h ۂRZfNLQ|uzrՁ?BN([F s9WЍUl ggr,k=DC^u5k:'MpcW{wĸwGz+D5rU$I\"o0 EG,]@Sx3†Uns\#PeP2siR!!y׃RtVHBCagjB5*XںgV[C) tm۹?"D 3yϾ)H)UCWN9;`2>! lfsKqlzk)FyÅm񼞞^W/˫qL+yduP 'ܔWau,5ֆٷ9S_j8&܊wtϳHf,B"<[5{qXF3iqiϲ%#\HcC(sm+i^42oV+t}j|A )+P[Ou]2 8H`NZAX(j^v} ݧH+ϕ P') ;b7Ddʝw9(L&re6mVo!%yf{gaesGt yT|h@j qV=@8{K`{2cS_cN\X(,̼星vxK_䖛d-hN.AY6fr k--D6o_¯͉4z_ŜJjKCQD}fP*(Ȃ$ 3)7mkUHJEkSPgL, =a\߱%$)M2 wiOAQ3΍'Ie͊ڋdOOMUFiNOZƫPrr:U Z0,K^^5pE{9W" kI&8~z'VY(&˂=orcIFf2hS !\˾YM;'363)HHZ=-ܽ=JK71mYn GJǒ\Hbڒ@]{ !umkreʻ\Tˑ6]CָR+# B*_$ RKQoV%ǴPKE >`Ubtf@S&ٴͶ~n)#H[%ikpR Ɠ$sBЧ.ydr=[hB&g5{ ӓiNN բ)V8@/񅹊v6zgU6z<߶L&P/RJ:.(BUt]]!~Zǟmw"0mYR/,9YGG殽K49Z53t9l%at+5mPYBfHڑ8.WXEu̧E>;zۻ\s'7| MAS&^LGߡl ټEW<Kl0uZ⯭oBiU\M7{in|!4@Nnf,&|k/v8vݸcڜadu)-- \\ަ-BvXdGR$]2tJ0VɼHE햩f&nZ W2ãjcv\&P9(8PF@irg8e~.Lzg"$*љ4cɹ7YܵwkgVѵwU~ªm042"N3ce'k,FTPOA8bY4V[Dk+oqg_]T6,=>]%H0>AUeKrlɎM1B̂kou&81T(Ms?g5Jݾ>V^ZxPYWÑu"ZD~4*MT]EOZfPtLq~=|c)=#%6q]781B"O[РԬW9G3:EcSDD)©5&փġPifLԴ]YUo^OpHO2̢/ѡ_@pUO mQ_ˇ#c)dB IaNj}.SpBAފ 2mh]c,*1 }9]ob ²y u96.op,I9 E^UQ֨tZ Lh "Qo)ӐZQUN+ve I΂3X@MxF@$fpT99#IgHg2\[S)A Ua:cp"4T9ZZ%3UfS+pPeG˺D\Q[Us,kRzWIiq#˅evTE% Åtys7Tp*J)sOwϪ8bh"E޲XfKG؍k;εw)cisJ`=~sjՌyi yt!zu%kL i/P3ha ҶrGgnSh6^+1ټMtUK+l:$ w~`7 !L[ҷ)Bw[ҵwhl$ڢQ_8trZ@W':v3a5.G_ #F,KTmY U%-¶-^gx0oom.3\f0D"jG ObK{ECv;sΖ uڻX:?Do@eRhi7ReP8q;59mi_\: Tg#@hQ!,hgΜOuQƁ6n/l2X6jBW4u0d6]pѕË!;N[O6|!W~M!g92Cuؗ&5ɧBg'];Z9[i&ƠF޶9?o|87[wV^)=$`b6˅$:oh*z:|s^I()sݴ+Tzma>zz~UIN%lr^~= 5zziJf=mB:d&>9ŷ-mgo iSCLfQD PTv0 ڻMBFu)\esg2[~4紀3Qg*d!SKycU XCn͋'Ieuٍ؎4ui.Őݎl5]+CMdlTοm.pO3Ubm-[Ø#'\Z_dDBkQI lHMY8辰,9ZLT8DW´W_b 82db6O{),Er *ՄhPOH_*AXC^]93<=5ïlAQ*'s2ΛemβdWD B1Hw9RtNi=d|6c/QFoy]g3Oo *\Vz#R~ݿFysd?_?W(=9t ר_w7:/Jqq*M9:5@!u_䱗ΐ[CnL+ 4ѱg* vRZ],'ag[`C@X 3(NtОAhC fImq#/y{bm;9;2t"m{{%[ dFRW:Tqyu*Eu"lljO+ /,$4D/$h+Љ `qI*GLMe[n; %[|3<|T^NkS Q^f6U'jynV33J lo!UΛ8ަ#8<TXv%ӖR N HLbaW%̃ACb"ǫIO]I,ͧ4Z[HX%5c^8>d]<. /KYb= _]F UIZB0kHlVꔮo@mG{(B:*ϏY"؎,Am~Y;6W%O\u*'%U45F c?Syz/$`8#/Dpzb_Չ '3}SmUhAx=xD&Ox _{')ؓ^frǞWx#kP)GRD uQN8Yxʼnz$f %w Lf,7ߍ_wNT?-kXaq |?|^D_DSSՎ+u ` #GJ>_09xtx#]\ʥ 'I&T'_u﬜ˌz <+. ir\cM{ ][0.b,_8k?"ÉE~^N21#TF_F y%-Lk )QZ{=~G@ExpB(Bh^.Iq$] h)pۅi"Q IDAT_|_AT}:Kó`E[griX :0[zRJ (SӃw\)V5lH)śD fW)L28AY&4E5 㧘ڴuFӇƱnɞ [b:oo5+ O,YBbon#@gzN|~ +?;/ ,5Zdq݂$ s2 Qr^ )k [b>8Sdh˴5-|f[>3g it}N}5NN9",Wĥa> oeli9f@JXMek K( 0HY)4ųF,n)@5#6X݈C!zd{[+wqUsμ4zZ1Pl. nRMnӦI&-j{W&nJ$.MHc0 16)~˒9{?f$HwYҜלo6I {|NuΎS%Bf(P(&u?eO~ݎhh@~P-ByuJvNj,A[44V$$H\wtbC=?A7ҟKa g10Vii [^~oL~=n"PjS':Cߓg`f{|~1ɷ9{'_C^nѻ8Q΍im Sk CLr>4{I>t~Ozo;zg/ Ψ㻹tDS,2}h@7U}?ْ|t>hĤ؀;vŇ1jfP d׹ӻ LJ1G8)+~r,}\`1➿/u82rN-z\ݭt }=-CZBz}#X}~!]~(Xn5 "XfZ)"N$dW8ӓo5At/=/|))X7KǘXP=v>("OHGaD&{Q0@ S[W%]8D C,57-*s{P9|DrܠQH)/ifwkWԳGwI[15öxDoW*|=EzAs +P\7H e,jQ!'JS#f_"$1{5ς⾔3H5~kw{#ˇވ}|zV#/?1P 0L e.T? C B>@%W8I0X4M^wt~Qj ){{ Mro{WRYS~"V)FlB|yfb#cy4;y\v \dO8|ԃiׇ2-Fa8ᒿ% p_YѦv~v Uj5~r̀ŌE8%c<*?VӐlXEܙ_ K܀p 02G&EF@!4Q'X8.gmkXs'Si_IZtU t|mL{_<̺V_H7**2QB@Nb{w{LA/4^I 5>מ=?cc[NrFw(cx B+V}rkO .T=pK վZt3hl!" &0)FRn+z*o߃K J"*fc?Q4GLWS:-%><(]0e@o8LRȣ^cW#*qپ>V]P H;]Qd"o`>Sx߀Kٸ:w> xvbB@9uh9g-TW 4(ay2(g};f1?Q;ݖ {a'O̸\q9̈ym[2*k .]m"ea sp~d%g?-|~йFlB߿M0c<s`*H%þJt.8APJ!, !L{l%/d2u֚24D839J >O%1NQ}1>$+P%/ݮu@u#]ZL (HL~K&Q`{# -Kcc[Ij ǢyϧУf ] \ V {h'&Z@ `sFKW]+`aY();A?T?!|DK86F"dGK=̚c ӑμѲ#&Q;q"x w@Ge~p1,@{jW=nhXQQAiĔ8/`(ϿD=ñ5w>yyρ* dbDbJDS9l]ZE%Ur#Aȟ?/mD,?UOV4 zXd,Jͻ=W'ӎp 頪y~^h(ۗ1>WKTJQY߰~/a a hK TDԞ=ko1sB;00Mm(@63UP~O҄ckW z~%;gR&$PM<|>kGo3f-T+@߰7bhOkWB`|[d?sG*T,tn>nYՁ! S2d?3~"B&}>eLRȊ@Gz l\YzJL<Cg*g @}7zR% 1aZDïGDŽe.u*IƎֲ+?b-^Szڧ80 "_\bAS7A2F޻>= R;y&_l1-_g^:ҽW7 Fzp=+[l?~wFyȓd'=t"PBj׳DEj벛xJ70:bJN!{D-j:_̆8'~ly M.&aJI + fJfZW|9 "c yMkqdc4$v{#~tzo'%D~(=2*9k@!b|Ӂ6cf#M"=Ct n JQ 1P&[{yc֪7VTO;x#B1i@~ƨ[ģ[tGdA N*U}Χ^3.\^BB50!;0HUE?z|?$7Rd`?`'Oo=I`Yx)=K0ZX#KAsc}no?[o~BOc/Y SB@OLU9ͳ;]֪*ԡr G+2s~ƘوW=7T$:݉no%M~:٬A֓v=QRsź*C%QAWɯDƼݱ}Χd%+$ @%H* ?FTԢHJ81Ԏ'![OnFE(1Vj<{sd{fkU"nQpA ]voKjNE.tMuP[;,7W^AXxB%+諞Q;{ׯ_3ISY ;T@ 9$1k*s86$50d,R$tǘ~8?~+̽ 9}(S.dF U׹_>=\J~P]{Ѝ Bo>⬅9۔x课,?^!;gʙQBOnL ih37xn/y J)TJ4.^j9h";d{MtpU~UՕ^҆T"j![R<W{8MvU(%;LAЙ] uox7/ZQ :wDlv<ԋ?A?sloʛj]ƒ<@o ZFcβ2ق w)ť)@5B {3WyD6mKkO Pp @)Q^s΍շnZW]z1|e3Pn[ɦu E6>`RTμﰮO*܁rL8(T jBmboqyѾ}ch!XZg\a]?FY*GuiVFNAe-~voyēثdM\+##~tl<[>f6cU+Ev%Jm\Ԡ$X>Du=C~ܳgQ-oS}do3 Av<-4`^q-ko0j AV*3pDE-*҅s|>gs}/lVp?,-tx=A墶auMg~Ə >TP*ҥ{-W Uu(Pgml۷wТ OÂ*G-+vK\o} ks Ӄ w9kt-B_*jl;'vm{yװ{QFG%ـac׿\;U^o.Y{d&^?DQnSP L P ~:)vfsd} :??Gzo,q2A@z{(ҵWKX5e Ey ʶ!҅JDh”$z=BD aTkSl޿9gxT:/?qUܠA6^EnEa*KUŌu"8 $$DL V09 i݋Pw'GԩC٣UO ݛ{WI5]ҏ3\Ȉ+wj,o1k1{RcŢnQ۰@̞)jY:C*rlm1-Ѣwf`z"eb!>R-&uDlm:ZOݧ hb[!S-I֧wI !ͦ޸ABd$۪1bfQ={Q7QTDu}(QBk L <>0,zNZ^痩!v$I!wGTaFvR(堔tH;c'T'Bb8vD[v"R6I"ډ.`C;)՞Y%+Fˤ8dt s. 3CV+/w$K a3fyFt2IvusnkI\~Ct$Df(2W8rɽF>dJf,r(0Kv}Py% .\p… .\pEJJ4džIENDB`tdesktop-1.2.17/Telegram/Resources/art/mac_setup.tiff000066400000000000000000001124701326245125100225770ustar00rootroot00000000000000MM*)̀Y BaPd6DbQ8V-FcQv=HdR9$M'JeRd]/LfS9m7NgS}?PcdGRiTe6OTjU:VWVkUvJQ;%gZmVeo\nW;oW`pX<& (F7drY'oexܾg7tz]=O!v{]wxf}nNjz}^gQ|~_?S̾ LlqB- 5 Д)D Eđ,Lől]mu1Iǒ !Ȓ,L#ɒl'u$r+̵-.L 1̐̽2L5͏%Kl9Γ fD;ϓ? =4- CJ5ER%I҉GR5MӔ7KEQԕ-4MUՕm]4U_Y֕m!5 o]ו} Mu_ؖ-cv amg0cYvkr3ږeo ۖsMԾ\w -y}~`w`NC)ᘎ%xv!c5t &/EdcGM[V]bnc1yu ɞghŞNƗ:C kZ.ͳ{ɴmn Ƿ ؋׿/ ;k |򜪴/-s\E􊈻]/U(_=oeg_uݣb~yu=s gqX?{@  ϑer ~~X 7_\@+~~zaQ =?'|o> sӃ@XR@#% `C\CTL`Cȣ9ǫd1?@2+X$A_ o^>X`H' BaPd6DbQ8V-FcQv=HdR9$M'JeRd>sb @P&D $]O=W;n퇃y?@6N`P:\nW;w^oW3 bqXf7EgB0ub-tB27<󫴝擶y؞;Mlo|>'ry\i{tz]>Wq7ՠg(Cpf罜cWf`Sް5ҧ= , ˞Al% 'Ү.#JBYCd{P 5Q,muPSH !Ȓ+ր S@  ѬȞ fj|s@x,L5͓l7y;S#4OӖinq&qد @U5MӔ9NEQԕ, h4`TɸxE0SJV-c `^eg&h*?-!(p'K-X4MwEeٷ{ : 2|C(4,0&aڝ8 ѪـQskL+wUv!4}` @Lv*{)' u  b;J)=IL'(XHP-IaX$C Б-cdl=4-i &rv^mN /,*ڼh`HpS䀦5ǼT%LrHk1>6^zc+7 C(zMP 82 1b)&Sqƨ$b( whB3)!'Pfa3$z=-w*}珲b yK;_),Wa F#80(Ph0Ѥ;([k?˹5@I&~PQMq9b' fHY\-#bs $4*;S #f<ٛG]`Ոa* KL#s3^Wc "s)ƴ〓8d<@ DQ@6s6Gs%ij*Ѫ)ZT'bi1'~E\uNU lLk`gI@9inbab @ CN)En9GI%ITY-KVK0> zo7~@ڙMSNUU 0 ,9~}8|_x< 0>P8ޔDQi;m,/+W(>u .[imyOM7S{5/'MaޗOuo" GN ްo _=c j,0| 4d{ J*B&T{&1DSEqd[7K;Rx`ԸOG!Hr"No2K81yyi CI}")/ 3̴r!rqdΟ/%G&rf9' TEQteG:XQw}5-~9KzE )#]Tp0F.bIi̝'17H {JB8bQW S z502%xtA##ԉ.kw=hq)Lw#8G eU)0 ct(#t2̀:>3p%7V%x,(Td36b(CyTj;c:&x9NiB F(ftSÞ@E;eb1{a 4B2W[bNU0N9K`sjYy@J`ڗTF)v sK퇳s.2!p=.Az 1gEhluÐǪَ?OQXԂ03Qw#I1SB|hDW 㾗Ԭ\;=Rle87N?LA~\ ?,R:d񖡪Ę!aD줎7yW)Ӈ鲓YKf#G  NӵM\6V䆕 "Rwo4j݁T+JN 01;4W%PpDI`]4% )[:,S `Ӯ{w\6$:5qO߅5` Vl Agpr._vYcxZ Hok#yo9Ht`4([rQW&m+N=?(;P!= 2GC] P 1=<=#Wd J:|nE /gƓZzs~:@V1𡟢}I^'dd Q@>mKiuT$&[Qco*)7n@>n{NߜGΙq(dn?~?Ȓ`p 9`tK/ojVr }`:W3L13@ 9 {1@[(DqC<,@?s'x#L& 1@3Z:{F@<` pEͽ1G3=:l5dIփsڒwBR3 p@ ) x<2@ |$BhFjpaqkC4=T&?z֏I xs?đ!P x;2l,2AC&S`s /4;4KI',ዚ4U,As!@lTYiiH:`:~H1v`ƩOqtj')}k"ZL\JDG Q Kc .Ih1hH{ #vD}itm,H跸J HƤ$EC` 3hxs\~{tWIX({Cl7Hk[F Ia^c[B!p `]Zdd 61|Jؿ*-z Dp$lI䚙>dqT!7iS/068v%[ hnc-JʛX|L둌t 8DΉ@LiHY Ѕ\r2 H |l<LX%HL ߲IЯ@EBNO,O:3J".QLy0ԻDOOT1!IP q L PmP};(LH@9B-Q  5: 9MOKQQQPayO.@(PR-#R=$Qг}ZȻE$ҭ+R]L ТDu,S=4SLރMRp;@m5S ndrYWv{T2R[Txg|~_?]Q o֒+Y=nz?pLl(2 % J 3Xh~,J9*ȏŐD1eƑlBo N9$5t6@[ʒ+̴0 +n|X2y$@mq|p @\,KT! /Д- CJ.T"( Z9 rJM`3C֢81Q8 ҀB[TZ1 {ZuO\TLѶ*@X4 Bt>!DX^1F~A!HY4Q)JE,LYY1gp0 <>߯;Β~ *-:*X( g;x'UNbU0JѱxJ-K2S_1fAh]6Q˙ VaE5ib}QjsNUl7%n1X瀻[ ҃ ULuJy}/rW_~4Z@04#M9t(2h`9**(,Bj2м8LwG H2"HLv4&>)GY,k@"8&R*1 Bjs. 8N3:N<"{$3pc/&GQ=jT/LS47NS>GR%-^}UTRֵo\W5w[y`X6 @0VhZ6jZ>WnOmc0CMt]7UvOCv7( Bx5ax |,`6a)v[XV6.(!}2x?d9GnavIe.P|͕fog1^a$˰Ue<^iv&Y}꺲$1 1V S;DziO9 O8oB`V.Vvռo;j Ѿp9ǀ)qwW'z6({לu[HnsJg}wߚS.|&c~ @ Ҵ'i77b,EQt`Rئ̂B4󄐥!%ZGhxϲC8i O&&]zU.8L]%#z`e.)E8JD9eB܇IPs-F8@W5"ր#ԩwq`#htZ=&MjhZ@@^Ti-V}pxRIeQǙq̟tz]>WApe8w}^g;;˯/S{_SXh@7)- ,ƳB мEđ,MPXM%cBQDuǑ1g~l}#L%ɊD6""I/̚m{L=0Ml7rZco4 A(pr.] BR%IҋJ@Jԕ-LP9|{MROY֕mJh =&)QV-bΓ ̒-CMɆt'lEҔ-#:`3 C#BU-MS y Im[,0 :!T2IL +0j*HvqkSyĖqЄ +'z^ b(M3hZ /7 bJkkZvɇrPIc S8*y23vP>f>@ZdˏiN:ziiizAKfQz]wz"SH=3Z6իQRsHMf  sm ׸|-}'ͩX|Eѩ=,S\3dם"v]mI,c;oߠ AT. CN)'`$@ Ki5MBEQU.MSwyj򺕎eYUM?PpW~*+n s6/cO)4UIiIlfG(@`Հ*o8qL|װunw׀BI*=Wk}ޯy;@p#t*XJ6t 2l' B3 ChK@h |39DDR.H,"@~%TrB{GiYw\'IL L^kd/)1Ls$H{@39Nssj.GL4 CPC$QgM+KNqê̲$/Q!uESUUiՕk; V F`O[Q5+aEQMzELRY`gnu| &/8AKT]gqz !-8COZfr~ -;HUcq~ +:IXgw'7HYj{+=Oat 2FZn  % : O d y  ' = T j " 9 Q i  * C \ u & @ Z t .Id %A^z &Ca~1Om&Ed#Cc'Ij4Vx&IlAe@e Ek*Qw;c*R{Gp@j>i  A l !!H!u!!!"'"U"""# #8#f###$$M$|$$% %8%h%%%&'&W&&&''I'z''( (?(q(())8)k))**5*h**++6+i++,,9,n,,- -A-v--..L.../$/Z///050l0011J1112*2c223 3F3334+4e4455M555676r667$7`7788P8899B999:6:t::;-;k;;<' >`>>?!?a??@#@d@@A)AjAAB0BrBBC:C}CDDGDDEEUEEF"FgFFG5G{GHHKHHIIcIIJ7J}JK KSKKL*LrLMMJMMN%NnNOOIOOP'PqPQQPQQR1R|RSS_SSTBTTU(UuUVV\VVWDWWX/X}XYYiYZZVZZ[E[[\5\\]']x]^^l^__a_``W``aOaabIbbcCccd@dde=eef=ffg=ggh?hhiCiijHjjkOkklWlmm`mnnknooxop+ppq:qqrKrss]sttptu(uuv>vvwVwxxnxy*yyzFz{{c{|!||}A}~~b~#G k͂0WGrׇ;iΉ3dʋ0cʍ1fΏ6n֑?zM _ɖ4 uL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)KmY BaPd6DbQ8V-FcQv=HdR9$M'JeRd]/LfS9m7NgS}?PhT:%GRiTe6OTjU:VWVkUd \XlV;%gZmVeo\nW;w^oW`pR2bqXf7drYMjuZf]lv[=mnw[ px\>'ry\g7tz[=Ov{]wx|^?'^g|~_?~lޟ , Là % - 5 ÐR5MӔ=O KuMSMUUՕm\'KW֕m[u]׈cY׶ aؖ-c5dٖmgi]k͵mۖqܗ-sJ7uwyޗ]u}ߗ 7| Ne_Xn%☮-8=Ex eNUn\d~e普m `ۈd}9iNmgvꚮHYO BaPd6DbQ8V-FcQv=HdR9$M'JeRd]/LfS9m7NgS}?PhT:%GRiTe6OTjU:VWVkUd \XlV;%gZmVeo\nW;w^oW`pR bqXf7drYMjuZf]lv[=mnw[px\>'ry\g7tz]=v{]wx|^?'t:_O|~_?~_ , Ll?|% - 5 Ð=pAđ,MLUől]aƑmuǑ|mȒ,#L%ɒl'r (J+̵-˒JR1̓,3L5͓j30ӌ9Γ;fD= AД- CJ>UG%IҔ-9FA1R=O EQԕ+Lu5UUՕm]WdUg[u]ו}_aؖ-cM9XV]giږf͵mۖo pܗ-sMuITݗyޗ{x_7maNn"`~%☮-#=DyMNUhwne普m _~g9}cNmvꚮ҈w BaPd6DbQ8V-FcQv=HdR9$M'JeRd]/LfS9m7NgS}?PhT:%GRiTe6OTjU:VWVkUd \XlV;%gZmVeo\nW;w^oW`pR< bqXf7drY'ry\g7tz[-Ov{]wx|^?'^g|~_?~ll7 , L% - 5 Ð< ,!đ,MLUől] lC1muǑ}B{7 #L%ɒl'9!Ȳ+̵-˒/ bȓ 3L5͓l723;=ϓrAД- CMEыF%IҔ-KbN=O EQԕ-LStOUՕm]WeYc%9AVu]ו}_ ;[UVcMeٖlbWuiږk h[Vo q܍rMuݗmwz7{}ߗz_. Ճan%Vc5=b׮?.MKj1ayUe晪ۖbu} qh:.Nhz^nεrXOW BaPd6DbQ8V-FcQv=HdR9$M'JeRd]/LfS9m7NgS}?PhT:%GRiTe6OTjU:VWVkUd \XlV;%gZmVeo\nW;w^oW`pR bqXf7drYMjuZf]lv[=mnw[ypx\>'ry\g7tvWv{]wx|^?'`aa޿g|~_?~VG , Ljn0/ - 5 Ð= ! D1,MLUől]muǑ} /1#L%ɒl'ȑ$+̵-˒/ i*3L5͓l7̈y̳;=ϓ?:P- CMEєmGҔ-K5Mӓ#BS EQԕ-MSKOuU]WeY֕lUu]ו}_ 6cMeٖl.VuiږkhTսo qܗ+`.uݗmw]U{}ߗ.[`. NQ%☬x5=1.MNTdy^]e拰_aٮu} o:.@ `> i`p .X@i:^ťN9~nh)~ zh)} \(w;| "n:?5͠ AT. CN)Edy?rI%ITY-Ki5My=OEQ 8XU5pf )g|?ā9m mqZ,7}_ aV/cO)eo9g_ (>M-G@{$~7Vu?^W/sOu' BaPd6DbQ8V-FcQv=HdR9$M'JeRd]/LfS9m7NgS}?`X%8:Q:uOm+mVeo\nW;w^oW:-@& bqXf7drY'Ǒ^8>G7tz]>Wt:q-81 FxJ@3ag;v_ @Ll$m;DΆ:(Лrv6Y}AeƑmtE !Ȓ,#Ӆ :k ` /|+ShdQa3L5͓l܍G9Γ;b0 "p,LXbaz4M-K5MӉгLEQԕ-MS (A PBL֬-"u}5aؖ-cdYmg(7A:¶ dV ☮-71=-*&EG'WFŅdžtQ普m"Ag} .x>=h2)ـÆWNε 嗭ű숢 ^$-pYj`5{~-YBCCMOaφ!<맅/MX *@@DE|1v]} C>/ vj CǓ:n<~H^t_Jd  7Y}St`P >7 D OpBi@48H-!]B(M C<[#֐XjTR =hۀm1ލ(F˜v vt.`Zh֗ lWp }XW\ dPd}dL #* 4I"!F~p8<ř=' RM)Bo2,0xx8*p, Lfd)Q a[ ?~9Xl3nnM#(ޜSr#5PH `z TFb+[xW!q HgAQΨy&qa9u9hEpM&@( !0\2"1(R+#1r;$2)K&%2\[.&1!P1 $2oq5Cx/VsXFl/ ͅ)-6[Z8 kzU-w~08, 18^3o ?+39s;4:-K8 8 tҧCچv> eж\(|X`i~R6{g2<>/=>\$x ??]1 \#k@4oG3m`'tt7l*Zy-5Ȩ7W%I#:> ̳7%TPEI}de VfZv^h)>( }@UM S d=_y@h| TBp.r`I9T=iFJVfʑPciJ} H*ʶ Ʋ$X z*MG1:g3GO@-*tԪl-W7UnKi?eTT3/eQS. piZ&I&^ s0q >Y (X=TTԤ Ks 2L^oQ@=6MF*+7:2I_-6'lgr #*N7 rM)劼/sN=vN@LOXGP˘0Nyx޸Ö́0ůlK$ 3m1wN׶7}Cy;O[cf^e #G-Jㅰèv`8b4߽.\ 8Ai1mJXJ@qF_#,fac@ecvB1:8v8 jꥳǔd@CbR+]9i'$잓:耓`J`yGG)%XYH ^J6P{m ̟9[,.,H؀FJYG׆f968 Ȝ<śpRY/W BaPd6DbQ8V}~F9$M'JeRd]/LfS9m7NgS}?PhT:%G0\ A@*D*p?`[%gZe0  ;, m^o@;4h='zg "PX=sYw=htZ=&MO`PMN;?Wjǭnw[}px\>&Ș-SR|v^wU.41=μjOd% "1f!: LlSV*B#}#+ Eđ,MkK!lPw(V1L}H"@Tƃ`Z -EFA0!pVGd1̓,3K I.F “l;=ϓN  a "4ĨXfp4T?RX9“SO˛K&xFdA b*j͐fuu]ו}_ `yE8Rv%giX%"xlg#UڗM I@8!D]HTƋ0k D8mWl((_3z%☫QaȆz]E.MBX>ȋXY4&I}!ǫ g0 , d:kE췣Md9wű1{ n`24'# ʸhĵX6hKCsvk 1G/DBJdA6l>aٝ39h也̋v4'gqA<;$r@N s0N҈K[rp?t.:ڠ.PA@KaJPFPiIU,"P0ztczU҄@Im2!nFJ`QXVtWեPil9Xy+_EDr%,r4%#7ػN" 噠ndCjM \(0N^Tܦŀ7JSz|`͟gOQ;o{[I߿oG1!|)Dm_I.@0հkϖ& JA0K Zp2vO>A\B0_" d~F#O||va20P p8Pg jŠ^f#aԣ \B+BC0I +|.q)ZȚ jl&o uOHVK`txCQUb!m&ȑ=YքrjJ8rp@,"jth,F1"y`0 }(%`uNBX6T~WF#d! r%"X7 BaPd6DbQ8V-FcQv.~\O'CJeRd]/LfS9m7NgS}?PhPp F -\~AQ'|Uv_XlV;% @Ѡ8, l;`pX<& bqXdvrY85By_w=htZ=&M<y'}px\>$D F@,*0bt{]wx|^:?qÛ|~_?7aƸNy rv6lp4]  Eđ,MO4Qg~Q}őm-@.%a֒&qu'G0`5Gxqq3L5͓j8 o<9QE4=ϓ=R%\ZL9O%IQ3X&M(fAdiOMUUՕj[82t$}u]ו*SIU_Z AM4)tVEYr6qܗ-9muޗW 09.e,)E۠t$@46kYvm=y^y G&%n]ax(AǙUFl>a0Kh\Sa{ꚮϹY>c;űb"J:udg=lJ D9OG2^o 6IgAX)Vӵcd'z>nݏe) `&l*q~3͠ ⬟Xdfpd Ȉ٪wFG~Y)oYz7sb7Ov "ya JK!<62@8ܽ  , %x vM B-P SL#br(4Zdž"@iei. 눱N*ET V1jԒusa4@ !gaUbbQ s\\\RI㜁R Edאy#_vH6"ȫA,$e9 B\_% RihbL0[|G_PࡿB6XmB_jJ;)``:u8ESL75ӄKi.g$ṥlj4 :?%D1Xhae>hE @7??BX|xD(SKit9E ,Y 7FiCbRI,XjSMđ29 dkdD Q]'MRXd˜m,E2*aPMFrRĜz$xѲW# @&3l8Vxq5X׋YlqA)@K#e"-.Wb S|^K;fe=%Mb[2x;[#Ak+Z}JD!pAyM k˻Wm{[L)\Vkyp.@EJ{WX%䄆5-FQ=Qa\ %Z , DZsPX'ry\g7tz]>Wkݼ Nͻz}^g|~_?~o7iHv`?Ll% - C܀aDiT;LUől]eƊ5>q̅!Ȓ,#L%ɒl|0)˒/ 1̓,32%@)Q*<9Γ;=ϓa?rP#vCAQmG%IҔ, t7KuGǃNT-MSMUUՕkQL]K@ΒYו}_ aؖaV6,RIDЖUiږkʹXM<-nDsMuݗmܪ[TL.7W}ߗN^8"ܠ)| %☮- 6EQ,9.MNT6V@\9~muYiNnTh{ykε:]"Q8յnݷ n2w;ࠧrJ۶ `+,{3|W%-?:{{[W9/M5u.gLomucGqfOl1s~s}G|ޗ~4ao ?nųhF $y A#W A=!mQ#pap2B] 1b0Wv{]wZ'#w@ |~_?~_3 nsI|GG@l% - 4 L7yܻ1}D1LUől]eƑtPvw!Ȓ,#L%ɒlts'ʎ2pr`J/ 1̓,3C1)Mkj F8t;=ϓ?sT@P.ZBєmG%IҔ%AJt4grG7Qԕ-MSMUUYW%B9Ta[u]ו}_#,L*v'j]45giږXTmo@nv%qܗ-sM[7w m8Ľ0@pH,¡pl:ĢqHZ/ƣqz? ȤrI,O(ʥrl_0̦sI d_;y@ШtJ-Hkw=  h]u|Xo֖y_VjޯwxL.xn;yLZ39y{?Ug}t hp" +S<`MǶ|o;}N[}{<O/?*߫5~c[O<`7% * :!J!XZ4!zW;U0=Dpn@}":;v9T"?$ C$YG$*KieJSvhURVAՈٴo%Fx<8P=Hf's'Yw'{!o( M 0hG5OAz:)j)z*xc/g*h+Zڷ+껯+ ñ,TxZc) {ϴ-Jӵ-[Z׳ ط-zܫ~..ۺa/Y5x -k0 0Z׮ BaPd6DbQ8V-FcQv=HdR9$M'JeRd]/LfS9:ޯMZ%GRiTe6OTjU:VWVkUv_XlV;%gHPKMo\nW;wFsp; bqXf7drY'}?@ cۍtz]>Wv{]x|^?'{=/|~_?K < Jxk*Fl% ,?kL/ Ð=G9作ALUől]t3F1mgUH,#L%Ɋ\g&)i/D-˒/ 3J˚I59Γ;L? BTMEєmG3!IҔM-MӔ=O .0ESL+CUVeY֕lT5o]ו˚DW-cM\Ɩ]k tܿl qܗ+ `Pf=ׅg޷UgruZ6xiX@nA`xD& CaF%Ebxf5GcE#A00Hr O ..]O-CQhzE&KSiFSUjzfUr<_K[Yl{Ek[ms]n{{_o 'ˀ< `ter|g5gs{5@Akuemv}uoq`p8r^>ϑoyGuzx>y|G{}ԟq`$P+@\|!P) ΋(p9 /@T`>M%!HT(IJAPUIRp58IG՝iZYyj[`VR?)8/VՔRsF|ضjΙ~4OkpWX0Xp \ze/Lt~XfmdR<h6 X(s9AR=ӄո4*-~E}lz4u9qYyP6UgDVYiZ^,y!ھZ&߃Ϩ"W>Ѵո[:(%!ɛ&p\۝ !Vr\9έшiEI?P@wS0s=OiOE4}y5ƀ BaPd6DbQ8V-FcQv=HdR9$M'JeRd]/LfS9m7N@ s{>T:%GRiTe6OTjU:VWVkUv_G0Bp6A-;w^oWFOG|`qXf7drY'ry\g6s`ak:wx|Sro|~_?DA [(&@*k@Ll&zB5 Ð= DƳBieƑnǰQ} !Ȓ,E8#WJ+sG21̓,3KRA);S;1mAД- CM,0(<E"JS5MG gANT-MSMTׁԐUti[t<1r5݅aؖ-clJ@iږ'5FCo q4r]mw|x_}ߗ U7&\7]Mn b%☪r`ض=x2佐NUH> ^[QKh`Ag}.zI/aJ 1O } ;[]/ ]I><:~7p0guzqr-8H xH)EѼpvyQ?Ke3''ϝk g$xpUyoVi8[wGp/ !-I>}?YPP@Rrgh)@2`4PLnj ^y A=c, D`{BF< µ] 9p` hJ C: DXȳM/!3 DQQMCR.E؎ه;chDiU.ݿ;b*Q:@(K0>Gr "$R-vaKNJIT `7X%zO4RVD3-Om-fRq/1i™S-Aq;INjMRZK2~Vdi7Sf'N 9A":M)&Sͅ? 8%^~O%`{4tS~Z  PzC04B: D(X@n0אh!Q n!; · BaPd6DbQ8V-FcQv=HdR9$M'JeRd]/LfS9m7NgS.x>W }GRiTe6OTjU:VWVkUv2 0_ZmQ2 k\nW;w^oW:ޯ}? f7drY'ro X P+L+-t~wx|^?'~y~_?~\: n˷ALlG! Ð= D:.DpT űeƑm' Olu G !Ȓ,#A$I_(ʒ+̴BP- 71̓,3:{4R9Γ; .7AД- C![B3Ca9Qԍ%IҔ,oIt /O EQԈD@*ԭ!VeY֐Kk LaZו}_Z[`}eٖmg-nEk͵m ]'=wqMu݈Ġ2vޗ{@]S|W-π`. ҆%:wf& ͍E i.n]Նيu{O}NC<ZhږʿlűFi"mm[nݷ sp0麱D߽%g. ϕC<7G\Ȥ1HxQt/MQ`d jmYj  +8?hg>o\@hzϵq`Q7^||6=}i_ y@X A*_!Ԁ:|E`6"`l}$#AQ0{yK" 0([CT5SGPC:'Vq XwHIy YQl)4\\Q P8$ BaPd6DbQ8V-A00h ǀH ̶]/LfS9m7NgSiQh(T:%GRiTe6OTjU:VWVkUv_7`ZmVeo\nW;w^i l" `i|bqXf6 >cYWva{y>{^?'z}^OS~\ , JѨ&{4 - <$@ D*Q4Uől]qr1eǑ}T%ɈQ&)ʒ XfTJ 1̓,ʒ$ HL7ɒ|9Γ;ʎ,J=P AЌ̐ - NT}%IҔ-K'IJ1O EQ5MIU4YWeY֊Qu{u}_ ݃tATaYȦZ?miږ<;uW6o A&%uhng BaPd6DbQ8V-FcQv=HdR9$M'JeRd]/LfS9m7NgS}?3\ RiTe6OTjU:VWVkUX\ZmVe[W;w^oW`pSrAtf]lv[=m| }px\>'@0|]>WSv{]wxnGC^gVz!$?篮 ,  {0% ,X$/ČLUől]"GuǏ H-3,{# O$ɒl'<3~Jr̵-˒mDA8+3N3L4͓l7İ 9O=3O,w5MEєmht}-K& 3 %5QEISMUU\lqYY֕l Oo_5aؖ-cz]#dZKW/xvˋa[Vo tnuݗlX Ӭ;w^ez}߈X{{~. 9PONPX>^~%☭VeNb=m D`TYTb9^]tk\Yu@ ͅC9Nyinw+Ο2sg6ZRmNյYVvٹ R ϫͦoI4omh qܪ'5asM͛ԃuo1eH|7]uŰ6@n_OA1RǗ^fu0g3.otPBfxhg=?ֲZ\P.$uȹBꁰN `T{(&!Hwa<ЎBҘCйA? !9Gy;y|;Q @@ Cwg"QF YD<RXb9ц2FXV4vў6D7Id6cyDQc܁`1:&FHF#GW0f)G,OҎRHJ;SecXYmF0tr^91&`aLY e8ƕSf8d䈙bM95ߤt2RlM֢@3, @@VNW BaPd6DbQ8V-FcQv=HdR9$M'JeRd]/LfS9m7NgS}??_VC@RiTe6OTjU:VWJH6VlV;%gZcp(%o\nW;w^oWT|cbqXf7dg` 5LK=htSkdGjuZf]lvZg9^[=}px\8X B'7IWv{]v|? 7CϽz}^fZ'\` F"N6>(1=RSƇs H "<t7h:9= ?CMXaekouzE%macos_setup@2x.pngtiffutil v294 HLinomntrRGB XYZ  1acspMSFTIEC sRGB-HP cprtP3desclwtptbkptrXYZgXYZ,bXYZ@dmndTpdmddvuedLview$lumimeas $tech0 rTRC< gTRC< bTRC< textCopyright (c) 1998 Hewlett-Packard CompanydescsRGB IEC61966-2.1sRGB IEC61966-2.1XYZ QXYZ XYZ o8XYZ bXYZ $descIEC http://www.iec.chIEC http://www.iec.chdesc.IEC 61966-2.1 Default RGB colour space - sRGB.IEC 61966-2.1 Default RGB colour space - sRGBdesc,Reference Viewing Condition in IEC61966-2.1,Reference Viewing Condition in IEC61966-2.1view_. \XYZ L VPWmeassig CRT curv #(-27;@EJOTY^chmrw| %+28>ELRY`gnu| &/8AKT]gqz !-8COZfr~ -;HUcq~ +:IXgw'7HYj{+=Oat 2FZn  % : O d y  ' = T j " 9 Q i  * C \ u & @ Z t .Id %A^z &Ca~1Om&Ed#Cc'Ij4Vx&IlAe@e Ek*Qw;c*R{Gp@j>i  A l !!H!u!!!"'"U"""# #8#f###$$M$|$$% %8%h%%%&'&W&&&''I'z''( (?(q(())8)k))**5*h**++6+i++,,9,n,,- -A-v--..L.../$/Z///050l0011J1112*2c223 3F3334+4e4455M555676r667$7`7788P8899B999:6:t::;-;k;;<' >`>>?!?a??@#@d@@A)AjAAB0BrBBC:C}CDDGDDEEUEEF"FgFFG5G{GHHKHHIIcIIJ7J}JK KSKKL*LrLMMJMMN%NnNOOIOOP'PqPQQPQQR1R|RSS_SSTBTTU(UuUVV\VVWDWWX/X}XYYiYZZVZZ[E[[\5\\]']x]^^l^__a_``W``aOaabIbbcCccd@dde=eef=ffg=ggh?hhiCiijHjjkOkklWlmm`mnnknooxop+ppq:qqrKrss]sttptu(uuv>vvwVwxxnxy*yyzFz{{c{|!||}A}~~b~#G k͂0WGrׇ;iΉ3dʋ0cʍ1fΏ6n֑?zM _ɖ4 uL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Kmtdesktop-1.2.17/Telegram/Resources/art/sunrise.jpg000066400000000000000000003516351326245125100221470ustar00rootroot00000000000000 Phttp://ns.adobe.com/xap/1.0/ ,Photoshop 3.08BIM%ُ B~RAdobed  !1AQ"aq2B#Rb$r3C% S4!1AQa"q2#3BR$b4r%CDS ?7ݱ%~6[<,(rM*q( $҃wŻa&Dj&I \2/ 1l:HG e1o\:lR#Q}cնKv8a Sb &AtBe6Hh xJ,n_42n[E￞&љLM͢aGC:lD#1 ځ\n,(g9j"AOOjAj3l$mHB I}B;}0!hL$ HNa6 N7~weޫeM#[ P܁X'[SdŶ` @1> .*ғ|+tDl&5ۮ'|֕dGlN$8Q wOdX&=-ae n Hy='åCpqGtAI7݈pTQl6[*TΫnmoL?wH&a x+JHopMƶH_+H"_ou72KBto9Cl-J̞L-dkx*c%$vl$g$ݰI-tᛡj'a\)@HMRzD}0E-)[Bg0v\kn uɿM\$~ةq=JRL= A?Y>qoa$\E:qn%;v)fH-;Ϳ =[aL=n/Il?LU1k֋Zm¶]LMMi :$I .LL%>'d cº\N*hNsa" -r\F_HS .#V0%jBcqN ):m<ξm:!HFb?̏+G'{Nݶŵ>!Lސbl#]<TQ`wb7P<= Ctȧ˄iL>I;GX͕6Yq=${DcVRq]a=>Xn8 )\d#HVނ| tn8GMƓB0@]I.Ny}z`jit:tG6"L'4UM BEp>F$-vMnL#z(m{ۧ߁8 o8Ӧ\.LDz\Rt? ZxT Gs1ݩĤkoFEt*2H1O\i*@9%rm `+N -h6A2'$b{Ӊ@YoOLa'vHSD10Q~!Ǡ8dN+z~#zrmݎ058r+]Ě(K앤ĸoLĢ/n >GtS6H$f ťI$, M ilvcCptR@^脲p u39GYoi\\]anA$mP lL8h 9n,N5lG(R7oڒ@݇AҒ@rOXᅕԽ.+齁Գ0$;q-7u(PDoh'UdA*x h7 _K-&HWXqdIhR-Q#j\_d""I8\6HtͶXtpɒ c3Vre/&jAil1h;1Ͷ<' EzN|\6F:.L/^aH8a(4N4Lt ƒ)cS'2\X 󵰔s7ڑ; ΋4$Twn)q? jM3#~pڞM ?  /"{~a5l=EW~xE G5 ~A,[P$ wm'Pɝ" GI`59lZ'&uB ג@vL-*&f@O}*o@1=o"lpt` 9d6#FTp;h-rU6F( A+e7a144p&8I%B"?h;jGl7 ʴ܎{clNrFʏ+uA [-&`G~9q& f<#Oba%lD$c|H2M* ~#[md~m{#@&?<'6U{NJ?\C!9i!:4I0D#~ka7OQx\K77$ ݵ@p O%h$IϮ59Pm7K ɂ>gt2Ň,uND EznAa@Bw^ৃcH&F1C9XwF.#$L#&h v#0׷H-MK.l Q: RmP^O0˽nJY,Eֺn!nL1G 6"Ȭb[$uS7'fgTD **"~kkd<u CpOwb.>Y8uY i s n ߬Q(ZU8{Gk Ԅ$mRKb`|Djl/hdA2"q;$rh#TA0Z~Xr zD 7r~pif[_P`@}ê H7#{b7N aMߡ|Aʜ!TH۾X ""3"'VA[I8l}> Z;L %6R? >`yH 7=K{xtwL:'4[8nn>n 3o.lBAO?u_z~x gKCrL~ci-hzy "%0#0@wCFb@l6!$,êI%" v`1+O%b;-A.uΈHD p:,T E7 ,Bd g@&'dKah"\TK4TI;ynK#l"C:'Hcᲁ"[N\37˦O;:SiHUKtZ&oQ<öH6'GkI-e:( u9"LzL4n%@Ld=8nRM:WĘs]t n<0[")7# N8Od+dfH,s?wvJ"G其6wvZ t\G;z=ߗxma'Ict#m $ד ր U~#0)a$= yISq?.v% |S:{j4*&C~i@ Q#^ⰳ:LϞ5,=-I 0mqSwtž70)Q ph{۷Q䤤Ȑcadpl,;G^D[I%=~&[u~foU&ݣB&ȁGCnBh_m٘lvbHξ!o]9IK)3Bڧq{&Txtta)dSJ:|Űr?%D z>wK"៩Ӫn/t*; + 8'8166BKi].zEyZ nNA=/Nrʺ}GŎV.4rNXm:'<4O'kRRvbӚ{ O7AkAyr[uD FA,2%DMw^u)mL>~e&w(E;[Y%ĥ)m@X/IV6#qovMR`@"֐lKB:pk7cm;D)ሶf+E`0v#.k'!72dVH8Ap8Z}<23^2uHRz8I6+aVB}& y鏫5V$Ih,wN0#A?Q(o[*F}g ڝ@t qQ)L鄆ejAx X{!:S}. 9R>C[ 6sԢPæUt)6vVº #Y "$ȿO/c 3_,$&Bz}D]aBsM_Է)3XXkߞ--G\gRiYD&޽&OQI*@H^o*IR?,%!)hT&N%%6AH|) g1R LOk$>m xfKl, HG H76Mء`Vco02f>q\KfXY9=/4n2VL=LXP P'c8-G`2|HO=Tm!P-W]1]qLxsgokᮛ>ib=pK:c%1~ȋ 2OIqtԛK:'⟐a#Mt͵o}CfVSk<5'-iPN֘Z%GwQ.hDQ~%Qm❤b(dI $%^]A3w煢\_dag̑%:`H:w]|šlDTY?LAۦ&#"٬@sºAV??l+3Ylm"Z'+>qG=t&z[ya'ͪ,ztRoߤGH)( 27ŮJFC-# UJ Eͧ`R2Fk%)T;;).7)|,K` R\[-) '?<@?g qVrPo 鸩20/El j47ㄤeoq7äD"""}1W/-ZvJI6OKGB-p'J͏{} d$Y:I^ޑ =Sy<:pD6 =HR{ h)€|A $ }sJ nRȦPb׷o@˿\DvlW{ckV$B6#nNdDI:'K$  (7=J^7b 7KJ$xʶ[ Y}FfͩNR$# . +H/녕l |&{@a@0/1aFE n/1+7,r,nD_pށL{-lS3~)텕x Q%E.+{,u\u\>č#@aIP$/ >qg,DGAuOvHw3N޻bYT1tmlL5^q=y!;y^z8AlHPֹ6quoo~Ԅ4"bQ"q]bNI]BIf:%OrHWaq \ęvQ{,?#[%dF兕ζ;E7Dn,ܢ4׶a60sed)leeT<VtBN{Ჷ{z,"{ +{'ŭ: eo@dIpo@^7JU=;t³zڝ {$yq&dقai>627Dn(3m|K(>p[-OH@-65&0['[k& =DB| @qeܫK8K6N|d͌6# qɮAJT Qp_O*|W>ra,b[MKe& ,cBl ÁZ"UnEOatg,7O|6Q%-g (I $Q?ǚh&@QI"޽4t F*:bA"~5ܑԁP0fkS [6pS*H< m -niw1<wT( ź؁kz.(dZ$M,F[%!_ ̸goFF>?~#w;'%![ށ%M*߮m`NХӧ$nR/1ؤr-npW=W?h%#$fKB`(ݣ D6;0W Ԥ)@$͐od>n>Sq܄Pu 7c᷺ Q-$H ]fdMoRJ@l?/ ֗@u%hI@a+[JJT 6}xm]n ZAV F{I)-${M'j ;%(\ PIlZ _} Ķ%R a  4OhRP:ahT_ t&` EdIMI 9JMIfJ#@hD A8 sL(wx|RN-p yV m֢};Y,ɵ ${&F )7n':;GE*qS\uNI^L|i{&6oOON%ulYW)gCm 9^ùpԫ^(4H鳁y Q"" jXLaGֲ_$:l>0t*m@vēI=K0xSț'(t)I)*=6.XN_ot?\|洤~ 8:ҮxæZ-d6 OaAE&5pAd~ )T3%"&߸ʖfiW# EϱСT]z*pgg,@tvE%|0k )R< )MSA#Z 8[|1s5<"-ylB NwĆ 7𮝲kSobywtM -VB߉nk&I'E;tXґF%wd`k6o~%`xxvQ$܂^{`_$6:Lt#@I2'R:K!:Fn#o5ā Y5eP;o 7 $@60OؤotP 6\$q 6GiĸnCE7qzwB;}C鈦X&:ă^2&: 7M<'3נ.2Joȓ*|N :-@|:Բ |;bHFSm|7SP/6D6=쇙ˑu6A3X( %_)?~&,,.}q5 귦?<=YԭiWq~Izlg'%{ꏥ%j60L[EW8 *7IwpʭhOo 2}HTt@dHLC]dfnN ZFHFQn>6k\9(6oA#ˮ+hDp1X)- F-hur {o\+'╮PIau&JN~Eh5Nzp{q_mpz%wuOs>8#[X&;bYHg{2hSS5whOU;!bL@ ]8N>n/tb%#;o;a>^xAo 찷0q{&9i 'u-mFtQ2CQ`AB[ni@w}| 3rRGO,4.( J&MӨFDwAϸOHU<)Bl5ۊ=qh|Ż߶#fLuܟ1$VM~qŶTFI4muFT36%wR 7q?!qtVLm>.Au|ixӍI>1IJd߾?Lߖ ߅7O5G눿q5Mb0+vOw7.-K{ԒyG$$逾68.(IGCoa]?Ii%/d5h a@T $oGUTI ̑a ly&T˄3><^FwCVR6=zwȱr&|aYII:W±Sf޽;aZ q$E mH2[ E)S>{b*y$2NIq; GÍQ(]$2>w?xUˍgqצN r֋|Sԧda!7DD6CswJCDIz~vP+H)&88@qE!QI#`OwUm#dX)ldIݱ63)uZ\Qa`Z{~WK:9 6:n0m:"|B#優8؃U5Bg|$Ot$aɺ1o|-MP]E:F f@ $bI-rI=wĂ Aw<&ω IS cS{voh y3MI }q$aԭF>"&{O8 OZ$\Oi,E$4v Sk $L$eMbRH;L$͔\n-n:/<7hH ~cdQEqᙼc eN"%Ao?4)r hkq}j{Aۨ:Q}SCL ^R/k}1KŎ Zbtdº\S/`?DӭL)kgYIv&mD~xWQ|0#xa!q=~K쒦IM~ĈhI Edئ "&Dۧ$ج%6'{.1mx$ YHL]D kФM7EShPIKHUIᄗSjjPzǜY?}R"'I6>XI cb/SO3d.S"6IYpEߪilM<=.)Z$ ΓOtx"x JNIh kaX!IPN~_gģT'+lv]y$ʢt"qSAm{^颋#焟zKAD兪c!ҹ 餞Xg_)MH)H,^qJm6P8m;\$EXK@Pr$=[%[ch~{a3N4إE#h$Q1|7x4LsAsO%H$#mgT9$TjD~%r 0:X->WwLC`HJo|]W|C>/;]#nSFè\-'opJ[$ӧkdO+Z u1p ҈|1,OAc6A8rJUܑ_ #m! )԰ QFzv6]%(&,o\\۝J12l>FtU"B-6O,"At[T |丁/)O=np7 \z'ensmeCtq+~&(nOaM!67|*|ϧăH6ql2Ejbg&gSjhq`4'D bwD?ۥɀRt \}?'Os4ޓ*q!NbV` Ĭ =da+T/SS3#(DmNPBK4DkaM83n0Z4A$%D,ăaH$x4#?-uQ. yQ/r4Q[,=15,4 I[a2y'9b4 I;7:v)*(=/$%]Dz^G|:*~t g"ZxIBH3sӧnmy;!4Mlp*yI&M+Ny[ $3=W1 XD6LdԢ 2a3yos@O%i2M[6r8J-$xRT@ 𲎊OdN'X$:;F (L.@o`R->rREq']y}Jm \>FtRݒlފw) ́#TlBF8Ch"tCm% Ǫh'V!FT' mߩ킰%z.v voԤw zknF74H O1FmcR~xBtfˋbx.Vg ֮c'gg9vO҃;8f显E=@? J`M&b֔OV }d;kA-i|73H% '0?,:3\FG`Oe`Ghag< MЂp߆o=R?OP>6r@cŋf=R T= "Jփo k0?X&630QPۧL?$ l((} JBf/;NS;bSM͂@vw/ Jd ָp ik 5Ҵ􏗦 g)( X~0)5!1}N0;(qdW۰oeġ .T+6hWJi;\$6l+&֛ 7F -U8N (`&-86"=#c3Fi4Z0mx?g0蟢IմZ?~[1!ԀH;ŔKIo LyQ6&?I]g'^fG5EDCm6]sP|ײ 4Dke I|~O?PDl3oC? &C{DϠ"m-5c1 _QzaYUtBDz~I`+7"O- A6Æ]rN":''tkH#m 'tީ2:=!o"[}T\Z0NݰŶQ$K Q@LEK䈙a(q (^~VéHk$?l CkitF/-c huMr6KC1,»['udIg(7bZi=`Rq{&AOp.l2L]ԛA8:wĚܥ[Mn)7ZaD${a\+ x%6MfoQnto|v$lflFN!0ź~uMµ@xuR1Em`|>ݐpq "Uw~N ͐Fh~1ۚ#]r-IVn: 1zaEh %Aah6KG֖{v+vvO!R:V(nΰ8t|PvA'P\t>9 $ 3Xe*mOGBu-#0=R>( ɛǬ_ 3;䐖,rJ=|v\4pL?\vK;"|;lp\ȦnH]I3|:DnewDt s\y'iRNF׃~-,7vD>#H\A9m jbD}1_ Sf!$c&O߉ac;1Ј` NluE,OgUQ*&uk3&jh=;F$.)ehb#MIoMl$9ImOL0FQ#y!*>! z!k{\6GtL\ȣfmj^okdL ob);Iߦ Owu)a/oO+PstiHϔ(2t6cd IlT35b'Q2A;wY1r,@LD E)L5H(N,N1! JmBCmPdIA (͔_D5ؘkBlp`m{GH MҔ 8sɒQa 7N AwJKI'qt)- "lP\]4Z ]ȷ ev.$v#~XV{)5oI aY q&Jf@"zSM5 NmJ IP'$mvXsnd{$-I,$auOQ|G+#AM,mnPA3pMLDnamSkh ISSD3i"I=[M=Ssol2kDwx%I}Ex* t@N$q)jmM ě?\N$L-:ƻ.mq&B3'88hD_4bs OحIZD?#eЊI'xo؋nvEkM('NL]JJD|$- 3d;xc|+4Ob@N/,2:tȱ=;Fc}6ZRҐ;Dt~iԧIBwPyٓ$N ?=ۈܪN굤 'V#7`)I@!F#3 hO%XH"U"ce:R-$nӼy ߔ4uFC{rPϛK 1/S'R[I% Iz}u@p2?MH1a ͺ|#P$ac in{&2IkI?хdtHElIe2T ; ;olJH9jq DDgcK747b߿<+(2Z*A6XV UҌț!k2H}i/im![<$xMn wIc},JOu,ҫ0xKtvQ⎡%M.AK8;Y68Z'miH":? pZ)0dZ7"FAxMry=vV)e 'Aºc5rn moM%8΀)MHKD3#pLnP|v*n706Juߖ47CA"7%\nR}ܿ$?*z38JmMA'N#l$VL=jhJ'O'_tX=|:5ApXGf kNRl;3sctҙe%'ym|VtN%){׹B)F.fV#U,d4"/I'1|"n7 ?@Q(O3\#d)&K @~IC'Q?<2s'=J$^'lYm=̶N : $ao>JNtB#=p&RtF#s~ TFL7 X9h& @?TZ6FDX l44y;a.Sm?4PBJy\ -\8./tMS+PKqcpP.,&Bv-xCt7Ct"F?]QCN%/H t#+BIy/ D`,fǨd`ho7pq9QߞB`;aKN>~dak9?sMoRPH#0JIO^8Lɵ\do\XoHq_q%6ˡ)$ ~c刿(]wNrkBY 0-,zwº\OdShg}exsynQw}syB6d1O4P}6æV)GCFIZE|%UҋD'dVt e;n7qVr_oߞ B8~CYK'SO{;'MrGtZ^Gt3"3q"/$^ ZS{OL"=7p3h}=S3 @"{$f 0dA݅d^'qtA+#/T%ID8I`q o^' e6<ݹ `?41hV{J&뇰VS' I|hDc?t[yM0]o"AnWCm,5Zґ!8& m?=}g[}NC asęI]2 ^7=m&s_aZo1;xS:ܖ'²bwJMҒ ^}0Hصo wA* I'A AiMǞ&lS8nDG޸' 7RJ(Ͽ14:_Ą,ꜧl姠<8ksO,bB0 {X1=R{=,+)jkµ%"t{a*.z HTBb0ImSnfHy@2/hQqT$1&v{O4$$[%<4f?=t/0Q)mDߞ_$v?L2x+AdI X3kzE4rZ LOϾY'K0I "$ʠ f E6ؓ K0[ /,[I|14^ec!PߗlV8))XbՆ۠7D) ?\WHf^إ~apږa@EkE‹. PNءElL P ӵ`=TZ޶Ee!AF@ۧUjJ [1b9L-,`גuA̦mÁm Y.b]h@6O剃O"'t"ŠV7dqfh&;}.7B.O``( GĔ3Pa<6,$7IoLz'#fIJY$UGi"A&֢R>͉b7DkSIeEDnHOxP7;GD#%N@ 6º}tQ i$*6aW!}QOS :FnxTr*L'|ep87P[z@h6oe+CvOȍ-[<*QD[ q6$Hjv&:o7l>P99&N2$udj3^OOadjd*L? NVMhP jYj msO@Tv鄬[U iFwLc` p"䭄~bޘ`ݔz&0&o$Gyb_?w돮_)3)-ʣ&}/aB|2sla'D{geJ`˽>sNA< (Ji&؋;c*q0,}!BV/$s~AD JS2-cl:\N$ ot[ .'DrLKϯlM S ߆'eY>g0sԠl&>SlI?+"m醲s(-,;}Lm}ur݀UҘ>wUֻM:wbe~'9LOEu5HFc#@~qLdc1!֐wO8m V.z `~p;r`.&mQHw*eI 1# ÉH(}Ƞnu^4cO,8XP@&D~w0TfHu6u8:sHP,G~XJC9l)DwĊ h!)DH4dh䶎56:t}7vVG\/p|+䡑ܓhE N%H:tRrf6": +(HԢޣpI?Mk\M7)if v($@Q"$?v)'9B` uK 'LL麈 Rف0D8,2zǾسuX`$}~l6N%.`;: mV忠텢x 0;K^(,25ILL}xW.薆m%JIᝨQ2(AMn.F,nJA7RM3huCm2 f'Qy(Ԓ Imas!IN!`7T3#]:d;b/!>BQa mxŘ.PU:tʈ)QD)7Hmk_6oJ(Tpn$툵.)=..۸I]*D(RBRYͯOdElSb4Eh>B`7MY2> pvNdtJm`~xJBD":qd$@t=4Y)OG& 7o;F%ep ('YëlyAI =Dꐀ'׮" l$&dtp} #ICDh6ȗiH'Gp ԍ0rj.-)3&v?<;nε5Ȏ3imy߾H"ygOsuOs"1%FW_ч`.$3aeīi@omJveP-TN ݲU2RAmD.*Qk(ԡAn%+HR4!P1|BJ.luJʦn*RPvqZRR&qQqd{+ aZn$9" ) JDY:nوX|42fIw>߳]X=| qBΒ&ڃ RlE;M$[BɴZC%$)H3b7NAj06&zO\$.-rRAI`4L }0|KJ >[F ϸE%6P#mw}]Gʀ*UKdHtAhsP=: ge AMF\6! \ֹu`y1V ۍrҤZ:c` gh9vj*Q$ ?>nR?58ī ao <jL¾_pDFItÌ\X7Ts0)QPI"`eMp.9Dxp(-A" Rٞ:"˜ӡ<)@H@A`\zBY1Y@RQ T PI& Q IIPmn+\%u07w(P Q:%?`9=9ZJZτ(̋MA-I@")YTFu^?EeתPoX`q~rPŷ#,O q%B~۲fC(b mz`+k6$Lݱ%,0 ͇[ww|G1Ql&DmMZpAH-'a)qH)e&?Q/$QBI"Tl-~um-&RN$b$r ɛH'xɕ2f-p(MEP$zaZ譑kBaD˲BDȼ$ZPG儬gDڒg Rk L 26_cV_#xDFVeVGfr%-P,E䍐죒|2tŏOk 薖t=|X~N6X`7't֋iCcs,tM=G~X,[$̃k_nD賒F֓.:0F-p5)ya]Dx<ɸ}Smec<(BH[JY,=) Cw$J ƓnLPZgDA׶Rؑu²p'v0 6HF;[˾YmBbc:Gr*R.%`x R:1^kW)BTM,:Z)YFŕ"pMD@Bе:R}iMؤFmF>,ɦr[[an4b!RaV;`=)1ph/MXiahYV?RA.Nk+n{`z@v7G5%%+Qꒂd^" l1YMpʏuN[A Hq%p/P\FH3b  \>~XYk$@HEϷu%+ G|\ B'mt6;|G(AP1:Z-c ,4YHd?atZYiN_?,3e#Kie[yb3/)I$! $/CmTBMXiq@G1ťcęb{˦<u캊AU33)cCvz% p.}V3]BJ өZΡ GN%-Ijt JTԡHK+% aIW_Ltdj"Ks#P)=t#quݔԭ*?L3ͶQe1~ kl%ID:RNSV.=4mT&R*’R$SVJqZ&G KyC p2yY#4d)$i *^vqliIm%!2a!BA:IUq%K(lK6.Ii: J$_X=8 Vm]t,.mbU `gKQdq_0\0X}du$Gc=qSB~+~놩sLtv 'K(ʖ$ Y$[T `ڕU7Rrځ08D ٥Wy7BեHҕJIeHF,S25F`8*!J`ۿB^-I"dF:{\;&\li0@ NXpl;dr C2$LMi$ 1XVN%hXP2#~EhBTLZG.4g"SG/bOÍּ 4"/Gd26FӇDkn ^[ua'k#kmFbAz~]_#dm9ZA;bE. :+؋o y\Cw Gx#tW6eiӠ |k5ى%P$ƞߤ`7|q߲[Bu@N6VWXƑ/Fח/FI$@ v[$dhEBFcdsCK p) avWvQhl;&'Ґ @O O5]7 o<8?NɳeHqe|)gzRb.,v&E!KKi$#obil4焘]"?(~kae _It7ВO*&pi 8{:r57~Kpu60T[ ɞcc'Ȣ`~Xk djQ)yt3ilEw- GMxdq- H qaxuR-%%Bnu""#gmuz#k.nU{T!ztD)9K¦uDY յRtJT@Dт F@sjM;p 4,*Æ#*RtRN?BB'H.<( d_evBɢhU(]Z *:PRLǗ6#(r\\@@o))J 6p%0>#| $nJ1B4vai]@mbbBa?:oh$rnhe)PLl ,gCE:V54A37 _ٺ@7:,>)qi_y rYU89M7R̀mTM֋$.=Ԙn'~HuD%#ov-& A*|iaT6bV` IM{ BQѺZD7=Hѿ\Rtni)D_ Nk5$&Ae!!2@W0"lSW^H7t 6B(Y)+ZCAGQ8%r^GccuU4!N&R 'Tź 8O WMw\2uN8\PS)%$!$ΈN; qy-A;ŹӒ䨭EJRtja@=޽hCN)M R$Z0sIٹe8$yIUMDZ n87 y^TYI ԥ $llg㞭&M؃[hr+O1 %Z4:*fr ZoTLꖗ D\$iUQT(̤4smQaL8S6 ”#xo劮=/-1}kptɴ 6\\M.VUږ$>_OA(J}79dґ I{툩, @}h-wGhh3{7xEtXsH ր6'ycce"t6 n8AIoX&焌CI}%!^o1|y>7MB#I|1}Tm3$V7(u6fnIVad; "焊G> {qXop"d7iؠܕGVjI=1;r6-A-|e7<2 <,9$  3E =q$R"\ZJoğHG84WKtހ@ ^o:,mhv:@۰釰P崢St'x;aQs[re}G9K`p =I =T87D[²bDP G]𬘸%&{M7Ky[0CpdL[,frrN`%Fɝ ]T0o&>V A3Pi`iwb!5q xeh|DŽ|=p G&e$PU)Ib$aLK 10BCԐw >`rЍ|md]3pBASlv<˿0jGy!_eѩ[hHPX TEŒwnɦӨo/2H&H$*, Rt n-e9ǫb|Knct(61qW* S̺úH+*2RI'/+Y2Xe.#CQuiuO&wR* Lq8\eyb!S8{lH0 WAQH|/4%E Jiu &4•pLt[N>Y/ER^h)*A: *;M;+t{#8蠳Be F#Jaljd hG do] ap6Y@ES4d!F($X)iق!%I$J!(ܝYj6VmBi/QuVL*6lwù]m%xsl+}K4ܒt(N#?* D]!G .Jإ*QC >~2gQE6kKJ:It Pa~I74-uU18C8ar%ԅ/L계Mz ],l59cGتYBy<jQZc(@D^3ᡚyH &s\PJҐ0 0)a46!2R J@ YIIa#ea?oul&֦ U)Ce)tɰH+SRDٯ^aa -Vr4V)l I$P6@o&s{WCMmOS!MnDRv Pc|Q!զ]ꁪ(}-4HS`JH#ؓbkD22V\Xf PabUN.yl%]}Z$RN$NG ?[/8sKUa%.UK%GZPKeVi>so =NK(pa1T5feKu)Ru AORzcZoeLpSOШڒI!Z_Råda8O+;,J:c'{r~rfaֈ-$jפ:J1b(]쪋Ny0-7ϖ4Z(Z|I\G~ISnf Z&:gyCmR 1O'"F`X6 nek}xIRв'JcRidAA AFڴHۯ\JWc7dij)#eR5Xo;+mb}H7S>aGAa5n2[: b %>#:K]pءLwD;!V"̏<8*FתdG:@Ԣd*3s,PS`6Zjz6#l6C !nmDJalS\Naŷ5Nsok^D}l4H+W] 5ULoA @e}*q S+:JTyF3[ӍJs XX@Фn#&.uj-JΤBt'ĒOO&d)p.TKZx ʣbzX_eY3ۑPeO!%M@(}~*ECeD*OeS kz|>U$ʒ?&0`'~}En23o] OLBT CA9c7X\.qRPyv Ĩ^/ki%-jE'E5hb2H\{(M ڂQ)UOKw=F:U N|l2K N¨1AK.\XN vU./ d%*P`U-54QUɌ*C!H7R\B bҢSx"jAeS{5FTICmK֒dR[${`TyhܔsP4@>h 0)%ۚפۓ,7OY\PJyd(L.*tE(n䂺Iyl$)h:|:D^?% iPKNz< W~dr\t-pnOcGJT.BLrw'{0Q3\Ni9]5O&] ,nAf7_+(%rN!m2Q%ZRZ=h$t^a q ͶTZ0MheI{" E:F AT`D I ]IJ',R jl !)JSjVJ8CO.FTBMԟAQiN)Z>R>#IBBSdזb),6-{R.B *V!eWAj S&6U+㤉p~ʖj7T QۤcN2-nd @p86HZ9raդ)IR@=I=qiT0x퉫 6@&>񆲐w#LIanBiRe ۦHb1f8 R/am@8$o J(Sruz|bJoBRo;`N ǟ)lRTa%5ZH']_8!BCttAP2!>@Vr[$߈(q4W& tDTA">ы-GY XZ-{yx{!@ !@16|HnM7DӶiy(~=za $ mIqwnami&$ ~rgJ,I sY2bViNidFmOpy 뤦NB-XF8?}|h(L'IBYJ2 wځ<yjt *ֈQbl5[✵ DP3da3N’ % ԡ ii\ETOdLp 4TXRƝ0>+N?S> SuRE+EpeƹzLB*2%려]Yy$)I)hCM$DB39bHT3dh+(VI A.,&6j;4KnJ!1**6lYfJI袜T;j{Xo|\|T?(نu&Su_✲fm ʜBHJU0U0J@띊qousLR"\ H;hqP6㢃'Ck'SuDc[b)TD@ ع4y,Ģ ls--!%Di+_E[@KPy] S*#L;LO\hARbL%!kz@G-)B!PoH!@uR[@Jy714i+p`!!$M@;7r;8(iiH#{! wP V|VTF`ORpnmJjR5T>Hp#b n[a-Y($#Í:iE\du fV6oVRd{c~o<릗e9`l%0F־ ۚPH3젪hJ)!*VI1L^u@cuجyixVZ¼,})lBeHBIhqBhWip}s-Ȑdjeqn  $ }؛% rQ ǘĕi!SCb>{zћL.c=MjiU5Ă"w]3/1FLJ[QHTfꡒgFh+@LjfwjRB( D@:d6:HËH7}$a;n^[Ę'iCQ쟈Ҕq"7.i;QD-f-ni"A9W/r 2*.nf4ḎQJ JrA(cN0>qIBkaӉl7"/&:$LywiHaWI{¸HWTMw¸H[Xre#P$.u¸Ndy ivBecR~$ RT*=\)dӮeAZПi|m莥8)yz*HDegYPSF,nRڜ.*hʁrtwFRp36uD@_-N!W0 { QfסalM\‘$jm@ ,TZ3]KL&(iԇj5 ʛJ q2Imκ6g5O3Hik K-X<φF()@vx-2IqZ>#ͦĢ{-bEd6ˮ_:z@'c~^UaM*F.% &IZ L,nbu7+ZyiL.oSUefJaZ\ڎ5aM_6~qIˣvEɛl*VB[-=O7YmV+N!גHW>!~0r\걾k %G$[78mݗ1$QsZu)I@Iu2qunJ u jJ|_鴟"qakʫ4Uw* | ?.ɋ= SL^ZN/@\:򙘌P Q#[I{Y:D排:-c1\u]m? Y4p Noi$be,$9z湳Tj 'JHJF7pg_q@vW+mK! RON [I8y^%Ă[*᭻(JJlIjO åjZ6eLh 6Υ Bq!&f 㘬ħ@^l15K%!#`’/~g>s'JІH-CI}-=E\A4\Ah:q[%f 2Sa l#ݘW#!nFhsZZ 2>xis5˅*K{eFMKw>e>+i@fUˆ_))ցI)P "V4#"$1B\@}ZO:&`&brGg.!݉7#pXL]v@)1 KyoWZEd鹋`w*yښ)mQ $}ؤrW$yIO&{7v c!|BJ4Pi# I$/ufJfm| (N3! M(&Zc_F%HICPTS:JH3냰3]f %TTԣ ZRD,Ƒ}IaVq '麌жVq֠ \$ %D%F*[\FUKuhB.ITI&;ǟl\l,P$}~}өBohWR;[{VP/ @R% jH7) 3Ӕ,. (Ql-I'RRz|_N~8'o Pos6Z^XIX`(YvNJIc=HR c#k૖<|W 逰n;DȭP|RiSTgrE:]y)R\_7X(BcBnIF$3p 0 @qMJCjAХ6VI31mTJi ޠvJBY[ W|Q.ktV&iSVN\J4 $?eGqo= E9sIJ[J5TOTN" tůCP^iǡHlO[SVn^%:BVpZH 90-, eҲ6i RA檚ف JD $37B+tm~T.P8ƨb 3xu<줩rR ZgXD~!eN+Jml%Hа\K@@zc}5^kcq9\qaS٫B[qhSh*B@ŵȻluWjnVomrk%s:iց鋴8dvdDzmUAir4P w)L.Cı9橕y3DҐ QԨ2 0Oաiyd/UʼM($δ58vUe9([)JBg~;Kl}erH" 4#urABNiăgr8X eⴒnʤ Q76ln ΰiNOQ|_`5׺6p[)-$zQ_kg9X85L!pUCQB@l<xv:sEM黺)CS3ZP5xH6i7+V8I k@C)J<L} ;lG]Hd$AZIF)-#.a'e C\lnUχyPRdj13`@1≲n5\Ep\pU9 he ^&?*F^ MZ/d1ÿ%ͳd6[qM(JA/Qڮb mR 5(Ll cyrUυR7&z^q13ksn s.QqAF&AA(_glB`-&1N^2$n־@QJH倭5` h!ΥB d`!Ԁ9 LH>I ew5 \-jRN"MPJ29i}HT vb3Lcsb._d$`xL1IՁN1Ń}W\y$J9mj RbQ۞s-H-$@RLL,q\z,zOMh4ЧH ʬ"pB3o7F^% Y*t"л E;)M4T#N2BV29tҕR1 \mi-7F$uG)~%J+ T`\Q]BOآ4'I%CTH(⼳)ٚ VU5:K%rAZbRticu'ZN=1JnIxj:*P\e+[)W3HJL̔D{kIvTu {keֲVr˩iT%BRg-~]KZ2颮ʢ]ZK;.;s}4PslvQ9qL'O:zk5DuZJ ܀v5RJT&@$A 1鍈*ծ-p#䫮С.KiHJI03UxirHiPR OH8 %Ͱ\n-nj;7e#!a N X1c7YY؅;%~V\6 eL॔PB9)cC>! {3WV[~AP?it4,3<ɨZ׺) +älbyep"WCxl-ʞw[%Ri_[:[HurBۤW,o-K >9.pbʛ<(&Cmv]!VW5`3FPByi8 H6bm)AgPMXcM'NCu.`j}Lҵ5|yUP $9X1K3]81tz TC23)p6!!A0/pV%NUHp_>)J|J#6P7n!qIZpکCo. 쿋8!s=2R8NLyL.qTx fnסdR4wi(*Sq!-!jd&V Iy8ަAaYE]FA9r|o Cy1EbEo6"=Has\&LӪ9W$DŽ[1}hX` *JR)M;+%LӰ bN*Ǫw%D ,K0ꛨJUS|%K(wIbeFm$IHFPS TITn8d,KmʽdyRе%)A:'ëI77:\@ PRݡhr)O5r1K>^XL[-m;=T* I$@ƕbbO GqAJq^I`)I1RYEuC[EQr5UNV1))&AJ&7^qȮeK5@fRnU.[.s+d^'kuadd^QcEcKIsYŀB"w&N3tiƃkKM< Sh%2$ o+Lk((.(j+@إyd}hA y4kKĥE3m2L1H6Īfn6e PO ~PjEg uB:t+$,Jฟ?(ă.: pc\FQN^UCT|-6PgعN.z(ʊ$< IKgHZS2|hG!T%âhqF?O*Qu(iDR N#fQ@<MNIN%%AJ5  \V\Wq=9[ [B"JR@v3O˖7uR%(F|edIWPmmT@4 χLwoHeLaZnEq>$]05l9njh)F E~ɋT2ُ#3Ml i$ ?Q5ALTOdcNW8tV̑kԀ$WK3, ͓(L( VM! +Ғ[U.8A(ϙ2B߂ibG\Ϸ2yKIZ|25 1K'.;#?wE8qm+/rD$i&JZ?\mAE4 eXiiJ]A2ڠ Nvsk=F+`FsUu JRO\0B5l?$%* BO!'OHTbMI=P4*VJjQ-kXpMu9w z!Ӗ+RB P+JLZ J d CwH R!An'ӹ#'.sC,n!(5Jy- IAj"Qy(b>0ɷ[.t%PwX6(3HC?% 傤2L,@|mU!)W-A*!)0NHIV`wFFFڒJL{I^I.٪OHmds e.)iOR:c!ٹ.C By9a(*HIHq@HD0g J%*&BU`U"Fس GpI1@+))KSi&U)Z_ A-֓uL1x'% MFbSPK(NQL%-*&myq"hdM5JTf6ލ$O S TId`.ptM;.0)IUԤR iVuFrrSg.iV $ne63犦W 1!!9z"N:(P~!yF $ Ѓq)%ho]2e,ZHW^ PH2]#G.B&TIoˠmy,׳rYiL)0ܤ"6 qI$`>Wk%+.2SEiIQ i>/@gExDTT%+Ҙd i*/]VT 0\ &RbZ>] "$}VP6J)O($>" Gc{-G~+ d=T6H\N1pbb)H'  58HҗV%%%!ʐ9QN7jy[@?8r\.W)K(Z *$|kynK M_ePK!UY {^>[h-!.8]qa36>q"zj+FZwW!eXl/?BLY7.f4j:KYۦU\Ytt|VsE)cl(k$B;ZD6Y+|qS5<*mESSny MqQd2NR5U@K.׉irz2e}wm—64l)iDI7!0tepkpC[ꌴk)+W繎\=fGX}.ja7K:O3 JOcPRf|6 9IMQUf[N*e͠.NbR0jp+ pseEV̎kw=,ϱU99Rc.q% -E3y#8z0i,< 1h8֣qh8#{,I|USSϲnTȮa˜q%r9 `B*lSppahИ[s_@S탄8k 5_=JUVg֪H S ( Z c]uUaqkh:lW m켋+*je*GO.M2TT :ԲvF=ßE0qT;+2y8D&R5yu$;V(HD hH2۹4|ɣJ\ .{]Rd:e.iRCde* :[*MʰH)P*z&̏KacʀH6JfA;Trhʊ'UnE6(:MP nSTQdƘRgb1 eB L\t+&RS@\jQ|^yci8S7܃ꔲ–$  #n0Hcid{.Yf{RԀ@#69\#Ww9szy€ |Fln9.Bg'r9J@@HL "<$|ZjX%8)blκ5U-zZI$A zb[e `:i)զHI||Wv^g4iN sa[+yAn)*3|=ăl]o=\sG:HZ&n!I)DGQ2%Sc.&oOeB]UJ_RЂl5 L_P%z9 _a!bBҹSx8xfhQ* O̢@PJaz{\cNge_=$UWIp{mlzdɍ& (5$ ;MHH(mƵAE[ack7{!MΤu$ ʢ@5p{6 澞kYsju)Q- J)h1LPed39P8Du#D;@LXo 檆oȧ:ڃEQs*T uTtgX.HH:I@ēH=1L4k\q2OCI XX7t Q_RJpbJNHm|ѶF&7[4iRB$i$~X~=:(?[kC'--”_u[l9 FHg {T\ZHWv 4mӕ4)Ę%#nA \~WmѪB!I'T:Xl6㞮B'.}Q UpÊnh__]t]#A呦7%"]t~}USܔ˹J]B\`J'qU?*4 Tk֒S`77MKO4hiH:{ 򖕡J 'Bl`ߙ{)S+\1$SN旷{.͕EkJƫjʰTXRTTQP pE>=ᦡא-p[*o2mU"ӉB^"z@C 9{r?ș95lmKfr1]M&]9aUPJJ|)&H ؎%/ 2<.:Z;s 6tV0Q5e:X;%>79h#Tlc#dK>#{.5}'ɳ\T VOq44mM+9H’h+`I:Jepc uRpI+mpn{+y[Қ]KL*Z !WƎ++E(FU?smy)s:TfTԴm#0nS]*M7Ğ"ctLt%Ց^Uc--t19~8nXUV%j=LP !$8D:1?giuW] F#m ZTډ|hҠ &; Y;qUd$.SShҨU$Em$26HAn1A iL@&XtŎ+Uho e,+}›\m͒q۲UVS )6 (tQIőͽ-4.JRUx/n͡,fmtʐ" UOM͎ecevR7툲@MU7F{~ҩhԙ2B`BN/ ":P@HR $IH7[VhIes e?nBI34%\Kq%R|l3%%I؉,mmV!-Ůz )Q VJgbH|HFOQ/\Z@ $vY]hMl֓#P>%Em1ARdmn7x@+*+nhO𨄟#Bf5Ѫ C2} P MuɦhQ !$u3lXs` (ofaũTR-Ҡ7,q؟u+ ]*麄$G(R [( i>,>$rjU )Zt W* A ҡ3i 3ؼ% mƅd: e ~Z٢IQ$ `j +RĚd{nfYP3ӪMD|kS@B։i\_.B4-,m .:Ԣuj}&q7XdʒA1?~#rK; +aAX\ms`YC!ŇQQKlT;S匊Z:G]EF%)u\q.)/4$;Pt2b1c&/fXK^u}(%: >-X/I ?C)6f4 Cd_f.{4uT!@h,<%A+'+ 9{TW`2 r#ۢԯ1Ԧt-KQR$ZPMRq&m4TДl} Ĩ/^*M;$tv܍K.Y<`SuCeZET3.H ZXnaEaō#I ,|H,M}?8/<ʘ23:*\TPYGPЩ`SKhP!D8ե(YplV5wqj$_NR Ri$Io8.it ֜f>H*a>!M!J:p/0Oc"أ_zb 3iaDAbopݳ}tPJ@@=ս7K!4s4mrT@$$إ@ $mV驲IwS,%0<%ILMV YI.t> RHTHTʁyPs_ñ6QJI!{-2HݹI hs)zj˟& ~p ,T-RLLM1#= hvN04I$"uHY)>)og-. !Rt6ުLd=( 0$/ê>OG"@@8uJQ 77R(4&B7n1&qB O8Rq;؜'LwS0>6.͸RVSeI@0AH=0>8O#,Ty S b@M75Pa& iF0A "X2KRt\OeFRdϨeX3~m]&ʘ 6btQrnh]u*bW'əuk˅C˪O$!jz㝪_]7쪼AZPBL+X Si@~إIk,b)F i7-e` !DxI bBǒjycy: A)%e.n:ۏǒ'W!![=+5?+t{+M2֔Ӥ a:Pl;8ʮtUKQD$cw2N3*<ԕ.Z)izznR scH>}~#+im 7]MLւןw,gw&ɲjLɊjsw˙c u!G,c'#34./ko{u^a ls ֽkr}q #3\/7*F9k+XiTJZ5Qx1GR#~7\up]ME#HbFEGÜxPq%JoqgTп=RV^soT!A y/kW&ߪ!)cHǶ]}q ʯxv3+g#}/jzE&iIRS+V&&kםz 8$q[h98,-iVTc/VbNd7jT#shUs8 Ğ l;e`t횮6HA'p9Zߺ49YAUͳŊ ޡʩʲp3AEM ELwdGKRz-ubUkQJMn o;V*+iRƹƎ:2F.>5 d!+izIǣ`IV7@w^w`Pe4G~`s0k6Ϊs <+nN0{3j <)A18L>z\;P<ۚJ2LƵ_VTdL%ԭ RQ#~񊎉3,=¶44pS̛ YEkj}(KH)l% ie)–\+ 1i4 Ő]tԣg< \{MMR[Ч8@JR  Q 4\^"䛛]sG@yjI鶸M{:JwD!B[Z % 7i֘cU[c2lK,::cm{]` =[t(:; dDAkRJN*'cu@"BV ^:>0*X*\Q#bm5Pw[TT' eK Gq0hym'IbZi2![=RZ2„H$jƭ(K"r{íxP Ֆ*Ay.dbJIH(;fka4ʨQm :T nT/!Xd7 PiZN 낇 wvTS}T3R⌐SVѤJ` *; g^4m撘X'FԶ'I$3[`9*9hAJ` Ltw,o->di.Ѕ[a@tO[L)bT3#uךyklagJu,mu)!38 &s:*uMR5(%m+Be" Dz åղhߗpm8 &D*@P;mW4-Mș>[-F0X Ŧ6&iRr^ :/S2KaK) >C+?-*Jj27$o{Wz'w2EBZqm hIPZVҵ 6?S!EK;{.2|!W˩S!j 4 i'~1(km(;nwe^MnxՄt.{/9fy][EkQ*Z'sf}{]l0𵲀53ؒyic:{'Vjt@J&# O+jct;ٽUs9:e!!KRcR(4#y?àYNi57:|ecOPp)m![>%7LsxcNLi@Ƞ^62OW%]nkp?s *8ad Z@N9A>/E:8yӞ󿳗/)u N{L @ &qfX.foaUs꺎W|$86> WRߺDp *N\``)2'ls lo"ƙͬ-ahu/5vwmuc2aMUe.UCgu{b8,7$r=Y^ mC],sϗEθٖa:s:)aԭPO8cQp1a>%,=Fڶߺ5X|iDvn:5$7 QK'2[ʚb OTq2P "|d`NRTb &II-D *f$3g[C>vfBK(%6".\ h ) HZWP1'RQcZ!$@au37kEIOSP TNR~+(+BЬo <(m1cDv* )'HҞ#mKjlU9))/u VJu.wI1j:X= _ IPP籶,>wUFX@SD)PVnbB@"t@x3=,.Hs G l)kjS"ۂi|24XfoUx~#~z OeeAJJR|qCJ섋v[3̾CYXu> S +D)+Jէ@ xe1pݵ=J5 ˷KsAQs'˔CJ .ƾ~%,2'YqtѺ xDbdz2x_0{a9Xul sń6JuxGMkmnvG)- e7mq&pZu5> l8ހZBҟh$|X,cN֒sieAEk_yz1qKcJ3vV\-f6Sy4ƍ+P嵇Ruuypz]A6?EqI"쌞!?%%Cī bR㨦b o/e*fR`PbG)6'|jxcy8ï[־)IȨ\-dI?5 "%O.)j $<{ò⳵ѷWI$Kó7h~Ǟӫ*rSf/eMW"!gK`T PM+ę̫_5C;n@?ײe+eC\Tr[i#JL2KAX1k+2FO*hu:tLic!_?)32f=̟{6le+UD+/iՆS ƫ%P\9x~;ڣ?tIɭkh:/F$؏Jq/68,*_b.Up 4͐9,xǍxĒb$4Ζ( xpa~u%Sb{suźߪ^w/c| yNxRe9M/J~ʲe-e[*f$Ip1If1u} +r"ü'>qy FaUMJL(*$ l{'EEKJq/ϸ^[WZ%O['uuIrEBX-L6X.,T ?c2bA"[_Ƽ'a[PN{e˜+dL~y2Y3X Ug4PyEj&Jlcxܘ1Hp-0$&|m5,"CTѦZm cNJ{l.x )~9NiQHR`|=q6]4^djM.І6(%V6?i{Oe )ux` ykC""eAlO >*reiFs!j*=o *A:JJs5Df6)\)S` c@&]ϩmur 7#@JuRdxV4f#{s~)CKtτ冒["kO6Ԙ0;JnknNڗ(AQJ>qvP$ipuA68[,%@6QrAK\5Py SqAnn<.m8?Q M5U.h%ӧBzA}A"=pVԵ =0XmTEca[ /(iBG E`˔؃uHd'UO1MА'A5H$) V.g27;*U6@suVKqJ Q&2jڧ %!QU oO\Vu*.je5~Ք啡sPyFƽ-TrGcۗsOULZT`iBИ33VN|.9Eo/ :.Ay{R ڎcZ+FX^>^xf xTP> B]~˗.hw E %zƢa^1S1)X,})7U/0308!PkDі(ih7VlEk<9!?i3m3ʮIskvJsT)r临8S6V(KTܜNf3`p0|UZjitA.!e:ҨQ񓸙ŅAl/fJA$o={ rTxf W*3'f ƒASV ym 9]f!M~606#4H5522\궾JYi:`,ꐸM„H;$1b a]al{\s'MzSLP*)KD$- l/RɷLs~@sT5uK`COY{.ar0<ڷ`MEUYU=URy X R8BBJԛP汩 mƗ)o!uӶʝ3Ww## e-V)kNYX;H>5 D`lxsǞjxxnK' ' <9[4,bQ'^XsTBF:Lj?Nd uuUT=N&nTTEJY<~Sae#IY7; k_3YYJ(*(kv ,.;i|{`+.;gc5M桸׿"rkRM*gxL(Hc8PԙmY'/O2²X)>!;1%]4#͸֋tdtuRn ,Æ8ڃ)Seeu5# #N\6QP )㎟.5FX죠+͎=CԏV7MvQT)R]EBiò !IJI\tt6Eq>c-kyBkNVm[`j8TN湵 r" kvKm5> R- 2 q=#n<B#Qy) :Xp(|iUMuޥ * f*kP(ZQ4vq'ZT&qlKi|C >Χobyr={ܫ|= 49aBuODj+L(bkC %*2IԣXߍ1SF.G{~ //7\o9rLi[3RTҾ)H37&xl/l\X>)j]rX,TU3ъ<) Ri©:p:bOaBn]OITiq.3SPU¦)rSK-h:t_BE{Si7pdZ=fE#iii%4iBʗ 6|n)xsC̅VJݗ-'RYth3'̒5 P//]-R9(ERYRڬh6@ )3%66UeQ4;cU}{Gy f]ESB O-Ҙީ" a~K)B⋎ұHJ[e8k`pHOr 箫v:f`+g<3AUj+ݫ9i֐i\ZyNAmz  fV '\kcч..ʍ fiC !J~reXRWPn 1bx#fCbo0KUț_CKQֳ+%Z9gx?,rs]bnۯEU;rHHFSs6)Mr8UI!Ffő%*Xԙ)#3Fæɺ|ŕKIMV'8I7S(.8cOȝՇir]:⒤ IүP! FKvzʺP/=J}- Jtf @382V`b: C VX"Tt0~f)y:-eV(HD%*sUd;IQVX* GJr7*~/S6DK'l~8 ˓Auƣ^(P8Z$W?*m)s Bޝղ)RAOqg QV:S,P6 eIPJT;M n1<f9}A*x*24aN^kPguH2Wr"tnc^ΊFZF=o'CT۴bO'q9u\0^ǦF=ZSWU%n04 WvbY_p 0E=Ѹ}$uVd'ɗmğr%yUˊx\=Ök]m GSgh3\)iS=mZR-I$͎qWrKU<>cPA:+ fͫx)4R{:[dR!]KMP=1ؗqLд ߷?,7Ř%4\ ̻_FY pG5eTRgS1ZܪC,ѬXsRF$d =|^/ņ6Pp8F_PUWTfoܩvs ԽʺAn` Ii%1^JDRAه Oׅ8d2ZѿJ * NJIRXlyHj_#*L.>u>/I}QN:BA]jC|)w7huuuiJ+ߏJz«+*[杘8{zz*Zri׿dIˀ 1ҽV^ PC)QCOIlz-GFm;. A|Q,= 7?:v.#kq(i⚤0A CKi R 9);h~!Sԙz+W3*\:iiVlL{N%au4˨)ʢ$ |RZHnxk]OoTV4UKf @{[sʧ&3,;Tgً"\޴P%$e6$I_`,UN܏nQQRd]8=-6@EmPJrV"jL;`uXLut 7*O_NgZV9T״-ˡC\R\1$iRmly{-lku,n.Xxi鳌Tr2ڐ])J@*IǞu%x<Scme u6_u² SJѵn_SH*B6(,& cEQ@{}NsM }a&[00E=+U2R V^ژ!8ӋII=(h`wcYpwwhWqO#к겊L!hBOEZɨSL(Qx*nG0e=:y5؍d5 Z:5//34bM4-jZ'JذD66kߚ\JjY_%5Pe*=+ed<&llm8-TƪpORK 3JڕPjKpIQ.4Ci%W4iWuto2*`)IlVcV$)RԥuxAƍe׹>) porl/Sǜz$~NaJvsPS/my54Hp-\_ cSaxtfmy׊0V⸻*e}Op? s]PHXMj<tTRTyiE̫ngfB,PL l-7v*/"Y)´JL@>i9ښڜ )0Δ8M{{j*sֺ{ g5pmm=3+SilJ#;>.Jx" o<+&s8,u5%ܥv5m=>[% sgflN: $xVE >OoﺺrY1# ֦HCM)mH;7e_qx+y9̪ڐ-ƮR &>cŌsDc/}}Iuf,.TiKZ ) 5 R˘TM}D/N )-8KI+Is0ieRaK8њ"]  Y jp-jY ~0_oa3PվTgҖN2QI /&u#M:mT7Õ /<~Ej: P{&1EMI cn~#U]5#U&pBjkS#PllwųOvEM v{LWRRN6%hbu@ms04Fњ~I_(l8~~UQZSn-ԥ]Zm B̩EPĪ.{{\TU34SrtVR`QT[ *i 7ZT&A60JH0l Z 8i>]LcB7 +k@b^*TzG r QN8ާ[aiŤùEtI+B5^t(O;uWA^)Vܜ/NSRtAG1Z[kT3&&,kd!t?_"w.uUl;U3P!ZfMZg_L3b+Nt#OXIvM3lJHq*\ y[°oGPvm0Ӎ"I@`$&v] nvr=^D1&+[yM9*Ai֓[w*k^~D̩{zHAI0$߾!< ΅\kz ٿ.EE T-5 !-K-ZgH&TD Lh[\ ZR5 n8/a\JDZ,/R =׉Sc3_F+\DJ kYq'R^K@w36';>+H Iؑ+>-6Xx/p߶<יNMARWVά-V-)[5.ӏT& l#qbQfu ! ױÀ$+usExlϒ*Yl;꼣Y}#-%5Jh>JCD6P RC85sHqZ-k+_3SvVܺ9˛̫(a1R]:iܬyhl)&qSBi-O\Zf5GouU8q%FER:˔O7MR+n䥽hi%&n8Ӌ~/N#iaےpyAe+C ;IP)Pˌ[5h SN@!HԒ``Y E}Usc~w ;|l)!\ZAJҐf-lt4s}Y5LpA]TSUk\EDuƓc;|'Tsdhʀh-(3p 㘩:r[Ԥ՞/#MBR KG+ ^h*/3aQS`RiKA{c۩O$M14u<O\A|;pPelRSfm,̩$)H;+'QpECXr6tl.`s_JՔZSE>u+{-KQgo"sB<\U$(X8 _OfpiYlA$i_eO[p eH+nĐ*iXGߎ5cq{~X Tƞ3ō5MfeښT,cF~߮u4_>K;Mn]=gT ܫ}w FX>UCzu)nm'%y)j1/a0J(zG6JcYI >)ӹ:3%F@.K%u(*O@"穎1qioBȶ&; b]@euO)>5iPhz>bxvVɨqEĠҰ > I \QXrֻ\9>mI)F lQ" r#%W8a{,.-$R8pT;*׿O5( qP(2RtjaySRAt8jia+mzșTLC4j+쇊gȬ8?*M(1I(uzRx.ܴu.c]E'xLJV SE.F']Si6)MLš!uyO;tSM)|zw^Ɍ>(c =+f?TeO?FCKk˲Q4ʗP2#҃4JSGGSiA^_5'3MhJ\NeXj!N.θSb/z6 6! 偵^}x@`?@bvcPPm(Ja*GHH)뎒f.V񹾝קjt^Ǒ!̳4uMӰy[HtPZl)H:B>#yh=čdpu?.fĻU=&D:;c3Yi3sW**)VlVX!&P?hهy+:zHY\\["s0Y2YD]Zir  R5@ֲL%*6 JgIgi&)HhӲ6r3EĹ f4Ы9v+t5©%@%=@9ADhvZ8w5+Ka)>8Gq.OK@m79) WGymK.0g29aM-b~-Pdu@B|cI'P 4&Ki~rhg7N+ӣ1f!@3p"11LHt*UGKHfwԕ⼕+}@siZ5xwM2mRӈN=ʋ dl=U$,1<'/_/6q k(Pf2S2R* Ve0m-9|no0t]qeTTFb]Ŵvrlˋ37V[ae9v^ (@rH5R "oΎw=?%b$eߒfya^/T- qAK !\R*Q'TIo8ឺSoOH?%X6?YÒN%: &F8,mfmVۗ ]){) =OSRRVCZH:\sWەӪbFbcQ˚L 2\+r =E=-E=g5L.\QVhmG\iR.+v_kpKD7ۘOq%=?EJ˹ECS"Pʔ 5P% tS{b161 !|dPOd/:QŹ"l&`Ѫ*֒ԥ!Zl‚cm䋑u`+ME-f!MiTT@ 6: o2E$ii6[EJSn5Qtsu J #D(dO˱񖝈CA {ݑ湋0]HIG拸APSI7Q! .lm  I RPL Zօ*d2Dt튓Ť&߲ъh7]7ͳf3弊UR(P[vyN>T$x7tmV _G8gUȨf3Xh)L컘EIX. qNN+W>#+_#7x>? !ll"׸OXÈ=&mN8j,jw<yjUU\XLjE3,e+Ld~1 L.uLM-$Jbuç0γmUfo,yUUSP-v}jR\PFPLܜ~â44p5/Uw3[uE ͸CR͡6r`Q[$%$_j*{\-Gꫧ-#澏,2L:)cQQK54y+qʉ*F# =K}_<7ulR7/3Dk~}.-k4HͺCsSnӗPP%S ĭ{gEe\^g[.ۛPGA7`XS{:f .Ih[TutIN Hq@+l{@&#-A[t:ZHc}}]mive4G(q:O3PZz/]{</2*i XBlME[,DH !* xڷHp$mV)q EUCZ@e̯~ѿgm-(PRy+Ҵq,2ڎ >(%C1H7cׯoelamO8k'xOv[S|+LeXISETJo(8)|GAb?EU\;r"UALFOlN-koeӼxxI!=͍+l-9hsrךeri8T<}gleϛn1OTc"lU y 9t$0 vT];* YjZRX GӰ 6*@[TXyZBҕ:R ?Trmc053UdmK_3otrofQ>`~;|vMS"@S4 ֭/r)zvVZ5:s >ֽpI9AeYzJ6jTrY<ӍLR%pb1?ƫKs(hO* 98oU[{D'#gM-VSy cRn`jiMJ2kIe逤v<'5Xhs2e&b؇fe6 Y#C\,,G~e|eIqI u-^ S`57= !^o\.d-˥5< 3$Ocz98&jv.<5N$yB7Rq|P+qI IJMز1&B=OtdM(!\1eTTn*a-㬴RF2'ap.-A7 6  /YGPr.S(E)BT0hԕvYuU;=fJ]kTQZӤrqbٔڶؑ\ۑP٨|:TXvL_ 獺JdnkhYіi2hyLe1I90ЖUe!.AJ,O݋t?He>Y6;MzsSq糟噣9GQWYSԹb2)R]K2RҌ|1iH ۇvi'YoeFvmoD;{=s):YEK jl2?ix׾ 6IϕҒNO]!^Ce*BP] :a)Zέ`ϐ꣖paeص E-I4_oe-Od!jeO54YQ%ClAp$,hpF;%C*aҀ-aZn.hpTJy:M^p9qF$FZxX^>L_ri>[=em"V_*U"jZiToV,Uw.=^',}|<ɼcR >*C]ikRԸ-2sd]Miv帵.G33N;p$S{y6sBFx,QRm!bd_iQW/~wOX]׷gvi++Wx+n4^u_Y+5)!ki`7] F#oa5Cl)OQ毼]`ʓF^VLaK:\i[.ҁ&D ']3k[wV%YFèQus8rGW$ܢ|ymkRT[(J ;S" =.i gvq_EvFW>;o?hKWiCr[R\Zw"G.!  o"%*%2 H I B'B87*#vlΓ9<)Vx;q_ hl lRUƋ3Z ᑅXCþȳn#fF9w&MeBiTf o>D$r1Np;k EmM~X{5(fU6fSPQW9OX)l%RJ61?UIQP]3dq`.e_9BJ|-Ջ,MXyJr:ET*4S4U=z9EզFa @cY r`ul;8dvcu?E?\+K-Vs8WU+>F^uZH< 9ѴĞZ R몃V6y%zM.|&rRe-QHo!_E";hm*B'MC߉X̵EŠNͯ3kK &"rx\=iبv֑V?;r{/6 0"1tKYF_%^xQU]zpsJ(‰ 9X{C(uyh H^;PxVtR*$`p>9(B2;li:iO! KA;,DrV:M)ԥ5H: aw !?NQD [;fgGh^3;#N *nOelJi YlUЫp_@&Mf4V^ja $68SN×䴩cku5 >׭ZZ\BPq*JB pGR X.nVh9m-9Uԭ+}RSTJw8xh#DPJ*ŐoCHzɔw |6P[_3!ҝV 0̲;P(!ÊJ$A*дk1,;WNԨHji+eB)m H(" ts1,M]|Iܑ5eZ]ykBRj\Y[Ш)rDZmж*\9:]<U=a. *a05Z!h2[EwRͽVR)@ؙuN=Xl,Ns'SPAqJ,$_ӷ3/GǴJ;gYu k̿զ+m-"A69% okzƋs{vNO[1VdN MӬ)BZBS+S 3D<Ùw QPoIt,xcM׺/i5Yu5u_X(ZgjO}7f_Y Nd:ed@o˰S1R6\?4j^ͲS:*R^AJ JV"lt8>l*&zWiFʥHȡJT9c\RJd=U"܊4֠84@J1Ȟx$ Tx'u0BCZu(%IJ2'LHqw*]L^JISc؍ߜh=m( )ɺ#T@˚7EU<閈 ]2S)u''V2sZ3 k*W~jU_MQE\ÁZ~]C+ ,$2(nF)|; *êc -u%I߳33<-yj[fJ]v%F+0Ptv+E'G&fKj:wUpz4$hP'b m,5˙t2貛^_#@B@hh[ Y~FY6?qB. ă@!hܑ-(dXj[Q٣U[SNCnqjԴM\ g5=@[Sa,B[9jJMu=KSV 4o-8,I2Ã.|աLJI$s^Õ\[)Z+3ZUqt*)٭vsWZ5-j$<YV_H^H٦>0h\(s}9[euΥ 7QER)]@ y:e'Qxu"JiT>1aÝ@#uN2:tq&e@W{t,{aX*CO:Kt7T ْ?OG m@k/0V=yk-\c@(ShXL)&(I ~ge-p_}Ψri)-e(JJLtjQ8Aݭ'3=++L2߼! (WZlLB;):>.nJR)n[Ec{FPO "\ܿ/b3b~P/R86eWʸu0P IڢidHy[Z#)y)GIӒ1w:_|c.Kq=X)a!ĸyHUqWx$ ܣ]\fV_-zsrHb)v _%zCNhr⩔AM:,fCGFL&$o*QU•-$,$$d덈؝[uYC \P+JaJ&@  N45-;5;^ڵHe;ge֪PIY -JZ,A[j:yxϙ㗾т. pӖ.HI`-iJ)+Z ֝P@kuj,|g6#1KcKٟq=[ d]}5]^\eeQMXƞ1~ S TLDn!:A;\9r+8*+[Q >xc*kO* 3. >2UJx Zi TkH%[&*^eI [\[6˨6ۺ>I ?s2GU|wKCX*e춭hAE2NIRH x牪1 @C˹h6V?l&7SKdY2qМˇ8K>4%LӶ U[1d+bJ-S%?\8s*83#%-m "5v(З^1pd'䚫 tuZrm$d:GE {5_{dA:Un^Ki4\k%DiM8*q0/:|5-,+.c_Mpzh2B9Bǽ!Mv-JYFi#/D-̰r{Jw$gdy:WQt$iS4I㪊yx-ײoUͳ9l4U-;TV_Qq"^: i!bVPd$p5%oe|/Xl#!Euk|Ԋu)ڀR8qy,:mq8bXs پ}R++ gny3G+ۙcEP<$ > 55:;RN'ݘH\O!J$Ao/V]Rp6vNfe6ViJ(6TBmm:JvfnmR1wqUP(ʪVrA!"Ga&LKMs~]e>4ͳ#[[]yni̱^Sk3QHi %Sd |j\rIvC o mH ,WqUF~ƛ5my}uka(;_j21.)K8#[dɄ3gKU)8^u_O2*!}Z*Tײ#[=M>X@Ouzn: WZ @s|U 3w$ʨdԨLoYKOe3#1M(QU؄ #so]VגJ?Uif?URXqR5U FW)$a|>B L(ZKb*VT Z &$Zʇ9)i"teu&U;:JZ bpsR{b1 zA)U ?6 H܍N-4fd6z\̭\Iq$NW&A "H` cn2jjT6a rϖO躔c5H)qBNԏ#pb. 5CUL ];y)XB )%dәo%z8~Ч<ƫ1.zԲKLIZM 8hɷEBtZv *e-ENJhZDf ǘYrA1#{v8.OK++=-+KHRPmS'*&pi-"n[E{4nR R .`oO4SeO%<=.b*8̛(.ׅ8Ac^Ls3Q Z={~ke:s̲~KUJ_9UB)ۨiMW>PBRDN؞ r.JղJ}.Sp]3G+.'@(i°ӴĠ4ܩq!fFi,%i}9-[.%]83,:q矨[+mןYqջ:KRHXu5%(S`?EXQjI\|3fϴJ Zg !AĴR m3c8e[T*x1E/Ado.+)ES j> $4LY06: hiY,'EƋNNBCk(uRԛ(tPTF:X#$sqFdstQ ܧ%* u@ҝpH9eBXAYecCo@j )\JUb\ɃqR[ȫUM5:nжХT0Aj݋z)prhafefK5}oB!-ISmS A3U*fY諎W%KRA_d &N.8 ]"&*ZޥL\y`q2?E]ԶBSm!,-NLj1Ri+S|Zc*\})($j]0-jBd+,>h؏Ol=)z_'0Pnt#b-dGuF#GqrBF*V+BR'G4ʊ;a/-{ *I[Px$ ֹ,.UBebx'Pt6!w-: javh۠=U9hDvSIW Ķ|SѶԤRRA'xCdƑ@kxi=,Ȉh#3EBiLa #N4N0f8˜owM<4C2 *3G-c0q/SrYWJDffAV ID"3c:X ._{E>&ʭf sE2YE"^Vf&]Jv~?\fJ8o ׇjx=@55 RJ}MEn{V=PCƪ3rNZAS5Jd"1}nRrX.t35.؎!ኺ^gKɩC()I_-BT`LgWx37 ~V uQᠴΨ꽯Qfՙ5:3'\4ŧ-4dL_lVҢż;7kT(s8^S_G]N6Pk)k->Qż2XؽCOVa?-U*N=;8o7 }ZQ]A-h[2 c1ָ<El.pqssk\oo4)Qm~+_z0[eDhH怔t{cV5k''fK _/_U{C*^ꪕRꡊʌŸtuT,;OuMt ֠ik(/aN 'O$<&*rHͲ*k:H 5!\JJdcE E׌;>dip+쏎g444>)\aF))MCtJ eI@!39t,!Аo l3<5QsΧ&/YGq"*2-S]Yޕ*ϻSPԜךs 7:\fTCxbtP󸃠b?UlATǍ0 [.i_fA9 R9˫3侓fniC\3,CkKH@0'qk.ƒl.>f#ӧ5 ?8L3is2)iCCmie| q"ATajKo秦*I'?PP*YqmF*hML6T{,SEO?&hi##:S|3#$/Cp7G8S*_4Ŋښi)Iie*Ю' uTmr.6HMZ_{ ˇh3*:F뗟*Ӛ2\B] U]'N:QqF'i߿u4&˒L~˕ԻtZR{YJ$!Jb@o@xWᤑӰHq~nq3D*W !-oBRwpiy-UB^$;KJ5T;+% l[Up Z`Qj6>c*03ʔBLbF؟vQg-VT\mhK椏e@amň5UcVdk\i$[mU$ ܘ 3AI6ND|XWQm#[pxC"'BOBNH3ml%6楙JT}P x].keVt1Β6KbH>w$ep%loPZJq5yI C N> ~P-[bL´)2KH&$ yݿd}JM=9 Jԭf>ø߼Zq:G"#\+]&nm =TT3م֋YmKh|X8Rn`.m^q*=/낰U==*AN~;zHuYMPTiK4 M#[!" ' D rHMTx8b5N :1z^RL2b#Ln{|JI y=ћ:Sqꍒ[nU,S҄^I < р~R2#ddNTKi.Q 4kD2/JO.T@mlZa+0қ+{7P*/JZmGG*X;6S+@*u*} [L()&F/y7׹ʭeiR %2cdhН,W]e[t -HS ScByCϬ~c`-r0pd9)MԗE M&$:ii^CkIjwVوNs{>Z+-?ZpPAH/ØKWsꊬۯiׂ”HJ*%m|GvB..p-p"Eu]njCPS1@n%(c2$xobWIv|'+sNR9ES+,KPh\u?RQ?;t$~ѫf 8s9a.9P[T*C)^y|R_U(gFWn]:*’Jm)I60vyNna Ӈ '+ꚅ1AISXPCpbPT7V0Uo? VO幞PVWf,+CUM;NIK A`-v<\WMREbQT{ io!)k $[Qs&zICK*TnWR (SũjX)g)Mj *o<ň[^v̏=<շ(e,Τ 7Dg*J̜GQ҃hÈWNd~XCθSN壔%nZ*X*Tq<t*>lVKf~KùϨԷйVz4ˋyIeB>(1*uړe θI"_>jӪrKn %vJc/I .it/eAecugdȳO T6+zF_rT馫Bc)q+YAqZ%v@5^ E< |l"s=_} :(%h*Pq*aA9~gVdղu$0pwW^}H|*){-5C1QRIb d=Z}k1RBH-.i7Ӻ]Z@ŭe|yS.#.}ܖh)T>-r[ZTrAdp\z,zW-{9|;/k&D[v%{GsʑUm'TXk ʹmI*^Of3St\ߖt67Ą Gq?^艩Uhau:uBH-a>${h5U_0Qu¯:Z"a湛eT= m\^MQ~eCq8 yfGJ]j4VI-a@ yƳ\[m;,~O4s:}dᯆrn 3FJv2fiR٨)ڪw?)WPʛKPӨ ?#\=aKZP LJZ ά, Xi#aISJ^Z҇5i Bu,*HP7Sx|8ט+ۍ@G@IPY״ ,S7beRv)ӨVʷiG &: "*Zs%CFֹW_KEoi!KKÕy;*ur_K:U[/R4U|s@at'n!?MVjF[NR-eO<񤪩t*pIR1{ E.FR2Z'Un5UFR~YXTb*LA-,|i~ZװZٚsxgy7:7$GUN UWgÈ3ҝј;zPSHQTԥaǰ3G)kC]`h9 uV6q8$u ubӞdj=?^qOYRCIrtm&9rX5 q>_G\Gu/e^rLzy7qTqO ZySe(CWS*mNV5Z4T$D ::zh0h4\V&jjh&Y1șz\l 6z{_N]>kk(tН_AT?[QPfˁ UrKqoVB\J<%nIHۮC3Z-,s U/ŷUU^OF9f~yFN(y|65J1#*=8igO). [U)YmnF厅22+aeu)Ĵ#j3_5˒pI3*VoDJ4cRYBTGy /2ފF˚I:p$^e "Ϳ@\q3:;)tHRTieBDy`5`ꏄ!73OlMtf:G dߡlLL9ʣe'=ZT)+/aӼ^Jy[COJ|{I`wL@椣Z8Z)gtjKJ3=& F!qLHt+(YF7Ru RA=-G~wt}8ujUg|eH]s1:<) V .ˡWZKR?ě%Sk\MmEWӠQ"=3Dv*m4͔ (:HOu6TS 1b#lDۖIHȞBLou#L()DA : 9 {0sC5RW"b}y3v*i C\$kKG*NoYam£RH 6GtBR,2֡O'Hikn9+TJxS7>R1Q᪗ P@[tV5ե+Zu) >+"g0 \DhA)%j HBm $E0$,I+쐖KIuEM4L xv [U)2ˍPpTp lZ4[P )JYL* _v( .-06QQ+)–VJȴx?~ ˣF`TeA *n{R #SMiIУ}ɱaY'Zâ%ЕI78ϧK̽խCN ߡ+[v.:JU me&6mEhkk-:zG0}z&tiЖ[lN%0TSM ҽќ̬_AGoj+UK24o--(q"iy\181|'u&ݗqb0CLusvNHgm=$QcT*NaVjږ.,{ z|嚆8w8o*ː^yͧ0eOKpêQmG69_l7p<:v__(j;l[M9}&o3TfxUCNηj_. %2<ph=)oٶIR6srGzD!T*9m+Z@XVшTbu7;Ƒ7 |sΨsokmm4Pfs+*.nd,7RQT`|8G?y};DeV Nk0l1o]OG9”uIVEdHcĸ5#_76g:}G>4:4%)!Zt LUNѐvPY妠B[,⚧e3BbATLaZ_>|Q y<=\kO>uA[y554mM+M\l@ߝ->)W$jںow"phb@uV\'E@f"Ri.N\]T6W\!*-xUP34qߧeGT `?^=q\Qʩte}@Ԇu%%nTN;n ho;x7t뗆*TVS'X=u ck.;!#PuH]l7b*"o.hhu SV)E)POU YǸ䪾[lyRgR7HN.E{\,ʜV8Am{}5s¹J:%Œ P@鎆,>(Yv_u-cfgֆU$%j1a4ߕ؄ݨ| )H7;alA+:G:Gf))/O`H(cgk\Mb)5/evlm >Se⟜,|o/QU}JҴ0 $wA2^ UJ9}2,u5YvNf"3 ^ iPs~wl/7uTt%ƶ~W3! hqR'Jd㫦9pI.q1pce|AUa硆}ڟwrEaBShhZf WqLni؂tX:8CC\9je?Sfcu-3۪* +o?P2 &@PlSSS<9[5m]MKbBce'qUnq2u#&aڊY=%4SMSo1’< IlCmDRY+q'Q9:˲C`Tpcٝ-&YJ2jJjzj_ׁ/6?8mE&y6宻o]oZ at`D9]Tw34̚ɪЦ::)kT6uOPԵH^hV⍡⣇jtǢϩƏg-C?!93̤ zZTQӠS!a! a*HyĆpNuXp):&eJ2s*󪶛_qiq(iqB\m5b)qoL!(N!u [KQ+mn.^Z[OP'&BE EF=*%m()hBR0RIa/6.h,s}C~j**A ߨ6ąLgr-+2  t1lKSKIrtP\)YBԅcK l_2M"f9u:G&$'+")?XnEoC`Nk|3u41^κTnT)""feCc$[TH]V-to'ZE:t'4M@օ2JPJ"$t1p%V09N#i巹OPH[3,E ۮS] (ACSISKe05X bF;nZB}:tITI$oRR-% B֞H3y33X\\H:)\do-.%)T-r6ψΈrzTԤBH$tÊ[a`N}#偾pȴҨaz`~`wS໲9N 3;$։BK5*RLOc#u&u1D@|=f5RzHFBTI~os(ky)6’R\Ęv1F]Ֆt)w|)'>x6]`ͭzVGDj ty%hj\ܹuRq@)"aE)%]`I㵔~II "=F)XtD'@d~#-njm-dc(uE){|~HŇ(h՛.a+ yˋ m˞G9Wፄ9b6Ԏ~[H)+J cleλoZ(&?\QUYW>hEZB5RӮjq-ce.{Fbw=}EMat\()D8H&SD"E}q3{PܩV#J0ԭIQCcJU YXA% D.?,V.хfόM;;w* b )n,/>!OscIl9hTfY5m,!o"YR/I>!MMJڠddKSl6^Oc;Rs /z|.dRSVԴu*CtjaE-%)QLR'x}/vڜ%[Fث ={6m.s,(j-}݇L2V֢v<0f* h{t%xۆg*-V]9 ( S.ӯR]OEFKm%c8\,rz5Tj}uku颦fO3YLƈFY؜ss#wKBPM=yV-bRXKkIp94$4&F2$`5e߯me_%/suV*WT?^yifU% iԀmƸג=D?䣩];$<9᪯@sZU+S)8Τ5'T bF.M*WԴLUR6lZ* h5 =+/#/poK5XHPRS溥Γs844me裄 Uڞ\(\yw {제uM =:IGluʭ.g[[ P;:ٛwU߄Vy@m .AtI6$ =coAq)ڒJ[/0ZI(@(P$DLAinm!"n5EuSm0CIJ[Bd"IzX9g(k6y.UiqKb%J2[ $'axXOQUw`74fPy|mA9:m`ƒA6PKq/~Yީ?st:(:N5\]XԔhH+>kUdDcsbhsƩ˪![-p)*S+ ,1ZH':pfE[2녏" mK hA[b>c\E*̸=WeU]_g>OGb HRU50a ېy4\ۯ!OG5Ɗ+d l5.~ʏ-.m)EFE%fk$Kv) פ>f<>+\;moU :|O`m UXMM?Lר1~fX^`9TҜYSk:QРR"1L1,Mqw:"swEŞڏWM+!MuM{a* %ZuZp3h*FQ#|˖_ot0xBlε7s24`FY@`MU5ZGIXbƔ˔hWQ:*.$faW2i,jE.|¬qmc&Cqlf54m;KpuO,I)Bu uU5q$`ѩ-Jj,;Soe{(ֆ-}iدiĴGUF耮jkAg\WljP.({}@1 -rM/oV*JJHT/q2mN),aǷIM]K< ־MI~ iqmOGU[QT!;%c5Yzrc{Y-WIaOQSf/UUJu) N:TGCXI 'F i^C #`#h7$NU*u,r~6̜Z\PS襬ZTj 77h*` hG~~(ն9ÿ=8eYZN4RjZ)͐X*cncXx9qc?Uxɸ1fjx4unNkrb;('⏈T&=1OX4cBvjR,l$Ě7}㨜Q +=7PtGTD+Hsy/iZQbkPq2iZuiH)&|D>L5>Mxe=VzlɗT\J*: lC{*j:tz\ykq7ݿU( Sm$ Z#~pRwA%^ Sd}ћLT[M$iZNاyp̧o $؂?pK;(=ԊK@Ԑ RI=D![\2}; ǟ#1u8"R~|ُRߪe:bH3i7;&8QtFAAI!u iJ+JRu 7!H5`Ti I&D\IV5;#htGF+rS` ;쑆uFqpxf۩Vbۤ{K*`Y+ $큇Wc`d]=F:ŇS' y tx G `EÇrEզFGH%57bTfǗ`BԒ & C Y]ЩFRTԐN $'WhFNJq5H)Dhߨ߮*J[qtKCTTAPl&g&qL.xqױRm >@|'q*uEAaJGLҞic.rȱ&5VY{ @w3zLږPMbzeaF-OKKM7(To-ɸ][)B #É:(-6?ERt%#r#ϤPz"*)$IQPOG0oDK4R a?ӝzuèH4$$$8Aź/a;$ m"Яj"[W Bc"qb:J(w(*hWqg=T1(tD A=q o+lHmjr$J ̉]l 53-]: ȼƌQ7Ty'VWKLyh* q{ :kZ40)52A `W*6^])ז! (uK6ROQQϒ^VTaמ)RےO[&:=>jiX,Fczn~K/CT9QsARV*Rn ө ebH"vPFo}W=9>oZ|2yh(B@Jä`T+#rn$|2 40y '{s,Ӈ:vs:-**i[)29P^B$j3WQAOVV˟3twkmn2,I')9rQqCSQJ2=f|I[HƇ&IiNu-R湻~$U1yaq:V s @]QURX]Kg-md ݽ%528:l#賗nTDkXHnJXh,jMTaR~]9lC9ypURL#(/T~]WgN=Hӗelfn7ڢ\*m0 ;.rjÔ,ؿt/KyW_Œp/fKNgJ:F&`5&{ͱ/̓~adn?WVNjY -鄀8Ѻ0L(ZrJӗr[yIԵ%UM6 ( %MQeyNeMR+hi:Ε)BґT0VuѺ'9fE YR@0qh׊ . r*ASl). NI%P )30FtḺܽ!ϫRB'j+)(T!n+.aRssZdE1F%SX6LFa| PuK}EjОRʐ_WSi LǢlVo_F1Բ#lWN47V.'lډ ~G;hVv9oUmBX#] Uk2uynjA mdRLОynYp@H3I xU%Cp! *]xQ U8iQs;) EF4ZK6JVA3j)("F5i3~o U#C}CpfFAV:DpBmjI*u8tQ YA@6aǪ\#'VLI#ϥ za'd66& zasVԏs{-hZT1^F"VT$)AO8e*\#[R爺K]Ydd 7Ju3h1B~j[6k)ңo [*#hRPĔQ@-u"ە Biǖ]6oaIV[뵈lUianL{``E!ж!e107 gK6/RJNO=WUG6+l(8l?muV).4$ᒙ1p?7ER6†c>YzR*A SU;nĖ[ E7B1vI7Tjb&mQNH ejpokN [)H$n炉ta7)A 2AQWRqd=Xj& 򌼒kl*:l)Ue-d&K||Z =cKꄀA!$ZAe Ł)Bg|/3ءIk^w-hؘ:n76feTU">> Y4Uk^HQ,_c+.hN B!xbmVl #5 [dJG'쬿uUW f@[Ba3½qr) "dZ*GSI>$c I,1HumJ 4PwI JPL1Dv6Rsn0n 40$,ܠ#.K[39{ZBMNxLָfe1h3h|Gz,V)O..zsG[+I TN3W 7UTx&]\e`nQpڪkZh梙u,U$m^C6*jЕ%E6.ȩu浸C눅s~ZVZ3Ԝ//y晋,åVUWPtB_U8I% )HD*H*L%7)QhE ԺYUT$ ;)򭦑)^7T$:wkH[IJ M_Q&h(d^vb|Q?/ Q@ G\{5ЇZ.B$)7ͤJQ{Z3$HՂoPi6C+X}SdqD24 <,HI+I5ckA iؖGƲ_xB*LV+l\@p8%vaq}mfZ=yF^.cWG+*sfhCJBqե%CJxq0ssEú oGuSlp y`o /3C7QdTIuU;*ZJ=ԴR P:\T4u9p;]պIqoQ3Ͳl0j["n56ۭ-8yc 3cx|9-VE&暇$jvI!*"KH|rq(1 "R~9#|?pfOquXV ")k^N&oMƊ x]T+wN$X &H`< U*K4ۺRzmba*G*v%i #aҊ+ Aב+|b-vVM4C**FקlC[]Q#TZP$ mucX8D 9@rt Utɵ|?REYQ}B6MUAⲇpy(OBN+adA t[(d :]@< .y+a"H_,8hء> hRQ *R  *X6(^@[`^ې\N A 0I0qI0m2%;@Ta1-Z.zX0H Ym)?6He[1vxJ,=百xZuࢇ*Je>$)Z6pEQ#ѸTØRSxJujR1*%3F`#]fK =jmWe/2hD6(VLykiR,!huDlF8ȨLSi|oރ>"ݬLb@|( Fm2?V+h2BߦaIh4r = 5dBfŠ4]2Ndzxˍ]/U9fXQR)Ʈt4$šPͫC_-S:paPPL&c,?]L߳ (Zjm5GSYVIK3vb^(iNZE?c4xgm xnGFȄM#n}Oc~xvMyX5NZdu%E(uU^Jk^x`Ⱦ6YOR#w=OÆE%a٤輋IyJ^SJr (U(KR*O%6iIQK y%e|  OH?=Olӽf|+u9}g-5UR45LtB|O)4RPDő.M8+j_~I+4 kW #sJ|=ᬈ(vZL«l-qMqdd^]Jchs-qTT_S%OFbbiSJֻ,>②,6m N:8蟇FP\FH:JORu{aʷ}J($&uN"z aqD(l`,Hn7 *mR8-mX_CqL@JOM7G'=Q|z }0RhBr)g؏ d旕(hDl&l7hPR?&z+Tjh2t`^dNI&( i'k>fF yGJZzXAQJN*o)Rޡy u"y!!_R~!5E{+2~[֭\RÏ&M;FB$:|)t],I<aU0+0"s,! i5jJa ,'J0;^^<}1lb:-dwJs hK5.7I$ۨ^=zܥU fqqk(n ̪j"*qJRʜ$^q%j7&I;&P,Q849GTGqk.~IL2STڌ]iS 0a[z\6Ӳk{+&]-o"ũ)2gUK25ɟT~1 -_/'=NNO')YEGG[uլPy󬬶q(Hg *19NX؂}|!f5#1}JUkYKT 7JYp[ yĬs(.,&sL Ac;uJ!+u%)$P |LjPYTG{J=Iþ36WYE+rP*upi?MgaT'e^)U-q}Esi EStmU:45Lj@`7V3w4uTۚa'K CY ]s/5iy}je]RrL{O-LтPNyC2PH&| c\mZ+ZuYaEWI t8@mqf5+vPٝovj!>\#bVrSMj3D!BTw !e a~s`ї "ECDT%IIA!$*`u`0P )UU|i:ޖj!<SP#Raw;M6Mƺ">f0?**5}@h:"Ę2o=Cg(see:RH?fNFF589sdOAc/p ZʦBJ4da7'e [by) z9~'PPblN;*ynZBBivOC"qCWeU^qZi)@LI9 lDvcZl8KH0TImJhTcH$99\7K˓'!ג>Ӫ4l"Y3Q˓֒5o?S4r'Ŭ(:6hJTϮ5Vư:P ƒA뱹䌣+]}?PYRGUnr%v>Ț!]# e[LQoi JPVcV\=v`kKSw*rOA|Hi"GKꯙ\E8uqBIqƚm:$.ATl2|1}K%>J\ʧ=TycP vqV?E P値BjL=D" Z4L.DERψ(K Rfo%WMj'l'TxéR'U}LoJ0RD*d(iN8=Iq{}n+Id3>lĢI߲[th0 u 6Qvbc7  #I?d=tQ):N$i1.(*P$ds\S*!%DŦ&o RlH)Ԡ@܀hP5(TxGM:dpIHRdHpf2,QĤ%Q.Y`RJvR‘ERRPT%2o"lWď'>ȖTA cuK:'j0 *69qRla VH#;\#̘&WlU]Xi(PR@0A+aXvV3J%8mKBTSqfV6CM?j\O(@1$UDYw;};+;w4"U:jVIPÉJom0{LG-w H}F}NR6 ;`ַqT ¤GD <*xyQu Zn%Xb6Bѩjp#jP^ "9!MŪ7r.XeuC),+Bm'OA8fW* 'rPw=mqQPlMM m!(' $tyC.~JWNC층n&1w#k/͞h;-G )'̃ܿӑw3;X ?CiIyJө$j ":`c:{P[`;>$$j#N0YT8hZQ&0uS$+7w~(t.eJ*jGFvOeQj,pizЃlW0GpnT `Tr^E:䇌FhTEZF\8`Cqr TT3jJ P%7qgqdxX-?pd˕HF[U[4-,Wu!mE#T$@#8sr4 xbynf Vuُ y WÉ>eXhm曣SfVԥR0* y.FڍV!amhS> 7,(St5 Y'ӺZxmT%jZ뎗` 7qoPE3b<7ENӏ\>Yrf9%e SjFԥU zPa- ixs8H.LITՙl J*kBS"RH &q8ez5w˔gO=Q[W]d>߼r0`6*\+i P!e7ulwM~ON5 eFPC^YG)J-R$okES2;[k>#hYP9eWFa]UNUVf)mT.4JAV%q '( `#TT>Sy'hr|/]E;UmgٕJ*N4ZU75N|oif*O{W)jdB󧔆ۥm갵) ԟ2Z'jX>Ἑ&eVP3YW :yթu,p4 m #YJCP̈́*~Ho9~#$4lu{,NcYd9UNh-CMۿeFlI@0 `Jw7|%\ wD*CKU%a 8.,JR֐ņH̃^JN7yo[T&uF bFh*~ST,–#m%cd%QMDcVQ_L%ENLE"UnA#O^y,mɲTL!%:Dw-IkKkY3͂IqF$J#?-$ˑ}aNAqB"6 /0yFt7*@L=$R#eAA}%jHH" : ibFuL@mEPJwOR~o3Vb؁0cvB`OHosņzEξɼ(B 6 q' kaVTĉN~7tL?P:s < yD|ucrR&a6 ]V 66Rfa1⁤" Ck6n|K0~~q;'lzf)Tx T'P|3Щ3 _qqd\Rg1J\a0s 3)=KfjT>Ip}6f&'H=Fs˔XnE=o4ۙQSk+M9հ2P)=.n'9|RmfEA/X+P;o8߈)6sl9շD&0봹lnO|2\6ZXP1b:Lc7eF9 OEDI-jEVI=w}S)s))&:\)R(µЯpp3{iY *(kd\+={}pR[@oa"Hza.IKܐຒI`#MЦTtRH& `wu(^@E+Rj@)tBLU13$fP߅: q-h%JZz* ԧtndI$kXQLrh,Ä()s )k.`U,}RF 2eaBy –r*R~:\Ur/ aSęKf9]Za㦩yCl PEN2> Nb W"©f4-1w3@=d9)P6jSR e P0xY1H0hoe1ƿɪ[Ju@HsRkPaXV,NOQp Uy'>/Ŕx ;ENVκ:tUHʆ] 05TH^nOUSCxX{]2߇=<ũL7JH^cU iAAhJ+yJ/< sCQpmEg NKa.Ti3]y[<`¼|M!ۭZ_ <]g (y׵Z8r&}E MqChIq) 2oK-᧶Ǫ|TM!^^GմE(%ULI"N͗Vׁu?Biq![}Jp)Nv ĺTi^JDbjx +.Ni-)lkG`(oˊGhzb/>\D]anGبjdU&LmunkJ*RJ#JSLTZ@8LJd"&<\}Rɳ p#, N+Q&:MRIoUrG1ӹ1B"J_-uL!ѪBHO7P&:`ZT?*!my! ԏ>;u[u( Q_=`Nr:A eӻ\2lʖeՒIR@)&(01&bov50bh^RiMSTQRu:ۢ^JT&mĞ[nK:J:FzSRJL8uEltq!Tv 4Qu Ֆy*JS W,"u*( q ^M*^K) RTrRB ncv$֛ e^m!jzTToePU#픧C:) BT$@)ۄXbܕ'(+Zw)ZBi].7GBSkA*^[$+ega NDn/^Y5o%ܡT殛3a(h=¹rIu:=qM: 6yf{)ZeKT.XiGVHh-$6"]q'iv\h_8%ff?JUQdJ)ߨsL3$->K/$ţ8gk `W{s3.&X㔷;*` OWsJRqXFyhIx%D$N \߆,@ . SfQCS8=gNdQUV^AZJJq *nlqC Bf 3ϭSpunfKlemgY]U9Pl;LsT($T,Hti_c‘~>6iy$V ^iN)֢1 *UC1]2׳jZn=U_ aR},,˒͆D|T u'ԮjUW_Y}sEE6ykPncJ(өK3"qYb$!a *Naetrd{ QSix Zu  7䊄6+ F/p)ǃ PkbcY.XQԢђ6X pqtP΅I1UiJI⁐%"ޘ_/=qa)vr:N<ѐ%!"|o7TCF7T@ːPR@ХK[( !P\b ,\X=az[V [A]_1'Tʤ%A ؏ayMtGZZNwAU_!CPIQ*:F} ]O¥(|Tk+#ILu>Uy(Ղ&-3c<3z&Bz(T*n3D\y$p .p[%]$QbFm =ܧB#￞͎-۞E?Qca`z`ՃtwзERɄ)XClO~A_+Y*HQ%dƢ,Ly^ RMT,bf> 2(rqe.b 0ϒHyIH0w!OG$S,$H' 2+H8 ܓiPzI!G_e,zO wWId%iH̕*Pܛz;E/-I d `fm7 7u Ѵ+G_.;&e06#_aqY#Μ׮G@I;`o# vܔRI+1}Au\u j%("[_+ةJl$EgZvS-WըGHb]?Uu YI5(T;'|XqpH m|n!LoH5Z$A#]Ԋ* e t?/3k)$$i0#xP AH\BiłIPV߭5 R ո"P~#ytDjHI"`AMSZ%i'UB@=d^/uPmwE5[P*n ) 3rE^RR˕nKvFT#).ZasZ3ƀ, B[n렺( Fn@攢u[ĦlJVtqX'U CEõ9E1()\֨ՊB|)SF烛ZH?ԃ=UQ $,6-2î+xY&L>WsU'=Y|:ߗejxhie1)S뮶ZJ@p/U%>Aeˆ뢥u[sW6Pb39ȒqR6ُ ql}%_iNi4Ք(j[l9@IJdWYURG2ING<*s nƲ zM0l4HH$}1A|g%6Q@+#;^θ59QSR&+ R %+mdRI$UA^ap?o8ZXBc >#`&9]\fIJ\u2e@'Ri>3!m"˜Ű>.ml@.%_Ε5m[Y0t7ד) cnF`6K5NS:o.TV3A*)(8u&4 =q+a hxԒ1A!@7=5r%wge/ *N  8. N^4hZKOy5y%tI 5B ^Xku^~#QN[W`!rmu dz>5ZTu,AmS\q Tc^-ESCD]y 2%54BԓC`7X)!68]~Zi= ›(5O8_mK$TuȘҲSkbr내(YpŞ9tPn2UYT$PP.Jy(*&b|zGO,@%]~bR}n7J@),$j4Tj ;I*5vJynAJA敨3me&іߩU,0.J^rޗeրJx/AR uVZg6nU4 M8JRH܃*f9aKU2ӺiZ>@mLRAwӂG('}8:4aVky).ӶeHpQS.鈵]T_Qbϳ 5U>oKrԌh{3miuHB%dűO3CGK,|͵ybjg-iuSҥ,(Z([D2 ''{\[joUlӎ!r/3:%TImCiynumj 38ӂJ|TkjyJ])xnR(鰇V"%fю:2@U_ KbcnI8ҙf|+Zi mVReSL4:`닖 mc̿Epim=Dž { hVl% iڧT"$Ԗ&˪8;̗QytSA&KM bO/ë`6-Vm0}Vgb 誥f.?m:LJUmdL4nT_Y6GSVZ-{-иؼP7T65:FJrH+2MNjJuk"hqq&dU696jhΥt=u]DĿ{ PWgRoاePt))IQf B?T'sbm@pU?,PN*&/rOsm?ps9lŠs-0De1  8H$[Ez MӦ68<_.*\:N4Req A3+*w K2F/ˢw6T^>cIUǺw5Up1 m8k3 QfeI;)CPQ84O{H 1 `^;Xz3); |!&vYN;(G:Dشg<(M[v# Zúg)%.Ubbt1ܷR!ĩN P~ i$Ru1hu8"DH!F@dw$B6*P)==L|m tG#WiA'EqHqTHa6t*E"DI H'{p7 iŵԫ\D- w%Q=L_%"6~!fKHw?dHbrEE,|ذQm7a\JqcH IxFۨ8BR#pU䑊s4mdvDjeR -qUm":SVHmc;d: Lm\2yм k4o@(qHH$|W㳿J:9I|x.ܓYe SĎPu"Wk1ř9YR' {JWnIl>

    sp@CtQN%D&Omg1ۙQȹ܅wEoX>s<@hf3 Ob@KFuNjJP2I>ilvQgVA{1`o2@lO1Js6%N<>! ' y^&H _ hƲH)/v gR:I#h6GRU G8 uhK/ NR)ܧ_A=“I//m\E=2a'LOkMR77!}؃xB/<oТ[VVEzP!!fmRMjD  5_f^oe!O |=$g8Q%A7m;#Z<Ҙ*#~ecꤚE!i736NA#p4̥Sj:?̅t'{MF9& nOM^H^JnU&"I!DzdBYuZ=Xk{q3ʀ 01)I?#\2RmRJVf{u?Z)6)Vx޼8!)!:(^ZEZm[)ԃ\s^:/$s~jQJ # g_`K ng`oJY N$$g66Xq0曕*kTGB@Ps:H#]r>RهEJGU#oJ>u܅n*noچDXJ &~L(,O`^Re+/|3&(P}ȕ,AUvل+>OVvЫ5eGM%M+l[jq: Y*9Y>_X"Z I>0BFlq){]wZ@F83LJs; |_)]uZ@[r'J,*Ry}$J#(N%EMM0paVjShqMx)҅*JaoΗ @23ȭvRHW| Kq -a+PL +ffk\+]\l$u]y6Y⾀䵡$r^h3JOAJL-m[^HтW8%/j $t\9I VM]3Q a#bx\gK6nTuV@jI=z؁$XfnV |_!V U Z Lxkw=\n%-9KWSP@3/}6{q\&$FÒ.א8[1i$HUHۛo Z6]W/ұT1RRY\XI%K+&F7 E 泛9V<*A@[1UE֞ qmlOB?*k^T v_qŷA$ߧ^gעlsEW0j  ޿~2dM-T\ƳRPye(AJuif+Z!%) k1 t\&gnʧVҕI~[ 1c9Ϥ9"π$jTwƄu\Y0^ Xi !&dinE\|UjĉԹR!PGAyHA꜔weShjhw-k:U ֞R w',^}Y9Y.)0uOi宫:Z=y*^xЀ&T3 O65BZkme\ dBZP$bmq7:ZVzfZO0q'&DƔlY0eW75 IgPL @Ŧ<|\2Ew=+qOP'CIh&vrϙ TCR/|[cCVO `@fTbO{ hs6U'6[j^E,>2i#8UqeW*TT G\Z>J Q.D!VDZ5Z@QulGlumy'ŀJiU w`*@o{]O):H!$xVS{-QgJo$'3XH F% asҗ |SoIv*x:E9 킵>k[E*Yµdb!$(sQo4BLtp=)BTS Oq|L4B-eQmew! hd>qPtE}NqR0Ce>#c 7E4M*z%FB?兔d"0J~\pZbE/q($!E^)z. b&X \L K\ ؀ RHpBb[T=qByGldꍦ+JD|l6HJb/*BQde gYUi X|lt?Tc0TTacolV᳢!R W-*}ds#ð"#{l)GgJXO8+J D0N|%R 1fyG?f&7*v`ƼJUT-^' FOiJ @R)JIp&n\8&\Z$Puؘ s's,Q2}թJ jMc{slS=BuR 1E"$)f&req$(}>W)m)'|#z- )<.B"y>X̊G夛k RmS '›[1)^ `ZVݏQv1%*4>aIHֵYYCQH;IP fSu*UBYALsBMˡθJqjZ^"LV0i1(P|Dm=LW$>d\ èn)|c@ۚviKq*RTp\e6b*E]M5ba6T& .n 37V\瀞E歹EeRVK@DL( H}@l$cXHv:͚RV¨)JH[K@Bj'~ 3eS-U@ 1 i6C},GqMQ0u\jJ\):N< &PG,F1&$6`GLKI܀J2-p!H$@3 in]ᨒime2`L 9%5[W t ={ʐ;p9i!2779 :rҤu2%N&DLcY[11]!6.W8Z[pXV:JMy60LМ>a-@p|YuWQ˒,O~$x7^d:aաf#RН&ɑ{1-učWT'CH7e_2<9<$%äD[/bsreG>]17%h4\ީj[(Ȁn߀.ޕ[zAԁ)#b1 4"h̪KVo)5TD M _>W3%B֢vwYR2J3rT>A12EInHzR+14?=m_܁8 lnm6AX#QvŖΈ'Q: qam2A-ŇRGNŀֆ!j:**fcmaōGt++ֵrOF12n4¢T'nMlzܟ݅Q82,g|Y` ˍVb1 Ml*t P& I7&-1˲s/tdesktop-1.2.17/Telegram/Resources/basic.style000066400000000000000000000171621326245125100213240ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ using "colors.palette"; TextPalette { linkFg: color; monoFg: color; selectBg: color; selectFg: color; selectLinkFg: color; selectMonoFg: color; selectOverlay: color; } TextStyle { font: font; linkFont: font; linkFontOver: font; lineHeight: pixels; } semibold: "Open Sans Semibold"; fsize: 13px; normalFont: font(fsize); semiboldFont: font(fsize semibold); boxFontSize: 14px; boxTextFont: font(boxFontSize); emojiImgSize: 18px; // exceptional value for retina emojiSize: 18px; emojiPadding: 1px; lineWidth: 1px; defaultTextPalette: TextPalette { linkFg: windowActiveTextFg; monoFg: windowSubTextFg; selectBg: msgInBgSelected; selectFg: transparent; // use painter current pen instead selectLinkFg: historyLinkInFgSelected; selectMonoFg: msgInMonoFgSelected; selectOverlay: msgSelectOverlay; } defaultTextStyle: TextStyle { font: normalFont; linkFont: normalFont; linkFontOver: font(fsize underline); lineHeight: 0px; } semiboldTextStyle: TextStyle(defaultTextStyle) { font: semiboldFont; linkFont: semiboldFont; linkFontOver: font(fsize semibold underline); } shadowToggleDuration: 200; slideDuration: 240; slideShift: 100px; slideShadow: icon {{ "slide_shadow", slideFadeOutShadowFg }}; slideWrapDuration: 150; fadeWrapDuration: 200; linkCropLimit: 360px; linkFont: normalFont; linkOverFont: font(fsize underline); dateRadius: 6px; buttonRadius: 3px; setLittleSkip: 9px; noContactsHeight: 100px; noContactsFont: font(fsize); noContactsColor: windowSubTextFg; activeFadeInDuration: 500; activeFadeOutDuration: 3000; msgMaxWidth: 430px; msgFont: font(fsize); msgNameFont: semiboldFont; msgNameStyle: semiboldTextStyle; msgServiceFont: semiboldFont; msgServiceNameFont: semiboldFont; msgServicePhotoWidth: 100px; msgDateFont: font(13px); msgMinWidth: 190px; msgPhotoSize: 33px; msgPhotoSkip: 40px; msgPadding: margins(13px, 7px, 13px, 8px); msgMargin: margins(16px, 6px, 56px, 2px); msgMarginTopAttached: 1px; msgLnkPadding: 2px; // for media open / save links msgShadow: 2px; msgReplyPadding: margins(6px, 6px, 11px, 6px); msgReplyBarPos: point(1px, 0px); msgReplyBarSize: size(2px, 36px); msgReplyBarSkip: 10px; msgServicePadding: margins(12px, 3px, 12px, 4px); msgServiceMargin: margins(10px, 10px, 10px, 2px); msgDateSpace: 12px; msgDateDelta: point(2px, 5px); msgDateImgDelta: 4px; msgDateImgPadding: point(8px, 2px); msgDateImgCheckSpace: 4px; messageTextStyle: defaultTextStyle; msgDateTextStyle: defaultTextStyle; serviceTextPalette: TextPalette(defaultTextPalette) { linkFg: msgServiceFg; monoFg: msgServiceFg; selectBg: msgServiceBgSelected; selectFg: msgServiceFg; selectLinkFg: msgServiceFg; selectMonoFg: msgServiceFg; selectOverlay: msgServiceBgSelected; } serviceTextStyle: TextStyle(defaultTextStyle) { font: msgServiceFont; linkFont: msgServiceFont; linkFontOver: font(fsize semibold underline); } inTextPalette: TextPalette(defaultTextPalette) { linkFg: historyLinkInFg; monoFg: msgInMonoFg; selectBg: msgInBgSelected; selectFg: historyTextInFgSelected; selectLinkFg: historyLinkInFgSelected; selectMonoFg: msgInMonoFgSelected; selectOverlay: msgSelectOverlay; } inTextPaletteSelected: TextPalette(inTextPalette) { linkFg: historyLinkInFgSelected; monoFg: msgInMonoFgSelected; } outTextPalette: TextPalette(defaultTextPalette) { linkFg: historyLinkOutFg; monoFg: msgOutMonoFg; selectBg: msgOutBgSelected; selectFg: historyTextOutFgSelected; selectLinkFg: historyLinkOutFgSelected; selectMonoFg: msgOutMonoFgSelected; selectOverlay: msgSelectOverlay; } outTextPaletteSelected: TextPalette(outTextPalette) { linkFg: historyLinkOutFgSelected; monoFg: msgOutMonoFgSelected; } fwdTextStyle: TextStyle(semiboldTextStyle) { linkFontOver: semiboldFont; } inFwdTextPalette: TextPalette(defaultTextPalette) { linkFg: msgInServiceFg; } outFwdTextPalette: TextPalette(defaultTextPalette) { linkFg: msgOutServiceFg; } inFwdTextPaletteSelected: TextPalette(defaultTextPalette) { linkFg: msgInServiceFgSelected; } outFwdTextPaletteSelected: TextPalette(defaultTextPalette) { linkFg: msgOutServiceFgSelected; } inSemiboldPalette: TextPalette(inTextPalette) { linkFg: msgInServiceFg; selectFg: msgInServiceFgSelected; selectLinkFg: msgInServiceFgSelected; } outSemiboldPalette: TextPalette(outTextPalette) { linkFg: msgOutServiceFg; selectFg: msgOutServiceFgSelected; selectLinkFg: msgOutServiceFgSelected; } mediaCaptionSkip: 5px; mediaInBubbleSkip: 5px; mediaThumbSize: 48px; mediaNameTop: 3px; mediaDetailsShift: 3px; mediaUnreadSize: 7px; mediaUnreadSkip: 5px; mediaUnreadTop: 6px; mediaInPalette: TextPalette(defaultTextPalette) { linkFg: mediaInFg; } mediaInPaletteSelected: TextPalette(defaultTextPalette) { linkFg: mediaInFgSelected; } textRectMargins: margins(-2px, -1px, -2px, -1px); searchedBarHeight: 32px; searchedBarFont: normalFont; searchedBarPosition: point(17px, 7px); smallCloseIcon: icon {{ "simple_close", smallCloseIconFg }}; smallCloseIconOver: icon {{ "simple_close", smallCloseIconFgOver }}; dialogsForwardCancelIcon: icon {{ "simple_close", dialogsForwardFg }}; emojiTextFont: font(15px); emojiReplaceWidth: 52px; emojiReplaceHeight: 56px; emojiReplaceInnerHeight: 42px; emojiReplacePadding: 14px; connectingPadding: margins(5px, 5px, 5px, 5px); dragFont: font(28px semibold); dragSubfont: font(20px semibold); dragColor: windowSubTextFg; dragDropColor: windowActiveTextFg; dragMargin: margins(0px, 10px, 0px, 10px); dragPadding: margins(20px, 10px, 20px, 10px); dragHeight: 72px; minPhotoSize: 100px; maxMediaSize: 430px; maxStickerSize: 256px; maxGifSize: 320px; maxSignatureSize: 144px; radialSize: size(50px, 50px); radialLine: 3px; radialDuration: 350; radialPeriod: 3000; youtubeIcon: icon { { "media_youtube_play_bg", youtubePlayIconBg }, { "media_youtube_play", youtubePlayIconFg, point(24px, 12px) }, }; videoIcon: icon { { "media_video_play_bg", videoPlayIconBg }, { "media_video_play", videoPlayIconFg, point(12px, 12px) }, }; locationSize: size(320px, 240px); webPageLeft: 10px; webPageBar: 2px; webPageTitleFont: semiboldFont; webPageTitleStyle: semiboldTextStyle; webPageTitleOutFg: historyTextOutFg; webPageTitleInFg: historyTextInFg; webPageDescriptionOutFg: historyTextOutFg; webPageDescriptionInFg: historyTextInFg; webPageDescriptionFont: normalFont; webPageDescriptionStyle: defaultTextStyle; webPagePhotoSize: 100px; webPagePhotoDelta: 8px; mediaPlayerSuppressDuration: 150; botDescSkip: 8px; inlineResultsLeft: 11px; inlineResultsSkip: 3px; inlineMediaHeight: 96px; inlineThumbSize: 64px; inlineThumbSkip: 10px; inlineTitleFg: windowFg; inlineDescriptionFg: windowSubTextFg; inlineRowMargin: 6px; inlineRowBorder: 1px; inlineRowBorderFg: shadowFg; inlineRowFileNameTop: 2px; inlineRowFileDescriptionTop: 23px; inlineResultsMinWidth: 48px; inlineDurationMargin: 3px; toastTextStyle: defaultTextStyle; toastMaxWidth: 480px; toastMinMargin: 13px; toastPadding: margins(19px, 13px, 19px, 12px); toastFadeInDuration: 200; toastFadeOutDuration: 1000; historyReplyCancelIcon: icon {{ "box_button_close", historyReplyCancelFg }}; historyReplyCancelIconOver: icon {{ "box_button_close", historyReplyCancelFgOver }}; boxTitleCloseIcon: icon {{ "box_button_close", boxTitleCloseFg }}; boxTitleCloseIconOver: icon {{ "box_button_close", boxTitleCloseFgOver }}; notifyFadeRight: icon {{ "fade_horizontal", notificationBg }}; stickerIconLeft: icon {{ "fade_horizontal-flip_horizontal", emojiPanCategories }}; stickerIconRight: icon {{ "fade_horizontal", emojiPanCategories }}; transparentPlaceholderSize: 4px; tdesktop-1.2.17/Telegram/Resources/colors.palette000066400000000000000000001225351326245125100220430ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ // basic windowBg: #ffffff; // white: fallback for background windowFg: #000000; // black: fallback for text windowBgOver: #f1f1f1; // light gray: fallback for background with mouse over windowBgRipple: #e5e5e5; // darker gray: fallback for ripple effect windowFgOver: windowFg; // black: fallback for text with mouse over windowSubTextFg: #999999; // gray: fallback for additional text windowSubTextFgOver: #919191; // darker gray: fallback for additional text with mouse over windowBoldFg: #222222; // dark gray: fallback for bold text windowBoldFgOver: #222222; // dark gray: fallback for bold text with mouse over windowBgActive: #40a7e3; // bright blue: fallback for blue filled active areas windowFgActive: #ffffff; // white: fallback for text on active areas windowActiveTextFg: #168acd; // online blue: fallback for active text like online status windowShadowFg: #000000; // black: fallback for shadow windowShadowFgFallback: #f1f1f1; // gray: fallback for shadow without opacity shadowFg: #00000018; // most shadows (including opacity) slideFadeOutBg: #0000003c; // slide animation (chat to profile) fade out filling slideFadeOutShadowFg: windowShadowFg; // slide animation (chat to profile) fade out right section shadow imageBg: #000000; // image background fallback (when photo size is less than minimum allowed) imageBgTransparent: #ffffff; // image background when displaying an image with opacity where no opacity is needed // widgets activeButtonBg: windowBgActive; // default active button background activeButtonBgOver: #39a5db; // default active button background with mouse over activeButtonBgRipple: #2095d0; // default active button ripple effect activeButtonFg: windowFgActive; // default active button text activeButtonFgOver: activeButtonFg; // default active button text with mouse over activeButtonSecondaryFg: #cceeff; // default active button additional text (selected messages counter in forward / delete buttons) activeButtonSecondaryFgOver: activeButtonSecondaryFg; // default active button additional text with mouse over activeLineFg: #37a1de; // default active line (like code input field bottom border when you log in and field is focused) activeLineFgError: #e48383; // default active line for error state (like code input field bottom border when you log in and you've entered incorrect code) lightButtonBg: windowBg; // default light button background (like buttons in boxes) lightButtonBgOver: #e3f1fa; // default light button background with mouse over lightButtonBgRipple: #c9e4f6; // default light button ripple effect lightButtonFg: windowActiveTextFg; // default light button text lightButtonFgOver: lightButtonFg; // default light button text with mouse over attentionButtonFg: #d14e4e; // default attention button text (like confirm button on log out) attentionButtonFgOver: #d14e4e; // default attention button text with mouse over attentionButtonBgOver: #fcdfde; // default attention button background with mouse over attentionButtonBgRipple: #f4c3c2; // default attention button ripple effect outlineButtonBg: windowBg; // default left outlined button background (like shared media links in profiles) outlineButtonBgOver: lightButtonBgOver; // default left outlined button background with mouse over outlineButtonOutlineFg: windowBgActive; // default left outlined button left outline border outlineButtonBgRipple: lightButtonBgRipple; // default left outlined button ripple effect menuBg: windowBg; // default popup menu background menuBgOver: windowBgOver; // default popup menu item background with mouse over menuBgRipple: windowBgRipple; // default popup menu item ripple effect menuIconFg: #a8a8a8; // default popup menu item icon (like main menu) menuIconFgOver: #999999; // default popup menu item icon with mouse over menuSubmenuArrowFg: #373737; // default popup menu submenu arrow icon (like in message field context menu in case of RTL system language) menuFgDisabled: #cccccc; // default popup menu item disabled text (like unavailable items in message field context menu) menuSeparatorFg: #f1f1f1; // default popup menu separator (like in message field context menu) scrollBarBg: #00000053; // default scroll bar current rectangle, the bar itself (like in chats list) scrollBarBgOver: #0000007a; // default scroll bar current rectangle with mouse over it scrollBg: #0000001a; // default scroll bar background scrollBgOver: #0000002c; // default scroll bar background with mouse over the scroll bar smallCloseIconFg: #c7c7c7; // small X icon (like in Show all sessions box to the right for sessions termination) smallCloseIconFgOver: #a3a3a3; // small X icon with mouse over radialFg: windowFgActive; // default radial loader line (like in Media Viewer when loading a photo) radialBg: #00000056; // default radial loader background (like in Media Viewer when loading a photo) placeholderFg: windowSubTextFg; // default input field placeholder when field is not focused (like in phone input field when you log in) placeholderFgActive: #aaaaaa; // default input field placeholder when field is focused inputBorderFg: #e0e0e0; // default input field bottom border (like in code input field when you log in and field is not focused) filterInputBorderFg: #54c3f3; // default rounded input field border (like in chats list search field when field is focused) filterInputActiveBg: windowBg; // default rounded input field active background (like in chats list search field when field is focused) filterInputInactiveBg: windowBgOver; // default rounded input field inactive background (like in chats list search field when field is inactive) checkboxFg: #b3b3b3; // default unchecked checkbox rounded rectangle botKbBg: menuBgOver; // bot keyboard button background botKbDownBg: menuBgRipple; // bot keyboard button ripple effect botKbColor: windowBoldFgOver; // bot keyboard button text sliderBgInactive: #e1eaef; // default slider not active bar (like in Settings when you choose interface scale or custom notifications count) sliderBgActive: windowBgActive; // default slider active bar (like in Settings when you choose interface scale or custom notifications count) tooltipBg: #eef2f5; // tooltip background (like when you put mouse over the message timestamp and wait) tooltipFg: #5d6c80; // tooltip text tooltipBorderFg: #c9d1db; // tooltip border // custom title bar for Windows and macOS titleShadow: #00000003; // one pixel line shadow at the bottom of custom window title titleBg: windowBgOver; // custom window title background when window is inactive titleBgActive: titleBg; // custom window title background when window is active titleButtonBg: titleBg; // custom window title minimize/maximize/restore button background when window is inactive (Windows only) titleButtonFg: #ababab; // custom window title minimize/maximize/restore button icon when window is inactive (Windows only) titleButtonBgOver: #e5e5e5; // custom window title minimize/maximize/restore button background with mouse over when window is inactive (Windows only) titleButtonFgOver: #9a9a9a; // custom window title minimize/maximize/restore button icon with mouse over when window is inactive (Windows only) titleButtonBgActive: titleButtonBg; // custom window title minimize/maximize/restore button background when window is active (Windows only) titleButtonFgActive: titleButtonFg; // custom window title minimize/maximize/restore button icon when window is active (Windows only) titleButtonBgActiveOver: titleButtonBgOver; // custom window title minimize/maximize/restore button background with mouse over when window is active (Windows only) titleButtonFgActiveOver: titleButtonFgOver; // custom window title minimize/maximize/restore button icon with mouse over when window is active (Windows only) titleButtonCloseBg: titleButtonBg; // custom window title close button background when window is inactive (Windows only) titleButtonCloseFg: titleButtonFg; // custom window title close button icon when window is inactive (Windows only) titleButtonCloseBgOver: #e81123; // custom window title close button background with mouse over when window is inactive (Windows only) titleButtonCloseFgOver: windowFgActive; // custom window title close button icon with mouse over when window is inactive (Windows only) titleButtonCloseBgActive: titleButtonCloseBg; // custom window title close button background when window is active (Windows only) titleButtonCloseFgActive: titleButtonCloseFg; // custom window title close button icon when window is active (Windows only) titleButtonCloseBgActiveOver: titleButtonCloseBgOver; // custom window title close button background with mouse over when window is active (Windows only) titleButtonCloseFgActiveOver: titleButtonCloseFgOver; // custom window title close button icon with mouse over when window is active (Windows only) titleFg: #acacac; // custom window title text when window is inactive (macOS only) titleFgActive: #3e3c3e; // custom window title text when window is active (macOS only) // tray icon trayCounterBg: #f23c34; // tray icon counter background trayCounterBgMute: #888888; // tray icon counter background if all unread messages are muted trayCounterFg: #ffffff; // tray icon counter text trayCounterBgMacInvert: #ffffff; // tray icon counter background when tray icon is pressed or when dark theme of macOS is used (macOS only) trayCounterFgMacInvert: #ffffff01; // tray icon counter text when tray icon is pressed or when dark theme of macOS is used (macOS only) // layers layerBg: #0000007f; // box and main menu background layer fade cancelIconFg: menuIconFg; // default for settings close icon and box search cancel icon cancelIconFgOver: menuIconFgOver; // default for settings close icon and box search cancel icon with mouse over // boxes boxBg: windowBg; // box background boxTextFg: windowFg; // box text boxTextFgGood: #4ab44a; // accepted box text (like when choosing username that is not occupied) boxTextFgError: #d84d4d; // rejecting box text (like when choosing username that is occupied) boxTitleFg: #404040; // box title text boxSearchBg: boxBg; // box search field background (like in contacts box) boxTitleAdditionalFg: #808080; // box title additional text (like in create group box when you see chosen members count) boxTitleCloseFg: cancelIconFg; // settings close icon and box search cancel icon (like in contacts box) boxTitleCloseFgOver: cancelIconFgOver; // settings close icon and box search cancel icon (like in contacts box) with mouse over //boxSearchCancelIconFg: cancelIconFg; // search cancel X button icon (like in contacts box) (not implemented yet) //boxSearchCancelIconFgOver: cancelIconFgOver; // search cancel X button icon with mouse over (not implemented yet) membersAboutLimitFg: windowSubTextFgOver; // text in channel members box about the limit (max 200 last members are shown) contactsBg: windowBg; // contacts (and some other) box row background contactsBgOver: windowBgOver; // contacts (and some other) box row background with mouse over contactsNameFg: boxTextFg; // contacts (and some other) box row name text contactsStatusFg: windowSubTextFg; // contacts (and some other) box row additional text (like last seen stamp) contactsStatusFgOver: windowSubTextFgOver; // contacts (and some other) box row additional text (like last seen stamp) with mouse over contactsStatusFgOnline: windowActiveTextFg; // contacts (and some other) box row active additional text (like online status) photoCropFadeBg: layerBg; // avatar crop box fade background (when choosing a new photo in Settings or for a group) photoCropPointFg: #ffffff7f; // avatar crop box corner rectangles (when choosing a new photo in Settings or for a group) callArrowFg: #2ab32a | boxTextFgGood; // received phone call arrow (in calls list box) callArrowMissedFg: #dd5b4a | boxTextFgError; // missed phone call arrow (in calls list box) // intro introBg: windowBg; // login background introTitleFg: windowBoldFg; // login title text introDescriptionFg: windowSubTextFg; // login description text introErrorFg: windowSubTextFg; // login error text (like when providing a wrong log in code) introCoverTopBg: #0f89d0; // intro gradient top (from) introCoverBottomBg: #39b0f0; // intro gradient bottom (to) introCoverIconsFg: #5ec6ff; // intro cloud graphics introCoverPlaneTrace: #5ec6ff69; // intro plane traces introCoverPlaneInner: #c6d8e8; // intro plane part introCoverPlaneOuter: #a1bed4; // intro plane part introCoverPlaneTop: #ffffff; // intro plane part // dialogs dialogsMenuIconFg: menuIconFg; // main menu and lock telegram icon dialogsMenuIconFgOver: menuIconFgOver; // main menu and lock telegram icon with mouse over dialogsBg: windowBg; // chat list background dialogsNameFg: windowBoldFg; // chat list name text dialogsChatIconFg: dialogsNameFg; // chat list group or channel icon dialogsDateFg: windowSubTextFg; // chat list date text dialogsTextFg: windowSubTextFg; // chat list message text dialogsTextFgService: windowActiveTextFg; // chat list group sender name text (or media message type text) dialogsDraftFg: #dd4b39; // chat list draft label dialogsVerifiedIconBg: windowBgActive; // chat list verified icon background dialogsVerifiedIconFg: windowFgActive; // chat list verified icon check dialogsSendingIconFg: #c1c1c1; // chat list sending message icon (clock) dialogsSentIconFg: #5dc452; // chat list sent message tick / double tick icon dialogsUnreadBg: windowBgActive; // chat list unread badge background for not muted chat dialogsUnreadBgMuted: #bbbbbb; // chat list unread badge background for muted chat dialogsUnreadFg: windowFgActive; // chat list unread badge text dialogsBgOver: windowBgOver; // chat list background with mouse over dialogsNameFgOver: windowBoldFgOver; // chat list name text with mouse over dialogsChatIconFgOver: dialogsNameFgOver; // chat list group or channel icon with mouse over dialogsDateFgOver: windowSubTextFgOver; // chat list date text with mouse over dialogsTextFgOver: windowSubTextFgOver; // chat list message text with mouse over dialogsTextFgServiceOver: dialogsTextFgService; // chat list group sender name text with mouse over dialogsDraftFgOver: dialogsDraftFg; // chat list draft label with mouse over dialogsVerifiedIconBgOver: dialogsVerifiedIconBg; // chat list verified icon background with mouse over dialogsVerifiedIconFgOver: dialogsVerifiedIconFg; // chat list verified icon check with mouse over dialogsSendingIconFgOver: dialogsSendingIconFg; // chat list sending message icon (clock) with mouse over dialogsSentIconFgOver: dialogsSentIconFg; // chat list sent message tick / double tick icon with mouse over dialogsUnreadBgOver: dialogsUnreadBg; // chat list unread badge background for not muted chat with mouse over dialogsUnreadBgMutedOver: dialogsUnreadBgMuted; // chat list unread badge background for muted chat with mouse over dialogsUnreadFgOver: dialogsUnreadFg; // chat list unread badge text with mouse over dialogsBgActive: #419fd9; // chat list background for current (active) chat dialogsNameFgActive: windowFgActive; // chat list name text for current (active) chat dialogsChatIconFgActive: dialogsNameFgActive; // chat list group or channel icon for current (active) chat dialogsDateFgActive: windowFgActive; // chat list date text for current (active) chat dialogsTextFgActive: windowFgActive; // chat list message text for current (active) chat dialogsTextFgServiceActive: dialogsTextFgActive; // chat list group sender name text for current (active) chat dialogsDraftFgActive: #c6e1f7; // chat list draft label for current (active) chat dialogsVerifiedIconBgActive: dialogsTextFgActive; // chat list verified icon background for current (active) chat dialogsVerifiedIconFgActive: dialogsBgActive; // chat list verified icon check for current (active) chat dialogsSendingIconFgActive: #ffffff99; // chat list sending message icon (clock) for current (active) chat dialogsSentIconFgActive: dialogsTextFgActive; // chat list sent message tick / double tick icon for current (active) chat dialogsUnreadBgActive: dialogsTextFgActive; // chat list unread badge background for not muted chat for current (active) chat dialogsUnreadBgMutedActive: dialogsDraftFgActive; // chat list unread badge background for muted chat for current (active) chat dialogsUnreadFgActive: dialogsBgActive; // chat list unread badge text for current (active) chat dialogsRippleBg: windowBgRipple; // chat list background ripple effect dialogsRippleBgActive: activeButtonBgRipple; // chat list background ripple effect for current (active) chat dialogsForwardBg: dialogsBgActive; // forwarding panel background (when forwarding messages in the smallest window size) dialogsForwardFg: dialogsNameFgActive; // forwarding panel text (when forwarding messages in the smallest window size) searchedBarBg: windowBgOver; // search results bar background (in chats list, contacts box..) searchedBarFg: windowSubTextFgOver; // search results bar text (in chats list, contacts box..) // history topBarBg: windowBg; // top bar background (in chat view, media overview..) emojiPanBg: windowBg; // emoji panel background emojiPanCategories: #f7f7f7 | windowBg; // emoji panel categories background emojiPanHeaderFg: windowSubTextFg; // emoji panel section header text emojiPanHeaderBg: #fffffff2 | emojiPanBg; // emoji panel section header background emojiIconFg: checkboxFg; // emoji category icon emojiIconFgActive: windowBgActive; // active emoji category icon stickerPanDeleteBg: #000000ff; // delete X button background for custom sent stickers in stickers panel (legacy) stickerPanDeleteFg: windowFgActive; // delete X button icon for custom sent stickers in stickers panel (legacy) stickerPreviewBg: #ffffffb0; // sticker and GIF preview background (when you press and hold on a sticker) historyTextInFg: windowFg; // inbox message text historyTextInFgSelected: historyTextInFg; // inbox message selected text or text in a selected message historyTextOutFg: windowFg; // outbox message text historyTextOutFgSelected: historyTextOutFg; // outbox message selected text or text in a selected message historyLinkInFg: windowActiveTextFg; // inbox message link historyLinkInFgSelected: historyLinkInFg; // inbox message link in a selected text or message historyLinkOutFg: windowActiveTextFg; // outbox message link historyLinkOutFgSelected: historyLinkOutFg; // outbox message link in a selected text or message historyFileNameInFg: historyTextInFg; // inbox media filename text historyFileNameInFgSelected: historyFileNameInFg; // inbox media filename text in a selected message historyFileNameOutFg: historyTextOutFg; // outbox media filename text historyFileNameOutFgSelected: historyFileNameOutFg; // outbox media filename text in a selected message historyOutIconFg: dialogsSentIconFg; // outbox message tick / double tick icon historyOutIconFgSelected: #4da79f; // outbox message tick / double tick icon in a selected message historyIconFgInverted: windowFgActive; // media message tick / double tick icon (like in sent photo) historySendingOutIconFg: #98d292; // outbox sending message icon (clock) historySendingInIconFg: #a0adb5; // inbox sending message icon (clock) (like in sent messages to yourself or in sent messages to a channel) historySendingInvertedIconFg: #ffffffc8; // media sending message icon (clock) (like in sent photo) historyCallArrowInFg: callArrowFg; // received phone call arrow historyCallArrowInFgSelected: callArrowFg; // received phone call arrow in a selected message historyCallArrowMissedInFg: callArrowMissedFg; // missed phone call arrow historyCallArrowMissedInFgSelected: callArrowMissedFg; // missed phone call arrow in a selected message historyCallArrowOutFg: historyCallArrowInFg; // outgoing phone call arrow historyCallArrowOutFgSelected: historyCallArrowInFgSelected; // outgoing phone call arrow historyUnreadBarBg: #fcfbfa; // new unread messages bar background historyUnreadBarBorder: shadowFg; // new unread messages bar shadow historyUnreadBarFg: #538bb4; // new unread messages bar text historyForwardChooseBg: #0000004c; // forwarding messages in a large window size "choose recipient" background historyForwardChooseFg: windowFgActive; // forwarding messages in a large window size "choose recipient" text historyPeer1NameFg: #c03d33; // red group member name historyPeer1NameFgSelected: historyPeer1NameFg; // red group member name in a selected message historyPeer1UserpicBg: #e17076; // red userpic background historyPeer2NameFg: #4fad2d; // green group member name historyPeer2NameFgSelected: historyPeer2NameFg; // green group member name in a selected message historyPeer2UserpicBg: #7bc862; // green userpic background historyPeer3NameFg: #d09306; // yellow group member name historyPeer3NameFgSelected: historyPeer3NameFg; // yellow group member name in a selected message historyPeer3UserpicBg: #e5ca77; // yellow userpic background historyPeer4NameFg: windowActiveTextFg; // blue group member name historyPeer4NameFgSelected: historyPeer4NameFg; // blue group member name in a selected message historyPeer4UserpicBg: #65aadd; // blue userpic background historyPeer5NameFg: #8544d6; // purple group member name historyPeer5NameFgSelected: historyPeer5NameFg; // purple group member name in a selected message historyPeer5UserpicBg: #a695e7; // purple userpic background historyPeer6NameFg: #cd4073; // pink group member name historyPeer6NameFgSelected: historyPeer6NameFg; // pink group member name in a selected message historyPeer6UserpicBg: #ee7aae; // pink userpic background historyPeer7NameFg: #2996ad; // sea group member name historyPeer7NameFgSelected: historyPeer7NameFg; // sea group member name in a selected message historyPeer7UserpicBg: #6ec9cb; // sea userpic background historyPeer8NameFg: #ce671b; // orange group member name historyPeer8NameFgSelected: historyPeer8NameFg; // orange group member name in a selected message historyPeer8UserpicBg: #faa774; // orange userpic background historyPeerUserpicFg: windowFgActive; // default userpic initials historyPeerSavedMessagesBg: historyPeer4UserpicBg; // saved messages userpic background // Some values are marked as (adjusted), it means they're adjusted by // hue and saturation of the average background color if user chooses // some other (not bundled to this color theme) background. If the // bundled background is used those colors are not adjusted in any way. historyScrollBarBg: #517c417a; // scroll bar current rectangle, the bar itself in the chat view (adjusted) historyScrollBarBgOver: #517c41bc; // scroll bar current rectangle with mouse over it in the chat view (adjusted) historyScrollBg: #517c414c; // scroll bar background (adjusted) historyScrollBgOver: #517c416b; // scroll bar background with mouse over the scroll bar (adjusted) msgInBg: windowBg; // inbox message background msgInBgSelected: #c2dcf2; // inbox selected message background (and background of selected text in those messages) msgOutBg: #effdde; // outbox message background msgOutBgSelected: #b7dbdb; // outbox selected message background (and background of selected text in those messages) msgSelectOverlay: #358cd44c; // overlay which is filling the media parts of selected messages (like in selected photo message) msgStickerOverlay: #358cd47f; // overlay which is filling the selected sticker message msgInServiceFg: windowActiveTextFg; // inbox message information text (like information about a forwarded message original sender) msgInServiceFgSelected: windowActiveTextFg; // inbox selected message information text (like information about a forwarded message original sender) msgOutServiceFg: #3a8e26; // outbox message information text (like information about a forwarded message original sender) msgOutServiceFgSelected: #367570; // outbox message information text (like information about a forwarded message original sender) msgInShadow: #748ea229; // inbox message shadow (below the bubble) msgInShadowSelected: #548dbb29; // inbox selected message shadow (below the bubble) msgOutShadow: #3ac34740; // outbox message shadow (below the bubble) msgOutShadowSelected: #37a78e40; // outbox selected message shadow (below the bubble) msgInDateFg: #a0acb6; // inbox message time text msgInDateFgSelected: #6a9cc5; // inbox selected message time text msgOutDateFg: #6cc264; // outbox message time text msgOutDateFgSelected: #50a79c; // outbox selected message time text msgServiceFg: windowFgActive; // service message text (like date dividers or service message about the group title being changed) msgServiceBg: #517c417f; // service message background (like in a service message about group title being changed) (adjusted) msgServiceBgSelected: #96b38ba2; // service message selected text background (like in a service message about group title being changed) (adjusted) msgInReplyBarColor: activeLineFg; // inbox message reply outline msgInReplyBarSelColor: activeLineFg; // inbox selected message reply outline msgOutReplyBarColor: historyOutIconFg; // outbox message reply outline msgOutReplyBarSelColor: historyOutIconFgSelected; // outbox selected message reply outline msgImgReplyBarColor: msgServiceFg; // sticker message reply outline msgInMonoFg: #4e7391; // inbox message monospace text (like a message sent with `test` text) msgOutMonoFg: #469165; // outbox message monospace text msgInMonoFgSelected: msgInMonoFg; // inbox message monospace text in a selected text or message msgOutMonoFgSelected: msgOutMonoFg; // outbox message monospace text in a selected text or message msgDateImgFg: msgServiceFg; // media message time text (like time text in a sent photo) msgDateImgBg: #00000054; // media message time bubble background (like time bubble in a sent photo) or file with thumbnail download icon circle background msgDateImgBgOver: #00000074; // media message download icon circle background with mouse over (like file with thumbnail download icon) msgDateImgBgSelected: #1c4a7187; // selected media message time bubble background msgFileThumbLinkInFg: lightButtonFg; // inbox media file message with thumbnail download / open with button text msgFileThumbLinkInFgSelected: lightButtonFgOver; // inbox selected media file message with thumbnail download / open with button text msgFileThumbLinkOutFg: #5eba5b; // outbox media file message with thumbnail download / open with button text msgFileThumbLinkOutFgSelected: #31a298; // outbox selected media file message with thumbnail download / open with button text msgFileInBg: windowBgActive; // inbox audio file download circle background msgFileInBgOver: #4eade3; // inbox audio file download circle background with mouse over msgFileInBgSelected: #51a3d3; // inbox selected audio file download circle background msgFileOutBg: #78c67f; // outbox audio file download circle background msgFileOutBgOver: #6bc272; // outbox audio file download circle background with mouse over msgFileOutBgSelected: #5fb389; // outbox selected audio file download circle background msgFile1Bg: #72b1df; // blue shared links / files without image square thumbnail msgFile1BgDark: #5c9ece; // blue shared files without image download circle background msgFile1BgOver: #5294c4; // blue shared files without image download circle background with mouse over msgFile1BgSelected: #5099d0; // blue shared files without image download circle background if file is selected msgFile2Bg: #61b96e; // green shared links / shared files without image square thumbnail msgFile2BgDark: #4da859; // green shared files without image download circle background msgFile2BgOver: #44a050; // green shared files without image download circle background with mouse over msgFile2BgSelected: #46a07e; // green shared files without image download circle background if file is selected msgFile3Bg: #e47272; // red shared links / shared files without image square thumbnail msgFile3BgDark: #cd5b5e; // red shared files without image download circle background msgFile3BgOver: #c35154; // red shared files without image download circle background with mouse over msgFile3BgSelected: #9f6a82; // red shared files without image download circle background if file is selected msgFile4Bg: #efc274; // yellow shared links / shared files without image square thumbnail msgFile4BgDark: #e6a561; // yellow shared files without image download circle background msgFile4BgOver: #dc9c5a; // yellow shared files without image download circle background with mouse over msgFile4BgSelected: #b19d84; // yellow shared files without image download circle background if file is selected historyFileInIconFg: msgInBg; // inbox file without thumbnail (like audio file) download arrow icon historyFileInIconFgSelected: msgInBgSelected; // inbox selected file without thumbnail (like audio file) download arrow icon historyFileInRadialFg: historyFileInIconFg; // inbox file without thumbnail (like audio file) radial download animation line historyFileInRadialFgSelected: historyFileInIconFgSelected; // inbox selected file without thumbnail (like audio file) radial download animation line historyFileOutIconFg: msgOutBg; // outbox file without thumbnail (like audio file) download arrow icon historyFileOutIconFgSelected: msgOutBgSelected; // outbox selected file without thumbnail (like audio file) download arrow icon historyFileOutRadialFg: historyFileOutIconFg; // outbox file without thumbnail (like audio file) radial download animation line historyFileOutRadialFgSelected: historyFileOutIconFgSelected; // outbox selected file without thumbnail (like audio file) radial download animation line historyFileThumbIconFg: msgInBg; // file with thumbnail (or photo / video) download arrow icon historyFileThumbIconFgSelected: msgInBgSelected; // selected file with thumbnail (or photo / video) download arrow icon historyFileThumbRadialFg: historyFileThumbIconFg; // file with thumbnail (or photo / video) radial download animation line historyFileThumbRadialFgSelected: historyFileThumbIconFgSelected; // selected file with thumbnail (or photo / video) radial download animation line historyVideoMessageProgressFg: historyFileThumbIconFg; // radial playback progress in round video messages msgWaveformInActive: windowBgActive; // inbox voice message active waveform lines (like played part of currently playing voice message) msgWaveformInActiveSelected: #51a3d3; // inbox selected voice message active waveform lines (like played part of currently playing voice message) msgWaveformInInactive: #d4dee6; // inbox voice message inactive waveform lines (like upcoming part of currently playing voice message) msgWaveformInInactiveSelected: #9cc1e1; // inbox selected voice message inactive waveform lines (like upcoming part of currently playing voice message) msgWaveformOutActive: #78c67f; // outbox voice message active waveform lines (like played part of currently playing voice message) msgWaveformOutActiveSelected: #6badad; // outbox selected voice message active waveform lines (like played part of currently playing voice message) msgWaveformOutInactive: #b3e2b4; // outbox voice message inactive waveform lines (like upcoming part of currently playing voice message) msgWaveformOutInactiveSelected: #91c3c3; // outbox selected voice message inactive waveform lines (like upcoming part of currently playing voice message) msgBotKbOverBgAdd: #ffffff20; // this is painted over a bot inline keyboard button (which has msgServiceBg background) when mouse is over that button msgBotKbIconFg: msgServiceFg; // bot inline keyboard button icon in the top-right corner (like in @vote bot when a poll is ready to be shared) msgBotKbRippleBg: #00000020; // bot inline keyboard button ripple effect mediaInFg: msgInDateFg; // inbox media message status text (like in file that is being downloaded) mediaInFgSelected: msgInDateFgSelected; // inbox selected media message status text (like in file that is being downloaded) mediaOutFg: msgOutDateFg; // outbox media message status text (like in file that is being downloaded) mediaOutFgSelected: msgOutDateFgSelected; // outbox selected media message status text (like in file that is being downloaded) youtubePlayIconBg: #e83131c8; // youtube play icon background (when a link to a youtube video with a webpage preview is sent) youtubePlayIconFg: windowFgActive; // youtube play icon arrow (when a link to a youtube video with a webpage preview is sent) videoPlayIconBg: #0000007f; // other video play icon background (like when a link to a vimeo video with a webpage preview is sent) videoPlayIconFg: #ffffff; // other video play icon arrow (like when a link to a vimeo video with a webpage preview is sent) toastBg: #000000b2; // toast notification background (like when you click on your t.me link when editing your username) toastFg: windowFgActive; // toast notification text (like when you click on your t.me link when editing your username) reportSpamBg: emojiPanHeaderBg; // report spam panel background (like a non contact user writes your for the first time) reportSpamFg: windowFg; // report spam panel text (when you send a report from that panel) historyToDownBg: windowBg; // arrow button background (to scroll to the end of the viewed chat) historyToDownBgOver: windowBgOver; // arrow button background with mouse over historyToDownBgRipple: windowBgRipple; // arrow button ripple effect historyToDownFg: menuIconFg; // arrow button icon historyToDownFgOver: menuIconFgOver; // arrow button icon with mouse over historyToDownShadow: #00000040; // arrow button shadow historyComposeAreaBg: msgInBg; // history compose area background (message write area / reply information / forwarding information) historyComposeAreaFg: historyTextInFg; // history compose area text historyComposeAreaFgService: msgInDateFg; // history compose area text when replying to a media message historyComposeIconFg: menuIconFg; // history compose area icon (like emoji, attach, bot command..) historyComposeIconFgOver: menuIconFgOver; // history compose area icon with mouse over historySendIconFg: windowBgActive; // send message icon historySendIconFgOver: windowBgActive; // send message icon with mouse over historyPinnedBg: historyComposeAreaBg; // pinned message area background historyReplyBg: historyComposeAreaBg; // reply / forward / edit message area background historyReplyIconFg: windowBgActive; // reply / forward / edit message left icon historyReplyCancelFg: cancelIconFg; // reply / forward / edit message cancel button historyReplyCancelFgOver: cancelIconFgOver; // reply / forward / edit message cancel button with mouse over historyComposeButtonBg: historyComposeAreaBg; // unblock / join channel / mute channel button background historyComposeButtonBgOver: windowBgOver; // unblock / join channel / mute channel button background with mouse over historyComposeButtonBgRipple: windowBgRipple; // unblock / join channel / mute channel button ripple effect // overview overviewCheckBg: #00000040; // shared media / files / links checkbox background for not selected rows when some rows are selected overviewCheckBgActive: windowBgActive; // shared media / files / links checkbox background for selected rows overviewCheckBorder: windowBg; // shared media round checkbox border overviewCheckFg: windowBg; // shared files / links checkbox icon for not selected rows when some rows are selected overviewCheckFgActive: windowBg; // shared files / links checkbox icon for selected rows overviewPhotoSelectOverlay: #40ace333; // shared photos / videos / links fill for selected rows // profile profileStatusFgOver: #7c99b2; // group members list in group profile user last seen text with mouse over profileVerifiedCheckBg: windowBgActive; // profile verified check icon background profileVerifiedCheckFg: windowFgActive; // profile verified check icon tick profileAdminStartFg: windowBgActive; // group members list creator star icon profileAdminStarFgOver: profileAdminStartFg; // group members list creator star icon with mouse over profileOtherAdminStarFg: windowSubTextFg; // group members list admin star icon profileOtherAdminStarFgOver: profileStatusFgOver; // group members list admin star icon with mouse over // settings notificationsBoxMonitorFg: windowFg; // custom notifications settings box monitor color notificationsBoxScreenBg: dialogsBgActive; // #6389a8; // custom notifications settings box monitor screen background notificationSampleUserpicFg: windowBgActive; // custom notifications settings box small sample userpic placeholder notificationSampleCloseFg: #d7d7d7 | windowSubTextFg; // custom notifications settings box small sample close button placeholder notificationSampleTextFg: #d7d7d7 | windowSubTextFg; // custom notifications settings box small sample text placeholder notificationSampleNameFg: #939393 | windowSubTextFg; // custom notifications settings box small sample name placeholder changePhoneSimcardFrom: notificationSampleTextFg; // change phone number box left simcard icon changePhoneSimcardTo: notificationSampleNameFg; // change phone number box right simcard and plane icons mainMenuBg: windowBg; // main menu background mainMenuCoverBg: dialogsBgActive; // main menu top cover background mainMenuCoverFg: windowFgActive; // main menu top cover text mainMenuCloudFg: activeButtonFg; mainMenuCloudBg: #2785bf | activeButtonBgRipple; mediaPlayerBg: windowBg; // audio file player background mediaPlayerActiveFg: windowBgActive; // audio file player playback progress already played part mediaPlayerInactiveFg: sliderBgInactive; // audio file player playback progress upcoming (not played yet) part with mouse over mediaPlayerDisabledFg: #9dd1ef; // audio file player loading progress (when you're playing an audio file and switch to the previous one which is not loaded yet) // mediaview mediaviewFileBg: windowBg; // file rectangle background (when you view a png file in Media Viewer and go to a previous, not loaded yet, file) mediaviewFileNameFg: windowFg; // file name in file rectangle mediaviewFileSizeFg: windowSubTextFg; // file size text in file rectangle mediaviewFileRedCornerFg: #d55959; // red file thumbnail placeholder corner in file rectangle (for a file without thumbnail, like .pdf) mediaviewFileYellowCornerFg: #e8a659; // yellow file thumbnail placeholder corner in file rectangle (for a file without thumbnail, like .zip) mediaviewFileGreenCornerFg: #49a957; // green file thumbnail placeholder corner in file rectangle (for a file without thumbnail, like .exe) mediaviewFileBlueCornerFg: #599dcf; // blue file thumbnail placeholder corner in file rectangle (for a file without thumbnail, like .dmg) mediaviewFileExtFg: activeButtonFg; // file extension text in file thumbnail placeholder in file rectangle mediaviewMenuBg: #383838; // context menu in Media Viewer background mediaviewMenuBgOver: #505050; // context menu item background with mouse over mediaviewMenuBgRipple: #676767; // context menu item ripple effect mediaviewMenuFg: windowFgActive; // context menu item text mediaviewBg: #222222eb; // Media Viewer background mediaviewVideoBg: imageBg; // Media Viewer background when viewing a video in full screen mediaviewControlBg: #0000003c; // controls background (like next photo / previous photo) mediaviewControlFg: windowFgActive; // controls icon (like next photo / previous photo) mediaviewCaptionBg: #11111180; // caption text background (when viewing photo with caption) mediaviewCaptionFg: mediaviewControlFg; // caption text mediaviewTextLinkFg: #91d9ff; // caption text link mediaviewSaveMsgBg: toastBg; // save to file toast message background in Media Viewer mediaviewSaveMsgFg: toastFg; // save to file toast message text mediaviewPlaybackActive: #c7c7c7; // video playback progress already played part mediaviewPlaybackInactive: #252525; // video playback progress upcoming (not played yet) part mediaviewPlaybackActiveOver: #ffffff; // video playback progress already played part with mouse over mediaviewPlaybackInactiveOver: #474747; // video playback progress upcoming (not played yet) part with mouse over mediaviewPlaybackProgressFg: #ffffffc7; // video playback progress text mediaviewPlaybackIconFg: mediaviewPlaybackActive; // video playback controls icon mediaviewPlaybackIconFgOver: mediaviewPlaybackActiveOver; // video playback controls icon with mouse over mediaviewTransparentBg: #ffffff; // transparent filling part (when viewing a transparent .png file in Media Viewer) mediaviewTransparentFg: #cccccc; // another transparent filling part // notification notificationBg: windowBg; // custom notification window background // calls callBg: #26282cf2; // phone call popup background callNameFg: #ffffff; // phone call popup name text callFingerprintBg: #00000066; // phone call popup emoji fingerprint background callStatusFg: #aaabac; // phone call popup status text callIconFg: #ffffff; // phone call popup answer, hangup and mute mic icon callAnswerBg: #64c15b; // phone call popup answer button background callAnswerRipple: #52b149; // phone call popup answer button ripple effect callAnswerBgOuter: #50eb4126; // phone call popup answer button outer ripple effect callHangupBg: #d75a5a; // phone call popup hangup button background callHangupRipple: #c04646; // phone call popup hangup button ripple effect callCancelBg: #ffffff; // phone call popup line busy cancel button background callCancelFg: #777777; // phone call popup line busy cancel button icon callCancelRipple: #f1f1f1; // phone call popup line busy cancel button ripple effect callMuteRipple: #ffffff12; // phone call popup mute mic ripple effect callBarBg: dialogsBgActive; // active phone call bar background callBarMuteRipple: dialogsRippleBgActive; // active phone call bar mute and hangup button ripple effect callBarBgMuted: #8f8f8f | dialogsUnreadBgMuted; // phone call bar with muted mic background callBarUnmuteRipple: #7f7f7f | shadowFg; // phone call bar with muted mic mute and hangup button ripple effect callBarFg: dialogsNameFgActive; // phone call bar text and icons importantTooltipBg: toastBg; importantTooltipFg: toastFg; importantTooltipFgLink: mediaviewTextLinkFg; tdesktop-1.2.17/Telegram/Resources/emoji_autocomplete.json000066400000000000000000016362041326245125100237450ustar00rootroot00000000000000{ "0023-20e3": { "output": "0023-fe0f-20e3", "name": "keycap: #", "alpha_code": ":hash:", "aliases": "" }, "0030-20e3": { "output": "0030-fe0f-20e3", "name": "keycap: 0", "alpha_code": ":zero:", "aliases": "" }, "0031-20e3": { "output": "0031-fe0f-20e3", "name": "keycap: 1", "alpha_code": ":one:", "aliases": "" }, "0032-20e3": { "output": "0032-fe0f-20e3", "name": "keycap: 2", "alpha_code": ":two:", "aliases": "" }, "0033-20e3": { "output": "0033-fe0f-20e3", "name": "keycap: 3", "alpha_code": ":three:", "aliases": "" }, "0034-20e3": { "output": "0034-fe0f-20e3", "name": "keycap: 4", "alpha_code": ":four:", "aliases": "" }, "0035-20e3": { "output": "0035-fe0f-20e3", "name": "keycap: 5", "alpha_code": ":five:", "aliases": "" }, "0036-20e3": { "output": "0036-fe0f-20e3", "name": "keycap: 6", "alpha_code": ":six:", "aliases": "" }, "0037-20e3": { "output": "0037-fe0f-20e3", "name": "keycap: 7", "alpha_code": ":seven:", "aliases": "" }, "0038-20e3": { "output": "0038-fe0f-20e3", "name": "keycap: 8", "alpha_code": ":eight:", "aliases": "" }, "0039-20e3": { "output": "0039-fe0f-20e3", "name": "keycap: 9", "alpha_code": ":nine:", "aliases": "" }, "00a9": { "output": "00a9-fe0f", "name": "copyright", "alpha_code": ":copyright:", "aliases": "" }, "00ae": { "output": "00ae-fe0f", "name": "registered", "alpha_code": ":registered:", "aliases": "" }, "203c": { "output": "203c-fe0f", "name": "double exclamation mark", "alpha_code": ":bangbang:", "aliases": "" }, "2049": { "output": "2049-fe0f", "name": "exclamation question mark", "alpha_code": ":interrobang:", "aliases": "" }, "2122": { "output": "2122-fe0f", "name": "trade mark", "alpha_code": ":tm:", "aliases": "" }, "2139": { "output": "2139-fe0f", "name": "information", "alpha_code": ":information_source:", "aliases": "" }, "2194": { "output": "2194-fe0f", "name": "left-right arrow", "alpha_code": ":left_right_arrow:", "aliases": "" }, "2195": { "output": "2195-fe0f", "name": "up-down arrow", "alpha_code": ":arrow_up_down:", "aliases": "" }, "2196": { "output": "2196-fe0f", "name": "up-left arrow", "alpha_code": ":arrow_upper_left:", "aliases": "" }, "1f949": { "output": "1f949", "name": "3rd place medal", "alpha_code": ":third_place:", "aliases": ":third_place_medal:" }, "2197": { "output": "2197-fe0f", "name": "up-right arrow", "alpha_code": ":arrow_upper_right:", "aliases": "" }, "2198": { "output": "2198-fe0f", "name": "down-right arrow", "alpha_code": ":arrow_lower_right:", "aliases": "" }, "2199": { "output": "2199-fe0f", "name": "down-left arrow", "alpha_code": ":arrow_lower_left:", "aliases": "" }, "1f948": { "output": "1f948", "name": "2nd place medal", "alpha_code": ":second_place:", "aliases": ":second_place_medal:" }, "21a9": { "output": "21a9-fe0f", "name": "right arrow curving left", "alpha_code": ":leftwards_arrow_with_hook:", "aliases": "" }, "21aa": { "output": "21aa-fe0f", "name": "left arrow curving right", "alpha_code": ":arrow_right_hook:", "aliases": "" }, "231a": { "output": "231a", "name": "watch", "alpha_code": ":watch:", "aliases": "" }, "231b": { "output": "231b", "name": "hourglass", "alpha_code": ":hourglass:", "aliases": "" }, "23e9": { "output": "23e9", "name": "fast-forward button", "alpha_code": ":fast_forward:", "aliases": "" }, "23ea": { "output": "23ea", "name": "fast reverse button", "alpha_code": ":rewind:", "aliases": "" }, "23eb": { "output": "23eb", "name": "fast up button", "alpha_code": ":arrow_double_up:", "aliases": "" }, "23ec": { "output": "23ec", "name": "fast down button", "alpha_code": ":arrow_double_down:", "aliases": "" }, "23f0": { "output": "23f0", "name": "alarm clock", "alpha_code": ":alarm_clock:", "aliases": "" }, "23f3": { "output": "23f3", "name": "hourglass with flowing sand", "alpha_code": ":hourglass_flowing_sand:", "aliases": "" }, "24c2": { "output": "24c2-fe0f", "name": "circled M", "alpha_code": ":m:", "aliases": "" }, "25aa": { "output": "25aa-fe0f", "name": "black small square", "alpha_code": ":black_small_square:", "aliases": "" }, "25ab": { "output": "25ab-fe0f", "name": "white small square", "alpha_code": ":white_small_square:", "aliases": "" }, "25b6": { "output": "25b6-fe0f", "name": "play button", "alpha_code": ":arrow_forward:", "aliases": "" }, "25c0": { "output": "25c0-fe0f", "name": "reverse button", "alpha_code": ":arrow_backward:", "aliases": "" }, "25fb": { "output": "25fb-fe0f", "name": "white medium square", "alpha_code": ":white_medium_square:", "aliases": "" }, "25fc": { "output": "25fc-fe0f", "name": "black medium square", "alpha_code": ":black_medium_square:", "aliases": "" }, "25fd": { "output": "25fd", "name": "white medium-small square", "alpha_code": ":white_medium_small_square:", "aliases": "" }, "25fe": { "output": "25fe", "name": "black medium-small square", "alpha_code": ":black_medium_small_square:", "aliases": "" }, "2600": { "output": "2600-fe0f", "name": "sun", "alpha_code": ":sunny:", "aliases": "" }, "2601": { "output": "2601-fe0f", "name": "cloud", "alpha_code": ":cloud:", "aliases": "" }, "260e": { "output": "260e-fe0f", "name": "telephone", "alpha_code": ":telephone:", "aliases": "" }, "2611": { "output": "2611-fe0f", "name": "ballot box with check", "alpha_code": ":ballot_box_with_check:", "aliases": "" }, "2614": { "output": "2614", "name": "umbrella with rain drops", "alpha_code": ":umbrella:", "aliases": "" }, "2615": { "output": "2615", "name": "hot beverage", "alpha_code": ":coffee:", "aliases": "" }, "261d": { "output": "261d-fe0f", "name": "index pointing up", "alpha_code": ":point_up:", "aliases": "" }, "263a": { "output": "263a-fe0f", "name": "smiling face", "alpha_code": ":relaxed:", "aliases": "" }, "2648": { "output": "2648", "name": "Aries", "alpha_code": ":aries:", "aliases": "" }, "1f947": { "output": "1f947", "name": "1st place medal", "alpha_code": ":first_place:", "aliases": ":first_place_medal:" }, "2649": { "output": "2649", "name": "Taurus", "alpha_code": ":taurus:", "aliases": "" }, "1f93a": { "output": "1f93a", "name": "person fencing", "alpha_code": ":person_fencing:", "aliases": ":fencer:|:fencing:" }, "264a": { "output": "264a", "name": "Gemini", "alpha_code": ":gemini:", "aliases": "" }, "264b": { "output": "264b", "name": "Cancer", "alpha_code": ":cancer:", "aliases": "" }, "1f945": { "output": "1f945", "name": "goal net", "alpha_code": ":goal:", "aliases": ":goal_net:" }, "264c": { "output": "264c", "name": "Leo", "alpha_code": ":leo:", "aliases": "" }, "002a": { "output": "002a-fe0f", "name": "asterisk", "alpha_code": ":asterisk_symbol:", "aliases": "" }, "264d": { "output": "264d", "name": "Virgo", "alpha_code": ":virgo:", "aliases": "" }, "1f93e": { "output": "1f93e", "name": "person playing handball", "alpha_code": ":person_playing_handball:", "aliases": ":handball:" }, "264e": { "output": "264e", "name": "Libra", "alpha_code": ":libra:", "aliases": "" }, "1f1ff": { "output": "1f1ff", "name": "regional indicator symbol letter z", "alpha_code": ":regional_indicator_z:", "aliases": "" }, "1f93d": { "output": "1f93d", "name": "person playing water polo", "alpha_code": ":person_playing_water_polo:", "aliases": ":water_polo:" }, "264f": { "output": "264f", "name": "Scorpius", "alpha_code": ":scorpius:", "aliases": "" }, "2650": { "output": "2650", "name": "Sagittarius", "alpha_code": ":sagittarius:", "aliases": "" }, "1f94b": { "output": "1f94b", "name": "martial arts uniform", "alpha_code": ":martial_arts_uniform:", "aliases": ":karate_uniform:" }, "2651": { "output": "2651", "name": "Capricorn", "alpha_code": ":capricorn:", "aliases": "" }, "1f94a": { "output": "1f94a", "name": "boxing glove", "alpha_code": ":boxing_glove:", "aliases": ":boxing_gloves:" }, "2652": { "output": "2652", "name": "Aquarius", "alpha_code": ":aquarius:", "aliases": "" }, "1f93c": { "output": "1f93c", "name": "people wrestling", "alpha_code": ":people_wrestling:", "aliases": ":wrestlers:|:wrestling:" }, "2653": { "output": "2653", "name": "Pisces", "alpha_code": ":pisces:", "aliases": "" }, "2660": { "output": "2660-fe0f", "name": "spade suit", "alpha_code": ":spades:", "aliases": "" }, "2663": { "output": "2663-fe0f", "name": "club suit", "alpha_code": ":clubs:", "aliases": "" }, "2665": { "output": "2665-fe0f", "name": "heart suit", "alpha_code": ":hearts:", "aliases": "" }, "2666": { "output": "2666-fe0f", "name": "diamond suit", "alpha_code": ":diamonds:", "aliases": "" }, "2668": { "output": "2668-fe0f", "name": "hot springs", "alpha_code": ":hotsprings:", "aliases": "" }, "267b": { "output": "267b-fe0f", "name": "recycling symbol", "alpha_code": ":recycle:", "aliases": "" }, "1f939": { "output": "1f939", "name": "person juggling", "alpha_code": ":person_juggling:", "aliases": ":juggling:|:juggler:" }, "267f": { "output": "267f", "name": "wheelchair symbol", "alpha_code": ":wheelchair:", "aliases": "" }, "2693": { "output": "2693", "name": "anchor", "alpha_code": ":anchor:", "aliases": "" }, "26a0": { "output": "26a0-fe0f", "name": "warning", "alpha_code": ":warning:", "aliases": "" }, "26a1": { "output": "26a1", "name": "high voltage", "alpha_code": ":zap:", "aliases": "" }, "26aa": { "output": "26aa", "name": "white circle", "alpha_code": ":white_circle:", "aliases": "" }, "26ab": { "output": "26ab", "name": "black circle", "alpha_code": ":black_circle:", "aliases": "" }, "26bd": { "output": "26bd", "name": "soccer ball", "alpha_code": ":soccer:", "aliases": "" }, "26be": { "output": "26be", "name": "baseball", "alpha_code": ":baseball:", "aliases": "" }, "26c4": { "output": "26c4", "name": "snowman without snow", "alpha_code": ":snowman:", "aliases": "" }, "26c5": { "output": "26c5", "name": "sun behind cloud", "alpha_code": ":partly_sunny:", "aliases": "" }, "26ce": { "output": "26ce", "name": "Ophiuchus", "alpha_code": ":ophiuchus:", "aliases": "" }, "1f938": { "output": "1f938", "name": "person cartwheeling", "alpha_code": ":person_doing_cartwheel:", "aliases": ":cartwheel:" }, "26d4": { "output": "26d4", "name": "no entry", "alpha_code": ":no_entry:", "aliases": "" }, "26ea": { "output": "26ea", "name": "church", "alpha_code": ":church:", "aliases": "" }, "26f2": { "output": "26f2", "name": "fountain", "alpha_code": ":fountain:", "aliases": "" }, "1f6f6": { "output": "1f6f6", "name": "canoe", "alpha_code": ":canoe:", "aliases": ":kayak:" }, "26f3": { "output": "26f3", "name": "flag in hole", "alpha_code": ":golf:", "aliases": "" }, "26f5": { "output": "26f5", "name": "sailboat", "alpha_code": ":sailboat:", "aliases": "" }, "26fa": { "output": "26fa", "name": "tent", "alpha_code": ":tent:", "aliases": "" }, "26fd": { "output": "26fd", "name": "fuel pump", "alpha_code": ":fuelpump:", "aliases": "" }, "2702": { "output": "2702-fe0f", "name": "scissors", "alpha_code": ":scissors:", "aliases": "" }, "2705": { "output": "2705", "name": "white heavy check mark", "alpha_code": ":white_check_mark:", "aliases": "" }, "2708": { "output": "2708-fe0f", "name": "airplane", "alpha_code": ":airplane:", "aliases": "" }, "2709": { "output": "2709-fe0f", "name": "envelope", "alpha_code": ":envelope:", "aliases": "" }, "270a": { "output": "270a", "name": "raised fist", "alpha_code": ":fist:", "aliases": "" }, "270b": { "output": "270b", "name": "raised hand", "alpha_code": ":raised_hand:", "aliases": "" }, "270c": { "output": "270c-fe0f", "name": "victory hand", "alpha_code": ":v:", "aliases": "" }, "270f": { "output": "270f-fe0f", "name": "pencil", "alpha_code": ":pencil2:", "aliases": "" }, "2712": { "output": "2712-fe0f", "name": "black nib", "alpha_code": ":black_nib:", "aliases": "" }, "2714": { "output": "2714-fe0f", "name": "heavy check mark", "alpha_code": ":heavy_check_mark:", "aliases": "" }, "2716": { "output": "2716-fe0f", "name": "heavy multiplication x", "alpha_code": ":heavy_multiplication_x:", "aliases": "" }, "2728": { "output": "2728", "name": "sparkles", "alpha_code": ":sparkles:", "aliases": "" }, "2733": { "output": "2733-fe0f", "name": "eight-spoked asterisk", "alpha_code": ":eight_spoked_asterisk:", "aliases": "" }, "2734": { "output": "2734-fe0f", "name": "eight-pointed star", "alpha_code": ":eight_pointed_black_star:", "aliases": "" }, "2744": { "output": "2744-fe0f", "name": "snowflake", "alpha_code": ":snowflake:", "aliases": "" }, "2747": { "output": "2747-fe0f", "name": "sparkle", "alpha_code": ":sparkle:", "aliases": "" }, "274c": { "output": "274c", "name": "cross mark", "alpha_code": ":x:", "aliases": "" }, "274e": { "output": "274e", "name": "cross mark button", "alpha_code": ":negative_squared_cross_mark:", "aliases": "" }, "2753": { "output": "2753", "name": "question mark", "alpha_code": ":question:", "aliases": "" }, "2754": { "output": "2754", "name": "white question mark", "alpha_code": ":grey_question:", "aliases": "" }, "1f6f5": { "output": "1f6f5", "name": "motor scooter", "alpha_code": ":motor_scooter:", "aliases": ":motorbike:" }, "2755": { "output": "2755", "name": "white exclamation mark", "alpha_code": ":grey_exclamation:", "aliases": "" }, "2757": { "output": "2757", "name": "exclamation mark", "alpha_code": ":exclamation:", "aliases": "" }, "2764": { "output": "2764-fe0f", "name": "red heart", "alpha_code": ":heart:", "aliases": "" }, "2795": { "output": "2795", "name": "heavy plus sign", "alpha_code": ":heavy_plus_sign:", "aliases": "" }, "2796": { "output": "2796", "name": "heavy minus sign", "alpha_code": ":heavy_minus_sign:", "aliases": "" }, "2797": { "output": "2797", "name": "heavy division sign", "alpha_code": ":heavy_division_sign:", "aliases": "" }, "27a1": { "output": "27a1-fe0f", "name": "right arrow", "alpha_code": ":arrow_right:", "aliases": "" }, "27b0": { "output": "27b0", "name": "curly loop", "alpha_code": ":curly_loop:", "aliases": "" }, "2934": { "output": "2934-fe0f", "name": "right arrow curving up", "alpha_code": ":arrow_heading_up:", "aliases": "" }, "2935": { "output": "2935-fe0f", "name": "right arrow curving down", "alpha_code": ":arrow_heading_down:", "aliases": "" }, "2b05": { "output": "2b05-fe0f", "name": "left arrow", "alpha_code": ":arrow_left:", "aliases": "" }, "2b06": { "output": "2b06-fe0f", "name": "up arrow", "alpha_code": ":arrow_up:", "aliases": "" }, "1f6f4": { "output": "1f6f4", "name": "kick scooter", "alpha_code": ":scooter:", "aliases": "" }, "2b07": { "output": "2b07-fe0f", "name": "down arrow", "alpha_code": ":arrow_down:", "aliases": "" }, "2b1b": { "output": "2b1b", "name": "black large square", "alpha_code": ":black_large_square:", "aliases": "" }, "2b1c": { "output": "2b1c", "name": "white large square", "alpha_code": ":white_large_square:", "aliases": "" }, "2b50": { "output": "2b50", "name": "white medium star", "alpha_code": ":star:", "aliases": "" }, "2b55": { "output": "2b55", "name": "heavy large circle", "alpha_code": ":o:", "aliases": "" }, "3030": { "output": "3030-fe0f", "name": "wavy dash", "alpha_code": ":wavy_dash:", "aliases": "" }, "303d": { "output": "303d-fe0f", "name": "part alternation mark", "alpha_code": ":part_alternation_mark:", "aliases": "" }, "3297": { "output": "3297-fe0f", "name": "Japanese \u201ccongratulations\u201d button", "alpha_code": ":congratulations:", "aliases": "" }, "1f6d2": { "output": "1f6d2", "name": "shopping cart", "alpha_code": ":shopping_cart:", "aliases": ":shopping_trolley:" }, "3299": { "output": "3299-fe0f", "name": "Japanese \u201csecret\u201d button", "alpha_code": ":secret:", "aliases": "" }, "1f004": { "output": "1f004", "name": "mahjong red dragon", "alpha_code": ":mahjong:", "aliases": "" }, "1f0cf": { "output": "1f0cf", "name": "joker", "alpha_code": ":black_joker:", "aliases": "" }, "1f170": { "output": "1f170-fe0f", "name": "A button (blood type)", "alpha_code": ":a:", "aliases": "" }, "1f171": { "output": "1f171-fe0f", "name": "B button (blood type)", "alpha_code": ":b:", "aliases": "" }, "1f17e": { "output": "1f17e-fe0f", "name": "O button (blood type)", "alpha_code": ":o2:", "aliases": "" }, "1f17f": { "output": "1f17f-fe0f", "name": "P button", "alpha_code": ":parking:", "aliases": "" }, "1f6d1": { "output": "1f6d1", "name": "stop sign", "alpha_code": ":octagonal_sign:", "aliases": ":stop_sign:" }, "1f18e": { "output": "1f18e", "name": "AB button (blood type)", "alpha_code": ":ab:", "aliases": "" }, "1f191": { "output": "1f191", "name": "CL button", "alpha_code": ":cl:", "aliases": "" }, "1f1fe": { "output": "1f1fe", "name": "regional indicator symbol letter y", "alpha_code": ":regional_indicator_y:", "aliases": "" }, "1f192": { "output": "1f192", "name": "COOL button", "alpha_code": ":cool:", "aliases": "" }, "1f193": { "output": "1f193", "name": "FREE button", "alpha_code": ":free:", "aliases": "" }, "1f194": { "output": "1f194", "name": "ID button", "alpha_code": ":id:", "aliases": "" }, "1f195": { "output": "1f195", "name": "NEW button", "alpha_code": ":new:", "aliases": "" }, "1f196": { "output": "1f196", "name": "NG button", "alpha_code": ":ng:", "aliases": "" }, "1f197": { "output": "1f197", "name": "OK button", "alpha_code": ":ok:", "aliases": "" }, "1f198": { "output": "1f198", "name": "SOS button", "alpha_code": ":sos:", "aliases": "" }, "1f944": { "output": "1f944", "name": "spoon", "alpha_code": ":spoon:", "aliases": "" }, "1f199": { "output": "1f199", "name": "UP! button", "alpha_code": ":up:", "aliases": "" }, "1f19a": { "output": "1f19a", "name": "VS button", "alpha_code": ":vs:", "aliases": "" }, "1f1e8-1f1f3": { "output": "1f1e8-1f1f3", "name": "China", "alpha_code": ":flag_cn:", "aliases": ":cn:" }, "1f1e9-1f1ea": { "output": "1f1e9-1f1ea", "name": "Germany", "alpha_code": ":flag_de:", "aliases": ":de:" }, "1f1ea-1f1f8": { "output": "1f1ea-1f1f8", "name": "Spain", "alpha_code": ":flag_es:", "aliases": ":es:" }, "1f1eb-1f1f7": { "output": "1f1eb-1f1f7", "name": "France", "alpha_code": ":flag_fr:", "aliases": ":fr:" }, "1f1ec-1f1e7": { "output": "1f1ec-1f1e7", "name": "United Kingdom", "alpha_code": ":flag_gb:", "aliases": ":gb:" }, "1f942": { "output": "1f942", "name": "clinking glasses", "alpha_code": ":champagne_glass:", "aliases": ":clinking_glass:" }, "1f943": { "output": "1f943", "name": "tumbler glass", "alpha_code": ":tumbler_glass:", "aliases": ":whisky:" }, "1f1ee-1f1f9": { "output": "1f1ee-1f1f9", "name": "Italy", "alpha_code": ":flag_it:", "aliases": ":it:" }, "1f1ef-1f1f5": { "output": "1f1ef-1f1f5", "name": "Japan", "alpha_code": ":flag_jp:", "aliases": ":jp:" }, "1f1f0-1f1f7": { "output": "1f1f0-1f1f7", "name": "South Korea", "alpha_code": ":flag_kr:", "aliases": ":kr:" }, "1f1fa-1f1f8": { "output": "1f1fa-1f1f8", "name": "United States", "alpha_code": ":flag_us:", "aliases": ":us:" }, "1f1f7-1f1fa": { "output": "1f1f7-1f1fa", "name": "Russia", "alpha_code": ":flag_ru:", "aliases": ":ru:" }, "1f201": { "output": "1f201", "name": "Japanese \u201chere\u201d button", "alpha_code": ":koko:", "aliases": "" }, "1f202": { "output": "1f202-fe0f", "name": "Japanese \u201cservice charge\u201d button", "alpha_code": ":sa:", "aliases": "" }, "1f21a": { "output": "1f21a", "name": "Japanese \u201cfree of charge\u201d button", "alpha_code": ":u7121:", "aliases": "" }, "1f22f": { "output": "1f22f", "name": "Japanese \u201creserved\u201d button", "alpha_code": ":u6307:", "aliases": "" }, "1f959": { "output": "1f959", "name": "stuffed flatbread", "alpha_code": ":stuffed_flatbread:", "aliases": ":stuffed_pita:" }, "1f232": { "output": "1f232", "name": "Japanese \u201cprohibited\u201d button", "alpha_code": ":u7981:", "aliases": "" }, "1f233": { "output": "1f233", "name": "Japanese \u201cvacancy\u201d button", "alpha_code": ":u7a7a:", "aliases": "" }, "1f234": { "output": "1f234", "name": "Japanese \u201cpassing grade\u201d button", "alpha_code": ":u5408:", "aliases": "" }, "1f235": { "output": "1f235", "name": "Japanese \u201cno vacancy\u201d button", "alpha_code": ":u6e80:", "aliases": "" }, "1f236": { "output": "1f236", "name": "Japanese \u201cnot free of charge\u201d button", "alpha_code": ":u6709:", "aliases": "" }, "1f958": { "output": "1f958", "name": "shallow pan of food", "alpha_code": ":shallow_pan_of_food:", "aliases": ":paella:" }, "1f237": { "output": "1f237-fe0f", "name": "Japanese \u201cmonthly amount\u201d button", "alpha_code": ":u6708:", "aliases": "" }, "1f238": { "output": "1f238", "name": "Japanese \u201capplication\u201d button", "alpha_code": ":u7533:", "aliases": "" }, "1f239": { "output": "1f239", "name": "Japanese \u201cdiscount\u201d button", "alpha_code": ":u5272:", "aliases": "" }, "1f957": { "output": "1f957", "name": "green salad", "alpha_code": ":salad:", "aliases": ":green_salad:" }, "1f23a": { "output": "1f23a", "name": "Japanese \u201copen for business\u201d button", "alpha_code": ":u55b6:", "aliases": "" }, "1f250": { "output": "1f250", "name": "Japanese \u201cbargain\u201d button", "alpha_code": ":ideograph_advantage:", "aliases": "" }, "1f251": { "output": "1f251", "name": "Japanese \u201cacceptable\u201d button", "alpha_code": ":accept:", "aliases": "" }, "1f300": { "output": "1f300", "name": "cyclone", "alpha_code": ":cyclone:", "aliases": "" }, "1f956": { "output": "1f956", "name": "baguette bread", "alpha_code": ":french_bread:", "aliases": ":baguette_bread:" }, "1f301": { "output": "1f301", "name": "foggy", "alpha_code": ":foggy:", "aliases": "" }, "1f302": { "output": "1f302", "name": "closed umbrella", "alpha_code": ":closed_umbrella:", "aliases": "" }, "1f303": { "output": "1f303", "name": "night with stars", "alpha_code": ":night_with_stars:", "aliases": "" }, "1f304": { "output": "1f304", "name": "sunrise over mountains", "alpha_code": ":sunrise_over_mountains:", "aliases": "" }, "1f305": { "output": "1f305", "name": "sunrise", "alpha_code": ":sunrise:", "aliases": "" }, "1f306": { "output": "1f306", "name": "cityscape at dusk", "alpha_code": ":city_dusk:", "aliases": "" }, "1f955": { "output": "1f955", "name": "carrot", "alpha_code": ":carrot:", "aliases": "" }, "1f307": { "output": "1f307", "name": "sunset", "alpha_code": ":city_sunset:", "aliases": ":city_sunrise:" }, "1f308": { "output": "1f308", "name": "rainbow", "alpha_code": ":rainbow:", "aliases": "" }, "1f954": { "output": "1f954", "name": "potato", "alpha_code": ":potato:", "aliases": "" }, "1f309": { "output": "1f309", "name": "bridge at night", "alpha_code": ":bridge_at_night:", "aliases": "" }, "1f30a": { "output": "1f30a", "name": "water wave", "alpha_code": ":ocean:", "aliases": "" }, "1f30b": { "output": "1f30b", "name": "volcano", "alpha_code": ":volcano:", "aliases": "" }, "1f30c": { "output": "1f30c", "name": "milky way", "alpha_code": ":milky_way:", "aliases": "" }, "1f30f": { "output": "1f30f", "name": "globe showing Asia-Australia", "alpha_code": ":earth_asia:", "aliases": "" }, "1f311": { "output": "1f311", "name": "new moon", "alpha_code": ":new_moon:", "aliases": "" }, "1f953": { "output": "1f953", "name": "bacon", "alpha_code": ":bacon:", "aliases": "" }, "1f313": { "output": "1f313", "name": "first quarter moon", "alpha_code": ":first_quarter_moon:", "aliases": "" }, "1f314": { "output": "1f314", "name": "waxing gibbous moon", "alpha_code": ":waxing_gibbous_moon:", "aliases": "" }, "1f315": { "output": "1f315", "name": "full moon", "alpha_code": ":full_moon:", "aliases": "" }, "1f319": { "output": "1f319", "name": "crescent moon", "alpha_code": ":crescent_moon:", "aliases": "" }, "1f31b": { "output": "1f31b", "name": "first quarter moon with face", "alpha_code": ":first_quarter_moon_with_face:", "aliases": "" }, "1f31f": { "output": "1f31f", "name": "glowing star", "alpha_code": ":star2:", "aliases": "" }, "1f952": { "output": "1f952", "name": "cucumber", "alpha_code": ":cucumber:", "aliases": "" }, "1f320": { "output": "1f320", "name": "shooting star", "alpha_code": ":stars:", "aliases": "" }, "1f330": { "output": "1f330", "name": "chestnut", "alpha_code": ":chestnut:", "aliases": "" }, "1f951": { "output": "1f951", "name": "avocado", "alpha_code": ":avocado:", "aliases": "" }, "1f331": { "output": "1f331", "name": "seedling", "alpha_code": ":seedling:", "aliases": "" }, "1f334": { "output": "1f334", "name": "palm tree", "alpha_code": ":palm_tree:", "aliases": "" }, "1f335": { "output": "1f335", "name": "cactus", "alpha_code": ":cactus:", "aliases": "" }, "1f337": { "output": "1f337", "name": "tulip", "alpha_code": ":tulip:", "aliases": "" }, "1f338": { "output": "1f338", "name": "cherry blossom", "alpha_code": ":cherry_blossom:", "aliases": "" }, "1f339": { "output": "1f339", "name": "rose", "alpha_code": ":rose:", "aliases": "" }, "1f33a": { "output": "1f33a", "name": "hibiscus", "alpha_code": ":hibiscus:", "aliases": "" }, "1f33b": { "output": "1f33b", "name": "sunflower", "alpha_code": ":sunflower:", "aliases": "" }, "1f33c": { "output": "1f33c", "name": "blossom", "alpha_code": ":blossom:", "aliases": "" }, "1f33d": { "output": "1f33d", "name": "ear of corn", "alpha_code": ":corn:", "aliases": "" }, "1f950": { "output": "1f950", "name": "croissant", "alpha_code": ":croissant:", "aliases": "" }, "1f33e": { "output": "1f33e", "name": "sheaf of rice", "alpha_code": ":ear_of_rice:", "aliases": "" }, "1f33f": { "output": "1f33f", "name": "herb", "alpha_code": ":herb:", "aliases": "" }, "1f340": { "output": "1f340", "name": "four leaf clover", "alpha_code": ":four_leaf_clover:", "aliases": "" }, "1f341": { "output": "1f341", "name": "maple leaf", "alpha_code": ":maple_leaf:", "aliases": "" }, "1f342": { "output": "1f342", "name": "fallen leaf", "alpha_code": ":fallen_leaf:", "aliases": "" }, "1f343": { "output": "1f343", "name": "leaf fluttering in wind", "alpha_code": ":leaves:", "aliases": "" }, "1f344": { "output": "1f344", "name": "mushroom", "alpha_code": ":mushroom:", "aliases": "" }, "1f345": { "output": "1f345", "name": "tomato", "alpha_code": ":tomato:", "aliases": "" }, "1f346": { "output": "1f346", "name": "eggplant", "alpha_code": ":eggplant:", "aliases": "" }, "1f347": { "output": "1f347", "name": "grapes", "alpha_code": ":grapes:", "aliases": "" }, "1f348": { "output": "1f348", "name": "melon", "alpha_code": ":melon:", "aliases": "" }, "1f349": { "output": "1f349", "name": "watermelon", "alpha_code": ":watermelon:", "aliases": "" }, "1f34a": { "output": "1f34a", "name": "tangerine", "alpha_code": ":tangerine:", "aliases": "" }, "1f940": { "output": "1f940", "name": "wilted flower", "alpha_code": ":wilted_rose:", "aliases": ":wilted_flower:" }, "1f34c": { "output": "1f34c", "name": "banana", "alpha_code": ":banana:", "aliases": "" }, "1f34d": { "output": "1f34d", "name": "pineapple", "alpha_code": ":pineapple:", "aliases": "" }, "1f34e": { "output": "1f34e", "name": "red apple", "alpha_code": ":apple:", "aliases": "" }, "1f34f": { "output": "1f34f", "name": "green apple", "alpha_code": ":green_apple:", "aliases": "" }, "1f351": { "output": "1f351", "name": "peach", "alpha_code": ":peach:", "aliases": "" }, "1f352": { "output": "1f352", "name": "cherries", "alpha_code": ":cherries:", "aliases": "" }, "1f353": { "output": "1f353", "name": "strawberry", "alpha_code": ":strawberry:", "aliases": "" }, "1f98f": { "output": "1f98f", "name": "rhinoceros", "alpha_code": ":rhino:", "aliases": ":rhinoceros:" }, "1f354": { "output": "1f354", "name": "hamburger", "alpha_code": ":hamburger:", "aliases": "" }, "1f355": { "output": "1f355", "name": "pizza", "alpha_code": ":pizza:", "aliases": "" }, "1f356": { "output": "1f356", "name": "meat on bone", "alpha_code": ":meat_on_bone:", "aliases": "" }, "1f98e": { "output": "1f98e", "name": "lizard", "alpha_code": ":lizard:", "aliases": "" }, "1f357": { "output": "1f357", "name": "poultry leg", "alpha_code": ":poultry_leg:", "aliases": "" }, "1f358": { "output": "1f358", "name": "rice cracker", "alpha_code": ":rice_cracker:", "aliases": "" }, "1f359": { "output": "1f359", "name": "rice ball", "alpha_code": ":rice_ball:", "aliases": "" }, "1f98d": { "output": "1f98d", "name": "gorilla", "alpha_code": ":gorilla:", "aliases": "" }, "1f35a": { "output": "1f35a", "name": "cooked rice", "alpha_code": ":rice:", "aliases": "" }, "1f35b": { "output": "1f35b", "name": "curry rice", "alpha_code": ":curry:", "aliases": "" }, "1f98c": { "output": "1f98c", "name": "deer", "alpha_code": ":deer:", "aliases": "" }, "1f35c": { "output": "1f35c", "name": "steaming bowl", "alpha_code": ":ramen:", "aliases": "" }, "1f35d": { "output": "1f35d", "name": "spaghetti", "alpha_code": ":spaghetti:", "aliases": "" }, "1f35e": { "output": "1f35e", "name": "bread", "alpha_code": ":bread:", "aliases": "" }, "1f35f": { "output": "1f35f", "name": "french fries", "alpha_code": ":fries:", "aliases": "" }, "1f98b": { "output": "1f98b", "name": "butterfly", "alpha_code": ":butterfly:", "aliases": "" }, "1f360": { "output": "1f360", "name": "roasted sweet potato", "alpha_code": ":sweet_potato:", "aliases": "" }, "1f361": { "output": "1f361", "name": "dango", "alpha_code": ":dango:", "aliases": "" }, "1f98a": { "output": "1f98a", "name": "fox face", "alpha_code": ":fox:", "aliases": ":fox_face:" }, "1f362": { "output": "1f362", "name": "oden", "alpha_code": ":oden:", "aliases": "" }, "1f363": { "output": "1f363", "name": "sushi", "alpha_code": ":sushi:", "aliases": "" }, "1f989": { "output": "1f989", "name": "owl", "alpha_code": ":owl:", "aliases": "" }, "1f364": { "output": "1f364", "name": "fried shrimp", "alpha_code": ":fried_shrimp:", "aliases": "" }, "1f365": { "output": "1f365", "name": "fish cake with swirl", "alpha_code": ":fish_cake:", "aliases": "" }, "1f988": { "output": "1f988", "name": "shark", "alpha_code": ":shark:", "aliases": "" }, "1f366": { "output": "1f366", "name": "soft ice cream", "alpha_code": ":icecream:", "aliases": "" }, "1f987": { "output": "1f987", "name": "bat", "alpha_code": ":bat:", "aliases": "" }, "1f367": { "output": "1f367", "name": "shaved ice", "alpha_code": ":shaved_ice:", "aliases": "" }, "1f1fd": { "output": "1f1fd", "name": "regional indicator symbol letter x", "alpha_code": ":regional_indicator_x:", "aliases": "" }, "1f368": { "output": "1f368", "name": "ice cream", "alpha_code": ":ice_cream:", "aliases": "" }, "1f986": { "output": "1f986", "name": "duck", "alpha_code": ":duck:", "aliases": "" }, "1f369": { "output": "1f369", "name": "doughnut", "alpha_code": ":doughnut:", "aliases": "" }, "1f985": { "output": "1f985", "name": "eagle", "alpha_code": ":eagle:", "aliases": "" }, "1f36a": { "output": "1f36a", "name": "cookie", "alpha_code": ":cookie:", "aliases": "" }, "1f5a4": { "output": "1f5a4", "name": "black heart", "alpha_code": ":black_heart:", "aliases": "" }, "1f36b": { "output": "1f36b", "name": "chocolate bar", "alpha_code": ":chocolate_bar:", "aliases": "" }, "1f36c": { "output": "1f36c", "name": "candy", "alpha_code": ":candy:", "aliases": "" }, "1f36d": { "output": "1f36d", "name": "lollipop", "alpha_code": ":lollipop:", "aliases": "" }, "1f36e": { "output": "1f36e", "name": "custard", "alpha_code": ":custard:", "aliases": ":pudding:|:flan:" }, "1f36f": { "output": "1f36f", "name": "honey pot", "alpha_code": ":honey_pot:", "aliases": "" }, "1f91e": { "output": "1f91e", "name": "crossed fingers", "alpha_code": ":fingers_crossed:", "aliases": ":hand_with_index_and_middle_finger_crossed:" }, "1f370": { "output": "1f370", "name": "shortcake", "alpha_code": ":cake:", "aliases": "" }, "1f371": { "output": "1f371", "name": "bento box", "alpha_code": ":bento:", "aliases": "" }, "1f372": { "output": "1f372", "name": "pot of food", "alpha_code": ":stew:", "aliases": "" }, "1f91d": { "output": "1f91d", "name": "handshake", "alpha_code": ":handshake:", "aliases": ":shaking_hands:" }, "1f373": { "output": "1f373", "name": "cooking", "alpha_code": ":cooking:", "aliases": "" }, "1f374": { "output": "1f374", "name": "fork and knife", "alpha_code": ":fork_and_knife:", "aliases": "" }, "1f375": { "output": "1f375", "name": "teacup without handle", "alpha_code": ":tea:", "aliases": "" }, "1f376": { "output": "1f376", "name": "sake", "alpha_code": ":sake:", "aliases": "" }, "1f377": { "output": "1f377", "name": "wine glass", "alpha_code": ":wine_glass:", "aliases": "" }, "1f378": { "output": "1f378", "name": "cocktail glass", "alpha_code": ":cocktail:", "aliases": "" }, "1f379": { "output": "1f379", "name": "tropical drink", "alpha_code": ":tropical_drink:", "aliases": "" }, "1f37a": { "output": "1f37a", "name": "beer mug", "alpha_code": ":beer:", "aliases": "" }, "1f37b": { "output": "1f37b", "name": "clinking beer mugs", "alpha_code": ":beers:", "aliases": "" }, "1f380": { "output": "1f380", "name": "ribbon", "alpha_code": ":ribbon:", "aliases": "" }, "1f381": { "output": "1f381", "name": "wrapped gift", "alpha_code": ":gift:", "aliases": "" }, "1f382": { "output": "1f382", "name": "birthday cake", "alpha_code": ":birthday:", "aliases": "" }, "1f383": { "output": "1f383", "name": "jack-o-lantern", "alpha_code": ":jack_o_lantern:", "aliases": "" }, "1f91b": { "output": "1f91b", "name": "left-facing fist", "alpha_code": ":left_facing_fist:", "aliases": ":left_fist:" }, "1f91c": { "output": "1f91c", "name": "right-facing fist", "alpha_code": ":right_facing_fist:", "aliases": ":right_fist:" }, "1f384": { "output": "1f384", "name": "Christmas tree", "alpha_code": ":christmas_tree:", "aliases": "" }, "1f385": { "output": "1f385", "name": "Santa Claus", "alpha_code": ":santa:", "aliases": "" }, "1f386": { "output": "1f386", "name": "fireworks", "alpha_code": ":fireworks:", "aliases": "" }, "1f91a": { "output": "1f91a", "name": "raised back of hand", "alpha_code": ":raised_back_of_hand:", "aliases": ":back_of_hand:" }, "1f387": { "output": "1f387", "name": "sparkler", "alpha_code": ":sparkler:", "aliases": "" }, "1f388": { "output": "1f388", "name": "balloon", "alpha_code": ":balloon:", "aliases": "" }, "1f389": { "output": "1f389", "name": "party popper", "alpha_code": ":tada:", "aliases": "" }, "1f38a": { "output": "1f38a", "name": "confetti ball", "alpha_code": ":confetti_ball:", "aliases": "" }, "1f38b": { "output": "1f38b", "name": "tanabata tree", "alpha_code": ":tanabata_tree:", "aliases": "" }, "1f38c": { "output": "1f38c", "name": "crossed flags", "alpha_code": ":crossed_flags:", "aliases": "" }, "1f919": { "output": "1f919", "name": "call me hand", "alpha_code": ":call_me:", "aliases": ":call_me_hand:" }, "1f38d": { "output": "1f38d", "name": "pine decoration", "alpha_code": ":bamboo:", "aliases": "" }, "1f57a": { "output": "1f57a", "name": "man dancing", "alpha_code": ":man_dancing:", "aliases": ":male_dancer:" }, "1f38e": { "output": "1f38e", "name": "Japanese dolls", "alpha_code": ":dolls:", "aliases": "" }, "1f933": { "output": "1f933", "name": "selfie", "alpha_code": ":selfie:", "aliases": "" }, "1f38f": { "output": "1f38f", "name": "carp streamer", "alpha_code": ":flags:", "aliases": "" }, "1f930": { "output": "1f930", "name": "pregnant woman", "alpha_code": ":pregnant_woman:", "aliases": ":expecting_woman:" }, "1f390": { "output": "1f390", "name": "wind chime", "alpha_code": ":wind_chime:", "aliases": "" }, "1f926": { "output": "1f926", "name": "person facepalming", "alpha_code": ":person_facepalming:", "aliases": ":face_palm:|:facepalm:" }, "1f937": { "output": "1f937", "name": "person shrugging", "alpha_code": ":person_shrugging:", "aliases": ":shrug:" }, "1f391": { "output": "1f391", "name": "moon viewing ceremony", "alpha_code": ":rice_scene:", "aliases": "" }, "1f392": { "output": "1f392", "name": "school backpack", "alpha_code": ":school_satchel:", "aliases": "" }, "1f393": { "output": "1f393", "name": "graduation cap", "alpha_code": ":mortar_board:", "aliases": "" }, "1f3a0": { "output": "1f3a0", "name": "carousel horse", "alpha_code": ":carousel_horse:", "aliases": "" }, "1f3a1": { "output": "1f3a1", "name": "ferris wheel", "alpha_code": ":ferris_wheel:", "aliases": "" }, "1f3a2": { "output": "1f3a2", "name": "roller coaster", "alpha_code": ":roller_coaster:", "aliases": "" }, "1f3a3": { "output": "1f3a3", "name": "fishing pole", "alpha_code": ":fishing_pole_and_fish:", "aliases": "" }, "1f3a4": { "output": "1f3a4", "name": "microphone", "alpha_code": ":microphone:", "aliases": "" }, "0023": { "output": "0023-fe0f", "name": "pound symbol", "alpha_code": ":pound_symbol:", "aliases": "" }, "1f3a5": { "output": "1f3a5", "name": "movie camera", "alpha_code": ":movie_camera:", "aliases": "" }, "1f3a6": { "output": "1f3a6", "name": "cinema", "alpha_code": ":cinema:", "aliases": "" }, "1f3a7": { "output": "1f3a7", "name": "headphone", "alpha_code": ":headphones:", "aliases": "" }, "1f936": { "output": "1f936", "name": "Mrs. Claus", "alpha_code": ":mrs_claus:", "aliases": ":mother_christmas:" }, "1f3a8": { "output": "1f3a8", "name": "artist palette", "alpha_code": ":art:", "aliases": "" }, "1f935": { "output": "1f935", "name": "man in tuxedo", "alpha_code": ":man_in_tuxedo:", "aliases": "" }, "1f3a9": { "output": "1f3a9", "name": "top hat", "alpha_code": ":tophat:", "aliases": "" }, "1f3aa": { "output": "1f3aa", "name": "circus tent", "alpha_code": ":circus_tent:", "aliases": "" }, "1f934": { "output": "1f934", "name": "prince", "alpha_code": ":prince:", "aliases": "" }, "1f3ab": { "output": "1f3ab", "name": "ticket", "alpha_code": ":ticket:", "aliases": "" }, "1f3ac": { "output": "1f3ac", "name": "clapper board", "alpha_code": ":clapper:", "aliases": "" }, "1f3ad": { "output": "1f3ad", "name": "performing arts", "alpha_code": ":performing_arts:", "aliases": "" }, "1f927": { "output": "1f927", "name": "sneezing face", "alpha_code": ":sneezing_face:", "aliases": ":sneeze:" }, "1f3ae": { "output": "1f3ae", "name": "video game", "alpha_code": ":video_game:", "aliases": "" }, "1f3af": { "output": "1f3af", "name": "direct hit", "alpha_code": ":dart:", "aliases": "" }, "1f3b0": { "output": "1f3b0", "name": "slot machine", "alpha_code": ":slot_machine:", "aliases": "" }, "1f3b1": { "output": "1f3b1", "name": "pool 8 ball", "alpha_code": ":8ball:", "aliases": "" }, "1f3b2": { "output": "1f3b2", "name": "game die", "alpha_code": ":game_die:", "aliases": "" }, "1f3b3": { "output": "1f3b3", "name": "bowling", "alpha_code": ":bowling:", "aliases": "" }, "1f3b4": { "output": "1f3b4", "name": "flower playing cards", "alpha_code": ":flower_playing_cards:", "aliases": "" }, "1f925": { "output": "1f925", "name": "lying face", "alpha_code": ":lying_face:", "aliases": ":liar:" }, "1f3b5": { "output": "1f3b5", "name": "musical note", "alpha_code": ":musical_note:", "aliases": "" }, "1f3b6": { "output": "1f3b6", "name": "musical notes", "alpha_code": ":notes:", "aliases": "" }, "1f3b7": { "output": "1f3b7", "name": "saxophone", "alpha_code": ":saxophone:", "aliases": "" }, "1f924": { "output": "1f924", "name": "drooling face", "alpha_code": ":drooling_face:", "aliases": ":drool:" }, "1f3b8": { "output": "1f3b8", "name": "guitar", "alpha_code": ":guitar:", "aliases": "" }, "1f3b9": { "output": "1f3b9", "name": "musical keyboard", "alpha_code": ":musical_keyboard:", "aliases": "" }, "1f3ba": { "output": "1f3ba", "name": "trumpet", "alpha_code": ":trumpet:", "aliases": "" }, "1f923": { "output": "1f923", "name": "rolling on the floor laughing", "alpha_code": ":rofl:", "aliases": ":rolling_on_the_floor_laughing:" }, "1f3bb": { "output": "1f3bb", "name": "violin", "alpha_code": ":violin:", "aliases": "" }, "1f3bc": { "output": "1f3bc", "name": "musical score", "alpha_code": ":musical_score:", "aliases": "" }, "1f3bd": { "output": "1f3bd", "name": "running shirt", "alpha_code": ":running_shirt_with_sash:", "aliases": "" }, "1f922": { "output": "1f922", "name": "nauseated face", "alpha_code": ":nauseated_face:", "aliases": ":sick:" }, "1f3be": { "output": "1f3be", "name": "tennis", "alpha_code": ":tennis:", "aliases": "" }, "1f3bf": { "output": "1f3bf", "name": "skis", "alpha_code": ":ski:", "aliases": "" }, "1f3c0": { "output": "1f3c0", "name": "basketball", "alpha_code": ":basketball:", "aliases": "" }, "1f3c1": { "output": "1f3c1", "name": "chequered flag", "alpha_code": ":checkered_flag:", "aliases": "" }, "1f921": { "output": "1f921", "name": "clown face", "alpha_code": ":clown:", "aliases": ":clown_face:" }, "1f3c2": { "output": "1f3c2", "name": "snowboarder", "alpha_code": ":snowboarder:", "aliases": "" }, "1f3c3": { "output": "1f3c3", "name": "person running", "alpha_code": ":person_running:", "aliases": ":runner:" }, "1f3c4": { "output": "1f3c4", "name": "person surfing", "alpha_code": ":person_surfing:", "aliases": ":surfer:" }, "1f3c6": { "output": "1f3c6", "name": "trophy", "alpha_code": ":trophy:", "aliases": "" }, "1f3c8": { "output": "1f3c8", "name": "american football", "alpha_code": ":football:", "aliases": "" }, "1f3ca": { "output": "1f3ca", "name": "person swimming", "alpha_code": ":person_swimming:", "aliases": ":swimmer:" }, "1f3e0": { "output": "1f3e0", "name": "house", "alpha_code": ":house:", "aliases": "" }, "1f3e1": { "output": "1f3e1", "name": "house with garden", "alpha_code": ":house_with_garden:", "aliases": "" }, "1f3e2": { "output": "1f3e2", "name": "office building", "alpha_code": ":office:", "aliases": "" }, "1f3e3": { "output": "1f3e3", "name": "Japanese post office", "alpha_code": ":post_office:", "aliases": "" }, "1f3e5": { "output": "1f3e5", "name": "hospital", "alpha_code": ":hospital:", "aliases": "" }, "1f3e6": { "output": "1f3e6", "name": "bank", "alpha_code": ":bank:", "aliases": "" }, "1f3e7": { "output": "1f3e7", "name": "ATM sign", "alpha_code": ":atm:", "aliases": "" }, "1f3e8": { "output": "1f3e8", "name": "hotel", "alpha_code": ":hotel:", "aliases": "" }, "1f3e9": { "output": "1f3e9", "name": "love hotel", "alpha_code": ":love_hotel:", "aliases": "" }, "1f3ea": { "output": "1f3ea", "name": "convenience store", "alpha_code": ":convenience_store:", "aliases": "" }, "1f3eb": { "output": "1f3eb", "name": "school", "alpha_code": ":school:", "aliases": "" }, "1f3ec": { "output": "1f3ec", "name": "department store", "alpha_code": ":department_store:", "aliases": "" }, "1f920": { "output": "1f920", "name": "cowboy hat face", "alpha_code": ":cowboy:", "aliases": ":face_with_cowboy_hat:" }, "1f3ed": { "output": "1f3ed", "name": "factory", "alpha_code": ":factory:", "aliases": "" }, "1f3ee": { "output": "1f3ee", "name": "red paper lantern", "alpha_code": ":izakaya_lantern:", "aliases": "" }, "1f3ef": { "output": "1f3ef", "name": "Japanese castle", "alpha_code": ":japanese_castle:", "aliases": "" }, "1f3f0": { "output": "1f3f0", "name": "castle", "alpha_code": ":european_castle:", "aliases": "" }, "1f40c": { "output": "1f40c", "name": "snail", "alpha_code": ":snail:", "aliases": "" }, "1f40d": { "output": "1f40d", "name": "snake", "alpha_code": ":snake:", "aliases": "" }, "1f40e": { "output": "1f40e", "name": "horse", "alpha_code": ":racehorse:", "aliases": "" }, "1f411": { "output": "1f411", "name": "ewe", "alpha_code": ":sheep:", "aliases": "" }, "1f412": { "output": "1f412", "name": "monkey", "alpha_code": ":monkey:", "aliases": "" }, "1f414": { "output": "1f414", "name": "chicken", "alpha_code": ":chicken:", "aliases": "" }, "1f417": { "output": "1f417", "name": "boar", "alpha_code": ":boar:", "aliases": "" }, "1f418": { "output": "1f418", "name": "elephant", "alpha_code": ":elephant:", "aliases": "" }, "1f419": { "output": "1f419", "name": "octopus", "alpha_code": ":octopus:", "aliases": "" }, "1f41a": { "output": "1f41a", "name": "spiral shell", "alpha_code": ":shell:", "aliases": "" }, "1f934-1f3fb": { "output": "1f934-1f3fb", "name": "prince: light skin tone", "alpha_code": ":prince_tone1:", "aliases": "" }, "1f41b": { "output": "1f41b", "name": "bug", "alpha_code": ":bug:", "aliases": "" }, "1f41c": { "output": "1f41c", "name": "ant", "alpha_code": ":ant:", "aliases": "" }, "1f41d": { "output": "1f41d", "name": "honeybee", "alpha_code": ":bee:", "aliases": "" }, "1f41e": { "output": "1f41e", "name": "lady beetle", "alpha_code": ":beetle:", "aliases": "" }, "1f41f": { "output": "1f41f", "name": "fish", "alpha_code": ":fish:", "aliases": "" }, "1f420": { "output": "1f420", "name": "tropical fish", "alpha_code": ":tropical_fish:", "aliases": "" }, "1f421": { "output": "1f421", "name": "blowfish", "alpha_code": ":blowfish:", "aliases": "" }, "1f422": { "output": "1f422", "name": "turtle", "alpha_code": ":turtle:", "aliases": "" }, "1f423": { "output": "1f423", "name": "hatching chick", "alpha_code": ":hatching_chick:", "aliases": "" }, "1f424": { "output": "1f424", "name": "baby chick", "alpha_code": ":baby_chick:", "aliases": "" }, "1f425": { "output": "1f425", "name": "front-facing baby chick", "alpha_code": ":hatched_chick:", "aliases": "" }, "1f426": { "output": "1f426", "name": "bird", "alpha_code": ":bird:", "aliases": "" }, "1f427": { "output": "1f427", "name": "penguin", "alpha_code": ":penguin:", "aliases": "" }, "1f428": { "output": "1f428", "name": "koala", "alpha_code": ":koala:", "aliases": "" }, "1f429": { "output": "1f429", "name": "poodle", "alpha_code": ":poodle:", "aliases": "" }, "1f42b": { "output": "1f42b", "name": "two-hump camel", "alpha_code": ":camel:", "aliases": "" }, "1f42c": { "output": "1f42c", "name": "dolphin", "alpha_code": ":dolphin:", "aliases": "" }, "1f42d": { "output": "1f42d", "name": "mouse face", "alpha_code": ":mouse:", "aliases": "" }, "1f42e": { "output": "1f42e", "name": "cow face", "alpha_code": ":cow:", "aliases": "" }, "1f42f": { "output": "1f42f", "name": "tiger face", "alpha_code": ":tiger:", "aliases": "" }, "1f430": { "output": "1f430", "name": "rabbit face", "alpha_code": ":rabbit:", "aliases": "" }, "1f431": { "output": "1f431", "name": "cat face", "alpha_code": ":cat:", "aliases": "" }, "1f432": { "output": "1f432", "name": "dragon face", "alpha_code": ":dragon_face:", "aliases": "" }, "1f433": { "output": "1f433", "name": "spouting whale", "alpha_code": ":whale:", "aliases": "" }, "1f434": { "output": "1f434", "name": "horse face", "alpha_code": ":horse:", "aliases": "" }, "1f435": { "output": "1f435", "name": "monkey face", "alpha_code": ":monkey_face:", "aliases": "" }, "1f436": { "output": "1f436", "name": "dog face", "alpha_code": ":dog:", "aliases": "" }, "1f437": { "output": "1f437", "name": "pig face", "alpha_code": ":pig:", "aliases": "" }, "1f438": { "output": "1f438", "name": "frog face", "alpha_code": ":frog:", "aliases": "" }, "1f439": { "output": "1f439", "name": "hamster face", "alpha_code": ":hamster:", "aliases": "" }, "1f43a": { "output": "1f43a", "name": "wolf face", "alpha_code": ":wolf:", "aliases": "" }, "1f43b": { "output": "1f43b", "name": "bear face", "alpha_code": ":bear:", "aliases": "" }, "1f43c": { "output": "1f43c", "name": "panda face", "alpha_code": ":panda_face:", "aliases": "" }, "1f43d": { "output": "1f43d", "name": "pig nose", "alpha_code": ":pig_nose:", "aliases": "" }, "1f43e": { "output": "1f43e", "name": "paw prints", "alpha_code": ":feet:", "aliases": ":paw_prints:" }, "1f934-1f3fc": { "output": "1f934-1f3fc", "name": "prince: medium-light skin tone", "alpha_code": ":prince_tone2:", "aliases": "" }, "1f440": { "output": "1f440", "name": "eyes", "alpha_code": ":eyes:", "aliases": "" }, "1f442": { "output": "1f442", "name": "ear", "alpha_code": ":ear:", "aliases": "" }, "1f443": { "output": "1f443", "name": "nose", "alpha_code": ":nose:", "aliases": "" }, "1f444": { "output": "1f444", "name": "mouth", "alpha_code": ":lips:", "aliases": "" }, "1f445": { "output": "1f445", "name": "tongue", "alpha_code": ":tongue:", "aliases": "" }, "1f934-1f3fd": { "output": "1f934-1f3fd", "name": "prince: medium skin tone", "alpha_code": ":prince_tone3:", "aliases": "" }, "1f446": { "output": "1f446", "name": "backhand index pointing up", "alpha_code": ":point_up_2:", "aliases": "" }, "1f447": { "output": "1f447", "name": "backhand index pointing down", "alpha_code": ":point_down:", "aliases": "" }, "1f448": { "output": "1f448", "name": "backhand index pointing left", "alpha_code": ":point_left:", "aliases": "" }, "1f449": { "output": "1f449", "name": "backhand index pointing right", "alpha_code": ":point_right:", "aliases": "" }, "1f44a": { "output": "1f44a", "name": "oncoming fist", "alpha_code": ":punch:", "aliases": "" }, "1f44b": { "output": "1f44b", "name": "waving hand", "alpha_code": ":wave:", "aliases": "" }, "1f44c": { "output": "1f44c", "name": "OK hand", "alpha_code": ":ok_hand:", "aliases": "" }, "1f44d": { "output": "1f44d", "name": "thumbs up", "alpha_code": ":thumbsup:", "aliases": ":+1:|:thumbup:" }, "1f44e": { "output": "1f44e", "name": "thumbs down", "alpha_code": ":thumbsdown:", "aliases": ":-1:|:thumbdown:" }, "1f44f": { "output": "1f44f", "name": "clapping hands", "alpha_code": ":clap:", "aliases": "" }, "1f450": { "output": "1f450", "name": "open hands", "alpha_code": ":open_hands:", "aliases": "" }, "1f451": { "output": "1f451", "name": "crown", "alpha_code": ":crown:", "aliases": "" }, "1f452": { "output": "1f452", "name": "woman\u2019s hat", "alpha_code": ":womans_hat:", "aliases": "" }, "1f453": { "output": "1f453", "name": "glasses", "alpha_code": ":eyeglasses:", "aliases": "" }, "1f454": { "output": "1f454", "name": "necktie", "alpha_code": ":necktie:", "aliases": "" }, "1f455": { "output": "1f455", "name": "t-shirt", "alpha_code": ":shirt:", "aliases": "" }, "1f934-1f3fe": { "output": "1f934-1f3fe", "name": "prince: medium-dark skin tone", "alpha_code": ":prince_tone4:", "aliases": "" }, "1f456": { "output": "1f456", "name": "jeans", "alpha_code": ":jeans:", "aliases": "" }, "1f457": { "output": "1f457", "name": "dress", "alpha_code": ":dress:", "aliases": "" }, "1f458": { "output": "1f458", "name": "kimono", "alpha_code": ":kimono:", "aliases": "" }, "1f934-1f3ff": { "output": "1f934-1f3ff", "name": "prince: dark skin tone", "alpha_code": ":prince_tone5:", "aliases": "" }, "1f459": { "output": "1f459", "name": "bikini", "alpha_code": ":bikini:", "aliases": "" }, "1f45a": { "output": "1f45a", "name": "woman\u2019s clothes", "alpha_code": ":womans_clothes:", "aliases": "" }, "1f45b": { "output": "1f45b", "name": "purse", "alpha_code": ":purse:", "aliases": "" }, "1f45c": { "output": "1f45c", "name": "handbag", "alpha_code": ":handbag:", "aliases": "" }, "1f45d": { "output": "1f45d", "name": "clutch bag", "alpha_code": ":pouch:", "aliases": "" }, "1f45e": { "output": "1f45e", "name": "man\u2019s shoe", "alpha_code": ":mans_shoe:", "aliases": "" }, "1f45f": { "output": "1f45f", "name": "running shoe", "alpha_code": ":athletic_shoe:", "aliases": "" }, "1f460": { "output": "1f460", "name": "high-heeled shoe", "alpha_code": ":high_heel:", "aliases": "" }, "1f461": { "output": "1f461", "name": "woman\u2019s sandal", "alpha_code": ":sandal:", "aliases": "" }, "1f462": { "output": "1f462", "name": "woman\u2019s boot", "alpha_code": ":boot:", "aliases": "" }, "1f463": { "output": "1f463", "name": "footprints", "alpha_code": ":footprints:", "aliases": "" }, "1f464": { "output": "1f464", "name": "bust in silhouette", "alpha_code": ":bust_in_silhouette:", "aliases": "" }, "1f936-1f3fb": { "output": "1f936-1f3fb", "name": "Mrs. Claus: light skin tone", "alpha_code": ":mrs_claus_tone1:", "aliases": ":mother_christmas_tone1:" }, "1f466": { "output": "1f466", "name": "boy", "alpha_code": ":boy:", "aliases": "" }, "1f467": { "output": "1f467", "name": "girl", "alpha_code": ":girl:", "aliases": "" }, "1f468": { "output": "1f468", "name": "man", "alpha_code": ":man:", "aliases": "" }, "1f469": { "output": "1f469", "name": "woman", "alpha_code": ":woman:", "aliases": "" }, "1f46a": { "output": "1f46a", "name": "family", "alpha_code": ":family:", "aliases": "" }, "1f46b": { "output": "1f46b", "name": "man and woman holding hands", "alpha_code": ":couple:", "aliases": "" }, "1f46e": { "output": "1f46e", "name": "police officer", "alpha_code": ":police_officer:", "aliases": ":cop:" }, "1f46f": { "output": "1f46f", "name": "people with bunny ears partying", "alpha_code": ":people_with_bunny_ears_partying:", "aliases": ":dancers:" }, "1f470": { "output": "1f470", "name": "bride with veil", "alpha_code": ":bride_with_veil:", "aliases": "" }, "1f471": { "output": "1f471", "name": "blond-haired person", "alpha_code": ":blond_haired_person:", "aliases": ":person_with_blond_hair:" }, "1f472": { "output": "1f472", "name": "man with Chinese cap", "alpha_code": ":man_with_chinese_cap:", "aliases": ":man_with_gua_pi_mao:" }, "1f473": { "output": "1f473", "name": "person wearing turban", "alpha_code": ":person_wearing_turban:", "aliases": ":man_with_turban:" }, "1f474": { "output": "1f474", "name": "old man", "alpha_code": ":older_man:", "aliases": "" }, "1f475": { "output": "1f475", "name": "old woman", "alpha_code": ":older_woman:", "aliases": ":grandma:" }, "1f476": { "output": "1f476", "name": "baby", "alpha_code": ":baby:", "aliases": "" }, "1f477": { "output": "1f477", "name": "construction worker", "alpha_code": ":construction_worker:", "aliases": "" }, "1f478": { "output": "1f478", "name": "princess", "alpha_code": ":princess:", "aliases": "" }, "1f479": { "output": "1f479", "name": "ogre", "alpha_code": ":japanese_ogre:", "aliases": "" }, "1f936-1f3fc": { "output": "1f936-1f3fc", "name": "Mrs. Claus: medium-light skin tone", "alpha_code": ":mrs_claus_tone2:", "aliases": ":mother_christmas_tone2:" }, "1f47a": { "output": "1f47a", "name": "goblin", "alpha_code": ":japanese_goblin:", "aliases": "" }, "1f47b": { "output": "1f47b", "name": "ghost", "alpha_code": ":ghost:", "aliases": "" }, "1f47c": { "output": "1f47c", "name": "baby angel", "alpha_code": ":angel:", "aliases": "" }, "1f47d": { "output": "1f47d", "name": "alien", "alpha_code": ":alien:", "aliases": "" }, "1f47e": { "output": "1f47e", "name": "alien monster", "alpha_code": ":space_invader:", "aliases": "" }, "1f936-1f3fd": { "output": "1f936-1f3fd", "name": "Mrs. Claus: medium skin tone", "alpha_code": ":mrs_claus_tone3:", "aliases": ":mother_christmas_tone3:" }, "1f47f": { "output": "1f47f", "name": "angry face with horns", "alpha_code": ":imp:", "aliases": "" }, "1f480": { "output": "1f480", "name": "skull", "alpha_code": ":skull:", "aliases": ":skeleton:" }, "1f4c7": { "output": "1f4c7", "name": "card index", "alpha_code": ":card_index:", "aliases": "" }, "1f481": { "output": "1f481", "name": "person tipping hand", "alpha_code": ":person_tipping_hand:", "aliases": ":information_desk_person:" }, "1f482": { "output": "1f482", "name": "guard", "alpha_code": ":guard:", "aliases": ":guardsman:" }, "1f483": { "output": "1f483", "name": "woman dancing", "alpha_code": ":dancer:", "aliases": "" }, "1f484": { "output": "1f484", "name": "lipstick", "alpha_code": ":lipstick:", "aliases": "" }, "1f485": { "output": "1f485", "name": "nail polish", "alpha_code": ":nail_care:", "aliases": "" }, "1f4d2": { "output": "1f4d2", "name": "ledger", "alpha_code": ":ledger:", "aliases": "" }, "1f486": { "output": "1f486", "name": "person getting massage", "alpha_code": ":person_getting_massage:", "aliases": ":massage:" }, "1f4d3": { "output": "1f4d3", "name": "notebook", "alpha_code": ":notebook:", "aliases": "" }, "1f487": { "output": "1f487", "name": "person getting haircut", "alpha_code": ":person_getting_haircut:", "aliases": ":haircut:" }, "1f4d4": { "output": "1f4d4", "name": "notebook with decorative cover", "alpha_code": ":notebook_with_decorative_cover:", "aliases": "" }, "1f488": { "output": "1f488", "name": "barber pole", "alpha_code": ":barber:", "aliases": "" }, "1f4d5": { "output": "1f4d5", "name": "closed book", "alpha_code": ":closed_book:", "aliases": "" }, "1f489": { "output": "1f489", "name": "syringe", "alpha_code": ":syringe:", "aliases": "" }, "1f4d6": { "output": "1f4d6", "name": "open book", "alpha_code": ":book:", "aliases": "" }, "1f48a": { "output": "1f48a", "name": "pill", "alpha_code": ":pill:", "aliases": "" }, "1f4d7": { "output": "1f4d7", "name": "green book", "alpha_code": ":green_book:", "aliases": "" }, "1f48b": { "output": "1f48b", "name": "kiss mark", "alpha_code": ":kiss:", "aliases": "" }, "1f4d8": { "output": "1f4d8", "name": "blue book", "alpha_code": ":blue_book:", "aliases": "" }, "1f48c": { "output": "1f48c", "name": "love letter", "alpha_code": ":love_letter:", "aliases": "" }, "1f4d9": { "output": "1f4d9", "name": "orange book", "alpha_code": ":orange_book:", "aliases": "" }, "1f48d": { "output": "1f48d", "name": "ring", "alpha_code": ":ring:", "aliases": "" }, "1f4da": { "output": "1f4da", "name": "books", "alpha_code": ":books:", "aliases": "" }, "1f48e": { "output": "1f48e", "name": "gem stone", "alpha_code": ":gem:", "aliases": "" }, "1f936-1f3fe": { "output": "1f936-1f3fe", "name": "Mrs. Claus: medium-dark skin tone", "alpha_code": ":mrs_claus_tone4:", "aliases": ":mother_christmas_tone4:" }, "1f4db": { "output": "1f4db", "name": "name badge", "alpha_code": ":name_badge:", "aliases": "" }, "1f48f": { "output": "1f48f", "name": "kiss", "alpha_code": ":couplekiss:", "aliases": "" }, "1f4dc": { "output": "1f4dc", "name": "scroll", "alpha_code": ":scroll:", "aliases": "" }, "1f490": { "output": "1f490", "name": "bouquet", "alpha_code": ":bouquet:", "aliases": "" }, "1f4dd": { "output": "1f4dd", "name": "memo", "alpha_code": ":pencil:", "aliases": ":memo:" }, "1f936-1f3ff": { "output": "1f936-1f3ff", "name": "Mrs. Claus: dark skin tone", "alpha_code": ":mrs_claus_tone5:", "aliases": ":mother_christmas_tone5:" }, "1f491": { "output": "1f491", "name": "couple with heart", "alpha_code": ":couple_with_heart:", "aliases": "" }, "1f4de": { "output": "1f4de", "name": "telephone receiver", "alpha_code": ":telephone_receiver:", "aliases": "" }, "1f492": { "output": "1f492", "name": "wedding", "alpha_code": ":wedding:", "aliases": "" }, "1f4df": { "output": "1f4df", "name": "pager", "alpha_code": ":pager:", "aliases": "" }, "1f4e0": { "output": "1f4e0", "name": "fax machine", "alpha_code": ":fax:", "aliases": "" }, "1f493": { "output": "1f493", "name": "beating heart", "alpha_code": ":heartbeat:", "aliases": "" }, "1f4e1": { "output": "1f4e1", "name": "satellite antenna", "alpha_code": ":satellite:", "aliases": "" }, "1f4e2": { "output": "1f4e2", "name": "loudspeaker", "alpha_code": ":loudspeaker:", "aliases": "" }, "1f935-1f3fb": { "output": "1f935-1f3fb", "name": "man in tuxedo: light skin tone", "alpha_code": ":man_in_tuxedo_tone1:", "aliases": ":tuxedo_tone1:" }, "1f494": { "output": "1f494", "name": "broken heart", "alpha_code": ":broken_heart:", "aliases": "" }, "1f4e3": { "output": "1f4e3", "name": "megaphone", "alpha_code": ":mega:", "aliases": "" }, "1f4e4": { "output": "1f4e4", "name": "outbox tray", "alpha_code": ":outbox_tray:", "aliases": "" }, "1f495": { "output": "1f495", "name": "two hearts", "alpha_code": ":two_hearts:", "aliases": "" }, "1f4e5": { "output": "1f4e5", "name": "inbox tray", "alpha_code": ":inbox_tray:", "aliases": "" }, "1f4e6": { "output": "1f4e6", "name": "package", "alpha_code": ":package:", "aliases": "" }, "1f935-1f3fc": { "output": "1f935-1f3fc", "name": "man in tuxedo: medium-light skin tone", "alpha_code": ":man_in_tuxedo_tone2:", "aliases": ":tuxedo_tone2:" }, "1f496": { "output": "1f496", "name": "sparkling heart", "alpha_code": ":sparkling_heart:", "aliases": "" }, "1f4e7": { "output": "1f4e7", "name": "e-mail", "alpha_code": ":e-mail:", "aliases": ":email:" }, "1f4e8": { "output": "1f4e8", "name": "incoming envelope", "alpha_code": ":incoming_envelope:", "aliases": "" }, "1f497": { "output": "1f497", "name": "growing heart", "alpha_code": ":heartpulse:", "aliases": "" }, "1f935-1f3fd": { "output": "1f935-1f3fd", "name": "man in tuxedo: medium skin tone", "alpha_code": ":man_in_tuxedo_tone3:", "aliases": ":tuxedo_tone3:" }, "1f4e9": { "output": "1f4e9", "name": "envelope with arrow", "alpha_code": ":envelope_with_arrow:", "aliases": "" }, "1f4ea": { "output": "1f4ea", "name": "closed mailbox with lowered flag", "alpha_code": ":mailbox_closed:", "aliases": "" }, "1f498": { "output": "1f498", "name": "heart with arrow", "alpha_code": ":cupid:", "aliases": "" }, "1f4eb": { "output": "1f4eb", "name": "closed mailbox with raised flag", "alpha_code": ":mailbox:", "aliases": "" }, "1f935-1f3fe": { "output": "1f935-1f3fe", "name": "man in tuxedo: medium-dark skin tone", "alpha_code": ":man_in_tuxedo_tone4:", "aliases": ":tuxedo_tone4:" }, "1f4ee": { "output": "1f4ee", "name": "postbox", "alpha_code": ":postbox:", "aliases": "" }, "1f499": { "output": "1f499", "name": "blue heart", "alpha_code": ":blue_heart:", "aliases": "" }, "1f4f0": { "output": "1f4f0", "name": "newspaper", "alpha_code": ":newspaper:", "aliases": "" }, "1f935-1f3ff": { "output": "1f935-1f3ff", "name": "man in tuxedo: dark skin tone", "alpha_code": ":man_in_tuxedo_tone5:", "aliases": ":tuxedo_tone5:" }, "1f4f1": { "output": "1f4f1", "name": "mobile phone", "alpha_code": ":iphone:", "aliases": "" }, "1f49a": { "output": "1f49a", "name": "green heart", "alpha_code": ":green_heart:", "aliases": "" }, "1f937-1f3fb": { "output": "1f937-1f3fb", "name": "person shrugging: light skin tone", "alpha_code": ":person_shrugging_tone1:", "aliases": ":shrug_tone1:" }, "1f4f2": { "output": "1f4f2", "name": "mobile phone with arrow", "alpha_code": ":calling:", "aliases": "" }, "1f4f3": { "output": "1f4f3", "name": "vibration mode", "alpha_code": ":vibration_mode:", "aliases": "" }, "1f49b": { "output": "1f49b", "name": "yellow heart", "alpha_code": ":yellow_heart:", "aliases": "" }, "1f937-1f3fc": { "output": "1f937-1f3fc", "name": "person shrugging: medium-light skin tone", "alpha_code": ":person_shrugging_tone2:", "aliases": ":shrug_tone2:" }, "1f4f4": { "output": "1f4f4", "name": "mobile phone off", "alpha_code": ":mobile_phone_off:", "aliases": "" }, "1f4f6": { "output": "1f4f6", "name": "antenna bars", "alpha_code": ":signal_strength:", "aliases": "" }, "1f937-1f3fd": { "output": "1f937-1f3fd", "name": "person shrugging: medium skin tone", "alpha_code": ":person_shrugging_tone3:", "aliases": ":shrug_tone3:" }, "1f49c": { "output": "1f49c", "name": "purple heart", "alpha_code": ":purple_heart:", "aliases": "" }, "1f937-1f3fe": { "output": "1f937-1f3fe", "name": "person shrugging: medium-dark skin tone", "alpha_code": ":person_shrugging_tone4:", "aliases": ":shrug_tone4:" }, "1f4f7": { "output": "1f4f7", "name": "camera", "alpha_code": ":camera:", "aliases": "" }, "1f4f9": { "output": "1f4f9", "name": "video camera", "alpha_code": ":video_camera:", "aliases": "" }, "1f49d": { "output": "1f49d", "name": "heart with ribbon", "alpha_code": ":gift_heart:", "aliases": "" }, "1f4fa": { "output": "1f4fa", "name": "television", "alpha_code": ":tv:", "aliases": "" }, "1f937-1f3ff": { "output": "1f937-1f3ff", "name": "person shrugging: dark skin tone", "alpha_code": ":person_shrugging_tone5:", "aliases": ":shrug_tone5:" }, "1f4fb": { "output": "1f4fb", "name": "radio", "alpha_code": ":radio:", "aliases": "" }, "1f49e": { "output": "1f49e", "name": "revolving hearts", "alpha_code": ":revolving_hearts:", "aliases": "" }, "1f4fc": { "output": "1f4fc", "name": "videocassette", "alpha_code": ":vhs:", "aliases": "" }, "1f926-1f3fb": { "output": "1f926-1f3fb", "name": "person facepalming: light skin tone", "alpha_code": ":person_facepalming_tone1:", "aliases": ":face_palm_tone1:|:facepalm_tone1:" }, "1f503": { "output": "1f503", "name": "clockwise vertical arrows", "alpha_code": ":arrows_clockwise:", "aliases": "" }, "1f49f": { "output": "1f49f", "name": "heart decoration", "alpha_code": ":heart_decoration:", "aliases": "" }, "1f50a": { "output": "1f50a", "name": "speaker high volume", "alpha_code": ":loud_sound:", "aliases": "" }, "1f50b": { "output": "1f50b", "name": "battery", "alpha_code": ":battery:", "aliases": "" }, "1f4a0": { "output": "1f4a0", "name": "diamond with a dot", "alpha_code": ":diamond_shape_with_a_dot_inside:", "aliases": "" }, "1f50c": { "output": "1f50c", "name": "electric plug", "alpha_code": ":electric_plug:", "aliases": "" }, "1f50d": { "output": "1f50d", "name": "left-pointing magnifying glass", "alpha_code": ":mag:", "aliases": "" }, "1f926-1f3fc": { "output": "1f926-1f3fc", "name": "person facepalming: medium-light skin tone", "alpha_code": ":person_facepalming_tone2:", "aliases": ":face_palm_tone2:|:facepalm_tone2:" }, "1f4a1": { "output": "1f4a1", "name": "light bulb", "alpha_code": ":bulb:", "aliases": "" }, "1f50e": { "output": "1f50e", "name": "right-pointing magnifying glass", "alpha_code": ":mag_right:", "aliases": "" }, "1f50f": { "output": "1f50f", "name": "locked with pen", "alpha_code": ":lock_with_ink_pen:", "aliases": "" }, "1f4a2": { "output": "1f4a2", "name": "anger symbol", "alpha_code": ":anger:", "aliases": "" }, "1f510": { "output": "1f510", "name": "locked with key", "alpha_code": ":closed_lock_with_key:", "aliases": "" }, "1f511": { "output": "1f511", "name": "key", "alpha_code": ":key:", "aliases": "" }, "1f4a3": { "output": "1f4a3", "name": "bomb", "alpha_code": ":bomb:", "aliases": "" }, "1f512": { "output": "1f512", "name": "locked", "alpha_code": ":lock:", "aliases": "" }, "1f513": { "output": "1f513", "name": "unlocked", "alpha_code": ":unlock:", "aliases": "" }, "1f4a4": { "output": "1f4a4", "name": "zzz", "alpha_code": ":zzz:", "aliases": "" }, "1f514": { "output": "1f514", "name": "bell", "alpha_code": ":bell:", "aliases": "" }, "1f516": { "output": "1f516", "name": "bookmark", "alpha_code": ":bookmark:", "aliases": "" }, "1f4a5": { "output": "1f4a5", "name": "collision", "alpha_code": ":boom:", "aliases": "" }, "1f926-1f3fd": { "output": "1f926-1f3fd", "name": "person facepalming: medium skin tone", "alpha_code": ":person_facepalming_tone3:", "aliases": ":face_palm_tone3:|:facepalm_tone3:" }, "1f517": { "output": "1f517", "name": "link", "alpha_code": ":link:", "aliases": "" }, "1f518": { "output": "1f518", "name": "radio button", "alpha_code": ":radio_button:", "aliases": "" }, "1f4a6": { "output": "1f4a6", "name": "sweat droplets", "alpha_code": ":sweat_drops:", "aliases": "" }, "1f519": { "output": "1f519", "name": "BACK arrow", "alpha_code": ":back:", "aliases": "" }, "1f51a": { "output": "1f51a", "name": "END arrow", "alpha_code": ":end:", "aliases": "" }, "1f4a7": { "output": "1f4a7", "name": "droplet", "alpha_code": ":droplet:", "aliases": "" }, "1f926-1f3fe": { "output": "1f926-1f3fe", "name": "person facepalming: medium-dark skin tone", "alpha_code": ":person_facepalming_tone4:", "aliases": ":face_palm_tone4:|:facepalm_tone4:" }, "1f51b": { "output": "1f51b", "name": "ON! arrow", "alpha_code": ":on:", "aliases": "" }, "1f51c": { "output": "1f51c", "name": "SOON arrow", "alpha_code": ":soon:", "aliases": "" }, "1f4a8": { "output": "1f4a8", "name": "dashing away", "alpha_code": ":dash:", "aliases": "" }, "1f51d": { "output": "1f51d", "name": "TOP arrow", "alpha_code": ":top:", "aliases": "" }, "1f51e": { "output": "1f51e", "name": "no one under eighteen", "alpha_code": ":underage:", "aliases": "" }, "1f4a9": { "output": "1f4a9", "name": "pile of poo", "alpha_code": ":poop:", "aliases": ":shit:|:hankey:|:poo:" }, "1f51f": { "output": "1f51f", "name": "keycap 10", "alpha_code": ":keycap_ten:", "aliases": "" }, "1f4aa": { "output": "1f4aa", "name": "flexed biceps", "alpha_code": ":muscle:", "aliases": "" }, "1f520": { "output": "1f520", "name": "input latin uppercase", "alpha_code": ":capital_abcd:", "aliases": "" }, "1f521": { "output": "1f521", "name": "input latin lowercase", "alpha_code": ":abcd:", "aliases": "" }, "1f4ab": { "output": "1f4ab", "name": "dizzy", "alpha_code": ":dizzy:", "aliases": "" }, "1f926-1f3ff": { "output": "1f926-1f3ff", "name": "person facepalming: dark skin tone", "alpha_code": ":person_facepalming_tone5:", "aliases": ":face_palm_tone5:|:facepalm_tone5:" }, "1f522": { "output": "1f522", "name": "input numbers", "alpha_code": ":1234:", "aliases": "" }, "1f523": { "output": "1f523", "name": "input symbols", "alpha_code": ":symbols:", "aliases": "" }, "1f4ac": { "output": "1f4ac", "name": "speech balloon", "alpha_code": ":speech_balloon:", "aliases": "" }, "1f524": { "output": "1f524", "name": "input latin letters", "alpha_code": ":abc:", "aliases": "" }, "1f525": { "output": "1f525", "name": "fire", "alpha_code": ":fire:", "aliases": ":flame:" }, "1f4ae": { "output": "1f4ae", "name": "white flower", "alpha_code": ":white_flower:", "aliases": "" }, "1f526": { "output": "1f526", "name": "flashlight", "alpha_code": ":flashlight:", "aliases": "" }, "1f527": { "output": "1f527", "name": "wrench", "alpha_code": ":wrench:", "aliases": "" }, "1f4af": { "output": "1f4af", "name": "hundred points", "alpha_code": ":100:", "aliases": "" }, "1f528": { "output": "1f528", "name": "hammer", "alpha_code": ":hammer:", "aliases": "" }, "1f529": { "output": "1f529", "name": "nut and bolt", "alpha_code": ":nut_and_bolt:", "aliases": "" }, "1f4b0": { "output": "1f4b0", "name": "money bag", "alpha_code": ":moneybag:", "aliases": "" }, "1f52a": { "output": "1f52a", "name": "kitchen knife", "alpha_code": ":knife:", "aliases": "" }, "1f52b": { "output": "1f52b", "name": "pistol", "alpha_code": ":gun:", "aliases": "" }, "1f4b1": { "output": "1f4b1", "name": "currency exchange", "alpha_code": ":currency_exchange:", "aliases": "" }, "1f930-1f3fb": { "output": "1f930-1f3fb", "name": "pregnant woman: light skin tone", "alpha_code": ":pregnant_woman_tone1:", "aliases": ":expecting_woman_tone1:" }, "1f52e": { "output": "1f52e", "name": "crystal ball", "alpha_code": ":crystal_ball:", "aliases": "" }, "1f4b2": { "output": "1f4b2", "name": "heavy dollar sign", "alpha_code": ":heavy_dollar_sign:", "aliases": "" }, "1f52f": { "output": "1f52f", "name": "dotted six-pointed star", "alpha_code": ":six_pointed_star:", "aliases": "" }, "1f4b3": { "output": "1f4b3", "name": "credit card", "alpha_code": ":credit_card:", "aliases": "" }, "1f530": { "output": "1f530", "name": "Japanese symbol for beginner", "alpha_code": ":beginner:", "aliases": "" }, "1f531": { "output": "1f531", "name": "trident emblem", "alpha_code": ":trident:", "aliases": "" }, "1f4b4": { "output": "1f4b4", "name": "yen banknote", "alpha_code": ":yen:", "aliases": "" }, "1f930-1f3fc": { "output": "1f930-1f3fc", "name": "pregnant woman: medium-light skin tone", "alpha_code": ":pregnant_woman_tone2:", "aliases": ":expecting_woman_tone2:" }, "1f532": { "output": "1f532", "name": "black square button", "alpha_code": ":black_square_button:", "aliases": "" }, "1f533": { "output": "1f533", "name": "white square button", "alpha_code": ":white_square_button:", "aliases": "" }, "1f4b5": { "output": "1f4b5", "name": "dollar banknote", "alpha_code": ":dollar:", "aliases": "" }, "1f534": { "output": "1f534", "name": "red circle", "alpha_code": ":red_circle:", "aliases": "" }, "1f535": { "output": "1f535", "name": "blue circle", "alpha_code": ":blue_circle:", "aliases": "" }, "1f4b8": { "output": "1f4b8", "name": "money with wings", "alpha_code": ":money_with_wings:", "aliases": "" }, "1f536": { "output": "1f536", "name": "large orange diamond", "alpha_code": ":large_orange_diamond:", "aliases": "" }, "1f537": { "output": "1f537", "name": "large blue diamond", "alpha_code": ":large_blue_diamond:", "aliases": "" }, "1f4b9": { "output": "1f4b9", "name": "chart increasing with yen", "alpha_code": ":chart:", "aliases": "" }, "1f930-1f3fd": { "output": "1f930-1f3fd", "name": "pregnant woman: medium skin tone", "alpha_code": ":pregnant_woman_tone3:", "aliases": ":expecting_woman_tone3:" }, "1f538": { "output": "1f538", "name": "small orange diamond", "alpha_code": ":small_orange_diamond:", "aliases": "" }, "1f539": { "output": "1f539", "name": "small blue diamond", "alpha_code": ":small_blue_diamond:", "aliases": "" }, "1f4ba": { "output": "1f4ba", "name": "seat", "alpha_code": ":seat:", "aliases": "" }, "1f53a": { "output": "1f53a", "name": "red triangle pointed up", "alpha_code": ":small_red_triangle:", "aliases": "" }, "1f53b": { "output": "1f53b", "name": "red triangle pointed down", "alpha_code": ":small_red_triangle_down:", "aliases": "" }, "1f4bb": { "output": "1f4bb", "name": "laptop computer", "alpha_code": ":computer:", "aliases": "" }, "1f53c": { "output": "1f53c", "name": "up button", "alpha_code": ":arrow_up_small:", "aliases": "" }, "1f4bc": { "output": "1f4bc", "name": "briefcase", "alpha_code": ":briefcase:", "aliases": "" }, "1f53d": { "output": "1f53d", "name": "down button", "alpha_code": ":arrow_down_small:", "aliases": "" }, "1f550": { "output": "1f550", "name": "one o\u2019clock", "alpha_code": ":clock1:", "aliases": "" }, "1f4bd": { "output": "1f4bd", "name": "computer disk", "alpha_code": ":minidisc:", "aliases": "" }, "1f551": { "output": "1f551", "name": "two o\u2019clock", "alpha_code": ":clock2:", "aliases": "" }, "1f4be": { "output": "1f4be", "name": "floppy disk", "alpha_code": ":floppy_disk:", "aliases": "" }, "1f930-1f3fe": { "output": "1f930-1f3fe", "name": "pregnant woman: medium-dark skin tone", "alpha_code": ":pregnant_woman_tone4:", "aliases": ":expecting_woman_tone4:" }, "1f552": { "output": "1f552", "name": "three o\u2019clock", "alpha_code": ":clock3:", "aliases": "" }, "1f4bf": { "output": "1f4bf", "name": "optical disk", "alpha_code": ":cd:", "aliases": "" }, "1f553": { "output": "1f553", "name": "four o\u2019clock", "alpha_code": ":clock4:", "aliases": "" }, "1f4c0": { "output": "1f4c0", "name": "dvd", "alpha_code": ":dvd:", "aliases": "" }, "1f554": { "output": "1f554", "name": "five o\u2019clock", "alpha_code": ":clock5:", "aliases": "" }, "1f555": { "output": "1f555", "name": "six o\u2019clock", "alpha_code": ":clock6:", "aliases": "" }, "1f4c1": { "output": "1f4c1", "name": "file folder", "alpha_code": ":file_folder:", "aliases": "" }, "1f556": { "output": "1f556", "name": "seven o\u2019clock", "alpha_code": ":clock7:", "aliases": "" }, "1f557": { "output": "1f557", "name": "eight o\u2019clock", "alpha_code": ":clock8:", "aliases": "" }, "1f4c2": { "output": "1f4c2", "name": "open file folder", "alpha_code": ":open_file_folder:", "aliases": "" }, "1f558": { "output": "1f558", "name": "nine o\u2019clock", "alpha_code": ":clock9:", "aliases": "" }, "1f559": { "output": "1f559", "name": "ten o\u2019clock", "alpha_code": ":clock10:", "aliases": "" }, "1f4c3": { "output": "1f4c3", "name": "page with curl", "alpha_code": ":page_with_curl:", "aliases": "" }, "1f55a": { "output": "1f55a", "name": "eleven o\u2019clock", "alpha_code": ":clock11:", "aliases": "" }, "1f55b": { "output": "1f55b", "name": "twelve o\u2019clock", "alpha_code": ":clock12:", "aliases": "" }, "1f4c4": { "output": "1f4c4", "name": "page facing up", "alpha_code": ":page_facing_up:", "aliases": "" }, "1f5fb": { "output": "1f5fb", "name": "mount fuji", "alpha_code": ":mount_fuji:", "aliases": "" }, "1f5fc": { "output": "1f5fc", "name": "Tokyo tower", "alpha_code": ":tokyo_tower:", "aliases": "" }, "1f4c5": { "output": "1f4c5", "name": "calendar", "alpha_code": ":date:", "aliases": "" }, "1f5fd": { "output": "1f5fd", "name": "Statue of Liberty", "alpha_code": ":statue_of_liberty:", "aliases": "" }, "1f5fe": { "output": "1f5fe", "name": "map of Japan", "alpha_code": ":japan:", "aliases": "" }, "1f4c6": { "output": "1f4c6", "name": "tear-off calendar", "alpha_code": ":calendar:", "aliases": "" }, "1f5ff": { "output": "1f5ff", "name": "moai", "alpha_code": ":moyai:", "aliases": "" }, "1f601": { "output": "1f601", "name": "grinning face with smiling eyes", "alpha_code": ":grin:", "aliases": "" }, "1f602": { "output": "1f602", "name": "face with tears of joy", "alpha_code": ":joy:", "aliases": "" }, "1f603": { "output": "1f603", "name": "smiling face with open mouth", "alpha_code": ":smiley:", "aliases": "" }, "1f4c8": { "output": "1f4c8", "name": "chart increasing", "alpha_code": ":chart_with_upwards_trend:", "aliases": "" }, "1f604": { "output": "1f604", "name": "smiling face with open mouth & smiling eyes", "alpha_code": ":smile:", "aliases": "" }, "1f605": { "output": "1f605", "name": "smiling face with open mouth & cold sweat", "alpha_code": ":sweat_smile:", "aliases": "" }, "1f4c9": { "output": "1f4c9", "name": "chart decreasing", "alpha_code": ":chart_with_downwards_trend:", "aliases": "" }, "1f606": { "output": "1f606", "name": "smiling face with open mouth & closed eyes", "alpha_code": ":laughing:", "aliases": ":satisfied:" }, "1f609": { "output": "1f609", "name": "winking face", "alpha_code": ":wink:", "aliases": "" }, "1f4ca": { "output": "1f4ca", "name": "bar chart", "alpha_code": ":bar_chart:", "aliases": "" }, "1f60a": { "output": "1f60a", "name": "smiling face with smiling eyes", "alpha_code": ":blush:", "aliases": "" }, "1f60b": { "output": "1f60b", "name": "face savouring delicious food", "alpha_code": ":yum:", "aliases": "" }, "1f4cb": { "output": "1f4cb", "name": "clipboard", "alpha_code": ":clipboard:", "aliases": "" }, "1f60c": { "output": "1f60c", "name": "relieved face", "alpha_code": ":relieved:", "aliases": "" }, "1f60d": { "output": "1f60d", "name": "smiling face with heart-eyes", "alpha_code": ":heart_eyes:", "aliases": "" }, "1f4cc": { "output": "1f4cc", "name": "pushpin", "alpha_code": ":pushpin:", "aliases": "" }, "1f60f": { "output": "1f60f", "name": "smirking face", "alpha_code": ":smirk:", "aliases": "" }, "1f612": { "output": "1f612", "name": "unamused face", "alpha_code": ":unamused:", "aliases": "" }, "1f4cd": { "output": "1f4cd", "name": "round pushpin", "alpha_code": ":round_pushpin:", "aliases": "" }, "1f613": { "output": "1f613", "name": "face with cold sweat", "alpha_code": ":sweat:", "aliases": "" }, "1f614": { "output": "1f614", "name": "pensive face", "alpha_code": ":pensive:", "aliases": "" }, "1f4ce": { "output": "1f4ce", "name": "paperclip", "alpha_code": ":paperclip:", "aliases": "" }, "1f616": { "output": "1f616", "name": "confounded face", "alpha_code": ":confounded:", "aliases": "" }, "1f618": { "output": "1f618", "name": "face blowing a kiss", "alpha_code": ":kissing_heart:", "aliases": "" }, "1f930-1f3ff": { "output": "1f930-1f3ff", "name": "pregnant woman: dark skin tone", "alpha_code": ":pregnant_woman_tone5:", "aliases": ":expecting_woman_tone5:" }, "1f4cf": { "output": "1f4cf", "name": "straight ruler", "alpha_code": ":straight_ruler:", "aliases": "" }, "1f61a": { "output": "1f61a", "name": "kissing face with closed eyes", "alpha_code": ":kissing_closed_eyes:", "aliases": "" }, "1f61c": { "output": "1f61c", "name": "face with stuck-out tongue & winking eye", "alpha_code": ":stuck_out_tongue_winking_eye:", "aliases": "" }, "1f4d0": { "output": "1f4d0", "name": "triangular ruler", "alpha_code": ":triangular_ruler:", "aliases": "" }, "1f61d": { "output": "1f61d", "name": "face with stuck-out tongue & closed eyes", "alpha_code": ":stuck_out_tongue_closed_eyes:", "aliases": "" }, "1f61e": { "output": "1f61e", "name": "disappointed face", "alpha_code": ":disappointed:", "aliases": "" }, "1f4d1": { "output": "1f4d1", "name": "bookmark tabs", "alpha_code": ":bookmark_tabs:", "aliases": "" }, "1f620": { "output": "1f620", "name": "angry face", "alpha_code": ":angry:", "aliases": "" }, "1f621": { "output": "1f621", "name": "pouting face", "alpha_code": ":rage:", "aliases": "" }, "1f622": { "output": "1f622", "name": "crying face", "alpha_code": ":cry:", "aliases": "" }, "1f623": { "output": "1f623", "name": "persevering face", "alpha_code": ":persevere:", "aliases": "" }, "1f624": { "output": "1f624", "name": "face with steam from nose", "alpha_code": ":triumph:", "aliases": "" }, "1f625": { "output": "1f625", "name": "disappointed but relieved face", "alpha_code": ":disappointed_relieved:", "aliases": "" }, "1f628": { "output": "1f628", "name": "fearful face", "alpha_code": ":fearful:", "aliases": "" }, "1f629": { "output": "1f629", "name": "weary face", "alpha_code": ":weary:", "aliases": "" }, "1f62a": { "output": "1f62a", "name": "sleepy face", "alpha_code": ":sleepy:", "aliases": "" }, "1f62b": { "output": "1f62b", "name": "tired face", "alpha_code": ":tired_face:", "aliases": "" }, "1f62d": { "output": "1f62d", "name": "loudly crying face", "alpha_code": ":sob:", "aliases": "" }, "1f630": { "output": "1f630", "name": "face with open mouth & cold sweat", "alpha_code": ":cold_sweat:", "aliases": "" }, "1f631": { "output": "1f631", "name": "face screaming in fear", "alpha_code": ":scream:", "aliases": "" }, "1f632": { "output": "1f632", "name": "astonished face", "alpha_code": ":astonished:", "aliases": "" }, "1f633": { "output": "1f633", "name": "flushed face", "alpha_code": ":flushed:", "aliases": "" }, "1f635": { "output": "1f635", "name": "dizzy face", "alpha_code": ":dizzy_face:", "aliases": "" }, "1f637": { "output": "1f637", "name": "face with medical mask", "alpha_code": ":mask:", "aliases": "" }, "1f638": { "output": "1f638", "name": "grinning cat face with smiling eyes", "alpha_code": ":smile_cat:", "aliases": "" }, "1f639": { "output": "1f639", "name": "cat face with tears of joy", "alpha_code": ":joy_cat:", "aliases": "" }, "1f63a": { "output": "1f63a", "name": "smiling cat face with open mouth", "alpha_code": ":smiley_cat:", "aliases": "" }, "1f63b": { "output": "1f63b", "name": "smiling cat face with heart-eyes", "alpha_code": ":heart_eyes_cat:", "aliases": "" }, "1f63c": { "output": "1f63c", "name": "cat face with wry smile", "alpha_code": ":smirk_cat:", "aliases": "" }, "1f63d": { "output": "1f63d", "name": "kissing cat face with closed eyes", "alpha_code": ":kissing_cat:", "aliases": "" }, "1f63e": { "output": "1f63e", "name": "pouting cat face", "alpha_code": ":pouting_cat:", "aliases": "" }, "1f63f": { "output": "1f63f", "name": "crying cat face", "alpha_code": ":crying_cat_face:", "aliases": "" }, "1f640": { "output": "1f640", "name": "weary cat face", "alpha_code": ":scream_cat:", "aliases": "" }, "1f645": { "output": "1f645", "name": "person gesturing NO", "alpha_code": ":person_gesturing_no:", "aliases": ":no_good:" }, "1f646": { "output": "1f646", "name": "person gesturing OK", "alpha_code": ":person_gesturing_ok:", "aliases": ":ok_woman:" }, "1f647": { "output": "1f647", "name": "person bowing", "alpha_code": ":person_bowing:", "aliases": ":bow:" }, "1f648": { "output": "1f648", "name": "see-no-evil monkey", "alpha_code": ":see_no_evil:", "aliases": "" }, "1f57a-1f3fb": { "output": "1f57a-1f3fb", "name": "man dancing: light skin tone", "alpha_code": ":man_dancing_tone1:", "aliases": ":male_dancer_tone1:" }, "1f649": { "output": "1f649", "name": "hear-no-evil monkey", "alpha_code": ":hear_no_evil:", "aliases": "" }, "1f64a": { "output": "1f64a", "name": "speak-no-evil monkey", "alpha_code": ":speak_no_evil:", "aliases": "" }, "1f57a-1f3fc": { "output": "1f57a-1f3fc", "name": "man dancing: medium-light skin tone", "alpha_code": ":man_dancing_tone2:", "aliases": ":male_dancer_tone2:" }, "1f64b": { "output": "1f64b", "name": "person raising hand", "alpha_code": ":person_raising_hand:", "aliases": ":raising_hand:" }, "1f64c": { "output": "1f64c", "name": "raising hands", "alpha_code": ":raised_hands:", "aliases": "" }, "1f64d": { "output": "1f64d", "name": "person frowning", "alpha_code": ":person_frowning:", "aliases": "" }, "1f64e": { "output": "1f64e", "name": "person pouting", "alpha_code": ":person_pouting:", "aliases": ":person_with_pouting_face:" }, "1f64f": { "output": "1f64f", "name": "folded hands", "alpha_code": ":pray:", "aliases": "" }, "1f680": { "output": "1f680", "name": "rocket", "alpha_code": ":rocket:", "aliases": "" }, "1f683": { "output": "1f683", "name": "railway car", "alpha_code": ":railway_car:", "aliases": "" }, "1f684": { "output": "1f684", "name": "high-speed train", "alpha_code": ":bullettrain_side:", "aliases": "" }, "1f685": { "output": "1f685", "name": "high-speed train with bullet nose", "alpha_code": ":bullettrain_front:", "aliases": "" }, "1f687": { "output": "1f687", "name": "metro", "alpha_code": ":metro:", "aliases": "" }, "1f689": { "output": "1f689", "name": "station", "alpha_code": ":station:", "aliases": "" }, "1f68c": { "output": "1f68c", "name": "bus", "alpha_code": ":bus:", "aliases": "" }, "1f68f": { "output": "1f68f", "name": "bus stop", "alpha_code": ":busstop:", "aliases": "" }, "1f691": { "output": "1f691", "name": "ambulance", "alpha_code": ":ambulance:", "aliases": "" }, "1f692": { "output": "1f692", "name": "fire engine", "alpha_code": ":fire_engine:", "aliases": "" }, "1f693": { "output": "1f693", "name": "police car", "alpha_code": ":police_car:", "aliases": "" }, "1f695": { "output": "1f695", "name": "taxi", "alpha_code": ":taxi:", "aliases": "" }, "1f697": { "output": "1f697", "name": "automobile", "alpha_code": ":red_car:", "aliases": "" }, "1f699": { "output": "1f699", "name": "sport utility vehicle", "alpha_code": ":blue_car:", "aliases": "" }, "1f69a": { "output": "1f69a", "name": "delivery truck", "alpha_code": ":truck:", "aliases": "" }, "1f6a2": { "output": "1f6a2", "name": "ship", "alpha_code": ":ship:", "aliases": "" }, "1f6a4": { "output": "1f6a4", "name": "speedboat", "alpha_code": ":speedboat:", "aliases": "" }, "1f6a5": { "output": "1f6a5", "name": "horizontal traffic light", "alpha_code": ":traffic_light:", "aliases": "" }, "1f6a7": { "output": "1f6a7", "name": "construction", "alpha_code": ":construction:", "aliases": "" }, "1f6a8": { "output": "1f6a8", "name": "police car light", "alpha_code": ":rotating_light:", "aliases": "" }, "1f6a9": { "output": "1f6a9", "name": "triangular flag", "alpha_code": ":triangular_flag_on_post:", "aliases": "" }, "1f6aa": { "output": "1f6aa", "name": "door", "alpha_code": ":door:", "aliases": "" }, "1f57a-1f3fd": { "output": "1f57a-1f3fd", "name": "man dancing: medium skin tone", "alpha_code": ":man_dancing_tone3:", "aliases": ":male_dancer_tone3:" }, "1f6ab": { "output": "1f6ab", "name": "prohibited", "alpha_code": ":no_entry_sign:", "aliases": "" }, "1f6ac": { "output": "1f6ac", "name": "cigarette", "alpha_code": ":smoking:", "aliases": "" }, "1f6ad": { "output": "1f6ad", "name": "no smoking", "alpha_code": ":no_smoking:", "aliases": "" }, "1f6b2": { "output": "1f6b2", "name": "bicycle", "alpha_code": ":bike:", "aliases": "" }, "1f6b6": { "output": "1f6b6", "name": "person walking", "alpha_code": ":person_walking:", "aliases": ":walking:" }, "1f6b9": { "output": "1f6b9", "name": "men\u2019s room", "alpha_code": ":mens:", "aliases": "" }, "1f6ba": { "output": "1f6ba", "name": "women\u2019s room", "alpha_code": ":womens:", "aliases": "" }, "1f57a-1f3fe": { "output": "1f57a-1f3fe", "name": "man dancing: medium-dark skin tone", "alpha_code": ":man_dancing_tone4:", "aliases": ":male_dancer_tone4:" }, "1f6bb": { "output": "1f6bb", "name": "restroom", "alpha_code": ":restroom:", "aliases": "" }, "1f6bc": { "output": "1f6bc", "name": "baby symbol", "alpha_code": ":baby_symbol:", "aliases": "" }, "1f57a-1f3ff": { "output": "1f57a-1f3ff", "name": "man dancing: dark skin tone", "alpha_code": ":man_dancing_tone5:", "aliases": ":male_dancer_tone5:" }, "1f6bd": { "output": "1f6bd", "name": "toilet", "alpha_code": ":toilet:", "aliases": "" }, "1f6be": { "output": "1f6be", "name": "water closet", "alpha_code": ":wc:", "aliases": "" }, "1f933-1f3fb": { "output": "1f933-1f3fb", "name": "selfie: light skin tone", "alpha_code": ":selfie_tone1:", "aliases": "" }, "1f6c0": { "output": "1f6c0", "name": "person taking bath", "alpha_code": ":bath:", "aliases": "" }, "1f918": { "output": "1f918", "name": "sign of the horns", "alpha_code": ":metal:", "aliases": ":sign_of_the_horns:" }, "1f600": { "output": "1f600", "name": "grinning face", "alpha_code": ":grinning:", "aliases": "" }, "1f607": { "output": "1f607", "name": "smiling face with halo", "alpha_code": ":innocent:", "aliases": "" }, "1f608": { "output": "1f608", "name": "smiling face with horns", "alpha_code": ":smiling_imp:", "aliases": "" }, "1f60e": { "output": "1f60e", "name": "smiling face with sunglasses", "alpha_code": ":sunglasses:", "aliases": "" }, "1f610": { "output": "1f610", "name": "neutral face", "alpha_code": ":neutral_face:", "aliases": "" }, "1f611": { "output": "1f611", "name": "expressionless face", "alpha_code": ":expressionless:", "aliases": "" }, "1f615": { "output": "1f615", "name": "confused face", "alpha_code": ":confused:", "aliases": "" }, "1f617": { "output": "1f617", "name": "kissing face", "alpha_code": ":kissing:", "aliases": "" }, "1f933-1f3fc": { "output": "1f933-1f3fc", "name": "selfie: medium-light skin tone", "alpha_code": ":selfie_tone2:", "aliases": "" }, "1f619": { "output": "1f619", "name": "kissing face with smiling eyes", "alpha_code": ":kissing_smiling_eyes:", "aliases": "" }, "1f61b": { "output": "1f61b", "name": "face with stuck-out tongue", "alpha_code": ":stuck_out_tongue:", "aliases": "" }, "1f61f": { "output": "1f61f", "name": "worried face", "alpha_code": ":worried:", "aliases": "" }, "1f626": { "output": "1f626", "name": "frowning face with open mouth", "alpha_code": ":frowning:", "aliases": "" }, "1f627": { "output": "1f627", "name": "anguished face", "alpha_code": ":anguished:", "aliases": "" }, "1f62c": { "output": "1f62c", "name": "grimacing face", "alpha_code": ":grimacing:", "aliases": "" }, "1f62e": { "output": "1f62e", "name": "face with open mouth", "alpha_code": ":open_mouth:", "aliases": "" }, "1f62f": { "output": "1f62f", "name": "hushed face", "alpha_code": ":hushed:", "aliases": "" }, "1f634": { "output": "1f634", "name": "sleeping face", "alpha_code": ":sleeping:", "aliases": "" }, "1f636": { "output": "1f636", "name": "face without mouth", "alpha_code": ":no_mouth:", "aliases": "" }, "1f681": { "output": "1f681", "name": "helicopter", "alpha_code": ":helicopter:", "aliases": "" }, "1f682": { "output": "1f682", "name": "locomotive", "alpha_code": ":steam_locomotive:", "aliases": "" }, "1f686": { "output": "1f686", "name": "train", "alpha_code": ":train2:", "aliases": "" }, "1f688": { "output": "1f688", "name": "light rail", "alpha_code": ":light_rail:", "aliases": "" }, "1f68a": { "output": "1f68a", "name": "tram", "alpha_code": ":tram:", "aliases": "" }, "1f68d": { "output": "1f68d", "name": "oncoming bus", "alpha_code": ":oncoming_bus:", "aliases": "" }, "1f68e": { "output": "1f68e", "name": "trolleybus", "alpha_code": ":trolleybus:", "aliases": "" }, "1f690": { "output": "1f690", "name": "minibus", "alpha_code": ":minibus:", "aliases": "" }, "1f694": { "output": "1f694", "name": "oncoming police car", "alpha_code": ":oncoming_police_car:", "aliases": "" }, "1f696": { "output": "1f696", "name": "oncoming taxi", "alpha_code": ":oncoming_taxi:", "aliases": "" }, "1f698": { "output": "1f698", "name": "oncoming automobile", "alpha_code": ":oncoming_automobile:", "aliases": "" }, "1f69b": { "output": "1f69b", "name": "articulated lorry", "alpha_code": ":articulated_lorry:", "aliases": "" }, "1f933-1f3fd": { "output": "1f933-1f3fd", "name": "selfie: medium skin tone", "alpha_code": ":selfie_tone3:", "aliases": "" }, "1f69c": { "output": "1f69c", "name": "tractor", "alpha_code": ":tractor:", "aliases": "" }, "1f69d": { "output": "1f69d", "name": "monorail", "alpha_code": ":monorail:", "aliases": "" }, "1f69e": { "output": "1f69e", "name": "mountain railway", "alpha_code": ":mountain_railway:", "aliases": "" }, "1f69f": { "output": "1f69f", "name": "suspension railway", "alpha_code": ":suspension_railway:", "aliases": "" }, "1f6a0": { "output": "1f6a0", "name": "mountain cableway", "alpha_code": ":mountain_cableway:", "aliases": "" }, "1f6a1": { "output": "1f6a1", "name": "aerial tramway", "alpha_code": ":aerial_tramway:", "aliases": "" }, "1f6a3": { "output": "1f6a3", "name": "person rowing boat", "alpha_code": ":person_rowing_boat:", "aliases": ":rowboat:" }, "1f6a6": { "output": "1f6a6", "name": "vertical traffic light", "alpha_code": ":vertical_traffic_light:", "aliases": "" }, "1f933-1f3fe": { "output": "1f933-1f3fe", "name": "selfie: medium-dark skin tone", "alpha_code": ":selfie_tone4:", "aliases": "" }, "1f6ae": { "output": "1f6ae", "name": "litter in bin sign", "alpha_code": ":put_litter_in_its_place:", "aliases": "" }, "1f6af": { "output": "1f6af", "name": "no littering", "alpha_code": ":do_not_litter:", "aliases": "" }, "1f933-1f3ff": { "output": "1f933-1f3ff", "name": "selfie: dark skin tone", "alpha_code": ":selfie_tone5:", "aliases": "" }, "1f6b0": { "output": "1f6b0", "name": "potable water", "alpha_code": ":potable_water:", "aliases": "" }, "1f6b1": { "output": "1f6b1", "name": "non-potable water", "alpha_code": ":non-potable_water:", "aliases": "" }, "1f6b3": { "output": "1f6b3", "name": "no bicycles", "alpha_code": ":no_bicycles:", "aliases": "" }, "1f91e-1f3fb": { "output": "1f91e-1f3fb", "name": "crossed fingers: light skin tone", "alpha_code": ":fingers_crossed_tone1:", "aliases": ":hand_with_index_and_middle_fingers_crossed_tone1:" }, "1f6b4": { "output": "1f6b4", "name": "person biking", "alpha_code": ":person_biking:", "aliases": ":bicyclist:" }, "1f6b5": { "output": "1f6b5", "name": "person mountain biking", "alpha_code": ":person_mountain_biking:", "aliases": ":mountain_bicyclist:" }, "1f6b7": { "output": "1f6b7", "name": "no pedestrians", "alpha_code": ":no_pedestrians:", "aliases": "" }, "1f6b8": { "output": "1f6b8", "name": "children crossing", "alpha_code": ":children_crossing:", "aliases": "" }, "1f6bf": { "output": "1f6bf", "name": "shower", "alpha_code": ":shower:", "aliases": "" }, "1f6c1": { "output": "1f6c1", "name": "bathtub", "alpha_code": ":bathtub:", "aliases": "" }, "1f6c2": { "output": "1f6c2", "name": "passport control", "alpha_code": ":passport_control:", "aliases": "" }, "1f91e-1f3fc": { "output": "1f91e-1f3fc", "name": "crossed fingers: medium-light skin tone", "alpha_code": ":fingers_crossed_tone2:", "aliases": ":hand_with_index_and_middle_fingers_crossed_tone2:" }, "1f6c3": { "output": "1f6c3", "name": "customs", "alpha_code": ":customs:", "aliases": "" }, "1f6c4": { "output": "1f6c4", "name": "baggage claim", "alpha_code": ":baggage_claim:", "aliases": "" }, "1f91e-1f3fd": { "output": "1f91e-1f3fd", "name": "crossed fingers: medium skin tone", "alpha_code": ":fingers_crossed_tone3:", "aliases": ":hand_with_index_and_middle_fingers_crossed_tone3:" }, "1f6c5": { "output": "1f6c5", "name": "left luggage", "alpha_code": ":left_luggage:", "aliases": "" }, "1f30d": { "output": "1f30d", "name": "globe showing Europe-Africa", "alpha_code": ":earth_africa:", "aliases": "" }, "1f30e": { "output": "1f30e", "name": "globe showing Americas", "alpha_code": ":earth_americas:", "aliases": "" }, "1f310": { "output": "1f310", "name": "globe with meridians", "alpha_code": ":globe_with_meridians:", "aliases": "" }, "1f312": { "output": "1f312", "name": "waxing crescent moon", "alpha_code": ":waxing_crescent_moon:", "aliases": "" }, "1f316": { "output": "1f316", "name": "waning gibbous moon", "alpha_code": ":waning_gibbous_moon:", "aliases": "" }, "1f317": { "output": "1f317", "name": "last quarter moon", "alpha_code": ":last_quarter_moon:", "aliases": "" }, "1f318": { "output": "1f318", "name": "waning crescent moon", "alpha_code": ":waning_crescent_moon:", "aliases": "" }, "1f31a": { "output": "1f31a", "name": "new moon face", "alpha_code": ":new_moon_with_face:", "aliases": "" }, "1f31c": { "output": "1f31c", "name": "last quarter moon with face", "alpha_code": ":last_quarter_moon_with_face:", "aliases": "" }, "1f31d": { "output": "1f31d", "name": "full moon with face", "alpha_code": ":full_moon_with_face:", "aliases": "" }, "1f31e": { "output": "1f31e", "name": "sun with face", "alpha_code": ":sun_with_face:", "aliases": "" }, "1f332": { "output": "1f332", "name": "evergreen tree", "alpha_code": ":evergreen_tree:", "aliases": "" }, "1f333": { "output": "1f333", "name": "deciduous tree", "alpha_code": ":deciduous_tree:", "aliases": "" }, "1f34b": { "output": "1f34b", "name": "lemon", "alpha_code": ":lemon:", "aliases": "" }, "1f91e-1f3fe": { "output": "1f91e-1f3fe", "name": "crossed fingers: medium-dark skin tone", "alpha_code": ":fingers_crossed_tone4:", "aliases": ":hand_with_index_and_middle_fingers_crossed_tone4:" }, "1f350": { "output": "1f350", "name": "pear", "alpha_code": ":pear:", "aliases": "" }, "1f37c": { "output": "1f37c", "name": "baby bottle", "alpha_code": ":baby_bottle:", "aliases": "" }, "1f3c7": { "output": "1f3c7", "name": "horse racing", "alpha_code": ":horse_racing:", "aliases": "" }, "1f3c9": { "output": "1f3c9", "name": "rugby football", "alpha_code": ":rugby_football:", "aliases": "" }, "1f3e4": { "output": "1f3e4", "name": "post office", "alpha_code": ":european_post_office:", "aliases": "" }, "1f400": { "output": "1f400", "name": "rat", "alpha_code": ":rat:", "aliases": "" }, "1f401": { "output": "1f401", "name": "mouse", "alpha_code": ":mouse2:", "aliases": "" }, "1f402": { "output": "1f402", "name": "ox", "alpha_code": ":ox:", "aliases": "" }, "1f403": { "output": "1f403", "name": "water buffalo", "alpha_code": ":water_buffalo:", "aliases": "" }, "1f404": { "output": "1f404", "name": "cow", "alpha_code": ":cow2:", "aliases": "" }, "1f405": { "output": "1f405", "name": "tiger", "alpha_code": ":tiger2:", "aliases": "" }, "1f406": { "output": "1f406", "name": "leopard", "alpha_code": ":leopard:", "aliases": "" }, "1f407": { "output": "1f407", "name": "rabbit", "alpha_code": ":rabbit2:", "aliases": "" }, "1f408": { "output": "1f408", "name": "cat", "alpha_code": ":cat2:", "aliases": "" }, "1f409": { "output": "1f409", "name": "dragon", "alpha_code": ":dragon:", "aliases": "" }, "1f40a": { "output": "1f40a", "name": "crocodile", "alpha_code": ":crocodile:", "aliases": "" }, "1f40b": { "output": "1f40b", "name": "whale", "alpha_code": ":whale2:", "aliases": "" }, "1f40f": { "output": "1f40f", "name": "ram", "alpha_code": ":ram:", "aliases": "" }, "1f410": { "output": "1f410", "name": "goat", "alpha_code": ":goat:", "aliases": "" }, "1f413": { "output": "1f413", "name": "rooster", "alpha_code": ":rooster:", "aliases": "" }, "1f415": { "output": "1f415", "name": "dog", "alpha_code": ":dog2:", "aliases": "" }, "1f416": { "output": "1f416", "name": "pig", "alpha_code": ":pig2:", "aliases": "" }, "1f91e-1f3ff": { "output": "1f91e-1f3ff", "name": "crossed fingers: dark skin tone", "alpha_code": ":fingers_crossed_tone5:", "aliases": ":hand_with_index_and_middle_fingers_crossed_tone5:" }, "1f42a": { "output": "1f42a", "name": "camel", "alpha_code": ":dromedary_camel:", "aliases": "" }, "1f465": { "output": "1f465", "name": "busts in silhouette", "alpha_code": ":busts_in_silhouette:", "aliases": "" }, "1f46c": { "output": "1f46c", "name": "two men holding hands", "alpha_code": ":two_men_holding_hands:", "aliases": "" }, "1f46d": { "output": "1f46d", "name": "two women holding hands", "alpha_code": ":two_women_holding_hands:", "aliases": "" }, "1f4ad": { "output": "1f4ad", "name": "thought balloon", "alpha_code": ":thought_balloon:", "aliases": "" }, "1f4b6": { "output": "1f4b6", "name": "euro banknote", "alpha_code": ":euro:", "aliases": "" }, "1f919-1f3fb": { "output": "1f919-1f3fb", "name": "call me hand: light skin tone", "alpha_code": ":call_me_tone1:", "aliases": ":call_me_hand_tone1:" }, "1f4b7": { "output": "1f4b7", "name": "pound banknote", "alpha_code": ":pound:", "aliases": "" }, "1f4ec": { "output": "1f4ec", "name": "open mailbox with raised flag", "alpha_code": ":mailbox_with_mail:", "aliases": "" }, "1f4ed": { "output": "1f4ed", "name": "open mailbox with lowered flag", "alpha_code": ":mailbox_with_no_mail:", "aliases": "" }, "1f919-1f3fc": { "output": "1f919-1f3fc", "name": "call me hand: medium-light skin tone", "alpha_code": ":call_me_tone2:", "aliases": ":call_me_hand_tone2:" }, "1f4ef": { "output": "1f4ef", "name": "postal horn", "alpha_code": ":postal_horn:", "aliases": "" }, "1f4f5": { "output": "1f4f5", "name": "no mobile phones", "alpha_code": ":no_mobile_phones:", "aliases": "" }, "1f500": { "output": "1f500", "name": "shuffle tracks button", "alpha_code": ":twisted_rightwards_arrows:", "aliases": "" }, "1f501": { "output": "1f501", "name": "repeat button", "alpha_code": ":repeat:", "aliases": "" }, "1f502": { "output": "1f502", "name": "repeat single button", "alpha_code": ":repeat_one:", "aliases": "" }, "1f504": { "output": "1f504", "name": "anticlockwise arrows button", "alpha_code": ":arrows_counterclockwise:", "aliases": "" }, "1f919-1f3fd": { "output": "1f919-1f3fd", "name": "call me hand: medium skin tone", "alpha_code": ":call_me_tone3:", "aliases": ":call_me_hand_tone3:" }, "1f505": { "output": "1f505", "name": "dim button", "alpha_code": ":low_brightness:", "aliases": "" }, "1f506": { "output": "1f506", "name": "bright button", "alpha_code": ":high_brightness:", "aliases": "" }, "1f507": { "output": "1f507", "name": "muted speaker", "alpha_code": ":mute:", "aliases": "" }, "1f509": { "output": "1f509", "name": "speaker medium volume", "alpha_code": ":sound:", "aliases": "" }, "1f515": { "output": "1f515", "name": "bell with slash", "alpha_code": ":no_bell:", "aliases": "" }, "1f52c": { "output": "1f52c", "name": "microscope", "alpha_code": ":microscope:", "aliases": "" }, "1f52d": { "output": "1f52d", "name": "telescope", "alpha_code": ":telescope:", "aliases": "" }, "1f55c": { "output": "1f55c", "name": "one-thirty", "alpha_code": ":clock130:", "aliases": "" }, "1f55d": { "output": "1f55d", "name": "two-thirty", "alpha_code": ":clock230:", "aliases": "" }, "1f55e": { "output": "1f55e", "name": "three-thirty", "alpha_code": ":clock330:", "aliases": "" }, "1f55f": { "output": "1f55f", "name": "four-thirty", "alpha_code": ":clock430:", "aliases": "" }, "1f560": { "output": "1f560", "name": "five-thirty", "alpha_code": ":clock530:", "aliases": "" }, "1f561": { "output": "1f561", "name": "six-thirty", "alpha_code": ":clock630:", "aliases": "" }, "1f562": { "output": "1f562", "name": "seven-thirty", "alpha_code": ":clock730:", "aliases": "" }, "1f563": { "output": "1f563", "name": "eight-thirty", "alpha_code": ":clock830:", "aliases": "" }, "1f564": { "output": "1f564", "name": "nine-thirty", "alpha_code": ":clock930:", "aliases": "" }, "1f565": { "output": "1f565", "name": "ten-thirty", "alpha_code": ":clock1030:", "aliases": "" }, "1f566": { "output": "1f566", "name": "eleven-thirty", "alpha_code": ":clock1130:", "aliases": "" }, "1f567": { "output": "1f567", "name": "twelve-thirty", "alpha_code": ":clock1230:", "aliases": "" }, "1f508": { "output": "1f508", "name": "speaker low volume", "alpha_code": ":speaker:", "aliases": "" }, "1f68b": { "output": "1f68b", "name": "tram car", "alpha_code": ":train:", "aliases": "" }, "27bf": { "output": "27bf", "name": "double curly loop", "alpha_code": ":loop:", "aliases": "" }, "1f1e6-1f1eb": { "output": "1f1e6-1f1eb", "name": "Afghanistan", "alpha_code": ":flag_af:", "aliases": ":af:" }, "1f1e6-1f1f1": { "output": "1f1e6-1f1f1", "name": "Albania", "alpha_code": ":flag_al:", "aliases": ":al:" }, "1f1e9-1f1ff": { "output": "1f1e9-1f1ff", "name": "Algeria", "alpha_code": ":flag_dz:", "aliases": ":dz:" }, "1f1e6-1f1e9": { "output": "1f1e6-1f1e9", "name": "Andorra", "alpha_code": ":flag_ad:", "aliases": ":ad:" }, "1f1e6-1f1f4": { "output": "1f1e6-1f1f4", "name": "Angola", "alpha_code": ":flag_ao:", "aliases": ":ao:" }, "1f1e6-1f1ec": { "output": "1f1e6-1f1ec", "name": "Antigua & Barbuda", "alpha_code": ":flag_ag:", "aliases": ":ag:" }, "1f1e6-1f1f7": { "output": "1f1e6-1f1f7", "name": "Argentina", "alpha_code": ":flag_ar:", "aliases": ":ar:" }, "1f1e6-1f1f2": { "output": "1f1e6-1f1f2", "name": "Armenia", "alpha_code": ":flag_am:", "aliases": ":am:" }, "1f1e6-1f1fa": { "output": "1f1e6-1f1fa", "name": "Australia", "alpha_code": ":flag_au:", "aliases": ":au:" }, "1f1e6-1f1f9": { "output": "1f1e6-1f1f9", "name": "Austria", "alpha_code": ":flag_at:", "aliases": ":at:" }, "1f1e6-1f1ff": { "output": "1f1e6-1f1ff", "name": "Azerbaijan", "alpha_code": ":flag_az:", "aliases": ":az:" }, "1f1e7-1f1f8": { "output": "1f1e7-1f1f8", "name": "Bahamas", "alpha_code": ":flag_bs:", "aliases": ":bs:" }, "1f1e7-1f1ed": { "output": "1f1e7-1f1ed", "name": "Bahrain", "alpha_code": ":flag_bh:", "aliases": ":bh:" }, "1f1e7-1f1e9": { "output": "1f1e7-1f1e9", "name": "Bangladesh", "alpha_code": ":flag_bd:", "aliases": ":bd:" }, "1f1e7-1f1e7": { "output": "1f1e7-1f1e7", "name": "Barbados", "alpha_code": ":flag_bb:", "aliases": ":bb:" }, "1f1e7-1f1fe": { "output": "1f1e7-1f1fe", "name": "Belarus", "alpha_code": ":flag_by:", "aliases": ":by:" }, "1f1e7-1f1ea": { "output": "1f1e7-1f1ea", "name": "Belgium", "alpha_code": ":flag_be:", "aliases": ":be:" }, "1f1e7-1f1ff": { "output": "1f1e7-1f1ff", "name": "Belize", "alpha_code": ":flag_bz:", "aliases": ":bz:" }, "1f1e7-1f1ef": { "output": "1f1e7-1f1ef", "name": "Benin", "alpha_code": ":flag_bj:", "aliases": ":bj:" }, "1f1e7-1f1f9": { "output": "1f1e7-1f1f9", "name": "Bhutan", "alpha_code": ":flag_bt:", "aliases": ":bt:" }, "1f1e7-1f1f4": { "output": "1f1e7-1f1f4", "name": "Bolivia", "alpha_code": ":flag_bo:", "aliases": ":bo:" }, "1f1e7-1f1e6": { "output": "1f1e7-1f1e6", "name": "Bosnia & Herzegovina", "alpha_code": ":flag_ba:", "aliases": ":ba:" }, "1f1e7-1f1fc": { "output": "1f1e7-1f1fc", "name": "Botswana", "alpha_code": ":flag_bw:", "aliases": ":bw:" }, "1f1e7-1f1f7": { "output": "1f1e7-1f1f7", "name": "Brazil", "alpha_code": ":flag_br:", "aliases": ":br:" }, "1f1e7-1f1f3": { "output": "1f1e7-1f1f3", "name": "Brunei", "alpha_code": ":flag_bn:", "aliases": ":bn:" }, "1f1e7-1f1ec": { "output": "1f1e7-1f1ec", "name": "Bulgaria", "alpha_code": ":flag_bg:", "aliases": ":bg:" }, "1f1e7-1f1eb": { "output": "1f1e7-1f1eb", "name": "Burkina Faso", "alpha_code": ":flag_bf:", "aliases": ":bf:" }, "1f1e7-1f1ee": { "output": "1f1e7-1f1ee", "name": "Burundi", "alpha_code": ":flag_bi:", "aliases": ":bi:" }, "1f1f0-1f1ed": { "output": "1f1f0-1f1ed", "name": "Cambodia", "alpha_code": ":flag_kh:", "aliases": ":kh:" }, "1f1e8-1f1f2": { "output": "1f1e8-1f1f2", "name": "Cameroon", "alpha_code": ":flag_cm:", "aliases": ":cm:" }, "1f1e8-1f1e6": { "output": "1f1e8-1f1e6", "name": "Canada", "alpha_code": ":flag_ca:", "aliases": ":ca:" }, "1f1e8-1f1fb": { "output": "1f1e8-1f1fb", "name": "Cape Verde", "alpha_code": ":flag_cv:", "aliases": ":cv:" }, "1f919-1f3fe": { "output": "1f919-1f3fe", "name": "call me hand: medium-dark skin tone", "alpha_code": ":call_me_tone4:", "aliases": ":call_me_hand_tone4:" }, "1f1e8-1f1eb": { "output": "1f1e8-1f1eb", "name": "Central African Republic", "alpha_code": ":flag_cf:", "aliases": ":cf:" }, "1f1f9-1f1e9": { "output": "1f1f9-1f1e9", "name": "Chad", "alpha_code": ":flag_td:", "aliases": ":td:" }, "1f1e8-1f1f1": { "output": "1f1e8-1f1f1", "name": "Chile", "alpha_code": ":flag_cl:", "aliases": ":chile:" }, "1f1e8-1f1f4": { "output": "1f1e8-1f1f4", "name": "Colombia", "alpha_code": ":flag_co:", "aliases": ":co:" }, "1f1f0-1f1f2": { "output": "1f1f0-1f1f2", "name": "Comoros", "alpha_code": ":flag_km:", "aliases": ":km:" }, "1f1e8-1f1f7": { "output": "1f1e8-1f1f7", "name": "Costa Rica", "alpha_code": ":flag_cr:", "aliases": ":cr:" }, "1f1e8-1f1ee": { "output": "1f1e8-1f1ee", "name": "C\u00f4te d\u2019Ivoire", "alpha_code": ":flag_ci:", "aliases": ":ci:" }, "1f1ed-1f1f7": { "output": "1f1ed-1f1f7", "name": "Croatia", "alpha_code": ":flag_hr:", "aliases": ":hr:" }, "1f1e8-1f1fa": { "output": "1f1e8-1f1fa", "name": "Cuba", "alpha_code": ":flag_cu:", "aliases": ":cu:" }, "1f1e8-1f1fe": { "output": "1f1e8-1f1fe", "name": "Cyprus", "alpha_code": ":flag_cy:", "aliases": ":cy:" }, "1f1e8-1f1ff": { "output": "1f1e8-1f1ff", "name": "Czechia", "alpha_code": ":flag_cz:", "aliases": ":cz:" }, "1f919-1f3ff": { "output": "1f919-1f3ff", "name": "call me hand: dark skin tone", "alpha_code": ":call_me_tone5:", "aliases": ":call_me_hand_tone5:" }, "1f1e8-1f1e9": { "output": "1f1e8-1f1e9", "name": "Congo - Kinshasa", "alpha_code": ":flag_cd:", "aliases": ":congo:" }, "1f91b-1f3fb": { "output": "1f91b-1f3fb", "name": "left-facing fist: light skin tone", "alpha_code": ":left_facing_fist_tone1:", "aliases": ":left_fist_tone1:" }, "1f1e9-1f1f0": { "output": "1f1e9-1f1f0", "name": "Denmark", "alpha_code": ":flag_dk:", "aliases": ":dk:" }, "1f1e9-1f1ef": { "output": "1f1e9-1f1ef", "name": "Djibouti", "alpha_code": ":flag_dj:", "aliases": ":dj:" }, "1f1e9-1f1f2": { "output": "1f1e9-1f1f2", "name": "Dominica", "alpha_code": ":flag_dm:", "aliases": ":dm:" }, "1f1e9-1f1f4": { "output": "1f1e9-1f1f4", "name": "Dominican Republic", "alpha_code": ":flag_do:", "aliases": ":do:" }, "1f1f9-1f1f1": { "output": "1f1f9-1f1f1", "name": "Timor-Leste", "alpha_code": ":flag_tl:", "aliases": ":tl:" }, "1f1ea-1f1e8": { "output": "1f1ea-1f1e8", "name": "Ecuador", "alpha_code": ":flag_ec:", "aliases": ":ec:" }, "1f1ea-1f1ec": { "output": "1f1ea-1f1ec", "name": "Egypt", "alpha_code": ":flag_eg:", "aliases": ":eg:" }, "1f1f8-1f1fb": { "output": "1f1f8-1f1fb", "name": "El Salvador", "alpha_code": ":flag_sv:", "aliases": ":sv:" }, "1f1ec-1f1f6": { "output": "1f1ec-1f1f6", "name": "Equatorial Guinea", "alpha_code": ":flag_gq:", "aliases": ":gq:" }, "1f1ea-1f1f7": { "output": "1f1ea-1f1f7", "name": "Eritrea", "alpha_code": ":flag_er:", "aliases": ":er:" }, "1f1ea-1f1ea": { "output": "1f1ea-1f1ea", "name": "Estonia", "alpha_code": ":flag_ee:", "aliases": ":ee:" }, "1f1ea-1f1f9": { "output": "1f1ea-1f1f9", "name": "Ethiopia", "alpha_code": ":flag_et:", "aliases": ":et:" }, "1f91b-1f3fc": { "output": "1f91b-1f3fc", "name": "left-facing fist: medium-light skin tone", "alpha_code": ":left_facing_fist_tone2:", "aliases": ":left_fist_tone2:" }, "1f1eb-1f1ef": { "output": "1f1eb-1f1ef", "name": "Fiji", "alpha_code": ":flag_fj:", "aliases": ":fj:" }, "1f1eb-1f1ee": { "output": "1f1eb-1f1ee", "name": "Finland", "alpha_code": ":flag_fi:", "aliases": ":fi:" }, "1f1ec-1f1e6": { "output": "1f1ec-1f1e6", "name": "Gabon", "alpha_code": ":flag_ga:", "aliases": ":ga:" }, "1f1ec-1f1f2": { "output": "1f1ec-1f1f2", "name": "Gambia", "alpha_code": ":flag_gm:", "aliases": ":gm:" }, "1f1ec-1f1ea": { "output": "1f1ec-1f1ea", "name": "Georgia", "alpha_code": ":flag_ge:", "aliases": ":ge:" }, "1f1ec-1f1ed": { "output": "1f1ec-1f1ed", "name": "Ghana", "alpha_code": ":flag_gh:", "aliases": ":gh:" }, "1f1ec-1f1f7": { "output": "1f1ec-1f1f7", "name": "Greece", "alpha_code": ":flag_gr:", "aliases": ":gr:" }, "1f1ec-1f1e9": { "output": "1f1ec-1f1e9", "name": "Grenada", "alpha_code": ":flag_gd:", "aliases": ":gd:" }, "1f1ec-1f1f9": { "output": "1f1ec-1f1f9", "name": "Guatemala", "alpha_code": ":flag_gt:", "aliases": ":gt:" }, "1f1ec-1f1f3": { "output": "1f1ec-1f1f3", "name": "Guinea", "alpha_code": ":flag_gn:", "aliases": ":gn:" }, "1f1ec-1f1fc": { "output": "1f1ec-1f1fc", "name": "Guinea-Bissau", "alpha_code": ":flag_gw:", "aliases": ":gw:" }, "1f1ec-1f1fe": { "output": "1f1ec-1f1fe", "name": "Guyana", "alpha_code": ":flag_gy:", "aliases": ":gy:" }, "1f1ed-1f1f9": { "output": "1f1ed-1f1f9", "name": "Haiti", "alpha_code": ":flag_ht:", "aliases": ":ht:" }, "1f1ed-1f1f3": { "output": "1f1ed-1f1f3", "name": "Honduras", "alpha_code": ":flag_hn:", "aliases": ":hn:" }, "1f1ed-1f1fa": { "output": "1f1ed-1f1fa", "name": "Hungary", "alpha_code": ":flag_hu:", "aliases": ":hu:" }, "1f1ee-1f1f8": { "output": "1f1ee-1f1f8", "name": "Iceland", "alpha_code": ":flag_is:", "aliases": ":is:" }, "1f1ee-1f1f3": { "output": "1f1ee-1f1f3", "name": "India", "alpha_code": ":flag_in:", "aliases": ":in:" }, "1f1ee-1f1e9": { "output": "1f1ee-1f1e9", "name": "Indonesia", "alpha_code": ":flag_id:", "aliases": ":indonesia:" }, "1f1ee-1f1f7": { "output": "1f1ee-1f1f7", "name": "Iran", "alpha_code": ":flag_ir:", "aliases": ":ir:" }, "1f1ee-1f1f6": { "output": "1f1ee-1f1f6", "name": "Iraq", "alpha_code": ":flag_iq:", "aliases": ":iq:" }, "1f1ee-1f1ea": { "output": "1f1ee-1f1ea", "name": "Ireland", "alpha_code": ":flag_ie:", "aliases": ":ie:" }, "1f1ee-1f1f1": { "output": "1f1ee-1f1f1", "name": "Israel", "alpha_code": ":flag_il:", "aliases": ":il:" }, "1f1ef-1f1f2": { "output": "1f1ef-1f1f2", "name": "Jamaica", "alpha_code": ":flag_jm:", "aliases": ":jm:" }, "1f1ef-1f1f4": { "output": "1f1ef-1f1f4", "name": "Jordan", "alpha_code": ":flag_jo:", "aliases": ":jo:" }, "1f1f0-1f1ff": { "output": "1f1f0-1f1ff", "name": "Kazakhstan", "alpha_code": ":flag_kz:", "aliases": ":kz:" }, "1f1f0-1f1ea": { "output": "1f1f0-1f1ea", "name": "Kenya", "alpha_code": ":flag_ke:", "aliases": ":ke:" }, "1f1f0-1f1ee": { "output": "1f1f0-1f1ee", "name": "Kiribati", "alpha_code": ":flag_ki:", "aliases": ":ki:" }, "1f1fd-1f1f0": { "output": "1f1fd-1f1f0", "name": "Kosovo", "alpha_code": ":flag_xk:", "aliases": ":xk:" }, "1f1f0-1f1fc": { "output": "1f1f0-1f1fc", "name": "Kuwait", "alpha_code": ":flag_kw:", "aliases": ":kw:" }, "1f1f0-1f1ec": { "output": "1f1f0-1f1ec", "name": "Kyrgyzstan", "alpha_code": ":flag_kg:", "aliases": ":kg:" }, "1f91b-1f3fd": { "output": "1f91b-1f3fd", "name": "left-facing fist: medium skin tone", "alpha_code": ":left_facing_fist_tone3:", "aliases": ":left_fist_tone3:" }, "1f1f1-1f1e6": { "output": "1f1f1-1f1e6", "name": "Laos", "alpha_code": ":flag_la:", "aliases": ":la:" }, "1f1f1-1f1fb": { "output": "1f1f1-1f1fb", "name": "Latvia", "alpha_code": ":flag_lv:", "aliases": ":lv:" }, "1f1f1-1f1e7": { "output": "1f1f1-1f1e7", "name": "Lebanon", "alpha_code": ":flag_lb:", "aliases": ":lb:" }, "1f1f1-1f1f8": { "output": "1f1f1-1f1f8", "name": "Lesotho", "alpha_code": ":flag_ls:", "aliases": ":ls:" }, "1f1f1-1f1f7": { "output": "1f1f1-1f1f7", "name": "Liberia", "alpha_code": ":flag_lr:", "aliases": ":lr:" }, "1f1f1-1f1fe": { "output": "1f1f1-1f1fe", "name": "Libya", "alpha_code": ":flag_ly:", "aliases": ":ly:" }, "1f1f1-1f1ee": { "output": "1f1f1-1f1ee", "name": "Liechtenstein", "alpha_code": ":flag_li:", "aliases": ":li:" }, "1f1f1-1f1f9": { "output": "1f1f1-1f1f9", "name": "Lithuania", "alpha_code": ":flag_lt:", "aliases": ":lt:" }, "1f1f1-1f1fa": { "output": "1f1f1-1f1fa", "name": "Luxembourg", "alpha_code": ":flag_lu:", "aliases": ":lu:" }, "1f1f2-1f1f0": { "output": "1f1f2-1f1f0", "name": "Macedonia", "alpha_code": ":flag_mk:", "aliases": ":mk:" }, "1f1f2-1f1ec": { "output": "1f1f2-1f1ec", "name": "Madagascar", "alpha_code": ":flag_mg:", "aliases": ":mg:" }, "1f1f2-1f1fc": { "output": "1f1f2-1f1fc", "name": "Malawi", "alpha_code": ":flag_mw:", "aliases": ":mw:" }, "1f1f2-1f1fe": { "output": "1f1f2-1f1fe", "name": "Malaysia", "alpha_code": ":flag_my:", "aliases": ":my:" }, "1f1f2-1f1fb": { "output": "1f1f2-1f1fb", "name": "Maldives", "alpha_code": ":flag_mv:", "aliases": ":mv:" }, "1f1f2-1f1f1": { "output": "1f1f2-1f1f1", "name": "Mali", "alpha_code": ":flag_ml:", "aliases": ":ml:" }, "1f1f2-1f1f9": { "output": "1f1f2-1f1f9", "name": "Malta", "alpha_code": ":flag_mt:", "aliases": ":mt:" }, "1f1f2-1f1ed": { "output": "1f1f2-1f1ed", "name": "Marshall Islands", "alpha_code": ":flag_mh:", "aliases": ":mh:" }, "1f1f2-1f1f7": { "output": "1f1f2-1f1f7", "name": "Mauritania", "alpha_code": ":flag_mr:", "aliases": ":mr:" }, "1f1f2-1f1fa": { "output": "1f1f2-1f1fa", "name": "Mauritius", "alpha_code": ":flag_mu:", "aliases": ":mu:" }, "1f1f2-1f1fd": { "output": "1f1f2-1f1fd", "name": "Mexico", "alpha_code": ":flag_mx:", "aliases": ":mx:" }, "1f1eb-1f1f2": { "output": "1f1eb-1f1f2", "name": "Micronesia", "alpha_code": ":flag_fm:", "aliases": ":fm:" }, "1f1f2-1f1e9": { "output": "1f1f2-1f1e9", "name": "Moldova", "alpha_code": ":flag_md:", "aliases": ":md:" }, "1f1f2-1f1e8": { "output": "1f1f2-1f1e8", "name": "Monaco", "alpha_code": ":flag_mc:", "aliases": ":mc:" }, "1f1f2-1f1f3": { "output": "1f1f2-1f1f3", "name": "Mongolia", "alpha_code": ":flag_mn:", "aliases": ":mn:" }, "1f1f2-1f1ea": { "output": "1f1f2-1f1ea", "name": "Montenegro", "alpha_code": ":flag_me:", "aliases": ":me:" }, "1f1f2-1f1e6": { "output": "1f1f2-1f1e6", "name": "Morocco", "alpha_code": ":flag_ma:", "aliases": ":ma:" }, "1f1f2-1f1ff": { "output": "1f1f2-1f1ff", "name": "Mozambique", "alpha_code": ":flag_mz:", "aliases": ":mz:" }, "1f1f2-1f1f2": { "output": "1f1f2-1f1f2", "name": "Myanmar (Burma)", "alpha_code": ":flag_mm:", "aliases": ":mm:" }, "1f1f3-1f1e6": { "output": "1f1f3-1f1e6", "name": "Namibia", "alpha_code": ":flag_na:", "aliases": ":na:" }, "1f1f3-1f1f7": { "output": "1f1f3-1f1f7", "name": "Nauru", "alpha_code": ":flag_nr:", "aliases": ":nr:" }, "1f1f3-1f1f5": { "output": "1f1f3-1f1f5", "name": "Nepal", "alpha_code": ":flag_np:", "aliases": ":np:" }, "1f1f3-1f1f1": { "output": "1f1f3-1f1f1", "name": "Netherlands", "alpha_code": ":flag_nl:", "aliases": ":nl:" }, "1f1f3-1f1ff": { "output": "1f1f3-1f1ff", "name": "New Zealand", "alpha_code": ":flag_nz:", "aliases": ":nz:" }, "1f1f3-1f1ee": { "output": "1f1f3-1f1ee", "name": "Nicaragua", "alpha_code": ":flag_ni:", "aliases": ":ni:" }, "1f1f3-1f1ea": { "output": "1f1f3-1f1ea", "name": "Niger", "alpha_code": ":flag_ne:", "aliases": ":ne:" }, "1f1f3-1f1ec": { "output": "1f1f3-1f1ec", "name": "Nigeria", "alpha_code": ":flag_ng:", "aliases": ":nigeria:" }, "1f1f0-1f1f5": { "output": "1f1f0-1f1f5", "name": "North Korea", "alpha_code": ":flag_kp:", "aliases": ":kp:" }, "1f1f3-1f1f4": { "output": "1f1f3-1f1f4", "name": "Norway", "alpha_code": ":flag_no:", "aliases": ":no:" }, "1f1f4-1f1f2": { "output": "1f1f4-1f1f2", "name": "Oman", "alpha_code": ":flag_om:", "aliases": ":om:" }, "1f1f5-1f1f0": { "output": "1f1f5-1f1f0", "name": "Pakistan", "alpha_code": ":flag_pk:", "aliases": ":pk:" }, "1f1f5-1f1fc": { "output": "1f1f5-1f1fc", "name": "Palau", "alpha_code": ":flag_pw:", "aliases": ":pw:" }, "1f1f5-1f1e6": { "output": "1f1f5-1f1e6", "name": "Panama", "alpha_code": ":flag_pa:", "aliases": ":pa:" }, "1f1f5-1f1ec": { "output": "1f1f5-1f1ec", "name": "Papua New Guinea", "alpha_code": ":flag_pg:", "aliases": ":pg:" }, "1f91b-1f3fe": { "output": "1f91b-1f3fe", "name": "left-facing fist: medium-dark skin tone", "alpha_code": ":left_facing_fist_tone4:", "aliases": ":left_fist_tone4:" }, "1f1f5-1f1fe": { "output": "1f1f5-1f1fe", "name": "Paraguay", "alpha_code": ":flag_py:", "aliases": ":py:" }, "1f1f5-1f1ea": { "output": "1f1f5-1f1ea", "name": "Peru", "alpha_code": ":flag_pe:", "aliases": ":pe:" }, "1f1f5-1f1ed": { "output": "1f1f5-1f1ed", "name": "Philippines", "alpha_code": ":flag_ph:", "aliases": ":ph:" }, "1f1f5-1f1f1": { "output": "1f1f5-1f1f1", "name": "Poland", "alpha_code": ":flag_pl:", "aliases": ":pl:" }, "1f1f5-1f1f9": { "output": "1f1f5-1f1f9", "name": "Portugal", "alpha_code": ":flag_pt:", "aliases": ":pt:" }, "1f1f6-1f1e6": { "output": "1f1f6-1f1e6", "name": "Qatar", "alpha_code": ":flag_qa:", "aliases": ":qa:" }, "1f1f9-1f1fc": { "output": "1f1f9-1f1fc", "name": "Taiwan", "alpha_code": ":flag_tw:", "aliases": ":tw:" }, "1f1e8-1f1ec": { "output": "1f1e8-1f1ec", "name": "Congo - Brazzaville", "alpha_code": ":flag_cg:", "aliases": ":cg:" }, "1f1f7-1f1f4": { "output": "1f1f7-1f1f4", "name": "Romania", "alpha_code": ":flag_ro:", "aliases": ":ro:" }, "1f1f7-1f1fc": { "output": "1f1f7-1f1fc", "name": "Rwanda", "alpha_code": ":flag_rw:", "aliases": ":rw:" }, "1f1f0-1f1f3": { "output": "1f1f0-1f1f3", "name": "St. Kitts & Nevis", "alpha_code": ":flag_kn:", "aliases": ":kn:" }, "1f1f1-1f1e8": { "output": "1f1f1-1f1e8", "name": "St. Lucia", "alpha_code": ":flag_lc:", "aliases": ":lc:" }, "1f1fb-1f1e8": { "output": "1f1fb-1f1e8", "name": "St. Vincent & Grenadines", "alpha_code": ":flag_vc:", "aliases": ":vc:" }, "1f1fc-1f1f8": { "output": "1f1fc-1f1f8", "name": "Samoa", "alpha_code": ":flag_ws:", "aliases": ":ws:" }, "1f1f8-1f1f2": { "output": "1f1f8-1f1f2", "name": "San Marino", "alpha_code": ":flag_sm:", "aliases": ":sm:" }, "1f1f8-1f1f9": { "output": "1f1f8-1f1f9", "name": "S\u00e3o Tom\u00e9 & Pr\u00edncipe", "alpha_code": ":flag_st:", "aliases": ":st:" }, "1f1f8-1f1e6": { "output": "1f1f8-1f1e6", "name": "Saudi Arabia", "alpha_code": ":flag_sa:", "aliases": ":saudiarabia:|:saudi:" }, "1f91b-1f3ff": { "output": "1f91b-1f3ff", "name": "left-facing fist: dark skin tone", "alpha_code": ":left_facing_fist_tone5:", "aliases": ":left_fist_tone5:" }, "1f1f8-1f1f3": { "output": "1f1f8-1f1f3", "name": "Senegal", "alpha_code": ":flag_sn:", "aliases": ":sn:" }, "1f1f7-1f1f8": { "output": "1f1f7-1f1f8", "name": "Serbia", "alpha_code": ":flag_rs:", "aliases": ":rs:" }, "1f1f8-1f1e8": { "output": "1f1f8-1f1e8", "name": "Seychelles", "alpha_code": ":flag_sc:", "aliases": ":sc:" }, "1f1f8-1f1f1": { "output": "1f1f8-1f1f1", "name": "Sierra Leone", "alpha_code": ":flag_sl:", "aliases": ":sl:" }, "1f1f8-1f1ec": { "output": "1f1f8-1f1ec", "name": "Singapore", "alpha_code": ":flag_sg:", "aliases": ":sg:" }, "1f1f8-1f1f0": { "output": "1f1f8-1f1f0", "name": "Slovakia", "alpha_code": ":flag_sk:", "aliases": ":sk:" }, "1f1f8-1f1ee": { "output": "1f1f8-1f1ee", "name": "Slovenia", "alpha_code": ":flag_si:", "aliases": ":si:" }, "1f1f8-1f1e7": { "output": "1f1f8-1f1e7", "name": "Solomon Islands", "alpha_code": ":flag_sb:", "aliases": ":sb:" }, "1f1f8-1f1f4": { "output": "1f1f8-1f1f4", "name": "Somalia", "alpha_code": ":flag_so:", "aliases": ":so:" }, "1f1ff-1f1e6": { "output": "1f1ff-1f1e6", "name": "South Africa", "alpha_code": ":flag_za:", "aliases": ":za:" }, "1f1f1-1f1f0": { "output": "1f1f1-1f1f0", "name": "Sri Lanka", "alpha_code": ":flag_lk:", "aliases": ":lk:" }, "1f1f8-1f1e9": { "output": "1f1f8-1f1e9", "name": "Sudan", "alpha_code": ":flag_sd:", "aliases": ":sd:" }, "1f1f8-1f1f7": { "output": "1f1f8-1f1f7", "name": "Suriname", "alpha_code": ":flag_sr:", "aliases": ":sr:" }, "1f1f8-1f1ff": { "output": "1f1f8-1f1ff", "name": "Swaziland", "alpha_code": ":flag_sz:", "aliases": ":sz:" }, "1f1f8-1f1ea": { "output": "1f1f8-1f1ea", "name": "Sweden", "alpha_code": ":flag_se:", "aliases": ":se:" }, "1f1e8-1f1ed": { "output": "1f1e8-1f1ed", "name": "Switzerland", "alpha_code": ":flag_ch:", "aliases": ":ch:" }, "1f1f8-1f1fe": { "output": "1f1f8-1f1fe", "name": "Syria", "alpha_code": ":flag_sy:", "aliases": ":sy:" }, "1f1f9-1f1ef": { "output": "1f1f9-1f1ef", "name": "Tajikistan", "alpha_code": ":flag_tj:", "aliases": ":tj:" }, "1f1f9-1f1ff": { "output": "1f1f9-1f1ff", "name": "Tanzania", "alpha_code": ":flag_tz:", "aliases": ":tz:" }, "1f1f9-1f1ed": { "output": "1f1f9-1f1ed", "name": "Thailand", "alpha_code": ":flag_th:", "aliases": ":th:" }, "1f1f9-1f1ec": { "output": "1f1f9-1f1ec", "name": "Togo", "alpha_code": ":flag_tg:", "aliases": ":tg:" }, "1f1f9-1f1f4": { "output": "1f1f9-1f1f4", "name": "Tonga", "alpha_code": ":flag_to:", "aliases": ":to:" }, "1f1f9-1f1f9": { "output": "1f1f9-1f1f9", "name": "Trinidad & Tobago", "alpha_code": ":flag_tt:", "aliases": ":tt:" }, "1f1f9-1f1f3": { "output": "1f1f9-1f1f3", "name": "Tunisia", "alpha_code": ":flag_tn:", "aliases": ":tn:" }, "1f1f9-1f1f7": { "output": "1f1f9-1f1f7", "name": "Turkey", "alpha_code": ":flag_tr:", "aliases": ":tr:" }, "1f1f9-1f1f2": { "output": "1f1f9-1f1f2", "name": "Turkmenistan", "alpha_code": ":flag_tm:", "aliases": ":turkmenistan:" }, "1f1f9-1f1fb": { "output": "1f1f9-1f1fb", "name": "Tuvalu", "alpha_code": ":flag_tv:", "aliases": ":tuvalu:" }, "1f1fa-1f1ec": { "output": "1f1fa-1f1ec", "name": "Uganda", "alpha_code": ":flag_ug:", "aliases": ":ug:" }, "1f1fa-1f1e6": { "output": "1f1fa-1f1e6", "name": "Ukraine", "alpha_code": ":flag_ua:", "aliases": ":ua:" }, "1f1e6-1f1ea": { "output": "1f1e6-1f1ea", "name": "United Arab Emirates", "alpha_code": ":flag_ae:", "aliases": ":ae:" }, "1f1fa-1f1fe": { "output": "1f1fa-1f1fe", "name": "Uruguay", "alpha_code": ":flag_uy:", "aliases": ":uy:" }, "1f1fa-1f1ff": { "output": "1f1fa-1f1ff", "name": "Uzbekistan", "alpha_code": ":flag_uz:", "aliases": ":uz:" }, "1f1fb-1f1fa": { "output": "1f1fb-1f1fa", "name": "Vanuatu", "alpha_code": ":flag_vu:", "aliases": ":vu:" }, "1f1fb-1f1e6": { "output": "1f1fb-1f1e6", "name": "Vatican City", "alpha_code": ":flag_va:", "aliases": ":va:" }, "1f1fb-1f1ea": { "output": "1f1fb-1f1ea", "name": "Venezuela", "alpha_code": ":flag_ve:", "aliases": ":ve:" }, "1f1fb-1f1f3": { "output": "1f1fb-1f1f3", "name": "Vietnam", "alpha_code": ":flag_vn:", "aliases": ":vn:" }, "1f1ea-1f1ed": { "output": "1f1ea-1f1ed", "name": "Western Sahara", "alpha_code": ":flag_eh:", "aliases": ":eh:" }, "1f91c-1f3fb": { "output": "1f91c-1f3fb", "name": "right-facing fist: light skin tone", "alpha_code": ":right_facing_fist_tone1:", "aliases": ":right_fist_tone1:" }, "1f1fe-1f1ea": { "output": "1f1fe-1f1ea", "name": "Yemen", "alpha_code": ":flag_ye:", "aliases": ":ye:" }, "1f1ff-1f1f2": { "output": "1f1ff-1f1f2", "name": "Zambia", "alpha_code": ":flag_zm:", "aliases": ":zm:" }, "1f1ff-1f1fc": { "output": "1f1ff-1f1fc", "name": "Zimbabwe", "alpha_code": ":flag_zw:", "aliases": ":zw:" }, "1f1f5-1f1f7": { "output": "1f1f5-1f1f7", "name": "Puerto Rico", "alpha_code": ":flag_pr:", "aliases": ":pr:" }, "1f1f0-1f1fe": { "output": "1f1f0-1f1fe", "name": "Cayman Islands", "alpha_code": ":flag_ky:", "aliases": ":ky:" }, "1f1e7-1f1f2": { "output": "1f1e7-1f1f2", "name": "Bermuda", "alpha_code": ":flag_bm:", "aliases": ":bm:" }, "1f1f5-1f1eb": { "output": "1f1f5-1f1eb", "name": "French Polynesia", "alpha_code": ":flag_pf:", "aliases": ":pf:" }, "1f1f5-1f1f8": { "output": "1f1f5-1f1f8", "name": "Palestinian Territories", "alpha_code": ":flag_ps:", "aliases": ":ps:" }, "1f1f3-1f1e8": { "output": "1f1f3-1f1e8", "name": "New Caledonia", "alpha_code": ":flag_nc:", "aliases": ":nc:" }, "1f91c-1f3fc": { "output": "1f91c-1f3fc", "name": "right-facing fist: medium-light skin tone", "alpha_code": ":right_facing_fist_tone2:", "aliases": ":right_fist_tone2:" }, "1f1f8-1f1ed": { "output": "1f1f8-1f1ed", "name": "St. Helena", "alpha_code": ":flag_sh:", "aliases": ":sh:" }, "1f1e6-1f1fc": { "output": "1f1e6-1f1fc", "name": "Aruba", "alpha_code": ":flag_aw:", "aliases": ":aw:" }, "1f1fb-1f1ee": { "output": "1f1fb-1f1ee", "name": "U.S. Virgin Islands", "alpha_code": ":flag_vi:", "aliases": ":vi:" }, "1f1ed-1f1f0": { "output": "1f1ed-1f1f0", "name": "Hong Kong SAR China", "alpha_code": ":flag_hk:", "aliases": ":hk:" }, "1f1e6-1f1e8": { "output": "1f1e6-1f1e8", "name": "Ascension Island", "alpha_code": ":flag_ac:", "aliases": ":ac:" }, "1f1f2-1f1f8": { "output": "1f1f2-1f1f8", "name": "Montserrat", "alpha_code": ":flag_ms:", "aliases": ":ms:" }, "1f1ec-1f1fa": { "output": "1f1ec-1f1fa", "name": "Guam", "alpha_code": ":flag_gu:", "aliases": ":gu:" }, "1f1ec-1f1f1": { "output": "1f1ec-1f1f1", "name": "Greenland", "alpha_code": ":flag_gl:", "aliases": ":gl:" }, "1f1f3-1f1fa": { "output": "1f1f3-1f1fa", "name": "Niue", "alpha_code": ":flag_nu:", "aliases": ":nu:" }, "1f1fc-1f1eb": { "output": "1f1fc-1f1eb", "name": "Wallis & Futuna", "alpha_code": ":flag_wf:", "aliases": ":wf:" }, "1f1f2-1f1f4": { "output": "1f1f2-1f1f4", "name": "Macau SAR China", "alpha_code": ":flag_mo:", "aliases": ":mo:" }, "1f91c-1f3fd": { "output": "1f91c-1f3fd", "name": "right-facing fist: medium skin tone", "alpha_code": ":right_facing_fist_tone3:", "aliases": ":right_fist_tone3:" }, "1f1eb-1f1f4": { "output": "1f1eb-1f1f4", "name": "Faroe Islands", "alpha_code": ":flag_fo:", "aliases": ":fo:" }, "1f1eb-1f1f0": { "output": "1f1eb-1f1f0", "name": "Falkland Islands", "alpha_code": ":flag_fk:", "aliases": ":fk:" }, "1f1ef-1f1ea": { "output": "1f1ef-1f1ea", "name": "Jersey", "alpha_code": ":flag_je:", "aliases": ":je:" }, "1f1e6-1f1ee": { "output": "1f1e6-1f1ee", "name": "Anguilla", "alpha_code": ":flag_ai:", "aliases": ":ai:" }, "1f1ec-1f1ee": { "output": "1f1ec-1f1ee", "name": "Gibraltar", "alpha_code": ":flag_gi:", "aliases": ":gi:" }, "1f39e": { "output": "1f39e-fe0f", "name": "film frames", "alpha_code": ":film_frames:", "aliases": "" }, "1f39f": { "output": "1f39f-fe0f", "name": "admission tickets", "alpha_code": ":tickets:", "aliases": ":admission_tickets:" }, "1f3c5": { "output": "1f3c5", "name": "sports medal", "alpha_code": ":medal:", "aliases": ":sports_medal:" }, "1f3cb": { "output": "1f3cb-fe0f", "name": "person lifting weights", "alpha_code": ":person_lifting_weights:", "aliases": ":lifter:|:weight_lifter:" }, "1f3cc": { "output": "1f3cc-fe0f", "name": "person golfing", "alpha_code": ":person_golfing:", "aliases": ":golfer:" }, "1f3cd": { "output": "1f3cd-fe0f", "name": "motorcycle", "alpha_code": ":motorcycle:", "aliases": ":racing_motorcycle:" }, "1f3ce": { "output": "1f3ce-fe0f", "name": "racing car", "alpha_code": ":race_car:", "aliases": ":racing_car:" }, "1f396": { "output": "1f396-fe0f", "name": "military medal", "alpha_code": ":military_medal:", "aliases": "" }, "1f397": { "output": "1f397-fe0f", "name": "reminder ribbon", "alpha_code": ":reminder_ribbon:", "aliases": "" }, "1f336": { "output": "1f336-fe0f", "name": "hot pepper", "alpha_code": ":hot_pepper:", "aliases": "" }, "1f91c-1f3fe": { "output": "1f91c-1f3fe", "name": "right-facing fist: medium-dark skin tone", "alpha_code": ":right_facing_fist_tone4:", "aliases": ":right_fist_tone4:" }, "1f327": { "output": "1f327-fe0f", "name": "cloud with rain", "alpha_code": ":cloud_rain:", "aliases": ":cloud_with_rain:" }, "1f328": { "output": "1f328-fe0f", "name": "cloud with snow", "alpha_code": ":cloud_snow:", "aliases": ":cloud_with_snow:" }, "1f329": { "output": "1f329-fe0f", "name": "cloud with lightning", "alpha_code": ":cloud_lightning:", "aliases": ":cloud_with_lightning:" }, "1f32a": { "output": "1f32a-fe0f", "name": "tornado", "alpha_code": ":cloud_tornado:", "aliases": ":cloud_with_tornado:" }, "1f32b": { "output": "1f32b-fe0f", "name": "fog", "alpha_code": ":fog:", "aliases": "" }, "1f32c": { "output": "1f32c-fe0f", "name": "wind face", "alpha_code": ":wind_blowing_face:", "aliases": "" }, "1f43f": { "output": "1f43f-fe0f", "name": "chipmunk", "alpha_code": ":chipmunk:", "aliases": "" }, "1f577": { "output": "1f577-fe0f", "name": "spider", "alpha_code": ":spider:", "aliases": "" }, "1f578": { "output": "1f578-fe0f", "name": "spider web", "alpha_code": ":spider_web:", "aliases": "" }, "1f321": { "output": "1f321-fe0f", "name": "thermometer", "alpha_code": ":thermometer:", "aliases": "" }, "1f399": { "output": "1f399-fe0f", "name": "studio microphone", "alpha_code": ":microphone2:", "aliases": ":studio_microphone:" }, "1f39a": { "output": "1f39a-fe0f", "name": "level slider", "alpha_code": ":level_slider:", "aliases": "" }, "1f39b": { "output": "1f39b-fe0f", "name": "control knobs", "alpha_code": ":control_knobs:", "aliases": "" }, "1f3f3": { "output": "1f3f3-fe0f", "name": "white flag", "alpha_code": ":flag_white:", "aliases": ":waving_white_flag:" }, "1f3f4": { "output": "1f3f4", "name": "black flag", "alpha_code": ":flag_black:", "aliases": ":waving_black_flag:" }, "1f3f5": { "output": "1f3f5-fe0f", "name": "rosette", "alpha_code": ":rosette:", "aliases": "" }, "1f3f7": { "output": "1f3f7-fe0f", "name": "label", "alpha_code": ":label:", "aliases": "" }, "1f4f8": { "output": "1f4f8", "name": "camera with flash", "alpha_code": ":camera_with_flash:", "aliases": "" }, "1f4fd": { "output": "1f4fd-fe0f", "name": "film projector", "alpha_code": ":projector:", "aliases": ":film_projector:" }, "271d": { "output": "271d-fe0f", "name": "latin cross", "alpha_code": ":cross:", "aliases": ":latin_cross:" }, "1f549": { "output": "1f549-fe0f", "name": "om", "alpha_code": ":om_symbol:", "aliases": "" }, "1f54a": { "output": "1f54a-fe0f", "name": "dove", "alpha_code": ":dove:", "aliases": ":dove_of_peace:" }, "1f56f": { "output": "1f56f-fe0f", "name": "candle", "alpha_code": ":candle:", "aliases": "" }, "1f570": { "output": "1f570-fe0f", "name": "mantelpiece clock", "alpha_code": ":clock:", "aliases": ":mantlepiece_clock:" }, "1f573": { "output": "1f573-fe0f", "name": "hole", "alpha_code": ":hole:", "aliases": "" }, "1f576": { "output": "1f576-fe0f", "name": "sunglasses", "alpha_code": ":dark_sunglasses:", "aliases": "" }, "1f579": { "output": "1f579-fe0f", "name": "joystick", "alpha_code": ":joystick:", "aliases": "" }, "1f587": { "output": "1f587-fe0f", "name": "linked paperclips", "alpha_code": ":paperclips:", "aliases": ":linked_paperclips:" }, "1f58a": { "output": "1f58a-fe0f", "name": "pen", "alpha_code": ":pen_ballpoint:", "aliases": ":lower_left_ballpoint_pen:" }, "1f58b": { "output": "1f58b-fe0f", "name": "fountain pen", "alpha_code": ":pen_fountain:", "aliases": ":lower_left_fountain_pen:" }, "1f58c": { "output": "1f58c-fe0f", "name": "paintbrush", "alpha_code": ":paintbrush:", "aliases": ":lower_left_paintbrush:" }, "1f58d": { "output": "1f58d-fe0f", "name": "crayon", "alpha_code": ":crayon:", "aliases": ":lower_left_crayon:" }, "1f5a5": { "output": "1f5a5-fe0f", "name": "desktop computer", "alpha_code": ":desktop:", "aliases": ":desktop_computer:" }, "1f5a8": { "output": "1f5a8-fe0f", "name": "printer", "alpha_code": ":printer:", "aliases": "" }, "1f91c-1f3ff": { "output": "1f91c-1f3ff", "name": "right-facing fist: dark skin tone", "alpha_code": ":right_facing_fist_tone5:", "aliases": ":right_fist_tone5:" }, "2328": { "output": "2328-fe0f", "name": "keyboard", "alpha_code": ":keyboard:", "aliases": "" }, "1f5b2": { "output": "1f5b2-fe0f", "name": "trackball", "alpha_code": ":trackball:", "aliases": "" }, "1f91a-1f3fb": { "output": "1f91a-1f3fb", "name": "raised back of hand: light skin tone", "alpha_code": ":raised_back_of_hand_tone1:", "aliases": ":back_of_hand_tone1:" }, "1f5bc": { "output": "1f5bc-fe0f", "name": "framed picture", "alpha_code": ":frame_photo:", "aliases": ":frame_with_picture:" }, "1f5c2": { "output": "1f5c2-fe0f", "name": "card index dividers", "alpha_code": ":dividers:", "aliases": ":card_index_dividers:" }, "1f5c3": { "output": "1f5c3-fe0f", "name": "card file box", "alpha_code": ":card_box:", "aliases": ":card_file_box:" }, "1f5c4": { "output": "1f5c4-fe0f", "name": "file cabinet", "alpha_code": ":file_cabinet:", "aliases": "" }, "1f5d1": { "output": "1f5d1-fe0f", "name": "wastebasket", "alpha_code": ":wastebasket:", "aliases": "" }, "1f5d2": { "output": "1f5d2-fe0f", "name": "spiral notepad", "alpha_code": ":notepad_spiral:", "aliases": ":spiral_note_pad:" }, "1f5d3": { "output": "1f5d3-fe0f", "name": "spiral calendar", "alpha_code": ":calendar_spiral:", "aliases": ":spiral_calendar_pad:" }, "1f5dc": { "output": "1f5dc-fe0f", "name": "clamp", "alpha_code": ":compression:", "aliases": "" }, "1f5dd": { "output": "1f5dd-fe0f", "name": "old key", "alpha_code": ":key2:", "aliases": ":old_key:" }, "1f5de": { "output": "1f5de-fe0f", "name": "rolled-up newspaper", "alpha_code": ":newspaper2:", "aliases": ":rolled_up_newspaper:" }, "1f5e1": { "output": "1f5e1-fe0f", "name": "dagger", "alpha_code": ":dagger:", "aliases": ":dagger_knife:" }, "1f5e3": { "output": "1f5e3-fe0f", "name": "speaking head", "alpha_code": ":speaking_head:", "aliases": ":speaking_head_in_silhouette:" }, "1f5e8": { "output": "1f5e8-fe0f", "name": "left speech bubble", "alpha_code": ":speech_left:", "aliases": ":left_speech_bubble:" }, "1f91a-1f3fc": { "output": "1f91a-1f3fc", "name": "raised back of hand: medium-light skin tone", "alpha_code": ":raised_back_of_hand_tone2:", "aliases": ":back_of_hand_tone2:" }, "1f5ef": { "output": "1f5ef-fe0f", "name": "right anger bubble", "alpha_code": ":anger_right:", "aliases": ":right_anger_bubble:" }, "1f91a-1f3fd": { "output": "1f91a-1f3fd", "name": "raised back of hand: medium skin tone", "alpha_code": ":raised_back_of_hand_tone3:", "aliases": ":back_of_hand_tone3:" }, "1f5f3": { "output": "1f5f3-fe0f", "name": "ballot box with ballot", "alpha_code": ":ballot_box:", "aliases": ":ballot_box_with_ballot:" }, "1f5fa": { "output": "1f5fa-fe0f", "name": "world map", "alpha_code": ":map:", "aliases": ":world_map:" }, "1f6cc": { "output": "1f6cc", "name": "person in bed", "alpha_code": ":sleeping_accommodation:", "aliases": "" }, "1f6e0": { "output": "1f6e0-fe0f", "name": "hammer and wrench", "alpha_code": ":tools:", "aliases": ":hammer_and_wrench:" }, "1f6e1": { "output": "1f6e1-fe0f", "name": "shield", "alpha_code": ":shield:", "aliases": "" }, "1f6e2": { "output": "1f6e2-fe0f", "name": "oil drum", "alpha_code": ":oil:", "aliases": ":oil_drum:" }, "1f6f0": { "output": "1f6f0-fe0f", "name": "satellite", "alpha_code": ":satellite_orbital:", "aliases": "" }, "1f37d": { "output": "1f37d-fe0f", "name": "fork and knife with plate", "alpha_code": ":fork_knife_plate:", "aliases": ":fork_and_knife_with_plate:" }, "1f441": { "output": "1f441-fe0f", "name": "eye", "alpha_code": ":eye:", "aliases": "" }, "1f574": { "output": "1f574-fe0f", "name": "man in business suit levitating", "alpha_code": ":man_in_business_suit_levitating:", "aliases": "" }, "1f575": { "output": "1f575-fe0f", "name": "detective", "alpha_code": ":detective:", "aliases": ":spy:|:sleuth_or_spy:" }, "270d": { "output": "270d-fe0f", "name": "writing hand", "alpha_code": ":writing_hand:", "aliases": "" }, "1f590": { "output": "1f590-fe0f", "name": "raised hand with fingers splayed", "alpha_code": ":hand_splayed:", "aliases": ":raised_hand_with_fingers_splayed:" }, "1f595": { "output": "1f595", "name": "middle finger", "alpha_code": ":middle_finger:", "aliases": ":reversed_hand_with_middle_finger_extended:" }, "1f596": { "output": "1f596", "name": "vulcan salute", "alpha_code": ":vulcan:", "aliases": ":raised_hand_with_part_between_middle_and_ring_fingers:" }, "1f641": { "output": "1f641", "name": "slightly frowning face", "alpha_code": ":slight_frown:", "aliases": ":slightly_frowning_face:" }, "1f642": { "output": "1f642", "name": "slightly smiling face", "alpha_code": ":slight_smile:", "aliases": ":slightly_smiling_face:" }, "1f3d4": { "output": "1f3d4-fe0f", "name": "snow-capped mountain", "alpha_code": ":mountain_snow:", "aliases": ":snow_capped_mountain:" }, "1f3d5": { "output": "1f3d5-fe0f", "name": "camping", "alpha_code": ":camping:", "aliases": "" }, "1f3d6": { "output": "1f3d6-fe0f", "name": "beach with umbrella", "alpha_code": ":beach:", "aliases": ":beach_with_umbrella:" }, "1f3d7": { "output": "1f3d7-fe0f", "name": "building construction", "alpha_code": ":construction_site:", "aliases": ":building_construction:" }, "1f3d8": { "output": "1f3d8-fe0f", "name": "houses", "alpha_code": ":homes:", "aliases": ":house_buildings:" }, "1f3d9": { "output": "1f3d9-fe0f", "name": "cityscape", "alpha_code": ":cityscape:", "aliases": "" }, "1f3da": { "output": "1f3da-fe0f", "name": "derelict house", "alpha_code": ":house_abandoned:", "aliases": ":derelict_house_building:" }, "1f3db": { "output": "1f3db-fe0f", "name": "classical building", "alpha_code": ":classical_building:", "aliases": "" }, "1f3dc": { "output": "1f3dc-fe0f", "name": "desert", "alpha_code": ":desert:", "aliases": "" }, "1f3dd": { "output": "1f3dd-fe0f", "name": "desert island", "alpha_code": ":island:", "aliases": ":desert_island:" }, "1f3de": { "output": "1f3de-fe0f", "name": "national park", "alpha_code": ":park:", "aliases": ":national_park:" }, "1f3df": { "output": "1f3df-fe0f", "name": "stadium", "alpha_code": ":stadium:", "aliases": "" }, "1f6cb": { "output": "1f6cb-fe0f", "name": "couch and lamp", "alpha_code": ":couch:", "aliases": ":couch_and_lamp:" }, "1f91a-1f3fe": { "output": "1f91a-1f3fe", "name": "raised back of hand: medium-dark skin tone", "alpha_code": ":raised_back_of_hand_tone4:", "aliases": ":back_of_hand_tone4:" }, "1f6cd": { "output": "1f6cd-fe0f", "name": "shopping bags", "alpha_code": ":shopping_bags:", "aliases": "" }, "1f6ce": { "output": "1f6ce-fe0f", "name": "bellhop bell", "alpha_code": ":bellhop:", "aliases": ":bellhop_bell:" }, "1f6cf": { "output": "1f6cf-fe0f", "name": "bed", "alpha_code": ":bed:", "aliases": "" }, "1f6e3": { "output": "1f6e3-fe0f", "name": "motorway", "alpha_code": ":motorway:", "aliases": "" }, "1f6e4": { "output": "1f6e4-fe0f", "name": "railway track", "alpha_code": ":railway_track:", "aliases": ":railroad_track:" }, "1f6e5": { "output": "1f6e5-fe0f", "name": "motor boat", "alpha_code": ":motorboat:", "aliases": "" }, "1f6e9": { "output": "1f6e9-fe0f", "name": "small airplane", "alpha_code": ":airplane_small:", "aliases": ":small_airplane:" }, "1f6eb": { "output": "1f6eb", "name": "airplane departure", "alpha_code": ":airplane_departure:", "aliases": "" }, "1f6ec": { "output": "1f6ec", "name": "airplane arrival", "alpha_code": ":airplane_arriving:", "aliases": "" }, "1f6f3": { "output": "1f6f3-fe0f", "name": "passenger ship", "alpha_code": ":cruise_ship:", "aliases": ":passenger_ship:" }, "1f476-1f3fb": { "output": "1f476-1f3fb", "name": "baby: light skin tone", "alpha_code": ":baby_tone1:", "aliases": "" }, "1f476-1f3fc": { "output": "1f476-1f3fc", "name": "baby: medium-light skin tone", "alpha_code": ":baby_tone2:", "aliases": "" }, "1f476-1f3fd": { "output": "1f476-1f3fd", "name": "baby: medium skin tone", "alpha_code": ":baby_tone3:", "aliases": "" }, "1f476-1f3fe": { "output": "1f476-1f3fe", "name": "baby: medium-dark skin tone", "alpha_code": ":baby_tone4:", "aliases": "" }, "1f476-1f3ff": { "output": "1f476-1f3ff", "name": "baby: dark skin tone", "alpha_code": ":baby_tone5:", "aliases": "" }, "1f466-1f3fb": { "output": "1f466-1f3fb", "name": "boy: light skin tone", "alpha_code": ":boy_tone1:", "aliases": "" }, "1f466-1f3fc": { "output": "1f466-1f3fc", "name": "boy: medium-light skin tone", "alpha_code": ":boy_tone2:", "aliases": "" }, "1f466-1f3fd": { "output": "1f466-1f3fd", "name": "boy: medium skin tone", "alpha_code": ":boy_tone3:", "aliases": "" }, "1f466-1f3fe": { "output": "1f466-1f3fe", "name": "boy: medium-dark skin tone", "alpha_code": ":boy_tone4:", "aliases": "" }, "1f466-1f3ff": { "output": "1f466-1f3ff", "name": "boy: dark skin tone", "alpha_code": ":boy_tone5:", "aliases": "" }, "1f467-1f3fb": { "output": "1f467-1f3fb", "name": "girl: light skin tone", "alpha_code": ":girl_tone1:", "aliases": "" }, "1f467-1f3fc": { "output": "1f467-1f3fc", "name": "girl: medium-light skin tone", "alpha_code": ":girl_tone2:", "aliases": "" }, "1f467-1f3fd": { "output": "1f467-1f3fd", "name": "girl: medium skin tone", "alpha_code": ":girl_tone3:", "aliases": "" }, "1f467-1f3fe": { "output": "1f467-1f3fe", "name": "girl: medium-dark skin tone", "alpha_code": ":girl_tone4:", "aliases": "" }, "1f467-1f3ff": { "output": "1f467-1f3ff", "name": "girl: dark skin tone", "alpha_code": ":girl_tone5:", "aliases": "" }, "1f468-1f3fb": { "output": "1f468-1f3fb", "name": "man: light skin tone", "alpha_code": ":man_tone1:", "aliases": "" }, "1f468-1f3fc": { "output": "1f468-1f3fc", "name": "man: medium-light skin tone", "alpha_code": ":man_tone2:", "aliases": "" }, "1f468-1f3fd": { "output": "1f468-1f3fd", "name": "man: medium skin tone", "alpha_code": ":man_tone3:", "aliases": "" }, "1f468-1f3fe": { "output": "1f468-1f3fe", "name": "man: medium-dark skin tone", "alpha_code": ":man_tone4:", "aliases": "" }, "1f468-1f3ff": { "output": "1f468-1f3ff", "name": "man: dark skin tone", "alpha_code": ":man_tone5:", "aliases": "" }, "1f469-1f3fb": { "output": "1f469-1f3fb", "name": "woman: light skin tone", "alpha_code": ":woman_tone1:", "aliases": "" }, "1f469-1f3fc": { "output": "1f469-1f3fc", "name": "woman: medium-light skin tone", "alpha_code": ":woman_tone2:", "aliases": "" }, "1f469-1f3fd": { "output": "1f469-1f3fd", "name": "woman: medium skin tone", "alpha_code": ":woman_tone3:", "aliases": "" }, "1f469-1f3fe": { "output": "1f469-1f3fe", "name": "woman: medium-dark skin tone", "alpha_code": ":woman_tone4:", "aliases": "" }, "1f469-1f3ff": { "output": "1f469-1f3ff", "name": "woman: dark skin tone", "alpha_code": ":woman_tone5:", "aliases": "" }, "1f470-1f3fb": { "output": "1f470-1f3fb", "name": "bride with veil: light skin tone", "alpha_code": ":bride_with_veil_tone1:", "aliases": "" }, "1f470-1f3fc": { "output": "1f470-1f3fc", "name": "bride with veil: medium-light skin tone", "alpha_code": ":bride_with_veil_tone2:", "aliases": "" }, "1f91a-1f3ff": { "output": "1f91a-1f3ff", "name": "raised back of hand: dark skin tone", "alpha_code": ":raised_back_of_hand_tone5:", "aliases": ":back_of_hand_tone5:" }, "1f470-1f3fd": { "output": "1f470-1f3fd", "name": "bride with veil: medium skin tone", "alpha_code": ":bride_with_veil_tone3:", "aliases": "" }, "1f470-1f3fe": { "output": "1f470-1f3fe", "name": "bride with veil: medium-dark skin tone", "alpha_code": ":bride_with_veil_tone4:", "aliases": "" }, "1f470-1f3ff": { "output": "1f470-1f3ff", "name": "bride with veil: dark skin tone", "alpha_code": ":bride_with_veil_tone5:", "aliases": "" }, "1f471-1f3fb": { "output": "1f471-1f3fb", "name": "blond-haired person: light skin tone", "alpha_code": ":blond_haired_person_tone1:", "aliases": ":person_with_blond_hair_tone1:" }, "1f471-1f3fc": { "output": "1f471-1f3fc", "name": "blond-haired person: medium-light skin tone", "alpha_code": ":blond_haired_person_tone2:", "aliases": ":person_with_blond_hair_tone2:" }, "1f471-1f3fd": { "output": "1f471-1f3fd", "name": "blond-haired person: medium skin tone", "alpha_code": ":blond_haired_person_tone3:", "aliases": ":person_with_blond_hair_tone3:" }, "1f471-1f3fe": { "output": "1f471-1f3fe", "name": "blond-haired person: medium-dark skin tone", "alpha_code": ":blond_haired_person_tone4:", "aliases": ":person_with_blond_hair_tone4:" }, "1f471-1f3ff": { "output": "1f471-1f3ff", "name": "blond-haired person: dark skin tone", "alpha_code": ":blond_haired_person_tone5:", "aliases": ":person_with_blond_hair_tone5:" }, "1f472-1f3fb": { "output": "1f472-1f3fb", "name": "man with Chinese cap: light skin tone", "alpha_code": ":man_with_chinese_cap_tone1:", "aliases": ":man_with_gua_pi_mao_tone1:" }, "1f472-1f3fc": { "output": "1f472-1f3fc", "name": "man with Chinese cap: medium-light skin tone", "alpha_code": ":man_with_chinese_cap_tone2:", "aliases": ":man_with_gua_pi_mao_tone2:" }, "1f472-1f3fd": { "output": "1f472-1f3fd", "name": "man with Chinese cap: medium skin tone", "alpha_code": ":man_with_chinese_cap_tone3:", "aliases": ":man_with_gua_pi_mao_tone3:" }, "1f472-1f3fe": { "output": "1f472-1f3fe", "name": "man with Chinese cap: medium-dark skin tone", "alpha_code": ":man_with_chinese_cap_tone4:", "aliases": ":man_with_gua_pi_mao_tone4:" }, "1f472-1f3ff": { "output": "1f472-1f3ff", "name": "man with Chinese cap: dark skin tone", "alpha_code": ":man_with_chinese_cap_tone5:", "aliases": ":man_with_gua_pi_mao_tone5:" }, "1f473-1f3fb": { "output": "1f473-1f3fb", "name": "person wearing turban: light skin tone", "alpha_code": ":person_wearing_turban_tone1:", "aliases": ":man_with_turban_tone1:" }, "1f473-1f3fc": { "output": "1f473-1f3fc", "name": "person wearing turban: medium-light skin tone", "alpha_code": ":person_wearing_turban_tone2:", "aliases": ":man_with_turban_tone2:" }, "1f473-1f3fd": { "output": "1f473-1f3fd", "name": "person wearing turban: medium skin tone", "alpha_code": ":person_wearing_turban_tone3:", "aliases": ":man_with_turban_tone3:" }, "1f473-1f3fe": { "output": "1f473-1f3fe", "name": "person wearing turban: medium-dark skin tone", "alpha_code": ":person_wearing_turban_tone4:", "aliases": ":man_with_turban_tone4:" }, "1f473-1f3ff": { "output": "1f473-1f3ff", "name": "person wearing turban: dark skin tone", "alpha_code": ":person_wearing_turban_tone5:", "aliases": ":man_with_turban_tone5:" }, "1f474-1f3fb": { "output": "1f474-1f3fb", "name": "old man: light skin tone", "alpha_code": ":older_man_tone1:", "aliases": "" }, "1f474-1f3fc": { "output": "1f474-1f3fc", "name": "old man: medium-light skin tone", "alpha_code": ":older_man_tone2:", "aliases": "" }, "1f474-1f3fd": { "output": "1f474-1f3fd", "name": "old man: medium skin tone", "alpha_code": ":older_man_tone3:", "aliases": "" }, "1f474-1f3fe": { "output": "1f474-1f3fe", "name": "old man: medium-dark skin tone", "alpha_code": ":older_man_tone4:", "aliases": "" }, "1f474-1f3ff": { "output": "1f474-1f3ff", "name": "old man: dark skin tone", "alpha_code": ":older_man_tone5:", "aliases": "" }, "1f475-1f3fb": { "output": "1f475-1f3fb", "name": "old woman: light skin tone", "alpha_code": ":older_woman_tone1:", "aliases": ":grandma_tone1:" }, "1f475-1f3fc": { "output": "1f475-1f3fc", "name": "old woman: medium-light skin tone", "alpha_code": ":older_woman_tone2:", "aliases": ":grandma_tone2:" }, "1f475-1f3fd": { "output": "1f475-1f3fd", "name": "old woman: medium skin tone", "alpha_code": ":older_woman_tone3:", "aliases": ":grandma_tone3:" }, "1f475-1f3fe": { "output": "1f475-1f3fe", "name": "old woman: medium-dark skin tone", "alpha_code": ":older_woman_tone4:", "aliases": ":grandma_tone4:" }, "1f475-1f3ff": { "output": "1f475-1f3ff", "name": "old woman: dark skin tone", "alpha_code": ":older_woman_tone5:", "aliases": ":grandma_tone5:" }, "1f46e-1f3fb": { "output": "1f46e-1f3fb", "name": "police officer: light skin tone", "alpha_code": ":police_officer_tone1:", "aliases": ":cop_tone1:" }, "1f46e-1f3fc": { "output": "1f46e-1f3fc", "name": "police officer: medium-light skin tone", "alpha_code": ":police_officer_tone2:", "aliases": ":cop_tone2:" }, "1f46e-1f3fd": { "output": "1f46e-1f3fd", "name": "police officer: medium skin tone", "alpha_code": ":police_officer_tone3:", "aliases": ":cop_tone3:" }, "1f46e-1f3fe": { "output": "1f46e-1f3fe", "name": "police officer: medium-dark skin tone", "alpha_code": ":police_officer_tone4:", "aliases": ":cop_tone4:" }, "1f46e-1f3ff": { "output": "1f46e-1f3ff", "name": "police officer: dark skin tone", "alpha_code": ":police_officer_tone5:", "aliases": ":cop_tone5:" }, "1f477-1f3fb": { "output": "1f477-1f3fb", "name": "construction worker: light skin tone", "alpha_code": ":construction_worker_tone1:", "aliases": "" }, "1f477-1f3fc": { "output": "1f477-1f3fc", "name": "construction worker: medium-light skin tone", "alpha_code": ":construction_worker_tone2:", "aliases": "" }, "1f477-1f3fd": { "output": "1f477-1f3fd", "name": "construction worker: medium skin tone", "alpha_code": ":construction_worker_tone3:", "aliases": "" }, "1f477-1f3fe": { "output": "1f477-1f3fe", "name": "construction worker: medium-dark skin tone", "alpha_code": ":construction_worker_tone4:", "aliases": "" }, "1f477-1f3ff": { "output": "1f477-1f3ff", "name": "construction worker: dark skin tone", "alpha_code": ":construction_worker_tone5:", "aliases": "" }, "1f478-1f3fb": { "output": "1f478-1f3fb", "name": "princess: light skin tone", "alpha_code": ":princess_tone1:", "aliases": "" }, "1f478-1f3fc": { "output": "1f478-1f3fc", "name": "princess: medium-light skin tone", "alpha_code": ":princess_tone2:", "aliases": "" }, "1f478-1f3fd": { "output": "1f478-1f3fd", "name": "princess: medium skin tone", "alpha_code": ":princess_tone3:", "aliases": "" }, "1f478-1f3fe": { "output": "1f478-1f3fe", "name": "princess: medium-dark skin tone", "alpha_code": ":princess_tone4:", "aliases": "" }, "1f938-1f3fb": { "output": "1f938-1f3fb", "name": "person cartwheeling: light skin tone", "alpha_code": ":person_doing_cartwheel_tone1:", "aliases": ":cartwheel_tone1:" }, "1f478-1f3ff": { "output": "1f478-1f3ff", "name": "princess: dark skin tone", "alpha_code": ":princess_tone5:", "aliases": "" }, "1f482-1f3fb": { "output": "1f482-1f3fb", "name": "guard: light skin tone", "alpha_code": ":guard_tone1:", "aliases": ":guardsman_tone1:" }, "1f482-1f3fc": { "output": "1f482-1f3fc", "name": "guard: medium-light skin tone", "alpha_code": ":guard_tone2:", "aliases": ":guardsman_tone2:" }, "1f938-1f3fc": { "output": "1f938-1f3fc", "name": "person cartwheeling: medium-light skin tone", "alpha_code": ":person_doing_cartwheel_tone2:", "aliases": ":cartwheel_tone2:" }, "1f482-1f3fd": { "output": "1f482-1f3fd", "name": "guard: medium skin tone", "alpha_code": ":guard_tone3:", "aliases": ":guardsman_tone3:" }, "1f482-1f3fe": { "output": "1f482-1f3fe", "name": "guard: medium-dark skin tone", "alpha_code": ":guard_tone4:", "aliases": ":guardsman_tone4:" }, "1f482-1f3ff": { "output": "1f482-1f3ff", "name": "guard: dark skin tone", "alpha_code": ":guard_tone5:", "aliases": ":guardsman_tone5:" }, "1f938-1f3fd": { "output": "1f938-1f3fd", "name": "person cartwheeling: medium skin tone", "alpha_code": ":person_doing_cartwheel_tone3:", "aliases": ":cartwheel_tone3:" }, "1f47c-1f3fb": { "output": "1f47c-1f3fb", "name": "baby angel: light skin tone", "alpha_code": ":angel_tone1:", "aliases": "" }, "1f47c-1f3fc": { "output": "1f47c-1f3fc", "name": "baby angel: medium-light skin tone", "alpha_code": ":angel_tone2:", "aliases": "" }, "1f47c-1f3fd": { "output": "1f47c-1f3fd", "name": "baby angel: medium skin tone", "alpha_code": ":angel_tone3:", "aliases": "" }, "1f47c-1f3fe": { "output": "1f47c-1f3fe", "name": "baby angel: medium-dark skin tone", "alpha_code": ":angel_tone4:", "aliases": "" }, "1f47c-1f3ff": { "output": "1f47c-1f3ff", "name": "baby angel: dark skin tone", "alpha_code": ":angel_tone5:", "aliases": "" }, "1f647-1f3fb": { "output": "1f647-1f3fb", "name": "person bowing: light skin tone", "alpha_code": ":person_bowing_tone1:", "aliases": ":bow_tone1:" }, "1f647-1f3fc": { "output": "1f647-1f3fc", "name": "person bowing: medium-light skin tone", "alpha_code": ":person_bowing_tone2:", "aliases": ":bow_tone2:" }, "1f647-1f3fd": { "output": "1f647-1f3fd", "name": "person bowing: medium skin tone", "alpha_code": ":person_bowing_tone3:", "aliases": ":bow_tone3:" }, "1f647-1f3fe": { "output": "1f647-1f3fe", "name": "person bowing: medium-dark skin tone", "alpha_code": ":person_bowing_tone4:", "aliases": ":bow_tone4:" }, "1f647-1f3ff": { "output": "1f647-1f3ff", "name": "person bowing: dark skin tone", "alpha_code": ":person_bowing_tone5:", "aliases": ":bow_tone5:" }, "1f481-1f3fb": { "output": "1f481-1f3fb", "name": "person tipping hand: light skin tone", "alpha_code": ":person_tipping_hand_tone1:", "aliases": ":information_desk_person_tone1:" }, "1f481-1f3fc": { "output": "1f481-1f3fc", "name": "person tipping hand: medium-light skin tone", "alpha_code": ":person_tipping_hand_tone2:", "aliases": ":information_desk_person_tone2:" }, "1f481-1f3fd": { "output": "1f481-1f3fd", "name": "person tipping hand: medium skin tone", "alpha_code": ":person_tipping_hand_tone3:", "aliases": ":information_desk_person_tone3:" }, "1f938-1f3fe": { "output": "1f938-1f3fe", "name": "person cartwheeling: medium-dark skin tone", "alpha_code": ":person_doing_cartwheel_tone4:", "aliases": ":cartwheel_tone4:" }, "1f481-1f3fe": { "output": "1f481-1f3fe", "name": "person tipping hand: medium-dark skin tone", "alpha_code": ":person_tipping_hand_tone4:", "aliases": ":information_desk_person_tone4:" }, "1f481-1f3ff": { "output": "1f481-1f3ff", "name": "person tipping hand: dark skin tone", "alpha_code": ":person_tipping_hand_tone5:", "aliases": ":information_desk_person_tone5:" }, "1f645-1f3fb": { "output": "1f645-1f3fb", "name": "person gesturing NO: light skin tone", "alpha_code": ":person_gesturing_no_tone1:", "aliases": ":no_good_tone1:" }, "1f938-1f3ff": { "output": "1f938-1f3ff", "name": "person cartwheeling: dark skin tone", "alpha_code": ":person_doing_cartwheel_tone5:", "aliases": ":cartwheel_tone5:" }, "1f645-1f3fc": { "output": "1f645-1f3fc", "name": "person gesturing NO: medium-light skin tone", "alpha_code": ":person_gesturing_no_tone2:", "aliases": ":no_good_tone2:" }, "1f645-1f3fd": { "output": "1f645-1f3fd", "name": "person gesturing NO: medium skin tone", "alpha_code": ":person_gesturing_no_tone3:", "aliases": ":no_good_tone3:" }, "1f645-1f3fe": { "output": "1f645-1f3fe", "name": "person gesturing NO: medium-dark skin tone", "alpha_code": ":person_gesturing_no_tone4:", "aliases": ":no_good_tone4:" }, "1f645-1f3ff": { "output": "1f645-1f3ff", "name": "person gesturing NO: dark skin tone", "alpha_code": ":person_gesturing_no_tone5:", "aliases": ":no_good_tone5:" }, "1f646-1f3fb": { "output": "1f646-1f3fb", "name": "person gesturing OK: light skin tone", "alpha_code": ":person_gesturing_ok_tone1:", "aliases": ":ok_woman_tone1:" }, "1f646-1f3fc": { "output": "1f646-1f3fc", "name": "person gesturing OK: medium-light skin tone", "alpha_code": ":person_gesturing_ok_tone2:", "aliases": ":ok_woman_tone2:" }, "1f646-1f3fd": { "output": "1f646-1f3fd", "name": "person gesturing OK: medium skin tone", "alpha_code": ":person_gesturing_ok_tone3:", "aliases": ":ok_woman_tone3:" }, "1f646-1f3fe": { "output": "1f646-1f3fe", "name": "person gesturing OK: medium-dark skin tone", "alpha_code": ":person_gesturing_ok_tone4:", "aliases": ":ok_woman_tone4:" }, "1f646-1f3ff": { "output": "1f646-1f3ff", "name": "person gesturing OK: dark skin tone", "alpha_code": ":person_gesturing_ok_tone5:", "aliases": ":ok_woman_tone5:" }, "1f64b-1f3fb": { "output": "1f64b-1f3fb", "name": "person raising hand: light skin tone", "alpha_code": ":person_raising_hand_tone1:", "aliases": ":raising_hand_tone1:" }, "1f64b-1f3fc": { "output": "1f64b-1f3fc", "name": "person raising hand: medium-light skin tone", "alpha_code": ":person_raising_hand_tone2:", "aliases": ":raising_hand_tone2:" }, "1f64b-1f3fd": { "output": "1f64b-1f3fd", "name": "person raising hand: medium skin tone", "alpha_code": ":person_raising_hand_tone3:", "aliases": ":raising_hand_tone3:" }, "1f64b-1f3fe": { "output": "1f64b-1f3fe", "name": "person raising hand: medium-dark skin tone", "alpha_code": ":person_raising_hand_tone4:", "aliases": ":raising_hand_tone4:" }, "1f64b-1f3ff": { "output": "1f64b-1f3ff", "name": "person raising hand: dark skin tone", "alpha_code": ":person_raising_hand_tone5:", "aliases": ":raising_hand_tone5:" }, "1f64e-1f3fb": { "output": "1f64e-1f3fb", "name": "person pouting: light skin tone", "alpha_code": ":person_pouting_tone1:", "aliases": ":person_with_pouting_face_tone1:" }, "1f64e-1f3fc": { "output": "1f64e-1f3fc", "name": "person pouting: medium-light skin tone", "alpha_code": ":person_pouting_tone2:", "aliases": ":person_with_pouting_face_tone2:" }, "1f64e-1f3fd": { "output": "1f64e-1f3fd", "name": "person pouting: medium skin tone", "alpha_code": ":person_pouting_tone3:", "aliases": ":person_with_pouting_face_tone3:" }, "1f64e-1f3fe": { "output": "1f64e-1f3fe", "name": "person pouting: medium-dark skin tone", "alpha_code": ":person_pouting_tone4:", "aliases": ":person_with_pouting_face_tone4:" }, "1f64e-1f3ff": { "output": "1f64e-1f3ff", "name": "person pouting: dark skin tone", "alpha_code": ":person_pouting_tone5:", "aliases": ":person_with_pouting_face_tone5:" }, "1f64d-1f3fb": { "output": "1f64d-1f3fb", "name": "person frowning: light skin tone", "alpha_code": ":person_frowning_tone1:", "aliases": "" }, "1f64d-1f3fc": { "output": "1f64d-1f3fc", "name": "person frowning: medium-light skin tone", "alpha_code": ":person_frowning_tone2:", "aliases": "" }, "1f64d-1f3fd": { "output": "1f64d-1f3fd", "name": "person frowning: medium skin tone", "alpha_code": ":person_frowning_tone3:", "aliases": "" }, "1f64d-1f3fe": { "output": "1f64d-1f3fe", "name": "person frowning: medium-dark skin tone", "alpha_code": ":person_frowning_tone4:", "aliases": "" }, "1f64d-1f3ff": { "output": "1f64d-1f3ff", "name": "person frowning: dark skin tone", "alpha_code": ":person_frowning_tone5:", "aliases": "" }, "1f486-1f3fb": { "output": "1f486-1f3fb", "name": "person getting massage: light skin tone", "alpha_code": ":person_getting_massage_tone1:", "aliases": ":massage_tone1:" }, "1f486-1f3fc": { "output": "1f486-1f3fc", "name": "person getting massage: medium-light skin tone", "alpha_code": ":person_getting_massage_tone2:", "aliases": ":massage_tone2:" }, "1f486-1f3fd": { "output": "1f486-1f3fd", "name": "person getting massage: medium skin tone", "alpha_code": ":person_getting_massage_tone3:", "aliases": ":massage_tone3:" }, "1f486-1f3fe": { "output": "1f486-1f3fe", "name": "person getting massage: medium-dark skin tone", "alpha_code": ":person_getting_massage_tone4:", "aliases": ":massage_tone4:" }, "1f486-1f3ff": { "output": "1f486-1f3ff", "name": "person getting massage: dark skin tone", "alpha_code": ":person_getting_massage_tone5:", "aliases": ":massage_tone5:" }, "1f487-1f3fb": { "output": "1f487-1f3fb", "name": "person getting haircut: light skin tone", "alpha_code": ":person_getting_haircut_tone1:", "aliases": ":haircut_tone1:" }, "1f487-1f3fc": { "output": "1f487-1f3fc", "name": "person getting haircut: medium-light skin tone", "alpha_code": ":person_getting_haircut_tone2:", "aliases": ":haircut_tone2:" }, "1f487-1f3fd": { "output": "1f487-1f3fd", "name": "person getting haircut: medium skin tone", "alpha_code": ":person_getting_haircut_tone3:", "aliases": ":haircut_tone3:" }, "1f487-1f3fe": { "output": "1f487-1f3fe", "name": "person getting haircut: medium-dark skin tone", "alpha_code": ":person_getting_haircut_tone4:", "aliases": ":haircut_tone4:" }, "1f487-1f3ff": { "output": "1f487-1f3ff", "name": "person getting haircut: dark skin tone", "alpha_code": ":person_getting_haircut_tone5:", "aliases": ":haircut_tone5:" }, "1f64c-1f3fb": { "output": "1f64c-1f3fb", "name": "raising hands: light skin tone", "alpha_code": ":raised_hands_tone1:", "aliases": "" }, "1f64c-1f3fc": { "output": "1f64c-1f3fc", "name": "raising hands: medium-light skin tone", "alpha_code": ":raised_hands_tone2:", "aliases": "" }, "1f64c-1f3fd": { "output": "1f64c-1f3fd", "name": "raising hands: medium skin tone", "alpha_code": ":raised_hands_tone3:", "aliases": "" }, "1f64c-1f3fe": { "output": "1f64c-1f3fe", "name": "raising hands: medium-dark skin tone", "alpha_code": ":raised_hands_tone4:", "aliases": "" }, "1f64c-1f3ff": { "output": "1f64c-1f3ff", "name": "raising hands: dark skin tone", "alpha_code": ":raised_hands_tone5:", "aliases": "" }, "1f44f-1f3fb": { "output": "1f44f-1f3fb", "name": "clapping hands: light skin tone", "alpha_code": ":clap_tone1:", "aliases": "" }, "1f44f-1f3fc": { "output": "1f44f-1f3fc", "name": "clapping hands: medium-light skin tone", "alpha_code": ":clap_tone2:", "aliases": "" }, "1f44f-1f3fd": { "output": "1f44f-1f3fd", "name": "clapping hands: medium skin tone", "alpha_code": ":clap_tone3:", "aliases": "" }, "1f93d-1f3fb": { "output": "1f93d-1f3fb", "name": "person playing water polo: light skin tone", "alpha_code": ":person_playing_water_polo_tone1:", "aliases": ":water_polo_tone1:" }, "1f44f-1f3fe": { "output": "1f44f-1f3fe", "name": "clapping hands: medium-dark skin tone", "alpha_code": ":clap_tone4:", "aliases": "" }, "1f44f-1f3ff": { "output": "1f44f-1f3ff", "name": "clapping hands: dark skin tone", "alpha_code": ":clap_tone5:", "aliases": "" }, "1f93d-1f3fc": { "output": "1f93d-1f3fc", "name": "person playing water polo: medium-light skin tone", "alpha_code": ":person_playing_water_polo_tone2:", "aliases": ":water_polo_tone2:" }, "1f442-1f3fb": { "output": "1f442-1f3fb", "name": "ear: light skin tone", "alpha_code": ":ear_tone1:", "aliases": "" }, "1f442-1f3fc": { "output": "1f442-1f3fc", "name": "ear: medium-light skin tone", "alpha_code": ":ear_tone2:", "aliases": "" }, "1f442-1f3fd": { "output": "1f442-1f3fd", "name": "ear: medium skin tone", "alpha_code": ":ear_tone3:", "aliases": "" }, "1f442-1f3fe": { "output": "1f442-1f3fe", "name": "ear: medium-dark skin tone", "alpha_code": ":ear_tone4:", "aliases": "" }, "1f442-1f3ff": { "output": "1f442-1f3ff", "name": "ear: dark skin tone", "alpha_code": ":ear_tone5:", "aliases": "" }, "1f443-1f3fb": { "output": "1f443-1f3fb", "name": "nose: light skin tone", "alpha_code": ":nose_tone1:", "aliases": "" }, "1f443-1f3fc": { "output": "1f443-1f3fc", "name": "nose: medium-light skin tone", "alpha_code": ":nose_tone2:", "aliases": "" }, "1f443-1f3fd": { "output": "1f443-1f3fd", "name": "nose: medium skin tone", "alpha_code": ":nose_tone3:", "aliases": "" }, "1f443-1f3fe": { "output": "1f443-1f3fe", "name": "nose: medium-dark skin tone", "alpha_code": ":nose_tone4:", "aliases": "" }, "1f443-1f3ff": { "output": "1f443-1f3ff", "name": "nose: dark skin tone", "alpha_code": ":nose_tone5:", "aliases": "" }, "1f485-1f3fb": { "output": "1f485-1f3fb", "name": "nail polish: light skin tone", "alpha_code": ":nail_care_tone1:", "aliases": "" }, "1f485-1f3fc": { "output": "1f485-1f3fc", "name": "nail polish: medium-light skin tone", "alpha_code": ":nail_care_tone2:", "aliases": "" }, "1f485-1f3fd": { "output": "1f485-1f3fd", "name": "nail polish: medium skin tone", "alpha_code": ":nail_care_tone3:", "aliases": "" }, "1f485-1f3fe": { "output": "1f485-1f3fe", "name": "nail polish: medium-dark skin tone", "alpha_code": ":nail_care_tone4:", "aliases": "" }, "1f485-1f3ff": { "output": "1f485-1f3ff", "name": "nail polish: dark skin tone", "alpha_code": ":nail_care_tone5:", "aliases": "" }, "1f44b-1f3fb": { "output": "1f44b-1f3fb", "name": "waving hand: light skin tone", "alpha_code": ":wave_tone1:", "aliases": "" }, "1f44b-1f3fc": { "output": "1f44b-1f3fc", "name": "waving hand: medium-light skin tone", "alpha_code": ":wave_tone2:", "aliases": "" }, "1f44b-1f3fd": { "output": "1f44b-1f3fd", "name": "waving hand: medium skin tone", "alpha_code": ":wave_tone3:", "aliases": "" }, "1f44b-1f3fe": { "output": "1f44b-1f3fe", "name": "waving hand: medium-dark skin tone", "alpha_code": ":wave_tone4:", "aliases": "" }, "1f44b-1f3ff": { "output": "1f44b-1f3ff", "name": "waving hand: dark skin tone", "alpha_code": ":wave_tone5:", "aliases": "" }, "1f44d-1f3fb": { "output": "1f44d-1f3fb", "name": "thumbs up: light skin tone", "alpha_code": ":thumbsup_tone1:", "aliases": ":+1_tone1:|:thumbup_tone1:" }, "1f44d-1f3fc": { "output": "1f44d-1f3fc", "name": "thumbs up: medium-light skin tone", "alpha_code": ":thumbsup_tone2:", "aliases": ":+1_tone2:|:thumbup_tone2:" }, "1f44d-1f3fd": { "output": "1f44d-1f3fd", "name": "thumbs up: medium skin tone", "alpha_code": ":thumbsup_tone3:", "aliases": ":+1_tone3:|:thumbup_tone3:" }, "1f44d-1f3fe": { "output": "1f44d-1f3fe", "name": "thumbs up: medium-dark skin tone", "alpha_code": ":thumbsup_tone4:", "aliases": ":+1_tone4:|:thumbup_tone4:" }, "1f44d-1f3ff": { "output": "1f44d-1f3ff", "name": "thumbs up: dark skin tone", "alpha_code": ":thumbsup_tone5:", "aliases": ":+1_tone5:|:thumbup_tone5:" }, "1f44e-1f3fb": { "output": "1f44e-1f3fb", "name": "thumbs down: light skin tone", "alpha_code": ":thumbsdown_tone1:", "aliases": ":-1_tone1:|:thumbdown_tone1:" }, "1f44e-1f3fc": { "output": "1f44e-1f3fc", "name": "thumbs down: medium-light skin tone", "alpha_code": ":thumbsdown_tone2:", "aliases": ":-1_tone2:|:thumbdown_tone2:" }, "1f44e-1f3fd": { "output": "1f44e-1f3fd", "name": "thumbs down: medium skin tone", "alpha_code": ":thumbsdown_tone3:", "aliases": ":-1_tone3:|:thumbdown_tone3:" }, "1f44e-1f3fe": { "output": "1f44e-1f3fe", "name": "thumbs down: medium-dark skin tone", "alpha_code": ":thumbsdown_tone4:", "aliases": ":-1_tone4:|:thumbdown_tone4:" }, "1f44e-1f3ff": { "output": "1f44e-1f3ff", "name": "thumbs down: dark skin tone", "alpha_code": ":thumbsdown_tone5:", "aliases": ":-1_tone5:|:thumbdown_tone5:" }, "261d-1f3fb": { "output": "261d-1f3fb", "name": "index pointing up: light skin tone", "alpha_code": ":point_up_tone1:", "aliases": "" }, "261d-1f3fc": { "output": "261d-1f3fc", "name": "index pointing up: medium-light skin tone", "alpha_code": ":point_up_tone2:", "aliases": "" }, "261d-1f3fd": { "output": "261d-1f3fd", "name": "index pointing up: medium skin tone", "alpha_code": ":point_up_tone3:", "aliases": "" }, "261d-1f3fe": { "output": "261d-1f3fe", "name": "index pointing up: medium-dark skin tone", "alpha_code": ":point_up_tone4:", "aliases": "" }, "261d-1f3ff": { "output": "261d-1f3ff", "name": "index pointing up: dark skin tone", "alpha_code": ":point_up_tone5:", "aliases": "" }, "1f446-1f3fb": { "output": "1f446-1f3fb", "name": "backhand index pointing up: light skin tone", "alpha_code": ":point_up_2_tone1:", "aliases": "" }, "1f446-1f3fc": { "output": "1f446-1f3fc", "name": "backhand index pointing up: medium-light skin tone", "alpha_code": ":point_up_2_tone2:", "aliases": "" }, "1f446-1f3fd": { "output": "1f446-1f3fd", "name": "backhand index pointing up: medium skin tone", "alpha_code": ":point_up_2_tone3:", "aliases": "" }, "1f446-1f3fe": { "output": "1f446-1f3fe", "name": "backhand index pointing up: medium-dark skin tone", "alpha_code": ":point_up_2_tone4:", "aliases": "" }, "1f446-1f3ff": { "output": "1f446-1f3ff", "name": "backhand index pointing up: dark skin tone", "alpha_code": ":point_up_2_tone5:", "aliases": "" }, "1f447-1f3fb": { "output": "1f447-1f3fb", "name": "backhand index pointing down: light skin tone", "alpha_code": ":point_down_tone1:", "aliases": "" }, "1f447-1f3fc": { "output": "1f447-1f3fc", "name": "backhand index pointing down: medium-light skin tone", "alpha_code": ":point_down_tone2:", "aliases": "" }, "1f447-1f3fd": { "output": "1f447-1f3fd", "name": "backhand index pointing down: medium skin tone", "alpha_code": ":point_down_tone3:", "aliases": "" }, "1f447-1f3fe": { "output": "1f447-1f3fe", "name": "backhand index pointing down: medium-dark skin tone", "alpha_code": ":point_down_tone4:", "aliases": "" }, "1f447-1f3ff": { "output": "1f447-1f3ff", "name": "backhand index pointing down: dark skin tone", "alpha_code": ":point_down_tone5:", "aliases": "" }, "1f448-1f3fb": { "output": "1f448-1f3fb", "name": "backhand index pointing left: light skin tone", "alpha_code": ":point_left_tone1:", "aliases": "" }, "1f448-1f3fc": { "output": "1f448-1f3fc", "name": "backhand index pointing left: medium-light skin tone", "alpha_code": ":point_left_tone2:", "aliases": "" }, "1f448-1f3fd": { "output": "1f448-1f3fd", "name": "backhand index pointing left: medium skin tone", "alpha_code": ":point_left_tone3:", "aliases": "" }, "1f448-1f3fe": { "output": "1f448-1f3fe", "name": "backhand index pointing left: medium-dark skin tone", "alpha_code": ":point_left_tone4:", "aliases": "" }, "1f448-1f3ff": { "output": "1f448-1f3ff", "name": "backhand index pointing left: dark skin tone", "alpha_code": ":point_left_tone5:", "aliases": "" }, "1f449-1f3fb": { "output": "1f449-1f3fb", "name": "backhand index pointing right: light skin tone", "alpha_code": ":point_right_tone1:", "aliases": "" }, "1f449-1f3fc": { "output": "1f449-1f3fc", "name": "backhand index pointing right: medium-light skin tone", "alpha_code": ":point_right_tone2:", "aliases": "" }, "1f449-1f3fd": { "output": "1f449-1f3fd", "name": "backhand index pointing right: medium skin tone", "alpha_code": ":point_right_tone3:", "aliases": "" }, "1f449-1f3fe": { "output": "1f449-1f3fe", "name": "backhand index pointing right: medium-dark skin tone", "alpha_code": ":point_right_tone4:", "aliases": "" }, "1f449-1f3ff": { "output": "1f449-1f3ff", "name": "backhand index pointing right: dark skin tone", "alpha_code": ":point_right_tone5:", "aliases": "" }, "1f44c-1f3fb": { "output": "1f44c-1f3fb", "name": "OK hand: light skin tone", "alpha_code": ":ok_hand_tone1:", "aliases": "" }, "1f44c-1f3fc": { "output": "1f44c-1f3fc", "name": "OK hand: medium-light skin tone", "alpha_code": ":ok_hand_tone2:", "aliases": "" }, "1f93d-1f3fd": { "output": "1f93d-1f3fd", "name": "person playing water polo: medium skin tone", "alpha_code": ":person_playing_water_polo_tone3:", "aliases": ":water_polo_tone3:" }, "1f44c-1f3fd": { "output": "1f44c-1f3fd", "name": "OK hand: medium skin tone", "alpha_code": ":ok_hand_tone3:", "aliases": "" }, "1f44c-1f3fe": { "output": "1f44c-1f3fe", "name": "OK hand: medium-dark skin tone", "alpha_code": ":ok_hand_tone4:", "aliases": "" }, "1f93d-1f3fe": { "output": "1f93d-1f3fe", "name": "person playing water polo: medium-dark skin tone", "alpha_code": ":person_playing_water_polo_tone4:", "aliases": ":water_polo_tone4:" }, "1f44c-1f3ff": { "output": "1f44c-1f3ff", "name": "OK hand: dark skin tone", "alpha_code": ":ok_hand_tone5:", "aliases": "" }, "270c-1f3fb": { "output": "270c-1f3fb", "name": "victory hand: light skin tone", "alpha_code": ":v_tone1:", "aliases": "" }, "270c-1f3fc": { "output": "270c-1f3fc", "name": "victory hand: medium-light skin tone", "alpha_code": ":v_tone2:", "aliases": "" }, "270c-1f3fd": { "output": "270c-1f3fd", "name": "victory hand: medium skin tone", "alpha_code": ":v_tone3:", "aliases": "" }, "270c-1f3fe": { "output": "270c-1f3fe", "name": "victory hand: medium-dark skin tone", "alpha_code": ":v_tone4:", "aliases": "" }, "270c-1f3ff": { "output": "270c-1f3ff", "name": "victory hand: dark skin tone", "alpha_code": ":v_tone5:", "aliases": "" }, "1f44a-1f3fb": { "output": "1f44a-1f3fb", "name": "oncoming fist: light skin tone", "alpha_code": ":punch_tone1:", "aliases": "" }, "1f44a-1f3fc": { "output": "1f44a-1f3fc", "name": "oncoming fist: medium-light skin tone", "alpha_code": ":punch_tone2:", "aliases": "" }, "1f44a-1f3fd": { "output": "1f44a-1f3fd", "name": "oncoming fist: medium skin tone", "alpha_code": ":punch_tone3:", "aliases": "" }, "1f44a-1f3fe": { "output": "1f44a-1f3fe", "name": "oncoming fist: medium-dark skin tone", "alpha_code": ":punch_tone4:", "aliases": "" }, "1f44a-1f3ff": { "output": "1f44a-1f3ff", "name": "oncoming fist: dark skin tone", "alpha_code": ":punch_tone5:", "aliases": "" }, "270a-1f3fb": { "output": "270a-1f3fb", "name": "raised fist: light skin tone", "alpha_code": ":fist_tone1:", "aliases": "" }, "270a-1f3fc": { "output": "270a-1f3fc", "name": "raised fist: medium-light skin tone", "alpha_code": ":fist_tone2:", "aliases": "" }, "270a-1f3fd": { "output": "270a-1f3fd", "name": "raised fist: medium skin tone", "alpha_code": ":fist_tone3:", "aliases": "" }, "270a-1f3fe": { "output": "270a-1f3fe", "name": "raised fist: medium-dark skin tone", "alpha_code": ":fist_tone4:", "aliases": "" }, "270a-1f3ff": { "output": "270a-1f3ff", "name": "raised fist: dark skin tone", "alpha_code": ":fist_tone5:", "aliases": "" }, "270b-1f3fb": { "output": "270b-1f3fb", "name": "raised hand: light skin tone", "alpha_code": ":raised_hand_tone1:", "aliases": "" }, "270b-1f3fc": { "output": "270b-1f3fc", "name": "raised hand: medium-light skin tone", "alpha_code": ":raised_hand_tone2:", "aliases": "" }, "270b-1f3fd": { "output": "270b-1f3fd", "name": "raised hand: medium skin tone", "alpha_code": ":raised_hand_tone3:", "aliases": "" }, "270b-1f3fe": { "output": "270b-1f3fe", "name": "raised hand: medium-dark skin tone", "alpha_code": ":raised_hand_tone4:", "aliases": "" }, "270b-1f3ff": { "output": "270b-1f3ff", "name": "raised hand: dark skin tone", "alpha_code": ":raised_hand_tone5:", "aliases": "" }, "1f4aa-1f3fb": { "output": "1f4aa-1f3fb", "name": "flexed biceps: light skin tone", "alpha_code": ":muscle_tone1:", "aliases": "" }, "1f4aa-1f3fc": { "output": "1f4aa-1f3fc", "name": "flexed biceps: medium-light skin tone", "alpha_code": ":muscle_tone2:", "aliases": "" }, "1f4aa-1f3fd": { "output": "1f4aa-1f3fd", "name": "flexed biceps: medium skin tone", "alpha_code": ":muscle_tone3:", "aliases": "" }, "1f4aa-1f3fe": { "output": "1f4aa-1f3fe", "name": "flexed biceps: medium-dark skin tone", "alpha_code": ":muscle_tone4:", "aliases": "" }, "1f4aa-1f3ff": { "output": "1f4aa-1f3ff", "name": "flexed biceps: dark skin tone", "alpha_code": ":muscle_tone5:", "aliases": "" }, "1f450-1f3fb": { "output": "1f450-1f3fb", "name": "open hands: light skin tone", "alpha_code": ":open_hands_tone1:", "aliases": "" }, "1f450-1f3fc": { "output": "1f450-1f3fc", "name": "open hands: medium-light skin tone", "alpha_code": ":open_hands_tone2:", "aliases": "" }, "1f450-1f3fd": { "output": "1f450-1f3fd", "name": "open hands: medium skin tone", "alpha_code": ":open_hands_tone3:", "aliases": "" }, "1f450-1f3fe": { "output": "1f450-1f3fe", "name": "open hands: medium-dark skin tone", "alpha_code": ":open_hands_tone4:", "aliases": "" }, "1f450-1f3ff": { "output": "1f450-1f3ff", "name": "open hands: dark skin tone", "alpha_code": ":open_hands_tone5:", "aliases": "" }, "1f64f-1f3fb": { "output": "1f64f-1f3fb", "name": "folded hands: light skin tone", "alpha_code": ":pray_tone1:", "aliases": "" }, "1f93d-1f3ff": { "output": "1f93d-1f3ff", "name": "person playing water polo: dark skin tone", "alpha_code": ":person_playing_water_polo_tone5:", "aliases": ":water_polo_tone5:" }, "1f64f-1f3fc": { "output": "1f64f-1f3fc", "name": "folded hands: medium-light skin tone", "alpha_code": ":pray_tone2:", "aliases": "" }, "1f64f-1f3fd": { "output": "1f64f-1f3fd", "name": "folded hands: medium skin tone", "alpha_code": ":pray_tone3:", "aliases": "" }, "1f93e-1f3fb": { "output": "1f93e-1f3fb", "name": "person playing handball: light skin tone", "alpha_code": ":person_playing_handball_tone1:", "aliases": ":handball_tone1:" }, "1f64f-1f3fe": { "output": "1f64f-1f3fe", "name": "folded hands: medium-dark skin tone", "alpha_code": ":pray_tone4:", "aliases": "" }, "1f64f-1f3ff": { "output": "1f64f-1f3ff", "name": "folded hands: dark skin tone", "alpha_code": ":pray_tone5:", "aliases": "" }, "1f93e-1f3fc": { "output": "1f93e-1f3fc", "name": "person playing handball: medium-light skin tone", "alpha_code": ":person_playing_handball_tone2:", "aliases": ":handball_tone2:" }, "1f3c3-1f3fb": { "output": "1f3c3-1f3fb", "name": "person running: light skin tone", "alpha_code": ":person_running_tone1:", "aliases": ":runner_tone1:" }, "1f3c3-1f3fc": { "output": "1f3c3-1f3fc", "name": "person running: medium-light skin tone", "alpha_code": ":person_running_tone2:", "aliases": ":runner_tone2:" }, "1f3c3-1f3fd": { "output": "1f3c3-1f3fd", "name": "person running: medium skin tone", "alpha_code": ":person_running_tone3:", "aliases": ":runner_tone3:" }, "1f3c3-1f3fe": { "output": "1f3c3-1f3fe", "name": "person running: medium-dark skin tone", "alpha_code": ":person_running_tone4:", "aliases": ":runner_tone4:" }, "1f93e-1f3fd": { "output": "1f93e-1f3fd", "name": "person playing handball: medium skin tone", "alpha_code": ":person_playing_handball_tone3:", "aliases": ":handball_tone3:" }, "1f3c3-1f3ff": { "output": "1f3c3-1f3ff", "name": "person running: dark skin tone", "alpha_code": ":person_running_tone5:", "aliases": ":runner_tone5:" }, "1f6b6-1f3fb": { "output": "1f6b6-1f3fb", "name": "person walking: light skin tone", "alpha_code": ":person_walking_tone1:", "aliases": ":walking_tone1:" }, "1f6b6-1f3fc": { "output": "1f6b6-1f3fc", "name": "person walking: medium-light skin tone", "alpha_code": ":person_walking_tone2:", "aliases": ":walking_tone2:" }, "1f6b6-1f3fd": { "output": "1f6b6-1f3fd", "name": "person walking: medium skin tone", "alpha_code": ":person_walking_tone3:", "aliases": ":walking_tone3:" }, "1f6b6-1f3fe": { "output": "1f6b6-1f3fe", "name": "person walking: medium-dark skin tone", "alpha_code": ":person_walking_tone4:", "aliases": ":walking_tone4:" }, "1f6b6-1f3ff": { "output": "1f6b6-1f3ff", "name": "person walking: dark skin tone", "alpha_code": ":person_walking_tone5:", "aliases": ":walking_tone5:" }, "1f483-1f3fb": { "output": "1f483-1f3fb", "name": "woman dancing: light skin tone", "alpha_code": ":dancer_tone1:", "aliases": "" }, "1f93e-1f3fe": { "output": "1f93e-1f3fe", "name": "person playing handball: medium-dark skin tone", "alpha_code": ":person_playing_handball_tone4:", "aliases": ":handball_tone4:" }, "1f483-1f3fc": { "output": "1f483-1f3fc", "name": "woman dancing: medium-light skin tone", "alpha_code": ":dancer_tone2:", "aliases": "" }, "1f483-1f3fd": { "output": "1f483-1f3fd", "name": "woman dancing: medium skin tone", "alpha_code": ":dancer_tone3:", "aliases": "" }, "1f93e-1f3ff": { "output": "1f93e-1f3ff", "name": "person playing handball: dark skin tone", "alpha_code": ":person_playing_handball_tone5:", "aliases": ":handball_tone5:" }, "1f483-1f3fe": { "output": "1f483-1f3fe", "name": "woman dancing: medium-dark skin tone", "alpha_code": ":dancer_tone4:", "aliases": "" }, "1f483-1f3ff": { "output": "1f483-1f3ff", "name": "woman dancing: dark skin tone", "alpha_code": ":dancer_tone5:", "aliases": "" }, "1f939-1f3fb": { "output": "1f939-1f3fb", "name": "person juggling: light skin tone", "alpha_code": ":person_juggling_tone1:", "aliases": ":juggling_tone1:|:juggler_tone1:" }, "1f6a3-1f3fb": { "output": "1f6a3-1f3fb", "name": "person rowing boat: light skin tone", "alpha_code": ":person_rowing_boat_tone1:", "aliases": ":rowboat_tone1:" }, "1f6a3-1f3fc": { "output": "1f6a3-1f3fc", "name": "person rowing boat: medium-light skin tone", "alpha_code": ":person_rowing_boat_tone2:", "aliases": ":rowboat_tone2:" }, "1f6a3-1f3fd": { "output": "1f6a3-1f3fd", "name": "person rowing boat: medium skin tone", "alpha_code": ":person_rowing_boat_tone3:", "aliases": ":rowboat_tone3:" }, "1f6a3-1f3fe": { "output": "1f6a3-1f3fe", "name": "person rowing boat: medium-dark skin tone", "alpha_code": ":person_rowing_boat_tone4:", "aliases": ":rowboat_tone4:" }, "1f6a3-1f3ff": { "output": "1f6a3-1f3ff", "name": "person rowing boat: dark skin tone", "alpha_code": ":person_rowing_boat_tone5:", "aliases": ":rowboat_tone5:" }, "1f3ca-1f3fb": { "output": "1f3ca-1f3fb", "name": "person swimming: light skin tone", "alpha_code": ":person_swimming_tone1:", "aliases": ":swimmer_tone1:" }, "1f3ca-1f3fc": { "output": "1f3ca-1f3fc", "name": "person swimming: medium-light skin tone", "alpha_code": ":person_swimming_tone2:", "aliases": ":swimmer_tone2:" }, "1f939-1f3fc": { "output": "1f939-1f3fc", "name": "person juggling: medium-light skin tone", "alpha_code": ":person_juggling_tone2:", "aliases": ":juggling_tone2:|:juggler_tone2:" }, "1f3ca-1f3fd": { "output": "1f3ca-1f3fd", "name": "person swimming: medium skin tone", "alpha_code": ":person_swimming_tone3:", "aliases": ":swimmer_tone3:" }, "1f3ca-1f3fe": { "output": "1f3ca-1f3fe", "name": "person swimming: medium-dark skin tone", "alpha_code": ":person_swimming_tone4:", "aliases": ":swimmer_tone4:" }, "1f3ca-1f3ff": { "output": "1f3ca-1f3ff", "name": "person swimming: dark skin tone", "alpha_code": ":person_swimming_tone5:", "aliases": ":swimmer_tone5:" }, "1f939-1f3fd": { "output": "1f939-1f3fd", "name": "person juggling: medium skin tone", "alpha_code": ":person_juggling_tone3:", "aliases": ":juggling_tone3:|:juggler_tone3:" }, "1f3c4-1f3fb": { "output": "1f3c4-1f3fb", "name": "person surfing: light skin tone", "alpha_code": ":person_surfing_tone1:", "aliases": ":surfer_tone1:" }, "1f3c4-1f3fc": { "output": "1f3c4-1f3fc", "name": "person surfing: medium-light skin tone", "alpha_code": ":person_surfing_tone2:", "aliases": ":surfer_tone2:" }, "1f3c4-1f3fd": { "output": "1f3c4-1f3fd", "name": "person surfing: medium skin tone", "alpha_code": ":person_surfing_tone3:", "aliases": ":surfer_tone3:" }, "1f3c4-1f3fe": { "output": "1f3c4-1f3fe", "name": "person surfing: medium-dark skin tone", "alpha_code": ":person_surfing_tone4:", "aliases": ":surfer_tone4:" }, "1f3c4-1f3ff": { "output": "1f3c4-1f3ff", "name": "person surfing: dark skin tone", "alpha_code": ":person_surfing_tone5:", "aliases": ":surfer_tone5:" }, "1f6c0-1f3fb": { "output": "1f6c0-1f3fb", "name": "person taking bath: light skin tone", "alpha_code": ":bath_tone1:", "aliases": "" }, "1f6c0-1f3fc": { "output": "1f6c0-1f3fc", "name": "person taking bath: medium-light skin tone", "alpha_code": ":bath_tone2:", "aliases": "" }, "1f939-1f3fe": { "output": "1f939-1f3fe", "name": "person juggling: medium-dark skin tone", "alpha_code": ":person_juggling_tone4:", "aliases": ":juggling_tone4:|:juggler_tone4:" }, "1f6c0-1f3fd": { "output": "1f6c0-1f3fd", "name": "person taking bath: medium skin tone", "alpha_code": ":bath_tone3:", "aliases": "" }, "1f6c0-1f3fe": { "output": "1f6c0-1f3fe", "name": "person taking bath: medium-dark skin tone", "alpha_code": ":bath_tone4:", "aliases": "" }, "1f6c0-1f3ff": { "output": "1f6c0-1f3ff", "name": "person taking bath: dark skin tone", "alpha_code": ":bath_tone5:", "aliases": "" }, "1f939-1f3ff": { "output": "1f939-1f3ff", "name": "person juggling: dark skin tone", "alpha_code": ":person_juggling_tone5:", "aliases": ":juggling_tone5:|:juggler_tone5:" }, "1f6b4-1f3fb": { "output": "1f6b4-1f3fb", "name": "person biking: light skin tone", "alpha_code": ":person_biking_tone1:", "aliases": ":bicyclist_tone1:" }, "1f6b4-1f3fc": { "output": "1f6b4-1f3fc", "name": "person biking: medium-light skin tone", "alpha_code": ":person_biking_tone2:", "aliases": ":bicyclist_tone2:" }, "1f6b4-1f3fd": { "output": "1f6b4-1f3fd", "name": "person biking: medium skin tone", "alpha_code": ":person_biking_tone3:", "aliases": ":bicyclist_tone3:" }, "1f3f3-1f308": { "output": "1f3f3-fe0f-200d-1f308", "name": "rainbow flag", "alpha_code": ":rainbow_flag:", "aliases": ":gay_pride_flag:" }, "1f6b4-1f3fe": { "output": "1f6b4-1f3fe", "name": "person biking: medium-dark skin tone", "alpha_code": ":person_biking_tone4:", "aliases": ":bicyclist_tone4:" }, "1f6b4-1f3ff": { "output": "1f6b4-1f3ff", "name": "person biking: dark skin tone", "alpha_code": ":person_biking_tone5:", "aliases": ":bicyclist_tone5:" }, "1f6b5-1f3fb": { "output": "1f6b5-1f3fb", "name": "person mountain biking: light skin tone", "alpha_code": ":person_mountain_biking_tone1:", "aliases": ":mountain_bicyclist_tone1:" }, "1f6b5-1f3fc": { "output": "1f6b5-1f3fc", "name": "person mountain biking: medium-light skin tone", "alpha_code": ":person_mountain_biking_tone2:", "aliases": ":mountain_bicyclist_tone2:" }, "1f6b5-1f3fd": { "output": "1f6b5-1f3fd", "name": "person mountain biking: medium skin tone", "alpha_code": ":person_mountain_biking_tone3:", "aliases": ":mountain_bicyclist_tone3:" }, "1f6b5-1f3fe": { "output": "1f6b5-1f3fe", "name": "person mountain biking: medium-dark skin tone", "alpha_code": ":person_mountain_biking_tone4:", "aliases": ":mountain_bicyclist_tone4:" }, "1f6b5-1f3ff": { "output": "1f6b5-1f3ff", "name": "person mountain biking: dark skin tone", "alpha_code": ":person_mountain_biking_tone5:", "aliases": ":mountain_bicyclist_tone5:" }, "1f3c7-1f3fb": { "output": "1f3c7-1f3fb", "name": "horse racing: light skin tone", "alpha_code": ":horse_racing_tone1:", "aliases": "" }, "1f3c7-1f3fc": { "output": "1f3c7-1f3fc", "name": "horse racing: medium-light skin tone", "alpha_code": ":horse_racing_tone2:", "aliases": "" }, "1f3c7-1f3fd": { "output": "1f3c7-1f3fd", "name": "horse racing: medium skin tone", "alpha_code": ":horse_racing_tone3:", "aliases": "" }, "1f3c7-1f3fe": { "output": "1f3c7-1f3fe", "name": "horse racing: medium-dark skin tone", "alpha_code": ":horse_racing_tone4:", "aliases": "" }, "1f3c7-1f3ff": { "output": "1f3c7-1f3ff", "name": "horse racing: dark skin tone", "alpha_code": ":horse_racing_tone5:", "aliases": "" }, "270d-1f3fb": { "output": "270d-1f3fb", "name": "writing hand: light skin tone", "alpha_code": ":writing_hand_tone1:", "aliases": "" }, "270d-1f3fc": { "output": "270d-1f3fc", "name": "writing hand: medium-light skin tone", "alpha_code": ":writing_hand_tone2:", "aliases": "" }, "270d-1f3fd": { "output": "270d-1f3fd", "name": "writing hand: medium skin tone", "alpha_code": ":writing_hand_tone3:", "aliases": "" }, "270d-1f3fe": { "output": "270d-1f3fe", "name": "writing hand: medium-dark skin tone", "alpha_code": ":writing_hand_tone4:", "aliases": "" }, "270d-1f3ff": { "output": "270d-1f3ff", "name": "writing hand: dark skin tone", "alpha_code": ":writing_hand_tone5:", "aliases": "" }, "1f590-1f3fb": { "output": "1f590-1f3fb", "name": "raised hand with fingers splayed: light skin tone", "alpha_code": ":hand_splayed_tone1:", "aliases": ":raised_hand_with_fingers_splayed_tone1:" }, "1f590-1f3fc": { "output": "1f590-1f3fc", "name": "raised hand with fingers splayed: medium-light skin tone", "alpha_code": ":hand_splayed_tone2:", "aliases": ":raised_hand_with_fingers_splayed_tone2:" }, "1f590-1f3fd": { "output": "1f590-1f3fd", "name": "raised hand with fingers splayed: medium skin tone", "alpha_code": ":hand_splayed_tone3:", "aliases": ":raised_hand_with_fingers_splayed_tone3:" }, "1f590-1f3fe": { "output": "1f590-1f3fe", "name": "raised hand with fingers splayed: medium-dark skin tone", "alpha_code": ":hand_splayed_tone4:", "aliases": ":raised_hand_with_fingers_splayed_tone4:" }, "1f590-1f3ff": { "output": "1f590-1f3ff", "name": "raised hand with fingers splayed: dark skin tone", "alpha_code": ":hand_splayed_tone5:", "aliases": ":raised_hand_with_fingers_splayed_tone5:" }, "1f595-1f3fb": { "output": "1f595-1f3fb", "name": "middle finger: light skin tone", "alpha_code": ":middle_finger_tone1:", "aliases": ":reversed_hand_with_middle_finger_extended_tone1:" }, "1f595-1f3fc": { "output": "1f595-1f3fc", "name": "middle finger: medium-light skin tone", "alpha_code": ":middle_finger_tone2:", "aliases": ":reversed_hand_with_middle_finger_extended_tone2:" }, "1f595-1f3fd": { "output": "1f595-1f3fd", "name": "middle finger: medium skin tone", "alpha_code": ":middle_finger_tone3:", "aliases": ":reversed_hand_with_middle_finger_extended_tone3:" }, "1f595-1f3fe": { "output": "1f595-1f3fe", "name": "middle finger: medium-dark skin tone", "alpha_code": ":middle_finger_tone4:", "aliases": ":reversed_hand_with_middle_finger_extended_tone4:" }, "1f595-1f3ff": { "output": "1f595-1f3ff", "name": "middle finger: dark skin tone", "alpha_code": ":middle_finger_tone5:", "aliases": ":reversed_hand_with_middle_finger_extended_tone5:" }, "1f596-1f3fb": { "output": "1f596-1f3fb", "name": "vulcan salute: light skin tone", "alpha_code": ":vulcan_tone1:", "aliases": ":raised_hand_with_part_between_middle_and_ring_fingers_tone1:" }, "1f596-1f3fc": { "output": "1f596-1f3fc", "name": "vulcan salute: medium-light skin tone", "alpha_code": ":vulcan_tone2:", "aliases": ":raised_hand_with_part_between_middle_and_ring_fingers_tone2:" }, "1f596-1f3fd": { "output": "1f596-1f3fd", "name": "vulcan salute: medium skin tone", "alpha_code": ":vulcan_tone3:", "aliases": ":raised_hand_with_part_between_middle_and_ring_fingers_tone3:" }, "1f596-1f3fe": { "output": "1f596-1f3fe", "name": "vulcan salute: medium-dark skin tone", "alpha_code": ":vulcan_tone4:", "aliases": ":raised_hand_with_part_between_middle_and_ring_fingers_tone4:" }, "1f596-1f3ff": { "output": "1f596-1f3ff", "name": "vulcan salute: dark skin tone", "alpha_code": ":vulcan_tone5:", "aliases": ":raised_hand_with_part_between_middle_and_ring_fingers_tone5:" }, "1f468-1f468-1f466": { "output": "1f468-200d-1f468-200d-1f466", "name": "family: man, man, boy", "alpha_code": ":family_mmb:", "aliases": "" }, "1f468-1f468-1f466-1f466": { "output": "1f468-200d-1f468-200d-1f466-200d-1f466", "name": "family: man, man, boy, boy", "alpha_code": ":family_mmbb:", "aliases": "" }, "1f468-1f468-1f467": { "output": "1f468-200d-1f468-200d-1f467", "name": "family: man, man, girl", "alpha_code": ":family_mmg:", "aliases": "" }, "1f468-1f468-1f467-1f466": { "output": "1f468-200d-1f468-200d-1f467-200d-1f466", "name": "family: man, man, girl, boy", "alpha_code": ":family_mmgb:", "aliases": "" }, "1f468-1f468-1f467-1f467": { "output": "1f468-200d-1f468-200d-1f467-200d-1f467", "name": "family: man, man, girl, girl", "alpha_code": ":family_mmgg:", "aliases": "" }, "1f468-1f469-1f466-1f466": { "output": "1f468-200d-1f469-200d-1f466-200d-1f466", "name": "family: man, woman, boy, boy", "alpha_code": ":family_mwbb:", "aliases": "" }, "1f468-1f469-1f467": { "output": "1f468-200d-1f469-200d-1f467", "name": "family: man, woman, girl", "alpha_code": ":family_mwg:", "aliases": "" }, "1f468-1f469-1f467-1f466": { "output": "1f468-200d-1f469-200d-1f467-200d-1f466", "name": "family: man, woman, girl, boy", "alpha_code": ":family_mwgb:", "aliases": "" }, "1f468-1f469-1f467-1f467": { "output": "1f468-200d-1f469-200d-1f467-200d-1f467", "name": "family: man, woman, girl, girl", "alpha_code": ":family_mwgg:", "aliases": "" }, "1f469-1f469-1f466": { "output": "1f469-200d-1f469-200d-1f466", "name": "family: woman, woman, boy", "alpha_code": ":family_wwb:", "aliases": "" }, "1f469-1f469-1f466-1f466": { "output": "1f469-200d-1f469-200d-1f466-200d-1f466", "name": "family: woman, woman, boy, boy", "alpha_code": ":family_wwbb:", "aliases": "" }, "1f469-1f469-1f467": { "output": "1f469-200d-1f469-200d-1f467", "name": "family: woman, woman, girl", "alpha_code": ":family_wwg:", "aliases": "" }, "1f469-1f469-1f467-1f466": { "output": "1f469-200d-1f469-200d-1f467-200d-1f466", "name": "family: woman, woman, girl, boy", "alpha_code": ":family_wwgb:", "aliases": "" }, "1f469-1f469-1f467-1f467": { "output": "1f469-200d-1f469-200d-1f467-200d-1f467", "name": "family: woman, woman, girl, girl", "alpha_code": ":family_wwgg:", "aliases": "" }, "1f469-2764-1f469": { "output": "1f469-200d-2764-fe0f-200d-1f469", "name": "couple with heart: woman, woman", "alpha_code": ":couple_ww:", "aliases": ":couple_with_heart_ww:" }, "1f468-2764-1f468": { "output": "1f468-200d-2764-fe0f-200d-1f468", "name": "couple with heart: man, man", "alpha_code": ":couple_mm:", "aliases": ":couple_with_heart_mm:" }, "1f469-2764-1f48b-1f469": { "output": "1f469-200d-2764-fe0f-200d-1f48b-200d-1f469", "name": "kiss: woman, woman", "alpha_code": ":kiss_ww:", "aliases": ":couplekiss_ww:" }, "1f468-2764-1f48b-1f468": { "output": "1f468-200d-2764-fe0f-200d-1f48b-200d-1f468", "name": "kiss: man, man", "alpha_code": ":kiss_mm:", "aliases": ":couplekiss_mm:" }, "1f3fb": { "output": "1f3fb", "name": "light skin tone", "alpha_code": ":tone1:", "aliases": "" }, "1f3fc": { "output": "1f3fc", "name": "medium-light skin tone", "alpha_code": ":tone2:", "aliases": "" }, "1f3fd": { "output": "1f3fd", "name": "medium skin tone", "alpha_code": ":tone3:", "aliases": "" }, "1f3fe": { "output": "1f3fe", "name": "medium-dark skin tone", "alpha_code": ":tone4:", "aliases": "" }, "1f3ff": { "output": "1f3ff", "name": "dark skin tone", "alpha_code": ":tone5:", "aliases": "" }, "002a-20e3": { "output": "002a-fe0f-20e3", "name": "keycap: *", "alpha_code": ":asterisk:", "aliases": ":keycap_asterisk:" }, "23cf": { "output": "23cf-fe0f", "name": "eject button", "alpha_code": ":eject:", "aliases": ":eject_symbol:" }, "23ed": { "output": "23ed-fe0f", "name": "next track button", "alpha_code": ":track_next:", "aliases": ":next_track:" }, "23ee": { "output": "23ee-fe0f", "name": "last track button", "alpha_code": ":track_previous:", "aliases": ":previous_track:" }, "23ef": { "output": "23ef-fe0f", "name": "play or pause button", "alpha_code": ":play_pause:", "aliases": "" }, "1f441-1f5e8": { "output": "1f441-fe0f-200d-1f5e8-fe0f", "name": "eye in speech bubble", "alpha_code": ":eye_in_speech_bubble:", "aliases": "" }, "23f1": { "output": "23f1-fe0f", "name": "stopwatch", "alpha_code": ":stopwatch:", "aliases": "" }, "23f2": { "output": "23f2-fe0f", "name": "timer clock", "alpha_code": ":timer:", "aliases": ":timer_clock:" }, "23f8": { "output": "23f8-fe0f", "name": "pause button", "alpha_code": ":pause_button:", "aliases": ":double_vertical_bar:" }, "23f9": { "output": "23f9-fe0f", "name": "stop button", "alpha_code": ":stop_button:", "aliases": "" }, "23fa": { "output": "23fa-fe0f", "name": "record button", "alpha_code": ":record_button:", "aliases": "" }, "2602": { "output": "2602-fe0f", "name": "umbrella", "alpha_code": ":umbrella2:", "aliases": "" }, "2603": { "output": "2603-fe0f", "name": "snowman", "alpha_code": ":snowman2:", "aliases": "" }, "2604": { "output": "2604-fe0f", "name": "comet", "alpha_code": ":comet:", "aliases": "" }, "2618": { "output": "2618-fe0f", "name": "shamrock", "alpha_code": ":shamrock:", "aliases": "" }, "2620": { "output": "2620-fe0f", "name": "skull and crossbones", "alpha_code": ":skull_crossbones:", "aliases": ":skull_and_crossbones:" }, "2622": { "output": "2622-fe0f", "name": "radioactive", "alpha_code": ":radioactive:", "aliases": ":radioactive_sign:" }, "2623": { "output": "2623-fe0f", "name": "biohazard", "alpha_code": ":biohazard:", "aliases": ":biohazard_sign:" }, "2626": { "output": "2626-fe0f", "name": "orthodox cross", "alpha_code": ":orthodox_cross:", "aliases": "" }, "262a": { "output": "262a-fe0f", "name": "star and crescent", "alpha_code": ":star_and_crescent:", "aliases": "" }, "262e": { "output": "262e-fe0f", "name": "peace symbol", "alpha_code": ":peace:", "aliases": ":peace_symbol:" }, "262f": { "output": "262f-fe0f", "name": "yin yang", "alpha_code": ":yin_yang:", "aliases": "" }, "2638": { "output": "2638-fe0f", "name": "wheel of dharma", "alpha_code": ":wheel_of_dharma:", "aliases": "" }, "2639": { "output": "2639-fe0f", "name": "frowning face", "alpha_code": ":frowning2:", "aliases": ":white_frowning_face:" }, "2692": { "output": "2692-fe0f", "name": "hammer and pick", "alpha_code": ":hammer_pick:", "aliases": ":hammer_and_pick:" }, "2694": { "output": "2694-fe0f", "name": "crossed swords", "alpha_code": ":crossed_swords:", "aliases": "" }, "2696": { "output": "2696-fe0f", "name": "balance scale", "alpha_code": ":scales:", "aliases": "" }, "2697": { "output": "2697-fe0f", "name": "alembic", "alpha_code": ":alembic:", "aliases": "" }, "2699": { "output": "2699-fe0f", "name": "gear", "alpha_code": ":gear:", "aliases": "" }, "269b": { "output": "269b-fe0f", "name": "atom symbol", "alpha_code": ":atom:", "aliases": ":atom_symbol:" }, "269c": { "output": "269c-fe0f", "name": "fleur-de-lis", "alpha_code": ":fleur-de-lis:", "aliases": "" }, "26b0": { "output": "26b0-fe0f", "name": "coffin", "alpha_code": ":coffin:", "aliases": "" }, "26b1": { "output": "26b1-fe0f", "name": "funeral urn", "alpha_code": ":urn:", "aliases": ":funeral_urn:" }, "26c8": { "output": "26c8-fe0f", "name": "cloud with lightning and rain", "alpha_code": ":thunder_cloud_rain:", "aliases": ":thunder_cloud_and_rain:" }, "26cf": { "output": "26cf-fe0f", "name": "pick", "alpha_code": ":pick:", "aliases": "" }, "26d1": { "output": "26d1-fe0f", "name": "rescue worker\u2019s helmet", "alpha_code": ":helmet_with_cross:", "aliases": ":helmet_with_white_cross:" }, "26d3": { "output": "26d3-fe0f", "name": "chains", "alpha_code": ":chains:", "aliases": "" }, "26e9": { "output": "26e9-fe0f", "name": "shinto shrine", "alpha_code": ":shinto_shrine:", "aliases": "" }, "26f0": { "output": "26f0-fe0f", "name": "mountain", "alpha_code": ":mountain:", "aliases": "" }, "26f1": { "output": "26f1-fe0f", "name": "umbrella on ground", "alpha_code": ":beach_umbrella:", "aliases": ":umbrella_on_ground:" }, "26f4": { "output": "26f4-fe0f", "name": "ferry", "alpha_code": ":ferry:", "aliases": "" }, "26f7": { "output": "26f7-fe0f", "name": "skier", "alpha_code": ":skier:", "aliases": "" }, "26f8": { "output": "26f8-fe0f", "name": "ice skate", "alpha_code": ":ice_skate:", "aliases": "" }, "26f9": { "output": "26f9-fe0f", "name": "person bouncing ball", "alpha_code": ":person_bouncing_ball:", "aliases": ":basketball_player:|:person_with_ball:" }, "2721": { "output": "2721-fe0f", "name": "star of David", "alpha_code": ":star_of_david:", "aliases": "" }, "2763": { "output": "2763-fe0f", "name": "heavy heart exclamation", "alpha_code": ":heart_exclamation:", "aliases": ":heavy_heart_exclamation_mark_ornament:" }, "1f324": { "output": "1f324-fe0f", "name": "sun behind small cloud", "alpha_code": ":white_sun_small_cloud:", "aliases": ":white_sun_with_small_cloud:" }, "1f325": { "output": "1f325-fe0f", "name": "sun behind large cloud", "alpha_code": ":white_sun_cloud:", "aliases": ":white_sun_behind_cloud:" }, "1f326": { "output": "1f326-fe0f", "name": "sun behind rain cloud", "alpha_code": ":white_sun_rain_cloud:", "aliases": ":white_sun_behind_cloud_with_rain:" }, "1f5b1": { "output": "1f5b1-fe0f", "name": "computer mouse", "alpha_code": ":mouse_three_button:", "aliases": ":three_button_mouse:" }, "1f385-1f3fb": { "output": "1f385-1f3fb", "name": "Santa Claus: light skin tone", "alpha_code": ":santa_tone1:", "aliases": "" }, "1f385-1f3fc": { "output": "1f385-1f3fc", "name": "Santa Claus: medium-light skin tone", "alpha_code": ":santa_tone2:", "aliases": "" }, "1f385-1f3fd": { "output": "1f385-1f3fd", "name": "Santa Claus: medium skin tone", "alpha_code": ":santa_tone3:", "aliases": "" }, "1f385-1f3fe": { "output": "1f385-1f3fe", "name": "Santa Claus: medium-dark skin tone", "alpha_code": ":santa_tone4:", "aliases": "" }, "1f385-1f3ff": { "output": "1f385-1f3ff", "name": "Santa Claus: dark skin tone", "alpha_code": ":santa_tone5:", "aliases": "" }, "1f918-1f3fb": { "output": "1f918-1f3fb", "name": "sign of the horns: light skin tone", "alpha_code": ":metal_tone1:", "aliases": ":sign_of_the_horns_tone1:" }, "1f918-1f3fc": { "output": "1f918-1f3fc", "name": "sign of the horns: medium-light skin tone", "alpha_code": ":metal_tone2:", "aliases": ":sign_of_the_horns_tone2:" }, "1f918-1f3fd": { "output": "1f918-1f3fd", "name": "sign of the horns: medium skin tone", "alpha_code": ":metal_tone3:", "aliases": ":sign_of_the_horns_tone3:" }, "1f918-1f3fe": { "output": "1f918-1f3fe", "name": "sign of the horns: medium-dark skin tone", "alpha_code": ":metal_tone4:", "aliases": ":sign_of_the_horns_tone4:" }, "1f918-1f3ff": { "output": "1f918-1f3ff", "name": "sign of the horns: dark skin tone", "alpha_code": ":metal_tone5:", "aliases": ":sign_of_the_horns_tone5:" }, "1f3cb-1f3fb": { "output": "1f3cb-1f3fb", "name": "person lifting weights: light skin tone", "alpha_code": ":person_lifting_weights_tone1:", "aliases": ":lifter_tone1:|:weight_lifter_tone1:" }, "1f3cb-1f3fc": { "output": "1f3cb-1f3fc", "name": "person lifting weights: medium-light skin tone", "alpha_code": ":person_lifting_weights_tone2:", "aliases": ":lifter_tone2:|:weight_lifter_tone2:" }, "1f3cb-1f3fd": { "output": "1f3cb-1f3fd", "name": "person lifting weights: medium skin tone", "alpha_code": ":person_lifting_weights_tone3:", "aliases": ":lifter_tone3:|:weight_lifter_tone3:" }, "1f3cb-1f3fe": { "output": "1f3cb-1f3fe", "name": "person lifting weights: medium-dark skin tone", "alpha_code": ":person_lifting_weights_tone4:", "aliases": ":lifter_tone4:|:weight_lifter_tone4:" }, "1f3cb-1f3ff": { "output": "1f3cb-1f3ff", "name": "person lifting weights: dark skin tone", "alpha_code": ":person_lifting_weights_tone5:", "aliases": ":lifter_tone5:|:weight_lifter_tone5:" }, "26f9-1f3fb": { "output": "26f9-1f3fb", "name": "person bouncing ball: light skin tone", "alpha_code": ":person_bouncing_ball_tone1:", "aliases": ":basketball_player_tone1:|:person_with_ball_tone1:" }, "26f9-1f3fc": { "output": "26f9-1f3fc", "name": "person bouncing ball: medium-light skin tone", "alpha_code": ":person_bouncing_ball_tone2:", "aliases": ":basketball_player_tone2:|:person_with_ball_tone2:" }, "26f9-1f3fd": { "output": "26f9-1f3fd", "name": "person bouncing ball: medium skin tone", "alpha_code": ":person_bouncing_ball_tone3:", "aliases": ":basketball_player_tone3:|:person_with_ball_tone3:" }, "26f9-1f3fe": { "output": "26f9-1f3fe", "name": "person bouncing ball: medium-dark skin tone", "alpha_code": ":person_bouncing_ball_tone4:", "aliases": ":basketball_player_tone4:|:person_with_ball_tone4:" }, "26f9-1f3ff": { "output": "26f9-1f3ff", "name": "person bouncing ball: dark skin tone", "alpha_code": ":person_bouncing_ball_tone5:", "aliases": ":basketball_player_tone5:|:person_with_ball_tone5:" }, "1f643": { "output": "1f643", "name": "upside-down face", "alpha_code": ":upside_down:", "aliases": ":upside_down_face:" }, "1f911": { "output": "1f911", "name": "money-mouth face", "alpha_code": ":money_mouth:", "aliases": ":money_mouth_face:" }, "1f913": { "output": "1f913", "name": "nerd face", "alpha_code": ":nerd:", "aliases": ":nerd_face:" }, "1f917": { "output": "1f917", "name": "hugging face", "alpha_code": ":hugging:", "aliases": ":hugging_face:" }, "1f644": { "output": "1f644", "name": "face with rolling eyes", "alpha_code": ":rolling_eyes:", "aliases": ":face_with_rolling_eyes:" }, "1f914": { "output": "1f914", "name": "thinking face", "alpha_code": ":thinking:", "aliases": ":thinking_face:" }, "1f910": { "output": "1f910", "name": "zipper-mouth face", "alpha_code": ":zipper_mouth:", "aliases": ":zipper_mouth_face:" }, "1f912": { "output": "1f912", "name": "face with thermometer", "alpha_code": ":thermometer_face:", "aliases": ":face_with_thermometer:" }, "1f915": { "output": "1f915", "name": "face with head-bandage", "alpha_code": ":head_bandage:", "aliases": ":face_with_head_bandage:" }, "1f916": { "output": "1f916", "name": "robot face", "alpha_code": ":robot:", "aliases": ":robot_face:" }, "1f981": { "output": "1f981", "name": "lion face", "alpha_code": ":lion_face:", "aliases": ":lion:" }, "1f984": { "output": "1f984", "name": "unicorn face", "alpha_code": ":unicorn:", "aliases": ":unicorn_face:" }, "1f982": { "output": "1f982", "name": "scorpion", "alpha_code": ":scorpion:", "aliases": "" }, "1f980": { "output": "1f980", "name": "crab", "alpha_code": ":crab:", "aliases": "" }, "1f983": { "output": "1f983", "name": "turkey", "alpha_code": ":turkey:", "aliases": "" }, "1f9c0": { "output": "1f9c0", "name": "cheese wedge", "alpha_code": ":cheese:", "aliases": ":cheese_wedge:" }, "1f32d": { "output": "1f32d", "name": "hot dog", "alpha_code": ":hotdog:", "aliases": ":hot_dog:" }, "1f32e": { "output": "1f32e", "name": "taco", "alpha_code": ":taco:", "aliases": "" }, "1f32f": { "output": "1f32f", "name": "burrito", "alpha_code": ":burrito:", "aliases": "" }, "1f37f": { "output": "1f37f", "name": "popcorn", "alpha_code": ":popcorn:", "aliases": "" }, "1f37e": { "output": "1f37e", "name": "bottle with popping cork", "alpha_code": ":champagne:", "aliases": ":bottle_with_popping_cork:" }, "1f3f9": { "output": "1f3f9", "name": "bow and arrow", "alpha_code": ":bow_and_arrow:", "aliases": ":archery:" }, "1f3fa": { "output": "1f3fa", "name": "amphora", "alpha_code": ":amphora:", "aliases": "" }, "1f6d0": { "output": "1f6d0", "name": "place of worship", "alpha_code": ":place_of_worship:", "aliases": ":worship_symbol:" }, "1f54b": { "output": "1f54b", "name": "kaaba", "alpha_code": ":kaaba:", "aliases": "" }, "1f54c": { "output": "1f54c", "name": "mosque", "alpha_code": ":mosque:", "aliases": "" }, "1f54d": { "output": "1f54d", "name": "synagogue", "alpha_code": ":synagogue:", "aliases": "" }, "1f54e": { "output": "1f54e", "name": "menorah", "alpha_code": ":menorah:", "aliases": "" }, "1f4ff": { "output": "1f4ff", "name": "prayer beads", "alpha_code": ":prayer_beads:", "aliases": "" }, "1f3cf": { "output": "1f3cf", "name": "cricket game", "alpha_code": ":cricket_game:", "aliases": ":cricket_bat_ball:" }, "1f3d0": { "output": "1f3d0", "name": "volleyball", "alpha_code": ":volleyball:", "aliases": "" }, "1f3d1": { "output": "1f3d1", "name": "field hockey", "alpha_code": ":field_hockey:", "aliases": "" }, "1f3d2": { "output": "1f3d2", "name": "ice hockey", "alpha_code": ":hockey:", "aliases": "" }, "1f3d3": { "output": "1f3d3", "name": "ping pong", "alpha_code": ":ping_pong:", "aliases": ":table_tennis:" }, "1f3f8": { "output": "1f3f8", "name": "badminton", "alpha_code": ":badminton:", "aliases": "" }, "1f1e6-1f1fd": { "output": "1f1e6-1f1fd", "name": "\u00c5land Islands", "alpha_code": ":flag_ax:", "aliases": ":ax:" }, "1f1f9-1f1e6": { "output": "1f1f9-1f1e6", "name": "Tristan da Cunha", "alpha_code": ":flag_ta:", "aliases": ":ta:" }, "1f1ee-1f1f4": { "output": "1f1ee-1f1f4", "name": "British Indian Ocean Territory", "alpha_code": ":flag_io:", "aliases": ":io:" }, "1f1e7-1f1f6": { "output": "1f1e7-1f1f6", "name": "Caribbean Netherlands", "alpha_code": ":flag_bq:", "aliases": ":bq:" }, "1f1e8-1f1fd": { "output": "1f1e8-1f1fd", "name": "Christmas Island", "alpha_code": ":flag_cx:", "aliases": ":cx:" }, "1f1e8-1f1e8": { "output": "1f1e8-1f1e8", "name": "Cocos (Keeling) Islands", "alpha_code": ":flag_cc:", "aliases": ":cc:" }, "1f1ec-1f1ec": { "output": "1f1ec-1f1ec", "name": "Guernsey", "alpha_code": ":flag_gg:", "aliases": ":gg:" }, "1f1ee-1f1f2": { "output": "1f1ee-1f1f2", "name": "Isle of Man", "alpha_code": ":flag_im:", "aliases": ":im:" }, "1f1fe-1f1f9": { "output": "1f1fe-1f1f9", "name": "Mayotte", "alpha_code": ":flag_yt:", "aliases": ":yt:" }, "1f1f3-1f1eb": { "output": "1f1f3-1f1eb", "name": "Norfolk Island", "alpha_code": ":flag_nf:", "aliases": ":nf:" }, "1f1f5-1f1f3": { "output": "1f1f5-1f1f3", "name": "Pitcairn Islands", "alpha_code": ":flag_pn:", "aliases": ":pn:" }, "1f1e7-1f1f1": { "output": "1f1e7-1f1f1", "name": "St. Barth\u00e9lemy", "alpha_code": ":flag_bl:", "aliases": ":bl:" }, "1f1f5-1f1f2": { "output": "1f1f5-1f1f2", "name": "St. Pierre & Miquelon", "alpha_code": ":flag_pm:", "aliases": ":pm:" }, "1f1ec-1f1f8": { "output": "1f1ec-1f1f8", "name": "South Georgia & South Sandwich Islands", "alpha_code": ":flag_gs:", "aliases": ":gs:" }, "1f1f9-1f1f0": { "output": "1f1f9-1f1f0", "name": "Tokelau", "alpha_code": ":flag_tk:", "aliases": ":tk:" }, "1f1e7-1f1fb": { "output": "1f1e7-1f1fb", "name": "Bouvet Island", "alpha_code": ":flag_bv:", "aliases": ":bv:" }, "1f1ed-1f1f2": { "output": "1f1ed-1f1f2", "name": "Heard & McDonald Islands", "alpha_code": ":flag_hm:", "aliases": ":hm:" }, "1f1f8-1f1ef": { "output": "1f1f8-1f1ef", "name": "Svalbard & Jan Mayen", "alpha_code": ":flag_sj:", "aliases": ":sj:" }, "1f1fa-1f1f2": { "output": "1f1fa-1f1f2", "name": "U.S. Outlying Islands", "alpha_code": ":flag_um:", "aliases": ":um:" }, "1f1ee-1f1e8": { "output": "1f1ee-1f1e8", "name": "Canary Islands", "alpha_code": ":flag_ic:", "aliases": ":ic:" }, "1f1ea-1f1e6": { "output": "1f1ea-1f1e6", "name": "Ceuta & Melilla", "alpha_code": ":flag_ea:", "aliases": ":ea:" }, "1f1e8-1f1f5": { "output": "1f1e8-1f1f5", "name": "Clipperton Island", "alpha_code": ":flag_cp:", "aliases": ":cp:" }, "1f1e9-1f1ec": { "output": "1f1e9-1f1ec", "name": "Diego Garcia", "alpha_code": ":flag_dg:", "aliases": ":dg:" }, "1f1e6-1f1f8": { "output": "1f1e6-1f1f8", "name": "American Samoa", "alpha_code": ":flag_as:", "aliases": ":as:" }, "1f1e6-1f1f6": { "output": "1f1e6-1f1f6", "name": "Antarctica", "alpha_code": ":flag_aq:", "aliases": ":aq:" }, "1f1fb-1f1ec": { "output": "1f1fb-1f1ec", "name": "British Virgin Islands", "alpha_code": ":flag_vg:", "aliases": ":vg:" }, "1f1e8-1f1f0": { "output": "1f1e8-1f1f0", "name": "Cook Islands", "alpha_code": ":flag_ck:", "aliases": ":ck:" }, "1f1e8-1f1fc": { "output": "1f1e8-1f1fc", "name": "Cura\u00e7ao", "alpha_code": ":flag_cw:", "aliases": ":cw:" }, "1f1ea-1f1fa": { "output": "1f1ea-1f1fa", "name": "European Union", "alpha_code": ":flag_eu:", "aliases": ":eu:" }, "1f1ec-1f1eb": { "output": "1f1ec-1f1eb", "name": "French Guiana", "alpha_code": ":flag_gf:", "aliases": ":gf:" }, "1f1f9-1f1eb": { "output": "1f1f9-1f1eb", "name": "French Southern Territories", "alpha_code": ":flag_tf:", "aliases": ":tf:" }, "1f1ec-1f1f5": { "output": "1f1ec-1f1f5", "name": "Guadeloupe", "alpha_code": ":flag_gp:", "aliases": ":gp:" }, "1f1f2-1f1f6": { "output": "1f1f2-1f1f6", "name": "Martinique", "alpha_code": ":flag_mq:", "aliases": ":mq:" }, "1f1f2-1f1f5": { "output": "1f1f2-1f1f5", "name": "Northern Mariana Islands", "alpha_code": ":flag_mp:", "aliases": ":mp:" }, "1f1f7-1f1ea": { "output": "1f1f7-1f1ea", "name": "R\u00e9union", "alpha_code": ":flag_re:", "aliases": ":re:" }, "1f1f8-1f1fd": { "output": "1f1f8-1f1fd", "name": "Sint Maarten", "alpha_code": ":flag_sx:", "aliases": ":sx:" }, "1f1f8-1f1f8": { "output": "1f1f8-1f1f8", "name": "South Sudan", "alpha_code": ":flag_ss:", "aliases": ":ss:" }, "1f1f9-1f1e8": { "output": "1f1f9-1f1e8", "name": "Turks & Caicos Islands", "alpha_code": ":flag_tc:", "aliases": ":tc:" }, "1f1f2-1f1eb": { "output": "1f1f2-1f1eb", "name": "St. Martin", "alpha_code": ":flag_mf:", "aliases": ":mf:" }, "1f575-1f3fb": { "output": "1f575-1f3fb", "name": "detective: light skin tone", "alpha_code": ":detective_tone1:", "aliases": ":spy_tone1:|:sleuth_or_spy_tone1:" }, "1f575-1f3fc": { "output": "1f575-1f3fc", "name": "detective: medium-light skin tone", "alpha_code": ":detective_tone2:", "aliases": ":spy_tone2:|:sleuth_or_spy_tone2:" }, "1f575-1f3fd": { "output": "1f575-1f3fd", "name": "detective: medium skin tone", "alpha_code": ":detective_tone3:", "aliases": ":spy_tone3:|:sleuth_or_spy_tone3:" }, "1f575-1f3fe": { "output": "1f575-1f3fe", "name": "detective: medium-dark skin tone", "alpha_code": ":detective_tone4:", "aliases": ":spy_tone4:|:sleuth_or_spy_tone4:" }, "1f575-1f3ff": { "output": "1f575-1f3ff", "name": "detective: dark skin tone", "alpha_code": ":detective_tone5:", "aliases": ":spy_tone5:|:sleuth_or_spy_tone5:" }, "1f941": { "output": "1f941", "name": "drum", "alpha_code": ":drum:", "aliases": ":drum_with_drumsticks:" }, "1f990": { "output": "1f990", "name": "shrimp", "alpha_code": ":shrimp:", "aliases": "" }, "1f991": { "output": "1f991", "name": "squid", "alpha_code": ":squid:", "aliases": "" }, "1f95a": { "output": "1f95a", "name": "egg", "alpha_code": ":egg:", "aliases": "" }, "1f95b": { "output": "1f95b", "name": "glass of milk", "alpha_code": ":milk:", "aliases": ":glass_of_milk:" }, "1f95c": { "output": "1f95c", "name": "peanuts", "alpha_code": ":peanuts:", "aliases": ":shelled_peanut:" }, "1f95d": { "output": "1f95d", "name": "kiwi fruit", "alpha_code": ":kiwi:", "aliases": ":kiwifruit:" }, "1f95e": { "output": "1f95e", "name": "pancakes", "alpha_code": ":pancakes:", "aliases": "" }, "1f1fc": { "output": "1f1fc", "name": "regional indicator symbol letter w", "alpha_code": ":regional_indicator_w:", "aliases": "" }, "1f1fb": { "output": "1f1fb", "name": "regional indicator symbol letter v", "alpha_code": ":regional_indicator_v:", "aliases": "" }, "1f1fa": { "output": "1f1fa", "name": "regional indicator symbol letter u", "alpha_code": ":regional_indicator_u:", "aliases": "" }, "1f1f9": { "output": "1f1f9", "name": "regional indicator symbol letter t", "alpha_code": ":regional_indicator_t:", "aliases": "" }, "1f1f8": { "output": "1f1f8", "name": "regional indicator symbol letter s", "alpha_code": ":regional_indicator_s:", "aliases": "" }, "1f1f7": { "output": "1f1f7", "name": "regional indicator symbol letter r", "alpha_code": ":regional_indicator_r:", "aliases": "" }, "1f1f6": { "output": "1f1f6", "name": "regional indicator symbol letter q", "alpha_code": ":regional_indicator_q:", "aliases": "" }, "1f1f5": { "output": "1f1f5", "name": "regional indicator symbol letter p", "alpha_code": ":regional_indicator_p:", "aliases": "" }, "1f1f4": { "output": "1f1f4", "name": "regional indicator symbol letter o", "alpha_code": ":regional_indicator_o:", "aliases": "" }, "1f1f3": { "output": "1f1f3", "name": "regional indicator symbol letter n", "alpha_code": ":regional_indicator_n:", "aliases": "" }, "1f1f2": { "output": "1f1f2", "name": "regional indicator symbol letter m", "alpha_code": ":regional_indicator_m:", "aliases": "" }, "1f1f1": { "output": "1f1f1", "name": "regional indicator symbol letter l", "alpha_code": ":regional_indicator_l:", "aliases": "" }, "1f1f0": { "output": "1f1f0", "name": "regional indicator symbol letter k", "alpha_code": ":regional_indicator_k:", "aliases": "" }, "1f1ef": { "output": "1f1ef", "name": "regional indicator symbol letter j", "alpha_code": ":regional_indicator_j:", "aliases": "" }, "1f1ee": { "output": "1f1ee", "name": "regional indicator symbol letter i", "alpha_code": ":regional_indicator_i:", "aliases": "" }, "1f1ed": { "output": "1f1ed", "name": "regional indicator symbol letter h", "alpha_code": ":regional_indicator_h:", "aliases": "" }, "1f1ec": { "output": "1f1ec", "name": "regional indicator symbol letter g", "alpha_code": ":regional_indicator_g:", "aliases": "" }, "1f1eb": { "output": "1f1eb", "name": "regional indicator symbol letter f", "alpha_code": ":regional_indicator_f:", "aliases": "" }, "1f1ea": { "output": "1f1ea", "name": "regional indicator symbol letter e", "alpha_code": ":regional_indicator_e:", "aliases": "" }, "1f1e9": { "output": "1f1e9", "name": "regional indicator symbol letter d", "alpha_code": ":regional_indicator_d:", "aliases": "" }, "1f1e8": { "output": "1f1e8", "name": "regional indicator symbol letter c", "alpha_code": ":regional_indicator_c:", "aliases": "" }, "1f1e7": { "output": "1f1e7", "name": "regional indicator symbol letter b", "alpha_code": ":regional_indicator_b:", "aliases": "" }, "1f1e6": { "output": "1f1e6", "name": "regional indicator symbol letter a", "alpha_code": ":regional_indicator_a:", "aliases": "" }, "0039": { "output": "0039-fe0f", "name": "digit nine", "alpha_code": ":digit_nine:", "aliases": "" }, "0038": { "output": "0038-fe0f", "name": "digit eight", "alpha_code": ":digit_eight:", "aliases": "" }, "0037": { "output": "0037-fe0f", "name": "digit seven", "alpha_code": ":digit_seven:", "aliases": "" }, "0036": { "output": "0036-fe0f", "name": "digit six", "alpha_code": ":digit_six:", "aliases": "" }, "0035": { "output": "0035-fe0f", "name": "digit five", "alpha_code": ":digit_five:", "aliases": "" }, "0034": { "output": "0034-fe0f", "name": "digit four", "alpha_code": ":digit_four:", "aliases": "" }, "0033": { "output": "0033-fe0f", "name": "digit three", "alpha_code": ":digit_three:", "aliases": "" }, "0032": { "output": "0032-fe0f", "name": "digit two", "alpha_code": ":digit_two:", "aliases": "" }, "0031": { "output": "0031-fe0f", "name": "digit one", "alpha_code": ":digit_one:", "aliases": "" }, "0030": { "output": "0030-fe0f", "name": "digit zero", "alpha_code": ":digit_zero:", "aliases": "" }, "1f46f-2642": { "output": "1f46f-200d-2642-fe0f", "name": "men with bunny ears partying", "alpha_code": ":men_with_bunny_ears_partying:", "aliases": "" }, "1f46f-2640": { "output": "1f46f-200d-2640-fe0f", "name": "women with bunny ears partying", "alpha_code": ":women_with_bunny_ears_partying:", "aliases": "" }, "1f3c2-1f3fb": { "output": "1f3c2-1f3fb", "name": "snowboarder: light skin tone", "alpha_code": ":snowboarder_tone1:", "aliases": ":snowboarder_light_skin_tone:" }, "1f3cc-2642": { "output": "1f3cc-fe0f-200d-2642-fe0f", "name": "man golfing", "alpha_code": ":man_golfing:", "aliases": "" }, "1f3cc-1f3fb-2642": { "output": "1f3cc-1f3fb-200d-2642-fe0f", "name": "man golfing: light skin tone", "alpha_code": ":man_golfing_tone1:", "aliases": ":man_golfing_light_skin_tone:" }, "1f3cc-1f3fc-2642": { "output": "1f3cc-1f3fc-200d-2642-fe0f", "name": "man golfing: medium-light skin tone", "alpha_code": ":man_golfing_tone2:", "aliases": ":man_golfing_medium_light_skin_tone:" }, "1f3cc-1f3fd-2642": { "output": "1f3cc-1f3fd-200d-2642-fe0f", "name": "man golfing: medium skin tone", "alpha_code": ":man_golfing_tone3:", "aliases": ":man_golfing_medium_skin_tone:" }, "1f3cc-1f3fe-2642": { "output": "1f3cc-1f3fe-200d-2642-fe0f", "name": "man golfing: medium-dark skin tone", "alpha_code": ":man_golfing_tone4:", "aliases": ":man_golfing_medium_dark_skin_tone:" }, "1f3cc-1f3ff-2642": { "output": "1f3cc-1f3ff-200d-2642-fe0f", "name": "man golfing: dark skin tone", "alpha_code": ":man_golfing_tone5:", "aliases": ":man_golfing_dark_skin_tone:" }, "1f3cc-2640": { "output": "1f3cc-fe0f-200d-2640-fe0f", "name": "woman golfing", "alpha_code": ":woman_golfing:", "aliases": "" }, "1f3cc-1f3fb-2640": { "output": "1f3cc-1f3fb-200d-2640-fe0f", "name": "woman golfing: light skin tone", "alpha_code": ":woman_golfing_tone1:", "aliases": ":woman_golfing_light_skin_tone:" }, "1f3cc-1f3fc-2640": { "output": "1f3cc-1f3fc-200d-2640-fe0f", "name": "woman golfing: medium-light skin tone", "alpha_code": ":woman_golfing_tone2:", "aliases": ":woman_golfing_medium_light_skin_tone:" }, "1f3cc-1f3fd-2640": { "output": "1f3cc-1f3fd-200d-2640-fe0f", "name": "woman golfing: medium skin tone", "alpha_code": ":woman_golfing_tone3:", "aliases": ":woman_golfing_medium_skin_tone:" }, "1f3cc-1f3fe-2640": { "output": "1f3cc-1f3fe-200d-2640-fe0f", "name": "woman golfing: medium-dark skin tone", "alpha_code": ":woman_golfing_tone4:", "aliases": ":woman_golfing_medium_dark_skin_tone:" }, "1f3cc-1f3ff-2640": { "output": "1f3cc-1f3ff-200d-2640-fe0f", "name": "woman golfing: dark skin tone", "alpha_code": ":woman_golfing_tone5:", "aliases": ":woman_golfing_dark_skin_tone:" }, "1f93c-2642": { "output": "1f93c-200d-2642-fe0f", "name": "men wrestling", "alpha_code": ":men_wrestling:", "aliases": "" }, "1f93c-2640": { "output": "1f93c-200d-2640-fe0f", "name": "women wrestling", "alpha_code": ":women_wrestling:", "aliases": "" }, "1f939-1f3ff-2642": { "output": "1f939-1f3ff-200d-2642-fe0f", "name": "man juggling: dark skin tone", "alpha_code": ":man_juggling_tone5:", "aliases": ":man_juggling_dark_skin_tone:" }, "1f939-1f3fe-2642": { "output": "1f939-1f3fe-200d-2642-fe0f", "name": "man juggling: medium-dark skin tone", "alpha_code": ":man_juggling_tone4:", "aliases": ":man_juggling_medium_dark_skin_tone:" }, "1f939-1f3fd-2642": { "output": "1f939-1f3fd-200d-2642-fe0f", "name": "man juggling: medium skin tone", "alpha_code": ":man_juggling_tone3:", "aliases": ":man_juggling_medium_skin_tone:" }, "1f939-1f3fc-2642": { "output": "1f939-1f3fc-200d-2642-fe0f", "name": "man juggling: medium-light skin tone", "alpha_code": ":man_juggling_tone2:", "aliases": ":man_juggling_medium_light_skin_tone:" }, "1f939-1f3fb-2642": { "output": "1f939-1f3fb-200d-2642-fe0f", "name": "man juggling: light skin tone", "alpha_code": ":man_juggling_tone1:", "aliases": ":man_juggling_light_skin_tone:" }, "1f939-2642": { "output": "1f939-200d-2642-fe0f", "name": "man juggling", "alpha_code": ":man_juggling:", "aliases": "" }, "1f939-1f3ff-2640": { "output": "1f939-1f3ff-200d-2640-fe0f", "name": "woman juggling: dark skin tone", "alpha_code": ":woman_juggling_tone5:", "aliases": ":woman_juggling_dark_skin_tone:" }, "1f939-1f3fe-2640": { "output": "1f939-1f3fe-200d-2640-fe0f", "name": "woman juggling: medium-dark skin tone", "alpha_code": ":woman_juggling_tone4:", "aliases": ":woman_juggling_medium_dark_skin_tone:" }, "1f939-1f3fd-2640": { "output": "1f939-1f3fd-200d-2640-fe0f", "name": "woman juggling: medium skin tone", "alpha_code": ":woman_juggling_tone3:", "aliases": ":woman_juggling_medium_skin_tone:" }, "1f939-1f3fc-2640": { "output": "1f939-1f3fc-200d-2640-fe0f", "name": "woman juggling: medium-light skin tone", "alpha_code": ":woman_juggling_tone2:", "aliases": ":woman_juggling_medium_light_skin_tone:" }, "1f939-1f3fb-2640": { "output": "1f939-1f3fb-200d-2640-fe0f", "name": "woman juggling: light skin tone", "alpha_code": ":woman_juggling_tone1:", "aliases": ":woman_juggling_light_skin_tone:" }, "1f939-2640": { "output": "1f939-200d-2640-fe0f", "name": "woman juggling", "alpha_code": ":woman_juggling:", "aliases": "" }, "1f93e-1f3ff-2642": { "output": "1f93e-1f3ff-200d-2642-fe0f", "name": "man playing handball: dark skin tone", "alpha_code": ":man_playing_handball_tone5:", "aliases": ":man_playing_handball_dark_skin_tone:" }, "1f93e-1f3fe-2642": { "output": "1f93e-1f3fe-200d-2642-fe0f", "name": "man playing handball: medium-dark skin tone", "alpha_code": ":man_playing_handball_tone4:", "aliases": ":man_playing_handball_medium_dark_skin_tone:" }, "1f93e-1f3fd-2642": { "output": "1f93e-1f3fd-200d-2642-fe0f", "name": "man playing handball: medium skin tone", "alpha_code": ":man_playing_handball_tone3:", "aliases": ":man_playing_handball_medium_skin_tone:" }, "1f93e-1f3fc-2642": { "output": "1f93e-1f3fc-200d-2642-fe0f", "name": "man playing handball: medium-light skin tone", "alpha_code": ":man_playing_handball_tone2:", "aliases": ":man_playing_handball_medium_light_skin_tone:" }, "1f93e-1f3fb-2642": { "output": "1f93e-1f3fb-200d-2642-fe0f", "name": "man playing handball: light skin tone", "alpha_code": ":man_playing_handball_tone1:", "aliases": ":man_playing_handball_light_skin_tone:" }, "1f93e-2642": { "output": "1f93e-200d-2642-fe0f", "name": "man playing handball", "alpha_code": ":man_playing_handball:", "aliases": "" }, "1f93e-1f3ff-2640": { "output": "1f93e-1f3ff-200d-2640-fe0f", "name": "woman playing handball: dark skin tone", "alpha_code": ":woman_playing_handball_tone5:", "aliases": ":woman_playing_handball_dark_skin_tone:" }, "1f93e-1f3fe-2640": { "output": "1f93e-1f3fe-200d-2640-fe0f", "name": "woman playing handball: medium-dark skin tone", "alpha_code": ":woman_playing_handball_tone4:", "aliases": ":woman_playing_handball_medium_dark_skin_tone:" }, "1f93e-1f3fd-2640": { "output": "1f93e-1f3fd-200d-2640-fe0f", "name": "woman playing handball: medium skin tone", "alpha_code": ":woman_playing_handball_tone3:", "aliases": ":woman_playing_handball_medium_skin_tone:" }, "1f93e-1f3fc-2640": { "output": "1f93e-1f3fc-200d-2640-fe0f", "name": "woman playing handball: medium-light skin tone", "alpha_code": ":woman_playing_handball_tone2:", "aliases": ":woman_playing_handball_medium_light_skin_tone:" }, "1f93e-1f3fb-2640": { "output": "1f93e-1f3fb-200d-2640-fe0f", "name": "woman playing handball: light skin tone", "alpha_code": ":woman_playing_handball_tone1:", "aliases": ":woman_playing_handball_light_skin_tone:" }, "1f93e-2640": { "output": "1f93e-200d-2640-fe0f", "name": "woman playing handball", "alpha_code": ":woman_playing_handball:", "aliases": "" }, "1f93d-1f3ff-2642": { "output": "1f93d-1f3ff-200d-2642-fe0f", "name": "man playing water polo: dark skin tone", "alpha_code": ":man_playing_water_polo_tone5:", "aliases": ":man_playing_water_polo_dark_skin_tone:" }, "1f93d-1f3fe-2642": { "output": "1f93d-1f3fe-200d-2642-fe0f", "name": "man playing water polo: medium-dark skin tone", "alpha_code": ":man_playing_water_polo_tone4:", "aliases": ":man_playing_water_polo_medium_dark_skin_tone:" }, "1f93d-1f3fd-2642": { "output": "1f93d-1f3fd-200d-2642-fe0f", "name": "man playing water polo: medium skin tone", "alpha_code": ":man_playing_water_polo_tone3:", "aliases": ":man_playing_water_polo_medium_skin_tone:" }, "1f93d-1f3fc-2642": { "output": "1f93d-1f3fc-200d-2642-fe0f", "name": "man playing water polo: medium-light skin tone", "alpha_code": ":man_playing_water_polo_tone2:", "aliases": ":man_playing_water_polo_medium_light_skin_tone:" }, "1f93d-1f3fb-2642": { "output": "1f93d-1f3fb-200d-2642-fe0f", "name": "man playing water polo: light skin tone", "alpha_code": ":man_playing_water_polo_tone1:", "aliases": ":man_playing_water_polo_light_skin_tone:" }, "1f93d-2642": { "output": "1f93d-200d-2642-fe0f", "name": "man playing water polo", "alpha_code": ":man_playing_water_polo:", "aliases": "" }, "1f93d-1f3ff-2640": { "output": "1f93d-1f3ff-200d-2640-fe0f", "name": "woman playing water polo: dark skin tone", "alpha_code": ":woman_playing_water_polo_tone5:", "aliases": ":woman_playing_water_polo_dark_skin_tone:" }, "1f93d-1f3fe-2640": { "output": "1f93d-1f3fe-200d-2640-fe0f", "name": "woman playing water polo: medium-dark skin tone", "alpha_code": ":woman_playing_water_polo_tone4:", "aliases": ":woman_playing_water_polo_medium_dark_skin_tone:" }, "1f93d-1f3fd-2640": { "output": "1f93d-1f3fd-200d-2640-fe0f", "name": "woman playing water polo: medium skin tone", "alpha_code": ":woman_playing_water_polo_tone3:", "aliases": ":woman_playing_water_polo_medium_skin_tone:" }, "1f93d-1f3fc-2640": { "output": "1f93d-1f3fc-200d-2640-fe0f", "name": "woman playing water polo: medium-light skin tone", "alpha_code": ":woman_playing_water_polo_tone2:", "aliases": ":woman_playing_water_polo_medium_light_skin_tone:" }, "1f93d-1f3fb-2640": { "output": "1f93d-1f3fb-200d-2640-fe0f", "name": "woman playing water polo: light skin tone", "alpha_code": ":woman_playing_water_polo_tone1:", "aliases": ":woman_playing_water_polo_light_skin_tone:" }, "1f93d-2640": { "output": "1f93d-200d-2640-fe0f", "name": "woman playing water polo", "alpha_code": ":woman_playing_water_polo:", "aliases": "" }, "1f938-1f3ff-2642": { "output": "1f938-1f3ff-200d-2642-fe0f", "name": "man cartwheeling: dark skin tone", "alpha_code": ":man_cartwheeling_tone5:", "aliases": ":man_cartwheeling_dark_skin_tone:" }, "1f938-1f3fe-2642": { "output": "1f938-1f3fe-200d-2642-fe0f", "name": "man cartwheeling: medium-dark skin tone", "alpha_code": ":man_cartwheeling_tone4:", "aliases": ":man_cartwheeling_medium_dark_skin_tone:" }, "1f938-1f3fd-2642": { "output": "1f938-1f3fd-200d-2642-fe0f", "name": "man cartwheeling: medium skin tone", "alpha_code": ":man_cartwheeling_tone3:", "aliases": ":man_cartwheeling_medium_skin_tone:" }, "1f938-1f3fc-2642": { "output": "1f938-1f3fc-200d-2642-fe0f", "name": "man cartwheeling: medium-light skin tone", "alpha_code": ":man_cartwheeling_tone2:", "aliases": ":man_cartwheeling_medium_light_skin_tone:" }, "1f938-1f3fb-2642": { "output": "1f938-1f3fb-200d-2642-fe0f", "name": "man cartwheeling: light skin tone", "alpha_code": ":man_cartwheeling_tone1:", "aliases": ":man_cartwheeling_light_skin_tone:" }, "1f938-2642": { "output": "1f938-200d-2642-fe0f", "name": "man cartwheeling", "alpha_code": ":man_cartwheeling:", "aliases": "" }, "1f938-1f3ff-2640": { "output": "1f938-1f3ff-200d-2640-fe0f", "name": "woman cartwheeling: dark skin tone", "alpha_code": ":woman_cartwheeling_tone5:", "aliases": ":woman_cartwheeling_dark_skin_tone:" }, "1f938-1f3fe-2640": { "output": "1f938-1f3fe-200d-2640-fe0f", "name": "woman cartwheeling: medium-dark skin tone", "alpha_code": ":woman_cartwheeling_tone4:", "aliases": ":woman_cartwheeling_medium_dark_skin_tone:" }, "1f938-1f3fd-2640": { "output": "1f938-1f3fd-200d-2640-fe0f", "name": "woman cartwheeling: medium skin tone", "alpha_code": ":woman_cartwheeling_tone3:", "aliases": ":woman_cartwheeling_medium_skin_tone:" }, "1f938-1f3fc-2640": { "output": "1f938-1f3fc-200d-2640-fe0f", "name": "woman cartwheeling: medium-light skin tone", "alpha_code": ":woman_cartwheeling_tone2:", "aliases": ":woman_cartwheeling_medium_light_skin_tone:" }, "1f938-1f3fb-2640": { "output": "1f938-1f3fb-200d-2640-fe0f", "name": "woman cartwheeling: light skin tone", "alpha_code": ":woman_cartwheeling_tone1:", "aliases": ":woman_cartwheeling_light_skin_tone:" }, "1f938-2640": { "output": "1f938-200d-2640-fe0f", "name": "woman cartwheeling", "alpha_code": ":woman_cartwheeling:", "aliases": "" }, "1f6b6-1f3ff-2642": { "output": "1f6b6-1f3ff-200d-2642-fe0f", "name": "man walking: dark skin tone", "alpha_code": ":man_walking_tone5:", "aliases": ":man_walking_dark_skin_tone:" }, "1f6b6-1f3fe-2642": { "output": "1f6b6-1f3fe-200d-2642-fe0f", "name": "man walking: medium-dark skin tone", "alpha_code": ":man_walking_tone4:", "aliases": ":man_walking_medium_dark_skin_tone:" }, "1f6b6-1f3fd-2642": { "output": "1f6b6-1f3fd-200d-2642-fe0f", "name": "man walking: medium skin tone", "alpha_code": ":man_walking_tone3:", "aliases": ":man_walking_medium_skin_tone:" }, "1f6b6-1f3fc-2642": { "output": "1f6b6-1f3fc-200d-2642-fe0f", "name": "man walking: medium-light skin tone", "alpha_code": ":man_walking_tone2:", "aliases": ":man_walking_medium_light_skin_tone:" }, "1f6b6-1f3fb-2642": { "output": "1f6b6-1f3fb-200d-2642-fe0f", "name": "man walking: light skin tone", "alpha_code": ":man_walking_tone1:", "aliases": ":man_walking_light_skin_tone:" }, "1f6b6-2642": { "output": "1f6b6-200d-2642-fe0f", "name": "man walking", "alpha_code": ":man_walking:", "aliases": "" }, "1f6b6-1f3ff-2640": { "output": "1f6b6-1f3ff-200d-2640-fe0f", "name": "woman walking: dark skin tone", "alpha_code": ":woman_walking_tone5:", "aliases": ":woman_walking_dark_skin_tone:" }, "1f6b6-1f3fe-2640": { "output": "1f6b6-1f3fe-200d-2640-fe0f", "name": "woman walking: medium-dark skin tone", "alpha_code": ":woman_walking_tone4:", "aliases": ":woman_walking_medium_dark_skin_tone:" }, "1f6b6-1f3fd-2640": { "output": "1f6b6-1f3fd-200d-2640-fe0f", "name": "woman walking: medium skin tone", "alpha_code": ":woman_walking_tone3:", "aliases": ":woman_walking_medium_skin_tone:" }, "1f6b6-1f3fc-2640": { "output": "1f6b6-1f3fc-200d-2640-fe0f", "name": "woman walking: medium-light skin tone", "alpha_code": ":woman_walking_tone2:", "aliases": ":woman_walking_medium_light_skin_tone:" }, "1f6b6-1f3fb-2640": { "output": "1f6b6-1f3fb-200d-2640-fe0f", "name": "woman walking: light skin tone", "alpha_code": ":woman_walking_tone1:", "aliases": ":woman_walking_light_skin_tone:" }, "1f6b6-2640": { "output": "1f6b6-200d-2640-fe0f", "name": "woman walking", "alpha_code": ":woman_walking:", "aliases": "" }, "1f6b5-1f3ff-2642": { "output": "1f6b5-1f3ff-200d-2642-fe0f", "name": "man mountain biking: dark skin tone", "alpha_code": ":man_mountain_biking_tone5:", "aliases": ":man_mountain_biking_dark_skin_tone:" }, "1f6b5-1f3fe-2642": { "output": "1f6b5-1f3fe-200d-2642-fe0f", "name": "man mountain biking: medium-dark skin tone", "alpha_code": ":man_mountain_biking_tone4:", "aliases": ":man_mountain_biking_medium_dark_skin_tone:" }, "1f6b5-1f3fd-2642": { "output": "1f6b5-1f3fd-200d-2642-fe0f", "name": "man mountain biking: medium skin tone", "alpha_code": ":man_mountain_biking_tone3:", "aliases": ":man_mountain_biking_medium_skin_tone:" }, "1f6b5-1f3fc-2642": { "output": "1f6b5-1f3fc-200d-2642-fe0f", "name": "man mountain biking: medium-light skin tone", "alpha_code": ":man_mountain_biking_tone2:", "aliases": ":man_mountain_biking_medium_light_skin_tone:" }, "1f6b5-1f3fb-2642": { "output": "1f6b5-1f3fb-200d-2642-fe0f", "name": "man mountain biking: light skin tone", "alpha_code": ":man_mountain_biking_tone1:", "aliases": ":man_mountain_biking_light_skin_tone:" }, "1f6b5-2642": { "output": "1f6b5-200d-2642-fe0f", "name": "man mountain biking", "alpha_code": ":man_mountain_biking:", "aliases": "" }, "1f6b5-1f3ff-2640": { "output": "1f6b5-1f3ff-200d-2640-fe0f", "name": "woman mountain biking: dark skin tone", "alpha_code": ":woman_mountain_biking_tone5:", "aliases": ":woman_mountain_biking_dark_skin_tone:" }, "1f6b5-1f3fe-2640": { "output": "1f6b5-1f3fe-200d-2640-fe0f", "name": "woman mountain biking: medium-dark skin tone", "alpha_code": ":woman_mountain_biking_tone4:", "aliases": ":woman_mountain_biking_medium_dark_skin_tone:" }, "1f6b5-1f3fd-2640": { "output": "1f6b5-1f3fd-200d-2640-fe0f", "name": "woman mountain biking: medium skin tone", "alpha_code": ":woman_mountain_biking_tone3:", "aliases": ":woman_mountain_biking_medium_skin_tone:" }, "1f6b5-1f3fc-2640": { "output": "1f6b5-1f3fc-200d-2640-fe0f", "name": "woman mountain biking: medium-light skin tone", "alpha_code": ":woman_mountain_biking_tone2:", "aliases": ":woman_mountain_biking_medium_light_skin_tone:" }, "1f6b5-1f3fb-2640": { "output": "1f6b5-1f3fb-200d-2640-fe0f", "name": "woman mountain biking: light skin tone", "alpha_code": ":woman_mountain_biking_tone1:", "aliases": ":woman_mountain_biking_light_skin_tone:" }, "1f6b5-2640": { "output": "1f6b5-200d-2640-fe0f", "name": "woman mountain biking", "alpha_code": ":woman_mountain_biking:", "aliases": "" }, "1f6b4-1f3ff-2642": { "output": "1f6b4-1f3ff-200d-2642-fe0f", "name": "man biking: dark skin tone", "alpha_code": ":man_biking_tone5:", "aliases": ":man_biking_dark_skin_tone:" }, "1f6b4-1f3fe-2642": { "output": "1f6b4-1f3fe-200d-2642-fe0f", "name": "man biking: medium-dark skin tone", "alpha_code": ":man_biking_tone4:", "aliases": ":man_biking_medium_dark_skin_tone:" }, "1f6b4-1f3fd-2642": { "output": "1f6b4-1f3fd-200d-2642-fe0f", "name": "man biking: medium skin tone", "alpha_code": ":man_biking_tone3:", "aliases": ":man_biking_medium_skin_tone:" }, "1f6b4-1f3fc-2642": { "output": "1f6b4-1f3fc-200d-2642-fe0f", "name": "man biking: medium-light skin tone", "alpha_code": ":man_biking_tone2:", "aliases": ":man_biking_medium_light_skin_tone:" }, "1f6b4-1f3fb-2642": { "output": "1f6b4-1f3fb-200d-2642-fe0f", "name": "man biking: light skin tone", "alpha_code": ":man_biking_tone1:", "aliases": ":man_biking_light_skin_tone:" }, "1f6b4-2642": { "output": "1f6b4-200d-2642-fe0f", "name": "man biking", "alpha_code": ":man_biking:", "aliases": "" }, "1f6b4-1f3ff-2640": { "output": "1f6b4-1f3ff-200d-2640-fe0f", "name": "woman biking: dark skin tone", "alpha_code": ":woman_biking_tone5:", "aliases": ":woman_biking_dark_skin_tone:" }, "1f6b4-1f3fe-2640": { "output": "1f6b4-1f3fe-200d-2640-fe0f", "name": "woman biking: medium-dark skin tone", "alpha_code": ":woman_biking_tone4:", "aliases": ":woman_biking_medium_dark_skin_tone:" }, "1f6b4-1f3fd-2640": { "output": "1f6b4-1f3fd-200d-2640-fe0f", "name": "woman biking: medium skin tone", "alpha_code": ":woman_biking_tone3:", "aliases": ":woman_biking_medium_skin_tone:" }, "1f6b4-1f3fc-2640": { "output": "1f6b4-1f3fc-200d-2640-fe0f", "name": "woman biking: medium-light skin tone", "alpha_code": ":woman_biking_tone2:", "aliases": ":woman_biking_medium_light_skin_tone:" }, "1f6b4-1f3fb-2640": { "output": "1f6b4-1f3fb-200d-2640-fe0f", "name": "woman biking: light skin tone", "alpha_code": ":woman_biking_tone1:", "aliases": ":woman_biking_light_skin_tone:" }, "1f6b4-2640": { "output": "1f6b4-200d-2640-fe0f", "name": "woman biking", "alpha_code": ":woman_biking:", "aliases": "" }, "1f6a3-1f3ff-2642": { "output": "1f6a3-1f3ff-200d-2642-fe0f", "name": "man rowing boat: dark skin tone", "alpha_code": ":man_rowing_boat_tone5:", "aliases": ":man_rowing_boat_dark_skin_tone:" }, "1f6a3-1f3fe-2642": { "output": "1f6a3-1f3fe-200d-2642-fe0f", "name": "man rowing boat: medium-dark skin tone", "alpha_code": ":man_rowing_boat_tone4:", "aliases": ":man_rowing_boat_medium_dark_skin_tone:" }, "1f6a3-1f3fd-2642": { "output": "1f6a3-1f3fd-200d-2642-fe0f", "name": "man rowing boat: medium skin tone", "alpha_code": ":man_rowing_boat_tone3:", "aliases": ":man_rowing_boat_medium_skin_tone:" }, "1f6a3-1f3fc-2642": { "output": "1f6a3-1f3fc-200d-2642-fe0f", "name": "man rowing boat: medium-light skin tone", "alpha_code": ":man_rowing_boat_tone2:", "aliases": ":man_rowing_boat_medium_light_skin_tone:" }, "1f6a3-1f3fb-2642": { "output": "1f6a3-1f3fb-200d-2642-fe0f", "name": "man rowing boat: light skin tone", "alpha_code": ":man_rowing_boat_tone1:", "aliases": ":man_rowing_boat_light_skin_tone:" }, "1f6a3-2642": { "output": "1f6a3-200d-2642-fe0f", "name": "man rowing boat", "alpha_code": ":man_rowing_boat:", "aliases": "" }, "1f6a3-1f3ff-2640": { "output": "1f6a3-1f3ff-200d-2640-fe0f", "name": "woman rowing boat: dark skin tone", "alpha_code": ":woman_rowing_boat_tone5:", "aliases": ":woman_rowing_boat_dark_skin_tone:" }, "1f6a3-1f3fe-2640": { "output": "1f6a3-1f3fe-200d-2640-fe0f", "name": "woman rowing boat: medium-dark skin tone", "alpha_code": ":woman_rowing_boat_tone4:", "aliases": ":woman_rowing_boat_medium_dark_skin_tone:" }, "1f6a3-1f3fd-2640": { "output": "1f6a3-1f3fd-200d-2640-fe0f", "name": "woman rowing boat: medium skin tone", "alpha_code": ":woman_rowing_boat_tone3:", "aliases": ":woman_rowing_boat_medium_skin_tone:" }, "1f6a3-1f3fc-2640": { "output": "1f6a3-1f3fc-200d-2640-fe0f", "name": "woman rowing boat: medium-light skin tone", "alpha_code": ":woman_rowing_boat_tone2:", "aliases": ":woman_rowing_boat_medium_light_skin_tone:" }, "1f6a3-1f3fb-2640": { "output": "1f6a3-1f3fb-200d-2640-fe0f", "name": "woman rowing boat: light skin tone", "alpha_code": ":woman_rowing_boat_tone1:", "aliases": ":woman_rowing_boat_light_skin_tone:" }, "1f6a3-2640": { "output": "1f6a3-200d-2640-fe0f", "name": "woman rowing boat", "alpha_code": ":woman_rowing_boat:", "aliases": "" }, "1f3cb-1f3ff-2642": { "output": "1f3cb-1f3ff-200d-2642-fe0f", "name": "man lifting weights: dark skin tone", "alpha_code": ":man_lifting_weights_tone5:", "aliases": ":man_lifting_weights_dark_skin_tone:" }, "1f3cb-1f3fe-2642": { "output": "1f3cb-1f3fe-200d-2642-fe0f", "name": "man lifting weights: medium-dark skin tone", "alpha_code": ":man_lifting_weights_tone4:", "aliases": ":man_lifting_weights_medium_dark_skin_tone:" }, "1f3cb-1f3fd-2642": { "output": "1f3cb-1f3fd-200d-2642-fe0f", "name": "man lifting weights: medium skin tone", "alpha_code": ":man_lifting_weights_tone3:", "aliases": ":man_lifting_weights_medium_skin_tone:" }, "1f3cb-1f3fc-2642": { "output": "1f3cb-1f3fc-200d-2642-fe0f", "name": "man lifting weights: medium-light skin tone", "alpha_code": ":man_lifting_weights_tone2:", "aliases": ":man_lifting_weights_medium_light_skin_tone:" }, "1f3cb-1f3fb-2642": { "output": "1f3cb-1f3fb-200d-2642-fe0f", "name": "man lifting weights: light skin tone", "alpha_code": ":man_lifting_weights_tone1:", "aliases": ":man_lifting_weights_light_skin_tone:" }, "1f3cb-2642": { "output": "1f3cb-fe0f-200d-2642-fe0f", "name": "man lifting weights", "alpha_code": ":man_lifting_weights:", "aliases": "" }, "1f3cb-1f3ff-2640": { "output": "1f3cb-1f3ff-200d-2640-fe0f", "name": "woman lifting weights: dark skin tone", "alpha_code": ":woman_lifting_weights_tone5:", "aliases": ":woman_lifting_weights_dark_skin_tone:" }, "1f3cb-1f3fe-2640": { "output": "1f3cb-1f3fe-200d-2640-fe0f", "name": "woman lifting weights: medium-dark skin tone", "alpha_code": ":woman_lifting_weights_tone4:", "aliases": ":woman_lifting_weights_medium_dark_skin_tone:" }, "1f3cb-1f3fd-2640": { "output": "1f3cb-1f3fd-200d-2640-fe0f", "name": "woman lifting weights: medium skin tone", "alpha_code": ":woman_lifting_weights_tone3:", "aliases": ":woman_lifting_weights_medium_skin_tone:" }, "1f3cb-1f3fc-2640": { "output": "1f3cb-1f3fc-200d-2640-fe0f", "name": "woman lifting weights: medium-light skin tone", "alpha_code": ":woman_lifting_weights_tone2:", "aliases": ":woman_lifting_weights_medium_light_skin_tone:" }, "1f3cb-1f3fb-2640": { "output": "1f3cb-1f3fb-200d-2640-fe0f", "name": "woman lifting weights: light skin tone", "alpha_code": ":woman_lifting_weights_tone1:", "aliases": ":woman_lifting_weights_light_skin_tone:" }, "1f3cb-2640": { "output": "1f3cb-fe0f-200d-2640-fe0f", "name": "woman lifting weights", "alpha_code": ":woman_lifting_weights:", "aliases": "" }, "1f3ca-1f3ff-2642": { "output": "1f3ca-1f3ff-200d-2642-fe0f", "name": "man swimming: dark skin tone", "alpha_code": ":man_swimming_tone5:", "aliases": ":man_swimming_dark_skin_tone:" }, "1f3ca-1f3fe-2642": { "output": "1f3ca-1f3fe-200d-2642-fe0f", "name": "man swimming: medium-dark skin tone", "alpha_code": ":man_swimming_tone4:", "aliases": ":man_swimming_medium_dark_skin_tone:" }, "1f3ca-1f3fd-2642": { "output": "1f3ca-1f3fd-200d-2642-fe0f", "name": "man swimming: medium skin tone", "alpha_code": ":man_swimming_tone3:", "aliases": ":man_swimming_medium_skin_tone:" }, "1f3ca-1f3fc-2642": { "output": "1f3ca-1f3fc-200d-2642-fe0f", "name": "man swimming: medium-light skin tone", "alpha_code": ":man_swimming_tone2:", "aliases": ":man_swimming_medium_light_skin_tone:" }, "1f3ca-1f3fb-2642": { "output": "1f3ca-1f3fb-200d-2642-fe0f", "name": "man swimming: light skin tone", "alpha_code": ":man_swimming_tone1:", "aliases": ":man_swimming_light_skin_tone:" }, "1f3ca-2642": { "output": "1f3ca-200d-2642-fe0f", "name": "man swimming", "alpha_code": ":man_swimming:", "aliases": "" }, "1f3ca-1f3ff-2640": { "output": "1f3ca-1f3ff-200d-2640-fe0f", "name": "woman swimming: dark skin tone", "alpha_code": ":woman_swimming_tone5:", "aliases": ":woman_swimming_dark_skin_tone:" }, "1f3ca-1f3fe-2640": { "output": "1f3ca-1f3fe-200d-2640-fe0f", "name": "woman swimming: medium-dark skin tone", "alpha_code": ":woman_swimming_tone4:", "aliases": ":woman_swimming_medium_dark_skin_tone:" }, "1f3ca-1f3fd-2640": { "output": "1f3ca-1f3fd-200d-2640-fe0f", "name": "woman swimming: medium skin tone", "alpha_code": ":woman_swimming_tone3:", "aliases": ":woman_swimming_medium_skin_tone:" }, "1f3ca-1f3fc-2640": { "output": "1f3ca-1f3fc-200d-2640-fe0f", "name": "woman swimming: medium-light skin tone", "alpha_code": ":woman_swimming_tone2:", "aliases": ":woman_swimming_medium_light_skin_tone:" }, "1f3ca-1f3fb-2640": { "output": "1f3ca-1f3fb-200d-2640-fe0f", "name": "woman swimming: light skin tone", "alpha_code": ":woman_swimming_tone1:", "aliases": ":woman_swimming_light_skin_tone:" }, "1f3ca-2640": { "output": "1f3ca-200d-2640-fe0f", "name": "woman swimming", "alpha_code": ":woman_swimming:", "aliases": "" }, "1f3c4-1f3ff-2642": { "output": "1f3c4-1f3ff-200d-2642-fe0f", "name": "man surfing: dark skin tone", "alpha_code": ":man_surfing_tone5:", "aliases": ":man_surfing_dark_skin_tone:" }, "1f3c4-1f3fe-2642": { "output": "1f3c4-1f3fe-200d-2642-fe0f", "name": "man surfing: medium-dark skin tone", "alpha_code": ":man_surfing_tone4:", "aliases": ":man_surfing_medium_dark_skin_tone:" }, "1f3c4-1f3fd-2642": { "output": "1f3c4-1f3fd-200d-2642-fe0f", "name": "man surfing: medium skin tone", "alpha_code": ":man_surfing_tone3:", "aliases": ":man_surfing_medium_skin_tone:" }, "1f3c4-1f3fc-2642": { "output": "1f3c4-1f3fc-200d-2642-fe0f", "name": "man surfing: medium-light skin tone", "alpha_code": ":man_surfing_tone2:", "aliases": ":man_surfing_medium_light_skin_tone:" }, "1f3c4-1f3fb-2642": { "output": "1f3c4-1f3fb-200d-2642-fe0f", "name": "man surfing: light skin tone", "alpha_code": ":man_surfing_tone1:", "aliases": ":man_surfing_light_skin_tone:" }, "1f3c4-2642": { "output": "1f3c4-200d-2642-fe0f", "name": "man surfing", "alpha_code": ":man_surfing:", "aliases": "" }, "1f3c4-1f3ff-2640": { "output": "1f3c4-1f3ff-200d-2640-fe0f", "name": "woman surfing: dark skin tone", "alpha_code": ":woman_surfing_tone5:", "aliases": ":woman_surfing_dark_skin_tone:" }, "1f3c4-1f3fe-2640": { "output": "1f3c4-1f3fe-200d-2640-fe0f", "name": "woman surfing: medium-dark skin tone", "alpha_code": ":woman_surfing_tone4:", "aliases": ":woman_surfing_medium_dark_skin_tone:" }, "1f3c4-1f3fd-2640": { "output": "1f3c4-1f3fd-200d-2640-fe0f", "name": "woman surfing: medium skin tone", "alpha_code": ":woman_surfing_tone3:", "aliases": ":woman_surfing_medium_skin_tone:" }, "1f3c4-1f3fc-2640": { "output": "1f3c4-1f3fc-200d-2640-fe0f", "name": "woman surfing: medium-light skin tone", "alpha_code": ":woman_surfing_tone2:", "aliases": ":woman_surfing_medium_light_skin_tone:" }, "1f3c4-1f3fb-2640": { "output": "1f3c4-1f3fb-200d-2640-fe0f", "name": "woman surfing: light skin tone", "alpha_code": ":woman_surfing_tone1:", "aliases": ":woman_surfing_light_skin_tone:" }, "1f3c4-2640": { "output": "1f3c4-200d-2640-fe0f", "name": "woman surfing", "alpha_code": ":woman_surfing:", "aliases": "" }, "1f3c3-1f3ff-2642": { "output": "1f3c3-1f3ff-200d-2642-fe0f", "name": "man running: dark skin tone", "alpha_code": ":man_running_tone5:", "aliases": ":man_running_dark_skin_tone:" }, "1f3c3-1f3fe-2642": { "output": "1f3c3-1f3fe-200d-2642-fe0f", "name": "man running: medium-dark skin tone", "alpha_code": ":man_running_tone4:", "aliases": ":man_running_medium_dark_skin_tone:" }, "1f3c3-1f3fd-2642": { "output": "1f3c3-1f3fd-200d-2642-fe0f", "name": "man running: medium skin tone", "alpha_code": ":man_running_tone3:", "aliases": ":man_running_medium_skin_tone:" }, "1f3c3-1f3fc-2642": { "output": "1f3c3-1f3fc-200d-2642-fe0f", "name": "man running: medium-light skin tone", "alpha_code": ":man_running_tone2:", "aliases": ":man_running_medium_light_skin_tone:" }, "1f3c3-1f3fb-2642": { "output": "1f3c3-1f3fb-200d-2642-fe0f", "name": "man running: light skin tone", "alpha_code": ":man_running_tone1:", "aliases": ":man_running_light_skin_tone:" }, "1f3c3-2642": { "output": "1f3c3-200d-2642-fe0f", "name": "man running", "alpha_code": ":man_running:", "aliases": "" }, "1f3c3-1f3ff-2640": { "output": "1f3c3-1f3ff-200d-2640-fe0f", "name": "woman running: dark skin tone", "alpha_code": ":woman_running_tone5:", "aliases": ":woman_running_dark_skin_tone:" }, "1f3c3-1f3fe-2640": { "output": "1f3c3-1f3fe-200d-2640-fe0f", "name": "woman running: medium-dark skin tone", "alpha_code": ":woman_running_tone4:", "aliases": ":woman_running_medium_dark_skin_tone:" }, "1f3c3-1f3fd-2640": { "output": "1f3c3-1f3fd-200d-2640-fe0f", "name": "woman running: medium skin tone", "alpha_code": ":woman_running_tone3:", "aliases": ":woman_running_medium_skin_tone:" }, "1f3c3-1f3fc-2640": { "output": "1f3c3-1f3fc-200d-2640-fe0f", "name": "woman running: medium-light skin tone", "alpha_code": ":woman_running_tone2:", "aliases": ":woman_running_medium_light_skin_tone:" }, "1f3c3-1f3fb-2640": { "output": "1f3c3-1f3fb-200d-2640-fe0f", "name": "woman running: light skin tone", "alpha_code": ":woman_running_tone1:", "aliases": ":woman_running_light_skin_tone:" }, "1f3c3-2640": { "output": "1f3c3-200d-2640-fe0f", "name": "woman running", "alpha_code": ":woman_running:", "aliases": "" }, "26f9-1f3ff-2642": { "output": "26f9-1f3ff-200d-2642-fe0f", "name": "man bouncing ball: dark skin tone", "alpha_code": ":man_bouncing_ball_tone5:", "aliases": ":man_bouncing_ball_dark_skin_tone:" }, "26f9-1f3fe-2642": { "output": "26f9-1f3fe-200d-2642-fe0f", "name": "man bouncing ball: medium-dark skin tone", "alpha_code": ":man_bouncing_ball_tone4:", "aliases": ":man_bouncing_ball_medium_dark_skin_tone:" }, "26f9-1f3fd-2642": { "output": "26f9-1f3fd-200d-2642-fe0f", "name": "man bouncing ball: medium skin tone", "alpha_code": ":man_bouncing_ball_tone3:", "aliases": ":man_bouncing_ball_medium_skin_tone:" }, "26f9-1f3fc-2642": { "output": "26f9-1f3fc-200d-2642-fe0f", "name": "man bouncing ball: medium-light skin tone", "alpha_code": ":man_bouncing_ball_tone2:", "aliases": ":man_bouncing_ball_medium_light_skin_tone:" }, "26f9-1f3fb-2642": { "output": "26f9-1f3fb-200d-2642-fe0f", "name": "man bouncing ball: light skin tone", "alpha_code": ":man_bouncing_ball_tone1:", "aliases": ":man_bouncing_ball_light_skin_tone:" }, "26f9-2642": { "output": "26f9-fe0f-200d-2642-fe0f", "name": "man bouncing ball", "alpha_code": ":man_bouncing_ball:", "aliases": "" }, "26f9-1f3ff-2640": { "output": "26f9-1f3ff-200d-2640-fe0f", "name": "woman bouncing ball: dark skin tone", "alpha_code": ":woman_bouncing_ball_tone5:", "aliases": ":woman_bouncing_ball_dark_skin_tone:" }, "26f9-1f3fe-2640": { "output": "26f9-1f3fe-200d-2640-fe0f", "name": "woman bouncing ball: medium-dark skin tone", "alpha_code": ":woman_bouncing_ball_tone4:", "aliases": ":woman_bouncing_ball_medium_dark_skin_tone:" }, "26f9-1f3fd-2640": { "output": "26f9-1f3fd-200d-2640-fe0f", "name": "woman bouncing ball: medium skin tone", "alpha_code": ":woman_bouncing_ball_tone3:", "aliases": ":woman_bouncing_ball_medium_skin_tone:" }, "26f9-1f3fc-2640": { "output": "26f9-1f3fc-200d-2640-fe0f", "name": "woman bouncing ball: medium-light skin tone", "alpha_code": ":woman_bouncing_ball_tone2:", "aliases": ":woman_bouncing_ball_medium_light_skin_tone:" }, "26f9-1f3fb-2640": { "output": "26f9-1f3fb-200d-2640-fe0f", "name": "woman bouncing ball: light skin tone", "alpha_code": ":woman_bouncing_ball_tone1:", "aliases": ":woman_bouncing_ball_light_skin_tone:" }, "26f9-2640": { "output": "26f9-fe0f-200d-2640-fe0f", "name": "woman bouncing ball", "alpha_code": ":woman_bouncing_ball:", "aliases": "" }, "1f937-1f3ff-2642": { "output": "1f937-1f3ff-200d-2642-fe0f", "name": "man shrugging: dark skin tone", "alpha_code": ":man_shrugging_tone5:", "aliases": ":man_shrugging_dark_skin_tone:" }, "1f937-1f3fe-2642": { "output": "1f937-1f3fe-200d-2642-fe0f", "name": "man shrugging: medium-dark skin tone", "alpha_code": ":man_shrugging_tone4:", "aliases": ":man_shrugging_medium_dark_skin_tone:" }, "1f937-1f3fd-2642": { "output": "1f937-1f3fd-200d-2642-fe0f", "name": "man shrugging: medium skin tone", "alpha_code": ":man_shrugging_tone3:", "aliases": ":man_shrugging_medium_skin_tone:" }, "1f937-1f3fc-2642": { "output": "1f937-1f3fc-200d-2642-fe0f", "name": "man shrugging: medium-light skin tone", "alpha_code": ":man_shrugging_tone2:", "aliases": ":man_shrugging_medium_light_skin_tone:" }, "1f937-1f3fb-2642": { "output": "1f937-1f3fb-200d-2642-fe0f", "name": "man shrugging: light skin tone", "alpha_code": ":man_shrugging_tone1:", "aliases": ":man_shrugging_light_skin_tone:" }, "1f937-2642": { "output": "1f937-200d-2642-fe0f", "name": "man shrugging", "alpha_code": ":man_shrugging:", "aliases": "" }, "1f937-1f3ff-2640": { "output": "1f937-1f3ff-200d-2640-fe0f", "name": "woman shrugging: dark skin tone", "alpha_code": ":woman_shrugging_tone5:", "aliases": ":woman_shrugging_dark_skin_tone:" }, "1f937-1f3fe-2640": { "output": "1f937-1f3fe-200d-2640-fe0f", "name": "woman shrugging: medium-dark skin tone", "alpha_code": ":woman_shrugging_tone4:", "aliases": ":woman_shrugging_medium_dark_skin_tone:" }, "1f937-1f3fd-2640": { "output": "1f937-1f3fd-200d-2640-fe0f", "name": "woman shrugging: medium skin tone", "alpha_code": ":woman_shrugging_tone3:", "aliases": ":woman_shrugging_medium_skin_tone:" }, "1f937-1f3fc-2640": { "output": "1f937-1f3fc-200d-2640-fe0f", "name": "woman shrugging: medium-light skin tone", "alpha_code": ":woman_shrugging_tone2:", "aliases": ":woman_shrugging_medium_light_skin_tone:" }, "1f937-1f3fb-2640": { "output": "1f937-1f3fb-200d-2640-fe0f", "name": "woman shrugging: light skin tone", "alpha_code": ":woman_shrugging_tone1:", "aliases": ":woman_shrugging_light_skin_tone:" }, "1f937-2640": { "output": "1f937-200d-2640-fe0f", "name": "woman shrugging", "alpha_code": ":woman_shrugging:", "aliases": "" }, "1f926-1f3ff-2642": { "output": "1f926-1f3ff-200d-2642-fe0f", "name": "man facepalming: dark skin tone", "alpha_code": ":man_facepalming_tone5:", "aliases": ":man_facepalming_dark_skin_tone:" }, "1f926-1f3fe-2642": { "output": "1f926-1f3fe-200d-2642-fe0f", "name": "man facepalming: medium-dark skin tone", "alpha_code": ":man_facepalming_tone4:", "aliases": ":man_facepalming_medium_dark_skin_tone:" }, "1f926-1f3fd-2642": { "output": "1f926-1f3fd-200d-2642-fe0f", "name": "man facepalming: medium skin tone", "alpha_code": ":man_facepalming_tone3:", "aliases": ":man_facepalming_medium_skin_tone:" }, "1f926-1f3fc-2642": { "output": "1f926-1f3fc-200d-2642-fe0f", "name": "man facepalming: medium-light skin tone", "alpha_code": ":man_facepalming_tone2:", "aliases": ":man_facepalming_medium_light_skin_tone:" }, "1f926-1f3fb-2642": { "output": "1f926-1f3fb-200d-2642-fe0f", "name": "man facepalming: light skin tone", "alpha_code": ":man_facepalming_tone1:", "aliases": ":man_facepalming_light_skin_tone:" }, "1f926-2642": { "output": "1f926-200d-2642-fe0f", "name": "man facepalming", "alpha_code": ":man_facepalming:", "aliases": "" }, "1f926-1f3ff-2640": { "output": "1f926-1f3ff-200d-2640-fe0f", "name": "woman facepalming: dark skin tone", "alpha_code": ":woman_facepalming_tone5:", "aliases": ":woman_facepalming_dark_skin_tone:" }, "1f926-1f3fe-2640": { "output": "1f926-1f3fe-200d-2640-fe0f", "name": "woman facepalming: medium-dark skin tone", "alpha_code": ":woman_facepalming_tone4:", "aliases": ":woman_facepalming_medium_dark_skin_tone:" }, "1f926-1f3fd-2640": { "output": "1f926-1f3fd-200d-2640-fe0f", "name": "woman facepalming: medium skin tone", "alpha_code": ":woman_facepalming_tone3:", "aliases": ":woman_facepalming_medium_skin_tone:" }, "1f926-1f3fc-2640": { "output": "1f926-1f3fc-200d-2640-fe0f", "name": "woman facepalming: medium-light skin tone", "alpha_code": ":woman_facepalming_tone2:", "aliases": ":woman_facepalming_medium_light_skin_tone:" }, "1f926-1f3fb-2640": { "output": "1f926-1f3fb-200d-2640-fe0f", "name": "woman facepalming: light skin tone", "alpha_code": ":woman_facepalming_tone1:", "aliases": ":woman_facepalming_light_skin_tone:" }, "1f926-2640": { "output": "1f926-200d-2640-fe0f", "name": "woman facepalming", "alpha_code": ":woman_facepalming:", "aliases": "" }, "1f64e-1f3ff-2642": { "output": "1f64e-1f3ff-200d-2642-fe0f", "name": "man pouting: dark skin tone", "alpha_code": ":man_pouting_tone5:", "aliases": ":man_pouting_dark_skin_tone:" }, "1f64e-1f3fe-2642": { "output": "1f64e-1f3fe-200d-2642-fe0f", "name": "man pouting: medium-dark skin tone", "alpha_code": ":man_pouting_tone4:", "aliases": ":man_pouting_medium_dark_skin_tone:" }, "1f64e-1f3fd-2642": { "output": "1f64e-1f3fd-200d-2642-fe0f", "name": "man pouting: medium skin tone", "alpha_code": ":man_pouting_tone3:", "aliases": ":man_pouting_medium_skin_tone:" }, "1f64e-1f3fc-2642": { "output": "1f64e-1f3fc-200d-2642-fe0f", "name": "man pouting: medium-light skin tone", "alpha_code": ":man_pouting_tone2:", "aliases": ":man_pouting_medium_light_skin_tone:" }, "1f64e-1f3fb-2642": { "output": "1f64e-1f3fb-200d-2642-fe0f", "name": "man pouting: light skin tone", "alpha_code": ":man_pouting_tone1:", "aliases": ":man_pouting_light_skin_tone:" }, "1f64e-2642": { "output": "1f64e-200d-2642-fe0f", "name": "man pouting", "alpha_code": ":man_pouting:", "aliases": "" }, "1f64e-1f3ff-2640": { "output": "1f64e-1f3ff-200d-2640-fe0f", "name": "woman pouting: dark skin tone", "alpha_code": ":woman_pouting_tone5:", "aliases": ":woman_pouting_dark_skin_tone:" }, "1f64e-1f3fe-2640": { "output": "1f64e-1f3fe-200d-2640-fe0f", "name": "woman pouting: medium-dark skin tone", "alpha_code": ":woman_pouting_tone4:", "aliases": ":woman_pouting_medium_dark_skin_tone:" }, "1f64e-1f3fd-2640": { "output": "1f64e-1f3fd-200d-2640-fe0f", "name": "woman pouting: medium skin tone", "alpha_code": ":woman_pouting_tone3:", "aliases": ":woman_pouting_medium_skin_tone:" }, "1f64e-1f3fc-2640": { "output": "1f64e-1f3fc-200d-2640-fe0f", "name": "woman pouting: medium-light skin tone", "alpha_code": ":woman_pouting_tone2:", "aliases": ":woman_pouting_medium_light_skin_tone:" }, "1f64e-1f3fb-2640": { "output": "1f64e-1f3fb-200d-2640-fe0f", "name": "woman pouting: light skin tone", "alpha_code": ":woman_pouting_tone1:", "aliases": ":woman_pouting_light_skin_tone:" }, "1f64e-2640": { "output": "1f64e-200d-2640-fe0f", "name": "woman pouting", "alpha_code": ":woman_pouting:", "aliases": "" }, "1f64d-1f3ff-2642": { "output": "1f64d-1f3ff-200d-2642-fe0f", "name": "man frowning: dark skin tone", "alpha_code": ":man_frowning_tone5:", "aliases": ":man_frowning_dark_skin_tone:" }, "1f64d-1f3fe-2642": { "output": "1f64d-1f3fe-200d-2642-fe0f", "name": "man frowning: medium-dark skin tone", "alpha_code": ":man_frowning_tone4:", "aliases": ":man_frowning_medium_dark_skin_tone:" }, "1f64d-1f3fd-2642": { "output": "1f64d-1f3fd-200d-2642-fe0f", "name": "man frowning: medium skin tone", "alpha_code": ":man_frowning_tone3:", "aliases": ":man_frowning_medium_skin_tone:" }, "1f64d-1f3fc-2642": { "output": "1f64d-1f3fc-200d-2642-fe0f", "name": "man frowning: medium-light skin tone", "alpha_code": ":man_frowning_tone2:", "aliases": ":man_frowning_medium_light_skin_tone:" }, "1f64d-1f3fb-2642": { "output": "1f64d-1f3fb-200d-2642-fe0f", "name": "man frowning: light skin tone", "alpha_code": ":man_frowning_tone1:", "aliases": ":man_frowning_light_skin_tone:" }, "1f64d-2642": { "output": "1f64d-200d-2642-fe0f", "name": "man frowning", "alpha_code": ":man_frowning:", "aliases": "" }, "1f64d-1f3ff-2640": { "output": "1f64d-1f3ff-200d-2640-fe0f", "name": "woman frowning: dark skin tone", "alpha_code": ":woman_frowning_tone5:", "aliases": ":woman_frowning_dark_skin_tone:" }, "1f64d-1f3fe-2640": { "output": "1f64d-1f3fe-200d-2640-fe0f", "name": "woman frowning: medium-dark skin tone", "alpha_code": ":woman_frowning_tone4:", "aliases": ":woman_frowning_medium_dark_skin_tone:" }, "1f64d-1f3fd-2640": { "output": "1f64d-1f3fd-200d-2640-fe0f", "name": "woman frowning: medium skin tone", "alpha_code": ":woman_frowning_tone3:", "aliases": ":woman_frowning_medium_skin_tone:" }, "1f64d-1f3fc-2640": { "output": "1f64d-1f3fc-200d-2640-fe0f", "name": "woman frowning: medium-light skin tone", "alpha_code": ":woman_frowning_tone2:", "aliases": ":woman_frowning_medium_light_skin_tone:" }, "1f64d-1f3fb-2640": { "output": "1f64d-1f3fb-200d-2640-fe0f", "name": "woman frowning: light skin tone", "alpha_code": ":woman_frowning_tone1:", "aliases": ":woman_frowning_light_skin_tone:" }, "1f64d-2640": { "output": "1f64d-200d-2640-fe0f", "name": "woman frowning", "alpha_code": ":woman_frowning:", "aliases": "" }, "1f64b-1f3ff-2642": { "output": "1f64b-1f3ff-200d-2642-fe0f", "name": "man raising hand: dark skin tone", "alpha_code": ":man_raising_hand_tone5:", "aliases": ":man_raising_hand_dark_skin_tone:" }, "1f64b-1f3fe-2642": { "output": "1f64b-1f3fe-200d-2642-fe0f", "name": "man raising hand: medium-dark skin tone", "alpha_code": ":man_raising_hand_tone4:", "aliases": ":man_raising_hand_medium_dark_skin_tone:" }, "1f64b-1f3fd-2642": { "output": "1f64b-1f3fd-200d-2642-fe0f", "name": "man raising hand: medium skin tone", "alpha_code": ":man_raising_hand_tone3:", "aliases": ":man_raising_hand_medium_skin_tone:" }, "1f64b-1f3fc-2642": { "output": "1f64b-1f3fc-200d-2642-fe0f", "name": "man raising hand: medium-light skin tone", "alpha_code": ":man_raising_hand_tone2:", "aliases": ":man_raising_hand_medium_light_skin_tone:" }, "1f64b-1f3fb-2642": { "output": "1f64b-1f3fb-200d-2642-fe0f", "name": "man raising hand: light skin tone", "alpha_code": ":man_raising_hand_tone1:", "aliases": ":man_raising_hand_light_skin_tone:" }, "1f64b-2642": { "output": "1f64b-200d-2642-fe0f", "name": "man raising hand", "alpha_code": ":man_raising_hand:", "aliases": "" }, "1f64b-1f3ff-2640": { "output": "1f64b-1f3ff-200d-2640-fe0f", "name": "woman raising hand: dark skin tone", "alpha_code": ":woman_raising_hand_tone5:", "aliases": ":woman_raising_hand_dark_skin_tone:" }, "1f64b-1f3fe-2640": { "output": "1f64b-1f3fe-200d-2640-fe0f", "name": "woman raising hand: medium-dark skin tone", "alpha_code": ":woman_raising_hand_tone4:", "aliases": ":woman_raising_hand_medium_dark_skin_tone:" }, "1f64b-1f3fd-2640": { "output": "1f64b-1f3fd-200d-2640-fe0f", "name": "woman raising hand: medium skin tone", "alpha_code": ":woman_raising_hand_tone3:", "aliases": ":woman_raising_hand_medium_skin_tone:" }, "1f64b-1f3fc-2640": { "output": "1f64b-1f3fc-200d-2640-fe0f", "name": "woman raising hand: medium-light skin tone", "alpha_code": ":woman_raising_hand_tone2:", "aliases": ":woman_raising_hand_medium_light_skin_tone:" }, "1f64b-1f3fb-2640": { "output": "1f64b-1f3fb-200d-2640-fe0f", "name": "woman raising hand: light skin tone", "alpha_code": ":woman_raising_hand_tone1:", "aliases": ":woman_raising_hand_light_skin_tone:" }, "1f64b-2640": { "output": "1f64b-200d-2640-fe0f", "name": "woman raising hand", "alpha_code": ":woman_raising_hand:", "aliases": "" }, "1f647-1f3ff-2642": { "output": "1f647-1f3ff-200d-2642-fe0f", "name": "man bowing: dark skin tone", "alpha_code": ":man_bowing_tone5:", "aliases": ":man_bowing_dark_skin_tone:" }, "1f647-1f3fe-2642": { "output": "1f647-1f3fe-200d-2642-fe0f", "name": "man bowing: medium-dark skin tone", "alpha_code": ":man_bowing_tone4:", "aliases": ":man_bowing_medium_dark_skin_tone:" }, "1f647-1f3fd-2642": { "output": "1f647-1f3fd-200d-2642-fe0f", "name": "man bowing: medium skin tone", "alpha_code": ":man_bowing_tone3:", "aliases": ":man_bowing_medium_skin_tone:" }, "1f647-1f3fc-2642": { "output": "1f647-1f3fc-200d-2642-fe0f", "name": "man bowing: medium-light skin tone", "alpha_code": ":man_bowing_tone2:", "aliases": ":man_bowing_medium_light_skin_tone:" }, "1f647-1f3fb-2642": { "output": "1f647-1f3fb-200d-2642-fe0f", "name": "man bowing: light skin tone", "alpha_code": ":man_bowing_tone1:", "aliases": ":man_bowing_light_skin_tone:" }, "1f647-2642": { "output": "1f647-200d-2642-fe0f", "name": "man bowing", "alpha_code": ":man_bowing:", "aliases": "" }, "1f647-1f3ff-2640": { "output": "1f647-1f3ff-200d-2640-fe0f", "name": "woman bowing: dark skin tone", "alpha_code": ":woman_bowing_tone5:", "aliases": ":woman_bowing_dark_skin_tone:" }, "1f647-1f3fe-2640": { "output": "1f647-1f3fe-200d-2640-fe0f", "name": "woman bowing: medium-dark skin tone", "alpha_code": ":woman_bowing_tone4:", "aliases": ":woman_bowing_medium_dark_skin_tone:" }, "1f647-1f3fd-2640": { "output": "1f647-1f3fd-200d-2640-fe0f", "name": "woman bowing: medium skin tone", "alpha_code": ":woman_bowing_tone3:", "aliases": ":woman_bowing_medium_skin_tone:" }, "1f647-1f3fc-2640": { "output": "1f647-1f3fc-200d-2640-fe0f", "name": "woman bowing: medium-light skin tone", "alpha_code": ":woman_bowing_tone2:", "aliases": ":woman_bowing_medium_light_skin_tone:" }, "1f647-1f3fb-2640": { "output": "1f647-1f3fb-200d-2640-fe0f", "name": "woman bowing: light skin tone", "alpha_code": ":woman_bowing_tone1:", "aliases": ":woman_bowing_light_skin_tone:" }, "1f647-2640": { "output": "1f647-200d-2640-fe0f", "name": "woman bowing", "alpha_code": ":woman_bowing:", "aliases": "" }, "1f646-1f3ff-2642": { "output": "1f646-1f3ff-200d-2642-fe0f", "name": "man gesturing OK: dark skin tone", "alpha_code": ":man_gesturing_ok_tone5:", "aliases": ":man_gesturing_ok_dark_skin_tone:" }, "1f646-1f3fe-2642": { "output": "1f646-1f3fe-200d-2642-fe0f", "name": "man gesturing OK: medium-dark skin tone", "alpha_code": ":man_gesturing_ok_tone4:", "aliases": ":man_gesturing_ok_medium_dark_skin_tone:" }, "1f646-1f3fd-2642": { "output": "1f646-1f3fd-200d-2642-fe0f", "name": "man gesturing OK: medium skin tone", "alpha_code": ":man_gesturing_ok_tone3:", "aliases": ":man_gesturing_ok_medium_skin_tone:" }, "1f646-1f3fc-2642": { "output": "1f646-1f3fc-200d-2642-fe0f", "name": "man gesturing OK: medium-light skin tone", "alpha_code": ":man_gesturing_ok_tone2:", "aliases": ":man_gesturing_ok_medium_light_skin_tone:" }, "1f646-1f3fb-2642": { "output": "1f646-1f3fb-200d-2642-fe0f", "name": "man gesturing OK: light skin tone", "alpha_code": ":man_gesturing_ok_tone1:", "aliases": ":man_gesturing_ok_light_skin_tone:" }, "1f646-2642": { "output": "1f646-200d-2642-fe0f", "name": "man gesturing OK", "alpha_code": ":man_gesturing_ok:", "aliases": "" }, "1f646-1f3ff-2640": { "output": "1f646-1f3ff-200d-2640-fe0f", "name": "woman gesturing OK: dark skin tone", "alpha_code": ":woman_gesturing_ok_tone5:", "aliases": ":woman_gesturing_ok_dark_skin_tone:" }, "1f646-1f3fe-2640": { "output": "1f646-1f3fe-200d-2640-fe0f", "name": "woman gesturing OK: medium-dark skin tone", "alpha_code": ":woman_gesturing_ok_tone4:", "aliases": ":woman_gesturing_ok_medium_dark_skin_tone:" }, "1f646-1f3fd-2640": { "output": "1f646-1f3fd-200d-2640-fe0f", "name": "woman gesturing OK: medium skin tone", "alpha_code": ":woman_gesturing_ok_tone3:", "aliases": ":woman_gesturing_ok_medium_skin_tone:" }, "1f646-1f3fc-2640": { "output": "1f646-1f3fc-200d-2640-fe0f", "name": "woman gesturing OK: medium-light skin tone", "alpha_code": ":woman_gesturing_ok_tone2:", "aliases": ":woman_gesturing_ok_medium_light_skin_tone:" }, "1f646-1f3fb-2640": { "output": "1f646-1f3fb-200d-2640-fe0f", "name": "woman gesturing OK: light skin tone", "alpha_code": ":woman_gesturing_ok_tone1:", "aliases": ":woman_gesturing_ok_light_skin_tone:" }, "1f646-2640": { "output": "1f646-200d-2640-fe0f", "name": "woman gesturing OK", "alpha_code": ":woman_gesturing_ok:", "aliases": "" }, "1f645-1f3ff-2642": { "output": "1f645-1f3ff-200d-2642-fe0f", "name": "man gesturing NO: dark skin tone", "alpha_code": ":man_gesturing_no_tone5:", "aliases": ":man_gesturing_no_dark_skin_tone:" }, "1f645-1f3fe-2642": { "output": "1f645-1f3fe-200d-2642-fe0f", "name": "man gesturing NO: medium-dark skin tone", "alpha_code": ":man_gesturing_no_tone4:", "aliases": ":man_gesturing_no_medium_dark_skin_tone:" }, "1f645-1f3fd-2642": { "output": "1f645-1f3fd-200d-2642-fe0f", "name": "man gesturing NO: medium skin tone", "alpha_code": ":man_gesturing_no_tone3:", "aliases": ":man_gesturing_no_medium_skin_tone:" }, "1f645-1f3fc-2642": { "output": "1f645-1f3fc-200d-2642-fe0f", "name": "man gesturing NO: medium-light skin tone", "alpha_code": ":man_gesturing_no_tone2:", "aliases": ":man_gesturing_no_medium_light_skin_tone:" }, "1f645-1f3fb-2642": { "output": "1f645-1f3fb-200d-2642-fe0f", "name": "man gesturing NO: light skin tone", "alpha_code": ":man_gesturing_no_tone1:", "aliases": ":man_gesturing_no_light_skin_tone:" }, "1f645-2642": { "output": "1f645-200d-2642-fe0f", "name": "man gesturing NO", "alpha_code": ":man_gesturing_no:", "aliases": "" }, "1f645-1f3ff-2640": { "output": "1f645-1f3ff-200d-2640-fe0f", "name": "woman gesturing NO: dark skin tone", "alpha_code": ":woman_gesturing_no_tone5:", "aliases": ":woman_gesturing_no_dark_skin_tone:" }, "1f645-1f3fe-2640": { "output": "1f645-1f3fe-200d-2640-fe0f", "name": "woman gesturing NO: medium-dark skin tone", "alpha_code": ":woman_gesturing_no_tone4:", "aliases": ":woman_gesturing_no_medium_dark_skin_tone:" }, "1f645-1f3fd-2640": { "output": "1f645-1f3fd-200d-2640-fe0f", "name": "woman gesturing NO: medium skin tone", "alpha_code": ":woman_gesturing_no_tone3:", "aliases": ":woman_gesturing_no_medium_skin_tone:" }, "1f645-1f3fc-2640": { "output": "1f645-1f3fc-200d-2640-fe0f", "name": "woman gesturing NO: medium-light skin tone", "alpha_code": ":woman_gesturing_no_tone2:", "aliases": ":woman_gesturing_no_medium_light_skin_tone:" }, "1f645-1f3fb-2640": { "output": "1f645-1f3fb-200d-2640-fe0f", "name": "woman gesturing NO: light skin tone", "alpha_code": ":woman_gesturing_no_tone1:", "aliases": ":woman_gesturing_no_light_skin_tone:" }, "1f645-2640": { "output": "1f645-200d-2640-fe0f", "name": "woman gesturing NO", "alpha_code": ":woman_gesturing_no:", "aliases": "" }, "1f487-1f3ff-2642": { "output": "1f487-1f3ff-200d-2642-fe0f", "name": "man getting haircut: dark skin tone", "alpha_code": ":man_getting_haircut_tone5:", "aliases": ":man_getting_haircut_dark_skin_tone:" }, "1f487-1f3fe-2642": { "output": "1f487-1f3fe-200d-2642-fe0f", "name": "man getting haircut: medium-dark skin tone", "alpha_code": ":man_getting_haircut_tone4:", "aliases": ":man_getting_haircut_medium_dark_skin_tone:" }, "1f487-1f3fd-2642": { "output": "1f487-1f3fd-200d-2642-fe0f", "name": "man getting haircut: medium skin tone", "alpha_code": ":man_getting_haircut_tone3:", "aliases": ":man_getting_haircut_medium_skin_tone:" }, "1f487-1f3fc-2642": { "output": "1f487-1f3fc-200d-2642-fe0f", "name": "man getting haircut: medium-light skin tone", "alpha_code": ":man_getting_haircut_tone2:", "aliases": ":man_getting_haircut_medium_light_skin_tone:" }, "1f487-1f3fb-2642": { "output": "1f487-1f3fb-200d-2642-fe0f", "name": "man getting haircut: light skin tone", "alpha_code": ":man_getting_haircut_tone1:", "aliases": ":man_getting_haircut_light_skin_tone:" }, "1f487-2642": { "output": "1f487-200d-2642-fe0f", "name": "man getting haircut", "alpha_code": ":man_getting_haircut:", "aliases": "" }, "1f487-1f3ff-2640": { "output": "1f487-1f3ff-200d-2640-fe0f", "name": "woman getting haircut: dark skin tone", "alpha_code": ":woman_getting_haircut_tone5:", "aliases": ":woman_getting_haircut_dark_skin_tone:" }, "1f487-1f3fe-2640": { "output": "1f487-1f3fe-200d-2640-fe0f", "name": "woman getting haircut: medium-dark skin tone", "alpha_code": ":woman_getting_haircut_tone4:", "aliases": ":woman_getting_haircut_medium_dark_skin_tone:" }, "1f487-1f3fd-2640": { "output": "1f487-1f3fd-200d-2640-fe0f", "name": "woman getting haircut: medium skin tone", "alpha_code": ":woman_getting_haircut_tone3:", "aliases": ":woman_getting_haircut_medium_skin_tone:" }, "1f487-1f3fc-2640": { "output": "1f487-1f3fc-200d-2640-fe0f", "name": "woman getting haircut: medium-light skin tone", "alpha_code": ":woman_getting_haircut_tone2:", "aliases": ":woman_getting_haircut_medium_light_skin_tone:" }, "1f487-1f3fb-2640": { "output": "1f487-1f3fb-200d-2640-fe0f", "name": "woman getting haircut: light skin tone", "alpha_code": ":woman_getting_haircut_tone1:", "aliases": ":woman_getting_haircut_light_skin_tone:" }, "1f487-2640": { "output": "1f487-200d-2640-fe0f", "name": "woman getting haircut", "alpha_code": ":woman_getting_haircut:", "aliases": "" }, "1f486-1f3ff-2642": { "output": "1f486-1f3ff-200d-2642-fe0f", "name": "man getting massage: dark skin tone", "alpha_code": ":man_getting_face_massage_tone5:", "aliases": ":man_getting_face_massage_dark_skin_tone:" }, "1f486-1f3fe-2642": { "output": "1f486-1f3fe-200d-2642-fe0f", "name": "man getting massage: medium-dark skin tone", "alpha_code": ":man_getting_face_massage_tone4:", "aliases": ":man_getting_face_massage_medium_dark_skin_tone:" }, "1f486-1f3fd-2642": { "output": "1f486-1f3fd-200d-2642-fe0f", "name": "man getting massage: medium skin tone", "alpha_code": ":man_getting_face_massage_tone3:", "aliases": ":man_getting_face_massage_medium_skin_tone:" }, "1f486-1f3fc-2642": { "output": "1f486-1f3fc-200d-2642-fe0f", "name": "man getting massage: medium-light skin tone", "alpha_code": ":man_getting_face_massage_tone2:", "aliases": ":man_getting_face_massage_medium_light_skin_tone:" }, "1f486-1f3fb-2642": { "output": "1f486-1f3fb-200d-2642-fe0f", "name": "man getting massage: light skin tone", "alpha_code": ":man_getting_face_massage_tone1:", "aliases": ":man_getting_face_massage_light_skin_tone:" }, "1f486-2642": { "output": "1f486-200d-2642-fe0f", "name": "man getting massage", "alpha_code": ":man_getting_face_massage:", "aliases": "" }, "1f486-1f3ff-2640": { "output": "1f486-1f3ff-200d-2640-fe0f", "name": "woman getting massage: dark skin tone", "alpha_code": ":woman_getting_face_massage_tone5:", "aliases": ":woman_getting_face_massage_dark_skin_tone:" }, "1f486-1f3fe-2640": { "output": "1f486-1f3fe-200d-2640-fe0f", "name": "woman getting massage: medium-dark skin tone", "alpha_code": ":woman_getting_face_massage_tone4:", "aliases": ":woman_getting_face_massage_medium_dark_skin_tone:" }, "1f486-1f3fd-2640": { "output": "1f486-1f3fd-200d-2640-fe0f", "name": "woman getting massage: medium skin tone", "alpha_code": ":woman_getting_face_massage_tone3:", "aliases": ":woman_getting_face_massage_medium_skin_tone:" }, "1f486-1f3fc-2640": { "output": "1f486-1f3fc-200d-2640-fe0f", "name": "woman getting massage: medium-light skin tone", "alpha_code": ":woman_getting_face_massage_tone2:", "aliases": ":woman_getting_face_massage_medium_light_skin_tone:" }, "1f486-1f3fb-2640": { "output": "1f486-1f3fb-200d-2640-fe0f", "name": "woman getting massage: light skin tone", "alpha_code": ":woman_getting_face_massage_tone1:", "aliases": ":woman_getting_face_massage_light_skin_tone:" }, "1f486-2640": { "output": "1f486-200d-2640-fe0f", "name": "woman getting massage", "alpha_code": ":woman_getting_face_massage:", "aliases": "" }, "1f481-1f3ff-2642": { "output": "1f481-1f3ff-200d-2642-fe0f", "name": "man tipping hand: dark skin tone", "alpha_code": ":man_tipping_hand_tone5:", "aliases": ":man_tipping_hand_dark_skin_tone:" }, "1f481-1f3fe-2642": { "output": "1f481-1f3fe-200d-2642-fe0f", "name": "man tipping hand: medium-dark skin tone", "alpha_code": ":man_tipping_hand_tone4:", "aliases": ":man_tipping_hand_medium_dark_skin_tone:" }, "1f481-1f3fd-2642": { "output": "1f481-1f3fd-200d-2642-fe0f", "name": "man tipping hand: medium skin tone", "alpha_code": ":man_tipping_hand_tone3:", "aliases": ":man_tipping_hand_medium_skin_tone:" }, "1f481-1f3fc-2642": { "output": "1f481-1f3fc-200d-2642-fe0f", "name": "man tipping hand: medium-light skin tone", "alpha_code": ":man_tipping_hand_tone2:", "aliases": ":man_tipping_hand_medium_light_skin_tone:" }, "1f481-1f3fb-2642": { "output": "1f481-1f3fb-200d-2642-fe0f", "name": "man tipping hand: light skin tone", "alpha_code": ":man_tipping_hand_tone1:", "aliases": ":man_tipping_hand_light_skin_tone:" }, "1f481-2642": { "output": "1f481-200d-2642-fe0f", "name": "man tipping hand", "alpha_code": ":man_tipping_hand:", "aliases": "" }, "1f481-1f3ff-2640": { "output": "1f481-1f3ff-200d-2640-fe0f", "name": "woman tipping hand: dark skin tone", "alpha_code": ":woman_tipping_hand_tone5:", "aliases": ":woman_tipping_hand_dark_skin_tone:" }, "1f481-1f3fe-2640": { "output": "1f481-1f3fe-200d-2640-fe0f", "name": "woman tipping hand: medium-dark skin tone", "alpha_code": ":woman_tipping_hand_tone4:", "aliases": ":woman_tipping_hand_medium_dark_skin_tone:" }, "1f481-1f3fd-2640": { "output": "1f481-1f3fd-200d-2640-fe0f", "name": "woman tipping hand: medium skin tone", "alpha_code": ":woman_tipping_hand_tone3:", "aliases": ":woman_tipping_hand_medium_skin_tone:" }, "1f481-1f3fc-2640": { "output": "1f481-1f3fc-200d-2640-fe0f", "name": "woman tipping hand: medium-light skin tone", "alpha_code": ":woman_tipping_hand_tone2:", "aliases": ":woman_tipping_hand_medium_light_skin_tone:" }, "1f481-1f3fb-2640": { "output": "1f481-1f3fb-200d-2640-fe0f", "name": "woman tipping hand: light skin tone", "alpha_code": ":woman_tipping_hand_tone1:", "aliases": ":woman_tipping_hand_light_skin_tone:" }, "1f481-2640": { "output": "1f481-200d-2640-fe0f", "name": "woman tipping hand", "alpha_code": ":woman_tipping_hand:", "aliases": "" }, "1f471-1f3ff-2642": { "output": "1f471-1f3ff-200d-2642-fe0f", "name": "blond-haired man: dark skin tone", "alpha_code": ":blond-haired_man_tone5:", "aliases": ":blond-haired_man_dark_skin_tone:" }, "1f471-1f3fe-2642": { "output": "1f471-1f3fe-200d-2642-fe0f", "name": "blond-haired man: medium-dark skin tone", "alpha_code": ":blond-haired_man_tone4:", "aliases": ":blond-haired_man_medium_dark_skin_tone:" }, "1f471-1f3fd-2642": { "output": "1f471-1f3fd-200d-2642-fe0f", "name": "blond-haired man: medium skin tone", "alpha_code": ":blond-haired_man_tone3:", "aliases": ":blond-haired_man_medium_skin_tone:" }, "1f471-1f3fc-2642": { "output": "1f471-1f3fc-200d-2642-fe0f", "name": "blond-haired man: medium-light skin tone", "alpha_code": ":blond-haired_man_tone2:", "aliases": ":blond-haired_man_medium_light_skin_tone:" }, "1f471-1f3fb-2642": { "output": "1f471-1f3fb-200d-2642-fe0f", "name": "blond-haired man: light skin tone", "alpha_code": ":blond-haired_man_tone1:", "aliases": ":blond-haired_man_light_skin_tone:" }, "1f471-2642": { "output": "1f471-200d-2642-fe0f", "name": "blond-haired man", "alpha_code": ":blond-haired_man:", "aliases": "" }, "1f471-1f3ff-2640": { "output": "1f471-1f3ff-200d-2640-fe0f", "name": "blond-haired woman: dark skin tone", "alpha_code": ":blond-haired_woman_tone5:", "aliases": ":blond-haired_woman_dark_skin_tone:" }, "1f471-1f3fe-2640": { "output": "1f471-1f3fe-200d-2640-fe0f", "name": "blond-haired woman: medium-dark skin tone", "alpha_code": ":blond-haired_woman_tone4:", "aliases": ":blond-haired_woman_medium_dark_skin_tone:" }, "1f471-1f3fd-2640": { "output": "1f471-1f3fd-200d-2640-fe0f", "name": "blond-haired woman: medium skin tone", "alpha_code": ":blond-haired_woman_tone3:", "aliases": ":blond-haired_woman_medium_skin_tone:" }, "1f471-1f3fc-2640": { "output": "1f471-1f3fc-200d-2640-fe0f", "name": "blond-haired woman: medium-light skin tone", "alpha_code": ":blond-haired_woman_tone2:", "aliases": ":blond-haired_woman_medium_light_skin_tone:" }, "1f471-1f3fb-2640": { "output": "1f471-1f3fb-200d-2640-fe0f", "name": "blond-haired woman: light skin tone", "alpha_code": ":blond-haired_woman_tone1:", "aliases": ":blond-haired_woman_light_skin_tone:" }, "1f471-2640": { "output": "1f471-200d-2640-fe0f", "name": "blond-haired woman", "alpha_code": ":blond-haired_woman:", "aliases": "" }, "1f473-1f3ff-2642": { "output": "1f473-1f3ff-200d-2642-fe0f", "name": "man wearing turban: dark skin tone", "alpha_code": ":man_wearing_turban_tone5:", "aliases": ":man_wearing_turban_dark_skin_tone:" }, "1f473-1f3fe-2642": { "output": "1f473-1f3fe-200d-2642-fe0f", "name": "man wearing turban: medium-dark skin tone", "alpha_code": ":man_wearing_turban_tone4:", "aliases": ":man_wearing_turban_medium_dark_skin_tone:" }, "1f473-1f3fd-2642": { "output": "1f473-1f3fd-200d-2642-fe0f", "name": "man wearing turban: medium skin tone", "alpha_code": ":man_wearing_turban_tone3:", "aliases": ":man_wearing_turban_medium_skin_tone:" }, "1f473-1f3fc-2642": { "output": "1f473-1f3fc-200d-2642-fe0f", "name": "man wearing turban: medium-light skin tone", "alpha_code": ":man_wearing_turban_tone2:", "aliases": ":man_wearing_turban_medium_light_skin_tone:" }, "1f473-1f3fb-2642": { "output": "1f473-1f3fb-200d-2642-fe0f", "name": "man wearing turban: light skin tone", "alpha_code": ":man_wearing_turban_tone1:", "aliases": ":man_wearing_turban_light_skin_tone:" }, "1f473-2642": { "output": "1f473-200d-2642-fe0f", "name": "man wearing turban", "alpha_code": ":man_wearing_turban:", "aliases": "" }, "1f473-1f3ff-2640": { "output": "1f473-1f3ff-200d-2640-fe0f", "name": "woman wearing turban: dark skin tone", "alpha_code": ":woman_wearing_turban_tone5:", "aliases": ":woman_wearing_turban_dark_skin_tone:" }, "1f473-1f3fe-2640": { "output": "1f473-1f3fe-200d-2640-fe0f", "name": "woman wearing turban: medium-dark skin tone", "alpha_code": ":woman_wearing_turban_tone4:", "aliases": ":woman_wearing_turban_medium_dark_skin_tone:" }, "1f473-1f3fd-2640": { "output": "1f473-1f3fd-200d-2640-fe0f", "name": "woman wearing turban: medium skin tone", "alpha_code": ":woman_wearing_turban_tone3:", "aliases": ":woman_wearing_turban_medium_skin_tone:" }, "1f473-1f3fc-2640": { "output": "1f473-1f3fc-200d-2640-fe0f", "name": "woman wearing turban: medium-light skin tone", "alpha_code": ":woman_wearing_turban_tone2:", "aliases": ":woman_wearing_turban_medium_light_skin_tone:" }, "1f473-1f3fb-2640": { "output": "1f473-1f3fb-200d-2640-fe0f", "name": "woman wearing turban: light skin tone", "alpha_code": ":woman_wearing_turban_tone1:", "aliases": ":woman_wearing_turban_light_skin_tone:" }, "1f473-2640": { "output": "1f473-200d-2640-fe0f", "name": "woman wearing turban", "alpha_code": ":woman_wearing_turban:", "aliases": "" }, "1f482-1f3ff-2642": { "output": "1f482-1f3ff-200d-2642-fe0f", "name": "man guard: dark skin tone", "alpha_code": ":man_guard_tone5:", "aliases": ":man_guard_dark_skin_tone:" }, "1f482-1f3fe-2642": { "output": "1f482-1f3fe-200d-2642-fe0f", "name": "man guard: medium-dark skin tone", "alpha_code": ":man_guard_tone4:", "aliases": ":man_guard_medium_dark_skin_tone:" }, "1f482-1f3fd-2642": { "output": "1f482-1f3fd-200d-2642-fe0f", "name": "man guard: medium skin tone", "alpha_code": ":man_guard_tone3:", "aliases": ":man_guard_medium_skin_tone:" }, "1f482-1f3fc-2642": { "output": "1f482-1f3fc-200d-2642-fe0f", "name": "man guard: medium-light skin tone", "alpha_code": ":man_guard_tone2:", "aliases": ":man_guard_medium_light_skin_tone:" }, "1f482-1f3fb-2642": { "output": "1f482-1f3fb-200d-2642-fe0f", "name": "man guard: light skin tone", "alpha_code": ":man_guard_tone1:", "aliases": ":man_guard_light_skin_tone:" }, "1f482-2642": { "output": "1f482-200d-2642-fe0f", "name": "man guard", "alpha_code": ":man_guard:", "aliases": "" }, "1f482-1f3ff-2640": { "output": "1f482-1f3ff-200d-2640-fe0f", "name": "woman guard: dark skin tone", "alpha_code": ":woman_guard_tone5:", "aliases": ":woman_guard_dark_skin_tone:" }, "1f482-1f3fe-2640": { "output": "1f482-1f3fe-200d-2640-fe0f", "name": "woman guard: medium-dark skin tone", "alpha_code": ":woman_guard_tone4:", "aliases": ":woman_guard_medium_dark_skin_tone:" }, "1f482-1f3fd-2640": { "output": "1f482-1f3fd-200d-2640-fe0f", "name": "woman guard: medium skin tone", "alpha_code": ":woman_guard_tone3:", "aliases": ":woman_guard_medium_skin_tone:" }, "1f482-1f3fc-2640": { "output": "1f482-1f3fc-200d-2640-fe0f", "name": "woman guard: medium-light skin tone", "alpha_code": ":woman_guard_tone2:", "aliases": ":woman_guard_medium_light_skin_tone:" }, "1f482-1f3fb-2640": { "output": "1f482-1f3fb-200d-2640-fe0f", "name": "woman guard: light skin tone", "alpha_code": ":woman_guard_tone1:", "aliases": ":woman_guard_light_skin_tone:" }, "1f482-2640": { "output": "1f482-200d-2640-fe0f", "name": "woman guard", "alpha_code": ":woman_guard:", "aliases": "" }, "1f575-1f3ff-2642": { "output": "1f575-1f3ff-200d-2642-fe0f", "name": "man detective: dark skin tone", "alpha_code": ":man_detective_tone5:", "aliases": ":man_detective_dark_skin_tone:" }, "1f575-1f3fe-2642": { "output": "1f575-1f3fe-200d-2642-fe0f", "name": "man detective: medium-dark skin tone", "alpha_code": ":man_detective_tone4:", "aliases": ":man_detective_medium_dark_skin_tone:" }, "1f575-1f3fd-2642": { "output": "1f575-1f3fd-200d-2642-fe0f", "name": "man detective: medium skin tone", "alpha_code": ":man_detective_tone3:", "aliases": ":man_detective_medium_skin_tone:" }, "1f575-1f3fc-2642": { "output": "1f575-1f3fc-200d-2642-fe0f", "name": "man detective: medium-light skin tone", "alpha_code": ":man_detective_tone2:", "aliases": ":man_detective_medium_light_skin_tone:" }, "1f575-1f3fb-2642": { "output": "1f575-1f3fb-200d-2642-fe0f", "name": "man detective: light skin tone", "alpha_code": ":man_detective_tone1:", "aliases": ":man_detective_light_skin_tone:" }, "1f575-2642": { "output": "1f575-fe0f-200d-2642-fe0f", "name": "man detective", "alpha_code": ":man_detective:", "aliases": "" }, "1f575-1f3ff-2640": { "output": "1f575-1f3ff-200d-2640-fe0f", "name": "woman detective: dark skin tone", "alpha_code": ":woman_detective_tone5:", "aliases": ":woman_detective_dark_skin_tone:" }, "1f575-1f3fe-2640": { "output": "1f575-1f3fe-200d-2640-fe0f", "name": "woman detective: medium-dark skin tone", "alpha_code": ":woman_detective_tone4:", "aliases": ":woman_detective_medium_dark_skin_tone:" }, "1f575-1f3fd-2640": { "output": "1f575-1f3fd-200d-2640-fe0f", "name": "woman detective: medium skin tone", "alpha_code": ":woman_detective_tone3:", "aliases": ":woman_detective_medium_skin_tone:" }, "1f575-1f3fc-2640": { "output": "1f575-1f3fc-200d-2640-fe0f", "name": "woman detective: medium-light skin tone", "alpha_code": ":woman_detective_tone2:", "aliases": ":woman_detective_medium_light_skin_tone:" }, "1f575-1f3fb-2640": { "output": "1f575-1f3fb-200d-2640-fe0f", "name": "woman detective: light skin tone", "alpha_code": ":woman_detective_tone1:", "aliases": ":woman_detective_light_skin_tone:" }, "1f575-2640": { "output": "1f575-fe0f-200d-2640-fe0f", "name": "woman detective", "alpha_code": ":woman_detective:", "aliases": "" }, "1f477-1f3ff-2642": { "output": "1f477-1f3ff-200d-2642-fe0f", "name": "man construction worker: dark skin tone", "alpha_code": ":man_construction_worker_tone5:", "aliases": ":man_construction_worker_dark_skin_tone:" }, "1f477-1f3fe-2642": { "output": "1f477-1f3fe-200d-2642-fe0f", "name": "man construction worker: medium-dark skin tone", "alpha_code": ":man_construction_worker_tone4:", "aliases": ":man_construction_worker_medium_dark_skin_tone:" }, "1f477-1f3fd-2642": { "output": "1f477-1f3fd-200d-2642-fe0f", "name": "man construction worker: medium skin tone", "alpha_code": ":man_construction_worker_tone3:", "aliases": ":man_construction_worker_medium_skin_tone:" }, "1f477-1f3fc-2642": { "output": "1f477-1f3fc-200d-2642-fe0f", "name": "man construction worker: medium-light skin tone", "alpha_code": ":man_construction_worker_tone2:", "aliases": ":man_construction_worker_medium_light_skin_tone:" }, "1f477-1f3fb-2642": { "output": "1f477-1f3fb-200d-2642-fe0f", "name": "man construction worker: light skin tone", "alpha_code": ":man_construction_worker_tone1:", "aliases": ":man_construction_worker_light_skin_tone:" }, "1f477-2642": { "output": "1f477-200d-2642-fe0f", "name": "man construction worker", "alpha_code": ":man_construction_worker:", "aliases": "" }, "1f477-1f3ff-2640": { "output": "1f477-1f3ff-200d-2640-fe0f", "name": "woman construction worker: dark skin tone", "alpha_code": ":woman_construction_worker_tone5:", "aliases": ":woman_construction_worker_dark_skin_tone:" }, "1f477-1f3fe-2640": { "output": "1f477-1f3fe-200d-2640-fe0f", "name": "woman construction worker: medium-dark skin tone", "alpha_code": ":woman_construction_worker_tone4:", "aliases": ":woman_construction_worker_medium_dark_skin_tone:" }, "1f477-1f3fd-2640": { "output": "1f477-1f3fd-200d-2640-fe0f", "name": "woman construction worker: medium skin tone", "alpha_code": ":woman_construction_worker_tone3:", "aliases": ":woman_construction_worker_medium_skin_tone:" }, "1f477-1f3fc-2640": { "output": "1f477-1f3fc-200d-2640-fe0f", "name": "woman construction worker: medium-light skin tone", "alpha_code": ":woman_construction_worker_tone2:", "aliases": ":woman_construction_worker_medium_light_skin_tone:" }, "1f477-1f3fb-2640": { "output": "1f477-1f3fb-200d-2640-fe0f", "name": "woman construction worker: light skin tone", "alpha_code": ":woman_construction_worker_tone1:", "aliases": ":woman_construction_worker_light_skin_tone:" }, "1f477-2640": { "output": "1f477-200d-2640-fe0f", "name": "woman construction worker", "alpha_code": ":woman_construction_worker:", "aliases": "" }, "1f46e-1f3ff-2642": { "output": "1f46e-1f3ff-200d-2642-fe0f", "name": "man police officer: dark skin tone", "alpha_code": ":man_police_officer_tone5:", "aliases": ":man_police_officer_dark_skin_tone:" }, "1f46e-1f3fe-2642": { "output": "1f46e-1f3fe-200d-2642-fe0f", "name": "man police officer: medium-dark skin tone", "alpha_code": ":man_police_officer_tone4:", "aliases": ":man_police_officer_medium_dark_skin_tone:" }, "1f46e-1f3fd-2642": { "output": "1f46e-1f3fd-200d-2642-fe0f", "name": "man police officer: medium skin tone", "alpha_code": ":man_police_officer_tone3:", "aliases": ":man_police_officer_medium_skin_tone:" }, "1f46e-1f3fc-2642": { "output": "1f46e-1f3fc-200d-2642-fe0f", "name": "man police officer: medium-light skin tone", "alpha_code": ":man_police_officer_tone2:", "aliases": ":man_police_officer_medium_light_skin_tone:" }, "1f46e-1f3fb-2642": { "output": "1f46e-1f3fb-200d-2642-fe0f", "name": "man police officer: light skin tone", "alpha_code": ":man_police_officer_tone1:", "aliases": ":man_police_officer_light_skin_tone:" }, "1f46e-2642": { "output": "1f46e-200d-2642-fe0f", "name": "man police officer", "alpha_code": ":man_police_officer:", "aliases": "" }, "1f46e-1f3ff-2640": { "output": "1f46e-1f3ff-200d-2640-fe0f", "name": "woman police officer: dark skin tone", "alpha_code": ":woman_police_officer_tone5:", "aliases": ":woman_police_officer_dark_skin_tone:" }, "1f46e-1f3fe-2640": { "output": "1f46e-1f3fe-200d-2640-fe0f", "name": "woman police officer: medium-dark skin tone", "alpha_code": ":woman_police_officer_tone4:", "aliases": ":woman_police_officer_medium_dark_skin_tone:" }, "1f46e-1f3fd-2640": { "output": "1f46e-1f3fd-200d-2640-fe0f", "name": "woman police officer: medium skin tone", "alpha_code": ":woman_police_officer_tone3:", "aliases": ":woman_police_officer_medium_skin_tone:" }, "1f46e-1f3fc-2640": { "output": "1f46e-1f3fc-200d-2640-fe0f", "name": "woman police officer: medium-light skin tone", "alpha_code": ":woman_police_officer_tone2:", "aliases": ":woman_police_officer_medium_light_skin_tone:" }, "1f46e-1f3fb-2640": { "output": "1f46e-1f3fb-200d-2640-fe0f", "name": "woman police officer: light skin tone", "alpha_code": ":woman_police_officer_tone1:", "aliases": ":woman_police_officer_light_skin_tone:" }, "1f46e-2640": { "output": "1f46e-200d-2640-fe0f", "name": "woman police officer", "alpha_code": ":woman_police_officer:", "aliases": "" }, "1f468-1f3ff-1f4bb": { "output": "1f468-1f3ff-200d-1f4bb", "name": "man technologist: dark skin tone", "alpha_code": ":man_technologist_tone5:", "aliases": ":man_technologist_dark_skin_tone:" }, "1f468-1f3fe-1f4bb": { "output": "1f468-1f3fe-200d-1f4bb", "name": "man technologist: medium-dark skin tone", "alpha_code": ":man_technologist_tone4:", "aliases": ":man_technologist_medium_dark_skin_tone:" }, "1f468-1f3fd-1f4bb": { "output": "1f468-1f3fd-200d-1f4bb", "name": "man technologist: medium skin tone", "alpha_code": ":man_technologist_tone3:", "aliases": ":man_technologist_medium_skin_tone:" }, "1f468-1f3fc-1f4bb": { "output": "1f468-1f3fc-200d-1f4bb", "name": "man technologist: medium-light skin tone", "alpha_code": ":man_technologist_tone2:", "aliases": ":man_technologist_medium_light_skin_tone:" }, "1f468-1f3fb-1f4bb": { "output": "1f468-1f3fb-200d-1f4bb", "name": "man technologist: light skin tone", "alpha_code": ":man_technologist_tone1:", "aliases": ":man_technologist_light_skin_tone:" }, "1f468-1f4bb": { "output": "1f468-200d-1f4bb", "name": "man technologist", "alpha_code": ":man_technologist:", "aliases": "" }, "1f469-1f3ff-1f4bb": { "output": "1f469-1f3ff-200d-1f4bb", "name": "woman technologist: dark skin tone", "alpha_code": ":woman_technologist_tone5:", "aliases": ":woman_technologist_dark_skin_tone:" }, "1f469-1f3fe-1f4bb": { "output": "1f469-1f3fe-200d-1f4bb", "name": "woman technologist: medium-dark skin tone", "alpha_code": ":woman_technologist_tone4:", "aliases": ":woman_technologist_medium_dark_skin_tone:" }, "1f469-1f3fd-1f4bb": { "output": "1f469-1f3fd-200d-1f4bb", "name": "woman technologist: medium skin tone", "alpha_code": ":woman_technologist_tone3:", "aliases": ":woman_technologist_medium_skin_tone:" }, "1f469-1f3fc-1f4bb": { "output": "1f469-1f3fc-200d-1f4bb", "name": "woman technologist: medium-light skin tone", "alpha_code": ":woman_technologist_tone2:", "aliases": ":woman_technologist_medium_light_skin_tone:" }, "1f469-1f3fb-1f4bb": { "output": "1f469-1f3fb-200d-1f4bb", "name": "woman technologist: light skin tone", "alpha_code": ":woman_technologist_tone1:", "aliases": ":woman_technologist_light_skin_tone:" }, "1f469-1f4bb": { "output": "1f469-200d-1f4bb", "name": "woman technologist", "alpha_code": ":woman_technologist:", "aliases": "" }, "1f468-1f3ff-1f3eb": { "output": "1f468-1f3ff-200d-1f3eb", "name": "man teacher: dark skin tone", "alpha_code": ":man_teacher_tone5:", "aliases": ":man_teacher_dark_skin_tone:" }, "1f468-1f3fe-1f3eb": { "output": "1f468-1f3fe-200d-1f3eb", "name": "man teacher: medium-dark skin tone", "alpha_code": ":man_teacher_tone4:", "aliases": ":man_teacher_medium_dark_skin_tone:" }, "1f468-1f3fd-1f3eb": { "output": "1f468-1f3fd-200d-1f3eb", "name": "man teacher: medium skin tone", "alpha_code": ":man_teacher_tone3:", "aliases": ":man_teacher_medium_skin_tone:" }, "1f468-1f3fc-1f3eb": { "output": "1f468-1f3fc-200d-1f3eb", "name": "man teacher: medium-light skin tone", "alpha_code": ":man_teacher_tone2:", "aliases": ":man_teacher_medium_light_skin_tone:" }, "1f468-1f3fb-1f3eb": { "output": "1f468-1f3fb-200d-1f3eb", "name": "man teacher: light skin tone", "alpha_code": ":man_teacher_tone1:", "aliases": ":man_teacher_light_skin_tone:" }, "1f468-1f3eb": { "output": "1f468-200d-1f3eb", "name": "man teacher", "alpha_code": ":man_teacher:", "aliases": "" }, "1f469-1f3ff-1f3eb": { "output": "1f469-1f3ff-200d-1f3eb", "name": "woman teacher: dark skin tone", "alpha_code": ":woman_teacher_tone5:", "aliases": ":woman_teacher_dark_skin_tone:" }, "1f469-1f3fe-1f3eb": { "output": "1f469-1f3fe-200d-1f3eb", "name": "woman teacher: medium-dark skin tone", "alpha_code": ":woman_teacher_tone4:", "aliases": ":woman_teacher_medium_dark_skin_tone:" }, "1f469-1f3fd-1f3eb": { "output": "1f469-1f3fd-200d-1f3eb", "name": "woman teacher: medium skin tone", "alpha_code": ":woman_teacher_tone3:", "aliases": ":woman_teacher_medium_skin_tone:" }, "1f469-1f3fc-1f3eb": { "output": "1f469-1f3fc-200d-1f3eb", "name": "woman teacher: medium-light skin tone", "alpha_code": ":woman_teacher_tone2:", "aliases": ":woman_teacher_medium_light_skin_tone:" }, "1f469-1f3fb-1f3eb": { "output": "1f469-1f3fb-200d-1f3eb", "name": "woman teacher: light skin tone", "alpha_code": ":woman_teacher_tone1:", "aliases": ":woman_teacher_light_skin_tone:" }, "1f469-1f3eb": { "output": "1f469-200d-1f3eb", "name": "woman teacher", "alpha_code": ":woman_teacher:", "aliases": "" }, "1f468-1f3ff-1f393": { "output": "1f468-1f3ff-200d-1f393", "name": "man student: dark skin tone", "alpha_code": ":man_student_tone5:", "aliases": ":man_student_dark_skin_tone:" }, "1f468-1f3fe-1f393": { "output": "1f468-1f3fe-200d-1f393", "name": "man student: medium-dark skin tone", "alpha_code": ":man_student_tone4:", "aliases": ":man_student_medium_dark_skin_tone:" }, "1f468-1f3fd-1f393": { "output": "1f468-1f3fd-200d-1f393", "name": "man student: medium skin tone", "alpha_code": ":man_student_tone3:", "aliases": ":man_student_medium_skin_tone:" }, "1f468-1f3fc-1f393": { "output": "1f468-1f3fc-200d-1f393", "name": "man student: medium-light skin tone", "alpha_code": ":man_student_tone2:", "aliases": ":man_student_medium_light_skin_tone:" }, "1f468-1f3fb-1f393": { "output": "1f468-1f3fb-200d-1f393", "name": "man student: light skin tone", "alpha_code": ":man_student_tone1:", "aliases": ":man_student_light_skin_tone:" }, "1f468-1f393": { "output": "1f468-200d-1f393", "name": "man student", "alpha_code": ":man_student:", "aliases": "" }, "1f469-1f3ff-1f393": { "output": "1f469-1f3ff-200d-1f393", "name": "woman student: dark skin tone", "alpha_code": ":woman_student_tone5:", "aliases": ":woman_student_dark_skin_tone:" }, "1f469-1f3fe-1f393": { "output": "1f469-1f3fe-200d-1f393", "name": "woman student: medium-dark skin tone", "alpha_code": ":woman_student_tone4:", "aliases": ":woman_student_medium_dark_skin_tone:" }, "1f469-1f3fd-1f393": { "output": "1f469-1f3fd-200d-1f393", "name": "woman student: medium skin tone", "alpha_code": ":woman_student_tone3:", "aliases": ":woman_student_medium_skin_tone:" }, "1f469-1f3fc-1f393": { "output": "1f469-1f3fc-200d-1f393", "name": "woman student: medium-light skin tone", "alpha_code": ":woman_student_tone2:", "aliases": ":woman_student_medium_light_skin_tone:" }, "1f469-1f3fb-1f393": { "output": "1f469-1f3fb-200d-1f393", "name": "woman student: light skin tone", "alpha_code": ":woman_student_tone1:", "aliases": ":woman_student_light_skin_tone:" }, "1f469-1f393": { "output": "1f469-200d-1f393", "name": "woman student", "alpha_code": ":woman_student:", "aliases": "" }, "1f468-1f3ff-1f3a4": { "output": "1f468-1f3ff-200d-1f3a4", "name": "man singer: dark skin tone", "alpha_code": ":man_singer_tone5:", "aliases": ":man_singer_dark_skin_tone:" }, "1f468-1f3fe-1f3a4": { "output": "1f468-1f3fe-200d-1f3a4", "name": "man singer: medium-dark skin tone", "alpha_code": ":man_singer_tone4:", "aliases": ":man_singer_medium_dark_skin_tone:" }, "1f468-1f3fd-1f3a4": { "output": "1f468-1f3fd-200d-1f3a4", "name": "man singer: medium skin tone", "alpha_code": ":man_singer_tone3:", "aliases": ":man_singer_medium_skin_tone:" }, "1f468-1f3fc-1f3a4": { "output": "1f468-1f3fc-200d-1f3a4", "name": "man singer: medium-light skin tone", "alpha_code": ":man_singer_tone2:", "aliases": ":man_singer_medium_light_skin_tone:" }, "1f468-1f3fb-1f3a4": { "output": "1f468-1f3fb-200d-1f3a4", "name": "man singer: light skin tone", "alpha_code": ":man_singer_tone1:", "aliases": ":man_singer_light_skin_tone:" }, "1f468-1f3a4": { "output": "1f468-200d-1f3a4", "name": "man singer", "alpha_code": ":man_singer:", "aliases": "" }, "1f469-1f3ff-1f3a4": { "output": "1f469-1f3ff-200d-1f3a4", "name": "woman singer: dark skin tone", "alpha_code": ":woman_singer_tone5:", "aliases": ":woman_singer_dark_skin_tone:" }, "1f469-1f3fe-1f3a4": { "output": "1f469-1f3fe-200d-1f3a4", "name": "woman singer: medium-dark skin tone", "alpha_code": ":woman_singer_tone4:", "aliases": ":woman_singer_medium_dark_skin_tone:" }, "1f469-1f3fd-1f3a4": { "output": "1f469-1f3fd-200d-1f3a4", "name": "woman singer: medium skin tone", "alpha_code": ":woman_singer_tone3:", "aliases": ":woman_singer_medium_skin_tone:" }, "1f469-1f3fc-1f3a4": { "output": "1f469-1f3fc-200d-1f3a4", "name": "woman singer: medium-light skin tone", "alpha_code": ":woman_singer_tone2:", "aliases": ":woman_singer_medium_light_skin_tone:" }, "1f469-1f3fb-1f3a4": { "output": "1f469-1f3fb-200d-1f3a4", "name": "woman singer: light skin tone", "alpha_code": ":woman_singer_tone1:", "aliases": ":woman_singer_light_skin_tone:" }, "1f469-1f3a4": { "output": "1f469-200d-1f3a4", "name": "woman singer", "alpha_code": ":woman_singer:", "aliases": "" }, "1f468-1f3ff-1f52c": { "output": "1f468-1f3ff-200d-1f52c", "name": "man scientist: dark skin tone", "alpha_code": ":man_scientist_tone5:", "aliases": ":man_scientist_dark_skin_tone:" }, "1f468-1f3fe-1f52c": { "output": "1f468-1f3fe-200d-1f52c", "name": "man scientist: medium-dark skin tone", "alpha_code": ":man_scientist_tone4:", "aliases": ":man_scientist_medium_dark_skin_tone:" }, "1f468-1f3fd-1f52c": { "output": "1f468-1f3fd-200d-1f52c", "name": "man scientist: medium skin tone", "alpha_code": ":man_scientist_tone3:", "aliases": ":man_scientist_medium_skin_tone:" }, "1f468-1f3fc-1f52c": { "output": "1f468-1f3fc-200d-1f52c", "name": "man scientist: medium-light skin tone", "alpha_code": ":man_scientist_tone2:", "aliases": ":man_scientist_medium_light_skin_tone:" }, "1f468-1f3fb-1f52c": { "output": "1f468-1f3fb-200d-1f52c", "name": "man scientist: light skin tone", "alpha_code": ":man_scientist_tone1:", "aliases": ":man_scientist_light_skin_tone:" }, "1f468-1f52c": { "output": "1f468-200d-1f52c", "name": "man scientist", "alpha_code": ":man_scientist:", "aliases": "" }, "1f469-1f3ff-1f52c": { "output": "1f469-1f3ff-200d-1f52c", "name": "woman scientist: dark skin tone", "alpha_code": ":woman_scientist_tone5:", "aliases": ":woman_scientist_dark_skin_tone:" }, "1f469-1f3fe-1f52c": { "output": "1f469-1f3fe-200d-1f52c", "name": "woman scientist: medium-dark skin tone", "alpha_code": ":woman_scientist_tone4:", "aliases": ":woman_scientist_medium_dark_skin_tone:" }, "1f469-1f3fd-1f52c": { "output": "1f469-1f3fd-200d-1f52c", "name": "woman scientist: medium skin tone", "alpha_code": ":woman_scientist_tone3:", "aliases": ":woman_scientist_medium_skin_tone:" }, "1f469-1f3fc-1f52c": { "output": "1f469-1f3fc-200d-1f52c", "name": "woman scientist: medium-light skin tone", "alpha_code": ":woman_scientist_tone2:", "aliases": ":woman_scientist_medium_light_skin_tone:" }, "1f469-1f3fb-1f52c": { "output": "1f469-1f3fb-200d-1f52c", "name": "woman scientist: light skin tone", "alpha_code": ":woman_scientist_tone1:", "aliases": ":woman_scientist_light_skin_tone:" }, "1f469-1f52c": { "output": "1f469-200d-1f52c", "name": "woman scientist", "alpha_code": ":woman_scientist:", "aliases": "" }, "1f468-1f3ff-1f4bc": { "output": "1f468-1f3ff-200d-1f4bc", "name": "man office worker: dark skin tone", "alpha_code": ":man_office_worker_tone5:", "aliases": ":man_office_worker_dark_skin_tone:" }, "1f468-1f3fe-1f4bc": { "output": "1f468-1f3fe-200d-1f4bc", "name": "man office worker: medium-dark skin tone", "alpha_code": ":man_office_worker_tone4:", "aliases": ":man_office_worker_medium_dark_skin_tone:" }, "1f468-1f3fd-1f4bc": { "output": "1f468-1f3fd-200d-1f4bc", "name": "man office worker: medium skin tone", "alpha_code": ":man_office_worker_tone3:", "aliases": ":man_office_worker_medium_skin_tone:" }, "1f468-1f3fc-1f4bc": { "output": "1f468-1f3fc-200d-1f4bc", "name": "man office worker: medium-light skin tone", "alpha_code": ":man_office_worker_tone2:", "aliases": ":man_office_worker_medium_light_skin_tone:" }, "1f468-1f3fb-1f4bc": { "output": "1f468-1f3fb-200d-1f4bc", "name": "man office worker: light skin tone", "alpha_code": ":man_office_worker_tone1:", "aliases": ":man_office_worker_light_skin_tone:" }, "1f468-1f4bc": { "output": "1f468-200d-1f4bc", "name": "man office worker", "alpha_code": ":man_office_worker:", "aliases": "" }, "1f469-1f3ff-1f4bc": { "output": "1f469-1f3ff-200d-1f4bc", "name": "woman office worker: dark skin tone", "alpha_code": ":woman_office_worker_tone5:", "aliases": ":woman_office_worker_dark_skin_tone:" }, "1f469-1f3fe-1f4bc": { "output": "1f469-1f3fe-200d-1f4bc", "name": "woman office worker: medium-dark skin tone", "alpha_code": ":woman_office_worker_tone4:", "aliases": ":woman_office_worker_medium_dark_skin_tone:" }, "1f469-1f3fd-1f4bc": { "output": "1f469-1f3fd-200d-1f4bc", "name": "woman office worker: medium skin tone", "alpha_code": ":woman_office_worker_tone3:", "aliases": ":woman_office_worker_medium_skin_tone:" }, "1f469-1f3fc-1f4bc": { "output": "1f469-1f3fc-200d-1f4bc", "name": "woman office worker: medium-light skin tone", "alpha_code": ":woman_office_worker_tone2:", "aliases": ":woman_office_worker_medium_light_skin_tone:" }, "1f469-1f3fb-1f4bc": { "output": "1f469-1f3fb-200d-1f4bc", "name": "woman office worker: light skin tone", "alpha_code": ":woman_office_worker_tone1:", "aliases": ":woman_office_worker_light_skin_tone:" }, "1f469-1f4bc": { "output": "1f469-200d-1f4bc", "name": "woman office worker", "alpha_code": ":woman_office_worker:", "aliases": "" }, "1f468-1f3ff-1f527": { "output": "1f468-1f3ff-200d-1f527", "name": "man mechanic: dark skin tone", "alpha_code": ":man_mechanic_tone5:", "aliases": ":man_mechanic_dark_skin_tone:" }, "1f468-1f3fe-1f527": { "output": "1f468-1f3fe-200d-1f527", "name": "man mechanic: medium-dark skin tone", "alpha_code": ":man_mechanic_tone4:", "aliases": ":man_mechanic_medium_dark_skin_tone:" }, "1f468-1f3fd-1f527": { "output": "1f468-1f3fd-200d-1f527", "name": "man mechanic: medium skin tone", "alpha_code": ":man_mechanic_tone3:", "aliases": ":man_mechanic_medium_skin_tone:" }, "1f468-1f3fc-1f527": { "output": "1f468-1f3fc-200d-1f527", "name": "man mechanic: medium-light skin tone", "alpha_code": ":man_mechanic_tone2:", "aliases": ":man_mechanic_medium_light_skin_tone:" }, "1f468-1f3fb-1f527": { "output": "1f468-1f3fb-200d-1f527", "name": "man mechanic: light skin tone", "alpha_code": ":man_mechanic_tone1:", "aliases": ":man_mechanic_light_skin_tone:" }, "1f468-1f527": { "output": "1f468-200d-1f527", "name": "man mechanic", "alpha_code": ":man_mechanic:", "aliases": "" }, "1f469-1f3ff-1f527": { "output": "1f469-1f3ff-200d-1f527", "name": "woman mechanic: dark skin tone", "alpha_code": ":woman_mechanic_tone5:", "aliases": ":woman_mechanic_dark_skin_tone:" }, "1f469-1f3fe-1f527": { "output": "1f469-1f3fe-200d-1f527", "name": "woman mechanic: medium-dark skin tone", "alpha_code": ":woman_mechanic_tone4:", "aliases": ":woman_mechanic_medium_dark_skin_tone:" }, "1f469-1f3fd-1f527": { "output": "1f469-1f3fd-200d-1f527", "name": "woman mechanic: medium skin tone", "alpha_code": ":woman_mechanic_tone3:", "aliases": ":woman_mechanic_medium_skin_tone:" }, "1f469-1f3fc-1f527": { "output": "1f469-1f3fc-200d-1f527", "name": "woman mechanic: medium-light skin tone", "alpha_code": ":woman_mechanic_tone2:", "aliases": ":woman_mechanic_medium_light_skin_tone:" }, "1f469-1f3fb-1f527": { "output": "1f469-1f3fb-200d-1f527", "name": "woman mechanic: light skin tone", "alpha_code": ":woman_mechanic_tone1:", "aliases": ":woman_mechanic_light_skin_tone:" }, "1f469-1f527": { "output": "1f469-200d-1f527", "name": "woman mechanic", "alpha_code": ":woman_mechanic:", "aliases": "" }, "1f468-1f3ff-2695": { "output": "1f468-1f3ff-200d-2695-fe0f", "name": "man health worker: dark skin tone", "alpha_code": ":man_health_worker_tone5:", "aliases": ":man_health_worker_dark_skin_tone:" }, "1f468-1f3fe-2695": { "output": "1f468-1f3fe-200d-2695-fe0f", "name": "man health worker: medium-dark skin tone", "alpha_code": ":man_health_worker_tone4:", "aliases": ":man_health_worker_medium_dark_skin_tone:" }, "1f468-1f3fd-2695": { "output": "1f468-1f3fd-200d-2695-fe0f", "name": "man health worker: medium skin tone", "alpha_code": ":man_health_worker_tone3:", "aliases": ":man_health_worker_medium_skin_tone:" }, "1f468-1f3fc-2695": { "output": "1f468-1f3fc-200d-2695-fe0f", "name": "man health worker: medium-light skin tone", "alpha_code": ":man_health_worker_tone2:", "aliases": ":man_health_worker_medium_light_skin_tone:" }, "1f468-1f3fb-2695": { "output": "1f468-1f3fb-200d-2695-fe0f", "name": "man health worker: light skin tone", "alpha_code": ":man_health_worker_tone1:", "aliases": ":man_health_worker_light_skin_tone:" }, "1f468-2695": { "output": "1f468-200d-2695-fe0f", "name": "man health worker", "alpha_code": ":man_health_worker:", "aliases": "" }, "1f469-1f3ff-2695": { "output": "1f469-1f3ff-200d-2695-fe0f", "name": "woman health worker: dark skin tone", "alpha_code": ":woman_health_worker_tone5:", "aliases": ":woman_health_worker_dark_skin_tone:" }, "1f469-1f3fe-2695": { "output": "1f469-1f3fe-200d-2695-fe0f", "name": "woman health worker: medium-dark skin tone", "alpha_code": ":woman_health_worker_tone4:", "aliases": ":woman_health_worker_medium_dark_skin_tone:" }, "1f469-1f3fd-2695": { "output": "1f469-1f3fd-200d-2695-fe0f", "name": "woman health worker: medium skin tone", "alpha_code": ":woman_health_worker_tone3:", "aliases": ":woman_health_worker_medium_skin_tone:" }, "1f469-1f3fc-2695": { "output": "1f469-1f3fc-200d-2695-fe0f", "name": "woman health worker: medium-light skin tone", "alpha_code": ":woman_health_worker_tone2:", "aliases": ":woman_health_worker_medium_light_skin_tone:" }, "1f469-1f3fb-2695": { "output": "1f469-1f3fb-200d-2695-fe0f", "name": "woman health worker: light skin tone", "alpha_code": ":woman_health_worker_tone1:", "aliases": ":woman_health_worker_light_skin_tone:" }, "1f469-2695": { "output": "1f469-200d-2695-fe0f", "name": "woman health worker", "alpha_code": ":woman_health_worker:", "aliases": "" }, "1f468-1f3ff-1f3ed": { "output": "1f468-1f3ff-200d-1f3ed", "name": "man factory worker: dark skin tone", "alpha_code": ":man_factory_worker_tone5:", "aliases": ":man_factory_worker_dark_skin_tone:" }, "1f468-1f3fe-1f3ed": { "output": "1f468-1f3fe-200d-1f3ed", "name": "man factory worker: medium-dark skin tone", "alpha_code": ":man_factory_worker_tone4:", "aliases": ":man_factory_worker_medium_dark_skin_tone:" }, "1f468-1f3fd-1f3ed": { "output": "1f468-1f3fd-200d-1f3ed", "name": "man factory worker: medium skin tone", "alpha_code": ":man_factory_worker_tone3:", "aliases": ":man_factory_worker_medium_skin_tone:" }, "1f468-1f3fc-1f3ed": { "output": "1f468-1f3fc-200d-1f3ed", "name": "man factory worker: medium-light skin tone", "alpha_code": ":man_factory_worker_tone2:", "aliases": ":man_factory_worker_medium_light_skin_tone:" }, "1f468-1f3fb-1f3ed": { "output": "1f468-1f3fb-200d-1f3ed", "name": "man factory worker: light skin tone", "alpha_code": ":man_factory_worker_tone1:", "aliases": ":man_factory_worker_light_skin_tone:" }, "1f468-1f3ed": { "output": "1f468-200d-1f3ed", "name": "man factory worker", "alpha_code": ":man_factory_worker:", "aliases": "" }, "1f469-1f3ff-1f3ed": { "output": "1f469-1f3ff-200d-1f3ed", "name": "woman factory worker: dark skin tone", "alpha_code": ":woman_factory_worker_tone5:", "aliases": ":woman_factory_worker_dark_skin_tone:" }, "1f469-1f3fe-1f3ed": { "output": "1f469-1f3fe-200d-1f3ed", "name": "woman factory worker: medium-dark skin tone", "alpha_code": ":woman_factory_worker_tone4:", "aliases": ":woman_factory_worker_medium_dark_skin_tone:" }, "1f469-1f3fd-1f3ed": { "output": "1f469-1f3fd-200d-1f3ed", "name": "woman factory worker: medium skin tone", "alpha_code": ":woman_factory_worker_tone3:", "aliases": ":woman_factory_worker_medium_skin_tone:" }, "1f469-1f3fc-1f3ed": { "output": "1f469-1f3fc-200d-1f3ed", "name": "woman factory worker: medium-light skin tone", "alpha_code": ":woman_factory_worker_tone2:", "aliases": ":woman_factory_worker_medium_light_skin_tone:" }, "1f469-1f3fb-1f3ed": { "output": "1f469-1f3fb-200d-1f3ed", "name": "woman factory worker: light skin tone", "alpha_code": ":woman_factory_worker_tone1:", "aliases": ":woman_factory_worker_light_skin_tone:" }, "1f469-1f3ed": { "output": "1f469-200d-1f3ed", "name": "woman factory worker", "alpha_code": ":woman_factory_worker:", "aliases": "" }, "1f468-1f3ff-1f373": { "output": "1f468-1f3ff-200d-1f373", "name": "man cook: dark skin tone", "alpha_code": ":man_cook_tone5:", "aliases": ":man_cook_dark_skin_tone:" }, "1f468-1f3fe-1f373": { "output": "1f468-1f3fe-200d-1f373", "name": "man cook: medium-dark skin tone", "alpha_code": ":man_cook_tone4:", "aliases": ":man_cook_medium_dark_skin_tone:" }, "1f468-1f3fd-1f373": { "output": "1f468-1f3fd-200d-1f373", "name": "man cook: medium skin tone", "alpha_code": ":man_cook_tone3:", "aliases": ":man_cook_medium_skin_tone:" }, "1f468-1f3fc-1f373": { "output": "1f468-1f3fc-200d-1f373", "name": "man cook: medium-light skin tone", "alpha_code": ":man_cook_tone2:", "aliases": ":man_cook_medium_light_skin_tone:" }, "1f468-1f3fb-1f373": { "output": "1f468-1f3fb-200d-1f373", "name": "man cook: light skin tone", "alpha_code": ":man_cook_tone1:", "aliases": ":man_cook_light_skin_tone:" }, "1f468-1f373": { "output": "1f468-200d-1f373", "name": "man cook", "alpha_code": ":man_cook:", "aliases": "" }, "1f469-1f3ff-1f373": { "output": "1f469-1f3ff-200d-1f373", "name": "woman cook: dark skin tone", "alpha_code": ":woman_cook_tone5:", "aliases": ":woman_cook_dark_skin_tone:" }, "1f469-1f3fe-1f373": { "output": "1f469-1f3fe-200d-1f373", "name": "woman cook: medium-dark skin tone", "alpha_code": ":woman_cook_tone4:", "aliases": ":woman_cook_medium_dark_skin_tone:" }, "1f469-1f3fd-1f373": { "output": "1f469-1f3fd-200d-1f373", "name": "woman cook: medium skin tone", "alpha_code": ":woman_cook_tone3:", "aliases": ":woman_cook_medium_skin_tone:" }, "1f469-1f3fc-1f373": { "output": "1f469-1f3fc-200d-1f373", "name": "woman cook: medium-light skin tone", "alpha_code": ":woman_cook_tone2:", "aliases": ":woman_cook_medium_light_skin_tone:" }, "1f469-1f3fb-1f373": { "output": "1f469-1f3fb-200d-1f373", "name": "woman cook: light skin tone", "alpha_code": ":woman_cook_tone1:", "aliases": ":woman_cook_light_skin_tone:" }, "1f469-1f373": { "output": "1f469-200d-1f373", "name": "woman cook", "alpha_code": ":woman_cook:", "aliases": "" }, "1f468-1f3ff-1f33e": { "output": "1f468-1f3ff-200d-1f33e", "name": "man farmer: dark skin tone", "alpha_code": ":man_farmer_tone5:", "aliases": ":man_farmer_dark_skin_tone:" }, "1f468-1f3fe-1f33e": { "output": "1f468-1f3fe-200d-1f33e", "name": "man farmer: medium-dark skin tone", "alpha_code": ":man_farmer_tone4:", "aliases": ":man_farmer_medium_dark_skin_tone:" }, "1f468-1f3fd-1f33e": { "output": "1f468-1f3fd-200d-1f33e", "name": "man farmer: medium skin tone", "alpha_code": ":man_farmer_tone3:", "aliases": ":man_farmer_medium_skin_tone:" }, "1f468-1f3fc-1f33e": { "output": "1f468-1f3fc-200d-1f33e", "name": "man farmer: medium-light skin tone", "alpha_code": ":man_farmer_tone2:", "aliases": ":man_farmer_medium_light_skin_tone:" }, "1f468-1f3fb-1f33e": { "output": "1f468-1f3fb-200d-1f33e", "name": "man farmer: light skin tone", "alpha_code": ":man_farmer_tone1:", "aliases": ":man_farmer_light_skin_tone:" }, "1f468-1f33e": { "output": "1f468-200d-1f33e", "name": "man farmer", "alpha_code": ":man_farmer:", "aliases": "" }, "1f469-1f3ff-1f33e": { "output": "1f469-1f3ff-200d-1f33e", "name": "woman farmer: dark skin tone", "alpha_code": ":woman_farmer_tone5:", "aliases": ":woman_farmer_dark_skin_tone:" }, "1f469-1f3fe-1f33e": { "output": "1f469-1f3fe-200d-1f33e", "name": "woman farmer: medium-dark skin tone", "alpha_code": ":woman_farmer_tone4:", "aliases": ":woman_farmer_medium_dark_skin_tone:" }, "1f469-1f3fd-1f33e": { "output": "1f469-1f3fd-200d-1f33e", "name": "woman farmer: medium skin tone", "alpha_code": ":woman_farmer_tone3:", "aliases": ":woman_farmer_medium_skin_tone:" }, "1f469-1f3fc-1f33e": { "output": "1f469-1f3fc-200d-1f33e", "name": "woman farmer: medium-light skin tone", "alpha_code": ":woman_farmer_tone2:", "aliases": ":woman_farmer_medium_light_skin_tone:" }, "1f469-1f3fb-1f33e": { "output": "1f469-1f3fb-200d-1f33e", "name": "woman farmer: light skin tone", "alpha_code": ":woman_farmer_tone1:", "aliases": ":woman_farmer_light_skin_tone:" }, "1f469-1f33e": { "output": "1f469-200d-1f33e", "name": "woman farmer", "alpha_code": ":woman_farmer:", "aliases": "" }, "1f574-1f3fb": { "output": "1f574-1f3fb", "name": "man in business suit levitating: light skin tone", "alpha_code": ":man_in_business_suit_levitating_tone1:", "aliases": ":man_in_business_suit_levitating_light_skin_tone:" }, "1f574-1f3fc": { "output": "1f574-1f3fc", "name": "man in business suit levitating: medium-light skin tone", "alpha_code": ":man_in_business_suit_levitating_tone2:", "aliases": ":man_in_business_suit_levitating_medium_light_skin_tone:" }, "1f574-1f3fd": { "output": "1f574-1f3fd", "name": "man in business suit levitating: medium skin tone", "alpha_code": ":man_in_business_suit_levitating_tone3:", "aliases": ":man_in_business_suit_levitating_medium_skin_tone:" }, "1f574-1f3fe": { "output": "1f574-1f3fe", "name": "man in business suit levitating: medium-dark skin tone", "alpha_code": ":man_in_business_suit_levitating_tone4:", "aliases": ":man_in_business_suit_levitating_medium_dark_skin_tone:" }, "1f574-1f3ff": { "output": "1f574-1f3ff", "name": "man in business suit levitating: dark skin tone", "alpha_code": ":man_in_business_suit_levitating_tone5:", "aliases": ":man_in_business_suit_levitating_dark_skin_tone:" }, "1f6cc-1f3fb": { "output": "1f6cc-1f3fb", "name": "person in bed: light skin tone", "alpha_code": ":person_in_bed_tone1:", "aliases": ":person_in_bed_light_skin_tone:" }, "1f6cc-1f3fc": { "output": "1f6cc-1f3fc", "name": "person in bed: medium-light skin tone", "alpha_code": ":person_in_bed_tone2:", "aliases": ":person_in_bed_medium_light_skin_tone:" }, "1f6cc-1f3fd": { "output": "1f6cc-1f3fd", "name": "person in bed: medium skin tone", "alpha_code": ":person_in_bed_tone3:", "aliases": ":person_in_bed_medium_skin_tone:" }, "1f6cc-1f3fe": { "output": "1f6cc-1f3fe", "name": "person in bed: medium-dark skin tone", "alpha_code": ":person_in_bed_tone4:", "aliases": ":person_in_bed_medium_dark_skin_tone:" }, "1f6cc-1f3ff": { "output": "1f6cc-1f3ff", "name": "person in bed: dark skin tone", "alpha_code": ":person_in_bed_tone5:", "aliases": ":person_in_bed_dark_skin_tone:" }, "1f468-1f466": { "output": "1f468-200d-1f466", "name": "family: man, boy", "alpha_code": ":family_man_boy:", "aliases": "" }, "1f468-1f466-1f466": { "output": "1f468-200d-1f466-200d-1f466", "name": "family: man, boy, boy", "alpha_code": ":family_man_boy_boy:", "aliases": "" }, "1f468-1f467": { "output": "1f468-200d-1f467", "name": "family: man, girl", "alpha_code": ":family_man_girl:", "aliases": "" }, "1f468-1f467-1f466": { "output": "1f468-200d-1f467-200d-1f466", "name": "family: man, girl, boy", "alpha_code": ":family_man_girl_boy:", "aliases": "" }, "1f469-1f466": { "output": "1f469-200d-1f466", "name": "family: woman, boy", "alpha_code": ":family_woman_boy:", "aliases": "" }, "1f469-1f466-1f466": { "output": "1f469-200d-1f466-200d-1f466", "name": "family: woman, boy, boy", "alpha_code": ":family_woman_boy_boy:", "aliases": "" }, "1f469-1f467": { "output": "1f469-200d-1f467", "name": "family: woman, girl", "alpha_code": ":family_woman_girl:", "aliases": "" }, "1f469-1f467-1f466": { "output": "1f469-200d-1f467-200d-1f466", "name": "family: woman, girl, boy", "alpha_code": ":family_woman_girl_boy:", "aliases": "" }, "1f469-1f467-1f467": { "output": "1f469-200d-1f467-200d-1f467", "name": "family: woman, girl, girl", "alpha_code": ":family_woman_girl_girl:", "aliases": "" }, "1f468-2696": { "output": "1f468-200d-2696-fe0f", "name": "man judge", "alpha_code": ":man_judge:", "aliases": "" }, "1f468-1f3fb-2696": { "output": "1f468-1f3fb-200d-2696-fe0f", "name": "man judge: light skin tone", "alpha_code": ":man_judge_tone1:", "aliases": ":man_judge_light_skin_tone:" }, "1f468-1f3fc-2696": { "output": "1f468-1f3fc-200d-2696-fe0f", "name": "man judge: medium-light skin tone", "alpha_code": ":man_judge_tone2:", "aliases": ":man_judge_medium_light_skin_tone:" }, "1f468-1f3fd-2696": { "output": "1f468-1f3fd-200d-2696-fe0f", "name": "man judge: medium skin tone", "alpha_code": ":man_judge_tone3:", "aliases": ":man_judge_medium_skin_tone:" }, "1f468-1f3fe-2696": { "output": "1f468-1f3fe-200d-2696-fe0f", "name": "man judge: medium-dark skin tone", "alpha_code": ":man_judge_tone4:", "aliases": ":man_judge_medium_dark_skin_tone:" }, "1f468-1f3ff-2696": { "output": "1f468-1f3ff-200d-2696-fe0f", "name": "man judge: dark skin tone", "alpha_code": ":man_judge_tone5:", "aliases": ":man_judge_dark_skin_tone:" }, "1f469-2696": { "output": "1f469-200d-2696-fe0f", "name": "woman judge", "alpha_code": ":woman_judge:", "aliases": "" }, "1f469-1f3fb-2696": { "output": "1f469-1f3fb-200d-2696-fe0f", "name": "woman judge: light skin tone", "alpha_code": ":woman_judge_tone1:", "aliases": ":woman_judge_light_skin_tone:" }, "1f469-1f3fc-2696": { "output": "1f469-1f3fc-200d-2696-fe0f", "name": "woman judge: medium-light skin tone", "alpha_code": ":woman_judge_tone2:", "aliases": ":woman_judge_medium_light_skin_tone:" }, "1f469-1f3fd-2696": { "output": "1f469-1f3fd-200d-2696-fe0f", "name": "woman judge: medium skin tone", "alpha_code": ":woman_judge_tone3:", "aliases": ":woman_judge_medium_skin_tone:" }, "1f469-1f3fe-2696": { "output": "1f469-1f3fe-200d-2696-fe0f", "name": "woman judge: medium-dark skin tone", "alpha_code": ":woman_judge_tone4:", "aliases": ":woman_judge_medium_dark_skin_tone:" }, "1f469-1f3ff-2696": { "output": "1f469-1f3ff-200d-2696-fe0f", "name": "woman judge: dark skin tone", "alpha_code": ":woman_judge_tone5:", "aliases": ":woman_judge_dark_skin_tone:" }, "1f468-2708": { "output": "1f468-200d-2708-fe0f", "name": "man pilot", "alpha_code": ":man_pilot:", "aliases": "" }, "1f468-1f3fb-2708": { "output": "1f468-1f3fb-200d-2708-fe0f", "name": "man pilot: light skin tone", "alpha_code": ":man_pilot_tone1:", "aliases": ":man_pilot_light_skin_tone:" }, "1f468-1f3fc-2708": { "output": "1f468-1f3fc-200d-2708-fe0f", "name": "man pilot: medium-light skin tone", "alpha_code": ":man_pilot_tone2:", "aliases": ":man_pilot_medium_light_skin_tone:" }, "1f468-1f3fd-2708": { "output": "1f468-1f3fd-200d-2708-fe0f", "name": "man pilot: medium skin tone", "alpha_code": ":man_pilot_tone3:", "aliases": ":man_pilot_medium_skin_tone:" }, "1f468-1f3fe-2708": { "output": "1f468-1f3fe-200d-2708-fe0f", "name": "man pilot: medium-dark skin tone", "alpha_code": ":man_pilot_tone4:", "aliases": ":man_pilot_medium_dark_skin_tone:" }, "1f468-1f3ff-2708": { "output": "1f468-1f3ff-200d-2708-fe0f", "name": "man pilot: dark skin tone", "alpha_code": ":man_pilot_tone5:", "aliases": ":man_pilot_dark_skin_tone:" }, "1f469-2708": { "output": "1f469-200d-2708-fe0f", "name": "woman pilot", "alpha_code": ":woman_pilot:", "aliases": "" }, "1f469-1f3fb-2708": { "output": "1f469-1f3fb-200d-2708-fe0f", "name": "woman pilot: light skin tone", "alpha_code": ":woman_pilot_tone1:", "aliases": ":woman_pilot_light_skin_tone:" }, "1f469-1f3fc-2708": { "output": "1f469-1f3fc-200d-2708-fe0f", "name": "woman pilot: medium-light skin tone", "alpha_code": ":woman_pilot_tone2:", "aliases": ":woman_pilot_medium_light_skin_tone:" }, "1f469-1f3fd-2708": { "output": "1f469-1f3fd-200d-2708-fe0f", "name": "woman pilot: medium skin tone", "alpha_code": ":woman_pilot_tone3:", "aliases": ":woman_pilot_medium_skin_tone:" }, "1f469-1f3fe-2708": { "output": "1f469-1f3fe-200d-2708-fe0f", "name": "woman pilot: medium-dark skin tone", "alpha_code": ":woman_pilot_tone4:", "aliases": ":woman_pilot_medium_dark_skin_tone:" }, "1f469-1f3ff-2708": { "output": "1f469-1f3ff-200d-2708-fe0f", "name": "woman pilot: dark skin tone", "alpha_code": ":woman_pilot_tone5:", "aliases": ":woman_pilot_dark_skin_tone:" }, "1f468-1f3a8": { "output": "1f468-200d-1f3a8", "name": "man artist", "alpha_code": ":man_artist:", "aliases": "" }, "1f468-1f3fb-1f3a8": { "output": "1f468-1f3fb-200d-1f3a8", "name": "man artist: light skin tone", "alpha_code": ":man_artist_tone1:", "aliases": ":man_artist_light_skin_tone:" }, "1f468-1f3fc-1f3a8": { "output": "1f468-1f3fc-200d-1f3a8", "name": "man artist: medium-light skin tone", "alpha_code": ":man_artist_tone2:", "aliases": ":man_artist_medium_light_skin_tone:" }, "1f468-1f3fd-1f3a8": { "output": "1f468-1f3fd-200d-1f3a8", "name": "man artist: medium skin tone", "alpha_code": ":man_artist_tone3:", "aliases": ":man_artist_medium_skin_tone:" }, "1f468-1f3fe-1f3a8": { "output": "1f468-1f3fe-200d-1f3a8", "name": "man artist: medium-dark skin tone", "alpha_code": ":man_artist_tone4:", "aliases": ":man_artist_medium_dark_skin_tone:" }, "1f468-1f3ff-1f3a8": { "output": "1f468-1f3ff-200d-1f3a8", "name": "man artist: dark skin tone", "alpha_code": ":man_artist_tone5:", "aliases": ":man_artist_dark_skin_tone:" }, "1f469-1f3a8": { "output": "1f469-200d-1f3a8", "name": "woman artist", "alpha_code": ":woman_artist:", "aliases": "" }, "1f469-1f3fb-1f3a8": { "output": "1f469-1f3fb-200d-1f3a8", "name": "woman artist: light skin tone", "alpha_code": ":woman_artist_tone1:", "aliases": ":woman_artist_light_skin_tone:" }, "1f469-1f3fc-1f3a8": { "output": "1f469-1f3fc-200d-1f3a8", "name": "woman artist: medium-light skin tone", "alpha_code": ":woman_artist_tone2:", "aliases": ":woman_artist_medium_light_skin_tone:" }, "1f469-1f3fd-1f3a8": { "output": "1f469-1f3fd-200d-1f3a8", "name": "woman artist: medium skin tone", "alpha_code": ":woman_artist_tone3:", "aliases": ":woman_artist_medium_skin_tone:" }, "1f469-1f3fe-1f3a8": { "output": "1f469-1f3fe-200d-1f3a8", "name": "woman artist: medium-dark skin tone", "alpha_code": ":woman_artist_tone4:", "aliases": ":woman_artist_medium_dark_skin_tone:" }, "1f469-1f3ff-1f3a8": { "output": "1f469-1f3ff-200d-1f3a8", "name": "woman artist: dark skin tone", "alpha_code": ":woman_artist_tone5:", "aliases": ":woman_artist_dark_skin_tone:" }, "1f468-1f680": { "output": "1f468-200d-1f680", "name": "man astronaut", "alpha_code": ":man_astronaut:", "aliases": "" }, "1f468-1f3fb-1f680": { "output": "1f468-1f3fb-200d-1f680", "name": "man astronaut: light skin tone", "alpha_code": ":man_astronaut_tone1:", "aliases": ":man_astronaut_light_skin_tone:" }, "1f468-1f3fc-1f680": { "output": "1f468-1f3fc-200d-1f680", "name": "man astronaut: medium-light skin tone", "alpha_code": ":man_astronaut_tone2:", "aliases": ":man_astronaut_medium_light_skin_tone:" }, "1f468-1f3fd-1f680": { "output": "1f468-1f3fd-200d-1f680", "name": "man astronaut: medium skin tone", "alpha_code": ":man_astronaut_tone3:", "aliases": ":man_astronaut_medium_skin_tone:" }, "1f468-1f3fe-1f680": { "output": "1f468-1f3fe-200d-1f680", "name": "man astronaut: medium-dark skin tone", "alpha_code": ":man_astronaut_tone4:", "aliases": ":man_astronaut_medium_dark_skin_tone:" }, "1f468-1f3ff-1f680": { "output": "1f468-1f3ff-200d-1f680", "name": "man astronaut: dark skin tone", "alpha_code": ":man_astronaut_tone5:", "aliases": ":man_astronaut_dark_skin_tone:" }, "1f469-1f680": { "output": "1f469-200d-1f680", "name": "woman astronaut", "alpha_code": ":woman_astronaut:", "aliases": "" }, "1f469-1f3fb-1f680": { "output": "1f469-1f3fb-200d-1f680", "name": "woman astronaut: light skin tone", "alpha_code": ":woman_astronaut_tone1:", "aliases": ":woman_astronaut_light_skin_tone:" }, "1f469-1f3fc-1f680": { "output": "1f469-1f3fc-200d-1f680", "name": "woman astronaut: medium-light skin tone", "alpha_code": ":woman_astronaut_tone2:", "aliases": ":woman_astronaut_medium_light_skin_tone:" }, "1f469-1f3fd-1f680": { "output": "1f469-1f3fd-200d-1f680", "name": "woman astronaut: medium skin tone", "alpha_code": ":woman_astronaut_tone3:", "aliases": ":woman_astronaut_medium_skin_tone:" }, "1f469-1f3fe-1f680": { "output": "1f469-1f3fe-200d-1f680", "name": "woman astronaut: medium-dark skin tone", "alpha_code": ":woman_astronaut_tone4:", "aliases": ":woman_astronaut_medium_dark_skin_tone:" }, "1f469-1f3ff-1f680": { "output": "1f469-1f3ff-200d-1f680", "name": "woman astronaut: dark skin tone", "alpha_code": ":woman_astronaut_tone5:", "aliases": ":woman_astronaut_dark_skin_tone:" }, "1f468-1f692": { "output": "1f468-200d-1f692", "name": "man firefighter", "alpha_code": ":man_firefighter:", "aliases": "" }, "1f468-1f3fb-1f692": { "output": "1f468-1f3fb-200d-1f692", "name": "man firefighter: light skin tone", "alpha_code": ":man_firefighter_tone1:", "aliases": ":man_firefighter_light_skin_tone:" }, "1f468-1f3fc-1f692": { "output": "1f468-1f3fc-200d-1f692", "name": "man firefighter: medium-light skin tone", "alpha_code": ":man_firefighter_tone2:", "aliases": ":man_firefighter_medium_light_skin_tone:" }, "1f468-1f3fd-1f692": { "output": "1f468-1f3fd-200d-1f692", "name": "man firefighter: medium skin tone", "alpha_code": ":man_firefighter_tone3:", "aliases": ":man_firefighter_medium_skin_tone:" }, "1f468-1f3fe-1f692": { "output": "1f468-1f3fe-200d-1f692", "name": "man firefighter: medium-dark skin tone", "alpha_code": ":man_firefighter_tone4:", "aliases": ":man_firefighter_medium_dark_skin_tone:" }, "1f468-1f3ff-1f692": { "output": "1f468-1f3ff-200d-1f692", "name": "man firefighter: dark skin tone", "alpha_code": ":man_firefighter_tone5:", "aliases": ":man_firefighter_dark_skin_tone:" }, "1f469-1f692": { "output": "1f469-200d-1f692", "name": "woman firefighter", "alpha_code": ":woman_firefighter:", "aliases": "" }, "1f469-1f3fb-1f692": { "output": "1f469-1f3fb-200d-1f692", "name": "woman firefighter: light skin tone", "alpha_code": ":woman_firefighter_tone1:", "aliases": ":woman_firefighter_light_skin_tone:" }, "1f469-1f3fc-1f692": { "output": "1f469-1f3fc-200d-1f692", "name": "woman firefighter: medium-light skin tone", "alpha_code": ":woman_firefighter_tone2:", "aliases": ":woman_firefighter_medium_light_skin_tone:" }, "1f469-1f3fd-1f692": { "output": "1f469-1f3fd-200d-1f692", "name": "woman firefighter: medium skin tone", "alpha_code": ":woman_firefighter_tone3:", "aliases": ":woman_firefighter_medium_skin_tone:" }, "1f469-1f3fe-1f692": { "output": "1f469-1f3fe-200d-1f692", "name": "woman firefighter: medium-dark skin tone", "alpha_code": ":woman_firefighter_tone4:", "aliases": ":woman_firefighter_medium_dark_skin_tone:" }, "1f469-1f3ff-1f692": { "output": "1f469-1f3ff-200d-1f692", "name": "woman firefighter: dark skin tone", "alpha_code": ":woman_firefighter_tone5:", "aliases": ":woman_firefighter_dark_skin_tone:" }, "2640": { "output": "2640-fe0f", "name": "female sign", "alpha_code": ":female_sign:", "aliases": "" }, "2642": { "output": "2642-fe0f", "name": "male sign", "alpha_code": ":male_sign:", "aliases": "" }, "2695": { "output": "2695-fe0f", "name": "medical symbol", "alpha_code": ":medical_symbol:", "aliases": "" }, "1f1fa-1f1f3": { "output": "1f1fa-1f1f3", "name": "United Nations", "alpha_code": ":united_nations:", "aliases": "" }, "1f3c2-1f3fc": { "output": "1f3c2-1f3fc", "name": "snowboarder: medium-light skin tone", "alpha_code": ":snowboarder_tone2:", "aliases": ":snowboarder_medium_light_skin_tone:" }, "1f3c2-1f3fd": { "output": "1f3c2-1f3fd", "name": "snowboarder: medium skin tone", "alpha_code": ":snowboarder_tone3:", "aliases": ":snowboarder_medium_skin_tone:" }, "1f3c2-1f3fe": { "output": "1f3c2-1f3fe", "name": "snowboarder: medium-dark skin tone", "alpha_code": ":snowboarder_tone4:", "aliases": ":snowboarder_medium_dark_skin_tone:" }, "1f3c2-1f3ff": { "output": "1f3c2-1f3ff", "name": "snowboarder: dark skin tone", "alpha_code": ":snowboarder_tone5:", "aliases": ":snowboarder_dark_skin_tone:" }, "1f3cc-1f3fb": { "output": "1f3cc-1f3fb", "name": "person golfing: light skin tone", "alpha_code": ":person_golfing_tone1:", "aliases": ":person_golfing_light_skin_tone:" }, "1f3cc-1f3fc": { "output": "1f3cc-1f3fc", "name": "person golfing: medium-light skin tone", "alpha_code": ":person_golfing_tone2:", "aliases": ":person_golfing_medium_light_skin_tone:" }, "1f3cc-1f3fd": { "output": "1f3cc-1f3fd", "name": "person golfing: medium skin tone", "alpha_code": ":person_golfing_tone3:", "aliases": ":person_golfing_medium_skin_tone:" }, "1f3cc-1f3fe": { "output": "1f3cc-1f3fe", "name": "person golfing: medium-dark skin tone", "alpha_code": ":person_golfing_tone4:", "aliases": ":person_golfing_medium_dark_skin_tone:" }, "1f3cc-1f3ff": { "output": "1f3cc-1f3ff", "name": "person golfing: dark skin tone", "alpha_code": ":person_golfing_tone5:", "aliases": ":person_golfing_dark_skin_tone:" }, "1f468-1f467-1f467": { "output": "1f468-200d-1f467-200d-1f467", "name": "family: man, girl, girl", "alpha_code": ":family_man_girl_girl:", "aliases": "" }, "1f468-1f469-1f466": { "output": "1f468-200d-1f469-200d-1f466", "name": "family: man, woman, boy", "alpha_code": ":family_man_woman_boy:", "aliases": "" }, "1f469-2764-1f468": { "output": "1f469-200d-2764-fe0f-200d-1f468", "name": "couple with heart: woman, man", "alpha_code": ":couple_with_heart_woman_man:", "aliases": "" }, "1f469-2764-1f48b-1f468": { "output": "1f469-200d-2764-fe0f-200d-1f48b-200d-1f468", "name": "kiss: woman, man", "alpha_code": ":kiss_woman_man:", "aliases": "" }, "1f6f7": { "output": "1f6f7", "name": "sled", "alpha_code": ":sled:", "aliases": "" }, "1f6f8": { "output": "1f6f8", "name": "flying saucer", "alpha_code": ":flying_saucer:", "aliases": "" }, "1f91f": { "output": "1f91f", "name": "love-you gesture", "alpha_code": ":love_you_gesture:", "aliases": "" }, "1f928": { "output": "1f928", "name": "face with raised eyebrow", "alpha_code": ":face_with_raised_eyebrow:", "aliases": "" }, "1f929": { "output": "1f929", "name": "star-struck", "alpha_code": ":star_struck:", "aliases": "" }, "1f92a": { "output": "1f92a", "name": "crazy face", "alpha_code": ":crazy_face:", "aliases": "" }, "1f92b": { "output": "1f92b", "name": "shushing face", "alpha_code": ":shushing_face:", "aliases": "" }, "1f92c": { "output": "1f92c", "name": "face with symbols over mouth", "alpha_code": ":face_with_symbols_over_mouth:", "aliases": "" }, "1f92d": { "output": "1f92d", "name": "face with hand over mouth", "alpha_code": ":face_with_hand_over_mouth:", "aliases": "" }, "1f92e": { "output": "1f92e", "name": "face vomiting", "alpha_code": ":face_vomiting:", "aliases": "" }, "1f92f": { "output": "1f92f", "name": "exploding head", "alpha_code": ":exploding_head:", "aliases": "" }, "1f931": { "output": "1f931", "name": "breast-feeding", "alpha_code": ":breast_feeding:", "aliases": "" }, "1f932": { "output": "1f932", "name": "palms up together", "alpha_code": ":palms_up_together:", "aliases": "" }, "1f94c": { "output": "1f94c", "name": "curling stone", "alpha_code": ":curling_stone:", "aliases": "" }, "1f95f": { "output": "1f95f", "name": "dumpling", "alpha_code": ":dumpling:", "aliases": "" }, "1f960": { "output": "1f960", "name": "fortune cookie", "alpha_code": ":fortune_cookie:", "aliases": "" }, "1f961": { "output": "1f961", "name": "takeout box", "alpha_code": ":takeout_box:", "aliases": "" }, "1f962": { "output": "1f962", "name": "chopsticks", "alpha_code": ":chopsticks:", "aliases": "" }, "1f963": { "output": "1f963", "name": "bowl with spoon", "alpha_code": ":bowl_with_spoon:", "aliases": "" }, "1f964": { "output": "1f964", "name": "cup with straw", "alpha_code": ":cup_with_straw:", "aliases": "" }, "1f965": { "output": "1f965", "name": "coconut", "alpha_code": ":coconut:", "aliases": "" }, "1f966": { "output": "1f966", "name": "broccoli", "alpha_code": ":broccoli:", "aliases": "" }, "1f967": { "output": "1f967", "name": "pie", "alpha_code": ":pie:", "aliases": "" }, "1f968": { "output": "1f968", "name": "pretzel", "alpha_code": ":pretzel:", "aliases": "" }, "1f969": { "output": "1f969", "name": "cut of meat", "alpha_code": ":cut_of_meat:", "aliases": "" }, "1f96a": { "output": "1f96a", "name": "sandwich", "alpha_code": ":sandwich:", "aliases": "" }, "1f96b": { "output": "1f96b", "name": "canned food", "alpha_code": ":canned_food:", "aliases": "" }, "1f992": { "output": "1f992", "name": "giraffe", "alpha_code": ":giraffe:", "aliases": "" }, "1f993": { "output": "1f993", "name": "zebra", "alpha_code": ":zebra:", "aliases": "" }, "1f994": { "output": "1f994", "name": "hedgehog", "alpha_code": ":hedgehog:", "aliases": "" }, "1f995": { "output": "1f995", "name": "sauropod", "alpha_code": ":sauropod:", "aliases": "" }, "1f996": { "output": "1f996", "name": "T-Rex", "alpha_code": ":t_rex:", "aliases": "" }, "1f997": { "output": "1f997", "name": "cricket", "alpha_code": ":cricket:", "aliases": "" }, "1f9d0": { "output": "1f9d0", "name": "face with monocle", "alpha_code": ":face_with_monocle:", "aliases": "" }, "1f9d1": { "output": "1f9d1", "name": "adult", "alpha_code": ":adult:", "aliases": "" }, "1f9d2": { "output": "1f9d2", "name": "child", "alpha_code": ":child:", "aliases": "" }, "1f9d3": { "output": "1f9d3", "name": "older adult", "alpha_code": ":older_adult:", "aliases": "" }, "1f9d4": { "output": "1f9d4", "name": "bearded person", "alpha_code": ":bearded_person:", "aliases": "" }, "1f9d5": { "output": "1f9d5", "name": "woman with headscarf", "alpha_code": ":woman_with_headscarf:", "aliases": "" }, "1f9d6": { "output": "1f9d6", "name": "person in steamy room", "alpha_code": ":person_in_steamy_room:", "aliases": "" }, "1f9d7": { "output": "1f9d7", "name": "person climbing", "alpha_code": ":person_climbing:", "aliases": "" }, "1f9d8": { "output": "1f9d8", "name": "person in lotus position", "alpha_code": ":person_in_lotus_position:", "aliases": "" }, "1f9d9": { "output": "1f9d9", "name": "mage", "alpha_code": ":mage:", "aliases": "" }, "1f9da": { "output": "1f9da", "name": "fairy", "alpha_code": ":fairy:", "aliases": "" }, "1f9db": { "output": "1f9db", "name": "vampire", "alpha_code": ":vampire:", "aliases": "" }, "1f9dc": { "output": "1f9dc", "name": "merperson", "alpha_code": ":merperson:", "aliases": "" }, "1f9dd": { "output": "1f9dd", "name": "elf", "alpha_code": ":elf:", "aliases": "" }, "1f9de": { "output": "1f9de", "name": "genie", "alpha_code": ":genie:", "aliases": "" }, "1f9df": { "output": "1f9df", "name": "zombie", "alpha_code": ":zombie:", "aliases": "" }, "1f9e0": { "output": "1f9e0", "name": "brain", "alpha_code": ":brain:", "aliases": "" }, "1f9e1": { "output": "1f9e1", "name": "orange heart", "alpha_code": ":orange_heart:", "aliases": "" }, "1f9e2": { "output": "1f9e2", "name": "billed cap", "alpha_code": ":billed_cap:", "aliases": "" }, "1f9e3": { "output": "1f9e3", "name": "scarf", "alpha_code": ":scarf:", "aliases": "" }, "1f9e4": { "output": "1f9e4", "name": "gloves", "alpha_code": ":gloves:", "aliases": "" }, "1f9e5": { "output": "1f9e5", "name": "coat", "alpha_code": ":coat:", "aliases": "" }, "1f9e6": { "output": "1f9e6", "name": "socks", "alpha_code": ":socks:", "aliases": "" }, "1f3f4-e0067-e0062-e0065-e006e-e0067-e007f": { "output": "1f3f4-e0067-e0062-e0065-e006e-e0067-e007f", "name": "England", "alpha_code": ":england:", "aliases": "" }, "1f3f4-e0067-e0062-e0073-e0063-e0074-e007f": { "output": "1f3f4-e0067-e0062-e0073-e0063-e0074-e007f", "name": "Scotland", "alpha_code": ":scotland:", "aliases": "" }, "1f3f4-e0067-e0062-e0077-e006c-e0073-e007f": { "output": "1f3f4-e0067-e0062-e0077-e006c-e0073-e007f", "name": "Wales", "alpha_code": ":wales:", "aliases": "" }, "1f91f-1f3fb": { "output": "1f91f-1f3fb", "name": "love-you gesture: light skin tone", "alpha_code": ":love_you_gesture_tone1:", "aliases": ":love_you_gesture_light_skin_tone:" }, "1f91f-1f3fc": { "output": "1f91f-1f3fc", "name": "love-you gesture: medium-light skin tone", "alpha_code": ":love_you_gesture_tone2:", "aliases": ":love_you_gesture_medium_light_skin_tone:" }, "1f91f-1f3fd": { "output": "1f91f-1f3fd", "name": "love-you gesture: medium skin tone", "alpha_code": ":love_you_gesture_tone3:", "aliases": ":love_you_gesture_medium_skin_tone:" }, "1f91f-1f3fe": { "output": "1f91f-1f3fe", "name": "love-you gesture: medium-dark skin tone", "alpha_code": ":love_you_gesture_tone4:", "aliases": ":love_you_gesture_medium_dark_skin_tone:" }, "1f91f-1f3ff": { "output": "1f91f-1f3ff", "name": "love-you gesture: dark skin tone", "alpha_code": ":love_you_gesture_tone5:", "aliases": ":love_you_gesture_dark_skin_tone:" }, "1f931-1f3fb": { "output": "1f931-1f3fb", "name": "breast-feeding: light skin tone", "alpha_code": ":breast_feeding_tone1:", "aliases": ":breast_feeding_light_skin_tone:" }, "1f931-1f3fc": { "output": "1f931-1f3fc", "name": "breast-feeding: medium-light skin tone", "alpha_code": ":breast_feeding_tone2:", "aliases": ":breast_feeding_medium_light_skin_tone:" }, "1f931-1f3fd": { "output": "1f931-1f3fd", "name": "breast-feeding: medium skin tone", "alpha_code": ":breast_feeding_tone3:", "aliases": ":breast_feeding_medium_skin_tone:" }, "1f931-1f3fe": { "output": "1f931-1f3fe", "name": "breast-feeding: medium-dark skin tone", "alpha_code": ":breast_feeding_tone4:", "aliases": ":breast_feeding_medium_dark_skin_tone:" }, "1f931-1f3ff": { "output": "1f931-1f3ff", "name": "breast-feeding: dark skin tone", "alpha_code": ":breast_feeding_tone5:", "aliases": ":breast_feeding_dark_skin_tone:" }, "1f932-1f3fb": { "output": "1f932-1f3fb", "name": "palms up together: light skin tone", "alpha_code": ":palms_up_together_tone1:", "aliases": ":palms_up_together_light_skin_tone:" }, "1f932-1f3fc": { "output": "1f932-1f3fc", "name": "palms up together: medium-light skin tone", "alpha_code": ":palms_up_together_tone2:", "aliases": ":palms_up_together_medium_light_skin_tone:" }, "1f932-1f3fd": { "output": "1f932-1f3fd", "name": "palms up together: medium skin tone", "alpha_code": ":palms_up_together_tone3:", "aliases": ":palms_up_together_medium_skin_tone:" }, "1f932-1f3fe": { "output": "1f932-1f3fe", "name": "palms up together: medium-dark skin tone", "alpha_code": ":palms_up_together_tone4:", "aliases": ":palms_up_together_medium_dark_skin_tone:" }, "1f932-1f3ff": { "output": "1f932-1f3ff", "name": "palms up together: dark skin tone", "alpha_code": ":palms_up_together_tone5:", "aliases": ":palms_up_together_dark_skin_tone:" }, "1f9d1-1f3fb": { "output": "1f9d1-1f3fb", "name": "adult: light skin tone", "alpha_code": ":adult_tone1:", "aliases": ":adult_light_skin_tone:" }, "1f9d1-1f3fc": { "output": "1f9d1-1f3fc", "name": "adult: medium-light skin tone", "alpha_code": ":adult_tone2:", "aliases": ":adult_medium_light_skin_tone:" }, "1f9d1-1f3fd": { "output": "1f9d1-1f3fd", "name": "adult: medium skin tone", "alpha_code": ":adult_tone3:", "aliases": ":adult_medium_skin_tone:" }, "1f9d1-1f3fe": { "output": "1f9d1-1f3fe", "name": "adult: medium-dark skin tone", "alpha_code": ":adult_tone4:", "aliases": ":adult_medium_dark_skin_tone:" }, "1f9d1-1f3ff": { "output": "1f9d1-1f3ff", "name": "adult: dark skin tone", "alpha_code": ":adult_tone5:", "aliases": ":adult_dark_skin_tone:" }, "1f9d2-1f3fb": { "output": "1f9d2-1f3fb", "name": "child: light skin tone", "alpha_code": ":child_tone1:", "aliases": ":child_light_skin_tone:" }, "1f9d2-1f3fc": { "output": "1f9d2-1f3fc", "name": "child: medium-light skin tone", "alpha_code": ":child_tone2:", "aliases": ":child_medium_light_skin_tone:" }, "1f9d2-1f3fd": { "output": "1f9d2-1f3fd", "name": "child: medium skin tone", "alpha_code": ":child_tone3:", "aliases": ":child_medium_skin_tone:" }, "1f9d2-1f3fe": { "output": "1f9d2-1f3fe", "name": "child: medium-dark skin tone", "alpha_code": ":child_tone4:", "aliases": ":child_medium_dark_skin_tone:" }, "1f9d2-1f3ff": { "output": "1f9d2-1f3ff", "name": "child: dark skin tone", "alpha_code": ":child_tone5:", "aliases": ":child_dark_skin_tone:" }, "1f9d3-1f3fb": { "output": "1f9d3-1f3fb", "name": "older adult: light skin tone", "alpha_code": ":older_adult_tone1:", "aliases": ":older_adult_light_skin_tone:" }, "1f9d3-1f3fc": { "output": "1f9d3-1f3fc", "name": "older adult: medium-light skin tone", "alpha_code": ":older_adult_tone2:", "aliases": ":older_adult_medium_light_skin_tone:" }, "1f9d3-1f3fd": { "output": "1f9d3-1f3fd", "name": "older adult: medium skin tone", "alpha_code": ":older_adult_tone3:", "aliases": ":older_adult_medium_skin_tone:" }, "1f9d3-1f3fe": { "output": "1f9d3-1f3fe", "name": "older adult: medium-dark skin tone", "alpha_code": ":older_adult_tone4:", "aliases": ":older_adult_medium_dark_skin_tone:" }, "1f9d3-1f3ff": { "output": "1f9d3-1f3ff", "name": "older adult: dark skin tone", "alpha_code": ":older_adult_tone5:", "aliases": ":older_adult_dark_skin_tone:" }, "1f9d4-1f3fb": { "output": "1f9d4-1f3fb", "name": "bearded person: light skin tone", "alpha_code": ":bearded_person_tone1:", "aliases": ":bearded_person_light_skin_tone:" }, "1f9d4-1f3fc": { "output": "1f9d4-1f3fc", "name": "bearded person: medium-light skin tone", "alpha_code": ":bearded_person_tone2:", "aliases": ":bearded_person_medium_light_skin_tone:" }, "1f9d4-1f3fd": { "output": "1f9d4-1f3fd", "name": "bearded person: medium skin tone", "alpha_code": ":bearded_person_tone3:", "aliases": ":bearded_person_medium_skin_tone:" }, "1f9d4-1f3fe": { "output": "1f9d4-1f3fe", "name": "bearded person: medium-dark skin tone", "alpha_code": ":bearded_person_tone4:", "aliases": ":bearded_person_medium_dark_skin_tone:" }, "1f9d4-1f3ff": { "output": "1f9d4-1f3ff", "name": "bearded person: dark skin tone", "alpha_code": ":bearded_person_tone5:", "aliases": ":bearded_person_dark_skin_tone:" }, "1f9d5-1f3fb": { "output": "1f9d5-1f3fb", "name": "woman with headscarf: light skin tone", "alpha_code": ":woman_with_headscarf_tone1:", "aliases": ":woman_with_headscarf_light_skin_tone:" }, "1f9d5-1f3fc": { "output": "1f9d5-1f3fc", "name": "woman with headscarf: medium-light skin tone", "alpha_code": ":woman_with_headscarf_tone2:", "aliases": ":woman_with_headscarf_medium_light_skin_tone:" }, "1f9d5-1f3fd": { "output": "1f9d5-1f3fd", "name": "woman with headscarf: medium skin tone", "alpha_code": ":woman_with_headscarf_tone3:", "aliases": ":woman_with_headscarf_medium_skin_tone:" }, "1f9d5-1f3fe": { "output": "1f9d5-1f3fe", "name": "woman with headscarf: medium-dark skin tone", "alpha_code": ":woman_with_headscarf_tone4:", "aliases": ":woman_with_headscarf_medium_dark_skin_tone:" }, "1f9d5-1f3ff": { "output": "1f9d5-1f3ff", "name": "woman with headscarf: dark skin tone", "alpha_code": ":woman_with_headscarf_tone5:", "aliases": ":woman_with_headscarf_dark_skin_tone:" }, "1f9d6-1f3fb": { "output": "1f9d6-1f3fb", "name": "person in steamy room: light skin tone", "alpha_code": ":person_in_steamy_room_tone1:", "aliases": ":person_in_steamy_room_light_skin_tone:" }, "1f9d6-1f3fc": { "output": "1f9d6-1f3fc", "name": "person in steamy room: medium-light skin tone", "alpha_code": ":person_in_steamy_room_tone2:", "aliases": ":person_in_steamy_room_medium_light_skin_tone:" }, "1f9d6-1f3fd": { "output": "1f9d6-1f3fd", "name": "person in steamy room: medium skin tone", "alpha_code": ":person_in_steamy_room_tone3:", "aliases": ":person_in_steamy_room_medium_skin_tone:" }, "1f9d6-1f3fe": { "output": "1f9d6-1f3fe", "name": "person in steamy room: medium-dark skin tone", "alpha_code": ":person_in_steamy_room_tone4:", "aliases": ":person_in_steamy_room_medium_dark_skin_tone:" }, "1f9d6-1f3ff": { "output": "1f9d6-1f3ff", "name": "person in steamy room: dark skin tone", "alpha_code": ":person_in_steamy_room_tone5:", "aliases": ":person_in_steamy_room_dark_skin_tone:" }, "1f9d7-1f3fb": { "output": "1f9d7-1f3fb", "name": "person climbing: light skin tone", "alpha_code": ":person_climbing_tone1:", "aliases": ":person_climbing_light_skin_tone:" }, "1f9d7-1f3fc": { "output": "1f9d7-1f3fc", "name": "person climbing: medium-light skin tone", "alpha_code": ":person_climbing_tone2:", "aliases": ":person_climbing_medium_light_skin_tone:" }, "1f9d7-1f3fd": { "output": "1f9d7-1f3fd", "name": "person climbing: medium skin tone", "alpha_code": ":person_climbing_tone3:", "aliases": ":person_climbing_medium_skin_tone:" }, "1f9d7-1f3fe": { "output": "1f9d7-1f3fe", "name": "person climbing: medium-dark skin tone", "alpha_code": ":person_climbing_tone4:", "aliases": ":person_climbing_medium_dark_skin_tone:" }, "1f9d7-1f3ff": { "output": "1f9d7-1f3ff", "name": "person climbing: dark skin tone", "alpha_code": ":person_climbing_tone5:", "aliases": ":person_climbing_dark_skin_tone:" }, "1f9d8-1f3fb": { "output": "1f9d8-1f3fb", "name": "person in lotus position: light skin tone", "alpha_code": ":person_in_lotus_position_tone1:", "aliases": ":person_in_lotus_position_light_skin_tone:" }, "1f9d8-1f3fc": { "output": "1f9d8-1f3fc", "name": "person in lotus position: medium-light skin tone", "alpha_code": ":person_in_lotus_position_tone2:", "aliases": ":person_in_lotus_position_medium_light_skin_tone:" }, "1f9d8-1f3fd": { "output": "1f9d8-1f3fd", "name": "person in lotus position: medium skin tone", "alpha_code": ":person_in_lotus_position_tone3:", "aliases": ":person_in_lotus_position_medium_skin_tone:" }, "1f9d8-1f3fe": { "output": "1f9d8-1f3fe", "name": "person in lotus position: medium-dark skin tone", "alpha_code": ":person_in_lotus_position_tone4:", "aliases": ":person_in_lotus_position_medium_dark_skin_tone:" }, "1f9d8-1f3ff": { "output": "1f9d8-1f3ff", "name": "person in lotus position: dark skin tone", "alpha_code": ":person_in_lotus_position_tone5:", "aliases": ":person_in_lotus_position_dark_skin_tone:" }, "1f9d9-1f3fb": { "output": "1f9d9-1f3fb", "name": "mage: light skin tone", "alpha_code": ":mage_tone1:", "aliases": ":mage_light_skin_tone:" }, "1f9d9-1f3fc": { "output": "1f9d9-1f3fc", "name": "mage: medium-light skin tone", "alpha_code": ":mage_tone2:", "aliases": ":mage_medium_light_skin_tone:" }, "1f9d9-1f3fd": { "output": "1f9d9-1f3fd", "name": "mage: medium skin tone", "alpha_code": ":mage_tone3:", "aliases": ":mage_medium_skin_tone:" }, "1f9d9-1f3fe": { "output": "1f9d9-1f3fe", "name": "mage: medium-dark skin tone", "alpha_code": ":mage_tone4:", "aliases": ":mage_medium_dark_skin_tone:" }, "1f9d9-1f3ff": { "output": "1f9d9-1f3ff", "name": "mage: dark skin tone", "alpha_code": ":mage_tone5:", "aliases": ":mage_dark_skin_tone:" }, "1f9da-1f3fb": { "output": "1f9da-1f3fb", "name": "fairy: light skin tone", "alpha_code": ":fairy_tone1:", "aliases": ":fairy_light_skin_tone:" }, "1f9da-1f3fc": { "output": "1f9da-1f3fc", "name": "fairy: medium-light skin tone", "alpha_code": ":fairy_tone2:", "aliases": ":fairy_medium_light_skin_tone:" }, "1f9da-1f3fd": { "output": "1f9da-1f3fd", "name": "fairy: medium skin tone", "alpha_code": ":fairy_tone3:", "aliases": ":fairy_medium_skin_tone:" }, "1f9da-1f3fe": { "output": "1f9da-1f3fe", "name": "fairy: medium-dark skin tone", "alpha_code": ":fairy_tone4:", "aliases": ":fairy_medium_dark_skin_tone:" }, "1f9da-1f3ff": { "output": "1f9da-1f3ff", "name": "fairy: dark skin tone", "alpha_code": ":fairy_tone5:", "aliases": ":fairy_dark_skin_tone:" }, "1f9db-1f3fb": { "output": "1f9db-1f3fb", "name": "vampire: light skin tone", "alpha_code": ":vampire_tone1:", "aliases": ":vampire_light_skin_tone:" }, "1f9db-1f3fc": { "output": "1f9db-1f3fc", "name": "vampire: medium-light skin tone", "alpha_code": ":vampire_tone2:", "aliases": ":vampire_medium_light_skin_tone:" }, "1f9db-1f3fd": { "output": "1f9db-1f3fd", "name": "vampire: medium skin tone", "alpha_code": ":vampire_tone3:", "aliases": ":vampire_medium_skin_tone:" }, "1f9db-1f3fe": { "output": "1f9db-1f3fe", "name": "vampire: medium-dark skin tone", "alpha_code": ":vampire_tone4:", "aliases": ":vampire_medium_dark_skin_tone:" }, "1f9db-1f3ff": { "output": "1f9db-1f3ff", "name": "vampire: dark skin tone", "alpha_code": ":vampire_tone5:", "aliases": ":vampire_dark_skin_tone:" }, "1f9dc-1f3fb": { "output": "1f9dc-1f3fb", "name": "merperson: light skin tone", "alpha_code": ":merperson_tone1:", "aliases": ":merperson_light_skin_tone:" }, "1f9dc-1f3fc": { "output": "1f9dc-1f3fc", "name": "merperson: medium-light skin tone", "alpha_code": ":merperson_tone2:", "aliases": ":merperson_medium_light_skin_tone:" }, "1f9dc-1f3fd": { "output": "1f9dc-1f3fd", "name": "merperson: medium skin tone", "alpha_code": ":merperson_tone3:", "aliases": ":merperson_medium_skin_tone:" }, "1f9dc-1f3fe": { "output": "1f9dc-1f3fe", "name": "merperson: medium-dark skin tone", "alpha_code": ":merperson_tone4:", "aliases": ":merperson_medium_dark_skin_tone:" }, "1f9dc-1f3ff": { "output": "1f9dc-1f3ff", "name": "merperson: dark skin tone", "alpha_code": ":merperson_tone5:", "aliases": ":merperson_dark_skin_tone:" }, "1f9dd-1f3fb": { "output": "1f9dd-1f3fb", "name": "elf: light skin tone", "alpha_code": ":elf_tone1:", "aliases": ":elf_light_skin_tone:" }, "1f9dd-1f3fc": { "output": "1f9dd-1f3fc", "name": "elf: medium-light skin tone", "alpha_code": ":elf_tone2:", "aliases": ":elf_medium_light_skin_tone:" }, "1f9dd-1f3fd": { "output": "1f9dd-1f3fd", "name": "elf: medium skin tone", "alpha_code": ":elf_tone3:", "aliases": ":elf_medium_skin_tone:" }, "1f9dd-1f3fe": { "output": "1f9dd-1f3fe", "name": "elf: medium-dark skin tone", "alpha_code": ":elf_tone4:", "aliases": ":elf_medium_dark_skin_tone:" }, "1f9dd-1f3ff": { "output": "1f9dd-1f3ff", "name": "elf: dark skin tone", "alpha_code": ":elf_tone5:", "aliases": ":elf_dark_skin_tone:" }, "1f9d9-2640": { "output": "1f9d9-200d-2640-fe0f", "name": "woman mage", "alpha_code": ":woman_mage:", "aliases": "" }, "1f9d9-2642": { "output": "1f9d9-200d-2642-fe0f", "name": "man mage", "alpha_code": ":man_mage:", "aliases": "" }, "1f9d9-1f3fb-2640": { "output": "1f9d9-1f3fb-200d-2640-fe0f", "name": "woman mage: light skin tone", "alpha_code": ":woman_mage_tone1:", "aliases": ":woman_mage_light_skin_tone:" }, "1f9d9-1f3fb-2642": { "output": "1f9d9-1f3fb-200d-2642-fe0f", "name": "man mage: light skin tone", "alpha_code": ":man_mage_tone1:", "aliases": ":man_mage_light_skin_tone:" }, "1f9d9-1f3fc-2640": { "output": "1f9d9-1f3fc-200d-2640-fe0f", "name": "woman mage: medium-light skin tone", "alpha_code": ":woman_mage_tone2:", "aliases": ":woman_mage_medium_light_skin_tone:" }, "1f9d9-1f3fc-2642": { "output": "1f9d9-1f3fc-200d-2642-fe0f", "name": "man mage: medium-light skin tone", "alpha_code": ":man_mage_tone2:", "aliases": ":man_mage_medium_light_skin_tone:" }, "1f9d9-1f3fd-2640": { "output": "1f9d9-1f3fd-200d-2640-fe0f", "name": "woman mage: medium skin tone", "alpha_code": ":woman_mage_tone3:", "aliases": ":woman_mage_medium_skin_tone:" }, "1f9d9-1f3fd-2642": { "output": "1f9d9-1f3fd-200d-2642-fe0f", "name": "man mage: medium skin tone", "alpha_code": ":man_mage_tone3:", "aliases": ":man_mage_medium_skin_tone:" }, "1f9d9-1f3fe-2640": { "output": "1f9d9-1f3fe-200d-2640-fe0f", "name": "woman mage: medium-dark skin tone", "alpha_code": ":woman_mage_tone4:", "aliases": ":woman_mage_medium_dark_skin_tone:" }, "1f9d9-1f3fe-2642": { "output": "1f9d9-1f3fe-200d-2642-fe0f", "name": "man mage: medium-dark skin tone", "alpha_code": ":man_mage_tone4:", "aliases": ":man_mage_medium_dark_skin_tone:" }, "1f9d9-1f3ff-2640": { "output": "1f9d9-1f3ff-200d-2640-fe0f", "name": "woman mage: dark skin tone", "alpha_code": ":woman_mage_tone5:", "aliases": ":woman_mage_dark_skin_tone:" }, "1f9d9-1f3ff-2642": { "output": "1f9d9-1f3ff-200d-2642-fe0f", "name": "man mage: dark skin tone", "alpha_code": ":man_mage_tone5:", "aliases": ":man_mage_dark_skin_tone:" }, "1f9da-2640": { "output": "1f9da-200d-2640-fe0f", "name": "woman fairy", "alpha_code": ":woman_fairy:", "aliases": "" }, "1f9da-2642": { "output": "1f9da-200d-2642-fe0f", "name": "man fairy", "alpha_code": ":man_fairy:", "aliases": "" }, "1f9da-1f3fb-2640": { "output": "1f9da-1f3fb-200d-2640-fe0f", "name": "woman fairy: light skin tone", "alpha_code": ":woman_fairy_tone1:", "aliases": ":woman_fairy_light_skin_tone:" }, "1f9da-1f3fb-2642": { "output": "1f9da-1f3fb-200d-2642-fe0f", "name": "man fairy: light skin tone", "alpha_code": ":man_fairy_tone1:", "aliases": ":man_fairy_light_skin_tone:" }, "1f9da-1f3fc-2640": { "output": "1f9da-1f3fc-200d-2640-fe0f", "name": "woman fairy: medium-light skin tone", "alpha_code": ":woman_fairy_tone2:", "aliases": ":woman_fairy_medium_light_skin_tone:" }, "1f9da-1f3fc-2642": { "output": "1f9da-1f3fc-200d-2642-fe0f", "name": "man fairy: medium-light skin tone", "alpha_code": ":man_fairy_tone2:", "aliases": ":man_fairy_medium_light_skin_tone:" }, "1f9da-1f3fd-2640": { "output": "1f9da-1f3fd-200d-2640-fe0f", "name": "woman fairy: medium skin tone", "alpha_code": ":woman_fairy_tone3:", "aliases": ":woman_fairy_medium_skin_tone:" }, "1f9da-1f3fd-2642": { "output": "1f9da-1f3fd-200d-2642-fe0f", "name": "man fairy: medium skin tone", "alpha_code": ":man_fairy_tone3:", "aliases": ":man_fairy_medium_skin_tone:" }, "1f9da-1f3fe-2640": { "output": "1f9da-1f3fe-200d-2640-fe0f", "name": "woman fairy: medium-dark skin tone", "alpha_code": ":woman_fairy_tone4:", "aliases": ":woman_fairy_medium_dark_skin_tone:" }, "1f9da-1f3fe-2642": { "output": "1f9da-1f3fe-200d-2642-fe0f", "name": "man fairy: medium-dark skin tone", "alpha_code": ":man_fairy_tone4:", "aliases": ":man_fairy_medium_dark_skin_tone:" }, "1f9da-1f3ff-2640": { "output": "1f9da-1f3ff-200d-2640-fe0f", "name": "woman fairy: dark skin tone", "alpha_code": ":woman_fairy_tone5:", "aliases": ":woman_fairy_dark_skin_tone:" }, "1f9da-1f3ff-2642": { "output": "1f9da-1f3ff-200d-2642-fe0f", "name": "man fairy: dark skin tone", "alpha_code": ":man_fairy_tone5:", "aliases": ":man_fairy_dark_skin_tone:" }, "1f9db-2640": { "output": "1f9db-200d-2640-fe0f", "name": "woman vampire", "alpha_code": ":woman_vampire:", "aliases": "" }, "1f9db-2642": { "output": "1f9db-200d-2642-fe0f", "name": "man vampire", "alpha_code": ":man_vampire:", "aliases": "" }, "1f9db-1f3fb-2640": { "output": "1f9db-1f3fb-200d-2640-fe0f", "name": "woman vampire: light skin tone", "alpha_code": ":woman_vampire_tone1:", "aliases": ":woman_vampire_light_skin_tone:" }, "1f9db-1f3fb-2642": { "output": "1f9db-1f3fb-200d-2642-fe0f", "name": "man vampire: light skin tone", "alpha_code": ":man_vampire_tone1:", "aliases": ":man_vampire_light_skin_tone:" }, "1f9db-1f3fc-2640": { "output": "1f9db-1f3fc-200d-2640-fe0f", "name": "woman vampire: medium-light skin tone", "alpha_code": ":woman_vampire_tone2:", "aliases": ":woman_vampire_medium_light_skin_tone:" }, "1f9db-1f3fc-2642": { "output": "1f9db-1f3fc-200d-2642-fe0f", "name": "man vampire: medium-light skin tone", "alpha_code": ":man_vampire_tone2:", "aliases": ":man_vampire_medium_light_skin_tone:" }, "1f9db-1f3fd-2640": { "output": "1f9db-1f3fd-200d-2640-fe0f", "name": "woman vampire: medium skin tone", "alpha_code": ":woman_vampire_tone3:", "aliases": ":woman_vampire_medium_skin_tone:" }, "1f9db-1f3fd-2642": { "output": "1f9db-1f3fd-200d-2642-fe0f", "name": "man vampire: medium skin tone", "alpha_code": ":man_vampire_tone3:", "aliases": ":man_vampire_medium_skin_tone:" }, "1f9db-1f3fe-2640": { "output": "1f9db-1f3fe-200d-2640-fe0f", "name": "woman vampire: medium-dark skin tone", "alpha_code": ":woman_vampire_tone4:", "aliases": ":woman_vampire_medium_dark_skin_tone:" }, "1f9db-1f3fe-2642": { "output": "1f9db-1f3fe-200d-2642-fe0f", "name": "man vampire: medium-dark skin tone", "alpha_code": ":man_vampire_tone4:", "aliases": ":man_vampire_medium_dark_skin_tone:" }, "1f9db-1f3ff-2640": { "output": "1f9db-1f3ff-200d-2640-fe0f", "name": "woman vampire: dark skin tone", "alpha_code": ":woman_vampire_tone5:", "aliases": ":woman_vampire_dark_skin_tone:" }, "1f9db-1f3ff-2642": { "output": "1f9db-1f3ff-200d-2642-fe0f", "name": "man vampire: dark skin tone", "alpha_code": ":man_vampire_tone5:", "aliases": ":man_vampire_dark_skin_tone:" }, "1f9dc-2640": { "output": "1f9dc-200d-2640-fe0f", "name": "mermaid", "alpha_code": ":mermaid:", "aliases": "" }, "1f9dc-2642": { "output": "1f9dc-200d-2642-fe0f", "name": "merman", "alpha_code": ":merman:", "aliases": "" }, "1f9dc-1f3fb-2640": { "output": "1f9dc-1f3fb-200d-2640-fe0f", "name": "mermaid: light skin tone", "alpha_code": ":mermaid_tone1:", "aliases": ":mermaid_light_skin_tone:" }, "1f9dc-1f3fb-2642": { "output": "1f9dc-1f3fb-200d-2642-fe0f", "name": "merman: light skin tone", "alpha_code": ":merman_tone1:", "aliases": ":merman_light_skin_tone:" }, "1f9dc-1f3fc-2640": { "output": "1f9dc-1f3fc-200d-2640-fe0f", "name": "mermaid: medium-light skin tone", "alpha_code": ":mermaid_tone2:", "aliases": ":mermaid_medium_light_skin_tone:" }, "1f9dc-1f3fc-2642": { "output": "1f9dc-1f3fc-200d-2642-fe0f", "name": "merman: medium-light skin tone", "alpha_code": ":merman_tone2:", "aliases": ":merman_medium_light_skin_tone:" }, "1f9dc-1f3fd-2640": { "output": "1f9dc-1f3fd-200d-2640-fe0f", "name": "mermaid: medium skin tone", "alpha_code": ":mermaid_tone3:", "aliases": ":mermaid_medium_skin_tone:" }, "1f9dc-1f3fd-2642": { "output": "1f9dc-1f3fd-200d-2642-fe0f", "name": "merman: medium skin tone", "alpha_code": ":merman_tone3:", "aliases": ":merman_medium_skin_tone:" }, "1f9dc-1f3fe-2640": { "output": "1f9dc-1f3fe-200d-2640-fe0f", "name": "mermaid: medium-dark skin tone", "alpha_code": ":mermaid_tone4:", "aliases": ":mermaid_medium_dark_skin_tone:" }, "1f9dc-1f3fe-2642": { "output": "1f9dc-1f3fe-200d-2642-fe0f", "name": "merman: medium-dark skin tone", "alpha_code": ":merman_tone4:", "aliases": ":merman_medium_dark_skin_tone:" }, "1f9dc-1f3ff-2640": { "output": "1f9dc-1f3ff-200d-2640-fe0f", "name": "mermaid: dark skin tone", "alpha_code": ":mermaid_tone5:", "aliases": ":mermaid_dark_skin_tone:" }, "1f9dc-1f3ff-2642": { "output": "1f9dc-1f3ff-200d-2642-fe0f", "name": "merman: dark skin tone", "alpha_code": ":merman_tone5:", "aliases": ":merman_dark_skin_tone:" }, "1f9dd-2640": { "output": "1f9dd-200d-2640-fe0f", "name": "woman elf", "alpha_code": ":woman_elf:", "aliases": "" }, "1f9dd-2642": { "output": "1f9dd-200d-2642-fe0f", "name": "man elf", "alpha_code": ":man_elf:", "aliases": "" }, "1f9dd-1f3fb-2640": { "output": "1f9dd-1f3fb-200d-2640-fe0f", "name": "woman elf: light skin tone", "alpha_code": ":woman_elf_tone1:", "aliases": ":woman_elf_light_skin_tone:" }, "1f9dd-1f3fb-2642": { "output": "1f9dd-1f3fb-200d-2642-fe0f", "name": "man elf: light skin tone", "alpha_code": ":man_elf_tone1:", "aliases": ":man_elf_light_skin_tone:" }, "1f9dd-1f3fc-2640": { "output": "1f9dd-1f3fc-200d-2640-fe0f", "name": "woman elf: medium-light skin tone", "alpha_code": ":woman_elf_tone2:", "aliases": ":woman_elf_medium_light_skin_tone:" }, "1f9dd-1f3fc-2642": { "output": "1f9dd-1f3fc-200d-2642-fe0f", "name": "man elf: medium-light skin tone", "alpha_code": ":man_elf_tone2:", "aliases": ":man_elf_medium_light_skin_tone:" }, "1f9dd-1f3fd-2640": { "output": "1f9dd-1f3fd-200d-2640-fe0f", "name": "woman elf: medium skin tone", "alpha_code": ":woman_elf_tone3:", "aliases": ":woman_elf_medium_skin_tone:" }, "1f9dd-1f3fd-2642": { "output": "1f9dd-1f3fd-200d-2642-fe0f", "name": "man elf: medium skin tone", "alpha_code": ":man_elf_tone3:", "aliases": ":man_elf_medium_skin_tone:" }, "1f9dd-1f3fe-2640": { "output": "1f9dd-1f3fe-200d-2640-fe0f", "name": "woman elf: medium-dark skin tone", "alpha_code": ":woman_elf_tone4:", "aliases": ":woman_elf_medium_dark_skin_tone:" }, "1f9dd-1f3fe-2642": { "output": "1f9dd-1f3fe-200d-2642-fe0f", "name": "man elf: medium-dark skin tone", "alpha_code": ":man_elf_tone4:", "aliases": ":man_elf_medium_dark_skin_tone:" }, "1f9dd-1f3ff-2640": { "output": "1f9dd-1f3ff-200d-2640-fe0f", "name": "woman elf: dark skin tone", "alpha_code": ":woman_elf_tone5:", "aliases": ":woman_elf_dark_skin_tone:" }, "1f9dd-1f3ff-2642": { "output": "1f9dd-1f3ff-200d-2642-fe0f", "name": "man elf: dark skin tone", "alpha_code": ":man_elf_tone5:", "aliases": ":man_elf_dark_skin_tone:" }, "1f9de-2640": { "output": "1f9de-200d-2640-fe0f", "name": "woman genie", "alpha_code": ":woman_genie:", "aliases": "" }, "1f9de-2642": { "output": "1f9de-200d-2642-fe0f", "name": "man genie", "alpha_code": ":man_genie:", "aliases": "" }, "1f9df-2640": { "output": "1f9df-200d-2640-fe0f", "name": "woman zombie", "alpha_code": ":woman_zombie:", "aliases": "" }, "1f9df-2642": { "output": "1f9df-200d-2642-fe0f", "name": "man zombie", "alpha_code": ":man_zombie:", "aliases": "" }, "1f9d6-2640": { "output": "1f9d6-200d-2640-fe0f", "name": "woman in steamy room", "alpha_code": ":woman_in_steamy_room:", "aliases": "" }, "1f9d6-2642": { "output": "1f9d6-200d-2642-fe0f", "name": "man in steamy room", "alpha_code": ":man_in_steamy_room:", "aliases": "" }, "1f9d6-1f3fb-2640": { "output": "1f9d6-1f3fb-200d-2640-fe0f", "name": "woman in steamy room: light skin tone", "alpha_code": ":woman_in_steamy_room_tone1:", "aliases": ":woman_in_steamy_room_light_skin_tone:" }, "1f9d6-1f3fb-2642": { "output": "1f9d6-1f3fb-200d-2642-fe0f", "name": "man in steamy room: light skin tone", "alpha_code": ":man_in_steamy_room_tone1:", "aliases": ":man_in_steamy_room_light_skin_tone:" }, "1f9d6-1f3fc-2640": { "output": "1f9d6-1f3fc-200d-2640-fe0f", "name": "woman in steamy room: medium-light skin tone", "alpha_code": ":woman_in_steamy_room_tone2:", "aliases": ":woman_in_steamy_room_medium_light_skin_tone:" }, "1f9d6-1f3fc-2642": { "output": "1f9d6-1f3fc-200d-2642-fe0f", "name": "man in steamy room: medium-light skin tone", "alpha_code": ":man_in_steamy_room_tone2:", "aliases": ":man_in_steamy_room_medium_light_skin_tone:" }, "1f9d6-1f3fd-2640": { "output": "1f9d6-1f3fd-200d-2640-fe0f", "name": "woman in steamy room: medium skin tone", "alpha_code": ":woman_in_steamy_room_tone3:", "aliases": ":woman_in_steamy_room_medium_skin_tone:" }, "1f9d6-1f3fd-2642": { "output": "1f9d6-1f3fd-200d-2642-fe0f", "name": "man in steamy room: medium skin tone", "alpha_code": ":man_in_steamy_room_tone3:", "aliases": ":man_in_steamy_room_medium_skin_tone:" }, "1f9d6-1f3fe-2640": { "output": "1f9d6-1f3fe-200d-2640-fe0f", "name": "woman in steamy room: medium-dark skin tone", "alpha_code": ":woman_in_steamy_room_tone4:", "aliases": ":woman_in_steamy_room_medium_dark_skin_tone:" }, "1f9d6-1f3fe-2642": { "output": "1f9d6-1f3fe-200d-2642-fe0f", "name": "man in steamy room: medium-dark skin tone", "alpha_code": ":man_in_steamy_room_tone4:", "aliases": ":man_in_steamy_room_medium_dark_skin_tone:" }, "1f9d6-1f3ff-2640": { "output": "1f9d6-1f3ff-200d-2640-fe0f", "name": "woman in steamy room: dark skin tone", "alpha_code": ":woman_in_steamy_room_tone5:", "aliases": ":woman_in_steamy_room_dark_skin_tone:" }, "1f9d6-1f3ff-2642": { "output": "1f9d6-1f3ff-200d-2642-fe0f", "name": "man in steamy room: dark skin tone", "alpha_code": ":man_in_steamy_room_tone5:", "aliases": ":man_in_steamy_room_dark_skin_tone:" }, "1f9d7-2640": { "output": "1f9d7-200d-2640-fe0f", "name": "woman climbing", "alpha_code": ":woman_climbing:", "aliases": "" }, "1f9d7-2642": { "output": "1f9d7-200d-2642-fe0f", "name": "man climbing", "alpha_code": ":man_climbing:", "aliases": "" }, "1f9d7-1f3fb-2640": { "output": "1f9d7-1f3fb-200d-2640-fe0f", "name": "woman climbing: light skin tone", "alpha_code": ":woman_climbing_tone1:", "aliases": ":woman_climbing_light_skin_tone:" }, "1f9d7-1f3fb-2642": { "output": "1f9d7-1f3fb-200d-2642-fe0f", "name": "man climbing: light skin tone", "alpha_code": ":man_climbing_tone1:", "aliases": ":man_climbing_light_skin_tone:" }, "1f9d7-1f3fc-2640": { "output": "1f9d7-1f3fc-200d-2640-fe0f", "name": "woman climbing: medium-light skin tone", "alpha_code": ":woman_climbing_tone2:", "aliases": ":woman_climbing_medium_light_skin_tone:" }, "1f9d7-1f3fc-2642": { "output": "1f9d7-1f3fc-200d-2642-fe0f", "name": "man climbing: medium-light skin tone", "alpha_code": ":man_climbing_tone2:", "aliases": ":man_climbing_medium_light_skin_tone:" }, "1f9d7-1f3fd-2640": { "output": "1f9d7-1f3fd-200d-2640-fe0f", "name": "woman climbing: medium skin tone", "alpha_code": ":woman_climbing_tone3:", "aliases": ":woman_climbing_medium_skin_tone:" }, "1f9d7-1f3fd-2642": { "output": "1f9d7-1f3fd-200d-2642-fe0f", "name": "man climbing: medium skin tone", "alpha_code": ":man_climbing_tone3:", "aliases": ":man_climbing_medium_skin_tone:" }, "1f9d7-1f3fe-2640": { "output": "1f9d7-1f3fe-200d-2640-fe0f", "name": "woman climbing: medium-dark skin tone", "alpha_code": ":woman_climbing_tone4:", "aliases": ":woman_climbing_medium_dark_skin_tone:" }, "1f9d7-1f3fe-2642": { "output": "1f9d7-1f3fe-200d-2642-fe0f", "name": "man climbing: medium-dark skin tone", "alpha_code": ":man_climbing_tone4:", "aliases": ":man_climbing_medium_dark_skin_tone:" }, "1f9d7-1f3ff-2640": { "output": "1f9d7-1f3ff-200d-2640-fe0f", "name": "woman climbing: dark skin tone", "alpha_code": ":woman_climbing_tone5:", "aliases": ":woman_climbing_dark_skin_tone:" }, "1f9d7-1f3ff-2642": { "output": "1f9d7-1f3ff-200d-2642-fe0f", "name": "man climbing: dark skin tone", "alpha_code": ":man_climbing_tone5:", "aliases": ":man_climbing_dark_skin_tone:" }, "1f9d8-2640": { "output": "1f9d8-200d-2640-fe0f", "name": "woman in lotus position", "alpha_code": ":woman_in_lotus_position:", "aliases": "" }, "1f9d8-2642": { "output": "1f9d8-200d-2642-fe0f", "name": "man in lotus position", "alpha_code": ":man_in_lotus_position:", "aliases": "" }, "1f9d8-1f3fb-2640": { "output": "1f9d8-1f3fb-200d-2640-fe0f", "name": "woman in lotus position: light skin tone", "alpha_code": ":woman_in_lotus_position_tone1:", "aliases": ":woman_in_lotus_position_light_skin_tone:" }, "1f9d8-1f3fb-2642": { "output": "1f9d8-1f3fb-200d-2642-fe0f", "name": "man in lotus position: light skin tone", "alpha_code": ":man_in_lotus_position_tone1:", "aliases": ":man_in_lotus_position_light_skin_tone:" }, "1f9d8-1f3fc-2640": { "output": "1f9d8-1f3fc-200d-2640-fe0f", "name": "woman in lotus position: medium-light skin tone", "alpha_code": ":woman_in_lotus_position_tone2:", "aliases": ":woman_in_lotus_position_medium_light_skin_tone:" }, "1f9d8-1f3fc-2642": { "output": "1f9d8-1f3fc-200d-2642-fe0f", "name": "man in lotus position: medium-light skin tone", "alpha_code": ":man_in_lotus_position_tone2:", "aliases": ":man_in_lotus_position_medium_light_skin_tone:" }, "1f9d8-1f3fd-2640": { "output": "1f9d8-1f3fd-200d-2640-fe0f", "name": "woman in lotus position: medium skin tone", "alpha_code": ":woman_in_lotus_position_tone3:", "aliases": ":woman_in_lotus_position_medium_skin_tone:" }, "1f9d8-1f3fd-2642": { "output": "1f9d8-1f3fd-200d-2642-fe0f", "name": "man in lotus position: medium skin tone", "alpha_code": ":man_in_lotus_position_tone3:", "aliases": ":man_in_lotus_position_medium_skin_tone:" }, "1f9d8-1f3fe-2640": { "output": "1f9d8-1f3fe-200d-2640-fe0f", "name": "woman in lotus position: medium-dark skin tone", "alpha_code": ":woman_in_lotus_position_tone4:", "aliases": ":woman_in_lotus_position_medium_dark_skin_tone:" }, "1f9d8-1f3fe-2642": { "output": "1f9d8-1f3fe-200d-2642-fe0f", "name": "man in lotus position: medium-dark skin tone", "alpha_code": ":man_in_lotus_position_tone4:", "aliases": ":man_in_lotus_position_medium_dark_skin_tone:" }, "1f9d8-1f3ff-2640": { "output": "1f9d8-1f3ff-200d-2640-fe0f", "name": "woman in lotus position: dark skin tone", "alpha_code": ":woman_in_lotus_position_tone5:", "aliases": ":woman_in_lotus_position_dark_skin_tone:" }, "1f9d8-1f3ff-2642": { "output": "1f9d8-1f3ff-200d-2642-fe0f", "name": "man in lotus position: dark skin tone", "alpha_code": ":man_in_lotus_position_tone5:", "aliases": ":man_in_lotus_position_dark_skin_tone:" } }tdesktop-1.2.17/Telegram/Resources/etc/000077500000000000000000000000001326245125100177255ustar00rootroot00000000000000tdesktop-1.2.17/Telegram/Resources/etc/qt_linux.conf000066400000000000000000000000341326245125100224340ustar00rootroot00000000000000[Paths] Libraries=:/gui/art tdesktop-1.2.17/Telegram/Resources/etc/qt_win.conf000066400000000000000000000000601326245125100220710ustar00rootroot00000000000000[Platforms] WindowsArguments = nomousefromtouch tdesktop-1.2.17/Telegram/Resources/fonts/000077500000000000000000000000001326245125100203035ustar00rootroot00000000000000tdesktop-1.2.17/Telegram/Resources/fonts/OpenSans-Bold.ttf000066400000000000000000006665201326245125100234450ustar00rootroot000000000000000DSIGt:WtGDEF&SGPOS 77S8GSUB+=TOS/2u`cmap)/hcvt -fpgmsugaspS glyf%Khead<6hhea) Rt$hmtx$kernT+ ~qp6loca`+TVmaxp5 namef{"'postCl-&+prep k:9_< BJ   y X/\ 33f @ [(1ASC X ^ Ju+-X5?R!R=\?XR?=HuNJyNN#dH7HBHuR?XXX-f`w{dwBhP^w^wHh^) 3V1N 3BJLV\\\)Bqq}qB\\\y/B 7'h'RXJuRqhjd/RX=dm\X/;LH=qHu\9R . . Z=w{{{{*B9/^w^w^w^w^w^w    VVVVVVVV\\\\\qqqq\B\\\\\X\BBBBVVVw\w\w\w\\/1\{\{\{\{\{\wwwwBBq?qqBq+BqBhq}PqqcqmBBB;B^w\^w\^w\w\HHcHSh^\h^\h^\h^\)y/)y/)y/ B B B B B B171717VVV^w\h^\qT Hu JB`}D9{1^wBP3R^wN)\VmJ79-\qNBB)-\H\qN\B\BH\\y\9\N))V\mB )\)m{q)}jwh^B9hq)`9`} {/^`^ww)9\V?m?NHRV\P\qN)L\7\m/\ ?{J\B1\\qqq}B`RRRJT?%?!{5{bu ??!RRu wbf#R#B\?))9J7f : ; Z C;D9 )Xd%qLXXVXX))hq}^^N) T-;-+VyHTTq}) 3VrXy))wo\{\wbLf 7o) )qm^w\ w )\wo\w\wjw1\hu))Tj/y/;/^qN RP% 7w?\w\)f/ q)T/om{m?{TBJLm?{JhBVVVV{v\\\/^qN99^w\^w\^w\NHJ999m?{}?y/) V \\h\b\N9JND{XfwN\)//XsNVVVVVVVVVVV{\{\{\{\{{\{\{\BquBq^w\^w\^w\^w^w\^w\^w\wo\wo\wo\wo\wo\ B B1\- -11111 VV-f=BNdH7HB1)R)y/\\\\\n 0HI~'2a7 # O_?M   " & 0 3 : < D p y  !!!! !"!&!.!^"""""""+"H"`"e% IJ(3b7 #P`>M   & 0 2 9 < D p t  !!!! !"!&!.!["""""""+"H"`"d%aIvhcb]gD Xz}} Bvt n%"iOS0L\pr`<&'()*+,-./0123456789:;<=>?@AIJ$%TUVWXY\]^_`abcdefhijklmnopqrstuvhijkFopqrstu45]^@G[ZYXUTSRQPONMLKJIHGFEDCBA@?>=<;:9876510/.-,('&%$#"! , `E% Fa#E#aH-, EhD-,E#F` a F`&#HH-,E#F#a ` &a a&#HH-,E#F`@a f`&#HH-,E#F#a@` &a@a&#HH-, <<-, E# D# ZQX# D#Y QX# MD#Y &QX# D#Y!!-, EhD ` EFvhE`D-, C#Ce -, C#C -,(#p(>(#p(E: -, E%EadPQXED!!Y-,I#D-, EC`D-,CCe -, i@a ,b`+ d#da\XaY-,E+)#D)z-,Ee,#DE+#D-,KRXED!!Y-,KQXED!!Y-,%# `#-,%# a#-,%-,CRX!!!!!F#F`F# F`ab# # pE` PXaFY`h:Y-, E%FRKQ[X%F ha%%?#!8!Y-, E%FPX%F ha%%?#!8!Y-,CC -,!! d#d@b-,!QX d#d b@/+Y`-,!QX d#dUb/+Y`-, d#d@b`#!-,KSX%Id#Ei@ab aj#D#!# 9/Y-,KSX %Idi &%Id#ab aj#D&#D#D& 9# 9//Y-,E#E`#E`#E`#vhb -,H+-, ETX@D E@aD!!Y-,E0/E#Ea``iD-,KQX/#p#B!!Y-,KQX %EiSXD!!Y!!Y-,EC`c`iD-,/ED-,E# E`D-,F#F`F# F`ab# # pE` PXaYh:-,K#QX34 34YDD-,CX&EXdf`d `f X!@YaY#XeY)#D#)!!!!!Y-,CTXKS#KQZX8!!Y!!!!Y-,CX%Ed `f X!@Ya#XeY)#D%% XY%% F%#B<%%%% F%`#B< XY%%)) EeD%%)%% XY%%CH%%%%`CH!Y!!!!!!!-,% F%#B%%EH!!!!-,% %%CH!!!-,E# E P X#e#Y#h @PX!@Y#XeY`D-,KS#KQZX E`D!!Y-,KTX E`D!!Y-,KS#KQZX8!!Y-,!KTX8!!Y-,CTXF+!!!!Y-,CTXG+!!!Y-,CTXH+!!!!Y-,CTXI+!!!Y-, #KSKQZX#8!!Y-,%ISX @8!Y-,F#F`#Fa#  Fab@@pE`h:-, #Id#SX<!Y-,KRX}zY-,KKTB-,B#Q@SZX TXC`BY$QX @TXC`B$TX C`BKKRXC`BY@TXC`BY@cTXC`BY@cTXC`BY&QX@cTX@C`BY@cTXC`BYYYYYYCTX@ @@ @  CTX@   CRX@ @@ @Y@U@cUZX  YYYBBBBB-,Eh#KQX# E d@PX|Yh`YD-,%%#>#> #eB #B#?#? #eB#B-,CPCT[X!# Y-,Y+-,-@ !H U UHUPLOMdNLd&4U%3$UMLdLLF 3UU3U?KFFF#3"U3U3UU3U03UoTS++KRK P[%S@QZUZ[XYBK2SX YKdSXBYss+ss+++++s^st++++t+++++++++++++^u^{V+T$$$$Vzzp Kh}+y* }Hq R  5  Z y < { $ m:Xz:sQ?V0 eq:++\``fm.#D~5K`v  , = !! !2!!!!!!!""v"""""########$$$$$$$$%%%%%%%%%&4&&&&&&'>'O'`'p'''''''''( ((.(?(K(S(((()))$)0)<)M)])n)~)))))))**R******+ ++#+4+K+W+c+t+++++,,,,-,9,E,Q,,,,,- --*-6----...*.;./2/C/S/_/k/|////////0000.0?0~0001 11,1<1N1_1q11111111222"232D2T2~23X34 44/4?4J4U444455M5u5566Z6n6w666666777767>7F7N7V7777788D8L8q8y8889A9I99::!:1:A:Q:b:t:;E;<>7>>>?H??@@W@A$AvAAABBB)B;BBBBCCC!CCD&D7DHDzDDDDE(E0EEFF,FaFFFFFFFFG!G)G1GdGGHH]HHI;IIIJZJJK"K*K}KLL$L[LLMMMDMLMTM{MMMN%N]NNOObOOPCPPQQQmQuQ}QQQRIRRRRSS>SOS`SqSSSSSSSTT+TLTkTTTUUAUUVV^VWW W7WfWrWWXXXYYZWZ[([0[[[[[\>\w\\\]$]]]^(^`^^^^__?_a___``p``aLaaaaaaaaaaaaaabc\cmcuddKdddddee:eeeef`fgggg"gNgdgugggh)hhi(iij6jjk\klOlmn0nnnoSoop$~Pe~e:CgH9CM_V\q࿉TV]wYucVf=J,`5=@;Xj0]?3310#))Ry @  $??3333331073#&R1 ή27=d @  $??33333310#654'3d1:1?V5@    ?293393933393910%'%7)u!㜉'm)hy9w)hpX9 &@  /3332293333310!5!3!!#}}dz?/33210%#7!4|A$ =V/333105!=u9 @  TY ?+331074632#"&uZVS[\RT\TVXRO[YD@ ??993310!D!JJH (@  OY OY?+?+993310!"!2326&#"H5]nl`akm^|so  yN *@   ??3939339910!!77'3NMNMwNP=@  NYNY?+9?+9399333310!!5>54&#"'>32!Pod,aQUWlhtG}sn;XVNH\L)dteNB&[@/"  '(!$OYPY   $  OY ?+3?9/_^]+9+39933339910!"'32654&##532654#"'6!2UdoqH[o$O+6hsgVYl0;Ր#q F@"  PY ??39/3+3933393339910#!!5!3!5467#q" %4//i>RNkd5V@+  OYNY OY?+?+9/3+99399393993102!"'3 54!"'!!76fO^5({7#=O *5 BHP$A@! ""%&PYOYOY?+?+9/+99339910!2&#"3632#"&2654&#"Hon}GYWd ccjcd^}m` {k{zQw7P.@NY??+999393103!!%/HJ".S@), &  /0!!)) QY #QY?+?+93939939329399102#"$5467&&54$32654&'"6654&J|}nxhsrqOaMebNdɿpEXr̻}JOkV`cQCuBbQD<_2.`?EPBJ%?@  ##&' PYOYPY?+?+9/+99399210!"'532667##"&5432%"32654&JCT\j :r `lbd^}FPV[ë^L|j|{Pwus &@ TY TY ?+?+33331074632#"&4632#"&uZVS[\RT\ZVS[]QT\TVXRO[YTVXRQYX?s(@  TY /?+3323310%#74632#"&4|A$/ZVS[]QT\ TVXRQYXX9'@=/333233993310%59TX9$@ P`/33/]33233105!5!X%}X9+@=/3333339933105XT=J%D@"   &' ##TY# OY?+?+3/_^]99393331054676654&#"'6324632#"&RmiC`Vmd`3ZVS[\RT\J`PK^:ADb}ƥndGJ<D?tg610&RB)=9Jb)y&@ LY??+3933310!!!!!sPs^%@  LY?+?3333310# 5!3265^5N!}3 *@    ?3?3333393310!!!67991 @JM(\F@"     ?333?3333333933333310!!&'!!667!667!H 50 11+%* ,1),63ݢ9B37QNHV D@       ?3?3933332339933933310!!!!!V:V;5N5)+6@ ??3933939233310!!!1NP\Z/1q ;@ LYLY?+9?+999333310!!5!!!qVDs@  $?3?3329310!!#3s B@ ??333310!!!J3 @ $?3?33393103#5!!3qT=-@?39/32333933103#J}NH$?33310!5!NRL!@ @ /93210&&'5!?DV?,BeV;u"J@&  ""$# KY FYFY??+3?+9/+333392910!'##"&5467754#"'6323265f;M®evjaK 1QeXZzeB@!!    GYGY?+?+99??3339933102#"'##!36"32654&p31 kpqhkt^opsЏ{E!\s*@   GYGY?+?+333910 !2&&#"3267f šZH|>XKJ=-L%/2/$\q@@!  !   GYGY?+?+99??3333910"323&5!#'#'26754&#"o 2; hjumo}fqr23}bf쑥!\bsF@# JY JY FY?+?+9/+933333910"!&& 32!3267oanr6/ebP{qq{R*3*.(')u<@ FY GY ??+?3+3339333310!!#5754632&#"! ϨϞ{N\NA:yyRR/M1GO^} /@ IY GY?+3??+3333310"'53265!432#"FuTFIMG1pSSVT)kGOI@#    ??39393?3339333393107!!!!Ņ9XD1`TiJ??3310!!!1Bs#M@& % $  GY  ?33??3+39333933939310!!4&#"!33663236632!4&#"QWuj)-nY-nQWpoyy^MWNV'yys1@  GY  ?3??+93393310!!4&#"!336632V^r)3ryy^QS\s (@  GYGY?+?+33331032654&#"!"&5!2m{zkl{zl1̍0sC@"  ! GY GY?+???+99993333310"'#!33632"324&p+kiqhkte;J!R\qs B@!" ! GY GY ?+?+99??333399310%26754&#""3237!!47#otlo{kj< 1ۅ%14PT=kQTws%@    ?29??3393102&#"!3366>)%5-4s ^^s\s%?@ '&   FY FY?+?+993333339910#"&'53254&&'.54632&&#"zKUQ,lZy7ʿ\TLWz:L! (6`$-9&6\wWX$.I)<;5\x/7L=@   FY GY?+?3+3993333910%27#"&5#5773!!wPprX9I#3fA>^2@  GY??+9?33333910!'##"&5!3265!)1s1V^r1NUsyy^ ,@   ?2?3333393310!!367!V?$ (?V^yl`}^J@$   ?3?339933333933333310!#!!3677!3667!7Vt0 P  . +^LUVa]H, ^ L@$    ?3?3933333339933939910!!!!ZZ};#d^L@%    GY ?2?+3?39333333939310!367!#"'532677N  G'AOL7AQy"^Rpg[u cd77^ =@  FYFY?+9?+933399310!!5!5!!B Q6@ ! $ ?3?393333933339104춯463"&5}~cKJdW\XR>}FDռ" # DF}/?/93103#!R"6@ $#$?3?39333393333910#566554675&'4&'523"RcKvsJdQ{}-pr5DG+ Vk "+FD5sn TTRaX'9}7@  /32}/3333333993310"56323267#"&'&B7}6gIKb5~6eBxZC6m 7@9m%8u^&@  TY "??+39333103!#"&546323^ZVS[]QT\^1%TVXRQYXD@#  OYOY??99//99++9939333210%$753&&#"3267#3\ӲZH|>ytRd; A$-= RjX@,   QY NYOY?+?+99/3+39939933399102&#"!!!!56655#53546]sNTwgMR@YSۏN,rdq!'<@"  "() %/32993291047'76327'#"''7&732654&#"6[ji[55}_esT}6mPQoqONof_57Ynk\}}33{}]hMonNPnpq@8   RY RY  ??39/3+329+333339339933399310!3#3#!5#535#53!H9<\Zݲ/#@ ?/99//9333103#3#j)-8U@* ."'39:6611**%%KYKY?+?+993333993333333910467&54632&&#"#"'53254&&'&&7654&'yH=߶RDNQJcr}>?˒QF%ZP߂tNe%5%O(TT 3.01J-@mS(iJO)9u'03"JCh.9YD^1O  @   /333932104632#"&%4632#"&K@BKLA@KQ?^\X9 6@   /33333/39333333310!5!3!!#5!}}}{/J(@   ?39?399333310!576654&#"'632!yf90(Qc{^i`JdY2&(XuUu_;9%>@    &' # !?3?39/399933933910#"'53254&##532654&#"'6632^h{XNp\SQ23/T9e>g7 nOyFZk5549&2&(/>L!@ @ /93210567!L?V4Ge42^?@   GY ??+99??33339339103265!#'##"&'!!X^~r1+*xX>h 1yyUU.,UJq/@ MY/3/+9/932910####"&563!>T\P3u)} @   TY/+33104632#"&uZVS[]QT\TVXRQYX4@      ?33/99|/9339310#"'53254'73ND[HNJXr>S=e\JH *@    ??3939339910#77'%3H0Nm-Jp_$*=9 @  ?32993310#"&5463232654&#"ᷟ#AHH??HHA\ŧŦdeeddccR^ Z@*       /3339/33393333333223323310'7'7ss#;w\\w9;w\\w9.&{'< ?55.&{'t?5Z&u'< -?55=y^'F@$"() %%TY% OY#?+?+3/_^]3939333103267#"&54676655#"&54632Ylm9WYO`fbja_5(ZVS[]QT\^JbMNX?9J:*8EliFJ=;VTVXRQYXs&$CR&+5s&$vR&+5s&$KVR&+5`&$RVR&+5V&$jVR #&+55 &$PuX #?55%o@<  LY LY      LYLY?+??+399//_^]++3333393339310!!!!!!!!!!#%3z\`Nw&&zs&(CR&+5s&(v\R&+5s&(KR&+5V&(jR !&+55*s&,CR&+5B.s&,vR&+5As&,K"R&+59V&,j"R !&+55/u H@$    LY  LY LY?+?+9/3+333333333103! !!#%4&##3#3 /fe|bңRdT`&1RR&+5ws&2CuR&+5ws&2vFR&+5ws&2KR#&+5w`&2RR&+5wV&2jR +&+55  @  /2229333107'՘-1-Ӗ-+ј-՘w"9@  #$! LY !LY?+?+9999339910!"''7&!27&#"4'3 ŋZZiQƒTX8Ti3 LhszAlmF}ht-u'^s&8C+R&+5^s&8vR&+5^s&8KR &+5^V&8jR (&+55s&<vR&+5 6@  MY MY  ??99//++33333310!#!!3232654&##6 Dd|dt%&@aL:0*4([bzN"$2{)3<*HwQ@j17P.^;yxHN!R&\j ,&+55&$MXR&+5V;&DM &&+5}&$NVR&+5V;+&DN &&+5&$Q{VLu&DQws&&v R&+5\!&Fvo&+5ws&&KR$&+5\!&FK#&+5wf&&OR&+5\&FO;&+5ws&&LR&+5\!&FL&+5us&'LhR&+5\%&G8o/u\ (`@1  *&) #GYJYGY?+??99//3+3+993333333399910"323&55!5!5!3##'#'26754&#"l5 ;2@ hLnieoSd  TPe3ǡT{&(MR&+5\b&HM&+5}&(NR&+5\b+&HN&+5I&(O/5&+5\b&HO?#&+5&(Q5\(bs&HQ?s&(LR&+5\b!&HL!&+5w's&*KR(&+5m!&JKN&+5w'}&*NR&+5m+&JND&+5w'f&*OR"&+5m&JOH&+5w;'&*9m!&J:ZE&+5fs&+KR&+5&KK5 ## ?5Q@( LY     ?3?399//33333+333333333333103#!!!#535!!5!5!fʸ6C5w-ôT@*  GY JY    ?3?99//3+3+9333333933910!!4#"!#535!!!3632ϴsϜ1;fP/ǡS.`&,R"R&+5&R&+5?&,M$R&+5&M&+5}&,N$R&+5+&N&+5B&,Qw+&LQ!Bf&,OTR&+5^??3310!!!1^BR &,-)&LMJhR s&-KR&+5}!&7K&+5;P&.9;&N9u^A@    ??39393?333333293107!!!!ύ:EHƏ1FnZ^ۡR?s&/vR&+5&Ovg  ?5;?&/9Jc;&O9?&/8u ?5&O8?&/O/p&OO8? E@!       LY?+?993993993333333103'7!7!Eq6uQ)oXĞX K@#    ??99//33933333999999107!'7!FuGq1+ph+p-s&1vDR&+5!&Qv&+5;&19;s&Q9us&1LR&+5!&QLN&+5'QRH@#   LY'?+3?33?39932933999910"'3267#!!3'&5!rS]Imv {R[SN}PmJsA@!  GYGY?+3???+933933910"'53254#"!336632=kM;<{r)2tʼ۫^OUw&2MR&+5\&RM &+5w}&2NR&+5\+&RN &+5ws&2SBR (&+55\!&RS{ ,&+55wP#e@6  %$LY   LY  LY LYLY?+?+?+?+9/_^]+333339333310!!# !2!!!!!"3267&&P&-S>=#d3Az&# ik \{s+2p@:/0&&004 3/JY//,JY )GY FY#GY?+3+?+99?+9/+9333339399910 '#"&5!2632!326732654&#"%"!&&pGkdQfm{zkl{zl^| u-OM*.'(Esyo}Hs&5vR &+5!&Uv&+5;H&59c;ws&U9Hs&5L3R&+5S!&UL&+5^s&6vNR0&+5\!&Vv .&+5^s&6KR5&+5\!&VK3&+5^&6zb\s&Vz-^s&6LR-&+5\!&VL+&+5);y&79)/;7L&W9)ys&7LR &+5/(&W8?5)yF@#   LYLY ??9/3+3+39333339910!!#53!!!3#sPsTb/7L]@/     FY JY GY?+?39/3+3+39933399333910%27#"&55#535#5773!!!!wPp4IX9I#fƔA>^`&8RR&+5&XR1&+5^&8MR&+5&XM/&+5^}&8NR&+5+&XN1&+5^&8PR &+55&XPN &+55^s&8SR %&+55!&XS '&+55^&8QH^&XQs&:KqR+&+5!&ZK+&+5s&<KR&+5!&\K$&+5V&<jR &+551qs&=vNR&+57!&]v&+51qf&=OR&+57&]O&+51qs&=LR&+57!&]L&+5? "@  GY??+333310"!4632&Pϼ͞xG\-\//I@$   FYGYGY?++9/3+39333333910#"'5325#5754632&#"3#鼰kM;;}–pHR?myqRR/F!-w@;! "(@ ((  /.LY +!%?3?392239/+3333933932932999910!!!&54632&'5667!4&#"366/jl+pm1fVDpL.jVl6**7V&2U>J#:Wn-!EG*x t7-33-\3V;",8D~@?'(#,##3--9?3??  ""FE,66'<0B@ KY FYFY??+3?+9/+239333392993293210!'##"&5467754#"'63232655667!#"&546324&#"326f;M®evj.jVώppqn6**710*6aK 1QeXZze/*x iDlnli-33--44%s&vR&+5V!&vA&+5ws&v1R+&+5\!&v{,&+5^;&69\;s&V9! *@  /22933333310&'#567!TMN˽CeH]SQ_p4F!*@   /32933333310!&&'5367Tlw˓RT!T2/{]SWY/3910!!Z+ &@ @o /3]293210#"&'3327 ܦ/UU+/6} /3910432#"SSGOTJ  @   /33/]39310#"&546324&#"326Jppqn6**600*6lnli-33--44  ?3/9910327#"&54673-#7o- J8-lU)56m+QD^@  /93210667!#5 Nm6T 2@  ! /33399939323104632#"&%432#"&'667!#G:9JJ9:G#9JJ9+5u'{B'(T 2>+5'+T 2>+5',T 2>+59&2RT 2 >+5'<T  2 >+5l&vZT $2$$>+5&U "&+555$%T@LY??+33310!!TJ9 ((1q=f+wG@% LY   LY LY?+?+9/_^]+33333310!5! ! 32654&#"3iQQeϿfzmm|B,P.3 /@  LY ?3?+333393310!!!&& 3 R <>)Jo01R? C@#  LY    LY LY?+?+9/_^]+33333310!!!!!Rdw=Hw2=#@ LY?3?+333310!!!!!=L3NyS@)      LY  LY?+99?+93993339939310355!!"'63!N5J3#-  = )y7<\"+V@+""+ -',+ +MY! $$MY    ??99//3+33+333339333331032654&##5#"$546$335!32###"33)衏55)9V;m@@  MY ??339/3+33333933310!#!# !33!3265!33"#L !7 W@+  " !  LY  LY?3+3?+993333333333993310"!!&54$3 !!654&%Ąs<?yv}H]AƸ`H9V&,j"R !&+55V&<jR &+55\^&~T13&+5N%^&T%/&+5^&Tw&+5^&T &+5&UD *&+555\q *K@&)", +GY%HY%) GY ?+93+?+9?3333339910%26754&#""323673327#"&'#ovko{kv2+ 'T [pv"nߏ 00TT^7ahv MZ)X@," '' +*"##"GY## GY GY?+?+9/+9?333393339102#"'!4$"32654&##532654&~0<|H5cnnй?4'|pnsmf\d^<@  ??33333333939310!47!3667!8,V=B9=c-6VR>I,Yt\)k@5!!$+$*$$!''GY!!   FY?+3?99323+9933333993393910&54632&&#"!"$5464&'326oэy\XIJC_i{xioz-B-76.6iF^Ҷ]:#~e}N%s&b@2## ('&&JY& &&   FY FY ?+?+999/_^]+933333933910#"!267! $54675&54632&&#"3H gY瀐sX^wMqnAH}-)Mk 1э.&026B7\ P@' " ! !FY"??+99339333333399310!6654&'$4%#!5!EOOfH43V'IgA%]/ )M~ ߶JZ5 !}s2@  GY  ????+93393310!4&#"!336632V^r)3ryyy^QS\+ ?@ FY FY  FY?+?+9/+33333310! ! 267!"!&&  uk7iyln i kui5^ !@   GY ?+?33910327#"&5I#3^!"b@2 ""$"# HY HY ??+?+393333339939393103'&&#"5632327#"&'&'##$\_24OWss3%L7!$r'n)r+ .!\ZJ Fhb lwC4L^ws^ *@  ?2?9323393310!36!!9Esf4X^+\.e@2%&&))#  ##0/!++/JY &" JY ?+33?9/+9333339933339333104675&5467##5!#"33#"!6654&'&&\ۇC$K끓/aEOOf~6 4k% ҉u_R{{GU5!}f]/ )&\sR^B@!  FY GY?+??+333339933910%27#"&5!!#57!#C?)66#Buf31ys/@  GY GY?+??+3333310#"'#!32%"32654&ښqj+t#3f^-@  GY?+?333333310 !32654&'!2ir}r+3( `kַv\w #P@)## %$FY FY"  ??3+3?393?+33339333104&#"66$5474632^Z9@nxZJֺNOa a#zy4 m X@,"!  HYHY?+3??+3?93333333339333102!327#"&'!&&#"56ZrP)J39F=14Un}4hF88;rm3q{%@5 Fu`F>FA@   FY ??3+3?3?33339333106654!!$!PN#<& !3ŭ ;m{^'?@ %%) (! FY?3+33?39/333393910"&'##"47!3265!3265!z) .w0@%}c`SLLT^d}%@1iind.Ѥt'هs3 &j #&+55&j' +&+55\^&RTB"&+5^&TN&+5m{^&T50&+5V&(jR !&+55)R@*   LYLYMY?+??+39/+3339392910"'3266554&#!!!!!!2mtWcI62S_ZJ\&+D7YG^νTs&avR&+5w#?@  LYLY LY?+?+9/+33339910"!!327# 4$32'&J y ɼjzMloWɿM(#jW70%<^6B,9V&,j"R !&+55hR-#Q@* %$#LY LYLYLY?+?+?+9/+33399333103 !!!'"'5326!32654&##s'i>_T@:35>7[ X^HcaW Hefc[R@*   LY LY LY?+??39/++33333933310!!!!!!!3 32654&##i#65s'X^Hw=aefeY)F@#   LY LY ?3?+39/+333933910!4&#!!!!!!2FPZJYG^Ѻ`s&vR&+59&6^R&+5V= 2@  LY'??3+?333339310!!!!!!!=TR65VL$ ?@  LY LY LY?+?+9/+333339103 !!!!32654&##z8VhO7HefeY%Ta V Q@(     ' LY LY?+?+33?33333939393103!3!!!! q)Tl ];"COLTV^(T@(       ?33?339333333332333393333310!!!!!!?!@<<B^&J@% !(!'MY $$MY$ MY ?+?+9/+93339933910! '3 54&##532654&#"'6$32ȫ^nq{ԅ}`O-3ahXfKYwSM,@  ?2?39933339910!3!!4#! sZ>VJ&6R&+5` 4@    ?3?39333233333310!!!!!`6 J<B=1@  LY LY??+?+3339310!!!'"'5326!=>_T@:35>7[ caW 0f+w2=n3w&)y79E@!    LY?+?39333393939310#"'3267!379@    LY '??+33?33/3333339310!!3!!!!!5"68@`LJu A@!  LY LY LY?+?+9/+33393310!!!!3 32654&##uV{8/hPaefeY A@   LY LY?+?39/+?333339310!!!3 32654&##!!p6d5NQC5beffXy 2@ LY LY?+?9/+3333310!!!3 32654&##V6z80hO7aefeYHI@&  LY    LY LY?+?+9/_^]+33333910"'63 !"'3267!5!&&)c]bEc x8'gq}KMQ@+    LY LY    LY?+??9/_^]+?+3333393310! !!!!! 3 4&#"6"I<N+LwM>!3x Q@(   MY LY  ?3?+9/+933339339310!&&54$!!!#"33| ʙx12юJ1VdapV;uD\%#D@"% !!$ FYGYHY?+?+9/+933333310%6%36632! 2#"\%7,#~|=5d16kY5(1P{RXox+#2Q)^ N@'" !JY JY  JY ?+?+9/+93333393910#!!24&##3264#!326qnw=ffaead9Zc^B;If8^@FY??+33310!!-^^o1^I@%   FY # FY ?+33?3?+333399339310%#!!!36!3\WMw^`op$\bsH^R@'      ?33?339333333223333393333310!!!!!!;dVVd;?77FN#s(L@&'  #*)('('JY((  FY FY ?+?+9/+933399339102654&#"'6632# '532654&##5jzMPZw߉uV`v8=66&!-'9 "}efVE(.C>DA#^ ,@     ?3?39932339910!!47!o^FFw^#?&6&+5^ 6@   ?3?39333233333310!!!!}PE71^7^^5@  FY  HY??+3?+3339310!!!#"'5326! \|jD119M=Ny O!^:@     ?33?3993323393310!!#&''!!>!!6++13 !%,q>l nD^#MGn^ 3@  FY   ?3?39/+33333310!!!!!1V^R3^\sR^#@ FY?3?+333310!!!k^y^sS\sF/=^(@ FY??+3339310!!!5=^y^\\'P@( ! FY FY ??3+3?3+3?333393333310!$54%!4&'66>4&ŚXd/%d ^[od^ 4@   FY#??+3?333339310%3!!!!!N12^y{^-@ FY  ??39/+33333103267!!#"&5XM1jU^g( 8.!^ 5@  FY?+3?3333339310!!!!!!!!1w1w1^yyo^>@    FY#??+33?33/3333339310%3!!!!!!!!1w1w1^yyf^ A@!  JY FY JY?+?+9/+333933103 !!!54&##325gh^PyA:-^ C@! JY JY?+?39/+?3333393103 #!!4&##32!!ѓ11Xhg1^A:^^ 2@ JYJY?+?9/+33333103 !!!4&##321hg^A:Js?@   JY FYFY?+?+9/+33333910"'53267!5!&&#"'663 ҆nx ZkdwVK^EP{|?#-sI@&   GY FY  GY?+??9/+?+3333393310#"$'#!!36$3232654&#"1%bqobcpob13^R^ M@&     JYJY ?3?+9/+933339329310!!&&5463!!#33#"J-loϨnYKU-sbFOI\b&Hj 1&+55&j@6$$$('' GYJY  GY?+3??99//3+3+93333399339910"'53254&#"!#535!!!3632=kM;<{^VsϜ1;fʼnn/ǡS!&v&+5\sA@! JY FYFY?+?+9/+333339910 !2&#"!!3267!Xkis[ngOf#* JAz}˃}$,I\sVL&j &+55}M^Q@* ! JYFY HYJY?+?+?+9/+3339933310!##"'5326!32!4&##32- \|jD119M=#bay OP`A:^Q@)   FYJY JY ?3+?39/++3333393331032!!!!!!!4&##32^;1\1Dba3^RA:V@+   GY JY    ?3?99//3+3+9333339933910!!4#"!#535!!!3632ϴsϜ1;fP/ǡS!&v&+5?&\6&+5o^ 6@   #FY?+3?3?333393103!!!!!12x^yo}#@ LY??+333310!!!!! q6#@ FY??+333310!!!!!^1s&:CR&&+5!&ZC&&+5s&:vR&&+5!&Zvd&&+5V&:joR 3&+55&Zj 3&+55s&<C|R&+5!&\CY&+5R/333105!R\R/333105!R\R/333105!R\1N @  /39/3233310!5!5!5!NRR1@  ?33210'673'e5B#[q@ ?33210#72~E(?@ /33210%#7!4|A$ @  ?33210#&'7?%@;a Uw%@  ?323322210673!%673!e5B#e5B#[q[qw #@    ?323322210#7!#7!2~E2~E((? "@    /323322210%#7!#7!4|A$4|A$  { N@%     ??9/3339933339333993310%!5!%77777LB_{}@>           ??99//3339933333993333339333933333310%%!5'75!%%oK78L//L87K/-yxb)  /93104632#"&b욣ub9 #,@ $ TY! ?33+3333221074632#"&%4632#"&%4632#"&uZVS[\RT\GZWS[\RU\HZVS[\RT\TVXRO[YQTVXRO[YQTVXRO[Y?  "-7BCd@1.>8388E D)###C5 @+1;& ?32???333223/3329333293332103254#"#"&5!2%#3254#"#"&5!23254#"#"&5!2;-2``2-Y+-2``2-YP,2``2,X5}{}J}{}}{}j BR^0@/39/39333333210Rs=wwR^0@/39/39333323310'7s#;w\\w9u'Hw@ ??33210#+Jf *@     ??39299339104&#"#3363 D<9ZHǢIL@`qTe/#'V@+  RYNY NY ??+9/+9/3+333333333910!!!#53!!!!<ϕoRj%y@=  " &' ! RY !RYNYOY?+?+99/3+32+3993399333333939102&&#"!!!!!!5667#535#53546]NEPLggFJdKR#VVqsJl'*jUss)q@< #''% + +*QY NY&#&QY#!PY # # #NY??+99//3++3+?+3339339933391032654&##!#!! 27#"&5#5773!!B~T5uNSaXHhumh#3>lgGD==DG>Zud7f@IItZOJĨǤdeeddcc82)f_+-)$F@ "  %&""   /3/9/93399399333339310%2673#"&5556746324#"66+5X9Z@-       /3333322323333399392910'7#5!7!5!3!!1YPP`\OT۪V٪V9= :@     /2/9/339333333331035!5VTX9= 6@    /2/39/39333333331035! 5XT>JXP B@    ??9333993393333310#3P=r=r!fg)&IL)&IOh3? @  /3293210#"&'!32673Ysec ?gS[_}^ !@  GY?+3?33310"'53265!FuTFIMG1VT)^@  ?93210667!#^'PV1@^;@  /93210667!#^'K[V1@N!@  /93210!5673'NX1@)5  @   !?3?399331032654&#"!"&5!2%-12..21-X}|{{}3 J B@     ??9/3392999333399310##5!533!547}} 5ᗗAͤVblT9L@$  !?3?39/3393993939393102#"'53254&#"'!!6d27WQ?8m%84 *?@+-58@   !?3?39/39993399310#"&5%366322654&#"ٯ#C YJt;><9Dm5W+/G?6DjBT;J(@ ??399939310!5!TMJ)-5!-F@!%+%+  ./ (( !"?2?393939933993399102#"&5467&54632654'"6654&CLKB#GW:9;(A+B BA>=21 , 84 95!/333333333/3339333333333333339/333/39/3/339/393/399333333333933939332933333333333310!#%5!#533!5353!5!!5!5!#3#35!#35!35!#35#3#3#"&546323254#"%32##32654&##32654#"'53253T/0momImmmm0oowoooomm~smp.,;0m^{B.$*/;J1%Z^4+V}i0oo/mmmmmmoo;mmJoooo/yhIaCS1D D8QYb" "+%J fVr_cT*X@)%  +, (("""//99//3333/9933999939333210 54676654&#"63232654&#"TV,AgIOGRZ?>1HT;GFBIHCHEVW/2A1R~X8*P:/5K6DpJ;?HI>@IH}!&7L&+5))6v@="4 1 1*..((6  78GY44 **FY11FY1%FY?+?+9/+339/+9933939399333310&&#"!"&54654&#"'63232655'&$&546323e;F *0LZga\Ȣ,ߩ89r+-5i+*V^X?GKO!tьF@"   MY?+3??9333333393310>32&#"!!}>xhu_UB,(5C6PTB +'`/3^(l@5 ##  * )!!  FY &FY?3+39?+339/3339939393993310"'##"547!57!!4'!32655!326FS R??3@>\gTLLTg\fѲɰssu&0vT&+5B!&Pv,&+5&$[sV;u&D[r9&2R\XN  @  /39/393210#"&546324&#"326Nppqn6**600*6lnli-33--44yh+1@ ?2939392210467#"&767!#yIE%-%?BCJZ**RpJ%%-J'CW^)&I'IL/)&I'IO/wH@#     LY @ LY?+?+993/3393339910! ! 65!3 !"jRZ]-${=պszmk=2.\"#H@# $#   GY@ GY?+?+993/3393339910!"&5!265!32654&#"pG-Wi4m{zkl{zla1̍0EE-k}()9@  @LY?+?39/3/33323310665!# 5!3265^JF- k5jg¢!iJsJ@%  @KY GY??+9?39/+/339323310!'##"&5!3265!665!3)0s1V^r1GN- lMVsyyutfW!C!vR /23910#'6654&#"56632 K6*"AJi)Ϝ)G 3% " oR%}/10432#"٦TR疖GNs&(CzR&+5s&CTR&+5\b!&HC$&+5#!&C &+5w=2b@1++((00 4#3))  MY-&&LY ?3+39?3+3339/33339393310"'663 !"&'# !2&&#"327!3254&'ZDl@K )tMMt) JBlD[&SU6Hl!-1g]4^dSl"}O@(  MY LY   LY?+?99//+3+3333333339103 !!!5!5!!32654&##5{8V5yiPd7defeY'J@&  FYJY JY?+?39/++3333333339103 !!!5!5!!4&##3231g9hgyˤyA:R"U@,!$#LYLY! LY ?+??9/3+3?+333399339910!3267# #!!3!2&&#"!f Ъarhw6,|$dZWdw(%(#N=:g':s W@- " ! FY  FY FY?+??9/3+3?+333393339910"$'#!!36$32&#"!!3271[MVxdkZ ts3^R,$?qp~PE E@#     MY  ?33?39/+3333999222210!#!#!!!'df-{/u9 wwDdYE4^ L@%    JY  ?33?39/+3333393333999910#!#!!!!'&'bNPo #HZZ^uPW] t@9     LY  ?3333?9/+3393?3333339/39333399999910!#!#!!!!!!!'&'dg6{/u93\ wwwCDd'47^x@;  FY   ?3333??39/+339333339/339333399999910#!#!!!!!!!3&'oBBn#`553^RDI)F@B    LY MY ?22?9/33+39333+93333393339393393931036675!!&&'!!):9{)TCGV({ų$Ջ+%;|dqe{{9^@B    JY JY ?22?9/33+39333+93333393339393393931036675!!&&'!7!u(}[4Yy*t^8/6<^hZ} mjj {'MB ? DNm"#@V"  ! % $#% " "LYMY LY   ?333??9/39/++39333+933333233933393933939310!67!!!!5!!&&'!!P,36C9{)TCGV({3+%;|dqe{{9L^"#@U"  ! % $#% " "JYJY FY  ?333??9/9/+3+39333+933333233933393933939310!67!!!!5!!&&'!7!#u 3Yz)u^J0>]_\b)YhZ6ì{ԅϾSw`6NWe "')X)enahXfKYw6Lw(dN./#dL@Y4* 99 GEECCIIIB@ *0N?@@**M?{H3"6TJ~iholaKYUON0V4D|턄vpjzMPZA>]W9RVcd o"a_{ "}e'44*)@ADA8=66&!! l_)fL-muFw ?@ LY LY  LY?+?+9/+33339910! ! 267!"!&&iQQeHzmm|ۮ\s ?@ JY GY GY?+?+9/+33333310!"&5!2267!"!&&ap>ndbpm1̍0ttttqppqA@  LY ?3??+933333393310"!!67>32&B.@*9!+67\xVtF3GvsK'f@@    GY ??+?3933333393310!367>32&#"!?2 ,{3LkULH+' 3^Oo|Xj1,7s&v'R )&+55!&v *&+55w ".k@7  0)0#/ ,LY &LY  MY ?2?+3?393?+?+33222333939310!367!#"'532677! ! 326&#"N  G'ANM7AQy"LECK ^Rpg[u cd7{xxv\ )s"0k@7  2##2**1- GY-&GY&  GY ?2?+3?393?+?+33222333939310!367!#"'53267732654&#"!"&5!2M  H'AMO9@Qy";m{zkl{zl^Kwg[u cd7)̍0w91(P@*#'  !!* )#' 'LY@ LY?3+3?3+3333393910#"'$%66326632$%#"'9#qv.$D=5H1}E0-E'ff)(ss$FC{&<2,B&%**JMKKM\+T@, $$** !!- ,'$**FY@ FY ?3+33?3+33333393910#"&'&54763266326654&'#"&' H69G nj99+>SOPI>=6D1&5:;6'%#!RR">'+!3~/816Aw=1GY@PLO=cGRKNxRw>/-!pcenA6) 3;1bt6&-&]V:5:Z\R)@R@RDH54MH+AAHH# (##TS@,AHHEPEDDP,P,P15511;@   FY (@FY&?2+39?3+33323/99//9/3933333393339332910 32&&#"3273265#"'632!"'#"'&&#"#54>32356654.54632f|V?B%wlmv'A>V||z&g2.+ ":fT:pwN2B%,%NGNT)&< V$&-&^V ;4:Yw=B2@@H?<;4788;;++((00 B#A4??@95=<8<<@@))  MY-&&LY ?3+39?3+3339/23229333339393322310"'663 !"&'# !2&&#"327!3254&#'##'##'5'ZDXcGRKNxRw>/-!JT oYggggY*@A  )&%!""%%    ,+))&#''"&&* ?223?3323333933333939339993393229910!!366!36!!#'##'##'5f@% ? j`4ㅘ'R7117117P^ov6u>?;/XffffXw#3@  LYLY?+?+?3339310 4$32&&#"!267!ZMe[Z:NjWg':\s3@ GYGY?+?+?3339310&!2&#"327!Z!Xh~rw}* PB% hy ?/9910%'%7%7%LG㴁FGJ{J;{Z}9IĤ{{ @  /393210#"&543!632#6083m bm69+3G8u^s9H @  /29/392102>32#&&#"##5Nwp:in? +.IJ%-&6ua1;47X$@  /393939102&&546oGN%-%D1~UX:5 : V`MYX$@ /3939391056654.546320E%-%NGNT^V ; 5:Y) (6DR_m@JP4,H,,ck:&B&&^ k^VkVnod^WjgS``gIA;O7EE>LLZ-%3))"00ZZgg  /32/329/39/3332229/3332223322299932932932102#&&#"#62#&&#"#662#&&#"#66!2#&&#"#662#&&#"#66!2#&&#"#662#&&#"#6!2#&&#"#66]qOb@3/2),!$6::$,2?@ #037;(++;3# /3/393333339993333333210#6736673#&'5&&'5'766'677'&&&'7BF$a5Ia4GA݁ZBO݁ExbCExbC{LbRC&b'Z1BO݁GA܂!Ia5F$a5DnXb'XDnXbYFcbxF24bEV+!"[@-! #  " LY  '!?222??993+/33293339993210!3!!!!4#!#"&'!3267 sNZwYsce {>VTLgS[_ooN? !_@/  "  ! @  FY  #??+??39922/33293339993210!!!!47!#"&&'!3267o+މ)g  Yqgd^FFw^ὩJlN_[/N@( LY  LY  LY ?+?99//3+3+33333333910!!3 !!#535!32654&##+z8V6hO7!HefeYP@) JYJYJY??+9/39/++33333333391035!!!3 !!#4&##321ymhg5?oA:u@;    LY LY  ??9/2++99399399333393293999910'##!! 37'7654&##_]XsVr D:R)w>}p5Rou5Zmhs(@A!"$"##&&* )!$##"&"&GY GY?2+???99+9939939933992393393999910"'#!336632'"337'7654&p+6c^l4qhktfRe;JSSѠ{v!{dNl/P A@   LY  LY ??+9/3+3333333910!!!!#53PoʉTd^ A@   FY  GY ??+9/3+3333333910!!!!#53Lω^^yU@+  LY LY LY??+9/+?+393333393910"!!!632#"&'32654&m5Jk1nJ o ס/Ͱ ^Y@-  HYGY  HY?+3??+9/+93333339910%#"'3265!"!!!632zr-y1t}*.1JKD31^ Vn@6   LY  '?33??39333333+3/333333333933333910!!!!!#!!!?!@R=ը<<BLoX^i@4    FY  #??+?3?33933333/333333393333333910!!!!#!!!;d VVd;?77F^&N#s&1VJ@$    LY'??33+?3933/333333233910!!#!!!Gո6 J1 L<Bo5^M@&    FY#??+3??3933/333333233910!3!#!!}PE)71#^7^Pa@/      ?3?93333233393333339999910!!773!!#j|6zXdZcbGy^U@)        ?2?393333332233333999910!737!!#'!cthE>8.N\Nȱ?Mǿp7>8&=Jpb"W}L}{jz18\s*3@H" 220+0%%+++54+%0+0.2(.FY((GY 22 ""  GY JY ?+3+3/399?+9/+9933333339393933310327#"'#"32&#"327&&546324&#"6VN*;@HTb*y0CX8ohol *,-ZLgv4V"74OM9H~W@w&&9\s&F)Vy 6@  LYLY'??+?+393333310!!!!!!+sPs L/o=^ :@   FY FY#??+?+333933310!!!!!5=^fy<^5@  ??33?33333933310!!367!NP $"NM^I<`^@.   LY  ??39/3939+33333933339910!!!!!!5!1N??P\Z)^L@%    FY??33+3?33339939333310!!!!5!!367!#NP $"NM^I<`V^@.      LY'??3+3?39333/33332993239333910!!#!!!%ժ:V;5N5 L)+ o^g@3    FY  #?3??+39333?/33333399339399910!!!!#!!3ZZ ;#d)VHK@%@  ' LY LY?+?3+39/?333993310!!!!!!!!;i6+NTL/o7^D@"   FY FY  #??+3?3+3333993310!!!!!!#51^lyymVF@@   LY  LY'??+?39/+/333333310!!!#"&5!3267!+ʚ]5buRw6 L54&ɶ\jk!)J{o^@@  FY FY #??+?39/+/3333333103267!!!!#"&5XM1jU^g( 8.mJ@$    LY?3?9/333+3933333933310#"&5!367!!#q((5bmY6ʁnɶ\neH 3J5-{^J@$    FY?3?9/333+3933333933310#"&5!33367!!#F31}N^1iC}Zg)6f+@ LY ??39/+3333310!6632!4&#"!6[buOv3'Ǹjk *q^-@  FY ?3?9/+3333310!4#"!!66321jWH^D8.`!(c@3% &*) $""LY%@ LY LY?+3?9/3+3+99333333933310%2$7# #"&547333! !"!4&bLn}?5`)%d%\[ ҕ ]DKBU6ztYHX8u|Gݳ`s%g@5"#'&$! !  FYJY"@JY?+?9/3+3+99393333339333106$32!3267# ' 54733%"!&&N!jbN)`%^| w+-'(`E75Nsyp|V$+}@@#$$))!( - ,$''%%LY( @LY"MY"?3+3?9/3+3+99?3333933339939310&#"&547333! !32$7!"!4&?5`)%d%\[ ҼLnm~ (IztYHX8u|G]D@> duo`s '@C %%$  ) ( #&##!FY JY$@  !JY?+?9/3+3+9939?3333933339939310&&' 5473336$32!3267!"!&&ݵ)`!jb^| w(`E75N+-? +syp|B,&6uR&+5?&6/&+5X@,  !  LY MY  ??39/3+3?+33333393399310"!!7!32#"&'32654&Ko6X/nJ@Pס/Ͱ ^X@, GY  HY?+3??39/3+33333339939310%#"'32654&#"!!!2yr-y1t~2z1X'D3 ^ VI@%  'LY LYLY?+?+?+?/3332939310!!!'"'5326!!!=>_T@:35>7[ NucaW TLo^N@(FY HY FY#??+?+3?+/3329339310%!!!!#"'5326!+݉ \|jD119M=Ny OfC@" LY  LY?+3??39/+333333910%#"'3265!!!!!fKR~6C5Z/= ^E@"  HY FY??39/+?+333333310!!!!#"&'326711Lv@prlo3^R :VL@&    LY  LY '??3+?39/+/33293333310!!!!!!!!fN6C5 Lw=Jo^M@'    FY  FY #??+??39/+/33293333310!!!!!!!1+݉V^R3^mV=@  LY  'LY?+??39/+333333310!!!3#"&5!3267!]5buRw6V+4&ɶ\jk!){o^=@  FY  # FY ?+??39/+3333333103267!!!35#"&5XM1jU^g( op8.V!T@*   '   LY?33+?3993?/332933393310!#!!3!!!!46#!# ZoN L{uX^TL1oL^U@+  FY#??+?3?3993/332933393310%!!!#&''!!>!!+މ6++13 !%,q>l nD^#MGnB,&$6uR&+5V\?&D6)&&+5V&$jVR #&+55V;&Dj 8&+55%VuvA&(6R&+5\b?&H6&+5=@ LY  LY LY?+?+9/+33333310"6$3 ! 5!&&267!3pZ+ ӕ [G SEnuH#\ws=@ JY  JY FY?+?+9/+33333310%267! !"55!&&#"566Zcv >t<6ckXvun} 𔂒&2,$V&jR 1&+55\w&j 1&+55V&jXR '&+55&j '&+55^V&j-R <&+55N#&j >&+559jP@( MY  MY LY?+9?+9/+33339939310!!! '32654&##hP]h{Z\d O,5irf_9V^R@) FY  GY FY?+9?+9/+33333939310!5!#"'32654&##F\evbxP-3&MR&+5#&Mu&+5V&jR %&+55#&ju #&+55wV&2jR +&+55\&Rj /&+55w ?@ LY LY  LY?+?+9/+33339910! ! 267!"!&&iQQeHzmm|ۮ\s ?@ JY GY GY?+?+9/+33333310!"&5!2267!"!&&ap>ndbpm1̍0ttttqppqwV&~jR /&+55\&j 1&+55HV&j#R /&+55J&j /&+559&M1R&+5&\M&+59V&j1R )&+55&\j ,&+559s&SR &&+55!&\SR )&+55mV&jVR )&+55{&j# (&+55VT /@  LY LY'??+?+3333310!!!!T+TLo^ /@  FY FY#??+?+3333310!!!!-^f^V&j5R -&+55-&j ,&+55/P& >+5^& >+5&;>+5  ^&[>+5Va@1      LY?3?9/3+3399333393393333310!!!!!!!!q)V;5N'hN)j ^i@5       FY?3?9/3+33993333933939393103!!3#!!#fZZ.+dJ=\b 4@ LYLY??+9/+33333104$!3!! #"33\8{5VPh1JYefe\qG\&S@)&& ( ' ##LYLY??9/+39/+3933339339104$!3!3265!#"&'#"&#"3265\* s5OVZN1l'+}H][Tb1BAfq-N=?Jil`fA;\ ,W@- $ .*-  (GYHY!GY?+3+?+999/?333393910"323&&5!32655!#"&''26754&#"^j 1PXWK-x>.Zofjqb(6&*fiKFfq=L7R!(R@)$ *$)MY&&!MY& LY?+?+9/3/+9333933339103265!#"&54&##53 54&#"'6!2SUYO1ùXkqo$eYfq-jmNXdΐ9\s(V@+'" * )('('JY (( HY  FY ?+?+99//9+9333933339102654&#"'663232655!#"&54&##5erMOZxׄWK-8=66%".&9 'zfq̙efVs[@.  !  MY MY LY  '??+?+9/+99333333933910!!!4&##53 54#"'6!2+ȶu*o$LjmѦdΐNo-s"]@/" $ #!""!JY "" FY# FY ?+??+9/9+99333333939102654&#"'6632!!!4&##5jzMPZwсoי8=66&!-'9 "}egFNI!D@!   #" LY LY ?3+3?+9/3339933103265!#"&5!'"'5326!OWZN2>_T@:35>7[ rwHCfq-=caW ^D@! ! FYHY ?3+3?+9/33399331032655!#"&5##"'5326!jPXWK- \|jD119M=/yJCfq OZ@-  LY LY ?+??99//+3933333333933103265!#"&'!!!!!=IUUI165}KFfq-=^Z@-  FY  HY?+??99//+393333333393310!!32655!#"&'5!!2NQUI-k^RHCfqV3^w@@! LYLY LY?+?9/++333393910!! 4$3 &&#"32655!5MkrhӚ5{gTk9*\s@@!   GYFY GY?+?9/++333393910!! !2&&#"!265!\E,\KH]*0V#'tc)b?@  LY  LY ?+?9/+3333939103265!#"&5!!!KVXL1sPs}KFfq-˾?/F^?@  FY HY?+?9/+333393910!32655!#"&'!5=PXVL-^KFfqX(R@)% "*"")%MYLY LY?+?+9/+93333933991046632&#"33#"32$7! $54675&&v΅z릪 a̷`i[COwQKfXhaga1/OʒN%s$N@'!! &%$$JY$$ FY FY ?+?+9/+93339993310#"!267! $54675&54$32&#"3HoX瀐o[RㅏDIy.(Mk 1э,(GhB7b&9 >+5^& >+5R&$gDVR;u&Dg&$f#R&+5V;&Df'&+5&$w!R &+55V&Dw )&+55&$xR &+55;&Dx 0&+55J&$y!R '&+55V&Dy <&+55b&$zR ,&+55V;&Dz A&+55Rs&$'KXRgD &+5VR; &D&Kg ##&+5&${)R &+55V;&D{ .&+55&$|'R  &+55V;&D| 5&+55X&$}'R +&+55V;&D} @&+55b&$~'R &+55V;&D~ ,&+55R} $!!!!&'#"&'3327432#"7jj{%!F ܦ/UUTR\D`|$/6}jGNVR;+&D'gN/&+5R&(g\Rbs&Hg&(fR&+5\b&Hf &+5`&(RR&+5\b&HR &+5&(wR &+55\&Hw "&+55&(xR &+55b&Hx )&+55J&(yR %&+55\&Hy 5&+55b&(zR *&+55\b&Hz :&+55Rs&('KRg&+5\Tb!&H&Kg)&+5B&,fR&+5u<&f&+5BR&,gR&LgwR&2g\Rs&Rgw&2fR&+5\&Rf&+5w&2wR &+55\ &Rw  &+55w&2xR #&+55&Rx '&+55wJ&2yR /&+55\&Ry 3&+55wb&2zR 4&+55\&Rz 8&+55wRs&2'gKR&+5\R!&R'gK #&+5ws&_vR&+5\!&`v}$&+5ws&_CdR'&+5\!&`C,&+5w&_fR"&+5\&`f'&+5w`&_RR"&+5\&`R'&+5wR&_g\R&`gR^&8gR^&Xg^&8f^R&+5&Xf&+5)s&avR&&+5s!&bv &+5)s&aCR&&+5s!&bC &+5)&afdR!&+5s&bf$&+5)`&aRR!&+5s&bR3#&+5R)&ag}Rs&bgR&<g^&\gV&<fR &+5&\f&+5`&<RR &+5&\R&+5\ &B!  /2]210&&'5!!&&'5!F>"-!d)I-!d)17H892H8-9 @   /3]29/10#&'#567!'673#pcrapg;5YU5CK[eAN[nYu  @  /]239/1067!#&'#7#&'53/pg<1~(arjiX@6SH,Ae`FwWpY- %@ /3]29/239310#'6654&#"5632#&'#567!!} 7B%+#%F^qȢpcrapg;5`r=t H)K[eAN1%)@ ! !!!/3]22/39/3/310".#"#663232673#&'#567!7$KHC(+ q kS%MHB))q jZSB0<!1o$0t}GQJN`E;1@   /]23910673#%#"&'332677F/\s àsXXr i`naNWS^L1@   /3]2910#&'53%32673#"&'je/FrY[p Uz`i3K_WS1 -@   /33/]2393]210#'6654#"563232673#"&'126k 3';5FVdrY[p 4A)n )hCK_WS1 $+@"@ H"     /3]22/3+2/3103273#"&'%".#"#663232673jb $KHC+( q b\%MHB))q h;F1$.dy$0mp +@  /?39393393104&'3#"'5326NFOB#pJR<7#-4mB54&#"'6632!?pZ,PTTXz mTP03@MHwZx{NB&V@-"( ""'PY |  $$OY$& OY %?+?+9/_^]+93333933910!"'32654&##532654#"'6!2UdoqH[+$O+6hsgVYl0;Րys F@"  PY %??39/3+3933393339910%#!!5!3!5467#y#Vs2"0%d5rP@(OYNY OY%?+?+9/+33333933993102!"'3 54!"'!!76fO^5({7#=bO *5 BHP7Pp,@NY$??+93339310!!%/HJBJ%F@# ##&'  PYOY&PY%?+?+9/+999399210!"'532667##"&5432%"32654&JCT\j :r `lbd^}PV[ë^L|j|{Pw)&IIR#4@@4$,+,-0&(() )200//)562&&,*$$)  0))*5-*?33/339/33/3399333/993939223399333310#"'532654&'.54632&&#"##33#7#uoXsX-/#%lH'r_p48<'%-*JiFdq+6'#&57@7Cq7D\7F\7G7Hq7J7777777 7W7Xq7Y\7_7`q7b7q7\7q7 \7!q7"\7#q7%q7&\7'q7(\7)q7*\7+q7,\7-q7.\7/q70\71q72\73q74\76q78q7:q7<@<C<D<F<G<H<J<< <W<X<Y<_<`<b<<<< <!<"<#<%<&<'<(<)<*<+<,<-<.</<0<1<2<3<4<6<8<:<<<@<B<D<I<J<K<L<M<N<O<Q<R<S<T<U<V<W<X<Y<Z<[<\<]<^<_<`<b<d<f<h<j<l<n=&=*=2=4====================G=_=I=K=M=O=Q=S=U=W=Y=[=]=_>-DD DD EE EYEZE[E\E]EE7E<E>E@EEEE EpF)F )F)F )HH HYHZH[H\H]HH7H<H>H@HHHH HpI{I {I{I {KK KK NFNGNHNRNTNNNNNNNNNNNNNNNNNNNNNNNNNNNNHN`N6N8N:N<N@NBNDNJNLNNNRNTNVNXNZN\N^N`PP PP QQ QQ RR RYRZR[R\R]RR7R<R>R@RRRR RpSS SYSZS[S\S]SS7S<S>S@SSSS SpURU RUDUFUGUHUJURUTUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUDUFUHURU RUYU`UU U"U&U(U*U,U.U0U2U4U6U8U:U<U@UBUDUJULUNURUTUVUXUZU\U^U`W)W )W)W )YRY RYYY")YRYY RY ZRZ RZZZ")ZRZZ RZ [F[G[H[R[T[[[[[[[[[[[[[[[[[[[[[[[[[[[[H[`[6[8[:[<[@[B[D[J[L[N[R[T[V[X[Z[\[^[`\R\ R\\\")\R\\ R\ ^-q q&*- 247q9:<$q&q68:Gq q_IKMOQSUWY[]_oqsqq q&*- 247q9:<$q&q68:Gq q_IKMOQSUWY[]_oqsqq q&*- 247q9:<$q&q68:Gq q_IKMOQSUWY[]_oqsqq q&*- 247q9:<$q&q68:Gq q_IKMOQSUWY[]_oqsqq q&*- 247q9:<$q&q68:Gq q_IKMOQSUWY[]_oqsqq q&*- 247q9:<$q&q68:Gq q_IKMOQSUWY[]_oqsq-{&*24G_IKMOQSUWY[]_-{-{-{-{$79:;<=$&68:;=?C X!#%')+-/13oqs$79:;<=$&68:;=?C X!#%')+-/13oqs$79:;<=$&68:;=?C X!#%')+-/13oqs$79:;<=$&68:;=?C X!#%')+-/13oqs$79:;<=$&68:;=?C X!#%')+-/13oqs$79:;<=$&68:;=?C X!#%')+-/13oqs$79:;<=$&68:;=?C X!#%')+-/13oqs$C X!#%')+-/13$C X!#%')+-/13$C X!#%')+-/13$C X!#%')+-/13")$&*24DFGHJPQRSTUVX] !+-/135<>@CDFGHJ WXY_`b !"#%&'()*+,-./0123468:<@BDIJKLMNOQRSTUVWXYZ[\]^_`bdfhjln$;=;=?C X!#%')+-/13             YZ[\]7<>@ p YZ[\]7<>@ p YZ[\]7<>@ p YZ[\]7<>@ p YZ[\]7<>@ p YZ[\]7<>@ p YZ[\]7<>@ p YZ[\]7<>@ p   YZ[\]7<>@ pR R")R R  YZ[\]7<>@ pR R")R R q q&*- 247q9:<$q&q68:Gq q_IKMOQSUWY[]_oqsq  q q&*- 247q9:<$q&q68:Gq q_IKMOQSUWY[]_oqsq  q q&*- 247q9:<$q&q68:Gq q_IKMOQSUWY[]_oqsq  &*24G_IKMOQSUWY[]_&*24G_IKMOQSUWY[]_&*24G_IKMOQSUWY[]_&*24G_IKMOQSUWY[]_$79:;<=$&68:;=?C X!#%')+-/13oqsR R "@E=K=N=O=`={R R$79:;<=$&68:;=?C X!#%')+-/13oqs-{ YZ[\]7<>@ p-{ YZ[\]7<>@ p-{ YZ[\]7<>@ p-{ YZ[\]7<>@ p-{ YZ[\]7<>@ p  &*24G_IKMOQSUWY[]_FGHRTH`68:<@BDJLNRTVXZ\^`FGHRTH`68:<@BDJLNRTVXZ\^`\ \&*24789:<$&*,.02468:G\ \_aIKMOQSUWY[]_acegikmoqs\ \&*24789:<$&*,.02468:G\ \_aIKMOQSUWY[]_acegikmoqs\ \&*24789:<$&*,.02468:G\ \_aIKMOQSUWY[]_acegikmoqsR R "@E=K=N=O=`=R R\ \&*24789:<$&*,.02468:G\ \_aIKMOQSUWY[]_acegikmoqs\ \&*24789:<$&*,.02468:G\ \_aIKMOQSUWY[]_acegikmoqs  $79:;<=$&68:;=?C X!#%')+-/13oqs$79:;<=$&68:;=?C X!#%')+-/13oqs$79:;<=$&68:;=?C X!#%')+-/13oqs-{R RDFGHJRTDFHR RY` "&(*,.02468:<@BDJLNRTVXZ\^`R RDFGHJRTDFHR RY` "&(*,.02468:<@BDJLNRTVXZ\^`R RDFGHJRTDFHR RY` "&(*,.02468:<@BDJLNRTVXZ\^`$$$$")$$q$&$*$2$4$7)$D\$Fq$Gq$Hq$Jq$P$Q$Rq$S$Tq$U$V$X$Y$Z$[$\$]$q$q$q$q$q$q$$$$$$$$q$\$\$\$\$\$\$q$q$q$q$q$q$q$q$q$q$q$$$$$$q$\$q$\$q$\$$q$$q$$q$$q$q$q$q$q$q$q$q$$q$$q$$q$$q$$$$ $$q$$q$$q$$q$$$$!$$)$&)$+$-$/$1$3$5$7$<$>$@$Cq$D\$F\$G$Hq$J$$$$$$$ $W$Xq$Y\$_$`q$b$q$\$q$ \$!q$"\$#q$%q$&\$'q$(\$)q$*\$+q$,\$-q$.\$/q$0\$1q$2\$3q$4\$6q$8q$:q$&@&Cq&D\&F\&G&Hq&J&&&&&&& &W&Xq&Y\&_&`q&b&q&\&q& \&!q&"\&#q&%q&&\&'q&(\&)q&*\&+q&,\&-q&.\&/q&0\&1q&2\&3q&4\&6q&8q&:q&(@(Cq(D\(F\(G(Hq(J((((((( (W(Xq(Y\(_(`q(b(q(\(q( \(!q("\(#q(%q(&\('q((\()q(*\(+q(,\(-q(.\(/q(0\(1q(2\(3q(4\(6q(8q(:q(8@8C8D8F8G8H8J88 8W8X8Y8_8`8b8888 8!8"8#8%8&8'8(8)8*8+8,8-8.8/808182838486888:8<8@8B8D8I8J8K8L8M8N8O8Q8R8S8T8U8V8W8X8Y8Z8[8\8]8^8_8`8b8d8f8h8j8l8n9R9 R999")9R99 R9 :::"):$:&:*:2:4:D:F:G:H:J:P:Q:R:S:T:U:V:X:]:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ::::::::::::!:+:-:/:1:3:5:<:>:@:C:D:F:G:H:J:: :W:X:Y:_:`:b:::: :!:":#:%:&:':(:):*:+:,:-:.:/:0:1:2:3:4:6:8:::<:@:B:D:I:J:K:L:M:N:O:Q:R:S:T:U:V:W:X:Y:Z:[:\:]:^:_:`:b:d:f:h:j:l:n;&;*;2;4;;;;;;;;;;;;;;;;;;;;G;_;I;K;M;O;Q;S;U;W;Y;[;];_=&=*=2=4====================G=_=I=K=M=O=Q=S=U=W=Y=[=]=_?&?*?2?4????????????????????G?_?I?K?M?O?Q?S?U?W?Y?[?]?_CqC qC&C*C- C2C4C7qC9C:C<CCCCCCCCCCCCCCCCCCCCC$qC&qC6C8C:CGCCCCCqC qC_CICKCMCOCQCSCUCWCYC[C]C_CoCqCsCqDD DD E-{GGG$G7G9G:G;G<G=GGGGGGGGGGG$G&G6G8G:G;G=G?GCGGGGGGG GXGGG!G#G%G'G)G+G-G/G1G3GoGqGsGVqV qVfVmVqqVrVsVuVxVqV qVT[[[V[_[b[d[i[p[q[r[t[u[x[[[ [T\\\V\_\b\f\i\m\s\v\y\z\{\|\}\~\\\\\\\\\\\\\\\\\\\\ \!]q]r]x]T^^ ^^ _q_ q_f_m_qq_r_s_u_x_q_ q_T```V`_`b`i`t`` aaaaV\a_\ab\afai\amasavayqaza{a|a}a~qaaaaaaaaaqaaqaqaaqaaaaaqaaaaaaa a!aSbqb qbfbmbqqbrbsbubxbqb qbTdfdmdsfffVf_fbfdfifpfqfrftfufxfff fThfhmhshhiqi qifimiqqirisiuixiqi qiTmmmVm_mbmdmimpmqmrmtmumxmmm mToooVo_obodoiotooo qqqqV\q_\qb\qfqi\qmqsqvqyqqzq{q|q}q~qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq q!qSrrrVr_rbrfrirmrsrvryrzr{r|r}r~rrrrrrrrrrrrrrrrrrrr r!sssVs_sbsdsispsqsrstsxsss sTtftmtsttuuuVu_ubufuiumuu vqvrvxvTxxxVx_xbxfxixmxsxvxyxzx{x|x}x~xxxxxxxxxxxxxxxxxxxx x!y){{ {{ || |||| ~) y~     y~ y~         y~       n| q)qqqqqq jqklmqrqsuwy}~qqqqqqq)qqqqqqqqq q q q qqql~    n|   n|   n| l|~  qff jlrqs~ffqqq q  q q qqq qn|q qq  r|  q)qqqqqq jqklmqrqsuwy}~qqqqqqq)qqqqqqqqq q q q qqq)l|~    l|~   rsz|  rsv  l~  q)qqqqqq jqklmqrqsuwy}~qqqqqqq)qqqqqqqqq q q q qqqqff jlrqs~ffqqq q  q  rsz l|~  ))  n|   n|  rsz|  rsz|   ) js      j  )j       js   s  j        m  m         js    m  m  j   sq)qqqqqq jqklmqrqsuwy}~qqqqqqq)qqqqqqqqq q q q qqq js  ")$&*24DFGHJPQRSTUVX !+-/135CDFGHJ WXY_`b !"#%&'()*+,-./0123468:<@BDIJKLMNOQRSTUVWXYZ[\]^_`bdfhjlnR R")R R ")$&*24DFGHJPQRSTUVX !+-/135CDFGHJ WXY_`b !"#%&'()*+,-./0123468:<@BDIJKLMNOQRSTUVWXYZ[\]^_`bdfhjlnR R")R R ")$&*24DFGHJPQRSTUVX !+-/135CDFGHJ WXY_`b !"#%&'()*+,-./0123468:<@BDIJKLMNOQRSTUVWXYZ[\]^_`bdfhjlnR R")R R ")$&*24DFGHJPQRSTUVX] !+-/135<>@CDFGHJ WXY_`b !"#%&'()*+,-./0123468:<@BDIJKLMNOQRSTUVWXYZ[\]^_`bdfhjlnR R")R R 7$&q7$&q7$&q$q7)9):)<DFGHJPQRSTUVXqqqqqqqqq !$)&)+-/1356)8:CqDFHJVq_qbqiqyz{~qqqq)))WXqY`bjrqsq}qqqq q  q qqqq !q"#q%q&'q()q*+q,-q./q01q23q468:<@BDJLNRTVXZ\^`bdfhjlnoqs)$q7)9):)<DFGHJPQRSTUVXqqqqqqqqq !$)&)+-/1356)8:CqDFHJVq_qbqiqyz{~qqqq)))WXqY`bjrqsq}qqqq q  q qqqq !q"#q%q&'q()q*+q,-q./q01q23q468:<@BDJLNRTVXZ\^`bdfhjlnoqs)&*247q89:<$q&q*,.02468:Gfmqqrsuxqqq\qT_al|\~qqq\\\\ qIKMOQSUWY[]_acegikmoqsq $q 7) 9) :) < D F G H J P Q R S T U V X q q q q q q                        q  q  q                             ! $) &) + - / 1 3 5 6) 8 : Cq D F H J Vq _q bq iq y z { ~              q    q  q       q ) ) )  W Xq Y ` b j rq sq }         q q  q  q         q    q    q    q q  q   !q " #q %q & 'q ( )q * +q , -q . /q 0 1q 2 3q 4 6 8 : < @ B D J L N R T V X Z \ ^ ` b d f h j l n o q s ) & * 2 4 7q 8 9 : <                         $q &q * , . 0 2 4 6 8 : G f m qq r s u x  q  q   q  \ q        T _ a l |\ ~        q    q    q  \  \    \     \        q  I K M O Q S U W Y [ ] _ a c e g i k m o q s q!q!r!x!TSSSS TTTVT_TbTfTiTmTsTvTyTzT{T|T}T~TTTTTTTTTTTTTTTTTTTT T!XqX qX&X*X- X2X4X7qX9X:X<XXXXXXXXXXXXXXXXXXXXX$qX&qX6X8X:XGXXXXXqX qX_XIXKXMXOXQXSXUXWXYX[X]X_XoXqXsXqYY YY ZZZVZ_ZbZdZiZpZqZrZtZuZxZZZ ZT`IR`WR`Yf`Zf`[f`\f`f`%R`'R`7f`f`f`4R`5R`]R`^R`pf`R`RbIfbWfbYfbZfb[fb\fbfb%fb'fb7fbfbfb4fb5fb]fb^fbpfbfbfjj jj llllllllllllllllllllll lrlslzl|lllllllllllllllllllllllllllll llllllmmmmmmm msmmmnn nnnnnnnnn n|nnnnnnnnnnnnnnn nnnoo ooooooooo omooooooooooooooooooooooooooopppppplp~ppppppppppppppppp p ppprqr qrrrqrrrrrrrqr qrnr|rrrrrrrrqrrrrrrrrrrrrrrrrrrr qrrqrrrsqs qssssssssssssqs qsjsms}sssssssssssssssssssssssssssss s ssssstqt qtttqtttttttqt qtnt|ttttttttqttttttttttttttttttt qttqtttuqu quuuuuuuuuuuuqu qujumu}uuuuuuuuuuuuuuuuuuuuuuuuuuuuu u uuuuuv vx xzzzz zzzzz z|q|q||||||||q| q|r|s|||||| | ||||}} }}}}}}}}} }}}}}}}}}}}}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~ ~r~s~z~|~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~  qff jlrqs~ffqqq q  q  sqff jlrqs~ffqqq q  q  s s s rsz|    rsz|    rsz|  sl~  )    |   m rsv    )))))) jklqrsuwy}~)))))    ) js   f |   l{=}~j    )l{=}~j  l|~  j  l|~  j  l|~  j  )  )))))) jklqrsuwy}~)))))    ) js  \ \fH\ \|qqH HHq qq qmqq   l~  q)qqqqqq jqklmqrqsuwy}~qqqqqqq)qqqqqqqqq q q q qqq js  )) jklqrsuwy}~))     s)) jklqrsuwy}~))     sl{=}~j  )  )    |         l|~  j   f m| )  )  )  q qqq qn|q qq  q qqq qn|q qq       rsz|    rsz|   l|~  j    )   rsz|    rsz|    rsz|    rsz|   qff jlrqs~ffqqq q  q  sqff jlrqs~ffqqq q  q  sqff jlrqs~ffqqq q  q  sq)qqqqqq jqklmqrqsuwy}~qqqqqqq)qqqqqqqqq q q q qqq js  )))))) jklqrsuwy}~)))))    ) js  l{=}~j  l|~  j          q             r v |       q                                                                           q             r v |       q                                      )   q rv|q    q rv|q    r    r|   )  q q&*- 247q9:<$q&q68:Gq q_IKMOQSUWY[]_oqsq  q q&*- 247q9:<$q&q68:Gq q_IKMOQSUWY[]_oqsq     !q! q!&!*!- !2!4!7q!9!:!<!!!!!!!!!!!!!!!!!!!!!$q!&q!6!8!:!G!!!!!q! q!_!I!K!M!O!Q!S!U!W!Y![!]!_!o!q!s!q"" "" #q# q#&#*#- #2#4#7q#9#:#<#####################$q#&q#6#8#:#G#####q# q#_#I#K#M#O#Q#S#U#W#Y#[#]#_#o#q#s#q$$ $$ %q% q%&%*%- %2%4%7q%9%:%<%%%%%%%%%%%%%%%%%%%%%$q%&q%6%8%:%G%%%%%q% q%_%I%K%M%O%Q%S%U%W%Y%[%]%_%o%q%s%q&& && 'q' q'&'*'- '2'4'7q'9':'<'''''''''''''''''''''$q'&q'6'8':'G'''''q' q'_'I'K'M'O'Q'S'U'W'Y'[']'_'o'q's'q(( (( )q) q)&)*)- )2)4)7q)9):)<)))))))))))))))))))))$q)&q)6)8):)G)))))q) q)_)I)K)M)O)Q)S)U)W)Y)[)])_)o)q)s)q** ** +q+ q+&+*+- +2+4+7q+9+:+<+++++++++++++++++++++$q+&q+6+8+:+G+++++q+ q+_+I+K+M+O+Q+S+U+W+Y+[+]+_+o+q+s+q,, ,, -q- q-&-*-- -2-4-7q-9-:-<---------------------$q-&q-6-8-:-G-----q- q-_-I-K-M-O-Q-S-U-W-Y-[-]-_-o-q-s-q.. .. /q/ q/&/*/- /2/4/7q/9/:/</////////////////////$q/&q/6/8/:/G/////q/ q/_/I/K/M/O/Q/S/U/W/Y/[/]/_/o/q/s/q00 00 1q1 q1&1*1- 121417q191:1<111111111111111111111$q1&q16181:1G11111q1 q1_1I1K1M1O1Q1S1U1W1Y1[1]1_1o1q1s1q22 22 3q3 q3&3*3- 323437q393:3<333333333333333333333$q3&q36383:3G33333q3 q3_3I3K3M3O3Q3S3U3W3Y3[3]3_3o3q3s3q44 44 5-{66 6Y6Z6[6\6]6676<6>6@6666 6p7-{88 8Y8Z8[8\8]8878<8>8@8888 8p9-{:: :Y:Z:[:\:]::7:<:>:@:::: :p;-{<< <Y<Z<[<\<]<<7<<<><@<<<< <p=-{>> >Y>Z>[>\>]>>7><>>>@>>>> >p?-{@@ @Y@Z@[@\@]@@7@<@>@@@@@@ @pA-{BB BYBZB[B\B]BB7B<B>B@BBBB BpC-{DD DYDZD[D\D]DD7D<D>D@DDDD DpIII$I7I9I:I;I<I=IIIIIIIIIII$I&I6I8I:I;I=I?ICIIIIIII IXIII!I#I%I'I)I+I-I/I1I3IoIqIsIJJ JYJZJ[J\J]JJ7J<J>J@JJJJ JpKKK$K7K9K:K;K<K=KKKKKKKKKKK$K&K6K8K:K;K=K?KCKKKKKKK KXKKK!K#K%K'K)K+K-K/K1K3KoKqKsKLL LYLZL[L\L]LL7L<L>L@LLLL LpMMM$M7M9M:M;M<M=MMMMMMMMMMM$M&M6M8M:M;M=M?MCMMMMMMM MXMMM!M#M%M'M)M+M-M/M1M3MoMqMsMOOO$O7O9O:O;O<O=OOOOOOOOOOO$O&O6O8O:O;O=O?OCOOOOOOO OXOOO!O#O%O'O)O+O-O/O1O3OoOqOsOQQQ$Q7Q9Q:Q;Q<Q=QQQQQQQQQQQ$Q&Q6Q8Q:Q;Q=Q?QCQQQQQQQ QXQQQ!Q#Q%Q'Q)Q+Q-Q/Q1Q3QoQqQsQSSS$S7S9S:S;S<S=SSSSSSSSSSS$S&S6S8S:S;S=S?SCSSSSSSS SXSSS!S#S%S'S)S+S-S/S1S3SoSqSsSUUU$U7U9U:U;U<U=UUUUUUUUUUU$U&U6U8U:U;U=U?UCUUUUUUU UXUUU!U#U%U'U)U+U-U/U1U3UoUqUsUXIRXWRXYfXZfX[fX\fXfX%RX'RX7fXfXfX4RX5RX]RX^RXpfXRXRZIRZWRZYfZZfZ[fZ\fZfZ%RZ'RZ7fZfZfZ4RZ5RZ]RZ^RZpfZRZR\IR\WR\Yf\Zf\[f\\f\f\%R\'R\7f\f\f\4R\5R\]R\^R\pf\R\R^IR^WR^Yf^Zf^[f^\f^f^%R^'R^7f^f^f^4R^5R^]R^^R^pf^R^R`IR`WR`Yf`Zf`[f`\f`f`%R`'R`7f`f`f`4R`5R`]R`^R`pf`R`Raaa$aaaaaaaaaaCaa aXaaa!a#a%a'a)a+a-a/a1a3fIffWffYffZff[ff\ffff%ff'ff7ffffff4ff5ff]ff^ffpfffffhIfhWfhYfhZfh[fh\fhfh%fh'fh7fhfhfh4fh5fh]fh^fhpfhfhfjIfjWfjYfjZfj[fj\fjfj%fj'fj7fjfjfj4fj5fj]fj^fjpfjfjflIflWflYflZfl[fl\flfl%fl'fl7flflfl4fl5fl]fl^flpflflfnIfnWfnYfnZfn[fn\fnfn%fn'fn7fnfnfn4fn5fn]fn^fnpfnfnfooo")o$o&o*o2o4oDoFoGoHoJoPoQoRoSoToUoVoXo]oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo oooooooooooo!o+o-o/o1o3o5o<o>o@oCoDoFoGoHoJoo oWoXoYo_o`oboooo o!o"o#o%o&o'o(o)o*o+o,o-o.o/o0o1o2o3o4o6o8o:o<o@oBoDoIoJoKoLoMoNoOoQoRoSoToUoVoWoXoYoZo[o\o]o^o_o`obodofohojolonpRp Rppp")pRpp Rp qqq")q$q&q*q2q4qDqFqGqHqJqPqQqRqSqTqUqVqXq]qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq qqqqqqqqqqqq!q+q-q/q1q3q5q<q>q@qCqDqFqGqHqJqq qWqXqYq_q`qbqqqq q!q"q#q%q&q'q(q)q*q+q,q-q.q/q0q1q2q3q4q6q8q:q<q@qBqDqIqJqKqLqMqNqOqQqRqSqTqUqVqWqXqYqZq[q\q]q^q_q`qbqdqfqhqjqlqnrRr Rrrr")rRrr Rr sss")s$s&s*s2s4sDsFsGsHsJsPsQsRsSsTsUsVsXs]ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss ssssssssssss!s+s-s/s1s3s5s<s>s@sCsDsFsGsHsJss sWsXsYs_s`sbssss s!s"s#s%s&s's(s)s*s+s,s-s.s/s0s1s2s3s4s6s8s:s<s@sBsDsIsJsKsLsMsNsOsQsRsSsTsUsVsWsXsYsZs[s\s]s^s_s`sbsdsfshsjslsntRt Rttt")tRtt Rt { {{ {")$q&*247)D\FqGqHqJqPQRqSTqUVXYZ[\]qqqqqqq\\\\\\qqqqqqqqqqqq\q\q\qqqqqqqqqqqqqqq qqqq!$)&)+-/1357<>@CqD\F\GHqJ WXqY\_`qbq\q \!q"\#q%q&\'q(\)q*\+q,\-q.\/q0\1q2\3q4\6q8q:q9 9B#FQ i uR  . .2*` r   F , \ t  (2 8Z \ \ TJDigitized data copyright 2010-2011, Google Corporation.Open SansBoldAscender - Open Sans Bold Build 100Version 1.10OpenSans-BoldOpen Sans is a trademark of Google and may be registered in certain jurisdictions.Ascender Corporationhttp://www.ascendercorp.com/http://www.ascendercorp.com/typedesigners.htmlLicensed under the Apache License, Version 2.0http://www.apache.org/licenses/LICENSE-2.0Digitized data copyright 2010-2011, Google Corporation.Open SansBoldAscender - Open Sans Bold Build 100Version 1.10OpenSans-BoldOpen Sans is a trademark of Google and may be registered in certain jurisdictions.Ascender Corporationhttp://www.ascendercorp.com/http://www.ascendercorp.com/typedesigners.htmlLicensed under the Apache License, Version 2.0http://www.apache.org/licenses/LICENSE-2.0ff      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~.notdefnullnonmarkingreturnspaceexclamquotedbl numbersigndollarpercent ampersand quotesingle parenleft parenrightasteriskpluscommahyphenperiodslashzeroonetwothreefourfivesixseveneightninecolon semicolonlessequalgreaterquestionatABCDEFGHI.altJKLMNOPQRSTUVWXYZ bracketleft backslash bracketright asciicircum underscoregraveabcdefghijklmnopqrstuvwxyz braceleftbar braceright asciitildenonbreakingspace exclamdowncentsterlingcurrencyyen brokenbarsectiondieresis copyright ordfeminine guillemotleft logicalnotuni00AD registered overscoredegree plusminus twosuperior threesuperioracutemu paragraphperiodcenteredcedilla onesuperior ordmasculineguillemotright onequarteronehalf threequarters questiondownAgraveAacute AcircumflexAtilde AdieresisAringAECcedillaEgraveEacute Ecircumflex Edieresis Igrave.alt Iacute.altIcircumflex.alt Idieresis.altEthNtildeOgraveOacute OcircumflexOtilde OdieresismultiplyOslashUgraveUacute Ucircumflex UdieresisYacuteThorn germandblsagraveaacute acircumflexatilde adieresisaringaeccedillaegraveeacute ecircumflex edieresisigraveiacute icircumflex idieresisethntildeograveoacute ocircumflexotilde odieresisdivideoslashugraveuacute ucircumflex udieresisyacutethorn ydieresisAmacronamacronAbreveabreveAogonekaogonekCacutecacute Ccircumflex ccircumflexCdotcdotCcaronccaronDcarondcaronDcroatdcroatEmacronemacronEbreveebreve Edotaccent edotaccentEogonekeogonekEcaronecaron Gcircumflex gcircumflexGbrevegbreveGdotgdot Gcommaaccent gcommaaccent Hcircumflex hcircumflexHbarhbar Itilde.altitilde Imacron.altimacron Ibreve.altibreve Iogonek.altiogonekIdotaccent.altdotlessiIJ.altij Jcircumflex jcircumflex Kcommaaccent kcommaaccent kgreenlandicLacutelacute Lcommaaccent lcommaaccentLcaronlcaronLdotldotLslashlslashNacutenacute Ncommaaccent ncommaaccentNcaronncaron napostropheEngengOmacronomacronObreveobreve Ohungarumlaut ohungarumlautOEoeRacuteracute Rcommaaccent rcommaaccentRcaronrcaronSacutesacute Scircumflex scircumflexScedillascedillaScaronscaron Tcommaaccent tcommaaccentTcarontcaronTbartbarUtildeutildeUmacronumacronUbreveubreveUringuring Uhungarumlaut uhungarumlautUogonekuogonek Wcircumflex wcircumflex Ycircumflex ycircumflex YdieresisZacutezacute Zdotaccent zdotaccentZcaronzcaronlongsflorin Aringacute aringacuteAEacuteaeacute Oslashacute oslashacute Scommaaccent scommaaccent circumflexcaronmacronbreve dotaccentringogonektilde hungarumlauttonos dieresistonos Alphatonos anoteleia EpsilontonosEtatonos Iotatonos.alt Omicrontonos Upsilontonos OmegatonosiotadieresistonosAlphaBetaGammauni0394EpsilonZetaEtaThetaIota.altKappaLambdaMuNuXiOmicronPiRhoSigmaTauUpsilonPhiChiPsiuni03A9Iotadieresis.altUpsilondieresis alphatonos epsilontonosetatonos iotatonosupsilondieresistonosalphabetagammadeltaepsilonzetaetathetaiotakappalambdauni03BCnuxiomicronpirhosigma1sigmatauupsilonphichipsiomega iotadieresisupsilondieresis omicrontonos upsilontonos omegatonos afii10023 afii10051 afii10052 afii10053 afii10054 afii10055.alt afii10056.alt afii10057 afii10058 afii10059 afii10060 afii10061 afii10062 afii10145 afii10017 afii10018 afii10019 afii10020 afii10021 afii10022 afii10024 afii10025 afii10026 afii10027 afii10028 afii10029 afii10030 afii10031 afii10032 afii10033 afii10034 afii10035 afii10036 afii10037 afii10038 afii10039 afii10040 afii10041 afii10042 afii10043 afii10044 afii10045 afii10046 afii10047 afii10048 afii10049 afii10065 afii10066 afii10067 afii10068 afii10069 afii10070 afii10072 afii10073 afii10074 afii10075 afii10076 afii10077 afii10078 afii10079 afii10080 afii10081 afii10082 afii10083 afii10084 afii10085 afii10086 afii10087 afii10088 afii10089 afii10090 afii10091 afii10092 afii10093 afii10094 afii10095 afii10096 afii10097 afii10071 afii10099 afii10100 afii10101 afii10102 afii10103 afii10104 afii10105 afii10106 afii10107 afii10108 afii10109 afii10110 afii10193 afii10050 afii10098WgravewgraveWacutewacute Wdieresis wdieresisYgraveygraveendashemdash afii00208 underscoredbl quoteleft quoterightquotesinglbase quotereversed quotedblleft quotedblright quotedblbasedagger daggerdblbulletellipsis perthousandminutesecond guilsinglleftguilsinglright exclamdblfraction nsuperiorfranc afii08941pesetaEuro afii61248 afii61289 afii61352 trademarkOmega estimated oneeighth threeeighths fiveeighths seveneighths partialdiffDeltaproduct summationminusradicalinfinityintegral approxequalnotequal lessequal greaterequallozengeuniFB01uniFB02 cyrillicbrevedotlessjcaroncommaaccent commaaccentcommaaccentrotate zerosuperior foursuperior fivesuperior sixsuperior sevensuperior eightsuperior ninesuperioruni2000uni2001uni2002uni2003uni2004uni2005uni2006uni2007uni2008uni2009uni200Auni200BuniFEFFuniFFFCuniFFFDuni01F0uni02BCuni03D1uni03D2uni03D6uni1E3Euni1E3Funi1E00uni1E01uni1F4Duni02F3 dasiaoxiauniFB03uniFB04OhornohornUhornuhornuni0300uni0301uni0303hookdotbelowuni0400uni040Duni0450uni045Duni0460uni0461uni0462uni0463uni0464uni0465uni0466uni0467uni0468uni0469uni046Auni046Buni046Cuni046Duni046Euni046Funi0470uni0471uni0472uni0473uni0474uni0475uni0476uni0477uni0478uni0479uni047Auni047Buni047Cuni047Duni047Euni047Funi0480uni0481uni0482uni0483uni0484uni0485uni0486uni0488uni0489uni048Auni048Buni048Cuni048Duni048Euni048Funi0492uni0493uni0494uni0495uni0496uni0497uni0498uni0499uni049Auni049Buni049Cuni049Duni049Euni049Funi04A0uni04A1uni04A2uni04A3uni04A4uni04A5uni04A6uni04A7uni04A8uni04A9uni04AAuni04ABuni04ACuni04ADuni04AEuni04AFuni04B0uni04B1uni04B2uni04B3uni04B4uni04B5uni04B6uni04B7uni04B8uni04B9uni04BAuni04BBuni04BCuni04BDuni04BEuni04BF uni04C0.altuni04C1uni04C2uni04C3uni04C4uni04C5uni04C6uni04C7uni04C8uni04C9uni04CAuni04CBuni04CCuni04CDuni04CE uni04CF.altuni04D0uni04D1uni04D2uni04D3uni04D4uni04D5uni04D6uni04D7uni04D8uni04D9uni04DAuni04DBuni04DCuni04DDuni04DEuni04DFuni04E0uni04E1uni04E2uni04E3uni04E4uni04E5uni04E6uni04E7uni04E8uni04E9uni04EAuni04EBuni04ECuni04EDuni04EEuni04EFuni04F0uni04F1uni04F2uni04F3uni04F4uni04F5uni04F6uni04F7uni04F8uni04F9uni04FAuni04FBuni04FCuni04FDuni04FEuni04FFuni0500uni0501uni0502uni0503uni0504uni0505uni0506uni0507uni0508uni0509uni050Auni050Buni050Cuni050Duni050Euni050Funi0510uni0511uni0512uni0513uni1EA0uni1EA1uni1EA2uni1EA3uni1EA4uni1EA5uni1EA6uni1EA7uni1EA8uni1EA9uni1EAAuni1EABuni1EACuni1EADuni1EAEuni1EAFuni1EB0uni1EB1uni1EB2uni1EB3uni1EB4uni1EB5uni1EB6uni1EB7uni1EB8uni1EB9uni1EBAuni1EBBuni1EBCuni1EBDuni1EBEuni1EBFuni1EC0uni1EC1uni1EC2uni1EC3uni1EC4uni1EC5uni1EC6uni1EC7 uni1EC8.altuni1EC9 uni1ECA.altuni1ECBuni1ECCuni1ECDuni1ECEuni1ECFuni1ED0uni1ED1uni1ED2uni1ED3uni1ED4uni1ED5uni1ED6uni1ED7uni1ED8uni1ED9uni1EDAuni1EDBuni1EDCuni1EDDuni1EDEuni1EDFuni1EE0uni1EE1uni1EE2uni1EE3uni1EE4uni1EE5uni1EE6uni1EE7uni1EE8uni1EE9uni1EEAuni1EEBuni1EECuni1EEDuni1EEEuni1EEFuni1EF0uni1EF1uni1EF4uni1EF5uni1EF6uni1EF7uni1EF8uni1EF9uni20ABuni030Fcircumflexacutecombcircumflexgravecombcircumflexhookcombcircumflextildecombbreveacutecombbrevegravecomb brevehookcombbrevetildecombcyrillichookleftcyrillicbighookUCcyrillicbighookLCone.pnumzero.osone.ostwo.osthree.osfour.osfive.ossix.osseven.oseight.osnine.osffuni2120Tcedillatcedillag.altgcircumflex.alt gbreve.altgdot.altgcommaaccent.altIIgraveIacute Icircumflex IdieresisItildeImacronIbreveIogonek IdotaccentIJ IotatonosIota Iotadieresis afii10055 afii10056uni04C0uni04CFuni1EC8uni1ECA   46latnMOL ROM  nlatnMOL (ROM B      ligaligaligalnumlnumlnumloclloclonumonumonumpnumpnumpnumsalt saltsaltss01"ss01*ss012ss02:ss02@ss02Fss03Lss03Rss03Xtnum^tnumftnumn    &.6>FNV^Pz2HJJ.,ZgwEG      !$%IJ6 "(^IO]ILI5O4LI^V0R *H C0?1 0 +0a +7S0Q0, +7<<<Obsolete>>>0!0 +iV˱H,[ ]0z0b8%a&Z0  *H 0S1 0 UUS10U VeriSign, Inc.1+0)U"VeriSign Time Stamping Services CA0 070615000000Z 120614235959Z0\1 0 UUS10U VeriSign, Inc.1402U+VeriSign Time Stamping Services Signer - G200  *H 0ĵR`)J[/Kk5TX56^bMRQ4q{f*j 7٘tvJcEG.k NK+XJ,XB-uލǎlLgrIž`<cxi{-004+(0&0$+0http://ocsp.verisign.com0 U003U,0*0(&$"http://crl.verisign.com/tss-ca.crl0U% 0 +0U0U0010 UTSA1-20  *H PK$ $- 7 ,Za񑑳V@뒾89u6t:O7ʕBǠWdB5N3M'L8MxSݤ^ ⥾`߭(ǥKd[98"3/!?DA e$HDT\y>]r},CS}=*:Om ]^SWp`+nx'4[^I2300-GߍRFCmH 10  *H 01 0 UZA10U Western Cape10U Durbanville10 U Thawte10U Thawte Certification10UThawte Timestamping CA0 031204000000Z 131203235959Z0S1 0 UUS10U VeriSign, Inc.1+0)U"VeriSign Time Stamping Services CA0"0  *H 0 ʲ }uNgadڻ30X~k6xw~o< hlʽR-H=]_/kLR`@~ ?Ǵ߇_zj1.G 1s W-x43h/Š*Ë!fXWou<&]'x1"ijGC_^|}bM "Vͮv M٠h;004+(0&0$+0http://ocsp.verisign.com0U00AU:0806420http://crl.verisign.com/ThawteTimestampingCA.crl0U% 0 +0U0$U0010U TSA2048-1-530  *H JkXD1y+LͰXn)^ʓR G'/8ɓN"b?7!Op18UN$ҩ'NzaA*^ݻ+>W~ +;R8'?J00eeR&.Y)"\0  *H 0_1 0 UUS10U VeriSign, Inc.1705U .Class 3 Public Primary Certification Authority0 090521000000Z 190520235959Z01 0 UUS10U VeriSign, Inc.10U VeriSign Trust Network1;09U 2Terms of use at https://www.verisign.com/rpa (c)09100.U'VeriSign Class 3 Code Signing 2009-2 CA0"0  *H 0 g`IoV|f^ q-!ќPL"5; .Z|=;%X{ ξ'tag'MjaXy'M4+G Df$fO8TrfujIh8y 0,`Hת809:|@T/ܨR>+!\ P4.M^%Ԍn|)]1ZՌgX5+!`x^{`W ]A cT`C!00U00pU i0g0e `HE0V0(+https://www.verisign.com/cps0*+0https://www.verisign.com/rpa0U0m+ a0_][0Y0W0U image/gif0!00+kπjH,{.0%#http://logo.verisign.com/vslogo.gif0U%0++04+(0&0$+0http://ocsp.verisign.com01U*0(0&$" http://crl.verisign.com/pca3.crl0)U"0 010UClass3CA2048-1-550Uk&pȡ?-50  *H ݔAaix0Ɛ<~B$s/DrPU nQj71ܥ-OM2NgUejzd8xEv1z`³]fvYI8VAwX00fgymPSo0  *H 01 0 UUS10U VeriSign, Inc.10U VeriSign Trust Network1;09U 2Terms of use at https://www.verisign.com/rpa (c)09100.U'VeriSign Class 3 Code Signing 2009-2 CA0 100729000000Z 120808235959Z01 0 UUS10U Massachusetts10 UWoburn10U Monotype Imaging Inc.1>0<U 5Digital ID Class 3 - Microsoft Software Validation v210U Type Operations10UMonotype Imaging Inc.00  *H 0Di|U 25L3^ L*8ט@I "SOCʋVnH9c;$5}rGWyˊJ@p-5cįפ {uePd"}KXEMYLM00 U00U0DU=0;09753http://csc3-2009-2-crl.verisign.com/CSC3-2009-2.crl0DU =0;09 `HE0*0(+https://www.verisign.com/rpa0U% 0 +0u+i0g0$+0http://ocsp.verisign.com0?+03http://csc3-2009-2-aia.verisign.com/CSC3-2009-2.cer0U#0k&pȡ?-50 `HB0 +700  *H N"gA~™cjrb<8=_G_[KI  VD 5< D`E*oL;4gpZ9\Zl5|eKmIp=b۴A~}n"w6MZ S1+(RzkwD ]%,͊0>KyʦN $ 񺐶\<'MU,:&lDOw( 0{ *H  1l0h0g0S1 0 UUS10U VeriSign, Inc.1+0)U"VeriSign Time Stamping Services CA8%a&Z0 +]0 *H  1  *H 0 *H  1 110505165508Z0# *H  1񤟓 A{l˨ߐ`0  *H b=Di?=դZ ޝ'WBʅmo*s^sz-A&FxӂkVG}|`A_-`E4QV 2gSgutdesktop-1.2.17/Telegram/Resources/fonts/OpenSans-Regular.ttf000066400000000000000000006504201326245125100241560ustar00rootroot000000000000000DSIGD;tGDEF&7|GPOS 7778GSUB+=7OS/2>`cmap)/hcvt Mfpgm~agasp#7lglyft8K%/headv<6hhea st$hmtx5<kernT+ ~U@6loca)4VmaxpC  names xpostCl@&+prepC !__< 51LL b  { V/\33f @ [(1ASC@ X H #5+3hq^R^=jVh?T!fd^+u^hj!!?hwho1y/ }s!}T#`'9;};}djmh{R3V1s^ss}sb'3qssDj91'R=hHh#?{h!{5dFRhTdmh1!=q!%LBP=K=.=o3 }ssss<T</;};};};};};}{s^s^s^s^s^s^^s}s}s}s}sqssssshss^s^s^ }s }s }s }ss/ss}ss}ss}ss}ss}s}b'}b'}b'}b'*T5TT #`3%''Y''/s;}s;}s;}sb}qDD`Ddjjdjjdjjdjjmmmh9{{RRRRRRs^^;}sdjj  -%o%!}/)'sR;}T9mH;}Jm{bj^mBP<{sZs qZss%FVqs3sss^/s s/ss)}djT<#`o/)wsJ9;} }mbjBD% =f3s^wm)}sD'ss)q1'-))9q%}smsj'7mh9h9h9{RRRJ\\?\={{F d%oRoP y'mbD?)w'5%BPf=G= =G=jf' Lhd%w bhhhoq';)93#Vy!TT\ 9qs^Ru3uu=}s%RS ;s}sfZm ^!# ?^m=}s  }}s}Bs}ws^}sju)))%/7/m#3=JDJ\D/#)/;}s }sm){{V')=F3=F3Td9;Ts^s^^s}suyfuyfJDJ;}s=}s=}s =97m)7/mR'1's1+s;NjPN/PN }s-)oZs^s^s^s-s^s^s^s^s^s^s^s^s}ss}ss}ss}ss]}Js}ss}ss}sT{T;}s;}s;}s;}a;}s;}s;}s=}s=}s=}s=}s=}s%R%R%R%R%R{{{sqqhyyyh1-4s-)^u^hjmZ\mqqqqq;;;;;;;;;V;^; 0HI~'2a7 # O_?M   " & 0 3 : < D p y  !!!! !"!&!.!^"""""""+"H"`"e% IJ(3b7 #P`>M   & 0 2 9 < D p t  !!!! !"!&!.!["""""""+"H"`"d%aIvhcb]gD Xz}} Bvt n%"iOS0L\pr`<&'()*+,-./0123456789:;<=>?@AIJ$%TUVWXY\]^_`abcdefhijklmnopqrstuvhijkFopqrstu45]^@G[ZYXUTSRQPONMLKJIHGFEDCBA@?>=<;:9876510/.-,('&%$#"! , `E% Fa#E#aH-, EhD-,E#F` a F`&#HH-,E#F#a ` &a a&#HH-,E#F`@a f`&#HH-,E#F#a@` &a@a&#HH-, <<-, E# D# ZQX# D#Y QX# MD#Y &QX# D#Y!!-, EhD ` EFvhE`D-, C#Ce -, C#C -,(#p(>(#p(E: -, E%EadPQXED!!Y-,I#D-, EC`D-,CCe -, i@a ,b`+ d#da\XaY-,E+)#D)z-,Ee,#DE+#D-,KRXED!!Y-,KQXED!!Y-,%# `#-,%# a#-,%-,CRX!!!!!F#F`F# F`ab# # pE` PXaFY`h:Y-, E%FRKQ[X%F ha%%?#!8!Y-, E%FPX%F ha%%?#!8!Y-,CC -,!! d#d@b-,!QX d#d b@/+Y`-,!QX d#dUb/+Y`-, d#d@b`#!-,KSX%Id#Ei@ab aj#D#!# 9/Y-,KSX %Idi &%Id#ab aj#D&#D#D& 9# 9//Y-,E#E`#E`#E`#vhb -,H+-, ETX@D E@aD!!Y-,E0/E#Ea``iD-,KQX/#p#B!!Y-,KQX %EiSXD!!Y!!Y-,EC`c`iD-,/ED-,E# E`D-,E#E`D-,K#QX34 34YDD-,CX&EXdf`d `f X!@YaY#XeY)#D#)!!!!!Y-,CTXKS#KQZX8!!Y!!!!Y-,CX%Ed `f X!@Ya#XeY)#D%% XY%% F%#B<%%%% F%`#B< XY%%)) EeD%%)%% XY%%CH%%%%`CH!Y!!!!!!!-,% F%#B%%EH!!!!-,% %%CH!!!-,E# E P X#e#Y#h @PX!@Y#XeY`D-,KS#KQZX E`D!!Y-,KTX E`D!!Y-,KS#KQZX8!!Y-,!KTX8!!Y-,CTXF+!!!!Y-,CTXG+!!!Y-,CTXH+!!!!Y-,CTXI+!!!Y-, #KSKQZX#8!!Y-,%ISX @8!Y-,F#F`#Fa#  Fab@@pE`h:-, #Id#SX<!Y-,KRX}zY-,KKTB-,B#Q@SZX TXC`BY$QX @TXC`B$TX C`BKKRXC`BY@TXC`BY@cTXC`BY@cTXC`BY&QX@cTX@C`BY@cTXC`BYYYYYYCTX@ @@ @  CTX@   CRX@ @@ @Y@U@cUZX  YYYBBBBB-,Eh#KQX# E d@PX|Yh`YD-,%%#>#> #eB #B#?#? #eB#B-,CPCT[X!# Y-,Y+-,-@ !H U UHU?MK&LK3KF%&4U%3$UJI3IF%3UU3U?GFF#3"U3U3UU3UOU3UoTS++KRK P[%S@QZUZ[XYBK2SX YKdSXBYss++^stu+++++t+st+++++++++++++st+++^NuHQw{jA_t[F4DdA  U  9 l  V , y $ K 6Or$yT(f'O(hGKSGcEl{+7HYj|*;L]n  * ; L ^ o !!(!8!H!X!i!z"""!"1"A"R"c"t"""###/#?#O#`#$ $$,$<$M$]$$$$$%%%%0%@%Q%a%r%%%%%%&:&K&[&l&|&&&&&&&&' ''*';'G'W'h'y'("(3(D(U(f(w(((((((()) ))L)])n)y))))))*-*>*N*Z*e*v***+'+8+H+Y+i+{++,i,z,,,,,,,,----.->-I-T-e-u-...%.6.F.W.g.y........///+/;/L/]/n/~//0w11'181I1Y1d1o111122T2{2233N3_3g3x33333333444"4*4244444455525:5q5y5556<666666677k788g899L999:,:4:_::;;\;;<%<]<==_=====> >>o>>>>>>?S???@@7@?@@@@@A,AAABBVcVVVW2WbWX,X>XPX}XXXXYY!Y@YuYYZMZnZ['['['['['['['['['['['['['['\q\\\]l]^ ^^-^9^E^W^^^^_@__`1`:`C`L`z`````aNaab;bbc?ccd,ddeiefg0g8g@ggh/hghyhii iijk;kl:l}lmm3m`mmnooop1ppqCq{qrrUrrsssPssttXttuu]uuvvsvwBwwwxx4xPbtӋ -BVbnÌՌ /AVj{͍ލ&8J\nɎَ (4@L]nҏ #4EVfrj‘2{͒;DNޔ nzqǖؖ .?J[gyΗ /2/3/3/310!!7!!IhyJh+@   OY ??+9/933310#3432#"&Fi3x:?@94D#FB@G?@  ?32993310#!#?(i)+)h)3@U     !  ! NY NYO O   /3?399//]]33+3333+339939939939223910!!#!#!5!!5!3!3!!!BTTRPD+RR1TT/BRRTLLT  &-f@5'%* ! ./%  MY$*LY*+MY*//99//92+33+33+3933333333310#5"&'53&&546753&'4&'6 ̷pCSYͥ˧4JYZocf#%/AE; N2_{eHY,{L\)]h- !-1E@$ ("".( 023 + + +010%?3?3??99//33933331032#"#"&5463232654&#"#"&54632#JSSJʙJTTPPTTJ˙Փ+TR۫ Jq 5Q@0#*+.+-#& 673 IY3'-0/&** / JY ?+?9/99?+93333106654&#"27%467.546326673#'#"&HWegVYoKo\,U=$į8CD+vE}XKSMa`DYfAuf_bj9k]y>cݲj\??9310#?(i)R! @   '??99331073#&R1 ή26=  @    '??993310#654'3 1:1V0@    ?29333910%'%7++uo^j^Foh) (@  PY/]3+3933310!!#!5!3dfV?m /9910%#67^b5}A drh2\T?q/399105!T٘ @  OY ?+931074632#"&=9:AB93CjCEECAF???9910#ߦ!Jf- (@  KY KY?+?+993310#"3232#"-ᖤr~r~';;% $@    ??9/9993310!#47'3ˢ4Xt.r+d%+@ KYLY?+?+93310!!5>54&#"'632!%?p8~[dXSu+8% =1*"% BC55FY;GY "***GY*(?GY(.GY?+?+?+99//99++993333310#"'332!"&5467&&5467&&5463232654&##"3254#"1,1+jJZ²t*9@EUkVEnq~Ztu~Hi#qG8U-+dP59UlKHDJA,>85GF4@  GY@FY?+?+393310%267# #5773!!,Ri*F`>^u  OPE{cj9H4@   FY ??+?393993331032653#'##"&5Lz 3tH9@QVH @   ??399910!3363`P u̲`HvD5M0#H,@    ?3?339933339910!&'##3366733663/4(ծjo 1ɴ8#;ѯ_HcPK95uu$'H "@  ?3?39991033##! ʼ1\DH$@  FY ?2?+991033663#"'53277O S)FLJ7DI=H_3|  RmH +@ GYGY?+?+93310!!5!5!!mV]qV=,@  '??933333310%&&54ᘡ463uq~xtض f\/hY\`2' '{@ ??93103#H,@ '??933333310&54'52"5665467 vz~;otnq?''a[Yhљ\f)rxhP)T$@ PYPY/+/+9910"56323267#"&'&&R56dDqYBb/66fH~HKZC6m&@9n! ^+@  OY "??+9/9333103##"&54632i3y<@      ??99//333393333310%#5&5%53&#"327iK11m6 >!3;?DH@&   NY LYKY?+?+9/3+393333102&#"!!!!5655#5346={}ZAJTM|d,/<{' @ "()%/33/399331047'76327'#"''7&732654&#"J^hf_JJ\fd\Jttrtzk\II\qvg\GI\k|pqrqV@.        ??399//]9223333933333103!!!!#!5!5!5!3H{`=ä<e {$@ ??99//9333103#3# {1=C@&2*8 #>?;6-! !'GY! GY ?+?+99333310467&&54632&&#"#"'532654&&'.7654&'VNJT^a5bLtt{RJڀNR0lsB1DU)V%(oUy'';@54&#"'632!YR!P?4bEBYJhVaL6DE&2XopP!9#9@" $%]mL ! !?3?39/]]]39310#"'53254##532654&#"'6632sRDt{uwgcPCBp8E?^Pg/8{DkOD=D+#Z-6w!    /99106673#0o ,@o>AA4DH5@  FY  ??+??399333331032653#'##"'##3V oX }@\T4q`'@ /3?39/93310####"&563!`rs>T-P3LZ @   OY/+93104632#"&>8:AB93CBEEBAF?%$@ /29/393310#"'532654&'733--;OQOmXn7aj j(6+5s'LJ @  ?2?9/9933103#47'R6CC[Z-_`B %@  ?3]2993310#"&5463232654&#"[hi\\ig\ozzzz{vvPu #@   /3/393310'7'7uuXuuuX iG_^EiiG_^EiK'&{< ?55.'?&{tN?5!&u'<m +?553wT^(A@"##)* && OY& IY#?+?+9/_^]9333103267#"&54>76655#"&54632NKay=zPb;ƾ#@Y6eAy;>B73F3zTjKM8dq&0`FiYR/Xt]+EB@G@s&$CR&+5s&$vR&+5s&$K#R&+5/&$RR&+5%&$j7R $&+55&$P9N@,   IYIY IY  IY?+??99//+++33933310!!!#!!!!!!#DTv/)}&&zs&(CR &+5s&(v?R&+5s&(KR&+5%&(jR !&+55<Vs&,CR &+5Tss&,vaR&+5s&,KR&+5<o%&,jR !&+55/H W@2 IY?  JY JY?+?+9/_^]3+39333310!!#53! !#!!3 Hw{Q|{b@ ?/&1RR&+5}s&2CyR&+5}s&2v R!&+5}s&2KR&&+5}/&2RR!&+5}%&2jR -&+55  @    /993310'7`^`e^dacc_c``e}#N@,  $%! !IY IY?+?9+99933910!"''7&!27'32&#"exl`Dѝaxjn`s'ejndOme^PLR2*Is&8CFR&+5s&8vR&+5s&8K}R &+5%&8jR '&+55{s&<v1R&+5y 6@  JY JY ??99//++99333310!##33 32654&##yḪʾꏤ0A@")*# *12*..&FY.*FY?+??+9/9333310#"'53254&'&&54676654&# #4632X8GNf³k?HSn`EGK@sFC! *93_eE'/KkFR{T?j59Z5PUL^!&DC&&+5^!&Dv+.&+5^!&DK3&+5^&DR.&+5^&Dj :&+55^&DP (&+55^s\)4;a@3*$08090 <=-'-FY11GY8$'"'5FY?3+3?39/993+3+39333399310467754&#"'66326632!!267# '#"&732655"!4&^tw4Jb)5nC:[TVe}QņkXy /D{T)5W_X`ku#'&!j_Ycm2s\&FzFs!&HC&+5s!&HvN$&+5s!&HK)&+5s&Hj 0&+55c!&CQ&+52!&v  &+5U!&K&+5&j &+55qb!&J@+!  '( FY  $FY?+?39/99+933310#"54327&''7&'774&# 326bd9I\^EfLϘ3 yֿl>1uIKkwr蓪D&QR&+5sb!&RC&+5sb!&RvV"&+5sb!&RK'&+5sb&RR"&+5sb&Rj .&+55h)3@  PY/+3/33/39333105!4632#"&4632#"&h;64:;34=;64:;34=<=?:9@?<=?:9@?sb#K@)  $%! FY !FY?+?9+999339910#"''7&327&#"4'326bpTr^ tTua5Kr3/Gq%EuN +LwLf5Ԥd}39!&XC&+59!&Xvq&+59!&XK#&+59&Xj! *&+55!&\v&+5u">@ $#   FY FY?+?+99??993333106632#"'##3%"3 4&XBjz H/YOӡ"M?5.4Z)&\j +&+55&$M?R&+5^b&DM(&+57&$N+R&+5^&DN%&+5B&$Q^BZ&DQ}s&&vR &+5s!&FvD &+5}s&&KR%&+5s!&FK%&+5}1&&OR &+5s&FOP &+5}s&&LR"&+5s!&FL"&+5Xs&'LXR&+5s&G8 #?5/Hs'd@7%()GY/    "FY FY?+?+99?9/_^]3+3?933333310%##"323&55!5!533##%26554&#" sw @&,SI%w#&(MR&+5sb&HM &+57&(NR &+5s&HN&+5&(Oo5&+5s&HOT$&+5B&(Qssa\&HQfs&(LR&+5s!&HL&&+5}=s&*KR*&+5'1!&JKP&+5}=7&*NR&+5'1&JNB&+5}=1&*OdR%&+5'1&JOK&+5};=&*9''1!&J:DF&+5s&+KR&+5D&KK%&+5T@,   IY JY ?3?399//33+33+9333333331053!533##!##55!ɪȪuP1DY@2   FY GY   /    ?3?9///]3+3+3933333310!4&#"##5353!!36632z? 1tT8O[\/&,RR&+5x&R &+5*&,MR&+52b&M&+57&,NR &+58&N&+5TBV&,Qh5B&LQTV1&,OPR&+5VH@ ??9310!#3VHT&,-l&LM`es&-KR&+5O!&7K&+5;&.9;&N9+F /@   ?3?399333103##3/b FqyFqs&/vcR&+5,&Ov &+5;&/91Y;W&O9&/8 ?5&O8+?5&/Og&OOB8 =@!     IY?+?99//99333103'73%!iC)C;reFy<' 7@      ??99//9339333107#'73VHѦnF`^p?THqw ?s&1vR&+5D!&Qvy&+5;?&19;D\&Q9V?s&1LR&+5D!&QL &+5'Q?5?8@  IY"?+??3999333310"'53265##33&53b6GSijzoN=XD\8@ FYFY?+??9?+933310"'53254&#"#336632%V7<>z 4nnjyHRX}&2MR&+5sbb&RM&+5}7&2NR&+5sb&RN&+5}s&2SR +&+55sb!&RSZ ,&+55}S@.  !IY IY  IY IYIY?+?+?+?+9/+933310!!# !2!!!!!"327&f\\@fZ'MDpWWjh)!uqZ*1U@-%/%23+( (FY.FY .. ""FY?3+3?39/99++393399310 '#"326632!!26732654&#"%"!4&}>щ>:~'J^WX!G  tw1 ,wrpyiw#'' 9ؤs&5vyR&+5'!&Uv&+5;&59}`;'\&U9s&5LR!&+5'!&ULv&+5js&6vPR.&+5js!&Vv.&+5js&6KR3&+5js!&VK3&+5j&6z'js\&Vzjs&6LR0&+5js!&VL0&+5;Z&79;F&W9Zs&7LR&+5&W8b?5Z?@!   JY IY?+3?9/3+3933310!5!!!!#!51H16ʪ/^FL@)  GY GY @FY?+?9/3+3+39333310%27# 5#53#5773!!!!U< j*ȍF`>-u\PE/&8RoR&+59&XR&+5&8MR&+59b&XM&+57&8NR&+59&XN&+5&8PR &+559&XP# &+55s&8SR %&+559!&XSh (&+55B&8Q!BeH&XQLs&:KTR(&+5#!&ZK+&+5{s&<KR&+5!&\K$&+5{%&<jR &+55R?s&=vBR&+5Rm!&]v&+5R?1&=ODR&+5Rm&]O&+5R?s&=LR&+5Rm!&]L&+5 @  FY??+39310!#!2&#"Vg`d+WIaY%{z D@$  !" FY FYFY?+?+9/3+3933310"'53265#5754632&#"!!HE@F=_MޢUxfy! @    /329106673#%6673#$n%:ae1e%:`0E?0D:?0s   /99106673#5 m1dHRJL +@  !"   /399//393310673#'4632#"&%4632#"&A!y3P4&)17#&44&)17#&4C=4.4.21124.4.211 &$T ?5LZyu &(}T?5 '+T?5D ',T?5 &2DT?5 '< T ?53 &v?T#?5&U .&+555$%@IY??+99310!#{'m((R?=+}?@   IY IY IY?+?+9/+99339910!!%! ! 32#"u`D;bs3?nhep0,*.TV,. @   ?3?99910!#&'#3ӶW!GZ^q0?1H% 4@  IY  IY IY?+?+9/+910!!!!!5Ru#Hy}2 #@ IY?3?+993310!#!#! Ch3J\ 5@   IY IY?+?+3933310355!!'!J+\`To+Z7{<j"+P@)' +,- **JY"$$JY??99//3+3+339333333310332###5#"$54663332654&+"33۬F)-Cι:9˴EùԷ;m>@  IY ??339/3+393333310!##"&&5333332653##-ϰ-z!dƻ{P9@    !IY  IY?3+333?+93310"!5!&5! !!5654!lb:;bk5v^6`xN<o%&,jR !&+55{%&<jR &+55ss&~T4&+5Zs&T/&+5Ds&T;&+5s&T&+5q&U; 4&+555s\ *G@$ '"+,'(( FY FY$ ?3+3?+993?9333310%26554&# "323673327#"&'#Pѓy6 )T!.AQY ;wPԋ) )TT\8BtIr wQVVQ)L@('"*+#"FY## FY FY?+?+99//+?93333102#"&'#46"32654&##532654&ymO䞝]Vp\з3*&41%{ H!@ ??39/3910#4733>3@+?^)+k05`&r,OBGo[sұsJ5٠Z\%M@+# &'%%FY%% %% !FY FY ?+?+9/_^]+993310# 3267#"&54675&&54632&&#"!˔ɓTdnbkad?^O=Zb'/Kb) \!-*so 0@!"#FY?+33?9333105!#654&'&&54>7!;}}o˼;p(ߦbvI%m[k8=$rD\/@   FY  ???9?+99333104&#"#336632z3qHQYIsJ+ I@'FY  FY  FY?+?+9/_^]+99333310#"322!"!Jy   jvk13)H@ FY ?+?993103267#"&5NIW%ei2Hhe   FF!"3@$#FY FY ??+93?+3910#'.#"5632327#"&'&'#:2C1:9D?[yX6k*#!0=JST X7UF$ < 13 yLS`tDHwH@  ?2?9993103363#SHC>Qqo1I@'- (%2300GY00&)%&%FY&#??+39/+99333310#"#6654&'&&54675&5467##5!#"33V2_T6C5BsǞًsD3Pb=$nZAcG47="Ȱ' @u2 P_slsb\RH6@    FY FY?+??+3393310%27#"5!##57!#}&0+T#ݏL3uFJDŶH,@ FY FY ?+?+39310!3267#"&5!57P/b#o0הH }JDqH%@  FY?+?3993310"&332654&'3s覞"$ X֌sL\"A@#   #$FY  FY??3+3??+93333310$746324&#"66σYQhڈy|fIN #(Zu|u#l&'xrPN 9@!"! FYFY?+??+?9391023327#"&'#&&#"566N>,>T0R?--<;s;Ь&F+%1N+[pazJvhDcP =@ FY ??3+3?3?933333106654&'3#$3Z%?ix& " sH'=@ & ()& FY#?2+3?39/99339310"54733265332654'3#"'#7DD9xk^ij]kx7EA9˶D A(؏}7ɀֶ &j %&+55q&j9 +&+55sbs&RT!"&+5qs&T'&+5ss&T1&+5%&(j'R !&+55BF@& IYIYIY?+??+39/+933310"'5326554&#!#!5!!!2`67[ehC|pq^s&avZR&+5}8@ IYIY IY?+?+9/+93310"!!327# !2&B)  ɡyNG379m_XRj6TV,<o%&,jR !&+55`h-##G@& $%#IY IY JY JY?+?+?+9/+933310!!!#"'532>!3 32654&###9TPkE@2?0A+7DAz:LƷfHy>gM|TJ@&   IY  JY?+??39/3+3933333310!!!#3!33 32654&##T}y9NĹfPnM}B:@  IY IY  ?3?9/++3933310!2#4&#!#!5!! ٪}}}~qs&vR&+5^&6DR&+5  0@  IY"??3+?3933310!!#!3!3 />}$} =@   IY IYJY?+?+9/+933310!!!!3232654&##}T^Ltᆳ'YTx%aJ C@$    IY " IY?+33?3?+93333310#!#3!3!!Jq Ή}3Y(<@    ?33?33933333933310333###V9:R<<<J5(C@$# )*JY &&JY& JY ?+?+9/+993310!"'532654&##532654&#"'6632`gᢉnuTe`O.2k2JrKMR4@   ?2?3993399333310333#47##˟4 ˺J%5R^&6R&+5 -@  ?3?399393310!##33\y<:-@ IY JY??+?+93310!#!'"'53266!٪%=]~J;6;5O=]8!EWYq0+}2 nh3}&Z7*@    IY?+?3993910"'5326733673%oT]`nBǼ g-T+eA1/T5껪Ojs; 2@  IY"??+3?3933310%3#!3!3 漢}-@ IY ??39/+9933310!##"&5332673Ǫjߪa\5'Eyt7y 1@  IY?+3?33933310!!3!3!3yPXX;@  "  IY?+33?33?933331033!3!33#ɪGH} =@  IY IY JY?+?+9/+933310#!!5!3 32654&##G~  ?@  IY JY?+?39/+?9333310333 #%32654&###3ɪܑ{R 2@ IY JY?+?9/+9933310#!3! ! 4&#!# +l =:@ IY  IY IY?+?+9/+93310"'632!"'53 !5!&ӬH9Sc13LTl9"!G@&     IY IY  IY?+??9/+?+93333310! !#3!! 32#" dQ3VqoUP7No2**.3N =@   JY  JY  ?3?+9/9+933310#&&54$!!##"!3{㷾{b3ϞJb~^ZDwT!";@ $# FY FY??+9/9+39333107$736632#" !"w >k1Lu h2=&:"!T`bs?h7LHI@&   !FY FY  FY ?+?+9/+99333310#!! 4&#!! 4&#!!26){o1{}~5ko ~oHYQPCLDH@FY??+99310!#!DFH)hH C@$    GY " FY?+33?3?+93333310#!#36!3!#hV+ l{ G69s\HF<@        ?33?3393333393331033###3Ŷ67F++3D\"M@+  !#$"!"!FY"" "" FY FY ?+?+9/_^]+993310 54#"'632#"'532654!#57M~f;ɽ~t큷ɘ*L9%gGVc]bH 4@      ????999933333103#77#LQϛHI9\Hb &6=&+5 H -@    ?3?3993933103##3/' H+HH-@ FY GY??+?+93310!#!#"'532!`v6 s#^ {/F5@  ??3?399399333310%773##&'#3+)ӓ:5+]v:JKwFInbH 9@  FY/?   ?3?39/]+99333310!3#!#VfH5Hsb\RHH#@ FY?3?+993310!#!#!VHu\Ss\F)H$@ FY??+39310!#!5!jFH\qFL@'     FY FY??3+3?3+3?9333333310#&5473%66Fٰ{%$.&DT''H[H 2@    FY"??+3?3933310#!3!33ݦyF{HGG-H-@  FY  ??39/+993331032673##"&5B[iiqHp8CH;oH 1@   FY?+3?33933310%!3!3!3A妏HG F;@    FY "??+33?339333310%!33#!3!3榝N妏IyFI)H =@   FY FY FY?+?+9/+933310!2#!!5!4&#!! -9%L|9]SyH ?@   FY FY?+?39/+?9333310!2#!3#3! 54&#V+9#zHH\TLH 2@  FY FY?+?9/+9933310! #!3!2654&#VR@ˢH\][U9}\D@&   FY    FYFY?+?+9/_^]+93310"'53267!5!&&#"'663 Vv<[ )g/7P 9$6#3\Q@-     FY FY     FY?+??9/_^]+?+93333310#"'!#3!663232654&#"3ᦦ!% H5%H =@    FY FY ?3?+9/9+9333103#&&5463!#!!!!";ʵ zNrs&Hj 0&+55D'f@:%%()! FYGY/ !!FY?+??9///_^]3+3+3933333310"'53254&#"##5353!!36632/O4:7zo 1tɉRT8O[D!&v&+5s\D@& FY  FYFY?+?+9/_^]+93310"32&#"!!327yR91m) t#* 3;9js\VfL&j &+55fMBHL@) FY  FY GY FY?+?+?+9/+933331032!!!#"'532!4&##3 Ke(8 s#P}> {[UFJ@&    FY  FY?+??39/3+3933333310!2!!!#3!3 54&#N` F;F7\TD !&v3&+5 &\6&+5FF 2@   " FY?+3?3?933310!!3!3!#/JFI#@ IY??+9933103!#fk-:D'@ GY??+993310!#!3D9HALs&:CR&+5#!&ZCs&+5Ls&:vR#&+5#!&Zv&&+5L%&:jdR /&+55#&Zj 2&+55{s&<CR &+5!&\Ca&+5Rq/399105!R\٘Rq/399105!R\٘Rq1N@  /3/3333210!5!5!5!NRR1D ?9910'673% b8{B%Z yD ?9910#75b5zF dr?mF ?9910#&'7%B{-m^e@   ?32910'63!'6738z{; b8{B%saZ y@  ?32910#7!#675b5zF '`8}B dr[zd4] 8 @ H H H+++55{ C@!     ?.333?9/333933333310%#53%11L11`_{u@:          ??99//9922333333333393333333333333310%%#553%%9a11Z++Z11a+{+|^  /93104632#"&qlitsjkry~|{w&'%d ; $/;F[@0 0B6<+%%+<B GH33(? "99-D D D?3??99//3333?33393333331032#"#"&5!2%#32654&#"#"&5!232654&#"#"&5!2S]]S8iՔ+S][YY[]S78Q][YY[]Q뢛8TRJݫ? Ru@ //993310RVw!w'EGPu@ //993310'7uuX iG_^EiJ&y??3310#yJm!&@    ?2?399333104&#"#3363 LNPr[t` K!TGizXeTb#K@(  NYLY LY ??+99//+3+39333310!!##53!!!!4̦D +DH%p@@ "  &'NY  ! NY !!!?!O! !!LYKY?+?+99//_^]3+33+39333333102&#"!!!!!!5655#535#53546ɞb4@4ȹRu}PEӁGMR?&q@? $ '( NYNY/ ""LY"LY?+?+99//_^]3+33+3933333310 !!!!327#"#53'57#5332&OA%˪.'$ɥG5m9@-BA *,P$aV +E@$% *  *,-# '  ??99//33?3?39333310##"&546323254#"%"&54632&#"327Ք+ʦhX!QPbZNJ۱#g!e%w$=@#  %&#     /3/399//99333310%273#"&5556746324#"$}_``NruίRC>oզ#q&򊟡Jh{+VlK'+_@1  "+(,-% ((()JY(?3?3?+99//9933933333310!###33&53#"&5463232654&#"5!ǻL"Q][OO[\RVl:Grvusspp %O@'    ?229/33333333393333310##5!###33#7#q{Xw˴gjj/R//PvfH4@  ! /?/2/39/]93933310"&546632!3267&&#"y1RQHbٓ2Xz#5Fi)|5BuG'\&{@` ?555 ''@uu ?555G'&= @q ,?555j'F'@m?1 ?555f5(A@"&)* "GY FYFY?+?+9/9+933310#"&546327!"56632267&&#"5쭬a+>0/J_xPeee53]KZ,!%Ɛalv'm (@    IY??+999331073!!&'Ϧ!=(DhNfy!#@ IY?3?+993310!#!wX ZL 1@   IY IY?+?+933331055!!!Lw@Ck3lh)@ PY/+99105!h%@   //9/933310##5!3o!Tw- !-3@ +% ./" ( /333/3993393310#"&'#"&54632632267&&#""32654&-]AJ;=:᰻jb-/p@@(10'PY/ *@*$PY*@PY/ @ PY /]+_^]++_^]+993310"56323267#"&'&&"56323267#"&'&&P69lCpXM[-56eCoXI[195jEtRE_173dEvOTU@9n%!B9m%D5m "B7n !"h)F@&  PY  PY/3+3/_^]3+393310!5!!5!!!!!'}T-}m}97h)&+t ?55h)&!+t ?55o= @  //999933103# oHh{"&SYNX)h )9#6@! %$ !?3?39/]93933310632&#"36632#"&2654&#")J14S qU}DQcXVUpjr+;~c]cO[Z;Y|9J@  ??39310!5!^9VJt^39"-?@" &+ ./ )))) !#?2?39/]39993333102#"&5467&&54632654&''"654&d|IUJ95TVZT]QHFDKDQNvhLJqtEt..]Df~farT #'+/37;?CGS[kt|@A@=<10 TNXHvkp`zgED)(%$ ;g`87/k4,H# N  *BZQ\t\)AF>duulE=}VKkvk&2%1 BA>\=l 12k \lkkl\-, 9854! /333333333/3333333339///999999333322339333233933333333333333333333333310!#%5!#533!5353!5!!5!5!#3#35!#35!35!#35#3#3#"&546323254#"%32##32654&##32654#"'53253T/0momImmmm0oowoooomm~smp.,=.m^{B.$*/;J1%Z^4+V}i0oo/mmmmmmoo;mmJoooo/yhIaCS1BD5QYb" "+%J fVr_cT*.@ % +,(""//9///33910 54676654&#"63232654&#"TV,AgIOGRZ?>1HT;GFBIHCHEVW/2A1R~X8*P:/5K6DpJ;?HI>@IHW!&7L&+5D +-6f@9 4%.+-% 78GY!.!GY+... ..((1FY(FY?+?+99//_^]3+3+933333310! 47654&#"'6323 4'&$&546323%&#"Vw$ 6!S_X]wɠ*{]aN.AnX9{z/# v']]#:p?,iׁ_K{(@ JY?+??993106632&#"#39zM\:0((;V|e##7l08U/wH)L@'!!' *+ FY$FY?2+3?+339/99339310"&54!57!##"'#32655332654')LJuȹDD?Blu]lk]umoJD綶΄g}zqu&0vT&+5!&Pv-&+5&$[5^Z&D[&2\G ?55u5 @   /32993310#"&546324&#"3265}fexxee~nB33B<95@axubbuva9<<98==h@    /9310673#%47#"&F)w1Ny%]7CzN9v=H)5JD'I&ILm'I&IOm}d!<@ "#  IY IY?+?+999333310! ! >5332#"aCE2:h`uqjh Cfi'1+'1s"<@ #$  FY FY?+?+999333310#"&532>5332654&#"b| ى3:yfG%ӊ +Acn&{3@   IY?+?39999333310>53! 533265:F!ԪƸ>pnFD@"    FY??+?3939933333310326536653#'##"&5LzRJ  4oF;>y RUS!C !vRs@   /23339310#'6654&#"563 s i VNCI> &E׌"q2++)d ;}  /33104632#"&;;*(::(*;9669777s&(CR &+5Rs&ChR&+5s!&HC&+5b!&C&+51E@$"*'/ '23IY((,%%IY ?3+3?39/9+3933310"'632#"&'## 32&&#"327332<^-E~lSPk|F-]<ϻffΥ/)Pa-32.wSxP)L7LK0 (H(@   ??339?910#33663363#&'  .J - ۶}!3HI]5$,RZ\L@( IYIYJY?+?99//+3+393333310!3!!3 !!!32654&##?^1hڶdf+z'G@&    FYFY  FY ?+?39/++393333310!!! #!#5353! 54&#X?!1Hͦ\T! J@) !"IYIY IY?+??9/3+3?+933310"!!327# !#3!%2&&= ˜dq0նHd379pTP3N\0&\!Y@2   "# FY  FY    FY?+??9/_^]3+3?+93333310"'!#3!6$32&#"!!3267w ᦦ! Q62e =wbn H3 3%9m 4@   IY  ?33?9/9+39310####3#!'&'߲hg\LR8@VVJ?ϐdb yH 5@   FY  ?33?9/9+39310#####!&&'#Ѭqsͬ!+8" HH-lj\^F@%    IY    ?333?39/93+33933310####!#3!3#!&'"_ff>v #PPPnJH5V/ChHM@+    FY/? ?333??9/]93+33933310#####!#3!#!FΪqnѬߦ^h Y HH3s"_"K@( !$#!!IYJY"?33?9/33+3+99933310#.####"#>75!)Zvd2#DeY[cA /cve {H;o`&B'_o7şI9 H #N@*!"%$" "FYGY# ?33?9/33+33+99933310#.####"#>75!WoI1":TL KR8'0InW %Hi0PiqPWG @^P=iO2`i$'a@5!&#'%'"# )(#$&$&IY!IY'!!$?333??9/33+33+99933333310#.####"#67!#3!5!=]xe-Fi_^dB!78Rh {H;hc(D(_l7:P酙7H$'g@:!&#'%'"# )(#$&$&FY!FY'/!?!!!$?333??9/]33+33+99933333310#.####"#67!#3!5!1XoI0":VJ KT7&/%ͦ5!%Hi1NirPVF?\Px(H5bi?N5K@M!?FF ?7C<*-( LMIJYI941../. .*@CJY<**$JY* IY IY# IY "?+?++?+39/+9_^]92?+933310327632&#"#"&5467665!#532654&#"'67&''536632&#"WYaxxFGPDiii̵@ᢉjnV9u1{\\@20+,o0抆72'3}~  k7ErrBy4;sVq RG 7{NF@N)6 . >2@<) GHD>AGYAA/A A>&FY#FY3232FY!#& >>8FY> ",GY?+??3+9///+9++_^]+99333102&#"32772&&##"&5467$54&##53 54#"'67&'53663-)/g-z]m0KYVz}'T7\Nw7JX;|~\g{KXNp O>k9Gʔ*,1+'wpt}aZ"$7ub4nUmu} G@%IY  IY  IY?+?+9/_^]+99333310! ! 2!"!&`D;ba + nhepD sb\ I@'FY  FY  FY?+?+9/_^]+99333310#"&532267!"!&&b|  i %ӊ +MXH @JY ??9?+3910"#367>32&;N9RH# F;TnY*O87gVǏA=R@ GY ??9?+3910!336>32&#"jdR`%G[T-&/:Hdv 5z{4 T\Hs&vR !&+55=!&vd "&+55}  .D@& !.'/0%*JY%  IY IY?+?+?393?+93310! ! 32#"%33663#"'532677TC,#EoN S+ELJ7B^u#=ohfp1+)/Af,  gYs{\&R\u}-(Q@* & "" )*$"& &IY IY?33+33?33+339333333310#"'$%6326632654'#"'w|+|y -!ʽI6nʽnqs,oo)61,ll,s)0&V)1/'XV's-P@*  +%#  ./(%++FY  FY ?33+33?33+33933333310#"&'&54766326632665%#"&' @89= >98@ P} <5g| =35< }%%6-+8$& $8*+9&*"Jү`>* ,};ETU@.C7++&FKPH< 7 UV R@H:"@:@IY(:4IY.4?3+3?3+329/393310#".#"#54632326732#"'632!"&'# 32&&#"5654.5432Txf+/<}tp:pwN(X=7]ҥ<_+FyhLKnyF+^<x$\8Cy$+$43gn$,$B?9HN- (+Rb(0-+uUvR+h=H)5IDs*?N\@3((,"@E JB6 OP2:?--6LB @ FYFY% FY?3+3?39++323293310"'#"32&#"!273 4&#"'6632#".#"#5463235654.5432+^\Ϻ>w(9YGtm1{p>oC-nsGY9(w>QTxe+k}sp:qvNw$\8CAA#( 3^P*&3 x$*$fdo%+%ݡ>H(8JD^ @_@40$96>6) $AB-'-IY77!' @ H  @';3!3IY!?3+3?322+239/9+3933310#'##'##'5"'632!"&'## 32&&#"3267332P 21!1/!PC<]-F|tL Np~F-]<ҾA3fԥgggg+)Pc001/rUvP)&&7LJ1 ( *?@$$ +,( @ H  #??3322+239?910#'##'##'5#33663363#&R2112P'') # - ۶}!gggg%_HIo#Q,RZ\}-@  IY IY  ??+?+93310"!27## 4$32&H o9HG3tmVTNs\/@  FYFY?+?+?93310"32&#"3267#uO01h5P9+"3nju/@!    ?9910'%7%7%yB!C!v!DA9CBsdu=Css@   /332993310#"&5463!6632#*03)*6+/3-,6-2255).0138(@   /233991027632#54#"##5xQot}j+fyTb;:odf$+$y5@  /93104632&C8\$w8EL6(J@5@   /93105654.5432w$\8C@J(6LE) (6DR_m@I_(DZ"> R6mL0gno:HHAOED>LVcc\jf_Zm,,%3/"(6OLjm3663mjLO  /3/39////////333333333333333333910&&#"#632&&#"#6632&&#"#6632!&&#"#6632&&#"#6632!&&#"#6632&&#"#632!&&#"#6632o4@%% >:),25 ?@;+.6/'$3 //9910#67'66737&'&&'57667'67'&'7&&'7 F$a5; Ia4#GA݁hBO݁CCExEx+REC{Lj'ZC&#BO݁GA܂ Ia5; F$a5'XDnXY?DnXFcEf=? 'Q@*$%()IY$ IY !IY ?+?+99//3+3933333310473337! !3267# "&"!&=q"M)(eʍr݂nI62 3Z!(X@/  !&! )*!" FY%  "FYFY?+?+99//3+3??93333310&'$54733376632!3267#"!4&տj" ebD= E2/;g#i *AHTV,`&6T&+5 &6&+5B@% IYIY JY ??39/9++?+933310"#337 !"&'53254$^_ObR|Fz{<T1  !HB@%  FYGYFY??39/9++?+933310!#33#"'532654&#"T7n̅_.lGR\H<&9@IY JY IY"??+?+?+933310%3##!'"'53266!ٸŜ%=]~J;6;5O=]8}!EWYF9@ FY GY FY"??+?+?+933310%3##!#"'532!߰}^v:q"yd =@   IY IY?+??39/+9333310%!"&'53 !#3!3RzM{1#Pn bHG@'   FY    FY?+??39/_^]+9333310"'53265!#3!3ӄ]of}vd :=H5D@$    IY   IY"??+??39/+933333310%3##!#3!3Ş}PnFD@$    FY  FY "??+??39/+933333310!33##!#Vf}F7IyF=@    IY "IY?+??39/+9333310!##3#"&5332673Ǫjߪa5'Eyt7-H=@   FY  " FY ?+??39/+933331032673##3#"&5B[iiqHp8C aH;)H@%   IY" ?3?3??+9933933333310!##!333##47#PǞ /^J}F?@     FY "??3+??39939333310%7733###&'#3+)Ӱ}:5)-]vIy:JKwF-nTV,^&$69R&+5^ &D6%&+5%&$j=R $&+55^&Dj :&+55^s\^&(6R &+5s &H6 &+5uX=@  IY  IY IY?+?+9/+933310"5663 ! 5!27!s҆Ko/5L& qqF N f\;@    FY FYFY?+?+9/+9333102#"55!&&#"566267!b_Y Í\ i̻!)("uX%&jR /&+55f&j 1&+55%&jR '&+55&j '&+55J5%&jR >&+55D&j 8&+55J7@@#IYJY JY?+?9/++3933310! '532654&##5!5!$`jߌN? O.2ޙH@@# FYGY FY?+?9/++3933310#"'532654&##5!5!ꊷȡy8rʈFVr{R&MR&+5bb&M1&+5R%&jR %&+55b&j= #&+55}%&2jR -&+55sb&Rj .&+55}~sb\}%&~jR /&+55sb&j 0&+55=%&jR 0&+559}&j 0&+55&M/R&+5b&\M&+5%&j;R ,&+55&\j +&+55s&SR *&+55!&\S )&+55%&jjR )&+55-&j (&+55 -@   IY "IY?+??+93310!!3##?k}}BF -@   FY "FY?+??+93310!!3##Fy %&jR -&+55y&j ,&+55/u&uBH&uu&;X'u4H&[;@"     IY  ?3?39/993+3910!33!!##!3wklp<wpTbED}'H;@"     GY  ?3?39/993+3910!33!!##!u! hʼfw\/ D7 4@ IYJY??+9/+99333104$!33! $#"33$ ƪc ¶pJ|s7Gw#F@$ ##$%IY JY?2+3?99//9+93339310"&54$!3332653#"&'#"!265N*"dyϸv3q)!p{nRZwps".Q@), &&/0 *FY##FY ?3+3?+9/99?933339310%2653#"&'##"323&&53!26554&# vkȽ+Kj? mw9[qq[)  /MUp#NN*K@(("" +,JY  %%IY% JY?+?+99//+9933310#532654&#"'663232653#"&'&&՚ggT]bl|wpҽl7ErHPħ3іy)ȖP\%K@( $$ &'!FYFYFY?+?+99//+9933310%23# &&##53 54#"'6632Bݦo!KM9Uhc{ w 9McX$"($9zjN#J@(#! #$%JY##IY#!" JY?+??+9/+99333104&##532654&#"'66323##ᤇiiTaìk:BrJNħ}PZJ@(  FYFY" FY?+??+9/+99333104!#53 54&#"'6323##˖u9w=˿~p-ǍRPFJ9 %fy!#:@## $%IY  JY ?3+3?+9/93310!#"'53266!32653#"&5 H+LSdE@2?1@,8J7ospqͼDf>hωyy))F:@FYGY ?3+3?+9/93310323#"&5!#"'532!hwզ^v:q"q ;=d ^C@#  IY IY ?+??399//+9333331032653#"&5!#3!3nspqȿ'٪yy)3PnHM@*  FY   FY?+??399//_^]+93333310!3323#"&55!#VPjwզH5= 9sH}:@IY IY IY?+?+9/+93310!! 4$32&&# 3 !f4UxSBZW V`T1'&.s\:@ FY FY FY?+?+9/+93310!! !2&#"3265!C!ԯ;ũ?C'+PJߠ9@  IYIY?+3?+9/933105!!323#"&5&+5&${R &+55^&D{ -&+55&$|R &+55^&D| -&+55X&$}R !&+55^&D} 7&+55^&$~R '&+55^ &D~ =&+55I&$'N-dg&+5^&D&Ngy%&+5&(gs\&Hg&(fR&+5s&Hf&+5/&(RR&+5s&HR$&+5o&(wR &+55s\&Hw !&+55]&(xR &+55J&Hx !&+559J&(yR &+55s&Hy !&+55b&(zR *&+55s&Hz 9&+55s&('gKR%&+5s!&H'gK4&+5TV&,fR&+5{&fs&+5TV&,gf&Lgb}&2gsb\&Rg}&2fR&+5sb&Rf&+5}&2w}R &+55su&Rw &+55}&2x}R &+55ab&Rx &+55}J&2y{R &+55sb&Ry &+55}b&2zyR 6&+55sb&Rz 7&+55}s&2'gKR1&+5sb!&R'gK2&+5}ds&_v+R+&+5s!&`vm+&+5}ds&_CR#&+5s!&`C$&+5}d&_fR&&+5s&`f'&+5}d/&_RR+&+5s&`R#&+5}d&_g{s&`g&8gJ9H&Xg&8fTR&+59&Xf&+5{s&avR%&+5!&bvy&&+5{s&aCZR&+5!&bC&+5{&af`R &+5&bf"&+5{/&aRR%&+5&bR&+5{&agL&bg{&<gH&\g{&<fR &+5&\fj&+5{/&<RR&+5&\R&+5s&B! @   /32339910#&&'53#&&'53`4%c1`8%c1*?=D,?=Dq (@   /399//9339910#&'#57673'673#^pcra^5p4BPI6Sx`K[eA<{M^[pn` *@   /399//9339910#&'#57673%#&'53^arji^5p4B_xT4KAe`F<{M^^plaq{ 4@!   /3299//93339310#&'#57673#'6654&#"5632^pcra^5p4BP 9?9+.7K[eA<{M^{gQ &%Ph%:@ '"  " "/9///3339339910".#"#663232673#&'#57673-%GC?(*[ eK%IC>(* Z c^^arji^5p4B5%12jq$11hsAe`F<{M^y$@ @ /32339910673# 332673^P1Vw`>f LjbVi her]H9A@xy$@ @ /32339910#&'53 332673^wV4K5f LjbVi ]rlaH9A@xy.@  /239/2339310#'6654&#"5632 3326731R 9B9,%$>f LjbVi yd)Z %%NH9A@xh $0@" & @ !/2339/3329910".#"#663232673 332673-%GC?(*[ dL%IC>(* Z cf LjbVi 3$02hq$11grH9A@x1Bm@   /293104'3#"'5326ߋ{fcA2 6%3gx[gl 0uq @  /299310%#"'5325q8<)=^d0uq @  /299310%#"'5325q8<)=^d%4Cxss (@  KY &MY?+?+993310#"3232654&#"/55-7^ &@   ??99//993310!#47'37C>Z1C0pr#)s,@ KY&LY?+3?+9310!!5>54&#"'632!Rq,wX\Zڂ/whSAWg=Jms^t'G@&" ()KY %%KY%& KY %?+?+9/+9933310!"&'53 !#532654&#"'6632t[_`{^ȓ~`mTZ#,/1) kz4FpGQf^ B@!  MY $??9/933+393333310%##!533!47#f٨2 )D9s}D\V\_:@KYLY KY%?+?+9/+933102#"'53265!"'!!6- Fe^V7%s&O-327Iu/^+_@LY$??+9310!5!^h)j%t%A@"" &'MY KY&MY%?+?+9/9+933310!"'532##"&5432"326654&&%htDPf 7rЕx[XR)3SW0JFif'II\"3Z@.,00.*&&( (.54+1$-/-)/##( ())?3/32/3993339933333310#"'53254&&'&&54632&#"##33#7#H|Jjw6UxQn}\"dS7@7Cq7D\7F\7G7Hq7J7777777 7W7Xq7Y\7_7`q7b7q7\7q7 \7!q7"\7#q7%q7&\7'q7(\7)q7*\7+q7,\7-q7.\7/q70\71q72\73q74\76q78q7:q7<@<C<D<F<G<H<J<< <W<X<Y<_<`<b<<<< <!<"<#<%<&<'<(<)<*<+<,<-<.</<0<1<2<3<4<6<8<:<<<@<B<D<I<J<K<L<M<N<O<Q<R<S<T<U<V<W<X<Y<Z<[<\<]<^<_<`<b<d<f<h<j<l<n=&=*=2=4====================G=_=I=K=M=O=Q=S=U=W=Y=[=]=_>-DD DD EE EYEZE[E\E]EE7E<E>E@EEEE EpF)F )F)F )HH HYHZH[H\H]HH7H<H>H@HHHH HpI{I {I{I {KK KK NFNGNHNRNTNNNNNNNNNNNNNNNNNNNNNNNNNNNNHN`N6N8N:N<N@NBNDNJNLNNNRNTNVNXNZN\N^N`PP PP QQ QQ RR RYRZR[R\R]RR7R<R>R@RRRR RpSS SYSZS[S\S]SS7S<S>S@SSSS SpURU RUDUFUGUHUJURUTUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUDUFUHURU RUYU`UU U"U&U(U*U,U.U0U2U4U6U8U:U<U@UBUDUJULUNURUTUVUXUZU\U^U`W)W )W)W )YRY RYYY")YRYY RY ZRZ RZZZ")ZRZZ RZ [F[G[H[R[T[[[[[[[[[[[[[[[[[[[[[[[[[[[[H[`[6[8[:[<[@[B[D[J[L[N[R[T[V[X[Z[\[^[`\R\ R\\\")\R\\ R\ ^-q q&*- 247q9:<$q&q68:Gq q_IKMOQSUWY[]_oqsqq q&*- 247q9:<$q&q68:Gq q_IKMOQSUWY[]_oqsqq q&*- 247q9:<$q&q68:Gq q_IKMOQSUWY[]_oqsqq q&*- 247q9:<$q&q68:Gq q_IKMOQSUWY[]_oqsqq q&*- 247q9:<$q&q68:Gq q_IKMOQSUWY[]_oqsqq q&*- 247q9:<$q&q68:Gq q_IKMOQSUWY[]_oqsq-{&*24G_IKMOQSUWY[]_-{-{-{-{$79:;<=$&68:;=?C X!#%')+-/13oqs$79:;<=$&68:;=?C X!#%')+-/13oqs$79:;<=$&68:;=?C X!#%')+-/13oqs$79:;<=$&68:;=?C X!#%')+-/13oqs$79:;<=$&68:;=?C X!#%')+-/13oqs$79:;<=$&68:;=?C X!#%')+-/13oqs$79:;<=$&68:;=?C X!#%')+-/13oqs$C X!#%')+-/13$C X!#%')+-/13$C X!#%')+-/13$C X!#%')+-/13")$&*24DFGHJPQRSTUVX] !+-/135<>@CDFGHJ WXY_`b !"#%&'()*+,-./0123468:<@BDIJKLMNOQRSTUVWXYZ[\]^_`bdfhjln$;=;=?C X!#%')+-/13             YZ[\]7<>@ p YZ[\]7<>@ p YZ[\]7<>@ p YZ[\]7<>@ p YZ[\]7<>@ p YZ[\]7<>@ p YZ[\]7<>@ p YZ[\]7<>@ p   YZ[\]7<>@ pR R")R R  YZ[\]7<>@ pR R")R R q q&*- 247q9:<$q&q68:Gq q_IKMOQSUWY[]_oqsq  q q&*- 247q9:<$q&q68:Gq q_IKMOQSUWY[]_oqsq  q q&*- 247q9:<$q&q68:Gq q_IKMOQSUWY[]_oqsq  &*24G_IKMOQSUWY[]_&*24G_IKMOQSUWY[]_&*24G_IKMOQSUWY[]_&*24G_IKMOQSUWY[]_$79:;<=$&68:;=?C X!#%')+-/13oqsR R "@E=K=N=O=`={R R$79:;<=$&68:;=?C X!#%')+-/13oqs-{ YZ[\]7<>@ p-{ YZ[\]7<>@ p-{ YZ[\]7<>@ p-{ YZ[\]7<>@ p-{ YZ[\]7<>@ p  &*24G_IKMOQSUWY[]_FGHRTH`68:<@BDJLNRTVXZ\^`FGHRTH`68:<@BDJLNRTVXZ\^`\ \&*24789:<$&*,.02468:G\ \_aIKMOQSUWY[]_acegikmoqs\ \&*24789:<$&*,.02468:G\ \_aIKMOQSUWY[]_acegikmoqs\ \&*24789:<$&*,.02468:G\ \_aIKMOQSUWY[]_acegikmoqsR R "@E=K=N=O=`=R R\ \&*24789:<$&*,.02468:G\ \_aIKMOQSUWY[]_acegikmoqs\ \&*24789:<$&*,.02468:G\ \_aIKMOQSUWY[]_acegikmoqs  $79:;<=$&68:;=?C X!#%')+-/13oqs$79:;<=$&68:;=?C X!#%')+-/13oqs$79:;<=$&68:;=?C X!#%')+-/13oqs-{R RDFGHJRTDFHR RY` "&(*,.02468:<@BDJLNRTVXZ\^`R RDFGHJRTDFHR RY` "&(*,.02468:<@BDJLNRTVXZ\^`R RDFGHJRTDFHR RY` "&(*,.02468:<@BDJLNRTVXZ\^`$$$$")$$q$&$*$2$4$7)$D\$Fq$Gq$Hq$Jq$P$Q$Rq$S$Tq$U$V$X$Y$Z$[$\$]$q$q$q$q$q$q$$$$$$$$q$\$\$\$\$\$\$q$q$q$q$q$q$q$q$q$q$q$$$$$$q$\$q$\$q$\$$q$$q$$q$$q$q$q$q$q$q$q$q$$q$$q$$q$$q$$$$ $$q$$q$$q$$q$$$$!$$)$&)$+$-$/$1$3$5$7$<$>$@$Cq$D\$F\$G$Hq$J$$$$$$$ $W$Xq$Y\$_$`q$b$q$\$q$ \$!q$"\$#q$%q$&\$'q$(\$)q$*\$+q$,\$-q$.\$/q$0\$1q$2\$3q$4\$6q$8q$:q$&@&Cq&D\&F\&G&Hq&J&&&&&&& &W&Xq&Y\&_&`q&b&q&\&q& \&!q&"\&#q&%q&&\&'q&(\&)q&*\&+q&,\&-q&.\&/q&0\&1q&2\&3q&4\&6q&8q&:q&(@(Cq(D\(F\(G(Hq(J((((((( (W(Xq(Y\(_(`q(b(q(\(q( \(!q("\(#q(%q(&\('q((\()q(*\(+q(,\(-q(.\(/q(0\(1q(2\(3q(4\(6q(8q(:q(8@8C8D8F8G8H8J88 8W8X8Y8_8`8b8888 8!8"8#8%8&8'8(8)8*8+8,8-8.8/808182838486888:8<8@8B8D8I8J8K8L8M8N8O8Q8R8S8T8U8V8W8X8Y8Z8[8\8]8^8_8`8b8d8f8h8j8l8n9R9 R999")9R99 R9 :::"):$:&:*:2:4:D:F:G:H:J:P:Q:R:S:T:U:V:X:]:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ::::::::::::!:+:-:/:1:3:5:<:>:@:C:D:F:G:H:J:: :W:X:Y:_:`:b:::: :!:":#:%:&:':(:):*:+:,:-:.:/:0:1:2:3:4:6:8:::<:@:B:D:I:J:K:L:M:N:O:Q:R:S:T:U:V:W:X:Y:Z:[:\:]:^:_:`:b:d:f:h:j:l:n;&;*;2;4;;;;;;;;;;;;;;;;;;;;G;_;I;K;M;O;Q;S;U;W;Y;[;];_=&=*=2=4====================G=_=I=K=M=O=Q=S=U=W=Y=[=]=_?&?*?2?4????????????????????G?_?I?K?M?O?Q?S?U?W?Y?[?]?_CqC qC&C*C- C2C4C7qC9C:C<CCCCCCCCCCCCCCCCCCCCC$qC&qC6C8C:CGCCCCCqC qC_CICKCMCOCQCSCUCWCYC[C]C_CoCqCsCqDD DD E-{GGG$G7G9G:G;G<G=GGGGGGGGGGG$G&G6G8G:G;G=G?GCGGGGGGG GXGGG!G#G%G'G)G+G-G/G1G3GoGqGsGVqV qVfVmVqqVrVsVuVxVqV qVT[[[V[_[b[d[i[p[q[r[t[u[x[[[ [T\\\V\_\b\f\i\m\s\v\y\z\{\|\}\~\\\\\\\\\\\\\\\\\\\\ \!]q]r]x]T^^ ^^ _q_ q_f_m_qq_r_s_u_x_q_ q_T```V`_`b`i`t`` aaaaV\a_\ab\afai\amasavayqaza{a|a}a~qaaaaaaaaaqaaqaqaaqaaaaaqaaaaaaa a!aSbqb qbfbmbqqbrbsbubxbqb qbTdfdmdsfffVf_fbfdfifpfqfrftfufxfff fThfhmhshhiqi qifimiqqirisiuixiqi qiTmmmVm_mbmdmimpmqmrmtmumxmmm mToooVo_obodoiotooo qqqqV\q_\qb\qfqi\qmqsqvqyqqzq{q|q}q~qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq q!qSrrrVr_rbrfrirmrsrvryrzr{r|r}r~rrrrrrrrrrrrrrrrrrrr r!sssVs_sbsdsispsqsrstsxsss sTtftmtsttuuuVu_ubufuiumuu vqvrvxvTxxxVx_xbxfxixmxsxvxyxzx{x|x}x~xxxxxxxxxxxxxxxxxxxx x!y){{ {{ || |||| ~) y~     y~ y~         y~       n| q)qqqqqq jqklmqrqsuwy}~qqqqqqq)qqqqqqqqq q q q qqql~    n|   n|   n| l|~  qff jlrqs~ffqqq q  q q qqq qn|q qq  r|  q)qqqqqq jqklmqrqsuwy}~qqqqqqq)qqqqqqqqq q q q qqq)l|~    l|~   rsz|  rsv  l~  q)qqqqqq jqklmqrqsuwy}~qqqqqqq)qqqqqqqqq q q q qqqqff jlrqs~ffqqq q  q  rsz l|~  ))  n|   n|  rsz|  rsz|   ) js      j  )j       js   s  j        m  m         js    m  m  j   sq)qqqqqq jqklmqrqsuwy}~qqqqqqq)qqqqqqqqq q q q qqq js  ")$&*24DFGHJPQRSTUVX !+-/135CDFGHJ WXY_`b !"#%&'()*+,-./0123468:<@BDIJKLMNOQRSTUVWXYZ[\]^_`bdfhjlnR R")R R ")$&*24DFGHJPQRSTUVX !+-/135CDFGHJ WXY_`b !"#%&'()*+,-./0123468:<@BDIJKLMNOQRSTUVWXYZ[\]^_`bdfhjlnR R")R R ")$&*24DFGHJPQRSTUVX !+-/135CDFGHJ WXY_`b !"#%&'()*+,-./0123468:<@BDIJKLMNOQRSTUVWXYZ[\]^_`bdfhjlnR R")R R ")$&*24DFGHJPQRSTUVX] !+-/135<>@CDFGHJ WXY_`b !"#%&'()*+,-./0123468:<@BDIJKLMNOQRSTUVWXYZ[\]^_`bdfhjlnR R")R R 7$&q7$&q7$&q$q7)9):)<DFGHJPQRSTUVXqqqqqqqqq !$)&)+-/1356)8:CqDFHJVq_qbqiqyz{~qqqq)))WXqY`bjrqsq}qqqq q  q qqqq !q"#q%q&'q()q*+q,-q./q01q23q468:<@BDJLNRTVXZ\^`bdfhjlnoqs)$q7)9):)<DFGHJPQRSTUVXqqqqqqqqq !$)&)+-/1356)8:CqDFHJVq_qbqiqyz{~qqqq)))WXqY`bjrqsq}qqqq q  q qqqq !q"#q%q&'q()q*+q,-q./q01q23q468:<@BDJLNRTVXZ\^`bdfhjlnoqs)&*247q89:<$q&q*,.02468:Gfmqqrsuxqqq\qT_al|\~qqq\\\\ qIKMOQSUWY[]_acegikmoqsq $q 7) 9) :) < D F G H J P Q R S T U V X q q q q q q                        q  q  q                             ! $) &) + - / 1 3 5 6) 8 : Cq D F H J Vq _q bq iq y z { ~              q    q  q       q ) ) )  W Xq Y ` b j rq sq }         q q  q  q         q    q    q    q q  q   !q " #q %q & 'q ( )q * +q , -q . /q 0 1q 2 3q 4 6 8 : < @ B D J L N R T V X Z \ ^ ` b d f h j l n o q s ) & * 2 4 7q 8 9 : <                         $q &q * , . 0 2 4 6 8 : G f m qq r s u x  q  q   q  \ q        T _ a l |\ ~        q    q    q  \  \    \     \        q  I K M O Q S U W Y [ ] _ a c e g i k m o q s q!q!r!x!TSSSS TTTVT_TbTfTiTmTsTvTyTzT{T|T}T~TTTTTTTTTTTTTTTTTTTT T!XqX qX&X*X- X2X4X7qX9X:X<XXXXXXXXXXXXXXXXXXXXX$qX&qX6X8X:XGXXXXXqX qX_XIXKXMXOXQXSXUXWXYX[X]X_XoXqXsXqYY YY ZZZVZ_ZbZdZiZpZqZrZtZuZxZZZ ZT`IR`WR`Yf`Zf`[f`\f`f`%R`'R`7f`f`f`4R`5R`]R`^R`pf`R`RbIfbWfbYfbZfb[fb\fbfb%fb'fb7fbfbfb4fb5fb]fb^fbpfbfbfjj jj llllllllllllllllllllll lrlslzl|lllllllllllllllllllllllllllll llllllmmmmmmm msmmmnn nnnnnnnnn n|nnnnnnnnnnnnnnn nnnoo ooooooooo omooooooooooooooooooooooooooopppppplp~ppppppppppppppppp p ppprqr qrrrqrrrrrrrqr qrnr|rrrrrrrrqrrrrrrrrrrrrrrrrrrr qrrqrrrsqs qssssssssssssqs qsjsms}sssssssssssssssssssssssssssss s ssssstqt qtttqtttttttqt qtnt|ttttttttqttttttttttttttttttt qttqtttuqu quuuuuuuuuuuuqu qujumu}uuuuuuuuuuuuuuuuuuuuuuuuuuuuu u uuuuuv vx xzzzz zzzzz z|q|q||||||||q| q|r|s|||||| | ||||}} }}}}}}}}} }}}}}}}}}}}}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~ ~r~s~z~|~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~  qff jlrqs~ffqqq q  q  sqff jlrqs~ffqqq q  q  s s s rsz|    rsz|    rsz|  sl~  )    |   m rsv    )))))) jklqrsuwy}~)))))    ) js   f |   l{=}~j    )l{=}~j  l|~  j  l|~  j  l|~  j  )  )))))) jklqrsuwy}~)))))    ) js  \ \fH\ \|qqH HHq qq qmqq   l~  q)qqqqqq jqklmqrqsuwy}~qqqqqqq)qqqqqqqqq q q q qqq js  )) jklqrsuwy}~))     s)) jklqrsuwy}~))     sl{=}~j  )  )    |         l|~  j   f m| )  )  )  q qqq qn|q qq  q qqq qn|q qq       rsz|    rsz|   l|~  j    )   rsz|    rsz|    rsz|    rsz|   qff jlrqs~ffqqq q  q  sqff jlrqs~ffqqq q  q  sqff jlrqs~ffqqq q  q  sq)qqqqqq jqklmqrqsuwy}~qqqqqqq)qqqqqqqqq q q q qqq js  )))))) jklqrsuwy}~)))))    ) js  l{=}~j  l|~  j          q             r v |       q                                                                           q             r v |       q                                      )   q rv|q    q rv|q    r    r|   )  q q&*- 247q9:<$q&q68:Gq q_IKMOQSUWY[]_oqsq  q q&*- 247q9:<$q&q68:Gq q_IKMOQSUWY[]_oqsq     !q! q!&!*!- !2!4!7q!9!:!<!!!!!!!!!!!!!!!!!!!!!$q!&q!6!8!:!G!!!!!q! q!_!I!K!M!O!Q!S!U!W!Y![!]!_!o!q!s!q"" "" #q# q#&#*#- #2#4#7q#9#:#<#####################$q#&q#6#8#:#G#####q# q#_#I#K#M#O#Q#S#U#W#Y#[#]#_#o#q#s#q$$ $$ %q% q%&%*%- %2%4%7q%9%:%<%%%%%%%%%%%%%%%%%%%%%$q%&q%6%8%:%G%%%%%q% q%_%I%K%M%O%Q%S%U%W%Y%[%]%_%o%q%s%q&& && 'q' q'&'*'- '2'4'7q'9':'<'''''''''''''''''''''$q'&q'6'8':'G'''''q' q'_'I'K'M'O'Q'S'U'W'Y'[']'_'o'q's'q(( (( )q) q)&)*)- )2)4)7q)9):)<)))))))))))))))))))))$q)&q)6)8):)G)))))q) q)_)I)K)M)O)Q)S)U)W)Y)[)])_)o)q)s)q** ** +q+ q+&+*+- +2+4+7q+9+:+<+++++++++++++++++++++$q+&q+6+8+:+G+++++q+ q+_+I+K+M+O+Q+S+U+W+Y+[+]+_+o+q+s+q,, ,, -q- q-&-*-- -2-4-7q-9-:-<---------------------$q-&q-6-8-:-G-----q- q-_-I-K-M-O-Q-S-U-W-Y-[-]-_-o-q-s-q.. .. /q/ q/&/*/- /2/4/7q/9/:/</////////////////////$q/&q/6/8/:/G/////q/ q/_/I/K/M/O/Q/S/U/W/Y/[/]/_/o/q/s/q00 00 1q1 q1&1*1- 121417q191:1<111111111111111111111$q1&q16181:1G11111q1 q1_1I1K1M1O1Q1S1U1W1Y1[1]1_1o1q1s1q22 22 3q3 q3&3*3- 323437q393:3<333333333333333333333$q3&q36383:3G33333q3 q3_3I3K3M3O3Q3S3U3W3Y3[3]3_3o3q3s3q44 44 5-{66 6Y6Z6[6\6]6676<6>6@6666 6p7-{88 8Y8Z8[8\8]8878<8>8@8888 8p9-{:: :Y:Z:[:\:]::7:<:>:@:::: :p;-{<< <Y<Z<[<\<]<<7<<<><@<<<< <p=-{>> >Y>Z>[>\>]>>7><>>>@>>>> >p?-{@@ @Y@Z@[@\@]@@7@<@>@@@@@@ @pA-{BB BYBZB[B\B]BB7B<B>B@BBBB BpC-{DD DYDZD[D\D]DD7D<D>D@DDDD DpIII$I7I9I:I;I<I=IIIIIIIIIII$I&I6I8I:I;I=I?ICIIIIIII IXIII!I#I%I'I)I+I-I/I1I3IoIqIsIJJ JYJZJ[J\J]JJ7J<J>J@JJJJ JpKKK$K7K9K:K;K<K=KKKKKKKKKKK$K&K6K8K:K;K=K?KCKKKKKKK KXKKK!K#K%K'K)K+K-K/K1K3KoKqKsKLL LYLZL[L\L]LL7L<L>L@LLLL LpMMM$M7M9M:M;M<M=MMMMMMMMMMM$M&M6M8M:M;M=M?MCMMMMMMM MXMMM!M#M%M'M)M+M-M/M1M3MoMqMsMOOO$O7O9O:O;O<O=OOOOOOOOOOO$O&O6O8O:O;O=O?OCOOOOOOO OXOOO!O#O%O'O)O+O-O/O1O3OoOqOsOQQQ$Q7Q9Q:Q;Q<Q=QQQQQQQQQQQ$Q&Q6Q8Q:Q;Q=Q?QCQQQQQQQ QXQQQ!Q#Q%Q'Q)Q+Q-Q/Q1Q3QoQqQsQSSS$S7S9S:S;S<S=SSSSSSSSSSS$S&S6S8S:S;S=S?SCSSSSSSS SXSSS!S#S%S'S)S+S-S/S1S3SoSqSsSUUU$U7U9U:U;U<U=UUUUUUUUUUU$U&U6U8U:U;U=U?UCUUUUUUU UXUUU!U#U%U'U)U+U-U/U1U3UoUqUsUXIRXWRXYfXZfX[fX\fXfX%RX'RX7fXfXfX4RX5RX]RX^RXpfXRXRZIRZWRZYfZZfZ[fZ\fZfZ%RZ'RZ7fZfZfZ4RZ5RZ]RZ^RZpfZRZR\IR\WR\Yf\Zf\[f\\f\f\%R\'R\7f\f\f\4R\5R\]R\^R\pf\R\R^IR^WR^Yf^Zf^[f^\f^f^%R^'R^7f^f^f^4R^5R^]R^^R^pf^R^R`IR`WR`Yf`Zf`[f`\f`f`%R`'R`7f`f`f`4R`5R`]R`^R`pf`R`Raaa$aaaaaaaaaaCaa aXaaa!a#a%a'a)a+a-a/a1a3fIffWffYffZff[ff\ffff%ff'ff7ffffff4ff5ff]ff^ffpfffffhIfhWfhYfhZfh[fh\fhfh%fh'fh7fhfhfh4fh5fh]fh^fhpfhfhfjIfjWfjYfjZfj[fj\fjfj%fj'fj7fjfjfj4fj5fj]fj^fjpfjfjflIflWflYflZfl[fl\flfl%fl'fl7flflfl4fl5fl]fl^flpflflfnIfnWfnYfnZfn[fn\fnfn%fn'fn7fnfnfn4fn5fn]fn^fnpfnfnfooo")o$o&o*o2o4oDoFoGoHoJoPoQoRoSoToUoVoXo]oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo oooooooooooo!o+o-o/o1o3o5o<o>o@oCoDoFoGoHoJoo oWoXoYo_o`oboooo o!o"o#o%o&o'o(o)o*o+o,o-o.o/o0o1o2o3o4o6o8o:o<o@oBoDoIoJoKoLoMoNoOoQoRoSoToUoVoWoXoYoZo[o\o]o^o_o`obodofohojolonpRp Rppp")pRpp Rp qqq")q$q&q*q2q4qDqFqGqHqJqPqQqRqSqTqUqVqXq]qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq qqqqqqqqqqqq!q+q-q/q1q3q5q<q>q@qCqDqFqGqHqJqq qWqXqYq_q`qbqqqq q!q"q#q%q&q'q(q)q*q+q,q-q.q/q0q1q2q3q4q6q8q:q<q@qBqDqIqJqKqLqMqNqOqQqRqSqTqUqVqWqXqYqZq[q\q]q^q_q`qbqdqfqhqjqlqnrRr Rrrr")rRrr Rr sss")s$s&s*s2s4sDsFsGsHsJsPsQsRsSsTsUsVsXs]ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss ssssssssssss!s+s-s/s1s3s5s<s>s@sCsDsFsGsHsJss sWsXsYs_s`sbssss s!s"s#s%s&s's(s)s*s+s,s-s.s/s0s1s2s3s4s6s8s:s<s@sBsDsIsJsKsLsMsNsOsQsRsSsTsUsVsWsXsYsZs[s\s]s^s_s`sbsdsfshsjslsntRt Rttt")tRtt Rt { {{ {")$q&*247)D\FqGqHqJqPQRqSTqUVXYZ[\]qqqqqqq\\\\\\qqqqqqqqqqqq\q\q\qqqqqqqqqqqqqqq qqqq!$)&)+-/1357<>@CqD\F\GHqJ WXqY\_`qbq\q \!q"\#q%q&\'q(\)q*\+q,\-q.\/q0\1q2\3q4\6q8q:q9 9BI 9 gsR{  . .+*Y r   <  Q i y ( 8E \} \ T5Digitized data copyright 2010-2011, Google Corporation.Open SansRegularAscender - Open Sans Build 100Version 1.10OpenSansOpen Sans is a trademark of Google and may be registered in certain jurisdictions.Ascender Corporationhttp://www.ascendercorp.com/http://www.ascendercorp.com/typedesigners.htmlLicensed under the Apache License, Version 2.0http://www.apache.org/licenses/LICENSE-2.0Digitized data copyright 2010-2011, Google Corporation.Open SansRegularAscender - Open Sans Build 100Version 1.10OpenSansOpen Sans is a trademark of Google and may be registered in certain jurisdictions.Ascender Corporationhttp://www.ascendercorp.com/http://www.ascendercorp.com/typedesigners.htmlLicensed under the Apache License, Version 2.0http://www.apache.org/licenses/LICENSE-2.0ff      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~.notdefnullnonmarkingreturnspaceexclamquotedbl numbersigndollarpercent ampersand quotesingle parenleft parenrightasteriskpluscommahyphenperiodslashzeroonetwothreefourfivesixseveneightninecolon semicolonlessequalgreaterquestionatABCDEFGHI.altJKLMNOPQRSTUVWXYZ bracketleft backslash bracketright asciicircum underscoregraveabcdefghijklmnopqrstuvwxyz braceleftbar braceright asciitildenonbreakingspace exclamdowncentsterlingcurrencyyen brokenbarsectiondieresis copyright ordfeminine guillemotleft logicalnotuni00AD registered overscoredegree plusminus twosuperior threesuperioracutemu paragraphperiodcenteredcedilla onesuperior ordmasculineguillemotright onequarteronehalf threequarters questiondownAgraveAacute AcircumflexAtilde AdieresisAringAECcedillaEgraveEacute Ecircumflex Edieresis Igrave.alt Iacute.altIcircumflex.alt Idieresis.altEthNtildeOgraveOacute OcircumflexOtilde OdieresismultiplyOslashUgraveUacute Ucircumflex UdieresisYacuteThorn germandblsagraveaacute acircumflexatilde adieresisaringaeccedillaegraveeacute ecircumflex edieresisigraveiacute icircumflex idieresisethntildeograveoacute ocircumflexotilde odieresisdivideoslashugraveuacute ucircumflex udieresisyacutethorn ydieresisAmacronamacronAbreveabreveAogonekaogonekCacutecacute Ccircumflex ccircumflexCdotcdotCcaronccaronDcarondcaronDcroatdcroatEmacronemacronEbreveebreve Edotaccent edotaccentEogonekeogonekEcaronecaron Gcircumflex gcircumflexGbrevegbreveGdotgdot Gcommaaccent gcommaaccent Hcircumflex hcircumflexHbarhbar Itilde.altitilde Imacron.altimacron Ibreve.altibreve Iogonek.altiogonekIdotaccent.altdotlessiIJ.altij Jcircumflex jcircumflex Kcommaaccent kcommaaccent kgreenlandicLacutelacute Lcommaaccent lcommaaccentLcaronlcaronLdotldotLslashlslashNacutenacute Ncommaaccent ncommaaccentNcaronncaron napostropheEngengOmacronomacronObreveobreve Ohungarumlaut ohungarumlautOEoeRacuteracute Rcommaaccent rcommaaccentRcaronrcaronSacutesacute Scircumflex scircumflexScedillascedillaScaronscaron Tcommaaccent tcommaaccentTcarontcaronTbartbarUtildeutildeUmacronumacronUbreveubreveUringuring Uhungarumlaut uhungarumlautUogonekuogonek Wcircumflex wcircumflex Ycircumflex ycircumflex YdieresisZacutezacute Zdotaccent zdotaccentZcaronzcaronlongsflorin Aringacute aringacuteAEacuteaeacute Oslashacute oslashacute Scommaaccent scommaaccent circumflexcaronmacronbreve dotaccentringogonektilde hungarumlauttonos dieresistonos Alphatonos anoteleia EpsilontonosEtatonos Iotatonos.alt Omicrontonos Upsilontonos OmegatonosiotadieresistonosAlphaBetaGammauni0394EpsilonZetaEtaThetaIota.altKappaLambdaMuNuXiOmicronPiRhoSigmaTauUpsilonPhiChiPsiuni03A9Iotadieresis.altUpsilondieresis alphatonos epsilontonosetatonos iotatonosupsilondieresistonosalphabetagammadeltaepsilonzetaetathetaiotakappalambdauni03BCnuxiomicronpirhosigma1sigmatauupsilonphichipsiomega iotadieresisupsilondieresis omicrontonos upsilontonos omegatonos afii10023 afii10051 afii10052 afii10053 afii10054 afii10055.alt afii10056.alt afii10057 afii10058 afii10059 afii10060 afii10061 afii10062 afii10145 afii10017 afii10018 afii10019 afii10020 afii10021 afii10022 afii10024 afii10025 afii10026 afii10027 afii10028 afii10029 afii10030 afii10031 afii10032 afii10033 afii10034 afii10035 afii10036 afii10037 afii10038 afii10039 afii10040 afii10041 afii10042 afii10043 afii10044 afii10045 afii10046 afii10047 afii10048 afii10049 afii10065 afii10066 afii10067 afii10068 afii10069 afii10070 afii10072 afii10073 afii10074 afii10075 afii10076 afii10077 afii10078 afii10079 afii10080 afii10081 afii10082 afii10083 afii10084 afii10085 afii10086 afii10087 afii10088 afii10089 afii10090 afii10091 afii10092 afii10093 afii10094 afii10095 afii10096 afii10097 afii10071 afii10099 afii10100 afii10101 afii10102 afii10103 afii10104 afii10105 afii10106 afii10107 afii10108 afii10109 afii10110 afii10193 afii10050 afii10098WgravewgraveWacutewacute Wdieresis wdieresisYgraveygraveendashemdash afii00208 underscoredbl quoteleft quoterightquotesinglbase quotereversed quotedblleft quotedblright quotedblbasedagger daggerdblbulletellipsis perthousandminutesecond guilsinglleftguilsinglright exclamdblfraction nsuperiorfranc afii08941pesetaEuro afii61248 afii61289 afii61352 trademarkOmega estimated oneeighth threeeighths fiveeighths seveneighths partialdiffDeltaproduct summationminusradicalinfinityintegral approxequalnotequal lessequal greaterequallozengeuniFB01uniFB02 cyrillicbrevedotlessjcaroncommaaccent commaaccentcommaaccentrotate zerosuperior foursuperior fivesuperior sixsuperior sevensuperior eightsuperior ninesuperioruni2000uni2001uni2002uni2003uni2004uni2005uni2006uni2007uni2008uni2009uni200Auni200BuniFEFFuniFFFCuniFFFDuni01F0uni02BCuni03D1uni03D2uni03D6uni1E3Euni1E3Funi1E00uni1E01uni1F4Duni02F3 dasiaoxiauniFB03uniFB04OhornohornUhornuhornuni0300uni0301uni0303hookdotbelowuni0400uni040Duni0450uni045Duni0460uni0461uni0462uni0463uni0464uni0465uni0466uni0467uni0468uni0469uni046Auni046Buni046Cuni046Duni046Euni046Funi0470uni0471uni0472uni0473uni0474uni0475uni0476uni0477uni0478uni0479uni047Auni047Buni047Cuni047Duni047Euni047Funi0480uni0481uni0482uni0483uni0484uni0485uni0486uni0488uni0489uni048Auni048Buni048Cuni048Duni048Euni048Funi0492uni0493uni0494uni0495uni0496uni0497uni0498uni0499uni049Auni049Buni049Cuni049Duni049Euni049Funi04A0uni04A1uni04A2uni04A3uni04A4uni04A5uni04A6uni04A7uni04A8uni04A9uni04AAuni04ABuni04ACuni04ADuni04AEuni04AFuni04B0uni04B1uni04B2uni04B3uni04B4uni04B5uni04B6uni04B7uni04B8uni04B9uni04BAuni04BBuni04BCuni04BDuni04BEuni04BF uni04C0.altuni04C1uni04C2uni04C3uni04C4uni04C5uni04C6uni04C7uni04C8uni04C9uni04CAuni04CBuni04CCuni04CDuni04CE uni04CF.altuni04D0uni04D1uni04D2uni04D3uni04D4uni04D5uni04D6uni04D7uni04D8uni04D9uni04DAuni04DBuni04DCuni04DDuni04DEuni04DFuni04E0uni04E1uni04E2uni04E3uni04E4uni04E5uni04E6uni04E7uni04E8uni04E9uni04EAuni04EBuni04ECuni04EDuni04EEuni04EFuni04F0uni04F1uni04F2uni04F3uni04F4uni04F5uni04F6uni04F7uni04F8uni04F9uni04FAuni04FBuni04FCuni04FDuni04FEuni04FFuni0500uni0501uni0502uni0503uni0504uni0505uni0506uni0507uni0508uni0509uni050Auni050Buni050Cuni050Duni050Euni050Funi0510uni0511uni0512uni0513uni1EA0uni1EA1uni1EA2uni1EA3uni1EA4uni1EA5uni1EA6uni1EA7uni1EA8uni1EA9uni1EAAuni1EABuni1EACuni1EADuni1EAEuni1EAFuni1EB0uni1EB1uni1EB2uni1EB3uni1EB4uni1EB5uni1EB6uni1EB7uni1EB8uni1EB9uni1EBAuni1EBBuni1EBCuni1EBDuni1EBEuni1EBFuni1EC0uni1EC1uni1EC2uni1EC3uni1EC4uni1EC5uni1EC6uni1EC7 uni1EC8.altuni1EC9 uni1ECA.altuni1ECBuni1ECCuni1ECDuni1ECEuni1ECFuni1ED0uni1ED1uni1ED2uni1ED3uni1ED4uni1ED5uni1ED6uni1ED7uni1ED8uni1ED9uni1EDAuni1EDBuni1EDCuni1EDDuni1EDEuni1EDFuni1EE0uni1EE1uni1EE2uni1EE3uni1EE4uni1EE5uni1EE6uni1EE7uni1EE8uni1EE9uni1EEAuni1EEBuni1EECuni1EEDuni1EEEuni1EEFuni1EF0uni1EF1uni1EF4uni1EF5uni1EF6uni1EF7uni1EF8uni1EF9uni20ABuni030Fcircumflexacutecombcircumflexgravecombcircumflexhookcombcircumflextildecombbreveacutecombbrevegravecomb brevehookcombbrevetildecombcyrillichookleftcyrillicbighookUCcyrillicbighookLCone.pnumzero.osone.ostwo.osthree.osfour.osfive.ossix.osseven.oseight.osnine.osffuni2120Tcedillatcedillag.altgcircumflex.alt gbreve.altgdot.altgcommaaccent.altIIgraveIacute Icircumflex IdieresisItildeImacronIbreveIogonek IdotaccentIJ IotatonosIota Iotadieresis afii10055 afii10056uni04C0uni04CFuni1EC8uni1ECA   46latnMOL ROM  nlatnMOL (ROM B      ligaligaligalnumlnumlnumloclloclonumonumonumpnumpnumpnumsalt saltsaltss01"ss01*ss012ss02:ss02@ss02Fss03Lss03Rss03Xtnum^tnumftnumn    &.6>FNV^Pz2HJJ.,ZgwEG      !$%IJ6 "(^IO]ILI5O4LI^V0R *H C0?1 0 +0a +7S0Q0, +7<<<Obsolete>>>0!0 +@mnTA6}]0z0b8%a&Z0  *H 0S1 0 UUS10U VeriSign, Inc.1+0)U"VeriSign Time Stamping Services CA0 070615000000Z 120614235959Z0\1 0 UUS10U VeriSign, Inc.1402U+VeriSign Time Stamping Services Signer - G200  *H 0ĵR`)J[/Kk5TX56^bMRQ4q{f*j 7٘tvJcEG.k NK+XJ,XB-uލǎlLgrIž`<cxi{-004+(0&0$+0http://ocsp.verisign.com0 U003U,0*0(&$"http://crl.verisign.com/tss-ca.crl0U% 0 +0U0U0010 UTSA1-20  *H PK$ $- 7 ,Za񑑳V@뒾89u6t:O7ʕBǠWdB5N3M'L8MxSݤ^ ⥾`߭(ǥKd[98"3/!?DA e$HDT\y>]r},CS}=*:Om ]^SWp`+nx'4[^I2300-GߍRFCmH 10  *H 01 0 UZA10U Western Cape10U Durbanville10 U Thawte10U Thawte Certification10UThawte Timestamping CA0 031204000000Z 131203235959Z0S1 0 UUS10U VeriSign, Inc.1+0)U"VeriSign Time Stamping Services CA0"0  *H 0 ʲ }uNgadڻ30X~k6xw~o< hlʽR-H=]_/kLR`@~ ?Ǵ߇_zj1.G 1s W-x43h/Š*Ë!fXWou<&]'x1"ijGC_^|}bM "Vͮv M٠h;004+(0&0$+0http://ocsp.verisign.com0U00AU:0806420http://crl.verisign.com/ThawteTimestampingCA.crl0U% 0 +0U0$U0010U TSA2048-1-530  *H JkXD1y+LͰXn)^ʓR G'/8ɓN"b?7!Op18UN$ҩ'NzaA*^ݻ+>W~ +;R8'?J00eeR&.Y)"\0  *H 0_1 0 UUS10U VeriSign, Inc.1705U .Class 3 Public Primary Certification Authority0 090521000000Z 190520235959Z01 0 UUS10U VeriSign, Inc.10U VeriSign Trust Network1;09U 2Terms of use at https://www.verisign.com/rpa (c)09100.U'VeriSign Class 3 Code Signing 2009-2 CA0"0  *H 0 g`IoV|f^ q-!ќPL"5; .Z|=;%X{ ξ'tag'MjaXy'M4+G Df$fO8TrfujIh8y 0,`Hת809:|@T/ܨR>+!\ P4.M^%Ԍn|)]1ZՌgX5+!`x^{`W ]A cT`C!00U00pU i0g0e `HE0V0(+https://www.verisign.com/cps0*+0https://www.verisign.com/rpa0U0m+ a0_][0Y0W0U image/gif0!00+kπjH,{.0%#http://logo.verisign.com/vslogo.gif0U%0++04+(0&0$+0http://ocsp.verisign.com01U*0(0&$" http://crl.verisign.com/pca3.crl0)U"0 010UClass3CA2048-1-550Uk&pȡ?-50  *H ݔAaix0Ɛ<~B$s/DrPU nQj71ܥ-OM2NgUejzd8xEv1z`³]fvYI8VAwX00fgymPSo0  *H 01 0 UUS10U VeriSign, Inc.10U VeriSign Trust Network1;09U 2Terms of use at https://www.verisign.com/rpa (c)09100.U'VeriSign Class 3 Code Signing 2009-2 CA0 100729000000Z 120808235959Z01 0 UUS10U Massachusetts10 UWoburn10U Monotype Imaging Inc.1>0<U 5Digital ID Class 3 - Microsoft Software Validation v210U Type Operations10UMonotype Imaging Inc.00  *H 0Di|U 25L3^ L*8ט@I "SOCʋVnH9c;$5}rGWyˊJ@p-5cįפ {uePd"}KXEMYLM00 U00U0DU=0;09753http://csc3-2009-2-crl.verisign.com/CSC3-2009-2.crl0DU =0;09 `HE0*0(+https://www.verisign.com/rpa0U% 0 +0u+i0g0$+0http://ocsp.verisign.com0?+03http://csc3-2009-2-aia.verisign.com/CSC3-2009-2.cer0U#0k&pȡ?-50 `HB0 +700  *H N"gA~™cjrb<8=_G_[KI  VD 5< D`E*oL;4gpZ9\Zl5|eKmIp=b۴A~}n"w6MZ S1+(RzkwD ]%,͊0>KyʦN $ 񺐶\<'M0{ *H  1l0h0g0S1 0 UUS10U VeriSign, Inc.1+0)U"VeriSign Time Stamping Services CA8%a&Z0 +]0 *H  1  *H 0 *H  1 110505165510Z0# *H  1T+ 'S8V0  *H wo"kEN@;'JV:j|{`N+Wg3+)쾼Y)$wILt=.o !9V8 chX)tdesktop-1.2.17/Telegram/Resources/fonts/OpenSans-Semibold.ttf000066400000000000000000006602201326245125100243120ustar00rootroot000000000000000DSIG=>KtGDEF&FGPOS 77G8GSUB+=GTOS/2`cmap)/hcvt 4fpgm~agaspF glyfw%>head<6hhea t$hmtx ekernT+ ~dP6locaHVDVmaxp8 name$Ot9postCl &+prepxn_< L}LxT d  T{ W/\X33f @ [(1ASC X R 5}+/oT`R=bJ`#?H3XZV'u^JXV39?`f`/oJHywByJddVbDLyLyfd B3LojZfff#s;;;ffsb%'HhJD-h-`5Huhs%d9sR`Hdmm`3-j=q3T =sP<.77JJJJJJLywwww4J:/DLyLyLyLyLyLyTZZZZZZZfffff;;;;ffffff`fJJJZJZJZyfyfyfyff/ fwfwfwfwfwfysysysys ;4;;J;0J;HJbdd;V;V;]V;VZ\DDDDLyfLyfLyfyfssaskfdbfdbfdbfdb%'%'%' JBDBDBDLZLZLyffdb#;`J3dJHR/wBLyJbDLLyLbmFD:fTf'RfTffffff  f\o fow'RDyfdJ:dd'# JHR w%XVV#bLyy bq+B{3Zh#fo'HjjP/ff+JfhBo{Bf fb;;; J+o   JRRRJ%?--+{%{ R}RPh wDhBJ?yf)P/FDfA-PVP/ 9`d%s/\`^`b####;sff'H+9/'Vy3TT;jZ#bJZf # #jy'f4 wVfjd}d - y%/3'm\Nyf^q^q yfyXfXyfJjDy fhu))/ X/ X'H/ =X9y/fyf+LLs#+V%%J%o-1hJJZJZLZwf/`/`%oX'HBH)VjVjLyfNyfNyf+BB J J JZ{X/ hhofLoFfD3DN3N%7u9y5f+bT-JZJZJZJJZJZJZJZJZJZJZJZwfwfwfwfwwfwfwfJ;wJ;LyfLyfLyfLy!LyfLyfLyfjy'fjy'fjy'fjy'fjy'fJJJ fNTNRTTTT/mq5R5Vu^JXV#V%'fffffqqqqqqqqqLqq 0HI~'2a7 # O_?M   " & 0 3 : < D p y  !!!! !"!&!.!^"""""""+"H"`"e% IJ(3b7 #P`>M   & 0 2 9 < D p t  !!!! !"!&!.!["""""""+"H"`"d%aIvhcb]gD Xz}} Bvt n%"iOS0L\pr`<&'()*+,-./0123456789:;<=>?@AIJ$%TUVWXY\]^_`abcdefhijklmnopqrstuvhijkFopqrstu45]^@G[ZYXUTSRQPONMLKJIHGFEDCBA@?>=<;:9876510/.-,('&%$#"! , `E% Fa#E#aH-, EhD-,E#F` a F`&#HH-,E#F#a ` &a a&#HH-,E#F`@a f`&#HH-,E#F#a@` &a@a&#HH-, <<-, E# D# ZQX# D#Y QX# MD#Y &QX# D#Y!!-, EhD ` EFvhE`D-, C#Ce -, C#C -,(#p(>(#p(E: -, E%EadPQXED!!Y-,I#D-, EC`D-,CCe -, i@a ,b`+ d#da\XaY-,E+)#D)z-,Ee,#DE+#D-,KRXED!!Y-,KQXED!!Y-,%# `#-,%# a#-,%-,CRX!!!!!F#F`F# F`ab# # pE` PXaFY`h:Y-, E%FRKQ[X%F ha%%?#!8!Y-, E%FPX%F ha%%?#!8!Y-,CC -,!! d#d@b-,!QX d#d b@/+Y`-,!QX d#dUb/+Y`-, d#d@b`#!-,KSX%Id#Ei@ab aj#D#!# 9/Y-,KSX %Idi &%Id#ab aj#D&#D#D& 9# 9//Y-,E#E`#E`#E`#vhb -,H+-, ETX@D E@aD!!Y-,E0/E#Ea``iD-,KQX/#p#B!!Y-,KQX %EiSXD!!Y!!Y-,EC`c`iD-,/ED-,E# E`D-,E#E`D-,K#QX34 34YDD-,CX&EXdf`d `f X!@YaY#XeY)#D#)!!!!!Y-,CTXKS#KQZX8!!Y!!!!Y-,CX%Ed `f X!@Ya#XeY)#D%% XY%% F%#B<%%%% F%`#B< XY%%)) EeD%%)%% XY%%CH%%%%`CH!Y!!!!!!!-,% F%#B%%EH!!!!-,% %%CH!!!-,E# E P X#e#Y#h @PX!@Y#XeY`D-,KS#KQZX E`D!!Y-,KTX E`D!!Y-,KS#KQZX8!!Y-,!KTX8!!Y-,CTXF+!!!!Y-,CTXG+!!!Y-,CTXH+!!!!Y-,CTXI+!!!Y-, #KSKQZX#8!!Y-,%ISX @8!Y-,F#F`#Fa#  Fab@@pE`h:-, #Id#SX<!Y-,KRX}zY-,KKTB-,B#Q@SZX TXC`BY$QX @TXC`B$TX C`BKKRXC`BY@TXC`BY@cTXC`BY@cTXC`BY&QX@cTX@C`BY@cTXC`BYYYYYYCTX@ @@ @  CTX@   CRX@ @@ @Y@U@cUZX  YYYBBBBB-,Eh#KQX# E d@PX|Yh`YD-,%%#>#> #eB #B#?#? #eB#B-,CPCT[X!# Y-,Y+-,-@ !H U UHU?OFNMMF&4U%$HLKKF3UU3U?JJJIHFGFIF#H"U3U3UU3UU3UoTS++KRK P[%S@QZUZ[XYBK2SX YKdSXBYss++^stt+++++t++ssst+++++++++++++st+++^NuR{1$$$$W}ym>_v eV?8bn  H  5 f  _  u  6 j ,Z.N;#0y&_GwApAt+ppL!MO7Y*@Uk!2  ! s ! !s!!!!!""~""""""#m#y########$j$z$$$$$%%y%%%%%&"&3&D&T&e&u&&&&&&&&'''#'/'7''''''(((&(7(G(X(h(y((((((();))))))******A*M*Y*j*{******+++"+.+:+w++++++, ,,a,,,,,---u-. ..(.4.E.U.f.v.........//?///////0 00.0@0L0X0i0z0000000111:1222223333L3|33344G4445%565X5j5|555555566 666#6y6666677 7/777w77788O8888889 999:)::;>;y;;</>d>>?\?@@"@3@C@S@d@v@@A!A)A1ACAKABBDBUBfBBBBBC;CCCCD&D7DlDDDDDDDDE!E)E1EbEEF FOFFG"GGGH1HHHIIOIIIJ(J_JJJKKKK I:K#9L%7 JT )-W@0-*+,%  %*,/.'''-`   ,-,"?3?3??9/]q39/]393333331032#"#"&5!232#"#"&5!2#:BB:¥?;BB;¦@+''ڕ)%!J` 2T@.,+'(!+!(43%-*,'$', KY0 LY0?+?+?999933333106654&#"27%467&&54632673!'#"$C54&#"'6632!9!ym2wiTgzG{~Hcr>QgVմc V-&C@$ "('OY $$MY$ MY ?+?+9/+993310!"'532654&##53 54&#"'6!2]`^zwSis f O.2~un^f/D'm H@'  MY P??39/]]33+3933333310##!533!47#mdV <??Ho}B^u):@MYNY MY?+?+9/+933102!"'53265!"'!!66JQZ/4i8!#eO.2 >^?$D@#" &% OYOYMY?+?+9/+9933310!2&#"36632#"&2654&#"^nLLd /sޝ}y{{LJoZQY!~Aq;J=%@NY?+?93310!!5!BϤX9#07@+!21!!++ $OY OY ?+?+9/3999102#"$5467&&54632654&''"6654&HrF}t dz,Tdxc{ɿVu̻zLPohswfQ9 :?cU4RC/5uNUcV7%D@#" '& OYMYOY?+?+9/+9933310!"'532##"&5432%"32654&&7:YZ ;pޜ~zy{wE|FPVZP}_YZj (@ QY QY ?+?+93991074632#"&432#"&LHILMHHLKJMHHL}INQFGSRPGGSR?j2@   QY ?+/_^]93310%#7432#"&0E"#KJMHHLPGGSR`1&@?o/]q9310%51/#yf)-@ RYRY/Oo/]++9105!5!f=?q`1&@?o/]q93105`#//XyRm&A@!!('$ KY$QY$?+?+9/9393331054676654&#"'632432#"&PdwEpi_MT,Ym]?HLMGGL@nN^hHTZ6&qKujUI`Q-OHGSQoV5?L@&;:6#.(.A@ =288 +2&+/3?39/3339/39933333310#"&'##"&543232654$#"!27# $32327&#"ZkOt 1ZLHhO]Ŧ6"bS ?HTHNNҳ/̞ZVeش%J7@  LY ?3?39/+9333310!!#!&&'Lя##15 4jDd(({!I@&  #"KYLYLY?+?+9/+99333310! #!32654&##32654&#. |斊 Z_rg\1s|rny&@ LY LY ?+?+9310"3267# 4$32&&/]^h5@   ?3?399333310!!#367!5basFxo>@LY?+?9931033!k>@      ?33?3993333333310!##!3!#47#9XQR I9uJm^%=2@    ?3?3999933333310!!##!3&531" Ay!Qy (@  LY LY?+?+993310! ! 32#"eKFdtjjv  2@  LY LY?+?9/+9933310!##! 32654&##'~|y;@   LY LY?+3?+9333310!# ! 32#"^'eKFdJHjjv  D@#  KY   LY ?+?39/+39333331032654&###! !|z|l\tyHd $4@ &% LY LY?+?+9993310#"'532654&'&&54$32&#" da|ȤLÙtx0nFM/6l[RrNQВ\ReS9QH;Cth%@ LY?+3?9310!#!5!!RKR;%@  LY?+?3993310# 533 ;RN cc *@   ??3993333103#366716 6JsA2L0 @@!      ?3?33993333333310!!&&'!366736730 -1 ,#'9h9*@2͝UAVwR 5@    ?3?3999333310!!!!!: RR7V)<,@ ??399393310!#!^Z/BX 8@ LYLY?+9?+993310!!5!5!!X3Cͨq@  ?3/3993310!!!!q)g@ ??993310# J3@  ?3/3993310!!5!!3+/'@?2993333103#yYJsH/33310!5!swjP!  /9910&&'5!E0&,4:F3Zf&I@& $(' JY HYHY?+?+?9/+99333310!'##"&546%754&#"'6632%2655\/PchUHLZ_XeI;ji2"/1`cfJQ@@!   !  FYFY?+?+??9999333102#"'##336"3265t+ p}}fї)ʵƻyff&@  FYFY?+?+9310"!2&# 327fGaᏊ?%,A:N% fT@@!  !  FYFY?+?+??999933310"323&53#'#'26754&#"r ) q|,/wE쑥!Ѱɺf9fG@'    HY HYGY?+?+9/_^]+933310 32!3267"!&&baVp -6%+)"Ȏ#B;@  FY HY?3+3?+?9399310!##5754632&#"!춶|x>WOPI`nHHĽ)ccHNf+8Cr@=+%>9% 2,%ED"V*%p LRn[H=_Ghplutu3@  FY  ?3??+999333310!#4&#"#33663 ugp 0r~u_lPXk6@"  /_o??/]q933310!#34632#"&E@>EE>@ER%?DD?EE>@Ec?DD?u#ʵȹfTf @@  "! FY FY ?+?+99??993333310%26554&#""32373#467#^~hA h%ʹȻ-1MX,bNf#@    ?2?9?9933102&#"#3366G.26 7f Rctbf!4@ #" GYHY?+?+9993310#"'53254&&'&&54632&#"݆è0nbîLza|<;CZ*8<&JvOJj4H?5Xs'H=@   @  HYFY?+?+33939910%27# #5773!!DVV'{BP;U`ThVUQmR0@  FY?+??399933310!'##"&5332653! 1thoMXV'HR (@    ?2?39333310!33673\:  =ZR}dHsR>@!    ?3?393333333310!&#!33667!36733D :"0 )-7R+qRʐI/F18{!NR 3@     ?3?3999333310!!!!  5}bJR3@    FY ?+?3?39333310!3663!"'532773 0'NJ5DE)Rv7 hDR 8@  HYHY?+9?+993310!!5!5!!/3 -E@'   ?3/39/]39939993104!5265463&&57rgfs[]7SR' $RT?/93103#ٴ-E@'   ?3/39/]399399391056654675&54&'523"_jj{[n}{K\y ')RSdTUe`;1h@@$ RY @RY?o/]+_^]+9910"56323267#"&'&J2{=cBvXY4}:iA}T<=l%7>:o#7^(@   QY ?+/9/9333103!#"&546323!KJHLLHHM8JNOIETQJ@) MYMY  ?]2+?_^]+39333310%#5&54753&#"3267wΞFhKW; =;²%HVU@.   OY MYNY?+3?+9/_^]3+393399102&#"!!!!56655#5346µLzsBPb^RG[-pu':@%"  )( %o/]3299331047'76327'#"''7&732654&#"@ydssby??wcr~Yw@^aa]mhw?Audswbw==wctbba}[@3  PY   PY  ??39/3+39/_^]33+39391033!!!#5!5!5!533HAqv$@ ??99//9399103#3#ٴEs#-9M@,4&.27 !;:72*IY$IY?+?+9933333310467&54632&&#"#"'53254&&'.7654&'MI۹[cDtx=nԆMQ*da=0mm|6E'P+S"*2m6O3DmYPG(3+66&7]ua-FD8AgKg5[% @  /329933104632#"&%4632#"&%C05?@40CC05@B30Cw>7>75@:;>7>76?:dD&6F@(/' 87   #3+#?3?39/]39/]3933310"3267#"&54632&4$32#"$732$54$#"}oxl{7~.sxĊAj^^{$$ۧ߬ 3F7^Zƪݨ!%9!9@ !!#"! ?3]339/39333310'#"&546774&#"'63232655#.vGqqkEEZx6Gm`[\a76ijhoHH8sF}}A<@1XRR+Rh! )@    /329333310%Rde1^a^a`1+#@RY//]+99310#!5!1yHJdD%5]@3  .& 76 "2*"?3?39/]39/]39/3933333331032654&#####!24$32#"$732$54$#"EJLIOCZ^^{$$ۧ߬FAH9}>sZ^Zƪݨ!%/33310!5! m9 @   ?329933104632#"&&732654&#"mXXfJJfhHHhWYFhgGLfh`1 7@   RY  RY/]3+3?+939910!5!3!!#5!rqrij3J'@   ?39?393310!576654&#"'632!uA@3]l^\Jpj;46XywrS-9#9@ %$ ! !?3?39/]q3993310#"'53254##53254&#"'6632QO^_z}siE89a9T=bK_'nM>O}48(%r.;{jP!  /99105667!j9y#4GF==5uR;@   FY?+???39999333331032653#'##"'##3ڒ" 0hO'STZ$>qw-@ KY/+/9/393310####"&563!w>TD39j @   QY/+93104632#"&LHILMHHLINQFGSR-@   @ H/?39/+393310!"'532654'73B76E6?T)PZ!-UX_TJ "@     ?3?999310#77'%3/vXJg[,Yp= %@  ?3]2993310#"&5463232654&#"ϯNXXNNXXNdonnoqmmPh! %@   /3/293310'7'7!g=fQa\^^PQa\^^P<1'&{<\ ?55.H'&{t?57h''<u ?557w^'H@&"")( %%QY% KY"?+?+9/_^]939333103267#"&546676655#"&54632Rf|>lkZRR*Vr^>KJHLLHHM?jPbbKN^7&nIrhZLaO-@JNOIETQJs&$CR&+5Js&$vR&+5Js&$K;R&+5JH&$R-R&+5J>&$jFR %&+55J &$PVm $?55e@6   LY LY LYLY?+?+3?9/+9/+93333993310!!!#!!!!!!#/;7{jr5y&&zs&(CR&+5s&(vNR&+5s&(KR&+5>&(jR !&+554s&,CR&+5Js&,vR&+5s&,K R&+5:>&,jR !&+55/^ G@% LY LY LY?+?+9/3+39399310!!#53! ##!!3 ^no[3͠o IZH&1RR&+5ys&2CwR!&+5ys&2v'R!&+5ys&2KR$&+5yH&2RR &+5y>&2jR -&+55 @  /_/]9107'}HI}E{}FF{}F}y#N@-  %$! !LY LY?+?+99933910!"''7&!274'32&#"Ք^beKǛZcPaNK\tQ^yjR\<R;;s&8C7R&+5;s&8vR&+5;s&8KR&+5;>&8jR '&+55s&<vXR&+5 F@%  LY     LY ??9/+9/_^]+99333310!##33 32654&## ~{3A@"%,--54%1-1(FY1HY?+?+?999333310#"'53254&'&&54676654&#"#4$32}MBZ6-9_\W,̾m:CEywhDGK@oJ3E:3&@>_pGA%1=XJI|T?i57U3HQlisͨZ!&DC0&+5Z!&DvL0&+5Z!&DK3&+5Z&DR/&+5Z&Dj <&+55Z&DP *&+55Zf&07s@?!-4-5' -98!$ -JY$ 4IY44$ 1HY HY GY$)HY$?3++?3++9/+9/+999333339910467754#"'6632632!!27#"&'#"&732655"!4&ZɍJXccx8 #VnC^|p u=LR/1P)"mn}^`aff&Fzdf9!&HC%&+5f9!&Hv`%&+5f9!&HK(&+5f9&Hj 1&+55!&CQ &+5!&v2 &+5!&K&+5X&j &+55f}!'P@+ ")(  GY %GY?+?9/+99/9933310#"54327&''7&'774&#"326}W>XVQTvX𗃗7 ow;+?Qqu&QR!&+5f}!&RC&+5f}!&Rvo&+5f}!&RK "&+5f}&RR&+5f}&Rj +&+55`14@   RY//]+33/3/3933105!4632#"&4632#"&`?@=@D9{Puf<@ FYFY?+?+??9933310"'53254&#"#3366321bB:?in!1rŦ5zxROVy&2MR&+5f}&RM&+5yZ&2NR&+5f}&RN &+5ys&2S+R *&+55f}!&RSj (&+55yV@/ ! LY LY  LY LYLY?+?+?+?+9/+9333310!!# !2!!!!!"327&fmX?s^:@ЁTPjhr55#%fLf)0o@= $-$.$21  -HY ---- *HY 'FY GY!FY?+3+?+3+9/_^]+99933339910 '!"32632!326732654&#"%"!4&狄yB f`TGn ~6 +b`%+(#?¿̋ s&5vR&+5N!&Uv&+5; &59a;Nf&U9 s&5L'R&+5km!&UL&+5d s&6vNR.&+5b!&Vv+&+5d s&6KR1&+5b!&VK.&+5d &6zDbf&Vzd s&6LR*&+5b!&VL'&+5;h&79!';H&W9hs&7LR &+5'N&W8 h?@!  KY  LY?+3?9/3+3939910!#!5!!5!!!!RKR{V'HW@/  @ IY HYFY?+?3+39/+9993910%27#"&55#535#5773!!!!FM]&}AP;!ߪɣhVî;H&8RR&+5m&XR&+5;&8MR&+5m&XM#&+5;Z&8NR&+5m&XN!&+5;&8PR &+55m&XP7 &+55;s&8SR $&+55x!&XS '&+55+;&8Q3+}R&XQ s&:KbR)&+5s!&ZK*&+5s&<KR&+5J!&\K!&+5>&<jR &+55BXs&=vHR&+5D!&]v&+5BXL&=O-R&+5D&]O&+5BXs&=LR&+5D!&]L&+5  !@  FY?+?93310"#4632&/ƒj9]``+#D@$  ! HY FYFY?+?+9/3+3939910"'53265#5754632&#"3#bWH>>PFæNr*:WBNAZgqEoWamNL!.t@3   0/@ H%KY ,LY,,@ H@ H?3/+]9/+]99//+3++9333310#!#&54632&'5667!4&#"3326K‰Lkg uQ pp C9<./<32 -9j>m:nf{y/4k&P5'*3773299Z&.:F@D8>2 $28'*HG.@> H..5o**?*_* *;5P5`5555A/@ JY HYHY?+?+?9/+92_^]2^]q9/+933333310!'##"&54$%754&#"'6632%2655567!"&54632"32654&\/CvfeGVL`hX1bL-;hhdg5555566WW;ij**00`h`NM9[f @xkhywhmxM55655655s&vuR&+5Z!&vA&+5ys&v%R-&+5f}!&vh,&+5d; &69b;f&V9! (@     /229333310&'#567!F{igz??IkgMin! (@    /329333310!&'5367~gAWa!|.iZ]dS#/39910!!#$@   /2]2993310"&'332673^,QSie ٟ09B?   /93104632#"&E@>EE>@Ew?DD?l>13<@/1>o.@@.2=I1=P >7<9;::;u8=;::J&$T ?59j @   QY/+93104632#"&KIILNGGMKNPIHPN'(T?5'+T?5',T?5&2JT?5'<T ?5N&vLT#?5&U ,&+555J$%%@ LY?+?9910!#%/((BX=B+y:@  LY LY LY?+?+9/+93310!5! ! 32#"FeKFdLotjjvJ,. (@   ?3?39333310#!#& "A,jJJ01L1 :@   LY LY LY ?+?+9/+93310!!!!!5Rm^ y2%%@ LY?+?3993310!#!#!%{d3Lj C@#    LY LY?+3?+393933310355!!'%!L1!9;h7<b="+a@7+"" ' -,$$KY+ +KY! P `    ??99//]3+3_^]3+3933391032654&##5#"$546$335332###"33˽1!;= 1ʷh;mD@@   LY ??339/3+393339910!### 333332653D11H !7ɫD?@#   ! LY  LY?3+3?+9333310"!5!&5! !!5654&#ܓnl>>lqjkP$Yjd):>&,jR !&+55>&<jR &+55fh&~T'5&+5Th&T-&+5uh&TX&+5h&T&+5&U? 4&+555ff +J@') "-,* FY& FY FY& ?3++?+?99933993310%26754&# "323673327#"&'#^~Ox5 )",(O_hs α}, ,USa3Re9< NY%M@)#'&FY FY FY ?+?+?9/+993333102#"'#4$ 3 4&##532654&w̄ +\Hй4,A4NwmtLR6@   ??39993339910#4733>3P8.LD #)f*8VFJ9"Nmf{)B@"!$+*!! GY'FY?+?+93393339910&54632&&#"#"$5464&'326ڼi`UaOWtǬUpwĉ-B+;C8:lSlӲr>*ȏyTf#M@+  %$##HY #### GY GY ?+?+9/_^]+993310#"!27#"$54675&54632&&#"3{ܳNoG쑘SRVKi 6ȈO0LGf{!@@  #" "GY?+33?9393333310#6654&&'&&547#!5!Ʌ;L(`Qt,#7u=M1!%!& ZmA tuf2@   FY  ????+99933310#4&#"#33663 ulq! 2pzROVkfo+ ?@ GY HY  HY?+?+9/+99333310!"!22!"!&o %  kuj  R @   FY ?+?99310327#"&5Y[%zBRR!">@ "$#  FY FY?+?+?939333310#'&&#"5632327#"&'&'#-%]Q85DRge6B<*/R#Zn%B #=+cO ANJ Wi+uRw9R (@    ?3?293333103363# BRS֧<f{-b@3 %&&))# #/. ,, &HY &" GY ?+33?9/+99393333399104675&5467##5!#"33#"#6654&'$fێB-?6k:Khs\0 ѮR,@   GYFY?+?+39310327#"&5!57!+Y]e~ )NdTR+@  FY?+?399333310"&332654&'3~(' \mۨyfd!F@$   #"HY  GY?3+3?+??933929910$46324&#"66ܰ\L;Vm_%- tzw,9 $0\ H@) "!FY FY?+?+??9329391023327#"&'#&&#"56G_F,o)G#M@,2AZz7H3-+L\.a{'!XF ~e~]PLC@"  GY ?3+3??3?93333399106654&'3#$3*E㝫q& )Ѻ loR%K@%$ '&$ GY!?2+3?39/993333933310"47332332654'3#"'#8>B;neݲfm=@?6I I* / &j #&+55&j/ +&+55f}h&RT1&+5h&T9&+5oh&T/&+5>&(j R !&+55D@% LYLYLY?+?+3?9/+933310"'5326554&#!#!5!!!2gITKWOhwHrbal]=ȵ%s&avoR&+5y:@ LYLY LY?+?+9/+93310"!!327# 4$32&&FX ݡGZ}BAoU^7#d 6J,:>&,jR !&+55dh-b#L@)%$#LYLYLY LY ?+?+?+9/+9333310!!!#"'5326!3 32654&##b2EZmIB96HSf<w#RqV}_w@q~xl}N@)  LY LY  LY?+??39/++933399310!!!#3!33 32654&##}/u!TqVmVBq~vn;@ LY  LY ?+3?39/+933310!4&#!#!5!!!2_p)'h_=Ƕ#s&vR&+5 w&6PR&+5m% 0@  #LY?3+??3933310!!#!3!3%AHmJ$ =@  LYLY LY?+?+9/+933310!!!!3 32654&##W|s@ȏH{eq~yk%%a m E@$    #LY LY ?+33?+?39333331073!3#!#! q:sy Lmz(#L@)       ?33?33939333333310!3!!#!/-<<> X%C@$ !'&KY ##KY# KY ?+?+9/+993310! '53 54&##53 54&#"'6!2`ܯad㰥jgm1`O.2tz]l8C2@    ?3?299993333331033!#47#!  w+Jjw&6R&+5# 6@     ?3?393393333310!!#3!#P<> /@ LY LY?+?+?93310!#!'"'5326! `O_IB96HQaCV2P 0B+y2%n3y&h7 1@   LY?+?39393310#"'53267!3673Utcbk[{2e0 IP'Yt/#h#9b=s;m 2@  #LY?+3??3933310%3#!3!3%8@  LY ??39/+999933310!##"&5332673kq_H0*óRrp#+ 1@   LY?+3?33933310%!3!3!3Jmb;@   # LY ?+33??339333310%!33#!3!3FF 9@  LY LY LY?+?+9/+93310!!!5!3 32654&##FOP$@q~vnH =@  LY LY?+??39/+9333310!!33 32654&###3_%?s~wkf 2@ LY LY?+?9/+9933310!!33 32654&##8'ɡîBq~vnB:@ LY LY LY ?+?+9/+93310"'63 !"'53267!5!&&VFhdc\V^wuA%G@&     LY  LYLY?+?+??9/+93333310! !#3!! 32#"BL5TucDm-@t s D@#  KY LY?+?39/+393333310!$4$!!##"33𾗡Ja7JJkyvZfDhy#"=@ $# GYFY?+?39/+9933310%6%36632#" #"h! 9iAzgr9*(2S[hXL8\1R V@.   HY  HY  HY ?+?+9/_^]+993333910#!! 4&#!!24&#!326h倈||gfvz7)iRPCuB;=sR@GY?+?99310!#s RfR#yR E@$    " HY  GY?+33?+?393333310#!#36!3!# Zv wyWyy=dpf9fHmRL@(      ?33?339393333333331033!#!3M:;N911;Hf#M@+  #%$"##"HY #### GY GY?+?+9/_^]+9933102654#"'632# '532654&##5LՅsčGJHO:$cEVOQSNR2@      ?3?3999933333310!#467!+ RcZ-./R%&6h&+5R 6@   ?3?393393333310!!#3V7"R1R5R/@ GY FY?+?+?93310!#!#"'532!5_zM4%%fu!pR:@  ?33?39933333310%767!##&'#!# )9-2*7 0P\}&:jhR 2&+55s&Zj 3&+55s&<CR&+5J!&\C]&+5R/399105!R\ǾR/399105!R\ǾR/399105!R\Ǿ1N0@   H H/+3+]2333310!5!5!5!NRR1s@  ?99310'673% f6@%Srs@ ?99310#7d5{E!?@@P/]99310%#730E"׺u@  ?99310#&'7"E5ep\"@   ?329331063#%673#5}ERf6@%Sr "@    ?3293310#73#73s5{E5{E"!+) )@    @P/]3293310%#73#737yB$0B${ >@$   LY??9/+993910%#53%35A53V_{i@C    LY  LYP   ??99//]+9_^]+993910%%#5753%%TV33R--R33V- yz  /93104632#"&zyxx슐 #/@  %$ QY! ?33+3393331074632#"&%4632#"&%4632#"&LHILMHHL-LHILMHHL-LHILMHHL}INQFGSRHINQFGSRHINQFGSRR  $/:Em@< %+6;0A  %+;AGF"88-CCC`33(>?333?3??9/]q39/]333933333333103265#"#"&5!2%#3265#"#"&5!23265#"#"&5!2@HGDH@ժH+@GGDG@ժH9>GGDG>ӪH'J%ݕ%m Rh^@ /9310Rd1^aPh^@ /9310'7^fQa\^^P&6w@ ??333210#ZJh*@     ?3?2399333104&#"#3363 HEEfR IPCfsVeAB%L@)  PYMY  MY ?+?9/+9/3+393910!!##53!!!!7죣@-KJX"z@D   $# PY/  PYMYNY?+9?+9/3+3_^]2+3933333333102&#"!!!!!!5667#535#53546ŮL{yaV PCەTz\'c@5 # )(PY MY'MYOY?+?+?9/+9/3+3933333310%27# #57733#!##! 32654&##OBIL}>j<==%%AXLf,L@) & !*.-( $?3?3??9/39/q393333310##"&546323254#""&54632&#"327+!UGn_.XKmWLJrK+%%)P$I@$"  &%"  ?3?39/399999/93993310%2673#"&5556746324#"66JW DvjPem:4olhfĴǚK]6'+Y@/"  )*-, (%()((?333?39/]329993333333310!!##!3&53#"&5463232654&#"5! R  #HTQHHQRJ?q' kllkliiL@'    ?3333]2222339333333310##5!###33#7#w)LٲғPwX/DvfH5@  ! /??3/299/]3933310"&546632!3267&&#"y1RQHbٓ2Xz#5Fi)|5BuAP'&{@ ?555-j''@u ?555Ph'&=@ ,?555VL'q'@? ?555PL!D@#  #"HY FY FY?+?+9/+993331074&#"5632!"&54!2&#"32XyyG"GJy ^Llm/ 7@    LY?+99?993333107!!!&&/uF+!.+Ӌ٭_@#3$@ LY?+/3993310!#!?k}#<m9# D@#   LY LY/+9?+99393331055!!!9]fy#o5`y1+@ RY//]+99105!`y%.@  //39/33293331010##5!33s1+@@$)  #-,# & ?   /]333223993310#"'#"&54632632267&&#""32654&1zx|8_0-^>EPR6_2-a;CTV͈öOUQQ^FHZBOSRR\HHZ!@  ?2?393102&#"#"'532546`>CBLDbBO:iV \q3/.q@B'0/&) RY @RY)#RY)) )@RY?_/]+_^]++_^]/+910"56323267#"&'&&"56323267#"&'&&H57mHH\5~8fCsXGc349jFyRIb05|:eEvSU_A7o#7?:l%@9l"?:m##`1 U@4  RY   RY / O o  /]+993+9939910'7#5!7!5!!!!!`RgG dhEѲݳGӳݲ^1  %@  RY?/]/+9310%5 5!1/#-y?`1  '@  RY?/]/+931055!`#//XyRbF 5@    ?/99333333103# b^=^H!))#&IL#z&IO% @   /22993310"&'332673F +QSbf ٙHP#WdR @  FY?+?93310"'532537jFDGs@  ?99310673#s/Z+垑>Bf; @  /993106673#f+\*T@BP8f! @   /99310#56673- ]+5GM;'7  @   !?3?399331032654&#"!"&5!2@HHCCHH@J5J >@    ?3?9/]333393333310##5!533!547}x}4$CͲadh6H7-@  !?3?39/39333102#"&'53254&#"'!!6jK*1;gX=PT#2s{ )GM+' +9#6@! %$ !?3?39/]3993331032&#"36632#"&2654&#"+K63F !l@NARGHC_T04?PMEQJ5Qc9J @ ?3?93310!5!XCyJ՗{/7".?@" ),&0/)  # !?3?29933339102#"&5467&&54632654&'"6654&u]SJTG:GIJG`H;>8=8A1?;ygK-nGsuFm,1^Dg}j4CC49KG5,)><2,5'9#8@! %$ !?3?39/]39933310#"'53267##"&54632%"32654&N33E !l@ARGHC_T;04?PMEQJ5QcT #'+/37;?CGS[kt|@[p`zglvvkXHTN $%()DE01<=@A   #-.478;HN`gk}ZQVKuOlll\vkt\}KkQ\pkk@5 HkO\\ %&)*12=>ABEF\k,-  !4589 //39399/]/+]3339/q33339393933333310!#%5!#533!5353!5!!5!5!#3#35!#35!35!#35#3#3#"&546323254#"%32##32654&##32654#"'53253T/0momImmmm0oowoooomm~smp.,=.m^{B.$*/;J1%Z^4+V}i0oo/mmmmmmoo;mmJoooo/yhIaCS1DD5QYb" "+%J fVr_cT*8@ % ,+(""//9///339333310 54676654&#"63232654&#"TV,AgIOGRZ?>1HT;GFBIHCHEVW/2A1R~X8*P:/5K6DpJ;?HI>@IH!&7L&+5j=)+4i@8,)2$ $+65,*+*HY ++' GY''/GY'GY?+?+9/_^]+9/3+39333333310! 4774&#"'6323 5'&$&5463 3%&&#"` 1+7r}_\ mfNȡ̵YoLT#&<Q,7->fS/de?Y['**odžLB~6@ KY?+??933933910>32&#"#!ZqJ]??:#):XyV&TW(4c/#!R'X@.' %  )( GY"GY?2+3?+339/9933393310"&547!57!##"'#325332654'sb`ZMM^cnZ[khXdTá|u&0vT&+5!&Pv-&+5J&$[TZf&D[&23\ fB 2@ _   /q3]q]2993310#"&546324&#"326Bkjjl<./<650:h|zhh{d3773299h'@  ?93310673#%467#"&C=lƌ~BA")"96ۆ+Ќ .GD%½@@  @ KYLY?+?39/+9933323106653! 53! ;NJawZNd VVM@&   @ JY FY?+??39/+99933332310!'##"&53326536653!1thoOJbvMXV'wc4!C!vR /210#'6654#"5632 KIm<8DIӓ'] 0,Id y} /104632#"& E@@CD?@E?DE>;FEs&(CR&+5s&C^R&+5f9!&HC%&+5!&C&+5}2N@(++(0 # (43))  LY-&&LY ?3+3?3+39/99333910"'6632!"&'# 32&&#"3273324+XDX75!#&&####"!p/bdwl=)jJ#!Mf*32&#"#SM_nn=5eNFjkvǰᰦnDKKT\6B-4+e3am.3-' v~uw`i{u!M5]I- LN*TXGk@<.7 >D**<@2D   IH, 2332HY,33'D>A@ H>@>9HY>'GY'"?+?3++299/9+93933333102&#"32772&&#"#"&5%$54&##532654#"'67&'53>9@-/-]&|уqTc-H\Uf T7Ew+uLvFuNHW_X F9d9%d'(/-) SLGJH6U|7YM+mDuy ?@ LY LY  LY?+?+9/+99333310! ! 267!"!&&eKFdTƽXtjjvuHf}f ?@ HY FY  FY?+?+9/+99333310#"&532267!"!&&} ++Ќ .{w4@    LY?+??993333210"!367>32&6H.9:>32&#"!O 2l,JeSA/!+.3RJ5IGr3 HAws&vR )&+55!&v +&+55y  ,R@,, ! !'.-,  LY LY$)KY$?+?+?+?3?3933339310! ! 32#"%!3673!"'53277uH94Gž\1:(MJ5DE)zqp~{b}Ss hff *T@-*  %,+* FY FY"'FY"?+?+?+?3?39333393310#"&532! !"!3673!"'53277}攅166)KO7DE'+Ќ .{pa]q hy%/*O@*$( ! ,+LY$&( (LY ?33+33/33+339333310#"'$%66326632654'#"'%vx/ J86J1NF3g$"ki&y)qq&=<'>00>'{(-#P()RP)f)P@+"(  +*GY"%( (FY ?33+33?33+339333310#"'&547663266326'#"&'qt B62B h:85;=8;<+%ik$% "3''5$;(&(&<;3>-++->yd.BSp@?9:JC,  &0:CGO UTB1Q1:: ::F6=GFFLY)##LY ?3+3?3+3_^]229/^]33/39333310"'632!"&'# 32&#"3267324#".#"#5432356654.5432Pl@=# F?45#(+5?CK RQ*+N+55199@ H9CB  GY FY&?2+3?3+32+_^]29/33/39333310"32&#"3273265#"'632#"'#".#"#54632356654.54632ZيkGXJτu3~Q3O"Hpllcs[*3+y:qwNy;:!)!A8BK'#9/f+?ȿ9XXȝ#*#7<'vz%-%[}N7-/Nj#0>g@9(('. ! '147;> @?6::>@ H>84<@''LY+$$LY ?3+3?3+39/22+239333910"'632!"&'# 32&&#"3273324#'##'##'5+XDNsMXiPEU+Ʒmb]xƚP-21+1/-P,_c?=B:vSx_-L\P-#32#54#"##5Nwp;|h*\ra%-%xx)X#)#F@ /93104632&&MB9@6:<|AO0-* 7P}F@  /931056654.54632|;:!)!@9BKV|P7-0O) (6DR_m@8 7D=>S_YZ(!" )6/0`mfgERKL "(06>DLRZ_gmon)"0@ H03,,%3mZSf@ HfjVcc\jE>7L@ HLO:HHAO3jOOj3 @ H  H /3+2/3+29///333+2333+2333+29333333333333333310&&#"#632&&#"#6632&&#"#6632!&&#"#6632&&#"#6632!&&#"#6632&&#"#632!&&#"#6632o(+/2@# / :7+2 77 2+  //9//////]]]910#6736673#&'5&&'5'76&'7'677'&&BF$a5Ia4GA݁ZBO݁C?b~C7`#WCGaC)`Z1BO݁GA܂!Ia5F$a5D.`NDKb:;D4`ND6b2mw'e@6'!    )('   @$ # LY?+???32_^]29993333333331033!!!#4>7#!#"&'33267 $ Xtge Z;TU]\J.jwfU\_{% g@6  "!  @ " GY ?+???32_^]29993333333333310!3##47!%"&'332673+ Xvcf R*Zd/hRfU[`/O@*  LYLY  LY ?+?9/+9/3+3939310!!3 !!#535332654&##@".c DȪs~um wW@/  IYHY HY?+?9/_^]+9/3+3939310353!!!2#!#4&##!2 P {)SB]@2      LY  LY ?+?9/+9999999333910'###! 327'7654&##jdh}`{72RoT;cnaA{f'a@8% "#! !)( #"   FY FY?+?+??9999339310"'##33632'"327'7654&t# nhw;'nh>}Dh=>e#ʵfY/+ :@  LY  LY ?+?9/3+3939910!!##53!!dj7o R :@   GY  FY ?+?9/3+3939910!!!##53! T디=ö)I@(   LY   LYLY?+??+9/_^]+933310"#!!632#"&'532654&N>`pj(fE{T 6 ؟1 BR?@" FYFYFY?+?+?9/+933310"'532654&#"#!!63 qnmv|9:V< 89 ZR m\@2     # LY?+??3?33939333333333310!3!3###!/<<>{R\@2       " GY ?+??3?33939333333333310333###!3MBw:;NRy11=X+&{H+f&mA@$      #LY?+???399333310%3###367!5basFxo>yRF@$      "GY?+???3933933333310!3###3V7Tn"Ry1RC@$     ?3?3993333333310!!##373!}}PfFeRQ@*     ?3?393399933333333103!!#'#37cH`uR/K@* LY  ?3?399/3+39393310!!##53533#67!5ﴴbasN˝xo> Q@.     IY  ?3??9/3+399393310353!!37!!## ] NCfMȦd%h @@#      LY?+?3?99333310!!!#!<M35Ds9R A@!      GY ?+?3?933933333103!#!5!6!BR1m)D@$     LY   #LY?+???39/+93333310%3##!#3!3B^mV{\RD@$    GY " GY ?+???39/+93333310!33##!# RDd#R ?@!   LY   LY ?+?3?9/+9333310!#!#3!!^;mVR ?@!   GY   GY?+?3?9/+9333310!!!#!# KRDf#RZ M@*"!LY  LY LY?+?3?+9/_^]+9333310632#"&'532654&#"#!#!dZMfH5֛/ RC@$ ! FYGY FY?+?+?39/+9333310%#"'532654&#"#!#!632kˊlfst|)54:~?8; XhR# y*6m@;4 %+1%%874".  ((.KY(( (( LY @LY LY?+?++9/_^]+999993339910327#"'#"$5!2&# 327&546324&#"66{o=JN@$#  ('#@ ##HY ####" HYGY?3+?+?9/_^]+99_^]933333310&'$5473336$32!!267#"!4&ٽ d+ 6ggdr $R;*@Z#-?9J,#w&6BR&+5m%&6&+5bE@%   !  KY LY ??3?+9/+3933310"#37!32#"&'532654$ar*XQJ>R֜/ RB@# FY FY?+??39/3+93333310%#"'53265!"#3!tԍjol|Xe/?8;ksRmD@$ #LYLY LY ?+?+?+?93333310%!!#!'"'5326! `O_IB96HQaCV2P {!RD@$ "GYGY FY ?+?+?+?93333310%3##!#"'532!3_{R/%%av#qB=@  LY LY?+??39/+9333310%#"&'53265!#3!3BcG^w/mV R=@     GY  FY?+??39/+9333310"&'53267!#3!3Lp6mnvr ;#RDmFI@'     LY   #LY?+???39/+933333310%!!#!#3!3B^mV{uRI@'    GY " GY ?+???39/+933333310!33##!# RDd#RmH@%     LY  #LY?+??39/+999333310!##3#"&5332673͇kq_mdw0*óRrp#+yfRH@%  HY  " GY ?+??39/+99933331032673##3#"&5wU]޿akRl/4y=<9mU@.      # LY?+33??3993333333310!##!3!!!#467#9XQR IYu12x={RQ@*     " GY ?+33??39933333333310%767!3###&'#!# )9-2*7%0P\d}&$jJR %&+55Z&Dj <&+55Zfw&(6R &+5f9%&H6&+5=@   LY LY LY ?+?+9/+933310"5663 ! 5!&&267!Qz{- w=2otH`=f=@     HY GYHY?+?+9/+933310 #"55!&&#"566267!'Uq f R'$8>&jR /&+55`=&j 0&+55#>&j3R '&+55m&j '&+55X>&jR ;&+55H&j 9&+55BPH@&  KY LY KY ?+?+99/+3933310!5!! '53 4!#))cgnPLͪ> O.2)RH@&  HY GY FY ?+?+99/+3933310!5!#"'53265!#A^^iy1ДyL,.-&MR&+5&MR&+5>&jR &&+55&jX $&+55y>&2jR -&+55f}&Rj +&+55y ?@ LY LY  LY?+?+9/+99333310! ! 267!"!&&eKFdTƽXtjjvuHf}f ?@ HY FY  FY?+?+9/+99333310#"&532267!"!&&} ++Ќ .{y>&~jR /&+55f}&j 0&+55B>&jR /&+55B&j .&+55 &M/R&+5J&\M&+5 >&j5R ,&+55J&\j *&+55 s&SR )&+55J!&\S+ '&+55>&j`R )&+55f&j (&+55m- /@   #LY LY ?+?+?93310!!3##l{sR /@   "GY GY ?+?+?93310!!3## RH>&j'R -&+55&j -&+55/D+& DR&D>&;qDR&[I@)    LY ?3?39/33+339333310!!!!!!!!w/} RR}1^X+mVNRE@%   HY ?3?39/33+3393333103!!3#!!#m  JG}=%boL 2@  LY LY?+?9/+99333103! $54$!3#"33\\,!ȫJnx}pfTGo"R@) ""  $##LY# LY?2+3?9/+9/993339310"&54$!333253#"&'#"3265?+s+gnjwR#SHz_Sf+R@+# )-,  'FYFY FY?2++?+?9/99933339310"323&5332653#"&''26754&#"Dm ^ifZy@:h~v)1pLywfv|GaPZ!ү}3)P@* !( !+* %KY* KY%LY%?+?+9/+99/933310#532654&#"'6!232653#"&'4wY\w`gi`tah3=~lp|+Df&P@* %"%('!HY'GYFY?+?+9/+99/933310%2653!"&'&&##532654#"'6632fe\XѧI_zj}v|qg[GJH*%9ue3m!"J@(   $#KY # KY  LY ?+?+?9/+99333103##4&##532654&#"'6632ύwc f"r}v`ipHLN{fJ@(  ! HY " GY GY?+?+?9/+99333102654#"'6323##4&##5ᶠL~rGJHO9 !i9XVZ!C@# ! !#"!!LY! LY LY?+?+?+9/93331032653#"&5!#"'5326_ff_/FZmIB96HSf<i^rz+pz_wRC@#GY FY FY?+?+?+9/93331032653!"&5!#"'532![g`]\_{R/%%av#}idv|qqK@&    LY  LY?+??39/+9/93333310 !#3!332653VyZdf[mVjarz+RK@&   GYFY?+??39/+9/93333310!332653!"&'5!#^d^[^RD+idv|qd#Ry:@ LY LY LY?+?+9/+93310!! 4$3 &#"32655!NwQVhBcSc\ff:@ GY FY GY?+?+9/+93310!! !2&&#"3265!-G$KJMjO),R"(Š+9@ LY LY ?+?+39/9331032653#"&5!5!!cdd`ZEPjasy+n+R9@  GY FY?+?+39/93310"&5!5!!3253?^f!jeb&C@$  #('$KY LYLY?+?+9/+99331046632&&#"!3#"! 7! $54675&&~휚qrbfh ĸ̻^j]FL?8e`zv^MƓTf!C@$#"!!HY!! GY GY ?+?+9/+993310# !27#"$54675&54632&#"3Ǽ{ڳHVKi 6ȇOHLGD&DR&DyJ&$gZyf&DgJ&$fR&+5Z&Df+&+5J&$wR &+55Z&Dw -&+55J&$xR &+55&Dx 4&+55JJ&$yR &+55Z^&Dy -&+55Jb&$zR .&+55Z&Dz E&+55yJs&$'gKBR &+5Zy!&D'gK7&+5J&${ R &+55Z&D{ /&+55J&$|R &+55Z&D| /&+55JX&$}R "&+55Z&D} 9&+55J`&$~R (&+55Z&D~ ?&+55yJb&$'NBZg&+5Zy&D&Ng'&+5y&(gfy9f&Hg&(fR&+5f9&Hf &+5H&(RR&+5f9&HR$&+5&(wR &+55f&Hw "&+55&(xR &+559&Hx )&+55hJ&(yR &+55fb&Hy "&+55b&(zR *&+55f9&Hz :&+55ys&('gKR&+5fy9!&H'gK,&+5J&,fR&+5w&f&+5Jy&,gy&Lgyy&2gfy}f&Rgy&2fR&+5f}&Rf&+5y&2wR &+55f&Rw &+55y&2xR %&+55!}&Rx #&+55yJ&2yR &+55f}&Ry &+55yb&2zR 6&+55f}&Rz 4&+55yys&2'gKR(&+5fy}!&R'gK &&+5ys&_v!R(&+5fs!&`vu,&+5ys&_CuR(&+5fs!&`C#&+5y&_fR#&+5fs&`f'&+5yH&_RR'&+5fs&`R#&+5yy&_gfys&`gy;&8ghymR&Xg;&8fXR&+5m&Xf&+5s&avR#&+5!&bv(&+5s&aC7R#&+5!&bC&+5&afbR&+5&bf#&+5H&aRR"&+5&bR&+5y&agdy&bgy&<gJR&\g&<fR &+5J&\f&+5H&<RR&+5J&\R&+5f&B!  /2210&&'53!&&'53NDc1Dc163=D63=DN @    /399//310#&'#5673'673#pcra=XTS2E}K[eAYch\rT @  /399//3106673#&'#7#&'53RpMXarjiT{L5Nc)Ae`Fien]N !@   @ H/3+299//310#&'#5673#'6654#"5632pcra=X}h A8Z%#=K[eAYtlGz"7iR$I !!"H!@H ! !/?@H/3+]9///33++310".#"#663232673#&'#56731$IEB(*egR%KF@(*e gxuzc[w:C)!)1g"+1l{EYQMU`T@  @ H/3+99//310673#"&'332673JJ1ZsrP]eWh eft[LIKJT@  @ H/3+99//310#&'53"&'33273psZ1GI  [tk`㝎T.@H /?o/3]299//+310#'6654#"5632"&'332731u^ 91P)$EYe  {f)d 3ZDT%E@ "@"H  /?@H/3+]9///3+3]310".#"#663232673"&'3326731$IEB(*eeT%KF@(*e e䙩\fc\  3!(2by"+1hsC>>C+@`  //]393104&'3#"'5326JEH>"yhFA6.%/6qB8IT-ar-D @    3/299310"'53253QE78mݖDVD @    3/299310"'53253QE78mݖD>/ !@   ??999310!#47'3NwcDRmH} (@  MY &MY?+?+993310#"3232654&#"H535Ϻ5h !@   ??999310!#47'39DwČ<1{M5 }+@ MY&NY?+?+93310!!5>54&#"'6632! +[)ne^P}x|側n{YQ5GPLAhTv| V-}&C@$ "('OY $$MY$& MY %?+?+9/+993310!"'532654&##53 54&#"'6!2]`^zwUcs !P.2~uo^f2@oh A@"   OY $??9/33+39933310%##!533!47#o_T-Psm:8Vp7u)h<@  MYNY MY%?+?+9/+9333102!"'53265!"'!!66JLZ:(i8!#eDP-4 >^?J=h+@$NY?+9?93310!5!BΣX9V7}%D@#" '& OYMY&OY%?+?+9/+9933310!"'532##"&5432%"32654&&7:YZ 9sޜ~zy{wD}PVYQ}`Y[#)&IIV#4_@12-00/4$,+&(() ),/65 0,$))*2-&*?3333/3]2222/999333333333310#"'53254&&'.54632&&#"##33#7#/zNqfw'TQF"ogn+*N20=5UuI۰Ӕ`q%/V!%$7H4Yn.p)%&/"3\wX/h&7zd'H&WzfTf &J@'  !'(!#GY FYGY?+?+99??+99333310%26754&#"7##"32373!"'53265\} mv 䃕)ȷĽ- 4DVfT!&K3&+5fT&N'&+5fT&OF0&+5fT!&:w+&+5??91033Js&CR &+5s&vIR &+5s&KR&+5r>&jR &+55H&RR &+5z&MR&+5Z&NR&+5L+&Q5L&OR &+5h&-qN'Tr>&jR &+55r>&jR &+55+&fR&+5y&g2I6$$q7)9):)<DFGHJPQRSTUVXqqqqqqqqq !$)&)+-/1356)8:CqDFHJVq_qbqiqyz{~qqqq)))WXqY`bjrqsq}qqqq q  q qqqq !q"#q%q&'q()q*+q,-q./q01q23q468:<@BDJLNRTVXZ\^`bdfhjlnoqs) $q 7) 9) :) < D F G H J P Q R S T U V X q q q q q q  q q q           ! $) &) + - / 1 3 5 6) 8 : Cq D F H J Vq _q bq iq y z { ~              q    q  q       q ) ) )  W Xq Y ` b j rq sq }         q q  q  q         q   q   q    q q  q  !q " #q %q & 'q ( )q * +q , -q . /q 0 1q 2 3q 4 6 8 : < @ B D J L N R T V X Z \ ^ ` b d f h j l n o q s ) -&*247q89:<$q&q*,.02468:Gfmqqrsuxqqq\qT_al|\~qqq\\\\ qIKMOQSUWY[]_acegikmoqsq7$&q&*247q89:<$q&q*,.02468:Gfmqqrsuxqqq\qT_al|\~qqq\\\\ qIKMOQSUWY[]_acegikmoqsq$q$ q$&$*$- $2$4$7q$9$:$<$$$$$$$$$$$$$$$$$$$$$$q$&q$6$8$:$G$$$$$q$ q$_$I$K$M$O$Q$S$U$W$Y$[$]$_$o$q$s$q%%%$%7%9%:%;%<%=%%%%%%%%%%%$%&%6%8%:%;%=%?%C%%%%%%% %X%%%!%#%%%'%)%+%-%/%1%3%o%q%s%&&&*&2&4&&&&&&&&&&&&&&&&&&&&G&_&I&K&M&O&Q&S&U&W&Y&[&]&_'''$'7'9':';'<'='''''''''''$'&'6'8':';'='?'C''''''' 'X'''!'#'%''')'+'-'/'1'3'o'q's'(-{)))"))$))))))))))C)) )X)))!)#)%)')))+)-)/)1)3.&.*.2.4....................G._.I.K.M.O.Q.S.U.W.Y.[.]._/\/ \/&/*/2/4/7/8/9/:/</////////////////////////$/&/*/,/./0/2/4/6/8/:/G/////\/ \/_/a/I/K/M/O/Q/S/U/W/Y/[/]/_/a/c/e/g/i/k/m/o/q/s/222$27292:2;2<2=22222222222$2&26282:2;2=2?2C2222222 2X222!2#2%2'2)2+2-2/21232o2q2s2333$3;3=3333333333;3=3?3C33 3X333!3#3%3'3)3+3-3/3133444$47494:4;4<4=44444444444$4&46484:4;4=4?4C4444444 4X444!4#4%4'4)4+4-4/41434o4q4s47777")7$q7&7*727477)7D\7Fq7Gq7Hq7Jq7P7Q7Rq7S7Tq7U7V7X7Y7Z7[7\7]7q7q7q7q7q7q77777777q7\7\7\7\7\7\7q7q7q7q7q7q7q7q7q7q7q777777q7\7q7\7q7\77q77q77q77q7q7q7q7q7q7q7q77q77q77q77q7777 77q77q77q77q7777!7$)7&)7+7-7/717375777<7>7@7Cq7D\7F\7G7Hq7J7777777 7W7Xq7Y\7_7`q7b7q7\7q7 \7!q7"\7#q7%q7&\7'q7(\7)q7*\7+q7,\7-q7.\7/q70\71q72\73q74\76q78q7:q7<@<C<D<F<G<H<J<< <W<X<Y<_<`<b<<<< <!<"<#<%<&<'<(<)<*<+<,<-<.</<0<1<2<3<4<6<8<:<<<@<B<D<I<J<K<L<M<N<O<Q<R<S<T<U<V<W<X<Y<Z<[<\<]<^<_<`<b<d<f<h<j<l<n=&=*=2=4====================G=_=I=K=M=O=Q=S=U=W=Y=[=]=_>-DD DD EE EYEZE[E\E]EE7E<E>E@EEEE EpF)F )F)F )HH HYHZH[H\H]HH7H<H>H@HHHH HpI{I {I{I {KK KK NFNGNHNRNTNNNNNNNNNNNNNNNNNNNNNNNNNNNNHN`N6N8N:N<N@NBNDNJNLNNNRNTNVNXNZN\N^N`PP PP QQ QQ RR RYRZR[R\R]RR7R<R>R@RRRR RpSS SYSZS[S\S]SS7S<S>S@SSSS SpURU RUDUFUGUHUJURUTUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUDUFUHURU RUYU`UU U"U&U(U*U,U.U0U2U4U6U8U:U<U@UBUDUJULUNURUTUVUXUZU\U^U`W)W )W)W )YRY RYYY")YRYY RY ZRZ RZZZ")ZRZZ RZ [F[G[H[R[T[[[[[[[[[[[[[[[[[[[[[[[[[[[[H[`[6[8[:[<[@[B[D[J[L[N[R[T[V[X[Z[\[^[`\R\ R\\\")\R\\ R\ ^-q q&*- 247q9:<$q&q68:Gq q_IKMOQSUWY[]_oqsqq q&*- 247q9:<$q&q68:Gq q_IKMOQSUWY[]_oqsqq q&*- 247q9:<$q&q68:Gq q_IKMOQSUWY[]_oqsqq q&*- 247q9:<$q&q68:Gq q_IKMOQSUWY[]_oqsqq q&*- 247q9:<$q&q68:Gq q_IKMOQSUWY[]_oqsqq q&*- 247q9:<$q&q68:Gq q_IKMOQSUWY[]_oqsq-{&*24G_IKMOQSUWY[]_-{-{-{-{$79:;<=$&68:;=?C X!#%')+-/13oqs$79:;<=$&68:;=?C X!#%')+-/13oqs$79:;<=$&68:;=?C X!#%')+-/13oqs$79:;<=$&68:;=?C X!#%')+-/13oqs$79:;<=$&68:;=?C X!#%')+-/13oqs$79:;<=$&68:;=?C X!#%')+-/13oqs$79:;<=$&68:;=?C X!#%')+-/13oqs$C X!#%')+-/13$C X!#%')+-/13$C X!#%')+-/13$C X!#%')+-/13")$&*24DFGHJPQRSTUVX] !+-/135<>@CDFGHJ WXY_`b !"#%&'()*+,-./0123468:<@BDIJKLMNOQRSTUVWXYZ[\]^_`bdfhjln$;=;=?C X!#%')+-/13             YZ[\]7<>@ p YZ[\]7<>@ p YZ[\]7<>@ p YZ[\]7<>@ p YZ[\]7<>@ p YZ[\]7<>@ p YZ[\]7<>@ p YZ[\]7<>@ p   YZ[\]7<>@ pR R")R R  YZ[\]7<>@ pR R")R R q q&*- 247q9:<$q&q68:Gq q_IKMOQSUWY[]_oqsq  q q&*- 247q9:<$q&q68:Gq q_IKMOQSUWY[]_oqsq  q q&*- 247q9:<$q&q68:Gq q_IKMOQSUWY[]_oqsq  &*24G_IKMOQSUWY[]_&*24G_IKMOQSUWY[]_&*24G_IKMOQSUWY[]_&*24G_IKMOQSUWY[]_$79:;<=$&68:;=?C X!#%')+-/13oqsR R "@E=K=N=O=`={R R$79:;<=$&68:;=?C X!#%')+-/13oqs-{ YZ[\]7<>@ p-{ YZ[\]7<>@ p-{ YZ[\]7<>@ p-{ YZ[\]7<>@ p-{ YZ[\]7<>@ p  &*24G_IKMOQSUWY[]_FGHRTH`68:<@BDJLNRTVXZ\^`FGHRTH`68:<@BDJLNRTVXZ\^`\ \&*24789:<$&*,.02468:G\ \_aIKMOQSUWY[]_acegikmoqs\ \&*24789:<$&*,.02468:G\ \_aIKMOQSUWY[]_acegikmoqs\ \&*24789:<$&*,.02468:G\ \_aIKMOQSUWY[]_acegikmoqsR R "@E=K=N=O=`=R R\ \&*24789:<$&*,.02468:G\ \_aIKMOQSUWY[]_acegikmoqs\ \&*24789:<$&*,.02468:G\ \_aIKMOQSUWY[]_acegikmoqs  $79:;<=$&68:;=?C X!#%')+-/13oqs$79:;<=$&68:;=?C X!#%')+-/13oqs$79:;<=$&68:;=?C X!#%')+-/13oqs-{R RDFGHJRTDFHR RY` "&(*,.02468:<@BDJLNRTVXZ\^`R RDFGHJRTDFHR RY` "&(*,.02468:<@BDJLNRTVXZ\^`R RDFGHJRTDFHR RY` "&(*,.02468:<@BDJLNRTVXZ\^`$$$$")$$q$&$*$2$4$7)$D\$Fq$Gq$Hq$Jq$P$Q$Rq$S$Tq$U$V$X$Y$Z$[$\$]$q$q$q$q$q$q$$$$$$$$q$\$\$\$\$\$\$q$q$q$q$q$q$q$q$q$q$q$$$$$$q$\$q$\$q$\$$q$$q$$q$$q$q$q$q$q$q$q$q$$q$$q$$q$$q$$$$ $$q$$q$$q$$q$$$$!$$)$&)$+$-$/$1$3$5$7$<$>$@$Cq$D\$F\$G$Hq$J$$$$$$$ $W$Xq$Y\$_$`q$b$q$\$q$ \$!q$"\$#q$%q$&\$'q$(\$)q$*\$+q$,\$-q$.\$/q$0\$1q$2\$3q$4\$6q$8q$:q$&@&Cq&D\&F\&G&Hq&J&&&&&&& &W&Xq&Y\&_&`q&b&q&\&q& \&!q&"\&#q&%q&&\&'q&(\&)q&*\&+q&,\&-q&.\&/q&0\&1q&2\&3q&4\&6q&8q&:q&(@(Cq(D\(F\(G(Hq(J((((((( (W(Xq(Y\(_(`q(b(q(\(q( \(!q("\(#q(%q(&\('q((\()q(*\(+q(,\(-q(.\(/q(0\(1q(2\(3q(4\(6q(8q(:q(8@8C8D8F8G8H8J88 8W8X8Y8_8`8b8888 8!8"8#8%8&8'8(8)8*8+8,8-8.8/808182838486888:8<8@8B8D8I8J8K8L8M8N8O8Q8R8S8T8U8V8W8X8Y8Z8[8\8]8^8_8`8b8d8f8h8j8l8n9R9 R999")9R99 R9 :::"):$:&:*:2:4:D:F:G:H:J:P:Q:R:S:T:U:V:X:]:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ::::::::::::!:+:-:/:1:3:5:<:>:@:C:D:F:G:H:J:: :W:X:Y:_:`:b:::: :!:":#:%:&:':(:):*:+:,:-:.:/:0:1:2:3:4:6:8:::<:@:B:D:I:J:K:L:M:N:O:Q:R:S:T:U:V:W:X:Y:Z:[:\:]:^:_:`:b:d:f:h:j:l:n;&;*;2;4;;;;;;;;;;;;;;;;;;;;G;_;I;K;M;O;Q;S;U;W;Y;[;];_=&=*=2=4====================G=_=I=K=M=O=Q=S=U=W=Y=[=]=_?&?*?2?4????????????????????G?_?I?K?M?O?Q?S?U?W?Y?[?]?_CqC qC&C*C- C2C4C7qC9C:C<CCCCCCCCCCCCCCCCCCCCC$qC&qC6C8C:CGCCCCCqC qC_CICKCMCOCQCSCUCWCYC[C]C_CoCqCsCqDD DD E-{GGG$G7G9G:G;G<G=GGGGGGGGGGG$G&G6G8G:G;G=G?GCGGGGGGG GXGGG!G#G%G'G)G+G-G/G1G3GoGqGsGVqV qVfVmVqqVrVsVuVxVqV qVT[[[V[_[b[d[i[p[q[r[t[u[x[[[ [T\\\V\_\b\f\i\m\s\v\y\z\{\|\}\~\\\\\\\\\\\\\\\\\\\\ \!]q]r]x]T^^ ^^ _q_ q_f_m_qq_r_s_u_x_q_ q_T```V`_`b`i`t`` aaaaV\a_\ab\afai\amasavayqaza{a|a}a~qaaaaaaaaaqaaqaqaaqaaaaaqaaaaaaa a!aSbqb qbfbmbqqbrbsbubxbqb qbTdfdmdsfffVf_fbfdfifpfqfrftfufxfff fThfhmhshhiqi qifimiqqirisiuixiqi qiTmmmVm_mbmdmimpmqmrmtmumxmmm mToooVo_obodoiotooo qqqqV\q_\qb\qfqi\qmqsqvqyqqzq{q|q}q~qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq q!qSrrrVr_rbrfrirmrsrvryrzr{r|r}r~rrrrrrrrrrrrrrrrrrrr r!sssVs_sbsdsispsqsrstsxsss sTtftmtsttuuuVu_ubufuiumuu vqvrvxvTxxxVx_xbxfxixmxsxvxyxzx{x|x}x~xxxxxxxxxxxxxxxxxxxx x!y){{ {{ || |||| ~) y~     y~ y~         y~       n| q)qqqqqq jqklmqrqsuwy}~qqqqqqq)qqqqqqqqq q q q qqql~    n|   n|   n| l|~  qff jlrqs~ffqqq q  q q qqq qn|q qq  r|  q)qqqqqq jqklmqrqsuwy}~qqqqqqq)qqqqqqqqq q q q qqq)l|~    l|~   rsz|  rsv  l~  q)qqqqqq jqklmqrqsuwy}~qqqqqqq)qqqqqqqqq q q q qqqqff jlrqs~ffqqq q  q  rsz l|~  ))  n|   n|  rsz|  rsz|   ) js      j  )j       js   s  j        m  m         js    m  m  j   sq)qqqqqq jqklmqrqsuwy}~qqqqqqq)qqqqqqqqq q q q qqq js  ")$&*24DFGHJPQRSTUVX !+-/135CDFGHJ WXY_`b !"#%&'()*+,-./0123468:<@BDIJKLMNOQRSTUVWXYZ[\]^_`bdfhjlnR R")R R ")$&*24DFGHJPQRSTUVX !+-/135CDFGHJ WXY_`b !"#%&'()*+,-./0123468:<@BDIJKLMNOQRSTUVWXYZ[\]^_`bdfhjlnR R")R R ")$&*24DFGHJPQRSTUVX !+-/135CDFGHJ WXY_`b !"#%&'()*+,-./0123468:<@BDIJKLMNOQRSTUVWXYZ[\]^_`bdfhjlnR R")R R ")$&*24DFGHJPQRSTUVX] !+-/135<>@CDFGHJ WXY_`b !"#%&'()*+,-./0123468:<@BDIJKLMNOQRSTUVWXYZ[\]^_`bdfhjlnR R")R R 7$&q7$&q7$&q$q7)9):)<DFGHJPQRSTUVXqqqqqqqqq !$)&)+-/1356)8:CqDFHJVq_qbqiqyz{~qqqq)))WXqY`bjrqsq}qqqq q  q qqqq !q"#q%q&'q()q*+q,-q./q01q23q468:<@BDJLNRTVXZ\^`bdfhjlnoqs)$q7)9):)<DFGHJPQRSTUVXqqqqqqqqq !$)&)+-/1356)8:CqDFHJVq_qbqiqyz{~qqqq)))WXqY`bjrqsq}qqqq q  q qqqq !q"#q%q&'q()q*+q,-q./q01q23q468:<@BDJLNRTVXZ\^`bdfhjlnoqs)&*247q89:<$q&q*,.02468:Gfmqqrsuxqqq\qT_al|\~qqq\\\\ qIKMOQSUWY[]_acegikmoqsq $q 7) 9) :) < D F G H J P Q R S T U V X q q q q q q                        q  q  q                             ! $) &) + - / 1 3 5 6) 8 : Cq D F H J Vq _q bq iq y z { ~              q    q  q       q ) ) )  W Xq Y ` b j rq sq }         q q  q  q         q    q    q    q q  q   !q " #q %q & 'q ( )q * +q , -q . /q 0 1q 2 3q 4 6 8 : < @ B D J L N R T V X Z \ ^ ` b d f h j l n o q s ) & * 2 4 7q 8 9 : <                         $q &q * , . 0 2 4 6 8 : G f m qq r s u x  q  q   q  \ q        T _ a l |\ ~        q    q    q  \  \    \     \        q  I K M O Q S U W Y [ ] _ a c e g i k m o q s q!q!r!x!TSSSS TTTVT_TbTfTiTmTsTvTyTzT{T|T}T~TTTTTTTTTTTTTTTTTTTT T!XqX qX&X*X- X2X4X7qX9X:X<XXXXXXXXXXXXXXXXXXXXX$qX&qX6X8X:XGXXXXXqX qX_XIXKXMXOXQXSXUXWXYX[X]X_XoXqXsXqYY YY ZZZVZ_ZbZdZiZpZqZrZtZuZxZZZ ZT`IR`WR`Yf`Zf`[f`\f`f`%R`'R`7f`f`f`4R`5R`]R`^R`pf`R`RbIfbWfbYfbZfb[fb\fbfb%fb'fb7fbfbfb4fb5fb]fb^fbpfbfbfjj jj llllllllllllllllllllll lrlslzl|lllllllllllllllllllllllllllll llllllmmmmmmm msmmmnn nnnnnnnnn n|nnnnnnnnnnnnnnn nnnoo ooooooooo omooooooooooooooooooooooooooopppppplp~ppppppppppppppppp p ppprqr qrrrqrrrrrrrqr qrnr|rrrrrrrrqrrrrrrrrrrrrrrrrrrr qrrqrrrsqs qssssssssssssqs qsjsms}sssssssssssssssssssssssssssss s ssssstqt qtttqtttttttqt qtnt|ttttttttqttttttttttttttttttt qttqtttuqu quuuuuuuuuuuuqu qujumu}uuuuuuuuuuuuuuuuuuuuuuuuuuuuu u uuuuuv vx xzzzz zzzzz z|q|q||||||||q| q|r|s|||||| | ||||}} }}}}}}}}} }}}}}}}}}}}}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~ ~r~s~z~|~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~  qff jlrqs~ffqqq q  q  sqff jlrqs~ffqqq q  q  s s s rsz|    rsz|    rsz|  sl~  )    |   m rsv    )))))) jklqrsuwy}~)))))    ) js   f |   l{=}~j    )l{=}~j  l|~  j  l|~  j  l|~  j  )  )))))) jklqrsuwy}~)))))    ) js  \ \fH\ \|qqH HHq qq qmqq   l~  q)qqqqqq jqklmqrqsuwy}~qqqqqqq)qqqqqqqqq q q q qqq js  )) jklqrsuwy}~))     s)) jklqrsuwy}~))     sl{=}~j  )  )    |         l|~  j   f m| )  )  )  q qqq qn|q qq  q qqq qn|q qq       rsz|    rsz|   l|~  j    )   rsz|    rsz|    rsz|    rsz|   qff jlrqs~ffqqq q  q  sqff jlrqs~ffqqq q  q  sqff jlrqs~ffqqq q  q  sq)qqqqqq jqklmqrqsuwy}~qqqqqqq)qqqqqqqqq q q q qqq js  )))))) jklqrsuwy}~)))))    ) js  l{=}~j  l|~  j          q             r v |       q                                                                           q             r v |       q                                      )   q rv|q    q rv|q    r    r|   )  q q&*- 247q9:<$q&q68:Gq q_IKMOQSUWY[]_oqsq  q q&*- 247q9:<$q&q68:Gq q_IKMOQSUWY[]_oqsq     !q! q!&!*!- !2!4!7q!9!:!<!!!!!!!!!!!!!!!!!!!!!$q!&q!6!8!:!G!!!!!q! q!_!I!K!M!O!Q!S!U!W!Y![!]!_!o!q!s!q"" "" #q# q#&#*#- #2#4#7q#9#:#<#####################$q#&q#6#8#:#G#####q# q#_#I#K#M#O#Q#S#U#W#Y#[#]#_#o#q#s#q$$ $$ %q% q%&%*%- %2%4%7q%9%:%<%%%%%%%%%%%%%%%%%%%%%$q%&q%6%8%:%G%%%%%q% q%_%I%K%M%O%Q%S%U%W%Y%[%]%_%o%q%s%q&& && 'q' q'&'*'- '2'4'7q'9':'<'''''''''''''''''''''$q'&q'6'8':'G'''''q' q'_'I'K'M'O'Q'S'U'W'Y'[']'_'o'q's'q(( (( )q) q)&)*)- )2)4)7q)9):)<)))))))))))))))))))))$q)&q)6)8):)G)))))q) q)_)I)K)M)O)Q)S)U)W)Y)[)])_)o)q)s)q** ** +q+ q+&+*+- +2+4+7q+9+:+<+++++++++++++++++++++$q+&q+6+8+:+G+++++q+ q+_+I+K+M+O+Q+S+U+W+Y+[+]+_+o+q+s+q,, ,, -q- q-&-*-- -2-4-7q-9-:-<---------------------$q-&q-6-8-:-G-----q- q-_-I-K-M-O-Q-S-U-W-Y-[-]-_-o-q-s-q.. .. /q/ q/&/*/- /2/4/7q/9/:/</////////////////////$q/&q/6/8/:/G/////q/ q/_/I/K/M/O/Q/S/U/W/Y/[/]/_/o/q/s/q00 00 1q1 q1&1*1- 121417q191:1<111111111111111111111$q1&q16181:1G11111q1 q1_1I1K1M1O1Q1S1U1W1Y1[1]1_1o1q1s1q22 22 3q3 q3&3*3- 323437q393:3<333333333333333333333$q3&q36383:3G33333q3 q3_3I3K3M3O3Q3S3U3W3Y3[3]3_3o3q3s3q44 44 5-{66 6Y6Z6[6\6]6676<6>6@6666 6p7-{88 8Y8Z8[8\8]8878<8>8@8888 8p9-{:: :Y:Z:[:\:]::7:<:>:@:::: :p;-{<< <Y<Z<[<\<]<<7<<<><@<<<< <p=-{>> >Y>Z>[>\>]>>7><>>>@>>>> >p?-{@@ @Y@Z@[@\@]@@7@<@>@@@@@@ @pA-{BB BYBZB[B\B]BB7B<B>B@BBBB BpC-{DD DYDZD[D\D]DD7D<D>D@DDDD DpIII$I7I9I:I;I<I=IIIIIIIIIII$I&I6I8I:I;I=I?ICIIIIIII IXIII!I#I%I'I)I+I-I/I1I3IoIqIsIJJ JYJZJ[J\J]JJ7J<J>J@JJJJ JpKKK$K7K9K:K;K<K=KKKKKKKKKKK$K&K6K8K:K;K=K?KCKKKKKKK KXKKK!K#K%K'K)K+K-K/K1K3KoKqKsKLL LYLZL[L\L]LL7L<L>L@LLLL LpMMM$M7M9M:M;M<M=MMMMMMMMMMM$M&M6M8M:M;M=M?MCMMMMMMM MXMMM!M#M%M'M)M+M-M/M1M3MoMqMsMOOO$O7O9O:O;O<O=OOOOOOOOOOO$O&O6O8O:O;O=O?OCOOOOOOO OXOOO!O#O%O'O)O+O-O/O1O3OoOqOsOQQQ$Q7Q9Q:Q;Q<Q=QQQQQQQQQQQ$Q&Q6Q8Q:Q;Q=Q?QCQQQQQQQ QXQQQ!Q#Q%Q'Q)Q+Q-Q/Q1Q3QoQqQsQSSS$S7S9S:S;S<S=SSSSSSSSSSS$S&S6S8S:S;S=S?SCSSSSSSS SXSSS!S#S%S'S)S+S-S/S1S3SoSqSsSUUU$U7U9U:U;U<U=UUUUUUUUUUU$U&U6U8U:U;U=U?UCUUUUUUU UXUUU!U#U%U'U)U+U-U/U1U3UoUqUsUXIRXWRXYfXZfX[fX\fXfX%RX'RX7fXfXfX4RX5RX]RX^RXpfXRXRZIRZWRZYfZZfZ[fZ\fZfZ%RZ'RZ7fZfZfZ4RZ5RZ]RZ^RZpfZRZR\IR\WR\Yf\Zf\[f\\f\f\%R\'R\7f\f\f\4R\5R\]R\^R\pf\R\R^IR^WR^Yf^Zf^[f^\f^f^%R^'R^7f^f^f^4R^5R^]R^^R^pf^R^R`IR`WR`Yf`Zf`[f`\f`f`%R`'R`7f`f`f`4R`5R`]R`^R`pf`R`Raaa$aaaaaaaaaaCaa aXaaa!a#a%a'a)a+a-a/a1a3fIffWffYffZff[ff\ffff%ff'ff7ffffff4ff5ff]ff^ffpfffffhIfhWfhYfhZfh[fh\fhfh%fh'fh7fhfhfh4fh5fh]fh^fhpfhfhfjIfjWfjYfjZfj[fj\fjfj%fj'fj7fjfjfj4fj5fj]fj^fjpfjfjflIflWflYflZfl[fl\flfl%fl'fl7flflfl4fl5fl]fl^flpflflfnIfnWfnYfnZfn[fn\fnfn%fn'fn7fnfnfn4fn5fn]fn^fnpfnfnfooo")o$o&o*o2o4oDoFoGoHoJoPoQoRoSoToUoVoXo]oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo oooooooooooo!o+o-o/o1o3o5o<o>o@oCoDoFoGoHoJoo oWoXoYo_o`oboooo o!o"o#o%o&o'o(o)o*o+o,o-o.o/o0o1o2o3o4o6o8o:o<o@oBoDoIoJoKoLoMoNoOoQoRoSoToUoVoWoXoYoZo[o\o]o^o_o`obodofohojolonpRp Rppp")pRpp Rp qqq")q$q&q*q2q4qDqFqGqHqJqPqQqRqSqTqUqVqXq]qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq qqqqqqqqqqqq!q+q-q/q1q3q5q<q>q@qCqDqFqGqHqJqq qWqXqYq_q`qbqqqq q!q"q#q%q&q'q(q)q*q+q,q-q.q/q0q1q2q3q4q6q8q:q<q@qBqDqIqJqKqLqMqNqOqQqRqSqTqUqVqWqXqYqZq[q\q]q^q_q`qbqdqfqhqjqlqnrRr Rrrr")rRrr Rr sss")s$s&s*s2s4sDsFsGsHsJsPsQsRsSsTsUsVsXs]ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss ssssssssssss!s+s-s/s1s3s5s<s>s@sCsDsFsGsHsJss sWsXsYs_s`sbssss s!s"s#s%s&s's(s)s*s+s,s-s.s/s0s1s2s3s4s6s8s:s<s@sBsDsIsJsKsLsMsNsOsQsRsSsTsUsVsWsXsYsZs[s\s]s^s_s`sbsdsfshsjslsntRt Rttt")tRtt Rt { {{ {")$q&*247)D\FqGqHqJqPQRqSTqUVXYZ[\]qqqqqqq\\\\\\qqqqqqqqqqqq\q\q\qqqqqqqqqqqqqqq qqqq!$)&)+-/1357<>@CqD\F\GHqJ WXqY\_`qbq\q \!q"\#q%q&\'q(\)q*\+q,\-q.\/q0\1q2\3q4\6q8q:q h $ % N3 $  "  (_ 8 \ \ Tw  Digitized data copyright 2011, Google Corporation.Open Sans SemiboldRegularAscender - Open Sans Semibold Build 100Version 1.10OpenSans-SemiboldOpen Sans is a trademark of Google and may be registered in certain jurisdictions.Ascender Corporationhttp://www.ascendercorp.com/http://www.ascendercorp.com/typedesigners.htmlLicensed under the Apache License, Version 2.0http://www.apache.org/licenses/LICENSE-2.0Digitized data copyright 2011, Google Corporation.Open Sans SemiboldRegularAscender - Open Sans Semibold Build 100Version 1.10OpenSans-SemiboldOpen Sans is a trademark of Google and may be registered in certain jurisdictions.Ascender Corporationhttp://www.ascendercorp.com/http://www.ascendercorp.com/typedesigners.htmlLicensed under the Apache License, Version 2.0http://www.apache.org/licenses/LICENSE-2.0ff      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~.notdefnullnonmarkingreturnspaceexclamquotedbl numbersigndollarpercent ampersand quotesingle parenleft parenrightasteriskpluscommahyphenperiodslashzeroonetwothreefourfivesixseveneightninecolon semicolonlessequalgreaterquestionatABCDEFGHI.altJKLMNOPQRSTUVWXYZ bracketleft backslash bracketright asciicircum underscoregraveabcdefghijklmnopqrstuvwxyz braceleftbar braceright asciitildenonbreakingspace exclamdowncentsterlingcurrencyyen brokenbarsectiondieresis copyright ordfeminine guillemotleft logicalnotuni00AD registered overscoredegree plusminus twosuperior threesuperioracutemu paragraphperiodcenteredcedilla onesuperior ordmasculineguillemotright onequarteronehalf threequarters questiondownAgraveAacute AcircumflexAtilde AdieresisAringAECcedillaEgraveEacute Ecircumflex Edieresis Igrave.alt Iacute.altIcircumflex.alt Idieresis.altEthNtildeOgraveOacute OcircumflexOtilde OdieresismultiplyOslashUgraveUacute Ucircumflex UdieresisYacuteThorn germandblsagraveaacute acircumflexatilde adieresisaringaeccedillaegraveeacute ecircumflex edieresisigraveiacute icircumflex idieresisethntildeograveoacute ocircumflexotilde odieresisdivideoslashugraveuacute ucircumflex udieresisyacutethorn ydieresisAmacronamacronAbreveabreveAogonekaogonekCacutecacute Ccircumflex ccircumflexCdotcdotCcaronccaronDcarondcaronDcroatdcroatEmacronemacronEbreveebreve Edotaccent edotaccentEogonekeogonekEcaronecaron Gcircumflex gcircumflexGbrevegbreveGdotgdot Gcommaaccent gcommaaccent Hcircumflex hcircumflexHbarhbar Itilde.altitilde Imacron.altimacron Ibreve.altibreve Iogonek.altiogonekIdotaccent.altdotlessiIJ.altij Jcircumflex jcircumflex Kcommaaccent kcommaaccent kgreenlandicLacutelacute Lcommaaccent lcommaaccentLcaronlcaronLdotldotLslashlslashNacutenacute Ncommaaccent ncommaaccentNcaronncaron napostropheEngengOmacronomacronObreveobreve Ohungarumlaut ohungarumlautOEoeRacuteracute Rcommaaccent rcommaaccentRcaronrcaronSacutesacute Scircumflex scircumflexScedillascedillaScaronscaron Tcommaaccent tcommaaccentTcarontcaronTbartbarUtildeutildeUmacronumacronUbreveubreveUringuring Uhungarumlaut uhungarumlautUogonekuogonek Wcircumflex wcircumflex Ycircumflex ycircumflex YdieresisZacutezacute Zdotaccent zdotaccentZcaronzcaronlongsflorin Aringacute aringacuteAEacuteaeacute Oslashacute oslashacute Scommaaccent scommaaccent circumflexcaronmacronbreve dotaccentringogonektilde hungarumlauttonos dieresistonos Alphatonos anoteleia EpsilontonosEtatonos Iotatonos.alt Omicrontonos Upsilontonos OmegatonosiotadieresistonosAlphaBetaGammauni0394EpsilonZetaEtaThetaIota.altKappaLambdaMuNuXiOmicronPiRhoSigmaTauUpsilonPhiChiPsiuni03A9Iotadieresis.altUpsilondieresis alphatonos epsilontonosetatonos iotatonosupsilondieresistonosalphabetagammadeltaepsilonzetaetathetaiotakappalambdauni03BCnuxiomicronpirhosigma1sigmatauupsilonphichipsiomega iotadieresisupsilondieresis omicrontonos upsilontonos omegatonos afii10023 afii10051 afii10052 afii10053 afii10054 afii10055.alt afii10056.alt afii10057 afii10058 afii10059 afii10060 afii10061 afii10062 afii10145 afii10017 afii10018 afii10019 afii10020 afii10021 afii10022 afii10024 afii10025 afii10026 afii10027 afii10028 afii10029 afii10030 afii10031 afii10032 afii10033 afii10034 afii10035 afii10036 afii10037 afii10038 afii10039 afii10040 afii10041 afii10042 afii10043 afii10044 afii10045 afii10046 afii10047 afii10048 afii10049 afii10065 afii10066 afii10067 afii10068 afii10069 afii10070 afii10072 afii10073 afii10074 afii10075 afii10076 afii10077 afii10078 afii10079 afii10080 afii10081 afii10082 afii10083 afii10084 afii10085 afii10086 afii10087 afii10088 afii10089 afii10090 afii10091 afii10092 afii10093 afii10094 afii10095 afii10096 afii10097 afii10071 afii10099 afii10100 afii10101 afii10102 afii10103 afii10104 afii10105 afii10106 afii10107 afii10108 afii10109 afii10110 afii10193 afii10050 afii10098WgravewgraveWacutewacute Wdieresis wdieresisYgraveygraveendashemdash afii00208 underscoredbl quoteleft quoterightquotesinglbase quotereversed quotedblleft quotedblright quotedblbasedagger daggerdblbulletellipsis perthousandminutesecond guilsinglleftguilsinglright exclamdblfraction nsuperiorfranc afii08941pesetaEuro afii61248 afii61289 afii61352 trademarkOmega estimated oneeighth threeeighths fiveeighths seveneighths partialdiffDeltaproduct summationminusradicalinfinityintegral approxequalnotequal lessequal greaterequallozengeuniFB01uniFB02 cyrillicbrevedotlessjcaroncommaaccent commaaccentcommaaccentrotate zerosuperior foursuperior fivesuperior sixsuperior sevensuperior eightsuperior ninesuperioruni2000uni2001uni2002uni2003uni2004uni2005uni2006uni2007uni2008uni2009uni200Auni200BuniFEFFuniFFFCuniFFFDuni01F0uni02BCuni03D1uni03D2uni03D6uni1E3Euni1E3Funi1E00uni1E01uni1F4Duni02F3 dasiaoxiauniFB03uniFB04OhornohornUhornuhornuni0300uni0301uni0303hookdotbelowuni0400uni040Duni0450uni045Duni0460uni0461uni0462uni0463uni0464uni0465uni0466uni0467uni0468uni0469uni046Auni046Buni046Cuni046Duni046Euni046Funi0470uni0471uni0472uni0473uni0474uni0475uni0476uni0477uni0478uni0479uni047Auni047Buni047Cuni047Duni047Euni047Funi0480uni0481uni0482uni0483uni0484uni0485uni0486uni0488uni0489uni048Auni048Buni048Cuni048Duni048Euni048Funi0492uni0493uni0494uni0495uni0496uni0497uni0498uni0499uni049Auni049Buni049Cuni049Duni049Euni049Funi04A0uni04A1uni04A2uni04A3uni04A4uni04A5uni04A6uni04A7uni04A8uni04A9uni04AAuni04ABuni04ACuni04ADuni04AEuni04AFuni04B0uni04B1uni04B2uni04B3uni04B4uni04B5uni04B6uni04B7uni04B8uni04B9uni04BAuni04BBuni04BCuni04BDuni04BEuni04BF uni04C0.altuni04C1uni04C2uni04C3uni04C4uni04C5uni04C6uni04C7uni04C8uni04C9uni04CAuni04CBuni04CCuni04CDuni04CE uni04CF.altuni04D0uni04D1uni04D2uni04D3uni04D4uni04D5uni04D6uni04D7uni04D8uni04D9uni04DAuni04DBuni04DCuni04DDuni04DEuni04DFuni04E0uni04E1uni04E2uni04E3uni04E4uni04E5uni04E6uni04E7uni04E8uni04E9uni04EAuni04EBuni04ECuni04EDuni04EEuni04EFuni04F0uni04F1uni04F2uni04F3uni04F4uni04F5uni04F6uni04F7uni04F8uni04F9uni04FAuni04FBuni04FCuni04FDuni04FEuni04FFuni0500uni0501uni0502uni0503uni0504uni0505uni0506uni0507uni0508uni0509uni050Auni050Buni050Cuni050Duni050Euni050Funi0510uni0511uni0512uni0513uni1EA0uni1EA1uni1EA2uni1EA3uni1EA4uni1EA5uni1EA6uni1EA7uni1EA8uni1EA9uni1EAAuni1EABuni1EACuni1EADuni1EAEuni1EAFuni1EB0uni1EB1uni1EB2uni1EB3uni1EB4uni1EB5uni1EB6uni1EB7uni1EB8uni1EB9uni1EBAuni1EBBuni1EBCuni1EBDuni1EBEuni1EBFuni1EC0uni1EC1uni1EC2uni1EC3uni1EC4uni1EC5uni1EC6uni1EC7 uni1EC8.altuni1EC9 uni1ECA.altuni1ECBuni1ECCuni1ECDuni1ECEuni1ECFuni1ED0uni1ED1uni1ED2uni1ED3uni1ED4uni1ED5uni1ED6uni1ED7uni1ED8uni1ED9uni1EDAuni1EDBuni1EDCuni1EDDuni1EDEuni1EDFuni1EE0uni1EE1uni1EE2uni1EE3uni1EE4uni1EE5uni1EE6uni1EE7uni1EE8uni1EE9uni1EEAuni1EEBuni1EECuni1EEDuni1EEEuni1EEFuni1EF0uni1EF1uni1EF4uni1EF5uni1EF6uni1EF7uni1EF8uni1EF9uni20ABuni030Fcircumflexacutecombcircumflexgravecombcircumflexhookcombcircumflextildecombbreveacutecombbrevegravecomb brevehookcombbrevetildecombcyrillichookleftcyrillicbighookUCcyrillicbighookLCone.pnumzero.osone.ostwo.osthree.osfour.osfive.ossix.osseven.oseight.osnine.osffuni2120Tcedillatcedillag.altgcircumflex.alt gbreve.altgdot.altgcommaaccent.altIIgraveIacute Icircumflex IdieresisItildeImacronIbreveIogonek IdotaccentIJ IotatonosIota Iotadieresis afii10055 afii10056uni04C0uni04CFuni1EC8uni1ECA   46latnMOL ROM  nlatnMOL (ROM B      ligaligaligalnumlnumlnumloclloclonumonumonumpnumpnumpnumsalt saltsaltss01"ss01*ss012ss02:ss02@ss02Fss03Lss03Rss03Xtnum^tnumftnumn    &.6>FNV^Pz2HJJ.,ZgwEG      !$%IJ6 "(^IO]ILI5O4LI^V0R *H C0?1 0 +0a +7S0Q0, +7<<<Obsolete>>>0!0 +; U|xG{Ӊf;]0z0b8%a&Z0  *H 0S1 0 UUS10U VeriSign, Inc.1+0)U"VeriSign Time Stamping Services CA0 070615000000Z 120614235959Z0\1 0 UUS10U VeriSign, Inc.1402U+VeriSign Time Stamping Services Signer - G200  *H 0ĵR`)J[/Kk5TX56^bMRQ4q{f*j 7٘tvJcEG.k NK+XJ,XB-uލǎlLgrIž`<cxi{-004+(0&0$+0http://ocsp.verisign.com0 U003U,0*0(&$"http://crl.verisign.com/tss-ca.crl0U% 0 +0U0U0010 UTSA1-20  *H PK$ $- 7 ,Za񑑳V@뒾89u6t:O7ʕBǠWdB5N3M'L8MxSݤ^ ⥾`߭(ǥKd[98"3/!?DA e$HDT\y>]r},CS}=*:Om ]^SWp`+nx'4[^I2300-GߍRFCmH 10  *H 01 0 UZA10U Western Cape10U Durbanville10 U Thawte10U Thawte Certification10UThawte Timestamping CA0 031204000000Z 131203235959Z0S1 0 UUS10U VeriSign, Inc.1+0)U"VeriSign Time Stamping Services CA0"0  *H 0 ʲ }uNgadڻ30X~k6xw~o< hlʽR-H=]_/kLR`@~ ?Ǵ߇_zj1.G 1s W-x43h/Š*Ë!fXWou<&]'x1"ijGC_^|}bM "Vͮv M٠h;004+(0&0$+0http://ocsp.verisign.com0U00AU:0806420http://crl.verisign.com/ThawteTimestampingCA.crl0U% 0 +0U0$U0010U TSA2048-1-530  *H JkXD1y+LͰXn)^ʓR G'/8ɓN"b?7!Op18UN$ҩ'NzaA*^ݻ+>W~ +;R8'?J00eeR&.Y)"\0  *H 0_1 0 UUS10U VeriSign, Inc.1705U .Class 3 Public Primary Certification Authority0 090521000000Z 190520235959Z01 0 UUS10U VeriSign, Inc.10U VeriSign Trust Network1;09U 2Terms of use at https://www.verisign.com/rpa (c)09100.U'VeriSign Class 3 Code Signing 2009-2 CA0"0  *H 0 g`IoV|f^ q-!ќPL"5; .Z|=;%X{ ξ'tag'MjaXy'M4+G Df$fO8TrfujIh8y 0,`Hת809:|@T/ܨR>+!\ P4.M^%Ԍn|)]1ZՌgX5+!`x^{`W ]A cT`C!00U00pU i0g0e `HE0V0(+https://www.verisign.com/cps0*+0https://www.verisign.com/rpa0U0m+ a0_][0Y0W0U image/gif0!00+kπjH,{.0%#http://logo.verisign.com/vslogo.gif0U%0++04+(0&0$+0http://ocsp.verisign.com01U*0(0&$" http://crl.verisign.com/pca3.crl0)U"0 010UClass3CA2048-1-550Uk&pȡ?-50  *H ݔAaix0Ɛ<~B$s/DrPU nQj71ܥ-OM2NgUejzd8xEv1z`³]fvYI8VAwX00fgymPSo0  *H 01 0 UUS10U VeriSign, Inc.10U VeriSign Trust Network1;09U 2Terms of use at https://www.verisign.com/rpa (c)09100.U'VeriSign Class 3 Code Signing 2009-2 CA0 100729000000Z 120808235959Z01 0 UUS10U Massachusetts10 UWoburn10U Monotype Imaging Inc.1>0<U 5Digital ID Class 3 - Microsoft Software Validation v210U Type Operations10UMonotype Imaging Inc.00  *H 0Di|U 25L3^ L*8ט@I "SOCʋVnH9c;$5}rGWyˊJ@p-5cįפ {uePd"}KXEMYLM00 U00U0DU=0;09753http://csc3-2009-2-crl.verisign.com/CSC3-2009-2.crl0DU =0;09 `HE0*0(+https://www.verisign.com/rpa0U% 0 +0u+i0g0$+0http://ocsp.verisign.com0?+03http://csc3-2009-2-aia.verisign.com/CSC3-2009-2.cer0U#0k&pȡ?-50 `HB0 +700  *H N"gA~™cjrb<8=_G_[KI  VD 5< D`E*oL;4gpZ9\Zl5|eKmIp=b۴A~}n"w6MZ S1+(RzkwD ]%,͊0>KyʦN $ 񺐶\<'Ms]fa:Aof.1f8LJ~tv^9$BViS(YN|HKIENDB`tdesktop-1.2.17/Telegram/Resources/icons/add_contact_phone@2x.png000066400000000000000000000013041326245125100247770ustar00rootroot00000000000000PNG  IHDR$$IDATXرjPRm%"t**tT R:K];tp"R!&5ߗ?ENAe2 VdY&ø1 #Ўmۄ`:2 Z\" ~||0Hq8˅Hqb1<,\\.C8gJDžA!7GGGEHLO:88ڙmjlrW rf& Uhe@DRyTt}}Q%JH$hD=<<,t]'D3K0!"zPߧT*PPduumKOOO ˲x; u7K4IE0 *]$͒$Iqܿͦe&sTU%˲ :;;#)L>!"ՕeYiSUl뗹rM0nf/l}Glj{`0h4=tyy$=>>qjZ@uw@899!]$QY##H(tss:7a!CTQy8<AIENDB`tdesktop-1.2.17/Telegram/Resources/icons/add_contact_user.png000066400000000000000000000004741326245125100243010ustar00rootroot00000000000000PNG  IHDRVΎWIDAT8=0F?W Z`+nR KP+ t} Ϙy4& ~L 9RRs[y43BcLW<$;Y])")G۶[SQYEQs7c$ʚǹޣ]eI)IJIi۾Ÿ8|GEx>p]ma@u smaHMH]f'IB^,BqD8~,55gY}Sm~qEpIENDB`tdesktop-1.2.17/Telegram/Resources/icons/add_contact_user@2x.png000066400000000000000000000011031326245125100246410ustar00rootroot00000000000000PNG  IHDR$$ IDATX햱@Å"" ֖ ϒ 1ZRB)+ E8׻Wg~0$̙3@x"$i`! Cp9G`0I4>]#IbiR~r2[\M#%|+3i2D/gpQw]пPNG!0 Se} e 9PUUjgN4MqZIE±B( 8#/TlaP$}ZOcdI ٶ}ߣu]aqHU cW/Im;ۚm|'9qi\RדtQ|(NLpXD^GVCRATBP@6qf(X,01L^VidYևAeYdfm( mQ/,=?(nf3+Zhw![f4>Bl7xyo"ؒsbf9-Oe&WIENDB`tdesktop-1.2.17/Telegram/Resources/icons/box_search.png000066400000000000000000000005361326245125100231140ustar00rootroot00000000000000PNG  IHDRa%IDAT8APDcr@ [uzx 9sDDHo?jVD3iͯFqz&2ftbR׶l6cp8^F#O$I"ʲTE ."e)r$7) C=s0(v3Swy.362IӴS MF)܁sdy|>eA0Hb߳Z\. O9'IruF؂6ۍpt:팯Z?; n&HIENDB`tdesktop-1.2.17/Telegram/Resources/icons/box_search@2x.png000066400000000000000000000013031326245125100234570ustar00rootroot00000000000000PNG  IHDR szzIDATXŖk2A7p(^ceubaNRVrյ6b6 \X$E"B&w=o s<ع %\H  ynL&V a>Gk4z Jp8DBBpovLhT vX !Eol)V(,Ai&jEt]f$H$JMt:;P4UXMD"p88Lhp8LB\.l6 j5z}OcSYiL^n`]S%N5Kƻ}>YƳ`0q$9 Ҹ1s[h<5KCe~&,sa֎O77Z0 .\.Gs-*"`(2E2GNU,1$I4 i8NFm6?!)c?;a|||d<1z͝N'W<Ab@BVU-o~qSsT$я+$ 5Ѷv":/mIENDB`tdesktop-1.2.17/Telegram/Resources/icons/bubble_tail.png000066400000000000000000000002341326245125100232360ustar00rootroot00000000000000PNG  IHDR /3cIDATm̱ 22H(H蛹v6@,B RJpZ9>"I1j jz"޻Vu:nS[6N{IENDB`tdesktop-1.2.17/Telegram/Resources/icons/bubble_tail@2x.png000066400000000000000000000003671326245125100236170ustar00rootroot00000000000000PNG  IHDR IDAT( @FPD K'UPGA|r9f_2̘Q!BKeH.H椦iO:q]WO*ZkmI "n&1d2|J=*U_都ڄPIENDB`tdesktop-1.2.17/Telegram/Resources/icons/call_answer@2x.png000066400000000000000000000022651326245125100236440ustar00rootroot00000000000000PNG  IHDR00W|IDATh;O2[""x#K(1ZhaX =x&J0(!A NK89߉O+fVއap0||z#Po~ S$-Yd000y?-IE4%K(U|>V5RʶjI~rܻ" "Y,L&Xש^BT h{{{%]y$o_.$ ,>fHc<<<|Cԏa @>G.C{{;^/xqlBG*}P$BT ^fvqzzǺl'$!#bllł3$IEM(^'x<V_cjFA0$IbE---}W$|ww8He`f^FZJ(DH"(r0L&ן~z"ݘ155!XVL&zft:t:ES h4v`E1MwV N  V Q 2088XZΏ@7b%,f[IENDB`tdesktop-1.2.17/Telegram/Resources/icons/call_arrow_in.png000066400000000000000000000003211326245125100236020ustar00rootroot00000000000000PNG  IHDRaIDAT8 DR(D P LNIvGF=ED(!q[!?U%|$ς^ _  <>k$(~)Ejr9MpvZsc.wx`o\Ӗ8IENDB`tdesktop-1.2.17/Telegram/Resources/icons/call_arrow_in@2x.png000066400000000000000000000004621326245125100241620ustar00rootroot00000000000000PNG  IHDR szzIDATX E ]aX x{YIɠ""P9%ZkZcYqhƕ5 5.8PJ  D D03I[ 1$5TU6MmK HH H { H$g}mDG797mJ}YkZ^,K+"y!;wכ_-] i(IENDB`tdesktop-1.2.17/Telegram/Resources/icons/call_arrow_out.png000066400000000000000000000003231326245125100240050ustar00rootroot00000000000000PNG  IHDRaIDAT8 EQY8r`%F(lBSB( |=}#P,kwoHD0B"RJ=k̬bz@D`&$h;9K1զg6SKЃw0%"ef%`z O`Z;hLIfYIENDB`tdesktop-1.2.17/Telegram/Resources/icons/call_arrow_out@2x.png000066400000000000000000000004521326245125100243620ustar00rootroot00000000000000PNG  IHDR szzIDATX 0E'6`GiE"cڄ % ق_.F;9A G'^6/s΁sRJR_m['Mb새4 Ea.zy&]1ּ "ʲ-r?/|&>k /_@@(@H@h@ @,8AGq1?N G𣀔2%( u]S@UU4 C 8"Wߊ7zġgIENDB`tdesktop-1.2.17/Telegram/Resources/icons/call_discard.png000066400000000000000000000006251326245125100234020ustar00rootroot00000000000000PNG  IHDR $\IDAT8T1P*u !i.  SXXX(^B$bb5QM;0gfo!(G@Ձi(J0 l!Hv59F(`Pd2s<4M !yHӜfb8rTU$1QUUy6M.ˀf:2JOShzuzNEQ8>>޽dYf&UD"ѷH&Lt\677YVu%2 $2BF" h* Q1EQ 5?p8qQ6?G;r\_;@v<}Uwww0X,<9 H?\\\dPU6UUu&VRDIZQ=眙p:|E*>} #ަz̷teˍ,-O $I<99!Ƿo(:WI[GFk`TjIENDB`tdesktop-1.2.17/Telegram/Resources/icons/call_rating.png000066400000000000000000000012321326245125100232500ustar00rootroot00000000000000PNG  IHDR szzaIDATX?k"QOP#6: Xh>lb*E Hjk |S)+>Lc6ax~7O@|~|$ z1,UUyjZ'L& Zp;PT?YGtDxeӓ=>>$WߥibBmIl6 0ta,{vXVfGi킽xcXT*s9Ec,Ϸ?r0`h4H$B,c@<'IVui^g^?Z-Ij5[^>Ox|>ݮW.h6t:e2|כL&9N_&SԩgT*E]y}}}3o//^ IENDB`tdesktop-1.2.17/Telegram/Resources/icons/call_rating@2x.png000066400000000000000000000027201326245125100236250ustar00rootroot00000000000000PNG  IHDR@@iqIDATxZM/3]xQ#Z&#Ċ%bl%"bnP 6hhV~Ę)tJN2s=׹/@B[m$ -6@Ih 5ZZZpFZ2W7ÁX[[I5 Fb%U_W+**0 *+**~~Bjj*,P<fffƻ 7` :Nt:I`}Fo4I-u׋YYYd,++ ^/GbnjB("PV+9ꊗ,}jZSUUEp<?Inff&b X,_222PRZFV$iZwvvZj5T*~ rrr>lrD9@}8&hZѱG ?lI pl6~<~ftA.G=~iX__z^Ά54N 4!jbX__5 E^gg'4GO˄aY0xݩ#z z淪WI\_PZZ{{{<XTTo},--S?P(xon>kkk-Z|9PP:<`m6(jvy\.t:NCśv'& n70d2ɒm2 n7L&IxHWH@[m$ -6@Ih ހUqyIENDB`tdesktop-1.2.17/Telegram/Resources/icons/call_rating_filled.png000066400000000000000000000010601326245125100245660ustar00rootroot00000000000000PNG  IHDR szzIDATX헱@^! ZiH i-"XHb:`tNM [殎w{93 |~}g^~-|ݮViV )H)v]i%f@B8Su*Ԃ xīN P5 L&Q(*DQwEAUUq4_5QUUTJq80l޴F. 盶^aZ7D(|GQK.2jfmۘeYYm6ix8QӴלY1 Ca,\S9qc8!s./]`Y7`0pI$rR$a!ԟ)iZ'a\Ң(`\:, ({zf 55f`ъ(>AZZ '''dv <|>Z1uFT6EFC t{{ HH$no+E(+_)F[{Ez ccc׫`?R4b&xz||Dee&r]ov<x\6x<XV$I6Ip~~^ 0 mckk 0 %I,s቉ jp8`aa8F  ^__\.CTR ^^^}"zONNMv A777h0Ȭoooi$[www8==MvjFiUhZv`0zvm 777RЮJnV41Njt:Vt~ q uׯ$IP([|(/^Ex<ϓ! LtvvojffFFڲu7іr;rS(6⛲l]j @TՁ!WDZa, ][X$D"h6[5DdO dF}kWo}JRr:z\.LR]}v+  8<<I)U!\"O1 Vt-QQn^)Uɲ\v;g}qjͦ^v`2ٌ$в,a<ϙe%M$N&t:՟d۶y<)h4;t:1MSvǬb<S)Ţ(8ʳ~bB)%]}΋!$)GJ͗$`0x}q8Bp.^$z_/.S@WIENDB`tdesktop-1.2.17/Telegram/Resources/icons/call_record_active@2x.png000066400000000000000000000014401326245125100251500ustar00rootroot00000000000000PNG  IHDR00WIDAThK*QǏr& *݈ťohUIПЦ 7.Z(*)4A(B0-{N7:^op2s3?}q6FCiL$ၫYZZ2M.ߑqS"'''M(EA `r9],MKY* nP$ tJA4eR),//3hIJ~㭭- # VVV,^J7 9;"ܥR 'quueuvvf凘l,˸5~~~6|\__v( aӷh4TUf_\[b$3T#&:XBk5yñ +vww-xJP~L\UUM0:0$x2ɴ§N>Ue{l_`r߯,XIENDB`tdesktop-1.2.17/Telegram/Resources/icons/call_record_muted.png000066400000000000000000000007131326245125100244430ustar00rootroot00000000000000PNG  IHDRw=IDATH=@'jp @[M,9*GEDz+V.K~{oB@ KN,"!q4MRU4)c ?-B6 H}S7\. .܉/#gg1FMU*H wqo1iIӂΜ̄CDvqvȲLJjYEe봽M$BZP@@8??ԔocCU/ekvI0pssh6JXr9yu DV 4-yh6eٛ=iZh E )DOI zH @"UDꀸA F|~~FBY=9mnnzid2-'=4 +ӓrF#9|♗cز¿_]]9yA\?XXXpa"jooϹŦib||5><ԝM@Dwr|| EQ!~Т%Ȳj*KKKi!'eYצkkk`yrcX__E(066CO1vZ-zVqDU<<<W@DETY`Ắccc,CUUE|mX^^ 6wff&"}@LDl6v:!8 r? `#8r] يAӎ_@1f[˷IENDB`tdesktop-1.2.17/Telegram/Resources/icons/call_shadow_left.png000066400000000000000000000001441326245125100242640ustar00rootroot00000000000000PNG  IHDRĮn+IDATm1 J_ӉL dDIqc/]FIENDB`tdesktop-1.2.17/Telegram/Resources/icons/call_shadow_left@2x.png000066400000000000000000000001751326245125100246420ustar00rootroot00000000000000PNG  IHDR0_!DIDAT͍;@ni%͐@ IF0 mIBҖ3;{Iny~?~  IENDB`tdesktop-1.2.17/Telegram/Resources/icons/call_shadow_top.png000066400000000000000000000001471326245125100241370ustar00rootroot00000000000000PNG  IHDR;̗.IDATmA 0׋R)pP~̡vn iIENDB`tdesktop-1.2.17/Telegram/Resources/icons/call_shadow_top@2x.png000066400000000000000000000001771326245125100245140ustar00rootroot00000000000000PNG  IHDR0;FIDAT1 K?Yň8A(=,E2DU:%!Twbb 0inIENDB`tdesktop-1.2.17/Telegram/Resources/icons/call_shadow_top_left.png000066400000000000000000000004471326245125100251540ustar00rootroot00000000000000PNG  IHDRw=IDATH07qd-ha1ol9gꊗ(y,]y(jgP|<8zgXtlYb k7Q.x HU1)B~ ĸZ BoT}H#=`tjd[M10{NQ&ΠUHdEqU6YQmkAu7bm_@o G0UH7qeݪۀ;kfA@IENDB`tdesktop-1.2.17/Telegram/Resources/icons/call_shadow_top_left@2x.png000066400000000000000000000010571326245125100255240ustar00rootroot00000000000000PNG  IHDR00WIDAThᎃ0 _RIܤTR5Mڶ1boX獛n6m& ;W}@d]v\ M>eLFiItR%dۣz^VE}(=oPG g@! 92̫0A<2=K0D#(PE*RDULϟyvdh`IhMmD@I< `w3J9%܎㸜G[J`hD+m VG\XEAT"xBf9Il0 g)TU} J^ 5 )4( ^AX%($F}y|DVal%cvse y(ChxcWSƒLR3+߉1)  +T/_hXĢ^ BɭU-UTtʦUƤRr# *"Xk0;y"b<T{}+~ IENDB`tdesktop-1.2.17/Telegram/Resources/icons/color_slider_arrow.png000066400000000000000000000002301326245125100246600ustar00rootroot00000000000000PNG  IHDRRW_IDAT C!m0L0 (/W(Q-IH݉1dI轳bI)]9p掠FJ ;{o>1͌1_oCajUX)IENDB`tdesktop-1.2.17/Telegram/Resources/icons/color_slider_arrow@2x.png000066400000000000000000000003011326245125100252310ustar00rootroot00000000000000PNG  IHDRH-IDAT(A ! Ex4^+ 8L[y|I"D{N)afpwDk _i;K)9s]ZtYVl#D3RΒaBؿ'>`qIENDB`tdesktop-1.2.17/Telegram/Resources/icons/color_slider_arrow_vertical.png000066400000000000000000000002571326245125100265620ustar00rootroot00000000000000PNG  IHDRRWvIDATM !ާAl0L0"{ @P:%ѹpc& 3SAf&@k-$ZkssrΡ֊$cd11F[wklSJ{Rz_QJ 9$cX"sIENDB`tdesktop-1.2.17/Telegram/Resources/icons/color_slider_arrow_vertical@2x.png000066400000000000000000000003541326245125100271320ustar00rootroot00000000000000PNG  IHDRH-IDAT(0 +J!iKjJn"(oE!#FD)g`Gfx@ IJJ؃zn?gUoݱͽsIENDB`tdesktop-1.2.17/Telegram/Resources/icons/default_checkbox_check@2x.png000066400000000000000000000003771326245125100260030ustar00rootroot00000000000000PNG  IHDR^]IDATH1 Ps4&xCoc,!1QS!f{(g19g@ÎkXZk~c6J ,!`6: A"BJ)5t;tEP&`& P&A PkhJ ιQ7(ľDās/ /oyʬIENDB`tdesktop-1.2.17/Telegram/Resources/icons/dialogs_calendar.png000066400000000000000000000002551326245125100242500ustar00rootroot00000000000000PNG  IHDR+vtIDAT8 @uJ. M+;C o!(qT?,WUP$=Dj1"rrR31|%3 3Vۖ_%ܖp3+7W@K?<.pmIENDB`tdesktop-1.2.17/Telegram/Resources/icons/dialogs_calendar@2x.png000066400000000000000000000003771326245125100246270ustar00rootroot00000000000000PNG  IHDR.,q<IDATXQ !m0hpZ>DD9mnYBM q4DrWmCMWIU݋Ϣ̗hTDD~޴ynJV|xW-zNm+hBM q4!fxW"ٶ]q3Cx/k^"fp_8G^5IENDB`tdesktop-1.2.17/Telegram/Resources/icons/dialogs_cancel_search.png000066400000000000000000000003051326245125100252450ustar00rootroot00000000000000PNG  IHDRVΎWIDAT8K!C^=04O @}/ ÝoPHvN~gp=*3f|Ҽ?8 4[lTt((* !V5476P:A~wxۼo}_dgVs4p)IENDB`tdesktop-1.2.17/Telegram/Resources/icons/dialogs_cancel_search@2x.png000066400000000000000000000004211326245125100256160ustar00rootroot00000000000000PNG  IHDR$$IDATXWA0IeKKZTK*8@l:T@_0;X`?;ILQQ2RCz*E#bq sEr]O5<1̼d<]$溠힚t(Rjڵ"VUڷ[VŵUѪAk2d8CCU#_a|>vXadxBt7=&IENDB`tdesktop-1.2.17/Telegram/Resources/icons/dialogs_channel.png000066400000000000000000000003751326245125100241120ustar00rootroot00000000000000PNG  IHDR Kpl_IDAT( @E فX`!6 \5h&/DL6#\Nr)hYU}?1eY""'yHڶ?m(}e/5uR A`YLӄ0 O*EQ$!iyDZ#Ru] BZke)%8|aW}֞IENDB`tdesktop-1.2.17/Telegram/Resources/icons/dialogs_channel@2x.png000066400000000000000000000006611326245125100244620ustar00rootroot00000000000000PNG  IHDR}\xIDATH@OlB? c y %*`%BJ$"B9[F5&ޙt: aRAiZ+ trd,Qݙj8Lc}GR b2]WT* 4 il,~/=ɻi`6efNtbPx#3X,%d~*(B>Bu]x4l6_RbjwF@EQh& D@YץZrs808K3rpѲ,~1/IENDB`tdesktop-1.2.17/Telegram/Resources/icons/dialogs_chat.png000066400000000000000000000004111326245125100234100ustar00rootroot00000000000000PNG  IHDR v 9IDAT( 0 ENWB,RD 4,A ԎI%|Ʌl+z(̰Bk},4M;"{1";r޹8fkZk!"!"I™YeŁE1HqqDS^1f3)c4NK@v}Iݎ\}ZV)r`0$I>m~Rz#x<./u8z)n7T3haY֟*hZk0*>(X~:/~R ƓIENDB`tdesktop-1.2.17/Telegram/Resources/icons/dialogs_feed.png000066400000000000000000000005101326245125100233740ustar00rootroot00000000000000PNG  IHDR w&IDATmAg%JEpg $=.Cmiu]xGu4Mh4a8l6EUUEH8r sԶ-UU! 8Hb6p^_u]ɶmж-0 *$L7IENDB`tdesktop-1.2.17/Telegram/Resources/icons/dialogs_feed@2x.png000066400000000000000000000011621326245125100237520ustar00rootroot00000000000000PNG  IHDRĴl;9IDAT8;k*Q&XIJ ci#RKRK@|UZN!JDRQ+mfVF7r91a5{9{3|||w"2i6jjE9p"5J<A!g)x}}Qס/F I~>//A3wwdłFqf/F(fxx vT .d6 Ʉ˶*+0F>rr:t:W?x |!eeSj!]eDy1RJ߰N<88eA4n JU U+~ϸ wIENDB`tdesktop-1.2.17/Telegram/Resources/icons/dialogs_pinned@2x.png000066400000000000000000000010351326245125100243230ustar00rootroot00000000000000PNG  IHDR((mIDATX?@ $.V4/Xy',6hl LOk3OKq? x4>i<j5}d2l6|znw opFy`պq"ř\.Hq663)<83?H,(D"FH&8<!0&ZN`8' +ۚKSe xW^c,Q8>07͙ !^"i=YR䜣9RJ۾3m۬?l!!˲4M6z=뺘$ e8⺮_y8bYxu]cX|?*d Z۽ym4W?VçcuIENDB`tdesktop-1.2.17/Telegram/Resources/icons/dialogs_search_from@2x.png000066400000000000000000000012241326245125100253360ustar00rootroot00000000000000PNG  IHDR.,q<[IDATX9@Eo78A H%X ls/x#DF!@d 6cb&WGN[GU?BlxKl,q͆x4 E,C$ADfZRzMZH\GGT*UUJkJ Do5$I"`P&wR8>jp'g5za}7β,B X}:㊢`:>o6AQs _.Z5M\.Cنa("$05 HP5n?ci\.ryWzXbH%CE$ATB:F8L&Z-4 L&*95N't:|>S=e?48X @~. p<X,01l gH$H&HRpwAE~h4BFAv{*Vqy C+TU%~(2B5o<'N=IB{J)fF)=OG1抽71Ƈ;=19svrΨ{GkK=k-nsN>"IsNZk_5]qhC)=^ODZ+c Z !9"IENDB`tdesktop-1.2.17/Telegram/Resources/icons/dialogs_sending@2x.png000066400000000000000000000006601326245125100245000ustar00rootroot00000000000000PNG  IHDRĴl;wIDAT8@%$U$u(RXDu_Ѽƺ5}]Ո$9㮽JKdd˟dV` nvrcp:x*GJeY3ʲd).χx*KQ,+^v(YvȲQ7M3[4 A+҂)b滖ضD11mlq qFAǰ(#",<{H֭m1fD0PTU;iܦBD.[D*@D}뺞k}q %I2[$ImiimNmi ÐOZkðiz~fj^88\.n[KIENDB`tdesktop-1.2.17/Telegram/Resources/icons/dialogs_sent.png000066400000000000000000000002211326245125100234410ustar00rootroot00000000000000PNG  IHDR ,XIDAT 0D(P]79E7K  (x*DDpZ($ٶ%en &F_s7g]y>ZO2IENDB`tdesktop-1.2.17/Telegram/Resources/icons/dialogs_sent@2x.png000066400000000000000000000004051326245125100240170ustar00rootroot00000000000000PNG  IHDRIDAT8ӱ .[a!Ă)l \˜SO˟XW_΁E/0 yZkh<58M]J),YqYnsC\0p&6n=)X$ _>4")GtQ$?#MӤm˪$87lIENDB`tdesktop-1.2.17/Telegram/Resources/icons/dialogs_unlock.png000066400000000000000000000004371326245125100237740ustar00rootroot00000000000000PNG  IHDRbKv3IDAT8Փ[ E+#Ue\+gpcɴI}>["^5s"9gQU镵#=vWp1#9RJyhi6syhk c7Z Afp'w?PZ?M]Uk}r4~P_rT_{⠞),]!J)֩Bd272k1IENDB`tdesktop-1.2.17/Telegram/Resources/icons/dialogs_unlock@2x.png000066400000000000000000000007021326245125100243410ustar00rootroot00000000000000PNG  IHDR*( hPIDATX;0[}r )vNF2. ѹ-,Y;ͯ`@|=[ (99TpYECJIeY,IJX̲J}OY=VTJI]Kt]w`QUU k-{BdUUEZI<|RN/{I2w6M3jfRL4EsBoF18nem(DrXB*U46^Q9cmp9vBƵ"18Axձ9mf "leK*YEcf"br^EfT7䜓11;C? lWmD\?IENDB`tdesktop-1.2.17/Telegram/Resources/icons/dialogs_verified_check.png000066400000000000000000000002641326245125100254310ustar00rootroot00000000000000PNG  IHDRxU{IDAT] !D r5Oh`k jXEԀ Ml$!垝78PJ-q Àa"MӗyY WU4 mHDQu] !˲KI)!q;$yD9q W-?um4YJ)Fn_B},M?1KIENDB`tdesktop-1.2.17/Telegram/Resources/icons/dialogs_verified_star.png000066400000000000000000000005261326245125100253260ustar00rootroot00000000000000PNG  IHDRH-IDAT(@x@C0U#&m 6ZXh:Bdrsvvg8g!"TDQ'>>9^kH)=%j`Y8NneY"Igm 0TnT4z%Y)96b?;\}a)i@`TUESks@AUqxpùǹyp!"z=lVs&R.P$|{{ (n@EQP$"1buU*dY?<;jjJ5`ZPxPS;4V  ޳]u}}D ŰX,yaK9gggoo5кLDu]jd)꺎*{||\.|s?aRi:ǡm`i_,`]^^1Ԁd3(4ǃZ-b(B N8R?>>}`, ,,pzz '''[tr\ׅL&A6ua4eY!ug<ϣ DQqERBsܶ큂 `^ǫu:D5iIENDB`tdesktop-1.2.17/Telegram/Resources/icons/dropdown_submenu_arrow.png000066400000000000000000000001671326245125100256030ustar00rootroot00000000000000PNG  IHDR/e>IDATe 1 p-q<ۥH#Ps23C)@w"ҳx Z>fbNt||  WWWxyyQ}||  hWy3HDz$ɵ5hqq$)Ir||z2D"922 777$Sd$ю NYIޮud].nGI#FMhIX,֗3;;l&I}>EQ`P[ES$%tpppT8V@#"}>ϕ-eY}>'&&jj)uk?QIENDB`tdesktop-1.2.17/Telegram/Resources/icons/emoji_activity@2x.png000066400000000000000000000032311326245125100243630ustar00rootroot00000000000000PNG  IHDR44x`IDAThZk]? 4ZԴd,`Ս QȦ" tWP [7A6BUMAW)qGœo;wf2fq= ` +O݄ ;v 8˲kkkP,A#xm9`;ߏX.,2΢oG.|>&Ieٴ%dYd2>t:qjj e!JuNyUAoo/ܽ{~j}Pd2 lAbkkk,q ɓo>U\.H޿o*?So  ʊM8::jb:V\YY`0k JDr866fCa.%I¡ x:[euQh4p``uA<>w s||&(P<"#f,--YsaOōF<yAׯ_'HܹQ0 nnYЅ k׮o&oܸaNPGGV*$ >|˂ljH$BW*0.y`qq9qN[c YrQ~M[ѣ}}'"n<σn~K>|I({#G(f)IǏDٞ={(5~^ZZR{n:8#oߪ vvRQSw^GS˲],UDQ$n-JMرժ2e:mF_~USW&g!fS a+s$epf\.ߩ T{~F}F٧rP~1AF+Ν; \.kQj"S3ү5Q:PzF'x>N KbApPI7xT Gapyy(bSGΝ×/_eTJG?蓼vVÞ˂''' ?~v^ovK<47$ݻud\ҌyөzDgdYmϞ=+-[ S= nuۭq*NT¾>#7( *lnn-hꟈ fO$xBN>M3Eь97oި&X 0z^thnF׋p/] <|^ZkNgff~ɕdܺuk+Yk@| J~СCUoA$888h9.t:yqݽa$ DQASHoߍ} ?->2cIENDB`tdesktop-1.2.17/Telegram/Resources/icons/emoji_delete.png000066400000000000000000000003231326245125100234160ustar00rootroot00000000000000PNG  IHDRH-IDAT(A 0 ^Im}!ӤR`f /z|ܶZ$ofVܽd ~j֐Z9N$z1cLFG" r9T%"bGuwzH.'3N$d.;!:mIENDB`tdesktop-1.2.17/Telegram/Resources/icons/emoji_delete@2x.png000066400000000000000000000004201326245125100237660ustar00rootroot00000000000000PNG  IHDRr ߔIDATH= `q.W.. ׉J V[R'@D zXb7sDB̻B$"bi3̌+/"jVSJx/ ]bR:ha9imPCK)(\Amtc`G=|[ҘZk΃{ZcVo=Y5IENDB`tdesktop-1.2.17/Telegram/Resources/icons/emoji_faved.png000066400000000000000000000013031326245125100232400ustar00rootroot00000000000000PNG  IHDRJLIDATHVj*A=>^  i4!F,iWi"XXلTn,U!* 5on@^xn3s;gn"vvB/oZ-hZ?b$zVBHķo뤔BC)ZZHLL$Z:l _:4 hWWW r<. $v1`Y)9^__vh]Rl6nGv;qPalJr#4q{{ xzz|>bx<3L nTTdtD'OːB@J4ȝĨ|@Q0 ZhZi 55hzAï_u:,..\.sݰq\.8N'lll LOO Ӿ>޽ y7{zz0(Cwh,--ZiHRnԹ(333f3aD "*--IlX`mm akkr` \)))T*ih4!33Hyy9c =˲)5###~Jx֭ǐ!\.p3JvxxzR0L~zrrfѣGcDĮІ`__1ڊ'''Z ɍ( 4͂5~:XIkjj8!˲!qttTЌh| bb&!!yX鄨錙!Kp'$$hիDp8"n'ݼySGDbbbݻ'}DJJJ8pu-|Bď?!;r]T/r%NPba7n`jjjiBLIjZx87, Ǐ!+++h;Bgx(zBgWuuuJ&1ۍFQh\<޽{7~]2!NG6&i?SXX{C* L>,(s9ú:( ߾}K\{Y "]]]Ȳ`q{{{&C4|R"$vvvqEe(hxMX;Eռ):ܹ#ͤpv )?BF1hED'N`0LLͯz7n|f9ׇPTT ÀbH(!ɀa}6X,ZGl(^?nn)IENDB`tdesktop-1.2.17/Telegram/Resources/icons/emoji_food.png000066400000000000000000000013711326245125100231070ustar00rootroot00000000000000PNG  IHDRJLIDATHŕ?Hr}ǿA.*CNM QDn AN-"I A&5%4(FD }|}zyrι9{! B!(X,*/ݞd2I;YXXhTUGGGljD"gqx||ddda ن@( zI$D.c{{靋 RU͊!^^^񼹹$QTs~d2FGG1Mp8L*j +R l6Ϡ)nnn-λ-t:n5֠beCp8m`TOunooA8A6 Z%b$IZIV$4K*5N Kr5R4775h~~^f[lpuuu3E~uA.`ee_C; XZZ r:\. H 0Hά|ZrܘƓl6 hh44M&&&8==~-B9HDJ`2$ZPɤ488(өBL&|>/.0T*evWIzZAZ]$IENDB`tdesktop-1.2.17/Telegram/Resources/icons/emoji_food@2x.png000066400000000000000000000031051326245125100234560ustar00rootroot00000000000000PNG  IHDR44x IDATh_HSoǟ-E+צ6ׅhR&fQ^tJ IoD.b2 %&6-m#q``n]%wgv s>s"b]V!;;&''d2fcΝ;]]]8??4 T(|c 7oxR-PJJ ©)@B FшXWW###k׮( GGG9 6 _Rk¯:@w% `0`BBo||<rpkdd3*͛7 Y^9oǹ0 zhGvcII _mm-ZSJ!11t:hZ`Y fo߂냯_ ĵ]#?r t}.766޽{̹x0++guu_PP dfvhXBppL9.]v㿼gΜ,` C~ӧOnooMMMz*'>x1-- 333QGp}}=$E'zxᬂ_|tT*KPϟs 8A5 omm Glhh؏QWW񩩩Tp;wjRh4b[[MTBs o뱢R)fdd`ZZ,rA3V/u-WYYI&upBPPnn.Qˏ?O9%oTڱcLjZ~E9rdߎ5bf "0L%f!!:~8aK%줤0/\N;;;T_*$lRfYuIڢRVWW ԩSa%^999Hv ,*))!ln j%le,KZ-ajx|RSS:n7&&& J$$:DKLNNN9DQڕ+WhQӍ7G=)//'|>LOOՏ~S*rzo޿ϧ_pA76605550x;ĉw___ԁ?~L\XXs@ G}}}`mI&/_Y8Je"b;:vv;VUUE &)) ߽{Gx/^( χCYYdff"jiM@D(***hooNJk>|KFZ^¸81-n4448߿ɓ'(N_>77?|1Պ/_Y VFh4Rtf A"˲PZZ EEER@"ဩ)pK&'î Y3xYIENDB`tdesktop-1.2.17/Telegram/Resources/icons/emoji_nature.png000066400000000000000000000016521326245125100234600ustar00rootroot00000000000000PNG  IHDRJLqIDATHVKHrk]G#VE$A4Ӡq#4,fI6i$(ԨD"!  ",U؃z'/ݰ'|Z{o}B'D[!N# aii :G" ߏwx^a> 0G, z=m6{{{i4966Ƌ=OOOllld4ISl6)m6+**N\[[z$ BD822BAF$DIR R,"+ĿcL&IE!R3r9Y O\^^咭t\t$x> d]AQ900PiV-=#E.uwwCRLV+Ab),$Fʚ`0(;fYHEQ$IVWWnnnNj$}>_Fayy9pssoooa t)󹺺*qR)~^XӉ`0CCiReeeP(P*҈d2 `$p8Fϳwၹ\$i6]l6KESSS~JNLLHsB^ .//quul6a@4C8F" j5 Z ف hmmE[[[ [ u$jE{{;t:dp~~@ c\__G+[ƻ~^XIENDB`tdesktop-1.2.17/Telegram/Resources/icons/emoji_nature@2x.png000066400000000000000000000036721326245125100240360ustar00rootroot00000000000000PNG  IHDR44xIDAThZmH]ښHZmGP ʄ ؗ!IB~P" 3~'GQJM""H\_fZl4e2t#\joDؽs~}LDKw_B;8BQʢT86mDJmmmt5r ;vxbFX,X|@jTTT@ o߾ő#G$}[nECCd,Ʉe˖K|F2L466b˖-^ 3i3,O1j6OW\g.]DIIIf"___R7~2͔IeeeU .Љ'D>yZ={3 n߾tB֭[+VRT<55U0J%>} c}eZy:޽{єFYY,(J%ߟÙy"h+G2u{3ߑCYǶ`[[[Y{>Fɬ.$fڼy33$j'f1Z(,,/{B(,,aag_|al߾=t:άg׮] Fx`` #D9L&QWWiii<-_EEEHOO:󑑑(**hh4mVmׯ_3 v  {nI y4cׯ_Rdel$?oܸQ^ ͮ 55{K 4^kgBV3RddG>x EEEQkk+T;!MNN2Ç8jii!V̉u#SThoon7BBBFǛ#ŋlէ\ אNþ}P__QfwhZx011lهBHHx 8WռfbddχNICz{{"Z~~~DTTB@NNq Yt)m&mT*իkIjz&ܹ(;8w/pww!b̙38lZEii)222<^|8˃RأGx,J*ʳ5F "66'!qi0l6v;d-hݺu(((e1l6ގZb]88LMM2 ^m+W"..ΫV\\,***l_2&^a/@cc'TUU%qܹs %''c``@tUUU q svqe%CףNpz NwgI"qLORgܽ{WnM&~m~~~D>4TYY):?::J---I===#NrHR`` `xJNN$^9UUUT^^.gDbL޽{&?F^^O-rqOXD:4-<Fee%3OɓO q%&&RLL Z(00Z-jr:dbP__yٳgߡ‚]'_B;8B;Z3~_IENDB`tdesktop-1.2.17/Telegram/Resources/icons/emoji_objects.png000066400000000000000000000012231326245125100236050ustar00rootroot00000000000000PNG  IHDRJLZIDATH͕j*A^5vV<$1`EE҈F )"6ڨna(&V.kN 7g7;MD_?QEQ1MS8#8#@LӔh4/*X,TU:4Mhxź.8v1FMӾ2M+P,}g[,i ]- "V+^n lu53lKl6 p8Tf3bj5>T !Jv$0@oooaOu]\{6 Dp]W 48??ѓ$~dBnR ۶@t:w|L&AlRyyy^ l6F5`+_#t:+?͍'''XE2DDxzz`^sqqg(˼מrܡ>;܈۳lV Bp(] (vei^Z uED$GGG"";`|sBAD8;;c:@)Jk[ڿso:B|+UIENDB`tdesktop-1.2.17/Telegram/Resources/icons/emoji_objects@2x.png000066400000000000000000000025511326245125100241640ustar00rootroot00000000000000PNG  IHDR44x0IDAThK2_ǟɰ( MEI!IX@Qim Tv"TԢe-JZj#t7EETdϻxQ~uF􅳘yL]]2Cw@E^Ǐa@^lqee%na8<<  Q @ BT(8<<qWWW?@<cb6QV3Ԅf9f xВ{^KyŝH`(":,))I& ].J$a£].$yAڒa &8*++CGlhhHǸ9mkk#ꊩ{ҺBkooO8gꚲs(JF~)1-JDsH$"GX(&a Mv<F#vqr`4Ŵyxb4 ZmhĬu>~Nղ9N"xKKKL*$l=*JT(w'VWWnj522BnooOOOǵUThǃ**n#ža  &} "K`0|ٟE+D"222hfK'Ɉp|Cb vӨLdJhKxxx`:;Q{{{4^ր677i񻺺טG3˿LV+ j&U_@Hb9NDıl6-i2M@X+XG777X^^j]:DĩDx$+++ө)= V|zz" {~~{h6qaapDTn=& kMz<VvX>tvvKKK9Z>"+U8>VVVǽƅ8tLA'dha Be='3R4X[[$[r@_8 IM333i{lѾ鈕NObbvb139>>FX}pvvc xqqsssA~~]@] E5ٴIENDB`tdesktop-1.2.17/Telegram/Resources/icons/emoji_people.png000066400000000000000000000014471326245125100234500ustar00rootroot00000000000000PNG  IHDRJLIDATHVK^=y|iBZ"$Dhv(8v⨂+Rth-DE!jԡ,ԥP{S{~=s?9# ?!2V(Jacc4 hNV 8==E^2Ӳ,C~ᐖe1)dv=|>O0*EQ4 |[vd/f9 HJ,vRʲJB f_ % z=dPT, $^ǹυ*IT*R$YVG Nl6$EaXҒos:fX(&I&IR`#d.$ooo}I؇r9ᡇ~t: 8;;$`zzڇG"@oTIW 4MpwwׇqUU%I}(nHt:h4 :VWW133XYYΎWbwuV @4m$Ie4 p87BED"Iϣ^ömhQPj5{SSS+ku^۶􄅅cvv˲Ȳd25|||0 8IpX,EE Dh4\ B9m 1cy/FE*&LmfB\rfп[J*m[tIENDB`tdesktop-1.2.17/Telegram/Resources/icons/emoji_people@2x.png000066400000000000000000000032541326245125100240200ustar00rootroot00000000000000PNG  IHDR44xsIDAThZMhOhh#4 66ֈӋT(\<驘^D= Z Jb)`$1V/%R )f!}$?dafμY D#t.ZT(p)Zz4 T*@DHRhB,--w ,,6NNN"˲:_|e)}xQ.1'.:R)5%Ξ=+?w B(J&ڄaPᎺ Nzptt$~ttcz^,WDLDB… '`~~Aax477VV S8b q BWWd2XYYژL&*F>]Ql}ry~yСCގxaAݻwV.Sl} ݜZ,CDą({9jvqoo- zs p<&,l6T*J%l6%ynW}|H$3 f19CǃH7H$#d2|)X<~e2OܸqSh"x5A{Tu?GV,u| FSIKTax-,KԻ\.p8J% ˲|<o޼t:-t͛7`0O.@T#hBRTdrj$s;R:n^/^@ JRąd 2 uJbB95 vkviv#Seqbbk 4UQ;TL ò,dt:jEff66H**Iu]VT@j$UUMoh<Ah08h.Gi{It:J$@z$yA$)B!S:5nQ3|RI:x$^y$IBrI5jEj&p8,4ol6oGE93W(%uWr'Oʋnt~םac>Jn->IENDB`tdesktop-1.2.17/Telegram/Resources/icons/emoji_recent@2x.png000066400000000000000000000021631326245125100240120ustar00rootroot00000000000000PNG  IHDR44x:IDAThZK2]=<耶4~JVD.l#?ik"N0ʢEWFFA]Lw03xoq;s2 Xwǯ? f3ְt:aanFzZ 7773 "<R)jZVRy<Z#yi06"`0 r\o8J$6"GߧD"AeÁcϟprrJzFn lp:zX{aҧ|>5ͱ5[((H$BBa,;t9ONGQhXh4:FX,*;S s8c[&LmDLFQN$qTVqfUU-<$S*q2R zY0$AH Su~O<7tyy)!,kZ ~T*I4\]]3xMnd4ChTzJ#bX@t:ϐbv-!2CpXnbh7$Z͐VPVEƆ$}vv LX\.IT*1er rC 3#Y! L04??/I1er rCsssta$ !{r r#q!dDQ" r0i&aX$yEQ>i - L&gZz!-U+5z}}ޞsr BBM\N{{xxWVVTk 8̧>BG):9Ur("J~R>sfEql^Za6-T*}!fKp&4|qEo6+r1+ FmCm\.3۬D3ڬg4M|fvuzzr<:z::Z^^x 8:88ّd<7t$i0x|>8fv)L>O&vaٌUlnnbiiitjz#qwwsC_wwǿJbwYIENDB`tdesktop-1.2.17/Telegram/Resources/icons/emoji_settings.png000066400000000000000000000014621326245125100240210ustar00rootroot00000000000000PNG  IHDRJLIDATHVNZ]JC $ >E0(0UULyO4QAR`3q LPZuNkM;9Igڧ  ~ VVV CM&qzziV.``FKKKT*( uT*LӜ;2Q.yt:0MX 11X iboo<lG˜EW@UUxU???dl6eMyPUe4*GNS<=ޖlnn>&I6 iV@KUUU|||LμTOOO݅ix}}$I= L"ϣ|%Qeb^HuKu=>>ΌEMr'푝̍_H4 !.]ץbi_/|("}JIENDB`tdesktop-1.2.17/Telegram/Resources/icons/emoji_settings@2x.png000066400000000000000000000030631326245125100243720ustar00rootroot00000000000000PNG  IHDR44xIDAThZO_}BL$"baA ,Q& ƥ!`k"i`P#-_YL6;ltaZB$&.d2nL{Ő1-fsw+.B_dmOEQCQ2::x~89Z<w,8y^R(lǪkN۱~ :tzyyx^(ZG8XTUU}V__' i|길4?ӂDQ$,UU,$ Oss۷o\uXQ͛7,dvv|>S\ +GMM άPD'N"ի\A?JSSS\zF|q˥90'''w'(J<33cfggmawuu(leeo*3"zzzL/'OdNWX]]޽{G%hhhJrΝ,Y1<L?|2Na$Iyy$IatvvZ{ūXP&(͢Rq*y[x<ƂΜ9C%_ZZ(/,,)‚)̃byy5TUU_RFkѨi1EF@?~83 ԄL&$011fJ-A$|>|>H\.G09|׉LEE$dMSQf5wܡ˙_zxI +/IS$/k>~؈F S~|^^W㻽M477Syuޱ\.{jb1}Vs O֍%;F\̢q|n궋f(d2nQ(PVV̝JzGbmm hlgYHA $I*/LLMMM֍ ;vLbLzt1IŭXy5A0M#"`0h,ʕ+\.m*qqiZ33ҥKƂvHT BBT HıC]] BA[[eN V`zW?~<{rq^x?Vva~~0??P(d[wr~}AQ06c> &@lll0qtww37zC̗/_"p_cEQ_pus{y=ois*nܸ1>+\҃zsׂF0I.85ʉEbG֯&۶bD"a9.H8"h}C122EQN]:TU(u떣r]<쎎kIENDB`tdesktop-1.2.17/Telegram/Resources/icons/emoji_switch.png000066400000000000000000000002211326245125100234520ustar00rootroot00000000000000PNG  IHDR _XIDATQ C"&s1  y!G@5UH'^r+K)tΣ~^הڲ ϝVSIENDB`tdesktop-1.2.17/Telegram/Resources/icons/emoji_switch@2x.png000066400000000000000000000003251326245125100240310ustar00rootroot00000000000000PNG  IHDR} IDAT8 ! D?%!a,ݶGCn5*޹JU^;VI)qAr @8 ǠrA!߿~6V7`>DsN 9 1/7%*'5kV>IENDB`tdesktop-1.2.17/Telegram/Resources/icons/emoji_symbols.png000066400000000000000000000011241326245125100236440ustar00rootroot00000000000000PNG  IHDRJLIDATH="AeE1V5300t@H ELT@ L c10#ٝqgϻ *~jh~~?^YYOz{{!Lnoo1L^X,fh#~vvcFo,03n1SPP@ǵsttXD:<;+444%h2 XNATىj5FTcO^; ^OOBBvCr)7D( nH 4=??_C0~zzJ2PJRX,H{|PbRFU$JC~06b.ˡmHs* 5AExV[d2az6B9(S<>nV+U.5T*Ş ]2WAvh4 QF&3鿻e˹DޑH$ӴLV@|~~2,l6vL&S:z6H$YXX`:՘B4dkk b1fzfVWWf 81Cvd Jsc|nbh BN'< BUUc>Hv㻼diD$ ͣT* BRhc?Eayy@`0@"j97_$rAqqq6z7~0TYYݻ'n0 vZkppP㇅/3xۣ#<'''Vfossz,樭eeeH$£Gztww_R)Ibr:xl6=~Xra|>|o߾D|9 ʭ\봏ba̓Nj:Ji:h\N\[0tY"`!͇b~۵q]uAp(4Kh#O!FSSP[[JrH$BD"x<ǏX,k!و\p<~LNNpNr̙ 򯬯˗Idj%Pwwwݻ"$*W{---0<<1nAb1$IHR, رchnnJޣGp֭|d<'Ϟ=#uuuQ|vpG4 P(Fh4q)b(J_Wb厸Q<<1X6zk JOl P󲡡X,D,l bGŰZD"P~:WKa(=ϟGMM l6zzz:GB"@KK Ν;Nq~Db~AlMݻGj2ҷSNQNxܾ}466L$JJJH}}=~:D"TgϞϟ n6 ϗ`YAV8tt:2OLL@#H x"rr099 ߏ`09D"$ B@P(䟅d2r9J%qߟʕ+x9e`0+ҦfƗ  S[@fg2ʀ@ :B2$ 8qPTP*P(JH&P(B0_?cdjjjz/xJ)e6>IXJ)RB&ʂWf|N=s~9ӹ.@eˠH$4߂3+赵}*\ޞ$iqqFFF~Lqǣ)FdY$ibb lvvP(d ۴N +IWooo7%I?:::zݖ`0ρ\.Iv*))q2 $ IYOOMFsݙAwS]]Ei{{[j;f]CC llloÎ\9I2]MM$)Hb^$)Lfn]"$~Bt~~.@ggg*[\2$y|8 8::=SPPP`|3999(**tzz @ n7MMM@vvx<p}} 8oScc#xG7qlmmd8}IeY%I---Ʒ*Ifs+> p$XIʊ-BTZZQ[ F/++s433 ^;;;TmmCFb*++rhfCIENDB`tdesktop-1.2.17/Telegram/Resources/icons/emoji_trending@2x.png000066400000000000000000000031351326245125100243440ustar00rootroot00000000000000PNG  IHDR44x$IDATh_H]범RZjJԦ֪$K)%l\а |ޢ_eCdZC]dۮ,*+iHxzX̝;~w`ι{~޹3s'C PZ w7 F+e/޽ëW*R6FA1l'Pyy("˗/ĉP__$"?g\.(XYYYݻwߏG'OB5557$=~X9<*++bEZ[[hhh)ML&\^^fZZZݻLO7_oo j5̐v{z ހZZZt:X_~esMƠrF'##` nll"Ce2; JäM66pb6Vbpxܹs˭+PȎ۷Cbχ nI_xQҗ  }ttoܸv%=== ׮]#6`tvv\(>\T{^nǻEajj VWWӧ-''$s]Jr<|2w/խ-%Y"ZݷCB gJ抍%ń LǏw$::mppw_ Iҗ 477Gt'%D [0H?| g ݐ~r ѝN'Am\QQQ`X숾~9|0%N%)_V+9rDҿF>;; .KLL$E}nݺElJ`j4ȶ]p_S.gX^3;;{cxΠV+g6Es )55v&Wuu2xb#H۷o]T3'Gz_xq((99l6&%%$Ӗ&Ksf366629߿111soy;|ɓk 0==% @oQQQ3jA43gTtttpcu:]@qsNu P|vBA?U!//`iiIvN .ye5T3P9]IENDB`tdesktop-1.2.17/Telegram/Resources/icons/fade_horizontal.png000066400000000000000000000002161326245125100241420ustar00rootroot00000000000000PNG  IHDR(]UIDATM; Yq*(hDD ýIt7UEfd?޸;ޛcf913̌1|3"IENDB`tdesktop-1.2.17/Telegram/Resources/icons/fade_horizontal@2x.png000066400000000000000000000002761326245125100245220ustar00rootroot00000000000000PNG  IHDRPW[QIDAT(OI13`?{eΡlI;H$ qcū 1CfYӟ̼*\yOD"y#"V7 ^n%ϡ˓ץO3? $[MMIENDB`tdesktop-1.2.17/Telegram/Resources/icons/fast_share.png000066400000000000000000000005121326245125100231100ustar00rootroot00000000000000PNG  IHDRw=IDATH=j@5VYYhW@ldkKko`UWkmlAIeHb~/my9w b88z`6a?+MSup]VQ4>iCsm}˲|k]W6@`ض}:y>ܰ,Kn~]0 mI<LBE9(2Ȳ^ӦiUUA7k1A8I)EuVϲ,ws4y,!l( Iҷr~w9=r:-:IENDB`tdesktop-1.2.17/Telegram/Resources/icons/fast_share@2x.png000066400000000000000000000012251326245125100234640ustar00rootroot00000000000000PNG  IHDR00W\IDAThϫ1Q}F4Bb,DV )++N)`DH{; "%iv{^ftkuv9sB/F`UuTQXG)fb1hdrW`0d|}ŎP("Ʉ8NIUn7N$HMlXp8$lVBz4MR$inEVzF3{H lِdttW9[ߏnK|- ~_yӉ~e#rT ^ϩV eyWT*%@<(F#p'_pz- |рN{8OT*T0~rF\AP(H8n 3 Fa0{o[grMlwx< to(T*Aެ&0aZX,_ A@2RjAKO/p8y (Rm %"^y6x'Rf-S$Tv(n3nE"` m-~k *:ᦑIENDB`tdesktop-1.2.17/Telegram/Resources/icons/history_action_edit.png000066400000000000000000000006751326245125100250460ustar00rootroot00000000000000PNG  IHDRw=IDATH=@\_܂`K`&57b#=1Ϡ&.络;(D!uy8N<{8:<#Hy4_!:/wL}~<daHM$1'=@wo#Eѫx1@1]׵[EI?-ﶶn<[h:Z ęzi<@AD^wf3l6R C}r,253{pX |n#n~vty9375u5gѓ fV|VKVR Ax<"1Kj(TWW >IENDB`tdesktop-1.2.17/Telegram/Resources/icons/history_action_edit@2x.png000066400000000000000000000017631326245125100254170ustar00rootroot00000000000000PNG  IHDR00WIDAThY=K@=٤WX E "ElAKc%j,a%7׼ L6y»f p=D k@Xl, vﬠi^1baLӤu]@{c9:sh0P2FH7к >FcYEz}}_D1"!Ըi4xњ" ]VcImW/A"-uu qKKUd^e1 _: ~GF^]cHYTE1b$qyuk.ٶ-WF}dAOR T2놞xb۶'baV9C3BPlS~yy`,Ih&[eAVW`Z` LJ6щm;>NKv;82@ݞHmO ߯yV|צ8L0 NNN|r ؤv^[x KvV" an `0o>a&/"Yc?A@o{ BPyO"I8P3 Ƙ=,mt]O9qm6ʲiou_IENDB`tdesktop-1.2.17/Telegram/Resources/icons/history_action_forward@2x.png000066400000000000000000000011301326245125100261220ustar00rootroot00000000000000PNG  IHDR00WIDATh?@φW! DA*`g%bR0 VZXX("ZpǾk̝͐ű`7o$IS=HRB{{6 À(zbLӄ$Iyz#FdYvŏ.$ ^̶QC>Lqh4͹yl@p%we2TUaE{i g+ZDDٌDQ/jx<_"~O,x/mۤ>(nIYv8?/ni\"N_KOtJhԟk0 P':qNi=Ky7۶MZ7 ӉJ#ײ, \;z)v(ǎ=, dY|%& Cel6W<vPe@BR|v}2uq|q7kX Zt'_AIENDB`tdesktop-1.2.17/Telegram/Resources/icons/history_action_reply.png000066400000000000000000000006761326245125100252550ustar00rootroot00000000000000PNG  IHDRw=IDATH啯Pǿne"( 3QLkU,_6EFXqE ʦS.Ͻso ($IH$Ex rv;WRbL&sUbZBt: ۶q<zN۶V햽^=.Q|Rr82JZKNfxdB(eYUUN4>20Q;G~Y`IENDB`tdesktop-1.2.17/Telegram/Resources/icons/history_action_reply@2x.png000066400000000000000000000015401326245125100256160ustar00rootroot00000000000000PNG  IHDR00W'IDATh혿Kls"D "vI."BE#]P\R(vA )яjg&9 %-{_ },mi M[BEcr"cb󘘘*c\ (ba/8ƕ@")"yX`~~l,14M$I+cl R)AFǯ vwwԊ< wC귍DQD4 zzzeu=@P@Pa5_+~pwwG]9==MQvQ/֗_)d,F H%ky{{ $_^^ZJd2\@4~||5T*Ag#Nۿ### :shWWWAgCUU,AgeY$I<:: :'٬}{{{Aܜ}jR) =>>T*4MA4Y.(J$ə.//ۚ x88Y@4dُdYh,RQV\c0\ی((wt 6 0|9g:bh4Hu* qL uz~0 JOT*0 +h4~dYjbj ˲~A`<SFc>q4Xf q>R~Ӊiv8n0vÁt:7t:8M{ޏ}^|}}>:]Z-4<? yu]Z~-j5ضr>!I$I|fٶZ$ i)0M9, Ca\7MJ%i:EX,b3Q,*DQnK 30AjYY灈p8d4vZ۶E늰+Zm;{R$nh䩺ݮ(J7ͫAl^Jd2V6LD* \(y7<BQH!j[Ur"('Rۭr"}!:s38>r}(nw(s<8*ˡ M0 *mN8Xl6, 9///cEUBIENDB`tdesktop-1.2.17/Telegram/Resources/icons/history_down_circle@2x.png000066400000000000000000000031251326245125100254170ustar00rootroot00000000000000PNG  IHDRXXq04IDATxMO[sVcKj PH`*eF](~_(_nlٰeEѐ(5Յʢh"C ƅ@SmbLmB<.=/h;3g\.;]y޽{EQ;Mӧq5\rW^ٳguyMz /_ċ/W]޷Ux(Q:_~hFtby<SshzzŢRQ,izzDh~~4M&M(LR${wvvR"Z4MD"A`AT(x;4BFGGpX[p(e޾Zfyy)xhh=p8[!!"cuu؞5xddĖ$adddcl.ˌ؄T*!`{{j] s-\.޽[N۷M HDnݺU̙3U}j- $I͛7U qㆩHê&۷of$\NbY GWW{_xK@"R TdC`D%jLɓ'#" Lx^;N.HeFO(j.`D < UUٶ-J۶?6%K0"ɶ?r FD*]2kkk\JL۷o#".GF:~DagϞ:;ȁO,MLfCGġnoocvvִDavvN.`!"B$(uBT*ex`J[-ZYY%H͛7 Ci۠\& -cOgMg{B:t\yetR\8ܸǛۜ`Yɓ'sF24vZ/Y_M   ؓ.5\0Oī KL\YѨ7#R4ʼn?w|$L;4yKE2^`200@d:>}Jnr t}MGT ælrzl33μ^̾j4@~:._[\.t:ϟ#J!Up-ΝcKNB!A8q>Gő#GbB[[[;'AQs?,%XDUi 7&`p6B4MMUT>'+;lpy<n{E2wQ/b,:mqu:XyQpe7 Z-Ym'''hZmBahk n,Bрamj8>>ড@9V6E9+i4M(!PUj57aVPUյUHuȲW \[ Q(aD"BB{Hx||Ó˗h FFD" s{sf3LSf3ss|X,X,Q!" FF Yg>c:b:b<c49c`;E( H LB$HYd2d2x<AEf3f3L&F#  }ض;ڶ`pdԻ"0hAeT @Q(t: YH$pՑs1p8DG׃i0Ma0 ˲l"INd硪*TUE>G6E:F*bV#̟ Dn\7$4vn:t]G݆azm]A) re("LBQyEJ%J%Eyd2b"cy){||t ,˂ahh64 ln4M cJ!qC(B$A,c`TUET* 2 1 jѲ(#P^nV Fwwwj4 3PobM&#J!c{{믿|>EQ`uŃ2MvAů_PnaYFћXS^V#I<* qrr_˗/888@R xE,REDQkU #dYT**JH$ż&D4ORCrwwwu~\QNQ*PVjr\$AP(Ĭ7BE$ m~,izǯiEL888(H$oƖ^k5PdIa,['"8xl;;;l9>>FZEXD*(%^ ^ m?0. i-!88::?/_ZP(@e[xGevksw+L&J#899ap$IB,\UI`z||h4Z aEQC|'''gp(D ÈbeٱhTtA*Z ˖J%>G$csvi;LVZ. r21ixHGɄf]Ҁ((yGGGC* fl|>/kKR(J!P,!wbq(CZyD-H awwC\F:ft:rvv3Z@Z^OUr# eGb"W@DWTl6s5/Z_ۆ ]SA'c, h4r\J M4TXKIT }{#Y\m ܜ7T1L0^x@~h;(hn޹GWFlۆeY ̊Y-he@񘕏$+ ܜ;V=sqTxx롗ElflɂD/@W`n,z-BA,N̂".2zxx`cGe6ɂꛥӇ[xzFTDz,]U/sZ-t:Oo¦ WWW4mm;,+ߥfO] [#svvo߾z}?i-&~|*?^Zh͹el X[`-SX Q|͠op=:%Î  >p2{Ŧ_=nZ ?~`%j4xZ{>z@ EP~ꠃV[=;;Ï?Pb.f|1"? l2jm:0NGѓVzkPd1&oooqyy3noo _ԫDry|dӁ5:6k$ot]ia#ÛU Œn fJh4jjxSRu7[]b2Z%0d5lhlNuzRY**,N(*KZ'/Z , ^NvQac` uśb~,HRPRTd26eyPK]C͗x1 1MeﳻP 6T Jp҇҇:D;Aڶ>tW>]?hw@$L # X6ŽA&-y( @xXB0# rrq%-4V~/K@}8 \ +s|_qIENDB`tdesktop-1.2.17/Telegram/Resources/icons/history_file_cancel.png000066400000000000000000000004141326245125100247770ustar00rootroot00000000000000PNG  IHDRaIDAT8RAr0 $y7!ļBv;S\b˖e?70afEfu]?ݝ;J)9Sib)s=Ϩ2tEB&I3{1v a|>XUSJ^[k\:9wJvƟY!:rk! knx{eUm43HG>+13;Uثtӳ4=oQIENDB`tdesktop-1.2.17/Telegram/Resources/icons/history_file_cancel@2x.png000066400000000000000000000006211326245125100253510ustar00rootroot00000000000000PNG  IHDR szzXIDATXŗM ӳN/CEH/*Z0cR%UA?w'DT "}D:{ADkr{.k!o ^Dt_H Esd\֚@Dp΁1&y>TU%̜yg#a}[ #@)<F4OB(h 2@@Ǐc)sSPxv|g@|3̄mD, kb%usM(GFz֪K ǝpGH3C۶0sRbgh9i^hn-A$$0 *50 :⮸v q {vIENDB`tdesktop-1.2.17/Telegram/Resources/icons/history_file_document.png000066400000000000000000000002521326245125100253700ustar00rootroot00000000000000PNG  IHDRVΎWqIDAT8K 0 )3 t#ą-%$#sVQ [zX%-^9M~."*b&NsvK-*<̏EUc:f̟" QIENDB`tdesktop-1.2.17/Telegram/Resources/icons/history_file_document@2x.png000066400000000000000000000004311326245125100257410ustar00rootroot00000000000000PNG  IHDR$$IDATX c̳뱃#;o0.A7~\95MC4ѕVQ%a.@,*/ȉZD e :b,sW%=}ZPt Ag_ME7J J J J @˲6c}ڱuqϦ&)%)m[20g[>9IENDB`tdesktop-1.2.17/Telegram/Resources/icons/history_file_download.png000066400000000000000000000002561326245125100253650ustar00rootroot00000000000000PNG  IHDR ,uIDAT81 _/I];}E!6Ɵtiċ,o*H$TuSkKDl-GxZ}snRhMJߔVܠ>cst=180C\*)FbIENDB`tdesktop-1.2.17/Telegram/Resources/icons/history_file_download@2x.png000066400000000000000000000006431326245125100257370ustar00rootroot00000000000000PNG  IHDR(/jIDATX!@4A $hT}UAGi k ARSѤ᝸8Jd_2n'd cY(ɫE2(iDŽJ5A jP`ǥ ұR\ X.|`Z.d2h&ñlF4 @˲8- Z;жm_cqӶ%FRʻ1)%5:VI6 vVnn뺷 z~_iJj;,+Ų,cU@zb A((a~Á$9 b|C͌c800yqIENDB`tdesktop-1.2.17/Telegram/Resources/icons/history_file_image.png000066400000000000000000000004411326245125100246340ustar00rootroot00000000000000PNG  IHDRVΎWIDAT81@E&xC! TS E $2,'tU58RJkM)%R⎴$y' yFYXj?eI4\k,s]k0 thY۶e44Mm]]mcy!v\ ʲ~kB8,;xWin@)H)o}l82">mIENDB`tdesktop-1.2.17/Telegram/Resources/icons/history_file_image@2x.png000066400000000000000000000007241326245125100252120ustar00rootroot00000000000000PNG  IHDR$$IDATX1@7qK$$^ ,lxk#f0Fl o FnjM냿ː0 $ N$ Ð8RCQJqBJٴ%"cz=!J)NӴe,!Dӎ|@e?t !{zf%ڶ(V _-(`/˲6egAݎm2<żS])}/ ZVtl͆~%ƴ:-辪gn$ph)} TJxl1)HWUE*GW*YuWغ^NөiC4MaVy%yq|`q>3M!㑳7h>&IENDB`tdesktop-1.2.17/Telegram/Resources/icons/history_file_pause.png000066400000000000000000000001661326245125100246730ustar00rootroot00000000000000PNG  IHDR&N:=IDAT(1 W[;.>!i)&lH`]9񛣤$j$lЬ8F=8IENDB`tdesktop-1.2.17/Telegram/Resources/icons/history_file_pause@2x.png000066400000000000000000000002451326245125100252430ustar00rootroot00000000000000PNG  IHDR BlIDATH10@O p. N)o"UT"fv|Ω,O `DOE>@ (P@{,׋g'v.[ɩIENDB`tdesktop-1.2.17/Telegram/Resources/icons/history_file_play.png000066400000000000000000000003621326245125100245210ustar00rootroot00000000000000PNG  IHDR[IDAT8 @EgMM,@Vla- lJ0P ] gp9&0>9GQf]=Y=YiHTG癪0#40 y#J۶Xku{$DDQ^u~Aɏ۶uOQV[=z꺦,_Eo/C%B F@IENDB`tdesktop-1.2.17/Telegram/Resources/icons/history_file_play@2x.png000066400000000000000000000006411326245125100250730ustar00rootroot00000000000000PNG  IHDR($Kb::u:( @웥, T$IxɄWfh4Tyb49 *(뺼P5-lT *4s$I0xm[{8)^XAo(:( Y.B+("DZ)h "d4!bf< mP>uB4Ðs܊~iG~ڥz]4|>Z}A#nGPDx 4IENDB`tdesktop-1.2.17/Telegram/Resources/icons/history_received.png000066400000000000000000000002431326245125100243410ustar00rootroot00000000000000PNG  IHDR rjIDAT( D=ƻ~J6y=0XʄI̖L%)I"bG$d/JH3aVVOCB="ynZjs@;57c)lIENDB`tdesktop-1.2.17/Telegram/Resources/icons/history_received@2x.png000066400000000000000000000005001326245125100247070ustar00rootroot00000000000000PNG  IHDR$>>IDATH-07  pxDC`Vc j9 xf63Kj;C_( .BpO0 cx(G[ix_u]y?2!Geg<2$Yܥ0~@1 7PyP 9RIQs@RJ*!4Ɛ$֟RE-iWQo6VI>1ϷujI4lNP$Ia- JAeMX9m[VU9don@s͠}IENDB`tdesktop-1.2.17/Telegram/Resources/icons/history_sent.png000066400000000000000000000002261326245125100235250ustar00rootroot00000000000000PNG  IHDR a]IDAT(A Dя<=x7&j.h%6,m%,`_dۖeP`"٬a y 2w0(wv\NiIENDB`tdesktop-1.2.17/Telegram/Resources/icons/history_sent@2x.png000066400000000000000000000004131326245125100240750ustar00rootroot00000000000000PNG  IHDRވIDATHҽ 0Kzo1d6DSR1Ps\Yϟt y|<˲R ]ס*+Ҷ-ZH92e2/v->jN@y"~I)Q)I+8(s^g,wR1 ð)֮DW bEaX:Lʲ J)888GV nc٠t*EQ/E!QgZkTUe,iQk-J)$cy6$IL&k4M`($Iby8 r.^pnu^w4kTJQ%pӉ,t:kx<<d6IR`XWȲ̈UUI&(k/g$ Cy.AXDqOvnVTQp8r*GэT*}n"l6qT=5CuuuWrp8LǕITi4 B>>Roo/ Puwwh4Zuc$2H 1Jq\U2 40D~B rHR}U6#/&+@o?ˑb)=>>.xx<^__*&6  ˘Oq{{/S{{{ig R sW^v;tsTVN) BhJB BQ2Uomm[T*%Kfnٿo(ɰ͊\\\ @$Abtt[pfe*&&&AyyA`rdX*(1`E,oDe{D"(JFLMMl6CVW><<~dDUcV}IENDB`tdesktop-1.2.17/Telegram/Resources/icons/info_actions.png000066400000000000000000000002101326245125100234370ustar00rootroot00000000000000PNG  IHDR szzOIDATX1 D?!)t2hyܧ 3C5KA9  @d?.+gIENDB`tdesktop-1.2.17/Telegram/Resources/icons/info_actions@2x.png000066400000000000000000000003621326245125100240210ustar00rootroot00000000000000PNG  IHDR@@iqIDATx1 !Q :)aL+0X|t18@ h 4hMZ@S-)@ hw7XIqs}77Z@3GgS-)4 nx,&A-)@ h7y<hM@ h 4hMZ@S-)@ h.{'9IENDB`tdesktop-1.2.17/Telegram/Resources/icons/info_add_member.png000066400000000000000000000010741326245125100240670ustar00rootroot00000000000000PNG  IHDR szzIDATX햿@ƿ\oI 5)mRYZSKH| E-l c0$!mqwqW?r910;xP(A!.||tyn[m\.wo!Ij100 `0'IJyzMXk0Ib^y4M3x˲dPx<dY~ ( V7$IHRp]:m} 8vǀ^? `'Ji]V=IENDB`tdesktop-1.2.17/Telegram/Resources/icons/info_add_member@2x.png000066400000000000000000000023101326245125100244330ustar00rootroot00000000000000PNG  IHDR@@iqIDATxK*o8NVY"H۴UBgD;EJ^6wI %MIVaBM'E[_rszs9eFDZiH `i -4F0@Hm pR$IðH) t\瑋|>Nr9T*nT(kkkUEf20Tw:___GF 0ӃUattp8* @HT'p{{"n7T*Ufe#HkR85 ,[@a~Rd ߏEqƋD"=Ol0 ̠bWb?s:׀>{r9P(>}>B!L$5Wn7~I|||Pp*BǃKKK855j;::aiiFaP"ZFՊx0Ng"a`8ruu6 Fco\(F͆U'I o^;knN*<\ x+*Hh%d‡rx b4a e mlA1b~Ch)$`6Qʌ*A7I6ʂns9sw_60!D6~lR=ƛֿhss$Y,iYIrcc $hIs DQ=%I,|xx I.--mM(2Ld2diB~h;4Mau,}LOO055HxyyeYfh%MyyL-:Mږg;GC$#OOOp¶:( TU(a0MwLm$YV9??g|aaj$;&r9L$}OH$Jrv uLR=Ov`Bt !Nx<ދ+Z]]%I^\\|BX,$WVVzJHu@>GBᝦz2077|  58 M/0"UUIpttL&i6mf2d  u](svvF\^^fTjJ(ǩk Z {{{(ˈbb(( EVLLL ^`0،^*zq]E0D$, J-offömXǮ-6 .6IENDB`tdesktop-1.2.17/Telegram/Resources/icons/info_administrators@2x.png000066400000000000000000000030261326245125100254240ustar00rootroot00000000000000PNG  IHDR@@iqIDATxZmHSm{ݲVs΄H(Qԟ8eЏ% BEA+_Le)ID+c , BDN~x 3;X<Ͻ8;/?R  5H-@j Z 0@jRCRZ-ܻwOJ ŐGss3 N :?O$2AbcchQ ux rwyll L&|x@Dggg|nzz\DVAaǏD[^^ޟgh$lllt555mF2@ ݻw111xttD$&&b(Ǐroo/ϻ,19;P(pyylN\NN(//'[YYALƉdJĖ2dT* 7n@hh(h4Chkkjw݋/@Ӂnޜ< [ZZxws\_&qn_|AZ-Yj8844lnnܹ#zhX8z{p||< 6 DK>==m6oqqqg 0|oo =B޻J]]]xe8;Dz xPP >߾}+Q^ݍJZ׮]>K.G$FEE N>** 8;;;K`#߿爴X,94oЙ!!!C`ϟ?>}BJE+yzw~ DА`A<33#җܜ$FK LNN&8}FM/\*EZA.6M Cm >N˲hυvLIIAe yj5>|hKaBĢ"MjuuKJJ8'wEAJ| 1y`hp?pk r6p=&r| =BNIENDB`tdesktop-1.2.17/Telegram/Resources/icons/info_back@2x.png000066400000000000000000000010541326245125100232600ustar00rootroot00000000000000PNG  IHDR@@iqIDATxM0O䁂 p3 uN\84`v܉:)Ny-X^LN2Qlm-,ZLM& ``Dlv6Q;!ZC~ |)lRn((_^!@)eInu+ookU"4مWO`$IN h$ݠfI1F5dy;TMjjI(R"Ev l6 ?Dlv6Q;(M& `` W^@+IENDB`tdesktop-1.2.17/Telegram/Resources/icons/info_blacklist.png000066400000000000000000000011301326245125100237510ustar00rootroot00000000000000PNG  IHDR szzIDATX햽P\&P[;,?`/Xo><XCڈ:͕9LC>"0~ y1d&Oa|vKi$IK $nyG@QcE܍bo-n I 0eYeY}ò,Ȳ7ʏūg݊#>o2HIENDB`tdesktop-1.2.17/Telegram/Resources/icons/info_blacklist@2x.png000066400000000000000000000023131326245125100243270ustar00rootroot00000000000000PNG  IHDR@@iqIDATxZ=O*M>Y qJV1j߰ [B'%%DcT BEb X[..1\dsavؙ3?4Ҙ@i M4&@sH  HlD`gg<" j5(pssa=@0ߗ}ery8 ^7-^0i14mSOXLװrT**v [|F~[N$IjJhH8Mŏ!~KTĜ#Mc/Ew8x{{+v g'~ į`>qpp6:b #ZB0DX,x<`<;ˡb!ogqj"Q=h5]HQ^N)Btz}jL&Ei%^;-v@B:|A숌  7`d2(*."&II' looa`ii @EjPTڰcrϟ@i M4&@sH _L"3pkIENDB`tdesktop-1.2.17/Telegram/Resources/icons/info_close.png000066400000000000000000000004231326245125100231120ustar00rootroot00000000000000PNG  IHDR szzIDATXQ twH?%P{/ѩ &bhz NJ\+"dfm[523@u8}Uy3#EýYm@B1ƷzBDZmψ'o 9@p{};*pfj.oϵh@M]h7@ť~nQ3pAUVD`fX׵ $Ũ5|LLp7>GIENDB`tdesktop-1.2.17/Telegram/Resources/icons/info_close@2x.png000066400000000000000000000010771326245125100234720ustar00rootroot00000000000000PNG  IHDR@@iqIDATxr0D;wW/2*<ŭXyAbuHhOm@@m@@m@@m@@m@@m@@m@@m@@m@-1֢keMZ$Iצez%I<ϽqI]㭶7Z4M<N'v۶0 ,bU093EqcNp%׭VUiVu+y_[js_uBUU!%{;aN$=NpʲT',:˲yninΉnua<ϝg{?<~\8c>?{ϧi,VA |LAnf ɋ;!]i,˪?., sxڶR%<v]ڪ[~I$<$ 'Yk}?<^(<~ԊԊԊԊԊԊԊzK}rIENDB`tdesktop-1.2.17/Telegram/Resources/icons/info_common_groups.png000066400000000000000000000010471326245125100246770ustar00rootroot00000000000000PNG  IHDR szzIDATX햽@Om"&U*AK ћPU*+m¤ meF?|⁁ayfHoԏL|T*FB@pM`iwI/uUU#D0LHndT^$i{}ߧyeO{<3HDx ǞHĦi${S4>Ir<<Bve\~?b0u]` txD|8ΣSgjZ]Q,NBkf3LSH70 Zſmۦm KeY4 5h4 Ð$X,n:3 eY,d2uN$!t:lg*Ibtmϛxj4`uul6P0f٪=`۫50 2 ݸQ5FFF>]Q:dq2 *ƺ\ aV9^#0T*rAP)9^&yyZ:88hy)IPV ???}{ߨffေ1i3@D40 o dnr9z;Akk+F0 `2j5dY`Y$$ pssD D4Mj#%ax}}mUha@ ; ~86&]{yh4` FQdYKdI>> |||p:Fߏ899:ڐaiiFaPPNCÁKKK1|eYfsř+t:hX~yPQCCCt:*>qOn;mnNTR\;;;7Ų,Z,sZPΗJx;>>R;99)yxuo(hiim[Ktŕf3HEdjlL1U\JexJUWC4Mh,u vY!! i% m$ Ғ6@ZHK@i9ˊ% IENDB`tdesktop-1.2.17/Telegram/Resources/icons/info_feed.png000066400000000000000000000013541326245125100227140ustar00rootroot00000000000000PNG  IHDR szzIDATX=K3[\'GgDHSb I)*RE-)B6" I@)s+++y-ܰyk3cIBH'_J @RT*)RmYUF|>O&?b4epp f”J%fffW*! zN.d2eYH¶mvwwy{{P( U2HIӸ Dq' aP(0<<$憞c LJ dIb1\=S*8;;caa۶D?A8 D">Hboo/ܢ<'h1vvv(!,"͆0L}0ưE!ćXYYAkkkP!iDr$8zI st:MVZN?imwAѩ?//@:Ԗ-IENDB`tdesktop-1.2.17/Telegram/Resources/icons/info_feed@2x.png000066400000000000000000000031341326245125100232640ustar00rootroot00000000000000PNG  IHDR@@iq#IDATxML]PhŴm!DI Ѩc+Ѱp#jذhYALe MjL] H*ypcݢ}8=9ܙiB_, Vx+woen2x7[Z g2<>ϟիhjjByy9t:]ZՊn,,,0B5AH mTVVbxxǑ#G/|hmmb<ܸqSuuu@LHgΜQН:u9%(< V^ Á{ĉu^oBcccطo_m:t,S~Y{6ADgg'~* .Ľ->p( \R7F#099Boo/֬YBQߟX  nss3J߾}4<zzz܌ deelٲo߾ebÆ Wܹs)q>L&ܽ{cjVZxbe]]]qN˗/35̍Ӯ]Acccijj 555L r w_vdOVh0PXX ;v 7oT}K.1SWv3SBA@a4qQ>.{1Klfˣ|mmmM/qi|vuVf<{,X, KS͛ΆnAy"s]yYz fpag4'Jyv;"(@AAAFAAkhhJKK!IBB$IBii?''yNyN'D"Ǐ)Oyyp}p8T)(A*;v젶qm*___].g>'.Kdff{\TTDNy)U}v*Ⲳ_}4سgjZ57;;K߿O}VUUE/_bA޽'5Aɓ'Tm6*bQxtt5ǏUs=JXxx7<44D֭I*l #ixzE1ajj|>7LT>SyȬZC3J I00$IJĆ^@eƳT[K,woen2x7[N zIENDB`tdesktop-1.2.17/Telegram/Resources/icons/info_information.png000066400000000000000000000011211326245125100243260ustar00rootroot00000000000000PNG  IHDR szzIDATX)Qמٯ!E<""7.F|C)ēHC37s1Nǩ_jGFYlDZKٔ|.>>_2 lV7X,Bww7 |~---&mVAl@ P)$PNNNj jEѳ϶ bTU. <Hx 6 6`_o_zx'&u <õkYmVAl@&. n߾ML7@:&Al榡?<勁3͛7oV"]HY\E0=|nn{M>`б6=30 ߿7;<\.X >}DP(@[[p ! UDQMӜ =B#DXbht:=<@bj qaBrRg~m9{i81}sTfF A[6zh@[6zh@[6zh@[6ZvNl6LMMPTT*A:|>Lf\j& wvv0"8([gggL&!C&r Z V+8pv> @RJ4jriiK^D"qY1~||(nooqqqz ,DB!p}}8a>Wq-ÇD"9 =;;ÿu&I+A4͊DQč f|xxݡjlczzkiP4dB | 5F#^]]d"69NNNdk^^^ X[[zy{{C5\.bU>K\.Cm6qq_(vNlAVQ$Z-b |wsQ0iX]EĞC$x}}Ӿ9P.A$$g@{ܧ?^Bc* 0991v} & hV0`0cXe2 OWm q}=;55̇-q ,KbE J%<ȠC8W,$: Bo. `pE- iUvonTyss,ˎeLRZ-,}3KO&p8p& a"jSKϜj 5JBh@[6zh@[6zh@[6߾eŋcivIENDB`tdesktop-1.2.17/Telegram/Resources/icons/info_media_audio_empty.png000066400000000000000000000030651326245125100254700ustar00rootroot00000000000000PNG  IHDR`@4rIDATx_HSQ;aCPySɠE!))dsD3z tSF[W#HI YJcLtp*R{;yp^g;f!")aD X&2UFp֒(p8رcdXׯH$(R$>P8UCJ8vЪER|j:dΝǏSGGI ۷;D"F)Hзovr8( ŋʕ+t:(P(!S_D2!ɰWp0Dss>XLccc(++>ߚ,Q(//octtN@e<%& _uu5BeY>v;X2 cKvP\\Y2Ξ=k<(..>dY4`~~8E<͛7#c 5˗/|UUU!=tuu5?exxYx؞z @EE*&3`xݢVTT`0Дȵ5qXP[vgֆ5b1qs\WWW8&''Enr95<99Y*++(--Zii)`gg'On$ ӭuz%IDw1ē+ᥥ%@KKV+^/(44(^/VnF@$1]ǯsX]]͛7[nS 8,뼧+] a|>(Y!2EC8f~]/^ܽ{7tWeI}с?|]=ݾ### @-7_~]Sď?]]]Yݾ}w8NM׮]~:ޚMckjj@2?~\S)ئ&‚&oH$\p}*b&F۔Nfq1##o>(X&2WtvRmffF̌.K\7T*E,&8qKZ7A5=.=n*M{ȲJ ZUOR\ X&2V^eِ ,` X&2 @'"@,#"bX'\.""7Z˿z*O.RǬlŭzeVX,zٹKb/]ĵyIb$/"d~?- 9wܸqM>Μ9l=GII <d2=5 ckkk_d2O>ӧ7 /f52 @Le,`a{WIENDB`tdesktop-1.2.17/Telegram/Resources/icons/info_media_audio_empty@2x.png000066400000000000000000000064401326245125100260420ustar00rootroot00000000000000PNG  IHDR]fk IDATxmLSgǯak`ΆJ?(_^L&P1?՘&6M3KeqK(T'& b钖$^χ=۳Sܳs~?T=ߞa%P` #4Fh8p0B\b6 JKKf3dUV"qp8 LMM1P(D<|)"[o[n[jUt@<x<`rrR SUU:N@@ӉUUUsЕȑ#z5_zȑ#XTTD>?zTVV±c?+V0:: cccajj ax< PXX& ^y0PRR`n5wp\ kcf***/D"g`0W\6ZZֆW\`0\/D|t rY)ٌǗ]hX {{{FfތF#`oo/beqFL>Y,rY%mmm877'Ou[O<###v4 ~PF800Bxri߾}x?00>LBDR.almm%S%`ss3,R`oooŲǏ'??N9^,(( "7@bw^rc*++C˕rÒr"7@7Çe"H`GG?ёTÇqƍEn@sl1n'v;=;;uuuDn@Sرdo!kpǎDn@3֦\.]".]$h44MT]]󲦟?Ο?/y& W_}ev:ިt:e2==7Dn@U秼t… ޲E.\͏|ro܀J!Www7lSwwl>Sr_܀jjnn5RƆ vc$H$n7lؠژԬw5| pUTZZ*_)JǤE۷oKDr:r`0>I$veOnҢn}b<00˗RP\ ]=Kz! (5ǩy.KZ]@7;;k֬! "7>I?~eeeC?epqqQR#GϞPL8qBzUUU˾)Uzw Ix<_'7$ Vfee%nFFvr^7ˮ]FgEn@|BO_cVJ4HگёPD===Fy<rO/{RP2lƥ%I.--o Xǎ4{5|>A%_~%%yNۉ(y 3͛%LP- \lP($͛7Tسgd/+漣uuuG7b`ffFGGƍC]]eO{0IK{BL|sݻw{V+;w=zGsTt{n rO @*++4$ {SEEEՅO< ?'O`WWWV#պDCH[4իw`wI:WJ<>||3Qdm$ۃDNǙ3g͛P\\,;/^]vuח| v/^/pM8sCII'@0]ɳrggggwC:wMN1_^czz|d(ribH H:u*B=uTV;%igriA҄۷oyپ};>}TgrrݫX{䤤ӧOq횏֭[!HKG4GAATl k&&&4.Gt76MM}|>|>>}ZXyy9x@ٳ={i6{OR#_@ZJ+-[4oHtWFh8p0B` #4Fh8p0B` #4Fh8p0B`n$DN#y{'tɶ^oөGVZ%^\\$r9 d{DNjJm~vMM H뱱1"' J[ZZG\{<"'ʀzfgϞq8OFSw8Ϟ=CFҕn_~? I۳gqHaDn<骩ILРIm_euA"7F#޿_Ґ[niR[㏒GH2oVhpݲW@2.kU)R_.g]!X+WD+kҝ;w^"ܹ#zqʕ}WHbqYNHs9t:S8Z,~+(riڵR6qpp86mBٌ&)ZɄf7mڄ@˵|>\v-yE/˗mZh=N|2X*܀*jll\H VٞDn@5Fljj¡!eJfhh?\8\_wymUUU`Zᥗ^ɤzx<h |EIn/c%0B` #4Fh8p0B` CְIENDB`tdesktop-1.2.17/Telegram/Resources/icons/info_media_delete.png000066400000000000000000000002751326245125100244130ustar00rootroot00000000000000PNG  IHDR szzIDATX1 0oAP7H;w)4E-#q< 03AU Y>Pތ3zpv A轇9K)7 $ \D`u5FIENDB`tdesktop-1.2.17/Telegram/Resources/icons/info_media_delete@2x.png000066400000000000000000000005371326245125100247660ustar00rootroot00000000000000PNG  IHDR@@iq&IDATxA0E+ÜEr̺$Bt]T|4χ$h,f60_4 @ (-@4 @ -"^.-3}z{w2(= 9ZhOw޻AO4 @ (-@4 @ (-@4 @ Ф+{4 @ (-@4 @ csIENDB`tdesktop-1.2.17/Telegram/Resources/icons/info_media_file.png000066400000000000000000000003411326245125100240620ustar00rootroot00000000000000PNG  IHDR szzIDATXQ tRo=l1S'! G(1;!"0"aJDl&DT]ꓯ>RAL"&x?w P8hT J)_sMUۗ#9gc]m7#w,=#IENDB`tdesktop-1.2.17/Telegram/Resources/icons/info_media_file@2x.png000066400000000000000000000006501326245125100244370ustar00rootroot00000000000000PNG  IHDR@@iqoIDATxA0EaB}IBE= L]dы(H|x@h<Z@ h4LDLGJ}>E4# 9giMJIMd7XJ1 8n#{y_"g0wh=4>EL"h^/0h.Yf5fyZt >^q-AD&KP @$fur 6`44M|p~~]|8f[q` @D2EڀpJvf`"pg (w6s$"pg3H0ٓTѡ?u4 3K3յk` ` ` ` ` `hb$bn|@DRK)w4w$p]|J\^^{)ϟ?rrb38<} ͛䞜e !(S:88 1 {rBf !ML)mH򍠢Zހ+6jrC@n5A%Yn! 7ઍX7pFPAva r#]vY v#YvQ z#UNI ~#QNA (&'Pf#INT`VCY~***8 ź(yv !\ v.#X#X#Bus yX#X#X#X#X#X#X#X#X#X#X#X#0Iss3hGGGpttAssgqL3G EznM"7@4M3;znMptt555Ewzz ׮]S^1(mr F`{@:̌(_<)mmmUm (=4 5M빭VKNZ@`@`@`@`@`@`@`@`@`|۷oCgg'477`kk >|޽UνfkS0NLLޞÉ PߗO1˕ r8==rkM܀ۋl/$booZ@FFF0899}}}ֆuuuc]]a__NNNv822"N<3vee~A\YY)SSSm xb E7[~[(r t>߲qvv]]]m (=@ :L#"f2]M lm (=!  "7@|>ꏏ^w||\W۷oH$BDNCR)Gx...9̨Il侼v]<6552+vvv`iiI.=}=U<7ܬMF ,Ӫč ݀^~M+ZXXvcc+++}Y$r(4ܗ4מr_܀ezaXTϞ=#e Xj\^^6 -Lb4%(&ICܟ"7`Bi-sֆcccE{N1 {A,W}}=$"b*D" {vZ~ vtt`"/^ד{Ilѕ+WpffP133UUU\R===%_ I^{g~ R/~>J-Fwܺu Z[[!CMM ~jkO8>>L&tR|vww9 (1 A F F F F F F F oxAb(ܻIENDB`tdesktop-1.2.17/Telegram/Resources/icons/info_media_forward.png000066400000000000000000000003251326245125100246110ustar00rootroot00000000000000PNG  IHDR szzIDATXK P|fŸr'jb$HQ會3nY /gqIfPk(bkƜ@v%Ip$צ?KP>3 !؝/#wV (Ds۩ `9q~{eIENDB`tdesktop-1.2.17/Telegram/Resources/icons/info_media_forward@2x.png000066400000000000000000000006061326245125100251650ustar00rootroot00000000000000PNG  IHDR@@iqMIDATxA@ DQcxVD̴iDH4M28O I I Ip{JCDyO~;3?#??7~_lvo#~t ?1?ZAw,rGUa4@]a+U\@@D0 a,:pZdz}+?r ҇Ѕzu]6Nc8<^/f_b5I I I <\˭$IENDB`tdesktop-1.2.17/Telegram/Resources/icons/info_media_link.png000066400000000000000000000006161326245125100241050ustar00rootroot00000000000000PNG  IHDR szzUIDATX1n@EH\@Aͅ\nrIHf-&Pr$G/fwiB2|X!UcXkm[^kVbU}1qL4S4qGQ4QQ%yeY`p>oJ׵4 cZDDr֌{n=ϻkm69wAo@YO_n1f}0 Idp$I2Lpxn E~9z*EQP5]Mt]G]ENv)^. q+Br IENDB`tdesktop-1.2.17/Telegram/Resources/icons/info_media_link@2x.png000066400000000000000000000022051326245125100244530ustar00rootroot00000000000000PNG  IHDR@@iqLIDATxK:]X"/$R $EZT]2eM"-TɌ^2-,z8Y+ܙW~1 h#@[mdh lm -6F6@h |ZjP__,BmiپCІP( B&$ˀDg . :::l6R,_aʶ#`oo|>dY)dԭ6+q*eoP(pii _^^*/OOO8?? ,ˢ%-RmLNN 9CL&5_ /GD`Yt:8p:>SSSJ`aaAD@k]]](8<AmƓuhooGx0fh4qE%800[bt(D"zP,1 #r󜿼DFcW31H!8wbH탈;fHCh4 ꯬0"4...`|& 1b14L}$666R]__C<D"BA@꙱T* 0 5q\vakkK4>ꈍY+_|5)|5?a@,Q)Wn׿n@.Ulmm"ٱ80`ZrAoo/vjeju1rnoo! A hT A"dh lm -6F6@h#@[mdh ?uotIENDB`tdesktop-1.2.17/Telegram/Resources/icons/info_media_link_empty.png000066400000000000000000000023741326245125100253260ustar00rootroot00000000000000PNG  IHDR`@4rIDATx=O2KǏ7Fhl6cVhXh#~%habaF /)jQ z2ˀw~ɩ`̙CD 7=;B8#3B8#3B8#3BθݡY1e|GOOχ_]o6HWWWt~~N'''t||LZ`a@uj5gBVP-s9ہ$I@ٴ4 "@vl+@UUh|>ol"Nk\98;;B8nd0񞝝c588P*055}jhR p{{H$](;>^/II}ݝ+C׋slN=Hli)+k$ bN;[`04h䱲h4 0MӉ3]'YL&әY,X a0 b1}dPX<\b}.7 vY.,--1e r9V.; '+6+VV0Q\.TUe8nnnp}}xgQ_8ߞ/=Ua$:P` H .#5\Fjp0R` H .#5\Fjp0R` H .#5\Fjp0R` H .#5Pk }}}p8nî]jVm\EQJ;@:D"X "£G Wt,ۣAlkk#Z!i݅b}*M*}g+PyU>|-s!liÇfy=@ 'OL3gszgN:Ef0X}DžZ\\4tr:66&L:e` k666Fuǎ ^/y.6^WXe<GX,ztmM@/L^<72xx!W  ϔCggg;~8y|@"b&1xxq!W$!ϔC0qKKK>%@pxѢvn?x(I~R_}}}4A`8p@S$3:: TJT FGGM?^n$1\۳DI '@oo/ܹs2 d2sB$1xvkkȏ6ḱ<[===Z>y}wi|ϼ~:?#d5k#0qǎ#Tٻ"lP-+cDŽh|?| d'NTl'N0Ƈ;yG"_C(bܜիWU9Ӄ^ƚCB>'ϕCE<|>y|9sFsh2<<\񆇇5eTic:|⅐ŋUŋ˜/_ĺ:ҹX,F\1V ZV\YY&҈/+!677W}f\ZZIٳBT7][[CAWosϓr=[sf. SNa6uƓ J*`P3/4 |g>i[[KO.kJ<[(ȡ!oTZkRkիWuK`='(ڵkي<@*IBrɢP(Y o<@QӚGCPl"4J8997o7˪М`rrҬ~(IEQ.z^\ifUUE׫ywoÞ͒(ˡ_FRj#؈>O <@ٶYA2@ n)2!bwa$ދעN%tf8i@#̈fD3"`&J)} fC fCAc):Sױ\.Q6%ֵ80)v?yAqa>0eY0 v.u1 t]Ej. ϭYA$"۶)I]e`6m[Jl6\RJEWɣGfXH'~PbN O` }~ >a>/:y_fD3"`F0#)VEQQEjrƶ">JEQ~R}n]xivxzzB>(d/̈fD3PVx?xu3d2s! %mN4P( JA @i4P( JA @i67h&RH$޽{bddDB!n[Rqvv&Jx8::"Mhww...fIi{,viffjy{ àGCl6 e0M?w@#4mMe2{.avipp8=} c}A.id9kkkL&ca*\T^5eaaݗ_Nmr\G///򻖗-5o6466EpT64P({RElݓ[D"a~%huk/{ T*, E"vO(Xj_*=su 7nzё:B!K}g@d500^4P)yJ/yx@_y <y"<WywyvE0O?IYk:JA @i4P( JA @i4PeѨu]TUQV"f=?>nG"^()b1z;W n/K߉0"jUܼyggggbppP \A\ݻwү߇9xҋx<.z^b7JҀX,FSVZFLz d? @i4P( JAm@ ăԔFΝ;0 Q.EX_||^|Y\~؟p_j'Mhsss;2mnnzc\)044D;;;h4}h4hgg؏K!v H&trr %Iz[nzd |xxH[[[4;;K4<{4 ðsmmO $?+5??xP(P&!KITvmjhW\KITviffj $?ir9۫ dg*4MTJ$ q}Dl$j>"K @'"K1 Jme;39QP(d>??grׯ_[n19QvRar`߲L&Gk~= ,ۏ?frLN:nv;---Yjl6i||b7't:mw.cF\S7dlu6 Ó_"vy`0S6AD"* I>;n4ŋ}GUwAT.'l6)">}o`ۖT[S|>/޼y#}m(ŷw( JA @i4P( JXQM6IENDB`tdesktop-1.2.17/Telegram/Resources/icons/info_media_round.png000066400000000000000000000012301326245125100242700ustar00rootroot00000000000000PNG  IHDR szz_IDATXjPO# 4ҷ?-E ,`&4.P7Ҏ:Ǐ܏s="1} RDZ(D)%r{{+J%dd2YL&.//y> "mff Qin+t:ks<3V/J[X,lXn:$ I>zHPVÐL&v~%d2a@Z:}z=,²,z.y\~_۶v͊8l , L GGG+~˲HDkiX,6a(6z6<==ɉDDjſԖcy~~-CX.v]<|nB`445Njt(h0ff31FョnAE$Uj/^~F!pzz!\3=$?HgV<8W(b4}6k`I^U\/dIENDB`tdesktop-1.2.17/Telegram/Resources/icons/info_media_round@2x.png000066400000000000000000000024561326245125100246550ustar00rootroot00000000000000PNG  IHDR@@iqIDATxZj*Ks9mh$h;$Ɓ L4?3?@A (!AP!fHnp^T?M˜` vjuUWi4 @i M4&@{H J%:==E]]]!ݎL&:::B!4`0@vUUTۛRlx<V EՂx< lǐL&a6=p>f$IjC@ _8n f%0NT*A4  nFP*`:Jf)@\%  FK@(^'\.JE@8J* bp'_TvC~v}cOnΆ\.GV`0( l6h4nl 3l6 Z- Czz`& ïnJ0",~>44 `YXFt|>6 XrR)e`FpȉbK)G^_b1E98FEQ\nx||GQ`)qhXP($|^2.nl6c'!'qd2p8VAVL&#K-F5cIK/l0 >z]ҿT*a~ ,i8z'oZWƷZzE}0W(`튐fOOO~kytVt:(/%M.Rl4at:Ytt:e-NF/|%OceiZ|T*7Ϸ6-x<ڇ~///XLvs,&k VXZv/?Tn>79X[oZonnj]jh44Tnizi&4MMӟ: j2GaC˪2B^prX,AUFɂ-GB%e/Y88[$ƯF)^ChwprrFY|Y5j $z\ly10X,twXeE¤.G.B!Pղc4 `P-s [OS(Dr^(r @X\z=ioAbaZRF~aYREOcħ OZmL~ JB EI ^Hc/i4 >' ENDB`tdesktop-1.2.17/Telegram/Resources/icons/info_media_video.png000066400000000000000000000003521326245125100242530ustar00rootroot00000000000000PNG  IHDR szzIDATX] ! 0.^I)dn%˦Aԙȱ9g`Py 3>-9xiK 5s={RpnqDvX!k+~33z|bM)ʽ@;P2gIENDB`tdesktop-1.2.17/Telegram/Resources/icons/info_media_video@2x.png000066400000000000000000000007121326245125100246250ustar00rootroot00000000000000PNG  IHDR@@iqIDATxٍ E?F܂kA3#+^&G#(/t4"@@#Јt4"@@#Јt4hJ)e!EDJk*31{{1l'o]^~v<^@64.?s fYUjBV/Z X++_VLy[V*-#O3[84MÙ^rat.ιk~1`yȐT7T'BU s$-IBvS}&8 wi|Gն5ڇeZ{ۃkm؋dIDbًT9\w5FhD:FhD:ohJ_4;>IENDB`tdesktop-1.2.17/Telegram/Resources/icons/info_media_video_empty.png000066400000000000000000000024241326245125100254730ustar00rootroot00000000000000PNG  IHDR`@4rIDATxK:]Ə,(rhN~@Y_֦ ZV"*%6ir"LCW󮜷-s~u>pv3>3;&"q1?oXcq0XcrQ0D"A$ I \.l"h Áh4 )phSOF>\__󡽽 6|ytwwn\.فbamX,r9ֿI& X X,ɤk펎΢Cn uxvu*A,Q$BDUP(P$!ZpwwFmoo \\\Ą4iNixx[NOOK)&f3.//}}}UIOZۦz~~nmDߨ-o6TUx+++E6 6 (beeEdWpȗ##)~E"-2LKt:괖UϔN5K\f1Xcq0Xc - :IENDB`tdesktop-1.2.17/Telegram/Resources/icons/info_media_video_empty@2x.png000066400000000000000000000055751326245125100260570ustar00rootroot00000000000000PNG  IHDR]fk DIDATxQHS_ 1d@fs - /r]_B7YS @00J*9tS)e j/LM.ۯJs=;>2{3!@P(*P6J@iZ(M ia4-l ҴP6J@iZ(M ia46OO&v"OHFFkk\kwwlooEDH  /_$ q+N^!J>N')p8 y@ff&>S__dff>|C><< @QÖ0/.e{!&9PN<1i5Q̼>&I:sOW\_B^^qiQ|c? Q6;;;LKc?L5 QD⢤!͋j»wD>(+[;F'ޠ͛JF5s (0o>tQFo. SeEw7(N#sAߩW`|Μ9sAߩU`|V+|A]|N~ItEwj;X۷o;5 O2. _`|tQ##Ǡo7]|9[}677assAُf>. Dk%3[]Bؐ؀BG3 PHN. S`%u)qj'ޠkD-V͸r{{['֠{D-V lQrE\_`%@¹JSTTҢ"d>4]Εj*Faa![[[ ( ȇ֠¹RQ|hE8d2JTc /K.H'(1T^^.ƍӧ$/lԱt ?~`D>GG>.]J@DEF˃o߾1p (Q>8o,Tx+D4ʪtRVVFΞ=KHAA9u"My-!SSS$2vw%Ooo/z{o޼!.\ '9Ε*:%V:;;%Уhyy:;;j2C1Jl|k"فf~]JQJVVVɝO?MR`B4y硫 jkknd^zL&v.F4V)w>G 1$|c`rr2̯W|\.ۢX`%iiiyP帳kK+Ayy9<㻾D{Ayy9;5?[ZZ)^NSrϟ?``Z%*\`hnnkhhPl~LkT>x{t ,鰰 ޮ=|d> 8***DVWW!77W}sssauuUwUU:(R2lD]v(0>J ʰUr`mmM~P69(@T\2lD1(@TO2lD$3(@R"DeHJoPHa )O"2l$q\HiP8|gG`0HK?f3}L&(u枎:(SM̈0/2OII(9ڠ,>}}t:imgGFNjkk#}nϟ >Oԩ@0>@@M"Q? TfIn6~bQ&hl6s_7(]cccM"Ȱg^XEAboP:::YEx^iVQWWWv՞΂ba!6Ɂ<~p  5p8ѣրh!8}JOpyysstza||9I9u~PfjvIQQ'YYYD׳Ƶ~I"D"$W^yָ B@x6J@iZ(M ia4-l ҴP6J@iZ(M ia4-l/L,apIENDB`tdesktop-1.2.17/Telegram/Resources/icons/info_media_voice.png000066400000000000000000000007341326245125100242560ustar00rootroot00000000000000PNG  IHDR szzIDATX핱0{)d6)BV1yqBVo=!Yx'0R涴SA8#e#4ZcVXk1FhOaH۶m ð׿G ,C)R,0{k-J.zߖK)M+p !PJustf3@i(~Q!`ZiP%wvl9scLgþs\X.xt`y)姵d2yki~|>>ł@4h; 8,뺦( .>GH$P(xq2 D|_@&r:=cN'd>㨢]NS(T,qPK~hhH~% 6ZXKkIX`- 'h4T_PjTVDfnk٨~>G R}ud2rcz먯=<>>R CQL "2 낈dr} xvj?0j_Y >V JJV rb !3337jZ( H&H&(?|<& A'@<z_z~~Xŋ&''qxx\.%&qtt)Q2 X,d~~XV+rssCI,#EpfXxW^4&KbJ BLt{EQXk`O `m$ ֒6ZXo*IENDB`tdesktop-1.2.17/Telegram/Resources/icons/info_media_voice_empty.png000066400000000000000000000030421326245125100254670ustar00rootroot00000000000000PNG  IHDR`@4rIDATxMhMǟMR&~ Ċ(CECE(9D \mxA=( (bԃ&${ݥi$ f3f'@>X$ /@2(+ HP6\.GDa}eK6K.kXFeB*T8,,x6DSlA/@2M%1FDӧOdY2 ݾ}kBh1%LǏ0M0M?Ƒ#G组"=K*8{,сH$ݻwSSS9 Kϻ%R)Eac^ @{{;RX,6 T-dқ>>}ړpQhZB088]Ou >}*}M)'O,j[={۶ŤZQ1tDD411Ae-eY411ABV^]ꆲ_EA BT(ȶbAPL$!D3 X)(|QV@>|>1:eUoc5`8ˎPIw%HK hZZ{b4--@u""4Mr&ii?<3\?~ZZY_> TbLӤ-[չO.a?U|uV2MbTLBᲺo߾ѦMjyf""kY]8P(D߿9~=*ǏDg5˗/(ƚ϶mۈÇ5ǯR}vUV7>>NDD5?y$ݹsL7H;o>TyJ%9sfɱϝ;RIp_~ سgA>1L&qcǎzm8|&I!e;w8x_gLW"… ׯ_/ƍ\ mpՊ͛ٓ/_@[[@QqkI(J>?Np9GWWN8T*]Mxmɗ/088IF%իW۶aY,˂mޤNOOԩS'ۚΟ?/}֭[mt]1>>/_@!>1$ ^q Vlb('b;4M0 a@uhVqFo4*'Ӊ+m:th1ɤwSNؾ}q*+1h4mGB\|6lXrN\rGGGvZ;9_? !p] T~z;v &B{\ ֭åKtB|/^Ç#LOOcvvB8㵿x"֬YW_|VV,+MU<!b޽ b"4M\\ H=NP%)_d|H _dt ,ɦ~%" 2t>E`IENDB`tdesktop-1.2.17/Telegram/Resources/icons/info_media_voice_empty@2x.png000066400000000000000000000053571326245125100260540ustar00rootroot00000000000000PNG  IHDR]fk IDATxYHT_ǏkJԤBFCKCTD/IKZCddjQ.)QȨIfΌ̜>޹l!@Sv6 , \@p  5k0` \@MÉR$jjT*Ixxh@HXXXm 22RtuE.'RI5:JRtu,@@V;woyykk0۠]}+v MQQQz/FDWWbn@R J"aUW‹`k 5k0ر6 dD&"DRwޑ3v%Vyzz˗AR} >T*烧'^ 333F76333p#b17 *EGGCWW?76* ؄OtLt_Z=|!2==M!KRRRHff񫫫D.0O$|||L|rH ^ BgւZs+ -17 hřAl߾}_bn@3))u=Je"sUII ՔfC+))asՌ<0{JJKK5pxx^W"FR!`17 8I$(|%ϧK$"P17 8%&&R b...T}L|](|96Ӹ]UUE-VyyTUUil;{ )tdddXӧ)GhG#lccc1::Jmsss0'''jԔ}LOOStyC6`$ڶbq?~[܇C#\` \@`$64>>Ą0f^  5k0Ɔ@p  5k0`AƂ@p  @!!!!m 5@+AM 5@ P0p  @S Ӏ@p @)i]wӕnkkkÙm޴w6[5_m:xxxP~;ϭ [@׷3ˎ^m[}[=^7`ppp~ZMm1誹׵JRs,-BCC7mmkl;u9-$==]B 1 2#3gl9<S###1]]]'=== Mk3_qŋdlllqiiiW:DMWPP@ 0X 'JqdV j׮]j {{{SmkkӨcиÇS|@ 0u@EEA.^H5Uyy|VVVRrss [QQ1[` B-))1Dz2NssAcr[ k4ݻw  333TUUU[mm-588::4Xc7o͛7&sww7hlLL R;::BS&Wy 88ؠ97n`V$BTTWW<>&&~IcHLL4x$xιj5DDD""t6.@ww7ANNd2xdEEEݭw '==S;܀UWa z^`]РK]]C*‡4x5B17`UJNNٳgFϓPT*8qϟ?JJJbV(Nݣǘ끿uq7!==}[5u1_Wk? ZTm555$77w[yyy(rG6HH>LIgg'%߿V;vl[Z\]]a||:2OiFFFŅ7+sV@ٳgF][RT9E^'p17` _R566o~~017`ڳguݩ344M bn@ruu/_l8BR>u>aNGG'j711nB[ߔ/H N)))[FאH$YYYظi>HNNf.B>\v//-LNNI2??OVVV6>Ņ899___FBCCINNΖs~ܹsܾ}gPoz6|E D!'''χ&5}SS[b)HHH $55d2&ZS)bH4 J)joLRf2iibXrNVLӼ1___X.A@ ø<# ܁dBHnG֌㘾茶&m WO%s mۆ뺐R""$I( h۰m Js<!t:EHjFRJ{ej5uJ%[e^tR%zR2"z=V** >()n__B/-h4"ɧL$ϊi6 Ʋ,d-BPH}I\ E~Z遷nIENDB`tdesktop-1.2.17/Telegram/Resources/icons/info_members@2x.png000066400000000000000000000023751326245125100240210ustar00rootroot00000000000000PNG  IHDR@@iqIDATxMO]TڰB]TYP&66馻Jݠ龟km7'P7Z#U(U&5" &d,< :M%ws=p_$?C6 ;Dc ڀhDhm@4v @6 KP4Mi㘚B0lYhE{%wwwY]NNNL͚_f@t]ҼU:- 0866FEQ( #~.ÇoM}0NwdҨ5 2 Cϟ?[D:Iruu,[<5M#IfYԭ #IjFcN#mPf%,չ)Mprr߿~?4NNN,iBo޼1n2dWWׅϟ?uSSS@kN\0H$D*UUn_V<4Vll5]911q:-f>ɰ;;;L[cI  "ٳgo߾a~~?~D:n, Bu:Wa,kYt:\|}|>B!~=|dT _|A>?ىP(Ga``z bh$ R)lll\i/Qb1nnn]걹X,'Opddip8LIZ <~sssT*T*<886yppjZ*|>?~ҙ^»wp]ZZE:8vvvP(p|| hooǽ{ݍ!<}@$xIT*5|#>ZTۮR @riU>\.AutiO$j,|Լ./x<T*r96+I\.|>Ȳ ]בdn|=S*pSmmm72&rvS@XDZmT+f aDc ڀhDhm@4v @2rIENDB`tdesktop-1.2.17/Telegram/Resources/icons/info_notifications.png000066400000000000000000000010721326245125100246570ustar00rootroot00000000000000PNG  IHDR szzIDATX9Q_sNM)a%ckÆ;2 ;ۉ AQvJf?}_1#}}Ss:O{ 2~sJ%m[ xv, O(V88TURF#ni4P(l6,K6fvr`P(^H$t:d2I`6_ l6 hoz C `2P.}Jxa,j8R)_o:u]VeNRJLӔ(׻u]1MSRwB@DDB~ ]zOt 0 Z XAZa RJZanfe[%""pXZu9Ͼ{>۶oGe۶o~]MS1M:T|v2}u%-b1D"nz}{:xouj5\V=4eY}5^h4TN[~ީnߕȿ[IENDB`tdesktop-1.2.17/Telegram/Resources/icons/info_notifications@2x.png000066400000000000000000000017551326245125100252410ustar00rootroot00000000000000PNG  IHDR@@iqIDATx횽KP& :4+Ak:Tݵ, NRܜF ~M4Pi;S&#d8'͓ӓ b1@-ujP@Z.rPʓBR!Ɂ[,Ë lJ$bYo<˲xrrU(eٟ(&w4 I'<|>S`0xhOOOc6RdMJ%d2899sQoiooGFF7%p*˘H$>-I3 L^i9Ex||>AL}yyQawwSr~ y4p5u]|>x1ёN4 #ۋ(X[[3  >-[\\O"0E_ eC666 ^www800uh\,BJd\YY-ggg 僃xzzOe󆺙^V^hh4P(X=>>F*GѶCk'@$i:\]]Yz*G" @WWW:jٷZ2qx,\.rP :\vihe[!"hf$pge[qd6?[թT kaޮ*2 c}0Va*m(mZ  Ia"Ӊ@ `8vvveYf-LӦvNJ4L緼.PVۊe ===kl鴬d gwa8< E7ԁP6']Oy:k8Wbǹ:pO_Lkp. J)Z{ZkR/$3p V}9NIENDB`tdesktop-1.2.17/Telegram/Resources/icons/info_recent_actions@2x.png000066400000000000000000000010501326245125100253540ustar00rootroot00000000000000PNG  IHDR@@iqIDATxˊ0ӡRע>+A|-!n*!JX, 8NS /9lZ' `аt4, @@8nt:Jy.NȲLL&8BVvi#5u:gy~7' 6dYFV㧓/F!`0R\l60 )"Ro4Mc C:axDZu~_U|n([$I*RJp<+RRǔ$ )*Z[ e}_<ϻ{MT<;At4,MuWvMW @@v^U4~X s~;|X:R`h:;2cZri}WX,*ȼnG^}&4M|ZkRV]' @@аt4, @@ cWIENDB`tdesktop-1.2.17/Telegram/Resources/icons/info_restricted_users.png000066400000000000000000000005421326245125100254000ustar00rootroot00000000000000PNG  IHDR szz)IDATX]0=^ d[ JsĈAclef",:&MLDyeY֚BBm)dq]׈{jY޾,K@B̐RbR85@Tni!{f~eYq1MqQsucbOC¢(^u]5R.֮pmef֍(su)4*lkĐ=>'/QJZ c sDtoYk7A,$mtIENDB`tdesktop-1.2.17/Telegram/Resources/icons/info_restricted_users@2x.png000066400000000000000000000013411326245125100257500ustar00rootroot00000000000000PNG  IHDR@@iqIDATxʢPw2){q, zP^.6Q치s*Nǯ{/;.s PK6@-j P+U@݆`> ~Z(%_avgl6h4R{%{I>VN%I>{ßN'f8t:݅iaDru]GYdYF"r~^&EQ"hbnM\xY!Xn#\.Cf3{Ah]c6PmuC+qPԯV}?`6\8NP\XqV{|>Dz,ӿ$ Pa#4 mF"IbeY4 Cj߿ _۶S*4*X,cPTzp@ r9v_j E@4m<XRX qgKqݫ׋(9Ivzz$:tzd?JuPU}naZix<x˲iҷ4}RJ>mlۖp4DIENDB`tdesktop-1.2.17/Telegram/Resources/icons/inline_button_url.png000066400000000000000000000002221326245125100245220ustar00rootroot00000000000000PNG  IHDR 2ϽYIDAT1 @@/o|? W!+\@y& w'd&7+R;B:"~t5}FlzDIIENDB`tdesktop-1.2.17/Telegram/Resources/icons/inline_button_url@2x.png000066400000000000000000000003521326245125100251000ustar00rootroot00000000000000PNG  IHDR IDAT8= x Ht.lKv">$> wbIco˲/QN)a%RP<ض됿:XksnֱOpxYkjgX(ĠsN@5C"L|Cc u=źj8MCML@k} "IG=/|YIENDB`tdesktop-1.2.17/Telegram/Resources/icons/install_update.png000066400000000000000000000002701326245125100240020ustar00rootroot00000000000000PNG  IHDR szzIDATXA !PSxa C?b@`fdfegxGQ//"z D9HKqOc&ZcN3O})㭙KIENDB`tdesktop-1.2.17/Telegram/Resources/icons/install_update@2x.png000066400000000000000000000004351326245125100243570ustar00rootroot00000000000000PNG  IHDR@@iqIDATx1!A8/pjO7-"b1x0/-I-I-I-I-I-I-IyyU cSU뢪 "8B0B(B B#0B" B#c#¼*fsn2%Z@Z@Z@Z@Z@Z@Z@Z@~Z@Z@Z@Z@Z@Z@Z@o&JIENDB`tdesktop-1.2.17/Telegram/Resources/icons/intro_country_dropdown.png000066400000000000000000000002371326245125100256270ustar00rootroot00000000000000PNG  IHDR fIDAT} Dq\(pU׼~Դ5h "0.I2-LR:0{!$ZkSOq%_ͱ}W"_!KxuOZ1'IENDB`tdesktop-1.2.17/Telegram/Resources/icons/intro_country_dropdown@2x.png000066400000000000000000000003131326245125100261740ustar00rootroot00000000000000PNG  IHDR $IDAT8A )BB"g>Eij@/V̌D^^=3#x[J$Y}*ҋo'~H f`FğL(ǀ>CpG&!smUީYIENDB`tdesktop-1.2.17/Telegram/Resources/icons/intro_left.png000066400000000000000000000213421326245125100231420ustar00rootroot00000000000000PNG  IHDR2e IDATxyTW ˢ% ܈Dž1QќD8&Dq41f8: D52$!,n, H}yl],sOnխ FDCh f00,> `ðf00,> `ðf00,>LMMaffS>?F~~>bccԤ; ƀȑ#iLI111ny.]J TWWGDDA3fP&,3͚5233+.^H 4566v<==&$2888PFFF_Pjjj~'RgDOoG-w…=+..&ggg+OH^hccc̜9SZ###,X@3++@OOOlkk9[QQc4661`+9GuƍΖ-c@~[$ѩS8=G ,N$''LL}He9s&555]H*--:ɡ^LLL=JL1wqqq4lذnm͝;ICCC`bR,+(&&F&p o!Oi@HPccc7hjjBFFFT\000X,U.P*^^^:u*`ii [[[hjjPZZ,$''ҥKeנ]SSLLLhȐ!$:McggGv.GHff&ϘҮ]K+WnРAuVz1}K. ]iLLEDNdjiiZ`` Ȥ;v,)HOOC ]qLLAg2e TSS!;̙3)77W}k&t11ɟԔK]ؾ}d򸹹Qaa!/KD_]qLLAenϟ/c``@W^-x?~0 xɵ&ҥKq v0m4yꔒ$%%֭[F{{6 ^p! {L)3066˹&>}6P]]- Dkqa̘1F\\ܹ۷o#55ʓ 8ȀRRRpMDFFc* <戏,3 a+r0 `|3} FFc033iii}}}akkrXc&0BҥK\1h"ebb(.]D&La&&&"={311ψ9s<@"LD@;wro&&&5H~իWQabR6RSS[abh#jkki͚5Ÿ&&E%3v؁?OyHIIO?{ !|g}髯>?y,Y"LLrILDt^5 ̌N*LL&":p@ >ω$TDD?ICCCo{{;^ZpH+r޽ׯWu1bnnNiE"b ~P˒:… QTr744T' %WWWuׁ7xC*-jgii*`ff&֨HJJRG 'NPCe/++LMM`b"/+n:,]Tm=ZقӧC an7D":twA~~Jׇ\yN>gz۶m/vBvv6򐟟< c>+gΜUTZ9'"ѣG*#8z(VXcv444EVVrrr|TTTUUUj!$jo[oŋV(>M{}}6rssG/_NACCbbƄ d766B"Ann.rrrDfU Cػw/Q]]-w~ccchmmE\\֯_ߡ1c\6P__//2{lhjjbKOOvvvpuuu(((tNN2334CMY.l٢PK/DR;/9@999hjj QC>>DBB9 B{./^7/GTY9&NNNJ^'NףN'?wHIIX,ƠA Hp5ݻhhhPlllS]]$055Eee%!9VPP`(/pj~ڼy3uDt<;wvy38oSSӥ#Ã]F!!!<yv} RH$hΜ9Mmll$ooo+9t;}&3333f  '''*..V>f3)m2Ϗ-ZDSRRD~N+W|ѣmN4IWHmmm ^7Lkhll7o5\:p16zTWWxjFWMfG7y5\v@LKKCZZgj֢P) /Ez- ]]]NEEEr- ͼb Ŷmz5^!5VZbE1DO߻߿7nȈS y֮]`o=ٜc.XYYٳXnL-`nnu̙3UʧvA۝7+W=QQzj466">>χ%0~xwފRVVwwwPPVV.]t;H2ɧ} <8{[*<3EaaBD򉖖LLL0bAK_ T cРAҭV\7rZU]HR'b={6NÇ (//GTT_Xv}!___D"ڴiUWW }Rvء?:6Q^^NΝ#OOOoY{sL"cǎEkk+r&wwwz7i#ڷo_uttҒi͚5tqYy~7@TTT$s,''G[GJ_$wߥBjkkT^{ڤuF7oޤoN9;w.daaAbn޼)s&M$xg$%%UaÆQzz޸qY齛^f̘A߿qU* b0f\zB;ֲvvv QPPkyzzzΪk׮BzV2IB -dDFF o#++Kfk //w蹻CCCW\Aee%̙2ײz \pB+/O6001cqqqgbb---?;p@ `ƙ3gՠ(D"Ǝ HHH@[[奥 CU(? ^Qp1MW7)))ɺ?ގ^9o͛᝷6mڄp֭[Xf%P;w3EdjjJ&kkk!+++|8FaÆ066.tttԄfTUUEzz:222G)}UUUҵ "DEEaѢEJs%lٲ׬V9b,]~~~g~iO+++ܰN__6lo. ^esҡC޽{F )Jqq1ҡChܹdiip+4a rz-_D" GGGi\ihh\iƍjܕFI2+]"w֮]5k,:|p"==9BgVmnnNؔ)S(99[[[O[lYf ]w255T^ȑ#gKBi&s1qD6F~` e9r$V^ ///؀PXXDFFÇH$ ϟk׮j'O涃oQHH qqqzj^;ŅoQՋ+[n$Ç=UKK PMMd&;;}` ioo_ݻwXhwƘ1c???899u/_Ư=͛ Ҳrmۆtraٯ-5j(:y$X'7nܠ1ctiݝºC/CA %N_tFHHJ:99ŋiٲe4l0tnm{,ܹs2?}q:L'"{|oII ڵ 988PZZZiРA,#44߭:r No>@_vҁ-۷oNӵҲeX3˽{-QoM---ݞsMMtuWWn766ҬY=JJJLw9ޛFbwww*>> TVVvf `ʘ4i.\333]Q&:u4mmmҹ=ڴv;!CSSׯ `J2e ~1YELMM '&&h/99nΙ3uY3TŋCh7F"]733.wK hܹs.1+Vtf["66AAA=566n& Hfף ,ϟᘆ/_ذaΝ;#FȤH$_*ݗbDz:~p7#!'%%%ѡ'U[[裏>@Nwchkk#DB/Ю]hʔ){v{[l9r$}WNQQQt):񡚚NH aˣO?<==͍>IDATs555)lO?qƩ<]\\}S__aH$" ϦMinw}}=>}LLLX3rrrwO}g$''c ٳg;V\x1<,:ر)Smmmg}& 1H$DVVЮ(XZZbƍB0f x!ۗ`-0cccc  Bf0,_"Hh7JZIENDB`tdesktop-1.2.17/Telegram/Resources/icons/intro_left@2x.png000066400000000000000000000444431326245125100235230ustar00rootroot00000000000000PNG  IHDR@dȯ{ IDATxwtT7^I :қ" UQ,X(zUzAET@P/ J"=@p~!:̙Ld朽9䔽@JvDDD  0 XTLDD`"""H,DDD*`&""R 0 XTLDD`"""H,DDD*`&""R 0 XTLDD`"""H,DDD*`&""R 0 tj'p!Co߾^Gii)6oތ={(گ?zBdggc׮]GDDBjbȑ#2EEEѣbĉ,֭[WOiiigff_Mc`0 u:7nK׋˗/ѣG NW~%I=qF a0/ a}aNmmmEBBB>c!IRjbٲe?={Tb0 u;]vmφ.]Z>%IoIvA/`0pܹ&;JJJDTTT7ϟ~I٩1  ¬Ð5k^{Mt:o?f888g߾}ѢE ""<x7fh[9 ;:udr;DDDf+Mggg 0ڴi;gN I,mQeHZUn둞.K ,""jV7oެHF{}]JJJBllI=#YLWvvv(..roooYSB9k'""Y'Xrm_ 1c.\P~,XKDD2ۼ";;[*vډxa0j~aaX~Q}P`XUÕ+WV|:.;lǎbРA&?`0 0=`Ν3y\ɓ'Ѿ}:W_} qUٳX|9N} h\|$ŗ7m۶!W^999իbG +T]GGG ҥKjCDD+ֶl⏦M"##zl>j{{{=EDDdNcذahժJJJХK8::"-- uJJJ`kkg۷oGLL ۇ4h4$&"gF4o&MŒ3`kkkR[ϟN:\L4 2eJDD K/.]ixTM۷DDdun׮lmml\~m`ذa@啔`Xf}eSQFΝŒ%KEjjB_|!$IU{b۶mDk⯿ΪO`'PizJ~h 1l0Տ=`0T uxW\Ēm}۷O)͛73 CPm&|?#|PxZI-[ֹ%OH%f_6m¦ML*fҥ[|@Q4Hf=:֑#G]m۶ĉMGIW;""23w ܹs|ƌL_"J,I~il߾M6={T:w,[_rKLL̙3ѼysdffDѧxMQz^t޽>5M4@׋Kp՟c0 ńrK6y*...Vڷo3f_.`0,4zjEh4 r]+z $ ={ĺu̚M6! @WXXwYݻw$Iûヒ4%"" iuně,BCC+?hd3..NL0Ah4/]0 ~===+w",, F7 7i,[ /FRRR!""엠_{5H$wFի)˘[bРA$ 9s&/Nh"gUr:o60sL8;;C$ 2۷o1K"""d})V]vUs;crۏ`0 %C{YYYr7YF!<<Ntg"==}ŐƍnJiӆcrH}*,ܸqC&tMo)))f듈Tk"SNaĉa%"zG9"{yyyX`ZhI˗CYOB_֭[1dHggg%""!=`pvvFNNNKJJŋlٲ:ODDT_(RE~OJDDDdm[ i̙̬zll,Ǝ Ij1a_""jp;jx0m4`ɒ%HMMU+""zGLDDDS4UH,DDD*`&""R 0 XT]j;;;\|EEEjCDD H b˖-i&婝*+w͛j*l޼%%%jFDDd]z+W_DsEDDdժ-w ر/tnDDDVnϰb +&"":;?͛7̋ȪT'| ]ʓq-|O+WDDDVG|Gzz:>C,ZCP3gb֭JuADDT/):tXXlق-[ ,,Lɮ,0x`?9$""h^6xcn,ٗ#lٲ%vލe˖YիxꩧcR ""Rπָqcl۶ 1LȬT=۩S裏">>^TڵkGgU;"""YVXSPTa=QFjBDD$;}(;v W;"""YlooolٲX%{͝;fB=IIF___ ̄-;3A#k֬!Ǝ+~'a0*=b`0VfػwpssSxD|||`U=wPV*رc[WOV,^3tY`0V2E:sWZxUVṙ a0 kz1ϟǀv* ,ԩSekuHHH="^`HNNFϞ=v*gXf ZlmY*\͚5ݻv*磏>yY$"jOF~v*!88.\Pt4k OD3;"""eE9sbm{{{#**J9c2dNEUpppPuaرODd }bŊ$ITTh˗/7cLDd*+0n8|j^xA>rss9/7 믿)S*k^ǹs탈Y=cȐ!ؽ{ک= CDd =ڵkv*f3f(,,d%"2U`jb}|׊ADd'T; Ei4z Ql"π?~<{9PԀC׳ɠ`OvQr;6oެxDD A}ɓ'ѹsg)_e׮]q!!"v >{j!;s,`%"I++_~j!_~Y>9xSo%;\pdee,53ׯWKooo$ /_F||<Ν;Ddgg&# 1qD0YӦMqeig#F)SпAƦ¶HLLĹsgpTȞ,Y.B <۷oW;,\ӦMSs!44T>,Fy0m4h4ݭIOO7ZPXX(SDT4 .\@DDYOVJnn.㥗^¢EÒh4,^'OVk׮… 8w/#11())Q"RG/`٪7niWWWǶGQQX;;;9ݯ\s!!!gϞ-+Η.]R5/" QTT$Zl)5lllĬYDvv0 "!!Abbbnkk *---ݻ궣GeCKӪU+SXzzR; "\SV+FU/wJeϟXۖJ$hB4,VEDDDÌH ,U#SO=U{Ql+Ү%񁣣i?OFJJ T+OBɈ〫pW\\ltq=Ҿ}{?~\֜~׮]^m%IB`` nݺlYW---ő#G*.I\]] wwwxzz ~~~?|}}ˊ 4i"S6!:PmzdӦMME֡Ca0{ĉh!g}wFQ"99YHc(ZF#<<}:|}}~^I!HEEE~zלt.gj}j"=^ggg 2ڱu5|pܺu҇ǏC_0|UN7ȑ#())1cf S;r=L1cƠ]vHNNFjj*Ґ,dff"''豯ljhkC)ࢢ"kϟt`0 **Jqttij>{ΕnwY,X>%ki [켼0f :eO$gff"==iiiv^7n -- ptt4:Z]TsID\K<H=zts1KcǎO>9s+zAAA?>~*KHH#H`i0{4ptt_򐕕,V|333/K[D?<3FӧOQ.'Om۶`1o94jԨ\&MYYY3k,տ'BzΜ/^N9fff !bǎ"%%rfnnncUs u:J-7Vo4bտ'ڂhРAfkԩӧOrrssk׮Eǖŋcigc,mҥK h44X… O"22RM+,_ŊeҥKѳgOf[i6'ШQ#-))Ie=yR`ڴieÑ#G~8p,=֮]˗iӦ/_U V;r AT) -- OdXe޽{wnxر[n޽{e`;!U|͛\Ql3E.oU 5E&`h4#<<ӧOG~~>bbbeotIwUD/^Dbb"͛{Bh},00,VSRRp5ۣI&t[cCW)L1tP :}>kbݺurJ۫npvv6bbb0pu:233޽Xv-rrrX k/HzZk׮ƍ/ŋ(,,3:v숁~: IDATbРAر#@Q~?\]]ɳ&UȪ6Bzo>1uTY׫_/Y "00P...?o Qj#77WĈ:*wppbѢE"55Udffc{'{1l0տ'*Cl*PbƁնW=e˖޸qC̛7Od-<عsgeͱBCCU UR.\ѳKVo^^?X$+w#|M3cٝh֬شil9Ž91 y-Tpp0Ν˗/>+7TfܸqUGFHHlm-[ _|Imܻ{l)ڵkr}g999_Ĺs?_~pttr>Lovvv/bѢEȐ;<==!k!HDa'Z-ƌ;wV]iiC Zb"[!H""[͛7BѪU+Yڹy&V^(~~~AwV." ;w)r>w.^(K[wAƍk`le?̚kӧOν\\\dG]WH9,VxjNU/\ K;rss"mׄɪOdX7/^#G ::Zt,\Crss?ګ T j߾=< zR988[|||`kk+K[wS{/?)؊i4L6 gΜANǢ5I=z ??_ִiSU 0&R pмyslݺ|CZ,\O@K-Z(2V7::Z1,Dbn@z)8q]tQ;p{~XW>\ڮ)>QO,&88[oՠnݺlmcĉlcc#FiӦWWL,Hƞ={v: 0uTؘVdd$|M;^+WcnwǏO>jbvrx1l0h4u Č3da0T́ڱ7p>>>ؾ};^yHv:fwww7|~iooo|1b̙WA"R 0Aa޼yv:kѢbmb۶mxk<ݻwǮ]0|p888(_MǫՓFP<|(F˻$W_/t233]jRXXڵk'OV4$"S9?1|p>|XtaFTTT! WWWۣ[nR\Hy,TvލGy.00l}i4jժla!z"sGˉC{d~L8QTUW$/EEE~iY=`VҥK1m4Sӧ9Ħ .]*OMDa*I >1Ӡg2 #ayw1w\ӐqAӰX6IHiZZ1T?˾E^^~eS1Inn.}]/W;Ӑπ}||о}{t-ZJJJ^\rGVV233qQӦzJC`jaF>کXnݺ5+AAAر#ݻ#,,lN;^h4صkN: .`Æ t(--UP=LuoW; }:uiXFƍj(VVZw,"""({?YYYXr%~9rgT)`2Iqq1 ݻw켽VZu C˖-lo^?i(Bbx뭷[z}dggcϞ=XhvI8*Sd[[[lذ={ӧNGVHOOǡC*רQ#lhݺ5ڴi-["((j___<2e jիWq !!!ȀSWH\\\0l0=t/`26oތ: --Mt"55F V&MʊsXXZn4o?p8v6l^ux{{W]qq1N>ĉHJJBNN<==\xzz@燀 ܙcСDff&OE>_M?ѷo_竝Eh4 DHHBCCѶm[i!!!F'nNƍO?dU,%I°a[o}FHIIC{nl߾eBח`رڵ+:ud~Ο? .y)ʰ[,>#ѱcGՏ+Cް1T 8/iʟܹ3"##c͚5͕|۷{5FA^*ecc#F`ҤI+{=00:u¸q0rHFow}wy< ƍY͛#((۶máC#;߹s sOGGGcѕ^4i=PT0)6͛7'N`m0yr<<<ЦMB+/ĉ(,,T4G̙3֭[Qnb6m **6۶mHK0 }̙3aܸqX`[[[Szy 5z+CAAN}:mۆ7nСCd~YYY63LstHJJnݺرc5ZSNb&Ǖ+W0o%wڵ`0?ڷo_"Tۮ'v*W2`W$IBuVG *# +3222ϣChݺ5*{B&eފ85g:gHHP5k'|0uҥܚ§OFBBBwСe~`z/<<ׯǞ={]֣ҥK4ze}h޼}t:o߾1fޭM6~^pرj Add9XjGҥKv:7n6zE ݻw@93gѣaoo_>.ڞ={j_=+Y`*>,1zh!UСCѤIprrB׮]1c |x1hРZ ӧO3?F՞aS= DdڵCIHq-?/_h۶mm[AbbӦMʕ+&|^zU/R~"22R0-8رcѥK?F~qvv ~Ϟ=SNW^Nj/GGGl޼HOOpVբpttD˖-q}}FdddVՔvZ6iGii)9ÇW:tPdXb׮]x70|_ ڵj шB^^bccq)dddM|qg\иqc<3߿?~ܺu $!''%%%BfH*Yt:zك*/e>3Y83ׯΪ_zb><<<ĤIӧ˾ϫW8_޽{~ذaʟ/"!!A9rD$$$BEvԩrIıcǪ'66VSabgb;vL4nX_>Fbȑ⯿2UV NWHHH0OW핖A$1gbtk׮3g ;;;տiС]v;vٿϟ?oCu ѢE Z1`j𜝝uVw}jBϯѭrc~~_ݐ$ iiij\Pa&۷ss {jjݻwc,I<$&&70,DnnnjBF?~.]vݻ[ɓpႂF dddp.Z† _3j8x`EDDTTeԒ$z`3spp1|p4nݣo߾7oi=̓\GGGtرB۷OL]vUϚj.B#..aV KX6myӠ{ ++7o*Ub޽8tP'qSL[lСCkt(,, )))z*ڵkWKB\¸lŘk5&;;[>]Vݻ.99Y'Iزe~jjg޽"**JZbΜ9cQQHJJ+Wѣήg3`J`ժUlAJJJjkܸ1vZo!=jQXxb8qBT?B_𞭭-#)) ӦMC6m0rȲ[w?Wv!44T􍒄%DY9sV5gggy |||///8;;n͛7"-- ɸx".^k׮jmȑذaC͛7?=`lٲZVX,Z[lQ5~PvPLL hӞf_)ޗ$Ih۶-:tPmDDDٝx̙38y$9\͛7a4o~~~HG"66{ʼn'al޼bf[d F\1|pѤIՋZ}^{MA }baꫯh[IгgO<5jTӫ=e˖~@NNYGfpYfvO:tUCa„ D>|8[YYYpuuU;*)5G7 0Q-UVHIItL0&MB֭͘rssvZ|5Z>T$iii5jƎ ;;;4iҤ}Pnr'NÇcǎʕ+|ɰU;*͘1 .]`ZZd &O\ƍ㥗^¤I,~x !}v̟?;wT;JI&޽; F{{{4nθ~:N>СC8yd.2]HHΟ?v*((Ӕa&bl/_4of“O>iɫرcxwi&nM6N ڵk\zgΜ_ӳZk׮رcNFƎu& 0Q̟? ,ٳϲÇ1k,ڵKT*ugֳR3~~~~HII73]~],Dupgggg36mڄ3f 11QTHEݻwGlliJ׮]q!c&"+..ƢE;T2Y;w_~'({ZCtrHIIA6md!T?<6nܨv*d&| z)Yں|2233tRa߾}hҤ ڴi7ob„ >|8$I xWdiTn:K~کB6n܈aÆ??cs5cxg0h Yt7d)E*&z!gfbb,X@8::)^zl4S̘1C3`"(K,3V1;v@xxlm <<IIIunCgo:S\\ ;;:wL0i$=z"f-v .Z|ѫWʦX|9 :ckk+<&"~6lP;!Evqﹸ 88>(JKKѻwo$&&b͚5غukm`֬Y?~U#,DT`ӦMz}YhӦMK$ttt˗MjW^>,DTu?Cd%y͛u_+?&HMMEӦMMn?պ,Ddz!̟?_4^~e>}:˽v)Y*֬Y3Yuvvk= 0YiӦɲJ n0 u_$ :kΝҥKfQQN}*rFcÆ tSNa񈋋NŋPje&"5n8|uڷI&իvh2č7pY?~M~Ȓؠ !+`F N:A$_+W+<|ǎ;п:f̄ bŊmLDV+335)IuÇcf,ɓ'uV_Ǐ7KSOZz=zTz} ĉMZȘS/,DdնmۆW~˖-1aL>; ""+ ehC=$a0NϨRqFѧOrxGM>&'N#Fs:NO2|;͞=۴"[&DD*--Mx{{ F#x qYSǏÇ IT/Dii,#))IL>]8::֨_'''1a K>,ǃAعs'4iRaѣ9s&ݫv*uK/O?8,\wFzz:z=lmmo.]Ȳt?v`&"GXj^}z-du }<$Iddd… ԩ `r999xq#$'ٳg /:Ҋe} @YOOOt L.W\ADD\]]3`"zx'v*5bkk"Өɓ'1tPYNӉ*֭N8G}TTjk֬Q; v GGGo,0Oꫯ49xzz^֭[cǪ7π/-[MTatas/!Ij0믿0tPI+,, Ɯ9sޯ1,DDV(99Å NRhѢYzxbUSMDd5k}MHaLx>($I Ȫ]rݻwB 6 ;w] Ȋ`׮]Q;< vڷo/πXׯFk嚓FAVVF||< bg0QУG|WjQ`G}T}ccc憶mֻ A,dR~#4i[kDVZ%o߾̛7׶x r̙ dԺuܾM JצMɉ' ]sΕh4ǁtuuIIIؿ!DVTT$۷o#cF[p<|0@Fmݺɓ'Kkk FrYo[|tww""+)--N7 R9v오R)H,Y^7 zyz nӨp9s;wnv<l޼YxF#მ£&7JqFF:#)? sNx$""{5Ph"yqV}?(*..ˏ?IOOTTT??СC \CC>gy䉛4h6k,xbN""'UUU T -[HoooG""d UUU͛7]H[[3y`PZZZa:~7 xbyk#f~b9p@Np8H$b UWW'/_t} ""ϟ7^eO*//0ׯ_KYYs+3?|Pcc|ѓ ҥK͟{@͞=[._@FڵǙdP m۶ɗ/_<-?қiUWW˝;w</>қiGAٽ{|d$>Ho@ d$""A@vIܽ{/(׌NSSL&=\2_+yW(7]TJ͟;R`sU^ȑ#Ε+W<{=7& ̟7~ HIIŜ)SNmms^KUW_#H .O:>}ڵo ԴiӜX,B~n׵wDќ~͡!gڵy7a(-|ܱcٳ~M?14}tw,rah4둼yF͟ѫeۯgNooo./]3f̐d2wg(ȸ[ne4 ތG/dۯO>97ntDą|d[EEŸ[^dRV\i~Ǚ@>۷^ ܬɽ{$ k*++I<9sj:;;e͚57Zot:zs%??oGzQɷoߤ.d~ ?CٰaM>YHDDDf^d]^:Eb\'rTHIA2LDJlOz%3P*( BP*( BP*( BP*( BP*( BP*( BP*( BP*( BP*( Bի|BC4mT/_.vv(p8̔1cƈA(uֲ~zYzh2dũ|W("BHf|MSRRbrye}w(`:t۷˒%KQFULTT?)f͒tӧm6??_4t]SNwYc=Z0.P!OiРYpr*ͳ>)P!/1[ B!IXX:u)--LMzZdžG;vDݻ8wɅ '6@ EFFʜ9s$55Ve""2e9|dE!L޽}^޺ʕ+ŰB!IÆ ͢Ej?MÆ .ŰwJBBiFz/^89s=͛˪U$%%ELDDf̘!js!2rHs)_x<aBO۶mͦMԋc9b6mj5)""aaa2m4Ȑ_BF!v 6^NJ=v9sΝ;v}`ɋԭ[|ᇦ/K\Wmٲń[~NGJӧx^С_SPP ]t\C*5%K^&yP&װEqE{9s1.o]o_scÄO-իV$gi0<6 ty<3fdffJ\\\[\\,qqqrŀ xl@H/Rzꩀ{񒓓:K^Ixx>}9w\@JHH;::w,˖-nݺYFFCʬ2ի'sΕ[+˫2P'xB^oodeeY C!]ҥKMee}{( / /VZEk׮RZZj{C)r'W=Q{(T4iD+矃LDD~m9t1K^Z_6yyyWjo p0ߵjJ矷=Jeggˣ>*%%%G V|x<2i$ 2z/eR;l|PO>GQ3sLU !䎩S5k)++աjÆ PYJΝmɓ#ȩSl Cpkwu,X@vrer(_D{(4h Yt/Ν+۶m=FHa 5k&}3(~c0`/* k &}ݶG w} C =_{qۣ$6 ɓ'ˡCBLDD.\Q~ R=$%%I޽mdž_]S&.ҳgOz%&N(ٶyC\A_Ȯ]\W&˖-իWCB3<#Ҷm[ۣ\VVt֍= TEGGʕ+teqoQ(@x<+ÚiӦIFF1\BBTqqlݺVYFm: ֭[g{ɑ &ÕؔBX%77Wm/^}޽{mFl_3k," qnYڸq|駶p5תU+9z?J.](nƒN8!{=TTTȑ#)Pe>ȵG; K^ m{ u(,yn#=ӧOsP(K$''A1FƎQC.vZ#Yh`pJǎmQ+ҫW/N_tP7 Μ9QF.wV&M$1p  "/_.V=nB\&ʒɓ'w@.lO{qoPٷo=z6}t  2Ƙٜ_v,]& p`(GƏo{ Ю]$//t%6l5@.TYY觽fϞ-1PC RN]ڼy̟?TDDIflrMnnJ~~QPs|V/_\=ʗ2 ^ bNZ7oG9[Ktt9v)ƒfa3roPeqW(6n(Νr%K8\YYlڴ)MKKw}7P(gϞaÆIyyy@ PHJJJ@q8qB gΜ͛7^(">,SL}`l ""M4|S_ٳ8p/ׇu|EEEm6]wޡLB=한,~6%/״hBN8!j|8eb *t_#Gi @䧟~Krr'ӱ:u$iii*4ի/(\D#"Q'<ҲeKyǪgyD""DC1RUͱcLUFe}>JJJ7޸S b KSXpGR^^.+e2uTZ׻}Ԭ4ߝt522R~r޽Ճ~2==] ]TPwByu-'''7xQ=B_znss,,,կEY[[+O>-߾}Fzs_6oU?ztgggQ$͓'O{˫~H,AAnݻ;.?Ç233S>~x7UMLLt^y&RBccc͛ѣrxxx%:?/^rvvVQn=iZ視XիWIfjj= >@@( @@( @@( @@( @@( @@( @@( @@( @@( @@( @@( @@( @@( @@( @xAIENDB`tdesktop-1.2.17/Telegram/Resources/icons/intro_plane_outer@2x.png000066400000000000000000000043771326245125100251100ustar00rootroot00000000000000PNG  IHDRIDATx?hIv"t0P%Aҥk;uhqKABLnA"U֤T1S9's}& B o~9 NUVйU5UUhz.kz}USS0d@!(D @!(D @!(D @!(D @!(D @!(D @!(D @!(D @!(D @!(D @!(D @!(D @!(D @!(D @!(D @!(D @!(D @!(D @!(D @!(D @!(D @!(D @!(D @!(D @!(D @!(D @!(D @!(D @!(D @!(D @jժ{V__)g~_6o\_~eסCjME6ljZuȑZbEkʆ ~˗G}TVN:_'hѢ:x`W_}U[ld2G___ ֕+Wʕ+588_Ђz\UK?~\K+ᆱO>Ο?_SSS]c˖-qVk<|>~!4 p:پbŊ:rHZrctC]/Cx}yKW}}}o߾K.՞={']]522Rnݪ{-ZA:nݺq:}tZkٲe=ٗx kʕ=ӧu:qDݹs<6 tW_… uѺzjaCGkpp6m(T+MOsE={V﯑GPA={˗#M^k``n޼ {(@G:|uV1HAm۶U9AڶpZzuO]vtm[n]l>|zi}/;VN=g{Oym^/_?m~/Rn}]݇,AK^O<{_ݵ=A֭|1Wx hlN(u:sLp" AiP5<<BP[re-Y799YoV]x S1W 0cN6i"0+erƹ?޷M5"bYT P( @ E@Q(P( |v;Smm-˗)776A:cbbbxѢE|UnOAAȊj'C[n m7:$uf͚E/66Vv( ,FӧO']p7nEbXhر:M:"""ׅn@HHLLٳgٳ)!!AvQy4uTˣt4M> B ]idL׵kׄn_P*>>f͚ESRRR@DQQQx(Ln(;;t] &j O(n']i̙l'D( H>}(''rss)99Y8D@Z4au&OL]vmMv?_~!D-A:Wx͚5CťKxذaLDi8q§|'FK"JɵGss3rXX}|Vcc#kf/2X,}oܸ!LcilFEAԧK.rH E]~Gl m_weԟ$D]yӦMio޼+Vp׮]ۜoʔ)+v鳔O0{㹨(yp:<~xgΜ Lk׮u՟lXuӦMٔWrxƍX|$EW_}뺫?Xy\__ ˨j1b$&&۷:ߠA|]~bbb=oyٲeG:cCCAAQ=)))|AodW?Sc3f xBɜ~O4M+W dԋ/xΜ9~Jggg+{sӦM(Jgɂ \DFܷo_CǦrrrPΒ]م$ÇmכLχ ?,Y¿_ŋ/fӜqqqo:Qcbb=/X˗/ /ypMMԙdyuW3x`֭[>_Huuu_5СCV-޽{eȉns޽oZZZ`U4G[liwܑ'Mw2UE̋j匌 ޱc?{[[[yܽ{w7gҚ?%O&t/Vx/Ah[***rt MhմqFSlϟi:fZT"LVQ! ~I#)?Q(:reddŐ۷oǏl EY,:v3F(;wNڶd:xhܹT]]zPr8uVգ"(DAD |Aq>}Wܣ!ÇSUUQ:TYYI,m{( v~z1$aQܝ#B#G>Vvܹs񷦦v&C/)==֮]KG8tEjiiMpݴtR̤:EJKK)55˕`FQ1.bZ w\.ѣǤ~QΛ~M9<SSJJ dlEQff&-[n:siV~|8IKK{vsTTY_-m[mQpgre ͇$ܳgO3TPBDϯ^(/_6u6}Z۲(0 !!A^L2EŒqmˢP&111*^WbccM#""gϞն, ,,ƍ+G@#}éS-B Nj땩SJddqBmˣP@Ν;˴i$==]oz*..mˣPeر2sLINN #5uy @llx^IMMqVyDBf1(v Ǡ؍w(`AF '44T&M$FbKuueR(ڼ̔I&IhhqlrcPD@+81(v}C^B1(vP;Y /  dIOOwcPtyҾ\ ̙3%55U:wlzf"Сu[qKD$$33S8 O,WIEEdddHddI߾}eŊpI˖]."B5rHeU]]wUڮ~:ԩS8>\xn JII̞=[o^ϟo<'NlټC!8.{*gu|W^Qaaa߹;vT3׫ +YBqP>au[O9sF >{322R8pN8aձ1( |rYd~k׮cׯYȕk(FcP>,3gμ=>}Ν;;q'"\"ؘ8zjUVVf\rE?WxUZZjtqYulB>j*?? |޽ ARR4=ѣUǙB!X@yU[[k<]ii>}zĉ*++S(U˖-S%%%ϟy7UTTTGffjll4=RJXy)BHb5f̘f{+X:P!-Q~ijjR+WT7V2=̚5BtM-\P}'Ϗ(((PC i R[l1=wJHHABn`5ydLmQSS,XpS)С1=w|SNVN(B'>>3S}}aQ*UTTdGӹ/_yk&Jfw111c;-u^ "2zhȐ~Mtnvv̞=[JJJnӧرCwn`| ІƊT1=/^ٳg֭[oҥUPP`zBHJXXt~;|>~zޢ˗/ތfٳկ-nxԈ#\ӹ#FxN0Ri.r- !ihhPK.U!!!-ާ3gṭTk޽voBBB$%%E^$%%I@@!??_222-^Ư~+Ydx<Y5|xB{Q0bGRYYY]v-޷G⋦7ż^A.ytY͝;W>g9Ν;[}3]vjͦ7U `B!m RǏW٪ʑTZZZoDwAm߾JmmM=> t6mt8o˜9sUˉ2l0MqIihhe] `aaa2eIOO{8ˬY$''~ r]wi̬cǎٶ. pIJJ+)))bz$G|vZ?\r(;w=zh<;?EѫW/IKK4֭q\?L9p 8Prss](:zQWW~_k;cǎv!&wFZ(//OO?~1iڝ>}5#z%668s?]V|>>n: ԶLrX(((H%33SF'-;ȬY?׺g}V-[G4W~~!ӿ/K.j.]R=x /`z,w !fϞMG\7Hǩ]vjÆ 7rMMM*44ο $ @5J;} ';wz衇,9^!!!j۶m7N@Kq2c 3QccKh"־pٶm$&&j_9ruR(Mŋ%((8~㒑aMh͕X|'^pɋf䩧RW\1}%ïTWWyJn={sΙT :rM6>?ݻw޽{[z .\`zSmب:t`J9 ҥK?1FEE̜9SȲ$&&ʞ={$::ڲu8թS,u# p=zsolݺU~o!J)3n8͕pd}R(uJv|vJJJd„ 2i$xzu6 pɓ'ՔRꫯJ~$;;eee6\nGY;plz W:{ddd-_e˖ɳ>k^¤U;:vmzihhŋm)@Y~=e_Ǐ7Q&"%/,X pÇK||,\Ж-"[n'xuo_CG땦&ӣ8իWeܹ 'NezmY[:tb#!7ĉUYY9|wnҥ:vMw};47۷W<ڲe4}0TM>УGUTTdzLy<S# !-IHH4izwUuu|Me~뮻ԿoӛX&.(BZN:ӧ۷:K1cÆ Sw71@3jƌ?T/455+W #~zn|;tn_[g B{զM˗[u*,,T#F0M"wΞ=6Xq w[nQ&LPӟn  jҥ*$$6;UIIڶ ,B! U?ڶmޓΑ#G{- 3m4U__o<7z-)o'_|!1aΜ9rJxQM  ߗ(~-//@;;vH߾}MהRC~Ν;k׮G{gϞ/džhq)7=·P(Zm̘1M"##MfP(NPP,\P:vhz6Bw$))IV\)J) .\sΙ[(VWW'<󌤤HYYqߝP(4ΖA9i?Bg}&|r.YĩPx8$ˌ3F6o,QQQGW^pijj2=+ޡLnnɞ={L7<2.yXII9Ry|q=^PؠI-Z$#G/՜\(C`.]͛eԨQGqF jӣ|ץKdر`G8;v̩e""\`%KHRR_2=k8r%''(@uɣ>*/8F (dŊ(ŦqB)--5=uQ(СC2h 6=8݉a***d„ OK]]qé: #YF>)**2=#ZѣrwomzJKK7DpJy'w'" Xn 2DΜ9cz߿Bp'OeƍG޽{M,<+ɚ5k$44(nͱ5<;mڴI,'O4=P&"%/.v)=TVVG}ٸo[z-7=W^n P":tYFRiGD[(CM6رcP__/R]]ml \_?,3 ÇLDK^ J)Y| >\>3׿gש СC'ٶwݶO' Gyyܹs-_U(ZJ &~?\C@3K\\l~e˶TYY)?Od֬YR[[}v߯эBꫯJBB9sF2=*'OԶ<(hǏe˖-Z/kYi"!,^WUUU*..V۷7L9P6n(n%gyu}xB!ҡCzjw'o5\B!ݻڸqnl۶.u]K9Ovm"cǎx?~\֭['6lpO߄(<ЂBhAPZP(-( @ ЂBhAPZP(-( @ ЂBhAPZP(-( @ ЂBhAPZP(-( @ ЂBhAPZP(-( @ ЂBhAPZP(-( @ ЂBhAPZP(-( @ ЂBhAPZP(-( @ ЂBhAPZP(-( @ ЂBhAPZP(-( @ ЂBhAPZP(-( @ ЂBhAPZP(-( @ ЂBhAPZP(-( @ 4 h_4z D"nzUsX؇t^bIENDB`tdesktop-1.2.17/Telegram/Resources/icons/intro_plane_trace.png000066400000000000000000000051221326245125100244630ustar00rootroot00000000000000PNG  IHDRDw IDATx[HwPL7- vIJT0/ "uaDEԅT7^HPH@AQDR(hba9KrY4<_Qw>m^qrLF2U `(  `(  `(  `(  `(  `(  `(  `(  `(  `(  `( "! ,??_VTT$ ȯ=xF .8&{ǫV˗4fd2I ]biZMIII읐?c'Ngq$ Ӊ'{=Y0τFUWWBBBlAPVO333 IZ-EGG7CQPP~~@JKKiܹPF2 xϿӧO\.gcοhooxfh"{[a; RЬn`0Ѕ HP7Cz͑t:ںu+{/){)zp^(${ۨP((//@F#撗{3=-=Pjj*{/.v]\Zoqq1i1T*t]1 dM߁:r{J߿ou$===}vV5wmmm 6mJ9?ʕ+[=@Hsr boSLJ=zdu$555R{{MU*Mϟ'ƻ>>>#y&dN$.8uٳgb02#N>ﲻgϦJ#wZ=ϿW\?P@8O>L>>읂Lfz}1w9o;:zī~0@F#eeeI=B˗/Q{{;mڴ3tѢEb2>h4}v94[UUE!!!8K,V|bbbd7//i8C/_N?6ICC9@>/Eq###˗/&#)++#>/K]]]F(QQQiC{NN1_xsWyU۷oF288HdosIIId0իxin:ꢄ6'i/RR7rǏ #y-[͉@ C~0Oz`5..zzz&S?ϙ=JЄ #Ǐw1%fbb"|#(!l2C=ZZZ8Ăe199yHjjjĂepǎ40006Wgݽ{7o(n{h4c#w^&ܷo )66I²@>_2MOOIEE>hPLD><6k׮ݦ@'y12422i-{td4Q%{tN"H(Β=:гgћ7oh…=.,{tYYYF+{`JJ z-{tuuutQwQnFxx;mP@+  `(?+b%IENDB`tdesktop-1.2.17/Telegram/Resources/icons/intro_plane_trace@2x.png000066400000000000000000000137011326245125100250370ustar00rootroot00000000000000PNG  IHDRIDATx{lSzR8֡,]Tk:Hd\e# 321p Q JSh0 -==AJo~% Kj>RR t5.TR*IvťjUJM`i~R)W˿LZ0(- @ ЂAh`PZ0(- @ ЂAh`PZ0(- @ ЂAh`PZ0(- @ ЂAh`PZ0(- @ ЂAh`PZ0(- @ ЂAh`PZ0(- @ ЂAh`PZ0(- @ ЂAh`PZ0(- @ ЂAh`PZ0(- @ ЂAh`PZ0(- @ ЂAvպuԆ T\\1BN$%%*77W)Tff7ozOĮj/feeei|D1XR Q)//o7&TWW?lD1Btc&L .t:&״ȳ>kD1BtillvLna3~vA!RJɲe5&H~?rvn[֭[ҐܨLNCd0[RRl߾=11(:Yx#2P5P~}>jllu+`P(&LP*--M׮PsU XQ{Șl߾]31 پpwY/_..3޹nYv!9uL03H 7ׂo[Ҍ?#Q Š=Ե`'/nHcqmk/^TWwuleǤX=16rrrTQQk"֭[LΞ=vk(d"q-Jvvg#H(O.RׂKKK%==Y([bbbDoܸQ?bPȚEZpCC<ƟȢqmkǎS?яÇ}MiKĵw}W;14G^dQۏdɒ%Ɵ& Y#ׂO:%cǎ5\D6A.ׂw%Ɵf1(|-/ĦT&M]xQ͛7OizcPst_ ...Vyyya\FLy-XxcG x b"ׂkkkycGƋ\ZZ*Ɵa1(d6-!y뭷xcG"31(d.ׂ婧2LDk0C 5w\k8Pq Q8eL}5fȋ_?jnn˗[DPΖǤR&OlyM E']ׂ %--QlʪU&"0"Wbbl۶-13f"2"Sjj߿?1)))!C"6!a%!!Q@1(7ׂ%//QP1(/ׂ9"Ç7,Dt iҥa_ ޲eQH1(^:755ŋ? BZɓ'e̘1ƟŽAq-x׮]jYHK _FF<$>O^|Eؑ^1(\^ >O-[f"+z-ĉeDv.>>^}-cRSS#ӦM3LQA1]0ׂw)}5~f";/;w2&Ǐ3E)d^ |K/[D.))I>C-cK?ScPL"~S^pA~a%{={-ci&7LQA1Qׂ8 ~ٽ^z޽{'/1~z'QԻ[d߾}aI]]L>Af\ t̟?YPrrrPTי'NȈ#?hsu{-LFiDNo߾RRR;bP" sAXDQ*%%E:l޼M_Ad\ niig\ &RҰɊ+?KŠD@?^~?+S[/kLxwcP"Qzz;vo}ɠA) 8P9֘Pp3gΔ+W=&[n$wH JWׂo߾Hrrr//y=9{P:䥗^⛑pO<4775&2o-ƍ3~6"ISS,X?(.K^}vߌ{Sb>1x"{{P:W^Sb+V<$""tvwP:\__/FDWԘ|GlawmlDt_ABlgA;vlk;w-Tb_IKKrZ3gs\.keMgAZѣGF$\.w xL>c߿sSPY{P] O>FDWs\o<&7ox禠|-˯~+ނ(rݲiӦUVXarKNNs</ hL%;;)7(7_ >~9zGlИ9sFZ2k֬6ׂ+ED׋P;RRR" 2~fҒO?z}d֭d̤-k Jnnܹs/|ACr@c5(D%$$?n˒g" ={G}|2qD祈U D={Tjԩ]/R͜9S>}:J' n`M{V׿]v￟1q~EDAէO)..\k׮3LQp%''ߑfYpRTcP('rL2eREyIMMU{TTT3f( t+--Mv9&j񌉃1(4`UTTNΟ'+œ! uؠAѣ^dI1/QvmRVVӍb&^ސ!CTQQJOOO:f͚( P1tP't:&PƍcL[O>QC}]5eu… k(D$wyTVVvz aBċD䮻su8&MMM)EyF ՀYUUQ7p2X 8XVV*((Piiiرcj̙ꫯ2p2X/u={t8&wV&MbL4^C!rXcƌ_3y7$..r<Ӛ8q6*;I=R[[nLe֬YGA!rJ>׷3gHVVcPԩSݘ:tHl|d"7mڴdǎҫW/#ĠٹӧKSSS1yWv?*Ȯ͞=[\fHe…FA!cԩSlBdΝ+---mĉ2bg#[Ǡ٩k3&%%%2p@g#Ǡ٥'dǎdl";pBimmm3&k֬&E3-ZH~C"-2~.r\ [ti1ȑ1(DV^h3&gΜQF?96Ȋ\%}EcPیI~~1(DV*>>^˿׋1~."Tu`O?TW\QK.U6m2}$[.u_(L@9Unn*))1}F^RjR*ItEQ5/^Ah`PZ0(- @ ¥jU <~R):k$K?o̪$9lIENDB`tdesktop-1.2.17/Telegram/Resources/icons/intro_right.png000066400000000000000000000214311326245125100233240ustar00rootroot00000000000000PNG  IHDR`s IDATxyTWeQVVYȾ"JPLqߒ1&ĘLQGAq( 8E؟(Mo4֭{ 0AC B!!Bg Gpuu5d2 JQRRf!!nbcc1n8C,C(B Õ+Wp18pH$7>}J~~>{H$ndjjzic FMMZ1>M"9KLLl.RCC[j۷/ L(2|rv677CA[oaƍ000h7ߵkאJ뮨@DD_r<.DN...:yU1.\T`Bϸunݪv' $&&r^ S9? A!<'|Rꬩŋ*CCzJzz:/_ZN+((@NNZe`Bٽ{7>.kqI:7Dҝ 뵾4j(MH(###cx"qF%++ڪ.hGI&! fffAqq1كC[CC}455wU#G}=xwWkɒ%L*_&;w2;;;400`Vb2LKMM 2dmH]Y˖-cMMM~waEEE~.\hL%z뭷wcǎisgI1cͥYI$fffƬٌ3XAAǺgc~qƵMfҦ;J"uE}1kjjbqqqmsttdW^e1V__ςyYrrBs)((`ڴ'I*c?syǎ+.[؟X,f6l`ڵkH]QӧOg1¦On^Xnݺcl׮]5k+//KEEѪ^z "sRRR-'o!*]dرc^|EKگ< =C*  1|022BEE|{*ܸqz3 77GuAff&&OѣDz7Nձlƌۺ'O6m( ```e˖)L755رcfBuu5>|}Ө#;&/A(G(**Fe2=z333aرu>=zH鉗z ('Jy]ͅ6s_mQR疫+khhPxձwy2777믿*=钒'?oDz描8<<E%äί'n>|M23'yNN300`bXZ6tPޏ>h˖-ZԄR.]7nKaLk׮!33˗%cbbP:dggc NNNppphn333!99u'm PJUU.]Ԯܾ} ,@RRRϭ7xXt)6l؀;wEEEpvv=z(Ғtzakk KKKXXX&&&044u L֢ը{|eҊ={b2B%>ɓ'+LĨQ`ii&b 4xcgg 6ٳnw?@+++@"@" zB^`ee vwdAMM ݻwB*r 鉨(u`Cۖ*\1yjkk!`jjһF<@XX^vܝ ggg#88?zCCݾNh *QZZTDEEO<]\ ѣGcܸq+/q 8uR3g`Ŋ&X[[#../26U} PT_ yyy8u~7䠲; ,Y&Mj6{w I]G'NNLRSSʕ+ٰaúR Owlڵ/Եdee222:*++٩S؇~$ 355_W~ eXiii'փ؁ؼy󘯯/1s!///6'ك'Saa!_؜9sX>}xKJ%"5 HJJBqq^>>h 888ѣG(,,ĭ[SW_a޼y|%!_vիWtw͞=[ŀ/Sdhhvڥ˙L&E/Tp](++ck>Y^Աϸ}u{hojquuE޽._bӦM\F>.vW"M3[[[L,ޟ+VpGx p@ D"Qܕ+WpmTUUaՐdxt%,g}wz AA^^^ZmzS1###=bQQQK5fVWWm)hnnnj҂K.Ԅu֡"+VPH+j___|055;^0aeĉطo^{5 6 'N޽{9,--amm {{{b={-LMM!  ЀfS >}:7>|8 8<`aapV.D  66ʹ~Ghh(`bb.DDD ""hnndff$v.ĤIovUZ]6T%t ׷ʹ~ xG?gggDGGpqqQ!P(P(Dee%N<5 o6^}.1_ug`$,, }~}ܸqC"ٳwww=WOii)]I~pB̙3V'B%m>Kaff["??{Q\\,?1'X:t蹿gΜA}};H~/]2-066ưaLDBTTΝ$&&"??ǏQZZ*>--|5A{wi066'O,YRѣG4RJ011_,]Lhݦւxٳ;C=z4wwwYjjj  ܹs1btE 87o F0Zm}!;;pyUׯN OOO ϙ3g~ ^ylڴ b& 2 133CLLLi"(..vMܼy'Ncǎۼ#s…Yl֬Y##mOIC H\L&CQQ222C o~.Ο?V_~ѭLJ믿[o!2 @rrJo555III/r)))իW2Ǐȑ#u~+̰m6O]"iHqq1jkkΟ<XjJKKgxzz>g.R `4D `޼yXf 9?((/^ާ166FBB&Mv x{{#&&QQQ /uW\AddN׀122֭[1c Ad0һwo 88q"b1\]]ծgǎ={6>Ś5k !vvv;зo_Wisr"fΜ;v~xLѣG &&=Z+**Bhh(uǐ!CpAXZZ~x2@ܻw7oÇuҦ?E\CI033Ccc|;144DBB=Bt(d0݄_v; A HKKvv^̅9C ARRߡ1W&s!x F7n5 #` tH$ҥKҡ5 DXr%rss;2n/љ !!!xj b,YDWatKB!.\wbcׯ_1 uuu\ҭ S7\\\5gqww+rrrZ}SSNmjWW_U; ]xyya̙ j>`^{5L0~~~077ˑq kkkL20BSG;߸qcd2e AhC}z Pf?~ .?֯_?v666F~舞={`>Dee%JJJp 梬 UUUE=n8N!.P`RSS1~xN@ //ܩ  Cpp0aggQ]wŋq̙3OxbkT tZChVSSPjڴi 355e'Nh_~abb† ¾;v51hlldyyyl,<<ȑ#YKK qg^^ܹn1둑*-(("&&&x7d,XZMl+^^^XhRRRpL6 *Ri#8(6rdeeq?.]BKKӱ~zp^ Çm6aԩ|Dϡd2dddhPWcc#JKK %%|2JJJyN> `VZ?Kbcc>0B!kMIIGTЀ۷oիHII+WTf~kkk899F!jr+֯_~x &??pww.׮]Cvv6.],䠲 X[[C"74h<<<УGVy C]6l؀EiUA#++9IOOǒ%KpUTVVY8p  ooo+ ggg\~]Ӱ_| Atq IDATL&Cff&?ٳgPF@@BBB HmUXke0-￯qy TG322,l~׮]8~8,,,0A"o߾jFi&جQÇǺu4*Khe0YYY(..+n޼uց10߿_-C͛7L"88|ahhUUӧoNDpuuڵkqMkPVVs2wիWT䠨UUU<8z(}]̜9fff U~-yg 1_Ο?LMMzm%'' 6ɓ'3DD"1:u1i7h7udd$̙3> QQQ塰ennwyeeeعsJez쉴6o[6>011ACCJy?s|G: *nnnHKKC޽ %]57o Aގ`푞GGGC!nގ`^y24H'At{`:咗K=z4-I4ھ :zg0; #H  xzzd[W G |@+ At"`LLLwA2@@/щ?IENDB`tdesktop-1.2.17/Telegram/Resources/icons/intro_right@2x.png000066400000000000000000000416331326245125100237040ustar00rootroot00000000000000PNG  IHDR0@;F3 IDATxwXTW? Ƃ1j,hXM&1&Iܴu_4kVS%6,Q5hQ uW6>sgxsG{@Ȇd'@DDDT],`氀!"""l """9,`氀!"""c';"""GGGΝ;={HOO]ձ!"" ^x ((,,[}n޼s!99B||<?|IWL0 YѳgOea[琉`0 "00P,_\]Qtt>| ٸpZ ݻ@LL 0DDD5-[0zhUPP)S`Æ V,,`l1bi4޽{͛V`CDDdhKF{.5j\˅숈lPhh(RSS/鉥KB[_=XG"""2'.]$;b:tN'G`l̊+dP믿X?0DDD6BQL6 CJ)zk֬#/2>}ٙ5" ~#i;:m۪faa!qehBvhʔ)_e effm>kh4… i MCFF\]]eR%999@QQE  (**œ9s);*~:\\\,> """tR|DzӨ@deeY}0DDD6dٲeF.\h9t%sթSk#0DDD6&..˖-Fg>X -υINNG!""A)))x嗑!;R,ٰp^NNNS)燛7oZٰ͛7kj>̉',^,`lއ~Zv`}7n@BB8hY˖-m6[SNSNV`0 Q_,]TܻwOT$==]lݺUtIzZ ///1gΜ ϟZx k?'`0ѱcGwߙC_~:tb [nU)GcJFPPػwUYll%2 kCuرߓbĈ&NhIDD$]PP^Z}sss\}G;wZӹsgU+((@FZUu`H*WWW~=̛7CѤI 2cMC^T[pn׮]V/^>`0K{ib۶mBӕ{Nڵ'O,7o gggUkTs+=? ѯ_?QTTTꇢ`k֬TG{b{\,@`Ծpqq111e`\|Im8pϟ/j5\]]c=&nܸQh49s]c0 F틐a0J`LJJQEѹsga4K*j=w.233\9KDDR<3J3gOS÷~[jZjeR1i$U:O?^? c0 F ___qR߼y춛7o^3o[I&rG_ E d)D1 vEEAAAӦM3mEQĵkJ-iXQF7oYlٲEz~DDDVץKۗھm6Uڿ~zmBzUگ 裏W_K6ydIY ""(//W?p p=B %%͛`0~mr_œ9sݹsJ42!""5m/FNN~G!p%/A\꘽{ҷNCQQQ,^L>,كNX 9o;"?j9ܾ}T7nz^a!""<3x7L:vԨQhݺ94m/aWQ &٥KK/$\]]oQLѻwo爡jHO`05 JRX1|*qR 爡^gX ; @~~>,YR&N͛~idddX,?>0DD!CX<8<HJJ/u"00c{F֣"""yzz"==]ves SNNT""2ۻ+;rt:899NT""2[nnM W\o-[Pv:d0DTk4m!!!񁝝ܹ . !!FQvz6MQb0gkbI|2[ !ƌ#>2N;wĒ%KDVl1b>mIqqqAc`0 UBӉ]7xC9rDU`|SbKe 7{laZٴcРA2d TՕCڵk*dX |g֭CDD4v "^nݺaȐ! CΝө{Ce۶mqa몶]N,>>>S !"EXX0p@xzzZπDEEgϞ(**xjܸqS0 T4EKHHEz]ts=EYo[1c )믿s`H0x`apww ##Ζ&p=mqpp@6mТE 4mAAAWq(" }AXX `)XtT4C6ݻ]vYYYѣ-2E2N֭[#44{FΝѢE U<~+ѬY31k,k.#*o;-Ƴ>9q>^{}Ѹq ڵEsX[MDFF]vU>?~S fE:uēO>)[ 1b޼y޽{QiEc-EfT9OTۥÚz^;V?~\Ϫa0n;bشiZliվY,XvNj ";Mx'TkO>UWr۶mSO>ɓ'ѷo_iyHzb0ZEEEV2-;;[8::J?#..NZt:ؿcɪ|`0l3>S_R 8Pj3 gϰ0QXXJչprr-FQ\}B 텽ȐU! _ .~eF``F:u  !6lPxzzzzsh`j᳐:t蠙-Ynn.p#$$AAApppP0̝;Wlɓ-ֶ(X|9Pn]kZk׮Ub̘1ꫯ?-^1 ۊ'xB/_ezUԯ__;V9sFՑ@韁tjQ6mH?wyG3w!Qy{{N"Xn޽aX|9rrrT6NݢE )S Xz5ϟo' UCTTs`ӦMHHH(j=r5)777DGG#""Bv*bCDvM)( |_Ν;U+`{{{Uڲ%4Lj*)h^z~NR,`N:%;R[k{UmeK.^GGGilS4www۷=zJ!jKJJ7dQ^ǘ1c}͛(((P8FQ|G0Izz:dYu~[G;LNEQOV.!B`+^bݺuS,l۶ ݻwJ!"DGGNz*z}'''СTkVࡇ?O٩TҥKeI૯Beb3 ۋz,) j'33SI("?,.\R3e韃hڴ׿%233U?j} ,ј OT"ڴiر3f0{~M:tH|W"66VmذAg߿ضmEη-Ƙ1c4H]U@ d0o_B;w...%mӦHLL4]( C )L"OOOz^'gΜ1cTo-\h-5?jV> vK.f} ѽ{20`W髮4(bbbD߾}Ehh2ٳV"00P޽{DפI]K쬹3O$t֭[&__2"::Zů222DӦMԩɩv;۷ؼy>ݒdOŋ-~έw!ɌF#bccM> >SUo}~h4_~mn2iZrCQz̜9OVsΩڞ ?/; H ƤIL *//Otڵܶ6l(ŕwjҥ ׯ/si(}]'`?ߋVM/fO-`0l;ׯ_dF[(Rf"77W/dRzjo 4iϡG޽ƍܾ}[K]+h"}kdPaqqn߾-ZlYf[VubРA% &EQرcEVV^ZgPbԩ"..駟Vcǎ&U}R omё#Gw6Œ%KT_Ǐ/5coo/t"gr۷n:NP[_bb&L_ > #44/x1vX(Zn--ZhW^}-x~ܹztY@);-ExxO,v`aLQXXhv{EEE"66VhѢ5___Ѻuz^̙3Gܿߤϟ/s`0z^\xKZۨl{5Nunݺ3L;9r$._\vf͚a͚5x c۩I"""вeKiX `~L2E/dK-RO?$ƍbҤIիWOݻפyρx:N\rE^"REllE,| Þ={`0_9r$mۆ`/̙???(R5U1p@ ѬY3iX "Rō7p+@^^ۇ1cGbbb"/_FoW[F6m( F#޽kr!H%gϖEwbbbzB!( Cqq58p˗/Ǐ?cEAݺuѤI/ĉTmt:z}߫kȐ!A"u =zE!"`޼y%@~~>ܹ$/ݻwq ᧟~B^^^EQ|\p燾}ѱx|78s pqqA˖-(I9iӦ%D$SO=%;Sd""#-- f|8{,PXX1qttD&Mpʕ90< v gggܹsǏƍ!zUV!((ǎ͛7|<ԩ7nMv*"RMaa!ߏ#GεkرcU( իÍ7/"--~:յx&;;ŷo9 64+ws'2ѣk|!"EGG]9/e]tpttD еkWDDD 44^^^PYYY|F@QQ233(Jqzxyy!22ptt4+wm:R`PP|||//GˎBddd222qmܾ}ɸz*RSSkܸquDD /bV޽{zj$''@Ϟ=ѦM"vr*#:w^zgϞh߾=6mjvj B\zgΜѣGqaŕ&MkZ9K9X.\9sN!FcN+L{[Kd}A^еkui0??'OġCpA|Y30DbϞ=Ӱ> =4jVZaذa C޽\BLL Ν )Y "R#ӋX o.\Jcܸqhڴtx.??c=&;Yd +h׮ƏcǢE!XEDGGךܹs&;'N̙3ѹsgFYC=DiX\JJ IDAT zׯNٳ'z);V]aCDse4o\vs1=7oޔJaoocԩtHL{*QDGGN"222׿<ԭ[sERRV^*"{ |4Tb֭X~=dT#ԫWsŬYPn] aCD쌻wIv*&ILLDLL qܿ_vJ5Fݺu1{l̞=짗SĻbrssqA 4Hv*Ur}8pш f+OOO cCDuVM0/^,.X<<)H`„  ;x , ׮]Tػw/bbb۟GѣG٩P ,/ >)} !pȑ#(**KmӠA|G8qM}IIIӣrbŘ6mTbC6NNN7n9NV ƦMTDd*KSbĈ߿?kJoXnVXcǎIΔ &;8CW_}ov7 شi/_={X8;Ca7o#M`CEaƌf 6mR)3 ֭eBT i֜9sAv:+V`ƍTm ;";Xf8q]vx?gϞʕ+vZܺuي:` Q),`HOԩSԩG֭[ iNvDewǎ."Fݻwx!McC4qDXRs aʔ)ظqsψ4iҤIRwuuŊ+O?4/_^gT90I֜r9ԯ_r;v,lb\d5k-Z5^fKkIHH@ӦMQXXXb(ϸ(xӧOgB6 iΰaìӧǏz~XdU! &`ٲe,^氀!9r****~?RL<zZND2b\s^&!ͱf_~p],],X/yQM6n#Y,`HS\]]Z(?^{ w)_߾}i,]vغu+i,`HSc>\jۘ1cwN4hݻw]v*DfaC2}te4Pj1sLf`hԨTu`HS6 99YbHMMBDtˆ#dB fz^OII7}9/ĉ "{7YP4cVرc((((]vo)=%S#RհaoNHUD駟矷J_FÆ Cttt5i/_]yyyhٲ%RRR,&DAAA///x{{Ճ\]]nnnGaa!999((({pܹsHOOGrrrq`0|Ś5k'Or.8,`H3ʚsb)7oģ>~}?C+:t?%#zh!!!Сڶm`jy"%%/^DBBΜ9ӧOV-l\\\pQoj}Y ;;R"`^Bz / -&Unݺݻ7zPtEϮɓ'q>|.sg|1c' iBn||7o^o۶-Μ9䴱˗/}W;E*^Gn0dSN6B`@||\ӗ,%;;;wƍ*g<ΐH>a1FQj%u֭[W`(u͛Úڵ-ŋvUz_~eYG`H6mڔLYYYX~=VXcǎoW֭Ä J/ ((Y ...4ifΜݻNG?/֭KCٳpqquIQ'TAAX~0`hРAϟ/|߯_{Z(s='Ne֬Yc~l ,XK.ᩧbBD IuElٲZTr=ƏiӦ:*BCCqQ۵U`„ x(;"XTBT߉'Tv6mFev[?mڴҥKѫW/٩Q KH$Oٶm*}ܹG(P<VXQgeggspp?dzx!" Ik߯1iiiHHHqQ??sGrWVyիW#22RmUhh( l e$ijS~}{Ase8wPPP`RN3f̨;w4o^{5~"4&4jQFF\ӧOСC8s SSSSWjf͚aڵ֭TbCR١ )) gΜCp)\rUFmʕdNŋs"HI&YWWWo۷ɓQPP$$$$ÈåKV8pe(GGG|xgeBD},^xyyݻ޽{c˖-&NDd)\ѱǬXϹXbyu\Fm;x0abccY氀!:thYzuGEEaĈͣ>uYl޽{-ҮXn]LDnĈ>ʕ+߾}EAgy)j/z˖-û[ko'"j=_HL<5HOOW5'-pppU0~x٩U YUuZǏ#<<ׯ_PV՛Tl4kʳNNNظq#.;"JY?\cwk׮Ak׮wEppj9թSZǫַVoaBD6 Y:QFxO?A{aɒ%f-i?a„ja"'''ر>TȪ*,,_+WVNcǎ{Un25EMqpp͛9BD6 Yofgܹ˗/GTTTjmSSXnƍ';"j%$^Ǐ[Çc֭B@SN3$ս[/^ⅈl O?Tvӧ/_W˖-PV5o<>l/!Uyxx -- vvѶm0KDDV^]c.QnĈؾ}4L=j%z¾} Yݎ;0rHdggNZmxiԨnⅈj0$QN4io&eTUVNdظq#*MD5/!x{{cҤI2e :u$;ly˒%K8ij0iNNN0aL}JVDFFbʕ "R 9F´iӬzlzz:֟Z5kSNY}@""K9QQQ1bE(xGfXbm[=֬Yⅈj$Pӭ[7DFF"""ٯ_?8p@e&; ""`C5^pp0"##1yd4jԨ޽#F`@vѳgOS!"0T'@dd$Zn]YYYhݺ5RSS_{#"e,`1}tt (,,Ě5kpBdffNZ4, џz899!''Gv*֦Ms]"XСC 6jbĉ,^ Q K.t- Q kx!Z#0D6. /^T#0D6;"u8CdZlNȪ8Cdx /DT+qFh.\󎈨V ;w."8Cd||| ggg٩I" /x!Z#0D6))) 4!1&MbBD^,`5kg}x *ٜzJv DDՊSNO!\?ٳy'ٌۣ[n "F?ӦMøqT>N:x饗sN\xYUߓO>);""M1c <>mڴaC興ii]Brpp@dd$;! ;L*^ U1C"2dի'; ""M'&NӧSNC0 iH ƍfhn@L<SNEfͬgjj*DGT]...udBD G`ڶm)S "" 6CÆ \999̊ⅈ>&44˖-CVV8wΝ+xNݻWO`` ҥKBҥKXp!<<<%fFXҨQuVkteh 6 F-[` /O?tzTt:Sv*DDaM/Fھ{ 5kR"tڕ џXeK/dnVZ!%%;wVx\˖--V-Z ((AAA wqԭ[{{{";;;wGJJ D\zBhjz :Tv DDc~F7fCΝߖ~JÍ7,ZԸqc<ѣ:tvݽZmR^=4nܸ}srr'Oرc8ty V3x`)iU4o/_t7f۶mƌSj̩SRqS9OOO 8aaa8p eT"&&{JqwwGzz:ED'Vk /_F߾}q_y|.]X:=p;za#G`ƍؼy3~W){1|7 "0n݂52Y^^W{:="1j(̘1tR/DTT_?VYՕɜСCR++(JFMƍpBb0`M/1`_Xpak,j+0WVWf޽;E)Uzn4SNX~=1gxyyNIU^^^3g~z=,zݺuZDDjL||2K۶m\scƍ7}tKI;vĎ; &NS),&L@\\v܉;Zf͚DDZ#@FF3Y&M\믿^zY*%Mi֬6mڄZ;4l0a}hv,6M6Y;ٳggggggK%{=?e^fME1cpy|)aCDT>06lfw& -s[*Tj'|G5N ""A^޽]M6e\RDDRaÆDeRo?# VXUL^^ 6RO˗Wxѣ-żKضm<<Vt:|GXxquYAH)`L||<"##`2_/***={Z"5U988`Æ 5kTjYfaÆ S~}+eDDd07om8)SsXv-({V NSUغu+eR+#**•kC1&ʒ-`9r$E(ׯVZU}W>~xsӴر=TjCbΝfbV_GG#//7oƗ_~vE#44 DEEaСS{n5 %ϜKDTiUtgSܽ{-BΝ( 1ydUDFFb/cvZ/3tP[^S#"-Tpʤ`ʕXjUw8q"ON:Uwyo9cɒ%xgeAs=##q0@a,!!+Wի.~,00'OFdd$Zhav{III&j />ciP%̙>'駟\~ǝ;wTm۶2e """T{ziaСCcb qڵ=Qm#Ƀׯ~f/Xl!s0w\/иqc]͚5+s~iӃyJZ#"2̪U*';;܀ ƍa0 ÇOՔTM2fg...زe QwwwlٲEvDD'P<'xk׮pNqaڴi4hٳС?s̜9SJDDD&IMM_ݻ,nݺ?~>>HKKpV燳gr%W""Ѥ΁5k֔%=z)pAL6 ...P;vD^0sLܼyK`#FR[x1""(Xb۽{[jyu(,,ٵkKDEEU3bsa[5zhN%"ZAzw)1jo};_~ _cطoIm899aذaxХKR \GfJIDAT[?S󉈈j闐`ƍޱcG JHHFFk׮PS=)$$xhFm۶Y;v`iHXذΝ;ɓ "":Ḿ!ӼꫲS ""#06*00IIIVYcHk8cfϞⅈj- GGGGDDTkq= j,`le@DD$/!___BcIDD ژ1cưx!"Z? m̸qd@DD$/!OOOܾ}z^v*DDDRqƆx!"" 2h )i/!ِTNH:؈͛x!""?,`lD^d@DD,`lD׮]e@DD,`lDe@DDk#222.; """M hܸ1""?`cZh!;"""Mac6m*;"""Macd@DD),`l@@@4 S ""06Kv DDDԫWOv DDDl ";"""M᳐l#06Pv DDD.@RIENDB`tdesktop-1.2.17/Telegram/Resources/icons/mac_tray_icon.png000066400000000000000000000006741326245125100236110ustar00rootroot00000000000000PNG  IHDRĴl;IDAT80Kߠ 8 N(N>C7( At)&\|$i (& c'W !H)EC)H\W c0CEA1?0眴o!{pYVeYgY:yo,27XJ].nEzݛRa05yN8jUU? Ciϴ(ksz<8My:\.1]K;9[ADt:(IގkmūR][ө3 ׊~o=Rl6je{+PT׵uGMPt݌mAO_WZkmd`(PUUu ܖ9jCD_=<šIENDB`tdesktop-1.2.17/Telegram/Resources/icons/mac_tray_icon@2x.png000066400000000000000000000015271326245125100241610ustar00rootroot00000000000000PNG  IHDR,,ZIDATXg3Aǿ} !T%%k5S ae)K{?lm=SN9*9TzV警R[Ooٝ 2g$3<Ϯ 4~܄g\]],@id6cx<&۶I5I8Qr4M`4I6L,1Ff,u1/4r 4HU~a~>i(;=9e%8運dBZ,u| .J%vv].!ɲ/9z4|>^GF#<xxXc:sΥ tzzJVEYD>lf3pHaP\Nz`0Hl /+JX7 8>>jVPrv+ǩT*Kgam0dHP booa$^R`kk+}C(MCM8...kkQc5"`4DL&R58, h4,8"7ٙ#]pοnCIVKGQK<}j.}(uxͷ,vp8|!^-l<>Et:TV2)R%".;?5¨I,jGZ5Sϡ4,rsc.Z4V0kc6T%*kJ*݋8}88|>`cL0Ͻ_ӖU" YklHLƩgZ"e9͇"@( ۇ%a* ڳ3{*,iG"ubC3Wh6'5&ߥ~#' ׆=!L$XEN6 fq*xNH~瞵dαH_bcm4 Rl"*X4 \2( v5"~ Dфh1!$530͉M EF"5AJ:}ȓ-5DUHEHjB phK+9ː0ѼNƸX@h}AGktNZ9KCUݒb\ ӄFyi$jfId@L?Ri N16IENDB`tdesktop-1.2.17/Telegram/Resources/icons/mac_window_shadow_top_left@2x.png000066400000000000000000000036731326245125100267460ustar00rootroot00000000000000PNG  IHDRƈIDATxr8E '_2b<ѹm{UuA[+hqtTJSIfn!rnt:e7 Ȥ"&sjPm(1 APRDDg=؛Z!bGmOQ[DIZ x(!]D+|nm_"b"E*dӵ"CB#.k iB|I^ǐUVqGѽŊdkK? kO [5/q<8#u074[l\ QD:ᨭ_ jS-=1C,tqHH#j熒U4Jrp~mKOqtoZh[yhزHIGYtBH`v˦eK670N+P0%4 H9c#vuxTEDDZIZn)lFDNnVosYh٬1_)֞H5#@ǵDsԪy '5$r "k%VKM#m@Q:֔a9wٲ8Q;48҇歡vZZ+ +ړ|(jMDHhע#~FѬ)`,"NhK׹Ebrzf1\МChDP knh$ѱ-e9FΥ55;-9<ϧO I H?YF]NEDs<[>ď4je#1=o'r.~{xPN#b&EsAJkZ?9G#}lғJ'jORj[J|D`=)^<]5T)2"a%݉HP"2K)\RD]> ܟ%ǤH E$R/]qZp!"!pGD!]DQ(?9IENDB`tdesktop-1.2.17/Telegram/Resources/icons/media_fullscreen_from.png000066400000000000000000000003371326245125100253220ustar00rootroot00000000000000PNG  IHDR*ԠIDATHA ?slr\NI-f>J`DFlſSDp{-<ϡO1Ut_fx1Dg7}_.].J+^GRtې+?~Y2x :U'Q{)<ϡPJ_E3mؽrIENDB`tdesktop-1.2.17/Telegram/Resources/icons/media_fullscreen_to.png000066400000000000000000000003411326245125100247740ustar00rootroot00000000000000PNG  IHDR*ԠIDATH 0D/݁LNןFnc8tQW摻!ZD8u8jͬZ|NïR+p0瘺{~Q>Bof 3KSDf{j1<ǒK$b(Xrl2̾P1IENDB`tdesktop-1.2.17/Telegram/Resources/icons/media_fullscreen_to@2x.png000066400000000000000000000006141326245125100253510ustar00rootroot00000000000000PNG  IHDR..W+7SIDAThQ0IƓ\\ NEM͌1h3<D$nKTs0hm 3cEc4MN IXp).*xm/EOd/Ev5x)tGnjsO9(ßE4=/DZvuf@DWyrs9B_8cuKOyf5{45𛢯㕡p# nýh|yYS/rp t]1F8g)RJ03{aĠIqIENDB`tdesktop-1.2.17/Telegram/Resources/icons/media_pause.png000066400000000000000000000346741326245125100232650ustar00rootroot00000000000000PNG  IHDRkI pHYs  8iTXtXML:com.adobe.xmp Adobe Photoshop CC 2015.5 (Macintosh) 2016-07-22T17:24:01+01:00 2016-08-03T13:02:05+01:00 2016-08-03T13:02:05+01:00 image/png 3 xmp.did:092C2F674AF511E6B460E4FF16857D44 xmp.iid:297deca2-13d9-4f6e-8b2b-0659dcdd0d51 xmp.did:297deca2-13d9-4f6e-8b2b-0659dcdd0d51 xmp.did:297deca2-13d9-4f6e-8b2b-0659dcdd0d51 created xmp.iid:297deca2-13d9-4f6e-8b2b-0659dcdd0d51 2016-07-22T17:24:01+01:00 Adobe Photoshop CC 2015.5 (Macintosh) 1 720000/10000 720000/10000 2 65535 17 20 ms cHRMz%u0`:o_FEIDATx쌱 0ÒUt/uR2$EM.m $78F6|DD93'BSB?IENDB`tdesktop-1.2.17/Telegram/Resources/icons/media_pause@2x.png000066400000000000000000000347621326245125100236350ustar00rootroot00000000000000PNG  IHDR"(( pHYs  8iTXtXML:com.adobe.xmp Adobe Photoshop CC 2015.5 (Macintosh) 2016-07-22T17:24:01+01:00 2016-08-03T13:01:43+01:00 2016-08-03T13:01:43+01:00 image/png 3 xmp.did:3343DD104AF911E6B460E4FF16857D44 xmp.iid:eba9fd4a-b50d-4428-b790-68e6181b82e2 xmp.did:eba9fd4a-b50d-4428-b790-68e6181b82e2 xmp.did:eba9fd4a-b50d-4428-b790-68e6181b82e2 created xmp.iid:eba9fd4a-b50d-4428-b790-68e6181b82e2 2016-07-22T17:24:01+01:00 Adobe Photoshop CC 2015.5 (Macintosh) 1 720000/10000 720000/10000 2 65535 34 40 8? cHRMz%u0`:o_F{IDATx10CQAnU[Xo]d]fzN .IAZp.I @ @yA]>紻݈PU}^ފUq{Z:c~ĨIENDB`tdesktop-1.2.17/Telegram/Resources/icons/media_play.png000066400000000000000000000005211326245125100230750ustar00rootroot00000000000000PNG  IHDR&hwBIDAT81@g XB)`-x bo %e!` !O|ū^܏ٟ\nKSJvHBbx>Oif(W4 t\=RBr6X;SkL.t 4IENDB`tdesktop-1.2.17/Telegram/Resources/icons/media_play@2x.png000066400000000000000000000005751326245125100234600ustar00rootroot00000000000000PNG  IHDR",MjDIDATX1E@v$n~Tj""h$6c Qd= 3sKNs~y$ DQDeYR$VC{,ao67M,uBuBXudBXtdC< 1&ĈJVՐc:BE!^^ 9&c:9vpEQ!Ks}HUUv8Hm[A`T UR0"`*䱂W :Ct(+hSP Ѫ&Ĉc !dBXcap<4 8wBкds?ai{IENDB`tdesktop-1.2.17/Telegram/Resources/icons/media_video_play.png000066400000000000000000000006431326245125100242700ustar00rootroot00000000000000PNG  IHDR$$jIDATX1@XXH!)D,2)Ry #XSZ )SxI!1^61n, jg>BM MSL&2]bJݎ,˒Dz t:a\0 w[F{!x}}j2v;d2AB[,FRFRl6RӶ>@t1y/ߖZ@<˲0 x<~)\@Oog۶p[+yc \Sj|>==>,͂/减 8NPV!Nx?4??sT*0-t@ B]Wh4J` 4_~=Z-K(O[~Jwmۘ̆t  `HM qJ(Gu<>H5]  `(AMZ0:@3Dl@G h~HE.9RA_]OwVh^ 2@ $ 2@D {fIENDB`tdesktop-1.2.17/Telegram/Resources/icons/media_video_play_bg.png000066400000000000000000000016031326245125100247350ustar00rootroot00000000000000PNG  IHDR<<:rJIDATh1kNǿ=28t)Cp8Y}E^|AB;*I4Czw9TU(e$a:BEAY(yc"MSq,;thXub00L~;UU!"l6kt% Բ,yvM]4ú0 y{0 3iڟ0{$ d56 8Ѷmޚm3lU$I.-N6[kga˲,mkorh[a4a\B7*Y>em37z/B]קe'41yt>1LX,<0n>o7o& TUQf("2o̐eD$:!It:孃DE:!"ڶn 4 o h,K:Q%HQu0( }y7{OOO{Qe+ xSv~jp`=r8Zߴu)BC+yZ0c 0*=<5ޝkz)>S ~9>s<19z?FiIENDB`tdesktop-1.2.17/Telegram/Resources/icons/media_video_play_bg@2x.png000066400000000000000000000050371326245125100253140ustar00rootroot00000000000000PNG  IHDRxx9d6 IDATxݿK:o爆#>QRB7DE! "?"A#2lni̐JОPF~s>Z >Ի;&p8p8`ՅNtttjB$6L&;iH$㌟Y ZۋP}\.s8::B:8fk!20<< $a"NNNDt:1::(v֑#d+xrr㘘fc,DBa""#|>OU>'9""q}{pBTU%Pu7ԅB!*X,`C L =˲L~?fǾn,D[$Idyyb1L,#DU($߆(_ D4FYiÉFD4"Bslۉc= \\.:knb1k:aגkňY%d5d2I^ociIRHRDӴ(rMJhmDdgruwt|bXkוrM:2 `$W;U‚ G|>_5s/iU$L`ii9,--AQ/`Iqttwwcqq$|Y/,,`aae-Xek~~,ܷtR t:1;;[ֲy<_cf+b`ffK񴶶bffe>-`jjʔ`=SSS_,XELOOkzz([ɂn7L 366]o% 8hkrr<~-z||P5 A_q:[(RE6&pl6VTH]q÷+kL[,7Oqkoo/ࡡ!&8w)/?8<<,G0V+ׂ< Gn ׂ+t*@__0}NeA([r/ZZZp8 fAb3IWWN98tvvB`3IGG”XV5I |?-DQ:g6E3=??w8׼'8<==Ax||d3#L&:gL&u$www|\4[98Ba3 k98\__CH$sp&I$8r,e\xsy8_ 3y8 pzz4 G_S+t*蹺ׂ49<loo%G6vvv>^r`0hz :+YL ѳ+Yp&榩8z677%ϘeXH0d}*`X,| c666r_'>k?gKܷG"Q ѳVW}KDw$U  $}y^R[]]E%d ...r -'0VVVѱReAck|>"B}^*Zv:Nv{V^0raaq\Ք[]&XsbqՖ[}$L+LUUk)KRqRz[{idR4Ft .u$W.݂dUX,F3׼k U90j][Ov2 )lֿ`eƋOk~U;CŶ`eZ4FYcÉFDӴjEQ};@(b׭`e_r_7U,l6z&OdYX3)pxϺy:#(2WOF199qLLLYmH$P(TNӉQ@QTpo؃!20<< $a"Hѯ4(=ՊA AKK r8??NOOq||#4ILRl6ىXVHQֆV/G=>>"t588gVIENDB`tdesktop-1.2.17/Telegram/Resources/icons/media_volume.png000066400000000000000000000002241326245125100234370ustar00rootroot00000000000000PNG  IHDR, (_7[IDATHձ EKvƪ; h4kf^*p&n6`;VfG`=PB`:"ҁC<ݤjUIENDB`tdesktop-1.2.17/Telegram/Resources/icons/media_volume@2x.png000066400000000000000000000003211326245125100240070ustar00rootroot00000000000000PNG  IHDRX~:XIDAThA DQhM:"߲!/PJX;>Q Ej0Q~-1QGc{D- x%L+1QӀ#b3& >.L~%bv^ z68M;>8NJK;IENDB`tdesktop-1.2.17/Telegram/Resources/icons/media_youtube_play.png000066400000000000000000000004361326245125100246560ustar00rootroot00000000000000PNG  IHDRIDATHՖ1 0q$<n@ppvq CI)-A Cx%bckZ-g]WEӺ7kc&dY0 c;BcYy8qD<pOu#plۆ,Gh"ZTcd1]88Q.I߇<Ϝ=84M!"-d5|s IENDB`tdesktop-1.2.17/Telegram/Resources/icons/media_youtube_play@2x.png000066400000000000000000000010221326245125100252200ustar00rootroot00000000000000PNG  IHDR04̓IDATh홯P!yEͲ"fA"Xlf bbX6,1dhr:_qGJAtH 03Ap8|>c<#EO}N%} ۶QTx@Jr|>)tS@l6yrW(l6a^]9^ (5Jp͏d$v% (-*p-KV " CF#d2NyhZ ˲`&p͏|[Y!~WJYfrULB΅N q;RbXp&1osrrk4 xca5ҵ[R+)%טi i"Iq'F#A'ExCIENDB`tdesktop-1.2.17/Telegram/Resources/icons/media_youtube_play_bg@2x.png000066400000000000000000000033031326245125100256740ustar00rootroot00000000000000PNG  IHDRdiEIDATxKn!6Ĭs9^҃†O:w/cކ/a8PDJeɎʠL+f>fΚfADy @A4\~BUU]סj8O<{{{AMӧO( P&B!mEAo]ŖFv+ܻwO?&Ç1@x\BT*all R2M400- t> }`ybdxܬݹZ^^F"haD;YLOOw( iΞ.t]($ ;wpppPְZXX`x)]ױ0J$ m FVC*BXpj477Ѕ$I\} D"+/jay&l`xecbb&l||<Ђ(|N2@|gRqmtàqd2tRdPuPH Ay:D)]T4tOˈD"TU  )UU!ɲtR,Cjv+E~肷p7$A4-:(4MװW(SˆZ>yi;I uww]Tww70N4vƓgJȲ&a|a^'"? #_ "_ "_y% ˲ j렐r]M/ /8A@!e6$۶Bq6aO$giBVAA!UVy&jl;4ى&xO9weA,+:(lۆnuPH90렐2 ȻZ=yg@뺐*JuPHU*^ Ld;4!}%:( ]TP@q|| ~?.0 \vo޼  Hh1>' g uq-{r뢐X__G\p/^Vt۷ & -ܿ>pS}^'l6t:Gm/VEQijgDZoVBQ2khyA@qRT*u,x 0<<,VVVqN1<<)ąD"]M`+!rU/M'ehh_!j{D63MG'"{u]+<>^k >j>k >;j>:}r!}p.}n&}l*}j I_n Rr ߨg dIENDB`tdesktop-1.2.17/Telegram/Resources/icons/mediaview_more.png000066400000000000000000000002221326245125100237630ustar00rootroot00000000000000PNG  IHDR$~YIDATα 0Q +4L HL:؎ *zs9qoLUeJk-o$ I I ?^ٞ9IENDB`tdesktop-1.2.17/Telegram/Resources/icons/mediaview_more@2x.png000066400000000000000000000003171326245125100243420ustar00rootroot00000000000000PNG  IHDR *ʋIDAT8A C!C?ߌ\q.Dwc ! rwTD("DRfyRl)M] I {׃RJxZk[ Yzf9gCc @i;R @D$C{\9w'7? eǘIENDB`tdesktop-1.2.17/Telegram/Resources/icons/mediaview_next.png000066400000000000000000000004501326245125100240020ustar00rootroot00000000000000PNG  IHDR 8}IDAT8-00UuH,"X;`H6&P@b88ϑ!Mj|i{ Ð]qx_Ipwd4mmQ5-!q|,N<<8iTJ1"=8Ѷm}_02^ueY躮`Q$LD)`UU=y+=:jT80j>!axDa |YzIIENDB`tdesktop-1.2.17/Telegram/Resources/icons/mediaview_next@2x.png000066400000000000000000000007611326245125100243610ustar00rootroot00000000000000PNG  IHDR,xCIDATX͘=1_Wb!b# ,l(^a!'-E^f .bu5f2f!Mz0 y۟}=J%v;RfuJ~8NVZEBdl?_CJ zJ)#d\2ϻALf٪|>!QSkp8t`EQ\.z=wN&#$I4wr9s#D)F (4B,mR8 C,<F۟Ovv{iݮ}0Զt:5Bnlw.<NMF7RaحV+ruenV˦@;[l,i~3Z\ [r.kEn"O"7K%|zbLsIENDB`tdesktop-1.2.17/Telegram/Resources/icons/mediaview_save_check.png000066400000000000000000000007061326245125100251230ustar00rootroot00000000000000PNG  IHDR_%.-IDAT8AW$ ͉Drhh4µ"F矠ADs~ 10&;~25>5P;bMpsqل8$k$].$W*N'n[D"[ƻT*m{D{! X,$,a ?,cZst:4ɀsNR4Jł@]D#L4Mb9f_$p8Ёd@ b Ʋ, P] > eӞm(˯e\Vp8zMkBTW2$9v;UUO"I6 ED"z}t:?у1ޑ>301N6ꚠ1IENDB`tdesktop-1.2.17/Telegram/Resources/icons/mediaview_save_check@2x.png000066400000000000000000000015621326245125100254760ustar00rootroot00000000000000PNG  IHDR,$l9IDATX_({atjʮV.v݈)%.t$iEQDʍqsG.v'XʝrjE)B4Ir|],?9<}ΞŸeYF8F{{ %I@447778 RU ^XX@Q]]]\ϵ_<౱1ݡ8`qxUUU ]׹XUUQWWpXEQii~؈W.Vu2xb}>X0044$ʰzq{{@0̔cR5󅒒cn7NOOEy_OE9***r-++C,bWVVd& t::ND"!6C$kᔿƍb!˲쩩)n133',bp8篯sCc(IVWWh4 _p*jjjL.// '''p=!WWWx<؋ S9DZaDgnbbB&"aϻ_r &"  '\aS$akk x( "tttdrM\.g dZZZrM&J\躎|``_]1 +Mc h>D%S|ćeY %؃Dɉ7H`-6`?6Zo2# )HPkk+1Ƭg>z?TlIENDB`tdesktop-1.2.17/Telegram/Resources/icons/menu_calls.png000066400000000000000000000006171326245125100231210ustar00rootroot00000000000000PNG  IHDRw=VIDATHձ`oiPE.n.Z.@ ZT.\R(|8>?SRaT8N8۶s^x<a+t]}BߗFB$Ii@CCD,ׄaHSb/l[.y`8q5RnzN$IL&uGQa^l6"|>ϐtR*g&rdX8Rl!00M((x}\wk4Dv]?`0t,b<Z}88oZT3% /IENDB`tdesktop-1.2.17/Telegram/Resources/icons/menu_calls@2x.png000066400000000000000000000014311326245125100234660ustar00rootroot00000000000000PNG  IHDR00WIDATh홿kPOo"uP]KS9" _"AGU]@xKymm~L=~rOIr" |W:@$4 $Iڧ_[]h,4v(}0 Ajz\աINOO]1DTtʜ3ND I+f $ 782'qŌQ>>>d2CNdb1>`W/?@xd~X`(@-jfs|Kۢ)/Kf\_%~T\eYm @E #o"!qvt:mIxg `xg\.7 O(%-T(prrNg@u.d2d2 @Y-i&/幼DQQe d2a.'EQPqZ (9 `R@躎Ba( akp8?z_1ojϳ=DrB\7l69N̛gfȲ⪪bTrl~/v$w@χOOO9Fûfݡ0CE|yy>? ߏ,ۛ+( }HR$IFpuu`FE9w+:~*z_[j$0IENDB`tdesktop-1.2.17/Telegram/Resources/icons/menu_cloud.png000066400000000000000000000004561326245125100231320ustar00rootroot00000000000000PNG  IHDR IDAT810&UPOq_{pQmC9|߇R <zpYԳn:6N IENDB`tdesktop-1.2.17/Telegram/Resources/icons/menu_cloud@2x.png000066400000000000000000000010561326245125100235010ustar00rootroot00000000000000PNG  IHDR((mIDATX_`g+."{W\ oDܺ[x`o) ɿMjssٙmӾCɧg Ey|nf H4VfC$I"@!jiH`z$Y,h  al)LjB&qq:L3r3h4 QSH~`۵S8:(0 N|nE1{f:h4l X(p\z6L&^DQDCT{tt:E< d5vE"0Z-}~X\.k@4ȻTT9E>v@ [A;i+(k }pXkx ƨ5 q`f^kwd2q,kRnuF=|UvDޗy7~.hX,eV( `YVYΪ],"h5h~jbIENDB`tdesktop-1.2.17/Telegram/Resources/icons/menu_contacts.png000066400000000000000000000005431326245125100236370ustar00rootroot00000000000000PNG  IHDRw=*IDATHU10ՠ jt R~s*$SLD$Ąpҍ5άeq #@RGն-4^RJӞ&!0-ZR eYq !q|46 !/i&yE9M}^C*bvv> RBV+0 ,r$IYlAHbUhbjC.pZ`* }b9wJ%{\EMӞݚy<^ DQ :bRx'r9<`ټ$L7SV, z>@=8@^sP^H[ _p %IENDB`tdesktop-1.2.17/Telegram/Resources/icons/menu_help.png000066400000000000000000000006111326245125100227450ustar00rootroot00000000000000PNG  IHDRw=PIDATHA@EM&%'[$7?Inq|W]]UbE5͝5$AQc` shNEEl.u0 '=@)4~WTJKC( `vּצ$39MVnv#ű݋(",^_'c 653dVE۶3mf n1z~ p\^A1"2=h[tٗ]OӼur<> `UUNc*3cfYƲ,u`e:aUWPpIENDB`tdesktop-1.2.17/Telegram/Resources/icons/menu_help@2x.png000066400000000000000000000014451326245125100233250ustar00rootroot00000000000000PNG  IHDR00WIDAThM1Qǯc#KEI KHbAxNbAJ/e1u/g}s,1 |0h/6<r#:)1 oN@ '(fϼ5p`\UU3,`8^Q4 EQ|SV@oT 6ʲl9,j~ÚjnAP,P(@6xRJEGJv}Hgjt\%$l'l'lG #mdleY4MBR~6Үh]W?頻eQ}4Mc68Pep1}op@i0 F{meUZf8oLO}F}IN؎oտZ5T IENDB`tdesktop-1.2.17/Telegram/Resources/icons/menu_new_group.png000066400000000000000000000005201326245125100240210ustar00rootroot00000000000000PNG  IHDRw=IDATH1ʄ0_DC!MFr-ƠՊDfɗ̄L Ks(`8PJsr!"J)%\yQ~ 0 ʭ|1NJt}tu[2]7眔Rd!c I))|k*$AZ{8CuZ#2qϲ씿DBz<۵iHJIB@eYrm i@iryF|<# C0ư,{>^0 Nh4l6AUU0 õo6Qq>SdpZ;4śf3 ??,@姓T*l6|񘺀hdUUeg7 wj`0HGZ;AnSjחջ p\>})<~E䪪|擬( 꺎d2jJSvjy/dq<$9LD^m}iwD"bߏz`0x<0P(`۶8{@Hq8͸HMVT.Ex<~ɼjCw0> t:ecwp8z0jE4>ҕt: 0fiN)>|zxIENDB`tdesktop-1.2.17/Telegram/Resources/icons/menu_night_mode@2x.png000066400000000000000000000020001326245125100244760ustar00rootroot00000000000000PNG  IHDR00WIDAThMK2_/MtzM/$2 M2hA-j Q/`- ʕEF+h1zg>3Q-v _bWJXV8tww A<| , -Uch:t:ztzz5xޟ%p8hoo/gp"%t?GP<~mm w'011AϒG"8KL&#)<\!Ktww'9 !`4) JODL 9SQQ \d2~loo##!LBFfX,8N b`}}Y VVV*'hxT `0d2Á0LJ,KP(oR|>{5|* H~ ,,,VU.w$G %A2ʶmdd l6@6==-R.//M݃L÷?99uӪ@ttt௯d2#_ Z\\shttTQﭱPVhAhssx'aQBհZpp\8 àDt:D"3͛@'(6<IENDB`tdesktop-1.2.17/Telegram/Resources/icons/menu_settings.png000066400000000000000000000010571326245125100236620ustar00rootroot00000000000000PNG  IHDRw=IDATHVPܴX+>O-D, Z-DAmTPdN=Sl֬5g _  !jڿ3ö~lzmY0 n[\,0d2AMxjh2vw 3luޖ n^MhIENDB`tdesktop-1.2.17/Telegram/Resources/icons/menu_settings@2x.png000066400000000000000000000021601326245125100242300ustar00rootroot00000000000000PNG  IHDR00W7IDAThXK2]FEP`rUm#0P`?H܄;q/DP ͠EE HA?)3r6Z oos wuH%ZҿXVk|>/5ZwqQrݘTqkkkO;r{{ >2Lû+x^0 xSxfff t:k.+^ᛜCn7d'D ̅ ѣGg%I/y,#X$I䜓|c*B7n=*RΨʪ M6 cs;PbS4M+I_/uH+?ٶ>&J"nǿ_7-z O~>wxˋ n%K  @0`, XA`   @0`, XA`   @0`, XA`   @0`, XA`   @0`, XAy{I @@`, XA`   @0`, XA`   @0`, XA`   @0`, XA`   @0`, XA`   @0`, XA`   @0`, XA`   @0`, XA`   @0`, XA`   @0`, XHիz뭷k~m,X~=z衇4=5%:HMwUU,Pvi%9|Xz_ÄGV;v.sY&EUU{}krA" k}8nn յf Q#XKR#1c v"<+b$yh5 V/_{T'MŤG]B"vU8([X"{dXojJ*R^2nKXՕz7k&94",K\b\IcnWݬ;acTN('=ijsWUJ;6Q^ TUVDpSʴSLl\}+b{BH$78Oc\sZ+Ri}E)۾cءd%޷g|7זNӽI `E*R(ϝ UdFL7tJKnjH*+aߘvr^*])"#XʲLUUN.1G5Hzlˑ6MjIUU`PE6~nc18h*B^HDZ>.R+VFZ*]!ʪq&Y^:i$1rnңn X+\UUn@[i_`ElxKuecYvci|UW\#n@"Uե꼒~ˆ};j.R]ylpYGSdN$SKZ+1V O66wl{3MRZ\ĨVzqy3jn m$>kE,ʚ.~IbD"t`qQǏZBځziZݬ5G tV-=z\Y}X2ftnj{ E`V¼\Y0 V瘦{Sg HH-..cwt:^):2FQ>_gtgmY5vTwOsrĮ Xy?~-_:=YQ^F6׷ڿoLxI+"33F{+ (_7vm6K'C7qbgHc=+g(T|m^ETNyTJTUsƻ4X䀘e:|.,Wr{HV@kj"t`*Uɹ 町F֘M+Y`VY:ҩ(ָr*R4|!ϛ5|]B^F`~eiA4rn%W+ )Rkel&s7i@+T-lbJeYl۫ﮪ|]i~nNkkZY[ȥt5[vp%e,BΥrSRZY4Fc'7LT\UU=_sj^*KicgvN޴[JI(:U6&JӎNn/M,1Ã0Sk2FJU],K"WkUZu=p g&vM9y5x@/=CS]vM_~._YVWV䪪\WFk&VY*M;I~6_joKU[N0FtLkffFfA=|Tu`qQKG?;w-19=uaϫ4NA~"^7>޷D%WUoittRuu,|յ5-_FL| "}:rdIssJӴy+16 LnD+ U]|Y/~fKChgoy%IENDB`tdesktop-1.2.17/Telegram/Resources/icons/monitor@2x.png000066400000000000000000000250361326245125100230420ustar00rootroot00000000000000PNG  IHDRX 5 IDATxݏdy_yߝMd(E(av'p|!Cr$rc֥s!rbbe&%-Ԓ\.9;ݧyrQt,/˭馾ޙ3[3Fu)Iq3h4p8|څX)9~Q.\}{׿|;׿_~$`mnn~/~Wu}ص%wJ1xxBۿA >9} _w-=Bqgi$IgϞwwi|E.mN#)kXX򗿬zH9%1圕sփ>??8F~X_(H!w}:/<'N<ɲ 5jF.]߯g},o~p圵󒤸__9X}KM_׽u9{Ż븯]㮻VG?v\gn%B4~*nmm̎zV"mi* PAA)%EwWmo\ @e,X*!(FrXfp+]&q3rHkkkuٳFѡ| k:zvEP  & 2@e,X*#`TFP 2@e,X*#`TFP 2@e,X*#`TFP 2@e,X*#`TFP 2@e,X*#`TFP 2@e,X*#`TFP 2@e,X*#`TFP 2@e,X*#`TFP 2@e,X*#`TFP 2@E!@m,X*#`TFP 2@e,X*#`TFP 2@e,X*#`TFP 2@e,X*#`TFP 2@e,X*#`TFP 2@e,X*#`TFP 2@e,X*#`TFP 2@e,X*#`TFP 2@e,X*#`TFP 2@e,X*#`TFP 2@e,X*#`TFP 2@e,X*#`TFP 2@e,X*#`TFP 2@e,X*#`TFP 2@e,X*#`TFP 2@e,X*#`TFP 2@e,X*#`TFP 2@e,X*#`TFP 2@e,X*#`TFP 2@ENP 2@e,X*#`TFP 2@e,X*#`TFP 2@e,X*#`TFP 2@e,X*#`TFP 2@e,X*#`TFP 2@e,X*#`TFP 2@e,X*#`TFP 2@e,X*#`TFP 2@e,XX*rw@m1pBPlV,BP۶e m{hZf6qw4hPΎxCbggP'Qxo !(Fjr0C X1F;wNC :٫ޡ,he@e,X*#`TFP 2@e,Xq_FAq8r3@pxyJ!1O=q_ SO=o~]ⷿwoۊ?.\@u1… zx =裢];BУ>'x4diVN4L&(cG9 Xe<{1IR䒴{Q8`e4MzJۗp޾Μ9gyBi=3:s,\IWO3Ξ=i4 ,pYN:{>'هbnLRl4^u~,`mk8MefgBp(yr zV XA[jVvfAr<u~f\8KP XA[[[ !cr_jjd5cp)xw;>нWemnUEV˔s̻ [&VunfsV`54)(YPfԷ2"Ik&rvʋ%Re<]ڱ^3wxlR(7`z*E)H?TȂԍ|>~r/SW4V`($˭N˃\BܔR.uXA,`5͠ ,HK¥Rww_<__/X-,`(,Y16]sRyw @Z*Kޝ!Y 肢tiVAM3Y{L "`+(m\[,7nwkcT]u3K\r˃dfjFmKV  X1@kkjS+6~]jw/ДkɺДK\pݓ;zcj X1kkm[e ڳKJ3Kse=pnV Sr)e bY MZ+pfyY+n²Kƻ~6tKmLn4Inpxԗ bM&I}a YKlD]ꇩYV X1HfRLH('^8Rix5V  X!! Me˳mJsQw#5X$+4!*~*H{߃d}8*ɱ]7kGVܻbf%'RnrK 1(ˤy|8 uX!`+&g{[^ fz4Z&,Zm܁UDVLJ&|7lsrC?KgX>H`cʽ-؅\.ŜK_7+ ʹt_q`[SfҰd!vKzI wOȒ/X*T&L1̥(eMy鄅R,ҊC*)E) XE,`?^ٖ,gX.9g)HQ7Qnn.nN "`+"Myg;Ζcں/ۼņuV  X tٍzf_fY14?cKѨ> k*cj.5i" rsIYVnY hҰ\bdFQ$5] 3QxV:`APV X!BSβ?ו|agpFb!| Q_u!`+b}c'vն,`M)MM+bI ]MJPz;5m.w"`+bks[vOCKNo]X9؊E&b72\__SLp "7`(Wi4S~ә%K,tV,!$5e`0 5GRKml+IJk >kf]72} RvWPNh>?z}}MDo3`q mmoܻg]:pΊK^bs`R !^qr~R,ƚ[Z4za+5}jb͍[,hfX~]͕v%4nyK.=p X'O7KZ[__#WCY[lͰhDؽ_: AAA'Oƛn"`+m# n_-T!yKi}5XW,I@;;[s X2w pVaO%@pĭ%+Rgg MSwݭià,3B瞻s`0˻Mf\w<.XZ65 AG Uf}ʓ%+Tvnf?Q_I=Fj7|<׍g!jqVڏcN%hO#s?f|{[+ jsVh"ɐ/ܰoe.ؕ!bӎ4{ڥMmN9W^щ'|Ds DSO?.^&6Zmq°<WưdJ9)V)%d:˺K/xȾo X";;zR۶jSxM(+)[V;teS F`` Ma}7r)笔[55L-{Rv٣)$v7u?d_}$In&˲˭ 8r%ҷiSpJRkjsV;mնSMۉ&̒v; U%Cf.km &pX1='padSPj59I!(gS%xY %hA干CEXf{7{,IAV(s^ݔ-)YْrȘio_X7|^x! 8F,8mM&S5ܳR mV bʩQl|!`E Bi٠8oJiR%Kxۍ4{5*u+$K݌VVf(S[곤,w+M&F:}zW/xpBk_@qSVI!6r2K6Ŋ)+rp QnAvU;ŕ\ceiw/\lUy*[Y{]&SJY%M |i;rfW)p88B1F> MIDr[VJM]MjI,gYJr7AsyX\FݜU'+=c8?&JIM4)Mfώ夜JK~ƳM7ݠhx9,FCᤂV1ٍ0Sl IDAT̂ܳܢ,BS5XKۃf|̐0dv;$LIݸ5X%8L9rYJNIiVϵ$h%s>K^ե{~ #'wɽQYPeyX51*%Ӡ)K)A1J2%( ,I-/Q|-yPCppɃ̳BjrV,jSi6˲٫>krgڤ[.^H 8d;;Tjlv"yŚ@wfb*[sVJQM̊)T>G|>Yq4|rNH*5Wdݲ`M8m,!,OlRlXtpxB/twCBζ6Jmnb5eeX51I\fTYnƣ4e%H( %$<_5]P*=ri٨e{k&y21mJIH7pR_lD>uR1Di!q$Bw3RcQ\ e*[Y(74PML)Y'2q9Xi鲅6sdVZ6䬜Қmpe232|;*6n<}J/\d2=¿ l8hԮ^g<[S,%by粲4IjⰛꂖblHMṂ;fMb,2- o!\;\03YJcWNmJҠ;NBY9u_,/Ů;d[ryc:uΟQ5?6X@Ek㑶7e)ώ=otmk^Vv(esŦB2g5]vkCjhw]9e]2nQ] ] 2fJnRB[Zl^~c5;.V;j uY@E, #/nARmu7r2t?jĘr4(I*Ώ]ibZ.ΝJҊ!Ⱥ n\ɺCsuW5;)e^{fCk<[)&=쾖e67Kt~j!`)'5hJɩ`aΘ-HZɔġ)+)ǡ,$5D R郋uʲW$vr)j7se+gS)ͥ[D-zBKܹeB-k[6̸Z2#7˭hJ3~2˕?c]-V?_jWbe)ٺ烂rN4^[ӹstG~W^yE.{Dv_i.7龦ө&Ӥv2Qj,Y;Snta6Ktջ{Ca6IYMhxpxhMPAhx mig{G'N7[nӧ5=}믿ׯw Jn=Owyםw.INȲ^K^d2d{ڻTɞӬJ'dN2bea#A'iBL($**b|?soeY?L&V/rx<8BUлEA$G"0`Ӏgu8( ?)۶L&rx+C?qd&K$Q٤t:QкXY?uh8Fv \@zR;\eYA*c4 a2@$=ò,sN)rn^ضm^j%rkZ-'˲?x"qrhUU*!)P(R 0躎|>{d l6 =$dYӻ=)Ba TThP*z=BPq(4ND(yk61DQO$c$4IENDB`tdesktop-1.2.17/Telegram/Resources/icons/new_chat_photo@2x.png000066400000000000000000000023641326245125100243530ustar00rootroot00000000000000PNG  IHDR<6gIDAThKzY"Js٥UBJDH\"݄9:/(Ej▢qY*Y;fJm{ըyl= t:N'l7zf," .,,|MYE*0"bR<55R:lUb'''8 `ñt:d2Vu}}CCCtAݎGۍMoJ%z<I~TTUFXE\\\uCFM XE[mf*4If~Y4 iyNNN@J[[[t~'ۋx{{\ְ41p>o8Ae^D9k8јnwC\.ײ_6ő^4.p\d?*}Rӂ D&>88$n7Q(DDUȗfbY Ye2d5ȗ jF#kbrr1[p*Y=L&<!VԘǝ EM_:&|lED:y8ȓX -d28==mjVDl2jA_~Ԙ-d/˨T*k6===H$~7 05* + IdÃ㩻8l~8<<\%. VX,ִW$ih4.O hxpgY].קtWWW~mooFTvղiF. <u'lnn҈IăZ3ԋxL+&2jp8"JT 'fF(ɮZ z޺tRQIJoideqoi& d2iyrڦRx9ڦx<;;;6moo,hTu(W'.t&qppX,;Xbn;vX)4=IENDB`tdesktop-1.2.17/Telegram/Resources/icons/notification_send.png000066400000000000000000000004441326245125100244740ustar00rootroot00000000000000PNG  IHDR.IDAT8TA0 } i|ߢ_<@o~ğ$OKagDX ꤠ Ui˲H۶eHCy{/}KD?XUiӉ8hގh^D$̌3cF !8`EzBk c "nۆy1 T J)(gCD ̌,53(KpiDk}K㘦u]Z;P.Y:nY$wR!rIENDB`tdesktop-1.2.17/Telegram/Resources/icons/notification_send@2x.png000066400000000000000000000007721326245125100250520ustar00rootroot00000000000000PNG  IHDR*$ɨ+IDATX՘_@OR !j-^(.uJt >;au=|z:#f3jZq\(cZ,v*(hd2_H4ҞZܵ1zkZcEjt-:ݢ㵨J4ZTZ rxhZ--o-sB o L)(yR޽c c ބJX.nPJ=/ݛ(F$A IETR 1!q{slc8B`pB땊>axP1~BȴD!vH\Sy(y Nv~B3V+FvNJg"!F#8CZe],p7I:f vLd2n$a}X 0Lnb$I WUwӀDh4tT*y x< c@"B p[V=$"|>v n>r=8z!1l6ۿAyoZ~+y֢IENDB`tdesktop-1.2.17/Telegram/Resources/icons/phone_simcard_from.png000066400000000000000000000013521326245125100246320ustar00rootroot00000000000000PNG  IHDR#0lIDATX홭w0Ɵ˹ bb1(0U0AC ?0Q3`A AWܭ b99y$i "byn}}?D$t:Łaf<80@׆af~yy) 3s+0~80̃ff@ҭ1ƤB RJa># Cql]yR8p]D_ ouf\׏;$!fܕ8f:qi&q^.h4Rj8R"BHj5x!ߡBZbaJoP^] ${|\Z-mWzQJAJ䭜ty)RJqn+ڞť%+FaUY1yd&q햅VlqnJ+"zA#+Sd*0n:Uguzxsɴ7V4̠!"<>>&yܤz\?<<=nn9L{WaU[7iSf3k"jho4ͬe;(:%QeXa2`\x~~Fɍ?+*rJbIĤ1oT(Apk@%hٰLDw[hIENDB`tdesktop-1.2.17/Telegram/Resources/icons/phone_simcard_from@2x.png000066400000000000000000000023471326245125100252110ustar00rootroot00000000000000PNG  IHDRF`IDATx흡W:K󼳅)k!e-$ bЂ -Dc@Z"Sa1{*\2묞Ιw~v 1t] 81ϼbTe5@˲p4^!2CU_g*(rF"39A+\1y1d"UꋑbjŬCkCQ׵TrT']k1b(\b(NRFC(Guri6Z Eb(QLt:""h>quuZ Ch1Z Ch1Z Ch1Z YN$*<ad[l Cf.) P,m8::(:11zbq=)[44jIۣѶmeB6۶lJp8DƘr_c á1Ai%lzkmU7՜|>}kyjmq}cc%Cp2`dCq."X=#ҝN'+FXLEhYJa`Chf;^G0VbXI%<}||tr^CV3 j5+ǧ)<<!b- ׵Jr]+DSou\JJ/eq.u Dܼźǘ`mqOr]+-፹AT޳}49 D5ivZ qg7eC`۶p,a1JeeC@ a1r,Z9^W^rܲ,888DXi$I6al|5s!m8;;[s]74jUyQU*`4}ߎ*߿OB 绑8jR7} v>_zAO-@!— $yx ;nWu,}Y?~RL8 h|BlyAIENDB`tdesktop-1.2.17/Telegram/Resources/icons/phone_simcard_migrate.png000066400000000000000000000010671326245125100253220ustar00rootroot00000000000000PNG  IHDR+fIDATXؿP񒴑J *"Bb!&_P?dKȂ]"Iқms9%"!"8\7D `\!H|60i.Y^i[(p)a#E0#4;n|>ef%"+4[lV-6=}p\|.It]bncv\FX,& 2X,b^c0t^DsIŐQ.( >& V-LӼNRlJf-VVAFѿt7fQՠi øR ZiFaimx<*fIPfMӳy""HE$|>ɲL@dY&KnA jYvIQl2=W`@>Z>>xff?ECa]hDÁ c8Ɲ^/477jJ ux(1M_~lM`VOJ&yFn<8)lXnXnͨ`xuwwkoCIWvZ6z87u:rffs ;J 넟JҙBnnG8+#s`vvv+n2 .cQ1TnnooQTSgIENDB`tdesktop-1.2.17/Telegram/Resources/icons/phone_simcard_to@2x.png000066400000000000000000000026331326245125100246660ustar00rootroot00000000000000PNG  IHDRF`bIDATxOHJ^B AoBW (AP nT P(PZܹӕ"J *.DQtەBB5Z-.vj&Zl3if&'MrEA ؈j###8>>̀vCrྨJrH:8MFL4*B4_ɉV1|dqqUUI$I>YL&%^(UBcc#$+USb@$lmmW6&&FGGMm_br85-u3m-&&& 6b`uu`Xc{v >؝]LOO 14C#14ѨC# 14DUUB*:KKKb(|! B !CA Pb(1 B !CAӪCuEK;H{6q~~k<>>ro bg4h93Lz%ooo31rh9Ykvwwn' >9ZQB!oC$A ({]+]]]!H`qqd1 ݿŒJ+x*BjOTmZ!Q[MӪ2<+ׯ0]!S=\}WD|o16CA PD5:.//Σ긼D]";#HKćirȗQ+HvUUt尳k z V  aqq4 _5.IENDB`tdesktop-1.2.17/Telegram/Resources/icons/player_close.png000066400000000000000000000002051326245125100234510ustar00rootroot00000000000000PNG  IHDR w&LIDAT ! C\B&)$48$-ɨ*ێ-qOB(87+c6k0~h>kmIENDB`tdesktop-1.2.17/Telegram/Resources/icons/player_close@2x.png000066400000000000000000000003051326245125100240240ustar00rootroot00000000000000PNG  IHDRĴl;IDAT8Q 1D. L?a ]M чfTDOk!瞤e643%Is.{W}Ё>:@/p^mTeӨ^jS8Mq{:`^EIj_JR֮*{މ19??VUb&BIENDB`tdesktop-1.2.17/Telegram/Resources/icons/player_panel_next@2x.png000066400000000000000000000004511326245125100250560ustar00rootroot00000000000000PNG  IHDR"/ IDATH՗10Ed\bce,BnBo~$0sG=Y!"Q?`,K1_".]QE?iDbK}Ǒ<σEB[y쵼q p --r -,Z>ZU" - f]WiV̓""ƼR9>^_kuEe菞+bms"? + YNIENDB`tdesktop-1.2.17/Telegram/Resources/icons/player_panel_pin.png000066400000000000000000000005351326245125100243170ustar00rootroot00000000000000PNG  IHDRH-$IDAT(=0Z+ 60 i,,j,m˩<ւ R+'n3>&$|oB!kwp^}?!Eѱ8++Q$ +qB\.MӐ9B۶B t]a:8y3iMն@?7AJ)|(6p]w>yG ðm[(|kYpʲta뺆$IK1ILӄJ\0 UU,.,v!"}ٲӯQ*RIENDB`tdesktop-1.2.17/Telegram/Resources/icons/player_panel_pin@2x.png000066400000000000000000000010431326245125100246640ustar00rootroot00000000000000PNG  IHDRr ߔIDATHֱPo_Q$AEp!h \3"!??1Ɍ󆇽'Z߁;p=Z \,K\~wIf30۶gq,B0c6MZ0 +Xi$IbI*%j$6`0@44 t:, n`"t:h6P^~z)(+51}򆕅eYl6_HDרmt8n "}iǿĞFsm2p/anY_n{?t,n.K9`Fi, ` xZ0 z t:qoebDD$DwTEa0 q}?,MUe%䡞 pjQqE8˲̲,fmE.<7Բ,1h#a4yvl#y륱&ha^IENDB`tdesktop-1.2.17/Telegram/Resources/icons/player_repeat.png000066400000000000000000000003231326245125100236250ustar00rootroot00000000000000PNG  IHDR IDAT( 0 C )d-dtCH A`eٖb!$afl+wGM#{qLktNUVoBJ)HOZS'i $RJ˄ZkVIPJya0fx@Fb%IENDB`tdesktop-1.2.17/Telegram/Resources/icons/player_volume0@2x.png000066400000000000000000000002471326245125100243130ustar00rootroot00000000000000PNG  IHDRnIDATH1 0 @/ߚKHAn)h7x+"c-P8TdVŨkUrż3fDSu[N-ļj6IUe)]7,pWx=<zv NmeIENDB`tdesktop-1.2.17/Telegram/Resources/icons/player_volume2.png000066400000000000000000000002321326245125100237350ustar00rootroot00000000000000PNG  IHDR v4AaIDAT(A D?h*Iм!nv: q%<ˆǠ\$l# mYƳ_|`I?,c@6?Eݾb'IENDB`tdesktop-1.2.17/Telegram/Resources/icons/player_volume2@2x.png000066400000000000000000000003421326245125100243110ustar00rootroot00000000000000PNG  IHDRIDATHQ qݏ]D:GKSikvZ&毣`5t;l[$apwuqu]<LXۡR}ndZCup48jǝUIz(>;=c{v~ /0)I4IENDB`tdesktop-1.2.17/Telegram/Resources/icons/player_volume3.png000066400000000000000000000002351326245125100237410ustar00rootroot00000000000000PNG  IHDR v4AdIDAT( @gZ=O(Lt  dff8kzk09z$! $*w>`r ~|ʠ{8t;9S4EIENDB`tdesktop-1.2.17/Telegram/Resources/icons/player_volume3@2x.png000066400000000000000000000003621326245125100243140ustar00rootroot00000000000000PNG  IHDRIDATH ʳeizeM EQ69(+XDeG>UL>cQ x0[^GYZ~L+^Vǻc 5s9g(o;Xj\O5N9WGHDq+߹$Dkȹ'Fl|3ƢIENDB`tdesktop-1.2.17/Telegram/Resources/icons/playlist_cancel.png000066400000000000000000000003301326245125100241350ustar00rootroot00000000000000PNG  IHDR Vu\IDAT(u E/~ZA覟S"~j@Y 툏 9!QI3r43z$8J۴R u]CO_#Wi࿰:4ٶ }stC`^)缜D0傽qTS%"TU/[yNaIENDB`tdesktop-1.2.17/Telegram/Resources/icons/playlist_cancel@2x.png000066400000000000000000000005121326245125100245110ustar00rootroot00000000000000PNG  IHDRw=IDATHK0E/=&Nj9B K-ю4WI*9GI0@k-Ϲg"jx!xD9~߶'x K`}]$9q-B!eY%91~j$sI@%S*y? j% T>t]]ʼI9=I"/\QY/OxJg4QUf#-U0 [$"T&x, G~R/IENDB`tdesktop-1.2.17/Telegram/Resources/icons/playlist_download.png000066400000000000000000000002241326245125100245210ustar00rootroot00000000000000PNG  IHDR "a[IDAT(풱 0 Ϭ4  t|gNt慗OplCf>w֒<$3N*(TvP K$}㏬IENDB`tdesktop-1.2.17/Telegram/Resources/icons/playlist_download@2x.png000066400000000000000000000003051326245125100250730ustar00rootroot00000000000000PNG  IHDR ^8IDATH0 CQ3)wf8/3.}ofyƗ@ $@`򮬇[J)ŷHjH  Fv0 9ŏS&~E{`@q}Cqh!x 8#q٦DFPk~Q6`ϐIENDB`tdesktop-1.2.17/Telegram/Resources/icons/playlist_pause.png000066400000000000000000000001651326245125100240330ustar00rootroot00000000000000PNG  IHDR 1`4f~l v`7Z,v&XaK>ϋܷ2vfgQqc&ɓ@)EUU8Lt, Iܕd0|.8q]d]W44M^7pIݒ׍7,ڥ)(۶{Զ-4MDQ?G۶eeOv4xk3#Rg/? I71IENDB`tdesktop-1.2.17/Telegram/Resources/icons/playlist_play@2x.png000066400000000000000000000005551326245125100242400ustar00rootroot00000000000000PNG  IHDR$ zڎ4IDATX͗!0;+ \! KW !*ޠ8 t&ːex_R|I_=c`:Z(`I\,Cu}О=!Xu]qB)%#dQ58aYe ۶iȲA0 |>y !R eY²,Bq)!C}Bmڶ`a8TviX4'!ud ij2^)I}d>W2cgTUu@}r/D%r^Ġ֚(= 8R> \=>xIENDB`tdesktop-1.2.17/Telegram/Resources/icons/playlist_shadow.png000066400000000000000000000001131326245125100241740ustar00rootroot00000000000000PNG  IHDR'IDATc EzRIENDB`tdesktop-1.2.17/Telegram/Resources/icons/playlist_shadow@2x.png000066400000000000000000000001331326245125100245500ustar00rootroot00000000000000PNG  IHDR9"IDAT J߱FwUuEp}wbIENDB`tdesktop-1.2.17/Telegram/Resources/icons/profile_add_member.png000066400000000000000000000004771326245125100246020ustar00rootroot00000000000000PNG  IHDR&IDAT8107`e^ZD,J#Db:e^ƍ~K2u)sRJѲ,e1ty!Nn&$ߋwJ)XJy!?c,.R+$}5 !NiqsLŻsNRJRRRd?=, i1SMIL\Df&gL0%IRJ4M<=C0PQE1JdDyp~0ƐRǽWyNhF:~g'{EQ#,IRhY?? f:NZjJ rdq8`Yl6X0`ˤ$ {Ka`JpEV}%6M$#,.EoX,_n48N#h‚ |6 r^FG#lۆl6&%6lۆˆcM_EyxIENDB`tdesktop-1.2.17/Telegram/Resources/icons/profile_admin_star.png000066400000000000000000000004321326245125100246330ustar00rootroot00000000000000PNG  IHDR Kpl_IDAT(u1 E$ H@%(S"qD9%)  go?n|>qKx  \OJI*cg0ò,ZSJ)_R(HZkS<+GK0 Rjhw}om_⒕s%MnZB1uRk#bܴYIENDB`tdesktop-1.2.17/Telegram/Resources/icons/profile_admin_star@2x.png000066400000000000000000000010141326245125100252020ustar00rootroot00000000000000PNG  IHDR}\IDATHU@ 2be;/I#4 RlRHlhogc!Dޮ1{;3psN1z<KKL"v_)!~m[m EQ;<89'~c EQ EQ1$u@#8' 24M8#78M (nHY,K}?K:Q%,CEl6c8/>p Zv}3mUU}*ض!뺎2yeu}-eQ-&h&M)MөIiJu]O΢zXYȖ$Inq$I[d& 0yNy i,4AaP4ͧ5cdza@PUu*00 l؉$I`YiiYnN>9jzAc N FtI1VIENDB`tdesktop-1.2.17/Telegram/Resources/icons/profile_divider_bottom.png000066400000000000000000000001211326245125100255170ustar00rootroot00000000000000PNG  IHDRIDATc```D0333_ IENDB`tdesktop-1.2.17/Telegram/Resources/icons/profile_divider_bottom@2x.png000066400000000000000000000001251326245125100260750ustar00rootroot00000000000000PNG  IHDR IIDATcd``1XQEZ<:IENDB`tdesktop-1.2.17/Telegram/Resources/icons/profile_divider_left.png000066400000000000000000000001521326245125100251510ustar00rootroot00000000000000PNG  IHDR I1IDATƱ @@a /r=UJv7*93.Dϟ9/3':UIENDB`tdesktop-1.2.17/Telegram/Resources/icons/profile_divider_left@2x.png000066400000000000000000000002021326245125100255170ustar00rootroot00000000000000PNG  IHDR'IIDAT @Eы0dl@6 IBGZz!3+^U$=0ѻ;/73:lĄL7"CIENDB`tdesktop-1.2.17/Telegram/Resources/icons/profile_divider_top.png000066400000000000000000000001301326245125100250150ustar00rootroot00000000000000PNG  IHDRIDATA T_nHR_+#›CIENDB`tdesktop-1.2.17/Telegram/Resources/icons/profile_divider_top@2x.png000066400000000000000000000001521326245125100253730ustar00rootroot00000000000000PNG  IHDR I1IDATe10 9DՑCVlj̀$|?Q $0IENDB`tdesktop-1.2.17/Telegram/Resources/icons/profile_verified_check.png000066400000000000000000000003361326245125100254470ustar00rootroot00000000000000PNG  IHDR 2ϽIDAT 0 F͹AY(AJz6@J1hX&ud$tE>ɒ'?ۅ @YsNy,Kq *ιky8B4M Àu۶+k@ ,<cnڶe{~}5ZRJ4MB2~i{IENDB`tdesktop-1.2.17/Telegram/Resources/icons/profile_verified_check@2x.png000066400000000000000000000005671326245125100260270ustar00rootroot00000000000000PNG  IHDR >IDAT8?@7"i*Xl| e&`cc/[Iic#`wEH.{=ycvFP_Sqn> J) gqei^y4 ӉUU5m[rQ矖F$X>\D51R"(Ykc J)E)+,K\1 {m1hǯ ]圣0 &BӐ=H0\5#GO{M\ Lipw|Z>_iMk{3gu,vՎ4Mws "H)a~Xkx<@DǴm؃(:E]ר?tsIENDB`tdesktop-1.2.17/Telegram/Resources/icons/profile_verified_star@2x.png000066400000000000000000000013561326245125100257200ustar00rootroot00000000000000PNG  IHDR$$IDATXŘ1kPO2X;d i8~ ݄.]tNvr$:dQC<^SMc5/KNι?r=a\p8D*:*q\N_V+^|>f)Wq MkJiE *\L&<<UQg?AI$D UE)Lӄy_cn_M22):o0wo{HsiA f? R!0td'h=o eY1Auw#ƠOb&("+=3^(0A2 JrQ4sڃ2-̏l*Hή [F6˸qIENDB`tdesktop-1.2.17/Telegram/Resources/icons/round_shadow_box_bottom_left@2x.png000066400000000000000000000010331326245125100273040ustar00rootroot00000000000000PNG  IHDR..W+7IDAThn OMHQHJcmZ;[k;}?1xZh7Zf rrw5x\jwQ]v$MO'dm2r`r>8(׽ f {pPy4PRŪV*q+Uf 0pjpZ .[p".WAFRKVu\%r#'588ow֚[Vgɭ*pΏՈ8OUB[[Z8 .+U-$4oG"xXkS"˝@&5"Ge8i׭ݭg*I<ֶG`agࣁxcDKQg_ 3D< Vg?ʄWSR;rxu>$=&'7.^GIENDB`tdesktop-1.2.17/Telegram/Resources/icons/round_shadow_box_left.png000066400000000000000000000001351326245125100253500ustar00rootroot00000000000000PNG  IHDR ?~$IDAT00?23$! mirw|)XцIENDB`tdesktop-1.2.17/Telegram/Resources/icons/round_shadow_box_left@2x.png000066400000000000000000000001421326245125100257200ustar00rootroot00000000000000PNG  IHDRX)IDAT 0 0vJ@~I[n]ň]f8IENDB`tdesktop-1.2.17/Telegram/Resources/icons/round_shadow_box_top.png000066400000000000000000000001351326245125100252200ustar00rootroot00000000000000PNG  IHDR u4J$IDAT-ı0 0C xeJ3WIENDB`tdesktop-1.2.17/Telegram/Resources/icons/round_shadow_box_top@2x.png000066400000000000000000000001461326245125100255740ustar00rootroot00000000000000PNG  IHDR9-IDATcd`` FFF"XpAH`ffFehSIENDB`tdesktop-1.2.17/Telegram/Resources/icons/round_shadow_box_top_left.png000066400000000000000000000003331326245125100262320ustar00rootroot00000000000000PNG  IHDR*ԠIDATH C+BHFbX8k.nAk@{/34, ɶ ep$+63=W+YrE&8/W{CiϽ8s\mfsv@ \ή?*zi7w~L?UoiIIENDB`tdesktop-1.2.17/Telegram/Resources/icons/round_shadow_box_top_left@2x.png000066400000000000000000000006471326245125100266140ustar00rootroot00000000000000PNG  IHDR..W+7nIDATh EeCYn8}QC@lDtwDkG6B5> :ٝj7@R}g{i ZnLKH#Y\Qڽԏ$ܚp[e¢-\EP<,61߂A/YƆN5޻ h\~hbg  ]&6Y|ɩY }Hx\891|c Z-.4!t[r+陖ʼ<:ɲ6N FTsTbԡgɉjOֲ/e(d&5 D]|[ms JXIENDB`tdesktop-1.2.17/Telegram/Resources/icons/round_shadow_left.png000066400000000000000000000001161326245125100244770ustar00rootroot00000000000000PNG  IHDRIDATcdaa ,, ^6k0pIENDB`tdesktop-1.2.17/Telegram/Resources/icons/round_shadow_left@2x.png000066400000000000000000000001351326245125100250520ustar00rootroot00000000000000PNG  IHDR{]^$IDATu SO߶N`r$$l- 6k-$/0_UEwH" $S=xLKzG;IENDB`tdesktop-1.2.17/Telegram/Resources/icons/round_shadow_top_left@2x.png000066400000000000000000000003031326245125100257310ustar00rootroot00000000000000PNG  IHDR&/IDAT(Q !D'2,"T"R= i]Xu`kd$TUzwT[k 8Է&y?p2OtOK[ OIENDB`tdesktop-1.2.17/Telegram/Resources/icons/send_control_attach.png000066400000000000000000000016271326245125100250160ustar00rootroot00000000000000PNG  IHDRw=^IDATH+moƖe)$[dFQp"vD"%Br"Y8&L#e,9d|yo勺zA ӄjY]]Kyxx#Os2oc|~=<**M)//G "$$$ՅjlF~WVV> VnJXXػnDGGj ~9qqq<<>>"??J)n{{;"Brr2,--}MLLpqqkjjxkk+"BJJ VET*brrsrssy^ZZZRSS9::`0| pvvFNN"B]]>fD4l6~5n$&&xpz2=ӱ,SUUULOO+X,222p81T??WN绑4g`0Gii)t:NOO j֔)Jl6F xLö׍IENDB`tdesktop-1.2.17/Telegram/Resources/icons/send_control_attach@2x.png000066400000000000000000000035151326245125100253660ustar00rootroot00000000000000PNG  IHDR00WIDATh՚kHyزL˨CiBFo %E  EJV)SRQ:iŒԕJ0ujHB]tSW ϛ|]\S/ 07$r9[leì5447o0NnjFp%q444Z%K=𡡡xS @TT>}$ى:t:z477cttl69s$@˹xL\v |.''F{{;044,>wޑo"33%RtIG*AHH ƢKۋi۷Fq8}}}زeˌt###ONsŊk !!|ww76o,T괾JⴳgπB@ww7d2!>>Ħhll`>exk\bv $&&gʋD"bxxfAVC,;#ɸgn 11}}}Lgg'6mDb RT櫫Ȁ+ {ek0Ľ6::m۶9Y[[;k2o %%\vƒr wF6^ii`ND>ǵL@RRf3wr{U]HR___ooorJ]ZRSSO{SΝ;3g~z_~ OOO;;;ӺpkJ%9_"** |yy9Wy~~~hll |tt4ZM+**Fj*?//l3A;vnJ*兂_VVDB*o08Պ]vG6 ۷ow) \n4A*_XXHKKKIW^.b`D"s FRyoooA5Ǐ^G%555SYYYfڵmHbR|)cǎGK^ݻ111mp͚5Ǐعs'uqp©b1nݺEi4xyyq֭ϟIN'N =_SS@2퍲2_PP@*o߾qqɓ'W\ڀD"/,,G`^ԩSDSӹ ?XrHVhkksO&p~rB[Z&qB~؜j!ɦ@ff&IRHlذT~hhiiiNxϟ.ԟOD"( ֭[ٳg g /l@Trjkk1w\.ߟ?f3޹sg\Pd`Ifa߾}${j06 |UUf^@eex2Պ0hjj|nn.lUޱIłŋO*"?~ /l`add1ŋ G/l\7o:-(4Nļy/l $${ZnV/]D>|Hv0`ƍHoo/RSScM$`0m GIZk-Ƙ{GS]L IENDB`tdesktop-1.2.17/Telegram/Resources/icons/send_control_bot_command@2x.png000066400000000000000000000013021326245125100263740ustar00rootroot00000000000000PNG  IHDR00WIDATh@ODkBBI/{e/ԢF ܆֭BhlժU/$X^ęh3TĒ^Qeض 1Bc`&h`:/4 0|uLae/l۾j$Iɂ IgIJR\;e}l6O.lTn !鄊|xG|oe^p iu]Eѯw]rX,r\Fn7cTU#x<&~A/3mI IENDB`tdesktop-1.2.17/Telegram/Resources/icons/send_control_bot_keyboard.png000066400000000000000000000005001326245125100262030ustar00rootroot00000000000000PNG  IHDRw=IDATH10ESn88tqÉ(dnvYB&OȞF )-s@rW "pέ9ǣ)˒w("$LkmZ6MCl @0Ɛ$y&*$?묔:_N.yWonti_pK0 j?&uQOm#j0M ˲͍1#TUns5t]w9g燏̢( \w)"t]&JYJe)bIENDB`tdesktop-1.2.17/Telegram/Resources/icons/send_control_bot_keyboard@2x.png000066400000000000000000000010421326245125100265570ustar00rootroot00000000000000PNG  IHDR00WIDAThO0ǴQ9V1z4wȶv)%ݶx(&!ϧU[0 dO$8 2isBPJ1a!sk!J)qiH)1 CtZ(d%@ "{T* } ='Zd1Z+c}g jGGAmíRWm⥰AG~Ɨ?QE@(50U.4]$mԯRi] w]W뺎u]׽⟁d5(ĔRhcR7 *b1f ƘB@Y{LeYbq`E1#h> À$I$ aF^ٍ9slGc<'sp13^mgpί ,6[keU_4M i^~#tr q pIENDB`tdesktop-1.2.17/Telegram/Resources/icons/send_control_bot_keyboard_hide@2x.png000066400000000000000000000012351326245125100275540ustar00rootroot00000000000000PNG  IHDR0 TdIDATXX-@ QBP7 1 `H@ L @T;]`I烙2"1gȊOgو VE ?DD8Z-bct:^s{UF[15(fo67z~(&0 n.hq~Wo8&`60v]ټ z\0 avDZ% Q{DQZ-TZE\h\.|.x<Aj+Z̲,L&yLS.,+._]"B\nZPUbpvrZ4My\sc_VKe.fX׌4.)WW'~\do+iIENDB`tdesktop-1.2.17/Telegram/Resources/icons/send_control_emoji.png000066400000000000000000000006661326245125100246570ustar00rootroot00000000000000PNG  IHDRa}IDAT8P?hŴ,`1}$oCE# 9OP(D jJ!гbsܛIf3df yg Q#i_m2)fR! m`6<v݌O x8t:}?ּ4Myt]eWuy4'>( ^8aHO|ZF IV9@*zfEa$ )%brr,Jv4MLp^L& CFMӨT*iJǜg:tMݠP(>~rf! C,ˢjtX,8v{uAu)_A!u]܅jJE8`0lnB o;SIENDB`tdesktop-1.2.17/Telegram/Resources/icons/send_control_emoji@2x.png000066400000000000000000000013531326245125100252230ustar00rootroot00000000000000PNG  IHDR szzIDATXU1K`=Pb;bZR\ť"չ[@TK 6b@r^ 5I{=ܜ{C8dYF\FTB"MQ*P.!r :Xefp8ìeYb ?"yszzS@,//bϾ---*###b}TUWL4OuٟD"A۶;4M$y%Iim\XX܄di+ _2iV*jL&*m&nooqww<==X!JaeeV.n|wmAi|rr˄]700== ]qqqUU1;;DQD"LLL`||i¶mZ-4Mj5uj5Ȳl6h4 0}xttDtB B$:W*(b¢(r{{v{./bHEQL&)ICC%Ib2(,u_۶&#ft{{{/?88[׾[EEQxT󼺺 \x׻Izz"I{%l[+H4wwhi0Mx<0Mx}5M˲0#.PqneYh_EQ@uA}߿4}8A@uEDQD,r<lۖmxp!s{ ?IQ!ITUa0 B$ I`& ~5oWWW r8ٶYm7'3vWt< MU|՗dvL|6B pεR ~~R @qh4rN2zujY_ug4M@~mQ3iƶ, O`3qF&kl6ivwwǷ籷 PřBie///L 8)^__`0D"8}0Lp}}͌% A/˲LdY,qLE_,_Heg޲,JR @ @F 7VyƮhP `+l6iFHLE$TU%00 RU$I"Q]H4McbgYwi-IDAT(!P0nfq#ШT?`h ,f @`H"l w~^˷}ϳ~1kZaBEfGEQyA;@Ava>?nQ!Aڶ8?= |H~Oi`o8sz"2$!H]װ,1vdY&QQ/ xbm|<i1,|럾4MTUE+sYfQUst%}'HL&].4h4<_ʔ-1IENDB`tdesktop-1.2.17/Telegram/Resources/icons/send_control_save@2x.png000066400000000000000000000012571326245125100250610ustar00rootroot00000000000000PNG  IHDR$pEvIDATH=HrQrI4됃X6(B .Mќ[HCS495!B "JD.7[Q˛8pq=DDD"Jo0[[[x}}r9( AeBN7}XdH$ĴZ-8s2oy11777XZZFR`j54sqq0(@DyHšaOqsssR}wwnwl3~ "HLR6p8h4Ix fA#\.Ҙ "NC.{ǴZ-f<Qo)P,׿7+Z.IENDB`tdesktop-1.2.17/Telegram/Resources/icons/send_control_send.png000066400000000000000000000011261326245125100244750ustar00rootroot00000000000000PNG  IHDRw=IDATH?hPϵ2VmҡBLN:(;u) Jqsw%H.vq;PKAv=p ^~7G ~(D"UUDB4ʷ7^]]Q4;cl.N\#899,. 6p8SB^777_O `՚+pu}5Jw0K \ 3|>uڶ=dWm )p-.;;;ePU~CUU Nc~xv޿?x^B!HY!x<`0h4m|s] E'ʧ'N&gAfgggښFFx<|yyV#S_?rRUTpViUa_d2aZe"X쎏g~~Ҳ,b|*qV*FM ^$ @˲P,Z1paIi{IENDB`tdesktop-1.2.17/Telegram/Resources/icons/send_control_send@2x.png000066400000000000000000000023411326245125100250470ustar00rootroot00000000000000PNG  IHDR00WIDAThݚKSmǿpgV-c)DajJiňA]y? M* cF4w!.)*j-Z1A0L8S ਕKfٳJ83gÇ\YY8l;A__SI666* @WW%Ilmmqbb/_VtZ d~Ef zyfOdѡ4x@jҥKTTC?gd$I _@kk+ h4ʗ/_jZ fnn=^e 'ׯK!^ӂx^޸q _[+pbI$ |>:t XV~hzt:n$$C۷o\>ydYXX`OOOϑJө@2@9` `٠R ҽ}l6ܹsW^E4E0D"]톌 O(tE08rDQĉ'`4aXjhh A-[+**r_F|˪*r.UB]TYbIENDB`tdesktop-1.2.17/Telegram/Resources/icons/send_control_silent_off.png000066400000000000000000000005721326245125100257000ustar00rootroot00000000000000PNG  IHDRw=AIDATHA0'lwuKLRE'[T.ĿG4 o~U2%Q!;E x"c 13cHJu~OMjEMnRJTUʲZ xnPJ!c035Na!,Iݓ naPJN<u]ql6@uo׭6؝,iWYmXWu[P֋~Vk=v'[ղA%q8!Y(ж-qzEE^y牖m$F+IENDB`tdesktop-1.2.17/Telegram/Resources/icons/send_control_silent_off@2x.png000066400000000000000000000012241326245125100262450ustar00rootroot00000000000000PNG  IHDR00W[IDATh@{1Z#XA=G=AOa#bn-"jAA!i:wq MךYof }~]5瑪r ۶(y*^@#Egz=^t3`&qAAZ2 C7BH&P(v à1O"K)I, l۸i6AZ Ÿ( p@*zv-KH& +~il6AE`YbihZ0L\!x`0`El6(J?-˰nh40s]!{ΑH$0 ,$I%6~y%9]by/-* |@uivNNXhZu~S`zQ|5[1[(~LƑsBaƲ9r^Wc}we.=R lEa+[@ 22>4ɲ=xH,dQ⃐x/?6h_ Q{~ضeXEԋ at]'G\LĎlyx<˲,F4/(EPz?3 1IENDB`tdesktop-1.2.17/Telegram/Resources/icons/send_control_silent_on.png000066400000000000000000000010741326245125100255400ustar00rootroot00000000000000PNG  IHDRw=IDATH?k2A6, `@@),RHa \!G@GM /ޛDw`ggw=ED?5AQmY,rss~Aa˥*Rt]VA^0|+J⺮pMu]) dipuu&u:t]< J1x}}\.c2 rppeYf "b1`>szz19Bږ|Dzf @ xm4M[f<==E@Z<>DbU+|t h4boooc"ca{^^^rwH$ xGRDaveZ-jm;8D˲nFQϓG8j:L&AT3MdYtŗAE-x{{x<*/ғE _O-IENDB`tdesktop-1.2.17/Telegram/Resources/icons/send_control_silent_on@2x.png000066400000000000000000000020331326245125100261060ustar00rootroot00000000000000PNG  IHDR00WIDAThKj]CAD !E f5 a%M@ԨA #yIE$b(F `\|ym۽5 hfsO㸢5JeP(${,(t:!C sT*9 \^n]]]|0B"BKK p8 FSD2yDFKύ`rc}}L&/7l6W(y_ foo`#(zVu. mmmLH$"`#`U2 nYoPp8F,^X,fy:tlnn &i,,0wPz=bp8nx!v|Z@ jpjAP[Zl63 "F$IGkkk uyhFstvv)GGG_\JU@R!3b1 ?@<σy\.!'_ߟZǏ֝ ?IENDB`tdesktop-1.2.17/Telegram/Resources/icons/settings_edit_name.png000066400000000000000000000002301326245125100246330ustar00rootroot00000000000000PNG  IHDRH-_IDAT(A YxpqD4&Aۗ`67PkzD}Ma[.4u^.pYBo00YIENDB`tdesktop-1.2.17/Telegram/Resources/icons/settings_edit_name@2x.png000066400000000000000000000006401326245125100252120ustar00rootroot00000000000000PNG  IHDRr ߔgIDATHս@໑@VvBvi 䵶kARR$T*Dl!fqL4^qHD˶m`fs.f`rd2 8N rh(^*a4MhDMRAunN@"HPaHDodcԞvWm ib QEҴQng;(=`U,{^*= |>vV_Pa`Wl\vbuP *Jp:"IH4N'p?U'LTV ,BVA 9~G͟u]\e3,˒R 9o\)[9jݩIENDB`tdesktop-1.2.17/Telegram/Resources/icons/simple_close.png000066400000000000000000000001741326245125100234530ustar00rootroot00000000000000PNG  IHDR 2ϽCIDAT1 0C|oҡ`sIIEt*<\n[ŒfYsIENDB`tdesktop-1.2.17/Telegram/Resources/icons/simple_close@2x.png000066400000000000000000000002601326245125100240210ustar00rootroot00000000000000PNG  IHDR wIDAT8[ 0zG5@ ;#Jz_Z[Ј 3m@ڥc6UO3 /BI:wҀq9rkSZǦu[^r8ZQ}io+IENDB`tdesktop-1.2.17/Telegram/Resources/icons/slide_shadow.png000066400000000000000000000001471326245125100234420ustar00rootroot00000000000000PNG  IHDR0$-.IDAT}A 0 L7@1!$"ɀl PU:=EwNK< IENDB`tdesktop-1.2.17/Telegram/Resources/icons/slide_shadow@2x.png000066400000000000000000000001641326245125100240130ustar00rootroot00000000000000PNG  IHDR`i;IDAT(cd``π022b).FpC%F-DP τ3G]qIENDB`tdesktop-1.2.17/Telegram/Resources/icons/stickers_add.png000066400000000000000000000002551326245125100234340ustar00rootroot00000000000000PNG  IHDR Vu\tIDAT(K 0 !K?/ Օ *MlYHz9cY"[$qKw/\{N3;~h3odK#Ss}ݹ+uUS PD(_IENDB`tdesktop-1.2.17/Telegram/Resources/icons/stickers_add@2x.png000066400000000000000000000001701326245125100240020ustar00rootroot00000000000000PNG  IHDRw=?IDATH1 0/ӹA͑03&2*DA /+ҦD0_n (rIENDB`tdesktop-1.2.17/Telegram/Resources/icons/stickers_empty.png000066400000000000000000000034051326245125100240420ustar00rootroot00000000000000PNG  IHDRA>IDATxݿO}O uQY窋A"`&^pDb .b,hH:YkI&`ؾQ\Jމ+׷1ը-, 0 C*, 0 C*, 0 C*, 0 C*, 0 C*, 0 C*, 0 C*, 0 C*, 0 C*, 0 C*, 0 C*h۶ettTd޽{nly޾}+޽ׯ_,..6%m¶'O`ס1>@CrI| zzzsy|;[b|@z* N~Ç%hkkz4Ν;׭1>@xp # uݪ8p^kWg(U/~y 7q»EunϖJ"TXRaaHvq]Wy)JR*$K:uxh3~$==ur\wsN>2s-7fJ888A*B6yY k\r/_x" ԳD?$ɚwY?H$X(!f2XsB@Ȣ:'bQDD OR;oRdx"[/_Ν; OR]vϟ O\d 3;;+""}}}'7 O\d +I&'7k65'xϫC1=                                                                                          f=c\|rl23uT=RB{7nh$u3a[oٞK.-[{W}ɐ>QFٵkH$}F{; "@ `cL2ill4PȜ8qtttp8l^j$Nz>̖.]j)--5s1~F棏>2ׯ7W\ r<+ћmmms6~ؾɓ>9}υ *SYYih̅uY[SS>Pr<hveωV تΝ;qjS07|r{Сr1 ϋ28 }f;)Yn]ܒ3gT?7Q锖(؊ 瘝=}bJիv޼yGٟ~)ibs4i?Yk׮GyDIܨl֮]s֪%s=kӦM1v[VV~$&8˜ovء>TyϹZjH۰aCLɜ9s+D}'m6qӢtR644 yoFL466QF/'8<0LQQ|/^h҆۷ǔL]]>sYj8|LHkk缧N޽۳h4jg͚~X8Ç{.~Ja~7==?~ܳGڻKN۾>PPP>W2dya;f̘컢"[*s&8gys|3%3---_reyfϾ/_lǎ~.{&ٌ3<ۡPHi 6{۶m\SSS}ΩK/=}gJfY͛7'lطa䎢>Sikk\@@}d_nL}gffSNyb sv88nσ=Yք믿TӞG++VY?k2uvv^T(w}qKuH?]:vgF;;~4_ߛ:3f̘AﷱѳiNs)<8q_^fff=xT(['On öN:?kۧ~nF}B^{[0wE`WZe͛g'Olsrr[VVy~7u~9--Miԗn^{5rJSPP}E$3x~TVVr /;($`fɒ%f֬Yf& Y`I"3{l3c SZZjM~~4>/װE$\.h@ @ @ @ `B2]]]& OcRxH=s)-))ᶵʠd/֛kR`B> z>*tVǦ]]]fرqdggţ5(8p`?ۿ?Ri.E5`08`p/֛kR`/))Pvwwn Dz^M2^tL.[CCCCCCCC3J{t3}t3g3}tLaa?XD"ܹs~0}}}ʓ#ը^gc&Nh7mdϝ;3뭜;wnڴN8QǍgnj]6bڵkv֭vܸq番Pp*_`p . nG}j7ھq)eh"[VVfsss>ڲ2h"e{ԩ7ndݍN%qKVUU zUUU)>_oG>SIuuRzuN%g϶h3G{{-//O1m{{hΞ=۹v<8|FFmkkpi;mڴkڴiӞcٌ g֛T55513TTT"x555ά7h>mGGuuuǭ#~&p*sz ď;a=~GGz~@YrT.>M @ +##ó}uIE$Y$l;Vi%nG$٥K<J|999NIE$Y{{g{ҤIJ|@֦4($;qg; *M2򕔔xO<4($kjjl/^Xi9rDiw>$+((0.\c1`h϶+N$٥KLss=Jӌ\˗/l777S. (vl/]Ti޽[i^^gd)mooSѨdoΞ=wɱgΜr88BD<Ν;=kDlaa\G}gvژ ֪5\S[[\G}glsssE!yxwknn>O}6ǣ> +i=%svNfwKX 23/ϗ*yoKee|'yMffLv~]f=rHܵD"RxÇSP 0{1SL466P(dZZZLGG &7n$q|&;;bSZZj/^lfΜ9fٲe_M⤸]-Gb3zh~_vÆ v^d@#w}===sʸzݶm-**R!E&LEE)//7sep(a̝;̘1ÔbooHnܸazzzL86455rSa NCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC? FIENDB`tdesktop-1.2.17/Telegram/Resources/icons/stickers_remove.png000066400000000000000000000002501326245125100241740ustar00rootroot00000000000000PNG  IHDR oIDAT81 Ƀ5G+ -$^"F Ude3R&$Š<᪪;t˺p s~}RY:HwLKT IENDB`tdesktop-1.2.17/Telegram/Resources/icons/stickers_remove@2x.png000066400000000000000000000004041326245125100245470ustar00rootroot00000000000000PNG  IHDR((mIDATX 0 K@QJ6qP/5|1!$8o= 0  Ƹ?M{8U*X+SΔ;҃m{P,= Wpzצ|%"A HE,d Y$"A _pY˒|(ηy&]m~73O)&R:*byxIENDB`tdesktop-1.2.17/Telegram/Resources/icons/stickers_reorder.png000066400000000000000000000001601326245125100243410ustar00rootroot00000000000000PNG  IHDR 7IDAT8푱 0 L_GJVp ')"vfbSpNiÅ'SIENDB`tdesktop-1.2.17/Telegram/Resources/icons/stickers_reorder@2x.png000066400000000000000000000002251326245125100247150ustar00rootroot00000000000000PNG  IHDR((m\IDATX1 Ф:3x(H~Dhv 0scn4jI)w2r))w2r))w2r))OMIENDB`tdesktop-1.2.17/Telegram/Resources/icons/stickers_search.png000066400000000000000000000007551326245125100241560ustar00rootroot00000000000000PNG  IHDRJLIDATHA@{ E0P{tTmi[7AD?Ͽ@d&OYQQTv;}Lg2EhRJzwY,8yuM̲,vl6V!! !XհlbE˲Bsl4R $3NQӴfmہ8Mj`rAHylqu]HeL)J)0 m`օΦi$IRyyyH4(IV3,;̺KzJ+rszvvE5,66D@ǾCjMbcI:t7 *JnSRo|܀<Ю*W$)$جwE,6$66D@ϾztجwRc~@a[$gEUUrgh,){ms C^`k/ t: g#l6ÃS/ĶmTU5 /..5x<ƪVX*T*aZzzz TƊNfZxC%@A]_QJ?;oto^a\.c|||i3TPElq48H)EJ):>mE12Gl@< 'A@u:U<g U|✡O3T  Bna ~]ՏC;IENDB`tdesktop-1.2.17/Telegram/Resources/icons/title_back.png000066400000000000000000000002241326245125100230720ustar00rootroot00000000000000PNG  IHDRa[IDAT8 ! DѰE鿚ih<L ?PvHݣ\,I$k8 (3Јg 7e{J'aIENDB`tdesktop-1.2.17/Telegram/Resources/icons/title_button_close.png000066400000000000000000000002441326245125100246740ustar00rootroot00000000000000PNG  IHDR v4AkIDAT( M^}Q!x`rPIY0Bp$1\B,9CrӮfإUz|F 9mIENDB`tdesktop-1.2.17/Telegram/Resources/icons/title_button_close@2x.png000066400000000000000000000003401326245125100252430ustar00rootroot00000000000000PNG  IHDRIDATH 0P_'nc1Ia >UIDAT(cd``@`D3 2H#``s!.` Db*.`d M^ Jo4IENDB`tdesktop-1.2.17/Telegram/Resources/icons/title_button_maximize@2x.png000066400000000000000000000002201326245125100257560ustar00rootroot00000000000000PNG  IHDR ֛^?WIDATH1 0 O;e 5xh4CY8PUw!nw mNu34͐݁ oۿ݁\xF# 4qIENDB`tdesktop-1.2.17/Telegram/Resources/icons/title_button_minimize.png000066400000000000000000000001441326245125100254070ustar00rootroot00000000000000PNG  IHDR $+IDAT(cd``@`Ddĥ)FFF김a2WAIENDB`tdesktop-1.2.17/Telegram/Resources/icons/title_button_minimize@2x.png000066400000000000000000000001721326245125100257620ustar00rootroot00000000000000PNG  IHDR ֛^?AIDATHֱ 0wW& .E 5.@Cۢ`+p9h3(IENDB`tdesktop-1.2.17/Telegram/Resources/icons/title_button_restore.png000066400000000000000000000002101326245125100252430ustar00rootroot00000000000000PNG  IHDR $OIDAT(K @! '\WC˲iBP͆g$s}3jq ~!sg;ƈ঍fS#L$wIENDB`tdesktop-1.2.17/Telegram/Resources/icons/title_button_restore@2x.png000066400000000000000000000002401326245125100256200ustar00rootroot00000000000000PNG  IHDR ֛^?gIDATHQ 0 BuWξ7(aT^ P0r>XWm'rSeg?}$;h@lvb [0n'6d' =i#IENDB`tdesktop-1.2.17/Telegram/Resources/icons/title_menu_dots.png000066400000000000000000000002021326245125100241630ustar00rootroot00000000000000PNG  IHDR|kIIDAT( 0BM rI8=pa$7(b;S2ω@2")U/CxPuIENDB`tdesktop-1.2.17/Telegram/Resources/icons/title_menu_dots@2x.png000066400000000000000000000003241326245125100245420ustar00rootroot00000000000000PNG  IHDR(zIDATHA C! D{ LB 'SHK+VZƅ܈yD$dJ3BRH Qա$^ɸC2T5;t-䜇_M)6:Au?޿!RIQq\/damWyYp0¶<$IENDB`tdesktop-1.2.17/Telegram/Resources/icons/title_search.png000066400000000000000000000006401326245125100234410ustar00rootroot00000000000000PNG  IHDRVΎWgIDAT81@m8\J0 {#݇ B;_s7NqICDɲDLDuz(,K|anh y|>-J"I8X,,ߥ(<k7uq{!}߻q A4M!""Zk׵lf|fEV׋r錕$ 0ev;9a1EQX,Kwִm `OSZk1mkcG;8uUU_SJ4 O8ߑi~ J)!"DNc$2n|?_DD~PWIENDB`tdesktop-1.2.17/Telegram/Resources/icons/title_search@2x.png000066400000000000000000000015221326245125100240130ustar00rootroot00000000000000PNG  IHDR$$IDATX͖1OPOT뀮u@\,q0+?#F? :hqvt#()8b?:>@1q{? sT""АFJDhjj6ٶMi) aЕJh4 ( 0[7@Pe &''RC3affHӨT*x{{8PTL&bXE<<A33M^(wnooΝ*m{ā \VJx -//3ɉHڧr{$}OjD"+g9$˲<4P0d<;xm$ID_^^xzzzh MӘnٶĆa ď@z@n4yh8h4*:c`6&&&|èfx5=bFapk_PVqIRb3m[}x e^q Mt8S8??fgg!2dYF$:ժgeYP?cYP(u?0iŢgJWe?;tLD2?i.꺎|>+<==jB*qwwW L6hggd2 (P690Dqd2tpp].ass2[[[25BDtttD@$I/gƏUjicIENDB`tdesktop-1.2.17/Telegram/Resources/icons/top_bar_call.png000066400000000000000000000006641326245125100234220ustar00rootroot00000000000000PNG  IHDR szz{IDATXֿ0/NjACA\\ .B} W'M E\$(wӕ;A$V ޘ;'O) ()q >;~ϯ\8nE GG lu4QT^?lΩn'h4b6ra0 CB\Gxn ~i2"gj5m; NC\.,˯p8$If3ڶBlT*1!~z=L&KRr^3;Ar:n?kR>esZ]Gx4ahjZqXbumQ HC֬IENDB`tdesktop-1.2.17/Telegram/Resources/icons/top_bar_call@2x.png000066400000000000000000000016111326245125100237650ustar00rootroot00000000000000PNG  IHDR@@iqPIDATxAK*aȘX1Pঠ]n" ZvE7D"M4#pmE"$Cp콫;{<DDjaH/ -@zil Hc ^@[ 4l"Q>k]\\(8VQ\Vq(2NQUN'HEs<==YAjkk{˞@ @qdM0Ep856uqdM9Yar\\c6ggg`wzzJrk, lD"N@+k*|2X訙}0X[[3 t]g1u83OF?tpp2 t "N6t ͍It "Cwrҁ'l:///3\.( ѨIBP$~DQc}}SSSEGG<nw "x^$T*,,, xxXuul'''HHs 0@[[[6#LV@DPUU,"N'|>RTX,~F---T tC /D \__8@D4 +++(J)!Rw `T(,2\.YBi P?Q(`0H~TURD7 4H/ -@zil H~h^xM IENDB`tdesktop-1.2.17/Telegram/Resources/icons/top_bar_profile.png000066400000000000000000000003021326245125100241340ustar00rootroot00000000000000PNG  IHDR szzIDATXM B1 ੷K2-ƅk|@6 iv` I[IDh$m@ ~B1Z[#]`T|xc,NN{Wfw_ĵkp&> f=IENDB`tdesktop-1.2.17/Telegram/Resources/icons/top_bar_profile@2x.png000066400000000000000000000005361326245125100245170ustar00rootroot00000000000000PNG  IHDR@@iq%IDATx1n@ MpiJqKiWx#m0 H+-@4 @ (-@4 @ дE1"TgQ¾g0ZGhavN'3m۶v,Kk7-k߮;hQZFhhQZFhhQZ5(ydL[qi}ٜykkT83+"~:_x)4 @ (-@4 @ <>кAQIENDB`tdesktop-1.2.17/Telegram/Resources/icons/top_bar_search.png000066400000000000000000000007131326245125100237470ustar00rootroot00000000000000PNG  IHDR szzIDATX1@gsHI ),KHf ]LbQ68ngLj'DzcY>F ,#)%)HJIÁ|cۡ8\{֓ևa¶mX!5Atӌ<,a(ij ضݛsPU U*YE1/s1jR9;򡴟r!"tڛB<NV\x<iޑbv,K{ Ec ͦ$=@ [īJP%fm{4MQU|~﮽,K@QCe !P s0hyw_|El[mV T\hW3IENDB`tdesktop-1.2.17/Telegram/Resources/icons/top_bar_search@2x.png000066400000000000000000000022441326245125100243220ustar00rootroot00000000000000PNG  IHDR@@iqkIDATxK:_CZDe(H "$ !"jaR;]'XZEPآ] uE6FR*YΙ9sq?, xt@Z<H@:iH -Q%kjjh4d^* d2A"h4 {{{ PQ>VT*kkk*V^Æ\]]-fqqqbڊ`ht1LSVVVO1R( yxx@׋[ZZiJֆ0x<(~9wxxhb|xi.CTb `yݡJnxxx ߛFKRuW&p2DVcqr5vYgrH$DXCCCUy mݜ>j `5V՜=3L___憫%qָg1q)+X#"gb H0}DMMM\-ATXU@4e4M`j &5D"=3}OQP(\.6cR9сTyfl.{J} Ű^dX^ӕ,;NfGGGhJ0xss8==ELV_]\\ا7r|||D^_zUB<Xւ}H|> ATBoo/CcccX,V,rD,Ѥi VwdnW%(©)vpF# }wxoJqrr333 O~x\%VBŤP(@AOOj|>D)ifH$. (b9NpeXSs:?l^ˁ|_5Jl\JngrYrS?]"<H@:iH - xtzk `qIENDB`tdesktop-1.2.17/Telegram/Resources/icons/upload_chat_photo.png000066400000000000000000000006511326245125100244710ustar00rootroot00000000000000PNG  IHDR|0pIDAT8T@J 6jX+JMo/H>D$-b%3&'7e7oS7@C:vHFbݢV=gRZ-n6^l6s?{1 Ҋy~D8W0"9Nm P.h4 |G.K.==v왦iu_[[A@4)eY#͢R<<8ApR(|>Ӻ1'''( `0Qtu]\\ޡ!^^^qpp_]]_1;WWWm:""u].mrIsssqTpbbbn~~5-$yssÅ y{{p޶+8866fz\^^1 h*g-n 3o:gk{3H5*}:_Lg~{{i[/ qIENDB`tdesktop-1.2.17/Telegram/Resources/icons/volume_mute.png000066400000000000000000000002231326245125100233310ustar00rootroot00000000000000PNG  IHDR v4AZIDAT([ Dѻ52~HKMH4w}au`GX+|ŷȇ3LKY|8Aح1uk S?oIENDB`tdesktop-1.2.17/Telegram/Resources/icons/volume_mute@2x.png000066400000000000000000000003161326245125100237060ustar00rootroot00000000000000PNG  IHDRIDATHI PծAǒqzJDDjPXM5P:lTx`t :? `n~onXua\Y`^ST;\ }D7ZD'iSN-<igިyUIENDB`tdesktop-1.2.17/Telegram/Resources/icons/window_shadow.png000066400000000000000000000003411326245125100236450ustar00rootroot00000000000000PNG  IHDRrP6IDAT8 __א ,0HD- Jg "%)*#eb=:Q1.BUfv=n;3+ zەH3w  @ y`R_O{Yv>â˧O]e'N˟AHIENDB`tdesktop-1.2.17/Telegram/Resources/icons/window_shadow@2x.png000066400000000000000000000005641326245125100242260ustar00rootroot00000000000000PNG  IHDR&&=;IDATXk0`g$ YڇcɌfo[ z k4GƢa7I@Z{pC,35Up2\CD=^ hEDZnDZ)),*E يSa$`˸aX3ܴ"fkZlKPD5nD(>ݰm6k۶)/G}< S?vl־S/7y q6c#0pށhllKfxa,pGw˘f셵Ʋ"w|kIENDB`tdesktop-1.2.17/Telegram/Resources/langs/000077500000000000000000000000001326245125100202565ustar00rootroot00000000000000tdesktop-1.2.17/Telegram/Resources/langs/de.lproj/000077500000000000000000000000001326245125100217735ustar00rootroot00000000000000tdesktop-1.2.17/Telegram/Resources/langs/de.lproj/Localizable.strings000066400000000000000000000003601326245125100256260ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ tdesktop-1.2.17/Telegram/Resources/langs/download.sh000077500000000000000000000021221326245125100224210ustar00rootroot00000000000000#!/usr/bin/env bash set -e FullExecPath=$PWD pushd `dirname $0` > /dev/null FullScriptPath=`pwd` popd > /dev/null if [ ! -d "$FullScriptPath/../../../../TelegramPrivate" ]; then echo "" echo "This script is for building the production version of Telegram Desktop." echo "" echo "For building custom versions please visit the build instructions page at:" echo "https://github.com/telegramdesktop/tdesktop/#build-instructions" exit fi Error () { cd $FullExecPath echo "$1" exit 1 } cd $FullScriptPath/../../../../ while IFS='' read -r line || [[ -n "$line" ]]; do tx pull -f -l $line --minimum-perc=100 done < tdesktop/Telegram/Resources/langs/list cd translations/telegram-desktop.langstrings/ for file in *.strings; do iconv -f "UTF-16LE" -t "UTF-8" "$file" > "../../tdesktop/Telegram/Resources/langs/lang_$file.tmp" awk '{ if (NR==1) sub(/^\xef\xbb\xbf/,""); sub(/ /,""); print }' "../../tdesktop/Telegram/Resources/langs/lang_$file.tmp" > "../../tdesktop/Telegram/Resources/langs/lang_$file" rm "../../tdesktop/Telegram/Resources/langs/lang_$file.tmp" done cd $FullExecPath tdesktop-1.2.17/Telegram/Resources/langs/en.lproj/000077500000000000000000000000001326245125100220055ustar00rootroot00000000000000tdesktop-1.2.17/Telegram/Resources/langs/en.lproj/Localizable.strings000066400000000000000000000003601326245125100256400ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ tdesktop-1.2.17/Telegram/Resources/langs/es.lproj/000077500000000000000000000000001326245125100220125ustar00rootroot00000000000000tdesktop-1.2.17/Telegram/Resources/langs/es.lproj/Localizable.strings000066400000000000000000000003601326245125100256450ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ tdesktop-1.2.17/Telegram/Resources/langs/it.lproj/000077500000000000000000000000001326245125100220175ustar00rootroot00000000000000tdesktop-1.2.17/Telegram/Resources/langs/it.lproj/Localizable.strings000066400000000000000000000003601326245125100256520ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ tdesktop-1.2.17/Telegram/Resources/langs/ko.lproj/000077500000000000000000000000001326245125100220145ustar00rootroot00000000000000tdesktop-1.2.17/Telegram/Resources/langs/ko.lproj/Localizable.strings000066400000000000000000000003601326245125100256470ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ tdesktop-1.2.17/Telegram/Resources/langs/lang.strings000066400000000000000000002335221326245125100226210ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ "lng_language_name" = "English"; "lng_switch_to_this" = "Continue in English"; "lng_menu_contacts" = "Contacts"; "lng_menu_calls" = "Calls"; "lng_menu_settings" = "Settings"; "lng_menu_about" = "About"; "lng_menu_update" = "Update"; "lng_menu_back" = "Back"; "lng_menu_night_mode" = "Night Mode"; "lng_disable_notifications_from_tray" = "Disable notifications"; "lng_enable_notifications_from_tray" = "Enable notifications"; "lng_open_from_tray" = "Open Telegram"; "lng_minimize_to_tray" = "Minimize to tray"; "lng_quit_from_tray" = "Quit Telegram"; "lng_tray_icon_text" = "Telegram is still running here,\nyou can change this from settings page.\nIf this icon disappears from tray menu,\nyou can drag it here from hidden icons."; "lng_month1" = "January"; "lng_month2" = "February"; "lng_month3" = "March"; "lng_month4" = "April"; "lng_month5" = "May"; "lng_month6" = "June"; "lng_month7" = "July"; "lng_month8" = "August"; "lng_month9" = "September"; "lng_month10" = "October"; "lng_month11" = "November"; "lng_month12" = "December"; "lng_month1_small" = "Jan"; "lng_month2_small" = "Feb"; "lng_month3_small" = "Mar"; "lng_month4_small" = "Apr"; "lng_month5_small" = "May"; "lng_month6_small" = "Jun"; "lng_month7_small" = "Jul"; "lng_month8_small" = "Aug"; "lng_month9_small" = "Sep"; "lng_month10_small" = "Oct"; "lng_month11_small" = "Nov"; "lng_month12_small" = "Dec"; "lng_weekday1" = "Mon"; "lng_weekday2" = "Tue"; "lng_weekday3" = "Wed"; "lng_weekday4" = "Thu"; "lng_weekday5" = "Fri"; "lng_weekday6" = "Sat"; "lng_weekday7" = "Sun"; "lng_month_day" = "{month} {day}"; "lng_month_day_year" = "{month} {day}, {year}"; "lng_month_year" = "{month} {year}"; "lng_box_ok" = "OK"; "lng_cancel" = "Cancel"; "lng_continue" = "Continue"; "lng_close" = "Close"; "lng_connecting" = "Connecting..."; "lng_connecting_to_proxy" = "Connecting to proxy..."; "lng_connecting_settings" = "Settings"; "lng_reconnecting#one" = "Reconnect in {count} s..."; "lng_reconnecting#other" = "Reconnect in {count} s..."; "lng_reconnecting_try_now" = "Try now"; "lng_status_service_notifications" = "service notifications"; "lng_status_support" = "support"; "lng_status_bot" = "bot"; "lng_status_bot_reads_all" = "has access to messages"; "lng_status_bot_not_reads_all" = "has no access to messages"; "lng_status_offline" = "last seen a long time ago"; "lng_status_recently" = "last seen recently"; "lng_status_last_week" = "last seen within a week"; "lng_status_last_month" = "last seen within a month"; "lng_status_lastseen_now" = "last seen just now"; "lng_status_lastseen_minutes#one" = "last seen {count} minute ago"; "lng_status_lastseen_minutes#other" = "last seen {count} minutes ago"; "lng_status_lastseen_hours#one" = "last seen {count} hour ago"; "lng_status_lastseen_hours#other" = "last seen {count} hours ago"; "lng_status_lastseen_today" = "last seen today at {time}"; "lng_status_lastseen_yesterday" = "last seen yesterday at {time}"; "lng_status_lastseen_date" = "last seen {date}"; "lng_status_lastseen_date_time" = "last seen {date} at {time}"; "lng_status_online" = "online"; "lng_status_connecting" = "connecting..."; "lng_chat_status_unaccessible" = "group is inaccessible"; "lng_chat_status_members#one" = "{count} member"; "lng_chat_status_members#other" = "{count} members"; "lng_chat_status_online#one" = "{count} online"; "lng_chat_status_online#other" = "{count} online"; "lng_chat_status_members_online" = "{members_count}, {online_count}"; "lng_channel_status" = "channel"; "lng_group_status" = "group"; "lng_channel_members_link#one" = "{count} member"; "lng_channel_members_link#other" = "{count} members"; "lng_channel_admins_link#one" = "{count} administrator"; "lng_channel_admins_link#other" = "{count} administrators"; "lng_flood_error" = "Too many tries. Please try again later."; "lng_gif_error" = "An error has occurred while reading GIF animation :("; "lng_edit_error" = "You cannot edit this message"; "lng_join_channel_error" = "Sorry, you have joined too many channels and supergroups. Please leave some before joining this one."; "lng_error_phone_flood" = "Sorry, you have deleted and re-created your account too many times recently. Please wait for a few days before signing up again."; "lng_error_start_minimized_passcoded" = "You have set a local passcode, so Telegram Desktop can't be launched minimised; it will ask you to enter your passcode before it can start working."; "lng_error_pinned_max#one" = "Sorry, you can pin no more than {count} chat to the top."; "lng_error_pinned_max#other" = "Sorry, you can pin no more than {count} chats to the top."; "lng_error_public_groups_denied" = "Unfortunately, you were banned from participating in public groups.\n{more_info}"; "lng_error_cant_add_member" = "Sorry, you can't add the bot to this group. Ask a group admin to do it."; "lng_error_cant_add_bot" = "Sorry, this bot can't be added to groups."; "lng_error_cant_add_admin_invite" = "Sorry, you can't add this user as an admin because they are not a member of this group and you are not allowed to invite them."; "lng_error_cant_add_admin_unban" = "Sorry, you can't add this user as an admin because they are in the blacklist and you can't unban them."; "lng_error_cant_ban_admin" = "Sorry, you can't ban this user because they are an admin in this group and you are not allowed to demote them."; "lng_sure_add_admin_invite" = "This user is not a member of this group. Add them to the group and promote them to admin?"; "lng_sure_add_admin_invite_channel" = "This user is not a subscriber of this channel. Add them to the channel and promote them to admin?"; "lng_sure_add_admin_unban" = "This user is currently restricted or banned. Are you sure you want to unban and promote them?"; "lng_sure_ban_admin" = "This user is an admin. Are you sure you want to go ahead and restrict them?"; "lng_sure_ban_user_group" = "Ban {user} in the group?"; "lng_sure_enable_socks" = "Are you sure you want to enable this proxy?\n\nServer: {server}\nPort: {port}\n\nYou can change your proxy server later in the Settings (Connection Type)."; "lng_sure_enable" = "Enable"; "lng_edit_deleted" = "This message was deleted"; "lng_edit_too_long" = "Your message text is too long"; "lng_edit_message" = "Edit message"; "lng_edit_message_text" = "New message text..."; "lng_deleted" = "Deleted Account"; "lng_deleted_message" = "Deleted message"; "lng_pinned_message" = "Pinned message"; "lng_pinned_unpin_sure" = "Would you like to unpin this message?"; "lng_pinned_pin_sure" = "Would you like to pin this message?"; "lng_pinned_pin" = "Pin"; "lng_pinned_unpin" = "Unpin"; "lng_pinned_notify" = "Notify all members"; "lng_intro_about" = "Welcome to the official Telegram Desktop app.\nIt's fast and secure."; "lng_start_msgs" = "START MESSAGING"; "lng_intro_next" = "NEXT"; "lng_intro_finish" = "SIGN UP"; "lng_intro_submit" = "SUBMIT"; "lng_photo_caption" = "Caption"; "lng_photos_comment" = "Comment"; "lng_phone_title" = "Your Phone Number"; "lng_phone_desc" = "Please confirm your country code and\nenter your mobile phone number."; "lng_phone_notreg" = "If you don't have a Telegram account yet,\nplease [b]sign up[/b] with {link_start}Android / iPhone{link_end} or {signup_start}here{signup_end}"; "lng_country_code" = "Country Code"; "lng_bad_country_code" = "Invalid Country Code"; "lng_country_ph" = "Search"; "lng_country_none" = "Country not found"; "lng_country_select" = "Select country"; "lng_code_ph" = "Your code"; "lng_code_desc" = "We have sent you a message with activation\ncode to your phone. Please enter it below."; "lng_code_telegram" = "Please enter the code you've just received\nin your previous [b]Telegram[/b] app."; "lng_code_no_telegram" = "Send code via SMS"; "lng_code_call" = "Telegram will call you in {minutes}:{seconds}"; "lng_code_calling" = "Requesting a call from Telegram..."; "lng_code_called" = "Telegram dialed your number"; "lng_bad_phone" = "Invalid phone number. Please try again."; "lng_bad_phone_noreg" = "Phone number not registered."; "lng_bad_code" = "You have entered an invalid code."; "lng_bad_name" = "Please enter your first and last name."; "lng_bad_photo" = "Sorry, Telegram can't process that type of image."; "lng_signin_title" = "Cloud password check"; "lng_signin_desc" = "Please enter your cloud password."; "lng_signin_recover_desc" = "Please enter the code from the e-mail\n{email}"; "lng_signin_password" = "Your cloud password"; "lng_signin_code" = "Code from e-mail"; "lng_signin_recover" = "Forgot password?"; "lng_signin_recover_title" = "Password reset"; "lng_signin_hint" = "Hint: {password_hint}"; "lng_signin_recover_hint" = "Code was sent to {recover_email}"; "lng_signin_bad_password" = "You have entered a wrong password."; "lng_signin_wrong_code" = "You have entered an invalid code."; "lng_signin_try_password" = "Having trouble accessing your e-mail?"; "lng_signin_password_removed" = "Your cloud password was disabled.\nYou can set up a new one in Settings."; "lng_signin_no_email_forgot" = "Since you didn't provide a recovery e-mail when setting up your password, your remaining options are either to remember your password or to reset your account."; "lng_signin_cant_email_forgot" = "If you can't restore access to the e-mail, your remaining options are either to remember your password or to reset your account."; "lng_signin_reset_account" = "Reset your account"; "lng_signin_sure_reset" = "Warning!\n\nYou will lose all your chats and messages, along with any media and files you shared!\n\nDo you want to reset your account?"; "lng_signin_reset" = "Reset"; "lng_signin_reset_wait" = "Since the account {phone_number} is active and protected by a password, we will delete it in 1 week for security purposes. You can cancel this process at any time.\n\nYou’ll be able to reset your account in:\n{when}"; "lng_signin_reset_days#one" = "{count} day"; "lng_signin_reset_days#other" = "{count} days"; "lng_signin_reset_hours#one" = "{count} hour"; "lng_signin_reset_hours#other" = "{count} hours"; "lng_signin_reset_minutes#one" = "{count} minute"; "lng_signin_reset_minutes#other" = "{count} minutes"; "lng_signin_reset_in_days" = "{days_count} {hours_count} {minutes_count}"; "lng_signin_reset_in_hours" = "{hours_count} {minutes_count}"; "lng_signin_reset_cancelled" = "Your recent attempts to reset this account have been cancelled by its active user. Please try again in 7 days."; "lng_signin_banned_text" = "This phone number is banned."; "lng_signin_banned_help" = "Help"; "lng_signup_title" = "Your Info"; "lng_signup_desc" = "Please enter your name and\nupload a photo."; "lng_signup_firstname" = "First Name"; "lng_signup_lastname" = "Last Name"; "lng_dlg_filter" = "Search"; "lng_dlg_new_group_name" = "Group name"; "lng_dlg_new_channel_name" = "Channel name"; "lng_no_contacts" = "You have no contacts"; "lng_no_chats" = "Your chats will be here"; "lng_contacts_loading" = "Loading..."; "lng_contacts_not_found" = "No contacts found"; "lng_dlg_search_for_messages" = "Search for messages"; "lng_update_telegram" = "Update Telegram"; "lng_dlg_search_in" = "Search messages in"; "lng_dlg_search_from" = "From: {user}"; "lng_settings_save" = "Save"; "lng_settings_upload" = "Set Profile Photo"; "lng_settings_crop_profile" = "Select an area for your profile photo"; "lng_settings_uploading_photo" = "Uploading photo..."; "lng_settings_edit" = "Edit"; "lng_settings_drop_area_subtitle" = "to set it as your photo"; "lng_username_title" = "Username"; "lng_username_about" = "You can choose a username on Telegram.\nIf you do, other people will be able to find\nyou by this username and contact you\nwithout knowing your phone number.\n\nYou can use a-z, 0-9 and underscores.\nMinimum length is 5 characters."; "lng_username_choose" = "Choose your username."; "lng_username_invalid" = "This username is invalid."; "lng_username_occupied" = "This username is already occupied."; "lng_username_too_short" = "This username is too short."; "lng_username_bad_symbols" = "Only a-z, 0-9, and underscores allowed."; "lng_username_available" = "This username is available."; "lng_username_not_found" = "User @{user} not found."; "lng_username_link_willbe" = "This link will open a chat with you:"; "lng_username_link" = "This link opens a chat with you:"; "lng_username_copied" = "Link copied to clipboard."; "lng_bio_title" = "Edit your bio"; "lng_bio_placeholder" = "Bio"; "lng_bio_about" = "You can add a few lines about yourself. Anyone who opens your profile will see this text."; "lng_settings_section_info" = "Info"; "lng_settings_username" = "Username:"; "lng_settings_choose_username" = "Choose username"; "lng_settings_empty_bio" = "None"; "lng_settings_section_notify" = "Notifications"; "lng_settings_desktop_notify" = "Desktop notifications"; "lng_settings_show_name" = "Show sender's name"; "lng_settings_show_preview" = "Show message preview"; "lng_settings_use_windows" = "Use Windows notifications"; "lng_settings_use_native_notifications" = "Use native notifications"; "lng_settings_advanced_notifications" = "Notifications position and count"; "lng_settings_notifications_position" = "Location on the screen"; "lng_settings_notifications_count" = "Notifications count"; "lng_settings_sound_notify" = "Play sound"; "lng_settings_include_muted" = "Include muted chats in unread count"; "lng_notification_preview" = "You have a new message"; "lng_notification_reply" = "Reply"; "lng_notification_hide_all" = "Hide all"; "lng_notification_sample" = "This is a sample notification"; "lng_settings_section_general" = "General"; "lng_settings_change_lang" = "Change language"; "lng_languages" = "Languages"; "lng_sure_save_language" = "Telegram will restart in order to change language"; "lng_settings_update_automatically" = "Update automatically"; "lng_settings_current_version_label" = "Version {version}:"; "lng_settings_current_version" = "Version {version}"; "lng_settings_check_now" = "Check for updates"; "lng_settings_update_checking" = "Checking for updates..."; "lng_settings_latest_installed" = "Latest version is installed"; "lng_settings_downloading" = "Downloading update {ready} / {total} MB..."; "lng_settings_update_ready" = "New version is ready"; "lng_settings_update_now" = "Restart Now"; "lng_settings_update_fail" = "Update check failed :("; "lng_settings_workmode_tray" = "Show tray icon"; "lng_settings_workmode_window" = "Show taskbar icon"; "lng_settings_auto_start" = "Launch Telegram when system starts"; "lng_settings_start_min" = "Launch minimized"; "lng_settings_add_sendto" = "Place Telegram in \"Send to\" menu"; "lng_settings_section_scale" = "Interface Scale"; "lng_settings_scale_auto" = "Auto ({cur})"; "lng_settings_section_chat_settings" = "Chat Settings"; "lng_settings_replace_emojis" = "Replace emoji"; "lng_settings_suggest_by_emoji" = "Suggest popular stickers by emoji"; "lng_settings_view_emojis" = "View list"; "lng_settings_send_enter" = "Send by Enter"; "lng_settings_send_ctrlenter" = "Send by Ctrl+Enter"; "lng_settings_send_cmdenter" = "Send by Cmd+Enter"; "lng_settings_section_background" = "Chat background"; "lng_settings_bg_use_default" = "Use default color theme"; "lng_settings_bg_from_gallery" = "Choose from gallery"; "lng_settings_bg_from_file" = "Choose from file"; "lng_settings_bg_edit_theme" = "Edit theme"; "lng_settings_bg_tile" = "Tile background"; "lng_settings_adaptive_wide" = "Adaptive layout for wide screens"; "lng_backgrounds_header" = "Choose your new chat background"; "lng_theme_sure_keep" = "Keep this theme?"; "lng_theme_reverting#one" = "Reverting to the old theme in {count} second."; "lng_theme_reverting#other" = "Reverting to the old theme in {count} seconds."; "lng_theme_keep_changes" = "Keep changes"; "lng_theme_revert" = "Revert"; "lng_download_path_dont_ask" = "Don't ask download path for each file"; "lng_download_path_label" = "Download path:"; "lng_download_path_temp" = "temp folder"; "lng_download_path_default" = "default folder"; "lng_download_path_clear" = "Clear all"; "lng_download_path_header" = "Choose download path"; "lng_download_path_default_radio" = "Telegram folder in system «Downloads»"; "lng_download_path_temp_radio" = "Temp folder, cleared on logout or uninstall"; "lng_download_path_dir_radio" = "Custom folder, cleared only manually"; "lng_download_path_choose" = "Choose download path"; "lng_sure_clear_downloads" = "Do you want to remove all downloaded files from temp folder? It is done automatically on logout or program uninstall."; "lng_download_path_failed" = "File download could not be started.\n\nThis might be because the download location you've selected is invalid. Try changing the \"Download path\" in Settings."; "lng_download_path_settings" = "Settings"; "lng_download_finish_failed" = "File download could not be finished.\n\nWould you like to try again?"; "lng_download_path_clearing" = "Clearing..."; "lng_download_path_cleared" = "Cleared!"; "lng_settings_section_privacy" = "Privacy and Security"; "lng_local_storage_title" = "Local storage"; "lng_settings_no_data_cached" = "No cached data found!"; "lng_settings_images_cached#one" = "{count} image, {size}"; "lng_settings_images_cached#other" = "{count} images, {size}"; "lng_settings_audios_cached#one" = "{count} voice message, {size}"; "lng_settings_audios_cached#other" = "{count} voice messages, {size}"; "lng_local_storage_clear" = "Clear all"; "lng_local_storage_clearing" = "Clearing..."; "lng_local_storage_cleared" = "Cleared!"; "lng_settings_section_advanced_settings" = "Advanced Settings"; "lng_settings_enable_night_theme" = "Enable night mode"; "lng_settings_disable_night_theme" = "Disable night mode"; "lng_passcode_remove_button" = "Remove"; "lng_passcode_turn_on" = "Turn on local passcode"; "lng_passcode_change" = "Change local passcode"; "lng_passcode_create" = "Local passcode"; "lng_passcode_remove" = "Remove local passcode"; "lng_passcode_turn_off" = "Turn off"; "lng_passcode_autolock" = "Auto-Lock"; "lng_passcode_autolock_away" = "Auto-Lock if away for:"; "lng_passcode_autolock_inactive" = "Auto-Lock if inactive for:"; "lng_passcode_autolock_minutes#one" = "{count} minute"; "lng_passcode_autolock_minutes#other" = "{count} minutes"; "lng_passcode_autolock_hours#one" = "{count} hour"; "lng_passcode_autolock_hours#other" = "{count} hours"; "lng_passcode_enter_old" = "Enter current passcode"; "lng_passcode_enter_first" = "Enter a passcode"; "lng_passcode_enter_new" = "Enter new passcode"; "lng_passcode_confirm_new" = "Re-enter new passcode"; "lng_passcode_about" = "When a local passcode is set, a lock icon appears at the top of your chats list. Click it to lock the app.\n\nNote: if you forget your local passcode, you'll need to relogin in Telegram Desktop."; "lng_passcode_differ" = "Passcodes are different"; "lng_passcode_wrong" = "Wrong passcode"; "lng_passcode_is_same" = "Passcode was not changed"; "lng_passcode_enter" = "Enter your local passcode"; "lng_passcode_ph" = "Your passcode"; "lng_passcode_submit" = "Submit"; "lng_passcode_logout" = "Log out"; "lng_passcode_need_unblock" = "You need to unlock me first."; "lng_cloud_password_waiting" = "Confirmation link sent to {email}..."; "lng_cloud_password_change" = "Change cloud password"; "lng_cloud_password_create" = "Cloud password"; "lng_cloud_password_remove" = "Remove cloud password"; "lng_cloud_password_set" = "Enable two-step verification"; "lng_cloud_password_edit" = "Change cloud password"; "lng_cloud_password_enter_old" = "Enter current password"; "lng_cloud_password_enter_first" = "Enter a password"; "lng_cloud_password_enter_new" = "Enter new password"; "lng_cloud_password_confirm_new" = "Re-enter new password"; "lng_cloud_password_hint" = "Enter password hint"; "lng_cloud_password_change_hint" = "Enter new password hint"; "lng_cloud_password_bad" = "Password and hint cannot be the same."; "lng_cloud_password_email" = "Enter recovery e-mail"; "lng_cloud_password_bad_email" = "Incorrect e-mail, please try other."; "lng_cloud_password_about" = "This password will be asked when you log in on a new device in addition to the SMS code."; "lng_cloud_password_about_recover" = "Warning! Are you sure you don't want to\nadd a password recovery e-mail?\n\nIf you forget your password, you will\nlose access to your Telegram account."; "lng_cloud_password_skip_email" = "Skip e-mail"; "lng_cloud_password_almost" = "A confirmation link was sent to the\ne-mail you provided. Two-step verification will be enabled as soon as you follow that link."; "lng_cloud_password_was_set" = "Two-step verification enabled."; "lng_cloud_password_updated" = "Your cloud password was updated."; "lng_cloud_password_removed" = "Two-step verification was disabled."; "lng_cloud_password_differ" = "Passwords do not match"; "lng_cloud_password_wrong" = "Wrong cloud password"; "lng_cloud_password_is_same" = "Password was not changed"; "lng_connection_type" = "Connection type:"; "lng_connection_auto_connecting" = "Default (connecting...)"; "lng_connection_auto" = "Default ({transport} used)"; "lng_connection_proxy_connecting" = "Connecting through proxy..."; "lng_connection_proxy" = "{transport} with proxy"; "lng_connection_header" = "Connection type"; "lng_connection_auto_rb" = "Auto (TCP if available or HTTP)"; "lng_connection_http_proxy_rb" = "HTTP with custom http-proxy"; "lng_connection_tcp_proxy_rb" = "TCP with custom socks5-proxy"; "lng_connection_try_ipv6" = "Try connecting through IPv6"; "lng_connection_host_ph" = "Hostname"; "lng_connection_port_ph" = "Port"; "lng_connection_user_ph" = "Username"; "lng_connection_password_ph" = "Password"; "lng_connection_save" = "Save"; "lng_settings_blocked_users" = "Blocked users"; "lng_settings_last_seen_privacy" = "Last seen privacy"; "lng_settings_calls_privacy" = "Phone calls privacy"; "lng_settings_groups_invite_privacy" = "Group invite settings"; "lng_settings_show_sessions" = "Show all sessions"; "lng_settings_self_destruct" = "Account self-destruct settings"; "lng_settings_change_phone" = "Change phone number"; "lng_settings_reset" = "Terminate all other sessions"; "lng_settings_reset_sure" = "Are you sure you want to terminate\nall other sessions?"; "lng_settings_reset_one_sure" = "Do you want to terminate this session?"; "lng_settings_reset_button" = "Terminate"; "lng_settings_manage_local_storage" = "Manage local storage"; "lng_settings_ask_question" = "Ask a Question"; "lng_settings_ask_sure" = "Please note that Telegram Support is run by volunteers. We try to respond as quickly as possible, but it may take a while.\n\nPlease take a look at the Telegram FAQ: it has important troubleshooting tips and answers to most questions."; "lng_settings_faq_button" = "Go to FAQ"; "lng_settings_ask_ok" = "Ask a Volunteer"; "lng_settings_faq" = "Telegram FAQ"; "lng_settings_logout" = "Log Out"; "lng_sure_logout" = "Are you sure you want to log out?"; "lng_settings_need_restart" = "You need to restart for applying some of the new settings. Restart now?"; "lng_settings_restart_now" = "RESTART"; "lng_sessions_header" = "Current session"; "lng_sessions_other_header" = "Active sessions"; "lng_sessions_other_desc" = "You can log in to Telegram from other mobile, tablet and desktop devices, using the same phone number. All your data will be instantly synchronized."; "lng_sessions_terminate_all" = "Terminate all other sessions"; "lng_blocked_list_title" = "Blocked users"; "lng_blocked_list_unknown_phone" = "unknown phone number"; "lng_blocked_list_unblock" = "Unblock"; "lng_blocked_list_add" = "Block user"; "lng_blocked_list_add_title" = "Select user to block"; "lng_blocked_list_already_blocked" = "blocked already"; "lng_blocked_list_about" = "Blocked users can't send you messages or add you to groups. They will not see your profile pictures, online and last seen status."; "lng_blocked_list_not_found" = "No users found."; "lng_edit_privacy_everyone" = "Everybody"; "lng_edit_privacy_contacts" = "My contacts"; "lng_edit_privacy_nobody" = "Nobody"; "lng_edit_privacy_exceptions" = "Add exceptions"; "lng_edit_privacy_lastseen_title" = "Last seen privacy"; "lng_edit_privacy_lastseen_description" = "You can choose who can see your last seen time:"; "lng_edit_privacy_lastseen_warning" = "Important: you won't be able to see Last Seen times for people with whom you don't share your Last Seen time. Approximate last seen will be shown instead (recently, within a week, within a month)."; "lng_edit_privacy_lastseen_always_empty" = "Always share with"; "lng_edit_privacy_lastseen_always#one" = "Always share with {count} user"; "lng_edit_privacy_lastseen_always#other" = "Always share with {count} users"; "lng_edit_privacy_lastseen_never_empty" = "Never share with"; "lng_edit_privacy_lastseen_never#one" = "Never share with {count} user"; "lng_edit_privacy_lastseen_never#other" = "Never share with {count} users"; "lng_edit_privacy_lastseen_exceptions" = "These settings will override the values above."; "lng_edit_privacy_lastseen_always_title" = "Always share with"; "lng_edit_privacy_lastseen_never_title" = "Never share with"; "lng_edit_privacy_groups_title" = "Group invite settings"; "lng_edit_privacy_groups_description" = "You can choose who can add you to groups and channels with granular precision:"; "lng_edit_privacy_groups_always_empty" = "Always allow"; "lng_edit_privacy_groups_always#one" = "Always allow {count} user"; "lng_edit_privacy_groups_always#other" = "Always allow {count} users"; "lng_edit_privacy_groups_never_empty" = "Never allow"; "lng_edit_privacy_groups_never#one" = "Never allow {count} user"; "lng_edit_privacy_groups_never#other" = "Never allow {count} users"; "lng_edit_privacy_groups_exceptions" = "These users will or will not be able to add you to groups and channels regardless of the settings above."; "lng_edit_privacy_groups_always_title" = "Always allow"; "lng_edit_privacy_groups_never_title" = "Never allow"; "lng_edit_privacy_calls_title" = "Telegram call privacy"; "lng_edit_privacy_calls_description" = "You can restrict who can call you:"; "lng_edit_privacy_calls_always_empty" = "Always allow"; "lng_edit_privacy_calls_always#one" = "Always allow {count} user"; "lng_edit_privacy_calls_always#other" = "Always allow {count} users"; "lng_edit_privacy_calls_never_empty" = "Never allow"; "lng_edit_privacy_calls_never#one" = "Never allow {count} user"; "lng_edit_privacy_calls_never#other" = "Never allow {count} users"; "lng_edit_privacy_calls_exceptions" = "These users will or will not be able to call you regardless of the settings above."; "lng_edit_privacy_calls_always_title" = "Always allow"; "lng_edit_privacy_calls_never_title" = "Never allow"; "lng_self_destruct_title" = "Account self-destruction"; "lng_self_destruct_description" = "If you don't come online at least once within this period, your account will be deleted along with all groups, messages and contacts."; "lng_self_destruct_months#one" = "{count} month"; "lng_self_destruct_months#other" = "{count} months"; "lng_self_destruct_years#one" = "{count} year"; "lng_self_destruct_years#other" = "{count} years"; "lng_change_phone_title" = "Change phone number"; "lng_change_phone_description" = "You can change your Telegram number\nhere. Your account and all your cloud data\n— messages, media, contacts, etc. will be\nmoved to the new number.\n\n[b]Important[/b]: all your Telegram contacts will\nget your [b]new number[/b] added to their address\nbook, provided they had your old number and\nyou haven't blocked them in Telegram."; "lng_change_phone_warning" = "All your Telegram contacts will get your new number added to their address book, provided they had your old number and you haven't blocked them in Telegram."; "lng_change_phone_occupied" = "The number {phone} is already connected to a Telegram account. Please delete that account before migrating to the new number."; "lng_change_phone_button" = "Change number"; "lng_change_phone_new_title" = "Enter new number"; "lng_change_phone_new_description" = "We will send an SMS with a confirmation code to your new number."; "lng_change_phone_new_submit" = "Submit"; "lng_change_phone_code_title" = "Enter code"; "lng_change_phone_code_description" = "We've sent an SMS with a confirmation code to your phone {phone}."; "lng_change_phone_success" = "Your phone number has been changed."; "lng_mute_duration_hours#one" = "For {count} hour"; "lng_mute_duration_hours#other" = "For {count} hours"; "lng_mute_box_tip" = "Choose for how long you would like to turn off notifications for the following chat"; "lng_preview_loading" = "Getting Link Info..."; "lng_profile_settings_section" = "Settings"; "lng_profile_actions_section" = "Actions"; "lng_profile_bot_settings" = "Bot Settings"; "lng_profile_bot_help" = "Bot Help"; "lng_profile_invite_link_section" = "Invite link"; "lng_profile_create_public_link" = "Create public link"; "lng_profile_edit_public_link" = "Edit public link"; "lng_profile_search_members" = "Search members"; "lng_profile_manage_admins" = "Manage administrators"; "lng_profile_manage_blocklist" = "Manage banned users"; "lng_profile_manage_restrictedlist" = "Manage restricted users"; "lng_profile_recent_actions" = "Recent actions"; "lng_profile_common_groups#one" = "{count} group in common"; "lng_profile_common_groups#other" = "{count} groups in common"; "lng_profile_participants_section" = "Members"; "lng_profile_mobile_number" = "Mobile:"; "lng_profile_username" = "Username:"; "lng_profile_link" = "Link:"; "lng_profile_bio" = "Bio:"; "lng_profile_add_contact" = "Add Contact"; "lng_profile_edit_contact" = "Edit"; "lng_profile_clear_and_exit" = "Delete and exit"; "lng_profile_leave_channel" = "Leave channel"; "lng_profile_delete_channel" = "Delete channel"; "lng_profile_leave_group" = "Leave group"; "lng_profile_delete_group" = "Delete group"; "lng_profile_report" = "Report"; "lng_profile_search_messages" = "Search for messages"; "lng_profile_block_bot" = "Stop and block bot"; "lng_profile_unblock_bot" = "Unblock bot"; "lng_profile_invite_to_group" = "Add to Group"; "lng_profile_delete_contact" = "Delete"; "lng_profile_set_group_photo" = "Set Photo"; "lng_profile_add_participant" = "Add Members"; "lng_profile_view_channel" = "View Channel"; "lng_profile_join_channel" = "Join Channel"; "lng_profile_join_group" = "Join Group"; "lng_profile_delete_and_exit" = "Leave"; "lng_profile_kick" = "Remove"; "lng_profile_sure_kick" = "Remove {user} from the group?"; "lng_profile_sure_kick_channel" = "Remove {user} from the channel?"; "lng_profile_sure_remove_admin" = "Remove {user} from admins?"; "lng_profile_loading" = "Loading..."; "lng_profile_photos#one" = "{count} photo"; "lng_profile_photos#other" = "{count} photos"; "lng_profile_photos_header" = "Photos"; "lng_profile_videos#one" = "{count} video"; "lng_profile_videos#other" = "{count} videos"; "lng_profile_videos_header" = "Videos"; "lng_profile_songs#one" = "{count} audio file"; "lng_profile_songs#other" = "{count} audio files"; "lng_profile_songs_header" = "Audio files"; "lng_profile_files#one" = "{count} file"; "lng_profile_files#other" = "{count} files"; "lng_profile_files_header" = "Files"; "lng_profile_audios#one" = "{count} voice message"; "lng_profile_audios#other" = "{count} voice messages"; //"lng_profile_rounds#one" = "{count} video message"; //"lng_profile_rounds#other" = "{count} video messages"; "lng_profile_audios_header" = "Voice messages"; "lng_profile_shared_links#one" = "{count} shared link"; "lng_profile_shared_links#other" = "{count} shared links"; "lng_profile_shared_links_header" = "Shared links"; "lng_profile_copy_phone" = "Copy Phone Number"; "lng_profile_copy_fullname" = "Copy Name"; "lng_profile_drop_area_title" = "Drop your image here"; "lng_profile_drop_area_subtitle" = "to set it as a group photo"; "lng_profile_drop_area_subtitle_channel" = "to set it as a channel photo"; "lng_profile_top_bar_share_contact" = "Share"; "lng_profile_info_section" = "Info"; "lng_info_tab_media" = "Media"; "lng_info_mobile_label" = "Mobile"; "lng_info_username_label" = "Username"; "lng_info_bio_label" = "Bio"; "lng_info_link_label" = "Link"; "lng_info_about_label" = "About"; "lng_info_user_title" = "User Info"; "lng_info_bot_title" = "Bot Info"; "lng_info_group_title" = "Group Info"; "lng_info_channel_title" = "Channel Info"; "lng_profile_enable_notifications" = "Notifications"; "lng_profile_send_message" = "Send Message"; "lng_info_add_as_contact" = "Add as contact"; "lng_profile_shared_media" = "Shared media"; "lng_media_type_photos" = "Photos"; "lng_media_type_videos" = "Videos"; "lng_media_type_songs" = "Audio files"; "lng_media_type_files" = "Files"; "lng_media_type_audios" = "Voice messages"; "lng_media_type_links" = "Shared links"; "lng_media_type_rounds" = "Video messages"; "lng_profile_common_groups_section" = "Groups in common"; "lng_profile_share_contact" = "Share Contact"; "lng_info_edit_contact" = "Edit contact"; "lng_info_delete_contact" = "Delete contact"; "lng_info_share_contact" = "Share contact"; "lng_profile_clear_history" = "Clear history"; "lng_profile_delete_conversation" = "Delete conversation"; "lng_profile_block_user" = "Block user"; "lng_profile_unblock_user" = "Unblock user"; "lng_media_selected_photo#one" = "{count} Photo"; "lng_media_selected_photo#other" = "{count} Photos"; "lng_media_selected_video#one" = "{count} Video"; "lng_media_selected_video#other" = "{count} Videos"; "lng_media_selected_song#one" = "{count} Audio file"; "lng_media_selected_song#other" = "{count} Audio files"; "lng_media_selected_file#one" = "{count} File"; "lng_media_selected_file#other" = "{count} Files"; "lng_media_selected_audio#one" = "{count} Voice message"; "lng_media_selected_audio#other" = "{count} Voice messages"; //"lng_media_selected_round#one" = "{count} Video message"; //"lng_media_selected_round#other" = "{count} Video messages"; "lng_media_selected_link#one" = "{count} Shared link"; "lng_media_selected_link#other" = "{count} Shared links"; "lng_media_photo_empty" = "No photos here yet"; "lng_media_video_empty" = "No videos here yet"; "lng_media_song_empty" = "No music files here yet"; "lng_media_file_empty" = "No files here yet"; "lng_media_audio_empty" = "No voice messages here yet"; "lng_media_link_empty" = "No shared links here yet"; "lng_media_song_empty_search" = "No music files found"; "lng_media_file_empty_search" = "No files found"; "lng_media_link_empty_search" = "No shared links found"; "lng_manage_group_title" = "Manage Group"; "lng_manage_channel_title" = "Manage Channel"; "lng_manage_group_info" = "Group Info"; "lng_manage_channel_info" = "Channel Info"; "lng_manage_peer_recent_actions" = "Recent Actions"; "lng_manage_peer_members" = "Members"; "lng_manage_peer_administrators" = "Administrators"; "lng_manage_peer_banned_users" = "Banned users"; "lng_manage_peer_restricted_users" = "Restricted users"; "lng_manage_history_visibility_title" = "Chat history for new members"; "lng_manage_history_visibility_shown" = "Visible"; "lng_manage_history_visibility_shown_about" = "New members will see messages that were sent before they joined."; "lng_manage_history_visibility_hidden" = "Hidden"; "lng_manage_history_visibility_hidden_about" = "New members won't see earlier messages."; "lng_report_title" = "Report channel"; "lng_report_group_title" = "Report group"; "lng_report_bot_title" = "Report bot"; "lng_report_reason_spam" = "Spam"; "lng_report_reason_violence" = "Violence"; "lng_report_reason_pornography" = "Pornography"; "lng_report_reason_other" = "Other"; "lng_report_reason_description" = "Description"; "lng_report_button" = "Report"; "lng_report_thanks" = "Thank you! Your report will be reviewed by our team very soon."; "lng_channel_add_members" = "Add members"; "lng_channel_add_banned" = "Ban user"; "lng_channel_add_restricted" = "Restrict user"; "lng_channel_admins" = "Administrators"; "lng_channel_add_admin" = "Add Administrator"; "lng_channel_admin_status_creator" = "Creator"; "lng_channel_admin_status_promoted_by" = "Promoted by {user}"; "lng_channel_admin_status_not_admin" = "Not administrator"; "lng_channel_banned_status_restricted_by" = "Restricted by {user}"; "lng_group_blocked_list_about" = "Banned users are removed from the group and can only come back if invited by an admin.\nInvite links don't work for them."; "lng_channel_blocked_list_about" = "Banned users are removed from the channel.\nInvite links don't work for them."; "lng_chat_all_members_admins" = "All Members Are Admins"; "lng_chat_about_all_admins" = "Group members can add new members, edit name and photo of the group."; "lng_chat_about_admins" = "Group admins can add and remove members, edit name and photo of the group."; "lng_participant_filter" = "Search"; "lng_participant_invite" = "Invite"; "lng_participant_invite_sorry#one" = "Sorry, you can only add the first {count} member to a channel personally.\n\nFrom now on, people will need to join via your invite link."; "lng_participant_invite_sorry#other" = "Sorry, you can only add the first {count} members to a channel personally.\n\nFrom now on, people will need to join via your invite link."; "lng_create_group_back" = "Back"; "lng_create_group_next" = "Next"; "lng_create_group_create" = "Create"; "lng_create_group_title" = "New Group"; "lng_create_channel_title" = "New Channel"; "lng_create_public_channel_title" = "Public Channel"; "lng_create_public_channel_about" = "Anyone can find the channel in search and join"; "lng_create_private_channel_title" = "Private Channel"; "lng_create_private_channel_about" = "Only people with a special invite link may join"; "lng_create_public_group_title" = "Public Group"; "lng_create_public_group_about" = "Anyone can find the group in search and join, chat history is available to everybody"; "lng_create_private_group_title" = "Private Group"; "lng_create_private_group_about" = "People can only join if they were invited or have an invite link"; "lng_create_group_skip" = "Skip"; "lng_create_channel_link_invalid" = "This link is invalid"; "lng_create_channel_link_occupied" = "Sorry, this link is already occupied"; "lng_create_channel_link_too_short" = "Sorry, this link is too short"; "lng_create_channel_link_bad_symbols" = "Only 0-9, a-z, and underscores allowed."; "lng_create_channel_link_available" = "This link is available"; "lng_create_channel_link_copied" = "Link copied to clipboard"; "lng_create_group_crop" = "Select an area for group photo"; "lng_create_channel_crop" = "Select an area for channel photo"; "lng_failed_add_participant" = "Could not add user. Please try again later."; "lng_failed_add_not_mutual" = "Sorry, if a person leaves a group, only a mutual contact can bring them back (they need to have your phone number, and you need theirs)."; "lng_failed_add_not_mutual_channel" = "Sorry, if a person leaves a channel, only a mutual contact can bring them back (they need to have your phone number, and you need theirs)."; "lng_sure_delete_contact" = "Are you sure you want to delete {contact} from your contact list?"; "lng_sure_delete_history" = "Are you sure you want to delete all message history with {contact}?\n\nThis action cannot be undone."; "lng_sure_delete_group_history" = "Are you sure, you want to delete all message history in «{group}»?\n\nThis action cannot be undone."; "lng_sure_delete_and_exit" = "Are you sure, you want to delete all message history and leave «{group}»?\n\nThis action cannot be undone."; "lng_sure_leave_channel" = "Are you sure you want to leave\nthis channel?"; "lng_sure_delete_channel" = "Are you sure you want to delete this channel? All members will be removed and all messages will be lost."; "lng_sure_leave_group" = "Are you sure you want to leave this group?"; "lng_sure_delete_group" = "Are you sure, you want to delete this group? All members will be removed and all messages will be lost."; "lng_sure_delete_saved_messages" = "Are you sure, you want to delete all your saved messages?\n\nThis action cannot be undone."; "lng_message_empty" = "Empty Message"; "lng_message_unsupported" = "This message is not supported by your version of Telegram Desktop. Please update to the latest version in Settings, or install it from {link}"; "lng_duration_seconds#one" = "{count} second"; "lng_duration_seconds#other" = "{count} seconds"; "lng_duration_minsec_minutes#one" = "{count} min"; "lng_duration_minsec_minutes#other" = "{count} min"; "lng_duration_minsec_seconds#one" = "{count} sec"; "lng_duration_minsec_seconds#other" = "{count} sec"; "lng_duration_minutes_seconds" = "{minutes_count} {seconds_count}"; "lng_action_add_user" = "{from} added {user}"; "lng_action_add_users_many" = "{from} added {users}"; "lng_action_add_users_and_one" = "{accumulated}, {user}"; "lng_action_add_users_and_last" = "{accumulated} and {user}"; "lng_action_add_you" = "{from} added you to this channel"; "lng_action_you_joined" = "You joined this channel"; "lng_action_add_you_group" = "{from} added you to this group"; "lng_action_kick_user" = "{from} removed {user}"; "lng_action_user_left" = "{from} left the group"; "lng_action_user_joined" = "{from} joined the group"; "lng_action_user_joined_by_link" = "{from} joined the group via invite link"; "lng_action_user_registered" = "{from} just joined Telegram"; "lng_action_removed_photo" = "{from} removed group photo"; "lng_action_removed_photo_channel" = "Channel photo removed"; "lng_action_changed_photo" = "{from} updated group photo"; "lng_action_changed_photo_channel" = "Channel photo updated"; "lng_action_changed_title" = "{from} changed group name to «{title}»"; "lng_action_changed_title_channel" = "Channel name was changed to «{title}»"; "lng_action_created_chat" = "{from} created group «{title}»"; "lng_action_created_channel" = "Channel created"; "lng_action_group_migrate" = "The group was upgraded to a supergroup"; "lng_action_pinned_message" = "{from} pinned «{text}»"; "lng_action_pinned_media" = "{from} pinned {media}"; "lng_action_pinned_media_photo" = "a photo"; "lng_action_pinned_media_video" = "a video"; "lng_action_pinned_media_audio" = "an audio file"; "lng_action_pinned_media_voice" = "a voice message"; "lng_action_pinned_media_video_message" = "a video message"; "lng_action_pinned_media_file" = "a file"; "lng_action_pinned_media_gif" = "a GIF animation"; "lng_action_pinned_media_contact" = "a contact information"; "lng_action_pinned_media_location" = "a location mark"; "lng_action_pinned_media_sticker" = "a sticker"; "lng_action_pinned_media_emoji_sticker" = "a {emoji} sticker"; "lng_action_pinned_media_game" = "the game «{game}»"; "lng_action_game_score#one" = "{from} scored {count} in {game}"; "lng_action_game_score#other" = "{from} scored {count} in {game}"; "lng_action_game_you_scored#one" = "You scored {count} in {game}"; "lng_action_game_you_scored#other" = "You scored {count} in {game}"; "lng_action_game_score_no_game#one" = "{from} scored {count}"; "lng_action_game_score_no_game#other" = "{from} scored {count}"; "lng_action_game_you_scored_no_game#one" = "You scored {count}"; "lng_action_game_you_scored_no_game#other" = "You scored {count}"; "lng_action_payment_done" = "You have just successfully transferred {amount} to {user}"; "lng_action_payment_done_for" = "You have just successfully transferred {amount} to {user} for {invoice}"; "lng_action_took_screenshot" = "{from} took a screenshot!"; "lng_action_you_took_screenshot" = "You took a screenshot!"; "lng_action_bot_allowed_from_domain" = "You allowed this bot to message you when you logged in on {domain}."; "lng_ttl_photo_received" = "{from} sent you a self-destructing photo. Please view it on your mobile."; "lng_ttl_photo_sent" = "You sent a self-destructing photo."; "lng_ttl_photo_expired" = "Photo has expired"; "lng_ttl_video_received" = "{from} sent you a self-destructing video. Please view it on your mobile."; "lng_ttl_video_sent" = "You sent a self-destructing video."; "lng_ttl_video_expired" = "Video has expired"; "lng_profile_migrate_reached#one" = "{count} member limit reached"; "lng_profile_migrate_reached#other" = "{count} members limit reached"; "lng_profile_migrate_body" = "To add more members, you can upgrade your group to a supergroup."; "lng_profile_migrate_learn_more" = "Learn more »"; "lng_profile_migrate_button" = "Upgrade to supergroup"; "lng_profile_convert_title" = "Convert to supergroup"; "lng_profile_convert_feature1" = "— New members see the full message history"; "lng_profile_convert_feature2" = "— Messages are deleted for all members"; "lng_profile_convert_feature3" = "— Admins can pin important messages"; "lng_profile_convert_feature4" = "— Creator can set a public link for the group"; "lng_profile_convert_warning" = "{bold_start}Note:{bold_end} This action can not be undone"; "lng_profile_convert_confirm" = "Convert"; "lng_profile_add_more_after_upgrade#one" = "You will be able to add up to {count} member after you upgrade your group to a supergroup."; "lng_profile_add_more_after_upgrade#other" = "You will be able to add up to {count} members after you upgrade your group to a supergroup."; "lng_channel_not_accessible" = "Sorry, this channel is not accessible."; "lng_group_not_accessible" = "Sorry, this group is not accessible."; "lng_group_full" = "Sorry, this group is full."; "lng_channels_too_much_public_about" = "Sorry, you have reserved too many public usernames. You can revoke the link from one of your older groups or channels."; "lng_channels_too_much_public_revoke_confirm_group" = "Are you sure you want to revoke the link {link}?\n\nThe group «{group}» will become private."; "lng_channels_too_much_public_revoke_confirm_channel" = "Are you sure you want to revoke the link {link}?\n\nThe channel «{group}» will become private."; "lng_channels_too_much_public_revoke" = "Revoke"; "lng_group_invite_bad_link" = "This invite link is broken or has expired."; "lng_group_invite_join" = "Join"; "lng_group_invite_members#one" = "{count} member, among them:"; "lng_group_invite_members#other" = "{count} members, among them:"; "lng_group_invite_create" = "Create an invite link"; "lng_group_invite_about" = "Telegram users will be able to join\nyour group by following this link."; "lng_group_invite_about_channel" = "Telegram users will be able to join\nyour channel by following this link."; "lng_group_invite_create_new" = "Revoke invite link"; "lng_group_invite_about_new" = "Your previous link will be deactivated and we'll generate a new invite link for you."; "lng_group_invite_copied" = "Invite link copied to clipboard."; "lng_group_invite_no_room" = "Unable to join this group because there are too many members in it already."; "lng_channel_public_link_copied" = "Link copied to clipboard."; "lng_forwarded" = "Forwarded from {user}"; "lng_forwarded_date" = "Original: {date}"; "lng_forwarded_channel" = "Forwarded from {channel}"; "lng_forwarded_via" = "Forwarded from {user} via {inline_bot}"; "lng_forwarded_channel_via" = "Forwarded from {channel} via {inline_bot}"; "lng_forwarded_signed" = "{channel} ({user})"; "lng_in_reply_to" = "In reply to"; "lng_edited" = "edited"; "lng_edited_date" = "Edited: {date}"; "lng_admin_badge" = "admin"; "lng_fast_reply" = "Reply"; "lng_cancel_edit_post_sure" = "Cancel editing?"; "lng_cancel_edit_post_yes" = "Yes"; "lng_cancel_edit_post_no" = "No"; "lng_bot_share_location_unavailable" = "Sorry, location sharing is currently unavailable in Telegram Desktop."; "lng_bot_share_phone" = "Do you want to share your phone number with this bot?"; "lng_bot_share_phone_confirm" = "Share"; "lng_attach_failed" = "Failed"; "lng_attach_file" = "File"; "lng_attach_photo" = "Photo"; "lng_media_open_with" = "Open With"; "lng_media_download" = "Download"; "lng_media_cancel" = "Cancel"; "lng_media_video" = "Video"; "lng_media_audio" = "Voice message"; "lng_media_auto_settings" = "Automatic media download settings"; "lng_media_auto_photo" = "Automatic photo download"; "lng_media_auto_audio" = "Automatic voice message download"; "lng_media_auto_gif" = "Automatic GIF download"; "lng_media_auto_private_chats" = "Private chats"; "lng_media_auto_groups" = "Groups and channels"; "lng_media_auto_play" = "Autoplay"; "lng_emoji_category0" = "Frequently used"; "lng_emoji_category1" = "People"; "lng_emoji_category2" = "Nature"; "lng_emoji_category3" = "Food & Drink"; "lng_emoji_category4" = "Activity"; "lng_emoji_category5" = "Travel & Places"; "lng_emoji_category6" = "Objects"; "lng_emoji_category7" = "Symbols & Flags"; "lng_emoji_hide_panel" = "Click here to hide the emoji sidebar"; "lng_recent_stickers" = "Frequently used"; "lng_faved_stickers_add" = "Add to Favorites"; "lng_faved_stickers_remove" = "Remove from Favorites"; "lng_group_stickers" = "Group stickers"; "lng_group_stickers_description" = "You can choose a sticker set which will be available for every member while in the group chat."; "lng_group_stickers_add" = "Choose sticker set"; "lng_switch_stickers" = "Stickers"; "lng_switch_emoji" = "Emoji"; "lng_switch_gifs" = "GIFs"; "lng_stickers_featured_add" = "Add"; "lng_gifs_search" = "Search GIFs"; "lng_gifs_no_saved" = "You have no saved GIFs yet."; "lng_inline_bot_no_results" = "No results."; "lng_inline_bot_via" = "via {inline_bot}"; "lng_box_remove" = "Remove"; "lng_stickers_installed_tab" = "Stickers"; "lng_stickers_featured_tab" = "Trending"; "lng_stickers_archived_tab" = "Archived"; "lng_stickers_remove_pack" = "Remove «{sticker_pack}»?"; "lng_stickers_add_pack" = "Add stickers"; "lng_stickers_share_pack" = "Share Stickers"; "lng_stickers_not_found" = "Sticker pack not found."; "lng_stickers_packs_archived" = "Some of your unused stickers have been archived to make room for the sets you've activated."; "lng_stickers_copied" = "Sticker pack link copied to clipboard."; "lng_stickers_default_set" = "Great Minds"; "lng_stickers_you_have" = "Manage and reorder sticker packs"; "lng_stickers_return" = "Undo"; "lng_stickers_count#one" = "{count} sticker"; "lng_stickers_count#other" = "{count} stickers"; "lng_stickers_masks_pack" = "This is a pack of mask stickers. You can use them in the photo editor on our mobile apps."; "lng_stickers_group_set" = "Group sticker set"; "lng_stickers_remove_group_set" = "Remove group sticker set?"; "lng_stickers_group_from_your" = "Choose from your stickers"; "lng_stickers_group_from_featured" = "Choose from trending stickers"; "lng_stickers_search_sets" = "Search sticker sets"; "lng_stickers_nothing_found" = "No stickers found"; "lng_stickers_remove_pack_confirm" = "Remove"; "lng_in_dlg_photo" = "Photo"; "lng_in_dlg_album" = "Album"; "lng_in_dlg_video" = "Video"; "lng_in_dlg_audio_file" = "Audio file"; "lng_in_dlg_contact" = "Contact"; "lng_in_dlg_audio" = "Voice message"; "lng_in_dlg_video_message" = "Video message"; "lng_in_dlg_file" = "File"; "lng_in_dlg_sticker" = "Sticker"; "lng_in_dlg_sticker_emoji" = "{emoji} Sticker"; "lng_ban_user" = "Ban User"; "lng_delete_all_from" = "Delete all from this user"; "lng_report_spam" = "Report Spam"; "lng_report_spam_hide" = "Hide"; "lng_report_spam_thanks" = "Thank you for your report!"; "lng_report_spam_sure" = "Are you sure you want to report spam from this user?"; "lng_report_spam_sure_group" = "Are you sure you want to report spam in this group?"; "lng_report_spam_sure_channel" = "Are you sure you want to report spam in this channel?"; "lng_report_spam_ok" = "Report"; "lng_cant_send_to_not_contact" = "Sorry, you can only send messages to\nmutual contacts at the moment.\n{more_info}"; "lng_cant_invite_not_contact" = "Sorry, you can only add mutual contacts\nto groups at the moment.\n{more_info}"; "lng_cant_more_info" = "More info »"; "lng_cant_invite_banned" = "Sorry, only admin can add this user."; "lng_cant_invite_privacy" = "Sorry, you cannot add this user to groups because of their privacy settings."; "lng_cant_invite_privacy_channel" = "Sorry, you cannot add this user to channels because of their privacy settings."; "lng_cant_do_this" = "Sorry, this action is unavailable."; "lng_send_button" = "Send"; "lng_message_ph" = "Write a message..."; "lng_broadcast_ph" = "Broadcast a message..."; "lng_broadcast_silent_ph" = "Silent broadcast..."; "lng_record_cancel" = "Release outside this field to cancel"; "lng_will_be_notified" = "Members will be notified when you post"; "lng_wont_be_notified" = "Members will not be notified when you post"; "lng_willbe_history" = "Please select a chat to start messaging"; "lng_from_you" = "You"; "lng_from_draft" = "Draft"; "lng_bot_description" = "What can this bot do?"; "lng_unblock_button" = "Unblock"; "lng_channel_mute" = "Mute"; "lng_channel_unmute" = "Unmute"; "lng_saved_messages" = "Saved Messages"; "lng_saved_short" = "Save"; "lng_saved_forward_here" = "Forward messages here for quick access"; "lng_dialogs_text_with_from" = "{from_part} {message}"; "lng_dialogs_text_from_wrapped" = "{from}:"; "lng_dialogs_text_media" = "{media_part} {caption}"; "lng_dialogs_text_media_wrapped" = "{media},"; "lng_dialogs_show_all_chats" = "Show all chats"; "lng_dialogs_hide_muted_chats" = "Hide muted chats"; "lng_open_this_link" = "Open this link?"; "lng_open_link" = "Open"; "lng_allow_bot_pass" = "Allow {bot_name} to pass your Telegram name and ID to the web pages you open via this bot?"; "lng_allow_bot" = "Allow"; "lng_bot_start" = "Start"; "lng_bot_choose_group" = "Select a Group"; "lng_bot_no_groups" = "You have no groups"; "lng_bot_groups_not_found" = "No groups found"; "lng_bot_sure_invite" = "Add the bot to «{group}»?"; "lng_bot_already_in_group" = "The bot is already a member of the group."; "lng_bot_choose_chat" = "Select a chat"; "lng_bot_no_chats" = "You have no chats"; "lng_bot_chats_not_found" = "No chats found"; "lng_bot_sure_share_game" = "Share the game with {user}?"; "lng_bot_sure_share_game_group" = "Share the game with «{group}»?"; "lng_typing" = "typing"; "lng_user_typing" = "{user} is typing"; "lng_users_typing" = "{user} and {second_user} are typing"; "lng_many_typing#one" = "{count} person is typing"; "lng_many_typing#other" = "{count} people are typing"; "lng_playing_game" = "playing a game"; "lng_user_playing_game" = "{user} is playing a game"; "lng_users_playing_game" = "{user} and {second_user} are playing a game"; "lng_many_playing_game#one" = "{count} person is playing a game"; "lng_many_playing_game#other" = "{count} people are playing a game"; "lng_send_action_record_video" = "recording a video"; "lng_user_action_record_video" = "{user} is recording a video"; "lng_send_action_upload_video" = "sending a video"; "lng_user_action_upload_video" = "{user} is sending a video"; "lng_send_action_record_audio" = "recording a voice message"; "lng_user_action_record_audio" = "{user} is recording a voice message"; "lng_send_action_upload_audio" = "sending a voice message"; "lng_user_action_upload_audio" = "{user} is sending a voice message"; "lng_send_action_record_round" = "recording a video message"; "lng_user_action_record_round" = "{user} is recording a video message"; "lng_send_action_upload_round" = "sending a video message"; "lng_user_action_upload_round" = "{user} is sending a video message"; "lng_send_action_upload_photo" = "sending a photo"; "lng_user_action_upload_photo" = "{user} is sending a photo"; "lng_send_action_upload_file" = "sending a file"; "lng_user_action_upload_file" = "{user} is sending a file"; "lng_unread_bar#one" = "{count} unread message"; "lng_unread_bar#other" = "{count} unread messages"; "lng_unread_bar_some" = "Unread messages"; "lng_maps_point" = "Location"; "lng_save_photo" = "Save image"; "lng_save_video" = "Save video"; "lng_save_audio_file" = "Save audio file"; "lng_save_audio" = "Save voice message"; "lng_save_file" = "Save file"; "lng_save_downloaded" = "{ready} / {total} {mb}"; "lng_duration_and_size" = "{duration}, {size}"; "lng_duration_played" = "{played} / {duration}"; "lng_date_and_duration" = "{date}, {duration}"; "lng_choose_image" = "Choose an image"; "lng_choose_files" = "Choose files"; "lng_game_tag" = "Game"; "lng_context_view_profile" = "View profile"; "lng_context_view_group" = "View group info"; "lng_context_view_channel" = "View channel info"; "lng_context_view_feed_info" = "View feed info"; "lng_context_pin_to_top" = "Pin to top"; "lng_context_unpin_from_top" = "Unpin from top"; "lng_context_promote_admin" = "Promote to admin"; "lng_context_edit_permissions" = "Edit permissions"; "lng_context_restrict_user" = "Restrict user"; "lng_context_remove_from_group" = "Remove from group"; "lng_context_copy_link" = "Copy Link"; "lng_context_copy_post_link" = "Copy Post Link"; "lng_context_copy_email" = "Copy Email Address"; "lng_context_copy_hashtag" = "Copy Hashtag"; "lng_context_copy_mention" = "Copy Username"; "lng_context_save_image" = "Save Image As..."; "lng_context_copy_image" = "Copy Image"; "lng_context_cancel_download" = "Cancel Download"; "lng_context_show_in_folder" = "Show in Folder"; "lng_context_show_in_finder" = "Show in Finder"; "lng_context_save_video" = "Save Video As..."; "lng_context_save_audio_file" = "Save Audio File As..."; "lng_context_save_audio" = "Save Voice Message As..."; "lng_context_pack_info" = "Pack Info"; "lng_context_pack_add" = "Add Stickers"; "lng_context_save_file" = "Save File As..."; "lng_context_copy_text" = "Copy Text"; "lng_context_open_gif" = "Open GIF"; "lng_context_save_gif" = "Save GIF"; "lng_context_to_msg" = "Go To Message"; "lng_context_reply_msg" = "Reply"; "lng_context_edit_msg" = "Edit"; "lng_context_forward_msg" = "Forward Message"; "lng_context_delete_msg" = "Delete Message"; "lng_context_select_msg" = "Select Message"; "lng_context_pin_msg" = "Pin Message"; "lng_context_unpin_msg" = "Unpin Message"; "lng_context_cancel_upload" = "Cancel Upload"; "lng_context_copy_selected" = "Copy Selected Text"; "lng_context_copy_selected_items" = "Copy Selected as Text"; "lng_context_forward_selected" = "Forward Selected"; "lng_context_delete_selected" = "Delete Selected"; "lng_context_clear_selection" = "Clear Selection"; "lng_send_image_empty" = "Could not send an empty file: {name}"; "lng_send_image_too_large" = "Could not send a file, because it is larger than 1500 MB: {name}"; "lng_send_images_selected#one" = "{count} image selected"; "lng_send_images_selected#other" = "{count} images selected"; "lng_send_photos#one" = "Send {count} photo"; "lng_send_photos#other" = "Send {count} photos"; "lng_send_separate_photos" = "Send as separate photos"; "lng_send_separate_photos_videos" = "Send as separate media"; "lng_send_files_selected#one" = "{count} file selected"; "lng_send_files_selected#other" = "{count} files selected"; "lng_send_files#one" = "Send {count} file"; "lng_send_files#other" = "Send {count} files"; "lng_send_album" = "Send as an album"; "lng_send_photo" = "Send as a photo"; "lng_send_file" = "Send as a file"; "lng_forward_choose" = "Choose recipient..."; "lng_forward_cant" = "Sorry, no way to forward here :("; "lng_forward_share_contact" = "Share contact to {recipient}?"; "lng_forward_share_cant" = "Sorry, no way to share contact here :("; "lng_forward_send_files_cant" = "Sorry, no way to send media here :("; "lng_forward_send" = "Send"; "lng_forward_messages#one" = "{count} forwarded message"; "lng_forward_messages#other" = "{count} forwarded messages"; "lng_forwarding_from#one" = "{user} and {count} other"; "lng_forwarding_from#other" = "{user} and {count} others"; "lng_forwarding_from_two" = "{user} and {second_user}"; "lng_inline_switch_choose" = "Choose conversation..."; "lng_inline_switch_cant" = "Sorry, no way to write here :("; "lng_share_cant" = "Sorry, no way to share here :("; "lng_reply_cant" = "Sorry, no way to reply to an old message in supergroup :("; "lng_reply_cant_forward" = "Sorry, you can't reply to a message that was sent before the group was upgraded to a supergroup. Do you wish to forward it and add your comment?"; "lng_share_title" = "Share to"; "lng_share_copy_link" = "Copy share link"; "lng_share_confirm" = "Send"; "lng_share_wrong_user" = "This game was opened from a different user."; "lng_share_game_link_copied" = "Game link copied to clipboard."; "lng_share_done" = "Done!"; "lng_contact_phone" = "Phone Number"; "lng_enter_contact_data" = "New Contact"; "lng_edit_contact_title" = "Edit contact name"; "lng_edit_channel_title" = "Edit channel"; "lng_edit_sign_messages" = "Sign messages"; "lng_edit_group_who_invites" = "Who can add members"; "lng_edit_group_invites_everybody" = "All members"; "lng_edit_group_invites_only_admins" = "Only admins"; "lng_edit_group" = "Edit group"; "lng_edit_self_title" = "Edit your name"; "lng_confirm_contact_data" = "New Contact"; "lng_add_contact" = "Create"; "lng_add_contact_button" = "New contact"; "lng_contacts_header" = "Contacts"; "lng_contact_not_joined" = "Unfortunately {name} has not joined Telegram yet, but you can send them an invitation.\n\nWe will notify you about any of your contacts who join Telegram."; "lng_try_other_contact" = "Try someone else"; "lng_create_group_link" = "Link"; "lng_create_group_invite_link" = "Invite link"; "lng_create_group_description" = "Description (optional)"; "lng_drag_images_here" = "Drop images here"; "lng_drag_photos_here" = "Drop photos here"; "lng_drag_files_here" = "Drop files here"; "lng_drag_to_send_quick" = "to send them in a quick way"; "lng_drag_to_send_no_compression" = "to send them without compression"; "lng_drag_to_send_files" = "to send them as files"; "lng_selected_clear" = "Cancel"; "lng_selected_delete" = "Delete"; "lng_selected_forward" = "Forward"; "lng_selected_cancel_sure_this" = "Cancel uploading?"; "lng_selected_upload_stop" = "Stop"; "lng_selected_delete_sure_this" = "Do you want to delete this message?"; "lng_selected_delete_sure#one" = "Do you want to delete {count} message?"; "lng_selected_delete_sure#other" = "Do you want to delete {count} messages?"; "lng_delete_photo_sure" = "Do you want to delete this photo?"; "lng_delete_for_everyone_hint#one" = "This will delete it for everyone in this chat."; "lng_delete_for_everyone_hint#other" = "This will delete them for everyone in this chat."; "lng_delete_for_me_chat_hint#one" = "This will delete it just for you, not for other participants of the chat."; "lng_delete_for_me_chat_hint#other" = "This will delete them just for you, not for other participants of the chat."; "lng_delete_for_me_hint#one" = "This will delete it just for you."; "lng_delete_for_me_hint#other" = "This will delete them just for you."; "lng_delete_for_everyone_check" = "Delete for everyone"; "lng_delete_for_other_check" = "Delete for {user}"; "lng_box_delete" = "Delete"; "lng_box_leave" = "Leave"; "lng_about_version" = "version {version}"; "lng_about_text_1" = "Official free messaging app based on [a href=\"https://core.telegram.org/api\"]Telegram API[/a]\nfor speed and security."; "lng_about_text_2" = "This software is licensed under [a href=\"https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE\"]GNU GPL[/a] version 3.\nSource code is available on [a href=\"https://github.com/telegramdesktop/tdesktop\"]GitHub[/a]."; "lng_about_text_3" = "Visit {faq_open}Telegram FAQ{faq_close} for more info."; "lng_about_done" = "Done"; "lng_search_no_results" = "No messages found"; "lng_search_found_results#one" = "Found {count} message"; "lng_search_found_results#other" = "Found {count} messages"; "lng_search_global_results" = "Global search results"; "lng_search_messages_from" = "Show messages from"; "lng_media_save_progress" = "{ready} of {total} {mb}"; "lng_mediaview_save_as" = "Save As..."; "lng_mediaview_copy" = "Copy"; "lng_mediaview_forward" = "Forward"; "lng_mediaview_delete" = "Delete"; "lng_mediaview_photos_all" = "View all photos"; "lng_mediaview_files_all" = "View all files"; "lng_mediaview_single_photo" = "Single Photo"; "lng_mediaview_group_photo" = "Group Photo"; "lng_mediaview_channel_photo" = "Channel Photo"; "lng_mediaview_profile_photo" = "Profile Photo"; "lng_mediaview_file_n_of_count" = "{file} {n} of {count}"; "lng_mediaview_n_of_count" = "Photo {n} of {count}"; "lng_mediaview_doc_image" = "File"; "lng_mediaview_today" = "today at {time}"; "lng_mediaview_yesterday" = "yesterday at {time}"; "lng_mediaview_date_time" = "{date} at {time}"; "lng_mediaview_saved" = "Image was saved to your [c]Downloads[/c] folder"; "lng_theme_preview_title" = "Theme Preview"; "lng_theme_preview_generating" = "Generating color theme preview..."; "lng_theme_preview_invalid" = "Invalid data in this theme file."; "lng_theme_preview_apply" = "Apply this theme"; "lng_new_authorization" = "{name},\nWe detected a login into your account from a new device on {day}, {date} at {time}\n\nDevice: {device}\nLocation: {location}\n\nIf this wasn't you, you can go to Settings — Show all sessions and terminate that session.\n\nIf you think that somebody logged in to your account against your will, you can enable two-step verification in Settings.\n\nSincerely,\nThe Telegram Team"; "lng_new_version_wrap" = "Telegram Desktop was updated to version {version}\n\n{changes}\n\nFull version history is available here:\n{link}"; "lng_new_version_minor" = "— Bug fixes and other minor improvements"; "lng_menu_insert_unicode" = "Insert Unicode control character"; "lng_full_name" = "{first_name} {last_name}"; "lng_confirm_phone_link_invalid" = "This link is broken or has expired."; "lng_confirm_phone_title" = "Cancel account reset"; "lng_confirm_phone_about" = "Somebody with access to your phone number {phone} has requested to delete your Telegram account and reset your 2-Step Verification password.\n\nIf this wasn't you, please enter the code we've just sent you via SMS to your number."; "lng_confirm_phone_success" = "Success!\n\nThe deletion process was cancelled for your account {phone}. You may close this window now."; "lng_confirm_phone_send" = "Send"; "lng_confirm_phone_enter_code" = "Please enter the code."; "lng_theme_editor_no_keys" = "No keys in the palette yet"; "lng_theme_editor_cant_change_theme" = "You can't apply a new theme while you're editing the colour palette. Please close the theme editor first."; "lng_theme_editor_new_keys" = "Not in the palette yet"; "lng_theme_editor_background_image" = "Background image"; "lng_theme_editor_saved_to_jpg" = "Saved to JPEG, {size}"; "lng_theme_editor_read_from_jpg" = "JPEG image, {size}"; "lng_theme_editor_read_from_png" = "PNG image, {size}"; "lng_theme_editor_export" = "Export"; "lng_theme_editor_choose_image" = "Choose background image"; "lng_theme_editor_save_palette" = "Save palette file"; "lng_theme_editor_choose_name" = "Save theme file"; "lng_theme_editor_error" = "The editor encountered an error :( See 'log.txt' for details."; "lng_theme_editor_done" = "Theme exported successfully!"; "lng_theme_editor_title" = "Edit color palette"; "lng_theme_editor_export_button" = "Export theme"; "lng_payments_not_supported" = "Sorry, Telegram Desktop doesn't support payments yet. Please use one of our mobile apps to do this."; "lng_payments_receipt_label" = "Receipt"; "lng_payments_receipt_label_test" = "Test receipt"; "lng_payments_invoice_label" = "Invoice"; "lng_payments_invoice_label_test" = "Test invoice"; "lng_payments_receipt_button" = "Receipt"; "lng_call_status_incoming" = "is calling you..."; "lng_call_status_connecting" = "connecting..."; "lng_call_status_exchanging" = "exchanging encryption keys..."; "lng_call_status_waiting" = "waiting..."; "lng_call_status_requesting" = "requesting..."; "lng_call_status_hanging" = "hanging up..."; "lng_call_status_ended" = "call ended"; "lng_call_status_failed" = "failed to connect"; "lng_call_status_ringing" = "ringing..."; "lng_call_status_busy" = "line busy"; "lng_call_fingerprint_tooltip" = "If emoji on {user}'s screen are the same, this call is 100% secure"; "lng_call_error_not_available" = "Sorry, {user} doesn't accept calls."; "lng_call_error_outdated" = "{user}'s app does not support calls. They need to update their app before you can call them."; "lng_call_error_audio_io" = "There seems to be a problem with audio playback on your computer. Please make sure that your computer's speakers and microphone are working and try again."; "lng_call_bar_hangup" = "End call"; "lng_call_box_title" = "Calls"; "lng_call_box_about" = "You haven't made any Telegram calls yet."; "lng_call_box_status_today" = "{time}"; "lng_call_box_status_yesterday" = "Yesterday at {time}"; "lng_call_box_status_date" = "{date} at {time}"; "lng_call_box_status_group" = "({count}) {status}"; "lng_call_outgoing" = "Outgoing call"; "lng_call_incoming" = "Incoming call"; "lng_call_missed" = "Missed call"; "lng_call_cancelled" = "Cancelled call"; "lng_call_declined" = "Declined call"; "lng_call_duration_info" = "{time}, {duration}"; "lng_call_type_and_duration" = "{type} ({duration})"; "lng_call_rate_label" = "Please rate the quality of your call"; "lng_call_rate_comment" = "Comment (optional)"; "lng_player_message_today" = "Today at {time}"; "lng_player_message_yesterday" = "Yesterday at {time}"; "lng_player_message_date" = "{date} at {time}"; "lng_rights_edit_admin" = "Manage permissions"; "lng_rights_edit_admin_header" = "What can this admin do?"; "lng_rights_about_add_admins_yes" = "This admin will be able to add new admins with the same (or more limited) permissions."; "lng_rights_about_add_admins_no" = "This admin will not be able to add new admins."; "lng_rights_about_admin_cant_edit" = "You cannot edit rights of this admin."; "lng_rights_user_restrictions" = "User restrictions"; "lng_rights_user_restrictions_header" = "What can this user do?"; "lng_rights_channel_info" = "Change channel info"; "lng_rights_channel_post" = "Post messages"; "lng_rights_channel_edit" = "Edit messages of others"; "lng_rights_channel_delete" = "Delete messages of others"; "lng_rights_group_info" = "Change group info"; "lng_rights_group_ban" = "Ban users"; "lng_rights_group_invite_link" = "Invite users via link"; "lng_rights_group_invite" = "Add users"; "lng_rights_group_pin" = "Pin messages"; "lng_rights_group_delete" = "Delete messages"; "lng_rights_add_admins" = "Add new admins"; "lng_rights_chat_read" = "Read messages"; "lng_rights_chat_send_text" = "Send messages"; "lng_rights_chat_send_media" = "Send media"; "lng_rights_chat_send_stickers" = "Send stickers & GIFs"; "lng_rights_chat_send_links" = "Embed links"; "lng_rights_chat_banned_until_header" = "Restricted until"; "lng_rights_chat_banned_forever" = "Forever"; "lng_rights_chat_banned_day#one" = "For {count} day"; "lng_rights_chat_banned_day#other" = "For {count} days"; "lng_rights_chat_banned_week#one" = "For {count} week"; "lng_rights_chat_banned_week#other" = "For {count} weeks"; "lng_rights_chat_banned_custom" = "Custom"; "lng_rights_chat_banned_custom_date" = "Until {date}"; "lng_restricted_send_message" = "The admins of this group restricted you from writing here."; "lng_restricted_send_media" = "The admins of this group restricted you from posting media content here."; "lng_restricted_send_stickers" = "The admins of this group restricted you from posting stickers here."; "lng_restricted_send_gifs" = "The admins of this group restricted you from posting GIFs here."; "lng_restricted_send_inline" = "The admins of this group restricted you from posting inline content here."; "lng_restricted_list_title" = "Restricted users"; "lng_banned_list_title" = "Banned users"; "lng_admin_log_title_all" = "All actions"; "lng_admin_log_title_selected" = "Selected actions"; "lng_admin_log_filter" = "Filter"; "lng_admin_log_filter_title" = "Filter"; "lng_admin_log_filter_all_actions" = "All actions"; "lng_admin_log_filter_restrictions" = "New restrictions"; "lng_admin_log_filter_admins_new" = "New admins"; "lng_admin_log_filter_members_new" = "New members"; "lng_admin_log_filter_info_group" = "Group info"; "lng_admin_log_filter_info_channel" = "Channel info"; "lng_admin_log_filter_messages_deleted" = "Deleted messages"; "lng_admin_log_filter_messages_edited" = "Edited messages"; "lng_admin_log_filter_messages_pinned" = "Pinned messages"; "lng_admin_log_filter_members_removed" = "Leaving members"; "lng_admin_log_filter_all_admins" = "All users and admins"; "lng_admin_log_about" = "What is this?"; "lng_admin_log_about_text" = "This is a list of all service actions taken by the group's members and admins in the last 48 hours."; "lng_admin_log_no_results_title" = "No actions found"; "lng_admin_log_no_results_text" = "No recent actions that match your query have been found."; "lng_admin_log_no_results_search_text" = "No recent actions that contain '{query}' have been found."; "lng_admin_log_no_events_title" = "No actions yet"; "lng_admin_log_no_events_text" = "There were no service actions\ntaken by the group's members\nand admins in the last 48 hours."; "lng_admin_log_empty_text" = "Empty"; "lng_admin_log_changed_title_group" = "{from} changed group name to «{title}»"; "lng_admin_log_changed_title_channel" = "{from} changed channel name to «{title}»"; "lng_admin_log_changed_description_group" = "{from} edited group description:"; "lng_admin_log_removed_description_group" = "{from} removed group description"; "lng_admin_log_changed_description_channel" = "{from} edited channel description:"; "lng_admin_log_removed_description_channel" = "{from} removed channel description"; "lng_admin_log_previous_description" = "Previous description"; "lng_admin_log_changed_link_group" = "{from} changed group link:"; "lng_admin_log_removed_link_group" = "{from} removed group link"; "lng_admin_log_changed_link_channel" = "{from} changed channel link:"; "lng_admin_log_removed_link_channel" = "{from} removed channel link"; "lng_admin_log_previous_link" = "Previous link"; "lng_admin_log_changed_photo_group" = "{from} changed group photo"; "lng_admin_log_changed_photo_channel" = "{from} changed channel photo"; "lng_admin_log_removed_photo_group" = "{from} removed group photo"; "lng_admin_log_removed_photo_channel" = "{from} removed channel photo"; "lng_admin_log_invites_enabled" = "{from} enabled group invites"; "lng_admin_log_invites_disabled" = "{from} disabled group invites"; "lng_admin_log_signatures_enabled" = "{from} enabled signatures"; "lng_admin_log_signatures_disabled" = "{from} disabled signatures"; "lng_admin_log_history_made_hidden" = "{from} made group history hidden for new members"; "lng_admin_log_history_made_visible" = "{from} made group history visible for new members"; "lng_admin_log_pinned_message" = "{from} pinned message:"; "lng_admin_log_unpinned_message" = "{from} unpinned message"; "lng_admin_log_edited_caption" = "{from} edited caption:"; "lng_admin_log_removed_caption" = "{from} removed caption"; "lng_admin_log_previous_caption" = "Original caption"; "lng_admin_log_edited_message" = "{from} edited message:"; "lng_admin_log_previous_message" = "Original message"; "lng_admin_log_deleted_message" = "{from} deleted message:"; "lng_admin_log_participant_joined" = "{from} joined the group"; "lng_admin_log_participant_joined_channel" = "{from} joined the channel"; "lng_admin_log_participant_left" = "{from} left the group"; "lng_admin_log_participant_left_channel" = "{from} left the channel"; "lng_admin_log_invited" = "invited {user}"; "lng_admin_log_banned" = "banned {user}"; "lng_admin_log_restricted" = "changed restrictions for {user} {until}"; "lng_admin_log_promoted" = "changed privileges for {user}"; "lng_admin_log_changed_stickers_group" = "{from} changed the group's {sticker_set}"; "lng_admin_log_changed_stickers_set" = "sticker set"; "lng_admin_log_removed_stickers_group" = "{from} removed the group's sticker set"; "lng_admin_log_user_with_username" = "{name} ({mention})"; "lng_admin_log_restricted_forever" = "indefinitely"; "lng_admin_log_restricted_until" = "until {date}"; "lng_admin_log_banned_view_messages" = "Read messages"; "lng_admin_log_banned_send_messages" = "Send messages"; "lng_admin_log_banned_send_media" = "Send media"; "lng_admin_log_banned_send_stickers" = "Send stickers & GIFs"; "lng_admin_log_banned_embed_links" = "Embed links"; "lng_admin_log_admin_change_info" = "Change info"; "lng_admin_log_admin_post_messages" = "Post messages"; "lng_admin_log_admin_edit_messages" = "Edit messages"; "lng_admin_log_admin_delete_messages" = "Delete messages"; "lng_admin_log_admin_ban_users" = "Ban users"; "lng_admin_log_admin_invite_users" = "Add users"; "lng_admin_log_admin_invite_link" = "Invite users via link"; "lng_admin_log_admin_pin_messages" = "Pin messages"; "lng_admin_log_admin_add_admins" = "Add new admins"; "lng_feed_name" = "Feed"; "lng_feed_show_next" = "Show Next"; "lng_feed_group" = "Group in feed"; "lng_feed_ungroup" = "Ungroup from feed"; "lng_feed_channel_added" = "Channel added to your feed."; "lng_feed_channel_removed" = "Channel removed from your feed."; "lng_feed_no_messages" = "No messages in this feed yet"; "lng_feed_channels#one" = "{count} channel"; "lng_feed_channels#other" = "{count} channels"; "lng_feed_notifications" = "Feed notifications"; "lng_feed_ungroup_all" = "Ungroup all channels"; "lng_feed_sure_ungroup_all" = "Are you sure you want to ungroup all channels from this feed?"; "lng_feed_ungroup_sure" = "Ungroup"; "lng_feed_create_new" = "New feed"; "lng_feed_too_few_channels#one" = "You need at least {count} channel to create a feed."; "lng_feed_too_few_channels#other" = "You need at least {count} channels to create a feed."; "lng_feed_select_more_channels#one" = "Select {count} channel or more."; "lng_feed_select_more_channels#other" = "Select {count} channels or more."; "lng_feed_create" = "Create"; "lng_feed_edit_title" = "Edit feed"; "lng_feed_channels_not_found" = "No channels found"; "lng_info_feed_title" = "Feed Info"; "lng_info_feed_is_default" = "Group new channels"; "lng_info_feed_channels" = "Channels"; // Wnd specific "lng_wnd_choose_program_menu" = "Choose Default Program..."; "lng_wnd_menu_undo" = "Undo"; "lng_wnd_menu_redo" = "Redo"; // Linux specific "lng_linux_menu_undo" = "Undo"; "lng_linux_menu_redo" = "Redo"; // Mac specific "lng_mac_choose_program_menu" = "Other..."; "lng_mac_choose_app" = "Choose Application"; "lng_mac_choose_text" = "Choose an application to open the document \"{file}\"."; "lng_mac_enable_filter" = "Enable:"; "lng_mac_recommended_apps" = "Recommended Applications"; "lng_mac_all_apps" = "All Applications"; "lng_mac_always_open_with" = "Always Open With"; "lng_mac_this_app_can_open" = "This application can open \"{file}\"."; "lng_mac_not_known_app" = "It's not known if this application can open \"{file}\"."; "lng_mac_menu_services" = "Services"; "lng_mac_menu_hide_telegram" = "Hide {telegram}"; "lng_mac_menu_hide_others" = "Hide Others"; "lng_mac_menu_show_all" = "Show All"; "lng_mac_menu_preferences" = "Preferences..."; "lng_mac_menu_quit_telegram" = "Quit {telegram}"; "lng_mac_menu_about_telegram" = "About {telegram}"; "lng_mac_menu_file" = "File"; "lng_mac_menu_logout" = "Log Out"; "lng_mac_menu_edit" = "Edit"; "lng_mac_menu_undo" = "Undo"; "lng_mac_menu_redo" = "Redo"; "lng_mac_menu_cut" = "Cut"; "lng_mac_menu_copy" = "Copy"; "lng_mac_menu_paste" = "Paste"; "lng_mac_menu_delete" = "Delete"; "lng_mac_menu_select_all" = "Select All"; "lng_mac_menu_window" = "Window"; "lng_mac_menu_contacts" = "Contacts"; "lng_mac_menu_add_contact" = "Add Contact"; "lng_mac_menu_new_group" = "New Group"; "lng_mac_menu_new_channel" = "New Channel"; "lng_mac_menu_show" = "Show Telegram"; // Keys finished tdesktop-1.2.17/Telegram/Resources/langs/lang_de.strings000066400000000000000000002435751326245125100233020ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ "lng_language_name" = "Deutsch"; "lng_switch_to_this" = "Auf Deutsch zurücksetzen"; "lng_menu_contacts" = "Kontakte"; "lng_menu_calls" = "Anrufe"; "lng_menu_settings" = "Einstellungen"; "lng_menu_about" = "Über"; "lng_menu_update" = "Update"; "lng_menu_restart" = "Neustart"; "lng_menu_back" = "Zurück"; "lng_menu_night_mode" = "Nachtmodus"; "lng_disable_notifications_from_tray" = "Benachrichtigungen aus"; "lng_enable_notifications_from_tray" = "Benachrichtigungen ein"; "lng_open_from_tray" = "Telegram öffnen"; "lng_minimize_to_tray" = "Minimieren"; "lng_quit_from_tray" = "Telegram beenden"; "lng_tray_icon_text" = "Telegram ist minimiert und läuft im Hintergrund,\ndu kannst das in den Einstellungen ändern.\nUm Telegram wieder anzuzeigen, klicke auf \ndas Symbol im Benachrichtigungsfeld."; "lng_month1" = "Januar"; "lng_month2" = "Februar"; "lng_month3" = "März"; "lng_month4" = "April"; "lng_month5" = "Mai"; "lng_month6" = "Juni"; "lng_month7" = "Juli"; "lng_month8" = "August"; "lng_month9" = "September"; "lng_month10" = "Oktober"; "lng_month11" = "November"; "lng_month12" = "Dezember"; "lng_month1_small" = "Jan"; "lng_month2_small" = "Feb"; "lng_month3_small" = "Mär"; "lng_month4_small" = "Apr"; "lng_month5_small" = "Mai"; "lng_month6_small" = "Jun"; "lng_month7_small" = "Jul"; "lng_month8_small" = "Aug"; "lng_month9_small" = "Sept"; "lng_month10_small" = "Okt"; "lng_month11_small" = "Nov"; "lng_month12_small" = "Dez"; "lng_weekday1" = "Mo"; "lng_weekday2" = "Di"; "lng_weekday3" = "Mi"; "lng_weekday4" = "Do"; "lng_weekday5" = "Fr"; "lng_weekday6" = "Sa"; "lng_weekday7" = "So"; "lng_weekday1_full" = "Montag"; "lng_weekday2_full" = "Dienstag"; "lng_weekday3_full" = "Mittwoch"; "lng_weekday4_full" = "Donnerstag"; "lng_weekday5_full" = "Freitag"; "lng_weekday6_full" = "Samstag"; "lng_weekday7_full" = "Sonntag"; "lng_month_day" = "{day}. {month}"; "lng_month_day_year" = "{day}. {month} {year}"; "lng_month_year" = "{month} {year}"; "lng_box_ok" = "OK"; "lng_cancel" = "Abbrechen"; "lng_continue" = "Weiter"; "lng_close" = "Schließen"; "lng_connecting" = "Verbinde..."; "lng_connecting_to_proxy" = "Verbinde mit Proxy..."; "lng_connecting_settings" = "Einstellungen"; "lng_reconnecting#one" = "Neu verbinden in {count} s..."; "lng_reconnecting#other" = "Neu verbinden in {count} s..."; "lng_reconnecting_try_now" = "Jetzt versuchen"; "lng_status_service_notifications" = "Servicemeldungen"; "lng_status_support" = "Support"; "lng_status_bot" = "Bot"; "lng_status_bot_reads_all" = "hat Zugriff auf Nachrichten"; "lng_status_bot_not_reads_all" = "hat keinen Zugriff auf Nachrichten"; "lng_status_offline" = "vor langer Zeit gesehen"; "lng_status_recently" = "kürzlich gesehen"; "lng_status_last_week" = "innerhalb einer Woche gesehen"; "lng_status_last_month" = "innerhalb eines Monats gesehen"; "lng_status_invisible" = "unsichtbar"; "lng_status_lastseen_now" = "gerade eben gesehen"; "lng_status_lastseen_minutes#one" = "zuletzt gesehen vor {count} Minute"; "lng_status_lastseen_minutes#other" = "zuletzt gesehen vor {count} Minuten"; "lng_status_lastseen_hours#one" = "zuletzt gesehen vor {count} Stunde"; "lng_status_lastseen_hours#other" = "zuletzt gesehen vor {count} Stunden"; "lng_status_lastseen_today" = "zuletzt heute um {time} gesehen"; "lng_status_lastseen_yesterday" = "gestern um {time} zuletzt gesehen"; "lng_status_lastseen_date" = "zuletzt gesehen am {date}"; "lng_status_lastseen_date_time" = "zuletzt gesehen am {date} um {time}"; "lng_status_online" = "online"; "lng_status_connecting" = "verbinden..."; "lng_chat_status_unaccessible" = "Gruppe ist nicht verfügbar"; "lng_chat_status_no_members" = "keine Mitglieder"; "lng_chat_status_members#one" = "{count} Mitglied"; "lng_chat_status_members#other" = "{count} Mitglieder"; "lng_chat_status_online#one" = "{count} online"; "lng_chat_status_online#other" = "{count} online"; "lng_chat_status_members_online" = "{members_count}, {online_count}"; "lng_channel_status" = "Kanal"; "lng_group_status" = "Gruppe"; "lng_channel_members_link#one" = "{count} Mitglied"; "lng_channel_members_link#other" = "{count} Mitglieder"; "lng_channel_admins_link#one" = "{count} Administrator"; "lng_channel_admins_link#other" = "{count} Administratoren"; "lng_server_error" = "Interner Serverfehler."; "lng_flood_error" = "Zu viele Versuche, bitte später erneut probieren."; "lng_gif_error" = "Ein Fehler ist beim Laden der GIF-Animation aufgetreten :("; "lng_edit_error" = "Du kannst diese Nachricht nicht bearbeiten"; "lng_join_channel_error" = "Du bist bereits in zu vielen Kanälen und Supergruppen. Bitte trenne dich zuvor von ein paar davon."; "lng_error_phone_flood" = "Du hast dein Konto leider zu oft gelöscht. Bitte warte einige Tage, erst dann kannst du dich erneut registrieren."; "lng_error_start_minimized_passcoded" = "Du hast den lokalen Pincode-Schutz aktiviert, deshalb kannst du die App nicht minimiert starten. Die App muss dich vorher nach dem Pincode fragen."; "lng_error_pinned_max#one" = "Du kannst nicht mehr als {count} Chat oben anheften."; "lng_error_pinned_max#other" = "Du kannst nicht mehr als {count} Chats oben anheften."; "lng_error_public_groups_denied" = "Leider kannst du an keinen öffentlichen Gruppen mehr teilnehmen.\n{more_info}"; "lng_error_cant_edit_admin" = "Du kannst die Berechtigungen für diesen Admin nicht ändern."; "lng_error_cant_add_member" = "Du kannst diesen Bot leider nicht der Gruppe hinzufügen. Ein Admin der Gruppe kann ihn hinzufügen."; "lng_error_cant_add_bot" = "Dieser Bot kann Gruppen nicht hinzugefügt werden."; "lng_error_cant_add_admin_invite" = "Du kannst diesen Nutzer nicht als Admin festlegen, da er kein Mitglied dieser Gruppe ist oder du keine Erlaubnis hast, ihn einzuladen."; "lng_error_cant_add_admin_unban" = "Du kannst diesen Nutzer nicht als Admin festlegen, da er sich in der Sperrliste befindet und du ihn nicht entsperren darfst."; "lng_error_cant_ban_admin" = "Du kannst diesen Nutzer nicht sperren, da er ein Admin dieser Gruppe ist und du keine Berechtigung hast, ihn zurückzustufen."; "lng_sure_add_admin_invite" = "Dieser Nutzer ist kein Gruppenmitglied. Möchtest du ihn der Gruppe hinzufügen und als Admin festlegen?"; "lng_sure_add_admin_unban" = "Dieser Nutzer ist derzeit in dieser Gruppe eingeschränkt oder gesperrt. Möchtest du ihn entsperren und befördern?"; "lng_sure_ban_admin" = "Dieser Nutzer ist Admin in dieser Gruppe. Möchtest du fortfahren und ihn einschränken?"; "lng_sure_ban_user_group" = "{user} in der Gruppe sperren?"; "lng_sure_enable_socks" = "Möchtest du wirklich diesen Proxy aktivieren?\n\nServer: {server}\nPort: {port}\n\nDu kannst ihn später in den Einstellungen (Verbindungsart) ändern."; "lng_sure_enable" = "Aktivieren"; "lng_edit_deleted" = "Diese Nachricht wurde gelöscht"; "lng_edit_too_long" = "Der Text ist leider zu lang"; "lng_edit_message" = "Nachricht bearbeiten"; "lng_edit_message_text" = "Neuer Text..."; "lng_deleted" = "Gelöschtes Konto"; "lng_deleted_message" = "Gelöschte Nachricht"; "lng_pinned_message" = "Angeheftete Nachricht"; "lng_pinned_unpin_sure" = "Angeheftete Nachricht entfernen?"; "lng_pinned_pin_sure" = "Möchtest du diese Nachricht anheften?"; "lng_pinned_pin" = "Anheften"; "lng_pinned_unpin" = "Entfernen"; "lng_pinned_notify" = "Alle benachrichtigen"; "lng_intro_about" = "Willkommen in der offiziellen Telegram Desktop App.\nDie App ist schnell und sicher."; "lng_start_msgs" = "JETZT STARTEN"; "lng_intro_next" = "WEITER"; "lng_intro_finish" = "REGISTRIEREN"; "lng_intro_submit" = "SENDEN"; "lng_photo_caption" = "Beschriftung"; "lng_photos_comment" = "Kommentar"; "lng_phone_ph" = "Deine Telefonnummer"; "lng_phone_title" = "Dein Telefon"; "lng_phone_desc" = "Bitte Land und Telefonnummer\nohne führende Null eintragen."; "lng_phone_notreg" = "Wenn du noch kein Telegram Konto hast,\n[b]registriere[/b] dich über unsere {link_start}Android / iPhone Apps{link_end}\noder direkt {signup_start}hier{signup_end}"; "lng_country_code" = "Ländercode"; "lng_bad_country_code" = "Falscher Ländercode"; "lng_country_ph" = "Suche"; "lng_country_done" = "Fertig"; "lng_country_none" = "Land nicht gefunden"; "lng_country_select" = "Land auswählen"; "lng_code_ph" = "Dein Code"; "lng_code_desc" = "Wir haben dir einen Aktivierungscode gesendet.\nBitte Code hier eingeben."; "lng_code_telegram" = "Bitte den Code eingeben, den du in der\nzuvor benutzen [b]Telegram[/b]-App erhalten hast."; "lng_code_no_telegram" = "Code per SMS senden"; "lng_code_call" = "Telegram ruft dich an in {minutes}:{seconds}"; "lng_code_calling" = "Telegram ruft dich an..."; "lng_code_called" = "Telegram ruft dich gerade an."; "lng_bad_phone" = "Falsche Nummer, bitte erneut versuchen."; "lng_bad_phone_noreg" = "Telefonnummer nicht registriert."; "lng_bad_code" = "Ungültiger Code."; "lng_bad_name" = "Bitte trage Vor- und Nachnamen ein."; "lng_bad_photo" = "Fehler beim Upload."; "lng_signin_title" = "Dein Kennwort"; "lng_signin_desc" = "Bitte Kennwort eingeben."; "lng_signin_recover_desc" = "Bitte Code aus der E-Mail\n{email} eingeben."; "lng_signin_password" = "Dein Kennwort"; "lng_signin_code" = "Code aus der E-Mail"; "lng_signin_recover" = "Kennwort vergessen?"; "lng_signin_recover_title" = "Kennwort zurücksetzen"; "lng_signin_hint" = "Hinweis: {password_hint}"; "lng_signin_recover_hint" = "Code wurde an {recover_email} gesendet"; "lng_signin_bad_password" = "Falsches Kennwort eingegeben."; "lng_signin_wrong_code" = "Ungültiger Code."; "lng_signin_try_password" = "Du hast keinen Zugang zu deinen E-Mails?"; "lng_signin_password_removed" = "Dein Kennwort wurde entfernt.\nRichte ein neues in den Einstellungen ein."; "lng_signin_no_email_forgot" = "Da du keine Email Adresse hinterlegt hast, \nkannst du nur noch hoffen, dass dir dein Kennwort wieder einfällt oder du musst tatsächlich dein Telegram Konto zurücksetzen."; "lng_signin_cant_email_forgot" = "Da du keine E-Mail Adresse hinterlegt hast, kannst du nur noch hoffen, dass dir dein Kennwort wieder einfällt oder du musst tatsächlich dein Telegram Konto zurücksetzen."; "lng_signin_reset_account" = "Konto zurücksetzen"; "lng_signin_sure_reset" = "Hinweis!\n\nDu verlierst du alle Chats und Nachrichten, ebenso deine geteilten Bilder und Videos.\n\nKonto wirklich zurücksetzen?"; "lng_signin_reset" = "Zurücksetzen"; "lng_signin_reset_wait" = "Da dein Konto {phone_number} aktiv und durch ein Kennwort geschützt ist, löschen wir es aus Sicherheitsgründen in einer Woche. Du kannst den Vorgang jederzeit abbrechen.\n\nDu kannst dein Konto zurücksetzen in:\n{when}"; "lng_signin_reset_days#one" = "{count} Tag"; "lng_signin_reset_days#other" = "{count} Tage"; "lng_signin_reset_hours#one" = "{count} Stunde"; "lng_signin_reset_hours#other" = "{count} Stunden"; "lng_signin_reset_minutes#one" = "{count} Minute"; "lng_signin_reset_minutes#other" = "{count} Minuten"; "lng_signin_reset_in_days" = "{days_count} {hours_count} {minutes_count}"; "lng_signin_reset_in_hours" = "{hours_count} {minutes_count}"; "lng_signin_reset_cancelled" = "Deine vorherigen Versuche das Konto zurückzusetzen wurden durch den aktiven Nutzer abgebrochen. Bitte in 7 Tagen erneut probieren."; "lng_signup_title" = "Deine Info"; "lng_signup_desc" = "Bitte trage deinen Namen ein \nund lade ein Bild hoch."; "lng_signup_firstname" = "Vorname"; "lng_signup_lastname" = "Nachname"; "lng_dlg_filter" = "Suche"; "lng_dlg_new_group_name" = "Name der Gruppe"; "lng_dlg_new_channel_name" = "Kanalname"; "lng_no_contacts" = "Du hast keine Kontakte"; "lng_no_chats" = "Noch keine Chats"; "lng_contacts_loading" = "Lade..."; "lng_contacts_not_found" = "Keine Kontakte gefunden"; "lng_dlg_search_for_messages" = "Nach Nachrichten suchen"; "lng_update_telegram" = "Aktualisiere Telegram"; "lng_dlg_search_in" = "Suche Nachrichten in"; "lng_dlg_search_from" = "Von: {user}"; "lng_settings_save" = "Speichern"; "lng_settings_upload" = "Profilbild festlegen"; "lng_settings_crop_profile" = "Wähle einen Bereich für dein Profilbild"; "lng_settings_uploading_photo" = "Bild wird geladen..."; "lng_settings_edit" = "Bearbeiten"; "lng_settings_drop_area_subtitle" = "um es als dein Bild auszuwählen"; "lng_username_title" = "Benutzername"; "lng_username_about" = "Wähle einen (optionalen) öffentlichen \nBenutzernamen, wenn du von anderen \ngefunden werden willst, ohne, dass sie \ndeine Nummer kennen müssen.\n\nErlaubt sind a-z, 0-9 und Unterstriche. \nDie Mindestlänge beträgt 5 Zeichen."; "lng_username_choose" = "Wähle einen Benutzernamen."; "lng_username_invalid" = "Benutzername ungültig."; "lng_username_occupied" = "Benutzername ist bereits belegt."; "lng_username_too_short" = "Benutzername ist zu kurz."; "lng_username_bad_symbols" = "Ungültige Zeichen im Benutzernamen."; "lng_username_available" = "Benutzername ist verfügbar."; "lng_username_not_found" = "Kontakt @{user} konnte nicht gefunden werden."; "lng_username_link_willbe" = "So könnte dein Link aussehen:"; "lng_username_link" = "Dieser Link öffnet einen Chat mit dir:"; "lng_username_copied" = "Link in die Zwischenablage kopiert."; "lng_bio_title" = "Bio bearbeiten"; "lng_bio_placeholder" = "Bio"; "lng_bio_about" = "Hier kannst du etwas über dich schreiben. Jeder der dein Profil öffnet, kann diesen Text lesen."; "lng_settings_section_info" = "Info"; "lng_settings_phone_number" = "Telefonnummer:"; "lng_settings_username" = "Benutzername:"; "lng_settings_choose_username" = "Benutzername wählen"; "lng_settings_empty_bio" = "Keine"; "lng_settings_section_notify" = "Benachrichtigungen"; "lng_settings_desktop_notify" = "Desktopbenachrichtigungen"; "lng_settings_show_name" = "Absendername anzeigen"; "lng_settings_show_preview" = "Nachrichtenvorschau anzeigen"; "lng_settings_use_windows" = "Windows Benachrichtigungen"; "lng_settings_use_native_notifications" = "Linux Benachrichtigungen"; "lng_settings_advanced_notifications" = "Position und Anzahl der Benachrichtigungen"; "lng_settings_notifications_position" = "Position auf dem Bildschirm"; "lng_settings_notifications_count" = "Anzahl der Benachrichtigungen"; "lng_settings_sound_notify" = "Ton abspielen"; "lng_settings_include_muted" = "Stummgeschaltete Chats mitzählen"; "lng_notification_preview" = "Du hast eine neue Nachricht"; "lng_notification_reply" = "Antworten"; "lng_notification_hide_all" = "Alle verstecken"; "lng_notification_sample" = "Eine Beispiel-Benachrichtigung"; "lng_settings_section_general" = "Allgemein"; "lng_settings_change_lang" = "Sprache ändern"; "lng_languages" = "Sprachen"; "lng_sure_save_language" = "Telegram wird neugestartet, um in der neuen Sprache angezeigt werden zu können"; "lng_settings_update_automatically" = "Automatisch aktualisieren"; "lng_settings_current_version_label" = "Version {version}:"; "lng_settings_current_version" = "Version {version}"; "lng_settings_check_now" = "Auf Updates prüfen"; "lng_settings_update_checking" = "Prüfe auf Updates..."; "lng_settings_latest_installed" = "Aktuellste Version bereits installiert"; "lng_settings_downloading" = "Update wird geladen {ready} / {total} MB..."; "lng_settings_update_ready" = "Neue Version ist da!"; "lng_settings_update_now" = "Jetzt neustarten"; "lng_settings_update_fail" = "Konnte nicht auf Updates prüfen :("; "lng_settings_workmode_tray" = "Symbol im System Tray anzeigen"; "lng_settings_workmode_window" = "Symbol in der Taskleiste anzeigen"; "lng_settings_auto_start" = "Telegram beim Systemstart ausführen"; "lng_settings_start_min" = "Minimiert starten"; "lng_settings_add_sendto" = "Telegram in den Explorer einbinden"; "lng_settings_section_scale" = "Größe ändern"; "lng_settings_scale_auto" = "Auto ({cur})"; "lng_settings_section_chat_settings" = "Chat-Einstellungen"; "lng_settings_replace_emojis" = "Emoji ersetzen"; "lng_settings_view_emojis" = "Liste anzeigen"; "lng_settings_emoji_list" = "Unterstützte Emoji"; "lng_settings_send_enter" = "Mit Enter senden"; "lng_settings_send_ctrlenter" = "Mit Strg+Enter senden"; "lng_settings_send_cmdenter" = "Mit Cmd+Enter senden"; "lng_settings_section_background" = "Hintergrundbild"; "lng_settings_bg_use_default" = "Benutze Standardthema"; "lng_settings_bg_from_gallery" = "Aus der Galerie"; "lng_settings_bg_from_file" = "Durchsuchen"; "lng_settings_bg_edit_theme" = "Thema bearbeiten"; "lng_settings_bg_tile" = "Nebeneinander"; "lng_settings_adaptive_wide" = "Flexibles Layout für breite Bildschirme"; "lng_backgrounds_header" = "Neuen Chat-Hintergrund wählen"; "lng_theme_sure_keep" = "Wirklich dieses Thema verwenden?"; "lng_theme_reverting#one" = "Vorheriges Thema wird in {count} Sekunde benutzt."; "lng_theme_reverting#other" = "Vorheriges Thema wird in {count} Sekunden benutzt."; "lng_theme_keep_changes" = "Anwenden"; "lng_theme_revert" = "Zurücksetzen"; "lng_download_path_dont_ask" = "Speicherort merken"; "lng_download_path_label" = "Speicherort:"; "lng_download_path_temp" = "Temporär"; "lng_download_path_default" = "Standardordner"; "lng_download_path_clear" = "Alles entfernen"; "lng_download_path_header" = "Wähle Speicherort"; "lng_download_path_default_radio" = "Telegram-Ordner für «Downloads»"; "lng_download_path_temp_radio" = "Temporärer Ordner (bis du dich abmeldest)"; "lng_download_path_dir_radio" = "Eigener Ordner, wird nur manuell geleert"; "lng_download_path_choose" = "Wähle Speicherort"; "lng_sure_clear_downloads" = "Willst du alle Downloads vom Temp-Ordner löschen? Das passiert automatisch wenn du dich abmeldest oder Telegram deinstallierst."; "lng_download_path_failed" = "Download konnte nicht gestartet werden. Das kann am eingestellten Speicherort liegen.\n\nDu kannst den Speicherort in den Einstellungen ändern."; "lng_download_path_settings" = "Einstellungen"; "lng_download_finish_failed" = "Datei konnte nicht geladen werden.\n\nErneut versuchen?"; "lng_download_path_clearing" = "Leeren..."; "lng_download_path_cleared" = "Geleert!"; "lng_download_path_clear_failed" = "Ein Fehler ist aufgetreten :("; "lng_settings_section_privacy" = "Privatsphäre und Sicherheit"; "lng_local_storage_title" = "Lokaler Cache"; "lng_settings_no_data_cached" = "Keine temporären Daten!"; "lng_settings_images_cached#one" = "{count} Bild, {size}"; "lng_settings_images_cached#other" = "{count} Bilder, {size}"; "lng_settings_audios_cached#one" = "{count} Sprachnachricht, {size}"; "lng_settings_audios_cached#other" = "{count} Sprachnachrichten, {size}"; "lng_local_storage_clear" = "Leeren"; "lng_local_storage_clearing" = "Leeren..."; "lng_local_storage_cleared" = "Alles entfernt!"; "lng_local_storage_clear_failed" = "Ein Fehler ist aufgetreten :("; "lng_settings_section_advanced_settings" = "Erweiterte Einstellungen"; "lng_settings_enable_night_theme" = "Nachtmodus aktivieren"; "lng_settings_disable_night_theme" = "Nachtmodus deaktivieren"; "lng_passcode_remove_button" = "Entfernen"; "lng_passcode_turn_on" = "Pincode aktivieren"; "lng_passcode_change" = "Pincode ändern"; "lng_passcode_create" = "Pincode festlegen"; "lng_passcode_remove" = "Pincode entfernen"; "lng_passcode_turn_off" = "Deaktivieren"; "lng_passcode_autolock" = "Auto-Sperre"; "lng_passcode_autolock_away" = "Auto-Sperre bei Abwesenheit:"; "lng_passcode_autolock_inactive" = "Auto-Sperre bei Inaktivität:"; "lng_passcode_autolock_minutes#one" = "{count} Minute"; "lng_passcode_autolock_minutes#other" = "{count} Minuten"; "lng_passcode_autolock_hours#one" = "{count} Stunde"; "lng_passcode_autolock_hours#other" = "{count} Stunden"; "lng_passcode_enter_old" = "Aktuellen Pincode eingeben"; "lng_passcode_enter_first" = "Pincode festlegen"; "lng_passcode_enter_new" = "Neuer Pincode"; "lng_passcode_confirm_new" = "Pincode wiederholen"; "lng_passcode_about" = "Sobald du einen Pincode einstellst, siehst du ein kleines Schloss über deiner Chatliste. Tippe darauf um die App zu sperren.\n\nWichtig: Vergisst du den Pincode, so musst du dich bei Telegram Desktop neu anmelden."; "lng_passcode_differ" = "Pincode ist nicht identisch"; "lng_passcode_wrong" = "Falscher Pincode"; "lng_passcode_is_same" = "Pincode wurde nicht geändert"; "lng_passcode_enter" = "Pincode eingeben"; "lng_passcode_ph" = "Dein Pincode"; "lng_passcode_submit" = "Absenden"; "lng_passcode_logout" = "Abmelden"; "lng_passcode_need_unblock" = "Bitte erst entsperren."; "lng_cloud_password_waiting" = "Bestätigungslink gesendet an {email}..."; "lng_cloud_password_change" = "Kennwort ändern"; "lng_cloud_password_create" = "Kennwort erstellen"; "lng_cloud_password_remove" = "Kennwort entfernen"; "lng_cloud_password_set" = "Zweistufige Bestätigung aktivieren"; "lng_cloud_password_edit" = "Kennwort ändern"; "lng_cloud_password_enter_old" = "Aktuelles Kennwort"; "lng_cloud_password_enter_first" = "Kennwort eingeben"; "lng_cloud_password_enter_new" = "Neues Kennwort eingeben"; "lng_cloud_password_confirm_new" = "Kennwort wiederholen"; "lng_cloud_password_hint" = "Kennwort-Hinweis"; "lng_cloud_password_change_hint" = "Neuer Kennwort-Hinweis"; "lng_cloud_password_bad" = "Kennwort und Hinweis müssen sich unterscheiden."; "lng_cloud_password_email" = "E-Mail für die Wiederherstellung"; "lng_cloud_password_bad_email" = "Ungültige E-Mai, bitte erneut versuchen."; "lng_cloud_password_about" = "Dieses Kennwort brauchst du, wenn du dich mit einem neuen Gerät anmeldest."; "lng_cloud_password_about_recover" = "Hinweis! Möchtest du wirklich keine E-Mail Adresse hinterlegen? \n\nWenn dir dein Kennwort nicht mehr einfällt, verlierst du Zugriff auf dein Telegram Konto."; "lng_cloud_password_skip_email" = "E-Mail überspringen"; "lng_cloud_password_almost" = "Ein Bestätigungslink wurde an deine \nE-Mail Adresse gesendet. Zweistufige Bestätigung wird aktiviert, sobald du den Link öffnest."; "lng_cloud_password_was_set" = "Zweistufige Bestätigung aktiviert."; "lng_cloud_password_updated" = "Kennwort wurde geändert"; "lng_cloud_password_removed" = "Zweistufige Bestätigung deaktiviert."; "lng_cloud_password_differ" = "Kennwörter stimmen nicht überein"; "lng_cloud_password_wrong" = "Falsches Kennwort"; "lng_cloud_password_is_same" = "Kennwort wurde nicht geändert"; "lng_connection_type" = "Verbindungsart:"; "lng_connection_auto_connecting" = "Standard (verbinden...)"; "lng_connection_auto" = "Standard ({transport} verwendet)"; "lng_connection_proxy_connecting" = "Verbinde über Proxy..."; "lng_connection_proxy" = "{transport} mit Proxy"; "lng_connection_header" = "Verbindungsart"; "lng_connection_auto_rb" = "Automatisch (TCP oder HTTP)"; "lng_connection_http_proxy_rb" = "HTTP (benutzerdef. Proxy)"; "lng_connection_tcp_proxy_rb" = "TCP (benutzerdef. Socks5-Proxy)"; "lng_connection_try_ipv6" = "Über IPv6 verbinden"; "lng_connection_host_ph" = "Server"; "lng_connection_port_ph" = "Port"; "lng_connection_user_ph" = "Benutzer"; "lng_connection_password_ph" = "Kennwort"; "lng_connection_save" = "Speichern"; "lng_settings_blocked_users" = "Blockierte Nutzer"; "lng_settings_last_seen_privacy" = "Zuletzt gesehen"; "lng_settings_calls_privacy" = "Anrufe"; "lng_settings_groups_invite_privacy" = "Gruppen"; "lng_settings_show_sessions" = "Sitzungen"; "lng_settings_self_destruct" = "Kontolöschung bei Inaktivität"; "lng_settings_change_phone" = "Nummer ändern"; "lng_settings_reset" = "Alle anderen Sitzungen abmelden"; "lng_settings_reset_sure" = "Sicher, dass du alle anderen Sitzungen\nabmelden möchtest?"; "lng_settings_reset_one_sure" = "Diese Sitzung wirklich abmelden?"; "lng_settings_reset_button" = "Beenden"; "lng_settings_reset_done" = "Alle anderen Sitzungen wurden erfolgreich beendet."; "lng_settings_manage_local_storage" = "Lokalen Cache verwalten"; "lng_settings_ask_question" = "Eine Frage stellen"; "lng_settings_ask_sure" = "Bedenke bitte, dass der Telegram Support von ehrenamtlichen Helfern betreut wird.\n\nKennst du schon die Telegram FAQ? Dort findest du Antworten auf die am häufigsten gestellten Fragen."; "lng_settings_faq_button" = "FAQ öffnen"; "lng_settings_ask_ok" = "Frage stellen"; "lng_settings_faq" = "Telegram FAQ"; "lng_settings_logout" = "Abmelden"; "lng_sure_logout" = "Du kannst Telegram von all deinen Geräten gleichzeitig nutzen. Wirklich abmelden?"; "lng_settings_need_restart" = "Neustart für die neuen Einstellungen erforderlich. Jetzt neustarten?"; "lng_settings_restart_now" = "NEUSTART"; "lng_settings_restart_later" = "SPÄTER"; "lng_sessions_header" = "Aktuelle Sitzung"; "lng_sessions_other_header" = "Aktive Sitzungen"; "lng_sessions_no_other" = "Keine anderen Sitzungen"; "lng_sessions_other_desc" = "Du kannst Telegram gleichzeitig vom Handy, Tablet oder auch Computer mit der gleichen Handynummer benutzen. Alles wird immer synchronisiert."; "lng_sessions_terminate_all" = "Alle anderen Sitzungen abmelden"; "lng_blocked_list_title" = "Blockierte Nutzer"; "lng_blocked_list_unknown_phone" = "unbekannte Nummer"; "lng_blocked_list_unblock" = "Freigeben"; "lng_blocked_list_add" = "Nutzer blockieren"; "lng_blocked_list_add_title" = "Wen möchtest du blockieren?"; "lng_blocked_list_already_blocked" = "bereits blockiert"; "lng_blocked_list_about" = "Blockierte Nutzer können dir keine Nachrichten schicken oder dich in Gruppen einladen. Sie sehen weder dein Profilbild, noch deinen Online- und \"zuletzt gesehen\" Status."; "lng_blocked_list_not_found" = "Keine Nutzer gefunden."; "lng_edit_privacy_everyone" = "Jeder"; "lng_edit_privacy_contacts" = "Meine Kontakte"; "lng_edit_privacy_nobody" = "Niemand"; "lng_edit_privacy_exceptions" = "Ausnahmen hinzufügen"; "lng_edit_privacy_lastseen_title" = "Zuletzt gesehen"; "lng_edit_privacy_lastseen_description" = "Wer darf deine \"zuletzt gesehen\" Zeit sehen:"; "lng_edit_privacy_lastseen_warning" = "Wichtig: Du kannst den \"zuletzt gesehen\" Status nur von Personen sehen, mit denen du auch deinen teilst. Sonst wird nur die ungefähre Zeit angezeigt."; "lng_edit_privacy_lastseen_always_empty" = "Immer teilen mit"; "lng_edit_privacy_lastseen_always#one" = "Immer teilen mit {count} Kontakt"; "lng_edit_privacy_lastseen_always#other" = "Immer teilen mit {count} Kontakten"; "lng_edit_privacy_lastseen_never_empty" = "Niemals teilen mit"; "lng_edit_privacy_lastseen_never#one" = "Niemals teilen mit {count} Kontakt"; "lng_edit_privacy_lastseen_never#other" = "Niemals teilen mit {count} Kontakten"; "lng_edit_privacy_lastseen_exceptions" = "Hier kannst du Kontakte hinzufügen, für die eine Ausnahme gemacht werden soll."; "lng_edit_privacy_lastseen_always_title" = "Immer teilen mit"; "lng_edit_privacy_lastseen_never_title" = "Niemals teilen mit"; "lng_edit_privacy_groups_title" = "Wer kann mich Gruppen hinzufügen?"; "lng_edit_privacy_groups_description" = "Du kannst bestimmen, wer dich in Gruppen oder Kanälen hinzufügen kann:"; "lng_edit_privacy_groups_always_empty" = "Immer erlauben"; "lng_edit_privacy_groups_always#one" = "Immer {count} Kontakt erlauben"; "lng_edit_privacy_groups_always#other" = "Immer {count} Kontakten erlauben"; "lng_edit_privacy_groups_never_empty" = "Niemals erlauben"; "lng_edit_privacy_groups_never#one" = "Niemals {count} Kontakt erlauben"; "lng_edit_privacy_groups_never#other" = "Niemals {count} Kontakten erlauben"; "lng_edit_privacy_groups_exceptions" = "Hier kannst du Nutzer hinzufügen, für die eine Ausnahme gemacht werden soll."; "lng_edit_privacy_groups_always_title" = "Immer erlauben"; "lng_edit_privacy_groups_never_title" = "Niemals erlauben"; "lng_edit_privacy_calls_title" = "Anrufe"; "lng_edit_privacy_calls_description" = "Du kannst festlegen, wer dich anrufen darf:"; "lng_edit_privacy_calls_always_empty" = "Immer erlauben"; "lng_edit_privacy_calls_always#one" = "Immer {count} Kontakt erlauben"; "lng_edit_privacy_calls_always#other" = "Immer {count} Kontakten erlauben"; "lng_edit_privacy_calls_never_empty" = "Niemals erlauben"; "lng_edit_privacy_calls_never#one" = "Niemals {count} Kontakt erlauben"; "lng_edit_privacy_calls_never#other" = "Niemals {count} Kontakten erlauben"; "lng_edit_privacy_calls_exceptions" = "Hier kannst du Nutzer hinzufügen, für die eine Ausnahme gemacht werden soll."; "lng_edit_privacy_calls_always_title" = "Immer erlauben"; "lng_edit_privacy_calls_never_title" = "Niemals erlauben"; "lng_self_destruct_title" = "Automatische Kontolöschung"; "lng_self_destruct_description" = "Wenn du dich innerhalb dieser Zeit nicht anmeldest, wird dein Konto mit allen Nachrichten, Gruppen und Kontakten gelöscht."; "lng_self_destruct_months#one" = "{count} Monat"; "lng_self_destruct_months#other" = "{count} Monate"; "lng_self_destruct_years#one" = "{count} Jahr"; "lng_self_destruct_years#other" = "{count} Jahre"; "lng_change_phone_title" = "Nummer ändern"; "lng_change_phone_description" = "Du kannst deine Telefonnummer hier ändern.\nDein Konto und alle Daten in der Cloud, also \nNachrichten, Medien, Kontakte, etc. werden \nauf das neue Konto übertragen.\n\n[b]Wichtig[/b]: Alle deine Kontakte erhalten deine\n[b]neue Nummer[/b] ihrem Telefonbuch\nhinzugefügt, sofern sie deine alte Nummer \nhatten und du sie nicht blockiert hattest."; "lng_change_phone_warning" = "Bitte beachte, dass alle deine Kontakte deine neue Nummer ihrem Telefonbuch hinzugefügt bekommen, sofern sie deine alte Nummer gespeichert hatten und du sie nicht blockiert hattest."; "lng_change_phone_occupied" = "Unter der Nummer {phone} gibt es bereits ein Telegram-Konto. Du musst es vor der Änderung deiner Nummer löschen."; "lng_change_phone_button" = "Nummer ändern"; "lng_change_phone_new_title" = "Neue Nummer eingeben"; "lng_change_phone_new_description" = "Der Bestätigungscode kommt per SMS an deine neue Nummer."; "lng_change_phone_new_submit" = "Absenden"; "lng_change_phone_code_title" = "Code eingeben"; "lng_change_phone_code_description" = "Wir haben dir einen Bestätigungscode per SMS an {phone} gesendet."; "lng_change_phone_success" = "Deine Telefonnummer wurde geändert."; "lng_mute_duration_hours#one" = "Für {count} Stunde"; "lng_mute_duration_hours#other" = "Für {count} Stunden"; "lng_mute_box_tip" = "Wie lange sollen die Benachrichtigungen für diesen Chat ausgeschaltet werden?"; "lng_preview_loading" = "Lade Linkvorschau..."; "lng_profile_chat_unaccessible" = "Gruppe ist nicht verfügbar"; "lng_profile_about_section" = "Info"; "lng_profile_description_section" = "Beschreibung"; "lng_profile_settings_section" = "Einstellungen"; "lng_profile_actions_section" = "Aktionen"; "lng_profile_bot_settings" = "Einstellungen"; "lng_profile_bot_help" = "Hilfe"; "lng_profile_invite_link_section" = "Einladungslink"; "lng_profile_create_public_link" = "Öffentlichen Link erstellen"; "lng_profile_edit_public_link" = "Öffentlichen Link bearbeiten"; "lng_profile_search_members" = "Mitglieder suchen"; "lng_profile_manage_admins" = "Administratoren verwalten"; "lng_profile_manage_blocklist" = "Gesperrte Nutzer verwalten"; "lng_profile_manage_restrictedlist" = "Eingeschränke Nutzer verwalten"; "lng_profile_recent_actions" = "Letzte Aktionen"; "lng_profile_common_groups#one" = "{count} gemeinsame Gruppe"; "lng_profile_common_groups#other" = "{count} gemeinsame Gruppen"; "lng_profile_common_groups_section" = "Gemeinsame Gruppen"; "lng_profile_participants_section" = "Teilnehmer"; "lng_profile_info_section" = "Info"; "lng_profile_mobile_number" = "Telefon:"; "lng_profile_username" = "Benutzername:"; "lng_profile_link" = "Link:"; "lng_profile_bio" = "Bio:"; "lng_profile_add_contact" = "Kontakt hinzufügen"; "lng_profile_edit_contact" = "Bearbeiten"; "lng_profile_enable_notifications" = "Benachrichtigungen"; "lng_profile_clear_history" = "Verlauf löschen"; "lng_profile_delete_conversation" = "Chat löschen"; "lng_profile_clear_and_exit" = "Löschen und verlassen"; "lng_profile_leave_channel" = "Kanal verlassen"; "lng_profile_delete_channel" = "Kanal löschen"; "lng_profile_leave_group" = "Gruppe verlassen"; "lng_profile_delete_group" = "Gruppe löschen"; "lng_profile_report" = "Melden"; "lng_profile_search_messages" = "In diesem Chat suchen"; "lng_profile_block_user" = "Blockieren"; "lng_profile_unblock_user" = "Freigeben"; "lng_profile_block_bot" = "Bot anhalten und blockieren"; "lng_profile_unblock_bot" = "Bot freigeben"; "lng_profile_send_message" = "Nachricht senden"; "lng_profile_share_contact" = "Kontakt teilen"; "lng_profile_invite_to_group" = "In eine Gruppe einladen"; "lng_profile_delete_contact" = "Löschen"; "lng_profile_set_group_photo" = "Bild festlegen"; "lng_profile_add_participant" = "Neues Mitglied"; "lng_profile_view_channel" = "Kanal anzeigen"; "lng_profile_join_channel" = "Beitreten"; "lng_profile_delete_and_exit" = "Verlassen"; "lng_profile_kick" = "Entfernen"; "lng_profile_admin" = "Admin"; "lng_profile_edit_permissions" = "Bearbeiten"; "lng_profile_sure_kick" = "{user} aus der Gruppe entfernen?"; "lng_profile_sure_kick_channel" = "{user} aus dem Kanal entfernen?"; "lng_profile_sure_kick_admin" = "{user} als Administrator entfernen?"; "lng_profile_loading" = "Lade..."; "lng_profile_shared_media" = "Geteilte Medien"; "lng_profile_no_media" = "Noch keine Medien in diesem Chat"; "lng_profile_photos#one" = "{count} Bild"; "lng_profile_photos#other" = "{count} Bilder"; "lng_profile_photos_header" = "Bilder"; "lng_profile_videos#one" = "{count} Video"; "lng_profile_videos#other" = "{count} Videos"; "lng_profile_videos_header" = "Videos"; "lng_profile_songs#one" = "{count} Audiodatei"; "lng_profile_songs#other" = "{count} Audiodateien"; "lng_profile_songs_header" = "Audiodateien"; "lng_profile_files#one" = "{count} Datei"; "lng_profile_files#other" = "{count} Dateien"; "lng_profile_files_header" = "Dateien"; "lng_profile_audios#one" = "{count} Sprachnachricht"; "lng_profile_audios#other" = "{count} Sprachnachrichten"; "lng_profile_audios_header" = "Sprachnachrichten"; "lng_profile_shared_links#one" = "{count} geteilter Link"; "lng_profile_shared_links#other" = "{count} geteilte Links"; "lng_profile_shared_links_header" = "Geteilte Links"; "lng_profile_copy_phone" = "Telefonnummer kopieren"; "lng_profile_copy_fullname" = "Name kopieren"; "lng_profile_drop_area_title" = "Hierher ziehen"; "lng_profile_drop_area_subtitle" = "um es als Gruppenbild auszuwählen"; "lng_profile_drop_area_subtitle_channel" = "um es als Kanalbild auszuwählen"; "lng_profile_top_bar_share_contact" = "Teilen"; "lng_report_title" = "Kanal melden"; "lng_report_group_title" = "Gruppe melden"; "lng_report_bot_title" = "Bot melden"; "lng_report_reason_spam" = "Spam"; "lng_report_reason_violence" = "Gewalt"; "lng_report_reason_pornography" = "Pornografie"; "lng_report_reason_other" = "Sonstiges"; "lng_report_reason_description" = "Beschreibung"; "lng_report_button" = "Melden"; "lng_report_thanks" = "Vielen Dank! Unser Team wird sich um deine Meldung kümmern."; "lng_channel_add_members" = "Mitglieder hinzufügen"; "lng_channel_add_banned" = "Nutzer sperren"; "lng_channel_add_restricted" = "Nutzer einschränken"; "lng_channel_members" = "Mitglieder"; "lng_channel_only_last_shown#one" = "Nur {count} letztes Mitgled wird hier angezeigt"; "lng_channel_only_last_shown#other" = "Nur {count} letzte Mitgleder werden hier angezeigt"; "lng_channel_admins" = "Administratoren"; "lng_channel_add_admin" = "Administrator hinzufügen"; "lng_channel_admins_too_much" = "Leider kannst du keine weiteren Admins dieser Gruppe hinzufügen, bitte erst welche löschen."; "lng_channel_admin_status_creator" = "Ersteller"; "lng_channel_admin_status_promoted_by" = "Befördert von {user}"; "lng_channel_admin_status_not_admin" = "Kein Administrator"; "lng_channel_banned_status_restricted_by" = "Eingeschränkt von {user}"; "lng_group_blocked_list_about" = "Gesperrte Nutzer werden von der Gruppe entfernt und können nur durch Admins erneut hinzugefügt werden. \nEinladungslinks funktionieren nicht."; "lng_chat_all_members_admins" = "Alle Mitglieder Sind Admins"; "lng_chat_about_all_admins" = "Gruppenmitglieder können neue Leute hinzufügen sowie den Gruppennamen und das Bild ändern."; "lng_chat_about_admins" = "Nur Admins können neue Leute hinzufügen und entfernen, den Gruppennamen und das Bild ändern."; "lng_participant_filter" = "Suche"; "lng_participant_invite" = "Einladen"; "lng_participant_invite_sorry#one" = "Du kannst selbst nur das erste {count} Mitglied einem Kanal hinzufügen.\n\nWeitere Leute können den Kanal über den Einladungslink betreten."; "lng_participant_invite_sorry#other" = "Du kannst selbst nur die ersten {count} Mitglieder einem Kanal hinzufügen.\n\nWeitere Leute können den Kanal über den Einladungslink betreten."; "lng_create_group_back" = "Zurück"; "lng_create_group_next" = "Weiter"; "lng_create_group_create" = "Erstellen"; "lng_create_group_title" = "Neue Gruppe erstellen"; "lng_create_channel_title" = "Neuen Kanal erstellen"; "lng_create_public_channel_title" = "Öffentlicher Kanal"; "lng_create_public_channel_about" = "Jeder kann deinen Kanal finden und beitreten"; "lng_create_private_channel_title" = "Privater Kanal"; "lng_create_private_channel_about" = "Man kann nur per Einladungslink deinem Kanal beitreten"; "lng_create_public_group_title" = "Öffentliche Gruppe"; "lng_create_public_group_about" = "Öffentliche Gruppen kann jeder über die Suche finden, gesamter Chatverlauf ist für alle einsehbar und jeder kann der Gruppe beitreten"; "lng_create_private_group_title" = "Private Gruppe"; "lng_create_private_group_about" = "Nur eingeladene Nutzer können die Gruppe betreten und den gesamten Chatverlauf einsehen"; "lng_create_group_skip" = "Überspringen"; "lng_create_channel_link_invalid" = "Dieser Link ist ungültig"; "lng_create_channel_link_occupied" = "Der Linkname ist bereits belegt"; "lng_create_channel_link_too_short" = "Dieser Link ist zu kurz"; "lng_create_channel_link_bad_symbols" = "Nur a-z, 0-9 und Unterstriche erlaubt"; "lng_create_channel_link_available" = "Dieser Link ist verfügbar"; "lng_create_channel_link_copied" = "Link in die Zwischenablage kopiert"; "lng_create_group_crop" = "Wähle einen Bereich für dein Gruppenbild"; "lng_create_channel_crop" = "Wähle einen Bereich für dein Kanalbild"; "lng_failed_add_participant" = "Kann Teilnehmer nicht hinzufügen. Später erneut versuchen."; "lng_failed_add_not_mutual" = "Wenn man eine Gruppe verlässt, kann nur ein gemeinsamer Kontakt die Person erneut einladen (beide Seiten müssen die Nummer des anderen gespeichert haben)."; "lng_failed_add_not_mutual_channel" = "Wenn man einen Kanal verlässt, kann nur ein gemeinsamer Kontakt die Person erneut einladen (beide Seiten müssen die Nummer \ndes anderen gespeichert haben)."; "lng_sure_delete_contact" = "Bist du sicher, dass du {contact} von deinen Kontakten löschen willst?"; "lng_sure_delete_history" = "Sicher, dass du den kompletten Verlauf mit {contact} löschen willst?\n\nDas kann man nicht rückgängig machen."; "lng_sure_delete_group_history" = "Möchtest du wirklich deinen Verlauf von «{group}» löschen?\n\nDas kann man nicht rückgängig machen."; "lng_sure_delete_and_exit" = "Deinen Verlauf von «{group}» löschen und die Gruppe verlassen?\n\nDas kann man nicht rückgängig machen."; "lng_sure_leave_channel" = "Sicher, dass du diesen Kanal\nverlassen willst?"; "lng_sure_delete_channel" = "Sicher, dass du diesen Kanal löschen willst? Alle Mitglieder und Nachrichten werden entfernt."; "lng_sure_leave_group" = "Diese Gruppe wirklich verlassen?\nDas kann man nicht rückgängig machen."; "lng_sure_delete_group" = "Sicher, dass du diese Gruppe löschen willst? Alle Mitglieder und Nachrichten werden entfernt."; "lng_message_empty" = "Leere Nachricht"; "lng_message_unsupported" = "Diese Nachricht wird von deiner Telegram Desktop Version nicht unterstützt. Bitte aktualisiere sie in den Einstellungen oder über {link}"; "lng_duration_seconds#one" = "{count} Sekunde"; "lng_duration_seconds#other" = "{count} Sekunden"; "lng_duration_minsec_minutes#one" = "{count} Min"; "lng_duration_minsec_minutes#other" = "{count} Min"; "lng_duration_minsec_seconds#one" = "{count} Sek"; "lng_duration_minsec_seconds#other" = "{count} Sek"; "lng_duration_minutes_seconds" = "{minutes_count} {seconds_count}"; "lng_action_add_user" = "{from} hat {user} hinzugefügt"; "lng_action_add_users_many" = "{from} hat {users} hinzugefügt"; "lng_action_add_users_and_one" = "{accumulated}, {user}"; "lng_action_add_users_and_last" = "{accumulated} und {user}"; "lng_action_add_you" = "{from} hat dich zum Kanal hinzugefügt"; "lng_action_you_joined" = "Du bist dem Kanal beigetreten"; "lng_action_add_you_group" = "{from} hat dich der Gruppe hinzugefügt"; "lng_action_you_joined_group" = "Du bist der Gruppe beigetreten"; "lng_action_kick_user" = "{from} hat {user} entfernt"; "lng_action_user_left" = "{from} hat die Gruppe verlassen"; "lng_action_user_joined" = "{from} ist der Gruppe beigetreten"; "lng_action_user_joined_by_link" = "{from} ist der Gruppe über den Einladungslink beigetreten"; "lng_action_user_registered" = "{from} benutzt nun auch Telegram"; "lng_action_removed_photo" = "{from} hat das Gruppenbild entfernt"; "lng_action_removed_photo_channel" = "Das Kanalbild wurde entfernt"; "lng_action_changed_photo" = "{from} hat das Gruppenbild geändert"; "lng_action_changed_photo_channel" = "Das Kanalbild wurde geändert"; "lng_action_changed_title" = "{from} hat den Gruppennamen zu «{title}» geändert"; "lng_action_changed_title_channel" = "Kanalname wurde zu «{title}» geändert"; "lng_action_created_chat" = "{from} hat die Gruppe «{title}» erstellt"; "lng_action_created_channel" = "Kanal erstellt"; "lng_action_group_migrate" = "Gruppe wurde in eine Supergruppe geändert"; "lng_action_pinned_message" = "{from} hat «{text}» angeheftet"; "lng_action_pinned_media" = "{from} hat {media} angeheftet"; "lng_action_pinned_media_photo" = "ein Bild"; "lng_action_pinned_media_video" = "ein Video"; "lng_action_pinned_media_audio" = "eine Audiodatei"; "lng_action_pinned_media_voice" = "eine Sprachnachricht"; "lng_action_pinned_media_video_message" = "eine Videonachricht"; "lng_action_pinned_media_file" = "eine Datei"; "lng_action_pinned_media_gif" = "ein GIF"; "lng_action_pinned_media_contact" = "einen Kontakt"; "lng_action_pinned_media_location" = "einen Standort"; "lng_action_pinned_media_sticker" = "einen Sticker"; "lng_action_pinned_media_emoji_sticker" = "einen {emoji} Sticker"; "lng_action_pinned_media_game" = "das Spiel «{game}»"; "lng_action_game_score#one" = "{from} erzielte {count} Punkt bei {game}"; "lng_action_game_score#other" = "{from} erzielte {count} Punkte bei {game}"; "lng_action_game_you_scored#one" = "Du hast {count} Punkt bei {game} erzielt"; "lng_action_game_you_scored#other" = "Du hast {count} Punkte bei {game} erzielt"; "lng_action_game_score_no_game#one" = "{from} erzielte {count} Punkt"; "lng_action_game_score_no_game#other" = "{from} erzielte {count} Punkte"; "lng_action_game_you_scored_no_game#one" = "Du hast {count} Punkt erzielt"; "lng_action_game_you_scored_no_game#other" = "Du hast {count} Punkte erzielt"; "lng_action_payment_done" = "Du hast erfolgreich {amount} an {user} übermittelt"; "lng_action_payment_done_for" = "Du hast erfolgreich {amount} an {user} für {invoice} übermittelt"; "lng_action_took_screenshot" = "{from} hat ein Bildschirmfoto gemacht!"; "lng_action_you_took_screenshot" = "Du hast ein Bildschirmfoto gemacht!"; "lng_ttl_photo_received" = "{from} hat dir ein selbstzerstörendes Bild gesendet. Um es anzuzeigen, bitte Handy benutzen."; "lng_ttl_photo_sent" = "Du hast ein selbstzerstörendes Bild gesendet."; "lng_ttl_photo_expired" = "Bild ist abgelaufen"; "lng_ttl_video_received" = "{from} hat dir ein selbstzerstörendes Video gesendet. Um es anzuzeigen, bitte Handy benutzen."; "lng_ttl_video_sent" = "Du hast ein selbstzerstörendes Video gesendet."; "lng_ttl_video_expired" = "Video ist abgelaufen"; "lng_profile_migrate_reached#one" = "Limit von {count} Mitglied erreicht"; "lng_profile_migrate_reached#other" = "Limit von {count} Mitgliedern erreicht"; "lng_profile_migrate_body" = "Um das Limit aufzuheben, ändere die Gruppe in eine Supergruppe."; "lng_profile_migrate_learn_more" = "Mehr unter:"; "lng_profile_migrate_about" = "Für weitere Funktionen und um das Limit aufzuheben in Supergruppe ändern:"; "lng_profile_migrate_feature1#one" = "— Das Limit beträgt {count} Mitglied"; "lng_profile_migrate_feature1#other" = "— Das Limit beträgt {count} Mitglieder"; "lng_profile_migrate_feature2" = "— Neue Mitglieder sehen gesamten Verlauf"; "lng_profile_migrate_feature3" = "— Admins können alle Nachrichten löschen"; "lng_profile_migrate_feature4" = "— Mitteilungen sind standardmäßig stumm"; "lng_profile_migrate_button" = "In Supergruppe ändern"; "lng_profile_migrate_sure" = "Wirklich diese Gruppe in eine Supergruppe ändern? Das kann nicht rückgängig gemacht werden."; "lng_profile_convert_button" = "In Supergruppe ändern"; "lng_profile_convert_title" = "In Supergruppe ändern"; "lng_profile_convert_about" = "Supergruppen:"; "lng_profile_convert_feature1" = "— Neue Mitglieder sehen gesamten Verlauf"; "lng_profile_convert_feature2" = "— Löschen gilt für die ganze Gruppe"; "lng_profile_convert_feature3" = "— Admins können Nachrichten anheften"; "lng_profile_convert_feature4" = "— Gründer kann Gruppe öffentlich machen"; "lng_profile_convert_warning" = "{bold_start}Wichtig:{bold_end} Die Änderung in eine Supergruppe kann nicht rückgängig gemacht werden."; "lng_profile_convert_confirm" = "Ändern"; "lng_profile_add_more_after_upgrade#one" = "Du kannst nach der Änderung der Gruppe in eine Supergruppe bis zu {count} Mitglied hinzufügen."; "lng_profile_add_more_after_upgrade#other" = "Du kannst nach der Änderung der Gruppe in eine Supergruppe bis zu {count} Mitglieder hinzufügen."; "lng_channel_not_accessible" = "Dieser Kanal ist nicht zugänglich"; "lng_group_not_accessible" = "Diese Gruppe ist nicht zugänglich."; "lng_channels_too_much_public_about" = "Du bereits zu viele öffentliche Benutzernamen reserviert. Du kannst jederzeit einen bestehenden Benutzernamen von deinen Gruppen oder Kanälen entfernen."; "lng_channels_too_much_public_revoke_confirm_group" = "Link {link} wirklich widerrufen?\n\nDie Gruppe «{group}» wird dann nicht mehr öffentlich sein."; "lng_channels_too_much_public_revoke_confirm_channel" = "Link {link} wirklich widerrufen?\n\nDer Kanal «{group}» wird dann nicht mehr öffentlich sein."; "lng_channels_too_much_public_revoke" = "Widerrufen"; "lng_group_invite_bad_link" = "Der Einladungslink ist nicht mehr gültig."; "lng_group_invite_want_join" = "Möchtest du der Gruppe «{title}» beitreten?"; "lng_group_invite_want_join_channel" = "Möchtest du dem Kanal «{title}» beitreten?"; "lng_group_invite_join" = "Beitreten"; "lng_group_invite_members#one" = "{count} Mitglied, darunter:"; "lng_group_invite_members#other" = "{count} Mitglieder, darunter:"; "lng_group_invite_link" = "Einladungslink:"; "lng_group_invite_create" = "Neuer Link"; "lng_group_invite_about" = "Jeder, der Telegram installiert hat,\nkann anhand dieses Links in deine Gruppe."; "lng_group_invite_create_new" = "Link widerrufen"; "lng_group_invite_about_new" = "Dein vorheriger Link wird ungültig, wenn du einen neuen erstellst."; "lng_group_invite_copied" = "Einladungslink wurde kopiert."; "lng_group_invite_no_room" = "Leider kannst du dieser Gruppe nicht mehr beitreten, da sie bereits voll ist."; "lng_channel_public_link_copied" = "Link in die Zwischenablage kopiert.\n"; "lng_forwarded" = "Weitergeleitet von {user}"; "lng_forwarded_date" = "Original: {date}"; "lng_forwarded_channel" = "Weitergeleitet aus {channel}"; "lng_forwarded_via" = "Weitergeleitet von {user} via {inline_bot}"; "lng_forwarded_channel_via" = "Weitergeleitet aus {channel} via {inline_bot}"; "lng_forwarded_signed" = "{channel} ({user})"; "lng_in_reply_to" = "Antwort auf"; "lng_edited" = "bearbeitet"; "lng_edited_date" = "Bearbeitet: {date}"; "lng_cancel_edit_post_sure" = "Bearbeitung abbrechen?"; "lng_cancel_edit_post_yes" = "Ja"; "lng_cancel_edit_post_no" = "Nein"; "lng_bot_share_location_unavailable" = "Teilen von Standorten ist derzeit bei Telegram Desktop nicht möglich."; "lng_bot_inline_geo_unavailable" = "Dieser Bot braucht deinen aktuellen Standort. Die Funktion ist bei Telegram Desktop derzeit nicht verfügbar."; "lng_bot_share_phone" = "Telefonnummer teilen?"; "lng_bot_share_phone_confirm" = "Teilen"; "lng_attach_failed" = "Uploadfehler"; "lng_attach_file" = "Datei"; "lng_attach_photo" = "Bild"; "lng_media_type" = "Dateityp"; "lng_media_type_photos" = "Bilder"; "lng_media_type_videos" = "Videos"; "lng_media_type_songs" = "Audios"; "lng_media_type_files" = "Dateien"; "lng_media_type_audios" = "Sprachnachrichten"; "lng_media_type_links" = "Links"; "lng_media_open_with" = "Öffnen mit"; "lng_media_download" = "Download"; "lng_media_cancel" = "Abbrechen"; "lng_media_video" = "Video"; "lng_media_audio" = "Sprachnachricht"; "lng_media_auto_settings" = "Einstellungen für Mediendownloads"; "lng_media_auto_photo" = "Automatischer Bilder-Download"; "lng_media_auto_audio" = "Automatischer Audio-Download"; "lng_media_auto_gif" = "Automatischer GIF-Download"; "lng_media_auto_private_chats" = "Chats"; "lng_media_auto_groups" = "Gruppen und Kanäle"; "lng_media_auto_play" = "Automatisch Abspielen"; "lng_emoji_category0" = "Häufig genutzt"; "lng_emoji_category1" = "Personen"; "lng_emoji_category2" = "Natur"; "lng_emoji_category3" = "Essen & Trinken"; "lng_emoji_category4" = "Aktivitäten"; "lng_emoji_category5" = "Reisen & Orte"; "lng_emoji_category6" = "Objekte"; "lng_emoji_category7" = "Symbole & Flaggen"; "lng_emoji_hide_panel" = "Hier klicken, um Emoji Sidebar zu verstecken"; "lng_recent_stickers" = "Häufig genutzt"; "lng_faved_stickers" = "Favoriten"; "lng_faved_stickers_add" = "Zu Favoriten hinzufügen"; "lng_faved_stickers_remove" = "Aus Favoriten entfernen"; "lng_group_stickers" = "Gruppen-Sticker"; "lng_group_stickers_description" = "Paket wählen, welches alle in der Gruppe sehen können."; "lng_group_stickers_add" = "Sticker-Paket wählen"; "lng_switch_stickers" = "Sticker"; "lng_switch_emoji" = "Emoji"; "lng_switch_gifs" = "GIFs"; "lng_stickers_featured_add" = "Hinzufügen"; "lng_gifs_search" = "Suche GIFs"; "lng_gifs_no_saved" = "Du hast noch keine GIFs gespeichert."; "lng_inline_bot_no_results" = "Keine Ergebnisse."; "lng_inline_bot_via" = "via {inline_bot}"; "lng_box_remove" = "Entfernen"; "lng_stickers_installed_tab" = "Sticker"; "lng_stickers_featured_tab" = "Angesagt"; "lng_stickers_archived_tab" = "Archiviert"; "lng_stickers_remove_pack" = "«{sticker_pack}» entfernen?"; "lng_stickers_add_pack" = "Sticker hinzufügen"; "lng_stickers_share_pack" = "Sticker teilen"; "lng_stickers_not_found" = "Sticker-Paket nicht gefunden."; "lng_stickers_packs_archived" = "Einige deiner unbenutzen Sticker wurden archiviert, damit du Platz für neue Sticker hast."; "lng_stickers_archived" = "Archivierte Sticker"; "lng_stickers_copied" = "Sticker-Paket Link in die Zwischenablage kopiert."; "lng_stickers_default_set" = "Große Denker"; "lng_stickers_you_have" = "Sticker-Pakete verwalten"; "lng_stickers_featured" = "Angesagte Sticker"; "lng_stickers_return" = "Rückgängig"; "lng_stickers_count#one" = "{count} Sticker"; "lng_stickers_count#other" = "{count} Sticker"; "lng_stickers_masks_pack" = "Das ist ein Masken-Paket. Du kannst Masken nur im Editor unserer mobilen Apps verwenden."; "lng_stickers_group_set" = "Gruppen-Sticker"; "lng_stickers_remove_group_set" = "Gruppen-Sticker entfernen?"; "lng_stickers_group_from_your" = "Sticker-Paket auswählen"; "lng_in_dlg_photo" = "Bild"; "lng_in_dlg_video" = "Video"; "lng_in_dlg_audio_file" = "Audiodatei"; "lng_in_dlg_contact" = "Kontakt"; "lng_in_dlg_audio" = "Sprachnachricht"; "lng_in_dlg_video_message" = "Videonachricht"; "lng_in_dlg_file" = "Datei"; "lng_in_dlg_sticker" = "Sticker"; "lng_in_dlg_sticker_emoji" = "{emoji} Sticker"; "lng_ban_user" = "Nutzer sperren"; "lng_delete_all_from" = "Alles von diesem Nutzer löschen"; "lng_report_spam" = "Spam melden"; "lng_report_spam_hide" = "Schließen"; "lng_report_spam_thanks" = "Danke!"; "lng_report_spam_sure" = "Wirklich diesen Nutzer melden?"; "lng_report_spam_sure_group" = "Spam in dieser Gruppe melden?"; "lng_report_spam_sure_channel" = "Möchtest du wirklich Spam in diesem Kanal melden?"; "lng_report_spam_ok" = "Melden"; "lng_cant_send_to_not_contact" = "Derzeit kannst du nur Personen schreiben, wenn ihr eure Nummern ausgetauscht habt. {more_info}"; "lng_cant_invite_not_contact" = "Du kannst nur Personen hinzufügen, wenn ihr eure Nummern ausgetauscht habt. {more_info}"; "lng_cant_send_to_not_contact_flood" = "Heute hast du zu viele nicht-gemeinsame Kontakte angeschrieben, versuche es bitte morgen wieder. Du kannst heute nur noch Leuten antworten, die dir zuerst eine Nachricht senden."; "lng_cant_invite_not_contact_flood" = "Diesen Nutzer kannst du nicht hinzufügen, da du heute zu viele nicht-gemeinsame Kontakte angeschrieben hast. Versuche es morgen wieder oder frage ein anderes Mitglied ob es den Teilnehmer hinzufügen kann."; "lng_cant_more_info" = "Weitere Infos »"; "lng_cant_invite_banned" = "Nur Admins können diesen Nutzer hinzufügen."; "lng_cant_invite_privacy" = "Du kannst mit diesem Nutzer keine Gruppe erstellen, weil er es nicht erlaubt."; "lng_cant_invite_privacy_channel" = "Du kannst diesen Nutzer keinen Kanälen hinzufügen, weil er es nicht erlaubt."; "lng_cant_delete_group#one" = "Über die App kannst du derzeit keine Gruppen mit mehr als {count} Mitglied löschen. Bitte melde dich bei unserem Support Chat, wenn du diese Gruppe löschen lassen möchtest."; "lng_cant_delete_group#other" = "Über die App kannst du derzeit keine Gruppen mit mehr als {count} Mitgliedern löschen. Bitte melde dich bei unserem Support Chat, wenn du diese Gruppe löschen lassen möchtest."; "lng_cant_delete_channel#one" = "Über die App kannst du derzeit keine Kanäle mit mehr als {count} Mitglied löschen. Bitte melde dich bei unserem Support Chat, wenn du diesen Kanal löschen lassen möchtest."; "lng_cant_delete_channel#other" = "Über die App kannst du derzeit keinen Kanäle mit mehr als {count} Mitgliedern löschen. Bitte melde dich bei unserem Support Chat, wenn du diesen Kanal löschen lassen möchtest."; "lng_cant_do_this" = "Verzeihung. Das ist leider nicht möglich."; "lng_send_button" = "Senden"; "lng_message_ph" = "Schreibe deine Nachricht..."; "lng_broadcast_ph" = "Sende einen Broadcast..."; "lng_broadcast_silent_ph" = "Lautloser Broadcast..."; "lng_record_cancel" = "Zum Abbrechen rausbewegen"; "lng_will_be_notified" = "Mitglieder werden benachrichtigt"; "lng_wont_be_notified" = "Mitglieder werden nicht benachrichtigt"; "lng_willbe_history" = "Chat auswählen, um zu schreiben"; "lng_from_you" = "Ich"; "lng_from_draft" = "Entwurf"; "lng_bot_description" = "Was kann dieser Bot?"; "lng_unblock_button" = "Freigeben"; "lng_channel_join" = "Kanal beitreten"; "lng_channel_mute" = "Stumm"; "lng_channel_unmute" = "Stumm aus"; "lng_dialogs_text_with_from" = "{from_part} {message}"; "lng_dialogs_text_from_wrapped" = "{from}:"; "lng_dialogs_text_media" = "{media_part} {caption}"; "lng_dialogs_text_media_wrapped" = "{media},"; "lng_dialogs_show_all_chats" = "Alle Chats zeigen"; "lng_dialogs_hide_muted_chats" = "Stummgeschaltete Chats verstecken"; "lng_open_this_link" = "Diesen Link öffnen?"; "lng_open_link" = "Öffnen"; "lng_allow_bot_pass" = "Darf {bot_name} deinen Anzeigenamen und deine id mit Internetseiten teilen, die du mit diesem Bot öffnest?"; "lng_allow_bot" = "Erlauben"; "lng_bot_start" = "Starten"; "lng_bot_choose_group" = "Gruppe auswählen"; "lng_bot_no_groups" = "Du hast keine Gruppen"; "lng_bot_groups_not_found" = "Keine Gruppen gefunden"; "lng_bot_sure_invite" = "Den Bot zu «{group}» hinzufügen?"; "lng_bot_already_in_group" = "Der Bot ist schon in dieser Gruppe."; "lng_bot_choose_chat" = "Chat auswählen"; "lng_bot_no_chats" = "Du hast keine Chats"; "lng_bot_chats_not_found" = "Keine Chats gefunden"; "lng_bot_sure_share_game" = "Das Spiel mit {user} teilen?"; "lng_bot_sure_share_game_group" = "Das Spiel mit «{group}» teilen?"; "lng_typing" = "tippt"; "lng_user_typing" = "{user} tippt"; "lng_users_typing" = "{user} und {second_user} tippen"; "lng_many_typing#one" = "{count} tippt"; "lng_many_typing#other" = "{count} tippen"; "lng_playing_game" = "spielt ein Spiel"; "lng_user_playing_game" = "{user} spielt ein Spiel"; "lng_users_playing_game" = "{user} und {second_user} spielen ein Spiel"; "lng_many_playing_game#one" = "{count} spielt ein Spiel"; "lng_many_playing_game#other" = "{count} spielen ein Spiel"; "lng_send_action_record_video" = "schickt Video"; "lng_user_action_record_video" = "{user} sendet ein Video"; "lng_send_action_upload_video" = "schickt ein Video"; "lng_user_action_upload_video" = "{user} sendet ein Video"; "lng_send_action_record_audio" = "nimmt Sprachnachricht auf"; "lng_user_action_record_audio" = "{user} nimmt Sprachnachricht auf"; "lng_send_action_upload_audio" = "sendet Sprachnachricht"; "lng_user_action_upload_audio" = "{user} sendet Sprachnachricht"; "lng_send_action_record_round" = "nimmt Videonachricht auf"; "lng_user_action_record_round" = "{user} nimmt Videonachricht auf"; "lng_send_action_upload_round" = "sendet eine Videonachricht"; "lng_user_action_upload_round" = "{user} sendet eine Videonachricht"; "lng_send_action_upload_photo" = "sendet ein Bild"; "lng_user_action_upload_photo" = "{user} sendet ein Bild"; "lng_send_action_upload_file" = "sendet eine Datei"; "lng_user_action_upload_file" = "{user} sendet eine Datei"; "lng_send_action_geo_location" = "wählt einen Standort aus"; "lng_user_action_geo_location" = "{user} wählt einen Standort aus"; "lng_send_action_choose_contact" = "wählt einen Kontakt aus"; "lng_user_action_choose_contact" = "{user} wählt einen Kontakt aus"; "lng_unread_bar#one" = "{count} ungelesene Nachricht"; "lng_unread_bar#other" = "{count} ungelesene Nachrichten"; "lng_maps_point" = "Standort"; "lng_save_photo" = "Bild speichern"; "lng_save_video" = "Video speichern"; "lng_save_audio_file" = "Audiodatei speichern"; "lng_save_audio" = "Sprachnachricht speichern"; "lng_save_file" = "Datei speichern"; "lng_save_downloaded" = "{ready} / {total} {mb}"; "lng_duration_and_size" = "{duration}, {size}"; "lng_duration_played" = "{played} / {duration}"; "lng_date_and_duration" = "{date}, {duration}"; "lng_choose_image" = "Bild auswählen"; "lng_choose_files" = "Dateien auswählen"; "lng_game_tag" = "Spiel"; "lng_context_view_profile" = "Profil öffnen"; "lng_context_view_group" = "Gruppeninfo zeigen"; "lng_context_view_channel" = "Kanalinfo anzeigen"; "lng_context_pin_to_top" = "Oben anheften"; "lng_context_unpin_from_top" = "Oben entfernen"; "lng_context_promote_admin" = "Zum Admin machen"; "lng_context_edit_permissions" = "Berechtigungen bearbeiten"; "lng_context_restrict_user" = "Nutzer einschränken"; "lng_context_remove_from_group" = "Aus Gruppe entfernen"; "lng_context_copy_link" = "Link kopieren"; "lng_context_copy_post_link" = "Nachrichtenlink kopieren"; "lng_context_copy_email" = "E-Mail-Adresse kopieren"; "lng_context_copy_hashtag" = "Hashtag kopieren"; "lng_context_copy_mention" = "Benutzername kopieren"; "lng_context_save_image" = "Bild speichern unter..."; "lng_context_forward_image" = "Bild weiterleiten"; "lng_context_delete_image" = "Bild löschen"; "lng_context_copy_image" = "Bild kopieren"; "lng_context_close_image" = "Bild schließen"; "lng_context_cancel_download" = "Download abbrechen"; "lng_context_show_in_folder" = "Im Ordner anzeigen"; "lng_context_show_in_finder" = "Im Finder zeigen"; "lng_context_save_video" = "Video speichern unter..."; "lng_context_save_audio_file" = "Audiodatei speichern unter..."; "lng_context_save_audio" = "Sprachnachricht speichern unter..."; "lng_context_pack_info" = "Sticker-Paket"; "lng_context_pack_add" = "Sticker hinzufügen"; "lng_context_save_file" = "Datei speichern unter..."; "lng_context_forward_file" = "Datei weiterleiten"; "lng_context_delete_file" = "Datei löschen"; "lng_context_close_file" = "Datei schließen"; "lng_context_copy_text" = "Text kopieren"; "lng_context_open_gif" = "GIF öffnen"; "lng_context_save_gif" = "GIF speichern"; "lng_context_to_msg" = "Zur Nachricht"; "lng_context_reply_msg" = "Antworten"; "lng_context_edit_msg" = "Bearbeiten"; "lng_context_forward_msg" = "Nachricht weiterleiten"; "lng_context_delete_msg" = "Nachricht löschen"; "lng_context_select_msg" = "Nachricht auswählen"; "lng_context_pin_msg" = "Nachricht anheften"; "lng_context_unpin_msg" = "Nachricht entfernen"; "lng_context_cancel_upload" = "Upload abbrechen"; "lng_context_copy_selected" = "Text kopieren"; "lng_context_copy_selected_items" = "Als Text kopieren"; "lng_context_forward_selected" = "Auswahl weiterleiten"; "lng_context_delete_selected" = "Auswahl löschen"; "lng_context_clear_selection" = "Auswahl aufheben"; "lng_really_send_image" = "Willst du dieses Bild senden?"; "lng_really_send_file" = "Willst du diese Datei senden?"; "lng_really_share_contact" = "Willst du diesen Kontakt senden?"; "lng_send_images_compress#one" = "Bild komprimieren"; "lng_send_images_compress#other" = "Bilder komprimieren"; "lng_send_image_non_local" = "{name} befindet sich nicht direkt auf dem Gerät"; "lng_send_image_empty" = "Leere Datei {name} konnte nicht gesendet werden"; "lng_send_image_too_large" = "{name} ist größer als 1500 MB und kann daher nicht gesendet werden"; "lng_send_folder" = "Verzeichnis «{name}» kann nicht gesendet werden :("; "lng_send_images_selected#one" = "{count} Bild ausgewählt"; "lng_send_images_selected#other" = "{count} Bilder ausgewählt"; "lng_send_photos#one" = "{count} Bild senden"; "lng_send_photos#other" = "{count} Bilder senden"; "lng_send_files_selected#one" = "{count} Datei ausgewählt"; "lng_send_files_selected#other" = "{count} Dateien ausgewählt"; "lng_send_files#one" = "{count} Datei senden"; "lng_send_files#other" = "{count} Dateien senden"; "lng_forward_choose" = "Empfänger wählen..."; "lng_forward_cant" = "Weiterleiten nicht möglich :("; "lng_forward_confirm" = "An {recipient} weiterleiten?"; "lng_forward_share_contact" = "Kontakt an {recipient} senden?"; "lng_forward_share_cant" = "Kontakt teilen nicht möglich :("; "lng_forward_send_file_confirm" = "«{name}» an {recipient} senden?"; "lng_forward_send_files_confirm" = "Ausgewählte Dateien an {recipient} senden?"; "lng_forward_send_files_cant" = "Medien senden nicht möglich :("; "lng_forward_send" = "Senden"; "lng_forward_messages#one" = "{count} weitergeleitete Nachricht"; "lng_forward_messages#other" = "{count} weitergeleitete Nachrichten"; "lng_forwarding_from#one" = "{user} und {count} anderer"; "lng_forwarding_from#other" = "{user} und {count} andere"; "lng_forwarding_from_two" = "{user} und {second_user}"; "lng_inline_switch_choose" = "Chat wählen..."; "lng_inline_switch_cant" = "Hier kannst du nicht schreiben :("; "lng_share_cant" = "Weiterleitung nicht möglich :("; "lng_reply_cant" = "Leider kann man alte Nachrichten in Supergruppen nicht beantworten :("; "lng_reply_cant_forward" = "Leider kann man alte Nachrichten in Supergruppen nicht beantworten. Stattdessen mit Kommentar weiterleiten?"; "lng_share_title" = "Teilen"; "lng_share_copy_link" = "Link kopieren"; "lng_share_confirm" = "Senden"; "lng_share_wrong_user" = "Dieses Spiel wurde von einem anderen Nutzer geöffnet."; "lng_share_game_link_copied" = "Link in die Zwischenablage kopiert."; "lng_share_done" = "Fertig!"; "lng_contact_phone" = "Telefonnummer"; "lng_enter_contact_data" = "Neuer Kontakt"; "lng_edit_group_title" = "Gruppennamen ändern"; "lng_edit_contact_title" = "Kontaktnamen ändern"; "lng_edit_channel_title" = "Kanal bearbeiten"; "lng_edit_sign_messages" = "Nachrichten unterschreiben"; "lng_edit_group_who_invites" = "Wer kann Mitglieder hinzufügen"; "lng_edit_group_invites_everybody" = "Alle Mitglieder"; "lng_edit_group_invites_only_admins" = "Nur Admins"; "lng_edit_group" = "Gruppe bearbeiten"; "lng_edit_self_title" = "Name bearbeiten"; "lng_confirm_contact_data" = "Neuer Kontakt"; "lng_add_contact" = "Erstellen"; "lng_add_contact_button" = "Neuer Kontakt"; "lng_contacts_header" = "Kontakte"; "lng_contact_not_joined" = "Leider hat {name} noch kein Telegram, aber du kannst deinen Kontakt gerne einladen.\n\nSobald jemand aus deinem Telefonbuch Telegram nutzt, informieren wir dich."; "lng_try_other_contact" = "Andere probieren"; "lng_create_group_link" = "Link"; "lng_create_group_invite_link" = "Einladungslink"; "lng_create_group_photo" = "Bild festlegen"; "lng_create_group_description" = "Beschreibung (optional)"; "lng_drag_images_here" = "Hierher ziehen"; "lng_drag_photos_here" = "Hierher ziehen"; "lng_drag_files_here" = "Hierher ziehen"; "lng_drag_to_send_quick" = "Besonders schneller Versand"; "lng_drag_to_send_no_compression" = "Versand ohne Qualitätsverlust"; "lng_drag_to_send_files" = "um sie als Dateien zu senden"; "lng_selected_clear" = "Abbrechen"; "lng_selected_delete" = "Löschen"; "lng_selected_forward" = "Weiterleiten"; "lng_selected_count#one" = "{count} Nachricht"; "lng_selected_count#other" = "{count} Nachrichten"; "lng_selected_cancel_sure_this" = "Upload abbrechen?"; "lng_selected_upload_stop" = "Stoppen"; "lng_selected_delete_sure_this" = "Diese Nachricht wirklich löschen?"; "lng_selected_delete_sure#one" = "Möchtest du {count} Nachricht löschen?"; "lng_selected_delete_sure#other" = "Möchtest du {count} Nachrichten löschen?"; "lng_delete_photo_sure" = "Dieses Bild wirklich löschen?"; "lng_delete_for_everyone_hint#one" = "Das wird es bei allen im Chat löschen."; "lng_delete_for_everyone_hint#other" = "Das wird sie bei allen im Chat löschen."; "lng_delete_for_me_chat_hint#one" = "Das wird es nur bei dir löschen und nicht bei anderen Teilnehmern."; "lng_delete_for_me_chat_hint#other" = "Das wird sie nur bei dir löschen und nicht bei anderen Teilnehmern."; "lng_delete_for_me_hint#one" = "Das wird es nur bei dir löschen."; "lng_delete_for_me_hint#other" = "Das wird sie nur bei dir löschen."; "lng_delete_for_everyone_check" = "Bei allen löschen"; "lng_delete_for_other_check" = "Bei {user} löschen"; "lng_box_delete" = "Löschen"; "lng_box_leave" = "Verlassen"; "lng_about_version" = "Version {version}"; "lng_about_text_1" = "Offizielle Messaging App basierend auf [a href=\"https://core.telegram.org/api\"]Telegram API[/a]\nfür Geschwindigkeit und Sicherheit."; "lng_about_text_2" = "Diese Software steht unter der [a href=\"https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE\"]GNU GPL[/a] Version 3, der Quelltext ist bei [a href=\"https://github.com/telegramdesktop/tdesktop\"]GitHub[/a] verfügbar."; "lng_about_text_3" = "Fragen? Besuche doch {faq_open}unsere FAQ{faq_close} für mehr Infos."; "lng_about_done" = "Fertig"; "lng_search_no_results" = "Keine Nachrichten gefunden"; "lng_search_found_results#one" = "{count} Nachricht gefunden"; "lng_search_found_results#other" = "{count} Nachrichten gefunden"; "lng_search_global_results" = "Globale Suchergebnisse"; "lng_search_messages_from" = "Zeige Nachrichten von"; "lng_media_save_progress" = "{ready} von {total} {mb}"; "lng_mediaview_save_as" = "Speichern unter…"; "lng_mediaview_copy" = "Kopieren"; "lng_mediaview_forward" = "Weiterleiten"; "lng_mediaview_delete" = "Löschen"; "lng_mediaview_photos_all" = "Alle Fotos anzeigen"; "lng_mediaview_files_all" = "Alle Dateien anzeigen"; "lng_mediaview_single_photo" = "Bild"; "lng_mediaview_group_photo" = "Bild"; "lng_mediaview_channel_photo" = "Bild"; "lng_mediaview_profile_photo" = "Profilbild"; "lng_mediaview_file_n_of_count" = "{file} {n} von {count}"; "lng_mediaview_n_of_count" = "Bild {n} von {count}"; "lng_mediaview_doc_image" = "Datei"; "lng_mediaview_today" = "heute um {time}"; "lng_mediaview_yesterday" = "gestern um {time}"; "lng_mediaview_date_time" = "{date} um {time}"; "lng_mediaview_saved" = "Bild wurde im [c]Downloads[/c] Ordner gespeichert"; "lng_theme_preview_title" = "Themenvorschau"; "lng_theme_preview_generating" = "Erstelle Vorschau des Farbthemas..."; "lng_theme_preview_invalid" = "Themen-Datei beinhaltet ungültige Daten."; "lng_theme_preview_apply" = "Thema anwenden"; "lng_new_authorization" = "{name},\nwir haben eine Anmeldung von einem neuen Gerät am {day}, {date} um {time} festgestellt.\n\nGerät: {device}\nStandort: {location}\n\nWarst du das selbst? Wenn du das nicht selbst gewesen bist, melde alle anderen Sitzungen in den Telegram Einstellungen unverzüglich ab.\n\nBeachte unsere zweistufige Bestätigung, welche du in den Telegram Einstellungen optional aktivieren kannst.\n\nDanke,\nDein Telegram Team"; "lng_new_version_wrap" = "Telegram Desktop wurde aktualisiert auf Version {version}\n\n{changes}\n\nGesamter Versionsverlauf:\n{link}"; "lng_new_version_minor" = "— Fehlerbehebungen und Softwareoptimierungen"; "lng_new_version_text" = "— Verbesserte Videonachrichten: Fortschrittsanzeige (Rahmen um die Videonachricht), Bild-in-Bild-Unterstützung, Anzeige der verbleibenden Zeit.\n— Sprach- und Videonachrichten werden nun automatisch nacheinander abgespielt."; "lng_menu_insert_unicode" = "Unicode-Steuerzeichen einfügen"; "lng_full_name" = "{first_name} {last_name}"; "lng_confirm_phone_link_invalid" = "Dieser Link ist abgelaufen oder ungültig."; "lng_confirm_phone_title" = "Zurücksetzung abbrechen"; "lng_confirm_phone_about" = "Jemand mit Zugang zu deiner Telefonnummer {phone} hat die Kontolöschung und Zurücksetzung der zweistufige Bestätigung beantragt.\n\nWenn du das nicht selbst gewesen bist, tippe den Code der SMS ein, den wir dir gerade gesendet haben."; "lng_confirm_phone_success" = "Geschafft!\n\nDer Löschvorgang für dein Konto {phone} wurde abgebrochen. Du kannst dieses Fenster jetzt schließen."; "lng_confirm_phone_send" = "Senden"; "lng_confirm_phone_enter_code" = "Bitte den Code eingeben."; "lng_theme_editor_no_keys" = "Noch keine Werte in der Palette"; "lng_theme_editor_cant_change_theme" = "Neue Themen kannst du während der Bearbeitung nicht anwenden. Bitte schliesse zuerst den Editor."; "lng_theme_editor_new_keys" = "Noch nicht in der Palette"; "lng_theme_editor_background_image" = "Hintergrundbild"; "lng_theme_editor_saved_to_jpg" = "Als JPEG gespeichert, {size}"; "lng_theme_editor_read_from_jpg" = "JPEG Bild, {size}"; "lng_theme_editor_read_from_png" = "PNG Bild, {size}"; "lng_theme_editor_export" = "Exportieren"; "lng_theme_editor_choose_image" = "Hintergrundbild wählen"; "lng_theme_editor_save_palette" = "Palettendatei speichern"; "lng_theme_editor_choose_name" = "Themen-Datei speichern"; "lng_theme_editor_error" = "Ein Fehler ist im Editor aufgetreten. Mehr Infos gibt es in der Datei 'log.txt'."; "lng_theme_editor_done" = "Thema erfolgreich exportiert!"; "lng_theme_editor_title" = "Farbpalette bearbeiten"; "lng_theme_editor_export_button" = "Thema exportieren"; "lng_payments_not_supported" = "Telegram Desktop unterstützt derzeit leider noch keine Zahlungen. Bitte benutze eine unserer mobilen Apps, um die Zahlung abzuschliessen."; "lng_payments_receipt_label" = "Beleg"; "lng_payments_receipt_label_test" = "Test-Beleg"; "lng_payments_invoice_label" = "Rechnung"; "lng_payments_invoice_label_test" = "Test-Rechnung"; "lng_payments_receipt_button" = "Beleg"; "lng_call_status_incoming" = "ruft dich an..."; "lng_call_status_connecting" = "verbinden..."; "lng_call_status_exchanging" = "tausche Verschlüsselungsschlüssel aus..."; "lng_call_status_waiting" = "warte..."; "lng_call_status_requesting" = "anfordern..."; "lng_call_status_hanging" = "lege auf..."; "lng_call_status_ended" = "Anruf beendet"; "lng_call_status_failed" = "Verbindung nicht möglich"; "lng_call_status_ringing" = "es klingelt..."; "lng_call_status_busy" = "besetzt"; "lng_call_fingerprint_tooltip" = "Wenn diese Emoji genau so bei {user} aussehen, ist euer Anruf 100% sicher."; "lng_call_error_not_available" = "{user} akzeptiert leider keine Anrufe."; "lng_call_error_incompatible" = "{user}s App ist nicht mit unserem Protokoll kompatibel. Dein Chatpartner muss seine App aktualisieren, bevor du anrufen kannst."; "lng_call_error_outdated" = "{user}s App unterstützt keine Anrufe. Dein Chatpartner muss seine App aktualisieren, bevor du anrufen kannst."; "lng_call_error_audio_io" = "Leider gibt es ein Problem mit der Audiowiedergabe auf deinem Computer. Bitte stelle sicher, dass Lautsprecher und Mikrofon funktionieren und probiere es erneut."; "lng_call_bar_hangup" = "Anruf beenden"; "lng_call_box_title" = "Anrufe"; "lng_call_box_about" = "Du hast noch keine Anrufe geführt."; "lng_call_box_status_today" = "{time}"; "lng_call_box_status_yesterday" = "Gestern um {time}"; "lng_call_box_status_date" = "{date} um {time}"; "lng_call_box_status_group" = "({count}) {status}"; "lng_call_outgoing" = "Ausgehender Anruf"; "lng_call_incoming" = "Eingehender Anruf"; "lng_call_missed" = "Verpasster Anruf"; "lng_call_cancelled" = "Abgebrochener Anruf"; "lng_call_declined" = "Abgelehnter Anruf"; "lng_call_duration_info" = "{time}, {duration}"; "lng_call_type_and_duration" = "{type} ({duration})"; "lng_call_rate_label" = "Gesprächsqualität bewerten"; "lng_call_rate_comment" = "Beschreibung (optional)"; "lng_player_message_today" = "Heute um {time}"; "lng_player_message_yesterday" = "Gestern um {time}"; "lng_player_message_date" = "{date} um {time}"; "lng_rights_edit_admin" = "Administrator bearbeiten"; "lng_rights_edit_admin_header" = "Was kann dieser Admin machen?"; "lng_rights_about_add_admins_yes" = "Dieser Admin wird neue Admins mit gleichen (oder eingeschränkten) Rechten hinzufügen können."; "lng_rights_about_add_admins_no" = "Dieser Admin kann keine neuen Admins hinzufügen."; "lng_rights_about_admin_cant_edit" = "Du kannst die Berechtigungen für diesen Admin nicht ändern."; "lng_rights_user_restrictions" = "Nutzerbeschränkungen"; "lng_rights_user_restrictions_header" = "Was kann dieser Nutzer machen?"; "lng_rights_channel_info" = "Kanal-Info ändern"; "lng_rights_channel_post" = "Nachrichten senden"; "lng_rights_channel_edit" = "Nachrichten von anderen bearbeiten"; "lng_rights_channel_delete" = "Nachrichten von anderen löschen"; "lng_rights_group_info" = "Gruppen-Info ändern"; "lng_rights_group_ban" = "Nutzer sperren"; "lng_rights_group_invite_link" = "Nutzer per Link einladen"; "lng_rights_group_invite" = "Nutzer hinzufügen"; "lng_rights_group_pin" = "Nachrichten anheften"; "lng_rights_group_delete" = "Nachrichten löschen"; "lng_rights_add_admins" = "Neue Admins hinzufügen"; "lng_rights_chat_read" = "Nachrichten lesen"; "lng_rights_chat_send_text" = "Nachrichten senden"; "lng_rights_chat_send_media" = "Medien senden"; "lng_rights_chat_send_stickers" = "Sticker & GIFs senden"; "lng_rights_chat_send_links" = "Linkvorschau senden"; "lng_rights_chat_banned_until_header" = "Eingeschränkt bis"; "lng_rights_chat_banned_forever" = "Dauerhaft"; "lng_rights_chat_banned_day#one" = "Für {count} Tag"; "lng_rights_chat_banned_day#other" = "Für {count} Tage"; "lng_rights_chat_banned_week#one" = "Für {count} Woche"; "lng_rights_chat_banned_week#other" = "Für {count} Wochen"; "lng_rights_chat_banned_custom" = "Eigener"; "lng_rights_chat_banned_custom_date" = "Bis {date}"; "lng_rights_chat_banned_block" = "Sperren und aus Gruppe entfernen"; "lng_restricted_send_message" = "Die Admins dieser Gruppe haben dir die Berechtigung entzogen, Nachrichten zu senden."; "lng_restricted_send_media" = "Die Admins dieser Gruppe haben dir die Berechtigung entzogen, Medien zu senden."; "lng_restricted_send_stickers" = "Die Admins dieser Gruppe haben dir die Berechtigung entzogen, Sticker zu senden."; "lng_restricted_send_gifs" = "Die Admins dieser Gruppe haben dir die Berechtigung entzogen, GIFs zu senden."; "lng_restricted_send_inline" = "Die Admins dieser Gruppe haben dir die Berechtigung entzogen, Inline-Bot Inhalte zu senden."; "lng_restricted_list_title" = "Eingeschränke Nutzer"; "lng_banned_list_title" = "Gesperrte Nutzer"; "lng_admin_log_title_all" = "Alle Aktionen"; "lng_admin_log_title_selected" = "Ausgewählte Aktionen"; "lng_admin_log_filter" = "Filtern"; "lng_admin_log_filter_title" = "Filtern"; "lng_admin_log_filter_all_actions" = "Alle Aktionen"; "lng_admin_log_filter_restrictions" = "Neue Einschränkungen"; "lng_admin_log_filter_admins_new" = "Neue Admins"; "lng_admin_log_filter_members_new" = "Neue Mitglieder"; "lng_admin_log_filter_info_group" = "Gruppen-Info"; "lng_admin_log_filter_info_channel" = "Kanal-Info"; "lng_admin_log_filter_messages_deleted" = "Gelöschte Nachrichten"; "lng_admin_log_filter_messages_edited" = "Bearbeitete Nachrichten"; "lng_admin_log_filter_messages_pinned" = "Angeheftete Nachrichten"; "lng_admin_log_filter_members_removed" = "Ehemalige Mitglieder"; "lng_admin_log_filter_all_admins" = "Alle Nutzer"; "lng_admin_log_about" = "Was ist das?"; "lng_admin_log_about_text" = "Das ist eine Liste aller Aktionen, die von Gruppenmitgliedern und Admins in den letzten 48 Stunden durchgeführt wurden."; "lng_admin_log_no_results_title" = "Keine Aktionen gefunden"; "lng_admin_log_no_results_text" = "Keine kürzlichen Ereignisse gefunden, die deinen Suchbegriff beinhalten."; "lng_admin_log_no_results_search_text" = "Keine kürzlichen Aktionen gefunden, die '{query}' beinhalten."; "lng_admin_log_no_events_title" = "Keine Aktionen gefunden"; "lng_admin_log_no_events_text" = "Es wurden keine Service-Aktionen \nvon Admins und Gruppenmitgliedern\nin den letzten 48 Stunden durchgeführt."; "lng_admin_log_empty_text" = "Leer"; "lng_admin_log_changed_title_group" = "{from} hat den Gruppennamen in «{title}» geändert"; "lng_admin_log_changed_title_channel" = "{from} hat den Kanalnamen in «{title}» geändert"; "lng_admin_log_changed_description_group" = "{from} hat Gruppenbeschreibung bearbeitet:"; "lng_admin_log_removed_description_group" = "{from} hat Gruppenbeschreibung entfernt"; "lng_admin_log_changed_description_channel" = "{from} hat Kanalbeschreibung bearbeitet:"; "lng_admin_log_removed_description_channel" = "{from} hat Kanalbeschreibung entfernt"; "lng_admin_log_previous_description" = "Vorherige Beschreibung"; "lng_admin_log_changed_link_group" = "{from} hat Gruppen-Link geändert:"; "lng_admin_log_removed_link_group" = "{from} hat Gruppen-Link entfernt"; "lng_admin_log_changed_link_channel" = "{from} hat Kanal-Link geändert:"; "lng_admin_log_removed_link_channel" = "{from} hat Kanal-Link entfernt"; "lng_admin_log_previous_link" = "Vorheriger Link"; "lng_admin_log_changed_photo_group" = "{from} hat Gruppenbild geändert"; "lng_admin_log_changed_photo_channel" = "{from} hat Kanalbild geändert"; "lng_admin_log_removed_photo_group" = "{from} hat Gruppenbild entfernt"; "lng_admin_log_removed_photo_channel" = "{from} hat Kanalbild entfernt"; "lng_admin_log_invites_enabled" = "{from} hat Gruppeneinladungen aktiviert"; "lng_admin_log_invites_disabled" = "{from} hat Gruppeneinladungen deaktiviert"; "lng_admin_log_signatures_enabled" = "{from} hat Unterschriften aktiviert"; "lng_admin_log_signatures_disabled" = "{from} hat Unterschriften deaktiviert"; "lng_admin_log_pinned_message" = "{from} hat Nachricht angeheftet:"; "lng_admin_log_unpinned_message" = "{from} hat angeheftete Nachricht entfernt"; "lng_admin_log_edited_caption" = "{from} hat Beschriftung bearbeitet:"; "lng_admin_log_removed_caption" = "{from} hat Beschriftung entfernt"; "lng_admin_log_previous_caption" = "Original-Beschriftung"; "lng_admin_log_edited_message" = "{from} hat Nachricht bearbeitet:"; "lng_admin_log_previous_message" = "Original-Nachricht"; "lng_admin_log_deleted_message" = "{from} hat Nachricht gelöscht:"; "lng_admin_log_participant_joined" = "{from} ist der Gruppe beigetreten"; "lng_admin_log_participant_joined_channel" = "{from} ist dem Kanal beigetreten"; "lng_admin_log_participant_left" = "{from} hat die Gruppe verlassen"; "lng_admin_log_participant_left_channel" = "{from} hat den Kanal verlassen"; "lng_admin_log_invited" = "hat {user} eingeladen"; "lng_admin_log_banned" = "hat {user} gesperrt"; "lng_admin_log_restricted" = "hat Einschränkungen für {user} {until} geändert"; "lng_admin_log_promoted" = "hat Berechtigungen für {user} geändert"; "lng_admin_log_changed_stickers_group" = "{from} hat das Sticker-Paket {sticker_set} der Gruppe geändert"; "lng_admin_log_changed_stickers_set" = "Sticker-Paket"; "lng_admin_log_removed_stickers_group" = "{from} hat das Sticker-Paket der Gruppe entfernt"; "lng_admin_log_user_with_username" = "{name} ({mention})"; "lng_admin_log_restricted_forever" = "unbegrenzt"; "lng_admin_log_restricted_until" = "bis {date}"; "lng_admin_log_banned_view_messages" = "Nachrichten lesen"; "lng_admin_log_banned_send_messages" = "Nachrichten senden"; "lng_admin_log_banned_send_media" = "Medien senden"; "lng_admin_log_banned_send_stickers" = "Sticker & GIFs senden"; "lng_admin_log_banned_embed_links" = "Linkvorschau senden"; "lng_admin_log_admin_change_info" = "Info ändern"; "lng_admin_log_admin_post_messages" = "Nachrichten senden"; "lng_admin_log_admin_edit_messages" = "Nachrichten bearbeiten"; "lng_admin_log_admin_delete_messages" = "Nachrichten löschen"; "lng_admin_log_admin_ban_users" = "Nutzer sperren"; "lng_admin_log_admin_invite_users" = "Nutzer hinzufügen"; "lng_admin_log_admin_invite_link" = "Nutzer per Link einladen"; "lng_admin_log_admin_pin_messages" = "Nachrichten anheften"; "lng_admin_log_admin_add_admins" = "Neue Admins hinzufügen"; // Not used "lng_topbar_info" = "Info"; "lng_profile_group_info" = "Gruppeninfo"; "lng_profile_channel_info" = "Info"; "lng_channel_add_admins" = "Neuer Administrator"; "lng_dlg_search_chat" = "In diesem Chat suchen"; "lng_dlg_search_channel" = "In diesem Kanal suchen"; // Wnd specific "lng_wnd_choose_program_menu" = "Öffnen mit..."; "lng_wnd_menu_undo" = "Rückgängig"; "lng_wnd_menu_redo" = "Wiederholen"; // Linux specific "lng_linux_menu_undo" = "Rückgängig"; "lng_linux_menu_redo" = "Wiederholen"; // Mac specific "lng_mac_choose_program_menu" = "Öffnen mit..."; "lng_mac_choose_app" = "Programm auswählen"; "lng_mac_choose_text" = "Programm auswählen, um \"{file}\" zu öffnen."; "lng_mac_enable_filter" = "Aktivieren:"; "lng_mac_recommended_apps" = " Empfohlene Programme"; "lng_mac_all_apps" = "Alle Programme"; "lng_mac_always_open_with" = "Immer öffnen mit"; "lng_mac_this_app_can_open" = "Dieses Programm kann \"{file}\"-Dateien öffnen."; "lng_mac_not_known_app" = "Es ist nicht bekannt, ob dieses Programm \"{file}\" öffnen kann."; "lng_mac_menu_services" = "Dienste"; "lng_mac_menu_hide_telegram" = "{telegram} ausblenden"; "lng_mac_menu_hide_others" = "Andere ausblenden"; "lng_mac_menu_show_all" = "Alle einblenden"; "lng_mac_menu_preferences" = "Einstellungen..."; "lng_mac_menu_quit_telegram" = "{telegram} beenden"; "lng_mac_menu_about_telegram" = "Über {telegram}"; "lng_mac_menu_file" = "Ablage"; "lng_mac_menu_logout" = "Abmelden"; "lng_mac_menu_edit" = "Bearbeiten"; "lng_mac_menu_undo" = "Widerrufen"; "lng_mac_menu_redo" = "Wiederholen"; "lng_mac_menu_cut" = "Ausschneiden"; "lng_mac_menu_copy" = "Kopieren"; "lng_mac_menu_paste" = "Einfügen"; "lng_mac_menu_delete" = "Löschen"; "lng_mac_menu_select_all" = "Alles auswählen"; "lng_mac_menu_window" = "Fenster"; "lng_mac_menu_contacts" = "Kontakte"; "lng_mac_menu_add_contact" = "Kontakt hinzufügen"; "lng_mac_menu_new_group" = "Neue Gruppe"; "lng_mac_menu_new_channel" = "Neuer Kanal"; "lng_mac_menu_show" = "Telegram anzeigen"; // Keys finished tdesktop-1.2.17/Telegram/Resources/langs/lang_es.strings000066400000000000000000002430131326245125100233040ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ "lng_language_name" = "Español"; "lng_switch_to_this" = "Cambiar a español"; "lng_menu_contacts" = "Contactos"; "lng_menu_calls" = "Llamadas"; "lng_menu_settings" = "Ajustes"; "lng_menu_about" = "Acerca de"; "lng_menu_update" = "Actualizar"; "lng_menu_restart" = "Reiniciar"; "lng_menu_back" = "Atrás"; "lng_menu_night_mode" = "Modo noche"; "lng_disable_notifications_from_tray" = "Desactivar notificaciones"; "lng_enable_notifications_from_tray" = "Activar notificaciones"; "lng_open_from_tray" = "Abrir Telegram"; "lng_minimize_to_tray" = "Minimizar al icono"; "lng_quit_from_tray" = "Salir de Telegram"; "lng_tray_icon_text" = "Telegram aún está funcionando,\npuedes cambiar esto en los ajustes. \nSi este icono desaparece del menú, \npuedes arrastrarlo aquí desde los iconos ocultos."; "lng_month1" = "enero"; "lng_month2" = "febrero"; "lng_month3" = "marzo"; "lng_month4" = "abril"; "lng_month5" = "mayo"; "lng_month6" = "junio"; "lng_month7" = "julio"; "lng_month8" = "agosto"; "lng_month9" = "septiembre"; "lng_month10" = "octubre"; "lng_month11" = "noviembre"; "lng_month12" = "diciembre"; "lng_month1_small" = "ene"; "lng_month2_small" = "feb"; "lng_month3_small" = "mar"; "lng_month4_small" = "abr"; "lng_month5_small" = "may"; "lng_month6_small" = "jun"; "lng_month7_small" = "jul"; "lng_month8_small" = "ago"; "lng_month9_small" = "sep"; "lng_month10_small" = "oct"; "lng_month11_small" = "nov"; "lng_month12_small" = "dic"; "lng_weekday1" = "lun"; "lng_weekday2" = "mar"; "lng_weekday3" = "mié"; "lng_weekday4" = "jue"; "lng_weekday5" = "vie"; "lng_weekday6" = "sáb"; "lng_weekday7" = "dom"; "lng_weekday1_full" = "lunes"; "lng_weekday2_full" = "martes"; "lng_weekday3_full" = "miércoles"; "lng_weekday4_full" = "jueves"; "lng_weekday5_full" = "viernes"; "lng_weekday6_full" = "sábado"; "lng_weekday7_full" = "domingo"; "lng_month_day" = "{day} de {month}"; "lng_month_day_year" = "{day} de {month} de {year}"; "lng_month_year" = "{month} de {year}"; "lng_box_ok" = "OK"; "lng_cancel" = "Cancelar"; "lng_continue" = "Continuar"; "lng_close" = "Cerrar"; "lng_connecting" = "Conectando..."; "lng_connecting_to_proxy" = "Conectando al proxy..."; "lng_connecting_settings" = "Ajustes"; "lng_reconnecting#one" = "Reconectar en {count} s..."; "lng_reconnecting#other" = "Reconectar en {count} s..."; "lng_reconnecting_try_now" = "Intentar ahora"; "lng_status_service_notifications" = "servicio de notificaciones"; "lng_status_support" = "soporte"; "lng_status_bot" = "bot"; "lng_status_bot_reads_all" = "tiene acceso a los mensajes"; "lng_status_bot_not_reads_all" = "no tiene acceso a los mensajes"; "lng_status_offline" = "última vez hace mucho tiempo"; "lng_status_recently" = "última vez recientemente"; "lng_status_last_week" = "última vez hace unos días"; "lng_status_last_month" = "última vez hace unas semanas"; "lng_status_invisible" = "invisible"; "lng_status_lastseen_now" = "última vez hace un momento"; "lng_status_lastseen_minutes#one" = "última vez hace {count} minuto"; "lng_status_lastseen_minutes#other" = "última vez hace {count} minutos"; "lng_status_lastseen_hours#one" = "última vez hace {count} hora"; "lng_status_lastseen_hours#other" = "última vez hace {count} horas"; "lng_status_lastseen_today" = "última vez hoy a las {time}"; "lng_status_lastseen_yesterday" = "última vez ayer a las {time}"; "lng_status_lastseen_date" = "última vez el {date}"; "lng_status_lastseen_date_time" = "última vez el {date} a las {time}"; "lng_status_online" = "en línea"; "lng_status_connecting" = "conectando..."; "lng_chat_status_unaccessible" = "grupo inaccesible"; "lng_chat_status_no_members" = "sin miembros"; "lng_chat_status_members#one" = "{count} miembro"; "lng_chat_status_members#other" = "{count} miembros"; "lng_chat_status_online#one" = "{count} en línea"; "lng_chat_status_online#other" = "{count} en línea"; "lng_chat_status_members_online" = "{members_count}, {online_count}"; "lng_channel_status" = "canal"; "lng_group_status" = "grupo"; "lng_channel_members_link#one" = "{count} miembro"; "lng_channel_members_link#other" = "{count} miembros"; "lng_channel_admins_link#one" = "{count} administrador"; "lng_channel_admins_link#other" = "{count} administradores"; "lng_server_error" = "Error interno del servidor."; "lng_flood_error" = "Muchos intentos. Por favor, reinténtalo más tarde."; "lng_gif_error" = "Ocurrió un error al leer la animación GIF :("; "lng_edit_error" = "No puedes editar este mensaje"; "lng_join_channel_error" = "Lo sentimos, te has unido a demasiados canales y supergrupos. Por favor, sal de algunos antes de unirte."; "lng_error_phone_flood" = "Has eliminado y vuelto a crear tu cuenta muchas veces recientemente. Por favor, espera algunos días antes de inscribirte de nuevo."; "lng_error_start_minimized_passcoded" = "Has configurado un código de acceso, así que no puedes iniciar la app minimizada. La app te pedirá el código de acceso para que empiece a funcionar."; "lng_error_pinned_max#one" = "Lo sentimos, no puedes anclar más de {count} chat."; "lng_error_pinned_max#other" = "Lo sentimos, no puedes anclar más de {count} chats."; "lng_error_public_groups_denied" = "Lamentablemente, tienes prohibido participar en grupos públicos.\n{more_info}"; "lng_error_cant_edit_admin" = "No puedes editar los permisos de este administrador."; "lng_error_cant_add_member" = "No puedes añadir al bot a este grupo. Pide a algún administrador que lo haga."; "lng_error_cant_add_bot" = "Este bot no puede se añadido a grupos."; "lng_error_cant_add_admin_invite" = "No puedes añadir a este usuario como administrador porque no es un miembro de este grupo y no puedes invitarlo."; "lng_error_cant_add_admin_unban" = "No puedes añadir a este usuario como administrador porque está en la lista de suspendidos y no puedes quitar su suspensión."; "lng_error_cant_ban_admin" = "No puedes suspender a este usuario porque es un administrador del grupo y no puedes degradarlo."; "lng_sure_add_admin_invite" = "Este usuario no es miembro del grupo. ¿Quieres añadirlo y ascenderlo a administrador?"; "lng_sure_add_admin_unban" = "Este usuario está restringido o suspendido del grupo. ¿Quieres quitar su suspensión y ascenderlo a administrador?"; "lng_sure_ban_admin" = "Este usuario es administrador del grupo. ¿Quieres continuar y restringirlo?"; "lng_sure_ban_user_group" = "¿Suspender a {user} en el grupo?"; "lng_sure_enable_socks" = "¿Quieres activar este proxy?\n\nServidor: {server}\nPuerto: {port}\n\nPuedes cambiar tu servidor proxy más tarde en Ajustes (Tipo de conexión)."; "lng_sure_enable" = "Activar"; "lng_edit_deleted" = "Este mensaje fue eliminado"; "lng_edit_too_long" = "Tu texto es demasiado largo"; "lng_edit_message" = "Editar mensaje"; "lng_edit_message_text" = "Mensaje nuevo..."; "lng_deleted" = "Cuenta eliminada"; "lng_deleted_message" = "Mensaje eliminado"; "lng_pinned_message" = "Mensaje anclado"; "lng_pinned_unpin_sure" = "¿Quieres desanclar este mensaje?"; "lng_pinned_pin_sure" = "¿Quieres anclar este mensaje?"; "lng_pinned_pin" = "Anclar"; "lng_pinned_unpin" = "Desanclar"; "lng_pinned_notify" = "Notificar a todos los miembros"; "lng_intro_about" = "Te damos la bienvenida a la app oficial para PC.\nEs rápida y segura."; "lng_start_msgs" = "EMPEZAR A CONVERSAR"; "lng_intro_next" = "SIGUIENTE"; "lng_intro_finish" = "REGISTRARSE"; "lng_intro_submit" = "ENVIAR"; "lng_photo_caption" = "Comentario"; "lng_photos_comment" = "Comentario"; "lng_phone_ph" = "Tu número de teléfono"; "lng_phone_title" = "Tu teléfono"; "lng_phone_desc" = "Por favor, confirma el código de tu país\ny tu número de teléfono."; "lng_phone_notreg" = "Si no tienes una cuenta de Telegram todavía, \npor favor, [b]regístrate[/b] con {link_start}Android / iPhone{link_end}\no {signup_start}aquí{signup_end}"; "lng_country_code" = "Código de país"; "lng_bad_country_code" = "Código de país inválido"; "lng_country_ph" = "Buscar"; "lng_country_done" = "Hecho"; "lng_country_none" = "País no encontrado"; "lng_country_select" = "Seleccionar país"; "lng_code_ph" = "Tu código"; "lng_code_desc" = "Hemos enviado un mensaje con un código de \nactivación a tu teléfono. Por favor, ponlo abajo."; "lng_code_telegram" = "Por favor, pon el código que acabas de recibir \nen tu otra aplicación de [b]Telegram[/b]."; "lng_code_no_telegram" = "Enviar el código vía SMS"; "lng_code_call" = "Telegram marcará tu número en {minutes}:{seconds}"; "lng_code_calling" = "Solicitando una llamada de Telegram..."; "lng_code_called" = "Telegram marcó tu número"; "lng_bad_phone" = "Número de teléfono inválido. Por favor, reinténtalo."; "lng_bad_phone_noreg" = "Número de teléfono no registrado."; "lng_bad_code" = "Has ingresado un código inválido."; "lng_bad_name" = "Por favor, pon tu nombre y apellidos."; "lng_bad_photo" = "La imagen seleccionada es errónea."; "lng_signin_title" = "Contraseña"; "lng_signin_desc" = "Por favor, pon tu contraseña."; "lng_signin_recover_desc" = "Por favor, pon el código desde el e-mail\n{email}"; "lng_signin_password" = "Tu contraseña"; "lng_signin_code" = "Código desde el e-mail"; "lng_signin_recover" = "¿Olvidaste la contraseña?"; "lng_signin_recover_title" = "Restablecer contraseña"; "lng_signin_hint" = "Pista: {password_hint}"; "lng_signin_recover_hint" = "El código fue enviado a {recover_email}"; "lng_signin_bad_password" = "Pusiste la contraseña equivocada."; "lng_signin_wrong_code" = "Has ingresado un código inválido."; "lng_signin_try_password" = "¿Tienes problemas para acceder a tu e-mail?"; "lng_signin_password_removed" = "Tu contraseña fue desactivada.\nPuedes configurar una nueva en Ajustes."; "lng_signin_no_email_forgot" = "Como no estableciste un e-mail de recuperación \ncuando configuraste tu contraseña, las opciones restantes son recordar tu contraseña o restablecer tu cuenta."; "lng_signin_cant_email_forgot" = "Si no puedes acceder a tu e-mail, las opciones restantes son recordar tu contraseña o restablecer tu cuenta."; "lng_signin_reset_account" = "Restablecer tu cuenta"; "lng_signin_sure_reset" = "¡Advertencia!\n\n¡Perderás todos tus chats y mensajes, junto con la multimedia y archivos compartidos!\n\n¿Quieres restablecer tu cuenta?"; "lng_signin_reset" = "Restablecer"; "lng_signin_reset_wait" = "Como la cuenta {phone_number} está activa y protegida con una contraseña, la eliminaremos en 1 semana, por motivos de seguridad. Puedes cancelar el proceso en cualquier momento.\n\nPodrás restablecer tu cuenta en:\n{when}"; "lng_signin_reset_days#one" = "{count} día"; "lng_signin_reset_days#other" = "{count} días"; "lng_signin_reset_hours#one" = "{count} hora"; "lng_signin_reset_hours#other" = "{count} horas"; "lng_signin_reset_minutes#one" = "{count} minuto"; "lng_signin_reset_minutes#other" = "{count} minutos"; "lng_signin_reset_in_days" = "{days_count} {hours_count} {minutes_count}"; "lng_signin_reset_in_hours" = "{hours_count} {minutes_count}"; "lng_signin_reset_cancelled" = "Tus intentos recientes para restablecer la cuenta fueron cancelados por su usuario activo. Por favor, reinténtalo en 7 días."; "lng_signup_title" = "Tu información"; "lng_signup_desc" = "Por favor, pon tu nombre \ny una foto."; "lng_signup_firstname" = "Nombre"; "lng_signup_lastname" = "Apellidos"; "lng_dlg_filter" = "Buscar"; "lng_dlg_new_group_name" = "Nombre del grupo"; "lng_dlg_new_channel_name" = "Nombre del canal"; "lng_no_contacts" = "No tienes contactos"; "lng_no_chats" = "Tus chats estarán aquí"; "lng_contacts_loading" = "Cargando..."; "lng_contacts_not_found" = "No se encontraron contactos"; "lng_dlg_search_for_messages" = "Buscar mensajes"; "lng_update_telegram" = "Actualizar Telegram"; "lng_dlg_search_in" = "Buscar mensajes en"; "lng_dlg_search_from" = "De: {user}"; "lng_settings_save" = "Guardar"; "lng_settings_upload" = "Poner foto de perfil"; "lng_settings_crop_profile" = "Selecciona un área para tu foto de perfil"; "lng_settings_uploading_photo" = "Subiendo foto..."; "lng_settings_edit" = "Editar"; "lng_settings_drop_area_subtitle" = "para ponerla como tu foto"; "lng_username_title" = "Alias"; "lng_username_about" = "Puedes elegir un alias en Telegram. \nSi lo haces, otras personas te podrán \nencontrar por ese alias y contactarte \nsin saber tu número de teléfono.\n\nPuedes usar a-z, 0-9 y guiones bajos.\nLa longitud mínima es de 5 caracteres."; "lng_username_choose" = "Elige tu alias."; "lng_username_invalid" = "Este alias es inválido."; "lng_username_occupied" = "Este alias ya está ocupado."; "lng_username_too_short" = "Este alias es muy corto."; "lng_username_bad_symbols" = "Este alias tiene símbolos equivocados."; "lng_username_available" = "Este alias está disponible."; "lng_username_not_found" = "No se encontró el usuario @{user}."; "lng_username_link_willbe" = "Este enlace abrirá un chat contigo:"; "lng_username_link" = "Este enlace abre un chat contigo:"; "lng_username_copied" = "Enlace copiado al portapapeles."; "lng_bio_title" = "Editar tu biografía"; "lng_bio_placeholder" = "Biografía"; "lng_bio_about" = "Puedes añadir algunas palabras sobre ti. Cualquiera que abra tu perfil verá este texto."; "lng_settings_section_info" = "Información"; "lng_settings_phone_number" = "Número de teléfono:"; "lng_settings_username" = "Alias:"; "lng_settings_choose_username" = "Elige un alias"; "lng_settings_empty_bio" = "Ninguna"; "lng_settings_section_notify" = "Notificaciones"; "lng_settings_desktop_notify" = "Notificaciones de escritorio"; "lng_settings_show_name" = "Mostrar el nombre del remitente"; "lng_settings_show_preview" = "Mostrar la vista previa del mensaje"; "lng_settings_use_windows" = "Usar notificaciones de Windows"; "lng_settings_use_native_notifications" = "Usar notificaciones nativas"; "lng_settings_advanced_notifications" = "Posición de las notificaciones y conteo"; "lng_settings_notifications_position" = "Ubicación en la pantalla"; "lng_settings_notifications_count" = "Conteo de notificaciones"; "lng_settings_sound_notify" = "Reproducir sonido"; "lng_settings_include_muted" = "Contar chats silenciados en los “no leídos”"; "lng_notification_preview" = "Tienes un mensaje nuevo"; "lng_notification_reply" = "Responder"; "lng_notification_hide_all" = "Ocultar todas"; "lng_notification_sample" = "Esta es una notificación de ejemplo"; "lng_settings_section_general" = "General"; "lng_settings_change_lang" = "Cambiar idioma"; "lng_languages" = "Idiomas"; "lng_sure_save_language" = "Telegram se reiniciará para cambiar el idioma"; "lng_settings_update_automatically" = "Actualizar automáticamente"; "lng_settings_current_version_label" = "Versión {version}:"; "lng_settings_current_version" = "Versión {version}"; "lng_settings_check_now" = "Buscar actualizaciones"; "lng_settings_update_checking" = "Buscando actualizaciones..."; "lng_settings_latest_installed" = "La última versión está instalada"; "lng_settings_downloading" = "Descargando la actualización {ready} / {total} MB..."; "lng_settings_update_ready" = "La nueva versión está lista"; "lng_settings_update_now" = "Reiniciar ahora"; "lng_settings_update_fail" = "Revisión de actualización fallida :("; "lng_settings_workmode_tray" = "Mostrar el icono"; "lng_settings_workmode_window" = "Mostrar icono de la barra de tareas"; "lng_settings_auto_start" = "Abrir Telegram cuando el sistema inicie"; "lng_settings_start_min" = "Abrir minimizada"; "lng_settings_add_sendto" = "Poner Telegram en el menú «Enviar a»"; "lng_settings_section_scale" = "Escala de la interfaz"; "lng_settings_scale_auto" = "Automático ({cur})"; "lng_settings_section_chat_settings" = "Ajustes de chat"; "lng_settings_replace_emojis" = "Reemplazar emojis"; "lng_settings_view_emojis" = "Ver lista"; "lng_settings_emoji_list" = "Emojis soportados"; "lng_settings_send_enter" = "Enviar con Intro"; "lng_settings_send_ctrlenter" = "Enviar con Ctrl+Intro"; "lng_settings_send_cmdenter" = "Enviar con Cmd+Intro"; "lng_settings_section_background" = "Fondo de chat"; "lng_settings_bg_use_default" = "Usar el tema por defecto"; "lng_settings_bg_from_gallery" = "Elegir desde la galería"; "lng_settings_bg_from_file" = "Elegir desde archivos"; "lng_settings_bg_edit_theme" = "Editar tema"; "lng_settings_bg_tile" = "Fondo con mosaico"; "lng_settings_adaptive_wide" = "Diseño adaptable para pantallas anchas"; "lng_backgrounds_header" = "Elige tu nuevo fondo de chat"; "lng_theme_sure_keep" = "¿Mantener este tema?"; "lng_theme_reverting#one" = "Regresar al tema anterior en {count} segundo."; "lng_theme_reverting#other" = "Regresar al tema anterior en {count} segundos."; "lng_theme_keep_changes" = "Mantener cambios"; "lng_theme_revert" = "Revertir"; "lng_download_path_dont_ask" = "No preguntar dónde guardar archivos"; "lng_download_path_label" = "Ruta de descarga:"; "lng_download_path_temp" = "carpeta temporal"; "lng_download_path_default" = "carpeta por defecto"; "lng_download_path_clear" = "Eliminar todo"; "lng_download_path_header" = "Elegir ruta de la descarga"; "lng_download_path_default_radio" = "Carpeta de Telegram en «Descargas»"; "lng_download_path_temp_radio" = "Carpeta temporal, borrada al cerrar sesión"; "lng_download_path_dir_radio" = "Carpeta personalizada, borrado manual"; "lng_download_path_choose" = "Elegir ruta de la descarga"; "lng_sure_clear_downloads" = "¿Quieres eliminar todos los archivos descargados desde la carpeta temporal? Es hecho automáticamente al cerrar sesión o desinstalar el programa."; "lng_download_path_failed" = "La descarga del archivo no se pudo iniciar. Podría provocarse por una ubicación errónea de la descarga.\n\nPuedes cambiar la ruta de la descarga en Ajustes."; "lng_download_path_settings" = "Ajustes"; "lng_download_finish_failed" = "La descarga del archivo no finalizó.\n\n¿Quieres reintentarlo?"; "lng_download_path_clearing" = "Borrando..."; "lng_download_path_cleared" = "¡Borrado!"; "lng_download_path_clear_failed" = "Borrado fallido :("; "lng_settings_section_privacy" = "Privacidad y seguridad"; "lng_local_storage_title" = "Almacenamiento local"; "lng_settings_no_data_cached" = "¡No se encontraron datos en la caché!"; "lng_settings_images_cached#one" = "{count} imagen, {size}"; "lng_settings_images_cached#other" = "{count} imágenes, {size}"; "lng_settings_audios_cached#one" = "{count} mensaje de voz, {size}"; "lng_settings_audios_cached#other" = "{count} mensajes de voz, {size}"; "lng_local_storage_clear" = "Eliminar todo"; "lng_local_storage_clearing" = "Borrando..."; "lng_local_storage_cleared" = "¡Borrado!"; "lng_local_storage_clear_failed" = "Borrado fallido :("; "lng_settings_section_advanced_settings" = "Ajustes avanzados"; "lng_settings_enable_night_theme" = "Activar modo noche"; "lng_settings_disable_night_theme" = "Desactivar modo noche"; "lng_passcode_remove_button" = "Quitar"; "lng_passcode_turn_on" = "Activar código de acceso"; "lng_passcode_change" = "Cambiar código de acceso"; "lng_passcode_create" = "Código de acceso"; "lng_passcode_remove" = "Quitar código de acceso"; "lng_passcode_turn_off" = "Desactivar"; "lng_passcode_autolock" = "Autobloqueo"; "lng_passcode_autolock_away" = "Autobloqueo si estoy fuera:"; "lng_passcode_autolock_inactive" = "Autobloqueo si estoy inactivo:"; "lng_passcode_autolock_minutes#one" = "{count} minuto"; "lng_passcode_autolock_minutes#other" = "{count} minutos"; "lng_passcode_autolock_hours#one" = "{count} hora"; "lng_passcode_autolock_hours#other" = "{count} horas"; "lng_passcode_enter_old" = "Pon el código de acceso actual"; "lng_passcode_enter_first" = "Pon un código de acceso"; "lng_passcode_enter_new" = "Pon tu nuevo código"; "lng_passcode_confirm_new" = "Repite el nuevo código"; "lng_passcode_about" = "Una vez que establezcas un código de acceso, un candado aparecerá en la lista de chats. Pulsa sobre él para bloquear la aplicación.\n\nImportante: si olvidas el código de acceso, tendrás que reiniciar la sesión."; "lng_passcode_differ" = "Los códigos de acceso son diferentes"; "lng_passcode_wrong" = "Código de acceso equivocado"; "lng_passcode_is_same" = "El código no fue cambiado"; "lng_passcode_enter" = "Pon tu código de acceso"; "lng_passcode_ph" = "Tu código de acceso"; "lng_passcode_submit" = "Enviar"; "lng_passcode_logout" = "Cerrar sesión"; "lng_passcode_need_unblock" = "Necesitas desbloquearme primero."; "lng_cloud_password_waiting" = "Enlace de confirmación enviado a {email}..."; "lng_cloud_password_change" = "Cambiar contraseña"; "lng_cloud_password_create" = "Contraseña"; "lng_cloud_password_remove" = "Quitar contraseña"; "lng_cloud_password_set" = "Activar verificación en dos pasos"; "lng_cloud_password_edit" = "Cambiar contraseña"; "lng_cloud_password_enter_old" = "Pon la contraseña actual"; "lng_cloud_password_enter_first" = "Pon una contraseña"; "lng_cloud_password_enter_new" = "Pon la nueva contraseña"; "lng_cloud_password_confirm_new" = "Repite la nueva contraseña"; "lng_cloud_password_hint" = "Pon una pista para la contraseña"; "lng_cloud_password_change_hint" = "Pon una pista para la contraseña"; "lng_cloud_password_bad" = "La contraseña y la pista no pueden ser iguales."; "lng_cloud_password_email" = "Pon un e-mail de recuperación"; "lng_cloud_password_bad_email" = "E-mail incorrecto. Por favor, prueba otro."; "lng_cloud_password_about" = "Necesitarás la contraseña al iniciar sesión, además del código de activación."; "lng_cloud_password_about_recover" = "¡Advertencia! ¿No quieres añadir un e-mail \nde recuperación para la contraseña?\n\nSi olvidas tu contraseña, perderás\nel acceso a tu cuenta de Telegram."; "lng_cloud_password_skip_email" = "Omitir e-mail"; "lng_cloud_password_almost" = "Un enlace de confirmación fue enviado \nal e-mail que estableciste. La verificación en dos pasos se activará en cuanto sigas ese enlace."; "lng_cloud_password_was_set" = "Verificación en dos pasos activada."; "lng_cloud_password_updated" = "Tu contraseña fue actualizada."; "lng_cloud_password_removed" = "Verificación en dos pasos desactivada."; "lng_cloud_password_differ" = "Las contraseñas no coinciden"; "lng_cloud_password_wrong" = "Contraseña equivocada"; "lng_cloud_password_is_same" = "Contraseña no cambiada"; "lng_connection_type" = "Tipo de conexión:"; "lng_connection_auto_connecting" = "Por defecto (conectando...)"; "lng_connection_auto" = "Por defecto ({transport} en uso)"; "lng_connection_proxy_connecting" = "Conectando a través de proxy..."; "lng_connection_proxy" = "{transport} con un proxy"; "lng_connection_header" = "Tipo de conexión"; "lng_connection_auto_rb" = "Automático (TCP si existe o HTTP)"; "lng_connection_http_proxy_rb" = "HTTP con http-proxy personalizado"; "lng_connection_tcp_proxy_rb" = "TCP con socks5-proxy personalizado"; "lng_connection_try_ipv6" = "Intentar conectar a través de IPv6"; "lng_connection_host_ph" = "Nombre del host"; "lng_connection_port_ph" = "Puerto"; "lng_connection_user_ph" = "Alias"; "lng_connection_password_ph" = "Contraseña"; "lng_connection_save" = "Guardar"; "lng_settings_blocked_users" = "Usuarios bloqueados"; "lng_settings_last_seen_privacy" = "Última conexión"; "lng_settings_calls_privacy" = "Privacidad de llamadas"; "lng_settings_groups_invite_privacy" = "Invitación a grupos"; "lng_settings_show_sessions" = "Mostrar todas las sesiones"; "lng_settings_self_destruct" = "Autodestrucción de la cuenta"; "lng_settings_change_phone" = "Cambiar número de teléfono"; "lng_settings_reset" = "Cerrar todas las otras sesiones"; "lng_settings_reset_sure" = "¿Quieres cerrar todas \nlas otras sesiones?"; "lng_settings_reset_one_sure" = "¿Quieres cerrar esta sesión?"; "lng_settings_reset_button" = "Cerrar"; "lng_settings_reset_done" = "Se cerraron las otras sesiones"; "lng_settings_manage_local_storage" = "Administrar almacenamiento local"; "lng_settings_ask_question" = "Hacer una pregunta"; "lng_settings_ask_sure" = "Por favor, considera que el soporte de Telegram está hecho por voluntarios. Respondemos lo antes posible, pero puede tomar tiempo.\n\nPuedes mirar las preguntas frecuentes de Telegram: tienen soluciones a problemas y respuestas para muchas preguntas."; "lng_settings_faq_button" = "Ir a preguntas"; "lng_settings_ask_ok" = "Ir al soporte"; "lng_settings_faq" = "Preguntas frecuentes"; "lng_settings_logout" = "Cerrar sesión"; "lng_sure_logout" = "¿Quieres cerrar sesión?"; "lng_settings_need_restart" = "Necesitas reiniciar para aplicar algunos de los nuevos ajustes. ¿Reiniciar ahora?"; "lng_settings_restart_now" = "REINICIAR"; "lng_settings_restart_later" = "MÁS TARDE"; "lng_sessions_header" = "Sesión actual"; "lng_sessions_other_header" = "Sesiones activas"; "lng_sessions_no_other" = "Sin otras sesiones"; "lng_sessions_other_desc" = "Puedes iniciar sesión en Telegram desde otro móvil, tablet o computador, usando el mismo número de teléfono. Todos tus datos se sincronizarán al instante."; "lng_sessions_terminate_all" = "Cerrar todas las otras sesiones"; "lng_blocked_list_title" = "Usuarios bloqueados"; "lng_blocked_list_unknown_phone" = "número desconocido"; "lng_blocked_list_unblock" = "Desbloquear"; "lng_blocked_list_add" = "Bloquear"; "lng_blocked_list_add_title" = "Elige un usuario para bloquear"; "lng_blocked_list_already_blocked" = "ya está bloqueado"; "lng_blocked_list_about" = "Los usuarios bloqueados no pueden enviarte mensajes o añadirte a grupos. No verán tus fotos de perfil ni estados de conexión."; "lng_blocked_list_not_found" = "No se encontraron usuarios."; "lng_edit_privacy_everyone" = "Todos"; "lng_edit_privacy_contacts" = "Mis contactos"; "lng_edit_privacy_nobody" = "Nadie"; "lng_edit_privacy_exceptions" = "Añadir excepciones"; "lng_edit_privacy_lastseen_title" = "Última conexión"; "lng_edit_privacy_lastseen_description" = "Puedes elegir quién ve tu última conexión:"; "lng_edit_privacy_lastseen_warning" = "Importante: No verás la última conexión de las personas con las que no compartes la tuya. En su lugar, se mostrarán conexiones indeterminadas (recientemente, hace unos días, hace unas semanas)."; "lng_edit_privacy_lastseen_always_empty" = "Compartir con"; "lng_edit_privacy_lastseen_always#one" = "Compartir con {count} usuario"; "lng_edit_privacy_lastseen_always#other" = "Compartir con {count} usuarios"; "lng_edit_privacy_lastseen_never_empty" = "No compartir con"; "lng_edit_privacy_lastseen_never#one" = "No compartir con {count} usuario"; "lng_edit_privacy_lastseen_never#other" = "No compartir con {count} usuarios"; "lng_edit_privacy_lastseen_exceptions" = "Estos ajustes anularán los valores de arriba."; "lng_edit_privacy_lastseen_always_title" = "Compartir con"; "lng_edit_privacy_lastseen_never_title" = "No compartir con"; "lng_edit_privacy_groups_title" = "Invitación a grupos"; "lng_edit_privacy_groups_description" = "Puedes restringir quién puede añadirte a grupos o canales con gran precisión:"; "lng_edit_privacy_groups_always_empty" = "Permitir"; "lng_edit_privacy_groups_always#one" = "Permitir {count} usuario"; "lng_edit_privacy_groups_always#other" = "Permitir {count} usuarios"; "lng_edit_privacy_groups_never_empty" = "No permitir"; "lng_edit_privacy_groups_never#one" = "No permitir {count} usuario"; "lng_edit_privacy_groups_never#other" = "No permitir {count} usuarios"; "lng_edit_privacy_groups_exceptions" = "Estos usuarios podrán o no añadirte a grupos y canales, independiente de los ajustes de arriba."; "lng_edit_privacy_groups_always_title" = "Permitir"; "lng_edit_privacy_groups_never_title" = "No permitir"; "lng_edit_privacy_calls_title" = "Privacidad de llamadas"; "lng_edit_privacy_calls_description" = "Puedes restringir quién puede llamarte:"; "lng_edit_privacy_calls_always_empty" = "Permitir"; "lng_edit_privacy_calls_always#one" = "Permitir {count} usuario"; "lng_edit_privacy_calls_always#other" = "Permitir {count} usuarios"; "lng_edit_privacy_calls_never_empty" = "No permitir"; "lng_edit_privacy_calls_never#one" = "No permitir {count} usuario"; "lng_edit_privacy_calls_never#other" = "No permitir {count} usuarios"; "lng_edit_privacy_calls_exceptions" = "Estos usuarios podrán o no llamarte, independiente de los ajustes de arriba."; "lng_edit_privacy_calls_always_title" = "Permitir"; "lng_edit_privacy_calls_never_title" = "No permitir"; "lng_self_destruct_title" = "Autodestrucción de la cuenta"; "lng_self_destruct_description" = "Si no estás en línea durante este tiempo, al menos una vez, tu cuenta se eliminará con todos tus grupos, mensajes y contactos."; "lng_self_destruct_months#one" = "{count} mes"; "lng_self_destruct_months#other" = "{count} meses"; "lng_self_destruct_years#one" = "{count} año"; "lng_self_destruct_years#other" = "{count} años"; "lng_change_phone_title" = "Cambiar número de teléfono"; "lng_change_phone_description" = "Puedes cambiar tu número de Telegram aquí. \nTu cuenta y todos tus datos de la nube, mensajes,\narchivos, grupos, contactos, etc., se moverán al \nnuevo número.\n\n[b]Importante[/b]: Todos tus contactos de Telegram\ntendrán tu [b]nuevo número[/b] añadido a sus \nagendas de contactos, siempre que hayan tenido \ntu número viejo y no los hayas bloqueado."; "lng_change_phone_warning" = "Todos tus contactos de Telegram tendrán tu número nuevo añadido a sus agendas de contactos, siempre que hayan tenido tu número viejo y no los hayas bloqueado en Telegram."; "lng_change_phone_occupied" = "El número {phone} ya está vinculado a una cuenta de Telegram. Por favor, elimina esa cuenta antes de migrar al nuevo número."; "lng_change_phone_button" = "Cambiar número"; "lng_change_phone_new_title" = "Pon tu número nuevo"; "lng_change_phone_new_description" = "Enviaremos un SMS con el código de confirmación a tu nuevo número."; "lng_change_phone_new_submit" = "Enviar"; "lng_change_phone_code_title" = "Pon el código"; "lng_change_phone_code_description" = "Enviamos un SMS con el código de activación a tu teléfono {phone}."; "lng_change_phone_success" = "Tu número de teléfono ha sido cambiado."; "lng_mute_duration_hours#one" = "Por {count} hora"; "lng_mute_duration_hours#other" = "Por {count} horas"; "lng_mute_box_tip" = "Elige cuánto tiempo quieres silenciar las notificaciones de este chat"; "lng_preview_loading" = "Obteniendo enlace..."; "lng_profile_chat_unaccessible" = "El grupo es inaccesible"; "lng_profile_about_section" = "Acerca de"; "lng_profile_description_section" = "Descripción"; "lng_profile_settings_section" = "Ajustes"; "lng_profile_actions_section" = "Acciones"; "lng_profile_bot_settings" = "Ajustes"; "lng_profile_bot_help" = "Ayuda"; "lng_profile_invite_link_section" = "Enlace de invitación"; "lng_profile_create_public_link" = "Crear enlace público"; "lng_profile_edit_public_link" = "Editar enlace público"; "lng_profile_search_members" = "Buscar miembros"; "lng_profile_manage_admins" = "Gestionar administradores"; "lng_profile_manage_blocklist" = "Administrar usuarios suspendidos"; "lng_profile_manage_restrictedlist" = "Administrar usuarios restringidos"; "lng_profile_recent_actions" = "Acciones recientes"; "lng_profile_common_groups#one" = "{count} grupo en común"; "lng_profile_common_groups#other" = "{count} grupos en común"; "lng_profile_common_groups_section" = "Grupos en común"; "lng_profile_participants_section" = "Miembros"; "lng_profile_info_section" = "Información"; "lng_profile_mobile_number" = "Número:"; "lng_profile_username" = "Alias:"; "lng_profile_link" = "Enlace:"; "lng_profile_bio" = "Biografía:"; "lng_profile_add_contact" = "Añadir contacto"; "lng_profile_edit_contact" = "Editar"; "lng_profile_enable_notifications" = "Notificaciones"; "lng_profile_clear_history" = "Eliminar historial"; "lng_profile_delete_conversation" = "Eliminar chat"; "lng_profile_clear_and_exit" = "Eliminar y salir"; "lng_profile_leave_channel" = "Salir del canal"; "lng_profile_delete_channel" = "Eliminar el canal"; "lng_profile_leave_group" = "Salir del grupo"; "lng_profile_delete_group" = "Eliminar grupo"; "lng_profile_report" = "Reportar"; "lng_profile_search_messages" = "Buscar mensajes"; "lng_profile_block_user" = "Bloquear"; "lng_profile_unblock_user" = "Desbloquear"; "lng_profile_block_bot" = "Detener y bloquear el bot"; "lng_profile_unblock_bot" = "Desbloquear el bot"; "lng_profile_send_message" = "Enviar mensaje"; "lng_profile_share_contact" = "Compartir contacto"; "lng_profile_invite_to_group" = "Añadir al grupo"; "lng_profile_delete_contact" = "Eliminar"; "lng_profile_set_group_photo" = "Poner foto"; "lng_profile_add_participant" = "Añadir miembros"; "lng_profile_view_channel" = "Ver canal"; "lng_profile_join_channel" = "Unirme"; "lng_profile_delete_and_exit" = "Salir"; "lng_profile_kick" = "Eliminar"; "lng_profile_admin" = "administrador"; "lng_profile_edit_permissions" = "Editar"; "lng_profile_sure_kick" = "¿Eliminar a {user} del grupo?"; "lng_profile_sure_kick_channel" = "¿Eliminar a {user} del canal?"; "lng_profile_sure_kick_admin" = "¿Eliminar a {user} de los administradores?"; "lng_profile_loading" = "Cargando..."; "lng_profile_shared_media" = "Multimedia"; "lng_profile_no_media" = "No hay multimedia en este chat."; "lng_profile_photos#one" = "{count} foto"; "lng_profile_photos#other" = "{count} fotos"; "lng_profile_photos_header" = "Fotos"; "lng_profile_videos#one" = "{count} vídeo"; "lng_profile_videos#other" = "{count} vídeos"; "lng_profile_videos_header" = "Vídeos"; "lng_profile_songs#one" = "{count} audio"; "lng_profile_songs#other" = "{count} audios"; "lng_profile_songs_header" = "Audios"; "lng_profile_files#one" = "{count} archivo"; "lng_profile_files#other" = "{count} archivos"; "lng_profile_files_header" = "Archivos"; "lng_profile_audios#one" = "{count} mensaje de voz"; "lng_profile_audios#other" = "{count} mensajes de voz"; "lng_profile_audios_header" = "Mensajes de voz"; "lng_profile_shared_links#one" = "{count} enlace"; "lng_profile_shared_links#other" = "{count} enlaces"; "lng_profile_shared_links_header" = "Enlaces"; "lng_profile_copy_phone" = "Copiar número de teléfono"; "lng_profile_copy_fullname" = "Copiar nombre"; "lng_profile_drop_area_title" = "Arrastra tu imagen aquí"; "lng_profile_drop_area_subtitle" = "para ponerla como foto del grupo"; "lng_profile_drop_area_subtitle_channel" = "para ponerla como foto del canal"; "lng_profile_top_bar_share_contact" = "Compartir"; "lng_report_title" = "Reportar canal"; "lng_report_group_title" = "Reportar grupo"; "lng_report_bot_title" = "Reportar bot"; "lng_report_reason_spam" = "Spam"; "lng_report_reason_violence" = "Violencia"; "lng_report_reason_pornography" = "Pornografía"; "lng_report_reason_other" = "Otro"; "lng_report_reason_description" = "Descripción"; "lng_report_button" = "Reportar"; "lng_report_thanks" = "¡Gracias! Tu reporte será revisado por nuestro equipo muy pronto."; "lng_channel_add_members" = "Añadir miembros"; "lng_channel_add_banned" = "Suspender usuario"; "lng_channel_add_restricted" = "Restringir usuario"; "lng_channel_members" = "Miembros"; "lng_channel_only_last_shown#one" = "Sólo aparece el último miembro"; "lng_channel_only_last_shown#other" = "Sólo aparecen los últimos {count} miembros"; "lng_channel_admins" = "Administradores"; "lng_channel_add_admin" = "Añadir administrador"; "lng_channel_admins_too_much" = "Lo sentimos, alcanzaste el límite de administradores. Por favor, elimina un administrador primero."; "lng_channel_admin_status_creator" = "Creador"; "lng_channel_admin_status_promoted_by" = "Ascendido por {user}"; "lng_channel_admin_status_not_admin" = "No es administrador"; "lng_channel_banned_status_restricted_by" = "Restringido por {user}"; "lng_group_blocked_list_about" = "Los usuarios suspendidos son eliminados del grupo y sólo pueden volver si son invitados por un administrador. \nLos enlaces de invitación no funcionan para ellos."; "lng_chat_all_members_admins" = "Todos son administradores"; "lng_chat_about_all_admins" = "Todos pueden añadir nuevos miembros, editar el nombre y la foto del grupo."; "lng_chat_about_admins" = "Sólo los administradores pueden añadir y eliminar miembros, editar el nombre y la foto del grupo."; "lng_participant_filter" = "Buscar"; "lng_participant_invite" = "Invitar"; "lng_participant_invite_sorry#one" = "Lo sentimos, sólo puedes añadir a {count} miembro a un canal. \n\nAhora será necesario un enlace de invitación para unirse."; "lng_participant_invite_sorry#other" = "Lo sentimos, sólo puedes añadir a los primeros {count} miembros a un canal. \n\nAhora será necesario un enlace de invitación para unirse."; "lng_create_group_back" = "Atrás"; "lng_create_group_next" = "Siguiente"; "lng_create_group_create" = "Crear"; "lng_create_group_title" = "Nuevo grupo"; "lng_create_channel_title" = "Nuevo canal"; "lng_create_public_channel_title" = "Canal público"; "lng_create_public_channel_about" = "Cualquiera puede encontrar el canal en la búsqueda y unirse"; "lng_create_private_channel_title" = "Canal privado"; "lng_create_private_channel_about" = "Sólo las personas con el enlace de invitación especial podrán unirse"; "lng_create_public_group_title" = "Grupo público"; "lng_create_public_group_about" = "Cualquiera puede encontrar el grupo en la búsqueda y unirse. El historial del chat está disponible para todos"; "lng_create_private_group_title" = "Grupo privado"; "lng_create_private_group_about" = "Las personas pueden unirse sólo si son invitadas o tienen un enlace de invitación"; "lng_create_group_skip" = "Omitir"; "lng_create_channel_link_invalid" = "Este enlace es inválido"; "lng_create_channel_link_occupied" = "Este enlace ya está ocupado"; "lng_create_channel_link_too_short" = "Este enlace es muy corto"; "lng_create_channel_link_bad_symbols" = "Este enlace tiene símbolos equivocados"; "lng_create_channel_link_available" = "Este enlace está disponible"; "lng_create_channel_link_copied" = "Enlace copiado al portapapeles"; "lng_create_group_crop" = "Selecciona un área para la foto del grupo"; "lng_create_channel_crop" = "Selecciona un área para la foto del canal"; "lng_failed_add_participant" = "No se pudo añadir al usuario. Por favor, reinténtalo más tarde."; "lng_failed_add_not_mutual" = "Lo sentimos, si una persona sale del grupo, sólo un contacto mutuo puede volver a invitarlo (necesitan tener tu número y tú el suyo)."; "lng_failed_add_not_mutual_channel" = "Lo sentimos, si una persona sale del canal, sólo un contacto mutuo puede volver a invitarlo (necesitan tener tu número y tú el suyo)."; "lng_sure_delete_contact" = "¿Quieres eliminar a {contact} de tu lista de contactos?"; "lng_sure_delete_history" = "¿Quieres eliminar todo el historial de mensajes con {contact}?\n\nEsta acción no se puede deshacer."; "lng_sure_delete_group_history" = "¿Quieres eliminar todo el historial en «{group}»?\n\nEsta acción no se puede deshacer."; "lng_sure_delete_and_exit" = "¿Quieres eliminar todo el historial de mensajes y salir del grupo «{group}»?\n\nEsta acción no se puede deshacer."; "lng_sure_leave_channel" = "¿Quieres salir de este canal?"; "lng_sure_delete_channel" = "¿Quieres eliminar este canal? Todos los miembros y mensajes se perderán."; "lng_sure_leave_group" = "¿Quieres salir del grupo?\nNo puedes deshacer esta acción."; "lng_sure_delete_group" = "¿Quieres eliminar este grupo? Todos los miembros y mensajes se perderán."; "lng_message_empty" = "Mensaje vacío"; "lng_message_unsupported" = "Este mensaje no es soportado por tu versión de Telegram Desktop. Por favor, actualiza a la última versión desde Ajustes o instálala desde {link}"; "lng_duration_seconds#one" = "{count} segundo"; "lng_duration_seconds#other" = "{count} segundos"; "lng_duration_minsec_minutes#one" = "{count} min"; "lng_duration_minsec_minutes#other" = "{count} min"; "lng_duration_minsec_seconds#one" = "{count} s"; "lng_duration_minsec_seconds#other" = "{count} s"; "lng_duration_minutes_seconds" = "{minutes_count} {seconds_count}"; "lng_action_add_user" = "{from} añadió a {user}"; "lng_action_add_users_many" = "{from} añadió a {users}"; "lng_action_add_users_and_one" = "{accumulated}, {user}"; "lng_action_add_users_and_last" = "{accumulated} y {user}"; "lng_action_add_you" = "{from} te añadió a este canal"; "lng_action_you_joined" = "Te uniste a este canal"; "lng_action_add_you_group" = "{from} te añadió al grupo"; "lng_action_you_joined_group" = "Te uniste a este grupo"; "lng_action_kick_user" = "{from} eliminó a {user}"; "lng_action_user_left" = "{from} dejó el grupo"; "lng_action_user_joined" = "{from} se unió al grupo"; "lng_action_user_joined_by_link" = "{from} se unió al grupo con un enlace de invitación"; "lng_action_user_registered" = "{from} se acaba de unir a Telegram"; "lng_action_removed_photo" = "{from} eliminó la foto del grupo"; "lng_action_removed_photo_channel" = "Foto del canal eliminada"; "lng_action_changed_photo" = "{from} actualizó la foto del grupo"; "lng_action_changed_photo_channel" = "Foto del canal actualizada"; "lng_action_changed_title" = "{from} cambió el nombre del grupo a «{title}»"; "lng_action_changed_title_channel" = "El nombre del canal fue cambiado a «{title}»"; "lng_action_created_chat" = "{from} creó el grupo «{title}»"; "lng_action_created_channel" = "Canal creado"; "lng_action_group_migrate" = "Este grupo fue convertido en un supergrupo"; "lng_action_pinned_message" = "{from} ancló «{text}»"; "lng_action_pinned_media" = "{from} ancló {media}"; "lng_action_pinned_media_photo" = "una foto"; "lng_action_pinned_media_video" = "un vídeo"; "lng_action_pinned_media_audio" = "un audio"; "lng_action_pinned_media_voice" = "un mensaje de voz"; "lng_action_pinned_media_video_message" = "un videomensaje"; "lng_action_pinned_media_file" = "un archivo"; "lng_action_pinned_media_gif" = "un GIF"; "lng_action_pinned_media_contact" = "un contacto"; "lng_action_pinned_media_location" = "una ubicación"; "lng_action_pinned_media_sticker" = "un sticker"; "lng_action_pinned_media_emoji_sticker" = "un {emoji} sticker"; "lng_action_pinned_media_game" = "el juego «{game}»"; "lng_action_game_score#one" = "{from} consiguió {count} punto en {game}"; "lng_action_game_score#other" = "{from} consiguió {count} puntos en {game}"; "lng_action_game_you_scored#one" = "Conseguiste {count} punto en {game}"; "lng_action_game_you_scored#other" = "Conseguiste {count} puntos en {game}"; "lng_action_game_score_no_game#one" = "{from} consiguió {count} punto"; "lng_action_game_score_no_game#other" = "{from} consiguió {count} puntos"; "lng_action_game_you_scored_no_game#one" = "Conseguiste {count} punto"; "lng_action_game_you_scored_no_game#other" = "Conseguiste {count} puntos"; "lng_action_payment_done" = "Has transferido satisfactoriamente {amount} a {user}"; "lng_action_payment_done_for" = "Has transferido satisfactoriamente {amount} a {name} por {invoice}"; "lng_action_took_screenshot" = "¡{from} hizo una captura de pantalla!"; "lng_action_you_took_screenshot" = "¡Hiciste una captura de pantalla!"; "lng_ttl_photo_received" = "{from} te envió una foto con autodestrucción. Por favor, mírala en tu teléfono."; "lng_ttl_photo_sent" = "Enviaste una foto con autodestrucción"; "lng_ttl_photo_expired" = "La foto expiró"; "lng_ttl_video_received" = "{from} te envió un vídeo con autodestrucción. Por favor, míralo en tu teléfono."; "lng_ttl_video_sent" = "Enviaste un vídeo con autodestrucción"; "lng_ttl_video_expired" = "El vídeo expiró"; "lng_profile_migrate_reached#one" = "Límite de {count} miembro alcanzado"; "lng_profile_migrate_reached#other" = "Límite de {count} miembros alcanzado"; "lng_profile_migrate_body" = "Para superar este límite, puedes convertir tu grupo en un supergrupo."; "lng_profile_migrate_learn_more" = "Saber más »"; "lng_profile_migrate_about" = "Para superar el límite y tener características adicionales, conviértelo en un supergrupo:"; "lng_profile_migrate_feature1#one" = "— Permiten hasta {count} usuario"; "lng_profile_migrate_feature1#other" = "— Permiten hasta {count} usuarios"; "lng_profile_migrate_feature2" = "— Nuevos miembros ven todo el historial"; "lng_profile_migrate_feature3" = "— Un admin. borra mensajes para todos"; "lng_profile_migrate_feature4" = "— Notificaciones silenciadas por defecto"; "lng_profile_migrate_button" = "Convertir en supergrupo"; "lng_profile_migrate_sure" = "¿Quieres convertir este grupo en un supergrupo? No puedes deshacer esta acción."; "lng_profile_convert_button" = "Convertir en supergrupo"; "lng_profile_convert_title" = "Convertir en supergrupo"; "lng_profile_convert_about" = "En los supergrupos:"; "lng_profile_convert_feature1" = "— Los nuevos miembros ven todo el historial"; "lng_profile_convert_feature2" = "— Los mensajes son eliminados para todos"; "lng_profile_convert_feature3" = "— Los administradores pueden anclar mensajes"; "lng_profile_convert_feature4" = "— El creador puede generar un enlace público"; "lng_profile_convert_warning" = "{bold_start}Importante:{bold_end} Esta acción no se puede deshacer"; "lng_profile_convert_confirm" = "Convertir"; "lng_profile_add_more_after_upgrade#one" = "Podrás añadir hasta {count} miembro una vez que conviertas tu grupo en un supergrupo."; "lng_profile_add_more_after_upgrade#other" = "Podrás añadir hasta {count} miembros una vez que conviertas tu grupo en un supergrupo."; "lng_channel_not_accessible" = "Lo sentimos, este canal no es accesible."; "lng_group_not_accessible" = "Lo sentimos, este grupo no es accesible."; "lng_channels_too_much_public_about" = "Lo sentimos, has reservado demasiados alias públicos. Puedes anular el enlace de uno de tus grupos o canales más viejos. "; "lng_channels_too_much_public_revoke_confirm_group" = "¿Quieres anular el enlace {link}?\n\nEl grupo «{group}» pasará a ser privado."; "lng_channels_too_much_public_revoke_confirm_channel" = "¿Quieres anular el enlace {link}?\n\nEl canal «{group}» pasará a ser privado."; "lng_channels_too_much_public_revoke" = "Anular"; "lng_group_invite_bad_link" = "El enlace de invitación está roto o ha expirado."; "lng_group_invite_want_join" = "¿Quieres unirte al grupo «{title}»?"; "lng_group_invite_want_join_channel" = "¿Quieres unirte al canal «{title}»?"; "lng_group_invite_join" = "Unirme"; "lng_group_invite_members#one" = "{count} miembro, entre ellos:"; "lng_group_invite_members#other" = "{count} miembros, entre ellos:"; "lng_group_invite_link" = "Enlace de invitación:"; "lng_group_invite_create" = "Crear un enlace de invitación"; "lng_group_invite_about" = "Los usuarios de Telegram podrán unirse\na tu grupo a través de este enlace."; "lng_group_invite_create_new" = "Anular enlace"; "lng_group_invite_about_new" = "El enlace previo será desactivado y generaremos uno nuevo para ti."; "lng_group_invite_copied" = "Enlace de invitación copiado al portapapeles."; "lng_group_invite_no_room" = "No puedes unirte a este grupo porque hay muchos miembros en él."; "lng_channel_public_link_copied" = "Enlace copiado al portapapeles."; "lng_forwarded" = "Reenviado desde {user}"; "lng_forwarded_date" = "Original: {date}"; "lng_forwarded_channel" = "Reenviado desde {channel}"; "lng_forwarded_via" = "Reenviado desde {user} vía {inline_bot}"; "lng_forwarded_channel_via" = "Reenviado desde {channel} vía {inline_bot}"; "lng_forwarded_signed" = "{channel} ({user})"; "lng_in_reply_to" = "Respondiendo a"; "lng_edited" = "editado"; "lng_edited_date" = "Editado: {date}"; "lng_cancel_edit_post_sure" = "¿Cancelar edición?"; "lng_cancel_edit_post_yes" = "Sí"; "lng_cancel_edit_post_no" = "No"; "lng_bot_share_location_unavailable" = "Lo sentimos, compartir la ubicación no está disponible actualmente en Telegram Desktop."; "lng_bot_inline_geo_unavailable" = "Lo sentimos, este bot requiere compartir la ubicación.\nNo está disponible en Telegram Desktop."; "lng_bot_share_phone" = "¿Compartir número de teléfono?"; "lng_bot_share_phone_confirm" = "Compartir"; "lng_attach_failed" = "Fallido"; "lng_attach_file" = "Archivo"; "lng_attach_photo" = "Foto"; "lng_media_type" = "Formato multimedia"; "lng_media_type_photos" = "Fotos"; "lng_media_type_videos" = "Vídeos"; "lng_media_type_songs" = "Audios"; "lng_media_type_files" = "Archivos"; "lng_media_type_audios" = "Mensajes de voz"; "lng_media_type_links" = "Enlaces"; "lng_media_open_with" = "Abrir con"; "lng_media_download" = "Descargar"; "lng_media_cancel" = "Cancelar"; "lng_media_video" = "Vídeo"; "lng_media_audio" = "Mensaje de voz"; "lng_media_auto_settings" = "Ajustes de autodescarga de multimedia"; "lng_media_auto_photo" = "Autodescarga de fotos"; "lng_media_auto_audio" = "Autodescarga de audio"; "lng_media_auto_gif" = "Autodescarga de GIF"; "lng_media_auto_private_chats" = "Chats"; "lng_media_auto_groups" = "Grupos y canales"; "lng_media_auto_play" = "Autorreproducir"; "lng_emoji_category0" = "Uso frecuente"; "lng_emoji_category1" = "Personas"; "lng_emoji_category2" = "Naturaleza"; "lng_emoji_category3" = "Comida y bebida"; "lng_emoji_category4" = "Actividad"; "lng_emoji_category5" = "Viajes y destinos"; "lng_emoji_category6" = "Objetos"; "lng_emoji_category7" = "Símbolos y banderas"; "lng_emoji_hide_panel" = "Haz clic aquí para esconder el panel de emojis"; "lng_recent_stickers" = "Uso frecuente"; "lng_faved_stickers" = "Stickers favoritos"; "lng_faved_stickers_add" = "Añadir a favoritos"; "lng_faved_stickers_remove" = "Eliminar de favoritos"; "lng_group_stickers" = "Stickers de grupo"; "lng_group_stickers_description" = "Puedes elegir un pack para que los miembros del grupo lo utilicen en él."; "lng_group_stickers_add" = "Elegir pack de stickers"; "lng_switch_stickers" = "Stickers"; "lng_switch_emoji" = "Emoji"; "lng_switch_gifs" = "GIF"; "lng_stickers_featured_add" = "Añadir"; "lng_gifs_search" = "Buscar GIF"; "lng_gifs_no_saved" = "Aún no tienes GIF guardados."; "lng_inline_bot_no_results" = "Sin resultados."; "lng_inline_bot_via" = "vía {inline_bot}"; "lng_box_remove" = "Eliminar"; "lng_stickers_installed_tab" = "Stickers"; "lng_stickers_featured_tab" = "Destacados"; "lng_stickers_archived_tab" = "Archivados"; "lng_stickers_remove_pack" = "¿Quitar «{sticker_pack}»?"; "lng_stickers_add_pack" = "Añadir stickers"; "lng_stickers_share_pack" = "Compartir stickers"; "lng_stickers_not_found" = "Pack de stickers no encontrado."; "lng_stickers_packs_archived" = "Algunos de los stickers que no usas fueron archivados. Así tendrás más espacio para los packs que activaste."; "lng_stickers_archived" = "Stickers archivados"; "lng_stickers_copied" = "Enlace del pack de stickers copiado al portapapeles."; "lng_stickers_default_set" = "Grandes personajes"; "lng_stickers_you_have" = "Administrar y ordenar los packs de stickers"; "lng_stickers_featured" = "Stickers destacados"; "lng_stickers_return" = "Deshacer"; "lng_stickers_count#one" = "{count} sticker"; "lng_stickers_count#other" = "{count} stickers"; "lng_stickers_masks_pack" = "Este es un pack de stickers de máscaras. Puedes usarlas en el editor de fotos de nuestras aplicaciones en móviles."; "lng_stickers_group_set" = "Pack de stickers de grupo"; "lng_stickers_remove_group_set" = "¿Eliminar el pack de stickers de grupo?"; "lng_stickers_group_from_your" = "Elige desde tus stickers"; "lng_in_dlg_photo" = "Foto"; "lng_in_dlg_video" = "Vídeo"; "lng_in_dlg_audio_file" = "Audio"; "lng_in_dlg_contact" = "Contacto"; "lng_in_dlg_audio" = "Mensaje de voz"; "lng_in_dlg_video_message" = "Videomensaje"; "lng_in_dlg_file" = "Archivo"; "lng_in_dlg_sticker" = "Sticker"; "lng_in_dlg_sticker_emoji" = "{emoji} Sticker"; "lng_ban_user" = "Suspender usuario"; "lng_delete_all_from" = "Eliminar todo lo de este usuario"; "lng_report_spam" = "Reportar spam"; "lng_report_spam_hide" = "Ocultar"; "lng_report_spam_thanks" = "¡Gracias por tu reporte!"; "lng_report_spam_sure" = "¿Quieres reportar el spam de este usuario?"; "lng_report_spam_sure_group" = "¿Quieres reportar el spam en este grupo?"; "lng_report_spam_sure_channel" = "¿Quieres reportar a este canal como spam?"; "lng_report_spam_ok" = "Reportar"; "lng_cant_send_to_not_contact" = "Lo sentimos, por ahora sólo puedes \nenviar mensajes a contactos mutuos. \n{more_info}"; "lng_cant_invite_not_contact" = "Lo sentimos, por ahora sólo puedes \nañadir contactos mutuos a grupos. \n{more_info}"; "lng_cant_send_to_not_contact_flood" = "Has contactado a muchos usuarios que no son tus contactos hoy. Por favor, reinténtalo mañana. Por hoy podrás responderles si te escriben primero."; "lng_cant_invite_not_contact_flood" = "No puedes añadir a este usuario porque has contactado a muchos que no son tus contactos hoy. Puedes pedir a otro miembro que añada a este usuario al grupo. "; "lng_cant_more_info" = "Más información »"; "lng_cant_invite_banned" = "Sólo el administrador puede añadir a este usuario."; "lng_cant_invite_privacy" = "No puedes añadir a este usuario a grupos por sus ajustes de privacidad."; "lng_cant_invite_privacy_channel" = "No puedes añadir a este usuario a canales por sus ajustes de privacidad."; "lng_cant_delete_group#one" = "No es posible eliminar manualmente comunidades que tienen más de {count} miembro. Por favor, contacta al soporte de Telegram si quieres eliminar el grupo."; "lng_cant_delete_group#other" = "No es posible eliminar manualmente comunidades que tienen más de {count} miembros. Por favor, contacta al soporte de Telegram si quieres eliminar el grupo."; "lng_cant_delete_channel#one" = "No es posible eliminar manualmente comunidades que tienen más de {count} miembro. Por favor, contacta al soporte de Telegram si quieres eliminar el canal."; "lng_cant_delete_channel#other" = "No es posible eliminar manualmente comunidades que tienen más de {count} miembros. Por favor, contacta al soporte de Telegram si quieres eliminar el canal."; "lng_cant_do_this" = "Lo sentimos, esta acción no está disponible."; "lng_send_button" = "Enviar"; "lng_message_ph" = "Escribe un mensaje..."; "lng_broadcast_ph" = "Difunde un mensaje..."; "lng_broadcast_silent_ph" = "Difusión en silencio..."; "lng_record_cancel" = "Suelta fuera de aquí para cancelar"; "lng_will_be_notified" = "Tu publicación será notificada"; "lng_wont_be_notified" = "Tu publicación no será notificada"; "lng_willbe_history" = "Selecciona un chat para comenzar"; "lng_from_you" = "Tú"; "lng_from_draft" = "Borrador"; "lng_bot_description" = "¿Qué puede hacer este bot?"; "lng_unblock_button" = "Desbloquear"; "lng_channel_join" = "Unirse al canal"; "lng_channel_mute" = "Silenciar"; "lng_channel_unmute" = "No silenciar"; "lng_dialogs_text_with_from" = "{from_part} {message}"; "lng_dialogs_text_from_wrapped" = "{from}:"; "lng_dialogs_text_media" = "{media_part} {caption}"; "lng_dialogs_text_media_wrapped" = "{media},"; "lng_dialogs_show_all_chats" = "Mostrar todos los chats"; "lng_dialogs_hide_muted_chats" = "Ocultar chats silenciados"; "lng_open_this_link" = "¿Abrir este enlace?"; "lng_open_link" = "Abrir"; "lng_allow_bot_pass" = "¿Permites a {bot_name} entregar tu nombre e id de Telegram a los sitios web que abres a través de este bot?"; "lng_allow_bot" = "Permitir"; "lng_bot_start" = "Iniciar"; "lng_bot_choose_group" = "Elige un grupo"; "lng_bot_no_groups" = "No tienes grupos"; "lng_bot_groups_not_found" = "No se encontraron grupos"; "lng_bot_sure_invite" = "¿Añadir el bot a «{group}»?"; "lng_bot_already_in_group" = "El bot ya es un miembro del grupo."; "lng_bot_choose_chat" = "Elige un chat"; "lng_bot_no_chats" = "No tienes chats"; "lng_bot_chats_not_found" = "No se encontraron chats"; "lng_bot_sure_share_game" = "¿Compartir el juego con {user}?"; "lng_bot_sure_share_game_group" = "¿Compartir el juego en «{group}»?"; "lng_typing" = "escribiendo"; "lng_user_typing" = "{user} está escribiendo"; "lng_users_typing" = "{user} y {second_user} están escribiendo"; "lng_many_typing#one" = "{count} persona está escribiendo"; "lng_many_typing#other" = "{count} personas están escribiendo"; "lng_playing_game" = "jugando"; "lng_user_playing_game" = "{user} está jugando"; "lng_users_playing_game" = "{user} y {second_user} están jugando"; "lng_many_playing_game#one" = "{count} persona está jugando"; "lng_many_playing_game#other" = "{count} personas están jugando"; "lng_send_action_record_video" = "grabando un vídeo"; "lng_user_action_record_video" = "{user} está grabando un vídeo"; "lng_send_action_upload_video" = "enviando un vídeo"; "lng_user_action_upload_video" = "{user} está enviando un vídeo"; "lng_send_action_record_audio" = "grabando un mensaje de voz"; "lng_user_action_record_audio" = "{user} está grabando un mensaje de voz"; "lng_send_action_upload_audio" = "enviando un mensaje de voz"; "lng_user_action_upload_audio" = "{user} está enviando un mensaje de voz"; "lng_send_action_record_round" = "grabando un videomensaje"; "lng_user_action_record_round" = "{user} está grabando un videomensaje"; "lng_send_action_upload_round" = "enviando un videomensaje"; "lng_user_action_upload_round" = "{user} está enviando un videomensaje"; "lng_send_action_upload_photo" = "enviando una foto"; "lng_user_action_upload_photo" = "{user} está enviando una foto"; "lng_send_action_upload_file" = "enviando un archivo"; "lng_user_action_upload_file" = "{user} está enviando un archivo"; "lng_send_action_geo_location" = "obteniendo ubicación"; "lng_user_action_geo_location" = "{user} está obteniendo una ubicación"; "lng_send_action_choose_contact" = "eligiendo un contacto"; "lng_user_action_choose_contact" = "{user} está eligiendo un contacto"; "lng_unread_bar#one" = "{count} mensaje no leído"; "lng_unread_bar#other" = "{count} mensajes no leídos"; "lng_maps_point" = "Ubicación"; "lng_save_photo" = "Guardar imagen"; "lng_save_video" = "Guardar vídeo"; "lng_save_audio_file" = "Guardar audio"; "lng_save_audio" = "Guardar mensaje de voz"; "lng_save_file" = "Guardar archivo"; "lng_save_downloaded" = "{ready} / {total} {mb}"; "lng_duration_and_size" = "{duration}, {size}"; "lng_duration_played" = "{played} / {duration}"; "lng_date_and_duration" = "{date}, {duration}"; "lng_choose_image" = "Elegir una imagen"; "lng_choose_files" = "Elegir archivos"; "lng_game_tag" = "Juego"; "lng_context_view_profile" = "Ver información"; "lng_context_view_group" = "Ver información"; "lng_context_view_channel" = "Ver información"; "lng_context_pin_to_top" = "Anclar"; "lng_context_unpin_from_top" = "Desanclar"; "lng_context_promote_admin" = "Nombrar administrador"; "lng_context_edit_permissions" = "Editar permisos"; "lng_context_restrict_user" = "Restringir usuario"; "lng_context_remove_from_group" = "Eliminar del grupo"; "lng_context_copy_link" = "Copiar enlace"; "lng_context_copy_post_link" = "Copiar enlace de la publicación"; "lng_context_copy_email" = "Copiar e-mail"; "lng_context_copy_hashtag" = "Copiar hashtag"; "lng_context_copy_mention" = "Copiar alias"; "lng_context_save_image" = "Guardar como..."; "lng_context_forward_image" = "Reenviar imagen"; "lng_context_delete_image" = "Eliminar imagen"; "lng_context_copy_image" = "Copiar imagen"; "lng_context_close_image" = "Cerrar imagen"; "lng_context_cancel_download" = "Cancelar descarga"; "lng_context_show_in_folder" = "Mostrar en la carpeta"; "lng_context_show_in_finder" = "Mostrar en el Finder"; "lng_context_save_video" = "Guardar como..."; "lng_context_save_audio_file" = "Guardar como..."; "lng_context_save_audio" = "Guardar como..."; "lng_context_pack_info" = "Información del pack"; "lng_context_pack_add" = "Añadir stickers"; "lng_context_save_file" = "Guardar como..."; "lng_context_forward_file" = "Reenviar archivo"; "lng_context_delete_file" = "Eliminar archivo"; "lng_context_close_file" = "Cerrar archivo"; "lng_context_copy_text" = "Copiar texto"; "lng_context_open_gif" = "Abrir GIF"; "lng_context_save_gif" = "Guardar GIF"; "lng_context_to_msg" = "Ir al mensaje"; "lng_context_reply_msg" = "Responder"; "lng_context_edit_msg" = "Editar"; "lng_context_forward_msg" = "Reenviar mensaje"; "lng_context_delete_msg" = "Eliminar mensaje"; "lng_context_select_msg" = "Seleccionar mensaje"; "lng_context_pin_msg" = "Anclar mensaje"; "lng_context_unpin_msg" = "Desanclar mensaje"; "lng_context_cancel_upload" = "Cancelar envío"; "lng_context_copy_selected" = "Copiar el texto seleccionado"; "lng_context_copy_selected_items" = "Copiar como texto"; "lng_context_forward_selected" = "Reenviar lo seleccionado"; "lng_context_delete_selected" = "Eliminar lo seleccionado"; "lng_context_clear_selection" = "Eliminar selección"; "lng_really_send_image" = "¿Quieres enviar esta imagen?"; "lng_really_send_file" = "¿Quieres enviar este archivo?"; "lng_really_share_contact" = "¿Quieres compartir este contacto?"; "lng_send_images_compress#one" = "Comprimir imagen"; "lng_send_images_compress#other" = "Comprimir imágenes"; "lng_send_image_non_local" = "No se pudo enviar el archivo remoto: {name}"; "lng_send_image_empty" = "No se pudo enviar el archivo vacío: {name}"; "lng_send_image_too_large" = "No se pudo enviar el archivo porque supera los 1500 MB: {name}"; "lng_send_folder" = "No se pudo enviar «{name}», porque es un directorio :("; "lng_send_images_selected#one" = "{count} imagen seleccionada"; "lng_send_images_selected#other" = "{count} imágenes seleccionadas"; "lng_send_photos#one" = "Enviar {count} foto"; "lng_send_photos#other" = "Enviar {count} fotos"; "lng_send_files_selected#one" = "{count} archivo seleccionado"; "lng_send_files_selected#other" = "{count} archivos seleccionados"; "lng_send_files#one" = "Enviar {count} archivo"; "lng_send_files#other" = "Enviar {count} archivos"; "lng_forward_choose" = "Elige un destinatario..."; "lng_forward_cant" = "Lo sentimos, no puedes reenviar aquí :("; "lng_forward_confirm" = "¿Reenviar a {recipient}?"; "lng_forward_share_contact" = "¿Compartir contacto con {recipient}?"; "lng_forward_share_cant" = "No hay forma de compartir un contacto aquí :("; "lng_forward_send_file_confirm" = "¿Enviar «{name}» a {recipient}?"; "lng_forward_send_files_confirm" = "¿Enviar los archivos seleccionados a {recipient}?"; "lng_forward_send_files_cant" = "No hay forma de enviar multimedia aquí :("; "lng_forward_send" = "Enviar"; "lng_forward_messages#one" = "{count} mensaje reenviado"; "lng_forward_messages#other" = "{count} mensajes reenviados"; "lng_forwarding_from#one" = "{user} y {count} otro"; "lng_forwarding_from#other" = "{user} y {count} otros"; "lng_forwarding_from_two" = "{user} y {second_user}"; "lng_inline_switch_choose" = "Elige una conversación..."; "lng_inline_switch_cant" = "Lo sentimos, no puedes escribir aquí :("; "lng_share_cant" = "Lo sentimos. No hay forma de compartir aquí :("; "lng_reply_cant" = "Lo sentimos, no puedes responder un mensaje viejo en un supergrupo :("; "lng_reply_cant_forward" = "Lo sentimos, no puedes responder un mensaje viejo en un supergrupo :( ¿Quieres reenviarlo y añadir un comentario?"; "lng_share_title" = "Compartir con"; "lng_share_copy_link" = "Copiar enlace"; "lng_share_confirm" = "Enviar"; "lng_share_wrong_user" = "Este juego fue abierto por un usuario diferente."; "lng_share_game_link_copied" = "Enlace del juego copiado al portapapeles."; "lng_share_done" = "¡Hecho!"; "lng_contact_phone" = "Número de teléfono"; "lng_enter_contact_data" = "Nuevo contacto"; "lng_edit_group_title" = "Editar nombre del grupo"; "lng_edit_contact_title" = "Editar nombre del contacto"; "lng_edit_channel_title" = "Editar canal"; "lng_edit_sign_messages" = "Firmar los mensajes"; "lng_edit_group_who_invites" = "Quién puede añadir miembros"; "lng_edit_group_invites_everybody" = "Todos los miembros"; "lng_edit_group_invites_only_admins" = "Sólo administradores"; "lng_edit_group" = "Editar grupo"; "lng_edit_self_title" = "Editar tu nombre"; "lng_confirm_contact_data" = "Nuevo contacto"; "lng_add_contact" = "Crear"; "lng_add_contact_button" = "Nuevo contacto"; "lng_contacts_header" = "Contactos"; "lng_contact_not_joined" = "Lamentablemente {name} no se ha unido a Telegram aún, pero puedes enviarle una invitación.\n\nTe informaremos cuando alguno de tus contactos se una a Telegram."; "lng_try_other_contact" = "Intentar con otro"; "lng_create_group_link" = "Enlace"; "lng_create_group_invite_link" = "Invitación"; "lng_create_group_photo" = "Poner foto"; "lng_create_group_description" = "Descripción (opcional)"; "lng_drag_images_here" = "Arrastra imágenes aquí"; "lng_drag_photos_here" = "Arrastra fotos aquí"; "lng_drag_files_here" = "Arrastra archivos aquí"; "lng_drag_to_send_quick" = "para enviarlas rápidamente"; "lng_drag_to_send_no_compression" = "para enviarlas sin compresión"; "lng_drag_to_send_files" = "para enviarlos como archivos"; "lng_selected_clear" = "Cancelar"; "lng_selected_delete" = "Eliminar"; "lng_selected_forward" = "Reenviar"; "lng_selected_count#one" = "{count} mensaje"; "lng_selected_count#other" = "{count} mensajes"; "lng_selected_cancel_sure_this" = "¿Detener el envío?"; "lng_selected_upload_stop" = "Detener"; "lng_selected_delete_sure_this" = "¿Quieres eliminar este mensaje?"; "lng_selected_delete_sure#one" = "¿Quieres eliminar {count} mensaje?"; "lng_selected_delete_sure#other" = "¿Quieres eliminar {count} mensajes?"; "lng_delete_photo_sure" = "¿Quieres eliminar esta foto?"; "lng_delete_for_everyone_hint#one" = "Esto lo eliminará para todos en este chat."; "lng_delete_for_everyone_hint#other" = "Esto los eliminará para todos en este chat."; "lng_delete_for_me_chat_hint#one" = "Esto lo eliminará sólo para ti, no para los otros miembros del chat."; "lng_delete_for_me_chat_hint#other" = "Esto los eliminará sólo para ti, no para los otros miembros del chat."; "lng_delete_for_me_hint#one" = "Esto lo eliminará sólo para ti."; "lng_delete_for_me_hint#other" = "Esto los eliminará sólo para ti."; "lng_delete_for_everyone_check" = "Eliminar para todos"; "lng_delete_for_other_check" = "Eliminar para {user}"; "lng_box_delete" = "Eliminar"; "lng_box_leave" = "Salir"; "lng_about_version" = "versión {version}"; "lng_about_text_1" = "Aplicación oficial y gratuita basada en la [a href=\"https://core.telegram.org/api\"]API de Telegram[/a] para su velocidad y seguridad."; "lng_about_text_2" = "Este software está licenciado bajo [a href=\"https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE\"]GNU GPL[/a] versión 3.\nCódigo fuente disponible en [a href=\"https://github.com/telegramdesktop/tdesktop\"]GitHub[/a]."; "lng_about_text_3" = "Conoce más en las {faq_open}preguntas frecuentes de Telegram{faq_close}."; "lng_about_done" = "Hecho"; "lng_search_no_results" = "No se encontraron mensajes"; "lng_search_found_results#one" = "{count} mensaje encontrado"; "lng_search_found_results#other" = "{count} mensajes encontrados"; "lng_search_global_results" = "Resultados de la búsqueda global"; "lng_search_messages_from" = "Mostrar mensajes de"; "lng_media_save_progress" = "{ready} de {total} {mb}"; "lng_mediaview_save_as" = "Guardar como..."; "lng_mediaview_copy" = "Copiar"; "lng_mediaview_forward" = "Reenviar"; "lng_mediaview_delete" = "Eliminar"; "lng_mediaview_photos_all" = "Ver todas las fotos"; "lng_mediaview_files_all" = "Ver todos los archivos"; "lng_mediaview_single_photo" = "Foto"; "lng_mediaview_group_photo" = "Foto del grupo"; "lng_mediaview_channel_photo" = "Foto del canal"; "lng_mediaview_profile_photo" = "Foto de perfil"; "lng_mediaview_file_n_of_count" = "{file} {n} de {count}"; "lng_mediaview_n_of_count" = "Foto {n} de {count}"; "lng_mediaview_doc_image" = "Archivo"; "lng_mediaview_today" = "hoy a las {time}"; "lng_mediaview_yesterday" = "ayer a las {time}"; "lng_mediaview_date_time" = "{date} a las {time}"; "lng_mediaview_saved" = "La imagen fue guardada en tu carpeta [c]Descargas[/c]"; "lng_theme_preview_title" = "Vista previa del tema"; "lng_theme_preview_generating" = "Generando la vista previa del tema..."; "lng_theme_preview_invalid" = "Hay datos inválidos en este archivo de tema."; "lng_theme_preview_apply" = "Aplicar este tema"; "lng_new_authorization" = "{name}:\nDetectamos un inicio de sesión en tu cuenta desde un nuevo dispositivo el {day}, {date} a las {time}\n\nDispositivo: {device}\nUbicación: {location}\n\nSi no eras tú, puedes ir a Ajustes > Mostrar todas las sesiones, y cerrar esa sesión.\n\nSi crees que alguien inició tu sesión sin tu consentimiento, puedes activar la verificación en dos pasos en Ajustes.\n\nAtentamente,\nEl equipo de Telegram"; "lng_new_version_wrap" = "Telegram Desktop ha sido actualizada a la versión {version}\n\n{changes}\n\nEl historial completo está disponible aquí:\n{link}"; "lng_new_version_minor" = "— Corrección de errores y otras mejoras menores"; "lng_new_version_text" = "— Videomensajes mejorados: barra de progreso circular, soporte para Picture-in-Picture y cuenta regresiva de la duración.\n— Los mensajes de voz y de video ahora se reproducen automáticamente uno tras otro."; "lng_menu_insert_unicode" = "Insertar caracteres de control Unicode"; "lng_full_name" = "{first_name} {last_name}"; "lng_confirm_phone_link_invalid" = "El enlace está roto o ha expirado."; "lng_confirm_phone_title" = "No restablecer la cuenta"; "lng_confirm_phone_about" = "Alguien, con acceso a tu número de teléfono {phone}, solicitó eliminar tu cuenta de Telegram y restablecer tu contraseña de la verificación en dos pasos.\n\nSi no eras tú, por favor, inserta el código que enviamos por SMS a tu número."; "lng_confirm_phone_success" = "¡Listo!\n\nEl proceso de eliminación de la cuenta {phone} fue cancelado. Puedes cerrar esta ventana ahora."; "lng_confirm_phone_send" = "Enviar"; "lng_confirm_phone_enter_code" = "Por favor, pon el código."; "lng_theme_editor_no_keys" = "Aún no hay valores en la paleta"; "lng_theme_editor_cant_change_theme" = "No puedes aplicar nuevos temas mientras editas la paleta de colores. Por favor, cierra el editor de temas primero."; "lng_theme_editor_new_keys" = "Aún no está en la paleta"; "lng_theme_editor_background_image" = "Imagen de fondo"; "lng_theme_editor_saved_to_jpg" = "Guardado como JPEG, {size}"; "lng_theme_editor_read_from_jpg" = "Imagen JPEG, {size}"; "lng_theme_editor_read_from_png" = "Imagen PNG, {size}"; "lng_theme_editor_export" = "Exportar"; "lng_theme_editor_choose_image" = "Elige la imagen de fondo"; "lng_theme_editor_save_palette" = "Guardar archivo de la paleta de colores"; "lng_theme_editor_choose_name" = "Guardar archivo del tema"; "lng_theme_editor_error" = "El editor encontró un error :( Ve “log.txt” para los detalles."; "lng_theme_editor_done" = "¡Tema exportado con éxito!"; "lng_theme_editor_title" = "Editar la paleta de colores"; "lng_theme_editor_export_button" = "Exportar tema"; "lng_payments_not_supported" = "Lo sentimos, Telegram Desktop no soporta pagos todavía. Por favor, usa una de nuestras aplicaciones móviles para hacerlo."; "lng_payments_receipt_label" = "Recibo"; "lng_payments_receipt_label_test" = "Recibo de prueba"; "lng_payments_invoice_label" = "Factura"; "lng_payments_invoice_label_test" = "Factura de prueba"; "lng_payments_receipt_button" = "Recibo"; "lng_call_status_incoming" = "te está llamando..."; "lng_call_status_connecting" = "conectando..."; "lng_call_status_exchanging" = "intercambiando claves de cifrado..."; "lng_call_status_waiting" = "esperando..."; "lng_call_status_requesting" = "solicitando..."; "lng_call_status_hanging" = "colgando..."; "lng_call_status_ended" = "llamada terminada"; "lng_call_status_failed" = "conexión fallida"; "lng_call_status_ringing" = "llamando..."; "lng_call_status_busy" = "línea ocupada"; "lng_call_fingerprint_tooltip" = "Si los emojis son los mismos para {user}, la llamada es 100% segura."; "lng_call_error_not_available" = "Lo sentimos, {user} no acepta llamadas."; "lng_call_error_incompatible" = "La app de {user} usa un protocolo incompatible. Debe actualizar su app para recibir tu llamada."; "lng_call_error_outdated" = "La app de {user} no soporta llamadas. Debe actualizar su app para recibir tu llamada."; "lng_call_error_audio_io" = "Parece existir un problema con la reproducción de audio en tu PC. Por favor, verifica que tu altavoz y micrófono están funcionando y reinténtalo."; "lng_call_bar_hangup" = "Terminar llamada"; "lng_call_box_title" = "Llamadas"; "lng_call_box_about" = "Aún no has realizado llamadas."; "lng_call_box_status_today" = "{time}"; "lng_call_box_status_yesterday" = "Ayer a las {time}"; "lng_call_box_status_date" = "{date} a las {time}"; "lng_call_box_status_group" = "({count}) {status}"; "lng_call_outgoing" = "Llamada saliente"; "lng_call_incoming" = "Llamada entrante"; "lng_call_missed" = "Llamada perdida"; "lng_call_cancelled" = "Llamada cancelada"; "lng_call_declined" = "Llamada rechazada"; "lng_call_duration_info" = "{time}, {duration}"; "lng_call_type_and_duration" = "{type} ({duration})"; "lng_call_rate_label" = "Evalúa la calidad de tu llamada"; "lng_call_rate_comment" = "Comentario (opcional)"; "lng_player_message_today" = "Hoy a las {time}"; "lng_player_message_yesterday" = "Ayer a las {time}"; "lng_player_message_date" = "{date} a las {time}"; "lng_rights_edit_admin" = "Editar administrador"; "lng_rights_edit_admin_header" = "¿Qué puede hacer este administrador?"; "lng_rights_about_add_admins_yes" = "Este administrador podrá añadir administradores con los mismos (o menos) permisos."; "lng_rights_about_add_admins_no" = "Este administrador no podrá añadir administradores."; "lng_rights_about_admin_cant_edit" = "No puedes editar los permisos de este administrador."; "lng_rights_user_restrictions" = "Restricciones"; "lng_rights_user_restrictions_header" = "¿Qué puede hacer este usuario?"; "lng_rights_channel_info" = "Cambiar información del canal"; "lng_rights_channel_post" = "Publicar mensajes"; "lng_rights_channel_edit" = "Editar mensajes de otros"; "lng_rights_channel_delete" = "Eliminar mensajes de otros"; "lng_rights_group_info" = "Cambiar información del grupo"; "lng_rights_group_ban" = "Suspender usuarios"; "lng_rights_group_invite_link" = "Invitar con un enlace"; "lng_rights_group_invite" = "Añadir usuarios"; "lng_rights_group_pin" = "Anclar mensajes"; "lng_rights_group_delete" = "Eliminar mensajes"; "lng_rights_add_admins" = "Añadir administradores"; "lng_rights_chat_read" = "Leer mensajes"; "lng_rights_chat_send_text" = "Enviar mensajes"; "lng_rights_chat_send_media" = "Enviar multimedia"; "lng_rights_chat_send_stickers" = "Enviar stickers y GIF"; "lng_rights_chat_send_links" = "Enviar vista previa de enlaces"; "lng_rights_chat_banned_until_header" = "Restringido hasta"; "lng_rights_chat_banned_forever" = "Siempre"; "lng_rights_chat_banned_day#one" = "Por {count} día"; "lng_rights_chat_banned_day#other" = "Por {count} días"; "lng_rights_chat_banned_week#one" = "Por {count} semana"; "lng_rights_chat_banned_week#other" = "Por {count} semanas"; "lng_rights_chat_banned_custom" = "Personalizado"; "lng_rights_chat_banned_custom_date" = "Hasta el {date}"; "lng_rights_chat_banned_block" = "Suspender y eliminar del grupo"; "lng_restricted_send_message" = "Los administradores del grupo han restringido que escribas."; "lng_restricted_send_media" = "Los administradores del grupo han restringido que envíes multimedia."; "lng_restricted_send_stickers" = "Los administradores del grupo han restringido que envíes stickers."; "lng_restricted_send_gifs" = "Los administradores del grupo han restringido que envíes GIF."; "lng_restricted_send_inline" = "Los administradores del grupo han restringido que envíes contenido integrado."; "lng_restricted_list_title" = "Usuarios restringidos"; "lng_banned_list_title" = "Usuarios suspendidos"; "lng_admin_log_title_all" = "Todas las acciones"; "lng_admin_log_title_selected" = "Acciones seleccionadas"; "lng_admin_log_filter" = "Filtrar"; "lng_admin_log_filter_title" = "Filtrar"; "lng_admin_log_filter_all_actions" = "Todas las acciones"; "lng_admin_log_filter_restrictions" = "Nuevas restricciones"; "lng_admin_log_filter_admins_new" = "Nuevos administradores"; "lng_admin_log_filter_members_new" = "Nuevos miembros"; "lng_admin_log_filter_info_group" = "Información del grupo"; "lng_admin_log_filter_info_channel" = "Información del canal"; "lng_admin_log_filter_messages_deleted" = "Mensajes eliminados"; "lng_admin_log_filter_messages_edited" = "Mensajes editados"; "lng_admin_log_filter_messages_pinned" = "Mensajes anclados"; "lng_admin_log_filter_members_removed" = "Miembros eliminados"; "lng_admin_log_filter_all_admins" = "Todos los usuarios"; "lng_admin_log_about" = "¿Qué es esto?"; "lng_admin_log_about_text" = "Esta es una lista de las acciones de servicio realizadas por los miembros y administradores del grupo en las últimas 48 horas."; "lng_admin_log_no_results_title" = "No se encontraron acciones"; "lng_admin_log_no_results_text" = "No se encontraron acciones que coincidan con tu solicitud."; "lng_admin_log_no_results_search_text" = "No se encontraron acciones que contengan “{query}”."; "lng_admin_log_no_events_title" = "Aún no hay acciones"; "lng_admin_log_no_events_text" = "Los miembros y administradores\nno han realizado acciones de \nservicio en las últimas 48 horas."; "lng_admin_log_empty_text" = "Vacío"; "lng_admin_log_changed_title_group" = "{from} cambió el nombre del grupo a «{title}»"; "lng_admin_log_changed_title_channel" = "{from} cambió el nombre del canal a «{title}»"; "lng_admin_log_changed_description_group" = "{from} editó la descripción del grupo:"; "lng_admin_log_removed_description_group" = "{from} eliminó la descripción del grupo"; "lng_admin_log_changed_description_channel" = "{from} editó la descripción del canal:"; "lng_admin_log_removed_description_channel" = "{from} eliminó la descripción del canal"; "lng_admin_log_previous_description" = "Descripción anterior"; "lng_admin_log_changed_link_group" = "{from} cambió el enlace del grupo:"; "lng_admin_log_removed_link_group" = "{from} eliminó el enlace del grupo"; "lng_admin_log_changed_link_channel" = "{from} cambió el enlace del canal:"; "lng_admin_log_removed_link_channel" = "{from} eliminó el enlace del canal"; "lng_admin_log_previous_link" = "Enlace previo"; "lng_admin_log_changed_photo_group" = "{from} cambió la foto de grupo"; "lng_admin_log_changed_photo_channel" = "{from} cambió la foto del canal"; "lng_admin_log_removed_photo_group" = "{from} eliminó la foto del grupo"; "lng_admin_log_removed_photo_channel" = "{from} eliminó la foto del canal"; "lng_admin_log_invites_enabled" = "{from} permitió las invitaciones"; "lng_admin_log_invites_disabled" = "{from} desactivó las invitaciones"; "lng_admin_log_signatures_enabled" = "{from} activó las firmas"; "lng_admin_log_signatures_disabled" = "{from} desactivó las firmas"; "lng_admin_log_pinned_message" = "{from} ancló un mensaje:"; "lng_admin_log_unpinned_message" = "{from} desancló un mensaje"; "lng_admin_log_edited_caption" = "{from} editó un comentario:"; "lng_admin_log_removed_caption" = "{from} eliminó un comentario"; "lng_admin_log_previous_caption" = "Comentario original"; "lng_admin_log_edited_message" = "{from} editó un mensaje:"; "lng_admin_log_previous_message" = "Mensaje original"; "lng_admin_log_deleted_message" = "{from} eliminó un mensaje:"; "lng_admin_log_participant_joined" = "{from} se unió al grupo"; "lng_admin_log_participant_joined_channel" = "{from} se unió al canal"; "lng_admin_log_participant_left" = "{from} dejó el grupo"; "lng_admin_log_participant_left_channel" = "{from} dejó el canal"; "lng_admin_log_invited" = "invitó a {user}"; "lng_admin_log_banned" = "suspendió a {user}"; "lng_admin_log_restricted" = "cambió las restricciones de {user} {until}"; "lng_admin_log_promoted" = "cambió los privilegios de {user}"; "lng_admin_log_changed_stickers_group" = "{from} cambió el {sticker_set} de grupo"; "lng_admin_log_changed_stickers_set" = "pack de stickers"; "lng_admin_log_removed_stickers_group" = "{from} eliminó el pack de stickers"; "lng_admin_log_user_with_username" = "{name} ({mention})"; "lng_admin_log_restricted_forever" = "indefinidamente"; "lng_admin_log_restricted_until" = "hasta el {date}"; "lng_admin_log_banned_view_messages" = "Leer mensajes"; "lng_admin_log_banned_send_messages" = "Enviar mensajes"; "lng_admin_log_banned_send_media" = "Enviar multimedia"; "lng_admin_log_banned_send_stickers" = "Enviar stickers y GIF"; "lng_admin_log_banned_embed_links" = "Enviar vista previa de enlaces"; "lng_admin_log_admin_change_info" = "Cambiar información"; "lng_admin_log_admin_post_messages" = "Publicar mensajes"; "lng_admin_log_admin_edit_messages" = "Editar mensajes"; "lng_admin_log_admin_delete_messages" = "Eliminar mensajes"; "lng_admin_log_admin_ban_users" = "Suspender usuarios"; "lng_admin_log_admin_invite_users" = "Añadir usuarios"; "lng_admin_log_admin_invite_link" = "Invitar con un enlace"; "lng_admin_log_admin_pin_messages" = "Anclar mensajes"; "lng_admin_log_admin_add_admins" = "Añadir administradores"; // Not used "lng_topbar_info" = "Información"; "lng_profile_group_info" = "Información"; "lng_profile_channel_info" = "Información"; "lng_channel_add_admins" = "Nuevo administrador"; "lng_dlg_search_chat" = "Buscar en este chat"; "lng_dlg_search_channel" = "Buscar en este canal"; // Wnd specific "lng_wnd_choose_program_menu" = "Elegir programa predeterminado..."; "lng_wnd_menu_undo" = "Deshacer"; "lng_wnd_menu_redo" = "Rehacer"; // Linux specific "lng_linux_menu_undo" = "Deshacer"; "lng_linux_menu_redo" = "Rehacer"; // Mac specific "lng_mac_choose_program_menu" = "Otra..."; "lng_mac_choose_app" = "Elegir aplicación"; "lng_mac_choose_text" = "Elige una aplicación para abrir el archivo “{file}”."; "lng_mac_enable_filter" = "Habilitar:"; "lng_mac_recommended_apps" = "Aplicaciones recomendadas"; "lng_mac_all_apps" = "Todas las aplicaciones"; "lng_mac_always_open_with" = "Siempre abrir con"; "lng_mac_this_app_can_open" = "Esta aplicación puede abrir “{file}”."; "lng_mac_not_known_app" = "No se sabe si esta aplicación puede abrir “{file}”."; "lng_mac_menu_services" = "Servicios"; "lng_mac_menu_hide_telegram" = "Ocultar {telegram}"; "lng_mac_menu_hide_others" = "Ocultar otras"; "lng_mac_menu_show_all" = "Mostrar todas"; "lng_mac_menu_preferences" = "Preferencias..."; "lng_mac_menu_quit_telegram" = "Salir de {telegram}"; "lng_mac_menu_about_telegram" = "Acerca de {telegram}"; "lng_mac_menu_file" = "Archivo"; "lng_mac_menu_logout" = "Cerrar sesión"; "lng_mac_menu_edit" = "Editar"; "lng_mac_menu_undo" = "Deshacer"; "lng_mac_menu_redo" = "Rehacer"; "lng_mac_menu_cut" = "Cortar"; "lng_mac_menu_copy" = "Copiar"; "lng_mac_menu_paste" = "Pegar"; "lng_mac_menu_delete" = "Eliminar"; "lng_mac_menu_select_all" = "Seleccionar todo"; "lng_mac_menu_window" = "Ventana"; "lng_mac_menu_contacts" = "Contactos"; "lng_mac_menu_add_contact" = "Añadir contacto"; "lng_mac_menu_new_group" = "Nuevo grupo"; "lng_mac_menu_new_channel" = "Nuevo canal"; "lng_mac_menu_show" = "Mostrar Telegram"; // Keys finished tdesktop-1.2.17/Telegram/Resources/langs/lang_it.strings000066400000000000000000002443051326245125100233160ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ "lng_language_name" = "Italiano"; "lng_switch_to_this" = "Passa all'Italiano"; "lng_menu_contacts" = "Contatti"; "lng_menu_calls" = "Chiamate"; "lng_menu_settings" = "Impostazioni"; "lng_menu_about" = "Info"; "lng_menu_update" = "Aggiorna"; "lng_menu_restart" = "Riavvia"; "lng_menu_back" = "Indietro"; "lng_menu_night_mode" = "Modalità notte"; "lng_disable_notifications_from_tray" = "Disattiva notifiche"; "lng_enable_notifications_from_tray" = "Attiva notifiche"; "lng_open_from_tray" = "Apri Telegram"; "lng_minimize_to_tray" = "Riduci a icona"; "lng_quit_from_tray" = "Chiudi Telegram"; "lng_tray_icon_text" = "Telegram è ancora attivo qui,\npuoi cambiarlo nelle impostazioni.\nSe l'icona di notifica scompare,\npuoi ripristinarla dalle icone nascoste."; "lng_month1" = "Gennaio"; "lng_month2" = "Febbraio"; "lng_month3" = "Marzo"; "lng_month4" = "Aprile"; "lng_month5" = "Maggio"; "lng_month6" = "Giugno"; "lng_month7" = "Luglio"; "lng_month8" = "Agosto"; "lng_month9" = "Settembre"; "lng_month10" = "Ottobre"; "lng_month11" = "Novembre"; "lng_month12" = "Dicembre"; "lng_month1_small" = "Gen"; "lng_month2_small" = "Feb"; "lng_month3_small" = "Mar"; "lng_month4_small" = "Apr"; "lng_month5_small" = "Mag"; "lng_month6_small" = "Giu"; "lng_month7_small" = "Lug"; "lng_month8_small" = "Ago"; "lng_month9_small" = "Set"; "lng_month10_small" = "Ott"; "lng_month11_small" = "Nov"; "lng_month12_small" = "Dic"; "lng_weekday1" = "Lun"; "lng_weekday2" = "Mar"; "lng_weekday3" = "Mer"; "lng_weekday4" = "Gio"; "lng_weekday5" = "Ven"; "lng_weekday6" = "Sab"; "lng_weekday7" = "Dom"; "lng_weekday1_full" = "Lunedì"; "lng_weekday2_full" = "Martedì"; "lng_weekday3_full" = "Mercoledì"; "lng_weekday4_full" = "Giovedì"; "lng_weekday5_full" = "Venerdì"; "lng_weekday6_full" = "Sabato"; "lng_weekday7_full" = "Domenica"; "lng_month_day" = "{day} {month}"; "lng_month_day_year" = "{day} {month} {year}"; "lng_month_year" = "{month} {year}"; "lng_box_ok" = "Ok"; "lng_cancel" = "Annulla"; "lng_continue" = "Continua"; "lng_close" = "Chiudi"; "lng_connecting" = "Connetto..."; "lng_connecting_to_proxy" = "Connetto al proxy..."; "lng_connecting_settings" = "Impostazioni"; "lng_reconnecting#one" = "Riconnetto tra {count} s..."; "lng_reconnecting#other" = "Riconnetto tra {count} s..."; "lng_reconnecting_try_now" = "Prova ora"; "lng_status_service_notifications" = "notifiche di servizio"; "lng_status_support" = "assistenza"; "lng_status_bot" = "bot"; "lng_status_bot_reads_all" = "ha accesso ai messaggi"; "lng_status_bot_not_reads_all" = "non ha accesso ai messaggi"; "lng_status_offline" = "ultimo accesso molto tempo fa"; "lng_status_recently" = "ultimo accesso di recente"; "lng_status_last_week" = "ultimo accesso entro una settimana"; "lng_status_last_month" = "ultimo accesso entro un mese"; "lng_status_invisible" = "invisibile"; "lng_status_lastseen_now" = "ultimo accesso adesso"; "lng_status_lastseen_minutes#one" = "ultimo accesso {count} minuto fa"; "lng_status_lastseen_minutes#other" = "ultimo accesso {count} minuti fa"; "lng_status_lastseen_hours#one" = "ultimo accesso {count} ora fa"; "lng_status_lastseen_hours#other" = "ultimo accesso {count} ore fa"; "lng_status_lastseen_today" = "ultimo accesso oggi alle {time}"; "lng_status_lastseen_yesterday" = "ultimo accesso ieri alle {time}"; "lng_status_lastseen_date" = "ultimo accesso {date}"; "lng_status_lastseen_date_time" = "ultimo accesso {date} alle {time}"; "lng_status_online" = "in linea"; "lng_status_connecting" = "connetto..."; "lng_chat_status_unaccessible" = "gruppo inaccessibile"; "lng_chat_status_no_members" = "nessun membro"; "lng_chat_status_members#one" = "{count} membro"; "lng_chat_status_members#other" = "{count} membri"; "lng_chat_status_online#one" = "{count} in linea"; "lng_chat_status_online#other" = "{count} in linea"; "lng_chat_status_members_online" = "{members_count}, {online_count}"; "lng_channel_status" = "canale"; "lng_group_status" = "gruppo"; "lng_channel_members_link#one" = "{count} membro"; "lng_channel_members_link#other" = "{count} membri"; "lng_channel_admins_link#one" = "{count} amministratore"; "lng_channel_admins_link#other" = "{count} amministratori"; "lng_server_error" = "Errore interno del server."; "lng_flood_error" = "Troppi tentativi. Per favore riprova più tardi."; "lng_gif_error" = "Si è verificato un errore nel leggere la GIF :("; "lng_edit_error" = "Non puoi modificare questo messaggio"; "lng_join_channel_error" = "Spiacenti, ti sei unito a troppi canali e supergruppi. Per favore lasciane qualcuno prima di unirti."; "lng_error_phone_flood" = "Spiacenti, hai eliminato e ricreato il tuo account troppe volte di recente. Per favore attendi alcuni giorni prima di iscriverti di nuovo."; "lng_error_start_minimized_passcoded" = "Hai inserito un codice di blocco, quindi l'app non può essere avviata minimizzata. L'app ti chiederà il codice prima di potersi avviare."; "lng_error_pinned_max#one" = "Spiacenti, non puoi fissare in alto più di {count} chat."; "lng_error_pinned_max#other" = "Spiacenti, non puoi fissare in alto più di {count} chat."; "lng_error_public_groups_denied" = "Sfortunatamente, non ti è permesso partecipare ai gruppi pubblici.\n{more_info}"; "lng_error_cant_edit_admin" = "Spiacenti, non puoi modificare le autorizzazioni di questo amministratore."; "lng_error_cant_add_member" = "Spiacenti, non puoi aggiungere il bot a questo gruppo. Chiedi ad un amministratore di farlo."; "lng_error_cant_add_bot" = "Spiacenti, questo bot non può essere aggiunto ai gruppi."; "lng_error_cant_add_admin_invite" = "Spiacenti, non puoi aggiungere questo utente come amministratore perché non è un membro del gruppo e non sei autorizzato ad invitarlo."; "lng_error_cant_add_admin_unban" = "Spiacenti, non puoi aggiungere questo utente come amministratore perché è bloccato e non puoi sbloccarlo."; "lng_error_cant_ban_admin" = "Spiacenti, non puoi bloccare questo utente perché è un amministratore in questo gruppo e non sei autorizzato a degradarlo."; "lng_sure_add_admin_invite" = "Questo utente non è membro del gruppo. Vuoi aggiungerlo e promuoverlo ad amministratore?"; "lng_sure_add_admin_unban" = "Questo utente è limitato o bloccato nel gruppo. Sei sicuro di volerlo sbloccare e promuoverlo?"; "lng_sure_ban_admin" = "Questo utente è un amministratore nel gruppo. Sei sicuro di voler proseguire e limitarlo?"; "lng_sure_ban_user_group" = "Bloccare {user} nel gruppo?"; "lng_sure_enable_socks" = "Sei sicuro di voler attivare questo proxy?\n\nServer: {server}\nPorta: {port}\n\nPuoi cambiare il tuo server proxy più tardi nelle Impostazioni (Tipo di connessione)."; "lng_sure_enable" = "Attiva"; "lng_edit_deleted" = "Questo messaggio è stato eliminato"; "lng_edit_too_long" = "Il tuo messaggio è troppo lungo"; "lng_edit_message" = "Modifica messaggio"; "lng_edit_message_text" = "Nuovo testo messaggio..."; "lng_deleted" = "Account eliminato"; "lng_deleted_message" = "Messaggio eliminato"; "lng_pinned_message" = "Messaggio fissato"; "lng_pinned_unpin_sure" = "Vuoi togliere questo messaggio?"; "lng_pinned_pin_sure" = "Vuoi fissare questo messaggio?"; "lng_pinned_pin" = "Fissa"; "lng_pinned_unpin" = "Togli"; "lng_pinned_notify" = "Notifica tutti i membri"; "lng_intro_about" = "Benvenuti nell'app desktop ufficiale Telegram Desktop.\nÈ veloce e sicura."; "lng_start_msgs" = "INIZIA A MESSAGGIARE"; "lng_intro_next" = "AVANTI"; "lng_intro_finish" = "ISCRIVITI"; "lng_intro_submit" = "INVIA"; "lng_photo_caption" = "Didascalia"; "lng_photos_comment" = "Commento"; "lng_phone_ph" = "Il tuo numero di telefono"; "lng_phone_title" = "Il tuo telefono"; "lng_phone_desc" = "Per favore conferma il tuo prefisso internazionale e\ninserisci il tuo numero di telefono."; "lng_phone_notreg" = "Se non hai ancora un account Telegram,\nper favore [b]registrati[/b] con {link_start}Android / iPhone{link_end} o {signup_start}qui{signup_end}"; "lng_country_code" = "Prefisso nazionale"; "lng_bad_country_code" = "Prefisso nazionale non valido"; "lng_country_ph" = "Cerca"; "lng_country_done" = "Fatto"; "lng_country_none" = "Nazione non trovata"; "lng_country_select" = "Seleziona nazione"; "lng_code_ph" = "Codice"; "lng_code_desc" = "Abbiamo inviato un messaggio col codice\ndi attivazione al tuo telefono. Inseriscilo qui"; "lng_code_telegram" = "Per favore inserisci il codice che hai appena\nricevuto nell'altra app di [b]Telegram[/b]."; "lng_code_no_telegram" = "Invia codice tramite SMS"; "lng_code_call" = "Telegram ti chiamerà tra {minutes}:{seconds}"; "lng_code_calling" = "Richiedo una telefonata da Telegram..."; "lng_code_called" = "Telegram ti ha chiamato"; "lng_bad_phone" = "Numero di telefono non valido. Per favore riprova."; "lng_bad_phone_noreg" = "Numero di telefono non registrato."; "lng_bad_code" = "Hai inserito un codice non valido."; "lng_bad_name" = "Per favore inserisci il tuo nome e cognome."; "lng_bad_photo" = "Immagine selezionata non valida."; "lng_signin_title" = "Verifica password"; "lng_signin_desc" = "Per favore inserisci la tua password."; "lng_signin_recover_desc" = "Per favore inserisci il codice dall'e-mail\n{email}"; "lng_signin_password" = "La tua password"; "lng_signin_code" = "Codice dall'e-mail"; "lng_signin_recover" = "Password dimenticata?"; "lng_signin_recover_title" = "Ripristino password"; "lng_signin_hint" = "Suggerimento: {password_hint}"; "lng_signin_recover_hint" = "Il codice è stato inviato a {recover_email}"; "lng_signin_bad_password" = "Hai inserito una password errata."; "lng_signin_wrong_code" = "Hai inserito un codice non valido."; "lng_signin_try_password" = "Hai problemi ad accedere alla tua e-mail?"; "lng_signin_password_removed" = "La tua password è stata disattivata.\nNe puoi impostare una nuova dalle Impostazioni."; "lng_signin_no_email_forgot" = "Siccome non hai fornito un'email di recupero quando hai inserito la password, non ti resta che ricordarti la password o ripristinare il tuo account."; "lng_signin_cant_email_forgot" = "Se non puoi recuperare l'accesso all'e-mail, non ti resta che ricordarti la password o ripristinare il tuo account."; "lng_signin_reset_account" = "Ripristina il tuo account"; "lng_signin_sure_reset" = "Attenzione!\n\nPerderai tutte le chat e i messaggi, insieme a tutti i media e i file condivisi!\n\nVuoi ripristinare il tuo account?"; "lng_signin_reset" = "Ripristina"; "lng_signin_reset_wait" = "Dato che l'account {phone_number} è attivo e protetto da una password, lo elimineremo tra 1 settimana per motivi di sicurezza. Puoi annullare questo processo in qualsiasi momento.\n\nPotrai ripristinare il tuo account tra:\n{when}"; "lng_signin_reset_days#one" = "{count} giorno"; "lng_signin_reset_days#other" = "{count} giorni"; "lng_signin_reset_hours#one" = "{count} ora"; "lng_signin_reset_hours#other" = "{count} ore"; "lng_signin_reset_minutes#one" = "{count} minuto"; "lng_signin_reset_minutes#other" = "{count} minuti"; "lng_signin_reset_in_days" = "{days_count} {hours_count} {minutes_count}"; "lng_signin_reset_in_hours" = "{hours_count} {minutes_count}"; "lng_signin_reset_cancelled" = "I tuoi tentativi recenti di ripristinare questo account sono stati annullati dal suo utente attivo. Per favore riprova tra 7 giorni."; "lng_signup_title" = "Le tue info"; "lng_signup_desc" = "Inserisci il tuo nome e\ncarica una foto."; "lng_signup_firstname" = "Nome"; "lng_signup_lastname" = "Cognome"; "lng_dlg_filter" = "Cerca"; "lng_dlg_new_group_name" = "Nome del gruppo"; "lng_dlg_new_channel_name" = "Nome del canale"; "lng_no_contacts" = "Non hai contatti"; "lng_no_chats" = "Le tua chat saranno qui"; "lng_contacts_loading" = "Carico..."; "lng_contacts_not_found" = "Nessun contatto trovato"; "lng_dlg_search_for_messages" = "Cerca messaggi"; "lng_update_telegram" = "Aggiorna Telegram"; "lng_dlg_search_in" = "Cerca messaggi in"; "lng_dlg_search_from" = "Da: {user}"; "lng_settings_save" = "Salva"; "lng_settings_upload" = "Imposta foto profilo"; "lng_settings_crop_profile" = "Seleziona un'area per la tua foto profilo"; "lng_settings_uploading_photo" = "Carico foto..."; "lng_settings_edit" = "Modifica"; "lng_settings_drop_area_subtitle" = "per impostarla come foto profilo"; "lng_username_title" = "Username"; "lng_username_about" = "Puoi scegliere un username su Telegram.\nSe lo fai, le altre persone potranno trovarti\ntramite questo username e contattarti \nsenza conoscere il tuo numero di telefono.\n\nPuoi usare a-z, 0-9 e underscore.\nLa lunghezza minima è di 5 caratteri."; "lng_username_choose" = "Scegli il tuo username."; "lng_username_invalid" = "L'username non è valido."; "lng_username_occupied" = "L'username è già occupato."; "lng_username_too_short" = "L'username è troppo corto."; "lng_username_bad_symbols" = "Questo username ha simboli errati."; "lng_username_available" = "L'username è disponibile."; "lng_username_not_found" = "Utente @{user} non trovato."; "lng_username_link_willbe" = "Un link simile aprirà una chat con te:"; "lng_username_link" = "Questo link apre una chat con te:"; "lng_username_copied" = "Link copiato negli appunti."; "lng_bio_title" = "Modifica la tua bio"; "lng_bio_placeholder" = "Bio"; "lng_bio_about" = "Puoi aggiungere qualche riga su di te. Chiunque apra il tuo profilo vedrà questo testo."; "lng_settings_section_info" = "Info"; "lng_settings_phone_number" = "Numero di telefono:"; "lng_settings_username" = "Username:"; "lng_settings_choose_username" = "Scegli username"; "lng_settings_empty_bio" = "Nessuna"; "lng_settings_section_notify" = "Notifiche"; "lng_settings_desktop_notify" = "Notifiche desktop"; "lng_settings_show_name" = "Mostra nome del mittente"; "lng_settings_show_preview" = "Mostra anteprima messaggio"; "lng_settings_use_windows" = "Usa le notifiche di Windows"; "lng_settings_use_native_notifications" = "Usa notifiche native"; "lng_settings_advanced_notifications" = "Posizione e conteggio notifiche"; "lng_settings_notifications_position" = "Posizione sullo schermo"; "lng_settings_notifications_count" = "Conteggio notifiche"; "lng_settings_sound_notify" = "Riproduci suono"; "lng_settings_include_muted" = "Includi chat silenziate nel badge"; "lng_notification_preview" = "Hai un nuovo messaggio"; "lng_notification_reply" = "Rispondi"; "lng_notification_hide_all" = "Nascondi tutte"; "lng_notification_sample" = "Questa è una notifica di esempio"; "lng_settings_section_general" = "Generali"; "lng_settings_change_lang" = "Cambia lingua"; "lng_languages" = "Lingue"; "lng_sure_save_language" = "Telegram si riavvierà per cambiare lingua"; "lng_settings_update_automatically" = "Aggiorna automaticamente"; "lng_settings_current_version_label" = "Versione {version}:"; "lng_settings_current_version" = "Versione {version}"; "lng_settings_check_now" = "Cerca aggiornamenti"; "lng_settings_update_checking" = "Cerco aggiornamenti..."; "lng_settings_latest_installed" = "L'ultima versione è installata"; "lng_settings_downloading" = "Download aggiornamento {ready} / {total} MB..."; "lng_settings_update_ready" = "La nuova versione è pronta"; "lng_settings_update_now" = "Riavvia ora"; "lng_settings_update_fail" = "Ricerca aggiornamenti fallita :("; "lng_settings_workmode_tray" = "Mostra icona nell'area di notifica"; "lng_settings_workmode_window" = "Mostra icona nella barra applicazioni"; "lng_settings_auto_start" = "Avvia Telegram all'avvio del sistema"; "lng_settings_start_min" = "Avvia ridotto a icona"; "lng_settings_add_sendto" = "Inserisci Telegram nel menù «Invia a»"; "lng_settings_section_scale" = "Scala interfaccia"; "lng_settings_scale_auto" = "Auto ({cur})"; "lng_settings_section_chat_settings" = "Impostazioni chat"; "lng_settings_replace_emojis" = "Riconosci emoji"; "lng_settings_view_emojis" = "Visualizza lista"; "lng_settings_emoji_list" = "Emoji supportate"; "lng_settings_send_enter" = "Invia con tasto Invio"; "lng_settings_send_ctrlenter" = "Invia con Ctrl+Invio"; "lng_settings_send_cmdenter" = "Invia con Cmd+Invio"; "lng_settings_section_background" = "Sfondo chat"; "lng_settings_bg_use_default" = "Usa tema predefinito"; "lng_settings_bg_from_gallery" = "Scegli dalla galleria"; "lng_settings_bg_from_file" = "Scegli da file"; "lng_settings_bg_edit_theme" = "Modifica tema"; "lng_settings_bg_tile" = "Affianca sfondo"; "lng_settings_adaptive_wide" = "Layout adattivo per grandi schermi"; "lng_backgrounds_header" = "Scegli un nuovo sfondo per la chat"; "lng_theme_sure_keep" = "Mantenere questo tema?"; "lng_theme_reverting#one" = "Ripristino il vecchio tema tra {count} secondo."; "lng_theme_reverting#other" = "Ripristino il vecchio tema tra {count} secondi."; "lng_theme_keep_changes" = "Mantieni modifiche"; "lng_theme_revert" = "Ripristina"; "lng_download_path_dont_ask" = "Non chiedere il percorso di download per ogni file"; "lng_download_path_label" = "Percorso di download:"; "lng_download_path_temp" = "cartella temporanea"; "lng_download_path_default" = "cartella predefinita"; "lng_download_path_clear" = "Elimina tutto"; "lng_download_path_header" = "Scegli il percorso di download"; "lng_download_path_default_radio" = "Cartella Telegram in «Download»"; "lng_download_path_temp_radio" = "Cartella temporanea, pulita quando esci"; "lng_download_path_dir_radio" = "Cartella personalizzata, pulita a mano"; "lng_download_path_choose" = "Scegli il percorso di download"; "lng_sure_clear_downloads" = "Vuoi eliminare tutti i file scaricati nella cartella temporanea? Sarà fatto automaticamente all'uscita o alla disinstallazione del programma."; "lng_download_path_failed" = "Il download del file non può iniziare. La causa potrebbe essere una posizione sbagliata per i download.\n\nPuoi cambiare il percorso di download nelle Impostazioni."; "lng_download_path_settings" = "Impostazioni"; "lng_download_finish_failed" = "Il download del file non può essere concluso.\n\nVuoi riprovare?"; "lng_download_path_clearing" = "Elimino..."; "lng_download_path_cleared" = "Eliminato!"; "lng_download_path_clear_failed" = "Eliminazione fallita :("; "lng_settings_section_privacy" = "Privacy e sicurezza"; "lng_local_storage_title" = "Archivio locale"; "lng_settings_no_data_cached" = "Non ci sono dati nella cache!"; "lng_settings_images_cached#one" = "{count} immagine, {size}"; "lng_settings_images_cached#other" = "{count} immagini, {size}"; "lng_settings_audios_cached#one" = "{count} messaggio vocale, {size}"; "lng_settings_audios_cached#other" = "{count} messaggi vocali, {size}"; "lng_local_storage_clear" = "Elimina tutto"; "lng_local_storage_clearing" = "Elimino..."; "lng_local_storage_cleared" = "Eliminato!"; "lng_local_storage_clear_failed" = "Eliminazione fallita :("; "lng_settings_section_advanced_settings" = "Impostazioni avanzate"; "lng_settings_enable_night_theme" = "Attiva la modalità notte"; "lng_settings_disable_night_theme" = "Disattiva la modalità notte"; "lng_passcode_remove_button" = "Rimuovi"; "lng_passcode_turn_on" = "Attiva codice di blocco"; "lng_passcode_change" = "Cambia codice di blocco"; "lng_passcode_create" = "Codice di blocco"; "lng_passcode_remove" = "Rimuovi codice di blocco"; "lng_passcode_turn_off" = "Disattiva"; "lng_passcode_autolock" = "Blocco automatico"; "lng_passcode_autolock_away" = "Blocco automatico se lontano per:"; "lng_passcode_autolock_inactive" = "Blocco automatico se inattivo per:"; "lng_passcode_autolock_minutes#one" = "{count} minuto"; "lng_passcode_autolock_minutes#other" = "{count} minuti"; "lng_passcode_autolock_hours#one" = "{count} ora"; "lng_passcode_autolock_hours#other" = "{count} ore"; "lng_passcode_enter_old" = "Inserisci il codice attuale"; "lng_passcode_enter_first" = "Inserisci un codice"; "lng_passcode_enter_new" = "Inserisci il nuovo codice"; "lng_passcode_confirm_new" = "Reinserisci il nuovo codice"; "lng_passcode_about" = "Quando è attivo un codice, appare un'icona col lucchetto sopra la lista delle chat. Premila per bloccare l'app.\n\nNota: se dimentichi il codice, dovrai accedere di nuovo su Telegram Desktop."; "lng_passcode_differ" = "I codici sono diversi"; "lng_passcode_wrong" = "Codice errato"; "lng_passcode_is_same" = "Il codice non è stato cambiato"; "lng_passcode_enter" = "Inserisci il tuo codice di blocco"; "lng_passcode_ph" = "Il tuo codice"; "lng_passcode_submit" = "Invia"; "lng_passcode_logout" = "Esci"; "lng_passcode_need_unblock" = "Devi prima sbloccarmi."; "lng_cloud_password_waiting" = "Link di conferma inviato a {email}..."; "lng_cloud_password_change" = "Cambia password"; "lng_cloud_password_create" = "Password cloud"; "lng_cloud_password_remove" = "Rimuovi password"; "lng_cloud_password_set" = "Attiva verifica in due passaggi"; "lng_cloud_password_edit" = "Cambia password"; "lng_cloud_password_enter_old" = "Inserisci la password attuale"; "lng_cloud_password_enter_first" = "Inserisci una password"; "lng_cloud_password_enter_new" = "Inserisci una nuova password"; "lng_cloud_password_confirm_new" = "Reinserisci la nuova password"; "lng_cloud_password_hint" = "Inserisci un suggerimento password"; "lng_cloud_password_change_hint" = "Inserisci un nuovo suggerimento"; "lng_cloud_password_bad" = "Password e suggerimento devono essere diversi."; "lng_cloud_password_email" = "Inserisci email di recupero"; "lng_cloud_password_bad_email" = "E-mail non valida, riprova con un'altra."; "lng_cloud_password_about" = "La password sarà richiesta quando ti connetti da un nuovo dispositivo insieme al codice."; "lng_cloud_password_about_recover" = "Attenzione! Sicuro di non voler\naggiungere un'e-mail di recupero?\n\nSe dimentichi la tua password, perderai\nl'accesso al tuo account Telegram."; "lng_cloud_password_skip_email" = "Salta e-mail"; "lng_cloud_password_almost" = "Abbiamo inviato un link di conferma\nall'e-mail che ci hai fornito. La verifica in due passaggi sarà attivata non appena aprirai quel link."; "lng_cloud_password_was_set" = "Verifica in due passaggi abilitata."; "lng_cloud_password_updated" = "La tua password è stata aggiornata."; "lng_cloud_password_removed" = "La verifica in due passaggi è stata disattivata."; "lng_cloud_password_differ" = "Le password non corrispondono"; "lng_cloud_password_wrong" = "Password errata"; "lng_cloud_password_is_same" = "La password non è stata cambiata"; "lng_connection_type" = "Tipo di connessione:"; "lng_connection_auto_connecting" = "Predefinita (connetto...)"; "lng_connection_auto" = "Predefinita ({transport} in uso)"; "lng_connection_proxy_connecting" = "Connetto tramite proxy..."; "lng_connection_proxy" = "{transport} con proxy"; "lng_connection_header" = "Tipo di connessione"; "lng_connection_auto_rb" = "Auto (TCP se disponibile o HTTP)"; "lng_connection_http_proxy_rb" = "HTTP con proxy-http personalizzato"; "lng_connection_tcp_proxy_rb" = "TCP con proxy-socks5 personalizzato"; "lng_connection_try_ipv6" = "Prova a connettere tramite IPv6"; "lng_connection_host_ph" = "Hostname"; "lng_connection_port_ph" = "Porta"; "lng_connection_user_ph" = "Username"; "lng_connection_password_ph" = "Password"; "lng_connection_save" = "Salva"; "lng_settings_blocked_users" = "Utenti bloccati"; "lng_settings_last_seen_privacy" = "Ultimo accesso"; "lng_settings_calls_privacy" = "Privacy chiamate"; "lng_settings_groups_invite_privacy" = "Gruppi"; "lng_settings_show_sessions" = "Mostra tutte le sessioni"; "lng_settings_self_destruct" = "Impostazioni autodistruzione account"; "lng_settings_change_phone" = "Cambia numero di telefono"; "lng_settings_reset" = "Termina tutte le altre sessioni"; "lng_settings_reset_sure" = "Sicuro di voler terminare\ntutte le altre sessioni?"; "lng_settings_reset_one_sure" = "Vuoi terminare questa sessione?"; "lng_settings_reset_button" = "Chiudi"; "lng_settings_reset_done" = "Altre sessioni terminate"; "lng_settings_manage_local_storage" = "Gestisci l'archivio locale"; "lng_settings_ask_question" = "Fai una domanda"; "lng_settings_ask_sure" = "Per favore nota che l'assistenza di Telegram è fornita da volontari. Proviamo a rispondere quanto prima, ma potrebbe volerci del tempo.\n\nDai un'occhiata alle domande frequenti di Telegram: contengono suggerimenti importanti per risolvere i problemi e risposte a quasi tutte le domande."; "lng_settings_faq_button" = "Domande frequenti"; "lng_settings_ask_ok" = "Chiedi"; "lng_settings_faq" = "Domande frequenti"; "lng_settings_logout" = "Esci"; "lng_sure_logout" = "Sicuro di voler uscire?"; "lng_settings_need_restart" = "Devi riavviare per applicare alcune delle nuove impostazioni. Riavviare ora?"; "lng_settings_restart_now" = "RIAVVIA"; "lng_settings_restart_later" = "DOPO"; "lng_sessions_header" = "Sessione attuale"; "lng_sessions_other_header" = "Sessioni attive"; "lng_sessions_no_other" = "Nessun'altra sessione"; "lng_sessions_other_desc" = "Ti puoi connettere a Telegram da altri dispositivi mobili, tablet e desktop usando lo stesso numero. Tutti i tuoi dati saranno sincronizzati istantaneamente."; "lng_sessions_terminate_all" = "Termina tutte le altre sessioni"; "lng_blocked_list_title" = "Utenti bloccati"; "lng_blocked_list_unknown_phone" = "numero sconosciuto"; "lng_blocked_list_unblock" = "Sblocca"; "lng_blocked_list_add" = "Blocca utente"; "lng_blocked_list_add_title" = "Seleziona l'utente da bloccare"; "lng_blocked_list_already_blocked" = "già bloccato"; "lng_blocked_list_about" = "Gli utenti bloccati non possono inviarti messaggi o aggiungerti ai gruppi. Non vedranno le tue foto profilo, lo stato in linea e l'ultimo accesso."; "lng_blocked_list_not_found" = "Nessun utente trovato."; "lng_edit_privacy_everyone" = "Tutti"; "lng_edit_privacy_contacts" = "I miei contatti"; "lng_edit_privacy_nobody" = "Nessuno"; "lng_edit_privacy_exceptions" = "Aggiungi eccezioni"; "lng_edit_privacy_lastseen_title" = "Ultimo accesso"; "lng_edit_privacy_lastseen_description" = "Puoi decidere chi può vedere il tuo ultimo accesso:"; "lng_edit_privacy_lastseen_warning" = "Importante: non potrai vedere l'ultimo accesso delle persone con cui non condividi l'ultimo accesso. Verrà mostrato un orario approssimativo (di recente, entro una settimana, entro un mese)."; "lng_edit_privacy_lastseen_always_empty" = "Condividi con"; "lng_edit_privacy_lastseen_always#one" = "Condividi con {count} utente"; "lng_edit_privacy_lastseen_always#other" = "Condividi con {count} utenti"; "lng_edit_privacy_lastseen_never_empty" = "Non condividere con"; "lng_edit_privacy_lastseen_never#one" = "Non condividere con {count} utente"; "lng_edit_privacy_lastseen_never#other" = "Non condividere con {count} utenti"; "lng_edit_privacy_lastseen_exceptions" = "Queste impostazioni annulleranno i valori precedenti."; "lng_edit_privacy_lastseen_always_title" = "Condividi con"; "lng_edit_privacy_lastseen_never_title" = "Non condividere co"; "lng_edit_privacy_groups_title" = "Gruppi"; "lng_edit_privacy_groups_description" = "Puoi decidere chi può aggiungerti a gruppi e canali con precisione granulare:"; "lng_edit_privacy_groups_always_empty" = "Consenti sempre"; "lng_edit_privacy_groups_always#one" = "Consenti sempre {count} utente"; "lng_edit_privacy_groups_always#other" = "Consenti sempre {count} utenti"; "lng_edit_privacy_groups_never_empty" = "Non consentire mai"; "lng_edit_privacy_groups_never#one" = "Non consentire mai {count} utente"; "lng_edit_privacy_groups_never#other" = "Non consentire mai {count} utenti"; "lng_edit_privacy_groups_exceptions" = "Questi utenti potranno o non potranno aggiungerti a gruppi e canali indipendentemente dalle impostazioni precedenti."; "lng_edit_privacy_groups_always_title" = "Consenti sempre"; "lng_edit_privacy_groups_never_title" = "Non consentire mai"; "lng_edit_privacy_calls_title" = "Privacy chiamate"; "lng_edit_privacy_calls_description" = "Puoi decidere chi può chiamarti:"; "lng_edit_privacy_calls_always_empty" = "Consenti sempre"; "lng_edit_privacy_calls_always#one" = "Consenti sempre {count} utente"; "lng_edit_privacy_calls_always#other" = "Consenti sempre {count} utenti"; "lng_edit_privacy_calls_never_empty" = "Non consentire mai"; "lng_edit_privacy_calls_never#one" = "Non consentire mai {count} utente"; "lng_edit_privacy_calls_never#other" = "Non consentire mai {count} utenti"; "lng_edit_privacy_calls_exceptions" = "Questi utenti potranno o non potranno chiamarti indipendentemente dalle impostazioni precedenti."; "lng_edit_privacy_calls_always_title" = "Consenti sempre"; "lng_edit_privacy_calls_never_title" = "Non consentire mai"; "lng_self_destruct_title" = "Autodistruzione account"; "lng_self_destruct_description" = "Se non ti connetti almeno una volta in questo periodo, il tuo account verrà eliminato insieme a tutti i gruppi, i messaggi e i contatti."; "lng_self_destruct_months#one" = "{count} mese"; "lng_self_destruct_months#other" = "{count} mesi"; "lng_self_destruct_years#one" = "{count} anno"; "lng_self_destruct_years#other" = "{count} anni"; "lng_change_phone_title" = "Cambia numero di telefono"; "lng_change_phone_description" = "Puoi cambiare il tuo numero di telefono\nqui. Il tuo account e tutti i tuoi dati cloud\n— messaggi, file, contatti, etc. saranno\ntrasferiti sul nuovo numero.\n\n[b]Importante[/b]: a tutti i tuoi contatti di Telegram\nverrà aggiunto il tuo [b]nuovo numero[/b] ai contatti,\npurché abbiano il tuo vecchio numero e\ntu non li abbia bloccati su Telegram."; "lng_change_phone_warning" = "Tutti i tuoi contatti Telegram avranno il tuo nuovo numero tra i loro contatti, purché abbiano il tuo vecchio numero e tu non li abbia bloccati su Telegram."; "lng_change_phone_occupied" = "Il numero {phone} è già connesso ad un account Telegram. Per favore elimina quell'account prima di migrare sul nuovo numero."; "lng_change_phone_button" = "Cambia numero"; "lng_change_phone_new_title" = "Inserisci il nuovo numero"; "lng_change_phone_new_description" = "Invieremo un SMS con un codice di conferma al tuo nuovo numero."; "lng_change_phone_new_submit" = "Invia"; "lng_change_phone_code_title" = "Inserisci il codice"; "lng_change_phone_code_description" = "Abbiamo inviato un SMS con un codice di conferma al tuo numero {phone}."; "lng_change_phone_success" = "Il tuo numero di telefono è stato cambiato."; "lng_mute_duration_hours#one" = "Per {count} ora"; "lng_mute_duration_hours#other" = "Per {count} ore"; "lng_mute_box_tip" = "Scegli la durata della disattivazione delle notifiche per la chat seguente"; "lng_preview_loading" = "Recupero le info del link..."; "lng_profile_chat_unaccessible" = "Gruppo inaccessibile"; "lng_profile_about_section" = "Info"; "lng_profile_description_section" = "Descrizione"; "lng_profile_settings_section" = "Impostazioni"; "lng_profile_actions_section" = "Azioni"; "lng_profile_bot_settings" = "Impostazioni"; "lng_profile_bot_help" = "Aiuto"; "lng_profile_invite_link_section" = "Link d'invito"; "lng_profile_create_public_link" = "Crea link pubblico"; "lng_profile_edit_public_link" = "Modifica link pubblico"; "lng_profile_search_members" = "Cerca membri"; "lng_profile_manage_admins" = "Gestisci gli amministratori"; "lng_profile_manage_blocklist" = "Gestisci gli utenti bloccati"; "lng_profile_manage_restrictedlist" = "Gestisci gli utenti limitati"; "lng_profile_recent_actions" = "Azioni recenti"; "lng_profile_common_groups#one" = "{count} gruppo in comune"; "lng_profile_common_groups#other" = "{count} gruppi in comune"; "lng_profile_common_groups_section" = "Gruppi in comune"; "lng_profile_participants_section" = "Membri"; "lng_profile_info_section" = "Info"; "lng_profile_mobile_number" = "Cellulare:"; "lng_profile_username" = "Username:"; "lng_profile_link" = "Link:"; "lng_profile_bio" = "Bio:"; "lng_profile_add_contact" = "Aggiungi contatto"; "lng_profile_edit_contact" = "Modifica"; "lng_profile_enable_notifications" = "Notifiche"; "lng_profile_clear_history" = "Cancella cronologia"; "lng_profile_delete_conversation" = "Elimina chat"; "lng_profile_clear_and_exit" = "Elimina ed esci"; "lng_profile_leave_channel" = "Lascia il canale"; "lng_profile_delete_channel" = "Elimina canale"; "lng_profile_leave_group" = "Lascia il gruppo"; "lng_profile_delete_group" = "Elimina gruppo"; "lng_profile_report" = "Segnala"; "lng_profile_search_messages" = "Cerca messaggi"; "lng_profile_block_user" = "Blocca utente"; "lng_profile_unblock_user" = "Sblocca utente"; "lng_profile_block_bot" = "Arresta e blocca bot"; "lng_profile_unblock_bot" = "Sblocca bot"; "lng_profile_send_message" = "Invia messaggio"; "lng_profile_share_contact" = "Condividi contatto"; "lng_profile_invite_to_group" = "Aggiungi a un gruppo"; "lng_profile_delete_contact" = "Elimina"; "lng_profile_set_group_photo" = "Imposta foto"; "lng_profile_add_participant" = "Aggiungi membri"; "lng_profile_view_channel" = "Visualizza canale"; "lng_profile_join_channel" = "Unisciti"; "lng_profile_delete_and_exit" = "Esci"; "lng_profile_kick" = "Rimuovi"; "lng_profile_admin" = "amministratore"; "lng_profile_edit_permissions" = "Modifica"; "lng_profile_sure_kick" = "Rimuovere {user} dal gruppo?"; "lng_profile_sure_kick_channel" = "Rimuovere {user} dal canale?"; "lng_profile_sure_kick_admin" = "Rimuovere {user} dagli amministratori?"; "lng_profile_loading" = "Carico..."; "lng_profile_shared_media" = "Media condivisi"; "lng_profile_no_media" = "Nessun media in questa chat."; "lng_profile_photos#one" = "{count} foto"; "lng_profile_photos#other" = "{count} foto"; "lng_profile_photos_header" = "Foto"; "lng_profile_videos#one" = "{count} video"; "lng_profile_videos#other" = "{count} video"; "lng_profile_videos_header" = "Video"; "lng_profile_songs#one" = "{count} file audio"; "lng_profile_songs#other" = "{count} file audio"; "lng_profile_songs_header" = "File audio"; "lng_profile_files#one" = "{count} file"; "lng_profile_files#other" = "{count} file"; "lng_profile_files_header" = "File"; "lng_profile_audios#one" = "{count} messaggio vocale"; "lng_profile_audios#other" = "{count} messaggi vocali"; "lng_profile_audios_header" = "Messaggi vocali"; "lng_profile_shared_links#one" = "{count} link condiviso"; "lng_profile_shared_links#other" = "{count} link condivisi"; "lng_profile_shared_links_header" = "Link condivisi"; "lng_profile_copy_phone" = "Copia numero di telefono"; "lng_profile_copy_fullname" = "Copia nome"; "lng_profile_drop_area_title" = "Trascina la tua immagine qui"; "lng_profile_drop_area_subtitle" = "per impostarla come foto del gruppo"; "lng_profile_drop_area_subtitle_channel" = "per impostarla come foto del canale"; "lng_profile_top_bar_share_contact" = "Condividi"; "lng_report_title" = "Segnala canale"; "lng_report_group_title" = "Segnala gruppo"; "lng_report_bot_title" = "Segnala bot"; "lng_report_reason_spam" = "Spam"; "lng_report_reason_violence" = "Violenza"; "lng_report_reason_pornography" = "Pornografia"; "lng_report_reason_other" = "Altro"; "lng_report_reason_description" = "Descrizione"; "lng_report_button" = "Segnala"; "lng_report_thanks" = "Grazie! La tua segnalazione sarà presto esaminata dal nostro team."; "lng_channel_add_members" = "Aggiungi membri"; "lng_channel_add_banned" = "Blocca utente"; "lng_channel_add_restricted" = "Limita utente"; "lng_channel_members" = "Membri"; "lng_channel_only_last_shown#one" = "Solo l'ultimo membro è mostrato qui"; "lng_channel_only_last_shown#other" = "Solo gli ultimi {count} membri sono mostrati qui"; "lng_channel_admins" = "Amministratori"; "lng_channel_add_admin" = "Aggiungi amministratore"; "lng_channel_admins_too_much" = "Spiacenti, hai raggiunto il limite di amministratori. Prima devi rimuoverne qualcuno."; "lng_channel_admin_status_creator" = "Creatore"; "lng_channel_admin_status_promoted_by" = "Promosso da {user}"; "lng_channel_admin_status_not_admin" = "Non amministratore"; "lng_channel_banned_status_restricted_by" = "Limitato da {user}"; "lng_group_blocked_list_about" = "Gli utenti bloccati sono rimossi dal gruppo e possono rientrare solo se invitati da un amministratore.\nI link di invito non funzionano per loro."; "lng_chat_all_members_admins" = "Tutti sono amministratori"; "lng_chat_about_all_admins" = "Tutti i membri possono aggiungere nuovi membri, modificare nome e foto del gruppo."; "lng_chat_about_admins" = "Gli amministratori possono aggiungere e rimuovere membri e modificare nome e foto del gruppo."; "lng_participant_filter" = "Cerca"; "lng_participant_invite" = "Invita"; "lng_participant_invite_sorry#one" = "Spiacenti, puoi aggiungere solo il primo membro a un canale.\n\nDa adesso, le persone potranno unirsi tramite il tuo link d'invito."; "lng_participant_invite_sorry#other" = "Spiacenti, puoi aggiungere solo i primi {count} membri a un canale.\n\nDa adesso, le persone potranno unirsi tramite il tuo link d'invito."; "lng_create_group_back" = "Indietro"; "lng_create_group_next" = "Avanti"; "lng_create_group_create" = "Crea"; "lng_create_group_title" = "Nuovo gruppo"; "lng_create_channel_title" = "Nuovo canale"; "lng_create_public_channel_title" = "Canale pubblico"; "lng_create_public_channel_about" = "Chiunque può cercare il canale nella ricerca e unirsi"; "lng_create_private_channel_title" = "Canale privato"; "lng_create_private_channel_about" = "Solo le persone con uno speciale link d'invito potranno unirsi"; "lng_create_public_group_title" = "Gruppo pubblico"; "lng_create_public_group_about" = "Chiunque può trovare il gruppo nella ricerca e unirsi, la cronologia è disponibile per tutti"; "lng_create_private_group_title" = "Gruppo privato"; "lng_create_private_group_about" = "Le persone si possono unire solo se invitate o con un link d'invito"; "lng_create_group_skip" = "Salta"; "lng_create_channel_link_invalid" = "Questo link non è valido"; "lng_create_channel_link_occupied" = "Spiacenti, questo link è già occupato"; "lng_create_channel_link_too_short" = "Spiacenti, questo link è troppo corto"; "lng_create_channel_link_bad_symbols" = "Spiacenti, questo link ha simboli errati"; "lng_create_channel_link_available" = "Questo link è disponibile"; "lng_create_channel_link_copied" = "Link copiato negli appunti"; "lng_create_group_crop" = "Seleziona un'area per la foto del gruppo"; "lng_create_channel_crop" = "Seleziona un'area per la foto del canale"; "lng_failed_add_participant" = "Impossibile aggiungere l'utente. Riprova più tardi."; "lng_failed_add_not_mutual" = "Spiacenti, se una persona lascia un gruppo, solo un contatto reciproco può aggiungerla (chi ti invita deve avere il tuo numero, e viceversa)."; "lng_failed_add_not_mutual_channel" = "Spiacenti, se una persona lascia un canale, solo un contatto reciproco può aggiungerla (chi ti invita deve avere il tuo numero, e viceversa)."; "lng_sure_delete_contact" = "Sicuro di volere eliminare {contact} dalla tua lista dei contatti?"; "lng_sure_delete_history" = "Sicuro di voler eliminare tutta la cronologia dei messaggi con {contact}?\n\nQuesta azione non può essere annullata."; "lng_sure_delete_group_history" = "Sicuro di voler eliminare tutta la cronologia dei messaggi in «{group}»?\n\nQuesta azione non può essere annullata."; "lng_sure_delete_and_exit" = "Sicuro di voler eliminare tutta la cronologia dei messaggi e abbandonare «{group}»?\n\nQuesta azione non può essere annullata."; "lng_sure_leave_channel" = "Sicuro di voler lasciare\nquesto canale?"; "lng_sure_delete_channel" = "Sicuro di voler eliminare questo canale? Tutti i membri verranno rimossi e i messaggi verranno persi."; "lng_sure_leave_group" = "Sicuro di voler lasciare questo gruppo?\nQuesta azione non può essere annullata."; "lng_sure_delete_group" = "Sicuro di voler eliminare questo gruppo? Tutti i membri verranno rimossi e i messaggi verranno persi."; "lng_message_empty" = "Messaggio vuoto"; "lng_message_unsupported" = "Questo messaggio non è supportato dalla tua versione di Telegram Desktop. Per favore, aggiorna all'ultima versione dalle Impostazioni o installalo da {link}"; "lng_duration_seconds#one" = "{count} secondo"; "lng_duration_seconds#other" = "{count} secondi"; "lng_duration_minsec_minutes#one" = "{count} min"; "lng_duration_minsec_minutes#other" = "{count} min"; "lng_duration_minsec_seconds#one" = "{count} sec"; "lng_duration_minsec_seconds#other" = "{count} sec"; "lng_duration_minutes_seconds" = "{minutes_count} {seconds_count}"; "lng_action_add_user" = "{from} ha aggiunto {user}"; "lng_action_add_users_many" = "{from} ha aggiunto {users}"; "lng_action_add_users_and_one" = "{accumulated}, {user}"; "lng_action_add_users_and_last" = "{accumulated} e {user}"; "lng_action_add_you" = "{from} ti ha aggiunto a questo canale"; "lng_action_you_joined" = "Ti sei unito a questo canale"; "lng_action_add_you_group" = "{from} ti ha aggiunto a questo gruppo"; "lng_action_you_joined_group" = "Ti sei unito a questo gruppo"; "lng_action_kick_user" = "{from} ha rimosso {user}"; "lng_action_user_left" = "{from} ha lasciato il gruppo"; "lng_action_user_joined" = "{from} si è unito al gruppo"; "lng_action_user_joined_by_link" = "{from} si è unito al gruppo tramite link d'invito"; "lng_action_user_registered" = "{from} si è unito a Telegram"; "lng_action_removed_photo" = "{from} ha rimosso la foto del gruppo"; "lng_action_removed_photo_channel" = "Foto del canale rimossa"; "lng_action_changed_photo" = "{from} ha aggiornato la foto del gruppo"; "lng_action_changed_photo_channel" = "Foto del canale aggiornata"; "lng_action_changed_title" = "{from} ha cambiato il nome del gruppo in «{title}»"; "lng_action_changed_title_channel" = "Nome del canale cambiato in «{title}»"; "lng_action_created_chat" = "{from} ha creato il gruppo «{title}»"; "lng_action_created_channel" = "Canale creato"; "lng_action_group_migrate" = "Il gruppo è stato aggiornato a supergruppo"; "lng_action_pinned_message" = "{from} ha fissato «{text}»"; "lng_action_pinned_media" = "{from} ha fissato {media}"; "lng_action_pinned_media_photo" = "una foto"; "lng_action_pinned_media_video" = "un video"; "lng_action_pinned_media_audio" = "un file audio"; "lng_action_pinned_media_voice" = "un messaggio vocale"; "lng_action_pinned_media_video_message" = "un videomessaggio"; "lng_action_pinned_media_file" = "un file"; "lng_action_pinned_media_gif" = "una GIF"; "lng_action_pinned_media_contact" = "un contatto"; "lng_action_pinned_media_location" = "una posizione"; "lng_action_pinned_media_sticker" = "uno sticker"; "lng_action_pinned_media_emoji_sticker" = "uno {emoji} sticker"; "lng_action_pinned_media_game" = "il gioco «{game}»"; "lng_action_game_score#one" = "{from} ha totalizzato {count} punto a {game}"; "lng_action_game_score#other" = "{from} ha totalizzato {count} punti a {game}"; "lng_action_game_you_scored#one" = "Hai totalizzato {count} punto a {game}"; "lng_action_game_you_scored#other" = "Hai totalizzato {count} punti a {game}"; "lng_action_game_score_no_game#one" = "{from} ha totalizzato {count} punto"; "lng_action_game_score_no_game#other" = "{from} ha totalizzato {count} punti"; "lng_action_game_you_scored_no_game#one" = "Hai totalizzato {count} punto"; "lng_action_game_you_scored_no_game#other" = "Hai totalizzato {count} punti"; "lng_action_payment_done" = "Hai appena trasferito con successo {amount} a {user}"; "lng_action_payment_done_for" = "Hai appena trasferito con successo {amount} a {user} per {invoice}"; "lng_action_took_screenshot" = "{from} ha fatto uno screenshot!"; "lng_action_you_took_screenshot" = "Hai fatto uno screenshot!"; "lng_ttl_photo_received" = "{from} ti inviato una foto con autodistruzione. Per favore visualizzala sul tuo cellulare."; "lng_ttl_photo_sent" = "Hai inviato una foto con autodistruzione."; "lng_ttl_photo_expired" = "La foto è scaduta"; "lng_ttl_video_received" = "{from} ti inviato un video con autodistruzione. Per favore visualizzalo sul tuo cellulare."; "lng_ttl_video_sent" = "Hai inviato un video con autodistruzione."; "lng_ttl_video_expired" = "Il video è scaduto"; "lng_profile_migrate_reached#one" = "Limite di {count} membro raggiunto"; "lng_profile_migrate_reached#other" = "Limite di {count} membri raggiunto"; "lng_profile_migrate_body" = "Per superare questo limite, puoi aggiornare il gruppo a supergruppo."; "lng_profile_migrate_learn_more" = "Scopri di più »"; "lng_profile_migrate_about" = "Se vuoi superare questo limite, puoi aggiornare il gruppo a supergruppo. Nei supergruppi:"; "lng_profile_migrate_feature1#one" = "— I supergruppi hanno massimo {count} membro"; "lng_profile_migrate_feature1#other" = "— I supergruppi hanno massimo {count} membri"; "lng_profile_migrate_feature2" = "— I nuovi membri vedono tutta la cronologia"; "lng_profile_migrate_feature3" = "— Gli admin eliminano i messaggi per tutti"; "lng_profile_migrate_feature4" = "— Le notifiche sono disattivate di default"; "lng_profile_migrate_button" = "Aggiorna a supergruppo"; "lng_profile_migrate_sure" = "Sicuro di voler aggiornare questo gruppo a supergruppo? Questa opzione non può essere annullata."; "lng_profile_convert_button" = "Converti in supergruppo"; "lng_profile_convert_title" = "Converti in supergruppo"; "lng_profile_convert_about" = "Nei supergruppi:"; "lng_profile_convert_feature1" = "— I nuovi membri vedono tutta la cronologia"; "lng_profile_convert_feature2" = "— I messaggi eliminati scompaiono per tutti"; "lng_profile_convert_feature3" = "— Gli admin possono fissare i messaggi importanti"; "lng_profile_convert_feature4" = "— Il creatore può creare un link pubblico per il gruppo"; "lng_profile_convert_warning" = "{bold_start}Nota:{bold_end} Questa azione non può essere annullata"; "lng_profile_convert_confirm" = "Converti"; "lng_profile_add_more_after_upgrade#one" = "Potrai aggiungere fino a {count} membro dopo aver aggiornato il tuo gruppo a supergruppo."; "lng_profile_add_more_after_upgrade#other" = "Potrai aggiungere fino a {count} membri dopo aver aggiornato il tuo gruppo a supergruppo."; "lng_channel_not_accessible" = "Spiacenti, questo canale non è accessibile."; "lng_group_not_accessible" = "Spiacenti, questo gruppo non è accessibile."; "lng_channels_too_much_public_about" = "Spiacenti, hai riservato troppi username pubblici. Puoi revocare il link da uno dei tuoi gruppi o canali più vecchi."; "lng_channels_too_much_public_revoke_confirm_group" = "Sei sicuro di voler revocare il link {link}?\n\nIl gruppo «{group}» diventerà privato."; "lng_channels_too_much_public_revoke_confirm_channel" = "Sei sicuro di voler revocare il link {link}?\n\nIl canale «{group}» diventerà privato."; "lng_channels_too_much_public_revoke" = "Revoca"; "lng_group_invite_bad_link" = "Questo link d'invito non funziona o è scaduto."; "lng_group_invite_want_join" = "Vuoi unirti al gruppo «{title}»?"; "lng_group_invite_want_join_channel" = "Vuoi unirti al canale «{title}»?"; "lng_group_invite_join" = "Unisciti"; "lng_group_invite_members#one" = "{count} membro, tra cui:"; "lng_group_invite_members#other" = "{count} membri, tra cui:"; "lng_group_invite_link" = "Link d'invito:"; "lng_group_invite_create" = "Crea un link d'invito"; "lng_group_invite_about" = "Gli utenti di Telegram potranno \nunirsi al tuo gruppo aprendo il link."; "lng_group_invite_create_new" = "Revoca link"; "lng_group_invite_about_new" = "Il tuo link precedente sarà disattivato e verrà generato un nuovo link d'invito per te."; "lng_group_invite_copied" = "Link d'invito copiato negli appunti."; "lng_group_invite_no_room" = "Impossibile unirsi a questo gruppo perché ci sono già troppi membri."; "lng_channel_public_link_copied" = "Link copiato negli appunti."; "lng_forwarded" = "Inoltrato da {user}"; "lng_forwarded_date" = "Originale: {date}"; "lng_forwarded_channel" = "Inoltrato da {channel}"; "lng_forwarded_via" = "Inoltrato da {user} via {inline_bot}"; "lng_forwarded_channel_via" = "Inoltrato da {channel} via {inline_bot}"; "lng_forwarded_signed" = "{channel} ({user})"; "lng_in_reply_to" = "In risposta a"; "lng_edited" = "modificato"; "lng_edited_date" = "Modificato: {date}"; "lng_cancel_edit_post_sure" = "Annullare la modifica?"; "lng_cancel_edit_post_yes" = "Sì"; "lng_cancel_edit_post_no" = "No"; "lng_bot_share_location_unavailable" = "Spiacenti, la condivisione della posizione non è al momento disponibile su Telegram Desktop."; "lng_bot_inline_geo_unavailable" = "Spiacenti, questo bot richiede la condivisione della posizione. Non è disponibile su Telegram Desktop."; "lng_bot_share_phone" = "Condividere il numero di telefono?"; "lng_bot_share_phone_confirm" = "Condividi"; "lng_attach_failed" = "Fallito"; "lng_attach_file" = "File"; "lng_attach_photo" = "Foto"; "lng_media_type" = "Tipo di media"; "lng_media_type_photos" = "Foto"; "lng_media_type_videos" = "Video"; "lng_media_type_songs" = "File audio"; "lng_media_type_files" = "File"; "lng_media_type_audios" = "Messaggi vocali"; "lng_media_type_links" = "Link condivisi"; "lng_media_open_with" = "Apri con"; "lng_media_download" = "Scarica"; "lng_media_cancel" = "Annulla"; "lng_media_video" = "Video"; "lng_media_audio" = "Messaggio vocale"; "lng_media_auto_settings" = "Impostazioni di download automatico"; "lng_media_auto_photo" = "Download automatico foto"; "lng_media_auto_audio" = "Download automatico messaggi vocali"; "lng_media_auto_gif" = "Download automatico GIF"; "lng_media_auto_private_chats" = "Chat private"; "lng_media_auto_groups" = "Gruppi e canali"; "lng_media_auto_play" = "Autoriproduzione"; "lng_emoji_category0" = "Usate di frequente"; "lng_emoji_category1" = "Persone"; "lng_emoji_category2" = "Natura"; "lng_emoji_category3" = "Cibo e bevande"; "lng_emoji_category4" = "Attività"; "lng_emoji_category5" = "Viaggi e luoghi"; "lng_emoji_category6" = "Oggetti"; "lng_emoji_category7" = "Simboli e bandiere"; "lng_emoji_hide_panel" = "Clicca qui per nascondere la barra delle emoji"; "lng_recent_stickers" = "Usati di frequente"; "lng_faved_stickers" = "Sticker preferiti"; "lng_faved_stickers_add" = "Aggiungi ai preferiti"; "lng_faved_stickers_remove" = "Rimuovi dai preferiti"; "lng_group_stickers" = "Sticker del gruppo"; "lng_group_stickers_description" = "Puoi rendere un set di sticker disponibile a tutti i membri che scrivono all'interno di questo gruppo."; "lng_group_stickers_add" = "Scegli set di sticker"; "lng_switch_stickers" = "Sticker"; "lng_switch_emoji" = "Emoji"; "lng_switch_gifs" = "GIF"; "lng_stickers_featured_add" = "Aggiungi"; "lng_gifs_search" = "Cerca GIF"; "lng_gifs_no_saved" = "Non hai ancora GIF salvate."; "lng_inline_bot_no_results" = "Nessun risultato."; "lng_inline_bot_via" = "via {inline_bot}"; "lng_box_remove" = "Rimuovi"; "lng_stickers_installed_tab" = "Sticker"; "lng_stickers_featured_tab" = "In primo piano"; "lng_stickers_archived_tab" = "Archiviati"; "lng_stickers_remove_pack" = "Rimuovere «{sticker_pack}»?"; "lng_stickers_add_pack" = "Aggiungi sticker"; "lng_stickers_share_pack" = "Condividi sticker"; "lng_stickers_not_found" = "Set di sticker non trovato."; "lng_stickers_packs_archived" = "Alcuni dei tuoi sticker non usati sono stati archiviati per fare spazio ai set che hai attivato."; "lng_stickers_archived" = "Sticker archiviati"; "lng_stickers_copied" = "Link degli sticker copiato negli appunti."; "lng_stickers_default_set" = "Grandi menti"; "lng_stickers_you_have" = "Organizza e riordina i set di sticker"; "lng_stickers_featured" = "Sticker in primo piano"; "lng_stickers_return" = "Annulla"; "lng_stickers_count#one" = "{count} sticker"; "lng_stickers_count#other" = "{count} sticker"; "lng_stickers_masks_pack" = "Questo è un set di maschere. Puoi usarle nell'editor fotografico sulle nostre app mobili."; "lng_stickers_group_set" = "Sticker del gruppo"; "lng_stickers_remove_group_set" = "Rimuovere il set di sticker del gruppo?"; "lng_stickers_group_from_your" = "Scegli dai tuoi sticker"; "lng_in_dlg_photo" = "Foto"; "lng_in_dlg_video" = "Video"; "lng_in_dlg_audio_file" = "File audio"; "lng_in_dlg_contact" = "Contatto"; "lng_in_dlg_audio" = "Messaggio vocale"; "lng_in_dlg_video_message" = "Videomessaggio"; "lng_in_dlg_file" = "File"; "lng_in_dlg_sticker" = "Sticker"; "lng_in_dlg_sticker_emoji" = "{emoji} Sticker"; "lng_ban_user" = "Rimuovi utente"; "lng_delete_all_from" = "Elimina tutto da questo utente"; "lng_report_spam" = "Segnala spam"; "lng_report_spam_hide" = "Nascondi"; "lng_report_spam_thanks" = "Grazie per la tua segnalazione!"; "lng_report_spam_sure" = "Sicuro di voler segnalare questo utente come spam?"; "lng_report_spam_sure_group" = "Sicuro di voler segnalare dello spam in questo gruppo?"; "lng_report_spam_sure_channel" = "Sicuro di voler segnalare dello spam in questo canale?"; "lng_report_spam_ok" = "Segnala"; "lng_cant_send_to_not_contact" = "Spiacenti, al momento puoi scrivere\nsolo ai contatti reciproci.\n{more_info}"; "lng_cant_invite_not_contact" = "Spiacenti, al momento puoi aggiungere ai gruppi\nsolo i contatti reciproci.\n{more_info}"; "lng_cant_send_to_not_contact_flood" = "Oggi hai scritto a troppi non-contatti, per favore riprova domani. Potrai rispondere oggi se questo utente ti scrive per primo."; "lng_cant_invite_not_contact_flood" = "Non puoi aggiungere questo utente perché hai scritto a troppi non-contatti oggi. Per favore riprova domani. Puoi chiedere a un altro membro di aggiungere questo utente al gruppo. "; "lng_cant_more_info" = "Maggiori info »"; "lng_cant_invite_banned" = "Spiacenti, solo l'amministratore può aggiungere questo utente."; "lng_cant_invite_privacy" = "Spiacenti, non puoi aggiungere questo utente ai gruppi a causa delle sue impostazioni di privacy."; "lng_cant_invite_privacy_channel" = "Spiacenti, non puoi aggiungere questo utente ai canali a causa delle sue impostazioni di privacy."; "lng_cant_delete_group#one" = "Spiacenti, al momento non è possibile eliminare manualmente gruppi con più di {count} membro. Per favore contatta l'assistenza di Telegram se vuoi eliminare questo gruppo."; "lng_cant_delete_group#other" = "Spiacenti, al momento non è possibile eliminare manualmente gruppi con più di {count} membri. Per favore contatta l'assistenza di Telegram se vuoi eliminare questo gruppo."; "lng_cant_delete_channel#one" = "Spiacenti, al momento non è possibile eliminare manualmente canali con più di {count} membro. Per favore contatta l'assistenza di Telegram se vuoi eliminare questo canale."; "lng_cant_delete_channel#other" = "Spiacenti, al momento non è possibile eliminare manualmente canali con più di {count} membri. Per favore contatta l'assistenza di Telegram se vuoi eliminare questo canale."; "lng_cant_do_this" = "Spiacenti, questa azione non è disponibile."; "lng_send_button" = "Invia"; "lng_message_ph" = "Scrivi un messaggio..."; "lng_broadcast_ph" = "Pubblica un post..."; "lng_broadcast_silent_ph" = "Post silenzioso..."; "lng_record_cancel" = "Rilascia fuori per annullare"; "lng_will_be_notified" = "I post saranno notificati ai membri"; "lng_wont_be_notified" = "I post non saranno notificati ai membri"; "lng_willbe_history" = "Seleziona una chat per iniziare a messaggiare"; "lng_from_you" = "Tu"; "lng_from_draft" = "Bozza"; "lng_bot_description" = "Cosa può fare questo bot?"; "lng_unblock_button" = "Sblocca"; "lng_channel_join" = "Unisciti al canale"; "lng_channel_mute" = "Silenzia"; "lng_channel_unmute" = "Suona"; "lng_dialogs_text_with_from" = "{from_part} {message}"; "lng_dialogs_text_from_wrapped" = "{from}:"; "lng_dialogs_text_media" = "{media_part} {caption}"; "lng_dialogs_text_media_wrapped" = "{media},"; "lng_dialogs_show_all_chats" = "Mostra tutte le chat"; "lng_dialogs_hide_muted_chats" = "Nascondi chat silenziate"; "lng_open_this_link" = "Aprire questo link?"; "lng_open_link" = "Apri"; "lng_allow_bot_pass" = "Consentire a {bot_name} di trasmettere il tuo nome e id Telegram alle pagine web che apri tramite questo bot?"; "lng_allow_bot" = "Consenti"; "lng_bot_start" = "Avvia"; "lng_bot_choose_group" = "Seleziona un gruppo"; "lng_bot_no_groups" = "Non hai gruppi"; "lng_bot_groups_not_found" = "Nessun gruppo trovato"; "lng_bot_sure_invite" = "Aggiungere il bot a «{group}»?"; "lng_bot_already_in_group" = "Questo bot è già membro del gruppo."; "lng_bot_choose_chat" = "Seleziona una chat"; "lng_bot_no_chats" = "Non hai chat"; "lng_bot_chats_not_found" = "Nessuna chat trovata"; "lng_bot_sure_share_game" = "Condividere questo gioco con {user}?"; "lng_bot_sure_share_game_group" = "Condividere questo gioco con «{group}»?"; "lng_typing" = "sta scrivendo"; "lng_user_typing" = "{user} sta scrivendo"; "lng_users_typing" = "{user} e {second_user} stanno scrivendo"; "lng_many_typing#one" = "{count} persona sta scrivendo"; "lng_many_typing#other" = "{count} persone stanno scrivendo"; "lng_playing_game" = "sta giocando a un gioco"; "lng_user_playing_game" = "{user} sta giocando a un gioco"; "lng_users_playing_game" = "{user} e {second_user} stanno giocando a un gioco"; "lng_many_playing_game#one" = "{count} persona sta giocando a un gioco"; "lng_many_playing_game#other" = "{count} persone stanno giocando a un gioco"; "lng_send_action_record_video" = "sta registrando un video"; "lng_user_action_record_video" = "{user} sta registrando un video"; "lng_send_action_upload_video" = "sta inviando un video"; "lng_user_action_upload_video" = "{user} sta inviando un video"; "lng_send_action_record_audio" = "sta registrando un audio"; "lng_user_action_record_audio" = "{user} sta registrando un audio"; "lng_send_action_upload_audio" = "sta inviando un audio"; "lng_user_action_upload_audio" = "{user} sta inviando un audio"; "lng_send_action_record_round" = "sta registrando un videomessaggio"; "lng_user_action_record_round" = "{user} sta registrando un videomessaggio"; "lng_send_action_upload_round" = "sta inviando un videomessaggio"; "lng_user_action_upload_round" = "{user} sta inviando un videomessaggio"; "lng_send_action_upload_photo" = "sta inviando una foto"; "lng_user_action_upload_photo" = "{user} sta inviando una foto"; "lng_send_action_upload_file" = "sta inviando un file"; "lng_user_action_upload_file" = "{user} sta inviando un file"; "lng_send_action_geo_location" = "sta scegliendo una posizione"; "lng_user_action_geo_location" = "{user} sta scegliendo una posizione"; "lng_send_action_choose_contact" = "scegliendo un contatto"; "lng_user_action_choose_contact" = "{user} sta scegliendo un contatto"; "lng_unread_bar#one" = "{count} messaggio non letto"; "lng_unread_bar#other" = "{count} messaggi non letti"; "lng_maps_point" = "Posizione"; "lng_save_photo" = "Salva immagine"; "lng_save_video" = "Salva video"; "lng_save_audio_file" = "Salva file audio"; "lng_save_audio" = "Salva messaggio vocale"; "lng_save_file" = "Salva file"; "lng_save_downloaded" = "{ready} / {total} {mb}"; "lng_duration_and_size" = "{duration}, {size}"; "lng_duration_played" = "{played} / {duration}"; "lng_date_and_duration" = "{date}, {duration}"; "lng_choose_image" = "Scegli un'immagine"; "lng_choose_files" = "Scegli file"; "lng_game_tag" = "Gioco"; "lng_context_view_profile" = "Visualizza profilo"; "lng_context_view_group" = "Visualizza info gruppo"; "lng_context_view_channel" = "Visualizza info canale"; "lng_context_pin_to_top" = "Fissa in alto"; "lng_context_unpin_from_top" = "Togli dall'alto"; "lng_context_promote_admin" = "Rendi amministratore"; "lng_context_edit_permissions" = "Modifica autorizzazioni"; "lng_context_restrict_user" = "Limita utente"; "lng_context_remove_from_group" = "Rimuovi dal gruppo"; "lng_context_copy_link" = "Copia link"; "lng_context_copy_post_link" = "Copia link post"; "lng_context_copy_email" = "Copia indirizzo email"; "lng_context_copy_hashtag" = "Copia hashtag"; "lng_context_copy_mention" = "Copia username"; "lng_context_save_image" = "Salva immagine come..."; "lng_context_forward_image" = "Inoltra immagine"; "lng_context_delete_image" = "Elimina immagine"; "lng_context_copy_image" = "Copia immagine"; "lng_context_close_image" = "Chiudi immagine"; "lng_context_cancel_download" = "Annulla download"; "lng_context_show_in_folder" = "Mostra nella cartella"; "lng_context_show_in_finder" = "Mostra nel Finder"; "lng_context_save_video" = "Salva video come..."; "lng_context_save_audio_file" = "Salva file audio come..."; "lng_context_save_audio" = "Salva messaggio vocale come..."; "lng_context_pack_info" = "Mostra sticker"; "lng_context_pack_add" = "Aggiungi sticker"; "lng_context_save_file" = "Salva file come..."; "lng_context_forward_file" = "Inoltra file"; "lng_context_delete_file" = "Elimina file"; "lng_context_close_file" = "Chiudi file"; "lng_context_copy_text" = "Copia testo"; "lng_context_open_gif" = "Apri GIF"; "lng_context_save_gif" = "Salva GIF"; "lng_context_to_msg" = "Vai al messaggio"; "lng_context_reply_msg" = "Rispondi"; "lng_context_edit_msg" = "Modifica"; "lng_context_forward_msg" = "Inoltra messaggio"; "lng_context_delete_msg" = "Elimina messaggio"; "lng_context_select_msg" = "Seleziona messaggio"; "lng_context_pin_msg" = "Fissa messaggio"; "lng_context_unpin_msg" = "Togli messaggio"; "lng_context_cancel_upload" = "Annulla caricamento"; "lng_context_copy_selected" = "Copia testo selezionato"; "lng_context_copy_selected_items" = "Copia selezione come testo"; "lng_context_forward_selected" = "Inoltra selezione"; "lng_context_delete_selected" = "Elimina selezione"; "lng_context_clear_selection" = "Annulla selezione"; "lng_really_send_image" = "Vuoi inviare questa immagine?"; "lng_really_send_file" = "Vuoi inviare questo file?"; "lng_really_share_contact" = "Vuoi condividere questo contatto?"; "lng_send_images_compress#one" = "Comprimi immagine"; "lng_send_images_compress#other" = "Comprimi immagini"; "lng_send_image_non_local" = "Impossibile inviare un file non locale: {name}"; "lng_send_image_empty" = "Impossibile inviare un file vuoto: {name}"; "lng_send_image_too_large" = "Impossibile inviare il file, perché è più grande di 1500 MB: {name}"; "lng_send_folder" = "Impossibile inviare «{name}» perché è una cartella :("; "lng_send_images_selected#one" = "{count} immagine selezionata"; "lng_send_images_selected#other" = "{count} immagini selezionate"; "lng_send_photos#one" = "Invia {count} foto"; "lng_send_photos#other" = "Invia {count} foto"; "lng_send_files_selected#one" = "{count} file selezionato"; "lng_send_files_selected#other" = "{count} file selezionati"; "lng_send_files#one" = "Invia {count} file"; "lng_send_files#other" = "Invia {count} file"; "lng_forward_choose" = "Scegli destinatario..."; "lng_forward_cant" = "Spiacenti, impossibile inoltrare qui :("; "lng_forward_confirm" = "Inoltra a {recipient}?"; "lng_forward_share_contact" = "Condividi contatto con {recipient}?"; "lng_forward_share_cant" = "Spiacenti, non è possibile condividere contatti qui :("; "lng_forward_send_file_confirm" = "Invia «{name}» a {recipient}?"; "lng_forward_send_files_confirm" = "Invia i file selezionati a {recipient}?"; "lng_forward_send_files_cant" = "Spiacenti, non è possibile inviare media qui :("; "lng_forward_send" = "Invia"; "lng_forward_messages#one" = "{count} messaggio inoltrato"; "lng_forward_messages#other" = "{count} messaggi inoltrati"; "lng_forwarding_from#one" = "{user} e {count} altro"; "lng_forwarding_from#other" = "{user} e {count} altri"; "lng_forwarding_from_two" = "{user} e {second_user}"; "lng_inline_switch_choose" = "Scegli conversazione..."; "lng_inline_switch_cant" = "Spiacenti, impossibile scrivere qui :("; "lng_share_cant" = "Spiacenti, impossibile condividere qui :("; "lng_reply_cant" = "Spiacenti, non si può rispondere a un vecchio messaggio nel supergruppo :("; "lng_reply_cant_forward" = "Spiacenti, non si può rispondere a un vecchio messaggio nel supergruppo :( Vuoi inoltrarlo e aggiungere un tuo commento?"; "lng_share_title" = "Condividi"; "lng_share_copy_link" = "Copia link"; "lng_share_confirm" = "Invia"; "lng_share_wrong_user" = "Questo gioco è stato aperto da un altro utente."; "lng_share_game_link_copied" = "Link del gioco copiato negli appunti."; "lng_share_done" = "Fatto!"; "lng_contact_phone" = "Numero di telefono"; "lng_enter_contact_data" = "Nuovo contatto"; "lng_edit_group_title" = "Modifica il nome del gruppo"; "lng_edit_contact_title" = "Modifica nome contatto"; "lng_edit_channel_title" = "Modifica canale"; "lng_edit_sign_messages" = "Firma messaggi"; "lng_edit_group_who_invites" = "Chi può aggiungere membri"; "lng_edit_group_invites_everybody" = "Tutti i membri"; "lng_edit_group_invites_only_admins" = "Solo gli amministratori"; "lng_edit_group" = "Modifica gruppo"; "lng_edit_self_title" = "Modifica il tuo nome"; "lng_confirm_contact_data" = "Nuovo contatto"; "lng_add_contact" = "Crea"; "lng_add_contact_button" = "Nuovo contatto"; "lng_contacts_header" = "Contatti"; "lng_contact_not_joined" = "Sfortunatamente {name} non si è ancora unito a Telegram, ma puoi invitarlo.\n\nTi notificheremo per ogni tuo contatto che si unisce a Telegram."; "lng_try_other_contact" = "Prova un altro"; "lng_create_group_link" = "Link"; "lng_create_group_invite_link" = "Link d'invito"; "lng_create_group_photo" = "Imposta foto"; "lng_create_group_description" = "Descrizione (opzionale)"; "lng_drag_images_here" = "Trascina qui le immagini"; "lng_drag_photos_here" = "Trascina qui le foto"; "lng_drag_files_here" = "Trascina qui i file"; "lng_drag_to_send_quick" = "per inviarle in modo veloce"; "lng_drag_to_send_no_compression" = "per inviarle senza compressione"; "lng_drag_to_send_files" = "per inviarli come file"; "lng_selected_clear" = "Annulla"; "lng_selected_delete" = "Elimina"; "lng_selected_forward" = "Inoltra"; "lng_selected_count#one" = "{count} messaggio"; "lng_selected_count#other" = "{count} messaggi"; "lng_selected_cancel_sure_this" = "Annullare il caricamento?"; "lng_selected_upload_stop" = "Arresta "; "lng_selected_delete_sure_this" = "Vuoi eliminare questo messaggio?"; "lng_selected_delete_sure#one" = "Vuoi eliminare {count} messaggio?"; "lng_selected_delete_sure#other" = "Vuoi eliminare {count} messaggi?"; "lng_delete_photo_sure" = "Vuoi eliminare questa foto?"; "lng_delete_for_everyone_hint#one" = "Questo lo eliminerà per chiunque in questa chat."; "lng_delete_for_everyone_hint#other" = "Questo li eliminerà per chiunque in questa chat."; "lng_delete_for_me_chat_hint#one" = "Questo lo eliminerà solo per te, non per gli altri membri della chat."; "lng_delete_for_me_chat_hint#other" = "Questo li eliminerà solo per te, non per gli altri membri della chat."; "lng_delete_for_me_hint#one" = "Questo lo eliminerà solo per te."; "lng_delete_for_me_hint#other" = "Questo li eliminerà solo per te."; "lng_delete_for_everyone_check" = "Elimina per tutti"; "lng_delete_for_other_check" = "Elimina per {user}"; "lng_box_delete" = "Elimina"; "lng_box_leave" = "Lascia"; "lng_about_version" = "versione {version}"; "lng_about_text_1" = "App ufficiale basata sulle [a href=\"https://core.telegram.org/api\"]API di Telegram[/a]\nper offrire velocità e sicurezza."; "lng_about_text_2" = "Questo software è sotto licenza [a href=\"https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE\"]GNU GPL[/a] versione 3.\nIl source code è disponibile su [a href=\"https://github.com/telegramdesktop/tdesktop\"]GitHub[/a]."; "lng_about_text_3" = "Visita le {faq_open}domande frequenti{faq_close} per maggiori info."; "lng_about_done" = "Fatto"; "lng_search_no_results" = "Nessun messaggio trovato"; "lng_search_found_results#one" = "Trovato {count} messaggio"; "lng_search_found_results#other" = "Trovati {count} messaggi"; "lng_search_global_results" = "Risultati ricerca globale"; "lng_search_messages_from" = "Mostra messaggi da"; "lng_media_save_progress" = "{ready} di {total} {mb}"; "lng_mediaview_save_as" = "Salva come..."; "lng_mediaview_copy" = "Copia"; "lng_mediaview_forward" = "Inoltra"; "lng_mediaview_delete" = "Elimina"; "lng_mediaview_photos_all" = "Visualizza tutte le foto"; "lng_mediaview_files_all" = "Visualizza tutti i file"; "lng_mediaview_single_photo" = "Foto singola"; "lng_mediaview_group_photo" = "Foto del gruppo"; "lng_mediaview_channel_photo" = "Foto del canale"; "lng_mediaview_profile_photo" = "Foto profilo"; "lng_mediaview_file_n_of_count" = "{file} {n} di {count}"; "lng_mediaview_n_of_count" = "Foto {n} di {count}"; "lng_mediaview_doc_image" = "File"; "lng_mediaview_today" = "oggi alle {time}"; "lng_mediaview_yesterday" = "ieri alle {time}"; "lng_mediaview_date_time" = "{date} alle {time}"; "lng_mediaview_saved" = "L'immagine è stata salvata nella tua cartella [c]Download[/c]"; "lng_theme_preview_title" = "Anteprima tema"; "lng_theme_preview_generating" = "Genero anteprima tema..."; "lng_theme_preview_invalid" = "Dati non validi in questo file tema."; "lng_theme_preview_apply" = "Applica questo tema"; "lng_new_authorization" = "{name},\nAbbiamo rilevato un accesso al tuo account da un nuovo dispositivo {day}, {date} alle {time}\n\nDispositivo: {device}\nPosizione: {location}\n\nSe non sei tu, puoi andare nelle Impostazioni – Mostra tutte le sessioni e terminare quella sessione.\n\nSe credi che qualcuno si sia collegato al tuo account contro il tuo volere, puoi attivare la verifica in due passaggi nelle Impostazioni. \n\nCordiali saluti, \nil team di Telegram"; "lng_new_version_wrap" = "Telegram Desktop si è aggiornato alla versione {version}\n\n{changes}\n\nLa cronologia degli aggiornamenti è disponibile qui:\n{link}"; "lng_new_version_minor" = "— Risoluzione di problemi e altri miglioramenti minori"; "lng_new_version_text" = "— Videomessaggi migliorati: barra del progresso attorno al video durante la riproduzione, supporto picture-in-picture, conto alla rovescia per la durata.\n— Ora i messaggi vocali e i videomessaggi vengono riprodotti uno dopo l'altro"; "lng_menu_insert_unicode" = "Inserisci carattere di controllo Unicode"; "lng_full_name" = "{first_name} {last_name}"; "lng_confirm_phone_link_invalid" = "Questo link non funziona o è scaduto."; "lng_confirm_phone_title" = "Annulla ripristino account"; "lng_confirm_phone_about" = "Qualcuno con accesso al tuo numero di telefono {phone} ha richiesto l'eliminazione del tuo account Telegram e il ripristino della password della verifica in due passaggi.\n\nSe non sei stato tu, per favore inserisci il codice che abbiamo appena inviato tramite SMS al tuo numero."; "lng_confirm_phone_success" = "Fatto!\n\nIl processo di eliminazione per il tuo account {phone} è stato annullato. Puoi chiudere questa finestra ora."; "lng_confirm_phone_send" = "Invia"; "lng_confirm_phone_enter_code" = "Per favore inserisci il codice."; "lng_theme_editor_no_keys" = "Ancora nessun valore nella tavolozza"; "lng_theme_editor_cant_change_theme" = "Non puoi applicare nuovi temi mentre modifichi la tavolozza dei colori. Per favore chiudi prima l'editor dei temi."; "lng_theme_editor_new_keys" = "Non ancora nella tavolozza"; "lng_theme_editor_background_image" = "Immagine di sfondo"; "lng_theme_editor_saved_to_jpg" = "Salvato in JPEG, {size}"; "lng_theme_editor_read_from_jpg" = "Immagine JPEG, {size}"; "lng_theme_editor_read_from_png" = "Immagine PNG, {size}"; "lng_theme_editor_export" = "Esporta"; "lng_theme_editor_choose_image" = "Scegli immagine di sfondo"; "lng_theme_editor_save_palette" = "Salva file tavolozza"; "lng_theme_editor_choose_name" = "Salva file tema"; "lng_theme_editor_error" = "L'editor ha rilevato un errore :( Controlla 'log.txt' per i dettagli."; "lng_theme_editor_done" = "Tema esportato con successo!"; "lng_theme_editor_title" = "Modifica tavolozza dei colori"; "lng_theme_editor_export_button" = "Esporta tema"; "lng_payments_not_supported" = "Spiacenti, Telegram Desktop non supporta i pagamenti al momento. Per favore usa una delle nostre app mobili per fare questo."; "lng_payments_receipt_label" = "Ricevuta"; "lng_payments_receipt_label_test" = "Ricevuta di test"; "lng_payments_invoice_label" = "Fattura"; "lng_payments_invoice_label_test" = "Fattura di test"; "lng_payments_receipt_button" = "Ricevuta"; "lng_call_status_incoming" = "ti sta chiamando..."; "lng_call_status_connecting" = "connetto..."; "lng_call_status_exchanging" = "scambio chiavi di crittografia..."; "lng_call_status_waiting" = "attendo..."; "lng_call_status_requesting" = "richiedo..."; "lng_call_status_hanging" = "riaggancio..."; "lng_call_status_ended" = "chiamata terminata"; "lng_call_status_failed" = "connessione fallita"; "lng_call_status_ringing" = "suono..."; "lng_call_status_busy" = "occupato"; "lng_call_fingerprint_tooltip" = "Se le emoji sono uguali sullo schermo di {user}, la chiamata è sicura al 100%."; "lng_call_error_not_available" = "Spiacenti, {user} non accetta le chiamate."; "lng_call_error_incompatible" = "L'app di {user} sta usando un protocollo non compatibile. Deve aggiornare la sua app prima che tu possa chiamarlo."; "lng_call_error_outdated" = "L'app di {user} non supporta le chiamate. Deve aggiornare la sua app prima che tu possa chiamarlo."; "lng_call_error_audio_io" = "Sembra esserci un problema con la riproduzione audio del tuo computer. Per favore assicurati che gli altoparlanti e il microfono del tuo computer stiano funzionando e riprova."; "lng_call_bar_hangup" = "Termina chiamata"; "lng_call_box_title" = "Chiamate"; "lng_call_box_about" = "Non hai ancora effettuato alcuna chiamata."; "lng_call_box_status_today" = "{time}"; "lng_call_box_status_yesterday" = "Ieri alle {time}"; "lng_call_box_status_date" = "{date} alle {time}"; "lng_call_box_status_group" = "({count}) {status}"; "lng_call_outgoing" = "Chiamata in uscita"; "lng_call_incoming" = "Chiamata in entrata"; "lng_call_missed" = "Chiamata persa"; "lng_call_cancelled" = "Chiamata annullata"; "lng_call_declined" = "Chiamata rifiutata"; "lng_call_duration_info" = "{time}, {duration}"; "lng_call_type_and_duration" = "{type} ({duration})"; "lng_call_rate_label" = "Per favore valuta la tua chiamata"; "lng_call_rate_comment" = "Commento (opzionale)"; "lng_player_message_today" = "Oggi alle {time}"; "lng_player_message_yesterday" = "Ieri alle {time}"; "lng_player_message_date" = "{date} alle {time}"; "lng_rights_edit_admin" = "Modifica amministratore"; "lng_rights_edit_admin_header" = "Cosa può fare questo amministratore?"; "lng_rights_about_add_admins_yes" = "Questo amministratore sarà in grado di aggiungere nuovi amministratori con le stesse autorizzazioni (o più limitate) ."; "lng_rights_about_add_admins_no" = "Questo amministratore non sarà in grado di aggiungere nuovi amministratori."; "lng_rights_about_admin_cant_edit" = "Non puoi modificare le autorizzazioni di questo amministratore."; "lng_rights_user_restrictions" = "Restrizioni utente"; "lng_rights_user_restrictions_header" = "Cosa può fare questo utente?"; "lng_rights_channel_info" = "Cambiare le info del canale"; "lng_rights_channel_post" = "Pubblicare messaggi"; "lng_rights_channel_edit" = "Modificare i messaggi di altri"; "lng_rights_channel_delete" = "Eliminare i messaggi di altri"; "lng_rights_group_info" = "Cambiare le info del gruppo"; "lng_rights_group_ban" = "Bloccare utenti"; "lng_rights_group_invite_link" = "Invitare utenti tramite link"; "lng_rights_group_invite" = "Aggiungere utenti"; "lng_rights_group_pin" = "Fissare messaggi"; "lng_rights_group_delete" = "Eliminare messaggi"; "lng_rights_add_admins" = "Aggiungere amministratori"; "lng_rights_chat_read" = "Leggere messaggi"; "lng_rights_chat_send_text" = "Inviare messaggi"; "lng_rights_chat_send_media" = "Inviare media"; "lng_rights_chat_send_stickers" = "Inviare sticker e GIF"; "lng_rights_chat_send_links" = "Inviare link con anteprima"; "lng_rights_chat_banned_until_header" = "Limitato fino a"; "lng_rights_chat_banned_forever" = "Sempre"; "lng_rights_chat_banned_day#one" = "Per {count} giorno"; "lng_rights_chat_banned_day#other" = "Per {count} giorni"; "lng_rights_chat_banned_week#one" = "Per {count} settimana"; "lng_rights_chat_banned_week#other" = "Per {count} settimane"; "lng_rights_chat_banned_custom" = "Personalizzato"; "lng_rights_chat_banned_custom_date" = "Fino a {date}"; "lng_rights_chat_banned_block" = "Blocca e rimuovi dal gruppo"; "lng_restricted_send_message" = "Gli amministratori di questo gruppo ti hanno vietato di scrivere qui."; "lng_restricted_send_media" = "Spiacenti, gli amministratori di questo gruppo ti hanno vietato di inviare media."; "lng_restricted_send_stickers" = "Spiacenti, gli amministratori di questo gruppo ti hanno vietato di inviare sticker."; "lng_restricted_send_gifs" = "Spiacenti, gli amministratori di questo gruppo ti hanno vietato di inviare GIF."; "lng_restricted_send_inline" = "Gli amministratori di questo gruppo ti hanno vietato di inviare contenuti inline qui."; "lng_restricted_list_title" = "Utenti limitati"; "lng_banned_list_title" = "Utenti bloccati"; "lng_admin_log_title_all" = "Tutte le azioni"; "lng_admin_log_title_selected" = "Azioni selezionate"; "lng_admin_log_filter" = "Filtro"; "lng_admin_log_filter_title" = "Filtro"; "lng_admin_log_filter_all_actions" = "Tutte le azioni"; "lng_admin_log_filter_restrictions" = "Nuove restrizioni"; "lng_admin_log_filter_admins_new" = "Nuovi amministratori"; "lng_admin_log_filter_members_new" = "Nuovi membri"; "lng_admin_log_filter_info_group" = "Info gruppo"; "lng_admin_log_filter_info_channel" = "Info canale"; "lng_admin_log_filter_messages_deleted" = "Messaggi eliminati"; "lng_admin_log_filter_messages_edited" = "Messaggi modificati"; "lng_admin_log_filter_messages_pinned" = "Messaggi fissati"; "lng_admin_log_filter_members_removed" = "Membri rimossi"; "lng_admin_log_filter_all_admins" = "Tutti gli utenti e amministratori"; "lng_admin_log_about" = "Cos'è questo?"; "lng_admin_log_about_text" = "Questa è una lista di tutte le azioni di servizio eseguite dai membri e dagli amministratori del gruppo nelle ultime 48 ore."; "lng_admin_log_no_results_title" = "Nessuna azione trovata"; "lng_admin_log_no_results_text" = "Non sono stati trovate azioni recenti che rispondono alla tua richiesta."; "lng_admin_log_no_results_search_text" = "Non sono state trovate azioni recenti contenenti '{query}'."; "lng_admin_log_no_events_title" = "Ancora nessuna azione"; "lng_admin_log_no_events_text" = "Non ci sono state azioni di servizio\nda parte dei membri e degli amministratori\ndel gruppo nelle ultime 48 ore."; "lng_admin_log_empty_text" = "Vuota"; "lng_admin_log_changed_title_group" = "{from} ha cambiato il nome del gruppo in «{title}»"; "lng_admin_log_changed_title_channel" = "{from} ha cambiato il nome del canale in «{title}»"; "lng_admin_log_changed_description_group" = "{from} ha cambiato la descrizione del gruppo:"; "lng_admin_log_removed_description_group" = "{from} ha rimosso la descrizione del gruppo"; "lng_admin_log_changed_description_channel" = "{from} ha cambiato la descrizione del canale:"; "lng_admin_log_removed_description_channel" = "{from} ha rimosso la descrizione del canale"; "lng_admin_log_previous_description" = "Descrizione precedente"; "lng_admin_log_changed_link_group" = "{from} ha cambiato il link del gruppo:"; "lng_admin_log_removed_link_group" = "{from} ha rimosso il link del gruppo"; "lng_admin_log_changed_link_channel" = "{from} ha cambiato il link del canale:"; "lng_admin_log_removed_link_channel" = "{from} ha rimosso il link del canale"; "lng_admin_log_previous_link" = "Link precedente"; "lng_admin_log_changed_photo_group" = "{from} ha cambiato la foto del gruppo"; "lng_admin_log_changed_photo_channel" = "{from} ha cambiato la foto del canale"; "lng_admin_log_removed_photo_group" = "{from} ha rimosso la foto del gruppo"; "lng_admin_log_removed_photo_channel" = "{from} ha rimosso la foto del canale"; "lng_admin_log_invites_enabled" = "{from} ha attivato gli inviti del gruppo"; "lng_admin_log_invites_disabled" = "{from} ha disattivato gli inviti del gruppo"; "lng_admin_log_signatures_enabled" = "{from} ha attivato le firme"; "lng_admin_log_signatures_disabled" = "{from} ha disattivato le firme"; "lng_admin_log_pinned_message" = "{from} ha fissato un messaggio:"; "lng_admin_log_unpinned_message" = "{from} ha tolto un messaggio"; "lng_admin_log_edited_caption" = "{from} ha modificato la didascalia:"; "lng_admin_log_removed_caption" = "{from} ha rimosso la didascalia"; "lng_admin_log_previous_caption" = "Didascalia originale"; "lng_admin_log_edited_message" = "{from} ha modificato un messaggio:"; "lng_admin_log_previous_message" = "Messaggio originale"; "lng_admin_log_deleted_message" = "{from} ha eliminato un messaggio:"; "lng_admin_log_participant_joined" = "{from} si è unito al gruppo"; "lng_admin_log_participant_joined_channel" = "{from} si è unito al canale"; "lng_admin_log_participant_left" = "{from} ha lasciato il gruppo"; "lng_admin_log_participant_left_channel" = "{from} ha lasciato il canale"; "lng_admin_log_invited" = "ha invitato {user}"; "lng_admin_log_banned" = "ha bloccato {user}"; "lng_admin_log_restricted" = "ha cambiato le restrizioni di {user} {until}"; "lng_admin_log_promoted" = "ha cambiato i privilegi di {user}"; "lng_admin_log_changed_stickers_group" = "{from} ha cambiato il {sticker_set} del gruppo"; "lng_admin_log_changed_stickers_set" = "set di sticker"; "lng_admin_log_removed_stickers_group" = "{from} ha rimosso il set di sticker del gruppo"; "lng_admin_log_user_with_username" = "{name} ({mention})"; "lng_admin_log_restricted_forever" = "indefinitamente"; "lng_admin_log_restricted_until" = "fino a {date}"; "lng_admin_log_banned_view_messages" = "Leggere messaggi"; "lng_admin_log_banned_send_messages" = "Inviare messaggi"; "lng_admin_log_banned_send_media" = "Inviare media"; "lng_admin_log_banned_send_stickers" = "Inviare sticker e GIF"; "lng_admin_log_banned_embed_links" = "Inviare link con anteprima"; "lng_admin_log_admin_change_info" = "Cambiare le info"; "lng_admin_log_admin_post_messages" = "Pubblicare messaggi"; "lng_admin_log_admin_edit_messages" = "Modificare messaggi"; "lng_admin_log_admin_delete_messages" = "Eliminare messaggi"; "lng_admin_log_admin_ban_users" = "Bloccare utenti"; "lng_admin_log_admin_invite_users" = "Aggiungere utenti"; "lng_admin_log_admin_invite_link" = "Invitare utenti tramite link"; "lng_admin_log_admin_pin_messages" = "Fissare messaggi"; "lng_admin_log_admin_add_admins" = "Aggiungere amministratori"; // Not used "lng_topbar_info" = "Info"; "lng_profile_group_info" = "Info gruppo"; "lng_profile_channel_info" = "Info canale"; "lng_channel_add_admins" = "Nuovo amministratore"; "lng_dlg_search_chat" = "Cerca in questa chat"; "lng_dlg_search_channel" = "Cerca in questo canale"; // Wnd specific "lng_wnd_choose_program_menu" = "Scegli programma predefinito..."; "lng_wnd_menu_undo" = "Annulla"; "lng_wnd_menu_redo" = "Ripeti"; // Linux specific "lng_linux_menu_undo" = "Annulla"; "lng_linux_menu_redo" = "Ripeti"; // Mac specific "lng_mac_choose_program_menu" = "Altro..."; "lng_mac_choose_app" = "Scegli applicazione"; "lng_mac_choose_text" = "Scegli un'applicazione per aprire il file \"{file}\"."; "lng_mac_enable_filter" = "Attiva:"; "lng_mac_recommended_apps" = "Applicazioni consigliate"; "lng_mac_all_apps" = "Tutte le applicazioni"; "lng_mac_always_open_with" = "Apri sempre con"; "lng_mac_this_app_can_open" = "Questa applicazione può aprire \"{file}\"."; "lng_mac_not_known_app" = "Non si sa se questa applicazione può aprire \"{file}\"."; "lng_mac_menu_services" = "Servizi"; "lng_mac_menu_hide_telegram" = "Nascondi {telegram}"; "lng_mac_menu_hide_others" = "Nascondi altre"; "lng_mac_menu_show_all" = "Mostra tutte"; "lng_mac_menu_preferences" = "Preferenze..."; "lng_mac_menu_quit_telegram" = "Esci da {telegram}"; "lng_mac_menu_about_telegram" = "Informazioni su {telegram}"; "lng_mac_menu_file" = "File"; "lng_mac_menu_logout" = "Esci"; "lng_mac_menu_edit" = "Modifica"; "lng_mac_menu_undo" = "Annulla"; "lng_mac_menu_redo" = "Ripeti"; "lng_mac_menu_cut" = "Taglia"; "lng_mac_menu_copy" = "Copia"; "lng_mac_menu_paste" = "Incolla"; "lng_mac_menu_delete" = "Elimina"; "lng_mac_menu_select_all" = "Seleziona tutto"; "lng_mac_menu_window" = "Finestra"; "lng_mac_menu_contacts" = "Contatti"; "lng_mac_menu_add_contact" = "Aggiungi contatto"; "lng_mac_menu_new_group" = "Nuovo gruppo"; "lng_mac_menu_new_channel" = "Nuovo canale"; "lng_mac_menu_show" = "Mostra Telegram"; // Keys finished tdesktop-1.2.17/Telegram/Resources/langs/lang_ko.strings000066400000000000000000002564651326245125100233250ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ "lng_language_name" = "영어"; "lng_switch_to_this" = "영어로 변경"; "lng_menu_contacts" = "주소록"; "lng_menu_calls" = "전화"; "lng_menu_settings" = "설정"; "lng_menu_about" = "정보"; "lng_menu_update" = "업데이트"; "lng_menu_restart" = "재시작"; "lng_menu_back" = "뒤로가기"; "lng_menu_night_mode" = "야간 모드"; "lng_disable_notifications_from_tray" = "알림 해제"; "lng_enable_notifications_from_tray" = "알림 사용"; "lng_open_from_tray" = "텔레그램 실행"; "lng_minimize_to_tray" = "창을 최소화"; "lng_quit_from_tray" = "텔레그램 종료"; "lng_tray_icon_text" = "텔레그램은 아직 실행중입니다. \n설정 화면에서 실행설정을 변경하실 수 있습니다. \n트레이메뉴에서 이 아이콘이 사라질 경우. \n숨겨진 아이콘 목록에서 다시 복원할 수 있습니다."; "lng_month1" = "1월"; "lng_month2" = "2월"; "lng_month3" = "3월"; "lng_month4" = "4월"; "lng_month5" = "5월"; "lng_month6" = "6월"; "lng_month7" = "7월"; "lng_month8" = "8월"; "lng_month9" = "9월"; "lng_month10" = "10월"; "lng_month11" = "11월"; "lng_month12" = "12월"; "lng_month1_small" = "1월"; "lng_month2_small" = "2월"; "lng_month3_small" = "3월"; "lng_month4_small" = "4월"; "lng_month5_small" = "5월"; "lng_month6_small" = "6월"; "lng_month7_small" = "7월"; "lng_month8_small" = "8월"; "lng_month9_small" = "9월"; "lng_month10_small" = "10월"; "lng_month11_small" = "11월"; "lng_month12_small" = "12월"; "lng_weekday1" = "월"; "lng_weekday2" = "화"; "lng_weekday3" = "수"; "lng_weekday4" = "목"; "lng_weekday5" = "금"; "lng_weekday6" = "토"; "lng_weekday7" = "일"; "lng_weekday1_full" = "월요일"; "lng_weekday2_full" = "화요일"; "lng_weekday3_full" = "수요일"; "lng_weekday4_full" = "목요일"; "lng_weekday5_full" = "금요일"; "lng_weekday6_full" = "토요일"; "lng_weekday7_full" = "일요일"; "lng_month_day" = "{month} {day}일"; "lng_month_day_year" = "{month} {day}, {year}"; "lng_month_year" = "{month} {year}"; "lng_box_ok" = "확인"; "lng_cancel" = "취소"; "lng_continue" = "계속"; "lng_close" = "닫기"; "lng_connecting" = "연결중..."; "lng_connecting_to_proxy" = "프록시 연결 중..."; "lng_connecting_settings" = "설정"; "lng_reconnecting#one" = "{count} 초 후 재연결..."; "lng_reconnecting#other" = "{count} 초 후 재연결..."; "lng_reconnecting_try_now" = "다시 시도"; "lng_status_service_notifications" = "서비스 알림"; "lng_status_support" = "지원"; "lng_status_bot" = "bot"; "lng_status_bot_reads_all" = "메시지 접근 권한이 있습니다."; "lng_status_bot_not_reads_all" = "메시지 접근 권한이 없습니다."; "lng_status_offline" = "마지막으로 접속한 지 오래됨"; "lng_status_recently" = "최근에 접속"; "lng_status_last_week" = "일주일 이내 마지막으로 접속"; "lng_status_last_month" = "한 달 이내 마지막으로 접속"; "lng_status_invisible" = "숨김"; "lng_status_lastseen_now" = "방금 접속"; "lng_status_lastseen_minutes#one" = "{count} 분전 접속"; "lng_status_lastseen_minutes#other" = "{count} 분전 접속"; "lng_status_lastseen_hours#one" = "{count} 시간전 접속"; "lng_status_lastseen_hours#other" = "{count} 시간전 접속"; "lng_status_lastseen_today" = "오늘 {time}에 마지막으로 접속"; "lng_status_lastseen_yesterday" = "어제 {time}에 마지막으로 접속"; "lng_status_lastseen_date" = "{date}에 마지막으로 접속"; "lng_status_lastseen_date_time" = "{date}일 {time}에 마지막으로 접속"; "lng_status_online" = "온라인"; "lng_status_connecting" = "연결중..."; "lng_chat_status_unaccessible" = "그룹에 접근할 수 없습니다."; "lng_chat_status_no_members" = "대화상대가 없음"; "lng_chat_status_members#one" = "대화상대 {count}명"; "lng_chat_status_members#other" = "대화상대 {count}명"; "lng_chat_status_online#one" = "온라인 {count}명"; "lng_chat_status_online#other" = "온라인 {count}명"; "lng_chat_status_members_online" = "{members_count}, {online_count}"; "lng_channel_status" = "채널"; "lng_group_status" = "그룹방"; "lng_channel_members_link#one" = "대화상대 {count}명"; "lng_channel_members_link#other" = "대화상대 {count}명"; "lng_channel_admins_link#one" = "관리자 {count}명"; "lng_channel_admins_link#other" = "관리자 {count}명"; "lng_server_error" = "내부 서버 오류"; "lng_flood_error" = "시도가 너무 많습니다. 나중에 다시 시도해주세요."; "lng_gif_error" = "GIF 애니메이션을 불러올때 에러가 발생하였습니다 :("; "lng_edit_error" = "메시지를 수정 할 수 없습니다."; "lng_join_channel_error" = "너무 많은 채널과 슈퍼그룹에 참여하였습니다.\n기존 대화방을 나가주셔야 참여가 가능합니다."; "lng_error_phone_flood" = "죄송합니다, 너무 많이 계정 재가입이 최근에 이루어졌습니다. 다시 재가입까지 몇일 기다려주시기 바랍니다."; "lng_error_start_minimized_passcoded" = "잠금코드를 설정하였기 때문에 앱이 최소화된 상태에서 시작할 수 없습니다. 앱이 시작하기 전에 잠금코드 입력이 필요합니다."; "lng_error_pinned_max#one" = "죄송합니다, {count}개 이상의 대화를 고정할 수 없습니다."; "lng_error_pinned_max#other" = "죄송합니다, {count}개 이상의 대화를 고정할 수 없습니다."; "lng_error_public_groups_denied" = "죄송하지만, 회원님은 공개그룹 참여에 제한 되었습니다.\n{more_info}"; "lng_error_cant_edit_admin" = "죄송합니다, 이 관리자의 권한을 변경 할 수 없습니다."; "lng_error_cant_add_member" = "죄송합니다, 이 봇을 그룹에 추가할 수 없습니다. 관리자에게 추가 문의해주세요."; "lng_error_cant_add_bot" = "죄송합니다, 이 봇은 그룹에 추가할 수 없습니다."; "lng_error_cant_add_admin_invite" = "죄송합니다, 이 사용자는 이 그룹에 속해있지 않으며 초대가 되지 않기 때문에 관리자로 추가할 수 없습니다."; "lng_error_cant_add_admin_unban" = "죄송합니다, 이 사용자는 블랙리스트에 있으며 해제가 가능하지 않기 떄문에 관리자로 추가할 수 없습니다."; "lng_error_cant_ban_admin" = "죄송합니다, 이 사용자는 그룹의 관리자이며 해제권한이 없으시기 떄문에 차단할 수 없습니다."; "lng_sure_add_admin_invite" = "이 사용자는 그룹에 참여하고 있지 않습니다. 그룹에 초대하고 관리자로 추가하시겠습니까?"; "lng_sure_add_admin_unban" = "이 사용자는 제한되어져 있으며 그룹에서 차단당한 상태입니다. 정말로 차단을 해제하고 추가하시겠습니까?"; "lng_sure_ban_admin" = "이 사용자는 그룹의 관리자입니다. 정말로 제한을 하시겠습니까?"; "lng_sure_ban_user_group" = "{user} 를 그룹에서 차단하시겠습니까?"; "lng_sure_enable_socks" = "프록시 설정을 하시겠습니까?\n\n서버: {server}\n포트: {port}\n\n나중에 설정에서 프록시 서버를 변경 할 수 있습니다. (연결유형)"; "lng_sure_enable" = "활성"; "lng_edit_deleted" = "메시지는 삭제 되었습니다."; "lng_edit_too_long" = "메시지 길이가 너무 깁니다."; "lng_edit_message" = "메시지 수정"; "lng_edit_message_text" = "새로운 메시지..."; "lng_deleted" = "삭제된 계정"; "lng_deleted_message" = "삭제된 메시지"; "lng_pinned_message" = "고정된 메시지"; "lng_pinned_unpin_sure" = "메시지를 고정 해제하시겠습니까?"; "lng_pinned_pin_sure" = "메시지를 고정 하시겠습니까?"; "lng_pinned_pin" = "고정"; "lng_pinned_unpin" = "고정해제"; "lng_pinned_notify" = "모두알림"; "lng_intro_about" = "PC 공식버전에 오신 것을 환영합니다. \n안전하고 신속합니다."; "lng_start_msgs" = "시작하기"; "lng_intro_next" = "다음"; "lng_intro_finish" = "회원가입"; "lng_intro_submit" = "제출"; "lng_photo_caption" = "추가설명"; "lng_photos_comment" = "코멘트"; "lng_phone_ph" = "휴대폰 번호"; "lng_phone_title" = "전화번호"; "lng_phone_desc" = "국가번호와 전화번호를 입력하세요. \n(대한민국 국가번호: 82)"; "lng_phone_notreg" = "아직 텔레그램 계정이 없으실 경우, \n{link_start}안드로이드 / 아이폰{link_end}로 [b]회원가입[/b]을 해주시거나 {signup_start}여기{signup_end} 에서 회원가입을 해주세요."; "lng_country_code" = "국가번호"; "lng_bad_country_code" = "올바른 국가번호가 아닙니다."; "lng_country_ph" = "검색"; "lng_country_done" = "완료"; "lng_country_none" = "국가를 찾지 못하였습니다."; "lng_country_select" = "국가 선택"; "lng_code_ph" = "코드번호"; "lng_code_desc" = "인증코드 메세지를 휴대폰으로 전송하였습니다.\n인증코드를 아래에 입력하여 주세요."; "lng_code_telegram" = "기존 [b]Telegram[/b] 앱에서\n수신받으신 코드를 입력해주세요"; "lng_code_no_telegram" = "코드를 SMS로 전송"; "lng_code_call" = "텔레그램이 {minutes}:{seconds}후에는 전화를 겁니다."; "lng_code_calling" = "텔레그램으로부터 전화 요청을 하고 있습니다..."; "lng_code_called" = "텔레그램이 회원님의 전화번호로 전화를 걸었습니다."; "lng_bad_phone" = "잘못된 전화번호입니다. 다시 시도해주세요."; "lng_bad_phone_noreg" = "등록되지 않은 번호입니다."; "lng_bad_code" = "틀린 인증코드입니다. 다시 시도해주세요."; "lng_bad_name" = "성과 이름을 입력해주세요."; "lng_bad_photo" = "올바르지 않은 이미지를 선택하였습니다."; "lng_signin_title" = "클라우드 비밀번호 확인"; "lng_signin_desc" = "클라우드 비밀번호 입력"; "lng_signin_recover_desc" = "이메일로 부터 받은 코드를 입력해주세요\n{email}"; "lng_signin_password" = "클라우드 비밀번호"; "lng_signin_code" = "이메일 코드"; "lng_signin_recover" = "비밀번호를 잊어버리셨습니까?"; "lng_signin_recover_title" = "비밀번호 초기화"; "lng_signin_hint" = "힌트: {password_hint}"; "lng_signin_recover_hint" = "{recover_email} 로 코드를 전송하였습니다."; "lng_signin_bad_password" = "잘못된 비밀번호를 입력하였습니다."; "lng_signin_wrong_code" = "틀린 인증코드입니다. 다시 시도해주세요."; "lng_signin_try_password" = "이메일 접근이 안되시나요?"; "lng_signin_password_removed" = "클라우드 비밀번호가 삭제되었습니다.\n설정에서 새로운 비밀번호 설정이 가능합니다."; "lng_signin_no_email_forgot" = "비밀번호 복구 이메일을 설정하지 않았기때문에, \n비밀번호를 기억해내시거나 계정 초기화를 진행해주셔야합니다."; "lng_signin_cant_email_forgot" = "이메일 접근을 하실 수 없을 경우, 비밀번호를 기억해내시거나 계정 초기화를 진행해주셔야 합니다."; "lng_signin_reset_account" = "계정 초기화"; "lng_signin_sure_reset" = "경고!\n\n계정 초기화 진행시 모든 대화,\n메시지 및 공유받은 미디어와 파일이 삭제가 됩니다.\n\n계정 초기화를 진행하시겠습니까?"; "lng_signin_reset" = "초기화"; "lng_signin_reset_wait" = "{phone_number} 계정이 사용중이고 비밀번호 설정이 되어져 있어, 보안을 위하여 1주일 이후에 삭제가 될 예정입니다. 이 설정은 언제든지 취소 할 수 있습니다.\n\n계정은 다음시간 이후에 초기화가 됩니다:\n{when}"; "lng_signin_reset_days#one" = "{count} 일"; "lng_signin_reset_days#other" = "{count} 일"; "lng_signin_reset_hours#one" = "{count} 시간"; "lng_signin_reset_hours#other" = "{count} 시간"; "lng_signin_reset_minutes#one" = "{count} 분"; "lng_signin_reset_minutes#other" = "{count} 분"; "lng_signin_reset_in_days" = "{days_count} {hours_count} {minutes_count}"; "lng_signin_reset_in_hours" = "{hours_count} {minutes_count}"; "lng_signin_reset_cancelled" = "현재 사용중인 사용자가 요청하신 계정 초기화를 취소 하였습니다.\n7일 이후에 다시 시도해주세요."; "lng_signup_title" = "정보"; "lng_signup_desc" = "이름을 입력해주시고,\n사진을 업로드해주세요."; "lng_signup_firstname" = "이름"; "lng_signup_lastname" = "성"; "lng_dlg_filter" = "검색"; "lng_dlg_new_group_name" = "그룹이름"; "lng_dlg_new_channel_name" = "채널명"; "lng_no_contacts" = "연락처가 없습니다."; "lng_no_chats" = "대화시 대화방이 존재 할 곳입니다."; "lng_contacts_loading" = "로딩중..."; "lng_contacts_not_found" = "연락처를 찾을 수 없음"; "lng_dlg_search_chat" = "이 채팅에서 검색"; "lng_dlg_search_channel" = "이 채널방에서 검색"; "lng_dlg_search_for_messages" = "메시지 검색"; "lng_update_telegram" = "텔레그램 업데이트"; "lng_settings_save" = "저장"; "lng_settings_upload" = "프로필 이미지 선택"; "lng_settings_crop_profile" = "프로필 사진으로 설정할 영역을 선택해주세요."; "lng_settings_uploading_photo" = "사진 업로드 중..."; "lng_settings_edit" = "수정"; "lng_settings_drop_area_subtitle" = "프로필 사진으로 설정"; "lng_username_title" = "아이디"; "lng_username_about" = "텔레그램 아이디를 설정할 수 있습니다. \n아이디를 설정하면 회원님의 전화번호를 몰라도 아이디로 회원님을 찾아 대화를 나눌 수 있습니다.\n아이디는 영문, 밑줄, 숫자로 a-z, _, 0-9, \n다섯 글자 이상으로 설정해 주세요."; "lng_username_choose" = "아이디를 설정해주세요"; "lng_username_invalid" = "올바르지 않은 아이디입니다."; "lng_username_occupied" = "사용중인 아이디입니다."; "lng_username_too_short" = "아이디가 너무 짧습니다."; "lng_username_bad_symbols" = "아이디에 올바르지 않은 문자가 있습니다."; "lng_username_available" = "사용 가능한 아이디입니다."; "lng_username_not_found" = "아이디 @{user}를 찾을 수 없습니다."; "lng_username_link_willbe" = "회원님과 대화를 할 수 있는 링크를 생성합니다."; "lng_username_link" = "회원님과 대화를 할 수 있는 링크입니다."; "lng_username_copied" = "클립보드에 링크가 복사되었습니다."; "lng_bio_title" = "자기소개 수정"; "lng_bio_placeholder" = "자기속"; "lng_bio_about" = "소개하고 싶은 내용을 적어주세요. 다른 분이 회원님의 프로필을 확인했을 경우 노출됩니다."; "lng_settings_section_info" = "정보"; "lng_settings_phone_number" = "전화번호"; "lng_settings_username" = "아이디:"; "lng_settings_choose_username" = "아이디 설정하기"; "lng_settings_empty_bio" = "없음"; "lng_settings_section_notify" = "알림"; "lng_settings_desktop_notify" = "데스크탑 알림"; "lng_settings_show_name" = "발신인 표시"; "lng_settings_show_preview" = "메시지 미리보기"; "lng_settings_use_windows" = "윈도우 알림 사용"; "lng_settings_use_native_notifications" = "기존 알림 사용"; "lng_settings_advanced_notifications" = "알림 위치 및 개수"; "lng_settings_notifications_position" = "화면 위치"; "lng_settings_notifications_count" = "알림 개수"; "lng_settings_sound_notify" = "소리 재생"; "lng_settings_include_muted" = "안 읽은 메시지에 음소거된 채팅방 포함"; "lng_notification_preview" = "새로운 메시지가 있습니다."; "lng_notification_reply" = "답장"; "lng_notification_hide_all" = "모두 숨기기"; "lng_notification_sample" = "샘플 알림입니다"; "lng_settings_section_general" = "일반"; "lng_settings_change_lang" = "언어 변경"; "lng_languages" = "언어"; "lng_sure_save_language" = "언어를 변경하기 위해 텔레그램을 재시작합니다."; "lng_settings_update_automatically" = "자동 업데이트"; "lng_settings_current_version_label" = "버전 {version}:"; "lng_settings_current_version" = " {version}"; "lng_settings_check_now" = "업데이트 확인"; "lng_settings_update_checking" = "업데이트 확인 중..."; "lng_settings_latest_installed" = "최신 버전이 설치되어 있습니다."; "lng_settings_downloading" = "업데이트를 다운로드 중 {ready} / {total} MB.."; "lng_settings_update_ready" = "새로운 버전을 설치 할 수 있습니다."; "lng_settings_update_now" = "재시작 합니다."; "lng_settings_update_fail" = "업데이트 확인 실패 :("; "lng_settings_workmode_tray" = "트레이 아이콘 표시"; "lng_settings_workmode_window" = "테스크바 아이콘 표시"; "lng_settings_auto_start" = "시스템 시작시 자동실행"; "lng_settings_start_min" = "최소화로 시작"; "lng_settings_add_sendto" = " «보내기» 메뉴에 추가"; "lng_settings_section_scale" = "인터페이스 크기"; "lng_settings_scale_auto" = "자동 ({cur})"; "lng_settings_section_chat_settings" = "채팅방 설정"; "lng_settings_replace_emojis" = "이모티콘 자동변환"; "lng_settings_view_emojis" = "목록 보기"; "lng_settings_emoji_list" = "지원되는 이모티콘"; "lng_settings_send_enter" = "엔터키로 메시지 전송"; "lng_settings_send_ctrlenter" = " Ctrl+Enter로 메시지 전송"; "lng_settings_send_cmdenter" = "Cmd+Enter로 메시지 전송"; "lng_settings_section_background" = "대화창 배경화면"; "lng_settings_bg_use_default" = "기존 테마 색상 사용"; "lng_settings_bg_from_gallery" = "이미지 선택"; "lng_settings_bg_from_file" = "파일 선택"; "lng_settings_bg_edit_theme" = "테마 수정"; "lng_settings_bg_tile" = "타일형 배경화면"; "lng_settings_adaptive_wide" = "와이드 스크린 레이아웃 적용"; "lng_backgrounds_header" = "새로운 대화창 배경화면을 선택"; "lng_theme_sure_keep" = "이 테마 색상을 유지하시겠습니까?"; "lng_theme_reverting#one" = "{count}초 후에 이전 테마색으로 돌아갑니다."; "lng_theme_reverting#other" = "{count}초 후에 이전 테마색으로 돌아갑니다."; "lng_theme_keep_changes" = "변화 유지"; "lng_theme_revert" = "원복"; "lng_download_path_dont_ask" = "다운로드 경로 묻지 않기"; "lng_download_path_label" = "다운로드 경로:"; "lng_download_path_temp" = "임시 폴더"; "lng_download_path_default" = "기본 폴더"; "lng_download_path_clear" = "전체 지우기"; "lng_download_path_header" = "다운로드 경로 선택"; "lng_download_path_default_radio" = "텔레그램 기본 «다운로드» 폴더"; "lng_download_path_temp_radio" = "임시 폴더. 로그아웃 혹은 프로그램 제거시 초기화 됨"; "lng_download_path_dir_radio" = "임의 폴더. 직접 초기화"; "lng_download_path_choose" = "다운로드 경로 선택"; "lng_sure_clear_downloads" = "임시 폴더에 다운로드 받은 모든 파일을 삭제하시겠습니까? 로그아웃 혹은 프로그램 제거시 자동으로 초기화 됩니다."; "lng_download_path_failed" = "파일 다운로드를 시작 할 수 없습니다. 올바르지 않은 다운로드 경로가 원인 일 수도 있습니다.\n\n설정에 가시면 다운로드 경로를 변경하실 수 있습니다."; "lng_download_path_settings" = "설정"; "lng_download_finish_failed" = "파일 다운로드를 끝낼 수 없습니다.\n\n다시 시도하시겠습니까?"; "lng_download_path_clearing" = "초기화 중.."; "lng_download_path_cleared" = "초기화 완료!"; "lng_download_path_clear_failed" = "초기화 실패 :("; "lng_settings_section_privacy" = "개인정보 및 보안"; "lng_local_storage_title" = "로컬 저장소"; "lng_settings_no_data_cached" = "캐시 데이터가 없습니다!"; "lng_settings_images_cached#one" = "이미지 {count}개, {size}"; "lng_settings_images_cached#other" = "이미지 {count}개, {size}"; "lng_settings_audios_cached#one" = "음성메시지 {count}개, {size}"; "lng_settings_audios_cached#other" = "음성메시지 {count}개, {size}"; "lng_local_storage_clear" = "전체 정리"; "lng_local_storage_clearing" = "초기화 중.."; "lng_local_storage_cleared" = "초기화 완료!"; "lng_local_storage_clear_failed" = "초기화 실패 :("; "lng_settings_section_advanced_settings" = "고급 설정"; "lng_settings_enable_night_theme" = "야간 모드 활성화"; "lng_settings_disable_night_theme" = "야간 모드 비활성화"; "lng_passcode_remove_button" = "삭제"; "lng_passcode_turn_on" = "잠금코드 사용"; "lng_passcode_change" = "잠금코드 변경"; "lng_passcode_create" = "잠금코드"; "lng_passcode_remove" = "잠금코드 삭제"; "lng_passcode_turn_off" = "끄기"; "lng_passcode_autolock" = "자동-잠금"; "lng_passcode_autolock_away" = "미사용시 자동잠금 설정"; "lng_passcode_autolock_inactive" = "메신져 미사용시 자동잠금 설정"; "lng_passcode_autolock_minutes#one" = "{count} 분"; "lng_passcode_autolock_minutes#other" = "{count} 분"; "lng_passcode_autolock_hours#one" = "{count} 시간"; "lng_passcode_autolock_hours#other" = "{count} 시간"; "lng_passcode_enter_old" = "현재 잠금코드 입력"; "lng_passcode_enter_first" = "잠금코드 입력"; "lng_passcode_enter_new" = "신규 비밀번호 입력"; "lng_passcode_confirm_new" = "신규 비밀번호 재입력"; "lng_passcode_about" = "잠금번호를 설정하시면 상단 오른쪽 구석에 잠금 아이콘이 표시됩니다. 클릭을 하셔서 잠궈주세요. \n\n참고 : 잠금번호를 잊어버리셨을 경우 텔레그램 데스크탑을 재로그인을 해주셔야합니다."; "lng_passcode_differ" = "비밀번호가 다릅니다."; "lng_passcode_wrong" = "잘못된 비밀번호입니다."; "lng_passcode_is_same" = "비밀번호가 변경되지 않았습니다."; "lng_passcode_enter" = "잠금코드 입력"; "lng_passcode_ph" = "현재 잠금코드"; "lng_passcode_submit" = "입력"; "lng_passcode_logout" = "로그아웃"; "lng_passcode_need_unblock" = "잠금코드를 먼저 해제해주세요."; "lng_cloud_password_waiting" = "{email}로 확인 이메일을 전송하였습니다.."; "lng_cloud_password_change" = "클라우드 비밀번호 변경"; "lng_cloud_password_create" = "클라우드 비밀번호"; "lng_cloud_password_remove" = "클라우드 비밀번호 삭제"; "lng_cloud_password_set" = "2단계 인증 활성화"; "lng_cloud_password_edit" = "클라우드 비밀번호 변경"; "lng_cloud_password_enter_old" = "현재 비밀번호 입력"; "lng_cloud_password_enter_first" = "비밀번호 입력"; "lng_cloud_password_enter_new" = "새로운 비밀번호 입력"; "lng_cloud_password_confirm_new" = "새로운 비밀번호 재입력"; "lng_cloud_password_hint" = "비밀번호 힌트 입력"; "lng_cloud_password_change_hint" = "새로운 비밀번호 힌트 입력"; "lng_cloud_password_bad" = "비밀번호와 힌트는 동일할 수 없습니다."; "lng_cloud_password_email" = "복구 이메일 입력"; "lng_cloud_password_bad_email" = "올바르지 않은 이메일입니다. 다시 시도해주세요."; "lng_cloud_password_about" = "핀코드와 별도로 새로운 기기에서 로그인하실때 사용되는 비밀번호입니다."; "lng_cloud_password_about_recover" = "경고! 비밀번호 복구 이메일을 설정 안하시겠습니까?\n\n비밀번호 분실시 텔레그램 접근 권한이 유실됩니다."; "lng_cloud_password_skip_email" = "이메일 건너뛰기"; "lng_cloud_password_almost" = "제공하신 이메일로 확인링크를 전송하였습니다. \n링크를 누르시면 2단계 인증이 활성화 됩니다."; "lng_cloud_password_was_set" = "2단계 인증이 활성화되었습니다."; "lng_cloud_password_updated" = "클라우드 비밀번호가 변경되었습니다."; "lng_cloud_password_removed" = "2단계 인증이 비활성화 되었습니다."; "lng_cloud_password_differ" = "비밀번호가 일치하지 않습니다."; "lng_cloud_password_wrong" = "올바르지 않은 클라우드 비밀번호입니다."; "lng_cloud_password_is_same" = "비밀번호가 변경되지 않았습니다."; "lng_connection_type" = "연결 유형:"; "lng_connection_auto_connecting" = "기본값 (연결중...)"; "lng_connection_auto" = "기본값 ({transport} 사용)"; "lng_connection_proxy_connecting" = "프록시 연결 중..."; "lng_connection_proxy" = "{transport} 프록시 연결"; "lng_connection_header" = "연결 유형"; "lng_connection_auto_rb" = "자동 (사용 가능하다면 TCP 아니면 HTTP 사용)"; "lng_connection_http_proxy_rb" = "HTTP (커스텀 HTTP 프록시)"; "lng_connection_tcp_proxy_rb" = "TCP (커스텀 socks5-proxy)"; "lng_connection_try_ipv6" = "IPv6로 연결 시도 중"; "lng_connection_host_ph" = "호스트네임"; "lng_connection_port_ph" = "포트"; "lng_connection_user_ph" = "아이디"; "lng_connection_password_ph" = "비밀번호"; "lng_connection_save" = "저장"; "lng_settings_blocked_users" = "차단된 사용자"; "lng_settings_last_seen_privacy" = "접속 시간 개인설정"; "lng_settings_calls_privacy" = "전화 보안"; "lng_settings_groups_invite_privacy" = "그룹 초대 설정"; "lng_settings_show_sessions" = "모든 세션 보기"; "lng_settings_self_destruct" = "계정 자동탈퇴 설정"; "lng_settings_change_phone" = "전화번호 변경"; "lng_settings_reset" = "다른 모든 세션 강제 종료"; "lng_settings_reset_sure" = "현재 기기를 제외하고 다른 기기에 로그인된 세션을 모두 종료시킬까요?"; "lng_settings_reset_one_sure" = "해당 세션을 강제 종료 하시겠습니까?"; "lng_settings_reset_button" = "종료"; "lng_settings_reset_done" = "다른 모든 세션이 강제 종료 되었습니다."; "lng_settings_manage_local_storage" = "로컬 저장소 관리"; "lng_settings_ask_question" = "질문하기"; "lng_settings_ask_sure" = "텔레그램에 관한 질문은 자원봉사자들이 답변해 드립니다. 신속한 답변을 위해 노력하지만 답변이 다소 늦을 수 있습니다.\n\n일반적인 문제와 해결방법에 대해서는 자주 묻는 질문을 확인해 보세요."; "lng_settings_faq_button" = "자주 묻는 질문 으로 이동"; "lng_settings_ask_ok" = "질문하기"; "lng_settings_faq" = "텔레그램 자주 묻는 질문"; "lng_settings_logout" = "로그아웃"; "lng_sure_logout" = "로그아웃을 하시겠습니까?"; "lng_settings_need_restart" = "새로운 설정을 적용하시려면, 재시작을 하셔야합니다. \n재시작 할까요?"; "lng_settings_restart_now" = "재시작"; "lng_settings_restart_later" = "나중에"; "lng_sessions_header" = "현재 세션"; "lng_sessions_other_header" = "활성화된 세션"; "lng_sessions_no_other" = "다른 세션이 없습니다."; "lng_sessions_other_desc" = "동일한 휴대번호로 다른 휴대기기, 태블릿과 데스크탑에서 텔레그램 로그인이 가능합니다. 모든 데이터는 즉시 동기화 됩니다."; "lng_sessions_terminate_all" = "다른 모든 세션 강제 종료"; "lng_blocked_list_title" = "차단된 사용자"; "lng_blocked_list_unknown_phone" = "알 수 없는 번호"; "lng_blocked_list_unblock" = "차단해제"; "lng_blocked_list_add" = "사용자 차단"; "lng_blocked_list_add_title" = "차단할 사용자 선택"; "lng_blocked_list_already_blocked" = "이미 차단됨"; "lng_blocked_list_about" = "차단된 사용자는 회원님에게 메시지나 그룹으로 초대할 수 없습니다. 또한 프로필 사진, 온라인 및 접속 시간을 볼 수 없습니다."; "lng_blocked_list_not_found" = "사용자를 찾 을 수 없음"; "lng_edit_privacy_everyone" = "모두"; "lng_edit_privacy_contacts" = "내주소록"; "lng_edit_privacy_nobody" = "모두거절"; "lng_edit_privacy_exceptions" = "예외 추가"; "lng_edit_privacy_lastseen_title" = "접속 시간 개인설정"; "lng_edit_privacy_lastseen_description" = "내 접속시간을 볼 수 있는 사람 설정:"; "lng_edit_privacy_lastseen_warning" = "중요: 접속시간을 공유하지 않는 사람의 접속 시간은 볼 수 없습니다. 대신 대략적으로 접속 시간이 표시 됩니다. (최근에, 일주일 이내, 한달 이내)"; "lng_edit_privacy_lastseen_always_empty" = "항상 공유"; "lng_edit_privacy_lastseen_always#one" = "항상 {count}명의 대화상대와 공유"; "lng_edit_privacy_lastseen_always#other" = "항상 {count}명의 대화상대와 공유"; "lng_edit_privacy_lastseen_never_empty" = "항상 공유 안함"; "lng_edit_privacy_lastseen_never#one" = "항상 {count}명의 대화상대와 공유하지 않음"; "lng_edit_privacy_lastseen_never#other" = "항상 {count}명의 대화상대와 공유하지 않음"; "lng_edit_privacy_lastseen_exceptions" = "이 설정은 위 값을 무효화합니다"; "lng_edit_privacy_lastseen_always_title" = "항상 공유"; "lng_edit_privacy_lastseen_never_title" = "항상 공유 안함"; "lng_edit_privacy_groups_title" = "그룹 초대 설정"; "lng_edit_privacy_groups_description" = "그룹과 채널로 초대가 가능한 사람을 세세하게 설정 할 수 있습니다:"; "lng_edit_privacy_groups_always_empty" = "항상 허용"; "lng_edit_privacy_groups_always#one" = "항상 {count}명의 대화상대를 허용"; "lng_edit_privacy_groups_always#other" = "항상 {count}명의 대화상대를 허용"; "lng_edit_privacy_groups_never_empty" = "허용 안함"; "lng_edit_privacy_groups_never#one" = "항상 {count}명의 대화상대와 허용하지 않음"; "lng_edit_privacy_groups_never#other" = "항상 {count}명의 대화상대와 허용하지 않음"; "lng_edit_privacy_groups_exceptions" = "위의 설정과 무관하여 이 사용자는 회원님을 그룹이나 채널로 초대할 수 없습니다."; "lng_edit_privacy_groups_always_title" = "항상 허용"; "lng_edit_privacy_groups_never_title" = "허용 안함"; "lng_edit_privacy_calls_title" = "전화 보안"; "lng_edit_privacy_calls_description" = "전화대상을 제한할 수 있습니다"; "lng_edit_privacy_calls_always_empty" = "항상 허용"; "lng_edit_privacy_calls_always#one" = "항상 {count}명의 대화상대를 허용"; "lng_edit_privacy_calls_always#other" = "항상 {count}명의 대화상대를 허용"; "lng_edit_privacy_calls_never_empty" = "허용 안함"; "lng_edit_privacy_calls_never#one" = "항상 {count}명의 대화상대와 허용하지 않음"; "lng_edit_privacy_calls_never#other" = "항상 {count}명의 대화상대와 허용하지 않음"; "lng_edit_privacy_calls_exceptions" = "위의 설정과 무관하여 이 사용자는 회원님을 전화를 하거나 할 수 없게 됩니다."; "lng_edit_privacy_calls_always_title" = "항상 허용"; "lng_edit_privacy_calls_never_title" = "허용 안함"; "lng_self_destruct_title" = "계정 자동탈퇴"; "lng_self_destruct_description" = "이 기간동안 최소한 한번도 온라인이 안될 경우 이 계정은 그룹대화, 일반대화 및 연락처 모두가 삭제됩니다."; "lng_self_destruct_months#one" = "{count} 월"; "lng_self_destruct_months#other" = "{count} 월"; "lng_self_destruct_years#one" = "{count} 년"; "lng_self_destruct_years#other" = "{count} 년"; "lng_change_phone_title" = "전화번호 변경"; "lng_change_phone_description" = "텔레그램 번호 변경이 가능합니다.\n대화내용, 미디어, 연락처등 클라우드 데이터와\n모든 계정 정보는 새로운 번호로 이관됩니다.\n\n[b]중요[/b]:회원님 텔레그램 연락처에 있는 \n친구분들이회원님의 이전 전화번호가\n저장되어있고 차단을 하지 않았을 경우 \n[b]새로운 전화번호[/b]로 주소록에 저장이 됩니다."; "lng_change_phone_warning" = "회원님 텔레그램 연락처에 있는 친구분들이 회원님의 이전 전화번호가 저장되어있고 차단을 하지 않았을 경우 새로운 전화번호로 주소록에 저장이 됩니다."; "lng_change_phone_occupied" = "{phone}번호는 이미 텔레그램에 연결되어 있습니다. 새로운 번호로 마이그레이션 진행시 해당 계정을 탈퇴시켜주세요."; "lng_change_phone_button" = "전화번호 변경"; "lng_change_phone_new_title" = "신규 전화번호 입력"; "lng_change_phone_new_description" = "새로운 번호로 인증 문자를 전송하겠습니다."; "lng_change_phone_new_submit" = "입력"; "lng_change_phone_code_title" = "인증코드 입력"; "lng_change_phone_code_description" = "{phone} 번호로 인증코드 문자를 전송하였습니다."; "lng_change_phone_success" = "회원님의 전화번호가 변경되었습니다."; "lng_preview_loading" = "링크 정보를 가져오는 중.."; "lng_profile_chat_unaccessible" = "그룹에 접근할 수 없습니다."; "lng_profile_about_section" = "정보"; "lng_profile_description_section" = "설명"; "lng_profile_settings_section" = "환경설정"; "lng_profile_actions_section" = "동작"; "lng_profile_bot_settings" = "환경설정"; "lng_profile_bot_help" = "도움말"; "lng_profile_invite_link_section" = "초대링크"; "lng_profile_create_public_link" = "공개 링크 생성"; "lng_profile_edit_public_link" = "공개링크 수정"; "lng_profile_search_members" = "사용자 검색"; "lng_profile_manage_admins" = "관리자 관리"; "lng_profile_manage_blocklist" = "차단된 사용자 관리"; "lng_profile_manage_restrictedlist" = "제한된 사용자 관리"; "lng_profile_recent_actions" = "최근 활동"; "lng_profile_common_groups#one" = "공통그룹 {count}개"; "lng_profile_common_groups#other" = "공통그룹 {count}개"; "lng_profile_common_groups_section" = "공통 그룹"; "lng_profile_participants_section" = "사용자"; "lng_profile_info_section" = "정보"; "lng_profile_mobile_number" = "전화번호:"; "lng_profile_username" = "아이디:"; "lng_profile_link" = "링크:"; "lng_profile_bio" = "자기소개:"; "lng_profile_add_contact" = "연락처 추가"; "lng_profile_edit_contact" = "수정"; "lng_profile_enable_notifications" = "알림"; "lng_profile_clear_history" = "히스토리 초기화"; "lng_profile_delete_conversation" = "대화 지우기"; "lng_profile_clear_and_exit" = "삭제 후 종료"; "lng_profile_leave_channel" = "채널 나가기"; "lng_profile_delete_channel" = "채널 삭제"; "lng_profile_leave_group" = "그룹방 나가기"; "lng_profile_delete_group" = "그룹방 삭제"; "lng_profile_report" = "신고"; "lng_profile_search_messages" = "메시지 검색"; "lng_profile_block_user" = "차단하기"; "lng_profile_unblock_user" = "차단해제하기"; "lng_profile_block_bot" = "봇 중지 및 차단"; "lng_profile_unblock_bot" = "봇 차단해제"; "lng_profile_send_message" = "메세지 전송"; "lng_profile_share_contact" = "연락처 공유"; "lng_profile_invite_to_group" = "그룹에 추가"; "lng_profile_delete_contact" = "삭제"; "lng_profile_set_group_photo" = "사진 설정"; "lng_profile_add_participant" = "대화상대 추가"; "lng_profile_view_channel" = "채널 보기"; "lng_profile_join_channel" = "참여"; "lng_profile_delete_and_exit" = "나가기"; "lng_profile_kick" = "삭제"; "lng_profile_admin" = "관리자"; "lng_profile_edit_permissions" = "수정"; "lng_profile_sure_kick" = "{user}를 내보내시겠습니까?"; "lng_profile_sure_kick_channel" = "{user}를 내보내시겠습니까?"; "lng_profile_sure_kick_admin" = "{user}를 관리자에서 제외 하시겠습니까?"; "lng_profile_loading" = "로드중.."; "lng_profile_shared_media" = "공유된 미디어"; "lng_profile_no_media" = "대화에 미디어가 존재하지 않습니다."; "lng_profile_photos#one" = "사진 {count}개"; "lng_profile_photos#other" = "사진 {count}개"; "lng_profile_photos_header" = "사진"; "lng_profile_videos#one" = "비디오 {count}개"; "lng_profile_videos#other" = "비디오 {count}개"; "lng_profile_videos_header" = "동영상"; "lng_profile_songs#one" = "음성파일 {count}개"; "lng_profile_songs#other" = "음성파일 {count}개"; "lng_profile_songs_header" = "음성 파일"; "lng_profile_files#one" = "파일 {count}개"; "lng_profile_files#other" = "파일 {count}개"; "lng_profile_files_header" = "파일"; "lng_profile_audios#one" = "음성메시지 {count}개"; "lng_profile_audios#other" = "음성메시지 {count}개"; "lng_profile_audios_header" = "음성 메시지"; "lng_profile_shared_links#one" = "공유링크 {count}개"; "lng_profile_shared_links#other" = "공유링크 {count}개"; "lng_profile_shared_links_header" = "공유된 링크"; "lng_profile_copy_phone" = "전화번호 복사"; "lng_profile_copy_fullname" = "이름 복사"; "lng_profile_drop_area_title" = "이미지를 여기에 놓으세요."; "lng_profile_drop_area_subtitle" = "그룹 사진으로 설정"; "lng_profile_drop_area_subtitle_channel" = "채널 사진으로 설정"; "lng_profile_top_bar_share_contact" = "공유"; "lng_report_title" = "채널 신고"; "lng_report_group_title" = "그룹 신고"; "lng_report_bot_title" = "봇 신고"; "lng_report_reason_spam" = "스팸"; "lng_report_reason_violence" = "폭력성"; "lng_report_reason_pornography" = "음란성"; "lng_report_reason_other" = "기타"; "lng_report_reason_description" = "설명"; "lng_report_button" = "신고"; "lng_report_thanks" = "감사합니다! 신고내용은 신속하게 팀내에서 확인하겠습니다."; "lng_channel_add_members" = "대화상대 추가"; "lng_channel_add_banned" = "사용자 차단"; "lng_channel_add_restricted" = "사용자 제한"; "lng_channel_members" = "대화상대"; "lng_channel_only_last_shown#one" = "마지막 {count}명의 대화상대가 표시됩니다."; "lng_channel_only_last_shown#other" = "마지막 {count}명의 대화상대가 표시됩니다."; "lng_channel_admins" = "관리자"; "lng_channel_add_admin" = "관리자 추가"; "lng_channel_admins_too_much" = "죄송합니다, 관리자의 최대허용치입니다. 다른 관리자를 삭제해주세요"; "lng_channel_admin_status_creator" = "생성자"; "lng_channel_admin_status_promoted_by" = "{user} 님이 승인함"; "lng_channel_admin_status_not_admin" = "관리자가 아님"; "lng_channel_banned_status_restricted_by" = "{user} 님이 제한함"; "lng_group_blocked_list_about" = "차단된 사용자들은 그룹에서 추방이 되며 관리자의 초대로만 재입장이 가능합니다.\n초대링크는 작동되지 않습니다."; "lng_chat_all_members_admins" = "모든 대화상대가 관리자입니다"; "lng_chat_about_all_admins" = "그룹에 있는 모든 대화상대는 상대 초대, 이름 및 사진을 수정할 수 있습니다."; "lng_chat_about_admins" = "그룹에 있는 관리자는 상대 초대, 이름 및 사진을 수정할 수 있습니다."; "lng_participant_filter" = "검색"; "lng_participant_invite" = "초대"; "lng_participant_invite_sorry#one" = "죄송합니다, 초기 {count}명의 대화상대만 채널에 개별적으로 추가 할 수 있습니다.\n\n이후로는 초대링크를 통하여 입장해야합니다."; "lng_participant_invite_sorry#other" = "죄송합니다, 초기 {count}명의 대화상대만 채널에 개별적으로 추가 할 수 있습니다.\n\n이후로는 초대링크를 통하여 입장해야합니다."; "lng_create_group_back" = "뒤로가기"; "lng_create_group_next" = "다음"; "lng_create_group_create" = "만들기"; "lng_create_group_title" = "새로운 그룹"; "lng_create_channel_title" = "새로운 채널"; "lng_create_public_channel_title" = "공개 채널"; "lng_create_public_channel_about" = "누구나 채널을 검색하고 입장할 수 있습니다."; "lng_create_private_channel_title" = "비공개 채널"; "lng_create_private_channel_about" = "초대 링크를 통해서만 입장이 가능합니다."; "lng_create_public_group_title" = "공개그룹"; "lng_create_public_group_about" = "누구나 그룹을 검색 및 참여가 가능하며, 채팅 히스토리가 공개됩니다."; "lng_create_private_group_title" = "비공개그룹"; "lng_create_private_group_about" = "초대 및 초대링크를 통하여 참여 가능합니다."; "lng_create_group_skip" = "건너뛰기"; "lng_create_channel_link_invalid" = "올바르지 않은 링크입니다."; "lng_create_channel_link_occupied" = "사용중인 링크입니다."; "lng_create_channel_link_too_short" = "링크가 너무 짧습니다."; "lng_create_channel_link_bad_symbols" = "링크에 허용되지 않은 심볼이 있습니다."; "lng_create_channel_link_available" = "사용 가능한 링크입니다."; "lng_create_channel_link_copied" = "링크가 클립보드에 복사되었습니다."; "lng_create_group_crop" = "그룹프로필 사진으로 사용할 영역을 선택하세요"; "lng_create_channel_crop" = "채널프로필 사진으로 사용할 영역을 선택하세요"; "lng_failed_add_participant" = "사용자를 추가 하지 못하였습니다. 나중에 다시 시도해주세요"; "lng_failed_add_not_mutual" = "죄송합니다. 그룹방에서 대화상대방이 나갔을 경우, 상대 전화번호가 있는 분만 초대가 가능합니다. (서로 전화번호가 등록되어져 있어야만 가능)"; "lng_failed_add_not_mutual_channel" = "죄송합니다. 채널에서 대화상대방이 나갔을 경우, 상대 전화번호가 있는 분만 초대가 가능합니다. (서로 전화번호가 등록되어져 있어야만 가능)"; "lng_sure_delete_contact" = "{contact} 님을 주소록에서 \n삭제하시겠습니까?"; "lng_sure_delete_history" = "{contact} 님과 관련된 모든 메시지를 \n삭제하시겠습니까?\n\n삭제 하실 경우 취소가 불가능합니다."; "lng_sure_delete_group_history" = "그룹<<{group}>> 방의 모든 메시지 기록을 삭제하시겠습니까?\n\n이 작업은 취소가 불가능합니다."; "lng_sure_delete_and_exit" = "그룹 «{group}» 방에서 모든 메시지를 \n삭제하시고 퇴장하시겠습니까?\n\n삭제 하실 경우 취소가 불가능합니다."; "lng_sure_leave_channel" = "채널에서 나가시겠습니까?"; "lng_sure_delete_channel" = "채널을 삭제하시겠습니까? 모든 대화상대와 메시지가 삭제됩니다."; "lng_sure_leave_group" = "정말로 그룹방에서 나가시겠습니까?\n이 작업은 취소가 불가능합니다."; "lng_sure_delete_group" = "정말로 그룹방을 삭제하시겠습니까? 모든 대화상대 및 메시지가 삭제됩니다."; "lng_message_empty" = "메시지 없음"; "lng_message_unsupported" = "이 메시지는 텔레그램 데스크탑에서 호환이 되지 않습니다. 설정에서 최신 버전으로 업데이트를 하던가 {link}를 통하여 설치해주세요."; "lng_duration_seconds#one" = "{count} 초"; "lng_duration_seconds#other" = "{count} 초"; "lng_duration_minsec_minutes#one" = "{count} 분"; "lng_duration_minsec_minutes#other" = "{count} 분"; "lng_duration_minsec_seconds#one" = "{count} 초"; "lng_duration_minsec_seconds#other" = "{count} 초"; "lng_duration_minutes_seconds" = "{minutes_count} {seconds_count}"; "lng_action_add_user" = "{from} 님께서 {user} 님을 초대하셨습니다."; "lng_action_add_users_many" = "{from}님께서 {users}명을 초대하였습니다."; "lng_action_add_users_and_one" = "{accumulated}, {user}"; "lng_action_add_users_and_last" = "{accumulated}님과 {user}"; "lng_action_add_you" = "{from} 님께서 채널에 초대하셨습니다."; "lng_action_you_joined" = "채널에 입장하였습니다."; "lng_action_add_you_group" = "{from}님께서 그룹에 초대해주셨습니다."; "lng_action_you_joined_group" = "그룹방에 참여하였습니다."; "lng_action_kick_user" = "{from} 님께서 {user} 님을 내보내셨습니다."; "lng_action_user_left" = "{from} 님이 그룹을 나가셨습니다."; "lng_action_user_joined" = "{from} 님이 그룹에 들어오셨습니다."; "lng_action_user_joined_by_link" = "초대링크를 타고 {from} 님이 그룹에 참여하였습니다."; "lng_action_user_registered" = "{from} 님이 텔레그램에 가입하셨습니다."; "lng_action_removed_photo" = "{from} 님이 그룹사진을 삭제하셨습니다."; "lng_action_removed_photo_channel" = "채널 사진 삭제됨"; "lng_action_changed_photo" = "{from} 님이 그룹사진을 업데이트 하였습니다."; "lng_action_changed_photo_channel" = "채널 사진 업데이트됨"; "lng_action_changed_title" = "{from} 님이 그룹이름을 «{title}» 로 바꾸셨습니다."; "lng_action_changed_title_channel" = "채널명이 «{title}»로 수정됨"; "lng_action_created_chat" = "{from} 님이 그룹 «{title}» 을 생성하셨습니다."; "lng_action_created_channel" = "채널 생성됨"; "lng_action_group_migrate" = "이 그룹방은 슈퍼그룹방으로 변환되었습니다"; "lng_action_pinned_message" = "{from} 님이 «{text}» 를 고정함"; "lng_action_pinned_media" = "{from} 님이 {media} 를 고정함"; "lng_action_pinned_media_photo" = "사진"; "lng_action_pinned_media_video" = "동영상"; "lng_action_pinned_media_audio" = "오디오 파일"; "lng_action_pinned_media_voice" = "음성 메시지"; "lng_action_pinned_media_video_message" = "비디오 메시지"; "lng_action_pinned_media_file" = "파일"; "lng_action_pinned_media_gif" = "GIF 파일"; "lng_action_pinned_media_contact" = "연락처 정보"; "lng_action_pinned_media_location" = "위치 마크"; "lng_action_pinned_media_sticker" = "스티커"; "lng_action_pinned_media_emoji_sticker" = " {emoji} 스티커"; "lng_action_pinned_media_game" = " «{game}» 게임"; "lng_action_game_score#one" = "{game} 에서 {from} 님이 {count} 점 획득"; "lng_action_game_score#other" = "{game} 에서 {from} 님이 {count} 점 획득"; "lng_action_game_you_scored#one" = "회원님이 {game}에서 {count}점 획득"; "lng_action_game_you_scored#other" = "회원님이 {game}에서 {count}점 획득"; "lng_action_game_score_no_game#one" = "{from}님이 {count}점 획득"; "lng_action_game_score_no_game#other" = "{from}님이 {count}점 획득"; "lng_action_game_you_scored_no_game#one" = "회원님이 {count}점 획득"; "lng_action_game_you_scored_no_game#other" = "회원님이 {count}점 획득"; "lng_action_payment_done" = "{amount}만큼 {user}에게 성공적으로 전송하였습니다."; "lng_action_payment_done_for" = "{invoice}에 대하여 {user}에게 {amount}만큼 전송하였습니다."; "lng_action_took_screenshot" = "{from} 님이 화면을 캡춰했습니다!"; "lng_action_you_took_screenshot" = "화면을 캡춰했습니다!"; "lng_ttl_photo_received" = "{from} 님이 자동삭제되는 사진을 전송했습니다. 휴대폰에서 확인해주세요."; "lng_ttl_photo_sent" = "자동삭제되는 사진을 전송했습니다."; "lng_ttl_photo_expired" = "사진 유효기간이 만료되었습니다."; "lng_ttl_video_received" = "{from} 님이 자동삭제되는 동영상을 전송했습니다. 휴대폰에서 확인해주세요."; "lng_ttl_video_sent" = "자동삭제되는 동영상을 전송했습니다."; "lng_ttl_video_expired" = "동영상 유효기간이 만료되었습니다."; "lng_profile_migrate_reached#one" = "대화상대 한계치인 {count}명에 도달했습니다."; "lng_profile_migrate_reached#other" = "대화상대 한계치인 {count}명에 도달했습니다."; "lng_profile_migrate_body" = "제한을 초과하고 싶으실 경우, 슈퍼그룹으로 그룹 업그레이드가 가능합니다."; "lng_profile_migrate_learn_more" = "자세히 »"; "lng_profile_migrate_about" = "최대허용치를 초과하시고 싶으실 경우, 슈퍼그룹방으로 업그레이드해주세요. 슈퍼그룹방은 :"; "lng_profile_migrate_feature1#one" = "— 최대 {count}명까지 허용 가능합니다."; "lng_profile_migrate_feature1#other" = "— 최대 {count}명까지 허용 가능합니다."; "lng_profile_migrate_feature2" = "— 모든 대화상대가 전체 내용을 볼 수 있습니다."; "lng_profile_migrate_feature3" = "— 관리자는 대화상대로 부터 메시지 삭제가 가능합니다."; "lng_profile_migrate_feature4" = "— 기본값으로 알림이 무음으로 처리됩니다"; "lng_profile_migrate_button" = "슈퍼그룹방으로 업그레이드하기"; "lng_profile_migrate_sure" = "정말로 그룹방을 슈퍼그룹방으로 변환하시겠습니까? 이 작업은 취소가 불가능합니다."; "lng_profile_convert_button" = "슈퍼그룹으로 전환"; "lng_profile_convert_title" = "슈퍼그룹으로 전환"; "lng_profile_convert_about" = "슈퍼그룹:"; "lng_profile_convert_feature1" = "— 모든 대화상대가 이전 대화 내용 조회"; "lng_profile_convert_feature2" = "— 메시지 삭제시 모두에게 삭제"; "lng_profile_convert_feature3" = "— 관리자는 중요한 메시지 고정 가능"; "lng_profile_convert_feature4" = "— 방 생성자가 그룹 공개링크 생성가능"; "lng_profile_convert_warning" = "{bold_start}주의:{bold_end} 이 작업은 되돌릴 수 없습니다."; "lng_profile_convert_confirm" = "변환"; "lng_profile_add_more_after_upgrade#one" = "그룹에서 슈퍼그룹으로 업그레이드시 {count}명까지 추가 가능합니다."; "lng_profile_add_more_after_upgrade#other" = "그룹에서 슈퍼그룹으로 업그레이드시 {count}명까지 추가 가능합니다."; "lng_channel_not_accessible" = "채널에 접근 할 수 없습니다."; "lng_group_not_accessible" = "죄송합니다. 그룹방에 접근 할 수 없습니다."; "lng_channels_too_much_public_about" = "죄송합니다, 너무 많은 공개 아이디를 점유하셨습니다. 기존 그룹 및 채널에 있는 링크를 폐기해주셔야합니다."; "lng_channels_too_much_public_revoke_confirm_group" = "이 링크 {link} 를 폐기하시겠습니까?\n\n«{group}» 그룹방은 비공개로 전환됩니다."; "lng_channels_too_much_public_revoke_confirm_channel" = "이 링크 {link} 를 폐기하시겠습니까?\n\n«{group}» 채널방은 비공개로 전환됩니다."; "lng_channels_too_much_public_revoke" = "폐기"; "lng_group_invite_bad_link" = "초대링크가 깨졌거나 폐기되었습니다."; "lng_group_invite_want_join" = "«{title}» 그룹방에 참여하시겠습니까?"; "lng_group_invite_want_join_channel" = "«{title}» 채널에 참여하시겠습니까?"; "lng_group_invite_join" = "참여"; "lng_group_invite_members#one" = "대화상대 {count}명, 참여자 현황:"; "lng_group_invite_members#other" = "대화상대 {count}명, 참여자 현황:"; "lng_group_invite_link" = "초대링크: "; "lng_group_invite_create" = "초대링크 생성"; "lng_group_invite_about" = "이 링크를 통하여,\n그룹방에 초대가 가능합니다."; "lng_group_invite_create_new" = "초대링크 폐기"; "lng_group_invite_about_new" = "기존 링크는 폐기되며 새로운 초대링크가 생성됩니다."; "lng_group_invite_copied" = "클립보드에 초대링크가 복사 되었습니다."; "lng_group_invite_no_room" = "그룹방 인원 최대치가 도달하여 더이상 참여가 안됩니다."; "lng_channel_public_link_copied" = "클립보드에 링크가 복사되었습니다."; "lng_forwarded" = "{user}님으로 부터 전달 받음"; "lng_forwarded_date" = "원본: {date}"; "lng_forwarded_channel" = "{channel}로 부터 전달 받음"; "lng_forwarded_via" = "{inline_bot}을 {user}로 부터 전달 받음"; "lng_forwarded_channel_via" = "{inline_bot}을 {channel}로 부터 전달 받음"; "lng_forwarded_signed" = "{channel} ({user})"; "lng_in_reply_to" = "다음 유저에게 답장 :"; "lng_edited" = "수정됨"; "lng_edited_date" = "수정됨: {date}"; "lng_cancel_edit_post_sure" = "수정을 취소하겠습니까?"; "lng_cancel_edit_post_yes" = "네"; "lng_cancel_edit_post_no" = "아니요"; "lng_bot_share_location_unavailable" = "죄송합니다, 위치 공유는 텔레그램 테스크탑에서는 현재 지원을 하고 있지 않습니다."; "lng_bot_inline_geo_unavailable" = "죄송합니다, 위치 공유는 텔레그램 테스크탑에서는 현재 지원을 하고 있지 않습니다."; "lng_bot_share_phone" = "전화번호를 공유하겠습니까?"; "lng_bot_share_phone_confirm" = "공유"; "lng_attach_failed" = "실패"; "lng_attach_file" = "파일"; "lng_attach_photo" = "사진"; "lng_media_type" = "미디어 종류"; "lng_media_type_photos" = "사진"; "lng_media_type_videos" = "동영상"; "lng_media_type_songs" = "음성 파일"; "lng_media_type_files" = "파일"; "lng_media_type_audios" = "음성 메시지"; "lng_media_type_links" = "공유된 링크"; "lng_media_open_with" = "다음으로 열기"; "lng_media_download" = "다운로드"; "lng_media_cancel" = "취소"; "lng_media_video" = "동영상"; "lng_media_audio" = "음성 메시지"; "lng_media_auto_settings" = "미디어 자동 다운로드 설정"; "lng_media_auto_photo" = "사진 자동 다운로드"; "lng_media_auto_audio" = "음성 메시지 자동 다운로드"; "lng_media_auto_gif" = "GIF 자동 다운로드"; "lng_media_auto_private_chats" = "비밀대화"; "lng_media_auto_groups" = "그룹과 채널"; "lng_media_auto_play" = "자동재생"; "lng_emoji_category0" = "자주 사용"; "lng_emoji_category1" = "사람"; "lng_emoji_category2" = "자연"; "lng_emoji_category3" = "음식점"; "lng_emoji_category4" = "활동"; "lng_emoji_category5" = "여행 및 장소"; "lng_emoji_category6" = "물건"; "lng_emoji_category7" = "심볼 및 국기"; "lng_emoji_hide_panel" = "이모티콘 사이드바를 숨기시려면 여기를 클릭해주세요"; "lng_recent_stickers" = "자주 사용"; "lng_switch_stickers" = "스티커"; "lng_switch_emoji" = "이모티콘"; "lng_switch_gifs" = "GIF"; "lng_stickers_featured_add" = "추가"; "lng_gifs_search" = "GIF 검색"; "lng_gifs_no_saved" = "아직 저장된 GIF파일이 없습니다."; "lng_inline_bot_no_results" = "결과 없음"; "lng_inline_bot_via" = "{inline_bot} 결과"; "lng_box_remove" = "삭제"; "lng_stickers_installed_tab" = "스티커"; "lng_stickers_featured_tab" = "인기"; "lng_stickers_archived_tab" = "보관됨"; "lng_stickers_remove_pack" = "«{sticker_pack}»을 제거하시겠습니까?"; "lng_stickers_add_pack" = "스티커 추가"; "lng_stickers_share_pack" = "스티커 공유"; "lng_stickers_not_found" = "스티커 팩을 찾을 수 없습니다."; "lng_stickers_packs_archived" = "활성화된 스티커를 사용할 수 있는 공간이 필요하여 미사용 스티커중 일부는 보관되어집니다."; "lng_stickers_archived" = "보관된 스티커"; "lng_stickers_copied" = "클립보드에 스티커 팩 링크가 복사 되었습니다."; "lng_stickers_default_set" = "Great Minds"; "lng_stickers_you_have" = "스티커팩 관리 및 변경"; "lng_stickers_featured" = "인기 스티커"; "lng_stickers_return" = "실행취소"; "lng_stickers_count#one" = "스티커 {count}개"; "lng_stickers_count#other" = "스티커 {count}개"; "lng_stickers_masks_pack" = "마스크 스티커 팩입니다. 모바일 기기에 있는 사진 에디터에서 사용 가능합니다."; "lng_in_dlg_photo" = "사진"; "lng_in_dlg_video" = "동영상"; "lng_in_dlg_audio_file" = "음성 파일"; "lng_in_dlg_contact" = "연락처"; "lng_in_dlg_audio" = "음성 메시지"; "lng_in_dlg_video_message" = "비디오 메시지"; "lng_in_dlg_file" = "파일"; "lng_in_dlg_sticker" = "스티커"; "lng_in_dlg_sticker_emoji" = "{emoji} 스티커"; "lng_ban_user" = "차단하기"; "lng_delete_all_from" = "사용자의 모든 메시지 삭제"; "lng_report_spam" = "스팸 신고"; "lng_report_spam_hide" = "숨기기"; "lng_report_spam_thanks" = "신고해주셔서 감사합니다!"; "lng_report_spam_sure" = "선택한 메시지를 스팸으로 신고하시겠습니까?"; "lng_report_spam_sure_group" = "선택한 그룹메시지를 스팸으로 신고하시겠습니까?"; "lng_report_spam_sure_channel" = "선택한 채널메시지를 스팸으로 신고하시겠습니까?"; "lng_report_spam_ok" = "신고하기"; "lng_cant_send_to_not_contact" = "죄송하지만, 현재 서로 연락처가 추가된 회원들끼리만 전송이 가능합니다. {more_info}"; "lng_cant_invite_not_contact" = "죄송하지만, 현재 그룹방에 서로 연락처가 추가된 \n회원들끼리만 추가 가능합니다. {more_info}"; "lng_cant_send_to_not_contact_flood" = "연락처가 없으신분들과 너무 많이 연락을 해주셔서, 내일 다시 시도를 부탁드립니다. 다른 회원이 먼저 메시지를 전송하였을 경우 답장이 가능합니다."; "lng_cant_invite_not_contact_flood" = "연락처가 없으신분들과 너무 많이 연락을 해주셔서 연락처 추가를 할 수 없습니다. 내일 다시 시도를 부탁드립니다. 다른 회원이 이 그룹에 초대는 가능합니다."; "lng_cant_more_info" = "자세한 정보 »"; "lng_cant_invite_banned" = "죄송하지만, 관리자만 회원 추가가 가능합니다."; "lng_cant_invite_privacy" = "죄송합니다, 개인설정으로 인하여 이 사용자를 그룹에 초대할 수 없습니다."; "lng_cant_invite_privacy_channel" = "죄송합니다, 개인설정으로 인하여 이 사용자를 채널에 초대할 수 없습니다."; "lng_cant_delete_group#one" = "죄송합니다, 현재 {count}명 이상 사용자가 있는 그룹일 경우 삭제할 수 없습니다. 삭제하고 싶으실 경우 텔레그램 질문하기로 문의해주세요"; "lng_cant_delete_group#other" = "죄송합니다, 현재 {count}명 이상 사용자가 있는 그룹일 경우 삭제할 수 없습니다. 삭제하고 싶으실 경우 텔레그램 질문하기로 문의해주세요"; "lng_cant_delete_channel#one" = "죄송합니다, 현재 {count}명 이상 사용자가 있는 채널일 경우 삭제할 수 없습니다. 삭제하고 싶으실 경우 텔레그램 질문하기로 문의해주세요."; "lng_cant_delete_channel#other" = "죄송합니다, 현재 {count}명 이상 사용자가 있는 채널일 경우 삭제할 수 없습니다. 삭제하고 싶으실 경우 텔레그램 질문하기로 문의해주세요."; "lng_cant_do_this" = "죄송하지만, 할 수 없는 기능입니다."; "lng_send_button" = "보내기"; "lng_message_ph" = "메시지 쓰기.."; "lng_broadcast_ph" = "단체메시지 쓰기..."; "lng_broadcast_silent_ph" = "음소거 메시지..."; "lng_record_cancel" = "이 영역 밖에서 마우스 클릭을 해제하시면 취소가 됩니다."; "lng_will_be_notified" = "메시지 작성시 대화상대에게 알림이 갑니다."; "lng_wont_be_notified" = "메시지 작성시 대화상대에게 알림이 가지 않습니다."; "lng_willbe_history" = "대화하실 방을 선택해주세요."; "lng_from_you" = "회원님"; "lng_from_draft" = "임시저장"; "lng_bot_description" = "봇이 할 수 있는 일은 무엇일까요?"; "lng_unblock_button" = "차단해제"; "lng_channel_join" = "채널 입장"; "lng_channel_mute" = "음소거"; "lng_channel_unmute" = "음소거 취소"; "lng_dialogs_text_with_from" = "{from_part} {message}"; "lng_dialogs_text_from_wrapped" = "{from}:"; "lng_dialogs_text_media" = "{media_part} {caption}"; "lng_dialogs_text_media_wrapped" = "{media},"; "lng_dialogs_show_all_chats" = "모든 대화 보기"; "lng_dialogs_hide_muted_chats" = "음소거된 대화 숨기기"; "lng_open_this_link" = "이 링크로 이동하시겠나요?"; "lng_open_link" = "열기"; "lng_allow_bot_pass" = "이 bot을 통하여 {bot_name}이 텔레그램 이름과 아이디를 웹페이지로 전달하시기를 허락하시나요?"; "lng_allow_bot" = "허용"; "lng_bot_start" = "시작"; "lng_bot_choose_group" = "그룹방 선택"; "lng_bot_no_groups" = "그룹이 존재하지 않습니다."; "lng_bot_groups_not_found" = "그룹을 찾을 수 없습니다."; "lng_bot_sure_invite" = "<<{group}>>에 봇을 추가 하시겠습니까?"; "lng_bot_already_in_group" = "봇이 이미 그룹의 멤버입니다."; "lng_bot_choose_chat" = "채팅방 선택"; "lng_bot_no_chats" = "채팅방이 없습니다."; "lng_bot_chats_not_found" = "채팅방을 찾 을 수 없음"; "lng_bot_sure_share_game" = "{user}에게 게임을 공유하겠습니까?"; "lng_bot_sure_share_game_group" = " «{group}»에게 게임을 공유하겠습니까?"; "lng_typing" = "입력중"; "lng_user_typing" = "{user}님이 입력중입니다."; "lng_users_typing" = "{user}님과 {second_user}님이 입력중입니다."; "lng_many_typing#one" = "{count}명이 입력중"; "lng_many_typing#other" = "{count}명이 입력중"; "lng_playing_game" = "게임 중"; "lng_user_playing_game" = "{user}님이 게임 중입니다."; "lng_users_playing_game" = "{user}님과 {second_user}님이 게임 중입니다."; "lng_many_playing_game#one" = "{count}명이 게임 중"; "lng_many_playing_game#other" = "{count}명이 게임 중"; "lng_send_action_record_video" = "비디오 녹화 중"; "lng_user_action_record_video" = "{user}님이 녹화중입니다"; "lng_send_action_upload_video" = "비디오 전송 중"; "lng_user_action_upload_video" = "{user}님이 비디오를 전송 중입니다."; "lng_send_action_record_audio" = "음성 메시지 녹음 중"; "lng_user_action_record_audio" = "{user}님이 오디오를 녹음 중입니다"; "lng_send_action_upload_audio" = "음성 메시지 전송 중"; "lng_user_action_upload_audio" = "{user}님이 음성 메시지는 전송 중입니다"; "lng_send_action_record_round" = "비디오 메시지를 녹화 중"; "lng_user_action_record_round" = "{user}님이 비디오 메시지 녹화 중"; "lng_send_action_upload_round" = "비디오 메시지 전송 중"; "lng_user_action_upload_round" = "{user}님이 비디오 메시지 전송 중"; "lng_send_action_upload_photo" = "사진 전송 중"; "lng_user_action_upload_photo" = "{user}님이 사진을 전송 중입니다"; "lng_send_action_upload_file" = "파일 전송 중"; "lng_user_action_upload_file" = "{user}님이 파일을 전송 중입니다"; "lng_send_action_geo_location" = "위치 선택 중"; "lng_user_action_geo_location" = "{user}님이 위치를 선택 중입닏"; "lng_send_action_choose_contact" = "연락처 선택 중"; "lng_user_action_choose_contact" = "{user}님이 연락처를 선택 중입니다"; "lng_unread_bar#one" = "안 읽은 메시지 {count}개"; "lng_unread_bar#other" = "안 읽은 메시지 {count}개"; "lng_maps_point" = "위치"; "lng_save_photo" = "사진 저장"; "lng_save_video" = "동영상 저장"; "lng_save_audio_file" = "음성파일 저장"; "lng_save_audio" = "음성 메시지 저장"; "lng_save_file" = "파일 저장"; "lng_save_downloaded" = "{ready} / {total} {mb}"; "lng_duration_and_size" = "{duration}, {size}"; "lng_duration_played" = "{played} / {duration}"; "lng_date_and_duration" = "{date}, {duration}"; "lng_choose_image" = "이미지 선택"; "lng_choose_files" = "파일 선택"; "lng_game_tag" = "게임"; "lng_context_view_profile" = "프로필 보기"; "lng_context_view_group" = "그룹 정보 보기"; "lng_context_view_channel" = "채널 정보 보기"; "lng_context_pin_to_top" = "상단에 고정"; "lng_context_unpin_from_top" = "상단에서 고정해제"; "lng_context_promote_admin" = "관리자로 지정"; "lng_context_edit_permissions" = "권한 변경"; "lng_context_restrict_user" = "사용자 제한"; "lng_context_remove_from_group" = "그룹에서 추방"; "lng_context_copy_link" = "링크 복사"; "lng_context_copy_post_link" = "메시지 링크 복사"; "lng_context_copy_email" = "이메일 복사"; "lng_context_copy_hashtag" = "해시태그 복사"; "lng_context_copy_mention" = "아이디 복사"; "lng_context_save_image" = "이미지를 다른 이름으로 저장.."; "lng_context_forward_image" = "이미지 전달"; "lng_context_delete_image" = "이미지 삭제"; "lng_context_copy_image" = "이미지 복사"; "lng_context_close_image" = "이미지 닫기"; "lng_context_cancel_download" = "다운로드 취소"; "lng_context_show_in_folder" = "탐색기에서 보기"; "lng_context_show_in_finder" = "탐색기에서 보기"; "lng_context_save_video" = "비디오를 다른 이름으로 저장.."; "lng_context_save_audio_file" = "음성파일을 다른 이름으로 저장..."; "lng_context_save_audio" = "음성메시지를 다른 이름으로 저장..."; "lng_context_pack_info" = "팩 정보"; "lng_context_pack_add" = "스티커 추가"; "lng_context_save_file" = "파일을 다른 이름으로 저장..."; "lng_context_forward_file" = "파일 전달"; "lng_context_delete_file" = "파일 삭제"; "lng_context_close_file" = "파일 닫기"; "lng_context_copy_text" = "텍스트 복사"; "lng_context_open_gif" = "GIF 열기"; "lng_context_save_gif" = "GIF 저장"; "lng_context_to_msg" = "메시지로 이동"; "lng_context_reply_msg" = "답장"; "lng_context_edit_msg" = "수정"; "lng_context_forward_msg" = "메시지 전달"; "lng_context_delete_msg" = "메시지 삭제"; "lng_context_select_msg" = "메시지 선택"; "lng_context_pin_msg" = "메시지 고정"; "lng_context_unpin_msg" = "메시지 고정해제"; "lng_context_cancel_upload" = "업로드 취소"; "lng_context_copy_selected" = "선택한 텍스트 복사"; "lng_context_copy_selected_items" = "선택한 내용 텍스트 복사"; "lng_context_forward_selected" = "선택된 메시지 전달"; "lng_context_delete_selected" = "선택된 메시지 삭제"; "lng_context_clear_selection" = "선택된 메시지 초기화"; "lng_really_send_image" = "선택한 이미지를 전송하시겠습니까?"; "lng_really_send_file" = "선택한 파일을 전송하시겠습니까?"; "lng_really_share_contact" = "선택한 연락처를 공유하시겠습니까?"; "lng_send_images_compress#one" = "압축된 이미지"; "lng_send_images_compress#other" = "압축된 이미지"; "lng_send_image_non_local" = "로컬이 아닌 파일을 전송 할 수 없습니다 : {name}"; "lng_send_image_empty" = "빈 파일을 전송 할 수 없습니다 : {name}"; "lng_send_image_too_large" = "파일이 1500MB 이상이기 때문에 전송 할 수 없습니다: {name}"; "lng_send_folder" = " «{name}»은 폴더이기 때문에 전송 할 수 없습니다 :("; "lng_send_images_selected#one" = "이미지 {count}개가 선택됨"; "lng_send_images_selected#other" = "이미지 {count}개가 선택됨"; "lng_send_photos#one" = "{count}개의 사진 전송"; "lng_send_photos#other" = "{count}개의 사진 전송"; "lng_send_files_selected#one" = "파일 {count}개 선택됨"; "lng_send_files_selected#other" = "파일 {count}개 선택됨"; "lng_send_files#one" = "{count}개의 파일 전송"; "lng_send_files#other" = "{count}개의 파일 전송"; "lng_forward_choose" = "수신자를 선택.."; "lng_forward_cant" = "이쪽으로 전달 할 수 없습니다 :("; "lng_forward_confirm" = "{recipient} 님에게 전달하시겠습니까?"; "lng_forward_share_contact" = "{recipient} 님에게 연락처를 공유하시겠습니까?"; "lng_forward_share_cant" = "죄송합니다, 여기서 연락처를 공유할 수 없습니다 :("; "lng_forward_send_file_confirm" = "«{name}» 을 {recipient} 님에게 보내시겠습니까?"; "lng_forward_send_files_confirm" = "선택한 파일을 {recipient} 님에게 보내시겠습니까?"; "lng_forward_send_files_cant" = "죄송합니다, 여기서 미디어를 공유할 수 없습니다 :("; "lng_forward_send" = "보내기"; "lng_forward_messages#one" = "전달된 메시지 {count}개"; "lng_forward_messages#other" = "전달된 메시지 {count}개"; "lng_forwarding_from#one" = "{user}님 이외 {count}명"; "lng_forwarding_from#other" = "{user}님 이외 {count}명"; "lng_forwarding_from_two" = "{user} 님과 {second_user}"; "lng_inline_switch_choose" = "대화 선택..."; "lng_inline_switch_cant" = "죄송합니다. 이쪽으로 글을 쓸 수 없습니다 :("; "lng_share_cant" = "이쪽으로 공유 할 수 없습니다 :("; "lng_reply_cant" = "죄송합니다. 슈퍼그룹방의 이전 메시지에 답글을 할 수 없습니다 :("; "lng_reply_cant_forward" = "죄송합니다. 슈퍼그룹방의 이전 메시지에 답글을 할 수 없습니다 :( 메시지를 전달하거나 코멘트를 다시겠나요?"; "lng_share_title" = "공유하기"; "lng_share_copy_link" = "공유링크 복사"; "lng_share_confirm" = "전송"; "lng_share_wrong_user" = "다른 회원님이 게임을 시작하였습니다."; "lng_share_game_link_copied" = "클립보드에 게임링크가 복사되었습니다."; "lng_share_done" = "완료!"; "lng_contact_phone" = "전화번호"; "lng_enter_contact_data" = "새로운 연락처"; "lng_edit_group_title" = "그룹 이름 수정"; "lng_edit_contact_title" = "연락처 이름 수정"; "lng_edit_channel_title" = "채널 수정"; "lng_edit_sign_messages" = "서명 메시지"; "lng_edit_group_who_invites" = "초대가 가능한 사용자"; "lng_edit_group_invites_everybody" = "모든 사용자"; "lng_edit_group_invites_only_admins" = "관리자만 가능"; "lng_edit_group" = "그룹방 수정"; "lng_edit_self_title" = "이름 수정"; "lng_confirm_contact_data" = "새로운 연락처"; "lng_add_contact" = "추가"; "lng_add_contact_button" = "연락처 추가"; "lng_contacts_header" = "연락처"; "lng_contact_not_joined" = "{name}님은 아직 텔레그램에 가입되어져 있지 않지만 초대메시지를 보낼 수 있습니다.\n\n회원님 친구분들 중 텔레그램에 새로 가입을 할 경우 알림을 드리고 있습니다."; "lng_try_other_contact" = "다시 시도"; "lng_create_group_link" = "링크"; "lng_create_group_invite_link" = "초대링크"; "lng_create_group_photo" = "사진 설정"; "lng_create_group_description" = "설명 (선택사항)"; "lng_drag_images_here" = "이미지를 여기로 놓으세요."; "lng_drag_photos_here" = "사진을 여기로 놓으세요."; "lng_drag_files_here" = "파일을 여기로 놓으세요."; "lng_drag_to_send_quick" = "빠른 방법으로 보내기"; "lng_drag_to_send_no_compression" = "압축하지 않고 보내기"; "lng_drag_to_send_files" = "파일형태로 보내기"; "lng_selected_clear" = "취소"; "lng_selected_delete" = "삭제"; "lng_selected_forward" = "전달"; "lng_selected_count#one" = "메시지 {count}개"; "lng_selected_count#other" = "메시지 {count}개"; "lng_selected_cancel_sure_this" = "업로드를 취소하시겠습니까?"; "lng_selected_upload_stop" = "정지"; "lng_selected_delete_sure_this" = "메시지를 삭제하시겠습니까?"; "lng_selected_delete_sure#one" = "{count}개의 메시지를 삭제하시겠습니까?"; "lng_selected_delete_sure#other" = "{count}개의 메시지를 삭제하시겠습니까?"; "lng_delete_photo_sure" = "사진을 삭제하시겠습니까?"; "lng_delete_for_everyone_hint#one" = "이 메시지는 이 대화방에 있는 모두에게 삭제됩니다."; "lng_delete_for_everyone_hint#other" = "이 메시지는 이 대화방에 있는 모두에게 삭제됩니다."; "lng_delete_for_me_chat_hint#one" = "이 메시지는 다른 대화상대에게는 삭제가 되지 않고, 회원님에게만 삭제됩니다."; "lng_delete_for_me_chat_hint#other" = "이 메시지는 다른 대화상대에게는 삭제가 되지 않고, 회원님에게만 삭제됩니다."; "lng_delete_for_me_hint#one" = "이 메시지는 회원님에게만 삭제됩니다."; "lng_delete_for_me_hint#other" = "이 메시지는 회원님에게만 삭제됩니다."; "lng_delete_for_everyone_check" = "모두에게 삭제"; "lng_delete_for_other_check" = "{user}에게 메시지 삭제"; "lng_box_delete" = "삭제"; "lng_box_leave" = "나가기"; "lng_about_version" = "버전 {version}"; "lng_about_text_1" = "[a href=\"https://core.telegram.org/api\"]Telegram API[/a]에 기반하고 있는 공식 무료 메시징 앱으로 빠르고 안전합니다."; "lng_about_text_2" = "이 소프트웨어는 [a href=\"https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE\"]GNU GPL[/a] version 3 를 채택하고 있으며,\n소스코드는 [a href=\"https://github.com/telegramdesktop/tdesktop\"]GitHub[/a]에서 제공하고 있습니다."; "lng_about_text_3" = "자세한 사항은 {faq_open}Telegram FAQ{faq_close} 에서 확인해주세요."; "lng_about_done" = "완료"; "lng_search_no_results" = "메시지를 찾을 수 없습니다."; "lng_search_found_results#one" = "{count}개의 메시지를 찾았습니다."; "lng_search_found_results#other" = "{count}개의 메시지를 찾았습니다."; "lng_search_global_results" = "아이디 검색 결과"; "lng_search_messages_from" = "다음 사용자의 메시지 보기"; "lng_media_save_progress" = "{ready} / {total} {mb}"; "lng_mediaview_save_as" = " 다른 이름으로 저장..."; "lng_mediaview_copy" = "복사하기"; "lng_mediaview_forward" = "전달"; "lng_mediaview_delete" = "삭제"; "lng_mediaview_photos_all" = "모든 사진 보기"; "lng_mediaview_files_all" = "모든 파일 보기"; "lng_mediaview_single_photo" = "단일 사진"; "lng_mediaview_group_photo" = "그룹 사진"; "lng_mediaview_channel_photo" = "채널 사진"; "lng_mediaview_profile_photo" = "프로필 사진"; "lng_mediaview_file_n_of_count" = "{file} {n} 중 {count}"; "lng_mediaview_n_of_count" = "사진 {n} 중 {count}"; "lng_mediaview_doc_image" = "파일"; "lng_mediaview_today" = "오늘 {time}"; "lng_mediaview_yesterday" = "어제 {time}"; "lng_mediaview_date_time" = "{date} {time}"; "lng_mediaview_saved" = "[c]다운로드[/c] 폴더로 이미지가 저장되었습니다."; "lng_theme_preview_title" = "테마 미리보기"; "lng_theme_preview_generating" = "테마 색상 미리보기 생성중.."; "lng_theme_preview_invalid" = "테마 파일이 올바르지 않습니다."; "lng_theme_preview_apply" = "이 테마 적용"; "lng_new_authorization" = "{name}님,\n{time}, {date}.{day}에 새 기기에서 회원님의 계정 로그인이 감지되었습니다. \n\n기기: {device}\n위치: {location}\n\n본인의 접속이 아니라면 '설정' 창에서 '모든 세션 종료' 기능을 실행하세요.\n\n만약 강제접속 의심이 되신다면 2단계 인증을 설정 - 개인정보 및 보안에서 설정할 수 있습니다\n\n감사합니다.\n\n텔레그램 팀\n"; "lng_new_version_wrap" = "텔레그램 데스크탑은 {version} 버전으로 업데이트 되었습니다.\n\n{changes}\n\n전체 버전 히스토리는 아래에서 확인 가능합니다:\n{link}"; "lng_new_version_minor" = "— 버그 수정 및 일부 기능 향상"; "lng_new_version_text" = "— 영상 메시지 향상 : 원모양 재생 현황, 화면속 화면 지원, 재생시간 카운트다운\n— 음성 및 영상 메시지 순차적 자동재생"; "lng_menu_insert_unicode" = "유니코드 문자를 입력하세요."; "lng_full_name" = "{last_name} {first_name}"; "lng_confirm_phone_link_invalid" = "링크가 깨졌거나 폐기되었습니다."; "lng_confirm_phone_title" = "계정 초기화 취소"; "lng_confirm_phone_about" = "{phone} 번호에 접근이 가능한 누군가가 해당 계정 및 2단계 비밀번호 초기화를 요청하였습니다.\n\n만약 본인이 아니실 경우 방금 보내드린 코드를 본인 번호에 SMS로 전송해주세요."; "lng_confirm_phone_success" = "성공!\n\n{phone} 계정에 대한 삭제 프로세스가 취소 되었습니다. 이창을 당으셔도 됩니다."; "lng_confirm_phone_send" = "보내기"; "lng_confirm_phone_enter_code" = "코드를 입력해주세요"; "lng_theme_editor_no_keys" = "팔레트에 아직 키가 없습니다"; "lng_theme_editor_cant_change_theme" = "색상 팔레트를 수정하는 동시에 새로운 테마를 적용할 수 없습니다. 테마 에디터를 먼저 닫아주세요."; "lng_theme_editor_new_keys" = "팔레트에 아직 없습니다."; "lng_theme_editor_background_image" = "배경 이미지"; "lng_theme_editor_saved_to_jpg" = "JPEG로 저장, {size}"; "lng_theme_editor_read_from_jpg" = "JPEG 이미지, {size}"; "lng_theme_editor_read_from_png" = "PNG 이미지, {size}"; "lng_theme_editor_export" = "내보내기"; "lng_theme_editor_choose_image" = "배경 이미지 선택"; "lng_theme_editor_save_palette" = "팔레트 파일 저장"; "lng_theme_editor_choose_name" = "테마 파일 저장"; "lng_theme_editor_error" = "에디터 오류가 발생하였습니다 :( 'log.txt' 에서 자세한 내역 확인하세요."; "lng_theme_editor_done" = "성공적으로 테마 내보내기가 완료되었습니다!"; "lng_theme_editor_title" = "색상 팔레트 수정"; "lng_theme_editor_export_button" = "테마 내보내기"; "lng_payments_not_supported" = "죄송합니다, 텔레그램 데스크탑은 아직 결제를 지원하고 있지 않습니다. 모바일앱을 사용해주세요."; "lng_payments_receipt_label" = "영수증"; "lng_payments_receipt_label_test" = "테스트 영수증"; "lng_payments_invoice_label" = "인보이스"; "lng_payments_invoice_label_test" = "테스트 인보이스"; "lng_payments_receipt_button" = "영수증"; "lng_call_status_incoming" = "님으로 부터 전화가 오고 있습니다..."; "lng_call_status_connecting" = "연결중..."; "lng_call_status_exchanging" = "암호화 키 교환중..."; "lng_call_status_waiting" = "기다리는중..."; "lng_call_status_requesting" = "요청중.."; "lng_call_status_hanging" = "전화 끊는 중..."; "lng_call_status_ended" = "전화 종료"; "lng_call_status_failed" = "연결 실패"; "lng_call_status_ringing" = "전화중.."; "lng_call_status_busy" = "통화중"; "lng_call_fingerprint_tooltip" = "{user}님 화면과 이모티콘이 동일하다면, 이 통화는 100% 안전합니다"; "lng_call_error_not_available" = "죄송하지만, {user}는 전화 수신거부 상태입니다."; "lng_call_error_incompatible" = "{user}님 앱이 현재 적합하지 않은 프로토콜을 사용하고 있습니다. 전화를 하시려면 상대방 앱을 업데이트해야합니다."; "lng_call_error_outdated" = "{user}님의 앱이 전화를 지원하고 있지 않습니다. 상대방 앱을 업데이트 해야합니다."; "lng_call_error_audio_io" = "회원님 컴퓨터의 음성 재생에 문제가 있습니다. 스피커와 마이크가 작동을 하는지 확인 후 다시 시도해주세요."; "lng_call_bar_hangup" = "통화종료"; "lng_call_box_title" = "전화"; "lng_call_box_about" = "아직 전화내역이 없습니다"; "lng_call_box_status_today" = "{time}"; "lng_call_box_status_yesterday" = "어제 {time}"; "lng_call_box_status_date" = "{date} {time}"; "lng_call_box_status_group" = "({count}) {status}"; "lng_call_outgoing" = "발신 전화"; "lng_call_incoming" = "수신 전화"; "lng_call_missed" = "부재중 전화"; "lng_call_cancelled" = "취소한 전화"; "lng_call_declined" = "거부한 전화"; "lng_call_duration_info" = "{time}, {duration}"; "lng_call_type_and_duration" = "{type} ({duration})"; "lng_call_rate_label" = "통화품질을 평가해주세요"; "lng_call_rate_comment" = "의견 (선택사항)"; "lng_player_message_today" = "오늘 {time}"; "lng_player_message_yesterday" = "어제 {time}"; "lng_player_message_date" = "{date} {time}"; "lng_rights_edit_admin" = "관리가 변경"; "lng_rights_edit_admin_header" = "이 관리자가 할 수 있는 것은?"; "lng_rights_about_add_admins_yes" = "이 관리자는 동일한 권한 (혹은 더 제한된) 으로만 관리자 추가가 가능합니다."; "lng_rights_about_add_admins_no" = "이 관리자는 새로운 관리자 설정을 할 수 없습니다."; "lng_rights_about_admin_cant_edit" = "이 관리자의 권한을 변경 할 수 없습니다."; "lng_rights_user_restrictions" = "사용자 제한"; "lng_rights_user_restrictions_header" = "이 사용자가 할 수 있는 것은?"; "lng_rights_channel_info" = "채널 정보 변경"; "lng_rights_channel_post" = "메시지 작성"; "lng_rights_channel_edit" = "다른 사용자 메시지 수정"; "lng_rights_channel_delete" = "다른 사용자 메시지 삭제"; "lng_rights_group_info" = "그룹 정보 변경"; "lng_rights_group_ban" = "사용자 차단"; "lng_rights_group_invite_link" = "링크를 통하여 사용자 초대"; "lng_rights_group_invite" = "사용자 추가"; "lng_rights_group_pin" = "메시지 고정"; "lng_rights_group_delete" = "메시지 삭제"; "lng_rights_add_admins" = "새 관리자 추가"; "lng_rights_chat_read" = "메시지 읽기"; "lng_rights_chat_send_text" = "메시지 전송"; "lng_rights_chat_send_media" = "미디어 전송"; "lng_rights_chat_send_stickers" = "스티커 & GIF 전송"; "lng_rights_chat_send_links" = "링크 저장"; "lng_rights_chat_banned_until_header" = "다음까지 제한"; "lng_rights_chat_banned_forever" = "영원히"; "lng_rights_chat_banned_day#one" = "{count} 일 까지"; "lng_rights_chat_banned_day#other" = "{count} 일 까지"; "lng_rights_chat_banned_week#one" = "{count} 주 까지"; "lng_rights_chat_banned_week#other" = "{count} 주 까지"; "lng_rights_chat_banned_custom" = "커스텀"; "lng_rights_chat_banned_custom_date" = "{date} 일까지"; "lng_rights_chat_banned_block" = "그룹에서 차단 후 추방"; "lng_restricted_send_message" = "그룹 관리자가 회원님의 메시지 작성을 제한했습니다."; "lng_restricted_send_media" = "그룹 관리자가 회원님의 미디어 전송을 제한했습니다."; "lng_restricted_send_stickers" = "그룹 관리자가 회원님의 스티커 전송을 제한했습니다."; "lng_restricted_send_gifs" = "그룹 관리자가 회원님의 GIF 전송을 제한했습니다."; "lng_restricted_send_inline" = "그룹 관리자가 회원님의 인라인 명령어를 제한했습니다."; "lng_restricted_list_title" = "제한된 사용자"; "lng_banned_list_title" = "차단된 사용자"; "lng_admin_log_title_all" = "모든 활동"; "lng_admin_log_title_selected" = "선택한 활동"; "lng_admin_log_filter" = "필터"; "lng_admin_log_filter_title" = "필터"; "lng_admin_log_filter_all_actions" = "모든 활동"; "lng_admin_log_filter_restrictions" = "새 제한"; "lng_admin_log_filter_admins_new" = "새 관리자"; "lng_admin_log_filter_members_new" = "새 사용자"; "lng_admin_log_filter_info_group" = "그룹 정보"; "lng_admin_log_filter_info_channel" = "채널 정보"; "lng_admin_log_filter_messages_deleted" = "삭제된 메시지"; "lng_admin_log_filter_messages_edited" = "수정된 메시지"; "lng_admin_log_filter_messages_pinned" = "고정된 메시지"; "lng_admin_log_filter_members_removed" = "추방된 사용자"; "lng_admin_log_filter_all_admins" = "모든 사용자 및 관리자"; "lng_admin_log_about" = "이것은 무엇인가요?"; "lng_admin_log_about_text" = "그룹 사용자와 관리자의 지난 48시간동안 활동 리스트입니다."; "lng_admin_log_no_results_title" = "활동내역을 찾을 수 없습니다."; "lng_admin_log_no_results_text" = "검색한 내용의 활동을 찾을 수 없습니다."; "lng_admin_log_no_results_search_text" = "검색한 '{query}'을/를 포함한 활동을 찾을 수 없습니다."; "lng_admin_log_no_events_title" = "아직 활동내역이 없습니다."; "lng_admin_log_no_events_text" = "그룹 사용자와 관리자의\n지난 48시간동안\n활동 리스트입니다."; "lng_admin_log_empty_text" = "없음"; "lng_admin_log_changed_title_group" = "{from} 님이 그룹이름을 «{title}» 로 바꾸셨습니다."; "lng_admin_log_changed_title_channel" = "{from} 님이 채널이름을 «{title}» 로 바꾸셨습니다."; "lng_admin_log_changed_description_group" = "{from} 님이 그룹정보을 변경했습니다:"; "lng_admin_log_removed_description_group" = "{from} 님이 그룹정보을 삭제했습니다."; "lng_admin_log_changed_description_channel" = "{from} 님이 채널정보을 변경했습니다:"; "lng_admin_log_removed_description_channel" = "{from} 님이 그룹정보을 삭제했습니다."; "lng_admin_log_previous_description" = "기존 정보"; "lng_admin_log_changed_link_group" = "{from} 님이 그룹링크을 변경했습니다:"; "lng_admin_log_removed_link_group" = "{from} 님이 그룹링크를 삭제했습니다."; "lng_admin_log_changed_link_channel" = "{from} 님이 채널링크를 변경했습니다:"; "lng_admin_log_removed_link_channel" = "{from} 님이 채널링크를 삭제했습니다."; "lng_admin_log_previous_link" = "기존 링크"; "lng_admin_log_changed_photo_group" = "{from} 님이 그룹사진을 변경했습니다."; "lng_admin_log_changed_photo_channel" = "{from} 님이 채널사진을 변경했습니다."; "lng_admin_log_removed_photo_group" = "{from} 님이 그룹사진을 삭제했습니다."; "lng_admin_log_removed_photo_channel" = "{from} 님이 채널사진을 삭제했습니다."; "lng_admin_log_invites_enabled" = "{from} 님이 그룹 초대를 허용했습니다."; "lng_admin_log_invites_disabled" = "{from} 님이 그룹 초대를 금지했습니다."; "lng_admin_log_signatures_enabled" = "{from} 님이 서명을 허용했습니다."; "lng_admin_log_signatures_disabled" = "{from} 님이 서명을 금지했습니다."; "lng_admin_log_pinned_message" = "{from} 님이 메시지를 고정했습니다:"; "lng_admin_log_unpinned_message" = "{from} 님이 메시지 고정을 해제했습니다."; "lng_admin_log_edited_caption" = "{from} 님이 자막을 변경했습니다:"; "lng_admin_log_removed_caption" = "{from} 님이 자막은 삭제했습니다"; "lng_admin_log_previous_caption" = "원본 자막"; "lng_admin_log_edited_message" = "{from} 님이 메시지를 수정했습니다:"; "lng_admin_log_previous_message" = "원본 메시지"; "lng_admin_log_deleted_message" = "{from} 님이 메시지를 삭제했습니다:"; "lng_admin_log_participant_joined" = "{from} 님이 그룹에 들어왔습니다."; "lng_admin_log_participant_joined_channel" = "{from} 님이 채널에 들어오셨습니다."; "lng_admin_log_participant_left" = "{from} 님이 그룹을 나갔습니다."; "lng_admin_log_participant_left_channel" = "{from} 님이 채널을 나가셨습니다."; "lng_admin_log_invited" = "{user} 님을 초대함"; "lng_admin_log_banned" = "{user} 님을 차단함"; "lng_admin_log_restricted" = "{until}까지 {user}에 대한 제한을 변경함"; "lng_admin_log_promoted" = "{user}님에 대한 권한을 변경함"; "lng_admin_log_user_with_username" = "{name} ({mention})"; "lng_admin_log_restricted_forever" = "기한 없음"; "lng_admin_log_restricted_until" = "{date} 까지"; "lng_admin_log_banned_view_messages" = "메시지 읽기"; "lng_admin_log_banned_send_messages" = "메시지 전송"; "lng_admin_log_banned_send_media" = "미디어 전송"; "lng_admin_log_banned_send_stickers" = "스티커 & GIF 전송"; "lng_admin_log_banned_embed_links" = "링크 저장"; "lng_admin_log_admin_change_info" = "채널 정보"; "lng_admin_log_admin_post_messages" = "메시지 작성"; "lng_admin_log_admin_edit_messages" = "메시지 수정"; "lng_admin_log_admin_delete_messages" = "메시지 삭제"; "lng_admin_log_admin_ban_users" = "사용자 차단"; "lng_admin_log_admin_invite_users" = "사용자 추가"; "lng_admin_log_admin_invite_link" = "링크를 통하여 사용자 초대"; "lng_admin_log_admin_pin_messages" = "메시지 고정"; "lng_admin_log_admin_add_admins" = "새 관리자 추가"; // Not used "lng_topbar_info" = "정보"; "lng_profile_group_info" = "그룹 정보"; "lng_profile_channel_info" = "채널 정보"; "lng_channel_add_admins" = "새로운 관리자"; // Wnd specific "lng_wnd_choose_program_menu" = "기본 실행 프로그램을 선택해주세요.."; "lng_wnd_menu_undo" = "실행취소"; "lng_wnd_menu_redo" = "다시실행"; // Linux specific "lng_linux_menu_undo" = "실행취소"; "lng_linux_menu_redo" = "다시실행"; // Mac specific "lng_mac_choose_program_menu" = "이외..."; "lng_mac_choose_app" = "어플리케이션 선택"; "lng_mac_choose_text" = " \"{file}\"을 실행할 어플리케이션을 선택하세요."; "lng_mac_enable_filter" = "활성화:"; "lng_mac_recommended_apps" = "추천 어플리케이션"; "lng_mac_all_apps" = "모든 어플리케이션"; "lng_mac_always_open_with" = "항상 실행"; "lng_mac_this_app_can_open" = "\"{file}\"을 실행할 수 있는 어플리케이션입니다."; "lng_mac_not_known_app" = "\"{file}\"을 열수 있는 어플리케이션인지 알 수 없습니다."; "lng_mac_menu_services" = "서비스"; "lng_mac_menu_hide_telegram" = "숨기기 {telegram}"; "lng_mac_menu_hide_others" = "나머지 숨기기"; "lng_mac_menu_show_all" = "전체 보기"; "lng_mac_menu_preferences" = "환경설정..."; "lng_mac_menu_quit_telegram" = "{telegram} 종료 "; "lng_mac_menu_about_telegram" = "{telegram} 정보"; "lng_mac_menu_file" = "파일"; "lng_mac_menu_logout" = "로그아웃"; "lng_mac_menu_edit" = "수정"; "lng_mac_menu_undo" = "실행취소"; "lng_mac_menu_redo" = "다시실행"; "lng_mac_menu_cut" = "잘라내기"; "lng_mac_menu_copy" = "복사"; "lng_mac_menu_paste" = "붙여넣기"; "lng_mac_menu_delete" = "삭제"; "lng_mac_menu_select_all" = "전체선택"; "lng_mac_menu_window" = "윈도우"; "lng_mac_menu_contacts" = "주소록"; "lng_mac_menu_add_contact" = "연락처 추가"; "lng_mac_menu_new_group" = "새로운 그룹"; "lng_mac_menu_new_channel" = "새로운 채널"; "lng_mac_menu_show" = "텔레그램 보기"; // Keys finished tdesktop-1.2.17/Telegram/Resources/langs/lang_nl.strings000066400000000000000000002364551326245125100233220ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ "lng_language_name" = "Nederlands"; "lng_switch_to_this" = "Overschakelen naar Nederlands"; "lng_menu_contacts" = "Contacten"; "lng_menu_calls" = "Spraakoproepen"; "lng_menu_settings" = "Instellingen"; "lng_menu_about" = "Over"; "lng_menu_update" = "Bijwerken"; "lng_menu_restart" = "Herstarten"; "lng_menu_back" = "Vorige"; "lng_menu_night_mode" = "Nachtmodus"; "lng_disable_notifications_from_tray" = "Meldingen uitschakelen"; "lng_enable_notifications_from_tray" = "Meldingen inschakelen"; "lng_open_from_tray" = "Telegram openen"; "lng_minimize_to_tray" = "Minimaliseer naar systeemvak"; "lng_quit_from_tray" = "Telegram afsluiten"; "lng_tray_icon_text" = "Telegram is nog steeds actief, \nje kunt dit wijzigen via instellingen.\nAls dit icoon verdwijnt van het systeemvak,\nkun je deze terugslepen vanaf de verborgen iconen."; "lng_month1" = "januari"; "lng_month2" = "februari"; "lng_month3" = "maart"; "lng_month4" = "april"; "lng_month5" = "mei"; "lng_month6" = "juni"; "lng_month7" = "juli"; "lng_month8" = "augustus"; "lng_month9" = "september"; "lng_month10" = "oktober"; "lng_month11" = "november"; "lng_month12" = "december"; "lng_month1_small" = "jan"; "lng_month2_small" = "feb"; "lng_month3_small" = "mrt"; "lng_month4_small" = "apr"; "lng_month5_small" = "mei"; "lng_month6_small" = "jun"; "lng_month7_small" = "jul"; "lng_month8_small" = "aug"; "lng_month9_small" = "sep"; "lng_month10_small" = "okt"; "lng_month11_small" = "nov"; "lng_month12_small" = "dec"; "lng_weekday1" = "ma"; "lng_weekday2" = "di"; "lng_weekday3" = "wo"; "lng_weekday4" = "do"; "lng_weekday5" = "vr"; "lng_weekday6" = "za"; "lng_weekday7" = "zo"; "lng_weekday1_full" = "maandag"; "lng_weekday2_full" = "dinsdag"; "lng_weekday3_full" = "woensdag"; "lng_weekday4_full" = "donderdag"; "lng_weekday5_full" = "vrijdag"; "lng_weekday6_full" = "zaterdag"; "lng_weekday7_full" = "zondag"; "lng_month_day" = "{day} {month}"; "lng_month_day_year" = "{day} {month}, {year}"; "lng_month_year" = "{month} {year}"; "lng_box_ok" = "Ok"; "lng_cancel" = "Annuleren"; "lng_continue" = "Doorgaan"; "lng_close" = "Sluiten"; "lng_connecting" = "Verbinden..."; "lng_connecting_to_proxy" = "Verbinden via proxy..."; "lng_connecting_settings" = "Instellingen"; "lng_reconnecting#one" = "Opnieuw verbinden over {count}s..."; "lng_reconnecting#other" = "Opnieuw verbinden over {count}s..."; "lng_reconnecting_try_now" = "Probeer nu"; "lng_status_service_notifications" = "servicemeldingen"; "lng_status_support" = "ondersteuning"; "lng_status_bot" = "bot"; "lng_status_bot_reads_all" = "toegang tot berichten"; "lng_status_bot_not_reads_all" = "geen toegang tot berichten"; "lng_status_offline" = "lang geleden gezien"; "lng_status_recently" = "recent gezien"; "lng_status_last_week" = "afgelopen week gezien"; "lng_status_last_month" = "afgelopen maand gezien"; "lng_status_invisible" = "onzichtbaar"; "lng_status_lastseen_now" = "laatst gezien zojuist"; "lng_status_lastseen_minutes#one" = "laatst gezien {count} minuut geleden"; "lng_status_lastseen_minutes#other" = "laatst gezien {count} minuten geleden"; "lng_status_lastseen_hours#one" = "laatst gezien {count} uur geleden"; "lng_status_lastseen_hours#other" = "laatst gezien {count} uur geleden"; "lng_status_lastseen_today" = "laatst gezien vandaag om {time}"; "lng_status_lastseen_yesterday" = "laatst gezien gisteren om {time}"; "lng_status_lastseen_date" = "laatst gezien {date}"; "lng_status_lastseen_date_time" = "laatst gezien {date} om {time}"; "lng_status_online" = "online"; "lng_status_connecting" = "verbinden..."; "lng_chat_status_unaccessible" = "Groep is ontoegankelijk"; "lng_chat_status_no_members" = "geen leden"; "lng_chat_status_members#one" = "{count} lid"; "lng_chat_status_members#other" = "{count} leden"; "lng_chat_status_online#one" = "{count} online"; "lng_chat_status_online#other" = "{count} online"; "lng_chat_status_members_online" = "{members_count}, {online_count}"; "lng_channel_status" = "kanaal"; "lng_group_status" = "groep"; "lng_channel_members_link#one" = "{count} lid"; "lng_channel_members_link#other" = "{count} leden"; "lng_channel_admins_link#one" = "{count} beheerder"; "lng_channel_admins_link#other" = "{count} beheerders"; "lng_server_error" = "Interne serverfout."; "lng_flood_error" = "Teveel pogingen. Probeer het later opnieuw."; "lng_gif_error" = "Er is iets misgegaan bij het lezen van deze GIF :("; "lng_edit_error" = "Je mag dit bericht niet wijzigen"; "lng_join_channel_error" = "Je bent lid van teveel kanalen of supergroepen, verlaat er wat om hier lid te worden."; "lng_error_phone_flood" = "Je hebt in korte tijd je account veelvuldig verwijderd en opnieuw aangemaakt. Probeer het over een aantal dagen nog eens."; "lng_error_start_minimized_passcoded" = "Je hebt een toegangscode ingesteld, deze zal ingegeven moeten worden na het starten, geminimaliseerd starten kan daarom niet."; "lng_error_pinned_max#one" = "Sorry, je kunt niet meer dan {count} chat vastzetten."; "lng_error_pinned_max#other" = "Sorry, je kunt niet meer dan {count} chats vastzetten."; "lng_error_public_groups_denied" = "Sorry, je mag niet meer deelnemen aan publieke groepen.\n{more_info}"; "lng_error_cant_edit_admin" = "Je kunt de machtigingen van deze beheerder niet wijzigen."; "lng_error_cant_add_member" = "Je kunt deze bot niet toevoegen aan de groep, vraag een beheerder om dit te doen."; "lng_error_cant_add_bot" = "Deze bot kan geen lid worden van groepen."; "lng_error_cant_add_admin_invite" = "Je mag geen leden toevoegen, deze gebruiker is nog geen lid en kan daarom niet worden toegevoegd als beheerder."; "lng_error_cant_add_admin_unban" = "Je mag geen leden deblokkeren, deze gebruiker is geblokkeerd en kan daarom niet worden toegevoegd als beheerder."; "lng_error_cant_ban_admin" = "Je mag geen leden degraderen, deze gebruiker is een beheerder en kan daarom niet worden geblokkeerd."; "lng_sure_add_admin_invite" = "Deze gebruiker is geen lid, gebruiker toevoegen als beheerder?"; "lng_sure_add_admin_unban" = "Deze gebruiker is geblokkeerd of beperkt, wil je dit ongedaan maken en de gebruiker promoveren?"; "lng_sure_ban_admin" = "Deze gebruiker is een beheerder, gebruiker echt beperken?"; "lng_sure_ban_user_group" = "{user} blokkeren en verwijderen?"; "lng_sure_enable_socks" = "Proxy echt inschakelen? \n\nHost: {server}\nPoort: {port}\n\nJe kunt de proxy-server aanpassen via Instellingen (Verbindingstype)."; "lng_sure_enable" = "Inschakelen"; "lng_edit_deleted" = "Bericht is gewist"; "lng_edit_too_long" = "Je bericht is te lang"; "lng_edit_message" = "Bericht wijzigen"; "lng_edit_message_text" = "Nieuw bericht..."; "lng_deleted" = "Verwijderd Account"; "lng_deleted_message" = "Verwijderd bericht"; "lng_pinned_message" = "Vastgezet bericht"; "lng_pinned_unpin_sure" = "Wil je dit bericht losmaken?"; "lng_pinned_pin_sure" = "Wil je dit bericht vastzetten?"; "lng_pinned_pin" = "Vastzetten"; "lng_pinned_unpin" = "Losmaken"; "lng_pinned_notify" = "Leden informeren"; "lng_intro_about" = "Welkom bij de officiële Telegram desktop-app.\nSnel en veilig."; "lng_start_msgs" = "BEGIN MET CHATTEN"; "lng_intro_next" = "VOLGENDE"; "lng_intro_finish" = "AANMELDEN"; "lng_intro_submit" = "VERSTUREN"; "lng_photo_caption" = "Onderschrift"; "lng_photos_comment" = "Opmerking"; "lng_phone_ph" = "Je telefoonnummer"; "lng_phone_title" = "Je telefoon"; "lng_phone_desc" = "Bevestig je landnummer en \nvoer je telefoonnummer in."; "lng_phone_notreg" = "Als je nog geen Telegram-account hebt,\nkun je je [b]aanmelden [/b] met {link_start}Android / iPhone{link_end} of {signup_start}hier{signup_end}"; "lng_country_code" = "Landnummer"; "lng_bad_country_code" = "Ongeldig landnummer"; "lng_country_ph" = "Zoeken"; "lng_country_done" = "Gereed"; "lng_country_none" = "Land niet gevonden"; "lng_country_select" = "Land kiezen"; "lng_code_ph" = "Je code"; "lng_code_desc" = "We hebben een bericht met activatiecode\nverstuurd naar je nummer, geef deze hieronder in."; "lng_code_telegram" = "Voer de code in die je zojuist ontvangen\nhebt in je vorige [b]Telegram[/b]-app."; "lng_code_no_telegram" = "Verstuur code via SMS"; "lng_code_call" = "Telegram belt je over {minutes}:{seconds}"; "lng_code_calling" = "Oproepverzoek naar Telegram..."; "lng_code_called" = "Telegram heeft je nummer gebeld"; "lng_bad_phone" = "Ongeldig telefoonnummer. "; "lng_bad_phone_noreg" = "Telefoonnummer is niet geregistreerd."; "lng_bad_code" = "Je hebt een ongeldige code ingevoerd."; "lng_bad_name" = "Voer je voor- en achternaam in."; "lng_bad_photo" = "Ongeldig bestand gekozen."; "lng_signin_title" = "Cloud-wachtwoord:"; "lng_signin_desc" = "Cloud-wachtwoord invoeren"; "lng_signin_recover_desc" = "Voer de code uit de e-mail in\n{email}"; "lng_signin_password" = "Je cloud-wachtwoord"; "lng_signin_code" = "Code uit de e-mail"; "lng_signin_recover" = "Wachtwoord vergeten?"; "lng_signin_recover_title" = "Wachtwoord reset"; "lng_signin_hint" = "Hint: {password_hint}"; "lng_signin_recover_hint" = "De code is verstuurd naar {recover_email}"; "lng_signin_bad_password" = "Ongeldig wachtwoord."; "lng_signin_wrong_code" = "Je hebt een ongeldige code ingevoerd."; "lng_signin_try_password" = "Kun je je e-mail niet benaderen?"; "lng_signin_password_removed" = "Je cloud-wachtwoord is uitgeschakeld.\nVia instellingen kun je een nieuwe instellen."; "lng_signin_no_email_forgot" = "Omdat je geen herstel-e-mailadres hebt opgegeven voor je wachtwoord zul je bij verlies van je wachtwoord je account moeten resetten."; "lng_signin_cant_email_forgot" = "Als je geen toegang meer hebt tot je e-mail en je ingestelde wachtwoord zul je je account moeten resetten."; "lng_signin_reset_account" = "Account resetten"; "lng_signin_sure_reset" = "Let op:\n\nAl je chats, berichten en alle andere data gaan verloren als je verder gaat!\n\nEcht je account resetten?"; "lng_signin_reset" = "Reset"; "lng_signin_reset_wait" = "Account {phone_number} is actief en beveiligd met een wachtwoord, het verwijderen stellen we daarom 1 week uit.\nJe kunt dit proces ieder moment annuleren.\n\nJe account kan gereset worden over:\n{when}"; "lng_signin_reset_days#one" = "{count} dag"; "lng_signin_reset_days#other" = "{count} dagen"; "lng_signin_reset_hours#one" = "{count} uur"; "lng_signin_reset_hours#other" = "{count} uur"; "lng_signin_reset_minutes#one" = "{count} minuut"; "lng_signin_reset_minutes#other" = "{count} minuten"; "lng_signin_reset_in_days" = "{days_count} {hours_count} {minutes_count}"; "lng_signin_reset_in_hours" = "{hours_count} {minutes_count}"; "lng_signin_reset_cancelled" = "De actieve gebruiker heeft de recente poging om dit account te resetten geannuleerd. Probeer het over 7 dagen nog eens."; "lng_signup_title" = "Jouw Informatie"; "lng_signup_desc" = "Voer je naam en\nupload een foto."; "lng_signup_firstname" = "Voornaam"; "lng_signup_lastname" = "Achternaam"; "lng_dlg_filter" = "Zoeken"; "lng_dlg_new_group_name" = "Groepsnaam"; "lng_dlg_new_channel_name" = "Kanaalnaam"; "lng_no_contacts" = "Je hebt geen contacten"; "lng_no_chats" = "Hier komen je chats"; "lng_contacts_loading" = "Laden..."; "lng_contacts_not_found" = "Geen contacten gevonden"; "lng_dlg_search_for_messages" = "Zoek berichten"; "lng_update_telegram" = "Telegram bijwerken"; "lng_dlg_search_in" = "Zoek berichten"; "lng_dlg_search_from" = "Van: {user}"; "lng_settings_save" = "Opslaan"; "lng_settings_upload" = "Profielfoto instellen"; "lng_settings_crop_profile" = "Kies een gebied voor je profielfoto"; "lng_settings_uploading_photo" = "Foto uploaden..."; "lng_settings_edit" = "Wijzig"; "lng_settings_drop_area_subtitle" = "om als foto in te stellen"; "lng_username_title" = "Gebruikersnaam"; "lng_username_about" = "Je kunt hier je gebruikersnaam kiezen. \nHiermee kunnen anderen je vinden \nen contact met je opnemen zonder \nje telefoonnummer te weten.\n\na-z, 0-9 en underscore is toegestaan. \nDe minimale lengte is 5 tekens."; "lng_username_choose" = "Gebruikersnaam kiezen"; "lng_username_invalid" = "Deze gebruikersnaam is ongeldig."; "lng_username_occupied" = "Deze gebruikersnaam is al bezet"; "lng_username_too_short" = "Deze gebruikersnaam is te kort."; "lng_username_bad_symbols" = "Deze gebruikersnaam bevat ongeldige tekens."; "lng_username_available" = "Deze gebruikersnaam is beschikbaar."; "lng_username_not_found" = "Gebruiker @{user} niet gevonden."; "lng_username_link_willbe" = "Zo komt je link eruit te zien:"; "lng_username_link" = "Deze link opent een chat met jou:"; "lng_username_copied" = "Link gekopieerd naar klembord."; "lng_bio_title" = "Bio wijzigen"; "lng_bio_placeholder" = "Bio"; "lng_bio_about" = "Je kunt hier wat over jezelf vertellen. Iedereen die je profiel opent kan deze tekst zien."; "lng_settings_section_info" = "Info"; "lng_settings_phone_number" = "Telefoonnummer:"; "lng_settings_username" = "Gebruikersnaam:"; "lng_settings_choose_username" = "gebruikersnaam kiezen"; "lng_settings_empty_bio" = "Niets"; "lng_settings_section_notify" = "Meldingen"; "lng_settings_desktop_notify" = "Desktopmeldingen"; "lng_settings_show_name" = "Afzender weergeven"; "lng_settings_show_preview" = "Voorbeeld weergeven"; "lng_settings_use_windows" = "Windows-berichtgeving gebruiken"; "lng_settings_use_native_notifications" = "Linux-berichtgeving gebruiken"; "lng_settings_advanced_notifications" = "Plaats en teller voor meldingen"; "lng_settings_notifications_position" = "Plaats op het beeldscherm"; "lng_settings_notifications_count" = "Teller weergeven"; "lng_settings_sound_notify" = "Geluiden afspelen"; "lng_settings_include_muted" = "Stille chats meetellen in ongelezen"; "lng_notification_preview" = "Je hebt een nieuw bericht"; "lng_notification_reply" = "Antwoord"; "lng_notification_hide_all" = "Alles verbergen"; "lng_notification_sample" = "Berichtgevingsvoorbeeld"; "lng_settings_section_general" = "Algemeen"; "lng_settings_change_lang" = "Taal wijzigen"; "lng_languages" = "Talen"; "lng_sure_save_language" = "Opnieuw starten om de taal te wijzen?"; "lng_settings_update_automatically" = "Automatisch bijwerken"; "lng_settings_current_version_label" = "Versie {version}:"; "lng_settings_current_version" = "Versie {version}"; "lng_settings_check_now" = "Controleer op updates"; "lng_settings_update_checking" = "Controleren op updates..."; "lng_settings_latest_installed" = "Meest recente versie is geïnstalleerd."; "lng_settings_downloading" = "Update downloaden {ready} / {total} MB..."; "lng_settings_update_ready" = "Nieuwe versie staat klaar"; "lng_settings_update_now" = "Nu herstarten"; "lng_settings_update_fail" = "Controleren op updates mislukt"; "lng_settings_workmode_tray" = "Pictogram in systeemvak weergeven"; "lng_settings_workmode_window" = "Pictogram op taakbalk weergeven"; "lng_settings_auto_start" = "Uitvoeren bij opstarten"; "lng_settings_start_min" = "Minimaliseren bij uitvoeren"; "lng_settings_add_sendto" = "Toevoegen aan \"Kopiëren naar\"-menu"; "lng_settings_section_scale" = "Interfaceschaal"; "lng_settings_scale_auto" = "Auto ({cur})"; "lng_settings_section_chat_settings" = "Chatinstellingen"; "lng_settings_replace_emojis" = "Emoji's vervangen"; "lng_settings_view_emojis" = "Lijst bekijken"; "lng_settings_emoji_list" = "Ondersteunde emoji's"; "lng_settings_send_enter" = "Versturen met Enter"; "lng_settings_send_ctrlenter" = "Versturen met Ctrl+Enter"; "lng_settings_send_cmdenter" = "Versturen met Cmd+Enter"; "lng_settings_section_background" = "Achtergrond"; "lng_settings_bg_use_default" = "Standaardthema gebruiken"; "lng_settings_bg_from_gallery" = "Uit galerij kiezen"; "lng_settings_bg_from_file" = "Bestand kiezen"; "lng_settings_bg_edit_theme" = "Thema wijzigen"; "lng_settings_bg_tile" = "Naast elkaar"; "lng_settings_adaptive_wide" = "Adaptieve layout voor brede schermen"; "lng_backgrounds_header" = "Kies je nieuwe achtergrond"; "lng_theme_sure_keep" = "Dit thema behouden?"; "lng_theme_reverting#one" = "Ongedaan maken over {count} seconde."; "lng_theme_reverting#other" = "Ongedaan maken over {count} seconden."; "lng_theme_keep_changes" = "Behouden"; "lng_theme_revert" = "Herstellen"; "lng_download_path_dont_ask" = "Niet voor iedere download vragen"; "lng_download_path_label" = "Downloadpad:"; "lng_download_path_temp" = "temp map"; "lng_download_path_default" = "standaardmap"; "lng_download_path_clear" = "Alles wissen"; "lng_download_path_header" = "Downloadmap kiezen"; "lng_download_path_default_radio" = "Telegram-map \"Downloads\""; "lng_download_path_temp_radio" = "Temp map, gewist bij uitloggen/deïnstallatie"; "lng_download_path_dir_radio" = "Aangepaste map, handmatig schonen"; "lng_download_path_choose" = "Downloadmap kiezen"; "lng_sure_clear_downloads" = "Wil je all gedownloaden bestanden wissen uit de tijdelijk map? Dit gebeurt automatisch bij uitloggen of deïnstallatie."; "lng_download_path_failed" = "Download kon niet worden gestart. De oorzaak kan een ongeldige downloadmap zijn.\n\nJe kunt de map aanpassen via instellingen."; "lng_download_path_settings" = "Instellingen"; "lng_download_finish_failed" = "Downloaden mislukt.\n\nWil je het opnieuw proberen?"; "lng_download_path_clearing" = "Wissen..."; "lng_download_path_cleared" = "Gewist!"; "lng_download_path_clear_failed" = "Wissen mislukt"; "lng_settings_section_privacy" = "Privacy en veilighed"; "lng_local_storage_title" = "Lokale opslag"; "lng_settings_no_data_cached" = "Geen cache-data gevonden!"; "lng_settings_images_cached#one" = "{count} afbeelding, {size}"; "lng_settings_images_cached#other" = "{count} afbeeldingen, {size}"; "lng_settings_audios_cached#one" = "{count} spraakbericht, {size}"; "lng_settings_audios_cached#other" = "{count} spraakberichten, {size}"; "lng_local_storage_clear" = "Alles wissen"; "lng_local_storage_clearing" = "Wissen..."; "lng_local_storage_cleared" = "Gewist!"; "lng_local_storage_clear_failed" = "Wissen mislukt"; "lng_settings_section_advanced_settings" = "Geavanceerde instellingen"; "lng_settings_enable_night_theme" = "Nachtmodus inschakelen"; "lng_settings_disable_night_theme" = "Nachtmodus uitschakelen"; "lng_passcode_remove_button" = "Verwijder"; "lng_passcode_turn_on" = "Toegangscode"; "lng_passcode_change" = "Toegangscode wijzigen"; "lng_passcode_create" = "Toegangscode"; "lng_passcode_remove" = "Toegangscode verwijderen"; "lng_passcode_turn_off" = "Uitschakelen"; "lng_passcode_autolock" = "Automatisch vergrendelen"; "lng_passcode_autolock_away" = "Vergrendelen indien langer afwezig dan:"; "lng_passcode_autolock_inactive" = "Vergrendelen indien langer inactief dan:"; "lng_passcode_autolock_minutes#one" = "{count} minuut"; "lng_passcode_autolock_minutes#other" = "{count} minuten"; "lng_passcode_autolock_hours#one" = "{count} uur"; "lng_passcode_autolock_hours#other" = "{count} uur"; "lng_passcode_enter_old" = "Toegangscode invoeren"; "lng_passcode_enter_first" = "Toegangscode invoeren"; "lng_passcode_enter_new" = "Nieuwe toegangscode invoeren"; "lng_passcode_confirm_new" = "Toegangscode opnieuw invoeren"; "lng_passcode_about" = "Als je een toegangscode instelt verschijnt er een slotje op de chatspagina, klik erop om de app te vergrendelen.\n\nLet op: als je de toegangscode vergeet moet je opnieuw inloggen."; "lng_passcode_differ" = "Toegangscodes zijn verschillend"; "lng_passcode_wrong" = "Onjuiste toegangscode"; "lng_passcode_is_same" = "Toegangscode is niet gewijzigd"; "lng_passcode_enter" = "Toegangscode invoeren"; "lng_passcode_ph" = "Je toegangscode"; "lng_passcode_submit" = "Versturen"; "lng_passcode_logout" = "Uitloggen"; "lng_passcode_need_unblock" = "Deblokkeer de app eerst."; "lng_cloud_password_waiting" = "Bevestigingslink verstuurd naar {email}.."; "lng_cloud_password_change" = "Cloud-wachtwoord wijzigen"; "lng_cloud_password_create" = "Cloud-wachtwoord"; "lng_cloud_password_remove" = "Cloud-wachtwoord verwijderen"; "lng_cloud_password_set" = "Twee-staps-verificatie"; "lng_cloud_password_edit" = "Cloud-wachtwoord wijzigen"; "lng_cloud_password_enter_old" = "Wachtwoord invoeren"; "lng_cloud_password_enter_first" = "Wachtwoord invoeren"; "lng_cloud_password_enter_new" = "Nieuw wachtwoord invoeren"; "lng_cloud_password_confirm_new" = "Wachtwoord opnieuw invoeren"; "lng_cloud_password_hint" = "Wachtwoordhint invoeren"; "lng_cloud_password_change_hint" = "Wachtwoordhint invoeren"; "lng_cloud_password_bad" = "De hint moet anders zijn dan je wachtwoord."; "lng_cloud_password_email" = "Herstel-e-mailadres invoeren"; "lng_cloud_password_bad_email" = "Ongeldig e-mailadres, probeer een andere."; "lng_cloud_password_about" = "Dit wachtwoord is nodig als je inlogt vanaf een nieuw apparaat, naast de SMS-code."; "lng_cloud_password_about_recover" = "Let op: Echt geen herstel-emailadres\nopgeven voor je wachtwoord?\n\nBij verlies van je wachtwoord ben je\nook de toegang tot Telegram kwijt."; "lng_cloud_password_skip_email" = "E-mail overslaan"; "lng_cloud_password_almost" = "Een bevestigingslink is naar het e-mailadres verstuurd. \ntwee-staps-verificatie is actief na het klikken van de e-mail-link."; "lng_cloud_password_was_set" = "Twee-staps-verificatie ingeschakeld."; "lng_cloud_password_updated" = "Je cloud-wachtwoord is gewijzigd."; "lng_cloud_password_removed" = "Twee-staps-verificatie uitgeschakeld."; "lng_cloud_password_differ" = "Wachtwoorden komen niet overeen"; "lng_cloud_password_wrong" = "Onjuist cloud-wachtwoord"; "lng_cloud_password_is_same" = "Wachtwoord is niet gewijzigd"; "lng_connection_type" = "Verbindingstype:"; "lng_connection_auto_connecting" = "Standaard (verbinden...)"; "lng_connection_auto" = "Standaard ({transport} wordt gebruikt)"; "lng_connection_proxy_connecting" = "Verbinden via proxy..."; "lng_connection_proxy" = "{transport} met proxy"; "lng_connection_header" = "Verbindingstype"; "lng_connection_auto_rb" = "Auto (TCP indien beschikbaar of HTTP)"; "lng_connection_http_proxy_rb" = "HTTP met aangepaste http-proxy"; "lng_connection_tcp_proxy_rb" = "TCP met aangepaste socks5-proxy"; "lng_connection_try_ipv6" = "Verbinden via IPV6"; "lng_connection_host_ph" = "Hostnaam"; "lng_connection_port_ph" = "Poort"; "lng_connection_user_ph" = "Gebruiker"; "lng_connection_password_ph" = "Wachtwoord"; "lng_connection_save" = "Opslaan"; "lng_settings_blocked_users" = "Geblokkeerde gebruikers"; "lng_settings_last_seen_privacy" = "Laatst gezien"; "lng_settings_calls_privacy" = "Oproepen"; "lng_settings_groups_invite_privacy" = "Groepen"; "lng_settings_show_sessions" = "Alle sessies weergeven"; "lng_settings_self_destruct" = "Account verwijderen na inactiviteit"; "lng_settings_change_phone" = "Telefoonnummer wijzigen"; "lng_settings_reset" = "Beëindig alle andere sessies"; "lng_settings_reset_sure" = "Alle apparaten behalve het huidige\napparaat uitloggen?"; "lng_settings_reset_one_sure" = "Deze sessie beëindigen?"; "lng_settings_reset_button" = "Beëindigen"; "lng_settings_reset_done" = "Alle andere sessies zijn beëindigd"; "lng_settings_manage_local_storage" = "Lokale opslag beheren"; "lng_settings_ask_question" = "Een vraag stellen"; "lng_settings_ask_sure" = "De ondersteuning voor Telegram wordt gedaan door vrijwilligers. We proberen zo snel mogelijk te reageren maar het kan even duren. \n\nBekijk ook de veelgestelde vragen, deze bevat belangrijke tips en antwoorden op de meest gestelde vragen. "; "lng_settings_faq_button" = "Veelgestelde vragen"; "lng_settings_ask_ok" = "Vraag"; "lng_settings_faq" = "Veelgestelde vragen"; "lng_settings_logout" = "Uitloggen"; "lng_sure_logout" = "Weet je zeker dat je wilt uitloggen?"; "lng_settings_need_restart" = "Herstarten is vereist om sommige instellingen toe te passen, herstarten?"; "lng_settings_restart_now" = "Herstarten"; "lng_settings_restart_later" = "Later"; "lng_sessions_header" = "Huidige sessie"; "lng_sessions_other_header" = "Actieve sessies"; "lng_sessions_no_other" = "Geen andere sessies"; "lng_sessions_other_desc" = "Je kunt in Telegram inloggen vanaf andere apparaten (mobiel,tablet,desktop) met hetzelfde telefoonnummer. Al je data zal direct worden gesynchroniseerd."; "lng_sessions_terminate_all" = "Beëindig alle andere sessies"; "lng_blocked_list_title" = "Geblokkeerde gebruikers"; "lng_blocked_list_unknown_phone" = "onbekend nummer"; "lng_blocked_list_unblock" = "Deblokkeer"; "lng_blocked_list_add" = "Blokkeer"; "lng_blocked_list_add_title" = "Gebruikers blokkeren"; "lng_blocked_list_already_blocked" = "al geblokkeerd"; "lng_blocked_list_about" = "Deze gebruikers kunnen je geen berichten sturen, niet toevoegen aan groepen, je profielfoto, online status en laatst gezien niet zien."; "lng_blocked_list_not_found" = "Geen gebruikers."; "lng_edit_privacy_everyone" = "Iedereen"; "lng_edit_privacy_contacts" = "Mijn contacten"; "lng_edit_privacy_nobody" = "Niemand"; "lng_edit_privacy_exceptions" = "Uitzonderingen"; "lng_edit_privacy_lastseen_title" = "Laatst gezien"; "lng_edit_privacy_lastseen_description" = "Je kunt instellen wie je \"laatst gezien\" mag zien:"; "lng_edit_privacy_lastseen_warning" = "Let op: van mensen waarmee je je laatst gezien tijd niet deelt is deze voor jou ook niet zichtbaar. In plaats daarvan krijg je tijden bij benadering te zien."; "lng_edit_privacy_lastseen_always_empty" = "Altijd delen met"; "lng_edit_privacy_lastseen_always#one" = "Altijd delen met {count} gebruiker"; "lng_edit_privacy_lastseen_always#other" = "Altijd delen met {count} gebruikers"; "lng_edit_privacy_lastseen_never_empty" = "Nooit delen met"; "lng_edit_privacy_lastseen_never#one" = "Nooit delen met {count} gebruiker"; "lng_edit_privacy_lastseen_never#other" = "Nooit delen met {count} gebruikers"; "lng_edit_privacy_lastseen_exceptions" = "Deze instelling overschrijft de bovenstaande."; "lng_edit_privacy_lastseen_always_title" = "Altijd delen met"; "lng_edit_privacy_lastseen_never_title" = "Nooit delen met"; "lng_edit_privacy_groups_title" = "Wie kan mij toevoegen?"; "lng_edit_privacy_groups_description" = "Je kunt nauwkeurig bepalen wie je aan groepen en kanalen mag toevoegen."; "lng_edit_privacy_groups_always_empty" = "Altijd toestaan"; "lng_edit_privacy_groups_always#one" = "Altijd toestaan {count} gebruiker"; "lng_edit_privacy_groups_always#other" = "Altijd toestaan {count} gebruikers"; "lng_edit_privacy_groups_never_empty" = "Nooit toestaan"; "lng_edit_privacy_groups_never#one" = "Nooit toestaan {count} gebruiker"; "lng_edit_privacy_groups_never#other" = "Nooit toestaan {count} gebruikers"; "lng_edit_privacy_groups_exceptions" = "Deze gebruikers altijd toestaan of verbieden je aan groepen en kanalen toe te voegen."; "lng_edit_privacy_groups_always_title" = "Altijd toestaan"; "lng_edit_privacy_groups_never_title" = "Nooit toestaan"; "lng_edit_privacy_calls_title" = "Oproepen"; "lng_edit_privacy_calls_description" = "Je kunt bepalen wie jou mag bellen:"; "lng_edit_privacy_calls_always_empty" = "Altijd toestaan"; "lng_edit_privacy_calls_always#one" = "Altijd toestaan {count} gebruiker"; "lng_edit_privacy_calls_always#other" = "Altijd toestaan {count} gebruikers"; "lng_edit_privacy_calls_never_empty" = "Nooit toestaan"; "lng_edit_privacy_calls_never#one" = "Nooit toestaan {count} gebruiker"; "lng_edit_privacy_calls_never#other" = "Nooit toestaan {count} gebruikers"; "lng_edit_privacy_calls_exceptions" = "Deze gebruikers altijd toestaan of verbieden om je te bellen."; "lng_edit_privacy_calls_always_title" = "Altijd toestaan"; "lng_edit_privacy_calls_never_title" = "Nooit toestaan"; "lng_self_destruct_title" = "Account verwijderen"; "lng_self_destruct_description" = "Als je binnen deze periode niet minimaal één keer ingelogd bent geweest zal je account worden verwijderd, inclusief alle data."; "lng_self_destruct_months#one" = "{count} maand"; "lng_self_destruct_months#other" = "{count} maanden"; "lng_self_destruct_years#one" = "{count} jaar"; "lng_self_destruct_years#other" = "{count} jaar"; "lng_change_phone_title" = "Telefoonnummer wijzigen"; "lng_change_phone_description" = "Je kan je telefoonnummer hier wijzigen.\nAl je clouddata — berichten, bestanden, \ngroepen, contacten, etc. zullen worden \ngekopieërd naar je nieuwe nummer.\n\n[b]Let op[/b]: al je Telegram-contacten krijgen \nje [b]nieuwe nummer[/b] in hun adresboek, ervan \nuitgaande dat ze je oude nummer hadden en \nje hen niet had geblokkeerd in Telegram."; "lng_change_phone_warning" = "Al je Telegram-contacten krijgen je nieuwe nummer in hun adresboek, ervan uitgaande dat ze je oude nummer hadden en je hen niet had geblokkeerd in Telegram."; "lng_change_phone_occupied" = "Aan telefoonnummer {phone} is al een Telegram-account gekoppeld. Verwijder het account om te kunnen migreren naar het nieuwe nummer."; "lng_change_phone_button" = "Nummer wijzigen"; "lng_change_phone_new_title" = "Voer je nieuwe nummer in"; "lng_change_phone_new_description" = "We sturen een sms-bericht met verificatiecode naar je nieuwe nummer."; "lng_change_phone_new_submit" = "Versturen"; "lng_change_phone_code_title" = "Code invoeren"; "lng_change_phone_code_description" = "Een sms-bericht met verificatiecode is naar nummer {phone} verstuurd."; "lng_change_phone_success" = "Je telefoonnummer is gewijzigd."; "lng_mute_duration_hours#one" = "{count} uur"; "lng_mute_duration_hours#other" = "{count} uur"; "lng_mute_box_tip" = "Kies hoelang je meldingen op stil wilt zetten voor deze chat"; "lng_preview_loading" = "Link-preview ophalen..."; "lng_profile_chat_unaccessible" = "Groep is ontoegankelijk"; "lng_profile_about_section" = "Over"; "lng_profile_description_section" = "Beschrijving"; "lng_profile_settings_section" = "Instellingen"; "lng_profile_actions_section" = "Acties"; "lng_profile_bot_settings" = "Instellingen"; "lng_profile_bot_help" = "Help"; "lng_profile_invite_link_section" = "Uitnodigingslink"; "lng_profile_create_public_link" = "Publieke link maken"; "lng_profile_edit_public_link" = "Publieke link wijzigen"; "lng_profile_search_members" = "Zoek leden"; "lng_profile_manage_admins" = "Beheerders wijzigen"; "lng_profile_manage_blocklist" = "Beheer geblokkeerde gebruikers"; "lng_profile_manage_restrictedlist" = "Beheer beperkte gebruikers"; "lng_profile_recent_actions" = "Recente gebeurtenissen"; "lng_profile_common_groups#one" = "{count} gedeelde groep"; "lng_profile_common_groups#other" = "{count} gedeelde groepen"; "lng_profile_common_groups_section" = "Gedeelde groepen"; "lng_profile_participants_section" = "Leden"; "lng_profile_info_section" = "Informatie"; "lng_profile_mobile_number" = "Mobiel:"; "lng_profile_username" = "Gebruikersnaam:"; "lng_profile_link" = "Link:"; "lng_profile_bio" = "Bio:"; "lng_profile_add_contact" = "Contact toevoegen"; "lng_profile_edit_contact" = "Wijzig"; "lng_profile_enable_notifications" = "Meldingen"; "lng_profile_clear_history" = "Geschiedenis wissen"; "lng_profile_delete_conversation" = "Gesprek verwijderen"; "lng_profile_clear_and_exit" = "Verwijder en verlaat"; "lng_profile_leave_channel" = "Kanaal verlaten"; "lng_profile_delete_channel" = "Kanaal verwijderen"; "lng_profile_leave_group" = "Groep verlaten"; "lng_profile_delete_group" = "Groep verwijderen"; "lng_profile_report" = "Melden"; "lng_profile_search_messages" = "Zoek berichten"; "lng_profile_block_user" = "Blokkeer gebruiker"; "lng_profile_unblock_user" = "Deblokkeer gebruiker"; "lng_profile_block_bot" = "Stop en blokkeer bot"; "lng_profile_unblock_bot" = "Deblokkeer bot"; "lng_profile_send_message" = "Bericht sturen"; "lng_profile_share_contact" = "Contact delen"; "lng_profile_invite_to_group" = "Groepslid maken"; "lng_profile_delete_contact" = "Verwijder"; "lng_profile_set_group_photo" = "Foto instellen"; "lng_profile_add_participant" = "Lid toevoegen"; "lng_profile_view_channel" = "Naar kanaal"; "lng_profile_join_channel" = "Lid worden"; "lng_profile_delete_and_exit" = "Verlaat"; "lng_profile_kick" = "Verwijder"; "lng_profile_admin" = "beheerder"; "lng_profile_edit_permissions" = "Wijzig"; "lng_profile_sure_kick" = "{user} uit de groep verwijderen?"; "lng_profile_sure_kick_channel" = "{user} uit het kanaal verwijderen?"; "lng_profile_sure_kick_admin" = "{user} ontslaan als beheerder?"; "lng_profile_loading" = "Laden..."; "lng_profile_shared_media" = "Gedeelde media"; "lng_profile_no_media" = "Geen media in deze chat."; "lng_profile_photos#one" = "{count} foto"; "lng_profile_photos#other" = "{count} foto's"; "lng_profile_photos_header" = "Foto's"; "lng_profile_videos#one" = "{count} video"; "lng_profile_videos#other" = "{count} video's"; "lng_profile_videos_header" = "Video's"; "lng_profile_songs#one" = "{count} audiobestand"; "lng_profile_songs#other" = "{count} audiobestanden"; "lng_profile_songs_header" = "Audiobestanden"; "lng_profile_files#one" = "{count} bestand"; "lng_profile_files#other" = "{count} bestanden"; "lng_profile_files_header" = "Bestanden"; "lng_profile_audios#one" = "{count} spraakbericht"; "lng_profile_audios#other" = "{count} spraakberichten"; "lng_profile_audios_header" = "Spraakberichten"; "lng_profile_shared_links#one" = "{count} gedeelde link"; "lng_profile_shared_links#other" = "{count} gedeelde links"; "lng_profile_shared_links_header" = "Gedeelde links"; "lng_profile_copy_phone" = "Telefoonnummer kopiëren"; "lng_profile_copy_fullname" = "Naam kopiëren"; "lng_profile_drop_area_title" = "Sleep afbeelding hierheen"; "lng_profile_drop_area_subtitle" = "om als groepsfoto in te stellen"; "lng_profile_drop_area_subtitle_channel" = "om als kanaalfoto in te stellen"; "lng_profile_top_bar_share_contact" = "Delen"; "lng_report_title" = "Kanaal melden"; "lng_report_group_title" = "Groep melden"; "lng_report_bot_title" = "Spam melden"; "lng_report_reason_spam" = "Spam"; "lng_report_reason_violence" = "Geweld"; "lng_report_reason_pornography" = "Pornografie"; "lng_report_reason_other" = "Overig"; "lng_report_reason_description" = "Beschrijving"; "lng_report_button" = "Melden"; "lng_report_thanks" = "Bedankt! Je melding zal zo spoedig mogen worden gecontroleerd."; "lng_channel_add_members" = "Leden toevoegen"; "lng_channel_add_banned" = "Blokkeer gebruiker"; "lng_channel_add_restricted" = "Beperk gebruiker"; "lng_channel_members" = "Leden"; "lng_channel_only_last_shown#one" = "Het laatste {count} lid wordt hier weergegeven"; "lng_channel_only_last_shown#other" = "De laatste {count} leden worden hier weergegeven"; "lng_channel_admins" = "Beheerders"; "lng_channel_add_admin" = "Beheerder toevoegen"; "lng_channel_admins_too_much" = "Je hebt het maximum aantal beheerders voor deze groep bereikt, Verwijder er eerst één."; "lng_channel_admin_status_creator" = "Maker"; "lng_channel_admin_status_promoted_by" = "Gepromoveerd door {user}"; "lng_channel_admin_status_not_admin" = "Geen beheerder"; "lng_channel_banned_status_restricted_by" = "Beperkt door {user}"; "lng_group_blocked_list_about" = "Geblokkeerde gebruikers kunnen alleen worden uitgenodigd door beheerders. \nUitnodigingslinks werken niet voor hen."; "lng_chat_all_members_admins" = "Iedereen is beheerder"; "lng_chat_about_all_admins" = "Iedereen mag leden toevoegen en de groepsfoto of naam wijzigen."; "lng_chat_about_admins" = "Beheerders mogen leden beheren en de groepsfoto of naam wijzigen."; "lng_participant_filter" = "Zoeken"; "lng_participant_invite" = "Uitnodigen"; "lng_participant_invite_sorry#one" = "Het {count}e lid kun je persoonlijk uitnodigen.\n\nVanaf nu kunnen mensen lid worden via de uitnodigingslink."; "lng_participant_invite_sorry#other" = "De eerste {count} leden kun je persoonlijk uitnodigen.\n\nVanaf nu kunnen mensen lid worden via de uitnodigingslink."; "lng_create_group_back" = "Vorige"; "lng_create_group_next" = "Volgende"; "lng_create_group_create" = "Maak"; "lng_create_group_title" = "Nieuwe groep"; "lng_create_channel_title" = "Nieuw kanaal"; "lng_create_public_channel_title" = "Publiek kanaal"; "lng_create_public_channel_about" = "Iedereen kan je kanaal vinden en er lid van worden"; "lng_create_private_channel_title" = "Privé-kanaal"; "lng_create_private_channel_about" = "Lid worden kan alleen per uitnodigingslink"; "lng_create_public_group_title" = "Publieke groep"; "lng_create_public_group_about" = "Iedereen kan de groep vinden, er lid van worden en de geschiedenis zien"; "lng_create_private_group_title" = "Privé-groep"; "lng_create_private_group_about" = "Lid worden kan alleen op uitnodiging of via uitnodigingslink"; "lng_create_group_skip" = "Overslaan"; "lng_create_channel_link_invalid" = "Deze naam is ongeldig."; "lng_create_channel_link_occupied" = "Deze naam is bezet"; "lng_create_channel_link_too_short" = "Deze naam is te kort"; "lng_create_channel_link_bad_symbols" = "Deze naam bevat ongeldige tekens"; "lng_create_channel_link_available" = "Deze naam is beschikbaar."; "lng_create_channel_link_copied" = "Link gekopieerd naar klembord"; "lng_create_group_crop" = "Kies een gebied voor de groepsfoto"; "lng_create_channel_crop" = "Kies een gebied voor de kanaalfoto"; "lng_failed_add_participant" = "Gebruiker toevoegen mislukt. Probeer het later."; "lng_failed_add_not_mutual" = "Iemand die de groep verlaat kan alleen door een wederzijds contact worden toegevoegd (opgeslagen nummers)"; "lng_failed_add_not_mutual_channel" = "Iemand die het kanaal verlaat kan alleen door een wederzijds contact worden toegevoegd (opgeslagen nummers)"; "lng_sure_delete_contact" = "{contact} echt verwijderen uit contacten?"; "lng_sure_delete_history" = "Geschiedenis met {contact} echt wissen? \n\nJe kunt dit niet ongedaan maken."; "lng_sure_delete_group_history" = "Echt de geschiedenis van \"{group}\" wissen?\n\nJe kunt dit niet ongedaan maken."; "lng_sure_delete_and_exit" = "Wil je de groep \"{group}\" verlaten en de geschiedenis wissen?\n\nJe kunt dit niet ongedaan maken."; "lng_sure_leave_channel" = "Kanaal echt verlaten?"; "lng_sure_delete_channel" = "Kanaal echt verwijderen? Berichten worden gewist en alle leden verwijderd."; "lng_sure_leave_group" = "Groep echt verlaten?\nJe kunt dit niet ongedaan maken."; "lng_sure_delete_group" = "Groep echt verwijderen? Berichten worden gewist en alle leden verwijderd."; "lng_message_empty" = "Leeg bericht"; "lng_message_unsupported" = "Dit bericht wordt niet ondersteund door jouw versie van Telegram Desktop. Werk bij naar de laatste versie via de instellingen of installeer vanuit {link}"; "lng_duration_seconds#one" = "{count} seconde"; "lng_duration_seconds#other" = "{count} seconden"; "lng_duration_minsec_minutes#one" = "{count} min"; "lng_duration_minsec_minutes#other" = "{count} min"; "lng_duration_minsec_seconds#one" = "{count} sec"; "lng_duration_minsec_seconds#other" = "{count} sec"; "lng_duration_minutes_seconds" = "{minutes_count} {seconds_count}"; "lng_action_add_user" = "{from} heeft {user} toegevoegd"; "lng_action_add_users_many" = "{from} heeft {users} toegevoegd"; "lng_action_add_users_and_one" = "{accumulated}, {user}"; "lng_action_add_users_and_last" = "{accumulated} en {user}"; "lng_action_add_you" = "{from} heeft je toegevoegd aan dit kanaal"; "lng_action_you_joined" = "Je bent nu lid van dit kanaal"; "lng_action_add_you_group" = "{from} heeft je toegevoegd aan deze groep"; "lng_action_you_joined_group" = "Je bent nu lid van deze groep"; "lng_action_kick_user" = "{from} heeft {user} verwijderd"; "lng_action_user_left" = "{from} heeft de groep verlaten"; "lng_action_user_joined" = "{from} is nu lid van de groep"; "lng_action_user_joined_by_link" = "{from} is nu lid van de groep via uitnodigingslink"; "lng_action_user_registered" = "{from} heeft nu Telegram"; "lng_action_removed_photo" = "{from} heeft de groepsafbeelding verwijderd"; "lng_action_removed_photo_channel" = "Kanaalfoto verwijderd"; "lng_action_changed_photo" = "{from} heeft de groepsafbeelding gewijzigd"; "lng_action_changed_photo_channel" = "Kanaalfoto bijgewerkt"; "lng_action_changed_title" = "{from} heeft de groepsnaam gewijzigd naar \"{title}\""; "lng_action_changed_title_channel" = "Kanaalnaam gewijzigd naar \"{title}\""; "lng_action_created_chat" = "{from} heeft de groep \"{title}\" gemaakt"; "lng_action_created_channel" = "Kanaal aangemaakt"; "lng_action_group_migrate" = "De groep is opgewaardeerd naar een supergroep"; "lng_action_pinned_message" = "{from} heeft «{text}» vastgezet"; "lng_action_pinned_media" = "{from} heeft {media} vastgezet"; "lng_action_pinned_media_photo" = "een foto"; "lng_action_pinned_media_video" = "een video"; "lng_action_pinned_media_audio" = "een audiobestand"; "lng_action_pinned_media_voice" = "een spraakbericht"; "lng_action_pinned_media_video_message" = "een videobericht"; "lng_action_pinned_media_file" = "een bestand"; "lng_action_pinned_media_gif" = "een GIF"; "lng_action_pinned_media_contact" = "een contact"; "lng_action_pinned_media_location" = "een locatie"; "lng_action_pinned_media_sticker" = "een sticker"; "lng_action_pinned_media_emoji_sticker" = "een {emoji} sticker"; "lng_action_pinned_media_game" = "het spel «{game}»"; "lng_action_game_score#one" = "{from} heeft met {game} {count} punt behaald"; "lng_action_game_score#other" = "{from} heeft met {game} {count} punten behaald"; "lng_action_game_you_scored#one" = "Je hebt met {game} {count} punt behaald"; "lng_action_game_you_scored#other" = "Je hebt met {game} {count} punten behaald"; "lng_action_game_score_no_game#one" = "{from} heeft {count} punt behaald"; "lng_action_game_score_no_game#other" = "{from} heeft {count} punten behaald"; "lng_action_game_you_scored_no_game#one" = "Je hebt {count} punt behaald"; "lng_action_game_you_scored_no_game#other" = "Je hebt {count} punten behaald"; "lng_action_payment_done" = "Je hebt een bedrag van {amount} overgemaakt naar {user}"; "lng_action_payment_done_for" = "Je hebt een bedrag van {amount} overgemaakt naar {user} voor {invoice}"; "lng_action_took_screenshot" = "{from} heeft een schermafdruk gemaakt!"; "lng_action_you_took_screenshot" = "Schermafdruk gemaakt!"; "lng_ttl_photo_received" = "{from} heeft een zelfvernietigende foto gestuurd, bekijk deze op je mobiel."; "lng_ttl_photo_sent" = "Je hebt een zelfvernietigende foto gestuurd"; "lng_ttl_photo_expired" = "Foto is verlopen"; "lng_ttl_video_received" = "{from} heeft een zelfvernietigende video gestuurd, bekijk deze op je mobiel."; "lng_ttl_video_sent" = "Je hebt een zelfvernietigende video gestuurd"; "lng_ttl_video_expired" = "Video is verlopen"; "lng_profile_migrate_reached#one" = "{count} ledenlimiet bereikt"; "lng_profile_migrate_reached#other" = "{count} ledenlimiet bereikt"; "lng_profile_migrate_body" = "Wil je een hogere limiet? Waardeer op naar een supergroep."; "lng_profile_migrate_learn_more" = "Meer informatie »"; "lng_profile_migrate_about" = "Wil je extra functies en een hogere limiet? Waardeer op naar een supergroep:"; "lng_profile_migrate_feature1#one" = "— De ledenlimiet is {count} lid"; "lng_profile_migrate_feature1#other" = "— De ledenlimiet is {count} leden"; "lng_profile_migrate_feature2" = "— Nieuwe leden zien de hele geschiedenis"; "lng_profile_migrate_feature3" = "— Beheerder wist berichten voor iedereen"; "lng_profile_migrate_feature4" = "— Meldingen staan standaard uit"; "lng_profile_migrate_button" = "Opwaarderen naar supergroep."; "lng_profile_migrate_sure" = "Groep echt omzetten naar supergroep? Je kunt dit niet ongedaan maken."; "lng_profile_convert_button" = "Opwaarderen naar supergroep"; "lng_profile_convert_title" = "Opwaarderen naar supergroep"; "lng_profile_convert_about" = "supergroepen:"; "lng_profile_convert_feature1" = "— Nieuwe leden zien de hele geschiedenis"; "lng_profile_convert_feature2" = "— Gewiste berichten gelden voor alle leden"; "lng_profile_convert_feature3" = "— Beheerders kunnen berichten vastzetten"; "lng_profile_convert_feature4" = "— Maker kan een publieke groepslink instellen"; "lng_profile_convert_warning" = "{bold_start}Let op:{bold_end} Je kunt dit niet ongedaan maken."; "lng_profile_convert_confirm" = "Opwaarderen"; "lng_profile_add_more_after_upgrade#one" = "Waardeer op naar een supergroep om tot {count} lid toe te voegen"; "lng_profile_add_more_after_upgrade#other" = "Waardeer op naar een supergroep om tot {count} leden toe te voegen"; "lng_channel_not_accessible" = "Sorry, dit kanaal is privé."; "lng_group_not_accessible" = "Sorry, deze groep is niet beschikbaar."; "lng_channels_too_much_public_about" = "Het maximale aantal publieke gebruikersnamen is bereikt. Trek eerst een link van één van je oudere groepen of kanalen in."; "lng_channels_too_much_public_revoke_confirm_group" = "Deze link echt intrekken {link}?\n\nDe groep «{group}» zal privé worden."; "lng_channels_too_much_public_revoke_confirm_channel" = "Deze link echt intrekken {link}?\n\nHet kanaal «{group}» zal privé worden."; "lng_channels_too_much_public_revoke" = "Intrekken"; "lng_group_invite_bad_link" = "Deze uitnodigingslink is defect of verlopen."; "lng_group_invite_want_join" = "Wil je lid worden van de groep \"{title}\"?"; "lng_group_invite_want_join_channel" = "Wil je lid worden van het kanaal \"{title}\"?"; "lng_group_invite_join" = "Lid worden"; "lng_group_invite_members#one" = "{count} lid, waaronder:"; "lng_group_invite_members#other" = "{count} leden, waaronder:"; "lng_group_invite_link" = "Uitnodigingslink:"; "lng_group_invite_create" = "Uitnodigingslink maken"; "lng_group_invite_about" = "Gebruikers kunnen lid worden\nvan je groep via deze link."; "lng_group_invite_create_new" = "Uitnodigingslink intrekken"; "lng_group_invite_about_new" = "Je uitnodigingslink zal inactief worden, een nieuwe link zal worden gegenereerd."; "lng_group_invite_copied" = "Link gekopieerd naar klembord."; "lng_group_invite_no_room" = "De ledenlimiet van de groep is bereikt."; "lng_channel_public_link_copied" = "Link gekopieerd naar klembord"; "lng_forwarded" = "Doorgestuurd van {user}"; "lng_forwarded_date" = "Origineel: {date}"; "lng_forwarded_channel" = "Doorgestuurd van {channel}"; "lng_forwarded_via" = "Doorgestuurd van {user} via {inline_bot}"; "lng_forwarded_channel_via" = "Doorgestuurd van {channel} via {inline_bot}"; "lng_forwarded_signed" = "{channel} ({user})"; "lng_in_reply_to" = "Antwoord op"; "lng_edited" = "gewijzigd"; "lng_edited_date" = "Gewijzigd: {date}"; "lng_cancel_edit_post_sure" = "Wijzigen annuleren?"; "lng_cancel_edit_post_yes" = "Ja"; "lng_cancel_edit_post_no" = "Nee"; "lng_bot_share_location_unavailable" = "Sorry, locatie delen is nog niet beschikbaar via Telegram Desktop."; "lng_bot_inline_geo_unavailable" = "Deze bot heeft je locatie nodig, dit is\nnog niet beschikbaar via Telegram Desktop."; "lng_bot_share_phone" = "Telefoonnummer delen?"; "lng_bot_share_phone_confirm" = "Delen"; "lng_attach_failed" = "Mislukt"; "lng_attach_file" = "Bestand"; "lng_attach_photo" = "Foto"; "lng_media_type" = "Mediatype"; "lng_media_type_photos" = "Foto's"; "lng_media_type_videos" = "Video's"; "lng_media_type_songs" = "Audio"; "lng_media_type_files" = "Bestanden"; "lng_media_type_audios" = "Spraakberichten"; "lng_media_type_links" = "Gedeelde links"; "lng_media_open_with" = "Openen met"; "lng_media_download" = "Downloaden"; "lng_media_cancel" = "Annuleren"; "lng_media_video" = "Video"; "lng_media_audio" = "Spraakbericht"; "lng_media_auto_settings" = "Instellingen voor media-downloads"; "lng_media_auto_photo" = "Foto's automatisch downloaden"; "lng_media_auto_audio" = "Audio automatisch downloaden"; "lng_media_auto_gif" = "GIF's automatisch downloaden"; "lng_media_auto_private_chats" = "Chats"; "lng_media_auto_groups" = "Groepen en kanalen"; "lng_media_auto_play" = "Automatisch afspelen"; "lng_emoji_category0" = "Veelgebruikt"; "lng_emoji_category1" = "Mensen"; "lng_emoji_category2" = "Natuur"; "lng_emoji_category3" = "Eten & drinken"; "lng_emoji_category4" = "Activiteit"; "lng_emoji_category5" = "Reizen & plekken"; "lng_emoji_category6" = "Objecten"; "lng_emoji_category7" = "Symbolen en vlaggen"; "lng_emoji_hide_panel" = "Klik om het emojipaneel te verbergen"; "lng_recent_stickers" = "Veelgebruikt"; "lng_faved_stickers" = "Favoriete stickers"; "lng_faved_stickers_add" = "Toevoegen aan favorieten"; "lng_faved_stickers_remove" = "Verwijder uit favorieten"; "lng_group_stickers" = "Groepsstickers"; "lng_group_stickers_description" = "Je kunt een stickerbundel kiezen die voor iedereen in de groepschat beschikbaar is."; "lng_group_stickers_add" = "Kies stickerbundel"; "lng_switch_stickers" = "Stickers"; "lng_switch_emoji" = "Emoji"; "lng_switch_gifs" = "GIF's"; "lng_stickers_featured_add" = "Toevoegen"; "lng_gifs_search" = "GIF's zoeken"; "lng_gifs_no_saved" = "Je hebt nog geen GIF's opgeslagen."; "lng_inline_bot_no_results" = "Geen resultaten."; "lng_inline_bot_via" = "via {inline_bot}"; "lng_box_remove" = "Verwijder"; "lng_stickers_installed_tab" = "Stickers"; "lng_stickers_featured_tab" = "Populair"; "lng_stickers_archived_tab" = "Gearchiveerd"; "lng_stickers_remove_pack" = " \"{sticker_pack}\" verwijderen?"; "lng_stickers_add_pack" = "Stickers toevoegen"; "lng_stickers_share_pack" = "Stickers delen"; "lng_stickers_not_found" = "Stickerbundel niet gevonden."; "lng_stickers_packs_archived" = "Een aantal ongebruikte stickers zijn gearchiveerd om ruimte te maken voor je nieuwe stickerbundels"; "lng_stickers_archived" = "Gearchiveerde stickers"; "lng_stickers_copied" = "Stickerbundel-link gekopieerd naar klembord"; "lng_stickers_default_set" = "Grote geesten"; "lng_stickers_you_have" = "Beheer en sorteer stickerbundels"; "lng_stickers_featured" = "Populaire stickers"; "lng_stickers_return" = "Ongedaan maken"; "lng_stickers_count#one" = "{count} sticker"; "lng_stickers_count#other" = "{count} stickers"; "lng_stickers_masks_pack" = "Dit een set met maskers. Je kunt ze gebruiken als je een foto bewerkt in onze mobiele apps."; "lng_stickers_group_set" = "Groepsstickers"; "lng_stickers_remove_group_set" = "Groepsstickers verwijderen?"; "lng_stickers_group_from_your" = "Kies uit je stickers"; "lng_in_dlg_photo" = "Foto"; "lng_in_dlg_video" = "Video"; "lng_in_dlg_audio_file" = "Audiobestand"; "lng_in_dlg_contact" = "Contact"; "lng_in_dlg_audio" = "Spraakbericht"; "lng_in_dlg_video_message" = "Videobericht"; "lng_in_dlg_file" = "Bestand"; "lng_in_dlg_sticker" = "Sticker"; "lng_in_dlg_sticker_emoji" = "{emoji} Sticker"; "lng_ban_user" = "Blokkeer gebruiker"; "lng_delete_all_from" = "Verwijder alles van gebruiker"; "lng_report_spam" = "Spam melden"; "lng_report_spam_hide" = "Verbergen"; "lng_report_spam_thanks" = "Bedankt!"; "lng_report_spam_sure" = "Spam van deze gebruiker echt melden?"; "lng_report_spam_sure_group" = "Spam van deze groep echt melden?"; "lng_report_spam_sure_channel" = "Spam van dit kanaal echt melden?"; "lng_report_spam_ok" = "Melden"; "lng_cant_send_to_not_contact" = "Je kunt momenteel alleen berichten versturen naar onderlinge contacten. {more_info}"; "lng_cant_invite_not_contact" = "Je kunt momenteel alleen onderlinge contacten aan kanalen toevoegen. {more_info}"; "lng_cant_send_to_not_contact_flood" = "Je kunt momenteel alleen berichten versturen naar onderlinge contacten. Probeer het later, berichten beantwoorden kan wel."; "lng_cant_invite_not_contact_flood" = "Je kunt momenteel alleen onderlinge contacten aan groepen toevoegen. Probeer het later of vraag een ander lid om deze gebruiker aan de groep toe te voegen."; "lng_cant_more_info" = "Meer informatie »"; "lng_cant_invite_banned" = "Alleen beheerders kunnen deze gebruiker toevoegen."; "lng_cant_invite_privacy" = "Je kunt deze gebruiker niet toevoegen aan groepen door zijn/haar privacyinstellingen."; "lng_cant_invite_privacy_channel" = "Je kunt deze gebruiker niet toevoegen aan kanalen door zijn/haar privacyinstellingen."; "lng_cant_delete_group#one" = "Het is momenteel niet mogelijk om via de app groepen te verwijderen met meer dan {count} lid. Neem contact op met de ondersteuning van Telegram om deze groep te verwijderen."; "lng_cant_delete_group#other" = "Het is momenteel niet mogelijk om via de app groepen te verwijderen met meer dan {count} leden. Neem contact op met de ondersteuning van Telegram om deze groep te verwijderen."; "lng_cant_delete_channel#one" = "Het is momenteel niet mogelijk om via de app kanalen te verwijderen met meer dan {count} lid. Neem contact op met de ondersteuning van Telegram om dit kanaal te verwijderen."; "lng_cant_delete_channel#other" = "Het is momenteel niet mogelijk om via de app kanalen te verwijderen met meer dan {count} leden. Neem contact op met de ondersteuning van Telegram om dit kanaal te verwijderen."; "lng_cant_do_this" = "Deze actie is niet beschikbaar."; "lng_send_button" = "Stuur"; "lng_message_ph" = "Bericht..."; "lng_broadcast_ph" = "Massabericht..."; "lng_broadcast_silent_ph" = "Stil massabericht..."; "lng_record_cancel" = "Annuleren: uit het vak loslaten"; "lng_will_be_notified" = "Berichtgeving voor leden"; "lng_wont_be_notified" = "Geen berichtgeving voor leden"; "lng_willbe_history" = "Kies een chat om te beginnen"; "lng_from_you" = "Jij"; "lng_from_draft" = "Concept"; "lng_bot_description" = "Wat kan deze bot? "; "lng_unblock_button" = "Deblokkeer"; "lng_channel_join" = "Lid worden"; "lng_channel_mute" = "Geluid uit"; "lng_channel_unmute" = "Geluid aan"; "lng_dialogs_text_with_from" = "{from_part} {message}"; "lng_dialogs_text_from_wrapped" = "{from}:"; "lng_dialogs_text_media" = "{media_part} {caption}"; "lng_dialogs_text_media_wrapped" = "{media},"; "lng_dialogs_show_all_chats" = "Alle chats weergeven"; "lng_dialogs_hide_muted_chats" = "Stille chats verbergen"; "lng_open_this_link" = "Link openen?"; "lng_open_link" = "Openen"; "lng_allow_bot_pass" = "Wil je {bot_name} toestaan om je naam en ID door te geven aan webpagina's die je opent via deze bot?"; "lng_allow_bot" = "Toestaan"; "lng_bot_start" = "Begin"; "lng_bot_choose_group" = "Kies group"; "lng_bot_no_groups" = "Je hebt geen groepen"; "lng_bot_groups_not_found" = "Geen groepen gevonden"; "lng_bot_sure_invite" = "De bot toevoegen aan \"{group}\"?"; "lng_bot_already_in_group" = "Deze bot is al een groepslid."; "lng_bot_choose_chat" = "Kies chat"; "lng_bot_no_chats" = "Je hebt nog geen chats"; "lng_bot_chats_not_found" = "Geen chats gevonden"; "lng_bot_sure_share_game" = "Spel met {user} delen?"; "lng_bot_sure_share_game_group" = "Spel met «{group}» delen?"; "lng_typing" = "aan het typen"; "lng_user_typing" = "{user} is aan het typen"; "lng_users_typing" = "{user} en {second_user} zijn aan het typen"; "lng_many_typing#one" = "{count} aan het typen"; "lng_many_typing#other" = "{count} aan het typen"; "lng_playing_game" = "speelt een spel"; "lng_user_playing_game" = "{user} speelt een spel"; "lng_users_playing_game" = "{user} en {second_user} spelen een spel"; "lng_many_playing_game#one" = "{count} speelt een spel"; "lng_many_playing_game#other" = "{count} spelen een spel"; "lng_send_action_record_video" = "video opnemen"; "lng_user_action_record_video" = "{user} neemt video op"; "lng_send_action_upload_video" = "video versturen"; "lng_user_action_upload_video" = "{user} verstuurt video"; "lng_send_action_record_audio" = "spraakbericht opnemen"; "lng_user_action_record_audio" = "{user} neemt spraakbericht op"; "lng_send_action_upload_audio" = "spraakbericht versturen"; "lng_user_action_upload_audio" = "{user} verstuurt spraakbericht"; "lng_send_action_record_round" = "videobericht opnemen"; "lng_user_action_record_round" = "{user} neemt videobericht op"; "lng_send_action_upload_round" = "videobericht versturen"; "lng_user_action_upload_round" = "{user} verstuurt videobericht"; "lng_send_action_upload_photo" = "foto versturen"; "lng_user_action_upload_photo" = "{user} verstuurt een foto"; "lng_send_action_upload_file" = "bestand versturen"; "lng_user_action_upload_file" = "{user} verstuurt bestand"; "lng_send_action_geo_location" = "locatie kiezen"; "lng_user_action_geo_location" = "{user} kiest een locatie"; "lng_send_action_choose_contact" = "contact kiezen"; "lng_user_action_choose_contact" = "{user} kiest een contact"; "lng_unread_bar#one" = "{count} ongelezen bericht"; "lng_unread_bar#other" = "{count} ongelezen berichten"; "lng_maps_point" = "Locatie"; "lng_save_photo" = "Afbeelding opslaan"; "lng_save_video" = "Video opslaan"; "lng_save_audio_file" = "Audio opslaan"; "lng_save_audio" = "Spraakbericht opslaan"; "lng_save_file" = "Bestand opslaan"; "lng_save_downloaded" = "{ready} / {total} {mb}"; "lng_duration_and_size" = "{duration}, {size}"; "lng_duration_played" = "{played} / {duration}"; "lng_date_and_duration" = "{date}, {duration}"; "lng_choose_image" = "Afbeelding kiezen"; "lng_choose_files" = "Bestanden kiezen"; "lng_game_tag" = "Spel"; "lng_context_view_profile" = "Profiel weergeven"; "lng_context_view_group" = "Groepsinformatie weergeven"; "lng_context_view_channel" = "Kanaalinformatie weergeven"; "lng_context_pin_to_top" = "Vastzetten"; "lng_context_unpin_from_top" = "Losmaken"; "lng_context_promote_admin" = "Beheerder maken"; "lng_context_edit_permissions" = "Machtigingen aanpassen"; "lng_context_restrict_user" = "Beperk gebruiker"; "lng_context_remove_from_group" = "Uit de groep verwijderen"; "lng_context_copy_link" = "Link kopiëren"; "lng_context_copy_post_link" = "Berichtlink kopiëren"; "lng_context_copy_email" = "E-mailadres kopiëren"; "lng_context_copy_hashtag" = "Hashtag kopiëren"; "lng_context_copy_mention" = "Gebruikersnaam kopiëren"; "lng_context_save_image" = "Afbeelding opslaan als..."; "lng_context_forward_image" = "Afbeelding doorsturen"; "lng_context_delete_image" = "Afbeelding verwijderen"; "lng_context_copy_image" = "Afbeelding kopiëren"; "lng_context_close_image" = "Afbeelding sluiten"; "lng_context_cancel_download" = "Download annuleren"; "lng_context_show_in_folder" = "Weergeven in map"; "lng_context_show_in_finder" = "Weergeven in Finder"; "lng_context_save_video" = "Video opslaan als..."; "lng_context_save_audio_file" = "Video opslaan als..."; "lng_context_save_audio" = "Spraakbericht opslaan als..."; "lng_context_pack_info" = "Bundelinformatie"; "lng_context_pack_add" = "Stickers toevoegen"; "lng_context_save_file" = "Bestand opslaan als..."; "lng_context_forward_file" = "Bestand doorsturen"; "lng_context_delete_file" = "Bestand verwijderen"; "lng_context_close_file" = "Bestand sluiten"; "lng_context_copy_text" = "Tekst kopiëren"; "lng_context_open_gif" = "Gif openen"; "lng_context_save_gif" = "GIF opslaan"; "lng_context_to_msg" = "Naar bericht gaan"; "lng_context_reply_msg" = "Antwoord"; "lng_context_edit_msg" = "Wijzig"; "lng_context_forward_msg" = "Bericht doorsturen"; "lng_context_delete_msg" = "Bericht verwijderen"; "lng_context_select_msg" = "Bericht kiezen"; "lng_context_pin_msg" = "Bericht vastzetten"; "lng_context_unpin_msg" = "Bericht losmaken"; "lng_context_cancel_upload" = "Upload annuleren"; "lng_context_copy_selected" = "Tekstselectie kopiëren"; "lng_context_copy_selected_items" = "Selectie als tekst kopiëren"; "lng_context_forward_selected" = "Selectie doorsturen"; "lng_context_delete_selected" = "Selectie verwijderen"; "lng_context_clear_selection" = "Selectie wissen"; "lng_really_send_image" = "Wil je deze afbeeldingen versturen?"; "lng_really_send_file" = "Wil je dit bestand versturen?"; "lng_really_share_contact" = "Wil je dit contact delen?"; "lng_send_images_compress#one" = "Afbeelding comprimeren"; "lng_send_images_compress#other" = "Afbeeldingen comprimeren"; "lng_send_image_non_local" = "Verzenden mislukt, {name} is een netwerk-bestand."; "lng_send_image_empty" = "Verzenden mislukt, {name} is leeg."; "lng_send_image_too_large" = "Verzenden mislukt, {name} is groter dan 1500 MB."; "lng_send_folder" = "Kan de map «{name}» niet versturen, kies een bestand. :("; "lng_send_images_selected#one" = "{count} afbeelding gekozen"; "lng_send_images_selected#other" = "{count} afbeeldingen gekozen"; "lng_send_photos#one" = "Stuur {count} foto"; "lng_send_photos#other" = "Stuur {count} foto's"; "lng_send_files_selected#one" = "{count} bestand gekozen"; "lng_send_files_selected#other" = "{count} bestanden gekozen"; "lng_send_files#one" = "Stuur {count} bestand"; "lng_send_files#other" = "Stuur {count} bestanden"; "lng_forward_choose" = "Ontvanger kiezen..."; "lng_forward_cant" = "Sorry, doorsturen hierheen kan niet :("; "lng_forward_confirm" = "Doorsturen naar {recipient}?"; "lng_forward_share_contact" = "Contact delen met {recipient}?"; "lng_forward_share_cant" = "Sorry, een contact hier delen kan niet :("; "lng_forward_send_file_confirm" = " \"{name}\" naar {recipient} versturen?"; "lng_forward_send_files_confirm" = "Gekozen bestanden naar {recipient} sturen?"; "lng_forward_send_files_cant" = "Sorry, media sturen hierheen kan niet :("; "lng_forward_send" = "Stuur"; "lng_forward_messages#one" = "bijlage: {count} bericht"; "lng_forward_messages#other" = "bijlage: {count} berichten"; "lng_forwarding_from#one" = "{user} en {count} andere"; "lng_forwarding_from#other" = "{user} en {count} anderen"; "lng_forwarding_from_two" = "{user} en {second_user}"; "lng_inline_switch_choose" = "Kies chat..."; "lng_inline_switch_cant" = "Sorry, je kunt hier niets delen :("; "lng_share_cant" = "Sorry, delen hierheen kan niet :("; "lng_reply_cant" = "Sorry, je kunt geen oude berichten beantwoorden in een supergroep :("; "lng_reply_cant_forward" = "Je kunt geen oude berichten beantwoorden in een supergroep :( Doorsturen en commentaar toevoegen?"; "lng_share_title" = "Delen met"; "lng_share_copy_link" = "Link kopiëren"; "lng_share_confirm" = "Stuur"; "lng_share_wrong_user" = "Resultaten zijn van een andere gebruiker of sessie."; "lng_share_game_link_copied" = "Link gekopieerd naar klembord."; "lng_share_done" = "Klaar!"; "lng_contact_phone" = "Telefoonnummer"; "lng_enter_contact_data" = "Nieuw contact"; "lng_edit_group_title" = "Groepsnaam wijzigen"; "lng_edit_contact_title" = "Naam wijzigen"; "lng_edit_channel_title" = "Kanaal wijzigen"; "lng_edit_sign_messages" = "Ondertekenen"; "lng_edit_group_who_invites" = "Wie mag leden toevoegen"; "lng_edit_group_invites_everybody" = "Alle leden"; "lng_edit_group_invites_only_admins" = "Beheerders"; "lng_edit_group" = "Groep wijzigen"; "lng_edit_self_title" = "Je naam wijzigen"; "lng_confirm_contact_data" = "Nieuw contact"; "lng_add_contact" = "Opslaan"; "lng_add_contact_button" = "Nieuw contact"; "lng_contacts_header" = "Contacten"; "lng_contact_not_joined" = "{name} heeft zich nog niet aangemeld bij Telegram, je kunt je vriend een uitnodiging sturen.\n\nWe infomeren je wanneer een contact van jou lid is geworden van Telegram."; "lng_try_other_contact" = "Probeer een andere"; "lng_create_group_link" = "Link"; "lng_create_group_invite_link" = "Uitnodigingslink"; "lng_create_group_photo" = "Foto instellen"; "lng_create_group_description" = "Beschrijving (optioneel)"; "lng_drag_images_here" = "Sleep afbeeldingen hierheen"; "lng_drag_photos_here" = "Sleep foto's hierheen"; "lng_drag_files_here" = "Sleep bestanden hierheen"; "lng_drag_to_send_quick" = "om ze snel te versturen"; "lng_drag_to_send_no_compression" = "om ze ongecomprimeerd te versturen"; "lng_drag_to_send_files" = "om ze als bestanden te versturen"; "lng_selected_clear" = "Annuleren"; "lng_selected_delete" = "Verwijder"; "lng_selected_forward" = "Doorsturen"; "lng_selected_count#one" = "{count} bericht"; "lng_selected_count#other" = "{count} berichten"; "lng_selected_cancel_sure_this" = "Upload annuleren?"; "lng_selected_upload_stop" = "Stoppen"; "lng_selected_delete_sure_this" = "Wil je dit bericht verwijderen?"; "lng_selected_delete_sure#one" = "{count} bericht verwijderen?"; "lng_selected_delete_sure#other" = "{count} berichten verwijderen?"; "lng_delete_photo_sure" = "Wil je deze foto verwijderen?"; "lng_delete_for_everyone_hint#one" = "Hiermee verwijder je dit voor iedereen in de chat."; "lng_delete_for_everyone_hint#other" = "Hiermee verwijder je deze voor iedereen in de chat."; "lng_delete_for_me_chat_hint#one" = "Hiermee verwijder je dit alleen voor jezelf, niet voor andere leden."; "lng_delete_for_me_chat_hint#other" = "Hiermee verwijder je deze alleen voor jezelf, niet voor andere leden."; "lng_delete_for_me_hint#one" = "Hiermee verwijder je dit alleen voor jezelf."; "lng_delete_for_me_hint#other" = "Hiermee verwijder je deze alleen voor jezelf."; "lng_delete_for_everyone_check" = "Verwijder voor iedereen"; "lng_delete_for_other_check" = "Verwijder voor {user}"; "lng_box_delete" = "Verwijder"; "lng_box_leave" = "Verlaat"; "lng_about_version" = "versie {version}"; "lng_about_text_1" = "Officiële, gratis Telegram app gebaseerd op de [a href=\"https://core.telegram.org/api\"]Telegram API[/a] voor snelheid en veiligheid."; "lng_about_text_2" = "Deze software valt onder [a href=\"https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE\"]GNU GPL[/a] versie 3.\nDe broncode is beschikbaar via [a href=\"https://github.com/telegramdesktop/tdesktop\"]GitHub[/a]."; "lng_about_text_3" = "Bezoek de {faq_open}Telegram FAQ{faq_close} voor meer informatie."; "lng_about_done" = "Gereed"; "lng_search_no_results" = "Geen berichten gevonden"; "lng_search_found_results#one" = "{count} berichten gevonden"; "lng_search_found_results#other" = "{count} berichten gevonden"; "lng_search_global_results" = "Wereldwijde zoekresultaten"; "lng_search_messages_from" = "Berichten weergeven van"; "lng_media_save_progress" = "{ready} van {total} {mb}"; "lng_mediaview_save_as" = "Opslaan als..."; "lng_mediaview_copy" = "Kopiëren"; "lng_mediaview_forward" = "Doorsturen"; "lng_mediaview_delete" = "Verwijder"; "lng_mediaview_photos_all" = "Alle foto's weergeven"; "lng_mediaview_files_all" = "Alle bestanden weergeven"; "lng_mediaview_single_photo" = "Foto"; "lng_mediaview_group_photo" = "Groepsafbeelding"; "lng_mediaview_channel_photo" = "Kanaalfoto"; "lng_mediaview_profile_photo" = "Profielfoto"; "lng_mediaview_file_n_of_count" = "{file} {n} van {count}"; "lng_mediaview_n_of_count" = "Foto {n} van {count}"; "lng_mediaview_doc_image" = "Bestand"; "lng_mediaview_today" = "vandaag om {time}"; "lng_mediaview_yesterday" = "gisteren om {time}"; "lng_mediaview_date_time" = "{date} om {time}"; "lng_mediaview_saved" = "Het bestand is opgeslagen in je [c]Downloads[/c]-map"; "lng_theme_preview_title" = "Thema-voorbeeld"; "lng_theme_preview_generating" = "Voorbeeld van thema maken..."; "lng_theme_preview_invalid" = "Thema bevat ongeldige data."; "lng_theme_preview_apply" = "Thema toepassen"; "lng_new_authorization" = "{name},\nEr is op je account ingelogd vanaf een nieuw apparaat op {day}, {date} om {time}\n\nApparaat: {device}\nLocatie: {location}\n\nAls jij dit niet was, kun je alle sessies beëindigen via Instellingen – Alle sessies weergeven en de sessie beëindigen.\n\nAls je dat denkt dat iemand anders zonder jouw toestemming is ingelogd kun je twee-staps-verificatie activeren via instellingen.\n\nBedankt,\nHet Telegram-Team"; "lng_new_version_wrap" = "Telegram is bijgewerkt naar versie {version}\n\n{changes} \n\nVolledige versiegeschiedenis is hier te vinden:\n{link}"; "lng_new_version_minor" = "— Probleemoplossing en andere kleine verbeteringen"; "lng_new_version_text" = "— Verbeteringen in videoberichten: Voortgangsindicator in frame, Picture-In-Picture-modus, weergave van afspeelduur.\n— Spraak- en videoberichten spelen nu automatisch achter elkaar af."; "lng_menu_insert_unicode" = "Unicode-besturingsteken invoegen"; "lng_full_name" = "{first_name} {last_name}"; "lng_confirm_phone_link_invalid" = "Deze link is defect of verlopen."; "lng_confirm_phone_title" = "Account reset annuleren"; "lng_confirm_phone_about" = "Iemand met toegang tot nummer {phone} heeft geprobeerd om je Telegram account te verwijderen en je twee-staps-verificatie wachtwoord te resetten.\n\nAls jij dit niet was, geef dan de code in die we zojuist per SMS hebben gestuurd."; "lng_confirm_phone_success" = "De verwijdering van account: {phone} is geannuleerd.\nJe kunt dit venster nu sluiten."; "lng_confirm_phone_send" = "Stuur"; "lng_confirm_phone_enter_code" = "Geef de code in."; "lng_theme_editor_no_keys" = "Palet bevat nog geen waarden"; "lng_theme_editor_cant_change_theme" = "Je kunt geen nieuw thema toepassen terwijl je het kleurenpalet bewerkt. Sluit eerst de editor."; "lng_theme_editor_new_keys" = "Nog niet in het kleurenpalet"; "lng_theme_editor_background_image" = "Achtergrondafbeelding"; "lng_theme_editor_saved_to_jpg" = "Opgeslagen als JPEG, {size}"; "lng_theme_editor_read_from_jpg" = "JPEG-afbeelding, {size}"; "lng_theme_editor_read_from_png" = "PNG-afbeelding, {size}"; "lng_theme_editor_export" = "Exporteren"; "lng_theme_editor_choose_image" = "Achtergrondafbeelding kiezen"; "lng_theme_editor_save_palette" = "Kleurenpalet opslaan"; "lng_theme_editor_choose_name" = "Thema opslaan"; "lng_theme_editor_error" = "Er is iets misgegaan in de editor :( Bekijk 'log.txt' voor meer info."; "lng_theme_editor_done" = "Exporteren van thema gelukt!"; "lng_theme_editor_title" = "Kleurenpalet bewerken"; "lng_theme_editor_export_button" = "Thema exporteren"; "lng_payments_not_supported" = "Telegram Desktop heeft nog geen ondersteuning voor betalingen. Gebruik één van onze mobiele apps hiervoor."; "lng_payments_receipt_label" = "Betalingsbewijs"; "lng_payments_receipt_label_test" = "Test betalingsbewijs"; "lng_payments_invoice_label" = "Factuur"; "lng_payments_invoice_label_test" = "Test Factuur"; "lng_payments_receipt_button" = "Betalingsbewijs"; "lng_call_status_incoming" = "belt je..."; "lng_call_status_connecting" = "verbinden..."; "lng_call_status_exchanging" = "encryptiesleutels uitwisselen..."; "lng_call_status_waiting" = "wachten..."; "lng_call_status_requesting" = "aanvragen..."; "lng_call_status_hanging" = "hangt op..."; "lng_call_status_ended" = "oproep beëindigd"; "lng_call_status_failed" = "verbinden mislukt"; "lng_call_status_ringing" = "gaat over..."; "lng_call_status_busy" = "bezet"; "lng_call_fingerprint_tooltip" = "Als deze emoji's gelijk zijn bij {user} is dit gesprek 100% veilig."; "lng_call_error_not_available" = "{user} neemt geen oproepen aan."; "lng_call_error_incompatible" = "{user}'s app gebruikt een niet-ondersteund protocol en zal eerst een update moeten uitvoeren."; "lng_call_error_outdated" = "{user}'s app heeft nog geen ondersteuning voor spraakoproepen en zal eerst een update moeten uitvoeren."; "lng_call_error_audio_io" = "Er lijkt iets mis te gaan met het afspelen van audio op je computer. Zorg dat je microfoon en luidsprekers werken en probeer het opnieuw."; "lng_call_bar_hangup" = "Nu beëindigen"; "lng_call_box_title" = "Spraakoproepen"; "lng_call_box_about" = "Nog geen oproepen."; "lng_call_box_status_today" = "{time}"; "lng_call_box_status_yesterday" = "Gisteren om {time}"; "lng_call_box_status_date" = "{date} om {time}"; "lng_call_box_status_group" = "({count}) {status}"; "lng_call_outgoing" = "Uitgaande oproep"; "lng_call_incoming" = "Inkomende oproep"; "lng_call_missed" = "Gemiste oproep"; "lng_call_cancelled" = "Geannuleerde oproep"; "lng_call_declined" = "Geweigerde oproep"; "lng_call_duration_info" = "{time}, {duration}"; "lng_call_type_and_duration" = "{type} ({duration})"; "lng_call_rate_label" = "Beoordeel de kwaliteit van je oproep"; "lng_call_rate_comment" = "Opmerking (optioneel)"; "lng_player_message_today" = "vandaag om {time}"; "lng_player_message_yesterday" = "Gisteren om {time}"; "lng_player_message_date" = "{date} om {time}"; "lng_rights_edit_admin" = "Beheerder wijzigen"; "lng_rights_edit_admin_header" = "Wat mag deze beheerder?"; "lng_rights_about_add_admins_yes" = "Deze beheerder mag nieuwe beheerders toevoegen met gelijkwaardige machtigingen (of minder) als zichzelf."; "lng_rights_about_add_admins_no" = "Deze beheerder mag geen beheerders toevoegen"; "lng_rights_about_admin_cant_edit" = "Je kunt de machtigingen van deze beheerder niet wijzigen."; "lng_rights_user_restrictions" = "Gebruikersbeperkingen"; "lng_rights_user_restrictions_header" = "Wat mag deze gebruiker"; "lng_rights_channel_info" = "Informatie aanpassen"; "lng_rights_channel_post" = "Berichten plaatsen"; "lng_rights_channel_edit" = "Berichten van anderen wijzigen"; "lng_rights_channel_delete" = "Berichten van anderen verwijderen"; "lng_rights_group_info" = "Informatie aanpassen"; "lng_rights_group_ban" = "Leden blokkeren"; "lng_rights_group_invite_link" = "Uitnodigen via link"; "lng_rights_group_invite" = "Leden toevoegen"; "lng_rights_group_pin" = "Berichten vastzetten"; "lng_rights_group_delete" = "Berichten verwijderen"; "lng_rights_add_admins" = "Beheerders toevoegen"; "lng_rights_chat_read" = "Berichten lezen"; "lng_rights_chat_send_text" = "Berichten sturen"; "lng_rights_chat_send_media" = "Media sturen"; "lng_rights_chat_send_stickers" = "GIF's en stickers sturen"; "lng_rights_chat_send_links" = "Link-voorbeeld sturen"; "lng_rights_chat_banned_until_header" = "Beperkt tot"; "lng_rights_chat_banned_forever" = "Voor altijd"; "lng_rights_chat_banned_day#one" = "{count} dag"; "lng_rights_chat_banned_day#other" = "{count} dagen"; "lng_rights_chat_banned_week#one" = "{count} week"; "lng_rights_chat_banned_week#other" = "{count} weken"; "lng_rights_chat_banned_custom" = "Aangepast"; "lng_rights_chat_banned_custom_date" = "Tot {date}"; "lng_rights_chat_banned_block" = "Blokkeer en verwijder uit groep"; "lng_restricted_send_message" = "Je bent beperkt in het plaatsen van berichten door de beheerders."; "lng_restricted_send_media" = "Je bent beperkt in het sturen van media door de beheerders."; "lng_restricted_send_stickers" = "Je bent beperkt in het sturen van stickers door de beheerders."; "lng_restricted_send_gifs" = "Je bent beperkt in het sturen van GIF's door de beheerders."; "lng_restricted_send_inline" = "Je bent beperkt in het sturen van inline-content door de beheerders."; "lng_restricted_list_title" = "Beperkte gebruikers"; "lng_banned_list_title" = "Geblokkeerde gebruikers"; "lng_admin_log_title_all" = "Alle gebeurtenissen"; "lng_admin_log_title_selected" = "Geselecteerde gebeurtenissen"; "lng_admin_log_filter" = "Filteren"; "lng_admin_log_filter_title" = "Filteren"; "lng_admin_log_filter_all_actions" = "Alle gebeurtenissen"; "lng_admin_log_filter_restrictions" = "Nieuwe beperkingen"; "lng_admin_log_filter_admins_new" = "Nieuwe beheerders"; "lng_admin_log_filter_members_new" = "Nieuwe leden"; "lng_admin_log_filter_info_group" = "Groepsinformatie"; "lng_admin_log_filter_info_channel" = "Kanaalinformatie"; "lng_admin_log_filter_messages_deleted" = "Verwijderd berichten"; "lng_admin_log_filter_messages_edited" = "Gewijzigde berichten"; "lng_admin_log_filter_messages_pinned" = "Vastgezette berichten"; "lng_admin_log_filter_members_removed" = "Vertrokken leden"; "lng_admin_log_filter_all_admins" = "Alle gebruikers"; "lng_admin_log_about" = "Wat is dit?"; "lng_admin_log_about_text" = "Dit is een lijst met alle acties van de groepsleden en beheerders in de afgelopen 48 uur."; "lng_admin_log_no_results_title" = "Geen gebeurtenissen"; "lng_admin_log_no_results_text" = "Geen recente gebeurtenissen gevonden voor je zoekopdracht."; "lng_admin_log_no_results_search_text" = "Geen recente gebeurtenissen voor '{query}' gevonden."; "lng_admin_log_no_events_title" = "Nog geen gebeurtenissen"; "lng_admin_log_no_events_text" = "Beheerders en leden hebben\ngeen acties uitgevoerd in \nde afgelopen 48 uur."; "lng_admin_log_empty_text" = "Leeg"; "lng_admin_log_changed_title_group" = "{from} heeft de groepsnaam gewijzigd naar «{title}»"; "lng_admin_log_changed_title_channel" = "{from} heeft de kanaalnaam gewijzigd naar «{title}»"; "lng_admin_log_changed_description_group" = "beschrijving door {from} gewijzigd:"; "lng_admin_log_removed_description_group" = "beschrijving door {from} verwijderd"; "lng_admin_log_changed_description_channel" = "beschrijving door {from} gewijzigd:"; "lng_admin_log_removed_description_channel" = "beschrijving door {from} verwijderd"; "lng_admin_log_previous_description" = "Vorige beschrijving"; "lng_admin_log_changed_link_group" = "link door {from} gewijzigd:"; "lng_admin_log_removed_link_group" = "link door {from} verwijderd:"; "lng_admin_log_changed_link_channel" = "link door {from} gewijzigd:"; "lng_admin_log_removed_link_channel" = "link door {from} verwijderd:"; "lng_admin_log_previous_link" = "Vorige link"; "lng_admin_log_changed_photo_group" = "Groepsafbeelding door {from} gewijzigd"; "lng_admin_log_changed_photo_channel" = "Kanaalfoto door {from} gewijzigd"; "lng_admin_log_removed_photo_group" = "{from} heeft de groepsfoto verwijderd"; "lng_admin_log_removed_photo_channel" = "{from} heeft de kanaalfoto verwijderd"; "lng_admin_log_invites_enabled" = "uitnodigingen door {from} ingeschakeld"; "lng_admin_log_invites_disabled" = "uitnodigingen door {from} uitgeschakeld"; "lng_admin_log_signatures_enabled" = "ondertekenen door {from} ingeschakeld"; "lng_admin_log_signatures_disabled" = "ondertekenen door {from} uitgeschakeld"; "lng_admin_log_pinned_message" = "Bericht door {from} vastgezet:"; "lng_admin_log_unpinned_message" = "Bericht door {from} losgemaakt"; "lng_admin_log_edited_caption" = "Onderschrift door {from} gewijzigd:"; "lng_admin_log_removed_caption" = "Onderschrift door {from} verwijderd"; "lng_admin_log_previous_caption" = "Origineel onderschrift"; "lng_admin_log_edited_message" = "Bericht door {from} gewijzigd"; "lng_admin_log_previous_message" = "Origineel bericht"; "lng_admin_log_deleted_message" = "Bericht door {from} verwijderd"; "lng_admin_log_participant_joined" = "{from} is nu lid van de groep"; "lng_admin_log_participant_joined_channel" = "{from} is nu lid van het kanaal"; "lng_admin_log_participant_left" = "{from} heeft de groep verlaten"; "lng_admin_log_participant_left_channel" = "{from} heeft het kanaal verlaten"; "lng_admin_log_invited" = "{user} uitgenodigd"; "lng_admin_log_banned" = "{user} geblokkeerd"; "lng_admin_log_restricted" = "beperkingen van {user} aangepast tot {until}"; "lng_admin_log_promoted" = "privileges van {user} aangepast"; "lng_admin_log_changed_stickers_group" = "{sticker_set} door {from} gewijzigd"; "lng_admin_log_changed_stickers_set" = "stickerbundel"; "lng_admin_log_removed_stickers_group" = "groepsstickers door {from} verwijderd"; "lng_admin_log_user_with_username" = "{name} ({mention})"; "lng_admin_log_restricted_forever" = "Voor altijd"; "lng_admin_log_restricted_until" = "tot {date}"; "lng_admin_log_banned_view_messages" = "Berichten lezen"; "lng_admin_log_banned_send_messages" = "Berichten sturen"; "lng_admin_log_banned_send_media" = "Media sturen"; "lng_admin_log_banned_send_stickers" = "Stickers & GIF's sturen"; "lng_admin_log_banned_embed_links" = "Link-voorbeeld sturen"; "lng_admin_log_admin_change_info" = "Informatie aanpassen"; "lng_admin_log_admin_post_messages" = "Berichten plaatsen"; "lng_admin_log_admin_edit_messages" = "Berichten wijzigen"; "lng_admin_log_admin_delete_messages" = "Berichten verwijderen"; "lng_admin_log_admin_ban_users" = "Leden blokkeren"; "lng_admin_log_admin_invite_users" = "Leden toevoegen"; "lng_admin_log_admin_invite_link" = "Uitnodigen via link"; "lng_admin_log_admin_pin_messages" = "Berichten vastzetten"; "lng_admin_log_admin_add_admins" = "Beheerders toevoegen"; // Not used "lng_topbar_info" = "Info"; "lng_profile_group_info" = "Groepsinformatie"; "lng_profile_channel_info" = "Kanaalinformatie"; "lng_channel_add_admins" = "Beheerder toevoegen"; "lng_dlg_search_chat" = "Zoek in deze chat"; "lng_dlg_search_channel" = "Zoek in dit kanaal"; // Wnd specific "lng_wnd_choose_program_menu" = "Standaardprogramma selecteren... "; "lng_wnd_menu_undo" = "Ongedaan maken"; "lng_wnd_menu_redo" = "Opnieuw"; // Linux specific "lng_linux_menu_undo" = "Ongedaan maken"; "lng_linux_menu_redo" = "Opnieuw"; // Mac specific "lng_mac_choose_program_menu" = "Andere... "; "lng_mac_choose_app" = "Kies een programma"; "lng_mac_choose_text" = "Kies een programma voor het openen van het bestand \"{file}\"."; "lng_mac_enable_filter" = "Activeren:"; "lng_mac_recommended_apps" = "Aanbevolen programma's"; "lng_mac_all_apps" = "Alle programma's"; "lng_mac_always_open_with" = "Altijd openen met"; "lng_mac_this_app_can_open" = "Dit programma kan \"{file}\" openen."; "lng_mac_not_known_app" = "Het is niet bekend of dit programma \"{file}\" kan openen."; "lng_mac_menu_services" = "Voorzieningen"; "lng_mac_menu_hide_telegram" = "Verberg {telegram}"; "lng_mac_menu_hide_others" = "Verberg andere"; "lng_mac_menu_show_all" = "Toon alles"; "lng_mac_menu_preferences" = "Voorkeuren"; "lng_mac_menu_quit_telegram" = "Stop {telegram}"; "lng_mac_menu_about_telegram" = "Over {telegram}"; "lng_mac_menu_file" = "Bestand"; "lng_mac_menu_logout" = "Uitloggen"; "lng_mac_menu_edit" = "Wijzig"; "lng_mac_menu_undo" = "Herstel"; "lng_mac_menu_redo" = "Opnieuw"; "lng_mac_menu_cut" = "Knippen"; "lng_mac_menu_copy" = "Kopiëren"; "lng_mac_menu_paste" = "Plakken"; "lng_mac_menu_delete" = "Verwijder"; "lng_mac_menu_select_all" = "Alles selecteren"; "lng_mac_menu_window" = "Venster"; "lng_mac_menu_contacts" = "Contacten"; "lng_mac_menu_add_contact" = "Contact toevoegen"; "lng_mac_menu_new_group" = "Nieuwe groep"; "lng_mac_menu_new_channel" = "Nieuw kanaal"; "lng_mac_menu_show" = "Telegram weergeven"; // Keys finished tdesktop-1.2.17/Telegram/Resources/langs/lang_pt_BR.strings000066400000000000000000002436171326245125100237150ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ "lng_language_name" = "Português (Brasil)"; "lng_switch_to_this" = "Trocar para Português (Brasil)"; "lng_menu_contacts" = "Contatos"; "lng_menu_calls" = "Chamadas"; "lng_menu_settings" = "Configurações"; "lng_menu_about" = "Sobre"; "lng_menu_update" = "Atualizar"; "lng_menu_restart" = "Reiniciar"; "lng_menu_back" = "Voltar"; "lng_menu_night_mode" = "Modo noturno"; "lng_disable_notifications_from_tray" = "Desabilitar notificações"; "lng_enable_notifications_from_tray" = "Habilitar notificações"; "lng_open_from_tray" = "Abrir Telegram"; "lng_minimize_to_tray" = "Minimizar"; "lng_quit_from_tray" = "Sair do Telegram"; "lng_tray_icon_text" = "Telegram ainda está sendo executado,\nvocê pode mudar isso nas configurações.\nSe este ícone desaparecer de sua bandeja,\nvocê pode arrastar de volta dos ícones "; "lng_month1" = "Janeiro"; "lng_month2" = "Fevereiro"; "lng_month3" = "Março"; "lng_month4" = "Abril"; "lng_month5" = "Maio"; "lng_month6" = "Junho"; "lng_month7" = "Julho"; "lng_month8" = "Agosto"; "lng_month9" = "Setembro"; "lng_month10" = "Outubro"; "lng_month11" = "Novembro"; "lng_month12" = "Dezembro"; "lng_month1_small" = "Jan"; "lng_month2_small" = "Fev"; "lng_month3_small" = "Mar"; "lng_month4_small" = "Abr"; "lng_month5_small" = "Mai"; "lng_month6_small" = "Jun"; "lng_month7_small" = "Jul"; "lng_month8_small" = "Ago"; "lng_month9_small" = "Set"; "lng_month10_small" = "Out"; "lng_month11_small" = "Nov"; "lng_month12_small" = "Dez"; "lng_weekday1" = "Seg"; "lng_weekday2" = "Ter"; "lng_weekday3" = "Qua"; "lng_weekday4" = "Qui"; "lng_weekday5" = "Sex"; "lng_weekday6" = "Sáb"; "lng_weekday7" = "Dom"; "lng_weekday1_full" = "Segunda"; "lng_weekday2_full" = "Terça"; "lng_weekday3_full" = "Quarta"; "lng_weekday4_full" = "Quinta"; "lng_weekday5_full" = "Sexta"; "lng_weekday6_full" = "Sábado"; "lng_weekday7_full" = "Domingo"; "lng_month_day" = "{month} {day}"; "lng_month_day_year" = "{day} {month}, {year}"; "lng_month_year" = "{month} {year}"; "lng_box_ok" = "OK"; "lng_cancel" = "Cancelar"; "lng_continue" = "Continuar"; "lng_close" = "Fechar"; "lng_connecting" = "Conectando..."; "lng_connecting_to_proxy" = "Conectando ao proxy..."; "lng_connecting_settings" = "Configurações"; "lng_reconnecting#one" = "Reconectar em {count} s..."; "lng_reconnecting#other" = "Reconectar em {count} s..."; "lng_reconnecting_try_now" = "Tentar agora"; "lng_status_service_notifications" = "notificações de serviço"; "lng_status_support" = "suporte"; "lng_status_bot" = "bot"; "lng_status_bot_reads_all" = "tem acesso às mensagens"; "lng_status_bot_not_reads_all" = "não tem acesso às mensagens"; "lng_status_offline" = "visto há muito tempo"; "lng_status_recently" = "visto recentemente"; "lng_status_last_week" = "visto há uma semana"; "lng_status_last_month" = "visto há um mês"; "lng_status_invisible" = "invisível"; "lng_status_lastseen_now" = "visto agora "; "lng_status_lastseen_minutes#one" = "visto há {count} minuto"; "lng_status_lastseen_minutes#other" = "visto há {count} minutos"; "lng_status_lastseen_hours#one" = "visto há {count} hora"; "lng_status_lastseen_hours#other" = "visto há {count} horas"; "lng_status_lastseen_today" = "visto hoje às {time}"; "lng_status_lastseen_yesterday" = "visto ontem às {time}"; "lng_status_lastseen_date" = "visto por último {date}"; "lng_status_lastseen_date_time" = "visto por último {date} às {time}"; "lng_status_online" = "online"; "lng_status_connecting" = "conectando..."; "lng_chat_status_unaccessible" = "grupo inacessível"; "lng_chat_status_no_members" = "nenhum membro"; "lng_chat_status_members#one" = "{count} membro"; "lng_chat_status_members#other" = "{count} membros"; "lng_chat_status_online#one" = "{count} online"; "lng_chat_status_online#other" = "{count} online"; "lng_chat_status_members_online" = "{members_count}, {online_count}"; "lng_channel_status" = "canal"; "lng_group_status" = "grupo"; "lng_channel_members_link#one" = "{count} membro"; "lng_channel_members_link#other" = "{count} membros"; "lng_channel_admins_link#one" = "{count} administrador"; "lng_channel_admins_link#other" = "{count} administradores"; "lng_server_error" = "Erro interno do servidor."; "lng_flood_error" = "Muitas tentativas. Tente novamente mais tarde."; "lng_gif_error" = "Um erro ocorreu ao ler a animação do GIF :("; "lng_edit_error" = "Você não pode editar essa mensagem"; "lng_join_channel_error" = "Desculpe, você já entrou em muitos canais e supergrupos. Por favor, saia de alguns antes de entrar."; "lng_error_phone_flood" = "Desculpe, você apagou e recriou sua conta muitas vezes recentemente. Aguarde alguns dias antes de tentar novamente."; "lng_error_start_minimized_passcoded" = "Você tem uma senha de bloqueio, então o aplicativo não pode iniciar minimizado. O app precisa de sua senha para iniciar."; "lng_error_pinned_max#one" = "Você não pode fixar mais de {count} conversa no topo."; "lng_error_pinned_max#other" = "Você não pode fixar mais de {count} conversas no topo."; "lng_error_public_groups_denied" = "Infelizmente, você foi banido de participar em grupos públicos.\n{more_info}"; "lng_error_cant_edit_admin" = "Desculpe, você não pode editar permissões para esse admin."; "lng_error_cant_add_member" = "Você não pode adicionar bots à esse grupo. Peça para um admin fazer isso."; "lng_error_cant_add_bot" = "Esse bot não pode ser adicionado à grupos"; "lng_error_cant_add_admin_invite" = "Você não pode adicionar esse usuário como admin porque ele não é um membro do grupo e você não possui permissão para convidá-lo."; "lng_error_cant_add_admin_unban" = "Você não pode adicionar esse usuário como admin porque ele está na lista negra e você não pode desbaní-lo."; "lng_error_cant_ban_admin" = "Você não pode banir esse usuário porque ele é um admin do grupo e você não possui permissão para rebaixá-lo."; "lng_sure_add_admin_invite" = "Esse usuário não é um membro do grupo. Adicioná-lo ao grupo e promover à administrador?"; "lng_sure_add_admin_unban" = "Esse usuário está restrito ou banido do grupo. Você tem certeza que quer o desbanir e o promovê-lo?"; "lng_sure_ban_admin" = "Esse usuário é um administrador do grupo. Você tem certeza que deseja restringi-lo?"; "lng_sure_ban_user_group" = "Banir {user} do grupo?"; "lng_sure_enable_socks" = "Você tem certeza que deseja habilitar esse proxy?\n\nServidor: {server}\nPorta: {port}\n\nVocê pode alterar seu servidor proxy depois em Configurações (Tipo de Conexão)."; "lng_sure_enable" = "Habilitar"; "lng_edit_deleted" = "Essa mensagem foi apagada"; "lng_edit_too_long" = "Sua mensagem está muito longa"; "lng_edit_message" = "Editar mensagem"; "lng_edit_message_text" = "Nova mensagem de texto..."; "lng_deleted" = "Conta Excluída"; "lng_deleted_message" = "Mensagem apagada"; "lng_pinned_message" = "Mensagem fixada"; "lng_pinned_unpin_sure" = "Você gostaria de desafixar essa mensagem?"; "lng_pinned_pin_sure" = "Você gostaria de fixar essa mensagem?"; "lng_pinned_pin" = "Fixar"; "lng_pinned_unpin" = "Desafixar"; "lng_pinned_notify" = "Notificar todos os membros"; "lng_intro_about" = "Bem vindo ao aplicativo oficial Telegram Desktop.\nÉ rápido e seguro."; "lng_start_msgs" = "COMECE A CONVERSAR"; "lng_intro_next" = "PRÓXIMO"; "lng_intro_finish" = "CADASTRAR"; "lng_intro_submit" = "ENVIAR"; "lng_photo_caption" = "Legenda"; "lng_photos_comment" = "Comentário"; "lng_phone_ph" = "Seu número de telefone"; "lng_phone_title" = "Seu Telefone"; "lng_phone_desc" = "Confirme o código de seu país e\npreencha seu número de telefone."; "lng_phone_notreg" = "Se você ainda não possui uma conta do Telegram,\nse [b]cadastre[/b] com um {link_start}Android / iPhone{link_end} ou {signup_start}aqui{signup_end}"; "lng_country_code" = "Código do País"; "lng_bad_country_code" = "Código do País Inválido"; "lng_country_ph" = "Busca"; "lng_country_done" = "Concluído"; "lng_country_none" = "País não encontrado"; "lng_country_select" = "Selecionar país"; "lng_code_ph" = "Código"; "lng_code_desc" = "Enviamos uma SMS com um código de ativação\npara o seu telefone. Insira-o abaixo."; "lng_code_telegram" = "Por favor, insira o código recebido\nem seu aplicativo prévio do [b]Telegram[/b]."; "lng_code_no_telegram" = "Enviar código via SMS"; "lng_code_call" = "Telegram irá te ligar em {minutes}:{seconds}"; "lng_code_calling" = "Aguardando a ligação do Telegram.."; "lng_code_called" = "Telegram ligou para o seu número"; "lng_bad_phone" = "Número de telefone inválido. Tente novamente."; "lng_bad_phone_noreg" = "Número de telefone não registrado."; "lng_bad_code" = "Você inseriu um código inválido."; "lng_bad_name" = "Por favor, insira seu nome e sobrenome."; "lng_bad_photo" = "Erro na imagem selecionada."; "lng_signin_title" = "Verificar senha"; "lng_signin_desc" = "Por favor, insira sua senha."; "lng_signin_recover_desc" = "Por favor, insira o código de seu e-mail\n{email}"; "lng_signin_password" = "Sua senha"; "lng_signin_code" = "Código do e-mail"; "lng_signin_recover" = "Esqueceu sua senha?"; "lng_signin_recover_title" = "Redefinir senha"; "lng_signin_hint" = "Dica: {password_hint}"; "lng_signin_recover_hint" = "Código enviado para {recover_email}"; "lng_signin_bad_password" = "Você colocou uma senha errada."; "lng_signin_wrong_code" = "Você inseriu um código inválido"; "lng_signin_try_password" = "Problemas ao acessar seu e-mail?"; "lng_signin_password_removed" = "Sua senha da nuvem foi desativada.\nVocê pode definir uma nova em Configurações."; "lng_signin_no_email_forgot" = "Se você não providenciar um e-mail\nde recuperação ao configurar sua senha, suas opções restantes serão lembrar sua senha ou apagar sua conta."; "lng_signin_cant_email_forgot" = "Se você não pode restaurar o acesso ao e-mail, suas opções restantes serão lembrar sua senha ou apagar sua conta."; "lng_signin_reset_account" = "Apagar sua conta"; "lng_signin_sure_reset" = "Atenção!\n\nVocê perderá todos seus chats e mensagens, juntamente com quaisquer mídias e arquivos!\n\nVocê deseja apagar sua conta?"; "lng_signin_reset" = "Apagar"; "lng_signin_reset_wait" = "Uma vez que a conta {phone_number} está ativa e protegida por senha, nós iremos desativá-la em 1 semana, por questões de segurança. Você pode cancelar esse processo a qualquer momento.\n\nVocê poderá restaurar sua conta em:\n{when}"; "lng_signin_reset_days#one" = "{count} dia"; "lng_signin_reset_days#other" = "{count} dias"; "lng_signin_reset_hours#one" = "{count} hora"; "lng_signin_reset_hours#other" = "{count} horas"; "lng_signin_reset_minutes#one" = "{count} minuto"; "lng_signin_reset_minutes#other" = "{count} minutos"; "lng_signin_reset_in_days" = "{days_count} {hours_count} {minutes_count}"; "lng_signin_reset_in_hours" = "{hours_count} {minutes_count}"; "lng_signin_reset_cancelled" = "Suas tentativas recentes de restaurar essa conta foram canceladas pelo usuário ativo. Tente novamente em 7 dias."; "lng_signup_title" = "Suas Informações"; "lng_signup_desc" = "Por favor, insira nome e\ncarregue uma foto."; "lng_signup_firstname" = "Nome"; "lng_signup_lastname" = "Sobrenome"; "lng_dlg_filter" = "Buscar"; "lng_dlg_new_group_name" = "Nome do grupo"; "lng_dlg_new_channel_name" = "Nome do canal"; "lng_no_contacts" = "Você não possui contatos"; "lng_no_chats" = "Seus chats estarão aqui"; "lng_contacts_loading" = "Carregando..."; "lng_contacts_not_found" = "Nenhum contato encontrado"; "lng_dlg_search_for_messages" = "Buscar por mensagens"; "lng_update_telegram" = "Atualizar o Telegram"; "lng_dlg_search_in" = "Buscar mensagens em"; "lng_dlg_search_from" = "De: {user}"; "lng_settings_save" = "Salvar"; "lng_settings_upload" = "Definir Foto de Perfil"; "lng_settings_crop_profile" = "Selecione uma área para sua foto de perfil"; "lng_settings_uploading_photo" = "Carregando foto..."; "lng_settings_edit" = "Editar"; "lng_settings_drop_area_subtitle" = "para defini-la como sua foto"; "lng_username_title" = "Nome de usuário"; "lng_username_about" = "Você pode escolher um nome de usuário.\nAssim, outras pessoas poderão encontrar\nvocê pelo nome de usuário e entrar em\ncontato sem precisar saber seu telefone.\n\nVocê pode usar a-z, 0-9 e underline.\nO tamanho mínimo é de 5 caracteres."; "lng_username_choose" = "Escolha seu nome de usuário"; "lng_username_invalid" = "Nome de usuário inválido."; "lng_username_occupied" = "Nome de usuário ocupado."; "lng_username_too_short" = "Nome de usuário muito curto."; "lng_username_bad_symbols" = "Nome de usuário com símbolos incompatíveis."; "lng_username_available" = "Nome de usuário disponível."; "lng_username_not_found" = "Usuário @{user} não encontrado."; "lng_username_link_willbe" = "O seguinte link abrirá um chat com você:"; "lng_username_link" = "Esse link abre um chat com você:"; "lng_username_copied" = "Link copiado para área de transferência."; "lng_bio_title" = "Editar sua bio"; "lng_bio_placeholder" = "Bio"; "lng_bio_about" = "Você pode adicionar algumas linhas sobre você. Qualquer um que ver seu perfil poderá ver esse texto."; "lng_settings_section_info" = "Info"; "lng_settings_phone_number" = "Número de telefone:"; "lng_settings_username" = "Nome de usuário:"; "lng_settings_choose_username" = "Escolher nome de usuário"; "lng_settings_empty_bio" = "Nenhum"; "lng_settings_section_notify" = "Notificações"; "lng_settings_desktop_notify" = "Notificações na área de trabalho"; "lng_settings_show_name" = "Mostrar nome de quem enviou"; "lng_settings_show_preview" = "Mostrar pré-visualização da mensagem"; "lng_settings_use_windows" = "Usar notificações do Windows"; "lng_settings_use_native_notifications" = "Usar notificações nativas"; "lng_settings_advanced_notifications" = "Posição e contador de notificações"; "lng_settings_notifications_position" = "Localização na tela"; "lng_settings_notifications_count" = "Contador de notificações"; "lng_settings_sound_notify" = "Tocar som"; "lng_settings_include_muted" = "Incluir os chats silenciados no contador"; "lng_notification_preview" = "Você tem uma nova mensagem"; "lng_notification_reply" = "Responder"; "lng_notification_hide_all" = "Esconder tudo"; "lng_notification_sample" = "Esse é um exemplo de notificação"; "lng_settings_section_general" = "Geral"; "lng_settings_change_lang" = "Alterar idioma"; "lng_languages" = "Idiomas"; "lng_sure_save_language" = "Telegram irá reiniciar para alterar o idioma"; "lng_settings_update_automatically" = "Atualizar automaticamente"; "lng_settings_current_version_label" = "Versão {version}:"; "lng_settings_current_version" = "Versão {version}"; "lng_settings_check_now" = "Verificar atualizações"; "lng_settings_update_checking" = "Verificando atualizações..."; "lng_settings_latest_installed" = "Última versão está instalada"; "lng_settings_downloading" = "Baixando atualização {ready} / {total} MB..."; "lng_settings_update_ready" = "Nova versão está pronta"; "lng_settings_update_now" = "Reiniciar Agora"; "lng_settings_update_fail" = "Verificação de atualização falhou :("; "lng_settings_workmode_tray" = "Mostrar ícone na bandeja"; "lng_settings_workmode_window" = "Mostrar ícone na barra de ferramentas"; "lng_settings_auto_start" = "Iniciar Telegram junto ao sistema"; "lng_settings_start_min" = "Iniciar minimizado"; "lng_settings_add_sendto" = "Colocar Telegram no menu «Enviar para»"; "lng_settings_section_scale" = "Escala de Interface"; "lng_settings_scale_auto" = "Auto ({cur})"; "lng_settings_section_chat_settings" = "Configurações de Conversa"; "lng_settings_replace_emojis" = "Substituir emoji"; "lng_settings_view_emojis" = "Lista de visualização"; "lng_settings_emoji_list" = "Emojis suportados"; "lng_settings_send_enter" = "Enviar com Enter"; "lng_settings_send_ctrlenter" = "Enviar com Ctrl+Enter"; "lng_settings_send_cmdenter" = "Enviar com Cmd+Enter"; "lng_settings_section_background" = "Papel de Parede"; "lng_settings_bg_use_default" = "Usar o tema padrão de cores"; "lng_settings_bg_from_gallery" = "Escolher da galeria"; "lng_settings_bg_from_file" = "Escolher dos arquivos"; "lng_settings_bg_edit_theme" = "Editar tema"; "lng_settings_bg_tile" = "Lado-a-lado"; "lng_settings_adaptive_wide" = "Layout adaptativo para telas wide"; "lng_backgrounds_header" = "Escolha o seu novo papel de parede"; "lng_theme_sure_keep" = "Manter esse tema de cores?"; "lng_theme_reverting#one" = "Revertendo ao antigo tema em {count} segundo."; "lng_theme_reverting#other" = "Revertendo ao antigo tema em {count} segundos."; "lng_theme_keep_changes" = "Manter alterações"; "lng_theme_revert" = "Reverter"; "lng_download_path_dont_ask" = "Não perguntar onde salvar cada arquivo"; "lng_download_path_label" = "Baixar em:"; "lng_download_path_temp" = "pasta temporária"; "lng_download_path_default" = "pasta padrão"; "lng_download_path_clear" = "Limpar tudo"; "lng_download_path_header" = "Escolher caminho do download"; "lng_download_path_default_radio" = "Pasta do Telegram no sistema «Downloads»"; "lng_download_path_temp_radio" = "Pasta temporária, limpar ao sair"; "lng_download_path_dir_radio" = "Pasta escolhida, limpar manualmente"; "lng_download_path_choose" = "Escolher caminho do download"; "lng_sure_clear_downloads" = "Deseja remover todos os arquivos baixados da pasta temporária? Isso será feito automaticamente ao sair ou desinstalar o programa."; "lng_download_path_failed" = "Download do arquivo não pôde iniciar. Isso pode ter acontecido por um problema na pasta de downlaod.\n\nVocê pode alterar o caminho dos downloads em Configurações."; "lng_download_path_settings" = "Configurações"; "lng_download_finish_failed" = "Arquivo baixado não pôde ser finalizado.\n\nGostaria de tentar novamente?"; "lng_download_path_clearing" = "Limpando..."; "lng_download_path_cleared" = "Concluído!"; "lng_download_path_clear_failed" = "Limpeza falhou :("; "lng_settings_section_privacy" = "Privacidade e Segurança"; "lng_local_storage_title" = "Armazenamento local"; "lng_settings_no_data_cached" = "Nada encontrado no cache!"; "lng_settings_images_cached#one" = "{count} imagem, {size}"; "lng_settings_images_cached#other" = "{count} imagens, {size}"; "lng_settings_audios_cached#one" = "{count} mensagem de voz, {size}"; "lng_settings_audios_cached#other" = "{count} mensagens de voz, {size}"; "lng_local_storage_clear" = "Limpar tudo"; "lng_local_storage_clearing" = "Limpando..."; "lng_local_storage_cleared" = "Concluído!"; "lng_local_storage_clear_failed" = "Limpeza falhou :("; "lng_settings_section_advanced_settings" = "Configurações Avançadas"; "lng_settings_enable_night_theme" = "Habilitar modo noturno"; "lng_settings_disable_night_theme" = "Desabilitar modo noturno"; "lng_passcode_remove_button" = "Remover"; "lng_passcode_turn_on" = "Ativar senha de bloqueio"; "lng_passcode_change" = "Alterar senha de bloqueio"; "lng_passcode_create" = "Senha de bloqueio"; "lng_passcode_remove" = "Remover senha de bloqueio"; "lng_passcode_turn_off" = "Desativar"; "lng_passcode_autolock" = "Autobloquear"; "lng_passcode_autolock_away" = "Autobloquear se ausente por:"; "lng_passcode_autolock_inactive" = "Autobloquear se inativo por:"; "lng_passcode_autolock_minutes#one" = "{count} minuto"; "lng_passcode_autolock_minutes#other" = "{count} minutos"; "lng_passcode_autolock_hours#one" = "{count} hora"; "lng_passcode_autolock_hours#other" = "{count} horas"; "lng_passcode_enter_old" = "Insira sua senha atual"; "lng_passcode_enter_first" = "Insira uma senha"; "lng_passcode_enter_new" = "Insira sua nova senha"; "lng_passcode_confirm_new" = "Re-insira sua nova senha"; "lng_passcode_about" = "Quando uma senha de bloqueio é definida, um cadeado aparecerá no topo de sua lista de conversas. Clique para travar o aplicativo.\n\nNota: Se você esquecer sua senha, terá que entrar novamente no Telegram Desktop."; "lng_passcode_differ" = "As senhas não correspondem"; "lng_passcode_wrong" = "Senha errada"; "lng_passcode_is_same" = "Senha não alterada"; "lng_passcode_enter" = "Insira sua senha de bloqueio"; "lng_passcode_ph" = "Sua senha de bloqueio"; "lng_passcode_submit" = "Entrar"; "lng_passcode_logout" = "Sair"; "lng_passcode_need_unblock" = "Você precisa me desbloquear primeiro."; "lng_cloud_password_waiting" = "Link de confirmação enviado para {email}..."; "lng_cloud_password_change" = "Alterar senha da nuvem"; "lng_cloud_password_create" = "Senha da nuvem"; "lng_cloud_password_remove" = "Remover senha da nuvem"; "lng_cloud_password_set" = "Ativar verificação em duas etapas"; "lng_cloud_password_edit" = "Alterar senha da nuvem"; "lng_cloud_password_enter_old" = "Insira sua senha atual"; "lng_cloud_password_enter_first" = "Insira uma senha"; "lng_cloud_password_enter_new" = "Insira sua nova senha"; "lng_cloud_password_confirm_new" = "Re-insira sua nova senha"; "lng_cloud_password_hint" = "Insira uma dica de senha"; "lng_cloud_password_change_hint" = "Insira uma dica de senha"; "lng_cloud_password_bad" = "Senha e dica de senha não podem ser iguais."; "lng_cloud_password_email" = "E-mail de recuperação"; "lng_cloud_password_bad_email" = "E-mail incorreto, por favor, tente outro."; "lng_cloud_password_about" = "Essa senha será requisitada quando você entrar em um novo dispositivo, após o seu PIN."; "lng_cloud_password_about_recover" = "Atenção! Você tem certeza que não quer\nadicionar um email para recuperação de senha?\n\nSe você esquecer sua senha, perderá\nseu acesso a sua conta no Telegram."; "lng_cloud_password_skip_email" = "Pular e-mail"; "lng_cloud_password_almost" = "Link de confirmação enviado para\no email fornecido. A verificação em duas etapas será ativada assim que você abrir o link."; "lng_cloud_password_was_set" = "Verificação em duas etapas ativada."; "lng_cloud_password_updated" = "Sua senha da nuvem foi atualizada."; "lng_cloud_password_removed" = "Verificação em duas etapas desativada."; "lng_cloud_password_differ" = "As senhas não correspondem."; "lng_cloud_password_wrong" = "Senha errada"; "lng_cloud_password_is_same" = "Senha não alterada"; "lng_connection_type" = "Tipo de conexão:"; "lng_connection_auto_connecting" = "Padrão (conectando...)"; "lng_connection_auto" = "Padrão ({transport} usado)"; "lng_connection_proxy_connecting" = "Conectando via proxy..."; "lng_connection_proxy" = "{transport} com proxy"; "lng_connection_header" = "Tipo de conexão"; "lng_connection_auto_rb" = "Auto (TCP se disponível ou HTTP)"; "lng_connection_http_proxy_rb" = "HTTP com http-proxy customizado"; "lng_connection_tcp_proxy_rb" = "TCP com socks5-proxy customizado"; "lng_connection_try_ipv6" = "Tentar conexão via IPv6"; "lng_connection_host_ph" = "Nome do host"; "lng_connection_port_ph" = "Porta"; "lng_connection_user_ph" = "Usuário"; "lng_connection_password_ph" = "Senha"; "lng_connection_save" = "Salvar"; "lng_settings_blocked_users" = "Usuários bloqueados"; "lng_settings_last_seen_privacy" = "Visto por último"; "lng_settings_calls_privacy" = "Chamadas de voz"; "lng_settings_groups_invite_privacy" = "Grupos"; "lng_settings_show_sessions" = "Exibir todas as sessões"; "lng_settings_self_destruct" = "Configurações de auto-destruição da conta"; "lng_settings_change_phone" = "Alterar número de telefone"; "lng_settings_reset" = "Encerrar todas as outras sessões"; "lng_settings_reset_sure" = "Você tem certeza que deseja terminar\ntodas as outras sessões?"; "lng_settings_reset_one_sure" = "Você deseja terminar essa sessão?"; "lng_settings_reset_button" = "Encerrar"; "lng_settings_reset_done" = "Outras sessões encerradas"; "lng_settings_manage_local_storage" = "Gerenciar armazenamento local"; "lng_settings_ask_question" = "Fazer uma Pergunta"; "lng_settings_ask_sure" = "Entenda que o suporte do Telegram é feito por voluntários. Tentaremos responder o mais rápido possível, mas poderemos demorar um pouco.\n\nPor favor verifique a página de Perguntas Frequentes do Telegram: há dicas e respostas para a maioria das perguntas."; "lng_settings_faq_button" = "Ler o FAQ"; "lng_settings_ask_ok" = "Perguntar"; "lng_settings_faq" = "Perguntas Frequentes"; "lng_settings_logout" = "Sair"; "lng_sure_logout" = "Tem certeza que deseja sair?"; "lng_settings_need_restart" = "Você precisará reiniciar para aplicar algumas das novas configurações. Reiniciar agora?"; "lng_settings_restart_now" = "REINICIAR"; "lng_settings_restart_later" = "DEPOIS"; "lng_sessions_header" = "Sessão atual"; "lng_sessions_other_header" = "Sessões ativas"; "lng_sessions_no_other" = "Nenhuma outra sessão"; "lng_sessions_other_desc" = "Você pode entrar no Telegram de outro celular, tablet e computadores, usando o mesmo número de telefone. Todos seus dados estarão sincronizados."; "lng_sessions_terminate_all" = "Encerrar todas as outras sessões"; "lng_blocked_list_title" = "Usuários bloqueados"; "lng_blocked_list_unknown_phone" = "número desconhecido"; "lng_blocked_list_unblock" = "Desbloquear"; "lng_blocked_list_add" = "Bloquear usuário"; "lng_blocked_list_add_title" = "Selecionar usuário para bloquear"; "lng_blocked_list_already_blocked" = "já bloqueado"; "lng_blocked_list_about" = "Usuários bloqueados não podem te enviar mensagens ou adicionar em grupos. Eles não verão suas fotos de perfil, online e status de visto por último."; "lng_blocked_list_not_found" = "Nenhum usuário encontrado"; "lng_edit_privacy_everyone" = "Todos"; "lng_edit_privacy_contacts" = "Meus Contatos"; "lng_edit_privacy_nobody" = "Ninguém"; "lng_edit_privacy_exceptions" = "Adicionar exceções"; "lng_edit_privacy_lastseen_title" = "Visto por último"; "lng_edit_privacy_lastseen_description" = "Você pode escolher quem vê o seu \"visto por último\":"; "lng_edit_privacy_lastseen_warning" = "Importante: você não poderá ver o horário de Visto por Último de pessoas com as quais você não compartilha o seu. Você verá um valor aproximado (recentemente, na última semana, no último mês)."; "lng_edit_privacy_lastseen_always_empty" = "Sempre compartilhar com"; "lng_edit_privacy_lastseen_always#one" = "Sempre compartilhar com {count} usuário"; "lng_edit_privacy_lastseen_always#other" = "Sempre compartilhar com {count} usuários"; "lng_edit_privacy_lastseen_never_empty" = "Nunca compartilhar com"; "lng_edit_privacy_lastseen_never#one" = "Nunca compartilhar com {count} usuário"; "lng_edit_privacy_lastseen_never#other" = "Nunca compartilhar com {count} usuários"; "lng_edit_privacy_lastseen_exceptions" = "Essas configurações irão sobrescrever os valores anteriores."; "lng_edit_privacy_lastseen_always_title" = "Sempre compartilhar com"; "lng_edit_privacy_lastseen_never_title" = "Nunca compartilhar com"; "lng_edit_privacy_groups_title" = "Configurações de convite à grupos"; "lng_edit_privacy_groups_description" = "Você pode escolher quem pode te adicionar em grupos e canais com precisão:"; "lng_edit_privacy_groups_always_empty" = "Sempre permitir"; "lng_edit_privacy_groups_always#one" = "Sempre permitir {count} usuário"; "lng_edit_privacy_groups_always#other" = "Sempre permitir {count} usuários"; "lng_edit_privacy_groups_never_empty" = "Nunca permitir"; "lng_edit_privacy_groups_never#one" = "Nunca permitir {count} usuário"; "lng_edit_privacy_groups_never#other" = "Nunca permitir {count} usuários"; "lng_edit_privacy_groups_exceptions" = "Esses usuários poderão ou não te adicionar em grupos e canais dependendo de suas configurações."; "lng_edit_privacy_groups_always_title" = "Sempre permitir"; "lng_edit_privacy_groups_never_title" = "Nunca permitir"; "lng_edit_privacy_calls_title" = "Chamadas"; "lng_edit_privacy_calls_description" = "Você pode restringir quem te ligou:"; "lng_edit_privacy_calls_always_empty" = "Sempre permitir"; "lng_edit_privacy_calls_always#one" = "Sempre permitir {count} usuário"; "lng_edit_privacy_calls_always#other" = "Sempre permitir {count} usuários"; "lng_edit_privacy_calls_never_empty" = "Nunca permitir"; "lng_edit_privacy_calls_never#one" = "Nunca permitir {count} usuário"; "lng_edit_privacy_calls_never#other" = "Nunca permitir {count} usuários"; "lng_edit_privacy_calls_exceptions" = "Esses usuários poderão ou não te ligar dependendo de suas configurações."; "lng_edit_privacy_calls_always_title" = "Sempre permitir"; "lng_edit_privacy_calls_never_title" = "Nunca permitir"; "lng_self_destruct_title" = "Auto-destruição da conta"; "lng_self_destruct_description" = "Se você não ficar online ao menos uma vez nesse período, sua conta será apagada junto com seus grupos, mensagens e contatos."; "lng_self_destruct_months#one" = "{count} mês"; "lng_self_destruct_months#other" = "{count} meses"; "lng_self_destruct_years#one" = "{count} ano"; "lng_self_destruct_years#other" = "{count} anos"; "lng_change_phone_title" = "Alterar número de telefone"; "lng_change_phone_description" = "Você pode alterar seu número do Telegram aqui.\nSua conta e todos os seus dados — mensagens,\nmídia, contatos, etc. serão movidos para\no novo número.\n\n[b]Importante[/b]: todos os contatos do Telegram terão\nseu [b]novo número[/b] adicionado às suas lista de contatos,\ndesde que eles tenham seu número antigo e você não\nos tenha bloqueado no Telegram."; "lng_change_phone_warning" = "Todos os seus contatos do Telegram terão seu novo número adicionado às suas listas de contatos, desde que eles tenham seu antigo número e você não os tenha bloqueado no Telegram."; "lng_change_phone_occupied" = "O número {phone} já possui uma conta do Telegram. Por favor, exclua esta conta antes de migrar para o novo número."; "lng_change_phone_button" = "Alterar número"; "lng_change_phone_new_title" = "Insira o novo número"; "lng_change_phone_new_description" = "Enviaremos um SMS com um código de confirmação para o seu novo número."; "lng_change_phone_new_submit" = "Enviar"; "lng_change_phone_code_title" = "Insira o código"; "lng_change_phone_code_description" = "Enviamos um SMS com um código de ativação para {phone}."; "lng_change_phone_success" = "Seu número de telefone foi alterado."; "lng_mute_duration_hours#one" = "Por {count} hora"; "lng_mute_duration_hours#other" = "Por {count} horas"; "lng_mute_box_tip" = "Escolha a duração do silêncio das notificações na conversa correspondente"; "lng_preview_loading" = "Obtendo Informações do Link..."; "lng_profile_chat_unaccessible" = "Grupo inacessível"; "lng_profile_about_section" = "Sobre"; "lng_profile_description_section" = "Descrição"; "lng_profile_settings_section" = "Configurações"; "lng_profile_actions_section" = "Ações"; "lng_profile_bot_settings" = "Configurações"; "lng_profile_bot_help" = "Ajuda"; "lng_profile_invite_link_section" = "Link de convite"; "lng_profile_create_public_link" = "Criar link público"; "lng_profile_edit_public_link" = "Editar link público"; "lng_profile_search_members" = "Buscar membros"; "lng_profile_manage_admins" = "Gerenciar administradores"; "lng_profile_manage_blocklist" = "Gerenciar usuários banidos"; "lng_profile_manage_restrictedlist" = "Gerenciar usuários restritos"; "lng_profile_recent_actions" = "Ações recentes"; "lng_profile_common_groups#one" = "{count} grupo em comum"; "lng_profile_common_groups#other" = "{count} grupos em comum"; "lng_profile_common_groups_section" = "Grupos em comum"; "lng_profile_participants_section" = "Membros"; "lng_profile_info_section" = "Info"; "lng_profile_mobile_number" = "Celular:"; "lng_profile_username" = "Nome de usuário:"; "lng_profile_link" = "Link:"; "lng_profile_bio" = "Bio:"; "lng_profile_add_contact" = "Adicionar Contato"; "lng_profile_edit_contact" = "Editar"; "lng_profile_enable_notifications" = "Notificações"; "lng_profile_clear_history" = "Limpar histórico"; "lng_profile_delete_conversation" = "Apagar conversa"; "lng_profile_clear_and_exit" = "Apagar e sair"; "lng_profile_leave_channel" = "Sair do Canal"; "lng_profile_delete_channel" = "Apagar Canal"; "lng_profile_leave_group" = "Sair do grupo"; "lng_profile_delete_group" = "Apagar grupo"; "lng_profile_report" = "Reportar"; "lng_profile_search_messages" = "Buscar mensagens"; "lng_profile_block_user" = "Bloquear usuário"; "lng_profile_unblock_user" = "Desbloquear usuário"; "lng_profile_block_bot" = "Parar e bloquear o bot"; "lng_profile_unblock_bot" = "Desbloquear bot"; "lng_profile_send_message" = "Enviar Mensagem"; "lng_profile_share_contact" = "Compartilhar"; "lng_profile_invite_to_group" = "Adicionar ao Grupo"; "lng_profile_delete_contact" = "Apagar"; "lng_profile_set_group_photo" = "Definir Foto"; "lng_profile_add_participant" = "Adicionar Membros"; "lng_profile_view_channel" = "Visualizar Canal"; "lng_profile_join_channel" = "Entrar"; "lng_profile_delete_and_exit" = "Sair"; "lng_profile_kick" = "Remover"; "lng_profile_admin" = "administrador"; "lng_profile_edit_permissions" = "Editar"; "lng_profile_sure_kick" = "Remover {user} do grupo?"; "lng_profile_sure_kick_channel" = "Remover {user} do canal?"; "lng_profile_sure_kick_admin" = "Remover {user} dos administradores?"; "lng_profile_loading" = "Carregando..."; "lng_profile_shared_media" = "Mídia compartilhada"; "lng_profile_no_media" = "Nenhuma mídia nessa conversa."; "lng_profile_photos#one" = "{count} foto"; "lng_profile_photos#other" = "{count} fotos"; "lng_profile_photos_header" = "Fotos"; "lng_profile_videos#one" = "{count} vídeo"; "lng_profile_videos#other" = "{count} vídeos"; "lng_profile_videos_header" = "Vídeos"; "lng_profile_songs#one" = "{count} áudio"; "lng_profile_songs#other" = "{count} áudios"; "lng_profile_songs_header" = "Áudios"; "lng_profile_files#one" = "{count} arquivo"; "lng_profile_files#other" = "{count} arquivos"; "lng_profile_files_header" = "Arquivos"; "lng_profile_audios#one" = "{count} mensagem de voz"; "lng_profile_audios#other" = "{count} mensagens de voz"; "lng_profile_audios_header" = "Mensagens de voz"; "lng_profile_shared_links#one" = "{count} link compartilhado"; "lng_profile_shared_links#other" = "{count} links compartilhados"; "lng_profile_shared_links_header" = "Links compartilhados"; "lng_profile_copy_phone" = "Copiar Número de Telefone"; "lng_profile_copy_fullname" = "Copiar Nome"; "lng_profile_drop_area_title" = "Solte sua imagem aqui"; "lng_profile_drop_area_subtitle" = "para defini-la como foto do grupo"; "lng_profile_drop_area_subtitle_channel" = "para defini-la como foto do canal"; "lng_profile_top_bar_share_contact" = "Compartilhar"; "lng_report_title" = "Reportar canal"; "lng_report_group_title" = "Reportar grupo"; "lng_report_bot_title" = "Reportar bot"; "lng_report_reason_spam" = "Spam"; "lng_report_reason_violence" = "Violência"; "lng_report_reason_pornography" = "Pornografia"; "lng_report_reason_other" = "Outro"; "lng_report_reason_description" = "Descrição"; "lng_report_button" = "Reportar"; "lng_report_thanks" = "Obrigado! O relatório será revisado pela nossa equipe em breve."; "lng_channel_add_members" = "Adicionar membros"; "lng_channel_add_banned" = "Banir usuário"; "lng_channel_add_restricted" = "Restringir usuário"; "lng_channel_members" = "Membros"; "lng_channel_only_last_shown#one" = "Somente o último {count} membro é exibido aqui"; "lng_channel_only_last_shown#other" = "Somente os últimos {count} membros são exibidos aqui"; "lng_channel_admins" = "Administradores"; "lng_channel_add_admin" = "Adicionar Administrador"; "lng_channel_admins_too_much" = "Você atingiu o limite de administradores. Remova um dos administradores primeiro."; "lng_channel_admin_status_creator" = "Criador"; "lng_channel_admin_status_promoted_by" = "Promovido por {user}"; "lng_channel_admin_status_not_admin" = "Não é administrador"; "lng_channel_banned_status_restricted_by" = "Restringido por {user}"; "lng_group_blocked_list_about" = "Usuários banidos são removidos do grupo e só podem voltar se convidados por um admin.\nLinks de convite não funcionam para eles."; "lng_chat_all_members_admins" = "Todos São Admins"; "lng_chat_about_all_admins" = "Todos podem adicionar novos membros, editar nome e foto do grupo."; "lng_chat_about_admins" = "Administradores podem adicionar e remover membros, editar nome e a foto do grupo."; "lng_participant_filter" = "Buscar"; "lng_participant_invite" = "Convidar"; "lng_participant_invite_sorry#one" = "Desculpe, você só pode adicionar\no primeiro {count} membro ao canal.\n\nDe agora em diante, precisarão\ndo link de convite para entrar."; "lng_participant_invite_sorry#other" = "Desculpe, você só pode adicionar\nos primeiros {count} membros ao canal.\n\nDe agora em diante, precisarão\ndo link de convite para entrar."; "lng_create_group_back" = "Voltar"; "lng_create_group_next" = "Próximo"; "lng_create_group_create" = "Criar"; "lng_create_group_title" = "Novo Grupo"; "lng_create_channel_title" = "Novo Canal"; "lng_create_public_channel_title" = "Canal Público"; "lng_create_public_channel_about" = "Qualquer um pode encontrar o canal na busca e entrar"; "lng_create_private_channel_title" = "Canal privado"; "lng_create_private_channel_about" = "Somente pessoas com um link especial de convite poderão entrar"; "lng_create_public_group_title" = "Grupo Público"; "lng_create_public_group_about" = "Qualquer um pode encontrar o grupo pela busca e entrar, todo o histórico estará disponível"; "lng_create_private_group_title" = "Grupo Privado"; "lng_create_private_group_about" = "Somente pessoas convidadas ou com link de convite podem entrar"; "lng_create_group_skip" = "Pular"; "lng_create_channel_link_invalid" = "Esse link é inválido"; "lng_create_channel_link_occupied" = "Esse link já está em uso"; "lng_create_channel_link_too_short" = "Esse link é muito curto"; "lng_create_channel_link_bad_symbols" = "Esse link contém símbolos incompatíveis"; "lng_create_channel_link_available" = "Esse link está disponível"; "lng_create_channel_link_copied" = "Link copiado para área de transferência"; "lng_create_group_crop" = "Selecione uma área para a foto do grupo"; "lng_create_channel_crop" = "Selecione uma área para a foto do canal"; "lng_failed_add_participant" = "Usuário não pôde ser adicionado. Tente novamente mais tarde."; "lng_failed_add_not_mutual" = "Se uma pessoa deixou o grupo, apenas um contato mútuo pode colocá-la novamente (eles precisam do seu número na agenda do telefone e você do número deles)."; "lng_failed_add_not_mutual_channel" = "Se uma pessoa deixou o canal, apenas um contato mútuo pode colocá-la novamente (eles precisam do seu número na agenda do telefone e você do número deles)."; "lng_sure_delete_contact" = "Você tem certeza que deseja remover {contact} da sua lista de contatos?"; "lng_sure_delete_history" = "Você tem certeza que deseja apagar todo o seu histórico de mensagens com {contact}?\n\nEssa ação não pode ser desfeita."; "lng_sure_delete_group_history" = "Você tem certeza que deseja apagar todo o histórico de «{group}»?\n\nEssa ação não pode ser desfeita."; "lng_sure_delete_and_exit" = "Você tem certeza que deseja apagar todo o seu histórico de mensagens e deixar «{group}»?\n\nEssa ação não pode ser desfeita."; "lng_sure_leave_channel" = "Tem certeza que deseja sair\ndeste canal?"; "lng_sure_delete_channel" = "Tem certeza que deseja apagar este canal? Todos os membros serão removidos e todas as mensagens serão perdidas."; "lng_sure_leave_group" = "Tem certeza que deseja sair do grupo?\nEssa ação não pode ser desfeita."; "lng_sure_delete_group" = "Tem certeza que deseja apagar este grupo? Todos os membros serão removidos e todas as mensagens serão perdidas."; "lng_message_empty" = "Mensagem Vazia"; "lng_message_unsupported" = "Essa mensagem não é suportada em sua versão do Telegram Desktop. Atualize para a última versão nas Configurações ou instale por aqui {link}"; "lng_duration_seconds#one" = "{count} segundo"; "lng_duration_seconds#other" = "{count} segundos"; "lng_duration_minsec_minutes#one" = "{count} min"; "lng_duration_minsec_minutes#other" = "{count} min"; "lng_duration_minsec_seconds#one" = "{count} seg"; "lng_duration_minsec_seconds#other" = "{count} seg"; "lng_duration_minutes_seconds" = "{minutes_count} {seconds_count}"; "lng_action_add_user" = "{from} adicionou {user}"; "lng_action_add_users_many" = "{from} adicionou {users}"; "lng_action_add_users_and_one" = "{accumulated}, {user}"; "lng_action_add_users_and_last" = "{accumulated} and {user}"; "lng_action_add_you" = "{from} adicionou você ao canal"; "lng_action_you_joined" = "Você entrou no canal"; "lng_action_add_you_group" = "{from} adicionou você ao grupo"; "lng_action_you_joined_group" = "Você entrou no grupo"; "lng_action_kick_user" = "{from} removeu {user}"; "lng_action_user_left" = "{from} deixou o grupo"; "lng_action_user_joined" = "{from} entrou no grupo"; "lng_action_user_joined_by_link" = "{from} entrou no grupo via link de convite"; "lng_action_user_registered" = "{from} entrou para o Telegram"; "lng_action_removed_photo" = "{from} removeu a foto do grupo"; "lng_action_removed_photo_channel" = "Foto do canal removida"; "lng_action_changed_photo" = "{from} atualizou a foto do grupo"; "lng_action_changed_photo_channel" = "Foto do canal atualizada"; "lng_action_changed_title" = "{from} alterou o título do grupo para «{title}»"; "lng_action_changed_title_channel" = "Nome do canal foi alterado para «{title}»"; "lng_action_created_chat" = "{from} criou o grupo «{title}»"; "lng_action_created_channel" = "Canal criado"; "lng_action_group_migrate" = "O grupo foi convertido para um supergrupo"; "lng_action_pinned_message" = "{from} fixou «{text}»"; "lng_action_pinned_media" = "{from} fixou {media}"; "lng_action_pinned_media_photo" = "uma foto"; "lng_action_pinned_media_video" = "um vídeo"; "lng_action_pinned_media_audio" = "um áudio"; "lng_action_pinned_media_voice" = "uma mensagem de voz"; "lng_action_pinned_media_video_message" = "uma mensagem de vídeo"; "lng_action_pinned_media_file" = "um arquivo"; "lng_action_pinned_media_gif" = "um GIF"; "lng_action_pinned_media_contact" = "um contato"; "lng_action_pinned_media_location" = "uma localização"; "lng_action_pinned_media_sticker" = "um sticker"; "lng_action_pinned_media_emoji_sticker" = "um {emoji} sticker"; "lng_action_pinned_media_game" = "o jogo «{game}»"; "lng_action_game_score#one" = "{from} marcou {count} ponto em {game}"; "lng_action_game_score#other" = "{count} marcou {count} pontos em {game}"; "lng_action_game_you_scored#one" = "Você marcou {count} ponto em {game}"; "lng_action_game_you_scored#other" = "Você marcou {count} pontos em {game}"; "lng_action_game_score_no_game#one" = "{from} marcou {count} ponto"; "lng_action_game_score_no_game#other" = "{from} marcou {count} pontos"; "lng_action_game_you_scored_no_game#one" = "Você marcou {count} ponto"; "lng_action_game_you_scored_no_game#other" = "Você marcou {count} pontos"; "lng_action_payment_done" = "Você transferiu com sucesso {amount} para {user}"; "lng_action_payment_done_for" = "Você transferiu com sucesso {amount} para {user} por {invoice}"; "lng_action_took_screenshot" = "{from} realizou uma captura de tela!"; "lng_action_you_took_screenshot" = "Você realizou uma captura de tela!"; "lng_ttl_photo_received" = "{from} te enviou uma foto auto-destrutiva. Por favor, veja em seu celular."; "lng_ttl_photo_sent" = "Você enviou uma foto auto-destrutiva."; "lng_ttl_photo_expired" = "A foto expirou"; "lng_ttl_video_received" = "{from} te enviou um vídeo auto-destrutivo. Por favor, veja em seu celular."; "lng_ttl_video_sent" = "Você enviou um vídeo auto-destrutivo."; "lng_ttl_video_expired" = "O vídeo expirou"; "lng_profile_migrate_reached#one" = "{count} limite de membro alcançado"; "lng_profile_migrate_reached#other" = "{count} limite de membros alcançado"; "lng_profile_migrate_body" = "Para ir além desse limite, você pode converter seu grupo em um supergrupo."; "lng_profile_migrate_learn_more" = "Leia mais »"; "lng_profile_migrate_about" = "Se você deseja ir além do limite, pode converter seu grupo em um supergrupo. Nos supergrupos:"; "lng_profile_migrate_feature1#one" = "— O limite de membros é de {count} usuário"; "lng_profile_migrate_feature1#other" = "— O limite de membros é de {count} usuários"; "lng_profile_migrate_feature2" = "— Novos membros podem ver todo o histórico"; "lng_profile_migrate_feature3" = "— Administradores podem apagar mensagens para todos"; "lng_profile_migrate_feature4" = "— Notificações silenciadas por padrão"; "lng_profile_migrate_button" = "Converter a supergrupo"; "lng_profile_migrate_sure" = "Tem certeza que deseja converter esse grupo para um supergrupo? Essa ação não pode ser desfeita."; "lng_profile_convert_button" = "Converter a Supergrupo"; "lng_profile_convert_title" = "Converter a Supergrupo"; "lng_profile_convert_about" = "Em supergrupos:"; "lng_profile_convert_feature1" = "— Novos membros podem ver todo o histórico"; "lng_profile_convert_feature2" = "— Mensagens apagadas desaparecerão para todos"; "lng_profile_convert_feature3" = "— Admins podem fixar mensagens importantes"; "lng_profile_convert_feature4" = "— Criador pode definir um link público para o grupo"; "lng_profile_convert_warning" = "{bold_start}Nota:{bold_end} Essa ação não pode ser desfeita"; "lng_profile_convert_confirm" = "Converter"; "lng_profile_add_more_after_upgrade#one" = "Você pode adicionar até {count} membro depois de converter seu grupo a um supergrupo."; "lng_profile_add_more_after_upgrade#other" = "Você pode adicionar até {count} membros depois de converter seu grupo a um supergrupo."; "lng_channel_not_accessible" = "Desculpe, esse canal não está acessível."; "lng_group_not_accessible" = "Desculpe, esse grupo não está acessível."; "lng_channels_too_much_public_about" = "Desculpe, você reservou muitos nomes públicos. Você pode remover o link público de um de seus grupos ou canais."; "lng_channels_too_much_public_revoke_confirm_group" = "Você tem certeza que deseja remover o link {link}?\n\nO grupo «{group}» se tornará privado."; "lng_channels_too_much_public_revoke_confirm_channel" = "Você tem certeza que deseja remover o link {link}?\n\nO canal «{group}» se tornará privado."; "lng_channels_too_much_public_revoke" = "Remover"; "lng_group_invite_bad_link" = "Link de convite quebrado ou expirado."; "lng_group_invite_want_join" = "Você deseja entrar no grupo «{title}»?"; "lng_group_invite_want_join_channel" = "Você deseja entrar no canal «{title}»?"; "lng_group_invite_join" = "Entrar"; "lng_group_invite_members#one" = "{count} membro, entre eles:"; "lng_group_invite_members#other" = "{count} membros, entre eles:"; "lng_group_invite_link" = "Link de convite:"; "lng_group_invite_create" = "Criar um link de convite"; "lng_group_invite_about" = "Usuários do Telegram poderão entrar\nem seu grupo clicando no link."; "lng_group_invite_create_new" = "Desativar link"; "lng_group_invite_about_new" = "Seu link de convite será desativado e iremos gerar um novo link para você."; "lng_group_invite_copied" = "Link copiado para área de transferência."; "lng_group_invite_no_room" = "Não foi possível entrar no grupo porque ele já possui muitos membros."; "lng_channel_public_link_copied" = "Link copiado para área de transferência."; "lng_forwarded" = "Encaminhado de {user}"; "lng_forwarded_date" = "Original: {date}"; "lng_forwarded_channel" = "Encaminhado de {channel}"; "lng_forwarded_via" = "Encaminhado de {user} via {inline_bot}"; "lng_forwarded_channel_via" = "Encaminhado de {channel} via {inline_bot}"; "lng_forwarded_signed" = "{channel} ({user})"; "lng_in_reply_to" = "Em resposta a"; "lng_edited" = "editado"; "lng_edited_date" = "Editado: {date}"; "lng_cancel_edit_post_sure" = "Cancelar edição?"; "lng_cancel_edit_post_yes" = "Sim"; "lng_cancel_edit_post_no" = "Não"; "lng_bot_share_location_unavailable" = "O compartilhamento de localização está atualmente indisponível no Telegram Desktop."; "lng_bot_inline_geo_unavailable" = "Esse bot requer compartilhamento de localização\nIsso não está disponível no Telegram Desktop."; "lng_bot_share_phone" = "Compartilhar Número de Telefone?"; "lng_bot_share_phone_confirm" = "Compartilhar"; "lng_attach_failed" = "Falhou"; "lng_attach_file" = "Arquivo"; "lng_attach_photo" = "Foto"; "lng_media_type" = "Tipo de mídia"; "lng_media_type_photos" = "Fotos"; "lng_media_type_videos" = "Vídeos"; "lng_media_type_songs" = "Áudios"; "lng_media_type_files" = "Arquivos"; "lng_media_type_audios" = "Mensagens de voz"; "lng_media_type_links" = "Links"; "lng_media_open_with" = "Abrir Com"; "lng_media_download" = "Download"; "lng_media_cancel" = "Cancelar"; "lng_media_video" = "Vídeo"; "lng_media_audio" = "Mensagem de voz"; "lng_media_auto_settings" = "Download automático de mídia"; "lng_media_auto_photo" = "Baixar foto automaticamente"; "lng_media_auto_audio" = "Baixar áudio automaticamente"; "lng_media_auto_gif" = "Baixar GIF automaticamente"; "lng_media_auto_private_chats" = "Conversas privadas"; "lng_media_auto_groups" = "Grupos e canais"; "lng_media_auto_play" = "Reproduzir automaticamente"; "lng_emoji_category0" = "Usados frequentemente"; "lng_emoji_category1" = "Pessoas"; "lng_emoji_category2" = "Natureza"; "lng_emoji_category3" = "Comidas e Bebidas"; "lng_emoji_category4" = "Atividade"; "lng_emoji_category5" = "Viagens e Lugares"; "lng_emoji_category6" = "Objetos"; "lng_emoji_category7" = "Símbolos e Bandeiras"; "lng_emoji_hide_panel" = "Clique aqui para esconder a barra de emojis"; "lng_recent_stickers" = "Usados frequentemente"; "lng_faved_stickers" = "Stickers favoritos"; "lng_faved_stickers_add" = "Adicionar aos Favoritos"; "lng_faved_stickers_remove" = "Remover dos Favoritos"; "lng_group_stickers" = "Stickers do grupo"; "lng_group_stickers_description" = "Você pode escolher um pacote de sticker para estar disponível para todos os membros enquanto conversam no grupo."; "lng_group_stickers_add" = "Escolher pacote de sticker"; "lng_switch_stickers" = "Stickers"; "lng_switch_emoji" = "Emoji"; "lng_switch_gifs" = "GIFs"; "lng_stickers_featured_add" = "Adicionar"; "lng_gifs_search" = "Buscar GIFs"; "lng_gifs_no_saved" = "Você ainda não possui GIFs salvos."; "lng_inline_bot_no_results" = "Nenhum resultado."; "lng_inline_bot_via" = "via {inline_bot}"; "lng_box_remove" = "Remover"; "lng_stickers_installed_tab" = "Stickers"; "lng_stickers_featured_tab" = "Populares"; "lng_stickers_archived_tab" = "Arquivados"; "lng_stickers_remove_pack" = "Remover «{sticker_pack}»?"; "lng_stickers_add_pack" = "Adicionar aos Stickers"; "lng_stickers_share_pack" = "Compartilhar Stickers"; "lng_stickers_not_found" = "Pacote de sticker não encontrado."; "lng_stickers_packs_archived" = "Alguns de seus stickers não usados foram arquivados para dar espaço aos pacotes que você ativou."; "lng_stickers_archived" = "Stickers Arquivados"; "lng_stickers_copied" = "Link copiado para a área de transferência."; "lng_stickers_default_set" = "Grandes Mentes"; "lng_stickers_you_have" = "Gerenciar e reordenar os pacotes de sticker"; "lng_stickers_featured" = "Stickers Populares"; "lng_stickers_return" = "Desfazer"; "lng_stickers_count#one" = "{count} sticker"; "lng_stickers_count#other" = "{count} stickers"; "lng_stickers_masks_pack" = "Esse é um pacote de máscaras. Você pode usá-las no editor de fotos em nossos aplicativos para celular."; "lng_stickers_group_set" = "Pacote de stickers do grupo"; "lng_stickers_remove_group_set" = "Remover pacote de sticker do grupo?"; "lng_stickers_group_from_your" = "Escolher de seus stickers"; "lng_in_dlg_photo" = "Foto"; "lng_in_dlg_video" = "Vídeo"; "lng_in_dlg_audio_file" = "Áudio"; "lng_in_dlg_contact" = "Contato"; "lng_in_dlg_audio" = "Mensagem de voz"; "lng_in_dlg_video_message" = "Mensagem de vídeo"; "lng_in_dlg_file" = "Arquivo"; "lng_in_dlg_sticker" = "Sticker"; "lng_in_dlg_sticker_emoji" = "{emoji} Sticker"; "lng_ban_user" = "Banir Usuário"; "lng_delete_all_from" = "Apagar tudo deste usuário"; "lng_report_spam" = "Reportar Spam"; "lng_report_spam_hide" = "Ocultar"; "lng_report_spam_thanks" = "Obrigado por reportar!"; "lng_report_spam_sure" = "Tem certeza que deseja reportar spam desse usuário?"; "lng_report_spam_sure_group" = "Tem certeza que deseja reportar spam desse grupo?"; "lng_report_spam_sure_channel" = "Tem certeza que deseja reportar spam desse canal?"; "lng_report_spam_ok" = "Reportar"; "lng_cant_send_to_not_contact" = "Desculpe, você só pode enviar mensagens para contatos mútuos no momento. {more_info}"; "lng_cant_invite_not_contact" = "Desculpe, no momento você só pode adicionar contatos mútuos em grupos. {more_info}"; "lng_cant_send_to_not_contact_flood" = "Você contatou muitos não-contatos hoje, tente novamente amanhã. Hoje você poderá responder hoje se esse usuário te enviar uma mensagem primeiro."; "lng_cant_invite_not_contact_flood" = "Você não pode adicionar esse usuário porque você contatou muitos não-contatos hoje. Tente novamente amanhã. Você pode pedir para outro membro adicionar este usuário ao grupo."; "lng_cant_more_info" = "Informações »"; "lng_cant_invite_banned" = "Apenas um administrador pode adicionar esse usuário."; "lng_cant_invite_privacy" = "Você não pode adicionar esse usuário em grupos devido as configurações de privacidade."; "lng_cant_invite_privacy_channel" = "Você não pode adicionar esse usuário em canais devido as configurações de privacidade."; "lng_cant_delete_group#one" = "Desculpe, atualmente não é possível apagar comunidades que tenham mais de {count} membro. Por favor, contate o suporte do Telegram se você precisa apagar esse grupo."; "lng_cant_delete_group#other" = "Desculpe, atualmente não é possível apagar comunidades que tenham mais de {count} membros. Por favor, contate o suporte do Telegram se você precisa apagar esse grupo."; "lng_cant_delete_channel#one" = "Desculpe, atualmente não é possível apagar comunidades que tenham mais de {count} membro. Por favor, contate o suporte do Telegram se você precisa apagar esse canal."; "lng_cant_delete_channel#other" = "Desculpe, atualmente não é possível apagar comunidades que tenham mais de {count} membros. Por favor, contate o suporte do Telegram se você precisa apagar esse canal."; "lng_cant_do_this" = "Essa ação não está disponível."; "lng_send_button" = "Enviar"; "lng_message_ph" = "Escrever uma mensagem..."; "lng_broadcast_ph" = "Transmitir uma mensagem..."; "lng_broadcast_silent_ph" = "Transmissão silenciosa..."; "lng_record_cancel" = "Solte fora desse campo para cancelar"; "lng_will_be_notified" = "Os membros serão notificados quando você postar"; "lng_wont_be_notified" = "Os membros não serão notificados quando você postar"; "lng_willbe_history" = "Selecione um chat para começar a conversar"; "lng_from_you" = "Você"; "lng_from_draft" = "Rascunho"; "lng_bot_description" = "O que esse bot pode fazer?"; "lng_unblock_button" = "Desbloquear"; "lng_channel_join" = "Entrar no Canal"; "lng_channel_mute" = "Silenciar"; "lng_channel_unmute" = "Restaurar Som"; "lng_dialogs_text_with_from" = "{from_part} {message}"; "lng_dialogs_text_from_wrapped" = "{from}:"; "lng_dialogs_text_media" = "{media_part} {caption}"; "lng_dialogs_text_media_wrapped" = "{media},"; "lng_dialogs_show_all_chats" = "Mostrar todas as conversas"; "lng_dialogs_hide_muted_chats" = "Esconder conversas silenciadas"; "lng_open_this_link" = "Abrir este link?"; "lng_open_link" = "Abrir"; "lng_allow_bot_pass" = "Você permite ao {bot_name} passar seu nome do Telegram e id para páginas web que você abrir através desse bot?"; "lng_allow_bot" = "Permitir"; "lng_bot_start" = "Iniciar"; "lng_bot_choose_group" = "Selecione um Grupo"; "lng_bot_no_groups" = "Você não possui grupos"; "lng_bot_groups_not_found" = "Nenhum grupo encontrado"; "lng_bot_sure_invite" = "Adicionar bot ao «{group}»?"; "lng_bot_already_in_group" = "O bot já é um membro do grupo."; "lng_bot_choose_chat" = "Selecione uma Conversa"; "lng_bot_no_chats" = "Você não possui conversas"; "lng_bot_chats_not_found" = "Nenhuma conversa encontrada"; "lng_bot_sure_share_game" = "Compartilhar o jogo com {user}?"; "lng_bot_sure_share_game_group" = "Compartilhar o jogo com «{group}»?"; "lng_typing" = "escrevendo"; "lng_user_typing" = "{user} está escrevendo"; "lng_users_typing" = "{user} e {second_user} estão escrevendo"; "lng_many_typing#one" = "{count} pessoa está escrevendo"; "lng_many_typing#other" = "{count} pessoas estão escrevendo"; "lng_playing_game" = "jogando"; "lng_user_playing_game" = "{user} está jogando"; "lng_users_playing_game" = "{user} e {second_user} estão jogando"; "lng_many_playing_game#one" = "{count} pessoa está jogando"; "lng_many_playing_game#other" = "{count} pessoas estão jogando"; "lng_send_action_record_video" = "gravando um vídeo"; "lng_user_action_record_video" = "{user} está gravando um vídeo"; "lng_send_action_upload_video" = "enviando um vídeo"; "lng_user_action_upload_video" = "{user} está enviando um vídeo"; "lng_send_action_record_audio" = "gravando uma mensagem de voz"; "lng_user_action_record_audio" = "{user} está gravando um áudio"; "lng_send_action_upload_audio" = "enviando uma mensagem de voz"; "lng_user_action_upload_audio" = "{user} está enviando um áudio"; "lng_send_action_record_round" = "gravando uma mensagem de vídeo"; "lng_user_action_record_round" = "{user} está gravando uma mensagem de vídeo"; "lng_send_action_upload_round" = "enviando uma mensagem de vídeo"; "lng_user_action_upload_round" = "{user} está enviando uma mensagem de vídeo"; "lng_send_action_upload_photo" = "enviando uma foto"; "lng_user_action_upload_photo" = "{user} está enviando uma foto"; "lng_send_action_upload_file" = "enviando um arquivo"; "lng_user_action_upload_file" = "{user} está enviando um arquivo"; "lng_send_action_geo_location" = "escolhendo uma localização"; "lng_user_action_geo_location" = "{user} está escolhendo uma localização"; "lng_send_action_choose_contact" = "escolhendo um contato"; "lng_user_action_choose_contact" = "{user} está escolhendo um contato"; "lng_unread_bar#one" = "{count} mensagem não lida"; "lng_unread_bar#other" = "{count} mensagens não lidas"; "lng_maps_point" = "Localização"; "lng_save_photo" = "Salvar imagem"; "lng_save_video" = "Salvar vídeo"; "lng_save_audio_file" = "Salvar áudio"; "lng_save_audio" = "Salvar mensagens de voz"; "lng_save_file" = "Salvar arquivo"; "lng_save_downloaded" = "{ready} / {total} {mb}"; "lng_duration_and_size" = "{duration}, {size}"; "lng_duration_played" = "{played} / {duration}"; "lng_date_and_duration" = "{date}, {duration}"; "lng_choose_image" = "Escolha uma imagem"; "lng_choose_files" = "Escolher arquivos"; "lng_game_tag" = "Jogar"; "lng_context_view_profile" = "Ver perfil"; "lng_context_view_group" = "Ver info do grupo"; "lng_context_view_channel" = "Ver info do canal"; "lng_context_pin_to_top" = "Fixar no topo"; "lng_context_unpin_from_top" = "Desafixar do topo"; "lng_context_promote_admin" = "Promover a administrador"; "lng_context_edit_permissions" = "Editar permissões"; "lng_context_restrict_user" = "Restringir usuário"; "lng_context_remove_from_group" = "Remover do grupo"; "lng_context_copy_link" = "Copiar Link"; "lng_context_copy_post_link" = "Copiar Link do Post"; "lng_context_copy_email" = "Copiar Endereço de Email"; "lng_context_copy_hashtag" = "Copiar Hashtag"; "lng_context_copy_mention" = "Copiar Nome de Usuário"; "lng_context_save_image" = "Salvar Imagem Como..."; "lng_context_forward_image" = "Encaminhar Imagem"; "lng_context_delete_image" = "Apagar Imagem"; "lng_context_copy_image" = "Copiar Imagem"; "lng_context_close_image" = "Fechar Imagem"; "lng_context_cancel_download" = "Cancelar Download"; "lng_context_show_in_folder" = "Mostrar na Pasta"; "lng_context_show_in_finder" = "Mostrar no Finder"; "lng_context_save_video" = "Salvar Vídeo Como..."; "lng_context_save_audio_file" = "Salvar Áudio Como..."; "lng_context_save_audio" = "Salvar Mensagem de Voz Como..."; "lng_context_pack_info" = "Informação do pacote"; "lng_context_pack_add" = "Adicionar aos Stickers"; "lng_context_save_file" = "Salvar Arquivo Como..."; "lng_context_forward_file" = "Encaminhar Arquivo"; "lng_context_delete_file" = "Apagar Arquivo"; "lng_context_close_file" = "Fechar Arquivo"; "lng_context_copy_text" = "Copiar Texto"; "lng_context_open_gif" = "Abrir GIF"; "lng_context_save_gif" = "Salvar GIF"; "lng_context_to_msg" = "Ir Para Mensagem"; "lng_context_reply_msg" = "Responder"; "lng_context_edit_msg" = "Editar"; "lng_context_forward_msg" = "Encaminhar Mensagem"; "lng_context_delete_msg" = "Apagar Mensagem"; "lng_context_select_msg" = "Selecionar Mensagem"; "lng_context_pin_msg" = "Fixar Mensagem"; "lng_context_unpin_msg" = "Desafixar Mensagem"; "lng_context_cancel_upload" = "Cancelar Envio"; "lng_context_copy_selected" = "Copiar Texto Selecionado"; "lng_context_copy_selected_items" = "Copiar Seleção como Texto"; "lng_context_forward_selected" = "Encaminhar Selecionado"; "lng_context_delete_selected" = "Apagar Selecionado"; "lng_context_clear_selection" = "Limpar Seleção"; "lng_really_send_image" = "Você deseja enviar essa imagem?"; "lng_really_send_file" = "Você deseja enviar esse arquivo?"; "lng_really_share_contact" = "Você deseja compartilhar esse contato?"; "lng_send_images_compress#one" = "Comprimir imagem"; "lng_send_images_compress#other" = "Comprimir imagens"; "lng_send_image_non_local" = "Não foi possível enviar um arquivo não local: {name}"; "lng_send_image_empty" = "Não foi possível enviar um arquivo vazio: {name}"; "lng_send_image_too_large" = "Não foi possível enviar um arquivo, por ser maior que 1500 MB: {name}"; "lng_send_folder" = "Não pude enviar «{name}» porque é um diretório :("; "lng_send_images_selected#one" = "{count} imagem selecionada"; "lng_send_images_selected#other" = "{count} imagens selecionadas"; "lng_send_photos#one" = "Enviar {count} foto"; "lng_send_photos#other" = "Enviar {count} fotos"; "lng_send_files_selected#one" = "{count} arquivo selecionado"; "lng_send_files_selected#other" = "{count} arquivos selecionados"; "lng_send_files#one" = "Enviar {count} arquivo"; "lng_send_files#other" = "Enviar {count} arquivos"; "lng_forward_choose" = "Escolher recipiente..."; "lng_forward_cant" = "Desculpe, não há como encaminhar aqui :("; "lng_forward_confirm" = "Encaminhar para {recipient}?"; "lng_forward_share_contact" = "Compartilhar contato com {recipient}?"; "lng_forward_share_cant" = "Não há como compartilhar um contato aqui :("; "lng_forward_send_file_confirm" = "Enviar «{name}» para {recipient}"; "lng_forward_send_files_confirm" = "Enviar arquivos selecionados para {recipient}?"; "lng_forward_send_files_cant" = "Não há como compartilhar um mídias aqui :("; "lng_forward_send" = "Enviar"; "lng_forward_messages#one" = "{count} mensagem encaminhada"; "lng_forward_messages#other" = "{count} mensagens encaminhadas"; "lng_forwarding_from#one" = "{user} e {count} outro"; "lng_forwarding_from#other" = "{user} e {count} outros"; "lng_forwarding_from_two" = "{user} e {second_user}"; "lng_inline_switch_choose" = "Escolher conversa..."; "lng_inline_switch_cant" = "Desculpe, não há como escrever aqui :("; "lng_share_cant" = "Não há como compartilhar aqui :("; "lng_reply_cant" = "Desculpe, não há como responder uma mensagem antiga no supergrupo :("; "lng_reply_cant_forward" = "Não há como responder uma mensagem antiga no supergrupo :( Deseja encaminhá-la e adicionar um comentário?"; "lng_share_title" = "Compartilhar com"; "lng_share_copy_link" = "Copiar link"; "lng_share_confirm" = "Enviar"; "lng_share_wrong_user" = "Este jogo foi aberto por outro usuário."; "lng_share_game_link_copied" = "Link copiado para a área de transferência."; "lng_share_done" = "Concluído!"; "lng_contact_phone" = "Número de telefone"; "lng_enter_contact_data" = "Novo Contato"; "lng_edit_group_title" = "Editar nome do grupo"; "lng_edit_contact_title" = "Editar nome de contato"; "lng_edit_channel_title" = "Editar Canal"; "lng_edit_sign_messages" = "Assinar mensagens"; "lng_edit_group_who_invites" = "Quem pode adicionar membros"; "lng_edit_group_invites_everybody" = "Todos os membros"; "lng_edit_group_invites_only_admins" = "Somente administradores"; "lng_edit_group" = "Editar grupo"; "lng_edit_self_title" = "Editar seu nome"; "lng_confirm_contact_data" = "Novo Contato"; "lng_add_contact" = "Criar"; "lng_add_contact_button" = "Novo contato"; "lng_contacts_header" = "Contatos"; "lng_contact_not_joined" = "Infelizmente {name} não entrou para o Telegram ainda, mas você pode enviá-lo um convite.\n\nNós iremos te notificar sobre qualquer um de seus contatos que entrarem para o Telegram."; "lng_try_other_contact" = "Tentar outro"; "lng_create_group_link" = "Link"; "lng_create_group_invite_link" = "Link de convite"; "lng_create_group_photo" = "Definir Foto"; "lng_create_group_description" = "Descrição (opcional)"; "lng_drag_images_here" = "Soltar imagens aqui"; "lng_drag_photos_here" = "Soltar fotos aqui"; "lng_drag_files_here" = "Soltar arquivos aqui"; "lng_drag_to_send_quick" = "para enviar de maneira rápida"; "lng_drag_to_send_no_compression" = "para enviar sem comprimir"; "lng_drag_to_send_files" = "para enviá-los como arquivos"; "lng_selected_clear" = "Cancelar"; "lng_selected_delete" = "Apagar"; "lng_selected_forward" = "Encaminhar"; "lng_selected_count#one" = "{count} mensagem"; "lng_selected_count#other" = "{count} mensagens"; "lng_selected_cancel_sure_this" = "Cancelar envio?"; "lng_selected_upload_stop" = "Parar"; "lng_selected_delete_sure_this" = "Você deseja apagar essa mensagem?"; "lng_selected_delete_sure#one" = "Você deseja apagar {count} mensagem?"; "lng_selected_delete_sure#other" = "Você deseja apagar {count} mensagens?"; "lng_delete_photo_sure" = "Você deseja apagar essa foto?"; "lng_delete_for_everyone_hint#one" = "Isso irá apagar para todos nessa conversa."; "lng_delete_for_everyone_hint#other" = "Isso irá apagar para todos nessa conversa."; "lng_delete_for_me_chat_hint#one" = "Isso apagará somente para você, não para os outros participantes da conversa."; "lng_delete_for_me_chat_hint#other" = "Isso irá apagar somente para você, não para os outros participantes da conversa."; "lng_delete_for_me_hint#one" = "Isso irá apagar somente para você."; "lng_delete_for_me_hint#other" = "Isso irá apagar somente para você."; "lng_delete_for_everyone_check" = "Apagar para todos"; "lng_delete_for_other_check" = "Apagar para {user}"; "lng_box_delete" = "Apagar"; "lng_box_leave" = "Sair"; "lng_about_version" = "versão {version}"; "lng_about_text_1" = "Mensageiro oficial gratuito baseado na [a href=\"https://core.telegram.org/api\"]Telegram API[/a]\npara velocidade e segurança"; "lng_about_text_2" = "Software sob a licença [a href=\"https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE\"]GNU GPL[/a] versão 3.\nCódigo fonte disponível no [a href=\"https://github.com/telegramdesktop/tdesktop\"]GitHub[/a]."; "lng_about_text_3" = "Visite as {faq_open}Perguntas Frequentes{faq_close} para mais informações."; "lng_about_done" = "Concluído"; "lng_search_no_results" = "Nenhuma mensagem encontrada"; "lng_search_found_results#one" = "Encontrada {count} mensagem"; "lng_search_found_results#other" = "Encontradas {count} mensagens"; "lng_search_global_results" = "Resultados da busca global"; "lng_search_messages_from" = "Mostrar mensagens de"; "lng_media_save_progress" = "{ready} de {total} {mb}"; "lng_mediaview_save_as" = "Salvar Como..."; "lng_mediaview_copy" = "Copiar"; "lng_mediaview_forward" = "Encaminhar"; "lng_mediaview_delete" = "Apagar"; "lng_mediaview_photos_all" = "Visualizar Fotos"; "lng_mediaview_files_all" = "Visualizar Arquivos"; "lng_mediaview_single_photo" = "Foto Única"; "lng_mediaview_group_photo" = "Grupo de Fotos"; "lng_mediaview_channel_photo" = "Foto do Canal"; "lng_mediaview_profile_photo" = "Foto de Perfil"; "lng_mediaview_file_n_of_count" = "{file} {n} de {count}"; "lng_mediaview_n_of_count" = "Foto {n} de {count}"; "lng_mediaview_doc_image" = "Arquivo"; "lng_mediaview_today" = "hoje às {time}"; "lng_mediaview_yesterday" = "ontem às {time}"; "lng_mediaview_date_time" = "{date} às {time}"; "lng_mediaview_saved" = "Imagem foi salva na sua pasta [c]Downloads[/c]"; "lng_theme_preview_title" = "Visualizar Tema"; "lng_theme_preview_generating" = "Gerando uma prévia do tema de cores..."; "lng_theme_preview_invalid" = "Dados inválidos nesse arquivo de tema."; "lng_theme_preview_apply" = "Aplicar esse tema"; "lng_new_authorization" = "{name},\nDetectamos um acesso à sua conta de um novo dispositivo em {day}, {date} às {time}\n\nDispositivo: {device}\nLocalização: {location}\n\nCaso não tenha sido você, vá em Configurações – Mostrar todas as sessões e terminar essa sessão.\n\nSe você acha que alguém entrou em sua conta, você pode ativar a verificação em duas etapas nas Configurações.\n\nObrigado,\nEquipe Telegram"; "lng_new_version_wrap" = "Telegram Desktop foi atualizado para a versão {version}\n\n{changes}\n\nHistórico completo de mudanças disponível aqui:\n{link}"; "lng_new_version_minor" = "— Resolução de bugs e outras melhorias menores"; "lng_new_version_text" = "— Melhorias nas vídeo mensagens: progresso de reprodução, suporte à Picture-in-Picture, contador de duração.\n— Mensagens de voz e de vídeo agora são tocadas automaticamente uma após a outra."; "lng_menu_insert_unicode" = "Inserir caractere de controle Unicode"; "lng_full_name" = "{first_name} {last_name}"; "lng_confirm_phone_link_invalid" = "Esse link está quebrado ou expirado."; "lng_confirm_phone_title" = "Cancelar exclusão da conta"; "lng_confirm_phone_about" = "Alguém com acesso ao seu número de telefone {phone} solicitou a exclusão de sua conta do Telegram e redefiniu sua senha de Verificação em Duas Etapas.\n\nSe não foi você, por favor insira o código que te enviamos via SMS."; "lng_confirm_phone_success" = "Sucesso!\n\nO processo de exclusão foi cancelado em sua conta {phone}. Você pode fechar essa janela agora."; "lng_confirm_phone_send" = "Enviar"; "lng_confirm_phone_enter_code" = "Por favor, insira o código."; "lng_theme_editor_no_keys" = "Ainda não há chaves na paleta"; "lng_theme_editor_cant_change_theme" = "Você não pode aplicar novos temas enquanto está editando a paleta de cores. Feche o editor de temas primeiro."; "lng_theme_editor_new_keys" = "Ainda não está na paleta"; "lng_theme_editor_background_image" = "Imagem de fundo"; "lng_theme_editor_saved_to_jpg" = "Salvar como JPEG, {size}"; "lng_theme_editor_read_from_jpg" = "Imagem JPEG, {size}"; "lng_theme_editor_read_from_png" = "Imagem PNG, {size}"; "lng_theme_editor_export" = "Exportar"; "lng_theme_editor_choose_image" = "Escolher imagem de fundo"; "lng_theme_editor_save_palette" = "Salvar arquivo de paleta"; "lng_theme_editor_choose_name" = "Salvar arquivo de tema"; "lng_theme_editor_error" = "O editor encontrou um erro :( Veja o 'log.txt' para detalhes."; "lng_theme_editor_done" = "Tema exportado com sucesso!"; "lng_theme_editor_title" = "Editar paleta de cores"; "lng_theme_editor_export_button" = "Exportar tema"; "lng_payments_not_supported" = "Desculpe, o Telegram Desktop ainda não suporta pagamentos. Use um de nossos apps móveis para isso."; "lng_payments_receipt_label" = "Recibo"; "lng_payments_receipt_label_test" = "Recibo de teste"; "lng_payments_invoice_label" = "Fatura"; "lng_payments_invoice_label_test" = "Fatura de test"; "lng_payments_receipt_button" = "Recibo"; "lng_call_status_incoming" = "está te ligando..."; "lng_call_status_connecting" = "conectando..."; "lng_call_status_exchanging" = "trocando chaves criptográficas..."; "lng_call_status_waiting" = "esperando..."; "lng_call_status_requesting" = "requisitando..."; "lng_call_status_hanging" = "cancelando..."; "lng_call_status_ended" = "chamada encerrada"; "lng_call_status_failed" = "falha ao conectar"; "lng_call_status_ringing" = "tocando..."; "lng_call_status_busy" = "linha ocupada"; "lng_call_fingerprint_tooltip" = "Se esses emojis são os mesmos na tela de {user}, essa chamada é 100% segura."; "lng_call_error_not_available" = "Desculpe, {user} não aceita chamadas."; "lng_call_error_incompatible" = "O aplicativo de {user} usa um protocolo incompatível. O aplicativo desse usuário precisa ser atualizado para que você possa chamá-lo."; "lng_call_error_outdated" = "O aplicativo de {user} não suporta chamadas. O aplicativo desse usuário precisa ser atualizado para que você possa chamá-lo."; "lng_call_error_audio_io" = "Parece haver um problema com a reprodução de áudio em seu computador. Verifique se os alto-falantes e microfone estão funcionando e tente novamente."; "lng_call_bar_hangup" = "Encerrar chamada"; "lng_call_box_title" = "Chamadas"; "lng_call_box_about" = "Você não fez nenhuma ligação ainda."; "lng_call_box_status_today" = "{time}"; "lng_call_box_status_yesterday" = "Ontem às {time}"; "lng_call_box_status_date" = "{date} às {time}"; "lng_call_box_status_group" = "({count}) {status}"; "lng_call_outgoing" = "Chamada efetuada"; "lng_call_incoming" = "Chamada recebida"; "lng_call_missed" = "Chamada perdida"; "lng_call_cancelled" = "Chamada cancelada"; "lng_call_declined" = "Chamada recusada"; "lng_call_duration_info" = "{time}, {duration}"; "lng_call_type_and_duration" = "{type} ({duration})"; "lng_call_rate_label" = "Avalie a qualidade de sua chamada"; "lng_call_rate_comment" = "Comentário (opcional)"; "lng_player_message_today" = "Hoje às {time}"; "lng_player_message_yesterday" = "Ontem às {time}"; "lng_player_message_date" = "{date} às {time}"; "lng_rights_edit_admin" = "Editar administrador"; "lng_rights_edit_admin_header" = "O que esse administrador pode fazer?"; "lng_rights_about_add_admins_yes" = "Esse admin poderá adicionar novos admins com as mesmas (ou mais limitadas) permissões."; "lng_rights_about_add_admins_no" = "Esse admin não poderá adicionar novos admins."; "lng_rights_about_admin_cant_edit" = "Você não pode editar as permissões desse admin."; "lng_rights_user_restrictions" = "Restrições do usuário"; "lng_rights_user_restrictions_header" = "O que esse usuário pode fazer?"; "lng_rights_channel_info" = "Alterar info do canal"; "lng_rights_channel_post" = "Postar mensagens"; "lng_rights_channel_edit" = "Editar mensagens de outros"; "lng_rights_channel_delete" = "Apagar mensagens de outros"; "lng_rights_group_info" = "Alterar info do grupo"; "lng_rights_group_ban" = "Banir usuários"; "lng_rights_group_invite_link" = "Convidar usuários via link"; "lng_rights_group_invite" = "Adicionar usuários"; "lng_rights_group_pin" = "Fixar mensagens"; "lng_rights_group_delete" = "Apagar mensagens"; "lng_rights_add_admins" = "Adicionar novos admins"; "lng_rights_chat_read" = "Ler mensagens"; "lng_rights_chat_send_text" = "Enviar mensagens"; "lng_rights_chat_send_media" = "Enviar mídias"; "lng_rights_chat_send_stickers" = "Enviar stickers e GIFs"; "lng_rights_chat_send_links" = "Preview de links"; "lng_rights_chat_banned_until_header" = "Restringir até"; "lng_rights_chat_banned_forever" = "Sempre"; "lng_rights_chat_banned_day#one" = "Por {count} dia"; "lng_rights_chat_banned_day#other" = "Por {count} dias"; "lng_rights_chat_banned_week#one" = "Por {count} semana"; "lng_rights_chat_banned_week#other" = "Por {count} semanas"; "lng_rights_chat_banned_custom" = "Personalizado"; "lng_rights_chat_banned_custom_date" = "Até {date}"; "lng_rights_chat_banned_block" = "Banir e remover do grupo"; "lng_restricted_send_message" = "Os administradores do grupo restringiram você de escrever aqui."; "lng_restricted_send_media" = "Os administradores do grupo restringiram você de enviar mídias aqui."; "lng_restricted_send_stickers" = "Desculpe, os administradores do grupo te restringiram do envio de stickers."; "lng_restricted_send_gifs" = "Desculpe, os administradores do grupo te restringiram do envio de GIFs."; "lng_restricted_send_inline" = "Os administradores do grupo restringiram você de usar bots integrados aqui."; "lng_restricted_list_title" = "Usuários restritos"; "lng_banned_list_title" = "Usuários banidos"; "lng_admin_log_title_all" = "Todas as ações"; "lng_admin_log_title_selected" = "Ações selecionadas"; "lng_admin_log_filter" = "Filtro"; "lng_admin_log_filter_title" = "Filtro"; "lng_admin_log_filter_all_actions" = "Todas as ações"; "lng_admin_log_filter_restrictions" = "Novas restrições"; "lng_admin_log_filter_admins_new" = "Novos admins"; "lng_admin_log_filter_members_new" = "Novos membros"; "lng_admin_log_filter_info_group" = "Info do grupo"; "lng_admin_log_filter_info_channel" = "Info do canal"; "lng_admin_log_filter_messages_deleted" = "Mensagens apagadas"; "lng_admin_log_filter_messages_edited" = "Mensagens editadas"; "lng_admin_log_filter_messages_pinned" = "Mensagens fixadas"; "lng_admin_log_filter_members_removed" = "Membros saindo"; "lng_admin_log_filter_all_admins" = "Todos usuários e admins"; "lng_admin_log_about" = "O que é isso?"; "lng_admin_log_about_text" = "Essa é uma lista de todas as ações tomadas pelos membros e admins do grupo nas últimas 48 horas."; "lng_admin_log_no_results_title" = "Nenhuma ação encontrada"; "lng_admin_log_no_results_text" = "Nenhuma ação correspondente foi encontrado."; "lng_admin_log_no_results_search_text" = "Nenhuma ação que contenha \"{query}\" foi encontrada."; "lng_admin_log_no_events_title" = "Ainda não há ações aqui."; "lng_admin_log_no_events_text" = "Nenhuma ação foi tomada pelos\nmembros e admins do grupo\nnas últimas 48 horas."; "lng_admin_log_empty_text" = "Vazio"; "lng_admin_log_changed_title_group" = "{from} alterou o título do grupo para «{title}»"; "lng_admin_log_changed_title_channel" = "{from} alterou o título do canal para «{title}»"; "lng_admin_log_changed_description_group" = "{from} editou a descrição do grupo:"; "lng_admin_log_removed_description_group" = "{from} removeu a descrição do grupo"; "lng_admin_log_changed_description_channel" = "{from} editou a descrição do canal:"; "lng_admin_log_removed_description_channel" = "{from} removeu a descrição do canal"; "lng_admin_log_previous_description" = "Descrição prévia"; "lng_admin_log_changed_link_group" = "{from} alterou o link do grupo"; "lng_admin_log_removed_link_group" = "{from} removeu o link do grupo"; "lng_admin_log_changed_link_channel" = "{from} alterou o link do canal:"; "lng_admin_log_removed_link_channel" = "{from} removeu o link do canal"; "lng_admin_log_previous_link" = "Link prévio"; "lng_admin_log_changed_photo_group" = "{from} alterou a foto do grupo"; "lng_admin_log_changed_photo_channel" = "{from} alterou a foto do canal"; "lng_admin_log_removed_photo_group" = "{from} removeu a foto do grupo"; "lng_admin_log_removed_photo_channel" = "{from} removeu a foto do canal"; "lng_admin_log_invites_enabled" = "{from} habilitou convites ao grupo"; "lng_admin_log_invites_disabled" = "{from} desabilitou convites ao grupo"; "lng_admin_log_signatures_enabled" = "{from} assinaturas habilitadas"; "lng_admin_log_signatures_disabled" = "{from} assinaturas desabilitadas"; "lng_admin_log_pinned_message" = "{from} fixou a mensagem:"; "lng_admin_log_unpinned_message" = "{from} desafixou a mensagem"; "lng_admin_log_edited_caption" = "{from} editou a legenda:"; "lng_admin_log_removed_caption" = "{from} removeu a legenda"; "lng_admin_log_previous_caption" = "Legenda original"; "lng_admin_log_edited_message" = "{from} editou a mensagem:"; "lng_admin_log_previous_message" = "Mensagem original"; "lng_admin_log_deleted_message" = "{from} apagou a mensagem:"; "lng_admin_log_participant_joined" = "{from} entrou no grupo"; "lng_admin_log_participant_joined_channel" = "{from} entrou no canal"; "lng_admin_log_participant_left" = "{from} deixou o grupo"; "lng_admin_log_participant_left_channel" = "{from} saiu do canal"; "lng_admin_log_invited" = "convidou {user}"; "lng_admin_log_banned" = "baniu {user}"; "lng_admin_log_restricted" = "alterou as restrições de {user} {until}"; "lng_admin_log_promoted" = "alterou os privilégios de {user}"; "lng_admin_log_changed_stickers_group" = "{from} alterou o {sticker_set} do grupo"; "lng_admin_log_changed_stickers_set" = "pacote de sticker"; "lng_admin_log_removed_stickers_group" = "{from} removeu o pacote de sticker do grupo"; "lng_admin_log_user_with_username" = "{name} ({mention})"; "lng_admin_log_restricted_forever" = "indefinidamente"; "lng_admin_log_restricted_until" = "até {data}"; "lng_admin_log_banned_view_messages" = "Ler mensagens"; "lng_admin_log_banned_send_messages" = "Enviar mensagens"; "lng_admin_log_banned_send_media" = "Enviar mídias"; "lng_admin_log_banned_send_stickers" = "Enviar stickers e GIFs"; "lng_admin_log_banned_embed_links" = "Preview de links"; "lng_admin_log_admin_change_info" = "Alterar info"; "lng_admin_log_admin_post_messages" = "Postar mensagens"; "lng_admin_log_admin_edit_messages" = "Editar mensagens"; "lng_admin_log_admin_delete_messages" = "Apagar mensagens"; "lng_admin_log_admin_ban_users" = "Banir usuários"; "lng_admin_log_admin_invite_users" = "Adicionar usuários"; "lng_admin_log_admin_invite_link" = "Convidar usuários via link"; "lng_admin_log_admin_pin_messages" = "Fixar mensagens"; "lng_admin_log_admin_add_admins" = "Adicionar novos admins"; // Not used "lng_topbar_info" = "Info"; "lng_profile_group_info" = "Informação do grupo"; "lng_profile_channel_info" = "Info do Canal"; "lng_channel_add_admins" = "Novo administrador"; "lng_dlg_search_chat" = "Buscar nesse chat"; "lng_dlg_search_channel" = "Buscar nesse canal"; // Wnd specific "lng_wnd_choose_program_menu" = "Escolher programa padrão..."; "lng_wnd_menu_undo" = "Desfazer"; "lng_wnd_menu_redo" = "Refazer"; // Linux specific "lng_linux_menu_undo" = "Desfazer"; "lng_linux_menu_redo" = "Refazer"; // Mac specific "lng_mac_choose_program_menu" = "Outro..."; "lng_mac_choose_app" = "Escolher Aplicação"; "lng_mac_choose_text" = "Escolher aplicação para abrir o documento \"{file}\"."; "lng_mac_enable_filter" = "Habilitar:"; "lng_mac_recommended_apps" = "Aplicações Recomendadas"; "lng_mac_all_apps" = "Todas Aplicações"; "lng_mac_always_open_with" = "Abrir Sempre Com"; "lng_mac_this_app_can_open" = "Essa aplicação pode abrir \"{file}\"."; "lng_mac_not_known_app" = "Não sabemos se essa aplicação pode abrir \"{file}\"."; "lng_mac_menu_services" = "Serviços"; "lng_mac_menu_hide_telegram" = "Ocultar {telegram}"; "lng_mac_menu_hide_others" = "Ocultar Outros"; "lng_mac_menu_show_all" = "Mostrar Todos"; "lng_mac_menu_preferences" = "Preferências..."; "lng_mac_menu_quit_telegram" = "Fechar {telegram}"; "lng_mac_menu_about_telegram" = "Sobre {telegram}"; "lng_mac_menu_file" = "Arquivo"; "lng_mac_menu_logout" = "Sair"; "lng_mac_menu_edit" = "Editar"; "lng_mac_menu_undo" = "Desfazer"; "lng_mac_menu_redo" = "Refazer"; "lng_mac_menu_cut" = "Cortar"; "lng_mac_menu_copy" = "Copiar"; "lng_mac_menu_paste" = "Colar"; "lng_mac_menu_delete" = "Apagar"; "lng_mac_menu_select_all" = "Selecionar Tudo"; "lng_mac_menu_window" = "Janela"; "lng_mac_menu_contacts" = "Contatos"; "lng_mac_menu_add_contact" = "Adicionar Contato"; "lng_mac_menu_new_group" = "Novo Grupo"; "lng_mac_menu_new_channel" = "Novo Canal"; "lng_mac_menu_show" = "Mostrar Telegram"; // Keys finished tdesktop-1.2.17/Telegram/Resources/langs/list000066400000000000000000000000251326245125100211510ustar00rootroot00000000000000de,es,it,ko,nl,pt_BR tdesktop-1.2.17/Telegram/Resources/langs/nl.lproj/000077500000000000000000000000001326245125100220145ustar00rootroot00000000000000tdesktop-1.2.17/Telegram/Resources/langs/nl.lproj/Localizable.strings000066400000000000000000000003601326245125100256470ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ tdesktop-1.2.17/Telegram/Resources/langs/pt-BR.lproj/000077500000000000000000000000001326245125100223275ustar00rootroot00000000000000tdesktop-1.2.17/Telegram/Resources/langs/pt-BR.lproj/Localizable.strings000066400000000000000000000003601326245125100261620ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ tdesktop-1.2.17/Telegram/Resources/langs/refresh.py000066400000000000000000000013661326245125100222740ustar00rootroot00000000000000import os, sys, requests, re from os.path import expanduser filename = '' for arg in sys.argv: if re.match(r'.*\.strings$', arg): filename = expanduser(arg) result = '' if not os.path.isfile(filename): print("File not found.") sys.exit(1) with open(filename) as f: for line in f: if re.match(r'\s*\/\* new from [a-zA-Z0-9\.]+ \*\/\s*', line): continue if re.match(r'\"lng_[a-z_]+\#(zero|two|few|many)\".+', line): continue result = result + line remove = 0 while (len(result) > remove + 1) and (result[len(result) - remove - 1] == '\n') and (result[len(result) - remove - 2] == '\n'): remove = remove + 1 result = result[:len(result) - remove] with open('lang.strings', 'w') as out: out.write(result) sys.exit() tdesktop-1.2.17/Telegram/Resources/langs/refresh.sh000077500000000000000000000011521326245125100222520ustar00rootroot00000000000000set -e FullExecPath=$PWD pushd `dirname $0` > /dev/null FullScriptPath=`pwd` popd > /dev/null FileName="$1" if [ ! -d "$FullScriptPath/../../../../TelegramPrivate" ]; then echo "" echo "This script is for building the production version of Telegram Desktop." echo "" echo "For building custom versions please visit the build instructions page at:" echo "https://github.com/telegramdesktop/tdesktop/#build-instructions" exit fi Error () { cd $FullExecPath echo "$1" exit 1 } if [ ! -f "$FileName" ]; then Error "File '$FileName' not found." fi cd "$FullScriptPath" python refresh.py $FileName tdesktop-1.2.17/Telegram/Resources/langs/upload.sh000077500000000000000000000012501326245125100220770ustar00rootroot00000000000000#!/usr/bin/env bash set -e FullExecPath=$PWD pushd `dirname $0` > /dev/null FullScriptPath=`pwd` popd > /dev/null if [ ! -d "$FullScriptPath/../../../../TelegramPrivate" ]; then echo "" echo "This script is for building the production version of Telegram Desktop." echo "" echo "For building custom versions please visit the build instructions page at:" echo "https://github.com/telegramdesktop/tdesktop/#build-instructions" exit fi Error () { cd $FullExecPath echo "$1" exit 1 } cd $FullScriptPath/../../../../ while IFS='' read -r line || [[ -n "$line" ]]; do tx pull -f -l $line done < tdesktop/Telegram/Resources/langs/list tx push -s cd $FullExecPath tdesktop-1.2.17/Telegram/Resources/night.tdesktop-theme000066400000000000000000000212551326245125100231470ustar00rootroot00000000000000PK ?kWk9 tiled.jpg9LQvfvvW`w]]4P:r51DoJl[+D YX}yc*+)-RJ\"U$T46ɅuUҵ`E0Pz$(OUTMU5CiwH2<;&xc~˓K}HUӬ5B#|æ-LC* $!&'G|i1imv@KXXTE3/on6jhBkty@OdvtҊzc9U mvGLl\/c3rgΚ7'x J*.Zd+5VY׸~CM[n۾c{]G;~3gpܸy?x84<2/^zf>~%%"o ~=%-MdY݅z>:5)鳧Ty1鹣ɲ? kaFD"S Q &3O]-OoPK lcolors.tdesktop-theme][s6~ׯ`V¹%/'rV"-b$rHV~Aw hRK|Fx̊|>z.rC]x5hsF kF6g+SY@Œ^9',<+D/yEuÚgzvWj,UQ%tU./OGƳieݨ:ut*-FXf|y[,U6s)ĂjKTi3-+8I)iT6eHȍG4ݬVSqi6j=_l&3 ~sT?TΊAM)Y">MJUi[- a#kʊxjNiyU5[j6H"~`Q]UHf;\Y8D)߰Ʉ|ѐ4lϚmɂMoK^iZNC6'RHZQ4 `شaV&r[A"βKԟ\;oUzok'2$Q#ڞbm-ҕaV:3,qq_|:h'@ce іIp It0UW`Cpq"içеoۉvWr5ccqc͎LJQRYmvBoΡid>S%$e]ҰK^e$o\eqWVO8'k:Q7'1ooE)QcKwRФbTk b"}Q-hˌa[rӔedG4N9P!bi6WGGtpwl'vܡK; .ٓM䂼TFʹPDI*?x` ,M+*Mbu;$%KYᡟ,];JJY]J&DĿH}95 vs@+ٴYrNxfszDpgyi_L/7/%$9{Ʃ#PM7/qTrvp|DCWQ7Hn8eT[-}4V-'6{>:E}R)}Dk}gnZr^9QgKOdO/= _n]Hٳ|eS{zOAX,>/NYŸѺQGĩNTo]`vPȱjƪ-!{( ؗj1@ZQbwқ/@C^=A ր]#lE'\gd##GzA=/Wh|I9@UK:B% )ײj&Vƹ8™.lq #MU09P (gG阁ctZ n@GE7jH$igtpE# z[?^߭SH#7Zhy-@n]6-g\Szq blo\%0ҍtESe9'˪q%̥9.IJs!,U>}( //ϕ{`iJb:e:_AWeg˻̉b*?VQk?WFcUHV5\(_ߎU3ǪWGA?䭝osu8_B b^#r_5:>Gj*%UAϓX,ޭR՟RxmB|C.jGl>.:>H-Jt'JGV5~_ UvKV^ phV̝Ә>K#UBw!HG:MAwC3iR2 e o˷*^~[ xlsmI6vpj fM Nw˝N[Q=d(k((< .%_cݕi@Ēj>Qe|Lp"MNϩk>0W#(& S=nݯF G8oP,}ԟ{SM*l[o}>]QAG[n;琚J1oPwkw<.u ٥p(i7=(QViIm)I h(*iIT96iqwHyk<[;"zrz zY;u=QB|cePyN! =8IR֊o:3dv[Lss+pԾoC!Q)0/o2wå[`o$KRϣW hyRDp'P;{1DQ(@Ek puqA|E@0+[[ ꊥEktѻ2g5,oh𿜔%\Gyja`B]fsZ ?l#ى{y5c.tXlx;b$Ps'7Y.@SxbVk]aA@ V=勈Qՙ JHKgbWA DϜ)j-˴ޜSH ZM@͹.4]&є@+mUVKvy>3wgv{}^bLO%nPX޻x[hoDo)i4>"u']kZaDQ\TN]0R]oB|v>Ǡ儧d G;X&J}6Y l=".;u_/i7 Ym6jjSBa#) XŠEu8of;W ӆ:]h &[jEOv,vl]/,,шLze)ˡeispcbӘkoSlJ<ԔpvC+~!p3VrT8*>ؐPCƛֻ؈ÍY6_ov$`G0lJxC΄oӌՆ ZVlZ=ވ#X^۰mbV$eŊ5`%[g8RUSpcTݑ#+&cymI'ivudwfI%K; vI> %HpEV F;lFo}?p^ġ&ov;:6WܦNqؙ`h)lWNDo粈<֫3I`rHfJDtI/SV:u6IU;ČmgmW yt)4HߠjwV 8ꀭv€d4OU<'3)3mZPV]qt>w0< 3xU-M)Or|Aз7Vkz@e"6D|J\8VV˭H3&ٖ)0: lamꂝڇntBڏYrA6,7{W Wa 5}8bA=@˶ȚQh.GݩLq@B6HlucY`,a!w%#d"$Y^(7nn/ C=LM9x>yNoyG#c?eڹ>Pgk!4CR,+ *&a{#h۞!k\|3[2i>ܰ?2} K; h' lIVX΅,oCljZq5KczҬ$ *(Dn>Iޖl'YMgwygΨyT_}( }vTehܬ6)x.5eĪ/xo$>uꅰ}<4r+H[ LW:6l(,(l~|aaaâo^Bk` E"0,0tAXA\=F3m;]zb㜃| b3bVyy_Y/v|^ 73|nC} aE Y'o1@iˮ- 2UKͧ1zD5k_4{%fd`΁NxQۧseG(YP<׀ɶbYm=sc* "=|($}t.]9|߶ 9;'N#(sG |r訪>r``'{pXKt\CQGpWj('-O~ȁ 1iqd'XN׉V udt偮 ky:c?/ ;-2Bi{?'[l@mgXppv]6,uLڎ\%+̉pr~dE򂙉NYm,{ѐu (V駄 W&% )Eo/Dt+,:BR :>r;Kw|6ƍ(\>&ǎ^`'/j:+G y$Z r{h_L[yeCήku8Mf_BIBI'{El\~8:w/E @sE"9n !!G˂hBXiӚ)pLT6Ax /ݔ_L v>xрpw_.Ea ़w:3~ү璭ı#;'D*pWɺc5rB*0=U}8un v Zy)%<ΡAZ2XX$ge_:u6&N8azNM֩9̶Ϻ =XGU )GXulT u\ް w0ȋ}0"!%1X˕G'_\ߺ+w߿EE="!OG@l]1KGhh[IZ5XOYQX?_X-i o!94'v@/Æv!{̉;*<9S>Jߧ"J+W;̶a~&ԾuV_?bڷ7d?JRԝ :1_t0-ɍɅC7ц隔/e9C~i҆Gb}-!wV;u&Qih3}7NU&۪T̻DDF&EÚD{O B1Q!PcBEh@o0tucYdMǠ VfrFj-/-ᄐ7^TjĴ;ٮtrvwFi;5b[A&tPr&g>Na* n P]6 oDr}F"Uƀ.5':>7N>1o(%1Щ/ MG')Mۣܳ=B#+CvhLQ'-D܉R+ȕɼ4 :CϦ"3! n[ '&'0KXX'\!Jɒsur٥_s5Dϕ0gVU1#.|`ܛ\2ù[g=L[%Kz=P ݕ Dyo;׸-vWۇ\϶O^,GC]] ZtUQGqPʔE{&E/>f -'V^N_]'o 0[ -D\ahqϽA(Bk~;:DzẀzkq%̟fܲ3ꀁvϰT+׎tgLFތ l6>Ldl@~ n7 }b4cqS} S f3[љ}E;}bE-IѓگzKO4 [)cmg}9ԉ췽[X/ Ju,_}yJױ^\Xe}#ԥρ au )[H ꙙF2(*NmB,4/9o3L=aޑE>k7VHmc6󃛤H8otvasJQA `ו `̛[aA(X=WW.р+C#iqp@pY/m.ޯV?PzXYl_\4TPK ?kWk9 tiled.jpgPK l`colors.tdesktop-themePKz"tdesktop-1.2.17/Telegram/Resources/numbers.txt000066400000000000000000000152701326245125100213730ustar00rootroot000000000000001876;JM;Jamaica;1876 XXX XXXX;11; 1869;KN;Saint Kitts & Nevis;1869 XXX XXXX;11; 1868;TT;Trinidad & Tobago;1868 XXX XXXX;11; 1784;VC;Saint Vincent & the Grenadines;1784 XXX XXXX;11; 1767;DM;Dominica;1767 XXX XXXX;11; 1758;LC;Saint Lucia;1758 XXX XXXX;11; 1721;SX;Sint Maarten;1721 XXX XXXX;11; 1684;AS;American Samoa;1684 XXX XXXX;11; 1671;GU;Guam;1671 XXX XXXX;11; 1670;MP;Northern Mariana Islands;1670 XXX XXXX;11; 1664;MS;Montserrat;1664 XXX XXXX;11; 1649;TC;Turks & Caicos Islands;1649 XXX XXXX;11; 1473;GD;Grenada;1473 XXX XXXX;11; 1441;BM;Bermuda;1441 XXX XXXX;11; 1345;KY;Cayman Islands;1345 XXX XXXX;11; 1340;VI;US Virgin Islands;1340 XXX XXXX;11; 1284;VG;British Virgin Islands;1284 XXX XXXX;11; 1268;AG;Antigua & Barbuda;1268 XXX XXXX;11; 1264;AI;Anguilla;1264 XXX XXXX;11; 1246;BB;Barbados;1246 XXX XXXX;11; 1242;BS;Bahamas;1242 XXX XXXX;11; 998;UZ;Uzbekistan;998 XX XXXXXXX;12; 996;KG;Kyrgyzstan 995;GE;Georgia 994;AZ;Azerbaijan;994 XX XXX XX XX;12; 993;TM;Turkmenistan;993 XX XXXXXX;11; 992;TJ;Tajikistan 977;NP;Nepal 976;MN;Mongolia 975;BT;Bhutan 974;QA;Qatar 973;BH;Bahrain;973 XXXX XXXX;11; 972;IL;Israel;972 XX XXX XXXX;12; 971;AE;United Arab Emirates;971 XX XXX XXXX;12; 970;PS;Palestine;970 XXX XX XXXX;12; 968;OM;Oman;968 XXXX XXXX;11; 967;YE;Yemen;967 XXX XXX XXX;12; 966;SA;Saudi Arabia; 965;KW;Kuwait;965 XXXX XXXX;11; 964;IQ;Iraq;964 XXX XXX XXXX;13; 963;SY;Syria 962;JO;Jordan;962 X XXXX XXXX;12; 961;LB;Lebanon 960;MV;Maldives 886;TW;Taiwan 880;BD;Bangladesh 856;LA;Laos 855;KH;Cambodia 853;MO;Macau 852;HK;Hong Kong 850;KP;North Korea 692;MH;Marshall Islands 691;FM;Micronesia 690;TK;Tokelau 689;PF;French Polynesia 688;TV;Tuvalu 687;NC;New Caledonia 686;KI;Kiribati 685;WS;Samoa 683;NU;Niue 682;CK;Cook Islands 681;WF;Wallis & Futuna 680;PW;Palau 679;FJ;Fiji 678;VU;Vanuatu 677;SB;Solomon Islands 676;TO;Tonga 675;PG;Papua New Guinea 674;NR;Nauru 673;BN;Brunei Darussalam;673 XXX XXXX;10; 672;NF;Norfolk Island 670;TL;Timor-Leste 599;BQ;Bonaire, Sint Eustatius & Saba 599;CW;Curaçao 598;UY;Uruguay;598 XXXX XXXX;11; 597;SR;Suriname;597 XXX XXXX;10; 596;MQ;Martinique 595;PY;Paraguay;595 XXX XXX XXX;12; 594;GF;French Guiana 593;EC;Ecuador 592;GY;Guyana 591;BO;Bolivia;591 X XXX XXXX;11; 590;GP;Guadeloupe 509;HT;Haiti 508;PM;Saint Pierre & Miquelon 507;PA;Panama;507 XXXX XXXX;11; 506;CR;Costa Rica 505;NI;Nicaragua;505 XXXX XXXX;11; 504;HN;Honduras;504 XXXX XXXX;11; 503;SV;El Salvador;503 XXXX XXXX;11; 502;GT;Guatemala;502 X XXX XXXX;11; 501;BZ;Belize 500;FK;Falkland Islands 423;LI;Liechtenstein 421;SK;Slovakia 420;CZ;Czech Republic 389;MK;Macedonia 387;BA;Bosnia & Herzegovina 386;SI;Slovenia 385;HR;Croatia 382;ME;Montenegro 381;RS;Serbia;381 XX XXX XXXX;12; 380;UA;Ukraine;380 XX XXX XX XX;12; 378;SM;San Marino;378 XXX XXX XXXX;13; 377;MC;Monaco;377 XXXX XXXX;11; 376;AD;Andorra;376 XX XX XX;9; 375;BY;Belarus;375 XX XXX XXXX;12; 374;AM;Armenia;374 XX XXX XXX;11; 373;MD;Moldova;373 XX XXX XXX;11; 372;EE;Estonia; 371;LV;Latvia;371 XXX XXXXX;11; 370;LT;Lithuania;370 XXX XXXXX;11; 359;BG;Bulgaria; 358;FI;Finland; 357;CY;Cyprus;357 XXXX XXXX;11; 356;MT;Malta;356 XX XX XX XX;11; 355;AL;Albania;355 XX XXX XXXX;12; 354;IS;Iceland;354 XXX XXXX;10; 353;IE;Ireland;353 XX XXX XXXX;12; 352;LU;Luxembourg 351;PT;Portugal;351 X XXXX XXXX;12; 350;GI;Gibraltar;350 XXXX XXXX;11; 299;GL;Greenland;299 XXX XXX;9; 298;FO;Faroe Islands;298 XXX XXX;9; 297;AW;Aruba;297 XXX XXXX;10; 291;ER;Eritrea;291 X XXX XXX;10; 290;SH;Saint Helena;290 XX XXX;8; 269;KM;Comoros;269 XXX XXXX;10; 268;SZ;Swaziland;268 XXXX XXXX;11; 267;BW;Botswana;267 XX XXX XXX;11; 266;LS;Lesotho;266 XX XXX XXX;11; 265;MW;Malawi 264;NA;Namibia;264 XX XXX XXXX;12; 263;ZW;Zimbabwe;263 XX XXX XXXX;12; 262;RE;Réunion;262 XXX XXX XXX;12; 261;MG;Madagascar;261 XX XX XXX XX;12; 260;ZM;Zambia;260 XX XXX XXXX;12; 258;MZ;Mozambique;258 XX XXX XXXX;12; 257;BI;Burundi;257 XX XX XXXX;11; 256;UG;Uganda;256 XX XXX XXXX;12; 255;TZ;Tanzania;255 XX XXX XXXX;12; 254;KE;Kenya;254 XXX XXX XXX;12; 253;DJ;Djibouti;253 XX XX XX XX;11; 252;SO;Somalia;252 XX XXX XXX;11; 251;ET;Ethiopia;251 XX XXX XXXX;12; 250;RW;Rwanda;250 XXX XXX XXX;12; 249;SD;Sudan;249 XX XXX XXXX;12; 248;SC;Seychelles;248 X XX XX XX;10; 247;SH;Saint Helena;247 XXXX;7; 246;IO;Diego Garcia;246 XXX XXXX;10; 245;GW;Guinea-Bissau;245 XXX XXXX;10; 244;AO;Angola;244 XXX XXX XXX;12; 243;CD;Congo (Dem. Rep.);243 XX XXX XXXX;12; 242;CG;Congo (Rep.);242 XX XXX XXXX;12; 241;GA;Gabon;241 X XX XX XX;10; 240;GQ;Equatorial Guinea;240 XXX XXX XXX;12; 239;ST;São Tomé & Príncipe;239 XX XXXXX;10; 238;CV;Cape Verde;238 XXX XXXX;10; 237;CM;Cameroon;237 XXXX XXXX;11; 236;CF;Central African Rep.;236 XX XX XX XX;11; 235;TD;Chad;235 XX XX XX XX;11; 234;NG;Nigeria 233;GH;Ghana 232;SL;Sierra Leone;232 XX XXX XXX;11; 231;LR;Liberia; 230;MU;Mauritius; 229;BJ;Benin;229 XX XXX XXX;11; 228;TG;Togo;228 XX XXX XXX;11; 227;NE;Niger;227 XX XX XX XX;11; 226;BF;Burkina Faso;226 XX XX XX XX;11; 225;CI;Côte d`Ivoire;225 XX XXX XXX;11; 224;GN;Guinea;224 XXX XXX XXX;12; 223;ML;Mali;223 XXXX XXXX;11; 222;MR;Mauritania;222 XXXX XXXX;11; 221;SN;Senegal;221 XX XXX XXXX;12; 220;GM;Gambia;220 XXX XXXX;10; 218;LY;Libya;218 XX XXX XXXX;12; 216;TN;Tunisia;216 XX XXX XXX;11; 213;DZ;Algeria;213 XXX XX XX XX;12; 212;MA;Morocco;212 XX XXX XXXX;12; 211;SS;South Sudan;211 XX XXX XXXX;12; 98;IR;Iran;98 XXX XXX XXXX;12; 95;MM;Myanmar; 94;LK;Sri Lanka;94 XX XXX XXXX;11; 93;AF;Afghanistan;93 XXX XXX XXX;11; 92;PK;Pakistan;92 XXX XXX XXXX;12; 91;IN;India;91 XXXXX XXXXX;12; 90;TR;Turkey;90 XXX XXX XXXX;12 86;CN;China;86 XXX XXXX XXXX;13; 84;VN;Vietnam; 82;KR;South Korea; 81;JP;Japan;81 XX XXXX XXXX;12; 66;TH;Thailand;66 X XXXX XXXX;11; 65;SG;Singapore;65 XXXX XXXX;10; 64;NZ;New Zealand; 63;PH;Philippines;63 XXX XXX XXXX;12; 62;ID;Indonesia; 61;AU;Australia;61 XXX XXX XXX;11; 60;MY;Malaysia; 58;VE;Venezuela;58 XXX XXX XXXX;12; 57;CO;Colombia;57 XXX XXX XXXX;12; 56;CL;Chile;56 X XXXX XXXX;11; 55;BR;Brazil;55 XX XXXXX XXXX;13; 54;AR;Argentina; 53;CU;Cuba;53 XXXX XXXX;10; 52;MX;Mexico; 51;PE;Peru;51 XXX XXX XXX;11; 49;DE;Germany;49 XXX XXXXXXXX;13; 48;PL;Poland;48 XX XXX XXXX;11; 47;NO;Norway;47 XXXX XXXX;10; 46;SE;Sweden;46 XX XXX XXXX;11; 45;DK;Denmark;45 XXXX XXXX;10; 44;GB;United Kingdom;44 XXXX XXXXXX;12; 43;AT;Austria; 41;CH;Switzerland;41 XX XXX XXXX;11; 40;RO;Romania;40 XXX XXX XXX;11; 39;IT;Italy;39 XXX XXX XXXX;12; 36;HU;Hungary;36 XX XXX XXXX;11; 34;ES;Spain;34 XXX XXX XXX;11; 33;FR;France;33 X XX XX XX XX;11; 32;BE;Belgium;32 XXX XX XX XX;11; 31;NL;Netherlands;31 X XX XX XX XX;11; 30;GR;Greece;30 XX XXXX XXXX;12; 27;ZA;South Africa;27 XX XXX XXXX;11; 20;EG;Egypt;20 XX XXX XXXX;11; 7;RU;Russian Federation;7 XXX XXX XX XX;11; 7;KZ;Kazakhstan;7 XXX XXX XX XX;11 1;US;USA;1 XXX XXX XXXX;11; 1;PR;Puerto Rico;1 XXX XXX XXXX;11; 1;DO;Dominican Rep.;1 XXX XXX XXXX;11; 1;CA;Canada;1 XXX XXX XXXX;11;tdesktop-1.2.17/Telegram/Resources/qrc/000077500000000000000000000000001326245125100177375ustar00rootroot00000000000000tdesktop-1.2.17/Telegram/Resources/qrc/qmime/000077500000000000000000000000001326245125100210475ustar00rootroot00000000000000tdesktop-1.2.17/Telegram/Resources/qrc/qmime/freedesktop.org.xml000066400000000000000000071215341326245125100247070ustar00rootroot00000000000000 ]> ATK inset شكل ATK Ustaŭka ATK Сбор — ATK inset ATK Vložka ATK ATK-indsættelse ATK-Inset ATK inset inserción ATK ATK sartzapena ATK-osio ATK innskot encart ATK intlis ATK conxunto ATK תוספת ATK ATK betét Inset ATK Inset ATK ATK インセット ATK беті ATK inset ATK inset ATK ielaidums ATK-innsats ATK-invoegsel ATK-innskot Wstawka ATK Conjunto de entrada do ATK Inset ATK вкладка ATK Vložka ATK Vložka ATK Inset ATK ATK-inlägg вкладка ATK Bộ dát ATK ATK 嵌入对象 ATK Andrew Toolkit electronic book document مستند كتاب إلكتروني elektronnaja kniha Документ — електронна книга document de llibre electrònic Dokument elektronické knihy elektronisk bogdokument Elektronisches Buch έγγραφο ηλεκτρονικού βιβλίου electronic book document documento de libro electrónico liburu elektronikoaren dokumentua elektroninen kirja elektroniskbóka skjal document livre électronique leabhar leictreonach documento de libro electrónico מסמך מסוג ספר אלקטרוני dokument elektroničke knjige elektronikus könyvdokumentum dokumen buku elektronik Documento libro elettronico 電子ブックドキュメント электронды кітабы 전자책 문서 elektroninės knygos dokumentas elektroniskās grāmatas dokuments elektronisch boek elektronisk bok-dokument Dokument książki elektronicznej Documento de livro eletrônico document carte electronică электронная книга Dokument elektronickej knihy dokument elektronske knjige Dokument libri elektronik elektroniskt bokdokument elektronik kitap belgesi документ електронної книги tài liệu cuốn sách điện tử 电子书文档 電子書文件 Adobe Illustrator document مستند أدوبي المصور Dakument Adobe Illustrator Документ — Adobe Illustrator document d'Adobe Illustrator Dokument Adobe Illustrator Adobe Illustrator-dokument Adobe-Illustrator-Dokument έγγραφο Adobe Illustrator Adobe Illustrator document dokumento de Adobe Illustrator documento de Adobe Illustrator Adobe Illustrator dokumentua Adobe Illustrator -asiakirja Adobe Illustrator skjal document Adobe Illustrator cáipéis Adobe Illustrator documento de Adobe Ilustrator מסמך Adobe Ill Adobe Illustrator dokument Adobe Illustrator-dokumentum Dokumen Adobe Illustrator Documento Adobe Illustrator Adobe Illustrator ドキュメント Adobe Illustrator-ის დოკუმენტი Adobe Illustrator құжаты 어도비 일러스트레이터 문서 Adobe Illustrator dokumentas Adobe Illustrator dokuments Dokumen Adobe Illustrator Adobe Illustrator-dokument Adobe Illustrator-document Adobe Illustrator-dokument Dokument Adobe Illustrator documento Adobe Illustrator Documento do Adobe Illustrator Document Adobe Illustrator документ Adobe Illustrator Dokument Adobe Illustrator Dokument Adobe Illustrator Dokument Adobe Illustrator Адоуби Илустратор документ Adobe Illustrator-dokument документ Adobe Illustrator Tài liệu Adobe Illustrator Adobe Illustrator 文档 Adobe Illustrator 文件 Macintosh BinHex-encoded file ملف Macintosh BinHex مشفر Macintosh BinHex-kodlanmış fayl Fajł Macintosh, BinHex-zakadavany Файл — кодиран във формат BinHex за Macintosh fitxer codificat BinHex de Macintosh Soubor kódovaný pomocí Macintosh BinHex Ffeil BinHex-amgodwyd Macintosh Macintosh BinHex-kodet fil Macintosh-Datei (BinHex-kodiert) αρχείο Macintosh κωδικοποίησης BinHex Macintosh BinHex-encoded file dosiero kodigita laŭ Macintosh BinHex archivo Macintosh codificado con BinHex Macintosh BinHex-ekin kodetutako fitxategia Macintosh BinHex -koodattu tiedosto Macintosh BinHex-bronglað fíla fichier codé Macintosh BinHex comhad ionchódaithe le Macintosh BinHex ficheiro de Macintosh codificado con BinHex קובץ מסוג Macintosh BinHex-encoded Macintosh BinHex-kodirana datoteka Macintosh BinHex kódolású fájl Berkas tersandi Macintosh BinHex File Macintosh codificato BinHex Macintosh BinHex エンコードファイル Macintosh BinHex кодталған файлы 매킨토시 BinHex 인코딩된 압축 파일 Macintosh BinHex-encoded failas Macintosh BinHex-kodēts datne Fail terenkod-BinHex Macintosh Macintosh BinHe-kodet arkiv Macintosh BinHex-gecodeerd bestand Macintosh BinHex-koda fil Zakodowany w BinHex plik Macintosh ficheiro codificado em BinHex de Macintosh Arquivo do Macintosh codificado com BinHex Fișier codat Macintosh BinHex файл (закодированный Macintosh BinHex) Súbor kódovaný pomocou Macintosh BinHex Kodirana datoteka Macintosh (BinHex) File Macintosh i kodifikuar BinHex Мекинтош BinHex-encoded датотека Macintosh BinHex-kodad fil файл закодований Macintosh BinHex Tập tin đã mã hoá BinHex của Macintosh Macintosh BinHex 编码的文件 Macintosh BinHex 編碼檔 Mathematica Notebook مذكرة رياضيات Natatnik Mathematica Тетрадка — Mathematica llibreta de notes de Mathematica Sešit Mathematica Mathematica Notebook Mathematica-Dokument σημειωματάριο Mathematica Mathematica Notebook notebook de Mathematica Mathematica Notebook Mathematica-muistilehtiö Mathematica skriviblokkur carnet de notes Mathematica leabhar nótaí Mathematica notebook de Mathematica מחברת מתמטיקה Mathematica notesz Mathematica Notebook Notebook Mathematica Mathematica ノートブック Mathematica Notebook Mathematica 노트북 Mathematica užrašinė Mathematica bloknots Mathematica notisblokk Mathematica-notitieboek Mathematica-notatbok Notatnik Mathematica Caderno do Mathematica Carnețel Mathematica Mathematica Notebook Zošit Mathematica Datoteka dokumenta Mathematica Notebook matematike Mathematica Notebook-dokument математичний записник Cuốn vở Mathematica Mathematica 记事 Mathematica Notebook MathML document مستند MathML MathML sənədi Dakument MathML Документ — MathML document de MathML Dokument MathML Dogfen MathML MathML-dokument MathML-Dokument έγγραφο MathML MathML document MathML-dokumento documento MathML MathML dokumentua MathML-asiakirja MathML skjal document MathML cáipéis MathML documento de MathML מסמך MathML MathML dokument MathML-dokumentum Dokumen MathML Documento MathML MathML ドキュメント MathML-ის დოკუმენტი MathML құжаты MathML 문서 MathML dokumentas MathML dokuments Dokumen MathML MathML-dokument MathML-document MathML-dokument Dokument MathML documento MathML Documento do MathML Document MathML документ MathML Dokument MathML Dokument MathML Dokument MathML MathML документ MathML-dokument MathML belgesi документ MathML Tài liệu MathML MathML 文档 MathML 文件 MathML Mathematical Markup Language mailbox file ملف صندوق البريد fajł paštovaj skryni Файл — Mailbox fitxer mailbox Soubor mailbox postkassefil Mailbox-Datei αρχείο mailbox mailbox file archivo de buzón de correo mailbox fitxategia mailbox-tiedosto postkassafíla fichier boîte aux lettres comhad bhosca poist ficheiro de caixa de correo קובץ תיבת-דואר datoteka poštanskog sandučića mailbox fájl berkas kotak surat File mailbox メールボックスファイル пошта жәшігінің файлы 메일함 파일 pašto dėžutės failas pastkastītes datne postboksfil mailbox-bestand mailbox-fil Plik poczty (Mailbox) Arquivo de caixa de correio fișier căsuță poștală файл почтового ящика Súbor mailbox datoteka poštnega predala File mailbox brevlådefil файл поштової скриньки tập tin hộp thư mailbox 文件 郵箱檔 Metalink file ملف ميتالنك Изтегляне — Metalink fitxer Metalink Soubor metalink Metahenvisningsfil Metalink-Datei αρχείο Metalink Metalink file Metalink-dosiero archivo de metaenlace Metaestekaren fitxategia Metalink-tiedosto Metalink fíla fichier metalink comhad Metalink ficheiro Metalink קובץ Metalink Metalink fájl Berkas Metalink File Metalink Metalink ファイル Metalink файлы Metalink 파일 Metalink failas Metalink datne Metalink bestand Plik Metalink Arquivo Metalink Fișier Metalink файл Metalink Súbor Metalink Datoteka povezave Metalink Metalink-fil Metalink dosyası файл метапосилання 元链接文件 Metalink 檔案 Metalink file ملف ميتالنك Изтегляне — Metalink fitxer Metalink Soubor metalink Metahenvisningsfil Metalink-Datei αρχείο Metalink Metalink file Metalink-dosiero archivo de metaenlace Metaestekaren fitxategia Metalink-tiedosto Metalink fíla fichier metalink comhad Metalink ficheiro Metalink קובץ Metalink Metalink fájl Berkas Metalink File Metalink Metalink ファイル Metalink файлы Metalink 파일 Metalink failas Metalink datne Metalink bestand Plik Metalink Arquivo Metalink Fișier Metalink файл Metalink Súbor Metalink Datoteka povezave Metalink Metalink-fil Metalink dosyası файл метапосилання 元链接文件 Metalink 檔案 unknown مجهول nieviadomy Неизвестен тип desconegut Neznámý ukendt Unbekannt άγνωστο unknown nekonate desconocido ezezaguna tuntematon ókent inconnu anaithnid descoñecido לא ידוע nepoznato ismeretlen tak diketahui Sconosciuto 不明 უცნობი белгісіз 알 수 없음 nežinoma nezināms Entah ukjent onbekend ukjend Nieznany typ desconhecido Desconhecido necunoscut неизвестно Neznámy neznano Nuk njihet непознато okänd невідомо không rõ 未知 不明 ODA document مستند ODA ODA sənədi Dakument ODA Документ — ODA document ODA Dokument ODA Dogfen ODA ODA-dokument ODA-Dokument έγγραφο ODA ODA document ODA-dokumento documento ODA ODA dokumentua ODA-asiakirja ODA skjal document ODA cáipéis ODA documento ODA מסמך ODA ODA dokument ODA-dokumentum Dokumen ODA Documento ODA ODA ドキュメント ODA დოკუმენტი ODA құжаты ODA 문서 ODA dokumentas ODA dokuments Dokumen ODA ODA-dokument ODA-document ODA-dokument Dokument ODA documento ODA Documento ODA Document ODA документ ODA Dokument ODA Dokument ODA Dokument ODA ODA документ ODA-dokument ODA belgesi документ ODA Tài liệu ODA ODA 文档 ODA 文件 ODA Office Document Architecture WWF document Документ — WWF document WWF Dokument WWF WWF-dokument WWF-Dokument έγγραφο WWF WWF document WWF-dokumento Documento de WWF WWF-asiakirja document WWF documento de WWF מסמך WWF WWF dokument WWF dokumentum Dokumen WWF Documento WWF WWF 文書 WWF დოკუმენტი WWF құжаты WWF 문서 WWF dokuments WWF document Dokument WWF Documento WWF документ WWF Dokument WWF WWF-dokument документ WWF WWF WWF 文件 PDF document مستند PDF Dakument PDF Документ — PDF document PDF Dokument PDF Dogfen PDF PDF-dokument PDF-Dokument έγγραφο PDF PDF document PDF-dokumento documento PDF PDF dokumentua PDF-asiakirja PDF skjal document PDF cáipéis PDF documento PDF מסמך PDF PDF-dokumentum Dokumen PDF Documento PDF PDF ドキュメント PDF құжаты PDF 문서 PDF dokumentas PDF dokuments Dokumen PDF PDF-dokument PDF-document PDF-dokument Dokument PDF documento PDF Documento PDF Document PDF документ PDF Dokument PDF Dokument PDF Dokument PDF PDF документ PDF-dokument PDF belgesi документ PDF Tài liệu PDF PDF 文档 PDF 文件 PDF Portable Document Format XSPF playlist قائمة تشغيل XSPF Śpis piesień XSPF Списък за изпълнение — XSPF llista de reproducció XSPF Seznam skladeb XSPF XSPF-afspilningsliste XSPF-Wiedergabeliste λίστα αναπαραγωγής XSPF XSPF playlist XSPF-ludlisto lista de reproducción XSPF XSPF erreprodukzio-zerrenda XSPF-soittolista XSPF avspælingarlisti liste de lecture XSPF seinmliosta XSPF lista de reprodución XSPF רשימת השמעה XSPF XSPF popis za reprodukciju XSPF-lejátszólista Senarai pular XSPF Scaletta XSPF XSPF 再生リスト XSPF ойнау тізімі XSPF 재생 목록 XSPF grojaraštis XSPF repertuārs XSPF-spilleliste XSPF-afspeellijst XSPF-speleliste Lista odtwarzania XSPF Lista de reprodução XSPF Listă XSPF список воспроизведения XSPF Zoznam skladieb XSPF Seznam predvajanja XSPF Listë titujsh XSPF XSPF-spellista список програвання XSPF Danh mục nhạc XSPF XSPF 播放列表 XSPF 播放清單 XSPF XML Shareable Playlist Format Microsoft Windows theme pack حزمة سمات Microsoft Works Пакет с тема — Microsoft Windows paquet de temes Windows de Microsoft Balík motivů Microsoft Windows Microsoft Windows-temapakke Themenpaket für Microsoft Windows θεματικό πάρκο Microsoft Windows Microsoft Windows theme pack paquete de tema para Microsoft Windows Microsoft Windows-en gaiaren paketea Microsoft Windows -teemapaketti Microsoft Windows tema pakki paquet de thèmes Microsoft Windows paca téamaí Microsoft Windows paquete de tema de Microsoft Windows חבילת ערכות נושא של Microsoft Windows Microsoft Windows paket tema Microsoft Windows témacsomag Pak tema Microsoft Windows Pacchetto temi Microsoft Windows Microsoft Windows テーマパック Microsoft Windows-ის თემის შეკვრა Microsoft Windows тема дестесі 마이크로소프트 Windows 테마 패키지 Microsoft Windows temų paketas Microsoft Windows motīvu paka Microsoft Windows thema pack Pakiet motywu Microsoft Windows Pacote de temas do Microsoft Windows Pachet de teme Microsoft Windows пакет темы Microsoft Windows Balík tém Microsoft Windows Datoteka teme Microsoft Windows Microsoft Windows-temapaket пакунок з темою Microsoft Windows Microsoft Windows 主题包 微軟視窗佈景主題包 AmazonMP3 download file GSM 06.10 audio GSM 06.10 سمعي Аудио — GSM 06.10 àudio GSM 06.10 Zvuk GSM 06.10 GSM 06.10-lyd GSM-06.10-Audio ήχος GSM 06.10 GSM 06.10 audio sonido GSM 06.10 GSM 06.10 audioa GSM 06.10 -ääni GSM 06.10 ljóður audio GSM 06.10 fuaim GSM 06.10 son de GSM 06.10 שמע GSM 06.10 GSM 06.10 audio GSM 06.10 hang Audio GSM 06.10 Audio GSM 06.10 GSM 06.10 オーディオ GSM 06.10 აუდიო GSM 06.10 аудиосы GSM 06.10 오디오 GSM 06.10 garso įrašas GSM 06.10 audio GSM 06.10 audio Plik dźwiękowy GSM 06.10 Áudio GSM 06.10 GSM 06.10 audio аудио GSM 06.10 Zvuk GSM 06.10 Zvočna datoteka GSM 06.10 GSM 06.10-ljud звук GSM 06.10 Âm thanh GSM 06.10 GSM 06.10 音频 GSM 06.10 音訊 GSM Global System for Mobile communications iRiver Playlist قائمة تشغيل iRiver Śpis piesień iRiver Списък за изпълнение — iRiver llista de reproducció iRiver Seznam skladeb iRiver iRiver-afspilningsliste iRiver-Wiedergabeliste λίστα αναπαραγωγής iRiver iRiver Playlist iRiver-ludlisto lista de reproducción de iRiver iRiver erreprodukzio-zerrenda iRiver-soittolista iRiver avspælingarlisti liste de lecture iRiver seinmliosta iRiver lista de reprodución de iRiver רשימת השמעה של iRiver iRiver popis za reprodukciju iRiver lejátszólista iRiver Playlist Scaletta iRiver iRiver 再生リスト iRiver ойнау тізімі iRiver 재생 목록 iRiver grojaraštis iRiver repertuārs iRiver-spilleliste iRiver-afspeellijst iRiver speleliste Lista odtwarzania iRiver Lista de reprodução do iRiver Listă iRiver список воспроизведения iRiver Zoznam skladieb iRiver Seznam predvajanja iRiver Listë titujsh iRiver iRiver-spellista список програвання iRiver danh mục nhạc iRiver iRiver 播放列表 iRiver 播放清單 PGP/MIME-encrypted message header ترويسة رسالة PGP/MIME-مشفرة Zahałovak paviedamleńnia, zašyfravany ŭ PGP/MIME Заглавна част на шифрирано съобщение — PGP/MIME capçalera de missatge xifrat amb PGP/MIME Záhlaví zprávy zašifrované pomocí PGP/MIME PGP-/MIME-krypteret meddelelseshoved PGP/MIME-verschlüsselter Nachrichtenkopf κεφαλίδα μηνύματος κρυπτογραφημένου κατά PGP/MIME PGP/MIME-encrypted message header PGP/MIME-ĉifrita ĉapo de mesaĝo cabecera de mensaje cifrado PGP/MIME PGP/MIME enkriptatutako mezu-goiburua PGP/MIME-salattu viestiotsikko PGP/MIME-encrypted boð tekshøvd en-tête de message codé PGP/MIME ceanntásc teachtaireachta ionchódaithe le PGP/MIME cabeceira de mensaxe cifrado PGP/MIME כותר של קובץ מוצפן מסוג PGP/MIME PGP/MIME titkosított üzenetfejléc Tajuk pesan terenkripsi PGP/MIME Intestazione messaggio PGP/MIME-encrypted PGP/MIME 暗号化メッセージヘッダー PGP/MIME-шифрленген мәлімдеме тақырыптамасы PGP/MIME으로 암호화된 메시지 헤더 PGP/MIME užšifruota žinutės antraštė PGP/MIME-šifrēta ziņas galvene Pengepala mesej terenkripsi PGP/MIME PGP/MIME-kryptert meldingshode PGP/MIME-versleutelde berichtkopregels PGP/MIME-kryptert meldingshovud Nagłówek listu zaszyfrowanego PGP/MIME cabeçalho de mensagem cifrada com PGP/MIME Cabeçalho de mensagem criptografada PGP/MIME Antet de mesaj encriptat PGP/MIME заголовок сообщения, зашифрованный PGP/MIME Hlavičke správy zašifrovaná pomocou PGP/MIME Datoteka glave šifriranega sporočila PGP/MIME Header mesazhi të kriptuar PGP/MIME ПГП/МИМЕ шифровано заглавље поруке PGP/MIME-krypterat meddelandehuvud заголовок шифрованого PGP/MIME повідомлення Phần đầu thông điệp đã mật mã bằng PGP/MIME PGP/MIME 加密的信件头 PGP/MIME 加密訊息標頭 PGP keys مفاتيح PGP PGP açarları Klučy PGP Ключове — PGP claus PGP Klíče PGP Allweddi PGP PGP-nøgler PGP-Schlüssel κλειδιά PGP PGP keys PGP-ŝlosiloj claves PGP PGP giltzak PGP-avainrengas PGP lyklar clés PGP eochracha PGP Chaves PGP מפתח PGP PGP-kulcs Kunci PGP Chiavi PGP PGP 鍵 PGP кілттері PGP 키 PGP raktai PGP atslēgas Kekunci PGP PGP-nøkler PGP-sleutels PGP-nøkler Klucze PGP chaves PGP Chaves PGP Chei PGP ключи PGP Kľúče PGP Datoteka ključa PGP Kyçe PGP PGP кључ PGP-nycklar PGP anahtarları ключі PGP Khoá PGP PGP 密钥 PGP 鑰匙 PGP Pretty Good Privacy detached OpenPGP signature إمضاء OpenPGP مفصول adłučany podpis OpenPGP Отделен подпис — OpenPGP signatura OpenPGP separada Oddělený podpis OpenPGP frigjort OpenPGP-signatur Isolierte OpenPGP-Signatur αποκομμένη υπογραφή OpenPGP detached OpenPGP signature dekroĉa OpenPGP-subskribo firma OpenPGP separada desuzturtako OpenPGP sinadura erillinen OpenPGP-allekirjoitus skild OpenPGP undirskrift signature OpenPGP détachée síniú OpenPGP scartha sinatura de OpenPGP independente חתימת OpenPGP מנותקת odvojen OpenPGP potpis leválasztott OpenPGP-aláírás tanda tangan OpenPGP yang terlepas Firma staccata OpenPGP 分離 OpenPGP 署名 бөлінген OpenPGP қолтаңбасы 분리된 OpenPGP 서명 neprisegtas OpenPGP parašas atvienots OpenPGP paraksts Tandatangan OpenPGP terlerai frakoblet OpenPGP-signatur losse OpenPGP-ondertekening fråkopla OpenPGP-signatur Oddzielony podpis OpenPGP assinatura OpenPGP solta Assinatura OpenPGP destacada semnătură OpenPGP detașată отсоединённая подпись OpenPGP Oddelený podpis OpenPGP odpet podpis OpenPGP Firmë e shkëputur OpenPGP одвојени ОпенПГП потпис frikopplad OpenPGP-signatur відокремлений OpenPGP підпис chữ ký OpenPGP tách rời 分离的 OpenPGP 签名 分離的 OpenPGP 簽章 PKCS#7 Message or Certificate PKCS Public-Key Cryptography Standards detached S/MIME signature إمضاء S/MIME مفصول adłučany podpis S/MIME Отделен подпис — S/MIME signatura S/MIME separada Oddělený podpis S/MIME frigjort S/MIME-signatur Isolierte S/MIME-Signatur αποκομμένη υπογραφή S/MIME detached S/MIME signature dekroĉa S/MIME-subskribo firma S/MIME separada desuzturtako S/MIME sinadura erillinen S/MIME-allekirjoitus skild S/MIME undirskrift signature S/MIME détachée síniú S/MIME scartha sinatura S/MIME independente חתימת S/MIME מנותקת odvojen S/MIME potpis leválasztott S/MIME-aláírás tanda tangan S/MIME yang terlepas Firma staccata S/MIME 分離 S/MIME 署名 бөлінген S/MIME қолтаңбасы 분리된 S/MIME 서명 neprisegtas S/MIME parašas atvienots S/MIME paraksts Tandatangan S/MIME terlerai frakoblet S/MIME-signatur losse S/MIME-ondertekening fråkopla S/MIME-signatur Oddzielony podpis S/MIME assinatura S/MIME solta Assinatura S/MIME destacada semnătură S/MIME detașată отсоединённая подпись S/MIME Oddelený podpis S/MIME odpet podpis S/MIME Firmë e shkëputur S/MIME одвојени S/MIME потпис frikopplad S/MIME-signatur відокремлений S/MIME підпис chữ ký S/MIME tách rời 分离的 S/MIME 签名 分離的 S/MIME 簽章 S/MIME Secure/Multipurpose Internet Mail Extensions PKCS#8 private key رزمة الشهادة PKCS#8 Ключ, частен — PKCS#8 clau privada PKCS#8 Soukromý klíč PKCS#8 PKCS#8-privat nøgle PKCS#8 Geheimer Schlüssel ιδιωτικό κλειδί PKCS#8 PKCS#8 private key clave privada PCKS#8 PKCS#8 yksityinen avain PKCS#8 privatur lykil clé privée PKCS#8 eochair phríobháideach PKCS#8 Chave privada PKCS#8 מפתח פרטי של PKCS#8 PKCS#8 személyes kulcs Kunci privat PKCS#8 Chiave privata PKCS#8 PKCS#8 秘密鍵 PKCS#8 меншік кілті PKCS#8 개인 키 PKCS#8 asmeninis raktas PKCS#8 privātā atslēga PKCS#8 private sleutel Klucz prywatny PKCS#8 Chave privada PKCS#8 Cheie privată PKCS#8 личный ключ PKCS#8 Súkromný kľúč PKCS#8 Datoteka osebnega ključa PKCS#8 Privat PKCS#8-nyckel закритий ключ PKCS#8 PKCS#8 私钥 PKCS#8 私人金鑰 PKCS Public-Key Cryptography Standards PKCS#10 certification request طلب شهادة PKCS#10 Zapyt sertyfikacyi PKCS#10 Заявка за сертификат — PKCS#10 sol·licitud de certificació PKCS#10 Požadavek na certifikát PKCS#10 PKCS#10-certifikatanmodning PKCS#10-Zertifikatanfrage αίτηση πιστοποίησης PKCS#10 PKCS#10 certification request petición de certificados PKCS#10 PKCS#10 ziurtagirien eskaera PKCS#10-varmennepyyntö PKCS#10 váttanarumbøn requête de certification PKCS#10 iarratas dheimhniúchán PKCS#10 Solicitude de certificado PKCS#10 בקשה מוסמכת PLCS#10 PKCS#10-tanúsítványkérés Permintaan sertifikasi PKCS#10 Richiesta certificazione PKCS#10 PKCS#10 証明書署名要求 PKCS#10 сертификацияға сұранымы PKCS#10 인증서 요청 PKCS#10 liudijimų užklausa PKCS#10 sertifikācijas pieprasījums PKCS#10-sertifikatforespørsel PKCS#10-certificatieverzoek PKCS#10-sertifiseringsførespurnad Żądanie certyfikatu PKCS#10 Pedido de certificação PKCS#12 Cerere de certificat PKCS#10 запрос сертификации PKCS#10 Požiadavka na certifikát PKCS#10 Datoteka potrdila PKCS#10 Kërkesë çertifikimi PKCS#10 PKCS#10-certifikatbegäran комплект сертифікатів PKCS#10 Yêu cầu chứng nhận PKCS#10 PKCS#10 认证请求 PKCS#10 憑證請求 PKCS Public-Key Cryptography Standards X.509 certificate شهادة X.509 Сертификат — X.509 certificat X.509 Certifikát X.509 X.509-certifikat X.509-Zertifikat πιστοποιητικό X.509 X.509 certificate certificado X.509 X.509 ziurtagiria X.509-varmenne X.509 prógv certificat X.509 teastas X.509 Certificado X.509 אישור X.509 X.509 certifikat X.509 tanúsítvány Sertifikat X.509 Certificato X.509 X.509 証明書 X.509 сертификаты X.509 인증서 X.509 liudijimas X.509 sertifikāts X.509 certificaat Certyfikat X.509 Certificado X.509 Certificat X.509 сертификат X.509 Certifikát X.509 Datoteka potrdila X.509 X.509-certifikat сертифікат X.509 X.509 证书 X.509 憑證 Certificate revocation list قائمة إبطال الشهادات Списък с отхвърлени сертификати llista de revocació de certificats Seznam odvolaných certifikátů Certifikattilbagekaldelsesliste Liste widerrufener Zertifikate κατάλογος ανάκλησης πιστοποιητικού Certificate revocation list lista de revocación de certificados Ziurtagiri-errebokatzeen zerrenda Varmenteiden sulkulista Prógv afturtøkulisti liste de révocation de certificat liosta teastas cúlghairmthe lista de certificados de revogación רשימת אישורים מבוטלים popis povučenih certifikata Tanúsítvány-visszavonási lista Daftar pencabutan sertificat (CRL) Elenco certificati di revoca 証明書失効リスト Сертификатты қайта шақыру тізімі 인증서 철회 목록 Panaikintų liudijimų sąrašas Sertifikātu atsaukšanu saraksts Certificaat revocation lijst Lista unieważnień certyfikatów Lista de revogação de certificado Listă de revocare a certificatelor Список аннулирования сертификатов Zoznam zrušených certifikátov Datoteka seznama preklica potrdil Spärrlista för certifikat список відкликання сертифікатів 证书吊销列表 憑證撤銷清單 PkiPath certification path مسار شهادة PkiPath Сертификационна верига — PkiPath ruta de certificació PkiPath Cesta k certifikátu PkiPath PkiPath-certifikationssti PkiPath-Zertifikatspfad διαδρομή πιστοποιητικού PkiPath PkiPath certification path ruta de certificación PkiPath PkiPath ziurtagirien bide-izena PkiPath-varmennepolku PkiPath váttanleið chemin de certification PkiPath conair dheimhniúcháin PkiPath Ruta de certificación PkiPath נתיב מאושר של PkiPath PkiPath tanúsítványútvonal Alamat sertifikasi PkiPath Percorso certificazione PkiPath PkiPath 証明書パス PkiPath сертификаттау жолы PkiPath 인증서 요청 PkiPath liudijimų maršrutas PkiPath sertifikāta ceļš PkiPath-certificatiepad Ścieżka certyfikacji PkiPath Pedido de certificação PkiPath Cale certificare PkiPath путь сертификации PkiPath Cesta k certifikátu PkiPath Datoteka poti potrdila PkiPath PkiPath-certifikatsekvens шлях сертифікації PkiPath Đường dẫn cấp chứng nhận PkiPath PkiPath 证书目录 PkiPath 憑證路徑 PS document مستند PS Dakument PS Документ — PS document PS Dokument PS PS-dokument PS-Dokument έγγραφο PS PS document PS-dokumento documento PS PS dokumentua PS-asiakirja PS skjal document PS cáipéis PS documento PS מסמך PS PS dokumentum Dokumen PS Documento PS PS ドキュメント PS құжаты PS 문서 PS dokumentas PS dokuments PS-dokument PS-document PS-dokument Dokument PS Documento PS Document PS документ PS Dokument PS Dokument PS Dokument PS PS-dokument PS belgesi документ PS Tài liệu PS PS 文档 Ps 文件 PS PostScript Plucker document مستند Plucker Dakument Plucker Документ — Plucker document Plucker Dokument Plucker Pluckerdokument Plucker-Dokument έγγραφο Plucker Plucker document Plucker-dokumento documento de Plucker Plucker dokumentua Plucker-asiakirja Plucker skjal document Plucker cáipéis Plucker documento de Plucker מסמך של Plucker Plucker dokumentum Dokumen Plucker Documento Plucker Plucker ドキュメント Plucker құжаты Plucker 문서 Plucker dokumentas Plucker dokuments Plucker-dokument Plucker-document Plucker-dokument Dokument Plucker Documento do Plucker Document Plucker документ Plucker Dokument Plucker Dokument Plucker Dokument Plucker Plucker-dokument Plucker belgesi документ Plucker Tài liệu Plucker Plucker 文档 Plucker 文件 RELAX NG XML schema مخطط RELAX NG XML Схема за XML — RELAX NG esquema XML RELAX NG Schéma RELAX NG XML RELAX NG XML-skema RELAX NG XML-Schema σχήμα RELAX NG XML RELAX NG XML schema Esquema XML RELAX NG RELAX NG XML-skeema schéma XML RELAX NG scéimre XML RELAX NG Esquema XML RELAX NG סכנת RELAX NG XML RELAX NG XML shema RELAX NG XML-séma Skema XML RELAX NG Schema XML RELAX NG RELAX NG XML スキーマ RELAX NG XML сұлбасы RELAX NG XML 스키마 RELAX NG XML schema RELAX NG XML shēma RELAX NG XML schema Schemat XML RELAX NG Esquema XML de RELAX NG Schemă RELAX NG XML XML-схема RELAX NG XML schéma RELAX NG Datoteka shema RELAX NG XML RELAX NG XML-schema XML-схема RELAX NG RELAX NG XML 模式 RELAX NG XML schema RELAX NG REgular LAnguage for XML Next Generation RTF document مستند RTF Dakument RTF Документ — RTF document RTF Dokument RTF RTF-dokument RTF-Dokument έγγραφο RTF RTF document RTF-dokumento documento RTF RTF dokumentua RTF-asiakirja RTF skjal document RTF cáipéis RTF documento RTF מסמך RTF RTF dokument RTF dokumentum Dokumen RTF Documento RTF RTF ドキュメント RTF құжаты RTF 문서 RTF dokumentas RTF dokuments RTF-dokument RTF-document TRF-dokument Dokument RTF Documento RTF Document RTF документ RTF Dokument RTF Dokument RTF Dokument RTF RTF-dokument документ RTF Tài liệu RTF RTF 文档 RTF 文件 RTF Rich Text Format Sieve mail filter script سكربت مرشح بريد Sieve Skrypt filtravańnia pošty Sieve Скрипт-филтър за пресяване на поща script de filtre de correu Sieve Skript poštovního filtru Sieve Sieve e-post-filterprogram Sieve-E-Mail-Filterskript σενάριο φιλτραρίσματος αλληλογραφίας Sieve Sieve mail filter script script de filtro de correo Sieve Sieve posta-iragazki script-a Sieve-postinsuodatuskomentotiedosto script de filtrage de courriel Sieve script scagaire phost Sieve Script de filtro de correo Sieve תסריט סינון דואר של Sieve Sieve levélszűrő parancsfájl Skrip filter surat Sieve Script filtro posta Sieve Sieve メールフィルタスクリプト Sieve пошталық фильтр сценарийі Sieve 메일 필터 스크립트 Sieve pašto filtro scenarijus Sieve pasta filtra skripts Sieve e-postfilter skript Sieve mailfilter-script Sieve e-postfilterskript Skrypt filtra poczty Sieve Script de filtro de mensagens do Sieve Script filtrare email Sieve сценарий почтового фильтра Sieve Skript poštového filtra Sieve Skriptna datoteka Sieve poštnega filtra Script filtrim poste Sieve Sieve-epostfilterskript скрипт поштового фільтру Sieve Văn lệnh lọc thư Sieve Sieve 邮件过滤脚本 Sieve 郵件過濾指令稿 SMIL document مستند SMIL Dakument SMIL Документ — SMIL document SMIL Dokument SMIL SMIL-dokument SMIL-Dokument έγγραφο SMIL SMIL document SMIL-dokumento documento SMIL SMIL dokumentua SMIL-asiakirja SMIL skjal document SMIL cáipéis SMIL documento SMIL מסמך SMIL SMIL dokument SMIL dokumentum Dokumen SMIL Documento SMIL SMIL ドキュメント SMIL құжаты SMIL 문서 SMIL dokumentas SMIL dokuments SMIL-dokument SMIL-document SMIL-dokument Dokument SMIL Documento SMIL Document SMIL документ SMIL Dokument SMIL Dokument SMIL Dokument SMIL SMIL-dokument SMIL belgesi документ SMIL Tài liệu SMIL SMIL 文档 SMIL 文件 SMIL Synchronized Multimedia Integration Language WPL playlist قائمة تشغيل WPL Списък за изпълнение — WPL llista de reproducció WPL Seznam skladeb WPL WPL-afspilningsliste WPL-Wiedergabeliste λίστα αναπαραγωγής WPL WPL playlist WPL-ludlisto lista de reproducción WPL WPL erreprodukzio-zerrenda WPL-soittolista WPL avspælingarlisti liste de lecture WPL seinmliosta WPL lista de reprodución WPL רשימת השמעה WPL WPL popis za reprodukciju WPL-lejátszólista Senarai putar WPL Scaletta WPL WPL 再生リスト WPL ойнау тізімі WPL 재생 목록 WPL grojaraštis WPL repertuārs WPL-afspeellijst Lista odtwarzania WPL Lista de reprodução do WPL Listă redare WPL список воспроизведения WPL Zoznam skladieb WPL Seznam predvajanja WPL WPL-spellista список відтворення WPL Danh mục nhạc WPL WPL 播放列表 WPL 播放清單 WPL Windows Media Player Playlist SQLite2 database قاعدة بيانات SQLite2 Baza źviestak SQLite2 База от данни — SQLite2 base de dades SQLite2 Databáze SQLite2 SQLite2-database SQLite2-Datenbank βάση δεδομένων SQLite2 SQLite2 database SQLite2-datumbazo base de datos SQLite2 SQLite2 datu-basea SQLite2-tietokanta SQLite2 dátustovnur base de données SQLite2 bunachar sonraí SQLite2 base de datos SQLite2 בסיס מידע של SQLite2 SQLite2 baza podataka SQLite2 adatbázis Basis data SQLite2 Database SQLite2 SQLite2 データベース SQLite2 дерекқоры SQLite2 데이터베이스 SQLite2 duomenų bazė SQLite2 datubāze SQLite2-database SQLite2-gegevensbank SQLite2-database Baza danych SQLite2 Banco de dados do SQLite2 Bază de date SQLite2 база данных SQLite2 Databáza SQLite2 Podatkovna zbirka SQLite2 Bazë me të dhëna SQLite2 SQLite2-databas SQLite2 veritabanı База даних SQLite2 Cơ sở dữ liệu SQLite2 SQLite2 数据库 SQLite2 資料庫 SQLite3 database قاعدة بيانات SQLite3 Baza źviestak SQLite3 База от данни — SQLite3 base de dades SQLite3 Databáze SQLite3 SQLite3-database SQLite3-Datenbank βάση δεδομένων SQLite3 SQLite3 database SQLite3-datumbazo base de datos SQLite3 SQLite3 datu-basea SQLite3-tietokanta SQLite3 dátustovnur base de données SQLite3 bunachar sonraí SQLite3 base de datos SQLite3 בסיס מידע של SQLite3 SQLite3 baza podataka SQLite3 adatbázis Basis data SQLite3 Database SQLite3 SQLite3 データベース SQLite3 дерекқоры SQLite3 데이터베이스 SQLite3 duomenų bazė SQLite3 datubāze SQLite3-database SQLite3-gegevensbank SQLite3-database Baza danych SQLite3 Banco de dados do SQLite3 Bază de date SQLite3 база данных SQLite3 Databáza SQLite3 Podatkovna zbirka SQLite3 Bazë me të dhëna SQLite3 SQLite3-databas SQLite3 veritabanı база даних SQLite3 Cơ sở dữ liệu SQLite3 SQLite3 数据库 SQLite3 資料庫 GEDCOM family history تاريخ عائلة GEDCOM Siamiejnaja historyja GEDCOM Родословно дърво — GEDCOM història familiar GEDCOM Rodokmen GEDCOM GEDCOM-familiehistorie GEDCOM-Stammbaum οικογενειακό ιστορικό GEDCOM GEDCOM family history historia familiar GEDCOM GEDCOM famili historia GEDCOM-sukuhistoria GEDCOM familjusøga généalogie GEDCOM stair theaghlach GEDCOM historial de familia GEDCOM היסטוריה משפחתית של GEDCOM GEDCOM obiteljska povijest GEDCOM családtörténet Sejarah keluarga GEDCOM Cronologia famiglia GEDCOM GEDCOM 家系図データ GEDCOM ოჯახის ისტორია GEDCOM отбасы тарихы GEDCOM 가족 내력 GEDCOM šeimos istorija GEDCOM ģimenes vēsture GEDCOM-familiehistorikk GEDCOM-stamboom GEDCOM-familehistorie Plik historii rodziny GEDCOM Histórico familiar do GEDCOM Tablou genealogic GEDCOM история семьи GEDCOM Rodokmeň GEDCOM Datoteka družinske zgodovine GEDCOM Kronollogji familje GEDCOM GEDCOM-släktträd історія родини GEDCOM Lịch sử gia đình GEDCOM GEDCOM 家谱 GEDCOM 家族史 GEDCOM GEnealogical Data COMmunication Flash video Flash مرئي Videa Flash Видео — Flash vídeo de Flash Video Flash Flashvideo Flash-Video βίντεο Flash Flash video Flash-video vídeo Flash Flash bideoa Flash-video Flash video vidéo Flash físeán Flash vídeo Flash וידאו של פלאש Flash video Flash videó Video Flash Video Flash Flash 動画 Flash-ის ვიდეო Flash видеосы Flash 비디오 Flash vaizdo įrašas Flash video Flash-film Flash-video Flash-video Plik wideo Flash Vídeo Flash Video Flash видео Flash Video Flash Video datoteka Flash Video Flash Flash-video відеокліп Flash Ảnh động Flash Flash 影片 Flash 視訊 JavaFX video Видео — JavaFX vídeo JavaFX Video JavaFX JavaFX-video JavaFX-Video βίντεο JavaFX JavaFX video JavaFX-video vídeo JavaFX JavaFX-video JavaFX video vidéo JavaFX físeán JavaFX vídeo JavaFX וידאו JavaFX JavaFX video JavaFX videó Video JavaFX Video JavaFX JavaFX 動画 JavaFX аудиосы JavaFX 비디오 JavaFX video JavaFX video Plik wideo JavaFX Vídeo JavaFX Video JavaFX видео JavaFX Video JavaFX Video JavaFX JavaFX-video відеокліп JavaFX JavaFX 视频 JavaFX 視訊 SGF record تسجيلة SGF Zapisanaja hulnia SGF Запис — SGF registre SGF Záznam SGF SGF-optagelse SGF-Aufzeichnung εγγραφή SGF SGF record grabación SGF SGF erregistroa SGF-nauhoitus SGF met partie SGF taifead SGF Grabación SGF הקלטת SGF SGF zapis SGF pontszám Catatan SGF Registrazione SGF SGF レコード SGF жазбасы SGF 기록파일 SGF įrašas SGF ieraksts SGF-oppføring SGF-record SGF-logg Zapis gry SGF Gravação SGF Înregistrare SGF запись SGF Záznam SGF Datoteka shranjene igre SGF Regjistrim SGF SGF-protokoll SGF kaydı запис SGF Mục ghi SGF SGF 记录 SGF 紀錄 SGF Smart Game Format XLIFF translation file ملف ترجمة XLIFF Fajł pierakładu XLIFF Превод — XLIFF fitxer traducció XLIFF Soubor překladu XLIFF XLIFF-oversættelsesfil XLIFF-Übersetzung αρχείο προς μετάφραση XLIFF XLIFF translation file archivo de traducción XLIFF XLIFF itzulpen-fitxategia XLIFF-käännöstiedosto XLIFF týðingarfíla fichier de traduction XLIFF comhad aistrithe XLIFF ficheiro de tradución XLIFF קובץ תרגום CLIFF XLIFF datoteka prijevoda XLIFF fordítási fájl Berkas terjemahan XLIFF File traduzione XLIFF XLIFF 翻訳ファイル XLIFF аударма файлы XLIFF 번역 파일 XLIFF vertimo failas XLIFF tulkošanas datne XLIFF-oversettelsesfil XLIFF-vertalingsbestand XLIFF-omsetjingsfil Plik tłumaczenia XLIFF Arquivo de tradução XLIFF Fișier de traducere XLIFF файл перевода XLIFF Súbor prekladu XLIFF Datoteka prevoda XLIFF File përkthimesh XLIFF XLIFF-översättningsfil файл перекладу XLIFF Tập tin dịch XLIFF XLIFF 消息翻译文件 XLIFF 翻譯檔 XLIFF XML Localization Interchange File Format YAML document مستند YAML Документ — YAML document YAML Dokument YAML YAML-dokument YAML-Dokument έγγραφο YAML YAML document YAML-dokumento documento YAML YAML-asiakirja YAML skjal document YAML cáipéis YAML documento YAML מסמך YAML YAML-dokumentum Dokumen YAML Documento YAML YAML ドキュメント YAML құжаты YAML 문서 YAML dokumentas YAML dokuments YAML document Dokument YAML Documento YAML Document YAML документ YAML Dokument YAML Dokument YAML YAML-dokument YAML belgesi документ YAML YAML 文档 YAML 文件 YAML YAML Ain't Markup Language Corel Draw drawing تصميم Corel Draw Corel Draw çəkimi Rysunak Corel Draw Чертеж — Corel Draw dibuix de Corel Draw Kresba Corel Draw Darlun Corel Draw Corel Draw-tegning Corel-Draw-Zeichnung σχέδιο Corel Draw Corel Draw drawing grafikaĵo de Corel Draw dibujo de Corel Draw Corel Draw-eko marrazkia Corel Draw -piirros Corel Draw tekning dessin Corel Draw líníocht Corel Draw debuxo de Corel Draw ציור של Corel Draw Corel Draw crtež Corel Draw-rajz Gambar Corel Draw Disegno Corel Draw Corel Draw ドロー Corel Draw-ის ნახაზი Corel Draw суреті 코렐 드로우 드로잉 Corel Draw piešinys Corel Draw zīmējums Lukisan Corel Draw Corel Draw-tegning Corel Draw-tekening Corel Draw-teikning Rysunek Corel Draw desenho Corel Draw Desenho do Corel Draw Desen Corel Draw изображение Corel Draw Kresba Corel Draw Datoteka risbe Corel Draw Vizatim Corel Draw Corel Draw цртеж Corel Draw-teckning малюнок Corel Draw Bản vẽ Corel Draw Corel Draw 图形 Corel Draw 繪圖 HPGL file ملف HPGL Fajł HPGL Файл — HPGL fitxer HPGL Soubor HPGL HPGL-fil HPGL-Datei αρχείο HPGL HPGL file HPGL-dosiero archivo HPGL HPGL fitxategia HPGL-tiedosto HPGL fíla fichier HPGL comhad HPGL ficheiro HPGL קובץ HGPL HPGL datoteka HPGL fájl Berkas HPGL File HPGL HPGL ファイル HPGL файлы HPGL 파일 HPGL failas HPGL datne HPGL-fil HPGL-bestand HPGL-fil Plik HPGL Arquivo HPGL Fișier HPGL файл HPGL Súbor HPGL Datoteka HPGL File HPGL HPGL-fil файл HPGL Tập tin HPGL HPGL 文件 HPGL 檔案 HPGL HP Graphics Language PCL file ملف PCL Fajł PCL Файл — PCL fitxer PCL Soubor PCL PCL-fil PCL-Datei αρχείο PCL PCL file PCL-dosiero archivo PCL PCL fitxategia PCL-tiedosto PCL fíla fichier PCL comhad PCL ficheiro PCL קובץ PCL PCL fájl Berkas PCL File PCL PCL ファイル PCL файлы PCL 파일 PCL failas PCL datne PCL-fil PCL-bestand PCL-fil Plik PCL Arquivo PCL Fișier PCL файл PCL Súbor PCL Datoteka PCL File PCL PCL-fil PCL dosyası файл PCL Tập tin PCL PCL 文件 PCL 檔 PCL HP Printer Control Language Lotus 1-2-3 spreadsheet جدول Lotus 1-2-3 Lotus 1-2-3 hesab cədvəli Raźlikovy arkuš Lotus 1-2-3 Таблица — Lotus 1-2-3 full de càlcul de Lotus 1-2-3 Sešit Lotus 1-2-3 Taenlen Lotus 1-2-3 Lotus 1-2-3-regneark Lotus-1-2-3-Tabelle λογιστικό φύλλο Lotus 1-2-3 Lotus 1-2-3 spreadsheet Kalkultabelo de Lotus 1-2-3 hoja de cálculo de Lotus 1-2-3 Lotus 1-2-3 kalkulu-orria Lotus 1-2-3 -taulukko Lotus 1-2-3 rokniark feuille de calcul Lotus 1-2-3 scarbhileog Lotus 1-2-3 folla de cálculo de Lotus 1-2-3 גליון נתונים של Lotus 1-2-3 Lotus 1-2-3 proračunska tablica Lotus 1-2-3-munkafüzet Lembar sebar Lotus 1-2-3 Foglio di calcolo Lotus 1-2-3 Lotus 1-2-3 スプレッドシート Lotus 1-2-3 электрондық кестесі Lotus 1-2-3 스프레드시트 Lotus 1-2-3 skaičialentė Lotus 1-2-3 izklājlapa Hamparan Lotus 1-2-3 Lotus 1-2-3 regneark Lotus 1-2-3-rekenblad Lotus 1-2-3 rekneark Arkusz Lotus 1-2-3 folha de cálculo Lotus 1-2-3 Planilha do Lotus 1-2-3 Foaie de calcul Lotus 1-2-3 электронная таблица Lotus 1-2-3 Zošit Lotus 1-2-3 Preglednica Lotus 1-2-3 Fletë llogaritjesh Lotus 1-2-3 Lotus 1-2-3 табеларни прорачун Lotus 1-2-3-kalkylblad ел. таблиця Lotus 1-2-3 Bảng tính Lotus 1-2-3 Lotus 1-2-3 工作簿 Lotus 1-2-3 試算表 Lotus Word Pro JET database قاعدة بيانات JET Baza źviestak JET База от данни — JET base de dades JET Databáze JET JET-database JET-Datenbank βάση δεδομένων JET JET database JET-datumbazo base de datos JET JET datu-basea JET-tietokanta JET dátustovnur base de données JET bunachar sonraí JET base de datos JET מסד נתונים JET JET baza podataka JET adatbázis Basis data JET Database JET JET データベース JET дерекқоры JET 데이터베이스 JET duomenų bazė JET datubāze JET-database JET-gegevensbank JET-database Baza Danych JET Banco de dados JET Bază de date JET база данных JET Databáza JET Podatkovna zbirka JET Bazë me të dhëna JET JET-databas JET veritabanı База даних JET Cơ sở dữ liệu JET JET 数据库 JET 資料庫 JET Joint Engine Technology Microsoft Cabinet archive أرشيف Microsoft Cabinet Архив — Microsoft Cabinet arxiu Cabinet de Microsoft Archiv Microsoft Cabinet Microsoft Cabinet-arkiv Microsoft-Cabinet-Archiv συμπιεσμένο αρχείο Microsoft Cabinet Microsoft Cabinet archive archivador Microsoft Cabinet Microsoft Cabinet artxiboa Microsoft Cabinet -arkisto Microsoft Cabinet skjalasavn archive Cab Microsoft cartlann Microsoft Cabinet arquivo de Microsoft Cabinet ארכיון CAB (מיקרוסופט) Microsoft Cabinet arhiva Microsoft Cabinet archívum Arsip Microsoft Cabinet Archivio Microsoft Cabinet Microsoft Cabinet アーカイブ Microsoft-ის Cabinet არქივი Microsoft Cabinet архиві 마이크로소프트 캐비닛 묶음 Microsoft Cabinet archyvas Microsoft kabineta arhīvs Microsoft Cabinet-archief Archiwum Microsoft Cabinet Pacote do Microsoft Cabinet Arhivă Microsoft Cabinet архив Microsoft Cabinet Archív Microsoft Cabinet Datoteka arhiva Microsoft Cabinet Microsoft Cabinet-arkiv Microsoft Cabinet arşivi архів Cabinet Microsoft Kho lưu Cabinet Microsoft Microsoft CAB 归档文件 微軟 Cabinet 封存檔 Excel spreadsheet جدول Excel Raźlikovy akruš Excel Таблица — Excel full de càlcul d'Excel Sešit Excel Excelregneark Excel-Tabelle φύλλο εργασίας Excel Excel spreadsheet Excel-kalkultabelo hoja de cálculo de Excel Excel kalkulu-orria Excel-taulukko Excel rokniark feuille de calcul Excel scarbhileog Excel folla de cálculo de Excel גליון נתונים של אקסל Excel proračunska tablica Excel táblázat Lembar sebar Excel Foglio di calcolo Excel Excel スプレッドシート Excel-ის ცხრილი Excel электрондық кестесі 엑셀 스프레드시트 Excel skaičialentė Excel izklājlapa Excel regneark Excel-rekenblad Excel-rekneark Arkusz Excel Planilha do Excel Foaie de calcul Excel электронная таблица Excel Zošit Excel Razpredelnica Microsoft Excel Fletë llogaritje Excel Excel-kalkylblad ел. таблиця Excel Bảng tính Excel Microsoft Excel 工作簿 Excel 試算表 Excel add-in Приставка — Excel complement d'Excel Doplněk aplikace Excel Excel-tilføjelse Excel Add-in πρόσθετο Excel Excel add-in Complemento de Excel Excel-lisäosa complément Excel complemento de Excel תוסף של Excel Excel priključak Excel bővítmény Add-in Excel Add-in Excel Excel アドイン Excel-ის დამატება Excel қосымшасы 엑셀 추가 기능 Excel pievienojumprogramma Excel add-in Dodatek Excel Suplemento do Excel дополнение Excel Vstavek Excel додаток Excel Excel 附加组件 Excel 增益集 Excel 2007 binary spreadsheet Таблица — Excel 2007, двоична full de càlcul binari d'Excel 2007 Binární formát sešitu Excel 2007 Binært Excel 2007-regneark Excel 2007-Tabelle (binär) Excel 2007 binary spreadsheet Hoja de cálculo de Excel 2007 feuille de calcul binaire Excel 2007 ficheiro binario de folla de cálculo Excel 2007 גיליון נתונים בינרי של Excel 2007 Excel 2007 binarna proračunska tablica Excel 2007 bináris táblázat Lembar kerja biner Excel 2007 Foglio di calcolo binario Excel 2007 Excel 2007 バイナリスプレッドシート Excel 2007-ის ბინარული ცხრილი Excel 2007 бинарды кестесі 엑셀 2007 바이너리 스프레드시트 Excel 2007 binārā izklājlapa Excel 2007 binary spreadsheet Binarny arkusz Excel 2007 Planilha binária do Excel 2007 двоичная электронная таблица Excel 2007 Binarna preglednica Excel 2007 бінарна електронна таблиця Excel 2007 Excel 2007 二进制工作表 Excel 2007 二進位試算表 Excel macro-enabled spreadsheet Таблица — Excel, с макроси full de càlcul amb macros d'Excel Sešit Excel s podporou maker Makro-aktiveret Excel-regneark Excel-Tabelle mit aktivierten Makros φύλο εργασίας Excel με ενεργοποιημένες μακροεντολές Excel macro-enabled spreadsheet Hoja de cálculo con macros activados de Excel feuille de calcul Excel avec macros folla de cálculo de Excel con macros activadas גיליון נתונים עם תכונות מקרו פעילות של Excel Excel proračunska tablica s omogućenim makro naredbama Excel makrókat tartalmazó táblázat Lembar kerja Excel dengan makro Foglio di calcolo Excel con macro abilitate Excel マクロ有効スプレッドシート Excel-ის მაკროსიანი ცხრილი макростары іске қосылған Excel кестесі 엑셀 매크로 사용 스프레드시트 Excel izklājlapa ar makrosiem Excel macro-enabled spreadsheet Arkusz z włączonymi makrami Excel Planilha do Excel com macro ativada электронная таблица Excel с включёнными макросами Preglednica Excel z omogočenimi makri електронна таблиця Excel з увімкненими макросами Excel 启用宏的工作表 Excel 巨集啟用試算表 Excel macro-enabled spreadsheet template Шаблон за таблици — Excel, с макроси plantilla de full de càlcul amb macros d'Excel Šablona sešitu Excel s podporou maker Makro-aktiveret Excel-regnearksskabelon Excel-Tabellenvorlage mit aktivierten Makros πρότυπο φύλλο εργασίας Excel με ενεργοποιημένες μακροεντολές Excel macro-enabled spreadsheet template Plantilla de hoja de cálculo con macros activados de Excel modèle de feuille de calcul Excel avec macros modelo de folla de cálculo de Excel con macros activadas תבנית של גיליון נתונים עם תכונות מקרו פעילות של Excel Excel predložak proračunske tablice s omogućenim makro naredbama Excel makrókat tartalmazó táblázatsablon Templat lembar kerja Excel dengan makro Modello foglio di calcolo Excel con macro abilitate Excel マクロ有効スプレッドシートテンプレート Excel-ის მაკროსიანი ცხრილის შაბლონი макростары іске қосылған Excel кестесінің үлгісі 엑셀 매크로 사용 스프레드시트 서식 Excel izklājlapas ar makrosiem veidne Excel macro-enabled spreadsheet sjabloon Szablon arkusza z włączonymi makrami Excel Módulo de planilha do Excel com macro ativada шаблон электронной таблицы Excel с включёнными макросами Predloga preglednice Excel z omogočenimi makri шаблон електронної таблиці Excel з увімкненими макросами Excel 启用宏的工作表模板 Excel 巨集啟用試算表範本 PowerPoint presentation عرض تقديمي PowerPoint Prezentacyja PowerPoint Презентация — PowerPoint presentació de PowerPoint Prezentace PowerPoint PowerPoint-præsentation PowerPoint-Präsentation παρουσίαση PowerPoint PowerPoint presentation PowerPoint-prezentaĵo presentación de PowerPoint PowerPoint aurkezpena PowerPoint-esitys PowerPoint framløga présentation PowerPoint láithreoireacht PowerPoint presentación de PowerPoint מצגת PowerPoint PowerPoint prezentacija PowerPoint prezentáció Presentasi PowerPoint Presentazione PowerPoint PowerPoint プレゼンテーション PowerPoint презентациясы 파워포인트 프리젠테이션 PowerPoint pateiktis PowerPoint prezentācija PowerPoint-presentasjon PowerPoint-presentatie PowerPoint-presentasjon Prezentacja PowerPoint Apresentação do PowerPoint Prezentare PowerPoint презентация PowerPoint Prezentácia PowerPoint Predstavitev Microsoft PowerPoint Prezantim PowerPoint PowerPoint-presentation презентація PowerPoint Trình diễn PowerPoint Microsoft PowerPoint 演示文稿 PowerPoint 簡報 PowerPoint add-in Приставка — PowerPoint complement de Doplněk PowerPoint PowerPoint-tilføjelse PowerPoint Add-in πρόσθετο PowerPoint PowerPoint add-in Complemento de PowerPoint PowerPoint-lisäosa complément PowerPoint complemento de PowerPoint תוסף של PowerPoint PowerPoint priključak PowerPoint bővítmény Add-in PowerPoint Add-in PowerPoint PowerPoint アドイン PowerPoint-ის დამატება PowerPoint қосымшасы 파워포인트 추가 기능 PowerPoint pievienojumprogramma PowerPoint add-in Dodatek PowerPoint Suplemento do PowerPoint дополнение PowerPoint Vstavek PowerPoint додаток PowerPoint PowerPoint 附加组件 PowerPoint 增益集 PowerPoint macro-enabled presentation Презентация — PowerPoint, с макроси presentació amb macros Prezentace PowerPoint s podporou maker Makro-aktiveret PowerPoint-præsentation PowerPoint-Präsentation mit aktivierten Makros παρουσίαση PowerPoint με ενεργοποιημένες μακροεντολές PowerPoint macro-enabled presentation Presentación con macros activadas de PowerPoint présentation PowerPoint avec macros presentación con macros activadas de PowerPoint מצגת של PowerPoint בעלת תכונות מקרו פעילות PowerPoint prezentacija s omogućenim makro naredbama PowerPoint makrókat tartalmazó bemutató Presentasi PowerPoint dengan makro Presentazione PowerPoint con macro abilitate PowerPoint マクロ有効プレゼンテーション PowerPoint-ის მაკროსიანი პრეზენტაცია макростары іске қосылған PowerPoint презентациясы 파워포인트 매크로 사용 프리젠테이션 PowerPoint prezentācija ar makrosiem PowerPoint macro-enabled presentatie Prezentacja z włączonymi makrami PowerPoint Apresentação do PowerPoint com macro ativada презентация PowerPoint с включёнными макросами Predstavitev PowerPoint z omogočenimi makri презентація PowerPoint з увімкненими макросами PowerPoint 启用宏的演示文稿 PowerPoint 巨集啟用簡報 PowerPoint macro-enabled slide Кадър — PowerPoint, с макроси dispositiva amb macros Snímek PowerPoint s podporou maker Makro-aktiveret PowerPoint-slide PowerPoint-Folie mit aktivierten Makros σλάιντ PowerPoint με ενεργοποιημένες μακροεντολές PowerPoint macro-enabled slide Diapositiva con macros activadas de PowerPoint diapositive PowerPoint avec macros Diapositiva con macros activadas de Powerpoint שקופית של PowerPoint בעלת תכונות מקרו פעילות PowerPoint slajd s omogućenim makro naredbama PowerPoint makrókat tartalmazó dia Slide PowerPoint dengan makro Diapositiva PowerPoint con macro abilitate PowerPoint マクロ有効スライド PowerPoint-ის მაკროსიანი სლაიდი макростары іске қосылған PowerPoint слайды 파워포인트 매크로 사용 슬라이드 PowerPoint slaids ar makrosiem PowerPoint macro-enabled dia Slajd z włączonymi makrami PowerPoint Slide do PowerPoint com macro ativada слайд PowerPoint с включёнными макросами Prosojnica PowerPoint z omogočenimi makri слайд PowerPoint з увімкненими макросами PowerPoint 启用宏的幻灯片 PowerPoint 巨集啟用投影片 PowerPoint macro-enabled presentation Презентация — PowerPoint, с макроси presentació amb macros Prezentace PowerPoint s podporou maker Makro-aktiveret PowerPoint-præsentation PowerPoint-Präsentation mit aktivierten Makros παρουσίαση PowerPoint με ενεργοποιημένες μακροεντολές PowerPoint macro-enabled presentation Presentación con macros activadas de PowerPoint présentation PowerPoint avec macros presentación con macros activadas de PowerPoint מצגת של PowerPoint בעלת תכונות מקרו פעילות PowerPoint prezentacija s omogućenim makro naredbama PowerPoint makrókat tartalmazó bemutató Presentasi PowerPoint dengan makro Presentazione PowerPoint con macro abilitate PowerPoint マクロ有効プレゼンテーション PowerPoint-ის მაკროსიანი პრეზენტაცია макростары іске қосылған PowerPoint презентациясы 파워포인트 매크로 사용 프리젠테이션 PowerPoint prezentācija ar makrosiem PowerPoint macro-enabled presentatie Prezentacja z włączonymi makrami PowerPoint Apresentação do PowerPoint com macro ativada презентация PowerPoint с включёнными макросами Predstavitev PowerPoint z omogočenimi makri презентація PowerPoint з увімкненими макросами PowerPoint 启用宏的演示文稿 PowerPoint 巨集啟用簡報 PowerPoint macro-enabled presentation template Шаблон за презентации — PowerPoint, с макроси plantilla de presentació amb macros Šablona prezentace PowerPoint s podporou maker Makro-aktiveret PowerPoint-præsentationsskabelon PowerPoint-Präsentationsvorlage mit aktivierten Makros πρότυπη παρουσίαση PowerPoint με ενεργοποιημένες μακροεντολές PowerPoint macro-enabled presentation template Plantilla de presentación con macros activadas de PowerPoint modèle de présentation PowerPoint avec macros modelo de presentación con macros activadas de PowerPoint תבנית של מצגת של PowerPoint בעלת תכונות מקרו פעילות PowerPoint predložak prezentacije s omogućenim makro naredbama PowerPoint makrókat tartalmazó bemutatósablon Templat presentasi PowerPoint dengan makro Modello presentazione PowerPoint con macro abilitate PowerPoint マクロ有効プレゼンテーションテンプレート PowerPoint-ის მაკროსიანი პრეზენტაციის შაბლონი макростары іске қосылған PowerPoint презентациясының үлгісі 파워포인트 매크로 사용 프리젠테이션 서식 PowerPoint prezentācijas ar makrosiem veidne PowerPoint macro-enabled presentatie-sjabloon Szablon prezentacji z włączonymi makrami PowerPoint Modelo de apresentação do PowerPoint com macro ativada шаблон презентации PowerPoint с включёнными макросами Predloga predstavitve PowerPoint z omogočenimi makri шаблон презентації PowerPoint з увімкненими макросами PowerPoint 启用宏的演示文稿模板 PowerPoint 巨集啟用簡報範本 Word macro-enabled document Документ — Word, с макроси document amb macros de Word Dokument Word s podporou maker Makro-aktiveret Word-dokument Word-Dokument mit aktivierten Makros έγγραφο Word με ενεργοποιημένες μακροεντολές Word macro-enabled document Documento con macros activadas de Word document Word avec macros documento con macros activadas de Word מסמך של Word בעל תכונות מקרו פעילות Word dokument s omogućenim makro naredbama Word makrókat tartalmazó dokumentum Dokumen Word dengan makro Documento Word con macro abilitate Word マクロ有効文書 Word-ის მაკროსიანი დოკუმენტი макростары іске қосылған Word құжаты 워드 매크로 사용 문서 Word dokuments ar makrosiem Word macro-enabled document Dokument z włączonymi makrami Word Documento do Word com macro ativada документ Word с включёнными макросами Dokument Word z omogočenimi makri документ Word з увімкненими макросами Word 启用宏的文档 Word 巨集啟用文件 Word macro-enabled document template Шаблон за документи — Word, с макроси plantilla de document amb macros de Word Šablona dokumentu Word s podporou maker Makro-aktiveret Word-dokumentskabelon Word-Dokumentvorlage mit aktivierten Makros πρότυπο έγγραφο Word με ενεργοποιημένες μακροεντολές Word macro-enabled document template Plantilla de documento con macros activadas de Word modèle de document Word avec macros Plantilla de documento con macros activadas de Word תבנית של מסמך של Word בעל תכונות מקרו פעילות Word predložak dokumenta s omogućenim makro naredbama Word makrókat tartalmazó dokumentumsablon Templat dokumen Word dengan makro Modello documento Word con macro abilitate Word マクロ有効文書テンプレート Word-ის მაკროსიანი დოკუმენტის შაბლონი макростары іске қосылған Word құжатының үлгісі 워드 매크로 사용 문서 서식 Word dokumenta ar makrosiem veidne Word macro-enabled document sjabloon Szablon dokumentu z włączonymi makrami Word Modelo de documento do Word com macro ativada шаблон документа Word с включёнными макросами Predloga dokumenta Word z omogočenimi makri шаблон документа Word з увімкненими макросами Word 启用宏的文档模板 Word 巨集啟用文件範本 XPS document مستند XPS Dakument XPS Документ — XPS document XPS Dokument XPS XPS-dokument XPS-Dokument έγγραφο XPS XPS document XPS-dokumento documento XPS XPS dokumentua XPS-asiakirja XPS skjal document XPS cáipéis XPS documento XPS מסמך XPS XPS dokument XPS dokumentum Dokumen XPS Documento XPS XPS ドキュメント XPS құжаты XPS 문서 XPS dokumentas XPS dokuments XPS-dokument XPS-document XPS-dokument Dokument XPS Documento XPS Document XPS документ XPS Dokument XPS Dokument XPS Dokument XPS XPS-dokument документ XPS Tài liệu XPS XPS 文档 XPS 文件 XPS Open XML Paper Specification Microsoft Works document مستند Microsoft Works Dakument Microsoft Works Документ — Microsoft Works document Works de Microsoft Dokument Microsoft Works Microsoft Works-dokument Microsoft-Works-Dokument έγγραφο Microsoft Works Microsoft Works document documento de Microsoft Works Microsoft Works dokumentua Microsoft Works -asiakirja Microsoft Works skjal document Microsoft Works cáipéis Microsoft Works documento de Microsoft Works מסמך Microsoft Works Microsoft Works dokument Microsoft Works dokumentum Dokumen Microsoft Works Documento Microsoft Works Microsoft Works ドキュメント Microsoft Works-ის დოკუმენტი Microsoft Works құжаты 마이크로소프트 Works 문서 Microsoft Works dokumentas Microsoft Works dokuments Microsoft Works-dokument Microsoft Works-document Microsoft Works-dokument Dokument Microsoft Works Documento do Microsoft Works Document Microsoft Works документ Microsoft Works Dokument Microsoft Works Dokument Microsoft Works Dokument Microsoft Works Microsoft Works-dokument Microsoft Works belgesi документ Microsoft Works Tài liệu Microsoft Works Microsoft Works 文档 微軟 Works 文件 Microsoft Visio document Документ — Microsoft Visio document Visio de Microsoft Microsoft Visio-dokument Microsoft Visio-Dokument έγγραφο Microsoft Visio Microsoft Visio document documento de Microsoft Visio Microsoft Visio -asiakirja document Microsoft Visio Documento de Microsoft Visio מסמך Microsoft Visio dokument Microsoft Visio dokumentum Dokumen Microsoft Visio Documento Microsoft Visio Microsoft Visio ドキュメント Microsoft Visio-ის დოკუმენტი Microsoft Visio құжаты 마이크로소프트 Visio 문서 Microsoft Visio dokuments Microsoft Visio document Dokument Microsoft Visio Documento do Microsoft Visio документ Microsoft Visio Dokument Microsoft Visio документ Microsoft Visio Microsoft Visio 文档 Microsoft Visio文件 Word document مستند Word Dakument Word Документ — Word document de Word Dokument Word Worddokument Word-Dokument έγγραφο Word Word document Word-dokumento documento de Word Word dokumentua Word-asiakirja Word skjal document Word cáipéis Word documento de Word מסמך Word Word dokument Word dokumentum Dokumen Word Documento Word Word ドキュメント Word құжаты 워드 문서 Word dokumentas Word dokuments Word-dokument Word-document Word-dokument Dokument Word Documento do Word Document Word документ Word Dokument Word Dokument Word Dokument Word Word-dokument документ Word Tài liệu Word Microsoft Word 文档 Word 文件 Word template قالب Word Šablon Word Шаблон за документи — Word plantilla de Word Šablona Word Wordskabelon Word-Vorlage πρότυπο έγγραφο Word Word template Word-ŝablono plantilla de Word Word txantiloia Word-malli Word formur modèle Word teimpléad Word Plantilla de Word תבנית Word Word predložak Word sablon Templat Word Modello Word Word テンプレート Word үлгісі 워드 서식 Word šablonas Word veidne Word-mal Word-sjabloon Word-mal Szablon Word Modelo do Word Șablon Word шаблон Word Šablóna Word Predloga dokumenta Microsoft Word Model Word Word-mall шаблон Word Mẫu Word Word 模板 Word 範本 GML document GML Geography Markup Language GNUnet search file ملف بحث GNUnet fajł pošuku GNUnet Указател за търсене — GNUnet fitxer de cerca GNUnet Vyhledávací soubor GNUnet GNunet-søgefil GNUnet-Suchdatei αρχείο αναζήτησης GNUnet GNUnet search file archivo de búsqueda GNUnet GNUnet bilaketako fitxategia GNUnet-hakutiedosto GNUnet leitifíla fichier de recherche GNUnet comhad cuardaigh GNUnet ficheiro de busca de GNUnet קובץ חיפוש של GNUnet GNUnet datoteka pretrage GNUnet keresési fájl Berkas telusur GNUnet File ricerca GNUnet GNUnet 検索ファイル GNUnet ძებნის ფაილი GNUnet іздеу файлы GNUnet 검색 파일 GNUnet paieškos failas GNUnet meklēšanas datne GNUnet søkefil GNUnet-zoekbestand GNUnet-søkjefil Plik wyszukiwania GNUnet Arquivo de pesquisa do GNUnet Fișier căutare GNUnet файл поиска GNUnet Vyhľadávací súbor GNUnet Iskalna datoteka GNUnet File kërkimi GNUnet GNUnet-sökfil файл пошуку GNUnet Tập tin tìm kiếm GNUnet GNUnet 搜索文件 GNUnet 搜尋檔案 TNEF message رسالة TNEF List TNEF Съобщение — TNEF missatge TNEF Zpráva TNEF TNEF-meddelelse TNEF-Nachricht μήνυμα TNEF TNEF message mensaje TNEF TNEF mezua TNEF-viesti TNEF boð message TNEF teachtaireacht TNEF mensaxe TNEF הודעת TNEF TNEF poruka TNEF üzenet Pesan TNEF Messaggio TNEF TNEF メッセージ TNEF мәлімдемесі TNEF 메시지 TNEF žinutė TNEF ziņojums TNEF-melding TNEF-bericht TNEF-melding Wiadomość TNEF Mensagem TNEF Mesaj TNEF сообщение TNEF Správa TNEF Datoteka sporočila TNEF Mesazh TNEF TNEF-meddelande повідомлення TNEF Thông điệp TNEF TNEF 信件 TNEF 訊息 TNEF Transport Neutral Encapsulation Format StarCalc spreadsheet جدول StarCalc StarCalc hesab cədvəli Raźlikovy arkuš StarCalc Таблица — StarCalc full de càlcul de StarCalc Sešit StarCalc Taenlen StarCalc StarCalc-regneark StarCalc-Tabelle λογιστικό φύλλο StarCalc StarCalc spreadsheet StarCalc-kalkultabelo hoja de cálculo de StarCalc StarCalc kalkulu-orria StarCalc-taulukko StarCalc rokniark feuille de calcul StarCalc scarbhileog StarCalc folla de cálculo de StarCalc גליון נתונים של StarCalc StarCalc proračunska tablica StarCalc-munkafüzet Lembar sebar StarCalc Foglio di calcolo StarCalc StarCalc スプレッドシート StarCalc электрондық кестесі StarCalc 스프레드시트 StarCalc skaičialentė StarCalc izklājlapa Hamparan StarCalc StarCalc-regneark StarCalc-rekenblad StarCalc-rekneark Arkusz StarCalc folha de cálculo do StarCalc Planilha do StarCalc Foaie de calcul StarCalc электронная таблица StarCalc Zošit StarCalc Preglednica StarCalc Fletë llogaritjesh StarCalc StarCalc табеларни прорачун StarCalc-kalkylblad ел. таблиця StarCalc Bảng tính StarCalc STarCalc 工作簿 StarCalc 試算表 StarChart chart مخطط StarChart StarChart cədvəli Dyjahrama StarChart Диаграма — StarChart diagrama de StarChart Graf StarChart Siart StarChart StarChart-diagram StarChart-Diagramm γράφημα StarChart StarChart chart StarChart-diagramo gráfica de StarChart StarChart diagrama StarChart-kaavio StarChart strikumynd graphique StarChart cairt StarChart gráfica de StarChart טבלה של StarChart StarChart grafikon StarChart-grafikon Bagan StarChart Grafico StarChart StarChart チャート StarChart диаграммасы StarCalc 표 StarChart diagrama StarChart diagramma Carta StarChart StarChart graf StarChart-kaart StarChart-graf Wykres StarChart gráfico do StarChart Gráfico do StarChart Diagramă StarChart диаграмма StarChart Graf StarChart Datoteka grafikona StarChart Grafik StarChart StarChart графикон StarChart-diagram діаграма StarChart Đồ thị StarChart STarChart 图表 StarChart 圖表 StarDraw drawing تصميم StarDraw StarDraw çəkimi Rysunak StarDraw Чертеж — StarDraw dibuix de StarDraw Kresba StarDraw Darlun StarDraw StarDraw-tegning StarDraw-Zeichnung σχέδιο StarDraw StarDraw drawing StarDraw-grafikaĵo dibujo de StarDraw StarDraw marrazkia StarDraw-piirros StarDraw tekning dessin StarDraw líníocht StarDraw debuxo de StarDraw ציור של StarDrawing StarDraw crtež StarDraw-rajz Gambar StarDraw Disegno StarDraw StarDraw ドロー StarDraw суреті StarCalc 드로잉 StarDraw piešinys StarDraw zīmējums Lukisan StarDraw StarDraw tegning StarDraw-tekening StarDraw-teikning Rysunek StarDraw desenho do StarDraw Desenho do StarDraw Desen StarDraw изображение StarDraw Kresba StarDraw Datoteka risbe StarDraw Vizatim StarDraw StarDraw drawing StarDraw-teckning малюнок StarDraw Bản vẽ StarDraw STarDraw 绘图 StarDraw 繪圖 StarImpress presentation عرض تقديمي StarImpress StarImpress təqdimatı Prezentacyja StarImpress Презентация — StarImpress presentació de StarImpress Prezentace StarImpress Cyflwyniad StarImpress StarImpress-præsentation StarImpress-Präsentation παρουσίαση StarImpress StarImpress presentation StarImpress-prezentaĵo presentación de StarImpress StarImpress aurkezpena StarImpress-esitys StarImpress framløga présentation StarImpress láithreoireacht StarImpress presentación de StarImpress מצגת של StarImpress StarImpress prezentacija StarImpress-bemutató Presentasi StarImpress Presentazione StarImpress StarImpress プレゼンテーション StarImpress презентациясы StarImpress 프리젠테이션 StarImpress pateiktis StarImpress prezentācija Persembahan StarImpress StarImpress-presentasjon StarImpress-presentatie StarImpress-presentasjon Prezentacja StarImpress apresentação do StarImpress Apresentação do StarImpress Prezentare StarImpress презентация StarImpress Prezentácia StarImpress Predstavitev StarImpress Prezantim StarImpress StarImpress презентација StarImpress-presentation презентація StarImpress Trình diễn StarImpress STarImpress 演示文稿 StarImpress 簡報檔 StarMail email بريد StarMail الإلكتروني Email StarMail Електронно писмо — StarMail correu electrònic de StarMail E-mail StarMail StarMail-e-brev StarMail-E-Mail ηλ. μήνυμα StarMail StarMail email StarMail-retpoŝto correo electrónico de StarMail StarMail helb.el. StarMail-sähköposti StarMail t-postur courriel StarMail ríomhphost StarMail Correo electrónico de StarMail דוא"ל של StarMail StarMail e-pošta StarMail e-mail Email StarMail Email StarMail StarMail メール StarMail электрондық хаты StarMail 전자우편 StarMail el. laiškas StarMail epasts Emel StarMail StarMail-melding StarMail-e-mail StarMail-fil E-Mail StarMail e-mail do StarMail E-mail do StarMail Email StarEmail электронное письмо StarMail E-mail StarMail Datoteka pošte StarMail Mesazh StarMail StarMail пошта StarMail-e-post поштове повідомлення StarMail Thư điện tử StarMail STarMail 电子邮件 StarMail 郵件 StarMath formula صيغة StarMath Formuła StarMath Формула — StarMath fórmula de StarMath Vzorec StarMath StarMath-formel StarMath-Formel μαθηματικός τύπος StarMath StarMath formula StarMath-formulo fórmula de StarMath StarMath formula StarMath-kaava StarMath frymil formule StarMath foirmle StarMath fórmula de StarMath נוסחה של StarMath StarMath formula StarMath-képlet Formula StarMath Formula StarMath StarMath 計算式 StarMath формуласы StarMath 수식 StarMath formulė StarMath formula Formula StarMath StarMath-formel StarMath-formule StarMath-formel Formuła StarMath fórmula do StarMath Fórmula do StarMath Formulă StarMath формула StarMath Vzorec StarMath Datoteka formule StarMath Formulë StarMath StarMath формула StarMath-formel формула StarMath Công thức StarMath STarMath 公式 StarMath 公式 StarWriter document مستند StarWriter StarWriter sənədi Dakument StarWriter Документ — StarWriter document de StarWriter Dokument StarWriter Dogfen StarWriter StarWriter-dokument StarWriter-Dokument έγγραφο StarWriter StarWriter document StarWriter-dokumento documento de StarWriter StarWriter dokumentua StarWriter-asiakirja StarWriter skjal document StarWriter cáipéis StarWriter documento de StarWriter מסמך של StarWriter StarWriter dokument StarWriter-dokumentum Dokumen StarWriter Documento StrarWriter StarWriter ドキュメント StarWriter құжаты StarWriter 문서 StarWriter dokumentas StarWriter dokuments Dokumen StarWriter StarWriter-dokument StarWriter-document StarWriter document Dokument StarWriter documento do StarWriter Documento do StarWriter Document StarWriter документ StarWriter Dokument StarWriter Dokument StarWriter Dokument StarWriter StarWriter документ StarWriter-dokument StarWriter belgesi документ StarWriter Tài liệu StarWriter STarWriter 文档 StarWriter 文件 OpenOffice Calc spreadsheet جدول Calc المكتب المفتوح Raźlikovy arkuš OpenOffice Calc Таблица — OpenOffice Calc full de càlcul d'OpenOffice Calc Sešit OpenOffice Calc OpenOffice Calc-regneark OpenOffice-Calc-Tabelle φύλλο εργασίας OpenOffice Calc OpenOffice Calc spreadsheet hoja de cálculo de OpenOffice Calc OpenOffice.org Calc kalkulu-orria OpenOffice Calc -taulukko OpenOffice Calc rokniark feuille de calcul OpenOffice Calc scarbhileog OpenOffice Calc folla de cálculo de OpenOffice Calc גליון נתונים של OpenOffice Calc OpenOffice Calc táblázat Lembar sebar OpenOffice Calc Foglio di calcolo OpenOffice Calc OpenOffice Calc スプレッドシート OpenOffice Calc-ის ცხრილი OpenOffice Calc электрондық кестесі OpenOffice Calc 스프레드시트 OpenOffice Calc skaičialentė OpenOffice Calc izklājlapa OpenOffice Calc-regneark OpenOffice.org Calc-rekenblad OpenOffice Calc-rekneark Arkusz kalkulacyjny OpenOffice.org Calc Planilha do OpenOffice Calc Foaie de calcul OpenOffice Calc электронная таблица OpenOffice Calc Zošit OpenOffice Calc Razpredelnica OpenOffice.org Calc Fletë llogaritjesh OpenOffice Calc OpenOffice Calc-kalkylblad ел. таблиця OpenOffice Calc Bảng tính Calc của OpenOffice.org OpenOffice.org Calc 工作簿 OpenOffice Calc 試算表 OpenOffice Calc template قالب Calc المكتب المفتوح Šablon OpenOffice Calc Шаблон за таблици — OpenOffice Calc plantilla d'OpenOffice Calc Šablona OpenOffice Calc OpenOffice Calc-skabelon OpenOffice-Calc-Vorlage πρότυπο OpenOffice Calc OpenOffice Calc template plantilla de OpenOffice Calc OpenOffice Calc txantiloia OpenOffice Calc -malli OpenOffice Calc formur modèle OpenOffice Calc teimpléad OpenOffice Calc modelo de OpenOffice Calc תבנית של OpenOffice Calc OpenOffice Calc sablon Templat OpenOffice Calc Modello OpenOffice Calc OpenOffice Calc テンプレート OpenOffice Calc-ის შაბლონი OpenOffice Calc үлгісі OpenOffice Calc 스프레드시트 문서 서식 OpenOffice Calc šablonas OpenOffice Calc veidne OpenOffice Calc-mal OpenOffice.org Calc-sjabloon OpenOffice Calc-mal Szablon arkusza OpenOffice.org Calc Modelo do OpenOffice Calc Șablon OpenOffice Calc шаблон OpenOffice Calc Šablóna OpenOffice Calc Predloga OpenOffice.org Calc Model OpenOffice Calc OpenOffice Calc-mall шаблон ел.таблиці OpenOffice Calc Mẫu bảng tính Calc của OpenOffice.org OpenOffice.org Calc 工作簿模板 OpenOffice Calc 範本 OpenOffice Draw drawing تصميم Draw المكتب المفتوح Rysunak OpenOffice Draw Чертеж — OpenOffice Draw dibuix d'OpenOffice Draw Kresba OpenOffice Draw OpenOffice Draw-tegning OpenOffice-Draw-Zeichnung σχέδιο OpenOffice Draw OpenOffice Draw drawing dibujo de OpenOffice Draw OpenOffice.org Draw marrazkia OpenOffice Draw -piirros OpenOffice Draw tekning dessin OpenOffice Draw líníocht OpenOffice Draw debuxo de OpenOffice Draw ציור של OpenOffice Draw OpenOffice Draw rajz Gambar OpenOffice Draw Disegno OpenOffice Draw OpenOffice Draw ドロー OpenOffice Draw-ის ნახაზი OpenOffice Draw суреті OpenOffice Draw 그림 OpenOffice Draw piešinys OpenOffice Draw zīmējums OpenOffice Draw-tegning OpenOffice.org Draw-tekening OpenOffice Draw-teikning Rysunek OpenOffice.org Draw Desenho do OpenOffice Draw Desen OpenOffice Draw изображение OpenOffice Draw Kresba OpenOffice Draw Datoteka risbe OpenOffice.org Draw Vizatim OpenOffice Draw OpenOffice Draw-teckning малюнок OpenOffice Draw Bản vẽ Draw của OpenOffice.org OpenOffice.org Draw 绘图 OpenOffice Draw 繪圖 OpenOffice Draw template قالب Draw المكتب المفتوح Šablon OpenOffice Draw Шаблон за чертежи — OpenOffice Draw plantilla d'OpenOffice Draw Šablona OpenOffice Draw OpenOffice Draw-skabelon OpenOffice-Draw-Vorlage πρότυπο OpenOffice Draw OpenOffice Draw template plantilla de OpenOffice.org Draw OpenOffice Draw txantiloia OpenOffice Draw -malli OpenOffice Draw formur modèle OpenOffice Draw teimpléad OpenOffice Draw modelo de OpenOffice Draw תבנית של OpenOffice Draw OpenOffice Draw sablon Templat OpenOffice Draw Modello OpenOffice Draw OpenOffice Draw テンプレート OpenOffice Draw-ის შაბლონი OpenOffice Draw үлгісі OpenOffice Draw 그림 문서 서식 OpenOffice Draw šablonas OpenOffice Draw veidne OpenOffice Draw-mal OpenOffice.org Draw-sjabloon OpenOffice Draw-mal Szablon rysunku OpenOffice.org Draw Modelo do OpenOffice Draw Șablon OpenOffice Draw шаблон OpenOffice Draw Šablóna OpenOffice Draw Predloga OpenOffice.org Draw Model OpenOffice Draw OpenOffice Draw-mall шаблон малюнку OpenOffice Draw Mẫu bản vẽ Draw của OpenOffice.org OpenOffice.org Draw 绘图模板 OpenOffice Draw 範本 OpenOffice Impress presentation عرض تقديمي Impress المكتب المفتوح OpenOffice Impress sənədi Prezentacyja OpenOffice Impress Презентация — OpenOffice Impress presentació d'OpenOffice Impress Prezentace OpenOffice Impress Cyflwyniad OpenOffice (Impress) OpenOffice Impress-præsentation OpenOffice-Impress-Vorlage παρουσίαση OpenOffice Impress OpenOffice Impress presentation presentación de OpenOffice Impress OpenOffice.org Impress aurkezpena OpenOffice Impress -esitys OpenOffice Impress framløga présentation OpenOffice Impress láithreoireacht OpenOffice Impress presentación de de OpenOffice Impress מצגת של OpenOffice Impress OpenOffice Impress bemutató Presentasi OpenOffice Impress Presentazione OpenOffice Impress OpenOffice Impress プレゼンテーション OpenOffice Impress-ის პრეზენტაცია OpenOffice Impress презентациясы OpenOffice Impress 프리젠테이션 OpenOffice Impress pateiktis OpenOffice Impress prezentācija OpenOffice Impress-presentasjon OpenOffice.org Impress-presentatie OpenOffice Impress-presentasjon Prezentacja OpenOffice.org Impress Apresentação do OpenOffice Impress Prezentare OpenOffice Impress презентация OpenOffice Impress Prezentácia OpenOffice Impress Predstavitev OpenOffice.org Impress Prezantim OpenOffice Impress OpenOffice Impress-presentation презентація OpenOffice Impress Trình diễn Impress của OpenOffice.org OpenOffice.org Impress 演示文稿 OpenOffice Impress 簡報 OpenOffice Impress template قالب Impress المكتب المفتوح Šablon OpenOffice Impress Шаблон за презентации — OpenOffice Impress plantilla d'OpenOffice Impress Šablona OpenOffice Impress OpenOffice Impress-skabelon OpenOffice-Impress-Vorlage πρότυπο OpenOffice Impress OpenOffice Impress template plantilla de OpenOffice Impress OpenOffice Impress txantiloia OpenOffice Impress -malli OpenOffice Impress formur modèle OpenOffice Impress teimpléad OpenOffice Impress modelo de OpenOffice Impress תבנית של OpenOffice Impress OpenOffice Impress sablon Templat OpenOffice Impress Modello OpenOffice Impress OpenOffice Impress テンプレート OpenOffice Impress-ის შაბლონი OpenOffice Impress үлгісі OpenOffice Impress 프리젠테이션 문서 서식 OpenOffice Impress šablonas OpenOffice Impress veidne OpenOffice Impress-mal OpenOffice.org Impress-sjabloon OpenOffice Impress-mal Szablon prezentacji OpenOffice.org Impress Modelo do OpenOffice Impress Șablon OpenOffice Impress шаблон OpenOffice Impress Šablóna OpenOffice Impress Predloga OpenOffice.org Impress Model OpenOffice Impress OpenOffice Impress-mall шаблон презентації OpenOffice Impress Mẫu trình diễn Impress của OpenOffice.org OpenOffice.org Impress 演示文稿模板 OpenOffice Impress 範本 OpenOffice Math formula صيغة Math المكتب المفتوح Formuła OpenOffice Math Формула — OpenOffice Math fórmula d'OpenOffice Math Vzorec OpenOffice Math OpenOffice Math-formel OpenOffice-Math-Formel μαθηματικός τύπος OpenOffice Math OpenOffice Math formula fórmula de OpenOffice Math OpenOffice.org Math formula OpenOffice Math -kaava OpenOffice Math frymil formule OpenOffice Math foirmle OpenOffice Math fórmula de OpenOffice Math נוסחה של OpenOffice Math OpenOffice Math képlet Formula OpenOffice Math Formula OpenOffice Math OpenOffice Math 計算式 OpenOffice Math-ის ფორმულა OpenOffice Math формуласы OpenOffice Math 수식 OpenOffice Math formulė OpenOffice Math formula OpenOffice Math-formel OpenOffice.org Math-formule OpenOffice Math-formel Formuła OpenOffice.org Math Fórmula do OpenOffice Math Formulă OpenOffice Math формула OpenOffice Math Vzorec OpenOffice Math Dokument formule OpenOffice.org Math Formulë OpenOffice Math OpenOffice Math-formel OpenOffice Math formülü формула OpenOffice Math Công thức Math của OpenOffice.org OpenOffice.org Math 公式 OpenOffice Math 公式 OpenOffice Writer document مستند Writer المكتب المفتوح OpenOffice Writer sənədi Dakument OpenOffice Writer Документ — OpenOffice Writer document d'OpenOffice Writer Dokument OpenOffice Writer Dogfen OpenOffice (Writer) OpenOffice Writer-dokument OpenOffice-Writer-Dokument έγγραφο OpenOffice Writer OpenOffice Writer document documento de OpenOffice Writer OpenOffice.org Writer dokumentua OpenOffice Writer -asiakirja OpenOffice Writer skjal document OpenOffice Writer cáipéis OpenOffice Writer documento de OpenOffice Writer מסמך של OpenOffice Writer OpenOffice Writer dokumentum Dokumen OpenOffice Writer Documento OpenOffice Writer OpenOffice Writer ドキュメント OpenOffice Writer-ის დოკუმენტი OpenOffice Writer құжаты OpenOffice Writer 문서 OpenOffice Writer dokumentas OpenOffice Writer dokuments OpenOffice Writer-dokument OpenOffice.org Writer-document OpenOffice Writer-dokument Dokument OpenOffice.org Writer Documento do OpenOffice Writer Document OpenOffice Writer документ OpenOffice Writer Dokument OpenOffice Writer Dokument OpenOffice.org Writer Dokument OpenOffice Writer OpenOffice Writer-dokument OpenOffice Writer belgesi документ OpenOffice Writer Tài liệu Writer của OpenOffice.org OpenOffice.org Writer 文档 OpenOffice Writer 文件 OpenOffice Writer global document مستند المكتب المفتوح Writer العالمي OpenOffice Writer qlobal sənədi Hlabalny dakument OpenOffice Writer Документ - глобален — OpenOffice Writer document global d'OpenOffice Writer Globální dokument OpenOffice Writer Dogfen eang OpenOffice (Writer) OpenOffice Writer-globalt dokument OpenOffice-Writer-Globaldokument παγκόσμιο έγγραφο OpenOffice Writer OpenOffice Writer global document documento global de OpenOffice Writer OpenOffice.org Writer dokumentu globala OpenOffice Writer - yleinen asiakirja OpenOffice Writer heiltøkt skjal document global OpenOffice Writer cáipéis chomhchoiteann OpenOffice Writer documento global de OpenOffice Writer מסמך גלובלי של OpenOffice Writer OpenOffice Writer globális dokumentum Dokumen global OpenOffice Writer Documento globale OpenOffice Writer OpenOffice Writer グローバルドキュメント OpenOffice Writer-ის გლობალური დოკუმენტი OpenOffice Writer негізгі құжаты OpenOffice Writer 글로벌 문서 OpenOffice Writer bendrinis dokumentas OpenOffice Writer globālais dokuments Global OpenOffice Writer globalt dokument OpenOffice.org Writer-globaal-document OpenOffice Writer globalt dokument Globalny dokument OpenOffice.org Writer Documento global do OpenOffice Writer Document global OpenOffice Writer основной документ OpenOffice Writer Globálny dokument OpenOffice Writer Splošni dokument OpenOffice.org Writer Dokument i përgjithshëm OpenOffice Writer OpenOffice Writer-globaldokument загальний документ OpenOffice Writer Tài liệu toàn cục Writer của OpenOffice.org OpenOffice.org Writer 全局文档 OpenOffice Writer 主控文件 OpenOffice Writer template قالب Writer المكتب المفتوح OpenOffice Writer şablonu Šablon OpenOffice Writer Шаблон за документи — OpenOffice Writer plantilla d'OpenOffice Writer Šablona OpenOffice Writer Templed OpenOffice (Writer) OpenOffice Writer-skabelon OpenOffice-Writer-Vorlage πρότυπο OpenOffice Writer OpenOffice Writer template plantilla de OpenOffice Writer OpenOffice Writer txantiloia OpenOffice Writer -malli OpenOffice Writer formur modèle OpenOffice Writer teimpléad OpenOffice Writer modelo de OpenOffice Writer תסנית של OpenOffice Writer OpenOffice Writer sablon Templat OpenOffice Writer Modello OpenOffice Writer OpenOffice Writer ドキュメントテンプレート OpenOffice Writer-ის შაბლონი OpenOffice Writer үлгісі OpenOffice Writer 문서 서식 OpenOffice Writer šablonas OpenOffice Writer veidne Templat OpenOffice Writer OpenOffice Writer-mal OpenOffice.org Writer-sjabloon OpenOffice Writer-mal Szablon dokumentu OpenOffice.org Writer Modelo do OpenOffice Writer Șablon OpenOffice Writer шаблон OpenOffice Writer Šablóna OpenOffice Writer Predloga OpenOffice.org Writer Model OpenOffice Writer OpenOffice Writer-mall шаблон документа OpenOffice Writer Mẫu tài liệu Writer của OpenOffice.org OpenOffice.org Writer 文档模板 OpenOffice Writer 範本 ODT document مستند ODT Dakument ODT Документ — ODT document ODT Dokument ODT ODT-dokument ODT-Dokument έγγραφο ODT ODT document ODT-dokumento documento ODT ODT dokumentua ODT-asiakirja ODT skjal document ODT cáipéis ODT documento ODT מסמך ODT ODT dokument ODT-dokumentum Dokumen ODT Documento ODT ODT ドキュメント ODT დოკუმენტი ODT құжаты ODT 문서 ODT dokumentas ODT dokuments ODT-dokument ODT-document ODT-dokument Dokument ODT Documento ODT Document ODT документ ODT Dokument ODT Dokument ODT Dokument ODT ODT-dokument документ ODT Tài liệu ODT ODT 文档 ODT 文件 ODT OpenDocument Text ODT document (Flat XML) مستند ODT (Flat XML) Документ — ODT (само XML) document ODT (XML pla) Dokument ODT (Flat XML) ODT-dokument (flad XML) ODT-Dokument (Unkomprimiertes XML) έγγραφο ODT (Flat XML) ODT document (Flat XML) documento ODT (XML plano) ODT dokumentua (XML soila) ODT skjal (Flat XML) document ODT (XML plat) cáipéis ODT (XML cothrom) documento ODT (XML plano) מסמך ODT‏ (Flat XML) ODT-dokumentum (egyszerű XML) Dokumen ODT (Flat XML) Documento ODT (XML semplice) ODT ドキュメント (Flat XML) ODT დოკუმენტი (Flat XML) ODT құжаты (Тек XML) ODT 문서 (단일 XML) ODT dokumentas (Flat XML) ODT dokuments (plakans XML) ODT document (Flat XML) Dokument ODT (prosty XML) Documento ODT (Flat XML) Document ODT (XML simplu) документ ODT (простой XML) Dokument ODT (čisté XML) Datoteka dokumenta ODT (nepovezan XML) ODT-dokument (platt XML) документ ODT (Flat XML) ODT 文档(Flat XML) ODT 文件 (Flat XML) FODT OpenDocument Text (Flat XML) ODT template قالب ODT Šablon ODT Шаблон за документи — ODT plantilla ODT Šablona ODT ODT-skabelon ODT-Vorlage πρότυπο ODT ODT template ODT-ŝablono plantilla ODT ODT txantiloia ODT-malli ODT formur modèle ODT teimpléad ODT modelo ODT תבנית ODT ODT predložak ODT-sablon Templat ODT Modello ODT ODT テンプレート ODT დოკუმენტი ODT үлгісі ODT 문서 서식 ODT šablonas ODT veidne ODT-mal ODT-sjabloon ODT-mal Szablon ODT Modelo ODT Șablon ODT шаблон ODT Šablóna ODT Predloga dokumenta ODT Model ODT ODT-mall шаблон ODT Mẫu ODT ODT 模板 ODT 範本 ODT OpenDocument Text OTH template قالب OTH Šablon OTH Шаблон за страници — OTH plantilla OTH Šablona OTH OTH-skabelon OTH-Vorlage πρότυπο OTH OTH template OTH-ŝablono plantilla OTH OTH txantiloia OTH-malli OTH formur modèle OTH teimpléad OTH modelo OTH תבנית OTH OTH predložak OTH-sablon Templat OTH Modello OTH OTH テンプレート OTH შაბლონი OTH үлгісі OTH 문서 서식 OTH šablonas OTH veidne OTH-mal OTH-sjabloon OTH-mal Szablon OTH Modelo OTH Șablon OTH шаблон OTH Šablóna OTH Predloga OTH Model OTH OTH-mall шаблон OTH Mẫu ODH OTH 模板 OTH 範本 OTH OpenDocument HTML ODM document مستند ODM Dakument ODM Документ — ODM document ODM Dokument ODM ODM-dokument ODM-Dokument έγγραφο ODM ODM document ODM-dokumento documento ODM ODM dokumentua ODM-asiakirja ODM skjal document ODM cáipéis ODM documento ODM מסמך ODM ODM dokument ODM-dokumentum Dokumen ODM Documento ODM ODM ドキュメント ODM დოკუმენტი ODM құжаты ODM 문서 ODM dokumentas ODM dokuments ODM-dokument ODM-document ODM-dokument Dokument ODM Documento ODM Document ODM документ ODM Dokument ODM Dokument ODM Dokument ODM ODM-dokument ODM belgesi документ ODM Tài liệu ODM ODM 文档 ODM 文件 ODM OpenDocument Master ODG drawing تصميم ODG Rysunak ODG Чертеж — ODG dibuix ODG Kresba ODG ODG-tegning ODG-Zeichnung σχέδιο ODG ODG drawing ODG-desegnaĵo dibujo ODG ODG marrazkia ODG-piirros ODG tekning dessin ODG líníocht ODG debuxo ODG ציור ODG ODG crtež ODG-rajz Gambar ODG Disegno ODG ODG ドロー ODG-ის ნახაზი ODG суреті ODG 드로잉 ODG piešinys ODG zīmējums ODG-tegning ODG-tekening ODG-teikning Rysunek ODG Desenho ODG Desen ODG изображение ODG Kresba ODG Datoteka risbe ODG Vizatim ODG ODG-teckning малюнок ODG Bản vẽ ODG ODG 绘图 ODG 繪圖 ODG OpenDocument Drawing ODG drawing (Flat XML) رسمة ODG (Flat XML) Чертеж — ODG (само XML) dibuix ODG (XML pla) Kresba ODG (Flat XML) ODG-tegning (flad XML) ODG-Zeichnung (Unkomprimiertes XML) ODG drawing (Flat XML) dibujo ODG (XML plano) ODG marrazkia (XML soila) ODG tekning (Flat XML) dessin ODG (XML plat) líníocht ODG (XML cothrom) debuxo ODB (XML plano) ציור ODG (Flat XML( ODG-rajz (egyszerű XML) Gambar ODG (FLAT XML) Disegno ODG (XML semplice) ODG ドロー (Flat XML) ODG-ის ნახაზი (Flat XML) ODG сызбасы (Тек XML) ODG 드로잉 (단일 XML) ODG piešinys (Flat XML) ODG zīmējums (plakans XML) ODG-tekening (Flat XML) Rysunek ODG (prosty XML) Desenho ODG (Flat XML) Desen ODG (XML simplu) изображение ODG (простой XML) Kresba ODG (čisté XML) Datoteka risbe ODG (nepovezan XML) ODG-teckning (platt XML) малюнок ODG (Flat XML) ODG 绘图(Flat XML) ODG 繪圖 (Flat XML) FODG OpenDocument Drawing (Flat XML) ODG template قالب ODG Šablon ODG Шаблон за чертежи — ODG plantilla ODG Šablona ODG ODG-skabelon ODG-Vorlage πρότυπο ODG ODG template ODG-ŝablono plantilla ODG ODG txantiloia ODG-malli ODG formur modèle ODG teimpléad ODG modelo ODG תבנית ODG ODG predložak ODG-sablon Templat ODG Modello ODG ODG テンプレート ODG-ის შაბლონი ODG үлгісі ODG 문서 서식 ODG šablonas ODG veidne ODG-mal ODG-sjabloon ODG-mal Szablon ODG Modelo ODG Șablon ODG шаблон ODG Šablóna ODG Predloga dokumenta ODG Model ODG ODG-mall шаблон ODG Mẫu ODG ODG 模板 ODG 範本 ODG OpenDocument Drawing ODP presentation عرض تقديمي ODP Prezentacyja ODP Презентация — ODP presentació ODP Prezentace ODP ODP-præsentation ODP-Präsentation παρουσίαση ODP ODP presentation ODP-prezentaĵo presentación ODP ODP aurkezpena ODP-esitys ODP framløga présentation ODP láithreoireacht ODP presentación ODP מצגת ODP ODP prezentacija ODP-prezentáció Presentasi ODP Presentazione ODP ODP プレゼンテーション ODP პრეზენტაცია ODP презентациясы ODP 프리젠테이션 ODP pateiktis ODP prezentācija ODP-presentasjon ODP-presentatie ODP-presentasjon Prezentacja ODP Apresentação ODP Prezentare ODP презентация ODP Prezentácia ODP Predstavitev ODP Prezantim ODP ODP-presentation ODP sunumu презентація ODP Trình diễn ODM ODP 演示文稿 ODP 簡報 ODP OpenDocument Presentation ODP presentation (Flat XML) عرض ODP (Flat XML) Презентация — ODP (само XML) presentació ODP (XML pla) Prezentace ODP (Flat XML) ODP-præsentation (flad XML) ODP-Präsentation (Unkomprimiertes XML) παρουσίαση ODP (Flat XML) ODP presentation (Flat XML) presentación ODP (XML plano) ODP aurkezpena (XML soila) ODP framløga (Flat XML) présentation ODP (XML plat) láithreoireacht ODP (XML cothrom) presentación ODP (XML plano) מצגת ODP‏ (Flat XML) ODP-prezentáció (egyszerű XML) Presentasi ODP (Flat XML) Presentazione ODP (XML semplice) ODP プレゼンテーション (Flat XML) ODP პრეზენტაცია (Flat XML) ODP презентациясы (Тек XML) ODP 프리젠테이션 (단일 XML) ODP pateiktis (Flat XML) ODP prezentācija (plakans XML) ODP presentatie (Flat XML) Prezentacja ODP (prosty XML) Apresentação ODP (Flat XML) Prezentare ODP (XML simplu) презентация ODP (простой XML) Prezentácia ODP (čisté XML) Predstavitev ODP (nepovezan XML) ODP-presentation (platt XML) презентація ODP (Flat XML) ODP 演示文稿(Flat XML) ODP 範本 (Flat XML) FODP OpenDocument Presentation (Flat XML) ODP template قالب ODP Šablon ODP Шаблон за презентации — ODP plantilla ODP Šablona ODP ODP-skabelon ODP-Vorlage πρότυπο ODP ODP template ODP-ŝablono plantilla ODP ODP txantiloia ODP-malli ODP formur modèle ODP teimpléad ODP modelo ODP תבנית ODP ODP predložak ODP-sablon Templat ODP Modello ODP ODP テンプレート ODP შაბლონი ODP үлгісі ODP 문서 서식 ODP šablonas ODP veidne ODP-mal ODP-sjabloon ODP-mal Szablon ODP Modelo ODP Șablon ODP шаблон ODP Šablóna ODP Predloga dokumenta ODP Model ODP ODP-mall шаблон ODP Mẫu ODP ODP 模板 ODP 範本 ODP OpenDocument Presentation ODS spreadsheet جدول ODS Raźlikovy arkuš ODS Таблица — ODS full de càlcul ODS Sešit ODS ODS-regneark ODS-Tabelle φύλλο εργασίας ODS ODS spreadsheet ODS-kalkultabelo hoja de cálculo ODS ODS kalkulu-orria ODS-taulukko ODS rokniark feuille de calcul ODS scarbhileog ODS folla de cálculo ODS גליון נתונים ODS ODS proračunska tablica ODS-táblázat Lembar sebar ODS Foglio di calcolo ODS ODS スプレッドシート ODS ცხრილი ODS электрондық кестесі ODS 스프레드시트 ODS skaičialentė ODS izklājlapa ODS-regneark ODS-rekenblad ODS-rekneark Arkusz ODS Planilha ODS Foaie de calcul ODS электронная таблица ODS Zošit ODS Preglednica ODS Fletë llogaritjesh ODS ODS-kalkylblad ел. таблиця ODS Bảng tính ODS ODS 工作簿 ODS 試算表 ODS OpenDocument Spreadsheet ODS spreadsheet (Flat XML) جدول ODS (Flat XML) Таблица — ODS (само XML) full de càlcul ODS (XML pla) Sešit ODS (Flat XML) ODS-regneark (flad XML) ODS-Tabelle (Unkomprimiertes XML) φύλλο εργασίας ODS (Flat XML) ODS spreadsheet (Flat XML) hoja de cálculo ODS (XML plano) ODS kalkulu-orria (XML soila) ODS rokniark (Flat XML) feuille de calcul ODS (XML plat) scarbhileog ODS (XML cothrom) folla de cálculo ODS (XML plano) גליון נתונים ODS‏ (XML פשוט) ODS-táblázat (egyszerű XML) Lembar sebar ODS (Flat XML) Foglio di calcolo ODS (XML semplice) ODS スプレッドシート (Flat XML) ODS ცხრილი (Flat XML) ODS электрондық кестесі (Тек XML) ODS 스프레드시트 (단일 XML) ODS skaičialentė (Flat XML) ODS izklājlapa (plakans XML) ODS spreadsheet (Flat XML) Arkusz ODS (prosty XML) Planilha ODS (Flat XML) Foaie de calcul ODS (XML simplu) электронная таблица ODS (простой XML) Zošit ODS (čisté XML) Preglednica ODS (nepovezan XML) ODS-kalkylblad (platt XML) ел. таблиця ODS (Flat XML) ODS 工作簿(Flat XML) ODS 試算表 (Flat XML) FODS OpenDocument Spreadsheet (Flat XML) ODS template قالب ODS Šablon ODS Шаблон за таблици — ODS plantilla ODS Šablona ODS ODS-skabelon ODS-Vorlage πρότυπο ODS ODS template ODS-ŝablono plantilla ODS ODS txantiloia ODS-malli ODS formur modèle ODS teimpléad ODS modelo ODS תבנית ODS ODS predložak ODS-sablon Templat ODS Modello ODS ODS テンプレート ODS-ის შაბლონი ODS үлгісі ODS 문서 서식 ODS šablonas ODS veidne ODS-mal ODS-sjabloon ODS-mal Szablon ODS Modelo ODS Șablon ODS шаблон ODS Šablóna ODS Predloga dokumenta ODS Model ODS ODS-mall шаблон ODS Mẫu ODS ODS 模板 ODS 範本 ODS OpenDocument Spreadsheet ODC chart مخطط ODC Dyjahrama ODC Диаграма — ODC diagrama ODC Graf ODC ODC-diagram ODC-Diagramm διάγραμμα ODC ODC chart ODC-diagramo gráfica ODC ODC diagrama ODC-kaavio ODC strikumynd graphique ODC cairt ODC gráfica ODC תו ODC ODC grafikon ODC-táblázat Bagan ODC Grafico ODC ODC チャート ODC диаграммасы ODC 차트 ODC diagrama ODC diagramma ODC-graf ODC-grafiek ODC-diagram Wykres ODC Gráfico ODC Diagramă ODC диаграмма ODC Graf ODC Datoteka grafikona ODC Grafik ODC ODC-diagram діаграма ODC Sơ đồ ODC ODC 图表 ODC 圖表 ODC OpenDocument Chart ODC template قالب ODC Шаблон за диаграми — ODC plantilla ODC Šablona ODC ODC-skabelon ODC-Vorlage πρότυπο ODC ODC template ODC-ŝablono plantilla ODC ODC txantiloia ODC-malli ODC formur modèle ODC teimpléad ODC modelo ODC תבנית ODC ODC predložak ODC-sablon Templat ODC Modello ODC ODC テンプレート ODC შაბლონი ODC үлгісі ODC 문서 서식 ODC šablonas ODC veidne ODC-sjabloon Szablon ODC Modelo ODC Șablon ODC шаблон ODC Šablóna ODC Predloga ODC ODC-mall шаблон ODC Mẫu ODC ODC 模板 ODC 範本 ODC OpenDocument Chart ODF formula صيغة ODF Formuła ODF Формула — ODF fórmula ODF Vzorec ODF ODF-formel ODF-Formel μαθηματικός τύπος ODF ODF formula ODF-formulo fórmula ODF ODF formula ODF-kaava ODF frymil formule ODF foirmle ODF Fórula ODF נוסחת ODF ODF formula ODF-képlet Formula ODF Formula ODF ODF 計算式 ODF-ის ფორმულა ODF формуласы ODF 수식 ODF formulė ODF formula ODF-formel ODF-formule ODF-formel Formuła ODF Fórmula ODF Formulă ODF формула ODF Vzorec ODF Dokument formule ODF Formulë ODF ODF-formel формула ODF Công thức ODF ODF 公式 ODF 公式 ODF OpenDocument Formula ODF template قالب ODF Шаблон за формули — ODF plantilla ODF Šablona ODF ODF-skabelon ODF-Vorlage πρότυπο ODF ODF template ODF-ŝablono plantilla ODF ODF txantiloia ODF-malli ODF formur modèle ODF teimpléad ODF modelo ODF תבנית ODF ODF predložak ODG-sablon Templat ODF Modello ODF ODF テンプレート ODF-ის შაბლონი ODF үлгісі ODF 문서 서식 ODF šablonas ODF veidne ODF-sjabloon Szablon ODF Modelo ODF Șablon ODF шаблон ODF Šablóna ODF Predloga dokumenta ODF ODF-mall шаблон ODF Mẫu ODF ODF 模板 ODF 範本 ODF OpenDocument Formula ODB database قاعدة بيانات ODB Baza źviestak ODB База от данни — ODB base de dades ODB Databáze ODB ODB-database ODB-Datenbank Βάση δεδομένων ODB ODB database ODB-datumbazo base de datos ODB ODB datu-basea ODB-tietokanta ODB dátustovnur base de données ODB bunachar sonraí ODB base de datos ODB בסיס נתונים ODB ODB baza podataka ODB-adatbázis Basis data ODB Database ODB ODB データベース ODB-ის მონაცემთა ბაზა ODB дерекқоры ODB 데이터베이스 ODB duomenų bazė ODB datubāze ODB-database ODB-gegevensbank ODB-database Baza danych ODB Banco de dados ODB Bază de date ODB база данных ODB Databáza ODB Podatkovna zbirka ODB Bazë me të dhëna ODB ODB-databas ODB veritabanı база даних ODB Cơ sở dữ liệu ODB ODB 数据库 ODB 資料庫 ODB OpenDocument Database ODI image صورة ODI Vyjava ODI Изображение — ODI imatge ODI Obrázek ODI ODI-billede ODI-Bild εικόνα ODI ODI image ODI-bildo imagen ODI ODI irudia ODI-kuva ODI mynd image ODI íomhá ODI imaxe ODI תמונת ODI ODI slika ODI-kép Citra ODI Immagine ODI ODI 画像 ODI გამოსახულება ODI суреті ODI 그림 ODI paveikslėlis ODI attēls ODI-bilde ODI-afbeelding ODI-bilete Obraz ODI Imagem ODI Imagine ODI изображение ODI Obrázok ODI Slikovna datoteka ODI Figurë ODI ODI-bild ODI görüntüsü зображення ODI Ảnh ODI ODI 图像 ODI 影像 ODI OpenDocument Image OpenOffice.org extension امتداد OpenOffice.org Pašyreńnie OpenOffice.org Разширение — OpenOffice extensió d'OpenOffice.org Rozšíření OpenOffice.org OpenOffice.org-udvidelse OpenOffice.org-Erweiterung επέκταση OpenOffice.org OpenOffice.org extension extensión de OpenOffice OpenOffice.org luzapena OpenOffice.org-laajennus OpenOffice.org víðkan extension OpenOffice.org eisínteacht OpenOffice.org Extensión de OpenOffice.org הרחבה של OpenOffice.org OpenOffice.org kiterjesztés Ekstensi OpenOffice.org Estensione OpenOffice.org OpenOffice.org 拡張機能 OpenOffice.org-ის გაფართოება OpenOffice.org кеңейтуі OpenOffice.org 확장 OpenOffice.org plėtinys OpenOffice.org paplašinājums OpenOffice.org-uitbreiding OpenOffice Writer-utviding Rozszerzenie OpenOffice.org Extensão do OpenOffice Extensie OpenOffice.org расширение OpenOffice.org Rozšírenie OpenOffice.org Razširitev OpenOffice.org Shtojcë për OpenOffice.org OpenOffice.org-tillägg OpenOffice.org eklentisi розширення OpenOffice.org Phần mở rộng của OpenOffice.org OpenOffice.org 扩展 OpenOffice.org 擴充套件 Android package Пакет — Android paquet d'Android Balíčky systému Android Android-pakke Android-Paket πακέτο Android Android package Android-pakaĵo Paquete de Android Android-paketti paquet Android paquete de Android חבילת אנדרויד Android paket Android csomag Paket Android Pacchetto Android Android パッケージ Android-ის პაკეტი Android дестесі 안드로이드 패키지 Android pakotne Android pakket Pakiet Androida Pacote do Android пакет Android Paket Android Android-paket пакунок Android Android Android 套件 SIS package حزمة SIS Pakunak SIS Пакет — SIS paquet SIS Balíček SIS SIS-pakke SIS-Paket πακέτο SIS SIS package SIS-pakaĵo paquete SIS SIS paketea SIS-paketti SIS pakki paquet SIS pacáiste SIS paquete SIS חבילת SIS SIS paket SIS csomag Paket SIS Pacchetto SIS SIS パッケージ SIS дестесі SIS 패키지 SIS paketas SIS pakotne SIS-pakke SIS-pakket SIS-pakke Pakiet SIS Pacote SIS Pachet SIS пакет SIS Balíček SIS Datoteka paketa SIS Paketë SIS SIS-paket SIS paketi пакунок SIS Gói SIS SIS 软件包 SIS 套件 SIS Symbian Installation File SISX package حزمة SISX Pakunak SISX Пакет — SISX paquet SISX Balíček SISX SISX-pakke SISX-Paket πακέτο SISX SISX package SISX-pakaĵo paquete SISX SISX paketea SISX-paketti SISX pakki paquet SISX pacáiste SISX paquete SISX חבילת SISX SISX paket SISX csomag Paket SISX Pacchetto SISX SISX パッケージ SISX дестесі SISX 패키지 SISX paketas SISX pakotne SISX-pakke SISX-pakket SISX-pakke Pakiet SISX Pacote SISX Pachet SISX пакет SISX Balíček SISX Datoteka paketa SISX Paketë SISX SISX-paket SISX paketi пакунок SISX Gói SISX SISX 软件包 SISX 套件 SIS Symbian Installation File Network Packet Capture Прихванати пакети по мрежата captura de paquets de xarxa Network Packet Capture Netværkspakkeoptegnelse Netzwerk-Paketmitschnitt Σύλληψη Πακέτων Δικτύου Network Packet Capture Caputra de paquete de red capture de paquet réseau Captura de Network Packet לכידה של מנות נתונים ברשת Hálózati csomagelfogás Tangkapan Paket Jaringan Cattura pacchetti rete ネットワークパケットキャプチャー ქსელური პაკეტის ანაბეჭდი ұсталған желілік пакеттер 네트워크 패킷 캡처 Network Packet Capture Network Packet Capture Przechwycenie pakietu sieciowego Pacote de captura de rede захваченные сетевые пакеты Zajem omrežnih paketov перехоплені дані мережевих пакетів 网络包抓取 網路封包捕捉 WordPerfect document مستند WordPerfect WordPerfect sənədi Dakument WordPerfect Документ — WordPerfect document de WordPerfect Dokument WordPerfect Dogfen WordPerfect WordPerfect-dokument WordPerfect-Dokument έγγραφο WordPerfect WordPerfect document WordPerfect-dokumento documento de WordPerfect WordPerfect dokumentua WordPerfect-asiakirja WordPerfect skjal document WordPerfect cáipéis WordPerfect documento de WordPerfect מסמך WordPerfect WordPerfect dokument WordPerfect-dokumentum Dokumen WordPerfect Documento WordPerfect WordPerfect ドキュメント WordPerfect құжаты 워드퍼펙트 문서 WordPerfect dokumentas WordPerfect dokuments Dokumen WordPerfect WordPerfect-dokument WordPerfect-document WordPerfect-dokument Dokument WordPerfect documento do WordPerfect Documento do WordPerfect Document WordPerfect документ WordPerfect Dokument WordPerfect Dokument WordPerfect Dokument WordPerfect WordPerfect документ WordPerfect-dokument документ WordPerfect Tài liệu WordPerfect WordPerfect 文档 WordPerfect 文件 SPSS Portable Data File ملف بيانات SPSS متنقلة Данни — SPSS, преносими fitxer de dades portables SPSS Soubor přenositelných dat SPSS Portabel SPSS-datafil SPSS portable Datendatei φορητό αρχείο δεδομένων SPSS SPSS Portable Data File archivo de datos portable SPSS SPSS datuen fitxategi eramangarria SPSS flytifør dátufíla fichier portable de données SPSS comhad iniompartha sonraí SPSS ficheiro de datos portábel SPSS קובץ מידע נייד SPSS SPSS prenosiva podatkovna datoteka SPSS hordozható adatfájl Berkas Data Portabel SPSS File dati SPSS Portable SPSS ポータブルデータファイル SPSS тасымалы ақпарат файлы SPSS 이동식 데이터 파일 SPSS perkeliamų duomenų failas SPSS pārvietojamu datu datne SPSS Portable Databestand Plik przenośnych danych SPSS Arquivo de Dados Portáteis SPSS Fișier portabil de date SPSS файл переносимых данных SPSS Súbor prenosných dát SPSS Prenosna podatkovna datoteka SPSS Portabel SPSS-datafil файл даних SPSS Portable SPSS 便携式数据文件 SPSS 可攜式資料檔 SPSS Data File ملف بيانات SPSS Данни — SPSS fitxer de dades SPSS Datový soubor SPSS SPSS-datafil SPSS-Datendatei αρχείο δεδομένων SPSS SPSS Data File archivo de datos SPSS SPSS datuen fitxategia SPSS dátufíla fichier de données SPSS comhad sonraí SPSS ficheiro de datos SPSS קובץ מידע SPSS SPSS podatkovna datoteka SPSS adatfájl Berkas Data SPSS File dati SPSS SPSS データファイル SPSS ақпарат файлы SPSS 데이터 파일 SPSS duomenų failas SPSS datu datne SPSS Databstand Plik danych SPSS Arquivo de dados SPSS Fișier date SPSS файл данных SPSS Dátový súbor SPSS Podatkovna datoteka SPSS SPSS-datafil файл даних SPSS SPSS 数据文件 SPSS 資料檔 XBEL bookmarks علامات XBEL Zakładki XBEL Отметки — XBEL llista d'adreces d'interès XBEL Záložky XBEL XBEL-bogmærker XBEL-Lesezeichen σελιδοδείκτες XBEL XBEL bookmarks XBEL-legosignoj marcadores XBEL XBEL laster-markak XBEL-kirjanmerkit XBEL bókamerki marque-pages XBEL leabharmharcanna XBEL Marcadores XBEL סמניית XBEL XBEL knjižne oznake XBEL-könyvjelzők Bookmark XBEL Segnalibri XBEL XBEL ブックマーク XBEL бетбелгілері XBEL 책갈피 XBEL žymelės XBEL grāmatzīmes Tandabuku XBEL XBEL-bokmerker XBEL-bladwijzers XBEL-bokmerker Zakładki XBEL marcadores XBEL Marcadores do XBEL Semne de carte XBEL закладки XBEL Záložky XBEL Datoteka zaznamkov XBEL Libërshënues XBEL XBEL обележивачи XBEL-bokmärken закладки XBEL Liên kết đã lưu XBEL XBEL 书签 XBEL 格式書籤 XBEL XML Bookmark Exchange Language 7-zip archive أرشيف 7-zip Archiŭ 7-zip Архив — 7-zip arxiu 7-zip Archiv 7-zip 7-zip-arkiv 7zip-Archiv αρχείο 7-zip 7-zip archive 7z-arkivo archivador 7-zip 7-zip artxiboa 7-zip-arkisto 7-zip skjalasavn archive 7-zip cartlann 7-zip arquivo 7-zip ארכיון 7-zip 7-zip arhiva 7-zip archívum Arsip 7-zip Archivio 7-zip 7-zip アーカイブ 7-zip არქივი 7-zip архиві 7-ZIP 압축 파일 7-zip archyvas 7-zip arhīvs 7-zip-arkiv 7-zip-archief 7-zip-arkiv Archiwum 7-zip Pacote 7-zip Arhivă 7-zip архив 7-zip Archív 7-zip Datoteka arhiva 7-zip Arkiv 7-zip 7-zip-arkiv 7-Zip arşivi архів 7-zip Kho nén 7-zip 7-zip 归档文件 7-zip 封存檔 AbiWord document مستند آبي وورد Dakument AbiWord Документ — AbiWord document d'AbiWord Dokument AbiWord AbiWord-dokument AbiWord-Dokument έγγραφο AbiWord AbiWord document AbiWord-dokumento documento de Abiword AbiWord dokumentua AbiWord-asiakirja AbiWord skjal document AbiWord cáipéis AbiWord documento de AbiWord מסמך AbiWord AbiWord dokument AbiWord-dokumentum Dokumen AbiWord Documento AbiWord AbiWord ドキュメント AbiWord-ის დოკუმენტი AbiWord құжаты AbiWord 문서 AbiWord dokumentas AbiWord dokuments Dokumen AbiWord AbiWord-dokument AbiWord-document AbiWord-dokument Dokument AbiWord documento AbiWord Documento do AbiWord Document AbiWord документ AbiWord Dokument AbiWord Dokument AbiWord Dokument AbiWord Абиворд документ AbiWord-dokument AbiWord belgesi документ AbiWord Tài liệu AbiWord AbiWord 文档 AbiWord 文件 CD image cuesheet صفيحة صورة الـCD جديلة Infarmacyjny arkuš vyjavy CD Описание на изображение на CD «cuesheet» d'imatge de CD Rozvržení stop obrazu CD Cd-aftrykscuesheet CD-Abbild-Cuesheet CD image cuesheet cue sheet de una imagen de CD CD irudiaren CUE orria CD-vedos cuesheet index de pistes de CD bileog chiúáil íomhá CD cue sheet dunha imaxe de CD גליון נתונים לתמונת דיסק CD kép cuesheet Citra cuesheet CD Cuesheet immagine CD CD イメージキューシート CD бейнесінің құрама кестесі CD 이미지 큐시트 CD atvaizdžio aprašas CD attēla rindulapa Filliste for CD-avtrykk CD-inhoudsopgave CD-bilete-indeksfil Obraz cuesheet płyty CD Índice de Imagem de CD Imagine CD cuesheet таблица содержания образа CD Rozvrhnutie stôp obrazu CD Datoteka razpredelnice odtisa CD cue Cuesheet imazhi CD Indexblad för cd-avbild таблиця CUE образу CD Tờ tín hiệu báo ảnh CD CD 映像标记文件 CD 映像指示表 Lotus AmiPro document مستند Lotus AmiPro Lotus AmiPro sənədi Dakument Lotus AmiPro Документ — Lotus AmiPro document de Lotus AmiPro Dokument Lotus AmiPro Dogfen Lotus AmiPro Lotus AmiPro-dokument Lotus-AmiPro-Dokument έγγραφο Lotus AmiPro Lotus AmiPro document dokumento de Lotus AmiPro documento de Lotus AmiPro Lotus AmiPro dokumentua Lotus AmiPro -asiakirja Lotus AmiPro skjal document Lotus AmiPro cáipéis Lotus AmiPro documento de Lotus AmiPro מסמך של Lotus AmiPro Lotus AmiPro dokument Lotus AmiPro-dokumentum Dokumen Lotus AmiPro Documento Lotus AmiPro Lotus AmiPro ドキュメント Lotus AmiPro құжаты Lotus AmiPro 문서 Lotus AmiPro dokumentas Lotus AmiPro dokuments Dokumen Lotus AmiPro Lotus AmiPro-dokument Lotus AmiPro-document Lotus AmiPro-dokument Dokument Lotus AmiPro documento Lotus AmiPro Documento do Lotus AmiPro Document Lotus AmiPro документ Lotus AmiPro Dokument Lotus AmiPro Dokument Lotus AmiPro Dokument Lotus AmiPro Лотус АмиПро документ Lotus AmiPro-dokument документ Lotus AmiPro Tài liệu Lotus AmiPro Lotus AmiPro 文档 Lotus AmiPro 文件 AportisDoc document مستند AportisDoc Документ — AportisDoc document AportisDoc Dokument AportisDoc AportisDoc-dokument AportisDoc-Dokument έγγραφο AportisDoc AportisDoc document AportisDoc-dokumento documento AportisDoc AportisDoc dokumentua AportisDoc-asiakirja AportisDoc skjal document AportisDoc cáipéis AportisDoc documento de AportiDoc מסמך AportisDoc AportisDoc dokument AportisDoc-dokumentum Dokumen AportisDoc Documento AportisDoc AportisDoc ドキュメント AportisDoc-ის დოკუმენტი AportisDoc құжаты AportisDoc 문서 AportisDoc dokumentas AportisDoc dokuments AportisDoc-document Dokument AportisDoc Documento do AportisDoc Document AportisDoc документ AportisDoc Dokument AportisDoc Dokument AportisDoc AportisDoc-dokument AportisDoc belgesi документ AportisDoc Tài liệu AportisDoc AportisDoc 文档 AportisDoc 文件 Applix Spreadsheets spreadsheet جداول بيانات Applix Raźlikovy arkuš Applix Spreadsheets Таблица — Applix Spreadsheets full de càlcul d'Applix Spreadsheets Sešit Applix Spreadsheets Applix Spreadsheets-regneark Applix-Spreadsheets-Tabelle λογιστικό φύλλο Applix Spreadsheets Applix Spreadsheets spreadsheet sterntabelo de Applix Spreadsheets hoja de cálculo de Applix Spreadsheets Applix Spreadsheets kalkulu-orria Applix Spreadsheets -taulukko Applix Spreadsheets rokniark feuille de calcul Applix scarbhileog Applix Spreadsheets folla de cálculo de Applix גליון נתונים של Applix Spreadsheets Applix Spreadsheets proračunska tablica Applix Spreadsheets-munkafüzet Lembar sebar Applix Spreadsheets Foglio di calcolo Applix Spreadsheets Applix Spreadsheets スプレッドシート Applix Spreadsheets-ის ცხრილი Applix Spreadsheets электрондық кестесі Applix 스프레드시트 Applix Spreadsheets skaičialentė Applix Spreadsheets izklājlapa Hamparan Applix Spreadsheets Applix Spreadsheets-regneark Applix Spreadsheets-rekenblad Applix Spreadsheets-dokument Arkusz Applix Spreadsheets folha de cálculo Applix Spreadsheets Planilha do Applix Spreadsheets Foaie de calcul Applix электронная таблица Applix Spreadsheets Zošit Applix Spreadsheets Razpredelnica Applix Spreadsheets Fletë llogaritjesh Applix Spreadsheets Applix табеларни документ Applix Spreadsheets-kalkylblad ел. таблиця Applix Spreadsheets Bảng tính Applix Spreadsheets Applix Spreadsheets 工作簿 Applix Spreadsheets 試算表 Applix Words document مستند كلمات Applix Applix Words sənədi Dakument Applix Words Документ — Applix Words document d'Applix Words Dokument Applix Words Dogfen Applix Words Applix Words-dokument Applix-Words-Dokument έγγραφο Applix Words Applix Words document dokumento de Applix Words documento de Applix Words Applix Words dokumentua Applix Words -asiakirja Applix Words skjal document Applix Words cáipéis Applix Words documento de Applix Words מסמך של Applix Words Applix Words dokument Applix Words-dokumentum Dokumen Applix Words Documento Applix Words Applix Words ドキュメント Applix Words-ის დოკუმენტი Applix Words құжаты Applix Words 문서 Applix Words dokumentas Applix Words dokuments Dokumen Perkataan Applix Applix Words-dokument Applix Words-document Applix Words dokument Dokument Applix Words documento Applix Words Documento do Applix Words Document Applix Words документ Applix Words Dokument Applix Words Dokument Applix Words Dokument Applix Words Applix Words документ Applix Words-dokument документ Applix Words Tài liệu Applix Words Applix Words 文档 Applix Words 文件 ARC archive أرشيف ARC Archiŭ ARC Архив — ARC arxiu ARC Archiv ARC ARC-arkiv ARC-Archiv αρχείο ARC ARC archive ARC-arkivo archivador ARC ARC artxiboa ARC-arkisto ARC skjalasavn archive ARC cartlann ARC arquivo ARC ארכיון ARC ARC arhiva ARC-archívum Arsip ARC Archivio ARC ARC アーカイブ ARC არქივი ARC архиві ARC 압축 파일 ARC archyvas ARC arhīvs ARC-arkiv ARC-archief ARC-arkiv Archiwum ARC Pacote ARC Arhivă ARC архив ARC Archív ARC Datoteka arhiva ARC Arkiv ARC ARC-arkiv ARC arşivi архів ARC Kho nén ARC ARC 归档文件 ARC 封存檔 AR archive أرشيف AR Archiŭ AR Архив — AR arxiu AR Archiv AR AR-arkiv AR-Archiv σρχείο AR AR archive AR-arkivo archivador AR AR artxiboa AR-arkisto AR skjalasavn archive AR cartlann AR arquivo AR ארכיון AR AR arhiva AR-archívum Arsip AR Archivio AR AR アーカイブ AR არქივი AR архиві AR 묶음 파일 AR archyvas AR arhīvs Arkib AR AR-arkiv AR-archief AR-arkiv Archiwum AR arquivo AR Pacote AR Arhivă AR архив AR Archív AR Datoteka arhiva AR Arkiv AR АР архива AR-arkiv AR arşivi архів AR Kho nén AR AR 归档文件 AR 封存檔 ARJ archive أرشيف ARJ ARJ arxivi Archiŭ ARJ Архив — ARJ arxiu ARJ Archiv ARJ Archif ARJ ARJ-arkiv ARJ-Archiv αρχείο ARJ ARJ archive ARJ-arkivo archivador ARJ ARJ artxiboa ARJ-arkisto ARJ skjalasavn archive ARJ cartlann ARJ arquivo ARJ ארכיון ARJ ARJ arhiva ARJ-archívum Arsip ARJ Archivio ARJ ARJ アーカイブ ARJ არქივი ARJ архиві ARJ 압축 파일 ARJ archyvas ARJ arhīvs Arkib ARJ ARJ-arkiv ARJ-archief ARJ-arkiv Archiwum ARJ arquivo ARJ Pacote ARJ Arhivă ARJ архив ARJ Archív ARJ Datoteka arhiva ARJ Arkiv ARJ ARJ архива ARJ-arkiv ARJ arşivi архів ARJ Kho nén ARJ ARJ 归档文件 ARJ 封存檔 ARJ Archived by Robert Jung ASP page صفحة ASP Staronka ASP Страница — ASP pàgina ASP Stránka ASP ASP-side ASP-Seite σελίδα ASP ASP page ASP-paĝo página ASP ASP orria ASP-sivu ASP síða page ASP leathanach ASP páxina ASP עמוד ASP ASP stranica ASP oldal Halaman ASP Pagina ASP ASP ページ ASP გვერდი ASP парағы ASP 페이지 ASP puslapis ASP lapa ASP-side ASP-pagina ASP-side Strona ASP Página ASP Pagină ASP страница ASP Stránka ASP Datoteka spletne strani ASP Faqe ASP ASP-sida сторінка ASP Trang ASP ASP 页面 ASP 頁面 ASP Active Server Page AWK script سكربت AWK AWK skripti Skrypt AWK Скрипт — AWK script AWK Skript AWK Sgript AWK AWK-program AWK-Skript πρόγραμμα εντολών AWK AWK script AWK-skripto script en AWK AWK script-a AWK-komentotiedosto AWK boðrøð script AWK script AWK script de AWK תסריט AWK AWK skripta AWK-parancsfájl Skrip AWK Script AWK AWK スクリプト AWK სცენარი AWK сценарийі AWK 스크립트 AWK scenarijus AWK skripts Skrip AWK AWK-skript AWK-script WAK-skript Skrypt AWK 'script' AWK Script AWK Script AWK сценарий AWK Skript AWK Skriptna datoteka AWK Script AWK AWK скрипта AWK-skript AWK betiği скрипт AWK Văn lệnh AWK AWK 脚本 AWK 指令稿 BCPIO document مستند BCPIO BCPIO sənədi Dakument BCPIO Документ — BCPIO document BCPIO Dokument BCPIO Dogfen BCPIO BCPIO-dokument BCPIO-Dokument έγγραφο BCPIO BCPIO document BCPIO-dokumento documento BCPIO BCPIO dokumentua BCPIO-asiakirja BCPIO skjal document BCPIO cáipéis BCPIO documento BCPIO מסמך של BCPO BCPIO dokument BCPIO-dokumentum Dokumen BCPIO Documento BCPIO BCPIO ドキュメント BCPIO-ის დოკუმენტი BCPIO құжаты BCPIO 문서 BCPIO dokumentas BCPIO dokuments Dokumen BCPIO BCPIO-dokument BCPIO-document BCPIO-dokument Dokument BCPIO documento BCPIO Documento BCPIO Document BCPIO документ BCPIO Dokument BCPIO Dokument BCPIO Dokument BCPIO BCPIO документ BCPIO-dokument BCPIO belgesi документ BCPIO Tài liệu BCPIO BCPIO 文档 BCPIO 文件 BCPIO Binary CPIO BitTorrent seed file ملف باذر البت تورنت BitTorrent seed faylı Fajł krynicy BitTorrent Файл-източник — BitTorrent fitxer llavor de BitTorrent Soubor BitTorrent Ffeil hadu BitTorrent BitTorrent-frøfil BitTorrent-Seed-Datei αρχείο BitTorrent seed BitTorrent seed file BitTorrent-semdosiero archivo semilla de BitTorrent BitTorrent hazi-fitxategia BitTorrent-siementiedosto BitTorrent seed fíla fichier graine BitTorrent comhad síl BitTorrent ficheiro de orixe BitTorrent קובץ זריעה של BitTorrent BitTorrent-magfájl Berkas benih BitTorrent File seed BitTorrent BitTorrent シードファイル BitTorrent көз файлы 비트토렌트 시드 파일 BitTorrent šaltinio failas BitTorrent avota datne Fail seed BitTorrent Fil med utgangsverdi for BitTorrent BitTorrent-bestand Nedlastingsfil for BitTorrent Plik ziarna BitTorrent ficheiro de origem BitTorrent Arquivo semente BitTorrent Fișier sursă-completă BitTorrent файл источника BitTorrent Súbor BitTorrent Datoteka sejanja BitTorrent File bazë BitTorrent Датотека са БитТорентовим полазиштима BitTorrent-distributionsfil файл поширення BitTorrent Tải tập hạt BitTorrent BitTorrent 种子文件 BitTorrent 種子檔 Blender scene مشهد بلندر Scena Blender Сцена — Blender escena Blender Scéna Blender Blenderscene Blender-Szene σκηνή Blender Blender scene Blender-sceno escena de Blender Blender-eko fitxategia Blender-näkymä Blender leikmynd scène Blender radharc Blender escena de Blender סצנת Blender Blender scena Blender-jelenet Scene Blender Scena Blender Blender シーン Blender-ის სცენა Blender сахнасы Blender 장면 Blender scena Blender aina Babak Blender Blender-scene Blender-scène Blender-scene Scena programu Blender cenário Blender Cena do Blender Scenă Blender сцена Blender Scéna Blender Datoteka scene Blender Skenë Blender Блендер сцена Blender-scen сцена Blender Cảnh Blender Blender 场景 Blender 場景 TeX DVI document (bzip-compressed) مستند TeX DVI (مضغوط-bzip) Dakument TeX DVI (bzip-skampresavany) Документ — TeX DVI, компресиран с bzip document TeX DVI (comprimit amb bzip) Dokument TeX DVI (komprimovaný pomocí bzip) TeX DVI-dokument (bzip-komprimeret) TeX-DVI-Dokument (bzip-komprimiert) αρχείο TeX DVI (συμπιεσμένο με bzip) TeX DVI document (bzip-compressed) documento DVI de TeX (comprimido con bzip) TeX DVI dokumentua (bzip-ekin konprimitua) TeX DVI -asiakirja (bzip-pakattu) TeX DVI skjal (bzip-stappað) document DVI TeX (compressé bzip) cáipéis DVI TeX (comhbhrúite le bzip) documento DVI de TeX (comprimido con bzip) מסמך מסוג TeX DVI (מכווץ ע"י bzip) TeX DVI dokument (komprimiran bzip-om) TeX DVI dokumentum (bzip-pel tömörítve) Dokumen TeX DVI (terkompresi bzip) Documento TeX DVI (compresso con bzip) Tex DVI ドキュメント (bzip 圧縮) TeX DVI құжаты (bzip-пен сығылған) TeX DVI 문서 (BZIP 압축) TeX DVI dokumentas (suglaudintas su bzip) TeX DVI dokuments (saspiests ar bzip) TeX DVI-dokument (bzip-komprimert) TeX DVI-document (ingepakt met bzip) TeX DVI-dokument (pakka med bzip) Dokument TeX DVI (kompresja bzip) Documento DVI TeX (compactado com bzip) Document TeX DVI (comprimat bzip) документ TeX DVI (сжатый bzip) Dokument TeX DVI (komprimovaný pomocou bzip) Dokument TeX DVI (stisnjen z bzip) Dokument Tex DVI (i kompresuar me bzip) TeX DVI-dokument (bzip-komprimerat) документ TeX DVI (стиснений bzip) Tài liệu DVI TeX (đã nén bzip) TeX DVI 文档(gzip 压缩) TeX DVI 文件 (bzip 格式壓縮) Bzip archive أرشيف Bzip Archiŭ bzip Архив — bzip arxiu bzip Archiv bzip Bzip-arkiv Bzip-Archiv συμπιεσμένο αρχείο Bzip Bzip archive Bzip-arkivo archivador Bzip Bzip artxiboa Bzip-arkisto Bzip skjalasavn archive bzip cartlann Bzip arquivo Bzip ארכיון Bzip Bzip arhiva Bzip archívum Arsip Bzip Archivio bzip Bzip アーカイブ Bzip არქივი Bzip архиві BZIP 압축 파일 Bzip archyvas Bzip arhīvs Bzip-arkiv Bzip-archief Bzip-arkiv Archiwum bzip Pacote Bzip Arhivă Bzip архив BZIP Archív bzip Datoteka arhiva Bzip Arkiv bzip Bzip-arkiv Bzip arşivi архів bzip Kho nén bzip bzip 归档文件 Bzip 封存檔 Tar archive (bzip-compressed) أرشيف Tar (مضغوط-bzip) Archiŭ tar (bzip-skampresavany) Архив — tar, компресиран с bzip arxiu tar (comprimit amb bzip) Archiv tar (komprimovaný pomocí bzip) Tar-arkiv (bzip-komprimeret) Tar-Archiv (bzip-komprimiert) αρχείο Tar (συμπιεσμένο με bzip) Tar archive (bzip-compressed) archivador Tar (comprimido con bzip) Tar artxiboa (bzip-ekin konprimitua) Tar-arkisto (bzip-pakattu) Tar skjalasavn (bzip-stappað) archive tar (compressée bzip) cartlann Tar (comhbhrúite le bzip) arquivo Tar (comprimido con bzip) ארכיון Tar (מכווץ ע"י bzip) Tar arhiva (komprimirana bzip-om) Tar archívum (bzip-pel tömörítve) Arsip Tar (terkompresi bzip) Archivio tar (compresso con bzip) Tar アーカイブ (bzip 圧縮) Tar архиві (bzip-пен сығылған) TAR 묶음 파일 (BZIP 압축) Tar archyvas (suglaudintas su bzip) Tar arhīvs (saspiests ar bzip) Tar-arkiv (bzip-komprimert) Tar-archief (ingepakt met bzip) Tar-arkiv (pakka med bzip) Archiwum tar (kompresja bzip) Pacote tar (compactado com bzip) Arhivă Tar (comprimată bzip) архив TAR (сжатый BZIP) Archív tar (komprimovaný pomocou bzip) Datoteka arhiva Tar (stisnjen z bzip) Arkiv tar (i kompresuar me bzip) Tar-arkiv (bzip-komprimerat) архів tar (стиснений bzip) Kho nén tar (đã nén bzip) Tar 归档文件(bzip 压缩) Tar 封存檔 (bzip 格式壓縮) PDF document (bzip-compressed) مستند PDF (مضغوط-bzip) Dakument PDF (bzip-skampresavany) Документ — PDF, компресиран с bzip document PDF (comprimit amb bzip) Dokument PDF (komprimovaný pomocí bzip) PDF-dokument (bzip-komprimeret) PDF-Dokument (bzip-komprimiert) έγγραφο PDF (συμπιεσμένο με bzip) PDF document (bzip-compressed) documento PDF (comprimido con bzip) PostScript dokumentua (bzip-ekin konprimitua) PDF-asiakirja (bzip-pakattu) PDF skjal (bzip-stappað) document PDF (compressé bzip) cáipéis PDF (comhbhrúite le bzip) documento PDF (comprimido en bzip) מסמך PDF (מכווץ ע"י bzip) PDF dokumentum (bzip-tömörítésű) Dokumen PDF (terkompresi bzip) Documento PDF (compresso con bzip) PDF ドキュメント (bzip 圧縮) PDF құжаты (bzip-пен сығылған) PDF 문서 (BZIP 압축) PDF dokumentas (suglaudintas su bzip) PDF dokuments (saspiests ar bzip) PDF-dokument (bzip-komprimert) PDF-document (ingepakt met bzip) PDF-dokument (pakka med bzip) Dokument PDF (kompresja bzip) Documento PDF (compactado com bzip) Document PDF (comprimat bzip) документ PDF (сжатый bzip) Dokument PDF (komprimovaný pomocou bzip) Dokument PDF (stisnjen z bzip) Dokument PDF (i kompresuar me bzip) PDF-dokument (bzip-komprimerat) PDF belgesi (bzip ile sıkıştırılmış) документ PDF (стиснений bzip) Tài liệu PDF (đã nén bzip) PDF 文档(bzip 压缩) PDF 文件 (bzip 格式壓縮) PostScript document (bzip-compressed) مستند PostScript (مضغوط-bzip) Dakument PostScript (bzip-skampresavany) Документ — PostScript, компресиран с bzip document PostScript (comprimit amb bzip) Dokument PostScript (komprimovaný pomocí bzip) PostScript-dokument (bzip-komprimeret) PostScript-Dokument (bzip-komprimiert) έγγραφο PostScript (συμπιεσμένο με bzip) PostScript document (bzip-compressed) documento PostScript (comprimido con bzip) PostScript dokumentua (bzip-ekin konprimitua) PostScript-asiakirja (bzip-pakattu) PostScript skjal (bzip-stappað) document PostScript (compressé bzip) cáipéis PostScript (comhbhrúite le bzip) documento PostScript (comprimido con bzip) מסמך PostDcript (מכווץ ע"י bzip) PostScript dokumentum (bzip-tömörítésű) Dokumen PostScript (terkompresi bzip) Documento PostScript (compresso con bzip) PostScript ドキュメント (bzip 圧縮) PostScript құжаты (bzip-пен сығылған) 포스트스크립트 문서 (BZIP 압축) PostScript dokumentas (suglaudintas su bzip) PostScript dokuments (saspiests ar bzip) PostScript-dokument (bzip-komprimert) PostScript-document (ingepakt met bzip) PostScript-dokument (pakka med bzip) Dokument Postscript (kompresja bzip) Documento PostScript (compactado com bzip) Document PostScript (comprimat bzip) документ PostScript (сжатый bzip) Dokument PostScript (komprimovaný pomocou bzip) Dokument PostScript (stisnjen z bzip) Dokument PostScript (i kompresuar me bzip) Postscript-dokument (bzip-komprimerat) документ PostScript (стиснене bzip) Tài liệu PostScript (đã nén bzip) PostScript 文档(bzip 压缩) PostScript 文件 (bzip 格式壓縮) comic book archive أرشيف comic book archiŭ komiksaŭ Архив — комикси arxiu comic book Archiv knihy komiksů comic book-arkiv Comic-Book-Archiv συμπιεσμένο αρχείο κόμικ comic book archive archivador de libro de cómic komiki artxiboa sarjakuva-arkisto teknisøgubóka skjalasavn archive Comic Book cartlann chartúin ficheiro de libro de banda deseñada ארכיון ספר קומי képregényarchívum arsip buku komik Archivio comic book コミックブックアーカイブ комикстар архиві 만화책 압축 파일 komiksų knygos archyvas komiksu grāmatas arhīvs Tegneseriearkiv stripboek-archief teikneseriearkiv Archiwum komiksu Pacote de livro de revista em quadrinhos arhivă benzi desenate архив комиксов Archív knihy komiksov Datoteka arhiva stripov Arkiv comic book serietidningsarkiv архів коміксів Kho nén sách tranh chuyện vui Comic Book 归档文件 漫畫書封存檔 comic book archive أرشيف comic book archiŭ komiksaŭ Архив — комикси arxiu comic book Archiv knihy komiksů comic book-arkiv Comic-Book-Archiv συμπιεσμένο αρχείο κόμικ comic book archive archivador de libro de cómic komiki artxiboa sarjakuva-arkisto teknisøgubóka skjalasavn archive Comic Book cartlann chartúin ficheiro de libro de banda deseñada ארכיון ספר קומי képregényarchívum arsip buku komik Archivio comic book コミックブックアーカイブ комикстар архиві 만화책 압축 파일 komiksų knygos archyvas komiksu grāmatas arhīvs Tegneseriearkiv stripboek-archief teikneseriearkiv Archiwum komiksu Pacote de livro de revista em quadrinhos arhivă benzi desenate архив комиксов Archív knihy komiksov Datoteka arhiva stripov Arkiv comic book serietidningsarkiv архів коміксів Kho nén sách tranh chuyện vui Comic Book 归档文件 漫畫書封存檔 comic book archive أرشيف comic book archiŭ komiksaŭ Архив — комикси arxiu comic book Archiv knihy komiksů comic book-arkiv Comic-Book-Archiv συμπιεσμένο αρχείο κόμικ comic book archive archivador de libro de cómic komiki artxiboa sarjakuva-arkisto teknisøgubóka skjalasavn archive Comic Book cartlann chartúin ficheiro de libro de banda deseñada ארכיון ספר קומי képregényarchívum arsip buku komik Archivio comic book コミックブックアーカイブ комикстар архиві 만화책 압축 파일 komiksų knygos archyvas komiksu grāmatas arhīvs Tegneseriearkiv stripboek-archief teikneseriearkiv Archiwum komiksu Pacote de livro de revista em quadrinhos arhivă benzi desenate архив комиксов Archív knihy komiksov Datoteka arhiva stripov Arkiv comic book serietidningsarkiv архів коміксів Kho nén sách tranh chuyện vui Comic Book 归档文件 漫畫書封存檔 comic book archive أرشيف comic book archiŭ komiksaŭ Архив — комикси arxiu comic book Archiv knihy komiksů comic book-arkiv Comic-Book-Archiv συμπιεσμένο αρχείο κόμικ comic book archive archivador de libro de cómic komiki artxiboa sarjakuva-arkisto teknisøgubóka skjalasavn archive Comic Book cartlann chartúin ficheiro de libro de banda deseñada ארכיון ספר קומי képregényarchívum arsip buku komik Archivio comic book コミックブックアーカイブ комикстар архиві 만화책 압축 파일 komiksų knygos archyvas komiksu grāmatas arhīvs Tegneseriearkiv stripboek-archief teikneseriearkiv Archiwum komiksu Pacote de livro de revista em quadrinhos arhivă benzi desenate архив комиксов Archív knihy komiksov Datoteka arhiva stripov Arkiv comic book serietidningsarkiv архів коміксів Kho nén sách tranh chuyện vui Comic Book 归档文件 漫畫書封存檔 Lrzip archive أرشيف Lrzip Архив — lrzip arxiu lrzip Archiv Lrzip Lrzip-arkiv Lrzip-Archiv συμπιεσμένο αρχείο Lrzip Lrzip archive Lrzip-arkivo archivador Lrzip Lrzip-arkisto Lrzip skjalasavn archive lrzip cartlann Lrzip arquivo Lrzip ארכיון Lrzip Lrzip arhiva Lrzip archívum Arsip Lrzip Archivio Lrzip Lrzip アーカイブ Lrzip архиві LRZIP 압축 파일 Lrzip archyvas Lrzip arhīvs Lrzip archief Archiwum lrzip Pacote Lrzip Arhivă Lrzip архив LRZIP Archív Lrzip Datoteka arhiva Lrzip Lrzip-arkiv архів lrzip Lrzip 归档文件 Lrzip 封存檔 Tar archive (lrzip-compressed) أرشيف Tar (مضغوط-lrzip) Архив — tar, компресиран с lrzip arxiu tar (comprimit amb lrzip) Archiv tar (komprimovaný pomocí lrzip) Tar-arkiv (lrzip-komprimeret) Tar-Archiv (lrzip-komprimiert) αρχείο Tar (συμπιεσμένο με lrzip) Tar archive (lrzip-compressed) archivador Tar (comprimido con lrzip) Tar-arkisto (lrzip-pakattu) Tar skjalasavn (lrzip-stappað) archive tar (compressée lrzip) cartlann Tar (comhbhrúite le lrzip) arquivo Tar (comprimido con lrzip) ארכיון Tar (מכווץ ע"י lrzip) Tar arhiva (komprimirana lrzip-om) Tar archívum (lrzip-pel tömörítve) Arsip Tar (terkompresi lrzip) Archivio tar (compresso con lrzip) Tar アーカイブ (lrzip 圧縮) Tar архиві (lrzip-пен сығылған) TAR 묶음 파일 (LRZIP 압축) Tar archyvas (suglaudintas su lrzip) Tar arhīvs (saspiests ar lrzip) Tar archief (lrzip-compressed) Archiwum tar (kompresja lrzip) Pacote tar (compactado com lrzip) Arhivă Tar (comprimată lrzip) архив TAR (сжатый LRZIP) Archív tar (komprimovaný pomocou lrzip) Datoteka arhiva Tar (stisnjen z lrzip) Tar-arkiv (lrzip-komprimerat) архів tar (стиснений lrzip) Tar 归档文件 (lrzip 压缩) Tar 封存檔 (lrzip 格式壓縮) Apple disk image Диск — Apple imatge de disc d'Apple Obraz disku Apple Apple-diskaftryk Apple-Datenträgerabbild εικόνα δίσκου Apple Apple disk image imagen de disco de Apple Apple-levytiedosto image disque Apple imaxe de disco de Appl תמונת כונן Apple Apple snimka diska Apple lemezkép Image disk Apple Immagine disco Apple Apple ディスクイメージ Apple-ის სადისკო გამოსახულება Apple диск бейнесі 애플 디스크 이미지 Apple diska attēls Apple disk image Obraz dysku Apple Imagem de disco Apple образ диска Apple Mac OS X Obraz disku Apple Odtis diska Apple Apple-skivavbild Apple disk görüntüsü образ диска Apple Apple 磁盘镜像 Apple 磁碟映像 Raw disk image Raw disk image (XZ-compressed) raw CD image صورة CD خامة suvoraja vyjava CD Изображение — raw CD imatge de CD en cru Surový obraz CD rå cd-aftryk CD-Roh-Abbild εικόνα περιεχομένου ψηφιακού δίσκου raw CD image kruda lumdiskbildo imagen de CD en bruto CD gordinaren irudia raaka CD-vedos rá CD mynd image CD brute amhíomhá dhlúthdhiosca imaxe de CD en bruto תמונת דיסק גולמית nyers CD-lemezkép citra CD mentah Immagine raw CD 生 CD イメージ өңделмеген CD бейнесі CD 이미지 raw CD atvaizdis CD jēlattēls Imej CD mentah rått CD-bilde ruw CD-beeldbestand rått CD-bilete Surowy obraz CD imagem em bruto de CD Imagem bruta de CD imagine de CD brută образ компакт-диска Surový obraz CD surovi CD odtis Imazh raw CD сирови отисак ЦД-а rå cd-avbild образ raw CD ảnh đĩa CD thô 原始 CD 映像 原生 CD 映像 CD Table Of Contents جدول محتويات الـ CD Źmieściva CD Съдържание на CD taula de continguts de CD Obsah CD Cd-indholdsfotegnelse CD-Inhaltsverzeichnis Πίνακας Περιεχομένων CD CD Table Of Contents Índice de contenidos de CD CDaren edukien aurkibidea CD-sisällysluettelo CD innihaldsyvurlit table des matières de CD clár ábhar dlúthdhiosca táboa de contidos de CD תוכן עניינים של דיסק CD sadržaj CD tartalomjegyzék Tabel Isi CD Indice CD CD Table Of Contents CD құрама кестесі CD 내용 목록 CD turinys CD satura rādītājs Innholdsfortegnelse for CD CD-inhoudsopgave CD innhaldsliste Plik zawartości płyty CD Sumário de CD Tabel conținut CD таблица содержания CD Obsah CD Kazalo vsebine CD nosilca Tregues CD Cd-innehållsförteckning зміст CD Mục Lục của đĩa CD CD 索引 CD 內容目錄 PGN chess game notation تدوينة لعبة الشطرنج PGN Zaciem ab šachmatnaj partyi PGN Игра шах — PGN notació de joc d'escacs PGN Šachová notace PGN PGN-skakspilsnotation PGN-Schachspielnotation σημειογραφία παιχνιδιού σκακιού PGN PGN chess game notation notación para juegos de ajedrez PGN PGN xake jokoaren notazioa PGN-šakkipelinotaatio PGN talv teknskipan notation de jeu d'échecs PGN nodaireacht chluiche ficheall PGN Notación de xogo de xadrez PGN סימון משחק שח PGN PGN sakkfeljegyzés Notasi permainan catur PGN Notazione partita a scacchi PGN PGN チェスゲーム記録 PGN шахмат ойыны PGN 체스게임 기보 PGN šachmatų žaidimo žymėjimas PGN šaha spēles notācija PGN sjakkspillnotasjon PGN-schaakspelnotatie PGN-sjakkspelnotasjon Plik PGN notacji gry w szachy Notação de jogo de xadrez PGN Notație joc șah PGN шахматная партия PGN Šachová notácia PGN Datoteka opomb šahovske igre PGN Njoftim loje shahu PGN PGN-schackpartinotation запис гри у шахи PGN Cách ghi lượt chơi cờ PGN PGN 象棋游戏注记 PGN 國際象棋棋譜 CHM document مستند CHM Dakument CHM Документ — CHM document CHM Dokument CHM CHM-dokument CHM-Dokument έγγραφο CHM CHM document CHM-dokumento documento CHM CHM dokumentua CHM-asiakirja CHM skjal document CHM cáipéis CHM documento CHM מסמך CHM CHM dokument CHM dokumentum Dokumen CHM Documento CHM CHM ドキュメント CHM დოკუმენტი CHM құжаты CHM 문서 CHM dokumentas CHM dokuments CHM-dokument CHM-document CHM-dokument Dokument CHM Documento CHM Document CHM документ CHM Dokument CHM Dokument CHM Dokument CHM CHM-dokument документ CHM Tài liệu CHM CHM 文档 CHM 文件 CHM Compiled Help Modules Java byte code رمز بايت الـJava Java bayt kodu Bajtavy kod Java Байт код за Java codi byte de Java Bajtový kód Java Côd beit Java Javabytekode Java-Bytecode συμβολοκώδικας Java Java byte code Java-bajtkodo bytecode Java Java byte-kodea Java-tavukoodi Java býtkota code Java binaire beartchód Java byte code de Java קוד Java byte Java-bájtkód Kode bita Java Bytecode Java Java バイトコード Java байт коды 자바 바이트코드 Java baitinis kodas Java bitu kods Kod bait Java Java-bytekode Java-bytecode Jave byte-kode Kod bajtowy Java 'byte-code' Java Código compilado Java Bytecode Java байт-код Java Bajtový kód Java Datoteka bitne kode Java Byte code Java Јава бајтни ко̂д Java-bytekod Байт-код Java Mã byte Java Java 字节码 Java 位元組碼 UNIX-compressed file ملف يونكس-مضغوط Skampresavany UNIX-fajł Файл — компресиран за UNIX fitxer comprimit UNIX Soubor komprimovaný v Unixu UNIX-komprimeret fil UNIX-komprimierte Datei αρχείο συμπιεσμένο με compress UNIX-compressed file UNIX-kunpremita dosiero archivo comprimido de Unix UNIX-en konprimitutako fitxategia UNIX-pakattu tiedosto UNIX-stappað fíla fichier compressé UNIX comhad UNIX-comhbhrúite ficheiro comprimido de UNIX קובץ מכווץ של UNIX UNIX-komprimirana datoteka Tömörített UNIX-fájl Berkas terkompresi UNIX File compresso-UNIX UNIX-compress ファイル файл (UNIX-сығылған) UNIX 압축 파일 UNIX suglaudintas failas UNIX saspiesta datne Fail termampat-UNIX UNIX-komprimert fil UNIX-ingepakt bestand UNIX-komprimert fil Skompresowany plik systemu UNIX ficheiro comprimido UNIX Arquivo compactado do UNIX Fișier comprimat UNIX файл (UNIX-сжатый) Súbor komprimovaný v Unixe Skrčena Unix datoteka File i kompresuar UNIX UNIX-компресована датотека UNIX-komprimerad fil UNIX-sıkıştırılmış dosyası стиснений файл UNIX Tập tin đã nén UNIX UNIX 压缩文件 UNIX 格式壓縮檔 Tar archive (gzip-compressed) أرشيف Tar (مضغوط-gzip) Archiŭ tar (gzip-skampresavany) Архив — tar, компресиран с gzip arxiu tar (comprimit amb gzip) Archiv tar (komprimovaný pomocí gzip) Tar-arkiv (gzip-komprimeret) Tar-Archiv (gzip-komprimiert) αρχείο Tar (συμπιεσμένο με gzip) Tar archive (gzip-compressed) archivador Tar (comprimido con gzip) Tar artxiboa (gzip-ekin konprimitua) Tar-arkisto (gzip-pakattu) Tar skjalasavn (gzip-stappað) archive tar (compressée gzip) cartlann Tar (comhbhrúite le gzip) arquivo Tar (comprimido con gzip) ארכיון Tar (מכווץ ע"י gzip) Tar arhiva (komprimirana gzip-om) Tar archívum (gzip-pel tömörítve) Arsip Tar (terkompresi gzip) Archivio tar (compresso con gzip) Tar アーカイブ (gzip 圧縮) Tar архиві (gzip-пен сығылған) TAR 묶음 파일 (GZIP 압축) Tar archyvas (suglaudintas su gzip) Tar arhīvs (saspiests ar gzip) Tar-arkiv (gzip-komprimert) Tar-archief (ingepakt met gzip) Tar-arkiv (pakka med gzip) Archiwum tar (kompresja gzip) Pacote tar (compactado com gzip) Arhivă Tar (comprimată gzip) архив TAR (сжатый GZIP) Archív tar (komprimovaný pomocou gzip) Datoteka arhiva Tar (stisnjen z gzip) Arkiv tar (i kompresuar me gzip) Tar-arkiv (gzip-komprimerat) архів tar (стиснений gzip) Kho nén tar (đã nén gzip) Tar 归档文件(gzip 压缩) Tar 封存檔 (gzip 格式壓縮) program crash data معلومات انهيار البرنامج źviestki złamanaj prahramy Данни от забиване на програма dades de fallada de programa Data o pádu programu programnedbrudsdata Daten zu Programmabsturz δεδομένα από την κατάρρευση προγράμματος program crash data datumo pri kraŝo de programo datos de cuelgue de programa programaren kraskaduraren datuak ohjelman kaatumistiedot forrits sordáta données de plantage de programme sonraí thuairt ríomhchláir datos de colgue do programa מידע מקריסת תוכנה podaci o rušenju programa összeomlott program adatai data program macet Dati crash di applicazione プログラムクラッシュデータ апатты аяқтаудың мәліметтері 프로그램 비정상 종료 데이터 programos nulūžimo duomenys programmas avārijas dati Data program musnah krasjdata fra program programma-crashgegevens data om programkrasj Dane awarii programu dados de estoiro de aplicação Dados de travamento de programa date eroare program данные аварийного завершения Údaje o páde programu podatki sesutja programa Të dhëna nga programi i bllokuar подаци о кркљавини програма programkraschdata аварійні дані про програму dữ liệu sụp đổ chương trình 程序崩溃数据 程式當掉資料 CPIO archive أرشيف CPIO CPIO arxivi Archiŭ CPIO Архив — CPIO arxiu CPIO Archiv CPIO Archif CPIO CPIO-arkiv CPIO-Archiv αρχείο CPIO CPIO archive CPIO-arkivo archivador CPIO CPIO artxiboa CPIO-arkisto CPIO skjalasavn archive CPIO cartlann CPIO arquivo CPIO ארכיון CPIO CPIO arhiva CPIO-archívum Arsip CPIO Archivio CPIO CPIO アーカイブ CPIO არქივი CPIO архиві CPIO 묶음 파일 CPIO archyvas CPIO arhīvs Arkib CPIO CPIO-arkiv CPIO-archief CPIO-arkiv Archiwum CPIO arquivo CPIO Pacote CPIO Arhivă CPIO архив CPIO Archív CPIO Datoteka arhiva CPIO Arkiv CPIO CPIO архива CPIO-arkiv архів CPIO Kho nén CPIO CPIO 归档文件 CPIO 封存檔 CPIO archive (gzip-compressed) أرشيف CPIO (مضغوط-gzip) CPIO arxivi (gzip ilə sıxışdırılmış) Archiŭ CPIO (gzip-skampresavany) Архив — CPIO, компресиран с gzip arxiu CPIO (comprimit amb gzip) Archiv CPIO (komprimovaný pomocí gzip) Archif CPIO (gywasgwyd drwy gzip) CPIO-arkiv (gzip-komprimeret) CPIO-Archiv (gzip-komprimiert) αρχείο CPIO (συμπιεσμένο με gzip) CPIO archive (gzip-compressed) CPIO-arkivo (kunpremita per gzip) archivador CPIO (comprimido con gzip) CPIO artxiboa (gzip-ekin konprimitua) CPIO-arkisto (gzip-pakattu) CPIO skjalasavn (gzip-stappað) archive CPIO (compressé gzip) cartlann CPIO (comhbhrúite le gzip) arquivo CPIO (comprimido con gzip) ארכיון CPIO (מכווץ ע"י gzip) CPIO arhiva (komprimirana gzip-om) CPIO-archívum (gzip-pel tömörítve) Arsip CPIO (terkompresi gzip) Archivio CPIO (compresso con gzip) CPIO (gzip 圧縮) アーカイブ CPIO არქივი (gzip-ით შეკუმშული) CPIO архиві (gzip-пен сығылған) CPIO 묶음 파일 (GZIP 압축) CPIO archyvas (suglaudintas su gzip) CPIO arhīvs (saspiests ar gzip) Arkib CPIO (dimampatkan-gzip) CPIO-arkiv (gzip-komprimert) CPIO-archief (ingepakt met gzip) CPIO-arkiv (gzip-pakka) Archiwum CPIO (kompresja gzip) arquivo CPIO (comprimido com gzip) Pacote CPIO (compactado com gzip) Arhivă CPIO (compresie gzip) архив CPIO (сжатый GZIP) Archív CPIO (komprimovaný pomocou gzip) Datoteka arhiva CPIO (skrčena z gzip) Arkiv CPIO (kompresuar me gzip) CPIO архива (компресована gzip-ом) CPIO-arkiv (gzip-komprimerat) архів CPIO (стиснений gzip) Kho nén CPIO (đã nén gzip) CPIO 归档文件(gzip 压缩) CPIO 封存檔 (gzip 格式壓縮) C shell script سكربت شِل سي C qabıq skripti Skrypt abałonki C Скрипт — обвивка C script en C Skript shellu C Sgript plisgyn C C-skalprogram C-Shell-Skript πρόγραμμα εντολών φλοιού C C shell script skripto de C-ŝelo script en C shell C shell script-a Csh-komentotiedosto C skel boðrøð script C shell script bhlaosc C script de C shell תסריט מעטפת C C skripta C héj-parancsfájl Skrip shell C Script C shell C シェルスクリプト C shell сценарийі C 셸 스크립트 C shell scenarijus C čaulas skripts Skrip shell C C-skallskript C-shellscript C-skalskript Skrypt powłoki C 'script' de consola C Script de shell C Script C shell сценарий C shell Skript shellu C Skriptna datoteka lupine C Script shell C C скрипта окружења Skalskript (csh) C kabuk betiği скрипт оболонки C Văn lệnh trình bao C C shell 脚本 C shell 指令稿 Xbase document مستند Xbase Dakument Xbase Документ — Xbase document Xbase Dokument Xbase Xbasedokument Xbase-Dokument έγγραφο Xbase Xbase document Xbase-dokumento documento Xbase Xbase dokumentua Xbase-asiakirja Xbase skjal document Xbase cáipéis Xbase documento Xbase מסמך Xbase Xbase dokumentum Dokumen Xbase Documento Xbase Xbase ドキュメント Xbase құжаты Xbase 문서 Xbase dokumentas Xbase dokuments Xbase-dokument Xbase-document Xbase-dokument Dokument Xbase Documento do Xbase Document Xbase документ Xbase Dokument Xbase Dokument Xbase Dokument Xbase Xbase-dokument Xbase belgesi документ Xbase Tài liệu Xbase Xbase 文档 Xbase 文件 ECMAScript program برنامج ECMAScript Prahrama ECMAScript Програма — ECMAScript programa ECMAScript Program ECMAScript ECMA-program ECMAScript-Programm πρόγραμμα ECMAScript ECMAScript program programa en ECMAScript ECMAScript programa ECMAScript-ohjelma ECMAScript forrit programme ECMAScript ríomhchlár ECMAScript programa en ECMAScript תכנית EMCAScript ECMAScript program ECMAScript program Program ECMAScript Programma ECMAScript ECMAScript プログラム ECMAScript პროგრამა ECMAScript программасы ECMA스크립트 프로그램 ECMAScript programa ECMAScript programma ECMAScript-program ECMAScript-programma ECMAScript-program Pogram ECMAScript Programa ECMAScript Program ECMAScript программа ECMAScript Program ECMAScript Programska datoteka ECMAScript Program ECMAScript ECMAScript-program ECMAScript programı програма мовою ECMAScript Chương trình ECMAScript ECMAScript 程序 ECMAScript 程式 Dreamcast ROM Dreamcast ROM Dreamcast ROM ROM — Dreamcast ROM de Dreamcast ROM pro Dreamcast Dreamcast-rom Dreamcast-ROM εικόνα μνήμης ROM Dreamcast Dreamcast ROM Dreamcast-NLM ROM de Dreamcast Dreamcast-en ROM Dreamcast-ROM Dreamcast ROM ROM Dreamcast ROM Dreamcast ROM de Dreamcast ROM של Dreamcast Dreamcast ROM Dreamcast ROM Memori baca-saja Dreamcast ROM Dreamcast Dreamcast ROM Dreamcast-ის ROM Dreamcast ROM 드림캐스트 롬 Dreamcast ROM Dreamcast ROM ROM Dreamcast Dreamcast-ROM Dreamcast-ROM Dreamcast-ROM Plik ROM konsoli Dreamcast ROM Dreamcast ROM do Dreamcast ROM Dreamcast Dreamcast ROM ROM pre Dreamcast Bralni pomnilnik Dreamcast ROM Dreamcast Dreamcast ROM Dreamcast-rom ППП Dreamcast ROM Dreamcast Dreamcast ROM Dreamcast ROM Nintendo DS ROM Nintendo DS ROM Nintendo DS ROM ROM — Nintendo DS ROM de Nintendo DS ROM pro Nintendo DS Nintendo DS-rom Nintendo-DS-ROM εικόνα μνήημης ROM Nintendo DS Nintendo DS ROM ROM de Nintendo DS Nintendo DS-ko ROMa Nintendo DS-ROM Nintendo DS ROM ROM Nintendo DS ROM Nintendo DS ROM de Nintendo DS ROM של Nintendo Nintendo DS ROM Nintendo DS ROM Memori baca-saja Nintendo DS ROM Nintendo DS Nintendo DS ROM Nintendo DS ROM 닌텐도 DS 롬 Nintendo DS ROM Nintendo DS ROM Nintendo DS-ROM Nintendo-DS-ROM Nintendo DS-ROM Plik ROM konsoli Nintendo DS ROM do Nintendo DS ROM Nintendo DS Nintendo DS ROM ROM pre Nintendo DS Bralni pomnilnik Nintendo DS ROM Nintendo DS Nintendo DS-rom Nintendo DS ROM ППП Nintendo ROM DS Nintendo Nintendo DS ROM 任天堂 DS ROM Debian package حزمة ديبيان Debian paketi Pakunak Debian Пакет — Debian paquet Debian Balíček Debianu Pecyn Debian Debianpakke Debian-Paket πακέτο Debian Debian package Debian-pakaĵo paquete Debian Debian paketea Debian-paketti Debian pakki paquet Debian pacáiste Debian paquete de Debian חבילת דביאן Debian paket Debian-csomag Paket Debian Pacchetto Debian Debian パッケージ Debian-ის პაკეტი Debian дестесі 데비안 패키지 Debian paketas Debian pakotne Pakej Debian Debian pakke Debian-pakket Debian pakke Pakiet Debiana pacote Debian Pacote Debian Pachet Debian пакет Debian Balíček Debianu Datoteka paketa Debian Paketë Debian Debian пакет Debianpaket Debian paketi пакунок Debian Gói Debian Debian 软件包 Debian 套件 Qt Designer file ملف Qt Designer Fajł Qt Designer Файл — Qt Designer fitxer de Qt Designer Soubor Qt Designer Qt Designer-fil Qt-Designer-Datei αρχείο Qt Designer Qt Designer file dosiero de Qt Designer archivo de Qt Designer Qt Designer Fitxategia Qt Designer -tiedosto Qt Designer fíla fichier Qt Designer comhad Qt Designer ficheiro de Qt Designer קובץ של Qt Designer Qt Designer-fájl Berkas Qt Designer File Qt Designer Qt Designer ファイル Qt Designer файлы Qt 디자이너 파일 Qt Designer failas Qt Designer datne Fail Qt Designer Qt Designer-fil Qt Designer-bestand Qt Designer-fil Plik Qt Designer ficheiro do Qt Designer Arquivo do Qt Designer Fișier Qt Designer файл Qt Designer Súbor Qt Designer Datoteka Qt Designer File Qt Designer Qt Designer датотека Qt Designer-fil файл програми Qt-дизайнер Tập tin thiết kế Qt Designer Qt Designer 文件 Qt Designer 檔案 Qt Markup Language file Файл — Qt Markup fitxer de llenguatge de marcadors Qt Soubor Qt Markup Language Qt Markup Language-fil Qt-Auszeichnungssprachendatei αρχείο Qt Markup Language Qt Markup Language file Archivo de lenguaje de marcado Qt fichier Qt Markup Language ficheiro de linguaxe de marcado Qt קובץ שפת סימון של Qt Qt jelölőnyelvű fájl Berkas Bahasa Markup Qt File Qt Markup Language Qt マークアップ言語ファイル Qt Markup Language файлы Qt 마크업 언어 파일 Qt marķēšanas valodas datne Qt Markup Tallbestand Plik języka znaczników Qt Arquivo de Qt Markup Language файл Qt Markup Language Datoteka označevalnega jezika Qt файл мови розмітки Qt Qt Qt 標記語言檔 desktop configuration file ملف تضبيط سطح المكتب kanfihuracyjny fajł asiarodździa Файл с информация за работния плот fitxer de configuració d'escriptori Soubor nastavení pracovní plochy skrivebordskonfigurationsfil Desktop-Konfigurationsdatei ρυθμίσεις επιφάνειας εργασίας desktop configuration file dosiero de agordoj de labortablo archivo de configuración del escritorio Mahaigainaren konfigurazio-fitxategia työpöydän asetustiedosto skriviborðssamansetingarfíla fichier de configuration desktop comhad chumraíocht deisce ficheiro de configuración de escritorio קובץ הגדרות של שולחן העבודה datoteka postavki radne površine asztalbeállító fájl berkas konfigurasi destop File configurazione desktop デスクトップ設定ファイル жұмыс үстел баптаулар файлы 데스크톱 설정 파일 darbastalio konfigūracijos failas darbvirsmas konfigurācijas datne Fail konfigurasi desktop konfigurasjonsfil for skrivebordet bureaublad-configuratiebestand skrivebordsoppsettfil Plik konfiguracji środowiska ficheiro de configuração de área de trabalho Arquivo de configuração de área de trabalho fișier de configurare al desktopului файл настроек рабочего стола Súbor nastavení pracovnej plochy nastavitvena datoteka namizja File konfigurimi desktop датотека за подешавања радне површи skrivbordskonfigurationsfil файл конфігурації стільниці tập tin cấu hình môi trường 桌面配置文件 桌面組態檔 FictionBook document مستند FictionBook Документ — FictionBook document FictionBook Dokument FictionBook FictionBook-dokument FictionBook-Dokument έγγραφο FictionBook FictionBook document FictionBook-dokumento documento FictionBook FictionBook dokumentua FictionBook-asiakirja FictionBook skjal document FictionBook cáipéis FictionBook documento de FictionBook מסמך FictionBook FictionBook dokument FictionBook-dokumentum Dokumen FictionBook Documento FictionBook FictionBook ドキュメント FictionBook-ის დოკუმენტი FictionBook құжаты FictionBook 문서 FictionBook dokumentas FictionBook dokuments FictionBook-document Dokument FictionBook Documento FictionBook Document FictionBook документ FictionBook Dokument FictionBook Dokument FictionBook FictionBook-dokument FictionBook belgesi документ FictionBook Tài liệu FictionBook FictionBook 文档 FictionBook 文件 Dia diagram خطاطة Dia Dia diaqramı Dyjahrama Dia Диаграма — Dia diagrama de Dia Diagram Dia Diagram Dia Dia-diagram Dia-Diagramm διάγραμμα Dia Dia diagram Dia-diagramo diagrama de Dia Dia diagrama Dia-kaavio Dia ritmynd diagramme Dia léaráid Dia diagrama de Dia גרף של Dia Dia dijagram Dia-diagram Diagram Dia Diagramma Dia Dia ダイアグラム Dia-ის დიაგრამა Dia диаграммасы Dia 도표 Dia diagrama Dia diagramma Diagram Dia Dia-diagram Dia-diagram Dia diagram Diagram Dia diagrama Dia Diagrama do Dia Diagramă Dia диаграмма Dia Diagram Dia Datoteka diagrama Dia Diagramë Dia Диа дијаграм Dia-diagram діаграма Dia Biểu đồ Dia Dia 图表 Dia 圖表 Dia shape شكل Dia Фигура — Dia forma del Dia Tvary Dia Dia-figur Dia-Form σχήμα Dia Dia shape forma de Dia Dia-ren forma Dia skapur forme Dia cruth Dia forma de Dia צורה של Dia Dia oblik Dia alakzat Shape Dia Sagoma Dia Dia 図形 Dia сызбасы Dia 그림 Dia forma Dia forma Diavorm Kształt Dia Formato Dia Figură Dia фигура Dia Datoteka oblik Dia Dia-figur форма Dia Dia 形状 Dia 形狀 TeX DVI document مستند TeX DVI Dakument TeX DVI Документ — TeX DVI document TeX DVI Dokument TeX DVI TeX DVI-dokument TeX-DVI-Dokument έγγραφο TeX DVI TeX DVI document DVI-dokumento de TeX documento TeX DVI TeX DVI dokumentua TeX DVI -asiakirja TeX DVI skjal document TeX DVI cáipéis DVI TeX documento TeX DVI מסמך מסוג TeX DVI TeX DVI dokument TeX DVI-dokumentum Dokumen TeX DVI Documento TeX DVI TeX DVI ドキュメント TeX DVI құжаты TeX DVI 문서 TeX DVI dokumentas TeX DVI dokuments Dokumen TeX DVI TeX DVI-dokument TeX DVI-document TeX DVI-dokument Dokument TeX DVI documento TeX DVI Documento DVI TeX Document Tex DVI документ TeX DVI Dokument TeX DVI Dokument TeX DVI Dokument TeX DVI ТеХ ДВИ документ TeX DVI-dokument документ TeX DVI Tài liệu DVI Tex TeX DVI 文档 TeX DVI 文件 DVI Device independent file format Enlightenment theme سمة Enlightenment Enlightenment örtüyü Matyŭ Enlightenment Тема — Enlightenment tema d'Enlightenment Motiv Enlightenment Thema Enlightenment Enlightenmenttema Enlightenment-Thema Θέμα Enlightenment Enlightenment theme etoso de Enlightenment tema de Enlightenment Enlightenment gaia Enlightenment-teema Enlightenment tema thème Enlightenment téama Enlightenment tema de Enlightenment ערכת נושא של Enlightenment Enlightenment tema Enlightenment-téma Tema Enlightenment Tema Enlightenment Enlightenment テーマ Enlightenment-ის თემა Enlightenment темасы 인라이튼먼트 테마 Enlightenment tema Enlightenment motīvs Tema Enlightenment Enlightenment tema Enlightenment-thema Enlightenment-tema Motyw Enlightenment tema Enlightenment Tema do Enlightenment Temă Enlightenment тема Enlightenment Motív Enlightenment Datoteka teme Enlightenment Tema Enlightenment Enlightenment тема Enlightenment-tema Enlightenment teması тема Enlightenment Sắc thái Enlightenment Enlightenment 主题 Enlightenment 佈景主題 Egon Animator animation تحريكة محرك Egon Animacyja Egon Animator Анимация — Egon Animator animació d'Egon Animator Animace Egon Animator Egon Animator-animation Egon-Animator-Animation κινούμενα σχέδια Egon Animator Egon Animator animation animacio de Egon Animator animación de Egon Animator Egon Animator-eko animazioa Egon Animator -animaatio Egon Animator teknimyndagerð animation Egon Animator beochan Egon Animator animación de Egon Animator אנימצייה של Egon Animator Egon Animator animacija Egon Animator-animáció Animasi Egon Animator Animazione Egon Animator Egon Animator アニメーション Egon Animator-ის ანიმაცია Egon Animator анимациясы Egon 애니메이터 애니메이션 Egon Animator animacija Egon Animator animācija Animasi Egon Animator Egon animator-animasjon Egon Animator-animatie Egon Animator-animasjon Animacja Egon Animator animação Egon Animator Animação do Egon Animator Animație Egon Animator анимация Egon Animator Animácia Egon Animator Datoteka animacije Egon Animator Animim Egon Animator Егон аниматор анимација Egon Animator-animering анімація Egon Animator Hoạt ảnh Egon Animator Egon Animator 动画 Egon Animator 動畫 executable تنفيذي vykonvalny fajł Изпълним файл executable Spustitelný soubor kørbar Programm εκτελέσιμο executable plenumebla ejecutable exekutagarria suoritettava ohjelma inningarfør exécutable comhad inrite executábel קובץ הרצה izvršna datoteka futtatható dapat dieksekusi Eseguibile 実行ファイル орындалатын 실행 파일 vykdomasis failas izpildāmais Bolehlaksana kjørbar uitvoerbaar bestand køyrbar Program executável Executável executabil исполняемый Spustiteľný súbor izvedljiva datoteka I ekzekutueshëm извршна körbar fil çalıştırılabilir виконуваний файл thực hiện được 可执行文件 可執行檔 FLTK Fluid file ملف FLTK Fluid Fajł FLTK Fluid Интерфейс — FLTK Fluid fitxer Fluid FLTK Soubor FLTK Fluid FLTK Fluid-fil FLTK-Fluid-Datei FLTK Fluid file archivo FLTK Fluid FLTK Fluid fitxategia FLTK Fluid -tiedosto FLTK Fluid fíla fichier Fluid FLTK comhad FLTK Fluid ficheiro FLTK Fluid קובץ FLTK Fluid FLTK Fluid datoteka FLTK Fluid fájl Berkas FLTK Fluid File FLTK Fluid FLTK Fluid ファイル FLTK Fluid-ის ფაილი FLTK Fluid файлы FLTK Fluid 파일 FLTK Fluid failas FLTK Fluid datne FLTK Fluid-fil FLTK FLUID-bestand FLTK Fluid-fil Plik Fluid FLTK Arquivo Fluid do FLTK Fișier FLTK Fluid файл FLTK Fluid Súbor FLTK Fluid Datoteka FLTK Fluid File FLTK Fluid FLTK Fluid-fil FLTK Fluid dosyası файл FLTK Fluid Tập tin Fluid FLTK FLTK 流体文档 FLTK Fluid 檔 FLTK Fast Light Toolkit WOFF font WOFF Web Open Font Format Postscript type-1 font خط Postscript type-1 Šryft Postscript type-1 Шрифт — Postscript Type 1 tipus de lletra Postscript type-1 Písmo Postscript type-1 PostScript type-1-skrifttype Postscript-Typ-1-Schrift Postscript type-1 font tipografía PostScript tipo-1 PostScript type-1 letra-tipoa PostScript tyyppi-1 -asiakirja Postscript type-1 stavasnið police Postscript Type 1 cló Postscript type-1 tipo de letra PostScript tipo-1 גופן של Postscript type-1 Postscript type-1 betűkészlet Fonta tipe-1 Postscript Tipo carattere Postscript type-1 PostScript type-1 フォント Postscript type-1 қарібі 포스트스크립트 Type-1 글꼴 Postscript type-1 šriftas Postscript 1-tipa fonts Postscript type-1 skrift PostScript type-1-lettertype PostScript type 1-skrifttype Czcionka PostScript Type-1 Fonte PostScript tipo-1 Font Postscript type-1 шрифт PostScript Type-1 Písmo Postscript type-1 Datoteka pisave Postscript vrste-1 Lloj gërmash Postscript type-1 Postscript type-1-typsnitt шрифт Postscript type-1 Phông kiểu 1 PostScript Postscript type-1 字体 Postscript type-1 字型 Adobe font metrics مقاييس خط أدوبي Adobe yazı növü metrikləri Metryka šryftu Adobe Шрифтова метрика — Adobe mètrica de tipus de lletra Adobe Metrika písma Adobe Metrigau Ffont Adobe Adobe skrifttypefil Adobe-Schriftmetriken μετρικά γραμματοσειράς Adobe Adobe font metrics metrikoj de Adobe-tiparo métricas de tipografía Adobe Adobe letra-tipoen neurriak Adobe-kirjasinmitat métriques de police Adobe meadarachtaí cló Adobe métricas de fonte de Adobe מדדי גופן של Adobe Adobe mjere fonta Adobe-betűmetrika Metrik fonta Adobe Metriche tipo carattere Adobe Adobe フォントメトリック Adobe қаріп метрикалары 어도비 글꼴 메트릭 Adobe šriftų metrika Adobe fonta metrika Metrik font Adobe Adobe skrifttypefil Adobe-lettertype-metrieken Adobe skrifttypemetrikk Metryka czcionki Adobe métrica de tipos de letra Adobe Métricas de fonte Adobe Dimensiuni font Adobe метрика шрифта Adobe Metrika písma Adobe Matrika pisave Adobe Metrik lloj gërmash Adobe Adobe метрика фонта Adobe-typsnittsmetrik метрики шрифту Adobe Cách đo phông chữ Adobe Adobe 字体参数 Adobe 字型描述檔 BDF font خط BDF BDF yazı növü Šryft BDF Шрифт — BDF tipus de lletra BDF Písmo BDF Ffont BDF BDF-skrifttype BDF-Schrift γραμματοσειρά BDF BDF font BDF-tiparo tipografía BDF BDF letra-tipoa BDF-kirjasin BDF stavasnið police BDF cló BDF tipo de fonte BDF גופן BDF BDF font BDF-betűkészlet Fonta BDF Tipo carattere BDF BDF フォント BDF қарібі BDF 글꼴 BDF šriftas BDF fonts Font BDF BDF-skrifttype BDF-lettertype BDF-skrifttype Czcionka BDF tipo de letra BDF Fonte BDF Font BDF шрифт BDF Písmo BDF Datoteka pisave BDF Lloj gërme BDF BDF фонт BDF-typsnitt BDF fontu шрифт BDF Phông chữ BDF BDF 字体 BDF 字型 DOS font خط DOS DOS yazı növü Šryft DOS Шрифт — DOS tipus de lletra DOS Písmo pro DOS Ffont DOS DOS-skrifttype DOS-Schrift γραμματοσειρά DOS DOS font DOS-tiparo tipografía DOS DOS letra-tipoa DOS-kirjasin DOS stavasnið police DOS cló DOS tipo de fonte de DOS גופן DOS DOS font DOS-betűkészlet Fonta DOS Tipo carattere DOS DOS フォント DOS қарібі DOS 글꼴 DOS šriftas DOS fonts Font DOS DOS-skrifttype DOS-lettertype DOS-skrifttype Czcionka DOS tipo de letra DOS Fonte do DOS Font DOS шрифт DOS Písmo pre DOS Datoteka pisave DOS Gërmë DOS DOS фонт DOS-typsnitt DOS fontu шрифт DOS Phông chữ DOS DOS 字体 DOS 字型 Adobe FrameMaker font خط أدوبي الصانع للإطارات Adobe FrameMaker yazı növü Šryft Adobe FrameMaker Шрифт — Adobe FrameMaker tipus de lletra d'Adobe FrameMaker Písmo Adobe FrameMaker Ffont Adobe FrameMaker Adobe FrameMaker-skrifttype Adobe-FrameMaker-Schrift γραμματοσειρά Adobe FrameMaker Adobe FrameMaker font Tiparo de Adobe FrameMaker tipografía de Adobe FrameMaker Adobe FrameMaker-en letra-tipoa Adobe FrameMaker -kirjasin Adobe FrameMaker stavasnið police Adobe FrameMaker cló Adobe FrameMaker tipo de fonte de Adobe FrameMaker גופן של Adobe FrameMaker Adobe FrameMaker font Adobe FrameMaker-betűkészlet Fonta Adobe FrameMaker Tipo carattere Adobe FrameMaker Adobe FrameMaker フォント Adobe FrameMaker қарібі 어도비 프레임메이커 글꼴 Adobe FrameMaker šriftas Adobe FrameMaker fonts Font Adobe FrameMaker Adobe FrameMaker skrifttype Adobe FrameMaker-lettertype Adobe FrameMaker-skrifttype Czcionka Adobe FrameMaker tipo de letra Adobe FrameMaker Fonte do Adobe FrameMaker Font Adobe FrameMaker шрифт Adobe FrameMaker Písmo Adobe FrameMaker Datoteka pisave Adobe FrameMaker Gërma Adobe FrameMaker Adobe FrameMaker фонт Adobe FrameMaker-typsnitt шрифт Adobe FrameMaker Phông chữ Adobe FrameMaker Adobe FrameMaker 字体 Adobe FrameMaker 字型 LIBGRX font خط LIBGRX LIBGRX yazı növü Šryft LIBGRX Шрифт — LIBGRX tipus de lletra LIBGRX Písmo LIBGRX Ffont LIBGRX LIBGRX-skrifttype LIBGRX-Schrift γραμματοσειρά LIBGRX LIBGRX font LIBGRX-tiparo tipografía LIBGRX LIBGRX letra-tipoa LIBGRX-kirjasin LIBGRX stavasnið police LIBGRX cló LIBGRX tipo de fonte en LIBGRX גופן LIBGRX LIBGRX font LIBGRX-betűkészlet Fonta LIBGRX Tipo carattere LIBGRX LIBGRX フォーマット LIBGRX қарібі LIBGRX 글꼴 LIBGRX šriftas LIBGRX fonts Font LIBGRX LIBGRX-skrifttype LIBGRX-lettertype LIBGRX skrifttype Czcionka LIBGRX tipo de letra LIBGRX Fonte LIBGRX Font LIBGRX шрифт LIBGRX Písmo LIBGRX Datoteka pisave LIBGRX Lloj gërme LIBGRX LIBGRX фонт LIBGRX-typsnitt LIBGRX fontu шрифт LIBGRX Phông chữ LIBGRX LIBGRX 字体 LIBGRX 字型 Linux PSF console font خط كونسول PSF لينكس Linux PSF konsol yazı növü Kansolny šryft PSF dla Linuksa Шрифт — PSF, за конзолата на Линукс tipus de lletra de consola Linux PSF Písmo PSF pro konzolu Linuxu Ffont Linux PSF Linux PSF-konsolskrifttype Linux-PSF-Konsolenschrift γραμματοσειρά κονσόλας PSF Linux Linux PSF console font PSF-tiparo de Linux-konzolo tipografía de consola Linux PSF Linux PSF kontsolako letra-tipoa Linux PSF -konsolikirjasin Linux PSF stýristøðs stavasnið police console Linux PSF cló chonsól Linux PSF tipo de fonte de consola Linux PSF גופן לקונסול מסוג Linux PSF Linux PSF konzolni font Linux PSF konzolos betűkészlet Fonta konsol Linux PSF Tipo carattere console Linux PSF Linux PSF コンソールフォント Linux PSF консольдік қарібі 리눅스 PSF 콘솔 글꼴 Linux PSF konsolės šriftas Linux PSF konsoles fonts Font konsol PSF Linux Linux PSF konsollskrifttype Linux PSF-console-lettertype Linux PSF konsoll-skrifttype Czcionka konsoli PSF Linux tipo de letra de consola Linux PSF Fonte de console Linux PSF Font consolă Linux PSF консольный шрифт Linux PSF Písmo PSF pre konzolu Linuxu Datoteka pisave konzole Linux PSF Lloj gërme për konsolë Linux PSF Линукс PSF конзолни фонт Linux PSF-konsolltypsnitt Linux PSF konsol fontu консольний шрифт Linux PSF Phông chữ bàn giao tiếp PSF Linux Linux PSF 控制台字体 Linux PSF console 字型 Linux PSF console font (gzip-compressed) خط كونسول PSF لينكس (مضغوط-gzip) Kansolny šryft PSF dla Linuksa (gzip-skampresavany) Шрифт — Linux PSF, компресиран с gzip tipus de lletra de consola Linux PSF (comprimida amb gzip) Písmo PSF pro konzolu Linuxu (komprimované pomocí gzip) Linux PSF-konsolskrifttype (gzip-komprimeret) Linux-PSF-Konsolenschrift (gzip-komprimiert) γραμματοσειρά κονσόλας PSF Linux (συμπιεσμένη με gzip) Linux PSF console font (gzip-compressed) tipografía de consola Linux PSF (comprimida con gzip) Linux PSF kontsolako letra-tipoa (gzip-ekin konprimitua) Linux PSF -konsolikirjasin (gzip-pakattu) Linux PSF stýristøðs stavasnið (gzip-stappað) police console Linux PSF (compressée gzip) cló chonsól Linux PSF (comhbhrúite le gzip) tipo de fonte de consola Linux PSF (comprimida con gzip) גופן לקונסול מסוג Linux PSF (מכווץ ע"י gzip) Linux PSF konzolni font (komprimiran gzip-om) Linux PSF konzolos betűkészlet (gzip-tömörítésű) Fonta konsol Linux PSF (terkompresi gzip) Tipo carattere console Linux PSF (compresso con gzip) Linux PSF コンソールフォント (gzip 圧縮) Linux PSF консольдік қарібі (gzip-пен сығылған) 리눅스 PSF 콘솔 글꼴 (GZIP 압축) Linux PSF konsolės šriftas (suglaudintas su gzip) Linux PSF konsoles fonts (saspiests ar gzip) Linux PSF konsollskrifttype (gzip-komprimert) Linux PSF-console-lettertype (ingepakt met gzip) Linux PSF konsoll-skrifttype (gzip-pakka) Czcionka konsoli PSF Linux (kompresja gzip) Fonte de console Linux PSF (compactada com gzip) Font consolă Linux PSF (compresie gzip) консольный шрифт Linux PSF (сжатый gzip) Písmo PSF pre konzolu Linuxu (komprimované pomocou gzip) Datoteka pisave konzole Linux PSF (skrčena z gzip) Lloj gërme për konsolë Linux PSF (komresuar me gzip) Linux PSF-konsolltypsnitt (gzip-komprimerat) Linux PSF konsol fontu (gzip ile sıkıştırılmış) консольний шрифт Linux PSF (стиснений gzip) Phông chữ bàn giao tiếp PSF Linux (đã nén gzip) Linux PSF 控制台字体(gzip 压缩) Linux PSF console 字型 (gzip 格式壓縮) PCF font خط PCF PCF yazı növü Šryft PCF Шрифт — PCF tipus de lletra PCF Písmo PCF Ffont PCF PCF-skrifttype PCF-Schrift γραμματοσειρά PCF PCF font PCF-tiparo tipografía PCF PCF letra-tipoa PCF-kirjasin PCF stavasnið police PCF cló PCF tipo de letra PCF פונט PCF PCF-betűkészlet Fonta PCF Tipo carattere PCF PCF フォント PCF қарібі PCF 글꼴 PCF šriftas PCF fonts Font PCF PCF-skrifttype PCF-lettertype PCF-skrifttype Czcionka PCF tipo de letra PCF Fonte PCF Font PCF шрифт PCF Písmo PCF Datoteka pisave PCF Gërma PCF PCF фонт PCF-typsnitt PCF fontu шрифт PCF Phông chữ PCF PCF 字体 PCF 字型 OpenType font خط OpenType OpenType yazı növü Šryft OpenType Шрифт — OpenType tipus de lletra OpenType Písmo OpenType Ffont OpenType OpenType-skrifttype OpenType-Schrift γραμματοσειρά OpenType OpenType font OpenType-tiparo tipografía de OpenType OpenType letra-tipoa OpenType-kirjasin OpenType stavasnið police OpenType cló OpenType tipo de fonte OpenType גופן של OpenType OpenType-betűkészlet Fonta OpenType Tipo carattere OpenType OpenType フォント OpenType қарібі 오픈타입 글꼴 OpenType šriftas OpenType fonts Font OpenType OpenType-skrifttype OpenType-lettertype OpenType-skrifttype Czcionka OpenType tipo de letra OpenType Fonte OpenType Font OpenType шрифт OpenType Písmo OpenType Datoteka pisave OpenType Gërma OpenType OpenType фонт OpenType-typsnitt OpenType fontu шрифт OpenType Phông chữ OpenType OpenType 字体 OpenType 字型 Speedo font خط Speedo Speedo yazı növü Šryft Speedo Шрифт — Speedo tipus de lletra Speedo Písmo Speedo Ffont Speedo Speedoskrifttype Speedo-Schrift γραμματοσειρά Speedo Speedo font Speedo-tiparo tipografía Speedo Speedo letra-tipoa Speedo-kirjasin Speedo stavasnið police Speedo cló Speedo tipo de letra Speedo גופן של Speedo Speedo font Speedo-betűkészlet Fonta Speedo Tipo carattere Speedo Speedo フォント Speedo қарібі Speedo 글꼴 Speedo šriftas Speedo fonts Font Speedo Speedo-skrifttype Speedo-lettertype Speedo-skrifttype Czcionka Speedo tipo de letra Speedo Fonte Speedo Font Speedo шрифт Speedo Písmo Speedo Datoteka pisave Speedo Gërma Speedo Speedo фонт Speedo-typsnitt Speedo fontu шрифт Speedo Phông chữ Speedo Speedo 字体 Speedo 字型 SunOS News font خط SunOS News SunOS News yazı növü Šryft SunOS News Шрифт — SunOS News tipus de lletra SunOS News Písmo SunOS News Ffont SunOS News SunOS News-skrifttype SunOS-News-Schrift γραμματοσειρά SunOS News SunOS News font tiparo de SunOS News tipografía SunOS News SunOs News letra-tipoa SunOS News -kirjasin SunOS News stavasnið police SunOS News cló SunOS News tipo de letra SunOS News גופן של SunOS News SunOS News font SunOS News-betűkészlet Fonta SunOS News Tipo carattere SunOS News SunOS News フォント SunOS News қарібі SunOS News 글꼴 SunOS News šriftas SunOS News fonts Font News SunOS SunOS News-skrifttype SunOS News-lettertype SunOS NEWS-skrifttype Czcionka SunOS News tipo de letra SunOS News Fonte SunOS News Font SunOS News шрифт SunOS News Písmo SunOS News Datoteka pisave SunOS News Gërma SunOS News SunOS News фонт SunOS News-typsnitt шрифт SunOS News Phông chữ SunOS News SunOS News 字体 SunOS News 字型 TeX font خط TeX TeX yazı növü Šryft TeX Шрифт — TeX tipus de lletra TeX Písmo TeX Ffont TeX TeX-skrifttype TeX-Schrift γραμματοσειρά TeX TeX font TeX-tiparo tipografía de TeX TeX letra-tipoa TeX-kirjasin TeX stavasnið police TeX cló TeX tipo de letra de TeX גופן TeX TeX font TeX-betűkészlet Fonta TeX Tipo carattere TeX TeX フォント TeX қарібі TeX 글꼴 TeX šriftas TeX fonts Font TeX TeX-skrift TeX-lettertype TeX-skrifttype Czcionka TeX tipo de letra TeX Fonte TeX Font TeX шрифт TeX Písmo TeX Datoteka pisave TeX Gërma TeX ТеХ фонт TeX-typsnitt TeX fontu шрифт TeX Phông chữ TeX TeX 字体 TeX 字型 TeX font metrics مقاييس خط TeX TeX yazı növü metrikləri Metryka šryftu TeX Шрифтова метрика — TeX mètrica de tipus de lletra TeX Metrika písma TeX Metrigau Ffont TeX TeX-skrifttypeinformation TeX-Schriftmetriken μετρικά γραμματοσειράς TeX TeX font metrics metrikoj de TeX-tiparo métricas de tipografía de TeX TeX letra-tipoen neurriak TeX-kirjasinmitat métriques de police TeX meadarachtaí cló TeX Métricas de tipo de letra de TeX גופן מטריקס של TeX TeX mjere fonta TeX-betűmetrika Fonta metrik TeX Metriche tipo carattere TeX TeX フォントメトリック TeX қаріп метрикалары Tex 글꼴 메트릭 TeX šriftų metrika TeX fonta metrikas Metrik font TeX TeX skrifttypemetrikk TeX-lettertype-metrieken TeX skrifttypemetrikk Metryki czcionki TeX métricas de tipo de letra TeX Métrica de fonte TeX Dimensiuni font TeX метрика шрифта TeX Metrika písma TeX Matrika pisave Tex Gërma TeX metrics ТеХ метрика фонта TeX-typsnittsmetrik метрики шрифту TeX Cách đo phông chữ TeX TeX 字体参数 TeX 字型描述檔 TrueType font خط TrueType Šryft TrueType Шрифт — TrueType tipus de lletra TrueType Písmo TrueType TrueType-skrifttype TrueType-Schrift γραμματοσειρά TrueType TrueType font TrueType-tiparo tipografía TrueType TrueType letra-tipoa TrueType-kirjasin TrueType stavasnið police Truetype cló TrueType tipo de letra TrueType גופן מסוג TrueType TrueType font TrueType-betűkészlet Fonta TrueType Tipo carattere TrueType TrueType フォント TrueType қарібі 트루타입 글꼴 TrueType šriftas TrueType fonts Font TrueType TrueType-skrift TrueType-lettertype TrueType-skrifttype Czcionka TrueType tipo de letra TrueType Fonte TrueType Font TrueType шрифт TrueType Písmo TrueType Datoteka pisave TrueType Lloj gërme TrueType Трутајп фонт Truetype-typsnitt TrueType fontu шрифт TrueType Phông chữ TrueType TrueType 字体 TrueType 字型 TrueType XML font خط TrueType XML Šryft TrueType XML Шрифт — TrueType XML tipus de lletra TrueType XML Písmo TrueType XML TrueType XML-skrifttype TrueType-XML-Schrift γραμματοσειρά XML TrueType TrueType XML font tipografía TrueType XML TrueType XML letra-tipoa TrueType-XML-kirjasin TrueType XML stavasnið police Truetype XML cló XML TrueType tipo de letra TrueType XML גופן XML מסוג TrueType TrueType XML font TrueType XML betűkészlet Fonta TrueType XML Tipo carattere TrueType XML TrueType XML フォント TrueType XML қарібі 트루타입 XML 글꼴 TrueType XML šriftas TrueType XML fonts TrueType XML-skrift TrueType XML-lettertype TrueType XML-skrifttype Czcionka TrueType XML Fonte TrueType XML Font XML TrueType шрифт TrueType XML Písmo TrueType XML Datoteka pisave TrueType XML Lloj gërme TrueType XML Truetype XML-typsnitt TrueType XML fontu шрифт TrueType XML Phông chữ XML TrueType TrueType XML 字体 TrueType XML 字型 V font خط V V yazı növü Šryft V Шрифт — V tipus de lletra V Písmo V Ffont V V-skrifttype V-Schrift γραμματοσειρά V V font V-tiparo tipografía V V letra-tipoa V-kirjasin V stavasnið police V cló V tipo de letra V גופן של V V font V-betűkészlet Fonta V Tipo carattere V V フォント V font қарібі V 글꼴 V šriftas V fonts Font V V-skrift V-lettertype V-skrifttype Czcionka V tipo de letra V Fonte V Font V шрифт V font Písmo V Datoteka pisave V Gërmë V V фонт V-typsnitt V fontu V-шрифт Phông chữ V V 字体 V 字型 Adobe FrameMaker document مستند أدوبي الصانع للإطارات Dakument Adobe FrameMaker Документ — Adobe FrameMaker document FrameMaker d'Adobe Dokument Adobe FrameMaker Adobe FrameMaker-dokument Adobe-FrameMaker-Dokument αρχείο Adobe FrameMaker Adobe FrameMaker document Dokumento de Adobe FrameMaker documento de Adobe FrameMaker Adobe FrameMaker-en dokumentua Adobe FrameMaker -asiakirja Adobe FrameMaker skjal document Adobe FrameMaker cáipéis Adobe FrameMaker documento de Adobe FrameMaker מסמך Adobe FrameMaker Adobe FrameMaker dokument Adobe FrameMaker-dokumentum Dokumen Adobe FrameMaker Documento Adobe FrameMaker Adobe FrameMaker ドキュメント Adobe FrameMaker-ის დოკუმენტი Adobe FrameMaker құжаты 어도비 프레임메이커 문서 Adobe FrameMaker dokumentas Adobe FrameMaker dokuments Adobe FrameMaker-dokument Adobe FrameMaker-document Adobe FrameMaker-dokument Dokument Adobe FrameMaker Documento do Adobe FrameMaker Document Adobe FrameMaker документ Adobe FrameMaker Dokument Adobe FrameMaker Dokument Adobe FrameMaker Dokument Adobe FrameMaker Adobe FrameMaker-dokument документ Adobe FrameMaker Tài liệu Adobe FrameMaker Adobe FrameMaker 文档 Adobe FrameMaker 文件 Game Boy ROM Game Boy ROM Game Boy ROM ROM — Game Boy ROM de Game Boy ROM pro Game Boy Game Boy-rom Game-Boy-ROM εικόνα μνήμης ROM GameBoy Game Boy ROM NLM de Game Boy ROM de Game Boy Game Boy-eko ROMa Game Boy -ROM Game Boy ROM ROM Game Boy ROM Game Boy ROM de Game Boy ROM של Game Boy Game Boy ROM Game Boy ROM Memori baca-saja Game Boy ROM Game Boy ゲームボーイ ROM Game Boy-ის ROM Game Boy ROM 게임보이 롬 Game Boy ROM Game Boy ROM ROM Game Boy Game Boy-ROM Game Boy-ROM Game Boy-ROM Plik ROM konsoli Game Boy ROM Game Boy ROM do Game Boy ROM Game Boy Game Boy ROM ROM pre Game Boy Bralni pomnilnik Game Boy ROM Game Boy Гејмбој РОМ Game Boy-rom ППП Game Boy ROM Game Boy Game Boy ROM Game Boy ROM Game Boy Advance ROM Game Boy Advance ROM Game Boy Advance ROM ROM — Game Boy Advance ROM de Game Boy Advance ROM pro Game Boy Advance Game Boy Advance-rom Game-Boy-Advance-ROM εικόνα μνήμης Game Boy Advance Game Boy Advance ROM ROM de Game Boy Advance Game Boy Advance-ko ROMa Game Boy Advance -ROM Game Boy Advance ROM ROM Game Boy Advance ROM Game Boy Advance ROM de Game Boy Advance ROM של Game Boy Advance Game Boy Advance ROM Game Boy Advance ROM Memori baca-saja Game Boy Advance ROM Game Boy Advance ゲームボーイアドバンス ROM Game Boy Advance-ის ROM Game Boy Advance ROM 게임보이 어드밴스 롬 Game Boy Advance ROM Game Boy Advance ROM Game Boy Advance-ROM Game Boy Advance-ROM Game Boy Advance-ROM Plik ROM konsoli Game Boy Advance ROM do Game Boy Advance ROM Game Boy Advance Game Boy Advance ROM ROM pre Game Boy Advance Bralni pomnilnik Game Boy Advance ROM Game Boy Advance Game Boy Advance-rom розширений ППП Game Boy ROM Game Boy Advance Game Boy Advance ROM Game Boy Advance ROM GDBM database قاعدة بيانات GDBM Baza źviestak GDBM База от данни — GDBM base de dades GDBM Databáze GDBM GDBM-database GDBM-Datenbank βάση δεδομένων GDBM GDBM database GDBM-datumbazo base de datos GDBM GDBM datu-basea GDBM-tietokanta GDBM dátustovnur base de données GDBM bunachar sonraí GDBM base de datos GDBM בסיס נתונים GDBM GDBM baza podataka GDBM adatbázis Basis data GDBM Database GDBM GDBM データベース GDBM მონაცემთა ბაზა GDBM дерекқоры GDBM 데이터베이스 GDBM duomenų bazė GDBM datubāze GDBM-database GDBM-gegevensbank GDBM-database Baza danych GDBM Banco de dados GDBM Bază de date GDBM база данных GDBM Databáza GDBM Podatkovna zbirka GDBM Bazë me të dhëna GDBM GDBM-databas GDBM veritabanı база даних GDBM Cơ sở dữ liệu GDBM GDBM 数据库 GDBM 資料庫 GDBM GNU Database Manager Genesis ROM Genesis ROM Genesis ROM ROM — Genesis ROM de Genesis ROM pro Genesis Genesis-rom Genesis-ROM εικόνα μνήμης ROM Genesis Genesis ROM Genesis-NLM ROM de Genesis (Mega Drive) Genesis-eko ROMa Genesis-ROM Genesis ROM ROM Mega Drive/Genesis ROM Genesis ROM xenérica ROM של Genesis Genesis ROM Genesis ROM Memori baca-saja Genesis ROM Megadrive メガドライブ ROM Genesis ROM 제네시스 롬 Genesis ROM Genesis ROM ROM Genesis Genesis-ROM Mega Drive Genesis-ROM Plik ROM konsoli Mega Drive ROM Genesis ROM do Gênesis (Mega Drive) ROM Genesis Genesis ROM ROM pre Megadrive Bralni pomnilnik Genesis ROM Genesis Genesis РОМ Genesis-rom ППП Genesis ROM Genesis Genesis ROM Genesis ROM translated messages (machine-readable) رسائل مترجمة (مقروءة آليا) pierakładzienyja paviedamleńni (dla čytańnia kamputaram) Преведени съобщения — машинен формат missatges traduïts (llegible per màquina) Přeložené zprávy (strojově čitelné) oversatte meddelelser (maskinlæsbare) Übersetzte Meldungen (maschinenlesbar) μεταφρασμένα μηνύματα (για μηχανική ανάγνωση) translated messages (machine-readable) tradukitaj mesaĝoj (maŝinlegebla) mensajes traducidos (legibles por máquinas) itzulitako mezuak (ordenagailuek irakurtzeko) käännetyt viestit (koneluettava) týdd boð (maskin-lesifør) messages traduits (lisibles par machine) teachtaireachtaí aistrithe (inléite ag meaisín) mensaxes traducidos (lexíbeis por máquinas) מסר מתורגם (מובן ע"י מכונה) prevedene poruke (strojno čitljive) lefordított üzenetek (gépi kód) pesan diterjemahkan (dapat dibaca mesin) Messaggi tradotti (leggibili da macchina) 翻訳メッセージ (マシン用) ნათარგმნი შეტყობინებები (მანქანისთვის განკუთვნილი) аударылған хабарламалар (машиналық түрде) 번역 메시지 (컴퓨터 사용 형식) išversti užrašai (kompiuteriniu formatu) pārtulkotie ziņojumi (mašīnlasāms) Mesej diterjemah (bolehdibaca-mesin) oversatte meldinger (maskinlesbar) vertaalde berichten (machine-leesbaar) oversette meldingar (maskinlesbare) Przetłumaczone komunikaty (czytelne dla komputera) mensagens traduzidas (leitura pelo computador) Mensagens traduzidas (legível pelo computador) mesaje traduse (citite de calculator) переводы сообщений (откомпилированые) Preložené správy (strojovo čitateľné) prevedena sporočila (strojni zapis) Mesazhe të përkthyer (të lexueshëm nga makina) преведене поруке (машинама читљиво) översatta meddelanden (maskinläsbara) перекладені повідомлення (у машинній формі) thông điệp đã dịch (máy đọc được) 消息翻译(机读) 翻譯訊息 (程式讀取格式) GTK+ Builder Glade project مشروع Glade Glade layihəsi Prajekt Glade Проект — Glade projecte de Glade Projekt Glade Prosiect Glade Gladeprojekt Glade-Projekt έργο Glade Glade project Glade-projekto proyecto de Glade Glade proiektua Glade-projekti Glade verkætlan projet Glade tionscadal Glade proxecto de Glade מיזם Glade Glade projekt Glade-projekt Proyek Glade Progetto Glade Glade プロジェクト Glade жобасы Glade 프로젝트 Glade projektas Glade projekts Projek Glade Glade prosjekt Glade-project Glade prosjekt Projekt Glade projecto Glade Projeto do Glade Proiect Glade проект Glade Projekt Glade Datoteka projekta Glade Projekt Glade Глејд пројекат Glade-projekt проект Glade Dự án Glade Glade 工程 Glade 專案 GMC link وصلة GMC GMC körpüsü Spasyłka GMC Връзка — GMC enllaç GMC Odkaz GMC Cyswllt GMC GMC-henvisning GMC-Verweis σύνδεσμος GMC GMC link GMC-ligilo enlace GMC GMC esteka GMC-linkki GMC leinkja lien GMC nasc GMC ligazón GMC קישור GMC GMC veza GMC-link Taut GMC Collegamento GMC GMC リンク GMC ბმული GMC сілтемесі GMC 연결 GMC nuoroda GMC saite Pautan GMC GMC-lenke GMC-verwijzing GMC-lenkje Odnośnik GMC 'link' GMC Link GMC Legătură GMC ссылка GMC Odkaz GMC Datoteka povezave GMC Lidhje GMC GMC веза GMC-länk GMC bağlantısı посилання GMC Liên kết GMC GMC 链接 GMC 鏈結 GnuCash financial data معلومات GnuCash المالية Финансови данни — GnuCash dades financeres del GnuCash Finanční data GnuCash Finansielle data til GnuCash GnuCash-Finanzdaten οικονομικά στοιχεία GnuCash GnuCash financial data datos financieros GnuCash GnuCash finantzako datuak GnuCash-taloustiedot GnuCash fíggjarligar dátur données financières GnuCash sonraí airgeadúla GnuCash datos financeiros de GNUCash מידע כלכלי של GnuCash GnuCash financijski podaci GnuCash pénzügyi adatok Data keuangan GnuCash Dati finanziari GnuCash GnuCash 会計データ GnuCash қаржы ақпараты GnuCash 재정 자료 GnuCash finansiniai duomenys GnuCash finanšu dati GnuCash financiële gegevens Dane finansowe GnuCash Dados financeiros do GnuCash Date financiare GnuCash финансовые данные GnuCash Finančné údaje GnuCash Datoteka finančnih podatkov GnuCash GnuCash-finansdata фінансові дані GnuCash Dữ liệu tài chính GnuCash GnuCash 财务数据 GnuCash 財務資料 Gnumeric spreadsheet جدول Gnumeric Raźlikovy arkuš Gnumeric Таблица — Gnumeric full de càlcul de Gnumeric Sešit Gnumeric Gnumeric-regneark Gnumeric-Tabelle Λογιστικό φύλλο Gnumeric Gnumeric spreadsheet Gnumeric-kalkultabelo hoja de cálculo de Gnumeric Gnumeric kalkulu-orria Gnumeric-taulukko Gnumeric rokniark feuille de calcul Gnumeric scarbhileog Gnumeric folla de cálculo de Gnumeric גליון עבודה Gnumeric Gnumeric proračunska tablica Gnumeric-munkafüzet Lembar sebar Gnumeric Foglio di calcolo Gnumeric Gnumeric スプレッドシート Gnumeric электрондық кестесі Gnumeric 스프레드시트 Gnumeric skaičialentė Gnumeric izklājlapa Hamparan Gnumeric Gnumeric-regneark Gnumeric-rekenblad Gnumeric-rekneark Arkusz Gnumeric folha de cálculo Gnumeric Planilha do Gnumeric Foaie de calcul Gnumeric электронная таблица Gnumeric Zošit Gnumeric Razpredelnica Gnumeric Fletë llogaritjesh Gnumeric Гнумерик табеларни рачун Gnumeric-kalkylblad ел. таблиця Gnumeric Bảng tính Gnumeric. Gnumeric 工作簿 Gnumeric 試算表 Gnuplot document مستند Gnuplot Dakument Gnuplot Документ — Gnuplot document gnuplot Dokument Gnuplot Gnuplotdokument Gnuplot-Dokument έγγραφο Gnuplot Gnuplot document Gnuplot-dokumento documento de Gnuplot Gnuplot dokumentua Gnuplot-asiakirja Gnuplot skjal document Gnuplot cáipéis Gnuplot documento de Gnuplot מסמך Gnuplot Gnuplot dokument Gnuplot dokumentum Dokumen Gnuplot Documento Gnuplot Gnuplot ドキュメント Gnuplot құжаты Gnuplot 문서 Gnuplot dokumentas Gnuplot dokuments Gnuplot-dokument Gnuplot-document Gnuplot-dokument Dokument Gnuplot Documento do Gnuplot Document Gnuplot документ Gnuplot Dokument Gnuplot Dokument Gnuplot Dokument Gnuplot Gnuplot-dokument документ Gnuplot Tài liệu Gnuplot Gnuplot 文档 Gnuplot 文件 Graphite scientific graph مبيان الجرافيت العلمي Navukovy hrafik Graphite Графика — Graphite gràfic científic Graphite Vědecký graf Graphite Graphite videnskabelig graf Wissenschaftlicher Graphite-Graph επιστημονικό γράφημα Graphite Graphite scientific graph scienca grafikaĵo de Graphite gráfica científica de Graphite Graphite - grafiko zientifikoak Graphite- tieteellinen graafi Grapite vísindarlig ritmynd graphe Graphite scientific graf eolaíoch Graphite gráfica científica de Graphite גרך מדעי של Graphite Graphite znanstveni grafikon Graphite tudományos grafikon Grafik sains Graphite Grafico scientifico Graphite Graphite scientific グラフ Graphite ғылыми кескіні Graphite 과학 그래프 Graphite mokslinė diagrama Graphite zinātniskais grafiks Graf saintifik Graphite Vitenskapelig graf fra Graphite Graphite wetenschappelijke grafiek Graphite vitskaplege graf Wykres naukowy Graphite gráfico científico Graphite Gráfico científico do Graphite Grafic științific Graphite научная диаграмма Graphite Vedecký graf Graphite Datoteka znanstvenega grafa Graphite Grafik shkencor Graphite Graphite научни графикони Vetenskaplig Graphite-grafer наукова графіка Graphite Biểu đồ khoa học Graphite Graphite 科学图形 Graphite 科學圖表 GTKtalog catalog كتالوج GTKtalog Kataloh GTKtalog Каталог — Gtktalog catàleg de GTKtalog Katalog GTKtalog GTKtalog-katalog GTKtalog-Katalog Κατάλογος GTKtalog GTKtalog catalogue katalogo de GTKtalog catálogo de GTKtalog Gtktalog katalogoa GTKtalog-luettelo GTKtalog skrá catalogue Gtktalog catalóg GTKtalog catálogo de GTKtalog קטלוג GTKtalog GTKtalog katalog GTKtalog-katalógus Katalog GTKtalog Catalogo GTKtalog GTKtalog カタログ GTKtalog-ის კატალოგი GTKtalog каталогы GTKtalog 카탈로그 GTKtalog katalogas GTKtalog katalogs Katalog GTKtalog GTKtalog-katalog GTKtalog-catalogus GTKtalog-katalog Katalog programu GTKtalog catálogo GTKtalog Catálogo GTKtalog Catalog GTKalog каталог GTKtalog Katalóg GTKtalog Datoteka kataloga GTKtalog Katallog GTKtalog Гткталог каталог GTKtalog-katalog каталог GTKtalog Phân loại GTKtalog GTKtalog 目录 GTKtalog 光碟目錄 TeX DVI document (gzip-compressed) مستند TeX DVI (مضغوط-gzip) Dakument TeX DVI (gzip-skampresavany) Документ — TeX DVI, компресиран с gzip document TeX DVI (comprimit amb gzip) Dokument TeX DVI (komprimovaný pomocí gzip) TeX DVI-dokument (gzip-komprimeret) TeX-DVI-Dokument (gzip-komprimiert) αρχείο TeX DVI (συμπιεσμένο με gzip) TeX DVI document (gzip-compressed) documento DVI de TeX (comprimido con gzip) TeX DVI dokumentua (gzip-ekin konprimitua) TeX DVI -asiakirja (gzip-pakattu) TeX DVI skjal (gzip-stappað) document DVI TeX (compressé gzip) cáipéis DVI TeX (comhbhrúite le gzip) documento DVI de TeX (comprimido con gzip) מסמך מסוג TeX DVI (מכווץ ע"י gzip) TeX DVI dokument (komprimiran gzip-om) TeX DVI dokumentum (gzip-pel tömörítve) Dokumen TeX DVI (terkompresi gzip) Documento Tex DVI (compresso con gzip) Tex DVI ドキュメント (gzip 圧縮) TeX DVI құжаты (gzip-пен сығылған) TeX DVI 문서 (GZIP 압축) TeX DVI dokumentas (suglaudintas su gzip) TeX DVI dokuments (saspiests ar gzip) TeX DVI-dokument (gzip-komprimert) TeX DVI-document (ingepakt met gzip) TeX DVI-dokument (pakka med gzip) Dokument TeX DVI (kompresja gzip) Documento DVI TeX (compactado com gzip) Document TeX DVI (comprimat gzip) документ TeX DVI (сжатый gzip) Dokument TeX DVI (komprimovaný pomocou gzip) Dokument TeX DVI (stisnjen z gzip) Dokument TeX DVI (i kompresuar me gzip) TeX DVI-dokument (gzip-komprimerat) TeX DVI belgesi (gzip ile sıkıştırılmış) документ TeX DVI (стиснений gzip) Tài liệu DVI TeX (đã nén gzip) TeX DVI 文档(gzip 压缩) TeX DVI 文件 (gzip 格式壓縮) Gzip archive أرشيف Gzip Archiŭ gzip Архив — gzip arxiu gzip Archiv gzip Gzip-arkiv Gzip-Archiv συμπιεσμένο αρχείο Gzip Gzip archive Gzip-arkivo archivador Gzip Gzip artxiboa Gzip-arkisto Gzip skjalasavn archive gzip cartlann Gzip arquivo Gzip ארכיון Gzip Gzip arhiva Gzip archívum Arsip Gzip Archivio gzip Gzip アーカイブ Gzip архиві GZIP 압축 파일 Gzip archyvas Gzip arhīvs Gzip-arkiv Gzip-archief Gzip-arkiv Archiwum gzip Pacote Gzip Arhivă Gzip архив GZIP Archív gzip Datoteka arhiva Gzip Arkiv gzip Gzip-arkiv архів gzip Kho nén gzip Gzip 归档文件 Gzip 封存檔 PDF document (gzip-compressed) مستند PDF (مضغوط-gzip) Dakument PDF (gzip-skampresavany) Документ — PDF, компресиран с gzip document PDF (comprimit amb gzip) Dokument PDF (komprimovaný pomocí gzip) PDF-dokument (gzip-komprimeret) PDF-Dokument (gzip-komprimiert) έγγραφο PDF (συμπιεσμένο με gzip) PDF document (gzip-compressed) documento PDF (comprimido con gzip) PDF dokumentua (gzip-ekin konprimitua) PDF-asiakirja (gzip-pakattu) PDF skjal (gzip-stappað) document PDF (compressé gzip) cáipéis PDF (comhbhrúite le gzip) documento PDF (comprimido en gzip) מסמך PDF (מכווץ ע"י gzip) PDF dokumentum (gzip-tömörítésű) Dokumen PDF (terkompresi gzip) Documento PDF (compresso con gzip) PDF ドキュメント (gzip 圧縮) PDF құжаты (gzip-пен сығылған) PDF 문서 (GZIP 압축) PDF dokumentas (suglaudintas su gzip) PDF dokuments (saspiests ar gzip) PDF-dokument (gzip-komprimert) PDF-document (ingepakt met gzip) PDF-dokument (pakka med gzip) Dokument PDF (kompresja gzip) Documento PDF (compactado com gzip) Document PDF (comprimat gzip) документ PDF (сжатый gzip) Dokument PDF (komprimovaný pomocou gzip) Dokument PDF (stisnjen z gzip) Dokument PDF (i kompresuar me gzip) PDF-dokument (gzip-komprimerat) PDF belgesi (gzip ile sıkıştırılmış) документ PDF (стиснений gzip) Tài liệu PDF (đã nén gzip) PDF 文档(gzip 压缩) PDF 文件 (gzip 格式壓縮) PostScript document (gzip-compressed) مستند PostScript (مضغوط-gzip) Dakument PostScript (gzip-skampresavany) Документ — PostScript, компресиран с gzip document PostScript (comprimit amb gzip) Dokument PostScript (komprimovaný pomocí gzip) PostScript-dokument (gzip-komprimeret) PostScript-Dokument (gzip-komprimiert) έγγραφο PostScript (συμπιεσμένο με gzip) PostScript document (gzip-compressed) PostScript-dokumento (kunpremita per gzip) documento PostScript (comprimido con gzip) PostScript dokumentua (gzip-konprimitua) PostScript-asiakirja (gzip-pakattu) PostScript skjal (gzip-stappað) document PostScript (compressé gzip) cáipéis PostScript (comhbhrúite le gzip) documento PostScript (comprimido con gzip) מסמך PostScript (מכוות ע"י gzip) PostScript-dokumentum (gzip-pel tömörítve) Dokumen PostScript (terkompresi gzip) Documento PostScript (compresso con gzip) PostScript ドキュメント (gzip 圧縮) PostScript құжаты (gzip-пен сығылған) 포스트스크립트 문서 (GZIP 압축) PostScript dokumentas (suglaudintas su gzip) PostScript dokuments (saspiests ar gzip) Dokumen PostScript (dimampatkan-gzip) PostScript-dokument (gzip-komprimert) PostScript-document (ingepakt met gzip) PostScript-dokument (pakka med gzip) Dokument Postscript (kompresja gzip) documento PostScript (comprimido com gzip) Documento PostScript (compactado com gzip) Document PostScript (comprimat gzip) документ PostScript (сжатый gzip) Dokument PostScript (komprimovaný pomocou gzip) Dokument PostScript (stisnjen z gzip) Dokument PostScript (i kompresuar me gzip) Постскрипт документ (компресована gzip-ом) Postscript-dokument (gzip-komprimerat) документ PostScript (стиснене gzip) Tài liệu PostScript (đã nén gzip) PostScript 文档(gzip 压缩) PostScript 文件 (gzip 格式壓縮) HDF document مستند HDF HDF sənədi Dakument HDF Документ — HDF document HDF Dokument HDF Dogfen HDF HDF-dokument HDF-Dokument έγγραφο HDF HDF document HDF-dokumento documento HDF HDF dokumentua HDF-asiakirja HDF skjal document HDF cáipéis HDF documento HDF מסמך HDF HDF dokument HDF-dokumentum Dokumen HDF Documento HDF HDF ドキュメント HDF құжаты HDF 문서 HDF dokumentas HDF dokuments Dokumen HDF HDF-dokument HDF-document HDF-dokument Dokument HDF documento HDF Documento HDF Document HDF документ HDF Dokument HDF Dokument HDF Dokument HDF HDF документ HDF-dokument документ HDF Tài liệu HDF HDF 文档 HDF 文件 HDF Hierarchical Data Format IFF file IFF Interchange File Format iPod firmware برنامج عتاد الـiPod Firmware iPod Фърмуер за iPod microprogramari d'iPod Firmware iPod iPod-styreprogram iPod-Firmware εικόνα μνήμης firmware iPod iPod firmware iPod-mikroprogramaro firmware de iPod iPod firmwarea iPod-laiteohjelmisto iPod fastbúnaður firmware iPod dochtearraí iPod firmware de iPod קושחת ipod iPod-firmware peranti tegar iPod Firmware iPod iPod ファームウェア iPod микробағдарламасы iPod 펌웨어 iPod programinė įranga iPod aparātprogrammatūra Firmware iPod iPod-firmware iPod-firmware iPod-firmvare Oprogramowanie wewnętrzne iPod 'firmware' iPod Firmware do iPod Firmware iPod микропрограмма iPod Firmware iPod Programska strojna oprema iPod Firmware iPod iPod програм fast iPod-program мікропрограма iPod phần vững iPod iPod 固件 iPod 韌體 Java archive أرشيف Java Archiŭ Java Архив — Java arxiu Java Archiv Java Javaarkiv Java-Archiv αρχείο Java Java archive Java-arkivo archivador Java Java artxiboa Java-arkisto Java skjalasavn archive Java cartlann Java arquivo Java ארכיון Java Java arhiva Java-archívum Arsip Java Archivio Java Java アーカイブ Java архиві 자바 묶음 파일 Java archyvas Java arhīvs Arkib Java Java-arkiv Java-archief Java-arkiv Archiwum Java arquivo Java Pacote Java Arhivă Java архив Java Archív Java Datoteka arhiva Java Arkiv Java Јава архива Java-arkiv Java arşivi архів Java Kho nén Java Java 归档文件 Java 封存檔 Java class صنف java Klasa Java Клас на Java classe Java Třída Java Javaklasse Java-Klasse κλάση Java Java class Java-klaso clase Java Java-ko klasea Java-luokka Java flokkur classe Java aicme Java clase de Java מחלקת Java Java klasa Java-osztály Kelas Java Classe Java Java クラス Java класы 자바 클래스 Java klasė Java klase Kelas Java Java-klasse Java-klasse Java-klasse Klasa Java classe Java Classe Java Clasă Java класс Java Trieda Java Datoteka razreda Java Klasë Java Јава класа Java-klass Java sınıfı клас Java Hạng Java Java 类 Java class JNLP file ملف JNLP Fajł JNLP Файл — JNLP fitxer JNLP Soubor JNLP JNPL-fil JNLP-Datei αρχείο JNLP JNLP file JNLP-dosiero archivo JNPL JNLP fitxategia JNLP-tiedosto JNLP fíla fichier JNLP comhad JNLP ficheiro JNLP קובץ JNLP JNLP datoteka JNLP fájl Berkas JNLP File JNPL JNLP ファイル JNLP файлы JNLP 파일 JNLP failas JNLP datne JNLP-fil JNLP-bestand JNLP-fil Plik JNLP Arquivo JNLP Fișier JNLP файл JNLP Súbor JNLP Datoteka JNLP File JNLP JNLP-fil JNLP dosyası файл JNLP Tập tin JNLP JNLP 文件 JNLP 檔案 JNLP Java Network Launching Protocol Java keystore مخزن مفاتيح جافا Ключодържател — Java magatzem de claus Java Java keystore Javanøglelager Java-Schlüsselbund χώρος αποθήκευσης κλειδιών Java Java keystore almacén de claves de Java Java-ren gako-biltegia Java-avainvarasto Java lyklagoymsla stockage de clés Java eochairstór Java almacén de chaves de Java הקשת מקלדת של Java Java kulcstároló Penyimpanan kunci Java Keystore Java Java キーストア Java сақталымы 자바 키 저장소 Java raktų saugykla Java keystore Java keystore Baza kluczy Java Keystore de Java Stocare chei Java хранилище ключей Java Úložisko kľúčov Java Datoteka tipkovne razporeditve Java Java-nyckellager сховище ключів Java Java 密钥库 Java 金鑰儲存 Java JCE keystore مخزن مفاتيح Java JCE Ключодържател — Java JCE magatzem de claus JCE Java Java JCE keystore Java JCE-nøglelager Java JCE-Schlüsselbund Java JCE keystore almacén de claves JCE de Java Java JCE-ren gako-biltegia Java JCE -avainvarasto Java JCE lyklagoymsla stockage de clés Java JCE eochairstór Java JCE almacén de chves JCE de Java הקשה מסוג Java JCE Java JCE kulcstároló Penyimpanan kunci Java JCE Keystore Java JCE Java JCE キーストア Java JCE сақталымы 자바 JCE 키 저장소 Java JCE raktų saugykla Java JCE keystore Java JCE keystore Baza kluczy Java JCE Keystore JCE do Java Stocare chei Java JCE хранилище ключей Java JCE Úložisko kľúčov Java JCE Datoteka tipkovne razporeditve Java JCE Java JCE-nyckellager сховище ключів JCE Java Java JCE 密钥库 Java JCE 金鑰儲存 JCE Java Cryptography Extension Pack200 Java archive أرشيف Pack200 Java Archiŭ Pack200 Java Архив — Java Pack200 arxiu Java Pack200 Archiv Java Pack200 Pack200 Java-arkiv Pack200-Java-Archiv αρχείο Java Pack200 Pack200 Java archive archivador Pack200 Java Pack2000 Java artxiboa Pack200-Java-arkisto Pack200 Java skjalasavn archive Java Pack200 cartlann Java Pack200 arquivo Pack200 Java ארכיון מסוג Pack200 Java Pack200 Java-archívum Arsip Pack200 Java Archivio Pack200 Java Pack200 Java アーカイブ Pack200 Java архиві Pack200 자바 압축 파일 Pack200 Java archyvas Pack200 Java arhīvs Pack200 Java-arkiv Pack200 Java-archief Pack200 Java-arkiv Archiwum Java Pack200 Pacote Java Pack200 Arhivă Java Pack2000 архив Java Pack200 Archív Java Pack200 Datoteka arhiva Pack200 Java Arkiv Java Pack200 Pack200 Java-arkiv Pack200 Java arşivi архів Java Pack200 Kho nén Java Pack200 Pack200 Java 归档文件 Pack200 Java 封存檔 JavaScript program برنامج جافاسكربت Prahrama JavaScript Програма на JavaScript programa JavaScript Program v JavaScriptu JavaScript-program JavaScript-Programm πρόγραμμα JavaScript JavaScript program JavaScript-programo programa en JavaScript JavaScript programa JavaScript-ohjelma JavaScript forrit programme JavaScript ríomhchlár JavaScript programa JavaScript תכנית JavaScript JavaScript program JavaScript-program Program JavaScript Programma JavaScript JavaScript プログラム JavaScript бағдарламасы 자바스크립트 프로그램 JavaScript programa JavaScript programma Program JavaScript JavaScript-program JavaScript-programma JavaScript-program Pogram JavaScript Programa JavaScript Program JavaScript сценарий JavaScript Program jazyka JavaScript Programska datoteka JavaScript Program JavaScript JavaScript-program JavaScript programı програма мовою JavaScript Chương trình JavaScript Javascript 程序 JavaScript 程式 JBuilder project مشروع JBuilder Prajekt JBuilder Проект — JBuilder projecte de JBuilder Projekt JBuilder JBuilder-projekt JBuilder-Projekt έργο JBuilder JBuilder project JBuilder-projekto proyecto JBuilder JBuilder proiektua JBuilder-projekti JBuilder verkætlan projet JBuilder tionscadal JBuilder proxecto de JBuilder מיזם JBuilder JBuilder projekt JBuilder-projekt Proyek JBuilder Progetto JBuilder JBuilder プロジェクト JBuilder жобасы JBuilder 프로젝트 JBuilder projektas JBuilder projekts Projek JBuilder JBuilder-prosjekt JBuilder-project JBuilder-prosjekt Projekt JBuilder projecto JBuilder Projeto do JBuilder Proiect JBuilder проект JBuilder Projekt JBuilder Datoteka projekta JBuilder Projekt JBuilder JBuilder пројекат JBuilder-projekt JBuilder projesi проект JBuilder Dự án JBuilder JBuilder 工程 JBuilder 專案 Karbon14 drawing تصميم Karbon14 Rysunak Karbon14 Чертеж — Karbon14 dibuix de Karbon14 Kresba Karbon14 Karbon14-tegning Karbon14-Zeichnung σχέδιο Karbon14 Karbon14 drawing Karbon14-grafikaĵo dibujo de Karbon14 Karbon14 marrazkia Karbon14-piirros Karbon14 tekning dessin Karbon14 líníocht Karbon14 debuxo de Karbon14 ציור Karbon14 Karbon14 crtež Karbon14-rajz Gambar Karbon14 Disegno Karbon14 Karbon14 ドロー Karbon14 суреті Karbon14 그림 Karbon14 piešinys Karbon14 zīmējums Lukisan Karbon14 Karbon14-tegning Karbon14-tekening Karbon14-teikning Rysunek Karbon14 desenho Karbon14 Desenho do Karbon14 Desen Karbon14 изображение Karbon14 Kresba Karbon14 Datoteka risbe Karbon14 Vizatim Karbon14 Karbon14 цртеж Karbon14-teckning Karbon14 çizimi малюнок Karbon14 Bản vẽ Karbon14 Karbon14 绘图 Karbon14 繪圖 KChart chart رسم بياني KChart Hrafik KChart Диаграма — KChart diagrama de KChart Graf Chart KChart-diagram KChart-Diagramm γράφημα KChart KChart chart KChart-diagramo gráfica de KChart KChart diagrama KChart-kaavio KChart strikumynd graphique KChart cairt KChart gráfica de KChart תרשים KChart KChart grafikon KChart-grafikon Bagan KChart Grafico KChart KChart チャート KChart диаграммасы KChart 차트 KChart diagrama KChart diagramma Carta KChart KChart-graf KChart-grafiek KChart-diagram Wykres KChart gráfico KChart Gráfico do KChart Diagramă KChart диаграмма KChart Graf KChart Datoteka grafikona KChart Grafik KChart KChart графикон KChart-diagram діаграма KChart Sơ đồ KChart KChart 图表 KChart 圖表 Kexi settings for database server connection إعدادات Kexi للإتصال بخادم قاعدة البيانات Връзка към база от данни — Kexi configuració del Kexi per a la connexió al servidor de bases de dades Nastavení Kexi ke spojení s databázovým serverem Kexiopsætning til forbindelsen for databaseserveren Kexi-Einstellungen für Verbindung zum Datenbankserver ρυθμίσεις Kexi για σύνδεση με εξυπηρετητή βάσεων δεδομένων Kexi settings for database server connection configuración de Kexi para la conexión con el servidor de bases de datos Kexi-ren ezarpenak datu-basearen zerbitzariarekin konektatzeko Kexi-tietokantayhteysasetukset Kexi stillingar fyri dátustovnsambætara sambinding paramètres Kexi pour connexion au serveur de base de données socruithe Kexi do cheangal le freastalaí bunachair sonraí configuración de Kexi para conexión con servidor de base de datos הגדרות של Kexi עבור חיבור שרת לבסיס נתונים Kexi beállítások adatbáziskiszolgáló-kapcsolathoz Tatanan Kexi bagi koneksi server basis data Impostazioni Kexi per connessione a server di database データベースサーバ接続用の Kexi 設定 Дерекқор серверге байланыс Kexi баптаулары Kexi 데이터베이스 서버 연결 설정 Kexi duomenų bazės ryšio su serveriu parametrai Kexi iestatījumi datubāzes servera savienojumam Kexi instellingen voor database server connectie Ustawienia Kexi dla połączenia serwera bazy danych Configurações do Kexi para conexão a servidor de banco de dados Configurări Kexi pentru conexiunea la serverul de baze de date параметры Kexi для подключения к серверу БД Nastavenia Kexi pre pripojenie k databázovému serveru Strežniška povezava do nastavitvene datoteke Kexi. Kexi-inställningar för anslutning till databasserver параметри Kexi для встановлення з’єднання з сервером бази даних Kexi 数据库服务器连接设置 Kexi 設定值 (資料庫伺服器連線用) shortcut to Kexi project on database server اختصار لمشروع Kexi على خادم قاعدة بيانات Връзка към проект — Kexi drecera a projecte del Kexi en un servidor de base de dades Zástupce projektu Kexi na databázovém serveru genvej til Kexiprojekt på databaseserver Schnellzugriff zum Kexi-Projekt auf dem Datenbankserver συντόμευση σε έργο Kexi στον εξυπηρετητή βάσης δεδομένων shortcut to Kexi project on database server acceso directo a proyecto Kexi en el servidor de bases de datos lasterbidea datu-basearen zerbitzariko Kexi proiekturako snarvegur til Kexi verkætlan á dátustovnsambætara raccourci vers projet Kexi sur serveur de base de données aicearra go tionscadal Kexi ar fhreastalaí bunachair sonraí acceso directo a proxecto Kexi no servidor de bases de datos קיצור דרך לפרוירט Kexi בשרת נתונים prečac za Kexi projekt na poslužitelju baze podataka indítóikon adatbázis-kiszolgálón lévő Kexi projektre pintasan ke projek Kexi pada server basis data Scorciatoia a progetto Kexi su server di database データベースサーバの Kexi プロジェクトへのショートカット дерекқор серверіндегі Kexi жобасына сілтеме 데이터베이스 서버의 Kexi 프로젝트 바로 가기 nuoroda į Kexi projektą duomenų bazės serveryje īsceļš uz Kexi projektu datubāzes serverī shortcut naar Kexi project op database server Skrót do projektu Kexi na serwerze bazy danych Atalho para projeto Kexi no servidor de banco de dados scurtătură către un proiect Kexi pe un server de baze de date ссылка на проект Kexi на сервере БД bližnjica do Kexi projekta na podatkovnem strežniku genväg till Kexi-projekt på databasserver скорочення для проекту Kexi на сервері бази даних 数据库服务器上 Kexi 项目的快捷方式 資料庫伺服器上 Kexi 專案的捷徑 Kexi database file-based project مشروع قاعدة بيانات Kexi يعتمد على ملفات Проект с база от данни — Kexi projecte basat en fitxer de base de dades del Kexi Projekt založený na souboru databáze Kexi Filbaseret projekt for Kexidatabase Dateibasiertes Kexi-Datenbankprojekt Kexi database file-based project proyecto basado en el archivo-base de datos Kexi Kexi datu-baseko fitxategian oinarritutako proiektua Kexi dátustovns fílugrundað verkætlan projet de base de données Kexi en mode fichier tionscadal bunachair sonraí Kexi bunaithe ar chomhaid proxecto baseado no ficheiro-base de datos Kexi פרויקט בסיס נתונים מבוסס-קובץ של Kexi Kexi adatbázisfájl-alapú projekt Projek berbasis berkas basis data Kexi Progetto su file di database Kexi Kexi データベース ファイルベースプロジェクト Файл негізінде жоба үшін Kexi дерекқоры Kexi 데이터베이스 파일 기반 프로젝트 Kexi duomenų bazės failo tipo projektas Kexi datubāzes datnes balstīts projekts Kexi database bestandgebaseerd project Projekt bazy danych Kexi oparty na pliku Projeto de banco de dados baseado em arquivo do Kexi Proiect bazat pe fișiere al bazei de date Kexi файловый проект базы данных Kexi Datoteka projekta podatkovne zbirke Kexi Kexi-databas för filbaserat projekt проект файлової бази даних Kexi Kexi 基于文件的数据库项目 Kexi 資料庫檔案基礎專案 Kexi database file-based project مشروع قاعدة بيانات Kexi يعتمد على ملفات Проект с база от данни — Kexi projecte basat en fitxer de base de dades del Kexi Projekt založený na souboru databáze Kexi Filbaseret projekt for Kexidatabase Dateibasiertes Kexi-Datenbankprojekt Kexi database file-based project proyecto basado en el archivo-base de datos Kexi Kexi datu-baseko fitxategian oinarritutako proiektua Kexi dátustovns fílugrundað verkætlan projet de base de données Kexi en mode fichier tionscadal bunachair sonraí Kexi bunaithe ar chomhaid proxecto baseado no ficheiro-base de datos Kexi פרויקט בסיס נתונים מבוסס-קובץ של Kexi Kexi adatbázisfájl-alapú projekt Projek berbasis berkas basis data Kexi Progetto su file di database Kexi Kexi データベース ファイルベースプロジェクト Файл негізінде жоба үшін Kexi дерекқоры Kexi 데이터베이스 파일 기반 프로젝트 Kexi duomenų bazės failo tipo projektas Kexi datubāzes datnes balstīts projekts Kexi database bestandgebaseerd project Projekt bazy danych Kexi oparty na pliku Projeto de banco de dados baseado em arquivo do Kexi Proiect bazat pe fișiere al bazei de date Kexi файловый проект базы данных Kexi Datoteka projekta podatkovne zbirke Kexi Kexi-databas för filbaserat projekt проект файлової бази даних Kexi Kexi 基于文件的数据库项目 Kexi 資料庫檔案基礎專案 KFormula formula صيغة KFormula Formuła KFormula Формула — KFormula fórmula de KFormula Vzorec KFormula KFormula-formel KFormula-Formel μαθηματικός τύπος KFormula KFormula formula KFormula-formulo fórmula de KFormula KFormula formula KFormula-kaava KFormula frymil formule KFormula foirmle KFormula fórmula de KFormula נוסחת KFormula KFormula formula KFormula-képlet Formula KFormula Formula KFormula KFormula 計算式 KFormula формуласы KFormula 수식 KFormula formulė KFormula formula Formula KFormula KFormula-formel KFormula-formule KFormula-formel Formuła KFormula fórmula KFormula Fórmula do KFormula Formulă KFormula формула KFormula Vzorec KFormula Datoteka formule KFormula Formulë KFormula KFormula формула KFormula-formel KFormula formülü формула KFormula Công thức KFormula KFormula 公式 KFormula 公式 KIllustrator drawing تصميم KIllustrator Rysunak KIllustrator Чертеж — KIllustrator dibuix de KIllustrator Kresba KIllustrator KIllustrator-tegning KIllustrator-Zeichnung σχέδιο KIllustrator KIllustrator drawing KIllustrator-grafikaĵo dibujo de KIllustrator KIllustrator marrazkia KIllustrator-piirros KIllustrator tekning dessin KIllustrator líníocht KIllustrator debuxo de KIllustrator ציור KIllustrator KIllustrator crtež KIllustrator-rajz Gambar KIllustrator Disegno KIllustrator KIllustrator ドロー KIllustrator суреті KIllustrator 그림 KIllustrator piešinys KIllustrator zīmējums Lukisan KIllustrator KIllustrator-tegning KIllustrator-tekening KIllustrator-teikning Rysunek KIllustrator desenho KIllustrator Desenho do KIllustrator Desen KIllustrator изображение KIllustrator Kresba KIllustrator Datoteka risbe KIllustrator Vizatim KIllustrator KIllustrator цртеж KIllustrator-teckning KIllustrator çizimi малюнок KIllustrator Bản vẽ KIllustrator KIllustrator 绘图 KIllustrator 繪圖 Kivio flowchart قائمة تدفق Kivio Blok-schiema Kivio Диаграма — Kivio diagrama de flux de Kivio Vývojový diagram Kivio Kiviorutediagram Kivio-Flussdiagramm διάγραμμα ροής Kivio Kivio flowchart Kivo-fluskemo diagrama de flujo de Kivio Kivio diagrama Kivio-vuokaavio Kivio leiðarit diagramme de flux Kivio sreabhchairt Kivio gráfica de fluxo de Kivio תרשים זרימה של Kivio Kivio dijagram toka Kivio-folyamatábra Bagan Kivio Diagramma di flusso Kivio Kivio フローチャート Kivio диаграммасы Kivio 흐름도 Kivio eigos diagrama Kivio blokshēma Cartalir Kivio Kivio-flytdiagram Kivio-stroomschema Kivio-flytdiagram Diagram przepływów Kivio gráfico de fluxo Kivio Fluxograma do Kivio Diagramă Kivio диаграмма Kivio Vývojový diagram Kivio Datoteka grafikona Kivio Diagramë fluksi Kivio Kivio дијаграм Kivio-flödesschema блок-схема Kivio Lược đồ Kivio Kivio 流程图 Kivio 圖表 Kontour drawing تصميم Kontour Rysunak Kontour Чертеж — Kontour dibuix de Kontour Kresba Kontour Kontourtegning Kontour-Zeichnung σχέδιο Kontour Kontour drawing Kontour-grafikaĵo dibujo de Kontour Kontour marrazkia Kontour-piirros Kontour tekning dessin Kontour líníocht Kontour debuxo de Kontour ציור Kontour Kontour crtež Kontour-rajz Gambar Kontour Disegno Kontour Kontour ドロー Kontour суреті Kontour 그림 Kontour piešinys Kontour zīmējums Lukisan Kontour Kontour-tegning Kontour-tekening Kontour-teikning Rysunek Kontour desenho Kontour Desenho do Kontour Desen Kontour изображение Kontour Kresba Kontour Datoteka risbe Kontour Vizatim Kontour Kontour цртеж Kontour-teckning Kontour çizimi малюнок Kontour Bản vẽ Kontour Kontour 绘图 Kontour 繪圖 KPovModeler scene مشهد KPovModeler Scena KPovModeler Сцена — KPovModeler escena de KPovModeler Scéna KPovModeler KPovModeler-scene KPovModeler-Szene σκηνή KPovModeler KPovModeler scene KPovModeler-sceno escena de KPovModeler KPovModeler eszena KPovModeler-näkymä KPovModeler leikmynd scène KPovModeler radharc KPovModeler escena de KPovModeler סצנת KPovModeler KPovModeler scena KPovModeler-jelenet Scene KPovModeler Scena KPovModeler KPovModeler シーン KPovModeler сахнасы KPovModeler 장면 KPovModeler scena KPovModeler aina Babak KPovModeler KPovModeler-scene KPovModeler-scène KPovModeler-scene Scena KPovModeler cenário KPovModeler Cena do KPovModeler Scenă KPovModeler сцена KPovModeler Scéna KPovModeler Datoteka scene KPovModeler Skenë KPovModeler KPovModeler сцена KPovModeler-scen сцена KPovModeler Cảnh KPovModeler KPovModeler 场景 KPovModeler 場景 KPresenter presentation عرض تقديمي KPresenter Prezentacyja KPresenter Презентация — KPresenter presentació de KPresenter Prezentace KPresenter KPresenter-præsentation KPresenter-Präsentation παρουσίαση KPresenter KPresenter presentation KPresenter-prezentaĵo presentación de KPresenter Kpresenter aurkezpena KPresenter-esitys KPresenter framløga présentation KPresenter láithreoireacht KPresenter presentación de KPresenter מצגת KPresenter KPresenter prezentacija KPresenter-bemutató Presentasi KPresenter Presentazione KPresenter KPresenter プレゼンテーション KPresenter презентациясы KPresenter 프리젠테이션 KPresenter pateiktis KPresenter prezentācija Persembahan Kpresenter KPresenter-presentasjon KPresenter-presentatie KPresenter-presentasjon Prezentacja KPresenter apresentação KPresenter Apresentação do KPresenter Prezentare KPresenter презентация KPresenter Prezentácia KPresenter Predstavitev KPresenter Prezantim i KPresenter KPresenter презентација KPresenter-presentation KPresenter sunum dosyası презентація KPresenter Trình diễn KPresenter KPresenter 演示文稿 KPresenter 簡報檔 Krita document مستند Krita Dakument Krita Документ — Krita document de Krita Dokument Krita Kritadokument Krita-Dokument έγγραφο Krita Krita document Krita-dokumento documento de Krita Krita dokumentua Krita-asiakirja Krita skjal document Krita cáipéis Krita documento de Krita מסמך Krita Krita dokument Krita-dokumentum Dokumen Krita Documento Krita Krita ドキュメント Krita құжаты Krita 문서 Krita dokumentas Krita dokuments Dokumen Krita Krita-dokument Krita-document Krita-dokument Dokument Krita documento Krita Documento do Krita Document Krita документ Krita Dokument Krita Dokument Krita Dokument Krita Krita документ Krita-dokument Krita belgesi документ Krita Tài liệu Krita Krita 文档 Krita 文件 KSpread spreadsheet جدول KSpread Raźlikovy arkuš KSpread Таблица — KSpread full de càlcul de KSpread Sešit KSpread KSpread-regneark KSpread-Tabelle λογιστικό φύλλο KSpread KSpread spreadsheet KSpread-kalkultabelo hoja de cálculo de KSpread KSpread kalkulu-orria KSpread-taulukko KSpread rokniark feuille de calcul KSpread scarbhileog KSpread folla de cálculo de KSpread גליון נתונים של Kspread KSpread proračunska tablica KSpread-munkafüzet Lembar sebar KSpread Foglio di calcolo KSpread KSpread スプレッドシート KSpread электрондық кестесі KSpread 스프레드시트 KSpread skaičialentė KSpread izklājlapa Hamparan KSpread KSpread-regneark KSpread-rekenblad KSpread-rekneark Arkusz KSpread folha de cálculo KSpread Planilha do KSpread Foaie de calcul KSpread электронная таблица KSpread Zošit KSpread Preglednica KSpread Fletë llogaritjesh KSpread KSpread табеларни прорачун KSpread-kalkylblad ел. таблиця KSpread Bảng tính KSpread KSpread 工作簿 KSpread 試算表 KSpread spreadsheet (encrypted) جدول KSpread (مشفر) Raźlikovy arkuš KSpread (zašyfravany) Таблица — KSpread, шифрирана full de càlcul de KSpread (xifrat) Sešit KSpread (šifrovaný) KSpread-regneark (krypteret) KSpread-Tabelle (verschlüsselt) λογιστικό φύλλο KSpread (κρυπτογραφημένο) KSpread spreadsheet (encrypted) KSpread-kalkultabelo (ĉifrita) hoja de cálculo de KSpread (cifrada) KSpread kalkulu-orria (enkriptatua) KSpread-taulukko (salattu) KSpread rokniark (bronglað) feuille de calcul KSpread (chiffrée) scarbhileog KSpread (criptithe) folla de cálculo de KSpread (cifrada) גליון נתונים של KSpread (מוצפן) KSpread proračunska tablica (šifrirana) KSpread-munkafüzet (titkosított) Lembar sebar KSpread (terenkripsi) Foglio di calcolo KSpread (cifrato) KSpread (暗号化) スプレッドシート KSpread электрондық кестесі (шифрленген) 암호화된 KSpread 스프레드시트 KSpread skaičialentė (užšifruota) KSpread izklājlapa (šifrēta) Hampatan KSpread (terenkripsi) KSpread-regneark (kryptert) KSpread-rekenblad (versleuteld) Kryptert KSpread-rekneark Arkusz KSpread (zaszyfrowany) folha de cálculo KSpread (cifrada) Planilha do KSpread (criptografada) Foaie de calcul KSpread (criptat) электронная таблица KSpread (зашифрованная) Zošit KSpread (šifrovaný) Preglednica KSpread (šifrirana) Fletë llogaritjesh KSpread (e kriptuar) KSpread табеларни прорачун (шифровани) KSpread-kalkylblad (krypterat) ел. таблиця KSpread (зашифрована) Bảng tính KSpread (đã mật mã) KSpread 加密工作簿 KSpread 試算表 (已加密) KSysV init package حزمة KSysV init Inicyjalny pakunak KSysV Пакет — KSysV init paquet d'inici KSysV Balíček init KSysV KSsV init-pakke KSysV-Init-Paket KSysV init package paquete de configuración de init para KSysV KSysV hasieratzeko paketea KSysV init -paketti KSysV init pakki paquet d'initialisation KSysV pacáiste thosú KSysV paquete de KsysV init חבילת KSysV init KSysV init paket KSysV init csomag Paket init KSysV Pacchetto init KSysV KSysV init パッケージ KSysV инициализация дестесі KSysV init 패키지 KSysV init paketas KSysV inicializācijas pakotne KSysV init-pakke KSysV-init-pakket KSysV init-pakke Pakiet KSysV init Pacote init do KSysV Pachet KSysV init пакет инициализации KSysV Balíček KSysV init Datoteka paketa KSysV init Paketë init KSysV KSysV init-paket пакунок KSysV init Gói sở khởi KSysV KSysV init 软件包 KSysV init 套件 Kugar document مستند Kugar Dakument Kugar Документ — Kugar document de Kugar Dokument Kugar Kugardokument Kugar-Dokument έγγραφο Kugar Kugar document Kugar-dokumento documento de Kugar Kugar dokumentua Kugar-asiakirja Kugar skjal document Kugar cáipéis Kugar documento de Kugar מסמך Kugar Kugar dokument Kugar-dokumentum Dokumen Kugar Documento Kugar KuKrita ドキュメント Kugar құжаты Kugar 문서 Kugar dokumentas Kugar dokuments Dokumen Kugar Kugar-dokument Kugar-document Kugar-dokument Dokument Kuguar documento Kugar Documento do Kugar Document Kugar документ Kugar Dokument Kugar Dokument Kugar Dokument Kugar Kugar документ Kugar-dokument Kugar belgesi документ Kugar Tài liệu Kugar Kugar 文档 Kugar 文件 KWord document مستند KWord Dakument KWord Документ — KWord document de KWord Dokument KWord Dogfen KWord KWord-dokument KWord-Dokument έγγραφο KWord KWord document KWord-dokumento documento de KWord KWord dokumentua KWord-asiakirja KWord skjal document KWord cáipéis KWord documento de KWord מסמך KWord KWord dokument KWord-dokumentum Dokumen KWord Documento KWord KWord ドキュメント KWord құжаты KWord 문서 KWord dokumentas KWord dokuments Dokumen KWord KWord-dokument KWord-document KWord-dokument Dokument KWord documento KWord Documento do KWord Document KWord документ KWord Dokument KWord Dokument KWord Dokument KWord KWord документ KWord-dokument KWord belgesi документ KWord Tài liệu KWord KWord 文档 KWord 文件 KWord document (encrypted) مستند KWord (مشفر) Dakument KWord (zašyfravany) Документ — KWord, шифриран document de KWord (xifrat) Dokument KWord (šifrovaný) KWord-dokument (krypteret) KWord-Dokument (verschlüsselt) έγγραφο KWord (κρυπτογραφημένο) KWord document (encrypted) KWord-dokumento (ĉifrita) documento de KWord (cifrado) KWord dokumentua (enkriptatua) KWord-asiakirja (salattu) KWord skjal (bronglað) document KWord (chiffré) cáipéis KWord (criptithe) documento de KWord (cifrado) מסמך KWord (מוצפן) KWord dokument (šifriran) KWord-dokumentum (titkosított) Dokumen KWord (terenkripsi) Documento KWord (cifrato) KWord (暗号化) ドキュメント KWord құжаты (шифрленген) 암호화된 KWord 문서 KWord dokumentas (užšifruotas) KWord dokuments (šifrēts) Dokumen Kword (terenkripsi) KWord-dokument (kryptert) KWord-document (versleuteld) Kryptert KWord-dokument Dokument KWord (zaszyfrowany) documento KWord (cifrado) Documento do KWord (criptografado) Document KWord (criptat) документ KWord (зашифрованный) Dokument KWord (šifrovaný) Dokument KWord (šifriran) Dokument KWord (i kriptuar) KWord документ (шифровани) KWord-dokument (krypterat) KWord belgesi (şifreli) документ KWord (зашифрований) Tài liệu KWord (đã mật mã) KWord 加密文档 KWord 文件 (已加密) LHA archive أرشيف LHA LHA arxivi Archiŭ LHA Архив — LHA arxiu LHA Archiv LHA Archif LHA LHA-arkiv LHA-Archiv αρχείο LHA LHA archive LHA-arkivo archivador LHA LHA artxiboa LHA-arkisto LHA skjalasavn archive LHA cartlann LHA arquivo LHA ארכיון LHA LHA arhiva LHA-archívum Arsip LHA Archivio LHA LHA アーカイブ LHA архиві LHA 압축 파일 LHA archyvas LHA arhīvs Arkib LHA LHA-arkiv LHA-archief LHA-arkiv Archiwum LHA arquivo LHA Pacote LHA Arhivă LHA архив LHA Archív LHA Datoteka arhiva LHA Arkiv LHA LHA архива LHA-arkiv LHA arşivi архів LHA Kho nén LHA LHA 归档文件 LHA 封存檔 LHZ archive أرشيف LHZ Archiŭ LHZ Архив — LHZ arxiu LHZ Archiv LHZ LHZ-arkiv LHZ-Archiv αρχείο LHZ LHZ archive LHZ-arkivo archivador LHZ LHZ artxiboa LHZ-arkisto LHZ skjalasavn archive LHZ cartlann LHZ arquivo LHZ ארכיון LHZ LHZ arhiva LHZ-archívum Arsip LHZ Archivio LHZ LHZ アーカイブ LHZ архиві LHZ 압축 파일 LHZ archyvas LHZ arhīvs Arkib LHZ LHZ-arkiv LHZ-archief LHZ-arkiv Archiwum LHZ arquivo LHZ Pacote LHZ Arhivă LHZ архив LHZ Archív LHZ Datoteka arhiva LHZ Arkiv LHZ LHZ архива LHZ-arkiv LHZ arşivi архів LHZ Kho nén LHZ (LHA đã nén) LHZ 归档文件 LHZ 封存檔 message catalog كتالوج الرسالة kataloh paviedamleńniaŭ Каталог със съобщения catàleg de missatges Katalog zpráv meddelelseskatalog Nachrichtenkatalog κατάλογος μηνυμάτων message catalogue katalogo de mesaĝoj catálogo de mensajes mezuen katalogoa viestiluettelo boðskrá catalogue de messages catalóg theachtaireachtaí catálogo de mensaxes קטלוג הודעות katalog poruka üzenetkatalógus katalog pesan Catalogo di messaggi メッセージカタログ мәлімдемелер каталогы 메시지 카탈로그 laiškų katalogas ziņojumu katalogs Katalog mesej meldingskatalog berichtencatalogus meldingskatalog Katalog wiadomości catálogo de mensagens Catálogo de mensagens catalog de mesaje каталог сообщений Katalóg správ katalogov sporočil Katallog mesazhesh каталог порука meddelandekatalog каталог повідомлень phân loại thông điệp 消息库 訊息目錄 LyX document مستند LyX Dakument LyX Документ — LyX document de LyX Dokument LyX LyX-dokument LyX-Dokument έγγραφο LyX LyX document LyX-dokumento documento de LyX LyX dokumentua LyX-asiakirja LyX skjal document LyX cáipéis LyX documento LyX מסמך Lyx LyX dokument LyX-dokumentum Dokumen LyX Documento LyX LyX ドキュメント LyX құжаты LyX 문서 LyX dokumentas LyX dokuments Dokumen LyX LyX-dokument LyX-document LyX-dokument Dokument LyX documento LyX Documento LyX Document LyX документ LyX Dokument LyX Dokument LyX Dokument LyX LyX документ LyX-dokument документ LyX Tài liệu LyX LyX 文档 LyX 文件 Lzip archive أرشيف Lzip Архив — lzip arxiu lzip Archiv Lzip Lzip-arkiv Lzip-Archiv συμπιεσμένο αρχείο Lzip Lzip archive Lzip-arkivo archivador Lzip Lzip artxiboa Lzip-arkisto Lzip skjalasavn archive lzip cartlann Lzip arquivo Lzip ארכיון Lzip Lzip arhiva Lzip archívum Arsip Lzip Archivio Lzip Lzip アーカイブ Lzip архиві LZIP 압축 파일 Lzip archyvas Lzip arhīvs Lzip archief Archiwum lzip Pacote Lzip Arhivă Lzip архив LZIP Archív Lzip Datoteka arhiva Lzip Lzip-arkiv архів lzip Lzip 归档文件 Lzip 封存檔 LZMA archive أرشيف LZMA Archiŭ LZMA Архив — LZMA arxiu LZMA Archiv LZMA LZHA-arkiv LZMA-Archiv συμπιεσμένο αρχείο LZMA LZMA archive LZMA-arkivo archivador LZMA LZMA artxiboa LZMA-arkisto LZMA skjalasavn archive LZMA cartlann LZMA arquivo LZMA ארכיון LZMA LZMA arhiva LZMA-archívum Arsip LZMA Archivio LZMA LZMA アーカイブ LZMA архиві LZMA 압축 파일 LZMA archyvas LZMA arhīvs LZMA-arkiv LZMA-archief LZMA-arkiv Archiwum LZMA Pacote LZMA Arhivă LZMA архив LZMA Archív LZMA Datoteka arhiva LZMA Arkiv LZMA LZMA-arkiv LZMA arşivi архів LZMA Kho nén LZMA LZMA 归档文件 LZMA 封存檔 LZMA Lempel-Ziv-Markov chain-Algorithm Tar archive (LZMA-compressed) أرشيف Tar (مضغوط-LZMA) Archiŭ tar (LZMA-skampresavany) Архив — tar, компресиран с LZMA arxiu tar (comprimit amb LZMA) Archiv tar (komprimovaný pomocí LZMA) Tar-arkiv (LZMA-komprimeret) Tar-Archiv (LZMA-komprimiert) αρχείο Tar (συμπιεσμένο με LZMA) Tar archive (LZMA-compressed) archivador Tar (comprimido con LZMA) Tar artxiboa (LZMA-rekin konprimitua) Tar-arkisto (LZMA-pakattu) Tar skjalasavn (LZMA-stappað) archive tar (compression LZMA) cartlann Tar (comhbhrúite le LZMA) arquivo Tar (comprimido con LZMA) ארכיון Tar (מכווץ ע"י LZMA) Tar arhiva (komprimirana LZMA-om) Tar archívum (LZMA-val tömörítve) Arsip Tar (terkompresi LZMA) Archivio tar (compresso con LZMA) Tar アーカイブ (LZMA 圧縮) Tar архиві (LZMA-мен сығылған) TAR 묶음 파일 (LZMA 압축) Tar archyvas (suglaudintas su LZMA) Tar arhīvs (saspiests ar LZMA) Tar-arkiv (LZMA-komprimert) Tar-archief (ingepakt met LZMA) Tar-arkiv (pakka med LZMA) Archiwum tar (kompresja LZMA) Pacote tar (compactado com LZMA) Arhivă Tar (comprimată LZMA) архив TAR (сжатый LZMA) Archív tar (komprimovaný pomocou LZMA) Datoteka arhiva Tar (stisnjen z LZMA) Arkiv tar (i kompresuar me LZMA) Tar-arkiv (LZMA-komprimerat) архів tar (стиснений LZMA) Kho nén tar (đã nén LZMA) Tar 归档文件 (LZMA 压缩) Tar 封存檔 (LZMA 格式壓縮) LZO archive أرشيف LZO Archiŭ LZO Архив — LZO arxiu LZO Archiv LZO LZO-arkiv LZO-Archiv αρχείο LZO LZO archive LZO-arkivo archivador LZO LZO artxiboa LZO-arkisto LZO skjalasavn archive LZO cartlann LZO arquivo LZO ארכיון LZO LZO arhiva LZO-archívum Arsip LZO Archivio LZO LZO アーカイブ LZO архиві LZO 압축 파일 LZO archyvas LZO arhīvs Arkib LZO LZO-arkiv LZO-archief LZO-arkiv Archiwum LZO arquivo LZO Pacote LZO Arhivă LZO архив LZO Archív LZO Datoteka arhiva LZO Arkiv LZO LZO архива LZO-arkiv LZO arşivi архів LZO Kho nén LZO LZO 归档文件 LZO 封存檔 LZO Lempel-Ziv-Oberhumer MagicPoint presentation عرض تقديمي MagicPoint Prezentacyja MagicPoint Презентация — MagicPoint presentació de MagicPoint Prezentace MagicPoint Cyflwyniad MagicPoint MagicPoint-præsentation MagicPoint-Präsentation παρουσίαση MagicPoint MagicPoint presentation MagicPoint-prezentaĵo presentación de MagicPoint MagicPoint aurkezpena MagicPoint-esitys MagicPoint framløga présentation MagicPoint láithreoireacht MagicPoint presentación de MagicPoint מצגת MagicPoint MagicPoint prezentacija MagicPoint-bemutató Presentasi MagicPoint Presentazione MagicPoint MagicPoint プレゼンテーション MagicPoint-ის პრეზენტაცია MagicPoint презентациясы MagicPoint 프리젠테이션 MagicPoint pateiktis MagicPoint prezentācija Persembahan MagicPoint MagicPoint-presentasjon MagicPoint-presentatie MagicPoint-presentasjon Prezentacja programu MagicPoint apresentação MagicPoint Apresentação do MagicPoint Prezentare MagicPoint презентация MagicPoint Prezentácia MagicPoint Predstavitev MagicPoint Prezantim MagicPoint MagicPoint презентација MagicPoint-presentation презентація MagicPoint Trình diễn MagicPoint MagicPoint 演示文稿 MagicPoint 簡報檔 Macintosh MacBinary file ملف Macintosh MacBinary Fajł Macintosh MacBinary Файл — MacBinary fitxer MacBinary de Macintosh Soubor MacBinary pro Macintosh Macintosh MacBinary-fil Macintosh-MacBinary-Datei εκτελέσιμο Macintosh MacBinary Macintosh MacBinary file MacBinary-dosiero de Macintosh archivo de Macintosh MacBinary Macintosh MacBinary fitxategia Macintosh MacBinary -tiedosto Macintosh MacBinary fíla fichier Macintosh MacBinary comhad Macintosh MacBinary ficheiro MacBinary de Macintosh קובץ בינארי של מקינטוש Macintosh MacBinary datoteka Macintosh MacBinary-fájl Berkas Macintosh MacBinary File Macintosh MacBinary MacBinary MacBinary ファイル Macintosh MacBinary файлы MacBinary 파일 Macintosh MacBinary failas Macintosh MacBinary datne Fail MacBinary Macintosh Macintosh MacBinary-fil Macintosh MacBinary-bestand Macintosh MacBinary-fil Plik MacBinary Macintosh ficheiro MacBinary de Macintosh Arquivo do Macintosh MacBinary Fișier Macintosh MacBinary файл Macintosh MacBinary Súbor pre Macintosh MacBinary Izvedljiva dvojiška datoteka Macintosh MacBinary File MacBinary Macintosh Мекинтош MacBinary датотека Macintosh MacBinary-fil файл Macintosh MacBinary Tập tin nhị phân MacBinary của Macintosh Macintosh MacBinary 文件 Macintosh MacBinary 檔 Matroska stream دفق Matroska Płyń Matroska Поток — Matroska flux Matroska Proud Matroska Matroskastrøm Matroska-Datenstrom ροή Matroska Matroska stream flujo Matroska Matroska korrontea Matroska-virta Matroska streymur flux Matroska sruth Matroska fluxo de Matroska זרימת Matroska Matroska adatfolyam Stream Matroska Stream Matroska Matroska ストリーム Matroska-ის ნაკადი Matroska ағымы Matroska 스트림 Matroska srautas Matroska straume Matroska-stream Matroska-straum Strumień Matroska Transmissão do Matroska Flux Matroska поток Matroska Stream Matroska Pretočni vir Matroska Stream Matroska Matroska-ström потік даних Matroska Luồng Matroska Matroska 流 Matroska 串流 Matroska video Matroska مرئي Videa Matroska Видео — Matroska vídeo Matroska Video Matroska Matroskavideo Matroska-Video βίντεο Matroska Matroska video Matroska-video vídeo Matroska Matroska bideoa Matroska-video Matroska video vidéo Matroska físeán Matroska vídeo de Matroska וידאו Matroska Matroska video Matroska-videó Video Matroska Video Matroska Matroska 動画 Matroska-ის ვიდეო Matroska видеосы Matroska 비디오 Matroska vaizdo įrašas Matroska video Video Matroska Matroska-film Matroska-video Matroska-video Plik wideo Matroska vídeo Matroska Vídeo Matroska Video Matroska видео Matroska Video Matroska Video datoteka Matroska Video Matroska Матрошка видео Matroska-video відеокліп Matroska Ảnh động Matroska Matroska 视频 Matroska 視訊 Matroska 3D video Matroska audio سمعي Matroska Aŭdyjo Matroska Аудио — Matroska àudio Matroska Zvuk Matroska Matroskalyd Matroska-Audio ήχος Matroska Matroska audio Matroska-sondosiero sonido Matroska Matroska audioa Matroska-ääni Matroska ljóður audio Matroska fuaim Matroska son de Matroska שמע Matroska Matroska audio Matroska hang Audio Matroska Audio Matroska Matroska オーディオ Matroska-ის აუდიო Matroska аудиосы Matroska 오디오 Matroska garso įrašas Matroska audio Matroska-lyd Matroska-audio Matroska-lyd Plik dźwiękowy Matroska Áudio Matroska Audio Matroska аудио Matroska Zvuk Matroska Zvočna datoteka Matroska Audio Matroska Matroska-ljud звук Matroska Âm thanh Matroska Matroska 音频 Matroska 音訊 WebM video WebM مرئي Видео — WebM vídeo WebM Video WebM WebM-video WebM-Video βίντεο WebM WebM video WebM-video vídeo WebM WebM-video WebM video vidéo WebM físeán WebM vídeo WebM וידאו WebM WebM video WebM videó Video WebM Video WebM WebM 動画 WebM видеосы WebM 비디오 WebM vaizdo įrašas WebM video WebM video Plik wideo WebM Vídeo WebM Video WebM видео WebM Video WebM Video datoteka WebM WebM-video відео WebM WebM 视频 WebM 視訊 WebM audio WebM سمعي Аудио — WebM àudio WebM Zvuk WebM WebM-lyd WebM-Audio ήχος WebM WebM audio WebM-sondosiero sonido WebM WebM-ääni WebM ljóður audio WebM fuaim WebM son WebM שמע WebM WebM audio WebM hang Audio WebM Audio WebM WebM オーディオ WebM аудиосы WebM 오디오 WebM garso įrašas WebM audio WebM audio Plik dźwiękowy WebM Áudio WebM Audio WebM аудио WebM Zvuk WebM Zvočna datoteka WebM WebM-ljud звук WebM WebM 音频 WebM 音訊 MHTML web archive MHTML MIME HTML MXF video MXF مرئي Видео — MXF vídeo MXF Video MXF MXF-video MXF-Video βίντεο MXF MXF video MXF-video vídeo MXF MXF bideoa MXF-video MXF video vidéo MXF físeán MXF vídeo MXF וידאו MXF MXF video MXF videó Video MXF Video MXF MXF 動画 MXF ვიდეო MXF видеосы MXF 비디오 MXF vaizdo įrašas MXF video MXF video Plik wideo MXF Vídeo MXF Video MXF видео MXF Video MXF Video datoteka MXF MXF-video відеокліп MXF MXF 视频 MXF 視訊 MXF Material Exchange Format OCL file ملف OCL Fajł OCL Файл — OCL fitxer OCL Soubor OCL OCL-fil OCL-Datei αρχείο OCL OCL file OCL-dosiero archivo OCL OCL fitxategia OCL-tiedosto OCL fíla fichier OCL comhad OCL ficheiro OCL קובץ OCL OCL datoteka OCL fájl Berkas OCL File OCL OCL ファイル OCL файлы OCL 파일 OCL failas OCL datne OCL-fil OCL-bestand OCL-fil Plik OCL Arquivo OCL Fișier OCL файл OCL Súbor OCL Datoteka OCL File OCL OCL-fil OCL dosyası файл OCL Tập tin OCL OCL 文件 OCL 檔 OCL Object Constraint Language COBOL source file Изходен код — COBOL codi font en COBOL Zdrojový soubor COBOL COBOL-kildefil COBOL-Quelldatei πηγαίο αρχείο COBOL COBOL source file COBOL-fontdosiero Archivo fuente de COBOL COBOL-lähdekoodi fichier source COBOL ficheiro fonte de COBOL קובץ מקור של COBOL COBOL izvorna datoteka COBOL forrásfájl Berkas sumber COBOL File sorgente COBOL COBOL ソースファイル COBOL-ის საწყისი ფაილი COBOL бастапқы коды COBOL 소스 파일 COBOL pirmkods COBOL bronbestand Plik źródłowy COBOL Arquivo de código-fonte em COBOL файл исходного кода на COBOL Izvorna koda COBOL COBOL-källkodsfil вихідний код мовою COBOL COBOL 源 COBOL 源檔 COBOL COmmon Business Oriented Language Mobipocket e-book Е-книга — Mobipocket llibre electrònic Mobipocket Elektronická kniha Mobipocket Mobipocket e-bog Mobipocket E-Book ηλεκτρονικό βιβλίο Mobipocket Mobipocket e-book Libro electrónico Mobipocket Mobipocket e-kirja livre numérique Mobipocket E-book Mobipocket ספר אלקטרוני של Mobipocket Mobipocket e-knjiga Mobipocket e-könyv e-book Mobipocket E-book Mobipocket Mobipocket 電子書籍 Mobipocket-ის ელწიგნი Mobipocket эл. кітабы Mobipocket 전자책 Mobipocket e-grāmata Mobipocket e-book E-book Mobipocket E-book Mobipocket электронная книга Mobipocket e-knjiga Mobipocket електронна книга Mobipocket Mobipocket 电子书 Mobipocket e-book Adobe FrameMaker MIF document مستند أدوبي الصانع للإطارات MIF Dakument Adobe FrameMaker MIF Документ — Adobe FrameMaker MIF document de FrameMaker MIF d'Adobe Dokument Adobe FrameMaker MIF Adobe FrameMaker MIF-dokument Adobe-FrameMaker-MIF-Dokument αρχείο MIF του Adobe FrameMaker Adobe FrameMaker MIF document MIF-dokumento de Adobe FrameMaker documento MIF de Adobe FrameMaker Adobe FrameMaker-en MIF dokumentua Adobe FrameMaker MIF -asiakirja Adobe FrameMaker MIF skjal document MIF Adobe FrameMaker cáipéis MIF Adobe FrameMaker documento MIF de Adobe FrameMaker מסמך MIF של Adobe FrameMaker Adobe FrameMaker MIF dokument Adobe FrameMaker MIF-dokumentum Dokumen Adobe FrameMaker MIF Documento MIF Adobe FrameMaker Adobe FrameMaker MIF ドキュメント Adobe FrameMaker-ის MIF დოკუმენტი Adobe FrameMaker MIF құжаты 어도비 프레임메이커 MIF 문서 Adobe FrameMaker MIF dokumentas Adobe FrameMaker MIF dokuments Adobe FrameMaker MIF-dokument Adobe FrameMaker MIF-document Adobe FrameMaker MIF-dokument Dokument MIF Adobe FrameMaker Documento MIF do Adobe FrameMaker Document Adobe FrameMaker MIF документ Adobe FrameMaker MIF Dokument Adobe FrameMaker MIF Dokument Adobe FrameMaker MIF Dokument MIF Adobe FrameMaker Adobe FrameMaker MIF-dokument документ Adobe FrameMaker MIF Tài liệu Adobe FrameMaker MIF Adobe FrameMaker MIF 文档 Adobe FrameMaker MIF 文件 Mozilla bookmarks علامات موزيلا Zakładki Mozilla Отметки — Mozilla llista d'adreces d'interès de Mozilla Záložky Mozilla Mozillabogmærker Mozilla-Lesezeichen σελιδοδείκτες Mozilla Mozilla bookmarks Mozilla-legosignoj marcadores de Mozilla Mozillako laster-markak Mozilla-kirjanmerkit Mozilla bókamerki marque-pages Mozilla leabharmharcanna Mozilla Marcadores de Mozilla סמניה של Mozilla Mozilla knjižne oznake Mozilla-könyvjelzők Bookmark Mozilla Segnalibri Mozilla Mozilla ブックマーク Mozilla бетбелгілері 모질라 책갈피 Mozilla žymelės Mozilla grāmatzīmes Tandabuku Mozilla Mozilla-bokmerker Mozilla-bladwijzers Mozilla-bokmerker Zakładki Mozilla marcadores do Mozilla Marcadores do Mozilla Semne de carte Mozilla закладки Mozilla Záložky Mozilla Datoteka zaznamkov Mozilla Libërshënues Mozilla Mozilla обележивачи Mozilla-bokmärken закладки Mozilla Liên kết đã lưu Mozilla Mozilla 书签 Mozilla 書籤 DOS/Windows executable تنفيذي DOS/Windows Vykonvalny fajł DOS/Windows Изпълним файл — DOS/Windows executable de DOS/Windows Spustitelný soubor pro DOS/Windows DOS-/Windowskørbar DOS/Windows-Programm εκτελέσιμο DOS/Windows DOS/Windows executable DOS/Windows-plenumebla ejecutable de DOS/Windows DOS/Windows-eko exekutagarria DOS/Windows-ohjelma DOS/Windows inningarfør exécutable DOS/Windows comhad inrite DOS/Windows executábel de DOS/Windows קובץ בר־הרצה של DOS/חלונות DOS/Windows izvršna datoteka DOS/Windows futtatható DOS/Windows dapat dieksekusi Eseguibile DOS/Windows DOS/Windows 実行ファイル DOS/Windows გაშვებადი ფაილი DOS/Windows орындалатын файлы DOS/Windows 실행 파일 DOS/Windows vykdomasis failas DOS/Windows izpildāmais Bolehlaksana DOS/Windows Kjørbar fil for DOS/Windows DOS/Windows-uitvoerbaar bestand DOS/Windows køyrbar fil Program DOS/Windows executável DOS/Windows Executável do DOS/Windows Executabil DOS/Windows исполняемый файл DOS/Windows Spustiteľný súbor pre DOS/Windows Izvedljiva datoteka DOS/Windows I ekzekutueshëm DOS/Windows ДОС/Виндоуз извршна датотека Körbar DOS/Windows-fil DOS/Windows çalıştırılabilir виконуваний файл DOS/Windows Tập tin có thực hiện được DOS/Windows DOS/Windows 可执行文件 DOS/Windows 可執行檔 Internet shortcut اختصار الإنترنت Sieciŭnaja spasyłka Адрес в Интернет drecera d'Internet Odkaz do Internetu Internetgenvej Internet-Verweis συντόμευση διαδικτύου Internet shortcut acceso directo a Internet Interneteko lasterbidea Internet-pikakuvake Alnetssnarvegur raccourci Internet aicearra Idirlín atallo de Internet קיצור דרך של האינטרנט Internetski prečac Internetes indítóikon Jalan pintas Internet Scorciatoia Internet インターネットショートカット Интернет сілтемесі 인터넷 바로 가기 Interneto nuoroda Interneta īsceļš Internettsnarvei internetkoppeling Internett-snarveg Skrót internetowy Atalho da internet Scurtătură Internet Интернет-ссылка Internetový odkaz Internetna bližnjica Shkurtim internet Internetgenväg інтернет-посилання Lối tắt Internet Internet 快捷方式 網際網路捷徑 WRI document مستند WRI Dakument WRI Документ — WRI document WRI Dokument WRI WRI-dokument WRI-Dokument έγγραφο WRI WRI document WRI-dokumento documento WRI WRI dokumentua WRI-asiakirja WRI skjal document WRI cáipéis WRI documento WRI מסמך WRI WRI dokument WRI dokumentum Dokumen WRI Documento WRI WRI ドキュメント WRI құжаты WRI 문서 WRI dokumentas WRI dokuments WRI-dokument WRI-document WRI-dokument Dokument WRI Documento WRI Document WRI документ WRI Dokument WRI Dokument WRI Dokument WRI WRI-dokument WRI belgesi документ WRI Tài liệu WRI WRI 文档 WRI 文件 MSX ROM MSX ROM MSX ROM ROM — MSX ROM de MSX ROM pro MSX ROM MSX MSX-rom MSX-ROM εικόνα μνήμης ROM MSX MSX ROM MSX-NLM ROM de MSX MSX-ko ROMa MSX-ROM MSX ROM ROM MSX ROM MSX ROM de MSX MSX ROM MSX ROM MSX ROM Memori baca-saja MSX ROM MSX MSX ROM MSX-ის ROM MSX ROM MSX 롬 MSX ROM MSX ROM ROM MSX MSX-ROM MSX-ROM MSX-ROM Plik ROM konsoli MSX ROM MSX ROM do MSX ROM MSX MSX ROM ROM pre MSX Bralni pomnilnik MSX ROM MSX MSX ром MSX-rom ППП MSX ROM MSX MSX ROM MSX ROM M4 macro M4 macro Makras M4 Макроси — M4 macro M4 Makro M4 M4-makro M4-Makro μακροεντολή m4 M4 macro macro M4 M4 makroa M4-makro M4 fjølvi macro M4 macra M4 macro M4 מאקרו M4 M4 makro M4 makró Makro M4 Macro M4 M4 マクロ M4 макросы M4 매크로 M4 macro M4 makross M4-makro M4-macro M4-makro Makro M4 Macro M4 Macro M4 макрос M4 Makro M4 Makro datoteka M4 Macro M4 M4-makro макрос M4 Vĩ lệnh M4 M4 宏 M4 巨集 Nintendo64 ROM Nintendo64 ROM Nintendo64 ROM ROM — Nintendo64 ROM de Nintendo64 ROM pro Nintendo64 Nintendo64-rom Nintendo64-ROM εικόνα μνήμης ROM Nintendo64 Nintendo64 ROM Nintendo64-NLM ROM de Nintendo64 Nintendo64-ko ROMa Nintendo64-ROM Nintendo64 ROM ROM Nintendo64 ROM Nintendo64 ROM de Nintendo64 ROM של Nןמאקמגם64 Nintendo64 ROM Nintendo64 ROM Memori baca-saja Nintendo64 ROM Nintendo64 Nintendo64 ROM Nintendo64 ROM 닌텐도 64 롬 Nintendo64 ROM Nintendo64 ROM ROM Nintendo64 Nintendo64-ROM Nintendo64-ROM Nintendo64-ROM Plik ROM konsoli Nintendo64 ROM Nintendo64 ROM do Nintendo64 ROM Nintendo64 Nintendo64 ROM ROM pre Nintendo64 Bralni pomnilnik Nintendo64 ROM Nintendo64 Нинтендо64 РОМ Nintendo64-rom Nintendo64 ROM ППП Nintendo64 ROM Nintendo64 Nintendo64 ROM Nintendo64 ROM Nautilus link وصلة Nautilus Nautilus körpüsü Spasyłka Nautilus Връзка — Nautilus enllaç de Nautilus Odkaz Nautilus Cyswllt Nautilus Nautilus-henvisning Nautilus-Verknüpfung σύνδεσμος Nautilus Nautilus link Nautilus-ligilo enlace de Nautilus Nautilus esteka Nautilus-linkki Nautilus leinkja lien Nautilus nasc Nautilus ligazón de nautilus קישור של Nautilus Nautilus veza Nautilus-link Taut Nautilus Collegamento Nautilus Nautilus リンク Nautilus сілтемесі 노틸러스 바로 가기 Nautilus nuoroda Nautilus saite Pautan Nautilus Nautilus-lenke Nautilus-verwijzing Nautilus-lenke Odnośnik Nautilus atalho Nautilus Link do Nautilus Legătură Nautilus ссылка Nautilus Odkaz Nautilus Datoteka povezave Nautilus Lidhje Nautilus Наутилус веза Nautiluslänk Nautilus bağlantısı посилання Nautilus Liên kết Nautilus Nautilus 链接 Nautilus 鏈結 NES ROM NES ROM NES ROM ROM — NES ROM de NES ROM pro NES ROM NES NES-rom NES-ROM εικόνα μνήμης ROM NES NES ROM NES-NLM ROM de NES NES-eko ROMa NES-ROM NES ROM ROM NES ROM NES ROM de NES ROM של NES NES ROM NES ROM Memori baca-saja NES ROM NES ファミコン ROM NES ROM NES 롬 NES ROM NES ROM ROM NES NES ROM Nintendo NES-ROM Plik ROM konsoli NES ROM NES ROM do NES ROM NES NES ROM ROM pre NES Bralni pomnilnik NES ROM NES NES ром NES-rom NES ROM ППП NES ROM NES NES ROM 任天堂 ROM Unidata NetCDF document مستند Unidata NetCDF Dakument Unidata NetCDF Документ — Unidata NetCDF document Unidata NetCDF Dokument Unidata NetCDF Unidata NetCDF-dokument Unidata-NetCDF-Dokument έγγραφο Unidata NetCDF Unidata NetCDF document dokumento en NetCDF-formato de Unidata documento de Unidata NetCDF Unidata NetCDF dokumentua Unidata NetCDF -asiakirja Unidata NetCDF skjal document Unidata NetCDF cáipéis Unidata NetCDF Documentno de Unixdata NetCDF מסמך של Unidata NetCDF Unidata NetCDF dokument Unidata NetCDF-dokumentum Dokumen Unidata NetCDF Documento Unidata NetCDF Unidata NetCDF ドキュメント Unidata NetCDF құжаты Unidata NetCDF 문서 Unidata NetCDF dokumentas Unidata NetCDF dokuments Dokumen Unidata NetCDF Unidata NetCDF-dokument Unidata NetCDF-document Unidata netCDF-dokument Dokument Unidata NetCDF documento Unidata NetCDF Documento do Unidata NetCDF Document Unidata NetCDF документ Unidata NetCDF Dokument Unidata NetCDF Dokument Unidata NetCDF Dokument Unidata NetCDF Unidata NetCDF документ Unidata NetCDF-dokument Unidata NetCDF belgesi документ Unidata NetCDF Tài liệu NetCDF Unidata Unidata NetCDF 文档 Unidata NetCDF 文件 NetCDF Network Common Data Form NewzBin usenet index Индекс — Usenet, NewzBin índex d'Usenet NewzBin Index NewzBin diskuzních skupin Usenet NewzBin-brugernetindex NewzBin-Usenet-Index ευρετήριο usenet NewzBin NewzBin usenet index Índice NewzBin de usenet index usenet Índice de usenet NEwzBin אינדקס שרתי חדשות NewzBin NewzBin usenet index Indeks usenet NewzBin Indice Usenet NewzBiz NewzBin Usenet インデックス NewzBin usenet индексі NewzBin 유즈넷 인덱스 NewzBin usenet rādītājs NewzBin usenet index Indeks grup dyskusyjnych NewzBin Índice de usenet NewzBin индекс usenet NewzBin Kazalo usenet NewzBin покажчик usenet NewzBin NewzBin usenet 索引 NewzBin usenet 索引 object code رمز الكائن abjektny kod Обектен код codi objecte Objektový kód objektkode Objekt-Code μεταφρασμένος κώδικας object code celkodo código objeto objektu kodea objektikoodi code objet cód réada código obxecto קוד אובייקט kod objekta tárgykód kode object Codice oggetto オブジェクトコード объектті коды 개체 코드 objektinis kodas objekta kods Kod objek objektkode objectcode objektkode Kod obiektowy código de objecto Código-objeto cod sursă obiect объектный код Objektový kód predmetna koda Kod objekti објектни ко̂д objektkod об'єктний код mã đối tượng 目标代码 目的碼 Annodex exchange format صيغة Annodex البديلة Формат за обмяна — Annodex format d'intercanvi Annodex Výměnný formát Annodex Udvekslingsformat for Annodex Annodex-Wechselformat μορφή ανταλλαγής Annodex Annodex exchange format formato de intercambio Annodex Annodex trukatze-formatua Annodex-siirtomuoto Annodex umbýtingarsnið format d'échange Annodex formáid mhalairte Annodex formato intercambiábel de Annodex פורמט החלפת Annodex Annodex oblik za razmjenu Annodex csereformátum Format pertukaran Annodex Formato di scambio Annodex Annodex 交換フォーマット Annodex-ის გაცვლითი ფორმატი Annodex алмасу пішімі Annodex 교환 형식 Annodex mainų formatas Annodex apmaiņas formāts Annodex-exchange Format wymiany Annodex Formato de troca Annodex Format schimb Annodex формат обмена Annodex Formát pre výmenu Annodex Izmenjalna datoteka Annodex Annodex-utväxlingsformat формат обміну даними Annodex Định dạng trao đổi Annodex Annodex 交换格式 Annodex 交換格式 Annodex Video Annodex مرئي Видео — Annodex Annodex Video Annodex Video Annodexvideo Annodex-Video βίντεο Annodex Annodex Video Annodex-video vídeo Annodex Annodex bideoa Annodex-video Annodex video vidéo Annodex físeán Annodex vídeo de Annodex וידאו Annodex Annodex Video Annodex videó Video Annodex Video Annodex Annodex 動画 Annodex-ის ვიდეო Annodex видеосы Annodex 비디오 Annodex vaizdo įrašas Annodex video Annodex Video Plik wideo Annodex Vídeo Annodex Video Annodex видео Annodex Video Annodex Video datoteka Annodex Annodex-video відеокліп Annodex Ảnh động Annodex Annodex 视频 Annodex 視訊 Annodex Audio Annodex سمعي Аудио — Annodex Annodex Audio Annodex Audio Annodexlyd Annodex-Audio ήχος Annodex Annodex Audio Annodex-sondosiero sonido Annodex Annodex audioa Annodex-ääni Annodex ljóður audio Annodex fuaim Annodex son de Annodex שמע Annodex Annodex Audio Annodex hang Audio Annodex Audio Annodex Annodex オーディオ Annodex-ის აუდიო Annodex аудиосы Annodex 오디오 Annodex garso įrašas Annodex audio Annodex Audio Plik dźwiękowy Annodex Áudio Annodex Audio Annodex аудио Annodex Zvuk Annodex Zvočna datoteka Annodex Annodex-ljud звук Annodex Âm thanh Annodex Annodex 音频 Annodex 音訊 Ogg multimedia file ملف وسائط متعددة Ogg Multymedyjny fajł Ogg Мултимедия — Ogg fitxer Ogg multimèdia Soubor multimédií Ogg Ogg multimedie-fil Ogg-Multimediadatei Αρχείο πολυμέσων Ogg Ogg multimedia file archivo multimedia Ogg Ogg multimediako fitxategia Ogg-multimediatiedosto Ogg margmiðlafíla fichier multimédia Ogg comhad ilmheán Ogg ficheiro multimedia Ogg קובץ מולטימדיה Ogg Ogg multimédiafájl Berkas multimedia Ogg File multimediale Ogg Ogg マルチメディアファイル Ogg-ის მულტიმედია ფაილი Ogg мультимедиа файлы Ogg 멀티미디어 파일 Ogg multimedijos failas Ogg multimediju datne Ogg-multimediafil Ogg-multimediabestand Ogg multimediafil Plik multimedialny Ogg Arquivo multimídia Ogg Fișier multimedia Ogg мультимедийный файл Ogg Súbor multimédií Ogg Večpredstavnostna datoteka Ogg File multimedial Ogg Ogg-multimediafil мультимедійний файл Ogg Tập tin đa phương tiện Ogg Ogg 多媒体文件 Ogg 多媒體檔案 Ogg Audio Ogg سمعي Aŭdyjo Ogg Аудио — Ogg àudio Ogg Zvuk Ogg Ogg-lyd Ogg-Audio ήχος Ogg Ogg Audio sonido Ogg Ogg audioa Ogg-ääni Ogg ljóður audio Ogg fuaim Ogg son Ogg שמע Ogg Ogg hang Audio Ogg Audio Ogg Ogg オーディオ Ogg-ის აუდიო Ogg аудиосы Ogg 오디오 Ogg garso įrašas Ogg audio Ogg lyd Ogg-audio Ogg-lyd Plik dźwiękowy Ogg Áudio Ogg Audio Ogg аудио Ogg Zvuk Ogg Zvočna datoteka Ogg Audio Ogg Ogg-ljud звук ogg Âm thanh Ogg Ogg 音频 Ogg 音訊 Ogg Video Ogg مرئي Videa Ogg Видео — Ogg vídeo Ogg Video Ogg Ogg-video Ogg-Video βίντεο Ogg Ogg Video vídeo Ogg Ogg bideoa Ogg-video Ogg Video vidéo Ogg físeán Ogg vídeo Ogg וידאו Ogg Ogg videó Video Ogg Video Ogg Ogg 動画 Ogg ვიდეო Ogg видеосы Ogg 비디오 Ogg vaizdo įrašas Ogg video Ogg video Ogg-video Ogg-video Plik wideo Ogg Vídeo Ogg Video Ogg видео Ogg Video Ogg Video datoteka Ogg Video Ogg Ogg-video відеокліп ogg Ảnh động Ogg Ogg 视频 Ogg 視訊 Ogg Vorbis audio Ogg Vorbis سمعي Ogg Vorbis audio faylı Aŭdyjo Ogg Vorbis Аудио — Ogg Vorbis àudio Ogg Vorbis Zvuk Ogg Vorbis Sain Ogg Vorbis Ogg Vorbis-lyd Ogg-Vorbis-Audio ήχος Ogg Vobris Ogg Vorbis audio Ogg-Vorbis-sondosiero sonido Ogg Vorbis Ogg Vorbis audioa Ogg Vorbis -ääni Ogg Vorbis ljóður audio Ogg Vorbis fuaim Ogg Vorbis son Ogg Vorbis שמע Ogg Vorbis Ogg Vorbis hang Audio Ogg Vorbis Audio Ogg Vorbis Ogg Vorbis オーディオ Ogg Vorbis აუდიო Ogg Vorbis аудиосы Ogg Vorbis 오디오 Ogg Vorbis garso įrašas Ogg Vorbis audio Audio Ogg Vorbis Ogg Vorbis lyd Ogg Vorbis-audio Ogg Vorbis-lyd Plik dźwiękowy Ogg Vorbis áudio Ogg Vorbis Áudio do Ogg Vorbis Audio Ogg Vorbis аудио Ogg Vorbis Zvuk Ogg Vorbis Zvočna datoteka Ogg Vorbis Audio Ogg Vorbis Ог-ворбис звучни запис Ogg Vorbis-ljud звук ogg Vorbis Âm thanh Vorbis Ogg Ogg Vorbis 音频 Ogg Vorbis 音訊 Ogg FLAC audio Ogg FLAC سمعي Aŭdyjo Ogg FLAC Аудио — Ogg FLAC àudio Ogg FLAC Zvuk Ogg FLAC Ogg FLAC-lyd Ogg-FLAC-Audio ήχος Ogg FLAC Ogg FLAC audio sonido Ogg FLAC Ogg FLAC audioa Ogg FLAC -ääni Ogg FLAC ljóður audio Ogg FLAC fuaim Ogg FLAC son Ogg FLAC שמע Ogg FLAC Ogg FLAC hang Audio Ogg FLAC Audio Ogg FLAC Ogg FLAC オーディオ Ogg FLAC აუდიო Ogg FLAC аудиосы Ogg FLAC 오디오 Ogg FLAC garso įrašas Ogg FLAC audio Ogg FLAC-lyd Ogg FLAC-audio Ogg FLAC-lyd Plik dźwiękowy Ogg FLAC Áudio FLAC Ogg Audio Ogg FLAC аудио Ogg FLAC Zvuk Ogg FLAC Zvočna datoteka Ogg FLAC Audio Ogg FLAC Ogg FLAC-ljud звук ogg FLAC Âm thanh FLAC Ogg Ogg FLAC 音频 Ogg FLAC 音訊 Ogg Speex audio Ogg Speex سمعي Aŭdyjo Ogg Speex Аудио — Ogg Speex àudio Ogg Speex Zvuk Ogg Speex Ogg Speex-lyd Ogg-Speex-Audio ήχος Ogg Speex Ogg Speex audio sonido Ogg Speex Ogg Speex audioa Ogg Speex -ääni Ogg Speex ljóður audio Ogg Speex fuaim Ogg Speex son Ogg Speex שמע Ogg Speex Ogg Speex hang Audio Ogg Speex Audio Ogg Speex Ogg Speex オーディオ Ogg Speex აუდიო Ogg Speex аудиосы Ogg Speex 오디오 Ogg Speex garso įrašas Ogg Speex audio Ogg Speex lyd Ogg Speex-audio Ogg Speex-lyd Plik dźwiękowy Ogg Speex Áudio Speex Ogg Audio Ogg Speex аудио Ogg Speex Zvuk Ogg Speex Zvočna datoteka Ogg Speex Audio Ogg Speex Ogg Speex-ljud звук ogg Speex Âm thanh Speex Ogg Ogg Speex 音频 Ogg Speex 音訊 Speex audio Speex سمعي Aŭdyjo Speex Аудио — Speex àudio Speex Zvuk Speex Speexlyd Speex-Audio ήχος Speex Speex audio sonido Speex Speex audioa Speex-ääni Speex ljóður audio Speex fuaim Speex son Speex שמע של Speex Speex audio Speex hang Audio Speex Audio Speex Speex オーディオ Speex аудиосы Speex 오디오 Speex garso įrašas Speex audio Speex lyd Speex-audio Speex-lyd Plik dźwiękowy Speex Áudio Speex Audio Speex аудио Speex Zvuk Speex Zvočna datoteka Speex Audio Speex Speex-ljud звук Speex Âm thanh Speex Speex 音频 Speex 音訊 Ogg Theora video Ogg Theora مرئي Videa Ogg Theora Видео — Ogg Theora vídeo Ogg Theora Video Ogg Theora Ogg Theora-video Ogg-Theora-Video βίντεο Ogg Theora Ogg Theora video vídeo Ogg Theora Ogg Theora bideoa Ogg Theora -video Ogg Theora video vidéo Ogg Theora físeán Ogg Theora vídeo Ogg Theora שמע Ogg Theora Ogg Theora videó Video Ogg Theora Video Ogg Theora Ogg Theora 動画 Ogg Theora ვიდეო Ogg Theora видеосы Ogg Theora 비디오 Ogg Theora vaizdo įrašas Ogg Theora video Ogg Theora video Ogg Theora-video Ogg Theora-video Plik wideo Ogg Theora Vídeo do Ogg Theora Video Ogg Theora видео Ogg Theora Video Ogg Theora Video datoteka Ogg Theora Video Ogg Theora Ogg Theora-video відеокліп ogg Theora Ảnh động Theora Ogg Ogg Theora 视频 Ogg Theora 視訊 OGM video OGM مرئي Videa OGM Видео — OGM vídeo OGM Video OGM OGM-video OGM-Video βίντεο OGM OGM video OGM-video vídeo OGM OGM bideoa OGM-video OGM video vidéo OGM físeán OGM vídeo OGM וידאו OGM OGM video OGM-videó Video OGM Video OGM OGM 動画 OGM ვიდეო OGM видеосы OGM 비디오 OGM vaizdo įrašas OGM video OGM-film OGM-video OGM-video Plik wideo OGM Vídeo OGM Video OGM видео OGM Video OGM Video datoteka OGM Video OGM OGM-video відеокліп OGM Ảnh động OGM OGM 视频 OGM 視訊 OLE2 compound document storage تخزين مجمع مستند OLE2 Schovišča dla kampanentaŭ dakumentu OLE2 Съставен документ-хранилище — OLE2 emmagatzematge de documents composats OLE2 Úložiště složeného dokumentu OLE2 OLE2-sammensat dokumentlager OLE2-Verbunddokumentenspeicher αρχείο συμπαγούς αποθήκευσης εγγράφων OLE2 OLE2 compound document storage OLE2-deponejo de parentezaj dokumentoj almacenamiento de documentos compuestos OLE2 OLE2 konposatutako dokumentu-bilduma OLE2-yhdisteasiakirjatallenne OLE2 samansett skjalagoymsla document de stockage composé OLE2 stóras cháipéisí comhshuite OLE2 almacenamento de documento composto OLE2 אחסון מסמך משותף OLE2 OLE2 összetett dokumentumtároló penyimpan dokumen kompon OLE2 Memorizzazione documento composto OLE2 OLE2 複合ドキュメントストレージ OLE2 құрама құжаттар қоймасы OLE2 복합 문서 OLE2 sudėtinių dokumentų laikmena OLE2 savienoto dokumentu glabātuve Storan dokumen halaman OLE2 OLE-lager for sammensatte dokumenter OLE2-samengestelde documentopslag OLE2 lager for samansett dokument Magazyn dokumentu złożonego OLE2 armazenamento de documento composto OLE2 Armazenamento de documento composto OLE2 Document de stocare compus OLE2 хранилище составных документов OLE2 Úložisko zloženého dokumentu OLE2 Združeni dokument OLE2 Arkiv dokumenti i përbërë OLE2 ОЛЕ2 сједињени документ Sammansatt OLE2-dokumentlager сховище складних документів OLE2 Kho lưu tài liệu ghép OLE2 OLE2 组合文档存储 OLE2 複合文件儲存 Microsoft Publisher document Windows Installer package حزمة مثبّت ويندوز Pakunak Windows Installer Пакет — инсталация за Windows paquet de Windows Installer Balíček Windows Installer Windows Installer-pakke Windows Installationspaket πακέτο Windows Installer Windows Installer package paquete de instalación de Windows Windows-eko pakete instalatzailea Windows-asennuspaketti Windows innleggingarpakki paquet d'installation Windows pacáiste Windows Installer paquete de instalación de Windows חבילה של Windows Installer Windows Installer paket Windows Installer csomag Paket Windows Installer Pacchetto Windows Installer Windodws インストーラパッケージ Windows Installer дестесі Windows 설치 패키지 Windows Installer paketas Windows Installer pakotne Windows-installatiepakket Windows Installer-pakke Pakiet instalatora Windows Pacote do instalador do Windows Pachet instalator Windows пакет Windows Installer Balík Windows Installer Datoteka paketa Windows namestilnika Paketë Windows Installer Windows Installer-paket Windows Installer paketi пакунок Windows Installer Gói cài đặt Windows Windows 程序安装包 Windows Installer 套件 GNU Oleo spreadsheet جدول جنو Oleo Raźlikovy arkuš GNU Oleo Таблица — GNU Oleo full de càlcul de GNU Oleo Sešit GNU Oleo GNU Oleo-regneark GNU-Oleo-Tabelle λογιστικό φύλλο GNU Oleo GNU Oleo spreadsheet Kalkultabelo de GNU Oleo hoja de cálculo de GNU Oleo GNU Oleo kalkulu-orria GNU Oleo -taulukko GNU Oleo rokniark feuille de calcul GNU Oleo scarbhileog GNU Oleo folla de cálculo de Oleo GNU גליון נתונים של GNU Oleo GNU Oleo proračunska tablica GNU Oleo-munkafüzet Lembar sebar GNU Oleo Foglio di calcolo GNU Oleo GNU Oleo スプレッドシート GNU Oleo ცხრილი GNU Oleo электрондық кестесі GNU Oleo 스프레드시트 GNU Oleo skaičialentė GNU Oleo izklājlapa Hamparan GNU Oleo GNU Oleo regneark GNU Oleo-rekenblad GNU Oleo-rekneark Arkusz GNU Oleo folha de cálculo GNU Oleo Planilha do GNU Oleo Foaie de calcul GNU Oleo электронная таблица GNU Oleo Zošit GNU Oleo Preglednica GNU Oleo Fletë llogaritje GNU Oleo ГНУ Oleo табеларни прорачун GNU Oleo-kalkylblad ел. таблиця GNU Oleo Bảng tính Oleo của GNU GNU Oleo 工作簿 GNU Oleo 試算表 PAK archive أرشيف PAK Archiŭ PAK Архив — PAK arxiu PAK Archiv PAK PAK-arkiv PAK-Archiv συμπιεσμένο αρχείο PAK PAK archive PAK-arkivo archivador PAK PAK artxiboa PAK-arkisto PAK skjalasavn archive PAK cartlann PAK arquivo PAK ארכיון PAK PAK-archívum Arsip PAK Archivio PAK PAK アーカイブ PAK არქივი PAK архиві PAK 압축 파일 PAK archyvas PAK arhīvs PAK-arkiv PAK-archief PAK-arkiv Archiwum PAK Pacote PAK Arhivă PAK архив PAK Archív PAK Datoteka arhiva PAK Arkiv PAK PAK-arkiv PAK arşivi архів PAK Kho nén PAK AR 归档文件 PAK 封存檔 Palm OS database قاعدة بيانات Palm OS Palm OS mə'lumat bazası Baza źviestak Palm OS База от данни — Palm OS base de dades Palm OS Databáze Palm OS Cronfa Ddata Palm OS Palm OS-database Palm-OS-Datenbank βάση δεδομένων Palm OS Palm OS database datumbazo de Palm OS base de datos de Palm OS Palm OS datu-basea Palm OS -tietokanta Palm OS dátustovnur base de données Palm OS bunachar sonraí Palm OS base de datos de Palm OS מסד נתונים של Palm OS Palm OS-adatbázis Basis data Palm OS Database Palm OS Palm OS データベース Palm OS дерекқоры Palm OS 데이터베이스 Palm OS duomenų bazė Palm OS datubāze Pangkalandata PalmOS Palm OS-database Palm OS-gegevensbank Palm OS-database Baza danych Palm OS base de dados do Palm OS Banco de dados do Palm OS Bază de date Palm OS база данных Palm OS Databáza Palm OS Podatkovna zbirka Palm OS Bankë me të dhëna Palm OS Palm OS база података Palm OS-databas Palm OS veritabanı база даних Palm OS Cơ sở dữ liệu PalmOS Palm OS 数据库 Palm OS 資料庫 Parchive archive أرشيف Parchive Archiŭ Parchive Архив — parchive arxiu Parchive Archiv Parchive Parchive-arkiv Parchive-Archiv συμπιεσμένο αρχείο Parchive Parchive archive archivador Parchive Parchive artxiboa Parchive-arkisto Parchive skjalasavn archive Parchive cartlann Parchive arquivo Parchive ארכיון של Parchive Parchive archívum Arsip Parchive Archivio Parchive Parchive アーカイブ Parchive архиві Parchive 압축 파일 Parchive archyvas Parchive arhīvs Parchive-arkiv Parchive-archief Parchive-arkiv Archiwum parchive Pacote Parchive Arhivă Parchive архив Parchive Archív Parchive Datoteka arhiva Parchive Arkiv Parchive Parchive-arkiv Parchive arşivi архів Parchive Kho nén Parchive Parchive 归档文件 Parchive 封存檔 Parchive Parity Volume Set Archive PEF executable PEF تنفيذي Vykonvalny fajł PEF Изпълним файл — PEF executable PEF Spustitelný soubor PEF PEF-kørbar PEF-Programm εκτελέσιμο PEF PEF executable PEF-plenumebla ejecutable PEF PEF exekutagarria PEF-ohjelma PEF inningarfør exécutable PEF comhad inrite PEF Executábel PEF קובץ הרצה PEF PEF futtatható PEF dapat dieksekusi Eseguibile PEF PEF 実行ファイル PEF орындалатын файлы PEF 실행 파일 PEF vykdomasis failas PEF izpildāmais Bolehlaksana PEF PEF-kjørbar PEF-uitvoerbaar bestand Køyrbar PEF-fil Program PEF executável PEF Executável PEF Executabil PEF исполняемый файл PEF Spustiteľný súbor PEF Izvedljiva datoteka PEF E ekzekutueshme PEF PEF извршна Körbar PEF-fil PEF çalıştırılabilir виконуваний файл PEF Tập tin thực hiện được PEF PEF 可执行文件 PEF 可執行檔 Perl script سكربت بيرل Skrypt Perl Скрипт — Perl script Perl Skript v Perlu Sgript Perl Perlprogram Perl-Skript πρόγραμμα εντολών Perl Perl script Perl-skripto script en Perl Perl script-a Perl-komentotiedosto Perl boðrøð script Perl script Perl Script de Perl תסריט מעטפת של Perl Perl-parancsfájl Skrip Perl Script Perl Perl スクリプト Perl сценарийі 펄 스크립트 Perl scenarijus Perl skripts Skrip Perl Perl skript Perl-script Perl-skript Skrypt Perl 'script' Perl Script Perl Script Perl сценарий Perl Skript jazyka Perl Skriptna datoteka Perl Script Perl Перл скрипта Perlskript Perl betiği скрипт на Perl Văn lệnh Perl Perl 脚本 Perl 指令稿 PHP script سكربت PHP PHP skripti Skrypt PHP Скрипт — PHP script PHP Skript PHP Sgript PHP PHP-program PHP-Skript πρόγραμμα εντολών PHP PHP script PHP-skripto script en PHP PHP script-a PHP-komentotiedosto PHP boðrøð script PHP script PHP Script de PHP תסריט מעטפת של PHP PHP-parancsfájl Skrip PHP Script PHP PHP スクリプト PHP сценарийі PHP 스크립트 PHP scenarijus PHP skripts Skrip PHP PHP-skript PHP-script PHP-skript Skrypt PHP 'script' PHP Script PHP Script PHP сценарий PHP Skript PHP Skriptna datoteka PHP Script PHP PHP скрипта PHP-skript PHP betiği скрипт PHP Văn lệnh PHP PHP 脚本 PHP 指令稿 PKCS#7 certificate bundle رزمة الشهادة PKCS#7 Сбор със сертификати — PKCS#7 conjunt de certificats PKCS#7 Svazek certifikátů PKCS#7 PKCS#7-certifikatbundt PKCS#7-Zertifikatspaket πακέτο ψηφιακών πιστοποιητικών PKCS#7 PKCS#7 certificate bundle lote de certificados PCKS#7 PKCS#7 zertifikazio sorta PKCS#7-varmennenippu PKCS#7 váttanar bundi lot de certificats PKCS#7 cuach theastas PKCS#7 paquete de certificado PKCS#7 בקשה מוסמכת PKCS#7 PKCS#7-tanúsítványcsomag Bundel sertifikat PKCS#7 Bundle certificato PKCS#7 PKCS#7 証明書 PKCS#7 сертификаттар дестесі PKCS#7 인증서 묶음 PKCS#7 liudijimų ryšulys PKCS#7 sertifikātu saišķis PKCS#7-certificaatbundel Pakiet certyfikatu PKCS#7 Pacote de certificados PKCS#7 Pachet certificat PKCS#7 пакет сертификатов PKCS#7 Zväzok certifikátov PKCS#7 Datoteka potrdila PKCS#7 PKCS#7-certifikatsamling комплект сертифікатів PKCS#7 Bó chứng nhận PKCS#7 PKCS#7 证书束 PKCS#7 憑證綁包 PKCS Public-Key Cryptography Standards PKCS#12 certificate bundle رزمة الشهادة PKCS#12 Viazka sertyfikataŭ PKCS#12 Сбор със сертификати — PKCS#12 conjunt de certificats PKCS#12 Svazek certifikátů PKCS#12 PKCS#12-certifikatbundt PKCS#12-Zertifikatspaket πακέτο ψηφιακών πιστοποιητικών PKCS#12 PKCS#12 certificate bundle ligaĵo de PKCS#12-atestiloj lote de certificados PCKS#12 PKCS#12 zertifikazio sorta PKCS#12-varmennenippu PKCS#12 váttanar bundi lot de certificats PKCS#12 cuach theastas PKCS#12 paquete de certificado PKCS#12 בקשה מוסמכת PKCS#12 PKCS#12-tanúsítványcsomag Bundel sertifikat PKCS#12 Bundle certificato PKCS#12 PKCS#12 証明書 PKCS#12 сертификаттар дестесі PKCS#12 인증서 묶음 PKCS#12 liudijimų ryšulys PKCS#12 sertifikātu saišķis Sijil PKCS#12 PKCS#12 sertifikathaug PKCS#12-certificaatbundel PKCS#12-sertifikatbunt Pakiet certyfikatu PKCS#12 conjunto de certificados PKCS#12 Pacote de certificados PKCS#12 Certificat împachetat PKCS#12 пакет сертификатов PKCS#12 Zväzok certifikátov PKCS#12 Datoteka potrdila PKCS#12 Bundle çertifikate PKCS#12 PKCS#12 пакет сертификата PKCS#12-certifikatsamling комплект сертифікатів PKCS#12 Bó chứng nhận PKCS#12 PKCS#12 证书束 PKCS#12 憑證檔 PKCS Public-Key Cryptography Standards PlanPerfect spreadsheet جدول PlanPerfect Raźlikovy arkuš PlanPerfect Таблица — PlanPerfect full de càlcul de PlanPerfect Sešit PlanPerfect PlanPerfect-regneark PlanPerfect-Tabelle φύλλο εργασίας PlanPerfect PlanPerfect spreadsheet hoja de cálculo de PlanPerfect PlanPerfect kalkulu-orria PlanPerfect-taulukko PlanPerfect rokniark feuille de calcul PlanPerfect scarbhileog PlanPerfect folla de cálculo de PlanPerfect גליון נתונים של PlanPerfect PlanPerfect táblázat Lembar sebar PlanPerfect Foglio di calcolo PlanPerfect PlanPerfect スプレッドシート PlanPerfect электрондық кестесі PlanPerfect 스프레드시트 PlanPerfect skaičialentė PlanPerfect izklājlapa PlanPerfect-regneark PlanPerfect-rekenblad PlanPerfect-rekneark Arkusz PlanPerfect Planilha do PlanPerfect Foaie de calcul PlanPerfect электронная таблица PlanPerfect Zošit PlanPerfect Preglednica PlanPerfect Fletë llogaritjesh PlanPerfect PlanPerfect-kalkylblad ел. таблиця PlanPerfect Bảng tính PlanPerfect PlanPerfect 工作簿 PlanPerfect 試算表 Pocket Word document مستند Pocket Word Документ — Pocket Word document Pocket Word Dokument Pocket Word Pocket Word-dokument Pocket-Word-Dokument έγγραφο Pocket Word Pocket Word document documento de Pocket Word Pocket Word dokumentua Pocket Word -asiakirja Pocket Word skjal document Pocket Word cáipéis Pocket Word documento de Pocket Word מסמך של Pocket Word Pocket Word dokumentum Dokumen Pocket Word Documento Pocket Word Pocket Word ドキュメント Pocket Word құжаты Pocket Word 문서 Pocket Word dokumentas Pocket Word dokuments Pocket Word-document Dokument Pocket Word Documento do Pocket Word Document Pocket Word документ Pocket Word Dokument Pocket Word Dokument Pocket Word Pocket Word-dokument Pocket Word belgesi документ Pocket Word Tài liệu Pocket Word Pocket Word 文档 Pocket Word 文件 profiler results نتائج المحلل profiler nəticələri vyniki profilera Резултати от анализатора resultats del perfilador Výsledky profiler canlyniadau proffeilio profileringsresultater Profiler-Ergebnisse αποτελέσματα μετρήσεων για την εκτέλεση προγράμματος profiler results resultoj de profililo resultados del perfilador profiler-aren emaitzak profilointitulokset résultats de profileur torthaí próifíleora resultados do perfilador תוצאות מאבחן profilírozó-eredmények hasil profiler Risultati profiler プロファイラー結果 прифильдеу нәтижелері 프로파일러 결과 profiliklio rezultatai profilētāja rezultāti Hasil pemprofil profileingsresultat profiler-resultaten profileringsresultat Wyniki profilowania resultados do 'profiler' Resultados do profiler rezultate profiler результаты профилирования Výsledky profilera rezultati profilirnika Rezultate të profiluesit резултати профилатора profilerarresultat результати профілювання kết quả nét hiện trạng profiler 结果 硬體資訊產生器成果 Pathetic Writer document مستند Pathetic Writer Dakument Pathetic Writer Документ — Pathetic Writer document de Pathetic Writer Dokument Pathetic Writer Pathetic Writer-dokument Pathetic-Writer-Dokument έγγραφο Pathetic Writer Pathetic Writer document dokumento de Pathetic Writer documento de Pathetic Writer Pathetic Writer dokumentua Pathetic Writer -asiakirja Pathetic Writer skjal document Pathetic Writer cáipéis Pathetic Writer documento de Pathetic Writer מסמך של Pathetic Writer Pathetic Writer-dokumentum Dokumen Pathetic Writer Documento Pathetic Writer Pathetic Writer ドキュメント Pathetic Writer құжаты Pathetic Writer 문서 Pathetic Writer dokumentas Pathetic Writer dokuments Dokumen Pathetic Writer Pathetic Writer-dokument Pathetic Writer-document Pathetic Writer-dokument Dokument Pathetic Writer documento do Pathetic Writer Documento do Pathetic Writer Document Pathetic Writer документ Pathetic Writer Dokument Pathetic Writer Dokument Pathetic Writer Dokument Pathetic Writer Документ Патетичног писца Pathetic Writer-dokument Pathetic Writer belgesi документ Pathetic Writer Tài liệu Pathetic Writer Pathetic Writer 文档 Pathetic Writer 文件 Python bytecode Python bytecode Python bayt kodu Bajtavy kod Python Байт код — Python bytecode de Python Bajtový kód Python Côd beit Python Pythonbytekode Python-Bytecode συμβολοκώδικας Python Python bytecode Python-bajtkodo bytecode Python Python byte-kodea Python-tavukoodi Python býtkota bytecode Python beartchód Python bytecode de Python Bytecode של Python Python-bájtkód Kode bita Python Bytecode Python Python バイトコード Python байткоды 파이썬 바이트코드 Python baitinis kodas Python bitkods Kodbait Python Python-bytekode Python-bytecode Python-bytekode Kod bajtowy Python código binário Python Código compilado Python Bytecode Python байт-код Python Bajtový kód Python Datoteka bitne kode Python Bytecode Python Питонов бајт ко̂д Python-bytekod байт-код Python Mã byte Python Python 字节码 Python 位元組碼 QtiPlot document Quattro Pro spreadsheet جدول Quattro Pro Raźlikovy arkuš Quattro Pro Таблица — Quattro Pro full de càlcul de Quattro Pro Sešit Quattro Pro Quattro Pro-regneark Quattro-Pro-Tabelle λογιστικό φύλλο Quattro Pro Quattro Pro spreadsheet sterntabelo de Quattro Pro hoja de cálculo Quattro Pro Quattro Pro kalkulu-orria Quattro Pro -taulukko Quattro Pro rokniark feuille de calcul Quattro Pro scarbhileog Quattro Pro folla de cálculo Quattro Pro גליון נתונים של Quattro Pro Quattro Pro proračunska tablica Quattro Pro-munkafüzet Lembar sebar Quattro Pro Foglio di calcolo Quattro Pro Quattro Pro スプレッドシート Quattro Pro электрондық кестесі Quattro Pro 스프레드시트 Quattro Pro skaičialentė Quattro Pro izklājlapa Hamparan Quatro Pro Quattro Pro-regneark Quattro Pro-rekenblad Quattro Pro-rekneark Arkusz Quattro Pro folha de cálculo Quattro Pro Planilha do Quattro Pro Foaie de calcul Quattro Pro электронная таблица Quattro Pro Zošit Quattro Pro Preglednica Quattro Pro Fletë llogaritjesh Quattro Pro Quattro Pro табеларни рачун Quattro Pro-kalkylblad ел. таблиця Quattro Pro Bảng tính Quattro Pro Quattro Pro 工作簿 Quattro Pro 試算表 QuickTime metalink playlist قائمة تشغيل QuickTime metalink śpis metaspasyłak na pieśni QuickTime Списък за изпълнение — QuickTime llista de reproducció de metaenllaços QuickTime Seznam skladeb metalink QuickTime QuickTime metalink-afspilningsliste QuickTime-Metalink-Wiedergabeliste λίστα αναπαραγωγής metalinks QuickTime QuickTime metalink playlist lista de reproducción de metaenlaces QuickTime QuickTime meta-esteken erreprodukzio-zerrenda QuickTime metalink -soittolista QuickTime metaleinkju avspælingarlisti liste de lecture metalink QuickTime seinmliosta meiteanasc QuickTime lista de reprodución de metaligazóns QuickTime רשימת השמעה מקושרת של QuickTime QuickTime metalink lejátszólista Senarai berkas taut meta QuickTime Scaletta metalink QuickTime QuickTime メタリンク再生リスト QuickTime метасілтемелер ойнау тізімі 퀵타임 메타링크 재생 목록 QuickTime metanuorodos grojaraštis QuickTime metasaites repertuārs QuickTime metalink-spilleliste QuickTime metalink-afspeellijst QuickTime metalink-speleliste Lista odtwarzania metaodnośników QuickTime Lista de reprodução metalink do QuickTime Listă cu metalegături QuickTime список воспроизведения мета-ссылок QuickTime Zoznam skladieb metalink QuickTime Seznam predvajanja QuickTime Listë titujsh metalink QuickTime QuickTime-metalänkspellista список відтворення QuickTime metalink Danh mục nhạc siêu liên kết Quicktime QuickTime 元链接播放列表 QuickTime metalink 播放清單 Quicken document مستند Quicken Quicken sənədi Dakument Quicken Документ — Quicken document de Quicken Dokument Quicken Dogfen Quicken Quickendokument Quicken-Dokument έγγραφο Quicken Quicken document Quicken-dokumento documento de Quicken Quicken dokumentua Quicken-asiakirja Quicken skjal document Quicken cáipéis Quicken documento de Quicken מסמך של Quicken Quicken dokument Quicken-dokumentum Dokumen Quicken Documento Quicken Quicken ドキュメント Quicken құжаты Quicken 문서 Quicken dokumentas Quicken dokuments Dokumen Quicken Quicken-dokument Quicken-document Quicken-dokument Dokument Quicken documento Quicken Documento do Quicken Document Quicken документ Quicken Dokument Quicken Dokument Quicken Dokument Quicken Quicken документ Quicken-dokument документ Quicken Tài liệu Quicken Quicken 文档 Quicken 文件 RAR archive أرشيف RAR Archiŭ RAR Архив — RAR arxiu RAR Archiv RAR Archif RAR RAR-arkiv RAR-Archiv αρχείο RAR RAR archive RAR-arkivo archivador RAR RAR artxiboa RAR-arkisto RAR skjalasavn archive RAR cartlann RAR ficheiro RAR ארכיון RAR RAR arhiva RAR-archívum Arsip RAR Archivio RAR RAR アーカイブ RAR архиві RAR 압축 파일 RAR archyvas RAR arhīvs Arkib RAR RAR-arkiv RAR-archief RAR-arkiv Archiwum RAR arquivo RAR Pacote RAR Arhivă RAR архив RAR Archív RAR Datoteka arhiva RAR Arkiv RAR РАР архива RAR-arkiv архів RAR Kho nén RAR RAR 归档文件 RAR 封存檔 RAR Roshal ARchive DAR archive أرشيف DAR Archiŭ DAR Архив — DAR arxiu DAR Archiv DAR DAR-arkiv DAR-Archiv συμπιεσμένο αρχείο DAR DAR archive DAR-arkivo archivador DAR DAR artxiboa DAR-arkisto DAR skjalasavn archive DAR cartlann DAR arquivo DAR ארכיון DAR DAR arhiva DAR archívum Arsip DAR Archivio DAR DAR アーカイブ DAR არქივი DAR архиві DAR 묶음 파일 DAR archyvas DAR arhīvs DAR-arkiv DAR-archief DAR-arkiv Archiwum DAR Pacote DAR Arhivă DAR архив DAR Archív DAR Datoteka arhiva DAR Arkiv DAR DAR-arkiv архів DAR Kho nén DAR DAR 归档文件 DAR 封存檔 Alzip archive أرشيف Alzip Archiŭ Alzip Архив — alzip arxiu Alzip Archiv Alzip Alziparkiv Alzip-Archiv Alzip archive Alzip-arkivo archivador Alzip Alzip artxiboa Alzip-arkisto Alsip skjalasavn archive alzip cartlann Alzip arquivo Alzip ארכיון Alzip Alzip arhiva Alzip archívum Arsip Alzip Archivio Alzip Alzip アーカイブ Alzip არქივი Alzip архиві 알집 압축 파일 Alzip archyvas Alzip arhīvs Alzip-arkiv Alzip-archief Alzip-arkiv Archiwum alzip Pacote Alzip Arhivă Alzip архив ALZIP Archív Alzip Datoteka arhiva Alzip Arkiv Alzip Alzip-arkiv Alzip arşivi архів Alzip Kho nén Alzip Alzip 归档文件 Alzip 封存檔 rejected patch رقعة مرفوضة niepryniaty patch Отхвърлен файл с кръпка pedaç rebutjat Odmítnutá záplata afvist tekstlap Abgelehnter Patch μπάλωμα που απορρίφθηκε rejected patch reĵeta flikaĵo parche rechazado baztertutako bide-izena hylättyjen muutosten tiedosto vrakað rætting correctif rejeté paiste diúltaithe parche rexeitado תלאי שנדחה odbijena zakrpa visszautasított folt patch ditolak Patch rifiutata 拒否されたパッチ алынбаған патч 거부된 패치 파일 atmestas lopas noraidītais ceļš Tampungan ditolak avvist patchfil verworpen patch avvist programfiks Odrzucona łata ficheiro de patch rejeitado Arquivo de patch rejeitado petec respsins отвергнутый патч Odmietnutá záplata zavrnjen popravek Patch i kthyer mbrapsht одбијена закрпа avvisad programfix відхилена латка đắp vá bị từ chối 拒绝的补丁 回絕的修補 RPM package حزمة RPM Pakunak RPM Пакет — RPM paquet RPM Balíček RPM RPM-pakke RPM-Paket πακέτο RPM RPM package RPM-pakaĵo paquete RPM RPM paketea RPM-paketti RPM pakki paquet RPM pacáiste RPM paquete RFM חבילת RPM RPM paket RPM-csomag Paket RPM Pacchetto RPM RPM パッケージ RPM дестесі RPM 패키지 RPM paketas RPM pakotne Pakej RPM RPM-pakke RPM-pakket RPM-pakke Pakiet RPM pacote RPM Pacote RPM Pachet RPM пакет RPM Balík RPM Datoteka paketa RPM Paketë RPM RPM пакет RPM-paket пакунок RPM Gói RPM RPM 软件包 RPM 套件 Source RPM package Ruby script سكربت روبي Skrypt Ruby Скрипт — Ruby script Ruby Skript v Ruby Rubyprogram Ruby-Skript πρόγραμμα εντολών Ruby Ruby script Ruby-skripto script en Ruby Ruby script-a Ruby-komentotiedosto Ruby boðrøð script Ruby script Ruby Script de Ruby תסריט Ruby Ruby skripta Ruby-parancsfájl Skrip Ruby Script Ruby Ruby スクリプト Ruby сценарийі 루비 스크립트 Ruby scenarijus Ruby skripts Skrip Ruby Ruby-skript Ruby-script Ruby-skript Skrypt Ruby 'script' Ruby Script Ruby Script Ruby сценарий Ruby Skript Ruby Skriptna datoteka Ruby Script Ruby Руби скрипта Ruby-skript Ruby betiği скрипт Ruby Văn lệnh Ruby Ruby 脚本 Ruby 指令稿 Markaby script سكربت Markaby Skrypt Markaby Скрипт — Markaby script Markaby Skript Markaby Markabyprogram Markaby-Skript δέσμη εντολών Markaby Markaby script Markaby-skripto script en Markaby Markaby script-a Markaby-komentotiedosto Markaby boðrøð script Markaby script Markaby Script de Markaby תסריט Markby Markaby skripta Markaby parancsfájl Skrip Markaby Script Markaby Markaby スクリプト Markaby-ის სცენარი Markaby сценарийі Markaby 스크립트 Markaby scenarijus Markaby skripts Markaby-skript Markaby-script Markaby-skript Skrypt Markaby Script Markaby Script Markaby сценарий Markaby Skript Markaby Skriptna datoteka Markaby Script Markaby Markaby-skript Markaby betiği скрипт Markaby Văn lệnh Markaby RMarkaby 脚本 Markaby 指令稿 SC/Xspread spreadsheet جدول SC/Xspread Raźlikovy arkuš SC/Xspread Таблица — SC/Xspread full de càlcul de SC/Xspread Sešit SC/Xspread SC/Xspread-regneark SX/Xspread-Tabelle φύλλο εργασίας SC/Xspread SC/Xspread spreadsheet SC/Xspread-kalkultabelo hoja de cálculo SC/Xspread SC/Xspread kalkulu-orria SC/Xspread-taulukko SC/Xspread rokniark feuille de calcul SC/Xspread scarbhileog SC/Xspread folla de cálculo SC/Xspread גליון נתונית של SC/Xspread SC/Xspread proračunska tablica SC/Xspread táblázat Lembar sebar SC/Xspread Foglio di calcolo SC/Xspread SC/Xspread スプレッドシート SC/Xspread электрондық кестесі SC/Xspread 스프레드시트 SC/Xspread skaičialentė SC/Xspread izklājlapa SC/Xspread-regneark SC/Xspread-rekenblad SC/Xspread-rekneark Arkusz SC/Xspread Planilha do SC/Xspread Foaie de calcul SC/Xspread электронная таблица SC/Xspread Zošit SC/Xspread Preglednica SC/Xspread Fletë llogaritjesh SC/Xspread SC/Xspread-kalkylblad ел. таблиця SC/Xspread Bảng tính SC/Xspread SC/Xspread 工作簿 SC/Xspread 試算表 shell archive أرشيف شِل qabıq arxivi archiŭ abałonki Архив на обвивката arxiu d'intèrpret d'ordres Archiv shellu archif plisgyn skalarkiv Shell-Archiv αρχείο φλοιού (SHAR) shell archive ŝel-arkivo archivador shell shell artxiboa komentotulkkiarkisto skel savn archive shell cartlann bhlaoisce ficheiro shell ארכיון מעטפת arhiva ljuske héjarchívum arsip shell Archivio shell シェルアーカイブ қоршам архиві 셸 압축 파일 shell archyvas čaulas arhīvs Arkib shell skallarkiv shell-archief skal-arkiv Archiwum powłoki arquivo de consola Pacote shell arhivă shell архив оболочки UNIX Archív shellu lupinski arhiv Arkiv shell Архива љуске (SHAR) skalarkiv архів оболонки kho trình bao shell 归档文件 shell 封存檔 libtool shared library مكتبة libtool المشتركة supolnaja biblijateka libtool Споделена библиотека — libtool biblioteca compartida libtool Sdílená knihovna libtool libtool delt bibliotek Gemeinsame libtool-Bibliothek κοινόχρηστη βιβλιοθήκη libtool libtool shared library biblioteca compartida de libtool libtool partekatutako liburutegia jaettu libtool-kirjasto libtool felagssavn bibliothèque partagée libtool comhleabharlann libtool biblioteca compartida de libtool ספריה משותפת של libtool libtool dijeljena biblioteka libtool osztott programkönyvtár pustaka bersama libtool Libreria condivisa libtool libtool 共有ライブラリ libtool ортақ жинағы libtool 공유 라이브러리 libtool bendroji biblioteka libtool koplietotā bibliotēka libtool delt bibliotek gedeelde libtool-bibliotheek libtool delt bibliotek Biblioteka współdzielona libtool Biblioteca compartilhada libtool bibliotecă partajată libtool разделяемая библиотека libtool Zdieľaná knižnica libtool Souporabna knjižnica libtool Librari e përbashkët libtool delat libtool-bibliotek спільна бібліотека libtool thư viện dùng chung libtool libtool 共享库 libtool 共享函式庫 shared library مكتبة مشتركة bölüşülmüş kitabxana supolnaja biblijateka Споделена библиотека biblioteca compartida Sdílená knihovna llyfrgell wedi ei rhannu delt bibliotek Gemeinsame Bibliothek αρχείο κοινόχρηστης βιβλιοθήκης shared library dinamike bindebla biblioteko biblioteca compartida partekatutako liburutegia jaettu kirjasto felagssavn bibliothèque partagée comhleabharlann biblioteca compartida ספרייה משותפת dijeljena biblioteka osztott programkönyvtár pustaka bersama Libreria condivisa 共有ライブラリ бөлісетін библиотека 공유 라이브러리 bendroji biblioteka koplietotā bibliotēka Pustaka terkongsi delt bibliotek gedeelde bibliotheek delt bibliotek Biblioteka współdzielona biblioteca partilhada Biblioteca compartilhada bibliotecă partajată разделяемая библиотека Zdieľaná knižnica souporabljena knjižnica Librari e përbashkët дељена библиотека delat bibliotek спільна бібліотека thư viện dùng chung 共享库 共享函式庫 shell script سكربت شِل qabıq skripti skrypt abałonki Скрипт на обвивката script d'intèrpret d'ordres Skript shellu sgript plisgyn skalprogram Shell-Skript αρχείο εντολών φλοιού shell script ŝelskripto script en shell shell script-a komentotulkin komentotiedosto skel boðrøð script shell script bhlaoisce script de shell תסריט מעטפת skripta ljuske héj-parancsfájl skrip shell Script shell シェルスクリプト қоршам сценарийі 셸 스크립트 shell scenarijus čaulas skripts Skrip shell skallskript shellscript skalskript Skrypt powłoki 'script' de consola Script shell script shell сценарий оболочки UNIX Skript shellu lupinski skript Script shell скрипта љуске skalskript скрипт оболонки văn lệnh trình bao shell 脚本 shell 指令稿 Shockwave Flash file ملف Shockwave Flash Fajł Shockwave Flash Файл — Shockwave Flash fitxer Shockwave Flash Soubor Shockwave Flash Shockwave Flash-fil Shockwave-Flash-Datei αρχείο Shockwave Flash Shockwave Flash file dosiero de Shockwave Flash archivo Shockwave Flash Shockwave Flash fitxategia Shockwave Flash -tiedosto Shockwave Flash fíla fichier Shockwave Flash comhad Shockwave Flash ficheiro sockwave Flash קובץ של Shockwave Flash Shockwave Flash datoteka Shockwave Flash-fájl Berkas Shockwave Flash File Shockwave Flash Shockwave Flash ファイル Shockwave Flash файлы Shockwave 플래시 파일 Shockwave Flash failas Shockwave Flash datne Fail Shockwave Flash Shockwave Flash-fil Shockwave Flash-bestand Shockwave Flash-fil Plik Shockwave Flash ficheiro Shockwave Flash Arquivo Shockwave Flash Fișier Shockwave Flash файл Shockwave Flash Súbor Shockwave Flash Datoteka Shockwave Flash File Flash Shockwave Шоквејв Флеш датотека Shockwave Flash-fil файл Shockwave Flash Tập tin Flash Shockwave Shockwave Flash 文件 Shockwave Flash 檔 Shorten audio Shorten سمعي Aŭdyjo Shorten Аудио — Shorten àudio Shorten Zvuk Shorten Shortenlyd Shorten-Audio ήχος Shorten Shorten audio Shorten-sondosiero sonido Shorten Shorten audioa Shorten-ääni Shorten ljóður audio Shorten fuaim Shorten son Shorten שמע של Shorten Shorten audio Shorten hang Audio Shorten Audio Shorten Shorten オーディオ Shorten аудиосы Shorten 오디오 Shorten garso įrašas Shorten audio Shorten lyd Shorten-audio Shorten-lyd Plik dźwiękowy Shorten Áudio Shorten Audio Shorten аудио Shorten Zvuk Shorten Zvočna datoteka Shorten Audio Shorten Shorten-ljud звук Shorten Âm thanh Shorten Shorten 音频 Shorten 音訊 Siag spreadsheet جدول Siag Raźlikovy arkuš Siag Таблица — Siag full de càlcul Siag Sešit Siag Siagregneark Siag-Tabelle λογιστικό φύλλο Siag Siag spreadsheet Siag-kalkultabelo hoja de cálculo de Siag Siag kalkulu-orria Siag-taulukko Siag rokniark feuille de calcul Siag scarbhileog Siag folla de cálculo de Siag גליון נתונים של Siag Siag proračunska tablica Siag-munkafüzet Lembar sebar Siag Foglio di calcolo Siag Siag スプレッドシート Siag электрондық кестесі Siag 스프레드시트 Siag skaičialentė Siag izklājlapa Hamparan Siag Siag-regneark Siag-rekenblad Siag-rekneark Arkusz Siag folha de cálculo Siag Planilha do Siag Foaie de calcul Siag электронная таблица Siag Zošit Siag Preglednica Siag Fletë llogaritjesh Siag Siag табеларни прорачун Siag-kalkylblad ел. таблиця Siag Bảng tính Slag Siag 工作簿 Siag 試算表 Skencil document مستند Skencil Dakument Skencil Документ — Skencil document Skencil Dokument Skencil Skencildokument Skencil-Dokument έγγραφο Skencil Skencil document Skencil-dokumento documento Skencil Skencil dokumentua Skencil-asiakirja Skencil skjal document Skencil cáipéis Skencil documento Skencil מסמך Skencil Skencil dokument Skencil-dokumentum Dokumen Skencil Documento Skencil Skencil ドキュメント Skencil құжаты Skencil 문서 Skencil dokumentas Skencil dokuments Skencil-document Skencil-dokument Dokument Skencil Documento do Skencil Document Skencil документ Skencil Dokument Skencil Dokument Skencil Dokument Skencil Skencil-dokument Skencil belgesi документ Skencil Tài liệu Skencil Skencil 文档 Skencil 文件 Stampede package حزمة Stampede Stampede paketi Pakunak Stampede Пакет — Stampede paquet Stampede Balíček Stampede Pecyn Stampede Stampedepakke Stampede-Paket πακέτο Stampede Stampede package Stampede-pakaĵo paquete Stampede Stampede paketea Stampede-paketti Stampede pakki paquet Stampede pacáiste Stampede paquete Stampede חבילה של Stampede Stampede paket Stampede-csomag Paket Stampede Pacchetto Stampede Stampede パッケージ Stampede дестесі Stampete 패키지 Stampede paketas Stampede pakotne Pakej Stampede Stampede-pakke Stampede-pakket Stampede-pakke Pakiet Stampede pacote Stampede Pacote Stampede Pachet Stampede пакет Stampede Balíček Stampede Datoteka paketa Stampede Paketë Stampede Stampede пакет Stampede-paket Stampede paketi пакунок Stampede Gói Stampede Stampede 软件包 Stampede 套件 Sega Master System/Game Gear ROM ROM الخاص بدولاب لعبة/نظام سيجا ماستر Sega Master System/Game Gear ROM ROM — Sega Master System/Game Gear ROM de Sega Master System/Game Gear ROM pro Sega Master System/Game Gear Sega Master System/Game Gear-rom Sega-Master-System/Game-Gear-ROM Εικόνα μνήμης ROM Sega Master System/Game Gear Sega Master System/Game Gear ROM ROM de Sega Master System/Game Gear Sega Master System/Game Gear-eko ROMa Sega Master System/Game Gear -ROM Sega Master System/Game Gear ROM ROM Sega Master System/Game Gear ROM Sega Master System/Game Gear ROM de Sega Master System/Game Gear Sega Master System/Game Gear של ROM Sega Master System/Game Gear ROM Sega Master System/Game Gear ROM Memori baca-saja Sega Master System/Game Gear ROM Sega Master System/Game Gear セガ マスターシステム/ゲームギア ROM Sega Master System/Game Gear ROM 세가 마스터 시스템/게임 기어 롬 Sega Master System/Game Gear ROM Sega Master System/Game Gear ROM Sega Master System/Game Gear-ROM Sega Master System/Game Gear-ROM Sega Master System/Game Gear-ROM Plik ROM konsoli SMS/Game Gear ROM do Sega Master System/Game Gear ROM Sega Master System/Game Gear Sega Master System/Game Gear ROM ROM pre Sega Master System/Game Gear Bralni pomnilnik Sega Master System/Game Gear ROM Sega Master System/Game Gear Sega Master System/Game Gear-rom ППП Sega Master System/Game Gear ROM Sega Master System/Game Gear Sega Master System/Game Gear ROM Sega Master System/Game Gear ROM Super NES ROM Super NES ROM Super Nintendo ROM ROM — Super NES ROM de Super NES ROM pro Super Nintendo Super NES-rom Super-NES-ROM εικόνα μνήημης ROM Super NES Super NES ROM ROM de Super NES Super Nintendo-ko ROMa Super Nintendo -ROM Super NES ROM ROM Super Nintendo ROM Super NES ROM de Super NES ROM של Super NES Super NES ROM Super NES ROM Memori baca-saja Super Nintendo ROM Super Nintendo スーパーファミコン ROM Super NES ROM 수퍼 NES 롬 Super NES ROM Super NES ROM Super Nintendo ROM Super Nintendo Super NES-ROM Plik ROM konsoli SNES ROM do Super Nintendo ROM Super Nintendo Super NES ROM ROM pre Super Nintendo Bralni pomnilnik Super NES ROM Super NES Super NES-rom ППП Super NES ROM Super Nintendo Super NES ROM 超級任天堂 ROM StuffIt archive أرشيف StuffIt Archiŭ StuffIt Архив — StuffIt arxiu StuffIt Archiv StuffIt StuffIt-arkiv StuffIt-Archiv συμπιεσμένο αρχείο StuffIt StuffIt archive StuffIt-arkivo archivador StuffIt StuffIt artxiboa StuffIt-arkisto StuffIt skjalasavn archive StuffIt cartlann StuffIt arquivo StuffIt ארכיון של Sאוככןא StuffIt arhiva StuffIt-archívum Arsip StuffIt Archivio StuffIt StuffIt アーカイブ StuffIt архиві StuffIt 압축 파일 StuffIt archyvas StuffIt arhīvs StuffIt arkiv StuffIt-archief StuffIt-arkiv Archiwum StuffIt Pacote StuffIt Arhivă StuffIt архив StuffIt Archív StuffIt Datoteka arhiva StuffIt Arkiv StuffIt StuffIt архива StuffIt-arkiv StuffIt arşivi архів StuffIt Kho nén Stuffit Macintosh StuffIt 归档文件 StuffIt 封存檔 SubRip subtitles ترجمات SubRip Subtytry SubRip Субтитри — SubRip subtítols SubRip Titulky SubRip SubRip-undertekster SubRip-Untertitel υπότιτλοι SubRip SubRip subtitles SubRip-subtekstoj subtítulos SubRip SubRip azpitituluak SubRip-tekstitykset SubRip undirtekstir sous-titres SubRip fotheidil SubRip subtítulos SubRip כתוביות של SubRip SubRip titlovi SubRip feliratok Subjudul SubRip Sottotitoli SubRip SubRip 字幕 SubRip субтитрлары SubRip 자막 파일 SubRip subtitrai SubRip subtitri SubRip undertekst SubRip-ondertitels SubRip-teksting Napisy SubRip Legendas SubRip Subtitrare SubRip субтитры SubRip Titulky SubRip Datoteka podnapisov SubRip Nëntituj SubRip SubRip-undertexter SubRip altyazıları субтитри SubRip Phụ đề SubRip SubRip 字幕 SubRip 字幕 WebVTT subtitles Субтитри — WebVTT subtítols WebVTT Titulky WebVTT WebVTT-undertekster WebVTT-Untertitel υπότιτλοι WebVTT WebVTT subtitles Subtítulos WebVTT WebVTT-tekstitykset sous-titres WebVTT subtítulos WebVTT כתוביות WebVTT WebVTT titlovi WebVTT feliratok Subtitel WebVTT Sottotitoli WebVTT WebVTT サブタイトル WebVTT ქვეტიტრები WebVTT субтитрлары WebVTT 자막 WebVTT subtitri WebVTT ondertitels Napisy WebVTT Legendas WebVTT субтитры WebVTT Podnapisi WebVTT WebVTT-undertexter субтитри WebVTT WebVTT 字幕 WebVTT 字幕 VTT Video Text Tracks SAMI subtitles ترجمات SAMI Subtytry SAMI Субтитри — SAMI subtítols SAMI Titulky SAMI SAMI-undertekster SAMI-Untertitel υπότιτλοι SAMI SAMI subtitles SAMI-subtekstoj subtítulos SAMI SAMI azpitituluak SAMI-tekstitykset SAMI undirtekstir sous-titres SAMI fotheidil SAMI subtítulos SAMI כתוביות SAMI SAMI titlovi SAMI feliratok Subjudul SAMI Sottotitoli SAMI SAMI 字幕 SAMI субтитрлары SAMI 자막 파일 SAMI subtitrai SAMI subtitri SAMI undertekst SAMI-ondertitels SAMI teksting Napisy SAMI Legendas SAMI Subtitrări SAMI субтитры SAMI Titulky SAMI Datoteka podnapisov SAMI Nëntituj SAMI SAMI-undertexter SAMI altyazıları субтитри SAMI Phụ đề SAMI SAMI 字幕 SAMI 字幕 SAMI Synchronized Accessible Media Interchange MicroDVD subtitles ترجمات MicroDVD Subtytry MicroDVD Субтитри — MicroDVD subtítols MicroDVD Titulky MicroDVD MicroDVD-undertekster MicroDVD-Untertitel υπότιτλοι MicroDVD MicroDVD subtitles MicroDVD-subtekstoj subtítulos MicroDVD MicroDVD azpitituluak MicroDVD-tekstitykset MicroDVD undirtekstir sous-titres MicroDVD fotheidil MicroDVD subtítulos de MicroDVD כתוביות של MicroDVD MicroDVD titlovi MicroDVD feliratok Subjudul MicroDVD Sottotitoli MicroDVD MicroDVD 字幕 MicroDVD-ის ქვეტიტრები MicroDVD субтитрлары MicroDVD 자막 파일 MicroDVD subtitrai MicroDVD subtitri MicroDVD undertekst MicroDVD-ondertitels MicroDVD-teksting Napisy MicroDVD Legendas MicroDVD Subtitrări MicroDVD субтитры MicroDVD Titulky MicroDVD Datoteka podnapisov MicroDVD Nëntituj MicroDVD MicroDVD-undertexter MicroDVD altyazısı субтитри MicroDVD Phụ đề MicroDVD MicroDVD 字幕 MicroDVD 字幕 MPSub subtitles ترجمات MPSub Subtytry MPSub Субтитри — MPSub subtítols MPSub Titulky MPSub MPSub-undertekster MPSub-Untertitel υπότιτλοι MPSub MPSub subtitles MPSub-subtekstoj subtítulos MPSub MPSub azpitituluak MPSub-tekstitykset MPSub undirtekstir sous-titres MPSub fotheidil MPSub subtítulos MPSub כתוביות MPSub MPSub titlovi MPSub feliratok Subjudul MPSub Sottotitoli MPSub MPSub サブタイトル MPSub ქვეტიტრები MPSub субтитрлары MPSub 자막 파일 MPSub subtitrai MPSub subtitri MPSub undertekst MPSub-ondertitels MPSub-undertekstar Napisy MPSub Legendas MPSub Subtitrări MPSub субтитры MPSub Titulky MPSub Datoteka podnapisov MPSub Nëntituj MPSub MPSub-undertexter субтитри MPSub Phụ đề MPSub MPSub 字幕 MPSub 字幕 MPSub MPlayer Subtitle SSA subtitles ترجمات SSA Subtytry SSA Субтитри — SSA subtítols SSA Titulky SSA SSA-undertekster SSA-Untertitel υπότιτλοι SSA SSA subtitles SSA-subtekstoj subtítulos SSA SSA azpitituluak SSA-tekstitykset SSA undirtekstir sous-titres SSA fotheidil SSA Subtitulos SSA כתובית SSA SSA titlovi SSA feliratok Subjudul SSA Sottotitoli SSA SSA 字幕 SSA субтитрлары SSA 자막 파일 SSA subtitrai SSA subtitri SSA undertekst SSA-ondertitels SSA-teksting Napisy SSA Legendas SSA Subtitrări SSA субтитры SSA Titulky SSA Datoteka podnapisov SSA Nëntituj SSA SSA-undertexter SSA altyazıları субтитри SSA Phụ đề SSA SSA 字幕 SSA 字幕 SSA SubStation Alpha SubViewer subtitles ترجمات SubViewer Subtytry SubViewer Субтитри — SubViewer subtítols SubViewer Titulky SubViewer SubViewer-undertekster SubViewer-Untertitel υπότιτλοι SubViewer SubViewer subtitles SubViewer-subtekstoj subtítulos SubViewer SubViewer azpitituluak SubViewer-tekstitykset SubViewer undirtekstir sous-titres SubViewer fotheidil SubViewer subtítulos SubViewer כתוביות של SubViewe SubViewer titlovi SubViewer feliratok Subjudul SubViewer Sottotitoli SubViewer SubViewer 字幕 SubViewer субтитрлары SubViewer 자막 파일 SubViewer subtitrai SubViewer subtitri SubViewer undertekst SubViewer-ondertitels SubViewer-teksting Napisy SubViewer Legendas SubViewer Subtitrare SubViewer субтитры SubViewer Titulky SubViewer Datoteka podnapisov SubViewer Nëntituj SubViewer SubViewer-undertexter субтитри SubViewer Phụ đề SubViewer SubViewer 字幕 SubViewer 字幕 iMelody ringtone نغمة iMelody Rington iMelody Аудио — iMelody to de trucada iMelody Vyzváněcí melodie iMelody iMelody-ringetone iMelody-Klingelton ringtone iMelody iMelody ringtone tono de llamada iMelody iMelody doinua iMelody-soittoääni iMelody ringitóni sonnerie iMelody ton buailte iMelody Melodía de iMelody רינגטון של iMelody iMelody ton zvonjenja iMelody csengőhang nada dering iMelody Suoneria iMelody iMelody リングトーン iMelody әуені iMelody 벨소리 iMelody skambučio melodija iMelody melodija iMelody ringetone iMelody-beltoon iMelody-ringetone Dzwonek iMelody Toque de celular do iMelody Sonerie iMelody мелодия iMelody Vyzváňacie melódie iMelody Zvonjenje iMelody Zile iMelody iMelody-ringsignal рінгтон iMelody tiếng réo iMelody iMelody 铃声 iMelody 鈴聲 SMAF audio SMAF سمعي Aŭdyjo SMAF Аудио — SMAF àudio SMAF Zvuk SMAF SMAF-lyd SMAF-Audio ήχος SMAF SMAF audio SMAF-sondosiero sonido SMAF SMAF audioa SMAF-ääni SMAF ljóður audio SMAF fuaim SMAF son SMAF שמע SMAF SMAF audio SMAF hang Audio SMAF Audio SMAF SMAF オーディオ SMAF аудиосы SMAF 오디오 SMAF garso įrašas SMAF audio SMAF-lyd SMAF-audio SMAF-lyd Plik dźwiękowy SMAF Áudio SMAF Audio SMAF аудио SMAF Zvuk SMAF Zvočna datoteka SMAF Audio SMAF SMAF-ljud звук SMAF Âm thanh SMAF SMAF 音频 SMAF 音訊 SMAF Synthetic music Mobile Application Format MRML playlist قائمة تشغيل MRML Śpis piesień MRML Списък за изпълнение — MRML llista de reproducció MRML Seznam skladeb MRML MRML-afspilningsliste MRML-Wiedergabeliste λίστα αναπαραγωγής MRML MRML playlist MRML-ludlisto lista de reproducción MRML MRML erreprodukzio-zerrenda MRML-soittolista MRML avspælingarlisti liste de lecture MRML seinmliosta MRML lista de reprodución MRML רשימת השמעה MRML MRML popis za reprodukciju MRML-lejátszólista Senarai putar MRML Scaletta MRML MPML 再生リスト MRML რეპერტუარი MRML ойнау тізімі MRML 재생 목록 MRML grojaraštis MRML repertuārs MRML-spilleliste MRML-afspeellijst MRML-speleliste Lista odtwarzania MRML Lista de reprodução do MRML Listă redare MRML список воспроизведения MRML Zoznam skladieb MRML Seznam predvajanja MRML Listë titujsh MRML MRML-spellista список відтворення MRML Danh mục nhạc MRML MRML 播放列表 MRML 播放清單 MRML Multimedia Retrieval Markup Language XMF audio XMF سمعي Aŭdyjo XMF Аудио — XMF àudio XMF Zvuk XMF XMF-lyd XMF-Audio ήχος XMF XMF audio XMF-sondosiero sonido XMF XMF audioa XMF-ääni XMF ljóður audio XMF fuaim XMF son XMF שמע XMF XMF audio XMF hang Audio XMF Audio XMF XMF オーディオ XMF аудиосы XMF 오디오 XMF garso įrašas XMF audio XMF-lyd XMF-audio XMF-lyd Plik dźwiękowy XMF Áudio XMF Audio XMF аудио XMF Zvuk XMF Zvočna datoteka XMF Audio XMF XMF-ljud звук XMF Âm thanh XMF XMF 音频 XMF 音訊 XMF eXtensible Music Format SV4 CPIO archive أرشيف SV4 CPIO SV4 CPIO arxivi Archiŭ SV4 CPIO Архив — SV4 CPIO arxiu CPIO SV4 Archiv SV4 CPIO Archif CPIO SV4 SV4 CPIO-arkiv SV4-CPIO-Archiv αρχείο SV4 CPIO SV4 CPIO archive SV4-CPIO-arkivo archivador SV4 CPIO SV4 CPIO artxiboa SV4 CPIO -arkisto SV4 CPIO skjalasavn archive SV4 CPIO cartlann SV4 CPIO arquivo SV4 CPIO ארכיון של SV4 SPIO SV4 CPIO arhiva SV4 CPIO-archívum Arsip SV4 CPIO Archivio SV4 CPIO SV4 CPIO アーカイブ SV4 CPIO архиві SV4 CPIO 묶음 파일 SV4 CPIO archyvas SV4 CPIO arhīvs Arkib CPIO SV4 SV4 CPIO-arkiv SV4 CPIO-archief SV4 CPIO-arkiv Archiwum SV4 CPIO ficheiro SV4 CPIO Pacote SV4 CPIO Arhivă SV4 CPIO архив SV4 CPIO Archív SV4 CPIO Datoteka arhiva SV4 CPIO Arkiv SV4 CPIO SV4 CPIO архива SV4 CPIO-arkiv SV4 CPIO arşivi архів SV4 CPIO Kho nén CPIO SV4 SV4 CPIO 归档文件 SV4 CPIO 封存檔 SV4 CPIO archive (with CRC) أرشيف SV4 CPIO (مع CRC) Archiŭ SV4 CPIO (z CRC) Архив — SV4 CPIO, проверка за грешки CRC arxiu CPIO SV4 (amb CRC) Archiv SV4 CPIO (s CRC) SV4 CPIO-arkiv (med CRC) SV4-CPIO-Archiv (mit CRC) αρχείο SV4 CPIO (με CRC) SV4 CPIO archive (with CRC) SV4-CPIO-arkivo (kun CRC) archivador SV4 CPIO (con CRC) SV4 CPIO artxiboa (CRC-rekin) SV4 CPIO -arkisto (CRC:llä) SV4 CPIO skjalasavn (við CRC) archive SV4 CPIO (avec CRC) cartlann SV4 CPIO (le CRC) Arquivador SV4 CPIO (con CRC) ארכיון של SV4 SPIO (עם CRC) SV4 CPIO arhiva (s CRC-om) SV4 CPIO-archívum (CRC-vel) Arsip SV4 CPIO (dengan CRC) Archivio SV4 CPIO (con CRC) SV4 CPIO アーカイブ (CRC 有り) SV4 CPIO архиві (CRC бар) SV4 CPIO 묶음 파일 (CRC 포함) SV4 CPII archyvas (su CRC) SV4 CPIO arhīvs (ar CRC) Arkib CPIO SV4 (dengan CRC) SV4 CPIO-arkiv (med CRC) SV4 CPIO-archief (met CRC) SV4 CPIO arkiv (med CRC) Archiwum SV4 CPIO (z sumą kontrolną) Pacote SV4 CPIO (com CRC) Arhivă SV4 CPIO (cu CRC) архив SV4 CPIP (с CRC) Archív SV4 CPIO (s CRC) Datoteka arhiva SV4 CPIO (z razpršilom CRC) Arkiv SV4 CPIO (me CRC) SV4 CPIO-arkiv (med CRC) SV4 CPIO arşivi (CRC ile) архів SV4 CPIO (з CRC) Kho nén CPIO SV4 (với CRC) SV4 CPIP 归档文件(带有 CRC) SV4 CPIO 封存檔 (具有 CRC) Tar archive أرشيف Tar Tar arxivi Archiŭ tar Архив — tar arxiu tar Archiv tar Archif tar Tar-arkiv Tar-Archiv συμπιεσμένο αρχείο Tar Tar archive archivador Tar Tar artxiboa Tar-arkisto Tar skjalasavn archive tar cartlann Tar arquivo Tar ארכיון Tar Tar arhiva Tar archívum Arsip Tar Archivio tar Tar アーカイブ Tar архиві TAR 묶음 파일 Tar archyvas Tar arhīvs Arkib Tar Tar-arkiv Tar-archief Tar-arkiv Archiwum tar Pacote tar Arhivă Tar архив TAR Archív tar Datoteka arhiva Tar Arkiv tar Тар архива Tar-arkiv архів tar Kho nén tar Tar 归档文件 Tar 封存檔 Tar archive (compressed) أرشيف Tar (مضغوط) Archiŭ tar (skampresavany) Архив — tar, компресиран arxiu tar (comprimit) Archiv tar (komprimovaný) Tar-arkiv (komprimeret) Tar-Archiv (komprimiert) αρχείο Tar (συμπιεσμένο) Tar archive (compressed) archivador Tar (comprimido) Tar artxiboa (konprimitua) Tar-arkisto (pakattu) Tar skjalasavn (stappað) archive tar (compressée) cartlann Tar (comhbhrúite) arquivo Tar (comprimido) ארכיון Tar (מכווץ) Tar arhiva (komprimirana) Tar archívum (tömörített) Arsip Tar (terkompresi) Archivio tar (compresso) Tar アーカイブ (compress 圧縮) Tar архиві (сығылған) TAR 묶음 파일 (압축) Tar archyvas (suglaudintas) Tar arhīvs (saspiests) Tar-arkiv (komprimert) Tar-archief (ingepakt) Tar-arkiv (pakka) Archiwum tar (skompresowane) Pacote tar (compactado) Arhivă Tar (comprimată) архив TAR (сжатый) Archív tar (komprimovaný) Datoteka arhiva Tar (stisnjen) Arkiv tar (i kompresuar) Tar-arkiv (komprimerat) архів tar (стиснений) Kho nén tar (đã nén) Tar 归档文件(压缩) Tar 封存檔 (UNIX 格式壓縮) generic font file ملف الخط العام zvyčajny fajł šryftu Шрифт fitxer genèric de tipus de lletra Obecný soubor písma general skrifttypefil Allgemeine Schriftdatei γενικό αρχείο γραμματοσειράς generic font file genera tipara dosiero tipografía genérico letra-tipo orokorra yleinen kirjasintiedosto felagsstavasniðsfíla fichier de polices générique comhad cló ginearálta ficheiro de tipo de fonte xenérica קובץ גופן גנרי općenita datoteka fonta általános betűkészletfájl berkas fonta generik File tipo carattere generico 一般フォントファイル қаріп файлы 일반 글꼴 파일 bendras šrifto failas vispārēja fonta datne Fail font generik vanlig skriftfil algemeen lettertypebestand vanleg skrifttypefil Zwykły plik czcionki ficheiro genérico de tipo de letra Arquivo de fonte genérico fișier de font generic файл шрифта Obyčajný súbor písma izvorna datoteka pisave File lloj gërme i përgjithshëm општа датотека фонта allmän typsnittsfil загальний файл шрифту tập tin phông giống loài 通用字体文件 通用字型檔 packed font file ملف الخط المرزم zapakavany fajł šryftu Шрифт — компресиран fitxer empaquetat de tipus de lletra Komprimovaný soubor písma pakket skrifttypefil Gepackte Schriftdatei αρχείο συμπιεσμένης γραμματοσειράς packed font file pakigita tipara dosiero archivo de tipografía empaquetada Letra-tipo fitxategi paketatua pakattu kirjasintiedosto pakkað stavasniðsfíla fichier de polices empaquetées comhad cló pacáilte ficheiro de fonte empaquetada קובץ גופן ארוז pakirana datoteka fonta packed font-fájl berkas fonta terkemas File tipo carattere condensato パックされたフォントファイル қаріп файлы (дестеленген) 글꼴 묶음 파일 supakuotas šrifto failas sapakota fonta datne Fail font dipek pakket skriftfil ingepakt lettertypebestand pakka skrifttypefil Plik ze spakowaną czcionką ficheiro de fontes empacotadas Arquivo de fonte empacotado fișier font împachetat сжатый файл шрифта Komprimovaný súbor písma pakirana datoteka pisave File lloj gërmash i kondensuar пакована датотека са фонтом packad typsnittsfil запакований файл шрифту tập tin phông chữ đã đóng gói 打包的字体文件 包裝字型檔 TGIF document مستند TGIF Dakument TGIF Документ — TGIF document TGIF Dokument TGIF TGIF-dokument TGIF-Dokument Σχέδιο TGIF TGIF document TGIF-dokumento documento TGIF TGIF dokumentua TGIF-asiakirja TGIF skjal document TGIF cáipéis TGIF documento TGIF מסמך TGIF TGIF dokument TGIF-dokumentum Dokumen TGIF Documento TGIF TGIF ドキュメント TGIF құжаты TGIF 문서 TGIF dokumentas TGIF dokuments Dokumen TGIF TGIF-dokument TGIF-document TGIF-dokument Dokument TGIF documento TGIF Documento TGIF Document TGIF документ TGIF Dokument TGIF Dokument TGIF Dokument TGIF TGIF документ TGIF-dokument документ TGIF Tài liệu TGIF TGIF 文档 TGIF 文件 theme سمة örtük matyŭ Тема tema Motiv thema tema Thema Θέμα theme etoso tema gaia teema tema thème téama tema ערכת נושא tema téma tema Tema テーマ თემა тема 테마 tema motīvs Tema tema thema drakt Motyw tema Tema temă тема Motív tema Temë тема tema тема sắc thái 主题 佈景主題 ToutDoux document مستند ToutDoux ToutDoux sənədi Dakument ToutDoux Документ — ToutDoux document ToutDoux Dokument ToutDoux Dogfen ToutDoux ToutDoux-dokument ToutDoux-Dokument έγγραφο ToutDoux ToutDoux document ToutDoux-dokumento documento de ToutDoux ToutDoux dokumentua ToutDoux-asiakirja ToutDoux skjal document ToutDoux cáipéis ToutDoux documento de ToutDoux משמך של ToutDoux ToutDoux dokument ToutDoux-dokumentum Dokumen ToutDoux Documento ToutDoux ToutDoux ドキュメント ToutDoux құжаты ToutDoux 문서 ToutDoux dokumentas ToutDoux dokuments Dokumen ToutDoux ToutDoux-dokument ToutDoux-document ToutDoux-dokument Dokument ToutDoux documento ToutDoux Documento do ToutDoux Document ToutDoux документ ToutDoux Dokument ToutDoux Dokument ToutDoux Dokument ToutDoux ToutDoux документ ToutDoux-dokument ToutDoux belgesi документ ToutDoux Tài liệu ToutDoux ToutDoux 文档 ToutDoux 文件 backup file ملف النسخ الاحتياطي zapasny fajł Резервно копие fitxer de còpia de seguretat Záložní soubor sikkerhedskopi Sicherungsdatei αντίγραφο ασφαλείας backup file restaŭrkopio archivo de respaldo babes-kopiako fitxategia varmuuskopio trygdarritsfíla fichier de sauvegarde comhad cúltaca ficheiro de copia de seguridade קובץ גיבוי biztonsági mentés berkas cadangan File di backup バックアップファイル резервті көшірмесі 백업 파일 atsarginis failas dublējuma datne Fail backup sikkerhetskopi reservekopiebestand tryggleikskopi Plik zapasowy cópia de segurança Arquivo de backup fișier de backup резервная копия Záložný súbor varnostna kopija datoteke File backup резервна копија säkerhetskopia yedek dosyası резервна копія tập tin sao lưu 备份文件 備份檔 Troff document مستند Troff Troff sənədi Dakument Troff Документ — Troff document Troff Dokument troff Dogfen troff Troffdokument Troff-Dokument έγγραφο troff Troff document Troff-dokumento documento troff Troff dokumentua Troff-asiakirja Troff skjal document Troff cáipéis Troff documento Troff מסמך Troff Troff dokument Troff-dokumentum Dokumen Troff Documento Troff Troff 入力ドキュメント Troff құжаты Troff 문서 Troff dokumentas Troff dokuments Dokumen Troff Troff-dokument Troff-document Troff-dokument Dokument Troff documento Troff Documento troff Document Troff документ Troff Dokument troff Dokument Troff Dokument Troff Troff документ Troff-dokument Troff belgesi документ Troff Tài liệu Troff Troff 文档 Troff 文件 Troff document (with manpage macros) مستند Troff (مع اختصارات صفحة المساعدة) Dakument Troff (z makrasam man-staronak) Документ — Troff, с макроси за справочни страници document Troff (amb macros de pàgines de manual) Dokument troff (s makry pro manuálové stránky) Troffdokument (med manualsidemakroer) Troff-Dokument (mit man-Seitenmakros) έγγραφο troff (με μακροεντολές manpage) Troff document (with manpage macros) Troff-dokumento (kun manpaĝaj makrooj) documento troff (con macros de páginas de manual) Troff dokumentua (manpage makroekin) Troff-asiakirja (man-sivu-makroilla) Troff skjal (við manpage fjølvi) document Troff (avec macros manpage) cáipéis Troff (le macraí manpage) documento Troff (con macros de páxinas de manual) מסמך של Troff (עם מאקרו בmanpage) Troff dokument (s makro naredbama priručnika) Troff-dokumentum (manpage-makrókkal) Dokumen Troff (dengan makro halaman manual) Documento Troff (con macro per manpage) Troff 入力ドキュメント (man ページマクロ有り) Troff құжаты (әдістемелік парақтар макростарымен) Troff 문서 (man 페이지 매크로 포함) Troff dokumentas (su žin. puslapių makrokomandomis) Troff dokuments (ar manpage makrosiem) Dokumen Troff (dengan macros halaman man) Troff-dokument (med manualsidemakroer) Troff-document (met man-macro's) Troff-dokument med manualside-makroar Dokument Troff (z makrami stron pomocy) documento Troff (com macros manpage) Documento troff (com macros de páginas de manual) Document Troff (cu macro-uri manpage) документ Troff (с макросами страниц руководства) Dokument troff (s makrami pre manuálové stránky) Dokument Troff (z makroji manpage) Dokumet Troff (me makro për manpage) Troff документ (са макроима за ман странице) Troff-dokument (med manualsidemakron) документ Troff (з макросами manpage) Tài liệu Troff (có vĩ lệnh trang hướng dẫn) Troff 文档(带 Man 手册宏) Troff 文件 (含有手冊頁面巨集) manual page (compressed) صفحة المساعدة (مضغوطة) man səhifəsi (sıxışdırılmış) staronka dapamohi (skampresavanaja) Страница от справочника, компресирана pàgina de manual (comprimida) Manuálová stránka (komprimovaná) tudalen llawlyfr (wedi ei gywasgu) manualside (komprimeret) Handbuchseite (komprimiert) σελίδα οδηγιών (συμπιεσμένη) manual page (compressed) manpaĝo (kunpremita) página de manual (comprimida) eskuliburu orria (konprimitua) manuaalisivu (pakattu) handbókasíða (stappað) page de manuel (compressée) leathanach lámhleabhair (comhbhrúite) páxina de manual (comprimida) דף עזר (מכווץ) stranica priručnika (komprimirana) kézikönyvoldal (tömörített) halaman manual (terkompresi) Pagina di manuale (compressa) (圧縮) man ページ әдістемелік парағы (сығылған) man 페이지 (압축) žinyno puslapis (suglaudintas) rokasgrāmatas lapa (saspiesta) Halaman manual (termampat) manualside (komprimert) handleidingspagina (ingepakt) manualside (pakka) Strona podręcznika (skompresowana) página de manual (comprimida) Página de manual (compactada) pagină de manual (comprimată) страница руководства (сжатая) Manuálová stránka (komprimovaná) stran priročnika (stisnjena) Faqe manuali (e kompresuar) страна упутства (компресована) manualsida (komprimerad) сторінка посібника (стиснена) trang hướng dẫn (đã nén) 手册页 (压缩) 手冊頁面 (壓縮版) Tar archive (LZO-compressed) أرشيف Tar (مضغوط-LZO) Archiŭ tar (LZO-skampresavany) Архив — tar, компресиран с LZO arxiu tar (comprimit amb LZO) Archiv tar (komprimovaný pomocí LZO) Tar-arkiv (LZO-komprimeret) Tar-Archiv (LZO-komprimiert) αρχείο Tar (συμπιεσμένο με LZO) Tar archive (LZO-compressed) archivador Tar (comprimido con LZO) Tar artxiboa (LZO-rekin konprimitua) Tar-arkisto (LZO-pakattu) Tar skjalasavn (LZO-stappað) archive tar (compression LZO) cartlann Tar (comhbhrúite le LZO) arquivo Tar (comprimido con LZO) ארכיון Tar (מכווץ ע"י LZO) Tar arhiva (komprimirana LZO-om) Tar archívum (LZO-val tömörítve) Arsip Tar (terkompresi LZO) Archivio tar (compresso con LZO) Tar アーカイブ (LZO 圧縮) Tar архиві (LZO-мен сығылған) TAR 묶음 파일 (LZO 압축) Tar archyvas (suglaudintas su LZO) Tar arhīvs (saspiests ar LZO) Tar-arkiv (LZO-komprimert) Tar-archief (ingepakt met LZO) Tar-arkiv (pakka med LZO) Archiwum tar (kompresja LZO) Pacote tar (compactado com LZO) Arhivă Tar (comprimată LZO) архив TAR (сжатый LZO) Archív tar (komprimovaný pomocou LZO) Datoteka arhiva Tar (stisnjen z LZO) Arkiv tar (i kompresuar me LZO) Tar-arkiv (LZO-komprimerat) архів tar (стиснений LZO) Kho nén tar (đã nén LZO) Tar 归档文件(LZO 压缩) Tar 封存檔 (LZO 格式壓縮) XZ archive أرشيف XZ Архив — XZ arxiu XZ Archiv XZ XZ-arkiv XZ-Archiv συμπιεσμένο αρχείο XZ XZ archive XZ-arkivo archivador XZ XZ artxiboa XZ-arkisto XZ skjalasavn archive XZ cartlann XZ ficheiro XZ ארכיון XZ XZ-archívum Arsip XZ Archivio XZ XZ アーカイブ XZ архиві XZ 압축 파일 XZ archyvas XZ arhīvs XZ archief Archiwum XZ Pacote XZ Arhivă XZ архив XZ Archív XZ Datoteka arhiva XZ XZ-arkiv XZ arşivi архів XZ XZ 归档文件 XZ 封存檔 Tar archive (XZ-compressed) أرشيف Tar (مضغوط-XZ) Архив — tar, компресиран с XZ arxiu tar (comprimit amb XZ) Archiv tar (komprimovaný pomocí XZ) Tar-arkiv (XZ-komprimeret) Tar-Archiv (XZ-komprimiert) αρχείο Tar (συμπιεσμένο με XZ) Tar archive (XZ-compressed) archivador Tar (comprimido con XZ) Tar artxiboa (XZ-rekin konprimitua) Tar-arkisto (XZ-pakattu) Tar skjalasavn(XZ-stappað) archive tar (compression XZ) cartlann Tar (comhbhrúite le XZ) arquivo Tar (comprimido con XZ) ארכיון Tar (מכווץ ע"י XZ) Tar arhiva (komprimirana XZ-om) Tar archívum (XZ-vel tömörítve) Arsip Tar (terkompresi XZ) Archivio tar (compresso con XZ) Tar アーカイブ (XZ 圧縮) Tar архиві (XZ-мен сығылған) TAR 묶음 파일 (XZ 압축) Tar archyvas (suglaudintas su XZ) Tar arhīvs (saspiests ar XZ) Tar archief (XZ-compressed) Archiwum tar (kompresja XZ) Pacote tar (compactado com XZ) Arhivă Tar (comprimată XZ) архив TAR (сжатый XZ) Archív tar (komprimovaný pomocou XZ) Datoteka arhiva Tar (stisnjen z XZ) Tar-arkiv (XZ-komprimerat) архів tar (стиснений XZ) Tar 归档文件(XZ 压缩) Tar 封存檔 (XZ 格式壓縮) PDF document (XZ-compressed) Документ — PDF, компресиран с XZ document PDF (comprimit amb XZ) Dokument PDF (komprimovaný pomocí XZ) PDF-dokument (XZ-komprimeret) PDF-Dokument (XZ-komprimiert) έγγραφο PDF (συμπιεσμένο με XZ) PDF document (XZ-compressed) Documento PDF (comprimido en XZ) PDF-asiakirja (XZ-pakattu) document PDF (compressé XZ) documento PDF (comprimido en XZ) מסמך PDF (בדחיסת XZ) PDF dokument (komprimiran XZ-om) PDF dokumentum (XZ-vel tömörített) Dokumen PDF (terkompresi XZ) Documento PDF (compresso con XZ) PDF 文書(XZ 圧縮) PDF დოკუმენტი (XZ-ით შეკუმშული) PDF құжаты (XZ-мен сығылған) PDF 문서 (XZ 압축) PDF dokuments (saspiests ar XZ) PDF document (XZ-compressed) Dokument PDF (kompresja XZ) Documento PDF (compactado com XZ) документ PDF (сжатый XZ) Dokument PDF (XZ-stisnjen) PDF-dokument (XZ-komprimerad) документ PDF (стиснений xz) PDF 文档(XZ) PDF 文件 (XZ 格式壓縮) Ustar archive أرشيف Ustar Archiŭ ustar Архив — ustar arxiu ustar Archiv ustar Ustararkiv Ustar-Archiv συμπιεσμένο αρχείο Ustar Ustar archive Ustar-arkivo archivador Ustar Ustar artxiboa Ustar-arkisto Ustar skjalasavn archive Ustar cartlann Ustar arquivo Ustar ארכיון Ustar Ustar arhiva Ustar archívum Arsip Ustar Archivio ustar Ustar アーカイブ Ustar архиві Ustar 압축 파일 Ustar archyvas Ustar arhīvs Ustar-arkiv Ustar-archief Ustar-arkiv Archiwum ustar Pacote Ustar Arhivă Ustar архив Ustar Archív ustar Datoteka arhiva Ustar Arkiv Ustar Ustar-arkiv Ustar arşivi архів ustar Kho nén ustar Ustar 归档文件 Ustar 封存檔 WAIS source code شفرة مصدر WAIS WAIS mənbə faylı Kryničny kod WAIS Изходен код — WAIS codi font en WAIS Zdrojový kód WAIS Ffynhonnell Rhaglen WAIS WAIS-kildekode WAIS-Quelltext πηγαίος κώδικας WAIS WAIS source code WAIS-fontkodo código fuente WAIS WAIS iturburu-kodea WAIS-lähdekoodi WAIS keldukota code source WAIS cód foinseach WAIS código fonte WAIS קוד מקור של WAIS WAIS izvorni kod WAIS-forráskód Kode program WAIS Codice sorgente WAIS WAIS ソースコード WAIS бастапқы коды WAIS 소스 코드 WAIS pradinis kodas WAIS pirmkods Kod sumber WAIS WAIS-kildekode WAIS-broncode WAIS-kjeldekode Plik źródłowy WAIS código fonte WAIS Código-fonte WAIS Cod sursă WAIS исходный код WAIS Zdrojový kód WAIS Datoteka izvorne kode WAIS Kod burues WAIS WAIS изворни ко̂д WAIS-källkod WAIS kaynak kodu вихідний код мовою WAIS Mã nguồn WAIS WAIS 源代码 WAIS 源碼 WordPerfect/Drawperfect image صورة WordPerfect/Drawperfect Vyjava WordPerfect/Drawperfect Изображение — WordPerfect/Drawperfect imatge de WordPerfect/Drawperfect Obrázek WordPerfect/Drawperfect WordPerfect/Drawperfect-billede WordPerfect/DrawPerfect-Bild εικόνα WordPerfect/Drawperfect WordPerfect/Drawperfect image WordPerfect/Drawperfect-bildo imagen de WordPerfect/Drawperfect WordPerfect/Drawperfect irudia WordPerfect/Drawperfect-kuva WordPerfect/Drawperfect mynd image WordPerfect/DrawPerfect íomhá WordPerfect/Drawperfect imaxe de WordPerfect/DrawPerfect תמונה של WordPerfect/Drawperfect WordPerfect/Drawperfect slika WordPerfect/Drawperfect-kép Gambar WordPerfect/Drawperfect Immagine WordPerfect/Drawperfect WordPerfect/Drawperfect 画像 WordPerfect/Drawperfect суреті 워드퍼펙트/드로퍼펙트 그림 WordPerfect/Drawperfect paveikslėlis WordPerfect/Drawperfect attēls Imej WordPerfect/Drawperfect WordPerfect-/Drawperfect-bilde WordPerfect/Drawperfect-afbeelding WordPerfect/DrawPerfect-bilete Obraz WordPerfect/DrawPerfect imagem do WordPerfect/Drawperfect Imagem do WordPerfect/Drawperfect Imagine WordPerfect/Drawperfect изображение WordPerfect/Drawperfect Obrázok WordPerfect/Drawperfect Slikovna datoteka Drawperfect Figurë WordPerfect/Drawperfect WordPerfect/Drawperfect слика WordPerfect/Drawperfect-bild зображення WordPerfect/Drawperfect Ảnh WordPerfect/Drawperfect WordPerfect/Drawperfect 图像 WordPerfect/Drawperfect 影像 DER/PEM/Netscape-encoded X.509 certificate شهادة DER/PEM/Netscape-encoded X.509 Sertyfikat X.509, zakadavany ŭ DER/PEM/Netscape Сертификат — DER/PEM/Netscape X.509 certificat X.509 codificat com DER/PEM/Netscape Certifikát X.509 kódovaný jako DER/PEM/Netscape DER-/PEM-/Netscapekodet X.509-certifikat DER/PEM/Netscape-kodiertes X.509-Zertifikat ψηφιακό πιστοποιητικό X.509 κωδικοποιημένο κατά DER/PEM/Netscape DER/PEM/Netscape-encoded X.509 certificate DER/PEM/Netscape-kodigita X.509-atestilo certificado X.509 codificado con DER/PEM/Netscape X.509rekin kodetutako DER, PEM edo Netscape zertifikatua DER/PEM/Netscape-koodattu X.509-varmenne DER/PEM/Netscape-encoded X.509 váttan certificat X.509 codé DER/PEM/Netscape teastas X.509 ionchódaithe le DER/PEM/Netscape certificado X.509 codificado con DER/PEM/Netscape אישור מסוג X.509 של DER/PEM/Netscape-encoded DER/PEM/Netscape-kodiran X.509 certifikat DER/PEM/Netscape formátumú X.509-tanúsítvány Sertifikat DER/PEM/Netscape-tersandi X.509 Certificato DER/PEM/Netscape-encoded X.509 DER/PEM/Netscape エンコード X.509 証明書 DER/PEM/Netscape კოდირებული X.509 სერტიფიკატი X.509 сертификаты (DER/PEM/Netscape кодталған) DER/PEM/넷스케이프로 인코딩된 X.509 인증서 DER/PEM/Netscape-encoded X.509 liudijimas DER/PEM/Netscape-encoded X.509 sertifikāts Sijil X.509 dienkod /DER/PEM/Netscape DER/PEM/Netscape-kodet X.509-sertifikat DER/PEM/Netscape-gecodeerd X.509-certificaat DER/PEM/Netscape-koda X.509-sertifikat Zakodowany w DER/PEM/Netscape certyfikat X.509 certificado X.509 codificado com DER/PEM/Netscape Certificado X.509 codificado com DER/PEM/Netscape Certificat DER/PEM/Netscape-codat X.509 сертификат X.509 (DER/PEM/Netscape-закодированный) Certifikát X.509 kódovaný ako DER/PEM/Netscape Datoteka potrdila DER/PEM/Netscape X.509 Çertifikatë DER/PEM/Netscape-encoded X.509 DER, PEM или Нетскејп кодирани сертификат X.509 DER/PEM/Netscape-kodat X.509-certifikat сертифікат X.509 у форматі DER/PEM/Netscape Chứng nhận X.509 mã hoá bằng Netscape/PEM/DER DER/PEM/Netscape-encoded X.509 证书 DER/PEM/Netscape 編碼的 X.509 憑證 empty document مستند فارغ pusty dakument Празен документ document buit Prázdný dokument tomt dokument Leeres Dokument κενό έγγραφο empty document malplena dokumento documento vacío dokumentu hutsa tyhjä asiakirja tómt skjal document vide cáipéis fholamh documeto baleiro מסמך ריק prazan dokument üres dokumentum dokumen kosong Documento vuoto 空のドキュメント бос құжат 빈 문서 tuščias dokumentas tukšs dokuments Dokumen kosong tomt dokument leeg document tomt dokument Pusty dokument documento vazio Documento vazio document gol пустой документ Prázdny dokument prazen dokument Dokument bosh празан документ tomt dokument boş belge порожній документ tài liệu rỗng 空文档 空白文件 Zoo archive أرشيف Zoo Zoo arxivi Archiŭ zoo Архив — zoo arxiu zoo Archiv zoo Archif zoo Zooarkiv Zoo-Archiv συμπιεσμένο αρχείο Zoo Zoo archive Zoo-arkivo archivador Zoo Zoo artxiboa Zoo-arkisto Zoo skjalasavn archive zoo cartlann Zoo ficheiro Zoo ארכיון Zoo Zoo archívum Arsip Zoo Archivio zoo Zoo アーカイブ Zoo архиві ZOO 압축 파일 Zoo archyvas Zoo arhīvs Zoo-arkiv Zoo-archief Zoo-arkiv Archiwum zoo Pacote Zoo Arhivă Zoo архив ZOO Archív zoo Datoteka arhiva ZOO Arkiv zoo Zoo-arkiv Zoo arşivi архів zoo Kho nén zoo Zoo 归档文件 Zoo 封存檔 XHTML page صفحة XHTML Staronka XHTML Страница — XHTML pàgina XHTML Stránka XHTML XHTML-side XHTML-Seite σελίδα XHTML XHTML page XHTML-paĝo página XHTML XHTML orria XHTML-sivu XHTML síða page XHTML leathanach XHTML Páxina XHTML דף XHTML XHTML stranica XHTML-oldal Halaman XHTML Pagina XHTML XHTML ページ XHTML парағы XHTML 페이지 XHTML puslapis XHTML lapa Laman XHTML XHTML-side XHTML-pagina XHTML-side Strona XHTML página XHTML Página XHTML Pagină XHTML страница XHTML Stránka XHTML Datoteka spletne strani XHTML Faqe XHTML XHTML страна XHTML-sida сторінка XHTML Trang XHTML XHTML 页面 XHTML 網頁 XHTML Extensible HyperText Markup Language Zip archive أرشيف Zip Zip arxivi Archiŭ zip Архив — zip arxiu zip Archiv ZIP Archif ZIP Ziparkiv Zip-Archiv συμπιεσμένο αρχείο Zip Zip archive Zip-arkivo archivador Zip Zip artxiboa Zip-arkisto Zip skjalasavn archive zip cartlann Zip ficheiro Zip ארכיון Zip Zip archívum Arsip Zip Archivio zip Zip アーカイブ Zip архиві ZIP 압축 파일 Zip archyvas Zip arhīvs Zip-arkiv Zip-archief Zip-arkiv Archiwum ZIP Pacote Zip Arhivă zip архив ZIP Archív ZIP Datoteka arhiva ZIP Arkiv zip Zip-arkiv Zip arşivi архів zip Kho nén zip Zip 归档文件 Zip 封存檔 Windows Imaging Format Disk Image Диск — Windows Imaging imatge de disc «Windows Imaging Format» Windows Imaging Format Disk Image Windows Imaging Format-diskaftryk Windows-Imaging-Format-Datenträgerabbild εικόνα δίσκου Windows Image Format Windows Imaging Format Disk Image imagen de disco de Windows Imaging Format image disque Windows Imaging Format imaxe de disco de Windows Imaging Format מבנה תמונת כונן של Windows Imaging Windows Imaging Format lemezkép Image Disk Windows Imaging Format Immagine disco formato Windows Imaging Windows イメージング形式 ディスクイメージ Windows Imaging Format Disk бейнесі Windows 이미지 포맷 디스크 이미지 Windows Imaging Format diska attēls Windows Imaging Format Disk Image Obraz dysku Windows Imaging Format Imagem de Disco de Formato Windows Imaging Imagine de disc „Windows Imaging Format” образ диска Windows Odtis slike Windows Imaging Windows Imaging Format Disk-avbild формат образів дисків Windows Imaging Windows Imaging 格式磁盘镜像 Windows Imaging Format Disk 映像 Dolby Digital audio Dolby Digital سمعي Dolby Digital audio Aŭdyjo Dolby Digital Аудио — Dolby Digital àudio Dolby Digital Zvuk Dolby Digital Sain Dolby Digital Dolby Ditital-lyd Dolby-Digital-Audio ψηφιακός Ήχος Dolby Dolby Digital audio Sondosiero en Dolby Digital sonido Dolby Digital Dolby audio digitala Dolby Digital -ääni Dolby Digital ljóður audio Dolby Digital fuaim Dolby Digital son Dolby Digital שמע Dolby Digital Dolby Digital audio Dolby Digital hang Audio Dolby Digital Audio Dolby Digital ドルビーデジタルオーディオ Dolby Digital-ის აუდიო Dolby Digital аудиосы 돌비 디지털 오디오 Dolby Digital garso įrašas Dolby Digital audio Audio Digital Dolby Dolby digital lyd Dolby Digital-audio Dolby Digital lyd Plik dźwiękowy Dolby Digital áudio Dolby Digital Áudio Dolby Digital Audio Dolby Digital аудио Dolby Digital Zvuk Dolby Digital Zvočna datoteka Dolby Digital Audio Dolby Digital Дигитални Dolby звучни запис Dolby Digital-ljud звук Dolby Digital Âm thanh Dolby Digital 杜比数字音频 杜比數位音訊 DTS audio DTSHD audio AMR audio AMR سمعي Aŭdyjo AMR Аудио — AMR àudio AMR Zvuk AMR AMR-lyd AMR-Audio ήχος AMR AMR audio AMR-sondosiero sonido AMR AMR audioa AMR-ääni AMR ljóður audio AMR fuaim AMR son AMR שמע AMR AMR audio AMR hang Audio AMR Audio AMR AMR オーディオ AMR აუდიო AMR аудиосы AMR 오디오 AMR garso įrašas AMR audio AMR-lyd AMR-audio AMR-lyd Plik dźwiękowy AMR Áudio AMR Audio AMR аудио AMR Zvuk AMR Zvočna datoteka AMR Audio AMR AMR-ljud звук AMR Âm thanh AMR AMR 音频 AMR 音訊 AMR Adaptive Multi-Rate AMR-WB audio AMR-WB سمعي Aŭdyjo AMR-WB Аудио — AMR-WB àudio AMR-WB Zvuk AMR-WB AMR-WB-lyd AMR-WB-Audio ήχος AMR-WB AMR-WB audio AMR-WB-sondosiero sonido AMR-WB AMR-WB audioa AMR-WB-ääni AMR-WB ljóður audio AMR-WB fuaim AMR-WB son AMR-WB שמע AMR-WN AMR-WB audio AMR-WB hang Audio AMR-WB Audio AMR-WB AMR-WB オーディオ AMR-WB აუდიო AMR-WB аудиосы AMR-WB 오디오 AMR-WB garso įrašas AMR-WB audio AMR-WB-lyd AMR-WB-audio AMR-WB-lyd Plik dźwiękowy AMR-WB Áudio AMR-WB Audio AMR-WB аудио AMR-WB Zvuk AMR-WB Zvočna datoteka AMR-WB Audio AMR-WB AMR-WB-ljud звук AMR-WB Âm thanh AMR-WB AMR-WB 音频 AMR-WB 音訊 AMR-WB Adaptive Multi-Rate Wideband ULAW (Sun) audio ULAW (صن) سمعي ULAW (Sun) audio faylı Aŭdyjo ULAW (Sun) Аудио — ULAW, Sun àudio ULAW (Sun) Zvuk ULAW (Sun) Sain ULAW (Sun) ULAW-lyd (Sun) ULAW-Audio (Sun) ήχος ULAW (Sun) ULAW (Sun) audio ULAW-sondosiero (Sun) sonido ULAW (Sun) ULAW (sun) audioa ULAW (Sun) -ääni ULAW (Sun) ljóður audio ULAW (Sun) fuaim ULAW (Sun) son ULAW (Sun) שמע ULAW (של Sun) ULAW (Sun) hang Audio ULAW (Sun) Audio ULAW (Sun) ULAW (Sun) オーディオ ULAW (Sun) аудиосы ULAW (Sun) 오디오 ULAW (Sun) garso įrašas ULAW (Sun) audio Audio ULAW (Sun) ULAW-lyd (Sun) (Sun) ULAW-audio ULAW (Sun)-lyd Plik dźwiękowy ULAW (Sun) áudio ULAW (Sun) Áudio ULAW (Sun) Fișier audio ULAW (Sun) аудио ULAW (Sun) Zvuk ULAW (Sun) Zvočna datoteka ULAW (Sun) Audio ULAW (Sun) ULAW (Sun) звучни запис ULAW-ljud (Sun) звук ULAW (Sun) Âm thanh ULAW (Sun) ULAW (Sun) 音频 ULAW (Sun) 音訊 Commodore 64 audio Commodore 64 سمعي Aŭdyjo Commodore 64 Аудио — Commodore 64 àudio Commodore 64 Zvuk Commodore 64 Commodore 64-lyd Commodore-64-Audio ήχος Commodore 64 Commodore 64 audio Sondosiero de Commodore 64 sonido de Commodore 64 Commodore 64 Audioa Commodore 64 -ääni Commodore 64 ljóð audio Commodore 64 fuaim Commodore 64 son de Commodore 64 שמע של Commodore 64 Commodore 64 audio Commodore 64 hang Audio Commodore 64 Audio Commodore 64 Commodore 64 オーディオ Commodore 64-ის აუდიო Commodore 64 аудиосы Commodore 64 오디오 Commodore 64 garso įrašas Commodore 64 audio Audio Commodore 64 Commodore 64-lyd Commodore 64-audio Commodore 64-lyd Plik dźwiękowy Commodore 64 áudio Commodore 64 Áudio Commodore 64 Audio Commodore 64 аудио Commodore 64 Zvuk Commodore 64 Zvočna datoteka Commodore 64 Audio Commodore 64 Комодор 64 звучни запис Commodore 64-ljud звук Commodore 64 Âm thanh Commodore 64 Commodore 64 音频 Commodore 64 音訊 PCM audio سمعي PCM PCM audio faylı Aŭdyjo PCM Аудио — PCM àudio PCM Zvuk PCM Sain PCM PCM-lyd PCM-Audio ήχος PCM PCM audio PCM-sondosiero sonido PCM PCM audioa PCM-ääni PCM ljóður audio PCM fuaim PCM son PCM שמע PCM PCM hang Audio PCM Audio PCM PCM オーディオ PCM аудиосы PCM 오디오 PCM garso įrašas PCM audio Audio PCM PCM-lyd PCM-audio PCM-lyd Plik dźwiękowy PCM áudio PCM Áudio PCM Audio PCM аудио PCM Zvuk PCM Zvočna datoteka PCM Audio PCM PCM звучни запис PCM-ljud звук PCM Âm thanh PCM PCM 音频 PCM 音訊 AIFC audio AIFC سمعي AIFC audio faylı Aŭdyjo AIFC Аудио — AIFC àudio AIFC Zvuk AIFC Sain AIFC AIFC-lyd AIFC-Audio ήχος AIFC AIFC audio AIFC-sondosiero sonido AIFC AIFC audioa AIFC-ääni AIFC ljóður audio AIFC fuaim AIFC son AIFC שמע AIFC AIFC audio AIFC hang Audio AIFC Audio AIFC AIFC オーディオ AIFC აუდიო AIFC аудиосы AIFC 오디오 AIFC garso įrašas AIFC audio Audio AIFC AIFC-lyd AIFC-audio AIFC-lyd Plik dźwiękowy AIFC áudio AIFC Áudio AIFC Fișier audio AIFC аудио AIFC Zvuk AIFC Zvočna datoteka AIFC Audio AIFC AIFC звучни запис AIFC-ljud звук AIFC Âm thanh AIFC AIFC 音频 AIFC 音訊 AIFC Audio Interchange File format Compressed AIFF/Amiga/Mac audio AIFF/Amiga/Mac سمعي AIFF/Amiga/Mac audio faylı Aŭdyjo AIFF/Amiga/Mac Аудио — AIFF/Amiga/Mac àudio AIFF/Amiga/Mac Zvuk AIFF/Amiga/Mac Sain AIFF/Amiga/Mac AIFF-/Amiga-/Maclyd AIFF/Amiga/Mac-Audio ήχος AIFF/Amiga/Mac AIFF/Amiga/Mac audio AIFF/Amiga/Mac-sondosiero sonido AIFF/Amiga/Mac AIFF/Amiga/Mac audioa AIFF/Amiga/Mac-ääni AIFF/Amiga/Mac ljóður audio AIFF/Amiga/Mac fuaim AIFF/Amiga/Mac son AIFF/Amiga/Mac שמע AIFF/Amiga/Mac AIFF/Amiga/Mac audio AIFF/Amiga/Mac hang Audio AIFF/Amiga/Mac Audio AIFF/Amiga/Mac AIFF/Amiga/Mac オーディオ AIFF/Amiga/Mac აუდიო AIFF/Amiga/Mac аудиосы AIFF/Amiga/Mac 오디오 AIFF/Amiga/Mac garso įrašas AIFF/Amiga/Mac audio Audio AIFF/Amiga/Mac AIFF/Amiga/Mac-lyd AIFF/Amiga/Mac-audio AIFF/Amiga/Mac-lyd Plik dźwiękowy AIFF/Amiga/Mac áudio AIFF/Amiga/Mac Áudio AIFF/Amiga/Mac Audio AIFF/Amiga/Mac аудио AIFF/Amiga/Mac Zvuk AIFF/Amiga/Mac Zvočna datoteka AIFF/Amiga/Mac Audio AIFF/Amiga/Mac AIFF/Амига/Мекинтош звучни запис AIFF/Amiga/Mac-ljud звук AIFF/Amiga/Mac Âm thanh AIFF/Amiga/Mac AIFF/Amiga/Mac 音频 AIFF/Amiga/Mac 音訊 AIFF Audio Interchange File Format Monkey's audio Monkey سمعي Aŭdyjo Monkey's Аудио — Monkey àudio Monkey Zvuk Monkey's Monkeys lyd Monkey's-Audio ήχος Monkey's Monkey's audio sonido Monkey Monkey audioa Monkey's Audio -ääni Monkey's ljóður audio Monkey fuaim Monkey's son de Monkey שמע של Monkey's Monkey hang Audio Monkey Audio Monkey's Monkey's オーディオ Monkey аудиосы Monkey's 오디오 Monkey garso įrašas Monkey's audio Monkey's-lyd Monkey's-audio Monkey's Audio-lyd Plik dźwiękowy Monkey's Audio Áudio Monkey's Audio Monkey's аудио Monkey's Zvuk Monkey's Zvočna datoteka Monkey Audio Monkey's Monkey's audio звук Monkey's Âm thanh cua Monkey Monkey's audio 音频 Monkey's 音訊 Impulse Tracker audio Impulse Tracker سمعي Impulse Tracker audio faylı Aŭdyjo Impulse Tracker Аудио — Impulse Tracker àudio d'Impulse Tracker Zvuk Impulse Tracker Sain Impulse Tracker Impulse Tracker-lyd Impulse-Tracker-Audio ήχος Impulse Tracker Impulse Tracker audio Sondosiero de Impulse Tracker sonido Impulse Tracker Impulse Tracker audioa Impulse Tracker -ääni Impulse Tracker ljóður audio Impulse Tracker fuaim Impulse Tracker son de Impulse Tracker שמע של Impulse Tracker Impulse Tracker audio Impulse Tracker hang Audio Impulse Tracker Audio Impulse Tracker Impulse Tracker オーディオ Impulse Tracker аудиосы Impulse Tracker 오디오 Impulse Tracker garso įrašas Impulse Tracker audio Audio Impulse Tracker Impulse Tracker-lyd Impulse Tracker-audio Impulse Tracker lyd Plik dźwiękowy Impulse Tracker áudio Impulse Tracker Áudio Impulse Tracker Audio Impulse Tracker аудио Impulse Tracker Zvuk Impulse Tracker Zvočna datoteka Impulse Tracker Audio Impulse Tracker Impulse Tracker звучни запис Impulse Tracker-ljud звук Impulse Tracker Âm thanh Impulse Tracker Impulse Tracker 音频 Impulse Tracker 音訊 FLAC audio FLAC سمعي Aŭdyjo FLAC Аудио — FLAC àudio FLAC Zvuk FLAC FLAC-lyd FLAC-Audio Ήχος FLAC FLAC audio FLAC-sondosiero sonido FLAC FLAC audioa FLAC-ääni FLAC ljóður audio FLAC fuaim FLAC son FLAC קובץ שמע מסוג FLAC FLAC audio FLAC hang Audio FLAC Audio FLAC FLAC オーディオ FLAC აუდიო FLAC аудиосы FLAC 오디오 FLAC garso įrašas FLAC audio Audio FLAC FLAC-lyd FLAC-audio FLAC-lyd Plik dźwiękowy FLAC áudio FLAC Áudio FLAC Audio FLAC аудио FLAC Zvuk FLAC Zvočna datoteka Flac Audio FLAC FLAC звучни запис FLAC-ljud звук FLAC Âm thanh FLAC FLAC 音频 FLAC 音訊 WavPack audio WavPack سمعي Aŭdyjo WavPack Аудио — WavPack àudio WavPack Zvuk WavPack WavPack-lyd WavPack-Audio ήχος WavePack WavPack audio WavPack-sondosiero sonido WavPack WavPack audioa WavPack-ääni WavPack ljóður audio WavPack fuaim WavPack son WavPack שמע WavPack WavPack audio WavPack hang Audio WavPack Audio WavPack WavPack オーディオ WavPack аудиосы WavPack 오디오 WavPack garso įrašas WavPack audio WavPack-lyd WavPack-audio WavPack-lyd Plik dźwiękowy WavPack Áudio WavPack Audio WavPack аудио WavPack Zvuk WavPack Zvočna datoteka WavPack Audio WavPack WavPack-ljud звук WavPack Âm thanh WavPack WavPack 音频 WavPack 音訊 WavPack audio correction file ملف تصحيح WavPack السمعي Fajł aŭdyjokarekcyi WavPack Файл за корекции на аудио — WavPack fitxer de correcció d'àudio WavPack Opravný zvukový soubor WavPack WavPack-lydkorrektionsfil WavPack-Audiokorrekturdatei αρχείο διόρθωσης ήχου WavePack WavPack audio correction file archivo de corrección de sonido WavPack WavPack audio-zuzenketaren fitxategia WavPack-äänikorjaustiedosto WavPack ljóðrættingarfíla fichier de correction audio WavPack comhad cheartú fhuaim WavPack ficheiro de corrección de son WavPack קובץ תיקון שמע של WavPack WavPack hangjavítási fájl Berkas koreksi audio WavPack File correzione audio WavPack WavPack オーディオコレクションファイル WavPack аудио түзету файлы WavPack 오디오 교정 파일 WavPack garso korekcijos failas WavPack audio korekciju datne WavPack lydkorrigeringsfil WavPack-audio-correctiebestand WawPack lydopprettingsfil Plik korekcji dźwięku WavPack Arquivo de correção de áudio WavPack Fișier audio de corecție WavPack файл коррекции аудио WavPack Opravný zvukový súbor WavPack popravljalna zvočna datoteka WavPack File korrigjgimi audio WavPack WavPack-ljudkorrigeringsfil файл корекції звуку WavPack Tập tin sửa chữa âm thanh WavPack WavPack 音频校正文档 WavPack 音訊校正檔 MIDI audio MIDI سمعي MIDI audio faylı Aŭdyjo MIDI Аудио — MIDI àudio MIDI Zvuk MIDI Sain MIDI MIDI-lyd MIDI-Audio ήχος MIDI MIDI audio MIDI-sondosiero sonido MIDI MIDI audioa MIDI-ääni MIDI ljóður audio MIDI fuaim MIDI son MIDI שמע MIDI MIDI audio MIDI hang Audio MIDI Audio MIDI MIDI オーディオ MIDI аудиосы 미디 오디오 MIDI garso įrašas MIDI audio Audio MIDI MIDI-lyd MIDI-audio MIDI-lyd Plik dźwiękowy MIDI áudio MIDI Áudio MIDI Audio MIDI аудио MIDI Zvuk MIDI Zvočna datoteka MIDI Audio MIDI MIDI звучни запис MIDI-ljud звук MIDI Âm thanh MIDI MIDI 音频 MIDI 音訊 compressed Tracker audio Tracker سمعي مضغوط aŭdyjo skampresavanaha Trackera Аудио — Tracker, компресирано àudio Tracker comprimit Komprimovaný zvuk Tracker Trackerkomprimeret lyd Komprimiertes Tracker-Audio συμπιεσμένος ήχος Tracker compressed Tracker audio tracker de sonido comprimido konprimitutako Tracker audioa pakattu Tracker-ääni stappað Tracker ljóður audio Tracker compressé fuaim chomhbhrúite Tracker son comprimido de Tracker שמע גשש מכווץ komprimirani Tracker audio tömörített Tracker hang audio Tracker terkompresi Audio compresso Tracker 圧縮 Tracker オーディオ сығылған Tracker аудиосы 압축된 Tracker 오디오 suglaudintas Tracker garso įrašas saspiests Tracker audio ingepakte Tracker-audio komprimert Tracker-lyd Skompresowany plik dźwiękowy Tracker Áudio Tracker compactado Tracker audio comprimat аудио Tracker (сжатое) Komprimovaný zvuk Tracker Skrčena zvočna datoteka Tracker Audio Tracker e kompresuar komprimerat Tracker-ljud стиснутий звук Tracker âm thanh Tracker đã nén 压缩的 Tracker 音频 壓縮版 Tracker 音訊 AAC audio AAC Advanced Audio Coding MPEG-4 audio MPEG-4 سمعي Aŭdyjo MPEG-4 Аудио — MPEG-4 àudio MPEG-4 Zvuk MPEG-4 MPEG4-lyd MPEG-4-Audio ήχος MPEG-4 MPEG-4 audio MPEG4-sondosiero sonido MPEG-4 MPEG-4 audioa MPEG-4-ääni MPEG-4 ljóður audio MPEG-4 fuaim MPEG-4 son MPEG-4 שמע MPEG-4 MPEG-4 audio MPEG-4 hang Audio MPEG-4 Audio MPEG-4 MPEG-4 オーディオ MPEG-4 აუდიო MPEG-4 аудиосы MPEG-4 오디오 MPEG-4 garso įrašas MPEG-4 audio MPEG-4-lyd MPEG4-audio MPEG-4-lyd Plik dźwiękowy MPEG-4 Áudio MPEG-4 Audio MPEG-4 аудио MPEG-4 Zvuk MPEG-4 Zvočna datoteka MPEG-4 Audio MPEG-4 MPEG-4-ljud звук MPEG-4 Âm thanh MPEG-4 MPEG-4 音频 MPEG-4 音訊 MPEG-4 video MPEG-4 مرئي Videa MPEG-4 Видео — MPEG-4 vídeo MPEG-4 Video MPEG-4 MPEG4-video MPEG-4-Video βίντεο MPEG-4 MPEG-4 video MPEG-4-video vídeo MPEG-4 MPEG-4 bideoa MPEG-4-video MPEG-4 video vidéo MPEG-4 físeán MPEG-4 vídeo MPEG-4 וידאו MPEG-4 MPEG-4 video MPEG-4 videó Video MPEG-4 Video MPEG-4 MPEG-4 動画 MPEG-4 ვიდეო MPEG-4 видеосы MPEG-4 비디오 MPEG-4 vaizdo įrašas MPEG-4 video MPEG-4-film MPEG4-video MPEG-4-video Plik wideo MPEG-4 Vídeo MPEG-4 Video MPEG-4 видео MPEG-4 Video MPEG-4 Video datoteka MPEG-4 Video MPEG-4 MPEG-4-video відеокліп MPEG-4 Ảnh động MPEG-4 MPEG-4 视频 MPEG-4 視訊 MPEG-4 audio book كتاب MPEG-4 السمعي Aŭdyjokniha MPEG-4 Аудио книга — MPEG-4 llibre d'àudio MPEG-4 Zvuková kniha MPEG-4 MPEG4-lydbog MPEG-4-Hörbuch ηχητικό βιβλίο MPEG-4 MPEG-4 audio book MPEG-4-sonlibro audiolibro en MPEG-4 MPEG-4 audio-liburua MPEG-4-äänikirja MPEG-4 ljóðbók livre audio MPEG-4 leabhar fhuaim MPEG-4 sonlibro de MPEG-4 ספר דיגיטלי MPEG-4 MPEG-4 audio knjiga MPEG-4 hangoskönyv Buku audio MPEG-4 Audiolibro MPEG-4 MPEG-4 オーディオブック MPEG-4 აუდიოწიგნი MPEG-4 аудио кітабы MPEG-4 오디오북 MPEG-4 garso knyga MPEG-4 audio grāmata MPEG-4-lydbok MPEG4-audioboek MPEG-4-lydbok Książka dźwiękowa MPEG-4 Áudio livro MPEG-4 Carte audio MPEG-4 аудиокнига MPEG-4 Zvuková kniha MPEG-4 Zvočna knjiga MPEG-4 Audiolibër MPEG-4 MPEG-4-ljudbok аудіокнига MPEG-4 Sách âm thanh MPEG-4 MPEG-4 有声书 MPEG-4 音訊書 3GPP multimedia file ملف وسائط متعددة 3GPP Multymedyjny fajł 3GPP Мултимедия — 3GPP fitxer multimèdia 3GPP Soubor multimédií 3GPP 3GPP multimedie-fil 3GPP-Multimediadatei αρχείο πολυμέσων 3GPP 3GPP multimedia file archivo multimedia 3GPP 3GPP multimediako fitxategia 3GPP-multimediatiedosto 3GGP margmiðlafíla fichier multimédia 3GPP comhad ilmheán 3GPP ficheiro multimedia 3GPP קובץ מולטימדיה מסוג 3GPP 3GPP multimedijska datoteka 3GPP multimédiafájl Berkas multimedia 3GPP File multimediale 3GPP 3GPP マルチメディアファイル 3GPP მულტიმედიური ფაილი 3GPP мультимедиялық файлы 3GPP 멀티미디어 파일 3GPP multimedijos failas 3GPP multimediju datne 3GPP-multimediafil 3GPP-multimediabestand 3GPP-multimediafil Plik multimedialny 3GPP Arquivo multimídia 3GPP Fișier multimedia 3GPP мультимедийный файл 3GPP Súbor multimédií 3GPP Večpredstavnostna datoteka 3GPP File multimedial 3GPP 3GPP-multimediafil 3GPP multimedya dosyası файл мультимедійних даних 3GPP Tập tin đa phương tiện 3GPP 3GPP 多媒体文件 3GPP 多媒體檔案 3GPP 3rd Generation Partnership Project 3GPP2 multimedia file Мултимедия — 3GPP2 fitxer multimèdia 3GPP2 Soubor multimédií 3GPP2 3GPP2 multimedie-fil 3GPP2-Multimediadatei αρχείο πολυμέσων 3GPP2 3GPP2 multimedia file archivo multimedia 3GPP2 3GPP2-multimediatiedosto 3GGP2 margmiðlafíla fichier multimédia 3GPP2 comhad ilmheán 3GPP2 ficheiro multimedia 3GPP2 קובץ מולטימדיה 3GPP2 3GPP2 multimedijska datoteka 3GPP2 multimédiafájl Berkas multimedia 3GPP2 File multimediale 3GPP2 3GPP2 マルチメディアファイル 3GPP2 მულტიმედიური ფაილი 3GPP2 мультимедиялық файлы 3GPP2 멀티미디어 파일 3GPP2 multimediju datne 3GPP2 multimedia bestand Plik multimedialny 3GPP2 Arquivo multimídia 3GPP2 Fișier multimedia 3GPP2 мультимедийный файл 3GPP2 Súbor multimédií 3GPP2 Večpredstavnostna datoteka 3GPP2 3GPP2-multimediafil 3GPP2 multimedya dosyası файл мультимедійних даних 3GPP2 3GPP2 多媒体文件 3GPP2 多媒體檔案 3GPP2 3rd Generation Partnership Project 2 Amiga SoundTracker audio مقتفي صوت Amiga السمعي Aŭdyjo Amiga SoundTracker Аудио — Amiga SoundTracker àudio Amiga SoundTracker Zvuk Amiga SoundTracker Amiga SoundTracker-lyd Amiga-SoundTracker-Audio ήχος Amiga SoundTracker Amiga SoundTracker audio Sondosiero de Amiga SoundTracker sonido de Amiga SoundTracker Amiga soundtracker audioa Amiga SoundTracker -ääni Amiga SoundTracker ljóður audio SoundTracker Amiga fuaim Amiga SoundTracker son de Amiga SoundTracker קובץ שמע של Amiga SoundTracker Amiga SoundTracker audio Amiga SoundTracker hang Audio Amida SoundTracker Audio Amiga SoundTracker Amiga SoundTracker オーディオ Amiga SoundTracker-ის აუდიო Amiga SoundTracker аудиосы Amiga SoundTracker 오디오 Amiga SoundTracker garso įrašas Amiga SoundTracker audio Audio Amiga Soundtracker Amiga SoundTracker-lyd Amiga SoundTracker-audio Amiga soundtracker-lyd Plik dźwiękowy Amiga SoundTracker áudio SoundTracker do Amiga Áudio Amiga SoundTracker Audio Amiga SoundTracker аудио Amiga SoundTracker Zvuk Amiga SoundTracker Zvočna datoteka Amiga SoundTracker Audio Amiga SoundTracker Амига soundtracker звук Amiga SoundTracker-ljud звук Amiga SoundTracker Âm thanh Amiga SoundTracker Amiga SoundTracker 音频 Amiga SoundTracker 音訊 MP2 audio MP2 سمعي Aŭdyjo MP2 Аудио — MP2 àudio MP2 Zvuk MP2 MP2-lyd MP2-Audio ήχος MP2 MP2 audio MP2-sondosiero sonido MP2 MP2 audioa MP2-ääni MP2 ljóður audio MP2 fuaim MP2 son MP2 שמע MP2 MP2 audio MP2 hang Audio MP2 Audio MP2 MP2 オーディオ MP2 аудиосы MP2 오디오 MP2 garso įrašas MP2 audio MP2-lyd MP2-audio MP2-lyd Plik dźwiękowy MP2 Áudio MP2 Audio MP2 аудио MP2 Zvuk MP2 Zvočna datoteka MP2 Audio MP2 MP2-ljud звук MP2 Âm thanh MP2 MP2 音频 MP2 音訊 MP3 audio MP3 سمعي MP3 audio faylı Aŭdyjo MP3 Аудио — MP3 àudio MP3 Zvuk MP3 Sain MP3 MP3-lyd MP3-Audio ήχος MP3 MP3 audio MP3-sondosiero sonido MP3 MP3 audioa MP3-ääni MP3 ljóður audio MP3 fuaim MP3 son MP3 שמע MP3 MP3 audio MP3 hang Audio MP3 Audio MP3 MP3 オーディオ MP3 აუდიო MP3 аудиосы MP3 오디오 MP3 garso įrašas MP3 audio Audio MP3 MP3-lyd MP3-audio MP3-lyd Plik dźwiękowy MP3 áudio MP3 Áudio MP3 Audio MP3 аудио MP3 Zvuk MP3 Zvočna datoteka MP3 Audio MP3 MP3 звучни запис MP3-ljud звук MP3 Âm thanh MP3 MP3 音频 MP3 音訊 MP3 audio (streamed) MP3 سمعي (تدفق) Aŭdyjo MP3 (płyń) Аудио — MP3, поточно àudio MP3 (flux) Zvuk MP3 (proud) MP3-lyd (strøm) MP3-Audio (Stream) ήχος MP3 (εκπεμπόμενος) MP3 audio (streamed) MP3-sondosiero (fluigate) sonido MP3 (en flujo) MP3 audioa (korrontea) MP3-ääni (virtaus) MP3 ljóður (streymað) audio MP3 (flux) fuaim MP3 (sruthaithe) son MP3 (en stream) שמע MP3 (מוזרם) MP3 hang (sugárzott) Audio MP3 (stream) Audio MP3 (in streaming) MP3 オーディオ (ストリーム) MP3 აუდიო (ნაკადი) MP3 аудиосы (ағымдық) MP3 오디오 (스트림) MP3 garso įrašas (transliuojamas) MP3 audio (straumēts) Audio MP3 (aliran) MP3-lyd (streaming) MP3-audio (gestreamd) Strauma MP3-lyd Dźwięk MP3 (strumień) áudio MP3 (em fluxo) Áudio MP3 (em fluxo) Audio MP3 (flux) аудио MP3 (потоковое) Zvuk MP3 (streamovaný) Zvočna datoteka MP3 (pretočna) Audio MP3 (streamed) MP3 звучни запис (непрекидан) MP3-ljud (flöde) звук MP3 (потоковий) Âm thanh MP3 (chạy luồng) MP3 流音频 MP3 音訊 (串流) HTTP Live Streaming playlist قائمة بث HTTP حية Списък за изпълнение — поток по HTTP llista de reproducció en temps real HTTP Seznam skladeb HTTP Live Streaming Afspilningsliste til HTTP-livestrøm HTTP Live-Streaming-Wiedergabeliste HTTP Live Streaming playlist lista de reproducción de flujo en directo HTTP HTTP zuzeneko korrontearen erreprodukzio-zerrenda HTTP beinleiðis streymaður avspælingarlisti liste de lecture de flux HTTP Live seinmliosta sruthaithe bheo HTTP lista de reprodución de fluxo HTTP רשימת השמעה הזרימה של HTTP HTTP élő lejátszólista Daftar putar HTTP Live Streaming Scaletta Live Steaming HTTP HTTP ライブストリーミング再生リスト HTTP тірі ағым ойнау тізімі HTTP 라이브 스트리밍 재생 목록 HTTP tiesioginio transliavimo grojaraštis HTTP dzīvās straumēšanas repertuārs HTTP Live Streaming afspeellijst Lista odtwarzania strumieniowego na żywo HTTP Lista de Reprodução Streaming ao Vivo de HTTP Listă de redare difuzată ca flux HTTP список воспроизведения HTTP-потока Seznam predvajanja živega pretoka HTTP HTTP Live Streaming-spellista список відтворення HTTP Live Streaming HTTP 直播流播放列表 HTTP 即時串流播放清單 Microsoft ASX playlist قائمة تشغيل مايكروسوفت ASX Śpis Microsoft ASX Списък за изпълнение — Microsoft ASX llista de reproducció ASX de Microsoft Seznam skladeb Microsoft ASX Microsoft ASX-afspilningsliste Microsoft-ASX-Wiedergabeliste λίστα αναπαραγωγής Microsoft ASX Microsoft ASX playlist lista de reproducción ASX de Microsoft Microsoft ASX erreprodukzio-zerrenda Microsoft ASX -soittolista Microsoft ASX avspælingarlisti liste de lecture Microsoft ASX seinmliosta Microsoft ASX lista de reprodución Microsoft ASX רשימת השמעה ASX (מיקרוסופט) Microsoft ASX popis za reprodukciju Microsoft ASX lejátszólista Senarai putar Microsoft ASX Scaletta Microsoft ASX Microsoft ASX 再生リスト Microsoft-ის ASX რეპერტუარი Microsoft ASX ойнау тізімі 마이크로소프트 ASX 재생 목록 Microsoft ASX grojaraštis Microsoft ASX repertuārs Microsoft ASX-spilleliste Microsoft ASX-afspeellijst Microsoft ASX-speleliste Lista odtwarzania Microsoft ASX Lista de reprodução do Microsoft ASX Listă redare Microsoft ASX список воспроизведения Microsoft ASX Zoznam skladieb Microsoft ASX Seznam predvajanja Microsoft ASX Listë titujsh Microsoft ASF Microsoft ASX-spellista список відтворення ASX Microsoft Danh mục nhạc Microsoft ASX Microsoft ASX 播放列表 微軟 ASX 播放清單 PSF audio PSF سمعي Aŭdyjo PSF Аудио — PSF àudio PSF Zvuk PSF PSF-lyd PSF-Audio ήχος PSF PSF audio PSF-sondosiero sonido PSF PSF audioa PSF-ääni PSF ljóður audio PSF fuaim PSF son PSF שמע PSF PSF hang Audio PSF Audio PSF PSF オーディオ PSF аудиосы PSF 오디오 PSF garso įrašas PSF audio PSF-lyd PSF-audio PSF-lyd Plik dźwiękowy PSF Áudio PSF Audio PSF аудио PSF Zvuk PSF Zvočna datoteka PSF Audio PSF PSF-ljud звук PSF Âm thanh PSF PSF 音频 PSF 音訊 PSF Portable Sound Format MiniPSF audio MiniPSF سمعي Aŭdyjo MiniPSF Аудио — MiniPSF àudio MiniPSF Zvuk MiniPSF MiniPSF-lyd MiniPSF-Audio ήχος MiniPSF MiniPSF audio MiniPSF-sondosiero sonido MiniPSF MiniPSF audioa MiniPSF-ääni MiniPSF ljóður audio MiniPSF fuaim MiniPSF son MiniPSF שמע של MiniPSP MiniPSF audio MiniPSF hang Audio MiniPSF Audio MiniPSF MiniPSF オーディオ MiniPSF-ის აუდიო MiniPSF аудиосы MiniPSF 오디오 MiniPSF garso įrašas MiniPSF audio MiniPSF-lyd MiniPSF-audio MiniPSF-lyd Plik dźwiękowy MiniPSF Áudio MiniPSF Audio MiniPSF аудио MiniPSF Zvuk MiniPSF Zvočna datoteka MiniPSF Audio MiniPSF MiniPSF-ljud звук MiniPSF Âm thanh MiniPSF MiniPSF 音频 MiniPSF 音訊 MiniPSF Miniature Portable Sound Format PSFlib audio library مكتبة PSFlib السمعية Aŭdyjobiblijateka PSFlib Аудио библиотека — PSFlib biblioteca d'àudio PSFlib Zvuková knihovna PSFlib PSFlib-lydbibliotek PSFlib-Audiobibliothek βιβλιοθήκη ήχου PSFlib PSFlib audio library biblioteca de sonido PSFlib PSFlib audioaren liburutegia PSFlib-äänikirjasto PSFlib ljóðsavn bibliothèque audio PSFlib leabharlann fhuaim PSFlib Biblioteca de son PSFlib ספריית שמע PSFlib PSFlib hanggyűjtemény Pustaka audio PSFlib Libreria audio PSFlib PSFlib オーディオライブラリ PSFlib аудио жинағы PSFlib 오디오 라이브러리 PSFlib garso biblioteka PSFlib fonotēka PSFlib-lydbibliotek PSFlib-audiobibliotheek PSFlib lydbibliotek Biblioteka dźwiękowa PSFlib Biblioteca de áudio PSFlib Bibliotecă audio PSFlib фонотека PSFlib Zvuková knižnica PSFlib Zvočna knjižnica PSFlib Librari audio PSFlib PSFlib-ljudbibliotek аудіобібліотека PSFlib Thư viện âm thanh PSFlib PSFlib 音频库文件 PSFlib 音訊庫 PSFlib Portable Sound Format Library Windows Media audio Windows Media سمعي Aŭdyjo Windows Media Аудио — Windows Media àudio Windows Media Zvuk Windows Media Windows Media-lyd Windows-Media-Audio ήχος Windows Media Windows Media audio sonido de Windows Media Windows Media audioa Windows Media -ääni Windows Media ljóður audio Windows Media fuaim Windows Media son de Windows Media שמע של Windows Media Windows Media audio Windows Media hang Audio Windows Media Audio Windows Media Windows Media オーディオ Windows Media аудиосы Windows 미디어 오디오 Windows Media garso įrašas Windows Media audio Windows Media lyd Windows Media-audio Windows Media-lyd Plik dźwiękowy Windows Media Áudio do Windows Media Audio Windows Media аудио Windows Media Zvuk Windows Media Zvočna datoteka Windows Media Audio Windows Media Windows Media-ljud звук Windows Media Âm thanh Windows Media Windows Media 音频 Windows Media 音訊 Musepack audio Musepack سمعي Aŭdyjo Musepack Аудио — Musepack àudio Musepack Zvuk Musepack Musepacklyd Musepack-Audio ήχος Musepack Musepack audio sonido Musepack Musepack audioa Musepack-ääni Musepack ljóður audio Musepack fuaim Musepack son de Musepack שמע של Musepack Musepack audio Musepack hang Audio Musepack Audio Musepack Musepack オーディオ Musepack аудиосы Musepack 오디오 Musepack garso įrašas Musepack audio Musepack-lyd Musepack-audio Musepack-lyd Plik dźwiękowy Musepack Áudio Musepack Audio Musepack аудио Musepack Zvuk Musepack Zvočna datoteka Musepack Audio Musepack Musepack-ljud звук Musepack Âm thanh Musepack Musepack 音频 Musepack 音訊 RealAudio document مستند RealAudio Dakument RealAudio Документ — RealAudio document RealAudio Dokument RealAudio RealAudio-dokument RealAudio-Dokument έγγραφο RealAudio RealAudio document RealAudio-dokumento documento RealAudio RealAudio dokumentua RealAudio-asiakirja RealAudio skjal document RealAudio cáipéis RealAudio documento Realson מסמך של RealAudio RealAudio dokument RealAudio dokumentum Dokumen RealAudio Documento RealAudio RealAudio ドキュメント RealAudio құжаты RealAudio 문서 RealAudio dokumentas RealAudio dokuments RealAudio-dokument RealAudio-document RealAudio-dokument Dokument RealAudio Documento RealAudio Document RealAudio документ RealAudio Dokument RealAudio Dokument RealAudio Dokument RealAudio RealAudio-dokument документ RealAudio Tài liệu âm thanh RealAudio RealAudio 文档 RealAudio 文件 RealMedia Metafile ملف تعريف RealMedia Metafajł RealMedia Метафайл — RealMedia metafitxer RealMedia RealMedia Metafile RealMedia-metafil RealMedia-Metadatei Metafile RealMedia RealMedia Metafile metarchivo RealMedia RealMedia metafitxategia RealMedia-metatiedosto RealMedia metafíla métafichier RealMedia meiteachomhad RealMedia Metaficheiro RealMedia קובץ מטא של RealMedia RealMedia metafájl RealMedia Metafile Metafile RealMedia RealMedia メタファイル RealMedia метафайлы RealMedia 메타파일 RealMedia metafailas RealMedia metadatne RealMedia-metafil RealMedia-metabestand RealMedia-metafil Metaplik RealMedia Meta arquivo do RealMedia Metafișier RealMedia мета-файл RealMedia RealMedia Metafile Metadatoteka RealMedia Metafile RealMedia RealMedia-metafil метафайл RealMedia Siêu tập tin RealMedia RealMedia 元文件 RealMedia 中介檔 RealVideo document مستند RealVideo Dakument RealVideo Документ — RealVideo document RealVideo Dokument RealVideo RealAudio-dokument RealVideo-Dokument έγγραφο RealVideo RealVideo document RealVideo-dokumento documento RealVideo RealVideo dokumentua RealVideo-asiakirja RealVideo skjal document RealVideo cáipéis RealVideo documento RealVideo מסמך של RealVideo RealVideo dokument RealVideo dokumentum Dokumen RealVideo Documento RealVideo RealVideo ドキュメント RealVideo құжаты RealVideo 문서 RealVideo dokumentas RealVideo dokuments RealAudio-dokument RealVideo-document RealVideo-dokument Dokument RealVideo Documento RealVideo Document RealVideo документ RealVideo Dokument RealVideo Video datoteka RealVideo Dokument RealVideo RealVideo-dokument документ RealVideo Tài liệu ảnh động RealVideo RealAudio 文档 RealVideo 文件 RealMedia document مستند RealMedia Dakument RealMedia Документ — RealMedia document RealMedia Dokument RealMedia RealMedia-dokument RealMedia-Dokument έγγραφο RealMedia RealMedia document RealMedia-dokumento documento RealMedia RealMedia dokumentua RealMedia-asiakirja RealMedia skjal document RealMedia cáipéis RealMedia documento RealMedia מסמך של RealMedia RealMedia dokument RealMedia dokumentum Dokumen RealMedia Documento RealMedia RealMedia ドキュメント RealMedia құжаты RealMedia 문서 RealMedia dokumentas RealMedia dokuments RealMedia-dokument RealMedia-document RealMedia-dokument Dokument RealMedia Documento RealMedia Document RealMedia документ RealMedia Dokument RealMedia Dokument RealMedia Dokument RealMedia RealMedia-dokument документ RealMedia Tài liệu RealMedia RealMedia 文档 RealMedia 文件 RealPix document مستند RealPix Dakument RealPix Документ — RealPix document RealPix Dokument RealPix RealPix-dokument RealPix-Dokument έγγραφο RealPix RealPix document RealPix-dokumento documento RealPix RealPix dokumentua RealPix-asiakirja RealPix skjal document RealPix cáipéis RealPix documento RealPix מסמך של RealPix RealPix dokument RealPix dokumentum Dokumen RealPix Documento RealPix RealPix ドキュメント RealPix құжаты RealPix 문서 RealPix dokumentas RealPix dokuments RealPix-dokument RealPix-document RealPix-dokument Dokument RealPix Documento RealPix Document RealPix документ RealPix Dokument RealPix Dokument RealPix Dokument RealPix RealPix-dokument документ RealPix Tài liệu ảnh RealPix RealPix 文档 RealPix 文件 RealText document مستند RealText Dakument RealText Документ — RealText document RealText Dokument RealText RealText-dokument RealText-Dokument έγγραφο RealText RealText document RealText-dokumento documento RealText RealText dokumentua RealText-asiakirja RealText skjal document RealText cáipéis RealText documento RealText מסמך של RealText RealText dokument RealText dokumentum Dokumen RealText Documento RealText RealText ドキュメント RealText құжаты RealText 문서 RealText dokumentas RealText dokuments RealText-dokument RealText-document RealText-dokument Dokument RealText Documento RealText Document RealText документ RealText Dokument RealText Dokument RealText Dokument RealText RealText-dokument документ RealText Tài liệu văn bản RealText RealText 文档 RealText 文件 RIFF audio RIFF سمعي RIFF audio faylı Aŭdyjo RIFF Аудио — RIFF àudio RIFF Zvuk RIFF Sain RIFF RIFF-lyd RIFF-Audio ήχος RIFF RIFF audio RIFF-sondosiero sonido RIFF RIFF audioa RIFF-ääni RIFF ljóð audio RIFF fuaim RIFF son RIFF שמע RIFF RIFF audio RIFF-kép Audio RIFF Audio RIFF RIFF オーディオ RIFF аудиосы RIFF 오디오 RIFF garso įrašas RIFF audio Audio RIFF RIFF-lyd RIFF-audio RIFF-lyd Plik dźwiękowy RIFF áudio RIFF Áudio RIFF Audio RIFF аудио RIFF Zvuk RIFF Zvočna datoteka RIFF Audio RIFF RIFF звучни запис RIFF-ljud звук RIFF Âm thanh RIFF RIFF 音频 RIFF 音訊 Scream Tracker 3 audio Scream Tracker 3 سمعي Scream Tracker 3 audio faylı Aŭdyjo Scream Tracker 3 Аудио — Scream Tracker 3 àudio de Scream Tracker 3 Skladba Scream Tracker 3 Sain Scream Tracker 3 Scream Tracker 3-lyd Scream-Tracker-3-Audio ήχος Scream Tracker 3 Scream Tracker 3 audio Sondosiero de Scream Tracker 3 sonido Scream Tracker 3 Scream Tracker 3 audioa Scream Tracker 3 -ääni Scream Tracker 3 ljóður audio Scream Tracker 3 fuaim Scream Tracker 3 son Scream Tracker 3 שמע של Scream Tracker 3 Scream Tracker 3 audio Scream Tracker 3 hang Audio Scream Tracker 3 Audio Scream Tracker 3 Scream Tracker 3 オーディオ Scream Tracker 3 аудиосы Scream Tracker 3 오디오 Scream Tracker 3 garso įrašas Scream Tracker 3 audio Audio Scream Tracker 3 Scream Tracker 3-lyd Scream Tracker 3-audio Sream Tracker 3 lyd Plik dźwiękowy Scream Tracker 3 áudio Scream Tracker 3 Áudio Scream Tracker 3 Audio Scream Tracker 3 аудио Scream Tracker 3 Skladba Scream Tracker 3 Zvočna datoteka Scream Tracker 3 Audio Scream Tracker 3 Scream Tracker 3 звучни запис Scream Tracker 3-ljud звук Scream Tracker 3 Âm thanh Scream Tracker 3 Scheme Tracker 3 音频 Scream Tracker 3 音訊 MP3 ShoutCast playlist قائمة تشغيل MP3 ShoutCast Śpis piesień dla tranślacyi MP3 Списък за изпълнение — MP3 ShoutCast llista de reproducció MP3 ShoutCast Seznam skladeb MP3 ShoutCast MP3 ShoutCast-afspilningsliste MP3-ShoutCast-Wiedergabeliste Λίστα αναπαραγωγής MP3 ShoutCast MP3 ShoutCast playlist MP3-ludlisto de ShoutCast lista de reproducción MP3 ShoutCast MP3 ShoutCast erreprodukzio-zerrenda MP3 ShoutCast -soittolista MP3 ShoutCast avspælingarlisti liste de lecture MP3 ShoutCast seinmliosta MP3 ShoutCast lista de reprodución MP3 de ShoutCast רשימת השמעה MP3 של ShoutCast MP3 ShoutCast popis za reprodukciju MP3 ShoutCast-lejátszólista Senarai putar MP3 ShoutCast Scaletta MP3 ShoutCast MP3 ShoutCast 再生リスト MP3 ShoutCast ойнау тізімі MP3 ShoutCast 재생 목록 MP3 ShoutCast grojaraštis MP3 ShoutCast repertuārs Senaraimain ShoutCast MP3 MP3 ShoutCast-spilleliste MP3 ShoutCast-afspeellijst MP3 ShoutCast-speleliste Lista odtwarzania MP3 ShoutCast lista de reprodução MP3 ShoutCast Lista de reprodução MP3 ShoutCast Listă MP3 ShoutCast список воспроизведения MP3 ShoutCast Zoznam skladieb MP3 ShoutCast Seznam predvajanja MP3 ShoutCast Listë titujsh MP3 ShoutCast MP3 ShoutCast списак песама MP3 ShoutCast-spellista список програвання MP3 ShoutCast Danh mục nhạc MP3 ShoutCast MP3 ShoutCast 播放列表 MP3 ShoutCast 播放清單 Scream Tracker audio Scream Tracker سمعي Scream Tracker audio faylı Aŭdyjo Scream Tracker Аудио — Scream Tracker àudio de Scream Tracker Skladba Scream Tracker Sain Scream Tracker Scream Tracker-lyd Scream-Tracker-Audio ήχος Scream Tracker Scream Tracker audio Sondosiero de Scream Tracker sonido Scream Tracker Scream Tracker audioa Scream Tracker -ääni Scream Tracker ljóður audio Scream Tracker fuaim Scream Tracker son Scream Tracker שמע של Scream Tracker Scream Tracker audio Scream Tracker hang Audio Scream Tracker Audio Scream Tracker Scream Tracker オーディオ Scream Tracker аудиосы Scream Tracker 오디오 Scream Tracker garso įrašas Scream Tracker audio Audio Scream Tracker Scream Tracker-lyd Scream Tracker-audio Scream Tracker lyd Plik dźwiękowy Scream Tracker áudio Scream Tracker Áudio Scream Tracker Audio Scream Tracker аудио Scream Tracker Skladba Scream Tracker Zvočna datoteka Scream Tracker Audio Scream Tracker Scream Tracker звучни запис Scream Tracker-ljud звук Scream Tracker Âm thanh Scream Tracker Scream Tracker 音频 Scream Tracker 音訊 VOC audio VOC سمعي VOC audio faylı Aŭdyjo VOC Аудио — VOC àudio VOC Zvuk VOC Sain VOC VOC-lyd VOC-Audio ήχος VOC VOC audio VOC-sondosiero sonido VOC VOC audioa VOC-ääni VOC ljóður audio VOC fuaim VOC son VOC שמע VOC VOC audio VOC hang Audio VOC Audio VOC VOC オーディオ VOC аудиосы VOC 오디오 VOC garso įrašas VOC audio Audio VOC VOC-lyd VOC-audio VOC-lyd Plik dźwiękowy VOC áudio VOC Áudio VOC Audio VOC аудио VOC Zvuk VOC Zvočna datoteka VOC Audio VOC VOC звучни запис VOC-ljud звук VOC Âm thanh VOC VOC 音频 VOC 音訊 WAV audio WAV سمعي WAV audio faylı Aŭdyjo WAV Аудио — WAV àudio WAV Zvuk WAV Sain WAV WAV-lyd WAV-Audio ήχος WAV WAV audio WAV-sonkodo sonido WAV WAV audioa WAV-ääni WAV ljóður audio WAV fuaim WAV son WAV שמע WAV WAV audio WAV hang Audio WAV Audio WAV WAV オーディオ WAV аудиосы WAV 오디오 WAV garso įrašas WAV audio Audio VOC WAV-lyd WAV-audio WAV-lyd Plik dźwiękowy WAV áudio WAV Áudio WAV Audio WAV аудио WAV Zvuk WAV Zvočna datoteka WAV Audio WAV WAV звучни запис WAV-ljud звук WAV Âm thanh WAV WAV 音频 WAV 音訊 Scream Tracker instrument آلة Scream Tracker Scream Tracker instrumenti Instrument Scream Tracker Инструмент — Scream Tracker instrument de Scream Tracker Nástroj pro Scream Tracker Offeryn Scream Tracker Scream Tracker-instrument Scream-Tracker-Instrument μουσικό όργανο Scream Tracker Scream Tracker instrument instrumento de Scream Tracker instrumento Scream Tracker Scream Tracker instrumentua Scream Tracker -soitin Scream Tracker ljóðføri instrument Scream Tracker ionstraim Scream Tracker Instrumento Scream Tracker כלי של Scream Tracker Scream Tracker instrument Scream Tracker hangszer Instrumen Scream Tracker Strumento Scream Tracker Scream Tracker インストゥルメント Scream Tracker сайманы Scream Tracker 악기 Scream Tracker instrumentas Scream Tracker instrumenti Instrumen Scream Tracker Scream Tracker-instrument Scream Tracker-instrument Scream Tracker instrument Instrument Scream Tracker instrumento Scream Tracker Instrumento Scream Tracker Instrument Scream Tracker инструмент Scream Tracker Nástroj pre Scream Tracker Datoteka zvoka glasbila Scream Tracker Instrument Scream Tracker Scream Tracker инструмент Scream Tracker-instrument інструмент Scream Tracker Nhạc khí Scream Tracker Scream Tracker 乐器 Scream Tracker 樂器檔 FastTracker II audio FastTracker II سمعي FastTracker II audio faylı Aŭdyjo FastTracker II Аудио — FastTracker II àudio de FastTracker II Zvuk FastTracker II Sain FastTracker II FastTracker II-lyd FastTracker-II-Audio ήχος FastTracker II FastTracker II audio Sondosiero de FastTracker II sonido FastTracker II FastTracker II.ren audioa FastTracker II -ääni FastTracker II ljóður audio FastTracker II fuaim FastTracker II son de FastTracker II שמע FastTracker II FastTracker II audio FastTracker II hang Audio FastTracker II Audio FastTracker II FastTracker II オーディオ FastTracker II-ის აუდიო FastTracker II аудиосы FastTracker II 오디오 FastTracker II garso įrašas FastTracker II audio Audio FastTracker II FastTracker II-lyd FastTracker II-audio FastTracker II lyd Plik dźwiękowy FastTracker II áudio FastTracker II Áudio FastTracker II Audio FastTracker II аудио FastTracker II Zvuk FastTracker II Zvočna datoteka FastTracker II Audio FastTracker II FastTracker II аудио запис FastTracker II-ljud звук FastTracker II Âm thanh FastTracker II FastTracker II 音频 FastTracker II 音訊 TrueAudio audio TrueAudio سمعي Aŭdyjo TrueAudio Аудио — TrueAudio àudio TrueAudio Zvuk TrueAudio TrueAudio-lyd TrueAudio-Audio ήχος TrueAudio TrueAudio audio TrueAudio-sondosiero sonido TrueAudio TrueAudio audioa TrueAudio-ääni TrueAudio ljóður audio TrueAudio fuaim TrueAudio son Trueson שמע TrueAudio TrueAudio audio TrueAudio hang Audio TrueAudio Audio TrueAudio TrueAudio オーディオ TrueAudio аудиосы TrueAudio 오디오 TrueAudio garso įrašas TrueAudio audio TrueAudio-lyd TrueAudio-audio TrueAudio-lyd Plik dźwiękowy TrueAudio Áudio TrueAudio Audio TrueAudio аудио TrueAudio Zvuk TrueAudio Zvočna datoteka TrueAudio Audio TrueAudio TrueAudio-ljud звук TrueAudio Âm thanh TrueAudio TrueAudio 音频 TrueAudio 音訊 Windows BMP image صورة Windows BMP Windows BMP rəsmi Vyjava Windows BMP Изображение — Windows BMP imatge BMP de Windows Obrázek Windows BMP Delwedd BMP Windows Windows BMP-billede Windows-BMP-Bild εικόνα Windows BMP Windows BMP image BMP-bildo de Vindozo imagen BMP de Windows Windows BMP irudia Windows BMP -kuva Windows BMP mynd image Windows BMP íomhá BMP Windows imaxe BMP de Windows תמונת BMP של Windows Windows BMP slika Windows BMP-kép Citra Windows BMP Immagine Windows BMP Windows BMP 画像 Windows BMP суреті Windows BMP 그림 Windows BMP paveikslėlis Windows BMP attēls Imej BMP Windows Windows BMP-bilde Windows BMP-afbeelding Windows BMP-bilete Obraz BMP Windows imagem BMP Windows Imagem BMP do Windows Imagine Windows BMP изображение Windows BMP Obrázok Windows BMP Slikovna datoteka Windows BMP Figurë Windows BMP Windows BMP слика Windows BMP-bild Windows BMP görüntüsü зображення Windows BMP Ảnh BMP Windows Windows BMP 图像 Windows BMP 影像 WBMP image صورة WBMP Vyjava WBMP Изображение — WBMP imatge WBMP Obrázek WBMP WBMP-billede WBMP-Bild εικόνα WBMP WBMP image WBMP-bildo imagen WBMP WBMP irudia WBMP-kuva WBMP mynd image WBMP íomhá WBMP imaxe WBMP תמונת WBMP WBMP slika WBMP kép Citra WBMP Immagine WBMP WBMP 画像 WBMP суреті WBMP 그림 WBMP paveikslėlis WBMP attēls WBMP-bilde WBMP-afbeelding WBMP-bilete Obraz WBMP Imagem WBMP Imagine WBMP изображение WBMP Obrázok WBMP Slikovna datoteka WBMP Figurë WBMP WBMP-bild зображення WBMP Ảnh WBMP WBMP 图像 WBMP 影像 WBMP WAP bitmap Computer Graphics Metafile ملف تعريف رسوميات الحاسوب Kompüter Qrafikası Meta Faylı Metafajł Computer Graphics Метафайл — Computer Graphics metafitxer de Computer Graphics Computer Graphics Metafile Delwedd ffurf CGM Computer Graphics-metafil CGM-Datei αρχείο Computer Graphics Metafile Computer Graphics Metafile metaarchivo de Computer Graphics Ordenagailuko grafikoen meta-fitxategia Computer Graphics -metatiedosto Teldugrafikk metafíla métafichier Computer Graphics meiteachomhad Grafaicí Ríomhaire metaficheiro de Computer Graphics קובץ-מטה מסוג Computer Graphics Computer Graphics-metafájl Computer Graphics Metafile Computer Graphics Metafile コンピューターグラフィックメタファイル компьютерлік графика метафайлы 컴퓨터 그래픽스 메타파일 Computer Graphics metafailas Datorgrafikas metadatne Failmeta Grafik Komputer Computer Graphics Metafile Computer Graphics-metabestand Computer Graphics Metafile Metaplik grafiki komputerowej (CGM) Computer Graphics Metafile Meta-arquivo do Computer Graphics Metafișier Computer Graphics метафайл компьютерной графики Computer Graphics Metafile Metadatoteka računalniške grafike (CGM) Metafile Computer Graphics Метадатотека са рачунарском графиком (CGM) Computer Graphics Metafil метафайл комп'ютерної графіки Siêu tập tin đồ họa máy tính (CMF) CGM 计算机图像元文件 CGM 影像 CCITT G3 fax فاكس CCITT G3 Faks CCITT G3 Факс — CCITT G3 fax CCITT G3 Fax CCITT G3 CCITT G3-fax CCITT-G3-Fax φαξ σε μορφή CCITT G3 CCITT G3 fax G3-fakso de CCITT fax de CCITT G3 CCITT G3 faxa CCITT G3 -faksi CCITT G3 telefaks télécopie G3 CCITT facs CCITT G3 fax de CCITT G3 פקס של CCITT G3 CCITT G3 faks CCITT G3-fax Faks CCITT G3 Fax CCITT G3 CCITT G3 FAX CCITT G3 ფაქსი CCITT G3 факсі CCITT G3 팩스 CCITT G3 faksas CCITT G3 fakss Faks g3 CCITT CCITT G3-faks CCITT G3-fax CCITT G3-fax Faks CCITT G3 fax CCITT G3 Fax do CCITT G3 Fax CCITT G3 факс CCITT G3 Fax CCITT G3 Datoteka faksimila CCITT G3 Fax CCITT G3 CCITT g3 факс CCITT G3-fax факс CCITT G3 Điện thư G3 CCITT CCITT G3 传真 CCITT G3 傳真檔 G3 fax image صورة فاكس G3 G3 faks rəsmi Faksavaja vyjava G3 Изображение — факс G3 imatge de fax G3 Obrázek fax G3 Delwedd Ffacs G3 G3-faxbillede G3-Faxbild εικόνα φαξ G3 G3 fax image G3-faksbildo imagen de fax G3 G3 fax-irudia G3-faksikuva G3 fax mynd image de télécopie G3 íomhá fhacs G3 imaxe de fax G3 תמונת פקס של G3 G3-faxkép Citra faks G3 Immagine fax G3 G3 FAX 画像 G3 fax გამოსახულება G3 факс суреті G3 팩스 그림 G3 fax paveikslėlis G3 faksa attēls Imej fax G3 G3-faksbilde G3 faxafbeelding G3 faksbilete Obraz faksowy G3 imagem de fax G3 Imagem de fax G3 Imagine fax G3 факсовое изображение G3 Obrázok fax G3 Slikovna datoteka G3 fax Figurë Fax G3 G3 факс слика G3-faxbild G3 fax görüntüsü факс G3 Ảnh điện thư G3 G3 传真文档 G3 傳真圖 GIF image صورة GIF GIF rəsmi Vyjava GIF Изображение — GIF imatge GIF Obrázek GIF Delwedd GIF GIF-billede GIF-Bild εικόνα GIF GIF image GIF-bildo imagen GIF GIF irudia GIF-kuva GIF mynd image GIF íomhá GIF imaxe GIF תמונת GIF GIF slika GIF-kép Citra GIF Immagine GIF GIF 画像 GIF გამოსახულება GIF суреті GIF 그림 GIF paveikslėlis GIF attēls Imej GIF GIF-bilde GIF-afbeelding GIF-bilete Obraz GIF imagem GIF Imagem GIF Imagine GIF изображение GIF Obrázok GIF Slikovna datoteka GIF Figurë GIF GIF слика GIF-bild GIF görüntüsü зображення GIF Ảnh GIF GIF 图像 GIF 影像 IEF image صورة IEF IEF rəsmi Vyjava IEF Изображение — IEF imatge IEF Obrázek IEF Delwedd IEF IEF-billede IEF-Bild εικόνα IEF IEF image IEF-bildo imagen IEF IEF irudia IEF-kuva IEF mynd image IEF íomhá IEF imaxe IEF תמונת IEF IEF slika IEF-kép Citra IEF Immagine IEF IEF 画像 IEF суреті IEF 그림 IEF paveikslėlis IEF attēls Imej IEF IEF-bilde IEF-afbeelding IEF-bilete Obraz IEF imagem IEF Imagem IEF Imagine IEF изображение IEF Obrázok IEF Slikovna datoteka IEF Figurë IEF IEF слика IEF-bild зображення IEF Ảnh IEF IEF 图像 IEF 影像 JPEG image صورة JPEG JPEG rəsmi Vyjava JPEG Изображение — JPEG imatge JPEG Obrázek JPEG Delwedd JPEG JPEG-billede JPEG-Bild εικόνα JPEG JPEG image JPEG-bildo imagen JPEG JPEG irudia JPEG-kuva JPEG mynd image JPEG íomhá JPEG imaxe JPEG תמונת JPEG JPEG slika JPEG-kép Citra JPEG Immagine JPEG JPEG 画像 JPEG суреті JPEG 그림 JPEG paveikslėlis JPEG attēls Imej JPEG JPEG-bilde JPEG-afbeelding JPEG-bilete Obraz JPEG imagem JPEG Imagem JPEG Imagine JPEG изображение JPEG Obrázok JPEG Slikovna datoteka JPEG Figurë JPEG JPEG слика JPEG-bild JPEG görüntüsü зображення JPEG Ảnh JPEG JPEG 图像 JPEG 影像 JPEG-2000 image صورة JPEG-2000 Vyjava JPEG-2000 Изображение — JPEG-2000 imatge JPEG-2000 Obrázek JPEG-2000 JPEG2000-billede JPEG-2000-Bild εικόνα JPEG-2000 JPEG-2000 image JPEG-2000-bildo imagen JPEG-2000 JPEG-2000 irudia JPEG-2000-kuva JPEG-2000 mynd image JPEG-2000 íomhá JPEG-2000 imaxe JPEG-2000 תמונת JPEG-2000 JPEG-2000 slika JPEG-2000 kép Citra JPEG-2000 Immagine JPEG-2000 JPEG-2000 画像 JPEG-2000 суреті JPEG-2000 그림 JPEG-2000 paveikslėlis JPEG-2000 attēls Imej JPEG-2000 JPEG-2000-bilde JPEG-2000-afbeelding JPEG-2000-bilete Obraz JPEG-2000 imagem JPEG-2000 Imagem JPEG-2000 Imagine JPEG-2000 изображение JPEG-2000 Obrázok JPEG-2000 Slikovna datoteka JPEG-2000 Figurë JPEG-2000 JPEG-2000 слика JPEG-2000-bild JPEG-2000 görüntüsü зображення JPEG-2000 Ảnh JPEG-2000 JPEG-2000 图像 JPEG-2000 影像 OpenRaster archiving image صورة أرشيف OpenRaster Изображение — OpenRaster imatge d'arxivat OpenRaster Archivační obraz OpenRaster OpenRaster-arkivaftryk OpenRaster-Archivierungsbild εικόνα αρχειοθέτησης OpenRaster OpenRaster archiving image imagen de archivado de OpenRaster OpenRaster artxiboaren irudia OpenRaster goymslumynd image d'archive OpenRaster íomhá chartlannaithe OpenRaster imaxe arquivada de OpenRaster תמונת ארכיון של OpenRaster OpenRaster archiválási kép Gambar pengarsipan OpenRaster Immagine archiviazione OpenRaster OpenRaster アーカイブイメージ OpenRaster-ის საარქივო გამოსახულება OpenRaster архивтеу суреті OpenRaster 압축 이미지 OpenRaster archyvavimo paveikslėlis OpenRaster arhivēšanas attēls OpenRaster archiverings-image Archiwalny obraz OpenRaster Imagem de arquivamento OpenRaster Arhivă imagine OpenRaster архивное изображение OpenRaster Odtis arhiva OpenRaster OpenRaster-arkivbild архівоване зображення OpenRaster OpenRaster 归档映像 OpenRaster 封存影像 WebP image صورة WebP WebP rəsmi Vyjava WebP Изображение — WebP imatge WebP Obrázek WebP Delwedd WebP WebP-billede WebP-Bild εικόνα WebP WebP image WebP-bildo imagen WebP WebP irudia WebP-kuva WebP mynd image WebP íomhá WebP imaxe WebP תמונת WebP WebP slika WebP-kép Citra WebP Immagine WebP WebP 画像 WebP суреті WebP 그림 WebP paveikslėlis WebP attēls Imej WebP WebP-bilde WebP-afbeelding WebP-bilete Obraz WebP imagem WebP Imagem WebP Imagine WebP изображение WebP Obrázok WebP Slikovna datoteka WebP Figurë WebP WebP слика WebP-bild WebP görüntüsü зображення WebP Ảnh WebP WebP 图像 WebP 影像 DirectDraw surface مساحة DirectDraw Pavierchnia DirectDraw Изображение — повърхност на DirectDraw superfície DirectDraw Plocha DirectDraw DirectDraw-overflade DirectDraw-Oberfläche επιφάνεια DirectDraw DirectDraw surface superficie DirectDraw DirectDraw gainazala DirectDraw-piirtoalue DirectDraw yvirflata surface DirectDraw dromchla DirectDraw superficie de DirectDraw משטח של DirectDraw DirectDraw ploha DirectDraw felület Permukaan DirectDraw Superficie DirectDraw DirectDraw サーフェイス DirectDraw-ის ზედაპირი DirectDraw жазықтығы DirectDraw 서피스 DirectDraw paviršius DirectDraw virsma DirectDraw-overflate DirectDraw-oppervlak DirectDraw-overflate Powierzchnia DirectDraw Superfície do DirectDraw Suprafață DirectDraw плоскость DirectDraw Plocha DirectDraw Datoteka predmeta DirectDraw Superfaqe DirectDraw DirectDraw-yta поверхня DirectDraw Mặt DirectDraw DirectDraw 表面 DirectDraw 表面 X11 cursor مؤشر X11 Kursor X11 Курсор — X11 cursor X11 Kurzor X11 X11-markør X11-Zeiger δείκτης X11 X11 cursor cursor X11 X11 kurtsorea X11-osoitin X11 vísi curseur X11 cúrsóir X11 Cursor X11 סמן של X11 X11 kursor X11 kurzor Kursor X11 Cursore X11 X11 カーソル X11 курсоры X11 커서 X11 žymiklis X11 kursors X11-markør X11-muisaanwijzer X11-peikar Kursor X11 Cursor do X11 Cursor X11 курсор X11 Kurzor X11 Datoteka kazalke X11 Kursor X11 X11-muspekare курсор X11 Con chạy X11 X11 指针 X11 滑鼠游標 EXR image صورة EXR Vyjava EXR Изображение — EXR imatge EXR Obrázek EXR EXR-billede EXR-Bild εικόνα EXR EXR image EXR-bildo imagen EXR EXR irudia EXR-kuva EXR mynd image EXR íomhá EXR imaxe EXR תמונת EXR EXR slika EXR kép Citra EXR Immagine EXR EXR 画像 EXR გამოსახულება EXR суреті EXR 그림 EXR paveikslėlis EXR attēls EXR-bilde EXR-afbeelding EXR-bilete Obraz EXR Imagem EXR Imagine EXR изображение EXR Obrázok EXR Slikovna datoteka EXR Figurë EXR EXR-bild EXR görüntüsü зображення EXR Ảnh EXR EXR 图像 EXR 影像 Macintosh Quickdraw/PICT drawing رسمة ماكنتوش Quickdraw/PICT Rysunak Macintosh Quickdraw/PICT Чертеж — Macintosh Quickdraw/PICT dibuix Quickdraw/PICT de Macintosh Kresba Macintosh Quickdraw/PICT Macintosh Quickdraw/PICT-tegning Macintosh-Quickdraw/PICT-Zeichnung σχέδιο Macintosh Quickdraw/PICT Macintosh Quickdraw/PICT drawing Quickdraw/PICT-grafikaĵo de Macintosh dibujo de Macintosh Quickdraw/PICT Macintosh Quickdraw/PICT marrazkia Macintosh Quickdraw/PICT -piirros Macintosh Quickdraw/PICT tekning dessin Macintosh Quickdraw/PICT líníocht Macintosh Quickdraw/PICT debuxo de Macintosh Quickdraw/PICT ציור של Macintosh Quickdraw/PICT Macintosh Quickdraw/PICT crtež Macintosh Quickdraw/PICT-rajz Gambar Macintosh Quickdraw/PICT Disegno Macintosh Quickdraw/PICT Macintosh Quickdraw/PICT ドロー Macintosh Quickdraw/PICT суреті 매킨토시 Quickdraw/PICT 그림 Macintosh Quickdraw/PICT piešinys Macintosh Quickdraw/PICT zīmējums Lukisan Macintosh Quickdraw/PICT Macintosh Quickdraw/PICT-tegning Macintosh Quickdraw/PICT-tekening Macintosh Quickdraw/PICT-teikning Rysunek Quickdraw/PICT Macintosh desenho Quickdraw/PICT de Macintosh Desenho do Macintosh Quickdraw/PICT Desen Macintosh Quickdraw/PICT рисунок Macintosh Quickdraw/PICT Kresba Macintosh QuickDraw/PICT Datoteka risbe Macintosh Quickdraw/PICT Vizatim Macintosh Quickdraw/PICT Мекинтош Quickdraw/PICT цртеж Macintosh Quickdraw/PICT-teckning малюнок Macintosh Quickdraw/PICT Bản vẽ Quickdraw/PICT của Macintosh Macintosh Quickdraw/PICT 绘图 Macintosh Quickdraw/PICT 繪圖 UFRaw ID image صورة UFRaw ID Vyjava UFRaw ID Изображение — UFRaw ID imatge ID UFRaw Obrázek ID UFRaw UFRaw ID-billede UFRaw-Bildbeschreibungsdatei ταυτότητα UFRaw UFRaw ID image imagen de identificación UFRaw UFRaw ID irudia UFRaw ID -kuva UFRaw ID mynd image ID UFRaw íomhá aitheantais UFRaw imaxe de identificación UFRaw תמונה של UFRaw ID UFRaw azonosítófájl Citra UFRaw ID Immagine UFRaw ID UFRaw ID イメージ UFRaw ID суреті UFRaw ID 그림 UFRaw ID paveikslėlis UFRaw ID attēls UFRaw ID-bilde UFRaw ID-afbeelding UFRaw ID-bilete Obraz UFRaw ID Imagem ID do UFRaw ID imagine UFRaw изображение UFRaw ID Obrázok ID UFRaw Slikovna datoteka UFRaw ID Figurë UFRaw ID UFRaw ID-bild зображення UFRaw ID Ảnh ID UFRaw UFRaw ID 图像 UFRaw ID 影像 UFRaw Unidentified Flying Raw digital raw image صورة رقمية خامة suvoraja ličbavaja vyjava Изображение — digital raw imatge digital en cru Digitální surový obrázek digitalt råbillede Digitales Rohbild ανεπεξέργαστη ψηφιακή εικόνα digital raw image imagen digital en bruto irudi gordin digitala digitaalinen raakakuva talgild rámynd image brute numérique amhíomhá digiteach imaxe en bruto dixital תמונה דיגטלית גולמית digitális nyers kép citra mentah digital Immagine raw digitale デジタル raw 画像 өңделмеген сандық суреттер 디지털 원본 이미지 skaitmeninis neapdorotas paveikslėlis digitāls jēlattēls digitalt raw-bilde onbewerkt digitaal beeld digitalt råbilete Surowy obraz cyfrowy Imagem digital bruta imagine digitală brută необработанные цифровые изображения Digitálny surový obrázok surova digitalna slika Figurë raw dixhitale digital råbild зображення цифрового негатива ảnh thô số 数字化原始图像 數位原生影像 Adobe DNG negative Adobe DNG negative Adobe DNG Negative Изображение — Adobe DNG negative negatiu DNG d'Adobe Adobe Digital Negative (DNG) Adobe DNG-negativ Adobe Digitales Negativ αρνητικό Adobe DNG Adobe DNG negative negativo digital de Adobe (ADN) Adobe DNG negatiboa Adobe-DNG-negatiivi Adobe DNG negativ négatif DNG Adobe claonchló DNG Adobe negativo DNG de Adobe תשליל Adobe DNG Adobe DNG negativ Adobe DNG negatív Negatif Adobe DNG Negativo Adobe DNG Adobe DNG ネガ Adobe DNG-ის ნეგატივი Adobe DNG негативі 어도비 DNG 네거티브 Adobe DNG negatyvas Adobe DNG negatīvs Adobe DNG-negativ Adobe DNG-negatief Adobe DNG-negativ Negatyw DNG Adobe Negativo DNG da Adobe Negativ Adobe DNG негатив Adobe DNG Adobe Digital Negative (DNG) Datoteka negativa Adobe DNG Negativ Adobe DNG Adobe DNG-negativ цифровий негатив DNG Adobe Âm bản Adobe DNG Adobe DNG 负片 Adobe DNG 負片 DNG Digital Negative Canon CRW raw image صورة Canon CRW خامة Suvoraja vyjava Canon CRW Изображение — Canon CRW raw imatge en cru de Canon CRW Surový obrázek Canon CRW Canon CRW-råbillede Canon-CRW-Rohbild ανεπεξέργαστη εικόνα Canon CRW Canon CRW raw image imagen en bruto CRW de Canon Canon CRW irudi gordina Canon-CRW-raakakuva Canon CRW rámynd image brute CRW Canon amhíomhá Canon CRW imaxe en bruto de Canon CRW תמונה גולמית של Canon CRW Canon CRW nyers kép Citra mentah Canon CRW Immagine raw Canon CRW Canon CRW raw 画像 Canon CRW raw გამოსახულება Canon CRW өңделмеген суреті 캐논 CRW 원본 이미지 Canon CRW neapdorotas paveikslėlis Canon CRW jēlattēls Canon CRW raw-bilde onbewerkt Canon CRW-beeld Canon CRW råbilete Surowy obraz CRW Canon Imagem bruta CRW da Canon Imagine brută Canon CRW необработанное изображение Canon CRW Surový obrázok Canon CRW Surova slikovna datoteka Canon CRW Figurë raw Canon CRW Canon CRW-råbild цифровий негатив CRW Canon Ảnh thô Canon CRW Canon CRW 原始图像 Canon CRW 原生影像 CRW Canon RaW Canon CR2 raw image صورة Canon CR2 خامة Suvoraja vyjava Canon CR2 Изображение — Canon CR2 raw imatge en cru de Canon CR2 Surový obrázek Canon CR2 Canon CR2-råbillede Canon-CR2-Rohbild ανεπεξέργαστη εικόνα Canon CR2 Canon CR2 raw image imagen en bruto CR2 de Canon Canon CR2 irudi gordina Canon-CR2-raakakuva Canon CR2 rámynd image brute CR2 Canon amhíomhá Canon CR2 imaxe en bruto de Canon CR2 תמונה גולמית של Canon CR2 Canon CR2 nyers kép Citra mentah Canon CR2 Immagine raw Canon CR2 Canon CR2 raw 画像 Canon CR2 raw გამოსახულება Canon CR2 өңделмеген суреті 캐논 CR2 원본 이미지 Canon CR2 neapdorotas paveikslėlis Canon CR2 jēlattēls Canon CR2 raw-bilde onbewerkt Canon CR2-beeld Canon CR2 råbilete Surowy obraz CR2 Canon Imagem bruta CR2 da Canon Imagine brută Canon CR2 необработанное изображение Canon CR2 Surový obrázok Canon CR2 Surova slikovna datoteka Canon CR2 Figurë raw Canon CR2 Canon CR2-råbild цифровий негатив CR2 Canon Ảnh thô Canon CR2 Canon CR2 原始图像 Canon CR2 原生影像 CR2 Canon Raw 2 Fuji RAF raw image صورة Fuji RAF خامة Suvoraja vyjava Fuji RAF Изображение — Fuji RAF raw imatge en cru de Fuji RAF Surový obrázek Fuji RAF Fuji RAF-råbillede Fuji-RAF-Rohbild ανεπεξέργαστη εικόνα Fuji RAF Fuji RAF raw image imagen en bruto RAF de Fuji Fuji RAF irudi gordina Fuji-RAF-raakakuva Fuji RAF raw mynd image brute RAF Fuji amhíomhá Fuji RAF imaxe en bruto de Fuji RAF תמונה גולמית של Fuji RAF Fuji RAF nyers kép Citra mentah Fuji RAF Immagine raw Fuji RAF Fuji RAF raw 画像 Fuji RAF-ის raw გამოსახულება Fuji RAF өңделмеген суреті 후지 RAF 원본 이미지 Fuji RAF neapdorotas paveikslėlis Fuji RAF jēlattēls Fuji RAF raw-bilde onbewerkt Fuji RAF-beeld Fuji RAF rått bilete Surowy obraz RAF Fuji Imagem bruta RAF da Fuji Imagine brută Fuji RAF необработанное изображение Fuji RAF Surový obrázok Fuji RAF Surova slikovna datoteka Fuji RAF Figurë raw Fuji RAF Fuji RAF-råbild Цифровий негатив RAF Fuji Ảnh thô Fuji RAF 富士RAF 原始图像 Fuji RAF 原生影像 RAF RAw Format Kodak DCR raw image صورة Kodak DCR خامة Suvoraja vyjava Kodak DCR Изображение — Kodak DCR raw imatge en cru de Kodak DCR Surový obrázek Kodak DCR Kodak DCR-råbillede Kodak-DCR-Rohbild ανεπεξέργαστη εικόνα Kodak DCR Kodak DCR raw image imagen en bruto DCR de Kodak Kodak DCR irudi gordina Kodak-DCR-raakakuva Kodak DCR rámynd image brute DCR Kodak amhíomhá Kodak DCR imaxe en bruto de Kodad DCR תמונה גולמית של Kodak DCR Kodak DCR nyers kép Citra mentah Kodak DCR Immagine raw Kodak DCR Kodak DCR raw 画像 Kodak DCR өңделмеген суреті 코닥 DCR 원본 이미지 Kodak DCR neapdorotas paveikslėlis Kodak DCR jēlattēls Kodak DCR raw-bilde onbewerkt Kodak DCR-beeld Kodak DCR råbilete Surowy obraz DCR Kodak Imagem bruta DCR da Kodak Imagine brută Kodak DCR необработанное изображение Kodak DCR Surový obrázok Kodak DCR Surova slikovna datoteka Kodak DCR Figurë raw Kodak DCR Kodak DCR-råbild цифровий негатив DCR Kodak Ảnh thô Kodak DCR Kodak DCR 原始图像 Kodak DCR 原生影像 DCR Digital Camera Raw Kodak K25 raw image صورة Kodak K25 خامة Suvoraja vyjava Kodak K25 Изображение — Kodak K25 raw imatge en cru de Kodak K25 Surový obrázek Kodak K25 Kodak K25-råbillede Kodak-K25-Rohbild ανεπεξέργαστη εικόνα Kodak K25 Kodak K25 raw image imagen en bruto K25 de Kodak Kodak K25 raw image Kodak-K25-raakakuva Kodak K25 rámynd image brute K25 Kodak amhíomhá Kodak K25 imaxe en bruto de Kodad K25 תמונה גולמית של Kodak K25 Kodak K25 nyers kép Citra mentah Kodak K25 Immagine raw Kodak K25 Kodak K25 raw 画像 Kodak K25 өңделмеген суреті 코닥 K25 원본 이미지 Kodak K25 neapdorotas paveikslėlis Kodak K25 jēlattēls Kodak K25 raw-bilde onbewerkt Kodak K25-beeld Kodak K25 råbilete Surowy obraz K25 Kodak Imagem bruta K25 da Kodak Imagine brută Kodak K25 необработанное изображение Kodak K25 Surový obrázok Kodak K25 Surova slikovna datoteka Kodak K25 Figurë raw Kodak K25 Kodak K25-råbild цифровий негатив K25 Kodak Ảnh thô Kodak K25 Kodak K25 原始图像 Kodak K25 原生影像 K25 Kodak DC25 Kodak KDC raw image صورة Kodak KDC خامة Suvoraja vyjava Kodak KDC Изображение — Kodak KDC raw imatge en cru de Kodak KDC Surový obrázek Kodak KDC Kodak KDC-råbillede Kodak-KDC-Rohbild ανεπεξέργαστη εικόνα Kodak KDC Kodak KDC raw image imagen en bruto KDC de Kodak Kodak KDC irudi gordina Kodak-KDC-raakakuva Kodak KDC rámynd image brute KDC Kodak amhíomhá Kodak KDC imaxe en bruto de Kodad KDC תמונה גולמית של Kodak KDC Kodak KDC nyers kép Citra mentah Kodak KDC Immagine raw Kodak KDC Kodak KDC raw 画像 Kodak KDC өңделмеген суреті 코닥 KDC 원본 이미지 Kodak KDC neapdorotas paveikslėlis Kodak KDC jēlattēls Kodak KDC raw-bilde onbewerkt Kodak KDC-beeld Kodak KDC råbilete Surowy obraz KDC Kodak Imagem bruta KDC da Kodak Imagine brută Kodak KDC необработанное изображение Kodak KDC Surový obrázok Kodak KDC Surova slikovna datoteka Kodak KDC Figurë raw Kodak KDC Kodak KDC-råbild цифровий негатив KDC Kodak Ảnh thô Kodak KDC Kodak KDC 原始图像 Kodak KDC 原生影像 KDC Kodak Digital Camera Minolta MRW raw image صورة Minolta MRW خامة Suvoraja vyjava Minolta MRW Изображение — Minolta MRW raw imatge en cru de Minolta MRW Surový obrázek Minolta MRW Minolta MRW-råbillede Minolta-MRW-Rohbild ανεπεξέργαστη εικόνα Minolta MRW Minolta MRW raw image imagen en bruto MRW de Minolta Minolta MRW irudi gordina Minolta-MRW-raakakuva Minolta MRW rámynd image brute MRW Minolta amhíomhá Minolta MRW imaxe RAW de Minolta MRW תמונה גולמית של Minolta MRW Minolta MRW nyers kép Citra mentah Minolta MRW Immagine raw Minolta MRW Minolta MRW raw 画像 Minolta MRW өңделмеген суреті 미놀타 MRW 원본 이미지 Minolta MRW neapdorotas paveikslėlis Minolta MRW jēlattēls Minolta MRW raw-bilde onbewerkt Minolta MRW-beeld Minolta MRW råbilete Surowy obraz MRW Minolta Imagem bruta MRW do Minolta Imagine brută Minolta MRW необработанное изображение Minolta MRW Surový obrázok Minolta MRW Surova slikovna datoteka Minolta MRW Figurë raw Minolta MRW Minolta MRW-råbild цифровий негатив MRW Minolta Ảnh thô Minolta MRW Minolta MRW 原始图像 Minolta MRW 原生影像 MRW Minolta RaW Nikon NEF raw image صورة Nikon NEF خامة Suvoraja vyjava Nikon NEF Изображение — Nikon NEF raw imatge en cru de Nikon NEF Surový obrázek Nikon NEF Nikon NEF-råbillede Nikon-NEF-Rohbild ανεπεξέργαστη εικόνα Nikon NEF Nikon NEF raw image imagen en bruto NEF de Nikon Nikon NEF irudi gordina Nikon-NEF-raakakuva Nikon NEF rámynd image brute NEF Nikon amhíomhá Nikon NEF imaxe RAW NEF Nikon תמונה גולמית של Nikon NEF Nikon NEF nyers kép Citra mentah Nikon NEF Immagine raw Nikon NEF Nikon NEF raw イメージ Nikon NEF өңделмеген суреті 니콘 NEF 원본 이미지 Nikon NEF neapdorotas paveikslėlis Nikon NEF jēlattēls Nikon NEF raw-bilde onbewerkt Nikon NEF-beeld Nikon NEF råbilete Surowy obraz NEF Nikon Imagem bruta NEF da Nikon Imagine brută Nikon NEF необработанное изображение Nikon NEF Surový obrázok Nikon NEF Surova slikovna datoteka Nikon NEF Figurë raw Nikon NEF Nikon NEF-råbild цифровий негатив NEF Nikon Ảnh thô Nikon NEF Nikon NEF 原始图像 Nikon NEF 原生影像 NEF Nikon Electronic Format Olympus ORF raw image صورة Olympus ORF خامة Suvoraja vyjava Olympus ORF Изображение — Olympus ORF raw imatge en cru d'Olympus ORF Surový obrázek Olympus ORF Olympus ORF-råbillede Olympus-ORF-Rohbild ανεπεξέργαστη εικόνα Olympus ORF Olympus ORF raw image imagen en bruto ORF de Olympus Olympus ORF irudi gordina Olympus-ORF-raakakuva Olympus ORF rámynd image brute ORF Olympus amhíomhá Olympus ORF imaxe en bruto de Olympus ORF תמונה גולמית של Olympus ORF Olympus ORF nyers kép Citra mentah Olympus ORF Immagine raw Olympus ORF Olympus ORF raw 画像 Olympus ORF-ის raw გამოსახულება Olympus ORF өңделмеген суреті 올림푸스 ORF 원본 이미지 Olympus ORF neapdorotas paveikslėlis Olympus ORF jēlattēls Olympus ORF raw-bilde onbewerkt Olympus ORF-beeld Olympus ORF råbilete Surowy obraz Olympus ORF Imagem bruta ORF da Olympus Imagine brută Olympus ORF необработанное изображение Olympus ORF Surový obrázok Olympus ORF Surova slikovna datoteka Olympus ORF Figurë raw Olympus ORF Olympus ORF-råbild цифровий негатив ORF Olympus Ảnh thô Olympus ORF Olympus ORF 原始图像 Olympus ORF 原生影像 ORF Olympus Raw Format Panasonic raw image صورة Panasonic خامة Suvoraja vyjava Panasonic Изображение — Panasonic raw imatge en cru de Panasonic Surový obrázek Panasonic Panasonicråbillede (raw) Panasonic-Rohbild Panasonic raw image imagen en bruto de Panasonic Panasonic irudi gordina Panasonic-raakakuva Panasonic rámynd image brute Panasonic amhíomhá Panasonic imaxe en bruto de Panasonic תמונה גולמית של Panasonic Panasonic nyers kép Citra mentah Panasonic Immagine raw Panasonic Panasonic raw 画像 Panasonic өңделмеген суреті 파나소닉 원본 이미지 Panasonic neapdorotas paveikslėlis Panasonic jēlattēls Panasonic raw-bilde onbewerkt Panasonic-beeld Panasonic råbilete Obraz raw Panasonic Imagem bruta da Panasonic Imagine brută Panasonic необработанное изображение Panasonic Surový obrázok Panasonic Surova slikovna datoteka Panasonic Figurë raw Panasonic Panasonic-råbild цифровий негатив Panasonic Ảnh thô Panasonic Panasonic 原始图像 Panasonic 原生影像 Panasonic raw2 image Изображение — Panasonic raw2 imatge «RAW2» de Panasonic Surový obrázek Panasonic raw2 Panasonic-rå2-billede (raw) Panasonic raw2-Bild Panasonic raw2 image imagen en bruto raw2 de Panasonic Panasonic raw2 -kuva image raw2 Panasonic imaxe en bruto raw2 de Panasonic תמונת raw2 של Panasonic Panasonic raw2 kép Image Panasonic raw2 Immagine raw2 Panasonic Panasonic raw2 画像 Panasonic raw2 суреті 파나소닉 원본 이미지 2 Panasonic raw2 jēlattēls Panasonic raw2 image Obraz raw2 Panasonic Imagem raw2 da Panasonic необработанное изображение Panasonic RAW 2 Surový obrázok Panasonic raw2 Slikovna datoteka Panasonic raw2 Panasonic raw2-bild Panasonic raw2 görüntüsü зображення формату raw2 Panasonic Panasonic raw2 图像 Panasonic raw2 影像 Pentax PEF raw image صورة Pentax PEF خامة Suvoraja vyjava Pentax PEF Изображение — Pentax PEF raw imatge en cru de Pentax PEF Surový obrázek Pentax PEF Pentax PEF-råbillede Pentax-PEF-Rohbild ανεπεξέργαστη εικόνα Pentax PEF Pentax PEF raw image imagen en bruto PEF de Pentax Pentax PEF irudi gordina Pentax-PEF-raakakuva Pentax PEF rámynd image brute PEF Pentax amhíomhá Pentax PEF imaxe en bruto PEF de Pentax תמונה גולמית של Pentax PEF Pentax PEF nyers kép Citra mentah Pentax PEF Immagine raw Pentax PEF Pentax PEF raw 画像 Pentax PEF өңделмеген суреті 펜탁스 PEF 원본 이미지 Pentax PEF neapdorotas paveikslėlis Pentax PEF jēlattēls Pentax PEF raw-bilde onbewerkt Pentax PEF-beeld Pentax PEF råbilete Surowy obraz Pentax PEF Imagem bruta PEF da Pentax Imagine brută Pentax PEF необработанное изображение Pentax PEF Surový obrázok Pentax PEF Surova slikovna datoteka Pentax PEF Figurë raw Pentax PEF Pentax PEF-råbild цифровий негатив PEF Pentax Ảnh thô Pentax PEF Pentax PEF 原始图像 Pentax PEF 原生影像 PEF Pentax Electronic Format Sigma X3F raw image صورة Sigma X3F خامة Suvoraja vyjava Sigma X3F Изображение — Sigma X3F raw imatge en cru de Sigma X3F Surový obrázek Sigma X3F Sigma X3F-råbillede Sigma-X3F-Rohbild ανεπεξέργαστη εικόνα Sigma X3F Sigma X3F raw image imagen en bruto X3F de Sigma Sigma X3F irudi gordina Sigma-X3F-raakakuva Sigma X3F rámynd image brute X3F Sigma amhíomhá Sigma X3F imaxe en bruto X3F de Sigma תמונה גולמית של Sigma X3F Sigma XF3 nyers kép Citra mentah Sigma X3F Immagine raw Sigma X3F Sigma X3F raw 画像 Sigma X3F өңделмеген суреті 시그마 X3F 원본 이미지 Sigma X3F neapdorotas paveikslėlis Sigma X3F jēlattēls Sigma X3F raw-bilde onbewerkt Sigma X3F-beeld Sigma X3F råbilete Surowy obraz X3F Sigma Imagem bruta X3F da Sigma Imagine brută Sigma X3F необработанное изображение Sigma X3F Surový obrázok Sigma X3F Surova slikovna datoteka Sigma X3F Fifurë raw Sigma X3F Sigma X3F-råbild цифровий негатив X3F Sigma Ảnh thô Sigma X3F Sigma X3F 原始图像 Sigma X3F 原生影像 X3F X3 Foveon Sony SRF raw image صورة Sony SRF خامة Suvoraja vyjava Sony SRF Изображение — Sony SRF raw imatge en cru de Sony SRF Surový obrázek Sony SRF Sony SRF-råbillede Sony-SRF-Rohbild ανεπεξέργαστη εικόνα Sony SRF Sony SRF raw image imagen en bruto SRF de Sony Sony SRF irudi gordina Sony-SRF-raakakuva Sony SRF rámynd image brute SRF Sony amhíomhá Sony SRF imaxe en bruto SRF de sony תמונה גולמית של Sony SRF Sony SRF nyers kép Citra mentah Sony SRF Immagine raw Sony SRF Sony SRF raw 画像 Sony SRF өңделмеген суреті 소니 SRF 원본 이미지 Sony SRF neapdorotas paveikslėlis Sony SRF jēlattēls Sony SRF raw-bilde onbewerkt Sony SRF-beeld Sony SRF råbilete Surowy obraz SRF Sony Imagem bruta SRF da Sony Imagine brută Sony SRF необработанное изображение Sony SRF Surový obrázok Sony SRF Surova slikovna datoteka Sony SRF Figurë raw Sony SRF Sony SRF-råbild цифровий негатив SRF Sony Ảnh thô Sony SRF Sony SRF 原始映像 Sony SRF 原生影像 SRF Sony Raw Format Sony SR2 raw image صورة Sony SR2 خامة Suvoraja vyjava Sony SR2 Изображение — Sony SR2 raw imatge en cru de Sony SR2 Surový obrázek Sony SR2 Sony SR2-råbillede Sony-SR2-Rohbild ανεπεξέργαστη εικόνα Sony SR2 Sony SR2 raw image imagen en bruto SR2 de Sony Sony SR2 irudi gordina Sony-SR2-raakakuva Sony SR2 rámynd image brute SR2 Sony amhíomhá Sony SR2 imaxe en bruto SR2 de sony תמונה גולמית של Sony SR2 Sony SR2 nyers kép Citra mentah Sony SR2 Immagine raw Sony SR2 Sony SR2 raw 画像 Sony SR2 өңделмеген суреті 소니 SR2 원본 이미지 Sony SR2 neapdorotas paveikslėlis Sony SR2 jēlattēls Sony SR2 raw-bilde onbewerkt Sony SR2-beeld Sony SR2 råbilete Surowy obraz SR2 Sony Imagem bruta SR2 da Sony Imagine brută Sony SR2 необработанное изображение Sony SR2 Surový obrázok Sony SR2 Surova slikovna datoteka Sony SR2 Figurë raw Sony SR2 Sony SR2-råbild цифровий негатив SR2 Sony Ảnh thô Sony SR2 Sony SR2 原始映像 Sony SR2 原生影像 SR2 Sony Raw format 2 Sony ARW raw image صورة Sony ARW خامة Suvoraja vyjava Sony ARW Изображение — Sony ARW raw imatge en cru de Sony ARW Surový obrázek Sony ARW Sony ARW-råbillede Sony-ARW-Rohbild ανεπεξέργαστη εικόνα Sony ARW Sony ARW raw image imagen en bruto ARW de Sony Sony ARW irudi gordina Sony-ARW-raakakuva Sony ARW rámynd image brute ARW Sony amhíomhá Sony ARW imaxe en bruto ARW de sony תמונה גולמית של Sony ARW Sony ARW nyers kép Citra mentah Sony ARW Immagine raw Sony ARW Sony ARW raw 画像 Sony ARW өңделмеген суреті 소니 ARW 원본 이미지 Sony ARW neapdorotas paveikslėlis Sony ARW jēlattēls Sony ARW raw-bilde onbewerkt Sony ARW-beeld Sony ARW råbilete Surowy obraz ARW Sony Imagem bruta ARW da Sony Imagine brută Sony ARW необработанное изображение Sony ARW Surový obrázok Sony ARW Surova slikovna datoteka Sony ARW Figurë raw Sony ARW Sony ARW-råbild цифровий негатив ARW Sony Ảnh thô Sony ARW Sony ARW 原始映像 Sony ARW 原生影像 ARW Alpha Raw format PNG image صورة PNG PNG rəsmi Vyjava PNG Изображение — PNG imatge PNG Obrázek PNG Delwedd PNG PNG-billede PNG-Bild εικόνα PNG PNG image PNG-bildo imagen PNG PNG irudia PNG-kuva PNG mynd image PNG íomhá PNG imaxe PNG תמונת PNG PNG-kép Citra PNG Immagine PNG PNG 画像 PNG суреті PNG 그림 PNG paveikslėlis PNG attēls Imej PNG PNG-bilde PNG-afbeelding PNG-bilete Obraz PNG imagem PNG Imagem PNG Imagine PNG изображение PNG Obrázok PNG Slikovna datoteka PNG Figurë PNG PNG слика PNG-bild зображення PNG Ảnh PNG PNG 图像 PNG 影像 Apple optimised PNG image Run Length Encoded bitmap image تشغيل صورة نقطية طولية الترميز Bitmapnaja vyjava, zakadavanaja ŭ Run Length Изображение — RLE Bitmap imatge de mapa de bits «Run Lenght Encoded» Obrázek bitové mapy Run Length Encoded Run Length Encoded-bitmapbillede Lauflängenkodiertes Bitmap-Bild Run Length Encoded bitmap image mapa de bits con codificación del tamaño durante la ejecución 'Run Lenght Encoded' bitmap irudia RLE-koodattu bittikartta image matricielle Run Length Encoded íomhá mhapa giotáin Run Length Encoded mapa de bits con codificación do tamaño durante a execución מקודד מפת סיביות של Run Length Run Length Encoded bitkép Citra peta bit Run Length Encoded Immagine bitmap RLE (Run Length Encoded) ランレングス符号化ビットマップ画像 RLE сығылған растрлік суреті RLE 인코딩된 비트맵 그림 Run Length Encoded rastrinis paveikslėlis Secīgo atkārtojumu kodēts bitkartes attēls Run Length Encoded bitmap bilde RLE-gecodeerde bitmap-afbeelding Run Length Encoded punktgrafikk Obraz bitmapy RLE Classe de comprimento imagem bitmap codificada Imagine bitmap codată RLE растровое изображение (сжатое RLE) Bitmapový obrázok Run Length Encoded Zaporedno kodirana bitna slika (RLE) Figurë bitmap RLE (Run Length Encoded) Körlängdskodad bitmappbild растрове зображення RLE Ảnh mảng mã hóa chiều dài chạy (RLE) 游程编码位图 Run Length Encoded 點陣影像 SVG image صورة SVG Vyjava SVG Изображение — SVG imatge SVG Obrázek SVG SVG-billede SVG-Bild εικόνα SVG SVG image SVG-bildo imagen SVG SVG irudia SVG-kuva SVG mynd image SVG íomhá SVG imaxe SVG תמונת SVG SVG slika SVG kép Citra SVG Immagine SVG SVG 画像 SVG суреті SVG 그림 SVG paveikslėlis SVG attēls SVG-bilde SVG-afbeelding SVG-bilete Obraz SVG Imagem SVG Imagine SVG изображение SVG Obrázok SVG Slikovna vektorska datoteka SVG Figurë SVG SVG-bild SVG görüntüsü зображення SVG Ảnh SVG SVG 图像 SVG 影像 SVG Scalable Vector Graphics compressed SVG image صورة SVG مضغوطة skampresavanaja vyjava SVG Изображение — SVG, компресирано imatge SVG comprimida Komprimovaný obrázek SVG SVG-komprimeret billede Komprimiertes SVG-Bild συμπιεσμένη εικόνα SVG compressed SVG image imagen SVG comprimida konprimitutako SVG irudia pakattu SVG-kuva stappað SVG mynd image SVG compressée íomhá SVG comhbhrúite imaxe SVG comprimida תמונת SVG מכוצת komprimirana SVG slika tömörített SVG kép Citra SVG terkompresi Immagine SVG compressa 圧縮 SVG 画像 сығылған SVG суреті 압축된 SVG 그림 suglaudintas SVG paveikslėlis saspiests SVG attēls komprimert SVG-bilde ingepakte SVG-afbeelding komprimert SVG-bilete Skompresowany obraz SVG Imagem compactada SVG imagine comprimată SVG сжатое изображение SVG Komprimovaný obrázok SVG Slikovna datoteka SVG (stisnjena) Figurë SVG e kompresuar komprimerad SVG-bild стиснене зображення SVG ảnh SVG đã nén 压缩的 SVG 图像 壓縮版 SVG 影像 SVG Scalable Vector Graphics TIFF image صورة TIFF Vyjava TIFF Изображение — TIFF imatge TIFF Obrázek TIFF TIFF-billede TIFF-Bild εικόνα TIFF TIFF image TIFF-bildo imagen TIFF TIFF irudia TIFF-kuva TIFF mynd image TIFF íomhá TIFF imaxe TIFF תמונת TIFF TIFF slika TIFF-kép Citra TIFF Immagine TIFF TIFF 画像 TIFF суреті TIFF 그림 TIFF paveikslėlis TIFF attēls Imej TIFF TIFF-bilde TIFF-afbeelding TIFF-bilete Obraz TIFF imagem TIFF Imagem TIFF Imagine TIFF изображение TIFF Obrázok TIFF Slikovna datoteka TIFF Figurë TIFF TIFF слика TIFF-bild зображення TIFF Ảnh TIFF TIFF 图像 TIFF 影像 TIFF Tagged Image File Format AutoCAD image صورة AutoCAD AutoCAD rəsmi Vyjava AutoCAD Изображение — AutoCAD imatge d'AutoCAD Obrázek AutoCAD Delwedd AutoCAD AutoCAD-billede AutoCAD-Bild εικόνα AutoCAD AutoCAD image AutoCAD-bildo imagen de AutoCAD AutoCAD-eko irudia AutoCAD-kuva AutoCAD mynd image AutoCAD íomhá AutoCAD imaxe de AutoCAD תמונה של AutoCAD AutoCAD slika AutoCAD-kép Citra AutoCAD Immagine AutoCAD AutoCAD 画像 AutoCAD-ის გამოსახულება AutoCAD суреті AutoCAD 그림 AutoCAD paveikslėlis AutoCAD attēls Imej AutoCAD AutoCAD-bilde AutoCAD-afbeelding AutoCAD-bilete Obraz AutoCAD imagem AutoCAD Imagem do AutoCAD Imagine AutoCAD изображение AutoCAD Obrázok AutoCAD Slikovna datoteka AutoCAD Figurë AutoCAD AutoCAD слика AutoCAD-bild AutoCAD görüntüsü зображення AutoCAD Ảnh AutoCAD AutoCAD 图像 AutoCAD 影像 DXF vector image صورة DXF نقطية Vektarnaja vyjava DXF Изображение — DXF imatge vectorial DXF Vektorový obrázek DXF DXF-vektorbillede DXF-Vektorbild ανυσματική εικόνα DXF DXF vector image vektora DXF-bildo imagen vectorial DXF DXF bektore-grafikoa DXF-vektorikuva DXF vektormynd image vectorielle DXF íomhá veicteoir DXF imaxe de vector DXF תמונת DXF וקטורית DXF vektorska slika DXF-vektorkép Citra vektor DXF Immagine vettoriale DXF DXF ベクター画像 DXF ვექტორული გამოსახულება DXF векторлық суреті DXF 벡터 그림 DXF vektorinis paveikslėlis DXF vektora attēls Imej vektor DXF DXF-vektorgrafikk DXF-vectorafbeelding DXF-vektorgrafikk Obraz wektorowy DXF imagem de vectores DXF Imagem vetorial DXF Imagine vectorială DXF векторное изображение DXF Vektorový obrázok DXF Slikovna vektorska datoteka DXF Figurë vektoriale DFX DXF векторска графика DXF-vektorbild векторне зображення DXF Ảnh véc-tơ DXF DXF 矢量图像 DXF 向量圖 Microsoft Document Imaging format صيغة مستند تصوير مايكروسوفت Изображение — Microsoft Document Imaging format Microsoft Document Imaging Formát Microsoft Document Imaging Microsofts dokumentbilledformat Microsoft-Document-Imaging-Bildformat μορφή Microsoft Document Imaging Microsoft Document Imaging format formato de imagen de Microsoft Document Microsoft Document Imaging formatua Microsoft Document Imaging -muoto Microsoft Document Imaging snið format Document Imaging Microsoft formáid Microsoft Document Imaging formato de Microsoft Document Imaging פורמט של Microsoft Document Imaging Microsoft Document Imaging formátum Format Microsoft Document Imaging Formato MDI (Microsoft Document Imaging) Microsoft ドキュメントイメージフォーマット Microsoft Document Imaging пішімі 마이크로소프트 문서 이미지 형식 Microsoft Document Imaging formatas Microsoft dokumentu attēlošanas formāts Microsoft Document Imaging Format Microsoft Document Imaging Formato do Microsoft Document Imaging Format Microsoft Document Imaging формат Microsoft Document Imaging Formát Microsoft Document Imaging Zapis Microsoft Document Imaging Microsoft Document Imaging-format формат Microsoft Document Imaging Định dạng tạo ảnh tài liệu Microsoft Microsoft Document Imaging 扫描图像 微軟文件影像格式 MDI Microsoft Document Imaging 3D Studio image صورة استديو ثلاثية الأبعاد 3D Studio rəsmi Vyjava 3D Studio Изображение — 3D Studio imatge de 3D Studio Obrázek 3D Studio Delwedd "3D Studio" 3D Studio-billede 3D-Studio-Bild εικόνα 3D Studio 3D Studio image bildo de 3D Studio imagen de 3D Studio 3D Studio-ko irudia 3D Studio -kuva 3D Studio mynd image 3D Studio íomhá 3D Studio imaxe de 3D Studio תמונת 3D Studio 3D Studio slika 3D Studio-kép Citra 3D Studio Immagine 3D Studio 3D Studio 画像 3D Studio-ის გამოსახულება 3D Studio суреті 3D Studio 그림 3D Studio paveikslėlis 3D Studio attēls Imej 3D Studio 3D Studio-bilde 3D-Studio-afbeelding 3D Studio-bilete Obraz 3D Studio imagem 3D Studio Imagem do 3D Studio Imagine 3D Studio сцена 3D Studio Obrázok 3D Studio Slikovna datoteka 3D Studio Figurë 3D Studio 3D Studio слика 3D Studio-bild 3D Studio görüntüsü зображення 3D Studio Ảnh xuởng vẽ 3D 3D Studio 图像 3D Studio 影像 Applix Graphics image صورة رسوميات Applix Vyjava Applix Graphics Изображение — Applix Graphics imatge d'Applix Graphics Obrázek Applix Graphics Applix Graphics-billede Applix-Graphics-Bild εικόνα Applix Graphics Applix Graphics image bildo de Applix Graphics imagen de Applix Graphics Applix Graphics irudia Applix Graphics -kuva Applix Graphics mynd image Applix Graphics íomhá Applix Graphics imaxe de Applix Graphics תמונה של Applix Graphics Applix Graphics slika Applix Graphics-kép Citra Applix Graphics Immagine Applix Graphics Applix Graphics 画像 Applix Graphics-ის გამოსახულება Applix Graphics суреті Applix Graphics 그림 Applix Graphics paveikslėlis Applix Graphics attēls Imej Applix Graphics Applix Graphics-dokument Applix Graphics-afbeelding Applix Graphics-dokument Obraz Applix Graphics imagem Applix Graphics Imagem do Applix Graphics Imagine Applix Graphics изображение Applix Graphics Obrázok Applix Graphics Slikovna datoteka Applix Graphics Figurë Applix Graphics Applix графички документ Applix Graphics-bild зображення Applix Graphics Ảnh Applix Graphics Applix Graphics 图像 Applix Graphics 影像 EPS image (bzip-compressed) صورة EPS (مضغوط-bzip) Vyjava EPS (bzip-skampresavanaja) Изображение — EPS, компресирано с bzip imatge EPS (comprimida amb bzip) Obrázek EPS (komprimovaný pomocí bzip) EPS-billede (bzip-komprimeret) EPS-Bild (bzip-komprimiert) EPS image (bzip-compressed) imagen EPS (comprimida con bzip) EPS irudia (bzip-ekin konprimitua) EPS-kuva (bzip-pakattu) EPS mynd (bzip-stappað) image EPS (compressée bzip) íomhá EPS (comhbhrúite le bzip) imaxe EPS (comprimida con bzip) תמונת EPS (מכווץ בbzip) EPS slika (komprimirana bzip-om) EPS kép (bzip-tömörítésű) Citra EPS (terkompresi bzip) Immagine EPS (compressa con bzip) EPS 画像 (bzip 圧縮) EPS გამოსახულება (bzip-ით შეკუმშული) EPS суреті (bzip-пен сығылған) EPS 그림 (BZIP 압축) EPS paveikslėlis (suglaudintas su bzip) EPS attēls (saspiests ar bzip) EPS-bilde (bzip-komprimert) EPS-afbeelding (ingepakt met bzip) EPS-bilete (pakka med bzip) Obraz EPS (kompresja bzip) Imagem EPS (compactada com bzip) Imagine EPS (compresie bzip) изображение EPS (сжатое bzip) Obrázok EPS (komprimovaný pomocou bzip) Slikovna datoteka EPS (stisnjena z bzip) Figurë EPS (e kompresuar me bzip) EPS-bild (bzip-komprimerad) EPS görüntüsü (bzip ile sıkıştırılmış) зображення EPS (стиснене bzip) Ảnh EPS (đã nén bzip) EPS 图像(bzip 压缩) EPS 影像 (bzip 格式壓縮) CMU raster image صورة CMU نقطية CMU raster rəsmi Rastravaja vyjava CMU Изображение — CMU raster imatge ràster CMU Rastrový obrázek CMU Delwedd raster CMU CMU-rasterbillede CMU-Rasterbild εικόνα ράστερ CMU CMU raster image rastruma bildo de CMU imagen ráster CMU CMU bilbe-irudia CMU-rasterikuva CMU raster mynd image raster CMU íomhá rastar CMU imaxe raster CMU תמונת סריקה CMU CMU-raszterkép Citra raster CMU Immagine raster CMU CMU ラスター画像 CMU-ის რასტრული გამოსახულება CMU растрлық суреті CMU 래스터 그림 CMU rastrinis paveikslėlis CMU rastra attēls Imej raster CMU CMU-rasterbilde CMU-rasterafbeelding CMU rasterbilete Obraz rastrowy CMU imagem rasterizada CMU Imagem raster CMU Imagine raster CMU растровое изображение CMU Rastrový obrázok CMU Slikovna rastrska datoteka CMU Figurë raster CMU CMU растерска слика CMU-rasterbild растрове зображення CMU Ảnh mành CMU CMU 矢量图像 CMU raster 影像 compressed GIMP image صورة GIMP مضغوطة skampresavanaja vyjava GIMP Изображение — GIMP, компресирано imatge GIMP comprimida Komprimovaný obrázek GIMP komprimeret GIMP-billede Komprimiertes GIMP-Bild συμπιεσμένη εικόνα GIMP compressed GIMP image imagen GIMP comprimida konprimitutako GIMP irudia pakattu GIMP-kuva stappað GIMP mynd image GIMP compressée íomhá GIMP comhbhrúite imaxe de GIMP comprimida תמונת GIMP מכווצת tömörített GIMP kép Citra GIMP terkompresi Immagine GIMP compressa 圧縮 GIMP 画像 сығылған GIMP суреті 압축된 GIMP 그림 suglaudintas GIMP paveikslėlis saspiests GIMP attēls komprimert GIMP-bilde ingepakte GIMP-afbeelding komprimert GIMP-bilete Skompresowany obraz GIMP Imagem compactada do GIMP imagine comprimată GIMP сжатое изображение GIMP Komprimovaný obrázok GIMP Slikovna datoteka GIMP (stisnjena) Figurë GIMP e kompresuar komprimerad GIMP-bild стиснене зображення GIMP ảnh GIMP đã nén 压缩的 GIMP 图像 壓縮版 GIMP 影像 DICOM image صورة DICOM Vyjava DICOM Изображение — DICOM imatge DICOM Obrázek DICOM DICOM-billede DICOM-Bild εικόνα DICOM DICOM image DICOM-bildo imagen DICOM DICOM irudia DICOM-kuva DICOM mynd image DICOM íomhá DICOM imaxe DICOM תמונת DICOM DICOM slika DICOM kép Citra DICOM Immagine DICOM DICOM 画像 DICOM გამოსახულება DICOM суреті DICOM 그림 DICOM paveikslėlis DICOM attēls DICOM-bilde DICOM-afbeelding DICOM-bilete Obraz DICOM Imagem DICOM Imagine DICOM изображение DICOM Obrázok DICOM Slikovna datoteka DICOM Figurë DICOM DICOM-bild DICOM görüntüsü зображення DICOM Ảnh DICOM DICOM 图像 DICOM 影像 DICOM Digital Imaging and Communications in Medicine DocBook document مستند DocBook Dakument DocBook Документ — DocBook document DocBook Dokument DocBook DocBook-dokument DocBook-Dokument έγγραφο DocBook DocBook document DocBook-dokumento documento DocBook DocBook dokumentua DocBook-asiakirja DocBook skjal document DocBook cáipéis DocBook documento de DocBook מסמך DocBook DocBook dokument DocBook dokumentum Dokumen DocBook Documento DocBook DocBook ドキュメント DocBook-ის დოკუმენტი DocBook құжаты DocBook 문서 DocBook dokumentas DocBook dokuments DocBook-dokument DocBook-document DocBook-dokument Dokument DocBook Documento DocBook Document DocBook документ DocBook Dokument DocBook Dokument DocBook Dokument DocBook DocBook-dokument DocBook belgesi документ DocBook Tài liệu DocBook DocBook 文档 DocBook 文件 DIB image صورة DIB Vyjava DIB Изображение — DIB imatge DIB Obrázek DIB DIB-billede DIB-Bild εικόνα DIB DIB image DIB-bildo imagen DIB DIB irudia DIB-kuva DIB mynd image DIB íomhá DIB imaxe DIB תמונת DIB DIB slika DIB kép Citra DIB Immagine DIB DIB 画像 DIB გამოსახულება DIB суреті DIB 그림 DIB paveikslėlis DIB attēls DIB-bilde DIB-afbeelding DIB-bilete Obraz DIB Imagem DIB Imagine DIB изображение DIB Obrázok DIB Slikovna datoteka DIB Figurë DIB DIB-bild DIB görüntüsü зображення DIB Ảnh DIB DIB 图像 DIB 影像 DIB Device Independent Bitmap DjVu image صورة DjVu Vyjava DjVu Изображение — DjVu imatge DjVu Obrázek DjVu DjVu-billede DjVu-Bild εικόνα DjVu DjVu image DjVu-bildo imagen DjVu DjVU-ko irudia DjVu-kuva DjVu mynd image DjVu íomhá DjVu imaxe de DjVu תמונת DjVu DjVu slika DjVu-kép Citra DjVu Immagine DjVu DjVu 画像 DjVu გამოსახულება DjVu суреті DjVu 그림 DjVu paveikslėlis DjVu attēls Imej DjVu DjVu-bilde DjVu-afbeelding DjVu-bilete Obraz DjVu imagem DjVu Imagem DjVu Imagine DjVu изображение DjVu Obrázok DjVu Slikovna datoteka DjVu Figurë DjVu DjVu слика DjVu-bild DjVu görüntüsü зображення DjVu Ảnh DjVu DjVu 图像 DjVu 影像 DPX image صورة DPX Vyjava DPX Изображение — DPX imatge DPX Obrázek DPX DPX-billede DPX-Bild εικόνα DPX DPX image DPX-bildo imagen DPX DPX irudia DPX-kuva DPX mynd image DPX íomhá DPX imaxe DPX תמונת DPX DPX slika DPX kép Citra DPX Immagine DPX DPX 画像 DPX გამოსახულება DPX суреті DPX 그림 DPX paveikslėlis DPX attēls DPX-bilde DPX-afbeelding DPX-bilete Obraz DPX Imagem DPX Imagine DPX изображение DPX Obrázok DPX Slikovna datoteka DPX Figurë DPX DPX-bild DPX görüntüsü зображення DPX Ảnh DPX DPX 图像 DPX 影像 DPX Digital Moving Picture Exchange EPS image صورة EPS Vyjava EPS Изображение — EPS imatge EPS Obrázek EPS EPS-billede EPS-Bild εικόνα EPS EPS image EPS-bildo imagen EPS EPS irudia EPS-kuva EPS mynd image EPS íomhá EPS imaxe EPS תמונת EPS EPS slika EPS kép Citra EPS Immagine EPS EPS 画像 EPS გამოსახულება EPS суреті EPS 그림 EPS paveikslėlis EPS attēls EPS-bilde EPS-afbeelding EPS-bilete Obraz EPS Imagem EPS Imagine EPS изображение EPS Obrázok EPS Slikovna datoteka EPS Figurë EPS EPS-bild EPS görüntüsü зображення EPS Ảnh EPS EPS 图像 EPS 影像 EPS Encapsulated PostScript FITS document مستند FITS Dakument FITS Документ — FITS document FITS Dokument FITS FITS-dokument FITS-Dokument έγγραφο FITS FITS document FITS-dokumento documento FITS FITS dokumentua FITS-asiakirja FITS skjal document FITS cáipéis FITS documento FICT מסמך FITS FITS dokument FITS dokumentum Dokumen FITS Documento FITS FITS ドキュメント FITS დოკუმენტი FITS құжаты FITS 문서 FITS dokumentas FITS dokuments FITS-dokument FITS-document FITS-dokument Dokument FITS Documento FITS Document FITS документ FITS Dokument FITS Dokument FITS Dokument FITS FITS-dokument документ FITS Tài liệu FITS FITS 文档 FITS 文件 FITS Flexible Image Transport System FPX image صورة FPX Vyjava FPX Изображение — FPX imatge FPX Obrázek FPX FPX-billede FPX-Bild εικόνα FPX FPX image FPX-bildo imagen FPX FPX irudia FPX-kuva FPX mynd image FPX íomhá FPX imaxe FPX תמונת FPX FPX slika FPX kép Citra FPX Immagine FPX FPX 画像 FPX გამოსახულება FPX суреті FPX 그림 FPX paveikslėlis FPX attēls FPX-bilde FPX-afbeelding FPX-bilete Obraz FPX Imagem FPX Imagine FPX изображение FPX Obrázok FPX Slikovna datoteka FPX Figurë FPX FPX-bild FPX görüntüsü зображення FPX Ảnh FPX FPX 图像 FPX 影像 FPX FlashPiX EPS image (gzip-compressed) صورة EPS (مضغوط-gzip) Vyjava EPS (gzip-skampresavanaja) Изображение — EPS, компресирано с gzip imatge EPS (comprimida amb gzip) Obrázek EPS (komprimovaný pomocí gzip) EPS-billede (gzip-komprimeret) EPS-Bild (gzip-komprimiert) EPS image (gzip-compressed) imagen EPS (comprimida con gzip) EPS irudia (gzip-ekin konprimitua) EPS-kuva (gzip-pakattu) EPS mynd (gzip-stappað) image EPS (compressée gzip) íomhá EPS (comhbhrúite le gzip) imaxe EPS (comprimida con gzip) תמונת EPS (מכווץ בgzip) EPS slika (komprimirana gzip-om) EPS kép (gzip-tömörítésű) Citra EPS (terkompresi gzip) Immagine EPS (compressa con gzip) EPS 画像 (gzip 圧縮) EPS გამოსახულება (gzip-ით შეკუმშული) EPS суреті (gzip-пен сығылған) EPS 그림 (GZIP 압축) EPS paveikslėlis (suglaudintas su gzip) EPS attēls (saspiests ar gzip) EPS-bilde (gzip-komprimert) EPS-afbeelding (ingepakt met gzip) EPS-bilete (pakka med gzip) Obraz EPS (kompresja gzip) Imagem EPS (compactada com gzip) Imagine EPS (compresie gzip) изображение EPS (сжатое gzip) Obrázok EPS (komprimovaný pomocou gzip) Slikovna datoteka EPS (stisnjena z gzip) Figurë EPS (e kompresuar me gzip) EPS-bild (gzip-komprimerad) EPS görüntüsü (gzip ile sıkıştırılmış) зображення EPS (стиснене gzip) Ảnh EPS (đã nén gzip) EPS 图像(gzip 压缩) EPS 影像 (gzip 格式壓縮) Microsoft icon أيقونة مايكروسوفت Икона — Microsoft icona de Microsoft Ikona Microsoft Microsoftikon Microsoft-Symbol εικονίδιο Microsoft Microsoft icon Microsoft-piktogramo icono de Microsoft Microsoft ikonoa Microsoft-kuvake Microsoft ímynd icône Microsoft deilbhín Microsoft Icona de microsoft אייקון של Microsofr Microsoft ikona Microsoft ikon Ikon Microsoft Icona Microsoft Microsoft アイコン Microsoft-ის ხატულა Microsoft таңбашасы 마이크로소프트 아이콘 Microsoft piktograma Microsoft ikona Microsoft pictogram Ikona Microsoft Ícone da Microsoft Iconiță Microsoft значок Microsoft Ikona Microsoft Datoteka ikone Microsoft Windows Microsoft-ikon піктограма Microsoft Biểu tượng Microsoft Microsoft 图标 微軟圖示 MacOS X icon أيقونة MacOS X Ikona MacOS X Икона — MacOS X icona MacOS X Ikona MacOS X MacOS X-ikon MacOS-X-Symbol εικονίδιο MacOS X MacOS X icon MacOS-X-piktogramo icono de MacOS X MacOS X ikonoa MacOS X -kuvake MacOS X ímynd icône MacOS X deilbhín MacOS X Icona de MacOS X אייקון של MacOS X MacOS X ikona MacOS X ikon Ikon MacOS X Icona MacOS X MacOS X アイコン MacOS X-ის ხატულა MacOS X таңбашасы MacOS X 아이콘 MacOS X piktograma MacOS X ikona MacOS X-ikon MacOS-X-pictogram MacOS X-ikon Ikona Mac OS X Ícone do MacOS X Iconiță MacOS X значок MacOS X Ikona MacOS X Datoteka ikone MacOS X Ikonë MacOS X MacOS X-ikon піктограма MacOS X Biểu tượng MacOS X MacOS X 图标 MacOS X 圖示 ILBM image صورة ILBM ILBM rəsmi Vyjava ILBM Изображение — ILBM imatge ILBM Obrázek ILMB Delwedd ILBM ILBM-billede ILBM-Bild εικόνα ILBM ILBM image ILBM-bildo imagen ILBM ILBM irudia ILBM-kuva ILBM mynd image ILBM íomhá ILBM imaxe ILBM תמונת ILBM ILBM slika ILBM-kép Citra ILBM Immagine ILBM ILBM 画像 ILBM суреті ILBM 그림 ILBM paveikslėlis ILBM attēls Imej ILBM ILBM-bilde ILBM-afbeelding ILMB-bilete Obraz ILBM imagem ILBM Imagem ILBM Imagine ILBM изображение ILBM Obrázok ILMB Slikovna datoteka ILBM Figurë ILBM ILBM слика ILBM-bild зображення ILBM Ảnh ILBM ILBM 图像 ILBM 影像 ILBM InterLeaved BitMap JNG image صورة JNG JNG rəsmi Vyjava JNG Изображение — JNG imatge JNG Obrázek JNG Delwedd JNG JNG-billede JNG-Bild εικόνα JNG JNG image JNG-bildo imagen JNG JNG irudia JNG-kuva JNG mynd image JNG íomhá JNG imaxe JNG תמונת JNG JNG slika JNG-kép Citra JNG Immagine JNG JNG 画像 JNG суреті JNG 그림 JNG paveikslėlis JNG attēls Imej PNG JNG-bilde JNG-afbeelding JNG-bilete Obraz JNG imagem JNG Imagem JNG Imagine JNG изображение JNG Obrázok JNG Slikovna datoteka JNG Figurë JNG JNG слика JNG-bild JNG görüntüsü зображення JNG Ảnh JNG JNG 图像 JNG 影像 JNG JPEG Network Graphics LightWave object كائن LightWave LightWave cismi Abjekt LightWave Обект — LightWave objecte de LightWave Objekt LightWave Gwrthrych LightWave LightWave-objekt LightWave-Objekt αντικείμενο LightWave LightWave object LightWave-objekto objeto LightWave LightWave objektua LightWave-esine LightWave lutur objet LightWave réad LightWave obxecto de LightWave עצם LightWave LightWave objekt LightWave-objektum Proyek LightWave Oggetto LightWave LightWave オブジェクト LightWave объекті LightWave 개체 LightWave objektas LightWave objekts Objek LightWave LightWave-objekt LightWave-object LightWave-objekt Obiekt LightWave Objecto LightWave Objeto LightWave Obiect LightWave объект LightWave Objekt LightWave Datoteka predmeta LightWave Objekt LightWave LightWave објекат LightWave-objekt LightWave nesnesi об'єкт LightWave Đối tượng LightWave LightWave 对象 LightWave 物件 LightWave scene مشهد LightWave LightWave səhnəsi Scena LightWave Сцена — LightWave escena de LightWave Scéna LightWave Golygfa LightWave LightWave-scene LightWave-Szene σκηνή LightWave LightWave scene LightWave-sceno escena LightWave LightWave eszena LightWave-maisema LightWave leikmynd scène LightWave radharc LightWave escena de LightWave סצנה של LightWave LightWave scena LightWave-jelenet Scene LightWave Scena LightWave LightWave シーン LightWave сахнасы LightWave 장면 LightWave scena LightWave aina Babak LightWave LightWave-scene LightWave-scène LightWave-scene Scena Lightwave cenário LightWave Cena LightWave Scenă LightWave сцена LightWave Scéna LightWave Datoteka scene LightWave Skenë LightWave LightWave сцена LightWave-scen сцена LightWave Cảnh LightWave LightWave 场景 LightWave 場景 MacPaint Bitmap image صورة MacPaint Bitmap Bitmapnaja vyjava MacPaint Изображение — MacPaint Bitmap imatge de mapa de bits MacPaint Obrázek MacPaint Bitmap MacPaint BitMap-billede MacPaint-Bitmap-Datei εικόνα Bitmap MacPaint MacPaint Bitmap image imagen en mapa de bits de MacPaint MacPaint Bitmap irudia MacPaint-bittikartta MacPaint Bitmap mynd image matricielle MacPaint íomhá MacPaint Bitmap imaxe de mapa de bits MacPaint תמונת מפת-סיביות של MacPaint MacPaint bitkép Citra MacPaint Bitmap Immagine Bitmap MacPaint MacPaint ビットマップ画像 MacPaint растрлық суреті MacPaint 비트맵 그림 MacPaint rastrinis paveikslėlis MacPaint bitkartes attēls MacPaint Bitmap-bilde MacPaint-bitmap-afbeelding MacPaint punktbilete Obraz bitmapowy MacPaint Imagem de bitmap do MacPaint Imagine MacPaint Bitmap растровое изображение MacPaint Obrázok MacPaint Bitmap Slikovna bitna datoteka MacPaint Figurë BitMap MacPaint MacPaint Bitmap-bild растрове зображення MacPaint Ảnh mảng MacPaint MacPaint 位图 MacPaint 點陣影像 Office drawing تصميم أوفيس Ofisny rysunak Чертеж — Office dibuix d'Office Kresba Office Officetegning Office-Zeichnung σχέδιο Office Office drawing dibujo de Office Office marrazkia Office-piirros Office tekning dessin Office líníocht Office debuxo de Office ציור של Office Office rajz Gambar Office Disegno Office Office ドロー Office суреті 오피스 드로잉 Office piešinys Office zīmējums Office-tegning Office-tekening Office-teikning Rysunek Office Desenho do Office Desen Office изображение Office Kresba Office Datoteka risbe Office Vizatim Office Office-teckning малюнок Office Bản vẽ Office Microsoft Office 绘图 Office 繪圖 NIFF image صورة NIFF Vyjava NIFF Изображение — NIFF imatge NIFF Obrázek NIFF NIFF-billede NIFF-Bild εικόνα NIFF NIFF image NIFF-bildo imagen NIFF NIFF irudia NIFF-kuva NIFF mynd image NIFF íomhá NIFF imaxe NIFF תמונת NIFF NIFF slika NIFF kép Citra NIFF Immagine NIFF NIFF 画像 NIFF суреті NIFF 그림 NIFF paveikslėlis NIFF attēls NIFF-bilde NIFF-afbeelding NIFF-bilete Obraz NIFF Imagem NIFF Imagine NIF изображение NIFF Obrázok NIFF Slikovna datoteka NIFF Figurë NIFF NIFF-bild NIFF görüntüsü зображення NIFF Ảnh NIFF NIFF 图像 NIFF 影像 PCX image صورة PCX Vyjava PCX Изображение — PCX imatge PCX Obrázek PCX PCX-billede PCX-Bild εικόνα PCX PCX image PCX-bildo imagen PCX PCX irudia PCX-kuva PCX mynd image PCX íomhá PCX imaxe PCX תמונת PCX PCX kép Citra PCX Immagine PCX PCX 画像 PCX суреті PCX 그림 PCX paveikslėlis PCX attēls PCX-bilde PCX-afbeelding PCX-bilete Obraz PCX Imagem PCX Imagine PCX изображение PCX Obrázok PCX Slikovna datoteka PCX Figurë PCX PCX-bild PCX görüntüsü зображення PCX Ảnh PCX PCX 图像 PCX 影像 PCX PiCture eXchange PCD image صورة PCD Vyjava PCD Изображение — PCD imatge PCD Obrázek PCD PCD-billede PCD-Bild εικόνα PCD PCD image PCD-bildo imagen PCD PCD irudia PCD-kuva PCD mynd image PCD íomhá PCD imaxe PCD תמונת PCD PCD kép Citra PCD Immagine PCD PCD 画像 PCD გამოსახულება PCD суреті PCD 그림 PCD paveikslėlis PCD attēls PCD-bilde PCD-afbeelding PCD-bilete Obraz PCD Imagem PCD Imagine PCD изображение PCD Obrázok PCD Slikovna datoteka PCD Figurë PCD PCD-bild PCD görüntüsü зображення PCD Ảnh PCD PCD 图像 PCD 影像 PCD PhotoCD PNM image صورة PNM PNM rəsmi Vyjava PNM Изображение — PNM imatge PNM Obrázek PNM Delwedd PNM PNM-billede PNM-Bild εικόνα PNM PNM image PNM-bildo imagen PNM PNM irudia PNM-kuva PNM mynd image PNM íomhá PNM imaxe PNM תמונת PNM PNM-kép Citra PNM Immagine PNM PNM 画像 PNM суреті PNM 그림 PNM paveikslėlis PNM attēls Imej PNM PNM-bilde PNM-afbeelding PNM-bilete Obraz PNM imagem PNM Imagem PNM Imagine PNM изображение PNM Obrázok PNM Slikovna datoteka PNM Figurë PNM PNM слика PNM-bild PNM görüntüsü зображення PNM Ảnh PNM PNM 图像 PNM 影像 PBM image صورة PBM Vyjava PBM Изображение — PBM imatge PBM Obrázek PBM Delwedd PBM PBM-billede PBM-Bild εικόνα PBM PBM image PBM-bildo imagen PBM PBM irudia PBM-kuva PBM mynd image PBM íomhá PBM imaxe PBM תמונת PBM PBM kép Citra PBM Immagine PBM PBM 画像 PBM გამოსახულება PBM суреті PBM 그림 PBM paveikslėlis PBM attēls PBM-bilde PBM-afbeelding PBM-bilete Obraz PBM Imagem PBM Imagine PBM изображение PBM Obrázok PBM Slikovna datoteka PBM Figurë PBM PBM слика PBM-bild PBM görüntüsü зображення PBM Ảnh PBM PBM 图像 PBM 影像 PBM Portable BitMap PGM image صورة PGM Vyjava PGM Изображение — PGM imatge PGM Obrázek PGM Delwedd PGM PGM-billede PGM-Bild εικόνα PGM PGM image PGM-bildo imagen PGM PGM irudia PGM-kuva PGM mynd image PGM íomhá PGM imaxe PGM תמונת PGM PGM kép Citra PGM Immagine PGM PGM 画像 PGM суреті PGM 그림 PGM paveikslėlis PGM attēls PGM-bilde PGM-afbeelding PGM-bilete Obraz PGM Imagem PGM Imagine PGM изображение PGM Obrázok PGM Slikovna datoteka PGM Figurë PGM PGM-bild PGM görüntüsü зображення PGM Ảnh PGM PGM 图像 PGM 影像 PGM Portable GrayMap PPM image صورة PPM Vyjava PPM Изображение — PPM imatge PPM Obrázek PPM Delwedd PPM PPM-billede PPM-Bild εικόνα PPM PPM image PPM-bildo imagen PPM PPM irudia PPM-kuva PPM mynd image PPM íomhá PPM imaxe PPM תמונת PPM PPM kép Citra PPM Immagine PPM PPM 画像 PPM суреті PPM 그림 PPM paveikslėlis PPM attēls PPM-bilde PPM-afbeelding PPM-bilete Obraz PPM Imagem PPM Imagine PPM изображение PPM Obrázok PPM Slikovna datoteka PPM Figurë PPM PPM слика PPM-bild PPM görüntüsü зображення PPM Ảnh PPM PPM 图像 PPM 影像 PPM Portable PixMap Photoshop image صورة فوتوشوب Изображение — Photoshop imatge de Photoshop Obrázek Photoshop Photoshop-billede Photoshop-Bild εικόνα Photoshop Photoshop image Photoshop-bildo imagen de Photoshop Photoshop irudia Photoshop-kuva Photoshop mynd image Photoshop íomhá Photoshop imaxe de Photoshop תמונת Photoshop Photoshop-kép Citra Photoshop Immagine Photoshop Photoshop 画像 изображение Photoshop 포토샵 이미지 Photoshop paveikslėlis Photoshop attēls Imej Photoshop Photoshop-afbeelding Obraz Photoshop imagem do Photoshop Imagem do Photoshop Imagine Photoshop изображение Photoshop Obrázok Photoshop Slikovna datoteka Photoshop Фотошоп слика Photoshop-bild Photoshop görüntüsü зображення Photoshop Ảnh Photoshop Photoshop 图像 Photoshop 影像 RGB image صورة RGB RGB rəsmi Vyjava RGB Изображение — RGB imatge RGB Obrázek RGB Delwedd RGB RGB-billede RGB-Bild εικόνα RGB RGB image RGB-bildo imagen RGB RGB irudia RGB-kuva RGB mynd image RGB íomhá RGB imaxe RGB תמונת RGB RGB slika RGB-kép Citra RGB Immagine RGB RGB 画像 RGB суреті RGB 그림 RGB paveikslėlis RGB attēls Imej RGB RGB-bilde RGB-afbeelding RGB-bilete Obraz RGB imagem RGB Imagem RGB Imagine RGB изображение RGB Obrázok RGB Slikovna datoteka RGB Figurë RGB RGB слика RGB-bild зображення RGB Ảnh kiểu RGB RGB 图像 RGB 影像 SGI image صورة SGI Vyjava SGI Изображение — SGI imatge SGI Obrázek SGI SGI-billede SGI-Bild εικόνα SGI SGI image SGI-bildo imagen SGI SGI irudia SGI-kuva SGI mynd image SGI íomhá SGI imaxe SGI תמונת SGI SGI slika SGI kép Citra SGI Immagine SGI SGI 画像 SGI суреті SGI 그림 SGI paveikslėlis SGI attēls SGI-bilde SGI-afbeelding SGI-bilete Obraz SGI Imagem SGI Imagine SGI изображение SGI Obrázok SGI Slikovna datoteka SGI Figurë SGI SGI-bild SGI görüntüsü зображення SGI Ảnh SGI SGI 图像 SGI 影像 Sun raster image صورة Sun raster Rastravaja vyjava Sun Изображение — Sun raster imatge ràster Sun Rastrový obrázek Sun Sun rasterbillede Sun-Rasterbild εικόνα Sun raster Sun raster image imagen ráster de Sun Sun raster irudia Sun-rasterikuva Sun raster mynd image raster Sun íomhá rastar Sun imaxe ráster de Sun תמונה סרוקה של Sun SUN raszterkép Citra raster Sun Immagine raster Sun Sun ラスタ画像 Sun растрлық суреті Sun 래스터 그림 Sun rastrinis paveikslėlis Sun rastra attēls Sun rasterbilde Sun-rasterafbeelding Sun rasterbilete Obraz rastrowy Sun Imagem raster da Sun Imagine rasterizată Sun растровое изображение Sun Rastrový obrázok Sun Slikovna rastrska datoteka Sun Figurë raster Sun Sun-rasterbild растрове зображення Sun Ảnh mành Sun Sun 光栅图像 Sun raster 影像 TGA image صورة TGA Vyjava TGA Изображение — TGA imatge TGA Obrázek TGA TGA-billede TGA-Bild εικόνα TGA TGA image TGA-bildo imagen TGA TGA irudia TGA-kuva TGA mynd image TGA íomhá TGA imaxe TGA תמונת TGA TGA slika TGA kép Citra TGA Immagine TGA TGA 画像 TGA суреті TGA 그림 TGA paveikslėlis TGA attēls TGA-bilde TGA-afbeelding TGA-bilete Obraz TGA Imagem TGA Imagine TGA изображение TGA Obrázok TGA Slikovna datoteka TGA Figurë TGA TGA-bild зображення TGA Ảnh TGA TGA 图像 TGA 影像 TGA Truevision Graphics Adapter Windows cursor مؤشر ويندوز Kursor Windows Курсор — Windows cursor de Windows Kurzor Windows Windowsmarkør Windows-Cursor δείκτης παραθυρικού περιβάλλοντος Windows cursor Windows-kursoro cursor de Windows Windows kurtsorea Windows-osoitin Windows vísi curseur Windows cúrsóir Windows Cursor de Windows סמן של Windows Windows kursor Windows-kurzor Kursor Windows Cursore Windows Windows カーソル Windows курсоры Windows 커서 Windows žymiklis Windows kursors Kursor Windows Windows-markør Windows-muisaanwijzer Windows-peikar Kursor Windows cursor Windows Cursor do Windows Cursor Windows курсор Windows Kurzor Windows Datoteka kazalke Windows Kursor Windows Виндуз курзор Windows-muspekare курсор Windows Con chạy Windows Windows 光标 Windows 滑鼠指標 Windows animated cursor مؤشر ويندوز المتحرك Animavany kursor Windows Курсор — Windows, анимиран cursor animat de Windows Animovaný kurzor Windows Windowsanimeret markør Animierter Windows-Cursor κινούμενος δρομέας Windows Windows animated cursor cursor animado de Windows Windows-eko kurtsore animatua animoitu Windows-osoitin Windows livindaigjørdur vísi curseur animé Windows cúrsóir beo Windows Cursor animado de Windows סמן מונפש של Windows Windows animirani kursor Windows animált kurzor Kursor animasi Windows Cursore animato Windows Windows アニメーションカーソル Windows анимациясы бар курсор Windows 움직이는 커서 Animuotas Windows žymiklis Windows animēts kursors geanimeerde Windows-muisaanwijzer Windows animert peikar Animowany kursor Windows Cursor animado do Windows Cursor animat Windows анимированный курсор Windows Animovaný kurzor Windows Datoteka animirane kazalke Windows Kursor i animuar Windows Animerad Windows-muspekare анімований курсор Windows Con chạy hoạt họa Windows Windows 动画光标 Windows 滑鼠動畫游標 EMF image صورة EMF Vyjava EMF Изображение — EMF imatge EMF Obrázek EMF EMF-billede EMF-Bild εικόνα EMF EMF image EMF-bildo imagen EMF EMF irudia EMF-kuva EMF mynd image EMF íomhá EMF imaxe EMF תמונת EMF EMF slika EMF kép Citra EMF Immagine EMF EMF 画像 EMF გამოსახულება EMF суреті EMF 그림 EMF paveikslėlis EMF attēls EMF-bilde EMF-afbeelding EMF-bilete Obraz EMF Imagem EMF Imagine EMF изображение EMF Obrázok EMF Slikovna datoteka EMF Figurë EMF EMF-bild EMF görüntüsü зображення EMF Ảnh EMF EMF 图像 EMF 影像 EMF Enhanced MetaFile WMF image صورة WMF Vyjava WMF Изображение — WMF imatge WMF Obrázek WMF WMF-billede WMF-Bild εικόνα WML WMF image WMF-bildo imagen WMF WMF irudia WMF-kuva WMF mynd image WMF íomhá WMF imaxe WMF תמונת WMF WMF slika WMF kép Citra WMF Immagine WMF WMF 画像 WMF суреті WMF 그림 WMF paveikslėlis WMF attēls WMF-bilde WMF-afbeelding WMF-bilete Obraz WMF Imagem WMF Imagine WMF изображение WMF Obrázok WMF Slikovna datoteka WMF Figurë WMF WMF-bild WMF görüntüsü зображення WMF Ảnh WMF WMF 图像 WMF 影像 WMF Windows Metafile XBM image صورة XBM Vyjava XBM Изображение — XBM imatge XBM Obrázek XBM XBM-billede XBM-Bild εικόνα XBM XBM image XBM-bildo imagen XBM XBM irudia XBM-kuva XBM mynd image XBM íomhá XBM imaxe XBM תמונת XBM XBM slika XBM-kép Citra XBM Immagine XBM XBM 画像 XBM суреті XBM 그림 XBM paveikslėlis XBM attēls XBM-bilde XBM-afbeelding XBM-bilete Obraz XBM Imagem XBM Imagine XBM изображение XBM Obrázok XBM Slikovna datoteka XBM Figurë XBM XBM-bild зображення XBM Ảnh XBM XBM 图像 XBM 影像 XBM X BitMap GIMP image صورة GIMP Vyjava GIMP Изображение — GIMP imatge del GIMP Obrázek GIMP GIMP-billede GIMP-Bild εικόνα GIMP GIMP image GIMP-bildo imagen del GIMP GIMP irudia GIMP-kuva GIMP mynd image GIMP íomhá GIMP imaxe de GIMP תמונת GIMP GIMP slika GIMP-kép Citra GIMP Immagine GIMP GIMP 画像 GIMP გამოსახულება GIMP суреті GIMP 그림 GIMP paveikslėlis GIMP attēls Imej GIMP GIMP-bilde GIMP-afbeelding GIMP-bilete Obraz GIMP imagem do GIMP Imagem do GIMP Imagine GIMP изображение GIMP Obrázok GIMP Slikovna datoteka GIMP Figurë GIMP Гимп слика GIMP-bild GIMP görüntüsü зображення GIMP Ảnh GIMP GIMP 图像 GIMP 影像 XFig image صورة XFig Vyjava XFig Изображение — XFig imatge de XFig Obrázek XFig XFig-billede XFig-Bild εικόνα XFig XFig image XFig-bildo imagen de XFig XFig irudia XFig-kuva XFig mynd image XFig íomhá XFig imaxe de XFig תמונת XFig XFig slika XFig-kép Citra XFig Immagine XFig XFig 画像 XFig суреті XFig 그림 XFig paveikslėlis XFig attēls Imej XFig XFig-bilde XFig-afbeelding XFig-bilete Obraz XFig imagem XFig Imagem do XFig Imagine XFig изображение XFig Obrázok XFig Slikovna datoteka XFig Figurë XFig XFig слика XFig-bild зображення XFig Ảnh XFig XFig 图像 XFig 影像 XPM image صورة XPM Vyjava XPM Изображение — XPM imatge XPM Obrázek XPM Delwedd XPM XPM-billede XPM-Bild εικόνα XPM XPM image XPM-bildo imagen XPM XPM irudia XPM-kuva XPM mynd image XPM íomhá XPM imaxe XPM תמונת XPM XPM slika XPM kép Citra XPM Immagine XPM XPM 画像 XPM суреті XPM 그림 XPM paveikslėlis XPM attēls XPM-bilde XPM-afbeelding XPM-bilete Obraz XPM Imagem XPM Imagine XPM изображение XPM Obrázok XPM Slikovna datoteka XPM Figurë XPM XPM-bild зображення XPM Ảnh XPM XPM 图像 XPM 影像 XPM X PixMap X window image صورة X window X window rəsmi Vyjava vakna X Изображение — X Window imatge de X window Obrázek X window Delwedd ffenest X X-billede X-Window-Bild εικόνα περιβάλλοντος X X window image bildo de X window imagen de X window X window irudia X-ikkunakuva X vindeyga mynd image X window íomhá fhuinneog X imaxe de X Window תמונת חלון של X X window slika X window-kép Citra X window Immagine X window X window 画像 X window суреті X 윈도 그림 X window paveikslėlis X window attēls Imej tetingkap X X-Windows skjermbilde X-window-afbeelding X window bilete Obraz X Window imagem de janela X Imagem de janela do X Imagine X window изображение X window Obrázok X window slika X oken Figurë X window X прозор слика X-fönsterbild зображення X window Ảnh cửa sổ X X window 图像 X window 影像 block device جهاز كتلي blokavaja pryłada Блоково устройство dispositiu de blocs Blokové zařízení blokenhed Blockorientiertes Gerät συσκευή block block device bloka disponaĵo dispositivo de bloques bloke-gailua laitetiedosto blokka tóleind périphérique de blocs gléas bloc dispositivo de bloque התקן בלוק blokkos eszköz blok divais Device a blocchi ブロックデバイス блоктық құрылғысы 블록 장치 blokinis įrenginys bloka ierīce Peranti blok blokkenhet blok-apparaat blokk-eining Urządzenie blokowe dispositivo de bloco Dispositivo de bloco dispozitiv bloc блочное устройство Blokové zariadenie bločna naprava device me blloqe блок уређај blockenhet blok aygıtı блоковий пристрій thiết bị khối 块设备 區塊裝置 character device جهاز حرفي znakavaja pryłada Символно устройство dispositiu de caràcters Znakové zařízení tegnenhed Zeichenorientiertes Gerät συσκευή χαρακτήρων character device signa disponaĵo dispositivo de caracteres karaktereen gailua merkkilaite stavatóleind périphérique de caractères gléas carachtar dispositivo de caracter התקן תכונה karakteres eszköz karakter divais Device a caratteri キャラクタデバイス символдық құрылғысы 문자 장치 simbolinis įrenginys rakstzīmju ierīce Peranti aksara tegnenhet byte-apparaat teikneining Urządzenie znakowe dispositivo de caracteres Dispositivo de caractere dispozitiv caracter символьное устройство Znakové zariadenie znakovna naprava device me karaktere знаковни уређај teckenenhet karakter aygıtı символьний пристрій thiết bị ký tự 字符设备 字元裝置 folder مجلّد kataloh Папка carpeta Složka mappe Ordner φάκελος folder dosierujo carpeta karpeta kansio mappa dossier fillteán cartafol תיקייה direktorij mappa folder Cartella フォルダー бума 폴더 aplankas mape Folder mappe map mappe Katalog pasta Pasta dosar папка Priečinok mapa Kartelë директоријум mapp dizin тека thư mục 文件夹 資料夾 pipe إنبوب kanvejer Конвейер conducte Roura datakanal Pipe σωλήνωση pipe dukto tubería kanalizazioa putki rør tube píopa tubería צינור adatcsatorna pipa Pipe パイプ арна 파이프 konvejeris programmkanāls Paip rør pijp røyr Potok canal Pipe canal pipe канал Rúra cev Pipe цев rör канал ống dẫn 管道 管線 mount point نقطة الوصْل punkt mantavańnia Точка на монтиране punt de muntatge Místo připojení monteringspunkt Einhängepunkt σημείο προσάρτησης mount point surmetingo punto de montaje muntatze-puntua liitospiste ísetingarpunkt point d'accès pointe feistithe punto de montaxe נקודת עיגון točka montiranja csatolási pont titik mount Punto di mount マウントポイント тіркеу нүктесі 마운트 위치 prijungimo taškas montēšanas punkts Titik lekapan monteringspunkt aankoppelingspunt monteringspunkt Punkt montowania ponto de montagem Ponto de montagem loc montare точка монтирования Miesto pripojenia priklopna točka Pikë montimi тачка прикључења monteringspunkt точка монтування điểm lắp 挂载点 掛載點 socket مقبس sokiet Гнездо sòcol Socket sokkel Socket υποδοχή socket kontaktoskatolo socket socketa pistoke sokkul connecteur réseau soicéad socket נקודת חיבור utičnica illesztőpont soket Socket ソケット сокет 소켓 lizdas sokets Soket plugg socket sokkel Gniazdo 'socket' Socket socket сокет Soket vtič Socket сокет uttag сокет ổ cắm 套接字 socket symbolic link وصلة رمزية simvolik körpü symbalnaja spasyłka Символна връзка enllaç simbòlic Symbolický odkaz cyswllt symbolaidd symbolsk henvisning Symbolische Verknüpfung συμβολικός σύνδεσμος symbolic link simbola ligilo enlace simbólico esteka sinbolikoa symbolinen linkki tykislig leinkja lien symbolique nasc siombalach ligazón simbólica קישור סימבולי simbolička veza szimbolikus link taut simbolik Collegamento simbolico シンボリックリンク სიმბოლური ბმული символдық сілтеме 심볼릭 링크 simbolinė nuoroda simboliskā saite Pautan simbolik symbolsk lenke symbolische koppeling symbolsk lenkje Dowiązanie symboliczne ligação simbólica Ligação simbólica legătură simbolică символьная ссылка Symbolický odkaz simbolna povezava Lidhje simbolike симболичка веза symbolisk länk символічне посилання liên kết tượng trưng 符号链接 符號鏈結 mail delivery report تقرير تسليم البريد poçt yollama raportu rapart ab dastaŭcy pošty Отчет за пристигналата поща informe de lliurament de correu Zpráva o doručení pošty Adroddiad trosgludo post postleveringsrapport E-Mail-Zustellungsbericht αναφορά παράδοσης μηνύματος mail delivery report raporto pri transdono de retpoŝto informe de entrega de correo posta banaketako txostena viestin jakeluilmoitus post útberingarfrásøgn rapport de livraison de courriels tuairisc sheachadadh poist informe de entrega de correo דוח העברת דואר izvještaj dostave pošte jelentés levélkézbesítésről laporan pengantaran surat Rapporto di consegna posta メール配送ポート пошта жеткізілгені туралы отчет 메일 배달 보고서 pašto pristatymo ataskaita pasta piegādes atskaite Laporan penghantaran mel e-postleveranserapport e-mail-bezorgingsbericht e-post-leveringsrapport Raport z dostarczenia poczty relatório de entrega de e-mail Relatório de entrega de correspondência raport de trimitere email отчёт о доставке сообщения Správa o doručení pošty poročilo dostave pošte Raport mbi dorëzimin e mesazhit извештај доставе поруке e-postleveransrapport звіт про доставку пошти thông báo phát thư 邮件投递报告 郵件寄送回報 mail disposition report تقرير ترتيب البريد poçt qayıtma raportu rapart ab raźmiaščeńni pošty Отчет за състоянието на пощата informe de disposició de correu Zpráva o předání pošty adroddiad ffurf post postdisponeringsrapport E-Mail-Übertragungsbericht αναφορά διάθεσης μηνύματος mail disposition report raporto pri dispono de retpoŝto informe de disposición de correo posta joerako txostena viestin kuittausilmoitus post avhendingarfrásøgn rapport de disposition de courriels tuairisc chóiriú poist informe de disposición de correo דוח אספקת דואר jelentés levélkidobásról laporan disposisi surat Rapporto di disposizione posta メール停止レポート пошта жылжытылғаны туралы отчет 메일 처리 보고서 pašto charakteristikos ataskaita pasta izvietojuma atskaite Laporan pelupusan mel e-postdispositionsrapport e-mail-plaatsingsbericht e-post-disposisjonsrapport Raport z wysyłania poczty relatório de disposição de e-mail Relatório de disposição de correspondência confirmare primire email отчёт о перемещении почты Správa o odovzdaní pošty poročilo razporeditve pošte Raport mbi njoftimin e mesazhit извештај слања поруке e-postdispositionsrapport звіт про розташування пошти thông báo chuyển nhượng thư 邮件接收报告 郵件處置回報 reference to remote file مرجع إلى ملف بعيد uzaq fayla göstəriş spasyłka da addalenaha fajłu Препратка към отдалечен файл referència a fitxer remot Odkaz na vzdálený soubor cyfeiriad at ffeil bell reference til fjern fil Verweis auf entfernte Datei αναφορά σε απόμακρο αρχείο reference to remote file referenco al fora dosiero referencia a un archivo remoto erreferentzia urruneko fitxategiari viittaus etätiedostoon tilvísing til fjarfílu référence au fichier distant tagairt do chomhad cianda referencia a un ficheiro remoto התיחסות לקובץ מרוחק referenca na udaljenu datoteku hivatkozás távoli fájlra referensi ke berkas jarak jauh Riferimento a file remoto リモートファイルへの参照 қашықтағы файлға сілтеме 원격 파일 참조 nuoroda į nutolusį failą norāde uz attālinātu datni Rujukan ke fail jauh referanse til ekstern fil verwijzing naar bestand op afstand referanse til fil over nettverk Odwołanie do pliku zdalnego referência a um ficheiro remoto Referência a um arquivo remoto referință fișier la distanță ссылка на удалённый файл Odkaz na vzdialený súbor sklic do oddaljene datoteke Referim për tek file në distancë референца на удаљену датотеку referens till fjärrfil посилання на віддалений файл tham chiếu đến tập tin ở xa 到远程文件的引用 遠端檔案的參照 Usenet news message رسالة أخبار Usenet Usenet xəbərlər ismarışı Navina Usenet Съобщение — Usenet missatge de notícies Usenet Příspěvek do diskusních skupin Usenet Neges newyddion Usenet Usenetnyhedsmeddelelse Usenet-News-Nachricht μήνυμα ομάδων συζητήσεων Usenet Usenet news message novaĵmesaĝo de Usenet mensaje de noticias de Usenet Usenet berrien mezua nyyssiviesti Usenet news boð message de groupe d'échange Usenet teachtaireacht nuacht Usenet mensaxes de noticias de Usenet הודעת חדשות של Usenet Usenet poruka novosti USENET-hírcsoportüzenet Pesan berita Usenet Messaggio news Usenet Usenet news メッセージ Usenet жаңалық мәлімдемесі 유즈넷 뉴스 메시지 Usenet naujienų žinutė Usenet jaunumu ziņojums Mesej berita USENET Usenet nyhetsmelding Usenet-nieuwsbericht USENET diskusjonsmelding Wiadomość grupy dyskusyjnej mensagem de notícias Usenet Mensagem de notícias da Usenet Mesaj Usenet de știri новостное сообщение Usenet Príspevok do diskusných skupín Usenet novičarsko sporočilo Usenet Mesazh lajmesh Usenet Порука са дискусионе групе Usenet-diskussionsgruppsmeddelande повідомлення новин Usenet Thông điệp tin tức USENET Usenet 新闻信 Usenet 新聞訊息 partial email message رسالة البريد الإلكتروني الجزئية qismi poçt ismarışı niapoŭny list email Част от електронно писмо missatge de correu electrònic parcial Částečná e-mailová zpráva darn o neges e-bost delvis postmeddelelse E-Mail-Nachrichtenfragment τμηματικό ηλ. μήνυμα partial email message parta retpoŝta mesaĝo mensaje de correo electrónico parcial posta mezu partziala osittainen sähköpostiviesti message partiel de courriel teachtaireacht ríomhphoist neamhiomlán mensaxe de correo electrónico parcial מסר דוא"ל חלקי djelomična poruka e-pošte részleges elektronikus levél pesan email sebagian Messaggio email parziale 部分メールメッセージ электронды поштаның үзінді мәлімдемесі 부분적 전자 우편 메시지 nepilnas el. laiškas daļēja e-pasta vēstule Bahagian mesej emel del av e-postmelding gedeeltelijk e-mailbericht del av e-post-melding Częściowa wiadomość e-mail mensagem parcial de e-mail Mensagem de e-mail parcial mesaj de email parțial фрагмент сообщения электронной почты Čiastočná e-mailová správa delno elektronsko sporočilo Mesazh poste i pjesëshëm делимична е-порука del av e-postmeddelande часткове поштове повідомлення thư điện tử riêng phần 部分电子邮件 部份電子郵件訊息 email message رسالة البريد الإلكتروني list email Съобщение по електронната поща missatge de correu electrònic E-mailová zpráva postmeddelelse E-Mail-Nachricht ηλ. μήνυμα email message retpoŝta mesaĝo mensaje de correo electrónico helbide elektronikoen mezua sähköpostiviesti t-post boð message de courriel teachtaireacht ríomhphoist mensaxe de correo electrónico הודעת דואר אלקטרוני poruka e-pošte elektronikus levél pesan email Messaggio email メール本文 пошталық мәлімдеме 전자 우편 본문 el. laiškas e-pasta vēstule Mesej emel e-postmelding e-mailbericht e-postmelding Wiadomość e-mail mensagem de e-mail Mensagem de e-mail mesaj email почтовое сообщение E-mailová správa sporočilo elektronske pošte Mesazh poste е-порука e-postmeddelande повідомлення email thư điện tử 电子邮件 電子郵件內容 GNU mail message رسالة بريد جنو GNU poçt ismarışı List GNU Съобщение — GNU mail missatge de GNU mail Zpráva GNU mail Neges E-Bost GNU GNU-postmeddelelse GNU-Mail-Nachricht μήνυμα αλληλογραφίας GNU GNU mail message mesaĝo de GNU mail mensaje de GNU mail GNU posta mezua GNU-postiviesti GNU mail boð message de courriel GNU teachtaireacht phost GNU mensaxe de correo electrónico de GNU הודעת דואר של GNU GNU poruka pošte GNU elektronikus levél Pesan surat GNU Messaggio GNU mail GNU メールメッセージ GNU mail შეტყობინება GNU пошта хабарламасы GNU 메일 메시지 GNU pašto žinutė GNU pasta vēstule Mesej emel GNU GNU e-postmelding GNU-mailbericht GNU e-postmelding Wiadomość pocztowa GNU mensagem de e-mail GNU Mensagem de correio GNU Mesaj GNU mail почтовое сообщение GNU Správa GNU mail Sporočilo pošte GNU Mesazh GNU mail ГНУ е-писмо GNU-epostmeddelande поштове повідомлення GNU Thư điện tử của GNU GNU mail 信件 GNU 郵件訊息 VRML document مستند VRML VRML sənədi Dakument VRML Документ — VRML document VRML Dokument VRML Dogfen VRML VRML-dokument VRML-Dokument έγγραφο VRML VRML document VRML-dokumento documento VRML VRML dokumentua VRML-asiakirja VRML skjal document VRML cáipéis VRML documento VRML מסמך VRML VRML dokument VRML-dokumentum Dokumen VRML Documento VRML VRML ドキュメント VRML құжаты VRML 문서 VRML dokumentas VRML dokuments Dokumen VRML VRML-dokument VRML-document VRML-dokument Dokument VRML documento VRML Documento VRML Document VRML документ VRML Dokument VRML Dokument VRML Dokument VRML VRML документ VRML-dokument VRML belgesi документ VRML Tài liệu VRML VRML 文档 VRML 文件 VRML Virtual Reality Modeling Language message in several formats رسالة في عدة صيغ verici formatlarında ismarış paviedamleńnie ŭ niekalkich farmatach Съобщение в няколко формата missatge en varis formats Zpráva v několika formátech neges mewn sawl fformat meddelelse i flere formater Nachricht in mehreren Formaten μήνυμα σε διάφορες μορφές message in several formats mesaĝo en pluraj formatoj mensaje en varios formatos hainbat formatuko mezua viesti useissa muodoissa boð í fleiri sniðum message en formats divers teachtaireacht i roinnt fhormáidí mensaxe en varios formatos הודעה במספר פורמטים poruka u nekoliko oblika többféle formátumú üzenet pesan dalam beberapa format Messaggio in diversi formati いくつかの形式でのメッセージ бірнеше пішімдегі мәлімдеме 여러가지 형식의 메시지 laiškas keletu formatų ziņojums dažādos formātos Mesej dalam beberapa format melding i flere formater bericht in meerdere opmaken melding i fleire format Wiadomość w wielu formatach mensagem em vários formatos Mensagem em vários formatos mesaj în diferite formate сообщение в нескольких форматах Správa v niekoľkých formátoch sporočilo v več zapisih Mesazh në formate të ndryshëm поруке у више записа meddelande i flera format повідомлення у кількох форматах thông điệp có vài định dạng 各种格式的消息 多種格式的訊息 Macintosh AppleDouble-encoded file ملف Macintosh AppleDouble مشفر Macintosh AppleDouble-kodlanmış fayl Fajł Macintosh, AppleDouble-zakadavany Файл — кодиран с Macintosh AppleDouble fitxer codificat AppleDouble de Macintosh Soubor kódovaný pomocí Macintosh AppleDouble Ffeil AppleDouble-amgodedig Macintosh Macintosh AppleDouble-kodet fil Macintosh-Datei (AppleDouble-kodiert) αρχείο Macintosh κωδικοποίησης AppleDouble Macintosh AppleDouble-encoded file dosiero kodigita laŭ Macintosh AppleDouble archivo Macintosh codificado con AppleDouble Macintosh AppleDouble-rekin kodetutako fitxategia Macintosh AppleDouble -koodattu tiedosto Macintosh AppleDouble-bronglað fíla fichier codé Macintosh AppleDouble comhad ionchódaithe le Macintosh AppleDouble ficheiro de Macintosh codificado con AppleDouble קובץ מסוג Macintosh AppleDouble-encoded Macintosh AppleDouble-kodirana datoteka Macintosh AppleDouble kódolású fájl Berkas tersandi Macintosh AppleDouble File Macintosh codificato AppleDouble Macintosh AppleDouble エンコードファイル Macintosh AppleDouble кодталған файлы 매킨토시 AppleDouble 인코딩된 파일 Macintosh AppleDouble-encoded failas Macintosh AppleDouble-kodēts datne Fail terenkod-AppleDouble Macintosh dokument kodet med Macintosh AppleDouble Macintosh AppleDouble-gecodeerd bestand Macintosh AppleDouble-koda fil Zakodowany w AppleDouble plik Macintosh ficheiro codificado em AppleDouble de Macintosh Arquivo do Macintosh codificado com AppleDouble Fișier codat Macintosh AppleDouble файл (закодированный Macintosh AppleDouble) Súbor kódovaný pomocou Macintosh AppleDouble Kodirana datoteka Macintosh (AppleDouble) File Macintosh i kodifikuar AppleDouble Мекинтош AppleDouble-encoded датотека Macintosh AppleDouble-kodad fil файл закодований Macintosh AppleDouble Tập tin đã mã hoá Apple-Double của Macintosh Macintosh AppleDouble 编码的文件 Macintosh AppleDouble 編碼檔 message digest خلاصة الرسالة ismarış daycesti digest paviedamleńniaŭ Извадка от съобщение recopilació de missatges Přehled zpráv crynodeb negeseuon meddelelsessammendrag Nachrichtensammlung περίληψη μηνύματος message digest mesaĝaro recopilación de mensajes mezu laburra viestikokoelma boð samandráttur condensé de message achoimre theachtaireachtaí recompilación de mensaxe תקציר ההודעה ömlesztett üzenet pesan digest Digest di messaggi メッセージダイジェスト мәлімдеме профилі 메시지 묶음 laiškų santrauka ziņojumu apkopojums Jilid mesej medldingssamling berichtenbundel meldingsamandrag Wiadomość przetwarzania 'digest' de mensagens Resumo de mensagem colecție mesaje email профиль сообщения Prehľad správ povzetek sporočila Shpërndarje mesazhesh гомила порука meddelandesamling збірка повідомлень bản tóm tắt thông điệp 消息摘要 訊息摘要 encrypted message رسالة مشفرة şifrələnmiş ismarış zašyfravanaje paviedamleńnie Шифрирано съобщение missatge xifrat Zašifrovaná zpráva Neges wedi ei hamgryptio krypteret meddelelse Verschlüsselte Nachricht κρυπτογραφημένο μήνυμα encrypted message ĉifrita mesaĝo mensaje cifrado mezu enkriptatua salattu viesti bronglað boð message chiffré teachtaireacht chriptithe mensaxe cifrado הודעה מוצפנת šifrirana poruka titkosított üzenet pesan terenkripsi Messaggio cifrato 暗号化メッセージ шифрленген мәлімдеме 암호화된 메시지 užšifruotas laiškas šifrēta vēstule Mesej terenkripsi kryptert melding versleuteld bericht kryptert melding Wiadomość zaszyfrowana mensagem cifrada Mensagem criptografada mesaj criptat зашифрованное сообщение Zašifrovaná správa šifrirano sporočilo Mesazh i kriptuar шифрована порука krypterat meddelande şifrelenmiş mesaj шифроване повідомлення thông điệp đã mật mã 加密信件 加密訊息 compound documents مستندات مركبة składanyja dakumenty Съставни документи documents composats Složené dokumenty sammensatte dokumenter Verbunddokumente σύνθετα έγγραφα compound documents parentezaj dokumentoj documentos compuestos konposatutako dokumentuak yhdisteasiakirjat samansett skjøl documents composés cáipéisí comhshuite documentos compostos מסמכים מורכבים összetett dokumentumok dokumen kompon Documenti composti 複合ドキュメント құрама құжаттары 복합 문서 sudurtiniai dokumentai salikti dokumenti Dokumen halaman sammensatte dokumenter samengestelde documenten samansette dokument Dokumenty złożone documentos compostos Documentos compostos documente compuse составные документы Zložené dokumenty združeni dokumenti dokumente të përbërë сједињени документи sammansatta dokument складні документи tài liệu ghép 组合文档 複合文件 compound document مستند مركب birləşik sənəd składany dakument Съставен документ document composat Složený dokument dogfen gyfansawdd sammensat dokument Verbunddokument σύνθετο έγγραφο compound document parenteza dokumento documento compuesto konposatutako dokumentua yhdisteasiakirja samansett skjal document composé cáipéis comhshuite documento composto מסמך מורכב összetett dokumentum dokumen kompon Documento composto 複合ドキュメント құрама құжаты 복합 문서 sudurtinis dokumentas salikts dokuments Dokumen halaman sammensatt dokument samengesteld document samansett dokument Dokument złożony documento composto Documento composto document compus составной документ Zložený dokument združeni dokument dokumet i përbërë сједињени документ sammansatt dokument складний документ tài liệu ghép 组合文档 複合文件 mail system report تقرير نظام البريد poçt sistemi raportu rapart paštovaj systemy Отчет за пощенската система informe de sistema de correu Zpráva poštovního systému adroddiad system bost postsystemrapport E-Mail-Systembericht αναφορά συστήματος ηλ. ταχυδρομείου mail system report raporto de retpoŝta sistemo informe del sistema de correo posta sistemako txostena viestijärjestelmän ilmoitus postkervisfrásøgn rapport système de courriels tuairisc chóras poist informe do sistema de correo דו"ח של מערכת הדואר levelezőrendszer jelentése laporan sistem surat Rapporto di sistema posta メールシステムレポート пошта жүйесінің мәлімдемесі 메일 시스템 보고서 pašto sistemos ataskaita pasta sistēmas atskaite Laporan sistem mel e-postsystemrapport e-mail-systeembericht e-post-systemrapport Raport systemu pocztowego relatório de sistema de e-mail Relatório do sistema de correspondência raport sistem email отчёт почтовой системы Správa poštového systému poročilo poštnega sistema Raport i sistemit të postës извештај поштанског система e-postsystemrapport звіт поштової системи thông báo hệ thống thư 邮件系统报告 郵件系統回報 signed message رسالة موقّعة imzalanmış ismarış padpisanaje paviedamleńnie Подписано съобщение missatge signat Podepsaná zpráva neges lofnodwyd signeret meddelelse Signierte Nachricht υπογεγραμμένο μήνυμα signed message pruvita mesaĝo mensaje firmado sinatutako mezua allekirjoitettu viesti undirskrivað boð message signé teachtaireacht sínithe mensaxe firmado הודעה חתומה potpisana poruka aláírt üzenet pesan ditandatangani Messaggio firmato 署名付きメッセージ қолтаңбасы бар мәлімдеме 서명된 메시지 pasirašytas laiškas parakstīta ziņa Mesej ditandatangani signert melding ondertekend bericht signert melding Podpisana wiadomość mensagem assinada Mensagem assinada mesaj semnat подписанное сообщение Podpísaná správa podpisano sporočilo Mesazh i firmosur потписана порука signerat meddelande підписане повідомлення thông điệp đã ký 签名信件 簽署的訊息 stream of data (server push) دفق بيانات (دفع خادم) płyń źviestak (ad servera) Поток от данни, от страна на сървър flux de dades (enviat pel servidor) Proud dat (posílaný serverem) datastrøm (serverskubbet) Datenstrom (Server-Push) χείμαρρος δεδομένων (στελλόμενα από τον εξυπηρετητή) stream of data (server push) datumstrio (puŝata per servilo) flujo de datos (por iniciativa del servidor) datu-korrontea (zerbitzari igortzailea) tietovirta (palvelin työntää) streymur av dáta (ambætara skump) flux de données (émis par le serveur) sruth sonraí (brú freastalaí) fluxo de datos (por iniciativa do servidor) מידע בזרימה (דחיפה ע"י השרת) sugárzott adatfolyam (kiszolgálóról) arus data (dorongan server) Flusso di dati (server push) データストリーム (サーバープッシュ型) мәліметтер ағымы (server push) 데이터 스트림 (서버 푸시) duomenų srautas (iš serverio) datu straume (servera grūsta) Aliran dara (paksaan pelayan) datastrøm (server push) gegevensstroom (server duwt) datastraum (dytta av tenaren) Strumień danych (wymuszenie serwera) fluxo de dados (empurrados pelo servidor) Fluxo de dados (por iniciativa do servidor) flux de date (de la server) поток данных (server push) Prúd dát (posielaný serverom) pretok podatkov (strežniški) Fluks me të dhëna (server push) проток података (гурање са сервера) dataflöde (serverutsändning) потік даних (від сервера) luồng dữ liệu (trình phục vụ đẩy) 数据流(服务器推送) 資料串流 (server push) VCS/ICS calendar سجل VCS/ICS Kalandar VCS/ICS Календар — VCS/ICS calendari VCS/ICS Kalendář VCS/ICS VCS/ICS-kalender VCS/ICS-Kalender ημερολόγιο VCS/ICS VCS/ICS calendar VCS/ICS-kalendaro calendario VCS/ICS VCS/ICS egutegia VCS/ICS-kalenteri VCS/ICS kalendari calendrier VCS/ICS féilire VCS/ICS Calendario VCS/ICS לוח שנה VCS/ICS VCS/ICS kalendar VCS/ICS naptár Kalender VCS/ICS Calendario VCS/ICS VCS/ICS カレンダー VCS/ICS күнтізбесі VCS/ICS 달력 VCS/ICS kalendorius VCS/ICS kalendārs VCS/ICS-kalender VCS/ICS-kalender VCS/ICS-kalender Kalendarz VCS/ICS Calendário VCS/ICS Calendar VCS/ICS календарь VCS/ICS Kalendár VCS/ICS Datoteka koledarja VCS/ICS Kalendar VCS/ICS VCS/ICS-kalender VCS/ICS takvimi календар VCS/ICS Lịch VCS/ICS VCS/ICS 日历 VCS/ICS 行事曆 VCS/ICS vCalendar/iCalendar CSS stylesheet نمط CSS Arkuš stylaŭ CSS Стилове — CSS llista d'estil CSS Styl CSS CSS-stilark CSS-Stilvorlage φύλο στυλ CSS CSS stylesheet CSS-stilfolio hoja de estilo CSS CSS estilo-orria CSS-tyylitiedosto CSS sniðark feuille de style CSS stílbhileog CSS folla de estilos CSS גליון עיצוב CSS CSS stilska tablica CSS stíluslap Lembar gaya CSS Foglio di stile CSS CSS スタイルシート CSS სტილი CSS стильдер кестесі CSS 스타일시트 CSS stiliaus aprašas CSS stilu saraksts CSS-stilark CSS-stijlblad CSS-stilark Arkusz stylów CSS Folha de estilo CSS Pagină de stil CSS таблица стилей CSS Štýly CSS Slogovna predloga CSS Fletë stili CSS CSS-stilmall таблиця стилів CSS Tờ kiểu dáng CSS CSS 样式表 CSS 樣式表 CSS Cascading Style Sheets electronic business card بطاقة أعمال إلكترونية elektronnaja biznes-kartka Електронна визитна картичка targeta de visita electrònica Elektronická navštívenka elektronisk visitkort Elektronische Visitenkarte ηλεκτρονική επαγγελματική κάρτα electronic business card elektronika vizitkarto tarjeta de visita electrónica enpresako txartel elektronikoa sähköinen käyntikortti elektroniskt handilskort carte de visite électronique cárta gnó leictreonach tarxeta de negocio electrónica כרטיס ביקור אלקטרוני elektronička posjetnica elektronikus névjegykártya kartu bisnis elektronik Biglietto da visita elettronico 電子名刺 электронда визит карточкасы 전자 명함 elektroninė vizitinė kortelė elektroniskā biznesa kartiņa elektronisch visitekaartje elektronisk visittkort Wizytówka elektroniczna cartão de visita electrónico Cartão de visitas eletrônico carte de vizită electronică электронная визитная карточка Elektronická vizitka elektronska poslovna vizitka Skedë elektronike biznesi elektroniskt visitkort електронна бізнес-картка danh thiếp điện tử 电子商务卡 電子商務名片 txt2tags document مستند txt2tags dakument txt2tags Документ — txt2tags document txt2tags Dokument txt2tags txt2tags-dokument txt2tags-Dokument αρχείο txt2tags txt2tags document txt2tags-dokumento documento txt2tags txt2tags dokumentua txt2tags-asiakirja txt2tags skjal document txt2tags cáipéis txt2tags documento txt2tags מסמך txt2tags txt2tags dokument txt2tags dokumentum dokumen txt2tags Documento txt2tags txt2tags ドキュメント txt2tags დოკუმენტი txt2tags құжаты txt2tags 문서 txt2tags dokumentas txt2tags dokuments txt2tags-dokument txt2tags-document txt2tags-dokument Dokument txt2tags Documento do txt2tags document txt2tags документ txt2tags Dokument txt2tags Dokument txt2tags Dokument txt2tags txt2tags-dokument документ txt2tags tài liệu txt2tags txt2tags 文档 txt2tags 文件 Verilog source code Изходен код — Verilog codi font en Verilog Zdrojový kód Verilog Verilog-kildekode Verilog-Quellcode πηγαίος κώδικας Verilog Verilog source code Verilog-fontkodo código fuente en Verilog Verilog-lähdekoodi code source Verilog código fonte en Verilog קוד מקור של Verilog izvorni kod Verilog-forráskód Kode sumber Verilog Codice sorgente Verilog Verilog ソースコード Verilog бастапқы коды Verilog 소스 코드 Verilog pirmkods Verilog broncode Kod źródłowy Verilog Código-fonte Verilog исходный код Verilog Zdrojový kód Verilog Datoteka izvorne kode Verilog Verilog-källkod Verilog kaynak kodu вихідний код мовою Verilog Verilog 源代码 Verilog 源碼 SystemVerilog header Заглавен файл — SystemVerilog capçalera de SystemVerilog Záhlaví SystemVerilog SystemVerilog-teksthoved SystemVerilog-Header κεφαλίδα SystemVerilog SystemVerilog header cabeceras de SystemVerilog SystemVerilog-otsake en-tête Cabeceiras de SystemVerilog כותרת SystemVerilog SystemVerilog zaglavlje SystemVerilog fejléc Header SystemVerilog Header SystemVerilog SystemVerilog ヘッダー SystemVerilog тақырыптамасы SystemVerilog 헤더 SystemVerilog galvene SystemVerilog header Nagłówek SystemVerilog Cabeçalho de SystemVerilog заголовочный файл SystemVerilog Hlavičky SystemVerilog Datoteka glave SystemVerilog SystemVerilog-headerfil заголовки SystemVerilog SystemVerilog 头 SystemVerilog 標頭 SystemVerilog source code Изходен код — SystemVerilog codi font en SystemVerilog Zdrojový kód SystemVerilog SystemVerilog-kildekode SystemVerilog-Quellcode πηγαίος κώδικας SystemVerilog SystemVerilog source code código fuente en SystemVerilog SystemVerilog-lähdekoodi code source código fonte en SystemVerilog קוד מקור של SystemVerilog SystemVerilog izvorni kod SystemVerilog-forráskód Kode sumber SystemVerilog Codice sorgente SystemVerilog ソースコード SystemVerilog бастапқы коды SystemVerilog 소스 코드 SystemVerilog pirmkods SystemVerilog broncode Kod źródłowy SystemVerilog Código-fonte de SystemVerilog исходный код SystemVerilog Zdrojový kód SystemVerilog Datoteka izvorne kode SystemVerilog SystemVerilog-källkod вихідний файл мовою SystemVerilog SystemVerilog 源代码 SystemVerilog 源碼 VHDL source code Изходен код — VHDL codi font en VHDL Zdrojový kód VHDL VHDL-kildekode VHDL-Quellcode πηγαίος κώδικας VHDL VHDL source code VHDL-fontkodo código fuente en VHDL VHDL-lähdekoodi code source VHDL código fonte en VHDL קוד מקור של VHDL VHDL izvorni kod VHDL-forráskód Kode sumber VHDL Codice sorgente VHDL VHDL ソースコード VHDL бастапқы коды VHDL 소스 코드 VHDL pirmkods VHDL broncode Kod źródłowy VHDL Código-fonte VHDL исходный код VHDL Zdrojový kód VHDL Datoteka izvorne kode VHDL VHDL-källkod VHDL kaynak kodu вихідний код мовою VHDL VHDL 源代码 VHDL 源碼 VHDL Very-High-Speed Integrated Circuit Hardware Description Language enriched text document مستند نصي مغنى zəngin mətn sənədi azdobleny tekstavy dakument Документ с обогатен текст document de text enriquit Rozšířený textový dokument Dogfen testun wedi ei gyfoethogi beriget tekstdokument Angereichertes Textdokument εγγραφο εμπλουτισμένου κειμένου enriched text document riĉigita teksta dokumento documento de texto enriquecido aberastutako testu dokumentua rikastettu tekstiasiakirja ríkað tekstskjal document texte enrichi cáipéis téacs saibhrithe documento de texto enriquecido מסמך טקסט מועשר obogaćeni tekstualni dokument enriched text dokumentum dokumen teks diperkaya Documento testo arricchito リッチテキストドキュメント пішімделген мәтіндік құжаты 확장된 텍스트 문서 praturtinto teksto dokumentas bagātināta teksta formāts Dokumen teks diperkaya riktekst-dokument verrijkt tekstdocument rik tekst tekstdokument Wzbogacony dokument tekstowy documento de texto rico Documento de texto enriquecido document text îmbogățit форматированный текстовый документ Rozšírený textový dokument dokument z obogatenim besedilom Dokument teksti i pasuruar обогаћени текстуални документ berikat textdokument форматований текстовий документ tài liệu văn bản có kiểu dáng 富文本文档 豐富化文字文件 help page صفحة المساعدة yardım səhifəsi staronka dapamohi Страница от помощта pàgina d'ajuda Stránka nápovědy tudalen gymorth hjælpeside Hilfeseite σελίδα βοήθειας help page help-paĝo página de ayuda laguntzako orria ohjesivu hjálparsíða page d'aide leathanach cabhrach páxina de axuda דף עזרה stranica pomoći súgóoldal halaman bantuan Pagina di aiuto ヘルプページ анықтама парағы 도움말 페이지 žinyno puslapis palīdzības lapa Halaman bantuan hjelpside hulppagina hjelpeside Strona pomocy página de ajuda Página de ajuda pagină de ajutor страница справки Stránka Pomocníka stran pomoči Faqe ndihme страна помоћи hjälpsida yardım sayfası сторінка довідки trang trợ giúp 帮助页面 幫助頁面 plain text document مستند نصي مجرد prosty tekstavy dakument Документ с неформатиран текст document de text pla Prostý textový dokument rent tekstdokument Einfaches Textdokument έγγραφο απλού κειμένου plain text document plata teksta dokumento documento de texto sencillo testu soileko dokumentua perustekstiasiakirja document texte brut cáipéis ghnáth-théacs documento de texto sinxelo מסמך טקסט פשוט običan tekstualni dokument egyszerű szöveg dokumen teks biasa Documento in testo semplice 平文テキストドキュメント мәтіндік құжаты 일반 텍스트 문서 paprastas tekstinis dokumentas vienkāršs teksta dokuments Dokumen teks jernih vanlig tekstdokument plattetekst-document vanleg tekstdokument Zwykły dokument tekstowy documento em texto simples Documento somente texto document text simplu текстовый документ Obyčajný textový dokument običajna besedilna datoteka Dokument në tekst të thjeshtë обичан текстуални документ vanligt textdokument звичайний текстовий документ tài liệu nhập thô 纯文本文档 純文字文件 RDF file ملف RDF Fajł RDF Файл — RDF fitxer RDF Soubor RDF RDF-fil RDF-Datei αρχείο RDF RDF file RDF-dosiero archivo RDF RDF fitxategia RDF-tiedosto RDF fíla fichier RDF comhad RDF ficheiro RDF קובץ RDF RDF datoteka RDF fájl Arsip RDF File RDF RDF ファイル RDF файлы RDF 파일 RDF failas RDF datne RDF-fil RDF-bestand RDF-fil Plik RDF Arquivo RDF Fișier RDF файл RDF Súbor RDF Datoteka RDF File RDF RDF-fil файл RDF Tập tin RDF RDF 文件 RDF 檔 RDF Resource Description Framework email headers ترويسة البريد الإلكتروني epoçt başlıqları paštovyja zahałoŭki Заглавни части на електронни писма capçaleres de correu electrònic Záhlaví e-mailu penawdau e-bost posthoveder E-Mail-Kopfzeilen κεφαλίδες ηλ. μηνυμάτων email headers retpoŝtaj ĉapoj cabeceras de correo electrónico helbide elektronikoen goiburuak sähköpostiotsakkeet t-post tekshøvd en-têtes de courriel ceanntásca ríomhphoist cabeceiras de correo electrónico כותרת דוא"ל zaglavlja e-pošte levélfejléc tajuk email Intestazioni email メールヘッダー пошталық тақырыптамалары 전자 우편 헤더 el. laiško antraštės e-pasta galvene Pengepala emel e-posthode e-mail-kopregels e-post-hovud Nagłówki wiadomości e-mail cabeçalhos de e-mail Cabeçalhos de e-mail antete email почтовые заголовки Hlavičky e-mailu glava elektronske pošte Header email заглавља е-порука e-posthuvuden eposta başlığı заголовки email dòng đầu thư điện tử 电子邮件头 電子郵件標頭 rich text document مستند نصي غني zəngin mətn sənədi azdobleny tekstavy dakument Документ — rich text document de text enriquit Textový dokument RTF dogfen testun gyfoethog (rtf) richtekstdokument RTF-Textdokument έγγραφο εμπλουτισμένου κειμένου (RTF) rich text document riĉteksta dokumento documento de texto enriquecido aberastutako testu formatua RTF-asiakirja document « rich text » cáipéis mhéith-théacs documento do texto enriquecido מסמך טקסט עשיר obogaćeni tekstualni dokument rich text-dokumentum dokumen teks kaya Documento rich text リッチテキストドキュメント пішімделген мәтіні бар құжаты 서식있는 텍스트 문서 praturtinto teksto dokumentas bagātā teksta dokuments Dokumen teks diperkaya rik tekst-dokument opgemaakt tekstdocument rik tekst-dokument Dokument Rich Text documento em texto rico Documento rich text document text îmbogățit документ с форматированным текстом Textový dokument RTF dokument z oblikovanim besedilom Dokument rich text обогаћени текстуални документ RTF-textdokument форматований текстовий документ tài liệu văn bản có kiểu dáng (RTF) RTF 丰富文本文档 豐富文字文件 RSS summary ملخص RSS Karotki ahlad RSS Обобщение за сайтове — RSS resum RSS Souhrn RSS RSS-sammendrag RSS-Zusammenfassung σύνοψη RSS RSS summary resumen RSS RSS laburpena RSS-tiivistelmä RSS samandráttur résumé RSS achoimre RSS Resumo RSS תקציר RSS RSS sažetak RSS összefoglaló Ringkasan RSS Sommario RSS RSS サマリ RSS жинақталғаны RSS 요약 RSS santrauka RSS kopsavilkums RSS-sammendrag RSS-samenvatting RSS-samandrag Podsumowanie RSS Resumo RSS Rezumat RSS сводка RSS Súhrn RSS Datoteka povzetek RSS Përmbledhje RSS RSS-sammanfattning зведення сайту RSS Bản tóm tắt RSS RSS 摘要 RSS 摘要 RSS RDF Site Summary Atom syndication feed مروج تغذية Atom Syndykacyjny kanał navinaŭ Atom Емисия — Atom canal de sindicació Atom Kanál Atom Atom syndication-feed Atom-Nachrichtenquelle Atom syndication feed proveedor de noticias Atom Atom harpidetze-iturria Atom-yhdistevirta fil de syndication Atom fotha sindeacáitithe Atom fonte de sindicación Atom הזנה דרך הרשת של Atom Atom egyesítőfolyam Umpan sindikasi Atom Feed di distribuzione Atom Atom 配信フィード Atom жаңалықтар таспасы Atom 동기화 피드 Atom sindikacijos kanalas Atom sindikāta barotne Atom syndikeringsstrøm Atom-syndicatie-feed Atom-kjelde Kanał Atom Fonte de notícias Atom Flux agregare Atom лента новостей Atom Kanál Atom Sindikalni vir Atom Feed për përhapje Atom Atom-syndikeringskanal трансляція подач Atom Nguồn tin tức Atom Atom 更新种子 Atom 聯合供稿饋流 OPML syndication feed مروج تغذية OPML Syndykacyjny kanał OPML Емисия — OPML canal de sindicació OPML Kanál OPML OPML-syndikeringsfeed OPML-Nachrichtenquelle ροή φορέα OPML OPML syndication feed proveedor de noticias OPML OPML harpidetze-iturria OPML-yhdistevirta fil de syndication OPML fotha sindeacáitithe OPML fonte de sindicación OPML הזנה דרך הרשת OPML OPML egyesítőfolyam Umpan sindikasi OPML Feed di distribuzione OPML OPML 配信フィード OPML жаңалықтар таспасы OPML 묶음 feed OPML sindikacijos kanalas OPML sindikāta barotne OPML syndikeringsstrøm OPML-syndicatie-feed OPML-kjelde Kanał OPML Fonte de notícias OPML Flux OPML syndication лента новостей OPML Kanál OPML Sindikalni vir OPML Feed për përhapje OPML OPML-syndikeringskanal трансляція подач OPML Nguồn tin tức OPML OPML 聚合种子 OPML 聯合供稿饋流 SGML document مستند SGML Dakument SGML Документ — SGML document SGML Dokument SGML Dogfen SGML SGML-dokument SGML-Dokument έγγραφο SGML SGML document SGML-dokumento documento SGML SGML dokumentua SGML-asiakirja SGML skjal document SGML cáipéis SGML documento SGML מסמך SGML SGML dokument SGML-dokumentum Dokumen SGML Documento SGML SGML ドキュメント SGML құжаты SGML 문서 SGML dokumentas SGML dokuments Dokumen SGML SGML-dokument SGML-document SGML-dokument Dokument SGML documento SGML Documento SGML Document SGML документ SGML Dokument SGML Dokument SGML Dokument SGML SGML документ SGML-dokument SGML belgesi документ SGML Tài liệu SGML SGML 文档 SGML 文件 SGML Standard Generalized Markup Language spreadsheet interchange document مستند تبادل الجدول dakument dla abmienu raźlikovymi arkušami Документ за обмяна между програми за електронни таблици document d'intercanvi de full de càlcul Sešitový přenosový dokument regnearksudvekslingsdokument Tabellenkalkulations-Austauschdokument έγγραφο ανταλλαγής λογιστικού φύλλου spreadsheet interchange document documento de intercambio de hojas de cálculo kalkulu-orriak trukatzeko dokumentua taulukkovälitysasiakirja rokniarks umbýtisskjal document d'échange de feuilles de calcul cáipéis idirmhalartaithe scarbhileog documento de intercambio de follas de cálculo מסמך גליון נתונים מתחלף spreadsheet-cserélhetődokumentum dokumen lembar sebar saling tukar Documento di scambio per foglio di calcolo スプレッドシート交換ドキュメント spreadsheet interchange құжаты 스프레드시트 교환 문서 skaičialenčių apsikeitimo dokumentas izklājlapu apmaiņas dokuments dokument for regnearkutveksling rekenblad-uitwisselingsdocument Utvekslingsdokument for rekneark Dokument wymiany arkuszy kalkulacyjnych Documento de intercâmbio de planilhas document schimb filă de calcul документ Spreadsheet Interchange Zošitový prenosový dokument dokument izmenjeve preglednic Dokument shkëmbimi për fletë llogaritje spreadsheet interchange-dokument документ обміну ел. таблицями tài liệu hoán đổi bảng tính 电子表格交换文档 試算表交換文件 TSV document مستند TSV Dakument TSV Документ — TSV document TSV Dokument TSV TSV-dokument TSV-Dokument έγγραφο TSV TSV document documento TSV TSV dokumentua TSV-asiakirja TSV skjal document TSV cáipéis TSV documento TSV מסמך TSV TSV dokument TSV dokumentum Dokumen TSV Documento TSV TSV ドキュメント TSV құжаты TSV 문서 TSV dokumentas TSV dokuments TSV-dokument TSV-document TSV-dokument Dokument TSV Documento TSV Document TSV документ TSV Dokument TSV Dokument TSV Dokument TSV TSV-dokument документ TSV Tài liệu TSV TSV 文档 TSV 文件 TSV Tab Separated Values Graphviz DOT graph مبيان Graphviz DOT Граф — Graphviz DOT gràfic Graphviz DOT Graf Graphviz DOT Graphviz DOT-graf Graphviz-DOT-Graph Γράφημα Graphviz DOT Graphviz DOT graph gráfico Graphviz DOT Graphviz DOT grafikoa Graphviz DOT -graafi Graphviz DOT ritmynd graphe Graphviz DOT graf DOT Graphviz gráfica DOT de Graphviz גרף של Graphviz DOT Graphviz DOT grafikon Graphviz DOT-grafikon Grafik Graphviz DOT Grafico Graphviz DOT Graphviz DOT グラフ Graphviz DOT сызбасы Graphviz DOT 그래프 Graphviz DOT diagrama Graphviz DOT grafiks Graphviz wetenschappelijke grafiek Wykres DOT Graphviz Gráfico do Graphviz DOT Grafic Graphviz DOT Диаграмма Graphviz DOT Graf Graphviz DOT Datoteka grafikona Graphviz DOT Graphviz DOT-graf граф DOT Graphviz Biểu đồ DOT Graphviz Graphviz DOT 科学图形 Graphviz DOT 圖 JAD document مستند JAD Dakument JAD Документ — JAD document JAD Dokument JAD JAD-dokument JAD-Dokument έγγραφο JAD JAD document JAD-dokumento documento JAD JAD dokumentua JAD-asiakirja JAD skjal document JAD cáipéis JAD documento JAD מסמך JAD JAD dokument JAD dokumentum Dokumen JAD Documento JAD JAD ドキュメント JAD құжаты JAD 문서 JAD dokumentas JAD dokuments JAD-dokument JAD-document JAD-dokument Dokument JAD Documento JAD Document JAD документ JAD Dokument JAD Dokument JAD Dokument JAD JAD-dokument JAD belgesi документ JAD Tài liệu JAD JAD 文档 JAD 文件 JAD Java Application Descriptor WML document مستند WML WML sənədi Dakument WML Документ — WML document WML Dokument WML Dogfen WML WML-dokument WML-Dokument έγγραφο WML WML document WML-dokumento documento WML WML dokumentua WML-asiakirja WML skjal document WML cáipéis WML documento WML מסמך WML WML dokument WML-dokumentum Dokumen WML Documento WML WML ドキュメント WML құжаты WML 문서 WML dokumentas WML dokuments Dokumen XML WML-dokument WML-document WML-dokument Dokument WML documento WML Documento WML Document WML документ WML Dokument WML Dokument WML Dokument WML WML документ WML-dokument WML belgesi документ WML Tài liệu WML WML 文档 WML 文件 WML Wireless Markup Language WMLScript program برنامج WMLScript Prahrama WMLScript Програма — WMLScript programa WMLScript Program WMLScript WMLScript-program WMLScript-Programm πρόγραμμα WMLScript WMLScript program programa en WMLScript WMLScript programa WMLScript-ohjelma WMLScript forrit programme WMLScript ríomhchlár WMLScript programa en WMLScript תוכנית של WMLScript WMLScript program WMLScript program Program WMLScript Programma WMLScript WMLScript プログラム WMLScript бағдарламасы WMLScript 프로그램 WMLScript programa WMLScript programma WMLScript-program WMLScript-programma WMLScript-program Pogram WMLScript Programa WMLScript Program WMLScript сценарий WMLScript Program WMLScript Programska datoteka WMLScript Program WMLScript WMLScript-program WMLScript programı програма мовою WMLScript Chương trình WMLScript WMLScript 程序 WMLScript 程式 ACE archive أرشيف ACE Archiŭ ACE Архив — ACE arxiu ACE Archiv ACE ACE-arkiv ACE-Archiv αρχείο ACE ACE archive ACE-arkivo archivador ACE ACE artxiboa ACE-arkisto ACE skjalasavn archive ACE cartlann ACE arquivo ACE ארכיון ACE ACE arhiva ACE archívum Arsip ACE Archivio ACE ACE アーカイブ ACE არქივი ACE архиві ACE 압축 파일 ACE archyvas ACE arhīvs ACE-arkiv ACE-archief ACE-arkiv Archiwum ACE Pacote ACE Arhivă ACE архив ACE Archív ACE Datoteka arhiva ACE Arkiv ACE ACE-arkiv ACE arşivi архів ACE Kho nén ACE ACE 归档文件 ACE 封存檔 Ada source code شفرة مصدر Ada Kryničny kod Ada Изходен код — Ada codi font en Ada Zdrojový kód v Adě Ada-kildekode Ada-Quelltext πηγαίος κώδικας Ada Ada source code Ada-fontkodo código fuente en Ada Ada iturburu-kodea Ada-lähdekoodi Ada keldukota code source Ada cód foinseach Ada código fonte en Ada קוד מקור Ada Ada izvorni kod Ada-forráskód Kode program Ada Codice sorgente Ada Ada ソースコード Ada-ის საწყისი კოდი Ada бастапқы коды Ada 소스 코드 Ada pradinis kodas Ada pirmkods Kod sumber Ada Ada-kildekode Ada-broncode Ada-kjeldekode Kod źródłowy Ada código fonte Ada Código-fonte Ada Cod sursă Ada исходный код Ada Zdrojový kód jazyka Ada Datoteka izvorne kode Ada Kod burues Ada Ада изворни ко̂д Ada-källkod Ada kaynak kodu вихідний код мовою Ada Mã nguồn Ada Ada 源代码 Ada 源碼 author list لائحة المؤلف śpis aŭtaraŭ Списък на авторите llista d'autors Seznam autorů forfatterliste Autorenliste κατάλογος συγγραφέων author list listo de aŭtoroj lista de autores egile-zerrenda tekijäluettelo høvundalisti liste d'auteurs liosta údar lista de autores רשימת יוצרים szerzőlista senarai penulis Elenco autori 著者リスト авторлар тізімі 저자 목록 autorių sąrašas autoru saraksts Senarai penulis forfatterliste auteurslijst forfattarliste Lista autorów lista de autores Lista de autores listă autori список авторов Zoznam autorov seznam avtorjev Lista e autorëve списак аутора författarlista yazar listesi перелік авторів danh sách tác giả 作者列表 作者清單 BibTeX document مستند BibTeX Dakument BibTeX Документ — BibTeX document BibTeX Dokument BibTeX BibTeX-dokument BibTeX-Dokument έγγραφο BibTeX BibTeX document BibTeX-dokumento documento BibTeX BibTeX dokumentua BibTeX-asiakirja BibTeX skjal document BibTeX cáipéis BibTeX documento BibTex מסמך BibTeX BibTeX dokument BibTeX dokumentum Dokumen BibTeX Documento BibTeX BibTeX ドキュメント BibTeX-ის დოკუმენტი BibTeX құжаты BibTeX 문서 BibTeX dokumentas BibTeX dokuments BibTeX-dokument BibTeX-document BibTeX-dokument Dokument BibTeX Documento BibTeX Document BibTeX документ BibTeX Dokument BibTeX Dokument BibTeX Dokument BibTeX BibTeX-dokument BibTeX belgesi документ BibTeX Tài liệu BibTeX BibTeX 文档 BibTeX 文件 C++ header ترويسة سي++ Zahałoŭny fajł C++ Заглавен файл — C++ capçalera en C++ Záhlaví v C++ C++-posthoved C++-Header κεφαλίδα C++ C++ header cabecera de código fuente en C++ C++ goiburua C++-otsake C++ tekshøvd en-tête C++ ceanntásc C++ cabeceira de código fonte en C++ כותר ++C C++ zaglavlje C++ fejléc Tajuk C++ Header C++ C++ ヘッダー C++-ის თავსართი C++ тақырыптама файлы C++ 헤더 C++ antraštė C++ galvene C++-kildekodeheader C++-header C++-kjeldekode-hovud Plik nagłówkowy C++ Cabeçalho C++ Antet C++ заголовочный файл C++ Hlavičky jazyka C++ Datoteka glave C++ Header C++ C++-huvud файл заголовків мовою C++ Phần đầu mã nguồn C++ C++ 源代码头文件 C++ 標頭檔 C++ source code شفرة مصدر سي++ Kryničny kod C++ Изходен код — C++ codi font en C++ Zdrojový kód v C++ C++-kildekode C++-Quelltext πηγαίος κώδικας C++ C++ source code C++-fontkodo código fuente en C++ C++ iturburu-kodea C++-lähdekoodi C++ keldukota code source C++ cód foinseach C++ código fonte de C++ קוד מקור של C++ C++ izvorni kod C++-forráskód Kode program C++ Codice sorgente C++ C++ ソースコード C++-ის საწყისი კოდი C++ бастапқы коды C++ 소스 코드 C++ pradinis kodas C++ pirmkods Kod sumber C++ C++-kildekode C++-broncode C++-kjeldekode Kod źródłowy C++ código fonte C++ Código-fonte C++ Cod sursă C++ исходный код C++ Zdrojový kód jazyka C++ Datoteka izvorne kode C++ Kod burues C++ C++ изворни ко̂д C++-källkod вихідний код мовою C++ Mã nguồn C++ C++ 源代码 C++ 源碼 ChangeLog document مستند ChangeLog Dakument zafiksavanych źmienaŭ ChangeLog Дневник за промени — ChangeLog document de registre de canvis Dokument ChangeLog ChangeLot-dokument Änderungsprotokoll έγγραφο ChangeLog ChangeLog document documento de cambios ChangeLog dokumentua Muutoslokiasiakirja ChangeLog skjal document ChangeLog cáipéis ChangeLog documento Changelog מסמך של ChangeLog ChangeLog dokumentum Dokumen ChangeLog Documento ChangeLog ChangeLog ドキュメント ChangeLog დოკუმენტი ChangeLog құжаты ChangeLog 문서 ChangeLog dokumentas ChangeLog dokuments ChangeLog-dokument ChangeLog-document ChangeLog-dokument Dokument zmian (ChangeLog) Documento ChangeLog Document ChangeLog протокол изменений Dokument ChangeLog Dokument ChangeLog Dokument ChangeLog Ändringsloggsdokument документ ChangeLog Tài liệu ChangeLog (ghi lưu thay đổi) 变更日志文档 ChangeLog 文件 C header ترويسة C Zahałoŭny fajł C Заглавен файл — C capçalera en C Záhlaví v C C-posthoved C-Header κεφαλίδα C C header cabecera de código fuente en C C goiburua C-otsake C tekshøvd en-tête C ceanntásc C cabeceira de códifo fonte de C כותר C C zaglavlje C fejléc Tajuk C Header C C ヘッダー C-ის თავსართი C тақырыптама файлы C 헤더 C antraštė C galvene C-kildekodeheader C-header C-kjeldekode-hovud Plik nagłówkowy C Cabeçalho C Antet C заголовочный файл C Hlavičky jazyka C Datoteka glave C Header C C-huvud C başlığı файл заголовків мовою C Phần đầu mã nguồn C C 程序头文件 C 標頭檔 CMake source code شفرة مصدر CMake Kryničny kod CMake Изходен код — CMake codi font en CMake Zdrojový kód CMake CMake-kildekode CMake-Quelltext πηγαίος κώδικας CMake CMake source code CMake-fontkodo código fuente en CMake CMake iturburu-kodea CMake-lähdekoodi CMake keldukota code source CMake cód foinseach CMake código fonte de CMake קוד מקור של CMake CMake izvorni kod CMake-forráskód Kode program CMake Codice sorgente CMake CMake ソースコード CMake-ის საწყისი კოდი CMake бастапқы коды CMake 소스 코드 CMake pirminis tekstas CMake pirmkods CMake-kildekode CMake-broncode CMake-kjeldekode Kod źródłowy CMake Código-fonte CMake Cod sursă CMake исходный код CMake Zdrojový kód CMake Datoteka izvorne kode CMake Kod burues CMake CMake-källkod вихідний код CMake Mã nguồn CMake CMake 源代码 CMake 源碼 CSV document مستند CSV Dakument CSV Документ — CSV document CSV Dokument CSV CSV-dokument CSV-Dokument αρχείο CSV CSV document CSV-dokumento documento CSV CSV dokumentua CSV-asiakirja CSV skjal document CSV cáipéis CSV documento CSV מסמך CSV CSV dokument CSV dokumentum Dokumen CSV Documento CSV CSV ドキュメント CSV დოკუმენტი CSV құжаты CSV 문서 CSV dokumentas CSV dokuments CSV-dokument CSV-document CSV-dokument Dokument CSV Documento CSV Document CSV документ CSV Dokument CSV Dokument CSV Dokument CSV CSV-dokument документ CSV Tài liệu CSV CSV 文档 CSV 文件 CSV Comma Separated Values license terms شروط الترخيص licenzijnyja ŭmovy Лицензни условия condicions de llicència Licenční podmínky licensbetingelser Lizenzbedingungen όροι άδειας licence terms términos de licencia lizentzia baldintzak lisenssiehdot loyvistreytir termes de licence téarmaí ceadúnais termos de licenza תנאי רישיון uvjeti licence licencfeltételek persyaratan lisensi Termini di licenza ソフトウェアライセンス条項 лицензиялық келісімі 라이선스 조항 licencijos sąlygos licences nosacījumi lisensbestemmelser licentievoorwaarden lisensvilkår Warunki licencji Termos de licença termeni de licență лицензионное соглашение Licenčné podmienky pogoji in dovoljenja uporabe Kushte liçence licensvillkor ліцензійні умови điều kiện giấy phép 软件许可条款 授權條款 author credits شكر وتقدير المؤلف zasłuhi aŭtara Благодарности към авторите atribucions d'autor Autorské zásluhy bidragydere Autorendanksagung author credits reconocimiento de autoría tekijöiden kiitokset høvundaheiður remerciements admhálacha údar créditos de autor קרדיטים של היוצר szerzők listája kredit penulis Riconoscimenti autori ソフトウェア作者クレジット бағдарлама авторлары 저작자 크레딧 padėkos autoriams veidotāji liste med bidragsytere auteursinformatie forfattarliste Podziękowania autorów programu Créditos do autor mulțumiri autori авторы программы Autorské zásluhy avtorske zasluge Kreditë e autorëve författarlista подяки авторам програми công trạng tác giả 软件作者致谢 作者致謝名單 C source code شفرة مصدر سي Kryničny kod C Изходен код — C codi font en C Zdrojový kód v C C-kildekode C-Quelltext πηγαίος κώδικας C C source code C-fontkodo código fuente en C C iturburu-kodea C-lähdekoodi C keldukota code source C cód foinseach C código fonte en C קוד מקור של C C izvorni kod C-forráskód Kode program C Codice sorgente C C ソースコード C-ის საწყისი კოდი C бастапқы коды C 소스 코드 C pradinis kodas C pirmkods Kod sumber C C-kildekode C-broncode C-kjeldekode Kod źródłowy C código fonte C Código-fonte C Cod sursă C исходный код C Zdrojový kód jazyka C Datoteka izvorne kode C Kod burues C C изворни ко̂д C-källkod C kaynak kodu вихідний код мовою C Mã nguồn C C 源代码 C 源碼 C# source code شفرة مصدر سي# Kryničny kod C# Изходен код — C# codi font en C# Zdrojový kód v C# C#-kildekode C#-Quelltext πηγαίος κώδικας C# C# source code C#-fontkodo código fuente en C# C# iturburu-kodea C#-lähdekoodi C# keldukota code source C# cód foinseach C# código fonte en C# קוד מקור של C# C# izvorni kod C#-forráskód Kode program C# Codice sorgente C# C# ソースコード C#-ის საწყისი კოდი C# бастапқы коды C# 소스 코드 C# pradinis kodas C# pirmkods Kod sumber C# C#-kildekode C#-broncode C#-kjeldekode Kod źródłowy C# código fonte C# Código-fonte C# Cod sursă C# исходный код C# Zdrojový kód jazyka C# Datoteka izvorne kode C# Kod burues C# C# изворни ко̂д C#-källkod C# kaynak kodu вихідний код мовою C# Mã nguồn C# C# 源代码 C# 源碼 Vala source code شفرة مصدر Vala Kryničny kod Vala Изходен код — Vala codi font en Vala Zdrojový kód Vala Valakildekode Vala-Quelltext πηγαίος κώδικας Vala Vala source code Vala-fontkodo código fuente en Vala Vala iturburu-kodea Vala-lähdekoodi Vala keldukota code source Vala cód foinseach Vala código fonte en Vala קוד מקור של Vala Vala izvorni kod Vala forráskód Kode program Vala Codice sorgente Vala Vala ソースコード Vala бастапқы коды Vala 소스 코드 Vala pradinis kodas Vala pirmkods Vala-kildekode Vala-broncode Vala-kjeldekode Kod źródłowy Vala Código-fonte Vala Cod sursă Vala исходный код Vala Zdrojový kód Vala Datoteka izvorne kode Vala Kod burues Vala Vala-källkod Vala kaynak kodu вихідний код мовою Vala Mã nguồn Vala Vala 源代码 Vala 源碼 OOC source code Изходен код — OOC codi font en OOC Zdrojový kód OOC OOC-kildekode OOC-Quellcode πηγαίος κώδικας OOC OOC source code OOC-fontkodo Código fuente en OOC OOC-lähdekoodi source code OOC código fonte de OOC קוד מקור של OOC OOC izvorni kod OOC forráskód Kode sumber OOC Codice sorgente OOC OOC ソースコード OOC-ის საწყისი კოდი OOC бастапқы коды OOC 소스 코드 OOC pirmkods OOC broncode Kod źródłowy OOC Código-fonte OOC исходный код OOC Izvorna koda OOC OOC-källkod вихідний код мовою OOC OOC OOC 源碼 OOC Out Of Class DCL script سكربت DCL DCL skripti Skrypt DCL Скрипт — DCL script DCL Skript DCL Sgript DCL DCL-program DCL-Skript πρόγραμμα εντολών DCL DCL script DCL-skripto script en DCL DCL script-a DCL-komentotiedosto DCL boðrøð script DCL script DCL script de DCL תסריט DCL DCL skripta DCL-parancsfájl Skrip DCL Script DCL DCL スクリプト DCL სცენარი DCL сценарийі DCL 스크립트 DCL scenarijus DCL skripts Skrip DCL DCL-skript DCL-script DCL-skript Skrypt DCL 'script' DCL Script DCL Script DCL сценарий DCL Skript DCL Skriptna datoteka DCL Script DCL DCL скрипта DCL-skript DCL betiği скрипт DCL Văn lệnh DCL DCL 脚本 DCL 指令稿 DCL Data Conversion Laboratory DSSSL document مستند DSSSL DSSSL sənədi Dakument DSSSL Документ — DSSSL document DSSSL Dokument DSSSL Dogfen DSSSL DSSSL-dokument DSSSL-Dokument έγγραφο DSSSL DSSSL document DSSSL-dokumento documento DSSSL DSSSL dokumentua DSSSL-asiakirja DSSSL skjal document DSSSL cáipéis DSSSL documento DSSSL מסמך DSSSL DSSSL dokument DSSSL-dokumentum Dokumen DSSSL Documento DSSSL DSSSL ドキュメント DSSSL დოკუმენტი DSSSL құжаты DSSSL 문서 DSSSL dokumentas DSSSL dokuments Dokumen DSSSL DSSSL-dokument DSSSL-document DSSSL-dokument Dokument DSSSL documento DSSSL Documento DSSSL Document DSSSL документ DSSSL Dokument DSSSL Dokument DSSSL Dokument DSSSL DSSSL документ DSSSL-dokument DSSSL belgesi документ DSSSL Tài liệu DSSSL DSSSL 文档 DSSSL 文件 DSSSL Document Style Semantics and Specification Language D source code شفرة مصدر D Kryničny kod D Изходен код — D codi font en D Zdrojový kód v D D-kildekode D-Quelltext πηγαίος κώδικας D D source code D-fontkodo código fuente en D D iturburu-kodea D-lähdekoodi D keldukota code source D cód foinseach D código fonte de D קוד מקור לשפת D D izvorni kod D-forráskód Kode program D Codice sorgente D D ソースコード D-ის საწყისი კოდი D бастапқы коды D 소스 코드 D pradinis kodas D pirmkods D-kildekode D-broncode D-kjeldekode Kod źródłowy D Código-fonte D Cod sursă D исходный код D Zdrojový kód jazyka D Datoteka izvorne kode D Kod burues D D изворни ко̂д D-källkod вихідний код мовою D Mã nguồn D D 源代码 D 源碼 DTD file ملف DTD Fajł DTD Документ — DTD fitxer DTD Soubor DTD DTD-fil DTD-Datei αρχείο DTD DTD file DTD-dosiero archivo DTD DTD fitxategia DTD-tiedosto DTD fíla fichier DTD comhad DTD ficheiro DTD מסמך DTD DTD datoteka DTD fájl Berkas DTD File DTD DTD ファイル DTD ფაილი DTD файлы DTD 파일 DTD failas DTD datne DTD-fil DTD-bestand DTD-fil Plik DTD Arquivo DTD Fișier DTD файл DTD Súbor DTD Datoteka DTD File DTD DTD-fil DTD dosyası файл DTD Tập tin DTD DTD 文件 DTD 檔 DTD Document Type Definition Eiffel source code شفرة مصدر Eiffel Kryničny kod Eiffel Изходен код — Eiffel codi font en Eiffel Zdrojový kód Eiffel Eiffelkildekode Eiffel-Quelltext πηγαίος κώδικας Eiffel Eiffel source code Eiffel-fontkodo código fuente en Eiffel Eiffel iturburu-kodea Eiffel-lähdekoodi Eiffel keldukota code source Eiffel cód foinseach Eiffel código fone de Eiffel קוד מקור של Eiffel Eiffel izvorni kod Eiffel forráskód Kode program Eiffel Codice sorgente Eiffel Eiffel ソースコード Eiffel-ის საწყისი კოდი Eiffel бастапқы коды Eiffel 소스 코드 Eiffel pirminis programos tekstas Eiffel pirmkods Eiffel-kildekode Eiffel-broncode Eiffel-kjeldekode Kod źródłowy Eiffel Código-fonte Eiffel Cod sursă Eiffel исходный код Eiffel Zdrojový kód Eiffel Datoteka izvorne kode Eiffel Kod burues Eiffel Eiffel-källkod Eiffel kaynak kodu вихідний код мовою Eiffel Mã nguồn Eiffel Eiffel 源代码 Eiffel 源碼 Emacs Lisp source code شفرة مصدر Emacs Lisp Emacs Lisp mənbə kodu Kryničny kod Emacs Lisp Изходен код — Emacs Lisp codi font en Emacs Lisp Zdrojový kód Emacs Lisp Ffynhonnell rhaglen EMACS LISP Emacs Lisp-kildekode Emacs-Lisp-Quelltext πηγαίος κώδικας Emacs Lisp Emacs Lisp source code fontkodo en Emacs Lisp código fuente en Lisp de Emacs Emacs Lisp iturburu-kodea Emacs Lisp -lähdekoodi Emacs Lisp keldukota code source Emacs Lisp cód foinseach Emacs Lisp código fonte de Emacs Lisp קוד מקור של Emcas Lisp Emacs Lisp izvorni kod Emacs Lisp-forráskód Kode sumber Emacs Lisp Codice sorgente Emacs Lisp Emacs Lisp ソースコード Emacs-ის Lisp საწყისი კოდი Emacs Lisp бастапқы коды Emacs Lisp 소스 코드 Emacs Lisp pradinis kodas Emacs Lisp pirmkods Kod sumber Emacs Lisp Emacs Lisp-kildekode Emacs Lisp-broncode Emacs Lisp kjeldekode Plik źródłowy Emacs Lisp código fonte Emacs Lisp Código-fonte Lisp do Emacs Cod sursă Emacs Lisp исходный код Emacs Lisp Zdrojový kód Emacs Lisp Datoteka izvorne kode Emacs Lisp Kod burues Emacs Lisp Емакс Лисп изворни ко̂д Emacs Lisp-källkod Emacs Lisp kaynak kodu вихідний код мовою Emacs Lisp Mã nguồn Lisp Emacs Emacs Lisp 源代码 Emacs Lisp 源碼 Erlang source code شفرة مصدر Erlang Kryničny kod Erlang Изходен код — Erlang codi font en Erlang Zdrojový kód Erlang Erlangkildekode Erlang-Quelltext πηγαίος κώδικας Erlang Erlang source code Erlang-fontkodo código fuente en Erlang Erlang iturburu-kodea Erlang-lähdekoodi Erlang keldukota code source Erlang cód foinseach Erlang código fonte de Erlang קוד מקור של Erlang Erlang izvorni kod Erlang forráskód Kode program Erlang Codice sorgente Erlang Erlang ソースコード Erlang-ის საწყისი კოდი Erlang бастапқы коды Erlang 소스 코드 Erlang pradinis kodas Erlang pirmkods Erlang-kildekode Erlang-broncode Erlang-kjeldekode Kod źródłowy Erlang Código-fonte Erlang Cod sursă Erlang исходный код Erlang Zdrojový kód Erlang Datoteka izvorne kode Erlang Kod burues Erlang Erlang-källkod Erlang kaynak kodu вихідний код мовою Erlang Mã nguồn Erlang Erlang 源代码 Erlang 源碼 Fortran source code شفرة مصدر Fortran Fortran mənbə kodu Kryničny kod Fortran Изходен код — Fortran codi font en Fortran Zdrojový kód Fortran Ffynhonnell rhaglen FORTRAN Fortrankildekode Fortran-Quelltext πηγαίος κώδικας Fortran Fortran source code Fotran-fontkodo código fuente en Fortran Fortran-en iturburu-kodea Fortran-lähdekoodi Fortran keldukota code source Fortran cód foinseach Fortran código fonte de Fortran קוד מקור של Fortran Fortran izvorni kod Fortran-forráskód Kode program Fortran Codice sorgente Fortran Fortran ソースコード Fortran-ის საწყისი კოდი Fortran бастапқы коды 포트란 소스 코드 Fortran pradinis kodas Fortran pirmkods kod sumber Fortran Fortran-kildekode Fortran-broncode Fortran-kjeldekode Kod źródłowy Fortran código fonte Fortran Código-fonte Fortran Cod sursă Fortran исходный код Fortran Zdrojový kód Fortran Datoteka izvorne kode Fortran Kod burues Fortran Фортран изворни ко̂д Fortran-källkod Fortran kaynak kodu вихідний код мовою Fortran Mã nguồn Fortran Fortran 源代码 Fortran 源碼 translation file ملف الترجمة fajł pierakładu Превод fitxer traducció Soubor překladu oversættelsesfil Übersetzungsdatei αρχείο μετάφρασης translation file tradukad-dosiero archivo de traducción itzulpen-fitxategia käännöstiedosto týðingarfíla fichier de traduction comhad aistrithe ficheiro de tradución קובץ תרגום datoteka prijevoda fordítási fájl berkas terjemahan File traduzione 翻訳ファイル თარგმნის ფაილი аудармалар файлы 번역 파일 vertimo failas tulkošanas datne oversettelsesfil vertalingsbestand omsetjingsfil Plik tłumaczenia Arquivo de tradução fișier traducere файл переводов Súbor prekladu datoteka prevoda programa File përkthimesh översättningsfil файл перекладу tập tin dịch 消息翻译文件 翻譯檔 translation template قالب الترجمة šablon dla pierakładu Шаблон за преводи plantilla de traducció Šablona překladu oversættelsesskabelon Übersetzungsvorlage πρότυπο μετάφρασης translation template tradukad-ŝablono plantilla de traducción itzulpenen txantiloia käännösmalli týðingarformur modèle de traduction teimpléad aistrithe plantilla de tradución תבנית תרגום predložak prijevoda fordítási sablon templat terjemahan Modello di traduzione 翻訳テンプレート თარგმნის შაბლონი аудармалар үлгісі 메시지 번역 서식 vertimo šablonas tulkošanas veidne mal for oversetting vertalingssjabloon omsetjingsmal Szablon tłumaczenia Modelo de tradução șablon de traducere шаблон переводов Šablóna prekladu predloga datoteke prevoda programa Model përkthimesh översättningsmall шаблон перекладу mẫu dịch 消息翻译模板 翻譯模版 HTML document مستند HTML Dakument HTML Документ — HTML document HTML Dokument HTML HTML-dokument HTML-Dokument έγγραφο HTML HTML document HTML-dokumento documento HTML HTML dokumentua HTML-asiakirja HTML skjal document HTML cáipéis HTML documento HTML מסמך HTML HTML dokument HTML dokumentum Dokumen HTML Documento HTML HTML ドキュメント HTML құжаты HTML 문서 HTML dokumentas HTML dokuments HTML-dokument HTML-document HTML-dokument Dokument HTML Documento HTML Document HTML документ HTML Dokument HTML Dokument HTML Dokument HTML HTML-dokument документ HTML Tài liệu HTML HTML 文档 HTML 文件 HTML HyperText Markup Language Web application cache manifest قائمة التخزين الموقت لتطبيق الويب Манифест за кеша на уеб приложение manifest de memòria cau d'aplicació Web Manifest mezipaměti webové aplikace Manifest for internetprogrammellemlager Webanwendungscache-Manifest Web application cache manifest manifiesto de caché de aplicación web Web aplikazioaren cache-aren agiria Net nýtsluskipanarkova manifest manifeste de cache d'application Web lastliosta taisce d'fheidhmchlár Gréasáin manifesto de caché de aplicativo web הצהרה של מטמון של תוכנית ברשת Webalkalmazás gyorsítótár-összefoglalója Manifes singgahan aplikasi web Manifesto cache applicazione Web Web アプリケーションキャッシュ manifest Веб қолданбасының кэш манифесті 웹 애플리케이션 캐시 정의 Žiniatinklio programos podėlio manifestas Tīmekļa lietotņu keša manifests Webapplicatie cache manifest Manifest pamięci podręcznej aplikacji WWW Manifest de cache de aplicação web Manifest de cache pentru aplicații web манифест кэша веб-приложения Predpomnilnik spletnega programa Cachemanifest för webbapplikation маніфест кешу веб-програми 网络应用程序缓存清单 網頁應用程式快取聲明 Google Video Pointer مؤشر فيديو جوجل Pakazalnik Google Video Документ-указател към видео на Google apuntador a vídeo de Google Google Video Pointer Google Video-peger Google Video Pointer Google Video Pointer lista de reproducción de Google Video (GVP) Google Video-ren erreprodukzio-zerrenda Google-video-osoitin Google Video Pointer pointeur vidéo Google pointeoir Google Video punteiro de vídeo de Google מצביע וידאו של Google Google Video Pointer Google Video Pointer Puntatore Google Video Google ビデオポインター Google Video Pointer 구글 비디오 포인터 Google Video Pointer Google Video Pointer Peker til Google Video Google-videoverwijzing Google Video-peikar Lista odtwarzania Google Video Ponteiro do Google Vídeo Indicator Google Video Google Video Pointer Google Video Pointer Kazalec Google Video Puntues Google Video Google Video-pekare вказівник відео Google Con trỏ ảnh động Google Google 视频指向 Google Video Pointer Haskell source code شفرة مصدر Haskell Haskell mənbə kodu Kryničny kod Haskell Изходен код на Haskell codi font en Haskell Zdrojový kód Haskell Ffynhonnell rhaglen Haskell Haskellkildekode Haskell-Quelltext πηγαίος κώδικας Haskell Haskell source code Haskell-fontkodo código fuente en Haskell Haskell iturburu-kodea Haskell-lähdekoodi Haskell keldukota code source Haskell cód foinseach Haskell código fonte de Haskell קוד מקור של Haskell Haskell izvorni kod Haskell-forráskód Kode program Haskell Codice sorgente Haskell Haskell ソースコード Haskell бастапқы коды Haskell 소스 코드 Haskell pradinis kodas Haskell pirmkods Kod sumber Haskell Haskell-kildekode Haskell-broncode Haskell-kjeldekode Kod źródłowy Haskell código fonte Haskell Código-fonte Haskell Cod sursă Haskell исходный код Haskell Zdrojový kód Haskell Datoteka izvorne kode Haskell Kod burues Haskell Haskell изворни ко̂д Haskell-källkod вихідний код мовою Haskell Mã nguồn Haskell Haskell 源代码 Haskell 源碼 IDL document مستند IDL IDL sənədi Dakument IDL Документ — IDL document IDL Dokument IDL Dogfen IDL IDL-dokument IDL-Dokument έγγραφο IDL IDL document IDL-dokumento documento IDL IDL dokumentua IDL-asiakirja IDL skjal document IDL cáipéis IDL documento IDL מסמך IDL IDL dokument IDL-dokumentum Dokumen IDL Documento IDL IDL ドキュメント IDL құжаты IDL 문서 IDL dokumentas IDL dokuments Dokumen IDL IDL-dokument IDL-document IDL-dokument Dokument IDL documento IDL Documento IDL Document IDL документ IDL Dokument IDL Dokument IDL Dokument IDL IDL документ IDL-dokument документ IDL Tài liệu IDL IDL 文档 IDL 文件 IDL Interface Definition Language installation instructions تعليمات التثبيت instrukcyja dla instalavańnia Инструкции за инсталация instruccions d'instal·lació Návod k instalaci installationsinstruktioner Installationsanleitung οδηγίες εγκατάστασης installation instructions instrucciones de instalación instalazioaren instrukzioak asennusohjeet innleggingar vegleiðing instructions d'installation treoracha suiteála instrucións de instalación הוראות התקנה upute za instalaciju telepítési utasítások instruksi instalasi Istruzioni di installazione ソフトウェアインストール説明 бағдарламаны орнату нұсқаулары 설치 방법 diegimo instrukcijos instalācijas instrukcijas installationsinstruksjoner installatie-instructies installasjonsinstruksjonar Instrukcje instalacji Instruções de instalação instrucțiuni de instalare инструкции по установке программы Návod na inštaláciu navodila namestitve Udhëzime instalimi installationsinstruktioner інструкції з встановлення hướng dẫn cài đặt 软件安装指南 安裝指引 Java source code شفرة مصدر Java Kryničny kod Java Изходен код на Java codi font en Java Zdrojový kód v Javě Javakildekode Java-Quelltext πηγαίος κώδικας Java Java source code Java-fontkodo código fuente en Java Java iturburu-kodea Java-lähdekoodi Java keldukota code source Java cód foinseach Java código fonte de Java קוד מקור ב־Java Java izvorni kod Java-forráskód Kode program Java Codice sorgente Java Java ソースコード Java бастапқы коды 자바 소스 코드 Java pradinis kodas Java pirmkods Kod sumber Java Java-kildekode Java-broncode Java-kjeldekode Kod źródłowy Java código fonte Java Código-fonte Java Cod sursă Java исходный код Java Zdrojový kód Java Datoteka izvorne kode Java Kod burues Java Јава изворни ко̂д Java-källkod Java kaynak kodu вихідний код мовою Java Mã nguồn Java Java 源代码 Java 源碼 LDIF address book دفتر عناوين LDIF Adrasnaja kniha LDIF Адресна книга — LDIF llibreta d'adreces LDIF Adresář LDIF LDIF-adressebog LDIF-Adressbuch διευθυνσιολόγιο LDIF LDIF address book LDIF-adresaro libreta de direcciones LDIF LDIF helbide-liburua LDIF-osoitekirja LDIF adressubók carnet d'adresses LDIF leabhar sheoltaí LDIF lista de enderezos LDIF ספר כתובות של LDIF LDIF adresar LDIF címjegyzék Buku alamat LDIF Rubrica LDIF LDIF アドレス帳 LDIF адрестер кітабы LDIF 주소록 LDIF adresų knygelė LDIF adrešu grāmata LDIF-adressebok LDIF-adresboek LDIF-adressebok Książka adresowa LDIF Livro de endereços LDIF Agendă LDIF адресная книга LDIF Adresár LDIF Datoteka imenika naslovov LDIF Rubrikë LDIF LDIF-adressbok LDIF adres defteri адресна книга LDIF Sổ địa chỉ LDIF LDIF 地址簿 LDIF 通訊錄 LDIF LDAP Data Interchange Format Lilypond music sheet صفحة موسيقى Lilypond Muzyčny arkuš Lilypond Нотация на Lilypond full de música Lilypond Notový papír Lilypond Lilypondmusikkort Lilypond-Notenblatt παρτιτούρα Lilypond Lilypond music sheet partitura de LilyPond Lilypond musika-orria Lilypond-nuotit Lilypond tónleika ark partition musicale Lilypond bileog cheoil Lilypond folla de música de Lilypond דף מוזיקה של Lilypond Lilypond kotta Lembar musik Lilypond Partitura Lilypond Lilypond 楽譜データ Lilypond музыка тізімі Lilypond 악보 Lilypond muzikos lapas Lilypond mūzikas lapa Lilypond-muziekblad Lilypond noteark Plik partytury Lilypond Partitura do Lilypond Fișă muzică Lilypond список музыки Lilypond Notový papier Lilypond Glasbena predloga Lilypond Partiturë Lilypond Lilypond-notblad нотний запис Lilypond Bản nhạc Lilypond Lilypond 乐谱 Lilypond 樂譜 LHS source code شفرة مصدر LHS Kryničny kod LHS Изходен код на LHS codi font en LHS Zdrojový kód v LHS LHS-kildekode LHS-Quelltext πηγαίος κώδικας LHS LHS source code LHS-fontkodo código fuente en LHS LHS iturburu-kodea LHS-lähdekoodi LHS keld code source LHS cód foinseach LHS código fonte en LHS קוד מקור של LHS LHS izvorni kod LHS forráskód Kode program LHS Codice sorgente LHS LHS ソースコード LHS бастапқы коды LHS 소스 코드 LHS pradinis kodas LHS pirmkods LHS-kildekode LHS-broncode LHS-kjeldekode Kod źródłowy LHS Código-fonte LHS Cod sursă LHS исходный код LHS Zdrojový kód LHS Datoteka izvorne kode LHS Kod burues LHS LHS-källkod LHS kaynak kodu вихідний код LHS Mã nguồn LHS LHS 源代码 LHS 源碼 LHS Literate Haskell source code application log سجل التطبيق časopis aplikacyi Файл-дневник на приложение registre d'aplicació Záznam aplikace programlog Anwendungsprotokoll ημερολόγιο συμβάντων εφαρμογής application log protokolo de aplikaĵo registro de aplicación aplikazio egunkaria sovelluksen lokitiedosto nýtsluskipan logg journal d'application logchomhad feidhmchláir rexistro de aplicativo יומן התוכנה alkalmazás naplója log aplikasi Log applicazione アプリケーションログ мәлімдемелер журналы 프로그램 기록 programos žurnalas lietotnes žurnāls Log aplikasi applikasjonslogg programma-logbestand programlogg Dziennik programu registo de aplicação Registro de aplicativo înregistrare aplicație журнал сообщений Záznam aplikácie dnevnik programa log i mesazheve të programit дневник програма programlogg uygulama günlüğü журнал програми bản ghi ứng dụng 应用程序日志 程式紀錄檔 Makefile ملف Makefile İnşa faylı Makefile Файл — make Makefile Makefile Ffeil "make" Bygningsfil Makefile Makefile Makefile Muntodosiero Makefile Makefile Makefile Makefile makefile Makefile Makefile Makefile Makefile Makefile Makefile Makefile Makefile Makefile (жинау файлы) Makefile Makefile Makefile Makefile Makefile Makefile Makefile Plik make Makefile Makefile (arquivo do make) Makefile Makefile (файл сборки) Makefile Datoteka Makefile Makefile Производна датотека Makefil файл проекту make Tập tin tạo ứng dụng (Makefile) Makefile Makefile Markdown document Документ — Markdown document Markdown Dokument Markdown Markdown-dokument Markdown-Dokument Markdown document Documento de Markdown Markdown-asiakirja document Markdown documento de Markdown מסמך Markdown Markdown dokument Markdown dokumentum Dokumen markdown Documento Markdown Markdown Markdown құжаты 마크다운 문서 Markdown dokuments Markdown document Dokument Markdown Documento Markdown документ Markdown Dokument Markdown документ Markdown Markdown 文档 Markdown 文件 Qt MOC file ملف Qt MOC Fajł Qt MOC Файл — Qt MOC fitxer MOC de Qt Soubor Qt MOC Qt MOC-fil Qt-MOC-Datei αρχείο Qt MOC Qt MOC file archivo MOC Qt Qt MOC fitxategia Qt MOC -tiedosto Qt MOC fíla fichier Qt MOC comhad MOC Qt ficheiro MOC Qt קובץ Qt MOC Qt MOC datoteka Qt MOC fájl Berkas Qt MOC File MOC Qt Qt MOC ファイル Qt MOC файлы Qt MOC 파일 Qt MOC failas Qt MOC datne Qt MOC-fil Qt MOC-bestand Qt MOC-fil Plik Qt MOC Arquivo Qt MOC Fișier Qt MOC файл Qt MOC Súbor Qt MOC Datoteka Qt MOC File Qt MOC Qt MOC-fil файл-метаоб'єкт Qt Tập tin MOC của Qt Qt 元对象编译文件 Qt MOC 檔 Qt MOC Qt Meta Object Compiler Windows Registry extract استخراج مسجل ويندوز Element rehistru Windows Извадка от регистъра на Windows extracte del registre de Windows Část registrů Windows Windows Registy-udtrækning Windows-Registry-Auszug αποσυμπίεση Windows Registry Windows Registry extract extracto del registro de Windows Windows-eko erregistro erauzlea Windows-rekisteritietue Windows Registry úrdráttur extrait de registre Windows sliocht as Clárlann Windows Extracto do rexistro de Windows קובץ Registry של Windows Windows Registry kivonat Ekstrak Windows Registry Estratto Windows Registry WIndows レジストリ抽出ファイル Windows Registry бөлігі Windows 레지스트리 파일 Windows registro ištrauka Windows Registry izvilkums Utdrag av Windows Registry Windows Registry-extract Windows Registry-utdrag Wycinek rejestru Windows Extrator de registro do Windows Extras al registrului Windows фрагмент Windows Registry Časť registrov Windows izvleček vpisnika Windows Pjesë Windows Registry Windows Registry-utdrag частина реєстру Windows Bản trích Registry Windows Windows 注册表文件 Windows Registry 抽出 Managed Object Format صيغة كائن مدار Farmat Managed Object Управлявани обекти — MOF format d'objecte gestionat Managed Object Format Håndteret objektformat Managed Object Format Μορφή Διαχειριζόμενου Αντικειμένου Managed Object Format formato de objeto manejado Kudeatutako objektu formatua Managed Object Format format Managed Object formáid réada bainistithe formato de obxecto xestionado פורמט ניהול אובייקט Felügyelt objektum (MO) formátum Managed Object Format Managed Object Format 管理オブジェクトフォーマット Басқарылатын объект пішімі 관리되는 오브젝트 형식 Sutvarkytų objektų formatas Pārvaldītu objektu formāts Managed Object Format Managed Object Format Managed Object Format Plik Managed Object Format Formato de objeto gerenciado Managed Object Format формат управляемого объекта Formát Managed Object Datoteka Managed Object Managed Object Format Managed Object Format формат керування об’єктами Định dạng Đối tượng đã Quản lý 托管对象格式 Managed Object Format Mup publication منشور Mup Publikacyja Mup Издание — Mup publicació Mup Publikace Mup Mupudgivelse Mup-Veröffentlichung δημοσίευση Mup Mup publication publicación Mup Mup publikazioa Mup-julkaisu Mup útgáva publication Mup foilseachán Mup publicación Mup פרסום של Mup Mup publikáció Publikasi Mup Pubblicazione Mup Mup 出版ファイル Mup жариялымы Mup 출판물 Mup leidinys Mup publikācija Mup publikasjon Mup-publicatie Mup-publikasjon Publikacja Mup Publicação do Mup Publicație Mup публикация Mup Publikácie Mup Datoteka objave Mup Publikim Mup Mup-publicering публікація Mup Bản xuất Mup Mup 应用程序 Mup 出版品 Objective-C source code شفرة مصدر الهدف-C Kryničny kod Objective-C Изходен код — Objective C codi font en Objective-C Zdrojový kód v Objective-C Objektiv C-kildekode Objective-C-Quelltext πηγαίος κώδικας Objective-C Objective-C source code fontkodo en Objective-C código fuente en Objective-C Objective-C iturburu-kodea Objective-C-lähdekoodi Objective-C keldukota code source Objective-C cód foinseach Objective-C código fonte de Objective-C קוד מקור של Objective-C Objective-C forráskód Kode program Objective-C Codice sorgente Objective-C Objective-C ソースコード Objective-C-ის საწყისი კოდი Objective-C бастапқы коды Objective-C 소스 코드 Objective-C pradinis kodas Objective-C pirmkods Kod sumber Objective-C Objective-C-kildekode Objective-C-broncode Objective-C-kjeldekode Kod źródłowy Objective-C código fonte Objective-C Código-fonte Objective-C Cod sursă Objective-C исходный код Objective-C Zdrojový kód Objective-C Datoteka izvorne kode Objective-C Kod burues C objekt Објектни-C изворни ко̂д Objective-C-källkod вихідний код мовою Objective-C Mã nguồn Objective-C Objective-C 源代码 Objective-C 源碼 OCaml source code شفرة مصدر OCaml Kryničny kod OCaml Изходен код — OCaml codi font en OCaml Zdrojový kód OCaml OCaml-kildekode OCaml-Quelltext πηγαίος κώδικας OCaml OCaml source code OCaml-fontkodo código fuente en OCaml OCaml iturburu-kodea OCaml-lähdekoodi OCaml keldukota code source OCaml cód foinseach OCaml código fonte de OCaml קוד מקור של OCaml OCaml izvorni kod OCaml forráskód Kode program OCaml Codice sorgente OCaml OCaml ソースコード OCaml бастапқы коды OCaml 소스 코드 OCaml pradinis kodas OCaml pirmkods OCaml-kildekode OCaml-broncode OCaml-kjeldekode Kod źródłowy OCaml Código-fonte OCaml Cod sursă OCaml исходный код OCaml Zdrojový kód OCaml Datoteka izvorne kode OCaml Kod burues OCaml OCaml-källkod OCaml kaynak kodu первинний код мовою OCaml Mã nguồn OCaml OCaml 源代码 OCaml 源碼 MATLAB script/function سكربت/وظيفة MATLAB Skrypt/funkcyja MATLAB Скрипт/функция — MATLAB script/funció MATLAB Skript/funkce MATLAB MATLAB-program/-funktion MATLAB-Skript/-Funktion δέσμη εντολών / συνάρτηση MATLAB MATLAB script/function script/función de MATLAB MATLAB script/funtzioa MATLAB-komentotiedosto/funktio MATLAB boðrøð/funka script/fonction MATLAB script/feidhm MATLAB función/script de MATLAB תסריט/פונקציית MATLAB MATLAB skripta/funkcija MATLAB parancsfájl/funkció Skrip/fungsi MATLAB Script/Funzione MATLAB MATLAB スクリプト/関数 MATLAB сценарий/функциясы MATLAB 스크립트/함수 MATLAB scenarijus / funkcija MATLAB skripts/funkcija Skript/funksjon for MATLAB MATLAB-script/functie MATLAB-skript/funksjon Skrypt/funkcja MATLAB Script/função do MATLAB Funcție/script MATLAB сценарий/функция MATLAB Skript/funkcia MATLAB Skriptna datoteka MATLAB Script/Funksion MATLAB MATLAB-skript/funktion скрипт/функція MATLAB Văn lệnh/chức năng MATLAB MATLAB 脚本/函数 MATLAB 指令稿/函式 Modelica model Pascal source code شفرة مصدر باسكال Kryničny kod Pascal Изходен код — Pascal codi font en Pascal Zdrojový kód v Pascalu Pascalkildekode Pascal-Quelltext πηγαίος κώδικας Pascal Pascal source code Pascal-fontkodo código fuente en Pascal Pascal iturburu-kodea Pascal-lähdekoodi Pascal keldukota code source Pascal cód foinseach Pascal código fonte en Pascal קוד מקור של Pascal Pascal-forráskód Kode program Pascal Codice sorgente Pascal Pascal ソースコード Pascal бастапқы коды 파스칼 소스 코드 Pascal pradinis kodas Pascal pirmkods Kod sumber Pascal Pascal-kildekode Pascal-broncode Pascal-kjeldekode Kod źródłowy Pascal código fonte Pascal Código-fonte Pascal Cod sursă Pascal исходный код Pascal Zdrojový kód Pascal Datoteka izvorne kode Pascal Kod burues Pascal Паскал изворни ко̂д Pascal-källkod Pascal kaynak kodu вихідний код мовою Pascal Mã nguồn Pascal Pascal 源代码 Pascal 源碼 differences between files الاختلافات بين الملفات adroźnieńni pamiž fajłami Разлики между файлове diferències entre fitxers Rozdíly mezi soubory forskel mellem filer Unterschiede zwischen Dateien διαφορές μεταξύ αρχείων differences between files diferencoj inter dosieroj diferencias entre archivos fitxategien arteko ezberdintasunak tiedostojen väliset erot munur millum fílur différences entre fichiers difríochtaí idir chomhaid diferenzas entre ficheiros ההבדל בין קבצים razlike između datoteka diff-különbségfájl perbedaan diantara berkas Differenze tra file ファイル間差分 файлдар арасындағы айырмашылықтары 파일 사이의 차이점 skirtumai tarp failų divu datņu atšķirība Perbezaan antara fail forskjeller mellom filer verschillen tussen bestanden skilnader mellom filer Różnica pomiędzy plikami diferenças entre ficheiros Diferenças entre arquivos diferențe între fișiere различия между файлами Rozdiely medzi súbormi razlike med datotekami Diferencë midis file разлике међу датотекама skillnader mellan filer різниця між файлами khác biệt giữa các tập tin 文件的区别 檔案內容差異 Go source code Изходен код — Go codi font en Go Zdrojový kód Go Go-kildekode Go-Quellcode πηγαίος κώδικας Go Go source code Go-fontkodo Ir al código fuente Go-lähdekoodi code source Go código fonte de Go קוד מקור של Go Go izvorni kod Go forráskód Kode sumber Go Codice sorgente Go Go ソースコード Go-ის საწყისი კოდი Go бастапқы коды Go 소스 코드 Go pirmkods Go broncode Kod źródłowy Go Código-fonte Go исходный код Go Izvorna koda Go вихідний код мовою Go Go Go 源碼 Python script سكربت بايثون Skrypt Python Скрипт — Python script Python Skript v Pythonu Pythonprogram Python-Skript πρόγραμμα εντολών Python Python script Python-skripto script en Python Python script-a Python-komentotiedosto Python boðrøð script Python script Python Script en Python תסריט Python Python skripta Python-parancsfájl Skrip Python Script Python Python スクリプト Python сценарийі 파이썬 스크립트 Python scenarijus Python skripts Skrip Python Python-skript Python-script Python-skript Skrypt Python 'script' Python Script Python Script Python сценарий Python Skript Python Skriptna datoteka Python Script Python Питон скрипта Pythonskript скрипт мовою Python Văn lệnh Python Python 脚本 Python 指令稿 Lua script سكربت Lua Skrypt Lua Скрипт на Lua script Lua Skript Lua Luaprogram Lua-Skript δέσμη εντολών Lua Lua script Lua-skripto script en Lua Lua script-a Lua-komentotiedosto Lua boðrøð script Lua script Lua script de Lua תסריט Lua Lua skripta Lua parancsfájl Skrip Lua Script Lua Lua スクリプト Lua сценарийі Lua 스크립트 Lua scenarijus Lua skripts Lua-skript Lua-script Lua-skript Skrypt Lua Script Lua Script Lua сценарий Lua Skript Lua Skriptna datoteka Lua Script Lua Lua-skript скрипт Lua Văn lệnh Lua Lua 脚本 Lua 指令稿 README document مستند README README sənədi Dakument README Документ — „Да се прочете“ document README Dokument README Dogfen README README-dokument README-Dokument έγγραφο README README document README-dokumento documento README README dokumentua LUEMINUT-asiakirja README skjal document LISEZ-MOI cáipéis README documento README מסמך README README dokument README-dokumentum Dokumen README Documento README README ドキュメント README құжаты README 문서 README dokumentas README dokuments Dokumen README README-dokument LEESMIJ-document README-dokument Dokument README documento LEIA-ME Documento README Document README документ README Dokument README Dokument README Dokument README ПРОЧИТАЈМЕ документ README-dokument документ README Tài liệu Đọc Đi (README) README 文档 README 說明文件 NFO document مستند NFO Dakument NFO Документ — NFO document NFO Dokument NFO NFO-dokument NFO-Dokument έγγραφο NFO NFO document NFO-dokumento documento NFO NFO dokumentua NFO-asiakirja NFO skjal document NFO cáipéis NFO documento NFO מסמך NFO NFO dokument NFO-dokumentum Dokumen NFO Documento NFO NFO ドキュメント NFO құжаты NFO 문서 NFO dokumentas NFO dokuments NFO-dokument NFO-document NFO-dokument Dokument NFO Documento NFO Document NFO документ NFO Dokument NFO Dokument NFO Dokument NFO NFO-dokument NFO belgesi документ NFO Tài liệu NFO NFO 文档 NFO 文件 RPM spec file ملف مواصفات RPM Specyfikacyjny fajł RPM Файл — спецификация за RPM fitxer spec RPM Soubor spec RPM RPM spec-fil RPM-Spezifikationsdatei αρχείο spec RPM RPM spec file archivo de especificaciones RPM RPM espezifikazio fitxategia RPM spec -tiedosto RPM tøknilýsingarfíla fichier de spécification RPM comhad spec RPM ficheiro de especificacións RPM קובץ מפרט RPM RPM spec fájl Berkas spesifikasi RPM File specifica RPM RPM spec ファイル RPM анықтама файлы RPM spec 파일 RPM spec failas RPM specifikācijas datne RPM-spesifikasjonsfil RPM-spec-bestand RPM spec-fil Plik spec RPM Arquivo de especificação RPM Fișier RPM spec файл описания RPM Súbor RPM spec Določilna datoteka RPM File specifikimi RPM RPM spec-fil spec-файл RPM Tập tin đặc tả RPM RPM spec 文件 RPM spec 規格檔 RPM Red Hat Package Manager Scala source code Изходен код — Scala codi font en Scala Zdrojový kód Scala Scala-kildekode Scala-Quellcode πηγαίος κώδικας Scala Scala source code Código fuente en Scala Scala-lähdekoodi code source Scala código fnote en Scala קוד מקור של Scala Scala izvorni kod Scala forráskód Kode sumber Scala Codice sorgente Scala Scala ソースコード Scala-ის საწყისი კოდი Scala бастапқы коды Scala 소스 코드 Scala pirmkods Scala broncode Kod źródłowy Scala Código-fonte Scala исходный код Scala Izvorna koda Scala Scala-källkod вихідний код мовою Scala Scala 源代码 Scala 源碼 Scheme source code شفرة مصدر Scheme Sxem mənbə kodu Kryničny kod Scheme Изходен код — Scheme codi font en Scheme Zdrojový kód Scheme Ffynhonnell Rhaglen Scheme Schemekildekode Scheme-Quelltext πηγαίος κώδικας Scheme Scheme source code Scheme-fontkodo código fuente en Scheme Scheme iturburu-kodea Scheme-lähdekoodi Scheme keldukota code source Scheme cód foinseach Scheme código fonte en Scheme קוד מקור של Scheme Scheme izvorni kod Scheme-forráskód Kode program Scheme Codice sorgente Scheme Scheme ソースコード Scheme бастапқы коды Scheme 소스 코드 Scheme pradinis kodas Scheme pirmkods Kod sumber Scheme Scheme-kildekode Scheme-broncode Scheme-kjeldekode Kod źródłowy Scheme código fonte Scheme Código-fonte Scheme Cod sursă Scheme исходный код Scheme Zdrojový kód Scheme Datoteka izvorne kode Scheme Kod burues Scheme Scheme изворни ко̂д Scheme-källkod Scheme kaynak kodu вихідний файл мовою Scheme Mã nguồn Scheme Scheme 源代码 Scheme 源碼 Setext document مستند Setext Setext sənədi Dakument Setext Документ — Setext document Setext Dokument Setext Dogfen Setext Setextdokument Setext-Dokument έγγραφο Setext Setext document Setext-dokumento documento Setext Setext dokumentua Setext-asiakirja Setext skjal document Setext cáipéis Setext documento Settext מסמך של Setext Setext dokument Setext-dokumentum Dokumen Setext Documento Setext Setext ドキュメント Setext құжаты Setext 문서 Setext dokumentas Setext dokuments Dokumen Setext Setext-dokument Setext-document Setext-dokument Dokument Setext documento Setext Documento Setext Document Setext документ Setext Dokument Setext Dokument Setext Dokument Setext Setext документ Setext-dokument Setext belgesi документ Setext Tài liệu Setext Setext 文档 Setext 文件 SQL code شفرة SQL SQL kodu Kod SQL Код — SQL codi en SQL Kód SQL Côd SQL SQL-kode SQL-Befehle κώδικας SQL SQL code SQL-kodo código SQL SQL kodea SQL-koodi SQL kota code SQL cód SQL código SQL קוד SQL SQL kod SQL-kód Kode SQL Codice SQL SQL コード SQL коды SQL 코드 SQL kodas SQL kods Kod SQL SQL-kildekode SQL-code SQL-kode Kod SQL código SQL Código SQL Cod SQL код SQL Kód SQL Datoteka kode SQL Kod SQL SQL ко̂д SQL-kod SQL kodu код SQL Mã SQL SQL 代码 SQL 代碼 Tcl script سكربت Tcl Skrypt Tcl Скрипт — Tcl script Tcl Skript v Tcl Tcl-program Tcl-Skript πρόγραμμα εντολών Tcl Tcl script Tcl-skripto script en Tcl Tcl script-a Tcl-komentotiedosto Tcl boðrøð script Tcl script Tcl Script en Tcl תסריט Tcl Tcl skripta Tcl-parancsfájl Skrip Tcl Script Tcl Tcl スクリプト Tcl сценарийі Tcl 스크립트 Tcl scenarijus Tcl skripts Skrip Tcl Tcl-skript Tcl-script Tcl-skript Skrypt Tcl 'script' Tcl Script Tcl Script Tcl сценарий Tcl Skript Tcl Skriptna datoteka Tcl Script Tcl Tcl скрипта Tcl-skript скрипт Tcl Văn lệnh Tcl Tcl 脚本 Tcl 描述語言檔 TeX document مستند TeX Dakument TeX Документ — TeX document TeX Dokument TeX Dogfen TeX TeX-dokument TeX-Dokument έγγραφο TeX TeX document TeX-dokumento documento TeX TeX dokumentua TeX-asiakirja TeX skjal document TeX cáipéis TeX documenton TeX מסמך TeX TeX dokument TeX-dokumentum Dokumen TeX Documento TeX TeX ドキュメント TeX құжаты TeX 문서 TeX dokumentas TeX dokuments Dokumen TeX TeX-dokument TeX-document TeX-dokument Dokument TeX documento TeX Documento TeX Document TeX документ TeX Dokument TeX Dokument TeX Dokument TeX ТеХ документ TeX-dokument TeX belgesi документ TeX Tài liệu TeX TeX 文档 TeX 文件 TeXInfo document مستند TeXInfo TeXInfo sənədi Dakument TeXInfo Документ — TeXInfo document TeXInfo Dokument TeXInfo Dogfen TeXInfo TeXInfo-dokument TeXInfo-Dokument έγγραφο TeXInfo TeXInfo document TeXInfo-dokumento documento TeXInfo TeXInfo dokumentua TeXInfo-asiakirja TeXInfo skjal document TeXInfo cáipéis TeXInfo documento TeXInfo מסמך של TeXInfo TeXInfo dokument TeXInfo-dokumentum Dokumen TeXInfo Documento TeXInfo TeXInfo ドキュメント TeXInfo құжаты TeXInfo 문서 TeXInfo dokumentas TeXInfo dokuments Dokumen TeXInfo TeXInfo-dokument TeXInfo-document TeXInfo-dokument Dokument TeXInfo documento TeXInfo Documento TeXInfo Document TexInfo документ TeXInfo Dokument TeXInfo Dokument TeXInfo Dokument TeXInfo ТеХинфо документ TeXInfo-dokument TeXInfo belgesi документ TeXInfo Tài liệu TeXInfo TeXInfo 文档 TeXInfo 文件 Troff ME input document مستند Troff ME input Uvodny dakument Troff ME Изходен документ — Troff ME document d'entrada Troff ME Vstupní dokument Troff ME Troff ME inddata-dokument Troff-ME-Eingabedokument έγγραφο/πρόγραμμα εντολών troff ME Troff ME input document eniga dokumento de Troff ME documento de entrada Troff ME Troff ME sarrerako dokumentua Troff ME -syöteasiakirja Troff ME inntaksskjal document d'entrée Troff ME cáipéis ionchur Troff ME documento de entrada Troff ME מסמך קלט של Troff ME Troff ME ulazni dokument Troff ME bemeneti dokumentum Dokumen masukan Troff ME Documento di input Troff ME Troff ME 入力ドキュメント Troff ME кіріс құжаты Troff ME 입력 문서 Troff ME įvesties dokumentas Troff ME ievades dokuments Dokumen input Troff ME Troff ME-inndatadokument Troff ME-invoerdocument Troff ME inndata-dokument Dokument wejściowy Troff ME documento origem Troff ME Documento de entrada Troff ME Document intrare Troff ME входной документ Troff ME Vstupný dokument Troff ME Vnosni dokument Troff ME Dokument i input Troff ME Troff ME улазни документ Troff ME-indatadokument Troff ME girdi belgesi вхідний документ Troff ME Tài liệu nhập ME Troff Troff ME 输入文档 Troff ME 輸入文件 Troff MM input document مستند Troff MM input Uvodny dakument Troff MM Изходен документ — Troff MM document d'entrada Troff MM Vstupní dokument Troff MM Troff MM inddata-dokument Troff-MM-Eingabedokument έγγραφο/πρόγραμμα εντολών troff MM Troff MM input document eniga dokumento de Troff MM documento de entrada Troff MM Troff MM sarrerako dokumentua Troff MM -syöteasiakirja Troff MM inntaksskjal document d'entrée Troff MM cáipéis ionchur Troff MM documento de entrada Troff MM מסמך קלט של Troff MM Troff MM ulazni dokument Troff MM bemeneti dokumentum Dokumen masukan Troff MM Documento di input Troff MM Troff MM 入力ドキュメント Troff MM кіріс құжаты Troff MM 입력 문서 Troff MM įvesties dokumentas Troff MM ievades dokuments Dokumen input Troff MM Troff MM-inndatadokument Troff MM-invoerdocument Troff MM inndata-dokument Dokument wejściowy Troff MM documento origem Troff MM Documento de entrada Troff MM Document intrare Troff MM входной документ Troff MM Vstupný dokument Troff MM Vnosni dokument Troff MM Dokument i input Troff MM Troff MM улазни документ Troff MM-indatadokument Troff MM girdi belgesi вхідний документ Troff MM Tài liệu nhập MM Troff Troff MM 输入文档 Troff MM 輸入文件 Troff MS input document مستند Troff MS input Uvodny dakument Troff MS Изходен документ — Troff MS document d'entrada Troff MS Vstupní dokument Troff MS Troff MS inddata-dokument Troff-MS-Eingabedokument έγγραφο/πρόγραμμα εντολών troff MS Troff MS input document eniga dokumento de Troff MS documento de entrada Troff MS Troff MS sarrerako dokumentua Troff MS -syöteasiakirja Troff MS inntaksskjal document d'entrée Troff MS cáipéis ionchur Troff MS documento de entrada Troff MS מסמך קלט של Troff MS Troff MS ulazni dokument Troff MS bemeneti dokumentum Dokumen masukan Troff MS Documento di input Troff MS Troff MS 入力ドキュメント Troff MS кіріс құжаты Troff MS 입력 문서 Troff MS įvesties dokumentas Troff MS ievades dokuments Dokumen input Troff MS Troff MS-inndatadokument Troff MS-invoerdocument Troff MS inndata-dokument Dokument wejściowy Troff MS documento origem Troff MS Documento de entrada Troff MS Document intrare Troff MS входной документ Troff MS Vstupný dokument Troff MS Vnosni dokument Troff MS Dokument i input Troff MS Troff MS улазни документ Troff MS-indatadokument Troff MS girdi belgesi вхідний документ Troff MS Tài liệu nhập MS Troff Troff MS 输入文档 Troff MS 輸入文件 X-Motif UIL table جدول X-Motif UIL Tablica X-Motif UIL Таблица — X-Motif UIL taula UIL de X-Motif Tabulka X-Motif UIL X-Motif UIL-tabel X-Motif-UIL-Tabelle πίνακας X-Motif UIL X-Motif UIL table tabla de X-Motif UIL X-Motif UIL taula X-Motif UIL -taulukko X-Motif UIL talva table X-Motif UIL tábla X-Motif UIL Táboa de X-Motif UIL טבלה של X-Motif UIL X-Motif UIL tablica X-Motif UIL-táblázat Tabel X-Motif UIL Tabella UIL X-Motif X-Motif UIL 表 X-Motif UIL кестесі X-Motif UIL 테이블 X-Motif UIL lentelė X-Motif UIL tabula Jadual X-Motif UIL X-Motif UIL-tabell X-Motif UIL-tabel X-Motif UIL-tabell Tabela UIL X-Motif tabela UIL do X-Motif Tabela UIL do X-Motif Tabel X-Motif UIL таблица UIL X-Motif Tabuľka X-Motif UIL Preglednica X-Motif UIL Tabelë X-Motif UIL X-Motif UIL табела X-Motif UIL-tabell таблиця X-Motif UIL Bảng UIL X-Motif X-Motif UIL 表 X-Motif UIL 表格 resource location موقع المورد pałažeńnie resursu Местоположение на ресурс localització de recurs Umístění zdroje resurseplacering Ressourcenort τοποθεσία πόρου resource location loko de risurco ubicación del recurso baliabidearen kokalekua resurssisijainti tilfeingisstaður localisation de ressource suíomh acmhainne localización do recurso מיקומו של המקור položaj resursa erőforrás-hely lokasi sumber daya Posizione risorsa リソースの場所 ресурс орналасуы 자원 위치 resurso vieta resursa atrašanās vieta Lokasi sumber ressurslokasjon bronlocatie ressursplassering Położenie zasobu localização de recurso Localização de recurso locație de resursă расположение ресурса Umiestnenie zdroja mesto vira Pozicion rezerve путања ресурса resursplats розташування ресурсу địa điểm tài nguyên 资源位置 資源位置 uuencoded file XMI file ملف XMI Fajł XMI Файл — XMI fitxer XMI Soubor XMI XMI-fil XMI-Datei αρχείο XML XMI file XMI-dosiero archivo XMI XMI fitxategia XMI-tiedosto XMI fíla fichier XMI comhad XMI ficheiro XMI קובץ XMI XMI datoteka XMI fájl Berkas XMI File XMI XMI ファイル XMI файлы XMI 파일 XMI failas XMI datne XMI-fil XMI-bestand XMI-fil Plik XMI Arquivo XMI Fișier XMI файл XMI Súbor XMI Datoteka XMI File XMI XMI-fil файл XMI Tập tin XMI XMI 文件 XMI 檔 XMI XML Metadata Interchange XSL FO file ملف XSL FO Fajł XSL FO Форматиращ файл — XSL FO fitxer FO XSL Soubor XSL FO XML FO-fil XSL-FO-Datei αρχείο XSL FO XSL FO file XSL-FO-dosiero archivo XSL FO XSL FO fitxategia XSL FO -tiedosto XSL FO fíla fichier XSL FO comhad XSL FO ficheiro XSL FO קובץ XSL FO XSL FO datoteka XSL FO fájl Berkas XSL FO File XSL FO XSL FO ファイル XSL FO файлы XSL 포매팅 개체 파일 XSL FO failas XSL FO datne FO-fil for XSL XSL FO-bestand XSL FO-fil Plik XSL FO Arquivo XSL FO Fișier XSL FO файл XSL FO Súbor XSL FO Datoteka XSL FO File XSL FO XSL FO-fil файл XSL FO Tập tin FO của XSL (XFO) XSL 格式化对象文件 XSL FO 檔 XSL FO XSL Formatting Objects iptables configuration file ملف تضبيط iptables kanfihuracyjny fajł iptables Настройки за iptables fitxer de configuració d'iptables Soubor nastavení iptables iptableskonfigurationsfil iptables-Konfigurationsdatei αρχείο ρυθμίσεων iptables iptables configuration file archivo de configuración de iptables iptables konfigurazio-fitxategia iptables-asetustiedosto iptables samansetingarfíla fichier de configuration iptables comhad cumraíochta iptables ficheiro de configuración de iptables קובץ הגדרה של iptables iptables datoteka s postavkama iptables beállítófájl berkas konfigurasi iptables File configurazione iptables iptables 設定ファイル iptables баптаулар файлы iptables 설정 파일 iptables konfigūracijos failas iptables konfigurācijas datne konfigurasjonsfil for iptables iptables-configuratiebestand iptables oppsettfil Plik konfiguracji iptables Arquivo de configuração do iptables fișier configurare iptables конфигурационный файл iptables Súbor nastavení iptables nastavitvena datoteka iptables File konfigurimi iptables iptables-konfigurationsfil файл налаштувань iptables tập tin cấu hình iptables iptables 防火墙配置文件 iptables 組態檔 XSLT stylesheet نمط XSLT Arkuš stylaŭ XSLT Стилове — XSLT full d'estil XSLT Styl XSLT XSLT-stilark XSLT-Stylesheet φύλλο στυλ XSLT XSLT stylesheet XSLT-stilfolio hoja de estilo XSLT XSLT estilo-orria XSLT-tyylitiedosto XSLT sniðark feuille de style XSLT stílbhileog XSLT folla de estilo XSLT סגנון גליון XSLT XSLT stilska tablica XSLT-stíluslap Lembar gaya XSLT Foglio di stile XSLT XSLT スタイルシート XSLT стильдер кестесі XSLT 스타일시트 XSLT stiliaus aprašas XSLT izklājlapa Helaian Gaya XSLT XSLT-stilark XSLT-stijlblad XSLT-stilark Arkusz stylów XSLT folha de estilos XSLT Folha de estilo XSLT Fișă de stil XSLT таблица стилей XSLT Štýl XSLT Slogovna predloga XSLT Fletë stili XSLT Датотека са XSLT стилом XSLT-stilmall таблиця стилів XSLT Tờ kiểu dáng XSLT XSLT 样式表 XSLT 樣式表 XSLT eXtensible Stylesheet Language Transformation XMCD CD database قاعدة بيانات XMCD CD Baza źviestak ab dyskach XMCD База от данни за CD-та — XMCD base de dades de CD XMCD Databáze XMCD CD XMCD-cd-database XMCD-CD-Datenbank βάση δεδομένων CD XMCD XMCD CD database base de datos de CD XMCD XMCD CD datu-basea XMCD CD -tietokanta XMCD fløgu dátustovnur base de données de CD XMCD bunachar sonraí XMCD CD base de datos de CD XMCD בסיס נתונים XMCD CD XMCD CD baza podataka XMCD CD-adatbázis Basis data XMCD CD Database XMCD CD XMCD CD データベース XMCD CD дерекқоры XMCD CD 데이터베이스 XMCD CD duomenų bazė XMCD CD datubāze XMCD CD-database XMCD CD-gegevensbank XMCD CD-database Baza danych CD XMCD Banco de dados de CD XMCD Bază de date XMCD CD база данных компакт-дисков XMCD Databáza XMCD CD Podatkovna zbirka XMCD CD Bazë me të dhëna XMCD CD XMCD cd-databas база даних XMCD CD Cơ sở dữ liệu CD XMCD XMCD CD 数据库 XMCD CD 資料庫 XML document مستند XML Dakument XML Документ — XML document XML Dokument XML XML-dokument XML-Dokument έγγραφο XML XML document XML-dokumento documento XML XML dokumentua XML-asiakirja XML skjal document XML cáipéis XML documento XML מסמך XML XML dokument XML dokumentum Dokumen XML Documento XML XML ドキュメント XML құжаты XML 문서 XML dokumentas XML dokuments XML-dokument XML-document XML-dokument Dokument XML Documento XML Document XML документ XML Dokument XML Dokument XML Dokument XML XML-dokument документ XML Tài liệu XML XML 文档 XML 文件 XML eXtensible Markup Language XML entities document مستند كيانات XML Dakument elementaŭ XML Документ — заместващи последователности в XML document d'entitats XML Dokument entit XML XML-enhedsdokument XML-Dokument-Entitäten έγγραφο οντοτήτων XML XML entities document documento de entidades XML XML entitateen dokumentua XML-entiteettiasiakirja XML einindisskjal document d'entités XML cáipéis aonán XML documento de entidades XML מסמך ישיות XML XML egyeddokumentum Dokumen entitas XML Documento entità XML XML エントリドキュメント XML мәндер құжаты XML 엔티티 문서 XML esybių dokumentas XML vienību dokuments XML-entitetsdokument XML entiteiten-document XML-entitet-dokument Dokument jednostek XML Documento de entidades XML Document entități XML файл сущностей XML Dokument entít XML Dokument XML določil Dokument njësish XML XML-entitetsdokument документ об’єктів XML Tài liệu thực thể XML XML 特征文档 XML 實體文件 XML eXtensible Markup Language DV video DV مرئي Videa DV Видео — DV vídeo DV Video DV DV-video DV-Video βίντεο DV DV video DV-video vídeo DV DV bideoa DV-video DV video vidéo DV físeán DV vídeo DV וידאו DV DV video DV videó Video DV Video DV DV 動画 DV ვიდეო DV видеосы DV 비디오 DV vaizdo įrašas DV video DV-film DV-video DV-video Plik wideo DV Vídeo DV Video DV видео DV Video DV Video datoteka DV Video DV DV-video відеокліп DV Ảnh động DV DV 视频 DV 視訊 DV Digital Video ISI video مرئي ISI ISI video faylı Videa ISI Видео — ISI vídeo ISI Video ISI Fideo ISI ISI-video ISI-Video βίντεο ISI ISI video ISI-video vídeo ISI ISI bideoa ISI-video ISI video vidéo ISI físeán ISI vídeo ISI וידאו ISI ISI video ISI-videó Video ISI Video ISI ISI 動画 ISI видеосы ISI 비디오 ISI vaizdo įrašas ISI video Video ISI ISI-film ISI-video ISI video Plik wideo ISI vídeoISI Vídeo ISI Video ISI видео ISI Video ISI Video datoteka ISI Video ISI ISI видео ISI-video відеокліп ISI Ảnh động ISI ISI 视频 ISI 視訊 MPEG-2 transport stream بث نقل MPEG-2 Поток — транспорт по MPEG-2 flux de transport MPEG-2 Přenosový proud MPEG-2 MPEG-2-transportstrøm MPEG-2-Transportstrom ροή μεταφορών MPEG-2 MPEG-2 transport stream flujo de transporte MPEG-2 MPEG-2 -siirtobittivirta MPEG-2 flutningsstreymur flux de transport MPEG-2 Sruth aistrithe MPEG-2 fluxo de transporte MPEG-2 העברת זרימה של MPEG-2 MPEG-2 átviteli adatfolyam Stream transport MPEG-2 Stream di trasporto MPEG-2 MPEG-2 トランスポートストリーム MPEG-2-ის ტრანსპორტული ნაკადი MPEG-2 көліктік ағыны MPEG-2 전송 스트림 MPEG-2 transportavimo srautas MPEG-2 transporta straume MPEG-2 transport stream Strumień przesyłania MPEG-2 Fluxo de transporte de MPEG-2 Flux transport MPEG-2 транспортный поток MPEG-2 Pretočni vir prenosega MPEG MPEG-2 transportström потік передавання даних MPEG-2 MPEG-2 传输流 MPEG-2 傳輸串流 MPEG-2 TS Moving Picture Experts Group 2 Transport Stream MPEG video MPEG مرئي Videa MPEG Видео — MPEG vídeo MPEG Video MPEG MPEG-video MPEG-Video βίντεο MPEG MPEG video MPEG-video vídeo MPEG MPEG bideoa MPEG-video MPEG video vidéo MPEG físeán MPEG vídeo MPEG וידאו MPEG MPEG video MPEG-videó Video MPEG Video MPEG MPEG 動画 MPEG ვიდეო MPEG видеосы MPEG 비디오 MPEG vaizdo įrašas MPEG video Video MPEG MPEG-film MPEG-video MPEG-video Plik wideo MPEG vídeo MPEG Vídeo MPEG Video MPEG видео MPEG Video MPEG Video datoteka MPEG Video MPEG MPEG видео MPEG-video відеокліп MPEG Ảnh động MPEG MPEG 视频 MPEG 視訊 MPEG Moving Picture Experts Group MPEG video (streamed) Видео — MPEG, поточно vídeo MPEG (flux) Video MPEG (proud) MPEG-video (streamet) MPEG-Video (Datenstrom) βίντεο MPEG (εκπεμπόμενο) MPEG video (streamed) Vídeo MPEG (flujo) MPEG-video (virtaus) vidéo MPEG (flux) vídeo MPEG (en stream) קובץ MPEG (בהזרמה) MPEG videó (szórt) Video MPEG (di-stream-kan) Video MPEG (streamed) MPEG ビデオ(ストリーム) MPEG ვიდეო (ნაკადი) MPEG видео (ағымдық) MPEG 비디오 (스트리밍) MPEG video (straumēts) MPEG video (streamed) Plik wideo MPEG (strumień) Vídeo MPEG (fluxo) видео MPEG (потоковое) MPEG-video (pretočni) MPEG-video (strömmad) відеокліп MPEG (потоковий) MPEG 视频流媒体 MPEG 視訊 (串流) QuickTime video QuickTime مرئي Videa QuickTime Видео — QuickTime vídeo QuickTime Video QuickTime QuickTime-video QuickTime-Video βίντεο QuickTime QuickTime video QuickTime-video vídeo QuickTime QuickTime bideoa QuickTime-video QuickTime video vidéo QuickTime físeán QuickTime vídeo QuickTime וידאו של QuickTime QuickTime video QuickTime videó Video QuickTime Video QuickTime QuickTime 動画 QuickTime видеосы 퀵타임 비디오 QuickTime vaizdo įrašas QuickTime video Video QuickTime Quicktime film QuickTime-video QuickTime-video Plik wideo QuickTime vídeo QuickTime Vídeo do QuickTime Video QuickTime видео QuickTime Video QuickTime Video datoteka QuickTime Video QuickTime QuickTime видео QuickTime-video відеокліп QuickTime Ảnh động QuickTime QuickTime 视频 QuickTime 視訊 QuickTime image صورة QuickTime Vyjava QuickTime Изображение — QuickTime imatge QuickTime Obrázek QuickTime QuickTime-billede QuickTime-Bild εικόνα QuickTime QuickTime image QuickTime-bildo imagen QuickTime QuickTime irudia QuickTime-kuva QuickTime mynd image QuickTime íomhá QuickTime imaxe QuickTime תמונה של QuickTime QuickTime slika QuickTime kép Citra QuickTime Immagine QuickTime QuickTime 画像 QuickTime суреті 퀵타임 그림 QuickTime paveikslėlis QuickTime attēls Quicktime bilde QuickTime-afbeelding QuickTime-bilete Obraz QuickTime Imagem do QuickTime Imagine QuickTime изображение QuickTime Obrázok QuickTime Slikovna datoteka QuickTime Figurë QuickTime QuickTime-bild зображення QuickTime Ảnh QuickTime QuickTime 图像 QuickTime 影像 Vivo video Vivo مرئي Vivo video faylı Videa Vivo Видео — Vivo vídeo Vivo Video Vivo Fideo Vivo Vivo-video Vivo-Video βίντεο Vivo Vivo video Vivo-video vídeo Vivo Vivo bideoa Vivo-video Vivo video vidéo Vivo físeán Vivo vídeo Vivo וידאו של Vivo Vivo video Vivo-videó Video Vivo Video Vivo Vivo 動画 Vivo видеосы Vivo 비디오 Vivo vaizdo įrašas Vivo video Video Vivo Vivo-film Vivo-video Vivo-film Plik wideo Vivo vídeo Vivo Vídeo Vivo Video Vivo видео Vivo Video Vivo Video datoteka Vivo Video Vivo Vivo видео Vivo-video відео Vivo Ảnh động Vivo Vivo 视频 Vivo 視訊 Wavelet video Wavelet مرئي Wavelet video faylı Videa Wavelet Видео — Wavelet vídeo Wavelet Video Wavelet Fideo Wavelet Waveletvideo Wavelet-Video βίντεο Wavelet Wavelet video Wavelet-video vídeo Wavelet Wavelet bideoa Wavelet-video Wavelet video vidéo Wavelet físeán Wavelet vídeo Wavelet וידאו של Wavelet Wavelet video Wavelet-videó Video Wavelet Video Wavelet Wavelet 動画 Wavelet видеосы Wavelet 비디오 Wavelet vaizdo įrašas Wavelet video Video Wavelet Wavelet-film Wavelet-video Wavelet video Plik wideo Wavelet vídeo Wavelet Vídeo Wavelet Video Wavelet видео Wavelet Video Wavelet Video datoteka Wavelet Video Wavelet Wavelet видео Wavelet-video відеокліп Wavelet Ảnh động Wavelet Wavelet 视频 Wavelet 視訊 ANIM animation تحريكة ANIM ANIM animasiyası Animacyja ANIM Анимация — ANIM animació ANIM Animace ANIM Animeiddiad ANIM ANIM-animation ANIM-Animation κινούμενο σχέδιο ANIM ANIM animation ANIM-animacio animación ANIM ANIM animazioa ANIM-animaatio ANIM teknmyndagerð animation ANIM beochan ANIM animación ANIM הנפשת ANIM ANIM animacija ANIM-animáció Animasi ANIM Animazione ANIM ANIM アニメーション ANIM ანიმაცია ANIM анимациясы ANIM 동화상 ANIM animacija ANIM animācija Animasi ANIM ANIM-animasjon ANIM-animatie ANIM-animasjon Plik animacji ANIM animação ANIM Animação ANIM Animație ANIM анимация ANIM Animácia ANIM Datoteka animacije ANIM Animim ANIM ANIM анимација ANIM-animering анімація ANIM Hoạt ảnh ANIM ANIM 动画 ANIM 動畫 FLIC animation تحريكة FLIC Animacyja FLIC Анимация — FLIC animació FLIC Animace FLIC FLIC-animation FLIC-Animation κινούμενες εικόνες FLIC FLIC animation animación FLIC FLIC animazioa FLIC-animaatio FLIC teknimyndagerð animation FLIC beochan FLIC animación FLIC הנפשת FLIC FLIC animacija FLIC animáció Animasi FLIC Animazione FLIC FLIC アニメーション FLIC ანიმაცია FLIC анимациясы FLIC 동화상 FLIC animacija FLIC animācija FLIC-animasjon FLIC-animatie FLIC-animasjon Plik animacji FLIC Animação FLIC Animație FLIC анимация FLIC Animácia FLIC Datoteka animacije FLIC Animim FLIC FLIC-animering FLIC animasyonu анімація FLIC Hoạt ảnh FLIC FLIC 动画 FLIC 動畫 Haansoft Hangul document مستند Haansoft Hangul Dakument Haansoft Hangul Документ — Haansoft Hangul document Haansoft Hangul Dokument Haansoft Hangul Haansoft Hangul-dokument Haansoft-Hangul-Dokument έγγραφο Haansoft Hangul Haansoft Hangul document documento de Haansoft Hangul Haansoft Hangul dokumentua Haansoft Hangul -asiakirja Haansoft Hangul skjal document Haansoft Hangul cáipéis Haansoft Hangul documento de Haansoft Hangul מסמך Haansoft Hangul Haansoft Hangul dokument Haansoft hangul dokumentum Dokumen Haansoft Hangul Documento Haansoft Hangul Haansoft Hangul ドキュメント Haansoft Hangul құжаты 한소프트 한글 문서 Haansoft Hangul dokumentas Haansoft Hangul dokuments Haansoft Hangul-dokument Haansoft Hangul-document Haansoft Hangul-dokument Dokument Haansoft Hangul Documento do Haansoft Hangul Document Haansoft Hangul документ Haansoft Hangul Dokument Haansoft Hangul Dokument Haansoft Hangul Dokument Haansoft Hangul Haansoft Hangul-dokument документ Haansoft Hangul Tài liệu Hangul Haansoft Haansoft Hangul 文档 Haansoft 韓文文件 Haansoft Hangul document template قالب مستند Haansoft Hangul Šablon dakumentu Haansoft Hangul Шаблон за документи — Haansoft Hangul plantilla de document Haansoft Hangul Šablona dokumentu Haansoft Hangul Haansoft Hangul-dokumentskabelon Haansoft-Hangul-Dokumentvorlage πρότυπο εγγράφου Haansoft Hangul Haansoft Hangul document template plantilla de documento de Haansoft Hangul Haansoft Hangul dokumentuaren txantiloia Haansoft Hangul -asiakirjamalli Haansoft Hangul skjalaformur modèle de document Haansoft Hangul teimpléad cháipéis Haansoft Hangul modelo de documento de Haansoft Hangul תבנית מסמך של Haansoft Hangul Haansoft Hangul predložak dokumenta Haansoft hangul dokumentumsablon Templat dokumen Haansoft Hangul Modello documento Haansoft Hangul Haansoft Hangul ドキュメントテンプレート Haansoft Hangul құжат үлгісі 한소프트 한글 문서 서식 Haansoft Hangul dokumento šablonas Haansoft Hangul dokumentu veidne Haansoft Hangul-dokumentmal Haansoft Hangul-documentsjabloon Haansoft Hangul-dokumentmal Szablon dokumentu Haansoft Hangul Modelo de documento do Haansoft Hangul Document șablon Haansoft Hangul шаблон документа Haansoft Hangul Šablóna dokumentu Haansoft Hangul Predloga dokumenta Haansoft Hangul Model dokumenti Haansoft Hangul Haansoft Hangul-dokumentmall шаблон документа Haansoft Hangul Mẫu tài liệu Hangul Haansoft Haansoft Hangul 文档模板 Haansoft 韓文文件範本 MNG animation تحريكة MNG Animacyja MNG Анимация — MNG animació MNG Animace MNG MNG-animation MNG-Animation κινούμενα σχέδια MNG MNG animation MNG-animacio animación MNG MNG animazioa MNG-animaatio MNG teknimyndagerð animation MNG beochan MNG animación MNG הנפשת MNG MNG animacija MNG-animáció Animasi MNG Animazione MNG MNG アニメーション MNG анимациясы MNG 동화상 MNG animacija MNG animācija Animasi MNG MNG-animasjon MNG-animatie MNG-animasjon Animacja MNG animação MNG Animação MNG Animație MNG анимация MNG Animácia MNG Datoteka animacije MNG Animim MNG MNG анимација MNG-animering анімація MNG Hoạt ảnh MNG MNG 动画 MNG 動畫 MNG Multiple-Image Network Graphics ASF video ASF مرئي Videa ASF Видео — ASF vídeo ASF Video ASF ASF-video ASF-Video βίντεο ASF ASF video ASF-video vídeo ASF ASF bideoa ASF-video ASF video vidéo ASF físeán ASF vídeo ASF וידאו ASF ASF video ASF videó Video ASF Video ASF ASF 動画 ASF ვიდეო ASF видеосы ASF 비디오 ASF vaizdo įrašas ASF video ASF-film ASF-video ASF-video Plik wideo ASF Vídeo ASF Video ASF видео ASF Video ASF Video datoteka ASF Video ASF ASF-video відеокліп ASF Ảnh động ASF ASF 视频 ASF 視訊 ASF Advanced Streaming Format Windows Media Station file ملف محطة Windows Media Fajł Windows Media Station Файл — Windows Media Station fitxer Windows Media Station Soubor Windows Media Station Windows Media Station-fil Windows-Media-Streamingbeschreibung αρχείο Windows Media Station Windows Media Station file archivo de emisora de Windows Media Windows Media Station fitxategia Windows Media Station-tiedosto Windows Media Station fíla fichier Windows Media Station comhad Windows Media Station ficheiro de emisora de Windows Media קובץ תחנה של Windows Media Windows Media Station fájl Berkas Windows Media Station File Windows Media Station Windows Media Station ファイル Windows Media Station файлы Windows 미디어 방송국 파일 Windows Media Station failas Windows Media Station datne Windows Media Station-fil Windows Media Station-bestand Windows Media Station-fil Plik Windows Media Station Arquivo de estação do Windows Media Fișier Windows Media Station файл Windows Media Station Súbor Windows Media Station Datoteka Windows Media Station File Windows Media Station Windows Media Station-fil Windows Media Station dosyası файл Windows Media Station Tập tin Windows Media Station Windows 媒体工作站文件 Windows Media Station 檔 Windows Media video Windows Media مرئي Videa Windows Media Видео — Windows Media vídeo Windows Media Video Windows Media Windows Medie-video Windows-Media-Video βίντεο Windows Media Windows Media video vídeo de Windows Media Windows Media bideoa Windows Media -video Windows Media video vidéo Windows Media físeán Windows Media vídeo de Windows Media וידאו של Windows Media Windows Media video Windows Media videó Video Windows Media Video Windows Media Windows Media 動画 Windows Media видеосы Windows 미디어 오디오 Windows Media vaizdo įrašas Windows Media video Windows Media film Windows Media-video Windows Media-video Plik wideo Windows Media Vídeo do Windows Media Video Windows Media видео Windows Media Video Windows Media Video datoteka Windows Media Video Windows Media Windows Media-video відеокліп Windows Media Ảnh động Windows Media Windows Media 视频 Windows Media 視訊 AVI video AVI مرئي AVI video faylı Videa AVI Видео — AVI vídeo AVI Video AVI Fideo AVI AVI-video AVI-Video βίντεο AVI AVI video AVI-video vídeo AVI AVI bideoa AVI-video AVI video vidéo AVI físeán AVI vídeo AVI וידאו AVI AVI video AVI-videó Video AVI Video AVI AVI 動画 AVI ვიდეო AVI видеосы AVI 비디오 AVI vaizdo įrašas AVI video Video AVI AVI-film AVI-video AVI-video Plik wideo AVI vídeo AVI Vídeo AVI Video AVI видео AVI Video AVI Video datoteka AVI Video AVI AVI видео AVI-video відеокліп AVI Ảnh động AVI AVI 视频 AVI 視訊 AVI Audio Video Interleave NullSoft video NullSoft مرئي Videa NullSoft Видео — NullSoft vídeo NullSoft Video NullSoft NullSoft-video NullSoft-Video βίντεο Nullsoft NullSoft video NullSoft-video vídeo NullSoft NullSoft bideoa NullSoft-video NullSoft video vidéo NullSoft físeán NullSoft vídeo de NullSoft וידאו של NullSot NullSoft video NullSoft videó Video NullSoft Video NullSoft NullSoft 動画 NullSoft видеосы 널소프트 비디오 NullSoft vaizdo įrašas NullSoft video Nullsoft-film NullSoft-video NullSoft-video Plik wideo NullSoft Vídeo do NullSoft Video NullSoft видео Nullsoft Video NullSoft Video datoteka NullSoft Video NullSoft NullSoft-video відеокліп NullSoft Ảnh động NullSoft Nullsoft 视频 NullSoft 視訊 SDP multicast stream file ملف دفق متعدد البث SDP Šmatadrasny płynievy fajł SDP Файл за поток — SDP multicast fitxer de flux de multidifusió SDP Soubor vícesměrového vysílání proudu SDP SDP multicast-strømfil SDP-Multicast-Datenstromdatei SDP multicast stream file archivo de flujo multicast SDP SDP multicast korrontearen fitxategia SDP-monilähetysvirran tiedosto SDP margvarpað streymafíla fichier de flux multidiffusion SDP comhad shruth ilchraolacháin SDP ficheiro de fluxo multicast SDP קובץ שידור בזרימה SDP SDP multicast műsorfájl Berkas SDP multicast stream File stream multicast SDP SDP マルチキャストストリームファイル SDP мультикаст ағым файлы SDP 멀티캐스트 스트림 파일 SDP daugiaadresio srauto failas SDP multiraides straumes datne SDP-multicaststrøm SDP-multicast-streambestand SDP multicast straumfil Plik strumienia multicast SDP Arquivo de canal multicast SDP Fișier flux multicast SDP файл мультикаст-потока SDP Súbor viacsmerového vysielania prúdu SDP Pretočni vir večsmernega oddajanja File stream multicast SDP SDP multicast stream-fil файл потокової трансляції SDP Tập tin luồng truyền một-nhiều SDP SDP 多播流文件 SDP multicast 串流檔 SDP Session Description Protocol SGI video SGI مرئي SGI video faylı Videa SGI Видео — SGI vídeo SGI Video SGI Video SGI SGI-video SGI-Video βίντεο SGI SGI video SGI-video vídeo SGI SGI bideoa SGI-video SGI video vidéo SGI físeán SGI vídeo SGI וידאו SGI SGI video SGI-videó Video SGI Video SGI SGI 動画 SGI видеосы SGI 비디오 SGI vaizdo įrašas SGI video Video SGI SGI-film SGI-video SGI-video Plik wideo SGI vídeo SGI Vídeo SGI Video SGI видео SGI Video SGI Video datoteka SGI Video SGI SGI видео SGI-video відеокліп SGI Ảnh động SGI SGI 视频 SGI 視訊 eMusic download package حزمة تنزيل eMusic pakunak zahruzki eMusic Пакет за сваляне — eMusic paquet de descàrrega eMusic Balíček stahování eMusic eMusic-hentpakke eMusic-Download-Paket πακέτο eMusic eMusic download package paquete de descarga eMusic eMusic deskargaren paketea eMusic-imurointipaketti eMusic niðurtøkupakki paquet de téléchargement eMusic pacáiste íosluchtú eMusic paquete de descarga de eMusic חבילת הורדה של eMusic eMusic letöltési csomag paket unduh eMusic Pacchetto scaricamento eMusic eMusic ダウンロードパッケージ eMusic жүктемелер дестесі eMusic 다운로드 패키지 eMusic atsiuntimo paketas eMusic lejupielādes paciņa eMusic nedlastingspakke eMusic-downloadpakket eMusic nedlastingspakke Pobieralny pakiet eMusic Pacote de download do eMusic pachet descărcare eMusic пакет загрузок eMusic Balíček sťahovania eMusic Datoteka paketa eMusic Paketë shkarkimi eMusic eMusic-hämtningspaket пакунок завантаження eMusic gói nhạc tải xuống eMusic eMusic 下载包 eMusic 下載套件 KML geographic data بيانات جغرافية KML Географски данни — KML dades geogràfiques KML Geografická data KML Geografiske data i KML-format KML geographische Daten γεωγραφικά δεδομένα KML KML geographic data datos geográficos KML KML datu geografikoak KML landafrøðilig dáta données géographiques KML sonraí geografacha KML datos xeográficos KML מידע גאוגרפי KML KML geografski podaci KML földrajzi adatok Data geografis KML Dati geografici KML KML 地理データ KML географилық ақпараты KML 지리 정보 데이터 KML geografiniai duomenys KML ģeogrāfiskie dati KML geographic data Dane geograficzne KML Dados geográficos KML Date geografice KML географические данные KML Zemepisné údaje KML Datoteka geografskih podatkov KML KML geografisk data географічні дані KML KML 地理数据 KML 地理資料 KML Keyhole Markup Language KML geographic compressed data بيانات جغرافية مضغوطة KML Географски данни — KML, компресирани dades geogràfiques comprimides KML Komprimovaná geografická data KML KML-geografiske komprimerede data KML geographische komprimierte Daten γεωγραφικά συμπιεσμένα δεδομένα KML KML geographic compressed data datos geográficos comprimidos KML KML datu geografiko konprimituak KML landafrøðilig stappað dáta données géographiques KML compressées sonraí comhbhrúite geografacha KML datos xeográficos KML comprimidos מידע גאוגרפי דחוס KML KML geografski komprimirani podaci KML tömörített földrajzi adatok Data geografis KML terkompresi Dati geografici KML compressi KML 地理圧縮データ KML географиялық сығылған ақпарат KML 지리 정보 압축 데이터 KML geografiniai suglaudinti duomenys KML saspiesti ģeogrāfiskie dati KML geographic compressed data Skompresowane dane geograficzne KML Dados comprimidos geográficos KML Date geografice comprimate KML сжатые географические данные KML Komprimované zemepisné údaje KML Skrčeni geografski podatki KML KML geografiskt komprimerat data стиснуті географічні дані KML KML 压缩地理数据 KML 地理壓縮資料 KML Keyhole Markup Language Citrix ICA settings file ملف إعدادات Citrix ICA Fajł naładaŭ Citrix ICA Настройки — Citrix ICA fitxer de paràmetres de Citrix ICA Soubor nastavení Citrix ICA Citrix ICA-opsætningsfil Citrix-ICA-Einstellungsdatei αρχείο ρυθμίσεων Citrix ICA Citrix ICA settings file archivo de opciones de Citrix ICA Citrix ICA ezarpenen fitxategia Citrix ICA -asetustiedosto Citrix ICA stillingarfíla fichier de paramètres ICA Citrix comhad socruithe Citrix ICA ficheiro de configuracións de Citrix ICA קובץ הגדרות של Citrix ICA Citrix ICA datoteka postavki Citrix ICA beállításfájl Berkas penataan Citrix ICA File impostazioni Citrix ICA Citrix ICA 設定ファイル Citrix ICA-ის პარამეტრების ფაილი Citrix ICA баптаулар файлы 시트릭스 ICA 설정 파일 Citrix ICA parametrų failas Citrix ICA iestatījumu datne Innstillingsfil for Citrix ICA Citrix ICA-instellingen Citrix ICA-innstillingsfil Plik ustawień Citrix ICA Arquivo de configurações do Citrix ICA Fișier de configurări Citrix ICA файл настроек Citrix ICA Súbor nastavení Citrix ICA Nastavitvena datoteka Citrix ICA File rregullimesh Citrix ICA Citrix ICA-inställningsfil файл параметрів ICA Citrix Tập tin thiết lập ICA Citrix Citrix ICA 设置文件 Citrix ICA 設定值檔案 ICA Independent Computing Architecture XUL interface document مستند واجهة XUL Interfejsny dakument XUL Документ — интерфейс за XUL document d'interfície XUL Dokument rozhraní XUL XUL-grænsefladedokument XUL-Oberflächendokument έγγραφο διεπαφής XUL XUL interface document documento de interfaz XUL XUL interfazearen dokumentua XUL-käyttöliittymäasiakirja XUL markamótsskjal document d'interface XUL cáipéis chomhéadan XUL documento de interface XUL מסמך ממשק XUL XUL-felületdokumentum Dokumen antarmuka XUL Documento interfaccia XUL XUL インターフェイスドキュメント XUL интерфейс құжаты XUL 인터페이스 문서 XUL sąsajos dokumentas XUL saskarnes dokuments XUL-grensesnittdokument XUL-interface-document XUL-grensesnitt-dokument Dokument interfejsu XUL Documento de interface XUL Document interfață XUL документ интерфейса XUL Dokument rozhrania XUL Dokument vmesnika XUL Dokument interfaqe XUL XUL-gränssnittsdokument XUL arayüz belgesi документ інтерфейсу XUL Tài liệu giao diện XUL XUL 界面文档 XUL 介面文件 XUL XML User interface markup Language XPInstall installer module وحدة مثبت XPInstall Пакет — инсталация XPInstall mòdul instal·lador d'XPinstall Modul instalátoru XPInstall XPInstall-installationsmodul XPInstall-Installermodul XPInstall installer module módulo del instalador XPInstall XPInstall instalatzailearen modulua XPInstall-asennuspaketti XPInstall innleggjaramótul module d'installation XPInstall modúl suiteála XPInstall Módulo do instalador XPInstall מודול התקנה של XPInstall XPInstall telepítőmodul Modul installer XPInstall Modulo installatore XPInstall XPInstall インストーラモジュール XPInstall орнату модулі XPInstall 설치 프로그램 모듈 XPInstall įdiegiklio modulis XPInstall instalatora modulis XPInstall installeer module Moduł instalatora XPInstall Módulo de instalador XPInstall Modul de instalare XPInstall модуль установщика XPInstall Modul inštalátora XPInstall modul namestilnika XPInstall XPInstall-installeringsmodul модуль засобу встановлення XPInstall XPInstall 安装工具模块 XPInstall 安裝程式模組 Word 2007 document مستند Word 2007 Документ — Word 2007 document de Word 2007 Dokument Word 2007 Word 2007-dokument Word-2007-Dokument έγγραφο Word 2007 Word 2007 document documento de Word 2007 Word 2007 dokumentua Word 2007 -asiakirja Word 2007 skjal document Word 2007 cáipéis Word 2007 documento de Word 2007 מסמך Word 2007 Word 2007 dokument Word 2007 dokumentum Dokumen Word 2007 Documento Word 2007 Word 2007 ドキュメント Word 2007 құжаты 워드 2007 문서 Word 2007 dokumentas Word 2007 dokuments Word 2007-document Dokument Word 2007 Documento do Word 2007 Document Word 2007 документ Word 2007 Dokument Word 2007 Dokument Word 2007 Word 2007-dokument документ Word 2007 Tài liệu Word 2007 Microsoft Word 2007 文档 Word 2007 文件 Word 2007 document template Шаблон за документи — Word 2007 plantilla de document de Word 2007 Šablona dokumentu Word 2007 Word 2007-dokumentskabelon Word 2007-Dokumentvorlage πρότυπο έγγραφο Word 2007 Word 2007 document template Plantilla de documento de Word 2007 Word 2007 -asiakirjamalli modèle de document Word 2007 Plantilla de documento de Word 2007 תבנית מסמך של Word 2007 Word 2007 predložak dokumenta Word 2007 dokumentumsablon Templat dokumen Word 2007 Modello documento Word 2007 Word 2007 文書テンプレート Word 2007-ის დოკუმენტის შაბლონი Word 2007 құжатының үлгісі 워드 2007 문서 서식 Word 2007 dokumenta veidne Word 2007 document sjabloon Szablon dokumentu Word 2007 Modelo de documento do Word 2007 шаблон документа Word 2007 Predloga dokumenta Word 2007 Word 2007-dokumentmall шаблон документа Word 2007 Word 2007 文档模板 Word 2007 文件範本 PowerPoint 2007 presentation عرض تقديمي PowerPoint 2007 Презентация — PowerPoint 2007 presentació de PowerPoint 2007 Prezentace PowerPoint 2007 PowerPoint 2007-præsentation PowerPoint-2007-Präsentation παρουσίαση PowerPoint 2007 PowerPoint 2007 presentation presentación de PowerPoint 2007 PowerPoint 2007 aurkezpena PowerPoint 2007 -esitys PowerPoint 2007 framløga présentation PowerPoint 2007 láithreoireacht PowerPoint 2007 presentación de PowerPoint 2007 מצגת של PowerPoint 2007 PowerPoint 2007 prezentacija PowerPoint 2007 prezentáció Presentasi PowerPoint 2007 Presentazione standard PowerPoint 2007 PowerPoint 2007 プレゼンテーション PowerPoint 2007 презентациясы 파워포인트 2007 프리젠테이션 PowerPoint 2007 pateiktis PowerPoint 2007 prezentācija PowerPoint 2007-presentatie Prezentacja PowerPoint 2007 Apresentação do PowerPoint 2007 Prezentare PowerPoint 2007 презентация PowerPoint 2007 Prezentácia PowerPoint 2007 Predstavitev Microsoft PowerPoint 2007 PowerPoint 2007-presentation презентація PowerPoint 2007 Trình diễn PowerPoint 2007 Microsoft PowerPoint 2007 演示文稿 PowerPoint 2007 簡報 PowerPoint 2007 slide Кадър — PoerPoint 2007 dispositiva de PowerPoint 2007 Snímek PowerPoint 2007 PowerPoint 2007-slide PowerPoint 2007-Folie σλάιντ PowerPoint 2007 PowerPoint 2007 slide Diapositiva de PowerPoint 2007 diapositive PowerPoint 2007 Diaporama de PowerPoint 2007 שקופית של PowerPoint 2007 PowerPoint 2007 slajd PowerPoint 2007 dia Slide PowerPoint 2007 Diapositiva PowerPoint 2007 PowerPoint 2007 スライド PowerPoint 2007-ის სლაიდი PowerPoint 2007 слайды 파워포인트 2007 슬라이드 PowerPoint 2007 slaids PowerPoint 2007 dia Slajd PowerPoint 2007 Slide do PowerPoint 2007 слайд PowerPoint 2007 Prosojnica PowerPoint 2007 слайд PowerPoint 2007 PowerPoint 2007 文稿 PowerPoint 2007 投影片 PowerPoint 2007 show عرض PowerPoint 2007 Презентация-шоу — PowerPoint 2007 exposició de PowerPoint 2007 Prezentace PowerPoint 2007 PowerPoint 2007-dias PowerPoint-2007-Präsentation σόου PowerPoint 2007 PowerPoint 2007 show exposición de PowerPoint 2007 PowerPoint 2007 ikuskizuna PowerPoint 2007 -diaesitys PowerPoint 2007 framsýning diaporama PowerPoint 2007 taispeántas PowerPoint 2007 Exposición de PowerPoint 2007 תצוגה של PowerPoint 2007 PowerPoint 2007 prezentacija PowerPoint 2007 bemutató Presentasi PowerPoint 2007 Solo presentazione PowerPoint 2007 PowerPoint 2007 プレゼンテーション PowerPoint 2007 көрсетілімі 파워포인트 2007 쇼 PowerPoint 2007 pateiktis PowerPoint 2007 slīdrāde PowerPoint 2007 show Pokaz PowerPoint 2007 Apresentação do PowerPoint 2007 Prezentare PowerPoint 2007 презентация PowerPoint 2007 Ukážka PowerPoint 2007 Zagonska predstavitev PowerPoint 2007 PowerPoint 2007-visning показ слайдів PowerPoint 2007 Microsoft PowerPoint 2007 演示文稿 PowerPoint 2007 展示 PowerPoint 2007 presentation template Шаблон за презентации — PowerPoint 2007 plantilla de presentació de PowerPoint 2007 Šablona prezentace PowerPoint 2007 PowerPoint 2007-præsentationsskabelon PowerPoint 2007-Präsentationsvorlage πρότυπο παρουσίασης PowerPoint 2007 PowerPoint 2007 presentation template Plantilla de presentación de PowerPoint 2007 PowerPoint 2007 -esitysmalli modèle de présentation PowerPoint 2007 modelo de presentación de PowerPoint 2007 תבנית למצגת של PowerPoint 2007 PowerPoint 2007 predložak prezentacije PowerPoint 2007 bemutatósablon Templat presentasi PowerPoint 2007 Modello presentazione PowerPoint 2007 PowerPoint 2007 プレゼンテーションテンプレート PowerPoint 2007-ის პრეზენტაციის შაბლონი PowerPoint 2007 презентация шаблоны 파워포인트 2007 프리젠테이션 서식 PowerPoint 2007 prezentācijas veidne PowerPoint 2007 presentation sjabloon Szablon prezentacji PowerPoint 2007 Modelo de apresentação do PowerPoint 2007 шаблон презентации PowerPoint 2007 Predloga predstavitve PowerPoint 2007 PowerPoint 2007-presentationsmall шаблон презентації PowerPoint 2007 PowerPoint 2007 演示文稿模板 PowerPoint 2007 簡報範本 Excel 2007 spreadsheet جدول Excel 2007 Таблица — Excel 2007 full de càlcul d'Excel 2007 Sešit Excel 2007 Excel 2007-regneark Excel-2007-Tabelle φύλλο εργασίας Excel 2007 Excel 2007 spreadsheet hoja de cálculo de Excel 2007 Excel 2007 kalkulu-orria Excel 2007 -taulukko Excel 2007 rokniark feuille de calcul Excel 2007 scarbhileog Excel 2007 folla de cálculo de Excel 2007 גליון נתונים של אקסל 2007 Excel 2007 proračunska tablica Excel 2007 táblázat Lembar sebar Excel 2007 Foglio di calcolo Excel 2007 Excel 2007 スプレッドシート Excel 2007-ის ცხრილი Excel 2007 электрондық кестесі 엑셀 2007 스프레드시트 Excel 2007 skaičialentė Excel 2007 izklājlapa Excel 2007-rekenblad Arkusz Excel 2007 Planilha do Excel 2007 Foaie de calcul Excel 2007 электронная таблица Excel 2007 Zošit Excel 2007 Razpredelnica Microsoft Excel 2007 Excel 2007-kalkylblad ел. таблиця Excel 2007 Bảng tính Excel 2007 Microsoft Excel 2007 工作簿 Excel 2007 試算表 Excel 2007 spreadsheet template Шаблон за таблици — Excel 2007 plantilla de full de càlcul d'Excel 2007 Šablona sešitu Excel 2007 Excel 2007-regnearksskabelon Excel 2007-Datenblattvorlage πρότυπο φύλλου εργασίας Excel 2007 Excel 2007 spreadsheet template Plantilla de hoja de cálculo Excel 2007 Excel 2007 -taulukkomalli modèle de feuille de calcul Excel 2007 modelo de folla de cálculo Excel 2007 תבנית של גיליון נתונים של Excel 2007 Excel 2007 predložak proračunske tablice Excel 2007 táblázatsablon Templat lembar kerja Excel 2007 Modello foglio di calcolo Excel 2007 Excel 2007 スプレッドシートテンプレート Excel 2007-ის ცხრილის შაბლონი Excel 2007 кесте шаблоны 엑셀 2007 스프레드쉬트 Excel 2007 izklājlapas veidne Excel 2007 spreadsheet sjabloon Szablon arkusza Excel 2007 Modelo de planilha do Excel 2007 шаблон электронной таблицы Excel 2007 Predloga razpredelnice Excel 2007 Excel 2007-kalkylarksmall шаблон електронної таблиці Excel 2007 Excel 2007 工作表模板 Excel 2007 試算表範本 T602 document مستند T602 Dakument T602 Документ — T602 document T602 Dokument T602 T602-dokument T602-Dokument αρχείο T602 T602 document T602-dokumento documento T602 T602 dokumentua T602-asiakirja T602 skjal document T602 cáipéis T602 documento T602 מסמך T602 T602 dokument T602 dokumentum Dokumen T602 Documento T602 T602 ドキュメント T602 құжаты T602 문서 T602 dokumentas T602 dokuments T602-dokument T602-document T602-dokument Dokument T602 Documento T602 Document T602 документ T602 Dokument T602 Dokument T602 Dokument T602 T602-dokument документ T602 Tài liệu T602 T602 文档 T602 文件 Cisco VPN Settings إعدادات Cisco VPN Nałady Cisco VPN Настройки — ВЧМ на Cisco paràmetres VPN de Cisco Nastavení Cisco VPN Cisco VPN-opsætning Cisco-VPN-Einstellungen ρυθμίσεις Cisco VPN Cisco VPN Settings configuración VPN de Cisco Cisco VPN ezarpenak Cisco VPN -asetukset Cisco VPN stillingar paramètres VPN Cisco socruithe VPN Cisco configuracións de VPN de Cisco הגדרות של Cisco VPN Cisco VPN postavke Cisco VPN beállítások Penataan Cisco VPN Impostazioni VPN Cisco Cisco VPN 設定 Cisco VPN-ის პარამეტრები Cisco VPN баптаулары Cisco VPN 설정 Cisco VPN parametrai Cisco VPN iestatījumi Cisco VPN-innstillinger Cisco VPN-instellingen Cisco VPN-innstillingar Ustawienia VPN Cisco Configurações de VPN da Cisco Configurări VPN Cisco файл настроек Cisco VPN Nastavenia Cisco VPN Datoteka nastavitev Cisco VPN Rregullime VPN Cisco Cisco VPN-inställningar параметри VPN Cisco Thiết lập VPN Cisco Cisco VPN 设置 Cisco VPN 設定值 ICC profile تشكيلة OCL Цветови профил — OCL perfil ICC Profil ICC ICC-profil ICC-Profil προφίλ ICC ICC profile ICC-profilo perfil ICC ICC profila ICC-profiili ICC umhvarv profil ICC próifíl ICC perfíl ICC פרופיל ICC ICC profil ICC profil Profil ICC Profilo ICC ICC プロファイル ICC профайлы ICC 프로필 ICC profilis ICC profils ICC profiel Profil ICC Perfil ICC Profil ICC профиль ICC Profil farieb ICC Datoteka profila ICC ICC-profil профіль ICC ICC 文件 ICC 設定檔 IT 8.7 color calibration file ملف ضبط ألوان IT 8.7 Файл за цветово калибриране — IT 8.7 fitxer de calibratge de color IT 8.7 Soubor kalibrace barev IT 8.7 IT 8.7 farvekalibreringsfil IT 8.7 Farbkalibrierungsdatei αρχείο ρύθμισης χρώματος ΙΤ 8.7 IT 8.7 color calibration file archivo de calibración de color IT 8.7 IT 8.7 kolore-kalibrazioaren fitxategia IT 8.7 -värikalibrointitiedosto IT 8.7 litstillingarfíla fichier de calibration couleur IT 8.7 comhad calabraithe dathanna IT 8.7 ficheiro de calibración de cor IT 8.7 קובץ כיול צבע IT 8.7 IT 8.7 datoteka kalibracije boja IT 8.7 színkalibrációs fájl Berkas kalibrasi warna IT 8.7 File calibrazione colore IT 8.7 IT 8.7 カラーキャリブレーションファイル IT 8.7 түс баптау файлы IT 8.7 색 조율 파일 IT 8.7 spalvų kalibravimo failas IT 8.7 krāsu kalibrācijas datne IT 8.7 kleurcalibratie bestand Plik kalibracji kolorów IT 8.7 Arquivo de calibração de cor IT 8.7 Fișier de calibrare a culorii IT 8.7 файл калибровки цвета IT 8.7 Umeritvena datoteka barve IT 8.7 IT 8.7-färgkalibreringsfil файл калібрування кольорів IT 8.7 IT 8.7 色彩校准文件 IT 8.7 色彩校正檔 CCMX color correction file WinHelp help file digital photos الصور الرقمية ličbavyja zdymki Цифрови фотографии fotos digitals Digitální fotografie digitale billeder Digitale Fotos ψηφιακές φωτογραφίες digital photos fotos digitales argazki digitalak digivalokuvia talgildar myndir photos numériques grianghraif dhigiteacha fotos dixitais תמונות דיגיטליות digitalne fotografije digitális fényképek foto digital Foto digitali デジタルフォト сандық фотосуреттер 디지털 사진 skaitmeninės nuotraukos digitāla fotogrāfija digitale foto's digitale fotografi Zdjęcia cyfrowe Fotos digitais fotografii digitale цифровые фотографии Digitálne fotografie digitalne fotografije Fotografi dixhitale digitalbilder цифрові фотографії ảnh chụp số 数字化图像 數位相片 Video CD Video CD Videa CD CD — видео Video CD Video CD Video-cd Video-CD Video CD Video CD Video-KD Video CD Bideo CDa Video CD Video CD CD vidéo Video CD Video CD תקליטור וידאו Video CD Video CD Video CD Video CD ビデオ CD видео CD 비디오 CD Vaizdo CD Video CD video-CD Video-CD Video CD CD de vídeo CD video видеодиск VCD Video CD Video CD CD Video Video-cd Video CD Đĩa CD ảnh động VCD Video CD Super Video CD Super Video CD Super Video CD CD — супер видео Super Video CD Super Video CD Super Video-cd Super-Video-CD Super Video CD Super Video CD Super-Video-KD Super Video CD Super Bideo CDa Super Video CD Super Video CD Super VCD Super Video CD Super vídeo CD Super Video CD Super Video CD Super Video CD Super Video CD Super Video CD スーパービデオ CD Super Video CD 수퍼 비디오 CD Super vaizdo CD Super Video CD super-video-CD Super Video-CD Super Video CD CD de Super Vídeo (SVCD) Super Video CD компакт-диск Super Video Super Video CD Super Video CD CD Super Video Super Video CD Super Video CD Đĩa CD siêu ảnh động SVCD Super Video CD video DVD DVD مرئي videa DVD DVD — видео vídeo DVD DVD-Video video-dvd Video-DVD βίντεο DVD video DVD video-DVD DVD de vídeo bideo DVDa video-DVD video DVD DVD vidéo DVD físe DVD de vídeo DVD וידאו video DVD video DVD DVD video DVD video ビデオ DVD ვიდეო DVD видео DVD 비디오 CD vaizdo DVD video DVD video-DVD Video-DVD DVD-Video DVD de vídeo DVD video видео-DVD DVD-Video video DVD DVD video video-dvd відео-DVD đĩa DVD ảnh động 视频 DVD 視訊 DVD audio CD CD سمعي aŭdyjo CD CD — аудио CD d'àudio Zvukové CD lyd-cd Audio-CD CD ήχου audio CD Son-KD CD de sonido Audio CDa ääni-CD audio CD CD audio dlúthdhiosca fuaime CD de son תקליטור שמע hang CD CD audio CD audio オーディオ CD аудио CD 오디오 CD garso CD audio CD audio-CD lyd-CD CD-Audio CD de áudio CD audio звуковой CD Zvukové CD zvočni CD CD audio ljud-cd Müzik CD'si звуковий CD đĩa CD âm thanh 音频 CD 音訊 CD blank CD disc قرص CD فارغ čysty dysk CD CD — празно disc CD en blanc Prázdný disk CD tom cd-disk Leere CD κενό CD blank CD disc disco CD virgen CD disko hutsa tyhjä CD-levy blonk fløga CD vierge dlúthdhiosca folamh disco de CD en brancho תקליטור ריק üres CD-lemez cakram CD kosong Disco vuoto CD ブランク CD ディスク таза CD дискі 빈 CD 디스크 tuščias CD diskas tukšs CD disks blanco CD tom CD-plate Pusta płyta CD Disco CD vazio disc gol CD чистый компакт-диск Prázdny disk CD prazen CD disk Disk bosh CD tom cd-skiva boş CD diski порожній компакт-диск đĩa CD trống 空 CD 光盘 空白 CD 光碟 blank DVD disc قرص DVD فارغ čysty dysk DVD DVD — празно disc DVD en blanc Prázdný disk DVD tom dvd-disk Leere DVD κενό DVD blank DVD disc disco DVD virgen DVD disko hutsa tyhjä DVD-levy blonk margfløga DVD vierge DVD folamh disco de DVD en branco תקליטור DVD ריק üres DVD-lemez cakram DVD kosong Disco vuoto DVD ブランク DVD ディスク таза DVD дискі 빈 DVD 디스크 tuščias DVD diskas tukšs DVD disks blanco DVD tom DVD-plate Pusta płyta DVD Disco DVD vazio disc gol DVD чистый диск DVD Prázdny disk DVD prazen DVD disk Disk bosh DVD tom dvd-skiva boş DVD diski порожній диск DVD đĩa DVD trống 空 DVD 光盘 空白 DVD 光碟 blank Blu-ray disc قرص بلو-راي فارغ čysty dysk Blu-ray Blu-ray — празно disc Blu-Ray en blanc Prázdný disk Blu-ray tom Blu-ray-disk Leeres Blu-Ray-Medium κενό Blu-ray blank Blu-ray disc disco Blu-ray virgen Blu-ray disko hutsa tyhjä Blu-ray-levy blankur Blu-ray diskur disque Blu-Ray vierge diosca folamh Blu-Ray disco Blu-ray en branco תקליטור בלו־ריי ריק üres Blu-Ray lemez cakram Blu-ray kosong Disco vuoto Blu-ray ブランク Blu-ray ディスク таза Blu-ray дискі 빈 블루레이 디스크 tuščias Blu-ray diskas tukšs Blu-ray disks blanco Blu-ray-disk tom Blu-Ray-plate Pusta płyta Blu-ray Disco Blu-ray vazio disc gol Blu-ray чистый диск Blu-ray Prázdny disk Blu-ray prazen Blu-Ray disk Disk bosh Blu-ray tom Blu-ray-skiva boş Blue-ray diski порожній диск Blu-ray đĩa Blu-ray trống 空蓝光 DVD 空白 Blu-ray 光碟 blank HD DVD disc قرص HD DVD فارغ čysty dysk HD DVD HD DVD — празно disc DVD HD en blanc Prázdný disk HD DVD tom HD dvd-disk Leere HD-DVD κενό HD DVD blank HD DVD disc disco HD DVD virgen HD DVD disko hutsa tyhjä HD DVD -levy blankur HD DVD diskur disque HD-DVD vierge HD DVD folamh disco de HD DVD en branco דיסק HD DVD ריק üres HD DVD-lemez cakram HD DVD kosong Disco vuoto DVD HD ブランク HD DVD ディスク таза HD DVD дискі 빈 HD DVD 디스크 tuščias HD DVD diskas tukšs HD DVD disks blanco HD-DVD tom HD-DVD-plate Pusta płyta HD DVD Disco DVD HD vazio disc gol HD DVD чистый диск HD DVD Prázdny disk HD DVD prazen HD DVD disk Disk bosh DVD HD tom HD DVD-skiva boş HD DVD diski порожній диск HD DVD đĩa DVD HD trống 空 HD DVD 光盘 空白 HD DVD 光碟 audio DVD DVD سمعي aŭdyjo DVD DVD — аудио DVD d'àudio Zvukové DVD lyd-dvd Audio-DVD DVD ήχου audio DVD Son-DVD DVD de sonido audio DVDa ääni-DVD Ljóð DVD DVD audio DVD fuaime DVD de son DVD שמע hang DVD DVD audio DVD audio オーディオ DVD аудио DVD 오디오 DVD garso DVD audio DVD audio-DVD lyd-DVD DVD-Audio DVD de áudio DVD audio звуковой DVD Zvukové DVD zvočni DVD DVD audio ljud-dvd Müzik DVD'si звуковий DVD đĩa DVD âm thanh 音频 DVD 音訊 DVD Blu-ray video disc قرص بلو-راي مرئي Videadysk Blu-ray Blu-ray — видео disc de vídeo Blu-Ray Videodisk Blu-ray Blu-ray video-disk Blu-ray-Videoscheibe δίσκος βίντεο Blu-ray Blu-ray video disc disco de vídeo Blu-ray Blu-ray bideo-diskoa Blu-ray-videolevy Blu-ray diskur disque vidéo Blu-Ray diosca físe Blu-Ray disco de vídeo Blu-ray תקליטור וידאו מסוג בלו־ריי Blu-ray video disk Blu-ray videolemez Cakram video Blu-ray Disco video Blu-ray Blu-ray ビデオディスク Blu-ray ვიდეო დისკი Blu-ray видео дискі 블루레이 비디오 디스크 Blu-ray vaizdo diskas Blu-ray video disks Blu-ray-videodisk Blu-Ray videoplate Płyta wideo Blu-ray Disco de vídeo Blu-ray Disc video Blu-ray видеодиск Blu-ray Videodisk Blu-ray Blu-ray video disk Disk video Blu-ray Blu-ray-videoskiva відеодиск Blu-ray Đĩa ảnh động Blu-ray 蓝光视频光盘 Blu-ray 視訊光碟 HD DVD video disc قرص HD DVD مرئي Videadysk HD DVD HD DVD — видео disc de vídeo DVD HD Videodisk HD DVD HD DVD-videodisk HD-DVD-Videoscheibe δίσκος βίντεο HD DVD HD DVD video disc disco de vídeo HD DVD HD DVD bideo-diskoa HD DVD -videolevy HD DVD video diskur disque vidéo HD DVD diosca físe HD DVD disco de vídeo HD DVD תקליטור וידאו HD DVD HD DVD video disk HD DVD videolemez Cakram video HD DVD Disco video DVD HD HD DVD ビデオディスク HD DVD видео дискі HD DVD 비디오 디스크 HD DVD vaizdo diskas HD DVD video disks HD-DVD-videodisk HD-DVD-videodisk Płyta wideo HD DVD Disco de vídeo HD DVD Disc video HD DVD видеодиск HD DVD Videodisk HD DVD HD DVD video disk Disk video DVD HD HD DVD-videoskiva відеодиск HD DVD Đĩa ảnh động DVD HD HD DVD 视频光盘 HD DVD 視訊光碟 e-book reader Четец на е-книги lector de llibres electrònics Čtečka elektronických knih e-bogslæser E-Book-Leser αναγνώστης ηλεκτρονικών βιβλίων e-book reader lector de libros electrónicos e-kirjan lukulaite lecteur de livre numérique lector de libros electrónicos קורא ספרים אלקטרוניים čitač e-knjiga e-könyvolvasó Pembaca e-book Lettore e-book 電子書籍リーダー электронды кітаптарды оқу құрылғысы 전자책 리더 e-grāmatu lasītājs e-book reader Czytnik e-booków Leitor de e-book устройство для чтения электронных книг Bralnik elektronskih knjig e-book-läsare e-kitap okuyucu пристрій для читання електронних книг 电子书阅读器 e-book 閱讀器 Picture CD Picture CD Picture CD CD — изображения Picture CD Picture CD Billedcd Bild-CD CD εικόνων Picture CD Picture CD Picture CD Picture CD Picture CD CD Picture Picture CD Picture CD תקליטור תמונות Picture CD CD Gambar Picture CD ピクチャー CD Picture CD Picture CD Paveikslėlių CD Attēlu CD foto-CD Bilete-CD Picture CD CD de Fotos CD cu fotografii Picture CD Picture CD Slikovni CD Picture CD Picture CD Resim CD'si CD з зображеннями Đĩa CD ảnh 柯达 Picture CD 圖片 CD portable audio player مشغل الملفات المسموعة المحمولة pieranosny aŭdyjoplayer Преносим аудио плеър reproductor d'àudio portàtil Přenosný zvukový přehrávač bærbar lydafspiller Portables Audio-Wiedergabegerät φορητός αναπαραγωγέας μουσικής portable audio player dispositivo de sonido portable audio erreproduzigailu eramangarria siirrettävä äänisoitin leysur ljóðavspælari lecteur audio portable seinnteoir iniompartha fuaime dispositivo de son portábel נגן מוזיקה נייד prenosivi audio svirač hordozható zenelejátszó pemutar audio portable Lettore audio portabile ポータブルオーディオプレイヤー тасымалы аудио плеер 휴대용 오디오 플레이어 nešiojamasis garso leistuvas portatīvais audio atskaņotājs draagbare audiospeler portable audio layer Przenośny odtwarzacz dźwięku Reprodutor de áudio portátil player audio portabil портативный аудиопроигрыватель Prenosný hudobný prehrávač prenosni predvajalnik zvoka Lexues audio portativ bärbar ljudspelare портативний аудіопрогравач bộ phát nhạc di động 便携式音频播放器 可攜式音訊播放程式 software برنامج prahrama Софтуер programari Software software Software λογισμικό software software softwarea ohjelmisto ritbúnaður logiciel bogearraí software תכנה softver szoftver peranti lunak Software ソフトウェア პროგრამული უზრუნველყოფა бағдарламалық қамтама 소프트웨어 programinė įranga programmatūra software programvare Oprogramowanie Aplicativo software программное обеспечение Softvér programska oprema Software programvara програмне забезпечення phần mềm 软件 軟體 UNIX software برنامج يونكس Софтуер за UNIX programari UNIX Software UNIX UNIX-programmer UNIX Software λογισμικό UNIX UNIX software software de UNIX UNIXeko softwarea UNIX-ohjelmisto UNIX ritbúnaður logiciel UNIX bogearraí UNIX Software de UNIX תוכנת UNIX UNIX softver UNIX-szoftver Peranti lunak UNIX Software UNIX UNIX ソフトウェア UNIX бағдарламасы UNIX 소프트웨어 UNIX programinė įranga UNIX programmatūra UNIX software Oprogramowanie systemu UNIX Software UNIX Software UNIX программа UNIX Softvér UNIX Programska datoteka UNIX UNIX-programvara UNIX yazılımı програмне забезпечення UNIX UNIX 软件 UNIX 軟體 Windows software برنامج ويندوز Софтуер — Windows programari de Windows Software Windows Windowsprogram Windows-Software λογισμικό Windows Windows software software de Windows Windows-eko softwarea Windows-ohjelmisto Windows ritbúnaður logiciel Windows bogearraí Windows Software de Windows תוכנה לWindows Windows softver Windows-szoftver Piranti lunak Windows Software Windows Windows ソフトウェア Windows бағдарламасы Windows 소프트웨어 Windows programinė įranga Windows programmatūra Windows software Oprogramowanie systemu Windows Programa do Windows Software Windows программа Windows Softvér Windows Programska oprema za okolje Windows Windows-program програмне забезпечення Windows Windows 软件 Windows 軟體 TriG RDF document TriG TriG RDF Graph Triple Language tdesktop-1.2.17/Telegram/Resources/qrc/telegram.qrc000066400000000000000000000032221326245125100222450ustar00rootroot00000000000000 ../fonts/OpenSans-Regular.ttf ../fonts/OpenSans-Bold.ttf ../fonts/OpenSans-Semibold.ttf ../art/bg.jpg ../art/bg_initial.jpg ../art/logo_256.png ../art/logo_256_no_margin.png ../art/sunrise.jpg ../night.tdesktop-theme ../sounds/msg_incoming.mp3 ../sounds/call_incoming.mp3 ../sounds/call_outgoing.mp3 ../sounds/call_busy.mp3 ../sounds/call_connect.mp3 ../sounds/call_end.mp3 qmime/freedesktop.org.xml ../langs/lang_it.strings ../langs/lang_es.strings ../langs/lang_de.strings ../langs/lang_nl.strings ../langs/lang_pt_BR.strings ../langs/lang_ko.strings tdesktop-1.2.17/Telegram/Resources/qrc/telegram_emoji.qrc000066400000000000000000000004761326245125100234400ustar00rootroot00000000000000 ../art/emoji.webp ../art/emoji_125x.webp ../art/emoji_150x.webp ../art/emoji_200x.webp tdesktop-1.2.17/Telegram/Resources/qrc/telegram_emoji_large.qrc000066400000000000000000000001741326245125100246050ustar00rootroot00000000000000 ../art/emoji_250x.webp tdesktop-1.2.17/Telegram/Resources/qrc/telegram_linux.qrc000066400000000000000000000001611326245125100234630ustar00rootroot00000000000000 ../etc/qt_linux.conf tdesktop-1.2.17/Telegram/Resources/qrc/telegram_mac.qrc000066400000000000000000000000151326245125100230620ustar00rootroot00000000000000 tdesktop-1.2.17/Telegram/Resources/qrc/telegram_sounds.qrc000066400000000000000000000007171326245125100236460ustar00rootroot00000000000000 ../sounds/msg_incoming.mp3 ../sounds/call_busy.mp3 ../sounds/call_connect.mp3 ../sounds/call_end.mp3 ../sounds/call_incoming.mp3 ../sounds/call_outgoing.mp3 tdesktop-1.2.17/Telegram/Resources/qrc/telegram_wnd.qrc000066400000000000000000000001571326245125100231210ustar00rootroot00000000000000 ../etc/qt_win.conf tdesktop-1.2.17/Telegram/Resources/scheme.tl000066400000000000000000002473301326245125100207700ustar00rootroot00000000000000// Core types (no need to gen) //vector#1cb5c415 {t:Type} # [ t ] = Vector t; /////////////////////////////// /////////////////// Layer cons /////////////////////////////// //invokeAfterMsg#cb9f372d msg_id:long query:!X = X; //invokeAfterMsgs#3dc4b4f0 msg_ids:Vector query:!X = X; //invokeWithLayer1#53835315 query:!X = X; //invokeWithLayer2#289dd1f6 query:!X = X; //invokeWithLayer3#b7475268 query:!X = X; //invokeWithLayer4#dea0d430 query:!X = X; //invokeWithLayer5#417a57ae query:!X = X; //invokeWithLayer6#3a64d54d query:!X = X; //invokeWithLayer7#a5be56d3 query:!X = X; //invokeWithLayer8#e9abd9fd query:!X = X; //invokeWithLayer9#76715a63 query:!X = X; //invokeWithLayer10#39620c41 query:!X = X; //invokeWithLayer11#a6b88fdf query:!X = X; //invokeWithLayer12#dda60d3c query:!X = X; //invokeWithLayer13#427c8ea2 query:!X = X; //invokeWithLayer14#2b9b08fa query:!X = X; //invokeWithLayer15#b4418b64 query:!X = X; //invokeWithLayer16#cf5f0987 query:!X = X; //invokeWithLayer17#50858a19 query:!X = X; //invokeWithLayer18#1c900537 query:!X = X; //invokeWithLayer#da9b0d0d layer:int query:!X = X; // after 18 layer /////////////////////////////// /// Authorization key creation /////////////////////////////// resPQ#05162463 nonce:int128 server_nonce:int128 pq:string server_public_key_fingerprints:Vector = ResPQ; p_q_inner_data#83c95aec pq:string p:string q:string nonce:int128 server_nonce:int128 new_nonce:int256 = P_Q_inner_data; server_DH_params_fail#79cb045d nonce:int128 server_nonce:int128 new_nonce_hash:int128 = Server_DH_Params; server_DH_params_ok#d0e8075c nonce:int128 server_nonce:int128 encrypted_answer:string = Server_DH_Params; server_DH_inner_data#b5890dba nonce:int128 server_nonce:int128 g:int dh_prime:string g_a:string server_time:int = Server_DH_inner_data; client_DH_inner_data#6643b654 nonce:int128 server_nonce:int128 retry_id:long g_b:string = Client_DH_Inner_Data; dh_gen_ok#3bcbf734 nonce:int128 server_nonce:int128 new_nonce_hash1:int128 = Set_client_DH_params_answer; dh_gen_retry#46dc1fb9 nonce:int128 server_nonce:int128 new_nonce_hash2:int128 = Set_client_DH_params_answer; dh_gen_fail#a69dae02 nonce:int128 server_nonce:int128 new_nonce_hash3:int128 = Set_client_DH_params_answer; destroy_auth_key_ok#f660e1d4 = DestroyAuthKeyRes; destroy_auth_key_none#0a9f2259 = DestroyAuthKeyRes; destroy_auth_key_fail#ea109b13 = DestroyAuthKeyRes; ---functions--- req_pq#60469778 nonce:int128 = ResPQ; req_pq_multi#be7e8ef1 nonce:int128 = ResPQ; req_DH_params#d712e4be nonce:int128 server_nonce:int128 p:string q:string public_key_fingerprint:long encrypted_data:string = Server_DH_Params; set_client_DH_params#f5045f1f nonce:int128 server_nonce:int128 encrypted_data:string = Set_client_DH_params_answer; destroy_auth_key#d1435160 = DestroyAuthKeyRes; /////////////////////////////// ////////////// System messages /////////////////////////////// ---types--- msgs_ack#62d6b459 msg_ids:Vector = MsgsAck; bad_msg_notification#a7eff811 bad_msg_id:long bad_msg_seqno:int error_code:int = BadMsgNotification; bad_server_salt#edab447b bad_msg_id:long bad_msg_seqno:int error_code:int new_server_salt:long = BadMsgNotification; msgs_state_req#da69fb52 msg_ids:Vector = MsgsStateReq; msgs_state_info#04deb57d req_msg_id:long info:string = MsgsStateInfo; msgs_all_info#8cc0d131 msg_ids:Vector info:string = MsgsAllInfo; msg_detailed_info#276d3ec6 msg_id:long answer_msg_id:long bytes:int status:int = MsgDetailedInfo; msg_new_detailed_info#809db6df answer_msg_id:long bytes:int status:int = MsgDetailedInfo; msg_resend_req#7d861a08 msg_ids:Vector = MsgResendReq; //rpc_result#f35c6d01 req_msg_id:long result:Object = RpcResult; // parsed manually rpc_error#2144ca19 error_code:int error_message:string = RpcError; rpc_answer_unknown#5e2ad36e = RpcDropAnswer; rpc_answer_dropped_running#cd78e586 = RpcDropAnswer; rpc_answer_dropped#a43ad8b7 msg_id:long seq_no:int bytes:int = RpcDropAnswer; future_salt#0949d9dc valid_since:int valid_until:int salt:long = FutureSalt; future_salts#ae500895 req_msg_id:long now:int salts:vector = FutureSalts; pong#347773c5 msg_id:long ping_id:long = Pong; destroy_session_ok#e22045fc session_id:long = DestroySessionRes; destroy_session_none#62d350c9 session_id:long = DestroySessionRes; new_session_created#9ec20908 first_msg_id:long unique_id:long server_salt:long = NewSession; //message msg_id:long seqno:int bytes:int body:Object = Message; // parsed manually //msg_container#73f1f8dc messages:vector = MessageContainer; // parsed manually //msg_copy#e06046b2 orig_message:Message = MessageCopy; // parsed manually, not used - use msg_container //gzip_packed#3072cfa1 packed_data:string = Object; // parsed manually http_wait#9299359f max_delay:int wait_after:int max_wait:int = HttpWait; ipPort ipv4:int port:int = IpPort; help.configSimple#d997c3c5 date:int expires:int dc_id:int ip_port_list:Vector = help.ConfigSimple; ---functions--- rpc_drop_answer#58e4a740 req_msg_id:long = RpcDropAnswer; get_future_salts#b921bd04 num:int = FutureSalts; ping#7abe77ec ping_id:long = Pong; ping_delay_disconnect#f3427b8c ping_id:long disconnect_delay:int = Pong; destroy_session#e7512126 session_id:long = DestroySessionRes; contest.saveDeveloperInfo#9a5f6e95 vk_id:int name:string phone_number:string age:int city:string = Bool; /////////////////////////////// ///////// Main application API /////////////////////////////// ---types--- boolFalse#bc799737 = Bool; boolTrue#997275b5 = Bool; true#3fedd339 = True; vector#1cb5c415 {t:Type} # [ t ] = Vector t; error#c4b9f9bb code:int text:string = Error; null#56730bcc = Null; inputPeerEmpty#7f3b18ea = InputPeer; inputPeerSelf#7da07ec9 = InputPeer; inputPeerChat#179be863 chat_id:int = InputPeer; inputPeerUser#7b8e7de6 user_id:int access_hash:long = InputPeer; inputPeerChannel#20adaef8 channel_id:int access_hash:long = InputPeer; inputUserEmpty#b98886cf = InputUser; inputUserSelf#f7c1b13f = InputUser; inputUser#d8292816 user_id:int access_hash:long = InputUser; inputPhoneContact#f392b7f4 client_id:long phone:string first_name:string last_name:string = InputContact; inputFile#f52ff27f id:long parts:int name:string md5_checksum:string = InputFile; inputFileBig#fa4f0bb5 id:long parts:int name:string = InputFile; inputMediaEmpty#9664f57f = InputMedia; inputMediaUploadedPhoto#1e287d04 flags:# file:InputFile stickers:flags.0?Vector ttl_seconds:flags.1?int = InputMedia; inputMediaPhoto#b3ba0635 flags:# id:InputPhoto ttl_seconds:flags.0?int = InputMedia; inputMediaGeoPoint#f9c44144 geo_point:InputGeoPoint = InputMedia; inputMediaContact#a6e45987 phone_number:string first_name:string last_name:string = InputMedia; inputMediaUploadedDocument#5b38c6c1 flags:# nosound_video:flags.3?true file:InputFile thumb:flags.2?InputFile mime_type:string attributes:Vector stickers:flags.0?Vector ttl_seconds:flags.1?int = InputMedia; inputMediaDocument#23ab23d2 flags:# id:InputDocument ttl_seconds:flags.0?int = InputMedia; inputMediaVenue#c13d1c11 geo_point:InputGeoPoint title:string address:string provider:string venue_id:string venue_type:string = InputMedia; inputMediaGifExternal#4843b0fd url:string q:string = InputMedia; inputMediaPhotoExternal#e5bbfe1a flags:# url:string ttl_seconds:flags.0?int = InputMedia; inputMediaDocumentExternal#fb52dc99 flags:# url:string ttl_seconds:flags.0?int = InputMedia; inputMediaGame#d33f43f3 id:InputGame = InputMedia; inputMediaInvoice#f4e096c3 flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:string = InputMedia; inputMediaGeoLive#7b1a118f geo_point:InputGeoPoint period:int = InputMedia; inputChatPhotoEmpty#1ca48f57 = InputChatPhoto; inputChatUploadedPhoto#927c55b4 file:InputFile = InputChatPhoto; inputChatPhoto#8953ad37 id:InputPhoto = InputChatPhoto; inputGeoPointEmpty#e4c123d6 = InputGeoPoint; inputGeoPoint#f3b7acc9 lat:double long:double = InputGeoPoint; inputPhotoEmpty#1cd7bf0d = InputPhoto; inputPhoto#fb95c6c4 id:long access_hash:long = InputPhoto; inputFileLocation#14637196 volume_id:long local_id:int secret:long = InputFileLocation; inputEncryptedFileLocation#f5235d55 id:long access_hash:long = InputFileLocation; inputDocumentFileLocation#430f0724 id:long access_hash:long version:int = InputFileLocation; inputAppEvent#770656a8 time:double type:string peer:long data:string = InputAppEvent; peerUser#9db1bc6d user_id:int = Peer; peerChat#bad0e5bb chat_id:int = Peer; peerChannel#bddde532 channel_id:int = Peer; storage.fileUnknown#aa963b05 = storage.FileType; storage.filePartial#40bc6f52 = storage.FileType; storage.fileJpeg#7efe0e = storage.FileType; storage.fileGif#cae1aadf = storage.FileType; storage.filePng#a4f63c0 = storage.FileType; storage.filePdf#ae1e508d = storage.FileType; storage.fileMp3#528a0677 = storage.FileType; storage.fileMov#4b09ebbc = storage.FileType; storage.fileMp4#b3cea0e4 = storage.FileType; storage.fileWebp#1081464c = storage.FileType; fileLocationUnavailable#7c596b46 volume_id:long local_id:int secret:long = FileLocation; fileLocation#53d69076 dc_id:int volume_id:long local_id:int secret:long = FileLocation; userEmpty#200250ba id:int = User; user#2e13f4c3 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true id:int access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?string bot_inline_placeholder:flags.19?string lang_code:flags.22?string = User; userProfilePhotoEmpty#4f11bae1 = UserProfilePhoto; userProfilePhoto#d559d8c8 photo_id:long photo_small:FileLocation photo_big:FileLocation = UserProfilePhoto; userStatusEmpty#9d05049 = UserStatus; userStatusOnline#edb93949 expires:int = UserStatus; userStatusOffline#8c703f was_online:int = UserStatus; userStatusRecently#e26f42f1 = UserStatus; userStatusLastWeek#7bf09fc = UserStatus; userStatusLastMonth#77ebc742 = UserStatus; chatEmpty#9ba2d800 id:int = Chat; chat#d91cdd54 flags:# creator:flags.0?true kicked:flags.1?true left:flags.2?true admins_enabled:flags.3?true admin:flags.4?true deactivated:flags.5?true id:int title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel = Chat; chatForbidden#7328bdb id:int title:string = Chat; channel#450b7115 flags:# creator:flags.0?true left:flags.2?true editor:flags.3?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true democracy:flags.10?true signatures:flags.11?true min:flags.12?true id:int access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int version:int restriction_reason:flags.9?string admin_rights:flags.14?ChannelAdminRights banned_rights:flags.15?ChannelBannedRights participants_count:flags.17?int = Chat; channelForbidden#289da732 flags:# broadcast:flags.5?true megagroup:flags.8?true id:int access_hash:long title:string until_date:flags.16?int = Chat; chatFull#2e02a614 id:int participants:ChatParticipants chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector = ChatFull; channelFull#76af5481 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true id:int about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?int migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int = ChatFull; chatParticipant#c8d7493e user_id:int inviter_id:int date:int = ChatParticipant; chatParticipantCreator#da13538a user_id:int = ChatParticipant; chatParticipantAdmin#e2d6e436 user_id:int inviter_id:int date:int = ChatParticipant; chatParticipantsForbidden#fc900c2b flags:# chat_id:int self_participant:flags.0?ChatParticipant = ChatParticipants; chatParticipants#3f460fed chat_id:int participants:Vector version:int = ChatParticipants; chatPhotoEmpty#37c1011c = ChatPhoto; chatPhoto#6153276a photo_small:FileLocation photo_big:FileLocation = ChatPhoto; messageEmpty#83e5de54 id:int = Message; message#44f9b43d flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true id:int from_id:flags.8?int to_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to_msg_id:flags.3?int date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long = Message; messageService#9e19a1f6 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true id:int from_id:flags.8?int to_id:Peer reply_to_msg_id:flags.3?int date:int action:MessageAction = Message; messageMediaEmpty#3ded6320 = MessageMedia; messageMediaPhoto#695150d7 flags:# photo:flags.0?Photo ttl_seconds:flags.2?int = MessageMedia; messageMediaGeo#56e0d474 geo:GeoPoint = MessageMedia; messageMediaContact#5e7d2f39 phone_number:string first_name:string last_name:string user_id:int = MessageMedia; messageMediaUnsupported#9f84f49e = MessageMedia; messageMediaDocument#9cb070d7 flags:# document:flags.0?Document ttl_seconds:flags.2?int = MessageMedia; messageMediaWebPage#a32dd600 webpage:WebPage = MessageMedia; messageMediaVenue#2ec0533f geo:GeoPoint title:string address:string provider:string venue_id:string venue_type:string = MessageMedia; messageMediaGame#fdb19008 game:Game = MessageMedia; messageMediaInvoice#84551347 flags:# shipping_address_requested:flags.1?true test:flags.3?true title:string description:string photo:flags.0?WebDocument receipt_msg_id:flags.2?int currency:string total_amount:long start_param:string = MessageMedia; messageMediaGeoLive#7c3c2609 geo:GeoPoint period:int = MessageMedia; messageActionEmpty#b6aef7b0 = MessageAction; messageActionChatCreate#a6638b9a title:string users:Vector = MessageAction; messageActionChatEditTitle#b5a1ce5a title:string = MessageAction; messageActionChatEditPhoto#7fcb13a8 photo:Photo = MessageAction; messageActionChatDeletePhoto#95e3fbef = MessageAction; messageActionChatAddUser#488a7337 users:Vector = MessageAction; messageActionChatDeleteUser#b2ae9b0c user_id:int = MessageAction; messageActionChatJoinedByLink#f89cf5e8 inviter_id:int = MessageAction; messageActionChannelCreate#95d2ac92 title:string = MessageAction; messageActionChatMigrateTo#51bdb021 channel_id:int = MessageAction; messageActionChannelMigrateFrom#b055eaee title:string chat_id:int = MessageAction; messageActionPinMessage#94bd38ed = MessageAction; messageActionHistoryClear#9fbab604 = MessageAction; messageActionGameScore#92a72876 game_id:long score:int = MessageAction; messageActionPaymentSentMe#8f31b327 flags:# currency:string total_amount:long payload:bytes info:flags.0?PaymentRequestedInfo shipping_option_id:flags.1?string charge:PaymentCharge = MessageAction; messageActionPaymentSent#40699cd0 currency:string total_amount:long = MessageAction; messageActionPhoneCall#80e11a7f flags:# call_id:long reason:flags.0?PhoneCallDiscardReason duration:flags.1?int = MessageAction; messageActionScreenshotTaken#4792929b = MessageAction; messageActionCustomAction#fae69f56 message:string = MessageAction; messageActionBotAllowed#abe9affe domain:string = MessageAction; dialog#e4def5db flags:# pinned:flags.2?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage = Dialog; photoEmpty#2331b22d id:long = Photo; photo#9288dd29 flags:# has_stickers:flags.0?true id:long access_hash:long date:int sizes:Vector = Photo; photoSizeEmpty#e17e23c type:string = PhotoSize; photoSize#77bfb61b type:string location:FileLocation w:int h:int size:int = PhotoSize; photoCachedSize#e9a734fa type:string location:FileLocation w:int h:int bytes:bytes = PhotoSize; geoPointEmpty#1117dd5f = GeoPoint; geoPoint#2049d70c long:double lat:double = GeoPoint; auth.checkedPhone#811ea28e phone_registered:Bool = auth.CheckedPhone; auth.sentCode#5e002502 flags:# phone_registered:flags.0?true type:auth.SentCodeType phone_code_hash:string next_type:flags.1?auth.CodeType timeout:flags.2?int = auth.SentCode; auth.authorization#cd050916 flags:# tmp_sessions:flags.0?int user:User = auth.Authorization; auth.exportedAuthorization#df969c2d id:int bytes:bytes = auth.ExportedAuthorization; inputNotifyPeer#b8bc5b0c peer:InputPeer = InputNotifyPeer; inputNotifyUsers#193b4417 = InputNotifyPeer; inputNotifyChats#4a95e84e = InputNotifyPeer; inputNotifyAll#a429b886 = InputNotifyPeer; inputPeerNotifyEventsEmpty#f03064d8 = InputPeerNotifyEvents; inputPeerNotifyEventsAll#e86a2c74 = InputPeerNotifyEvents; inputPeerNotifySettings#38935eb2 flags:# show_previews:flags.0?true silent:flags.1?true mute_until:int sound:string = InputPeerNotifySettings; peerNotifyEventsEmpty#add53cb3 = PeerNotifyEvents; peerNotifyEventsAll#6d1ded88 = PeerNotifyEvents; peerNotifySettingsEmpty#70a68512 = PeerNotifySettings; peerNotifySettings#9acda4c0 flags:# show_previews:flags.0?true silent:flags.1?true mute_until:int sound:string = PeerNotifySettings; peerSettings#818426cd flags:# report_spam:flags.0?true = PeerSettings; wallPaper#ccb03657 id:int title:string sizes:Vector color:int = WallPaper; wallPaperSolid#63117f24 id:int title:string bg_color:int color:int = WallPaper; inputReportReasonSpam#58dbcab8 = ReportReason; inputReportReasonViolence#1e22c78d = ReportReason; inputReportReasonPornography#2e59d922 = ReportReason; inputReportReasonOther#e1746d0a text:string = ReportReason; userFull#f220f3f flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true user:User about:flags.1?string link:contacts.Link profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo common_chats_count:int = UserFull; contact#f911c994 user_id:int mutual:Bool = Contact; importedContact#d0028438 user_id:int client_id:long = ImportedContact; contactBlocked#561bc879 user_id:int date:int = ContactBlocked; contactStatus#d3680c61 user_id:int status:UserStatus = ContactStatus; contacts.link#3ace484c my_link:ContactLink foreign_link:ContactLink user:User = contacts.Link; contacts.contactsNotModified#b74ba9d2 = contacts.Contacts; contacts.contacts#eae87e42 contacts:Vector saved_count:int users:Vector = contacts.Contacts; contacts.importedContacts#77d01c3b imported:Vector popular_invites:Vector retry_contacts:Vector users:Vector = contacts.ImportedContacts; contacts.blocked#1c138d15 blocked:Vector users:Vector = contacts.Blocked; contacts.blockedSlice#900802a1 count:int blocked:Vector users:Vector = contacts.Blocked; messages.dialogs#15ba6c40 dialogs:Vector

    messages:Vector chats:Vector users:Vector = messages.Dialogs; messages.dialogsSlice#71e094f3 count:int dialogs:Vector messages:Vector chats:Vector users:Vector = messages.Dialogs; messages.messages#8c718e87 messages:Vector chats:Vector users:Vector = messages.Messages; messages.messagesSlice#b446ae3 count:int messages:Vector chats:Vector users:Vector = messages.Messages; messages.channelMessages#99262e37 flags:# pts:int count:int messages:Vector chats:Vector users:Vector = messages.Messages; messages.messagesNotModified#74535f21 count:int = messages.Messages; messages.chats#64ff9fd5 chats:Vector = messages.Chats; messages.chatsSlice#9cd81144 count:int chats:Vector = messages.Chats; messages.chatFull#e5d7d19c full_chat:ChatFull chats:Vector users:Vector = messages.ChatFull; messages.affectedHistory#b45c69d1 pts:int pts_count:int offset:int = messages.AffectedHistory; inputMessagesFilterEmpty#57e2f66c = MessagesFilter; inputMessagesFilterPhotos#9609a51c = MessagesFilter; inputMessagesFilterVideo#9fc00e65 = MessagesFilter; inputMessagesFilterPhotoVideo#56e9f0e4 = MessagesFilter; inputMessagesFilterDocument#9eddf188 = MessagesFilter; inputMessagesFilterUrl#7ef0dd87 = MessagesFilter; inputMessagesFilterGif#ffc86587 = MessagesFilter; inputMessagesFilterVoice#50f5c392 = MessagesFilter; inputMessagesFilterMusic#3751b49e = MessagesFilter; inputMessagesFilterChatPhotos#3a20ecb8 = MessagesFilter; inputMessagesFilterPhoneCalls#80c99768 flags:# missed:flags.0?true = MessagesFilter; inputMessagesFilterRoundVoice#7a7c17a4 = MessagesFilter; inputMessagesFilterRoundVideo#b549da53 = MessagesFilter; inputMessagesFilterMyMentions#c1f8e69a = MessagesFilter; inputMessagesFilterGeo#e7026d0d = MessagesFilter; inputMessagesFilterContacts#e062db83 = MessagesFilter; updateNewMessage#1f2b0afd message:Message pts:int pts_count:int = Update; updateMessageID#4e90bfd6 id:int random_id:long = Update; updateDeleteMessages#a20db0e5 messages:Vector pts:int pts_count:int = Update; updateUserTyping#5c486927 user_id:int action:SendMessageAction = Update; updateChatUserTyping#9a65ea1f chat_id:int user_id:int action:SendMessageAction = Update; updateChatParticipants#7761198 participants:ChatParticipants = Update; updateUserStatus#1bfbd823 user_id:int status:UserStatus = Update; updateUserName#a7332b73 user_id:int first_name:string last_name:string username:string = Update; updateUserPhoto#95313b0c user_id:int date:int photo:UserProfilePhoto previous:Bool = Update; updateContactRegistered#2575bbb9 user_id:int date:int = Update; updateContactLink#9d2e67c5 user_id:int my_link:ContactLink foreign_link:ContactLink = Update; updateNewEncryptedMessage#12bcbd9a message:EncryptedMessage qts:int = Update; updateEncryptedChatTyping#1710f156 chat_id:int = Update; updateEncryption#b4a2e88d chat:EncryptedChat date:int = Update; updateEncryptedMessagesRead#38fe25b7 chat_id:int max_date:int date:int = Update; updateChatParticipantAdd#ea4b0e5c chat_id:int user_id:int inviter_id:int date:int version:int = Update; updateChatParticipantDelete#6e5f8c22 chat_id:int user_id:int version:int = Update; updateDcOptions#8e5e9873 dc_options:Vector = Update; updateUserBlocked#80ece81a user_id:int blocked:Bool = Update; updateNotifySettings#bec268ef peer:NotifyPeer notify_settings:PeerNotifySettings = Update; updateServiceNotification#ebe46819 flags:# popup:flags.0?true inbox_date:flags.1?int type:string message:string media:MessageMedia entities:Vector = Update; updatePrivacy#ee3b272a key:PrivacyKey rules:Vector = Update; updateUserPhone#12b9417b user_id:int phone:string = Update; updateReadHistoryInbox#9961fd5c peer:Peer max_id:int pts:int pts_count:int = Update; updateReadHistoryOutbox#2f2f21bf peer:Peer max_id:int pts:int pts_count:int = Update; updateWebPage#7f891213 webpage:WebPage pts:int pts_count:int = Update; updateReadMessagesContents#68c13933 messages:Vector pts:int pts_count:int = Update; updateChannelTooLong#eb0467fb flags:# channel_id:int pts:flags.0?int = Update; updateChannel#b6d45656 channel_id:int = Update; updateNewChannelMessage#62ba04d9 message:Message pts:int pts_count:int = Update; updateReadChannelInbox#4214f37f channel_id:int max_id:int = Update; updateDeleteChannelMessages#c37521c9 channel_id:int messages:Vector pts:int pts_count:int = Update; updateChannelMessageViews#98a12b4b channel_id:int id:int views:int = Update; updateChatAdmins#6e947941 chat_id:int enabled:Bool version:int = Update; updateChatParticipantAdmin#b6901959 chat_id:int user_id:int is_admin:Bool version:int = Update; updateNewStickerSet#688a30aa stickerset:messages.StickerSet = Update; updateStickerSetsOrder#bb2d201 flags:# masks:flags.0?true order:Vector = Update; updateStickerSets#43ae3dec = Update; updateSavedGifs#9375341e = Update; updateBotInlineQuery#54826690 flags:# query_id:long user_id:int query:string geo:flags.0?GeoPoint offset:string = Update; updateBotInlineSend#e48f964 flags:# user_id:int query:string geo:flags.0?GeoPoint id:string msg_id:flags.1?InputBotInlineMessageID = Update; updateEditChannelMessage#1b3f4df7 message:Message pts:int pts_count:int = Update; updateChannelPinnedMessage#98592475 channel_id:int id:int = Update; updateBotCallbackQuery#e73547e1 flags:# query_id:long user_id:int peer:Peer msg_id:int chat_instance:long data:flags.0?bytes game_short_name:flags.1?string = Update; updateEditMessage#e40370a3 message:Message pts:int pts_count:int = Update; updateInlineBotCallbackQuery#f9d27a5a flags:# query_id:long user_id:int msg_id:InputBotInlineMessageID chat_instance:long data:flags.0?bytes game_short_name:flags.1?string = Update; updateReadChannelOutbox#25d6c9c7 channel_id:int max_id:int = Update; updateDraftMessage#ee2bb969 peer:Peer draft:DraftMessage = Update; updateReadFeaturedStickers#571d2742 = Update; updateRecentStickers#9a422c20 = Update; updateConfig#a229dd06 = Update; updatePtsChanged#3354678f = Update; updateChannelWebPage#40771900 channel_id:int webpage:WebPage pts:int pts_count:int = Update; updateDialogPinned#19d27f3c flags:# pinned:flags.0?true peer:DialogPeer = Update; updatePinnedDialogs#ea4cb65b flags:# order:flags.0?Vector = Update; updateBotWebhookJSON#8317c0c3 data:DataJSON = Update; updateBotWebhookJSONQuery#9b9240a6 query_id:long data:DataJSON timeout:int = Update; updateBotShippingQuery#e0cdc940 query_id:long user_id:int payload:bytes shipping_address:PostAddress = Update; updateBotPrecheckoutQuery#5d2f3aa9 flags:# query_id:long user_id:int payload:bytes info:flags.0?PaymentRequestedInfo shipping_option_id:flags.1?string currency:string total_amount:long = Update; updatePhoneCall#ab0f6b1e phone_call:PhoneCall = Update; updateLangPackTooLong#10c2404b = Update; updateLangPack#56022f4d difference:LangPackDifference = Update; updateFavedStickers#e511996d = Update; updateChannelReadMessagesContents#89893b45 channel_id:int messages:Vector = Update; updateContactsReset#7084a7be = Update; updateChannelAvailableMessages#70db6837 channel_id:int available_min_id:int = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; updates.differenceEmpty#5d75a138 date:int seq:int = updates.Difference; updates.difference#f49ca0 new_messages:Vector new_encrypted_messages:Vector other_updates:Vector chats:Vector users:Vector state:updates.State = updates.Difference; updates.differenceSlice#a8fb1981 new_messages:Vector new_encrypted_messages:Vector other_updates:Vector chats:Vector users:Vector intermediate_state:updates.State = updates.Difference; updates.differenceTooLong#4afe8f6d pts:int = updates.Difference; updatesTooLong#e317af7e = Updates; updateShortMessage#914fbf11 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true id:int user_id:int message:string pts:int pts_count:int date:int fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to_msg_id:flags.3?int entities:flags.7?Vector = Updates; updateShortChatMessage#16812688 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true id:int from_id:int chat_id:int message:string pts:int pts_count:int date:int fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to_msg_id:flags.3?int entities:flags.7?Vector = Updates; updateShort#78d4dec1 update:Update date:int = Updates; updatesCombined#725b04c3 updates:Vector users:Vector chats:Vector date:int seq_start:int seq:int = Updates; updates#74ae4240 updates:Vector users:Vector chats:Vector date:int seq:int = Updates; updateShortSentMessage#11f1331c flags:# out:flags.1?true id:int pts:int pts_count:int date:int media:flags.9?MessageMedia entities:flags.7?Vector = Updates; photos.photos#8dca6aa5 photos:Vector users:Vector = photos.Photos; photos.photosSlice#15051f54 count:int photos:Vector users:Vector = photos.Photos; photos.photo#20212ca8 photo:Photo users:Vector = photos.Photo; upload.file#96a18d5 type:storage.FileType mtime:int bytes:bytes = upload.File; upload.fileCdnRedirect#f18cda44 dc_id:int file_token:bytes encryption_key:bytes encryption_iv:bytes file_hashes:Vector = upload.File; dcOption#5d8c6cc flags:# ipv6:flags.0?true media_only:flags.1?true tcpo_only:flags.2?true cdn:flags.3?true static:flags.4?true id:int ip_address:string port:int = DcOption; config#86b5778e flags:# phonecalls_enabled:flags.1?true default_p2p_contacts:flags.3?true preload_featured_stickers:flags.4?true ignore_phone_entities:flags.5?true revoke_pm_inbox:flags.6?true date:int expires:int test_mode:Bool this_dc:int dc_options:Vector chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int push_chat_period_ms:int push_chat_limit:int saved_gifs_limit:int edit_time_limit:int revoke_time_limit:int revoke_pm_time_limit:int rating_e_decay:int stickers_recent_limit:int stickers_faved_limit:int channels_read_media_period:int tmp_sessions:flags.0?int pinned_dialogs_count_max:int call_receive_timeout_ms:int call_ring_timeout_ms:int call_connect_timeout_ms:int call_packet_timeout_ms:int me_url_prefix:string suggested_lang_code:flags.2?string lang_pack_version:flags.2?int = Config; nearestDc#8e1a1775 country:string this_dc:int nearest_dc:int = NearestDc; help.appUpdate#8987f311 id:int critical:Bool url:string text:string = help.AppUpdate; help.noAppUpdate#c45a6536 = help.AppUpdate; help.inviteText#18cb9f78 message:string = help.InviteText; encryptedChatEmpty#ab7ec0a0 id:int = EncryptedChat; encryptedChatWaiting#3bf703dc id:int access_hash:long date:int admin_id:int participant_id:int = EncryptedChat; encryptedChatRequested#c878527e id:int access_hash:long date:int admin_id:int participant_id:int g_a:bytes = EncryptedChat; encryptedChat#fa56ce36 id:int access_hash:long date:int admin_id:int participant_id:int g_a_or_b:bytes key_fingerprint:long = EncryptedChat; encryptedChatDiscarded#13d6dd27 id:int = EncryptedChat; inputEncryptedChat#f141b5e1 chat_id:int access_hash:long = InputEncryptedChat; encryptedFileEmpty#c21f497e = EncryptedFile; encryptedFile#4a70994c id:long access_hash:long size:int dc_id:int key_fingerprint:int = EncryptedFile; inputEncryptedFileEmpty#1837c364 = InputEncryptedFile; inputEncryptedFileUploaded#64bd0306 id:long parts:int md5_checksum:string key_fingerprint:int = InputEncryptedFile; inputEncryptedFile#5a17b5e5 id:long access_hash:long = InputEncryptedFile; inputEncryptedFileBigUploaded#2dc173c8 id:long parts:int key_fingerprint:int = InputEncryptedFile; encryptedMessage#ed18c118 random_id:long chat_id:int date:int bytes:bytes file:EncryptedFile = EncryptedMessage; encryptedMessageService#23734b06 random_id:long chat_id:int date:int bytes:bytes = EncryptedMessage; messages.dhConfigNotModified#c0e24635 random:bytes = messages.DhConfig; messages.dhConfig#2c221edd g:int p:bytes version:int random:bytes = messages.DhConfig; messages.sentEncryptedMessage#560f8935 date:int = messages.SentEncryptedMessage; messages.sentEncryptedFile#9493ff32 date:int file:EncryptedFile = messages.SentEncryptedMessage; inputDocumentEmpty#72f0eaae = InputDocument; inputDocument#18798952 id:long access_hash:long = InputDocument; documentEmpty#36f8c871 id:long = Document; document#87232bc7 id:long access_hash:long date:int mime_type:string size:int thumb:PhotoSize dc_id:int version:int attributes:Vector = Document; help.support#17c6b5f6 phone_number:string user:User = help.Support; notifyPeer#9fd40bd8 peer:Peer = NotifyPeer; notifyUsers#b4c83b4c = NotifyPeer; notifyChats#c007cec3 = NotifyPeer; notifyAll#74d07c60 = NotifyPeer; sendMessageTypingAction#16bf744e = SendMessageAction; sendMessageCancelAction#fd5ec8f5 = SendMessageAction; sendMessageRecordVideoAction#a187d66f = SendMessageAction; sendMessageUploadVideoAction#e9763aec progress:int = SendMessageAction; sendMessageRecordAudioAction#d52f73f7 = SendMessageAction; sendMessageUploadAudioAction#f351d7ab progress:int = SendMessageAction; sendMessageUploadPhotoAction#d1d34a26 progress:int = SendMessageAction; sendMessageUploadDocumentAction#aa0cd9e4 progress:int = SendMessageAction; sendMessageGeoLocationAction#176f8ba1 = SendMessageAction; sendMessageChooseContactAction#628cbc6f = SendMessageAction; sendMessageGamePlayAction#dd6a8f48 = SendMessageAction; sendMessageRecordRoundAction#88f27fbc = SendMessageAction; sendMessageUploadRoundAction#243e1c66 progress:int = SendMessageAction; contacts.found#b3134d9d my_results:Vector results:Vector chats:Vector users:Vector = contacts.Found; inputPrivacyKeyStatusTimestamp#4f96cb18 = InputPrivacyKey; inputPrivacyKeyChatInvite#bdfb0426 = InputPrivacyKey; inputPrivacyKeyPhoneCall#fabadc5f = InputPrivacyKey; privacyKeyStatusTimestamp#bc2eab30 = PrivacyKey; privacyKeyChatInvite#500e6dfa = PrivacyKey; privacyKeyPhoneCall#3d662b7b = PrivacyKey; inputPrivacyValueAllowContacts#d09e07b = InputPrivacyRule; inputPrivacyValueAllowAll#184b35ce = InputPrivacyRule; inputPrivacyValueAllowUsers#131cc67f users:Vector = InputPrivacyRule; inputPrivacyValueDisallowContacts#ba52007 = InputPrivacyRule; inputPrivacyValueDisallowAll#d66b66c9 = InputPrivacyRule; inputPrivacyValueDisallowUsers#90110467 users:Vector = InputPrivacyRule; privacyValueAllowContacts#fffe1bac = PrivacyRule; privacyValueAllowAll#65427b82 = PrivacyRule; privacyValueAllowUsers#4d5bbe0c users:Vector = PrivacyRule; privacyValueDisallowContacts#f888fa1a = PrivacyRule; privacyValueDisallowAll#8b73e763 = PrivacyRule; privacyValueDisallowUsers#c7f49b7 users:Vector = PrivacyRule; account.privacyRules#554abb6f rules:Vector users:Vector = account.PrivacyRules; accountDaysTTL#b8d0afdf days:int = AccountDaysTTL; documentAttributeImageSize#6c37c15c w:int h:int = DocumentAttribute; documentAttributeAnimated#11b58939 = DocumentAttribute; documentAttributeSticker#6319d612 flags:# mask:flags.1?true alt:string stickerset:InputStickerSet mask_coords:flags.0?MaskCoords = DocumentAttribute; documentAttributeVideo#ef02ce6 flags:# round_message:flags.0?true supports_streaming:flags.1?true duration:int w:int h:int = DocumentAttribute; documentAttributeAudio#9852f9c6 flags:# voice:flags.10?true duration:int title:flags.0?string performer:flags.1?string waveform:flags.2?bytes = DocumentAttribute; documentAttributeFilename#15590068 file_name:string = DocumentAttribute; documentAttributeHasStickers#9801d2f7 = DocumentAttribute; messages.stickersNotModified#f1749a22 = messages.Stickers; messages.stickers#8a8ecd32 hash:string stickers:Vector = messages.Stickers; stickerPack#12b299d4 emoticon:string documents:Vector = StickerPack; messages.allStickersNotModified#e86602c3 = messages.AllStickers; messages.allStickers#edfd405f hash:int sets:Vector = messages.AllStickers; messages.affectedMessages#84d19185 pts:int pts_count:int = messages.AffectedMessages; contactLinkUnknown#5f4f9247 = ContactLink; contactLinkNone#feedd3ad = ContactLink; contactLinkHasPhone#268f3f59 = ContactLink; contactLinkContact#d502c2d0 = ContactLink; webPageEmpty#eb1477e8 id:long = WebPage; webPagePending#c586da1c id:long date:int = WebPage; webPage#5f07b4bc flags:# id:long url:string display_url:string hash:int type:flags.0?string site_name:flags.1?string title:flags.2?string description:flags.3?string photo:flags.4?Photo embed_url:flags.5?string embed_type:flags.5?string embed_width:flags.6?int embed_height:flags.6?int duration:flags.7?int author:flags.8?string document:flags.9?Document cached_page:flags.10?Page = WebPage; webPageNotModified#85849473 = WebPage; authorization#7bf2e6f6 hash:long flags:int device_model:string platform:string system_version:string api_id:int app_name:string app_version:string date_created:int date_active:int ip:string country:string region:string = Authorization; account.authorizations#1250abde authorizations:Vector = account.Authorizations; account.noPassword#96dabc18 new_salt:bytes email_unconfirmed_pattern:string = account.Password; account.password#7c18141c current_salt:bytes new_salt:bytes hint:string has_recovery:Bool email_unconfirmed_pattern:string = account.Password; account.passwordSettings#b7b72ab3 email:string = account.PasswordSettings; account.passwordInputSettings#86916deb flags:# new_salt:flags.0?bytes new_password_hash:flags.0?bytes hint:flags.0?string email:flags.1?string = account.PasswordInputSettings; auth.passwordRecovery#137948a5 email_pattern:string = auth.PasswordRecovery; receivedNotifyMessage#a384b779 id:int flags:int = ReceivedNotifyMessage; chatInviteEmpty#69df3769 = ExportedChatInvite; chatInviteExported#fc2e05bc link:string = ExportedChatInvite; chatInviteAlready#5a686d7c chat:Chat = ChatInvite; chatInvite#db74f558 flags:# channel:flags.0?true broadcast:flags.1?true public:flags.2?true megagroup:flags.3?true title:string photo:ChatPhoto participants_count:int participants:flags.4?Vector = ChatInvite; inputStickerSetEmpty#ffb62b95 = InputStickerSet; inputStickerSetID#9de7a269 id:long access_hash:long = InputStickerSet; inputStickerSetShortName#861cc8a0 short_name:string = InputStickerSet; stickerSet#5585a139 flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string count:int hash:int = StickerSet; messages.stickerSet#b60a24a6 set:StickerSet packs:Vector documents:Vector = messages.StickerSet; botCommand#c27ac8c7 command:string description:string = BotCommand; botInfo#98e81d3a user_id:int description:string commands:Vector = BotInfo; keyboardButton#a2fa4880 text:string = KeyboardButton; keyboardButtonUrl#258aff05 text:string url:string = KeyboardButton; keyboardButtonCallback#683a5e46 text:string data:bytes = KeyboardButton; keyboardButtonRequestPhone#b16a6c29 text:string = KeyboardButton; keyboardButtonRequestGeoLocation#fc796b3f text:string = KeyboardButton; keyboardButtonSwitchInline#568a748 flags:# same_peer:flags.0?true text:string query:string = KeyboardButton; keyboardButtonGame#50f41ccf text:string = KeyboardButton; keyboardButtonBuy#afd93fbb text:string = KeyboardButton; keyboardButtonRow#77608b83 buttons:Vector = KeyboardButtonRow; replyKeyboardHide#a03e5b85 flags:# selective:flags.2?true = ReplyMarkup; replyKeyboardForceReply#f4108aa0 flags:# single_use:flags.1?true selective:flags.2?true = ReplyMarkup; replyKeyboardMarkup#3502758c flags:# resize:flags.0?true single_use:flags.1?true selective:flags.2?true rows:Vector = ReplyMarkup; replyInlineMarkup#48a30254 rows:Vector = ReplyMarkup; messageEntityUnknown#bb92ba95 offset:int length:int = MessageEntity; messageEntityMention#fa04579d offset:int length:int = MessageEntity; messageEntityHashtag#6f635b0d offset:int length:int = MessageEntity; messageEntityBotCommand#6cef8ac7 offset:int length:int = MessageEntity; messageEntityUrl#6ed02538 offset:int length:int = MessageEntity; messageEntityEmail#64e475c2 offset:int length:int = MessageEntity; messageEntityBold#bd610bc9 offset:int length:int = MessageEntity; messageEntityItalic#826f8b60 offset:int length:int = MessageEntity; messageEntityCode#28a20571 offset:int length:int = MessageEntity; messageEntityPre#73924be0 offset:int length:int language:string = MessageEntity; messageEntityTextUrl#76a6d327 offset:int length:int url:string = MessageEntity; messageEntityMentionName#352dca58 offset:int length:int user_id:int = MessageEntity; inputMessageEntityMentionName#208e68c9 offset:int length:int user_id:InputUser = MessageEntity; messageEntityPhone#9b69e34b offset:int length:int = MessageEntity; messageEntityCashtag#4c4e743f offset:int length:int = MessageEntity; inputChannelEmpty#ee8c1e86 = InputChannel; inputChannel#afeb712e channel_id:int access_hash:long = InputChannel; contacts.resolvedPeer#7f077ad9 peer:Peer chats:Vector users:Vector = contacts.ResolvedPeer; messageRange#ae30253 min_id:int max_id:int = MessageRange; updates.channelDifferenceEmpty#3e11affb flags:# final:flags.0?true pts:int timeout:flags.1?int = updates.ChannelDifference; updates.channelDifferenceTooLong#6a9d7b35 flags:# final:flags.0?true pts:int timeout:flags.1?int top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int messages:Vector chats:Vector users:Vector = updates.ChannelDifference; updates.channelDifference#2064674e flags:# final:flags.0?true pts:int timeout:flags.1?int new_messages:Vector other_updates:Vector chats:Vector users:Vector = updates.ChannelDifference; channelMessagesFilterEmpty#94d42ee7 = ChannelMessagesFilter; channelMessagesFilter#cd77d957 flags:# exclude_new_messages:flags.1?true ranges:Vector = ChannelMessagesFilter; channelParticipant#15ebac1d user_id:int date:int = ChannelParticipant; channelParticipantSelf#a3289a6d user_id:int inviter_id:int date:int = ChannelParticipant; channelParticipantCreator#e3e2e1f9 user_id:int = ChannelParticipant; channelParticipantAdmin#a82fa898 flags:# can_edit:flags.0?true user_id:int inviter_id:int promoted_by:int date:int admin_rights:ChannelAdminRights = ChannelParticipant; channelParticipantBanned#222c1886 flags:# left:flags.0?true user_id:int kicked_by:int date:int banned_rights:ChannelBannedRights = ChannelParticipant; channelParticipantsRecent#de3f3c79 = ChannelParticipantsFilter; channelParticipantsAdmins#b4608969 = ChannelParticipantsFilter; channelParticipantsKicked#a3b54985 q:string = ChannelParticipantsFilter; channelParticipantsBots#b0d1865b = ChannelParticipantsFilter; channelParticipantsBanned#1427a5e1 q:string = ChannelParticipantsFilter; channelParticipantsSearch#656ac4b q:string = ChannelParticipantsFilter; channels.channelParticipants#f56ee2a8 count:int participants:Vector users:Vector = channels.ChannelParticipants; channels.channelParticipantsNotModified#f0173fe9 = channels.ChannelParticipants; channels.channelParticipant#d0d9b163 participant:ChannelParticipant users:Vector = channels.ChannelParticipant; help.termsOfService#f1ee3e90 text:string = help.TermsOfService; foundGif#162ecc1f url:string thumb_url:string content_url:string content_type:string w:int h:int = FoundGif; foundGifCached#9c750409 url:string photo:Photo document:Document = FoundGif; messages.foundGifs#450a1c0a next_offset:int results:Vector = messages.FoundGifs; messages.savedGifsNotModified#e8025ca2 = messages.SavedGifs; messages.savedGifs#2e0709a5 hash:int gifs:Vector = messages.SavedGifs; inputBotInlineMessageMediaAuto#3380c786 flags:# message:string entities:flags.1?Vector reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineMessageText#3dcd7a87 flags:# no_webpage:flags.0?true message:string entities:flags.1?Vector reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineMessageMediaGeo#c1b15d65 flags:# geo_point:InputGeoPoint period:int reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineMessageMediaVenue#aaafadc8 flags:# geo_point:InputGeoPoint title:string address:string provider:string venue_id:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineMessageMediaContact#2daf01a7 flags:# phone_number:string first_name:string last_name:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineMessageGame#4b425864 flags:# reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineResult#88bf9319 flags:# id:string type:string title:flags.1?string description:flags.2?string url:flags.3?string thumb:flags.4?InputWebDocument content:flags.5?InputWebDocument send_message:InputBotInlineMessage = InputBotInlineResult; inputBotInlineResultPhoto#a8d864a7 id:string type:string photo:InputPhoto send_message:InputBotInlineMessage = InputBotInlineResult; inputBotInlineResultDocument#fff8fdc4 flags:# id:string type:string title:flags.1?string description:flags.2?string document:InputDocument send_message:InputBotInlineMessage = InputBotInlineResult; inputBotInlineResultGame#4fa417f2 id:string short_name:string send_message:InputBotInlineMessage = InputBotInlineResult; botInlineMessageMediaAuto#764cf810 flags:# message:string entities:flags.1?Vector reply_markup:flags.2?ReplyMarkup = BotInlineMessage; botInlineMessageText#8c7f65e2 flags:# no_webpage:flags.0?true message:string entities:flags.1?Vector reply_markup:flags.2?ReplyMarkup = BotInlineMessage; botInlineMessageMediaGeo#b722de65 flags:# geo:GeoPoint period:int reply_markup:flags.2?ReplyMarkup = BotInlineMessage; botInlineMessageMediaVenue#4366232e flags:# geo:GeoPoint title:string address:string provider:string venue_id:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage; botInlineMessageMediaContact#35edb4d4 flags:# phone_number:string first_name:string last_name:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage; botInlineResult#11965f3a flags:# id:string type:string title:flags.1?string description:flags.2?string url:flags.3?string thumb:flags.4?WebDocument content:flags.5?WebDocument send_message:BotInlineMessage = BotInlineResult; botInlineMediaResult#17db940b flags:# id:string type:string photo:flags.0?Photo document:flags.1?Document title:flags.2?string description:flags.3?string send_message:BotInlineMessage = BotInlineResult; messages.botResults#947ca848 flags:# gallery:flags.0?true query_id:long next_offset:flags.1?string switch_pm:flags.2?InlineBotSwitchPM results:Vector cache_time:int users:Vector = messages.BotResults; exportedMessageLink#5dab1af4 link:string html:string = ExportedMessageLink; messageFwdHeader#559ebe6d flags:# from_id:flags.0?int date:int channel_id:flags.1?int channel_post:flags.2?int post_author:flags.3?string saved_from_peer:flags.4?Peer saved_from_msg_id:flags.4?int = MessageFwdHeader; auth.codeTypeSms#72a3158c = auth.CodeType; auth.codeTypeCall#741cd3e3 = auth.CodeType; auth.codeTypeFlashCall#226ccefb = auth.CodeType; auth.sentCodeTypeApp#3dbb5986 length:int = auth.SentCodeType; auth.sentCodeTypeSms#c000bba2 length:int = auth.SentCodeType; auth.sentCodeTypeCall#5353e5a7 length:int = auth.SentCodeType; auth.sentCodeTypeFlashCall#ab03c6d9 pattern:string = auth.SentCodeType; messages.botCallbackAnswer#36585ea4 flags:# alert:flags.1?true has_url:flags.3?true native_ui:flags.4?true message:flags.0?string url:flags.2?string cache_time:int = messages.BotCallbackAnswer; messages.messageEditData#26b5dde6 flags:# caption:flags.0?true = messages.MessageEditData; inputBotInlineMessageID#890c3d89 dc_id:int id:long access_hash:long = InputBotInlineMessageID; inlineBotSwitchPM#3c20629f text:string start_param:string = InlineBotSwitchPM; messages.peerDialogs#3371c354 dialogs:Vector messages:Vector chats:Vector users:Vector state:updates.State = messages.PeerDialogs; topPeer#edcdc05b peer:Peer rating:double = TopPeer; topPeerCategoryBotsPM#ab661b5b = TopPeerCategory; topPeerCategoryBotsInline#148677e2 = TopPeerCategory; topPeerCategoryCorrespondents#637b7ed = TopPeerCategory; topPeerCategoryGroups#bd17a14a = TopPeerCategory; topPeerCategoryChannels#161d9628 = TopPeerCategory; topPeerCategoryPhoneCalls#1e76a78c = TopPeerCategory; topPeerCategoryPeers#fb834291 category:TopPeerCategory count:int peers:Vector = TopPeerCategoryPeers; contacts.topPeersNotModified#de266ef5 = contacts.TopPeers; contacts.topPeers#70b772a8 categories:Vector chats:Vector users:Vector = contacts.TopPeers; draftMessageEmpty#ba4baec5 = DraftMessage; draftMessage#fd8e711f flags:# no_webpage:flags.1?true reply_to_msg_id:flags.0?int message:string entities:flags.3?Vector date:int = DraftMessage; messages.featuredStickersNotModified#4ede3cf = messages.FeaturedStickers; messages.featuredStickers#f89d88e5 hash:int sets:Vector unread:Vector = messages.FeaturedStickers; messages.recentStickersNotModified#b17f890 = messages.RecentStickers; messages.recentStickers#22f3afb3 hash:int packs:Vector stickers:Vector dates:Vector = messages.RecentStickers; messages.archivedStickers#4fcba9c8 count:int sets:Vector = messages.ArchivedStickers; messages.stickerSetInstallResultSuccess#38641628 = messages.StickerSetInstallResult; messages.stickerSetInstallResultArchive#35e410a8 sets:Vector = messages.StickerSetInstallResult; stickerSetCovered#6410a5d2 set:StickerSet cover:Document = StickerSetCovered; stickerSetMultiCovered#3407e51b set:StickerSet covers:Vector = StickerSetCovered; maskCoords#aed6dbb2 n:int x:double y:double zoom:double = MaskCoords; inputStickeredMediaPhoto#4a992157 id:InputPhoto = InputStickeredMedia; inputStickeredMediaDocument#438865b id:InputDocument = InputStickeredMedia; game#bdf9653b flags:# id:long access_hash:long short_name:string title:string description:string photo:Photo document:flags.0?Document = Game; inputGameID#32c3e77 id:long access_hash:long = InputGame; inputGameShortName#c331e80a bot_id:InputUser short_name:string = InputGame; highScore#58fffcd0 pos:int user_id:int score:int = HighScore; messages.highScores#9a3bfd99 scores:Vector users:Vector = messages.HighScores; textEmpty#dc3d824f = RichText; textPlain#744694e0 text:string = RichText; textBold#6724abc4 text:RichText = RichText; textItalic#d912a59c text:RichText = RichText; textUnderline#c12622c4 text:RichText = RichText; textStrike#9bf8bb95 text:RichText = RichText; textFixed#6c3f19b9 text:RichText = RichText; textUrl#3c2884c1 text:RichText url:string webpage_id:long = RichText; textEmail#de5a0dd6 text:RichText email:string = RichText; textConcat#7e6260d7 texts:Vector = RichText; pageBlockUnsupported#13567e8a = PageBlock; pageBlockTitle#70abc3fd text:RichText = PageBlock; pageBlockSubtitle#8ffa9a1f text:RichText = PageBlock; pageBlockAuthorDate#baafe5e0 author:RichText published_date:int = PageBlock; pageBlockHeader#bfd064ec text:RichText = PageBlock; pageBlockSubheader#f12bb6e1 text:RichText = PageBlock; pageBlockParagraph#467a0766 text:RichText = PageBlock; pageBlockPreformatted#c070d93e text:RichText language:string = PageBlock; pageBlockFooter#48870999 text:RichText = PageBlock; pageBlockDivider#db20b188 = PageBlock; pageBlockAnchor#ce0d37b0 name:string = PageBlock; pageBlockList#3a58c7f4 ordered:Bool items:Vector = PageBlock; pageBlockBlockquote#263d7c26 text:RichText caption:RichText = PageBlock; pageBlockPullquote#4f4456d3 text:RichText caption:RichText = PageBlock; pageBlockPhoto#e9c69982 photo_id:long caption:RichText = PageBlock; pageBlockVideo#d9d71866 flags:# autoplay:flags.0?true loop:flags.1?true video_id:long caption:RichText = PageBlock; pageBlockCover#39f23300 cover:PageBlock = PageBlock; pageBlockEmbed#cde200d1 flags:# full_width:flags.0?true allow_scrolling:flags.3?true url:flags.1?string html:flags.2?string poster_photo_id:flags.4?long w:int h:int caption:RichText = PageBlock; pageBlockEmbedPost#292c7be9 url:string webpage_id:long author_photo_id:long author:string date:int blocks:Vector caption:RichText = PageBlock; pageBlockCollage#8b31c4f items:Vector caption:RichText = PageBlock; pageBlockSlideshow#130c8963 items:Vector caption:RichText = PageBlock; pageBlockChannel#ef1751b5 channel:Chat = PageBlock; pageBlockAudio#31b81a7f audio_id:long caption:RichText = PageBlock; pagePart#8e3f9ebe blocks:Vector photos:Vector documents:Vector = Page; pageFull#556ec7aa blocks:Vector photos:Vector documents:Vector = Page; phoneCallDiscardReasonMissed#85e42301 = PhoneCallDiscardReason; phoneCallDiscardReasonDisconnect#e095c1a0 = PhoneCallDiscardReason; phoneCallDiscardReasonHangup#57adc690 = PhoneCallDiscardReason; phoneCallDiscardReasonBusy#faf7e8c9 = PhoneCallDiscardReason; dataJSON#7d748d04 data:string = DataJSON; labeledPrice#cb296bf8 label:string amount:long = LabeledPrice; invoice#c30aa358 flags:# test:flags.0?true name_requested:flags.1?true phone_requested:flags.2?true email_requested:flags.3?true shipping_address_requested:flags.4?true flexible:flags.5?true phone_to_provider:flags.6?true email_to_provider:flags.7?true currency:string prices:Vector = Invoice; paymentCharge#ea02c27e id:string provider_charge_id:string = PaymentCharge; postAddress#1e8caaeb street_line1:string street_line2:string city:string state:string country_iso2:string post_code:string = PostAddress; paymentRequestedInfo#909c3f94 flags:# name:flags.0?string phone:flags.1?string email:flags.2?string shipping_address:flags.3?PostAddress = PaymentRequestedInfo; paymentSavedCredentialsCard#cdc27a1f id:string title:string = PaymentSavedCredentials; webDocument#c61acbd8 url:string access_hash:long size:int mime_type:string attributes:Vector dc_id:int = WebDocument; webDocumentNoProxy#f9c8bcc6 url:string size:int mime_type:string attributes:Vector = WebDocument; inputWebDocument#9bed434d url:string size:int mime_type:string attributes:Vector = InputWebDocument; inputWebFileLocation#c239d686 url:string access_hash:long = InputWebFileLocation; upload.webFile#21e753bc size:int mime_type:string file_type:storage.FileType mtime:int bytes:bytes = upload.WebFile; payments.paymentForm#3f56aea3 flags:# can_save_credentials:flags.2?true password_missing:flags.3?true bot_id:int invoice:Invoice provider_id:int url:string native_provider:flags.4?string native_params:flags.4?DataJSON saved_info:flags.0?PaymentRequestedInfo saved_credentials:flags.1?PaymentSavedCredentials users:Vector = payments.PaymentForm; payments.validatedRequestedInfo#d1451883 flags:# id:flags.0?string shipping_options:flags.1?Vector = payments.ValidatedRequestedInfo; payments.paymentResult#4e5f810d updates:Updates = payments.PaymentResult; payments.paymentVerficationNeeded#6b56b921 url:string = payments.PaymentResult; payments.paymentReceipt#500911e1 flags:# date:int bot_id:int invoice:Invoice provider_id:int info:flags.0?PaymentRequestedInfo shipping:flags.1?ShippingOption currency:string total_amount:long credentials_title:string users:Vector = payments.PaymentReceipt; payments.savedInfo#fb8fe43c flags:# has_saved_credentials:flags.1?true saved_info:flags.0?PaymentRequestedInfo = payments.SavedInfo; inputPaymentCredentialsSaved#c10eb2cf id:string tmp_password:bytes = InputPaymentCredentials; inputPaymentCredentials#3417d728 flags:# save:flags.0?true data:DataJSON = InputPaymentCredentials; inputPaymentCredentialsApplePay#aa1c39f payment_data:DataJSON = InputPaymentCredentials; inputPaymentCredentialsAndroidPay#ca05d50e payment_token:DataJSON google_transaction_id:string = InputPaymentCredentials; account.tmpPassword#db64fd34 tmp_password:bytes valid_until:int = account.TmpPassword; shippingOption#b6213cdf id:string title:string prices:Vector = ShippingOption; inputStickerSetItem#ffa0a496 flags:# document:InputDocument emoji:string mask_coords:flags.0?MaskCoords = InputStickerSetItem; inputPhoneCall#1e36fded id:long access_hash:long = InputPhoneCall; phoneCallEmpty#5366c915 id:long = PhoneCall; phoneCallWaiting#1b8f4ad1 flags:# id:long access_hash:long date:int admin_id:int participant_id:int protocol:PhoneCallProtocol receive_date:flags.0?int = PhoneCall; phoneCallRequested#83761ce4 id:long access_hash:long date:int admin_id:int participant_id:int g_a_hash:bytes protocol:PhoneCallProtocol = PhoneCall; phoneCallAccepted#6d003d3f id:long access_hash:long date:int admin_id:int participant_id:int g_b:bytes protocol:PhoneCallProtocol = PhoneCall; phoneCall#ffe6ab67 id:long access_hash:long date:int admin_id:int participant_id:int g_a_or_b:bytes key_fingerprint:long protocol:PhoneCallProtocol connection:PhoneConnection alternative_connections:Vector start_date:int = PhoneCall; phoneCallDiscarded#50ca4de1 flags:# need_rating:flags.2?true need_debug:flags.3?true id:long reason:flags.0?PhoneCallDiscardReason duration:flags.1?int = PhoneCall; phoneConnection#9d4c17c0 id:long ip:string ipv6:string port:int peer_tag:bytes = PhoneConnection; phoneCallProtocol#a2bb35cb flags:# udp_p2p:flags.0?true udp_reflector:flags.1?true min_layer:int max_layer:int = PhoneCallProtocol; phone.phoneCall#ec82e140 phone_call:PhoneCall users:Vector = phone.PhoneCall; upload.cdnFileReuploadNeeded#eea8e46e request_token:bytes = upload.CdnFile; upload.cdnFile#a99fca4f bytes:bytes = upload.CdnFile; cdnPublicKey#c982eaba dc_id:int public_key:string = CdnPublicKey; cdnConfig#5725e40a public_keys:Vector = CdnConfig; langPackString#cad181f6 key:string value:string = LangPackString; langPackStringPluralized#6c47ac9f flags:# key:string zero_value:flags.0?string one_value:flags.1?string two_value:flags.2?string few_value:flags.3?string many_value:flags.4?string other_value:string = LangPackString; langPackStringDeleted#2979eeb2 key:string = LangPackString; langPackDifference#f385c1f6 lang_code:string from_version:int version:int strings:Vector = LangPackDifference; langPackLanguage#117698f1 name:string native_name:string lang_code:string = LangPackLanguage; channelAdminRights#5d7ceba5 flags:# change_info:flags.0?true post_messages:flags.1?true edit_messages:flags.2?true delete_messages:flags.3?true ban_users:flags.4?true invite_users:flags.5?true invite_link:flags.6?true pin_messages:flags.7?true add_admins:flags.9?true manage_call:flags.10?true = ChannelAdminRights; channelBannedRights#58cf4249 flags:# view_messages:flags.0?true send_messages:flags.1?true send_media:flags.2?true send_stickers:flags.3?true send_gifs:flags.4?true send_games:flags.5?true send_inline:flags.6?true embed_links:flags.7?true until_date:int = ChannelBannedRights; channelAdminLogEventActionChangeTitle#e6dfb825 prev_value:string new_value:string = ChannelAdminLogEventAction; channelAdminLogEventActionChangeAbout#55188a2e prev_value:string new_value:string = ChannelAdminLogEventAction; channelAdminLogEventActionChangeUsername#6a4afc38 prev_value:string new_value:string = ChannelAdminLogEventAction; channelAdminLogEventActionChangePhoto#b82f55c3 prev_photo:ChatPhoto new_photo:ChatPhoto = ChannelAdminLogEventAction; channelAdminLogEventActionToggleInvites#1b7907ae new_value:Bool = ChannelAdminLogEventAction; channelAdminLogEventActionToggleSignatures#26ae0971 new_value:Bool = ChannelAdminLogEventAction; channelAdminLogEventActionUpdatePinned#e9e82c18 message:Message = ChannelAdminLogEventAction; channelAdminLogEventActionEditMessage#709b2405 prev_message:Message new_message:Message = ChannelAdminLogEventAction; channelAdminLogEventActionDeleteMessage#42e047bb message:Message = ChannelAdminLogEventAction; channelAdminLogEventActionParticipantJoin#183040d3 = ChannelAdminLogEventAction; channelAdminLogEventActionParticipantLeave#f89777f2 = ChannelAdminLogEventAction; channelAdminLogEventActionParticipantInvite#e31c34d8 participant:ChannelParticipant = ChannelAdminLogEventAction; channelAdminLogEventActionParticipantToggleBan#e6d83d7e prev_participant:ChannelParticipant new_participant:ChannelParticipant = ChannelAdminLogEventAction; channelAdminLogEventActionParticipantToggleAdmin#d5676710 prev_participant:ChannelParticipant new_participant:ChannelParticipant = ChannelAdminLogEventAction; channelAdminLogEventActionChangeStickerSet#b1c3caa7 prev_stickerset:InputStickerSet new_stickerset:InputStickerSet = ChannelAdminLogEventAction; channelAdminLogEventActionTogglePreHistoryHidden#5f5c95f1 new_value:Bool = ChannelAdminLogEventAction; channelAdminLogEvent#3b5a3e40 id:long date:int user_id:int action:ChannelAdminLogEventAction = ChannelAdminLogEvent; channels.adminLogResults#ed8af74d events:Vector chats:Vector users:Vector = channels.AdminLogResults; channelAdminLogEventsFilter#ea107ae4 flags:# join:flags.0?true leave:flags.1?true invite:flags.2?true ban:flags.3?true unban:flags.4?true kick:flags.5?true unkick:flags.6?true promote:flags.7?true demote:flags.8?true info:flags.9?true settings:flags.10?true pinned:flags.11?true edit:flags.12?true delete:flags.13?true = ChannelAdminLogEventsFilter; popularContact#5ce14175 client_id:long importers:int = PopularContact; messages.favedStickersNotModified#9e8fa6d3 = messages.FavedStickers; messages.favedStickers#f37f2f16 hash:int packs:Vector stickers:Vector = messages.FavedStickers; recentMeUrlUnknown#46e1d13d url:string = RecentMeUrl; recentMeUrlUser#8dbc3336 url:string user_id:int = RecentMeUrl; recentMeUrlChat#a01b22f9 url:string chat_id:int = RecentMeUrl; recentMeUrlChatInvite#eb49081d url:string chat_invite:ChatInvite = RecentMeUrl; recentMeUrlStickerSet#bc0a57dc url:string set:StickerSetCovered = RecentMeUrl; help.recentMeUrls#e0310d7 urls:Vector chats:Vector users:Vector = help.RecentMeUrls; inputSingleMedia#1cc6e91f flags:# media:InputMedia random_id:long message:string entities:flags.0?Vector = InputSingleMedia; webAuthorization#cac943f2 hash:long bot_id:int domain:string browser:string platform:string date_created:int date_active:int ip:string region:string = WebAuthorization; account.webAuthorizations#ed56c9fc authorizations:Vector users:Vector = account.WebAuthorizations; inputMessageID#a676a322 id:int = InputMessage; inputMessageReplyTo#bad88395 id:int = InputMessage; inputMessagePinned#86872538 = InputMessage; inputDialogPeer#fcaafeb7 peer:InputPeer = InputDialogPeer; dialogPeer#e56dbf05 peer:Peer = DialogPeer; messages.foundStickerSetsNotModified#d54b65d = messages.FoundStickerSets; messages.foundStickerSets#5108d648 hash:int sets:Vector = messages.FoundStickerSets; fileHash#6242c773 offset:int limit:int hash:bytes = FileHash; ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; invokeAfterMsgs#3dc4b4f0 {X:Type} msg_ids:Vector query:!X = X; initConnection#c7481da6 {X:Type} api_id:int device_model:string system_version:string app_version:string system_lang_code:string lang_pack:string lang_code:string query:!X = X; invokeWithLayer#da9b0d0d {X:Type} layer:int query:!X = X; invokeWithoutUpdates#bf9459b7 {X:Type} query:!X = X; auth.checkPhone#6fe51dfb phone_number:string = auth.CheckedPhone; auth.sendCode#86aef0ec flags:# allow_flashcall:flags.0?true phone_number:string current_number:flags.0?Bool api_id:int api_hash:string = auth.SentCode; auth.signUp#1b067634 phone_number:string phone_code_hash:string phone_code:string first_name:string last_name:string = auth.Authorization; auth.signIn#bcd51581 phone_number:string phone_code_hash:string phone_code:string = auth.Authorization; auth.logOut#5717da40 = Bool; auth.resetAuthorizations#9fab0d1a = Bool; auth.sendInvites#771c1d97 phone_numbers:Vector message:string = Bool; auth.exportAuthorization#e5bfffcd dc_id:int = auth.ExportedAuthorization; auth.importAuthorization#e3ef9613 id:int bytes:bytes = auth.Authorization; auth.bindTempAuthKey#cdd42a05 perm_auth_key_id:long nonce:long expires_at:int encrypted_message:bytes = Bool; auth.importBotAuthorization#67a3ff2c flags:int api_id:int api_hash:string bot_auth_token:string = auth.Authorization; auth.checkPassword#a63011e password_hash:bytes = auth.Authorization; auth.requestPasswordRecovery#d897bc66 = auth.PasswordRecovery; auth.recoverPassword#4ea56e92 code:string = auth.Authorization; auth.resendCode#3ef1a9bf phone_number:string phone_code_hash:string = auth.SentCode; auth.cancelCode#1f040578 phone_number:string phone_code_hash:string = Bool; auth.dropTempAuthKeys#8e48a188 except_auth_keys:Vector = Bool; account.registerDevice#5cbea590 token_type:int token:string app_sandbox:Bool secret:bytes other_uids:Vector = Bool; account.unregisterDevice#3076c4bf token_type:int token:string other_uids:Vector = Bool; account.updateNotifySettings#84be5b93 peer:InputNotifyPeer settings:InputPeerNotifySettings = Bool; account.getNotifySettings#12b3ad31 peer:InputNotifyPeer = PeerNotifySettings; account.resetNotifySettings#db7e1747 = Bool; account.updateProfile#78515775 flags:# first_name:flags.0?string last_name:flags.1?string about:flags.2?string = User; account.updateStatus#6628562c offline:Bool = Bool; account.getWallPapers#c04cfac2 = Vector; account.reportPeer#ae189d5f peer:InputPeer reason:ReportReason = Bool; account.checkUsername#2714d86c username:string = Bool; account.updateUsername#3e0bdd7c username:string = User; account.getPrivacy#dadbc950 key:InputPrivacyKey = account.PrivacyRules; account.setPrivacy#c9f81ce8 key:InputPrivacyKey rules:Vector = account.PrivacyRules; account.deleteAccount#418d4e0b reason:string = Bool; account.getAccountTTL#8fc711d = AccountDaysTTL; account.setAccountTTL#2442485e ttl:AccountDaysTTL = Bool; account.sendChangePhoneCode#8e57deb flags:# allow_flashcall:flags.0?true phone_number:string current_number:flags.0?Bool = auth.SentCode; account.changePhone#70c32edb phone_number:string phone_code_hash:string phone_code:string = User; account.updateDeviceLocked#38df3532 period:int = Bool; account.getAuthorizations#e320c158 = account.Authorizations; account.resetAuthorization#df77f3bc hash:long = Bool; account.getPassword#548a30f5 = account.Password; account.getPasswordSettings#bc8d11bb current_password_hash:bytes = account.PasswordSettings; account.updatePasswordSettings#fa7c4b86 current_password_hash:bytes new_settings:account.PasswordInputSettings = Bool; account.sendConfirmPhoneCode#1516d7bd flags:# allow_flashcall:flags.0?true hash:string current_number:flags.0?Bool = auth.SentCode; account.confirmPhone#5f2178c3 phone_code_hash:string phone_code:string = Bool; account.getTmpPassword#4a82327e password_hash:bytes period:int = account.TmpPassword; account.getWebAuthorizations#182e6d6f = account.WebAuthorizations; account.resetWebAuthorization#2d01b9ef hash:long = Bool; account.resetWebAuthorizations#682d2594 = Bool; users.getUsers#d91a548 id:Vector = Vector; users.getFullUser#ca30a5b1 id:InputUser = UserFull; contacts.getStatuses#c4a353ee = Vector; contacts.getContacts#c023849f hash:int = contacts.Contacts; contacts.importContacts#2c800be5 contacts:Vector = contacts.ImportedContacts; contacts.deleteContact#8e953744 id:InputUser = contacts.Link; contacts.deleteContacts#59ab389e id:Vector = Bool; contacts.block#332b49fc id:InputUser = Bool; contacts.unblock#e54100bd id:InputUser = Bool; contacts.getBlocked#f57c350f offset:int limit:int = contacts.Blocked; contacts.exportCard#84e53737 = Vector; contacts.importCard#4fe196fe export_card:Vector = User; contacts.search#11f812d8 q:string limit:int = contacts.Found; contacts.resolveUsername#f93ccba3 username:string = contacts.ResolvedPeer; contacts.getTopPeers#d4982db5 flags:# correspondents:flags.0?true bots_pm:flags.1?true bots_inline:flags.2?true phone_calls:flags.3?true groups:flags.10?true channels:flags.15?true offset:int limit:int hash:int = contacts.TopPeers; contacts.resetTopPeerRating#1ae373ac category:TopPeerCategory peer:InputPeer = Bool; contacts.resetSaved#879537f1 = Bool; messages.getMessages#63c66506 id:Vector = messages.Messages; messages.getDialogs#191ba9c5 flags:# exclude_pinned:flags.0?true offset_date:int offset_id:int offset_peer:InputPeer limit:int = messages.Dialogs; messages.getHistory#dcbb8260 peer:InputPeer offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:int = messages.Messages; messages.search#8614ef68 flags:# peer:InputPeer q:string from_id:flags.0?InputUser filter:MessagesFilter min_date:int max_date:int offset_id:int add_offset:int limit:int max_id:int min_id:int hash:int = messages.Messages; messages.readHistory#e306d3a peer:InputPeer max_id:int = messages.AffectedMessages; messages.deleteHistory#1c015b09 flags:# just_clear:flags.0?true peer:InputPeer max_id:int = messages.AffectedHistory; messages.deleteMessages#e58e95d2 flags:# revoke:flags.0?true id:Vector = messages.AffectedMessages; messages.receivedMessages#5a954c0 max_id:int = Vector; messages.setTyping#a3825e50 peer:InputPeer action:SendMessageAction = Bool; messages.sendMessage#fa88427a flags:# no_webpage:flags.1?true silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector = Updates; messages.sendMedia#b8d1262b flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int media:InputMedia message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector = Updates; messages.forwardMessages#708e0195 flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true grouped:flags.9?true from_peer:InputPeer id:Vector random_id:Vector to_peer:InputPeer = Updates; messages.reportSpam#cf1592db peer:InputPeer = Bool; messages.hideReportSpam#a8f1709b peer:InputPeer = Bool; messages.getPeerSettings#3672e09c peer:InputPeer = PeerSettings; messages.report#bd82b658 peer:InputPeer id:Vector reason:ReportReason = Bool; messages.getChats#3c6aa187 id:Vector = messages.Chats; messages.getFullChat#3b831c66 chat_id:int = messages.ChatFull; messages.editChatTitle#dc452855 chat_id:int title:string = Updates; messages.editChatPhoto#ca4c79d8 chat_id:int photo:InputChatPhoto = Updates; messages.addChatUser#f9a0aa09 chat_id:int user_id:InputUser fwd_limit:int = Updates; messages.deleteChatUser#e0611f16 chat_id:int user_id:InputUser = Updates; messages.createChat#9cb126e users:Vector title:string = Updates; messages.getDhConfig#26cf8950 version:int random_length:int = messages.DhConfig; messages.requestEncryption#f64daf43 user_id:InputUser random_id:int g_a:bytes = EncryptedChat; messages.acceptEncryption#3dbc0415 peer:InputEncryptedChat g_b:bytes key_fingerprint:long = EncryptedChat; messages.discardEncryption#edd923c5 chat_id:int = Bool; messages.setEncryptedTyping#791451ed peer:InputEncryptedChat typing:Bool = Bool; messages.readEncryptedHistory#7f4b690a peer:InputEncryptedChat max_date:int = Bool; messages.sendEncrypted#a9776773 peer:InputEncryptedChat random_id:long data:bytes = messages.SentEncryptedMessage; messages.sendEncryptedFile#9a901b66 peer:InputEncryptedChat random_id:long data:bytes file:InputEncryptedFile = messages.SentEncryptedMessage; messages.sendEncryptedService#32d439a4 peer:InputEncryptedChat random_id:long data:bytes = messages.SentEncryptedMessage; messages.receivedQueue#55a5bb66 max_qts:int = Vector; messages.reportEncryptedSpam#4b0c8c0f peer:InputEncryptedChat = Bool; messages.readMessageContents#36a73f77 id:Vector = messages.AffectedMessages; messages.getStickers#85cb5182 flags:# exclude_featured:flags.0?true emoticon:string hash:string = messages.Stickers; messages.getAllStickers#1c9618b1 hash:int = messages.AllStickers; messages.getWebPagePreview#8b68b0cc flags:# message:string entities:flags.3?Vector = MessageMedia; messages.exportChatInvite#7d885289 chat_id:int = ExportedChatInvite; messages.checkChatInvite#3eadb1bb hash:string = ChatInvite; messages.importChatInvite#6c50051c hash:string = Updates; messages.getStickerSet#2619a90e stickerset:InputStickerSet = messages.StickerSet; messages.installStickerSet#c78fe460 stickerset:InputStickerSet archived:Bool = messages.StickerSetInstallResult; messages.uninstallStickerSet#f96e55de stickerset:InputStickerSet = Bool; messages.startBot#e6df7378 bot:InputUser peer:InputPeer random_id:long start_param:string = Updates; messages.getMessagesViews#c4c8a55d peer:InputPeer id:Vector increment:Bool = Vector; messages.toggleChatAdmins#ec8bd9e1 chat_id:int enabled:Bool = Updates; messages.editChatAdmin#a9e69f2e chat_id:int user_id:InputUser is_admin:Bool = Bool; messages.migrateChat#15a3b8e3 chat_id:int = Updates; messages.searchGlobal#9e3cacb0 q:string offset_date:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; messages.reorderStickerSets#78337739 flags:# masks:flags.0?true order:Vector = Bool; messages.getDocumentByHash#338e2464 sha256:bytes size:int mime_type:string = Document; messages.searchGifs#bf9a776b q:string offset:int = messages.FoundGifs; messages.getSavedGifs#83bf3d52 hash:int = messages.SavedGifs; messages.saveGif#327a30cb id:InputDocument unsave:Bool = Bool; messages.getInlineBotResults#514e999d flags:# bot:InputUser peer:InputPeer geo_point:flags.0?InputGeoPoint query:string offset:string = messages.BotResults; messages.setInlineBotResults#eb5ea206 flags:# gallery:flags.0?true private:flags.1?true query_id:long results:Vector cache_time:int next_offset:flags.2?string switch_pm:flags.3?InlineBotSwitchPM = Bool; messages.sendInlineBotResult#b16e06fe flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int random_id:long query_id:long id:string = Updates; messages.getMessageEditData#fda68d36 peer:InputPeer id:int = messages.MessageEditData; messages.editMessage#5d1b8dd flags:# no_webpage:flags.1?true stop_geo_live:flags.12?true peer:InputPeer id:int message:flags.11?string reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector geo_point:flags.13?InputGeoPoint = Updates; messages.editInlineBotMessage#b0e08243 flags:# no_webpage:flags.1?true stop_geo_live:flags.12?true id:InputBotInlineMessageID message:flags.11?string reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector geo_point:flags.13?InputGeoPoint = Bool; messages.getBotCallbackAnswer#810a9fec flags:# game:flags.1?true peer:InputPeer msg_id:int data:flags.0?bytes = messages.BotCallbackAnswer; messages.setBotCallbackAnswer#d58f130a flags:# alert:flags.1?true query_id:long message:flags.0?string url:flags.2?string cache_time:int = Bool; messages.getPeerDialogs#e470bcfd peers:Vector = messages.PeerDialogs; messages.saveDraft#bc39e14b flags:# no_webpage:flags.1?true reply_to_msg_id:flags.0?int peer:InputPeer message:string entities:flags.3?Vector = Bool; messages.getAllDrafts#6a3f8d65 = Updates; messages.getFeaturedStickers#2dacca4f hash:int = messages.FeaturedStickers; messages.readFeaturedStickers#5b118126 id:Vector = Bool; messages.getRecentStickers#5ea192c9 flags:# attached:flags.0?true hash:int = messages.RecentStickers; messages.saveRecentSticker#392718f8 flags:# attached:flags.0?true id:InputDocument unsave:Bool = Bool; messages.clearRecentStickers#8999602d flags:# attached:flags.0?true = Bool; messages.getArchivedStickers#57f17692 flags:# masks:flags.0?true offset_id:long limit:int = messages.ArchivedStickers; messages.getMaskStickers#65b8c79f hash:int = messages.AllStickers; messages.getAttachedStickers#cc5b67cc media:InputStickeredMedia = Vector; messages.setGameScore#8ef8ecc0 flags:# edit_message:flags.0?true force:flags.1?true peer:InputPeer id:int user_id:InputUser score:int = Updates; messages.setInlineGameScore#15ad9f64 flags:# edit_message:flags.0?true force:flags.1?true id:InputBotInlineMessageID user_id:InputUser score:int = Bool; messages.getGameHighScores#e822649d peer:InputPeer id:int user_id:InputUser = messages.HighScores; messages.getInlineGameHighScores#f635e1b id:InputBotInlineMessageID user_id:InputUser = messages.HighScores; messages.getCommonChats#d0a48c4 user_id:InputUser max_id:int limit:int = messages.Chats; messages.getAllChats#eba80ff0 except_ids:Vector = messages.Chats; messages.getWebPage#32ca8f91 url:string hash:int = WebPage; messages.toggleDialogPin#a731e257 flags:# pinned:flags.0?true peer:InputDialogPeer = Bool; messages.reorderPinnedDialogs#5b51d63f flags:# force:flags.0?true order:Vector = Bool; messages.getPinnedDialogs#e254d64e = messages.PeerDialogs; messages.setBotShippingResults#e5f672fa flags:# query_id:long error:flags.0?string shipping_options:flags.1?Vector = Bool; messages.setBotPrecheckoutResults#9c2dd95 flags:# success:flags.1?true query_id:long error:flags.0?string = Bool; messages.uploadMedia#519bc2b1 peer:InputPeer media:InputMedia = MessageMedia; messages.sendScreenshotNotification#c97df020 peer:InputPeer reply_to_msg_id:int random_id:long = Updates; messages.getFavedStickers#21ce0b0e hash:int = messages.FavedStickers; messages.faveSticker#b9ffc55b id:InputDocument unfave:Bool = Bool; messages.getUnreadMentions#46578472 peer:InputPeer offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages; messages.readMentions#f0189d3 peer:InputPeer = messages.AffectedHistory; messages.getRecentLocations#bbc45b09 peer:InputPeer limit:int hash:int = messages.Messages; messages.sendMultiMedia#2095512f flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int multi_media:Vector = Updates; messages.uploadEncryptedFile#5057c497 peer:InputEncryptedChat file:InputEncryptedFile = EncryptedFile; messages.searchStickerSets#c2b7d08b flags:# exclude_featured:flags.0?true q:string hash:int = messages.FoundStickerSets; updates.getState#edd4882a = updates.State; updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference; updates.getChannelDifference#3173d78 flags:# force:flags.0?true channel:InputChannel filter:ChannelMessagesFilter pts:int limit:int = updates.ChannelDifference; photos.updateProfilePhoto#f0bb5152 id:InputPhoto = UserProfilePhoto; photos.uploadProfilePhoto#4f32c098 file:InputFile = photos.Photo; photos.deletePhotos#87cf7f2f id:Vector = Vector; photos.getUserPhotos#91cd32a8 user_id:InputUser offset:int max_id:long limit:int = photos.Photos; upload.saveFilePart#b304a621 file_id:long file_part:int bytes:bytes = Bool; upload.getFile#e3a6cfb5 location:InputFileLocation offset:int limit:int = upload.File; upload.saveBigFilePart#de7b673d file_id:long file_part:int file_total_parts:int bytes:bytes = Bool; upload.getWebFile#24e6818d location:InputWebFileLocation offset:int limit:int = upload.WebFile; upload.getCdnFile#2000bcc3 file_token:bytes offset:int limit:int = upload.CdnFile; upload.reuploadCdnFile#9b2754a8 file_token:bytes request_token:bytes = Vector; upload.getCdnFileHashes#4da54231 file_token:bytes offset:int = Vector; upload.getFileHashes#c7025931 location:InputFileLocation offset:int = Vector; help.getConfig#c4f9186b = Config; help.getNearestDc#1fb33026 = NearestDc; help.getAppUpdate#ae2de196 = help.AppUpdate; help.saveAppLog#6f02f748 events:Vector = Bool; help.getInviteText#4d392343 = help.InviteText; help.getSupport#9cdf08cd = help.Support; help.getAppChangelog#9010ef6f prev_app_version:string = Updates; help.getTermsOfService#350170f3 = help.TermsOfService; help.setBotUpdatesStatus#ec22cfcd pending_updates_count:int message:string = Bool; help.getCdnConfig#52029342 = CdnConfig; help.getRecentMeUrls#3dc0f114 referer:string = help.RecentMeUrls; channels.readHistory#cc104937 channel:InputChannel max_id:int = Bool; channels.deleteMessages#84c1fd4e channel:InputChannel id:Vector = messages.AffectedMessages; channels.deleteUserHistory#d10dd71b channel:InputChannel user_id:InputUser = messages.AffectedHistory; channels.reportSpam#fe087810 channel:InputChannel user_id:InputUser id:Vector = Bool; channels.getMessages#ad8c9a23 channel:InputChannel id:Vector = messages.Messages; channels.getParticipants#123e05e9 channel:InputChannel filter:ChannelParticipantsFilter offset:int limit:int hash:int = channels.ChannelParticipants; channels.getParticipant#546dd7a6 channel:InputChannel user_id:InputUser = channels.ChannelParticipant; channels.getChannels#a7f6bbb id:Vector = messages.Chats; channels.getFullChannel#8736a09 channel:InputChannel = messages.ChatFull; channels.createChannel#f4893d7f flags:# broadcast:flags.0?true megagroup:flags.1?true title:string about:string = Updates; channels.editAbout#13e27f1e channel:InputChannel about:string = Bool; channels.editAdmin#20b88214 channel:InputChannel user_id:InputUser admin_rights:ChannelAdminRights = Updates; channels.editTitle#566decd0 channel:InputChannel title:string = Updates; channels.editPhoto#f12e57c9 channel:InputChannel photo:InputChatPhoto = Updates; channels.checkUsername#10e6bd2c channel:InputChannel username:string = Bool; channels.updateUsername#3514b3de channel:InputChannel username:string = Bool; channels.joinChannel#24b524c5 channel:InputChannel = Updates; channels.leaveChannel#f836aa95 channel:InputChannel = Updates; channels.inviteToChannel#199f3a6c channel:InputChannel users:Vector = Updates; channels.exportInvite#c7560885 channel:InputChannel = ExportedChatInvite; channels.deleteChannel#c0111fe3 channel:InputChannel = Updates; channels.toggleInvites#49609307 channel:InputChannel enabled:Bool = Updates; channels.exportMessageLink#ceb77163 channel:InputChannel id:int grouped:Bool = ExportedMessageLink; channels.toggleSignatures#1f69b606 channel:InputChannel enabled:Bool = Updates; channels.updatePinnedMessage#a72ded52 flags:# silent:flags.0?true channel:InputChannel id:int = Updates; channels.getAdminedPublicChannels#8d8d82d7 = messages.Chats; channels.editBanned#bfd915cd channel:InputChannel user_id:InputUser banned_rights:ChannelBannedRights = Updates; channels.getAdminLog#33ddf480 flags:# channel:InputChannel q:string events_filter:flags.0?ChannelAdminLogEventsFilter admins:flags.1?Vector max_id:long min_id:long limit:int = channels.AdminLogResults; channels.setStickers#ea8ca4f9 channel:InputChannel stickerset:InputStickerSet = Bool; channels.readMessageContents#eab5dc38 channel:InputChannel id:Vector = Bool; channels.deleteHistory#af369d42 channel:InputChannel max_id:int = Bool; channels.togglePreHistoryHidden#eabbb94c channel:InputChannel enabled:Bool = Updates; bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON; bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool; payments.getPaymentForm#99f09745 msg_id:int = payments.PaymentForm; payments.getPaymentReceipt#a092a980 msg_id:int = payments.PaymentReceipt; payments.validateRequestedInfo#770a8e74 flags:# save:flags.0?true msg_id:int info:PaymentRequestedInfo = payments.ValidatedRequestedInfo; payments.sendPaymentForm#2b8879b3 flags:# msg_id:int requested_info_id:flags.0?string shipping_option_id:flags.1?string credentials:InputPaymentCredentials = payments.PaymentResult; payments.getSavedInfo#227d824b = payments.SavedInfo; payments.clearSavedInfo#d83d70c1 flags:# credentials:flags.0?true info:flags.1?true = Bool; stickers.createStickerSet#9bd86e6a flags:# masks:flags.0?true user_id:InputUser title:string short_name:string stickers:Vector = messages.StickerSet; stickers.removeStickerFromSet#f7760f51 sticker:InputDocument = messages.StickerSet; stickers.changeStickerPosition#ffb6d4ca sticker:InputDocument position:int = messages.StickerSet; stickers.addStickerToSet#8653febe stickerset:InputStickerSet sticker:InputStickerSetItem = messages.StickerSet; phone.getCallConfig#55451fa9 = DataJSON; phone.requestCall#5b95b3d4 user_id:InputUser random_id:int g_a_hash:bytes protocol:PhoneCallProtocol = phone.PhoneCall; phone.acceptCall#3bd2b4a0 peer:InputPhoneCall g_b:bytes protocol:PhoneCallProtocol = phone.PhoneCall; phone.confirmCall#2efe1722 peer:InputPhoneCall g_a:bytes key_fingerprint:long protocol:PhoneCallProtocol = phone.PhoneCall; phone.receivedCall#17d54f61 peer:InputPhoneCall = Bool; phone.discardCall#78d413a6 peer:InputPhoneCall duration:int reason:PhoneCallDiscardReason connection_id:long = Updates; phone.setCallRating#1c536a34 peer:InputPhoneCall rating:int comment:string = Updates; phone.saveCallDebug#277add7e peer:InputPhoneCall debug:DataJSON = Bool; langpack.getLangPack#9ab5c58e lang_code:string = LangPackDifference; langpack.getStrings#2e1ee318 lang_code:string keys:Vector = Vector; langpack.getDifference#b2e4d7d from_version:int = LangPackDifference; langpack.getLanguages#800fd57d = Vector; // LAYER 76 tdesktop-1.2.17/Telegram/Resources/sounds/000077500000000000000000000000001326245125100204655ustar00rootroot00000000000000tdesktop-1.2.17/Telegram/Resources/sounds/call_busy.mp3000066400000000000000000002476011326245125100230750ustar00rootroot00000000000000InfoO  !%')-/0468<>ACEIKMPRTXZ\`aegimoptvx|~9LAME3.98r4$EOmc<S3i&??+oY^3C;룪7`<ʌ  X@ ^4K? H7Xff~Qz׾?0<0gCYh\0rœ@ ` X*(xc1F @``o.d- 4 1'gZ>+=Fp+{;߿~xxx<<0x0w@K'X u:jD+h8-8:PGfH %`!0@+0+#vjKhd(ƒYFr$ 1҃+ ]n s2^٩(^֔XKVs,LfZ[W1,Jڶ NG^::l&h{;n`b@<Œ R$#Dj6i0FޡwaLee.剕ALGs8,C+ZϹgܕ+餺.L+и*_^bޤ lİM lKȤ;`&Y9xXM"#U``ANԢZOlLM:NqQn7<T@"V?Ovծniah4cvU]eafB8(Ӵ9UAz0$'0c1^M4N0C0<00@E00@7iч~3P0mٌRZDNEe:%v+I^ )?n91]`DҸ@\J5É5&/O⎛IYP  ;L/^̋Z0.KH gJ#h"AmF!7: (h!Hc @ 08!yҒ1 ]s$Ik@*$lr&ir}2Ejs'UȘuQUG*ꊆ|\ic.#(j[8„vUЬ (8L'N32 4x!끛D5X>M٪|=%HƳHW$i#z SmG$4gx4՞ 3 *J۩i)/5|R*O/.X dEYҐ%L< )Um3(aSM .M IPbS~N꿡TqwFA xFDiD=[N\B̺\=n?Ի4'3x4ʧu{Sƃ,yఽ͠P\U$+1FWs.l]000&@0z7008s4+50>1l|kg]GZ EҁZBC˥V["ѫ7!eNS朵2f[_npZkGa>&=W*MN<׭l3wsk0o2LǪ׺{H  U=m`8pzF+frΏ{2qYISYb2&L^ϯPZյڸd t%Zk_8^t b]$Zw.UF/YgSXx+*!P P%0li6B< &cLG1٨(,ALm36>Ð GF0hHQ/ 8KRhFEz%i/l4Q,(y_4MѶ7m`O(7OI{8Q@TbOް 1tɡ FV`H$@ 0U0|}7=&sLU9֨#0 ;;@4tc+f?̗rZJ 'f@BhdD)!L Q(K,\_=2_ݾp&&lZF5zݝL]$_|&]T0]0*@0t7C8<3d@2(EP@" S BID!)uXP҂S6Ůl;*IE",m*V >[ ik\UQi`v  :a^b  @?Bѓe"k&N agY"cnXf,\GG2+xSY;"]'$X|rJYw,LȔmd+ԞlU3)g룆Ga,Z$c~ `Q5M-НtP6!0!1Wf# @,D1C1J0T+̑"G|$jOQx/d߶־-$0|enU:H䗞|uJ9:a-[`mkXS@\=s0C   JRq@IBh4TsbaBIB(-59,J)ϳI,Kcsb%#@ɘ3{ig^1ڜ6;}Brw}E\aZUg3l4@ds#QS9C4d L40X4a@k+gs_;|SoRvGq~K;SMTF)2n>a]'$I7Q*(|=r<9;rr)>݊ 8\Tn͏WZe*1nɂ(*Ѧ@Ή 1d茄1%0 H2T¦a`k~\j`D@Gs"Rū0 xR+$Be)|a,@ (Dv 1mɒQW]ퟟn26c[/P̒d2=7-^[wf3"d N:fȨ KQ9vvCڒn]E:ȝ#@z:HP<ԡW.3$AOUAm6-7+[nݨ7QwS{M-b>ɸV9p#y])N6wP?bZ6ɯp2TߛKL2Oa2+YuJDI{̈́ 쬊wjh)YYhT4%f4knڜZk7Bp% %s1- j!wR()MM,` "`6`kaL`Vlaҳ*p qU۝*fͅ5$U mƢ7#CL(&f'~܃Yi<6,X..0c[OMVP J0l61002@ 6kQ1 c55`Wܨq940GF1P-O$\#$ n1Hډ:zu*!;LB(WV&5*ckȫ(Nj6[ʩa VH:sKHNto0ei)2A8*CrVjmxr#@˔zkC6c {Қ + EjHr)Ʌh҂2lӫ  xIX܍|.=!@L3sN"+1Q %A3!(5Y`VѠeHSMQBFu+2d򛶪cFUGvܧV՝jNQSԔR%jG֞K/qcyWjj 0CpJ`HQ2AªuV16z@1j2SZc!,XΒ) I`nD<ⷯ<@Zn2 0+{PBV-42?􇅖]PK 00$- ``/h`}g p{h>/4(Ux]ZuFȐ3Vdg8)@+=5"! GFG30Xm9"ca%XMˡ M7ʰ \#dd/YLZ! IؚNmdZp#ĆĎJ}s)`#0F3S Q#s%HcCB$M#]J~H]'(30B\4.8XMcc2\YҔQ1N10Z1 *Va *bׯ\k5\70kCJQЉyQGa5٦9d@e-W[ކ!3[.JhTQ^s־ۣ.X@G.LQH#ax "1 AEr1uhBIgp;$w|%Xj"q IZ59i"j5%?V˥-Ň٩|~4InڡOs}*#W0D0?Z8"$[JkHh{!/h흤%M$6 NIIdˁ8rsY 򊑍T)2|(CI1]2MtZ+m1i%)G(%aQ ]NEGGmPizS^0X"t`MF3yaQm? O+KHQF"wӏdQ]ؙ$tLf.$ZM˳bDM޴[QAD A'Z8n!)@a=Fcd bd#aFK%c]*4gLsԈE$OiCqIfxYRʉ+pV !EAZ6&E0pE Z\폷)M5 P0G@i4B~8 > ) Vz9;wuzE)I!([ z¤hʂEڔV֬u0CetxLAS(A MEޏP$S `4٪v`@a2!9p!ˉEa}-QB qjqGa&R Lpg BHK0Gx%%X&)soRmH9Fewт 0Dnȵr9@-!P$*Qp @*D]@ ?hRNiAtjWjdqatL!zRM@n#q,C,̎\˽6k$DK<~/0{@ `%!.ígpaBKTuhZ0~Ds͆:+q Ae3zvQ88M*5?FsS3H '`<a *frkxO2G >m`FU$<M߿/\ a|sq ,*#i:Gα$m;Hc0gh{!/l%X>J4AZIicbgK|Ӕnh|2S)EH \SWWk ـLɛ:g  rB:ꆋ&Sʐx>)6E33S'ଜocŁ5rA,p'cXݮ'8v%y_$Ci@XmJGmPs0f&ÂfN2I }" h,t, FvRefPE0}h{lb p . Ի 5PJ 7 dld !t€ I[D@]D9$E -3qvZ]εѥU9pyg8g= MrGJhwIlx#v\7oE@cݳ &, k Cr ?T{LD44 TvzgmrtVaHhѷ lk[#.޷F4nIIj'V j.҆"G>]$VjvmvfiASLw-߮\d`!9@tH柪=C&f"fbE>6!0&‚c̞O.VVQxnԵy+_-j̳ݯ4-|`OuI*$ hI]ȓʵnj"|% M ȩDzy "[&vEļga*!`y1ӒO b pW^aZ4{VH%2"J~j. N .;2;D6Q/ȚZ(V!la26dyq>g j--wZDKG߷H,¸TEAk^x$dͱN1,RuLEe`F QdiHF",2n%"3 (JX-S&--#4qԲȍ'\JUpB%xl0̇VMs՛9HEHBgiS5JfyֶN|W;+ 9"DeyJ9#t8_i GHܻgaA%ImӁ<Ϣ@nAZ2E֛H)-TVY.!*w)2ء]egEV3-LO[$7Q{S3bEڏiH :1G~ *X#BHˈ~8zL2\s`곂PF@gbG yE bƖY -?#?)ɅK8Y ǒZm)tPNzF%LY?ҀB=v_S@'z+rI^I6VmYs4V8@fȓ96ͪl<X 6X́Cz|iW<.">^QUUځWGYsےQ O,PJGy2"D]Ј9-> L:,fL(p2doGzO<V,i$r0%J8#ʟJ$9 R%,UMb#2.L(kQpMm;dm?KdSQ*2 X^;adMܴ aD4,=#QuE["W~vl+@hlgRGa*K7v90ÓʣJ{ym-ӷܳk; :Õa 0kNTh I>dxdEH)i|Em#Xhh($ r5<.ޣݷKK,: EeA pzA;"5⹑:(LbPؤTXZ`l&@SdKLB)@\}@ (h\|Vh;fIRp,ٚQ6MFͮZZYw}|;t)ߋ2B<uRH˛y 2&Ť FuQ9S ;Ͳl6H6)8ĎDN# QA]Ikw Rce7KI)Y[~Jv/nrbU%zgγjeAII^.ƏeZk]̅ƚ D]h8D=!@x#y:ƈ)Q 0_ utH0YB('yGAA\.B.Y$Nk%i&EJe;5e`\4k<SU'X9c荪>Jmnuv=PFRw#6ZM؛0VE] 7" a6Q[)ܻnV8!}I`F6pΙ-G%\%c.^HB1 D!b,J >aZ4,&B db0AMiǒqWw;H'UETZ9 M2KN, .2DkkB9K V%2J v[üyUMW.33ED):iE33"^-C*҄ .l5&p!QQ(1f p(:G14% Te<okuM_'ẄgD (=#Qqъa"yrܴn^;5>& : (ڳH*Ъ\ǃFVdTǒԃ @<\hn$.d. TJqqfivGtw!DIQ[=9{h(pFE s|C GNklu H7*O翧\3mhڡf-nE Q xLJR?Pp=e5{y*u.GXTb{(irrA4auSbm+ul0u9b&AۆME -*I8I1`o?g%"N.ےtx, pd˂ 'm$56YrFP0z,uu`!+Lg)vi(v4;sI'&YHL kD$3)s%>giKzgi׺PZt>nJu 4`bXmf?YAȖCDj 9,!M a9,PsFіV 0,Y%DLTM(U>(ҨDkjyT1 =&~Rw!Jv"ya</5lV2`4설hXQ *6dB!q :@qXPiY[Z2M&4&dOU?vЖ "DEIB/$Ia ''݂01>Q-VF93$>-f8+ ]cQ$U e!,:Rtg=b5Tv|̤R6oHii2Py&rΓs Q-#'mb?Upb#9#k$haYa3 ճztrT#_}^Scĸ gJjΘ-Ԗa4ldtѦ$8KFg) )D @K~,/mq6W5-੾liF7Q*wmḳ6?[^vƖ%k} VYu<!")wLƄCcSIU輘PD'`4},b~Yi Ί]v?93dS gf8&VJ7A%f4-+7 |f;ǀDa2Hv_ C+يT.ۉ*na\Q2ȼMB.eU<_ -S?"HdHU!P!?Z:m>>QZ53,[_^˩\ULN]aa(N`:e Ta<i aE:`n`(``,``p`*<p9p /,)b\^zerkQfw1.es,v5rlᖹo;u5zJlp;ywnwekx~<Ϲ9Ѫ`4 F8X& M@+f` P& & @ :@<Y/l\'+.u kTe5}Ñ؟.r|G Osy5n٨dm$ B&l#s5>Uܞ)I!1yuUp3:F~ojoy @PL@ EAL#2 Lճ0d 3eXޫ b6'əBM95W֣tٰ_eev\a7lO, W;KI ȈO"cefĖawn+ۓFHF2 \R6'xXmcmY2_yݬt- LЪ3„ @` #t5DG$ф3@ISO$f#!yVl\ZeT+DEԂ_F@S^$ɢLl^^↋͖=$E20^@6D*:|'U A-Jp@D 55_'Eh&$M74mMM:= 9QUJQ&=Uϙ1OV ^jK:`拊[nAj`B"kwS   0k6D03ie a 5E-S= O-OVN)H!n| 5Miwc;f1pRjBjc"SL XfHïls^cX;m NG#m`D ta&g zby:"ƀ dYx,PǏY E"ObTUIh]2O$3=q)w Tr.%$ȽcAphhX$8՜(RI-R&D70G8F$Q `X 2`&xQ%sCKU'A0.MtܻnY\Q5՝nF2杔R~JQJ:6\:;cs ||q&(I˺kM׃K|gw.2y}_n( 41J JH쐣)%LGB#*9Z!fIH$҆ cOSal]M}v'T\k|&",y;ACA4B -ciZ  L, @Od0i 1 =)ui3,a(RX9:ipkm{Xܳk2]oot2]_߷!C#EN[H.h`TށDI>Q 1ق@[&ܥxBKJZtnf$;l"-k^T65e8H(5/p#cXzF4r$r[ɏ%j#L1kVAI|/* cƩhpQ"&LZSz:ij0c038%1hp`$+Đ9=ĉP!.NHQ(-##7P ى Q:LYd?b$7UՔK#f=KW+:@ ]-_PV8 }y1)F8$E&L5|ȜZV2ON~DMm&N[ETk8GfJ8G"A !zR969>|7]uv+m`a9'v1-(19bFiogr)H̒gBybF lE`҉ׁ̠CM!Un6T>c5hGq Ys`<09B ty\ÇIE6i$tP#p˔҉$a=Q{Nt\֗4g,/ 蒖?55Տζ fm#aEbx &"*hA BH0yGy-{O}%pY8z 낉(&VI o+3*LjH,&$U~H # sM:kwoY2)<E W5=BI+@ C dRlÄpP}0Z1 U#2s@RCsT"#YF$rIP嚉h)kp QW soH  @!" 8Pg@átAA2:2(èDȣģiHJC%h?YZأ )#Ӓ}ɟ{2n*RO 2Ņ & 0N<_.u00(0g6^;){212D$ KDwfM!9gӏE3a%*!0wZW#d\+5ǫ*'USyL&Gי[Vrk떲 AH1 |Y 7/ (#YXov{F\vV(`  u6#EF+ 4ReR/Cpx {YAdIsP,J,s=DHʋttS1WrPQ@ `Ώ8xBP (-^{uiʪD`&`&`@d ~m|Qf HY@"MW)K.4oe,LoFSD`b29iVƫX9襑.зigF|1يz ȇ~\)1NH #OL Qp M1|´ CL*K[jZ d]` @sGhlr,4X/b҅(m~їo/>^?}R(i:+n3K/}00_0003WX;ˉ#+,0]ZK8KXl`Tm$,6e7cMf_`蠖rϑcqKYH'l)>inzc^3lhQ7*㜗E`7׉BF30”*HQSHYd /~k+eđ\*eXEY{JH$Yf?`aJXwmr{ҟJFezY,-^Pj[j|_mcf ~8:L4ToC`HAX*YpcTX ֬=|rl]4Lbc^] ަ0dqaN.^QUJhX٥2UOW|}//SrVZ DJWvGRuT?0/c%jɊ}ڶD,C|hc9Cϒ! +!UB4Ҭښk>-$^--(me"M=#4;)q $^+Y/vI9Y l4-fUXKA0II$x/# P%6]#w[Q  @ PM򱡌6L@ ܡAL\ÓYjQjҬy-Z`zd#K4h  Vi;V }'gHʤN8Su$2Κ`Aa\]8c_]Vo.ZټY%֪QDeA۹M"| R9l63޻K4v2=>": )eʤQ6ɡ4Y,m`&`\G~ kT p00ȴdAd˶# q5$jl—FrUlԄ'L J""leFF?Yn"{1zJ w1ߌIX@ـ(I1-O@ |:@`'H zȶq-17f4m"DW֕ Vm'| QV=d0wث^6ҚMr 4=IW֗AO'>?9gw1o?&g+vuxުʀ` R`9\`ćNl£ac2`]o8'(bb3<4lfZ!Y @d!~J YLAO9CYE^B Mr_eܩBo6I3: b gjmWC|4@0Q0-wNHG@'yvcob-j7NRN@GۤJL&_Os3J)3P@%,1&5LKo[(ܖIKom&ڛ'cD99ddyha?a1xC} J@ 5h_@(ٜA ztEoyKq䁄t%lY9N-AɦQ%.ruCI5SV"Ɇ[( rj,P$IB%+ ZV0dW[;31RIf.Q/ٟ$Xnde[j4l$ƀp@\pP]" Rwt`«ƕTiA%YgaR/*ޅ4IqJ:iUվ('CE4. 7u^;jPթhhG`@﫻Mi)e'UUUUUUUU ) `<' 6IUxY;8V(#ұM``!A"0kAAtiQ#,gPbLf$`tG bK;S])I-?[Jf @Rj}{|P Ѯ2=y6@iN)cA $r2T`EmI`J,F]B D/ m (]3Xr)04) w%EuFʰ餓oc۞iY5^I@IzUQ W(ɬ9;>xH0JV%0f-nFʧ Z(GF((Y4%A'FX%EGdVT p4񳷾oN’C%,ei&h? IBHI(q `*d옸60!S480wHLjXZlYl ~cUMX$UKTIDdH((0ADȳE%SruzUE"!&]mɒLAME3.98.2UUU0psOHn20$ Hou"k$PTŠHPE9*H|LU=mk,Dރ$l&} k:͑@$@Q 2)vEd2"͆s7V#' ӈh(N9.wj ]4wq!Mw 6jhQsdZ4/%RbO-}X0݄}1؅.=tݧBPssLo#[f4uӄ7mUK4/͐ UR!. RS3Sj5l:Rڱ磤hYGeV"_WT(Cғ)M3E-YENYneM)λxɶ}`3U_3s bE+3r)o "Iuǰm"xxpnZ*G8ggT]k+sa!+*$.QdrɊI~$鮻 A!7%ݶ7hϔ,OZu$tJ}jEȭWqᛏf[6{5uH_XZxcuI "@uP4T * F>XB٢"dqQ*4܉ РpÃY) „ DM#LTj) 0¡NoP3[P{uGZS.]Z)TS8{o?=;O:L^TƉhye]yPfI ZG# l3!2EG#”j(MTUAmy Wt ;11cA62FUEMr{<lt\Mōؤs F +Dl@ޚcJ2P2dJYg,_/+ 1p Qoh0½Sunb;l|\ ^kV찜:YB>ܮB^Y;C*#oT7)uG kI9]hSZꠖ^oYo*R2֐2fKi@:厍dɷ-#hGR\"~;9 A??qtyM^N5-Kpf`f2a=ŇT Р(%pѹّnZeH PC8dT#^ na$@qKY'YBx kKR1g\f+(ٱy=Zc?:oݕZ U`X>>"$h Y\ׁ]9=Z@yX0*>S4ٹZ(2;GVXÖtE\;wS~V(^f3b҉lC4m(p=&PIuyXُѓcYb/NM3 yO}6,d!4do| !|8|8UzY9,P(eHʚJT sƜp4bW\[u JנmM@2_֠$>LZyohVJOR#)ĕ߉{J-h.;Ʀ.FᵇHQRԂ,ftc9+ vw"d!2d8j +U&Нisȁ"ɸVDS03G`VaJyҾQ8eN Բv^]3>@%ܠ'B2zJSRyc5QрC-c>*1kHˢV2[=Yzced0j̳Z+9DhA:;8\FN~f -c ݌D׳Ve$V[fɅ%1XC^2a!LETADP$,zsXZ*ȑLCt=(f4Btpˢt2 92>7dXEIIm`=Qmw "TyU)I\%[3Ү:׷2د0r 2xavrvMy>4i!@ʙ"1N6&G0\ަ84@BH7 G!41,,JZ>2Yνd3mk?^&68#n%6AJoWXDZE4֪jgbQCÞ]0Ij| _3ћܡoYᧉ$0*Fi⅛hK6,"fch\ 8]-XhETm%d*SYL24G4ZҘcAce'`dKNؠE#7IhݘG'承q[> H0raeG L,3K@)c Qp  HÍ-"7yLMe&[e,Lࢨ<21+Y#s0&̍ΈfD)4 =#QM 3yRЌlM (yX&6rE2B 7R_T)dUeLubW8`4.JE뼢8M39LP&=*ÒƑâ^JN:LL׊,UVw[F|>?K]N+h:;jrwi>_'\S& U0dxs]AS\iT"m]DoYʇ 2OeEpGe, :UHW S"*~\iǤY!JfZ<l'E܎9)+rK=oy/˹ě;SYe5nmv,j,J)#*;;mW2Z֙0\p0hvQS$$EDAf-AqQI6$C._oXbP\t`\) s 0i2O4 yfr-Mcjr8q8?!}W2W5’Raz}GO|gC/ĂlQ`=6}QܘldZpX2Taģnr"mQvI87 ^/-N,nUU79{X2%h0C3KG'b֯%oHw#R 0>yx w!bJG5 3,ڄODHdĐG@Φ&f"LQD b8A#5!MQk5}B kytKG{<β9rKSXrZ p ,LPэ sϒ3QDq}RreXbԶ-)d{z@lAfaJ8M4Spvǔ Pp&2j=dGz,^՘B] ^a3ֹl՛ @%8Kpo{O2'K1d_j|/Xd,si-쒦 )c )h@?K0~h{ 'x X>Q"K|7LCT]? v3Of?:]+".?,rX1-2@q` әߠ a#e!!:!&{d*YgH~9P,Ƚƛ ˔&ɦx}=ԔF19Wdy^J&5ds (>+@Kt%?@APz V(2"-k -ZbD"Yf~S?ZUK\VaTD]|زa !%[?[%ke`  .L ꌻrNe1Td@xـ(''xG\x{5e5c!t.y cçlɱJt/-u"A7 (atwMWu8I @4'!1LFV1yAh#VME0PL4Z.&e'G*E*MYKOz)f0D" W ?3պNю PS5׾f$ C,%6;GK0h{@/d)$GT02J 0B0j&-܄l>Oc25+ZF8i̹M >)E ]ܶTQmKpi%FkXal-8Dq?oi XÜOh Ƥ$1 F"3 E)F F }:q6J*9U4P VJJf!ت$F0M(18JZFKD>UR 2ߗj5ޡ4@Fggcc@!j8:h,$*6&L'[a!62.Ȟ"?NGڊթ4=jY3  cİ/+(ȡf'Tm"@a x p nbB^$ N:ai 3⼲Q^ 7הv-"¡NKqjS3jiE31\汢vTd,/ G vmٿJQm`N va&GkcN @&i3KCUY-D IՋsm_ҳ@O.cToV̔'DRedԄCah)NT/bU =j&'hPD%@iLX#En T3nQUhϫh>Jf!mw?)f7R5WWe UA',ga!!iC PܠK8:x QQIU&Agk=$M=lCJS )34"ԞNsqlR/IeM&F>.[#W"#/HV|)^uO[\?ʩ#Ic@ƞ0LmB8p`N%űĔcySt2ӵQDEcY-)D]12nEsah/ɳfn3[N4+By׸[3Ռ `<@P3I hxO'xJ@c`l%bDY dMLfM.V'Dƚm)1-7SN'anʏa^9-=eBSؑԙU*Sh}φgBf>Qkq> @ԼE))e'JAyH +nfrTjèF-$zL}&j(UWHuͳ6reD(IzrHttX]DrobA%&.zoc%#])f>TK')JrN]i矩޻ok[$зiS9* 50#"T dߨ4bN$J:rqv2DZt:_">i]}\*R+'gZ5XIBs{,$+_w&'ګʒ`4`2"`,ksJa<`Nh1#Q16 Dd)A  JvQn+ i4 m@h H׍JJ/:tfzuVW{}ʬ|͏"1B2TūH R/.c$&i%Vܧn]yPU. 5$(0,S3@ 6x $n]shJXڨbg;(I/4U?*se!E022pK;ߕa{~=z߱VЏ]2`%$`S`Noa`rk&"`bfp轜MQCM0L@r$12&,]&n8y.Pt&Pb>W֚/KխiI6kVC>DU_J/O$RSıil’Lq )v0qH_(41067 4ܚ $!$d̪e+H70Js m(zlΥtW ;Ξ}kn J7˥i!j,ūA.-{8吊 ^:N r;r_P  MߢJ* ƓL;)_DZqaSJUt꒳AB7-Hj؏$,QݝV89UI'6A1EXԺ`%T@CS W܈NF312gHi$R "5$n[mkq$23 :0 Өc10=0%ƒʗ7"UQ<=Pxx$p}=|}Zwrw|6yYp8GNjӻr=p~^eBGP\ *zAA@y:&AkgQ`,ᏀXI"*7䍟>Z A$h-l&mʍ]RK7Ju-s?C>)aetެO35$C <3CES O0*PgkIHPi >!$f;ɗ]Nmbb|@q Hk8s,dM/ASI%chTP FѲY^ͅ3u 1Jq"^*ik{ϑ_r80{00 2 >6<*C# 0u.Zj#zAЉ YƝQnI$|S#%Ȥg!NQ&ᙚwNKV/၃7HWZLEHHÊC#Y+Wo DB̂ 1Jx@0 .BSfP@Sҵ0Ŧi?I=,HI)1֌@+NcQGc6ړ3GUZgqͲ8[5rֻb``F`p h 5>{ T U& & BFa0P̑@aZ4CFKz@0 2\j𭄇'7'_96w/UOed=VYF_[[g򿷨?mQ=N.wxv{-ֵ2H qgxE O2`@bFkz%\>(  R{\,6aiy1^}H)k,XBԞNٙhnٹbPhTj9(paL3/{H y @,8Hd. gd 9@3(JlYTJӔ*$@\UOxŚ$ bqͅ!Ib)CP de]s$} 1!Omϳe' ܈LE0i9!/hĂO"bc8jf@7) ЏЈ$X4JȆ"_B 岑vEZZ]s4\36|(YEFC1pif.gĂF%#eCS}Ze@0@D0%m0h6m00.-4LJ/߅dKbF <E &AۢZΠPgNqS& V6I"=5ECrZu:mw1/ٰ8yS74w`RiP 1@^*FL Vk!NJekhL@>‡=2ϐ6xMB20DF~U'= 5K|}V =n ~`FV;3WZ[ Nm߯+aYT8S c9P#FcAdELq<ߎK3("J81KZ)} YJOΖ.s2>suF \/ɚBVOBIR*ULÅ@=.UI_yFb(ަ0 V0 0C1R5|3,^RРaçDdb0h\3K),[z>M[k[UUbTU0u$cS x Tj-,Qg?||8[(eӇوj;0‰(,/"d,b a t0(@0 E楾˨=ňt*aDa@>v30JfcρEWnbTjт/ 蘎 2LqH2,H*JK5އZs-kΫ?04: M JDLQE' T^ɩhfHVM^䪉v&uy0i{Rb7#rq4Y߈M+0(R !/i%n#$=y)zɂ р\I |usHjk/f8mT M"%&BC$+4g$¦ZU&VIbʺpFnss Vx D\"ȟ2PA(M% )A1{c,4`-<,=Lbԛ,Ɂ/LgnO.vs'IsRQ)Nຣu5HBt&M&:3?dfͶjWzk+CO8|P1тPѪj7f2w-)egu^AbfQjsRBPFdk(z@NCN4t(aRS鹪SiUzyGv={SF4 4*  "U?pVZj Euؘm%EHMAH0>`NtlR몃Hm'CN"'t'i$+뽼32 r 3)s Iba>X\I3(|cʥ}:NY̫SLYE1rɘ{F,RO3@5#Jhفi2c*:fV,G"6N6EXУvEL\MV@ڤ\J #Ӌ> Z:Bmn4tʹ7Sr}JPH i2 2I1QJ ďy- jMTC4;r̀31vW*rt~ U4QR xz"6]r27w# /m$҉p! ܔ!FqëȰ8o/@l3"yc9|άnM.Wq;%5%;fPp`78ԴSW^rز#dF"1>$˲̼f)mPC.ۋg(fS%-)G}M@ 8T\pQt=\@unmBN@J:[tU1>4uHD("(0!(@dE 2XBB KҳB-G$*S Wg[&lyW1uG8?qUXC\/$dA [V+L\45c;BSH mU'eRgRCRn E73 @YI4zEi`ǙK۝z\#' 8Z}lc2s>Orh^@^4FbR@X?ا  B˛ @k(bCI'y XS"IBJp]XLpd TYu2r#Pr.-H^6<O~oQٯnj#n]?qyBT>vD(>:`-\ra2Թ6xJ*LB'&,":3hX&(pMBfޒiS%a +k:AU-ַ*)$ԯˈkLE4r =&^R}Ǚ#)#$8FsƲ_'? l'-wcqw }TZy ⁘>:((Z~ʪc,LNKzjn'.'iI՜y㌡p#tJbHO)Yn,==pjiLP-W{U"ue3,vU5|JhPHǛD>Pjk]P)$M T<*a S. sȏ2!X詿T(66U*j6S4|ܟYm7V׼ ۹B#wu/}_n}(emj,\toW%'G#$,. 'a%Ѝ,6+x 3|QUQsN'c'La FLR-vw]J؛4ۥhk\Cfx~V-o6R$30A;{l `XGc(|a4%`ic ]d| @ JKMڂr&蛟J/CBp22E F5#_`F*d|gǟrdTF4A `F@D %-sr]vN #Ȃ6yҭ8_~yɭwKF'u@'6*h˟?MRTjڝRjHL(c!>v]6Qş\ca!(рf\DZ˻-ZTH(rD%US6QhOEȷx)/#d`X(IG@bMF f;!(âX[[5S]ˉTD,k$-[u~bt  L2L'1HC Zi&Ygm@Kתvp3O KǑ5l~ֱfm+s\5Ɏv76j$Xg|w(\XBDI5b'Or:`H L M^PL\ y]lN m;Q=9flu1D4גpaPɫ@U1r%ւTG$IU2)(0P* @ 0`T w񌪃` M3E5pm=\#Y ->qFbSOM?֥ܙ /wUpNZf0!UJl@z,,㲦R >pVbDNJ:= AzYjCқt L̬Gjr39"g*gZ "Acrn6Qڵ2Q_קHfFXTYGcdsBf4 P-49J΂2K1EEyF.0邉qK$Kj6bCUpiG\kzuF/tIKCwK6 q(l4Iق$0-I{ `IfifaHv<ɞF  ]=}tӃi)j.Hw7(3A3^\(Kr8LM'mC՘28@Lm_ UH j`1B`jO a`F:f&"`@( `si2,g0 a#dыBX7 &VkAcRi 1SEIУ֩5}LLu0@!"kWK̴q ѯ0.L,xynطlpܹ'RUbBsي|mx͸"Q~:Lj!hZ.GpKsy!1dݶWpJ(F!HFeJY9 `  0(,2@2' U ܫMH1u@!4ۏ{ǃ㢵-6Njrj!XdwV<2܍v 0޼b iBH@(H$Q#/ ]"`};+T_ T!5Zfũ0A6-dHJ&T)'9R<{03`vL#0*Q;CIE;{K'ydՆ<"؍)1{(t1 ;-;q_ӕWP@shm8J D"<cѐJvT&gY$CJ1VB ~ݯk}"48׆BaL䧁"nZQmR8MԯЀ c65?љmw y/t|]$ * HdHL#@CQ|t8,paPk%$ZOg$m<iw'$:<4}+Z헎e>e3mkFbRpQ҈vJ 0 (Qa/7=2c s@C]8S s'0łȏ)5vi(6R(:R&ӪE>ImAwh)JexLcmlJu9/㱃CM?:()ʦ0-0&*Z%` F(Ign&>Łiቋb' Y!PjLL6$ǝ L;I5ʐ-l;"fYY%ي.OO) hZBCҍrQ-{ъ3d'dUd.JkYm/ؽ0-T0BK8t?44(1'+  [ MZ<,R, 0e%덤DV2Uwj?eVZH'LyRz/BmyE?|g?i 4:J^ 2H5w8Z^vg2ύ멂_rQbR`k ?ۈJE0•R#XYܟV{fx F0퇴 &tD&I `  2sY0A64ycCC ١앍)aߺdOٸhBBɢsdOd*Aq0.񲖐9iA` Iaadp'ulnvֹHb7` ^ Z6K4ڑXbaL juqDsܦT=c97]B\k( e/2%n|  <")S L( kPD 獳3JQɭBm% E;mFxz+bm9_66mkb!MCg#1[_*[-N,*egod*A1Xyއ$^,IAh"Y4Q*&EBa0a1An1EW 9WĉG9JS] 2(IhM Qق[ iNѤ8.-by 7}ggj>(_9vhþ׊mMF+0ص!/"Q#AGMov > BsK-˜ M@L8йٗT\u&`KZ5N Gc80 2(EFq1nuE_)# gh !L\Mo_Tmr?g]ZQ(!f`Jf`vfYNEٛ|WGIїDtS+t,K(LYt^*}7]ƹT]JJ~t٧batX\U cHXۗH `* - 8F1Q-f`ɂр@YI"je5DPD $`'rrͭM+65,%QHb1ULqd{v-G旴kZV1M9(VHCP*@sObFm! Z0!3zD9\@HˊL[> ҳiN!GJ7.G5|'*XFE˪ԩ5qzgA;&a[fn0nR_6 ݏ `,nuZtxiP(p؈J+0HH#q"Z"F -Rw:> 17d"I|AD6Q\X wpȱjMl;HYIZGJo1$F H;nUdEۥ8Ҫ;"{fA=7:ۿ'٦ YTQ0!@A0g6 100/5430 3AI~nxbVz<% b%J`L( Nt8 ;ܧ Hd|{u|&UW&"o;7Jb@x|^س#A.O< `M`0oa4`n~kZ&:NbFTgi%cz.1ά(qs8&A"(a G2^C_ "Lhc>yt򵓘fQyh6CBRe>5hPwYNd\ A67Y#@8c6zd)4Jit)̰IA9AI6_L9Js*jRbxi6lU[ȣ( O97sy~ׄ/'rJSq;yVK;K;޻ܯF1@I(f.e "X"Yp '@2G m:Z 0U$/<{,9_{S]{ `'`Bd`rk< a9`QzDFB1y |.}ORZBN䞸y$"+^Ԝe߃&, @pd#%Ltቓ0>ۧܿpꨉƇB$qŘ? hkzoR@0`0`M`^l#xvb:ecHdLf! {_(Ȗ"TPO& k,NQp"o61%svԴ-z@d g]7trFg_OܟλK !f؛nZyY ``/trb=E^"8QKDD%LH4G:4 *~).zxx_`7L5*޾*}Ivt}ӂN80"FkRJm*~ iF0GhR=O#!$=~ံi!H 2ɌsBcǬ[>*5:u Q@hCB#^jFZ(tAFuw܃Zg`PHF+P0mQ@‰Mzc|&XGtSF(fɇ5OmHjd~'3H4JREr tS2㓂%UUUUh8ʇ$%hh٨2l &ZIXwxNV 9̡HeLn)&̸E8r"i9]ΦZH9ך]cZs`8k7U@JaMn `XjpHKƯF=+& yGH/BL\͕t]eAN V YsȜǑL VHBBK(| 3G9&<5@ɘiɝi Rqb1q[Su0%(q4Ĭ(e 2H^HFG{GQ!AVQ=JԻ qUH)Q"T'>Sڗ*'2َ /6ho(D rv־eAz8NL0\dc+ֈ@!#͘D1yT442NMr49BeHEF] *U ΤkdxMG,--Ʃ[fA&Q14si q]a[|lXAJaD;Pq88K1,bsFI_j%Cu27I0SGQB Hp=^Te'ʂ2n;Gor4"&UҰ?vY֕㦕lV(0+C $V0X\e>^eB69E2pPH|RH4iVĿfx2,"Ʊy9p6|ZFX *Ɖ@Ys;~Co~[u#UfN*;E xo䅩^jE<"띜أ+YrHM(2iS v LLHJF 0hB%DhFup 0ڗP$+Y A<'/*&(^Fu@!):(P0()zL8iQbJ [g3QNB.4PJ<!1F*=>pYDd 8 )41y#7ʙ]Ƃc@ڂ9p.O$(Bh*eF9lER`` uML]@g3hҩ'k+SEMAOv$#u=fdέ]H/oK0+;=2YtFU<- ^N' tYAKk.duiEtӢ#Kud)jgR{ZDtZPS2 dii4\:[L7'#s.ҋ{ yo!+ݽF  e@gm]N;8mGe|K#ů6#6`s껜eǞG'$ ַre aYHPBp8Ṯ$Ou?<P{/T3c]>AͲ⌼ޣ.*M2];l8zlc# ,DHpF B(Yfbn8"`B(|2U&rT2B@\ UeQHғbݵM6bՐG9 9*=3xs(YňLP)4r~+0Yu¡7xȔ@\Q>D޶GߍlP`@"mJ4F>p# CnjX5fvp3ZyEaeSˏI,:lN9΋n|g ;]u&Lĉ7Bdʁ|{e+_1Iiǭ2֭Ϊ;ngg+?#vz`@֍Ys YyHS-8+)!ZP2Ҝ4bA!eQ$/0`a(0z' r'F$‹A :F?E2b=񞊂O ({O: l3L#_+@>hj׭-m=%zM/S}&y=iJ:)pkWJ3e r@GE87y&KZ̰ quA=v9?f틻":"k \ <0[/CH\;epjbu4hܫp5ވ*Q.?|u4h^R]cHaghOK1)Ο]!)HL%HsI`"p+H"GQ@,Ba_Ylj6TA3ry1&nʏ!9XL\ٍüܠ?E9RŕXR+50#UMR%jfW/8trzUyr4 /u(?R++[*Guu#tΏ-(RydK @8¬"B@ZM`㛖y5%ӊ'rHzg} w_Py4[5M}k5V"! @*ei7#qak~V 5H$TV'wH1xG.%P(V5 +5aze4+j#Omׇ:/.8h*  }|! >^*"M&ʚ# @,@$zqQXJ=*NV\5h9> OqcPd$ LDL3%FD!ܢ ֫}0I<fzWtL +J*.æ7՘::Z$zekdB*("52e-L!8Se&"6 f4A 0R8YrުyGܴsF8Yͪkdj&^QY:+8.+*(y JdPh"'M ך(A @@+qBC}1hv>"̈́PaÖG_3aStQ)y)I}ʏ=e[3[g.\ނȪ~DxOZ2YL(N~l:iH!B% )rb"FuE̪P((I@b%&fQNu+s@N[ݢ/n[AѦ3W[i)4rM(=&QeOǤKby%iˏ%AiBT ~GS&T(@A '$GX>TJG;2^eu%ۥN!#B8 nŨOyZQɕ1{v0&xa2Z7_^=tHTUJم@GL.hXe93lZfK,=fOYSQWm볇gO#M*(-w `8>5>bN=4"h ם3u-T(23HrCY!)012f$q XAIQ7&"Fq7S1iF .!mA@U<JgJ`ʼ>*F *,yk5wkKe~q%Ζo/eL~CA*E[BX AÍ^i/R>B i@y!0KYy3&t>e#M0q'(A0x+`Dh(!$h1ڪm\;?tR\ogzvӈo r*x<>RQgq+TO3i0gŖ*0{ +RF*% 2q%]Gl63 ?R# !kYªV=HAw+l 857=G !6xIa KA9*jHQTJZdF9$Fk6"]#ӏbVD \؝ ]t.:/kiIKg3 iĩ2(%&Uʅ&qmnk: m^@sR:(ݷؙw!&#FPCY.(KȈ",)DM̒j(R(P!FI] ]rAh5 2IlQ2yoe+ͼKfP+`zsթ:Aij;%dŕ CP7$wPw|ǼTHN dDA"?/*h@%pL:,cP)z8@ pdŐUlyLR.9!9i ۠XėQ2s[44 (rfX˖V10r"U`d:+2QY(H^im>BNpFjM-1+0`5fM|[];+x1C8bIt)1TAGїxgߜJ$,A~6xPZi" ɂʔmqrLhNѫ[pmQDD{>HY0Fg"@\CKL1s>)vpYJ &kεa&_Z6 m?PEҋh1#S k6V}{<h ʓFm7]xQ@ H%cّlRFHy\:p+&dдZ)iB!P3 FȓƂ2ȍDQpZ)̼)E iLA\piXm^0݉)s8:QeJf5.l]NmsڕbvCg2;S&tɶoI[ l}DmɐiT"(t32 (_ $N34΋ $횇l]7JQyY5vϨuyIA(yj>Q9/a@HiDMl'+ weKi`oNv~x|WRVfm= Abu"RIdd$Li$^ &PDj1#fBA23flW0ĒNO"(%bj^P~ d6TC2PBc 0:12v2%- gGڣe&\ *]#p`E'IaLij]!@l)+'Z>kheB勠GF j0e5>VR@a6A,&E= b"ȱxM.AXu>0:5)c{lڽt,V8+cBdtBgH9@@x؉ET8O?MY5zFlp WV +DP}e [$Dz؅^tMixM  $g)rM=&SduˉK.0tz'e(7Tlˆn'u7/%DL]|-Vx.6Κq!f{ʖ摲>V#2SbS1@SK,&&kHj6L`V!谄4A 0[HQցe6808'qO>hX7K8y,ДZҴ H2**u,QE^q7bDr =&Rћ $䴘ybwIkgr/7[6zB@*lSbq3@D녱 PYS8h_ pDTɔywM~!i@!}"BgR@tqfZIѷi%r|J?j)nVo`J o#hɧձO+U۔L^DK"d \YIuQD+"(H- tL ଲ;SDIc~ iδBBUIjd ^qV.&, @`gs}qԉfa9,Z[*x=+Zü7٬x$YH^ B\4gEs`6<*kr$J#Td-Z$mee b@0m"n!(Á}׫sl$_oQ.yvtmJcg_wZ򷾼+O^:SDI~͖)/q #ÆUJP Q{ sF |PP_y H!30` =VYF"URfQGa@B`M( D$(. M-[Mjĩx=&ql0b97VYY:N^^#!v.bM4DdjV8OpHlb1GdNË`&N{.^d dsfWU9)1j*d&82DPá %":*8N +rvpE14&}5;LλQKB {뤟aDs]eZ#>ܐ/#7 Pa!L;V&#()2]5νRhN5:%NJ5}$s&Nȷ4x23v 4EδiXgDdD`-bJ#2eb .Ȣ3`Q1.!+,TBPT} Tmffų(deYy51z(] 1BFضPeL'IXمTwopR2B.vkV%  #1=z&!ƒ[{QHL4iQYS+SP*EI\1ۂÒgFEB>@}^g$ȟH헜L; I*lұOt+ʀVV2K(=)QYgYDԤ36峥v_h_| b"`$Ig2&2eB9hHEXU L풛DH3٢fB=1FQ2XnѢKiD9ڴȈU`ޜwt[;(F*uv³=7cfUC盝+Zg 0@Qr9FXe`h" 23DaaMϣSXF,vrI YhY[,dJTŵtv٤dttT1Ζji4rX=&NQUǤG3b?EJ4llqwN3Jx (yP@LsKB<] Cc0>"[%!'Z 'd%L _9ΪNH&G̓NJ:)O&,VYUh%;hԲp{YQݚ[~K~>fFLAJP+DG"L20IMNK&98PЫ4asL!\/)$X* I6$NŰD1[NN79 x&'R^ ÉJ&yJ'RGm5[̭k[ڍCTف9m&OYTڔMȬ$R<TŐ5 &Ml A҉#Bkᜊ PAI @{ ƌײK>0Toen4"Fn.B%ud ёl]*vʽw0co;V@Ç(({ ޘa>,f9A $@&yx54nN(U[6Pbta"fW ,}H-c,LQf49 "vj2E s1WocA0B =&>g.9=fhemnW0ON%&fU='KW$dd10*"DN %<_DyD 2129ˠ[t~&+f_$\@$Xgy:ڔ5eNy1?E+=K_'0nt[oGRFmF9ܯٛsCFѵCXpUDyK]xo-ф0 $LHLnբ0 4,}ǛRrwEYMMp-%[.(5-C$wD2BIO!f')ɮ?cW]«{е|sĚV]EJ@ %vk,v г/3H0FM;>)U^H#64Jy*AFL{U x H[,+"f3Mi̙$yS ktdZ$ e'8-ys`RuG%ɓ_ׇ$LiXG1bE0Y#̂$Q_Ab<Þ)ϊ4_g[%ڻu_M7H!!6,f|pױgD'[~Sf@BI,H4n`tKӇf0кP1GadLMgE(%%?ɦ(b.@ AAe~ 4gad1 sHŴnD~fyU υ$I(ges FtG=hUXuI6XB"nqȪ Ytx*tdMi0i ¦JG={CƏ-YčT(DA(p2n4gtGajDl9,pTQX>T.G>.B:Ky^Ryq)gCS !/'6[VȢg_A0sRz?O%vޫ34YVTCU0a,$D0 F'ΟUbz$#6IمXFos Z}2"$K U[ &6?9%%Q-DEeCD.):.^|_W)2 x=&~SuP,T}ؓmmg77v㷙q/r^PP v#6oceBm=}i6RaF$4ՌYTXTY}`D҄-P@NRe9C3Aa衊Q#THY5׳<[14a)$vEn>5yi);>fm6d]wX[\k { 4ɱiR~9j@ ;WM]lfܠu[; dڮnT@`X,&y!*ڢChJDì9"LDtR'XZhNmR ʨ?iEvu:1q$L)"Y͐b"l)V,A9&BYeĩ4r=&Rͱ$)ഘ)dYF_%k>Cئ|p.£6J-Dt'NGVUaGH y7YܪvbBQBƚYǓ{3b3w%TcS$8|tFr'JkSXL'lfȥXeAHܨ:JKƮ- uË%EG}"RFs B8Ab)FQc"D40mS:(FD[ĕ#E fQ$QN -d!w̘劌C6_UU}7}ZW3w &ǵ,NܘŏP1h[h6LM,Ɖm|4v'2VAr8e1YVU8sSG5Zi43s8w|5BzUvd0 S,>D!B7lYnKm'aU~hn{Z>QJ/flgw"oTQN/4ԯ ##\A" %uG `pӪ2e.2ʮC4#*DTFgl(#Zq,Dn/VЯDf5Jp6*Z M4 v(lA1 iZE)Db=&T!ʉyWYTͶO|v:r] z.PJ̬8JsvJ8>TP078C(r:!%+B@DK!r'Drmk'r,i%2E+ 'O.D[n{աI׌03i~ -?A1ZtC9eE 뱮oi|x)kCķ}usFt gS% DHN&,RPѦmia)N\iliN )ZMVX 2[gaӓY#?k)I;Qɽ%~biȲ#']*el3Pvf<\SŷR D`3a$xc2lax)4FZ09 !d cx6|}uˆR#ZS;WEU22fN{z$4Ѱ9mA2M$OXGItM5<4L_rmU]e}:htN֙ٙ2"]"$4ч2F]Ha;MidT#i" 5,YBgF5%2(Gщ!S\mQFCQ[Q[tUEH<ɎR k4bs}gy@Z*:Rމn-|BBC2f)L 5)2'9b  IzM@ aWԙ^(ѤL (Ș(|fAftX/LچQ注*P|=lᯱt3WvjU Wx,KX]*d$-4$hB%5>} Є<5dA5f ,C 79J8J0IaQEu9p0D~e @sLű"msMNn9lsܧj>*2'L" ̓@*ո!AqaҒ+eU`1*HLf%vX#H-xULH,k >]:6yhhR(Wp NK B<3w. O5,O$PJĜę%S۾>kQ8]"ܞ,v&Yp3u-<ŚEosfG{mI -9<㔽Is޺f)$Wa3MJM:qtR8$\k4Q! 7yj\MdcD2r+ؘ=&~RUu>@cJZBNb~ùoeЪe;"6]fv"V^Mk6 ͮ@a;5OsO1y,)b>kHF2mL0=9/h|'EқYٮlM=WJogw9+kl>mrV=sKs"ncZ/鉝 ]M8."MF##qǓ: 6ptɲF <,Gn,Q:itK@)lw];8JּL_JRc[=X#dyv4HjT( @ms7 "n* 5+lR %'3HMnmyK& æMJqSI#R^|=Oh\Jkv݇A1vzsM2P7@@{Sd'm s``x͂PQס@10V&60c 9rd&2oOu=XBM &*SVʼKzr زnoRqU"Tߛ#smfZþq R fi٥`IFvFȼXzD "US*Pnvx;H6i7I&F2Hɒ]",.%̅®0BfRz* EAJkXʝ4#w76s7 4ƄHd&fiSt^GFlN C+h/c'$TEAH"8=&lSNT2?&%(P@V3UTPl=ڛ:C 閃RfbDӆ&At?:VG4\4!hfYPX9STŧ gښT5MjLK҄Gk`ȳ,k1Vpeomv=evqL:e@}iMyb meQ4h"Swoq!DaL @7,Y 8Ө^20D7a3Ǥ5}77C!JB,[dOq7ζm?T7亴!W5sT&l8bE #%B>Q ٗ3 z:"$HB y2l>6* ł r:JYc ~3& e7\ d5)c*ig>,)+JyB>I;iy_o_xbV06flf ɶPQ&HJekKDY2!3liI|5zz1$Ʋtaɩ–Ři FX NQ·*넢b>7HMVB41``iH'&\ev4HpQJtysy\&u:}wKo2]~7es.=_@M M{ۗ/UV٭ԙA:gs:m BR1Q  kDMӉ Lej3/-VH &=k3dGqB2c?d;UbOzM,@eJ bs^) R9pRrE=r~3wn m-3?) >RKDtСI aHU5 'KBr4VHaBC*ZFBͫKY6-섨'%!<ա*&d(ԓpKӝW#LeGE$jLIuFB^S)2,v-?lj}1 bieM2UQFSJ /۞1ҎNC/EImh m8CQW ,O$\IH̸=)PW-/v)JJ.JM4b? "&qOE4bɑ\:tPZObdIcpư/*,CuFpjCD ݰZF=dǼ9=+ySǢHsmlInw'k4kYD4r=&R="Z~Sͧ dR!l; ]TY fnDl*-a*Ie9z4\@K`$2MNqF%$կ*4d1䢓)S\^*YEql;ߪ~G"Y  Ψlprl7mv_,bLP"RU 3&ID\(d5<ӲQLrpD"eՄ9ܜ* PrN~)LX(\8h9)'K(瀟v\3ZGoHkKXm9ᰌ1a[V54ȕx3#hH(AHJ )M9F B<[cday,rymj QPk&çٻ9K6sJ_ҎL"`#Y٢] -cl$!$ FtPr\jL`"""n|  H48.طB- I^SD;}3q4 r%F@Zf,+ݾ(҂$ZADr=&^Qk>-)>v޹9a?]LNu:`bH!%˕3RD?(@dL42n-3]I@&'Ifx@vJ1%REJ%/\4KTR=PZ5xK0Ons}+-BO$襝+>^cxʤ(ڲԻ" s4Q}OR,<2֮[oi -&Ŵ6eQcVe:fuBF_T vb$ J!PLr2#NE12_[eP0Y1Wz`IX Af0KS%MIvkI%͓jQvY;ѻu$?ayW/wwZ U$!#@ 9hFa rfŖ6i {$Sg-E.7FJGsumEmHF,ߕ3E>bp]xc`pe%Bu3U֝)Eh 3c!m: \a!Es"GV44u1BHR+lbWI0#Y28D$fE"{AZUT#L̫@h$Q0"I|&|k ˊ!FuhЛ)ogȴFz. ў>1Ftrbyܗw޿oaEȗI#Fow;zYcăB$Q(pJFd0PiRm"B' _[UD Mq) -6[,Abchd9{F{t6}8S8aeSuOke[b߼ yi +y0&L G TD\.qF1㓩07P0mɱTX"P@1=ɦrtbȮژ.ccW,9Lcj.r<}0 N ^PVnP2Q+K9 R|=%__ "R2GYbeȬ}'2,20/4kˑ2a֛k*rVx%o0ppW+)Y ldɪBN , ?MAI4(HHŽ\h^&W;x$XD4r =#R=k&zr:\*k?c:!?P) ^$!F)+,|$'Vj(:0)1~E>DC쓎BN1%ҖVIHG4 !P8)`m@Ҽ+AapYх6B 6LH⫵.:)r*K=޿41xoIލwfYzK y v @CJ+axHYZab(5Abc !3Ӱ0"*p$Dt,ŠYk@ltY ÓsffLf9n=Xb \>;m'MӜjADr x=&^Qc9tZU8mt$#Ei,i?]V\(3"\v &!$PA7F76"6Ry I%K+֖!RV^'U) \{T˗x àsbA##j "Y,sSxno'T@Bɵy6!Ѵ$S-Hpz"u!h eC~$0MGbNPum)!$ p:8C$E9Ìby} ɒEE`m!wST|J T ugZaڊYn1urPzͥr'}5Dׄƙ;L\|Y/5SmjR1tgPa8XE33e- 6]4Ҭ" N Y&C!>+P#dđqRG@o /Mu$.V;d JnʿW+4ɒOo %aUBLaC{5J(⃽\ƛhU'8s׵e^9=u} VIIX "܅ >dMπ$[D3r-͕D#nEiv26X^OKHITQ=.x"eZg^mǕGݣ ۴х\D3 =&~S=h2T¢&7Y73r!>7{$("C4LH"JN&XR'L"K)i̹Y3RtaVjE]%Fް׌~Q4p1_/Zyk_"25 Y QCVgaI(l ȣ ZRn14ka'Bȉyn7R%Z|e CY!qHȩ,HtKA<^ڿCfr5ZӮw|{~MU@ʀob( ,VPX2! llM.@Hx;JK8ת&HI̕idi3Bri'/N_oV^nGɴTUa$Eʀ_D4r=&nPC% b9`4Jl  p-[~{|zs!2] VUsj^GZMy0u=4g7 3%Kr&^'&H`b۱"g$N(LG29,ӽMyo/}O})'e_l:<=R^l#2:40f"ΫUeXСL 0fX^b(A$yt"dQ`> )!8bP\Eby:mѷ4=nsq c2ѲeboOcdM0~,zg.-Ղ$d)4p,=&Q[N9Hd rNlr)wUDx\@DZܻZd= sm:"f&"J} Ydl=ZQ]ԢU?o5`KhrJ^9Gbr^LLSP¯/!9\olWLDJc{zaEaR zd˛&%*Y+H!46F b$ȴnS^qV.''Ңpd2f`c0B8őb%j6-u|o7|þgQ"ޯbYp1tK2۷j+Ye74uJ2ۂ5i@`dق:DH/:&ֲ"Q-Q3aLr8icF(ដu'ځRPMSGVwh(׻me2ϟ-3)mNE։2'4Y!.࠘BS6X{dDa4LÇp]Q8M$u 3R"hd(Ɏ"oC>leY2fg|ԘRV\nQu'O4υԂdaD4rh=&NRG3b1&ysb~̓ q9M[W @3Pzvԧ8U؅c1_V-=$Z,~OFO/ɭ͒IN2m0=8 vB"ԝwhYje ^( ILdBSgms'dP%HLjY3 D=ī@LdUɮ^♂B!2HU!H t b"QwRkOP0Lk>[aU,JvmU ~94J۩/'0%;nǘ? A$0YgF49-r"ǤM )'$R4dǗ xm&.:πcEIH =&QY%Bd ly꓄>XbքSY,C˽>,dE zmHZa@Pɖa(GI&+&3]0Ff"YqlDОy ]iŹ hXIVGQJXKʴYa8{ '$eYo&%綶>=ccߨ< ˀE>m5h"◝fXno!!E "MDI @ d98(($YjB'Fg"&flTs}M- w24Bg&N6;7Hi {0@ |uwpbRC 8xH!:Dtq B2 R 6hMw;s$t, Y9'R{I*AzJܹFNR =FlV;NL%[wȯvtCV><ޣ#[0i!zFPDR`P>H첳:TH%PjDZ FNu-MnZkV.yaaV7| 4I(uoQW'g5n|z"[1RdvfISÝ׌S٭c{0WaALع=&%M3xw]ۓf1$B۩cB3x#FmXRNM7Jm0=C"D+2j5'T .F!SO#'>:(fD,d}.1EޟK-϶FNmU(e:M{ G\9OhITR^L{HCnhq$ k Ga59R2>-yy*% /GoRaȍMM'Sob\)-JYe dJtZcT/l^*-W6G%4pGF#Ӄ&,V!e%-٣k!>zObv7)Vӆ[ervTM;` ء:@m̮eFRh^4D810Il(#K"dJ4욽K0Q 8u4g0ZA)Nۚy&"$ 02P퍴&1DG[x)SC-Φ,__6,nDʖRPD-͓l=↜rDCy Fcޝm,j((jQGt"r95.*bl2ˊHĒg'PgR-7ֳ㪷׮q0  viY2И1_KGwu(LHbH:),`y .iӓYC$avBx"񘆺'\NhR*u81 |Sk^ZKM$-Ҁ$bA4r=&.b"`@SI%;m'kd? ڏQԓHLDiW`=\ޱE[ڨ[M^Q2E%al%^gE$JNU)m-V0+ӹZY+I2SS\z1W:]/ibsxJAN,Ű(v2BǴiBR2,=P'dϪhh)Z|!(2аɂH).Lx"Hfv,Q@2')FtjmH3L$#D^e=S.G9Titېk (U+c'y1)T+Oc/}J3*b\&rƀTg0h-&S984b!׋|*44Ֆl33 PEJ*G(`bEn ^6IJ\z"\I@1$˞@!w`pp-aEUٮfXՌ{3}\ޯ#\Dp9veU1ϩ!H =a 8,'(2Bԭ"-ڦUpfjVιrEá{6e#I'=jMi'\N/KL{IDcWnS秵h$h U@hDa&E$?*lCSny@ <_yr6=d/6}hzh843~2fA"Z~Q9<je#@c|WksڛÄ[3,,nU4) kHp[.kаhIMlȼ=&i&qO7bH8P$pX;X;RRB&/Ӭ1`2V46 q[H唲hA#0HH-ppAk) D Pb =D$H:! S>?h@\aH8&f|i];^٫) pcZxϦ˫'e'F2!\|@BFhiDW䩣c1j8@=Phg @։ '|Ҙ1+r0"p9bqBHL$94I >+,Y򧥙YYƕi0:&oMBz+fdy ӐDxĴ deTU ہ+TNm4)EM]?-e_%_A6&($GI'5) \9H$E'e nZȅΙkr$dD2rȅ=#&$2$֘*zѼȶ'|[.ׅ# 2p,KC*b%X9$$7H2VH[0mDJ6$,:o8ݴ7U*''uI'J+*d̗[*CQ#ՖC/YZ>LW,(OP]odǒlt*!$ؾ㘰 WdeR$M41&Ѧ{58.N ]lFW63h DDT 緒6i w(F9ʰDR20kOok}ntYCȫ뇜ͪ GAز&`c:o ؔ":,zٔUP Rb ( TR;/dԒ5k2GcYI5ܞmv=Qsf dVZERg"HffI6Ct3Nszh^[VA슷K` @3:0M+<(r( QMp8҂ZF(AbO>HfAf%) *[ Y\Hm8zkz2b)mvvI? ̀fA34Jw"$(hmhs,IŞxqBhd =`&S3 XɋW=A]82{xA Yg0Sr$SI$SYM(+uZ$ݮ-+9J2eW} 8$dz= &/@HPyb4Ra#MrcQHTrBd!3 ܹg}N N"q9,3I6sZ>=d~r#&aɹX#/۲bRJBO>P$DfD!S~ w%lZd@n-,w$33*"{%bNrAFL +wIZfEuTm3 }=>KUc߼Cego4ܽT"b%+e^9 E2S)4^uÞOoUb1n@[雸(g9%_ZI Og|9e[KHB5Qv̲b6dfEY0` (=&~R'M1cjlr휉yj-'"t]}p\ڡ8ayPGWPP*dQŤ{X~q^.azHGNtZ%*Xr2LYK&ר)E*8E~Ftf6vt Mti-7å2.NS"DIP g0%p _HZDtK<K[NK&H:J*1S ljajF:(C[-PRcKQ`:_1 L8UTyki^%E$9uFGxz~K S6>: qࠅ73J(9\+PU*rVO&iE0]:;tU6cH;8*h%e"4J)r0^7x $Jc@MwҚٲFV^Jz}['R5ɥdDYUa6L"WࡔQ #`*N *l6U[e&VfQWk"TD`}WZ>tE (gB*+9$hE1-=& ='!N3b8¾܎|G}eǤ%&4)TR!)mjSCe 07E D;]XvXm'-zNiQЋ )B+X}9:g@ף*j@L)„&o9Zl3HID+n5tgBvʌOx~5SCz2$0hs@dZ91.cd%4 ǪUjTTIiAIAj@hgSVsQrHӛFo'N @(9A\-hoI{5>$X]%Bʭ_eeb5_NQ">*`[p$A)Y"6VL&yeq(A%3[+k'3n r#S]NiGԊF-i esI0(IuP{^9%9=7ɂJiYԕ5~i.9 y?m8򛱒l>38fFI0,'^4;H&(IOBР`r.JdB pYh DmR0ƉH*I I= fjIG2<-h<=1,q܆Ye% 榒A˫rE^_,3 ΐqۦ׭ "!-v҆El`t% I}8rmȝC 8H b sagtifF# Y&fcPak%9IoZγھGpx~(EesW}~S_w?a$p@UYqH>܍Mg33gX%P-hwF^H>Ojm bjAd1'J5"L)3CU%%I嚘03A "'H&9 zNIpJǭ@+ #&'|L*eVMӻL!7* DLsSlcčv 8FV("-qѠNHCIɀ};,+[PGbT={u3;;Ӄ$iD2E/1v{v3u3w4BxVDг&@3`eԔsOʓŭkҡY{KiL-5Bfi+ Aw8֥OδpK(󨡞ϫyqf@b%38)(L!YejZCͪeBf >U i%Us] II}ԔרjnLg2e^+LLvQ")}'ytETz^7sbGQ˃ Qhs̍:ju),}M$ljf׬8ࣆ t VFEFl"8cPVTI8< t Zb)6"P Md E^Vv9rUZ\N 70o&U$B%$0{ aGhHLx ;,9E59+6(wua2-͙4Zjd0g:R,BFӧgDD$ԹoXܹv)v˾Y:^:`U.QenXPg8F7P]L{;qD!Dh*zɒÊ\d"*´Ene#q#<"1ch[299PR8Zd"P%K! M"ɔQ_=ք5iU"fJZQfДo|ϮA5U@V"5pU"8WfVQddƦj&7 hM˚tMb[KANa v!}$+Ïx۩xN |}<;4մ\$b0M=&nP'*0aqqU2Tt^cOjD~As)&\[Y2f'-Lĥc)2~(j.Y/Xnً$uOA +ͺğ>EWg)cv>ܼ./b/hɤX$ nqKM*@FAR (PCT$x0T;D,D?QA"T9),SZ$ڳDFNJ)RÞa_,޷}:RۥTKPvHϋݱWw>f ]I gSA?0:0p0,oBn~ do,tQƩ~l14UAY/>➣CRKS\]P[ MC4SbBȡ^&P΄H`u8r&|@笚)blg֤1ǎKhrm<ͯKt]ߣҀbI0=&nQ%040m!Yq]Nu(~@q}HJ# DbJ%Zo5ࠥ]n>- ޘՔ &5ӃJ;Z݆Ҿ+IfEkUq;P%sgj' Ys75s9B؈\06G={{ҏb!ainh 2Q߿(5{<|R)Jț.p1>98@ =\,. ۟M{R靽{ҌIJ{@@ ^4ņfkiIRXO3?,X8p|s p8n7X=x$!AP >HrKQ~3w.$*6z$EŃB[d6k:h1Y* (M?ӆ7!T,diU~웗;5nON7nP8P nx8TJ͒Ϛeqܻ{BLUm}l I*Hm@S(b€mtHmT .ov\g;KLrnso ݝ 5fT!kF; {I[)U5Vjr~ir* V[*S(5ֻx+ݔw84H_a)Z3hNlw4f |ފ.#K$Ig1A$1.w8OJ&δQQ24G$dbtZA `Xqd[ES/.$rPqƨXI'H@/ "VօFε&}n-Uk<pigŀNp{L :a<L2?h`4SVa6;dR:rz]u/58ZSNI͂ݰe#*јfJےG4jYiq96ǮA@@8;&;Fz XT$5sgoYr $8ֹ.yՆUP {ba†A} ٭ӱCG J<*5H$R9oz\u#\ 4L.X_5@:^C ND *K|_loȦ{#JICr)&o@T Oaw%쥸eG 6본7Gf;f*5N(gw Oo,#II6 9R1hn2섧ˏ M29C (4s9 b0!d-~҉S~o K[SզƷn-3_p׈."ۘ:3!U0]A˙OȲ72, yuk׊Fd׬+9C-R2Er}WȽ  $@A` z -@n#@X'm <-Z+TԶ^+R0) @]bo߿pxC`lӲTmtT3[PZY;\e,ۦo6`}ںmCx0u`ӿ{HHVMwCɃUᅹ2ƘYh*ɀXSFhZEdIVU9SkU/Qv㏜IJSlz^pL=TV]vr.I#j(R:ʺe R3߫QfJU^ ;E([_n J,J>`LF `>i.D@`a.œJGJmz 'kU d-j1@Z$P jb}i[h+vIn B;073YO E(ba@(`Hɀ"xd>ɁŠEQ]+L4w,GfvZas$[(o ˴˽x?.svX;voy{ß?Nv L4B`<}}? @`,rČ5blyV&µ)@E֦d9#FIѤ79aHƐqKT(fKQXc#<,E1b~5 OɉgHۘ5뚬eO"S@!Pw.,yYL0Id@AHd/I$فeZ 'y 桄\%#8.diPZpq( eElJ Q"b8jAYB 1n5뚬eO"S@vw嬐KxT̄L~ik-YPn`*7Y5N< fʦl`]+DȺ^621Df%哦'8#H%"AЃ`(!ʈ"NbRH:~/$IRZU:(y,4hmiudU$r3Ӟ𨱌}yY ˧'58u$M/:4P5I6&IyhRARTDUY:xtVVE'STuoEL$-,%%QsSSUQQJKM@R>,Љ&"z @#LID`= gq+4=l_>{έ.5;M2݀ 3ͼxS &dS\O8)G^Ģf"􏢼z(&&N-Cijw;{|s\hsN0w- RMl7NegߥKM̪MMVijΥYy}S@`b[L 3w&[7\z톿HW+BM4J'$twtj`D& 0tS0$1s @ eNU$K} FHdp)2wIn&Oa 4")ѿʚ.,?wJ~9gF=L⅋m)-KfU5w|x𣭍n4_7 &˴YjVr*2ɣFO07ǝ(ȽY^@N ?0`X`6` a!>s!}s$vNgt^˸07,( QbI}F <0=z߿;+ͼ=x͍yZ׀2n|r3f hT nFqU.޹Qϋޱ޻eaUhսZ 'R"6W)\XTGk]` R+r̦Glt9df.@@&0& %܆ݕRVys䩑K县.k)_wݤ 0ucSɢUbfB(L`.Wo٘닛iEQd*6o`1PFJ2@phL>+%LN.=%RP9nxLMCm~O}5LuiI&ǡ j*3XE,|fss!Xvćjq\Hkzp(Ԥ~M%9 K2#8  1FP'|=f]~ja4-cuzѼMe>Qjv[uSGaQB6=ֈCaSؔ,o"i(l ym:ⅎ 1L6DN@bx8Oglٝ%N%i)W*3%UQRslmj9mVr}{)=[ZIeDt9jV1): P栠G<ƙ>d X *VbWw|_­PmڟR-['9{}ٍD}bRZ,ZzbyƄ7?ŇyXk}CjAOhVb9oj@s`V{,*iaJ@3@3s2uM5{ fbf#U+ r |ETvy*bj?X|PUMȐ!>PARMkC{HYmNy 7%ǡݠ j 4% &׫9y]Mņjٴ?,t~nfg}3H[!Ϩ7q7Xx0x5 zcID%l~6PnT@ewTF H"34fl2/wrW^QaqIp읎‡qe}.MpG@,ڛ|'凝bqyԘ~/<{'yPtpMؽ &n4r GJC~Ymm~O/jI#!ї'CFS6Mu4mYyZ!-s&l)yӅ #.C@ܨO&`L=J Cߎ؍,MY훓@NV.&9c8ǘ)2cM1|U/vXtK`ؾZ__L}>&bCܷ5w` 80mMƷ10 H"֍(aޢ"3Z<6WN C!cDP%s?4G4 %?愺DrCo&9scNE@62J6,5i[:;@``qi ף`ezXkB9'yWucIbv%YfS$l6s5a@~XD(xLha/K86?Z[m/lp40n%q .` pv>(%B&oz!ϛdPs0OEzh3{4ma)1źQRLHabSU!!@c[~vaDf$g&4Zmʕ/\o[<`%oM`YxVYsBf.AKCmym(z!+N5!73-ϫw#>Pkf xyO8$5HEA*X1` PH1'_kx3]eƣ= eɨhӱ*\pۛ;um]?O ,zf]y/sNr c瓤GYqmC67Nš*V3}  "VC1,m%8DJ 5iwH4H60&<R}m(wnnh`¸9mɦiQ}CM8/@R:\f"iErzRq-  j $ =M kb^p[ i@XW[lM]hCp",h1vW;Mlis&\Cg1 Ʉk<;Z3]6vC7m˜)DQJkñHFB6k@r*[p$"@HjAƫzu~P#%1#c巙@Sqț~R^vs̑g>?:4)0u /D\暾%qYN"TreB'(na֖pr9A*=Y/UjZ-6 ARFVPRy/ д UUUUUU 5KM/≧CJVC|ɏˑ #4]ؚigSAܕw ZZwx5/iHVxXFYq7D`q7Yq̆gEI(,yɈbs۹GshEð-c_b/ cp/57rhT! %T5 LD!z>#t 6hТA+/%d_J4!D<3< ?i{a{5%s۔ TQcIgnp-iIVJzDZOJPյaʨ2vF%&3h29ίblOGne(EL$YĽ/hthBk-P|'2#@Y)Nn;_nP*s84.Q`?*`r$FI%:[֚6 HF3u~%0I$#yӘ r 6FF$~"6x+VlhEiFٶo!8ASYX 6%)m8ds-+)*܁!*8€qn3Wux7uDGXdt!&U'bY[;]J RyKs*^-:&Kf G Y}ZUHSF: ś8__ɴ k"%RZ BIa>FJ#:y(-4#yܻp,{:q&&3Nƹ#ܣ@Y]\$::-.$t=WrcoٯY"?)>2Bځq@H3(yncT9 B+3ظ #2e8UXhp+@Lw}ӝ:rnXT(*ip ΥajH(K,Z_p⁵QX AH #$f0Y9%3DHpUB\-mJOFs%%ޚ߰XCɏUϋL`72;9!5L(Du"o} _'QD2raxElrvbΣZ4P(H&wh#NS[6nXӥNvI<ǬeOld;_6{FHū3ci 10n,yHӘ^L2@@/56!u3H/aObdUoꃃ6SY]`h1Q[YHο=}VK+eԨHKrL A QAFE#3]?okcj8zTJX1(V0<7POSm^xzro6퐚F[W MVU &nIZ"&G# dlN8_ET5QD  Ip>0rj I5zE^e"U繪\" 4EC8`Ӓ8-FdO5ql~6i7k$#5$ɻYI)h\!ۢSIy-7ܹ~|0|..0@gM/U/֤.XҧYJTPJi0yCm88]gh?ߢL:z t 3(^21F13QL}f6KB*T OIZMN>LjXB]$܌HF 3~inRI.0x+Ǚ0ޥ"4 j1ƁFvަ3ŕ&ʫfG5o, 󶭽#X6_i 9i4źZҪL15WJNԤ4xv_m/ٸvr/NRg8ЩE"`ɖU+Dw>V#:ޗ6T8PD)Ap$m ,Ө/I5-K4,&,H$apsy3 9{%*vrMj5VEHTuN <j;2aW$ml)ja@qVn^[gI^Gg,Bۢm;{X~(` ŹLlrs(\qj>|3bwnA-Q֟GLjɩh:űQG r0`BF  `MsQ-+/ jZÜhm0v#l쭥mcE7^V؅G[G]~*? _D[d(iݯNY!&q!-=0e>P^ 3Hm&~R970D4"@%#jr2IXСP (P;{T9Q[vS[jzV|nt2 (ks(PJSD;<s}0n?{UE' 6;eO'Ԅ`$ HUMbPDPV|U%=Ri4Eg]ae$/4gJ./:Ѧ|9N;r 2L#xFQ覬_ :JpȐQIețnY|U.tVضgʼP|̤"s-=@Y +ԉ PmBYh4n4E U|%hmXdȽ.lkbPMR2JTB+W`ȿSG&e2ϗ ޷Z#z錦!$|ֹG,4c'7Z5[\u5nj ~ٿzO8p0-NTٓ$hpDPd6-KE45J swReR:Ѕɞe`(yԔ- B+rln(6(r%q ;Yo3hq&~R)-m(l(崎y7 &kWiu xx"aajbwBJA~ũ=9\x #1rb0?tQ*jZzU5vT) bSqQO$maُs%EBC6)= ]hcw έ8\wR@T ^L'bYl 9AdiH<+d .dСXKz+ -dhmIXq%׭'ԧ[كHmɤ$,JMSʌW"6IIMZ|ۖ,%5}F{⼟ Mg|Wid!/5';ךѬx88pMå.,HOMiy'-<ēH3p;-)vKFfInnvf19Sj+4e'I-ƚR8UE, Ye  RE^]Ug>>m2rNUBpn|]}4 @*G7a17 tueBLPB26Uq#$L͢I9õ9ReK"LMe "27vn;7/UoJSœq/ T-^zc8?1PtG<&R)Κ']lH&Z;Hyɐ^ :*X*ݠHDJrB#{1b9 2mc&6L]lzzMOiWQ5)$vCPu!/8 lbb]B񁆄Ǥ!>}S6PD`˷Ph8h&'_YSdh9-n܁(&ճ4Sή¥ʌmϴz>,Ž_̴{;Aپaǡgڔ=6 = =֥f6SM8fVq"jZHd\YxB H+xZ:Rj@HH  Î&U+'eV/K8IJGe茣4޷ɈuXEi3b,m&~OBLb$(g ␤LZ.aeEUvY:V =`\V%$*3H`DVC^ъРaj-QE$NnBuVZF%=Z#$!Haa:UnkdI6r]Z26͡FѵYpU,XqFD Gr#++ܨqj i鹨m]C8,2@v~{0m @1#*1hۚSK-\Ma|e9$rD% )LE4Oe : RF&o=[EYԕZY&hj}b4W{MR3cCq&e,fNh!{K6@%I[,$8h՛ JL!` suw PS@Ȭo_PkzţI;Qڴh' /wWzSWq)7}fJI14QK 'CJJO6%xux,ҷRdƋԳf%9a3鴑TQdۘȹBgA;p;=94q Db@ЈVFKi4 (m&~O+*hMٽ$(i"E PY SU)j-4PI m #u4BV(旚=L֖̀Zٶ{,[4ͤbQ0|.ͩYG0q*e vFY*RE*C*iU7~=Y.p0,JP,ʪ9jPKL"-'ʷuv-[-Ok^ -ԛ7uK@yƤ/l0e4hVL!Isr1KQ)%aGlC QJ(VQL–Efq#d+iJWYhzS1>V%糠y0Ť4䄌4 ʞiU X>̏.v u_&wQ ,("-!kdPeFmFpGFPʑ#ꏵ+r%z^+=9盳cڭsGn3r/;Xe/s$!,SMy03l:g bF-TDkt :HlVhqPUOM :${Qc6.`+%o)^}S8\A͓7nPȳ}6 gNF:omU' g4bw@RB3 KسC-* ::Y$g^X =ZJdTTN'*LU Xm"&'@B2$$ګ7r\81h$3p ~BXD3Vt5"gjefJYRϟ\7iAA}prj{oz5Jfئ@KRSRt4ǥ&$B#zGZMmp"-GL0r8A'=StjLUÇMdH{-:*Km1Uūi3С+؀m&O]G'`%bq JkRv)P` JO>i$0>ɤ8ȦTYK8ҝĘZogYN?DvFT4)x(HV eeXDQh4 ԟ BD:`c5Ȏ&dNhvF3sWvk.ypXSu]$ʞ~P9L g"v5Q(\ YL ckq*S azCв%&7 I44%̶bfH̼b(iA):&)"89e譁 B,M0I&tMEZIT$n$Ū*Љoߚ6ٴ%%CH0b5uJPR#S{nf\q`T4o%QvSk`J$g1&eeF`"J=Wb)&S߯,$m.ISR[5P4djre8TEHG0 ,Yd.]UA%);ϒ#QTNt1e (CݴKi])DhXfQ)ZX*6}%H, XAK-xdJEBS&nT^v`R= twh$^NkS6O_zڅI:O_V ,oŅF"abWh04Һvi9$0:yAqҳVY;=)43-?f ĨX&/(@+9妐I'eI%j5<{I4UExHPkxm&Ro-*e ± Q@.n $ɃzF #IqʮXI81D]e UB)()Fhԋrl~Ҡo5fS)/~YEf:41,DoO'5X,űIqSfm=wѱvWk>@Y8gS籮Z@/ eEC%ЪL} F2E#K왭\ػލk!#Cj r=ڤY2ŋ#Z%~ZL.%6Z.=rQRkIuȈAx2 UEm=܌h̪(mBS<( *iqh*ob@# ݑԇ%1XY jD7Ȍ($"b kP y(EphQW2\mAxQt^+i+)D#]["I7%qah"ћ|RniJ2ҭIQsecZBLlvBسKQ}ojvZi yĂ2,a+;^ iP+ԍSPU2R Q%'xe,v:P̍}LH2N+u=eifgRhŎ3ՀYKXHPlo) Rk $͉謹#QK54)l-{CMo/u9XM&T4,F&hysє`Z.X!$m>}#=4B FR0lS@&Tx *\VkLfng={ٌ8ߣsS۩Ikeq#Ǣqo:gS) a+f)ú) UuPePѤ(P⡱+0D8%KHe&dO)-dF\(RdQ%}fp\fa楘k&Sz.qOVʲEEzh a"<;$%'2^G+!ley9$ NI.P`$I1C@A?y1N /uozVyZmV?6&Sh3j` o2V0w$0%qnx͂ *":9fV+Dux(RnAJ(qⷺ&$Q3Q5#$S称q'1.oRe׏SfM>Zeي?cÃI4ꈹaVQuY ͂J+"8ݝ>؛,may%+Pm1hMimVN2D9r1bBj.EC3j*Kܵ8$ETmK!nT$I'C)"dG F(j5|QN-0XJNb`w!<=-(Ȱk<6蔈Ei'r-vGR@X7OOE544:l6Js*@!QёVqj ,Zr(e Y*]HdOtbIq rjY*L݀kIKf[(:+b='̯jimSs0l{6`jC<`i{a8i#M#!YdUF "lQlr>nM@!0%p,NFй"hd04`cMv5a =ܲ>lHrS=ԓv[G9J +8!'T1I zHGqv 3Wn͡.:E$K>H)D_TM"h 6356Jc$Rh݆ \P %?o2!<4&_>(ԙ@[^D 3b? 4RiY $2"ii?ce acDˆX`4z@I;= 0 b>ðeq ^EQ4(F&vȅ #Msx @*#,zyf tI4HN1Noh. I,,nV6Lѿ@20.E o:Kv{ w0$H~  ʃɦXsH ԁ,QDD %L0~+[cSH@&&6EM4wO$qm%FٓȸN--a}Xi*0qYFj4vpwD8dExk< GE3=]=yM\sZ’uTbP@$D@܋[$Ь+NHG; a "Al5d.{PN$$kZo^e4.]v!lRHQ%d?s@" Ҵ$^DHR a&~R{$l.U)t߹hm{Œ(Xl[x[+`WB!b4]JA5FڨͪF6"T´gh.4D"m B*>izP*q5TiXYcBM3vK∩#eO8KDX*s0SYd?ҤmɼNA@VkwMg1]ʉG3SFX.u1BHl 6iU'P.ooU',, ,CiHLؑqm"Q=I_:¦TՊdM.s~qݹ? \5Ј^˷ۊr&ս䯐N 2-"^p{ ~'!j%Xl nSP Y֊&-:8F՗bWxrQtUt%UCN ] /Z%i2 A* 4u(Gż)7Fu IS&Ѯӎa## ǮTT:$0 ovimDdִ'ɣ4$tXLDA'Tu0&F.ERG h@ɀUE3kH=&~Rk$0*طS o*S,41ʮ}1I3WD$ъ.iiڄT۫G0>ՇtDи,qޖ+gb`A:5 !%pMm{s'Mt,jh Tfm5iYe%`ThU@'fhW-~vtHVr0 Y 41zJn6&+ 92:8 DBF0xB$_3r,ӾJ^9jZzKBc*$s&{J_w %=LͦE@T\hxLG@g qR %VJ`mɹDLI̪GDzګI FdB} L,*ʼntZa0jBQz-ZeJRU&LrĒ yn.M'|AMu߼[x[+=#)pwd(H}Iw< Qbp" epa| R[Qm"r|0'aMHC6rk\&-11-rt(8&| V?4YD6B,1&Qow<+"TęiL.-*W۶No69Ft =,j$ Ól+gIX,C{٩>˓Q(YbRɴx F;\TL*r@"Эh6QKbK;ʕK]+_ƨەM!vwU&i9PBLfDU%l0#k̺% Tr&9 ˒D>osdirT FdSRtU4 U]r{Hɱ[ 'J7܉7q!0UϳV}4A5U׽?!lp95aizq [Џ-5M (lmbvL<.DJ(NH ($$'FFh(  Cp'ni4Ȓ\H3$)p~MdB Q>  kFEA#-C ۵4Kݿ\@$JN #@*Vr%:-Kd |HeD*|mX$W5qX2 +"kYcZ:Y)~M,pgMf]ٙnUxӀdD)x%#P %(cvfW(j NFWXK "DD`KM``$i$<$rBBa'pP >D8HW5[gI$HȀIU9g"F*w9ĶqIS#O\$[~XT(T4冔EYW^T5]-"4B"#a!,TxhdheyvaTif( JˁƔ[rRiEhӁ0 D QBD M4I,-$iG "'aI $PdBvwggofhҋ(THjU]"QUM; K@l$$DBl9(K @ۚBDThR7t1Fyj8H Yi" @" $Xh$Dy"'iGM8"a3#򥝝QeqRyNQETDS2㓂%UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU&/5"% 4UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.98.2UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUtdesktop-1.2.17/Telegram/Resources/sounds/call_end.mp3000066400000000000000000000621471326245125100226610ustar00rootroot00000000000000Info(dg  &&,,,33999@@FFFLLSSSYY```fflllssyyy9LAME3.98r4$>EdgydQ%r9TY+kw5@@9sъ 1Xl_n~|S(b J,Krhe.@B""'..{""'.((&h.xz@9sPHődwDJKwww{Dp<MP Q`[C/wwww*?Aqs)DDN\Pįҷw{As `0=*-|{?1d6]d϶sPw7,$x[Oʭ\~><ّ*kzݶZȋvD@ jmi]cQ5Ʒ"d YM5jo'{4Eq\e:o/ P8p8<TaE9d5$[Wt "}L?`<. NT< 02CIhDXG`zbR"0i,2B@zH'B3j@X Ռ5IqgI4TV訲dQ6R -fqcb; Dj^eTHSPmjP/`.#%!F|ZU"brƛ^:6u4\Z{  2{mk[5ZͪőHS[vwQ>o0b*;g8ʚkg@Т$DRt/LYA$Hu"&#")čsȨ@00s0' ؃MX(bj2'Z(pR_ڴV j#e$Z3#d偠jy~Z%FU (}wMբ1M;4k?r! F wU廉.rǿFMlucR?8&6@״N"DRDm9<߿-0PXc5ZJx `f 8c ` >H uV3b9K=rqrJ i}f"xd:'z}X^ZolB`_Fs"gGKTARA۝8 NՋyfic!`,aNp"r.C ^j 7bvKBz}7~!2DKӯD=uR/iq"cE=ldg<3a{,í~{QɍS G J蚛pnxJGV8xd0%b@ yk i10056vQ7H7 C@+Q۸K7vL݊mǟ;?~垳`2 Kw:9SV-_Za*ĘO#٦ *S,Tˍg%_,4Lݭ6 n~PĀZ(&df!#bj>(( >&N^]E -f@vÑz{}z!9D4bw<96M|jhDv>k2bgt"|s8t=m¶Ki(ӽIPْ߈Yaˀ 5z,{n$6g75ZbFe1a.7+.DWC&5E9ņͩCϦH~׎D (ٺPUXe `4~G ӘlDP r8t`L.BdҰB> 浓A9F AH-z F kU .1@NϘTRs.c,8]3YäBaz)d)IUUZVI-#c-Tu,{WzfD 4081p7";03( #~IPIN6lav;L\ʳQbثnX=i}ɋj<{4{MZЃȁ iH$/kη,j|J d1:& `8vR )1:ƩIќ#ƨ5 ")s#Jpq(Z() (/D&1b}mR4 nDŵMFt[hl1'MӤ0'ԓĈ%& KA 9XgEHl1 DtgRԒ2e^ ~׶zA ytd,lxYPn` *7N<.ܽF싢L񱑊'+Dtjl9$(D̓z`R) p&)ex$F:N'L@fPvE_FSI$ؼxo8 @uMj$33^[Fe%"^thtȾxH(Y'ɗKM :@BGj)P$1#Јz*Y^g:mJ o3ujSW [ޠPZEw]݃vSF({a& ŭ5r=+ pih6+*Ί{ƨ@AXw)HXgRԅ @x0'rM5LަM)lv jU?]WW^O@:WAkZOm[HҚmm뭝\U}MVP PVh"@ &5gRx)!n n,4pt̊@vc AH>){Ln"o,~B U!١bԪdO}~,:Pص u+F` HMȭ$R9;kjTVr:OwT=i?H@՛Q`&  BS`dfh1'cĔSШ ޫ)It.[9w-w/W#友ơhy.$\Abgwkvl]ckOfs:k z]A^yѐʤж?_0;7Z@Z!0?,0 <13c@Tuhוw9VٵS4*-Dj,l+Yw]~'H\JW[w Tkd7L{cBʷl8UWN?To07uA  GG0MEsXP0#B>0bޫ7ڿOzr pwcL %YaTF.mhIMHy¬;O+󧕄u=P)8'e~UU/157jݳm6h%_HR134ncQHW]>qnr`d z$6HW&:_)fb$t7$jaL:$Thh4WU,<.b8wY@ƵG JekgR@kZys^5N0Aܚ5=Zs\Ǜ3ugR64N@w!K{]*_ zNs}IA-a=4uBQEp4jSudz0D`vB&Cn~ %Cc uBIjA䏟h+  dO| IMvZ0ߥ% CzXcTD՚y@rK˿44KI|8@1fI@ #Pk=}߻XJ^3^|6~2%h3A۝eċ+ ŘW}n :*zVN0b#~N|AKC`}9qlO5+e $-`QӔtagq`AekD~_kcWurM *ye?A7be AkK-!|܋Д4vj 4uZH LHO/LW q 6Ėr>~?+R݀676ﻖ\ז?X" cunb^Ig_`• ;5%p[f%G@9! df|w®돚wG*Ig|yLxz4:,4ibGCp Mul + Ӣ%šS q2cB5zϔ[j/R֧ChLLEF4x0g H}Q0AE8`{c׀7" cXNzZI#UD8ѥ0T0aͱU5r\D@$[ 4xY<5&e0F0$|bmKu5,V }jZ93Q 5$g' ˲^IDa!(c[˖$Nz-dۙ}2N P݈`GG˯C`un.i"#졸D)5v+yk ȳ4B$ P @$8M{Rsgc3׳3M:K .-OkIʔT)"|mBR1iDs8Q5ܭ.S< tEǔk<`&PM WڈEFK`Hun#$9YץA@&ad`a0.`IG y#<<€&#<6TkGM_/ G6$FCj,\k^cL:SBP2bM$ CM ǀs`Bd_oؘf\rp\eUUUU``NO)+k  Â=FJwdnZՋ#rjܻnժvrb *N( 02CR5j1Y #QB7dBEJFhjALn5YRAK~CvZαӊu[½hZѩѹ n Gq$@/CS@XɆ YŎ%b#/iV4ҾLI,W<ѝзONڔhļi u1G:)b^LPRrH+KmӤ''t\*ڪ/7] `uP@m=~~F,%ɘaQqrÍ3?R[bSnUd_O{;|ܥµ=+2̣M֩zZhY**[t)7kIO+)AɧB dVRUR2ޥhC)Y\Jteƙ~Kc-ˠtА ]F`QF!']ΰN+V׻ssNebNQ4dbTX3p h(13j9^{hfG:ūs\өgy}敁`y1s \E3p*(q#).$ Obԛ@#x mqJ]K080t 1cry4w_Ug%wii7ܻ\jVKqbOm14GEΗ W;˻^4!*R-#uxz8k>L|ƮGeCe*nikM!@+m,C\0\ 0ZFdfEtklR+ I[BaL36P@8 ܳwzHiZ[g kB-%c+;D;f JEeQ;IHه-` >cbK(dJ9ڦlp<\7G6C}HQe} 0lta\ZOAIS ):9/븭GN+ L_u{]@d`jQkH k碐қsē9V-rcHc`@ EmW13gSݲV[L$U C%TN$'e@lAH=-pRQҁhN=DZX)a&H?hY-]Ϙ/؉ckֲd,!z.Ac⚬ѳvbMn|,AF,z'ɥ J fe|lGQmmVbJ{Z$Inxf0,5:,RLZSXbE1d53D$bYpsrl1z~jl:,/OUIaܤYkD90vf2Ø`PA@2bi8LCF8mL!f8IGMڳnud$S@/, QOY YvKJyo66o_2)Y[LB]ّL6A'[tm8@G.oEES?Mtӛ?G* m` W%nL\QkuwΘf\rq2KhŰ`f F~P Bo [eVe,zVϬ,P˄ l P&J.69ύ"ѵ2F%*m ͨBj I"$`0sLc6Jz! iK w9S{Mt}vªxt#B/'z$ y;YcAG|U.vhTXDTNo;\􈐖R2eX]s:tp: ʥu H'iCKpv&ew]%}3D"|C 2N%~|}*NWT^-7T`!}FoKi.!W2TP"Qf{&:,#֖"OPZ8n>y >T/rGh๊ǵ+)HS%iS;cS&Z&4҄N d^īoKbiؼmlRI$JYU^Q;奱؍bٗUG'} BZ,[8ҹ &`򅀁LZ[dVH 4akMl']5(葋ahO 6IÖґY5Ԝ8_Ir} k(h@J=}fHK90n77 k44&4^WXYI@s93P{\$~&_EƔYKI7(.v:֕ieHLAf57ZA`*  4ػwG/Wģ0p:f ±aZJCsHօԇkMH?'(airj"5lEQhtf.6i<(jYFYcG@$ GVWR7YOW_6ۮt & Mr h4v[Px H=K^$U7ݣkt$wJs殮:d&ByAײhLd='iΊfDObOE+3pJȐqlK-*@rw,~>;^j6IQܨS JZj0"Hs c+͑ tPBeNa(zO%"Bud) > MޔZz{㰖8}HPt771h#h֒L>hӟ:ǗUڷ-L`޻7ktd#u p`bb+A2,A&E8. 4QR$CU N*UdmMnV} =&([6@ۤq>1Q}FCj`70-**ѨtU"V/b}FeR񢃅0ᜈ`RF]gb2TG7,]뽅} OJ!׮^bͲkJ^~ "l 1ƥ<<$ޜHl13 JHD[imzI.Tͷt]f $dX0*}DށB0U*MV`bŻ2M0м `B먫hf FF[7JԤ 'puFt&f9\6'|CjcE鰯wo<`g8Α-ϚVE+i3*q~W$5{ievP  :ׇqm:'#$t@<藧:fvE-ȥ2 @GRIEa e釟 Ej@"F${.^Re <3A=fBX.?8; 푯Y 4^;}]z~Wngb]~7QƂ♴4CB)(4&֔yN$Xl,x :sMpt@ADfܔXď=Ȭ0xe%H5$V>Z%;W2<(D,2޵@IymC6Lmj~~lgJX|Q7 A4!_RB(6@؊ S"16cv%oe΃e\š@x+V JӖQX&F+G#b̯Q\6u3h}<`F& `ZO$@6wjlqDNl,Ⱥ$nm 99"6j<("V(˳gni"4yI%P0 $Fg7C7Z h1!4T:Ys\ɸ?wnO;6ܜ#k"<-d4fV-U.% %Ht5i!$\5%Lpco3NP'IO $5jce\xI=Λw;NyUH"GN0-zv=聁 Jqr*ug.45DH4h H",~idL%r^~Jw4(*YqT%Z2!Ezg%{F=xY䱓z3ǪGbr{RrTe£ET8h߻HU& |!9\PG`UHqbqXb,6f4mSg-ɪ#733U&d|? J%T}sE( [ب$?,;jnVHA@J.+n;esrjKei 9dUE)3Pji&MJ*"Uy=mq5 dS*UK4"E! `^-˲GcLV:4qqtIPͰU'&EjkjڲR\2-R6 941G2/n,*7>#ϐ28rq+cKi|V%Pl}›iAEN5Upi(b >tQzpZ*,RdU#"I lV2tObcQ db'>R(%]3xYk](op&}v};ѯ}i$l#<|@v`-45R;$?/b2֓ՕץZ[iU4,61#*lae^IgD0!F9 I$P"NfɄȪdٔH50qFDhrj1D G,DhbgŢx`n" H]GM;3~Yֽ րOGcFC` xm&nw-ˆ"%I,*$$Emk܁@?bїlaPoHiH"Q ,\(ybIHf(DE=(" ^j,Sm44xTX^8IP_J7KM AlϋZīI3`kxi&RsJ.bUe &?6@U5a |f׿i]TucC1z ] %Ѕ PD` nTp&P揊5Ap0xX{a[ )*Lx,! <}ca&E w-81̛|YKwˌͶA]ev?Dfoca)+hl*$M4촺 ]oO4.^aSDb:dԪ.NaP[bt}$?50y tȘizmQ8Ll2d4\6<Y`dZΪέ $IFDXbxѲ"UQ#S@Q^~G-e@ Ai t$ܩNɂJv&mZwydFZpA::àwڇS3F)}mh|MPuv vOpx]PpTm1`0/bP̉L -0 5i2hIjQ'T _}f DL@Z۲F$!]NX'H,SH-kEK)3pyJ?4qi!B1aŜ$10<~3PJ0;l$AUĊB%!)t&jl#V0Ӥ[B^\-_2(%#9̨¬9  I# $TF0UDxePFFd6F=Q{ZJOX%m+EYse J%[t\R6+P) 4ڤ.$duBI)L98̞VmbD2S$yD/nLvvO_jJyJQ]W:\SMEHoq z>ʽw@@Dbj$E^ $EF7K6+@LIɄa3L'h ONPQE i"eZ|2xjzc*iv(~(9Wu* Bd2~=[ڣfĢI[6(9> D̢ ,ܑ%ROWm',FL'QwR50RA֐ġb༹+b+>3tIMd<MɹMXڑl>f|.CNMUk~YtѾ2'2Bd%RYQIf%q6Q͛&L%t\A$ rP^v@J` &tbev*qH\rC6ّ&tXzHlL*F''0(i5c&tDX>P9G$@Ғ"*C >%m'"  539_`#kt[f2>\ƫ17ʃe?񿺤ˆm7gXEhrE UG-:dcItDu&[Sq5Ш(1xAoĢ+F)dcO=06M`#~Q)4M+:IvD eb2v"4pРRn0iNP~?VP:&vZpT kٲ$Ndl)*#8Lb̯dHVL@ DQDtTSJ -j?7* TqpNqZTE+I3rK(i& [ J*c%7 cT >0QHZJ%+;v@,@+r"aF\v'CJfY&A &t:J il͆=TTNDmcex5u=`F(Q#@L=vK]՘JŨ8IZ#8 \6$}H߭Uk`mr =IGjD9(h˒UIHq @LXIzVdR"TY,I<] zǘd*v'I)ѳW_M*VR%^"i>KTiBcb7k": @DZ[z("@jSzU@@ʈ2zQyL駪<,bBzy *nm>F "=Flm$J!.dh^m꜁'Pn,jyNm^e).1ȶ:"v$B\nb؞r88g"YNdz&H2ΘZe>:/Kf8Iḁ^O&Q~`D)4*șa@S'ʁ*#`7 J(UҤh61"e]w`D{%n(JA\4/c wR$ٍgv1A`]qeQdZ.EқBëaw5\˫Sq^eNYӸd-ѶZ5u _6# .JX2(옝Rtʋ ,4ce^`IQf`}S-3BOR' 9.iT :&t@̑{2Jin]ϯIlTd=t$2JW:)1~ߵgGRU`L\ ;dE5qOZWmIѱv& $Na$MAb4$U+"a @jt"H ͤmf*1amȪA%@ŊsvI 1zzSYr0 AyL0IB7ΐ Qgg;۵QiS˖RcGwXcʯ\T%[eǰToDRa4WdcL6.:i42BaRR0I+E6ӯU\yXn~௶Ts#sBUBؘrI6q:da QlsBuFd>XILDGOVBh_(+{Icb$=8ss=EZm6\kiw<"zvk׫7$x@иA:IU 7 (8-easn^ę9(0еW /'_OP-ELjw?$ȞrJ$B^8d"ڗ6ڸə[rrvVSMW{O |T@'?e>ތ~|$]481#IS&$B(└8K ؓ5EK$֛d.0=cb iY!F~aB9|~a'TR6f-kbMɂE8="iS;zNa 2P*.$S1h|{{>C2ԆmRMr{Z,1 %yZZ6bWJ*nI$qDuYy*bjKʢ4*(#P! ^$u9r&B{eQ0k؋9pFֱm:yMj" ԈXaUSYxQGM^7LAME3.98.2i  4LAME3.98.2tdesktop-1.2.17/Telegram/Resources/sounds/call_incoming.mp3000066400000000000000000001417521326245125100237160ustar00rootroot00000000000000InfoO  #&&*-0337:==@DGJJMQTWWZ]addgjnnqtw{{~9LAME3.98r4$ESN-<xźCSUyj;@xXFc Dje߿!Bww"" dɓ&LDDDBݓN"""" <<<0@xxy `0 ߿~B"DDAɓ&L; &DDDEݠ!8xxx`<@ Fx.P`APMj<̉[ x$2@BH)2BﯩAKȢߩ͌tAZ{v@H5uѧ]uQciT^t+PjP0ɉAHL%$XE`>@ Fx.P`APB5P̏dM (hp6Y2@B̐TIC]{&:GXjH+MAn]ϛ)3VQ9ݓU%u9'c04eCp2x C E(LA*ֻ!یaٰr +owMxp貾lI4yf8Wݣ.C{y޺ou\_{e=M"gh! ja2 $BGbMoI8I2{]AIBy#{Q"Oii8-8(Jq Ղpaǥ4n&7wI ȊLge%Ù't[y[%x:b\_m󘨲*ŮhNYt!{ /HϾȈ0 pt0$=0".>2뺓aPjj[!R@ bfgSnc MD]s]cXd/ͼ7c-wܫ[I^֘8_݋[Serꆈ9Uyumm T A4Ø*,L] ˌcV#TLEM%*JJR@h\zb8ߤ1w~vi ] \U ^r \ڽGGe뚐u YLz5ϰjCΞ]T$}be{ @0Lj$,àĈI""G(Mbk&>彤e?nɄkP-,yV|ghĒ FzmdtAdmC.>f")_6pI dҋP B˘AǞ|QXB+ (ap !P%ZŲ-@+ Ğ@HJg{O>h&oe%$d,X-P'7Ne01ڭUݒuߞanZWU"*}U=|]mPY yeŪ&t @5nb!Ahla x32P;El8J_PZر)#{O/ 3Em^/彣_PXb-渋5$ϳm $ G[q4 `V0c1L#0 1 qMF4B1U3UtF(SW[9hQ}wyowڂ*9 ?F;㝝)"n`ދㆎ޻~r~l8XkDG {ēy[`5 _0C$1J 0S0!h`1Y]KRsT.,&mեiXMhzsk=M:᎖vn9\/.Ys5PsP\CKa)<=*ixcKLE#  `E8;SCȧ'RkO*ChрzTjT"8Ǔb`ș{(L"Un䚽.d =T_g4")6QDzޱzV==:|ŲkvrCN$L7Έj}H`Fjab `$&"@,.ا$w2q aok42:[Bͪ3H (`H(ދRǖA$U:Ĥͨj}{3Q9 9Hy5Bm(c !*C!qN(8`c!POAp @0$o8ŁIjɬܱݰc~ߊK;)sRId*qk?ZV'Nu[`xxkNa "Ţ|GFp Y T:˛5ysw/NR(nP =9D(FiSvwRLF!xYkZ޺"]_&cej]㖊d]Q7_C7ȹ& dz L"CpqH IAY-O"NLtЭ>vCD v 7hL"ań`Z8-@VKkYV}-#nھ!@h>Wƒ6˘f8W_gcϵ@%% 0 :[\09BQY$*1 3q/-@b!NF ~f3K,a@wrhX "t wV卞ƵۗVKGsE 1*շ'XR,2qPkmϊGdjTHNuhFh=",VS/yk.NEƒ @?Rf%YQ5 $E7wmJɧrvgՅ3=oϋb4L秒1n{۹߶l8@[_(a[b @ ad&5GQ 6N6er)*00 [<JǙ3{F^S/m gdxophsb⌸(rcPȔ,)i-i9ݤmhIQE۪G.BA>qu{/[h(b01mJP@30&) ]l"kT~z&Ȇ>\0g5wP\Ik_MIkeX(͏ĉeHQ8‹: ܥJE<.% `bbi`8 cdFf& `[Tze%Q[(8i*F/5䖦7/L4IZ7viP&(y\ނAYŒ2648:w$aPCh@1LL!?jjCT*PcfQ0o&"KsiJqrɃS@(bvh^;w T*laGFJ),B(F Oc!1$581D @rH .I  FB|yq *nge,fP5^٤KxI6_-JUkL|I7iI-]ec]Y\ 1G[X)ŚXФq> L~B3DC6pBAn (?;z4,O MUj HHڷIŮy DfBGؙcor㤉"A X7G4"c-  $/Y1Ra*AU FR,m6`Y1y3[+@ӜU:YQgAs"#ˣl]$kCAՇ6]$ssُ&u@̸wQ89'i-79;MH_[{g!! ^0qz"500pɧҁC dA 7f 7Sx;ËK>faz2b\~[`^< ,|<^qMGqJjv"h*D%=mq &Y=0@P H2's  Lg#G_'0ZeDQL#+}XQ;13ŸPG,qx}_EyѦƅ\Mm~ Q`I [Ckx 13їo da'+Ƀ)9` 4TAC(`)e(` (I`ELu\5Bp2+(|ɌYeZO ɓ29Azf]D Ϙ'ꮧU>H"8D fADf ØILɐ8L8d / ``& (` 0bP8 W@@`(  7,\"c6P<56D4PuddmVc[#q(K S򡛨ĮW57[UU|#B f_왺LQ@Pq;" ErJe+$ @n;ʗ0p2oRk`7m`}cP 6neLZM6q_[Z߹ߐͨu#*!OOE4+XpRQP |_`n#b M $ܾ"r@ tWz|yY! M͊VO0rcɵKj I*͜|忸DՆ߹ߐͨu9Q x/v. Юbl# lRA\ РiJN f=Hf%HY< ǧi VxIHKZ5=r3(>Q4  wId .P 4K֊e[)짩@SD֧][: ؚV2v)5AVwUHa(n!ɺ,6vnzCXĀXQ qC Tj,v… e^88p6|Pc w"Dx)dt:o Rg0)zqtnjbpě6}k:[ S #89i>өf>ϭ_଼Z(jG_om0l@3aN9p5h_C08 P" dJ/q $ms@Egj5a+EXlOyJ %Op|案_O6)s j^,wN ,`@) ?@dděeĜv5ftӎOe2PriAӔO Ș2r$-wa1[mH'pDiEL*+Gj_(s]BDdN2Y2jX("\aضTR&lL㴶u&EWRTO#O*侘N)zjxwEeA8dƤ$6dmsUNKH"-6%0|0p@ kcu6J1|sw)>P 5֩ i eRI:bq$ذ~SS@F <ǝ "Z\:JoWIw޵<[٭_.Y ol!9! H8'ť$ȉ^5vCJk7GmkR@UJzKЂaCC?yT%CqLUhK'E<)e૴=jIӿ]b1kL X]@(xǹƋ=!˯1eҹ+D]kO(>H!OEf]%k"ћ?~6层؂ijEH`XFTkH2#+;ڰPwJ[*fn+cg0` L߃7ΊIYCj[ؚdƂYk')]5f%[O'Ht;}.HzALdp2Dm'yY:,N< GUǎE)crIc/ןϰȆ;j7P ǀ4_=4"{J hЮvpљϦ+v*|#Yac&ÖC̛1iS{F>SO@mJg0 Y2'LzIɘzzfe^$ =J`0ٰYIT GA2㲡 1Id) fJ➅!ī0+w~T&Cc/A\Wפ&pPKA Ba\3i__>:8pXYeq |+MXZcdxۋ7H#X.rnj9z3qU ,omU& <;HA@(ng[aWx4X7jO:fvw&TK-p7 zU>Dw= xO͗)`DyNM7iD {IƇH>,+C=$ 6 ARc6L6cBtaHgW:Md :)k1fs<ʀX b8j 7&"yh2YZ?!Gb$)ٷr0ȕ|0W7ˬR֝zLc^hy ܵwƀxp όf@ Ztmrg`FfФ'#, l4 z4< p嚶 `$aT՞OK;z9 ΧL :[!ņϖ ~ʡ5,>h6Q8 :ʛ^؂3{o>R Olc홹*vH樛fԑDD !(QBX+щU#Q)H$R u>j>AhO:Y@E D+)ʭ8; {yL&iUڟ1@1ݮ1">'Rv>׌82eٚH5`)m"Lm&(`*n@f!`&,0( #\() iX vtȇ;Ggo{eJKl]Qf{-oʶdaF B fV0=j2L%:&xΝivvD4 #%(&0S4O |0)sb43Y"Y0*|Zmu\ɰ`^RkzJѥH'n&"I)Suw1G~Hz0,xQg)W:Iā#+'~ A,J<FbH"xd`*&{fk1%E4#*<A 5h>͸b!d% |usVЭRr'k`9e8w. BHCpiC{o>R Om1e隘?eiGT NC` x (¨Mu 8Y ,.&L. @H@= ?VD4RȑN]J IӛϜ.K}Xh7e]6ȢbeCRI>{|µD1*:NŕCP"0P1>]|0 p%*=rKCT$ oSN,k4d%BsjYaRfW`y+q.ٖ:,)]D-ojm_Ww9UVB\jw 2 p`X bxg&fa&$` aN,7`; [fS9ȤF puYt:׈E'<WS+Ě \y.j,Ƞ^sVfNs!2iNxW2ӨW  aZaCWG < w,yjjGigE< $@hÌ_KTVlÌrs+adMJfU4T.rN.C*ҌėEHI({fNpOq'C 0βiloSרU7'n*D3$0A S0#:DX0E0,0! -pDŽ f6C2X3eRjК)d9#Api~z Y4rD4Qy<r&% OOf&{֐Ivr^dc8@Z.R$J +ӌe5" i)+wR8e uT}Kf-h"ӖLS*ȇ"S1!Sl2!#ljQ30ݨ>~-d8^ߚ&{ ^H@WՌZ7@ <6 $A&pdRž2ix|:/;iX40CC;HY@Ū>Y{:] a/;{0Mu\g;{{~߾,JIkk&:xcVg`qe;ca`a i@ ?[$& Lpk, \i!o޵{0{s_>YRafI T AӂV z!wn[I?~ZSJޕ]HDӹ1JR{F>c2m"K]f=˲NG-DeZuu5vQ[U"V[[x~Yb8k|VJ6b|CJܷx /ꮲ|{$)bKߢ]uyuRT ]Z\@\~GA. iPiЯ,*&#$+4ckak=<ڵ=\?W%|kKÇ oJ72\1YvLg$ja_[gvm寭RA60">ď@L>Ѐc4i8ԾF@ `3:k YS $hVD_ @5;%t$BrHȀNRNܦfWeR'RfDg:q48$hг2D!p  )G# 0g cc50r\DED4n Ljp!QW^[RAJe0|q8oqS鉻H2;~u= ҏe5Y='Ok:# Zk˓/*} D| ,@4] R f#\ۤ΅S"rdt*z:3=G6o/pj2Pc0Wp2]f:>ymO.m hi-*#5vrLT#t!(Ď0ʛOn1ӳZ|IeHDtXBA̟ :2p'{FNRlOi)i8ZA*X L8L/M(*LA` pytc̀^0=Ec-lrje){jp<Ơ&T6"Ntjw ^OTP}~"js+%I!GIe[R􏕼͗>:_j6H@ EMp!8k($XgA-ɘ݅oݷ~_auA{Zǔ"t.F3'!Z7Z Xj<@JC~pj8!b$T甉tƌZmz!}zP"@LlTQL@ȶ*a]%*  rP*s9Dؑŭt&VBjo]fSw{|NCeٶvs6[RVl 5Lڑph{1.h2oe 9UC\Dc@348OTx0`) 4pueqG_h~j- \SYh.t쾟{ޖL(GQCI!N.曎6J0j0kF?2CK xӖ&^Z;VBtـ0spH[LcLr̐0BƢ&Z$ZAjb-Dz~*P+gbL}֐T`[:e4'i|AیkM䵰@03 0Mlrn0"074|2\blLW$0׎M?;soXbhKti!v/5ZSg&$a[~ :nqJ~E[ t9$@P0`10vdXCR%6uBa%YKYiMRC7,Q7SUd+zΓ$H*IXs+03Bv -U|CrۍWq<&*030B0.s 0Q4S0<РL*PQ eX ilKҥdLԻ. JX%MR{!H.D01ak?LFYfceRddJM7}ڷak@ `0 1\30R`FG ٧P䂡xRR<5lco$a(x* O\eO"IjfD) 1X Gf>Tقʉ.)VdL4FJ2ph{fNQQ &d" 8@09N`ÈPK3 `Œ*&%^IZ1fgicWgz ͯ$b(qS` oUuFJIfzLQR:Ӻm⥓p裏Ѵ# LPb/8O+`1ѳ+3DqlLb;ALyI{/>0od 왹wٚmFR]oI&fqQ= > H֚Wx&Ɔ,H4er*({-TB8`n be2@U#P+0Gp,v-\5A#Nϼ%ug_ձ~ߍ[ja~L$Ƅ#ΣY{ 0ބXsa%\zʁcAf҇3Cj@0acfR0jAj<*qr_E4BNxzȒNӱYE/q^{>`Ok`YA"N6j/a,0orG;la E0 5S+j4ALK @e8MD"I/q,H; _!lqj(XzY}֘fq9-6wA0 ڛ)<@`B M*}ј1gdXV87mJQղ(yYFMIOƂF߽޼?-D0zP)CZ{R;JayS{FnP,oi')M<iӳl4~ލq`܊$ #e ׳hQcETnp" mɿJQJ*nӠ,FlIĔp%]*QKd(n-E6^\&ZQlM_PS3Zy+?=aueT`DbkIh;\L)W[rŴiۀOŦ:7S\Qc5F6\8וm-53b<؃[V 5]oe{=4|9 ^PP $Zgc@8G+Âfxy BB[L~j)ٺIoXذcx xa;f.P0Y;m!n3lj|d7eq}k8dhP1aZ\9;7}Ain%0 AHƎ Z@a@& R>C=4'$.Xe^E[;? m #BA W!8|Ml<Ϭu#{͛Q>t4TrsMĢKbS=&*-΁붬,BuWq`;8Yc{H>P/Nց!&<@ Bk[2l3sP]mjF3nwFоsW(B:z\^[۟:b *Ub(p)!b\IhDA0X uD@c`FsAఊ"n6[5qޗ;ʬv*RTM'4NNC9HWg'n\kH\og9B5~ȝuP?j E31*"Ch3P("S"511iT^T jT?զ'{)nJ;>Sc X(؝N5qk4@yd&os8 AX~z0bׯ;333?XSy%#{s Dwr,\JDADB* 9lCL8+.$-;Vi֙NnUQN7z 9M d%hz_X6 \>Z\3ݭ П i,*jUG%U33g$HD$I8*cJ3TC&@sݴppёT1)l*e@GZf ^ƂVkƀN7` +OMTi(@4K"Kh_00nc;/N>'0C0 S rP &P-5C KIm AH-UFLfP`TiS49sG0ҥfhS(;DP'IvKh olC%<,5)[(n\|P8.XE*٦&t {n3055%#(@I0Hc :T&l W( 8k!ܘ ܸJ*>c;€-ՍŶqzB{ݫwǮҲO=)~{fuiawEɡq$jc`F`BF&m`GFf`6wm A} ֛MW$21(°2)nuwaH'QE.U#eu>I)J{/'>ڴ zs  bt^(~_ހH0C 017,70 05n@jWqE (dF$A˜r;دnpx81y\b< P0x:~ 0$1 1SxV1RW*m| 둮 v }Q.PmݪX6L-ū(Vֲ|TjymfsP,?7ipg%x_15̸ m 0#p03-s&rǪRؠV ;Fȑ89q kij?i` !q拚jr`ӱߥƠ(rlA cZ6g".c4b!W152ZtӕNW~R!>x6&%CJq` f `f"@qC?`t*5T0N$,jJ#v"Ǝɔ35Eg~ݔ}\$afդ`}fSY*id_Jcn[pV eCT%IZ})= -=u^ 1;ǓIgX{>#Om'f)$ > D8@9xY8QL7m0#&'wrkgƐfmA αBXA8縯sGyy"|M9Z >/D\o|KSQLˎN UUUUUUUUUUUUUUUp00CLĞ1တY?h8@!t+BZ`$`rt[2On4(2=`*>yǐRy5,ՙ q8G=J%%'Lt 0X==`$"uی'`8`$l`Ȟ&f@4<8P#z%Wx00,S!w@݋OSĥI*!YɲGAhVjJ cyks.U]/Ji6dɅ9{v?U]B^*5-a8\.}ܖ4 0ɒi$”ejEskb@'d;v|fGaߎ.1ńNa2h6T]"Qnq=EPRYa?tSF_Ijilf|SQtϿ Z004Eć;G1i{i>T'OhL%84 p<)ZT+koo:Q@ڶN5w֘f\rp\d! `f`$`f#rM&?&2q柌@κ"P0h%JUW6NAhmKͩRUfW055\%7oX[-" A,n2'0˚ sGt Z"BN\NAgkF00j;50083;T OPބ xl^)zuiU^4NFiI2&K5 Ilk-ge2t34meZc4X@4mRT!ߍ/~ H" 4Δ E$0 , C}#SU=ZY( bYɄ0G-nC2ڽ,CZ7t$rh%+ .C.V2nV Ӕ#YeKC3I8{i>R-OyL7ESUnF4pB1M13Ѳ05^8{_+`4^胨rm=vXҗ:Yf}Lm0'&r EvD5IC(y İ_QBI2 )Ҫ1 $T61 $& <  OE,ъFHcfH^ZS-}iiII;"zj']r3B-)|E=3T9M& *W܄QYq"VS]1aٝ<ֳcHY<6f={ed D׌ĒKQ)TOv0AbmD޼J]eBXr/CUi6I IP7z9|ᄩ)@]MS29]yVb.C-IIٹB 0J莯1nqf1E&l]zw\N@(3$Գo!8Fx'*Ϸ6MyENې c?KBi9 kS60 t k9Rq!/i'h qQOhFY0עj @00 r#5 & 0ς㍌X$:7:yiނKm'mQ"IS[YNd65  us/h;߹IrH~v݊VޱN=]$ M-|Z e000F]1!  RFGpT YN^cPXy𘠣 ZTیDƓ1 H{f>.% ̘kgρȦ"l 4]H8υ Xed%Flā%& S. <sz jQrasge , 1Dӱ柗iNZP@52V5R ?i)3¹s0c01 @4XP m {\("*x9i(",? ; - 9VLKH\*xԑյZ<؄CKtrwXD faEn_ b*&\c,K < qoU1GFe~(Kԩcьkf{TkO!5u_ݳ4O0Tient.PmhL_kLd4oOE>t5y|\P5KPrr׻%&e5*`0^V+a$ oQ vsdr rY!i ^L)c"l.Fp!4Ggwh٢ئFǛ]Gϯ%ϱjP.bCwzH+1{L,Q#ezkB5 #&G?iz9DkjI¼WCdY˃tafv&Q>gs*L˕NքpM&J(^lt޿چSQLˎN UUUUUUUUU4~`3FxBg,\`" `l:n»r bu{"(vf>l3Y2iH2 .`Eԍ\MLh$A5-6Z9(̅ Ԝ>%Ɩs0zn3n]x:s{$P! LX O_$ 7 Lt#c^]4ɾۘj[2zia#p}h 02[K&)uID .pNֹDҙ7kZM&ݥ6aKFkill̇L$F Zڰ XJF`8fA@e4a֔$4,+ĜƩue6גpV"2kTIՠ΁l啩%4&y..C1{<'VVZ`1,+\Ê%5Hŋ2rwF>/d."cIѫE`8PC"LRA"f#!+rތ"^4ˢnGkM S5:pk>N̤,inQϊX2c= j8 ,(dFA$ Ō_{/Z]Cݽy* I B E0BvfJ6O%!̇G:`$rjDηH̿p%&9/ $f B 016kMh[P lAG[*u<%R 02'_&jQA9&4ON,;WYEa$*V)uh2=Cȥ6{<~QtM{kdI0LAME ֜` b9 ~y6y"{AERkv`(.| C4lA*xMA-i49(ɛˌҫ]hHT%%+_hh'HNz9[A>a`g DcAV#ݣpTD(0)oKʘB8Ҽy'^(Ef9B)#$"k5YtL6^bp|9&\49b3%Ҷ!}sExzIx"oZKK&Ų "rIS>˃B 2=BLH+nAeUAz#Or~4bsmN=4p]S `rjSK9%9*%\yHQ{LKI`(u?z'ܤ^S@ s<Ç `n}tƋ%1Ug&n$y$̔u%X.A53QaHѹN)1)JJٯޞz~Wh“F+TE1ADΆ F+I`s(>iQܘ`i㝨P!Np|p8BzC]r'm 9#kb,MHӵF5-*91M4|:De$1TL74$ ̂3&:A B4ia`G1jv*VG)@!RsFf@[Ū#ZlO!+69m؄zI 8Gp)(#4mӸO=O {1 gHe6fo}\h Sl"Q/Iayѻ ш` "| O@c'eCwaF,ƣڣX,,.k(ay(ߒcHib!siTӯLL #bfb!Q.g@Fc@"Wpٮ( l* ,$#h[%s4#'^ڵ&t XH%iOrQK({ysu(+gӪ_JIDw6!%=7~ܹVOF1iw>R9`='cIFeLJP*Mhkjma;,ad[k8}ډ;KDX;;R d`hM/\:i'y ,a%G Vj@s$h#$H&>tZZ1`vg@@!p00^bfL$!FrA-:dt"Ӷ?nZ8 7`ڏA}Y3!\V#+.znIN4 FL47VńV+Cj%A!d|(}y{ oB Mb2 iBx `($& -wkH Yl[dB6!!@.HY՛rżgGBE,($4w 7Hs(a&^$ ,*%=(vYmP"T,7@qrh<dxɉMv{ۏPri?.i91v5|&8L$5'X|,-ȁ2]+Yφgr:}߷Nv~չz׈R3Ii u&~_/dZ̘nDpK = 1)H4 ajËUf,fevx !d*ͪ"E͇ maɅ1F( bF8 5dA1tfJHM c-ɔt穢UAyfQT|s`Ǚ1Gi>#xϙk{||Uc;ʨX3uG(U!ai \ (h dh<,3N.SKX@@^5ݯ+>f}¤rx#U7NIJxy325$] pm^%'%i)й@96s$&@ @ܘJ(bliVO;@  8  #4$eV'()F,P7Ye4Yx'DUM--Yf%4PD,1GU/v`ܚBH&H[cBRҊ.Td97x - f_wJWBj[qVT!r8]|S6ѡt0eىӊSoʩ:(u:zu5@4mP_ɔӥpڻW֪0ZVԔ-A78R=HE1𞉘wLON Aduǘ|ZlpA DirҕN@ f2"PdqxP dcH[̂ 2!@*6.EJL:@Li~$6ʭRl),qiYRxțn m'3lS=?ha٭f\rpAЀ 0* P6$(6112xjQgd(c& T7%5-?ODPd%y*{rݨ2*IFnkACT6K%b&hђ~f3BeT4Y4F Jw,H4*2$NCذ0 hkrɦaIjؚBY]BWS"6UQc #䞠&ݡ'j4@r=4Y>n4@ťd!@xm@- y*)O1)s >!`QܘB~ O;ÃB`Eh @2u=w Pu%dpOii&3I,J-,И#1DYZ@tMG^4H/Z][$JKo*Q2.]el([3d H@ʔ,ghsZX :h(6y+Ӱ€sj4, {.,Fz~ Fh;@y7,L>M6c3En&qR+L 32XM+4QC=5or&{ԔNKO|(7J < }bУMe Bk/(0 1eF>8? 0CXS"R!hXQ3ꖹo*[A1a|1bݼB6gM)eQiI0m(4i5)Λ.ػM53SpB3 ->>ݜ&@ L*YcۈIF˸1 (s * +.'k+bvTē,P0ԩ934!pͳ1Hv6U<0YȺ7:K @y QKN"HсHaܥɽ'*7H;Kv&`YU >K2W}4wĞ#vg,P `2VEdDkxyk!iS!S\{XtXX 4 Z-%}$HF`+0Ud0*I%ӂ$m BTQr#h|4ɲDQ5-l /QAwd.P K @./ kn>/nL&T," >=̖SF4NO!3rrH ˠY`BrNxO*vW1LYh,5'fl9>SH8bޮnlGzw+'/9KՔJbАC}b')*S¤/Iy aم*BѮB/%kE  )#\j!()fmt>rܑg1yyCS 'eF*EZ\*j.ll)*hUF=Z"9 aQ4 L`i@$)7hV_i@麒XlDݘ׍ ."{y=ͰxK{/+T VE1ȵq&7y)ܘ'``i#׏x(ԱEn,8dށ@|080 jBU%*ip%RKtW|msgQa)h&p&@"*j(C JJHv43^TF7\(!Plڮ0ƬWhFEaE<`p,l&LJ:XӋdZFKaq@HkX$|&#a?Dr<=ds0?4\; U%$VC 1 Z VO9bdZaE\E:h,j Kf0ZMo?oHB (}O~}@WGAyB>“(1;hCw$0^4;NtzKolsj3$_dKEU1! ;4J0odē"qXiSk+]EFo04N2@FzT )!i!gt(XJZ1;QJ-3ƒ@$з: ęJڥ4hu "δ՚v%,IYH姦>0$%UMEEzkR7ñu7vT@,d[OF2pJ8mNSMUłFEy,U( -f%1Pط$KLo !i(鶯LS%2`4(iq%LD=.AOHX%La‹*vH\YAi⦦'Ke-Q.}pk䦭f:qe=lr/ КL`]b&JnZ%s9L`0`t*a4 ]BpeYDM[Xq^N(YZ8DRG# BgIH)z,F[^GV,cE`N3b?+lhs|=F k[m*C+g,MnM@Y0uQ.G8dB"JNjQ$shPE#]'9#e $X+)*4NC! 2*'YMnaURG9y.P}h.HK >LWZ9CC pkZfX\OˆAU!3h^m$X!nYwZHOw*_6C%=yGfHsF:y:[X[YE1q&)]:) 9߷PprO+$;%Arb?13 CGTv#N.U1x? OkWԁ7go'i?c{z̸{t~RkCb^D4JRIۖLٟ,MJаAA18Z Pa&q4`0" 5kQ{;/EoF. \ݚ'n+,$(_60ČIq͗Od e[g_?6m-7:"M)-vBo"p>zCh$A( ȦvYwR`PjG$UiYN~:3I׸SE \~]&)`R,;) PERrȝ|x4-X`h.ˇ`ϖ6@FOCL㊺!EiNx'!I8[)aئ4 P]sD.D@|2OcY"`E |u"˱"5Npa7T?,[' _Vd` +E2\Jrܨ}ilZ5vX"ЋsQ 40m8]$n≼$emXKgW{?@$xhPoA:1ELrk )Yp։/8f#6pQ.K%֐:UE$$[S.Ae ^JaY$D]məíiiA|f*͇L ǬUlh $N kGC87e;-].X[ 15 LF#`AqOWj#cܛ1Vקiu:(`%[|%Mv*[ y<4GS0dɶhZ\;~҃1 ḛ&BxK.vHU،:;C` 6H捪,M7ni׋jJ1LTxġé,(tWQE7IfgOa&Das ek#5P=_=3|R鴉̐*cj.nS8*ފTZiZS=vS3HS޶ϴĹ ލ4ck86W#9b*$toBżGͭ%e,9H}m=qViz ,*EV/S@USfL`Js (WӂT+"Ě8b'B$cAMQT: 6:9 AXMWX%B9;hУ'E&F@^ٙkjOGv 4n@ (g[etƘ!GD3-8!-k-5|)2GKfbMSqv0Yk3r9@c`Sh*X (eN*S:cʼ'!2N03Y+fJR)&H'֌UIog6sUg V|1,qO2)f *E((1?I}X?S; ˯p]7%zNiupWz} љSdhn[0K04&lqxQDcu׆@ܔ.cJ?4ﴹ3i^{+}նfU%ɷk{em8t2Tu2w*K$DS)7'=Y#. ѡ.zG8h4p$6YЮͅjH-%Rb ?O#)Ŗa*+)Ek4de='iWD9׈|T3LJl(mQqO-gbAĚ(1@LW?񭳪H|Yf ",{"T5qB-FA@30+3 JXS,ĢQ]wYH,#Q(4F뢥2&G6b 0$#m-4ܹoak74:7 v{ #Cd"[h^w~;*dd<ԾM@,HQWh`xdH"S`-Ap$vhQ,zdK,F!X8BO4kg5C\6ȒRR*t7V== Зr*lC`v,:(BMt‹m r A@(ӝ5&RRM,MV:QRI&qfv 2D) l8R$LCQ_Oar# e٣JSӘr'dQGr3=6Ԉ[E+i4Xm&|_-BR╷0L p.,ֺg+QW օ 7L0@ K@ (֑ ]}본'ZU t2ݴʙ -E:0&qTJI9HDHCΖr2S $r'>4&\$ٯf#[N4.%H 5kx)eM0RU1S ?*b0?nGj nCIU$0Ä1J!<#̶E+UĨ𰡄DF&kS]F]^+D8v[آ<"LT1K!]2&]BhHc vA!e t "NIx[8,iX0eRVųo2@ʘm&VWM1&`-"En F"(]JAVYvR# >J gXZbBNScCQ;Yj5OiTE??&N@YGbH5.-Z{oH9S/7qluZ9.nYC"?-g$Hvr櫥@1KnVc/eE.| D@% 0_5ѫ' E}i =9&+47 Nz=JVJdb4ޓ1k:~.;{%w1hRl(TPQI$7! [Dz&_d4  JMbSV8@#b6'2#UN)2*%9]%("IuXNN^OYF_%Pa\d*h(@ z6qYkeq3p|>2'g;)$Ԫl+Z-IqKĘA  dĴV\ m#aHӛ[:;A[8éϲfAS"@ x`xac#,HɽݙP YDTE3H*ةi8M$ A*ⅴ)M 8MEb:koK@jDNXn2 lަVa6G8Шc9,M9%cQuLNAhr6CK0 e0ؠLGH۴CHO"t1ra&a<5gZKCE4EG&zzdv^ĕ(*F*b+@D VXg4ZAE**ׂ;fqGVp*LFvРE0ޘr!_[Ӥe(K;b'd_'ϼSeMGm轢*(-A qMSQv; d)Kߪrn/v ӝz>}1kojgt4)Ӕo biUB̝\HQkSO@9O³lBH&VzDHYe%d A.f[B,rfi喨-)pbZ5\onl:v&Ȼ}JgE/pf,nS)ue JSZAbV(oU2 R=6m R,b @}Kk { :X1v+mɶ)]iXHžd^"?;u@lIu3jV j}LXZ"А^I)ܜ`zrbAFFmD #2t Q;Y`I4pf/9hdd߂04vZX$nQر"ѩ--YZf'QF#Bg0*YuTųi3 m&~Ug s^*%Db AQoΰ-A(LW,T?԰Jv ʘ\.BBHk{lßZth$٥": 4rLQD_19h=6NY8HMk^U$䒰B+IFPpX碦W\]{&1,'jmOġ^cu'K,Y "Uk\c#U|Tq\ܢNAcsܞRq+d0p"D9ԥ+њ^>ʸK9(o@\z6i 8d6׻W찂^ 8jzj#D`c_T&UV)7ћ% hRb%!L"XIMcq"ѧ XF8REפ6䉢˺9dSbnnju&Pyϗ|d7OID(rNp5f=dfPI=jr FGiհD9+>D_m$RbVct2t]/6`Wa)5)ࢫ2פ<+,+]J+HYƚNˍ$V!(`XЊh5HUͦ L [E=%` "tB^(ݴcObؽdvi8G"%+9H52g!qxD& Mnp,կZjYvDC & .%k"$.DaTd 4g`2wq #^01ZW&?w>F9K#;6W GnKV3 &o͂VSm]^?DQ0ɐ]zGh#M6ZQ8e!#9;@&lT̳$(N |/dԶkUdFw:qXc׵$ٷ>{9jf5ɌQ2(.)Zb> 1͟f\!\(ep[@e+YEM6Ladǜ*+Y|@}jȜYed`RfΆ",85.6urhv*?LQFar !b Qƙ4ާl+ DU:GgZuE$f[$/-@VQV7ƟWm+i.-˥z!a1% ,!' jlum^mJM$ęi:\mR:ݚisDZC%҂ eOq 6G)@E[mp*ڈ׊ja! GIQg`L"D%!Ii"RIl0@NhV i03FsA 4^O><Ԋ<Uj$ݗR*'ٗ6u-[Xtd A0liWcZEݪl ^H Y6aa:B"ĂEPicF\6}AizHƉEGh!YPB@:$DE( #ߪ-,4lJ[2kB߭1zV1ffV϶LQY$v|7~7buw i2ەJ"DQ d8)3x,JvYlÄL($I$V@BǑ+_ևyR ~Eߡ(d򫥚(I쳑%^@1VB1ȹ8L~ 8m֋wosw|!Tp* ɐCF?Hj"!0P" c.Q,pʵ*"c`}CVymUORѰaRH9jIT"T@SPָ~CӃY 3ja&nQ]owKXĚ8fBep*&$Q[uB`BâKc(*ZGړ^-R$0D I"VҒ#]92"a"K#Npd# .vmwR.W dMzdŻ ȭγ42 ]lr`4n@G#I,EBHukJ^icH󂒩Ò4EȢp2F%( jKr(jbDr^}Y:g8l3Ks"GDX Q纁x `@v!'4W%IdM#FHKVE"*)dqXTI"r"@Iע@$|4,Ju("E$HFg;mٙ"|Ӂ%#?דdYJ 2ϛFYEh#FYWQ L0!#К@āvCbY!d+&NuuuRUeRMI%VM%ԞeVU$l,!D, V$#A*"o*i.!,Tu%VM%҈QB(2k(5&~jC18 7M7m+M4UUKZ*')*}pdi.D%Q] 2XM#t>OC|Bi(C*Yݝ6..(/sM;P9˿X(*r]v5߱"%.i,UaKhq{$-FL:NJWk xu[G? b%9"E5(LeZtɉ)m_BoMߤ Q,zfyIE_ؔ6p9rYS =Z ֽY"BeP\# $}b! E#452h7er JJi>ZNp(HYܔ! Vj(ܐrBf7-th=jyv(2r>}ufdJ2*a8HƖ)X2>b3y1E$fL%0M$6I1dIĊ~lQaNץab3 1̨a&^S!-B\"_D2]{Q,L/%r>ɫC'oUyLWJEUx?y\i+$ ę ]ћH|-m2R/$L@eBQ2aJ.1K]6"۹.2Qde'|=e(gɻ[qEb$f!IR y4f3@q+tK:ɓIlzfBdԉ axWL/ܶS+2ZY&>w;LSL?Gmߣ3Rz@)%Ѿo.for%{< 6Ay5v:eFbQC^4 |$AJq74D]IUXDщWTQHܢ0Q4Q;L, hC$:|̄GJ9TBGK$\NE[߫lVF"ڶiT?=x^79ܺ8,;Xq6m4)JICC3oeӉA4pMgmR\bS1ls5R3Kddt&{FH4]LQKM:[1&>>ܺ^Ek3)޵>6t)ׅeMffq, =߬KL0 G;H(RB3 }79Y-KȠD@F4P+'^$i4\+sJRS9fnjgu5J0R-MvJL K4[-SB$hˠկ8u=g?*iEյ* u3. :mHFH)1]-wML|ُj;fMBhe0sj@YB$n}@2K*V$K&AH˘+q$%DJ䍼S]%lT[ \!X\Ii/4ʟQf$Z@K/A3pD7 롈.yjN崭lA&eە9?n^{t[,d^S?%*J€l0!1^;[:10ߋ_ɿ2GGK10(f&7Fgzr@EB^ΕG+:VЬ˔6JvxBP80,F&LS`}l#Q.@h04 9#7zb/%X d$[DLHF@BapwIHi|5~J@-#ґJaLN5V[@hTO-Ӄ9Wi'ʆg)m2SґrR{ J60Jn*4ŎL9?Y; b*f;tUP%VFql+2۬ !(5Q$EΗ*L Pi9M"ntǢPk;Q{}soF$P2ayݽIB.8q  :-@JD#  N+8s2bs\`M#WE_@(g5/ǂa#ůa,vAh\, QN,hj@éklf:47*9C⿱*Bҩ>=udB2:0|-2(2`tAtH5}"7*g2vh f$i!6!8i!abV5e%TvI!8mʜȥ{}U>PLl]lZzo3`,c`cWPcp(;!H@ku]JNLrQWUnG"~"G HNadtوf]J:&% AD1]W!0zTS/m,W =Bp*PI\}]p H2X s#P. &$93c'tH'̳L4M \Cztfκ(yE:+;5foJysbGݲڊ}~ DTt̑% P1&$&PW'X7utc7@ E00 k0;z3Q YY=2Sy縞DKs9EYVX}mT?ny.S{^JI_j3lJ` p L`!PbLæO2 âS 0x$ _j[Q14f>Nc{ӈ DDc@VAEHh=qOUt늧ݵdž^/k%u7LoG\W6"JD,HP JU}0 Á0@X>(BW.\qH &u@&F =F312h,Q"o$ZOe Ab%HID/xԴ֍Tn?or}jcx;vef=ˉ38ی6e97u x DP,Q%<\a , A?? 5 &Z'(R03R/X1)FuF n;~5)DID:NN Y翇o[&"e4i TĞT:h,CFSAJI #&P+P ZA&ZϛBˆp1ЁQ,E0DcqS<%ldkrP0` 6``ABga`(84e%3@~m{a6YK. #0ɻ`(^K&ԍ^]WqRP.F&q:+A)jl!I۔XA6,Ւ!M .5e;瞿TQWkEwJqӔlр,):Yh]!8b0=U$ Me( u%ID@L)af0k$LF31xg5A/a&Q-fsi([C "N6-g*ZSyy-oxe`J+hײhz2zK(LƀGFAFXF@!iX[u H\Il+җ+E6[C| AY!$ 4+7k ٽ.n}kRMH<&ਚ*kc4 ^ Yr^0S;2Zrg("}m/DHM6sr.B"3vjYȺHZRE6c䵱RY&^|79+ ͇ܿlϦir*5֕8h`A@Q$@X"iqGy@QX2FkK9+\HcW8tA(9$'ɁTH%" 9d@mEB$5j/_/˩|a$W3W2/I"f!I |R 8u3 @7*AG1'h{&4/dƂ Ȋh;ni-f.1` (Ur,~Uzl +|L@t`3A2ϔ[v=%f1zM2CcK!Q$Ujoen)eM T +|.%=Th&GM=Q(Q-7"" R8@¸1qǰ`0QhHe** LM}NmQpZK"05%=$n{ZO˰eAM; XEXŮ=t*fd.I 9'跚B SYvY+%cgby$#NB$Ƒn^?e"S&0_M:I#.[Bဏs9L'u9KCj'%֙by_IEG"@%2/b7:^Vlsv]S:΀+ZB.0C9SJA10}hH{& )l #$p0,.,̒;X c.Ki.Z!ew ۦaXuKs9xnlyɯg6$ŔחX9ko$di\L͵Y(DK:4盐ANe:ìyXdj^iDAɓ۟&QθvܢSкأfL ,fVEwHTXJ\X 084 :&ɗ&0hNӛOqVCڶ n-]f(3%S).UB,jZYFhZeu2h@E&]CWo$xg"\\*P =]Of/A5ws!y~;Ur ̂Lp ^$^HDd\nqTQim ѭBLylac$h]u"N&1${hyxdIQ%߇]/Ps+ 8`4P+!t; 0`81pr& NnϛIxaI㢽Dɚv(u˞RI[~G-uIů IW)-|: 0`81qr֑@*\hF<6YLQ򰅧tȝAGm &Y1SlYFFiT[ˣT9#gKO"0!ΆEW[-SSM &9%&!wjF1`lԣ&z{pa4*|3ĒK<9JEsc΄dձ ` ]"Y51(#R']c+;K12zgy {f&y,#ߝ`u&!2^OH>۞D辛͒̽ bҘEǃ A00 ]N;I&y,W梦r5o'%_14ɝȘ1˵hjFf&k|h Q8Е@>d$%ZLA`Lh53f& P 0T#v!3R"R 7 CV<ȢHaX6~]:du2+R12/;P+ h[$eW咺k5)[{'ެֶEeuA\Naϟn0ȀARCQFNLAME٤T0 |1P :8@ ;BpTԖ-~)&OhDPT]*L^V:rje$ZR2"[meMVߚL\B7zZ<"S,p|`L=4ЪZ)3r~Kʘ- 0̜ Ư\sHʷ' 3(~/~EQ}U[8muPt?&r*-"|P1.64L ,AO7 +L՛1c c<⑐[YzJ7:XM;zl$]W"nŐ@l Qii*+Ʈ̹V}P ȯ;OT*fL#D;][khY4,Xظ}g(w]eG;]W L 8Ow1 FGK1 x)4/i& %xLFЩ))*KI,a?VnHԕ,yqr9>7 DngeӟH٬vti mֆ~$ /ʷ19^*dF VqOEht~=kZb j)qqUUUUUUUUUUUUUUUUUUUUS 3X.F崣 0t3B<11cs][jEhT%+CmI7=cěd`(80 p%`B8<2Rq+@%$/T=pz7 Dn0nfis˄7DIVyΝumNA)sY]\9%/gH%5B*K00 + G/}()qEA: H<;r꤄"~dnr0 EZkܣ,$hs?klIC \tɕX@( b&0*01XFg0b8kǜ?mrn5h|Gnl}`^o+K ~֎*UO "1xPk9ekf5ƭk7J._1/fo-V ?t98c=^(N=uf@@9 ܙ/jR8D $2TfKlI5`dȎ4nq˸k[DDssUy=j C՚rY5 PA֧ h%?EC . ވIFKI0+(&R) /$H!#Txm.1n7(?$ P`a|>Lm(A"#yz9EFj /ts$*7DlAn/0cX~ج?Xn#,MfPpSWsk MG95n?Cr`UhB* V@ pK2(0lQl#2 BI`P!frӲėY^戣70yKͯ!{8Yne=xj9M دCcMF<դ!-Y [9R( @,"\فn Xdc5#JEKd K AEPD8NV*@N0DGnU})_%-97LS+untH37un|o~p8)gF@IfX@  $ V'xHso$@9J2QD%Ik%TnqLeᄷeAMꓴғ+/iʰ@ԉOF3I0H&R% | 5$V-d?|/v?}.[Qmx@`2F @vg g L c ! g[T@#Yh^/@^OnfqlFa6C<}Kl=|!sV(*N2OWZ0px.REr9 fZ00 0+0Ib$lk=_dP7rbxU,eQ vv* n[HGJ.,n d|);#M{_Nh>KW+ΈX:310G{i&#$ʀ"$'%\*7(@ `^`-Dcs+N`? _G$Fq(ҩI4*:<_6r! wfoHMH PjVk;mѳj|C&$?H!sF4+%݋yCgZ',KjШB ųc$ϑP!pPL84*=f ,סYq:`wA%vze@rD G&q=_H rn+E/rn'fW,:60,2}{5^l;m~Ƀ(0HBF@c@~i*(`(.D,(SWr qʣK:\拒(8pqBF(a1NDrFfhTíT܁sbPr:C EM4)bpTPtpE,G Ė5g.>?D/($  F 8 U/Ԓ.D mI"DdbtY$ԩ6;tBxVB+mV /='[ &QiMH֛su%-i>$P40ψE+12&P4!/m$0#Yp"y Uޤ">'geAL:-tqfS@K(Jpl֢B,B(H OHZ0ys,A>m;$8+>~̾-*@XCGX(9m!׀ BǕAad ,``HkRPmb"pAi4WH0i%+$bt1:62,q!O 8㉲vl\6M6w m\ʺN= m֙9 &``4`=8R5OhY54GY"t_O.b e&m)+D Xix"䗄"UT ){֖Z-_SxMO#5iz ӳێ`q٫-i= rYUbHAX/`@n09`b2!d C" T5̹#XS,N.N@1M !B(jV_MhAoPй# \JDF+_0¢HkQ,#X!M t1@\0&0qR",[Aov5k^K\%f$xM}6gQa hIjH'˗@mךCy63Dڱ_X흳LK2p'CSK|b퀪 q`Z)8@uc,B'PT]It6Ǜ,k( Ԛhb!I؁ň\cMI8-D<8c0l@咘C:vD@H0" d#6͚$JcUffF  FBF)Ţ M鹲ܧ8<֬ h Q rRbk 6؁2;}/8MEbva'M, ؀C AN'+QJ.<$ XXc3` Fc)# bҽUkjH B\"'d HĎTq"К&yGό s\FSt75?ScFh*/ D_K}H` p  MSfL"0`GLX@ENd>st""ujSuC漐J+8{!UBV"v R 3+( sU7hubSuM=nGZT[~;g ʲ9c:%qSdT:z<=F10Ȩ&Si/4 L9Fia#8tրBh 4<}9S! R30, G_خE0D3%$= *$\R82Q%*!8pxL =@2WH$P夛 1+"rǘmQ.rI20bz57sOz\^:cew6̩7$`!P `0OZ* 0!$ !u2$u?]2!==Lj^[9' 7S椏CN`2@i3n A 1Y,ZxQcv8{9Gu}@A2`T`bX0( C"/~qlQ#У(a3jEG1 eh^ha-[> "!K$;i'8m3(yէy9=` 1Kyծ!iKqS%#"@CC&vْY)fX0@ `@Y?9ȱ*lDI\T5S00I\d\*zN%92i^mg3CJF+12X&-|%혠E6ґ.m"is`"C2ny"ebHe[sԛ(*D1WN]iᦓ%EY ~ R%Lp(HG\j'&3PTn*y{)|"E(,2 BFW0C2#B0'450A0-T_S4cP7HptgNi`Io|9F <8}">q.eY zvv8׶I`r_گv/ yT^Pʡ9׭. 3Ӻ̇zTK/vz2 3$OQM 4pE000~iBđtwIh = y5iL(*Ne(&U_U%&D/-I,VǺGfc)c*^pj^_^8@ar0o4s\j@-qf̂$A 8( mg>xȦ*Dx'u?ʩOOWU;U5^Ebr+Dvj生SdRA(H OeH  XkLe&!!㙟$`*eEIJRꇫJA=͙H3@ #0+Vn(QsU+g>i%Pi_q!hGr%]Z%^I'7p;]M۸|ay*[HVڑC[e>f[z% HI91HꉎȣD+͋f`鷡D2m: L\Q ˁS#Șa2Qɔ4"veeQɲY*J_|)k߬!_`taC›)MH-p!)0A60&0(<2[ 41)\r'M$j9IefVWeKNLj,P@6`azI9%M*B ;5-Ƭ\ RVx`(f F8f"G5Ϋ1(bk:I /ڄڣЦLI I]IٱHE=)haIRn{2JcGu6L @ch."a @p UEI(&8U/&m"$Ѣ! 2d)Ixp0fFL0ь420$fߜTQJN8)SPGP"(!v.fEx2⺊c[n %!Zivg&P>Rں%t\.LN2!+5y*~y?WW*S468TMRҙ1#L0CT⑊lȧ5$@RE$%I'cLS%X%Zr&gAM MnmY5G EftۼqiC] _Uz2g`-{T^ 0,C q?> œ i0Fu3s Igpӈ8ɥ$|e'[8Q|k)ziqh4% 1Es&9&i19 TRekc&:D ѷo 'd̿I,L\ 0`0`3cִɝ"BH@qrTnwS2㓂*P Of2⥧=V``u@8L*%fd h*e ԢiDժ!8(/MgRX=TRGThAҭӂ`~UdjMa^< 3o5" @90 0C?2=+ˆ# * 0 Nڵ+,Ҡĝ:daJ7ɇnMMYaɣ60lkDvې=5<{u^ | fj+fqey`h2N*hAPXP@00? Y A1[ iv qZXSH(ѩҼF++BgKە<\ )cLe@QG\޴0U!PY`(&db.IjO2R v /Ƀ@ ,X42!!hU[*QAiрV1fթ)f&%`0 cNY#<8 @daXcFst5BIRg=&U REb03wl7~2:"A7󡍲9Vֲ|@i¾C _؟Jz}>-@< `  G,+>aQ A |9f :o_UJѣkL5 R9'^cEANjdJZG'es;XPxQ@0LiM,]fR:Q9l ;=6TF>IGU0ONY$0˥*D$WzʳV }Zq[|&@FN )Ywy@G׈Dū12&i|ÂBe$T!e%[f52,Z(fh<l@`Z 2iHQڈq=RAvi?ñ_YuuOhz;6S$I WjO K$% p9%ޡZXP baA&!I@E+`0`P/Y)0eHjB MQ(x^]0ݨ%VN!*ƹ~RCl S3d$,68Z:2$zVo~gCw+@T‚2DM@0zI@E+P`S5r85'xREZ!yh\q6VlP\PH#ρ!08E]EJ=4CN,t.EE Yn9 bJ2ntdZY:bݎѾ?Rw#3,Wn:-UToD4WHu,0IIf"gVUA( MB'G&i @ (因* /&9޵1ss~Fg3tIwJ4iÚIǬ^ohG`)00X4<0'0)@;*y"g.՜"-,nM&ȚawRvK0LfzVY٠aI4 I~jsEVϕW<̷%SpwjIh, s> L\2bX+H D 8  0g TLSqR25lHyqiMPI[$cMza? vvq)4wm u+bk3vʎދ{_QߵB!0H0 002d00@3C&2S2=HNeUsl<<2Vj&u@Q릊*륜I4X0U_7M:RI)bPRh )MyF2igk# mxT6겧rL߽yMbߡE 30@0I1+ fJrsX   -L.3|,&Fͯ$Dd槡 3$:RRd)&E&.JBB\B`6JRfGGd9؃n~! @L ` L" 7)`QL0 MPDaG kj84wC*1Tv/IOO1\b6>v6Wf`܈XEI0h&RIƂaa`\e@ -53cPP# d80@ lB]5=MO$4'q*E2sHW?]!j0| ,l֭DQOKk PV^hٙlQ:]x8bA(m.b2YS\p &FP & 8΃OŊfڍr|cUdkzȊZ[25I f$)Y̢ȔT %]8ڌj349mt2+cs F+_H‘GkM/&0#Uĺ\U>!` T!,t3bHbK0-NdGrO UuD% VJ% Laɷl}!dK,G}s.g-%ˏӓٕ{mZ'U$M&@7w^ 2 }2p{a-[2Ƣ&KO#Lx!U"NPx3ZpW;5ܢ{o\"klMr}|+$}OƝ ک0T` PeC@0B QYjȝrF)<|<"MF@V66eBVUQgHD`Hdbiud;4[OrMNI*J,Xl yQ[7T# Xh>nY8jdzlQ"GT}T]!Rqd¦gpZBK3 b1V㨤KeZx ^mN<Nb&'"=ks}?ݽbmki:6ABqX D'à܈DKI0Չ.,2 P @#>ʲ4F,#Fج>Uth9B3b!k (F,[7uHTHNFƧ~GKHi&~Sm}V"9e(# f_ ir[N(GP8BjQԅ2 ,nBr|P:|0Vz($ J!ȉY a[MS )ZK#2(0E(7FȔTXBHBb4l 25G}kEꗭb\ #m{vʩi80EhRd2F0x=,$ me2. y(HFDbgIV* i d7GPFPl DG1 r!*I;Nq >v A܂*"E"I"vJ]MR~Sc2fo,=Vdּ~Jso$7TYxR q eVT1:999^'Fjpr%$-/isZ ۶BxyD,E(@, ąHu,rt*D6 Xuu A4h I^wy!T4VhHʗuBa]Au|H# CdRBT3M≰aA<;OF"&l#^(fFV BArC}n;I蘥 [E׷?w/G@= +b(W%U1"D&m1 f|G[!,TM%'icl]д l2 ᾳj=UI8AҢ0PK6eDDI#\RX3ZP D7 5@ļ @UF5^W ]t+FTJhD3MGA!B[Ī;)Q.\QD"r%0JXE,% .&;ys˂ɴq`ٸ3iK4ÄZCJdaRSUU7n&ЉTD)+H%#Rr4")?wv%H"1{EN!6 L#dq*@8ZpTTrBx$8i Gg!)aAvE&LTSlh#53fJ\ӔBi/5|$SQriY,:L4Jqt ȋc$ E%El^.}ơ9E%̈cD )4&qběUFf_;q֜ηh|i̧zP  j'l%4׶jr=ʖ(TZ|>[ug';|3Vt W עlf})-`$ (@50,y3HvwJSNȼ#ĞI()U҂0{L*ltKBċ`~ib !AN0.+5g!(#\{Z VE0ӚQ "ɲ j|JCE*: 5!v#ŋ1 2vh[~w#7KNHRq+h6 RȘFLF93zI KR 0te)3BHvy$]|ÈDYb:c~iJt]K۾#Nlvg[O]#,j3f"2DUpLwQDmܱC! KXFaaUZd^mnXHK!AXF[F&‡J$ %`.:*m0*jLe.VKF4D\$+(k(L0]!ݡostYU;nב䯅V8\3r0DR*2k>RCBv+ B,Z f exJI #5h:Jj8kaXuN72S)IC%4D$,trvnN>ɶ߆_u%KH2إ!&*RMORbďmv\e* 㵜n}hq1\e (hr,d.*Rgҙ+}L!!42q (n]Sj6јkmQR<]ȵf7I9+ w!=~T3"&_]'cW*AI}'&D6`g~4|z600 L54FuU J.yt@%M Y%6(VBX4h>@ej4aA.r.ǖڔ>HGF(I)0 6A>^@S QDQ^V70ہiI6J4H &Y!,;hBc̨b~&+4D+n&be/""tHIf4,R$BR4s, %kN޷(۶D1ev۽DԮEYq8b%&B0 % @6.u Ј`. UIBiQ v&J@)+h#75T묍(&ypGH  Bse[9֊W))4,1&RuK9Te (ƙԢOPb*Q,# "ZRű9BB2+ErQTd} y,a]ps;) TrlYwEw QT80 u*fE Ͼ,ހ?%UNS} KbvdQt!eҵʫ6QȐdx"eGi .Dk(&("Imive4($ KBJEYb,QF:1TMSZ"44Xn /OVgX}Fl.Q6%*B !iachPq:$brRm,’( 4̲ɣF'Bϱ"yEteۊVbb4Yc< ,UEEmcSjL#N x\ĩ)j$Su&$dhSP""ZS zYJYWj 9*M;( 9C qe "G 7X*6Ѷz|U9РzL8e5ߕ~A @L+_be)%A jwd'^4UI04MwYu(5eq5bl"#`JCi=rI£=q1,D{&]|DʣYq}!' !q)U&"lN*̨Zq$-`$BH428pj@țm S,B̻R7sSW7u3Zqp:D6#m4nJT4%ܼ&D*& $,AU(JYT%D%x2M*`pBҫh ĀRHOP*ģcN3p(I2}VF(ğ1}sk_|t;{ޭ+0p )g3B&C`d XsگMkbfd Mm(۬,#-"l &Z`?ħZꢉ,?NRDǍmUCЉ_DA5җ H%&Q$J\9xNeiekrg+ʨ5)i*DY4}ʧه;"Į:tPB;%R̴㌬mdb7?O5[@+"]6lʊ6bX E11,əDB ԣ[&{Y`͋BDS7]0ao|>׸=,fhm8^9džM˟ ߷zܙRYeXH%(j"4jFѱ(C)#0܌ЯMFLPDmeKL#zZS,l5#B[ΓoNIȌr&zYI,UYR0]ȓHz5!Dܺ1<^Y\cЀMqCjĺH0@U#7E^*AhRFm2֤ 6*H qbDTxP>Ebi4&42CӍ~ 0sAWrYFGw4hTq)gQ )~-*ɆiB$ܪDHxAIܷ*H8uDT'07 =.yS tK_QPg#2#4ѢcBqF0aY(2A= JCZyj(ƪ38PtP\@@5oˀDt]E)pk($RmcsUHy<Q 3*O]i[J=6z)ԓD4QJc8QzdHHBgDzxjmYC&hA4eb*],AH$H#QMel:ڤѢM!De?t`&F'`hu΂1[" 2V=Kon-S\CbN-9Xnp"@q'gfu(%E(!HTAh$Op]8\Es'NəĕVTEaR?`QVm״KnE4hX@ #iɀ-YmyS;  6)WRF&y2¢Tv]q$dH!&$Z:)k)捣^F!+l* nSmD;F˨B*Aɤq v+`뉆oڰLTLnB(H|0J ͵Wd6*d#08AAeb*pѲGsiZFCh%-@f $DDR5sb pHZ֚&H[tets9\\ I)(68$ɢS=~5)䔚9UbKF*PY AqBFRrJkR$vYr,BF.O4{4&q藲3M}1) ygބMLR-{X`uh1"dP4nc! DNqz{#f\ZWS ga쾳[{rhK7Leu<@HN$̅+5 -ˑIN8Q9;$do(prgzyȷIH:RӳEӘLun'Mk+S ad`0P3AT*aDo8@LL ĔҦs:O"#ׄbI%d.g 4`u1g>YmKBˋU0{zX¤XrkC^ebw2Hi@Qr8E륬^EGώX0%)O+O~)0^Հ-| *GhՇG-N("Ź墁D M3 Z]n%1@y$TI;\$TBRTbP%FXCI+V`RM(γJH o:exAbsnhh "mR#( Y`-0mڒT˞BH)Qc-nZaSwM q$ >F -_&3] 7yۦ]eh׮ 1Wx[Qs"{"qdRF_v"$2E5_tg _ĩ)8$SYӊS0b_ gRU]C|C&Xk,j%i LYَ2{VH9)\qo-8+Tan-L^ZHe@t [ Ud:}b,}M0E"r;[O5Kr gz@#*Q4gtDw?:@uJ>t_\, (qG*JɓB܇#gbgJOmL$L\/j7Q3vIFHL2(ȓm [gM^T#CAAQQ']r4xYZ?GW0JhUk6pe⬻hB'QBO&Y(/2F+E9(+vߨ E4͐6i2^b&VF^W& WG U .ɗBڵ`4P߇5)LEvV[Λ.* [h ICTZM Fq . &QrMea.{rat 8&yBYYW$0E+&k`Cagik( yYR Q KLo"ۭVxحk,Oh8d-baVRd!@4Se7I$ME"hH!8"-*1c$tXl2%&) rI5ѻI ML!(ʵiL`Q텔2kNڶ#;))FtЀ])4r$Q!W#$B4⠔x)p L5${XaK eZ &Y힐D:CK e!uHK}ڷ7%PyCQsKzY>r"^b Qi2xzAht{I[ed|b:x3p*i654FUI:V$dĥ‡pm.q /.wZ*et #4HΒMB%& BXF r:L I*\M,$ɒ: 5'"UE jKy%gcܭJm7[.mR}\O#,`(re Z8+lC# DR0@Z18I_\)B:C,R!b^r%d&\VKř24bRdpPٓ ^ѥ}&4AȰG%Rudl ;+mI# "K;H PD$0ʆ9p$\LNpI)+42i"FѢYыIѧE 6Fr@&k)؀#z-IU< MXlr|)/PvՋb)48%#Q~:4b h ^-ܩrN!ot>:D$J$£Sak;$i&u2dp:JnHJO] ^$FH 0D<`UZZy4%f4;N{9<7ls x-i2/DSD  !l $dNr',NOMCPg`$OFF2JP2:!Dn( !nm+D$&rdBUiPdqm.b1*D[MdzTM_ 0Qt`(3x5GO+,zb2D/#K#3I?]B 8&n[sjE̹14&Qcؼh\1WzAw%:|!X ۅRм1XنH`eZ ZK.f}"wqQZb氭qC6xҴ֌W ;*bBqd)Ye4yrIXY"e"EADIX@t`RR(i΍ȟgy)ܬe))5rx0QyI.TbԿV<ЍHYq=ZLt'@(XRY0lQ{HmD{Ʀ@Q lbRvS$ZCN-޵U@nKI$>ԋYSi iR)%Eљ2ҍ[*ETR¢ۜD\PG6 0NvЊ$zQ cƍZ3#kmU=7"V,s g鲍>yzum ~Fw"uL@<CdȒ`SN<\h2(k|$5ȬY\GZ\X'91F{L'n$ԟ9{o iK=f{kG e 5gy6 Jԑʺ_%`jkؾ,@)fkB!"ʎd]t l$d*nb=j$e$*>شM$UI.!|4a1 >A&4 41vG.f )i-5%krQKl}+Mr_ĩ)*&kqyI?eQ :D(7sB#THDmbI A$@`듞lytKĮ9XBmnTNRsT qXg*HLXCV [EF5,jDahUxuk(9̛FBJt!XD~>hO6n(@6O((#hD-bbTXt M,DS(tRfL >7*Dae(@IP%Ϳ9a KM "M/DJ98SuýɘJ<$n[D 1(),(vnU0e=7C"@TaDU6SЙIĬ")P&QaҢ dD-<ǜqȖSƤj20 XG(#m%rv>g>YSj2{FaN =H͡bL *`Ft6)O^…B& *B(bmgɅK"i;3#K Ͳ^5Kd#mgJe1d>2&CMcѷ3hdӚmv$&f-*"a$;9&W=%O-H&#aRX \"QL2DO&H&]bi?P`BD"_\j,3Dm9 H;6Pv("<̦YFg954,ZDL6Y@ 'hV!MrB 2HLrRЄ65GbGY##;azm8&_ĩ&m8%#;Ĥ?-TC ߿у$i FT])=$kad/|pD^N]5L"Y-,yChQ$YZ<9z0)DXb4Ĺ>s*@Ì*;z :y]57- R^kϖ[ȫHQvA͊yLĩhТB(`hScI=bH",6\ąˑ)MMĔ DF";Q8Ij@EZj$)fQC*c 9&0V`tNv$8P1:[!0Gh(VD282M V͆Cj K.#Vyi2]RQ$@3/0W|LLDU*}YDOŒcȉ ID M } bMn9P s\[.\*\ϝ,7/0#QcP,0AbD̒n)&@8Ӕ P'dFMh)$SI2wcϵMA4PM % 1[$,YndM>؀%`D))3l8%&Res 1+Ta,8ϛmmk]b}B nM<fCDBg dDY jb٩#Nb.GI,| *VXy)qH&*ً0ElLVTwM0l)pZ.98R26dH]w:bޯ=3rmރ!&UhĘFFU"V ,D4B,@ˑmy(֚|ܬm=a-d2 (H8i=h֐Ө9-)'2:~B"t(Ք{ajIxy#5 +gA/9%j*u}!JmAdN i즉Ydht@`*Pv jEAdeh<Ҩg&7,+BlXa M̫cj s)fb4E)!^Y [W+5kXQk`` Z̊њPeDE cLC˲8Ao_ ['Mrxd1ĐLƒ텟/2PB/Q%'25<vNvABcV Ѐd`ĩ&긕$ɞRk{ X1b#,qgrZՊūT f HB]Ҕ"L%u.!XU z$ah&RDNۙTErQ&M9fEb1FVi ΃4_ĩ)4r$Qo{J]ěYfealϿz`acqA(vKմ|} *Nw!Z(Dh"_(1FH|BA-T*;QfuyX^wUEaȽtptw*]$Iηމ|D!enOrS s 229/PB ef: 㴾U2T\E 3J9w .˛JDXi].EiH) Dզ Q$ô &q (]M{(k-݌z҂4}EN (wD[@(02|8n g88,vQ*Va9HIӑ'Tic8nKg%}DڔK"rbdhc칳g%Y[^1[=|}N4h~ tcV CIr, ę!@H)dj.QTm`إi$Aj*$F k4Ͼ6p*{c0U'M:04dC4{U'Py:j4!IzJKfĩ&,H&RWYbT9"KPΠJB /kζNs.hNM41U8l18,fhk51H`3̙"V]emBN Xdjb5vKʔXati$"e9 %F$PDd #VHZIѨUi;A-4㉃(MPu KHQxi$p?7yÎ}5e)(uG)ńDC i;"Ͻ񶼕wo]]ۼn '8*J %%$&ZefehoBf- jkIU8YpPK5Tő IΙ(bܙzOJ=Q+ؔ# >"|:sI# %FTYdR|>[oJ|uMѓ:<Ҫ !.$(9"0<"EL $HOBmM elu\ [(nX2₮ Dž(V16J&CV c;c}E;T"IS+N9bKN3&l}?s̘y,X3qD>g)(Hٗwͩ98F"(@Țd~}:H.NH&ra,1N42 ] b>vϨēNNdh*UckxPVqTPYaFDwHF0s̲PL>JvSͩ('Qbh 4(DTpfmDzniѴ mk$&fGXWB` ;yh:K,%N33w_lVLI )~fq`2|DD-3l]C/&! eIJlHJ''HtVO蜔3E2F"`|k$FfexBQHmJIΧ6ɡ%qL9LcS%jACBqe="j4}4eiHQR鑓X ) ȑp(2 HagRbn+:\I (3D|84Rq쌿EUUaJLj+T҃t]ĩ)4r%&Rww1`)*⚡FZO}T_U/ o7P%B#H nJ]yg,>7Z@m&q&G@eUQacR4Q 2gT_ETPy\Q!$޺L HDG%FRD(mb˗ <%P870_^/~m*@حFs柶Mo5m{ \V=VSS8%J,@ DNe F i+Ɖ`K&wՏi( tqc  k(-π46lV 1GERT՗FR%QHGdF26vuSA \ՃhX'PQSJ` DdrN3IxԑHd&Dݕw=otd0M4YD)4rL8%#w^[0"yXrd֮9ƘzhpW!(D i9^DbChMz adt팛hH-fdl¹6P  m5c=JD)"3V^WbwpCK'*i:G$HZ=DD|UMg4}cr5*T~'GJ.@cc˦"j`' [f|^" ְ2p}-k?XJ@bUǮ骔qU`A5iRzb,y$@X,@BBHz:![\v'}䈇B"`cgvH6̴ Mzb]fɾ!X 4~7! F a8 !op}|eNr4`ĩ)4r$Y$ 0+d`U:=H:sD-3vs&1C "$ ?h$BwE2ڌ/ *l3ϴ2B$MUw(HgD +s%NIcZm|0Z67 t9tz9Ijbӛʊs{Złzkd4I{1RIj2ym,ju=Y0K|@3Qt.W]RDMZxz&[B'I"|QYkGt1w32NMETŚ_<-KjmfxGyseeufb)*y@OJ$!$H=|6BIIy"ϡ%B$Qdi@[YfD 52贘B'D> Eɕ9ԙ {J!XƢ$D;eB{ a.E$ҪBC#AWsl Wj!C tjN2>1q7S&d6\[>]$/)ܩ{Ԅ`EA&Bȕ$QyyB?*&u|bAhC2#Ds#'LTf2!dvlh(yrmZ#h] Na%ܚHOѲ0/2EvZ$]v+ < &Ggg>`|ѻ$ 9`F[MRL6foufvGF߫n@R r淉)3C N(N# qr `,/\ L#fS=0'G2,?h: QGA؟IZatX;@e#KN .hqB X^wU6Z,~P)_TI&K x%&R qwe"݄ykޣ #*4e6.Nx %0IJS9ѢF%6+ h2J!H ,d& #K- #r1/zomԧ%zgdϥ QJ84p_%gaoO蹣c܃MΕ:! $I٢Jt3nfxF 2+&WC.*gu"c q D4!lǗ:_=,Ӊ ib# 8d"7.mgVa3a) lid(G~ԛxn뮆. CM lA:$ DGI!QS,PbPDk H H]VI- Ŗe9>q:Ua`;9P}ܳylHN;ϸM8B "uoo~4z1ӊ m ]P3BcJ(D 9*Q "OOdQ%iP$I(K(oH*,JLv"d9G'YHI<.rڔw-unPLma94WD)3r$ReD.T940,dŭOkn\-EAsJaщBtK[˽q  Īpڲ :te+y&zΒhAKegF-1S,49iJ.,m%8 ((*k'M Gv?LK@q+"B$+FϻM: Er4g< M'`Xs$! Td%16]{ iώ<"ʗ;6e2)9 dFca;:N$m( (9(1wrlji$̓H@MknPAdCh=$JH 7E7t!n%6%StmkKu}nJ>̀Z&%&~QaY$B8bdi ́F4"@$d@xeA`s$>u L Cm$ihM$W 54>ORnX"@bAD$JtFA-п2M$~mݕi~+yMBmӘc1$ (EWX̳ ɞ,{̬@tE&c /&d{ز'FD80pmVc[ [Xr65$䈏%,$ZW{uu]^,ABb*,'2 FdJnMt]^8bD*@BPRqNxGd>2 mQDIk A(@1Dj#&9Hu'(*Wx|AYApnoJq,?X& e@7q ;$e CavG1fe/Kbk!\֮W,(QȢydx f"HihE:MT+=G[ iZeрWD3L8%&u6䔘x+5RQK[%߫)uO[ *ĹAbURuBBCDzK"aD"E<}we-A& 3O 8ĺjOwE@hɤ-γl8GKBߞR:5z, 6!J(p̌2<= bD?A@]xAeV(BX!bG &ifS(XcPдH`b7N!seH Avg M }Z)B>wc\,źVF NE"C;FàCN+f)1T 9RMS2\`L:}ujMY@H"B!Q,LHaP:x3+t`t"q BGp2Lj wVx&5Vb$-Gf}4|٥m(*(E U%b2FlC$դh H"^(`FT$#t9266&%,N4 H(-l؎#MlژI4% yf*K$IxYE8 2P$(M'_DTu$b&r $þTN)⠔GjekS$QW*ZONO u:YÜ4ok*4`+etsWG 20aF&V(ל SFMQ jDQd7jSti d,AdJ@R$H?É h<]/i\{z76*GanP(_8 ~")+|Ȥ!Z ”DHPIv/yT}j'IAiTCy]1)& /]8diJJcJC/<[pwCcl;AX2*`yUB !$-- 4b&$D^nM ?4HWFL L=:CA}G)0bd7IǤ9HDŧ]x3R fcvTf rfAmי߿tD6MqfmË XBilDqw$kbQ)S0e%>DM&V3U\̞ >M+ ]g5 Ա5E#OR 5FP)&0,$R&j[hwvANj}m)5t7πA0H$!lc &ih &Hnn= EKO&P$P:0))$F 6 HQ^t3L*}LYSwc_"^R)sgwwdŋ>*qv6Ro."bƄ`z3CJ+GLH]C(M <4|KH _^C=Meg.ds|)'1}Qi2d5`4n_>ʢkHұMj-7}}w6^S5:|nW,uLbK[÷L䝬@Hp\,$Bm% ƽeH}W:dIX|WZ(4AF NXI'9DhA ;,Q$xjT$ $H%42 DÄ:X6%†" ש(C>\l~q@$$BHxDah#.xdZC"4K3G7v(32LPCg`DB&1*M6ӥߔEb ongs9GHo VoӀqRт]D4rKx$RQ2-"c)ި"PxP4xBWsk?n D $i%Gr`-p$i1XPA# RנbQ)3 'BD6J9oEv)0BFZ'jyxQ}wn7 0C-@AT 6i q ;C>y'֣Y[$P2R0u:t:.$fÒbr DVڟ-=jR2añŌG,JF LTb]5^߈u>;@!DlTD 4 h"Xɋҹ^-ĕ%k9dQE%D>yЄ˓"Y:M&SELMBiAMhW*($_jdD"3M@1i'n9',˃8>!b(,cdP f !*[8֛ρDN A uϠ˓GSzڄ|ld3U6 dcOYIfZGRc5LRvcL04|vyЂ4WEH2rj&Rq$ Yb`cy{gc:#*>!rbA(@40`:⁂EPh"GQ$ m Bu/GE>PӵhdYX$Dϒ sBII*8c+:&ZRGz<9vhdJ_{xKC$Xy\}[I㪨Pe@T;`b@ÀK1Hmӓ%sFi|Vo0Os*)vϏ(SQяu2Wѧ:#Ƚ]=8?oTNmjxP`E"д!uH/i0Fq3Tb4XRfDIP%OVd<;}MYJ6- ,-fLۈܦko "KVm)OvE{nxY:eEFfhV 6"!ri!Y($ś4NәPj˲䵔N)0UyrfYJH")ITFzVCZf4y֯WfLd&Wz#$YR^)T%QXQ 5B{ZT@0⒌ȩ0þQuYq`hy1L-m$#(3-`fʤM &^*m!A)!)q&i&S%Y hcv2wrPBl$L0}8 ZHmˍ$ @9F%%>(󖔔7jDa8$), v=,*Lc^~bY|77nRQVB$$j6PI „L`Z U@>+۶Eڇ1nc2-b9HtaA z0[@@/ymu H˜S#L44>=l۩X8}ͅLH`!U",&;d0Fe*N-Ȏ,m QKlqyAK+˜d_7 :Ș!r W{&BPsM$`эHJf痠92 Ld1j/Z /wF()i)Vb=kN)n]kI`3k+pbY+9~^o~Eg# щb͆#erpK@m\H⠓._Jς[ĩ&8$¾݉GbT0.Cqr%iBs8c<ֽ c%H+C\mQ!#ةA 8`@P)H90TШf j2^]RbɚLх cM,S;a$+8xIG ѰL7b!bN[8w4E.. ɺ'B2TTDiY荱qFK#M !f$z*|4j]6O.H( <2JhbAZ0+OaWbD&Rk&zRu-10q]û c f)p@_#!mMvRH*-5Iqt$sC7P2Er[A:nKt]YM 0jQ^i؋~*lA3e̅c[%S>i#1X*&Q{=6)E\ʶK@u{y%T&4zy$ !aLx8<-!vAHw.欉5Y ޳JDm*!LuUdA6of /* A(}"癬dŔnYE2ΐqRBA7BS˶dУ`E (%DWaqwI*&v5(^Be: 9rBI-0T<( +R5i.21nE :ʈ(|JF;JS>"AI;f8"Zl\BL 4 `D#[gehlw%F+b1u/%Qu} ]3%AšAmtٿm4(6c8hӣdwv(,Sx5x𲖎WҀ$dĨ5Ҍ$R$S`A ;q3q:c P 3be̙QIioZbT-%;܊%8RDTyD6[',Ҝ% `+:ԓei*KLqg;ΰJ58)C:,Ԭ)٧ZʭBrǾΘEh=.?Ɩk=eF9d`La}.J2&.#?KQd(u=2X_ !>`TNRs& ٮ:ӴКvDHX]eZiSbh- frg!5&ƨuՄ^s +͕Z2A5R5Ƶ!$|B@Ն Q%սȍ0bEFLe2#ĕ!Bd+6Ԋ6a2(41g0Þs%/Rˋaw~] N\/$/0i1\vl hXT2%O{w?~6UV UdXc)0."LU/bt,DȎӵs ԏMA$ˆ!iIhAP,%vZP+xIDF4iCl`E X-)QvA򌉜l=seA&p-x$îQecKdYZ4bQa X6*\餄(J.,aA P+m9D*U1‡(idHBݦ #үEMi$8g]cҽd" 8ݳQgiRre_Lږ7_4-=- znjpHrB6C$NG $t HDc*҂b4%7D:a#,(( c@@f J8$g tj=b@M*bIR*`uIn4gT&mrD]/+U3E*ZN15X[htO˱B6@So'ٟ\ME Hb̃ƂE (H a2'whr-_)WBMvD IPre# :s ؂Ԅ&N)a:e }d).VG$M@eraZ2iQ IAFgF$ (oۓ^ EuJ1'0UC8wZ=mbn $YL@x֓ f4۳}c*H[D)1M$þ$O"d9 Kn׵=up{ajmoOz$BJȈ*ڃ, pD??;im <$=ӔtL{#%JM%ɭc A9e>,_-40ؔR#>%Wm[tPz.b L묥c"Mߕ?}BtU,@Џ#Y{ ~D%Ɍt!h`}/ cEZAsL.h:ɜГ5;Qd420+p*,Vwܤvz(AVF@%ӄR6ƶ}g-#k,]4)/ `ksw02l<ѐLT(1wD0%qRӤ2A0 NTvejG9!Vq얁!eHFI-\Y V- #sl?ٲmQ٫w}1rhؠ6`DM]D-i ,zDf(Q0Jmȉ:)Y!Gm> dI -FTVTQЫ"@m9"=3] Y(QC4gEɉ2𓬘1&R~T{L4/b}<lLia q݈EDی.IP6h@Y$$7r&d>W&QGD(e#GU!UԘh@#N3I D Ƀ.1$* "".d5:tF4SlliT Gi7dZ*3V(͠7m܉  ڪLsKH2Ml%G|Ɯ=3=F& r$9ֆ0d `t?gEz}3іR-Iw tOH(DvNt^YE 1enrgH( ϩ,B+2mF9FHMNNo[s^h_ekCw#j ɖ~L#dL[`M,ۺG;1;Y-O /RTRw9&Ǵ̷(EJ8lLw@a8%eUȕ &9F>uNA,i$2X[t mSGL"4у(}{Q*̯HouLT$z z`̀bE1+ؼ%&nA5L2"4NČUyaN z~5(V-.oQI{ +K @k %$V$YCS10Z9QL3%9%3ZfM//M3UYǾ1u5KLzj}!f}4Ļ֋Io ƃϖ` eB|%)@lỈא} D0$etY6"gQ="b(ߠD!^ D" L1$! DYxZ7F ZKikB\_~ɇ42f }ڸ꺣 =C(TP 4!3K(AБbмu;zhZx@D -#{'%!(]Ju5%j~ֈGȣG`M>r /q̗üS dvu7yJ+ y6lWE_X=oMR)Uj #V!2u<,U7J\PҋxyxӒSleN⯖TiٞɀeD)3m&Q%$B(d^W Z""h3LZ"z)(("nIBQ5-Z0]t"zI # J2|4?H3^lz& Yi_- 3_1fc9 ©*[鴂[Y SRL0- 5ЂQ=bݏb@J&Q@dU#% @#L'On% X8Kv|UHqc%2$`"R*bg3q)dJ fmßk3C%>cLFt#'vuWPkĝn\hB\Q95Eذ9t򚦊n pNm21t  ^1!L T*ѻe]!aEQHjBo!G?M` $iK‹c҅QMOzy 6RsJe!ba %% UÈapdӎ(Gu.#Y^I2i1"B00D@Kla}5aOBKDY،1))[G3rp'XqFSZl6I`|k^dΪruARk΁hEI(6L$R=$ad@ޖaE=&91ȩO.+5ra٥'*ZY*@B i"PY <"L|l,b\!&k/?dNfBxx'+K%--&-rD 1JSo]-n\QU3&+ L罚,mKfp$MhF1숹%p-wU4b"07^R J@}3$ h,dM#T Eb+&%1j2rk Hd(QF"EQfl'@D:HH<2DHlc/^4Yv/QGx)@ӔyYNc'*K N˔ RV֨-@@`@ {2@J'(I0" (BI1Y$IbOp`p7=u]8)F5f9~66;/79d-KGB_!65R:kK1Q1[E4>soAݲ ԫ1~}߯)0B 2-`zE(>UtCM$2b 5Q4h$E5N~I葊%kxE,i_aDh-ru0EƦ2) MUi#DVπgEH3rlîR} n1⤓5Axq[TQjOW5%Q9$L}e(ܘ&KF-lC)4A#%qĨ"r<\X C="癦&,H~j)S,mgjZu/-AXVeĽNYj796]k_LDr&_uĈ`x40f&IdYH9pJen,;Tf.ZIM G&U sBdˬWOL@&%. ӌcy- fםo)>z==q)ϏcS E]/(JSQ* 'H@H]#]LjޙN)FDPv'*,"4J{O#aˑMF`Aq}7ZL ;JӸoPB9_Z# (eY_΄ LD*jG4&|5WLO?Ui@6.Hh I3sLړ.DQ78bDJ 4hCM.q0(@ٛBV'%V2b6jFՙBSͶ-! 3jq 1#4eI/RjO'y]΁ 2Xv }$B4UTiE:81 ;EbH_q L3fH?W&+}$V;U*Z.RQr A`~M1,|$0 AwAi( Q`)N*u cM_lx2iRtO*17&-bA(G汩8pYY9rlV<Ϙ`m- y9.  `5"DFʴvyvYzzвײe!NtU=H>HSF.6'$ˑ2e naLG&9zE2jbCD$y6d^.fUr7Rd-M;9%i$7R%d:MvEoLiEH2Pm(&sdoe4 -j XzF^vƙ"q!eeA^"A|JRFVyi>R&M궘IݍN A(fJ=eau};ݮMRzd'\Ŧ pY_>_* uA2jtTTEą I&IW0@f"tdca=p,kVC 20i9ՒřJP#zGж"#FPjbPhAlЯ*g,3JI͖#/=*k<&驁vt+FYn>v dhf&%#_%xv3OeW=J⨾*/tF:@ Qi B4XZ8jX.8 ЉZJ&za,R?)ttp=Q7dWBi\_Z;4$#{lM WNd f[D$U&ÌaAP'av/Rr|$3fJFJlM D?)ƛ2B f;nԀ$eI)5pM%&^R$M2b8ܻwy*ЯX*%-_ָ_e 0~+M $Cj23M'Mt7DyQ&&f9L@iY|3rg#O4,86Rޒ}Us$Pc3c|mkbimAߞ "ĔGY'CP91AgCL13u  $: HNYeV4\37sLaD{Bf" P`I.2 =DIS:2xD{XqKh}42Hj܄a0z%fLY3ڃa0AnVPrZ݆YWݶ@#9(YD8bzHHZ4P æ-JV"~DRϮjdEPlq5v6(RB\tq3FүU[fEvpM[!Üuo|PUӑ 'kҫW A  "L WTD{"$ 8 p!jde2'[pnw;iȊr;v^maf=$`&$v40-h&Ғm$þu"ag$ mRpUlc:H@LNYfb-΂G&'d~Ȁ2( Ɨ&]i5QfAk&Ι8nqO*Yl!9U;U񫩻4gS! z"b3hNW$AVm}j&T aEhA >tzE!в2A#9LN@A> I * !*+~o"cH(~-xdo'aPu vωXP:,  _&eb؛ uht!H]+LtŚ]$&н]A!}I H:ߢup#>BһFMbfm1Z9wIXre`6QPU1_Ig&&L$d6T%jgg af\Ԏ Dт$g4 Lȕ%&Ru$Ba4"Tiis3+qGjgS}~ $Р@IGB,`gQ׽*e봒lą=Q]eDĄ(|ڦBdVeDي;S%J.ZZ.˛`Q+lYۨZjԞMd,-q&pNZ Iu0ɿz1ƹ>azFM( M3eH2%K餉8FMyh୹KZјn\U|m+%NERN${# 6es2u-ӌ47+LH-T6WwaK&ճ>fAvKU Ѥ $eq &'@W3-1}%0MX :C9$j0+Y$ 91LԵ7LR}xTc룓LfD&QpaB,I=`M>3,Q%=i7FO @I$#!QN V∗D(b\%i LĎjYvM}$aKG)EBI-Nɗ-$-ԕ̀gD)4m($Qq)#9MVJ v6VZ&A(>/IԎCNʱ J7fHW!z"hJ\kIIE 9 ob%yi6oQ|-0{(y9>, ݒk^mCa txr /7;=t5PՎN5 cM32۠P( (G?gÂp|(@‡ PP1 18>(sX>|\?|?>{ lÕ80ԃQo6sK65bp@h /\\\wDܹ`8> pN s`>!\# 1qkcHcpצL.hGVR 0( )1.%[CFj;~j4ß+GeqqPD]MbU9Kְre_/9jh^^s؜b$}ʿՄDr2b(ejz livd0PYWؑr@Pa@!S`aMX"CT^Kuʡ^RK!s= ñ$rpTN_R%VR6uX19n[ß3N_޿aqhzx8 &58K#/+IGzzi<9C)<ɢw$e0&`lWa"E1Drh &j%Ԗkd-7I7Ej6uuTgrPX! 5g5Kdcʔ 4W TC8C4. 9Bx4`D^>J sYP|ĭ曏Mn/wE˞6`.J^f8xb2vh]HҴdG{Չ2+1)A,M%D$Z"iP= C~9DOifQNEz q%˄g'S^ىS ~ý{Q־Y@jzt x#Af dFK|Q?V^yauQf qETD`8b7` @s 182d!"q5Ā/F_Gc.sPݥL{ea@pf'byTtm&.fY &nrHd_^'[S(kN>UNcÂ'j,ҥ0̇ j[~ɀ m H,4(#/T'#)a()$0d *%\qVѢײ +QZxRj_bXnfqB /Kxsn7ӑ9\N9ܫh4yۼ_O)_̬Q9`f`h)7*zɓ؂Ӄ Y'" M@űPiQ!ECC饜1+#Ri5#\$ٵwEVC=ADk;%¡P9cKѩ׎,8 Bz+d&``abah6+pD0 @cJ yq{Q/A&5}D\5!w+(} tDdbBBʊn[ (:76cHDHʃu~揖=UV2v,אov)/pl`w9"SLB6jH1eDKZ0!X\0s;#:VqWLmCzT(}ŊzPpyo(w@Fg;P 2Vz$(ҕ{ T-⮱-wu&؝HԕJWU7f?'h2hM%i2^zDOXé2$F<]bĝ"d>&b$`ˊ9K@+i8I0L±$74nf: ap,@W`r|EɥTKY_2P-v6TbIYU3|?kr u/ɔC)F`C;]Jd Q{P0XİZV7h *"fjfx"PywÈ +rEcV|,Hz >,qnѡ"g B( ]@1>1Lޫ1jG98KK,9nɌܶmjK5*+GA3 s@hFb} bjtzg,E"kn ښV%qKhA#%̛(SZi* Wd6+ET,60~R3Nt{Ŵ'Ɣĭ#NA֭ס2((-/J c/AfTm!#ez`2T_W~`9HH^0┪mg]+NmV%%z!H14^u03y/3K8]};4!IZبb_ؿ.c~zVI\xNOwιcΊDNmc1HL_GAFQpX;Gezֿ9--ij $"RH‡2 lD.pnKl'EQOXU5cٱQm5&\ùn{4='9mek4c]VIUKU {QA"B8( &jL(~"U<ܺӸQYBbu7)Jʦ~j`MEPA'@A ٺ$<ܤF`E@|R%d6jlU^6e/Nt(qwYT_ym,켆vKjƌDag\ݪ4 b2(`0'sc1k(#7@<9%LY p3 P2bHoTňDy9ȳ̙:yz}S+,u?%E @p%SPJ,?wpCf M } MPe1m98G]ܥL3N崣ntmp؛:x%]GvZ Md.)0c5J 22C00@LRߊ$X,ᵚV.&nTj *;P1:PiV=t#bbtbw%";-{=;ْA<'@wG2%y_?/'8$wmrL @2XLV=̅M)0 *e:u!1$/H(PpfQH81ͱApw,]GG&lC`z3,KmR18?ਸ਼#\`;Pɛ,1I8ԸW+Mu@,R@ H>5wlM`Āq" v"j-iF4#a-ZiPGZI,%aҲ֗Cer(s6fk/y,3^SkLf-꯮ Xs&$@\BJ|rUŘ*1ӁEnsNJ2jZ܅` @f'm~n >bŽ$ʖ`#3sܲ?^KNƿ&Y2QpmpL=U. )EG߳[(/Yd! L7jTӐ@BBCʺ^ Ml#֤}.h0{j%$E )璃jjrЪmZ@0ɶřUQ e엲1YՕY(W*O7IGY=JD#I5#i HHHf`h I ,o.kR~1 Sx1?22 f606r56~ D"~iIh6练AKYz};ZHӛII,TfSlȿe}/c;o<3v۫Ǫ-!= ͌el1C7 Fm pV02c ~b``Y BqłD29DmcZ}٫>ő[:ҟ=2 N1:WAiQx i2ow0BK4>hM'ͽwMshe q}K:.mHDjD ?#]D ϖ[.lpA$OѱK+4KMciJq[PP/ê!&=qTPW7]JLiԈ:eEdU;ĘKWHRɟ͗~dgE*b=iϜX;I"BNyd& exēP:b(*p++b*> Z@g^Έ £hCu=_XU9gi#ϏKRZThmK-M~ǿ+xOMB! ݲBJɑ #ᙘf~*U%#A7˷CJ eEeF [AHp\Aduѓ%}ȣT:Oit+|"^;X7mn?Z /knAA*cLk@ gd &eX ,? #Iq r3!´@7NV QsLQro:O ,{XpCs3kQn"ctHmGj 90(USА Ay()J zVhͮ]EW=W60ĢKl#]uD!' ~3,y7/ z}7k;KipÙ19$m#%ԙy1Z !]Ba& >r A"q#,pSICqa%o9e%Ȟ;d,+K M9J|j A)ެ~yC,3胞"!Y4tH6ZA@vDx"ؐi217MGr0w"@8(WB20Zj'O[jUlK_ H⃙TOG0 S#3y#|ٵvyJ2!u9ԜKr \Hr#D4C(3PAP86⦃& 6)^KkRƭh5Y 1T F 'X5!fdbÍ숛r;}Ǿ[ˡrڞ`:A$ 5.F~ޥ,-ǵ% $ ; cu<;IjԂ'XZ s֎;l0嚖a'hLt0zg"zU1 M.l_ne'Tg¢VԡWdZ ! !1l+Gd uWu,n)(eTD?gu˦,R\A1}ilÙPT5p&gu->7ٝ X*Z1;22#1NkZkNW%ؒFxSVKg|sEi˝B:dhL9#*A0%0mb5`!s.i)2V2>(-C]v(L*H5/R#"3Pˇl/[y$0xX֕Eek}Q "Wh@&-PT/:^v^i4^SgMx*69;7~5q*BL׀(PPͱ;: b  VE\̕I&VlP*5{[AYB"7rvJ*3 /3{@d+`bI[_ |$`ɤ/eb45!v%KƜ,R Nň'M*x&eIKz}i^tGt"cE[[[`mqAkH~)p}7q"g5 `U=a㠀̊4@<4BLw*ڵqI1&[OcmY]/CQ\Zʡ5]5= r@-sq@|2P+VD;%gm0Gwܟ޿<̙}&perEPt\(bAP ҀMMklIYm]30o* 5, <_&ᾨeLE٥TjaX(LENP$,D'<*)8h uAFb:mI2AYlEU--ϟygSn~|( #MDsP,D 4 M1@=HpщLeQINKDˊFRg}†x&W&m-J3% 3~+Az-J*@@ 0p!tl0"+RQ)EYId=@, pJ(2&fAQD} JOGycN{dWih'ukRY4ҙ':h5iYy(}%NnR߯b~uo/|FV2a75{0׬Kn4وm 9! xNhOJʇIU ;2~y%#* M\zKT [ppd6hOF #IKv̩x.>"EkΓaє^Pp+v\_1H.HpPJ Q@7g!BĖS0!?8h 􀄜OkȜIIkPe+3q*_d%9AeG^,`D |z/aE:ɂ/pa&7!N9ûeHy9}fUn*.o;J.|KX` f e/fK6a~&Gx bRʤNMLSرx=+fU" J( @bܝ@gmc5mB<;/ÅH2nd$V,F#6ߋjB;xzS?hfG)!ú"lg L pCQG$ $H9LAUa~<1Ҥ@hY#`mc%8MgJ DTsWM"|~nñ4.r$A  -@sP㐼CҐ<+`\NqUy  '9FHɛHI2HвB&d wha -Y09;0kUL|" V#TfVS#?8z/D˾|#km_$xp1u@=dK2i6-0H@@wG hgR+obxԚ ! W;ֶ?T-RƔ$(8ώDfǁE',D^'5*\rlnhgV֒<܍ۋ|T#Tج6"簠֘B*[ptj+k ^1 `$c:J~cmhK`0N2)lwe?ԙ دMH^A[C. UoJ`=9P39eQE Ҳpm״]"5`{=̥jQ%Ԋ@hsd]ޫ;芳7E˭h16HPL`N:85o`g gP)H:%XK'Rnd8UA.B"fYdg\5#: kDk^sUlxO1WP@CK4@aWa@A&.Wa jBDOIѴ?*03Pqg>>140QSOApvwAY;00bvjA} ԏCyab!(XiwF(tCkZX Ā!\I2%J)FuԜnH˦`y,tÚP /0m&}č턇PG4XBFi ibjFdLCd~%m0&A*TѢ DRJ,qF2>.rȉ  4ԝ;ԤR뺹)8*f Ă@ .#؇rZa!a՞6}& M.Fh%hy\qb pn9U3wra@ aR+3Y]k2Cɗ 6F-RG"˞^QJpT.av=(B"(A`Plp0 076hb1ԲUy)2bEپ& T㳕COo7ZE!:nz3N?OPׁZ!8cJm9O?7}_6w$R )D(T0r!,e4c Nl1TҀ3KC`N|hjOP4 Tz9`y@pSc{Qu59N$ed/\tƶ}Ʒyo֕yCi,]zXmD.Q' l`wG˩қ.tèQ81$eu f^q`Vk5$pY&$a2vvg~śam%:`0 0-}sɁ7 h3رU%L!1[(UcX䄱=A~^qU&S}8%ЉtFzNLY>6u=/_v"'?8;]*dM|`ҶmCeqSQĊW@0cmJaAl"tXatһϣ:d*W"Ä[^':4g)f#Fk1pV>$0E!%1XV9zkfeT]`֓C2U'ks/;nҘ1o ó^f`8}ԡG)Yf˩rxæR %S`'$%ܘaȈ5] qH{o;V´$-Y$N"ӦIwc%9TC%33M яKw/3+P~i_u0ctwn QYj4n*݆ywWE G@5>=)Tz~$8Y6G140+DQv[JOqtxFPH6aYs3*vWHHDlS5Hp&G+sKM󗦜K9XiP`vc[@mɆ2`?D0ŗxu^ׄǀ/O(QFB *B/`K3yu[RFql)ɉ'c 5TZaˆ!cc9?{UwTk؊u6 Dr_ Hx;I[j?]Kr_fH ^9#2 ^k-ܱ*-U1uj؜}!P0 ,*DAHd#ͬ`BDth$sjE/ۣ>/uFE `n%k$" @PL.yEӎ;S/EL BQ"8 1yYc Ĕ͒_h%e%9ՅńO$',"&Mn,zL]}r/vZ1) y{~fܥ-zJ$DT(0 "ѰV\`dHRDP@\3 EcB7YTkÏg^Ʃ5Z88q;+Js'&ՎaD6JElLCקE?xr+l},24nb9bT"$0LHKbItÚ)#NmE1$eq9b3m6P ( B"ŊC0bDg$4RB'ZPLsif`ebN̸6\hPZz\xb;icgKye?>S"b旾NYzRαsnn\*@ZͦD"$``CMFl>A,Ũtd P&M 1'oxlDFLR51qy5uwd6\Y0g T`IPeg?&] `4aT L4 e,Q;{o[c$DNɉ~٤j]'f:dg(-J2<]I8X/*Vn^R1El_͌Ȍ/?ē q܌ti`xC, )`XLatbwhpRa@  `-Yw`<0R@ R2iu0AHs s 1MG&:Eɒ!%U_'&T}rts?d?ÓM{Mgf|S;Ȑqd@FE'F{DOPؒ(u#zSM.qJu<ԍ&@pP@D&\"DlD5%2ʊ'rV)(Oܨ/jONB@B6De鞛L_L3,LiϮZLfr[MJgCL,9i!5A%Z~cQ 4 "KМ$@9I2CaRrr@Z k\*ADIY᎐v\$Y@g@̮V)I%M0(ML !fGf A@*McC28EIGɍ.S֚MՎb>JZ{4J(UY +%ߎ%H>R,c92Lc2e-RoX@>Y Յ!hkhlìU%pmOY>GX5iPvL(! Qg&*ɛZm׻yY<޼vA񷶷G>)½m4y߱NqWHN;qr 4Ȁ#D,/BKk؇im}Twʃ;# 6cݩ U"t˲D%D@96Y \wc؍Jѽ>NrNNAuJ.0%tX$jhy58RюT8xdEϧrӇK?~$m&*CA5m;Ԡ_4BA2ـcԓB8u1g*5̫n6y&Y3ŽFю{s5 4?f#Քr Lqά)}L7z?q !#AhVWuŇR2I^ԭ;"JCue@1mY;j!rd !d UCf.ˑ,I.kS˅wmZ_tzaꓻ:hmų+9voӼViu*πd0 9@InA/8`DA!22..2Ʒ*oCqiNj0B":N\kwBTL۔sYO'^S#)!l[XKcq󷢚g̽k빊ZGZ9C8a'KPH3bcxmkSaodq [>5sǂʪ*X82ğj.McW b%haE!6O$7Գ.X.ط1ܛx~g-7L^4ky]_tdFc۲*e,eI$8P.S {B/)GV\G!U٩Y,?(*eU0s#JF+NDΒQ[J>~ʩϺrzmi9S&b-_{ߟ3o@ RȂ a L.F+(PB0 82>Dv2WIK0*\p×q!.pi;ԍ65. Cm]XHtt.PH4gWFdC$LVU8l=L)+Y O08 PZ><]W> ]<SE2\fn1u5Mح$xWgL[Gei6D!-3le IF)BfBVOKfHyllÛ.mJpԎqT Hhp3AeLFQ; iW\."6xhQҋ ͵~}eSo|5^(5Cf[Wߍ=ީʚ?dPof̥ԛIz^ Ɏ 'jN]8hj< 0# <8;-,|˖[짩1@Vsju9HC&Z~2٪᪡u>)l pua3 k@HU)1:&IX4!8j [ɋhbT񜀉ѥ0f“ǁC?Z_B;㲤jRՒm%g}0֚ieZ #dn]n>q8՞"syKA ܇:ebYg (1$O#IAHLù,gCS OA,u!|>#quԇF(= 4+*Ms3=~eWtzgwW(R;'<iuz!,Lw@008?1E 40V4Jc1H9mqgmieqJd VeH^|bXh RѲa L[R8\4Ya:yw{iӋUQ͚i1|G޹QPfh\S|\^O=5xXr،reK ( Vf D@sD#@2V$AhJf2ދR 4&OF(qeYK4%҅JkX̷"*@l bn#IBR|*L,F@&Աe $•%}LۧPjή9=[EZ ./g*C取ڗĒ +̈N#yja|CGcz>ɞ~nևVBdped&XБUoB 7#i#b8S.q8AݕV[W捻D&k?"N?*D;먡. YąƒC ;'g =g+a%%RЭ(جh]DP]EAZC"dw>}B Ydqr*@DpvHЙ(ulP#+o"#5*bˠTKŃW )c:n!pD qAFHsAJ-+9 2) q8FLuܗ+jƓ8# %N "τ^ "E|H 0%NZeA A ;YS8t0mf}!z !8 pujQfY33Q]EF$:e-5F3O!v,sQu0bu#Ί̈ _wf&[6rn9E6N!sTSM"IK4),>aqc8hqB=½2+S<)Wv uJmcHNc DSh'rNΠ }{ QUU% "@ _hy"bw궠. (p\ d..XI§6~t^u%+%p|CuH^d?gl5BA#2d\pu:, u4F z I߇ hH, tÜQ%+5$o5)f5uooeOzg0 ?P5$AiK%vChk61ga ώR~5hX&Q5}|ONd 3εwz&n2{xDU cB2qb# "5@0B0Z1hu*I^.NTjI( #Gŕ=3 ]1|[ȼt*u@/^$:lOB&,$OL<_IħAe`Gb;q+wP`a`PVD2HbB /< hԶeb1kot%3F[58]F,[J=8UA!ɏ!C"e l,h)VO-AVuʕP;Q ^mĦZ?޷,Nh_pa %m@<Aa.W)ִQz<1->W`gAL`$vbpϧ4(B:USL؋ƃL"1[czd㔅Br`m m{1J# .P ,2@`[f4c!yG3šq#Qu/-s6%%4+_Kto/(-=%BQ \dI3KPrXoWۘzbKA5-Z'7, `F%^O{/P`!@d%+D2Ȼ(T HKh<P3H,S9ޞ`COR.@2 "$hx)0@[Rze:U'l"zj'tU$#ɪFB;)tYSqgie}.DV(3zV1@#H$h1 70Cfe^* ϣ-Y< 7 ,LE%%4ĝ f=I2}(B,Bd!rG3Tvj^V!Y0g1isGsb3S+1}nug \t`q8 &1`AJP*QvZ`?%lPɱi*}^e Ne=S;68jq0I#*n=u1C) 1(GIyWDss43/s7i*]QR#jh~ . MP aNUHcb(u#|S.0m]7% yF4)vgA*S"K)7^YanBF\SF-,֒xeȌsRӭEON;r3(!TfFߺ~E]hκjkC0 kDbIUJ)Rf &-J%u3O aK8y]FXL\;^, , mum ^W%ђa䐲rmpQ֭-3'c:ލ~? ||K#MiR X~It.8XV8-^yobQ|0@҂ML0 7 0j=H+k>Ԑ;2S2_YBxWvgVٳpiK~Lp(̭S* Y43/}("T+ ĦqKT;K)YH 櫪FhCwn[E^5%{gigi#Ұnn~R!dDύNSS/ ^eߥ>Et#a-&Kv|\KƞJ ѽ2xGˌBp¼Pu30n,5mDz6 b2Xȧt*zz ,A<IQw8r$R^̕-5ȋ!I˧/͋TJLlzj.Q,+_@bX0<)2Bm$Σ4ݝyܕ; @izӗ7K6 :G"@ FL  R;T&=Ul ͊Scjde;+2^OXM^!6W8+ n0 $@D`E\nm->$Y֟\01z"mi,u3&yBSϷ\t~ϟ2YZ6Z KR"9k[;tԓz+Jt#Di4ǹ Z1{~C R񑑚"&X#|@ْ"" Dũg5 *iMF:K ֖ZrKQ̡ʋ*+,NdHn\^>EBW[z,4cRt&;#$%Ea*\1@0*AP)zGC.tŮSsBZ;q$ kX@ 7#/j vK%oF1?qF&EĦ9=5I"bjxWf~/mTnNGÇȭBiޠ,wu3,VZvIs8SzPτgK!.7EdTFY6X#>P'\QYS@/8bESbV(.s[Rs9قLϖ)ŠDIb"@0899&J9dFr2\'32XR< ӸPfjYhxj}KEd$(qgPdfMAVFBQEJvHO8-YA >$P8yٟK_E֭ƽkhHH7m;U4!g0CbC-m/~c}E (de}P!F?/ }e5{5T+>Hp!tꑩd>JqF$oU))$0`j2V娻<ً9feQ3ޔqZpYKz\ijd;K&2r%34͒àrJDZ`55, fc$%(`yԄXbiiG#`4X蹅c1yEevXXoRE GtLYxO[T\b9cegE{y+svzP3"4E=d6B0  &0H.)xP *0<ʎq.J!9$F|9l -bI&< ~DYLl Fݒ4P1UZ |Abuˆ,qnQ1q)e=bo30 VEz逄OXV:T,O"Bd8l*?SG1M!F=QIN}ፘ2*ܖ_i4.0T]\s 7"*$ݣ0Ֆz" unW:S* ga 8Y5d ؜/anTcO¦*5/L{]=If网)tzƶ|y#Kh;=@G^ie2;, h`kp XVf<[`vPi,%  &FʣҖdmdT-s>z$ ş3'K\\>q^yPvd˙/uxs,_bZU]rI0Df*< kSF*T#Zbf2H{سi^UK;QN-99}p!2Pՙ0_^.R,u~Q>_2B!{ oWe@]iEzC #( $a&: c'i(m8 fR@ nJK{Ǎy+]XđҨ5 oWV,C )ZEWJY3[T- ZWG:u?;8I\>jfUQ- i(/?)І:$ P2 ljb˭l)&SrL sTSDDXlPvESB>Ds'Spl\5{PCJL APΈܿ۹2Y/JW߷,慱0ꋬw)3B[@zR!X 0\$@!IF77GRm0wf❏lÎR.$oG:d% LPjO{'~ >=hd{؁[*e/Rk %; h#'30ݡ"pp t`hJE3R)*oφ| >M;Ȯm|\AT :8"&B`i9i d)3\!Z!>U3̞G@@%\00L * G?WA,m:p;uT[+$\jT35AЈ:@Q3)0Ut+b%Ok&v, D)6&Jkr_9&jR2Ź f h7낵 E8QB?{ع#6u#*A\UA{!G2CTS"7̣s3BK#zT`rFDD (j9_$uH mȣ\h Ea$* Cq$}$F,u;Sy˩t3ʨ¬G'*XJ\ ZV]#W>ksHhxHL$rg8%,šO$Mh[-) !mFɈ-5uˌRHq#z))$o)%1,%ot`xiJ(itIUcivYDPߏɯaANHw fЩv[̤eqTrAҦW',yCyvW<ƴ}Xƃ @%^D6aK#u 3I7а#HU2_VG1GCJ $]7M JF+ga `LQgp*HIٖ_ Y)Zu''W.IK7,&9iv$ͽ9,+VU\@ƣky%>.qq)GiV0%b9p}ԌArnIJ[*Xu%(W10BUM*#xޮڢ0C<%c;$,:f5iF;swGMiIb6# ǀDbg8x-lL͔as,Exa,#a1 ~+ד6bGrک\YWhTdWL๿&{Ǭ?D wIkI6q?=V; I3V'{+>ںǶ6>5hr^4U I A}{GoSb׍*Q+[q<$R& 5T* w1eܓ;U/I_CF 2(N-%U*Rx9$6f`Gj] -M9ԓIɤL1DĞa@cI"%,Q& "G$13Bd$u` u#u2L)T]5Cn8<Cz&eI B>1<D9\*!u=p% =Fկ쪕A RIVN餦LeQd Ks hURGճ-Q8 ,t %RHv :! C؈0LEAR$dR;cL Q$wMa( YXX2\~kro;^1{ Yd=)鬏=l/J( þN["Y!o{@P&gH oP=@D=?k Z-ӱyAтug8X~hفG7 `H W.3J8!+8"RI&Қ<82?ȩL#!i`ۛeJ-FE*Zz2|b4;A`I,jV0qln ayND6!TiR ?Լtٌ>^XS5{[MEyOXDTڙy'Mj{r$iSn#??oq[FWќqa.USaz4|DᖏT*8(hLal?/׾珍'6zmc!H,KШ|.?AWAt^i~wmlvi>}Mqqw7$'V*:Kڐj\1n(@Qq!3SƉ]O9 a?giS?۷VoZiG(VI6u뙣- ~]+]g/s6/waBdmVrӚR^fWMmngS17{?~ЍW%`'2:M EQD7dlG)`0-h- PI0WLמꚤOWr6Li8r@][ǜz?|B$k+<4qv"ۛHc"verTL#u8SsN#%Za0jjճS"bd"Jue__S1̙l@0yRI !.tRy;l՛_cl8E}8Ĭe&|O@3arj-73:<ơ)<ƋPV#uN @1 ǢI|8iA#Rcg3+.{E6aA&Rxgxs9{oqwEe¯P.#*nb[cvY5uhoz@vOhoyԽ@?:_ܣ?I&&*ˍ3UqCԈ׺ͧff$1S.Y̬s1~ Hϗnnrd`P,PU6V1}Ռjhmq.zѬ%DD@U GEVW /7]>1xzonI-, 3;b ())GdW.qnCI^ xqQ6.c!N"D_\pj9Lk\ky[s"څLΡ]8PC a4#B ڡ5hk Vdԙi5&MaCXk2kKjjL,*F[3\Ӥ%.frz&OG(l 4j3[_\lmzե޶Ĺ3n*1/IEd*k`r],I̊ Rv9b:LAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUtdesktop-1.2.17/Telegram/Resources/uwp/000077500000000000000000000000001326245125100177655ustar00rootroot00000000000000tdesktop-1.2.17/Telegram/Resources/uwp/AppX/000077500000000000000000000000001326245125100206355ustar00rootroot00000000000000tdesktop-1.2.17/Telegram/Resources/uwp/AppX/AppxManifest.xml000066400000000000000000000034751326245125100237670ustar00rootroot00000000000000 Telegram Desktop Telegram Messenger LLP Telegram Desktop official messenger Assets\logo\logo.png tdesktop-1.2.17/Telegram/Resources/uwp/AppX/Assets/000077500000000000000000000000001326245125100220775ustar00rootroot00000000000000tdesktop-1.2.17/Telegram/Resources/uwp/AppX/Assets/logo/000077500000000000000000000000001326245125100230375ustar00rootroot00000000000000tdesktop-1.2.17/Telegram/Resources/uwp/AppX/Assets/logo/logo.scale-100.png000066400000000000000000000034751326245125100261020ustar00rootroot00000000000000PNG  IHDR22?IDAThKl\W91swjbǏ$N)  CҖjڦ5  6txH $m!eDH*&mqIgl=x>X&ib+ft9};5TSM5TSM;JzB#i iIf]uX[Ei| tͮ]N=3v:?oU- Tma&vߖ77:ӶtX3H[<(f;t&ߑ||kCDj@w 3) @clx'CɃ_m3h+cPX1f$ ?b,a9;Jms&FhR"xAːCDzQvU#D/>}0Б6Nzxs{[A/b,,Hi O ْRL LÑ@g Q6nMqYwo{rσSnʾ2 O|Hf鸉 @|𑧠?sH3<ŰM}S?՞x=enŒ5JB)1[1DW>ԬkR6.љ6w'әM!OŘysEF{S;Pqq1b^=Hf$Malwv?51&P3X !f/e?lH%ҟAB8 !p{;mJs43&犘ιit4P' `r!ϸj q ьmI=sWK1Qʸ[Y nl%?XD׆1^> \ !fU+V܁muf.BX"(1-a&]ir)ْc+B0828kMÆDd0O.0_ 0-  m%ː! T_Q?* p %vw/}~{ʱ*}bl!aɹ<ʿ)plR0":DžQ=*bM2za88҅wǿvO}[J[i f29ATeb[PX>4UR Q=Ɋ 3fdG#O'~qw_LO{r5α9 "v98]R𝵊5b 3n)V<κo[=RSc!CkS5_YjyI b`|n>:j,za 0;/@|.xFx W 3Hvc3y7tQ!HKAfdA b `ShB hf!W!Rtp1zV&#(ן4 `͈!rEbd*!ADSzDhkD}+ZJ3 p}O)|Dq.0VVkb q,ZI[//yy`OGI\(0K[" E\._.|8c u!:󣣻9C'Kye0zz|X ! 0\q8霄3#& S?~_J1CB/ gf!kb ¹~͡GGM=knoe$xm˳>=:ӗ A4o?5{ n d0-2J ߖ7=|ip㓇8pu'T^A ̰%s/<ߞ4_/S5TSM5TӍ?H!{IENDB`tdesktop-1.2.17/Telegram/Resources/uwp/AppX/Assets/logo/logo.scale-125.png000066400000000000000000000045451326245125100261100ustar00rootroot00000000000000PNG  IHDR??W_ ,IDATh[l\9s=3_r%W A6*I8 BS mIBhUUUPyҪR[J@PQR5$i Ďo㹟9W8gLZ{@5jԨQF5jBVێQ I:J 5*Wɰ%.(";ڣ;߿)x._ze0a@gB| |1xoi>|cc&bg&x+wV<3P Ō~m?НрHŰ<ɺ $-7{4Q|*Fc0&sφc;Ef=Q F;0Sɷ }QDu =0)yMɰ\<; g}N%gˑֈ޸k}d7ǟR[hg}HzaK. ̼x1b ؽLe+{Q 㾮辞.ю3"B2g#wlb9q=W RA1i[Bw 1 )|!pNdd! XX2l᠎-mutx҈0RO*TsM3dHf4=ݑzǷv- a,J"0i lO GP੥we`vJ[J^!m;2}hR!|zI l<  5CKeyd_ @wkߎϹrrfEbiäX2AX̄o:ֿ>'2x?Ύ{l&C)FC+>D2?YOi#B@,C&8;ih~2 t`dtREhyo%,?fQ=HL@k}ÿ(ƪFh/ N<KE,x Aeh(3ZyCpxƒ\X߯x #i)#]qoC4MV.'w ^SЗQoHJ MF.Õ zN{U<+U^C3ɹt%dG>F*g!k`ɂ;(s_σbl}f24;KdžDQk3)VKV!48+0304qO}[grO={'B1˗9S=@?ޜ4WgH嬩G_GADcq><|Wyx ,Ea+Sp=.փcׂ^`7o_G hE^a4k_ʸ'WY%4MeNqzfG03_秇N(aC_|]&i>@u‡i/>9:} c1AᯩP3_ Ikqށ*[ eSwgo[d^ط{.4$yᶤ0/>ly 0xc@T<>^D*DαTᢾ5jԨQF5j~uBIENDB`tdesktop-1.2.17/Telegram/Resources/uwp/AppX/Assets/logo/logo.scale-150.png000066400000000000000000000055171326245125100261060ustar00rootroot00000000000000PNG  IHDRKK8Nz IDATxYl\W߹Όxc'i!,J&M Dj$PBE O@HA[TUMhq8ı؎=rs>'83c'9w5 U{;6XG1b azjӷԞxHF."}c{,z*cQK7bIl_A.in?=yX$pӢ+?X _1{X|KWl{sTbRS1!p)kU W2,CĶtڻ>G{*p}rEy:#K* jn~?Į73]sYdcŪX{o?yWW=0 J^B ^͏R, g"bl~'?z[nKocVXѕ|DP#"\4QkM#p|ό{}kݴ3i#!MlORDɘ5O1]WJDQS.h2DdKwl;{-C1H Dxꩬ 3XQQS VܳE" %†s}kwYyODl7btAgs :`,e!U-ױbb1H2#wo~h}b؎x 5q- HŸ4STO*Ykn@)`8'qU)=,CD?ЃOnK@s"|aDd. (>kiiׇ^P$Fސ^V#Rzt#ZhJ^ +LfJP#DRX( T\.sͲ\pUଷv̖΄!tOU*T@b|ϗ "AL5=Eo/lnۘD≪As`.'3%R21MQ:0td2oCJi:۶́ QIL 1PɫjhwǯjLu$,,A,bڤ +AG 3sD*Fe#5a$/(5j.̴#DT`]  4BrǕ@sRD\b;rRT_Kكܢ`jvϺ;6tcuۆDRp|^dRBv`r| 1/HHFfE\ F'w` b5 "f>‘NsĞlmIu݊k.'%n`tM-%&E\yUygdy[00@Hyf_>u/G:u^jǦ IT+L23Z-jVʫ差87_:xV 3+\j_.#mYgno~Wib}<_^r@AtŃ:AB`D"ѐ_™L6%5/ _z;[ Є@oGѐ\<JxKc$ $L҂H#9爡 0pͥ:bըҹ?ps5(|g&3XD t"eݑiGijBCyKbDȗ]ɂ9(йM0F2-4HKe왬ܺ8ı)yfTеep~m,X$pt皤 0kq9] W IxE㦸bBT^:C{a C%?O_4t[*& EyRo ],S.ͺoAr+HgpExBkOp:e}9e x I a8CK;08~0fWkd NWugE2DN(6u"!)m^|/|S)%M]>_!8JX4"LXtOW6 W3M-m^}UW+_~_MKHuņTKl- V:vjGߟ!'LՇ %lirǟ#×A `4]J;(3ZaEMT ̀ v}BxBA96SV,Xס/mu{J*i7KAi|X ա=*Dt\qܣFD3u _|Wri,<_apy}Y1 g8Mq`:k_?GB9Wн{51tjٜf'9 Et Gڏ;Z0s~sfD5(X@>ÓS[7|5]&pB[,$!x9F W, X!LbX%^ցcN=TљZ|| UG7IENDB`tdesktop-1.2.17/Telegram/Resources/uwp/AppX/Assets/logo/logo.scale-200.png000066400000000000000000000076061326245125100261030ustar00rootroot00000000000000PNG  IHDRddpTMIDATxyl}ǿyٛK.oYÇjđ-:,ŇZeH:@?m .Цh1mr*NkWiAl2mK$IH.skzEQ,E^ofw{C*7Y7:3* e 1gr |VWΡ25:"u{:B<Q  dwJw| U,.apj9yζkl{ZǷߛy8'HeRln:ؖ/]ն-[b!t!Y73/HKj ݻ;y&!m.QERB*Y +3+YrIQm wo *qH絢<3 LI]RݭL %`p!c{"tosTF(i_ZŜ)4ܺk^)Kp)}}>tGkpBܖm '_=S\B´%* @OɻڃvFNm*]~yTG\@^aW{Ï]v(y/9)MK[ YgF ׆ ]ѽmOm~s{|4hsRLR-fu;2$s\T tKޮ#CoiЮg й@ QJd*T!69b!ɂ=Y(.U%Ȓ==~fa&t^WN3ق<&ba?ÐK[%8,0Lխ/DK8m=tosxf]ff&RyDS40!!)WQ =/mnK\!L|4aqHMR\B \`"o0z "%`p [J|,#|'vYKdq°&yrziDho AJTX@!3ys~8mQ!}RvI!om ܾ#Cp*jJ$$3E$pZb阱x٢=p1p)h.:m}O =t.QŊ cylE P@Jt#h_:18W N` zFwv߿1]6F XW BH$2$E)(q\:4FWp.Bk]2k"%LD]Gu\m@̥+RNe&Ryt B Mԇ|+^y>D RnIHtGm?ySw{H^-| 13CB )2"~6_ُRnYgFr5RaFvxrk[{c~Vgjys,*Ry&Ts!4G؊-pZ&3kzZ)d 2o R@@)޸+#u\R2 -0)`v^*pUll@S(*B&rXV!#3&^=7F`Oo NI^RhBH 0,^b5t5("Jpb*)`iӬXo|._~=QmX.i1M S}Sx$DC؇x@JE@V!LC_d#8ӑH5- XKbPa8YB N j*)ac2]@U,Fh !`(dJNH-: A,.~UII)cZ.Ee+.RbCCJQ(GYs(g#s^y~p`2{ڴ[6ԽV0,,&SyM`^,iXjJ$&gX LCwvݳb *q+xSSOOB,sB|TÀbq&I y|&;vv۷ 7uo5 c9?Zg &R&{RxѐV3F v[:[\9ϊ-ħ±F 2_4> mWg;oTjZ&º1UNe%RBS(: x2L等Q%OB)Q5(''MȏFSt]ߠ[nmm 9@G:g`*]e%b0t5E`hp2ϑ oA*Gj-CKٗέuw־w&H SJtДTm YWZ3հ.}~?| Rh@QWvnzV˞#D(#Bkݥ -YZej;zCC]svlu>¹T~H[SB"Ե{9PB̛uJ񌭍 ġR@j@x\B"/,4a}(`|$j^ K[Z/ N@_+!%ǧrq>qEQX3ƪnX&6-lERٌq DJ$[J!l.NL*dFK?2SkpNת<[8EF8:0EYf`:o]qQ)HJ䌤Rl'uVE:06c(:sd@+/DV([~z uNp\HԺ.$3V/j+.d_8Rm5/ʪ@(Ʀxd :V^!SFы.Qp^ʛ %R]=%v\Xo:D~U%Խq[\.afm6aF?V!Jڪ{oF)M{o)^7Hs6fjօ d ?>V;BS )/U(&OfO~ߝUB$R1Ah7X'8.h8_4ɂ EN7?;S۱_?G_:vMctP0Lne{)֏ H\8-[D6?Sþc6l @Vsϩo:|/ p{4c9_b%FH NUL5J$,۹SVcJdzLtC(PnϤM k  tC9Nk9IG #o-.m2_e891OG﫪 T 81h{}2\%Yĝ$ɺD݁dЩ4Fqtd7HZzu1| m>ԗof.)3iZiu&W}\82bpGfʆqF'A+iͯۏ mmz0'~̌ǫsj_ax'isDdPe?N^xbO|MmsD剱GQ&"#CF %X1YN&j*.Ju/~pkgƯ|\p4 .?M5Fт%JaX&PpˀJj8jẏ3)7` tM=ʻEsL_`d@BJqpNSX|(+%]Ja DbIÛXB4Y?x;*Pq)i]@P\#O!;|;w?hԖjm`9Z,9.&AJ H四4p.]0=ur}B;xֽPXȀT. wi]ƮCVp r͟Ab`&cMEL>!K,sQn{2:D̶m'!+P`,Mڈ Qz ) P9 mP`8x&~%sR2?`fd@kU n {0ơVCL#[jc!"ه>9GpFrS.cpu} %r2< S@)@ R.\xӿPO]k|u.G޽!)ӶG3ΐBu@DZkܧߝSvn!L#S]a &#&gJi`2 f3vsWoOd7 E w u*(`4pHTWQ3X H 5 ^;z&CipoFkAw606rZ?> |BB 07lA3/wȀO EgY V.mGJwŦv%4<Eȯ[oh0f JQCW8LW PZ:%=GuZeyov. 019%p@u@CsmX{_]IJw ,Wl O#O4 7 x& F6b,FB[+@d5l{ ]-x#p#pxT p1b&eB`aݦA4j0mW\LXE>ɀ,ϕ ؖ*m룝ƾ}uF Kh -0ck 7z@YXk4vPZMI^]Y.e1qŒM4=U5clc =$U4܈y*%:;2ZTf Uj_yx]TX4-]+ApSTF75LI Us^F U :|W*sKloO`!BȦr)n^L:$:ŤRrF.]gj,QeMrv1 4VY_:RQB,eQ*' PyWp b(ZɷǪ-*}gbVW>V0uf]{v>.(!n 6uZ3 P&,V탘p} lw~boTwH9Vtd|mn ʯL8` j˃f֯+ՀP{\vծA0Rtmھڮ)J]JQRc9= \Ҷ>eZ^ev͢B|Xe`ZWո >M!hJ#~QUhɟ(HXޝȁ1bBfO ~*Bmgs1"l%\.Pj%TFp9nlLe4@؂B#mUF 9"K)gI1rg—A mY.F qr 5ϖ(R@\Tk?_eC{ ~S܂l+!Ui߀< kBU⅊l1)l?[Kn+aՑ2TBp9ۘ*:PmE 70"k-,ζRbvHGC5^6-GBY ,UN+ E D&X|\(1tU3S1@_4e@{)OO.WF,D|Y5Tx\@UC0|Wud=y[2>-ƇML{ɀ`;t SwMCW!|%j%L9~į1 Ϥ}G"B!&,B0410@o+|{vmiYh{&WBqx9RxS[}`MdOE)7/, ˪lSKԫ}/zh薖soje/ tS܂ߺFێ^_[Gq>?(>قl!1;vaP}F g+"@&>3™K91BKM-ٻc w7B Y-Kf%ƴ_AR!VT:qU.V>oAXNZU}ZLddbf'. j uѽ# A_5ՐB`&i"]iLkUxSGJ7 E1eRj3 )44d<0fщyWCuwuwuHd-LsHeg[֑ |ZnY.FHE^+2Kk[zMsRLgbDlfoǣ~P暽!pt Ah +#r3I(e@XPYo~X4ݡz[Wےs*$/?R'B{5.Ttm){eY%Hls0iUF_yYq -N ַE:>*|M&cz\*s$ύ H"74iaۡXֿ $ч{#TFQ\j:Y/B 3g2E˦5x)BPݟ;ɾaQq1gKTʾ Ng?@/ܸપB=6gT@k1jH)U)^ F uo:V*+SN1^RL&͉ W Hy/sr*jBc !}Ӝ}h\%zpʝ]!|2f,O>-E< o9p8|% p÷  %b}*]RD67\d es~=.Du^3KʼJ* NI8CYØQJSNlA*\uћ^K,srPB0:;hʄzDeñl˳ H S*[Y@p9;2D&ύ`p"Fo.BE2'M(JX ,aةl73 e\)?0=|WKr3q(e k ,L:lg!Y\.ץHo`&.% 'ӳ 1@d@ Fp,sNokmDTE*ksy] Z_,*#.NĠ54B=rz2}d%NP/F!NM1X[Q\ %8ɜ6SI@CBpU9yˍr7:(bd:Ύ`:ТV\?#cDQJ2 UQp$&'dYQrysnuU M=N2Yd@>$R}Tu JF)83{0mu>_ %O.x%]b g6_]V]05:[g/#@Z2y)o:}&鲩U<ɀ,$TEPV;̍^oxp5TF1m=?(p±d *gz@d!A D#"7uF; - S×g2֤FkB}EUf۔"̵.WLSG;~iXȧ6e ?2 Kᚪb -ގuMQʧ2N%<Ϙzݑy{3y5vH*%09wxZ4%wbtnN5x8zi?:6E'Xw3:Jj//EsV$+EZ8=>9 ȒϞ~`M)!P)S6ZӀp6wOl~ޑ}[/;wJE`.zg㊪J9 RL&`rWm__/g@il ¶5v^1ɛW>#羣2j +5#t.2v{UrH-&43\IQմ *#Ȼ";6jjpPu+سcշoO~N͉Z7*#Fm2Z@?yr*R{"a:?zgg? ll pkC/㧾qa͐>*­pz2}̦j@fYkI-s.b+:M{&~ %`Z.WrM۟W| s",J*аJhB [$ }7?\0·L@ |oP:ɔ9].F P=S@:p__{) Gb᱁/}]&A~ pc[\˟~oHЧb u~)bp&;mJ?kYG2y&!!΁\%pdptr˨kz}GFUW|/:!6F NOeTIȀ\WøEO]ϩsfw*gGc_[/M3Nvoԛ;SKv,Hj=d@;닜JRvBK~w*"C7[X]Ӹjhnm_|[/~ޱY3BIc)t?={r:i;n$R4F܉~^E HeTg1##_ߞL]].(.dzglz rpMeG?9W=Q΢{^W&?7q1Frl֕Smo9>{9OQӓ,R@dy~~foyd,8(Sw?ifwC+]kշjo}_w^oΌ5=͗lg" ! _~|lk P(E@Spj"xr]7O)]/lն;F8p/9Gη.S 0n,ԞMoXc_ۜCS~aۢ_oz{CJ䋕csۤj۳#ԽT5m+UDXpGDYJQNb|QFx*0Ϸ 3L(q﹕IDATT3?yBS"v){-﮵pH+KaEl,Yae]I\H f\鞮+3`0L3_D=Vgwl@P( BP( BP( BP( BP( BP( BP( BP( BP( BP( Ertwq[ō!\BJ)APJr9C/n@lHھ\aXD]=M lO,yB r.+7%Zp bBq='v'Q fl~4gA u.9Zo03`@W(%D!|Y4Qª/H{KB.PBdkZ Q@7zcO?}gkNC\H8jLɔ#.3F*]JX@R:\mz;vߞOƴνcb#fzU>/ [C?g>xFc_}1]93ICV#UZ]D0qǴ]{⟹=Q%\.!yκCJJXDP^RF5=zݱ rNWA\20b[D s3yܻ9 C#%r(%؞5n4WJ Kfmm0ܳG"!XIxfJMGНqc]PZ.6Ҡ$t[qsm76Sث4B@s.2yd(h9?#aBpej}ۻc0_xkAC/8XB2k>%Φa4 %ŘLwOؓ]1CHsSB LژHDK<BʒÍ#iBiE(aO 66>{>͡;tJ%VP^YM$ٖc]@BF \_`"L6Yx 'JZhQ\ SUSd9IDGT빿|dw]A^_NOZp|Vx E[":U%s_C]’å=O?ݦ%$p*KQLc"Cj!A8h!% fX[5Qމ7uAdTSr(6Po|S{om b9eC`tJL1b~+…D`oCX]QR>Ѝ$q|)<)ekuopwG{"U~P9d,kD 3-CcS- qw$ɕ^Z=j]XTfļ-vtGE)[4?@J FT \*oFKwӗCIBhԦWhoyGwt}5jDWi~(.ƒr.RBB4%e2_ `{>fAU*jFX}NBʦk߾D_l}G[¬ASf}N!!!p %rL"39gf<'}W>֦Н>3Pgl50-d̹Hw9% x^A K4EcYBʓ;Pa9 o=ZCtjثsk!:]M1t4D Dm/7F Φ3Y (~ J (9uFo}+TGLk2pqEju7Cs&kc"e]BXH1*u7h u[Oԯܛ.NY@Wp!a*q_}q}ˮ3SکYԇɹrLr-n!,Xn5 Gx `C)DwL#]3%tExwtǞi&Wi5Xht^N["AEoQH qOYnRW;q$, wkwɎؓhJrdRIH Q P<1k_HvIX~޳ws+6ؾ@l`F) ӳyq(A`kD\%NmI3f$,"1|@U sba2E i+cfHI(k@We> mxK sL$kzn84JWǝc(];P&aQJ 1,R*Js.&S9^\\H4 tnbj̜#z $@uv:{*?m=J@t5Q%p|Ԃ^Hq!d%WYTt\Ä~e]9{ ={Y]5@0qv"J>!>2_+u{p&iw$,~eX_P7_IylۆwֽS[hw9|:G"F ɔ輚b1ML.'R jfTaȱS􉇷os罽MfF ?pW.tӳyZ (It6RXoPBu<1<ЙQSaxS=YnMLc?jzz?֞qOwcW$9夅b!%eRJBbѬlfPJ0v-1i5^( >̬bf3n.;75L7~OvE Ar\.RJ31ka&co -#uʗhN &O#Zm$@fN׻f3V.3KC(t@D۝pϗ|(5gyLY7):N[HِnjXRzZMD EE`9j-RR IZln:ML=v饞mmm=FOG#FHLpJ4:FpHCOY2 w2gI\@mfȘ&ƶؖI_}rw[=w?U|F! bBgnr)}. O, jLX ȈPs{>ܖˤ7gO|eb7hkŭYZ̛:Zh`p:nK6=ީ L=$B-]Vgh뗳֥c΅DBwAT/!%N؇x-E+`k!Q) ]:BcA*B.@0Z!9JG**s=joDEJIu;p^㼴ͫZlF &%GuR]a0ň%M˥|@{'DpFں4J04?dj ᲱE ȬϟN/w:D> _HBZkA:(bb)%CNjf02@WSE x>aM-c]cHZNF>Vf‘V_Z $r̅9^cQ#@Rh3~Ʌ5hYwrHjRT:F@|vl*wFfH*!4{'/!ݩQt͍dzu4,6RRr->6cD^: N{(FS`7UN4Jp1rV[C!,ԬfABrF ??Óp}J&%85m%Kհ֏4JȸK_H[hdqH)*N =y ӳX1 3s5/.$N$A_MnE B֛K( PQ~yz cI4pB{P;RJAJh.?HyۮgCb. ^2)vwϼ9Fu4F0_k#,:uk9*,Q(979}wbD7n:jhɯOIy-xg9hoؼmqwxɃ95Bs}9tjcլKaQd`\8=cأU56J٠Mp۫_KQkl=Ld싣z[KaAJI4>ȿ*%!BQ$(2C䃋̗C[Xwc#Ҟ>rai3HX%8 &+` AErRp)!N)|.?eTB~caZMXp,e"z3/yS2{K:x ׏}QC%HSIwZɯu,,H =:f"kh05g{W#{ֽHX,mwo?G`b֚eF_k%ֳL -[jҲ!l"s~.i8ޓXX.8 G[nx?)}7~j]3/ /;tr 骆OY7&2/zsaٱT.i@d]T2/.DZC>khTs}k.$]HhSkal2k3š8 V[Dc>0>9?|sm]93O? wep)7w9[,6W[ aް{7+.ih >x%g\\K eqR,Ah 749_;pHBEc,4vIcG#әObu`ѕ6\= k9f[Ɔ>t!?|nNdQg^P( BP( BP( BP( BP( BP( BP( BP( BP(>nu'ЄIENDB`tdesktop-1.2.17/Telegram/Resources/uwp/AppX/Assets/logo150/logo150.scale-125.png000066400000000000000000000200541326245125100265550ustar00rootroot00000000000000PNG  IHDRIīIDATxyp]}9vHjdJDɤ6.*93I[Opbiibi2n4mgI8Ylǎx\%E)Q"E$ H؁wsNx $@3H]_;s.(((((((((((((((((((((((((((((((~DJf[ʷSJs? 1磽RK6m/@){b?J:. r3&$Ʌ@"#:¡OK;!xe\RT{aGmp'#8b~ҟ%& r#s$&o2>TGm5{˄_^kQ3ȝ j 2 jq!]4Euևy3ОhVJ\W 홍\yJ~ RD"tK+a:Wf`9[On,~NV^(@Tx[dSgvv2F#I%F"]B]/`1ToMRBưGtƞz"*\ {nA}(!0i;a15+u"$,WN#;B=55o(,b4%@ܴ#97þͫ}ޔEJjuVo{nGm~rϦQь=rf!\6TJ0K)kCnOkxׁs ?Vdu){S^3ES|`8iA(5cf ӕ"];uD>9Qo!2eY )XӣP_ ='uĞ>`@A% ,BI26O~'o2]3UVP[^~65AHX\`h~29FP R\ɥ0( Vcl-g6WwBBȯH Nj4>ZTL. #Zˮ;!+d+!}-F FshܒUU}azbr)b}j]m<.H \ |. j_*Bh\JRp%gچJccmڣm |RcZDn- u%KH~o@J)B:/<.DUVq U~%D꥔ i>Dqe91^qo@ :C+Bu%0J0F,fPo2n{=[{uJ`rl,8\`4D /u1DBj% #sJ~yͽ% aqo{7^dcpOeoud7P$6(^n`l"7?a B-Uh0i!i޽#ȁ (h^(p$g,)A AGC OY=24ͦfLWp жޱ5 w?҈K0TC:11rGup)Q MvJ 6w/f~B/@,62@vF8{Г !Ko?k],5,9 `:ΰ1B0#y1yseE~a( IM.Fd['#l2f7^$!F&p :p!64lh@`|Pڼv01>L 4mY\ .%m kn>'CThAVqTϙ.PB {4cCctFud/b8,Ьð*s]q,ѝMO>!27mQJ TW^6Q_m % cP/b3%D7=d[5#U!^1S18CrA)'5 īάOg(g_L7:kzh_{օYSlZQ&^@̘Q {}e-QrE(Aڲs9c[1W4m4>xݭуbVg}$r0Hȕ)V0DFc9&-XnS\n 6G#:1U/D֜\Rkh ANSJ04Imlk936RVY>ކ0w7:gVf3 HilWtZ| E З(vM_Ja0^{Z#lwPB`wS%(.rHlRJB^ %D_ꦌ 蔂J//s!i4{=]Gb YųYy?5R)$Fy% pGuӭ*Pl={u,{/V9~,rzѦ{;">>)8ei^W#At0!c^4FXH@\%MYcya3e~br)4JUƎ"k\eܣ!oY#<&4!CCgC A] $ 2L\‚KZ_њZ޳#= G+ w8)lH< Hsq! hVU`6h>,y2낟}vWsdߺ^<7a;t4TƂčGu {EXGG} ]PZ)%zV7tgৗ>sB:+MF?yNbroXjZIaG.$"GU!@|ј~A3 GtF?)6j vr- Hɜ5B"8*U;'p^ h(qWAF_'S.gk%n%>pkنgw*cG9߀Χث>,`<_@q6ohU`2O2/wS#f`×nưd |Ƭ`x*ҵ#ןZ: ݰ\XP#nr-P(p.1z|7?m1TK*PR̉r] ]ho!W {E):Y%ǁE8X2߈]C{C!C[SwZ8eK5u/}Mo&-%2&RD8>Cc&ę)WXJ;H Xqi4G*oyK{,> /in EāO,/Ϫ %RJL Oy7j.$*#y x7#)kl8/k>:WIz$% ~.Vi6e*. -aGK {M4()Dlo(ZIυD|3OYՇ{vc}3QX _~ߊMZc<& U S` g& 'r2aJ0 o`Gk=?kU֝ݻg[ˮ{;j\{է2v/MBg- 1D_=]"%e\EGgHM@%TF42 St] lO$.MN\cT|O3?lwoky暻F#~:`T8K&D0. C_mF)&35oǁEJ@N6Y"MƇ{{Uࣷ5?c}A=rOJ\%țw2=Խ'[6V  gl2lIlٮ.he}6XY :`;~lj޼kk˃lYuU#g-d׫AGhhBhuلe~|/sZՐ UgBD69v.9yW65n6ʽ-59+5.WF&Bqr\;^840=npZӔ W7fvAdbݾkrQڊbNXN ՟rOϠϚM^_ v}jdlƖodFJբ1ta`Ԕ!eV&E1C]7t}/1]Hj~kQxTN8Y~t_Y/RO%7×:}/k/Ŭu@DdUnRc YR2V7{> +$5I)s+R,0vb0J11G/x敗Fx[lzR2wB,:]F @`z؟7De2ھ>߿_:;uO^͗N #c.xx[l-0qr<gu4Vg+R0H΁s %VoRw??7Tc GJAĔut\\=SH "e:ï|II(V|=`G7lb ?tx&d9YptO>47|̥%|4Οe_j é⠣mFО^Dm*ֱܫ]_ơAfoncYsBw{'~!wy*&Oى=};ܶeLsp*ii4Vr祳i#}j zdt̞rZ*I)C{ֿӍscW6 lF+8NQk?QvMq7?ʙEy>wT/<2) RWh(!5:7O\ mh6ZR,83o( %:>ҡ>MOqV2qjpD4E_B !yϳƲo$ڹmAk~A"-w ^؛_z/}gl%$lDሦiX/]KT" _b9.\U {74F}8$Ls[H]?ޫ/Y'"K0JϚ~R_ΧF \RJ0d_^ّ48g?s_6uGB-&@[PJH)^>Yq_֯\8 7Vg}K㮦4)D!? j !i8<0k9a4'5U9HP!֛7o~YcT]tZ)ƲTϬ;/햄ꆴLxRL[ï07 46,%@Ы,]Sh& ܖH$iF9ÿXOh%ro)n`=rRj-&hem|nN2B&mhj67=>B33|xa/~I~ؼsc+W[Z2 R: ˰^'+Tnchl5G׿mRdG6iH%%)@. `~ 8@n<7uC';;Ă:j-3ů:3ej_1Z.m%*jRU@_ym>ՑM p)!Y׺.bMsmfŦ; 'C~޻ou=ra飿EQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQִ Ve3IENDB`tdesktop-1.2.17/Telegram/Resources/uwp/AppX/Assets/logo150/logo150.scale-150.png000066400000000000000000000237671326245125100265710ustar00rootroot00000000000000PNG  IHDR>z IDATxyp]}9$v$J,9HZHJ-I;iL'Mⶓi7I6ILOq,[qDv}$R޽ ߂g3;wB!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B8PK PK+_u.iR_!pB*H]/xfBRX#T̲4YӤR3c+w य़b8(- zjÇg.Tgc/Ŀ\G @ƖC !)'DQ*/jG:5{+ZGρ m ;2Яs!*#BRjG=-/jS[1~|F @6K^Ƕz?`(pa>O7[Y !)Md1 xgmu`@Q2XH%LI)d#,;zׁOvјϑ Y[^w1$,;'Ӻr $ds4e+J6xE6#g56(r$2n^ t.P{ɖyiq\d/ J!(deMaKųg:cn{=wsFO7G)ɤ=WpG !Y^q4-G*Gmk[n? xx 9GA٥9gHeHՅJ`(dMMX'M}lL|s2)Tُ,ٻ(Ųo5 :|Ͻ`]h MX3 k~ভ uEM//}`SۂݻhO4+`9 u; G3,Sz< Q7eyp㝁5nM2nhQ!2p8ݔ Ol hRYG"sJ|kC*SΈEE)V+kzi;덝O2]o-c0OgiqT(@!BM-~6߾z/#Aݲ27ȩ8T<;]M!$n" 9[I|lḍ;N k˒z1HH`!} [CYSfEMF)jXwxgKm!<9t,LfmLHfpаBub(3(9UJ}BX.7N{EvzolkNB.JsQ0@J۩%8srx\?|Qe;Yl'u[<9e>)΀\2ݼwaq %lpPJGChBs!g ɜm%a]6tZև pwip3_[%8Cޖgp5#^[kkRka2a[jZhA!,QOsbYG9q'{g?i"QT8c"I 9 z@laJdG6"FB kzzoZ݁nkјldd!w0N!t[|r.& -A*\" )_'›配Ӫ+0wY}.nΕ=Ź{Jh>T8-x+#Zj}l `PTYG:J;1T,O3a+G?h g.M){D<sw1 [<yG1QN}mo<׺B3D,,b=D8d6dP2t5T}gpN  iip}\~:ݝnJΘxWC݁{ʑ 1@*qR `c&K8h4{!*. ] >=i+|շx6Cn`=M0I!9sKHqޖ j|M8BYTA(w 5ynovJ})wB{}[e@Fӹxхj/81xp_=3Ol h=J7{s)) S5K)t65D,=γY?1Wp bgg㝁' <θ)ݩ5)|1l )/%КL=n~>p$3U<poU^{VVHo[yD}q Yn]zIq %,DҰlgPloY3*)U\w*=O7Nc4xޭ;w?=pvBT"rg29ӑ ) |PCn-.aHFZŪ.m 0w Y#okLϖa[ƙs.l4sRT>PQ]0+!C.{6]mB;B@;q @Zhw<ũ"gWpi!aK@ky}4xrRB*PWY!n)yvg -\1KB0Qгg&Gz<wkzqzg;sB kE8R1E{Z9鲒E%?΀O>d@G4<-ۂ-:gKRb pOD5]A}>ЖZZ\c|r$ ꚷb.V&HFhxf{__7.zYo)Hd ~lV=yKNVڷprT˥{3k~oՎFM5^HYys 2i%wo2%`+կݵiG=R=k٘OVYt_J):Lԛ8@`P8J\P*y}:l7/0e.\, YKQqZgqZ-G?bՄ!TU/yBy׮8yLGHf|ERЅ@W ӧW|gs=Uɢ P! w"J34k//% Ъ`cٱ,Dv]F90 *ElŢ{r`!ElWU 0ˑRo!si*8XCh/(@/xfd!:?1w楷|sgkcݺo[^];9)[mȳHYn] WSFު)A\ g \t_Y>V!mNڦ?o _*sљ_|i][Rkb Kpٜ38L4PPv(>)垢ho2Q/,٩ɴҼU(P! s¶@pX]KRmNjOvfq䝍F*6a&totߊRVJ #@as*99XypŁ=6Zz.T,C#33G&>?K{w[i4i;7`Bp\{4aAa}^R:M<ձ m-NLOJNN,V!\lq ͼV%fZi]b'?05NA]-}۷fPscq.0M1Tz4t6a'W+B@ '^3Z6T"Ñ7ύM~v䭦@˝=[n{^ʙvZ{X(v6Fc[xtO43 S֫r6؝e[[ՒRg)Q▊ МƵy.viT--gX$ÏF~j>m띏:nz,۹x_*ۘj=hk 1mMti|LZiQ j b\@muYOSG$21h;c;}] w5<5yG"wͬYqBc[q }zմXCxS932¬ψ|ȓIxU<Gx~OK};yή:cܺUBI: RR H[y?jqQ4!\lQ lH3!m{qLDj&c}̙P"_z`o܌oq;ƚ߆sLҳ)yAqT`p%IVӔ5Mi L:O?kܳ(Zm& sP|j=28T6~f5Y8>NR`hotߊ[|Χ\O~UQ7 M!ې)#l O#b m; eO ]sy^RR3aL.o\v]-A>!Ňy]KQNꚦ&x4=Q]jGX^AcBNMhX(W23{j6|^ ]-Av~*g SТҗ O/d^lGT5~:Da#v2()qf.*QEA NLң NI7'!^/c@Ӻe (ƘLr LI9cHl$I肁RT,3>QÅZPMR :9~F!*x }!0lFJqd_RB)H0pJKd<%eȋ/M)-nD ٹGRhTYgп)37Ok/K93pXZd%b ʞt]U @!\ $'R5%YMpd@Sha>K9j(+4  P*ZKP)>4}I'x<4`y4Mp QfBsQHBj7t3ܝ1WGSx&7*rKs=M<PWJʁǟ<>|juNTmRk>i#wplpG9_NrGۛyRJxXhڊUܩJ)?08{'kI N}mABR >0J0V1%epR,i(;g'0>  ,6mAB PRz|񓳉W掄4w|88#ZK$@pվi{1 j(ɽ沫yѧHXVam058ƻ}Hwzj;Nϥ?䗞) J3yqn$<JZ3Ndq 8ې>i{0;IPWK MґPnMp؎ 389<|Jjf-V!ޥ(Խ^L8z&ϯ$R)e k3Qwnd^]lhnx#eS' Z(]ԄOGRmgHkTG'qbtaأk]SJ\CR,d4V+TRuW?? i.o98cg@:k:FAB6JI˟^ȼl{8zrABpv⏙7[}_KoBr +jz Xzd*.hZ]B&JF3p*CG:X7zi+ڬWO|۴uӹB}cS;1G"›N%LqB6*=3g\EO#k?GfhMYmsv$Do׿J QN&?fT훗B6 PJ֩kyǹsehL~쥣C97{k8oRT(x s߼}˯z`.q.0g l>NRµR1t] ԱDf:U@D,{Dz3гg4/.J \8Zkl}S7 o}8Fm3L2c5XhA!$+8g*<?ڔTp/?> t`4M))m;_l".]p 'O'$+l~TSQBx#R˜K~oZŚbIDAT{l!&?e<}'ڿ;7=}q nwscJYT񛗮Bx#r Ã|2X ,Y>OszasbYMO)hF~Uo\=sMG_o;. ڠ松 0u%SQ# l]2x `,㾱ij5ڶ̀ k2tiLw4}?}.#NO50KONe. GABx$ b1wQfwr|bwr{Btk bQ0|9o֎oONMFWOovtп<bW,(n4mŖNIG-φY3'xc[J+V[ퟋW^| _hYf|S3ɣRilNˡ8qBR\HlC7+'sky5.X,Z{8mA:Kuiz ;xP&MO9 e?ڬ" SO&^_{ &0J#W̞[&Y.!ݛt v~~x519cе<Pׇ;%uEso%þUv0xu x cRt-mut(_z3_~%C ~h1K;bf(CPyw~.~(J^g xchK>>*P;5Ói,*NOTpQ5ggc٘)g fGfBrcc?)b@ ?3eh VS\oQ=9Oɚ꧎,XMSa!s쇅M@B~>񃑹WGJE'5`K$uaZk[F+LOysG~ǭcO _PK~}ܴ2p=Iƹ6 2N邻គH睹~8OMEevlM!HU,Ru)s1?7p*7cz+t?Mk +5F)OKUo{ _^mL"o~r¹S_]W74}5G?8{y੩ m8@QR=w옩##SsֵGni-{Zk1R M^%SS->87y9<{W<`g_tJ!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!BU]K,;;B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!BH`͸/-^_ӑkk\vJ!kb](Bq\TBk@j"UUQ )CiTUMQ\comr.sd<&1rB&G^TT.Ӣ;`C04 R#+ G೽g>|κz]cc$DH'Wn!;uy] ޔOdv9zVSݡ??3XV@.׼EWޯ=y/] 嫓P`3VK]5k '>g=x!(z2l~cӻ09Td^_{Г*5 ,Bʑw0S~+ܭuֽ`ĆCjҨǺp}loWAP`uכԊz+^{[.O\+OԸ9` {_l95 ,BSy+pϱ>#AW3]>KF‘5Xlܵp]`ǚ>z:>wFRG|2kR`RV"'XWc}6N)jʷsgKd"Tڕ{]S}4#+~3xs~h '2;8Z cl|>-""k|,j/ɷH}}1zCj{1(H[s'5DUR6; z/ǂoPJÑ5җaphx$ ,RV !rJԪ \ܣglcWعSa K$I} ]J}m;TՅ*._o;c?}7t߃3-)zcXJ9GO XTS>fJdZC]O>'>ѬS3!Dvf&.6 "{5!H Gje[ɶ<|ƺ()ߍ0FʼTXTkڷ6ӷZO:k?GIS,v% {v61zkг_XRj䤆.󚩝0 C=P`R[4B͔xPGc;G;m6!*M|憋=g% ,R*kN\YNjcԚ=y~!`>Gjdw=T Y5&^UDEvʑCgJ9JɦTOß}-T}@+=Yq,2K܄iw- ,V xS>Z f|poS=k{9󎱪)zDJ%n=EXdXj𜫔6 >L7ZlG;Gۂ-ZYY 1HK(aBLu!V!EEú5SY]3~TOLJZ@ XJ0 J#h V}PZch!}D!(Yl>[BF??\Ry5Sg!ZJc1q$2yD9[O1ǕXgbyaU"fqAn?kTw.,Li5rMYL-;YְLe@bdpΰ-gո)D"(FsPWKc۟w(`r)tFXM*oc0s1B,1,xae L]L,f鹬^0lxqY/붠"׳A FNiZy?p{hkU[@OVZ8gP e0I#/UAJi,ƮDz.3R>/h TkLte2nk 9k?zLd]]!\U%yL.8]VRi}niV Χ.9Y4XijVNеSJa?lZOxS8VEpWjLGSe&@JpD_KQrt*Q87`([ʚ)D2drۏ>zƼ*}4UĘw/v0B*s5 F]ȇVP%1ǼbvT6u@U]VU'g\UX@7=qMC~s>S{G T^Jc!B}R*Fۏ]B֙lFx=y% v  Z;JFa?iAX3FSEf1R ټ,TV-ut7a`]LOeԂ%/ mMyY't}}uOlyWi^G9C֑JV'Z..,k~&st!(vTJy mv~'[./5rBv?¿ykRs,#)8 +imjl.V܆+k33ZN$(u9e%Bt>k?XgN<%|+KRxtaFS$wQJ凹a*xkW{[Ve[k'y'[k;zBO=cF#~̜[9Jp4iEȯSqQAz㈤KQg2ZR_n5 seӾrCU>x^>p]ĝMO؇ >κ`LTӔo+YD WX*ڠ0 Q69CU;Ư4+X֦cSjswqm<:t{ZOX֫Vu^֑I[4lz`%jQgf'S-9Uk=[>AZ |HG:G;lsV@f)_(ve1M!еH\@g ]ax#ʹTn2gKfZUBkU&g`SSOu>a f|[QV̺)M44hACj[%p~>9FºRPA`{P`jDa BFӡC{<g4:Jy5xХ4i,UXզjTeDC nθ|Z-n=HO]cݶsA qꊗ״tj) @W6,kt5h V 楾2 +Q`mudvn=|pk=,H&Y^T$DJ嚪|K[mzh}V״/q&SA]|27GRŵkۂ8gDkpc@>S4 +rZJ{lc)apo_`MЪذ hBN6gո%kk&Sh-m1yoK#=Ot [w3Mfj#gХMiaw 8D,{9%YܾrDe?-ڸ5L|3z3^3w4)+ӵ^U f"VnT@_KA( [ssAmX9T" Gjj}B~-x% J)_M+'+ga%Foņ˛+Ks j(vp S|<|ƺG0uT'l as fkVYRi6R!vR#%)9n Ǟud`;lt>|Ho,^Yw&tcj)D6͗*V<ن6%8bʹӳ"ī!@Qة5DNiJ|Cm#A8 AJx溇nTMa?m0V9[m6#q)=PZk^>BTFj*l9:uc"+5T3)QUfa\pv>5XUA:k+c=h@fFS[҈,)V 3k*sFqΐɻ¯}ػ:DU1ްdW#gh R{dv(vOd1M#RFm*ۃ"c pJŘ.tjE'~12`ɲnl>H_gYla,μ/V&b&am61 [mཆgff|*,~`<Si] ^j3#[GzGՑYܬbb,XܞEa-*FVEy0˝(.W py l"C0d\mIyol't-vzX0ƫR,UHfYJ#˃mCjRi}zðLUEaC*f FWUT`%]*u{='m!?_(GZ.e@zUmgTkapv3d$9I tVT`(uOxW35ϸ4 1pfH:t;jc9lFcy,!V,izXJk]dW_M)vʕ hlJRi4j 1TeX޴BjP ++bPq(_sR!6"bI+<[w\ !RW{J!f.(U\`gx<^=wtΐK, `Q1ZhzM53D3Nf4@-eVQU*aJ5F3p]UXڡh_UwSh)3>S"$JXh/cޚrR LpN)nl!\jSlGZLqa>q)!٢Z\ "b|,maj .T*Kp<JVW,f򘎤vvt55LF8PlbKs´h}^ ؤaCxR #sqslcJkX@_K 8ggd2?lhjXJ%|㦰BXL1j'ת[jVk08d,31a+*)a+copUkQVy &7ip up\ZL^ˆԵTT`@*j}z[[ݷ ƺoyK׫ݹеHQdC s.7TpRCk`!?;Ȟ0JT쩔˻q}5#JAj!q0AٞJ7K+t4[mhiqj6qC1ITT`k@IM͉T4`5*YDw|W;-ofע5าs 49(wލ(9GwSWwpƐݑ3hZ6:** =ML`AqI" M/M44_QwSMCmƐ^j++>14b)4NRv=![mm 3X3C ZTV`]ҞqB9s׬HFTVe3م'_:qަOk꪿.hJidlo{ s =tt7z}L0<|ήr {(V/u9|gHFBTFg3ѯs{uvc^wgo\ส,«Xb.͇nR~@W %(ng S9n$}ZʬaoJ^xy$/2̮r׉LHj41sK_~s[z};>H| «U!HEӈ͒J#7d48*uN&+ZV!Y!솬v%dcSGguKu4?vG?掰0R!/ZU`!+ծ[զ)49'>p/F Fk K9j2pr,ɨRZϹ]8~}hоoi B䥄#նrhl$d6_Rռ#|h W6 ɴ7k (:yX+]9@ôkI9ED$҉Iu~z;;/|;~ ~5(*c6Ffaὼٙ0IDAT,͵VYCRX J:(G+@.ꚓ=zk};8}\Ju=c^_h*hJ'B(vɩA* ;x1@(Υ-dFeR5Hbd>y!@O8tOO}-a#R94b9·om/j8n!#0GgME0ƀY)^uP`m+ekkrFcGLgd4KEkM/MѰ)eTx!BGa\ȜORK+5r]ev]Mx"|f&P]2ߤ};3T.q1ꜷitu#X;mKBj@zw>n]UuV4>]M6,CPA.08t,}q1I:XEtxZJj(iʈ6GSQ8+`9 J"=vaPBgn> `L`_4jC>t5&5Si9~EouP` P4{"-b]*M׺jpd7>Y\p'7@U0M#L{owO6:fyW;lw3n44S}4{m>;~վ@kg[J^K9/_;qtt @~`qDIM#,w0=~*[(!N$8LX@#ZO:RiۡWB]H_`rC WLP`NqZV rz:NZkGwSM.\#vGEӤ#7?0v* }?ScpdNӅ|US0,&Fr0T0Q4*- dc9SmKg\L DYǕV s9f%vEAX(ƹ b=h2'12;t:N%N1J *-êDv%̺2϶1HX᠈bW*|42/"/]"h7vҗ"΀eYgSZz ZIiyv*dn0Ai ) DoH]!8L<32U!@F74ͅi=3{aTe4[S4-Nlb0mx< M vE Zs!8T8=:f%Mw|CmZYPѧ<(hL_&Sv{8g088"Mw9#DU4-750F#I\N.}y1KM]NÖi0PM*ytRS׷F,98=:. KSm%bj( ϴ i,Ř|2;aamc XHୁI,4E& gW4V0TTg' S2JM8He;4 K`71 w|#IP-*)/<={djK.gid[dpTvz,dІ-O_yZ<uNDR00(!o5chkj1&0M4E,Sp /..8lfIIXEZfZ'3(ɀ1G*ཡ) M,(΀s3Ӯ lO +/޴A*_0qj:~ӭZht+_ 8yf/J])n\ [F򣠵RD։ؕz0`ot$ C0V \b47YW[GUViY_^Hx'l1a\va\radRicbMq!Q4֖P`:V0rz&֖7%ܩƢ{qR]D]d(Çրf)mD ?z(Z$'hiCR|ܿ*Woc !0I"ᎾېJT8=<-_~uShUVe,ǖRg:Yk )tn7G Zӂ?9?7~~[Y 00?f3V\" 򥡵Z'sԅʹFծv\28|S_Zͯ{_;>cO*wJ+KԆ/GW7|) !3?wv&m:R)]J,c?^`liʕ_}w|";ngC* ,Ϙp(" !E ==|qLdh+s3L th+iD}=#7/x_zg{aYL1Idi @fQ`7 Ѥ{j&Td% ` f^xotAu$)ntm]/Ʋ>SLjG`0L(5XM\`lfֱ\)w Ōs+~ahjᭀ.PMJJ@K(eEP[e-蟼|oXzzS`7iqz:6}Yb* 𦅧f70+~3vrJ0.G2'^81&gC Fqz -[kB+g.Kthobs3D6\X̞|>MWۃi@k세A'Rڳ 0_ÿI$ǂM3i v֊5'# M3 Ͽ?Yo`r4MMVZ Ð1eO_lƊ=oo(g4ۓ#W.7wNYp-6ΗRk(:sT2gx˟۷0M>sO44l| .}(ߕͲPL ;nw_p??JEc{uFVk9Wgw6䏽~n_O?C"j``6yR - Z+3cKQfAksn<8aQV[ SDXMS98nlħ7~?:GJ#0T ʁ4XAZ0FbMEѺ9|gx?jv2jPfO/P^t wP^܋o~~l1ya.08t4=q9^,QۊCUaLi_0~j&ڍrBJ}S5DKWO֙׃p?V2Bsś"V;{{~g&^Ub0y6޴0t"w'v 7orLƳ'_x#uuFǾsVEWƴ}`yf)/*felaLOJ֮VPމ:s84~ns^'M-3}L_Wc_1m=cCK__\~.b&c@qݳMol# Q[ȤS['oq_ldNe3)7 aH "WoX]k¡O wO+`0_3\Ns2)y0b>n ʢ`(?y;gi!C4~g08ׯ,?>5E%咿g7w-3 vwR2Fh<1;x?8+2lh K %^X]m# ʢ2,KOgR)V =} |pė1_GkhBc#DCI˟>_~7C)b995? ӗBkP`U u37+ӞƊMzSo|f׹oն$UWhʘ捝kZ;z_W[^~o010C SW;hh@9z3;['{o={kCvu v]p'\l]D%53hMsT2ZoGt^.S%bxi-$ۈx'Cj"NDRWO  D?¥ںEke;Q]{GK|s3~v%8Ŕ_NJEUyi2so }z,v!pq1ڗO a.o&aX*ڻFycg:p&9;6}_x߽(c IiR֯vVePV(}l%ߊ,W5&>Ct_`wSts@Xm/o]1MLfw?O;Ż랝K2,DmXIC+e;=g+79c07OwWݗyKLrre~˜oo7yz*nMM1MO^N eLXI`<lhI6_g&>jÖA0Ŵ3vOPuj=L^a\jtv콱nik wRce7C&ݱ/M{0I?TlZ3X~ir{ id<ɥ_O-FEI܊C"(ɉTΘ[̼24e.mZL.~3sӪ2 Oj`f,Y ZiV s:7?޻8 5t~Xi%B,۾tni/NE;9oKܛ8~48Y\d 1X9xkoFXZ̴1[S0| jNקfy+g#S9j-Kp\^LL?k␮㨮yДp` ,Xu V mqnȁsTw1.NR7 aȻ48pAzm5u N5 {uJHw іiif5(YaUED&;Z;&d>{~>Ml-/(`xR@<f)yk߼$_O˯}g'/8χ,;-??"d{~ 4%ܠrkUvY pܒ920Ga[kf}R?;o?<'B`BДpѢ;NZkhu`2H˥/烑?ZnRcLe K 8!{9 X';T3.0G!qazqx&a2)G= 6Öx D+h:k M(!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!IENDB`tdesktop-1.2.17/Telegram/Resources/uwp/AppX/Assets/logo150/logo150.scale-400.png000066400000000000000000001051541326245125100265560ustar00rootroot00000000000000PNG  IHDRXXf IDATxY[Y'w]B}O)ʥ2}ʪ1== 07~h`FatrMw-+R+CEjIF2+$#?sDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD#Hv s;hsu_7v'?E@DD43QJuEnUR4ZʮW4nT X~[eq+d*Y@w4"hT+T׸3\)k{*ɭ UEDDWj^ WzC\ xg9!I,1xT (`""aZpg-X "6yg w=7lޗi@9zbq_|՟_&R8?Tt8huVz-Tu,Tb4R<7[:?CNPIUGG w.+WϮ?K !F[FU_[g3T]LS TS3o.{v2|}蟰 V}ixִWz/ߌ@՘F b""6ިYұqRKls3+Ox݉w!$kZ;<% Xi_O3d1` . [#~6>>6)>7{܅sB-Gc TQ9X cS]SԽe XDD4H5m^4ZHl 8O$x`ЇKopmZX f'Jl_.?9!kX0` w]@֡ޜ)f\>YOV= O޾RMlȢ]v A'I-xap}ًfs-' :[Y(u8Z^zH?3` ,""MUg:uXk$xm6?g*壕4qs9ZyZM5h7[joOT76<쑗gެw)KaW8//Oc4 vRS*U=V9:'Ԧ&2r^tp!8s8kSQt XDD|MXWpO?:^zxCc)c5m:?{ 4Sah XDDJZKޜC/ɱw&՜ 94}F;(rW5h+mR#PHdlzd?79~6)ӉtZ="val XDDhJqPWbHݙ'ƾqH!NPNqYk 7a 6,"" 4I%ݩ̡3s-;.}shjqRI,rc""m+Utj6=YO^|bo/dI,t4h"WƤ"A2_h`""m1 дZO % ӹ+~'+#EÌNPJ$I=ŀEDDAv:6ju9c?>9澼zk Pv$k7J?7x9Fͷ9x[rU&=^g.d=u66h  1HxZϟEu)@qujXpONzUz=(\,\b2"jU?>[XE/phWvgPTdJ'?7'J>39cq%+sstR^ĀED4`v#P8Zl'#'d7 8mӞ9Xm9'p{jVd ,"+XOjclm553y_?Z|摉wϏ:XhzVm& 1owU5:hKwjg:6By*Q NG~bZ$ SFoWàp)p(0` ^K>AjJf H-T'.*\z$z@@VF_,Gʴd[} ,"o ^궪t8'pWʟ{c{ى̇g'&t{8TɗLkygb0 XDD;PuU:W'~rc{}Hl<< T#P UttyJpu-ZB ,Uj0`Yj}guhqPD&g WO/?q86uVxW7}P?^"\&d1`()+mTX+J$xq:;񉱷Oo N9rM*b,GNk n?i0`()6Vu%. 9;9|a&;GNKy_ƌՑaBkb; XD4 z^{z62N 2gs?'sż/gcΡFtV/kw l0Dhڬ=3` UA7Jx?;g'wNRX*z0"4n~% hH0`0Wg 2T%f"8Vޜ.o|pNU#֚kɪxV ~^C;8@A;ikK?95ԓ{3^?84 uQ=` ϋ | 8,"D]ctt8'p&Pʛ-zlSS'QjQ m"%їVt$Jud XD4hU^hT-FJxx''K5+ơ:GS]]<J;8ǍCASsa9zjmSݣœgƃW9Z4 gCG[uٕ)t3o! 0,"ڭ U}CUbRLSޞ.~|f?8;~q$br"@#a687ZGDhZ,CkA -,"MǟCԵZ\X9{~\>m,Zc: p'Pk~8e[4z5ht~nXEb?d僅 O _vT'Jn#zYoF[n Sv. I-2^X0DQb֪}A!ED[2ܽ TЩu[gB-hyH{d<|eI ֍&FP,ձRᜃSf]/ !%jVoԒ p3YR XDzBkC#q͙{'=V|*Y|FKX`wV8ܨ60RGZܻjfC1:T~pҹ&ç2L4R}UuBLq}a(ah7|p`r!Z[(Zۮ4FOA*UZKUT%\R}e:ʋE4ZzaBZu9O[G ' /qx@{9uhb>(s 6V'oYrC{ȇH/ )4b\mE(ߴz`ݱOԐa"^*Us6)%o=>{ ^(zZԹ88uO:nF~` }WD&P\S,5ME=E4  T=u6.>Rǎ'>>Y:̇c}Z $1Lk2\q}Z3@6!\l{}NZ)\[m\[ݢTeB>YChܫhwi-Rҁ*rjֆ} 4b̭4Pměf7qQVG!ĀEv Pzuia|TTnOW2;RO: H-J{~`b,VXFH6ah7BqpO5cY>՞oqk0`=x %&ޓтwc?<9Ξ~-I8n,x=>^ZǸTC=N7ah7k2C{g<6}4SŗB( 1, *uzjE/dg/- {0 WD 5ӛ{}AVֺWvYzg]{jhl0(A=N1r-6}9:r.ā|͹ߖjU!<8b6W`u?:YK-NJO_yp|)PO:g(i[Xm`~8Pzs f9ayJ~I UX;1,ڟgs ON]<7yxDH,UvZ3ŵ:挹paXyXsWIŋCYznnzsE_wݫRu[wuVZj'*SԏN^=Y-( 1kl>F&-Z/z V@뤠f' Bad~qmzZ/n}€Et]? p=X5Rk+>^.stq_^?k,r94}`~0Ժ[k+/9ƁJYj6U|>K#yUpnb`L&~p|f?;>^|pPɿZdҍ 9J-"بZͯ4fKOvΡ1;YD W[H+ˍ_ίuEî_ }ٯ_JcBbs6)%>QzDP ݃RJuDq pDs7s@5JZmU~;+W|9F ~ۗ:mKPpБqN& .-0Zѽ(DJKkMm&6LUSpIp7֚Bff0` Wz/Q ڌגѩ/Mg?=<7Mm4 F_#JZ C8 sDbmvV_ͭ&]Z ݭh0`nu?ZԺؠyg N=G~"⥖@)Xmpm[n9^kX.dAZ_4(vo[ڸTlD%/Mg=/{L㓙 &8Mu60m4 ZNrNS"h$fi5JoBJ,iSݹiN„Z$I/pn<< XtoET՚ -QmٙVm99fWPܬW#CHctSթ u3u`g *|+|Z=i0Zjƺm?:qpov_+|ve!27t60h, JuW궊)@X'u{3Of_:PyMs@#*ũZs ԚrNΡ pp_ w _?͵ZR.bЯhsYZ'u&Ђ@c?\|x$q.4CjS:Rw8XBh7zU@+ q XgU @G֡tɷz@~1멌qk is(8,u5 p,f&돓B+M^mFmн*U=ָ MT;S ߙ^JL 'Pfh5qFa(v~о*;k-jOj_A@\GĀEW 3Tuڸ-NSypCwN׎'CZD\M!X0!Nz9@]E+[7 hor5a-Xֶ4rȸL7ߛYO5.hgE C]QG߈`zzd<|220iWhWsuԣp+y)땬~T0^ٵ r31` {mX[#Ѝ584OT*,v_լ[?+hwJL-nԱX`۵۬kma~!DLm\+l/b\wkkm<)<^.sEoŵ&Wh4 ®^Z5 _+̌P WCBXFv k1` ~g['ƺ4u+?L/j#uFۈD(6[c@ X}+/\a+OW+?V"̔ol k+wVt=XDGJAyOLsG~ 6q6R acn85]82Ɓ\Ap5TDF噠CֈcۆA5Cb]LS{3./<2\L-R)@O{MbnZ`U.1;G`, joJq`o|GN(:N%&"YO?>5v}/N彃M4On[/,FYm Iwwn:rF 1~^kXXy X[{W@F+>6kNs& :YZͭԱXv cCk,"z W#J +_-6mmb"ծf1`Adѩ҇gW !lPJ4h{ 5p DZ AW_.4Ze"U⛄0`m "|wʡGw I@9R+ D`6 9bsP+P6E݆k u^bP$6nߎ= Mn%sKuT1Mmyc@kz 仅0`mJFo?6C C Xi I-DJ#{x7}&DzzI-"Z37?1P:gwmZrj#pTlaa;+jbT>H b\gkixDGs ,! V@J`z"|pE=o(TYE ~(DF{Z#r f UDŜpE}J+E|ۃa]V!zP"4m7  B D/Ak/X*, F X[ gPZu4"!VȆـsQr5JGY䛈60`m1{vA/jZ;U+*i =pE`}]3n^{AgŊokke?vV#M`q5pU( WtBOWZM5'PAԏ@4+ cYZ᪜1]s 7A+=}zsj+kk- @ >״(k1#n7\M28Wj~^U+- /[_}(D-"@%Xo:qo,>?q 7ς0 v=KqyRl$7J;Xq\W76p~`[} apEߎ_-յoLAn=Hj8]^,A j#󫸱R1û h*s}; kOb)l.w݁K[3zYǎqj97ˁ@남`x7ҷZW(V(C݁k E8V_\: Z'!o p=nËdv)D{/2ߍS56C{~`#Nqcj#Ъdkr3d|vgͣk^SJwvno㛍zbB_-mno vS&=Pnf$ <XFY0t4UrU?G;;m.-?'t#[?u-n/?ѳdEc2@D`jkQX0 "O XͤDPR=/H1":k|z7_>{d=u#_ c=t؃9Oen IDATI}E+ftuaZhO•":K9Ȯ 뒀hj`ִ]Ou|$O𙭣ibE9'~xeagfsb8o:]>xr8PsrD@j,v❧7[Gi#52aWP/XeEEX>MkYN Fޚb0.^(6zh xumP$k_wQ3s=TIب 8 0&Gl0{̤)5bbgi՜_i?=[9+?z?{ R=8""Ö4<I@V,\OJ11X}察#C#~ч>9>{ x3=`֎>\f`t7C+R!ѽWbAkS3 )LrܮD8KRyҋ+b'_ϹO~co|LJ?8(̲<f/֊PmDS:C7꿭}˯ݏIn^-_v  Hڟ'.t5.*0RN.MݶGQ;4γ1D@J 5+ZCSzy(=ux:?=t;{?}w#e۵ڰ%nÎ 0&Fr82_5yݣIr-*zw@3$kze?2"y!XIEmvGs򡬫:PJA)e4k'.??'oO{{0Ͻq=fF.Er+D!,ѡV蛣 ^{Vz4fǬ8AެuI:X6m1@((X+#.-n4Ƀml|Όl"MιJ7z:{N؛~>pgHf֮}{ t Fn @z2JpznB;);*q$`Lw">~]@{Dc9Kf<8rnws<)BU)7csi!T陧N~'G?᷎2p4UDHJ+FX++Vk91Vpޕ+g00ܞi莹]3%nm2"[ )N.m&aY9p4Hk`ٚ46?y޷ƣ{?cȃӌaaMgVڌЍRױ xƉ<s { dFp{ws|޹A~`THFU85^@w$\=Qb#Zܹ>V{%k@S{U@ktEW{L,{ r.8D xF'*;W..&?{}w70p 3@ܩ70"C_r>A[ZnKg{,s$X; K߯88!cNm;q&rAHZ),驥G//L.|o:yχ8}cw1hv @V6(tvwYf+:ݮyIE9[z?-S9Zܩ{ہM,l}vvI) /0yđ=ox{SeFj~o + WYf =+oF2|:x){M,n,j6DjLz C)8s鯞9uL4)tR_4WU._|7M>C)G)?J3d{l> E fV7j*e 8>V v}=X>:Y 7mSZ\ _44aiSDӸŝQ'$f9B Mqb/4} O##?=4^*w~aFa:0TY f`|(#ްUY{"Mݬ~Y%,q'6hsSn0H4rir9H<Ɂϙu) "*gh^^\yt0o>:t/?CyGE0wA7bюST!:Q ZfpC9v eV:d gm%ƫ <44prm"l7ÖGD.`|Zb8Y/uOމ;2l[J2Ҋ`I쯡86Zؠ,W._HZr6;GH[U6Cp$Awlim搥9X#c|&*{M3*v3?#E;O>}飩Y=g>~IM э39?u$aK YO/g3%f+q$`īk  '*:m9YR5 { v J1)WDL..>1Y)7y˿x=?hPDH KE,7V2duX h<>pޗ~+("Իlʨ(3P)!K1 Xbm Z!7h)7`Klզs靋f>9V:J;R\3{LJ?74H̭VG/41ʍIb@$03\?Ɠb_µjz8JʃH;ecz ʈF#isp:i搄$c")G+l ٕ|C~뉇rtݎRca_!N n\c}W•طʍb;],Eݓ%vq=%rǘaHm!}usyX_ii4T}% :33_?:O~@u\HMFˌVf$nQcal>]z3C)hC_nPBl1Er;AWHQ|r\evLk_hc4K+…t23G@BCR"E~T|gfOiba͸r‰B W #£JbY{`%,qmlV7͔^*5@=Y®9tvq)"tX2~_IskR"! i7sse7js:ܗ3#9g|#;Ł(B \ 8L42q\R$s!XrTKa To̵{Ė;Oq]br "\?Z?*G z 06Ur<˕!`è#5,^FDJ7|V,Z+p?J8N|%秠 \[I,fr+V2vvseS \9FܯU[,ҮgFJc(2AJDps rk_0DᕥK1Nqjr[u!n݄Lg*xQ4,9O㞱+3XMr))e!Kl)Y h?3c\>V,;G}c\-uXC)R{clIy)ڔjFlmwg9 K^.!ɦ'~]a%\ (]Xj%E dFa!\7﷙al lߋV4)"zVA!,qݼ2 Δt_T-a(׻xnRN:qhljڟ qW$` 0 %|vfigBIjpFg*HRGE(B\*'}?x.!,qXok ib~5~Zb2U@D^j๫KXnFJ.wqxiEu/WeMo=(qa~`h׺|ڌJ眻BevIGjO.F+(1XZ2]lb{ZgN.,ue,B@+$8%ĩ'[ƚ38jm[B[{`0~]ϔ[NŊf`e7+xyjsXnt(0&ADDhY㉩ʴR(e٘!,qm9d4ϙ3Kh'XRV1U@8H[ "։v `%,!6~A.n]lv_/^ܴ&$*NK)T;pj]]Dޑ#vā;/ͭL/{k݈`m#K%TwdzIֺ~t9ٍk.CMW[ Ý׻B=Wk@Z$sj` B6\|)m\>Zm/WbL"d2N_+2T 2*v]K J K ^: bqJ c)ܕ^LA+b&܂RJ2{H1/-v=/k˃ Ric K]O.$Y}/VȌV 0Waeؓ(<;2 F%P!KWW&T~5"S+LXVB /N hL*"ڎ.Lm,AKl++ B)|j=yRjwGm|BdvOQ/hk]<u +MhER2{g-T{ʮyIN[?tRJy5^[ĺsVNTa-d?,M䄤5(v,!^[Lsח[=r<[F+f f7^EVףhJ{)!ޱFJ7OlR^EkaƪξhdXmt+M(E=$WfW:[-gڬ4(!Kl+ XB5 ^hvfw,fMZ)t gpqXi;w4Yk&֞9(J J\afr\R|34^sЊz.HC$5V8fRAzB 3t|lw>WFZca-o9[r'1[nBQL0Y.I@y݃{ ,!^; A`꓉1ٮ2Q.V# 6rl]yn6vJqxi?/[0&n5{SWF.։,mj#l5QVĎ%k/RFO11 O_s*cBѸ\lN;ykw JyP XBMi&K]O~r|fh_@ ,Ta#Vr̎xm++ihV㔄*k$` q ɔډ[sjZN-aX2TlJNN)Ϸ~yPJbH0 sI|zigݳl JRcpy%D",Τ.%ĝ.Kvk˭(*;`+2Vx~rFV%^(J{Rs^=@Uuvo%ْ/OWtn:`C6``m?ٍqvŕETeY7fTZEV:#O@0X,AK< XB^RV ;5{0F4܎Ql/|g'V}G%vȌӮRR&D& q֭d' B#<ۊeOm@@f,vg8Z跮|3_L,/{㑡$'!DDhiѫI7J*dK[ivLeA.](57zGʄt( Ε|a;7)sst, ܎/mrt]ʃbIZDVpgyaV2kChʞŗ/,fG"X1Wm>ҭJ~G4' |rO]<*bQSJz2 tȑ;^V| 2YhPj_?~t$ .WjFlZ5}lv'q d|n4z#B\[]umRuc0g h`$RVhp0P2ۧ>+̯]-5{ &=vw+N!ML&zD A!]}ml9'ʔ(Q l&~?oD7W5 j_u=ЎY+XĮ%ݹ$bRkuyWMHN/6s܍ tU5Gί<~rksmzc)YʃbO%Y[|EZNj'kar2~Xf!&]l͗/I<KWQ=z /zcxQ wb^`0zTK )㗈fyMdq/>{v2ހ֕bcj7!0*" XBܽ-c\.iyWoq#4G߸0]Ll=vNtkg!vv9U`ɚ8Z;Lꋿ^+3*h:2\~A~\BS$` 5֯hmtO˝tb킦3c257Y'_LҔXCm?L#GqU_/d)ehhf溫喹\j^h5?+ Jm3P0gkWk0]ڣW+ԣ7.-~R~9mBM~?wj3G?Z0 U' J 1X3)u_QmNzqj$(Ͱ[(׺5{;҄Q[g}j?K٭x 3,O+[ѳ_:?ƕ+]HYo ?nJ1c5qONjpeXuMyK>T;3e&,5W lV%v,!%/HϞ>g&݊Grf6Y/ V:ߙ]FzE Uĩ&٫ ݭ;(7#zD\ʚWP3Ģ?1[R2YZXs9pKBZCvSjk&W.n`Es9Jp0qђ?Մ@f\{a8z_3 34r$'0T"&r~#?ZlWd01Qjg=ϳ)6= Hb{07t~.fr nJ0""<9_\n'.Y".FW.f\틕Ւ!ΕձjHovLя^;>{,%Eęɞ^=V,!+gDV8<}znÖv<>4,sʧsulRhX _֣'*jhڱplejD[-ϕMJVf =-pOlQdG/?Fx7TVS9rt\?Y%Ğ!K>H03n.EO;O2kvI*FM}or {%C9Ƥ)ۏ.}_;ٜ'g-Mv<FkYZo rL=׺zdζRbϟZ'y5oY iX~>{E"?SM(w _Y+{c^^T" ws4..էKt| MʸB-kW zj:TfvKȅ'iv+jdƎw 1 kCoeeP zTG(+qCI|bUivڿ+ Sd0m#ދת]Ԟ׍#˰2 pP]~,b5|IJ113|bƿ;;O/6v/FH>|$Xh~ٹFwVPZfX[6\)%Xo_Yn,s<ƎVhxnПYYF_{fpۤ#ex~f 7J3sO~Z23Ll& A,RF(fVڏB`)wOZTu4UrZZ e5qrY 4!wbC^==c'*DT'kb_Q/~odw`oucGT.iǀו(4 XBlWr{ki5WeuuQ qf[T(bҹ8bк^if;\Ҏ#s-ȚЅl3O~\*]NVǴ"t0%J]Mس$` 3֮b1(׋ʝLˮ~ .»i4%t~7/mB4vN j/ڐTMj#E~-& Ņ_{O~̍?#BrffyƅT.?(u ;6a()?P+KutV).z\MoDO|'UM1jD~)ֿ=y^`>rJBH5mwj0O~a?x~v rg*poR~.|/ohmUF407t~v#&6&᱊8բ@ Ы[koo0X}CM Hebyq>ܿ?P{4_2$"tQ릓ﳜ?(# XB+`&˴뀙2UVMmVb4!"Z~ZD,.\wF؉\?r[ޝH~Uw?]]1K!H&! (Oyx) % QI("! $D flvg_<3=]U'5mw/@_U5ꞹ5]=bjF#nUglUc$p~~mn4엕x'O]^,RV|ap* 맮tM՜Ze7Ҟ7/i=dRTJ4𹹧,OyxN6KNUײ~Կist5)esypoɟko!`m#^ED̲wV<]qKΉѤ:rVsuҙYpVe0\MYfzplN֬\BܴމxU g?z~[7wZ`7JNsבNWZ.À^޷[BF,?^~&i0UU2[FU5{?!ZԷONҹD-X>57yӊ֚Xd#P3 XYTR^|$̮ V:3wTTwԹ+ nO O%P , mD«x5s>TZ D,k'fΜ_}l{yÝ IDATW{3sW>uiqJU,$]p\ެr^,:/ZoV2U)eOg.-ydcځ*Ue-//~3_/åttl ΕD(O?%}n8QE7 iO-i6H|1,}AQ|o<^KxQJ ee?qnBo0]OTTMzu$+V `ڞ0ֻ{5kKy9{p_|wn;Ee/f/|ښDwb `;cˤ93J[ :;*GK.u_:SUnf|&b&i}M޴ \|"{n [Ci%đUWk;iMHZ f7~~N-v="U3+8lWO8-, c!%BF/ uI,$ʫb4WޢU4]s؁\}f֍gCc`ZoVĪ2Wjg,M KQ\\X;O}nR =97%½Xx6vy7iVJ.E*T,ah"TJ,檺jy9u\|/snn,73MkQZhip.Y>z~GG:ƭF~vyT\icmǵ`ƙ1u"ҵ兞[$jksдV_Uco?(f*!hQūV&l4UUUgs+[&G/v~_V *aB5,e̻N [Dą~9W+[YBpa ؝lS[Kms7GkQ'C&t-Ū]V|=3|25zw Sܵ3_}̗DajlvzmqF"VH'wSG\{rF}:1XM.?ѯ>>{+iUjPU1KGO$p#D(AԴ9RwUeeԢK&^r汼o?عӬVf۶LCާ?j*ѐl6F( %½Xv,Lkͨ!ͥ*3Jދp^ZSoj9 {?ȹ?o3YYŠ*WG[Xۈ`xƤ\r+sX9i?zaѱzmpxS3 uӗ~b=sO#`*&wmm0|6L'$-ҵ *e]eO^;<2߸_?^59@zADSO/WqK-x) XliLUBjqaʸVըKf8ٷ|L{:u{rறfV1UM"Y)"}U9k&?0{E! {_礨v)]Xt5KhS$],,IUFbL -G:C~}ZZ=w͋lQV-W7?X][6<9 mNYzs9.^ D$2Vs幝Ncf% NSq?pk+}sN"`h4UCjҞ][믽tļw❈32*H~QJW4z Q'#j^BxQ nLI.МIENDB`tdesktop-1.2.17/Telegram/Resources/uwp/AppX/Assets/logo44/000077500000000000000000000000001326245125100232075ustar00rootroot00000000000000tdesktop-1.2.17/Telegram/Resources/uwp/AppX/Assets/logo44/logo44.scale-100.png000066400000000000000000000037741326245125100264240ustar00rootroot00000000000000PNG  IHDR,,ZIDATX[l\G9g/gw&'!DJՆ&Ms)AVj"R *P*On ʈ(%cNv8ͻ{.3[l'NF;3oͷ iMkZh/hӜj;ԚsoFٖ|eP͢cJl rK liH֍@@ua <޹AWORnH&E׌ҋ$1U0VAĖhK"m.s;8" Y_ƑZ/?۷>*b0"+ΖQ @ԱZDb-_QQQ ,$Ƌ>FeJC 1 7j. [vsds!>pwhؖ#%hy&F -"Ra$R1\` s;+0/{pлOy)c!Ќp!D0`=W@@F:Ac`xe"&CEuZ-Щ.z'_;5=-zrSs;[2ԥ6ږ@hf|lDT 0IEѰ]lAxNKE&6HBč謼Q{JooKd$׍0!fE0cCM uqY[Fx2f/gE.B$ntTԉ-BGxD3<5!"QTgKp~{,.fYvf6Y5ɡRc 9+XBUkglpv], „tMBAzwވR/0H!ЙE B putZelNj>8gU˦c Q zٞ( %F+`E; (mɖpkcMҌ ; UM7?̹v,0 ]ƶv "}][X-+%>5V Rh_i/y}7w}F8wKA{oVtRk` ō|xJC-|?~{?tcn _׮*&qgH/{w@S`c` BOjK"zq63⎅1'g^=#5;a4eN߮' tFrWF _{mם[V#7-f8p]_f{ #Sd{sr~X #'zw/ԧDltgԷ]⎄pv Rk «_Bf.JJH6y+ݳm}|?7ZŁHdִ5_.2X7IENDB`tdesktop-1.2.17/Telegram/Resources/uwp/AppX/Assets/logo44/logo44.scale-125.png000066400000000000000000000050531326245125100264230ustar00rootroot00000000000000PNG  IHDR77F IDAThkl\3s޽#~$q;!(- %P)GHZ!< ~hUEZ$JUH헶JJ UIIxc'^w}͜~kǮP~zv9g ,iIKZҒK\ӟ:p$VKk9tS":)Of$5]Ti=;FfաԓJ?BZ8E-޹uךW?`^)8' Ps7Y~<ǧ njD-}x"2&7c'riټ=r+;:bj0W( ˺I3|bp()"ΛVX;o_cSh.}MFSxB،yHBk[7yh*kWTG*L/-ZLHoh$c<#`gF0l$ sjYdt\ɢiV#G)B5KZV! d; 'qatF[c6}` ۛiLynv!k׵nn{Kuת*Kyc `xƅTTXRe@2'>f_. n^aݹ#Іƚu`1BDv%.05u- ~KBw99j-x*B)b[[vvDay)B^)۲r0a"DCɼ+U)ȏ7^LpYcYcz;-*npPp|$R9FGgs &*A~j( V*$;n^޵=r uyg RG HTP"!MQhY.3$ƜQz+xjRdSkm7,oo k$89xz\ ;>8lY6` j8qOx ʨ0f3l :s-+ûwtD^Ske,WyK0O* xYXg21L*ZKŭ6[,k$ab ``\ xd hP-P ܃b?憺La%J$dD!ua4P4[9S1m/S|GR(,1`8kc(S+? :z b׌Es1܈ȡxӉTv8†1 2o(pi`Ն K%8pb-V[kz9t-{㪆kEJmR1&BrƥLR"DV6ZL;C{C7,, ] h?}RFs-nlo|c4c=X kqK@Εw? I%8 0aS}Ͼ[Cm>Xضako̼+qq1J}h9ږEQkscs c@^XVnH&PS#G ]{2v+oz+o][(:>Mp2i5FasCo=>#[ݔAL '%zxl}cxӛf5aSC۲(4/s3d~O=L;ى`0 g7+'"Z&ښb%ecW*wdq&Ipci>w\p},b2gfYڮE38Ùwa>?)PO0鹀A ̈́4pfuzrAwe 䠥 ]= LAR0!53TW DM08p00#GR)HY霡7Oo ^Z 韛 =a<5ϕ05Eّ2f#5ˋ A|_/SNkb*}wxM?dHdݳlw&ѝvo.8 {O~_ pQ؋ Y'Ut1H&Vedx>h8 #8>'q+瓙(Rp/Μ84OS9[ؙZ#h,Cnsxmo?VHd] @t=8WpL+ BG`zߟ[#g¦\Nnޮ)Y鴠 З?ɮjۢL>>?=*ޔj3BMoxtwtĜX?'Yn> ^48Bg_|c'ȅc#0RD"Vu/}{ϙm:ZHTCyXXf`𕄒 aUM:'20~`[VsW#yLeS~^4.!D%zkߟvˆڂَc풖%-i?pNAbIENDB`tdesktop-1.2.17/Telegram/Resources/uwp/AppX/Assets/logo44/logo44.scale-150.png000066400000000000000000000062141326245125100264210ustar00rootroot00000000000000PNG  IHDRBBT SIDATxmp\y9}wwCB i !@CblS0Lәc:mM'v3m%I'!4 icllYfko~X"kW$ ꞻsYꪫꪫAnя'_!oo3J:Y߸uUhqwMR 3|}Go |3@)?e7B {nk`:v{{ “ ֙qcs}6.EOVO=7D9 @e?k *>' z(_#B#׻B) R)ܻ/Zu]_!v1TR#1|MrvAxR  5k=<њRB~ψ RVHG8 #KPΓ񾀐 (^^u1z"*A<\IdV(PIJu(|W(ün}>: +Jo$>\c.UD@pG AC\gjH0#U:_hpLWGR9'r&>Q:!'N˙]*7tg؁1W<NNِR!1D(DHd{05O(R!jg>zk[xG=QJ٭71l zJE1PûQ:#vO{"1X>ۗlaeqF(>yd.Qd(urj1L[(;  BkX{# k9BVeS)ϥKKY R!BG @U-UB Yp\Mo=z0VULfT6Fyt2lc=a C)Y@Lh5*PP B]y@3>z'C] mjR 8#(bgshh|VD{E9ը@A E!q$\C]VX3XБ%?lc]6FES9]Ψ4=+n @jV8#w c v['xkx>Gk~;J"SmE<_b>bP]-m L|֚_lz-Em_PkEB6w0,2TJA =q fHtX])bY :Mn Kie7kz6ի[=T"DP8zZ, Qx$JtaMC-o>@HdlΦ9+,)(eެE0r71~=MGo>rG< [,+ R*D:4N2JIc3>rI\[T7G` %;P|*30АŋON^|jd}ћoP;n xBԹt9J)Th)'0QYA#?b<χ]"`g!u=鼯I`bD[vZN3RI?ө3WuܗfZJR@+ڽp9/ !TMP m{"h0bE[NMX-%AR Vņ#|y*zohp2p>mmj) 1 eO}QTt+n!f"H}Z :FsɌsem"+uJ9b.Xp I}b'A/]LurL|t6[ն,&Ȃƶa@c gOzj*3Xz>y7'`,:Q:#Ţ^=H @( F KSߡ).4]T f/Q*و’`şkV~u~S,4ήZmWD@ KkVv0/X>]=V\!prx Јqa6M%\ou tN/$ n|.!%!lh856?8w̔4FJN}vBRёj21L-Ofz>?6m%Rp)̎Z8S:p~iJ45 ׾7/m9kh?_> ?p]-" \~UF:gӶgO 3N^Y|){~SE3()B(7I|xx|vi444qiDLۓ9yVƨv’χ/g3_F/ 05p"a5g *.ԟݙ|b%yNJKs_)c%:k") (83rnP@f/m]ahߝ!|64$78T ܉WxXRgTYC!EKzʗO_'xhi] H X Qhg<}rGaHfLC[Q+5`'=fC!JPR"h:_]߱t̹:^jG!CC.nn+|G=fNso_D5R[c2 T)l<8X̻>>Bnt]W]uUW]uTq,\U IENDB`tdesktop-1.2.17/Telegram/Resources/uwp/AppX/Assets/logo44/logo44.scale-200.png000066400000000000000000000103651326245125100264170ustar00rootroot00000000000000PNG  IHDRXXq04IDATx{p\}ǿ{>ݕJd(7@̤0@vfڡMg:$3} LK۔ M[6%zXywqw,K]y%;cYqW{s *TPB *TPB *T& b8Ip@4 5ߵZI}LdF{H+3O+`rVxh][6kRc$zwxK70Z^s0\B@o?U}76)001s~KXس>^_ 3- \9$֨ݻUݽYݮ)4  [s5Ş+Rhxs4p=-;BJr86/x:Ş PokUw=.ubbQQPdmbhs\}vў>&4KĦL~qs-λesCW6;:Jr.*ZɌ3bs 5Uʆ;_֢=1'qL4[J )kH/v^`"}6۴= q^b')XƟJ]LJm]kBO6/JT ī83^ $bX.Ɲ᝻քove;F ݰ! /A Aʰc2c>#];ڵG4fK%,J xc 'XV " 8 wooKD2!ꆃ1 Q! ``T/\2)5#`VE4i[kUlkSr玲p!0`$(k54Vyq!`8e[\W]`W^[_lE۵kD[Hnc pͥ笄x,mґ6KnjpKDq(2 ^EpDR󗛃;w ucB,EM @)Fb:&SY!@ !@{4USp)^sրDwVѪ=zU0]TXy"u# 2F EC%Ȁn|(圕5"p>g#6_ju:&%p2UX^2m F+ !h~dqQi}|:'I>Q/i4R[/nk?PW`8= Si#:L^p+(lID VTH(v;vcm#k %̆b$A,mxA,*:(%  $n)+8BX*͉غj冀D d,.`.x"%2e+"r\ ?֙Kj,Q}*nk M;,ϒ.ƥCBeh>i^|aLg%JnAm. Qrɺ#UD)rOgkS:"Wgc &\~ua?#*bYPB0XgI7ܙlYرE{AHf. Xn!Hfmt G9 )z?A\ pIý e86n෢[О|C{! ո9\&0F lc"d2 p!PDCMҷ1?49S"YP`B 5[;T2H/ ;BԋlF_QsSl:{m3\ xc9is]L:Z-9-!@v19XP`Ms!2+,,%SI,h*%5mJ35)3VPpR@a,A"cWf Q _^G#i|vpw:{߁> 2P D $2Lf>I as}W~Ȕ }t6 ?⡴aU_]MЯQ􇀀Q kL#2R  a$Z4 9`vsfvA2wN^vUܷe[SM("QqaiJjt:Hn IƹRAtr@ At hi()~_ N;:q_ph:[5uuMׅ*B|e5Rr*UAs5W0.qC v3/ PJTqKƏ}7gچohֈ!Lǽ,eq8FLLb 6"!VET_/c@DqGw1/!s0Rgwb?Z皺wovcKƪ`, [efz` H؏U5*^6O8K~b:HtҰ^9tͭ5wԶ)S̪COѪ꫃b%rC\IJ.5?˱1 j$ʵ _=^1-;z'.@c\_a(e2xp.$EZ[[Mێ[CP ˙:%JZ}I%,,'Lz3?0F uj4߲Vg%HF8&wRQ d -0BAe٫Y\]`L%/&& >zW Y6JKI+K@io4m.a8F*kˉ3iVƂ 䄾wm\7p0%Z2#޸u&K p !@%gn wrtx8\.Xp\.3 re\. 'oO~rxK0J00]H& !^.a/e~>Vl C{7^G"{.bTto{GpJeȌs4uxC +(7,ٟ0\K{)0J183Cг&i눠+p sgɹلU@<Đ5,<~| ֞}=XQJ㙷f>(:,޸`=}C>[ݴ3|D)%a9)%)6nB@(NgC;&^7/î>,%L=ˠ Lrk(i]!rHb/w~㨥=lhI\ǮjUM?Gvp[Y•UJ4[|e&R]q|s19ldFXSqlkr{H gh90_Y22[Rt#B˷ IMoMSo_/1\{Ǵ||@9$!>_x./\fSJ0bCqZS`bGR)R_"_f}kxM뻊¼Dp>῿ډ`#J(%J00,1T&}DXTd*'O864+h!/,l2Ƶ>dT ~T. \%@$ͼ6l :n2_6"g%y}9%=AG/{Y',3zS%+'F橡?QF/XE喫z確g~z|ϼⓘn%ҭ/W[e!B|\߅㽆@4_.|\em?~4g UjnڗH&# (exvf@ V]?hHUHC-y8cZS{Iܟ|FN |%w8v=O n\ ?L *2nPXL =HRNU_㗙=?=xCմ}yŰݕ+JY"\`\Y{OG{mohӺiK *TPB *TV븀bYIENDB`tdesktop-1.2.17/Telegram/Resources/uwp/AppX/Assets/logo44/logo44.scale-400.png000066400000000000000000000227141326245125100264220ustar00rootroot00000000000000PNG  IHDR^ IDATx{pu{owOt7DRoEEIEّLTv+&ةTfkSIml%N6v"Ǵc'v%)Dxo`}3  @a ̇;sC }_/}xl=/tiM5$`6!L hKQ hj؜3JU:uebj0f%HhBVr3m.Aev{ygտyPid0F̏??fs!"G!, Gp?#M{^Njv,w`BݨIp|wWWbO+ !!AN#=WiW$p{7!+ߛJ񭫱k2]yX%!NeuOw?^ZjA ٕBK;GF{}wCOX]drP_~=oB.@ s!@;t#+7ew"1(D~[6<o]`#`q!VuU{Hom#MRa\`2eQJ\rQĞ 5Ȕw*w_<ԥ](! iu.7ù#@4v{Wj;\r[;kQ94c\ Olݬ#`rujh Y*@ض5 1V\ HlVOo WY],U-1Nb>anHTE**LAf&R5Fd ObqdsA\}wkzK?Z9eܐ\;71I:nRNC)5)I)N^6d\huv#=ghRe그@ !R%X&)D&B+ Q"Xr1rH$g!{.7n:[T5Б^S'4 tʻݤD3u8Xn(DcZԕ7#H7A (F \ FҎje] =c#x$,!A0JD+x@O(s D~9oME4Y.D >b-gTV]YE48\`*L4 Q8\H)@qcj&IE@or2߽ w/=۩[Aa1U7b"DʰA)AI ;UdMc"c'RΈĔe OY5>]:xH} cLU ksLϧ07 b<+v4W$B07M1|EDBh_y-pUZ!`8 nސ$MLDR0,75tptUFoS\G55Ҝ~x"8p#Q"Q<ԥxK?zGrL5d, !0m4IאXx zߠ *q&̉ͳutLt-ݐË́JLlt$.N1=lf*cua.E4r4HڵOur+y͑ߤe,b dYr.T٠oԵ=h`lMr-uhv>ݩ=|zϨH#lPꮞ B4_x ip!\@k@lxBSi>..J!Dl.l4N#WiWY3"YA9ە %HYLSHdn2Ipk (XKؘul~߰![x_]^RvnfnGސSeͩň7QDCuiBp-j\5RrY|ChvӠHD(Y 1d,ma*BڼِXt6+^}Ù+hEL1˅'hnK0m*FW*X6t$pֆRܺ[ëZW%E^}u{|{ui:إzg+-BܢpCv>D\Urkx0BIYщ3,1S(J 8ߊCi'_Q}"(SM",Q#`*B$e.|n%8P*|W S cl3_"To"*V=xncۚMdrsQ m 8\@ImB9(Dpz #hL+z8w˂!gksPLF.g;k{%BYnlic*FA%+WeVZqiդ/t>;P0w6&u ee8م8\FAk  p Fˌ%/TxnmOrG% 0 4+Ɩpz `ݵ@)A,cFΐ$JJ/m]s zthGuk=lΙ_RᘚOc.ahH 4U^e]n:aNYb5)lȸrz=ݮ?~G̣-Cu*r d7qv%򵺱H M 5\l$B0-v ) F gV>aus9[uKth$ 0h3$f") @{*Ż\&̀X)D+QmU!01q赠ӝ.Rg_I] q&!m;ꃨ kXH$I4] xBxz?J/pDW^Ԏvr[h `:F,e޶Vv[;؛D +3%Jb,`q7<V?ߥ?}H,ӰrB 1h sÅ(a F XA#~Ұ9ʮUzO.&K6i$2!AZ޻W5Z$F0I8J `[jS󗏴9B MbۥPJH ^! |)lSlؖB\K_T2K ` H/U o6ς!b:F,ܐXt dicZދ 돘R\F *U`ؐOR.P)l.̛J +Q^*C"ia:Vwp:]E{B&/0giJWҖS|!l,P,gR hvYëRpڌ$K/.`~ |チ} ww|!蓈e XS}!?<$md1I#edA֙[t4Q)6Ӱ2fFM24!=+ U syo:nfUERtVIzyP,p V,O~zWc >'Co9e}[QdCY rd ̏ N[=퍻ؓ[TC$j96X)Sct34Ҧ]pJp. ]zEu !7X0ڨtJz)4sĹ遉S_|iw?{] IJd3Y#M#0OMClԡ*lT !@rxĺ(K~`_`-rÇDTӔ-ChND?;P\0\K_` r+y/`UlDdsC[ڟzᡦ* Y1Sc`&F"7<⭫RZM]Pqy.s2̅X,fU?5G#dD"??3?lxdgj{u5ȹ+BW!1`.f[u\65!e'Km!ZO.fAAL_C{Xɤ|" y~p] 5w>|i6.` if34҆]TCI?RKjuKxo&,P Q% b&hUѪ }ΗI<N#sx;yMݏn{mOkZU|6l~)%p[zjܥ-g F &/hE<{5{uS)D06oΩH$:{/{wvxG=-{t,[Hd,"K彽^C= 90D8[1NiKY:pp0 |7(߼aw?%s PR9kR7h? \d6F̏%9 }JTs%NS.ȚT$U}ӑЩk^~NHsYlܜ]ńQhjj<6S^ՆHCjRMcRLg1W5RP  |.&2%#6 *6ȳ9҅ͲbKɘRuJ(~Z.Ij B~=ۮJKyJE06#wPx~ \쇝kyl Bfl[k+b}y8G50l~\Qq%=IN\+Ơ; /C>YbV0bޑ_s :(-Je#Qhf8dbT9Fk% $*VE/ ~鹏sV҃Q+ɋ /+!W|>0"B\G&(].Φ?$ /XBbLRp8uAfkѫ5@!@ʴ$I@`k ۲?Lm3$N#2f` Sd~?eyZ\Ka'i3]2w'2878$F?S}qB9p$a"F#>yB(Gx8{9Kԇ2@GH-V{5䟽Jtc gSB(T'.#2 3ZzJ: $YH !S sͬB !â'E4a \ @]X #sIcv$\ʴ˲Q =\VY(l9((w>v6o M_I'F"%6ɾ DƶR xeIy xnr]oKl'?lQR޿<(Y \,<ي ,ZZIdF CVV:6x8uH[|5 6ヱrp?x  PB]9YPEPvp8񌛥آL)FY2,f *#1x+%C F9 < \`.a̮6'פ9sفi6[+KpTV,`3BrlUB 8hU\b|.}c+ g,?b]?q(y|53qWGuǰHdL2ɰ{v2!G#%w_ Fj9Mѕ[^?oSZ]$Pwrq' :;USw+\!!)+/$YE8I]U;u 0J N~x2zu$|~3'%w$_V=w>`d+ʆ(t<3>w;py䟻0疶2!s^pYx{(?|4_cbv5kv_w.12^ ̦.ŝ6pފ mB8f/oo%R w7̝Gls7?am (uVwD#UF ?퓿54g œ2=8?8#$%26pފ#`@Ȓ$Mrn"(߉s jgN ,.RxQk@nL]SU];F~|q仿|r0tJWRd,}YID%,f[ "ԙK-[ )TErB<;Sok;h]k|V{6uF'3۷\*Q%J1όJ(`_ʶ0LUsY۹i2(f^fк' 뙆uݦ;8h;`ߝLIDAT"3I(Alb̡ӕͳ Ft"3h5D1NLqRsh)p!L];F޺<~/?%?&!P gJ\nI O nhg[>TG8=I;sje(w+5ZkL RҜq67_` ЍFȔ(Éc:+j)HۧɊu@1V]?џN|q* (RPL2I~_` P<3)Q`b,i2J+SË$5);A'ys.-5n?}?֕w4Ѻ%K^q:]i'1J\H=J]_ػ_4_qj)ſg*5!8S;.i2!DMpdsik??Nu8n=Ӱ8̑Z{C5=Gys_;?<^B3htWKqeAVεtưzD)bwv7gMq*u$Ѫv!?hjX,N<@w}${a*3}quEU-,ZBWy{b[:g~x_ ΄vUOW߷pi(U4Y=vh;ri2|_|w5Vl'^ YIFRG XH3ƸR]vBQbr"6L7]ƒvCfY2'{^4kγ .HMSBUij@0 BQarRR53>1dw]OFv'1ѯGL~^ @R6ƶDV9m8sYGOPtp{ve+ &ɛed-U)?^l{^`%d'g߼p Ui_ʝo1f 2SqcU{qC BA5`f&?{|glӠVe6$zlx68isHcsܥz[JϞqi}Dݿ?3Upog=~ӏpXˌ!Uވ<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<EqIENDB`tdesktop-1.2.17/Telegram/Resources/uwp/AppX/Assets/logo44/logo44.targetsize-16.png000066400000000000000000000012001326245125100274220ustar00rootroot00000000000000PNG  IHDRaGIDAT8ŒKHTQ31AjR N jcHA!A4 ]$mEж$UV"1 G6:qn8{=@hw}8eQ*pAx5:JQ !Ǔ޻=u>9o@]*{־өS.&l>IW]8vebB銂*d),Œ6Tꑎɸ"Bk拰M(!X\Mf[}Q֦/PJ5,|mv6!c"( dɈa$.UPqJİ=jKdQzmb4ϭ YTVP@mB A;}! A1IoV ZIj 54B=gb _1Fcq,tnub %@(&r59oIX/L( f/Z|0t$iP{a_jMcQy("ܶ}3o$n.W JMJ$٢2 аCޭg/[%9b'o 6IENDB`tdesktop-1.2.17/Telegram/Resources/uwp/AppX/Assets/logo44/logo44.targetsize-16_altform-unplated.png000066400000000000000000000015241326245125100327710ustar00rootroot00000000000000PNG  IHDRaIDAT8mk\e;̤cbJ6DDZ"XE ]X݈.QQץ TXSV1.,i0I 34̝{"΁<#O%st?scdn^h=ZysrRX\^o}{QOo31N;$G(\*\BqvԽs_ѨCF#;#L@$ZZ2G^Qٽ?~_7yOt31ι`#4f#MDfиscZx#]{V=9XGS;<)Nr0JS>UY=1䥎|Evd`!9Bs@)kBofWiH2tg: 1ZĖ6_LSiF;NꡦE f 5R^aOdr}Q$Cݼ4 VYzm`~Gzz=9m ۍrv@@54oH)h}9aUN|5ϥ9=ԁj_&J +$ʊl޾~5Vhƚ 5U}TRgz-cW\[_5r&F8GYku.N-OeXf2_&$NF}ۊ8j?1rpjYjJȷ6-ְR /gs fl&3BH< MOOʗcY0K7żxx):)'lW> q']ce"ۏb.^&\3(⺜.0IENDB`tdesktop-1.2.17/Telegram/Resources/uwp/AppX/Assets/logo44/logo44.targetsize-20.png000066400000000000000000000014601326245125100274250ustar00rootroot00000000000000PNG  IHDR IDAT8kU|7itK!RTP+^@7JEXڢܸ܊UA*v\Xc6bikIΤcI疙w;uĤZ >.< ;)G>Wb+FH9JJ~?tw~O!V 0c{/=[̿X!7[E)?04B`:2;D{2G:PjR+'Պ>S̽rl<{r/1b0 ڙ&p/P@ CYwDbO вŌhA`\j'0=8~DC#zndY#*Iϡ8G)j~i?v= #ϏOYR+,5{Ԛ=vH3V!ޝ:vtUmԥʵfv4!s&KF YMj7*ދf8}ݕ#EdyJq^F\0i2Ij'n6zk Ȳ/Ml \h-\%=M[jiLkZFp5v#rxuC,5f.{Z/n-KV6SZO SwB „qPjWml(X uf/?|dv=K\\kOm8h׺۾ ,mPx m8}HtIENDB`tdesktop-1.2.17/Telegram/Resources/uwp/AppX/Assets/logo44/logo44.targetsize-20_altform-unplated.png000066400000000000000000000021611326245125100327620ustar00rootroot00000000000000PNG  IHDR 8IDAT8uIlUPKK-BAj4њ\tԕ'pDM〚8؊BRt={}}w=.TƓ99Dfݣj<#վqS,妮.`;_bm?DV0"$\b o=264,&`s"m(V" %Hc+*OǗ?Ƈ(xͩ ='!0Bhle;۫x5ٙ"%L"9ݽ·Tc;$PTXVh*flo{3.T]\娯b8jRy5L!- 5g|kQMWS2cWU'gfJ\B浛<2u&#M[M[ݒ1塔hN^. "4dRd-0D *S̳Z[c 1xX0[(3+I4T! ,Bn^3z(0xQ~z2<[o.Pxi}=)2 R#{2h1ÅErKCkF`Ipe>$|h1{FߓA+̱f.YQK=Wx!`26Opbt L- Nڕ܌66vڰXp[kѱ|?s|I1\qŠi*WNmcGfNuCV`#c:A-f-cBZ.q27Ξ]]1""@9\Y8 S$^ B]dߋћM5qV=4hĚ(D9OcSOmPrgqZU߲dGʅ<|(vbwS5N^ikBSvGO?]Y[IENDB`tdesktop-1.2.17/Telegram/Resources/uwp/AppX/Assets/logo44/logo44.targetsize-24.png000066400000000000000000000020021326245125100274220ustar00rootroot00000000000000PNG  IHDRw=IDATHoUsBCS)hĀօM(F1&.[]ư1h F0ER8-eft3~bt ē}sy?-*aMoj>?4 nXZWykt0ο5ڑQ]K=ut0Pn՛X-QlYs=;߼iumojDk q7 ŋ-9W d۸*e}悘rBa_`,xAE;oٰ2P!\Vh6ӗqĖF#" JcՊZN΅|ᶗW;,{f%Af@1`e_ykE෪(rԙ?0~r<pՕkmN1 XO2ؗRZ"c9Vj%=?߿}ѡ#ULuГOߛ\N 8c Mt=mv|qUwgڑV’B4X80 NxB/t %T?wlKhEd ٴӱZߤRpiE6.k0Vvi%y+1]kr5Hǘ)ъ"]: ƻk^E}]L7ƢSX:}|hJ`N4W`97ֿOrUX@pjFǜnkH,o9YMr|Ⱦo3*Ls#mu3{NP8%JOn?0&nzARSa k̯IXРX"7a>?xwEڜi}Uzr )`>IENDB`tdesktop-1.2.17/Telegram/Resources/uwp/AppX/Assets/logo44/logo44.targetsize-24_altform-unplated.png000066400000000000000000000026451326245125100327750ustar00rootroot00000000000000PNG  IHDRw=lIDATHo\W{gLv2/6qdӤiCHԲJܖh(U?(EOj "hŊH*U6]n.Mwݙٹ3s=Ým{/>{99w:rh#^[q\n^m٤>>XwxKkӟ!4fW'zGY|Oѻ'?tܵH9=eS/OgÙw$(}姿%y7-nHjk'>HA?t%jwRLQ֙s_g3]-%: ι4!iхRgt#~죶^ADy8q4bG3 {M "Efxmܘoln?<"Q];XO3!~nl6"(? Z;`~ o+)(P1é;9ҕ!x\Q^y o  FNp6VDhY!rBw`sz}y>0bqB.c\ IKkJ&_@ik v6Y hőNƆ:׊0vL-hD vxV* "č&@<\&ISCE;y_YhXag4}pFFhD)EǨ9kuqk@ 0l4X4؝ϲ zJ  &8gK'XL2>1R#ԛ14ChujQB!A$Q$J*/zkR\+?}9C>Fn;S'+U%"ň/h¸m̬Kv@C[&כ&.ro]jdThFtiymNtq5u5;qQuv;X_$YSOW[̮7ڂ Y.]ߜƕMNtqzg./1zD!|_K3-SgGؘ4Rh ^ a(u,ԢyP /֟I̖7^F>IvM2fPiY6#gε氋3/qVMSEmivZhn @P.N6~386 rroϪ]#!6m[ȢTc)@6$ϭ_=/]scaC_ E{% 4.K+/f8y.zށ;auխο.oNg_ak>IENDB`tdesktop-1.2.17/Telegram/Resources/uwp/AppX/Assets/logo44/logo44.targetsize-256.png000066400000000000000000000352171326245125100275270ustar00rootroot00000000000000PNG  IHDR\rf IDATxwd}'of>SUUvfvvcxI"t##E:#"#@b $\?gjSW/3ǫ非鞩c=//3%((((((((((((((((((((((((((((((((((((((((('+g+e+*5yBZ?(eA@PI*(Jee+}POH.$!%` R7<-YKI_r#ft5R/8|AG-_DQHb~ޗwEmb2=.{k2ϥrrktTY/|M\0#ƍ-}ziGq!>3B#_9wxOxxM@E<+)>'F}# /z8[p%w$Ka?n0i?(z*(V`.җR4z'z;<aRE.׾ !rco9ћaV/V\l]| @+!FOv?o{zFVB+ {Sf7TP% A@:~v GB: ~>h=D\_J!贴"?#=b&k)~Q5ba(UR˖OW"GujAG{`v%B1/ ,DQZ!ZMϥd/9% ǞzSy%_F0u&D)_R+V/*񸐾h 1ɾȽ/_3)uzƳ>rQ@V fdGekPg^l0n% }}MbjU/S@ ҄RQ^Wnm =֨Ņ[S+\\nH5Q?qV/?yh2kow<z5J"wf&T]V.5)7뷟yɻ0c`6'\9tJJZu/%Å<l ?j/m*(#C&d TP*eՓw:5Dn}v#=֧l}KR7$V؈?r.-:zr߀M:#v_d8\la*(WRo{ +嶆X= Xb\m=rA+?B( @kKB?3h?x_1fBu}zۯR#ioD-l PU*(W!M6 !{zc_-hDVKQB,r~T@LW?h@W*UjBqOɅ=†;lˍY0J0q9(gTغV|)K3nj1w=o=u plNWS<!/@i;b b2J0vE G܈3e*l-O].!]ȃ گ~"f/h%PrsȻ>(%VHǎ( msK:$՝_5[Br-woyf~᱾K=~8\l}nc&Bƅ'@Pv`lBcbwlv3 ^P.{ A !1>}֫41].Πu>,d\L-9% oo\p, @w斺괄h};?Һ|! \o譆Q1C"K !$m@6A2$1Clj\x/Ϟe=yuW)JC8[i\g6UL?/kt6Elռss(!H:ɬOA6 @ k)~K)Z>7hzG{Nc[iCR z9(&_YyXl@ Y3 :#8;4/0*Ԗ+ބ Guj>v?k / T|~M2)b-3I.6sٛp\_rJAv6,uy&~A+z+O\!|g%!%!h6kni_lk 'D_vD_#%߫zE_`&\Ɓ)@[ VgMfDR`v!FO<;`?X`̸"zkEHP'ȺJQ G< aG{ VH7?dnc%JSm/+NS< hNiZ]ل;l!,[l(os{M8[բsts+ z8 h9g1DL1Ou..S`m.wΞypZ~gW ?dXkҍPN9\k tf 5VڻV#87q06x،/Z'v^]7@TB_ ͧ+1Jqx"5$%h!pC5tι` %e*|E;.%u})!d R֐`7.sJzYd=W"F)ۣhu=KM@Ӣr3/#RS|`)gbDܽo~^륝1~ξ.=XN]@{vXR̦rW3,Y`豵G{#_5Sߪ+Rc:G*炔_OM0en]zh`kFK3h`Wn[)%J'f}u w1p:S{+q! J{׊`>&&1=d4.-BɻݍƎKe7_7J\`&bfg$( )(b~)uGsSJf(p)BJiitxf~Г&k^*US%7DPAGc꾵 ʼnpMȦ`w~T}[_x;B_/=VDɜ 2ofkw+θi~fjΞK\.9% ;cF}֓O[i2 *}dER{˕K{{ZlD뻴w-8<=pwAYniK)Z,xoW.u-O[A~TГ.x r޾V @[L,r2ͮj+\jЗ[iQKdj^㽑c-۱[n zfyS5-[w4F0Lۼ}Oqn}}vZGmڮ؞r₞5vQb[vWN/榧rbRo` @@`fs}_ Vu1c Sy,lýBJhF4lW"L3aHTU,4 JekMɬdǃ2*BڢUڻVJ̨  iRϩdTгR- s!9&iˏb@JJ}>ʂ7$<~?vHG_W%1BuSg ~>e傞c&YW¥DC@o -׸(%˺"!Z3e F?r Ko K]%De^^l\ZD`MWr8 ~N9E̔W9-ݛxeݐM: :DM)BQS .,1 _w~vxh>x@^C)qēy$RoK{{[mF͜LMkJ]TRJqtwLp_WCloW|"aQB B\"_NH Ccmaxme%Y+ݓae5W. KHb,y|?;1vю|ޖ=z] 4[:c<3؎g d|)Y_H 6mֲ%M#i\%5b!܃y)qsGo%Zo|v_SQI0R{p|q)a4FaTF /dgRDP+*`B߃}EgfKńDsٱ?=1jst{Hgxegաg-u(!8;;-o\ pY+g8@LEmrPt5X6atLh:?8\:q7tfˈ !rQkUZ3# .ߨv+)wmm Cu8ONx[iNIY@0%5shhSYtٓ S_Yxlo~pWݏ\߹O5QK`'g=pVr)V t[6.!IH5E.Dtr1{ѯ?]fC(bSײŠ'سS.lTio JL/br[ 3"i(ASGV9Kh65\L}f|vX_;;yݝh2u`~ i'8_7KJRBJ{h=pM- DIBvh5weE6fT4bSg̜wߺi#{]]aj/"W@PM-lYR1 Ѱ1e;lXQmV?+H! E=zhH'?Gs0&!'n^ʝ8p~oi|pWǃ;Z]N Ee2p!c!o©R`=QX!MLg AR PO`Ii $rJIDAT.D]4wl"e1bǁ#Mi_eϣ{G(X![?[("#lY+2hj#ލ@Ap&9Y*T VK,d MF|*&]'o8|S}7>{};i2R*E^̀ ̧ HdK=5ވՆWE.p,= ӯzH Q_iDb79^R3%--Iύ3y,No+"?kmu\>\>l z 6 zVBk 3#)ﬦGjl e. kqHAB0`tLzN4JJ##t6-w؞k#jwKz@ \1@wFH:#8MLŘ޸[3,<h:' kqhMBd31b"9O7=]|_(Dť^mƨYګZvo4FFs' R^Tr˫uS-O -,BBaѳgϧ~?΁oR!Hi-TjlRZq0GbyZtD.tpfGMYSvU]{#h6D AH=r 7,wѹf$<^lr09~KW}_;O)B*T371j*v1Ȗ!Z7,֜#wki\ݬ-B\V8S38 =ly =% (b#6TO \xZ.VVR3myz<;[vEF@zpeP͜;:|3sBHC_{VHWtRi,3(*SJ9HmX<[|3ڣ5Dgcd*\IR0ǦR/MmQS>5QܱRr& 2sĈM}5zCoej_MIfu `@؅#3w7!%-=6(T]- C nb}wuR3+[Z0^\<<)uDվ%B{CXXF5dɱ?E_ k#) ӳ=V/nA(Qy1`q[ZpGf !vtB3 ꣖>s#EjHݤTX/U #&݃ 9g'MIL-d1DV|3i`Q`$LERhuV:p>>:7cWV+%Xȹ?jK3=2zZsBbi_>tfrBARQGc# ١GN~'u^ 8X8{ r` r!Bt0WKZx(gF_-l)馘/`υD1Q>fpldBL- 6x(tXo2O 3:2|oaS /es |xjRD˞]tNuvh9䬟#j!ae+(\\t\L- 6F Ss&_ a^ xS(R PjR7L>UFSZ>WUfQ"844C\,A`"ʋ!][:TA@=WG2jcӕk&7AIC=<ӓH=FQ`RTz\x=N !D ?A$Hd róɚĴ^QLG/ "~1TvB ")=RB,Ql sL3C_@k!H4fLz5g:7/F)I?5l!XUa`.L!]P*[*\= MWͻryiU֌"s$f D- ӧ3RJo9^ii!>)IFujO Y>WCNe{(oP FSMӧuJ vQB4F1>XLR`GW E37TV7HL~ $a4F.O'@2qVgV3J*\/_ #ϧCYgjk}6je(8sUF|]]Lk~@JZH mL<")e՗+lN,ytʈ/c\32@J(=#de̦޹VaA Ep̔VS ؟9=;Btn@eH@ =g΁Dޝ_w0^a@cs>#WɰbIYtNzm .H)4 E6z~>shˀMx4JsPtp|ó?k~tmj۾pHcdm?@!\"өjƹtF+oc? zZo<Bӧ·>17%89>,\o@J)VbtˬRSy^xWN/B)90L[X8R~/n%fjf2*IJ{8?9ދC62`jÉ?hW&Rg¶wH49 nl6,|w>wxre6 xɡ{JhSr$NE-<6|{= 1g~z* 74Y I$)8ӎEkJ9~/ FEnF5:]̎Nĸvqк71EA\yϯ9l@!Q[|ۇF:Iڵsz)ono:)ih˱Gi'Oǿ._0Jpz6sإFyf'R7Cbڕg*K Q2`J!߿=>Rkv 4RBSF[HDg~Ͼg/L}K,Rlt$Y*T Z8:VagNF 7L~M#=#o5i%{9 MƇ>w~/>8UQ+. %HܙnA*TR6@ މ_@Hq V3|į82FHrz0iJCb-,8"$ yֳ{$<;w|)Y-$``:Uu䘦ibTPYKkg ,?>ʟ^K .eoX{_x '&?̴ֽs4e!a"nh k$_{ۿZLDV^tȖ@ǃfO̤*,3]b"U8Geb!y2bYk \.1i{Ry>fEscw?{?1Hesy߫Q:e@r̦+WC81kG~m!q:C\AZwhe׹3]KJr^1j>tȖe*T^)|,䇯* ~_~>vPX5N]0.JUfz1{mxh|~06upT Bvl*/u]j" cljJHڞRO~ ^L͝s |zpKYikO:FcwwN1 7COBLr7-'a+l6|5i@B:o7?95U 2gҎPƗ j.d =nlE/w~>xƙڦC҇nPZJϐF8'YgR55S334:GhKw'W 0#9kY{˟Ko} (!H;svp0C[._@4'?d ApH7};ah];FhC{Bq֛ԺvE\bW!MF3d^l SZABљۗ.%"oj ik\h(YdݻFI%L7 BHk)Оͭȟ0̏Zfmf tV 6-G))PM8%\0 0$e k,*;;}m. ۱y{,ja\#qo6X#~k󟞚I5_Blc!wM. K] Gqh:oWB!jhf];&aFiZ|[YB9mI놢Bb׾o'1XPBsscanQ$B|iZSɷ/U@T5<stwxK-k tsiZ}P˅CD wxmB~O~v/yb1?lڦ4E]Nct^nR?۫@u܌XlT~lyi_6`F@33NM)< ͷ Ϻvj}CQSϽzo}'~6M-,[._@(.ggX_VaxBo~;&yt%A6sv/SJ1Rhv,tJ-]174٬{#_/IֽkĚr Fv;쐑=pvg_{o۰a-G+AT^ oYX~Rj"`(S VBG*\/boͅtf<ͳ$utF+摐}dyzoo-lh_ۓC:ߟO' mZ`N61LNN?96s ۽ ( B::ۙ|a.g];f`=BvcI^$N.٣Soj1ZɌ88\n>"jg@} [Lg?z~6fdf2ɷ3u݄>:Ќ0$ $ Ds3ggj M7H)QɀF sܗ?=/8pN:̭+D F[TqfO|4F y7Z&h{2hYգHBJA;3Č L&-;Sӣ7~&jjB(ÁguR5Jmn-.$MispQPgsl&!hZk} P6BP$沞ݓfKAdϝMTȅ7HZs%&"VHpjZ"):O;icBEv$93y+174kX5kh$+6E4rUe=%h8!vGtNPK&IJ\JCǺwOZ:GM"?O'GC:.ur,SC_ |pkS3|bI` |"3or;b;"fXCcǣo8E! I3:F@"v]2s3G9+_ٶX2K*cxb R-TPWA!)Q\?E'Zrqjnqg;hưQ͚oK /qM HAiv.ݘ7ƥ!L6?#bapKO ~땻K3 WIJm*[AbEA:%0 %"ȑȻH;޶(((((((((((((((((((((((((((((\L0Z.IENDB`tdesktop-1.2.17/Telegram/Resources/uwp/AppX/Assets/logo44/logo44.targetsize-256_altform-unplated.png000066400000000000000000000543401326245125100330630ustar00rootroot00000000000000PNG  IHDR\rf IDATxw[ut`:9^^j)(݉K(\'$N)~9'{q|qmŎlˎ$۲$[I :Nqp0bf"pଵZ{u [.)d x:~0cSH` ND 2DvTlH$LL'j.U PKgcҼ׳{=Kír58KT$~g/NLLB0$DˆMƌ2#鳇GD&y>}ͳl#65eb]j%Imޠn\7pemM?-ŒKi i]@%3.I]B)LLŇ#'Nk玤N<:}=sZ0ynJ NwfW7_?x5j{V9T  N`]kޓM:FĞ]9n&g]Kp*mo9xͷ.Ni=> [/Ջ/0Y i]p"u~?gTzq vwzOG6)$LYf| {0}` %lJώ=< ?Q_ύv3 Xf[#sgw7]sԱ` LR@M]|v ;-\Ӹ `llyg`;Ps+ g KETaħ&09뙟dg.vR \sώzo[W::4@z&sY,K`LόLŒݾ.Uw7{=]ʦAk !;rrK_zԁcݶFUKItCw_ ]c j7m]dㅙ 3djp0PK}o',um WPу|]>?Wx c.^JNĭ`矸$;wĺT1gHmj|{;LQ"&'7ҵOOjƱ XA ʮ-|˗гk?rA]%x-5ȣ"O{ {te׻mCD в` x0S_=?X쑓X'Z,96?/E㶲Y.at ˮhM'~$\e{Kg;?XHhngȂP䦇 l=a%@jo[D*, oeͷFbB侃kN5j 'L#,24p?|[߭v&sXcJ`-)[ov"Nn"zM'uj.}rxkH P1_ڵEݪ.,=-f*6:gonW+FW qu{o@#=rYciɊ馝oW[:#}=O@#+<{7їt[L'4\\ SGқo ]~/9s!l4wGz|-wYqE7;`C/{E0`{>Ol|eӒ"*b-*{˃?{ܒ A"^Oj{WLY}.u;N39mrq3]u誫f_ꇤg3c%P PKW}HbC/$f_ꇤSS%P `SWCWy-xK=!,=l \W$i4P ԛj_U9᯻eaH۳e?t5dhuԓ~4:VmK7.b'WozQ s.kҳ \~gP>IaN@(8}OjF]WWAO?k.k3lپ>%&#>aEW~23# KVM D-o}{χ?BHnzzLk7{CsGb-Rl? ܉=.׽ᗟ'c_Š~ w>luWqYo{-}O_Pں{J[ ct-".>!SҼ7pٶ}+_z ˿j]lrqYۻmVXIcfuCC`ۍ7OO؋T+y>8.bW3[|d5 _S;Ku $_|?7HVdqPM;!1IK]An 㻟!V Ih ">O wq2l+$  g?ir~j`,p7ϾTlh2/Ƚlzov aBEٗN~rk #":8;p̩Lr(jG/1I v,".Ma\P[HsM%!LHҲc~Z-oQ@i#7?^N.. N r#|6lڰ s@6OB 6mkpX䦝owK- aBin|MKJ0_a\Z` ]}u|3OP`@ҫIws]\j p_Уtuo"?G]ey$BhxFZ(UO] bLD]Sz#p.U_æ,+dP7 tkNeN{hp_ +_e]ڼe湯'z_o4x-2JkW9;1>E\@~MuqYYHK~[ D:ǥ\g 0CCdKK!WƠ5Jsh_z?%)Fm˧^Pz6 {6&Úu)n?59G=fxQ*';T< kqE!/VNs&wY%@$pG_;G8&$>8ō5Hנtnz l.K$z>䳞-W"K]\ 肐5~a Ia(69TDHhi5ƫ1L ;||?{nef,jŎp"|#lCAn/B64T06εۏf$:`3$Bk45`@Pں7dϿy {Xb]ƼMw*?. Lfn ``wtg< o⋇իh;o(EQ zm7.ܡ?eBIP9-<0[Y Ƣ:|2T`kooIyXD(5K>ks 2& ɸ'_9@E1D L~殐}0u䕟r(][ܻSdS;/..6&~.? ᎞6L)^jo_Sq̎G YYM!x=(][ oV{^ {"-П`W9öf pZHR5BA ߶r"@ $Ρn'2Smz)SipnE,%bJ F+tKzm7)GS;`[n6OhbsY* գH!1pn6 d%k/ƐM-w j(K Rӆve{g4`> k}.Qe>fRpW5f}[Q|C4cx2Mc1-)0SU5{aӄu$w pÃ!k>ȘI1+?87A,+ 󜀗 ?+Ts/ҳmvwTlqkUZ{ݙk(Āă!\"1dMBrk cɩ4 X +W s4`ljCnnϣB+)/NHy. 5fO0/Ŀӏf39UX&8:"Zd |e0V FQL⎻R!c*+CKnMZA]*⡡0 b(1kh/c1"L̦K`k1)pb: YY@.W  lo% KnAq`HT6_Vml=VqOoqm>C7 UgzDT&A8z0kx~`&S|s ! q^ÄdhP6_v j{`TT-۷KHkrLA7*]ۖTl"(& !7Yd{[LZ"5PJt ށ+3YOwR$[.+Hp_ؾяø? ga̚p3TḯɸB~!2ys2UݥlЯh_Q_8>@C Rd6m=@.a;rpCxh(k})1-w58CVJb*)lrU+%A{<5iĩt_YcJ@$~ H{=(X8Z5lkJǏG6qGO53Hhu X#SIdsA옂=h:$0cN8F07_ - SI(\GJsx&% ']v% <ƹ1rzEaf:9Ke O'1?ool%lI?yd0{ҙ…)Eۮa?1F* %@3ZE:^T%Ȯm=Rq5^Pp  zCspu)$u O'O9 ?-[ϩ}oN$Q3/r r= \ l+z& pe0!:y ԍC7 ޞ3M2ăa\Wfi3X,]H)C[-Aϊ ?`?HY?Mw}+;3cDX H@k  *0V;n0̙CDŽ}!<<U^Y3'L'N"KqcE=yŅ83LƄ,3͊:u*JeNwU,=l14 $ŃCe h*6otK`U+ξ vEp[ܘ}5Ho2ѩ$Y0c&wQ5pJ!k ees7V)7$– l(#39S+)L1>™2><8[`Q^zIp혂A|YED 9[p=<فl_k` A„ji::\Ȯ 5uldu}:'^Ŀna2@#.SYrs9s1)A_{Ț޻~$c@F7ql2R?/N B9.DICb ܚIH H+H)GjЉ7<& -Z -m+.?vn cGo]`7J@ω|Nt L ?8-0DR\dy48PJ>  MǐaBBI*n{ϼ7\,+({" IDAT/0xrķڋu\J"ѫK74"ssL技PQ`tԬϜg]ׇ13 S"Hg[ҵ Fz 2  *Û"aM9WBZ)Ṅ 3\}A =?66A$B $R_mC_AaRAʎ(vq $ (]:auPT׿ahL* zsiYI7f.L'8)9-|jopjk\(Ƕf =P@'( 0UrsgtB׬7 %jm]C&!7ecKo0.k#[r\GJA3FJd*,d-:^UMk=q6`S^q{@$`$YqnEbK3KY?rC6d6Ma@WnY!yZb,FS'w򩽫gI8=̹-9tfJ>8R/_g`$YR5U_dr>HJjX1e 7P0P(8i!?LL0Z8AVBO3 3Kj0Bٚ9/c%a!W|U8.NI $%},^Hr`-vy~k!ꪔV'ͦMè"A,a#*VNLMx5݂cn ǖz ;~0.˨]u.$9ՋX`AHT+P?bϳoȸ'"\)ZC>gd*X=;B|V'JJ4ƊD8-`ࣲ_X>(LF  R=`&\*$ƕ fࡡ0x!skkMfA>; } CP[K4Sxq끰K{d?i~'zmcs:> Z)T{McEaa!&DO*ϔYiI$3/) 85[k9Ut.Ćw p̗T++|lW LL,$pٛhSC|zG!MmKhAb5Ͼp`c)L3*zȗn 5s!sDX4ee\NlIE'9;PXx_ȏroSP}C@D.k۶4/Z!䯨 Ix~Yd: E|j JmoS 'SH"&}BvEmnYB)ڑ? cʔ6R%9@C/_ފ߿a# 9_|#:*[|cS{74ɏFxyJfT(ҿ?`pZÌ d6 Ș* N'Ch'&P = FSX ٮ#)]kI"A7nνpLm b(W$N_`^8%Uv21KsPo BOcWAVҚik>{}ݭA wsSәvBĘ/?䚕fuW[i;Ț x2PkB`kKA)꽹5qc3iϦaO)%B$l`_h,)DOu/ :kv\Ġ?cmXXJjGρ-E5c0䕠ph܇u10X[2ct:"χ ,qjQ8ÛI$ta Z#({˜q:h9]M*V)F`#YpX`x14$4p` ttsBHo_ ɬT*Eo_l8Ao$UK,JX2^qc3s$TjXõ 񴆓3)P\Ue t>ZÇǒS[˗]c%jmYwPUE"AIs>@0f `ăfq\BOS #!U@ 13TK̶/DbdV:߻b(ư|JE$2AE)#1HOB8BėŠr~l"em5JvK| wE83DJ@ =/cOv;"s[Z})ne4snbl&*X, 4=n 11ߪU,m|T].V*%}vQn 0 2Ba9MՙAX8/ 3+q j& 5JYS0SOIWb [-D8la9AA8Z%?OE_E!ހsJ~wNd~S!O)rOQq3QUfx|/AO5!;Ԃߔ\ʀYKkVN?_ZVLÆ&?Qp$&L K/P_9.`ҢN>|p̳+/@jkD65S2 3E@SC>#UVH eY4 wqAo2 ) =7j՘|jfۀy"qXgf2BVL!V}R>7WsD\zgvxJi!f $2&-c]Z0` @0i<}|*n ޡAWƬ^2?W?z& =g}v:ZIdM(2/Jv+w ,P5t['UصHd_<c -'Swh1+_ XS3“G'Ԍ#TAAM%Ϧ17 3 u^FbhIPSp.)hsILzE.@a^b|>QgeYҲE o/锎O-xSTxLLd |8sx =a/nޡlͯʋSГN!U/Xd몽Mh"P }e9N=|e*ͺ,'%.0Dr6jQCqK@ zrt"Jk,Ad1䕁J+281őa#ǝ͸{HEB,ГFl o,!>5[-2g84f,K@@\,_R"NDaK+kօ"+Y(G&3=mm~5؂-‘ML3\eH)OAw[_[BGs4[I@nn4Aʶgt{+;!ిjt͒q" LLEIˤ 5:^H2dI abX Fqi[rc*CJ1!PXU{k g@J3qh< IuZ=$̗wNo+Obp/ o/m WLZ&e& (7F|rZn~]grr@Bxe$ {(0%S{Af83k-Z"+AYqZ _mt_yEOP65 [ H4π+6}U҈cw4j$GGa_4nr&ͥ6+Lmg(w=n^I{3+Kg$wwWa>>j sH#z" 6жf &`9APP$6*lܖRbJ-*%hvXɶa8>7}̪ESY\CFc .)ǧ3-X'ȹ]~ tcP JcHL#'#'ͥ F)Nd!ɞ)\'2xc,+$"p@ 3Na$APWouq_,Z8(Sr[ mwsvlI Q~ +*g8d/}M5̭%2,*|Wь´o|?Kۚs~󨙘1o Bsp!Ew} ?̧&vL"m8)"1O`&k-V ٗ{W^oNqydme~.A$gƅon_Za#z8܄žY< e  g30 #B,M`h1'U2er~y|,r8#}Yqh<3z9362`$b{ IۺM$~c~2sac2Ƌx ėP# @ `w(Tγ7$ {@"\Vܳ5A8NgMK`!K)()$Mdqj: Jf_IGr!L'v/ 9 '1 U3`2+pp"Um0 持$3r/tsdDiTEf( 㯾Ԭ~zܦȜ;_8v\M.6YZo:ˇqflv]ơ f(NZ|_1h Hi2 ` ` W_WݭnėNe0 j}U`ob( S@ZB kG~:dk^X+ /Vf釨}cXi{r ^}%hiZ$506qd2 UZ7Y=8|3|,Wa /9+8wdĹaԾ=Zq>`R69}ȞZsC3H!UW*V˕KAQ91 4ᑥ\yu%S3i\kʽ;{[mE+/P69W:t>@tZvYӘȜ|B**3XZԌ1D ԐB3&t,W_KoTSGveud(=u ` -5We *:@e?cs($y{F{IñY ,dSKY|2>yiU&Ysj|~pxxBF 8cg KB8JIz̾}SvMWI9q~4i|U,;`hiWp-"Nua H&B*.k d̢K_ {G$E* հVt1U1.$ }-P$^t5 [`(d>WH<\ Ckf/0Ou߂U7*ZX@nL1ߢT,W{8vE X\|SM$P~P0A(0(~qa6*N]-`4 rk\ҿtϜk}b+O| Ta[gV4io~c77UH# Hj&GJ_cōk@ ʼͯHѪ(e^:!vEYP%n{kO8 y?HI1܍3)=߱/sML&g19 X$0Է~b]?W˳$s@;Cz PnQ k2 +s&58:sWժg7 `,x ?z3m Fg/鸘IDAT)|x(m:VI 5^[VjNS|ӭ\-ְK[j`6Ux|24>g Wde=~V%P:<.pc_ jK#q<v_H8,2ٿN_8{PO5Haƥ1d`xL0MnQe|qsOd//F mLR𩟝mNN% CcYF&(u(.#_iL/E m߳?48X@dcx 3 8<2OLo #C 8Ct"ϋNM_y =8"/޼4d1<Ʃ 䢢E׏3 Cھg\.,"gsB Yx/} sw)Fbp#qxd6A$1q|8vhq@+.dM|O'/~t xZpp,Q:~H?QŤJ\쯦Gz6` 0%2 đ Sق/skF̩IpIq>dC&FtkY$I`3s~WwEl"g4r?IT{H}Dqi,.N"C&S~a7X=g&5x9ᆮbi T! |q0^,+ pJS/z4Vw8]6>P'xhmX+#]D|3)Wa2鴆{[P%2ǩ>:ZC;g>XBN-7ؙ KgT38^=?X:nh_?4,E +( _~܂=$sWNb\FOZnS$|p?<6 ŞX֮s@z18 K5,.^o,n7]{5SUsA Gx}x3H.%TߡPr 폁*87SGh++"T0fUF'1FײAh/}WyX}<?d=^sN x@5Un%IWT0Ad UM(003GRdYFZ7QL$5ׂ`.A@I֊D ?$L~Xv׭ MIr劻qm[WX)8ZzL#. %ۊzUp(Kv«gq0ZβSX$SGp6Yeԫ(:~``i,aܿZżp2tR[ob @9'`+*?01q>@O-sƠp*a2|O_j+ ~aƉ{RO_D V_}/ SU?IX0YLj\&ENTtb,?=>TJ-P‘Lijxg3?0'b&zK;kq 0=+ VT,.4C )ّ2-XRof ? "1@s?kr!y1[X&(NEpCzzU&OY;$Π޵GuwνЃdS22 8O:vNיvƓqƉqcOƙN55Ďdc!/ 0H`q==]iV>7sgk;}9G>-BJ8ggM~/A,s_ MHQ)R&z $NUQ#ef?cH m}j8G#_cW:2=p_ \O@~Ƴ"{nrog{/@ikO0&ST(qQ>ʯ L yx=Y["J1+M%B@N3mO7`0ڸ+\>h| 5&%Rdd{I?vȟkLO}CaqC[yɞG0dU1nz~(0QHc ! rl!h3=)78Be߀"-0K'OƂ{G NkqO!C򧾡`Ar[vg9kb*@o/9i4<33#{HsUHL >d)m'xȟ' Hg ,|(ӛ!P@l0իzh/;b,p͘;r"O,WP2ޟr|q;Pn?@ҥȫ;9 ?t,sIHƆ潏} PV1^PjSvF\?;J00L 2yɿ=a ~阮utRpū}}54}Cp9')o_~@eG^f~SO~*]]4&H_r% &`jʷ>c~z|< J=-8ǭWےFKe?.˟mb@^zڿJ~}8n5 "06,Gl+?^УL~`pKs:P\U܂SKN ~$@io~j~|@ :mQٌ^Aږ P:y(^oU Av~z?LRiU̚Ǘ^{9% ܐ?M~(\ߟp O 3)\YmԮYr"v3!wPHX'w~up '@j8 3)mԭY+|sE`,ϱ}#"!>~`M[~_@Y#?@ہf04r =+o:۟zۈ(Bl/{ߠb~M^*l$%Ç^~= "0V̟f\$ _1˯c$?&t?&{nr8}6RD [?O0瞵9[/8H\={J\>mPEHsE[% ]JwL^e3p[mLm>װ5 AAbϭOtP=wtڏ}7iw;(D@>t.E H O(\̧u}ȓlQHRp}|G4~iI'sd㷄8(B7wקh;</ M71hr>Tuk"6Ոs"Kf;?zҫ :`Vx*6kl"9(QSŋ׿OɦHk6(TО z}b'4jR+7FK7@0cfx?|cp=sbrAq5L\z axt88?}5 (ٗ @j^ DWks16U-[st?im(}/"`r}&8_j% Pg> ア#v 0À-@];"PL'*nGO:ZP ‰:[OǷ)[Y~Jo4( X %خp44 TnC 2E^|cP-WeL(P _g>7ׯcqDa7h~/+m.9h\th0Ja1>kVN>@Wÿ Edw(~2JE(T1|/e<pE(3(XH%";oYMm, TOg'sB@0m4_񕛾n(J( =[G\;륹t]~2JQ4*,Pjn4oXB q`մupxܕxMR 5,#zx-o5_K'CƑJKe_2C{Cd!(9w8_ŋVo1l @vw\rZ>e[yr-~[}GZ(}|T6#,~ W C}Nme|*5uV߷Y- 8E|ÛxÏyw/@mxApdVLϝqN6qN5=g2n.tЀ*!j10Al.[а7l|/[ג")l/oSq=3 >]'M>mWq#9<@[ܨbw-o6莥wS„8yv$AAF-y9qNV8v-(>t/CW=-hݱ-Y6ew޳fVWcb TɎՎCuw+gN G#GeM餞os_&:W=< 2 WelѪ{+fIṶ w l2()b7嵋E穓҉6w]MzӖ]I/t@ ľ2 ϸ,f^F ulf<Ϛ A Gt='s-;c ڂ kzꐑp;M:V޷S_Z xbӂ Fҵ *|V].X4c|`x&0KI$e@jkN̽M8E@OqUtwɞyIC=R-n~ A7% ^TAC VNg!#SH6}&+CB`F`$!aڡRHHBXaFed !lM;8 |oFz ɄO''zӖ:"%0AInMt{ݛdRxu_a8$\QR$,fIENDB`tdesktop-1.2.17/Telegram/Resources/uwp/AppX/Assets/logo44/logo44.targetsize-30.png000066400000000000000000000024651326245125100274340ustar00rootroot00000000000000PNG  IHDR;0IDATHUUǿ{ssgQgFgD 򁥥&F VX= 짂@(f8ss{Ý;G1Ck}{KT6~޻G143f%)4Ru`,)mZf8#bش"؁QKoc]%k TO6/mH$_f?R=p&,;mlm[2 &CywxhW`ŀh$ڼ>Ds`mb<}~_Q\4cŲfj޴"ŒbCi$ 4D ܮݻ\ʘIƣG] O3<H _04Q@uBòfx+wQpQUn?Vm'< B ד$VQW Fy^KF>bpz|G3ZՅMbʦH)#<_am;@ cŠߘE7&,C<\[5b`yf ,~1Bjҁֆ*TEC|_` A5mSnQQ D9ԤL2$֮ 5/X#ՌYRPQsֆ*v &ŬfFHH0cHBxF8d>e"<ǿbНr;YYz{vM}GK՛kͧWJCz8[@$ƕ6 "ʌ,2&Afzo63G(y❋#%K[hK<22)Iji9"EUބ":2$ `._y3@Wҹ-[\AgƜu N(@AJmƁf\N9XAk'Ь4=$`~7 Y'pzE X~+`  I C#yWn\rGPRz߁!!З.^:5FۓֲylKb.bx:eC)ЋLڝ Ie{#9u scCc.ūgML FI{?"{}S|3c9Y\fD $z)ątoL`ΟipiS·)u?-1|Dj<_ "o ɡrSIENDB`tdesktop-1.2.17/Telegram/Resources/uwp/AppX/Assets/logo44/logo44.targetsize-30_altform-unplated.png000066400000000000000000000035401326245125100327650ustar00rootroot00000000000000PNG  IHDR;0'IDATHil\W7=8f%MCJJR҆!%, TBBJ? UBH|*@!HEYYl'/{<ͼw;N&)2sϦC-[dǪb͋:wwGEI/?@Gw|͟-<Տ=VDW@TL 2kn>\>55ƑEK;Cn.+a o__;{㻿S+61"|zR@ڙqnCo7o*(DK^@5噕선~ۓop=sDbʖ8|V ,]粩/ߓfMKxD8Aah0oݥN}cKb/rn *FH4%z6-N r`ә╈/[mh^W{(n^ ٹZeK{m7?F>G ̫ѐ24-EM$YF)ŖF-hk,L!9鲵-͎ $i;# \-2Yp~1qoI"88 (Mtծnڰr$OtټLEVr%`jk4ш&tg(JaM ڍ p[\Mf oŷ<ڞٵ-,ow@(VSeI#q&R˙"Z~8 0Ke\kbkX!B kЊ&M2QbT1g:bQ/j='cZk09!LFǴb_8o_hKh{I%ҔIژ {,WpZ"dQk(wcm XBTC`,,Ng8Jژ9XHk"T4r\B[UKZlWgJ+BP!!UMV蟪yJ᝚D#[MU+zPQҰ(+fe?h hC=C\8F[Npt8FmmPZ/񳗳@a-r;F"VnPHo}.ˎW>\8CyމZb0 #aE(EB0wF\vxZa.D*{{ ۛ\E4$o?&h'x%v<5bȲ l՛+2<(p&H]5e\Gxx^%X1̯m+W bX5J)|Cq+$¯A^}_O|x$ 9^"jL)VD4h*y) L]sI1w}tY*Ր:wZEN'T-x:[M`W~o6֜-S>TS[ ƀ8e패Yk)S]y7߫4y%8?W\YR ~~8&b}^fd9<]w;];&@r>2ruBp_o{Ir]{;nJ7R-TCkJ1'>?aOb/{SԆ3IENDB`tdesktop-1.2.17/Telegram/Resources/uwp/AppX/Assets/logo44/logo44.targetsize-32.png000066400000000000000000000026341326245125100274340ustar00rootroot00000000000000PNG  IHDR szzcIDATXYEuϽ00#ɘ71 HTP" (D &dF>$<ˋ$$8`(:,0+3skwWW}t*tUN4pll=m9Aj܈X:?ztv6`Mp!l0,^iSjs~/[O'n+&UD@c֭omhpWz 2 & dz nWO!]LcYutͬҐ/ t-(" W]W۾޲0n5 H< "Q%'uJMW}vcҒʈ)B>A*dD@h3i .Il\ZYޱ6|UHcsO31t*:ƀ匼0Yb=kmֽgp' ITɬ1̊Aaq.5cgs;Ճ}F@X0L<6q u|ʠPەԵ*7K&ܐCپGޑN6|r2{sܱbIENDB`tdesktop-1.2.17/Telegram/Resources/uwp/AppX/Assets/logo44/logo44.targetsize-32_altform-unplated.png000066400000000000000000000037421326245125100327730ustar00rootroot00000000000000PNG  IHDR szzIDATXk]U{sst^} e:-BX^-H@ĄQ 05*!QHxDD,-S R-ν{9kܹ3m֕uu g V\>5}N UKAA叶ؽ45HRW|{Wygenj" #Dթoۣןw k0T"$gJ&PޮÛw^0v~Co^c,RMK*@?=r_ާahQvɏMԪhvV4 PȯkK_lj `涫o)*:n=KUEyk]8O! `-=yQB@$,h9ƶwRs_OKE 3Zsgg$rsXǙOV03VUEDB8va[[$M5W_=WFUQ30j! darkhV+dM^qwfiE RHiK;\ѓT&aD5b?p%[ J5c,z>g[}dƞj>*?9Ekhc ;N1DF?+]Ѥyc ǎwת_}-&lEZ΢Q%pxazbGJ$mSXPTstv$=oҰz1*̩hThʣ;yy;w NXPCv8FOyxo=(4)]u]B9' !ũ*|XdR.Pyc+56w£ a6T~|u_OnLV&ڢapjwB(a  XP`A08pNn7oꑻ.v63^yD&$.ո KBÑk uQUи1$\K0$9 n漢 mٶ7)Ãj=ȺeF!\1cLzcpű&f`RYj/='5Icy ӿr( QL [Up&mywx' "ÃCg3DaIbLcf֌T==ypXs=4]KVe- 3g/ިD>VB* D:4P\lv9>׽|V.<\xm)G[YD`bF|yh֤EOwv=PqչݽY$]K;hk #R[Xu7u{RYRc6f@xKkkE|{g) ȹ)~OevR ЉAP֊vsi¦iؤQL;඀fSmU{7yHWZ)6cGVwhEl .vzYX@mڹ6 _y'B3Z88Z#\oQo3WQ ̬gˆa5|_+u9$RVXqS.=[vo+v䳥iZc(ԛ 98^*IR\lD]} $1nQHn|ej(Z8\/4P;] vX+8Z𧫭y;~ЅWRZp}BJɊ?do! Ɍk&%" 7u(MN/}XijYWϙN3<-f'ha#OˌΔ !v-K?C'$TT.`:W^96oFQoҎ! BL9c>8.*X/Ӂ.Rƒ|B8KIX4~UW{S{=8Iy~8KOr?mƿOI"IENDB`tdesktop-1.2.17/Telegram/Resources/uwp/AppX/Assets/logo44/logo44.targetsize-36_altform-unplated.png000066400000000000000000000044441326245125100327770ustar00rootroot00000000000000PNG  IHDR$$IDATXk]U{}sgw;Lgn!E `A bĠ&QEc"~(AbJT@ZdcJys絗ggJ[Q(%:zŚmgr/W FݶxI?хo[ʉW bAbMzJ:GlwߞȤ݇nݼ6A,,)6.MfsCoLKO>;e@^{S7~3DMAW;}Z`M<FIq8=Y\%b1$;/I8 P[w-Q!wz ZZ"+XgƙaۼN"]c7/}]n~_ P HyU[aYGn(q=}2.Qv$Y1/\5d SG Ip^떦EiJ1Q別k&i86} / (UkoA= Q@$P,IǸjQ -Iq^sɘb,aǑ) YNtZ+^HHGmURGXh#<JRӺ(WVYKi@2Jy+00Rd S- 8mM)*Ze q$RԔb#VLT̍L YQ}f%KjtLѧHBc$HCFQf6uD *2&=dm\`ׁ1^$CcuܺbhECyuI c=cL񡪀!A_xUP(F3R ykb(4% ry2^FUR+`g:4V0Z 9\"adؾ?p"(,*NDEE={?Ro"ĚSCky?X!Ctv K r,x lzܶhmG/ͦFd̰aG?7<$h+7wvuwTcsea6`zeZ((Fڄ|ԟ VA' voޒ{{c7GvoV]~JWIΙK0r2ˢ*PN)"HPn ?$?=b!@iV_y2#vC*SRudƚ2.-<{û=1swپ:gN GTF0HY7~Tݛijvt%kRj3;VPLhSD52zdO:l{9葮pצe 6wWS/&XC{t1׾'H9g]rY{zѹk*/ 7Rq0Ddks@tNи5_qYw^zkiW*PzJlK9xE?9~~ &TコT}JU;oJ$Ӡxd=2I ;g5IENDB`tdesktop-1.2.17/Telegram/Resources/uwp/AppX/Assets/logo44/logo44.targetsize-40.png000066400000000000000000000034611326245125100274320ustar00rootroot00000000000000PNG  IHDR((mIDATX[lW眙ٝ~8΅AU+A(%mАR*KS !/U*$xAR m&4T Q8ݮ;vf9ڊ]#9, m[zD44m S~kT&` w$qrNO9q@~u-a[ܱos`{#3hML-k_W@_EhSLgS:KZGE08dQޞ.Ps~@Yq5L{3q5.!EP3JneԔeZ xRC 0Fv_0OiYL3ڒhtbPYZs`l\ HE78㾸gSb}"x*=#10Ɛ-4agG=\D.0)_%`fk'MV-@ A11S\vc^f00^c%28 Hh2쨏z9V,\U =a"[p&'38BT 7B)IJEd6𛝉M*jIpr0>SD;1%ap @4_5<7nK?}fgݮpN޽ Ε0-CW@[$_Lap6di,|ƗG/[! D C11SDѓ`AhٙXi4<'k_+C2$X9@Q4l tE_s"Bk2.x^n r:]RGۃcOtR[ow&4B9Z-R"<51ֆ8DK%8pο6'u6NkBoֶQۻMO$ۨӹyUM6 Tk 3EwZ8`9 H8SK͏Jo&/t=o? dW3gjCk!4^g ٲ*ȫK2H8R\*jMhpm48BE+&CFEm{(( Κ,:'"\NqsjB0V.jy縘 "/C\J$4!*5e&\ϱeM&>+颼-T:rB0 g[({lSD<}MCYya[5 W,;8wj](x=ye쭸+v q3FZ;Țs|u:10Rxo$؉#?@JeU\L\J@C}fKZcRI_6-$''߾̐wYHDW5rF3Cd*~:k~Î ׎Ẫ;ʙ[{ߺ4rqj|Hz+dTbsASN(NH=:rɵM $h )IENDB`tdesktop-1.2.17/Telegram/Resources/uwp/AppX/Assets/logo44/logo44.targetsize-40_altform-unplated.png000066400000000000000000000051521326245125100327670ustar00rootroot00000000000000PNG  IHDR((m 1IDATXk]Wu{sߏyg_cOl`ppB**( Z@w'-F"$$Nb=8}̽^pl'vLt{{d ̆oݺ3w۶fe}o.,MǏ6N7JzkPܹgo|ޑZQ'3,.5ؠA0=raijO"rr;{g ;ڣxVB9.*FL܉g闿P~07|_qBk_GDziթ,0c?g.n[v_~$n&SՆN?>82tῨ}J]^~C}zNkWmTc nTNض=gFF'f-wিuBڕpN}燼_`-ooXi')QGF'~_g\ɷvlm~ Ҧɡ i<5iiK;kWp$:ֶlҡ\0ݷO{?ol)7#B#F%@B̦k'~T83: ?қwloq5|cݛ[)$4'f|4@E%8ֵ= nm4@$"+xFpbk[ ܻ)ֶV)d&\EF܎=v;*wlso,Bou9˳7K1`!'N]Ӎv儈Av(yXUEDm8jnNM-}}(E=0=D\Spr&+͒*.Tsdo}rܤlLц>"5F/$X`;d\M`R| EW|`AuV$sy.TD*q7d[T*[kOݜgvҕu,̈́.(6t)f+$D5`8 b-AF"Ei:sk꺝6v.| Kpo_ lmK‹,(FLX6)K'l%ߐrD)n2D4n~W;zDbm\қOro_+C!MN%"2RrLҡT!4E3ucI%ͼEM4].qq96b_B+m|lGYd,MQ__1Efl.p4pSSd$P1fDdZ+"q/pk+w1 Eւ0^S+Rnj6盟U+ k8Z/夀6=j++5qt7pw_ ۻs]e1YB9P]awbj#BVJP-aZ5Qk+&RI~l4nna"FFAG!͚`k8f`L="pX%mQ`&Gp?o~_А +pzd5.; ]Y[2{C)861Lͥ;H ɡm&OE婲(Y@H:q&jjEd-ej~ YѼHeAwyfE2S_9m<^8{vU:(̫e;9LA.墔Zt435Nt:D}#myjHxǟ>(**, }Z+(Gs @ј&3VՊsg8" 4%Zqj~͎VDiDB4"1>I'G'ǎRR-8 ·Պ`E&*M5>١ǾM"F:_|qj@1&xw^Ɂ)r 5hٺ4*KD 6O'wl{KV0E j<3ɬoHt#ID;71[sC4"Us=B]]\^{_?*2$߽7>n^+sȂTөR$xf eb`NK]>Jh_Z ЯlY(?z@$ͅVTmK5\U*n|+^t+H:nN?M{TǺ 8eI(hLsѳo W²4˿~L[oo Y-wDs!&cEDAŜ|2;V=[LKW>|sa RY8 Hec|˯>L\+w78{ J)Ą`%ڥ521{r6=<}ǼtJWo\~m*hgt"A]<5*g4]vպ_toR7'sǥ29(s?'y;iIENDB`tdesktop-1.2.17/Telegram/Resources/uwp/AppX/Assets/logo44/logo44.targetsize-48.png000066400000000000000000000042751326245125100274460ustar00rootroot00000000000000PNG  IHDR00WIDATh[l933vc;TRhp B"FJUVPԇ"(USQB[ri)7;ac;zwalYMH+ٙsf5iMkZ*D'1_1H5`衫ιϸ,ѭP3\H¸~uvd9?̀j˾^kkuq&-q|{M]!).Z3|`Ű$x`"1AjZ]X'xF%Ћ @S%5Q;קpf`kuO}o_}um 1Ihp$yO峮^|c]\nH-Q FrH p Lџy̧ZbRo¬9"Fɯ (Ȝ-”%cw~b ٞoϷ[ O1J! "*Ƨ`HB&h]`8 0jj$ϵY;uyKcl٧X$!Tl2v=M Ʃbxb`!!9JȞۻ컯IǯKL@s2%%e k sX:_ c%ot~;6&ҖL5H$)^p:[F肙ђ¦t \6aL.cPeloݙkO}fS3)\Pi/Wl@C#mcCM. PϳYzI |`udW^j}a%J+d%>ز>& 3zC,nHއ%cSbηwnNM[24`s~̌\S2f"6Z8R j[cofFO1DAkFܔضɘՠ@a4|6uOW#}=Nzs˕V̀jABl٢ p4#7 fAug/At^rF~xeSGGn®=;ӷ7wL^(acr ?PQ; ;a͆!Ry! 5cp}\@N)? ' {zvkGEK>h֌d 6а%Y=s/XaG/1; FjH7oqCoyWҌ'-ոp2LV!+ b$N }Ղ\僘!GjmN`s^<~!H?ZA HI3FZ3MsĚJ!] !p_CXÙ,F'g!h>/C~3<` `ogB2N!BkF!SpTY3矋 E!/aOr%5Cs, ̅Tv܅ؐ'pr*ShWf4tCӕ: pQ2Y & APZj)$1|?=p,S6k|;j9-gWLt$^[~fbO{n#g1˕O™r/' $ዚR`pn;aϬDN"1Z1GSsSC N|+ |0^y *^}CX*㇋o͕ݒeJhT N05 @W"sx!0](h4NS3XmGN=2nfaPc ވW a`XGgORmI|v"~ї'K!a38Vk@Q9W!r7(yr}'/T>rP@uIZXDT/d݄9Tw񙹗Kִ֚84P/0HIENDB`tdesktop-1.2.17/Telegram/Resources/uwp/AppX/Assets/logo44/logo44.targetsize-48_altform-unplated.png000066400000000000000000000063271326245125100330040ustar00rootroot00000000000000PNG  IHDR00W IDATh{]}?3gc{wzm]c'ٱynM%TiJ-J%*J- JVRIHRB!` `czz}̯{5Isu93=3KDEk?^~źdۊ&ߺX'RY$ K`0twpԑCHXW>N/[~;okX~Siq2IPM[)($)7ϟ*:rȮvһn3q rJ{(DC49ZsOv|Rk;>olk/b* ( 8"coΏt|۪_?34p:7$r~H |2nc0_9ߟ#`ֱIt,uѼI{ wlzjh`gv|ڕ1B+#9lVkjk&i'Gw{ Ons~ *8*9Z;V-ܷϬiDc"IfK^&or? `t%wJ̼+"|injUYV6qd[řVJd7o˷v؉ %/T JaPl_m]lhKNhrkw Id6W9qfҦO>lm'ͼX!BsY^;hB2Zi$B+E8>ZT@\]gGfÍ1M2յa QU)O%Ŏ<αnQ H(吾"L]uap2` +e@a23.IHehIi]Ep"VN'dgγ=CSR8 V p +Zs5<4'*U"R " |I,[ti[mL˲*KhF1bM$xG;4}U#8bT 4rȩI*=OS6E覄?RƷW)\)>E2i]mhoI!F)ֵؾ:ώ<[Ӥ=M%rLvxJZGX2 OlU CETKU?Jlh Ji̢NsMmLoQ`f>:24A)xP5sMhCptk?F@x (1X:JZs_@MImǾzQWe=vtfsW@D<.&ya496^A#8)aq_OMz5O) HgkL#'oTO>*J_%B9HԊRη?Raw~P #F+ *xs$?jCÆSϳI\SL!pxB$&a<^ý {&Y[lfn^~S{Xي arͷJ F#CE%m"R `Qa&"'bi7p- ~VQ|)i*ŋf!R+)Wc7ǜJR o74 .ఽ*y5@5P_|1O `T IDE @UZMevSü2I)x_\|zM7\krgwU3z.8^kI!{oW. L~k^z2M&.8|q;6sߌz1{ fLZW\uoޮVmܦۻT$Ϛ^(Ka;s]yU{| 2xM X.%gZTye*k$3( JR)RX6TKFCfIENDB`tdesktop-1.2.17/Telegram/Resources/uwp/AppX/Assets/logo44/logo44.targetsize-60.png000066400000000000000000000054701326245125100274360ustar00rootroot00000000000000PNG  IHDR<<:r IDAThYl\9Yǻ؎, eI&@B TA"/UiTV޻>U@i(T @Ďǻ==9}c'I@_}3o=XҒ%-iIKZҕ߸Ꮷ:.IJTB]Dۼ<*+ dt$'Rv yr)D &xΖ#w>P7p{}_ ؞X3ٽ%Uo ˕ Pl -\Pw5Fڽ|eR fጐhƝ*6ؕ Prn nsȞeFB"f8DwhϿ0RTJX!cJ[]v6 ʼn0,5VJ2Eo]Ueoi jYڑIY&e@g"{ҕ(: gY0nڶ"ޕ1c'vES lW$KWJ3T\WX) B)ԅ-~Нq'gMK$R6Ύ h5!c +$Ӣk<": l~osd]M#z#69#X@<) uk@Dr<{(#iaKH+Lmͷ"j5s}UFl' 4lO2ʈ[$B5FN9cY+:_b74DZ mqoiۛ-D~:i$3bYyBN6ʉq=eP#~}e-}w4Fv*~J0`x"WHpXE7kjEt;$.دJΦȞ-澯U65f5E2",4Җ "@c ͵&#߲HHSn{U2 -u-21k7\\^(v}45ah|U87pb$#0W|pEfS}xoTg%ҹtd0#؎xÚ!͵&8cJ-RHG,wZ11c-$bRABat"d6WjP : FYv!EdΥZʙ<5%AN?Ø@@< !UlDS 9v@a$fD6kOj6w4\cBc *ѱs#,)؞ctFJX@之)1o`Лgҿ7yUNW@} ܴ1zU5֯^  ݁"bh<@WJ"X(opi&&(J:EX ^Oٛz害n]g_m\ p= G4Ƨl((XٖWEPaTv撐 Ie4'*A&@{7_Ozq]ݶܾeu+0014BX"</NDƵ:#<8p FY N:GRovuTmzN^>?g QDCYsaKPJ@@c0c`U#{ @._mJss1{qt-IRR€D4-!D8[dX{>~Tjr9i ;t+i6bHJ ]ct-)G!w^`@#~>|lN xp./9-' qdž,KzsfO̸unt&qYg#NZ''5ruI᳓Nη9!1š}Yt$.poq#ρ-9GqgR)ar؞@/ռX$`?9p[PU^i# =CS!C^Sh{D_@nX4`mHNN N{oxؙ!4Fp:&\ZA!(y"ns:=y9#@ 1ʦ?pnP%4#-n} :Y4` ل8 sAlͼ>ò٠Y&uHjϞ~ӽQCKLOh0\`z3X#_l3Ld+G;߆ g}Y~t޳鷃g/" uz\GWA!)ɄAa0ysp@Py^$B'zϥ-;yQ#kD8[Hk\dq??da{e?0t^;-Bz}X{v[_; ^rg>sZ xjhSMq]A9?OuDPUw Ku 9d C:rbG `/p՛o__rfn hT^䕱RBXqz[̓VovY0Wml޴Dn)dq7I^ @~׶OȿL'uvW^"tm;7EHg ΁<礡yCO޴I`M%"Ba7}8ֱ53{ӉU1f0m@5^ֹ;,ٵ:v5|{חٰlvv F>U?:1N_vǺOX">]Ae_O9vO=W9ǻ_u#OX2=ZV $%euKaώW3'wVF]sk}>k6l 2{ b +~> \uwKU(Oo>tc݆W|zzޟ6OQ\Uk Xvv gwjY{[$V ֲ0l9ogi _+[S"1diUnWW@WXQ5@  Qj|+֬.?:XSu$\}~^Y1343Eܷ w_Zc>0sd'H$3OWX6՗r*ӞϿ03\5%n`KW wtaY[5a}x&PI#LogMG[>Z׀*[:SѺgD`0hnYBW]XFW\f͡Z3CAē700IN]sWf8J誳U)|3M1L@1@CW)"p|-C,S?uoܧ҄ʋzͶ5k_Y0)a`sG [ظ<\?eL!r`ɎJׁywa.\ewXƋV_u ҕj 7`Xp66wq{wklN{YJAp} X p2b HD|`%se 7Xl^Z/T4E43`VMlZilhM cKxJL=);s?Z@cƊ+ ) Qя^13 ~{BVu: S Wi4 l\؞Š 4&hE+SX^b<3w\&DATHLK΋Ȋe oum.; Iz;3:\`G1r$&dq:[hfjJ.@O -"(WϪLÎO7o@6CfZNV5;,LV1cYm+3SkZhJW؞TDAcplх!eАtgRiA@ pt܁!D^( iZS@֠X’uM ki|%_#iI\ז=uԞBG!2Yw^8"%J2$:ZMJOB \ NxB(+-  s{3 jh6Nf#w&6cKya\ۀ˛ 0 sBΕ| PtMLz +!r L:r?|K yNsv5nXj4\on Jcx 't:bDGK1S",.֞ŀ9uN~B#P 3$l3KiAX2AѱbNcyNJp|DĪ4,CD{vn}'t7!SRA( ͻf4#79?# ¸WHPşhf4ű1 ϻ#T} ceUp7}.*;4 -gNc!9:AAH;G}]Ii- 6&9t /OMժﲁNuFu!{ZP= i''<ʺh,%Bй)'%=S)ajm1`a֎X6'xtB81'08!,:0$a"F$٬~ 䘪SI;6ۮA225I֐(y?}'G`Ky*eow* _ct?(rxmjT~JΝ9~5֢6p$X΅IJihq9#$-Rk{Jr,z}3*s?f؈y_ݡ" ) I”8u/o=~ҶQY#Y^tN5p}1A5`x{_{*8y$ kqxtfpܐ9TC "3 >3*_e A84RP<W{ZBP'ܾ?);Y%S~nf]p^a p}[&+hq!8![TWGk&3~g[2g({B? P, mM @ gE4 LjuQ#FL.H<47`ޣ"eH)њpɲ:(1J-ܻjt`;[yW Lc8?v'e7* [iT1=u!'5%Y ~k~KߙˬZN1VoX` !C+oir/Wj9M=-Os߽:^M+7u]\CUsnadPW/h6ke5P<`o8Un~sLcvמ-׋s-3Y.gUL L"OA}g>^i`=A>ʳW_ UW*󎮣)}[;?goDmK\v_D^I\@k4ciW?~3X˷yivCGJ[PANI< 4OW?3.|@\޶PkO ͢ͼ> 2l}_w?P|ՠ%$&/ Xbq0-$t};,b"d\O-:.>'|7~AGŨxR/:/E^Oɞ6Qc XVk@kL7} /!´)umj> Y %Vtomk.ݔb iAg0<o#sN,jtI5OCJA+^i8U "03<_AD5";~~zsxOXٺ ͼH&杆 >Ņ T%-Ip 7ЈpM]5[owM[5[1:zC'v%͞ɐ0\$4GOhBbL*/#bjIu&9ê7 N懺96'3I8BiF\`y/c-fbgO&$"zI0jxo),&4@_сا{: G2ʞBe[ nKDق _)t%t& A6gAnX=h i}[b ğΈ]tײ:@U\Ȗa>+AGDlPl;$& a&CjIc@W(fW$sJv3Eo:f4L݉)nSɣ'v~+jY \T W A`Mǰ= j'hDQU$f ؖ&M߿%vpۢǷ 5{-[lwѕ C*.@ƥu@q=o Aj7y/vP])s&5xÉA1c:oa&o "BgPdW %9-!6(@D`:'?3ԇMp:68^v|LΗQ=AD &2q"fdWsl m$o7;r]?<,4zq](>3fs-XUR04LwoAEgzIkULwC_L5a՚ (0hyʖQq|:PaI"lj^E4A,ycEYn T4pK|E۫GB:2 _]YOA2Ʋkf†z ""F0W$KˍG t'ajm"̌ ZWyggO澝H#R Ǘk6+AC/9+&%#5Um PJޠ\dV,~}{Ə;#d ՘/ؘY-×ަ=BoG lxۜUГm<- OO_xؗB޿=~]=Gtݝ4W*\fF+q3WABTC`e3AO{@s] >k%ۉ Hād8%oMO?=2wƯ|l&=MMr| O*l+jy[hw[m`+$@(`$eZ@d Hx.v:1Y :ց lMǐNoՃ4@s˵į6CMMlk}v;sha|ԽPrp#M6n4YQדep(R&֑e|xbSR l댣-_54ռ=L]{  _mt0S%wDpH8DX3 ^p]]:j\ xkz`~i 8u~ !`1/b"Dt|w>_\aꍟB;>aXgs=_(8[yt w/N݋ѝ֩x̩B ka$g-KnԠ ` ^(: J-;53f dD"/[z#/\-nZuT sC s`|dO_һ)z$[_˟O ~Yُ\s+`!JI200 L/ǿ9?9N(^0ckӹbv1$;=^`0aNOkGn.+ڶhzUC57o{vO_:jp%g+tv K>oeפ;N{9cѥBID#a[|Ǿs_&V*J֬ yuM, xVYo|'S`j,4rYKO_ҎnۓP̨nG ~vw7`Dٺޓ÷)D{;^sSo ]zSW߈ 3a}.J!`A:ϧ/w߸z_}\ilo-Zh"s$3IENDB`tdesktop-1.2.17/Telegram/Resources/uwp/AppX/Assets/logo44/logo44.targetsize-64_altform-unplated.png000066400000000000000000000110241326245125100327700ustar00rootroot00000000000000PNG  IHDR@@iqIDATx[yp]y}nodI%Kfi fM6IS&PhgH&dҙNIgICCZJgBHC- Xez?}OO'KOswo;=; Mljm\llm[D&C$b\e'Ȑ?= t+8ogj5k3x#,jFCK%Ih‰R` K7ޟ?K/`:x&\RLcSnn'^pnH | J"9W^?" tH@ѮFѱ7?6пT14C:v!Þ6.+~EuH M+{>{s錤-+>swCfM3V47\ymZ,,x( f^ִ;T'FkZP)sG/K/D͜[-t_O& dq=ä%2oZպ535.,3L"?ndh[mv`\wr t!W;';W9}Ou~ggm:Oy ?85ngv(TЉ(]19`Xw@o~5eYKR @ bh"rQ5պno<|o u唖Őb0|Xp˚4M̺4W 0> `4#}{(ЍmA~pÓ +jM9Wۗx{iyyfsP9ҏd)Un,‹kHN HIo  y8?g90]pŮHosM9 gcW*H'tܲ*J㢖8Җ%dp0) )spM3 Kom dɍW}ByNM,y)pɊ8n$0Je \`Cτ Cp y03:dde17zf=۶J^.4H2ab546ec05+H'GH bfE8M .&B@" =BKzFkK^}FK>Ff_ćpeŗ4l['fueل@1\cP{![WٸFqv4#+&8`xbrib@3ذR#YȡY4ԛЈHFUS(cEfbhoJJSfp &%@CPQ-NXNhK(9> AXMRs @E_ލ`! <3 *wtUʍs X" G:LlmMẵܒƲeu@ ;9`pjR:a-&(T{dq.҅Ɲ.&$+*?1&pQxI Uʍ27/GVœ3 F%P׎?;:갭Sp;&&R1RV,KB ˩\}"O*XԫI+صYta=Z ]\{{quGl"P)PјcyC`:  4UI^eQ@"A 996#s^PT\[E02t]%|ZhҐDKC2 >4Q.Oި[Yx U  3.(G슗_*w:N##EȹM Z\3xcGཷ{'KhCfJnzG5 #M#(" >:EOX2? VN1%ZR`8p|9-`\šaSLo[ф."bNyG~y0c =.A# #?;:5|?zsH[zNe։H*g7O8}J8خCd;LA{ӇyXF-%"cg<1]LDdNTժ>O gꏷOʴg\릆Mz.\a`2#y݇C6Vٷ{wSP=n/CK oUdbIxygpJ@9d^pȌP ~_Gp"fDE.&wo#-it.K"ajxxw^<:"d-`^vK~n_gNq/a+9Xq}A@!\hYhʌS _‰ţ{@U+T0Q,Wg6;z]yXB^qDRP:*aEWv*k_21J 8p`bCR\ٽشKZ, ( i(?! !TІ"Ri6sLvJNy518SRWu9Q[f@"3V83T}V;O=kH7`?Gv=zsTCݻ󮼜e0r-s-ap*O4;R, o^9Yo, jϾB,XիNU25(' UZ+,mY-9D%6eX d%O|j}_@ʣ¨]|%X^>] st xzoQ^`{%{y"Ӕ@6*] ѽ[IHo=}?Y+3$Kbd˲cpMiHHZZ@zh*I+WE8]˒uS"iRer)K9{̮DI]^b4hРA 4hРjC>wV{+T@ޑfMMIS*R(\v\U B k<}g6?# [}75~%)l7<xzgXog` #3§: ǗTk$P a}ˮ~3#m" mVִY~R̢ xtOgp3Ÿ`_ ),D9SiJȖ e}|#C{#xwHwYK9tΉB+H*R=[vw=8|G#r]Hڹ2EE@PB5}53/4F|<$(D)PҼHT1ݛ 1"|$};tOXߤ3BޑZzTN%#ȜuI+[ 3. B[@xbogCmOk9 v  <򖃎50*Y+ɫ-)ZKHgu_|#Mz%V3d̘hk1nSNRF+IKNh=&PfA{jcsl Ԃ`DPJau .XbJ(Ǣw >;i6kk5L09#t;BK G#˕bTH]HX~aG̷ۯQ15:+70",d3uW`d,!g~@P0BPg]Omp̿=ʢ%Y,%wx"dƄr8zZCxu8 U >+LLxs,S9#޿'64F0B%+`"e 0FP{5y1g>F|"4)PIR=w>Do0%SiLlpF莅d,8;k[QzFr8W}c_ǓǶzxSۃJӲ܂O "SA2T t jry]\ㄑdrҔ^} ._ 2Sq;o_۾+|=pȊ#T ӳyL pX"O\kʟ okWҭ9F( 3BOԦ#=-~pk{˶&ë `:ҍx"l#8J A r-J%lGyZZ %hM@SlҶ<ϼW^~3mwE:2DtS*!Bȯ3gI'œXc99PkmVKbq`hmc}nOڲ{bI rHdx -u΀Ykl G#,~QK zKډ:>~9o{BլAHHȋ hq-hpy::QKXvg0t Yȿ1#/wm}^_U:TmTyIX& `\*&Ѿ&X!WGaeIkj2cyuˈ`;rEc{8#ho[)8Y/S7Rs,޺x 4r.8;(=\ǧk ԫm7n wgSCq_pwӺӖ87]e=bH>؃we(ȽGn\3932'"L٩+|(8Bܞ~ɵ,~qq3t Df%#(\Hظ^{yx8rg-۹ՠ*8BqGHO p|:_\/ EnY9[9VAk-#~~ezN͎5 H; Ċ DPH}A( )%+g(_ʹk'Oߋ}Wsߦ39+7]LVP y8įsr ?(3p"~T~fWH'µ]5tNRBJu3!OƋֿn΢9/֑5'VsP8?{VZ=@g?=+g^BI+!uzX<Пz.:|û&sM9]〪a8$tN:?ß|hЧ@wy9}V^ R c} E;F5FkRVt@sLѣ_ Oog*#֓b40:"ۆ=A'KxMGɱӽƷ׿yfLv5pu<]xV~E8덬]8kjPJo~E\ p,^۷4^{‹VVڵAh*0)  BK&~~9CVC 6scͿr.gW1p3f`>5|mpT65ZH|[ ѥ֐kC{N50B{m#ﱋnb*5u qn_q`o _ 3nGG{ ̶M1ۊe7.jrvr /ϑo6BRDdU7tw9OtϦY԰{2T.iV3ڳaꮹur2l+;gΌj/~`[b& Qx(Z/gRF}sѲ>Hɷ:6E! %QbzvN?M/N|OlzھYB^ak;Ni4綿L}FRVs]mLOUcJ{Ǽ~ȵicfX!=6E!]w|K;=Q bV0$0wBrr8Dk@ _ڑy=^ jzR;U0CC š31I'+3.@+}k4ﭐ|ՙ?%zN?@ͽK;W,#w\"p39&_x!(Y9w|sLz[i 0>IyӇ8g߫1/ot.*BVj8ÆZǕtp m2amB+Yl /Y{]d봝[|&؊pym׵qӂl @![ѮQDKNo_Y[{o+\(q=&jClI66 Ip5!drQ(f`S.9Dj\xӼMh'[R48ֵqsg pF+M}O'd9HljNel2t,O _y Fmg*s1 7tDq}GRR(9Pٔ $r# `1ܸVZ+]V?#d@"6Gpֵ65Q9 jq9 f\CK\rX4A"!ۇ?U)D5pzo(@lq ܺ(;XXk` A)g g3M!.+8Cw"69pR#D1etQ߶P&5j[ti|w5F pA%b`ܺ(sB8\]Ec͹8= M!+~y.i '3 +\3%H$x$ /]U(йb-3-tp)\DG\En =b8/ #5΅94Ώd7CmBgs 8nwj.c4ͦa"߿- /[}mf?XO,Hj4 ΰ6kpeq, ]]j eq$!oɼ{Gİ 31&$R+V`8ZzU"?4KַE밮5]H^g1y.t[`rQg6 + mF &*E҃ɬ`h7Br!jqnᖅqC{[jӵ1ޡ,lO3`nC͵aI38r@Pt]8iאRx6<4`+ `q]GqSg˛"<5 惓s%z3Hf]Qi^C I 8؟JH n,ҘQK1svҀay8`@EZOۂ)Q([yR`0<"@0  $Pc |x{KYO!+g Jk 0Ak ZL9-p.N`8)0˯t˃OR cbњ`q5bB _EHYGsV'` t4~Nd1)tiI9he iq >.i{IvICD%܅3HE8HT(r~&̉!` (Ǵ1Aח-+yg *Ae'%MKhAkhΐJXcJ"3)8ĂmQlcT>7SSNCw"*!0ո"5`m'ʿW]V:A ңsnDBLF0~xh07͝XZ W2H#ՙ( 񰅹Qh3#qt0 &(,Vz l̈!xdM8>PH3f5x0sU0o9u#+[1Cx.t9y[@; Q%*#(mg:LpNR !.fL !p<%{> rbA5D@,ۙճS0t%U9|36\8טչ s]qfs` Y㐂4E)wx EΖLHT&^ mspO{ JVa0xű STHfr_l7XZ "8_w;׵WRU8YOa`iIX\ 8p>```Tθv/RC˾ ܀=O(΁&a #ΥݢI ʹ},2 KNeA[ <ӂ(%2cpz\ ξgFN* 3?Ti(!8@יA<=I˘ sP{zFK3x\ I&σw{q~ stwAExD7 /?R[@`q6O~%pr5AOObA3 d\O伜^*ʿ61!W„;~VA/O rx@1 Ox L@w"~U|c08`RNJMt۔T"`ȳ;*v9{zD 0f q `p@)`J;u1MWޅoۋTK_D~q [[&Y'[I@9naOC/&gFaL xxd~{|рrŁ%Y݉ʦbP>O Bw?>Z9&,R˯cag'2:k l]$rW?g @֟k I[;O#iR9tz(y#;}={ܜX]$ˑFr>N\EE0XM>˟YC3'pd  "jx;G |g0<EW;ֱw]ĩz_A4wJ 1\&`@%),Pyz ?h <}l#gSJ]cr@`VԾ/-=ˮO@f\DRw>Ċ\Mpljyz$8k+2:iJD+7^W J/0n { ,G=.,x>LA޿շwVQXpenV6q+ ` }+D%ɣ/?gt,7o"VU a\ð R p )XlF,:ާɞۡG_m\k*,J[M@XXJ%y0܃F&LwCU/*l4D+P"|vfG,J%R~n}ؔ{ŀ8l:{Xz^ViyQWs>ݦas[tbfgSgyW;C4kϨ`CݩO<=3d}9 񎫖$H%)]p,ɧNJ *aFJ)_u5Z̪ 8w(w&aq'/y/{~Zʬy=]w<"N޶\ZN]s&2@CrG `քYXWƻϿ2( H4 ,螮3rߐ{C>.VlX{x`Tu.W.\uJVdW hM,>װh}\,i1",\q/ןަ p-+?L HG(5Ў]} B1mЗrP\Tcm \wWG \q"!,Wζw%غ!gY@[(B9ܿɼKwufȂ%R]v#B茝]IBd\SxcFR#$s%'-XRP ;wh)3 hs0uӎ\ݧZ@̺`]7l7>rssưVy|0)ϖ-S)+y-Vaoy`OGΘNhn VRiU:US#\k3G]CF 9O,@ HuyиzR/dS.k \WpѯՅxTh]\'a9.".D@ޕzOŀ6ƌ+wwDmcBId˜רԌ j1|ȈxNpb pEBmA[t}W_ {K̔amE8#dm#SY$6BM1h-ƜFfiGg( IBo 4"IZݮ"7PN ffolo lLw#$l/1al:!s*o2Q10$$.*#KГ-$ FGܱ!MM@!Υ8YH2T ^GoI|4Z+RR'!~@JQKT߾oogoֈ;R!ﭬhE#H0a"mAŽRƛbvNz8jl#;5iG=) 5g&ћBZP/^BYV7w1"g{`.O*st7h%嗊iQ#L,얖Toyj!p/U+J #YLe,@ Zh#B$фe[^BЖh 7{N5/o- :vFԌyخou;  h4gkޕCT>Z%UaYT81c]$"cϏ+pH4@tzfb[{[6Et`{nj15c"j4?k q]ҬqA'a/r %n V`{huX15!z*ɄsHlpGD@(bX;7^|ܰmmokk.4,TȯU'Ry'Pj~pyKƗ]k0@Y<]TP~0zzՐԾn+7kk#A]TJɹ8?M3Yk}5@ًAc?arsԺ(&c+5p_l4g{jo}׵7l34n&Hfl(yD4ZK5SDz܊ZX8918x`zNţ߾c)`"`,(tOYb-R@|1qDd}d_wG3ȩR:b|E7N%%EV>S{{œ~8!dQ®R _a2L JU+5tFDIW)aM1p)H}&b1VM@iN%|R._+`|8?*r+Մsu|~im1"w$N!c9W. `VY@3ΨL[Nę " 081ĕarw|>/YEpS?7m [\4 {pŸ7ϞU?7[eQL>Lؿ!<)g"8Fx2JҊ1™u’4Sͯsx%gR,.]da*[݃*jNkzX2QH]:Ԛos8zf''9Š @ԩiI1-H96gAO# I<_N.hRa$m g5tDZ|1+ xd~Л?NSg1DxFrz[9₥T_0(aRN|l;q_痏=Η)먪`a.k*xL_eX9'C 54[3Ѷ+Ϝs[̥;p=IHf9SyS_W= 157YÓEH zu쿏ӿ rߦ+Dl=Vi]d8`9ɠ?>#~4aVHp4l}ݿ?l glpP7Ѩ.vŻByw%Z^1t9&d:1}Nؓ1yq+|kc5QRd %=Ԗ`L 8woQMl Mnw*]u-W#o0(36Rn9w}l{aRRͲ%nwF/^0eDI2kO>VQдʍ"z}AkO=}w>zrT`?>Ya2 Č<I._^-Ӊ7wkā{?GMW o}hK}_{k]s@zQc۝?Iv~ʒ9KV\m/5'͗]v׃OY-Sc5@ yQͷz( -_wM~Ye~9D2Z}wd:͞% ]vC][~/"U{ٳIW'iu3k}^ТUd "^yU\=+hUm7oEm*ğ M&D v莧Ssv9ךtZ H*kckG=ұg*V>two̧er|l e"ܓ3eohl?QJZ8t>zpX?Q&ŸޛH!tգ>2SE}i߮pU͕H:Gз?:)wc߶reb׋Xk貫*3wld$68Fj^{ɗh,l[vfK"[Zc:/rsb.:6|.I{';!^&^~zg-SE j)MalXĂhF3wkHzWmw+W&hԷ^]GU:0\DG95Chlb~ u"A*Ae:ȚM]>sb'YHU@ S%L}ċ:FqYp$!+Ǻ$ YIi83pi7DpcsK* `IBʞ8éx=4!`&F`88+džQײ8l}/=Zyfr@yܤh踮)M-a lpؒ`Jr0Ơ(΁# $, "<".[&ˮ\9Vqnj.yVV!iM6$ %@DhUm'Pw08wZgmd%3huhɥʱvGG35 -aj?GFED1"EQle"p 1,˲nւAoRlcAmHi\jگѰl.o,j:nhvA۰8G"TQO3[*$O!бla &MHN<eF02A@4D,}Mk d L(B!4յlnZb8ܐkܜmc*D=-5p!%M 3c&4 [- _1i_j׷-yR, (n(~tsv+ 3BP 51?#` .qtgh{Lrr};mU^qDlPX([1ƈu:;p1S~ME Ua*a9pl8 S*80lj@$)qʕu `ܧAh!#3ڊ`: ฤʇZ%e 08CF*$l9cg@ڲqf8є + r]"Hx GmY&nYP| s[ZQ"1j຦6F.O(L֮3=w4"po B npd0dKL044Ad/8*EQruь+)%E,N>pϮ>p(ll!{n6if;˘3Ir$g+!`t '׸$qf \yZNL!"RLɶLG`1/i/t`qPnhET\_kr\7FNtē/tXb^l o͊ofp ǔ#gc;A jX<Αs[t!9ЅW(p^x8;W'>qo%G3/=pO>Za84HqCA!Н[(" #I $\' Y}MGzo#3}fzX> |^CƖ08CҖɞ3xthL1ioG'>e7{EB3Sجj,[3t/&5u9ƇMcMt#/qПlܷ  En`|ǩ >ҏO`bwd6qp@ٺ&Ma93H=ɮg'_+J_ u+nuH%ށu;n¯ 77K,q$m³GO[1!p?P~BDp:M?/B)[L%{Otn89?LM\`Ixfyˋ cx+8otba8OD ]pS~KjbE}SƇE%א=?y-WL `޸ !=n٣x S|(={:7b0?>`]6ˆ;DE]BZ; @*0Euj8 ssy +Qd-̯JʓoV^![71up@Y&s+ '4P&Ik3=”ItN߻ssnP n]/8:]_^WC]{TrD.f\!͚<[i&qKE:^C/3ko[z ueHV@?,$=%oB灁C0޿z>`WTʶ'J|Y,Y="^!y|>`/I#|Y)[R>qvidND|WOouBOG oW)knD0Aڀc͟ t׺f$_}yS{0m>glr>*DAVnqo-/vҁx;ChnhOT='n}L#qS0OmֲDuKX;"d2oxw R#cԾWUסd6"o` KPW7 #1{`(eR&1p?wSI5}=Ga:$ 'ň>o;,0gS?HIENDB`tdesktop-1.2.17/Telegram/Resources/uwp/AppX/Assets/logo44/logo44.targetsize-96.png000066400000000000000000000113431326245125100274430ustar00rootroot00000000000000PNG  IHDR``w8IDATxy}ǿ/K+"#. q8 .v*TǩJ$Wp 62cl$! Iйݝ枞{gEl쮀Ty]=uԩSN:uԩSN:uԩSN;ڇW|{[(Hq6\0ERc>{ɜ0PpBAַ}`0G"ta HGO>5\Ϫ ,O׵WUOm4` ¿'i.M|a'?\ϭ P<ݷoĚc#w5jH WT R\f܈ Õ $3־]w >V?7:% $KP[{\^cP(yU~Y{$}hP;I<=)R$T?Y|3H\]g$ryqՖO9{73!%GѢ8RP H뚍Dt{ZgC E. ]Loݶ?c-aXq1xW`N]5]g$ٸ}/m2.O-5RD:\*JTB{ܻ:zGh56tb6*`&?Y)")kC7x䆮恄6 .8OC .ț.B* RJ8UlyO P$lЯ'r}G6Ü[.Fan,*2J`c)9 4 T$< *Օ !~@M}-7Dl2Xܕ T(ۃf(i+`h :59!HdI 3> P(1h ]{{"wn 7lχʬ/N7(!c+4Fqt<Ԟ#B p=Lf@ *yR5̺JlNÕ^3X B(Ec)%#oς1R[ nePCn m7zbsvϾz!(%p=dt^R)m^1d(cA% cM!F-Mk-91.v*;l@ͣ=I;U$b$[ü[V'nyPCs6T٢^<ȞR.%'sa3TY}eƕ=?vOS~?j,JA5-aysO.k2FR!~zY(KQ=PJk|3  ͯ yg8)6)]1n~&h;w?74d s}Z@)t 󵀥T0tfhlmбIvZX-xTma>m>Ig- )/d&&JNy֟,B4-Ӛ 8u7sJžo޳*kv-QHfLd u:9۴R*$":b`. Bۓ5;|- Uxڎn` ӧs&2&\ZR9f5BBP8ȕƊQ>WP#w9Rj|ZViR*%BjPPK<|=U|kڒBFDBNqJj/Rʖ0-?io ) U689w#^HAW5j`ѿΖ1r_ Ap,XTU+l) )rIp< Pl{㆙<+X5j8<](kL clO^(J%DxIut\"lU]Pw]_6vٸn%n\sMkux({j"m´=PrnAu6R)0JEcT]; (X# ״fUhc [`XHIwΫTrAe9~>[$zb_>\zn@X{%Hc~k' ꅛu /o=wl]{wo[׳kjrFa,@?'2&E L*rxwA]ŭͯ:#"Ԛ.έ9LKCw%6nZ{ZbJ)X@*oc*kn_*Dz[bҵ(AtZH\ 2O]0iihi]{lzq eW8Ldv2 "_ZC q,b2e sz= -l;4pF當.)0SKc jXPSY9Rb߱I=6OJ0F}R#go57e(GkR^1Y84z%T9Gr%fE+\1Q#*x JBQPpF)Z~%3`te\#C)kZEjEMw̘̹~&:'QRH?>=GZahᷬF@1_埵PO.id*mG0/3" ]r c^l?+wL[??3g%u( %;Nx2 z?h9rBX1ٖ6kBۅ1 Ns;Țv6/ݙF v^*S:8]xU;K/0px/$/")~vωQïk'$t1XS)(;se2(W 5l趇|~fk!۞;bCJ @2 3SJAHyV(L͓_Ğ we']__xy%qJ0YtƦ-y"iVTFiXyyA^)!$r4T?<[n2!%N5?WO~_̮?|Fkyӵ4v`Nʀ򂋂(o+}LF<jjfژL(DG=|y?~!R*9i d- +f`y踂R>x#BԶgN. (BSoZ2[7opgT=|h0~CcC;dL7iWP F(cvo|hXAH[㷠L!olcc#YE1:w(2pR@O4V(ЍP`*.I߾;U ]+,خǟ|Uߩ\ڸ9NPPc},cyq+e;C!:'/c8Hh~,ƊܻXkS,ۿsu?y})WrA#;:uԩSN:uԩSNג;MuIENDB`tdesktop-1.2.17/Telegram/Resources/uwp/AppX/Assets/logo44/logo44.targetsize-96_altform-unplated.png000066400000000000000000000162271326245125100330070ustar00rootroot00000000000000PNG  IHDR``w8^IDATx}yy[UojZHH$2K2q&q$̌O&ビ̄d Ƅ`2 !! ֒Zooz[V/[zU} p,U,bU D$g\ 0̸DB&^UvƼU?/ ] BHpe.y`l5ݺ/z:PߺL@9ʅUVDZ湶#rV_O~DTT++. ʍ;ۑ9}xLs1*?z50=Q @RBʍL?#mF}d;} =@ߡ;'3Gޙn&@xZ}?pTև9 Rcj͙S[]'ۦg6,WK><2j=[f{Sj|2S3 «~xmfڔ Pw]Ԝ)Mzm<ɧaJ;ݿV$&-c#|*wo$}ߜ/y ,^{`[?@J"|koKwg%?ޏk[? P "\a+_{짘DlLJ-7RV|?M FVYoOnB@(xT]יZ~!|{@Ydj__eR7`aV6) .2)7-$x0Z+y(V 5'#qvV# :/I@#HKJ;2hGKXְ5?6+*jC*%zy Tr3o/#ǚy)[lIk:dxoB@̎ !'ksϴoi4Z"HLq`eUۗF)5h Oȁ|`"׆V:;>KVqgi?TԆ/`8>8!@`)qLW/ E G*AŦxݤdNk^\zee@c؊`IBTذ(OpCC(W%Zc =I})#N6h|]ẻ+0Q;E2H"U|bi75GqYMyS%p9pl QK"sX# "%)0#5`쉘eUfY1 vyi9v`#HEA*Bu4)-81bcX IJW7Y(i18RRAT.j 4BĪe塀hŎ l# XRtƋH;J#i:_ qA<ނ1tZHXc -qYjck`( TƃEg /,,;T#i])dl 8='U2 f$H˧@7< D{.R@?4j;P+qˊJ,谥%=g9H g04s`,&-iGXVbT+ #@R.8ct94m>AsbΥ1%F-Qخ, ch\Ceng8ps N fq^O w'lOFXnV)p35D3#x>Uٹ3i] g0 8G(*]c86`APy ([RYoFyQ)(?OӀiIRhF~?x V!8bbL8cPDɠo$Wh!g\VQhH{a -/R-A)f{'ߝHj$Ci4!vKඟXK%nh¢h ڝ\fӦR 8KEM cLN8RaWtdcA'ٴyPܼs_D٦Mu4(i*;!f CJlOᗧhvQRkX 3zGM d rB!Xp> P:O\:VL BzbHٙ4cjl Ył8.@1 !4F165V6b%<%L;N!me /R*/P9Tx!cC%10FB#X T& Ik8wf:RYf&ᑶa*WTp L ܾD4'P.h8x?܏UA5yn(EP_9#p7ITlgKK9S|Pkf G^y>y$PR2A08CРi: x#?QDP_gZvt'H.˝ 2(̬1q*G$ݬ"?3`™[f XXª gF2lhqn`!xb$o2y}> LA8){s ΀,toy^u,|œ:gx7ATess0Lv!GGݮs w{N#' 6];7_psI9ډ0*w4*r(T>$kΨֻo ]H"t %t &="WlEx7 (g k**[<v( W$8tpDO ȑJAb.9r/7H_ '_{#Yrqذb6dߙn׉:&\ib]Mю9z)?Z5C8x?sQ/(PSh'~:smo#Iw~!t<=)alǿMp Hc39cx3-1l 3~V4ֱ=lG shxwİBr9MpRxr8- (Jܛ@IX_y0"8g9r1obHc@W t-+Ka5{w//?iL{wȋl =z;!4=α=/X*wܣ%@,|K!qt N'ă^:9߹78؉X@OLQ *c0yٶS:dc635ԝ(9Cc g ?C#+Bs. Z S"?zp'zKQQEn@45)$`!v>ё~(yKњV6 6h*Csm|t!J0ŋ!HMa79?L{Ʃ6Qdzr)[`^BѺ]\t" 9`й@#`]0ʶ>WklkY9D 0@~B!NJ$`#uyM6[LA S<"'Nji|o(]aVL[_V]㜮L5o$Ψm7qo [Vz /9F>Аy{OS}mZjk}q6ڌ1*MXBC~1j1@ֳWp&io_:S_joCy&G(/n9) rmBQؿ# |@L> e_Fr:r I8G@X(gcϘO`hY&<+aꏀ$0ťe<Ŝ֓8}||&#z"G|Lyb߿NG jixvNWrp%V ;Ӭf+[>Ȉ6 SȢύ䷕+{`O`x6ٕCn9j?pR4+γnjB'J L2%GG`J_ɘ )CwC+jWKl/,l{c/w(K1O;s=s*=DӚt(ySu>D*\_!\)F&܊~&0W|A6}Xe3}`h 7:/UgS(18[k\ Voq_c Tq^ɏ}s4U;m u1 86W4ww?ԽIDdD0-g.Vop% O4 g.=y'κ|P|J49 z BkW|\v+_~GY6~MwI>'r!b"@!1+Xf_,D(gF0 }YB+M0F(IC]tzbTb%dYOKCnf7\bUIENDB`tdesktop-1.2.17/Telegram/Resources/uwp/priconfig.xml000066400000000000000000000017711326245125100224750ustar00rootroot00000000000000 tdesktop-1.2.17/Telegram/Resources/winrc/000077500000000000000000000000001326245125100202745ustar00rootroot00000000000000tdesktop-1.2.17/Telegram/Resources/winrc/Telegram.rc000066400000000000000000000040551326245125100223660ustar00rootroot00000000000000// Microsoft Visual C++ generated resource script. // #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "winres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // English (United States) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #pragma code_page(1252) ///////////////////////////////////////////////////////////////////////////// // // Icon // // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. IDI_ICON1 ICON "..\\art\\icon256.ico" ///////////////////////////////////////////////////////////////////////////// // // Version // VS_VERSION_INFO VERSIONINFO FILEVERSION 1,2,17,0 PRODUCTVERSION 1,2,17,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L #else FILEFLAGS 0x0L #endif FILEOS 0x40004L FILETYPE 0x0L FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904b0" BEGIN VALUE "CompanyName", "Telegram Messenger LLP" VALUE "FileDescription", "Telegram Desktop" VALUE "FileVersion", "1.2.17.0" VALUE "LegalCopyright", "Copyright (C) 2014-2018" VALUE "ProductName", "Telegram Desktop" VALUE "ProductVersion", "1.2.17.0" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1200 END END #endif // English (United States) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED tdesktop-1.2.17/Telegram/Resources/winrc/Updater.rc000066400000000000000000000034461326245125100222350ustar00rootroot00000000000000// Microsoft Visual C++ generated resource script. // #include "resource1.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "winres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // English (United States) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #pragma code_page(1252) ///////////////////////////////////////////////////////////////////////////// // // Version // VS_VERSION_INFO VERSIONINFO FILEVERSION 1,2,17,0 PRODUCTVERSION 1,2,17,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L #else FILEFLAGS 0x0L #endif FILEOS 0x40004L FILETYPE 0x0L FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904b0" BEGIN VALUE "CompanyName", "Telegram Messenger LLP" VALUE "FileDescription", "Telegram Desktop Updater" VALUE "FileVersion", "1.2.17.0" VALUE "LegalCopyright", "Copyright (C) 2014-2018" VALUE "ProductName", "Telegram Desktop" VALUE "ProductVersion", "1.2.17.0" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1200 END END #endif // English (United States) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED tdesktop-1.2.17/Telegram/Resources/winrc/resource.h000066400000000000000000000016101326245125100222720ustar00rootroot00000000000000//{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by Telegram.rc // #define IDI_ICON1 111 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 112 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 1001 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif tdesktop-1.2.17/Telegram/Resources/winrc/resource1.h000066400000000000000000000006031326245125100223540ustar00rootroot00000000000000//{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by Updater.rc // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 101 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 1001 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif tdesktop-1.2.17/Telegram/SetupFiles/000077500000000000000000000000001326245125100172635ustar00rootroot00000000000000tdesktop-1.2.17/Telegram/SetupFiles/data000066400000000000000000000000001326245125100201050ustar00rootroot00000000000000tdesktop-1.2.17/Telegram/SetupFiles/log.txt000066400000000000000000000000001326245125100205730ustar00rootroot00000000000000tdesktop-1.2.17/Telegram/SourceFiles/000077500000000000000000000000001326245125100174235ustar00rootroot00000000000000tdesktop-1.2.17/Telegram/SourceFiles/_other/000077500000000000000000000000001326245125100207035ustar00rootroot00000000000000tdesktop-1.2.17/Telegram/SourceFiles/_other/packer.cpp000066400000000000000000000430171326245125100226610ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "packer.h" #include #ifdef Q_OS_MAC //Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin) #endif bool AlphaChannel = false; quint64 BetaVersion = 0; const char *PublicKey = "\ -----BEGIN RSA PUBLIC KEY-----\n\ MIGJAoGBAMA4ViQrjkPZ9xj0lrer3r23JvxOnrtE8nI69XLGSr+sRERz9YnUptnU\n\ BZpkIfKaRcl6XzNJiN28cVwO1Ui5JSa814UAiDHzWUqCaXUiUEQ6NmNTneiGx2sQ\n\ +9PKKlb8mmr3BB9A45ZNwLT6G9AK3+qkZLHojeSA+m84/a6GP4svAgMBAAE=\n\ -----END RSA PUBLIC KEY-----\ "; const char *PublicAlphaKey = "\ -----BEGIN RSA PUBLIC KEY-----\n\ MIGJAoGBALWu9GGs0HED7KG7BM73CFZ6o0xufKBRQsdnq3lwA8nFQEvmdu+g/I1j\n\ 0LQ+0IQO7GW4jAgzF/4+soPDb6uHQeNFrlVx1JS9DZGhhjZ5rf65yg11nTCIHZCG\n\ w/CVnbwQOw0g5GBwwFV3r0uTTvy44xx8XXxk+Qknu4eBCsmrAFNnAgMBAAE=\n\ -----END RSA PUBLIC KEY-----\ "; extern const char *PrivateKey; extern const char *PrivateAlphaKey; #include "../../../../TelegramPrivate/packer_private.h" // RSA PRIVATE KEYS for update signing #include "../../../../TelegramPrivate/beta_private.h" // private key for beta version file generation QString countBetaVersionSignature(quint64 version); // sha1 hash typedef unsigned char uchar; typedef unsigned int uint32; typedef signed int int32; namespace{ inline uint32 sha1Shift(uint32 v, uint32 shift) { return ((v << shift) | (v >> (32 - shift))); } void sha1PartHash(uint32 *sha, uint32 *temp) { uint32 a = sha[0], b = sha[1], c = sha[2], d = sha[3], e = sha[4], round = 0; #define _shiftswap(f, v) { \ uint32 t = sha1Shift(a, 5) + (f) + e + v + temp[round]; \ e = d; \ d = c; \ c = sha1Shift(b, 30); \ b = a; \ a = t; \ ++round; \ } #define _shiftshiftswap(f, v) { \ temp[round] = sha1Shift((temp[round - 3] ^ temp[round - 8] ^ temp[round - 14] ^ temp[round - 16]), 1); \ _shiftswap(f, v) \ } while (round < 16) _shiftswap((b & c) | (~b & d), 0x5a827999) while (round < 20) _shiftshiftswap((b & c) | (~b & d), 0x5a827999) while (round < 40) _shiftshiftswap(b ^ c ^ d, 0x6ed9eba1) while (round < 60) _shiftshiftswap((b & c) | (b & d) | (c & d), 0x8f1bbcdc) while (round < 80) _shiftshiftswap(b ^ c ^ d, 0xca62c1d6) #undef _shiftshiftswap #undef _shiftswap sha[0] += a; sha[1] += b; sha[2] += c; sha[3] += d; sha[4] += e; } } // namespace int32 *hashSha1(const void *data, uint32 len, void *dest) { const uchar *buf = (const uchar *)data; uint32 temp[80], block = 0, end; uint32 sha[5] = {0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0}; for (end = block + 64; block + 64 <= len; end = block + 64) { for (uint32 i = 0; block < end; block += 4) { temp[i++] = (uint32) buf[block + 3] | (((uint32) buf[block + 2]) << 8) | (((uint32) buf[block + 1]) << 16) | (((uint32) buf[block]) << 24); } sha1PartHash(sha, temp); } end = len - block; memset(temp, 0, sizeof(uint32) * 16); uint32 last = 0; for (; last < end; ++last) { temp[last >> 2] |= (uint32)buf[last + block] << ((3 - (last & 0x03)) << 3); } temp[last >> 2] |= 0x80 << ((3 - (last & 3)) << 3); if (end >= 56) { sha1PartHash(sha, temp); memset(temp, 0, sizeof(uint32) * 16); } temp[15] = len << 3; sha1PartHash(sha, temp); uchar *sha1To = (uchar*)dest; for (int32 i = 19; i >= 0; --i) { sha1To[i] = (sha[i >> 2] >> (((3 - i) & 0x03) << 3)) & 0xFF; } return (int32*)sha1To; } QString BetaSignature; int main(int argc, char *argv[]) { QString workDir; QString remove; int version = 0; bool target32 = false; QFileInfoList files; for (int i = 0; i < argc; ++i) { if (string("-path") == argv[i] && i + 1 < argc) { QString path = workDir + QString(argv[i + 1]); QFileInfo info(path); files.push_back(info); if (remove.isEmpty()) remove = info.canonicalPath() + "/"; } else if (string("-target") == argv[i] && i + 1 < argc) { target32 = (string("mac32") == argv[i + 1]); } else if (string("-version") == argv[i] && i + 1 < argc) { version = QString(argv[i + 1]).toInt(); } else if (string("-alpha") == argv[i]) { AlphaChannel = true; } else if (string("-beta") == argv[i] && i + 1 < argc) { BetaVersion = QString(argv[i + 1]).toULongLong(); if (BetaVersion > version * 1000ULL && BetaVersion < (version + 1) * 1000ULL) { AlphaChannel = false; BetaSignature = countBetaVersionSignature(BetaVersion); if (BetaSignature.isEmpty()) { return -1; } } else { cout << "Bad -beta param value passed, should be for the same version: " << version << ", beta: " << BetaVersion << "\n"; return -1; } } } if (files.isEmpty() || remove.isEmpty() || version <= 1016 || version > 999999999) { #ifdef Q_OS_WIN cout << "Usage: Packer.exe -path {file} -version {version} OR Packer.exe -path {dir} -version {version}\n"; #elif defined Q_OS_MAC cout << "Usage: Packer.app -path {file} -version {version} OR Packer.app -path {dir} -version {version}\n"; #else cout << "Usage: Packer -path {file} -version {version} OR Packer -path {dir} -version {version}\n"; #endif return -1; } bool hasDirs = true; while (hasDirs) { hasDirs = false; for (QFileInfoList::iterator i = files.begin(); i != files.end(); ++i) { QFileInfo info(*i); QString fullPath = info.canonicalFilePath(); if (info.isDir()) { hasDirs = true; files.erase(i); QDir d = QDir(info.absoluteFilePath()); QString fullDir = d.canonicalPath(); QStringList entries = d.entryList(QDir::Files | QDir::Dirs | QDir::NoSymLinks | QDir::NoDotAndDotDot); files.append(d.entryInfoList(QDir::Files | QDir::Dirs | QDir::NoSymLinks | QDir::NoDotAndDotDot)); break; } else if (!info.isReadable()) { cout << "Can't read: " << info.absoluteFilePath().toUtf8().constData() << "\n"; return -1; } else if (info.isHidden()) { hasDirs = true; files.erase(i); break; } } } for (QFileInfoList::iterator i = files.begin(); i != files.end(); ++i) { QFileInfo info(*i); if (!info.canonicalFilePath().startsWith(remove)) { cout << "Can't find '" << remove.toUtf8().constData() << "' in file '" << info.canonicalFilePath().toUtf8().constData() << "' :(\n"; return -1; } } QByteArray result; { QBuffer buffer(&result); buffer.open(QIODevice::WriteOnly); QDataStream stream(&buffer); stream.setVersion(QDataStream::Qt_5_1); if (BetaVersion) { stream << quint32(0x7FFFFFFF); stream << quint64(BetaVersion); } else { stream << quint32(version); } stream << quint32(files.size()); cout << "Found " << files.size() << " file" << (files.size() == 1 ? "" : "s") << "..\n"; for (QFileInfoList::iterator i = files.begin(); i != files.end(); ++i) { QFileInfo info(*i); QString fullName = info.canonicalFilePath(); QString name = fullName.mid(remove.length()); cout << name.toUtf8().constData() << " (" << info.size() << ")\n"; QFile f(fullName); if (!f.open(QIODevice::ReadOnly)) { cout << "Can't open '" << fullName.toUtf8().constData() << "' for read..\n"; return -1; } QByteArray inner = f.readAll(); stream << name << quint32(inner.size()) << inner; #if defined Q_OS_MAC || defined Q_OS_LINUX stream << (QFileInfo(fullName).isExecutable() ? true : false); #endif } if (stream.status() != QDataStream::Ok) { cout << "Stream status is bad: " << stream.status() << "\n"; return -1; } } int32 resultSize = result.size(); cout << "Compression start, size: " << resultSize << "\n"; QByteArray compressed, resultCheck; #ifdef Q_OS_WIN // use Lzma SDK for win const int32 hSigLen = 128, hShaLen = 20, hPropsLen = LZMA_PROPS_SIZE, hOriginalSizeLen = sizeof(int32), hSize = hSigLen + hShaLen + hPropsLen + hOriginalSizeLen; // header compressed.resize(hSize + resultSize + 1024 * 1024); // rsa signature + sha1 + lzma props + max compressed size size_t compressedLen = compressed.size() - hSize; size_t outPropsSize = LZMA_PROPS_SIZE; uchar *_dest = (uchar*)(compressed.data() + hSize); size_t *_destLen = &compressedLen; const uchar *_src = (const uchar*)(result.constData()); size_t _srcLen = result.size(); uchar *_outProps = (uchar*)(compressed.data() + hSigLen + hShaLen); int res = LzmaCompress(_dest, _destLen, _src, _srcLen, _outProps, &outPropsSize, 9, 64 * 1024 * 1024, 4, 0, 2, 273, 2); if (res != SZ_OK) { cout << "Error in compression: " << res << "\n"; return -1; } compressed.resize(int(hSize + compressedLen)); memcpy(compressed.data() + hSigLen + hShaLen + hPropsLen, &resultSize, hOriginalSizeLen); cout << "Compressed to size: " << compressedLen << "\n"; cout << "Checking uncompressed..\n"; int32 resultCheckLen; memcpy(&resultCheckLen, compressed.constData() + hSigLen + hShaLen + hPropsLen, hOriginalSizeLen); if (resultCheckLen <= 0 || resultCheckLen > 1024 * 1024 * 1024) { cout << "Bad result len: " << resultCheckLen << "\n"; return -1; } resultCheck.resize(resultCheckLen); size_t resultLen = resultCheck.size(); SizeT srcLen = compressedLen; int uncompressRes = LzmaUncompress((uchar*)resultCheck.data(), &resultLen, (const uchar*)(compressed.constData() + hSize), &srcLen, (const uchar*)(compressed.constData() + hSigLen + hShaLen), LZMA_PROPS_SIZE); if (uncompressRes != SZ_OK) { cout << "Uncompress failed: " << uncompressRes << "\n"; return -1; } if (resultLen != size_t(result.size())) { cout << "Uncompress bad size: " << resultLen << ", was: " << result.size() << "\n"; return -1; } #else // use liblzma for others const int32 hSigLen = 128, hShaLen = 20, hPropsLen = 0, hOriginalSizeLen = sizeof(int32), hSize = hSigLen + hShaLen + hOriginalSizeLen; // header compressed.resize(hSize + resultSize + 1024 * 1024); // rsa signature + sha1 + lzma props + max compressed size size_t compressedLen = compressed.size() - hSize; lzma_stream stream = LZMA_STREAM_INIT; int preset = 9 | LZMA_PRESET_EXTREME; lzma_ret ret = lzma_easy_encoder(&stream, preset, LZMA_CHECK_CRC64); if (ret != LZMA_OK) { const char *msg; switch (ret) { case LZMA_MEM_ERROR: msg = "Memory allocation failed"; break; case LZMA_OPTIONS_ERROR: msg = "Specified preset is not supported"; break; case LZMA_UNSUPPORTED_CHECK: msg = "Specified integrity check is not supported"; break; default: msg = "Unknown error, possibly a bug"; break; } cout << "Error initializing the encoder: " << msg << " (error code " << ret << ")\n"; return -1; } stream.avail_in = resultSize; stream.next_in = (uint8_t*)result.constData(); stream.avail_out = compressedLen; stream.next_out = (uint8_t*)(compressed.data() + hSize); lzma_ret res = lzma_code(&stream, LZMA_FINISH); compressedLen -= stream.avail_out; lzma_end(&stream); if (res != LZMA_OK && res != LZMA_STREAM_END) { const char *msg; switch (res) { case LZMA_MEM_ERROR: msg = "Memory allocation failed"; break; case LZMA_DATA_ERROR: msg = "File size limits exceeded"; break; default: msg = "Unknown error, possibly a bug"; break; } cout << "Error in compression: " << msg << " (error code " << res << ")\n"; return -1; } compressed.resize(int(hSize + compressedLen)); memcpy(compressed.data() + hSigLen + hShaLen, &resultSize, hOriginalSizeLen); cout << "Compressed to size: " << compressedLen << "\n"; cout << "Checking uncompressed..\n"; int32 resultCheckLen; memcpy(&resultCheckLen, compressed.constData() + hSigLen + hShaLen, hOriginalSizeLen); if (resultCheckLen <= 0 || resultCheckLen > 1024 * 1024 * 1024) { cout << "Bad result len: " << resultCheckLen << "\n"; return -1; } resultCheck.resize(resultCheckLen); size_t resultLen = resultCheck.size(); stream = LZMA_STREAM_INIT; ret = lzma_stream_decoder(&stream, UINT64_MAX, LZMA_CONCATENATED); if (ret != LZMA_OK) { const char *msg; switch (ret) { case LZMA_MEM_ERROR: msg = "Memory allocation failed"; break; case LZMA_OPTIONS_ERROR: msg = "Specified preset is not supported"; break; case LZMA_UNSUPPORTED_CHECK: msg = "Specified integrity check is not supported"; break; default: msg = "Unknown error, possibly a bug"; break; } cout << "Error initializing the decoder: " << msg << " (error code " << ret << ")\n"; return -1; } stream.avail_in = compressedLen; stream.next_in = (uint8_t*)(compressed.constData() + hSize); stream.avail_out = resultLen; stream.next_out = (uint8_t*)resultCheck.data(); res = lzma_code(&stream, LZMA_FINISH); if (stream.avail_in) { cout << "Error in decompression, " << stream.avail_in << " bytes left in _in of " << compressedLen << " whole.\n"; return -1; } else if (stream.avail_out) { cout << "Error in decompression, " << stream.avail_out << " bytes free left in _out of " << resultLen << " whole.\n"; return -1; } lzma_end(&stream); if (res != LZMA_OK && res != LZMA_STREAM_END) { const char *msg; switch (res) { case LZMA_MEM_ERROR: msg = "Memory allocation failed"; break; case LZMA_FORMAT_ERROR: msg = "The input data is not in the .xz format"; break; case LZMA_OPTIONS_ERROR: msg = "Unsupported compression options"; break; case LZMA_DATA_ERROR: msg = "Compressed file is corrupt"; break; case LZMA_BUF_ERROR: msg = "Compressed data is truncated or otherwise corrupt"; break; default: msg = "Unknown error, possibly a bug"; break; } cout << "Error in decompression: " << msg << " (error code " << res << ")\n"; return -1; } #endif if (memcmp(result.constData(), resultCheck.constData(), resultLen)) { cout << "Data differ :(\n"; return -1; } /**/ result = resultCheck = QByteArray(); cout << "Counting SHA1 hash..\n"; uchar sha1Buffer[20]; memcpy(compressed.data() + hSigLen, hashSha1(compressed.constData() + hSigLen + hShaLen, uint32(compressedLen + hPropsLen + hOriginalSizeLen), sha1Buffer), hShaLen); // count sha1 uint32 siglen = 0; cout << "Signing..\n"; RSA *prKey = PEM_read_bio_RSAPrivateKey(BIO_new_mem_buf(const_cast((AlphaChannel || BetaVersion) ? PrivateAlphaKey : PrivateKey), -1), 0, 0, 0); if (!prKey) { cout << "Could not read RSA private key!\n"; return -1; } if (RSA_size(prKey) != hSigLen) { cout << "Bad private key, size: " << RSA_size(prKey) << "\n"; RSA_free(prKey); return -1; } if (RSA_sign(NID_sha1, (const uchar*)(compressed.constData() + hSigLen), hShaLen, (uchar*)(compressed.data()), &siglen, prKey) != 1) { // count signature cout << "Signing failed!\n"; RSA_free(prKey); return -1; } RSA_free(prKey); if (siglen != hSigLen) { cout << "Bad signature length: " << siglen << "\n"; return -1; } cout << "Checking signature..\n"; RSA *pbKey = PEM_read_bio_RSAPublicKey(BIO_new_mem_buf(const_cast((AlphaChannel || BetaVersion) ? PublicAlphaKey : PublicKey), -1), 0, 0, 0); if (!pbKey) { cout << "Could not read RSA public key!\n"; return -1; } if (RSA_verify(NID_sha1, (const uchar*)(compressed.constData() + hSigLen), hShaLen, (const uchar*)(compressed.constData()), siglen, pbKey) != 1) { // verify signature RSA_free(pbKey); cout << "Signature verification failed!\n"; return -1; } cout << "Signature verified!\n"; RSA_free(pbKey); #ifdef Q_OS_WIN QString outName(QString("tupdate%1").arg(BetaVersion ? BetaVersion : version)); #elif defined Q_OS_MAC QString outName((target32 ? QString("tmac32upd%1") : QString("tmacupd%1")).arg(BetaVersion ? BetaVersion : version)); #elif defined Q_OS_LINUX32 QString outName(QString("tlinux32upd%1").arg(BetaVersion ? BetaVersion : version)); #elif defined Q_OS_LINUX64 QString outName(QString("tlinuxupd%1").arg(BetaVersion ? BetaVersion : version)); #else #error Unknown platform! #endif if (BetaVersion) { outName += "_" + BetaSignature; } QFile out(outName); if (!out.open(QIODevice::WriteOnly)) { cout << "Can't open '" << outName.toUtf8().constData() << "' for write..\n"; return -1; } out.write(compressed); out.close(); if (BetaVersion) { QString keyName(QString("tbeta_%1_key").arg(BetaVersion)); QFile key(keyName); if (!key.open(QIODevice::WriteOnly)) { cout << "Can't open '" << keyName.toUtf8().constData() << "' for write..\n"; return -1; } key.write(BetaSignature.toUtf8()); key.close(); } cout << "Update file '" << outName.toUtf8().constData() << "' written successfully!\n"; return 0; } QString countBetaVersionSignature(quint64 version) { // duplicated in autoupdater.cpp QByteArray cBetaPrivateKey(BetaPrivateKey); if (cBetaPrivateKey.isEmpty()) { cout << "Error: Trying to count beta version signature without beta private key!\n"; return QString(); } QByteArray signedData = (QLatin1String("TelegramBeta_") + QString::number(version, 16).toLower()).toUtf8(); static const int32 shaSize = 20, keySize = 128; uchar sha1Buffer[shaSize]; hashSha1(signedData.constData(), signedData.size(), sha1Buffer); // count sha1 uint32 siglen = 0; RSA *prKey = PEM_read_bio_RSAPrivateKey(BIO_new_mem_buf(const_cast(cBetaPrivateKey.constData()), -1), 0, 0, 0); if (!prKey) { cout << "Error: Could not read beta private key!\n"; return QString(); } if (RSA_size(prKey) != keySize) { cout << "Error: Bad beta private key size: " << RSA_size(prKey) << "\n"; RSA_free(prKey); return QString(); } QByteArray signature; signature.resize(keySize); if (RSA_sign(NID_sha1, (const uchar*)(sha1Buffer), shaSize, (uchar*)(signature.data()), &siglen, prKey) != 1) { // count signature cout << "Error: Counting beta version signature failed!\n"; RSA_free(prKey); return QString(); } RSA_free(prKey); if (siglen != keySize) { cout << "Error: Bad beta version signature length: " << siglen << "\n"; return QString(); } signature = signature.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); signature = signature.replace('-', '8').replace('_', 'B'); return QString::fromUtf8(signature.mid(19, 32)); } tdesktop-1.2.17/Telegram/SourceFiles/_other/packer.h000066400000000000000000000015241326245125100223230ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_WIN // use Lzma SDK for win #include #else #include #endif #include #include #include using std::string; using std::wstring; using std::cout;tdesktop-1.2.17/Telegram/SourceFiles/_other/updater.cpp000066400000000000000000000476761326245125100230770ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "updater.h" bool _debug = false; wstring updaterName, updaterDir, updateTo, exeName, customWorkingDir, customKeyFile; bool equal(const wstring &a, const wstring &b) { return !_wcsicmp(a.c_str(), b.c_str()); } void updateError(const WCHAR *msg, DWORD errorCode) { WCHAR errMsg[2048]; LPWSTR errorTextFormatted = nullptr; auto formatFlags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS; FormatMessage( formatFlags, NULL, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&errorTextFormatted, 0, 0); auto errorText = errorTextFormatted ? errorTextFormatted : L"(Unknown error)"; wsprintf(errMsg, L"%s, error code: %d\nError message: %s", msg, errorCode, errorText); MessageBox(0, errMsg, L"Update error!", MB_ICONERROR); LocalFree(errorTextFormatted); } HANDLE _logFile = 0; void openLog() { if (!_debug || _logFile) return; wstring logPath = L"DebugLogs"; if (!CreateDirectory(logPath.c_str(), NULL)) { DWORD errorCode = GetLastError(); if (errorCode && errorCode != ERROR_ALREADY_EXISTS) { updateError(L"Failed to create log directory", errorCode); return; } } SYSTEMTIME stLocalTime; GetLocalTime(&stLocalTime); static const int maxFileLen = MAX_PATH * 10; WCHAR logName[maxFileLen]; wsprintf(logName, L"DebugLogs\\%04d%02d%02d_%02d%02d%02d_upd.txt", stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay, stLocalTime.wHour, stLocalTime.wMinute, stLocalTime.wSecond); _logFile = CreateFile(logName, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); if (_logFile == INVALID_HANDLE_VALUE) { // :( updateError(L"Failed to create log file", GetLastError()); _logFile = 0; return; } } void closeLog() { if (!_logFile) return; CloseHandle(_logFile); _logFile = 0; } void writeLog(const wstring &msg) { if (!_logFile) return; wstring full = msg + L'\n'; DWORD written = 0; BOOL result = WriteFile(_logFile, full.c_str(), full.size() * sizeof(wchar_t), &written, 0); if (!result) { updateError((L"Failed to write log entry '" + msg + L"'").c_str(), GetLastError()); closeLog(); return; } BOOL flushr = FlushFileBuffers(_logFile); if (!flushr) { updateError((L"Failed to flush log on entry '" + msg + L"'").c_str(), GetLastError()); closeLog(); return; } } void fullClearPath(const wstring &dir) { WCHAR path[4096]; memcpy(path, dir.c_str(), (dir.size() + 1) * sizeof(WCHAR)); path[dir.size() + 1] = 0; writeLog(L"Fully clearing path '" + dir + L"'.."); SHFILEOPSTRUCT file_op = { NULL, FO_DELETE, path, L"", FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT, false, 0, L"" }; int res = SHFileOperation(&file_op); if (res) writeLog(L"Error: failed to clear path! :("); } void delFolder() { wstring delPathOld = L"tupdates\\ready", delPath = L"tupdates\\temp", delFolder = L"tupdates"; fullClearPath(delPathOld); fullClearPath(delPath); RemoveDirectory(delFolder.c_str()); } DWORD versionNum = 0, versionLen = 0, readLen = 0; WCHAR versionStr[32] = { 0 }; bool update() { writeLog(L"Update started.."); wstring updDir = L"tupdates\\temp", readyFilePath = L"tupdates\\temp\\ready", tdataDir = L"tupdates\\temp\\tdata"; { HANDLE readyFile = CreateFile(readyFilePath.c_str(), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); if (readyFile != INVALID_HANDLE_VALUE) { CloseHandle(readyFile); } else { updDir = L"tupdates\\ready"; // old tdataDir = L"tupdates\\ready\\tdata"; } } HANDLE versionFile = CreateFile((tdataDir + L"\\version").c_str(), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); if (versionFile != INVALID_HANDLE_VALUE) { if (!ReadFile(versionFile, &versionNum, sizeof(DWORD), &readLen, NULL) || readLen != sizeof(DWORD)) { versionNum = 0; } else { if (versionNum == 0x7FFFFFFF) { // beta version } else if (!ReadFile(versionFile, &versionLen, sizeof(DWORD), &readLen, NULL) || readLen != sizeof(DWORD) || versionLen > 63) { versionNum = 0; } else if (!ReadFile(versionFile, versionStr, versionLen, &readLen, NULL) || readLen != versionLen) { versionNum = 0; } } CloseHandle(versionFile); writeLog(L"Version file read."); } else { writeLog(L"Could not open version file to update registry :("); } deque dirs; dirs.push_back(updDir); deque from, to, forcedirs; do { wstring dir = dirs.front(); dirs.pop_front(); wstring toDir = updateTo; if (dir.size() > updDir.size() + 1) { toDir += (dir.substr(updDir.size() + 1) + L"\\"); forcedirs.push_back(toDir); writeLog(L"Parsing dir '" + toDir + L"' in update tree.."); } WIN32_FIND_DATA findData; HANDLE findHandle = FindFirstFileEx((dir + L"\\*").c_str(), FindExInfoStandard, &findData, FindExSearchNameMatch, 0, 0); if (findHandle == INVALID_HANDLE_VALUE) { DWORD errorCode = GetLastError(); if (errorCode == ERROR_PATH_NOT_FOUND) { // no update is ready return true; } writeLog(L"Error: failed to find update files :("); updateError(L"Failed to find update files", errorCode); delFolder(); return false; } do { wstring fname = dir + L"\\" + findData.cFileName; if (fname.substr(0, tdataDir.size()) == tdataDir && (fname.size() <= tdataDir.size() || fname.at(tdataDir.size()) == '/')) { writeLog(L"Skipped 'tdata' path '" + fname + L"'"); } else if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { if (findData.cFileName != wstring(L".") && findData.cFileName != wstring(L"..")) { dirs.push_back(fname); writeLog(L"Added dir '" + fname + L"' in update tree.."); } } else { wstring tofname = updateTo + fname.substr(updDir.size() + 1); if (equal(tofname, updaterName)) { // bad update - has Updater.exe - delete all dir writeLog(L"Error: bad update, has Updater.exe! '" + tofname + L"' equal '" + updaterName + L"'"); delFolder(); return false; } else if (equal(tofname, updateTo + L"Telegram.exe") && exeName != L"Telegram.exe") { wstring fullBinaryPath = updateTo + exeName; writeLog(L"Target binary found: '" + tofname + L"', changing to '" + fullBinaryPath + L"'"); tofname = fullBinaryPath; } if (equal(fname, readyFilePath)) { writeLog(L"Skipped ready file '" + fname + L"'"); } else { from.push_back(fname); to.push_back(tofname); writeLog(L"Added file '" + fname + L"' to be copied to '" + tofname + L"'"); } } } while (FindNextFile(findHandle, &findData)); DWORD errorCode = GetLastError(); if (errorCode && errorCode != ERROR_NO_MORE_FILES) { // everything is found writeLog(L"Error: failed to find next update file :("); updateError(L"Failed to find next update file", errorCode); delFolder(); return false; } FindClose(findHandle); } while (!dirs.empty()); for (size_t i = 0; i < forcedirs.size(); ++i) { wstring forcedir = forcedirs[i]; writeLog(L"Forcing dir '" + forcedir + L"'.."); if (!forcedir.empty() && !CreateDirectory(forcedir.c_str(), NULL)) { DWORD errorCode = GetLastError(); if (errorCode && errorCode != ERROR_ALREADY_EXISTS) { writeLog(L"Error: failed to create dir '" + forcedir + L"'.."); updateError(L"Failed to create directory", errorCode); delFolder(); return false; } writeLog(L"Already exists!"); } } for (size_t i = 0; i < from.size(); ++i) { wstring fname = from[i], tofname = to[i]; BOOL copyResult; do { writeLog(L"Copying file '" + fname + L"' to '" + tofname + L"'.."); int copyTries = 0; do { copyResult = CopyFile(fname.c_str(), tofname.c_str(), FALSE); if (!copyResult) { ++copyTries; Sleep(100); } else { break; } } while (copyTries < 100); if (!copyResult) { writeLog(L"Error: failed to copy, asking to retry.."); WCHAR errMsg[2048]; wsprintf(errMsg, L"Failed to update Telegram :(\n%s is not accessible.", tofname.c_str()); if (MessageBox(0, errMsg, L"Update error!", MB_ICONERROR | MB_RETRYCANCEL) != IDRETRY) { delFolder(); return false; } } } while (!copyResult); } writeLog(L"Update succeed! Clearing folder.."); delFolder(); return true; } void updateRegistry() { if (versionNum && versionNum != 0x7FFFFFFF) { writeLog(L"Updating registry.."); versionStr[versionLen / 2] = 0; HKEY rkey; LSTATUS status = RegOpenKeyEx(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{53F49750-6209-4FBF-9CA8-7A333C87D1ED}_is1", 0, KEY_QUERY_VALUE | KEY_SET_VALUE, &rkey); if (status == ERROR_SUCCESS) { writeLog(L"Checking registry install location.."); static const int bufSize = 4096; DWORD locationType, locationSize = bufSize * 2; WCHAR locationStr[bufSize], exp[bufSize]; if (RegQueryValueEx(rkey, L"InstallLocation", 0, &locationType, (BYTE*)locationStr, &locationSize) == ERROR_SUCCESS) { locationSize /= 2; if (locationStr[locationSize - 1]) { locationStr[locationSize++] = 0; } if (locationType == REG_EXPAND_SZ) { DWORD copy = ExpandEnvironmentStrings(locationStr, exp, bufSize); if (copy <= bufSize) { memcpy(locationStr, exp, copy * sizeof(WCHAR)); } } if (locationType == REG_EXPAND_SZ || locationType == REG_SZ) { if (PathCanonicalize(exp, locationStr)) { memcpy(locationStr, exp, bufSize * sizeof(WCHAR)); if (GetFullPathName(L".", bufSize, exp, 0) < bufSize) { wstring installpath = locationStr, mypath = exp; if (installpath == mypath + L"\\" || true) { // always update reg info, if we found it WCHAR nameStr[bufSize], dateStr[bufSize], publisherStr[bufSize], icongroupStr[bufSize]; SYSTEMTIME stLocalTime; GetLocalTime(&stLocalTime); RegSetValueEx(rkey, L"DisplayVersion", 0, REG_SZ, (const BYTE*)versionStr, ((versionLen / 2) + 1) * sizeof(WCHAR)); wsprintf(nameStr, L"Telegram Desktop version %s", versionStr); RegSetValueEx(rkey, L"DisplayName", 0, REG_SZ, (const BYTE*)nameStr, (wcslen(nameStr) + 1) * sizeof(WCHAR)); wsprintf(publisherStr, L"Telegram Messenger LLP"); RegSetValueEx(rkey, L"Publisher", 0, REG_SZ, (const BYTE*)publisherStr, (wcslen(publisherStr) + 1) * sizeof(WCHAR)); wsprintf(icongroupStr, L"Telegram Desktop"); RegSetValueEx(rkey, L"Inno Setup: Icon Group", 0, REG_SZ, (const BYTE*)icongroupStr, (wcslen(icongroupStr) + 1) * sizeof(WCHAR)); wsprintf(dateStr, L"%04d%02d%02d", stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay); RegSetValueEx(rkey, L"InstallDate", 0, REG_SZ, (const BYTE*)dateStr, (wcslen(dateStr) + 1) * sizeof(WCHAR)); const WCHAR *appURL = L"https://desktop.telegram.org"; RegSetValueEx(rkey, L"HelpLink", 0, REG_SZ, (const BYTE*)appURL, (wcslen(appURL) + 1) * sizeof(WCHAR)); RegSetValueEx(rkey, L"URLInfoAbout", 0, REG_SZ, (const BYTE*)appURL, (wcslen(appURL) + 1) * sizeof(WCHAR)); RegSetValueEx(rkey, L"URLUpdateInfo", 0, REG_SZ, (const BYTE*)appURL, (wcslen(appURL) + 1) * sizeof(WCHAR)); } } } } } RegCloseKey(rkey); } } } int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE prevInstance, LPWSTR cmdParamarg, int cmdShow) { openLog(); _oldWndExceptionFilter = SetUnhandledExceptionFilter(_exceptionFilter); // CAPIHook apiHook("kernel32.dll", "SetUnhandledExceptionFilter", (PROC)RedirectedSetUnhandledExceptionFilter); writeLog(L"Updaters started.."); LPWSTR *args; int argsCount; bool needupdate = false, autostart = false, debug = false, writeprotected = false, startintray = false, testmode = false; args = CommandLineToArgvW(GetCommandLine(), &argsCount); if (args) { for (int i = 1; i < argsCount; ++i) { writeLog(std::wstring(L"Argument: ") + args[i]); if (equal(args[i], L"-update")) { needupdate = true; } else if (equal(args[i], L"-autostart")) { autostart = true; } else if (equal(args[i], L"-debug")) { debug = _debug = true; openLog(); } else if (equal(args[i], L"-startintray")) { startintray = true; } else if (equal(args[i], L"-testmode")) { testmode = true; } else if (equal(args[i], L"-writeprotected") && ++i < argsCount) { writeLog(std::wstring(L"Argument: ") + args[i]); writeprotected = true; updateTo = args[i]; for (int j = 0, l = updateTo.size(); j < l; ++j) { if (updateTo[j] == L'/') { updateTo[j] = L'\\'; } } } else if (equal(args[i], L"-workdir") && ++i < argsCount) { writeLog(std::wstring(L"Argument: ") + args[i]); customWorkingDir = args[i]; } else if (equal(args[i], L"-key") && ++i < argsCount) { writeLog(std::wstring(L"Argument: ") + args[i]); customKeyFile = args[i]; } else if (equal(args[i], L"-exename") && ++i < argsCount) { writeLog(std::wstring(L"Argument: ") + args[i]); exeName = args[i]; for (int j = 0, l = exeName.size(); j < l; ++j) { if (exeName[j] == L'/' || exeName[j] == L'\\') { exeName = L"Telegram.exe"; break; } } } } if (exeName.empty()) { exeName = L"Telegram.exe"; } if (needupdate) writeLog(L"Need to update!"); if (autostart) writeLog(L"From autostart!"); if (writeprotected) writeLog(L"Write Protected folder!"); if (!customWorkingDir.empty()) writeLog(L"Will pass custom working dir: " + customWorkingDir); updaterName = args[0]; writeLog(L"Updater name is: " + updaterName); if (updaterName.size() > 11) { if (equal(updaterName.substr(updaterName.size() - 11), L"Updater.exe")) { updaterDir = updaterName.substr(0, updaterName.size() - 11); writeLog(L"Updater dir is: " + updaterDir); if (!writeprotected) { updateTo = updaterDir; } writeLog(L"Update to: " + updateTo); if (needupdate && update()) { updateRegistry(); } if (writeprotected) { // if we can't clear all tupdates\ready (Updater.exe is there) - clear only version if (DeleteFile(L"tupdates\\temp\\tdata\\version") || DeleteFile(L"tupdates\\ready\\tdata\\version")) { writeLog(L"Version file deleted!"); } else { writeLog(L"Error: could not delete version file"); } } } else { writeLog(L"Error: bad exe name!"); } } else { writeLog(L"Error: short exe name!"); } LocalFree(args); } else { writeLog(L"Error: No command line arguments!"); } wstring targs; if (autostart) targs += L" -autostart"; if (debug) targs += L" -debug"; if (startintray) targs += L" -startintray"; if (testmode) targs += L" -testmode"; if (!customWorkingDir.empty()) { targs += L" -workdir \"" + customWorkingDir + L"\""; } if (!customKeyFile.empty()) { targs += L" -key \"" + customKeyFile + L"\""; } writeLog(L"Result arguments: " + targs); bool executed = false; if (writeprotected) { // run un-elevated writeLog(L"Trying to run un-elevated by temp.lnk"); HRESULT hres = CoInitialize(0); if (SUCCEEDED(hres)) { IShellLink* psl; HRESULT hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl); if (SUCCEEDED(hres)) { IPersistFile* ppf; wstring exe = updateTo + exeName, dir = updateTo; psl->SetArguments((targs.size() ? targs.substr(1) : targs).c_str()); psl->SetPath(exe.c_str()); psl->SetWorkingDirectory(dir.c_str()); psl->SetDescription(L""); hres = psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf); if (SUCCEEDED(hres)) { wstring lnk = L"tupdates\\temp\\temp.lnk"; hres = ppf->Save(lnk.c_str(), TRUE); if (!SUCCEEDED(hres)) { lnk = L"tupdates\\ready\\temp.lnk"; // old hres = ppf->Save(lnk.c_str(), TRUE); } ppf->Release(); if (SUCCEEDED(hres)) { writeLog(L"Executing un-elevated through link.."); ShellExecute(0, 0, L"explorer.exe", lnk.c_str(), 0, SW_SHOWNORMAL); executed = true; } else { writeLog(L"Error: ppf->Save failed"); } } else { writeLog(L"Error: Could not create interface IID_IPersistFile"); } psl->Release(); } else { writeLog(L"Error: could not create instance of IID_IShellLink"); } CoUninitialize(); } else { writeLog(L"Error: Could not initialize COM"); } } if (!executed) { ShellExecute(0, 0, (updateTo + exeName).c_str(), (L"-noupdate" + targs).c_str(), 0, SW_SHOWNORMAL); } writeLog(L"Executed '" + exeName + L"', closing log and quitting.."); closeLog(); return 0; } static const WCHAR *_programName = L"Telegram Desktop"; // folder in APPDATA, if current path is unavailable for writing static const WCHAR *_exeName = L"Updater.exe"; LPTOP_LEVEL_EXCEPTION_FILTER _oldWndExceptionFilter = 0; typedef BOOL (FAR STDAPICALLTYPE *t_miniDumpWriteDump)( _In_ HANDLE hProcess, _In_ DWORD ProcessId, _In_ HANDLE hFile, _In_ MINIDUMP_TYPE DumpType, _In_opt_ PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, _In_opt_ PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, _In_opt_ PMINIDUMP_CALLBACK_INFORMATION CallbackParam ); t_miniDumpWriteDump miniDumpWriteDump = 0; HANDLE _generateDumpFileAtPath(const WCHAR *path) { static const int maxFileLen = MAX_PATH * 10; WCHAR szPath[maxFileLen]; wsprintf(szPath, L"%stdata\\", path); if (!CreateDirectory(szPath, NULL)) { if (GetLastError() != ERROR_ALREADY_EXISTS) { return 0; } } wsprintf(szPath, L"%sdumps\\", path); if (!CreateDirectory(szPath, NULL)) { if (GetLastError() != ERROR_ALREADY_EXISTS) { return 0; } } WCHAR szFileName[maxFileLen]; WCHAR szExeName[maxFileLen]; wcscpy_s(szExeName, _exeName); WCHAR *dotFrom = wcschr(szExeName, WCHAR(L'.')); if (dotFrom) { wsprintf(dotFrom, L""); } SYSTEMTIME stLocalTime; GetLocalTime(&stLocalTime); wsprintf(szFileName, L"%s%s-%s-%04d%02d%02d-%02d%02d%02d-%ld-%ld.dmp", szPath, szExeName, updaterVersionStr, stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay, stLocalTime.wHour, stLocalTime.wMinute, stLocalTime.wSecond, GetCurrentProcessId(), GetCurrentThreadId()); return CreateFile(szFileName, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_WRITE|FILE_SHARE_READ, 0, CREATE_ALWAYS, 0, 0); } void _generateDump(EXCEPTION_POINTERS* pExceptionPointers) { static const int maxFileLen = MAX_PATH * 10; closeLog(); HMODULE hDll = LoadLibrary(L"DBGHELP.DLL"); if (!hDll) return; miniDumpWriteDump = (t_miniDumpWriteDump)GetProcAddress(hDll, "MiniDumpWriteDump"); if (!miniDumpWriteDump) return; HANDLE hDumpFile = 0; WCHAR szPath[maxFileLen]; DWORD len = GetModuleFileName(GetModuleHandle(0), szPath, maxFileLen); if (!len) return; WCHAR *pathEnd = szPath + len; if (!_wcsicmp(pathEnd - wcslen(_exeName), _exeName)) { wsprintf(pathEnd - wcslen(_exeName), L""); hDumpFile = _generateDumpFileAtPath(szPath); } if (!hDumpFile || hDumpFile == INVALID_HANDLE_VALUE) { WCHAR wstrPath[maxFileLen]; DWORD wstrPathLen; if (wstrPathLen = GetEnvironmentVariable(L"APPDATA", wstrPath, maxFileLen)) { wsprintf(wstrPath + wstrPathLen, L"\\%s\\", _programName); hDumpFile = _generateDumpFileAtPath(wstrPath); } } if (!hDumpFile || hDumpFile == INVALID_HANDLE_VALUE) { return; } MINIDUMP_EXCEPTION_INFORMATION ExpParam = {0}; ExpParam.ThreadId = GetCurrentThreadId(); ExpParam.ExceptionPointers = pExceptionPointers; ExpParam.ClientPointers = TRUE; miniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hDumpFile, MiniDumpWithDataSegs, &ExpParam, NULL, NULL); } LONG CALLBACK _exceptionFilter(EXCEPTION_POINTERS* pExceptionPointers) { _generateDump(pExceptionPointers); return _oldWndExceptionFilter ? (*_oldWndExceptionFilter)(pExceptionPointers) : EXCEPTION_CONTINUE_SEARCH; } // see http://www.codeproject.com/Articles/154686/SetUnhandledExceptionFilter-and-the-C-C-Runtime-Li LPTOP_LEVEL_EXCEPTION_FILTER WINAPI RedirectedSetUnhandledExceptionFilter(_In_opt_ LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter) { // When the CRT calls SetUnhandledExceptionFilter with NULL parameter // our handler will not get removed. _oldWndExceptionFilter = lpTopLevelExceptionFilter; return 0; } tdesktop-1.2.17/Telegram/SourceFiles/_other/updater.h000066400000000000000000000015651326245125100225270ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include #include #pragma warning(push) #pragma warning(disable:4091) #include #include #pragma warning(pop) #include #include #include #include using std::deque; using std::wstring; extern LPTOP_LEVEL_EXCEPTION_FILTER _oldWndExceptionFilter; LONG CALLBACK _exceptionFilter(EXCEPTION_POINTERS* pExceptionPointers); LPTOP_LEVEL_EXCEPTION_FILTER WINAPI RedirectedSetUnhandledExceptionFilter(_In_opt_ LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter); static int updaterVersion = 1000; static const WCHAR *updaterVersionStr = L"0.1.0"; tdesktop-1.2.17/Telegram/SourceFiles/_other/updater_linux.cpp000066400000000000000000000321421326245125100242740ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using std::string; using std::deque; using std::cout; bool do_mkdir(const char *path) { // from http://stackoverflow.com/questions/675039/how-can-i-create-directory-tree-in-c-linux struct stat statbuf; if (stat(path, &statbuf) != 0) { /* Directory does not exist. EEXIST for race condition */ if (mkdir(path, S_IRWXU) != 0 && errno != EEXIST) return false; } else if (!S_ISDIR(statbuf.st_mode)) { errno = ENOTDIR; return false; } return true; } bool _debug = false; string updaterDir; string updaterName; string workDir; string exeName; string exePath; FILE *_logFile = 0; void openLog() { if (!_debug || _logFile) return; if (!do_mkdir((workDir + "DebugLogs").c_str())) { return; } time_t timer; time(&timer); struct tm *t = localtime(&timer); static const int maxFileLen = 65536; char logName[maxFileLen]; sprintf(logName, "%sDebugLogs/%04d%02d%02d_%02d%02d%02d_upd.txt", workDir.c_str(), t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec); _logFile = fopen(logName, "w"); } void closeLog() { if (!_logFile) return; fclose(_logFile); _logFile = 0; } void writeLog(const char *format, ...) { if (!_logFile) { return; } va_list args; va_start(args, format); vfprintf(_logFile, format, args); fprintf(_logFile, "\n"); fflush(_logFile); va_end(args); } bool copyFile(const char *from, const char *to) { FILE *ffrom = fopen(from, "rb"), *fto = fopen(to, "wb"); if (!ffrom) { if (fto) fclose(fto); return false; } if (!fto) { fclose(ffrom); return false; } static const int BufSize = 65536; char buf[BufSize]; while (size_t size = fread(buf, 1, BufSize, ffrom)) { fwrite(buf, 1, size, fto); } struct stat fst; // from http://stackoverflow.com/questions/5486774/keeping-fileowner-and-permissions-after-copying-file-in-c //let's say this wont fail since you already worked OK on that fp if (fstat(fileno(ffrom), &fst) != 0) { fclose(ffrom); fclose(fto); return false; } //update to the same uid/gid if (fchown(fileno(fto), fst.st_uid, fst.st_gid) != 0) { fclose(ffrom); fclose(fto); return false; } //update the permissions if (fchmod(fileno(fto), fst.st_mode) != 0) { fclose(ffrom); fclose(fto); return false; } fclose(ffrom); fclose(fto); return true; } bool remove_directory(const string &path) { // from http://stackoverflow.com/questions/2256945/removing-a-non-empty-directory-programmatically-in-c-or-c DIR *d = opendir(path.c_str()); writeLog("Removing dir '%s'", path.c_str()); if (!d) { writeLog("Could not open dir '%s'", path.c_str()); return (errno == ENOENT); } while (struct dirent *p = readdir(d)) { /* Skip the names "." and ".." as we don't want to recurse on them. */ if (!strcmp(p->d_name, ".") || !strcmp(p->d_name, "..")) continue; string fname = path + '/' + p->d_name; struct stat statbuf; writeLog("Trying to get stat() for '%s'", fname.c_str()); if (!stat(fname.c_str(), &statbuf)) { if (S_ISDIR(statbuf.st_mode)) { if (!remove_directory(fname.c_str())) { closedir(d); return false; } } else { writeLog("Unlinking file '%s'", fname.c_str()); if (unlink(fname.c_str())) { writeLog("Failed to unlink '%s'", fname.c_str()); closedir(d); return false; } } } else { writeLog("Failed to call stat() on '%s'", fname.c_str()); } } closedir(d); writeLog("Finally removing dir '%s'", path.c_str()); return !rmdir(path.c_str()); } bool mkpath(const char *path) { int status = 0, pathsize = strlen(path) + 1; char *copypath = new char[pathsize]; memcpy(copypath, path, pathsize); char *pp = copypath, *sp; while (status == 0 && (sp = strchr(pp, '/')) != 0) { if (sp != pp) { /* Neither root nor double slash in path */ *sp = '\0'; if (!do_mkdir(copypath)) { delete[] copypath; return false; } *sp = '/'; } pp = sp + 1; } delete[] copypath; return do_mkdir(path); } bool equal(string a, string b) { std::transform(a.begin(), a.end(), a.begin(), ::tolower); std::transform(b.begin(), b.end(), b.begin(), ::tolower); return a == b; } void delFolder() { string delPathOld = workDir + "tupdates/ready", delPath = workDir + "tupdates/temp", delFolder = workDir + "tupdates"; writeLog("Fully clearing old path '%s'..", delPathOld.c_str()); if (!remove_directory(delPathOld)) { writeLog("Failed to clear old path! :( New path was used?.."); } writeLog("Fully clearing path '%s'..", delPath.c_str()); if (!remove_directory(delPath)) { writeLog("Error: failed to clear path! :("); } rmdir(delFolder.c_str()); } bool update() { writeLog("Update started.."); string updDir = workDir + "tupdates/temp", readyFilePath = workDir + "tupdates/temp/ready", tdataDir = workDir + "tupdates/temp/tdata"; { FILE *readyFile = fopen(readyFilePath.c_str(), "rb"); if (readyFile) { fclose(readyFile); writeLog("Ready file found! Using new path '%s'..", updDir.c_str()); } else { updDir = workDir + "tupdates/ready"; // old tdataDir = workDir + "tupdates/ready/tdata"; writeLog("Ready file not found! Using old path '%s'..", updDir.c_str()); } } deque dirs; dirs.push_back(updDir); deque from, to, forcedirs; do { string dir = dirs.front(); dirs.pop_front(); string toDir = exePath; if (dir.size() > updDir.size() + 1) { toDir += (dir.substr(updDir.size() + 1) + '/'); forcedirs.push_back(toDir); writeLog("Parsing dir '%s' in update tree..", toDir.c_str()); } DIR *d = opendir(dir.c_str()); if (!d) { writeLog("Failed to open dir %s", dir.c_str()); return false; } while (struct dirent *p = readdir(d)) { /* Skip the names "." and ".." as we don't want to recurse on them. */ if (!strcmp(p->d_name, ".") || !strcmp(p->d_name, "..")) continue; string fname = dir + '/' + p->d_name; struct stat statbuf; if (fname.substr(0, tdataDir.size()) == tdataDir && (fname.size() <= tdataDir.size() || fname.at(tdataDir.size()) == '/')) { writeLog("Skipping 'tdata' path '%s'", fname.c_str()); } else if (!stat(fname.c_str(), &statbuf)) { if (S_ISDIR(statbuf.st_mode)) { dirs.push_back(fname); writeLog("Added dir '%s' in update tree..", fname.c_str()); } else { string tofname = exePath + fname.substr(updDir.size() + 1); if (equal(tofname, updaterName)) { // bad update - has Updater - delete all dir writeLog("Error: bad update, has Updater! '%s' equal '%s'", tofname.c_str(), updaterName.c_str()); delFolder(); return false; } else if (equal(tofname, exePath + "Telegram") && exeName != "Telegram") { string fullBinaryPath = exePath + exeName; writeLog("Target binary found: '%s', changing to '%s'", tofname.c_str(), fullBinaryPath.c_str()); tofname = fullBinaryPath; } if (fname == readyFilePath) { writeLog("Skipped ready file '%s'", fname.c_str()); } else { from.push_back(fname); to.push_back(tofname); writeLog("Added file '%s' to be copied to '%s'", fname.c_str(), tofname.c_str()); } } } else { writeLog("Could not get stat() for file %s", fname.c_str()); } } closedir(d); } while (!dirs.empty()); for (size_t i = 0; i < forcedirs.size(); ++i) { string forcedir = forcedirs[i]; writeLog("Forcing dir '%s'..", forcedir.c_str()); if (!forcedir.empty() && !mkpath(forcedir.c_str())) { writeLog("Error: failed to create dir '%s'..", forcedir.c_str()); delFolder(); return false; } } for (size_t i = 0; i < from.size(); ++i) { string fname = from[i], tofname = to[i]; writeLog("Copying file '%s' to '%s'..", fname.c_str(), tofname.c_str()); int copyTries = 0, triesLimit = 30; do { if (!copyFile(fname.c_str(), tofname.c_str())) { ++copyTries; usleep(100000); } else { break; } } while (copyTries < triesLimit); if (copyTries == triesLimit) { writeLog("Error: failed to copy, asking to retry.."); delFolder(); return false; } } writeLog("Update succeed! Clearing folder.."); delFolder(); return true; } string CurrentExecutablePath(int argc, char *argv[]) { constexpr auto kMaxPath = 1024; char result[kMaxPath] = { 0 }; auto count = readlink("/proc/self/exe", result, kMaxPath); if (count > 0) { return string(result); } // Fallback to the first command line argument. return argc ? string(argv[0]) : string(); } int main(int argc, char *argv[]) { bool needupdate = true; bool autostart = false; bool debug = false; bool tosettings = false; bool startintray = false; bool testmode = false; bool customWorkingDir = false; char *key = 0; char *workdir = 0; for (int i = 1; i < argc; ++i) { if (equal(argv[i], "-noupdate")) { needupdate = false; } else if (equal(argv[i], "-autostart")) { autostart = true; } else if (equal(argv[i], "-debug")) { debug = _debug = true; } else if (equal(argv[i], "-startintray")) { startintray = true; } else if (equal(argv[i], "-testmode")) { testmode = true; } else if (equal(argv[i], "-tosettings")) { tosettings = true; } else if (equal(argv[i], "-workdir_custom")) { customWorkingDir = true; } else if (equal(argv[i], "-key") && ++i < argc) { key = argv[i]; } else if (equal(argv[i], "-workpath") && ++i < argc) { workDir = workdir = argv[i]; } else if (equal(argv[i], "-exename") && ++i < argc) { exeName = argv[i]; } else if (equal(argv[i], "-exepath") && ++i < argc) { exePath = argv[i]; } } if (exeName.empty() || exeName.find('/') != string::npos) { exeName = "Telegram"; } openLog(); writeLog("Updater started.."); for (int i = 0; i < argc; ++i) { writeLog("Argument: '%s'", argv[i]); } if (needupdate) writeLog("Need to update!"); if (autostart) writeLog("From autostart!"); updaterName = CurrentExecutablePath(argc, argv); writeLog("Updater binary full path is: %s", updaterName.c_str()); if (exePath.empty()) { writeLog("Executable path is not specified :("); } else { writeLog("Executable path: %s", exePath.c_str()); } if (updaterName.size() >= 7) { if (equal(updaterName.substr(updaterName.size() - 7), "Updater")) { updaterDir = updaterName.substr(0, updaterName.size() - 7); writeLog("Updater binary dir is: %s", updaterDir.c_str()); if (exePath.empty()) { exePath = updaterDir; writeLog("Using updater binary dir.", exePath.c_str()); } if (needupdate) { if (workDir.empty()) { // old app launched, update prepared in tupdates/ready (not in tupdates/temp) customWorkingDir = false; writeLog("No workdir, trying to figure it out"); struct passwd *pw = getpwuid(getuid()); if (pw && pw->pw_dir && strlen(pw->pw_dir)) { string tryDir = pw->pw_dir + string("/.TelegramDesktop/"); struct stat statbuf; writeLog("Trying to use '%s' as workDir, getting stat() for tupdates/ready", tryDir.c_str()); if (!stat((tryDir + "tupdates/ready").c_str(), &statbuf)) { writeLog("Stat got"); if (S_ISDIR(statbuf.st_mode)) { writeLog("It is directory, using home work dir"); workDir = tryDir; } } } if (workDir.empty()) { workDir = exePath; struct stat statbuf; writeLog("Trying to use current as workDir, getting stat() for tupdates/ready"); if (!stat("tupdates/ready", &statbuf)) { writeLog("Stat got"); if (S_ISDIR(statbuf.st_mode)) { writeLog("It is directory, using current dir"); workDir = string(); } } } } else { writeLog("Passed workpath is '%s'", workDir.c_str()); } update(); } } else { writeLog("Error: bad exe name!"); } } else { writeLog("Error: short exe name!"); } static const int MaxLen = 65536, MaxArgsCount = 128; char path[MaxLen] = {0}; string fullBinaryPath = exePath + exeName; strcpy(path, fullBinaryPath.c_str()); char *args[MaxArgsCount] = { 0 }; char p_noupdate[] = "-noupdate"; char p_autostart[] = "-autostart"; char p_debug[] = "-debug"; char p_tosettings[] = "-tosettings"; char p_key[] = "-key"; char p_startintray[] = "-startintray"; char p_testmode[] = "-testmode"; char p_workdir[] = "-workdir"; int argIndex = 0; args[argIndex++] = path; args[argIndex++] = p_noupdate; if (autostart) args[argIndex++] = p_autostart; if (debug) args[argIndex++] = p_debug; if (startintray) args[argIndex++] = p_startintray; if (testmode) args[argIndex++] = p_testmode; if (tosettings) args[argIndex++] = p_tosettings; if (key) { args[argIndex++] = p_key; args[argIndex++] = key; } if (customWorkingDir && workdir) { args[argIndex++] = p_workdir; args[argIndex++] = workdir; } pid_t pid = fork(); switch (pid) { case -1: writeLog("fork() failed!"); return 1; case 0: execv(path, args); return 1; } writeLog("Executed Telegram, closing log and quitting.."); closeLog(); return 0; } tdesktop-1.2.17/Telegram/SourceFiles/_other/updater_osx.m000066400000000000000000000263541326245125100234300ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #import NSString *appName = @"Telegram.app"; NSString *appDir = nil; NSString *workDir = nil; #ifdef _DEBUG BOOL _debug = YES; #else BOOL _debug = NO; #endif NSFileHandle *_logFile = nil; void openLog() { if (!_debug || _logFile) return; NSString *logDir = [workDir stringByAppendingString:@"DebugLogs"]; if (![[NSFileManager defaultManager] createDirectoryAtPath:logDir withIntermediateDirectories:YES attributes:nil error:nil]) { return; } NSDateFormatter *fmt = [[NSDateFormatter alloc] initWithDateFormat:@"DebugLogs/%Y%m%d_%H%M%S_upd.txt" allowNaturalLanguage:NO]; NSString *logPath = [workDir stringByAppendingString:[fmt stringFromDate:[NSDate date]]]; [[NSFileManager defaultManager] createFileAtPath:logPath contents:nil attributes:nil]; _logFile = [NSFileHandle fileHandleForWritingAtPath:logPath]; } void closeLog() { if (!_logFile) return; [_logFile closeFile]; } void writeLog(NSString *msg) { if (!_logFile) return; [_logFile writeData:[[msg stringByAppendingString:@"\n"] dataUsingEncoding:NSUTF8StringEncoding]]; [_logFile synchronizeFile]; } void delFolder() { writeLog([@"Fully clearing old path: " stringByAppendingString:[workDir stringByAppendingString:@"tupdates/ready"]]); if (![[NSFileManager defaultManager] removeItemAtPath:[workDir stringByAppendingString:@"tupdates/ready"] error:nil]) { writeLog(@"Failed to clear old path! :( New path was used?.."); } writeLog([@"Fully clearing new path: " stringByAppendingString:[workDir stringByAppendingString:@"tupdates/temp"]]); if (![[NSFileManager defaultManager] removeItemAtPath:[workDir stringByAppendingString:@"tupdates/temp"] error:nil]) { writeLog(@"Error: failed to clear new path! :("); } rmdir([[workDir stringByAppendingString:@"tupdates"] fileSystemRepresentation]); } int main(int argc, const char * argv[]) { NSString *path = [[NSBundle mainBundle] bundlePath]; if (!path) { return -1; } NSRange range = [path rangeOfString:@".app/" options:NSBackwardsSearch]; if (range.location == NSNotFound) { return -1; } path = [path substringToIndex:range.location > 0 ? range.location : 0]; range = [path rangeOfString:@"/" options:NSBackwardsSearch]; NSString *appRealName = (range.location == NSNotFound) ? path : [path substringFromIndex:range.location + 1]; appRealName = [[NSArray arrayWithObjects:appRealName, @".app", nil] componentsJoinedByString:@""]; appDir = (range.location == NSNotFound) ? @"" : [path substringToIndex:range.location + 1]; NSString *appDirFull = [appDir stringByAppendingString:appRealName]; openLog(); pid_t procId = 0; BOOL update = YES, toSettings = NO, autoStart = NO, startInTray = NO, testMode = NO; BOOL customWorkingDir = NO; NSString *key = nil; for (int i = 0; i < argc; ++i) { if ([@"-workpath" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) { if (++i < argc) { workDir = [NSString stringWithUTF8String:argv[i]]; } } else if ([@"-procid" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) { if (++i < argc) { NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init]; [formatter setNumberStyle:NSNumberFormatterDecimalStyle]; procId = [[formatter numberFromString:[NSString stringWithUTF8String:argv[i]]] intValue]; } } else if ([@"-noupdate" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) { update = NO; } else if ([@"-tosettings" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) { toSettings = YES; } else if ([@"-autostart" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) { autoStart = YES; } else if ([@"-debug" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) { _debug = YES; } else if ([@"-startintray" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) { startInTray = YES; } else if ([@"-testmode" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) { testMode = YES; } else if ([@"-workdir_custom" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) { customWorkingDir = YES; } else if ([@"-key" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) { if (++i < argc) key = [NSString stringWithUTF8String:argv[i]]; } } if (!workDir) { workDir = appDir; customWorkingDir = NO; } openLog(); NSMutableArray *argsArr = [[NSMutableArray alloc] initWithCapacity:argc]; for (int i = 0; i < argc; ++i) { [argsArr addObject:[NSString stringWithUTF8String:argv[i]]]; } writeLog([[NSArray arrayWithObjects:@"Arguments: '", [argsArr componentsJoinedByString:@"' '"], @"'..", nil] componentsJoinedByString:@""]); if (key) writeLog([@"Key: " stringByAppendingString:key]); if (toSettings) writeLog(@"To Settings!"); if (procId) { NSRunningApplication *app = [NSRunningApplication runningApplicationWithProcessIdentifier:procId]; for (int i = 0; i < 5 && app != nil && ![app isTerminated]; ++i) { usleep(200000); app = [NSRunningApplication runningApplicationWithProcessIdentifier:procId]; } if (app) [app forceTerminate]; app = [NSRunningApplication runningApplicationWithProcessIdentifier:procId]; for (int i = 0; i < 5 && app != nil && ![app isTerminated]; ++i) { usleep(200000); app = [NSRunningApplication runningApplicationWithProcessIdentifier:procId]; } } if (update) { NSFileManager *fileManager = [NSFileManager defaultManager]; NSString *readyFilePath = [workDir stringByAppendingString:@"tupdates/temp/ready"]; NSString *srcDir = [workDir stringByAppendingString:@"tupdates/temp/"], *srcEnum = [workDir stringByAppendingString:@"tupdates/temp"]; if ([fileManager fileExistsAtPath:readyFilePath]) { writeLog([@"Ready file found! Using new path: " stringByAppendingString: srcEnum]); } else { srcDir = [workDir stringByAppendingString:@"tupdates/ready/"]; // old srcEnum = [workDir stringByAppendingString:@"tupdates/ready"]; writeLog([@"Ready file not found! Using old path: " stringByAppendingString: srcEnum]); } writeLog([@"Starting update files iteration, path: " stringByAppendingString: srcEnum]); // Take the Updater (this currently running binary) from the place where it was placed by Telegram // and copy it to the folder with the new version of the app (ready), // so it won't be deleted when we will clear the "Telegram.app/Contents" folder. NSString *oldVersionUpdaterPath = [appDirFull stringByAppendingString: @"/Contents/Frameworks/Updater" ]; NSString *newVersionUpdaterPath = [srcEnum stringByAppendingString:[[NSArray arrayWithObjects:@"/", appName, @"/Contents/Frameworks/Updater", nil] componentsJoinedByString:@""]]; writeLog([[NSArray arrayWithObjects: @"Copying Updater from old path ", oldVersionUpdaterPath, @" to new path ", newVersionUpdaterPath, nil] componentsJoinedByString:@""]); if (![fileManager fileExistsAtPath:newVersionUpdaterPath]) { if (![fileManager copyItemAtPath:oldVersionUpdaterPath toPath:newVersionUpdaterPath error:nil]) { writeLog([[NSArray arrayWithObjects: @"Failed to copy file from ", oldVersionUpdaterPath, @" to ", newVersionUpdaterPath, nil] componentsJoinedByString:@""]); delFolder(); return -1; } } NSString *contentsPath = [appDirFull stringByAppendingString: @"/Contents"]; writeLog([[NSArray arrayWithObjects: @"Clearing dir ", contentsPath, nil] componentsJoinedByString:@""]); if (![fileManager removeItemAtPath:contentsPath error:nil]) { writeLog([@"Failed to clear path for directory " stringByAppendingString:contentsPath]); delFolder(); return -1; } NSArray *keys = [NSArray arrayWithObject:NSURLIsDirectoryKey]; NSDirectoryEnumerator *enumerator = [fileManager enumeratorAtURL:[NSURL fileURLWithPath:srcEnum] includingPropertiesForKeys:keys options:0 errorHandler:^(NSURL *url, NSError *error) { writeLog([[[@"Error in enumerating " stringByAppendingString:[url absoluteString]] stringByAppendingString: @" error is: "] stringByAppendingString: [error description]]); return NO; }]; for (NSURL *url in enumerator) { NSString *srcPath = [url path]; writeLog([@"Handling file " stringByAppendingString:srcPath]); NSRange r = [srcPath rangeOfString:srcDir]; if (r.location != 0) { writeLog([@"Bad file found, no base path " stringByAppendingString:srcPath]); delFolder(); break; } NSString *pathPart = [srcPath substringFromIndex:r.length]; r = [pathPart rangeOfString:appName]; if (r.location != 0) { writeLog([@"Skipping not app file " stringByAppendingString:srcPath]); continue; } NSString *dstPath = [appDirFull stringByAppendingString:[pathPart substringFromIndex:r.length]]; NSError *error; NSNumber *isDirectory = nil; if (![url getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:&error]) { writeLog([@"Failed to get IsDirectory for file " stringByAppendingString:[url path]]); delFolder(); break; } if ([isDirectory boolValue]) { writeLog([[NSArray arrayWithObjects: @"Copying dir ", srcPath, @" to ", dstPath, nil] componentsJoinedByString:@""]); if (![fileManager createDirectoryAtPath:dstPath withIntermediateDirectories:YES attributes:nil error:nil]) { writeLog([@"Failed to force path for directory " stringByAppendingString:dstPath]); delFolder(); break; } } else if ([srcPath isEqualToString:readyFilePath]) { writeLog([[NSArray arrayWithObjects: @"Skipping ready file ", srcPath, nil] componentsJoinedByString:@""]); } else if ([fileManager fileExistsAtPath:dstPath]) { if (![[NSData dataWithContentsOfFile:srcPath] writeToFile:dstPath atomically:YES]) { writeLog([@"Failed to edit file " stringByAppendingString:dstPath]); delFolder(); break; } } else { if (![fileManager copyItemAtPath:srcPath toPath:dstPath error:nil]) { writeLog([@"Failed to copy file to " stringByAppendingString:dstPath]); delFolder(); break; } } } delFolder(); } NSString *appPath = [[NSArray arrayWithObjects:appDir, appRealName, nil] componentsJoinedByString:@""]; NSMutableArray *args = [[NSMutableArray alloc] initWithObjects: @"-noupdate", nil]; if (toSettings) [args addObject:@"-tosettings"]; if (_debug) [args addObject:@"-debug"]; if (startInTray) [args addObject:@"-startintray"]; if (testMode) [args addObject:@"-testmode"]; if (autoStart) [args addObject:@"-autostart"]; if (key) { [args addObject:@"-key"]; [args addObject:key]; } if (customWorkingDir) { [args addObject:@"-workdir"]; [args addObject:workDir]; } writeLog([[NSArray arrayWithObjects:@"Running application '", appPath, @"' with args '", [args componentsJoinedByString:@"' '"], @"'..", nil] componentsJoinedByString:@""]); NSError *error = nil; NSRunningApplication *result = [[NSWorkspace sharedWorkspace] launchApplicationAtURL:[NSURL fileURLWithPath:appPath] options:NSWorkspaceLaunchDefault configuration:[NSDictionary dictionaryWithObject:args forKey:NSWorkspaceLaunchConfigurationArguments] error:&error]; if (!result) { writeLog([@"Could not run application, error: " stringByAppendingString:error ? [error localizedDescription] : @"(nil)"]); } closeLog(); return result ? 0 : -1; } tdesktop-1.2.17/Telegram/SourceFiles/apiwrap.cpp000066400000000000000000003720061326245125100216020ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "apiwrap.h" #include "data/data_drafts.h" #include "data/data_photo.h" #include "data/data_web_page.h" #include "data/data_feed.h" #include "data/data_media_types.h" #include "data/data_sparse_ids.h" #include "data/data_search_controller.h" #include "data/data_channel_admins.h" #include "data/data_session.h" #include "dialogs/dialogs_key.h" #include "core/tl_help.h" #include "base/overload.h" #include "observer_peer.h" #include "lang/lang_keys.h" #include "application.h" #include "mainwindow.h" #include "messenger.h" #include "mainwidget.h" #include "boxes/add_contact_box.h" #include "history/history.h" #include "history/history_message.h" #include "history/history_media_types.h" #include "history/history_item_components.h" #include "history/feed/history_feed_section.h" #include "storage/localstorage.h" #include "auth_session.h" #include "boxes/confirm_box.h" #include "window/notifications_manager.h" #include "window/window_controller.h" #include "chat_helpers/message_field.h" #include "chat_helpers/stickers.h" #include "storage/localimageloader.h" #include "storage/storage_facade.h" #include "storage/storage_shared_media.h" #include "storage/storage_user_photos.h" #include "storage/storage_media_prepare.h" #include "storage/storage_feed_messages.h" namespace { constexpr auto kReloadChannelMembersTimeout = 1000; // 1 second wait before reload members in channel after adding constexpr auto kSaveCloudDraftTimeout = 1000; // save draft to the cloud with 1 sec extra delay constexpr auto kSaveDraftBeforeQuitTimeout = 1500; // give the app 1.5 secs to save drafts to cloud when quitting constexpr auto kSmallDelayMs = 5; constexpr auto kUnreadMentionsPreloadIfLess = 5; constexpr auto kUnreadMentionsFirstRequestLimit = 10; constexpr auto kUnreadMentionsNextRequestLimit = 100; constexpr auto kSharedMediaLimit = 100; constexpr auto kFeedMessagesLimit = 50; constexpr auto kReadFeaturedSetsTimeout = TimeMs(1000); constexpr auto kFileLoaderQueueStopTimeout = TimeMs(5000); constexpr auto kFeedReadTimeout = TimeMs(1000); constexpr auto kStickersByEmojiInvalidateTimeout = TimeMs(60 * 60 * 1000); bool IsSilentPost(not_null item, bool silent) { const auto history = item->history(); return silent && history->peer->isChannel() && !history->peer->isMegagroup(); } MTPVector ComposeSendingDocumentAttributes( not_null document) { const auto filenameAttribute = MTP_documentAttributeFilename( MTP_string(document->filename())); const auto dimensions = document->dimensions; auto attributes = QVector(1, filenameAttribute); if (dimensions.width() > 0 && dimensions.height() > 0) { const auto duration = document->duration(); if (duration >= 0) { auto flags = MTPDdocumentAttributeVideo::Flags(0); if (document->isVideoMessage()) { flags |= MTPDdocumentAttributeVideo::Flag::f_round_message; } attributes.push_back(MTP_documentAttributeVideo( MTP_flags(flags), MTP_int(duration), MTP_int(dimensions.width()), MTP_int(dimensions.height()))); } else { attributes.push_back(MTP_documentAttributeImageSize( MTP_int(dimensions.width()), MTP_int(dimensions.height()))); } } if (document->type == AnimatedDocument) { attributes.push_back(MTP_documentAttributeAnimated()); } else if (document->type == StickerDocument && document->sticker()) { attributes.push_back(MTP_documentAttributeSticker( MTP_flags(0), MTP_string(document->sticker()->alt), document->sticker()->set, MTPMaskCoords())); } else if (const auto song = document->song()) { const auto flags = MTPDdocumentAttributeAudio::Flag::f_title | MTPDdocumentAttributeAudio::Flag::f_performer; attributes.push_back(MTP_documentAttributeAudio( MTP_flags(flags), MTP_int(song->duration), MTP_string(song->title), MTP_string(song->performer), MTPstring())); } else if (const auto voice = document->voice()) { const auto flags = MTPDdocumentAttributeAudio::Flag::f_voice | MTPDdocumentAttributeAudio::Flag::f_waveform; attributes.push_back(MTP_documentAttributeAudio( MTP_flags(flags), MTP_int(voice->duration), MTPstring(), MTPstring(), MTP_bytes(documentWaveformEncode5bit(voice->waveform)))); } return MTP_vector(attributes); } FileLoadTo FileLoadTaskOptions(const ApiWrap::SendOptions &options) { const auto peer = options.history->peer; return FileLoadTo( peer->id, peer->notifySilentPosts(), options.replyTo); } } // namespace ApiWrap::ApiWrap(not_null session) : _session(session) , _messageDataResolveDelayed([this] { resolveMessageDatas(); }) , _webPagesTimer([this] { resolveWebPages(); }) , _draftsSaveTimer([this] { saveDraftsToCloud(); }) , _featuredSetsReadTimer([this] { readFeaturedSets(); }) , _fileLoader(std::make_unique(kFileLoaderQueueStopTimeout)) , _feedReadTimer([this] { readFeeds(); }) { } void ApiWrap::requestChangelog( const QString &sinceVersion, base::lambda callback) { request(MTPhelp_GetAppChangelog( MTP_string(sinceVersion) )).done( callback ).send(); } void ApiWrap::applyUpdates( const MTPUpdates &updates, uint64 sentMessageRandomId) { App::main()->feedUpdates(updates, sentMessageRandomId); } void ApiWrap::savePinnedOrder() { const auto &order = _session->data().pinnedDialogsOrder(); auto peers = QVector(); peers.reserve(order.size()); for (const auto pinned : base::reversed(order)) { if (const auto history = pinned.history()) { peers.push_back(MTP_inputDialogPeer(history->peer->input)); } else if (const auto feed = pinned.feed()) { // peers.push_back(MTP_inputDialogPeerFeed(MTP_int(feed->id()))); // #feed } } auto flags = MTPmessages_ReorderPinnedDialogs::Flag::f_force; request(MTPmessages_ReorderPinnedDialogs( MTP_flags(flags), MTP_vector(peers) )).send(); } // #feed //void ApiWrap::toggleChannelGrouping( // not_null channel, // bool group, // base::lambda callback) { // if (const auto already = _channelGroupingRequests.take(channel)) { // request(already->first).cancel(); // } // const auto feedId = Data::Feed::kId; // const auto flags = group // ? MTPchannels_ChangeFeedBroadcast::Flag::f_feed_id // : MTPchannels_ChangeFeedBroadcast::Flag(0); // const auto requestId = request(MTPchannels_ChangeFeedBroadcast( // MTP_flags(flags), // channel->inputChannel, // MTP_int(feedId) // )).done([=](const MTPUpdates &result) { // applyUpdates(result); // if (group) { // channel->setFeed(Auth().data().feed(feedId)); // } else { // channel->clearFeed(); // } // if (const auto data = _channelGroupingRequests.take(channel)) { // data->second(); // } // }).fail([=](const RPCError &error) { // _channelGroupingRequests.remove(channel); // }).send(); // _channelGroupingRequests.emplace(channel, requestId, callback); //} // //void ApiWrap::ungroupAllFromFeed(not_null feed) { // const auto flags = MTPchannels_SetFeedBroadcasts::Flag::f_channels // | MTPchannels_SetFeedBroadcasts::Flag::f_also_newly_joined; // request(MTPchannels_SetFeedBroadcasts( // MTP_flags(flags), // MTP_int(feed->id()), // MTP_vector(0), // MTP_bool(false) // )).done([=](const MTPUpdates &result) { // applyUpdates(result); // }).send(); //} void ApiWrap::sendMessageFail(const RPCError &error) { if (error.type() == qstr("PEER_FLOOD")) { Ui::show(Box( PeerFloodErrorText(PeerFloodType::Send))); } else if (error.type() == qstr("USER_BANNED_IN_CHANNEL")) { const auto link = textcmdLink( Messenger::Instance().createInternalLinkFull(qsl("spambot")), lang(lng_cant_more_info)); Ui::show(Box(lng_error_public_groups_denied( lt_more_info, link))); } } void ApiWrap::requestMessageData(ChannelData *channel, MsgId msgId, RequestMessageDataCallback callback) { auto &req = (channel ? _channelMessageDataRequests[channel][msgId] : _messageDataRequests[msgId]); if (callback) { req.callbacks.append(callback); } if (!req.requestId) _messageDataResolveDelayed.call(); } QVector ApiWrap::collectMessageIds(const MessageDataRequests &requests) { auto result = QVector(); result.reserve(requests.size()); for (auto i = requests.cbegin(), e = requests.cend(); i != e; ++i) { if (i.value().requestId > 0) continue; result.push_back(MTP_inputMessageID(MTP_int(i.key()))); } return result; } ApiWrap::MessageDataRequests *ApiWrap::messageDataRequests(ChannelData *channel, bool onlyExisting) { if (channel) { auto i = _channelMessageDataRequests.find(channel); if (i == _channelMessageDataRequests.cend()) { if (onlyExisting) return 0; i = _channelMessageDataRequests.insert(channel, MessageDataRequests()); } return &i.value(); } return &_messageDataRequests; } void ApiWrap::resolveMessageDatas() { if (_messageDataRequests.isEmpty() && _channelMessageDataRequests.isEmpty()) return; auto ids = collectMessageIds(_messageDataRequests); if (!ids.isEmpty()) { auto requestId = request(MTPmessages_GetMessages( MTP_vector(ids) )).done([this](const MTPmessages_Messages &result, mtpRequestId requestId) { gotMessageDatas(nullptr, result, requestId); }).fail([this](const RPCError &error, mtpRequestId requestId) { finalizeMessageDataRequest(nullptr, requestId); }).afterDelay(kSmallDelayMs).send(); for (auto &request : _messageDataRequests) { if (request.requestId > 0) continue; request.requestId = requestId; } } for (auto j = _channelMessageDataRequests.begin(); j != _channelMessageDataRequests.cend();) { if (j->isEmpty()) { j = _channelMessageDataRequests.erase(j); continue; } auto ids = collectMessageIds(j.value()); if (!ids.isEmpty()) { auto channel = j.key(); auto requestId = request(MTPchannels_GetMessages( j.key()->inputChannel, MTP_vector(ids) )).done([=](const MTPmessages_Messages &result, mtpRequestId requestId) { gotMessageDatas(channel, result, requestId); }).fail([=](const RPCError &error, mtpRequestId requestId) { finalizeMessageDataRequest(channel, requestId); }).afterDelay(kSmallDelayMs).send(); for (auto &request : *j) { if (request.requestId > 0) continue; request.requestId = requestId; } } ++j; } } void ApiWrap::gotMessageDatas(ChannelData *channel, const MTPmessages_Messages &msgs, mtpRequestId requestId) { auto handleResult = [&](auto &&result) { App::feedUsers(result.vusers); App::feedChats(result.vchats); App::feedMsgs(result.vmessages, NewMessageExisting); }; switch (msgs.type()) { case mtpc_messages_messages: handleResult(msgs.c_messages_messages()); break; case mtpc_messages_messagesSlice: handleResult(msgs.c_messages_messagesSlice()); break; case mtpc_messages_channelMessages: { auto &d = msgs.c_messages_channelMessages(); if (channel) { channel->ptsReceived(d.vpts.v); } else { LOG(("App Error: received messages.channelMessages when no channel was passed! (ApiWrap::gotDependencyItem)")); } handleResult(d); } break; case mtpc_messages_messagesNotModified: LOG(("API Error: received messages.messagesNotModified! (ApiWrap::gotDependencyItem)")); break; } finalizeMessageDataRequest(channel, requestId); } void ApiWrap::finalizeMessageDataRequest( ChannelData *channel, mtpRequestId requestId) { auto requests = messageDataRequests(channel, true); if (requests) { for (auto i = requests->begin(); i != requests->cend();) { if (i.value().requestId == requestId) { for_const (auto &callback, i.value().callbacks) { callback(channel, i.key()); } i = requests->erase(i); } else { ++i; } } if (channel && requests->isEmpty()) { _channelMessageDataRequests.remove(channel); } } } void ApiWrap::requestContacts() { if (_session->data().contactsLoaded().value() || _contactsRequestId) { return; } _contactsRequestId = request(MTPcontacts_GetContacts( MTP_int(0) )).done([=](const MTPcontacts_Contacts &result) { _contactsRequestId = 0; if (result.type() == mtpc_contacts_contactsNotModified) { return; } Assert(result.type() == mtpc_contacts_contacts); const auto &d = result.c_contacts_contacts(); App::feedUsers(d.vusers); for (const auto &contact : d.vcontacts.v) { if (contact.type() != mtpc_contact) continue; const auto userId = contact.c_contact().vuser_id.v; if (userId == _session->userId() && App::self()) { App::self()->setContactStatus( UserData::ContactStatus::Contact); } } _session->data().contactsLoaded().set(true); }).fail([=](const RPCError &error) { _contactsRequestId = 0; }).send(); } void ApiWrap::requestDialogEntry(not_null feed) { if (_dialogFeedRequests.contains(feed)) { return; } _dialogFeedRequests.emplace(feed); //auto peers = QVector( // #feed // 1, // MTP_inputDialogPeerFeed(MTP_int(feed->id()))); //request(MTPmessages_GetPeerDialogs( // MTP_vector(std::move(peers)) //)).done([=](const MTPmessages_PeerDialogs &result) { // applyPeerDialogs(result); // _dialogFeedRequests.remove(feed); //}).fail([=](const RPCError &error) { // _dialogFeedRequests.remove(feed); //}).send(); } //void ApiWrap::requestFeedDialogsEntries(not_null feed) { // if (_dialogFeedRequests.contains(feed)) { // return; // } // _dialogFeedRequests.emplace(feed); // // request(MTPmessages_GetDialogs( // MTP_flags(MTPmessages_GetDialogs::Flag::f_feed_id), // MTP_int(feed->id()), // MTP_int(0), // offset_date // MTP_int(0), // offset_id // MTP_inputPeerEmpty(), // offset_peer // MTP_int(Data::Feed::kChannelsLimit) // )).done([=](const MTPmessages_Dialogs &result) { // applyFeedDialogs(feed, result); // _dialogFeedRequests.remove(feed); // }).fail([=](const RPCError &error) { // _dialogFeedRequests.remove(feed); // }).send(); //} void ApiWrap::requestDialogEntry(not_null history) { if (_dialogRequests.contains(history)) { return; } _dialogRequests.emplace(history); auto peers = QVector( 1, MTP_inputDialogPeer(history->peer->input)); request(MTPmessages_GetPeerDialogs( MTP_vector(std::move(peers)) )).done([=](const MTPmessages_PeerDialogs &result) { applyPeerDialogs(result); historyDialogEntryApplied(history); _dialogRequests.remove(history); }).fail([=](const RPCError &error) { _dialogRequests.remove(history); }).send(); } void ApiWrap::applyPeerDialogs(const MTPmessages_PeerDialogs &dialogs) { Expects(dialogs.type() == mtpc_messages_peerDialogs); const auto &data = dialogs.c_messages_peerDialogs(); App::feedUsers(data.vusers); App::feedChats(data.vchats); App::feedMsgs(data.vmessages, NewMessageLast); for (const auto &dialog : data.vdialogs.v) { switch (dialog.type()) { case mtpc_dialog: { const auto &fields = dialog.c_dialog(); if (const auto peerId = peerFromMTP(fields.vpeer)) { App::history(peerId)->applyDialog(fields); } } break; //case mtpc_dialogFeed: { // #feed // const auto &fields = dialog.c_dialogFeed(); // const auto feed = Auth().data().feed(fields.vfeed_id.v); // feed->applyDialog(fields); //} break; } } _session->data().sendHistoryChangeNotifications(); } void ApiWrap::historyDialogEntryApplied(not_null history) { if (!history->lastMessage()) { if (const auto chat = history->peer->asChat()) { if (!chat->haveLeft()) { Local::addSavedPeer( history->peer, history->chatsListDate()); } } else if (const auto channel = history->peer->asChannel()) { const auto inviter = channel->inviter; if (inviter != 0 && channel->amIn()) { if (const auto from = App::userLoaded(inviter)) { history->unloadBlocks(); history->addNewerSlice(QVector()); history->insertJoinedMessage(true); } } } else { App::main()->deleteConversation(history->peer, false); } return; } if (!history->chatsListDate().isNull() && history->loadedAtBottom()) { if (const auto channel = history->peer->asChannel()) { const auto inviter = channel->inviter; if (inviter != 0 && history->chatsListDate() <= ParseDateTime(channel->inviteDate) && channel->amIn()) { if (const auto from = App::userLoaded(inviter)) { history->insertJoinedMessage(true); } } } } history->updateChatListExistence(); } void ApiWrap::applyFeedDialogs( not_null feed, const MTPmessages_Dialogs &dialogs) { const auto [dialogsList, messagesList] = [&] { const auto process = [&](const auto &data) { App::feedUsers(data.vusers); App::feedChats(data.vchats); return std::make_tuple(&data.vdialogs.v, &data.vmessages.v); }; switch (dialogs.type()) { case mtpc_messages_dialogs: return process(dialogs.c_messages_dialogs()); case mtpc_messages_dialogsSlice: LOG(("API Error: " "Unexpected dialogsSlice in feed dialogs list.")); return process(dialogs.c_messages_dialogsSlice()); } Unexpected("Type in DialogsWidget::dialogsReceived"); }(); App::feedMsgs(*messagesList, NewMessageLast); auto channels = std::vector>(); channels.reserve(dialogsList->size()); for (const auto &dialog : *dialogsList) { switch (dialog.type()) { case mtpc_dialog: { if (const auto peerId = peerFromMTP(dialog.c_dialog().vpeer)) { if (peerIsChannel(peerId)) { const auto history = App::history(peerId); history->applyDialog(dialog.c_dialog()); channels.push_back(history->peer->asChannel()); } else { LOG(("API Error: " "Unexpected non-channel in feed dialogs list.")); } } } break; //case mtpc_dialogFeed: { // #feed // LOG(("API Error: Unexpected dialogFeed in feed dialogs list.")); //} break; default: Unexpected("Type in DialogsInner::dialogsReceived"); } } feed->setChannels(channels); _session->data().sendHistoryChangeNotifications(); } void ApiWrap::requestFullPeer(PeerData *peer) { if (!peer || _fullPeerRequests.contains(peer)) return; auto sendRequest = [this, peer] { auto failHandler = [this, peer](const RPCError &error) { _fullPeerRequests.remove(peer); }; if (auto user = peer->asUser()) { return request(MTPusers_GetFullUser(user->inputUser)).done([this, user](const MTPUserFull &result, mtpRequestId requestId) { gotUserFull(user, result, requestId); }).fail(failHandler).send(); } else if (auto chat = peer->asChat()) { return request(MTPmessages_GetFullChat(chat->inputChat)).done([this, peer](const MTPmessages_ChatFull &result, mtpRequestId requestId) { gotChatFull(peer, result, requestId); }).fail(failHandler).send(); } else if (auto channel = peer->asChannel()) { return request(MTPchannels_GetFullChannel(channel->inputChannel)).done([this, peer](const MTPmessages_ChatFull &result, mtpRequestId requestId) { gotChatFull(peer, result, requestId); }).fail(failHandler).send(); } return 0; }; if (auto requestId = sendRequest()) { _fullPeerRequests.insert(peer, requestId); } } void ApiWrap::processFullPeer(PeerData *peer, const MTPmessages_ChatFull &result) { gotChatFull(peer, result, mtpRequestId(0)); } void ApiWrap::processFullPeer(UserData *user, const MTPUserFull &result) { gotUserFull(user, result, mtpRequestId(0)); } void ApiWrap::gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result, mtpRequestId req) { auto &d = result.c_messages_chatFull(); auto &vc = d.vchats.v; auto badVersion = false; if (const auto chat = peer->asChat()) { badVersion = !vc.isEmpty() && (vc[0].type() == mtpc_chat) && (vc[0].c_chat().vversion.v < chat->version); } else if (const auto channel = peer->asChannel()) { badVersion = !vc.isEmpty() && (vc[0].type() == mtpc_channel) && (vc[0].c_channel().vversion.v < channel->version); } App::feedUsers(d.vusers); App::feedChats(d.vchats); using UpdateFlag = Notify::PeerUpdate::Flag; if (auto chat = peer->asChat()) { if (d.vfull_chat.type() != mtpc_chatFull) { LOG(("MTP Error: bad type in gotChatFull for chat: %1").arg(d.vfull_chat.type())); return; } auto &f = d.vfull_chat.c_chatFull(); App::feedParticipants(f.vparticipants, false); auto &v = f.vbot_info.v; for_const (auto &item, v) { switch (item.type()) { case mtpc_botInfo: { auto &b = item.c_botInfo(); if (auto user = App::userLoaded(b.vuser_id.v)) { user->setBotInfo(item); fullPeerUpdated().notify(user); } } break; } } chat->setUserpicPhoto(f.vchat_photo); chat->setInviteLink((f.vexported_invite.type() == mtpc_chatInviteExported) ? qs(f.vexported_invite.c_chatInviteExported().vlink) : QString()); chat->fullUpdated(); notifySettingReceived(MTP_inputNotifyPeer(peer->input), f.vnotify_settings); } else if (auto channel = peer->asChannel()) { if (d.vfull_chat.type() != mtpc_channelFull) { LOG(("MTP Error: bad type in gotChatFull for channel: %1").arg(d.vfull_chat.type())); return; } auto &f = d.vfull_chat.c_channelFull(); channel->setAvailableMinId(f.vavailable_min_id.v); auto canViewAdmins = channel->canViewAdmins(); auto canViewMembers = channel->canViewMembers(); auto canEditStickers = channel->canEditStickers(); channel->setFullFlags(f.vflags.v); channel->setUserpicPhoto(f.vchat_photo); if (f.has_migrated_from_chat_id()) { channel->addFlags(MTPDchannel::Flag::f_megagroup); auto cfrom = App::chat(peerFromChat(f.vmigrated_from_chat_id)); bool updatedTo = (cfrom->migrateToPtr != channel), updatedFrom = (channel->mgInfo->migrateFromPtr != cfrom); if (updatedTo) { cfrom->migrateToPtr = channel; } if (updatedFrom) { channel->mgInfo->migrateFromPtr = cfrom; if (auto h = App::historyLoaded(cfrom->id)) { if (auto hto = App::historyLoaded(channel->id)) { if (!h->isEmpty()) { h->unloadBlocks(); } if (hto->inChatList(Dialogs::Mode::All) && h->inChatList(Dialogs::Mode::All)) { App::main()->removeDialog(h); } } } Notify::migrateUpdated(channel); } if (updatedTo) { Notify::migrateUpdated(cfrom); } } auto &v = f.vbot_info.v; for_const (auto &item, v) { switch (item.type()) { case mtpc_botInfo: { auto &b = item.c_botInfo(); if (auto user = App::userLoaded(b.vuser_id.v)) { user->setBotInfo(item); fullPeerUpdated().notify(user); } } break; } } channel->setAbout(qs(f.vabout)); channel->setMembersCount(f.has_participants_count() ? f.vparticipants_count.v : 0); channel->setAdminsCount(f.has_admins_count() ? f.vadmins_count.v : 0); channel->setRestrictedCount(f.has_banned_count() ? f.vbanned_count.v : 0); channel->setKickedCount(f.has_kicked_count() ? f.vkicked_count.v : 0); channel->setInviteLink((f.vexported_invite.type() == mtpc_chatInviteExported) ? qs(f.vexported_invite.c_chatInviteExported().vlink) : QString()); if (const auto history = App::historyLoaded(channel->id)) { history->applyDialogFields( f.vunread_count.v, f.vread_inbox_max_id.v, f.vread_outbox_max_id.v); } if (f.has_pinned_msg_id()) { channel->setPinnedMessageId(f.vpinned_msg_id.v); } else { channel->clearPinnedMessage(); } if (channel->isMegagroup()) { auto stickersChanged = (canEditStickers != channel->canEditStickers()); auto stickerSet = (f.has_stickerset() ? &f.vstickerset.c_stickerSet() : nullptr); auto newSetId = (stickerSet ? stickerSet->vid.v : 0); auto oldSetId = (channel->mgInfo->stickerSet.type() == mtpc_inputStickerSetID) ? channel->mgInfo->stickerSet.c_inputStickerSetID().vid.v : 0; if (oldSetId != newSetId) { channel->mgInfo->stickerSet = stickerSet ? MTP_inputStickerSetID(stickerSet->vid, stickerSet->vaccess_hash) : MTP_inputStickerSetEmpty(); stickersChanged = true; } if (stickersChanged) { Notify::peerUpdatedDelayed(channel, UpdateFlag::ChannelStickersChanged); } } channel->fullUpdated(); if (canViewAdmins != channel->canViewAdmins() || canViewMembers != channel->canViewMembers()) { Notify::peerUpdatedDelayed(channel, UpdateFlag::ChannelRightsChanged); } notifySettingReceived(MTP_inputNotifyPeer(peer->input), f.vnotify_settings); } if (req) { auto i = _fullPeerRequests.find(peer); if (i != _fullPeerRequests.cend() && i.value() == req) { _fullPeerRequests.erase(i); } } if (badVersion) { if (const auto chat = peer->asChat()) { chat->version = vc[0].c_chat().vversion.v; } else if (const auto channel = peer->asChannel()) { channel->version = vc[0].c_channel().vversion.v; } requestPeer(peer); } fullPeerUpdated().notify(peer); } void ApiWrap::gotUserFull(UserData *user, const MTPUserFull &result, mtpRequestId req) { auto &d = result.c_userFull(); App::feedUsers(MTP_vector(1, d.vuser)); if (d.has_profile_photo()) { _session->data().photo(d.vprofile_photo); } App::feedUserLink(MTP_int(peerToUser(user->id)), d.vlink.c_contacts_link().vmy_link, d.vlink.c_contacts_link().vforeign_link); if (App::main()) { notifySettingReceived(MTP_inputNotifyPeer(user->input), d.vnotify_settings); } if (d.has_bot_info()) { user->setBotInfo(d.vbot_info); } else { user->setBotInfoVersion(-1); } user->setBlockStatus(d.is_blocked() ? UserData::BlockStatus::Blocked : UserData::BlockStatus::NotBlocked); user->setCallsStatus(d.is_phone_calls_private() ? UserData::CallsStatus::Private : d.is_phone_calls_available() ? UserData::CallsStatus::Enabled : UserData::CallsStatus::Disabled); user->setAbout(d.has_about() ? qs(d.vabout) : QString()); user->setCommonChatsCount(d.vcommon_chats_count.v); user->fullUpdated(); if (req) { auto i = _fullPeerRequests.find(user); if (i != _fullPeerRequests.cend() && i.value() == req) { _fullPeerRequests.erase(i); } } fullPeerUpdated().notify(user); } void ApiWrap::requestPeer(PeerData *peer) { if (!peer || _fullPeerRequests.contains(peer) || _peerRequests.contains(peer)) return; auto sendRequest = [this, peer] { auto failHandler = [this, peer](const RPCError &error) { _peerRequests.remove(peer); }; auto chatHandler = [this, peer](const MTPmessages_Chats &result) { _peerRequests.remove(peer); if (auto chats = Api::getChatsFromMessagesChats(result)) { auto &v = chats->v; bool badVersion = false; if (const auto chat = peer->asChat()) { badVersion = !v.isEmpty() && (v[0].type() == mtpc_chat) && (v[0].c_chat().vversion.v < chat->version); } else if (const auto channel = peer->asChannel()) { badVersion = !v.isEmpty() && (v[0].type() == mtpc_channel) && (v[0].c_channel().vversion.v < channel->version); } auto chat = App::feedChats(*chats); if (chat == peer) { if (badVersion) { if (auto chat = peer->asChat()) { chat->version = v[0].c_chat().vversion.v; } else if (auto channel = peer->asChannel()) { channel->version = v[0].c_channel().vversion.v; } requestPeer(peer); } } } }; if (auto user = peer->asUser()) { return request(MTPusers_GetUsers(MTP_vector(1, user->inputUser))).done([this, user](const MTPVector &result) { _peerRequests.remove(user); App::feedUsers(result); }).fail(failHandler).send(); } else if (auto chat = peer->asChat()) { return request(MTPmessages_GetChats(MTP_vector(1, chat->inputChat))).done(chatHandler).fail(failHandler).send(); } else if (auto channel = peer->asChannel()) { return request(MTPchannels_GetChannels(MTP_vector(1, channel->inputChannel))).done(chatHandler).fail(failHandler).send(); } return 0; }; if (auto requestId = sendRequest()) { _peerRequests.insert(peer, requestId); } } void ApiWrap::markMediaRead( const base::flat_set> &items) { auto markedIds = QVector(); auto channelMarkedIds = base::flat_map< not_null, QVector>(); markedIds.reserve(items.size()); for (const auto item : items) { if (!item->isMediaUnread() || (item->out() && !item->mentionsMe())) { continue; } item->markMediaRead(); if (!IsServerMsgId(item->id)) { continue; } if (const auto channel = item->history()->peer->asChannel()) { channelMarkedIds[channel].push_back(MTP_int(item->id)); } else { markedIds.push_back(MTP_int(item->id)); } } if (!markedIds.isEmpty()) { request(MTPmessages_ReadMessageContents( MTP_vector(markedIds) )).done([=](const MTPmessages_AffectedMessages &result) { applyAffectedMessages(result); }).send(); } for (const auto &channelIds : channelMarkedIds) { request(MTPchannels_ReadMessageContents( channelIds.first->inputChannel, MTP_vector(channelIds.second) )).send(); } } void ApiWrap::markMediaRead(not_null item) { if (!item->isMediaUnread() || (item->out() && !item->mentionsMe())) { return; } item->markMediaRead(); if (!IsServerMsgId(item->id)) { return; } const auto ids = MTP_vector(1, MTP_int(item->id)); if (const auto channel = item->history()->peer->asChannel()) { request(MTPchannels_ReadMessageContents( channel->inputChannel, ids )).send(); } else { request(MTPmessages_ReadMessageContents( ids )).done([=](const MTPmessages_AffectedMessages &result) { applyAffectedMessages(result); }).send(); } } void ApiWrap::requestPeers(const QList &peers) { QVector chats; QVector channels; QVector users; chats.reserve(peers.size()); channels.reserve(peers.size()); users.reserve(peers.size()); for (QList::const_iterator i = peers.cbegin(), e = peers.cend(); i != e; ++i) { if (!*i || _fullPeerRequests.contains(*i) || _peerRequests.contains(*i)) continue; if ((*i)->isUser()) { users.push_back((*i)->asUser()->inputUser); } else if ((*i)->isChat()) { chats.push_back((*i)->asChat()->inputChat); } else if ((*i)->isChannel()) { channels.push_back((*i)->asChannel()->inputChannel); } } auto handleChats = [=](const MTPmessages_Chats &result) { if (auto chats = Api::getChatsFromMessagesChats(result)) { App::feedChats(*chats); } }; if (!chats.isEmpty()) { request(MTPmessages_GetChats(MTP_vector(chats))).done(handleChats).send(); } if (!channels.isEmpty()) { request(MTPchannels_GetChannels(MTP_vector(channels))).done(handleChats).send(); } if (!users.isEmpty()) { request(MTPusers_GetUsers(MTP_vector(users))).done([=](const MTPVector &result) { App::feedUsers(result); }).send(); } } void ApiWrap::requestLastParticipants(not_null channel) { if (!channel->isMegagroup() || _participantsRequests.contains(channel)) { return; } const auto offset = 0; const auto participantsHash = 0; const auto requestId = request(MTPchannels_GetParticipants( channel->inputChannel, MTP_channelParticipantsRecent(), MTP_int(offset), MTP_int(Global::ChatSizeMax()), MTP_int(participantsHash) )).done([this, channel](const MTPchannels_ChannelParticipants &result) { _participantsRequests.remove(channel); parseChannelParticipants(channel, result, [&]( int availableCount, const QVector &list) { applyLastParticipantsList( channel, availableCount, list); }); }).fail([this, channel](const RPCError &error) { _participantsRequests.remove(channel); }).send(); _participantsRequests.insert(channel, requestId); } void ApiWrap::requestBots(not_null channel) { if (!channel->isMegagroup() || _botsRequests.contains(channel)) { return; } auto offset = 0; auto participantsHash = 0; auto requestId = request(MTPchannels_GetParticipants( channel->inputChannel, MTP_channelParticipantsBots(), MTP_int(offset), MTP_int(Global::ChatSizeMax()), MTP_int(participantsHash) )).done([this, channel](const MTPchannels_ChannelParticipants &result) { _botsRequests.remove(channel); parseChannelParticipants(channel, result, [&]( int availableCount, const QVector &list) { applyBotsList( channel, availableCount, list); }); }).fail([this, channel](const RPCError &error) { _botsRequests.remove(channel); }).send(); _botsRequests.insert(channel, requestId); } void ApiWrap::requestAdmins(not_null channel) { if (!channel->isMegagroup() || _adminsRequests.contains(channel)) { return; } auto offset = 0; auto participantsHash = 0; auto requestId = request(MTPchannels_GetParticipants( channel->inputChannel, MTP_channelParticipantsAdmins(), MTP_int(offset), MTP_int(Global::ChatSizeMax()), MTP_int(participantsHash) )).done([this, channel](const MTPchannels_ChannelParticipants &result) { _adminsRequests.remove(channel); TLHelp::VisitChannelParticipants(result, base::overload([&]( const MTPDchannels_channelParticipants &data) { App::feedUsers(data.vusers); applyAdminsList( channel, data.vcount.v, data.vparticipants.v); }, [&](mtpTypeId) { LOG(("API Error: channels.channelParticipantsNotModified received!")); })); }).fail([this, channel](const RPCError &error) { _adminsRequests.remove(channel); }).send(); _adminsRequests.insert(channel, requestId); } void ApiWrap::applyLastParticipantsList( not_null channel, int availableCount, const QVector &list) { channel->mgInfo->lastAdmins.clear(); channel->mgInfo->lastRestricted.clear(); channel->mgInfo->lastParticipants.clear(); channel->mgInfo->lastParticipantsStatus = MegagroupInfo::LastParticipantsUpToDate; auto botStatus = channel->mgInfo->botStatus; const auto emptyAdminRights = MTP_channelAdminRights(MTP_flags(0)); const auto emptyRestrictedRights = MTP_channelBannedRights( MTP_flags(0), MTP_int(0)); for (const auto &p : list) { const auto userId = TLHelp::ReadChannelParticipantUserId(p); const auto adminCanEdit = (p.type() == mtpc_channelParticipantAdmin) ? p.c_channelParticipantAdmin().is_can_edit() : false; const auto adminRights = (p.type() == mtpc_channelParticipantAdmin) ? p.c_channelParticipantAdmin().vadmin_rights : emptyAdminRights; const auto restrictedRights = (p.type() == mtpc_channelParticipantBanned) ? p.c_channelParticipantBanned().vbanned_rights : emptyRestrictedRights; if (!userId) { continue; } auto user = App::user(userId); if (p.type() == mtpc_channelParticipantCreator) { channel->mgInfo->creator = user; if (!channel->mgInfo->admins.empty() && !channel->mgInfo->admins.contains(userId)) { Data::ChannelAdminChanges changes(channel); changes.feed(userId, true); } } if (!base::contains(channel->mgInfo->lastParticipants, user)) { channel->mgInfo->lastParticipants.push_back(user); if (adminRights.c_channelAdminRights().vflags.v) { channel->mgInfo->lastAdmins.emplace( user, MegagroupInfo::Admin{ adminRights, adminCanEdit }); } else if (restrictedRights.c_channelBannedRights().vflags.v != 0) { channel->mgInfo->lastRestricted.emplace( user, MegagroupInfo::Restricted{ restrictedRights }); } if (user->botInfo) { channel->mgInfo->bots.insert(user); if (channel->mgInfo->botStatus != 0 && channel->mgInfo->botStatus < 2) { channel->mgInfo->botStatus = 2; } } } } // // getParticipants(Recent) sometimes can't return all members, // only some last subset, size of this subset is availableCount. // // So both list size and availableCount have nothing to do with // the full supergroup members count. // //if (list.isEmpty()) { // channel->setMembersCount(channel->mgInfo->lastParticipants.size()); //} else { // channel->setMembersCount(availableCount); //} Notify::PeerUpdate update(channel); update.flags |= Notify::PeerUpdate::Flag::MembersChanged | Notify::PeerUpdate::Flag::AdminsChanged; Notify::peerUpdatedDelayed(update); channel->mgInfo->botStatus = botStatus; if (App::main()) fullPeerUpdated().notify(channel); } void ApiWrap::applyBotsList( not_null channel, int availableCount, const QVector &list) { const auto history = App::historyLoaded(channel->id); channel->mgInfo->bots.clear(); channel->mgInfo->botStatus = -1; auto needBotsInfos = false; auto botStatus = channel->mgInfo->botStatus; auto keyboardBotFound = !history || !history->lastKeyboardFrom; for (const auto &p : list) { const auto userId = TLHelp::ReadChannelParticipantUserId(p); if (!userId) { continue; } auto user = App::user(userId); if (user->botInfo) { channel->mgInfo->bots.insert(user); botStatus = 2;// (botStatus > 0/* || !i.key()->botInfo->readsAllHistory*/) ? 2 : 1; if (!user->botInfo->inited) { needBotsInfos = true; } } if (!keyboardBotFound && user->id == history->lastKeyboardFrom) { keyboardBotFound = true; } } if (needBotsInfos) { requestFullPeer(channel); } if (!keyboardBotFound) { history->clearLastKeyboard(); } channel->mgInfo->botStatus = botStatus; if (App::main()) fullPeerUpdated().notify(channel); } void ApiWrap::applyAdminsList( not_null channel, int availableCount, const QVector &list) { auto admins = ranges::make_iterator_range( list.begin(), list.end() ) | ranges::view::transform([](const MTPChannelParticipant &p) { return TLHelp::ReadChannelParticipantUserId(p); }); auto adding = base::flat_set{ admins.begin(), admins.end() }; if (channel->mgInfo->creator) { adding.insert(peerToUser(channel->mgInfo->creator->id)); } auto removing = channel->mgInfo->admins; if (removing.empty() && adding.empty()) { // Add some admin-placeholder so we don't DDOS // server with admins list requests. LOG(("API Error: Got empty admins list from server.")); adding.insert(0); } Data::ChannelAdminChanges changes(channel); for (const auto addingId : adding) { if (!removing.remove(addingId)) { changes.feed(addingId, true); } } for (const auto removingId : removing) { changes.feed(removingId, false); } } void ApiWrap::requestSelfParticipant(ChannelData *channel) { if (_selfParticipantRequests.contains(channel)) { return; } auto requestId = request(MTPchannels_GetParticipant( channel->inputChannel, MTP_inputUserSelf() )).done([this, channel](const MTPchannels_ChannelParticipant &result) { _selfParticipantRequests.remove(channel); if (result.type() != mtpc_channels_channelParticipant) { LOG(("API Error: unknown type in gotSelfParticipant (%1)").arg(result.type())); channel->inviter = -1; if (App::main()) App::main()->onSelfParticipantUpdated(channel); return; } auto &p = result.c_channels_channelParticipant(); App::feedUsers(p.vusers); switch (p.vparticipant.type()) { case mtpc_channelParticipantSelf: { auto &d = p.vparticipant.c_channelParticipantSelf(); channel->inviter = d.vinviter_id.v; channel->inviteDate = d.vdate.v; } break; case mtpc_channelParticipantCreator: { auto &d = p.vparticipant.c_channelParticipantCreator(); channel->inviter = _session->userId(); channel->inviteDate = channel->date; if (channel->mgInfo) { channel->mgInfo->creator = App::self(); } } break; case mtpc_channelParticipantAdmin: { auto &d = p.vparticipant.c_channelParticipantAdmin(); channel->inviter = d.vinviter_id.v; channel->inviteDate = d.vdate.v; } break; } if (App::main()) App::main()->onSelfParticipantUpdated(channel); }).fail([this, channel](const RPCError &error) { _selfParticipantRequests.remove(channel); if (error.type() == qstr("USER_NOT_PARTICIPANT")) { channel->inviter = -1; } }).afterDelay(kSmallDelayMs).send(); _selfParticipantRequests.insert(channel, requestId); } void ApiWrap::kickParticipant( not_null chat, not_null user) { request(MTPmessages_DeleteChatUser( chat->inputChat, user->inputUser )).done([=](const MTPUpdates &result) { applyUpdates(result); }).send(); } void ApiWrap::kickParticipant( not_null channel, not_null user, const MTPChannelBannedRights ¤tRights) { const auto kick = KickRequest(channel, user); if (_kickRequests.contains(kick)) return; const auto rights = ChannelData::KickedRestrictedRights(); const auto requestId = request(MTPchannels_EditBanned( channel->inputChannel, user->inputUser, rights )).done([=](const MTPUpdates &result) { applyUpdates(result); _kickRequests.remove(KickRequest(channel, user)); channel->applyEditBanned(user, currentRights, rights); }).fail([this, kick](const RPCError &error) { _kickRequests.remove(kick); }).send(); _kickRequests.emplace(kick, requestId); } void ApiWrap::unblockParticipant( not_null channel, not_null user) { const auto kick = KickRequest(channel, user); if (_kickRequests.contains(kick)) { return; } const auto requestId = request(MTPchannels_EditBanned( channel->inputChannel, user->inputUser, MTP_channelBannedRights(MTP_flags(0), MTP_int(0)) )).done([=](const MTPUpdates &result) { applyUpdates(result); _kickRequests.remove(KickRequest(channel, user)); if (channel->kickedCount() > 0) { channel->setKickedCount(channel->kickedCount() - 1); } else { channel->updateFullForced(); } }).fail([this, kick](const RPCError &error) { _kickRequests.remove(kick); }).send(); _kickRequests.emplace(kick, requestId); } void ApiWrap::deleteAllFromUser( not_null channel, not_null from) { const auto history = App::historyLoaded(channel->id); const auto ids = history ? history->collectMessagesFromUserToDelete(from) : QVector(); const auto channelId = peerToChannel(channel->id); for (const auto msgId : ids) { if (const auto item = App::histItemById(channelId, msgId)) { item->destroy(); } } Auth().data().sendHistoryChangeNotifications(); deleteAllFromUserSend(channel, from); } void ApiWrap::deleteAllFromUserSend( not_null channel, not_null from) { request(MTPchannels_DeleteUserHistory( channel->inputChannel, from->inputUser )).done([=](const MTPmessages_AffectedHistory &result) { const auto offset = applyAffectedHistory(channel, result); if (offset > 0) { deleteAllFromUserSend(channel, from); } else if (const auto history = App::historyLoaded(channel)) { if (!history->lastMessageKnown()) { Auth().api().requestDialogEntry(history); } } }).send(); } void ApiWrap::requestChannelMembersForAdd( not_null channel, base::lambda callback) { _channelMembersForAddCallback = std::move(callback); if (_channelMembersForAdd == channel) { return; } request(base::take(_channelMembersForAddRequestId)).cancel(); auto offset = 0; auto participantsHash = 0; _channelMembersForAdd = channel; _channelMembersForAddRequestId = request(MTPchannels_GetParticipants( channel->inputChannel, MTP_channelParticipantsRecent(), MTP_int(offset), MTP_int(Global::ChatSizeMax()), MTP_int(participantsHash) )).done([this](const MTPchannels_ChannelParticipants &result) { base::take(_channelMembersForAddRequestId); base::take(_channelMembersForAdd); base::take(_channelMembersForAddCallback)(result); }).fail([this](const RPCError &error) { base::take(_channelMembersForAddRequestId); base::take(_channelMembersForAdd); base::take(_channelMembersForAddCallback); }).send(); } void ApiWrap::scheduleStickerSetRequest(uint64 setId, uint64 access) { if (!_stickerSetRequests.contains(setId)) { _stickerSetRequests.insert(setId, qMakePair(access, 0)); } } void ApiWrap::requestStickerSets() { for (auto i = _stickerSetRequests.begin(), j = i, e = _stickerSetRequests.end(); i != e; i = j) { ++j; if (i.value().second) continue; auto waitMs = (j == e) ? 0 : kSmallDelayMs; i.value().second = request(MTPmessages_GetStickerSet(MTP_inputStickerSetID(MTP_long(i.key()), MTP_long(i.value().first)))).done([this, setId = i.key()](const MTPmessages_StickerSet &result) { gotStickerSet(setId, result); }).fail([this, setId = i.key()](const RPCError &error) { _stickerSetRequests.remove(setId); }).afterDelay(waitMs).send(); } } void ApiWrap::saveStickerSets( const Stickers::Order &localOrder, const Stickers::Order &localRemoved) { for (auto requestId : base::take(_stickerSetDisenableRequests)) { request(requestId).cancel(); } request(base::take(_stickersReorderRequestId)).cancel(); request(base::take(_stickersClearRecentRequestId)).cancel(); auto writeInstalled = true, writeRecent = false, writeCloudRecent = false, writeFaved = false, writeArchived = false; auto &recent = Stickers::GetRecentPack(); auto &sets = _session->data().stickerSetsRef(); _stickersOrder = localOrder; for_const (auto removedSetId, localRemoved) { if (removedSetId == Stickers::CloudRecentSetId) { if (sets.remove(Stickers::CloudRecentSetId) != 0) { writeCloudRecent = true; } if (sets.remove(Stickers::CustomSetId)) { writeInstalled = true; } if (!recent.isEmpty()) { recent.clear(); writeRecent = true; } _stickersClearRecentRequestId = request(MTPmessages_ClearRecentStickers( MTP_flags(0) )).done([this](const MTPBool &result) { _stickersClearRecentRequestId = 0; }).fail([this](const RPCError &error) { _stickersClearRecentRequestId = 0; }).send(); continue; } auto it = sets.find(removedSetId); if (it != sets.cend()) { for (auto i = recent.begin(); i != recent.cend();) { if (it->stickers.indexOf(i->first) >= 0) { i = recent.erase(i); writeRecent = true; } else { ++i; } } if (!(it->flags & MTPDstickerSet::Flag::f_archived)) { MTPInputStickerSet setId = (it->id && it->access) ? MTP_inputStickerSetID(MTP_long(it->id), MTP_long(it->access)) : MTP_inputStickerSetShortName(MTP_string(it->shortName)); auto requestId = request(MTPmessages_UninstallStickerSet(setId)).done([this](const MTPBool &result, mtpRequestId requestId) { stickerSetDisenabled(requestId); }).fail([this](const RPCError &error, mtpRequestId requestId) { stickerSetDisenabled(requestId); }).afterDelay(kSmallDelayMs).send(); _stickerSetDisenableRequests.insert(requestId); int removeIndex = _session->data().stickerSetsOrder().indexOf(it->id); if (removeIndex >= 0) _session->data().stickerSetsOrderRef().removeAt(removeIndex); if (!(it->flags & MTPDstickerSet_ClientFlag::f_featured) && !(it->flags & MTPDstickerSet_ClientFlag::f_special)) { sets.erase(it); } else { if (it->flags & MTPDstickerSet::Flag::f_archived) { writeArchived = true; } it->flags &= ~(MTPDstickerSet::Flag::f_installed_date | MTPDstickerSet::Flag::f_archived); it->installDate = TimeId(0); } } } } // Clear all installed flags, set only for sets from order. for (auto &set : sets) { if (!(set.flags & MTPDstickerSet::Flag::f_archived)) { set.flags &= ~MTPDstickerSet::Flag::f_installed_date; } } auto &order = _session->data().stickerSetsOrderRef(); order.clear(); for_const (auto setId, _stickersOrder) { auto it = sets.find(setId); if (it != sets.cend()) { if ((it->flags & MTPDstickerSet::Flag::f_archived) && !localRemoved.contains(it->id)) { MTPInputStickerSet mtpSetId = (it->id && it->access) ? MTP_inputStickerSetID(MTP_long(it->id), MTP_long(it->access)) : MTP_inputStickerSetShortName(MTP_string(it->shortName)); auto requestId = request(MTPmessages_InstallStickerSet(mtpSetId, MTP_boolFalse())).done([this](const MTPmessages_StickerSetInstallResult &result, mtpRequestId requestId) { stickerSetDisenabled(requestId); }).fail([this](const RPCError &error, mtpRequestId requestId) { stickerSetDisenabled(requestId); }).afterDelay(kSmallDelayMs).send(); _stickerSetDisenableRequests.insert(requestId); it->flags &= ~MTPDstickerSet::Flag::f_archived; writeArchived = true; } order.push_back(setId); it->flags |= MTPDstickerSet::Flag::f_installed_date; if (!it->installDate) { it->installDate = unixtime(); } } } for (auto it = sets.begin(); it != sets.cend();) { if ((it->flags & MTPDstickerSet_ClientFlag::f_featured) || (it->flags & MTPDstickerSet::Flag::f_installed_date) || (it->flags & MTPDstickerSet::Flag::f_archived) || (it->flags & MTPDstickerSet_ClientFlag::f_special)) { ++it; } else { it = sets.erase(it); } } if (writeInstalled) Local::writeInstalledStickers(); if (writeRecent) Local::writeUserSettings(); if (writeArchived) Local::writeArchivedStickers(); if (writeCloudRecent) Local::writeRecentStickers(); if (writeFaved) Local::writeFavedStickers(); _session->data().notifyStickersUpdated(); if (_stickerSetDisenableRequests.empty()) { stickersSaveOrder(); } else { requestSendDelayed(); } } void ApiWrap::stickerSetDisenabled(mtpRequestId requestId) { _stickerSetDisenableRequests.remove(requestId); if (_stickerSetDisenableRequests.empty()) { stickersSaveOrder(); } }; void ApiWrap::joinChannel(not_null channel) { if (channel->amIn()) { Notify::peerUpdatedDelayed( channel, Notify::PeerUpdate::Flag::ChannelAmIn); } else if (!_channelAmInRequests.contains(channel)) { auto requestId = request(MTPchannels_JoinChannel( channel->inputChannel )).done([=](const MTPUpdates &result) { _channelAmInRequests.remove(channel); applyUpdates(result); }).fail([=](const RPCError &error) { if (error.type() == qstr("CHANNEL_PRIVATE") || error.type() == qstr("CHANNEL_PUBLIC_GROUP_NA") || error.type() == qstr("USER_BANNED_IN_CHANNEL")) { Ui::show(Box(lang(channel->isMegagroup() ? lng_group_not_accessible : lng_channel_not_accessible))); } else if (error.type() == qstr("CHANNELS_TOO_MUCH")) { Ui::show(Box(lang(lng_join_channel_error))); } else if (error.type() == qstr("USERS_TOO_MUCH")) { Ui::show(Box(lang(lng_group_full))); } _channelAmInRequests.remove(channel); }).send(); _channelAmInRequests.insert(channel, requestId); } } void ApiWrap::leaveChannel(not_null channel) { if (!channel->amIn()) { Notify::peerUpdatedDelayed( channel, Notify::PeerUpdate::Flag::ChannelAmIn); } else if (!_channelAmInRequests.contains(channel)) { auto requestId = request(MTPchannels_LeaveChannel( channel->inputChannel )).done([=](const MTPUpdates &result) { _channelAmInRequests.remove(channel); applyUpdates(result); }).fail([=](const RPCError &error) { _channelAmInRequests.remove(channel); }).send(); _channelAmInRequests.insert(channel, requestId); } } void ApiWrap::blockUser(UserData *user) { if (user->isBlocked()) { Notify::peerUpdatedDelayed(user, Notify::PeerUpdate::Flag::UserIsBlocked); } else if (!_blockRequests.contains(user)) { auto requestId = request(MTPcontacts_Block(user->inputUser)).done([this, user](const MTPBool &result) { _blockRequests.remove(user); user->setBlockStatus(UserData::BlockStatus::Blocked); }).fail([this, user](const RPCError &error) { _blockRequests.remove(user); }).send(); _blockRequests.insert(user, requestId); } } void ApiWrap::unblockUser(UserData *user) { if (!user->isBlocked()) { Notify::peerUpdatedDelayed(user, Notify::PeerUpdate::Flag::UserIsBlocked); } else if (!_blockRequests.contains(user)) { auto requestId = request(MTPcontacts_Unblock(user->inputUser)).done([this, user](const MTPBool &result) { _blockRequests.remove(user); user->setBlockStatus(UserData::BlockStatus::NotBlocked); }).fail([this, user](const RPCError &error) { _blockRequests.remove(user); }).send(); _blockRequests.insert(user, requestId); } } void ApiWrap::exportInviteLink(PeerData *peer) { if (_exportInviteRequests.contains(peer)) { return; } auto sendRequest = [this, peer] { auto exportFail = [this, peer](const RPCError &error) { _exportInviteRequests.remove(peer); }; if (auto chat = peer->asChat()) { return request(MTPmessages_ExportChatInvite(chat->inputChat)).done([this, chat](const MTPExportedChatInvite &result) { _exportInviteRequests.remove(chat); chat->setInviteLink((result.type() == mtpc_chatInviteExported) ? qs(result.c_chatInviteExported().vlink) : QString()); }).fail(exportFail).send(); } else if (auto channel = peer->asChannel()) { return request(MTPchannels_ExportInvite(channel->inputChannel)).done([this, channel](const MTPExportedChatInvite &result) { _exportInviteRequests.remove(channel); channel->setInviteLink((result.type() == mtpc_chatInviteExported) ? qs(result.c_chatInviteExported().vlink) : QString()); }).fail(exportFail).send(); } return 0; }; if (auto requestId = sendRequest()) { _exportInviteRequests.insert(peer, requestId); } } void ApiWrap::requestNotifySetting(PeerData *peer) { if (_notifySettingRequests.contains(peer)) return; auto notifyPeer = MTP_inputNotifyPeer(peer->input); auto requestId = request(MTPaccount_GetNotifySettings(notifyPeer)).done([this, notifyPeer, peer](const MTPPeerNotifySettings &result) { notifySettingReceived(notifyPeer, result); _notifySettingRequests.remove(peer); }).fail([this, notifyPeer, peer](const RPCError &error) { notifySettingReceived(notifyPeer, MTP_peerNotifySettingsEmpty()); _notifySettingRequests.remove(peer); }).send(); _notifySettingRequests.insert(peer, requestId); } void ApiWrap::saveDraftToCloudDelayed(History *history) { _draftsSaveRequestIds.insert(history, 0); if (!_draftsSaveTimer.isActive()) { _draftsSaveTimer.callOnce(kSaveCloudDraftTimeout); } } void ApiWrap::savePrivacy(const MTPInputPrivacyKey &key, QVector &&rules) { auto keyTypeId = key.type(); auto it = _privacySaveRequests.find(keyTypeId); if (it != _privacySaveRequests.cend()) { request(it->second).cancel(); _privacySaveRequests.erase(it); } auto requestId = request(MTPaccount_SetPrivacy(key, MTP_vector(std::move(rules)))).done([this, keyTypeId](const MTPaccount_PrivacyRules &result) { Expects(result.type() == mtpc_account_privacyRules); auto &rules = result.c_account_privacyRules(); App::feedUsers(rules.vusers); _privacySaveRequests.remove(keyTypeId); handlePrivacyChange(keyTypeId, rules.vrules); }).fail([this, keyTypeId](const RPCError &error) { _privacySaveRequests.remove(keyTypeId); }).send(); _privacySaveRequests.emplace(keyTypeId, requestId); } void ApiWrap::handlePrivacyChange(mtpTypeId keyTypeId, const MTPVector &rules) { if (keyTypeId == mtpc_privacyKeyStatusTimestamp) { enum class Rule { Unknown, Allow, Disallow, }; auto userRules = QMap(); auto contactsRule = Rule::Unknown; auto everyoneRule = Rule::Unknown; for (auto &rule : rules.v) { auto type = rule.type(); if (type != mtpc_privacyValueAllowAll && type != mtpc_privacyValueDisallowAll && contactsRule != Rule::Unknown) { // This is simplified: we ignore per-user rules that come after a contacts rule. // But none of the official apps provide such complicated rule sets, so its fine. continue; } switch (type) { case mtpc_privacyValueAllowAll: everyoneRule = Rule::Allow; break; case mtpc_privacyValueDisallowAll: everyoneRule = Rule::Disallow; break; case mtpc_privacyValueAllowContacts: contactsRule = Rule::Allow; break; case mtpc_privacyValueDisallowContacts: contactsRule = Rule::Disallow; break; case mtpc_privacyValueAllowUsers: { for_const (auto &userId, rule.c_privacyValueAllowUsers().vusers.v) { if (!userRules.contains(userId.v)) { userRules.insert(userId.v, Rule::Allow); } } } break; case mtpc_privacyValueDisallowUsers: { for_const (auto &userId, rule.c_privacyValueDisallowUsers().vusers.v) { if (!userRules.contains(userId.v)) { userRules.insert(userId.v, Rule::Disallow); } } } break; } if (everyoneRule != Rule::Unknown) { break; } } auto now = unixtime(); App::enumerateUsers([&](UserData *user) { if (user->isSelf() || user->loadedStatus != PeerData::FullLoaded) { return; } if (user->onlineTill <= 0) { return; } if (user->onlineTill + 3 * 86400 >= now) { user->onlineTill = -2; // recently } else if (user->onlineTill + 7 * 86400 >= now) { user->onlineTill = -3; // last week } else if (user->onlineTill + 30 * 86400 >= now) { user->onlineTill = -4; // last month } else { user->onlineTill = 0; } Notify::peerUpdatedDelayed(user, Notify::PeerUpdate::Flag::UserOnlineChanged); }); if (_contactsStatusesRequestId) { request(_contactsStatusesRequestId).cancel(); } _contactsStatusesRequestId = request(MTPcontacts_GetStatuses()).done([this](const MTPVector &result) { _contactsStatusesRequestId = 0; for_const (auto &item, result.v) { Assert(item.type() == mtpc_contactStatus); auto &data = item.c_contactStatus(); if (auto user = App::userLoaded(data.vuser_id.v)) { auto oldOnlineTill = user->onlineTill; auto newOnlineTill = onlineTillFromStatus(data.vstatus, oldOnlineTill); if (oldOnlineTill != newOnlineTill) { user->onlineTill = newOnlineTill; Notify::peerUpdatedDelayed(user, Notify::PeerUpdate::Flag::UserOnlineChanged); } } } }).fail([this](const RPCError &error) { _contactsStatusesRequestId = 0; }).send(); } } int ApiWrap::onlineTillFromStatus(const MTPUserStatus &status, int currentOnlineTill) { switch (status.type()) { case mtpc_userStatusEmpty: return 0; case mtpc_userStatusRecently: return (currentOnlineTill > -10) ? -2 : currentOnlineTill; // don't modify pseudo-online case mtpc_userStatusLastWeek: return -3; case mtpc_userStatusLastMonth: return -4; case mtpc_userStatusOffline: return status.c_userStatusOffline().vwas_online.v; case mtpc_userStatusOnline: return status.c_userStatusOnline().vexpires.v; } Unexpected("Bad UserStatus type."); } void ApiWrap::clearHistory(not_null peer) { auto deleteTillId = MsgId(0); if (auto history = App::historyLoaded(peer->id)) { if (const auto last = history->lastMessage()) { deleteTillId = last->id; Local::addSavedPeer(history->peer, ItemDateTime(last)); } history->clear(); history->markFullyLoaded(); } if (const auto channel = peer->asChannel()) { if (const auto migrated = peer->migrateFrom()) { clearHistory(migrated); } if (IsServerMsgId(deleteTillId)) { request(MTPchannels_DeleteHistory( channel->inputChannel, MTP_int(deleteTillId) )).send(); } } else { request(MTPmessages_DeleteHistory( MTP_flags(MTPmessages_DeleteHistory::Flag::f_just_clear), peer->input, MTP_int(0) )).done([=](const MTPmessages_AffectedHistory &result) { const auto offset = applyAffectedHistory(peer, result); if (offset > 0) { clearHistory(peer); } }).send(); } } int ApiWrap::applyAffectedHistory( not_null peer, const MTPmessages_AffectedHistory &result) { const auto &data = result.c_messages_affectedHistory(); if (const auto channel = peer->asChannel()) { channel->ptsUpdateAndApply(data.vpts.v, data.vpts_count.v); } else { App::main()->ptsUpdateAndApply(data.vpts.v, data.vpts_count.v); } return data.voffset.v; } void ApiWrap::applyAffectedMessages( not_null peer, const MTPmessages_AffectedMessages &result) { const auto &data = result.c_messages_affectedMessages(); if (const auto channel = peer->asChannel()) { channel->ptsUpdateAndApply(data.vpts.v, data.vpts_count.v); } else { applyAffectedMessages(result); } } void ApiWrap::applyAffectedMessages( const MTPmessages_AffectedMessages &result) { const auto &data = result.c_messages_affectedMessages(); App::main()->ptsUpdateAndApply(data.vpts.v, data.vpts_count.v); } void ApiWrap::saveDraftsToCloud() { for (auto i = _draftsSaveRequestIds.begin(), e = _draftsSaveRequestIds.end(); i != e; ++i) { if (i.value()) continue; // sent already auto history = i.key(); auto cloudDraft = history->cloudDraft(); auto localDraft = history->localDraft(); if (cloudDraft && cloudDraft->saveRequestId) { request(base::take(cloudDraft->saveRequestId)).cancel(); } cloudDraft = history->createCloudDraft(localDraft); auto flags = MTPmessages_SaveDraft::Flags(0); auto &textWithTags = cloudDraft->textWithTags; if (cloudDraft->previewCancelled) { flags |= MTPmessages_SaveDraft::Flag::f_no_webpage; } if (cloudDraft->msgId) { flags |= MTPmessages_SaveDraft::Flag::f_reply_to_msg_id; } if (!textWithTags.tags.isEmpty()) { flags |= MTPmessages_SaveDraft::Flag::f_entities; } auto entities = TextUtilities::EntitiesToMTP(ConvertTextTagsToEntities(textWithTags.tags), TextUtilities::ConvertOption::SkipLocal); cloudDraft->saveRequestId = request(MTPmessages_SaveDraft(MTP_flags(flags), MTP_int(cloudDraft->msgId), history->peer->input, MTP_string(textWithTags.text), entities)).done([this, history](const MTPBool &result, mtpRequestId requestId) { if (auto cloudDraft = history->cloudDraft()) { if (cloudDraft->saveRequestId == requestId) { cloudDraft->saveRequestId = 0; history->draftSavedToCloud(); } } auto i = _draftsSaveRequestIds.find(history); if (i != _draftsSaveRequestIds.cend() && i.value() == requestId) { _draftsSaveRequestIds.remove(history); checkQuitPreventFinished(); } }).fail([this, history](const RPCError &error, mtpRequestId requestId) { if (auto cloudDraft = history->cloudDraft()) { if (cloudDraft->saveRequestId == requestId) { history->clearCloudDraft(); } } auto i = _draftsSaveRequestIds.find(history); if (i != _draftsSaveRequestIds.cend() && i.value() == requestId) { _draftsSaveRequestIds.remove(history); checkQuitPreventFinished(); } }).send(); i.value() = cloudDraft->saveRequestId; } } bool ApiWrap::isQuitPrevent() { if (_draftsSaveRequestIds.isEmpty()) { return false; } LOG(("ApiWrap prevents quit, saving drafts...")); saveDraftsToCloud(); return true; } void ApiWrap::checkQuitPreventFinished() { if (_draftsSaveRequestIds.isEmpty()) { if (App::quitting()) { LOG(("ApiWrap doesn't prevent quit any more.")); } Messenger::Instance().quitPreventFinished(); } } PeerData *ApiWrap::notifySettingReceived( MTPInputNotifyPeer notifyPeer, const MTPPeerNotifySettings &settings) { PeerData *requestedPeer = nullptr; switch (notifyPeer.type()) { case mtpc_inputNotifyAll: App::main()->applyNotifySetting(MTP_notifyAll(), settings); break; case mtpc_inputNotifyUsers: App::main()->applyNotifySetting(MTP_notifyUsers(), settings); break; case mtpc_inputNotifyChats: App::main()->applyNotifySetting(MTP_notifyChats(), settings); break; case mtpc_inputNotifyPeer: { auto &peer = notifyPeer.c_inputNotifyPeer().vpeer; switch (peer.type()) { case mtpc_inputPeerEmpty: App::main()->applyNotifySetting( MTP_notifyPeer(MTP_peerUser(MTP_int(0))), settings); break; case mtpc_inputPeerSelf: requestedPeer = App::self(); break; case mtpc_inputPeerUser: requestedPeer = App::user(peerFromUser(peer.c_inputPeerUser().vuser_id)); break; case mtpc_inputPeerChat: requestedPeer = App::chat(peerFromChat(peer.c_inputPeerChat().vchat_id)); break; case mtpc_inputPeerChannel: requestedPeer = App::channel(peerFromChannel(peer.c_inputPeerChannel().vchannel_id)); break; } if (requestedPeer) { App::main()->applyNotifySetting( MTP_notifyPeer(peerToMTP(requestedPeer->id)), settings); } } break; } _session->notifications().checkDelayed(); return requestedPeer; } void ApiWrap::gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result) { _stickerSetRequests.remove(setId); Stickers::FeedSetFull(result); } void ApiWrap::requestWebPageDelayed(WebPageData *page) { if (page->pendingTill <= 0) return; _webPagesPending.insert(page, 0); auto left = (page->pendingTill - unixtime()) * 1000; if (!_webPagesTimer.isActive() || left <= _webPagesTimer.remainingTime()) { _webPagesTimer.callOnce((left < 0 ? 0 : left) + 1); } } void ApiWrap::clearWebPageRequest(WebPageData *page) { _webPagesPending.remove(page); if (_webPagesPending.isEmpty() && _webPagesTimer.isActive()) { _webPagesTimer.cancel(); } } void ApiWrap::clearWebPageRequests() { _webPagesPending.clear(); _webPagesTimer.cancel(); } void ApiWrap::resolveWebPages() { auto ids = QVector(); // temp_req_id = -1 using IndexAndMessageIds = QPair>; using MessageIdsByChannel = QMap; MessageIdsByChannel idsByChannel; // temp_req_id = -index - 2 ids.reserve(_webPagesPending.size()); int32 t = unixtime(), m = INT_MAX; for (auto i = _webPagesPending.begin(); i != _webPagesPending.cend(); ++i) { if (i.value() > 0) continue; if (i.key()->pendingTill <= t) { const auto item = _session->data().findWebPageItem(i.key()); if (item) { if (item->channelId() == NoChannel) { ids.push_back(MTP_inputMessageID(MTP_int(item->id))); i.value() = -1; } else { auto channel = item->history()->peer->asChannel(); auto channelMap = idsByChannel.find(channel); if (channelMap == idsByChannel.cend()) { channelMap = idsByChannel.insert( channel, IndexAndMessageIds( idsByChannel.size(), QVector( 1, MTP_inputMessageID(MTP_int(item->id))))); } else { channelMap.value().second.push_back( MTP_inputMessageID(MTP_int(item->id))); } i.value() = -channelMap.value().first - 2; } } } else { m = qMin(m, i.key()->pendingTill - t); } } auto requestId = mtpRequestId(0); if (!ids.isEmpty()) { requestId = request(MTPmessages_GetMessages( MTP_vector(ids) )).done([=](const MTPmessages_Messages &result, mtpRequestId requestId) { gotWebPages(nullptr, result, requestId); }).afterDelay(kSmallDelayMs).send(); } QVector reqsByIndex(idsByChannel.size(), 0); for (auto i = idsByChannel.cbegin(), e = idsByChannel.cend(); i != e; ++i) { reqsByIndex[i.value().first] = request(MTPchannels_GetMessages( i.key()->inputChannel, MTP_vector(i.value().second) )).done([=, channel = i.key()](const MTPmessages_Messages &result, mtpRequestId requestId) { gotWebPages(channel, result, requestId); }).afterDelay(kSmallDelayMs).send(); } if (requestId || !reqsByIndex.isEmpty()) { for (auto &pendingRequestId : _webPagesPending) { if (pendingRequestId > 0) continue; if (pendingRequestId < 0) { if (pendingRequestId == -1) { pendingRequestId = requestId; } else { pendingRequestId = reqsByIndex[-pendingRequestId - 2]; } } } } if (m < INT_MAX) { _webPagesTimer.callOnce(m * 1000); } } void ApiWrap::requestParticipantsCountDelayed( not_null channel) { _participantsCountRequestTimer.call( kReloadChannelMembersTimeout, [=] { channel->updateFullForced(); }); } void ApiWrap::requestChannelRangeDifference(not_null history) { Expects(history->isChannel()); const auto channel = history->peer->asChannel(); if (const auto requestId = _rangeDifferenceRequests.take(channel)) { request(*requestId).cancel(); } const auto range = history->rangeForDifferenceRequest(); if (!(range.from < range.till) || !channel->pts()) { return; } MTP_LOG(0, ("getChannelDifference { good - " "after channelDifferenceTooLong was received, " "validating history part }%1").arg(cTestMode() ? " TESTMODE" : "")); channelRangeDifferenceSend(channel, range, channel->pts()); } void ApiWrap::channelRangeDifferenceSend( not_null channel, MsgRange range, int32 pts) { Expects(range.from < range.till); const auto limit = range.till - range.from; const auto filter = MTP_channelMessagesFilter( MTP_flags(0), MTP_vector(1, MTP_messageRange( MTP_int(range.from), MTP_int(range.till - 1)))); const auto requestId = request(MTPupdates_GetChannelDifference( MTP_flags(MTPupdates_GetChannelDifference::Flag::f_force), channel->inputChannel, filter, MTP_int(pts), MTP_int(limit) )).done([=](const MTPupdates_ChannelDifference &result) { _rangeDifferenceRequests.remove(channel); channelRangeDifferenceDone(channel, range, result); }).fail([=](const RPCError &error) { _rangeDifferenceRequests.remove(channel); }).send(); _rangeDifferenceRequests.emplace(channel, requestId); } void ApiWrap::channelRangeDifferenceDone( not_null channel, MsgRange range, const MTPupdates_ChannelDifference &result) { auto nextRequestPts = int32(0); auto isFinal = true; switch (result.type()) { case mtpc_updates_channelDifferenceEmpty: { const auto &d = result.c_updates_channelDifferenceEmpty(); nextRequestPts = d.vpts.v; isFinal = d.is_final(); } break; case mtpc_updates_channelDifferenceTooLong: { const auto &d = result.c_updates_channelDifferenceTooLong(); App::feedUsers(d.vusers); App::feedChats(d.vchats); nextRequestPts = d.vpts.v; isFinal = d.is_final(); } break; case mtpc_updates_channelDifference: { const auto &d = result.c_updates_channelDifference(); App::main()->feedChannelDifference(d); nextRequestPts = d.vpts.v; isFinal = d.is_final(); } break; } if (!isFinal) { MTP_LOG(0, ("getChannelDifference { " "good - after not final channelDifference was received, " "validating history part }%1" ).arg(cTestMode() ? " TESTMODE" : "")); channelRangeDifferenceSend(channel, range, nextRequestPts); } } void ApiWrap::gotWebPages(ChannelData *channel, const MTPmessages_Messages &msgs, mtpRequestId req) { const QVector *v = 0; switch (msgs.type()) { case mtpc_messages_messages: { auto &d = msgs.c_messages_messages(); App::feedUsers(d.vusers); App::feedChats(d.vchats); v = &d.vmessages.v; } break; case mtpc_messages_messagesSlice: { auto &d = msgs.c_messages_messagesSlice(); App::feedUsers(d.vusers); App::feedChats(d.vchats); v = &d.vmessages.v; } break; case mtpc_messages_channelMessages: { auto &d = msgs.c_messages_channelMessages(); if (channel) { channel->ptsReceived(d.vpts.v); } else { LOG(("API Error: received messages.channelMessages when no channel was passed! (ApiWrap::gotWebPages)")); } App::feedUsers(d.vusers); App::feedChats(d.vchats); v = &d.vmessages.v; } break; case mtpc_messages_messagesNotModified: { LOG(("API Error: received messages.messagesNotModified! (ApiWrap::gotWebPages)")); } break; } if (!v) return; auto indices = base::flat_map(); // copied from feedMsgs for (auto i = 0, l = v->size(); i != l; ++i) { const auto msgId = idFromMessage(v->at(i)); indices.emplace((uint64(uint32(msgId)) << 32) | uint64(i), i); } for (const auto [position, index] : indices) { const auto item = App::histories().addNewMessage( v->at(index), NewMessageExisting); if (item) { _session->data().requestItemResize(item); } } for (auto i = _webPagesPending.begin(); i != _webPagesPending.cend();) { if (i.value() == req) { if (i.key()->pendingTill > 0) { i.key()->pendingTill = -1; _session->data().notifyWebPageUpdateDelayed(i.key()); } i = _webPagesPending.erase(i); } else { ++i; } } _session->data().sendWebPageGameNotifications(); } void ApiWrap::stickersSaveOrder() { if (_stickersOrder.size() > 1) { QVector mtpOrder; mtpOrder.reserve(_stickersOrder.size()); for_const (auto setId, _stickersOrder) { mtpOrder.push_back(MTP_long(setId)); } _stickersReorderRequestId = request(MTPmessages_ReorderStickerSets(MTP_flags(0), MTP_vector(mtpOrder))).done([this](const MTPBool &result) { _stickersReorderRequestId = 0; }).fail([this](const RPCError &error) { _stickersReorderRequestId = 0; _session->data().setLastStickersUpdate(0); updateStickers(); }).send(); } } void ApiWrap::updateStickers() { auto now = getms(true); requestStickers(now); requestRecentStickers(now); requestFavedStickers(now); requestFeaturedStickers(now); requestSavedGifs(now); } void ApiWrap::requestRecentStickersForce() { requestRecentStickersWithHash(0); } void ApiWrap::setGroupStickerSet(not_null megagroup, const MTPInputStickerSet &set) { Expects(megagroup->mgInfo != nullptr); megagroup->mgInfo->stickerSet = set; request(MTPchannels_SetStickers(megagroup->inputChannel, set)).send(); _session->data().notifyStickersUpdated(); } std::vector> *ApiWrap::stickersByEmoji( not_null emoji) { const auto it = _stickersByEmoji.find(emoji); const auto sendRequest = [&] { if (it == _stickersByEmoji.end()) { return true; } const auto received = it->second.received; const auto now = getms(true); return (received > 0) && (received + kStickersByEmojiInvalidateTimeout) <= now; }(); if (sendRequest) { const auto hash = (it != _stickersByEmoji.end()) ? it->second.hash : QString(); request(MTPmessages_GetStickers( MTP_flags(MTPmessages_GetStickers::Flag::f_exclude_featured), MTP_string(emoji->text()), MTP_string(hash) )).done([=](const MTPmessages_Stickers &result) { if (result.type() == mtpc_messages_stickersNotModified) { return; } Assert(result.type() == mtpc_messages_stickers); const auto &data = result.c_messages_stickers(); auto &entry = _stickersByEmoji[emoji]; entry.list.clear(); entry.list.reserve(data.vstickers.v.size()); for (const auto &sticker : data.vstickers.v) { const auto document = Auth().data().document(sticker); if (document->sticker()) { entry.list.push_back(document); } } entry.hash = qs(data.vhash); entry.received = getms(true); _session->data().notifyStickersUpdated(); }).send(); } if (it == _stickersByEmoji.end()) { _stickersByEmoji.emplace(emoji, StickersByEmoji()); } else if (it->second.received > 0) { return &it->second.list; } return nullptr; } void ApiWrap::requestStickers(TimeId now) { if (!_session->data().stickersUpdateNeeded(now) || _stickersUpdateRequest) { return; } auto onDone = [this](const MTPmessages_AllStickers &result) { _session->data().setLastStickersUpdate(getms(true)); _stickersUpdateRequest = 0; switch (result.type()) { case mtpc_messages_allStickersNotModified: return; case mtpc_messages_allStickers: { auto &d = result.c_messages_allStickers(); Stickers::SetsReceived(d.vsets.v, d.vhash.v); } return; default: Unexpected("Type in ApiWrap::stickersDone()"); } }; _stickersUpdateRequest = request(MTPmessages_GetAllStickers( MTP_int(Local::countStickersHash(true)) )).done(onDone).fail([=](const RPCError &error) { LOG(("App Fail: Failed to get stickers!")); onDone(MTP_messages_allStickersNotModified()); }).send(); } void ApiWrap::requestRecentStickers(TimeId now) { if (!_session->data().recentStickersUpdateNeeded(now)) { return; } requestRecentStickersWithHash(Local::countRecentStickersHash()); } void ApiWrap::requestRecentStickersWithHash(int32 hash) { if (_recentStickersUpdateRequest) { return; } _recentStickersUpdateRequest = request(MTPmessages_GetRecentStickers( MTP_flags(0), MTP_int(hash) )).done([=](const MTPmessages_RecentStickers &result) { _session->data().setLastRecentStickersUpdate(getms(true)); _recentStickersUpdateRequest = 0; switch (result.type()) { case mtpc_messages_recentStickersNotModified: return; case mtpc_messages_recentStickers: { auto &d = result.c_messages_recentStickers(); Stickers::SpecialSetReceived( Stickers::CloudRecentSetId, lang(lng_recent_stickers), d.vstickers.v, d.vhash.v, d.vpacks.v, d.vdates.v); } return; default: Unexpected("Type in ApiWrap::recentStickersDone()"); } }).fail([=](const RPCError &error) { _session->data().setLastRecentStickersUpdate(getms(true)); _recentStickersUpdateRequest = 0; LOG(("App Fail: Failed to get recent stickers!")); }).send(); } void ApiWrap::requestFavedStickers(TimeId now) { if (!_session->data().favedStickersUpdateNeeded(now) || _favedStickersUpdateRequest) { return; } _favedStickersUpdateRequest = request(MTPmessages_GetFavedStickers( MTP_int(Local::countFavedStickersHash()) )).done([=](const MTPmessages_FavedStickers &result) { _session->data().setLastFavedStickersUpdate(getms(true)); _favedStickersUpdateRequest = 0; switch (result.type()) { case mtpc_messages_favedStickersNotModified: return; case mtpc_messages_favedStickers: { auto &d = result.c_messages_favedStickers(); Stickers::SpecialSetReceived( Stickers::FavedSetId, Lang::Hard::FavedSetTitle(), d.vstickers.v, d.vhash.v, d.vpacks.v); } return; default: Unexpected("Type in ApiWrap::favedStickersDone()"); } }).fail([=](const RPCError &error) { _session->data().setLastFavedStickersUpdate(getms(true)); _favedStickersUpdateRequest = 0; LOG(("App Fail: Failed to get faved stickers!")); }).send(); } void ApiWrap::requestFeaturedStickers(TimeId now) { if (!_session->data().featuredStickersUpdateNeeded(now) || _featuredStickersUpdateRequest) { return; } _featuredStickersUpdateRequest = request(MTPmessages_GetFeaturedStickers( MTP_int(Local::countFeaturedStickersHash()) )).done([=](const MTPmessages_FeaturedStickers &result) { _session->data().setLastFeaturedStickersUpdate(getms(true)); _featuredStickersUpdateRequest = 0; switch (result.type()) { case mtpc_messages_featuredStickersNotModified: return; case mtpc_messages_featuredStickers: { auto &d = result.c_messages_featuredStickers(); Stickers::FeaturedSetsReceived(d.vsets.v, d.vunread.v, d.vhash.v); } return; default: Unexpected("Type in ApiWrap::featuredStickersDone()"); } }).fail([=](const RPCError &error) { _session->data().setLastFeaturedStickersUpdate(getms(true)); _featuredStickersUpdateRequest = 0; LOG(("App Fail: Failed to get featured stickers!")); }).send(); } void ApiWrap::requestSavedGifs(TimeId now) { if (!_session->data().savedGifsUpdateNeeded(now) || _savedGifsUpdateRequest) { return; } _savedGifsUpdateRequest = request(MTPmessages_GetSavedGifs( MTP_int(Local::countSavedGifsHash()) )).done([=](const MTPmessages_SavedGifs &result) { _session->data().setLastSavedGifsUpdate(getms(true)); _savedGifsUpdateRequest = 0; switch (result.type()) { case mtpc_messages_savedGifsNotModified: return; case mtpc_messages_savedGifs: { auto &d = result.c_messages_savedGifs(); Stickers::GifsReceived(d.vgifs.v, d.vhash.v); } return; default: Unexpected("Type in ApiWrap::savedGifsDone()"); } }).fail([=](const RPCError &error) { _session->data().setLastSavedGifsUpdate(getms(true)); _savedGifsUpdateRequest = 0; LOG(("App Fail: Failed to get saved gifs!")); }).send(); } void ApiWrap::readFeaturedSetDelayed(uint64 setId) { if (!_featuredSetsRead.contains(setId)) { _featuredSetsRead.insert(setId); _featuredSetsReadTimer.callOnce(kReadFeaturedSetsTimeout); } } void ApiWrap::readFeaturedSets() { auto &sets = _session->data().stickerSetsRef(); auto count = _session->data().featuredStickerSetsUnreadCount(); QVector wrappedIds; wrappedIds.reserve(_featuredSetsRead.size()); for (auto setId : _featuredSetsRead) { auto it = sets.find(setId); if (it != sets.cend()) { it->flags &= ~MTPDstickerSet_ClientFlag::f_unread; wrappedIds.append(MTP_long(setId)); if (count) { --count; } } } _featuredSetsRead.clear(); if (!wrappedIds.empty()) { auto requestData = MTPmessages_ReadFeaturedStickers( MTP_vector(wrappedIds)); request(std::move(requestData)).done([=](const MTPBool &result) { Local::writeFeaturedStickers(); _session->data().notifyStickersUpdated(); }).send(); _session->data().setFeaturedStickerSetsUnreadCount(count); } } void ApiWrap::parseChannelParticipants( not_null channel, const MTPchannels_ChannelParticipants &result, base::lambda &list)> callbackList, base::lambda callbackNotModified) { TLHelp::VisitChannelParticipants(result, base::overload([&]( const MTPDchannels_channelParticipants &data) { App::feedUsers(data.vusers); if (channel->mgInfo) { refreshChannelAdmins(channel, data.vparticipants.v); } if (callbackList) { callbackList(data.vcount.v, data.vparticipants.v); } }, [&](mtpTypeId) { if (callbackNotModified) { callbackNotModified(); } else { LOG(("API Error: channels.channelParticipantsNotModified received!")); } })); } void ApiWrap::refreshChannelAdmins( not_null channel, const QVector &participants) { Data::ChannelAdminChanges changes(channel); for (auto &p : participants) { const auto userId = TLHelp::ReadChannelParticipantUserId(p); const auto isAdmin = (p.type() == mtpc_channelParticipantAdmin) || (p.type() == mtpc_channelParticipantCreator); changes.feed(userId, isAdmin); } } void ApiWrap::parseRecentChannelParticipants( not_null channel, const MTPchannels_ChannelParticipants &result, base::lambda &list)> callbackList, base::lambda callbackNotModified) { parseChannelParticipants(channel, result, [&]( int availableCount, const QVector &list) { auto applyLast = channel->isMegagroup() && (channel->mgInfo->lastParticipants.size() <= list.size()); if (applyLast) { applyLastParticipantsList( channel, availableCount, list); } callbackList(availableCount, list); }, std::move(callbackNotModified)); } void ApiWrap::applyUpdatesNoPtsCheck(const MTPUpdates &updates) { switch (updates.type()) { case mtpc_updateShortMessage: { auto &d = updates.c_updateShortMessage(); auto flags = mtpCastFlags(d.vflags.v) | MTPDmessage::Flag::f_from_id; const auto peerUserId = d.is_out() ? d.vuser_id : MTP_int(_session->userId()); App::histories().addNewMessage( MTP_message( MTP_flags(flags), d.vid, d.is_out() ? MTP_int(_session->userId()) : d.vuser_id, MTP_peerUser(peerUserId), d.vfwd_from, d.vvia_bot_id, d.vreply_to_msg_id, d.vdate, d.vmessage, MTP_messageMediaEmpty(), MTPnullMarkup, d.has_entities() ? d.ventities : MTPnullEntities, MTPint(), MTPint(), MTPstring(), MTPlong()), NewMessageUnread); } break; case mtpc_updateShortChatMessage: { auto &d = updates.c_updateShortChatMessage(); auto flags = mtpCastFlags(d.vflags.v) | MTPDmessage::Flag::f_from_id; App::histories().addNewMessage( MTP_message( MTP_flags(flags), d.vid, d.vfrom_id, MTP_peerChat(d.vchat_id), d.vfwd_from, d.vvia_bot_id, d.vreply_to_msg_id, d.vdate, d.vmessage, MTP_messageMediaEmpty(), MTPnullMarkup, d.has_entities() ? d.ventities : MTPnullEntities, MTPint(), MTPint(), MTPstring(), MTPlong()), NewMessageUnread); } break; case mtpc_updateShortSentMessage: { auto &d = updates.c_updateShortSentMessage(); Q_UNUSED(d); // Sent message data was applied anyway. } break; default: Unexpected("Type in applyUpdatesNoPtsCheck()"); } } void ApiWrap::applyUpdateNoPtsCheck(const MTPUpdate &update) { switch (update.type()) { case mtpc_updateNewMessage: { auto &d = update.c_updateNewMessage(); auto needToAdd = true; if (d.vmessage.type() == mtpc_message) { // index forwarded messages to links _overview if (App::checkEntitiesAndViewsUpdate(d.vmessage.c_message())) { // already in blocks LOG(("Skipping message, because it is already in blocks!")); needToAdd = false; } } if (needToAdd) { App::histories().addNewMessage(d.vmessage, NewMessageUnread); } } break; case mtpc_updateReadMessagesContents: { auto &d = update.c_updateReadMessagesContents(); auto possiblyReadMentions = base::flat_set(); for (const auto &msgId : d.vmessages.v) { if (auto item = App::histItemById(NoChannel, msgId.v)) { if (item->isMediaUnread()) { item->markMediaRead(); _session->data().requestItemRepaint(item); if (item->out() && item->history()->peer->isUser()) { auto when = App::main()->requestingDifference() ? 0 : unixtime(); item->history()->peer->asUser()->madeAction(when); } } } else { // Perhaps it was an unread mention! possiblyReadMentions.insert(msgId.v); } } checkForUnreadMentions(possiblyReadMentions); } break; case mtpc_updateReadHistoryInbox: { auto &d = update.c_updateReadHistoryInbox(); App::feedInboxRead(peerFromMTP(d.vpeer), d.vmax_id.v); } break; case mtpc_updateReadHistoryOutbox: { auto &d = update.c_updateReadHistoryOutbox(); auto peerId = peerFromMTP(d.vpeer); auto when = App::main()->requestingDifference() ? 0 : unixtime(); App::feedOutboxRead(peerId, d.vmax_id.v, when); } break; case mtpc_updateWebPage: { auto &d = update.c_updateWebPage(); Q_UNUSED(d); // Web page was updated anyway. } break; case mtpc_updateDeleteMessages: { auto &d = update.c_updateDeleteMessages(); App::feedWereDeleted(NoChannel, d.vmessages.v); } break; case mtpc_updateNewChannelMessage: { auto &d = update.c_updateNewChannelMessage(); auto needToAdd = true; if (d.vmessage.type() == mtpc_message) { // index forwarded messages to links _overview if (App::checkEntitiesAndViewsUpdate(d.vmessage.c_message())) { // already in blocks LOG(("Skipping message, because it is already in blocks!")); needToAdd = false; } } if (needToAdd) { App::histories().addNewMessage(d.vmessage, NewMessageUnread); } } break; case mtpc_updateEditChannelMessage: { auto &d = update.c_updateEditChannelMessage(); App::updateEditedMessage(d.vmessage); } break; case mtpc_updateEditMessage: { auto &d = update.c_updateEditMessage(); App::updateEditedMessage(d.vmessage); } break; case mtpc_updateChannelWebPage: { auto &d = update.c_updateChannelWebPage(); Q_UNUSED(d); // Web page was updated anyway. } break; case mtpc_updateDeleteChannelMessages: { auto &d = update.c_updateDeleteChannelMessages(); App::feedWereDeleted(d.vchannel_id.v, d.vmessages.v); } break; default: Unexpected("Type in applyUpdateNoPtsCheck()"); } } void ApiWrap::jumpToDate(Dialogs::Key chat, const QDate &date) { if (const auto peer = chat.peer()) { jumpToHistoryDate(peer, date); } else if (const auto feed = chat.feed()) { jumpToFeedDate(feed, date); } } template void ApiWrap::requestMessageAfterDate( not_null peer, const QDate &date, Callback &&callback) { // API returns a message with date <= offset_date. // So we request a message with offset_date = desired_date - 1 and add_offset = -1. // This should give us the first message with date >= desired_date. auto offsetId = 0; auto offsetDate = static_cast(QDateTime(date).toTime_t()) - 1; auto addOffset = -1; auto limit = 1; auto maxId = 0; auto minId = 0; auto historyHash = 0; request(MTPmessages_GetHistory( peer->input, MTP_int(offsetId), MTP_int(offsetDate), MTP_int(addOffset), MTP_int(limit), MTP_int(maxId), MTP_int(minId), MTP_int(historyHash) )).done([ peer, offsetDate, callback = std::forward(callback) ](const MTPmessages_Messages &result) { auto getMessagesList = [&result, peer]() -> const QVector* { auto handleMessages = [](auto &messages) { App::feedUsers(messages.vusers); App::feedChats(messages.vchats); return &messages.vmessages.v; }; switch (result.type()) { case mtpc_messages_messages: return handleMessages(result.c_messages_messages()); case mtpc_messages_messagesSlice: return handleMessages(result.c_messages_messagesSlice()); case mtpc_messages_channelMessages: { auto &messages = result.c_messages_channelMessages(); if (peer && peer->isChannel()) { peer->asChannel()->ptsReceived(messages.vpts.v); } else { LOG(("API Error: received messages.channelMessages when no channel was passed! (ApiWrap::jumpToDate)")); } return handleMessages(messages); } break; case mtpc_messages_messagesNotModified: { LOG(("API Error: received messages.messagesNotModified! (ApiWrap::jumpToDate)")); } break; } return nullptr; }; if (auto list = getMessagesList()) { App::feedMsgs(*list, NewMessageExisting); for (auto &message : *list) { if (dateFromMessage(message) >= offsetDate) { callback(idFromMessage(message)); return; } } } callback(ShowAtUnreadMsgId); }).send(); } void ApiWrap::jumpToHistoryDate(not_null peer, const QDate &date) { if (const auto channel = peer->migrateTo()) { jumpToHistoryDate(channel, date); return; } const auto jumpToDateInPeer = [=] { requestMessageAfterDate(peer, date, [=](MsgId resultId) { Ui::showPeerHistory(peer, resultId); }); }; if (const auto chat = peer->migrateFrom()) { requestMessageAfterDate(chat, date, [=](MsgId resultId) { if (resultId) { Ui::showPeerHistory(chat, resultId); } else { jumpToDateInPeer(); } }); } else { jumpToDateInPeer(); } } template void ApiWrap::requestMessageAfterDate( not_null feed, const QDate &date, Callback &&callback) { const auto offsetId = 0; const auto offsetDate = static_cast(QDateTime(date).toTime_t()); const auto addOffset = -2; const auto limit = 1; const auto hash = 0; //request(MTPchannels_GetFeed( // #feed // MTP_flags(MTPchannels_GetFeed::Flag::f_offset_position), // MTP_int(feed->id()), // MTP_feedPosition( // MTP_int(offsetDate), // MTP_peerUser(MTP_int(_session->userId())), // MTP_int(0)), // MTP_int(addOffset), // MTP_int(limit), // MTPfeedPosition(), // max_id // MTPfeedPosition(), // min_id // MTP_int(hash) //)).done([ // =, // callback = std::forward(callback) //](const MTPmessages_FeedMessages &result) { // if (result.type() == mtpc_messages_feedMessagesNotModified) { // LOG(("API Error: " // "Unexpected messages.feedMessagesNotModified.")); // callback(Data::UnreadMessagePosition); // return; // } // Assert(result.type() == mtpc_messages_feedMessages); // const auto &data = result.c_messages_feedMessages(); // const auto &messages = data.vmessages.v; // const auto type = NewMessageExisting; // App::feedUsers(data.vusers); // App::feedChats(data.vchats); // for (const auto &msg : messages) { // if (const auto item = App::histories().addNewMessage(msg, type)) { // if (item->date() >= offsetDate || true) { // callback(item->position()); // return; // } // } // } // callback(Data::UnreadMessagePosition); //}).send(); } void ApiWrap::jumpToFeedDate(not_null feed, const QDate &date) { requestMessageAfterDate(feed, date, [=](Data::MessagePosition result) { Ui::hideLayer(); App::wnd()->controller()->showSection( HistoryFeed::Memento(feed, result)); }); } void ApiWrap::preloadEnoughUnreadMentions(not_null history) { auto fullCount = history->getUnreadMentionsCount(); auto loadedCount = history->getUnreadMentionsLoadedCount(); auto allLoaded = (fullCount >= 0) ? (loadedCount >= fullCount) : false; if (fullCount < 0 || loadedCount >= kUnreadMentionsPreloadIfLess || allLoaded) { return; } if (_unreadMentionsRequests.contains(history)) { return; } auto offsetId = loadedCount ? history->getMaxLoadedUnreadMention() : 1; auto limit = loadedCount ? kUnreadMentionsNextRequestLimit : kUnreadMentionsFirstRequestLimit; auto addOffset = loadedCount ? -(limit + 1) : -limit; auto maxId = 0; auto minId = 0; auto requestId = request(MTPmessages_GetUnreadMentions(history->peer->input, MTP_int(offsetId), MTP_int(addOffset), MTP_int(limit), MTP_int(maxId), MTP_int(minId))).done([this, history](const MTPmessages_Messages &result) { _unreadMentionsRequests.remove(history); history->addUnreadMentionsSlice(result); }).fail([this, history](const RPCError &error) { _unreadMentionsRequests.remove(history); }).send(); _unreadMentionsRequests.emplace(history, requestId); } void ApiWrap::checkForUnreadMentions( const base::flat_set &possiblyReadMentions, ChannelData *channel) { for (auto msgId : possiblyReadMentions) { requestMessageData(channel, msgId, [](ChannelData *channel, MsgId msgId) { if (auto item = App::histItemById(channel, msgId)) { if (item->mentionsMe()) { item->markMediaRead(); } } }); } } void ApiWrap::cancelEditChatAdmins(not_null chat) { _chatAdminsEnabledRequests.take( chat ) | requestCanceller(); _chatAdminsSaveRequests.take( chat ) | [&](auto &&requests) { ranges::for_each(std::move(requests), requestCanceller()); }; _chatAdminsToSave.remove(chat); } void ApiWrap::editChatAdmins( not_null chat, bool adminsEnabled, base::flat_set> &&admins) { cancelEditChatAdmins(chat); if (adminsEnabled) { _chatAdminsToSave.emplace(chat, std::move(admins)); } auto requestId = request(MTPmessages_ToggleChatAdmins(chat->inputChat, MTP_bool(adminsEnabled))).done([this, chat](const MTPUpdates &updates) { _chatAdminsEnabledRequests.remove(chat); applyUpdates(updates); saveChatAdmins(chat); }).fail([this, chat](const RPCError &error) { _chatAdminsEnabledRequests.remove(chat); if (error.type() == qstr("CHAT_NOT_MODIFIED")) { saveChatAdmins(chat); } }).send(); _chatAdminsEnabledRequests.emplace(chat, requestId); } void ApiWrap::saveChatAdmins(not_null chat) { if (!_chatAdminsToSave.contains(chat)) { return; } auto requestId = request(MTPmessages_GetFullChat(chat->inputChat)).done([this, chat](const MTPmessages_ChatFull &result) { _chatAdminsEnabledRequests.remove(chat); processFullPeer(chat, result); sendSaveChatAdminsRequests(chat); }).fail([this, chat](const RPCError &error) { _chatAdminsEnabledRequests.remove(chat); _chatAdminsToSave.remove(chat); }).send(); _chatAdminsEnabledRequests.emplace(chat, requestId); } void ApiWrap::sendSaveChatAdminsRequests(not_null chat) { auto editOne = [this, chat](not_null user, bool admin) { auto requestId = request(MTPmessages_EditChatAdmin( chat->inputChat, user->inputUser, MTP_bool(admin))) .done([this, chat, user, admin]( const MTPBool &result, mtpRequestId requestId) { _chatAdminsSaveRequests[chat].remove(requestId); if (_chatAdminsSaveRequests[chat].empty()) { _chatAdminsSaveRequests.remove(chat); Notify::peerUpdatedDelayed(chat, Notify::PeerUpdate::Flag::AdminsChanged); } if (mtpIsTrue(result)) { if (admin) { if (chat->noParticipantInfo()) { requestFullPeer(chat); } else { chat->admins.insert(user); } } else { chat->admins.remove(user); } } }).fail([this, chat]( const RPCError &error, mtpRequestId requestId) { _chatAdminsSaveRequests[chat].remove(requestId); if (_chatAdminsSaveRequests[chat].empty()) { _chatAdminsSaveRequests.remove(chat); } chat->invalidateParticipants(); if (error.type() == qstr("USER_RESTRICTED")) { Ui::show(Box(lang(lng_cant_do_this))); } }).afterDelay(kSmallDelayMs).send(); _chatAdminsSaveRequests[chat].insert(requestId); }; auto appointOne = [&](auto user) { editOne(user, true); }; auto removeOne = [&](auto user) { editOne(user, false); }; auto admins = _chatAdminsToSave.take(chat); Assert(!!admins); auto toRemove = chat->admins; auto toAppoint = std::vector>(); if (!admins->empty()) { toAppoint.reserve(admins->size()); for (auto user : *admins) { if (!toRemove.remove(user) && user->id != peerFromUser(chat->creator)) { toAppoint.push_back(user); } } } ranges::for_each(toRemove, removeOne); ranges::for_each(toAppoint, appointOne); requestSendDelayed(); } void ApiWrap::requestSharedMediaCount( not_null peer, Storage::SharedMediaType type) { requestSharedMedia(peer, type, 0, SliceType::Before); } void ApiWrap::requestSharedMedia( not_null peer, SharedMediaType type, MsgId messageId, SliceType slice) { auto key = std::make_tuple(peer, type, messageId, slice); if (_sharedMediaRequests.contains(key)) { return; } auto prepared = Api::PrepareSearchRequest( peer, type, QString(), messageId, slice); if (prepared.vfilter.type() == mtpc_inputMessagesFilterEmpty) { return; } auto requestId = request( std::move(prepared) ).done([this, peer, type, messageId, slice]( const MTPmessages_Messages &result) { auto key = std::make_tuple(peer, type, messageId, slice); _sharedMediaRequests.remove(key); sharedMediaDone(peer, type, messageId, slice, result); }).fail([this, key](const RPCError &error) { _sharedMediaRequests.remove(key); }).send(); _sharedMediaRequests.emplace(key, requestId); } void ApiWrap::sharedMediaDone( not_null peer, SharedMediaType type, MsgId messageId, SliceType slice, const MTPmessages_Messages &result) { auto parsed = Api::ParseSearchResult( peer, type, messageId, slice, result); _session->storage().add(Storage::SharedMediaAddSlice( peer->id, type, std::move(parsed.messageIds), parsed.noSkipRange, parsed.fullCount )); } void ApiWrap::requestUserPhotos( not_null user, PhotoId afterId) { if (_userPhotosRequests.contains(user)) { return; } auto limit = kSharedMediaLimit; auto requestId = request(MTPphotos_GetUserPhotos( user->inputUser, MTP_int(0), MTP_long(afterId), MTP_int(limit) )).done([this, user, afterId](const MTPphotos_Photos &result) { _userPhotosRequests.remove(user); userPhotosDone(user, afterId, result); }).fail([this, user](const RPCError &error) { _userPhotosRequests.remove(user); }).send(); _userPhotosRequests.emplace(user, requestId); } void ApiWrap::userPhotosDone( not_null user, PhotoId photoId, const MTPphotos_Photos &result) { auto fullCount = 0; auto &photos = *[&] { switch (result.type()) { case mtpc_photos_photos: { auto &d = result.c_photos_photos(); App::feedUsers(d.vusers); fullCount = d.vphotos.v.size(); return &d.vphotos.v; } break; case mtpc_photos_photosSlice: { auto &d = result.c_photos_photosSlice(); App::feedUsers(d.vusers); fullCount = d.vcount.v; return &d.vphotos.v; } break; } Unexpected("photos.Photos type in userPhotosDone()"); }(); auto photoIds = std::vector(); photoIds.reserve(photos.size()); for (auto &photo : photos) { if (auto photoData = _session->data().photo(photo)) { photoIds.push_back(photoData->id); } } _session->storage().add(Storage::UserPhotosAddSlice( user->id, std::move(photoIds), fullCount )); } // #feed //void ApiWrap::requestFeedChannels(not_null feed) { // if (_feedChannelsGetRequests.contains(feed)) { // return; // } // const auto hash = feed->channelsHash(); // request(MTPchannels_GetFeedSources( // MTP_flags(MTPchannels_GetFeedSources::Flag::f_feed_id), // MTP_int(feed->id()), // MTP_int(hash) // )).done([=](const MTPchannels_FeedSources &result) { // _feedChannelsGetRequests.remove(feed); // // switch (result.type()) { // case mtpc_channels_feedSourcesNotModified: // if (feed->channelsHash() == hash) { // feedChannelsDone(feed); // } else { // requestFeedChannels(feed); // } // break; // // case mtpc_channels_feedSources: { // const auto &data = result.c_channels_feedSources(); // applyFeedSources(data); // if (feed->channelsLoaded()) { // feedChannelsDone(feed); // } else { // LOG(("API Error: feed channels not received for " // ).arg(feed->id())); // } // } break; // // default: Unexpected("Type in channels.getFeedSources response."); // } // }).fail([=](const RPCError &error) { // _feedChannelsGetRequests.remove(feed); // }).send(); // _feedChannelsGetRequests.emplace(feed); //} // //void ApiWrap::applyFeedSources(const MTPDchannels_feedSources &data) { // // First we set channels without reading them from data. // // This allows us to apply them all at once without registering // // them one by one. // for (const auto &broadcasts : data.vfeeds.v) { // if (broadcasts.type() == mtpc_feedBroadcasts) { // const auto &list = broadcasts.c_feedBroadcasts(); // const auto feedId = list.vfeed_id.v; // const auto feed = _session->data().feed(feedId); // auto channels = std::vector>(); // for (const auto &channelId : list.vchannels.v) { // channels.push_back(App::channel(channelId.v)); // } // feed->setChannels(std::move(channels)); // } // } // // App::feedUsers(data.vusers); // App::feedChats(data.vchats); // // if (data.has_newly_joined_feed()) { // _session->data().setDefaultFeedId( // data.vnewly_joined_feed.v); // } //} // //void ApiWrap::setFeedChannels( // not_null feed, // const std::vector> &channels) { // if (const auto already = _feedChannelsSetRequests.take(feed)) { // request(*already).cancel(); // } // auto inputs = QVector(); // inputs.reserve(channels.size()); // for (const auto channel : channels) { // inputs.push_back(channel->inputChannel); // } // const auto requestId = request(MTPchannels_SetFeedBroadcasts( // MTP_flags(MTPchannels_SetFeedBroadcasts::Flag::f_channels), // MTP_int(feed->id()), // MTP_vector(inputs), // MTPbool() // )).done([=](const MTPUpdates &result) { // applyUpdates(result); // // _feedChannelsSetRequests.remove(feed); // }).fail([=](const RPCError &error) { // _feedChannelsSetRequests.remove(feed); // }).send(); // //} // //void ApiWrap::feedChannelsDone(not_null feed) { // feed->setChannelsLoaded(true); // for (const auto key : base::take(_feedMessagesRequestsPending)) { // std::apply( // [=](auto&&...args) { requestFeedMessages(args...); }, // key); // } //} // //void ApiWrap::requestFeedMessages( // not_null feed, // Data::MessagePosition messageId, // SliceType slice) { // const auto key = std::make_tuple(feed, messageId, slice); // if (_feedMessagesRequests.contains(key) // || _feedMessagesRequestsPending.contains(key)) { // return; // } // // if (!feed->channelsLoaded()) { // _feedMessagesRequestsPending.emplace(key); // requestFeedChannels(feed); // return; // } // // // We request messages with overlapping and skip overlapped in response. // const auto limit = kFeedMessagesLimit; // const auto addOffset = [&] { // switch (slice) { // case SliceType::Before: return -2; // case SliceType::Around: return -limit / 2; // case SliceType::After: return 1 - limit; // } // Unexpected("Direction in PrepareSearchRequest"); // }(); // const auto hash = int32(0); // const auto flags = (messageId && messageId.fullId.channel) // ? MTPchannels_GetFeed::Flag::f_offset_position // : MTPchannels_GetFeed::Flag::f_offset_to_max_read; // const auto requestId = request(MTPchannels_GetFeed( // MTP_flags(flags), // MTP_int(feed->id()), // MTP_feedPosition( // MTP_int(messageId.date), // MTP_peerChannel(MTP_int(messageId.fullId.channel)), // MTP_int(messageId.fullId.msg)), // MTP_int(addOffset), // MTP_int(limit), // MTPFeedPosition(), // MTPFeedPosition(), // MTP_int(hash) // )).done([=](const MTPmessages_FeedMessages &result) { // const auto key = std::make_tuple(feed, messageId, slice); // _feedMessagesRequests.remove(key); // feedMessagesDone(feed, messageId, slice, result); // }).fail([=](const RPCError &error) { // _feedMessagesRequests.remove(key); // if (error.type() == qstr("SOURCES_HASH_INVALID")) { // _feedMessagesRequestsPending.emplace(key); // requestFeedChannels(feed); // } // }).send(); // _feedMessagesRequests.emplace(key); //} // //void ApiWrap::feedMessagesDone( // not_null feed, // Data::MessagePosition messageId, // SliceType slice, // const MTPmessages_FeedMessages &result) { // if (result.type() == mtpc_messages_feedMessagesNotModified) { // LOG(("API Error: Unexpected messages.feedMessagesNotModified.")); // _session->storage().add(Storage::FeedMessagesAddSlice( // feed->id(), // std::vector(), // Data::FullMessagesRange)); // return; // } // Assert(result.type() == mtpc_messages_feedMessages); // const auto &data = result.c_messages_feedMessages(); // const auto &messages = data.vmessages.v; // const auto type = NewMessageExisting; // // auto ids = std::vector(); // auto noSkipRange = Data::MessagesRange(messageId, messageId); // const auto accumulateFrom = [](auto &from, const auto &candidate) { // if (!from || from > candidate) { // from = candidate; // } // }; // const auto accumulateTill = [](auto &till, const auto &candidate) { // if (!till || till < candidate) { // till = candidate; // } // }; // const auto tooLargePosition = [&](const auto &position) { // return (slice == SliceType::Before) && !(position < messageId); // }; // const auto tooSmallPosition = [&](const auto &position) { // return (slice == SliceType::After) && !(messageId < position); // }; // App::feedUsers(data.vusers); // App::feedChats(data.vchats); // if (!messages.empty()) { // ids.reserve(messages.size()); // for (const auto &msg : messages) { // if (const auto item = App::histories().addNewMessage(msg, type)) { // const auto position = item->position(); // if (tooLargePosition(position)) { // accumulateTill(noSkipRange.till, position); // continue; // } else if (tooSmallPosition(position)) { // accumulateFrom(noSkipRange.from, position); // continue; // } // ids.push_back(position); // accumulateFrom(noSkipRange.from, position); // accumulateTill(noSkipRange.till, position); // } // } // ranges::reverse(ids); // } // if (data.has_min_position() && !ids.empty()) { // accumulateFrom( // noSkipRange.from, // Data::FeedPositionFromMTP(data.vmin_position)); // } else if (slice == SliceType::Before) { // noSkipRange.from = Data::MinMessagePosition; // } // if (data.has_max_position() && !ids.empty()) { // accumulateTill( // noSkipRange.till, // Data::FeedPositionFromMTP(data.vmax_position)); // } else if (slice == SliceType::After) { // noSkipRange.till = Data::MaxMessagePosition; // } // // const auto unreadPosition = [&] { // if (data.has_read_max_position()) { // return Data::FeedPositionFromMTP(data.vread_max_position); // } else if (!messageId) { // const auto result = ids.empty() // ? noSkipRange.till // : ids.back(); // return Data::MessagePosition( // result.date, // FullMsgId(result.fullId.channel, result.fullId.msg - 1)); // } // return Data::MessagePosition(); // }(); // // _session->storage().add(Storage::FeedMessagesAddSlice( // feed->id(), // std::move(ids), // noSkipRange)); // // if (unreadPosition) { // feed->setUnreadPosition(unreadPosition); // } //} // //void ApiWrap::saveDefaultFeedId(FeedId id, bool isDefaultFeedId) { // if (const auto already = base::take(_saveDefaultFeedIdRequest)) { // request(already).cancel(); // } // _saveDefaultFeedIdRequest = request(MTPchannels_SetFeedBroadcasts( // MTP_flags(MTPchannels_SetFeedBroadcasts::Flag::f_also_newly_joined), // MTP_int(id), // MTPVector(), // MTP_bool(isDefaultFeedId) // )).send(); //} void ApiWrap::sendAction(const SendOptions &options) { readServerHistory(options.history); options.history->getReadyFor(ShowAtTheEndMsgId); _sendActions.fire_copy(options); } void ApiWrap::forwardMessages( HistoryItemsList &&items, const SendOptions &options, base::lambda_once &&successCallback) { Expects(!items.empty()); struct SharedCallback { int requestsLeft = 0; base::lambda_once callback; }; const auto shared = successCallback ? std::make_shared() : std::shared_ptr(); if (successCallback) { shared->callback = std::move(successCallback); } const auto count = int(items.size()); const auto genClientSideMessage = options.generateLocal && (count < 2); const auto history = options.history; const auto peer = history->peer; readServerHistory(history); const auto channelPost = peer->isChannel() && !peer->isMegagroup(); const auto silentPost = channelPost && peer->notifySilentPosts(); auto flags = MTPDmessage::Flags(0); auto sendFlags = MTPmessages_ForwardMessages::Flags(0); if (channelPost) { flags |= MTPDmessage::Flag::f_views; flags |= MTPDmessage::Flag::f_post; } if (!channelPost) { flags |= MTPDmessage::Flag::f_from_id; } else if (peer->asChannel()->addsSignature()) { flags |= MTPDmessage::Flag::f_post_author; } if (silentPost) { sendFlags |= MTPmessages_ForwardMessages::Flag::f_silent; } auto forwardFrom = items.front()->history()->peer; auto currentGroupId = items.front()->groupId(); auto ids = QVector(); auto randomIds = QVector(); const auto sendAccumulated = [&] { if (shared) { ++shared->requestsLeft; } const auto finalFlags = sendFlags | (currentGroupId == MessageGroupId() ? MTPmessages_ForwardMessages::Flag(0) : MTPmessages_ForwardMessages::Flag::f_grouped); history->sendRequestId = request(MTPmessages_ForwardMessages( MTP_flags(finalFlags), forwardFrom->input, MTP_vector(ids), MTP_vector(randomIds), peer->input )).done([=, callback = std::move(successCallback)]( const MTPUpdates &updates) { applyUpdates(updates); if (shared && !--shared->requestsLeft) { shared->callback(); } }).afterRequest( history->sendRequestId ).send(); ids.resize(0); randomIds.resize(0); }; ids.reserve(count); randomIds.reserve(count); for (const auto item : items) { auto randomId = rand_value(); if (genClientSideMessage) { if (auto message = item->toHistoryMessage()) { const auto newId = FullMsgId( peerToChannel(peer->id), clientMsgId()); const auto self = _session->user(); const auto messageFromId = channelPost ? UserId(0) : peerToUser(self->id); const auto messagePostAuthor = channelPost ? (self->firstName + ' ' + self->lastName) : QString(); history->addNewForwarded( newId.msg, flags, unixtime(), messageFromId, messagePostAuthor, message); App::historyRegRandom(randomId, newId); } } const auto newFrom = item->history()->peer; const auto newGroupId = item->groupId(); if (forwardFrom != newFrom || currentGroupId != newGroupId) { sendAccumulated(); forwardFrom = newFrom; currentGroupId = newGroupId; } ids.push_back(MTP_int(item->id)); randomIds.push_back(MTP_long(randomId)); } sendAccumulated(); _session->data().sendHistoryChangeNotifications(); } void ApiWrap::shareContact( const QString &phone, const QString &firstName, const QString &lastName, const SendOptions &options) { const auto userId = UserId(0); sendSharedContact(phone, firstName, lastName, userId, options); } void ApiWrap::shareContact( not_null user, const SendOptions &options) { const auto userId = peerToUser(user->id); const auto phone = _session->data().findContactPhone(user); if (phone.isEmpty()) { return; } sendSharedContact( phone, user->firstName, user->lastName, userId, options); } void ApiWrap::sendSharedContact( const QString &phone, const QString &firstName, const QString &lastName, UserId userId, const SendOptions &options) { sendAction(options); const auto history = options.history; const auto peer = history->peer; const auto newId = FullMsgId(history->channelId(), clientMsgId()); const auto channelPost = peer->isChannel() && !peer->isMegagroup(); auto flags = NewMessageFlags(peer) | MTPDmessage::Flag::f_media; if (options.replyTo) { flags |= MTPDmessage::Flag::f_reply_to_msg_id; } if (channelPost) { flags |= MTPDmessage::Flag::f_views; flags |= MTPDmessage::Flag::f_post; if (peer->asChannel()->addsSignature()) { flags |= MTPDmessage::Flag::f_post_author; } } else { flags |= MTPDmessage::Flag::f_from_id; } const auto messageFromId = channelPost ? 0 : _session->userId(); const auto messagePostAuthor = channelPost ? (_session->user()->firstName + ' ' + _session->user()->lastName) : QString(); const auto item = history->addNewMessage( MTP_message( MTP_flags(flags), MTP_int(newId.msg), MTP_int(messageFromId), peerToMTP(peer->id), MTPnullFwdHeader, MTPint(), MTP_int(options.replyTo), MTP_int(unixtime()), MTP_string(""), MTP_messageMediaContact( MTP_string(phone), MTP_string(firstName), MTP_string(lastName), MTP_int(userId)), MTPnullMarkup, MTPnullEntities, MTP_int(1), MTPint(), MTP_string(messagePostAuthor), MTPlong()), NewMessageUnread); const auto media = MTP_inputMediaContact( MTP_string(phone), MTP_string(firstName), MTP_string(lastName)); sendMedia(item, media, peer->notifySilentPosts()); if (const auto main = App::main()) { _session->data().sendHistoryChangeNotifications(); main->historyToDown(history); main->dialogsToUp(); } } void ApiWrap::sendVoiceMessage( QByteArray result, VoiceWaveform waveform, int duration, const SendOptions &options) { const auto caption = QString(); const auto to = FileLoadTaskOptions(options); _fileLoader->addTask(std::make_unique( result, duration, waveform, to, caption)); } void ApiWrap::sendFiles( Storage::PreparedList &&list, SendMediaType type, QString caption, std::shared_ptr album, const SendOptions &options) { if (list.files.size() > 1 && !caption.isEmpty()) { auto message = MainWidget::MessageToSend(options.history); message.textWithTags = { caption, TextWithTags::Tags() }; message.replyTo = options.replyTo; message.clearDraft = false; App::main()->sendMessage(message); caption = QString(); } const auto to = FileLoadTaskOptions(options); if (album) { album->silent = to.silent; } auto tasks = std::vector>(); tasks.reserve(list.files.size()); for (auto &file : list.files) { if (album) { switch (file.type) { case Storage::PreparedFile::AlbumType::Photo: type = SendMediaType::Photo; break; case Storage::PreparedFile::AlbumType::Video: type = SendMediaType::File; break; default: Unexpected("AlbumType in uploadFilesAfterConfirmation"); } } tasks.push_back(std::make_unique( file.path, file.content, std::move(file.information), type, to, caption, album)); } if (album) { _sendingAlbums.emplace(album->groupId, album); album->items.reserve(tasks.size()); for (const auto &task : tasks) { album->items.push_back(SendingAlbum::Item(task->id())); } } _fileLoader->addTasks(std::move(tasks)); } void ApiWrap::sendFile( const QByteArray &fileContent, SendMediaType type, const SendOptions &options) { auto to = FileLoadTaskOptions(options); auto caption = QString(); _fileLoader->addTask(std::make_unique( QString(), fileContent, nullptr, type, to, caption)); } void ApiWrap::sendUploadedPhoto( FullMsgId localId, const MTPInputFile &file, bool silent) { if (const auto item = App::histItemById(localId)) { const auto media = MTP_inputMediaUploadedPhoto( MTP_flags(0), file, MTPVector(), MTP_int(0)); if (const auto groupId = item->groupId()) { uploadAlbumMedia(item, groupId, media); } else { sendMedia(item, media, silent); } } } void ApiWrap::sendUploadedDocument( FullMsgId localId, const MTPInputFile &file, const base::optional &thumb, bool silent) { if (const auto item = App::histItemById(localId)) { auto media = item->media(); if (auto document = media ? media->document() : nullptr) { const auto groupId = item->groupId(); const auto flags = MTPDinputMediaUploadedDocument::Flags(0) | (thumb ? MTPDinputMediaUploadedDocument::Flag::f_thumb : MTPDinputMediaUploadedDocument::Flag(0)) | (groupId ? MTPDinputMediaUploadedDocument::Flag::f_nosound_video : MTPDinputMediaUploadedDocument::Flag(0)); const auto media = MTP_inputMediaUploadedDocument( MTP_flags(flags), file, thumb ? *thumb : MTPInputFile(), MTP_string(document->mimeString()), ComposeSendingDocumentAttributes(document), MTPVector(), MTP_int(0)); if (groupId) { uploadAlbumMedia(item, groupId, media); } else { sendMedia(item, media, silent); } } } } void ApiWrap::cancelLocalItem(not_null item) { Expects(!IsServerMsgId(item->id)); if (const auto groupId = item->groupId()) { sendAlbumWithCancelled(item, groupId); } } void ApiWrap::uploadAlbumMedia( not_null item, const MessageGroupId &groupId, const MTPInputMedia &media) { const auto localId = item->fullId(); const auto failed = [=] { }; request(MTPmessages_UploadMedia( item->history()->peer->input, media )).done([=](const MTPMessageMedia &result) { const auto item = App::histItemById(localId); if (!item) { failed(); } if (const auto media = item->media()) { if (const auto photo = media->photo()) { photo->setWaitingForAlbum(); } else if (const auto document = media->document()) { document->setWaitingForAlbum(); } } switch (result.type()) { case mtpc_messageMediaPhoto: { const auto &data = result.c_messageMediaPhoto(); if (data.vphoto.type() != mtpc_photo) { failed(); return; } const auto &photo = data.vphoto.c_photo(); const auto flags = MTPDinputMediaPhoto::Flags(0) | (data.has_ttl_seconds() ? MTPDinputMediaPhoto::Flag::f_ttl_seconds : MTPDinputMediaPhoto::Flag(0)); const auto media = MTP_inputMediaPhoto( MTP_flags(flags), MTP_inputPhoto(photo.vid, photo.vaccess_hash), data.has_ttl_seconds() ? data.vttl_seconds : MTPint()); sendAlbumWithUploaded(item, groupId, media); } break; case mtpc_messageMediaDocument: { const auto &data = result.c_messageMediaDocument(); if (data.vdocument.type() != mtpc_document) { failed(); return; } const auto &document = data.vdocument.c_document(); const auto flags = MTPDinputMediaDocument::Flags(0) | (data.has_ttl_seconds() ? MTPDinputMediaDocument::Flag::f_ttl_seconds : MTPDinputMediaDocument::Flag(0)); const auto media = MTP_inputMediaDocument( MTP_flags(flags), MTP_inputDocument(document.vid, document.vaccess_hash), data.has_ttl_seconds() ? data.vttl_seconds : MTPint()); sendAlbumWithUploaded(item, groupId, media); } break; } }).fail([=](const RPCError &error) { failed(); }).send(); } void ApiWrap::sendMedia( not_null item, const MTPInputMedia &media, bool silent) { const auto randomId = rand_value(); App::historyRegRandom(randomId, item->fullId()); sendMediaWithRandomId(item, media, silent, randomId); } void ApiWrap::sendMediaWithRandomId( not_null item, const MTPInputMedia &media, bool silent, uint64 randomId) { const auto history = item->history(); const auto replyTo = item->replyToId(); auto caption = item->originalText(); TextUtilities::Trim(caption); auto sentEntities = TextUtilities::EntitiesToMTP( caption.entities, TextUtilities::ConvertOption::SkipLocal); const auto flags = MTPmessages_SendMedia::Flags(0) | (replyTo ? MTPmessages_SendMedia::Flag::f_reply_to_msg_id : MTPmessages_SendMedia::Flag(0)) | (IsSilentPost(item, silent) ? MTPmessages_SendMedia::Flag::f_silent : MTPmessages_SendMedia::Flag(0)) | (!sentEntities.v.isEmpty() ? MTPmessages_SendMedia::Flag::f_entities : MTPmessages_SendMedia::Flag(0)); history->sendRequestId = request(MTPmessages_SendMedia( MTP_flags(flags), history->peer->input, MTP_int(replyTo), media, MTP_string(caption.text), MTP_long(randomId), MTPnullMarkup, sentEntities )).done([=](const MTPUpdates &result) { applyUpdates(result); }).fail([=](const RPCError &error) { sendMessageFail(error); }).afterRequest(history->sendRequestId ).send(); } void ApiWrap::sendAlbumWithUploaded( not_null item, const MessageGroupId &groupId, const MTPInputMedia &media) { const auto localId = item->fullId(); const auto randomId = rand_value(); App::historyRegRandom(randomId, localId); const auto albumIt = _sendingAlbums.find(groupId.raw()); Assert(albumIt != _sendingAlbums.end()); const auto &album = albumIt->second; const auto proj = [](const SendingAlbum::Item &item) { return item.msgId; }; const auto itemIt = ranges::find(album->items, localId, proj); Assert(itemIt != album->items.end()); Assert(!itemIt->media); auto caption = item->originalText(); TextUtilities::Trim(caption); auto sentEntities = TextUtilities::EntitiesToMTP( caption.entities, TextUtilities::ConvertOption::SkipLocal); const auto flags = !sentEntities.v.isEmpty() ? MTPDinputSingleMedia::Flag::f_entities : MTPDinputSingleMedia::Flag(0); itemIt->media = MTP_inputSingleMedia( MTP_flags(flags), media, MTP_long(randomId), MTP_string(caption.text), sentEntities); sendAlbumIfReady(album.get()); } void ApiWrap::sendAlbumWithCancelled( not_null item, const MessageGroupId &groupId) { const auto localId = item->fullId(); const auto albumIt = _sendingAlbums.find(groupId.raw()); if (albumIt == _sendingAlbums.end()) { // Sometimes we destroy item being sent already after the album // was sent successfully. For example the message could be loaded // from server (by messages.getHistory or updateNewMessage) and // added to history and after that updateMessageID was received with // the same message id, in this case we destroy a detached local // item and sendAlbumWithCancelled is called for already sent album. return; } const auto &album = albumIt->second; const auto proj = [](const SendingAlbum::Item &item) { return item.msgId; }; const auto itemIt = ranges::find(album->items, localId, proj); Assert(itemIt != album->items.end()); album->items.erase(itemIt); sendAlbumIfReady(album.get()); } void ApiWrap::sendAlbumIfReady(not_null album) { const auto groupId = album->groupId; if (album->items.empty()) { _sendingAlbums.remove(groupId); return; } auto sample = (HistoryItem*)nullptr; auto medias = QVector(); medias.reserve(album->items.size()); for (const auto &item : album->items) { if (!item.media) { return; } else if (!sample) { sample = App::histItemById(item.msgId); } medias.push_back(*item.media); } if (!sample) { _sendingAlbums.remove(groupId); return; } else if (medias.size() < 2) { const auto &single = medias.front().c_inputSingleMedia(); sendMediaWithRandomId( sample, single.vmedia, album->silent, single.vrandom_id.v); _sendingAlbums.remove(groupId); return; } const auto history = sample->history(); const auto replyTo = sample->replyToId(); const auto flags = MTPmessages_SendMultiMedia::Flags(0) | (replyTo ? MTPmessages_SendMultiMedia::Flag::f_reply_to_msg_id : MTPmessages_SendMultiMedia::Flag(0)) | (IsSilentPost(sample, album->silent) ? MTPmessages_SendMultiMedia::Flag::f_silent : MTPmessages_SendMultiMedia::Flag(0)); history->sendRequestId = request(MTPmessages_SendMultiMedia( MTP_flags(flags), history->peer->input, MTP_int(replyTo), MTP_vector(medias) )).done([=](const MTPUpdates &result) { _sendingAlbums.remove(groupId); applyUpdates(result); }).fail([=](const RPCError &error) { _sendingAlbums.remove(groupId); sendMessageFail(error); }).afterRequest(history->sendRequestId ).send(); } void ApiWrap::readServerHistory(not_null history) { if (history->unreadCount()) { readServerHistoryForce(history); } } void ApiWrap::readServerHistoryForce(not_null history) { const auto peer = history->peer; const auto upTo = history->readInbox(); if (!upTo) { return; } if (const auto channel = peer->asChannel()) { if (!channel->amIn()) { return; // no read request for channels that I didn't join } else if (const auto migrateFrom = channel->migrateFrom()) { if (const auto migrated = App::historyLoaded(migrateFrom)) { readServerHistory(migrated); } } } if (_readRequests.contains(peer)) { const auto i = _readRequestsPending.find(peer); if (i == _readRequestsPending.cend()) { _readRequestsPending.emplace(peer, upTo); } else if (i->second < upTo) { i->second = upTo; } } else { sendReadRequest(peer, upTo); } } void ApiWrap::readFeed( not_null feed, Data::MessagePosition position) { const auto already = feed->unreadPosition(); if (already && already >= position) { return; } feed->setUnreadPosition(position); if (!_feedReadsDelayed.contains(feed)) { if (_feedReadsDelayed.empty()) { _feedReadTimer.callOnce(kFeedReadTimeout); } _feedReadsDelayed.emplace(feed, getms(true) + kFeedReadTimeout); } } void ApiWrap::readFeeds() { auto delay = kFeedReadTimeout; const auto now = getms(true); //for (auto i = begin(_feedReadsDelayed); i != end(_feedReadsDelayed);) { // #feed // const auto feed = i->first; // const auto time = i->second; // // Clang fails to capture structure-binded feed to lambda :( // //const auto [feed, time] = *i; // if (time > now) { // accumulate_min(delay, time - now); // ++i; // } else if (_feedReadRequests.contains(feed)) { // ++i; // } else { // const auto position = feed->unreadPosition(); // const auto requestId = request(MTPchannels_ReadFeed( // MTP_int(feed->id()), // MTP_feedPosition( // MTP_int(position.date), // MTP_peerChannel(MTP_int(position.fullId.channel)), // MTP_int(position.fullId.msg)) // )).done([=](const MTPUpdates &result) { // applyUpdates(result); // _feedReadRequests.remove(feed); // }).fail([=](const RPCError &error) { // _feedReadRequests.remove(feed); // }).send(); // _feedReadRequests.emplace(feed, requestId); // i = _feedReadsDelayed.erase(i); // } //} //if (!_feedReadsDelayed.empty()) { // _feedReadTimer.callOnce(delay); //} } void ApiWrap::sendReadRequest(not_null peer, MsgId upTo) { const auto requestId = [&] { const auto finished = [=] { _readRequests.remove(peer); if (const auto next = _readRequestsPending.take(peer)) { sendReadRequest(peer, *next); } }; if (const auto channel = peer->asChannel()) { return request(MTPchannels_ReadHistory( channel->inputChannel, MTP_int(upTo) )).done([=](const MTPBool &result) { finished(); }).fail([=](const RPCError &error) { finished(); }).send(); } return request(MTPmessages_ReadHistory( peer->input, MTP_int(upTo) )).done([=](const MTPmessages_AffectedMessages &result) { applyAffectedMessages(peer, result); finished(); }).fail([=](const RPCError &error) { finished(); }).send(); }(); _readRequests.emplace(peer, requestId, upTo); } ApiWrap::~ApiWrap() = default; tdesktop-1.2.17/Telegram/SourceFiles/apiwrap.h000066400000000000000000000417331326245125100212470ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include #include "base/timer.h" #include "base/flat_map.h" #include "base/flat_set.h" #include "core/single_timer.h" #include "mtproto/sender.h" #include "chat_helpers/stickers.h" #include "data/data_messages.h" class TaskQueue; class AuthSession; struct MessageGroupId; struct SendingAlbum; enum class SendMediaType; namespace Storage { enum class SharedMediaType : signed char; struct PreparedList; } // namespace Storage namespace Dialogs { class Key; } // namespace Dialogs namespace Api { inline const MTPVector *getChatsFromMessagesChats(const MTPmessages_Chats &chats) { switch (chats.type()) { case mtpc_messages_chats: return &chats.c_messages_chats().vchats; case mtpc_messages_chatsSlice: return &chats.c_messages_chatsSlice().vchats; } return nullptr; } template inline int32 CountHash(IntRange &&range) { uint32 acc = 0; for (auto value : range) { acc += (acc * 20261) + uint32(value); } return int32(acc & 0x7FFFFFFF); } } // namespace Api class ApiWrap : private MTP::Sender, private base::Subscriber { public: ApiWrap(not_null session); void applyUpdates(const MTPUpdates &updates, uint64 sentMessageRandomId = 0); void savePinnedOrder(); //void toggleChannelGrouping( // #feed // not_null channel, // bool group, // base::lambda callback); //void ungroupAllFromFeed(not_null feed); using RequestMessageDataCallback = base::lambda; void requestMessageData( ChannelData *channel, MsgId msgId, RequestMessageDataCallback callback); void requestContacts(); void requestDialogEntry(not_null feed); //void requestFeedDialogsEntries(not_null feed); void requestDialogEntry(not_null history); //void applyFeedSources(const MTPDchannels_feedSources &data); // #feed //void setFeedChannels( // not_null feed, // const std::vector> &channels); void requestFullPeer(PeerData *peer); void requestPeer(PeerData *peer); void requestPeers(const QList &peers); void requestLastParticipants(not_null channel); void requestBots(not_null channel); void requestAdmins(not_null channel); void requestParticipantsCountDelayed(not_null channel); void requestChannelRangeDifference(not_null history); void requestChangelog( const QString &sinceVersion, base::lambda callback); void requestChannelMembersForAdd( not_null channel, base::lambda callback); void processFullPeer(PeerData *peer, const MTPmessages_ChatFull &result); void processFullPeer(UserData *user, const MTPUserFull &result); void markMediaRead(const base::flat_set> &items); void markMediaRead(not_null item); void requestSelfParticipant(ChannelData *channel); void kickParticipant(not_null chat, not_null user); void kickParticipant( not_null channel, not_null user, const MTPChannelBannedRights ¤tRights); void unblockParticipant( not_null channel, not_null user); void deleteAllFromUser( not_null channel, not_null from); void requestWebPageDelayed(WebPageData *page); void clearWebPageRequest(WebPageData *page); void clearWebPageRequests(); void scheduleStickerSetRequest(uint64 setId, uint64 access); void requestStickerSets(); void saveStickerSets( const Stickers::Order &localOrder, const Stickers::Order &localRemoved); void updateStickers(); void requestRecentStickersForce(); void setGroupStickerSet( not_null megagroup, const MTPInputStickerSet &set); std::vector> *stickersByEmoji( not_null emoji); void joinChannel(not_null channel); void leaveChannel(not_null channel); void blockUser(UserData *user); void unblockUser(UserData *user); void exportInviteLink(PeerData *peer); void requestNotifySetting(PeerData *peer); void saveDraftToCloudDelayed(History *history); void savePrivacy(const MTPInputPrivacyKey &key, QVector &&rules); void handlePrivacyChange(mtpTypeId keyTypeId, const MTPVector &rules); int onlineTillFromStatus(const MTPUserStatus &status, int currentOnlineTill); void clearHistory(not_null peer); base::Observable &fullPeerUpdated() { return _fullPeerUpdated; } bool isQuitPrevent(); void applyUpdatesNoPtsCheck(const MTPUpdates &updates); void applyUpdateNoPtsCheck(const MTPUpdate &update); void jumpToDate(Dialogs::Key chat, const QDate &date); void preloadEnoughUnreadMentions(not_null history); void checkForUnreadMentions(const base::flat_set &possiblyReadMentions, ChannelData *channel = nullptr); void editChatAdmins( not_null chat, bool adminsEnabled, base::flat_set> &&admins); using SliceType = Data::LoadDirection; void requestSharedMedia( not_null peer, Storage::SharedMediaType type, MsgId messageId, SliceType slice); void requestSharedMediaCount( not_null peer, Storage::SharedMediaType type); void requestUserPhotos( not_null user, PhotoId afterId); //void requestFeedChannels( // #feed // not_null feed); //void requestFeedMessages( // not_null feed, // Data::MessagePosition messageId, // SliceType slice); //void saveDefaultFeedId(FeedId id, bool isDefaultFeedId); void stickerSetInstalled(uint64 setId) { _stickerSetInstalled.fire_copy(setId); } auto stickerSetInstalled() const { return _stickerSetInstalled.events(); } void readFeaturedSetDelayed(uint64 setId); void parseChannelParticipants( not_null channel, const MTPchannels_ChannelParticipants &result, base::lambda &list)> callbackList, base::lambda callbackNotModified = nullptr); void parseRecentChannelParticipants( not_null channel, const MTPchannels_ChannelParticipants &result, base::lambda &list)> callbackList, base::lambda callbackNotModified = nullptr); struct SendOptions { SendOptions(not_null history) : history(history) { } not_null history; MsgId replyTo = 0; WebPageId webPageId = 0; bool clearDraft = false; bool generateLocal = true; }; rpl::producer sendActions() const { return _sendActions.events(); } void sendAction(const SendOptions &options); void forwardMessages( HistoryItemsList &&items, const SendOptions &options, base::lambda_once &&successCallback = nullptr); void shareContact( const QString &phone, const QString &firstName, const QString &lastName, const SendOptions &options); void shareContact(not_null user, const SendOptions &options); void readServerHistory(not_null history); void readServerHistoryForce(not_null history); void readFeed( not_null feed, Data::MessagePosition position); void sendVoiceMessage( QByteArray result, VoiceWaveform waveform, int duration, const SendOptions &options); void sendFiles( Storage::PreparedList &&list, SendMediaType type, QString caption, std::shared_ptr album, const SendOptions &options); void sendFile( const QByteArray &fileContent, SendMediaType type, const SendOptions &options); void sendUploadedPhoto( FullMsgId localId, const MTPInputFile &file, bool silent); void sendUploadedDocument( FullMsgId localId, const MTPInputFile &file, const base::optional &thumb, bool silent); void cancelLocalItem(not_null item); ~ApiWrap(); private: struct MessageDataRequest { using Callbacks = QList; mtpRequestId requestId = 0; Callbacks callbacks; }; using MessageDataRequests = QMap; using SharedMediaType = Storage::SharedMediaType; struct StickersByEmoji { std::vector> list; QString hash; TimeMs received = 0; }; void updatesReceived(const MTPUpdates &updates); void checkQuitPreventFinished(); void saveDraftsToCloud(); void resolveMessageDatas(); void gotMessageDatas(ChannelData *channel, const MTPmessages_Messages &result, mtpRequestId requestId); void finalizeMessageDataRequest( ChannelData *channel, mtpRequestId requestId); QVector collectMessageIds(const MessageDataRequests &requests); MessageDataRequests *messageDataRequests(ChannelData *channel, bool onlyExisting = false); void applyPeerDialogs(const MTPmessages_PeerDialogs &dialogs); void historyDialogEntryApplied(not_null history); void applyFeedDialogs( not_null feed, const MTPmessages_Dialogs &dialogs); void gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result, mtpRequestId req); void gotUserFull(UserData *user, const MTPUserFull &result, mtpRequestId req); void applyLastParticipantsList( not_null channel, int availableCount, const QVector &list); void applyBotsList( not_null channel, int availableCount, const QVector &list); void applyAdminsList( not_null channel, int availableCount, const QVector &list); void resolveWebPages(); void gotWebPages( ChannelData *channel, const MTPmessages_Messages &result, mtpRequestId req); void gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result); void channelRangeDifferenceSend( not_null channel, MsgRange range, int32 pts); void channelRangeDifferenceDone( not_null channel, MsgRange range, const MTPupdates_ChannelDifference &result); PeerData *notifySettingReceived( MTPInputNotifyPeer peer, const MTPPeerNotifySettings &settings); void stickerSetDisenabled(mtpRequestId requestId); void stickersSaveOrder(); void requestStickers(TimeId now); void requestRecentStickers(TimeId now); void requestRecentStickersWithHash(int32 hash); void requestFavedStickers(TimeId now); void requestFeaturedStickers(TimeId now); void requestSavedGifs(TimeId now); void readFeaturedSets(); void cancelEditChatAdmins(not_null chat); void saveChatAdmins(not_null chat); void sendSaveChatAdminsRequests(not_null chat); void refreshChannelAdmins( not_null channel, const QVector &participants); void jumpToHistoryDate(not_null peer, const QDate &date); void jumpToFeedDate(not_null feed, const QDate &date); template void requestMessageAfterDate( not_null peer, const QDate &date, Callback &&callback); template void requestMessageAfterDate( not_null feed, const QDate &date, Callback &&callback); void sharedMediaDone( not_null peer, SharedMediaType type, MsgId messageId, SliceType slice, const MTPmessages_Messages &result); void userPhotosDone( not_null user, PhotoId photoId, const MTPphotos_Photos &result); //void feedChannelsDone(not_null feed); // #feed //void feedMessagesDone( // not_null feed, // Data::MessagePosition messageId, // SliceType slice, // const MTPmessages_FeedMessages &result); void sendSharedContact( const QString &phone, const QString &firstName, const QString &lastName, UserId userId, const SendOptions &options); void sendReadRequest(not_null peer, MsgId upTo); int applyAffectedHistory( not_null peer, const MTPmessages_AffectedHistory &result); void applyAffectedMessages(const MTPmessages_AffectedMessages &result); void applyAffectedMessages( not_null peer, const MTPmessages_AffectedMessages &result); void deleteAllFromUserSend( not_null channel, not_null from); void sendMessageFail(const RPCError &error); void uploadAlbumMedia( not_null item, const MessageGroupId &groupId, const MTPInputMedia &media); void sendAlbumWithUploaded( not_null item, const MessageGroupId &groupId, const MTPInputMedia &media); void sendAlbumWithCancelled( not_null item, const MessageGroupId &groupId); void sendAlbumIfReady(not_null album); void sendMedia( not_null item, const MTPInputMedia &media, bool silent); void sendMediaWithRandomId( not_null item, const MTPInputMedia &media, bool silent, uint64 randomId); void readFeeds(); not_null _session; MessageDataRequests _messageDataRequests; QMap _channelMessageDataRequests; SingleQueuedInvokation _messageDataResolveDelayed; using PeerRequests = QMap; PeerRequests _fullPeerRequests; PeerRequests _peerRequests; PeerRequests _participantsRequests; PeerRequests _botsRequests; PeerRequests _adminsRequests; base::DelayedCallTimer _participantsCountRequestTimer; ChannelData *_channelMembersForAdd = nullptr; mtpRequestId _channelMembersForAddRequestId = 0; base::lambda _channelMembersForAddCallback; base::flat_map< not_null, std::pair>> _channelGroupingRequests; using KickRequest = std::pair< not_null, not_null>; base::flat_map _kickRequests; QMap _selfParticipantRequests; base::flat_map< not_null, mtpRequestId> _rangeDifferenceRequests; QMap _webPagesPending; base::Timer _webPagesTimer; QMap > _stickerSetRequests; QMap _channelAmInRequests; QMap _blockRequests; QMap _exportInviteRequests; QMap _notifySettingRequests; QMap _draftsSaveRequestIds; base::Timer _draftsSaveTimer; base::flat_set _stickerSetDisenableRequests; Stickers::Order _stickersOrder; mtpRequestId _stickersReorderRequestId = 0; mtpRequestId _stickersClearRecentRequestId = 0; mtpRequestId _stickersUpdateRequest = 0; mtpRequestId _recentStickersUpdateRequest = 0; mtpRequestId _favedStickersUpdateRequest = 0; mtpRequestId _featuredStickersUpdateRequest = 0; mtpRequestId _savedGifsUpdateRequest = 0; base::Timer _featuredSetsReadTimer; base::flat_set _featuredSetsRead; base::flat_map, StickersByEmoji> _stickersByEmoji; base::flat_map _privacySaveRequests; mtpRequestId _contactsRequestId = 0; mtpRequestId _contactsStatusesRequestId = 0; base::flat_set> _dialogFeedRequests; base::flat_set> _dialogRequests; base::flat_map, mtpRequestId> _unreadMentionsRequests; base::flat_map< not_null, mtpRequestId> _chatAdminsEnabledRequests; base::flat_map< not_null, base::flat_set>> _chatAdminsToSave; base::flat_map< not_null, base::flat_set> _chatAdminsSaveRequests; base::flat_map, SharedMediaType, MsgId, SliceType>, mtpRequestId> _sharedMediaRequests; base::flat_map, mtpRequestId> _userPhotosRequests; base::flat_set> _feedChannelsGetRequests; base::flat_map< not_null, mtpRequestId> _feedChannelsSetRequests; base::flat_set, Data::MessagePosition, SliceType>> _feedMessagesRequests; base::flat_set, Data::MessagePosition, SliceType>> _feedMessagesRequestsPending; mtpRequestId _saveDefaultFeedIdRequest = 0; rpl::event_stream _sendActions; struct ReadRequest { ReadRequest(mtpRequestId requestId, MsgId upTo) : requestId(requestId) , upTo(upTo) { } mtpRequestId requestId = 0; MsgId upTo = 0; }; base::flat_map, ReadRequest> _readRequests; base::flat_map, MsgId> _readRequestsPending; std::unique_ptr _fileLoader; base::flat_map> _sendingAlbums; base::Observable _fullPeerUpdated; rpl::event_stream _stickerSetInstalled; base::flat_map, TimeMs> _feedReadsDelayed; base::flat_map, mtpRequestId> _feedReadRequests; base::Timer _feedReadTimer; }; tdesktop-1.2.17/Telegram/SourceFiles/app.cpp000066400000000000000000001671311326245125100207200ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "app.h" #ifdef OS_MAC_OLD #include #endif // OS_MAC_OLD #include "styles/style_overview.h" #include "styles/style_mediaview.h" #include "styles/style_chat_helpers.h" #include "styles/style_history.h" #include "styles/style_boxes.h" #include "lang/lang_keys.h" #include "data/data_abstract_structure.h" #include "data/data_media_types.h" #include "data/data_session.h" #include "history/history.h" #include "history/history_location_manager.h" #include "history/history_media_types.h" #include "history/history_item_components.h" #include "history/view/history_view_service_message.h" #include "media/media_audio.h" #include "inline_bots/inline_bot_layout_item.h" #include "messenger.h" #include "application.h" #include "storage/file_upload.h" #include "mainwindow.h" #include "mainwidget.h" #include "storage/localstorage.h" #include "apiwrap.h" #include "numbers.h" #include "observer_peer.h" #include "auth_session.h" #include "core/crash_reports.h" #include "storage/storage_facade.h" #include "storage/storage_shared_media.h" #include "window/themes/window_theme.h" #include "window/notifications_manager.h" #include "platform/platform_notifications_manager.h" namespace { App::LaunchState _launchState = App::Launched; UserData *self = nullptr; using PeersData = QHash; PeersData peersData; using MutedPeers = QMap, bool>; MutedPeers mutedPeers; using LocationsData = QHash; LocationsData locationsData; using DependentItemsSet = OrderedSet; using DependentItems = QMap; DependentItems dependentItems; Histories histories; using MsgsData = QHash; MsgsData msgsData; using ChannelMsgsData = QMap; ChannelMsgsData channelMsgsData; using RandomData = QMap; RandomData randomData; using SentData = QMap>; SentData sentData; HistoryView::Element *hoveredItem = nullptr, *pressedItem = nullptr, *hoveredLinkItem = nullptr, *pressedLinkItem = nullptr, *mousedItem = nullptr; QPixmap *emoji = nullptr, *emojiLarge = nullptr; style::font monofont; struct CornersPixmaps { QPixmap p[4]; }; QVector corners; using CornersMap = QMap; CornersMap cornersMap; QImage cornersMaskLarge[4], cornersMaskSmall[4]; using EmojiImagesMap = QMap; EmojiImagesMap MainEmojiMap; QMap OtherEmojiMap; int32 serviceImageCacheSize = 0; } // namespace namespace App { QString formatPhone(QString phone) { if (phone.isEmpty()) return QString(); if (phone.at(0) == '0') return phone; QString number = phone; for (const QChar *ch = phone.constData(), *e = ch + phone.size(); ch != e; ++ch) { if (ch->unicode() < '0' || ch->unicode() > '9') { number = phone.replace(QRegularExpression(qsl("[^\\d]")), QString()); } } QVector groups = phoneNumberParse(number); if (groups.isEmpty()) return '+' + number; QString result; result.reserve(number.size() + groups.size() + 1); result.append('+'); int32 sum = 0; for (int32 i = 0, l = groups.size(); i < l; ++i) { result.append(number.midRef(sum, groups.at(i))); sum += groups.at(i); if (sum < number.size()) result.append(' '); } if (sum < number.size()) result.append(number.midRef(sum)); return result; } MainWindow *wnd() { if (auto instance = Messenger::InstancePointer()) { return instance->getActiveWindow(); } return nullptr; } MainWidget *main() { if (auto window = wnd()) { return window->mainWidget(); } return nullptr; } bool passcoded() { if (auto window = wnd()) { return window->passcodeWidget(); } return false; } namespace { bool loggedOut() { if (Global::LocalPasscode()) { Global::SetLocalPasscode(false); Global::RefLocalPasscodeChanged().notify(); } Media::Player::mixer()->stopAndClear(); if (auto w = wnd()) { w->tempDirDelete(Local::ClearManagerAll); w->setupIntro(); } histories().clear(); Messenger::Instance().authSessionDestroy(); Local::reset(); Window::Theme::Background()->reset(); cSetOtherOnline(0); clearStorageImages(); if (auto w = wnd()) { w->updateConnectingStatus(); } return true; } } // namespace void logOut() { if (auto mtproto = Messenger::Instance().mtp()) { mtproto->logout(rpcDone([] { return loggedOut(); }), rpcFail([] { return loggedOut(); })); } else { // We log out because we've forgotten passcode. // So we just start mtproto from scratch. Messenger::Instance().startMtp(); loggedOut(); } } namespace { // we should get a full restriction in "{fulltype}: {reason}" format and we // need to find a "-all" tag in {fulltype}, otherwise ignore this restriction QString extractRestrictionReason(const QString &fullRestriction) { int fullTypeEnd = fullRestriction.indexOf(':'); if (fullTypeEnd <= 0) { return QString(); } // {fulltype} is in "{type}-{tag}-{tag}-{tag}" format // if we find "all" tag we return the restriction string auto typeTags = fullRestriction.mid(0, fullTypeEnd).split('-').mid(1); #ifndef OS_MAC_STORE auto restrictionApplies = typeTags.contains(qsl("all")); #else // OS_MAC_STORE auto restrictionApplies = typeTags.contains(qsl("all")) || typeTags.contains(qsl("ios")); #endif // OS_MAC_STORE if (restrictionApplies) { return fullRestriction.midRef(fullTypeEnd + 1).trimmed().toString(); } return QString(); } } UserData *feedUser(const MTPUser &user) { UserData *data = nullptr; bool minimal = false; const MTPUserStatus *status = 0, emptyStatus = MTP_userStatusEmpty(); Notify::PeerUpdate update; using UpdateFlag = Notify::PeerUpdate::Flag; switch (user.type()) { case mtpc_userEmpty: { auto &d = user.c_userEmpty(); auto peer = peerFromUser(d.vid.v); data = App::user(peer); auto canShareThisContact = data->canShareThisContactFast(); data->input = MTP_inputPeerUser(d.vid, MTP_long(0)); data->inputUser = MTP_inputUser(d.vid, MTP_long(0)); data->setName(lang(lng_deleted), QString(), QString(), QString()); data->setPhoto(MTP_userProfilePhotoEmpty()); //data->setFlags(MTPDuser_ClientFlag::f_inaccessible | 0); data->setFlags(MTPDuser::Flag::f_deleted); if (!data->phone().isEmpty()) { data->setPhone(QString()); update.flags |= UpdateFlag::UserPhoneChanged; } data->setBotInfoVersion(-1); status = &emptyStatus; data->setContactStatus(UserData::ContactStatus::PhoneUnknown); if (canShareThisContact != data->canShareThisContactFast()) { update.flags |= UpdateFlag::UserCanShareContact; } } break; case mtpc_user: { auto &d = user.c_user(); minimal = d.is_min(); auto peer = peerFromUser(d.vid.v); data = App::user(peer); auto canShareThisContact = data->canShareThisContactFast(); if (minimal) { auto mask = 0 //| MTPDuser_ClientFlag::f_inaccessible | MTPDuser::Flag::f_deleted; data->setFlags((data->flags() & ~mask) | (d.vflags.v & mask)); } else { data->setFlags(d.vflags.v); if (d.is_self()) { data->input = MTP_inputPeerSelf(); data->inputUser = MTP_inputUserSelf(); } else if (!d.has_access_hash()) { data->input = MTP_inputPeerUser(d.vid, MTP_long(data->accessHash())); data->inputUser = MTP_inputUser(d.vid, MTP_long(data->accessHash())); } else { data->input = MTP_inputPeerUser(d.vid, d.vaccess_hash); data->inputUser = MTP_inputUser(d.vid, d.vaccess_hash); } if (d.is_restricted()) { data->setRestrictionReason(extractRestrictionReason(qs(d.vrestriction_reason))); } else { data->setRestrictionReason(QString()); } } if (d.is_deleted()) { if (!data->phone().isEmpty()) { data->setPhone(QString()); update.flags |= UpdateFlag::UserPhoneChanged; } data->setName(lang(lng_deleted), QString(), QString(), QString()); data->setPhoto(MTP_userProfilePhotoEmpty()); status = &emptyStatus; } else { // apply first_name and last_name from minimal user only if we don't have // local values for first name and last name already, otherwise skip bool noLocalName = data->firstName.isEmpty() && data->lastName.isEmpty(); QString fname = (!minimal || noLocalName) ? (d.has_first_name() ? TextUtilities::SingleLine(qs(d.vfirst_name)) : QString()) : data->firstName; QString lname = (!minimal || noLocalName) ? (d.has_last_name() ? TextUtilities::SingleLine(qs(d.vlast_name)) : QString()) : data->lastName; QString phone = minimal ? data->phone() : (d.has_phone() ? qs(d.vphone) : QString()); QString uname = minimal ? data->username : (d.has_username() ? TextUtilities::SingleLine(qs(d.vusername)) : QString()); bool phoneChanged = (data->phone() != phone); if (phoneChanged) { data->setPhone(phone); update.flags |= UpdateFlag::UserPhoneChanged; } bool nameChanged = (data->firstName != fname) || (data->lastName != lname); bool showPhone = !isServiceUser(data->id) && !d.is_self() && !d.is_contact() && !d.is_mutual_contact(); bool showPhoneChanged = !isServiceUser(data->id) && !d.is_self() && ((showPhone && data->contactStatus() == UserData::ContactStatus::Contact) || (!showPhone && data->contactStatus() == UserData::ContactStatus::CanAdd)); if (minimal) { showPhoneChanged = false; showPhone = !isServiceUser(data->id) && (data->id != Auth().userPeerId()) && (data->contactStatus() == UserData::ContactStatus::CanAdd); } // see also Local::readPeer const auto pname = (showPhoneChanged || phoneChanged || nameChanged) ? ((showPhone && !phone.isEmpty()) ? formatPhone(phone) : QString()) : data->nameOrPhone; if (!minimal && d.is_self() && uname != data->username) { CrashReports::SetAnnotation("Username", uname); } data->setName(fname, lname, pname, uname); if (d.has_photo()) { data->setPhoto(d.vphoto); } else { data->setPhoto(MTP_userProfilePhotoEmpty()); } if (d.has_access_hash()) { data->setAccessHash(d.vaccess_hash.v); } status = d.has_status() ? &d.vstatus : &emptyStatus; } if (!minimal) { if (d.has_bot_info_version()) { data->setBotInfoVersion(d.vbot_info_version.v); data->botInfo->readsAllHistory = d.is_bot_chat_history(); if (data->botInfo->cantJoinGroups != d.is_bot_nochats()) { data->botInfo->cantJoinGroups = d.is_bot_nochats(); update.flags |= UpdateFlag::BotCanAddToGroups; } data->botInfo->inlinePlaceholder = d.has_bot_inline_placeholder() ? '_' + qs(d.vbot_inline_placeholder) : QString(); } else { data->setBotInfoVersion(-1); } data->setContactStatus((d.is_contact() || d.is_mutual_contact()) ? UserData::ContactStatus::Contact : data->phone().isEmpty() ? UserData::ContactStatus::PhoneUnknown : UserData::ContactStatus::CanAdd); if (d.is_self() && ::self != data) { ::self = data; Global::RefSelfChanged().notify(); } } if (canShareThisContact != data->canShareThisContactFast()) { update.flags |= UpdateFlag::UserCanShareContact; } } break; } if (!data) { return nullptr; } if (minimal) { if (data->loadedStatus == PeerData::NotLoaded) { data->loadedStatus = PeerData::MinimalLoaded; } } else if (data->loadedStatus != PeerData::FullLoaded) { data->loadedStatus = PeerData::FullLoaded; } if (status && !minimal) { auto oldOnlineTill = data->onlineTill; auto newOnlineTill = Auth().api().onlineTillFromStatus(*status, oldOnlineTill); if (oldOnlineTill != newOnlineTill) { data->onlineTill = newOnlineTill; update.flags |= UpdateFlag::UserOnlineChanged; } } if (data->contactStatus() == UserData::ContactStatus::PhoneUnknown && !data->phone().isEmpty() && data->id != Auth().userPeerId()) { data->setContactStatus(UserData::ContactStatus::CanAdd); } if (App::main()) { if (update.flags) { update.peer = data; Notify::peerUpdatedDelayed(update); } } return data; } UserData *feedUsers(const MTPVector &users) { UserData *result = nullptr; for_const (auto &user, users.v) { if (auto feededUser = feedUser(user)) { result = feededUser; } } return result; } PeerData *feedChat(const MTPChat &chat) { PeerData *data = nullptr; bool minimal = false; Notify::PeerUpdate update; using UpdateFlag = Notify::PeerUpdate::Flag; switch (chat.type()) { case mtpc_chat: { auto &d(chat.c_chat()); data = App::chat(peerFromChat(d.vid.v)); auto cdata = data->asChat(); auto canEdit = cdata->canEdit(); if (cdata->version < d.vversion.v) { cdata->version = d.vversion.v; cdata->invalidateParticipants(); } data->input = MTP_inputPeerChat(d.vid); cdata->setName(qs(d.vtitle)); cdata->setPhoto(d.vphoto); cdata->date = d.vdate.v; if (d.has_migrated_to() && d.vmigrated_to.type() == mtpc_inputChannel) { auto &c = d.vmigrated_to.c_inputChannel(); auto channel = App::channel(peerFromChannel(c.vchannel_id)); channel->addFlags(MTPDchannel::Flag::f_megagroup); if (!channel->access) { channel->input = MTP_inputPeerChannel(c.vchannel_id, c.vaccess_hash); channel->inputChannel = d.vmigrated_to; channel->access = d.vmigrated_to.c_inputChannel().vaccess_hash.v; } bool updatedTo = (cdata->migrateToPtr != channel), updatedFrom = (channel->mgInfo->migrateFromPtr != cdata); if (updatedTo) { cdata->migrateToPtr = channel; } if (updatedFrom) { channel->mgInfo->migrateFromPtr = cdata; if (auto h = App::historyLoaded(cdata->id)) { if (auto hto = App::historyLoaded(channel->id)) { if (!h->isEmpty()) { h->unloadBlocks(); } if (hto->inChatList(Dialogs::Mode::All) && h->inChatList(Dialogs::Mode::All)) { App::main()->removeDialog(h); } } } Notify::migrateUpdated(channel); update.flags |= UpdateFlag::MigrationChanged; } if (updatedTo) { Notify::migrateUpdated(cdata); update.flags |= UpdateFlag::MigrationChanged; } } if (!(cdata->flags() & MTPDchat::Flag::f_admins_enabled) && (d.vflags.v & MTPDchat::Flag::f_admins_enabled)) { cdata->invalidateParticipants(); } cdata->setFlags(d.vflags.v); cdata->count = d.vparticipants_count.v; if (canEdit != cdata->canEdit()) { update.flags |= UpdateFlag::ChatCanEdit; } } break; case mtpc_chatForbidden: { auto &d(chat.c_chatForbidden()); data = App::chat(peerFromChat(d.vid.v)); auto cdata = data->asChat(); auto canEdit = cdata->canEdit(); data->input = MTP_inputPeerChat(d.vid); cdata->setName(qs(d.vtitle)); cdata->setPhoto(MTP_chatPhotoEmpty()); cdata->date = 0; cdata->count = -1; cdata->invalidateParticipants(); cdata->setFlags(MTPDchat_ClientFlag::f_forbidden | 0); if (canEdit != cdata->canEdit()) { update.flags |= UpdateFlag::ChatCanEdit; } } break; case mtpc_channel: { const auto &d = chat.c_channel(); const auto peerId = peerFromChannel(d.vid.v); minimal = d.is_min(); if (minimal) { data = App::channelLoaded(peerId); if (!data) { // Can't apply minimal to a not loaded channel. // Need to make getDifference. return nullptr; } } else { data = App::channel(peerId); const auto accessHash = d.has_access_hash() ? d.vaccess_hash : MTP_long(0); data->input = MTP_inputPeerChannel(d.vid, accessHash); } const auto cdata = data->asChannel(); const auto wasInChannel = cdata->amIn(); const auto canViewAdmins = cdata->canViewAdmins(); const auto canViewMembers = cdata->canViewMembers(); const auto canAddMembers = cdata->canAddMembers(); if (d.has_participants_count()) { cdata->setMembersCount(d.vparticipants_count.v); } if (minimal) { auto mask = 0 | MTPDchannel::Flag::f_broadcast | MTPDchannel::Flag::f_verified | MTPDchannel::Flag::f_megagroup | MTPDchannel::Flag::f_democracy | MTPDchannel_ClientFlag::f_forbidden; cdata->setFlags((cdata->flags() & ~mask) | (d.vflags.v & mask)); } else { if (d.has_admin_rights()) { cdata->setAdminRights(d.vadmin_rights); } else if (cdata->hasAdminRights()) { cdata->setAdminRights(MTP_channelAdminRights(MTP_flags(0))); } if (d.has_banned_rights()) { cdata->setRestrictedRights(d.vbanned_rights); } else if (cdata->hasRestrictions()) { cdata->setRestrictedRights(MTP_channelBannedRights(MTP_flags(0), MTP_int(0))); } cdata->inputChannel = MTP_inputChannel(d.vid, d.vaccess_hash); cdata->access = d.vaccess_hash.v; cdata->date = d.vdate.v; if (cdata->version < d.vversion.v) { cdata->version = d.vversion.v; } if (d.is_restricted()) { cdata->setRestrictionReason(extractRestrictionReason(qs(d.vrestriction_reason))); } else { cdata->setRestrictionReason(QString()); } cdata->setFlags(d.vflags.v); //if (d.has_feed_id()) { // #feed // cdata->setFeed(Auth().data().feed(d.vfeed_id.v)); //} else { // cdata->clearFeed(); //} } QString uname = d.has_username() ? TextUtilities::SingleLine(qs(d.vusername)) : QString(); cdata->setName(qs(d.vtitle), uname); cdata->setPhoto(d.vphoto); if (wasInChannel != cdata->amIn()) { update.flags |= UpdateFlag::ChannelAmIn; } if (canViewAdmins != cdata->canViewAdmins() || canViewMembers != cdata->canViewMembers() || canAddMembers != cdata->canAddMembers()) { update.flags |= UpdateFlag::ChannelRightsChanged; } } break; case mtpc_channelForbidden: { auto &d(chat.c_channelForbidden()); auto peerId = peerFromChannel(d.vid.v); data = App::channel(peerId); data->input = MTP_inputPeerChannel(d.vid, d.vaccess_hash); auto cdata = data->asChannel(); auto wasInChannel = cdata->amIn(); auto canViewAdmins = cdata->canViewAdmins(); auto canViewMembers = cdata->canViewMembers(); auto canAddMembers = cdata->canAddMembers(); cdata->inputChannel = MTP_inputChannel(d.vid, d.vaccess_hash); auto mask = mtpCastFlags(MTPDchannelForbidden::Flag::f_broadcast | MTPDchannelForbidden::Flag::f_megagroup); cdata->setFlags((cdata->flags() & ~mask) | (mtpCastFlags(d.vflags) & mask) | MTPDchannel_ClientFlag::f_forbidden); if (cdata->hasAdminRights()) { cdata->setAdminRights(MTP_channelAdminRights(MTP_flags(0))); } if (cdata->hasRestrictions()) { cdata->setRestrictedRights(MTP_channelBannedRights(MTP_flags(0), MTP_int(0))); } cdata->setName(qs(d.vtitle), QString()); cdata->access = d.vaccess_hash.v; cdata->setPhoto(MTP_chatPhotoEmpty()); cdata->date = 0; cdata->setMembersCount(0); if (wasInChannel != cdata->amIn()) { update.flags |= UpdateFlag::ChannelAmIn; } if (canViewAdmins != cdata->canViewAdmins() || canViewMembers != cdata->canViewMembers() || canAddMembers != cdata->canAddMembers()) { update.flags |= UpdateFlag::ChannelRightsChanged; } } break; } if (!data) { return nullptr; } if (minimal) { if (data->loadedStatus == PeerData::NotLoaded) { data->loadedStatus = PeerData::MinimalLoaded; } } else if (data->loadedStatus != PeerData::FullLoaded) { data->loadedStatus = PeerData::FullLoaded; } if (update.flags) { update.peer = data; Notify::peerUpdatedDelayed(update); } return data; } PeerData *feedChats(const MTPVector &chats) { PeerData *result = nullptr; for_const (auto &chat, chats.v) { if (auto feededChat = feedChat(chat)) { result = feededChat; } } return result; } void feedParticipants(const MTPChatParticipants &p, bool requestBotInfos) { ChatData *chat = 0; switch (p.type()) { case mtpc_chatParticipantsForbidden: { const auto &d(p.c_chatParticipantsForbidden()); chat = App::chat(d.vchat_id.v); chat->count = -1; chat->invalidateParticipants(); } break; case mtpc_chatParticipants: { const auto &d(p.c_chatParticipants()); chat = App::chat(d.vchat_id.v); auto canEdit = chat->canEdit(); if (!requestBotInfos || chat->version <= d.vversion.v) { // !requestBotInfos is true on getFullChat result chat->version = d.vversion.v; auto &v = d.vparticipants.v; chat->count = v.size(); int32 pversion = chat->participants.empty() ? 1 : (chat->participants.begin()->second + 1); chat->invitedByMe.clear(); chat->admins.clear(); chat->removeFlags(MTPDchat::Flag::f_admin); for (auto i = v.cbegin(), e = v.cend(); i != e; ++i) { int32 uid = 0, inviter = 0; switch (i->type()) { case mtpc_chatParticipantCreator: { const auto &p(i->c_chatParticipantCreator()); uid = p.vuser_id.v; chat->creator = uid; } break; case mtpc_chatParticipantAdmin: { const auto &p(i->c_chatParticipantAdmin()); uid = p.vuser_id.v; inviter = p.vinviter_id.v; } break; case mtpc_chatParticipant: { const auto &p(i->c_chatParticipant()); uid = p.vuser_id.v; inviter = p.vinviter_id.v; } break; } if (!uid) continue; UserData *user = App::userLoaded(uid); if (user) { chat->participants[user] = pversion; if (inviter == Auth().userId()) { chat->invitedByMe.insert(user); } if (i->type() == mtpc_chatParticipantAdmin) { chat->admins.insert(user); if (user->isSelf()) { chat->addFlags(MTPDchat::Flag::f_admin); } } } else { chat->invalidateParticipants(); break; } } if (!chat->participants.empty()) { auto h = App::historyLoaded(chat->id); bool found = !h || !h->lastKeyboardFrom; auto botStatus = -1; for (auto i = chat->participants.begin(), e = chat->participants.end(); i != e;) { auto [user, version] = *i; if (version < pversion) { i = chat->participants.erase(i); } else { if (user->botInfo) { botStatus = 2;// (botStatus > 0/* || !user->botInfo->readsAllHistory*/) ? 2 : 1; if (requestBotInfos && !user->botInfo->inited) { Auth().api().requestFullPeer(user); } } if (!found && user->id == h->lastKeyboardFrom) { found = true; } ++i; } } chat->botStatus = botStatus; if (!found) { h->clearLastKeyboard(); } } } if (canEdit != chat->canEdit()) { Notify::peerUpdatedDelayed(chat, Notify::PeerUpdate::Flag::ChatCanEdit); } } break; } Notify::peerUpdatedDelayed(chat, Notify::PeerUpdate::Flag::MembersChanged | Notify::PeerUpdate::Flag::AdminsChanged); } void feedParticipantAdd(const MTPDupdateChatParticipantAdd &d) { ChatData *chat = App::chat(d.vchat_id.v); if (chat->version + 1 < d.vversion.v) { chat->version = d.vversion.v; chat->invalidateParticipants(); Auth().api().requestPeer(chat); } else if (chat->version <= d.vversion.v && chat->count >= 0) { chat->version = d.vversion.v; UserData *user = App::userLoaded(d.vuser_id.v); if (user) { if (chat->participants.empty() && chat->count) { chat->count++; chat->botStatus = 0; } else if (chat->participants.find(user) == chat->participants.end()) { chat->participants[user] = (chat->participants.empty() ? 1 : chat->participants.begin()->second); if (d.vinviter_id.v == Auth().userId()) { chat->invitedByMe.insert(user); } else { chat->invitedByMe.remove(user); } chat->count++; if (user->botInfo) { chat->botStatus = 2;// (chat->botStatus > 0/* || !user->botInfo->readsAllHistory*/) ? 2 : 1; if (!user->botInfo->inited) { Auth().api().requestFullPeer(user); } } } } else { chat->invalidateParticipants(); chat->count++; } Notify::peerUpdatedDelayed(chat, Notify::PeerUpdate::Flag::MembersChanged); } } void feedParticipantDelete(const MTPDupdateChatParticipantDelete &d) { ChatData *chat = App::chat(d.vchat_id.v); if (chat->version + 1 < d.vversion.v) { chat->version = d.vversion.v; chat->invalidateParticipants(); Auth().api().requestPeer(chat); } else if (chat->version <= d.vversion.v && chat->count > 0) { chat->version = d.vversion.v; auto canEdit = chat->canEdit(); UserData *user = App::userLoaded(d.vuser_id.v); if (user) { if (chat->participants.empty()) { if (chat->count > 0) { chat->count--; } } else { auto i = chat->participants.find(user); if (i != chat->participants.end()) { chat->participants.erase(i); chat->count--; chat->invitedByMe.remove(user); chat->admins.remove(user); if (user->isSelf()) { chat->removeFlags(MTPDchat::Flag::f_admin); } History *h = App::historyLoaded(chat->id); if (h && h->lastKeyboardFrom == user->id) { h->clearLastKeyboard(); } } if (chat->botStatus > 0 && user->botInfo) { int32 botStatus = -1; for (auto [participant, v] : chat->participants) { if (participant->botInfo) { if (true || botStatus > 0/* || !participant->botInfo->readsAllHistory*/) { botStatus = 2; break; } botStatus = 1; } } chat->botStatus = botStatus; } } } else { chat->invalidateParticipants(); chat->count--; } if (canEdit != chat->canEdit()) { Notify::peerUpdatedDelayed(chat, Notify::PeerUpdate::Flag::ChatCanEdit); } Notify::peerUpdatedDelayed(chat, Notify::PeerUpdate::Flag::MembersChanged); } } void feedChatAdmins(const MTPDupdateChatAdmins &d) { auto chat = App::chat(d.vchat_id.v); if (chat->version <= d.vversion.v) { auto wasCanEdit = chat->canEdit(); auto badVersion = (chat->version + 1 < d.vversion.v); chat->version = d.vversion.v; if (mtpIsTrue(d.venabled)) { chat->addFlags(MTPDchat::Flag::f_admins_enabled); } else { chat->removeFlags(MTPDchat::Flag::f_admins_enabled); } if (badVersion || mtpIsTrue(d.venabled)) { chat->invalidateParticipants(); Auth().api().requestPeer(chat); } if (wasCanEdit != chat->canEdit()) { Notify::peerUpdatedDelayed( chat, Notify::PeerUpdate::Flag::ChatCanEdit); } Notify::peerUpdatedDelayed( chat, Notify::PeerUpdate::Flag::AdminsChanged); } } void feedParticipantAdmin(const MTPDupdateChatParticipantAdmin &d) { ChatData *chat = App::chat(d.vchat_id.v); if (chat->version + 1 < d.vversion.v) { chat->version = d.vversion.v; chat->invalidateParticipants(); Auth().api().requestPeer(chat); } else if (chat->version <= d.vversion.v && chat->count > 0) { chat->version = d.vversion.v; auto canEdit = chat->canEdit(); UserData *user = App::userLoaded(d.vuser_id.v); if (user) { if (mtpIsTrue(d.vis_admin)) { if (user->isSelf()) { chat->addFlags(MTPDchat::Flag::f_admin); } if (chat->noParticipantInfo()) { Auth().api().requestFullPeer(chat); } else { chat->admins.insert(user); } } else { if (user->isSelf()) { chat->removeFlags(MTPDchat::Flag::f_admin); } chat->admins.remove(user); } } else { chat->invalidateParticipants(); } if (canEdit != chat->canEdit()) { Notify::peerUpdatedDelayed(chat, Notify::PeerUpdate::Flag::ChatCanEdit); } Notify::peerUpdatedDelayed(chat, Notify::PeerUpdate::Flag::AdminsChanged); } } bool checkEntitiesAndViewsUpdate(const MTPDmessage &m) { auto peerId = peerFromMTP(m.vto_id); if (m.has_from_id() && peerId == Auth().userPeerId()) { peerId = peerFromUser(m.vfrom_id); } if (const auto existing = App::histItemById(peerToChannel(peerId), m.vid.v)) { auto text = qs(m.vmessage); auto entities = m.has_entities() ? TextUtilities::EntitiesFromMTP(m.ventities.v) : EntitiesInText(); const auto media = m.has_media() ? &m.vmedia : nullptr; existing->setText({ text, entities }); existing->updateSentMedia(m.has_media() ? &m.vmedia : nullptr); existing->updateReplyMarkup(m.has_reply_markup() ? (&m.vreply_markup) : nullptr); existing->setViewsCount(m.has_views() ? m.vviews.v : -1); existing->indexAsNewItem(); Auth().data().requestItemTextRefresh(existing); if (existing->mainView()) { App::checkSavedGif(existing); return true; } return false; } return false; } void updateEditedMessage(const MTPMessage &m) { auto apply = [](const auto &data) { auto peerId = peerFromMTP(data.vto_id); if (data.has_from_id() && peerId == Auth().userPeerId()) { peerId = peerFromUser(data.vfrom_id); } if (auto existing = App::histItemById(peerToChannel(peerId), data.vid.v)) { existing->applyEdition(data); } }; if (m.type() == mtpc_message) { // apply message edit apply(m.c_message()); } else if (m.type() == mtpc_messageService) { apply(m.c_messageService()); } } void addSavedGif(DocumentData *doc) { auto &saved = Auth().data().savedGifsRef(); int32 index = saved.indexOf(doc); if (index) { if (index > 0) saved.remove(index); saved.push_front(doc); if (saved.size() > Global::SavedGifsLimit()) saved.pop_back(); Local::writeSavedGifs(); Auth().data().notifySavedGifsUpdated(); Auth().data().setLastSavedGifsUpdate(0); Auth().api().updateStickers(); } } void checkSavedGif(HistoryItem *item) { if (!item->Has() && (item->out() || item->history()->peer == App::self())) { if (const auto media = item->media()) { if (const auto document = media->document()) { if (document->isGifv()) { addSavedGif(document); } } } } } void feedMsgs(const QVector &msgs, NewMessageType type) { auto indices = base::flat_map(); for (int i = 0, l = msgs.size(); i != l; ++i) { const auto &msg = msgs[i]; if (msg.type() == mtpc_message) { const auto &data = msg.c_message(); if (type == NewMessageUnread) { // new message, index my forwarded messages to links overview if (checkEntitiesAndViewsUpdate(data)) { // already in blocks LOG(("Skipping message, because it is already in blocks!")); continue; } } } const auto msgId = idFromMessage(msg); indices.emplace((uint64(uint32(msgId)) << 32) | uint64(i), i); } for (const auto [position, index] : indices) { histories().addNewMessage(msgs[index], type); } } void feedMsgs(const MTPVector &msgs, NewMessageType type) { return feedMsgs(msgs.v, type); } ImagePtr image(const MTPPhotoSize &size) { switch (size.type()) { case mtpc_photoSize: { auto &d = size.c_photoSize(); if (d.vlocation.type() == mtpc_fileLocation) { auto &l = d.vlocation.c_fileLocation(); return ImagePtr(StorageImageLocation(d.vw.v, d.vh.v, l.vdc_id.v, l.vvolume_id.v, l.vlocal_id.v, l.vsecret.v), d.vsize.v); } } break; case mtpc_photoCachedSize: { auto &d = size.c_photoCachedSize(); if (d.vlocation.type() == mtpc_fileLocation) { auto &l = d.vlocation.c_fileLocation(); auto bytes = qba(d.vbytes); return ImagePtr(StorageImageLocation(d.vw.v, d.vh.v, l.vdc_id.v, l.vvolume_id.v, l.vlocal_id.v, l.vsecret.v), bytes); } else if (d.vlocation.type() == mtpc_fileLocationUnavailable) { auto bytes = qba(d.vbytes); return ImagePtr(StorageImageLocation(d.vw.v, d.vh.v, 0, 0, 0, 0), bytes); } } break; } return ImagePtr(); } void feedInboxRead(const PeerId &peer, MsgId upTo) { if (const auto history = App::historyLoaded(peer)) { history->inboxRead(upTo); } } void feedOutboxRead(const PeerId &peer, MsgId upTo, TimeId when) { if (auto history = App::historyLoaded(peer)) { history->outboxRead(upTo); if (const auto user = history->peer->asUser()) { user->madeAction(when); } } } inline MsgsData *fetchMsgsData(ChannelId channelId, bool insert = true) { if (channelId == NoChannel) return &msgsData; ChannelMsgsData::iterator i = channelMsgsData.find(channelId); if (i == channelMsgsData.cend()) { if (insert) { i = channelMsgsData.insert(channelId, MsgsData()); } else { return 0; } } return &(*i); } void feedWereDeleted( ChannelId channelId, const QVector &msgsIds) { const auto data = fetchMsgsData(channelId, false); if (!data) return; const auto affectedHistory = (channelId != NoChannel) ? App::history(peerFromChannel(channelId)).get() : nullptr; auto historiesToCheck = base::flat_set>(); for (const auto msgId : msgsIds) { auto j = data->constFind(msgId.v); if (j != data->cend()) { const auto history = (*j)->history(); (*j)->destroy(); if (!history->lastMessageKnown()) { historiesToCheck.emplace(history); } } else if (affectedHistory) { affectedHistory->unknownMessageDeleted(msgId.v); } } for (const auto history : historiesToCheck) { Auth().api().requestDialogEntry(history); } } void feedUserLink(MTPint userId, const MTPContactLink &myLink, const MTPContactLink &foreignLink) { if (const auto user = userLoaded(userId.v)) { const auto wasShowPhone = (user->contactStatus() == UserData::ContactStatus::CanAdd); switch (myLink.type()) { case mtpc_contactLinkContact: user->setContactStatus(UserData::ContactStatus::Contact); break; case mtpc_contactLinkHasPhone: user->setContactStatus(UserData::ContactStatus::CanAdd); break; case mtpc_contactLinkNone: case mtpc_contactLinkUnknown: user->setContactStatus(UserData::ContactStatus::PhoneUnknown); break; } if (user->contactStatus() == UserData::ContactStatus::PhoneUnknown && !user->phone().isEmpty() && user->id != Auth().userPeerId()) { user->setContactStatus(UserData::ContactStatus::CanAdd); } const auto showPhone = !isServiceUser(user->id) && !user->isSelf() && user->contactStatus() == UserData::ContactStatus::CanAdd; const auto showPhoneChanged = !isServiceUser(user->id) && !user->isSelf() && (showPhone != wasShowPhone); if (showPhoneChanged) { user->setName( TextUtilities::SingleLine(user->firstName), TextUtilities::SingleLine(user->lastName), showPhone ? App::formatPhone(user->phone()) : QString(), TextUtilities::SingleLine(user->username)); } } } PeerData *peer(const PeerId &id, PeerData::LoadedStatus restriction) { if (!id) return nullptr; auto i = peersData.constFind(id); if (i == peersData.cend()) { PeerData *newData = nullptr; if (peerIsUser(id)) { newData = new UserData(id); } else if (peerIsChat(id)) { newData = new ChatData(id); } else if (peerIsChannel(id)) { newData = new ChannelData(id); } Assert(newData != nullptr); newData->input = MTPinputPeer(MTP_inputPeerEmpty()); i = peersData.insert(id, newData); } switch (restriction) { case PeerData::MinimalLoaded: { if (i.value()->loadedStatus == PeerData::NotLoaded) { return nullptr; } } break; case PeerData::FullLoaded: { if (i.value()->loadedStatus != PeerData::FullLoaded) { return nullptr; } } break; } return i.value(); } void enumerateUsers(base::lambda action) { for_const (auto peer, peersData) { if (auto user = peer->asUser()) { action(user); } } } UserData *self() { return ::self; } PeerData *peerByName(const QString &username) { QString uname(username.trimmed()); for_const (PeerData *peer, peersData) { if (!peer->userName().compare(uname, Qt::CaseInsensitive)) { return peer; } } return nullptr; } LocationData *location(const LocationCoords &coords) { auto i = locationsData.constFind(coords); if (i == locationsData.cend()) { i = locationsData.insert(coords, new LocationData(coords)); } return i.value(); } void forgetMedia() { for_const (auto location, ::locationsData) { location->thumb->forget(); } } MTPPhoto photoFromUserPhoto(MTPint userId, MTPint date, const MTPUserProfilePhoto &photo) { if (photo.type() == mtpc_userProfilePhoto) { const auto &uphoto(photo.c_userProfilePhoto()); QVector photoSizes; photoSizes.push_back(MTP_photoSize(MTP_string("a"), uphoto.vphoto_small, MTP_int(160), MTP_int(160), MTP_int(0))); photoSizes.push_back(MTP_photoSize(MTP_string("c"), uphoto.vphoto_big, MTP_int(640), MTP_int(640), MTP_int(0))); return MTP_photo(MTP_flags(0), uphoto.vphoto_id, MTP_long(0), date, MTP_vector(photoSizes)); } return MTP_photoEmpty(MTP_long(0)); } QString peerName(const PeerData *peer, bool forDialogs) { return peer ? ((forDialogs && peer->isUser() && !peer->asUser()->nameOrPhone.isEmpty()) ? peer->asUser()->nameOrPhone : peer->name) : lang(lng_deleted); } Histories &histories() { return ::histories; } not_null history(const PeerId &peer) { return ::histories.findOrInsert(peer); } History *historyLoaded(const PeerId &peer) { if (!peer) { return nullptr; } return ::histories.find(peer); } HistoryItem *histItemById(ChannelId channelId, MsgId itemId) { if (!itemId) return nullptr; auto data = fetchMsgsData(channelId, false); if (!data) return nullptr; auto i = data->constFind(itemId); if (i != data->cend()) { return i.value(); } return nullptr; } void historyRegItem(not_null item) { const auto data = fetchMsgsData(item->channelId()); const auto i = data->constFind(item->id); if (i == data->cend()) { data->insert(item->id, item); } else if (i.value() != item) { LOG(("App Error: trying to historyRegItem() an already registered item")); i.value()->destroy(); data->insert(item->id, item); } } void historyUnregItem(not_null item) { const auto data = fetchMsgsData(item->channelId(), false); if (!data) return; const auto i = data->find(item->id); if (i != data->cend()) { if (i.value() == item) { data->erase(i); } } const auto j = ::dependentItems.find(item); if (j != ::dependentItems.cend()) { DependentItemsSet items; std::swap(items, j.value()); ::dependentItems.erase(j); for_const (auto dependent, items) { dependent->dependencyItemRemoved(item); } } Auth().notifications().clearFromItem(item); } void historyUpdateDependent(not_null item) { const auto j = ::dependentItems.find(item); if (j != ::dependentItems.cend()) { for_const (const auto dependent, j.value()) { dependent->updateDependencyItem(); } } if (App::main()) { App::main()->itemEdited(item); } } void historyClearMsgs() { ::dependentItems.clear(); QVector toDelete; for_const (auto item, msgsData) { if (!item->mainView()) { toDelete.push_back(item); } } for_const (auto &chMsgsData, channelMsgsData) { for_const (auto item, chMsgsData) { if (!item->mainView()) { toDelete.push_back(item); } } } msgsData.clear(); channelMsgsData.clear(); for_const (auto item, toDelete) { delete item; } clearMousedItems(); } void historyClearItems() { randomData.clear(); sentData.clear(); mutedPeers.clear(); cSetSavedPeers(SavedPeers()); cSetSavedPeersByTime(SavedPeersByTime()); cSetRecentInlineBots(RecentInlineBots()); for_const (auto peer, ::peersData) { delete peer; } ::peersData.clear(); if (AuthSession::Exists()) { Auth().api().clearWebPageRequests(); } cSetRecentStickers(RecentStickerPack()); cSetReportSpamStatuses(ReportSpamStatuses()); cSetAutoDownloadPhoto(0); cSetAutoDownloadAudio(0); cSetAutoDownloadGif(0); ::self = nullptr; Global::RefSelfChanged().notify(true); } void historyRegDependency(HistoryItem *dependent, HistoryItem *dependency) { ::dependentItems[dependency].insert(dependent); } void historyUnregDependency(HistoryItem *dependent, HistoryItem *dependency) { auto i = ::dependentItems.find(dependency); if (i != ::dependentItems.cend()) { i.value().remove(dependent); if (i.value().isEmpty()) { ::dependentItems.erase(i); } } } void historyRegRandom(uint64 randomId, const FullMsgId &itemId) { randomData.insert(randomId, itemId); } void historyUnregRandom(uint64 randomId) { randomData.remove(randomId); } FullMsgId histItemByRandom(uint64 randomId) { RandomData::const_iterator i = randomData.constFind(randomId); if (i != randomData.cend()) { return i.value(); } return FullMsgId(); } void historyRegSentData(uint64 randomId, const PeerId &peerId, const QString &text) { sentData.insert(randomId, qMakePair(peerId, text)); } void historyUnregSentData(uint64 randomId) { sentData.remove(randomId); } void histSentDataByItem(uint64 randomId, PeerId &peerId, QString &text) { QPair d = sentData.value(randomId); peerId = d.first; text = d.second; } void prepareCorners(RoundCorners index, int32 radius, const QBrush &brush, const style::color *shadow = nullptr, QImage *cors = nullptr) { Expects(::corners.size() > index); int32 r = radius * cIntRetinaFactor(), s = st::msgShadow * cIntRetinaFactor(); QImage rect(r * 3, r * 3 + (shadow ? s : 0), QImage::Format_ARGB32_Premultiplied), localCors[4]; { Painter p(&rect); PainterHighQualityEnabler hq(p); p.setCompositionMode(QPainter::CompositionMode_Source); p.fillRect(QRect(0, 0, rect.width(), rect.height()), Qt::transparent); p.setCompositionMode(QPainter::CompositionMode_SourceOver); p.setPen(Qt::NoPen); if (shadow) { p.setBrush((*shadow)->b); p.drawRoundedRect(0, s, r * 3, r * 3, r, r); } p.setBrush(brush); p.drawRoundedRect(0, 0, r * 3, r * 3, r, r); } if (!cors) cors = localCors; cors[0] = rect.copy(0, 0, r, r); cors[1] = rect.copy(r * 2, 0, r, r); cors[2] = rect.copy(0, r * 2, r, r + (shadow ? s : 0)); cors[3] = rect.copy(r * 2, r * 2, r, r + (shadow ? s : 0)); if (index != SmallMaskCorners && index != LargeMaskCorners) { for (int i = 0; i < 4; ++i) { ::corners[index].p[i] = pixmapFromImageInPlace(std::move(cors[i])); ::corners[index].p[i].setDevicePixelRatio(cRetinaFactor()); } } } void tryFontFamily(QString &family, const QString &tryFamily) { if (family.isEmpty()) { if (!QFontInfo(QFont(tryFamily)).family().trimmed().compare(tryFamily, Qt::CaseInsensitive)) { family = tryFamily; } } } void createMaskCorners() { QImage mask[4]; prepareCorners(SmallMaskCorners, st::buttonRadius, QColor(255, 255, 255), nullptr, mask); for (int i = 0; i < 4; ++i) { ::cornersMaskSmall[i] = mask[i].convertToFormat(QImage::Format_ARGB32_Premultiplied); ::cornersMaskSmall[i].setDevicePixelRatio(cRetinaFactor()); } prepareCorners(LargeMaskCorners, st::historyMessageRadius, QColor(255, 255, 255), nullptr, mask); for (int i = 0; i < 4; ++i) { ::cornersMaskLarge[i] = mask[i].convertToFormat(QImage::Format_ARGB32_Premultiplied); ::cornersMaskLarge[i].setDevicePixelRatio(cRetinaFactor()); } } void createPaletteCorners() { prepareCorners(MenuCorners, st::buttonRadius, st::menuBg); prepareCorners(BoxCorners, st::boxRadius, st::boxBg); prepareCorners(BotKbOverCorners, st::dateRadius, st::msgBotKbOverBgAdd); prepareCorners(StickerCorners, st::dateRadius, st::msgServiceBg); prepareCorners(StickerSelectedCorners, st::dateRadius, st::msgServiceBgSelected); prepareCorners(SelectedOverlaySmallCorners, st::buttonRadius, st::msgSelectOverlay); prepareCorners(SelectedOverlayLargeCorners, st::historyMessageRadius, st::msgSelectOverlay); prepareCorners(DateCorners, st::dateRadius, st::msgDateImgBg); prepareCorners(DateSelectedCorners, st::dateRadius, st::msgDateImgBgSelected); prepareCorners(InShadowCorners, st::historyMessageRadius, st::msgInShadow); prepareCorners(InSelectedShadowCorners, st::historyMessageRadius, st::msgInShadowSelected); prepareCorners(ForwardCorners, st::historyMessageRadius, st::historyForwardChooseBg); prepareCorners(MediaviewSaveCorners, st::mediaviewControllerRadius, st::mediaviewSaveMsgBg); prepareCorners(EmojiHoverCorners, st::buttonRadius, st::emojiPanHover); prepareCorners(StickerHoverCorners, st::buttonRadius, st::emojiPanHover); prepareCorners(BotKeyboardCorners, st::buttonRadius, st::botKbBg); prepareCorners(PhotoSelectOverlayCorners, st::buttonRadius, st::overviewPhotoSelectOverlay); prepareCorners(Doc1Corners, st::buttonRadius, st::msgFile1Bg); prepareCorners(Doc2Corners, st::buttonRadius, st::msgFile2Bg); prepareCorners(Doc3Corners, st::buttonRadius, st::msgFile3Bg); prepareCorners(Doc4Corners, st::buttonRadius, st::msgFile4Bg); prepareCorners(MessageInCorners, st::historyMessageRadius, st::msgInBg, &st::msgInShadow); prepareCorners(MessageInSelectedCorners, st::historyMessageRadius, st::msgInBgSelected, &st::msgInShadowSelected); prepareCorners(MessageOutCorners, st::historyMessageRadius, st::msgOutBg, &st::msgOutShadow); prepareCorners(MessageOutSelectedCorners, st::historyMessageRadius, st::msgOutBgSelected, &st::msgOutShadowSelected); } void createCorners() { ::corners.resize(RoundCornersCount); createMaskCorners(); createPaletteCorners(); } void clearCorners() { ::corners.clear(); ::cornersMap.clear(); } void initMedia() { if (!::monofont) { QString family; tryFontFamily(family, qsl("Consolas")); tryFontFamily(family, qsl("Liberation Mono")); tryFontFamily(family, qsl("Menlo")); tryFontFamily(family, qsl("Courier")); if (family.isEmpty()) family = QFontDatabase::systemFont(QFontDatabase::FixedFont).family(); ::monofont = style::font(st::normalFont->f.pixelSize(), 0, family); } Ui::Emoji::Init(); if (!::emoji) { ::emoji = new QPixmap(Ui::Emoji::Filename(Ui::Emoji::Index())); if (cRetina()) ::emoji->setDevicePixelRatio(cRetinaFactor()); } if (!::emojiLarge) { ::emojiLarge = new QPixmap(Ui::Emoji::Filename(Ui::Emoji::Index() + 1)); if (cRetina()) ::emojiLarge->setDevicePixelRatio(cRetinaFactor()); } createCorners(); using Update = Window::Theme::BackgroundUpdate; static auto subscription = Window::Theme::Background()->add_subscription([](const Update &update) { if (update.paletteChanged()) { createPaletteCorners(); if (App::main()) { App::main()->updateScrollColors(); } HistoryView::serviceColorsUpdated(); } else if (update.type == Update::Type::New) { prepareCorners(StickerCorners, st::dateRadius, st::msgServiceBg); prepareCorners(StickerSelectedCorners, st::dateRadius, st::msgServiceBgSelected); if (App::main()) { App::main()->updateScrollColors(); } HistoryView::serviceColorsUpdated(); } }); } void clearHistories() { ClickHandler::clearActive(); ClickHandler::unpressed(); if (AuthSession::Exists()) { // Clear notifications to prevent any showNotification() calls while destroying items. Auth().notifications().clearAllFast(); } histories().clear(); clearStorageImages(); cSetServerBackgrounds(WallPapers()); serviceImageCacheSize = imageCacheSize(); } void deinitMedia() { delete ::emoji; ::emoji = nullptr; delete ::emojiLarge; ::emojiLarge = nullptr; clearCorners(); MainEmojiMap.clear(); OtherEmojiMap.clear(); Data::clearGlobalStructures(); clearAllImages(); } void hoveredItem(HistoryView::Element *item) { ::hoveredItem = item; } HistoryView::Element *hoveredItem() { return ::hoveredItem; } void pressedItem(HistoryView::Element *item) { ::pressedItem = item; } HistoryView::Element *pressedItem() { return ::pressedItem; } void hoveredLinkItem(HistoryView::Element *item) { ::hoveredLinkItem = item; } HistoryView::Element *hoveredLinkItem() { return ::hoveredLinkItem; } void pressedLinkItem(HistoryView::Element *item) { ::pressedLinkItem = item; } HistoryView::Element *pressedLinkItem() { return ::pressedLinkItem; } void mousedItem(HistoryView::Element *item) { ::mousedItem = item; } HistoryView::Element *mousedItem() { return ::mousedItem; } void clearMousedItems() { hoveredItem(nullptr); pressedItem(nullptr); hoveredLinkItem(nullptr); pressedLinkItem(nullptr); mousedItem(nullptr); } const style::font &monofont() { return ::monofont; } const QPixmap &emoji() { return *::emoji; } const QPixmap &emojiLarge() { return *::emojiLarge; } const QPixmap &emojiSingle(EmojiPtr emoji, int32 fontHeight) { auto &map = (fontHeight == st::msgFont->height) ? MainEmojiMap : OtherEmojiMap[fontHeight]; auto i = map.constFind(emoji->index()); if (i == map.cend()) { auto image = QImage(Ui::Emoji::Size() + st::emojiPadding * cIntRetinaFactor() * 2, fontHeight * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); if (cRetina()) image.setDevicePixelRatio(cRetinaFactor()); image.fill(Qt::transparent); { QPainter p(&image); emojiDraw(p, emoji, st::emojiPadding * cIntRetinaFactor(), (fontHeight * cIntRetinaFactor() - Ui::Emoji::Size()) / 2); } i = map.insert(emoji->index(), App::pixmapFromImageInPlace(std::move(image))); } return i.value(); } void checkImageCacheSize() { int64 nowImageCacheSize = imageCacheSize(); if (nowImageCacheSize > serviceImageCacheSize + MemoryForImageCache) { App::forgetMedia(); Auth().data().forgetMedia(); serviceImageCacheSize = imageCacheSize(); } } bool isValidPhone(QString phone) { phone = phone.replace(QRegularExpression(qsl("[^\\d]")), QString()); return phone.length() >= 8 || phone == qsl("777") || phone == qsl("333") || phone == qsl("111") || (phone.startsWith(qsl("42")) && (phone.length() == 2 || phone.length() == 5 || phone == qsl("4242"))); } void quit() { if (quitting()) return; setLaunchState(QuitRequested); if (auto window = wnd()) { if (!Sandbox::isSavingSession()) { window->hide(); } } if (auto mainwidget = main()) { mainwidget->saveDraftToCloud(); } Messenger::QuitAttempt(); } bool quitting() { return _launchState != Launched; } LaunchState launchState() { return _launchState; } void setLaunchState(LaunchState state) { _launchState = state; } void restart() { #ifndef TDESKTOP_DISABLE_AUTOUPDATE bool updateReady = (Sandbox::updatingState() == Application::UpdatingReady); #else // !TDESKTOP_DISABLE_AUTOUPDATE bool updateReady = false; #endif // else for !TDESKTOP_DISABLE_AUTOUPDATE if (updateReady) { cSetRestartingUpdate(true); } else { cSetRestarting(true); cSetRestartingToSettings(true); } App::quit(); } QImage readImage(QByteArray data, QByteArray *format, bool opaque, bool *animated) { QByteArray tmpFormat; QImage result; QBuffer buffer(&data); if (!format) { format = &tmpFormat; } { QImageReader reader(&buffer, *format); #ifndef OS_MAC_OLD reader.setAutoTransform(true); #endif // OS_MAC_OLD if (animated) *animated = reader.supportsAnimation() && reader.imageCount() > 1; QByteArray fmt = reader.format(); if (!fmt.isEmpty()) *format = fmt; if (!reader.read(&result)) { return QImage(); } fmt = reader.format(); if (!fmt.isEmpty()) *format = fmt; } buffer.seek(0); auto fmt = QString::fromUtf8(*format).toLower(); if (fmt == "jpg" || fmt == "jpeg") { #ifdef OS_MAC_OLD if (auto exifData = exif_data_new_from_data((const uchar*)(data.constData()), data.size())) { auto byteOrder = exif_data_get_byte_order(exifData); if (auto exifEntry = exif_data_get_entry(exifData, EXIF_TAG_ORIENTATION)) { auto orientationFix = [exifEntry, byteOrder] { auto orientation = exif_get_short(exifEntry->data, byteOrder); switch (orientation) { case 2: return QTransform(-1, 0, 0, 1, 0, 0); case 3: return QTransform(-1, 0, 0, -1, 0, 0); case 4: return QTransform(1, 0, 0, -1, 0, 0); case 5: return QTransform(0, -1, -1, 0, 0, 0); case 6: return QTransform(0, 1, -1, 0, 0, 0); case 7: return QTransform(0, 1, 1, 0, 0, 0); case 8: return QTransform(0, -1, 1, 0, 0, 0); } return QTransform(); }; result = result.transformed(orientationFix()); } exif_data_free(exifData); } #endif // OS_MAC_OLD } else if (opaque) { result = Images::prepareOpaque(std::move(result)); } return result; } QImage readImage(const QString &file, QByteArray *format, bool opaque, bool *animated, QByteArray *content) { QFile f(file); if (f.size() > kImageSizeLimit || !f.open(QIODevice::ReadOnly)) { if (animated) *animated = false; return QImage(); } auto imageBytes = f.readAll(); auto result = readImage(imageBytes, format, opaque, animated); if (content && !result.isNull()) { *content = imageBytes; } return result; } QPixmap pixmapFromImageInPlace(QImage &&image) { return QPixmap::fromImage(std::move(image), Qt::ColorOnly); } void regMuted(not_null peer, TimeMs changeIn) { ::mutedPeers.insert(peer, true); App::main()->updateMutedIn(changeIn); } void unregMuted(not_null peer) { ::mutedPeers.remove(peer); } void updateMuted() { auto changeInMin = TimeMs(0); for (auto i = ::mutedPeers.begin(); i != ::mutedPeers.end();) { const auto history = App::historyLoaded(i.key()->id); const auto muteFinishesIn = i.key()->notifyMuteFinishesIn(); if (muteFinishesIn > 0) { if (history) { history->changeMute(true); } if (!changeInMin || muteFinishesIn < changeInMin) { changeInMin = muteFinishesIn; } ++i; } else { if (history) { history->changeMute(false); } i = ::mutedPeers.erase(i); } } if (changeInMin) App::main()->updateMutedIn(changeInMin); } void setProxySettings(QNetworkAccessManager &manager) { #ifndef TDESKTOP_DISABLE_NETWORK_PROXY manager.setProxy(getHttpProxySettings()); #endif // !TDESKTOP_DISABLE_NETWORK_PROXY } #ifndef TDESKTOP_DISABLE_NETWORK_PROXY QNetworkProxy getHttpProxySettings() { const ProxyData *proxy = nullptr; if (Global::started()) { proxy = (Global::ConnectionType() == dbictHttpProxy) ? (&Global::ConnectionProxy()) : nullptr; } else { proxy = Sandbox::PreLaunchProxy().host.isEmpty() ? nullptr : (&Sandbox::PreLaunchProxy()); } if (proxy) { return QNetworkProxy(QNetworkProxy::HttpProxy, proxy->host, proxy->port, proxy->user, proxy->password); } return QNetworkProxy(QNetworkProxy::DefaultProxy); } #endif // !TDESKTOP_DISABLE_NETWORK_PROXY void setProxySettings(QTcpSocket &socket) { #ifndef TDESKTOP_DISABLE_NETWORK_PROXY if (Global::ConnectionType() == dbictTcpProxy) { auto &p = Global::ConnectionProxy(); socket.setProxy(QNetworkProxy(QNetworkProxy::Socks5Proxy, p.host, p.port, p.user, p.password)); } else { socket.setProxy(QNetworkProxy(QNetworkProxy::NoProxy)); } #endif // !TDESKTOP_DISABLE_NETWORK_PROXY } void rectWithCorners(Painter &p, QRect rect, const style::color &bg, RoundCorners index, RectParts corners) { auto parts = RectPart::Top | RectPart::NoTopBottom | RectPart::Bottom | corners; roundRect(p, rect, bg, index, nullptr, parts); if ((corners & RectPart::AllCorners) != RectPart::AllCorners) { const auto size = ::corners[index].p[0].width() / cIntRetinaFactor(); if (!(corners & RectPart::TopLeft)) { p.fillRect(rect.x(), rect.y(), size, size, bg); } if (!(corners & RectPart::TopRight)) { p.fillRect(rect.x() + rect.width() - size, rect.y(), size, size, bg); } if (!(corners & RectPart::BottomLeft)) { p.fillRect(rect.x(), rect.y() + rect.height() - size, size, size, bg); } if (!(corners & RectPart::BottomRight)) { p.fillRect(rect.x() + rect.width() - size, rect.y() + rect.height() - size, size, size, bg); } } } void complexOverlayRect(Painter &p, QRect rect, ImageRoundRadius radius, RectParts corners) { if (radius == ImageRoundRadius::Ellipse) { PainterHighQualityEnabler hq(p); p.setPen(Qt::NoPen); p.setBrush(p.textPalette().selectOverlay); p.drawEllipse(rect); } else { auto overlayCorners = (radius == ImageRoundRadius::Small) ? SelectedOverlaySmallCorners : SelectedOverlayLargeCorners; const auto bg = p.textPalette().selectOverlay; rectWithCorners(p, rect, bg, overlayCorners, corners); } } void complexLocationRect(Painter &p, QRect rect, ImageRoundRadius radius, RectParts corners) { rectWithCorners(p, rect, st::msgInBg, MessageInCorners, corners); } QImage *cornersMask(ImageRoundRadius radius) { switch (radius) { case ImageRoundRadius::Large: return ::cornersMaskLarge; case ImageRoundRadius::Small: default: break; } return ::cornersMaskSmall; } void roundRect(Painter &p, int32 x, int32 y, int32 w, int32 h, style::color bg, const CornersPixmaps &corner, const style::color *shadow, RectParts parts) { auto cornerWidth = corner.p[0].width() / cIntRetinaFactor(); auto cornerHeight = corner.p[0].height() / cIntRetinaFactor(); if (w < 2 * cornerWidth || h < 2 * cornerHeight) return; if (w > 2 * cornerWidth) { if (parts & RectPart::Top) { p.fillRect(x + cornerWidth, y, w - 2 * cornerWidth, cornerHeight, bg); } if (parts & RectPart::Bottom) { p.fillRect(x + cornerWidth, y + h - cornerHeight, w - 2 * cornerWidth, cornerHeight, bg); if (shadow) { p.fillRect(x + cornerWidth, y + h, w - 2 * cornerWidth, st::msgShadow, *shadow); } } } if (h > 2 * cornerHeight) { if ((parts & RectPart::NoTopBottom) == RectPart::NoTopBottom) { p.fillRect(x, y + cornerHeight, w, h - 2 * cornerHeight, bg); } else { if (parts & RectPart::Left) { p.fillRect(x, y + cornerHeight, cornerWidth, h - 2 * cornerHeight, bg); } if ((parts & RectPart::Center) && w > 2 * cornerWidth) { p.fillRect(x + cornerWidth, y + cornerHeight, w - 2 * cornerWidth, h - 2 * cornerHeight, bg); } if (parts & RectPart::Right) { p.fillRect(x + w - cornerWidth, y + cornerHeight, cornerWidth, h - 2 * cornerHeight, bg); } } } if (parts & RectPart::TopLeft) { p.drawPixmap(x, y, corner.p[0]); } if (parts & RectPart::TopRight) { p.drawPixmap(x + w - cornerWidth, y, corner.p[1]); } if (parts & RectPart::BottomLeft) { p.drawPixmap(x, y + h - cornerHeight, corner.p[2]); } if (parts & RectPart::BottomRight) { p.drawPixmap(x + w - cornerWidth, y + h - cornerHeight, corner.p[3]); } } void roundRect(Painter &p, int32 x, int32 y, int32 w, int32 h, style::color bg, RoundCorners index, const style::color *shadow, RectParts parts) { roundRect(p, x, y, w, h, bg, ::corners[index], shadow, parts); } void roundShadow(Painter &p, int32 x, int32 y, int32 w, int32 h, style::color shadow, RoundCorners index, RectParts parts) { auto &corner = ::corners[index]; auto cornerWidth = corner.p[0].width() / cIntRetinaFactor(); auto cornerHeight = corner.p[0].height() / cIntRetinaFactor(); if (parts & RectPart::Bottom) { p.fillRect(x + cornerWidth, y + h, w - 2 * cornerWidth, st::msgShadow, shadow); } if (parts & RectPart::BottomLeft) { p.fillRect(x, y + h - cornerHeight, cornerWidth, st::msgShadow, shadow); p.drawPixmap(x, y + h - cornerHeight + st::msgShadow, corner.p[2]); } if (parts & RectPart::BottomRight) { p.fillRect(x + w - cornerWidth, y + h - cornerHeight, cornerWidth, st::msgShadow, shadow); p.drawPixmap(x + w - cornerWidth, y + h - cornerHeight + st::msgShadow, corner.p[3]); } } void roundRect(Painter &p, int32 x, int32 y, int32 w, int32 h, style::color bg, ImageRoundRadius radius, RectParts parts) { auto colorKey = ((uint32(bg->c.alpha()) & 0xFF) << 24) | ((uint32(bg->c.red()) & 0xFF) << 16) | ((uint32(bg->c.green()) & 0xFF) << 8) | ((uint32(bg->c.blue()) & 0xFF) << 24); auto i = cornersMap.find(colorKey); if (i == cornersMap.cend()) { QImage images[4]; switch (radius) { case ImageRoundRadius::Small: prepareCorners(SmallMaskCorners, st::buttonRadius, bg, nullptr, images); break; case ImageRoundRadius::Large: prepareCorners(LargeMaskCorners, st::historyMessageRadius, bg, nullptr, images); break; default: p.fillRect(x, y, w, h, bg); return; } CornersPixmaps pixmaps; for (int j = 0; j < 4; ++j) { pixmaps.p[j] = pixmapFromImageInPlace(std::move(images[j])); pixmaps.p[j].setDevicePixelRatio(cRetinaFactor()); } i = cornersMap.insert(colorKey, pixmaps); } roundRect(p, x, y, w, h, bg, i.value(), nullptr, parts); } WallPapers gServerBackgrounds; } tdesktop-1.2.17/Telegram/SourceFiles/app.h000066400000000000000000000227641326245125100203670ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "core/basic_types.h" #include "data/data_types.h" #include "data/data_peer.h" enum NewMessageType : char; class Messenger; class MainWindow; class MainWidget; class LocationCoords; struct LocationData; class HistoryItem; class History; class Histories; namespace HistoryView { class Element; } // namespace HistoryView using HistoryItemsMap = base::flat_set>; using GifItems = QHash; enum RoundCorners { SmallMaskCorners = 0x00, // for images LargeMaskCorners, BoxCorners, MenuCorners, BotKbOverCorners, StickerCorners, StickerSelectedCorners, SelectedOverlaySmallCorners, SelectedOverlayLargeCorners, DateCorners, DateSelectedCorners, ForwardCorners, MediaviewSaveCorners, EmojiHoverCorners, StickerHoverCorners, BotKeyboardCorners, PhotoSelectOverlayCorners, Doc1Corners, Doc2Corners, Doc3Corners, Doc4Corners, InShadowCorners, // for photos without bg InSelectedShadowCorners, MessageInCorners, // with shadow MessageInSelectedCorners, MessageOutCorners, MessageOutSelectedCorners, RoundCornersCount }; namespace App { MainWindow *wnd(); MainWidget *main(); bool passcoded(); void logOut(); QString formatPhone(QString phone); UserData *feedUser(const MTPUser &user); UserData *feedUsers(const MTPVector &users); // returns last user PeerData *feedChat(const MTPChat &chat); PeerData *feedChats(const MTPVector &chats); // returns last chat void feedParticipants(const MTPChatParticipants &p, bool requestBotInfos); void feedParticipantAdd(const MTPDupdateChatParticipantAdd &d); void feedParticipantDelete(const MTPDupdateChatParticipantDelete &d); void feedChatAdmins(const MTPDupdateChatAdmins &d); void feedParticipantAdmin(const MTPDupdateChatParticipantAdmin &d); bool checkEntitiesAndViewsUpdate(const MTPDmessage &m); // returns true if item found and it is not detached void updateEditedMessage(const MTPMessage &m); void addSavedGif(DocumentData *doc); void checkSavedGif(HistoryItem *item); void feedMsgs(const QVector &msgs, NewMessageType type); void feedMsgs(const MTPVector &msgs, NewMessageType type); void feedInboxRead(const PeerId &peer, MsgId upTo); void feedOutboxRead(const PeerId &peer, MsgId upTo, TimeId when); void feedWereDeleted(ChannelId channelId, const QVector &msgsIds); void feedUserLink(MTPint userId, const MTPContactLink &myLink, const MTPContactLink &foreignLink); ImagePtr image(const MTPPhotoSize &size); PeerData *peer(const PeerId &id, PeerData::LoadedStatus restriction = PeerData::NotLoaded); inline UserData *user(const PeerId &id, PeerData::LoadedStatus restriction = PeerData::NotLoaded) { return asUser(peer(id, restriction)); } inline ChatData *chat(const PeerId &id, PeerData::LoadedStatus restriction = PeerData::NotLoaded) { return asChat(peer(id, restriction)); } inline ChannelData *channel(const PeerId &id, PeerData::LoadedStatus restriction = PeerData::NotLoaded) { return asChannel(peer(id, restriction)); } inline UserData *user(UserId userId, PeerData::LoadedStatus restriction = PeerData::NotLoaded) { return asUser(peer(peerFromUser(userId), restriction)); } inline ChatData *chat(ChatId chatId, PeerData::LoadedStatus restriction = PeerData::NotLoaded) { return asChat(peer(peerFromChat(chatId), restriction)); } inline ChannelData *channel(ChannelId channelId, PeerData::LoadedStatus restriction = PeerData::NotLoaded) { return asChannel(peer(peerFromChannel(channelId), restriction)); } inline PeerData *peerLoaded(const PeerId &id) { return peer(id, PeerData::FullLoaded); } inline UserData *userLoaded(const PeerId &id) { return user(id, PeerData::FullLoaded); } inline ChatData *chatLoaded(const PeerId &id) { return chat(id, PeerData::FullLoaded); } inline ChannelData *channelLoaded(const PeerId &id) { return channel(id, PeerData::FullLoaded); } inline UserData *userLoaded(UserId userId) { return user(userId, PeerData::FullLoaded); } inline ChatData *chatLoaded(ChatId chatId) { return chat(chatId, PeerData::FullLoaded); } inline ChannelData *channelLoaded(ChannelId channelId) { return channel(channelId, PeerData::FullLoaded); } void enumerateUsers(base::lambda action); UserData *self(); PeerData *peerByName(const QString &username); QString peerName(const PeerData *peer, bool forDialogs = false); LocationData *location(const LocationCoords &coords); void forgetMedia(); MTPPhoto photoFromUserPhoto(MTPint userId, MTPint date, const MTPUserProfilePhoto &photo); Histories &histories(); not_null history(const PeerId &peer); History *historyLoaded(const PeerId &peer); HistoryItem *histItemById(ChannelId channelId, MsgId itemId); inline not_null history(const PeerData *peer) { Assert(peer != nullptr); return history(peer->id); } inline History *historyLoaded(const PeerData *peer) { return peer ? historyLoaded(peer->id) : nullptr; } inline HistoryItem *histItemById(const ChannelData *channel, MsgId itemId) { return histItemById(channel ? peerToChannel(channel->id) : 0, itemId); } inline HistoryItem *histItemById(const FullMsgId &msgId) { return histItemById(msgId.channel, msgId.msg); } void historyRegItem(not_null item); void historyUnregItem(not_null item); void historyUpdateDependent(not_null item); void historyClearMsgs(); void historyClearItems(); void historyRegDependency(HistoryItem *dependent, HistoryItem *dependency); void historyUnregDependency(HistoryItem *dependent, HistoryItem *dependency); void historyRegRandom(uint64 randomId, const FullMsgId &itemId); void historyUnregRandom(uint64 randomId); FullMsgId histItemByRandom(uint64 randomId); void historyRegSentData(uint64 randomId, const PeerId &peerId, const QString &text); void historyUnregSentData(uint64 randomId); void histSentDataByItem(uint64 randomId, PeerId &peerId, QString &text); void hoveredItem(HistoryView::Element *item); HistoryView::Element *hoveredItem(); void pressedItem(HistoryView::Element *item); HistoryView::Element *pressedItem(); void hoveredLinkItem(HistoryView::Element *item); HistoryView::Element *hoveredLinkItem(); void pressedLinkItem(HistoryView::Element *item); HistoryView::Element *pressedLinkItem(); void mousedItem(HistoryView::Element *item); HistoryView::Element *mousedItem(); void clearMousedItems(); const style::font &monofont(); const QPixmap &emoji(); const QPixmap &emojiLarge(); const QPixmap &emojiSingle(EmojiPtr emoji, int32 fontHeight); void clearHistories(); void initMedia(); void deinitMedia(); void checkImageCacheSize(); bool isValidPhone(QString phone); enum LaunchState { Launched = 0, QuitRequested = 1, QuitProcessed = 2, }; void quit(); bool quitting(); LaunchState launchState(); void setLaunchState(LaunchState state); void restart(); constexpr auto kFileSizeLimit = 1500 * 1024 * 1024; // Load files up to 1500mb constexpr auto kImageSizeLimit = 64 * 1024 * 1024; // Open images up to 64mb jpg/png/gif QImage readImage(QByteArray data, QByteArray *format = nullptr, bool opaque = true, bool *animated = nullptr); QImage readImage(const QString &file, QByteArray *format = nullptr, bool opaque = true, bool *animated = nullptr, QByteArray *content = 0); QPixmap pixmapFromImageInPlace(QImage &&image); void regMuted(not_null peer, TimeMs changeIn); void unregMuted(not_null peer); void updateMuted(); void setProxySettings(QNetworkAccessManager &manager); #ifndef TDESKTOP_DISABLE_NETWORK_PROXY QNetworkProxy getHttpProxySettings(); #endif // !TDESKTOP_DISABLE_NETWORK_PROXY void setProxySettings(QTcpSocket &socket); void complexOverlayRect(Painter &p, QRect rect, ImageRoundRadius radius, RectParts corners); void complexLocationRect(Painter &p, QRect rect, ImageRoundRadius radius, RectParts corners); QImage *cornersMask(ImageRoundRadius radius); void roundRect(Painter &p, int32 x, int32 y, int32 w, int32 h, style::color bg, RoundCorners index, const style::color *shadow = nullptr, RectParts parts = RectPart::Full); inline void roundRect(Painter &p, const QRect &rect, style::color bg, RoundCorners index, const style::color *shadow = nullptr, RectParts parts = RectPart::Full) { return roundRect(p, rect.x(), rect.y(), rect.width(), rect.height(), bg, index, shadow, parts); } void roundShadow(Painter &p, int32 x, int32 y, int32 w, int32 h, style::color shadow, RoundCorners index, RectParts parts = RectPart::Full); inline void roundShadow(Painter &p, const QRect &rect, style::color shadow, RoundCorners index, RectParts parts = RectPart::Full) { return roundShadow(p, rect.x(), rect.y(), rect.width(), rect.height(), shadow, index, parts); } void roundRect(Painter &p, int32 x, int32 y, int32 w, int32 h, style::color bg, ImageRoundRadius radius, RectParts parts = RectPart::Full); inline void roundRect(Painter &p, const QRect &rect, style::color bg, ImageRoundRadius radius, RectParts parts = RectPart::Full) { return roundRect(p, rect.x(), rect.y(), rect.width(), rect.height(), bg, radius, parts); } struct WallPaper { WallPaper(int32 id, ImagePtr thumb, ImagePtr full) : id(id), thumb(thumb), full(full) { } int32 id; ImagePtr thumb; ImagePtr full; }; typedef QList WallPapers; DeclareSetting(WallPapers, ServerBackgrounds); }; tdesktop-1.2.17/Telegram/SourceFiles/application.cpp000066400000000000000000000452231326245125100224400ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "application.h" #include "platform/platform_specific.h" #include "mainwidget.h" #include "mainwindow.h" #include "storage/localstorage.h" #include "autoupdater.h" #include "window/notifications_manager.h" #include "core/crash_reports.h" #include "messenger.h" #include "base/timer.h" #include "core/crash_report_window.h" namespace { QChar _toHex(ushort v) { v = v & 0x000F; return QChar::fromLatin1((v >= 10) ? ('a' + (v - 10)) : ('0' + v)); } ushort _fromHex(QChar c) { return ((c.unicode() >= uchar('a')) ? (c.unicode() - uchar('a') + 10) : (c.unicode() - uchar('0'))) & 0x000F; } QString _escapeTo7bit(const QString &str) { QString result; result.reserve(str.size() * 2); for (int i = 0, l = str.size(); i != l; ++i) { QChar ch(str.at(i)); ushort uch(ch.unicode()); if (uch < 32 || uch > 127 || uch == ushort(uchar('%'))) { result.append('%').append(_toHex(uch >> 12)).append(_toHex(uch >> 8)).append(_toHex(uch >> 4)).append(_toHex(uch)); } else { result.append(ch); } } return result; } QString _escapeFrom7bit(const QString &str) { QString result; result.reserve(str.size()); for (int i = 0, l = str.size(); i != l; ++i) { QChar ch(str.at(i)); if (ch == QChar::fromLatin1('%') && i + 4 < l) { result.append(QChar(ushort((_fromHex(str.at(i + 1)) << 12) | (_fromHex(str.at(i + 2)) << 8) | (_fromHex(str.at(i + 3)) << 4) | _fromHex(str.at(i + 4))))); i += 4; } else { result.append(ch); } } return result; } } // namespace Application::Application( not_null launcher, int &argc, char **argv) : QApplication(argc, argv) , _launcher(launcher) { const auto d = QFile::encodeName(QDir(cWorkingDir()).absolutePath()); char h[33] = { 0 }; hashMd5Hex(d.constData(), d.size(), h); #ifndef OS_MAC_STORE _localServerName = psServerPrefix() + h + '-' + cGUIDStr(); #else // OS_MAC_STORE h[4] = 0; // use only first 4 chars _localServerName = psServerPrefix() + h; #endif // OS_MAC_STORE connect(&_localSocket, SIGNAL(connected()), this, SLOT(socketConnected())); connect(&_localSocket, SIGNAL(disconnected()), this, SLOT(socketDisconnected())); connect(&_localSocket, SIGNAL(error(QLocalSocket::LocalSocketError)), this, SLOT(socketError(QLocalSocket::LocalSocketError))); connect(&_localSocket, SIGNAL(bytesWritten(qint64)), this, SLOT(socketWritten(qint64))); connect(&_localSocket, SIGNAL(readyRead()), this, SLOT(socketReading())); connect(&_localServer, SIGNAL(newConnection()), this, SLOT(newInstanceConnected())); QTimer::singleShot(0, this, SLOT(startApplication())); connect(this, SIGNAL(aboutToQuit()), this, SLOT(closeApplication())); #ifndef TDESKTOP_DISABLE_AUTOUPDATE _updateCheckTimer.create(this); connect(_updateCheckTimer, SIGNAL(timeout()), this, SLOT(updateCheck())); connect(this, SIGNAL(updateFailed()), this, SLOT(onUpdateFailed())); connect(this, SIGNAL(updateReady()), this, SLOT(onUpdateReady())); #endif // !TDESKTOP_DISABLE_AUTOUPDATE if (cManyInstance()) { LOG(("Many instance allowed, starting...")); singleInstanceChecked(); } else { LOG(("Connecting local socket to %1...").arg(_localServerName)); _localSocket.connectToServer(_localServerName); } } Application::~Application() = default; bool Application::event(QEvent *e) { if (e->type() == QEvent::Close) { App::quit(); } return QApplication::event(e); } void Application::socketConnected() { LOG(("Socket connected, this is not the first application instance, sending show command...")); _secondInstance = true; QString commands; const QStringList &lst(cSendPaths()); for (QStringList::const_iterator i = lst.cbegin(), e = lst.cend(); i != e; ++i) { commands += qsl("SEND:") + _escapeTo7bit(*i) + ';'; } if (!cStartUrl().isEmpty()) { commands += qsl("OPEN:") + _escapeTo7bit(cStartUrl()) + ';'; } commands += qsl("CMD:show;"); DEBUG_LOG(("Application Info: writing commands %1").arg(commands)); _localSocket.write(commands.toLatin1()); } void Application::socketWritten(qint64/* bytes*/) { if (_localSocket.state() != QLocalSocket::ConnectedState) { LOG(("Socket is not connected %1").arg(_localSocket.state())); return; } if (_localSocket.bytesToWrite()) { return; } LOG(("Show command written, waiting response...")); } void Application::socketReading() { if (_localSocket.state() != QLocalSocket::ConnectedState) { LOG(("Socket is not connected %1").arg(_localSocket.state())); return; } _localSocketReadData.append(_localSocket.readAll()); if (QRegularExpression("RES:(\\d+);").match(_localSocketReadData).hasMatch()) { uint64 pid = _localSocketReadData.mid(4, _localSocketReadData.length() - 5).toULongLong(); psActivateProcess(pid); LOG(("Show command response received, pid = %1, activating and quitting...").arg(pid)); return App::quit(); } } void Application::socketError(QLocalSocket::LocalSocketError e) { if (App::quitting()) return; if (_secondInstance) { LOG(("Could not write show command, error %1, quitting...").arg(e)); return App::quit(); } if (e == QLocalSocket::ServerNotFoundError) { LOG(("This is the only instance of Telegram, starting server and app...")); } else { LOG(("Socket connect error %1, starting server and app...").arg(e)); } _localSocket.close(); // Local server does not work in WinRT build. #ifndef Q_OS_WINRT psCheckLocalSocket(_localServerName); if (!_localServer.listen(_localServerName)) { LOG(("Failed to start listening to %1 server, error %2").arg(_localServerName).arg(int(_localServer.serverError()))); return App::quit(); } #endif // !Q_OS_WINRT #ifndef TDESKTOP_DISABLE_AUTOUPDATE if (!cNoStartUpdate() && checkReadyUpdate()) { cSetRestartingUpdate(true); DEBUG_LOG(("Application Info: installing update instead of starting app...")); return App::quit(); } #endif // !TDESKTOP_DISABLE_AUTOUPDATE singleInstanceChecked(); } void Application::singleInstanceChecked() { if (cManyInstance()) { Logs::multipleInstances(); } Sandbox::start(); if (!Logs::started() || (!cManyInstance() && !Logs::instanceChecked())) { new NotStartedWindow(); } else { const auto status = CrashReports::Start(); if (status == CrashReports::CantOpen) { new NotStartedWindow(); } else if (status == CrashReports::LastCrashed) { if (Sandbox::LastCrashDump().isEmpty()) { // don't handle bad closing for now if (CrashReports::Restart() == CrashReports::CantOpen) { new NotStartedWindow(); } else { Sandbox::launch(); } } else { new LastCrashedWindow(); } } else { Sandbox::launch(); } } } void Application::socketDisconnected() { if (_secondInstance) { DEBUG_LOG(("Application Error: socket disconnected before command response received, quitting...")); return App::quit(); } } void Application::newInstanceConnected() { DEBUG_LOG(("Application Info: new local socket connected")); for (QLocalSocket *client = _localServer.nextPendingConnection(); client; client = _localServer.nextPendingConnection()) { _localClients.push_back(LocalClient(client, QByteArray())); connect(client, SIGNAL(readyRead()), this, SLOT(readClients())); connect(client, SIGNAL(disconnected()), this, SLOT(removeClients())); } } void Application::readClients() { // This method can be called before Messenger is constructed. QString startUrl; QStringList toSend; for (LocalClients::iterator i = _localClients.begin(), e = _localClients.end(); i != e; ++i) { i->second.append(i->first->readAll()); if (i->second.size()) { QString cmds(QString::fromLatin1(i->second)); int32 from = 0, l = cmds.length(); for (int32 to = cmds.indexOf(QChar(';'), from); to >= from; to = (from < l) ? cmds.indexOf(QChar(';'), from) : -1) { QStringRef cmd(&cmds, from, to - from); if (cmd.startsWith(qsl("CMD:"))) { Sandbox::execExternal(cmds.mid(from + 4, to - from - 4)); QByteArray response(qsl("RES:%1;").arg(QCoreApplication::applicationPid()).toLatin1()); i->first->write(response.data(), response.size()); } else if (cmd.startsWith(qsl("SEND:"))) { if (cSendPaths().isEmpty()) { toSend.append(_escapeFrom7bit(cmds.mid(from + 5, to - from - 5))); } } else if (cmd.startsWith(qsl("OPEN:"))) { if (cStartUrl().isEmpty()) { startUrl = _escapeFrom7bit(cmds.mid(from + 5, to - from - 5)).mid(0, 8192); } } else { LOG(("Application Error: unknown command %1 passed in local socket").arg(QString(cmd.constData(), cmd.length()))); } from = to + 1; } if (from > 0) { i->second = i->second.mid(from); } } } if (!toSend.isEmpty()) { QStringList paths(cSendPaths()); paths.append(toSend); cSetSendPaths(paths); } if (!cSendPaths().isEmpty()) { if (App::wnd()) { App::wnd()->sendPaths(); } } if (!startUrl.isEmpty()) { cSetStartUrl(startUrl); } if (auto messenger = Messenger::InstancePointer()) { messenger->checkStartUrl(); } } void Application::removeClients() { DEBUG_LOG(("Application Info: remove clients slot called, clients %1").arg(_localClients.size())); for (LocalClients::iterator i = _localClients.begin(), e = _localClients.end(); i != e;) { if (i->first->state() != QLocalSocket::ConnectedState) { DEBUG_LOG(("Application Info: removing client")); i = _localClients.erase(i); e = _localClients.end(); } else { ++i; } } } void Application::startApplication() { if (App::quitting()) { quit(); } } void Application::createMessenger() { Expects(!App::quitting()); _messengerInstance = std::make_unique(_launcher); } void Application::closeApplication() { if (App::launchState() == App::QuitProcessed) return; App::setLaunchState(App::QuitProcessed); _messengerInstance.reset(); Sandbox::finish(); _localServer.close(); for (LocalClients::iterator i = _localClients.begin(), e = _localClients.end(); i != e; ++i) { disconnect(i->first, SIGNAL(disconnected()), this, SLOT(removeClients())); i->first->close(); } _localClients.clear(); _localSocket.close(); #ifndef TDESKTOP_DISABLE_AUTOUPDATE delete _updateReply; _updateReply = 0; if (_updateChecker) _updateChecker->deleteLater(); _updateChecker = 0; if (_updateThread) { _updateThread->quit(); } _updateThread = 0; #endif // !TDESKTOP_DISABLE_AUTOUPDATE } #ifndef TDESKTOP_DISABLE_AUTOUPDATE void Application::updateCheck() { startUpdateCheck(false); } void Application::updateGotCurrent() { if (!_updateReply || _updateThread) return; cSetLastUpdateCheck(unixtime()); QRegularExpressionMatch m = QRegularExpression(qsl("^\\s*(\\d+)\\s*:\\s*([\\x21-\\x7f]+)\\s*$")).match(QString::fromLatin1(_updateReply->readAll())); if (m.hasMatch()) { uint64 currentVersion = m.captured(1).toULongLong(); QString url = m.captured(2); bool betaVersion = false; if (url.startsWith(qstr("beta_"))) { betaVersion = true; url = url.mid(5) + '_' + countBetaVersionSignature(currentVersion); } if ((!betaVersion || cBetaVersion()) && currentVersion > (betaVersion ? cBetaVersion() : uint64(AppVersion))) { _updateThread = new QThread(); connect(_updateThread, SIGNAL(finished()), _updateThread, SLOT(deleteLater())); _updateChecker = new UpdateChecker(_updateThread, url); _updateThread->start(); } } if (_updateReply) _updateReply->deleteLater(); _updateReply = 0; if (!_updateThread) { QDir updates(cWorkingDir() + "tupdates"); if (updates.exists()) { QFileInfoList list = updates.entryInfoList(QDir::Files); for (QFileInfoList::iterator i = list.begin(), e = list.end(); i != e; ++i) { if (QRegularExpression("^(tupdate|tmacupd|tmac32upd|tlinuxupd|tlinux32upd)\\d+(_[a-z\\d]+)?$", QRegularExpression::CaseInsensitiveOption).match(i->fileName()).hasMatch()) { QFile(i->absoluteFilePath()).remove(); } } } emit updateLatest(); } startUpdateCheck(true); Local::writeSettings(); } void Application::updateFailedCurrent(QNetworkReply::NetworkError e) { LOG(("App Error: could not get current version (update check): %1").arg(e)); if (_updateReply) _updateReply->deleteLater(); _updateReply = 0; emit updateFailed(); startUpdateCheck(true); } void Application::onUpdateReady() { if (_updateChecker) { _updateChecker->deleteLater(); _updateChecker = nullptr; } _updateCheckTimer->stop(); cSetLastUpdateCheck(unixtime()); Local::writeSettings(); } void Application::onUpdateFailed() { if (_updateChecker) { _updateChecker->deleteLater(); _updateChecker = 0; if (_updateThread) _updateThread->quit(); _updateThread = 0; } cSetLastUpdateCheck(unixtime()); Local::writeSettings(); } Application::UpdatingState Application::updatingState() { if (!_updateThread) return Application::UpdatingNone; if (!_updateChecker) return Application::UpdatingReady; return Application::UpdatingDownload; } int32 Application::updatingSize() { if (!_updateChecker) return 0; return _updateChecker->size(); } int32 Application::updatingReady() { if (!_updateChecker) return 0; return _updateChecker->ready(); } void Application::stopUpdate() { if (_updateReply) { _updateReply->abort(); _updateReply->deleteLater(); _updateReply = 0; } if (_updateChecker) { _updateChecker->deleteLater(); _updateChecker = 0; if (_updateThread) _updateThread->quit(); _updateThread = 0; } } void Application::startUpdateCheck(bool forceWait) { if (!Sandbox::started()) return; _updateCheckTimer->stop(); if (_updateThread || _updateReply || !cAutoUpdate() || cExeName().isEmpty()) return; int32 constDelay = cBetaVersion() ? 600 : UpdateDelayConstPart, randDelay = cBetaVersion() ? 300 : UpdateDelayRandPart; int32 updateInSecs = cLastUpdateCheck() + constDelay + int32(rand() % randDelay) - unixtime(); bool sendRequest = (updateInSecs <= 0 || updateInSecs > (constDelay + randDelay)); if (!sendRequest && !forceWait) { QDir updates(cWorkingDir() + "tupdates"); if (updates.exists()) { QFileInfoList list = updates.entryInfoList(QDir::Files); for (QFileInfoList::iterator i = list.begin(), e = list.end(); i != e; ++i) { if (QRegularExpression("^(tupdate|tmacupd|tmac32upd|tlinuxupd|tlinux32upd)\\d+(_[a-z\\d]+)?$", QRegularExpression::CaseInsensitiveOption).match(i->fileName()).hasMatch()) { sendRequest = true; } } } } if (cManyInstance() && !cDebug()) return; // only main instance is updating if (sendRequest) { QUrl url(cUpdateURL()); if (cBetaVersion()) { url.setQuery(qsl("version=%1&beta=%2").arg(AppVersion).arg(cBetaVersion())); } else if (cAlphaVersion()) { url.setQuery(qsl("version=%1&alpha=1").arg(AppVersion)); } else { url.setQuery(qsl("version=%1").arg(AppVersion)); } QString u = url.toString(); QNetworkRequest checkVersion(url); if (_updateReply) _updateReply->deleteLater(); App::setProxySettings(_updateManager); _updateReply = _updateManager.get(checkVersion); connect(_updateReply, SIGNAL(finished()), this, SLOT(updateGotCurrent())); connect(_updateReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(updateFailedCurrent(QNetworkReply::NetworkError))); emit updateChecking(); } else { _updateCheckTimer->start((updateInSecs + 5) * 1000); } } #endif // !TDESKTOP_DISABLE_AUTOUPDATE inline Application *application() { return qobject_cast(QApplication::instance()); } namespace Sandbox { QRect availableGeometry() { if (auto a = application()) { return a->desktop()->availableGeometry(); } return QDesktopWidget().availableGeometry(); } QRect screenGeometry(const QPoint &p) { if (auto a = application()) { return a->desktop()->screenGeometry(p); } return QDesktopWidget().screenGeometry(p); } void setActiveWindow(QWidget *window) { if (auto a = application()) { a->setActiveWindow(window); } } bool isSavingSession() { if (auto a = application()) { return a->isSavingSession(); } return false; } void execExternal(const QString &cmd) { DEBUG_LOG(("Application Info: executing external command '%1'").arg(cmd)); if (cmd == "show") { if (App::wnd()) { App::wnd()->activate(); } else if (PreLaunchWindow::instance()) { PreLaunchWindow::instance()->activate(); } } } void adjustSingleTimers() { if (auto a = application()) { a->adjustSingleTimers(); } base::Timer::Adjust(); } #ifndef TDESKTOP_DISABLE_AUTOUPDATE void startUpdateCheck() { if (auto a = application()) { return a->startUpdateCheck(false); } } void stopUpdate() { if (auto a = application()) { return a->stopUpdate(); } } Application::UpdatingState updatingState() { if (auto a = application()) { return a->updatingState(); } return Application::UpdatingNone; } int32 updatingSize() { if (auto a = application()) { return a->updatingSize(); } return 0; } int32 updatingReady() { if (auto a = application()) { return a->updatingReady(); } return 0; } void updateChecking() { if (auto a = application()) { emit a->updateChecking(); } } void updateLatest() { if (auto a = application()) { emit a->updateLatest(); } } void updateProgress(qint64 ready, qint64 total) { if (auto a = application()) { emit a->updateProgress(ready, total); } } void updateFailed() { if (auto a = application()) { emit a->updateFailed(); } } void updateReady() { if (auto a = application()) { emit a->updateReady(); } } #endif // !TDESKTOP_DISABLE_AUTOUPDATE void connect(const char *signal, QObject *object, const char *method) { if (auto a = application()) { a->connect(a, signal, object, method); } } void launch() { Assert(application() != 0); float64 dpi = Application::primaryScreen()->logicalDotsPerInch(); if (dpi <= 108) { // 0-96-108 cSetScreenScale(dbisOne); } else if (dpi <= 132) { // 108-120-132 cSetScreenScale(dbisOneAndQuarter); } else if (dpi <= 168) { // 132-144-168 cSetScreenScale(dbisOneAndHalf); } else { // 168-192-inf cSetScreenScale(dbisTwo); } auto devicePixelRatio = application()->devicePixelRatio(); if (devicePixelRatio > 1.) { if ((cPlatform() != dbipMac && cPlatform() != dbipMacOld) || (devicePixelRatio != 2.)) { LOG(("Found non-trivial Device Pixel Ratio: %1").arg(devicePixelRatio)); LOG(("Environmental variables: QT_DEVICE_PIXEL_RATIO='%1'").arg(QString::fromLatin1(qgetenv("QT_DEVICE_PIXEL_RATIO")))); LOG(("Environmental variables: QT_SCALE_FACTOR='%1'").arg(QString::fromLatin1(qgetenv("QT_SCALE_FACTOR")))); LOG(("Environmental variables: QT_AUTO_SCREEN_SCALE_FACTOR='%1'").arg(QString::fromLatin1(qgetenv("QT_AUTO_SCREEN_SCALE_FACTOR")))); LOG(("Environmental variables: QT_SCREEN_SCALE_FACTORS='%1'").arg(QString::fromLatin1(qgetenv("QT_SCREEN_SCALE_FACTORS")))); } cSetRetina(true); cSetRetinaFactor(devicePixelRatio); cSetIntRetinaFactor(int32(cRetinaFactor())); cSetConfigScale(dbisOne); cSetRealScale(dbisOne); } application()->createMessenger(); } } // namespace Sandbox tdesktop-1.2.17/Telegram/SourceFiles/application.h000066400000000000000000000055421326245125100221050ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once class UpdateChecker; namespace Core { class Launcher; } // namespace Core class Application : public QApplication { Q_OBJECT public: Application(not_null launcher, int &argc, char **argv); bool event(QEvent *e) override; void createMessenger(); ~Application(); signals: void adjustSingleTimers(); // Single instance application public slots: void socketConnected(); void socketError(QLocalSocket::LocalSocketError e); void socketDisconnected(); void socketWritten(qint64 bytes); void socketReading(); void newInstanceConnected(); void readClients(); void removeClients(); void startApplication(); // will be done in exec() void closeApplication(); // will be done in aboutToQuit() private: typedef QPair LocalClient; typedef QList LocalClients; not_null _launcher; std::unique_ptr _messengerInstance; QString _localServerName, _localSocketReadData; QLocalServer _localServer; QLocalSocket _localSocket; LocalClients _localClients; bool _secondInstance = false; void singleInstanceChecked(); #ifndef TDESKTOP_DISABLE_AUTOUPDATE // Autoupdating public: void startUpdateCheck(bool forceWait); void stopUpdate(); enum UpdatingState { UpdatingNone, UpdatingDownload, UpdatingReady, }; UpdatingState updatingState(); int32 updatingSize(); int32 updatingReady(); signals: void updateChecking(); void updateLatest(); void updateProgress(qint64 ready, qint64 total); void updateReady(); void updateFailed(); public slots: void updateCheck(); void updateGotCurrent(); void updateFailedCurrent(QNetworkReply::NetworkError e); void onUpdateReady(); void onUpdateFailed(); private: object_ptr _updateCheckTimer = { nullptr }; QNetworkReply *_updateReply = nullptr; QNetworkAccessManager _updateManager; QThread *_updateThread = nullptr; UpdateChecker *_updateChecker = nullptr; #endif // !TDESKTOP_DISABLE_AUTOUPDATE }; namespace Sandbox { QRect availableGeometry(); QRect screenGeometry(const QPoint &p); void setActiveWindow(QWidget *window); bool isSavingSession(); void execExternal(const QString &cmd); void adjustSingleTimers(); #ifndef TDESKTOP_DISABLE_AUTOUPDATE void startUpdateCheck(); void stopUpdate(); Application::UpdatingState updatingState(); int32 updatingSize(); int32 updatingReady(); void updateChecking(); void updateLatest(); void updateProgress(qint64 ready, qint64 total); void updateFailed(); void updateReady(); #endif // !TDESKTOP_DISABLE_AUTOUPDATE void connect(const char *signal, QObject *object, const char *method); void launch(); } // namespace Sandbox tdesktop-1.2.17/Telegram/SourceFiles/auth_session.cpp000066400000000000000000000256601326245125100226440ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "auth_session.h" #include "apiwrap.h" #include "messenger.h" #include "core/changelogs.h" #include "storage/file_download.h" #include "storage/file_upload.h" #include "storage/localstorage.h" #include "storage/storage_facade.h" #include "storage/serialize_common.h" #include "data/data_session.h" #include "window/notifications_manager.h" #include "window/themes/window_theme.h" #include "platform/platform_specific.h" #include "calls/calls_instance.h" #include "window/section_widget.h" #include "chat_helpers/tabbed_selector.h" #include "boxes/send_files_box.h" namespace { constexpr auto kAutoLockTimeoutLateMs = TimeMs(3000); } // namespace AuthSessionSettings::Variables::Variables() : sendFilesWay(SendFilesWay::Album) , selectorTab(ChatHelpers::SelectorTab::Emoji) , floatPlayerColumn(Window::Column::Second) , floatPlayerCorner(RectPart::TopRight) { } QByteArray AuthSessionSettings::serialize() const { auto size = sizeof(qint32) * 10; for (auto i = _variables.soundOverrides.cbegin(), e = _variables.soundOverrides.cend(); i != e; ++i) { size += Serialize::stringSize(i.key()) + Serialize::stringSize(i.value()); } size += _variables.groupStickersSectionHidden.size() * sizeof(quint64); auto result = QByteArray(); result.reserve(size); { QDataStream stream(&result, QIODevice::WriteOnly); stream.setVersion(QDataStream::Qt_5_1); stream << static_cast(_variables.selectorTab); stream << qint32(_variables.lastSeenWarningSeen ? 1 : 0); stream << qint32(_variables.tabbedSelectorSectionEnabled ? 1 : 0); stream << qint32(_variables.soundOverrides.size()); for (auto i = _variables.soundOverrides.cbegin(), e = _variables.soundOverrides.cend(); i != e; ++i) { stream << i.key() << i.value(); } stream << qint32(_variables.tabbedSelectorSectionTooltipShown); stream << qint32(_variables.floatPlayerColumn); stream << qint32(_variables.floatPlayerCorner); stream << qint32(_variables.groupStickersSectionHidden.size()); for (auto peerId : _variables.groupStickersSectionHidden) { stream << quint64(peerId); } stream << qint32(_variables.thirdSectionInfoEnabled ? 1 : 0); stream << qint32(_variables.smallDialogsList ? 1 : 0); stream << qint32(snap( qRound(_variables.dialogsWidthRatio.current() * 1000000), 0, 1000000)); stream << qint32(_variables.thirdColumnWidth.current()); stream << qint32(_variables.thirdSectionExtendedBy); stream << qint32(_variables.sendFilesWay); } return result; } void AuthSessionSettings::constructFromSerialized(const QByteArray &serialized) { if (serialized.isEmpty()) { return; } QDataStream stream(serialized); stream.setVersion(QDataStream::Qt_5_1); qint32 selectorTab = static_cast(ChatHelpers::SelectorTab::Emoji); qint32 lastSeenWarningSeen = 0; qint32 tabbedSelectorSectionEnabled = 1; qint32 tabbedSelectorSectionTooltipShown = 0; qint32 floatPlayerColumn = static_cast(Window::Column::Second); qint32 floatPlayerCorner = static_cast(RectPart::TopRight); QMap soundOverrides; base::flat_set groupStickersSectionHidden; qint32 thirdSectionInfoEnabled = 0; qint32 smallDialogsList = 0; float64 dialogsWidthRatio = _variables.dialogsWidthRatio.current(); int thirdColumnWidth = _variables.thirdColumnWidth.current(); int thirdSectionExtendedBy = _variables.thirdSectionExtendedBy; qint32 sendFilesWay = static_cast(_variables.sendFilesWay); stream >> selectorTab; stream >> lastSeenWarningSeen; if (!stream.atEnd()) { stream >> tabbedSelectorSectionEnabled; } if (!stream.atEnd()) { auto count = qint32(0); stream >> count; if (stream.status() == QDataStream::Ok) { for (auto i = 0; i != count; ++i) { QString key, value; stream >> key >> value; soundOverrides[key] = value; } } } if (!stream.atEnd()) { stream >> tabbedSelectorSectionTooltipShown; } if (!stream.atEnd()) { stream >> floatPlayerColumn >> floatPlayerCorner; } if (!stream.atEnd()) { auto count = qint32(0); stream >> count; if (stream.status() == QDataStream::Ok) { for (auto i = 0; i != count; ++i) { quint64 peerId; stream >> peerId; groupStickersSectionHidden.insert(peerId); } } } if (!stream.atEnd()) { stream >> thirdSectionInfoEnabled; stream >> smallDialogsList; } if (!stream.atEnd()) { qint32 value = 0; stream >> value; dialogsWidthRatio = snap(value / 1000000., 0., 1.); stream >> value; thirdColumnWidth = value; stream >> value; thirdSectionExtendedBy = value; } if (stream.status() != QDataStream::Ok) { LOG(("App Error: " "Bad data for AuthSessionSettings::constructFromSerialized()")); return; } auto uncheckedTab = static_cast(selectorTab); switch (uncheckedTab) { case ChatHelpers::SelectorTab::Emoji: case ChatHelpers::SelectorTab::Stickers: case ChatHelpers::SelectorTab::Gifs: _variables.selectorTab = uncheckedTab; break; } _variables.lastSeenWarningSeen = (lastSeenWarningSeen == 1); _variables.tabbedSelectorSectionEnabled = (tabbedSelectorSectionEnabled == 1); _variables.soundOverrides = std::move(soundOverrides); _variables.tabbedSelectorSectionTooltipShown = tabbedSelectorSectionTooltipShown; auto uncheckedColumn = static_cast(floatPlayerColumn); switch (uncheckedColumn) { case Window::Column::First: case Window::Column::Second: case Window::Column::Third: _variables.floatPlayerColumn = uncheckedColumn; break; } auto uncheckedCorner = static_cast(floatPlayerCorner); switch (uncheckedCorner) { case RectPart::TopLeft: case RectPart::TopRight: case RectPart::BottomLeft: case RectPart::BottomRight: _variables.floatPlayerCorner = uncheckedCorner; break; } _variables.groupStickersSectionHidden = std::move(groupStickersSectionHidden); _variables.thirdSectionInfoEnabled = thirdSectionInfoEnabled; _variables.smallDialogsList = smallDialogsList; _variables.dialogsWidthRatio = dialogsWidthRatio; _variables.thirdColumnWidth = thirdColumnWidth; _variables.thirdSectionExtendedBy = thirdSectionExtendedBy; if (_variables.thirdSectionInfoEnabled) { _variables.tabbedSelectorSectionEnabled = false; } auto uncheckedSendFilesWay = static_cast(sendFilesWay); switch (uncheckedSendFilesWay) { case SendFilesWay::Album: case SendFilesWay::Photos: case SendFilesWay::Files: _variables.sendFilesWay = uncheckedSendFilesWay; } } void AuthSessionSettings::setTabbedSelectorSectionEnabled(bool enabled) { _variables.tabbedSelectorSectionEnabled = enabled; if (enabled) { setThirdSectionInfoEnabled(false); } setTabbedReplacedWithInfo(false); } rpl::producer AuthSessionSettings::tabbedReplacedWithInfoValue() const { return _tabbedReplacedWithInfoValue.events_starting_with( tabbedReplacedWithInfo()); } void AuthSessionSettings::setThirdSectionInfoEnabled(bool enabled) { if (_variables.thirdSectionInfoEnabled != enabled) { _variables.thirdSectionInfoEnabled = enabled; if (enabled) { setTabbedSelectorSectionEnabled(false); } setTabbedReplacedWithInfo(false); _thirdSectionInfoEnabledValue.fire_copy(enabled); } } rpl::producer AuthSessionSettings::thirdSectionInfoEnabledValue() const { return _thirdSectionInfoEnabledValue.events_starting_with( thirdSectionInfoEnabled()); } void AuthSessionSettings::setTabbedReplacedWithInfo(bool enabled) { if (_tabbedReplacedWithInfo != enabled) { _tabbedReplacedWithInfo = enabled; _tabbedReplacedWithInfoValue.fire_copy(enabled); } } QString AuthSessionSettings::getSoundPath(const QString &key) const { auto it = _variables.soundOverrides.constFind(key); if (it != _variables.soundOverrides.end()) { return it.value(); } return qsl(":/sounds/") + key + qsl(".mp3"); } void AuthSessionSettings::setDialogsWidthRatio(float64 ratio) { _variables.dialogsWidthRatio = ratio; } float64 AuthSessionSettings::dialogsWidthRatio() const { return _variables.dialogsWidthRatio.current(); } rpl::producer AuthSessionSettings::dialogsWidthRatioChanges() const { return _variables.dialogsWidthRatio.changes(); } void AuthSessionSettings::setThirdColumnWidth(int width) { _variables.thirdColumnWidth = width; } int AuthSessionSettings::thirdColumnWidth() const { return _variables.thirdColumnWidth.current(); } rpl::producer AuthSessionSettings::thirdColumnWidthChanges() const { return _variables.thirdColumnWidth.changes(); } AuthSession &Auth() { auto result = Messenger::Instance().authSession(); Assert(result != nullptr); return *result; } AuthSession::AuthSession(UserId userId) : _userId(userId) , _autoLockTimer([this] { checkAutoLock(); }) , _api(std::make_unique(this)) , _calls(std::make_unique()) , _downloader(std::make_unique()) , _uploader(std::make_unique()) , _storage(std::make_unique()) , _notifications(std::make_unique(this)) , _data(std::make_unique(this)) , _changelogs(Core::Changelogs::Create(this)) { Expects(_userId != 0); _saveDataTimer.setCallback([=] { Local::writeUserSettings(); }); subscribe(Messenger::Instance().passcodedChanged(), [=] { _shouldLockAt = 0; notifications().updateAll(); }); Window::Theme::Background()->start(); } bool AuthSession::Exists() { if (auto messenger = Messenger::InstancePointer()) { return (messenger->authSession() != nullptr); } return false; } UserData *AuthSession::user() const { return App::user(userId()); } base::Observable &AuthSession::downloaderTaskFinished() { return downloader().taskFinished(); } bool AuthSession::validateSelf(const MTPUser &user) { if (user.type() != mtpc_user || !user.c_user().is_self() || user.c_user().vid.v != userId()) { LOG(("Auth Error: wrong self user received.")); App::logOutDelayed(); return false; } return true; } void AuthSession::saveSettingsDelayed(TimeMs delay) { Expects(this == &Auth()); _saveDataTimer.callOnce(delay); } void AuthSession::checkAutoLock() { if (!Global::LocalPasscode() || App::passcoded()) return; Messenger::Instance().checkLocalTime(); auto now = getms(true); auto shouldLockInMs = Global::AutoLock() * 1000LL; auto idleForMs = psIdleTime(); auto notPlayingVideoForMs = now - settings().lastTimeVideoPlayedAt(); auto checkTimeMs = qMin(idleForMs, notPlayingVideoForMs); if (checkTimeMs >= shouldLockInMs || (_shouldLockAt > 0 && now > _shouldLockAt + kAutoLockTimeoutLateMs)) { Messenger::Instance().setupPasscode(); } else { _shouldLockAt = now + (shouldLockInMs - checkTimeMs); _autoLockTimer.callOnce(shouldLockInMs - checkTimeMs); } } void AuthSession::checkAutoLockIn(TimeMs time) { if (_autoLockTimer.isActive()) { auto remain = _autoLockTimer.remainingTime(); if (remain > 0 && remain <= time) return; } _autoLockTimer.callOnce(time); } AuthSession::~AuthSession() = default; tdesktop-1.2.17/Telegram/SourceFiles/auth_session.h000066400000000000000000000161111326245125100223000ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include #include #include #include "base/timer.h" class ApiWrap; enum class SendFilesWay; namespace Data { class Session; } // namespace Data namespace Storage { class Downloader; class Uploader; class Facade; } // namespace Storage namespace Window { namespace Notifications { class System; } // namespace Notifications enum class Column; } // namespace Window namespace Calls { class Instance; } // namespace Calls namespace ChatHelpers { enum class SelectorTab; } // namespace ChatHelpers namespace Core { class Changelogs; } // namespace Core class AuthSessionSettings final { public: void moveFrom(AuthSessionSettings &&other) { _variables = std::move(other._variables); } QByteArray serialize() const; void constructFromSerialized(const QByteArray &serialized); void setLastSeenWarningSeen(bool lastSeenWarningSeen) { _variables.lastSeenWarningSeen = lastSeenWarningSeen; } bool lastSeenWarningSeen() const { return _variables.lastSeenWarningSeen; } void setSendFilesWay(SendFilesWay way) { _variables.sendFilesWay = way; } SendFilesWay sendFilesWay() const { return _variables.sendFilesWay; } ChatHelpers::SelectorTab selectorTab() const { return _variables.selectorTab; } void setSelectorTab(ChatHelpers::SelectorTab tab) { _variables.selectorTab = tab; } bool tabbedSelectorSectionEnabled() const { return _variables.tabbedSelectorSectionEnabled; } void setTabbedSelectorSectionEnabled(bool enabled); bool thirdSectionInfoEnabled() const { return _variables.thirdSectionInfoEnabled; } void setThirdSectionInfoEnabled(bool enabled); rpl::producer thirdSectionInfoEnabledValue() const; int thirdSectionExtendedBy() const { return _variables.thirdSectionExtendedBy; } void setThirdSectionExtendedBy(int savedValue) { _variables.thirdSectionExtendedBy = savedValue; } bool tabbedReplacedWithInfo() const { return _tabbedReplacedWithInfo; } void setTabbedReplacedWithInfo(bool enabled); rpl::producer tabbedReplacedWithInfoValue() const; void setSmallDialogsList(bool enabled) { _variables.smallDialogsList = enabled; } bool smallDialogsList() const { return _variables.smallDialogsList; } void setLastTimeVideoPlayedAt(TimeMs time) { _lastTimeVideoPlayedAt = time; } TimeMs lastTimeVideoPlayedAt() const { return _lastTimeVideoPlayedAt; } void setSoundOverride(const QString &key, const QString &path) { _variables.soundOverrides.insert(key, path); } void clearSoundOverrides() { _variables.soundOverrides.clear(); } QString getSoundPath(const QString &key) const; void setTabbedSelectorSectionTooltipShown(int shown) { _variables.tabbedSelectorSectionTooltipShown = shown; } int tabbedSelectorSectionTooltipShown() const { return _variables.tabbedSelectorSectionTooltipShown; } void setFloatPlayerColumn(Window::Column column) { _variables.floatPlayerColumn = column; } Window::Column floatPlayerColumn() const { return _variables.floatPlayerColumn; } void setFloatPlayerCorner(RectPart corner) { _variables.floatPlayerCorner = corner; } RectPart floatPlayerCorner() const { return _variables.floatPlayerCorner; } void setDialogsWidthRatio(float64 ratio); float64 dialogsWidthRatio() const; rpl::producer dialogsWidthRatioChanges() const; void setThirdColumnWidth(int width); int thirdColumnWidth() const; rpl::producer thirdColumnWidthChanges() const; void setGroupStickersSectionHidden(PeerId peerId) { _variables.groupStickersSectionHidden.insert(peerId); } bool isGroupStickersSectionHidden(PeerId peerId) const { return _variables.groupStickersSectionHidden.contains(peerId); } void removeGroupStickersSectionHidden(PeerId peerId) { _variables.groupStickersSectionHidden.remove(peerId); } private: struct Variables { Variables(); static constexpr auto kDefaultDialogsWidthRatio = 5. / 14; static constexpr auto kDefaultThirdColumnWidth = 0; bool lastSeenWarningSeen = false; SendFilesWay sendFilesWay; ChatHelpers::SelectorTab selectorTab; // per-window bool tabbedSelectorSectionEnabled = false; // per-window int tabbedSelectorSectionTooltipShown = 0; QMap soundOverrides; Window::Column floatPlayerColumn; // per-window RectPart floatPlayerCorner; // per-window base::flat_set groupStickersSectionHidden; bool thirdSectionInfoEnabled = true; // per-window bool smallDialogsList = false; // per-window int thirdSectionExtendedBy = -1; // per-window rpl::variable dialogsWidthRatio = kDefaultDialogsWidthRatio; // per-window rpl::variable thirdColumnWidth = kDefaultThirdColumnWidth; // per-window }; rpl::event_stream _thirdSectionInfoEnabledValue; bool _tabbedReplacedWithInfo = false; rpl::event_stream _tabbedReplacedWithInfoValue; Variables _variables; TimeMs _lastTimeVideoPlayedAt = 0; }; // One per Messenger. class AuthSession; AuthSession &Auth(); class AuthSession final : public base::has_weak_ptr , private base::Subscriber { public: AuthSession(UserId userId); AuthSession(const AuthSession &other) = delete; AuthSession &operator=(const AuthSession &other) = delete; static bool Exists(); UserId userId() const { return _userId; } PeerId userPeerId() const { return peerFromUser(userId()); } UserData *user() const; bool validateSelf(const MTPUser &user); Storage::Downloader &downloader() { return *_downloader; } Storage::Uploader &uploader() { return *_uploader; } Storage::Facade &storage() { return *_storage; } base::Observable &downloaderTaskFinished(); Window::Notifications::System ¬ifications() { return *_notifications; } Data::Session &data() { return *_data; } AuthSessionSettings &settings() { return _settings; } void saveSettingsDelayed(TimeMs delay = kDefaultSaveDelay); ApiWrap &api() { return *_api; } Calls::Instance &calls() { return *_calls; } void checkAutoLock(); void checkAutoLockIn(TimeMs time); base::Observable documentUpdated; base::Observable, MsgId>> messageIdChanging; ~AuthSession(); private: static constexpr auto kDefaultSaveDelay = TimeMs(1000); const UserId _userId = 0; AuthSessionSettings _settings; base::Timer _saveDataTimer; TimeMs _shouldLockAt = 0; base::Timer _autoLockTimer; const std::unique_ptr _api; const std::unique_ptr _calls; const std::unique_ptr _downloader; const std::unique_ptr _uploader; const std::unique_ptr _storage; const std::unique_ptr _notifications; // _data depends on _downloader / _uploader, including destructor. const std::unique_ptr _data; // _changelogs depends on _data, subscribes on chats loading event. const std::unique_ptr _changelogs; rpl::lifetime _lifetime; }; tdesktop-1.2.17/Telegram/SourceFiles/autoupdater.cpp000066400000000000000000000505071326245125100224730ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "autoupdater.h" #include #include #include #include #ifdef Q_OS_WIN // use Lzma SDK for win #include #else // Q_OS_WIN #include #endif // else of Q_OS_WIN #include "application.h" #include "platform/platform_specific.h" #ifndef TDESKTOP_DISABLE_AUTOUPDATE #ifdef Q_OS_WIN typedef DWORD VerInt; typedef WCHAR VerChar; #else // Q_OS_WIN typedef int VerInt; typedef wchar_t VerChar; #endif // Q_OS_WIN UpdateChecker::UpdateChecker(QThread *thread, const QString &url) : reply(0), already(0), full(0) { updateUrl = url; moveToThread(thread); manager.moveToThread(thread); App::setProxySettings(manager); connect(thread, SIGNAL(started()), this, SLOT(start())); initOutput(); } void UpdateChecker::initOutput() { QString fileName; QRegularExpressionMatch m = QRegularExpression(qsl("/([^/\\?]+)(\\?|$)")).match(updateUrl); if (m.hasMatch()) { fileName = m.captured(1).replace(QRegularExpression(qsl("[^a-zA-Z0-9_\\-]")), QString()); } if (fileName.isEmpty()) { fileName = qsl("tupdate-%1").arg(rand_value() % 1000000); } QString dirStr = cWorkingDir() + qsl("tupdates/"); fileName = dirStr + fileName; QFileInfo file(fileName); QDir dir(dirStr); if (dir.exists()) { QFileInfoList all = dir.entryInfoList(QDir::Files); for (QFileInfoList::iterator i = all.begin(), e = all.end(); i != e; ++i) { if (i->absoluteFilePath() != file.absoluteFilePath()) { QFile::remove(i->absoluteFilePath()); } } } else { dir.mkdir(dir.absolutePath()); } outputFile.setFileName(fileName); if (file.exists()) { uint64 fullSize = file.size(); if (fullSize < INT_MAX) { int32 goodSize = (int32)fullSize; if (goodSize % UpdateChunk) { goodSize = goodSize - (goodSize % UpdateChunk); if (goodSize) { if (outputFile.open(QIODevice::ReadOnly)) { QByteArray goodData = outputFile.readAll().mid(0, goodSize); outputFile.close(); if (outputFile.open(QIODevice::WriteOnly)) { outputFile.write(goodData); outputFile.close(); QMutexLocker lock(&mutex); already = goodSize; } } } } else { QMutexLocker lock(&mutex); already = goodSize; } } if (!already) { QFile::remove(fileName); } } } void UpdateChecker::start() { sendRequest(); } void UpdateChecker::sendRequest() { QNetworkRequest req(updateUrl); QByteArray rangeHeaderValue = "bytes=" + QByteArray::number(already) + "-"; req.setRawHeader("Range", rangeHeaderValue); req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true); if (reply) reply->deleteLater(); reply = manager.get(req); connect(reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(partFinished(qint64,qint64))); connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(partFailed(QNetworkReply::NetworkError))); connect(reply, SIGNAL(metaDataChanged()), this, SLOT(partMetaGot())); } void UpdateChecker::partMetaGot() { typedef QList Pairs; Pairs pairs = reply->rawHeaderPairs(); for (Pairs::iterator i = pairs.begin(), e = pairs.end(); i != e; ++i) { if (QString::fromUtf8(i->first).toLower() == "content-range") { QRegularExpressionMatch m = QRegularExpression(qsl("/(\\d+)([^\\d]|$)")).match(QString::fromUtf8(i->second)); if (m.hasMatch()) { { QMutexLocker lock(&mutex); full = m.captured(1).toInt(); } Sandbox::updateProgress(already, full); } } } } int32 UpdateChecker::ready() { QMutexLocker lock(&mutex); return already; } int32 UpdateChecker::size() { QMutexLocker lock(&mutex); return full; } void UpdateChecker::partFinished(qint64 got, qint64 total) { if (!reply) return; QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); if (statusCode.isValid()) { int status = statusCode.toInt(); if (status != 200 && status != 206 && status != 416) { LOG(("Update Error: Bad HTTP status received in partFinished(): %1").arg(status)); return fatalFail(); } } if (!already && !full) { QMutexLocker lock(&mutex); full = total; } DEBUG_LOG(("Update Info: part %1 of %2").arg(got).arg(total)); if (!outputFile.isOpen()) { if (!outputFile.open(QIODevice::Append)) { LOG(("Update Error: Could not open output file '%1' for appending").arg(outputFile.fileName())); return fatalFail(); } } QByteArray r = reply->readAll(); if (!r.isEmpty()) { outputFile.write(r); QMutexLocker lock(&mutex); already += r.size(); } if (got >= total) { reply->deleteLater(); reply = 0; outputFile.close(); unpackUpdate(); } else { Sandbox::updateProgress(already, full); } } void UpdateChecker::partFailed(QNetworkReply::NetworkError e) { if (!reply) return; QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); reply->deleteLater(); reply = 0; if (statusCode.isValid()) { int status = statusCode.toInt(); if (status == 416) { // Requested range not satisfiable outputFile.close(); unpackUpdate(); return; } } LOG(("Update Error: failed to download part starting from %1, error %2").arg(already).arg(e)); Sandbox::updateFailed(); } void UpdateChecker::fatalFail() { clearAll(); Sandbox::updateFailed(); } void UpdateChecker::clearAll() { psDeleteDir(cWorkingDir() + qsl("tupdates")); } //QString winapiErrorWrap() { // WCHAR errMsg[2048]; // DWORD errorCode = GetLastError(); // LPTSTR errorText = NULL, errorTextDefault = L"(Unknown error)"; // FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&errorText, 0, 0); // if (!errorText) { // errorText = errorTextDefault; // } // StringCbPrintf(errMsg, sizeof(errMsg), L"Error code: %d, error message: %s", errorCode, errorText); // if (errorText != errorTextDefault) { // LocalFree(errorText); // } // return QString::fromWCharArray(errMsg); //} void UpdateChecker::unpackUpdate() { QByteArray packed; if (!outputFile.open(QIODevice::ReadOnly)) { LOG(("Update Error: cant read updates file!")); return fatalFail(); } #ifdef Q_OS_WIN // use Lzma SDK for win const int32 hSigLen = 128, hShaLen = 20, hPropsLen = LZMA_PROPS_SIZE, hOriginalSizeLen = sizeof(int32), hSize = hSigLen + hShaLen + hPropsLen + hOriginalSizeLen; // header #else // Q_OS_WIN const int32 hSigLen = 128, hShaLen = 20, hPropsLen = 0, hOriginalSizeLen = sizeof(int32), hSize = hSigLen + hShaLen + hOriginalSizeLen; // header #endif // Q_OS_WIN QByteArray compressed = outputFile.readAll(); int32 compressedLen = compressed.size() - hSize; if (compressedLen <= 0) { LOG(("Update Error: bad compressed size: %1").arg(compressed.size())); return fatalFail(); } outputFile.close(); QString tempDirPath = cWorkingDir() + qsl("tupdates/temp"), readyFilePath = cWorkingDir() + qsl("tupdates/temp/ready"); psDeleteDir(tempDirPath); QDir tempDir(tempDirPath); if (tempDir.exists() || QFile(readyFilePath).exists()) { LOG(("Update Error: cant clear tupdates/temp dir!")); return fatalFail(); } uchar sha1Buffer[20]; bool goodSha1 = !memcmp(compressed.constData() + hSigLen, hashSha1(compressed.constData() + hSigLen + hShaLen, compressedLen + hPropsLen + hOriginalSizeLen, sha1Buffer), hShaLen); if (!goodSha1) { LOG(("Update Error: bad SHA1 hash of update file!")); return fatalFail(); } RSA *pbKey = PEM_read_bio_RSAPublicKey(BIO_new_mem_buf(const_cast(AppAlphaVersion ? UpdatesPublicAlphaKey : UpdatesPublicKey), -1), 0, 0, 0); if (!pbKey) { LOG(("Update Error: cant read public rsa key!")); return fatalFail(); } if (RSA_verify(NID_sha1, (const uchar*)(compressed.constData() + hSigLen), hShaLen, (const uchar*)(compressed.constData()), hSigLen, pbKey) != 1) { // verify signature RSA_free(pbKey); if (cAlphaVersion() || cBetaVersion()) { // try other public key, if we are in alpha or beta version pbKey = PEM_read_bio_RSAPublicKey(BIO_new_mem_buf(const_cast(AppAlphaVersion ? UpdatesPublicKey : UpdatesPublicAlphaKey), -1), 0, 0, 0); if (!pbKey) { LOG(("Update Error: cant read public rsa key!")); return fatalFail(); } if (RSA_verify(NID_sha1, (const uchar*)(compressed.constData() + hSigLen), hShaLen, (const uchar*)(compressed.constData()), hSigLen, pbKey) != 1) { // verify signature RSA_free(pbKey); LOG(("Update Error: bad RSA signature of update file!")); return fatalFail(); } } else { LOG(("Update Error: bad RSA signature of update file!")); return fatalFail(); } } RSA_free(pbKey); QByteArray uncompressed; int32 uncompressedLen; memcpy(&uncompressedLen, compressed.constData() + hSigLen + hShaLen + hPropsLen, hOriginalSizeLen); uncompressed.resize(uncompressedLen); size_t resultLen = uncompressed.size(); #ifdef Q_OS_WIN // use Lzma SDK for win SizeT srcLen = compressedLen; int uncompressRes = LzmaUncompress((uchar*)uncompressed.data(), &resultLen, (const uchar*)(compressed.constData() + hSize), &srcLen, (const uchar*)(compressed.constData() + hSigLen + hShaLen), LZMA_PROPS_SIZE); if (uncompressRes != SZ_OK) { LOG(("Update Error: could not uncompress lzma, code: %1").arg(uncompressRes)); return fatalFail(); } #else // Q_OS_WIN lzma_stream stream = LZMA_STREAM_INIT; lzma_ret ret = lzma_stream_decoder(&stream, UINT64_MAX, LZMA_CONCATENATED); if (ret != LZMA_OK) { const char *msg; switch (ret) { case LZMA_MEM_ERROR: msg = "Memory allocation failed"; break; case LZMA_OPTIONS_ERROR: msg = "Specified preset is not supported"; break; case LZMA_UNSUPPORTED_CHECK: msg = "Specified integrity check is not supported"; break; default: msg = "Unknown error, possibly a bug"; break; } LOG(("Error initializing the decoder: %1 (error code %2)").arg(msg).arg(ret)); return fatalFail(); } stream.avail_in = compressedLen; stream.next_in = (uint8_t*)(compressed.constData() + hSize); stream.avail_out = resultLen; stream.next_out = (uint8_t*)uncompressed.data(); lzma_ret res = lzma_code(&stream, LZMA_FINISH); if (stream.avail_in) { LOG(("Error in decompression, %1 bytes left in _in of %2 whole.").arg(stream.avail_in).arg(compressedLen)); return fatalFail(); } else if (stream.avail_out) { LOG(("Error in decompression, %1 bytes free left in _out of %2 whole.").arg(stream.avail_out).arg(resultLen)); return fatalFail(); } lzma_end(&stream); if (res != LZMA_OK && res != LZMA_STREAM_END) { const char *msg; switch (res) { case LZMA_MEM_ERROR: msg = "Memory allocation failed"; break; case LZMA_FORMAT_ERROR: msg = "The input data is not in the .xz format"; break; case LZMA_OPTIONS_ERROR: msg = "Unsupported compression options"; break; case LZMA_DATA_ERROR: msg = "Compressed file is corrupt"; break; case LZMA_BUF_ERROR: msg = "Compressed data is truncated or otherwise corrupt"; break; default: msg = "Unknown error, possibly a bug"; break; } LOG(("Error in decompression: %1 (error code %2)").arg(msg).arg(res)); return fatalFail(); } #endif // Q_OS_WIN tempDir.mkdir(tempDir.absolutePath()); quint32 version; { QDataStream stream(uncompressed); stream.setVersion(QDataStream::Qt_5_1); stream >> version; if (stream.status() != QDataStream::Ok) { LOG(("Update Error: cant read version from downloaded stream, status: %1").arg(stream.status())); return fatalFail(); } quint64 betaVersion = 0; if (version == 0x7FFFFFFF) { // beta version stream >> betaVersion; if (stream.status() != QDataStream::Ok) { LOG(("Update Error: cant read beta version from downloaded stream, status: %1").arg(stream.status())); return fatalFail(); } if (!cBetaVersion() || betaVersion <= cBetaVersion()) { LOG(("Update Error: downloaded beta version %1 is not greater, than mine %2").arg(betaVersion).arg(cBetaVersion())); return fatalFail(); } } else if (int32(version) <= AppVersion) { LOG(("Update Error: downloaded version %1 is not greater, than mine %2").arg(version).arg(AppVersion)); return fatalFail(); } quint32 filesCount; stream >> filesCount; if (stream.status() != QDataStream::Ok) { LOG(("Update Error: cant read files count from downloaded stream, status: %1").arg(stream.status())); return fatalFail(); } if (!filesCount) { LOG(("Update Error: update is empty!")); return fatalFail(); } for (uint32 i = 0; i < filesCount; ++i) { QString relativeName; quint32 fileSize; QByteArray fileInnerData; bool executable = false; stream >> relativeName >> fileSize >> fileInnerData; #if defined Q_OS_MAC || defined Q_OS_LINUX stream >> executable; #endif // Q_OS_MAC || Q_OS_LINUX if (stream.status() != QDataStream::Ok) { LOG(("Update Error: cant read file from downloaded stream, status: %1").arg(stream.status())); return fatalFail(); } if (fileSize != quint32(fileInnerData.size())) { LOG(("Update Error: bad file size %1 not matching data size %2").arg(fileSize).arg(fileInnerData.size())); return fatalFail(); } QFile f(tempDirPath + '/' + relativeName); if (!QDir().mkpath(QFileInfo(f).absolutePath())) { LOG(("Update Error: cant mkpath for file '%1'").arg(tempDirPath + '/' + relativeName)); return fatalFail(); } if (!f.open(QIODevice::WriteOnly)) { LOG(("Update Error: cant open file '%1' for writing").arg(tempDirPath + '/' + relativeName)); return fatalFail(); } auto writtenBytes = f.write(fileInnerData); if (writtenBytes != fileSize) { f.close(); LOG(("Update Error: cant write file '%1', desiredSize: %2, write result: %3").arg(tempDirPath + '/' + relativeName).arg(fileSize).arg(writtenBytes)); return fatalFail(); } f.close(); if (executable) { QFileDevice::Permissions p = f.permissions(); p |= QFileDevice::ExeOwner | QFileDevice::ExeUser | QFileDevice::ExeGroup | QFileDevice::ExeOther; f.setPermissions(p); } } // create tdata/version file tempDir.mkdir(QDir(tempDirPath + qsl("/tdata")).absolutePath()); std::wstring versionString = ((version % 1000) ? QString("%1.%2.%3").arg(int(version / 1000000)).arg(int((version % 1000000) / 1000)).arg(int(version % 1000)) : QString("%1.%2").arg(int(version / 1000000)).arg(int((version % 1000000) / 1000))).toStdWString(); VerInt versionNum = VerInt(version), versionLen = VerInt(versionString.size() * sizeof(VerChar)); VerChar versionStr[32]; memcpy(versionStr, versionString.c_str(), versionLen); QFile fVersion(tempDirPath + qsl("/tdata/version")); if (!fVersion.open(QIODevice::WriteOnly)) { LOG(("Update Error: cant write version file '%1'").arg(tempDirPath + qsl("/version"))); return fatalFail(); } fVersion.write((const char*)&versionNum, sizeof(VerInt)); if (versionNum == 0x7FFFFFFF) { // beta version fVersion.write((const char*)&betaVersion, sizeof(quint64)); } else { fVersion.write((const char*)&versionLen, sizeof(VerInt)); fVersion.write((const char*)&versionStr[0], versionLen); } fVersion.close(); } QFile readyFile(readyFilePath); if (readyFile.open(QIODevice::WriteOnly)) { if (readyFile.write("1", 1)) { readyFile.close(); } else { LOG(("Update Error: cant write ready file '%1'").arg(readyFilePath)); return fatalFail(); } } else { LOG(("Update Error: cant create ready file '%1'").arg(readyFilePath)); return fatalFail(); } outputFile.remove(); Sandbox::updateReady(); } UpdateChecker::~UpdateChecker() { delete reply; reply = 0; } bool checkReadyUpdate() { QString readyFilePath = cWorkingDir() + qsl("tupdates/temp/ready"), readyPath = cWorkingDir() + qsl("tupdates/temp"); if (!QFile(readyFilePath).exists() || cExeName().isEmpty()) { if (QDir(cWorkingDir() + qsl("tupdates/ready")).exists() || QDir(cWorkingDir() + qsl("tupdates/temp")).exists()) { UpdateChecker::clearAll(); } return false; } // check ready version QString versionPath = readyPath + qsl("/tdata/version"); { QFile fVersion(versionPath); if (!fVersion.open(QIODevice::ReadOnly)) { LOG(("Update Error: cant read version file '%1'").arg(versionPath)); UpdateChecker::clearAll(); return false; } VerInt versionNum; if (fVersion.read((char*)&versionNum, sizeof(VerInt)) != sizeof(VerInt)) { LOG(("Update Error: cant read version from file '%1'").arg(versionPath)); UpdateChecker::clearAll(); return false; } if (versionNum == 0x7FFFFFFF) { // beta version quint64 betaVersion = 0; if (fVersion.read((char*)&betaVersion, sizeof(quint64)) != sizeof(quint64)) { LOG(("Update Error: cant read beta version from file '%1'").arg(versionPath)); UpdateChecker::clearAll(); return false; } if (!cBetaVersion() || betaVersion <= cBetaVersion()) { LOG(("Update Error: cant install beta version %1 having beta version %2").arg(betaVersion).arg(cBetaVersion())); UpdateChecker::clearAll(); return false; } } else if (versionNum <= AppVersion) { LOG(("Update Error: cant install version %1 having version %2").arg(versionNum).arg(AppVersion)); UpdateChecker::clearAll(); return false; } fVersion.close(); } #ifdef Q_OS_WIN QString curUpdater = (cExeDir() + qsl("Updater.exe")); QFileInfo updater(cWorkingDir() + qsl("tupdates/temp/Updater.exe")); #elif defined Q_OS_MAC // Q_OS_WIN QString curUpdater = (cExeDir() + cExeName() + qsl("/Contents/Frameworks/Updater")); QFileInfo updater(cWorkingDir() + qsl("tupdates/temp/Telegram.app/Contents/Frameworks/Updater")); #elif defined Q_OS_LINUX // Q_OS_MAC QString curUpdater = (cExeDir() + qsl("Updater")); QFileInfo updater(cWorkingDir() + qsl("tupdates/temp/Updater")); #endif // Q_OS_LINUX if (!updater.exists()) { QFileInfo current(curUpdater); if (!current.exists()) { UpdateChecker::clearAll(); return false; } if (!QFile(current.absoluteFilePath()).copy(updater.absoluteFilePath())) { UpdateChecker::clearAll(); return false; } } #ifdef Q_OS_WIN if (CopyFile(updater.absoluteFilePath().toStdWString().c_str(), curUpdater.toStdWString().c_str(), FALSE) == FALSE) { DWORD errorCode = GetLastError(); if (errorCode == ERROR_ACCESS_DENIED) { // we are in write-protected dir, like Program Files cSetWriteProtected(true); return true; } else { UpdateChecker::clearAll(); return false; } } if (DeleteFile(updater.absoluteFilePath().toStdWString().c_str()) == FALSE) { UpdateChecker::clearAll(); return false; } #elif defined Q_OS_MAC // Q_OS_WIN QDir().mkpath(QFileInfo(curUpdater).absolutePath()); DEBUG_LOG(("Update Info: moving %1 to %2...").arg(updater.absoluteFilePath()).arg(curUpdater)); if (!objc_moveFile(updater.absoluteFilePath(), curUpdater)) { UpdateChecker::clearAll(); return false; } #elif defined Q_OS_LINUX // Q_OS_MAC if (!linuxMoveFile(QFile::encodeName(updater.absoluteFilePath()).constData(), QFile::encodeName(curUpdater).constData())) { UpdateChecker::clearAll(); return false; } #endif // Q_OS_LINUX return true; } #endif // !TDESKTOP_DISABLE_AUTOUPDATE QString countBetaVersionSignature(uint64 version) { // duplicated in packer.cpp if (cBetaPrivateKey().isEmpty()) { LOG(("Error: Trying to count beta version signature without beta private key!")); return QString(); } QByteArray signedData = (qstr("TelegramBeta_") + QString::number(version, 16).toLower()).toUtf8(); static const int32 shaSize = 20, keySize = 128; uchar sha1Buffer[shaSize]; hashSha1(signedData.constData(), signedData.size(), sha1Buffer); // count sha1 uint32 siglen = 0; RSA *prKey = PEM_read_bio_RSAPrivateKey(BIO_new_mem_buf(const_cast(cBetaPrivateKey().constData()), -1), 0, 0, 0); if (!prKey) { LOG(("Error: Could not read beta private key!")); return QString(); } if (RSA_size(prKey) != keySize) { LOG(("Error: Bad beta private key size: %1").arg(RSA_size(prKey))); RSA_free(prKey); return QString(); } QByteArray signature; signature.resize(keySize); if (RSA_sign(NID_sha1, (const uchar*)(sha1Buffer), shaSize, (uchar*)(signature.data()), &siglen, prKey) != 1) { // count signature LOG(("Error: Counting beta version signature failed!")); RSA_free(prKey); return QString(); } RSA_free(prKey); if (siglen != keySize) { LOG(("Error: Bad beta version signature length: %1").arg(siglen)); return QString(); } signature = signature.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); signature = signature.replace('-', '8').replace('_', 'B'); return QString::fromUtf8(signature.mid(19, 32)); } tdesktop-1.2.17/Telegram/SourceFiles/autoupdater.h000066400000000000000000000022211326245125100221260ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #ifndef TDESKTOP_DISABLE_AUTOUPDATE #include #include #include class UpdateChecker : public QObject { Q_OBJECT public: UpdateChecker(QThread *thread, const QString &url); void unpackUpdate(); int32 ready(); int32 size(); static void clearAll(); ~UpdateChecker(); public slots: void start(); void partMetaGot(); void partFinished(qint64 got, qint64 total); void partFailed(QNetworkReply::NetworkError e); void sendRequest(); private: void initOutput(); void fatalFail(); QString updateUrl; QNetworkAccessManager manager; QNetworkReply *reply; int32 already, full; QFile outputFile; QMutex mutex; }; bool checkReadyUpdate(); #else // TDESKTOP_DISABLE_AUTOUPDATE class UpdateChecker : public QObject { Q_OBJECT }; #endif // TDESKTOP_DISABLE_AUTOUPDATE QString countBetaVersionSignature(uint64 version); tdesktop-1.2.17/Telegram/SourceFiles/base/000077500000000000000000000000001326245125100203355ustar00rootroot00000000000000tdesktop-1.2.17/Telegram/SourceFiles/base/algorithm.h000066400000000000000000000011161326245125100224730ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace base { template inline Type take(Type &value) { return std::exchange(value, Type {}); } template inline Type duplicate(const Type &value) { return value; } template inline constexpr size_t array_size(const Type(&)[Size]) { return Size; } } // namespace base tdesktop-1.2.17/Telegram/SourceFiles/base/algorithm_tests.cpp000066400000000000000000000025411326245125100242530ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "catch.hpp" #include "base/index_based_iterator.h" TEST_CASE("index_based_iterator tests", "[base::algorithm]") { auto v = std::vector(); v.insert(v.end(), { 1, 2, 3, 4, 5, 4, 3, 2, 1 }); auto push_back_safe_remove_if = [](auto &v, auto predicate) { auto begin = base::index_based_begin(v); auto end = base::index_based_end(v); auto from = std::remove_if(begin, end, predicate); if (from != end) { auto newEnd = base::index_based_end(v); if (newEnd != end) { REQUIRE(newEnd > end); while (end != newEnd) { *from++ = *end++; } } v.erase(from.base(), newEnd.base()); } }; SECTION("allows to push_back from predicate") { push_back_safe_remove_if(v, [&v](int value) { v.push_back(value); return (value % 2) == 1; }); auto expected = std::vector { 2, 4, 4, 2, 1, 2, 3, 4, 5, 4, 3, 2, 1 }; REQUIRE(v == expected); } SECTION("allows to push_back while removing all") { push_back_safe_remove_if(v, [&v](int value) { if (value == 5) { v.push_back(value); } return true; }); auto expected = std::vector { 5 }; REQUIRE(v == expected); } }tdesktop-1.2.17/Telegram/SourceFiles/base/assertion.h000066400000000000000000000037051326245125100225220ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include namespace base { namespace assertion { // Client must define that method. void log(const char *message, const char *file, int line); // Release build assertions. inline constexpr void noop() { } [[noreturn]] inline void fail(const char *message, const char *file, int line) { log(message, file, line); // Crash with access violation and generate crash report. volatile auto nullptr_value = (int*)nullptr; *nullptr_value = 0; // Silent the possible failure to comply noreturn warning. std::abort(); } #ifndef GSL_UNLIKELY #define DEFINED_GSL_UNLIKELY_ #define GSL_UNLIKELY(expression) (expression) #endif // GSL_UNLIKELY inline constexpr void validate(bool condition, const char *message, const char *file, int line) { (GSL_UNLIKELY(!(condition))) ? fail(message, file, line) : noop(); } #ifdef DEFINED_GSL_UNLIKELY_ #undef GSL_UNLIKELY #undef DEFINED_GSL_UNLIKELY_ #endif // DEFINED_GSL_UNLIKELY_ } // namespace assertion } // namespace base #define AssertCustom(condition, message) (::base::assertion::validate(condition, message, __FILE__, __LINE__)) #define Assert(condition) AssertCustom(condition, "\"" #condition "\"") // Define our own versions of Expects() and Ensures(). // Let them crash with reports and logging. #ifdef Expects #undef Expects #endif // Expects #define Expects(condition) (::base::assertion::validate(condition, "\"" #condition "\"", __FILE__, __LINE__)) #ifdef Ensures #undef Ensures #endif // Ensures #define Ensures(condition) (::base::assertion::validate(condition, "\"" #condition "\"", __FILE__, __LINE__)) #ifdef Unexpected #undef Unexpected #endif // Unexpected #define Unexpected(message) (::base::assertion::fail("Unexpected: " message, __FILE__, __LINE__)) tdesktop-1.2.17/Telegram/SourceFiles/base/build_config.h000066400000000000000000000036061326245125100231370ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include // thanks Chromium #if defined(__APPLE__) #define OS_MAC 1 #elif defined(__linux__) // __APPLE__ #define OS_LINUX 1 #elif defined(_WIN32) // __APPLE__ || __linux__ #define OS_WIN 1 #else // __APPLE__ || __linux__ || _WIN32 #error Please add support for your platform in base/build_config.h #endif // else for __APPLE__ || __linux__ || _WIN32 // For access to standard POSIXish features, use OS_POSIX instead of a // more specific macro. #if defined(OS_MAC) || defined(OS_LINUX) #define OS_POSIX 1 #endif // OS_MAC || OS_LINUX // Compiler detection. #if defined(__clang__) #define COMPILER_CLANG 1 #elif defined(__GNUC__) // __clang__ #define COMPILER_GCC 1 #elif defined(_MSC_VER) // __clang__ || __GNUC__ #define COMPILER_MSVC 1 #else // _MSC_VER || __clang__ || __GNUC__ #error Please add support for your compiler in base/build_config.h #endif // else for _MSC_VER || __clang__ || __GNUC__ // Processor architecture detection. #if defined(_M_X64) || defined(__x86_64__) #define ARCH_CPU_X86_FAMILY 1 #define ARCH_CPU_X86_64 1 #define ARCH_CPU_64_BITS 1 #elif defined(_M_IX86) || defined(__i386__) #define ARCH_CPU_X86_FAMILY 1 #define ARCH_CPU_X86 1 #define ARCH_CPU_32_BITS 1 #elif defined(__aarch64__) #define ARCH_CPU_64_BITS 1 #elif defined(_M_ARM) || defined(__arm__) #define ARCH_CPU_32_BITS 1 #else #error Please add support for your architecture in base/build_config.h #endif #if defined(__GNUC__) #define FORCE_INLINE inline __attribute__((always_inline)) #elif defined(_MSC_VER) #define FORCE_INLINE __forceinline #else #define FORCE_INLINE inline #endif #include static_assert(CHAR_BIT == 8, "Not supported char size."); tdesktop-1.2.17/Telegram/SourceFiles/base/enum_mask.h000066400000000000000000000016461326245125100224740ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace base { template class enum_mask { using Type = std::uint32_t; public: static_assert(static_cast(Enum::kCount) <= 32, "We have only 32 bit."); enum_mask() = default; enum_mask(Enum value) : _value(ToBit(value)) { } enum_mask added(enum_mask other) const { auto result = *this; result.set(other); return result; } void set(enum_mask other) { _value |= other._value; } bool test(Enum value) const { return _value & ToBit(value); } explicit operator bool() const { return _value != 0; } private: inline static Type ToBit(Enum value) { return 1 << static_cast(value); } Type _value = 0; }; } // namespace base tdesktop-1.2.17/Telegram/SourceFiles/base/flags.h000066400000000000000000000250771326245125100216150ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include namespace base { template class flags; template struct extended_flags; template using extended_flags_t = typename extended_flags::type; namespace details { struct flags_zero_helper_struct { }; using flags_zero_helper = void(base::details::flags_zero_helper_struct::*)(); template ::type> inline constexpr auto extended_flag_convert(ExtendedEnum value) { return static_cast(value); } template ::type> inline constexpr auto extended_flags_convert(ExtendedEnum value) { return flags(extended_flag_convert(value)); } } // namespace details template class flags { public: using Enum = EnumType; using Type = std::underlying_type_t; constexpr flags() = default; constexpr flags(details::flags_zero_helper) noexcept { } constexpr flags(Enum value) noexcept : _value(static_cast(value)) { } static constexpr flags from_raw(Type value) noexcept { return flags(static_cast(value)); } constexpr auto value() const noexcept { return _value; } constexpr operator Type() const noexcept { return value(); } constexpr auto &operator|=(flags b) noexcept { _value |= b.value(); return *this; } constexpr auto &operator&=(flags b) noexcept { _value &= b.value(); return *this; } constexpr auto &operator^=(flags b) noexcept { _value ^= b.value(); return *this; } constexpr auto operator~() const noexcept { return from_raw(~value()); } constexpr auto operator|(flags b) const noexcept { return (flags(*this) |= b); } constexpr auto operator&(flags b) const noexcept { return (flags(*this) &= b); } constexpr auto operator^(flags b) const noexcept { return (flags(*this) ^= b); } constexpr auto operator|(Enum b) const noexcept { return (flags(*this) |= b); } constexpr auto operator&(Enum b) const noexcept { return (flags(*this) &= b); } constexpr auto operator^(Enum b) const noexcept { return (flags(*this) ^= b); } constexpr auto operator==(Enum b) const noexcept { return (value() == static_cast(b)); } constexpr auto operator!=(Enum b) const noexcept { return !(*this == b); } constexpr auto operator<(Enum b) const noexcept { return value() < static_cast(b); } constexpr auto operator>(Enum b) const noexcept { return (b < *this); } constexpr auto operator<=(Enum b) const noexcept { return !(b < *this); } constexpr auto operator>=(Enum b) const noexcept { return !(*this < b); } private: Type _value = 0; }; template constexpr auto make_flags(Enum value) noexcept { return flags(value); } template ::value>, typename = std::enable_if_t> inline constexpr auto operator|(Enum a, flags b) noexcept { return b | a; } template ::value>, typename = std::enable_if_t> inline constexpr auto operator&(Enum a, flags b) noexcept { return b & a; } template ::value>, typename = std::enable_if_t> inline constexpr auto operator^(Enum a, flags b) noexcept { return b ^ a; } template ::type> inline constexpr auto operator|(flags> a, ExtendedEnum b) { return a | details::extended_flags_convert(b); } template ::type> inline constexpr auto operator|(ExtendedEnum a, flags> b) { return b | a; } template > inline constexpr auto operator&(flags> a, ExtendedEnum b) { return a & details::extended_flags_convert(b); } template ::type> inline constexpr auto operator&(ExtendedEnum a, flags> b) { return b & a; } template > inline constexpr auto operator^(flags> a, ExtendedEnum b) { return a ^ details::extended_flags_convert(b); } template ::type> inline constexpr auto operator^(ExtendedEnum a, flags> b) { return b ^ a; } template ::type> inline constexpr auto &operator&=(flags> &a, ExtendedEnum b) { return (a &= details::extended_flags_convert(b)); } template ::type> inline constexpr auto &operator|=(flags> &a, ExtendedEnum b) { return (a |= details::extended_flags_convert(b)); } template ::type> inline constexpr auto &operator^=(flags> &a, ExtendedEnum b) { return (a ^= details::extended_flags_convert(b)); } template ::type> inline constexpr auto operator==(flags> a, ExtendedEnum b) { return a == details::extended_flags_convert(b); } template ::type> inline constexpr auto operator==(ExtendedEnum a, flags> b) { return (b == a); } template ::type> inline constexpr auto operator!=(flags> a, ExtendedEnum b) { return !(a == b); } template ::type> inline constexpr auto operator!=(ExtendedEnum a, flags> b) { return !(a == b); } template ::type> inline constexpr auto operator<(flags> a, ExtendedEnum b) { return a < details::extended_flags_convert(b); } template ::type> inline constexpr auto operator<(ExtendedEnum a, flags> b) { return details::extended_flags_convert(a) < b; } template ::type> inline constexpr auto operator>(flags> a, ExtendedEnum b) { return (b < a); } template ::type> inline constexpr auto operator>(ExtendedEnum a, flags> b) { return (b < a); } template ::type> inline constexpr auto operator<=(flags> a, ExtendedEnum b) { return !(b < a); } template ::type> inline constexpr auto operator<=(ExtendedEnum a, flags> b) { return !(b < a); } template ::type> inline constexpr auto operator>=(flags> a, ExtendedEnum b) { return !(a < b); } template ::type> inline constexpr auto operator>=(ExtendedEnum a, flags> b) { return !(a < b); } } // namespace base template ::value>, typename = std::enable_if_t> inline constexpr auto operator!(Enum a) noexcept { return !base::make_flags(a); } template ::value>, typename = std::enable_if_t> inline constexpr auto operator~(Enum a) noexcept { return ~base::make_flags(a); } template ::value>, typename = std::enable_if_t> inline constexpr auto operator|(Enum a, Enum b) noexcept { return base::make_flags(a) | b; } template ::value>, typename = std::enable_if_t> inline constexpr auto operator|(Enum a, base::details::flags_zero_helper) noexcept { return base::make_flags(a); } template ::value>, typename = std::enable_if_t> inline constexpr auto operator|(base::details::flags_zero_helper, Enum b) noexcept { return base::make_flags(b); } template ::type> inline constexpr auto operator|(ExtendedEnum a, ExtendedEnum b) { return base::details::extended_flags_convert(a) | b; } template ::type> inline constexpr auto operator|(ExtendedEnum a, typename base::extended_flags::type b) { return base::details::extended_flags_convert(a) | b; } template ::type> inline constexpr auto operator|(typename base::extended_flags::type a, ExtendedEnum b) { return b | a; } template ::type> inline constexpr auto operator|(base::details::flags_zero_helper, ExtendedEnum b) { return 0 | base::details::extended_flag_convert(b); } template ::type> inline constexpr auto operator|(ExtendedEnum a, base::details::flags_zero_helper) { return base::details::extended_flag_convert(a) | 0; } template ::type> inline constexpr auto operator~(ExtendedEnum b) { return ~base::details::extended_flags_convert(b); } tdesktop-1.2.17/Telegram/SourceFiles/base/flags_tests.cpp000066400000000000000000000054331326245125100233640ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "catch.hpp" #include "base/flags.h" namespace MethodNamespace { template void TestFlags(Enum a, Enum b, Enum c) { auto abc = a | b; abc |= c; auto test = abc != a; CHECK(abc != a); CHECK(abc != (a | b)); CHECK((abc & a) == a); CHECK((abc & b) == b); CHECK((abc & c) == c); CHECK((abc & ~a) == (b | c)); CHECK((abc & ~(b | c)) == a); CHECK((abc ^ a) == (abc & ~a)); auto another = a | b; another |= c; CHECK(abc == another); another &= ~b; CHECK(another == (a | c)); another ^= a; CHECK(another == c); another = 0; another = nullptr; auto is_zero = ((another & abc) == 0); CHECK(is_zero); CHECK(!(another & abc)); auto more = a | another; auto just = a | 0; CHECK(more == just); CHECK(just); } } // namespace MethodNamespace namespace FlagsNamespace { enum class Flag : int { one = (1 << 0), two = (1 << 1), three = (1 << 2), }; inline constexpr auto is_flag_type(Flag) { return true; } class Class { public: enum class Public : long { one = (1 << 2), two = (1 << 1), three = (1 << 0), }; friend inline constexpr auto is_flag_type(Public) { return true; } static void TestPrivate(); private: enum class Private : long { one = (1 << 0), two = (1 << 1), three = (1 << 2), }; friend inline constexpr auto is_flag_type(Private) { return true; } }; void Class::TestPrivate() { MethodNamespace::TestFlags(Private::one, Private::two, Private::three); } } // namespace FlagsNamespace namespace ExtendedNamespace { enum class Flag : int { one = (1 << 3), two = (1 << 4), three = (1 << 5), }; } // namespace ExtendedNamespace namespace base { template<> struct extended_flags { using type = FlagsNamespace::Flag; }; } // namespace base TEST_CASE("flags operators on scoped enums", "[flags]") { SECTION("testing non-member flags") { MethodNamespace::TestFlags( FlagsNamespace::Flag::one, FlagsNamespace::Flag::two, FlagsNamespace::Flag::three); } SECTION("testing public member flags") { MethodNamespace::TestFlags( FlagsNamespace::Class::Public::one, FlagsNamespace::Class::Public::two, FlagsNamespace::Class::Public::three); } SECTION("testing private member flags") { FlagsNamespace::Class::TestPrivate(); } SECTION("testing extended flags") { MethodNamespace::TestFlags( ExtendedNamespace::Flag::one, ExtendedNamespace::Flag::two, ExtendedNamespace::Flag::three); auto onetwo = FlagsNamespace::Flag::one | ExtendedNamespace::Flag::two; auto twoone = ExtendedNamespace::Flag::two | FlagsNamespace::Flag::one; CHECK(onetwo == twoone); } } tdesktop-1.2.17/Telegram/SourceFiles/base/flat_map.h000066400000000000000000000426641326245125100223050ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include #include #include "base/optional.h" namespace base { template < typename Key, typename Type, typename Compare = std::less<>> class flat_map; template < typename Key, typename Type, typename Compare = std::less<>> class flat_multi_map; template < typename Me, typename Key, typename Type, typename iterator_impl, typename pointer_impl, typename reference_impl> class flat_multi_map_iterator_base_impl; template struct flat_multi_map_pair_type { using first_type = const Key; using second_type = Value; constexpr flat_multi_map_pair_type() : first() , second() { } template constexpr flat_multi_map_pair_type(OtherKey &&key, OtherValue &&value) : first(std::forward(key)) , second(std::forward(value)) { } flat_multi_map_pair_type(const flat_multi_map_pair_type&) = default; flat_multi_map_pair_type(flat_multi_map_pair_type&&) = default; flat_multi_map_pair_type &operator=(const flat_multi_map_pair_type&) = delete; flat_multi_map_pair_type &operator=(flat_multi_map_pair_type &&other) { const_cast(first) = other.first; second = std::move(other.second); return *this; } void swap(flat_multi_map_pair_type &other) { using std::swap; if (this != &other) { std::swap( const_cast(first), const_cast(other.first)); std::swap(second, other.second); } } const Key first; Value second; }; template < typename Me, typename Key, typename Type, typename iterator_impl, typename pointer_impl, typename reference_impl> class flat_multi_map_iterator_base_impl { public: using iterator_category = typename iterator_impl::iterator_category; using pair_type = flat_multi_map_pair_type; using value_type = pair_type; using difference_type = typename iterator_impl::difference_type; using pointer = pointer_impl; using reference = reference_impl; flat_multi_map_iterator_base_impl(iterator_impl impl = iterator_impl()) : _impl(impl) { } reference operator*() const { return *_impl; } pointer operator->() const { return std::addressof(**this); } Me &operator++() { ++_impl; return static_cast(*this); } Me operator++(int) { return _impl++; } Me &operator--() { --_impl; return static_cast(*this); } Me operator--(int) { return _impl--; } Me &operator+=(difference_type offset) { _impl += offset; return static_cast(*this); } Me operator+(difference_type offset) const { return _impl + offset; } Me &operator-=(difference_type offset) { _impl -= offset; return static_cast(*this); } Me operator-(difference_type offset) const { return _impl - offset; } template < typename other_me, typename other_iterator_impl, typename other_pointer_impl, typename other_reference_impl> difference_type operator-( const flat_multi_map_iterator_base_impl< other_me, Key, Type, other_iterator_impl, other_pointer_impl, other_reference_impl> &right) const { return _impl - right._impl; } reference operator[](difference_type offset) const { return _impl[offset]; } template < typename other_me, typename other_iterator_impl, typename other_pointer_impl, typename other_reference_impl> bool operator==( const flat_multi_map_iterator_base_impl< other_me, Key, Type, other_iterator_impl, other_pointer_impl, other_reference_impl> &right) const { return _impl == right._impl; } template < typename other_me, typename other_iterator_impl, typename other_pointer_impl, typename other_reference_impl> bool operator!=( const flat_multi_map_iterator_base_impl< other_me, Key, Type, other_iterator_impl, other_pointer_impl, other_reference_impl> &right) const { return _impl != right._impl; } template < typename other_me, typename other_iterator_impl, typename other_pointer_impl, typename other_reference_impl> bool operator<( const flat_multi_map_iterator_base_impl< other_me, Key, Type, other_iterator_impl, other_pointer_impl, other_reference_impl> &right) const { return _impl < right._impl; } private: iterator_impl _impl; template < typename OtherKey, typename OtherType, typename OtherCompare> friend class flat_multi_map; template < typename OtherMe, typename OtherKey, typename OtherType, typename other_iterator_impl, typename other_pointer_impl, typename other_reference_impl> friend class flat_multi_map_iterator_base_impl; }; template class flat_multi_map { public: class iterator; class const_iterator; class reverse_iterator; class const_reverse_iterator; private: using pair_type = flat_multi_map_pair_type; using impl_t = std::deque; using iterator_base = flat_multi_map_iterator_base_impl< iterator, Key, Type, typename impl_t::iterator, pair_type*, pair_type&>; using const_iterator_base = flat_multi_map_iterator_base_impl< const_iterator, Key, Type, typename impl_t::const_iterator, const pair_type*, const pair_type&>; using reverse_iterator_base = flat_multi_map_iterator_base_impl< reverse_iterator, Key, Type, typename impl_t::reverse_iterator, pair_type*, pair_type&>; using const_reverse_iterator_base = flat_multi_map_iterator_base_impl< const_reverse_iterator, Key, Type, typename impl_t::const_reverse_iterator, const pair_type*, const pair_type&>; public: using value_type = pair_type; using size_type = typename impl_t::size_type; using difference_type = typename impl_t::difference_type; using pointer = pair_type*; using const_pointer = const pair_type*; using reference = pair_type&; using const_reference = const pair_type&; class iterator : public iterator_base { public: using iterator_base::iterator_base; iterator() = default; iterator(const iterator_base &other) : iterator_base(other) { } friend class const_iterator; }; class const_iterator : public const_iterator_base { public: using const_iterator_base::const_iterator_base; const_iterator() = default; const_iterator(const_iterator_base other) : const_iterator_base(other) { } const_iterator(const iterator &other) : const_iterator_base(other._impl) { } }; class reverse_iterator : public reverse_iterator_base { public: using reverse_iterator_base::reverse_iterator_base; reverse_iterator() = default; reverse_iterator(reverse_iterator_base other) : reverse_iterator_base(other) { } friend class const_reverse_iterator; }; class const_reverse_iterator : public const_reverse_iterator_base { public: using const_reverse_iterator_base::const_reverse_iterator_base; const_reverse_iterator() = default; const_reverse_iterator(const_reverse_iterator_base other) : const_reverse_iterator_base(other) { } const_reverse_iterator(const reverse_iterator &other) : const_reverse_iterator_base(other._impl) { } }; size_type size() const { return impl().size(); } bool empty() const { return impl().empty(); } void clear() { impl().clear(); } iterator begin() { return impl().begin(); } iterator end() { return impl().end(); } const_iterator begin() const { return impl().begin(); } const_iterator end() const { return impl().end(); } const_iterator cbegin() const { return impl().cbegin(); } const_iterator cend() const { return impl().cend(); } reverse_iterator rbegin() { return impl().rbegin(); } reverse_iterator rend() { return impl().rend(); } const_reverse_iterator rbegin() const { return impl().rbegin(); } const_reverse_iterator rend() const { return impl().rend(); } const_reverse_iterator crbegin() const { return impl().crbegin(); } const_reverse_iterator crend() const { return impl().crend(); } reference front() { return *begin(); } const_reference front() const { return *begin(); } reference back() { return *(end() - 1); } const_reference back() const { return *(end() - 1); } iterator insert(const value_type &value) { if (empty() || compare()(value.first, front().first)) { impl().push_front(value); return begin(); } else if (!compare()(value.first, back().first)) { impl().push_back(value); return (end() - 1); } auto where = getUpperBound(value.first); return impl().insert(where, value); } iterator insert(value_type &&value) { if (empty() || compare()(value.first, front().first)) { impl().push_front(std::move(value)); return begin(); } else if (!compare()(value.first, back().first)) { impl().push_back(std::move(value)); return (end() - 1); } auto where = getUpperBound(value.first); return impl().insert(where, std::move(value)); } template iterator emplace(Args&&... args) { return insert(value_type(std::forward(args)...)); } bool removeOne(const Key &key) { if (empty() || compare()(key, front().first) || compare()(back().first, key)) { return false; } auto where = getLowerBound(key); if (compare()(key, where->first)) { return false; } impl().erase(where); return true; } int removeAll(const Key &key) { if (empty() || compare()(key, front().first) || compare()(back().first, key)) { return 0; } auto range = getEqualRange(key); if (range.first == range.second) { return 0; } impl().erase(range.first, range.second); return (range.second - range.first); } iterator erase(const_iterator where) { return impl().erase(where._impl); } iterator erase(const_iterator from, const_iterator till) { return impl().erase(from._impl, till._impl); } iterator findFirst(const Key &key) { if (empty() || compare()(key, front().first) || compare()(back().first, key)) { return end(); } auto where = getLowerBound(key); return compare()(key, where->first) ? impl().end() : where; } const_iterator findFirst(const Key &key) const { if (empty() || compare()(key, front().first) || compare()(back().first, key)) { return end(); } auto where = getLowerBound(key); return compare()(key, where->first) ? impl().end() : where; } bool contains(const Key &key) const { return findFirst(key) != end(); } int count(const Key &key) const { if (empty() || compare()(key, front().first) || compare()(back().first, key)) { return 0; } auto range = getEqualRange(key); return (range.second - range.first); } private: friend class flat_map; struct transparent_compare : Compare { inline constexpr const Compare &initial() const noexcept { return *this; } template < typename OtherType1, typename OtherType2, typename = std::enable_if_t< !std::is_same_v, pair_type> && !std::is_same_v, pair_type>>> inline constexpr auto operator()( OtherType1 &&a, OtherType2 &&b) const { return initial()( std::forward(a), std::forward(b)); } template < typename OtherType1, typename OtherType2> inline constexpr auto operator()( OtherType1 &&a, OtherType2 &&b) const -> std::enable_if_t< std::is_same_v, pair_type> && std::is_same_v, pair_type>, bool> { return initial()(a.first, b.first); } template < typename OtherType, typename = std::enable_if_t< !std::is_same_v, pair_type>>> inline constexpr auto operator()( const pair_type &a, OtherType &&b) const { return operator()(a.first, std::forward(b)); } template < typename OtherType, typename = std::enable_if_t< !std::is_same_v, pair_type>>> inline constexpr auto operator()( OtherType &&a, const pair_type &b) const { return operator()(std::forward(a), b.first); } }; struct Data : transparent_compare { template Data(Args &&...args) : elements(std::forward(args)...) { } impl_t elements; }; Data _data; const transparent_compare &compare() const noexcept { return _data; } const impl_t &impl() const noexcept { return _data.elements; } impl_t &impl() noexcept { return _data.elements; } typename impl_t::iterator getLowerBound(const Key &key) { return std::lower_bound( std::begin(impl()), std::end(impl()), key, compare()); } typename impl_t::const_iterator getLowerBound(const Key &key) const { return std::lower_bound( std::begin(impl()), std::end(impl()), key, compare()); } typename impl_t::iterator getUpperBound(const Key &key) { return std::upper_bound( std::begin(impl()), std::end(impl()), key, compare()); } typename impl_t::const_iterator getUpperBound(const Key &key) const { return std::upper_bound( std::begin(impl()), std::end(impl()), key, compare()); } std::pair< typename impl_t::iterator, typename impl_t::iterator > getEqualRange(const Key &key) { return std::equal_range( std::begin(impl()), std::end(impl()), key, compare()); } std::pair< typename impl_t::const_iterator, typename impl_t::const_iterator > getEqualRange(const Key &key) const { return std::equal_range( std::begin(impl()), std::end(impl()), key, compare()); } }; template class flat_map : private flat_multi_map { using parent = flat_multi_map; using pair_type = typename parent::pair_type; public: using value_type = typename parent::value_type; using size_type = typename parent::size_type; using difference_type = typename parent::difference_type; using pointer = typename parent::pointer; using const_pointer = typename parent::const_pointer; using reference = typename parent::reference; using const_reference = typename parent::const_reference; using iterator = typename parent::iterator; using const_iterator = typename parent::const_iterator; using reverse_iterator = typename parent::reverse_iterator; using const_reverse_iterator = typename parent::const_reverse_iterator; using parent::parent; using parent::size; using parent::empty; using parent::clear; using parent::begin; using parent::end; using parent::cbegin; using parent::cend; using parent::rbegin; using parent::rend; using parent::crbegin; using parent::crend; using parent::front; using parent::back; using parent::erase; using parent::contains; std::pair insert(const value_type &value) { if (this->empty() || this->compare()(value.first, this->front().first)) { this->impl().push_front(value); return { this->begin(), true }; } else if (this->compare()(this->back().first, value.first)) { this->impl().push_back(value); return { this->end() - 1, true }; } auto where = this->getLowerBound(value.first); if (this->compare()(value.first, where->first)) { return { this->impl().insert(where, value), true }; } return { where, false }; } std::pair insert(value_type &&value) { if (this->empty() || this->compare()(value.first, this->front().first)) { this->impl().push_front(std::move(value)); return { this->begin(), true }; } else if (this->compare()(this->back().first, value.first)) { this->impl().push_back(std::move(value)); return { this->end() - 1, true }; } auto where = this->getLowerBound(value.first); if (this->compare()(value.first, where->first)) { return { this->impl().insert(where, std::move(value)), true }; } return { where, false }; } template std::pair emplace( const Key &key, Args&&... args) { return this->insert(value_type( key, Type(std::forward(args)...))); } template std::pair try_emplace( const Key &key, Args&&... args) { if (this->empty() || this->compare()(key, this->front().first)) { this->impl().push_front(value_type( key, Type(std::forward(args)...))); return { this->begin(), true }; } else if (this->compare()(this->back().first, key)) { this->impl().push_back(value_type( key, Type(std::forward(args)...))); return { this->end() - 1, true }; } auto where = this->getLowerBound(key); if (this->compare()(key, where->first)) { return { this->impl().insert( where, value_type( key, Type(std::forward(args)...))), true }; } return { where, false }; } bool remove(const Key &key) { return this->removeOne(key); } iterator find(const Key &key) { return this->findFirst(key); } const_iterator find(const Key &key) const { return this->findFirst(key); } Type &operator[](const Key &key) { if (this->empty() || this->compare()(key, this->front().first)) { this->impl().push_front({ key, Type() }); return this->front().second; } else if (this->compare()(this->back().first, key)) { this->impl().push_back({ key, Type() }); return this->back().second; } auto where = this->getLowerBound(key); if (this->compare()(key, where->first)) { return this->impl().insert(where, { key, Type() })->second; } return where->second; } optional take(const Key &key) { auto it = find(key); if (it == this->end()) { return base::none; } auto result = std::move(it->second); this->erase(it); return std::move(result); } }; } // namespace base tdesktop-1.2.17/Telegram/SourceFiles/base/flat_map_tests.cpp000066400000000000000000000032241326245125100240470ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "catch.hpp" #include "base/flat_map.h" #include struct int_wrap { int value; }; struct int_wrap_comparator { inline bool operator()(const int_wrap &a, const int_wrap &b) const { return a.value < b.value; } }; using namespace std; TEST_CASE("flat_maps should keep items sorted by key", "[flat_map]") { base::flat_map v; v.emplace(0, "a"); v.emplace(5, "b"); v.emplace(4, "d"); v.emplace(2, "e"); auto checkSorted = [&] { auto prev = v.begin(); REQUIRE(prev != v.end()); for (auto i = prev + 1; i != v.end(); prev = i, ++i) { REQUIRE(prev->first < i->first); } }; REQUIRE(v.size() == 4); checkSorted(); SECTION("adding item puts it in the right position") { v.emplace(3, "c"); REQUIRE(v.size() == 5); REQUIRE(v.find(3) != v.end()); checkSorted(); } } TEST_CASE("flat_maps custom comparator", "[flat_map]") { base::flat_map v; v.emplace({ 0 }, "a"); v.emplace({ 5 }, "b"); v.emplace({ 4 }, "d"); v.emplace({ 2 }, "e"); auto checkSorted = [&] { auto prev = v.begin(); REQUIRE(prev != v.end()); for (auto i = prev + 1; i != v.end(); prev = i, ++i) { REQUIRE(int_wrap_comparator()(prev->first, i->first)); } }; REQUIRE(v.size() == 4); checkSorted(); SECTION("adding item puts it in the right position") { v.emplace({ 3 }, "c"); REQUIRE(v.size() == 5); REQUIRE(v.find({ 3 }) != v.end()); checkSorted(); } } tdesktop-1.2.17/Telegram/SourceFiles/base/flat_set.h000066400000000000000000000414041326245125100223120ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include #include namespace base { template > class flat_set; template > class flat_multi_set; template class flat_multi_set_iterator_impl; template class flat_multi_set_iterator_impl { public: using iterator_category = typename iterator_impl::iterator_category; using value_type = Type; using difference_type = typename iterator_impl::difference_type; using pointer = const Type*; using reference = const Type&; flat_multi_set_iterator_impl( iterator_impl impl = iterator_impl()) : _impl(impl) { } template flat_multi_set_iterator_impl( const flat_multi_set_iterator_impl< Type, other_iterator_impl> &other) : _impl(other._impl) { } reference operator*() const { return *_impl; } pointer operator->() const { return std::addressof(**this); } flat_multi_set_iterator_impl &operator++() { ++_impl; return *this; } flat_multi_set_iterator_impl operator++(int) { return _impl++; } flat_multi_set_iterator_impl &operator--() { --_impl; return *this; } flat_multi_set_iterator_impl operator--(int) { return _impl--; } flat_multi_set_iterator_impl &operator+=(difference_type offset) { _impl += offset; return *this; } flat_multi_set_iterator_impl operator+(difference_type offset) const { return _impl + offset; } flat_multi_set_iterator_impl &operator-=(difference_type offset) { _impl -= offset; return *this; } flat_multi_set_iterator_impl operator-(difference_type offset) const { return _impl - offset; } template difference_type operator-( const flat_multi_set_iterator_impl< Type, other_iterator_impl> &right) const { return _impl - right._impl; } reference operator[](difference_type offset) const { return _impl[offset]; } template bool operator==( const flat_multi_set_iterator_impl< Type, other_iterator_impl> &right) const { return _impl == right._impl; } template bool operator!=( const flat_multi_set_iterator_impl< Type, other_iterator_impl> &right) const { return _impl != right._impl; } template bool operator<( const flat_multi_set_iterator_impl< Type, other_iterator_impl> &right) const { return _impl < right._impl; } private: iterator_impl _impl; template friend class flat_multi_set; template friend class flat_set; template < typename OtherType, typename other_iterator_impl> friend class flat_multi_set_iterator_impl; Type &wrapped() { return _impl->wrapped(); } }; template class flat_multi_set_const_wrap { public: constexpr flat_multi_set_const_wrap(const Type &value) : _value(value) { } constexpr flat_multi_set_const_wrap(Type &&value) : _value(std::move(value)) { } inline constexpr operator const Type&() const { return _value; } constexpr Type &wrapped() { return _value; } private: Type _value; }; template class flat_multi_set { using const_wrap = flat_multi_set_const_wrap; using impl_t = std::deque; public: using value_type = Type; using size_type = typename impl_t::size_type; using difference_type = typename impl_t::difference_type; using pointer = const Type*; using reference = const Type&; using iterator = flat_multi_set_iterator_impl< Type, typename impl_t::iterator>; using const_iterator = flat_multi_set_iterator_impl< Type, typename impl_t::const_iterator>; using reverse_iterator = flat_multi_set_iterator_impl< Type, typename impl_t::reverse_iterator>; using const_reverse_iterator = flat_multi_set_iterator_impl< Type, typename impl_t::const_reverse_iterator>; flat_multi_set() = default; template < typename Iterator, typename = typename std::iterator_traits::iterator_category> flat_multi_set(Iterator first, Iterator last) : _data(first, last) { std::sort(std::begin(impl()), std::end(impl()), compare()); } flat_multi_set(std::initializer_list iter) : flat_multi_set(iter.begin(), iter.end()) { } size_type size() const { return impl().size(); } bool empty() const { return impl().empty(); } void clear() { impl().clear(); } iterator begin() { return impl().begin(); } iterator end() { return impl().end(); } const_iterator begin() const { return impl().begin(); } const_iterator end() const { return impl().end(); } const_iterator cbegin() const { return impl().cbegin(); } const_iterator cend() const { return impl().cend(); } reverse_iterator rbegin() { return impl().rbegin(); } reverse_iterator rend() { return impl().rend(); } const_reverse_iterator rbegin() const { return impl().rbegin(); } const_reverse_iterator rend() const { return impl().rend(); } const_reverse_iterator crbegin() const { return impl().crbegin(); } const_reverse_iterator crend() const { return impl().crend(); } reference front() const { return *begin(); } reference back() const { return *(end() - 1); } iterator insert(const Type &value) { if (empty() || compare()(value, front())) { impl().push_front(value); return begin(); } else if (!compare()(value, back())) { impl().push_back(value); return (end() - 1); } auto where = getUpperBound(value); return impl().insert(where, value); } iterator insert(Type &&value) { if (empty() || compare()(value, front())) { impl().push_front(std::move(value)); return begin(); } else if (!compare()(value, back())) { impl().push_back(std::move(value)); return (end() - 1); } auto where = getUpperBound(value); return impl().insert(where, std::move(value)); } template iterator emplace(Args&&... args) { return insert(Type(std::forward(args)...)); } bool removeOne(const Type &value) { if (empty() || compare()(value, front()) || compare()(back(), value)) { return false; } auto where = getLowerBound(value); if (compare()(value, *where)) { return false; } impl().erase(where); return true; } int removeAll(const Type &value) { if (empty() || compare()(value, front()) || compare()(back(), value)) { return 0; } auto range = getEqualRange(value); if (range.first == range.second) { return 0; } impl().erase(range.first, range.second); return (range.second - range.first); } iterator erase(const_iterator where) { return impl().erase(where._impl); } iterator erase(const_iterator from, const_iterator till) { return impl().erase(from._impl, till._impl); } iterator findFirst(const Type &value) { if (empty() || compare()(value, front()) || compare()(back(), value)) { return end(); } auto where = getLowerBound(value); return compare()(value, *where) ? impl().end() : where; } const_iterator findFirst(const Type &value) const { if (empty() || compare()(value, front()) || compare()(back(), value)) { return end(); } auto where = getLowerBound(value); return compare()(value, *where) ? impl().end() : where; } template < typename OtherType, typename = typename Compare::is_transparent> iterator findFirst(const OtherType &value) { if (empty() || compare()(value, front()) || compare()(back(), value)) { return end(); } auto where = getLowerBound(value); return compare()(value, *where) ? impl().end() : where; } template < typename OtherType, typename = typename Compare::is_transparent> const_iterator findFirst(const OtherType &value) const { if (empty() || compare()(value, front()) || compare()(back(), value)) { return end(); } auto where = getLowerBound(value); return compare()(value, *where) ? impl().end() : where; } bool contains(const Type &value) const { return findFirst(value) != end(); } int count(const Type &value) const { if (empty() || compare()(value, front()) || compare()(back(), value)) { return 0; } auto range = getEqualRange(value); return (range.second - range.first); } template auto modify(iterator which, Action action) { auto result = action(which.wrapped()); for (auto i = which + 1, e = end(); i != e; ++i) { if (compare()(*i, *which)) { std::swap(i.wrapped(), which.wrapped()); } else { break; } } for (auto i = which, b = begin(); i != b;) { --i; if (compare()(*which, *i)) { std::swap(i.wrapped(), which.wrapped()); } else { break; } } return result; } template < typename Iterator, typename = typename std::iterator_traits::iterator_category> void merge(Iterator first, Iterator last) { impl().insert(impl().end(), first, last); std::sort(std::begin(impl()), std::end(impl()), compare()); } void merge(const flat_multi_set &other) { merge(other.begin(), other.end()); } void merge(std::initializer_list list) { merge(list.begin(), list.end()); } private: friend class flat_set; struct transparent_compare : Compare { inline constexpr const Compare &initial() const noexcept { return *this; } template < typename OtherType1, typename OtherType2, typename = std::enable_if_t< !std::is_same_v, const_wrap> && !std::is_same_v, const_wrap>>> inline constexpr auto operator()( OtherType1 &&a, OtherType2 &&b) const { return initial()( std::forward(a), std::forward(b)); } template < typename OtherType1, typename OtherType2> inline constexpr auto operator()( OtherType1 &&a, OtherType2 &&b) const -> std::enable_if_t< std::is_same_v, const_wrap> && std::is_same_v, const_wrap>, bool> { return initial()( static_cast(a), static_cast(b)); } template < typename OtherType, typename = std::enable_if_t< !std::is_same_v, const_wrap>>> inline constexpr auto operator()( const const_wrap &a, OtherType &&b) const { return initial()( static_cast(a), std::forward(b)); } template < typename OtherType, typename = std::enable_if_t< !std::is_same_v, const_wrap>>> inline constexpr auto operator()( OtherType &&a, const const_wrap &b) const { return initial()( std::forward(a), static_cast(b)); } }; struct Data : transparent_compare { template Data(Args &&...args) : elements(std::forward(args)...) { } impl_t elements; }; Data _data; const transparent_compare &compare() const { return _data; } const impl_t &impl() const { return _data.elements; } impl_t &impl() { return _data.elements; } typename impl_t::iterator getLowerBound(const Type &value) { return std::lower_bound( std::begin(impl()), std::end(impl()), value, compare()); } typename impl_t::const_iterator getLowerBound(const Type &value) const { return std::lower_bound( std::begin(impl()), std::end(impl()), value, compare()); } template < typename OtherType, typename = typename Compare::is_transparent> typename impl_t::iterator getLowerBound(const OtherType &value) { return std::lower_bound( std::begin(impl()), std::end(impl()), value, compare()); } template < typename OtherType, typename = typename Compare::is_transparent> typename impl_t::const_iterator getLowerBound(const OtherType &value) const { return std::lower_bound( std::begin(impl()), std::end(impl()), value, compare()); } typename impl_t::iterator getUpperBound(const Type &value) { return std::upper_bound( std::begin(impl()), std::end(impl()), value, compare()); } typename impl_t::const_iterator getUpperBound(const Type &value) const { return std::upper_bound( std::begin(impl()), std::end(impl()), value, compare()); } std::pair< typename impl_t::iterator, typename impl_t::iterator > getEqualRange(const Type &value) { return std::equal_range( std::begin(impl()), std::end(impl()), value, compare()); } std::pair< typename impl_t::const_iterator, typename impl_t::const_iterator > getEqualRange(const Type &value) const { return std::equal_range( std::begin(impl()), std::end(impl()), value, compare()); } }; template class flat_set : private flat_multi_set { using parent = flat_multi_set; public: using iterator = typename parent::iterator; using const_iterator = typename parent::const_iterator; using reverse_iterator = typename parent::reverse_iterator; using const_reverse_iterator = typename parent::const_reverse_iterator; using value_type = typename parent::value_type; using size_type = typename parent::size_type; using difference_type = typename parent::difference_type; using pointer = typename parent::pointer; using reference = typename parent::reference; flat_set() = default; template < typename Iterator, typename = typename std::iterator_traits::iterator_category > flat_set(Iterator first, Iterator last) : parent(first, last) { finalize(); } flat_set(std::initializer_list iter) : parent(iter.begin(), iter.end()) { finalize(); } using parent::parent; using parent::size; using parent::empty; using parent::clear; using parent::begin; using parent::end; using parent::cbegin; using parent::cend; using parent::rbegin; using parent::rend; using parent::crbegin; using parent::crend; using parent::front; using parent::back; using parent::contains; using parent::erase; iterator insert(const Type &value) { if (this->empty() || this->compare()(value, this->front())) { this->impl().push_front(value); return this->begin(); } else if (this->compare()(this->back(), value)) { this->impl().push_back(value); return (this->end() - 1); } auto where = this->getLowerBound(value); if (this->compare()(value, *where)) { return this->impl().insert(where, value); } return this->end(); } iterator insert(Type &&value) { if (this->empty() || this->compare()(value, this->front())) { this->impl().push_front(std::move(value)); return this->begin(); } else if (this->compare()(this->back(), value)) { this->impl().push_back(std::move(value)); return (this->end() - 1); } auto where = this->getLowerBound(value); if (this->compare()(value, *where)) { return this->impl().insert(where, std::move(value)); } return this->end(); } template iterator emplace(Args&&... args) { return this->insert(Type(std::forward(args)...)); } bool remove(const Type &value) { return this->removeOne(value); } iterator find(const Type &value) { return this->findFirst(value); } const_iterator find(const Type &value) const { return this->findFirst(value); } template < typename OtherType, typename = typename Compare::is_transparent> iterator find(const OtherType &value) { return this->findFirst(value); } template < typename OtherType, typename = typename Compare::is_transparent> const_iterator find(const OtherType &value) const { return this->findFirst(value); } template void modify(iterator which, Action action) { action(which.wrapped()); for (auto i = iterator(which + 1), e = end(); i != e; ++i) { if (this->compare()(*i, *which)) { std::swap(i.wrapped(), which.wrapped()); } else if (!this->compare()(*which, *i)) { erase(which); return; } else{ break; } } for (auto i = which, b = begin(); i != b;) { --i; if (this->compare()(*which, *i)) { std::swap(i.wrapped(), which.wrapped()); } else if (!this->compare()(*i, *which)) { erase(which); return; } else { break; } } } template < typename Iterator, typename = typename std::iterator_traits::iterator_category> void merge(Iterator first, Iterator last) { parent::merge(first, last); finalize(); } void merge(const flat_multi_set &other) { merge(other.begin(), other.end()); } void merge(std::initializer_list list) { merge(list.begin(), list.end()); } private: void finalize() { this->impl().erase( std::unique( std::begin(this->impl()), std::end(this->impl()), [&](auto &&a, auto &&b) { return !this->compare()(a, b); } ), std::end(this->impl())); } }; } // namespace base tdesktop-1.2.17/Telegram/SourceFiles/base/flat_set_tests.cpp000066400000000000000000000035261326245125100240720ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "catch.hpp" #include "base/flat_set.h" struct int_wrap { int value; }; struct int_wrap_comparator { using is_transparent = void; inline bool operator()(const int &a, const int_wrap &b) const { return a < b.value; } inline bool operator()(const int_wrap &a, const int_wrap &b) const { return a.value < b.value; } inline bool operator()(const int_wrap &a, const int &b) const { return a.value < b; } inline bool operator()(const int &a, const int &b) const { return a < b; } }; TEST_CASE("flat_sets should keep items sorted", "[flat_set]") { base::flat_set v; v.insert(0); v.insert(5); v.insert(4); v.insert(2); REQUIRE(v.contains(4)); auto checkSorted = [&] { auto prev = v.begin(); REQUIRE(prev != v.end()); for (auto i = prev + 1; i != v.end(); prev = i, ++i) { REQUIRE(*prev < *i); } }; REQUIRE(v.size() == 4); checkSorted(); SECTION("adding item puts it in the right position") { v.insert(3); REQUIRE(v.size() == 5); REQUIRE(v.find(3) != v.end()); checkSorted(); } } TEST_CASE("flat_sets with custom comparators", "[flat_set]") { base::flat_set v; v.insert({ 0 }); v.insert({ 5 }); v.insert({ 4 }); v.insert({ 2 }); REQUIRE(v.find(4) != v.end()); auto checkSorted = [&] { auto prev = v.begin(); REQUIRE(prev != v.end()); for (auto i = prev + 1; i != v.end(); prev = i, ++i) { REQUIRE(prev->value < i->value); } }; REQUIRE(v.size() == 4); checkSorted(); SECTION("adding item puts it in the right position") { v.insert({ 3 }); REQUIRE(v.size() == 5); REQUIRE(v.find(3) != v.end()); checkSorted(); } } tdesktop-1.2.17/Telegram/SourceFiles/base/functors.h000066400000000000000000000016311326245125100223520ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace base { namespace functors { struct abs_helper { template < typename Type, typename = decltype(0 < std::declval()), typename = decltype(-std::declval())> constexpr Type operator()(Type value) const { return (0 < value) ? value : (-value); } }; constexpr auto abs = abs_helper{}; constexpr auto add = [](auto value) { return [value](auto other) { return value + other; }; }; struct negate_helper { template < typename Type, typename = decltype(-std::declval())> constexpr Type operator()(Type value) const { return -value; } }; constexpr auto negate = negate_helper{}; } // namespace functors } // namespace base tdesktop-1.2.17/Telegram/SourceFiles/base/index_based_iterator.h000066400000000000000000000057611326245125100246750ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "base/assertion.h" namespace base { template class index_based_iterator { public: using iterator_category = std::random_access_iterator_tag; using value_type = typename Container::value_type; using difference_type = typename Container::difference_type; using pointer = typename Container::pointer; using reference = typename Container::reference; index_based_iterator( Container *container, typename Container::iterator impl) : _container(container) , _index(impl - _container->begin()) { } reference operator*() const { return *(_container->begin() + _index); } pointer operator->() const { return std::addressof(**this); } index_based_iterator &operator++() { ++_index; return *this; } index_based_iterator operator++(int) { auto copy = *this; ++*this; return copy; } index_based_iterator &operator--() { --_index; return *this; } index_based_iterator operator--(int) { auto copy = *this; --*this; return copy; } index_based_iterator &operator+=(difference_type offset) { _index += offset; return *this; } index_based_iterator operator+(difference_type offset) const { auto copy = *this; copy += offset; return copy; } index_based_iterator &operator-=(difference_type offset) { _index -= offset; return *this; } index_based_iterator operator-(difference_type offset) const { auto copy = *this; copy -= offset; return copy; } difference_type operator-(const index_based_iterator &other) const { return _index - other._index; } reference operator[](difference_type offset) const { return *(*this + offset); } bool operator==(const index_based_iterator &other) const { Expects(_container == other._container); return _index == other._index; } bool operator!=(const index_based_iterator &other) const { Expects(_container == other._container); return _index != other._index; } bool operator<(const index_based_iterator &other) const { Expects(_container == other._container); return _index < other._index; } bool operator>(const index_based_iterator &other) const { return other < *this; } bool operator<=(const index_based_iterator &other) const { return !(other < *this); } bool operator>=(const index_based_iterator &other) const { return !(*this < other); } typename Container::iterator base() const { return _container->begin() + _index; } private: Container *_container = nullptr; difference_type _index = 0; }; template index_based_iterator index_based_begin(Container &container) { return { &container, std::begin(container) }; } template index_based_iterator index_based_end(Container &container) { return { &container, std::end(container) }; } } // namespace base tdesktop-1.2.17/Telegram/SourceFiles/base/lambda.h000066400000000000000000000361771326245125100217440ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include #ifndef CUSTOM_LAMBDA_WRAP #include #include "base/unique_function.h" namespace base { template using lambda = std::function; template using lambda_once = unique_function; namespace lambda_internal { template struct lambda_call_type { using type = decltype(&Lambda::operator()); }; } // namespace lambda_internal template using lambda_call_type_t = typename lambda_internal::lambda_call_type::type; } // namespace base #else // CUSTOM_LAMBDA_WRAP #ifndef Assert #define LambdaAssertDefined #define Assert(v) ((v) ? ((void)0) : std::abort()) #endif // Assert #ifndef Unexpected #define LambdaUnexpectedDefined #define Unexpected(v) std::abort() #endif // Unexpected namespace base { template class lambda_once; template class lambda; // Get lambda type from a lambda template parameter. namespace lambda_internal { template struct type_resolver; template struct type_resolver { using type = lambda; static constexpr auto is_mutable = false; }; template struct type_resolver { using type = lambda_once; static constexpr auto is_mutable = true; }; template struct type_helper { using type = typename type_resolver::type; static constexpr auto is_mutable = type_resolver::is_mutable; }; } // namespace lambda_internal template using lambda_type = typename lambda_internal::type_helper>::type; namespace lambda_internal { constexpr auto kFullStorageSize = 32U; static_assert(kFullStorageSize % sizeof(void*) == 0, "Invalid pointer size!"); constexpr auto kStorageSize = kFullStorageSize - sizeof(void*); using alignment = std::max_align_t; template constexpr bool is_large = (sizeof(std::decay_t) > kStorageSize); [[noreturn]] inline void bad_construct_copy(void *lambda, const void *source) { Unexpected("base::lambda bad_construct_copy() called!"); } template [[noreturn]] Return bad_const_call(const void *lambda, Args...) { Unexpected("base::lambda bad_const_call() called!"); } template struct vtable_base { using construct_copy_other_type = void(*)(void *, const void *); // dst, src using construct_move_other_type = void(*)(void *, void *); // dst, src using const_call_type = Return(*)(const void *, Args...); using call_type = Return(*)(void *, Args...); using destruct_type = void(*)(const void *); vtable_base() = delete; vtable_base(const vtable_base &other) = delete; vtable_base &operator=(const vtable_base &other) = delete; vtable_base( construct_copy_other_type construct_copy_other, construct_move_other_type construct_move_other, const_call_type const_call, call_type call, destruct_type destruct) : construct_copy_other(construct_copy_other) , construct_move_other(construct_move_other) , const_call(const_call) , call(call) , destruct(destruct) { } const construct_copy_other_type construct_copy_other; const construct_move_other_type construct_move_other; const const_call_type const_call; const call_type call; const destruct_type destruct; }; template struct vtable_once_impl; template struct vtable_once_impl : public vtable_base { using JustLambda = std::decay_t; using LambdaPtr = std::unique_ptr; using Parent = vtable_base; static void construct_move_other_method(void *storage, void *source) { auto source_lambda_ptr = static_cast(source); new (storage) LambdaPtr(std::move(*source_lambda_ptr)); } static Return call_method(void *storage, Args... args) { return (**static_cast(storage))(std::forward(args)...); } static void destruct_method(const void *storage) { static_cast(storage)->~LambdaPtr(); } vtable_once_impl() : Parent( &bad_construct_copy, &vtable_once_impl::construct_move_other_method, &bad_const_call, &vtable_once_impl::call_method, &vtable_once_impl::destruct_method) { } // Used directly. static void construct_move_lambda_method(void *storage, void *source) { auto source_lambda = static_cast(source); new (storage) LambdaPtr(std::make_unique(static_cast(*source_lambda))); } protected: vtable_once_impl( typename Parent::construct_copy_other_type construct_copy_other, typename Parent::const_call_type const_call ) : Parent( construct_copy_other, &vtable_once_impl::construct_move_other_method, const_call, &vtable_once_impl::call_method, &vtable_once_impl::destruct_method) { } }; template struct vtable_once_impl : public vtable_base { using JustLambda = std::decay_t; using Parent = vtable_base; static void construct_move_other_method(void *storage, void *source) { auto source_lambda = static_cast(source); new (storage) JustLambda(static_cast(*source_lambda)); } static Return call_method(void *storage, Args... args) { return (*static_cast(storage))(std::forward(args)...); } static void destruct_method(const void *storage) { static_cast(storage)->~JustLambda(); } vtable_once_impl() : Parent( &bad_construct_copy, &vtable_once_impl::construct_move_other_method, &bad_const_call, &vtable_once_impl::call_method, &vtable_once_impl::destruct_method) { } // Used directly. static void construct_move_lambda_method(void *storage, void *source) { auto source_lambda = static_cast(source); new (storage) JustLambda(static_cast(*source_lambda)); } protected: vtable_once_impl( typename Parent::construct_copy_other_type construct_copy_other, typename Parent::const_call_type const_call ) : Parent( construct_copy_other, &vtable_once_impl::construct_move_other_method, const_call, &vtable_once_impl::call_method, &vtable_once_impl::destruct_method) { } }; template struct vtable_once : public vtable_once_impl, Return, Args...> { static const vtable_once instance; }; template const vtable_once vtable_once::instance = {}; template struct vtable_impl; template struct vtable_impl : public vtable_once_impl { using JustLambda = std::decay_t; using LambdaPtr = std::unique_ptr; using Parent = vtable_once_impl; static void construct_copy_other_method(void *storage, const void *source) { auto source_lambda = static_cast(source); new (storage) LambdaPtr(std::make_unique(*source_lambda->get())); } static Return const_call_method(const void *storage, Args... args) { auto lambda_ptr = static_cast(storage)->get(); return (*static_cast(lambda_ptr))(std::forward(args)...); } vtable_impl() : Parent( &vtable_impl::construct_copy_other_method, &vtable_impl::const_call_method ) { } }; template struct vtable_impl : public vtable_once_impl { using JustLambda = std::decay_t; using Parent = vtable_once_impl; static void construct_copy_other_method(void *storage, const void *source) { auto source_lambda = static_cast(source); new (storage) JustLambda(static_cast(*source_lambda)); } static Return const_call_method(const void *storage, Args... args) { return (*static_cast(storage))(std::forward(args)...); } vtable_impl() : Parent( &vtable_impl::construct_copy_other_method, &vtable_impl::const_call_method ) { } }; template struct vtable : public vtable_impl, Return, Args...> { static const vtable instance; }; template const vtable vtable::instance = {}; } // namespace lambda_internal template class lambda_once { using VTable = lambda_internal::vtable_base; public: using return_type = Return; lambda_once() { data_.vtable = nullptr; } lambda_once(const lambda_once &other) = delete; lambda_once &operator=(const lambda_once &other) = delete; // Move construct / assign from the same type. lambda_once(lambda_once &&other) { if ((data_.vtable = other.data_.vtable)) { data_.vtable->construct_move_other(data_.storage, other.data_.storage); } } lambda_once &operator=(lambda_once &&other) { if (this != &other) { if (data_.vtable) { data_.vtable->destruct(data_.storage); } if ((data_.vtable = other.data_.vtable)) { data_.vtable->construct_move_other(data_.storage, other.data_.storage); data_.vtable->destruct(other.data_.storage); other.data_.vtable = nullptr; } } return *this; } // Move construct / assign from a derived type. lambda_once(lambda &&other) { if ((data_.vtable = other.data_.vtable)) { data_.vtable->construct_move_other(data_.storage, other.data_.storage); data_.vtable->destruct(other.data_.storage); other.data_.vtable = nullptr; } } lambda_once &operator=(lambda &&other) { if (this != &other) { if (data_.vtable) { data_.vtable->destruct(data_.storage); } if ((data_.vtable = other.data_.vtable)) { data_.vtable->construct_move_other(data_.storage, other.data_.storage); data_.vtable->destruct(other.data_.storage); other.data_.vtable = nullptr; } } return *this; } // Copy construct / assign from a derived type. lambda_once(const lambda &other) { if ((data_.vtable = other.data_.vtable)) { data_.vtable->construct_copy_other(data_.storage, other.data_.storage); } } lambda_once &operator=(const lambda &other) { if (this != &other) { if (data_.vtable) { data_.vtable->destruct(data_.storage); } if ((data_.vtable = other.data_.vtable)) { data_.vtable->construct_copy_other(data_.storage, other.data_.storage); } } return *this; } // Copy / move construct / assign from an arbitrary type. template ()(std::declval()...)),Return>::value>> lambda_once(Lambda other) { data_.vtable = &lambda_internal::vtable_once::instance; lambda_internal::vtable_once::construct_move_lambda_method(data_.storage, &other); } template ()(std::declval()...)),Return>::value>> lambda_once &operator=(Lambda other) { if (data_.vtable) { data_.vtable->destruct(data_.storage); } data_.vtable = &lambda_internal::vtable_once::instance; lambda_internal::vtable_once::construct_move_lambda_method(data_.storage, &other); return *this; } void swap(lambda_once &other) { if (this != &other) { std::swap(*this, other); } } template < typename ...OtherArgs, typename = std::enable_if_t<(sizeof...(Args) == sizeof...(OtherArgs))>> inline Return operator()(OtherArgs&&... args) { Assert(data_.vtable != nullptr); return data_.vtable->call( data_.storage, std::forward(args)...); } explicit operator bool() const { return (data_.vtable != nullptr); } ~lambda_once() { if (data_.vtable) { data_.vtable->destruct(data_.storage); } } protected: struct Private { }; lambda_once(const VTable *vtable, const Private &) { data_.vtable = vtable; } struct Data { char storage[lambda_internal::kStorageSize]; const VTable *vtable; }; union { lambda_internal::alignment alignment_; char raw_[lambda_internal::kFullStorageSize]; Data data_; }; }; template class lambda final : public lambda_once { using Parent = lambda_once; public: lambda() = default; // Move construct / assign from the same type. lambda(lambda &&other) : Parent(std::move(other)) { } lambda &operator=(lambda &&other) { Parent::operator=(std::move(other)); return *this; } // Copy construct / assign from the same type. lambda(const lambda &other) : Parent(other) { } lambda &operator=(const lambda &other) { Parent::operator=(other); return *this; } // Copy / move construct / assign from an arbitrary type. template ()(std::declval()...)),Return>::value>> lambda(Lambda other) : Parent(&lambda_internal::vtable::instance, typename Parent::Private()) { lambda_internal::vtable::construct_move_lambda_method(this->data_.storage, &other); } template ()(std::declval()...)),Return>::value>> lambda &operator=(Lambda other) { if (this->data_.vtable) { this->data_.vtable->destruct(this->data_.storage); } this->data_.vtable = &lambda_internal::vtable::instance; lambda_internal::vtable::construct_move_lambda_method(this->data_.storage, &other); return *this; } template < typename ...OtherArgs, typename = std::enable_if_t<(sizeof...(Args) == sizeof...(OtherArgs))>> inline Return operator()(OtherArgs&&... args) const { Assert(this->data_.vtable != nullptr); return this->data_.vtable->const_call( this->data_.storage, std::forward(args)...); } void swap(lambda &other) { if (this != &other) { std::swap(*this, other); } } }; } // namespace base #ifdef LambdaAssertDefined #undef Assert #endif // LambdaAssertDefined #ifdef LambdaUnexpectedDefined #undef Unexpected #endif // LambdaUnexpectedDefined #endif // CUSTOM_LAMBDA_WRAP tdesktop-1.2.17/Telegram/SourceFiles/base/lambda_guard.h000066400000000000000000000054131326245125100231130ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include #include "base/weak_ptr.h" // Guard lambda call by QObject* or has_weak_ptr* pointers. namespace base { namespace lambda_internal { template class guard_with_QObject { public: template guard_with_QObject(const QObject *object, OtherLambda &&other) : _guard(object) , _callable(std::forward(other)) { } template < typename ...OtherArgs, typename Return = decltype(std::declval()(std::declval()...))> Return operator()(OtherArgs &&...args) { return _guard ? _callable(std::forward(args)...) : Return(); } template < typename ...OtherArgs, typename Return = decltype(std::declval()(std::declval()...))> Return operator()(OtherArgs &&...args) const { return _guard ? _callable(std::forward(args)...) : Return(); } private: QPointer _guard; Lambda _callable; }; template class guard_with_weak { public: template guard_with_weak( const base::has_weak_ptr *object, OtherLambda &&other) : _guard(base::make_weak(object)) , _callable(std::forward(other)) { } template < typename ...OtherArgs, typename Return = decltype(std::declval()(std::declval()...))> Return operator()(OtherArgs &&...args) { return _guard ? _callable(std::forward(args)...) : Return(); } template < typename ...OtherArgs, typename Return = decltype(std::declval()(std::declval()...))> Return operator()(OtherArgs &&...args) const { return _guard ? _callable(std::forward(args)...) : Return(); } private: base::weak_ptr _guard; Lambda _callable; }; template struct lambda_call_type> { using type = lambda_call_type_t; }; template struct lambda_call_type> { using type = lambda_call_type_t; }; } // namespace lambda_internal template inline auto lambda_guarded(const QObject *object, Lambda &&lambda) { using Guarded = lambda_internal::guard_with_QObject< std::decay_t>; return Guarded(object, std::forward(lambda)); } template inline auto lambda_guarded( const base::has_weak_ptr *object, Lambda &&lambda) { using Guarded = lambda_internal::guard_with_weak< std::decay_t>; return Guarded(object, std::forward(lambda)); } } // namespace base tdesktop-1.2.17/Telegram/SourceFiles/base/observer.cpp000066400000000000000000000035641326245125100227000ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "base/observer.h" namespace base { namespace internal { namespace { bool CantUseObservables = false; struct ObservableListWrap { ~ObservableListWrap() { CantUseObservables = true; } OrderedSet list; }; ObservableListWrap &PendingObservables() { static ObservableListWrap result; return result; } ObservableListWrap &ActiveObservables() { static ObservableListWrap result; return result; } } // namespace void RegisterPendingObservable(ObservableCallHandlers *handlers) { if (CantUseObservables) return; PendingObservables().list.insert(handlers); Global::RefHandleObservables().call(); } void UnregisterActiveObservable(ObservableCallHandlers *handlers) { if (CantUseObservables) return; ActiveObservables().list.remove(handlers); } void UnregisterObservable(ObservableCallHandlers *handlers) { if (CantUseObservables) return; PendingObservables().list.remove(handlers); ActiveObservables().list.remove(handlers); } } // namespace internal void HandleObservables() { if (internal::CantUseObservables) return; auto &active = internal::ActiveObservables().list; qSwap(active, internal::PendingObservables().list); while (!active.empty()) { auto first = *active.begin(); (*first)(); if (!active.empty() && *active.begin() == first) { active.erase(active.begin()); } } } rpl::producer<> ObservableViewer(base::Observable &observable) { return [&observable](const auto &consumer) { auto lifetime = rpl::lifetime(); lifetime.make_state( observable.add_subscription([consumer]() { consumer.put_next({}); })); return lifetime; }; } } // namespace base tdesktop-1.2.17/Telegram/SourceFiles/base/observer.h000066400000000000000000000266331326245125100223470ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include #include #include #include "base/type_traits.h" namespace base { namespace internal { using ObservableCallHandlers = base::lambda; void RegisterPendingObservable(ObservableCallHandlers *handlers); void UnregisterActiveObservable(ObservableCallHandlers *handlers); void UnregisterObservable(ObservableCallHandlers *handlers); template struct SubscriptionHandlerHelper { using type = base::lambda)>; }; template <> struct SubscriptionHandlerHelper { using type = base::lambda; }; template using SubscriptionHandler = typename SubscriptionHandlerHelper::type; class BaseObservableData { }; template class CommonObservableData; template class ObservableData; } // namespace internal class Subscription { public: Subscription() = default; Subscription(const Subscription &) = delete; Subscription &operator=(const Subscription &) = delete; Subscription(Subscription &&other) : _node(base::take(other._node)), _removeAndDestroyMethod(other._removeAndDestroyMethod) { } Subscription &operator=(Subscription &&other) { qSwap(_node, other._node); qSwap(_removeAndDestroyMethod, other._removeAndDestroyMethod); return *this; } explicit operator bool() const { return (_node != nullptr); } void destroy() { if (_node) { (*_removeAndDestroyMethod)(base::take(_node)); } } ~Subscription() { destroy(); } private: struct Node { Node(const std::shared_ptr &observable) : observable(observable) { } Node *next = nullptr; Node *prev = nullptr; std::weak_ptr observable; }; using RemoveAndDestroyMethod = void(*)(Node*); Subscription(Node *node, RemoveAndDestroyMethod removeAndDestroyMethod) : _node(node) , _removeAndDestroyMethod(removeAndDestroyMethod) { } Node *_node = nullptr; RemoveAndDestroyMethod _removeAndDestroyMethod; template friend class internal::CommonObservableData; template friend class internal::ObservableData; }; namespace internal { template class BaseObservable; template class CommonObservable { public: Subscription add_subscription(Handler &&handler) { if (!_data) { _data = std::make_shared>(this); } return _data->append(std::move(handler)); } private: std::shared_ptr> _data; friend class CommonObservableData; friend class BaseObservable::is_fast_copy_type::value>; }; template class BaseObservable : public internal::CommonObservable { public: void notify(EventType event, bool sync = false) { if (this->_data) { this->_data->notify(std::move(event), sync); } } }; template class BaseObservable : public internal::CommonObservable { public: void notify(EventType &&event, bool sync = false) { if (this->_data) { this->_data->notify(std::move(event), sync); } } void notify(const EventType &event, bool sync = false) { if (this->_data) { auto event_copy = event; this->_data->notify(std::move(event_copy), sync); } } }; } // namespace internal namespace internal { template class CommonObservableData : public BaseObservableData { public: CommonObservableData(CommonObservable *observable) : _observable(observable) { } Subscription append(Handler &&handler) { auto node = new Node(_observable->_data, std::move(handler)); if (_begin) { _end->next = node; node->prev = _end; _end = node; } else { _begin = _end = node; } return { _end, &CommonObservableData::removeAndDestroyNode }; } bool empty() const { return !_begin; } private: struct Node : public Subscription::Node { Node( const std::shared_ptr &observer, Handler &&handler) : Subscription::Node(observer) , handler(std::move(handler)) { } Handler handler; }; void remove(Subscription::Node *node) { if (node->prev) { node->prev->next = node->next; } if (node->next) { node->next->prev = node->prev; } if (_begin == node) { _begin = static_cast(node->next); } if (_end == node) { _end = static_cast(node->prev); } if (_current == node) { _current = static_cast(node->prev); } else if (!_begin) { _observable->_data.reset(); } } static void removeAndDestroyNode(Subscription::Node *node) { if (const auto that = node->observable.lock()) { static_cast(that.get())->remove(node); } delete static_cast(node); } template void notifyEnumerate(CallCurrent callCurrent) { _current = _begin; do { callCurrent(); if (_current) { _current = static_cast(_current->next); } else if (_begin) { _current = _begin; } else { break; } } while (_current); } bool destroyMeIfEmpty() const { if (empty()) { _observable->_data.reset(); return true; } return false; } CommonObservable *_observable = nullptr; Node *_begin = nullptr; Node *_current = nullptr; Node *_end = nullptr; ObservableCallHandlers _callHandlers; friend class ObservableData; }; template class ObservableData : public CommonObservableData { public: using CommonObservableData::CommonObservableData; void notify(EventType &&event, bool sync) { if (_handling) { sync = false; } if (sync) { _events.push_back(std::move(event)); callHandlers(); } else { if (!this->_callHandlers) { this->_callHandlers = [this]() { callHandlers(); }; } if (_events.empty()) { RegisterPendingObservable(&this->_callHandlers); } _events.push_back(std::move(event)); } } ~ObservableData() { UnregisterObservable(&this->_callHandlers); } private: void callHandlers() { _handling = true; auto events = base::take(_events); for (auto &event : events) { this->notifyEnumerate([this, &event]() { this->_current->handler(event); }); if (this->destroyMeIfEmpty()) { return; } } _handling = false; UnregisterActiveObservable(&this->_callHandlers); } std::deque _events; bool _handling = false; }; template class ObservableData : public CommonObservableData { public: using CommonObservableData::CommonObservableData; void notify(bool sync) { if (_handling) { sync = false; } if (sync) { ++_eventsCount; callHandlers(); } else { if (!this->_callHandlers) { this->_callHandlers = [this]() { callHandlers(); }; } if (!_eventsCount) { RegisterPendingObservable(&this->_callHandlers); } ++_eventsCount; } } ~ObservableData() { UnregisterObservable(&this->_callHandlers); } private: void callHandlers() { _handling = true; auto eventsCount = base::take(_eventsCount); for (int i = 0; i != eventsCount; ++i) { this->notifyEnumerate([this]() { this->_current->handler(); }); if (this->destroyMeIfEmpty()) { return; } } _handling = false; UnregisterActiveObservable(&this->_callHandlers); } int _eventsCount = 0; bool _handling = false; }; template class BaseObservable::is_fast_copy_type::value> : public internal::CommonObservable { public: void notify(bool sync = false) { if (this->_data) { this->_data->notify(sync); } } }; } // namespace internal template > class Observable : public internal::BaseObservable::is_fast_copy_type::value> { public: Observable() = default; Observable(const Observable &other) = delete; Observable(Observable &&other) = default; Observable &operator=(const Observable &other) = delete; Observable &operator=(Observable &&other) = default; }; template class Variable { public: Variable(parameter_type startValue = Type()) : _value(startValue) { } Variable(Variable &&other) = default; Variable &operator=(Variable &&other) = default; parameter_type value() const { return _value; } void setForced(parameter_type newValue, bool sync = false) { _value = newValue; changed().notify(_value, sync); } void set(parameter_type newValue, bool sync = false) { if (_value != newValue) { setForced(newValue, sync); } } template void process(Callback callback, bool sync = false) { callback(_value); changed().notify(_value, sync); } Observable &changed() const { return _changed; } private: Type _value; mutable Observable _changed; }; class Subscriber { protected: template int subscribe(base::Observable &observable, Lambda &&handler) { _subscriptions.push_back(observable.add_subscription(std::forward(handler))); return _subscriptions.size(); } template int subscribe(base::Observable *observable, Lambda &&handler) { return subscribe(*observable, std::forward(handler)); } template int subscribe(const base::Variable &variable, Lambda &&handler) { return subscribe(variable.changed(), std::forward(handler)); } template int subscribe(const base::Variable *variable, Lambda &&handler) { return subscribe(variable->changed(), std::forward(handler)); } void unsubscribe(int index) { if (!index) return; auto count = static_cast(_subscriptions.size()); Assert(index > 0 && index <= count); _subscriptions[index - 1].destroy(); if (index == count) { while (index > 0 && !_subscriptions[--index]) { _subscriptions.pop_back(); } } } ~Subscriber() { auto subscriptions = base::take(_subscriptions); for (auto &subscription : subscriptions) { subscription.destroy(); } } private: std::vector _subscriptions; }; void HandleObservables(); template < typename Type, typename = std::enable_if_t>> inline auto ObservableViewer(base::Observable &observable) { return rpl::make_producer([&observable]( const auto &consumer) { auto lifetime = rpl::lifetime(); lifetime.make_state( observable.add_subscription([consumer](auto &&update) { consumer.put_next_forward( std::forward(update)); })); return lifetime; }); } rpl::producer<> ObservableViewer(base::Observable &observable); } // namespace base tdesktop-1.2.17/Telegram/SourceFiles/base/openssl_help.h000066400000000000000000000114311326245125100232010ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include #include #include namespace openssl { class Context { public: Context() : _data(BN_CTX_new()) { } Context(const Context &other) = delete; Context(Context &&other) : _data(base::take(other._data)) { } Context &operator=(const Context &other) = delete; Context &operator=(Context &&other) { _data = base::take(other._data); return *this; } ~Context() { if (_data) { BN_CTX_free(_data); } } BN_CTX *raw() const { return _data; } private: BN_CTX *_data = nullptr; }; class BigNum { public: BigNum() : _data(BN_new()) { } BigNum(const BigNum &other) : BigNum() { *this = other; } BigNum &operator=(const BigNum &other) { if (other.failed() || !BN_copy(raw(), other.raw())) { _failed = true; } return *this; } ~BigNum() { BN_clear_free(raw()); } explicit BigNum(unsigned int word) : BigNum() { setWord(word); } explicit BigNum(base::const_byte_span bytes) : BigNum() { setBytes(bytes); } void setWord(unsigned int word) { if (!BN_set_word(raw(), word)) { _failed = true; } } void setBytes(base::const_byte_span bytes) { if (!BN_bin2bn( reinterpret_cast(bytes.data()), bytes.size(), raw())) { _failed = true; } } void setModExp( const BigNum &a, const BigNum &p, const BigNum &m, const Context &context = Context()) { if (a.failed() || p.failed() || m.failed()) { _failed = true; } else if (a.isNegative() || p.isNegative() || m.isNegative()) { _failed = true; } else if (!BN_mod_exp(raw(), a.raw(), p.raw(), m.raw(), context.raw())) { _failed = true; } else if (isNegative()) { _failed = true; } } void setSub(const BigNum &a, const BigNum &b) { if (a.failed() || b.failed()) { _failed = true; } else if (!BN_sub(raw(), a.raw(), b.raw())) { _failed = true; } } void setSubWord(unsigned int word) { if (failed()) { return; } else if (!BN_sub_word(raw(), word)) { _failed = true; } } BN_ULONG setDivWord(BN_ULONG word) { Expects(word != 0); if (failed()) { return (BN_ULONG)-1; } auto result = BN_div_word(raw(), word); if (result == (BN_ULONG)-1) { _failed = true; } return result; } bool isNegative() const { return failed() ? false : BN_is_negative(raw()); } bool isPrime(const Context &context = Context()) const { if (failed()) { return false; } constexpr auto kMillerRabinIterationCount = 30; auto result = BN_is_prime_ex( raw(), kMillerRabinIterationCount, context.raw(), NULL); if (result == 1) { return true; } else if (result != 0) { _failed = true; } return false; } BN_ULONG modWord(BN_ULONG word) const { Expects(word != 0); if (failed()) { return (BN_ULONG)-1; } auto result = BN_mod_word(raw(), word); if (result == (BN_ULONG)-1) { _failed = true; } return result; } int bitsSize() const { return failed() ? 0 : BN_num_bits(raw()); } int bytesSize() const { return failed() ? 0 : BN_num_bytes(raw()); } base::byte_vector getBytes() const { if (failed()) { return base::byte_vector(); } auto length = BN_num_bytes(raw()); auto result = base::byte_vector(length, gsl::byte()); auto resultSize = BN_bn2bin( raw(), reinterpret_cast(result.data())); Assert(resultSize == length); return result; } BIGNUM *raw() { return _data; } const BIGNUM *raw() const { return _data; } BIGNUM *takeRaw() { return base::take(_data); } bool failed() const { return _failed; } static BigNum ModExp(const BigNum &base, const BigNum &power, const openssl::BigNum &mod) { BigNum result; result.setModExp(base, power, mod); return result; } private: BIGNUM *_data = nullptr; mutable bool _failed = false; }; inline BigNum operator-(const BigNum &a, const BigNum &b) { BigNum result; result.setSub(a, b); return result; } inline base::byte_array Sha256(base::const_byte_span bytes) { auto result = base::byte_array(); SHA256(reinterpret_cast(bytes.data()), bytes.size(), reinterpret_cast(result.data())); return result; } inline base::byte_array Sha1(base::const_byte_span bytes) { auto result = base::byte_array(); SHA1(reinterpret_cast(bytes.data()), bytes.size(), reinterpret_cast(result.data())); return result; } inline int FillRandom(base::byte_span bytes) { return RAND_bytes(reinterpret_cast(bytes.data()), bytes.size()); } } // namespace openssl tdesktop-1.2.17/Telegram/SourceFiles/base/optional.h000066400000000000000000000111301326245125100223270ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include #include "base/variant.h" namespace base { struct none_type { bool operator==(none_type other) const { return true; } bool operator!=(none_type other) const { return false; } bool operator<(none_type other) const { return false; } bool operator<=(none_type other) const { return true; } bool operator>(none_type other) const { return false; } bool operator>=(none_type other) const { return true; } }; constexpr none_type none = {}; template class optional_variant { public: optional_variant() : _impl(none) { } optional_variant(const optional_variant &other) : _impl(other._impl) { } optional_variant(optional_variant &&other) : _impl(std::move(other._impl)) { } template >::value>> optional_variant(T &&value) : _impl(std::forward(value)) { } optional_variant &operator=(const optional_variant &other) { _impl = other._impl; return *this; } optional_variant &operator=(optional_variant &&other) { _impl = std::move(other._impl); return *this; } template >::value>> optional_variant &operator=(T &&value) { _impl = std::forward(value); return *this; } bool has_value() const { return !is(); } explicit operator bool() const { return has_value(); } bool operator==(const optional_variant &other) const { return _impl == other._impl; } bool operator!=(const optional_variant &other) const { return _impl != other._impl; } bool operator<(const optional_variant &other) const { return _impl < other._impl; } bool operator<=(const optional_variant &other) const { return _impl <= other._impl; } bool operator>(const optional_variant &other) const { return _impl > other._impl; } bool operator>=(const optional_variant &other) const { return _impl >= other._impl; } template decltype(auto) is() const { return _impl.template is(); } template decltype(auto) get_unchecked() { return _impl.template get_unchecked(); } template decltype(auto) get_unchecked() const { return _impl.template get_unchecked(); } private: variant _impl; }; template inline T *get_if(optional_variant *v) { return (v && v->template is()) ? &v->template get_unchecked() : nullptr; } template inline const T *get_if(const optional_variant *v) { return (v && v->template is()) ? &v->template get_unchecked() : nullptr; } template class optional; template struct optional_wrap_once { using type = optional; }; template struct optional_wrap_once> { using type = optional; }; template using optional_wrap_once_t = typename optional_wrap_once>::type; template struct optional_chain_result { using type = optional_wrap_once_t; }; template <> struct optional_chain_result { using type = bool; }; template using optional_chain_result_t = typename optional_chain_result::type; template class optional : public optional_variant { public: using optional_variant::optional_variant; Type &operator*() { auto result = get_if(this); Expects(result != nullptr); return *result; } const Type &operator*() const { auto result = get_if(this); Expects(result != nullptr); return *result; } Type *operator->() { auto result = get_if(this); Expects(result != nullptr); return result; } const Type *operator->() const { auto result = get_if(this); Expects(result != nullptr); return result; } }; template optional_wrap_once_t make_optional(Type &&value) { return optional_wrap_once_t { std::forward(value) }; } template inline auto operator|(const optional &value, Method method) -> optional_chain_result_t { if constexpr (std::is_same_v) { return value ? (method(*value), true) : false; } else { return value ? make_optional(method(*value)) : none; } } } // namespace base tdesktop-1.2.17/Telegram/SourceFiles/base/ordered_set.h000066400000000000000000000147201326245125100230110ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include // ordered set template based on QMap template class OrderedSet { struct NullType { }; using Self = OrderedSet; using Impl = QMap; using IteratorImpl = typename Impl::iterator; using ConstIteratorImpl = typename Impl::const_iterator; Impl impl_; public: OrderedSet() = default; OrderedSet(const OrderedSet &other) = default; OrderedSet(OrderedSet &&other) = default; OrderedSet &operator=(const OrderedSet &other) = default; OrderedSet &operator=(OrderedSet &&other) = default; ~OrderedSet() = default; inline bool operator==(const Self &other) const { return impl_ == other.impl_; } inline bool operator!=(const Self &other) const { return impl_ != other.impl_; } inline int size() const { return impl_.size(); } inline bool isEmpty() const { return impl_.isEmpty(); } inline void detach() { return impl_.detach(); } inline bool isDetached() const { return impl_.isDetached(); } inline void clear() { return impl_.clear(); } inline QList values() const { return impl_.keys(); } inline const T &first() const { return impl_.firstKey(); } inline const T &last() const { return impl_.lastKey(); } class const_iterator; class iterator { public: typedef typename IteratorImpl::iterator_category iterator_category; typedef typename IteratorImpl::difference_type difference_type; typedef T value_type; typedef T *pointer; typedef T &reference; iterator() = default; iterator(const iterator &other) = default; iterator &operator=(const iterator &other) = default; inline const T &operator*() const { return impl_.key(); } inline const T *operator->() const { return &impl_.key(); } inline bool operator==(const iterator &other) const { return impl_ == other.impl_; } inline bool operator!=(const iterator &other) const { return impl_ != other.impl_; } inline iterator &operator++() { ++impl_; return *this; } inline iterator operator++(int) { return iterator(impl_++); } inline iterator &operator--() { --impl_; return *this; } inline iterator operator--(int) { return iterator(impl_--); } inline iterator operator+(int j) const { return iterator(impl_ + j); } inline iterator operator-(int j) const { return iterator(impl_ - j); } inline iterator &operator+=(int j) { impl_ += j; return *this; } inline iterator &operator-=(int j) { impl_ -= j; return *this; } friend class const_iterator; inline bool operator==(const const_iterator &other) const { return impl_ == other.impl_; } inline bool operator!=(const const_iterator &other) const { return impl_ != other.impl_; } private: explicit iterator(const IteratorImpl &impl) : impl_(impl) { } IteratorImpl impl_; friend class OrderedSet; }; friend class iterator; class const_iterator { public: typedef typename IteratorImpl::iterator_category iterator_category; typedef typename IteratorImpl::difference_type difference_type; typedef T value_type; typedef T *pointer; typedef T &reference; const_iterator() = default; const_iterator(const const_iterator &other) = default; const_iterator &operator=(const const_iterator &other) = default; const_iterator(const iterator &other) : impl_(other.impl_) { } const_iterator &operator=(const iterator &other) { impl_ = other.impl_; return *this; } inline const T &operator*() const { return impl_.key(); } inline const T *operator->() const { return &impl_.key(); } inline bool operator==(const const_iterator &other) const { return impl_ == other.impl_; } inline bool operator!=(const const_iterator &other) const { return impl_ != other.impl_; } inline const_iterator &operator++() { ++impl_; return *this; } inline const_iterator operator++(int) { return const_iterator(impl_++); } inline const_iterator &operator--() { --impl_; return *this; } inline const_iterator operator--(int) { return const_iterator(impl_--); } inline const_iterator operator+(int j) const { return const_iterator(impl_ + j); } inline const_iterator operator-(int j) const { return const_iterator(impl_ - j); } inline const_iterator &operator+=(int j) { impl_ += j; return *this; } inline const_iterator &operator-=(int j) { impl_ -= j; return *this; } friend class iterator; inline bool operator==(const iterator &other) const { return impl_ == other.impl_; } inline bool operator!=(const iterator &other) const { return impl_ != other.impl_; } private: explicit const_iterator(const ConstIteratorImpl &impl) : impl_(impl) { } ConstIteratorImpl impl_; friend class OrderedSet; }; friend class const_iterator; // STL style inline iterator begin() { return iterator(impl_.begin()); } inline const_iterator begin() const { return const_iterator(impl_.cbegin()); } inline const_iterator constBegin() const { return const_iterator(impl_.cbegin()); } inline const_iterator cbegin() const { return const_iterator(impl_.cbegin()); } inline iterator end() { detach(); return iterator(impl_.end()); } inline const_iterator end() const { return const_iterator(impl_.cend()); } inline const_iterator constEnd() const { return const_iterator(impl_.cend()); } inline const_iterator cend() const { return const_iterator(impl_.cend()); } inline iterator erase(iterator it) { return iterator(impl_.erase(it.impl_)); } inline iterator insert(const T &value) { return iterator(impl_.insert(value, NullType())); } inline iterator insert(const_iterator pos, const T &value) { return iterator(impl_.insert(pos.impl_, value, NullType())); } inline int remove(const T &value) { return impl_.remove(value); } inline bool contains(const T &value) const { return impl_.contains(value); } // more Qt typedef iterator Iterator; typedef const_iterator ConstIterator; inline int count() const { return impl_.count(); } inline iterator find(const T &value) { return iterator(impl_.find(value)); } inline const_iterator find(const T &value) const { return const_iterator(impl_.constFind(value)); } inline const_iterator constFind(const T &value) const { return const_iterator(impl_.constFind(value)); } inline Self &unite(const Self &other) { impl_.unite(other.impl_); return *this; } // STL compatibility typedef typename Impl::difference_type difference_type; typedef typename Impl::size_type size_type; inline bool empty() const { return impl_.empty(); } }; tdesktop-1.2.17/Telegram/SourceFiles/base/overload.h000066400000000000000000000046641326245125100223330ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace base { namespace details { // This implementation was taken from range-v3 library. // It was modified so that more than one of passed function objects // could be called with an argument list and the first one that // matches gets used instead of a compile-time ambiguity error. // // This allows to write "default" visitor handlers with [](auto&&) syntax. template constexpr bool is_callable_v = rpl::details::is_callable_plain_v; template struct overloaded; template <> struct overloaded<> { }; template struct overloaded : private First , private overloaded { private: using Others = overloaded; public: overloaded() = default; constexpr overloaded(First first, Rest... rest) : First(std::move(first)) , Others(std::move(rest)...) { } template auto operator()(Args&&... args) -> decltype(std::declval()(std::forward(args)...)) { return static_cast(*this)(std::forward(args)...); } template auto operator()(Args&&... args) const -> decltype(std::declval()(std::forward(args)...)) { return static_cast(*this)(std::forward(args)...); } template < typename... Args, typename = std::enable_if_t>> auto operator()(Args&&... args) -> decltype(std::declval()(std::forward(args)...)) { return static_cast(*this)(std::forward(args)...); } template < typename... Args, typename = std::enable_if_t>> auto operator()(Args&&... args) const -> decltype(std::declval()(std::forward(args)...)) { return static_cast(*this)(std::forward(args)...); } }; } // namespace details template Function overload(Function &&function) { return std::forward(function); } template 1)>> auto overload(Functions ...functions) { return details::overloaded(std::move(functions)...); } } // namespace base tdesktop-1.2.17/Telegram/SourceFiles/base/parse_helper.cpp000066400000000000000000000055201326245125100235140ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "base/parse_helper.h" namespace base { namespace parse { // inspired by https://github.com/sindresorhus/strip-json-comments QByteArray stripComments(const QByteArray &content) { enum class InsideComment { None, SingleLine, MultiLine, }; auto insideComment = InsideComment::None; auto insideString = false; QByteArray result; auto begin = content.cbegin(), end = content.cend(), offset = begin; auto feedContent = [&result, &offset, end](const char *ch) { if (ch > offset) { if (result.isEmpty()) result.reserve(end - offset - 2); result.append(offset, ch - offset); offset = ch; } }; auto feedComment = [&result, &offset, end](const char *ch) { if (ch > offset) { if (result.isEmpty()) result.reserve(end - offset - 2); result.append(' '); offset = ch; } }; for (auto ch = offset; ch != end;) { auto currentChar = *ch; auto nextChar = (ch + 1 == end) ? 0 : *(ch + 1); if (insideComment == InsideComment::None && currentChar == '"') { auto escaped = ((ch > begin) && *(ch - 1) == '\\') && ((ch - 1 < begin) || *(ch - 2) != '\\'); if (!escaped) { insideString = !insideString; } } if (insideString) { ++ch; continue; } if (insideComment == InsideComment::None && currentChar == '/' && nextChar == '/') { feedContent(ch); insideComment = InsideComment::SingleLine; ch += 2; } else if (insideComment == InsideComment::SingleLine && currentChar == '\r' && nextChar == '\n') { feedComment(ch); ch += 2; insideComment = InsideComment::None; } else if (insideComment == InsideComment::SingleLine && currentChar == '\n') { feedComment(ch); ++ch; insideComment = InsideComment::None; } else if (insideComment == InsideComment::None && currentChar == '/' && nextChar == '*') { feedContent(ch); ch += 2; insideComment = InsideComment::MultiLine; } else if (insideComment == InsideComment::MultiLine && currentChar == '*' && nextChar == '/') { ch += 2; feedComment(ch); insideComment = InsideComment::None; } else if (insideComment == InsideComment::MultiLine && currentChar == '\r' && nextChar == '\n') { feedComment(ch); ch += 2; feedContent(ch); } else if (insideComment == InsideComment::MultiLine && currentChar == '\n') { feedComment(ch); ++ch; feedContent(ch); } else { ++ch; } } if (insideComment == InsideComment::MultiLine) { // unexpected end of content } if (insideComment == InsideComment::None && end > offset) { if (result.isEmpty()) { return content; } else { result.append(offset, end - offset); } } return result; } } // namespace parse } // namespace base tdesktop-1.2.17/Telegram/SourceFiles/base/parse_helper.h000066400000000000000000000017201326245125100231570ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace base { namespace parse { // Strip all C-style comments. QByteArray stripComments(const QByteArray &content); inline bool skipWhitespaces(const char *&from, const char *end) { Assert(from <= end); while (from != end && ( (*from == ' ') || (*from == '\n') || (*from == '\t') || (*from == '\r'))) { ++from; } return (from != end); } inline QLatin1String readName(const char *&from, const char *end) { Assert(from <= end); auto start = from; while (from != end && ( (*from >= 'a' && *from <= 'z') || (*from >= 'A' && *from <= 'Z') || (*from >= '0' && *from <= '9') || (*from == '_'))) { ++from; } return QLatin1String(start, from - start); } } // namespace parse } // namespace base tdesktop-1.2.17/Telegram/SourceFiles/base/qthelp_regex.h000066400000000000000000000055261326245125100232050ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "base/flags.h" namespace qthelp { class RegularExpressionMatch { public: RegularExpressionMatch(const QRegularExpressionMatch &other) = delete; RegularExpressionMatch(const RegularExpressionMatch &other) = delete; RegularExpressionMatch(QRegularExpressionMatch &&match) : data_(std::move(match)) { } RegularExpressionMatch(RegularExpressionMatch &&other) : data_(std::move(other.data_)) { } RegularExpressionMatch &operator=(const QRegularExpressionMatch &match) = delete; RegularExpressionMatch &operator=(const RegularExpressionMatch &other) = delete; RegularExpressionMatch &operator=(QRegularExpressionMatch &&match) { data_ = std::move(match); return *this; } RegularExpressionMatch &operator=(RegularExpressionMatch &&other) { data_ = std::move(other.data_); return *this; } QRegularExpressionMatch *operator->() { return &data_; } const QRegularExpressionMatch *operator->() const { return &data_; } explicit operator bool() const { return data_.hasMatch(); } private: QRegularExpressionMatch data_; }; enum class RegExOption { None = QRegularExpression::NoPatternOption, CaseInsensitive = QRegularExpression::CaseInsensitiveOption, DotMatchesEverything = QRegularExpression::DotMatchesEverythingOption, Multiline = QRegularExpression::MultilineOption, ExtendedSyntax = QRegularExpression::ExtendedPatternSyntaxOption, InvertedGreediness = QRegularExpression::InvertedGreedinessOption, DontCapture = QRegularExpression::DontCaptureOption, UseUnicodeProperties = QRegularExpression::UseUnicodePropertiesOption, #ifndef OS_MAC_OLD OptimizeOnFirstUsage = QRegularExpression::OptimizeOnFirstUsageOption, DontAutomaticallyOptimize = QRegularExpression::DontAutomaticallyOptimizeOption, #endif // OS_MAC_OLD }; using RegExOptions = base::flags; inline constexpr auto is_flag_type(RegExOption) { return true; }; inline RegularExpressionMatch regex_match(const QString &string, const QString &subject, RegExOptions options = 0) { auto qtOptions = QRegularExpression::PatternOptions(static_cast(options)); return RegularExpressionMatch(QRegularExpression(string, qtOptions).match(subject)); } inline RegularExpressionMatch regex_match(const QString &string, const QStringRef &subjectRef, RegExOptions options = 0) { auto qtOptions = QRegularExpression::PatternOptions(static_cast(options)); #ifndef OS_MAC_OLD return RegularExpressionMatch(QRegularExpression(string, qtOptions).match(subjectRef)); #else // OS_MAC_OLD return RegularExpressionMatch(QRegularExpression(string, qtOptions).match(subjectRef.toString())); #endif // OS_MAC_OLD } } // namespace qthelp tdesktop-1.2.17/Telegram/SourceFiles/base/qthelp_url.cpp000066400000000000000000000021621326245125100232210ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "base/qthelp_url.h" namespace qthelp { QMap url_parse_params( const QString ¶ms, UrlParamNameTransform transform) { auto result = QMap(); const auto transformParamName = [transform](const QString &name) { if (transform == UrlParamNameTransform::ToLower) { return name.toLower(); } return name; }; for (const auto ¶m : params.split('&')) { // Skip params without a name (starting with '='). if (auto separatorPosition = param.indexOf('=')) { const auto paramName = transformParamName( (separatorPosition > 0) ? param.mid(0, separatorPosition) : param); const auto paramValue = (separatorPosition > 0) ? url_decode(param.mid(separatorPosition + 1)) : QString(); if (!result.contains(paramName)) { result.insert(paramName, paramValue); } } } return result; } } // namespace qthelp tdesktop-1.2.17/Telegram/SourceFiles/base/qthelp_url.h000066400000000000000000000014021326245125100226620ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace qthelp { inline QString url_encode(const QString &part) { return QString::fromLatin1(QUrl::toPercentEncoding(part)); } inline QString url_decode(const QString &encoded) { return QUrl::fromPercentEncoding(encoded.toUtf8()); } enum class UrlParamNameTransform { NoTransform, ToLower, }; // Parses a string like "p1=v1&p2=v2&..&pn=vn" to a map. QMap url_parse_params(const QString ¶ms, UrlParamNameTransform transform = UrlParamNameTransform::NoTransform); } // namespace qthelp tdesktop-1.2.17/Telegram/SourceFiles/base/runtime_composer.cpp000066400000000000000000000022561326245125100244400ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "base/runtime_composer.h" struct RuntimeComposerMetadatasMap { QMap data; ~RuntimeComposerMetadatasMap() { for_const (const RuntimeComposerMetadata *p, data) { delete p; } } }; const RuntimeComposerMetadata *GetRuntimeComposerMetadata(uint64 mask) { static RuntimeComposerMetadatasMap RuntimeComposerMetadatas; static QMutex RuntimeComposerMetadatasMutex; QMutexLocker lock(&RuntimeComposerMetadatasMutex); auto i = RuntimeComposerMetadatas.data.constFind(mask); if (i == RuntimeComposerMetadatas.data.cend()) { RuntimeComposerMetadata *meta = new RuntimeComposerMetadata(mask); Assert(meta != nullptr); i = RuntimeComposerMetadatas.data.insert(mask, meta); } return i.value(); } const RuntimeComposerMetadata *RuntimeComposerBase::ZeroRuntimeComposerMetadata = GetRuntimeComposerMetadata(0); RuntimeComponentWrapStruct RuntimeComponentWraps[64]; QAtomicInt RuntimeComponentIndexLast; tdesktop-1.2.17/Telegram/SourceFiles/base/runtime_composer.h000066400000000000000000000171061326245125100241050ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once template class RuntimeComposer; class RuntimeComposerBase; typedef void(*RuntimeComponentConstruct)(void *location, RuntimeComposerBase *composer); typedef void(*RuntimeComponentDestruct)(void *location); typedef void(*RuntimeComponentMove)(void *location, void *waslocation); struct RuntimeComponentWrapStruct { // Don't init any fields, because it is only created in // global scope, so it will be filled by zeros from the start. RuntimeComponentWrapStruct() = default; RuntimeComponentWrapStruct(std::size_t size, std::size_t align, RuntimeComponentConstruct construct, RuntimeComponentDestruct destruct, RuntimeComponentMove move) : Size(size) , Align(align) , Construct(construct) , Destruct(destruct) , Move(move) { } std::size_t Size; std::size_t Align; RuntimeComponentConstruct Construct; RuntimeComponentDestruct Destruct; RuntimeComponentMove Move; }; template struct CeilDivideMinimumOne { static constexpr int Result = ((Value / Denominator) + ((!Value || (Value % Denominator)) ? 1 : 0)); }; extern RuntimeComponentWrapStruct RuntimeComponentWraps[64]; extern QAtomicInt RuntimeComponentIndexLast; template struct RuntimeComponent { using RuntimeComponentBase = Base; RuntimeComponent() { // While there is no std::aligned_alloc(). static_assert(alignof(Type) <= alignof(std::max_align_t), "Components should align to std::max_align_t!"); } RuntimeComponent(const RuntimeComponent &other) = delete; RuntimeComponent &operator=(const RuntimeComponent &other) = delete; RuntimeComponent(RuntimeComponent &&other) = delete; RuntimeComponent &operator=(RuntimeComponent &&other) = default; static int Index() { static QAtomicInt MyIndex(0); if (auto index = MyIndex.loadAcquire()) { return index - 1; } while (true) { auto last = RuntimeComponentIndexLast.loadAcquire(); if (RuntimeComponentIndexLast.testAndSetOrdered(last, last + 1)) { Assert(last < 64); if (MyIndex.testAndSetOrdered(0, last + 1)) { RuntimeComponentWraps[last] = RuntimeComponentWrapStruct( sizeof(Type), alignof(Type), Type::RuntimeComponentConstruct, Type::RuntimeComponentDestruct, Type::RuntimeComponentMove); } break; } } return MyIndex.loadAcquire() - 1; } static uint64 Bit() { return (1ULL << Index()); } protected: static void RuntimeComponentConstruct(void *location, RuntimeComposerBase *composer) { new (location) Type(); } static void RuntimeComponentDestruct(void *location) { ((Type*)location)->~Type(); } static void RuntimeComponentMove(void *location, void *waslocation) { *(Type*)location = std::move(*(Type*)waslocation); } }; class RuntimeComposerMetadata { public: RuntimeComposerMetadata(uint64 mask) : _mask(mask) { for (int i = 0; i != 64; ++i) { auto componentBit = (1ULL << i); if (_mask & componentBit) { auto componentSize = RuntimeComponentWraps[i].Size; if (componentSize) { auto componentAlign = RuntimeComponentWraps[i].Align; if (auto badAlign = (size % componentAlign)) { size += (componentAlign - badAlign); } offsets[i] = size; size += componentSize; accumulate_max(align, componentAlign); } } else if (_mask < componentBit) { last = i; break; } } } // Meta pointer in the start. std::size_t size = sizeof(const RuntimeComposerMetadata*); std::size_t align = alignof(const RuntimeComposerMetadata*); std::size_t offsets[64] = { 0 }; int last = 64; bool equals(uint64 mask) const { return _mask == mask; } uint64 maskadd(uint64 mask) const { return _mask | mask; } uint64 maskremove(uint64 mask) const { return _mask & (~mask); } private: uint64 _mask; }; const RuntimeComposerMetadata *GetRuntimeComposerMetadata(uint64 mask); class RuntimeComposerBase { public: RuntimeComposerBase(uint64 mask = 0) : _data(zerodata()) { if (mask) { auto meta = GetRuntimeComposerMetadata(mask); auto data = operator new(meta->size); Assert(data != nullptr); _data = data; _meta() = meta; for (int i = 0; i < meta->last; ++i) { auto offset = meta->offsets[i]; if (offset >= sizeof(_meta())) { try { auto constructAt = _dataptrunsafe(offset); auto space = RuntimeComponentWraps[i].Size; auto alignedAt = constructAt; std::align(RuntimeComponentWraps[i].Align, space, alignedAt, space); Assert(alignedAt == constructAt); RuntimeComponentWraps[i].Construct(constructAt, this); } catch (...) { while (i > 0) { --i; offset = meta->offsets[--i]; if (offset >= sizeof(_meta())) { RuntimeComponentWraps[i].Destruct(_dataptrunsafe(offset)); } } throw; } } } } } RuntimeComposerBase(const RuntimeComposerBase &other) = delete; RuntimeComposerBase &operator=(const RuntimeComposerBase &other) = delete; ~RuntimeComposerBase() { if (_data != zerodata()) { auto meta = _meta(); for (int i = 0; i < meta->last; ++i) { auto offset = meta->offsets[i]; if (offset >= sizeof(_meta())) { RuntimeComponentWraps[i].Destruct(_dataptrunsafe(offset)); } } operator delete(_data); } } protected: bool UpdateComponents(uint64 mask = 0) { if (_meta()->equals(mask)) { return false; } RuntimeComposerBase result(mask); result.swap(*this); if (_data != zerodata() && result._data != zerodata()) { const auto meta = _meta(); const auto wasmeta = result._meta(); for (auto i = 0; i != meta->last; ++i) { const auto offset = meta->offsets[i]; const auto wasoffset = wasmeta->offsets[i]; if (offset >= sizeof(_meta()) && wasoffset >= sizeof(_meta())) { RuntimeComponentWraps[i].Move( _dataptrunsafe(offset), result._dataptrunsafe(wasoffset)); } } } return true; } bool AddComponents(uint64 mask = 0) { return UpdateComponents(_meta()->maskadd(mask)); } bool RemoveComponents(uint64 mask = 0) { return UpdateComponents(_meta()->maskremove(mask)); } private: template friend class RuntimeComposer; static const RuntimeComposerMetadata *ZeroRuntimeComposerMetadata; static void *zerodata() { return &ZeroRuntimeComposerMetadata; } void *_dataptrunsafe(int skip) const { return (char*)_data + skip; } void *_dataptr(int skip) const { return (skip >= sizeof(_meta())) ? _dataptrunsafe(skip) : nullptr; } const RuntimeComposerMetadata *&_meta() const { return *static_cast(_data); } void *_data = nullptr; void swap(RuntimeComposerBase &other) { std::swap(_data, other._data); } }; template class RuntimeComposer : public RuntimeComposerBase { public: using RuntimeComposerBase::RuntimeComposerBase; template < typename Type, typename = std::enable_if_t>> bool Has() const { return (_meta()->offsets[Type::Index()] >= sizeof(_meta())); } template < typename Type, typename = std::enable_if_t>> Type *Get() { return static_cast(_dataptr(_meta()->offsets[Type::Index()])); } template < typename Type, typename = std::enable_if_t>> const Type *Get() const { return static_cast(_dataptr(_meta()->offsets[Type::Index()])); } }; tdesktop-1.2.17/Telegram/SourceFiles/base/tests_main.cpp000066400000000000000000000071261326245125100232150ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #define CATCH_CONFIG_RUNNER #include "catch.hpp" #include "reporters/catch_reporter_compact.hpp" #include namespace base { namespace assertion { // For Assert() / Expects() / Ensures() / Unexpected() to work. void log(const char *message, const char *file, int line) { std::cout << message << " (" << file << ":" << line << ")" << std::endl; } } // namespace assertion } // namespace base namespace Catch { struct MinimalReporter : CompactReporter { MinimalReporter( ReporterConfig const& _config ) : CompactReporter( _config ) {} virtual void testRunEnded( TestRunStats const& _testRunStats ) { printTotals( _testRunStats.totals ); } private: // Colour, message variants: // - white: No tests ran. // - red: Failed [both/all] N test cases, failed [both/all] M assertions. // - white: Passed [both/all] N test cases (no assertions). // - red: Failed N tests cases, failed M assertions. // - green: Passed [both/all] N tests cases with M assertions. std::string bothOrAll( std::size_t count ) const { return count == 1 ? std::string() : count == 2 ? "both " : "all " ; } void printTotals( const Totals& totals ) const { if( totals.testCases.total() == 0 ) { } else if( totals.testCases.failed == totals.testCases.total() ) { Colour colour( Colour::ResultError ); const std::string qualify_assertions_failed = totals.assertions.failed == totals.assertions.total() ? bothOrAll( totals.assertions.failed ) : std::string(); stream << "Failed " << bothOrAll( totals.testCases.failed ) << pluralise( totals.testCases.failed, "test case" ) << ", " "failed " << qualify_assertions_failed << pluralise( totals.assertions.failed, "assertion" ) << '.'; } else if( totals.assertions.total() == 0 ) { stream << "Passed " << bothOrAll( totals.testCases.total() ) << pluralise( totals.testCases.total(), "test case" ) << " (no assertions)."; } else if( totals.assertions.failed ) { Colour colour( Colour::ResultError ); stream << "Failed " << pluralise( totals.testCases.failed, "test case" ) << ", " "failed " << pluralise( totals.assertions.failed, "assertion" ) << '.'; } else { } } }; INTERNAL_CATCH_REGISTER_REPORTER( "minimal", MinimalReporter ) } // end namespace Catch int main(int argc, const char *argv[]) { auto touchFile = QString(); for (auto i = 0; i != argc; ++i) { if (argv[i] == QString("--touch") && i + 1 != argc) { touchFile = QFile::decodeName(argv[++i]); } } const char *catch_argv[] = { argv[0], touchFile.isEmpty() ? "-b" : "-r", touchFile.isEmpty() ? "-b" : "minimal" }; constexpr auto catch_argc = sizeof(catch_argv) / sizeof(catch_argv[0]); auto result = Catch::Session().run(catch_argc, catch_argv); if (result == 0 && !touchFile.isEmpty()) { QFile(touchFile).open(QIODevice::WriteOnly); } return (result < 0xff ? result : 0xff); } tdesktop-1.2.17/Telegram/SourceFiles/base/timer.cpp000066400000000000000000000050761326245125100221710ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "base/timer.h" namespace base { namespace { QObject *TimersAdjuster() { static QObject adjuster; return &adjuster; } } // namespace Timer::Timer(base::lambda callback) : QObject(nullptr) , _callback(std::move(callback)) , _type(Qt::PreciseTimer) , _adjusted(false) { setRepeat(Repeat::Interval); connect(TimersAdjuster(), &QObject::destroyed, this, [this] { adjust(); }, Qt::QueuedConnection); } void Timer::start(TimeMs timeout, Qt::TimerType type, Repeat repeat) { cancel(); _type = type; setRepeat(repeat); _adjusted = false; setTimeout(timeout); _timerId = startTimer(_timeout, _type); if (_timerId) { _next = getms(true) + _timeout; } else { _next = 0; } } void Timer::cancel() { if (isActive()) { killTimer(base::take(_timerId)); } } TimeMs Timer::remainingTime() const { if (!isActive()) { return -1; } auto now = getms(true); return (_next > now) ? (_next - now) : TimeMs(0); } void Timer::Adjust() { QObject emitter; connect(&emitter, &QObject::destroyed, TimersAdjuster(), &QObject::destroyed); } void Timer::adjust() { auto remaining = remainingTime(); if (remaining >= 0) { cancel(); _timerId = startTimer(remaining, _type); _adjusted = true; } } void Timer::setTimeout(TimeMs timeout) { Expects(timeout >= 0 && timeout <= std::numeric_limits::max()); _timeout = static_cast(timeout); } int Timer::timeout() const { return _timeout; } void Timer::timerEvent(QTimerEvent *e) { if (repeat() == Repeat::Interval) { if (_adjusted) { start(_timeout, _type, repeat()); } else { _next = getms(true) + _timeout; } } else { cancel(); } if (_callback) { _callback(); } } int DelayedCallTimer::call(TimeMs timeout, lambda_once callback, Qt::TimerType type) { Expects(timeout >= 0); if (!callback) { return 0; } auto timerId = startTimer(static_cast(timeout), type); if (timerId) { _callbacks.emplace(timerId, std::move(callback)); } return timerId; } void DelayedCallTimer::cancel(int callId) { if (callId) { killTimer(callId); _callbacks.erase(callId); } } void DelayedCallTimer::timerEvent(QTimerEvent *e) { auto timerId = e->timerId(); killTimer(timerId); auto it = _callbacks.find(timerId); if (it != _callbacks.end()) { auto callback = std::move(it->second); _callbacks.erase(it); callback(); } } } // namespace base tdesktop-1.2.17/Telegram/SourceFiles/base/timer.h000066400000000000000000000042661326245125100216360ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "base/lambda.h" #include "base/observer.h" namespace base { class Timer final : private QObject { public: Timer(base::lambda callback = base::lambda()); static Qt::TimerType DefaultType(TimeMs timeout) { constexpr auto kThreshold = TimeMs(1000); return (timeout > kThreshold) ? Qt::CoarseTimer : Qt::PreciseTimer; } void setCallback(base::lambda callback) { _callback = std::move(callback); } void callOnce(TimeMs timeout) { callOnce(timeout, DefaultType(timeout)); } void callEach(TimeMs timeout) { callEach(timeout, DefaultType(timeout)); } void callOnce(TimeMs timeout, Qt::TimerType type) { start(timeout, type, Repeat::SingleShot); } void callEach(TimeMs timeout, Qt::TimerType type) { start(timeout, type, Repeat::Interval); } bool isActive() const { return (_timerId != 0); } void cancel(); TimeMs remainingTime() const; static void Adjust(); protected: void timerEvent(QTimerEvent *e) override; private: enum class Repeat : unsigned { Interval = 0, SingleShot = 1, }; void start(TimeMs timeout, Qt::TimerType type, Repeat repeat); void adjust(); void setTimeout(TimeMs timeout); int timeout() const; void setRepeat(Repeat repeat) { _repeat = static_cast(repeat); } Repeat repeat() const { return static_cast(_repeat); } base::lambda _callback; TimeMs _next = 0; int _timeout = 0; int _timerId = 0; Qt::TimerType _type : 2; bool _adjusted : 1; unsigned _repeat : 1; }; class DelayedCallTimer final : private QObject { public: int call(TimeMs timeout, lambda_once callback) { return call(timeout, std::move(callback), Timer::DefaultType(timeout)); } int call(TimeMs timeout, lambda_once callback, Qt::TimerType type); void cancel(int callId); protected: void timerEvent(QTimerEvent *e) override; private: std::map> _callbacks; // Better to use flatmap. }; } // namespace base tdesktop-1.2.17/Telegram/SourceFiles/base/type_traits.h000066400000000000000000000066761326245125100230740ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace base { template struct custom_is_fast_copy_type : public std::false_type { }; // To make your own type a fast copy type just write: // template <> // struct base::custom_is_fast_copy_type : public std::true_type { // }; namespace internal { template struct type_list_contains; template struct type_list_contains : public std::false_type { }; template struct type_list_contains : public std::integral_constant::value || type_list_contains::value> { }; template using is_std_unsigned_int = type_list_contains; template using is_std_signed_int = type_list_contains; template using is_std_integral = std::integral_constant::value || is_std_signed_int::value || type_list_contains::value>; template using is_std_float = type_list_contains; template using is_std_arith = std::integral_constant::value || is_std_float::value>; template using is_std_fundamental = std::integral_constant::value || std::is_same::value>; template struct is_pointer : public std::false_type { }; template struct is_pointer : public std::true_type { }; template struct is_member_pointer : public std::false_type { }; template struct is_member_pointer : public std::true_type { }; template using is_fast_copy_type = std::integral_constant::value || is_pointer::value || is_member_pointer::value || custom_is_fast_copy_type::value>; template struct add_const_reference { using type = const T &; }; template <> struct add_const_reference { using type = void; }; template using add_const_reference_t = typename add_const_reference::type; template struct remove_pointer { using type = T; }; template struct remove_pointer { using type = T; }; template using remove_pointer_t = typename remove_pointer::type; } // namespace internal template struct type_traits { using is_std_unsigned_int = internal::is_std_unsigned_int; using is_std_signed_int = internal::is_std_signed_int; using is_std_integral = internal::is_std_integral; using is_std_float = internal::is_std_float; using is_std_arith = internal::is_std_arith; using is_std_fundamental = internal::is_std_fundamental; using is_pointer = internal::is_pointer; using is_member_pointer = internal::is_member_pointer; using is_fast_copy_type = internal::is_fast_copy_type; using parameter_type = std::conditional_t>; using pointed_type = internal::remove_pointer_t; }; template using parameter_type = typename type_traits::parameter_type; } // namespace base tdesktop-1.2.17/Telegram/SourceFiles/base/unique_any.h000066400000000000000000000133601326245125100226660ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include namespace base { namespace details { template struct moveable_as_copyable_wrap { moveable_as_copyable_wrap(Value &&other) : value(std::move(other)) { } moveable_as_copyable_wrap &operator=(Value &&other) { value = std::move(other); return *this; } moveable_as_copyable_wrap(moveable_as_copyable_wrap &&other) : value(std::move(other.value)) { } moveable_as_copyable_wrap( const moveable_as_copyable_wrap &other) { Unexpected("Attempt to copy-construct a move-only type."); } moveable_as_copyable_wrap &operator=( moveable_as_copyable_wrap &&other) { value = std::move(other.value); return *this; } moveable_as_copyable_wrap &operator=( const moveable_as_copyable_wrap &other) { Unexpected("Attempt to copy-assign a move-only type."); } Value value; }; template < typename Value, typename = std::enable_if_t< std::is_move_constructible_v> && !std::is_lvalue_reference_v>> auto wrap_moveable_as_copyable(Value &&value) { return moveable_as_copyable_wrap(std::move(value)); } } // namespace details class unique_any; template Value *any_cast(unique_any *value) noexcept; template const Value *any_cast(const unique_any *value) noexcept; class unique_any final { public: // Construction and destruction [any.cons] constexpr unique_any() noexcept { } unique_any(const unique_any &other) = delete; unique_any &operator=(const unique_any &other) = delete; unique_any(unique_any &&other) noexcept : _impl(std::move(other._impl)) { } unique_any &operator=(unique_any &&other) noexcept { _impl = std::move(other._impl); return *this; } template < typename Value, typename = std::enable_if_t< !std::is_same_v, unique_any>>> unique_any(Value &&other) : unique_any( std::forward(other), std::is_copy_constructible>()) { } template < typename Value, typename = std::enable_if_t< !std::is_same_v, unique_any>>> unique_any &operator=(Value &&other) { if constexpr (std::is_copy_constructible_v>) { _impl = std::forward(other); } else if constexpr (std::is_move_constructible_v> && !std::is_lvalue_reference_v) { _impl = details::wrap_moveable_as_copyable(std::move(other)); } else { static_assert( false_t(Value{}), "Bad value for base::unique_any."); } return *this; } template < typename Value, typename ...Args, typename = std::enable_if_t< std::is_constructible_v, Args...> && std::is_copy_constructible_v>>> std::decay_t &emplace(Args &&...args) { return _impl.emplace(std::forward(args)...); } void reset() noexcept { _impl.reset(); } void swap(unique_any &other) noexcept { _impl.swap(other._impl); } bool has_value() const noexcept { return _impl.has_value(); } // Should check if it is a moveable_only wrap first. //const std::type_info &type() const noexcept { // return _impl.type(); //} private: template < typename Value, typename = std::enable_if_t< !std::is_same_v, unique_any> && std::is_copy_constructible_v>>> unique_any(Value &&other, std::true_type) : _impl(std::forward(other)) { } template < typename Value, typename = std::enable_if_t< !std::is_same_v, unique_any> && !std::is_copy_constructible_v> && std::is_move_constructible_v> && !std::is_lvalue_reference_v>> unique_any(Value &&other, std::false_type) : _impl(details::wrap_moveable_as_copyable(std::move(other))) { } template < typename Value, typename ...Args> friend unique_any make_any(Args &&...args); template friend const Value *any_cast(const unique_any *value) noexcept; template friend Value *any_cast(unique_any *value) noexcept; std::any _impl; }; inline void swap(unique_any &a, unique_any &b) noexcept { a.swap(b); } template < typename Value, typename ...Args> inline auto make_any(Args &&...args) -> std::enable_if_t< std::is_copy_constructible_v>, unique_any> { return std::make_any(std::forward(args)...); } template < typename Value, typename ...Args> inline auto make_any(Args &&...args) -> std::enable_if_t< !std::is_copy_constructible_v> && std::is_move_constructible_v>, unique_any> { return Value(std::forward(args)...); } template inline Value *any_cast(unique_any *value) noexcept { if constexpr (std::is_copy_constructible_v) { return std::any_cast(&value->_impl); } else if constexpr (std::is_move_constructible_v) { auto wrap = std::any_cast< details::moveable_as_copyable_wrap >(&value->_impl); return wrap ? &wrap->value : nullptr; } else { static_assert( false_t(Value{}), "Bad type for base::any_cast."); } } template inline const Value *any_cast(const unique_any *value) noexcept { if constexpr (std::is_copy_constructible_v) { return std::any_cast(&value->_impl); } else if constexpr (std::is_move_constructible_v) { auto wrap = std::any_cast< details::moveable_as_copyable_wrap >(&value->_impl); return wrap ? &wrap->value : nullptr; } else { static_assert( false_t(Value{}), "Bad type for base::any_cast."); } } } // namespace base tdesktop-1.2.17/Telegram/SourceFiles/base/unique_function.h000066400000000000000000000104571326245125100237300ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #ifndef Unexpected #define Unexpected(message) std::abort() #define UniqueFunctionUnexpected #endif // Unexpected namespace base { namespace details { template class moveable_callable_wrap { public: static_assert( std::is_move_constructible_v, "Should be at least moveable."); moveable_callable_wrap(Callable &&other) : _value(std::move(other)) { } moveable_callable_wrap &operator=(Callable &&other) { _value = std::move(other); return *this; } moveable_callable_wrap(moveable_callable_wrap &&other) : _value(std::move(other._value)) { } moveable_callable_wrap( const moveable_callable_wrap &other) : _value(fail_construct()) { } moveable_callable_wrap &operator=( moveable_callable_wrap &&other) { _value = std::move(other._value); return *this; } moveable_callable_wrap &operator=( const moveable_callable_wrap &other) { return fail_assign(); } template decltype(auto) operator()(Args &&...args) const { return _value(std::forward(args)...); } private: [[noreturn]] Callable fail_construct() { Unexpected("Attempt to copy-construct a move-only type."); } [[noreturn]] moveable_callable_wrap &fail_assign() { Unexpected("Attempt to copy-assign a move-only type."); } mutable Callable _value; }; } // namespace details template class unique_function; template class unique_function final { public: unique_function(std::nullptr_t = nullptr) noexcept { } unique_function(const unique_function &other) = delete; unique_function &operator=(const unique_function &other) = delete; // Move construct / assign from the same type. unique_function(unique_function &&other) : _impl(std::move(other._impl)) { } unique_function &operator=(unique_function &&other) { _impl = std::move(other._impl); return *this; } template < typename Callable, typename = std::enable_if_t< std::is_convertible_v< decltype(std::declval()( std::declval()...)), Return>>> unique_function(Callable &&other) : unique_function( std::forward(other), std::is_copy_constructible>{}) { } template < typename Callable, typename = std::enable_if_t< std::is_convertible_v< decltype(std::declval()( std::declval()...)), Return>>> unique_function &operator=(Callable &&other) { using Decayed = std::decay_t; if constexpr (std::is_copy_constructible_v) { _impl = std::forward(other); } else if constexpr (std::is_move_constructible_v) { _impl = details::moveable_callable_wrap( std::forward(other)); } else { static_assert(false_t(other), "Should be moveable."); } return *this; } void swap(unique_function &other) { _impl.swap(other._impl); } template Return operator()(OtherArgs &&...args) { return _impl(std::forward(args)...); } explicit operator bool() const { return _impl.operator bool(); } friend inline bool operator==( const unique_function &value, std::nullptr_t) noexcept { return value._impl == nullptr; } friend inline bool operator==( std::nullptr_t, const unique_function &value) noexcept { return value._impl == nullptr; } friend inline bool operator!=( const unique_function &value, std::nullptr_t) noexcept { return value._impl != nullptr; } friend inline bool operator!=( std::nullptr_t, const unique_function &value) noexcept { return value._impl != nullptr; } private: template unique_function(Callable &&other, std::true_type) : _impl(std::forward(other)) { } template unique_function(Callable &&other, std::false_type) : _impl(details::moveable_callable_wrap>( std::forward(other))) { } std::function _impl; }; } // namespace base #ifdef UniqueFunctionUnexpected #undef UniqueFunctionUnexpected #undef Unexpected #endif // UniqueFunctionUnexpectedb tdesktop-1.2.17/Telegram/SourceFiles/base/unique_qptr.h000066400000000000000000000046641326245125100230740ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace base { template class unique_qptr { public: unique_qptr() = default; unique_qptr(std::nullptr_t) noexcept { } explicit unique_qptr(T *pointer) noexcept : _object(pointer) { } unique_qptr(const unique_qptr &other) = delete; unique_qptr &operator=(const unique_qptr &other) = delete; unique_qptr(unique_qptr &&other) noexcept : _object(base::take(other._object)) { } unique_qptr &operator=(unique_qptr &&other) noexcept { if (_object != other._object) { destroy(); _object = base::take(other._object); } return *this; } template < typename U, typename = std::enable_if_t>> unique_qptr(unique_qptr &&other) noexcept : _object(base::take(other._object)) { } template < typename U, typename = std::enable_if_t>> unique_qptr &operator=(unique_qptr &&other) noexcept { if (_object != other._object) { destroy(); _object = base::take(other._object); } return *this; } unique_qptr &operator=(std::nullptr_t) noexcept { destroy(); return *this; } template explicit unique_qptr(std::in_place_t, Args &&...args) : _object(new T(std::forward(args)...)) { } template T *emplace(Args &&...args) { reset(new T(std::forward(args)...)); return get(); } void reset(T *value = nullptr) noexcept { if (_object != value) { destroy(); _object = value; } } T *get() const noexcept { return static_cast(_object.data()); } operator T*() const noexcept { return get(); } T *release() noexcept { return static_cast(base::take(_object).data()); } explicit operator bool() const noexcept { return _object != nullptr; } T *operator->() const noexcept { return get(); } T &operator*() const noexcept { return *get(); } ~unique_qptr() noexcept { destroy(); } private: void destroy() noexcept { delete base::take(_object).data(); } template friend class unique_qptr; QPointer _object; }; template inline unique_qptr make_unique_q(Args &&...args) { return unique_qptr(std::in_place, std::forward(args)...); } } // namespace base tdesktop-1.2.17/Telegram/SourceFiles/base/value_ordering.h000066400000000000000000000077341326245125100235260ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace base { namespace details { template < typename Type, typename Operator, typename = decltype(Operator{}( std::declval(), std::declval()))> char test_operator(const Type &, const Operator &); int test_operator(...); template typename Operator> struct has_operator : std::bool_constant< sizeof(test_operator( std::declval(), std::declval>() )) == sizeof(char)> { }; template < typename Type, template typename Operator> constexpr bool has_operator_v = has_operator::value; template constexpr bool has_less_v = has_operator_v; template constexpr bool has_greater_v = has_operator_v; template constexpr bool has_less_equal_v = has_operator_v; template constexpr bool has_greater_equal_v = has_operator_v; template constexpr bool has_equal_to_v = has_operator_v; template constexpr bool has_not_equal_to_v = has_operator_v; } // namespace details } // namespace base template < typename ValueType, typename Helper = decltype( value_ordering_helper(std::declval()))> inline auto operator<(const ValueType &a, const ValueType &b) -> std::enable_if_t, bool> { return value_ordering_helper(a) < value_ordering_helper(b); } template < typename ValueType, typename Helper = decltype( value_ordering_helper(std::declval()))> inline auto operator>(const ValueType &a, const ValueType &b) -> std::enable_if_t< base::details::has_greater_v || base::details::has_less_v, bool > { if constexpr (base::details::has_greater_v) { return value_ordering_helper(a) > value_ordering_helper(b); } else { return value_ordering_helper(b) < value_ordering_helper(a); } } template < typename ValueType, typename Helper = decltype( value_ordering_helper(std::declval()))> inline auto operator<=(const ValueType &a, const ValueType &b) -> std::enable_if_t< base::details::has_less_equal_v || base::details::has_less_v, bool > { if constexpr (base::details::has_less_equal_v) { return value_ordering_helper(a) <= value_ordering_helper(b); } else { return !(value_ordering_helper(b) < value_ordering_helper(a)); } } template < typename ValueType, typename Helper = decltype( value_ordering_helper(std::declval()))> inline auto operator>=(const ValueType &a, const ValueType &b) -> std::enable_if_t< base::details::has_greater_equal_v || base::details::has_less_v, bool > { if constexpr (base::details::has_greater_equal_v) { return value_ordering_helper(a) >= value_ordering_helper(b); } else { return !(value_ordering_helper(a) < value_ordering_helper(b)); } } template < typename ValueType, typename Helper = decltype( value_ordering_helper(std::declval()))> inline auto operator==(const ValueType &a, const ValueType &b) -> std::enable_if_t, bool> { return value_ordering_helper(a) == value_ordering_helper(b); } template < typename ValueType, typename Helper = decltype( value_ordering_helper(std::declval()))> inline auto operator!=(const ValueType &a, const ValueType &b) -> std::enable_if_t< base::details::has_not_equal_to_v || base::details::has_equal_to_v, bool > { if constexpr (base::details::has_not_equal_to_v) { return value_ordering_helper(a) != value_ordering_helper(b); } else { return !(value_ordering_helper(a) == value_ordering_helper(b)); } } tdesktop-1.2.17/Telegram/SourceFiles/base/variant.h000066400000000000000000000025121326245125100221520ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include // We use base::variant<> alias and base::get_if() helper while we don't have std::variant<>. namespace base { template using variant = mapbox::util::variant; template inline T *get_if(variant *v) { return (v && v->template is()) ? &v->template get_unchecked() : nullptr; } template inline const T *get_if(const variant *v) { return (v && v->template is()) ? &v->template get_unchecked() : nullptr; } // Simplified visit template inline auto visit(Method &&method, const variant &value) { return value.match(std::forward(method)); } template inline auto visit(Method &&method, variant &value) { return value.match(std::forward(method)); } template inline auto visit(Method &&method, variant &&value) { return value.match(std::forward(method)); } } // namespace base tdesktop-1.2.17/Telegram/SourceFiles/base/virtual_method.h000066400000000000000000000524021326245125100235370ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace base { template class virtual_object; template class virtual_method; template class virtual_override; namespace virtual_methods { struct child_entry; using is_parent_check = bool(*)(const child_entry &possible_parent); struct child_entry { is_parent_check check_is_parent; int *table_index; }; using child_entries = std::vector; // Recursive method to find if some class is a child of some other class. template struct is_parent { static inline bool check(const child_entry &possible_parent) { // Generate a good error message if ConcreteObject is not a child of virtual_object<>. using all_objects_must_derive_virtual_object = typename ConcreteObject::virtual_object_parent; using ConcreteObjectParent = all_objects_must_derive_virtual_object; return (possible_parent.check_is_parent == &is_parent::check) || is_parent::check(possible_parent); } }; template <> struct is_parent { static inline bool check(const child_entry &possible_parent) { return (possible_parent.check_is_parent == &is_parent::check); } }; // Just force the compiler not to optimize away the object that "enforce" points at. inline void dont_optimize_away(void *enforce) { static volatile void *result = nullptr; if (result) { result = enforce; } } template struct dont_optimize_away_struct { }; inline bool first_dispatch_fired(bool did_fire = false) { static bool fired = false; if (did_fire) { fired = true; } return fired; } template class object_registrator { public: inline object_registrator() { Assert(!first_dispatch_fired()); Creator(child_entry { &is_parent::check, &_index, }); } static inline int &Index() { return _index; } private: static int _index; }; template int object_registrator::_index = -1; class object_base { protected: virtual ~object_base() = default; }; template struct multi_index_collector; template struct override_key_collector_helper; template struct table_fill_entry_helper; template struct table_count_size; } // namespace virtual_methods // This should be a base class for every child object in your hierarchy. // It registers this child in the root virtual_objects classes list. // Also it holds its own index in the classes list that is used for fast // invoking of methods from the virtual tables in different virtual_methods. template class virtual_object : public ParentObject { protected: virtual ~virtual_object() { virtual_methods::dont_optimize_away(&_virtual_object_registrator); } private: using virtual_object_parent = ParentObject; friend struct virtual_methods::is_parent; template friend struct virtual_methods::multi_index_collector; template friend struct virtual_methods::override_key_collector_helper; template friend class virtual_object; template friend class virtual_method; static inline void virtual_object_register_child(const virtual_methods::child_entry &entry) { return ParentObject::virtual_object_register_child(entry); } using virtual_object_registrator = virtual_methods::object_registrator; static virtual_object_registrator _virtual_object_registrator; using virtual_object_dont_optimize_away_registrator = virtual_methods::dont_optimize_away_struct; static inline int &virtual_object_child_index_static() { return virtual_object_registrator::Index(); } int &virtual_object_child_index() override { return virtual_object_child_index_static(); } }; template typename virtual_object::virtual_object_registrator virtual_object::_virtual_object_registrator = {}; // This should be a base class for the root of the whole hierarchy. // It holds the table of all child classes in a list. // This list is used by virtual_methods to generate virtual table. template class virtual_object : public virtual_methods::object_base { protected: virtual ~virtual_object() { virtual_methods::dont_optimize_away(&_virtual_object_registrator); } private: using virtual_object_parent = void; friend struct virtual_methods::is_parent; template friend struct virtual_methods::table_count_size; template friend struct virtual_methods::multi_index_collector; template friend struct virtual_methods::override_key_collector_helper; template friend struct virtual_methods::table_fill_entry_helper; template friend class virtual_object; template friend class virtual_method; static inline virtual_methods::child_entries &virtual_object_get_child_entries() { static virtual_methods::child_entries entries; return entries; } // Registers a new child class. // After that on the next call to virtual_method::virtual_method_prepare_table() will // generate a new virtual table for that virtual method. static inline void virtual_object_register_child(const virtual_methods::child_entry &entry) { auto &entries = virtual_object_get_child_entries(); for (auto i = entries.begin(), e = entries.end(); i != e; ++i) { if (entry.check_is_parent(*i)) { *entry.table_index = (i - entries.begin()); i = entries.insert(i, entry); for (++i, e = entries.end(); i != e; ++i) { ++*(i->table_index); } return; } } *entry.table_index = entries.size(); entries.push_back(entry); } using virtual_object_registrator = virtual_methods::object_registrator; static virtual_object_registrator _virtual_object_registrator; using virtual_object_dont_optimize_away_registrator = virtual_methods::dont_optimize_away_struct; static inline int &virtual_object_child_index_static() { return virtual_object_registrator::Index(); } virtual int &virtual_object_child_index() { return virtual_object_child_index_static(); } }; template typename virtual_object::virtual_object_registrator virtual_object::_virtual_object_registrator = {}; namespace virtual_methods { template struct is_virtual_argument : public std::integral_constant::is_pointer::value ? std::is_base_of::pointed_type>::value : false> { }; template class multi_int_wrap { public: inline multi_int_wrap(int *indices) : _indices(indices) { } inline multi_int_wrap subindex() const { static_assert(N > 0, "Wrong multi_int_wrap created!"); return multi_int_wrap(_indices + 1); } inline int ¤t() const { return *_indices; } private: int *_indices; }; template class multi_int_wrap<0, Instance> { public: inline multi_int_wrap(int *indices) { } inline int current() const { return 1; } }; template using multi_index_wrap = multi_int_wrap; template using multi_size_wrap = multi_int_wrap; template struct multi_index_collector { static constexpr int N = sizeof...(ConcreteArgs) + 1; static inline void call(multi_index_wrap indices, ConcreteArg arg, ConcreteArgs... args) { indices.current() = computeIndex(is_virtual_argument(), arg); multi_index_collector::call(indices.subindex(), args...); } static inline int computeIndex(std::integral_constant, ConcreteArg arg) { return 0; } static inline int computeIndex(std::integral_constant, ConcreteArg arg) { return arg->virtual_object_child_index(); } }; template <> struct multi_index_collector<> { static inline void call(multi_index_wrap<0> indices) { } }; template class override_key; template class multi_int { public: inline multi_int_wrap data_wrap() { return multi_int_wrap(_indices); } template static inline multi_int collect(ConcreteArgs... args) { multi_int result; multi_index_collector::call(result.data_wrap(), args...); return result; } inline void reset() { memset(_indices, 0, sizeof(_indices)); } inline int value(int index) const { return _indices[index]; } inline void copy(multi_int_wrap other) { memcpy(_indices, &other.current(), sizeof(_indices)); } private: int _indices[N] = { 0 }; friend class override_key; }; template using multi_index = multi_int; template using multi_size = multi_int; template class table_data_wrap { public: inline table_data_wrap(Call *data, multi_size_wrap size) : _data(data), _size(size) { } inline table_data_wrap operator[](int index) const { return table_data_wrap(_data + index * _size.subindex().current(), _size.subindex()); } inline Call &operator[](multi_index_wrap index) const { return (*this)[index.current()][index.subindex()]; } inline int size() const { return count_size(std::integral_constant()); } private: template inline int count_size(std::integral_constant) const { return _size.current() / _size.subindex().current(); } inline int count_size(std::integral_constant) const { return _size.current(); } Call *_data; multi_size_wrap _size; }; template class table_data_wrap { public: inline table_data_wrap(Call *data, multi_size_wrap<0> size) : _data(data) { } inline Call &operator[](multi_index_wrap<0> index) const { return *_data; } private: Call *_data; }; template class table_data_wrap; template struct table_count_size { static constexpr int N = sizeof...(Args) + 1; static inline void call(multi_size_wrap index) { auto subindex = index.subindex(); table_count_size::call(subindex); index.current() = count(is_virtual_argument()) * subindex.current(); } static inline int count(std::integral_constant) { return 1; } static inline int count(std::integral_constant) { return base::type_traits::pointed_type::virtual_object_get_child_entries().size(); } }; template <> struct table_count_size<> { static inline void call(multi_size_wrap<0> index) { } }; template class table_data { public: inline table_data_wrap data_wrap() { return table_data_wrap(_data.data(), _size.data_wrap()); } inline Call &operator[](multi_index index) { int flat_index = 0; for (int i = 0; i != N - 1; ++i) { flat_index += _size.value(i + 1) * index.value(i); } flat_index += index.value(N - 1); return _data[flat_index]; } template inline bool changed() { if (!_data.empty()) { return false; } multi_size size; table_count_size::call(size.data_wrap()); _size = size; _data.resize(_size.value(0), nullptr); return true; } private: std::vector _data; multi_size _size; }; template class table_data { public: inline table_data_wrap data_wrap() { return table_data_wrap(&_call, multi_size_wrap<0>(nullptr)); } inline Call &operator[](multi_index<0> index) { return _call; } inline bool changed() const { return false; } private: Call _call = nullptr; }; template struct table_fill_entry_helper; template struct table_fill_entry_helper { static constexpr int N = sizeof...(Args) + 1; static inline bool call(table_data_wrap table, multi_index_wrap index, Call &fill) { auto start = index.current(); for (auto i = start, count = table.size(); i != count; ++i) { auto foundGoodType = good(is_virtual_argument(), start, index.current()); if (foundGoodType) { index.current() = i; if (table_fill_entry_helper::call(table[i], index.subindex(), fill)) { return true; } } } index.current() = start; return false; } static inline bool good(std::integral_constant, int start, int current) { return (start == current); } static inline bool good(std::integral_constant, int start, int current) { using BaseObject = typename base::type_traits::pointed_type; auto &entries = BaseObject::virtual_object_get_child_entries(); return (start == current) || entries[start].check_is_parent(entries[current]); } }; template struct table_fill_entry_helper { static inline bool call(table_data_wrap table, multi_index_wrap<0> index, Call &fill) { if (auto overrideMethod = table[index]) { fill = overrideMethod; return true; } return false; } }; template struct table_fill_entry; template struct table_fill_entry { using Call = ReturnType(*)(BaseMethod*, Args...); static inline void call(table_data_wrap table, multi_index_wrap index, Call &fill) { table_fill_entry_helper::call(table, index, fill); } }; template inline void fill_entry(table_data_wrap table, multi_index_wrap index, Call &fill) { return virtual_methods::table_fill_entry::call(table, index, fill); } template struct override_key_collector_helper; template struct override_key_collector_helper { static inline void call(int **indices) { setValue(is_virtual_argument(), indices); override_key_collector_helper::call(indices); } static inline void setValue(std::integral_constant, int **indices) { indices[M] = nullptr; } static inline void setValue(std::integral_constant, int **indices) { using ConcreteObject = typename base::type_traits::pointed_type; using IsParentCheckStruct = is_parent; using IsParentCheckPointer = decltype(&IsParentCheckStruct::check); using override_key_collector_dont_optimize_away = dont_optimize_away_struct; override_key_collector_dont_optimize_away dont_optimize_away_object; (void)dont_optimize_away_object; // Check that is_parent<> can be instantiated. // So every ConcreteObject is a valid child of virtual_object<>. dont_optimize_away(reinterpret_cast(&IsParentCheckStruct::check)); indices[M] = &ConcreteObject::virtual_object_child_index_static(); } }; template struct override_key_collector_helper { static inline void call(int **indices) { } }; template struct override_key_collector; template struct override_key_collector { static inline void call(int **indices) { override_key_collector_helper<0, ConcreteArgs...>::call(indices); } }; template class override_key { public: inline multi_index value() const { multi_index result; for (int i = 0; i != N; ++i) { auto pointer = _indices[i]; result._indices[i] = (pointer ? *pointer : 0); } return result; } friend inline bool operator<(const override_key &k1, const override_key &k2) { for (int i = 0; i != N; ++i) { auto pointer1 = k1._indices[i], pointer2 = k2._indices[i]; if (pointer1 < pointer2) { return true; } else if (pointer1 > pointer2) { return false; } } return false; } template inline void collect() { override_key_collector::call(_indices); } private: int *_indices[N]; }; template struct static_cast_helper; template struct static_cast_helper { static inline ReturnType call(BaseMethod *context, Args ...args) { return ConcreteMethod::call(context, static_cast(args)...); } }; } // namespace virtual_methods // This is a base class for all your virtual methods. // It dispatches a call to one of the registered virtual_overrides // or calls the fallback method of the BaseMethod class. template class virtual_method { static constexpr int N = sizeof...(Args); using virtual_method_call = ReturnType(*)(BaseMethod *context, Args... args); public: inline ReturnType call(Args... args) { auto context = static_cast(this); auto index = virtual_methods::multi_index::collect(args...); auto &table = virtual_method_prepare_table(); auto &entry = table[index]; if (!entry) { virtual_methods::fill_entry(table.data_wrap(), index.data_wrap(), entry); if (!entry) { entry = &virtual_method::virtual_method_base_instance; } } return (*entry)(context, args...); } private: // This map of methods contains only the original registered overrides. using virtual_method_override_key = virtual_methods::override_key; using virtual_method_override_map = std::map; static inline virtual_method_override_map &virtual_method_get_override_map() { static virtual_method_override_map override_map; return override_map; } // This method generates and returns a virtual table which holds a method // for any child in the hierarchy or nullptr if none of the virtual_overrides fit. using virtual_method_table_data = virtual_methods::table_data; static inline virtual_method_table_data &virtual_method_get_table_data() { static virtual_method_table_data virtual_table; return virtual_table; } static inline virtual_method_table_data &virtual_method_prepare_table() { auto &virtual_table = virtual_method_get_table_data(); if (virtual_table.template changed()) { virtual_methods::first_dispatch_fired(true); // The class hierarchy has changed - we need to generate the virtual table once again. // All other handlers will be placed if they're called. for (auto &i : virtual_method_get_override_map()) { virtual_table[i.first.value()] = i.second; } } return virtual_table; } static ReturnType virtual_method_base_instance(BaseMethod *context, Args... args) { return BaseMethod::default_call(context, args...); } template static ReturnType virtual_method_override_instance(BaseMethod *context, Args... args) { return virtual_methods::static_cast_helper::call(context, args...); } template static inline void virtual_method_register_override() { auto call = &virtual_method_override_instance; virtual_methods::override_key key; key.template collect(); virtual_method_get_override_map()[key] = call; } template friend class virtual_override; }; template class virtual_override { protected: virtual ~virtual_override() { virtual_methods::dont_optimize_away(&_virtual_override_registrator); } private: class virtual_override_registrator { public: inline virtual_override_registrator() { Assert(!virtual_methods::first_dispatch_fired()); BaseMethod::template virtual_method_register_override(); } }; static virtual_override_registrator _virtual_override_registrator; using virtual_override_dont_optimize_away_registrator = virtual_methods::dont_optimize_away_struct; }; template typename virtual_override::virtual_override_registrator virtual_override::_virtual_override_registrator = {}; } // namespace base tdesktop-1.2.17/Telegram/SourceFiles/base/weak_ptr.h000066400000000000000000000167441326245125100223360ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include namespace base { namespace details { struct alive_tracker { std::atomic counter = 1; std::atomic dead = false; }; inline alive_tracker *check_and_increment(alive_tracker *tracker) noexcept { if (tracker) { ++tracker->counter; } return tracker; } inline void decrement(alive_tracker *tracker) noexcept { if (tracker->counter.fetch_sub(1) == 0) { delete tracker; } } } // namespace details class has_weak_ptr; template class weak_ptr; class has_weak_ptr { public: has_weak_ptr() = default; has_weak_ptr(const has_weak_ptr &other) noexcept { } has_weak_ptr(has_weak_ptr &&other) noexcept { } has_weak_ptr &operator=(const has_weak_ptr &other) noexcept { return *this; } has_weak_ptr &operator=(has_weak_ptr &&other) noexcept { return *this; } ~has_weak_ptr() { if (auto alive = _alive.load()) { alive->dead.store(true); details::decrement(alive); } } private: template friend class weak_ptr; details::alive_tracker *incrementAliveTracker() const { auto current = _alive.load(); if (!current) { auto alive = std::make_unique(); if (_alive.compare_exchange_strong(current, alive.get())) { return alive.release(); } } ++current->counter; return current; } mutable std::atomic _alive = nullptr; }; template class weak_ptr { public: weak_ptr() = default; weak_ptr(T *value) : _alive(value ? value->incrementAliveTracker() : nullptr) , _value(value) { } weak_ptr(const std::unique_ptr &value) : weak_ptr(value.get()) { } weak_ptr(const std::shared_ptr &value) : weak_ptr(value.get()) { } weak_ptr(const std::weak_ptr &value) : weak_ptr(value.lock().get()) { } weak_ptr(const weak_ptr &other) noexcept : _alive(details::check_and_increment(other._alive)) , _value(other._value) { } weak_ptr(weak_ptr &&other) noexcept : _alive(std::exchange(other._alive, nullptr)) , _value(std::exchange(other._value, nullptr)) { } template < typename Other, typename = std::enable_if_t< std::is_base_of_v && !std::is_same_v>> weak_ptr(const weak_ptr &other) noexcept : _alive(details::check_and_increment(other._alive)) , _value(other._value) { } template < typename Other, typename = std::enable_if_t< std::is_base_of_v && !std::is_same_v>> weak_ptr(weak_ptr &&other) noexcept : _alive(std::exchange(other._alive, nullptr)) , _value(std::exchange(other._value, nullptr)) { } weak_ptr &operator=(T *value) { reset(value); return *this; } weak_ptr &operator=(const std::unique_ptr &value) { reset(value.get()); return *this; } weak_ptr &operator=(const std::shared_ptr &value) { reset(value.get()); return *this; } weak_ptr &operator=(const std::weak_ptr &value) { reset(value.lock().get()); return *this; } weak_ptr &operator=(const weak_ptr &other) noexcept { if (_value != other._value) { destroy(); _alive = details::check_and_increment(other._alive); _value = other._value; } return *this; } weak_ptr &operator=(weak_ptr &&other) noexcept { if (_value != other._value) { destroy(); _alive = std::exchange(other._alive, nullptr); _value = std::exchange(other._value, nullptr); } return *this; } template < typename Other, typename = std::enable_if_t< std::is_base_of_v && !std::is_same_v>> weak_ptr &operator=(const weak_ptr &other) noexcept { if (_value != other._value) { destroy(); _alive = details::check_and_increment(other._alive); _value = other._value; } return *this; } template < typename Other, typename = std::enable_if_t< std::is_base_of_v && !std::is_same_v>> weak_ptr &operator=(weak_ptr &&other) noexcept { if (_value != other._value) { destroy(); _alive = std::exchange(other._alive, nullptr); _value = std::exchange(other._value, nullptr); } return *this; } ~weak_ptr() { destroy(); } T *get() const noexcept { return (_alive && !_alive->dead) ? _value : nullptr; } explicit operator bool() const noexcept { return (_alive && !_alive->dead); } T &operator*() const noexcept { return *get(); } T *operator->() const noexcept { return get(); } void reset(T *value = nullptr) { if (_value != value) { destroy(); _alive = value ? value->incrementAliveTracker() : nullptr; _value = value; } } private: void destroy() noexcept { if (_alive) { details::decrement(_alive); } } details::alive_tracker *_alive = nullptr; T *_value = nullptr; template friend class weak_ptr; }; template inline bool operator==(const weak_ptr &pointer, std::nullptr_t) noexcept { return (pointer.get() == nullptr); } template inline bool operator==(std::nullptr_t, const weak_ptr &pointer) noexcept { return (pointer == nullptr); } template inline bool operator!=(const weak_ptr &pointer, std::nullptr_t) noexcept { return !(pointer == nullptr); } template inline bool operator!=(std::nullptr_t, const weak_ptr &pointer) noexcept { return !(pointer == nullptr); } template < typename T, typename = std::enable_if_t>> weak_ptr make_weak(T *value) { return value; } template < typename T, typename = std::enable_if_t>> weak_ptr make_weak(const std::unique_ptr &value) { return value; } template < typename T, typename = std::enable_if_t>> weak_ptr make_weak(const std::shared_ptr &value) { return value; } template < typename T, typename = std::enable_if_t>> weak_ptr make_weak(const std::weak_ptr &value) { return value; } } // namespace base namespace crl { template struct guard_traits; template struct guard_traits, void> { static base::weak_ptr create(const base::weak_ptr &value) { return value; } static base::weak_ptr create(base::weak_ptr &&value) { return std::move(value); } static bool check(const base::weak_ptr &guard) { return guard.get() != nullptr; } }; template struct guard_traits< T*, std::enable_if_t< std::is_base_of_v>>> { static base::weak_ptr create(T *value) { return value; } static bool check(const base::weak_ptr &guard) { return guard.get() != nullptr; } }; template struct guard_traits< gsl::not_null, std::enable_if_t< std::is_base_of_v>>> { static base::weak_ptr create(gsl::not_null value) { return value.get(); } static bool check(const base::weak_ptr &guard) { return guard.get() != nullptr; } }; } // namespace crl #ifdef QT_VERSION template inline void InvokeQueued(const base::has_weak_ptr *context, Lambda &&lambda) { auto callback = [ guard = base::make_weak(context), lambda = std::forward(lambda) ] { if (guard) { lambda(); } }; QObject proxy; QObject::connect( &proxy, &QObject::destroyed, QCoreApplication::instance(), std::move(callback), Qt::QueuedConnection); } #endif // QT_VERSION tdesktop-1.2.17/Telegram/SourceFiles/base/zlib_help.h000066400000000000000000000203541326245125100224620ustar00rootroot00000000000000/* WARNING! All changes made in this file will be lost! Created from 'colors.palette' by 'codegen_style' This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "zip.h" #include "unzip.h" namespace zlib { namespace internal { class InMemoryFile { public: InMemoryFile(const QByteArray &data = QByteArray()) : _data(data) { } zlib_filefunc_def funcs() { zlib_filefunc_def result; result.opaque = this; result.zopen_file = &InMemoryFile::Open; result.zerror_file = &InMemoryFile::Error; result.zread_file = &InMemoryFile::Read; result.zwrite_file = &InMemoryFile::Write; result.zclose_file = &InMemoryFile::Close; result.zseek_file = &InMemoryFile::Seek; result.ztell_file = &InMemoryFile::Tell; return result; } int error() const { return _error; } QByteArray result() const { return _data; } private: voidpf open(const char *filename, int mode) { if (mode & ZLIB_FILEFUNC_MODE_WRITE) { if (mode & ZLIB_FILEFUNC_MODE_CREATE) { _data.clear(); } _position = _data.size(); _data.reserve(2 * 1024 * 1024); } else if (mode & ZLIB_FILEFUNC_MODE_READ) { _position = 0; } _error = 0; return this; } uLong read(voidpf stream, void* buf, uLong size) { uLong toRead = 0; if (!_error) { if (_data.size() > int(_position)) { toRead = qMin(size, uLong(_data.size() - _position)); memcpy(buf, _data.constData() + _position, toRead); _position += toRead; } if (toRead < size) { _error = -1; } } return toRead; } uLong write(voidpf stream, const void* buf, uLong size) { if (_data.size() < int(_position + size)) { _data.resize(_position + size); } memcpy(_data.data() + _position, buf, size); _position += size; return size; } int close(voidpf stream) { auto result = _error; _position = 0; _error = 0; return result; } int error(voidpf stream) const { return _error; } long tell(voidpf stream) const { return _position; } long seek(voidpf stream, uLong offset, int origin) { if (!_error) { switch (origin) { case ZLIB_FILEFUNC_SEEK_SET: _position = offset; break; case ZLIB_FILEFUNC_SEEK_CUR: _position += offset; break; case ZLIB_FILEFUNC_SEEK_END: _position = _data.size() + offset; break; } if (int(_position) > _data.size()) { _error = -1; } } return _error; } static voidpf Open(voidpf opaque, const char* filename, int mode) { return static_cast(opaque)->open(filename, mode); } static uLong Read(voidpf opaque, voidpf stream, void* buf, uLong size) { return static_cast(opaque)->read(stream, buf, size); } static uLong Write(voidpf opaque, voidpf stream, const void* buf, uLong size) { return static_cast(opaque)->write(stream, buf, size); } static int Close(voidpf opaque, voidpf stream) { return static_cast(opaque)->close(stream); } static int Error(voidpf opaque, voidpf stream) { return static_cast(opaque)->error(stream); } static long Tell(voidpf opaque, voidpf stream) { return static_cast(opaque)->tell(stream); } static long Seek(voidpf opaque, voidpf stream, uLong offset, int origin) { return static_cast(opaque)->seek(stream, offset, origin); } uLong _position = 0; int _error = 0; QByteArray _data; }; } // namespace internal constexpr int kCaseSensitive = 1; constexpr int kCaseInsensitive = 2; class FileToRead { public: FileToRead(const QByteArray &content) : _data(content) { auto funcs = _data.funcs(); if (!(_handle = unzOpen2(nullptr, &funcs))) { _error = -1; } } int getGlobalInfo(unz_global_info *pglobal_info) { if (error() == UNZ_OK) { _error = _handle ? unzGetGlobalInfo(_handle, pglobal_info) : -1; } return _error; } int locateFile(const char *szFileName, int iCaseSensitivity) { if (error() == UNZ_OK) { _error = _handle ? unzLocateFile(_handle, szFileName, iCaseSensitivity) : -1; } return error(); } int getCurrentFileInfo( unz_file_info *pfile_info, char *szFileName, uLong fileNameBufferSize, void *extraField, uLong extraFieldBufferSize, char *szComment, uLong commentBufferSize ) { if (error() == UNZ_OK) { _error = _handle ? unzGetCurrentFileInfo( _handle, pfile_info, szFileName, fileNameBufferSize, extraField, extraFieldBufferSize, szComment, commentBufferSize ) : -1; } return error(); } int openCurrentFile() { if (error() == UNZ_OK) { _error = _handle ? unzOpenCurrentFile(_handle) : -1; } return error(); } int readCurrentFile(voidp buf, unsigned len) { if (error() == UNZ_OK) { auto result = _handle ? unzReadCurrentFile(_handle, buf, len) : -1; if (result >= 0) { return result; } else { _error = result; } } return error(); } int closeCurrentFile() { if (error() == UNZ_OK) { _error = _handle ? unzCloseCurrentFile(_handle) : -1; } return error(); } QByteArray readCurrentFileContent(int fileSizeLimit) { unz_file_info fileInfo = { 0 }; if (getCurrentFileInfo(&fileInfo, nullptr, 0, nullptr, 0, nullptr, 0) != UNZ_OK) { LOG(("Error: could not get current file info in a zip file.")); return QByteArray(); } auto size = fileInfo.uncompressed_size; if (size > static_cast(fileSizeLimit)) { if (_error == UNZ_OK) _error = -1; LOG(("Error: current file is too large (should be less than %1, got %2) in a zip file.").arg(fileSizeLimit).arg(size)); return QByteArray(); } if (openCurrentFile() != UNZ_OK) { LOG(("Error: could not open current file in a zip file.")); return QByteArray(); } QByteArray result; result.resize(size); auto couldRead = readCurrentFile(result.data(), size); if (couldRead != static_cast(size)) { LOG(("Error: could not read current file in a zip file, got %1.").arg(couldRead)); return QByteArray(); } if (closeCurrentFile() != UNZ_OK) { LOG(("Error: could not close current file in a zip file.")); return QByteArray(); } return result; } QByteArray readFileContent(const char *szFileName, int iCaseSensitivity, int fileSizeLimit) { if (locateFile(szFileName, iCaseSensitivity) != UNZ_OK) { LOG(("Error: could not locate '%1' in a zip file.").arg(szFileName)); return QByteArray(); } return readCurrentFileContent(fileSizeLimit); } void close() { if (_handle && unzClose(_handle) != UNZ_OK && _error == UNZ_OK) { _error = -1; } _handle = nullptr; } int error() const { if (auto dataError = _data.error()) { return dataError; } return _error; } void clearError() { _error = UNZ_OK; } ~FileToRead() { close(); } private: internal::InMemoryFile _data; unzFile _handle = nullptr; int _error = 0; }; class FileToWrite { public: FileToWrite() { auto funcs = _data.funcs(); if (!(_handle = zipOpen2(nullptr, APPEND_STATUS_CREATE, nullptr, &funcs))) { _error = -1; } } int openNewFile( const char *filename, const zip_fileinfo *zipfi, const void *extrafield_local, uInt size_extrafield_local, const void* extrafield_global, uInt size_extrafield_global, const char* comment, int method, int level ) { if (error() == ZIP_OK) { _error = _handle ? zipOpenNewFileInZip( _handle, filename, zipfi, extrafield_local, size_extrafield_local, extrafield_global, size_extrafield_global, comment, method, level ) : -1; } return error(); } int writeInFile(const void* buf, unsigned len) { if (error() == ZIP_OK) { _error = _handle ? zipWriteInFileInZip(_handle, buf, len) : -1; } return error(); } int closeFile() { if (error() == ZIP_OK) { _error = _handle ? zipCloseFileInZip(_handle) : -1; } return error(); } void close() { if (_handle && zipClose(_handle, nullptr) != ZIP_OK && _error == ZIP_OK) { _error = -1; } _handle = nullptr; } int error() const { if (auto dataError = _data.error()) { return dataError; } return _error; } QByteArray result() const { return _data.result(); } ~FileToWrite() { close(); } private: internal::InMemoryFile _data; zipFile _handle = nullptr; int _error = 0; }; } // namespace zlib tdesktop-1.2.17/Telegram/SourceFiles/boxes/000077500000000000000000000000001326245125100205435ustar00rootroot00000000000000tdesktop-1.2.17/Telegram/SourceFiles/boxes/about_box.cpp000066400000000000000000000072771326245125100232460ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/about_box.h" #include "lang/lang_keys.h" #include "mainwidget.h" #include "mainwindow.h" #include "autoupdater.h" #include "boxes/confirm_box.h" #include "application.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" #include "styles/style_boxes.h" #include "platform/platform_file_utilities.h" #include "core/click_handler_types.h" AboutBox::AboutBox(QWidget *parent) : _version(this, lng_about_version(lt_version, QString::fromLatin1(AppVersionStr.c_str()) + (cAlphaVersion() ? " alpha" : "") + (cBetaVersion() ? qsl(" beta %1").arg(cBetaVersion()) : QString())), st::aboutVersionLink) , _text1(this, lang(lng_about_text_1), Ui::FlatLabel::InitType::Rich, st::aboutLabel) , _text2(this, lang(lng_about_text_2), Ui::FlatLabel::InitType::Rich, st::aboutLabel) , _text3(this, st::aboutLabel) { } void AboutBox::prepare() { setTitle([] { return qsl("Telegram Desktop"); }); addButton(langFactory(lng_close), [this] { closeBox(); }); const auto linkHook = [](const ClickHandlerPtr &link, auto button) { if (const auto url = dynamic_cast(link.get())) { url->UrlClickHandler::onClick(button); return false; } return true; }; _text3->setRichText(lng_about_text_3(lt_faq_open, qsl("[a href=\"%1\"]").arg(telegramFaqLink()), lt_faq_close, qsl("[/a]"))); _text1->setClickHandlerHook(linkHook); _text2->setClickHandlerHook(linkHook); _text3->setClickHandlerHook(linkHook); _version->setClickedCallback([this] { showVersionHistory(); }); setDimensions(st::aboutWidth, st::aboutTextTop + _text1->height() + st::aboutSkip + _text2->height() + st::aboutSkip + _text3->height()); } void AboutBox::resizeEvent(QResizeEvent *e) { BoxContent::resizeEvent(e); _version->moveToLeft(st::boxPadding.left(), st::aboutVersionTop); _text1->moveToLeft(st::boxPadding.left(), st::aboutTextTop); _text2->moveToLeft(st::boxPadding.left(), _text1->y() + _text1->height() + st::aboutSkip); _text3->moveToLeft(st::boxPadding.left(), _text2->y() + _text2->height() + st::aboutSkip); } void AboutBox::showVersionHistory() { if (cRealBetaVersion()) { auto url = qsl("https://tdesktop.com/"); switch (cPlatform()) { case dbipWindows: url += qsl("win/%1.zip"); break; case dbipMac: url += qsl("mac/%1.zip"); break; case dbipMacOld: url += qsl("mac32/%1.zip"); break; case dbipLinux32: url += qsl("linux32/%1.tar.xz"); break; case dbipLinux64: url += qsl("linux/%1.tar.xz"); break; } url = url.arg(qsl("tbeta%1_%2").arg(cRealBetaVersion()).arg(countBetaVersionSignature(cRealBetaVersion()))); Application::clipboard()->setText(url); Ui::show(Box("The link to the current private beta version of Telegram Desktop was copied to the clipboard.")); } else { QDesktopServices::openUrl(qsl("https://desktop.telegram.org/changelog")); } } void AboutBox::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) { closeBox(); } else { BoxContent::keyPressEvent(e); } } QString telegramFaqLink() { auto result = qsl("https://telegram.org/faq"); auto language = Lang::Current().id(); for (auto faqLanguage : { "de", "es", "it", "ko", "br" }) { if (language.startsWith(QLatin1String(faqLanguage))) { result.append('/').append(faqLanguage); } } return result; } QString currentVersionText() { auto result = QString::fromLatin1(AppVersionStr.c_str()); if (cAlphaVersion()) { result += " alpha"; } if (cBetaVersion()) { result += qsl(" beta %1").arg(cBetaVersion() % 1000); } return result; } tdesktop-1.2.17/Telegram/SourceFiles/boxes/about_box.h000066400000000000000000000014321326245125100226760ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "boxes/abstract_box.h" namespace Ui { class LinkButton; class FlatLabel; } // namespace Ui class AboutBox : public BoxContent { public: AboutBox(QWidget*); protected: void prepare() override; void resizeEvent(QResizeEvent *e) override; void keyPressEvent(QKeyEvent *e) override; private: void showVersionHistory(); object_ptr _version; object_ptr _text1; object_ptr _text2; object_ptr _text3; }; QString telegramFaqLink(); QString currentVersionText(); tdesktop-1.2.17/Telegram/SourceFiles/boxes/abstract_box.cpp000066400000000000000000000317001326245125100237230ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/abstract_box.h" #include "styles/style_boxes.h" #include "styles/style_profile.h" #include "storage/localstorage.h" #include "lang/lang_keys.h" #include "ui/widgets/buttons.h" #include "ui/widgets/scroll_area.h" #include "ui/widgets/labels.h" #include "ui/widgets/shadow.h" #include "ui/wrap/fade_wrap.h" #include "mainwidget.h" #include "mainwindow.h" QPointer BoxContent::addButton(base::lambda textFactory, base::lambda clickCallback) { return addButton(std::move(textFactory), std::move(clickCallback), st::defaultBoxButton); } QPointer BoxContent::addLeftButton(base::lambda textFactory, base::lambda clickCallback) { return getDelegate()->addLeftButton(std::move(textFactory), std::move(clickCallback), st::defaultBoxButton); } void BoxContent::setInner(object_ptr inner) { setInner(std::move(inner), st::boxLayerScroll); } void BoxContent::setInner(object_ptr inner, const style::ScrollArea &st) { if (inner) { getDelegate()->setLayerType(true); _scroll.create(this, st); _scroll->setGeometryToLeft(0, _innerTopSkip, width(), 0); _scroll->setOwnedWidget(std::move(inner)); if (_topShadow) { _topShadow->raise(); _bottomShadow->raise(); } else { _topShadow.create(this); _bottomShadow.create(this); } if (!_preparing) { // We didn't set dimensions yet, this will be called from finishPrepare(); finishScrollCreate(); } } else { getDelegate()->setLayerType(false); _scroll.destroyDelayed(); _topShadow.destroyDelayed(); _bottomShadow.destroyDelayed(); } } void BoxContent::finishPrepare() { _preparing = false; if (_scroll) { finishScrollCreate(); } setInnerFocus(); } void BoxContent::finishScrollCreate() { Expects(_scroll != nullptr); updateScrollAreaGeometry(); connect(_scroll, SIGNAL(scrolled()), this, SLOT(onScroll())); connect(_scroll, SIGNAL(innerResized()), this, SLOT(onInnerResize())); } void BoxContent::scrollToWidget(not_null widget) { if (_scroll) { _scroll->scrollToWidget(widget); } } void BoxContent::onScrollToY(int top, int bottom) { if (_scroll) { _scroll->scrollToY(top, bottom); } } void BoxContent::onDraggingScrollDelta(int delta) { _draggingScrollDelta = _scroll ? delta : 0; if (_draggingScrollDelta) { if (!_draggingScrollTimer) { _draggingScrollTimer.create(this); _draggingScrollTimer->setSingleShot(false); connect(_draggingScrollTimer, SIGNAL(timeout()), this, SLOT(onDraggingScrollTimer())); } _draggingScrollTimer->start(15); } else { _draggingScrollTimer.destroy(); } } void BoxContent::onDraggingScrollTimer() { auto delta = (_draggingScrollDelta > 0) ? qMin(_draggingScrollDelta * 3 / 20 + 1, int32(MaxScrollSpeed)) : qMax(_draggingScrollDelta * 3 / 20 - 1, -int32(MaxScrollSpeed)); _scroll->scrollToY(_scroll->scrollTop() + delta); } void BoxContent::updateInnerVisibleTopBottom() { if (auto widget = static_cast(_scroll ? _scroll->widget() : nullptr)) { auto top = _scroll->scrollTop(); widget->setVisibleTopBottom(top, top + _scroll->height()); } } void BoxContent::updateShadowsVisibility() { if (!_scroll) return; auto top = _scroll->scrollTop(); _topShadow->toggle( (top > 0 || _innerTopSkip > 0), anim::type::normal); _bottomShadow->toggle( (top < _scroll->scrollTopMax()), anim::type::normal); } void BoxContent::onScroll() { updateInnerVisibleTopBottom(); updateShadowsVisibility(); } void BoxContent::onInnerResize() { updateInnerVisibleTopBottom(); updateShadowsVisibility(); } void BoxContent::setInnerTopSkip(int innerTopSkip, bool scrollBottomFixed) { if (_innerTopSkip != innerTopSkip) { auto delta = innerTopSkip - _innerTopSkip; _innerTopSkip = innerTopSkip; if (_scroll && width() > 0) { auto scrollTopWas = _scroll->scrollTop(); updateScrollAreaGeometry(); if (scrollBottomFixed) { _scroll->scrollToY(scrollTopWas + delta); } } } } void BoxContent::setInnerVisible(bool scrollAreaVisible) { if (_scroll) { _scroll->setVisible(scrollAreaVisible); } } QPixmap BoxContent::grabInnerCache() { auto isTopShadowVisible = !_topShadow->isHidden(); auto isBottomShadowVisible = !_bottomShadow->isHidden(); if (isTopShadowVisible) _topShadow->setVisible(false); if (isBottomShadowVisible) _bottomShadow->setVisible(false); auto result = Ui::GrabWidget(this, _scroll->geometry()); if (isTopShadowVisible) _topShadow->setVisible(true); if (isBottomShadowVisible) _bottomShadow->setVisible(true); return result; } void BoxContent::resizeEvent(QResizeEvent *e) { if (_scroll) { updateScrollAreaGeometry(); } } void BoxContent::updateScrollAreaGeometry() { auto newScrollHeight = height() - _innerTopSkip; auto changed = (_scroll->height() != newScrollHeight); _scroll->setGeometryToLeft(0, _innerTopSkip, width(), newScrollHeight); _topShadow->entity()->resize(width(), st::lineWidth); _topShadow->moveToLeft(0, _innerTopSkip); _bottomShadow->entity()->resize(width(), st::lineWidth); _bottomShadow->moveToLeft(0, height() - st::lineWidth); if (changed) { updateInnerVisibleTopBottom(); auto top = _scroll->scrollTop(); _topShadow->toggle( (top > 0 || _innerTopSkip > 0), anim::type::instant); _bottomShadow->toggle( (top < _scroll->scrollTopMax()), anim::type::instant); } } object_ptr BoxContent::doTakeInnerWidget() { return _scroll->takeWidget(); } void BoxContent::paintEvent(QPaintEvent *e) { Painter p(this); if (testAttribute(Qt::WA_OpaquePaintEvent)) { for (auto rect : e->region().rects()) { p.fillRect(rect, st::boxBg); } } } AbstractBox::AbstractBox(QWidget *parent, Window::Controller *controller, object_ptr content) : LayerWidget(parent) , _controller(controller) , _content(std::move(content)) { subscribe(Lang::Current().updated(), [this] { refreshLang(); }); _content->setParent(this); _content->setDelegate(this); } void AbstractBox::setLayerType(bool layerType) { _layerType = layerType; updateTitlePosition(); } int AbstractBox::titleHeight() const { return _layerType ? st::boxLayerTitleHeight : st::boxTitleHeight; } int AbstractBox::buttonsHeight() const { auto padding = _layerType ? st::boxLayerButtonPadding : st::boxButtonPadding; return padding.top() + st::defaultBoxButton.height + padding.bottom(); } int AbstractBox::buttonsTop() const { auto padding = _layerType ? st::boxLayerButtonPadding : st::boxButtonPadding; return height() - padding.bottom() - st::defaultBoxButton.height; } void AbstractBox::paintEvent(QPaintEvent *e) { Painter p(this); auto clip = e->rect(); auto paintTopRounded = clip.intersects(QRect(0, 0, width(), st::boxRadius)); auto paintBottomRounded = clip.intersects(QRect(0, height() - st::boxRadius, width(), st::boxRadius)); if (paintTopRounded || paintBottomRounded) { auto parts = RectPart::None | 0; if (paintTopRounded) parts |= RectPart::FullTop; if (paintBottomRounded) parts |= RectPart::FullBottom; App::roundRect(p, rect(), st::boxBg, BoxCorners, nullptr, parts); } auto other = e->region().intersected(QRect(0, st::boxRadius, width(), height() - 2 * st::boxRadius)); if (!other.isEmpty()) { for (auto rect : other.rects()) { p.fillRect(rect, st::boxBg); } } if (!_additionalTitle.isEmpty() && clip.intersects(QRect(0, 0, width(), titleHeight()))) { paintAdditionalTitle(p); } } void AbstractBox::paintAdditionalTitle(Painter &p) { p.setFont(st::boxLayerTitleAdditionalFont); p.setPen(st::boxTitleAdditionalFg); p.drawTextLeft(_titleLeft + (_title ? _title->width() : 0) + st::boxLayerTitleAdditionalSkip, _titleTop + st::boxTitleFont->ascent - st::boxLayerTitleAdditionalFont->ascent, width(), _additionalTitle); } void AbstractBox::parentResized() { auto newHeight = countRealHeight(); auto parentSize = parentWidget()->size(); setGeometry((parentSize.width() - width()) / 2, (parentSize.height() - newHeight) / 2, width(), newHeight); update(); } void AbstractBox::setTitle(base::lambda titleFactory) { _titleFactory = std::move(titleFactory); refreshTitle(); } void AbstractBox::refreshTitle() { auto wasTitle = hasTitle(); if (_titleFactory) { if (!_title) { _title.create(this, st::boxTitle); } _title->setMarkedText(_titleFactory()); updateTitlePosition(); } else { _title.destroy(); } if (wasTitle != hasTitle()) { updateSize(); } } void AbstractBox::setAdditionalTitle(base::lambda additionalFactory) { _additionalTitleFactory = std::move(additionalFactory); refreshAdditionalTitle(); } void AbstractBox::refreshAdditionalTitle() { _additionalTitle = _additionalTitleFactory ? _additionalTitleFactory() : QString(); update(); } void AbstractBox::refreshLang() { refreshTitle(); refreshAdditionalTitle(); InvokeQueued(this, [this] { updateButtonsPositions(); }); } bool AbstractBox::hasTitle() const { return (_title != nullptr) || !_additionalTitle.isEmpty(); } void AbstractBox::updateSize() { setDimensions(width(), _maxContentHeight); } void AbstractBox::updateButtonsPositions() { if (!_buttons.empty() || _leftButton) { auto padding = _layerType ? st::boxLayerButtonPadding : st::boxButtonPadding; auto right = padding.right(); auto top = buttonsTop(); if (_leftButton) { _leftButton->moveToLeft(right, top); } for_const (auto &button, _buttons) { button->moveToRight(right, top); right += button->width() + padding.left(); } } } void AbstractBox::updateTitlePosition() { _titleLeft = _layerType ? st::boxLayerTitlePosition.x() : st::boxTitlePosition.x(); _titleTop = _layerType ? st::boxLayerTitlePosition.y() : st::boxTitlePosition.y(); if (_title) { _title->resizeToWidth(qMin(_title->naturalWidth(), width() - _titleLeft * 2)); _title->moveToLeft(_titleLeft, _titleTop); } } void AbstractBox::clearButtons() { for (auto &button : base::take(_buttons)) { button.destroy(); } _leftButton.destroy(); } QPointer AbstractBox::addButton(base::lambda textFactory, base::lambda clickCallback, const style::RoundButton &st) { _buttons.push_back(object_ptr(this, std::move(textFactory), st)); auto result = QPointer(_buttons.back()); result->setClickedCallback(std::move(clickCallback)); result->show(); updateButtonsPositions(); return result; } QPointer AbstractBox::addLeftButton(base::lambda textFactory, base::lambda clickCallback, const style::RoundButton &st) { _leftButton = object_ptr(this, std::move(textFactory), st); auto result = QPointer(_leftButton); result->setClickedCallback(std::move(clickCallback)); result->show(); updateButtonsPositions(); return result; } void AbstractBox::setDimensions(int newWidth, int maxHeight) { _maxContentHeight = maxHeight; auto fullHeight = countFullHeight(); if (width() != newWidth || _fullHeight != fullHeight) { _fullHeight = fullHeight; if (parentWidget()) { auto oldGeometry = geometry(); resize(newWidth, countRealHeight()); auto newGeometry = geometry(); auto parentHeight = parentWidget()->height(); if (newGeometry.top() + newGeometry.height() + st::boxVerticalMargin > parentHeight) { auto newTop = qMax(parentHeight - int(st::boxVerticalMargin) - newGeometry.height(), (parentHeight - newGeometry.height()) / 2); if (newTop != newGeometry.top()) { move(newGeometry.left(), newTop); } } parentWidget()->update(oldGeometry.united(geometry()).marginsAdded(st::boxRoundShadow.extend)); } else { resize(newWidth, 0); } } } int AbstractBox::countRealHeight() const { return qMin(_fullHeight, parentWidget()->height() - 2 * st::boxVerticalMargin); } int AbstractBox::countFullHeight() const { return contentTop() + _maxContentHeight + buttonsHeight(); } int AbstractBox::contentTop() const { return hasTitle() ? titleHeight() : (_noContentMargin ? 0 : st::boxTopMargin); } void AbstractBox::resizeEvent(QResizeEvent *e) { updateButtonsPositions(); updateTitlePosition(); auto top = contentTop(); _content->resize(width(), height() - top - buttonsHeight()); _content->moveToLeft(0, top); LayerWidget::resizeEvent(e); } void AbstractBox::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Escape) { closeBox(); } else { LayerWidget::keyPressEvent(e); } } BoxContentDivider::BoxContentDivider(QWidget *parent) : RpWidget(parent) { } int BoxContentDivider::resizeGetHeight(int newWidth) { return st::rightsDividerHeight; } void BoxContentDivider::paintEvent(QPaintEvent *e) { Painter p(this); p.fillRect(e->rect(), st::contactsAboutBg); auto dividerFillTop = myrtlrect(0, 0, width(), st::profileDividerTop.height()); st::profileDividerTop.fill(p, dividerFillTop); auto dividerFillBottom = myrtlrect(0, height() - st::profileDividerBottom.height(), width(), st::profileDividerBottom.height()); st::profileDividerBottom.fill(p, dividerFillBottom); } tdesktop-1.2.17/Telegram/SourceFiles/boxes/abstract_box.h000066400000000000000000000200231326245125100233640ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "window/layer_widget.h" #include "ui/rp_widget.h" namespace style { struct RoundButton; struct ScrollArea; } // namespace style namespace Ui { class RoundButton; class IconButton; class ScrollArea; class FlatLabel; class FadeShadow; } // namespace Ui namespace Window { class Controller; } // namespace Window class BoxContentDelegate { public: virtual Window::Controller *controller() const = 0; virtual void setLayerType(bool layerType) = 0; virtual void setTitle(base::lambda titleFactory) = 0; virtual void setAdditionalTitle(base::lambda additionalFactory) = 0; virtual void clearButtons() = 0; virtual QPointer addButton(base::lambda textFactory, base::lambda clickCallback, const style::RoundButton &st) = 0; virtual QPointer addLeftButton(base::lambda textFactory, base::lambda clickCallback, const style::RoundButton &st) = 0; virtual void updateButtonsPositions() = 0; virtual void setDimensions(int newWidth, int maxHeight) = 0; virtual void setNoContentMargin(bool noContentMargin) = 0; virtual bool isBoxShown() const = 0; virtual void closeBox() = 0; }; class BoxContent : public Ui::RpWidget, protected base::Subscriber { Q_OBJECT public: BoxContent() { setAttribute(Qt::WA_OpaquePaintEvent); } bool isBoxShown() const { return getDelegate()->isBoxShown(); } void closeBox() { getDelegate()->closeBox(); } void setTitle(base::lambda titleFactory) { if (titleFactory) { getDelegate()->setTitle([titleFactory] { return TextWithEntities { titleFactory(), EntitiesInText() }; }); } else { getDelegate()->setTitle(base::lambda()); } } void setTitle(base::lambda titleFactory) { getDelegate()->setTitle(std::move(titleFactory)); } void setAdditionalTitle(base::lambda additional) { getDelegate()->setAdditionalTitle(std::move(additional)); } void scrollToWidget(not_null widget); void clearButtons() { getDelegate()->clearButtons(); } QPointer addButton(base::lambda textFactory, base::lambda clickCallback); QPointer addLeftButton(base::lambda textFactory, base::lambda clickCallback); QPointer addButton(base::lambda textFactory, base::lambda clickCallback, const style::RoundButton &st) { return getDelegate()->addButton(std::move(textFactory), std::move(clickCallback), st); } void updateButtonsGeometry() { getDelegate()->updateButtonsPositions(); } virtual void setInnerFocus() { setFocus(); } base::Observable boxClosing; void setDelegate(BoxContentDelegate *newDelegate) { _delegate = newDelegate; _preparing = true; prepare(); finishPrepare(); } Window::Controller *controller() { return getDelegate()->controller(); } public slots: void onScrollToY(int top, int bottom = -1); void onDraggingScrollDelta(int delta); protected: virtual void prepare() = 0; void setLayerType(bool layerType) { getDelegate()->setLayerType(layerType); } void setNoContentMargin(bool noContentMargin) { if (_noContentMargin != noContentMargin) { _noContentMargin = noContentMargin; setAttribute(Qt::WA_OpaquePaintEvent, !_noContentMargin); } getDelegate()->setNoContentMargin(noContentMargin); } void setDimensions(int newWidth, int maxHeight) { getDelegate()->setDimensions(newWidth, maxHeight); } void setInnerTopSkip(int topSkip, bool scrollBottomFixed = false); template QPointer setInnerWidget(object_ptr inner, const style::ScrollArea &st, int topSkip = 0) { auto result = QPointer(inner.data()); setInnerTopSkip(topSkip); setInner(std::move(inner), st); return result; } template QPointer setInnerWidget(object_ptr inner, int topSkip = 0) { auto result = QPointer(inner.data()); setInnerTopSkip(topSkip); setInner(std::move(inner)); return result; } template object_ptr takeInnerWidget() { return static_object_cast(doTakeInnerWidget()); } void setInnerVisible(bool scrollAreaVisible); QPixmap grabInnerCache(); void resizeEvent(QResizeEvent *e) override; void paintEvent(QPaintEvent *e) override; private slots: void onScroll(); void onInnerResize(); void onDraggingScrollTimer(); private: void finishPrepare(); void finishScrollCreate(); void setInner(object_ptr inner); void setInner(object_ptr inner, const style::ScrollArea &st); void updateScrollAreaGeometry(); void updateInnerVisibleTopBottom(); void updateShadowsVisibility(); object_ptr doTakeInnerWidget(); BoxContentDelegate *getDelegate() const { Expects(_delegate != nullptr); return _delegate; } BoxContentDelegate *_delegate = nullptr; bool _preparing = false; bool _noContentMargin = false; int _innerTopSkip = 0; object_ptr _scroll = { nullptr }; object_ptr _topShadow = { nullptr }; object_ptr _bottomShadow = { nullptr }; object_ptr _draggingScrollTimer = { nullptr }; int _draggingScrollDelta = 0; }; class AbstractBox : public Window::LayerWidget , public BoxContentDelegate , protected base::Subscriber { public: AbstractBox(QWidget *parent, Window::Controller *controller, object_ptr content); Window::Controller *controller() const override { return _controller; } void parentResized() override; void setLayerType(bool layerType) override; void setTitle(base::lambda titleFactory) override; void setAdditionalTitle(base::lambda additionalFactory) override; void clearButtons() override; QPointer addButton(base::lambda textFactory, base::lambda clickCallback, const style::RoundButton &st) override; QPointer addLeftButton(base::lambda textFactory, base::lambda clickCallback, const style::RoundButton &st) override; void updateButtonsPositions() override; void setDimensions(int newWidth, int maxHeight) override; void setNoContentMargin(bool noContentMargin) override { if (_noContentMargin != noContentMargin) { _noContentMargin = noContentMargin; updateSize(); } } bool isBoxShown() const override { return !isHidden(); } void closeBox() override { closeLayer(); } protected: void keyPressEvent(QKeyEvent *e) override; void resizeEvent(QResizeEvent *e) override; void paintEvent(QPaintEvent *e) override; void doSetInnerFocus() override { _content->setInnerFocus(); } void closeHook() override { _content->boxClosing.notify(true); } private: void paintAdditionalTitle(Painter &p); void updateTitlePosition(); void refreshTitle(); void refreshAdditionalTitle(); void refreshLang(); bool hasTitle() const; int titleHeight() const; int buttonsHeight() const; int buttonsTop() const; int contentTop() const; int countFullHeight() const; int countRealHeight() const; void updateSize(); Window::Controller *_controller = nullptr; int _fullHeight = 0; bool _noContentMargin = false; int _maxContentHeight = 0; object_ptr _content; object_ptr _title = { nullptr }; base::lambda _titleFactory; QString _additionalTitle; base::lambda _additionalTitleFactory; int _titleLeft = 0; int _titleTop = 0; bool _layerType = false; std::vector> _buttons; object_ptr _leftButton = { nullptr }; }; class BoxContentDivider : public Ui::RpWidget { public: BoxContentDivider(QWidget *parent); protected: int resizeGetHeight(int newWidth) override; void paintEvent(QPaintEvent *e) override; }; enum CreatingGroupType { CreatingGroupNone, CreatingGroupGroup, CreatingGroupChannel, }; tdesktop-1.2.17/Telegram/SourceFiles/boxes/add_contact_box.cpp000066400000000000000000001476211326245125100243750ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/add_contact_box.h" #include "styles/style_boxes.h" #include "styles/style_dialogs.h" #include "lang/lang_keys.h" #include "messenger.h" #include "mtproto/sender.h" #include "base/flat_set.h" #include "boxes/confirm_box.h" #include "boxes/photo_crop_box.h" #include "boxes/peer_list_controllers.h" #include "core/file_utilities.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/buttons.h" #include "ui/widgets/input_fields.h" #include "ui/widgets/labels.h" #include "ui/toast/toast.h" #include "ui/special_buttons.h" #include "ui/text_options.h" #include "mainwidget.h" #include "mainwindow.h" #include "apiwrap.h" #include "observer_peer.h" #include "auth_session.h" namespace { constexpr auto kMaxGroupChannelTitle = 255; constexpr auto kMaxChannelDescription = 255; // See also edit_peer_info_box. constexpr auto kMaxBioLength = 70; constexpr auto kMinUsernameLength = 5; style::InputField CreateBioFieldStyle() { auto result = st::newGroupDescription; result.textMargins.setRight(st::boxTextFont->spacew + st::boxTextFont->width(QString::number(kMaxBioLength))); return result; } } // namespace QString PeerFloodErrorText(PeerFloodType type) { auto link = textcmdLink( Messenger::Instance().createInternalLinkFull(qsl("spambot")), lang(lng_cant_more_info)); if (type == PeerFloodType::InviteGroup) { return lng_cant_invite_not_contact(lt_more_info, link); } return lng_cant_send_to_not_contact(lt_more_info, link); } class RevokePublicLinkBox::Inner : public TWidget, private MTP::Sender { public: Inner(QWidget *parent, base::lambda revokeCallback); protected: void mouseMoveEvent(QMouseEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void paintEvent(QPaintEvent *e) override; private: struct ChatRow { ChatRow(not_null peer) : peer(peer) { } not_null peer; Text name, status; }; void paintChat(Painter &p, const ChatRow &row, bool selected) const; void updateSelected(); PeerData *_selected = nullptr; PeerData *_pressed = nullptr; std::vector _rows; int _rowsTop = 0; int _rowHeight = 0; int _revokeWidth = 0; base::lambda _revokeCallback; mtpRequestId _revokeRequestId = 0; QPointer _weakRevokeConfirmBox; }; AddContactBox::AddContactBox(QWidget*, QString fname, QString lname, QString phone) : _first(this, st::defaultInputField, langFactory(lng_signup_firstname), fname) , _last(this, st::defaultInputField, langFactory(lng_signup_lastname), lname) , _phone(this, st::defaultInputField, langFactory(lng_contact_phone), phone) , _invertOrder(langFirstNameGoesSecond()) { if (!phone.isEmpty()) { _phone->setDisabled(true); } } AddContactBox::AddContactBox(QWidget*, UserData *user) : _user(user) , _first(this, st::defaultInputField, langFactory(lng_signup_firstname), user->firstName) , _last(this, st::defaultInputField, langFactory(lng_signup_lastname), user->lastName) , _phone(this, st::defaultInputField, langFactory(lng_contact_phone), user->phone()) , _invertOrder(langFirstNameGoesSecond()) { _phone->setDisabled(true); } void AddContactBox::prepare() { if (_invertOrder) { setTabOrder(_last, _first); } if (_user) { setTitle(langFactory(lng_edit_contact_title)); } else { auto readyToAdd = !_phone->getLastText().isEmpty() && (!_first->getLastText().isEmpty() || !_last->getLastText().isEmpty()); setTitle(langFactory(readyToAdd ? lng_confirm_contact_data : lng_enter_contact_data)); } updateButtons(); connect(_first, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); connect(_last, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); connect(_phone, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); setDimensions(st::boxWideWidth, st::contactPadding.top() + _first->height() + st::contactSkip + _last->height() + st::contactPhoneSkip + _phone->height() + st::contactPadding.bottom() + st::boxPadding.bottom()); } void AddContactBox::setInnerFocus() { if ((_first->getLastText().isEmpty() && _last->getLastText().isEmpty()) || !_phone->isEnabled()) { (_invertOrder ? _last : _first)->setFocusFast(); _phone->finishAnimating(); } else { _phone->setFocusFast(); } } void AddContactBox::paintEvent(QPaintEvent *e) { BoxContent::paintEvent(e); Painter p(this); if (_retrying) { p.setPen(st::boxTextFg); p.setFont(st::boxTextFont); auto textHeight = height() - st::contactPadding.top() - st::contactPadding.bottom() - st::boxPadding.bottom(); p.drawText(QRect(st::boxPadding.left(), st::contactPadding.top(), width() - st::boxPadding.left() - st::boxPadding.right(), textHeight), lng_contact_not_joined(lt_name, _sentName), style::al_topleft); } else { st::contactUserIcon.paint(p, st::boxPadding.left(), _first->y() + st::contactIconTop, width()); st::contactPhoneIcon.paint(p, st::boxPadding.left(), _phone->y() + st::contactIconTop, width()); } } void AddContactBox::resizeEvent(QResizeEvent *e) { BoxContent::resizeEvent(e); _first->resize(width() - st::boxPadding.left() - st::contactPadding.left() - st::boxPadding.right(), _first->height()); _last->resize(_first->width(), _last->height()); _phone->resize(_first->width(), _last->height()); if (_invertOrder) { _last->moveToLeft(st::boxPadding.left() + st::contactPadding.left(), st::contactPadding.top()); _first->moveToLeft(st::boxPadding.left() + st::contactPadding.left(), _last->y() + _last->height() + st::contactSkip); _phone->moveToLeft(st::boxPadding.left() + st::contactPadding.left(), _first->y() + _first->height() + st::contactPhoneSkip); } else { _first->moveToLeft(st::boxPadding.left() + st::contactPadding.left(), st::contactPadding.top()); _last->moveToLeft(st::boxPadding.left() + st::contactPadding.left(), _first->y() + _first->height() + st::contactSkip); _phone->moveToLeft(st::boxPadding.left() + st::contactPadding.left(), _last->y() + _last->height() + st::contactPhoneSkip); } } void AddContactBox::onSubmit() { if (_first->hasFocus()) { _last->setFocus(); } else if (_last->hasFocus()) { if (_phone->isEnabled()) { _phone->setFocus(); } else { onSave(); } } else if (_phone->hasFocus()) { onSave(); } } void AddContactBox::onSave() { if (_addRequest) return; auto firstName = TextUtilities::PrepareForSending(_first->getLastText()); auto lastName = TextUtilities::PrepareForSending(_last->getLastText()); auto phone = _phone->getLastText().trimmed(); if (firstName.isEmpty() && lastName.isEmpty()) { if (_invertOrder) { _last->setFocus(); _last->showError(); } else { _first->setFocus(); _first->showError(); } return; } else if (!_user && !App::isValidPhone(phone)) { _phone->setFocus(); _phone->showError(); return; } if (firstName.isEmpty()) { firstName = lastName; lastName = QString(); } _sentName = firstName; if (_user) { _contactId = rand_value(); QVector v(1, MTP_inputPhoneContact(MTP_long(_contactId), MTP_string(_user->phone()), MTP_string(firstName), MTP_string(lastName))); _addRequest = MTP::send(MTPcontacts_ImportContacts(MTP_vector(v)), rpcDone(&AddContactBox::onSaveUserDone), rpcFail(&AddContactBox::onSaveUserFail)); } else { _contactId = rand_value(); QVector v(1, MTP_inputPhoneContact(MTP_long(_contactId), MTP_string(phone), MTP_string(firstName), MTP_string(lastName))); _addRequest = MTP::send(MTPcontacts_ImportContacts(MTP_vector(v)), rpcDone(&AddContactBox::onImportDone)); } } bool AddContactBox::onSaveUserFail(const RPCError &error) { if (MTP::isDefaultHandledError(error)) return false; _addRequest = 0; QString err(error.type()); QString firstName = _first->getLastText().trimmed(), lastName = _last->getLastText().trimmed(); if (err == "CHAT_TITLE_NOT_MODIFIED") { _user->setName(firstName, lastName, _user->nameOrPhone, _user->username); closeBox(); return true; } else if (err == "NO_CHAT_TITLE") { _first->setFocus(); _first->showError(); return true; } _first->setFocus(); return true; } void AddContactBox::onImportDone(const MTPcontacts_ImportedContacts &res) { if (!isBoxShown() || !App::main()) return; const auto &d = res.c_contacts_importedContacts(); App::feedUsers(d.vusers); const auto &v = d.vimported.v; const auto user = [&]() -> UserData* { if (!v.isEmpty()) { auto &c = v.front().c_importedContact(); if (c.vclient_id.v == _contactId) { return App::userLoaded(c.vuser_id.v); } } return nullptr; }(); if (user) { if (user->contactStatus() == UserData::ContactStatus::Contact) { Ui::showPeerHistory(user, ShowAtTheEndMsgId); } Ui::hideLayer(); } else { hideChildren(); _retrying = true; updateButtons(); update(); } } void AddContactBox::onSaveUserDone(const MTPcontacts_ImportedContacts &res) { auto &d = res.c_contacts_importedContacts(); App::feedUsers(d.vusers); closeBox(); } void AddContactBox::onRetry() { _addRequest = 0; _contactId = 0; showChildren(); _retrying = false; updateButtons(); _first->setText(QString()); _last->setText(QString()); _phone->clearText(); _phone->setDisabled(false); _first->setFocus(); update(); } void AddContactBox::updateButtons() { clearButtons(); if (_retrying) { addButton(langFactory(lng_try_other_contact), [this] { onRetry(); }); } else { addButton(langFactory(_user ? lng_settings_save : lng_add_contact), [this] { onSave(); }); addButton(langFactory(lng_cancel), [this] { closeBox(); }); } } GroupInfoBox::GroupInfoBox(QWidget*, CreatingGroupType creating, bool fromTypeChoose) : _creating(creating) , _fromTypeChoose(fromTypeChoose) { } void GroupInfoBox::prepare() { setMouseTracking(true); _photo.create( this, (_creating == CreatingGroupChannel) ? peerFromChannel(0) : peerFromChat(0), Ui::UserpicButton::Role::ChangePhoto, st::defaultUserpicButton); _title.create( this, st::defaultInputField, langFactory(_creating == CreatingGroupChannel ? lng_dlg_new_channel_name : lng_dlg_new_group_name)); _title->setMaxLength(kMaxGroupChannelTitle); if (_creating == CreatingGroupChannel) { _description.create(this, st::newGroupDescription, langFactory(lng_create_group_description)); _description->show(); _description->setMaxLength(kMaxChannelDescription); connect(_description, SIGNAL(resized()), this, SLOT(onDescriptionResized())); connect(_description, SIGNAL(submitted(bool)), this, SLOT(onNext())); connect(_description, SIGNAL(cancelled()), this, SLOT(onClose())); } connect(_title, SIGNAL(submitted(bool)), this, SLOT(onNameSubmit())); addButton(langFactory(_creating == CreatingGroupChannel ? lng_create_group_create : lng_create_group_next), [this] { onNext(); }); addButton(langFactory(_fromTypeChoose ? lng_create_group_back : lng_cancel), [this] { closeBox(); }); updateMaxHeight(); } void GroupInfoBox::setInnerFocus() { _title->setFocusFast(); } void GroupInfoBox::resizeEvent(QResizeEvent *e) { BoxContent::resizeEvent(e); _photo->moveToLeft(st::boxPadding.left() + st::newGroupInfoPadding.left(), st::boxPadding.top() + st::newGroupInfoPadding.top()); auto nameLeft = st::defaultUserpicButton.size.width() + st::newGroupNamePosition.x(); _title->resize(width() - st::boxPadding.left() - st::newGroupInfoPadding.left() - st::boxPadding.right() - nameLeft, _title->height()); _title->moveToLeft(st::boxPadding.left() + st::newGroupInfoPadding.left() + nameLeft, st::boxPadding.top() + st::newGroupInfoPadding.top() + st::newGroupNamePosition.y()); if (_description) { _description->resize(width() - st::boxPadding.left() - st::newGroupInfoPadding.left() - st::boxPadding.right(), _description->height()); auto descriptionLeft = st::boxPadding.left() + st::newGroupInfoPadding.left(); auto descriptionTop = st::boxPadding.top() + st::newGroupInfoPadding.top() + st::defaultUserpicButton.size.height() + st::newGroupDescriptionPadding.top(); _description->moveToLeft(descriptionLeft, descriptionTop); } } void GroupInfoBox::onNameSubmit() { if (_title->getLastText().trimmed().isEmpty()) { _title->setFocus(); _title->showError(); } else if (_description) { _description->setFocus(); } else { onNext(); } } void GroupInfoBox::createGroup(not_null selectUsersBox, const QString &title, const std::vector> &users) { if (_creationRequestId) return; auto inputs = QVector(); inputs.reserve(users.size()); for (auto peer : users) { auto user = peer->asUser(); Assert(user != nullptr); if (!user->isSelf()) { inputs.push_back(user->inputUser); } } if (inputs.empty()) { return; } _creationRequestId = request(MTPmessages_CreateChat(MTP_vector(inputs), MTP_string(title))).done([this](const MTPUpdates &result) { Ui::hideLayer(); App::main()->sentUpdatesReceived(result); auto success = base::make_optional(&result) | [](auto updates) -> base::optional*> { switch (updates->type()) { case mtpc_updates: return &updates->c_updates().vchats.v; case mtpc_updatesCombined: return &updates->c_updatesCombined().vchats.v; } LOG(("API Error: unexpected update cons %1 (GroupInfoBox::creationDone)").arg(updates->type())); return base::none; } | [](auto chats) { return (!chats->empty() && chats->front().type() == mtpc_chat) ? base::make_optional(chats) : base::none; } | [](auto chats) { return App::chat(chats->front().c_chat().vid.v); } | [this](not_null chat) { auto image = _photo->takeResultImage(); if (!image.isNull()) { Messenger::Instance().uploadProfilePhoto( std::move(image), chat->id); } Ui::showPeerHistory(chat, ShowAtUnreadMsgId); }; if (!success) { LOG(("API Error: chat not found in updates (ContactsBox::creationDone)")); } }).fail([this, selectUsersBox](const RPCError &error) { _creationRequestId = 0; if (error.type() == qstr("NO_CHAT_TITLE")) { auto weak = make_weak(this); selectUsersBox->closeBox(); if (weak) { _title->showError(); } } else if (error.type() == qstr("USERS_TOO_FEW")) { Ui::show( Box(lang(lng_cant_invite_privacy)), LayerOption::KeepOther); } else if (error.type() == qstr("PEER_FLOOD")) { Ui::show( Box( PeerFloodErrorText(PeerFloodType::InviteGroup)), LayerOption::KeepOther); } else if (error.type() == qstr("USER_RESTRICTED")) { Ui::show( Box(lang(lng_cant_do_this)), LayerOption::KeepOther); } }).send(); } void GroupInfoBox::onNext() { if (_creationRequestId) return; auto title = TextUtilities::PrepareForSending(_title->getLastText()); auto description = _description ? TextUtilities::PrepareForSending(_description->getLastText(), TextUtilities::PrepareTextOption::CheckLinks) : QString(); if (title.isEmpty()) { _title->setFocus(); _title->showError(); return; } if (_creating != CreatingGroupGroup) { createChannel(title, description); } else { auto initBox = [title, weak = make_weak(this)]( not_null box) { auto create = [box, title, weak] { if (weak) { auto rows = box->peerListCollectSelectedRows(); if (!rows.empty()) { weak->createGroup(box, title, rows); } } }; box->addButton( langFactory(lng_create_group_create), std::move(create)); box->addButton( langFactory(lng_cancel), [box] { box->closeBox(); }); }; Ui::show( Box( std::make_unique( nullptr), std::move(initBox)), LayerOption::KeepOther); } } void GroupInfoBox::createChannel(const QString &title, const QString &description) { bool mega = false; auto flags = mega ? MTPchannels_CreateChannel::Flag::f_megagroup : MTPchannels_CreateChannel::Flag::f_broadcast; _creationRequestId = request(MTPchannels_CreateChannel(MTP_flags(flags), MTP_string(title), MTP_string(description))).done([this](const MTPUpdates &result) { App::main()->sentUpdatesReceived(result); auto success = base::make_optional(&result) | [](auto updates) -> base::optional*> { switch (updates->type()) { case mtpc_updates: return &updates->c_updates().vchats.v; case mtpc_updatesCombined: return &updates->c_updatesCombined().vchats.v; } LOG(("API Error: unexpected update cons %1 (GroupInfoBox::createChannel)").arg(updates->type())); return base::none; } | [](auto chats) { return (!chats->empty() && chats->front().type() == mtpc_channel) ? base::make_optional(chats) : base::none; } | [](auto chats) { return App::channel(chats->front().c_channel().vid.v); } | [this](not_null channel) { auto image = _photo->takeResultImage(); if (!image.isNull()) { Messenger::Instance().uploadProfilePhoto( std::move(image), channel->id); } _createdChannel = channel; _creationRequestId = request( MTPchannels_ExportInvite(_createdChannel->inputChannel) ).done([this](const MTPExportedChatInvite &result) { _creationRequestId = 0; if (result.type() == mtpc_chatInviteExported) { auto link = qs(result.c_chatInviteExported().vlink); _createdChannel->setInviteLink(link); } Ui::show(Box(_createdChannel)); }).send(); }; if (!success) { LOG(("API Error: channel not found in updates (GroupInfoBox::creationDone)")); closeBox(); } }).fail([this](const RPCError &error) { _creationRequestId = 0; if (error.type() == "NO_CHAT_TITLE") { _title->setFocus(); _title->showError(); } else if (error.type() == qstr("USER_RESTRICTED")) { Ui::show(Box(lang(lng_cant_do_this))); } else if (error.type() == qstr("CHANNELS_TOO_MUCH")) { Ui::show(Box(lang(lng_cant_do_this))); // TODO } }).send(); } void GroupInfoBox::onDescriptionResized() { updateMaxHeight(); update(); } void GroupInfoBox::updateMaxHeight() { auto newHeight = st::boxPadding.top() + st::newGroupInfoPadding.top() + st::defaultUserpicButton.size.height() + st::boxPadding.bottom() + st::newGroupInfoPadding.bottom(); if (_description) { newHeight += st::newGroupDescriptionPadding.top() + _description->height() + st::newGroupDescriptionPadding.bottom(); } setDimensions(st::boxWideWidth, newHeight); } SetupChannelBox::SetupChannelBox(QWidget*, ChannelData *channel, bool existing) : _channel(channel) , _existing(existing) , _privacyGroup(std::make_shared>(Privacy::Public)) , _public(this, _privacyGroup, Privacy::Public, lang(channel->isMegagroup() ? lng_create_public_group_title : lng_create_public_channel_title), st::defaultBoxCheckbox) , _private(this, _privacyGroup, Privacy::Private, lang(channel->isMegagroup() ? lng_create_private_group_title : lng_create_private_channel_title), st::defaultBoxCheckbox) , _aboutPublicWidth(st::boxWideWidth - st::boxPadding.left() - st::boxButtonPadding.right() - st::newGroupPadding.left() - st::defaultRadio.diameter - st::defaultBoxCheckbox.textPosition.x()) , _aboutPublic(st::defaultTextStyle, lang(channel->isMegagroup() ? lng_create_public_group_about : lng_create_public_channel_about), _defaultOptions, _aboutPublicWidth) , _aboutPrivate(st::defaultTextStyle, lang(channel->isMegagroup() ? lng_create_private_group_about : lng_create_private_channel_about), _defaultOptions, _aboutPublicWidth) , _link(this, st::setupChannelLink, base::lambda(), channel->username, true) { } void SetupChannelBox::prepare() { _aboutPublicHeight = _aboutPublic.countHeight(_aboutPublicWidth); setMouseTracking(true); _checkRequestId = MTP::send(MTPchannels_CheckUsername(_channel->inputChannel, MTP_string("preston")), RPCDoneHandlerPtr(), rpcFail(&SetupChannelBox::onFirstCheckFail)); addButton(langFactory(lng_settings_save), [this] { onSave(); }); addButton(langFactory(_existing ? lng_cancel : lng_create_group_skip), [this] { closeBox(); }); connect(_link, SIGNAL(changed()), this, SLOT(onChange())); _link->setVisible(_privacyGroup->value() == Privacy::Public); _checkTimer.setSingleShot(true); connect(&_checkTimer, SIGNAL(timeout()), this, SLOT(onCheck())); _privacyGroup->setChangedCallback([this](Privacy value) { privacyChanged(value); }); subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(Notify::PeerUpdate::Flag::InviteLinkChanged, [this](const Notify::PeerUpdate &update) { if (update.peer == _channel) { rtlupdate(_invitationLink); } })); subscribe(boxClosing, [this] { if (!_existing) { AddParticipantsBoxController::Start(_channel); } }); updateMaxHeight(); } void SetupChannelBox::setInnerFocus() { if (_link->isHidden()) { setFocus(); } else { _link->setFocusFast(); } } void SetupChannelBox::updateMaxHeight() { auto newHeight = st::boxPadding.top() + st::newGroupPadding.top() + _public->heightNoMargins() + _aboutPublicHeight + st::newGroupSkip + _private->heightNoMargins() + _aboutPrivate.countHeight(_aboutPublicWidth) + st::newGroupSkip + st::newGroupPadding.bottom(); if (!_channel->isMegagroup() || _privacyGroup->value() == Privacy::Public) { newHeight += st::newGroupLinkPadding.top() + _link->height() + st::newGroupLinkPadding.bottom(); } setDimensions(st::boxWideWidth, newHeight); } void SetupChannelBox::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) { if (_link->hasFocus()) { if (_link->text().trimmed().isEmpty()) { _link->setFocus(); _link->showError(); } else { onSave(); } } } else { BoxContent::keyPressEvent(e); } } void SetupChannelBox::paintEvent(QPaintEvent *e) { Painter p(this); p.fillRect(e->rect(), st::boxBg); p.setPen(st::newGroupAboutFg); QRect aboutPublic(st::boxPadding.left() + st::newGroupPadding.left() + st::defaultRadio.diameter + st::defaultBoxCheckbox.textPosition.x(), _public->bottomNoMargins(), _aboutPublicWidth, _aboutPublicHeight); _aboutPublic.drawLeft(p, aboutPublic.x(), aboutPublic.y(), aboutPublic.width(), width()); QRect aboutPrivate(st::boxPadding.left() + st::newGroupPadding.left() + st::defaultRadio.diameter + st::defaultBoxCheckbox.textPosition.x(), _private->bottomNoMargins(), _aboutPublicWidth, _aboutPublicHeight); _aboutPrivate.drawLeft(p, aboutPrivate.x(), aboutPrivate.y(), aboutPrivate.width(), width()); if (!_channel->isMegagroup() || !_link->isHidden()) { p.setPen(st::boxTextFg); p.setFont(st::newGroupLinkFont); p.drawTextLeft(st::boxPadding.left() + st::newGroupPadding.left() + st::defaultInputField.textMargins.left(), _link->y() - st::newGroupLinkPadding.top() + st::newGroupLinkTop, width(), lang(_link->isHidden() ? lng_create_group_invite_link : lng_create_group_link)); } if (_link->isHidden()) { if (!_channel->isMegagroup()) { QTextOption option(style::al_left); option.setWrapMode(QTextOption::WrapAnywhere); p.setFont(_linkOver ? st::boxTextFont->underline() : st::boxTextFont); p.setPen(st::defaultLinkButton.color); auto inviteLinkText = _channel->inviteLink().isEmpty() ? lang(lng_group_invite_create) : _channel->inviteLink(); p.drawText(_invitationLink, inviteLinkText, option); } } else { if (!_errorText.isEmpty()) { p.setPen(st::boxTextFgError); p.setFont(st::boxTextFont); p.drawTextRight(st::boxPadding.right(), _link->y() - st::newGroupLinkPadding.top() + st::newGroupLinkTop + st::newGroupLinkFont->ascent - st::boxTextFont->ascent, width(), _errorText); } else if (!_goodText.isEmpty()) { p.setPen(st::boxTextFgGood); p.setFont(st::boxTextFont); p.drawTextRight(st::boxPadding.right(), _link->y() - st::newGroupLinkPadding.top() + st::newGroupLinkTop + st::newGroupLinkFont->ascent - st::boxTextFont->ascent, width(), _goodText); } } } void SetupChannelBox::resizeEvent(QResizeEvent *e) { BoxContent::resizeEvent(e); _public->moveToLeft(st::boxPadding.left() + st::newGroupPadding.left(), st::boxPadding.top() + st::newGroupPadding.top()); _private->moveToLeft(st::boxPadding.left() + st::newGroupPadding.left(), _public->bottomNoMargins() + _aboutPublicHeight + st::newGroupSkip); _link->resize(width() - st::boxPadding.left() - st::newGroupLinkPadding.left() - st::boxPadding.right(), _link->height()); _link->moveToLeft(st::boxPadding.left() + st::newGroupLinkPadding.left(), _private->bottomNoMargins() + _aboutPrivate.countHeight(_aboutPublicWidth) + st::newGroupSkip + st::newGroupPadding.bottom() + st::newGroupLinkPadding.top()); _invitationLink = QRect(_link->x(), _link->y() + (_link->height() / 2) - st::boxTextFont->height, _link->width(), 2 * st::boxTextFont->height); } void SetupChannelBox::mouseMoveEvent(QMouseEvent *e) { updateSelected(e->globalPos()); } void SetupChannelBox::mousePressEvent(QMouseEvent *e) { if (_linkOver) { if (_channel->inviteLink().isEmpty()) { Auth().api().exportInviteLink(_channel); } else { QGuiApplication::clipboard()->setText(_channel->inviteLink()); Ui::Toast::Show(lang(lng_create_channel_link_copied)); } } } void SetupChannelBox::leaveEventHook(QEvent *e) { updateSelected(QCursor::pos()); } void SetupChannelBox::updateSelected(const QPoint &cursorGlobalPosition) { QPoint p(mapFromGlobal(cursorGlobalPosition)); bool linkOver = _invitationLink.contains(p); if (linkOver != _linkOver) { _linkOver = linkOver; update(); setCursor(_linkOver ? style::cur_pointer : style::cur_default); } } void SetupChannelBox::onSave() { if (_privacyGroup->value() == Privacy::Private) { if (_existing) { _sentUsername = QString(); _saveRequestId = MTP::send(MTPchannels_UpdateUsername(_channel->inputChannel, MTP_string(_sentUsername)), rpcDone(&SetupChannelBox::onUpdateDone), rpcFail(&SetupChannelBox::onUpdateFail)); } else { closeBox(); } } if (_saveRequestId) return; QString link = _link->text().trimmed(); if (link.isEmpty()) { _link->setFocus(); _link->showError(); return; } _sentUsername = link; _saveRequestId = MTP::send(MTPchannels_UpdateUsername(_channel->inputChannel, MTP_string(_sentUsername)), rpcDone(&SetupChannelBox::onUpdateDone), rpcFail(&SetupChannelBox::onUpdateFail)); } void SetupChannelBox::onChange() { QString name = _link->text().trimmed(); if (name.isEmpty()) { if (!_errorText.isEmpty() || !_goodText.isEmpty()) { _errorText = _goodText = QString(); update(); } _checkTimer.stop(); } else { int32 len = name.size(); for (int32 i = 0; i < len; ++i) { QChar ch = name.at(i); if ((ch < 'A' || ch > 'Z') && (ch < 'a' || ch > 'z') && (ch < '0' || ch > '9') && ch != '_') { if (_errorText != lang(lng_create_channel_link_bad_symbols)) { _errorText = lang(lng_create_channel_link_bad_symbols); update(); } _checkTimer.stop(); return; } } if (name.size() < kMinUsernameLength) { if (_errorText != lang(lng_create_channel_link_too_short)) { _errorText = lang(lng_create_channel_link_too_short); update(); } _checkTimer.stop(); } else { if (!_errorText.isEmpty() || !_goodText.isEmpty()) { _errorText = _goodText = QString(); update(); } _checkTimer.start(UsernameCheckTimeout); } } } void SetupChannelBox::onCheck() { if (_checkRequestId) { MTP::cancel(_checkRequestId); } QString link = _link->text().trimmed(); if (link.size() >= kMinUsernameLength) { _checkUsername = link; _checkRequestId = MTP::send( MTPchannels_CheckUsername( _channel->inputChannel, MTP_string(link)), rpcDone(&SetupChannelBox::onCheckDone), rpcFail(&SetupChannelBox::onCheckFail)); } } void SetupChannelBox::privacyChanged(Privacy value) { if (value == Privacy::Public) { if (_tooMuchUsernames) { _privacyGroup->setValue(Privacy::Private); Ui::show(Box(base::lambda_guarded(this, [this] { _tooMuchUsernames = false; _privacyGroup->setValue(Privacy::Public); onCheck(); })), LayerOption::KeepOther); return; } _link->show(); _link->setDisplayFocused(true); _link->setFocus(); } else { _link->hide(); setFocus(); } if (_channel->isMegagroup()) { updateMaxHeight(); } update(); } void SetupChannelBox::onUpdateDone(const MTPBool &result) { _channel->setName(TextUtilities::SingleLine(_channel->name), _sentUsername); closeBox(); } bool SetupChannelBox::onUpdateFail(const RPCError &error) { if (MTP::isDefaultHandledError(error)) return false; _saveRequestId = 0; QString err(error.type()); if (err == "USERNAME_NOT_MODIFIED" || _sentUsername == _channel->username) { _channel->setName(TextUtilities::SingleLine(_channel->name), TextUtilities::SingleLine(_sentUsername)); closeBox(); return true; } else if (err == "USERNAME_INVALID") { _link->setFocus(); _link->showError(); _errorText = lang(lng_create_channel_link_invalid); update(); return true; } else if (err == "USERNAME_OCCUPIED" || err == "USERNAMES_UNAVAILABLE") { _link->setFocus(); _link->showError(); _errorText = lang(lng_create_channel_link_occupied); update(); return true; } _link->setFocus(); return true; } void SetupChannelBox::onCheckDone(const MTPBool &result) { _checkRequestId = 0; QString newError = (mtpIsTrue(result) || _checkUsername == _channel->username) ? QString() : lang(lng_create_channel_link_occupied); QString newGood = newError.isEmpty() ? lang(lng_create_channel_link_available) : QString(); if (_errorText != newError || _goodText != newGood) { _errorText = newError; _goodText = newGood; update(); } } bool SetupChannelBox::onCheckFail(const RPCError &error) { if (MTP::isDefaultHandledError(error)) return false; _checkRequestId = 0; QString err(error.type()); if (err == qstr("CHANNEL_PUBLIC_GROUP_NA")) { Ui::hideLayer(); return true; } else if (err == qstr("CHANNELS_ADMIN_PUBLIC_TOO_MUCH")) { if (_existing) { showRevokePublicLinkBoxForEdit(); } else { _tooMuchUsernames = true; _privacyGroup->setValue(Privacy::Private); } return true; } else if (err == qstr("USERNAME_INVALID")) { _errorText = lang(lng_create_channel_link_invalid); update(); return true; } else if (err == qstr("USERNAME_OCCUPIED") && _checkUsername != _channel->username) { _errorText = lang(lng_create_channel_link_occupied); update(); return true; } _goodText = QString(); _link->setFocus(); return true; } void SetupChannelBox::showRevokePublicLinkBoxForEdit() { closeBox(); Ui::show(Box([channel = _channel, existing = _existing]() { Ui::show( Box(channel, existing), LayerOption::KeepOther); }), LayerOption::KeepOther); } bool SetupChannelBox::onFirstCheckFail(const RPCError &error) { if (MTP::isDefaultHandledError(error)) return false; _checkRequestId = 0; QString err(error.type()); if (err == qstr("CHANNEL_PUBLIC_GROUP_NA")) { Ui::hideLayer(); return true; } else if (err == qstr("CHANNELS_ADMIN_PUBLIC_TOO_MUCH")) { if (_existing) { showRevokePublicLinkBoxForEdit(); } else { _tooMuchUsernames = true; _privacyGroup->setValue(Privacy::Private); } return true; } _goodText = QString(); _link->setFocus(); return true; } EditNameBox::EditNameBox(QWidget*, not_null user) : _user(user) , _first(this, st::defaultInputField, langFactory(lng_signup_firstname), _user->firstName) , _last(this, st::defaultInputField, langFactory(lng_signup_lastname), _user->lastName) , _invertOrder(langFirstNameGoesSecond()) { } void EditNameBox::prepare() { auto newHeight = st::contactPadding.top() + _first->height(); setTitle(langFactory(lng_edit_self_title)); newHeight += st::contactSkip + _last->height(); newHeight += st::boxPadding.bottom() + st::contactPadding.bottom(); setDimensions(st::boxWideWidth, newHeight); addButton(langFactory(lng_settings_save), [this] { save(); }); addButton(langFactory(lng_cancel), [this] { closeBox(); }); if (_invertOrder) { setTabOrder(_last, _first); } _first->setMaxLength(kMaxGroupChannelTitle); _last->setMaxLength(kMaxGroupChannelTitle); connect(_first, &Ui::InputField::submitted, this, [this] { submit(); }); connect(_last, &Ui::InputField::submitted, this, [this] { submit(); }); } void EditNameBox::setInnerFocus() { (_invertOrder ? _last : _first)->setFocusFast(); } void EditNameBox::submit() { if (_first->hasFocus()) { _last->setFocus(); } else if (_last->hasFocus()) { if (_first->getLastText().trimmed().isEmpty()) { _first->setFocus(); _first->showError(); } else if (_last->getLastText().trimmed().isEmpty()) { _last->setFocus(); _last->showError(); } else { save(); } } } void EditNameBox::resizeEvent(QResizeEvent *e) { BoxContent::resizeEvent(e); _first->resize(width() - st::boxPadding.left() - st::newGroupInfoPadding.left() - st::boxPadding.right(), _first->height()); _last->resize(_first->size()); if (_invertOrder) { _last->moveToLeft(st::boxPadding.left() + st::newGroupInfoPadding.left(), st::contactPadding.top()); _first->moveToLeft(st::boxPadding.left() + st::newGroupInfoPadding.left(), _last->y() + _last->height() + st::contactSkip); } else { _first->moveToLeft(st::boxPadding.left() + st::newGroupInfoPadding.left(), st::contactPadding.top()); _last->moveToLeft(st::boxPadding.left() + st::newGroupInfoPadding.left(), _first->y() + _first->height() + st::contactSkip); } } void EditNameBox::save() { if (_requestId) return; auto first = TextUtilities::PrepareForSending(_first->getLastText()); auto last = TextUtilities::PrepareForSending(_last->getLastText()); if (first.isEmpty() && last.isEmpty()) { if (_invertOrder) { _last->setFocus(); _last->showError(); } else { _first->setFocus(); _first->showError(); } return; } if (first.isEmpty()) { first = last; last = QString(); } _sentName = first; auto flags = MTPaccount_UpdateProfile::Flag::f_first_name | MTPaccount_UpdateProfile::Flag::f_last_name; _requestId = MTP::send( MTPaccount_UpdateProfile( MTP_flags(flags), MTP_string(first), MTP_string(last), MTPstring()), rpcDone(&EditNameBox::saveSelfDone), rpcFail(&EditNameBox::saveSelfFail)); } void EditNameBox::saveSelfDone(const MTPUser &user) { App::feedUsers(MTP_vector(1, user)); closeBox(); } bool EditNameBox::saveSelfFail(const RPCError &error) { if (MTP::isDefaultHandledError(error)) return false; auto err = error.type(); auto first = TextUtilities::SingleLine(_first->getLastText().trimmed()); auto last = TextUtilities::SingleLine(_last->getLastText().trimmed()); if (err == "NAME_NOT_MODIFIED") { _user->setName(first, last, QString(), TextUtilities::SingleLine(_user->username)); closeBox(); return true; } else if (err == "FIRSTNAME_INVALID") { _first->setFocus(); _first->showError(); return true; } else if (err == "LASTNAME_INVALID") { _last->setFocus(); _last->showError(); return true; } _first->setFocus(); return true; } EditBioBox::EditBioBox(QWidget*, not_null self) : BoxContent() , _dynamicFieldStyle(CreateBioFieldStyle()) , _self(self) , _bio(this, _dynamicFieldStyle, langFactory(lng_bio_placeholder), _self->about()) , _countdown(this, QString(), Ui::FlatLabel::InitType::Simple, st::editBioCountdownLabel) , _about(this, lang(lng_bio_about), Ui::FlatLabel::InitType::Simple, st::aboutRevokePublicLabel) { } void EditBioBox::prepare() { setTitle(langFactory(lng_bio_title)); addButton(langFactory(lng_settings_save), [this] { save(); }); addButton(langFactory(lng_cancel), [this] { closeBox(); }); _bio->setMaxLength(kMaxBioLength); _bio->setCtrlEnterSubmit(Ui::CtrlEnterSubmit::Both); auto cursor = _bio->textCursor(); cursor.setPosition(_bio->getLastText().size()); _bio->setTextCursor(cursor); connect(_bio, &Ui::InputArea::submitted, this, [this](bool ctrlShiftEnter) { save(); }); connect(_bio, &Ui::InputArea::resized, this, [this] { updateMaxHeight(); }); connect(_bio, &Ui::InputArea::changed, this, [this] { handleBioUpdated(); }); handleBioUpdated(); updateMaxHeight(); } void EditBioBox::updateMaxHeight() { auto newHeight = st::contactPadding.top() + _bio->height() + st::boxLittleSkip + _about->height() + st::boxPadding.bottom() + st::contactPadding.bottom(); setDimensions(st::boxWideWidth, newHeight); } void EditBioBox::handleBioUpdated() { auto text = _bio->getLastText(); if (text.indexOf('\n') >= 0) { auto position = _bio->textCursor().position(); _bio->setText(text.replace('\n', ' ')); auto cursor = _bio->textCursor(); cursor.setPosition(position); _bio->setTextCursor(cursor); } auto countLeft = qMax(kMaxBioLength - text.size(), 0); _countdown->setText(QString::number(countLeft)); } void EditBioBox::setInnerFocus() { _bio->setFocusFast(); } void EditBioBox::resizeEvent(QResizeEvent *e) { BoxContent::resizeEvent(e); _bio->resize(width() - st::boxPadding.left() - st::newGroupInfoPadding.left() - st::boxPadding.right(), _bio->height()); _bio->moveToLeft(st::boxPadding.left() + st::newGroupInfoPadding.left(), st::contactPadding.top()); _countdown->moveToRight(st::boxPadding.right(), _bio->y() + _dynamicFieldStyle.textMargins.top()); _about->moveToLeft(st::boxPadding.left(), _bio->y() + _bio->height() + st::boxLittleSkip); } void EditBioBox::save() { if (_requestId) return; auto text = TextUtilities::PrepareForSending(_bio->getLastText()); _sentBio = text; auto flags = MTPaccount_UpdateProfile::Flag::f_about; _requestId = request(MTPaccount_UpdateProfile(MTP_flags(flags), MTPstring(), MTPstring(), MTP_string(text))).done([this](const MTPUser &result) { App::feedUsers(MTP_vector(1, result)); _self->setAbout(_sentBio); closeBox(); }).send(); } EditChannelBox::EditChannelBox(QWidget*, not_null channel) : _channel(channel) , _title(this, st::defaultInputField, langFactory(_channel->isMegagroup() ? lng_dlg_new_group_name : lng_dlg_new_channel_name), _channel->name) , _description(this, st::newGroupDescription, langFactory(lng_create_group_description), _channel->about()) , _sign(this, lang(lng_edit_sign_messages), channel->addsSignature(), st::defaultBoxCheckbox) , _inviteGroup(std::make_shared>(channel->anyoneCanAddMembers() ? Invites::Everybody : Invites::OnlyAdmins)) , _inviteEverybody(this, _inviteGroup, Invites::Everybody, lang(lng_edit_group_invites_everybody)) , _inviteOnlyAdmins(this, _inviteGroup, Invites::OnlyAdmins, lang(lng_edit_group_invites_only_admins)) , _publicLink(this, lang(channel->isPublic() ? lng_profile_edit_public_link : lng_profile_create_public_link), st::boxLinkButton) { } void EditChannelBox::prepare() { setTitle(langFactory(_channel->isMegagroup() ? lng_edit_group : lng_edit_channel_title)); addButton(langFactory(lng_settings_save), [this] { onSave(); }); addButton(langFactory(lng_cancel), [this] { closeBox(); }); subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(Notify::PeerUpdate::Flag::NameChanged, [this](const Notify::PeerUpdate &update) { if (update.peer == _channel) { handleChannelNameChange(); } })); setMouseTracking(true); _title->setMaxLength(kMaxGroupChannelTitle); _description->setMaxLength(kMaxChannelDescription); connect(_description, SIGNAL(resized()), this, SLOT(onDescriptionResized())); connect(_description, SIGNAL(submitted(bool)), this, SLOT(onSave())); connect(_description, SIGNAL(cancelled()), this, SLOT(onClose())); connect(_publicLink, SIGNAL(clicked()), this, SLOT(onPublicLink())); _publicLink->setVisible(_channel->canEditUsername()); _sign->setVisible(canEditSignatures()); _inviteEverybody->setVisible(canEditInvites()); _inviteOnlyAdmins->setVisible(canEditInvites()); updateMaxHeight(); } void EditChannelBox::setInnerFocus() { _title->setFocusFast(); } void EditChannelBox::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) { if (_title->hasFocus()) { onSave(); } } else { BoxContent::keyPressEvent(e); } } void EditChannelBox::handleChannelNameChange() { _publicLink->setText(lang(_channel->isPublic() ? lng_profile_edit_public_link : lng_profile_create_public_link)); _sign->setChecked(_channel->addsSignature()); } void EditChannelBox::onDescriptionResized() { updateMaxHeight(); update(); } bool EditChannelBox::canEditSignatures() const { return _channel->canEditInformation() && !_channel->isMegagroup(); } bool EditChannelBox::canEditInvites() const { return _channel->canEditInformation() && _channel->isMegagroup(); } void EditChannelBox::updateMaxHeight() { auto newHeight = st::newGroupInfoPadding.top() + _title->height(); newHeight += st::newGroupDescriptionPadding.top() + _description->height() + st::newGroupDescriptionPadding.bottom(); if (canEditSignatures()) { newHeight += st::newGroupPublicLinkPadding.top() + _sign->heightNoMargins() + st::newGroupPublicLinkPadding.bottom(); } if (canEditInvites()) { newHeight += st::boxTitleHeight + _inviteEverybody->heightNoMargins(); newHeight += st::boxLittleSkip + _inviteOnlyAdmins->heightNoMargins(); } if (_channel->canEditUsername()) { newHeight += st::newGroupPublicLinkPadding.top() + _publicLink->height() + st::newGroupPublicLinkPadding.bottom(); } newHeight += st::boxPadding.bottom() + st::newGroupInfoPadding.bottom(); setDimensions(st::boxWideWidth, newHeight); } void EditChannelBox::resizeEvent(QResizeEvent *e) { BoxContent::resizeEvent(e); _title->resize(width() - st::boxPadding.left() - st::newGroupInfoPadding.left() - st::boxPadding.right(), _title->height()); _title->moveToLeft(st::boxPadding.left() + st::newGroupInfoPadding.left(), st::newGroupInfoPadding.top() + st::newGroupNamePosition.y()); _description->resize(width() - st::boxPadding.left() - st::newGroupInfoPadding.left() - st::boxPadding.right(), _description->height()); _description->moveToLeft(st::boxPadding.left() + st::newGroupInfoPadding.left(), _title->y() + _title->height() + st::newGroupDescriptionPadding.top()); _sign->moveToLeft(st::boxPadding.left() + st::newGroupInfoPadding.left(), _description->y() + _description->height() + st::newGroupDescriptionPadding.bottom() + st::newGroupPublicLinkPadding.top()); _inviteEverybody->moveToLeft(st::boxPadding.left() + st::newGroupInfoPadding.left(), _description->y() + _description->height() + st::boxTitleHeight); _inviteOnlyAdmins->moveToLeft(st::boxPadding.left() + st::newGroupInfoPadding.left(), _inviteEverybody->bottomNoMargins() + st::boxLittleSkip); if (canEditSignatures()) { _publicLink->moveToLeft(st::boxPadding.left() + st::newGroupInfoPadding.left(), _sign->bottomNoMargins() + st::newGroupDescriptionPadding.bottom() + st::newGroupPublicLinkPadding.top()); } else if (canEditInvites()) { _publicLink->moveToLeft(st::boxPadding.left() + st::newGroupInfoPadding.left(), _inviteOnlyAdmins->bottomNoMargins() + st::newGroupDescriptionPadding.bottom() + st::newGroupPublicLinkPadding.top()); } else { _publicLink->moveToLeft(st::boxPadding.left() + st::newGroupInfoPadding.left(), _description->y() + _description->height() + st::newGroupDescriptionPadding.bottom() + st::newGroupPublicLinkPadding.top()); } } void EditChannelBox::paintEvent(QPaintEvent *e) { BoxContent::paintEvent(e); if (canEditInvites()) { Painter p(this); p.setPen(st::boxTitleFg); p.setFont(st::autoDownloadTitleFont); p.drawTextLeft(st::boxTitlePosition.x(), _description->y() + _description->height() + st::boxTitlePosition.y(), width(), lang(lng_edit_group_who_invites)); } } void EditChannelBox::onSave() { if (_saveTitleRequestId || _saveDescriptionRequestId || _saveSignRequestId || _saveInvitesRequestId) return; auto title = TextUtilities::PrepareForSending(_title->getLastText()); auto description = TextUtilities::PrepareForSending(_description->getLastText(), TextUtilities::PrepareTextOption::CheckLinks); if (title.isEmpty()) { _title->setFocus(); _title->showError(); return; } _sentTitle = title; _sentDescription = description; if (_sentTitle == _channel->name) { saveDescription(); } else { _saveTitleRequestId = MTP::send(MTPchannels_EditTitle(_channel->inputChannel, MTP_string(_sentTitle)), rpcDone(&EditChannelBox::onSaveTitleDone), rpcFail(&EditChannelBox::onSaveFail)); } } void EditChannelBox::onPublicLink() { Ui::show( Box(_channel, true), LayerOption::KeepOther); } void EditChannelBox::saveDescription() { if (_sentDescription == _channel->about()) { saveSign(); } else { _saveDescriptionRequestId = MTP::send(MTPchannels_EditAbout(_channel->inputChannel, MTP_string(_sentDescription)), rpcDone(&EditChannelBox::onSaveDescriptionDone), rpcFail(&EditChannelBox::onSaveFail)); } } void EditChannelBox::saveSign() { if (!canEditSignatures() || _channel->addsSignature() == _sign->checked()) { saveInvites(); } else { _saveSignRequestId = MTP::send(MTPchannels_ToggleSignatures(_channel->inputChannel, MTP_bool(_sign->checked())), rpcDone(&EditChannelBox::onSaveSignDone), rpcFail(&EditChannelBox::onSaveFail)); } } void EditChannelBox::saveInvites() { if (!canEditInvites() || _channel->anyoneCanAddMembers() == (_inviteGroup->value() == Invites::Everybody)) { closeBox(); } else { _saveInvitesRequestId = MTP::send(MTPchannels_ToggleInvites(_channel->inputChannel, MTP_bool(_inviteGroup->value() == Invites::Everybody)), rpcDone(&EditChannelBox::onSaveInvitesDone), rpcFail(&EditChannelBox::onSaveFail)); } } bool EditChannelBox::onSaveFail(const RPCError &error, mtpRequestId req) { if (MTP::isDefaultHandledError(error)) return false; QString err(error.type()); if (req == _saveTitleRequestId) { _saveTitleRequestId = 0; if (err == qstr("CHAT_NOT_MODIFIED") || err == qstr("CHAT_TITLE_NOT_MODIFIED")) { _channel->setName(_sentTitle, _channel->username); saveDescription(); return true; } else if (err == qstr("NO_CHAT_TITLE")) { _title->setFocus(); _title->showError(); return true; } else { _title->setFocus(); } } else if (req == _saveDescriptionRequestId) { _saveDescriptionRequestId = 0; if (err == qstr("CHAT_ABOUT_NOT_MODIFIED")) { _channel->setAbout(_sentDescription); saveSign(); return true; } else { _description->setFocus(); } } else if (req == _saveSignRequestId) { _saveSignRequestId = 0; if (err == qstr("CHAT_NOT_MODIFIED")) { saveInvites(); return true; } } else if (req == _saveInvitesRequestId) { _saveInvitesRequestId = 0; if (err == qstr("CHAT_NOT_MODIFIED")) { closeBox(); return true; } } return true; } void EditChannelBox::onSaveTitleDone(const MTPUpdates &result) { _saveTitleRequestId = 0; Auth().api().applyUpdates(result); saveDescription(); } void EditChannelBox::onSaveDescriptionDone(const MTPBool &result) { _saveDescriptionRequestId = 0; _channel->setAbout(_sentDescription); saveSign(); } void EditChannelBox::onSaveSignDone(const MTPUpdates &result) { _saveSignRequestId = 0; Auth().api().applyUpdates(result); saveInvites(); } void EditChannelBox::onSaveInvitesDone(const MTPUpdates &result) { _saveSignRequestId = 0; Auth().api().applyUpdates(result); closeBox(); } RevokePublicLinkBox::Inner::Inner(QWidget *parent, base::lambda revokeCallback) : TWidget(parent) , _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) , _revokeWidth(st::normalFont->width(lang(lng_channels_too_much_public_revoke))) , _revokeCallback(std::move(revokeCallback)) { setMouseTracking(true); resize(width(), 5 * _rowHeight); request(MTPchannels_GetAdminedPublicChannels()).done([this](const MTPmessages_Chats &result) { if (auto chats = Api::getChatsFromMessagesChats(result)) { for_const (auto &chat, chats->v) { if (auto peer = App::feedChat(chat)) { if (!peer->isChannel() || peer->userName().isEmpty()) { continue; } auto row = ChatRow(peer); row.peer = peer; row.name.setText( st::contactsNameStyle, peer->name, Ui::NameTextOptions()); row.status.setText( st::defaultTextStyle, Messenger::Instance().createInternalLink( textcmdLink(1, peer->userName())), Ui::DialogTextOptions()); _rows.push_back(std::move(row)); } } } resize(width(), _rows.size() * _rowHeight); update(); }).send(); } RevokePublicLinkBox::RevokePublicLinkBox(QWidget*, base::lambda revokeCallback) : _aboutRevoke(this, lang(lng_channels_too_much_public_about), Ui::FlatLabel::InitType::Simple, st::aboutRevokePublicLabel) , _revokeCallback(std::move(revokeCallback)) { } void RevokePublicLinkBox::prepare() { _innerTop = st::boxPadding.top() + _aboutRevoke->height() + st::boxPadding.top(); _inner = setInnerWidget(object_ptr(this, [this] { closeBox(); if (_revokeCallback) { _revokeCallback(); } }), st::boxLayerScroll, _innerTop); addButton(langFactory(lng_cancel), [this] { closeBox(); }); subscribe(Auth().downloaderTaskFinished(), [this] { update(); }); _inner->resizeToWidth(st::boxWideWidth); setDimensions(st::boxWideWidth, _innerTop + _inner->height()); } void RevokePublicLinkBox::Inner::mouseMoveEvent(QMouseEvent *e) { updateSelected(); } void RevokePublicLinkBox::Inner::updateSelected() { auto point = mapFromGlobal(QCursor::pos()); PeerData *selected = nullptr; auto top = _rowsTop; for_const (auto &row, _rows) { auto revokeLink = rtlrect(width() - st::contactsPadding.right() - st::contactsCheckPosition.x() - _revokeWidth, top + st::contactsPadding.top() + (st::contactsPhotoSize - st::normalFont->height) / 2, _revokeWidth, st::normalFont->height, width()); if (revokeLink.contains(point)) { selected = row.peer; break; } top += _rowHeight; } if (selected != _selected) { _selected = selected; setCursor((_selected || _pressed) ? style::cur_pointer : style::cur_default); update(); } } void RevokePublicLinkBox::Inner::mousePressEvent(QMouseEvent *e) { if (_pressed != _selected) { _pressed = _selected; update(); } } void RevokePublicLinkBox::Inner::mouseReleaseEvent(QMouseEvent *e) { auto pressed = base::take(_pressed); setCursor((_selected || _pressed) ? style::cur_pointer : style::cur_default); if (pressed && pressed == _selected) { auto text_method = pressed->isMegagroup() ? lng_channels_too_much_public_revoke_confirm_group : lng_channels_too_much_public_revoke_confirm_channel; auto text = text_method(lt_link, Messenger::Instance().createInternalLink(pressed->userName()), lt_group, pressed->name); auto confirmText = lang(lng_channels_too_much_public_revoke); _weakRevokeConfirmBox = Ui::show(Box(text, confirmText, base::lambda_guarded(this, [this, pressed]() { if (_revokeRequestId) return; _revokeRequestId = request(MTPchannels_UpdateUsername(pressed->asChannel()->inputChannel, MTP_string(""))).done([this](const MTPBool &result) { if (_weakRevokeConfirmBox) { _weakRevokeConfirmBox->closeBox(); } if (_revokeCallback) { _revokeCallback(); } }).send(); })), LayerOption::KeepOther); } } void RevokePublicLinkBox::Inner::paintEvent(QPaintEvent *e) { Painter p(this); p.translate(0, _rowsTop); for_const (auto &row, _rows) { paintChat(p, row, (row.peer == _selected)); p.translate(0, _rowHeight); } } void RevokePublicLinkBox::resizeEvent(QResizeEvent *e) { BoxContent::resizeEvent(e); _aboutRevoke->moveToLeft(st::boxPadding.left(), st::boxPadding.top()); } void RevokePublicLinkBox::Inner::paintChat(Painter &p, const ChatRow &row, bool selected) const { auto peer = row.peer; peer->paintUserpicLeft(p, st::contactsPadding.left(), st::contactsPadding.top(), width(), st::contactsPhotoSize); p.setPen(st::contactsNameFg); int32 namex = st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left(); int32 namew = width() - namex - st::contactsPadding.right() - (_revokeWidth + st::contactsCheckPosition.x() * 2); if (peer->isVerified()) { auto icon = &st::dialogsVerifiedIcon; namew -= icon->width(); icon->paint(p, namex + qMin(row.name.maxWidth(), namew), st::contactsPadding.top() + st::contactsNameTop, width()); } row.name.drawLeftElided(p, namex, st::contactsPadding.top() + st::contactsNameTop, namew, width()); p.setFont(selected ? st::linkOverFont : st::linkFont); p.setPen(selected ? st::defaultLinkButton.overColor : st::defaultLinkButton.color); p.drawTextRight(st::contactsPadding.right() + st::contactsCheckPosition.x(), st::contactsPadding.top() + (st::contactsPhotoSize - st::normalFont->height) / 2, width(), lang(lng_channels_too_much_public_revoke), _revokeWidth); p.setPen(st::contactsStatusFg); p.setTextPalette(st::revokePublicLinkStatusPalette); row.status.drawLeftElided(p, namex, st::contactsPadding.top() + st::contactsStatusTop, namew, width()); p.restoreTextPalette(); } tdesktop-1.2.17/Telegram/SourceFiles/boxes/add_contact_box.h000066400000000000000000000162461326245125100240400ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "boxes/abstract_box.h" #include "mtproto/sender.h" #include "styles/style_widgets.h" class ConfirmBox; class PeerListBox; namespace Ui { class FlatLabel; class InputField; class PhoneInput; class InputArea; class UsernameInput; class Checkbox; template class RadioenumGroup; template class Radioenum; class LinkButton; class UserpicButton; } // namespace Ui enum class PeerFloodType { Send, InviteGroup, InviteChannel, }; QString PeerFloodErrorText(PeerFloodType type); class AddContactBox : public BoxContent, public RPCSender { Q_OBJECT public: AddContactBox(QWidget*, QString fname = QString(), QString lname = QString(), QString phone = QString()); AddContactBox(QWidget*, UserData *user); protected: void prepare() override; void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; void setInnerFocus() override; private slots: void onSubmit(); void onSave(); void onRetry(); private: void updateButtons(); void onImportDone(const MTPcontacts_ImportedContacts &res); void onSaveUserDone(const MTPcontacts_ImportedContacts &res); bool onSaveUserFail(const RPCError &e); UserData *_user = nullptr; object_ptr _first; object_ptr _last; object_ptr _phone; bool _retrying = false; bool _invertOrder = false; uint64 _contactId = 0; mtpRequestId _addRequest = 0; QString _sentName; }; class GroupInfoBox : public BoxContent, private MTP::Sender { Q_OBJECT public: GroupInfoBox(QWidget*, CreatingGroupType creating, bool fromTypeChoose); protected: void prepare() override; void setInnerFocus() override; void resizeEvent(QResizeEvent *e) override; private slots: void onNext(); void onNameSubmit(); void onDescriptionResized(); void onClose() { closeBox(); } private: void createChannel(const QString &title, const QString &description); void createGroup(not_null selectUsersBox, const QString &title, const std::vector> &users); void updateMaxHeight(); void updateSelected(const QPoint &cursorGlobalPosition); CreatingGroupType _creating; bool _fromTypeChoose = false; object_ptr _photo = { nullptr }; object_ptr _title = { nullptr }; object_ptr _description = { nullptr }; // group / channel creation mtpRequestId _creationRequestId = 0; ChannelData *_createdChannel = nullptr; }; class SetupChannelBox : public BoxContent, public RPCSender { Q_OBJECT public: SetupChannelBox(QWidget*, ChannelData *channel, bool existing = false); void setInnerFocus() override; protected: void prepare() override; void keyPressEvent(QKeyEvent *e) override; void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void leaveEventHook(QEvent *e) override; private slots: void onSave(); void onChange(); void onCheck(); private: enum class Privacy { Public, Private, }; void privacyChanged(Privacy value); void updateSelected(const QPoint &cursorGlobalPosition); void showAddContactsToChannelBox() const; void onUpdateDone(const MTPBool &result); bool onUpdateFail(const RPCError &error); void onCheckDone(const MTPBool &result); bool onCheckFail(const RPCError &error); bool onFirstCheckFail(const RPCError &error); void updateMaxHeight(); void showRevokePublicLinkBoxForEdit(); ChannelData *_channel = nullptr; bool _existing = false; std::shared_ptr> _privacyGroup; object_ptr> _public; object_ptr> _private; int32 _aboutPublicWidth, _aboutPublicHeight; Text _aboutPublic, _aboutPrivate; object_ptr _link; QRect _invitationLink; bool _linkOver = false; bool _tooMuchUsernames = false; mtpRequestId _saveRequestId = 0; mtpRequestId _checkRequestId = 0; QString _sentUsername, _checkUsername, _errorText, _goodText; QTimer _checkTimer; }; class EditNameBox : public BoxContent, public RPCSender { public: EditNameBox(QWidget*, not_null user); protected: void setInnerFocus() override; void prepare() override; void resizeEvent(QResizeEvent *e) override; private: void submit(); void save(); void saveSelfDone(const MTPUser &user); bool saveSelfFail(const RPCError &error); not_null _user; object_ptr _first; object_ptr _last; bool _invertOrder = false; mtpRequestId _requestId = 0; QString _sentName; }; class EditBioBox : public BoxContent, private MTP::Sender { public: EditBioBox(QWidget*, not_null self); protected: void setInnerFocus() override; void prepare() override; void resizeEvent(QResizeEvent *e) override; private: void updateMaxHeight(); void handleBioUpdated(); void save(); style::InputField _dynamicFieldStyle; not_null _self; object_ptr _bio; object_ptr _countdown; object_ptr _about; mtpRequestId _requestId = 0; QString _sentBio; }; class EditChannelBox : public BoxContent, public RPCSender { Q_OBJECT public: EditChannelBox(QWidget*, not_null channel); protected: void prepare() override; void setInnerFocus() override; void keyPressEvent(QKeyEvent *e) override; void resizeEvent(QResizeEvent *e) override; void paintEvent(QPaintEvent *e) override; private slots: void onSave(); void onDescriptionResized(); void onPublicLink(); void onClose() { closeBox(); } private: void updateMaxHeight(); bool canEditSignatures() const; bool canEditInvites() const; void handleChannelNameChange(); void onSaveTitleDone(const MTPUpdates &result); void onSaveDescriptionDone(const MTPBool &result); void onSaveSignDone(const MTPUpdates &result); void onSaveInvitesDone(const MTPUpdates &result); bool onSaveFail(const RPCError &error, mtpRequestId req); void saveDescription(); void saveSign(); void saveInvites(); not_null _channel; object_ptr _title; object_ptr _description; object_ptr _sign; enum class Invites { Everybody, OnlyAdmins, }; std::shared_ptr> _inviteGroup; object_ptr> _inviteEverybody; object_ptr> _inviteOnlyAdmins; object_ptr _publicLink; mtpRequestId _saveTitleRequestId = 0; mtpRequestId _saveDescriptionRequestId = 0; mtpRequestId _saveSignRequestId = 0; mtpRequestId _saveInvitesRequestId = 0; QString _sentTitle, _sentDescription; }; class RevokePublicLinkBox : public BoxContent, public RPCSender { public: RevokePublicLinkBox(QWidget*, base::lambda revokeCallback); protected: void prepare() override; void resizeEvent(QResizeEvent *e) override; private: object_ptr _aboutRevoke; class Inner; QPointer _inner; int _innerTop = 0; base::lambda _revokeCallback; }; tdesktop-1.2.17/Telegram/SourceFiles/boxes/autolock_box.cpp000066400000000000000000000032261326245125100237430ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/autolock_box.h" #include "lang/lang_keys.h" #include "storage/localstorage.h" #include "mainwindow.h" #include "ui/widgets/checkbox.h" #include "styles/style_boxes.h" void AutoLockBox::prepare() { setTitle(langFactory(lng_passcode_autolock)); addButton(langFactory(lng_box_ok), [this] { closeBox(); }); auto options = { 60, 300, 3600, 18000 }; auto group = std::make_shared(Global::AutoLock()); auto y = st::boxOptionListPadding.top() + st::langsButton.margin.top(); auto count = int(options.size()); _options.reserve(count); for (auto seconds : options) { _options.emplace_back(this, group, seconds, (seconds % 3600) ? lng_passcode_autolock_minutes(lt_count, seconds / 60) : lng_passcode_autolock_hours(lt_count, seconds / 3600), st::langsButton); _options.back()->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), y); y += _options.back()->heightNoMargins() + st::boxOptionListSkip; } group->setChangedCallback([this](int value) { durationChanged(value); }); setDimensions(st::langsWidth, st::boxOptionListPadding.top() + count * _options.back()->heightNoMargins() + (count - 1) * st::boxOptionListSkip + st::boxOptionListPadding.bottom() + st::boxPadding.bottom()); } void AutoLockBox::durationChanged(int seconds) { Global::SetAutoLock(seconds); Local::writeUserSettings(); Global::RefLocalPasscodeChanged().notify(); Auth().checkAutoLock(); closeBox(); } tdesktop-1.2.17/Telegram/SourceFiles/boxes/autolock_box.h000066400000000000000000000010701326245125100234030ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "boxes/abstract_box.h" namespace Ui { class Radiobutton; } // namespace Ui class AutoLockBox : public BoxContent { Q_OBJECT public: AutoLockBox(QWidget*) { } protected: void prepare() override; private: void durationChanged(int seconds); std::vector> _options; }; tdesktop-1.2.17/Telegram/SourceFiles/boxes/background_box.cpp000066400000000000000000000167641326245125100242540ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/background_box.h" #include "lang/lang_keys.h" #include "mainwidget.h" #include "mainwindow.h" #include "window/themes/window_theme.h" #include "styles/style_overview.h" #include "styles/style_boxes.h" #include "ui/effects/round_checkbox.h" #include "auth_session.h" class BackgroundBox::Inner : public TWidget, public RPCSender, private base::Subscriber { public: Inner(QWidget *parent); void setBackgroundChosenCallback(base::lambda callback) { _backgroundChosenCallback = std::move(callback); } ~Inner(); protected: void paintEvent(QPaintEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; private: void gotWallpapers(const MTPVector &result); void updateWallpapers(); base::lambda _backgroundChosenCallback; int _bgCount = 0; int _rows = 0; int _over = -1; int _overDown = -1; std::unique_ptr _check; // this is not a widget }; BackgroundBox::BackgroundBox(QWidget*) { } void BackgroundBox::prepare() { setTitle(langFactory(lng_backgrounds_header)); addButton(langFactory(lng_close), [this] { closeBox(); }); setDimensions(st::boxWideWidth, st::boxMaxListHeight); _inner = setInnerWidget(object_ptr(this), st::backgroundScroll); _inner->setBackgroundChosenCallback([this](int index) { backgroundChosen(index); }); } void BackgroundBox::backgroundChosen(int index) { if (index >= 0 && index < App::cServerBackgrounds().size()) { auto &paper = App::cServerBackgrounds()[index]; if (App::main()) App::main()->setChatBackground(paper); using Update = Window::Theme::BackgroundUpdate; Window::Theme::Background()->notify(Update(Update::Type::Start, !paper.id)); } closeBox(); } BackgroundBox::Inner::Inner(QWidget *parent) : TWidget(parent) , _check(std::make_unique(st::overviewCheck, [this] { update(); })) { _check->setChecked(true, Ui::RoundCheckbox::SetStyle::Fast); if (App::cServerBackgrounds().isEmpty()) { resize(BackgroundsInRow * (st::backgroundSize.width() + st::backgroundPadding) + st::backgroundPadding, 2 * (st::backgroundSize.height() + st::backgroundPadding) + st::backgroundPadding); MTP::send(MTPaccount_GetWallPapers(), rpcDone(&Inner::gotWallpapers)); } else { updateWallpapers(); } subscribe(Auth().downloaderTaskFinished(), [this] { update(); }); subscribe(Window::Theme::Background(), [this](const Window::Theme::BackgroundUpdate &update) { if (update.paletteChanged()) { _check->invalidateCache(); } }); setMouseTracking(true); } void BackgroundBox::Inner::gotWallpapers(const MTPVector &result) { App::WallPapers wallpapers; auto oldBackground = ImagePtr(qsl(":/gui/art/bg_initial.jpg")); wallpapers.push_back(App::WallPaper(Window::Theme::kInitialBackground, oldBackground, oldBackground)); auto &v = result.v; for_const (auto &w, v) { switch (w.type()) { case mtpc_wallPaper: { auto &d = w.c_wallPaper(); auto &sizes = d.vsizes.v; const MTPPhotoSize *thumb = 0, *full = 0; int32 thumbLevel = -1, fullLevel = -1; for (QVector::const_iterator j = sizes.cbegin(), e = sizes.cend(); j != e; ++j) { char size = 0; int32 w = 0, h = 0; switch (j->type()) { case mtpc_photoSize: { auto &s = j->c_photoSize().vtype.v; if (s.size()) size = s[0]; w = j->c_photoSize().vw.v; h = j->c_photoSize().vh.v; } break; case mtpc_photoCachedSize: { auto &s = j->c_photoCachedSize().vtype.v; if (s.size()) size = s[0]; w = j->c_photoCachedSize().vw.v; h = j->c_photoCachedSize().vh.v; } break; } if (!size || !w || !h) continue; int32 newThumbLevel = qAbs((st::backgroundSize.width() * cIntRetinaFactor()) - w), newFullLevel = qAbs(2560 - w); if (thumbLevel < 0 || newThumbLevel < thumbLevel) { thumbLevel = newThumbLevel; thumb = &(*j); } if (fullLevel < 0 || newFullLevel < fullLevel) { fullLevel = newFullLevel; full = &(*j); } } if (thumb && full && full->type() != mtpc_photoSizeEmpty) { wallpapers.push_back(App::WallPaper(d.vid.v ? d.vid.v : INT_MAX, App::image(*thumb), App::image(*full))); } } break; case mtpc_wallPaperSolid: { auto &d = w.c_wallPaperSolid(); } break; } } App::cSetServerBackgrounds(wallpapers); updateWallpapers(); } void BackgroundBox::Inner::updateWallpapers() { _bgCount = App::cServerBackgrounds().size(); _rows = _bgCount / BackgroundsInRow; if (_bgCount % BackgroundsInRow) ++_rows; resize(BackgroundsInRow * (st::backgroundSize.width() + st::backgroundPadding) + st::backgroundPadding, _rows * (st::backgroundSize.height() + st::backgroundPadding) + st::backgroundPadding); for (int i = 0; i < BackgroundsInRow * 3; ++i) { if (i >= _bgCount) break; App::cServerBackgrounds().at(i).thumb->load(); } } void BackgroundBox::Inner::paintEvent(QPaintEvent *e) { QRect r(e->rect()); Painter p(this); if (_rows) { for (int i = 0; i < _rows; ++i) { if ((st::backgroundSize.height() + st::backgroundPadding) * (i + 1) <= r.top()) continue; for (int j = 0; j < BackgroundsInRow; ++j) { int index = i * BackgroundsInRow + j; if (index >= _bgCount) break; const App::WallPaper &paper(App::cServerBackgrounds().at(index)); paper.thumb->load(); int x = st::backgroundPadding + j * (st::backgroundSize.width() + st::backgroundPadding); int y = st::backgroundPadding + i * (st::backgroundSize.height() + st::backgroundPadding); const QPixmap &pix(paper.thumb->pix(st::backgroundSize.width(), st::backgroundSize.height())); p.drawPixmap(x, y, pix); if (paper.id == Window::Theme::Background()->id()) { auto checkLeft = x + st::backgroundSize.width() - st::overviewCheckSkip - st::overviewCheck.size; auto checkTop = y + st::backgroundSize.height() - st::overviewCheckSkip - st::overviewCheck.size; _check->paint(p, getms(), checkLeft, checkTop, width()); } } } } else { p.setFont(st::noContactsFont); p.setPen(st::noContactsColor); p.drawText(QRect(0, 0, width(), st::noContactsHeight), lang(lng_contacts_loading), style::al_center); } } void BackgroundBox::Inner::mouseMoveEvent(QMouseEvent *e) { int x = e->pos().x(), y = e->pos().y(); int row = int((y - st::backgroundPadding) / (st::backgroundSize.height() + st::backgroundPadding)); if (y - row * (st::backgroundSize.height() + st::backgroundPadding) > st::backgroundPadding + st::backgroundSize.height()) row = _rows + 1; int col = int((x - st::backgroundPadding) / (st::backgroundSize.width() + st::backgroundPadding)); if (x - col * (st::backgroundSize.width() + st::backgroundPadding) > st::backgroundPadding + st::backgroundSize.width()) row = _rows + 1; int newOver = row * BackgroundsInRow + col; if (newOver >= _bgCount) newOver = -1; if (newOver != _over) { _over = newOver; setCursor((_over >= 0 || _overDown >= 0) ? style::cur_pointer : style::cur_default); } } void BackgroundBox::Inner::mousePressEvent(QMouseEvent *e) { _overDown = _over; } void BackgroundBox::Inner::mouseReleaseEvent(QMouseEvent *e) { if (_overDown == _over && _over >= 0) { if (_backgroundChosenCallback) { _backgroundChosenCallback(_over); } } else if (_over < 0) { setCursor(style::cur_default); } } BackgroundBox::Inner::~Inner() = default; tdesktop-1.2.17/Telegram/SourceFiles/boxes/background_box.h000066400000000000000000000010411326245125100236770ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "boxes/abstract_box.h" namespace Ui { class RoundCheckbox; } // namespace Ui class BackgroundBox : public BoxContent { public: BackgroundBox(QWidget*); protected: void prepare() override; private: void backgroundChosen(int index); class Inner; QPointer _inner; }; tdesktop-1.2.17/Telegram/SourceFiles/boxes/boxes.style000066400000000000000000000440721326245125100227540ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ using "basic.style"; using "ui/widgets/widgets.style"; using "intro/intro.style"; boxDuration: 200; boxRadius: 3px; boxButtonFont: font(boxFontSize semibold); defaultBoxButton: RoundButton(defaultLightButton) { width: -24px; height: 36px; font: boxButtonFont; } boxTextStyle: TextStyle(defaultTextStyle) { font: font(boxFontSize); linkFont: font(boxFontSize); linkFontOver: font(boxFontSize underline); } boxLabelStyle: TextStyle(boxTextStyle) { lineHeight: 22px; } attentionBoxButton: RoundButton(defaultBoxButton) { textFg: attentionButtonFg; textFgOver: attentionButtonFgOver; textBgOver: attentionButtonBgOver; ripple: RippleAnimation(defaultRippleAnimation) { color: attentionButtonBgRipple; } } defaultBoxCheckbox: Checkbox(defaultCheckbox) { width: -46px; textPosition: point(12px, 1px); style: boxTextStyle; } boxRoundShadow: Shadow { left: icon {{ "round_shadow_box_left", windowShadowFg }}; topLeft: icon {{ "round_shadow_box_top_left", windowShadowFg }}; top: icon {{ "round_shadow_box_top", windowShadowFg }}; topRight: icon {{ "round_shadow_box_top_left-flip_horizontal", windowShadowFg }}; right: icon {{ "round_shadow_box_left-flip_horizontal", windowShadowFg }}; bottomRight: icon {{ "round_shadow_box_bottom_left-flip_horizontal", windowShadowFg }}; bottom: icon {{ "round_shadow_box_bottom", windowShadowFg }}; bottomLeft: icon {{ "round_shadow_box_bottom_left", windowShadowFg }}; extend: margins(10px, 10px, 10px, 10px); fallback: windowShadowFgFallback; } boxTitleFont: font(17px semibold); boxTitle: FlatLabel(defaultFlatLabel) { textFg: boxTitleFg; maxHeight: 24px; style: TextStyle(defaultTextStyle) { font: boxTitleFont; linkFont: boxTitleFont; linkFontOver: font(17px semibold underline); } } boxTitlePosition: point(23px, 20px); boxTitleHeight: 56px; boxLayerTitlePosition: point(23px, 16px); boxLayerTitleHeight: 56px; boxLayerTitleAdditionalSkip: 9px; boxLayerTitleAdditionalFont: normalFont; boxLayerScroll: defaultSolidScroll; boxTopMargin: 6px; boxTitleClose: IconButton(defaultIconButton) { width: boxTitleHeight; height: boxTitleHeight; icon: boxTitleCloseIcon; iconOver: boxTitleCloseIconOver; rippleAreaPosition: point(6px, 6px); rippleAreaSize: 44px; ripple: RippleAnimation(defaultRippleAnimation) { color: windowBgOver; } } boxLinkButton: LinkButton(defaultLinkButton) { font: boxTextFont; overFont: font(boxFontSize underline); } boxOptionListPadding: margins(0px, 0px, 0px, 0px); boxOptionListSkip: 20px; boxOptionInputSkip: 6px; boxVerticalMargin: 10px; boxWidth: 320px; boxWideWidth: 364px; boxPadding: margins(23px, 30px, 23px, 8px); boxMaxListHeight: 492px; boxLittleSkip: 10px; boxMediumSkip: 20px; boxButtonPadding: margins(8px, 12px, 13px, 12px); boxLayerButtonPadding: margins(8px, 8px, 8px, 8px); boxLabel: FlatLabel(defaultFlatLabel) { minWidth: 285px; align: align(topleft); style: boxLabelStyle; } countryRowHeight: 36px; countryRowNameFont: semiboldFont; countryRowNameFg: boxTextFg; countryRowPadding: margins(22px, 9px, 8px, 0px); countryRowCodeFont: font(fsize); countryRowBg: windowBg; countryRowBgOver: windowBgOver; countryRowCodeFg: windowSubTextFg; countryRowCodeFgOver: windowSubTextFgOver; countriesSkip: 12px; countriesScroll: ScrollArea(boxLayerScroll) { deltat: 9px; deltab: 3px; } boxPhotoTitleFont: font(16px semibold); boxPhotoTitlePosition: point(28px, 20px); boxPhotoPadding: margins(28px, 28px, 28px, 18px); boxPhotoCompressedSkip: 20px; boxPhotoCaptionSkip: 8px; boxPhotoTextFg: windowSubTextFg; cropPointSize: 10px; cropSkip: 13px; cropMinSize: 20px; confirmInviteTitle: FlatLabel(defaultFlatLabel) { align: align(center); minWidth: 320px; maxHeight: 24px; textFg: windowBoldFg; style: TextStyle(defaultTextStyle) { font: font(16px semibold); linkFont: font(16px semibold); linkFontOver: font(16px semibold underline); } } confirmInviteStatus: FlatLabel(boxLabel) { align: align(center); minWidth: 320px; maxHeight: 20px; textFg: windowSubTextFg; } confirmInviteTitleTop: 106px; confirmInvitePhotoSize: 76px; confirmInvitePhotoTop: 20px; confirmInviteStatusTop: 136px; confirmInviteUserHeight: 84px; confirmInviteUserPhotoSize: 56px; confirmInviteUserPhotoTop: 166px; confirmInviteUserName: FlatLabel(defaultFlatLabel) { align: align(center); minWidth: 66px; maxHeight: 20px; } confirmInviteUserNameTop: 227px; confirmPhoneAboutLabel: FlatLabel(defaultFlatLabel) { minWidth: 282px; } confirmPhoneCodeField: InputField(defaultInputField) { } revokePublicLinkStatusPalette: TextPalette(defaultTextPalette) { linkFg: contactsStatusFgOnline; } aboutRevokePublicLabel: FlatLabel(defaultFlatLabel) { align: align(topleft); minWidth: 320px; } editBioCountdownLabel: FlatLabel(defaultFlatLabel) { style: boxTextStyle; textFg: windowSubTextFg; } contactUserIcon: icon {{ "add_contact_user", menuIconFg }}; contactPhoneIcon: icon {{ "add_contact_phone", menuIconFg }}; contactIconTop: 28px; contactsAddIconAbove: icon {{ "contacts_add", activeButtonFg, point(18px, 18px) }}; contactsAdd: TwoIconButton { width: 52px; height: 52px; iconBelow: contactsAddIconBelow; iconBelowOver: contactsAddIconBelowOver; iconAbove: contactsAddIconAbove; iconAboveOver: contactsAddIconAbove; rippleAreaPosition: point(5px, 5px); rippleAreaSize: 42px; ripple: RippleAnimation(defaultRippleAnimation) { color: activeButtonBgRipple; } } contactsAddPosition: point(14px, 8px); contactPadding: margins(49px, 2px, 0px, 12px); contactSkip: 6px; contactPhoneSkip: 30px; contactsPhotoSize: 42px; contactsPadding: margins(16px, 7px, 16px, 7px); contactsNameTop: 2px; contactsNameStyle: TextStyle(defaultTextStyle) { font: semiboldFont; linkFont: semiboldFont; linkFontOver: semiboldFont; } contactsStatusTop: 23px; contactsStatusFont: font(fsize); contactsCheckPosition: point(8px, 16px); contactsAllAdminsTop: 15px; contactsAboutBg: windowBgOver; contactsAboutFg: windowSubTextFgOver; contactsAboutTop: 60px; contactsAboutBottom: 19px; contactsSearchField: InputField(defaultInputField) { textBg: transparent; textMargins: margins(2px, 7px, 2px, 0px); placeholderFg: placeholderFg; placeholderFgActive: placeholderFgActive; placeholderFgError: placeholderFgActive; placeholderMargins: margins(2px, 0px, 2px, 0px); placeholderScale: 0.; placeholderFont: normalFont; border: 0px; borderActive: 0px; heightMin: 32px; font: normalFont; } contactsSearchCancel: CrossButton { width: 44px; height: 44px; cross: CrossAnimation { size: 36px; skip: 12px; stroke: 2px; minScale: 0.3; } crossFg: boxTitleCloseFg; crossFgOver: boxTitleCloseFgOver; crossPosition: point(4px, 4px); duration: 150; loadingPeriod: 1000; ripple: RippleAnimation(defaultRippleAnimation) { color: windowBgOver; } } contactsMultiSelect: MultiSelect { bg: boxSearchBg; padding: margins(8px, 6px, 8px, 6px); maxHeight: 104px; scroll: ScrollArea(defaultSolidScroll) { deltat: 3px; deltab: 3px; round: 1px; width: 8px; deltax: 3px; hiding: 1000; } item: defaultMultiSelectItem; itemSkip: 8px; field: contactsSearchField; fieldMinWidth: 42px; fieldIcon: boxFieldSearchIcon; fieldIconSkip: 36px; fieldCancel: contactsSearchCancel; fieldCancelSkip: 40px; } contactsPhotoCheckIcon: icon {{ "default_checkbox_check", windowFgActive, point(3px, 6px) }}; contactsPhotoCheck: RoundCheckbox(defaultRoundCheckbox) { size: 20px; sizeSmall: 0.3; check: contactsPhotoCheckIcon; } contactsPhotoCheckbox: RoundImageCheckbox { imageRadius: 21px; imageSmallRadius: 18px; selectWidth: 2px; selectFg: windowBgActive; selectDuration: 150; check: contactsPhotoCheck; } contactsPhotoDisabledCheckFg: menuIconFg; contactsNameCheckedFg: windowActiveTextFg; contactsRipple: defaultRippleAnimation; contactsMarginTop: 4px; contactsMarginBottom: 4px; membersMarginTop: 10px; membersMarginBottom: 10px; peerListBox: PeerList(defaultPeerList) { padding: margins( 0px, membersMarginTop, 0px, membersMarginBottom); item: PeerListItem(defaultPeerListItem) { height: 56px; photoSize: contactsPhotoSize; photoPosition: point(16px, 7px); namePosition: point(74px, 9px); statusPosition: point(74px, 30px); button: OutlineButton(defaultPeerListButton) { textBg: contactsBg; textBgOver: contactsBgOver; ripple: contactsRipple; } statusFg: contactsStatusFg; statusFgOver: contactsStatusFgOver; statusFgActive: contactsStatusFgOnline; } } localStorageBoxSkip: 10px; shareRowsTop: 12px; shareRowHeight: 108px; sharePhotoTop: 6px; sharePhotoCheckbox: RoundImageCheckbox(contactsPhotoCheckbox) { imageRadius: 28px; imageSmallRadius: 24px; } shareNameStyle: TextStyle(defaultTextStyle) { font: font(11px); linkFont: font(11px); linkFontOver: font(11px); } shareNameFg: windowFg; shareNameActiveFg: windowActiveTextFg; shareNameTop: 6px; shareColumnSkip: 6px; shareActivateDuration: 150; shareScrollDuration: 300; notificationsBoxHeight: 420px; notificationsBoxMonitorTop: 63px; notificationsBoxMonitor: icon {{ "monitor", notificationsBoxMonitorFg }}; notificationsBoxScreenTop: 10px; notificationsBoxScreenSize: size(280px, 160px); notificationsBoxCountLabelTop: 80px; notificationsBoxCountTop: 30px; notificationsSampleSkip: 5px; notificationsSampleTopSkip: 5px; notificationsSampleBottomSkip: 5px; notificationsSampleMargin: 2px; notificationSampleOpacity: 0.5; notificationSampleSize: size(64px, 16px); membersAboutLimitPadding: margins(0px, 12px, 0px, 12px); membersAbout: FlatLabel(defaultFlatLabel) { minWidth: 240px; textFg: membersAboutLimitFg; align: align(top); style: boxLabelStyle; } sessionsScroll: boxLayerScroll; sessionsHeight: 350px; sessionHeight: 70px; sessionCurrentPadding: margins(0px, 7px, 0px, 4px); sessionCurrentHeight: 118px; sessionPadding: margins(21px, 10px, 21px, 0px); sessionNameFont: msgNameFont; sessionNameFg: boxTextFg; sessionWhenFont: msgDateFont; sessionWhenFg: windowSubTextFg; sessionInfoFont: msgFont; sessionInfoFg: windowSubTextFg; sessionTerminateTop: 28px; sessionTerminateSkip: 22px; sessionTerminate: IconButton { width: 20px; height: 20px; icon: smallCloseIcon; iconOver: smallCloseIconOver; iconPosition: point(5px, 5px); rippleAreaPosition: point(0px, 0px); rippleAreaSize: 20px; ripple: RippleAnimation(defaultRippleAnimation) { color: windowBgOver; } } sessionTerminateAllButton: LinkButton(boxLinkButton) { color: attentionButtonFg; overColor: attentionButtonFg; } passcodeHeaderFont: font(19px); passcodeHeaderHeight: 80px; passcodeInput: InputField(introPhone) { textMargins: margins(1px, 27px, 1px, 6px); } passcodeSubmit: RoundButton(introNextButton) { width: 225px; } passcodeSubmitSkip: 40px; passcodePadding: margins(0px, 0px, 0px, 5px); passcodeTextLine: 28px; passcodeLittleSkip: 5px; passcodeAboutSkip: 7px; passcodeSkip: 20px; newGroupAboutFg: windowSubTextFg; newGroupPadding: margins(4px, 6px, 4px, 3px); newGroupSkip: 27px; newGroupInfoPadding: margins(0px, -4px, 0px, 1px); newGroupLinkPadding: margins(4px, 27px, 4px, 21px); newGroupLinkTop: 3px; newGroupLinkFont: font(16px); newGroupNamePosition: point(27px, 5px); newGroupDescriptionPadding: margins(0px, 13px, 0px, 4px); newGroupDescription: InputField(defaultInputField) { textMargins: margins(1px, 26px, 1px, 4px); heightMax: 116px; } setupChannelLink: InputField(defaultInputField) { textMargins: margins(0px, 6px, 0px, 4px); heightMin: 32px; } newGroupPublicLinkPadding: margins(0px, 20px, 0px, 5px); themeWarningWidth: boxWideWidth; themeWarningHeight: 150px; themeWarningTextTop: 60px; aboutWidth: 390px; aboutVersionTop: -3px; aboutVersionLink: LinkButton(defaultLinkButton) { color: windowSubTextFg; overColor: windowSubTextFg; } aboutTextTop: 34px; aboutSkip: 14px; aboutLabel: FlatLabel(defaultFlatLabel) { minWidth: 330px; align: align(topleft); style: TextStyle(defaultTextStyle) { lineHeight: 22px; } } autoDownloadTopDelta: 10px; autoDownloadTitlePosition: point(23px, 18px); autoDownloadTitleFont: font(15px semibold); confirmCaptionArea: InputField(defaultInputField) { textMargins: margins(1px, 26px, 1px, 4px); heightMax: 78px; } confirmBg: windowBgOver; confirmMaxHeight: 245px; confirmCompressedSkip: 10px; connectionHostInputField: InputField(defaultInputField) { width: 160px; } connectionPortInputField: InputField(defaultInputField) { width: 55px; } connectionUserInputField: InputField(defaultInputField) { width: 95px; } connectionPasswordInputField: InputField(defaultInputField) { width: 120px; } connectionIPv6Skip: 11px; langsWidth: 256px; langsButton: Checkbox(defaultBoxCheckbox) { width: 200px; } backgroundPadding: 10px; backgroundSize: size(108px, 193px); backgroundScroll: ScrollArea(boxLayerScroll) { deltax: 3px; width: 10px; deltat: 10px; deltab: 10px; } calendarTitleHeight: boxTitleHeight; calendarPrevious: IconButton { width: calendarTitleHeight; height: calendarTitleHeight; icon: icon {{ "title_back", boxTitleFg }}; iconPosition: point(20px, 20px); rippleAreaPosition: point(6px, 6px); rippleAreaSize: 44px; ripple: RippleAnimation(defaultRippleAnimation) { color: windowBgOver; } } calendarPreviousDisabled: icon {{ "title_back", menuIconFg }}; calendarNext: IconButton(calendarPrevious) { icon: icon {{ "title_back-flip_horizontal", boxTitleFg }}; } calendarNextDisabled: icon {{ "title_back-flip_horizontal", menuIconFg }}; calendarTitleFont: boxTitleFont; calendarDaysFont: normalFont; calendarDaysFg: boxTitleAdditionalFg; calendarDaysHeight: 32px; calendarCellSize: size(48px, 40px); calendarCellInner: 34px; calendarPadding: margins(14px, 15px, 14px, 10px); calendarScroll: backgroundScroll; passcodeTextStyle: TextStyle(defaultTextStyle) { lineHeight: 20px; } usernamePadding: margins(23px, 6px, 21px, 12px); usernameSkip: 49px; usernameTextStyle: TextStyle(boxTextStyle, passcodeTextStyle) { } usernameDefaultFg: windowSubTextFg; downloadPathSkip: 10px; colorEditWidth: 390px; colorEditSkip: 10px; colorPickerSize: 256px; colorPickerMarkRadius: 6px; colorPickerMarkLine: 1px; colorSliderSkip: 8px; colorSliderArrowLeft: icon {{ "color_slider_arrow", sliderBgActive }}; colorSliderArrowRight: icon {{ "color_slider_arrow-flip_horizontal", sliderBgActive }}; colorSliderArrowTop: icon {{ "color_slider_arrow_vertical", sliderBgActive }}; colorSliderArrowBottom: icon {{ "color_slider_arrow_vertical-flip_vertical", sliderBgActive }}; colorSliderWidth: 19px; colorSampleSize: size(60px, 34px); colorFieldSkip: 13px; colorValueInput: InputField(defaultInputField) { textMargins: margins(16px, 3px, 0px, 2px); heightMin: 27px; } colorResultInput: InputField(colorValueInput) { } editPrivacyOptionMargin: margins(23px, 14px, 21px, 0px); editPrivacyPadding: margins(23px, 0px, 21px, 0px); editPrivacyWarningPadding: margins(23px, 14px, 21px, 0px); editPrivacyTitle: FlatLabel(defaultFlatLabel) { minWidth: 320px; textFg: boxTitleFg; maxHeight: 56px; style: TextStyle(defaultTextStyle) { font: boxTitleFont; linkFont: boxTitleFont; linkFontOver: boxTitleFont; } } editPrivacyTitlePadding: margins(23px, 20px, 21px, 13px); editPrivacyLabel: FlatLabel(defaultFlatLabel) { minWidth: 320px; textFg: membersAboutLimitFg; style: defaultTextStyle; } editPrivacyLinkMargin: margins(0px, 0px, 0px, 8px); changePhoneIcon: icon { { "phone_simcard_from", changePhoneSimcardFrom }, { "phone_simcard_migrate", changePhoneSimcardTo, point(30px, 11px) }, { "phone_simcard_to", changePhoneSimcardTo, point(78px, 0px) } }; changePhoneDescription: FlatLabel(boxLabel) { minWidth: 332px; align: align(top); } changePhoneIconTop: 20px; changePhoneDescriptionTop: 96px; changePhoneLabel: FlatLabel(defaultFlatLabel) { minWidth: 275px; textFg: windowSubTextFg; } changePhoneError: FlatLabel(changePhoneLabel) { textFg: boxTextFgError; } adminLogFilterUserpicLeft: 15px; adminLogFilterLittleSkip: 16px; adminLogFilterCheckbox: Checkbox(defaultBoxCheckbox) { style: TextStyle(boxTextStyle) { font: font(boxFontSize semibold); linkFont: font(boxFontSize semibold); linkFontOver: font(boxFontSize semibold underline); } } adminLogFilterSkip: 32px; adminLogFilterUserCheckbox: Checkbox(defaultBoxCheckbox) { margin: margins(8px, 6px, 8px, 6px); checkPosition: point(8px, 6px); } rightsCheckbox: Checkbox(defaultBoxCheckbox) { rippleBg: attentionButtonBgOver; } rightsToggle: Toggle(defaultToggle) { toggledFg: windowBgActive; untoggledFg: attentionButtonFg; xsize: 8px; vsize: 5px; vshift: 1px; stroke: 2px; duration: 120; } rightsDividerHeight: 10px; rightsHeaderMargin: margins(23px, 20px, 23px, 8px); rightsToggleMargin: margins(23px, 8px, 23px, 8px); rightsAboutMargin: margins(23px, 8px, 23px, 8px); rightsPhotoButton: UserpicButton(defaultUserpicButton) { size: size(60px, 60px); photoSize: 60px; } rightsPhotoMargin: margins(20px, 0px, 15px, 18px); rightsNameStyle: TextStyle(semiboldTextStyle) { font: font(15px semibold); linkFont: font(15px semibold); linkFontOver: font(15px semibold underline); } rightsNameTop: 8px; rightsStatusTop: 32px; rightsHeaderLabel: FlatLabel(boxLabel) { style: TextStyle(semiboldTextStyle) { font: font(boxFontSize semibold); linkFont: font(boxFontSize semibold); linkFontOver: font(boxFontSize semibold underline); } textFg: windowActiveTextFg; } rightsUntilMargin: margins(0px, 8px, 0px, 0px); mutePhotoButton: UserpicButton(defaultUserpicButton) { size: size(40px, 40px); photoSize: 40px; } muteChatTitle: FlatLabel(boxLabel) { minWidth: 235px; maxHeight: 20px; // block word wrap style: TextStyle(boxTextStyle) { font: font(boxFontSize semibold); } } muteChatTitleLeft: 50px; groupStickersRemove: contactsSearchCancel; groupStickersRemovePosition: point(6px, 6px); groupStickersFieldPadding: margins(8px, 6px, 8px, 6px); groupStickersField: InputField(contactsSearchField) { placeholderFont: boxTextFont; font: boxTextFont; placeholderMargins: margins(0px, 0px, 0px, 0px); textMargins: margins(0px, 7px, 0px, 0px); textBg: boxBg; heightMin: 32px; } groupStickersSubTitleHeight: 36px; sendMediaPreviewSize: 308px; sendMediaPreviewHeightMax: 1280; sendMediaPreviewPhotoSkip: 10px; sendMediaFileThumbSize: 64px; sendMediaFileThumbSkip: 10px; sendMediaFileNameTop: 7px; sendMediaFileStatusTop: 37px; tdesktop-1.2.17/Telegram/SourceFiles/boxes/calendar_box.cpp000066400000000000000000000345011326245125100236730ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/calendar_box.h" #include "ui/widgets/buttons.h" #include "styles/style_boxes.h" #include "styles/style_dialogs.h" #include "lang/lang_keys.h" #include "ui/effects/ripple_animation.h" namespace { constexpr auto kDaysInWeek = 7; } // namespace class CalendarBox::Context { public: Context(QDate month, QDate highlighted); void start() { _month.setForced(_month.value(), true); } void setMinDate(QDate date); void setMaxDate(QDate date); int minDayIndex() const { return _minDayIndex; } int maxDayIndex() const { return _maxDayIndex; } void skipMonth(int skip); void showMonth(QDate month); int highlightedIndex() const { return _highlightedIndex; } int rowsCount() const { return _rowsCount; } int daysShift() const { return _daysShift; } int daysCount() const { return _daysCount; } bool isEnabled(int index) const { return (index >= _minDayIndex) && (index <= _maxDayIndex); } const base::Variable &month() { return _month; } QDate dateFromIndex(int index) const; QString labelFromIndex(int index) const; private: void applyMonth(const QDate &month, bool forced = false); static int daysShiftForMonth(QDate month); static int rowsCountForMonth(QDate month); base::Variable _month; QDate _min, _max; QDate _highlighted; int _highlightedIndex = 0; int _minDayIndex = 0; int _maxDayIndex = 0; int _daysCount = 0; int _daysShift = 0; int _rowsCount = 0; }; CalendarBox::Context::Context(QDate month, QDate highlighted) : _highlighted(highlighted) { showMonth(month); } void CalendarBox::Context::setMinDate(QDate date) { _min = date; applyMonth(_month.value(), true); } void CalendarBox::Context::setMaxDate(QDate date) { _max = date; applyMonth(_month.value(), true); } void CalendarBox::Context::showMonth(QDate month) { if (month.day() != 1) { month = QDate(month.year(), month.month(), 1); } applyMonth(month); } void CalendarBox::Context::applyMonth(const QDate &month, bool forced) { _daysCount = month.daysInMonth(); _daysShift = daysShiftForMonth(month); _rowsCount = rowsCountForMonth(month); auto yearIndex = month.year(); auto monthIndex = month.month(); _highlightedIndex = month.daysTo(_highlighted); _minDayIndex = _min.isNull() ? INT_MIN : month.daysTo(_min); _maxDayIndex = _max.isNull() ? INT_MAX : month.daysTo(_max); if (forced) { _month.setForced(month, true); } else { _month.set(month, true); } } void CalendarBox::Context::skipMonth(int skip) { auto year = _month.value().year(); auto month = _month.value().month(); month += skip; while (month < 1) { --year; month += 12; } while (month > 12) { ++year; month -= 12; } showMonth(QDate(year, month, 1)); } int CalendarBox::Context::daysShiftForMonth(QDate month) { Assert(!month.isNull()); constexpr auto kMaxRows = 6; auto inMonthIndex = month.day() - 1; auto inWeekIndex = month.dayOfWeek() - 1; return ((kMaxRows * kDaysInWeek) + inWeekIndex - inMonthIndex) % kDaysInWeek; } int CalendarBox::Context::rowsCountForMonth(QDate month) { Assert(!month.isNull()); auto daysShift = daysShiftForMonth(month); auto daysCount = month.daysInMonth(); auto cellsCount = daysShift + daysCount; auto result = (cellsCount / kDaysInWeek); if (cellsCount % kDaysInWeek) ++result; return result; } QDate CalendarBox::Context::dateFromIndex(int index) const { constexpr auto kMonthsCount = 12; auto month = _month.value().month(); auto year = _month.value().year(); while (index < 0) { if (!--month) { month += kMonthsCount; --year; } index += QDate(year, month, 1).daysInMonth(); } for (auto maxIndex = QDate(year, month, 1).daysInMonth(); index >= maxIndex; maxIndex = QDate(year, month, 1).daysInMonth()) { index -= maxIndex; if (month++ == kMonthsCount) { month -= kMonthsCount; ++year; } } return QDate(year, month, index + 1); } QString CalendarBox::Context::labelFromIndex(int index) const { auto day = [this, index] { if (index >= 0 && index < daysCount()) { return index + 1; } return dateFromIndex(index).day(); }; return QString::number(day()); } class CalendarBox::Inner : public TWidget, public RPCSender, private base::Subscriber { public: Inner(QWidget *parent, Context *context); int countHeight() { auto innerHeight = st::calendarDaysHeight + _context->rowsCount() * st::calendarCellSize.height(); return st::calendarPadding.top() + innerHeight + st::calendarPadding.bottom(); } void setDateChosenCallback(base::lambda callback) { _dateChosenCallback = std::move(callback); } ~Inner(); protected: void paintEvent(QPaintEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; private: void monthChanged(QDate month); void setSelected(int selected); void setPressed(int pressed); int rowsLeft() const; int rowsTop() const; void resizeToCurrent(); void paintDayNames(Painter &p, QRect clip); void paintRows(Painter &p, QRect clip); Context *_context = nullptr; std::map> _ripples; base::lambda _dateChosenCallback; static constexpr auto kEmptySelection = -kDaysInWeek; int _selected = kEmptySelection; int _pressed = kEmptySelection; }; CalendarBox::Inner::Inner(QWidget *parent, Context *context) : TWidget(parent) , _context(context) { setMouseTracking(true); subscribe(context->month(), [this](QDate month) { monthChanged(month); }); } void CalendarBox::Inner::monthChanged(QDate month) { setSelected(kEmptySelection); _ripples.clear(); resizeToCurrent(); update(); sendSynteticMouseEvent(this, QEvent::MouseMove, Qt::NoButton); } void CalendarBox::Inner::resizeToCurrent() { resize(st::boxWideWidth, countHeight()); } void CalendarBox::Inner::paintEvent(QPaintEvent *e) { Painter p(this); auto clip = e->rect(); paintDayNames(p, clip); paintRows(p, clip); } void CalendarBox::Inner::paintDayNames(Painter &p, QRect clip) { p.setFont(st::calendarDaysFont); p.setPen(st::calendarDaysFg); auto y = st::calendarPadding.top(); auto x = rowsLeft(); if (!myrtlrect(x, y, st::calendarCellSize.width() * kDaysInWeek, st::calendarDaysHeight).intersects(clip)) { return; } for (auto i = 0; i != kDaysInWeek; ++i, x += st::calendarCellSize.width()) { auto rect = myrtlrect(x, y, st::calendarCellSize.width(), st::calendarDaysHeight); if (!rect.intersects(clip)) { continue; } p.drawText(rect, langDayOfWeek(i + 1), style::al_top); } } int CalendarBox::Inner::rowsLeft() const { return st::calendarPadding.left(); } int CalendarBox::Inner::rowsTop() const { return st::calendarPadding.top() + st::calendarDaysHeight; } void CalendarBox::Inner::paintRows(Painter &p, QRect clip) { p.setFont(st::calendarDaysFont); auto ms = getms(); auto y = rowsTop(); auto index = -_context->daysShift(); auto highlightedIndex = _context->highlightedIndex(); for (auto row = 0, rowsCount = _context->rowsCount(), daysCount = _context->daysCount() ; row != rowsCount ; ++row, y += st::calendarCellSize.height()) { auto x = rowsLeft(); if (!myrtlrect(x, y, st::calendarCellSize.width() * kDaysInWeek, st::calendarCellSize.height()).intersects(clip)) { index += kDaysInWeek; continue; } for (auto col = 0; col != kDaysInWeek; ++col, ++index, x += st::calendarCellSize.width()) { auto rect = myrtlrect(x, y, st::calendarCellSize.width(), st::calendarCellSize.height()); auto grayedOut = (index < 0 || index >= daysCount || !rect.intersects(clip)); auto highlighted = (index == highlightedIndex); auto enabled = _context->isEnabled(index); auto innerLeft = x + (st::calendarCellSize.width() - st::calendarCellInner) / 2; auto innerTop = y + (st::calendarCellSize.height() - st::calendarCellInner) / 2; if (highlighted) { PainterHighQualityEnabler hq(p); p.setPen(Qt::NoPen); p.setBrush(grayedOut ? st::windowBgOver : st::dialogsBgActive); p.drawEllipse(myrtlrect(innerLeft, innerTop, st::calendarCellInner, st::calendarCellInner)); p.setBrush(Qt::NoBrush); } auto it = _ripples.find(index); if (it != _ripples.cend()) { auto colorOverride = [highlighted, grayedOut] { if (highlighted) { return grayedOut ? st::windowBgRipple : st::dialogsRippleBgActive; } return st::windowBgOver; }; it->second->paint(p, innerLeft, innerTop, width(), ms, &(colorOverride()->c)); if (it->second->empty()) { _ripples.erase(it); } } if (highlighted) { p.setPen(grayedOut ? st::windowSubTextFg : st::dialogsNameFgActive); } else if (enabled) { p.setPen(grayedOut ? st::windowSubTextFg : st::boxTextFg); } else { p.setPen(st::windowSubTextFg); } p.drawText(rect, _context->labelFromIndex(index), style::al_center); } } } void CalendarBox::Inner::mouseMoveEvent(QMouseEvent *e) { const auto size = st::calendarCellSize; const auto point = e->pos(); const auto inner = QRect( rowsLeft(), rowsTop(), kDaysInWeek * size.width(), _context->rowsCount() * size.height()); if (inner.contains(point)) { const auto row = (point.y() - rowsTop()) / size.height(); const auto col = (point.x() - rowsLeft()) / size.width(); const auto index = row * kDaysInWeek + col - _context->daysShift(); setSelected(index); } else { setSelected(kEmptySelection); } } void CalendarBox::Inner::setSelected(int selected) { if (selected != kEmptySelection && !_context->isEnabled(selected)) { selected = kEmptySelection; } _selected = selected; setCursor((_selected == kEmptySelection) ? style::cur_default : style::cur_pointer); } void CalendarBox::Inner::mousePressEvent(QMouseEvent *e) { setPressed(_selected); if (_selected != kEmptySelection) { auto index = _selected + _context->daysShift(); Assert(index >= 0); auto row = index / kDaysInWeek; auto col = index % kDaysInWeek; auto cell = QRect(rowsLeft() + col * st::calendarCellSize.width(), rowsTop() + row * st::calendarCellSize.height(), st::calendarCellSize.width(), st::calendarCellSize.height()); auto it = _ripples.find(_selected); if (it == _ripples.cend()) { auto mask = Ui::RippleAnimation::ellipseMask(QSize(st::calendarCellInner, st::calendarCellInner)); auto update = [this, cell] { rtlupdate(cell); }; it = _ripples.emplace(_selected, std::make_unique(st::defaultRippleAnimation, std::move(mask), std::move(update))).first; } auto ripplePosition = QPoint(cell.x() + (st::calendarCellSize.width() - st::calendarCellInner) / 2, cell.y() + (st::calendarCellSize.height() - st::calendarCellInner) / 2); it->second->add(e->pos() - ripplePosition); } } void CalendarBox::Inner::mouseReleaseEvent(QMouseEvent *e) { auto pressed = _pressed; setPressed(kEmptySelection); if (pressed != kEmptySelection && pressed == _selected) { _dateChosenCallback(_context->dateFromIndex(pressed)); } } void CalendarBox::Inner::setPressed(int pressed) { if (_pressed != pressed) { if (_pressed != kEmptySelection) { auto it = _ripples.find(_pressed); if (it != _ripples.cend()) { it->second->lastStop(); } } _pressed = pressed; } } CalendarBox::Inner::~Inner() = default; class CalendarBox::Title : public TWidget, private base::Subscriber { public: Title(QWidget *parent, Context *context) : TWidget(parent) , _context(context) { subscribe(_context->month(), [this](QDate date) { monthChanged(date); }); } protected: void paintEvent(QPaintEvent *e); private: void monthChanged(QDate month); Context *_context = nullptr; QString _text; int _textWidth = 0; }; void CalendarBox::Title::monthChanged(QDate month) { _text = langMonthOfYearFull(month.month(), month.year()); _textWidth = st::calendarTitleFont->width(_text); update(); } void CalendarBox::Title::paintEvent(QPaintEvent *e) { Painter p(this); p.setFont(st::calendarTitleFont); p.setPen(st::boxTitleFg); p.drawTextLeft((width() - _textWidth) / 2, (height() - st::calendarTitleFont->height) / 2, width(), _text, _textWidth); } CalendarBox::CalendarBox(QWidget*, QDate month, QDate highlighted, base::lambda callback) : _context(std::make_unique(month, highlighted)) , _inner(this, _context.get()) , _title(this, _context.get()) , _previous(this, st::calendarPrevious) , _next(this, st::calendarNext) , _callback(std::move(callback)) { } void CalendarBox::setMinDate(QDate date) { _context->setMinDate(date); } void CalendarBox::setMaxDate(QDate date) { _context->setMaxDate(date); } void CalendarBox::prepare() { _previous->setClickedCallback([this] { if (isPreviousEnabled()) { _context->skipMonth(-1); } }); _next->setClickedCallback([this] { if (isNextEnabled()) { _context->skipMonth(1); } }); // _inner = setInnerWidget(object_ptr(this, _context.get()), st::calendarScroll, st::calendarTitleHeight); _inner->setDateChosenCallback(std::move(_callback)); addButton(langFactory(lng_close), [this] { closeBox(); }); subscribe(_context->month(), [this](QDate month) { monthChanged(month); }); _context->start(); } bool CalendarBox::isPreviousEnabled() const { return (_context->minDayIndex() < 0); } bool CalendarBox::isNextEnabled() const { return (_context->maxDayIndex() >= _context->daysCount()); } void CalendarBox::monthChanged(QDate month) { setDimensions(st::boxWideWidth, st::calendarTitleHeight + _inner->countHeight()); auto previousEnabled = isPreviousEnabled(); _previous->setIconOverride(previousEnabled ? nullptr : &st::calendarPreviousDisabled); _previous->setRippleColorOverride(previousEnabled ? nullptr : &st::boxBg); _previous->setCursor(previousEnabled ? style::cur_pointer : style::cur_default); auto nextEnabled = isNextEnabled(); _next->setIconOverride(nextEnabled ? nullptr : &st::calendarNextDisabled); _next->setRippleColorOverride(nextEnabled ? nullptr : &st::boxBg); _next->setCursor(nextEnabled ? style::cur_pointer : style::cur_default); } void CalendarBox::resizeEvent(QResizeEvent *e) { _previous->moveToLeft(0, 0); _next->moveToRight(0, 0); _title->setGeometryToLeft(_previous->width(), 0, width() - _previous->width() - _next->width(), st::calendarTitleHeight); _inner->setGeometryToLeft(0, st::calendarTitleHeight, width(), height() - st::calendarTitleHeight); BoxContent::resizeEvent(e); } CalendarBox::~CalendarBox() = default; tdesktop-1.2.17/Telegram/SourceFiles/boxes/calendar_box.h000066400000000000000000000017561326245125100233460ustar00rootroot00000000000000/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "abstract_box.h" namespace Ui { class IconButton; } // namespace Ui class CalendarBox : public BoxContent { public: CalendarBox(QWidget*, QDate month, QDate highlighted, base::lambda callback); void setMinDate(QDate date); void setMaxDate(QDate date); ~CalendarBox(); protected: void prepare() override; void resizeEvent(QResizeEvent *e) override; private: void monthChanged(QDate month); bool isPreviousEnabled() const; bool isNextEnabled() const; class Context; std::unique_ptr _context; class Inner; object_ptr _inner; class Title; object_ptr _title; object_ptr<Ui::IconButton> _previous; object_ptr<Ui::IconButton> _next; base::lambda<void(QDate date)> _callback; }; ������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/change_phone_box.cpp�������������������������������������0000664�0000000�0000000�00000024765�13262451251�0024553�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/change_phone_box.h" #include <rpl/filter.h> #include <rpl/mappers.h> #include <rpl/take.h> #include "lang/lang_keys.h" #include "styles/style_boxes.h" #include "ui/widgets/labels.h" #include "ui/widgets/input_fields.h" #include "ui/wrap/fade_wrap.h" #include "boxes/confirm_phone_box.h" #include "ui/toast/toast.h" #include "boxes/confirm_box.h" namespace { void createErrorLabel( QWidget *parent, object_ptr<Ui::FadeWrap<Ui::FlatLabel>> &label, const QString &text, int x, int y) { if (label) { label->hide(anim::type::normal); auto saved = label.data(); auto destroy = [old = std::move(label)]() mutable { old.destroyDelayed(); }; using namespace rpl::mappers; saved->shownValue() | rpl::filter(_1 == false) | rpl::take(1) | rpl::start_with_done( std::move(destroy), saved->lifetime()); } if (!text.isEmpty()) { label.create( parent, object_ptr<Ui::FlatLabel>( parent, text, Ui::FlatLabel::InitType::Simple, st::changePhoneError)); label->hide(anim::type::instant); label->moveToLeft(x, y); label->show(anim::type::normal); } } } // namespace class ChangePhoneBox::EnterPhone : public BoxContent { public: EnterPhone(QWidget*) { } void setInnerFocus() override { _phone->setFocusFast(); } protected: void prepare() override; private: void submit(); void sendPhoneDone(const QString &phoneNumber, const MTPauth_SentCode &result); bool sendPhoneFail(const QString &phoneNumber, const RPCError &error); void showError(const QString &text); void hideError() { showError(QString()); } object_ptr<Ui::PhoneInput> _phone = { nullptr }; object_ptr<Ui::FadeWrap<Ui::FlatLabel>> _error = { nullptr }; mtpRequestId _requestId = 0; }; class ChangePhoneBox::EnterCode : public BoxContent { public: EnterCode(QWidget*, const QString &phone, const QString &hash, int codeLength, int callTimeout); void setInnerFocus() override { _code->setFocusFast(); } protected: void prepare() override; private: void submit(); void sendCall(); void updateCall(); bool sendCodeFail(const RPCError &error); void showError(const QString &text); void hideError() { showError(QString()); } int countHeight(); QString _phone; QString _hash; int _codeLength = 0; int _callTimeout = 0; object_ptr<SentCodeField> _code = { nullptr }; object_ptr<Ui::FadeWrap<Ui::FlatLabel>> _error = { nullptr }; object_ptr<Ui::FlatLabel> _callLabel = { nullptr }; mtpRequestId _requestId = 0; SentCodeCall _call; }; void ChangePhoneBox::EnterPhone::prepare() { setTitle(langFactory(lng_change_phone_title)); auto phoneValue = QString(); _phone.create(this, st::defaultInputField, langFactory(lng_change_phone_new_title), phoneValue); _phone->resize(st::boxWidth - 2 * st::boxPadding.left(), _phone->height()); _phone->moveToLeft(st::boxPadding.left(), st::boxLittleSkip); connect(_phone, &Ui::PhoneInput::submitted, this, [this] { submit(); }); auto description = object_ptr<Ui::FlatLabel>(this, lang(lng_change_phone_new_description), Ui::FlatLabel::InitType::Simple, st::changePhoneLabel); auto errorSkip = st::boxLittleSkip + st::changePhoneError.style.font->height; description->moveToLeft(st::boxPadding.left(), _phone->y() + _phone->height() + errorSkip + st::boxLittleSkip); setDimensions(st::boxWidth, description->bottomNoMargins() + st::boxLittleSkip); addButton(langFactory(lng_change_phone_new_submit), [this] { submit(); }); addButton(langFactory(lng_cancel), [this] { closeBox(); }); } void ChangePhoneBox::EnterPhone::submit() { if (_requestId) { return; } hideError(); auto phoneNumber = _phone->getLastText().trimmed(); _requestId = MTP::send(MTPaccount_SendChangePhoneCode(MTP_flags(0), MTP_string(phoneNumber), MTP_bool(false)), rpcDone(base::lambda_guarded(this, [this, phoneNumber](const MTPauth_SentCode &result) { return sendPhoneDone(phoneNumber, result); })), rpcFail(base::lambda_guarded(this, [this, phoneNumber](const RPCError &error) { return sendPhoneFail(phoneNumber, error); }))); } void ChangePhoneBox::EnterPhone::sendPhoneDone(const QString &phoneNumber, const MTPauth_SentCode &result) { Expects(result.type() == mtpc_auth_sentCode); _requestId = 0; auto codeLength = 0; auto &data = result.c_auth_sentCode(); switch (data.vtype.type()) { case mtpc_auth_sentCodeTypeApp: LOG(("Error: should not be in-app code!")); showError(Lang::Hard::ServerError()); return; case mtpc_auth_sentCodeTypeSms: codeLength = data.vtype.c_auth_sentCodeTypeSms().vlength.v; break; case mtpc_auth_sentCodeTypeCall: codeLength = data.vtype.c_auth_sentCodeTypeCall().vlength.v; break; case mtpc_auth_sentCodeTypeFlashCall: LOG(("Error: should not be flashcall!")); showError(Lang::Hard::ServerError()); return; } auto phoneCodeHash = qs(data.vphone_code_hash); auto callTimeout = 0; if (data.has_next_type() && data.vnext_type.type() == mtpc_auth_codeTypeCall) { callTimeout = data.has_timeout() ? data.vtimeout.v : 60; } Ui::show( Box<EnterCode>( phoneNumber, phoneCodeHash, codeLength, callTimeout), LayerOption::KeepOther); } bool ChangePhoneBox::EnterPhone::sendPhoneFail(const QString &phoneNumber, const RPCError &error) { auto errorText = Lang::Hard::ServerError(); if (MTP::isFloodError(error)) { errorText = lang(lng_flood_error); } else if (MTP::isDefaultHandledError(error)) { return false; } else if (error.type() == qstr("PHONE_NUMBER_INVALID")) { errorText = lang(lng_bad_phone); } else if (error.type() == qstr("PHONE_NUMBER_OCCUPIED")) { Ui::show(Box<InformBox>(lng_change_phone_occupied(lt_phone, App::formatPhone(phoneNumber)), lang(lng_box_ok))); _requestId = 0; return true; } showError(errorText); _requestId = 0; return true; } void ChangePhoneBox::EnterPhone::showError(const QString &text) { createErrorLabel(this, _error, text, st::boxPadding.left(), _phone->y() + _phone->height() + st::boxLittleSkip); if (!text.isEmpty()) { _phone->showError(); } } ChangePhoneBox::EnterCode::EnterCode(QWidget*, const QString &phone, const QString &hash, int codeLength, int callTimeout) : _phone(phone) , _hash(hash) , _codeLength(codeLength) , _callTimeout(callTimeout) , _call(this, [this] { sendCall(); }, [this] { updateCall(); }) { } void ChangePhoneBox::EnterCode::prepare() { setTitle(langFactory(lng_change_phone_title)); auto descriptionText = lng_change_phone_code_description(lt_phone, textcmdStartSemibold() + App::formatPhone(_phone) + textcmdStopSemibold()); auto description = object_ptr<Ui::FlatLabel>(this, descriptionText, Ui::FlatLabel::InitType::Rich, st::changePhoneLabel); description->moveToLeft(st::boxPadding.left(), 0); auto phoneValue = QString(); _code.create(this, st::defaultInputField, langFactory(lng_change_phone_code_title), phoneValue); _code->setAutoSubmit(_codeLength, [this] { submit(); }); _code->setChangedCallback([this] { hideError(); }); _code->resize(st::boxWidth - 2 * st::boxPadding.left(), _code->height()); _code->moveToLeft(st::boxPadding.left(), description->bottomNoMargins()); connect(_code, &Ui::InputField::submitted, this, [this] { submit(); }); setDimensions(st::boxWidth, countHeight()); if (_callTimeout > 0) { _call.setStatus({ SentCodeCall::State::Waiting, _callTimeout }); updateCall(); } addButton(langFactory(lng_change_phone_new_submit), [this] { submit(); }); addButton(langFactory(lng_cancel), [this] { closeBox(); }); } int ChangePhoneBox::EnterCode::countHeight() { auto errorSkip = st::boxLittleSkip + st::changePhoneError.style.font->height; return _code->bottomNoMargins() + errorSkip + 3 * st::boxLittleSkip; } void ChangePhoneBox::EnterCode::submit() { if (_requestId) { return; } hideError(); auto code = _code->getLastText().trimmed(); _requestId = MTP::send(MTPaccount_ChangePhone( MTP_string(_phone), MTP_string(_hash), MTP_string(code) ), rpcDone([weak = make_weak(this)](const MTPUser &result) { App::feedUser(result); if (weak) { Ui::hideLayer(); } Ui::Toast::Show(lang(lng_change_phone_success)); }), rpcFail(base::lambda_guarded(this, [this](const RPCError &error) { return sendCodeFail(error); }))); } void ChangePhoneBox::EnterCode::sendCall() { MTP::send(MTPauth_ResendCode(MTP_string(_phone), MTP_string(_hash)), rpcDone(base::lambda_guarded(this, [this] { _call.callDone(); }))); } void ChangePhoneBox::EnterCode::updateCall() { auto text = _call.getText(); if (text.isEmpty()) { _callLabel.destroy(); } else if (!_callLabel) { _callLabel.create(this, text, Ui::FlatLabel::InitType::Simple, st::changePhoneLabel); _callLabel->moveToLeft(st::boxPadding.left(), countHeight() - _callLabel->height()); _callLabel->show(); } else { _callLabel->setText(text); } } void ChangePhoneBox::EnterCode::showError(const QString &text) { createErrorLabel(this, _error, text, st::boxPadding.left(), _code->y() + _code->height() + st::boxLittleSkip); if (!text.isEmpty()) { _code->showError(); } } bool ChangePhoneBox::EnterCode::sendCodeFail(const RPCError &error) { auto errorText = Lang::Hard::ServerError(); if (MTP::isFloodError(error)) { errorText = lang(lng_flood_error); } else if (MTP::isDefaultHandledError(error)) { return false; } else if (error.type() == qstr("PHONE_CODE_EMPTY") || error.type() == qstr("PHONE_CODE_INVALID")) { errorText = lang(lng_bad_code); } else if (error.type() == qstr("PHONE_CODE_EXPIRED")) { closeBox(); // Go back to phone input. _requestId = 0; return true; } else if (error.type() == qstr("PHONE_NUMBER_INVALID")) { errorText = lang(lng_bad_phone); } _requestId = 0; showError(errorText); return true; } void ChangePhoneBox::prepare() { setTitle(langFactory(lng_change_phone_title)); addButton(langFactory(lng_change_phone_button), [] { Ui::show(Box<ConfirmBox>(lang(lng_change_phone_warning), [] { Ui::show(Box<EnterPhone>()); })); }); addButton(langFactory(lng_cancel), [this] { closeBox(); }); auto label = object_ptr<Ui::FlatLabel>(this, lang(lng_change_phone_description), Ui::FlatLabel::InitType::Rich, st::changePhoneDescription); label->moveToLeft((st::boxWideWidth - label->width()) / 2, st::changePhoneDescriptionTop); setDimensions(st::boxWideWidth, label->bottomNoMargins() + st::boxLittleSkip); } void ChangePhoneBox::paintEvent(QPaintEvent *e) { BoxContent::paintEvent(e); Painter p(this); st::changePhoneIcon.paint(p, (width() - st::changePhoneIcon.width()) / 2, st::changePhoneIconTop, width()); } �����������tdesktop-1.2.17/Telegram/SourceFiles/boxes/change_phone_box.h���������������������������������������0000664�0000000�0000000�00000000767�13262451251�0024214�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "boxes/abstract_box.h" class ChangePhoneBox : public BoxContent { public: ChangePhoneBox(QWidget*) { } protected: void prepare() override; void paintEvent(QPaintEvent *e) override; private: class EnterPhone; class EnterCode; }; ���������tdesktop-1.2.17/Telegram/SourceFiles/boxes/confirm_box.cpp������������������������������������������0000664�0000000�0000000�00000055334�13262451251�0023566�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/confirm_box.h" #include "styles/style_boxes.h" #include "lang/lang_keys.h" #include "mainwidget.h" #include "mainwindow.h" #include "apiwrap.h" #include "application.h" #include "history/history.h" #include "history/history_item.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" #include "ui/toast/toast.h" #include "ui/empty_userpic.h" #include "core/click_handler_types.h" #include "storage/localstorage.h" #include "data/data_session.h" #include "auth_session.h" #include "observer_peer.h" TextParseOptions _confirmBoxTextOptions = { TextParseLinks | TextParseMultiline | TextParseRichText, // flags 0, // maxw 0, // maxh Qt::LayoutDirectionAuto, // dir }; ConfirmBox::ConfirmBox(QWidget*, const QString &text, base::lambda_once<void()> confirmedCallback, base::lambda_once<void()> cancelledCallback) : _confirmText(lang(lng_box_ok)) , _cancelText(lang(lng_cancel)) , _confirmStyle(st::defaultBoxButton) , _text(st::boxWidth - st::boxPadding.left() - st::boxButtonPadding.right()) , _confirmedCallback(std::move(confirmedCallback)) , _cancelledCallback(std::move(cancelledCallback)) { init(text); } ConfirmBox::ConfirmBox(QWidget*, const QString &text, const QString &confirmText, base::lambda_once<void()> confirmedCallback, base::lambda_once<void()> cancelledCallback) : _confirmText(confirmText) , _cancelText(lang(lng_cancel)) , _confirmStyle(st::defaultBoxButton) , _text(st::boxWidth - st::boxPadding.left() - st::boxButtonPadding.right()) , _confirmedCallback(std::move(confirmedCallback)) , _cancelledCallback(std::move(cancelledCallback)) { init(text); } ConfirmBox::ConfirmBox(QWidget*, const QString &text, const QString &confirmText, const style::RoundButton &confirmStyle, base::lambda_once<void()> confirmedCallback, base::lambda_once<void()> cancelledCallback) : _confirmText(confirmText) , _cancelText(lang(lng_cancel)) , _confirmStyle(confirmStyle) , _text(st::boxWidth - st::boxPadding.left() - st::boxButtonPadding.right()) , _confirmedCallback(std::move(confirmedCallback)) , _cancelledCallback(std::move(cancelledCallback)) { init(text); } ConfirmBox::ConfirmBox(QWidget*, const QString &text, const QString &confirmText, const QString &cancelText, base::lambda_once<void()> confirmedCallback, base::lambda_once<void()> cancelledCallback) : _confirmText(confirmText) , _cancelText(cancelText) , _confirmStyle(st::defaultBoxButton) , _text(st::boxWidth - st::boxPadding.left() - st::boxButtonPadding.right()) , _confirmedCallback(std::move(confirmedCallback)) , _cancelledCallback(std::move(cancelledCallback)) { init(text); } ConfirmBox::ConfirmBox(QWidget*, const QString &text, const QString &confirmText, const style::RoundButton &confirmStyle, const QString &cancelText, base::lambda_once<void()> confirmedCallback, base::lambda_once<void()> cancelledCallback) : _confirmText(confirmText) , _cancelText(cancelText) , _confirmStyle(st::defaultBoxButton) , _text(st::boxWidth - st::boxPadding.left() - st::boxButtonPadding.right()) , _confirmedCallback(std::move(confirmedCallback)) , _cancelledCallback(std::move(cancelledCallback)) { init(text); } ConfirmBox::ConfirmBox(const InformBoxTag &, const QString &text, const QString &doneText, base::lambda<void()> closedCallback) : _confirmText(doneText) , _confirmStyle(st::defaultBoxButton) , _informative(true) , _text(st::boxWidth - st::boxPadding.left() - st::boxButtonPadding.right()) , _confirmedCallback(generateInformCallback(closedCallback)) , _cancelledCallback(generateInformCallback(closedCallback)) { init(text); } base::lambda_once<void()> ConfirmBox::generateInformCallback(base::lambda<void()> closedCallback) { return base::lambda_guarded(this, [this, closedCallback] { closeBox(); if (closedCallback) { closedCallback(); } }); } void ConfirmBox::init(const QString &text) { _text.setText(st::boxLabelStyle, text, _informative ? _confirmBoxTextOptions : _textPlainOptions); } void ConfirmBox::prepare() { addButton([this] { return _confirmText; }, [this] { confirmed(); }, _confirmStyle); if (!_informative) { addButton([this] { return _cancelText; }, [this] { _cancelled = true; closeBox(); }); } subscribe(boxClosing, [this] { if (!_confirmed && (!_strictCancel || _cancelled) && _cancelledCallback) { _cancelledCallback(); } }); textUpdated(); } void ConfirmBox::textUpdated() { _textWidth = st::boxWidth - st::boxPadding.left() - st::boxButtonPadding.right(); _textHeight = qMin(_text.countHeight(_textWidth), 16 * st::boxLabelStyle.lineHeight); setDimensions(st::boxWidth, st::boxPadding.top() + _textHeight + st::boxPadding.bottom()); setMouseTracking(_text.hasLinks()); } void ConfirmBox::confirmed() { if (!_confirmed) { _confirmed = true; if (_confirmedCallback) { _confirmedCallback(); } } } void ConfirmBox::mouseMoveEvent(QMouseEvent *e) { _lastMousePos = e->globalPos(); updateHover(); } void ConfirmBox::mousePressEvent(QMouseEvent *e) { _lastMousePos = e->globalPos(); updateHover(); ClickHandler::pressed(); return BoxContent::mousePressEvent(e); } void ConfirmBox::mouseReleaseEvent(QMouseEvent *e) { _lastMousePos = e->globalPos(); updateHover(); if (auto activated = ClickHandler::unpressed()) { Ui::hideLayer(); App::activateClickHandler(activated, e->button()); } return BoxContent::mouseReleaseEvent(e); } void ConfirmBox::leaveEventHook(QEvent *e) { ClickHandler::clearActive(this); } void ConfirmBox::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { setCursor(active ? style::cur_pointer : style::cur_default); update(); } void ConfirmBox::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) { update(); } void ConfirmBox::updateLink() { _lastMousePos = QCursor::pos(); updateHover(); } void ConfirmBox::updateHover() { auto m = mapFromGlobal(_lastMousePos); auto state = _text.getStateLeft(m - QPoint(st::boxPadding.left(), st::boxPadding.top()), _textWidth, width()); ClickHandler::setActive(state.link, this); } void ConfirmBox::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) { confirmed(); } else { BoxContent::keyPressEvent(e); } } void ConfirmBox::paintEvent(QPaintEvent *e) { BoxContent::paintEvent(e); Painter p(this); // draw box title / text p.setPen(st::boxTextFg); _text.drawLeftElided(p, st::boxPadding.left(), st::boxPadding.top(), _textWidth, width(), 16, style::al_left); } InformBox::InformBox(QWidget*, const QString &text, base::lambda<void()> closedCallback) : ConfirmBox(ConfirmBox::InformBoxTag(), text, lang(lng_box_ok), std::move(closedCallback)) { } InformBox::InformBox(QWidget*, const QString &text, const QString &doneText, base::lambda<void()> closedCallback) : ConfirmBox(ConfirmBox::InformBoxTag(), text, doneText, std::move(closedCallback)) { } MaxInviteBox::MaxInviteBox(QWidget*, not_null<ChannelData*> channel) : BoxContent() , _channel(channel) , _text(st::boxLabelStyle, lng_participant_invite_sorry(lt_count, Global::ChatSizeMax()), _confirmBoxTextOptions, st::boxWidth - st::boxPadding.left() - st::boxButtonPadding.right()) { } void MaxInviteBox::prepare() { setMouseTracking(true); addButton(langFactory(lng_box_ok), [this] { closeBox(); }); _textWidth = st::boxWidth - st::boxPadding.left() - st::boxButtonPadding.right(); _textHeight = qMin(_text.countHeight(_textWidth), 16 * st::boxLabelStyle.lineHeight); setDimensions(st::boxWidth, st::boxPadding.top() + _textHeight + st::boxTextFont->height + st::boxTextFont->height * 2 + st::newGroupLinkPadding.bottom()); subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(Notify::PeerUpdate::Flag::InviteLinkChanged, [this](const Notify::PeerUpdate &update) { if (update.peer == _channel) { rtlupdate(_invitationLink); } })); } void MaxInviteBox::mouseMoveEvent(QMouseEvent *e) { updateSelected(e->globalPos()); } void MaxInviteBox::mousePressEvent(QMouseEvent *e) { mouseMoveEvent(e); if (_linkOver) { if (_channel->inviteLink().isEmpty()) { Auth().api().exportInviteLink(_channel); } else { QGuiApplication::clipboard()->setText(_channel->inviteLink()); Ui::Toast::Show(lang(lng_create_channel_link_copied)); } } } void MaxInviteBox::leaveEventHook(QEvent *e) { updateSelected(QCursor::pos()); } void MaxInviteBox::updateSelected(const QPoint &cursorGlobalPosition) { QPoint p(mapFromGlobal(cursorGlobalPosition)); bool linkOver = _invitationLink.contains(p); if (linkOver != _linkOver) { _linkOver = linkOver; update(); setCursor(_linkOver ? style::cur_pointer : style::cur_default); } } void MaxInviteBox::paintEvent(QPaintEvent *e) { BoxContent::paintEvent(e); Painter p(this); // draw box title / text p.setPen(st::boxTextFg); _text.drawLeftElided(p, st::boxPadding.left(), st::boxPadding.top(), _textWidth, width(), 16, style::al_left); QTextOption option(style::al_left); option.setWrapMode(QTextOption::WrapAnywhere); p.setFont(_linkOver ? st::defaultInputField.font->underline() : st::defaultInputField.font); p.setPen(st::defaultLinkButton.color); auto inviteLinkText = _channel->inviteLink().isEmpty() ? lang(lng_group_invite_create) : _channel->inviteLink(); p.drawText(_invitationLink, inviteLinkText, option); } void MaxInviteBox::resizeEvent(QResizeEvent *e) { BoxContent::resizeEvent(e); _invitationLink = myrtlrect(st::boxPadding.left(), st::boxPadding.top() + _textHeight + st::boxTextFont->height, width() - st::boxPadding.left() - st::boxPadding.right(), 2 * st::boxTextFont->height); } ConvertToSupergroupBox::ConvertToSupergroupBox(QWidget*, ChatData *chat) : _chat(chat) , _text(100) , _note(100) { } void ConvertToSupergroupBox::prepare() { QStringList text; text.push_back(lang(lng_profile_convert_feature1)); text.push_back(lang(lng_profile_convert_feature2)); text.push_back(lang(lng_profile_convert_feature3)); text.push_back(lang(lng_profile_convert_feature4)); setTitle(langFactory(lng_profile_convert_title)); addButton(langFactory(lng_profile_convert_confirm), [this] { convertToSupergroup(); }); addButton(langFactory(lng_cancel), [this] { closeBox(); }); _text.setText(st::boxLabelStyle, text.join('\n'), _confirmBoxTextOptions); _note.setText(st::boxLabelStyle, lng_profile_convert_warning(lt_bold_start, textcmdStartSemibold(), lt_bold_end, textcmdStopSemibold()), _confirmBoxTextOptions); _textWidth = st::boxWideWidth - st::boxPadding.left() - st::boxButtonPadding.right(); _textHeight = _text.countHeight(_textWidth); setDimensions(st::boxWideWidth, _textHeight + st::boxPadding.bottom() + _note.countHeight(_textWidth)); } void ConvertToSupergroupBox::convertToSupergroup() { MTP::send(MTPmessages_MigrateChat(_chat->inputChat), rpcDone(&ConvertToSupergroupBox::convertDone), rpcFail(&ConvertToSupergroupBox::convertFail)); } void ConvertToSupergroupBox::convertDone(const MTPUpdates &updates) { Ui::hideLayer(); App::main()->sentUpdatesReceived(updates); auto handleChats = [](auto &mtpChats) { for_const (auto &mtpChat, mtpChats.v) { if (mtpChat.type() == mtpc_channel) { const auto channel = App::channel(mtpChat.c_channel().vid.v); Ui::showPeerHistory(channel, ShowAtUnreadMsgId); Auth().api().requestParticipantsCountDelayed(channel); } } }; switch (updates.type()) { case mtpc_updates: handleChats(updates.c_updates().vchats); break; case mtpc_updatesCombined: handleChats(updates.c_updatesCombined().vchats); break; default: LOG(("API Error: unexpected update cons %1 (ConvertToSupergroupBox::convertDone)").arg(updates.type())); break; } } bool ConvertToSupergroupBox::convertFail(const RPCError &error) { if (MTP::isDefaultHandledError(error)) return false; Ui::hideLayer(); return true; } void ConvertToSupergroupBox::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) { convertToSupergroup(); } else { BoxContent::keyPressEvent(e); } } void ConvertToSupergroupBox::paintEvent(QPaintEvent *e) { BoxContent::paintEvent(e); Painter p(this); // draw box title / text p.setPen(st::boxTextFg); _text.drawLeft(p, st::boxPadding.left(), 0, _textWidth, width()); _note.drawLeft(p, st::boxPadding.left(), _textHeight + st::boxPadding.bottom(), _textWidth, width()); } PinMessageBox::PinMessageBox(QWidget*, ChannelData *channel, MsgId msgId) : _channel(channel) , _msgId(msgId) , _text(this, lang(lng_pinned_pin_sure), Ui::FlatLabel::InitType::Simple, st::boxLabel) { } void PinMessageBox::prepare() { addButton(langFactory(lng_pinned_pin), [this] { pinMessage(); }); addButton(langFactory(lng_cancel), [this] { closeBox(); }); if (_channel->isMegagroup()) { _notify.create(this, lang(lng_pinned_notify), true, st::defaultBoxCheckbox); } auto height = st::boxPadding.top() + _text->height() + st::boxPadding.bottom(); if (_notify) { height += st::boxMediumSkip + _notify->heightNoMargins(); } setDimensions(st::boxWidth, height); } void PinMessageBox::resizeEvent(QResizeEvent *e) { BoxContent::resizeEvent(e); _text->moveToLeft(st::boxPadding.left(), st::boxPadding.top()); if (_notify) { _notify->moveToLeft(st::boxPadding.left(), _text->y() + _text->height() + st::boxMediumSkip); } } void PinMessageBox::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) { pinMessage(); } else { BoxContent::keyPressEvent(e); } } void PinMessageBox::pinMessage() { if (_requestId) return; auto flags = MTPchannels_UpdatePinnedMessage::Flags(0); if (_notify && !_notify->checked()) { flags |= MTPchannels_UpdatePinnedMessage::Flag::f_silent; } _requestId = MTP::send( MTPchannels_UpdatePinnedMessage( MTP_flags(flags), _channel->inputChannel, MTP_int(_msgId)), rpcDone(&PinMessageBox::pinDone), rpcFail(&PinMessageBox::pinFail)); } void PinMessageBox::pinDone(const MTPUpdates &updates) { if (App::main()) { App::main()->sentUpdatesReceived(updates); } Ui::hideLayer(); } bool PinMessageBox::pinFail(const RPCError &error) { if (MTP::isDefaultHandledError(error)) return false; Ui::hideLayer(); return true; } DeleteMessagesBox::DeleteMessagesBox( QWidget*, not_null<HistoryItem*> item, bool suggestModerateActions) : _ids(1, item->fullId()) , _singleItem(true) { if (suggestModerateActions) { _moderateBan = item->suggestBanReport(); _moderateDeleteAll = item->suggestDeleteAllReport(); if (_moderateBan || _moderateDeleteAll) { _moderateFrom = item->from()->asUser(); _moderateInChannel = item->history()->peer->asChannel(); } } } DeleteMessagesBox::DeleteMessagesBox( QWidget*, MessageIdsList &&selected) : _ids(std::move(selected)) { Expects(!_ids.empty()); } void DeleteMessagesBox::prepare() { auto text = QString(); if (_moderateFrom) { Assert(_moderateInChannel != nullptr); text = lang(lng_selected_delete_sure_this); if (_moderateBan) { _banUser.create(this, lang(lng_ban_user), false, st::defaultBoxCheckbox); } _reportSpam.create(this, lang(lng_report_spam), false, st::defaultBoxCheckbox); if (_moderateDeleteAll) { _deleteAll.create(this, lang(lng_delete_all_from), false, st::defaultBoxCheckbox); } } else { text = _singleItem ? lang(lng_selected_delete_sure_this) : lng_selected_delete_sure(lt_count, _ids.size()); auto canDeleteAllForEveryone = true; auto now = unixtime(); auto deleteForUser = (UserData*)nullptr; auto peer = (PeerData*)nullptr; auto forEveryoneText = lang(lng_delete_for_everyone_check); for (const auto fullId : std::as_const(_ids)) { if (const auto item = App::histItemById(fullId)) { peer = item->history()->peer; if (!item->canDeleteForEveryone(now)) { canDeleteAllForEveryone = false; break; } else if (auto user = item->history()->peer->asUser()) { if (!deleteForUser || deleteForUser == user) { deleteForUser = user; forEveryoneText = lng_delete_for_other_check( lt_user, user->firstName); } else { forEveryoneText = lang(lng_delete_for_everyone_check); } } } else { canDeleteAllForEveryone = false; } } auto count = int(_ids.size()); if (canDeleteAllForEveryone) { _forEveryone.create(this, forEveryoneText, false, st::defaultBoxCheckbox); } else if (peer && peer->isChannel()) { if (peer->isMegagroup()) { text += qsl("\n\n") + lng_delete_for_everyone_hint(lt_count, count); } } else if (peer->isChat()) { text += qsl("\n\n") + lng_delete_for_me_chat_hint(lt_count, count); } else if (!peer->isSelf()) { text += qsl("\n\n") + lng_delete_for_me_hint(lt_count, count); } } _text.create(this, text, Ui::FlatLabel::InitType::Simple, st::boxLabel); addButton(langFactory(lng_box_delete), [this] { deleteAndClear(); }); addButton(langFactory(lng_cancel), [this] { closeBox(); }); auto fullHeight = st::boxPadding.top() + _text->height() + st::boxPadding.bottom(); if (_moderateFrom) { fullHeight += st::boxMediumSkip; if (_banUser) { fullHeight += _banUser->heightNoMargins() + st::boxLittleSkip; } fullHeight += _reportSpam->heightNoMargins(); if (_deleteAll) { fullHeight += st::boxLittleSkip + _deleteAll->heightNoMargins(); } } else if (_forEveryone) { fullHeight += st::boxMediumSkip + _forEveryone->heightNoMargins(); } setDimensions(st::boxWidth, fullHeight); } void DeleteMessagesBox::resizeEvent(QResizeEvent *e) { BoxContent::resizeEvent(e); _text->moveToLeft(st::boxPadding.left(), st::boxPadding.top()); if (_moderateFrom) { auto top = _text->bottomNoMargins() + st::boxMediumSkip; if (_banUser) { _banUser->moveToLeft(st::boxPadding.left(), top); top += _banUser->heightNoMargins() + st::boxLittleSkip; } _reportSpam->moveToLeft(st::boxPadding.left(), top); top += _reportSpam->heightNoMargins() + st::boxLittleSkip; if (_deleteAll) { _deleteAll->moveToLeft(st::boxPadding.left(), top); } } else if (_forEveryone) { auto availableWidth = width() - 2 * st::boxPadding.left(); _forEveryone->resizeToNaturalWidth(availableWidth); _forEveryone->moveToLeft(st::boxPadding.left(), _text->bottomNoMargins() + st::boxMediumSkip); } } void DeleteMessagesBox::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) { deleteAndClear(); } else { BoxContent::keyPressEvent(e); } } void DeleteMessagesBox::deleteAndClear() { if (!App::main()) { return; } if (_moderateFrom) { if (_banUser && _banUser->checked()) { Auth().api().kickParticipant( _moderateInChannel, _moderateFrom, MTP_channelBannedRights(MTP_flags(0), MTP_int(0))); } if (_reportSpam->checked()) { MTP::send( MTPchannels_ReportSpam( _moderateInChannel->inputChannel, _moderateFrom->inputUser, MTP_vector<MTPint>(1, MTP_int(_ids[0].msg)))); } if (_deleteAll && _deleteAll->checked()) { Auth().api().deleteAllFromUser( _moderateInChannel, _moderateFrom); } } if (_deleteConfirmedCallback) { _deleteConfirmedCallback(); } QMap<PeerData*, QVector<MTPint>> idsByPeer; for (const auto itemId : _ids) { if (auto item = App::histItemById(itemId)) { auto history = item->history(); auto wasOnServer = (item->id > 0); auto wasLast = (history->lastMessage() == item); item->destroy(); if (wasOnServer) { idsByPeer[history->peer].push_back(MTP_int(itemId.msg)); } else if (wasLast && !history->lastMessageKnown()) { Auth().api().requestDialogEntry(history); } } } auto forEveryone = _forEveryone ? _forEveryone->checked() : false; for (auto i = idsByPeer.cbegin(), e = idsByPeer.cend(); i != e; ++i) { App::main()->deleteMessages(i.key(), i.value(), forEveryone); } Ui::hideLayer(); Auth().data().sendHistoryChangeNotifications(); } ConfirmInviteBox::ConfirmInviteBox(QWidget*, const QString &title, bool isChannel, const MTPChatPhoto &photo, int count, const QVector<UserData*> &participants) : _title(this, st::confirmInviteTitle) , _status(this, st::confirmInviteStatus) , _participants(participants) { _title->setText(title); QString status; if (_participants.isEmpty() || _participants.size() >= count) { if (count > 0) { status = lng_chat_status_members(lt_count, count); } else { status = lang(isChannel ? lng_channel_status : lng_group_status); } } else { status = lng_group_invite_members(lt_count, count); } _status->setText(status); if (photo.type() == mtpc_chatPhoto) { const auto &data = photo.c_chatPhoto(); const auto size = 160; const auto location = StorageImageLocation::FromMTP( size, size, data.vphoto_small); if (!location.isNull()) { _photo = ImagePtr(location); if (!_photo->loaded()) { subscribe(Auth().downloaderTaskFinished(), [this] { update(); }); _photo->load(); } } } if (!_photo) { _photoEmpty = std::make_unique<Ui::EmptyUserpic>( Data::PeerUserpicColor(0), title); } } void ConfirmInviteBox::prepare() { addButton(langFactory(lng_group_invite_join), [] { if (auto main = App::main()) { main->onInviteImport(); } }); addButton(langFactory(lng_cancel), [this] { closeBox(); }); if (_participants.size() > 4) { _participants.resize(4); } auto newHeight = st::confirmInviteStatusTop + _status->height() + st::boxPadding.bottom(); if (!_participants.isEmpty()) { int skip = (st::boxWideWidth - 4 * st::confirmInviteUserPhotoSize) / 5; int padding = skip / 2; _userWidth = (st::confirmInviteUserPhotoSize + 2 * padding); int sumWidth = _participants.size() * _userWidth; int left = (st::boxWideWidth - sumWidth) / 2; for_const (auto user, _participants) { auto name = new Ui::FlatLabel(this, st::confirmInviteUserName); name->resizeToWidth(st::confirmInviteUserPhotoSize + padding); name->setText(user->firstName.isEmpty() ? App::peerName(user) : user->firstName); name->moveToLeft(left + (padding / 2), st::confirmInviteUserNameTop); left += _userWidth; } newHeight += st::confirmInviteUserHeight; } setDimensions(st::boxWideWidth, newHeight); } void ConfirmInviteBox::resizeEvent(QResizeEvent *e) { BoxContent::resizeEvent(e); _title->move((width() - _title->width()) / 2, st::confirmInviteTitleTop); _status->move((width() - _status->width()) / 2, st::confirmInviteStatusTop); } void ConfirmInviteBox::paintEvent(QPaintEvent *e) { BoxContent::paintEvent(e); Painter p(this); if (_photo) { p.drawPixmap((width() - st::confirmInvitePhotoSize) / 2, st::confirmInvitePhotoTop, _photo->pixCircled(st::confirmInvitePhotoSize, st::confirmInvitePhotoSize)); } else { _photoEmpty->paint(p, (width() - st::confirmInvitePhotoSize) / 2, st::confirmInvitePhotoTop, width(), st::confirmInvitePhotoSize); } int sumWidth = _participants.size() * _userWidth; int left = (width() - sumWidth) / 2; for_const (auto user, _participants) { user->paintUserpicLeft(p, left + (_userWidth - st::confirmInviteUserPhotoSize) / 2, st::confirmInviteUserPhotoTop, width(), st::confirmInviteUserPhotoSize); left += _userWidth; } } ConfirmInviteBox::~ConfirmInviteBox() = default; ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/confirm_box.h��������������������������������������������0000664�0000000�0000000�00000014700�13262451251�0023223�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "boxes/abstract_box.h" namespace Ui { class Checkbox; class FlatLabel; class EmptyUserpic; } // namespace Ui class InformBox; class ConfirmBox : public BoxContent, public ClickHandlerHost { public: ConfirmBox(QWidget*, const QString &text, base::lambda_once<void()> confirmedCallback = base::lambda_once<void()>(), base::lambda_once<void()> cancelledCallback = base::lambda_once<void()>()); ConfirmBox(QWidget*, const QString &text, const QString &confirmText, base::lambda_once<void()> confirmedCallback = base::lambda_once<void()>(), base::lambda_once<void()> cancelledCallback = base::lambda_once<void()>()); ConfirmBox(QWidget*, const QString &text, const QString &confirmText, const style::RoundButton &confirmStyle, base::lambda_once<void()> confirmedCallback = base::lambda_once<void()>(), base::lambda_once<void()> cancelledCallback = base::lambda_once<void()>()); ConfirmBox(QWidget*, const QString &text, const QString &confirmText, const QString &cancelText, base::lambda_once<void()> confirmedCallback = base::lambda_once<void()>(), base::lambda_once<void()> cancelledCallback = base::lambda_once<void()>()); ConfirmBox(QWidget*, const QString &text, const QString &confirmText, const style::RoundButton &confirmStyle, const QString &cancelText, base::lambda_once<void()> confirmedCallback = base::lambda_once<void()>(), base::lambda_once<void()> cancelledCallback = base::lambda_once<void()>()); void updateLink(); // If strict cancel is set the cancelledCallback is only called if the cancel button was pressed. void setStrictCancel(bool strictCancel) { _strictCancel = strictCancel; } // ClickHandlerHost interface void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override; protected: void prepare() override; void keyPressEvent(QKeyEvent *e) override; void paintEvent(QPaintEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void leaveEventHook(QEvent *e) override; private: struct InformBoxTag { }; ConfirmBox(const InformBoxTag &, const QString &text, const QString &doneText, base::lambda<void()> closedCallback); base::lambda_once<void()> generateInformCallback(base::lambda<void()> closedCallback); friend class InformBox; void confirmed(); void init(const QString &text); void textUpdated(); QString _confirmText; QString _cancelText; const style::RoundButton &_confirmStyle; bool _informative = false; Text _text; int _textWidth = 0; int _textHeight = 0; void updateHover(); QPoint _lastMousePos; bool _confirmed = false; bool _cancelled = false; bool _strictCancel = false; base::lambda_once<void()> _confirmedCallback; base::lambda_once<void()> _cancelledCallback; }; class InformBox : public ConfirmBox { public: InformBox(QWidget*, const QString &text, base::lambda<void()> closedCallback = base::lambda<void()>()); InformBox(QWidget*, const QString &text, const QString &doneText, base::lambda<void()> closedCallback = base::lambda<void()>()); }; class MaxInviteBox : public BoxContent { public: MaxInviteBox(QWidget*, not_null<ChannelData*> channel); protected: void prepare() override; void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void leaveEventHook(QEvent *e) override; private: void updateSelected(const QPoint &cursorGlobalPosition); not_null<ChannelData*> _channel; Text _text; int32 _textWidth, _textHeight; QRect _invitationLink; bool _linkOver = false; QPoint _lastMousePos; }; class ConvertToSupergroupBox : public BoxContent, public RPCSender { public: ConvertToSupergroupBox(QWidget*, ChatData *chat); protected: void prepare() override; void keyPressEvent(QKeyEvent *e) override; void paintEvent(QPaintEvent *e) override; private: void convertToSupergroup(); void convertDone(const MTPUpdates &updates); bool convertFail(const RPCError &error); ChatData *_chat; Text _text, _note; int32 _textWidth, _textHeight; }; class PinMessageBox : public BoxContent, public RPCSender { public: PinMessageBox(QWidget*, ChannelData *channel, MsgId msgId); protected: void prepare() override; void resizeEvent(QResizeEvent *e) override; void keyPressEvent(QKeyEvent *e) override; private: void pinMessage(); void pinDone(const MTPUpdates &updates); bool pinFail(const RPCError &error); ChannelData *_channel; MsgId _msgId; object_ptr<Ui::FlatLabel> _text; object_ptr<Ui::Checkbox> _notify = { nullptr }; mtpRequestId _requestId = 0; }; class DeleteMessagesBox : public BoxContent, public RPCSender { public: DeleteMessagesBox( QWidget*, not_null<HistoryItem*> item, bool suggestModerateActions); DeleteMessagesBox(QWidget*, MessageIdsList &&selected); void setDeleteConfirmedCallback(base::lambda<void()> callback) { _deleteConfirmedCallback = std::move(callback); } protected: void prepare() override; void resizeEvent(QResizeEvent *e) override; void keyPressEvent(QKeyEvent *e) override; private: void deleteAndClear(); const MessageIdsList _ids; const bool _singleItem = false; UserData *_moderateFrom = nullptr; ChannelData *_moderateInChannel = nullptr; bool _moderateBan = false; bool _moderateDeleteAll = false; object_ptr<Ui::FlatLabel> _text = { nullptr }; object_ptr<Ui::Checkbox> _forEveryone = { nullptr }; object_ptr<Ui::Checkbox> _banUser = { nullptr }; object_ptr<Ui::Checkbox> _reportSpam = { nullptr }; object_ptr<Ui::Checkbox> _deleteAll = { nullptr }; base::lambda<void()> _deleteConfirmedCallback; }; class ConfirmInviteBox : public BoxContent, public RPCSender { public: ConfirmInviteBox(QWidget*, const QString &title, bool isChannel, const MTPChatPhoto &photo, int count, const QVector<UserData*> &participants); ~ConfirmInviteBox(); protected: void prepare() override; void resizeEvent(QResizeEvent *e) override; void paintEvent(QPaintEvent *e) override; private: object_ptr<Ui::FlatLabel> _title; object_ptr<Ui::FlatLabel> _status; ImagePtr _photo; std::unique_ptr<Ui::EmptyUserpic> _photoEmpty; QVector<UserData*> _participants; int _userWidth = 0; }; ����������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/confirm_phone_box.cpp������������������������������������0000664�0000000�0000000�00000022244�13262451251�0024751�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/confirm_phone_box.h" #include "styles/style_boxes.h" #include "boxes/confirm_box.h" #include "ui/widgets/buttons.h" #include "ui/widgets/input_fields.h" #include "ui/widgets/labels.h" #include "mainwidget.h" #include "lang/lang_keys.h" namespace { object_ptr<ConfirmPhoneBox> CurrentConfirmPhoneBox = { nullptr }; } // namespace void SentCodeField::fix() { if (_fixing) return; _fixing = true; auto newText = QString(); auto now = getLastText(); auto oldPos = textCursor().position(); auto newPos = -1; auto oldLen = now.size(); auto digitCount = 0; for_const (auto ch, now) { if (ch.isDigit()) { ++digitCount; } } if (_autoSubmitLength > 0 && digitCount > _autoSubmitLength) { digitCount = _autoSubmitLength; } auto strict = (_autoSubmitLength > 0 && digitCount == _autoSubmitLength); newText.reserve(oldLen); int i = 0; for_const (auto ch, now) { if (i++ == oldPos) { newPos = newText.length(); } if (ch.isDigit()) { if (!digitCount--) { break; } newText += ch; if (strict && !digitCount) { break; } } } if (newPos < 0) { newPos = newText.length(); } if (newText != now) { now = newText; setText(now); setCursorPosition(newPos); } _fixing = false; if (_changedCallback) { _changedCallback(); } if (strict && _submitCallback) { _submitCallback(); } } SentCodeCall::SentCodeCall(QObject *parent, base::lambda_once<void()> callCallback, base::lambda<void()> updateCallback) : _timer(parent) , _call(std::move(callCallback)) , _update(std::move(updateCallback)) { _timer->connect(_timer, &QTimer::timeout, [this] { if (_status.state == State::Waiting) { if (--_status.timeout <= 0) { _status.state = State::Calling; _timer->stop(); if (_call) { _call(); } } } if (_update) { _update(); } }); } void SentCodeCall::setStatus(const Status &status) { _status = status; if (_status.state == State::Waiting) { _timer->start(1000); } } QString SentCodeCall::getText() const { switch (_status.state) { case State::Waiting: { if (_status.timeout >= 3600) { return lng_code_call(lt_minutes, qsl("%1:%2").arg(_status.timeout / 3600).arg((_status.timeout / 60) % 60, 2, 10, QChar('0')), lt_seconds, qsl("%1").arg(_status.timeout % 60, 2, 10, QChar('0'))); } return lng_code_call(lt_minutes, QString::number(_status.timeout / 60), lt_seconds, qsl("%1").arg(_status.timeout % 60, 2, 10, QChar('0'))); } break; case State::Calling: return lang(lng_code_calling); case State::Called: return lang(lng_code_called); } return QString(); } void ConfirmPhoneBox::start(const QString &phone, const QString &hash) { if (CurrentConfirmPhoneBox && CurrentConfirmPhoneBox->getPhone() != phone) { CurrentConfirmPhoneBox.destroyDelayed(); } if (!CurrentConfirmPhoneBox) { CurrentConfirmPhoneBox = Box<ConfirmPhoneBox>(phone, hash); } CurrentConfirmPhoneBox->checkPhoneAndHash(); } ConfirmPhoneBox::ConfirmPhoneBox(QWidget*, const QString &phone, const QString &hash) : _phone(phone) , _hash(hash) , _call(this, [this] { sendCall(); }, [this] { update(); }) { } void ConfirmPhoneBox::sendCall() { MTP::send(MTPauth_ResendCode(MTP_string(_phone), MTP_string(_phoneHash)), rpcDone(&ConfirmPhoneBox::callDone)); } void ConfirmPhoneBox::checkPhoneAndHash() { if (_sendCodeRequestId) { return; } _sendCodeRequestId = MTP::send(MTPaccount_SendConfirmPhoneCode(MTP_flags(0), MTP_string(_hash), MTPBool()), rpcDone(&ConfirmPhoneBox::sendCodeDone), rpcFail(&ConfirmPhoneBox::sendCodeFail)); } void ConfirmPhoneBox::sendCodeDone(const MTPauth_SentCode &result) { Expects(result.type() == mtpc_auth_sentCode); _sendCodeRequestId = 0; auto &resultInner = result.c_auth_sentCode(); switch (resultInner.vtype.type()) { case mtpc_auth_sentCodeTypeApp: LOG(("Error: should not be in-app code!")); break; case mtpc_auth_sentCodeTypeSms: _sentCodeLength = resultInner.vtype.c_auth_sentCodeTypeSms().vlength.v; break; case mtpc_auth_sentCodeTypeCall: _sentCodeLength = resultInner.vtype.c_auth_sentCodeTypeCall().vlength.v; break; case mtpc_auth_sentCodeTypeFlashCall: LOG(("Error: should not be flashcall!")); break; } _phoneHash = qs(resultInner.vphone_code_hash); if (resultInner.has_next_type() && resultInner.vnext_type.type() == mtpc_auth_codeTypeCall) { _call.setStatus({ SentCodeCall::State::Waiting, resultInner.has_timeout() ? resultInner.vtimeout.v : 60 }); } launch(); } bool ConfirmPhoneBox::sendCodeFail(const RPCError &error) { auto errorText = Lang::Hard::ServerError(); if (MTP::isFloodError(error)) { errorText = lang(lng_flood_error); } else if (MTP::isDefaultHandledError(error)) { return false; } else if (error.code() == 400) { errorText = lang(lng_confirm_phone_link_invalid); } _sendCodeRequestId = 0; Ui::show(Box<InformBox>(errorText)); if (this == CurrentConfirmPhoneBox) { CurrentConfirmPhoneBox.destroyDelayed(); } else { deleteLater(); } return true; } void ConfirmPhoneBox::launch() { if (!CurrentConfirmPhoneBox) return; Ui::show(std::move(CurrentConfirmPhoneBox)); } void ConfirmPhoneBox::prepare() { _about.create(this, st::confirmPhoneAboutLabel); TextWithEntities aboutText; auto formattedPhone = App::formatPhone(_phone); aboutText.text = lng_confirm_phone_about(lt_phone, formattedPhone); auto phonePosition = aboutText.text.indexOf(formattedPhone); if (phonePosition >= 0) { aboutText.entities.push_back(EntityInText(EntityInTextBold, phonePosition, formattedPhone.size())); } _about->setMarkedText(aboutText); _code.create(this, st::confirmPhoneCodeField, langFactory(lng_code_ph)); _code->setAutoSubmit(_sentCodeLength, [this] { onSendCode(); }); _code->setChangedCallback([this] { showError(QString()); }); setTitle(langFactory(lng_confirm_phone_title)); addButton(langFactory(lng_confirm_phone_send), [this] { onSendCode(); }); addButton(langFactory(lng_cancel), [this] { closeBox(); }); setDimensions(st::boxWidth, st::usernamePadding.top() + _code->height() + st::usernameSkip + _about->height() + st::usernameSkip); connect(_code, SIGNAL(submitted(bool)), this, SLOT(onSendCode())); showChildren(); } void ConfirmPhoneBox::callDone(const MTPauth_SentCode &result) { _call.callDone(); } void ConfirmPhoneBox::onSendCode() { if (_sendCodeRequestId) { return; } auto code = _code->getLastText(); if (code.isEmpty()) { _code->showError(); return; } _code->setDisabled(true); setFocus(); showError(QString()); _sendCodeRequestId = MTP::send(MTPaccount_ConfirmPhone(MTP_string(_phoneHash), MTP_string(_code->getLastText())), rpcDone(&ConfirmPhoneBox::confirmDone), rpcFail(&ConfirmPhoneBox::confirmFail)); } void ConfirmPhoneBox::confirmDone(const MTPBool &result) { _sendCodeRequestId = 0; Ui::show(Box<InformBox>(lng_confirm_phone_success(lt_phone, App::formatPhone(_phone)))); } bool ConfirmPhoneBox::confirmFail(const RPCError &error) { auto errorText = Lang::Hard::ServerError(); if (MTP::isFloodError(error)) { errorText = lang(lng_flood_error); } else if (MTP::isDefaultHandledError(error)) { return false; } else { auto &errorType = error.type(); if (errorType == qstr("PHONE_CODE_EMPTY") || errorType == qstr("PHONE_CODE_INVALID")) { errorText = lang(lng_bad_code); } } _sendCodeRequestId = 0; _code->setDisabled(false); _code->setFocus(); showError(errorText); return true; } void ConfirmPhoneBox::showError(const QString &error) { _error = error; if (!_error.isEmpty()) { _code->showError(); } update(); } void ConfirmPhoneBox::paintEvent(QPaintEvent *e) { BoxContent::paintEvent(e); Painter p(this); p.setFont(st::boxTextFont); auto callText = _call.getText(); if (!callText.isEmpty()) { p.setPen(st::usernameDefaultFg); auto callTextRectLeft = st::usernamePadding.left(); auto callTextRectTop = _about->y() + _about->height(); auto callTextRectWidth = width() - 2 * st::usernamePadding.left(); auto callTextRect = QRect(callTextRectLeft, callTextRectTop, callTextRectWidth, st::usernameSkip); p.drawText(callTextRect, callText, style::al_left); } auto errorText = _error; if (errorText.isEmpty()) { p.setPen(st::usernameDefaultFg); errorText = lang(lng_confirm_phone_enter_code); } else { p.setPen(st::boxTextFgError); } auto errorTextRectLeft = st::usernamePadding.left(); auto errorTextRectTop = _code->y() + _code->height(); auto errorTextRectWidth = width() - 2 * st::usernamePadding.left(); auto errorTextRect = QRect(errorTextRectLeft, errorTextRectTop, errorTextRectWidth, st::usernameSkip); p.drawText(errorTextRect, errorText, style::al_left); } void ConfirmPhoneBox::resizeEvent(QResizeEvent *e) { BoxContent::resizeEvent(e); _code->resize(width() - st::usernamePadding.left() - st::usernamePadding.right(), _code->height()); _code->moveToLeft(st::usernamePadding.left(), st::usernamePadding.top()); _about->moveToLeft(st::usernamePadding.left(), _code->y() + _code->height() + st::usernameSkip); } void ConfirmPhoneBox::setInnerFocus() { _code->setFocusFast(); } ConfirmPhoneBox::~ConfirmPhoneBox() { if (_sendCodeRequestId) { MTP::cancel(_sendCodeRequestId); } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/confirm_phone_box.h��������������������������������������0000664�0000000�0000000�00000006354�13262451251�0024422�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "boxes/abstract_box.h" #include "ui/widgets/input_fields.h" namespace Ui { class InputField; class FlatLabel; } // namespace Ui class SentCodeField : public Ui::InputField { public: SentCodeField(QWidget *parent, const style::InputField &st, base::lambda<QString()> placeholderFactory = base::lambda<QString()>(), const QString &val = QString()) : Ui::InputField(parent, st, std::move(placeholderFactory), val) { connect(this, &Ui::InputField::changed, [this] { fix(); }); } void setAutoSubmit(int length, base::lambda<void()> submitCallback) { _autoSubmitLength = length; _submitCallback = std::move(submitCallback); } void setChangedCallback(base::lambda<void()> changedCallback) { _changedCallback = std::move(changedCallback); } private: void fix(); // Flag for not calling onTextChanged() recursively. bool _fixing = false; int _autoSubmitLength = 0; base::lambda<void()> _submitCallback; base::lambda<void()> _changedCallback; }; class SentCodeCall { public: SentCodeCall(QObject *parent, base::lambda_once<void()> callCallback, base::lambda<void()> updateCallback); enum class State { Waiting, Calling, Called, Disabled, }; struct Status { Status() { } Status(State state, int timeout) : state(state), timeout(timeout) { } State state = State::Disabled; int timeout = 0; }; void setStatus(const Status &status); void callDone() { if (_status.state == State::Calling) { _status.state = State::Called; if (_update) { _update(); } } } QString getText() const; private: Status _status; object_ptr<QTimer> _timer; base::lambda_once<void()> _call; base::lambda<void()> _update; }; class ConfirmPhoneBox : public BoxContent, public RPCSender { Q_OBJECT public: static void start(const QString &phone, const QString &hash); ~ConfirmPhoneBox(); private slots: void onSendCode(); protected: void prepare() override; void setInnerFocus() override; void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; private: ConfirmPhoneBox(QWidget*, const QString &phone, const QString &hash); friend class object_ptr<ConfirmPhoneBox>; void sendCall(); void checkPhoneAndHash(); void sendCodeDone(const MTPauth_SentCode &result); bool sendCodeFail(const RPCError &error); void callDone(const MTPauth_SentCode &result); void confirmDone(const MTPBool &result); bool confirmFail(const RPCError &error); QString getPhone() const { return _phone; } void launch(); void showError(const QString &error); mtpRequestId _sendCodeRequestId = 0; // _hash from the link for account.sendConfirmPhoneCode call. // _phoneHash from auth.sentCode for account.confirmPhone call. QString _phone, _hash; QString _phoneHash; // If we receive the code length, we autosubmit _code field when enough symbols is typed. int _sentCodeLength = 0; mtpRequestId _checkCodeRequestId = 0; object_ptr<Ui::FlatLabel> _about = { nullptr }; object_ptr<SentCodeField> _code = { nullptr }; QString _error; SentCodeCall _call; }; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/connection_box.cpp���������������������������������������0000664�0000000�0000000�00000034571�13262451251�0024270�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/connection_box.h" #include "data/data_photo.h" #include "data/data_document.h" #include "boxes/confirm_box.h" #include "lang/lang_keys.h" #include "storage/localstorage.h" #include "mainwidget.h" #include "mainwindow.h" #include "auth_session.h" #include "data/data_session.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/buttons.h" #include "ui/widgets/input_fields.h" #include "history/history_location_manager.h" #include "styles/style_boxes.h" void ConnectionBox::ShowApplyProxyConfirmation(const QMap<QString, QString> &fields) { auto server = fields.value(qsl("server")); auto port = fields.value(qsl("port")).toInt(); if (!server.isEmpty() && port != 0) { auto weakBox = std::make_shared<QPointer<ConfirmBox>>(nullptr); auto box = Ui::show(Box<ConfirmBox>(lng_sure_enable_socks(lt_server, server, lt_port, QString::number(port)), lang(lng_sure_enable), [fields, weakBox] { auto p = ProxyData(); p.host = fields.value(qsl("server")); p.user = fields.value(qsl("user")); p.password = fields.value(qsl("pass")); p.port = fields.value(qsl("port")).toInt(); Global::SetConnectionType(dbictTcpProxy); Global::SetLastProxyType(dbictTcpProxy); Global::SetConnectionProxy(p); Local::writeSettings(); Global::RefConnectionTypeChanged().notify(); MTP::restart(); reinitLocationManager(); reinitWebLoadManager(); if (*weakBox) (*weakBox)->closeBox(); }), LayerOption::KeepOther); *weakBox = box; } } ConnectionBox::ConnectionBox(QWidget *parent) : _hostInput(this, st::connectionHostInputField, langFactory(lng_connection_host_ph), Global::ConnectionProxy().host) , _portInput(this, st::connectionPortInputField, langFactory(lng_connection_port_ph), QString::number(Global::ConnectionProxy().port)) , _userInput(this, st::connectionUserInputField, langFactory(lng_connection_user_ph), Global::ConnectionProxy().user) , _passwordInput(this, st::connectionPasswordInputField, langFactory(lng_connection_password_ph), Global::ConnectionProxy().password) , _typeGroup(std::make_shared<Ui::RadioenumGroup<DBIConnectionType>>(Global::ConnectionType())) , _autoRadio(this, _typeGroup, dbictAuto, lang(lng_connection_auto_rb), st::defaultBoxCheckbox) , _httpProxyRadio(this, _typeGroup, dbictHttpProxy, lang(lng_connection_http_proxy_rb), st::defaultBoxCheckbox) , _tcpProxyRadio(this, _typeGroup, dbictTcpProxy, lang(lng_connection_tcp_proxy_rb), st::defaultBoxCheckbox) , _tryIPv6(this, lang(lng_connection_try_ipv6), Global::TryIPv6(), st::defaultBoxCheckbox) { } void ConnectionBox::prepare() { setTitle(langFactory(lng_connection_header)); addButton(langFactory(lng_connection_save), [this] { onSave(); }); addButton(langFactory(lng_cancel), [this] { closeBox(); }); _typeGroup->setChangedCallback([this](DBIConnectionType value) { typeChanged(value); }); connect(_hostInput, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); connect(_portInput, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); connect(_userInput, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); connect(_passwordInput, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); connect(_hostInput, SIGNAL(focused()), this, SLOT(onFieldFocus())); connect(_portInput, SIGNAL(focused()), this, SLOT(onFieldFocus())); connect(_userInput, SIGNAL(focused()), this, SLOT(onFieldFocus())); connect(_passwordInput, SIGNAL(focused()), this, SLOT(onFieldFocus())); updateControlsVisibility(); } bool ConnectionBox::badProxyValue() const { return (_hostInput->getLastText().isEmpty() || !_portInput->getLastText().toInt()); } void ConnectionBox::updateControlsVisibility() { auto newHeight = st::boxOptionListPadding.top() + _autoRadio->heightNoMargins() + st::boxOptionListSkip + _httpProxyRadio->heightNoMargins() + st::boxOptionListSkip + _tcpProxyRadio->heightNoMargins() + st::boxOptionListSkip + st::connectionIPv6Skip + _tryIPv6->heightNoMargins() + st::defaultCheckbox.margin.bottom() + st::boxOptionListPadding.bottom() + st::boxPadding.bottom(); if (_typeGroup->value() == dbictAuto && badProxyValue()) { _hostInput->hide(); _portInput->hide(); _userInput->hide(); _passwordInput->hide(); } else { newHeight += 2 * st::boxOptionInputSkip + 2 * _hostInput->height(); _hostInput->show(); _portInput->show(); _userInput->show(); _passwordInput->show(); } setDimensions(st::boxWidth, newHeight); updateControlsPosition(); } void ConnectionBox::setInnerFocus() { if (_typeGroup->value() == dbictAuto) { setFocus(); } else { _hostInput->setFocusFast(); } } void ConnectionBox::resizeEvent(QResizeEvent *e) { BoxContent::resizeEvent(e); updateControlsPosition(); } void ConnectionBox::updateControlsPosition() { auto type = _typeGroup->value(); _autoRadio->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _autoRadio->getMargins().top() + st::boxOptionListPadding.top()); _httpProxyRadio->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _autoRadio->bottomNoMargins() + st::boxOptionListSkip); auto inputy = 0; auto fieldsVisible = (type != dbictAuto) || (!badProxyValue() && Global::LastProxyType() != dbictAuto); auto fieldsBelowHttp = fieldsVisible && (type == dbictHttpProxy || (type == dbictAuto && Global::LastProxyType() == dbictHttpProxy)); auto fieldsBelowTcp = fieldsVisible && (type == dbictTcpProxy || (type == dbictAuto && Global::LastProxyType() == dbictTcpProxy)); if (fieldsBelowHttp) { inputy = _httpProxyRadio->bottomNoMargins() + st::boxOptionInputSkip; _tcpProxyRadio->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), inputy + st::boxOptionInputSkip + 2 * _hostInput->height() + st::boxOptionListSkip); } else { _tcpProxyRadio->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _httpProxyRadio->bottomNoMargins() + st::boxOptionListSkip); if (fieldsBelowTcp) { inputy = _tcpProxyRadio->bottomNoMargins() + st::boxOptionInputSkip; } } if (inputy) { _hostInput->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left() + st::defaultCheck.diameter + st::defaultBoxCheckbox.textPosition.x() - st::defaultInputField.textMargins.left(), inputy); _portInput->moveToRight(st::boxPadding.right(), inputy); _userInput->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left() + st::defaultCheck.diameter + st::defaultBoxCheckbox.textPosition.x() - st::defaultInputField.textMargins.left(), _hostInput->y() + _hostInput->height() + st::boxOptionInputSkip); _passwordInput->moveToRight(st::boxPadding.right(), _userInput->y()); } auto tryipv6y = (fieldsBelowTcp ? _userInput->bottomNoMargins() : _tcpProxyRadio->bottomNoMargins()) + st::boxOptionListSkip + st::connectionIPv6Skip; _tryIPv6->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), tryipv6y); } void ConnectionBox::typeChanged(DBIConnectionType type) { if (type == dbictAuto) { setFocus(); } updateControlsVisibility(); if (type != dbictAuto) { Global::SetLastProxyType(type); if (!_hostInput->hasFocus() && !_portInput->hasFocus() && !_userInput->hasFocus() && !_passwordInput->hasFocus()) { _hostInput->setFocusFast(); } if ((type == dbictHttpProxy) && !_portInput->getLastText().toInt()) { _portInput->setText(qsl("80")); _portInput->finishAnimating(); } } update(); } void ConnectionBox::onFieldFocus() { if (Global::LastProxyType() == dbictHttpProxy) { _typeGroup->setValue(dbictHttpProxy); } else if (Global::LastProxyType() == dbictTcpProxy) { _typeGroup->setValue(dbictTcpProxy); } } void ConnectionBox::onSubmit() { onFieldFocus(); if (_hostInput->hasFocus()) { if (!_hostInput->getLastText().trimmed().isEmpty()) { _portInput->setFocus(); } else { _hostInput->showError(); } } else if (_portInput->hasFocus()) { if (_portInput->getLastText().trimmed().toInt() > 0) { _userInput->setFocus(); } else { _portInput->showError(); } } else if (_userInput->hasFocus()) { _passwordInput->setFocus(); } else if (_passwordInput->hasFocus()) { if (_hostInput->getLastText().trimmed().isEmpty()) { _hostInput->setFocus(); _hostInput->showError(); } else if (_portInput->getLastText().trimmed().toInt() <= 0) { _portInput->setFocus(); _portInput->showError(); } else { onSave(); } } } void ConnectionBox::onSave() { auto p = ProxyData(); p.host = _hostInput->getLastText().trimmed(); p.user = _userInput->getLastText().trimmed(); p.password = _passwordInput->getLastText().trimmed(); p.port = _portInput->getLastText().toInt(); auto type = _typeGroup->value(); if (type == dbictAuto) { if (p.host.isEmpty() || !p.port) { p = ProxyData(); } #ifndef TDESKTOP_DISABLE_NETWORK_PROXY QNetworkProxyFactory::setUseSystemConfiguration(false); QNetworkProxyFactory::setUseSystemConfiguration(true); #endif // !TDESKTOP_DISABLE_NETWORK_PROXY } else { if (p.host.isEmpty()) { _hostInput->showError(); return; } else if (!p.port) { _portInput->showError(); return; } Global::SetLastProxyType(type); } Global::SetConnectionType(type); Global::SetConnectionProxy(p); if (cPlatform() == dbipWindows && Global::TryIPv6() != _tryIPv6->checked()) { Global::SetTryIPv6(_tryIPv6->checked()); Local::writeSettings(); Global::RefConnectionTypeChanged().notify(); App::restart(); } else { Global::SetTryIPv6(_tryIPv6->checked()); Local::writeSettings(); Global::RefConnectionTypeChanged().notify(); MTP::restart(); reinitLocationManager(); reinitWebLoadManager(); closeBox(); } } AutoDownloadBox::AutoDownloadBox(QWidget *parent) : _photoPrivate(this, lang(lng_media_auto_private_chats), !(cAutoDownloadPhoto() & dbiadNoPrivate), st::defaultBoxCheckbox) , _photoGroups(this, lang(lng_media_auto_groups), !(cAutoDownloadPhoto() & dbiadNoGroups), st::defaultBoxCheckbox) , _audioPrivate(this, lang(lng_media_auto_private_chats), !(cAutoDownloadAudio() & dbiadNoPrivate), st::defaultBoxCheckbox) , _audioGroups(this, lang(lng_media_auto_groups), !(cAutoDownloadAudio() & dbiadNoGroups), st::defaultBoxCheckbox) , _gifPrivate(this, lang(lng_media_auto_private_chats), !(cAutoDownloadGif() & dbiadNoPrivate), st::defaultBoxCheckbox) , _gifGroups(this, lang(lng_media_auto_groups), !(cAutoDownloadGif() & dbiadNoGroups), st::defaultBoxCheckbox) , _gifPlay(this, lang(lng_media_auto_play), cAutoPlayGif(), st::defaultBoxCheckbox) , _sectionHeight(st::boxTitleHeight + 2 * (st::defaultCheck.diameter + st::setLittleSkip)) { } void AutoDownloadBox::prepare() { addButton(langFactory(lng_connection_save), [this] { onSave(); }); addButton(langFactory(lng_cancel), [this] { closeBox(); }); setDimensions(st::boxWidth, 3 * _sectionHeight - st::autoDownloadTopDelta + st::setLittleSkip + _gifPlay->heightNoMargins() + st::setLittleSkip); } void AutoDownloadBox::paintEvent(QPaintEvent *e) { BoxContent::paintEvent(e); Painter p(this); p.setPen(st::boxTitleFg); p.setFont(st::autoDownloadTitleFont); p.drawTextLeft(st::autoDownloadTitlePosition.x(), st::autoDownloadTitlePosition.y(), width(), lang(lng_media_auto_photo)); p.drawTextLeft(st::autoDownloadTitlePosition.x(), _sectionHeight + st::autoDownloadTitlePosition.y(), width(), lang(lng_media_auto_audio)); p.drawTextLeft(st::autoDownloadTitlePosition.x(), 2 * _sectionHeight + st::autoDownloadTitlePosition.y(), width(), lang(lng_media_auto_gif)); } void AutoDownloadBox::resizeEvent(QResizeEvent *e) { BoxContent::resizeEvent(e); auto top = st::boxTitleHeight - st::autoDownloadTopDelta; _photoPrivate->moveToLeft(st::boxTitlePosition.x(), top + st::setLittleSkip); _photoGroups->moveToLeft(st::boxTitlePosition.x(), _photoPrivate->bottomNoMargins() + st::setLittleSkip); _audioPrivate->moveToLeft(st::boxTitlePosition.x(), _sectionHeight + top + st::setLittleSkip); _audioGroups->moveToLeft(st::boxTitlePosition.x(), _audioPrivate->bottomNoMargins() + st::setLittleSkip); _gifPrivate->moveToLeft(st::boxTitlePosition.x(), 2 * _sectionHeight + top + st::setLittleSkip); _gifGroups->moveToLeft(st::boxTitlePosition.x(), _gifPrivate->bottomNoMargins() + st::setLittleSkip); _gifPlay->moveToLeft(st::boxTitlePosition.x(), _gifGroups->bottomNoMargins() + st::setLittleSkip); } void AutoDownloadBox::onSave() { auto photosChanged = false; auto documentsChanged = false; auto autoplayChanged = false; auto photosEnabled = false; auto voiceEnabled = false; auto animationsEnabled = false; auto autoDownloadPhoto = (_photoPrivate->checked() ? 0 : dbiadNoPrivate) | (_photoGroups->checked() ? 0 : dbiadNoGroups); if (cAutoDownloadPhoto() != autoDownloadPhoto) { const auto enabledPrivate = (cAutoDownloadPhoto() & dbiadNoPrivate) && !(autoDownloadPhoto & dbiadNoPrivate); const auto enabledGroups = (cAutoDownloadPhoto() & dbiadNoGroups) && !(autoDownloadPhoto & dbiadNoGroups); photosEnabled = enabledPrivate || enabledGroups; photosChanged = true; cSetAutoDownloadPhoto(autoDownloadPhoto); } auto autoDownloadAudio = (_audioPrivate->checked() ? 0 : dbiadNoPrivate) | (_audioGroups->checked() ? 0 : dbiadNoGroups); if (cAutoDownloadAudio() != autoDownloadAudio) { const auto enabledPrivate = (cAutoDownloadAudio() & dbiadNoPrivate) && !(autoDownloadAudio & dbiadNoPrivate); const auto enabledGroups = (cAutoDownloadAudio() & dbiadNoGroups) && !(autoDownloadAudio & dbiadNoGroups); voiceEnabled = enabledPrivate || enabledGroups; documentsChanged = true; cSetAutoDownloadAudio(autoDownloadAudio); } auto autoDownloadGif = (_gifPrivate->checked() ? 0 : dbiadNoPrivate) | (_gifGroups->checked() ? 0 : dbiadNoGroups); if (cAutoDownloadGif() != autoDownloadGif) { const auto enabledPrivate = (cAutoDownloadGif() & dbiadNoPrivate) && !(autoDownloadGif & dbiadNoPrivate); const auto enabledGroups = (cAutoDownloadGif() & dbiadNoGroups) && !(autoDownloadGif & dbiadNoGroups); animationsEnabled = enabledPrivate || enabledGroups; documentsChanged = true; cSetAutoDownloadGif(autoDownloadGif); } if (cAutoPlayGif() != _gifPlay->checked()) { cSetAutoPlayGif(_gifPlay->checked()); if (!cAutoPlayGif()) { Auth().data().stopAutoplayAnimations(); } autoplayChanged = true; } if (photosChanged || documentsChanged || autoplayChanged) { Local::writeUserSettings(); } if (photosEnabled) { Auth().data().photoLoadSettingsChanged(); } if (voiceEnabled) { Auth().data().voiceLoadSettingsChanged(); } if (animationsEnabled) { Auth().data().animationLoadSettingsChanged(); } closeBox(); } ���������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/connection_box.h�����������������������������������������0000664�0000000�0000000�00000003747�13262451251�0023736�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "boxes/abstract_box.h" namespace Ui { class InputField; class PortInput; class PasswordInput; class Checkbox; template <typename Enum> class RadioenumGroup; template <typename Enum> class Radioenum; } // namespace Ui class ConnectionBox : public BoxContent { Q_OBJECT public: ConnectionBox(QWidget *parent); static void ShowApplyProxyConfirmation(const QMap<QString, QString> &fields); protected: void prepare() override; void setInnerFocus() override; void resizeEvent(QResizeEvent *e) override; private slots: void onSubmit(); void onFieldFocus(); void onSave(); private: void typeChanged(DBIConnectionType type); void updateControlsVisibility(); void updateControlsPosition(); bool badProxyValue() const; object_ptr<Ui::InputField> _hostInput; object_ptr<Ui::PortInput> _portInput; object_ptr<Ui::InputField> _userInput; object_ptr<Ui::PasswordInput> _passwordInput; std::shared_ptr<Ui::RadioenumGroup<DBIConnectionType>> _typeGroup; object_ptr<Ui::Radioenum<DBIConnectionType>> _autoRadio; object_ptr<Ui::Radioenum<DBIConnectionType>> _httpProxyRadio; object_ptr<Ui::Radioenum<DBIConnectionType>> _tcpProxyRadio; object_ptr<Ui::Checkbox> _tryIPv6; }; class AutoDownloadBox : public BoxContent { Q_OBJECT public: AutoDownloadBox(QWidget *parent); protected: void prepare() override; void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; private slots: void onSave(); private: object_ptr<Ui::Checkbox> _photoPrivate; object_ptr<Ui::Checkbox> _photoGroups; object_ptr<Ui::Checkbox> _audioPrivate; object_ptr<Ui::Checkbox> _audioGroups; object_ptr<Ui::Checkbox> _gifPrivate; object_ptr<Ui::Checkbox> _gifGroups; object_ptr<Ui::Checkbox> _gifPlay; int _sectionHeight = 0; }; �������������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/download_path_box.cpp������������������������������������0000664�0000000�0000000�00000011136�13262451251�0024744�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/download_path_box.h" #include "lang/lang_keys.h" #include "storage/localstorage.h" #include "core/file_utilities.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/buttons.h" #include "platform/platform_specific.h" #include "styles/style_boxes.h" DownloadPathBox::DownloadPathBox(QWidget *parent) : _path(Global::DownloadPath()) , _pathBookmark(Global::DownloadPathBookmark()) , _group(std::make_shared<Ui::RadioenumGroup<Directory>>(typeFromPath(_path))) , _default(this, _group, Directory::Downloads, lang(lng_download_path_default_radio), st::defaultBoxCheckbox) , _temp(this, _group, Directory::Temp, lang(lng_download_path_temp_radio), st::defaultBoxCheckbox) , _dir(this, _group, Directory::Custom, lang(lng_download_path_dir_radio), st::defaultBoxCheckbox) , _pathLink(this, QString(), st::boxLinkButton) { } void DownloadPathBox::prepare() { addButton(langFactory(lng_connection_save), [this] { save(); }); addButton(langFactory(lng_cancel), [this] { closeBox(); }); setTitle(langFactory(lng_download_path_header)); _group->setChangedCallback([this](Directory value) { radioChanged(value); }); connect(_pathLink, SIGNAL(clicked()), this, SLOT(onEditPath())); if (!_path.isEmpty() && _path != qsl("tmp")) { setPathText(QDir::toNativeSeparators(_path)); } updateControlsVisibility(); } void DownloadPathBox::updateControlsVisibility() { auto custom = (_group->value() == Directory::Custom); _pathLink->setVisible(custom); auto newHeight = st::boxOptionListPadding.top() + _default->getMargins().top() + _default->heightNoMargins() + st::boxOptionListSkip + _temp->heightNoMargins() + st::boxOptionListSkip + _dir->heightNoMargins(); if (custom) { newHeight += st::downloadPathSkip + _pathLink->height(); } newHeight += st::boxOptionListPadding.bottom() + _dir->getMargins().bottom(); setDimensions(st::boxWideWidth, newHeight); } void DownloadPathBox::resizeEvent(QResizeEvent *e) { BoxContent::resizeEvent(e); _default->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), st::boxOptionListPadding.top() + _default->getMargins().top()); _temp->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _default->bottomNoMargins() + st::boxOptionListSkip); _dir->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _temp->bottomNoMargins() + st::boxOptionListSkip); auto inputx = st::boxPadding.left() + st::boxOptionListPadding.left() + st::defaultCheck.diameter + st::defaultBoxCheckbox.textPosition.x(); auto inputy = _dir->bottomNoMargins() + st::downloadPathSkip; _pathLink->moveToLeft(inputx, inputy); } void DownloadPathBox::radioChanged(Directory value) { if (value == Directory::Custom) { if (_path.isEmpty() || _path == qsl("tmp")) { _group->setValue(_path.isEmpty() ? Directory::Downloads : Directory::Temp); onEditPath(); } else { setPathText(QDir::toNativeSeparators(_path)); } } else if (value == Directory::Temp) { _path = qsl("tmp"); } else { _path = QString(); } updateControlsVisibility(); update(); } void DownloadPathBox::onEditPath() { auto initialPath = [] { if (!Global::DownloadPath().isEmpty() && Global::DownloadPath() != qstr("tmp")) { return Global::DownloadPath().left(Global::DownloadPath().size() - (Global::DownloadPath().endsWith('/') ? 1 : 0)); } return QString(); }; FileDialog::GetFolder(lang(lng_download_path_choose), initialPath(), base::lambda_guarded(this, [this](const QString &result) { if (!result.isEmpty()) { _path = result + '/'; _pathBookmark = psDownloadPathBookmark(_path); setPathText(QDir::toNativeSeparators(_path)); _group->setValue(Directory::Custom); } })); } void DownloadPathBox::save() { #ifndef OS_WIN_STORE auto value = _group->value(); auto computePath = [this, value] { if (value == Directory::Custom) { return _path; } else if (value == Directory::Temp) { return qsl("tmp"); } return QString(); }; Global::SetDownloadPath(computePath()); Global::SetDownloadPathBookmark((value == Directory::Custom) ? _pathBookmark : QByteArray()); Local::writeUserSettings(); Global::RefDownloadPathChanged().notify(); closeBox(); #endif // OS_WIN_STORE } void DownloadPathBox::setPathText(const QString &text) { auto availw = st::boxWideWidth - st::boxPadding.left() - st::defaultCheck.diameter - st::defaultBoxCheckbox.textPosition.x() - st::boxPadding.right(); _pathLink->setText(st::boxTextFont->elided(text, availw)); } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/download_path_box.h��������������������������������������0000664�0000000�0000000�00000002505�13262451251�0024411�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "boxes/abstract_box.h" #include "base/observer.h" namespace Ui { template <typename Enum> class RadioenumGroup; template <typename Enum> class Radioenum; class LinkButton; } // namespace Ui class DownloadPathBox : public BoxContent { Q_OBJECT public: DownloadPathBox(QWidget *parent); protected: void prepare() override; void resizeEvent(QResizeEvent *e) override; private slots: void onEditPath(); private: enum class Directory { Downloads, Temp, Custom, }; void radioChanged(Directory value); Directory typeFromPath(const QString &path) { if (path.isEmpty()) { return Directory::Downloads; } else if (path == qsl("tmp")) { return Directory::Temp; } return Directory::Custom; } void save(); void updateControlsVisibility(); void setPathText(const QString &text); QString _path; QByteArray _pathBookmark; std::shared_ptr<Ui::RadioenumGroup<Directory>> _group; object_ptr<Ui::Radioenum<Directory>> _default; object_ptr<Ui::Radioenum<Directory>> _temp; object_ptr<Ui::Radioenum<Directory>> _dir; object_ptr<Ui::LinkButton> _pathLink; }; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/edit_caption_box.cpp�������������������������������������0000664�0000000�0000000�00000027452�13262451251�0024573�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/edit_caption_box.h" #include "ui/widgets/input_fields.h" #include "ui/text_options.h" #include "media/media_clip_reader.h" #include "history/history.h" #include "history/history_item.h" #include "data/data_media_types.h" #include "data/data_photo.h" #include "data/data_document.h" #include "lang/lang_keys.h" #include "window/window_controller.h" #include "mainwidget.h" #include "layout.h" #include "styles/style_history.h" #include "styles/style_boxes.h" EditCaptionBox::EditCaptionBox( QWidget*, not_null<HistoryItem*> item) : _msgId(item->fullId()) { Expects(item->media() != nullptr); Expects(item->media()->allowsEditCaption()); QSize dimensions; ImagePtr image; DocumentData *doc = nullptr; const auto media = item->media(); if (const auto photo = media->photo()) { _photo = true; dimensions = QSize(photo->full->width(), photo->full->height()); image = photo->full; } else if (const auto document = media->document()) { dimensions = document->dimensions; image = document->thumb; if (document->isAnimation()) { _animated = true; } else if (document->isVideoFile()) { _animated = true; } else { _doc = true; } doc = document; } auto caption = item->originalText().text; if (!_animated && (dimensions.isEmpty() || doc || image->isNull())) { if (image->isNull()) { _thumbw = 0; } else { int32 tw = image->width(), th = image->height(); if (tw > th) { _thumbw = (tw * st::msgFileThumbSize) / th; } else { _thumbw = st::msgFileThumbSize; } auto options = Images::Option::Smooth | Images::Option::RoundedSmall | Images::Option::RoundedTopLeft | Images::Option::RoundedTopRight | Images::Option::RoundedBottomLeft | Images::Option::RoundedBottomRight; _thumb = Images::pixmap(image->pix().toImage(), _thumbw * cIntRetinaFactor(), 0, options, st::msgFileThumbSize, st::msgFileThumbSize); } if (doc) { auto nameString = doc->isVoiceMessage() ? lang(lng_media_audio) : doc->composeNameString(); _name.setText( st::semiboldTextStyle, nameString, Ui::NameTextOptions()); _status = formatSizeText(doc->size); _statusw = qMax( _name.maxWidth(), st::normalFont->width(_status)); _isImage = doc->isImage(); _isAudio = (doc->isVoiceMessage() || doc->isAudioFile()); } } else { int32 maxW = 0, maxH = 0; if (_animated) { int32 limitW = st::sendMediaPreviewSize; int32 limitH = st::confirmMaxHeight; maxW = qMax(dimensions.width(), 1); maxH = qMax(dimensions.height(), 1); if (maxW * limitH > maxH * limitW) { if (maxW < limitW) { maxH = maxH * limitW / maxW; maxW = limitW; } } else { if (maxH < limitH) { maxW = maxW * limitH / maxH; maxH = limitH; } } _thumb = image->pixNoCache(maxW * cIntRetinaFactor(), maxH * cIntRetinaFactor(), Images::Option::Smooth | Images::Option::Blurred, maxW, maxH); prepareGifPreview(doc); } else { maxW = dimensions.width(); maxH = dimensions.height(); _thumb = image->pixNoCache(maxW * cIntRetinaFactor(), maxH * cIntRetinaFactor(), Images::Option::Smooth, maxW, maxH); } int32 tw = _thumb.width(), th = _thumb.height(); if (!tw || !th) { tw = th = 1; } _thumbw = st::sendMediaPreviewSize; if (_thumb.width() < _thumbw) { _thumbw = (_thumb.width() > 20) ? _thumb.width() : 20; } int32 maxthumbh = qMin(qRound(1.5 * _thumbw), int(st::confirmMaxHeight)); _thumbh = qRound(th * float64(_thumbw) / tw); if (_thumbh > maxthumbh) { _thumbw = qRound(_thumbw * float64(maxthumbh) / _thumbh); _thumbh = maxthumbh; if (_thumbw < 10) { _thumbw = 10; } } _thumbx = (st::boxWideWidth - _thumbw) / 2; _thumb = App::pixmapFromImageInPlace(_thumb.toImage().scaled(_thumbw * cIntRetinaFactor(), _thumbh * cIntRetinaFactor(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); _thumb.setDevicePixelRatio(cRetinaFactor()); } Assert(_animated || _photo || _doc); _field.create(this, st::confirmCaptionArea, langFactory(lng_photo_caption), caption); _field->setMaxLength(MaxPhotoCaption); _field->setCtrlEnterSubmit(Ui::CtrlEnterSubmit::Both); } void EditCaptionBox::prepareGifPreview(DocumentData *document) { auto createGifPreview = [document] { return (document && document->isAnimation()); }; auto createGifPreviewResult = createGifPreview(); // Clang freeze workaround. if (createGifPreviewResult) { _gifPreview = Media::Clip::MakeReader(document, _msgId, [this](Media::Clip::Notification notification) { clipCallback(notification); }); if (_gifPreview) _gifPreview->setAutoplay(); } } void EditCaptionBox::clipCallback(Media::Clip::Notification notification) { using namespace Media::Clip; switch (notification) { case NotificationReinit: { if (_gifPreview && _gifPreview->state() == State::Error) { _gifPreview.setBad(); } if (_gifPreview && _gifPreview->ready() && !_gifPreview->started()) { auto s = QSize(_thumbw, _thumbh); _gifPreview->start(s.width(), s.height(), s.width(), s.height(), ImageRoundRadius::None, RectPart::None); } update(); } break; case NotificationRepaint: { if (_gifPreview && !_gifPreview->currentDisplayed()) { update(); } } break; } } void EditCaptionBox::prepare() { addButton(langFactory(lng_settings_save), [this] { save(); }); addButton(langFactory(lng_cancel), [this] { closeBox(); }); updateBoxSize(); connect(_field, &Ui::InputArea::submitted, this, [this] { save(); }); connect(_field, &Ui::InputArea::cancelled, this, [this] { closeBox(); }); connect(_field, &Ui::InputArea::resized, this, [this] { captionResized(); }); auto cursor = _field->textCursor(); cursor.movePosition(QTextCursor::End); _field->setTextCursor(cursor); } void EditCaptionBox::captionResized() { updateBoxSize(); resizeEvent(0); update(); } void EditCaptionBox::updateBoxSize() { auto newHeight = st::boxPhotoPadding.top() + st::boxPhotoCaptionSkip + _field->height() + errorTopSkip() + st::normalFont->height; if (_photo || _animated) { newHeight += _thumbh; } else if (_thumbw) { newHeight += 0 + st::msgFileThumbSize + 0; } else if (_doc) { newHeight += 0 + st::msgFileSize + 0; } else { newHeight += st::boxTitleFont->height; } setDimensions(st::boxWideWidth, newHeight); } int EditCaptionBox::errorTopSkip() const { return (st::boxButtonPadding.top() / 2); } void EditCaptionBox::paintEvent(QPaintEvent *e) { BoxContent::paintEvent(e); Painter p(this); if (_photo || _animated) { if (_thumbx > st::boxPhotoPadding.left()) { p.fillRect(st::boxPhotoPadding.left(), st::boxPhotoPadding.top(), _thumbx - st::boxPhotoPadding.left(), _thumbh, st::confirmBg); } if (_thumbx + _thumbw < width() - st::boxPhotoPadding.right()) { p.fillRect(_thumbx + _thumbw, st::boxPhotoPadding.top(), width() - st::boxPhotoPadding.right() - _thumbx - _thumbw, _thumbh, st::confirmBg); } if (_gifPreview && _gifPreview->started()) { auto s = QSize(_thumbw, _thumbh); auto paused = controller()->isGifPausedAtLeastFor(Window::GifPauseReason::Layer); auto frame = _gifPreview->current(s.width(), s.height(), s.width(), s.height(), ImageRoundRadius::None, RectPart::None, paused ? 0 : getms()); p.drawPixmap(_thumbx, st::boxPhotoPadding.top(), frame); } else { p.drawPixmap(_thumbx, st::boxPhotoPadding.top(), _thumb); } if (_animated && !_gifPreview) { QRect inner(_thumbx + (_thumbw - st::msgFileSize) / 2, st::boxPhotoPadding.top() + (_thumbh - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize); p.setPen(Qt::NoPen); p.setBrush(st::msgDateImgBg); { PainterHighQualityEnabler hq(p); p.drawEllipse(inner); } auto icon = &st::historyFileInPlay; icon->paintInCenter(p, inner); } } else if (_doc) { int32 w = width() - st::boxPhotoPadding.left() - st::boxPhotoPadding.right(); int32 h = _thumbw ? (0 + st::msgFileThumbSize + 0) : (0 + st::msgFileSize + 0); int32 nameleft = 0, nametop = 0, nameright = 0, statustop = 0; if (_thumbw) { nameleft = 0 + st::msgFileThumbSize + st::msgFileThumbPadding.right(); nametop = st::msgFileThumbNameTop - st::msgFileThumbPadding.top(); nameright = 0; statustop = st::msgFileThumbStatusTop - st::msgFileThumbPadding.top(); } else { nameleft = 0 + st::msgFileSize + st::msgFilePadding.right(); nametop = st::msgFileNameTop - st::msgFilePadding.top(); nameright = 0; statustop = st::msgFileStatusTop - st::msgFilePadding.top(); } int32 namewidth = w - nameleft - 0; if (namewidth > _statusw) { //w -= (namewidth - _statusw); //namewidth = _statusw; } int32 x = (width() - w) / 2, y = st::boxPhotoPadding.top(); // App::roundRect(p, x, y, w, h, st::msgInBg, MessageInCorners, &st::msgInShadow); if (_thumbw) { QRect rthumb(rtlrect(x + 0, y + 0, st::msgFileThumbSize, st::msgFileThumbSize, width())); p.drawPixmap(rthumb.topLeft(), _thumb); } else { QRect inner(rtlrect(x + 0, y + 0, st::msgFileSize, st::msgFileSize, width())); p.setPen(Qt::NoPen); p.setBrush(st::msgFileInBg); { PainterHighQualityEnabler hq(p); p.drawEllipse(inner); } auto icon = &(_isAudio ? st::historyFileInPlay : _isImage ? st::historyFileInImage : st::historyFileInDocument); icon->paintInCenter(p, inner); } p.setFont(st::semiboldFont); p.setPen(st::historyFileNameInFg); _name.drawLeftElided(p, x + nameleft, y + nametop, namewidth, width()); auto &status = st::mediaInFg; p.setFont(st::normalFont); p.setPen(status); p.drawTextLeft(x + nameleft, y + statustop, width(), _status); } else { p.setFont(st::boxTitleFont); p.setPen(st::boxTextFg); p.drawTextLeft(_field->x(), st::boxPhotoPadding.top(), width(), lang(lng_edit_message)); } if (!_error.isEmpty()) { p.setFont(st::normalFont); p.setPen(st::boxTextFgError); p.drawTextLeft(_field->x(), _field->y() + _field->height() + errorTopSkip(), width(), _error); } } void EditCaptionBox::resizeEvent(QResizeEvent *e) { BoxContent::resizeEvent(e); _field->resize(st::sendMediaPreviewSize, _field->height()); _field->moveToLeft(st::boxPhotoPadding.left(), height() - st::normalFont->height - errorTopSkip() - _field->height()); } void EditCaptionBox::setInnerFocus() { _field->setFocusFast(); } void EditCaptionBox::save() { if (_saveRequestId) return; auto item = App::histItemById(_msgId); if (!item) { _error = lang(lng_edit_deleted); update(); return; } auto flags = MTPmessages_EditMessage::Flag::f_message | 0; if (_previewCancelled) { flags |= MTPmessages_EditMessage::Flag::f_no_webpage; } MTPVector<MTPMessageEntity> sentEntities; if (!sentEntities.v.isEmpty()) { flags |= MTPmessages_EditMessage::Flag::f_entities; } auto text = TextUtilities::PrepareForSending(_field->getLastText(), TextUtilities::PrepareTextOption::CheckLinks); _saveRequestId = MTP::send( MTPmessages_EditMessage( MTP_flags(flags), item->history()->peer->input, MTP_int(item->id), MTP_string(text), MTPnullMarkup, sentEntities, MTP_inputGeoPointEmpty()), rpcDone(&EditCaptionBox::saveDone), rpcFail(&EditCaptionBox::saveFail)); } void EditCaptionBox::saveDone(const MTPUpdates &updates) { _saveRequestId = 0; closeBox(); if (App::main()) { App::main()->sentUpdatesReceived(updates); } } bool EditCaptionBox::saveFail(const RPCError &error) { if (MTP::isDefaultHandledError(error)) return false; _saveRequestId = 0; QString err = error.type(); if (err == qstr("MESSAGE_ID_INVALID") || err == qstr("CHAT_ADMIN_REQUIRED") || err == qstr("MESSAGE_EDIT_TIME_EXPIRED")) { _error = lang(lng_edit_error); } else if (err == qstr("MESSAGE_NOT_MODIFIED")) { closeBox(); return true; } else if (err == qstr("MESSAGE_EMPTY")) { _field->setFocus(); _field->showError(); } else { _error = lang(lng_edit_error); } update(); return true; } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/edit_caption_box.h���������������������������������������0000664�0000000�0000000�00000002574�13262451251�0024236�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "boxes/abstract_box.h" namespace Data { class Media; } // namespace Data namespace Ui { class InputArea; } // namespace Ui class EditCaptionBox : public BoxContent, public RPCSender { public: EditCaptionBox(QWidget*, not_null<HistoryItem*> item); protected: void prepare() override; void setInnerFocus() override; void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; private: void updateBoxSize(); void prepareGifPreview(DocumentData *document); void clipCallback(Media::Clip::Notification notification); void save(); void captionResized(); void saveDone(const MTPUpdates &updates); bool saveFail(const RPCError &error); int errorTopSkip() const; FullMsgId _msgId; bool _animated = false; bool _photo = false; bool _doc = false; QPixmap _thumb; Media::Clip::ReaderPointer _gifPreview; object_ptr<Ui::InputArea> _field = { nullptr }; int _thumbx = 0; int _thumbw = 0; int _thumbh = 0; Text _name; QString _status; int _statusw = 0; bool _isAudio = false; bool _isImage = false; bool _previewCancelled = false; mtpRequestId _saveRequestId = 0; QString _error; }; ������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/edit_color_box.cpp���������������������������������������0000664�0000000�0000000�00000065612�13262451251�0024254�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/edit_color_box.h" #include "lang/lang_keys.h" #include "styles/style_boxes.h" #include "ui/widgets/shadow.h" #include "styles/style_mediaview.h" #include "ui/widgets/input_fields.h" class EditColorBox::Picker : public TWidget { public: Picker(QWidget *parent, QColor color); float64 valueX() const { return _x; } float64 valueY() const { return _y; } base::Observable<void> &changed() { return _changed; } void setHSV(int hue, int saturation, int brightness); void setRGB(int red, int green, int blue); protected: void paintEvent(QPaintEvent *e); void mousePressEvent(QMouseEvent *e); void mouseMoveEvent(QMouseEvent *e); void mouseReleaseEvent(QMouseEvent *e); private: void setFromColor(QColor color); QCursor generateCursor(); void preparePalette(); void updateCurrentPoint(QPoint localPosition); QColor _topleft; QColor _topright; QColor _bottomleft; QColor _bottomright; QImage _palette; bool _paletteInvalidated = false; float64 _x = 0.; float64 _y = 0.; bool _choosing = false; base::Observable<void> _changed; }; QCursor EditColorBox::Picker::generateCursor() { auto diameter = convertScale(16); auto line = convertScale(1); auto size = ((diameter + 2 * line) >= 32) ? 64 : 32; auto cursor = QImage(QSize(size, size) * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); cursor.setDevicePixelRatio(cRetinaFactor()); cursor.fill(Qt::transparent); { Painter p(&cursor); PainterHighQualityEnabler hq(p); p.setBrush(Qt::NoBrush); auto pen = QPen(Qt::white); pen.setWidth(3 * line); p.setPen(pen); p.drawEllipse((size - diameter) / 2, (size - diameter) / 2, diameter, diameter); pen = QPen(Qt::black); pen.setWidth(line); p.setPen(pen); p.drawEllipse((size - diameter) / 2, (size - diameter) / 2, diameter, diameter); } return QCursor(QPixmap::fromImage(cursor)); } EditColorBox::Picker::Picker(QWidget *parent, QColor color) : TWidget(parent) { setCursor(generateCursor()); auto size = QSize(st::colorPickerSize, st::colorPickerSize); resize(size); _palette = QImage(size * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); setFromColor(color); } void EditColorBox::Picker::paintEvent(QPaintEvent *e) { Painter p(this); preparePalette(); p.drawImage(0, 0, _palette); auto left = anim::color(_topleft, _bottomleft, _y); auto right = anim::color(_topright, _bottomright, _y); auto color = anim::color(left, right, _x); auto lightness = 0.2989 * color.redF() + 0.5870 * color.greenF() + 0.1140 * color.blueF(); auto pen = QPen((lightness > 0.6) ? QColor(0, 0, 0) : QColor(255, 255, 255)); pen.setWidth(st::colorPickerMarkLine); p.setPen(pen); p.setBrush(Qt::NoBrush); auto x = anim::interpolate(0, width() - 1, _x); auto y = anim::interpolate(0, height() - 1, _y); PainterHighQualityEnabler hq(p); p.drawEllipse(QRect(x - st::colorPickerMarkRadius, y - st::colorPickerMarkRadius, 2 * st::colorPickerMarkRadius, 2 * st::colorPickerMarkRadius)); } void EditColorBox::Picker::mousePressEvent(QMouseEvent *e) { _choosing = true; updateCurrentPoint(e->pos()); } void EditColorBox::Picker::mouseMoveEvent(QMouseEvent *e) { if (_choosing) { updateCurrentPoint(e->pos()); } } void EditColorBox::Picker::mouseReleaseEvent(QMouseEvent *e) { _choosing = false; } void EditColorBox::Picker::preparePalette() { if (!_paletteInvalidated) return; _paletteInvalidated = false; auto size = _palette.width(); auto ints = reinterpret_cast<uint32*>(_palette.bits()); auto intsAddPerLine = (_palette.bytesPerLine() - size * sizeof(uint32)) / sizeof(uint32); constexpr auto Large = 1024 * 1024; constexpr auto LargeBit = 20; // n / Large == (n >> LargeBit) auto part = Large / size; auto topleft = anim::shifted(_topleft); auto topright = anim::shifted(_topright); auto bottomleft = anim::shifted(_bottomleft); auto bottomright = anim::shifted(_bottomright); auto y_accumulated = 0; for (auto y = 0; y != size; ++y, y_accumulated += part) { auto y_ratio = y_accumulated >> (LargeBit - 8); // (y_accumulated * 256) / Large; // 0 <= y_accumulated < Large // 0 <= y_ratio < 256 auto top_ratio = 255 - y_ratio; auto bottom_ratio = y_ratio; auto left = anim::reshifted(bottomleft * bottom_ratio + topleft * top_ratio); auto right = anim::reshifted(bottomright * bottom_ratio + topright * top_ratio); auto x_accumulated = 0; for (auto x = 0; x != size; ++x, x_accumulated += part) { auto x_ratio = x_accumulated >> (LargeBit - 8); // (x_accumulated * 256) / Large; // 0 <= x_accumulated < Large // 0 <= x_ratio < 256 auto left_ratio = 255 - x_ratio; auto right_ratio = x_ratio; *ints++ = anim::unshifted(left * left_ratio + right * right_ratio); } ints += intsAddPerLine; } } void EditColorBox::Picker::updateCurrentPoint(QPoint localPosition) { auto x = snap(localPosition.x(), 0, width()) / float64(width()); auto y = snap(localPosition.y(), 0, height()) / float64(height()); if (_x != x || _y != y) { _x = x; _y = y; update(); _changed.notify(); } } void EditColorBox::Picker::setHSV(int hue, int saturation, int brightness) { _topleft = QColor(255, 255, 255); _topright.setHsv(qMax(0, hue), 255, 255); _topright = _topright.toRgb(); _bottomleft = _bottomright = QColor(0, 0, 0); _paletteInvalidated = true; update(); _x = snap(saturation / 255., 0., 1.); _y = 1. - snap(brightness / 255., 0., 1.); } void EditColorBox::Picker::setRGB(int red, int green, int blue) { setFromColor(QColor(red, green, blue)); } void EditColorBox::Picker::setFromColor(QColor color) { setHSV(color.hsvHue(), color.hsvSaturation(), color.value()); } class EditColorBox::Slider : public TWidget { public: enum class Direction { Horizontal, Vertical, }; enum class Type { Hue, Opacity, }; Slider(QWidget *parent, Direction direction, Type type, QColor color); base::Observable<void> &changed() { return _changed; } float64 value() const { return _value; } void setValue(float64 value) { _value = snap(value, 0., 1.); update(); } void setHSV(int hue, int saturation, int brightness); void setRGB(int red, int green, int blue); void setAlpha(int alpha); protected: void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; private: float64 valueFromColor(QColor color) const; float64 valueFromHue(int hue) const; bool isHorizontal() const { return (_direction == Direction::Horizontal); } void colorUpdated(); void prepareMinSize(); void generatePixmap(); void updatePixmapFromMask(); void updateCurrentPoint(QPoint localPosition); Direction _direction = Direction::Horizontal; Type _type = Type::Hue; QColor _color; float64 _value = 0; QImage _mask; QPixmap _pixmap; QBrush _transparent; bool _choosing = false; base::Observable<void> _changed; }; EditColorBox::Slider::Slider(QWidget *parent, Direction direction, Type type, QColor color) : TWidget(parent) , _direction(direction) , _type(type) , _color(color.red(), color.green(), color.blue()) , _value(valueFromColor(color)) , _transparent((_type == Type::Hue) ? QBrush() : style::transparentPlaceholderBrush()) { prepareMinSize(); } void EditColorBox::Slider::prepareMinSize() { auto minSize = st::colorSliderSkip + st::colorSliderWidth + st::colorSliderSkip; resize(minSize, minSize); } void EditColorBox::Slider::paintEvent(QPaintEvent *e) { Painter p(this); auto to = rect().marginsRemoved(QMargins(st::colorSliderSkip, st::colorSliderSkip, st::colorSliderSkip, st::colorSliderSkip)); Ui::Shadow::paint(p, to, width(), st::defaultRoundShadow); if (_type == Type::Opacity) { p.fillRect(to, _transparent); } p.drawPixmap(to, _pixmap, _pixmap.rect()); if (isHorizontal()) { auto x = st::colorSliderSkip + qRound(_value * to.width()); st::colorSliderArrowTop.paint(p, x - st::colorSliderArrowTop.width() / 2, 0, width()); st::colorSliderArrowBottom.paint(p, x - st::colorSliderArrowBottom.width() / 2, height() - st::colorSliderArrowBottom.height(), width()); } else { auto y = st::colorSliderSkip + qRound(_value * to.height()); st::colorSliderArrowLeft.paint(p, 0, y - st::colorSliderArrowLeft.height() / 2, width()); st::colorSliderArrowRight.paint(p, width() - st::colorSliderArrowRight.width(), y - st::colorSliderArrowRight.height() / 2, width()); } } void EditColorBox::Slider::resizeEvent(QResizeEvent *e) { generatePixmap(); update(); } void EditColorBox::Slider::mousePressEvent(QMouseEvent *e) { _choosing = true; updateCurrentPoint(e->pos()); } void EditColorBox::Slider::mouseMoveEvent(QMouseEvent *e) { if (_choosing) { updateCurrentPoint(e->pos()); } } void EditColorBox::Slider::mouseReleaseEvent(QMouseEvent *e) { _choosing = false; } void EditColorBox::Slider::generatePixmap() { auto size = (isHorizontal() ? width() : height()) * cIntRetinaFactor(); auto image = QImage(size, cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); image.setDevicePixelRatio(cRetinaFactor()); auto ints = reinterpret_cast<uint32*>(image.bits()); auto intsPerLine = image.bytesPerLine() / sizeof(uint32); auto intsPerLineAdded = intsPerLine - size; constexpr auto Large = 1024 * 1024; constexpr auto LargeBit = 20; // n / Large == (n >> LargeBit) auto part = Large / size; if (_type == Type::Hue) { QColor color; for (auto x = 0; x != size; ++x) { color.setHsv(x * 360 / size, 255, 255); auto value = anim::getPremultiplied(color.toRgb()); for (auto y = 0; y != cIntRetinaFactor(); ++y) { ints[y * intsPerLine] = value; } ++ints; } if (!isHorizontal()) { image = std::move(image).transformed(QTransform(0, -1, 1, 0, 0, 0)); } _pixmap = App::pixmapFromImageInPlace(std::move(image)); } else { auto color = anim::shifted(QColor(255, 255, 255, 255)); auto transparent = anim::shifted(QColor(255, 255, 255, 0)); for (auto y = 0; y != cIntRetinaFactor(); ++y) { auto x_accumulated = 0; for (auto x = 0; x != size; ++x, x_accumulated += part) { auto x_ratio = x_accumulated >> (LargeBit - 8); // 0 <= x_accumulated < Large // 0 <= x_ratio < 256 *ints++ = anim::unshifted(color * x_ratio + transparent * (255 - x_ratio)); } ints += intsPerLineAdded; } if (!isHorizontal()) { image = std::move(image).transformed(QTransform(0, -1, 1, 0, 0, 0)); } _mask = std::move(image); updatePixmapFromMask(); } } void EditColorBox::Slider::setHSV(int hue, int saturation, int brightness) { if (_type == Type::Hue) { // hue == 360 converts to 0 if done in general way _value = valueFromHue(hue); update(); } else { _color.setHsv(hue, saturation, brightness); colorUpdated(); } } void EditColorBox::Slider::setRGB(int red, int green, int blue) { _color.setRgb(red, green, blue); colorUpdated(); } void EditColorBox::Slider::colorUpdated() { if (_type == Type::Hue) { _value = valueFromColor(_color); } else if (!_mask.isNull()) { updatePixmapFromMask(); } update(); } float64 EditColorBox::Slider::valueFromColor(QColor color) const { return (_type == Type::Hue) ? valueFromHue(color.hsvHue()) : color.alphaF(); } float64 EditColorBox::Slider::valueFromHue(int hue) const { return (1. - snap(hue, 0, 360) / 360.); } void EditColorBox::Slider::setAlpha(int alpha) { if (_type == Type::Opacity) { _value = snap(alpha, 0, 255) / 255.; update(); } } void EditColorBox::Slider::updatePixmapFromMask() { _pixmap = App::pixmapFromImageInPlace(style::colorizeImage(_mask, _color)); } void EditColorBox::Slider::updateCurrentPoint(QPoint localPosition) { auto coord = (isHorizontal() ? localPosition.x() : localPosition.y()) - st::colorSliderSkip; auto maximum = (isHorizontal() ? width() : height()) - 2 * st::colorSliderSkip; auto value = snap(coord, 0, maximum) / float64(maximum); if (_value != value) { _value = value; update(); _changed.notify(); } } class EditColorBox::Field : public Ui::MaskedInputField { public: Field(QWidget *parent, const style::InputField &st, const QString &placeholder, int limit, const QString &units = QString()); int value() const { return getLastText().toInt(); } void setTextWithFocus(const QString &text) { setText(text); if (hasFocus()) { selectAll(); } } protected: void correctValue(const QString &was, int wasCursor, QString &now, int &nowCursor) override; void paintAdditionalPlaceholder(Painter &p, TimeMs ms) override; void wheelEvent(QWheelEvent *e) override; void keyPressEvent(QKeyEvent *e) override; private: void changeValue(int delta); QString _placeholder, _units; int _limit = 0; int _digitLimit = 1; int _wheelDelta = 0; }; EditColorBox::Field::Field(QWidget *parent, const style::InputField &st, const QString &placeholder, int limit, const QString &units) : Ui::MaskedInputField(parent, st) , _placeholder(placeholder) , _units(units) , _limit(limit) , _digitLimit(QString::number(_limit).size()) { } void EditColorBox::Field::correctValue(const QString &was, int wasCursor, QString &now, int &nowCursor) { QString newText; int oldPos(nowCursor), newPos(-1), oldLen(now.length()); newText.reserve(oldLen); for (int i = 0; i < oldLen; ++i) { if (i == oldPos) { newPos = newText.length(); } QChar ch(now[i]); if (ch.isDigit()) { newText += ch; } if (newText.size() >= _digitLimit) { break; } } if (newPos < 0 || newPos > newText.size()) { newPos = newText.size(); } if (newText.toInt() > _limit) { newText = QString::number(_limit); newPos = newText.size(); } if (newText != now) { now = newText; setText(now); startPlaceholderAnimation(); nowCursor = -1; } if (newPos != nowCursor) { nowCursor = newPos; setCursorPosition(nowCursor); } } void EditColorBox::Field::paintAdditionalPlaceholder(Painter &p, TimeMs ms) { p.setFont(_st.font); p.setPen(_st.placeholderFg); auto inner = QRect(_st.textMargins.right(), _st.textMargins.top(), width() - 2 * _st.textMargins.right(), height() - _st.textMargins.top() - _st.textMargins.bottom()); p.drawText(inner, _placeholder, style::al_topleft); if (!_units.isEmpty()) { p.drawText(inner, _units, style::al_topright); } } void EditColorBox::Field::wheelEvent(QWheelEvent *e) { if (!hasFocus()) { return; } auto deltaX = e->angleDelta().x(), deltaY = e->angleDelta().y(); if (cPlatform() == dbipMac || cPlatform() == dbipMacOld) { deltaY *= -1; } else { deltaX *= -1; } _wheelDelta += (qAbs(deltaX) > qAbs(deltaY)) ? deltaX : deltaY; constexpr auto step = 5; if (auto delta = _wheelDelta / step) { _wheelDelta -= delta * step; changeValue(delta); } } void EditColorBox::Field::changeValue(int delta) { auto currentValue = value(); auto newValue = snap(currentValue + delta, 0, _limit); if (newValue != currentValue) { setText(QString::number(newValue)); setFocus(); selectAll(); emit changed(); } } void EditColorBox::Field::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Up) { changeValue(1); } else if (e->key() == Qt::Key_Down) { changeValue(-1); } else { MaskedInputField::keyPressEvent(e); } } class EditColorBox::ResultField : public Ui::MaskedInputField { public: ResultField(QWidget *parent, const style::InputField &st); void setTextWithFocus(const QString &text) { setText(text); if (hasFocus()) { selectAll(); } } protected: void correctValue(const QString &was, int wasCursor, QString &now, int &nowCursor) override; void paintAdditionalPlaceholder(Painter &p, TimeMs ms) override; }; EditColorBox::ResultField::ResultField(QWidget *parent, const style::InputField &st) : Ui::MaskedInputField(parent, st) { } void EditColorBox::ResultField::correctValue(const QString &was, int wasCursor, QString &now, int &nowCursor) { QString newText; int oldPos(nowCursor), newPos(-1), oldLen(now.length()); newText.reserve(oldLen); for (int i = 0; i < oldLen; ++i) { if (i == oldPos) { newPos = newText.length(); } QChar ch(now[i]); auto code = ch.unicode(); if ((code >= '0' && code <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')) { newText += ch; } if (newText.size() >= 8) { break; } } if (newPos < 0 || newPos > newText.size()) { newPos = newText.size(); } if (newText != now) { now = newText; setText(now); startPlaceholderAnimation(); nowCursor = -1; } if (newPos != nowCursor) { nowCursor = newPos; setCursorPosition(nowCursor); } } void EditColorBox::ResultField::paintAdditionalPlaceholder(Painter &p, TimeMs ms) { p.setFont(_st.font); p.setPen(_st.placeholderFg); p.drawText(QRect(_st.textMargins.right(), _st.textMargins.top(), width(), height() - _st.textMargins.top() - _st.textMargins.bottom()), "#", style::al_topleft); } EditColorBox::EditColorBox(QWidget*, const QString &title, QColor current) : BoxContent() , _title(title) , _picker(this, current) , _hueSlider(this, Slider::Direction::Vertical, Slider::Type::Hue, current) , _opacitySlider(this, Slider::Direction::Horizontal, Slider::Type::Opacity, current) , _hueField(this, st::colorValueInput, "H", 360, QString() + QChar(176)) // degree character , _saturationField(this, st::colorValueInput, "S", 100, "%") , _brightnessField(this, st::colorValueInput, "B", 100, "%") , _redField(this, st::colorValueInput, "R", 255) , _greenField(this, st::colorValueInput, "G", 255) , _blueField(this, st::colorValueInput, "B", 255) , _result(this, st::colorResultInput) , _transparent(style::transparentPlaceholderBrush()) , _current(current) , _new(current) { } void EditColorBox::prepare() { setTitle([this] { return _title; }); connect(_hueField, SIGNAL(changed()), this, SLOT(onFieldChanged())); connect(_saturationField, SIGNAL(changed()), this, SLOT(onFieldChanged())); connect(_brightnessField, SIGNAL(changed()), this, SLOT(onFieldChanged())); connect(_redField, SIGNAL(changed()), this, SLOT(onFieldChanged())); connect(_greenField, SIGNAL(changed()), this, SLOT(onFieldChanged())); connect(_blueField, SIGNAL(changed()), this, SLOT(onFieldChanged())); connect(_result, SIGNAL(changed()), this, SLOT(onFieldChanged())); connect(_hueField, SIGNAL(submitted(bool)), this, SLOT(onFieldSubmitted())); connect(_saturationField, SIGNAL(submitted(bool)), this, SLOT(onFieldSubmitted())); connect(_brightnessField, SIGNAL(submitted(bool)), this, SLOT(onFieldSubmitted())); connect(_redField, SIGNAL(submitted(bool)), this, SLOT(onFieldSubmitted())); connect(_greenField, SIGNAL(submitted(bool)), this, SLOT(onFieldSubmitted())); connect(_blueField, SIGNAL(submitted(bool)), this, SLOT(onFieldSubmitted())); connect(_result, SIGNAL(submitted(bool)), this, SLOT(onFieldSubmitted())); addButton(langFactory(lng_settings_save), [this] { saveColor(); }); addButton(langFactory(lng_cancel), [this] { closeBox(); }); auto height = st::colorEditSkip + st::colorPickerSize + st::colorEditSkip + st::colorSliderWidth + st::colorEditSkip; setDimensions(st::colorEditWidth, height); subscribe(_picker->changed(), [this] { updateFromControls(); }); subscribe(_hueSlider->changed(), [this] { updateFromControls(); }); subscribe(_opacitySlider->changed(), [this] { updateFromControls(); }); subscribe(boxClosing, [this] { if (_cancelCallback) { _cancelCallback(); } }); updateFromControls(); } void EditColorBox::setInnerFocus() { _result->setFocus(); _result->selectAll(); } void EditColorBox::onFieldChanged() { auto emitter = sender(); auto checkHSVSender = [this, emitter](QObject *field) { if (emitter == field) { updateFromHSVFields(); } }; auto checkRGBSender = [this, emitter](QObject *field) { if (emitter == field) { updateFromRGBFields(); } }; checkHSVSender(_hueField); checkHSVSender(_saturationField); checkHSVSender(_brightnessField); checkRGBSender(_redField); checkRGBSender(_greenField); checkRGBSender(_blueField); if (emitter == _result) { updateFromResultField(); } } void EditColorBox::onFieldSubmitted() { Ui::MaskedInputField *fields[] = { _hueField, _saturationField, _brightnessField, _redField, _greenField, _blueField, _result }; for (auto i = 0, count = int(base::array_size(fields)); i + 1 != count; ++i) { if (fields[i]->hasFocus()) { fields[i + 1]->setFocus(); fields[i + 1]->selectAll(); return; } } if (_result->hasFocus()) { saveColor(); } } void EditColorBox::saveColor() { _cancelCallback = base::lambda<void()>(); if (_saveCallback) { _saveCallback(_new.toRgb()); } closeBox(); } void EditColorBox::updateHSVFields() { auto hue = qRound((1. - _hueSlider->value()) * 360); auto saturation = qRound(_picker->valueX() * 255); auto brightness = qRound((1. - _picker->valueY()) * 255); auto alpha = qRound(_opacitySlider->value() * 255); _hueField->setTextWithFocus(QString::number(hue)); _saturationField->setTextWithFocus(QString::number(percentFromByte(saturation))); _brightnessField->setTextWithFocus(QString::number(percentFromByte(brightness))); } void EditColorBox::updateRGBFields() { _redField->setTextWithFocus(QString::number(_new.red())); _greenField->setTextWithFocus(QString::number(_new.green())); _blueField->setTextWithFocus(QString::number(_new.blue())); } void EditColorBox::updateResultField() { auto text = QString(); auto addHex = [&text](int value) { if (value >= 0 && value <= 9) { text.append('0' + value); } else if (value >= 10 && value <= 15) { text.append('a' + (value - 10)); } }; auto addValue = [addHex](int value) { addHex(value / 16); addHex(value % 16); }; addValue(_new.red()); addValue(_new.green()); addValue(_new.blue()); if (_new.alpha() != 255) { addValue(_new.alpha()); } _result->setTextWithFocus(text); } void EditColorBox::resizeEvent(QResizeEvent *e) { auto fullwidth = _picker->width() + 2 * (st::colorEditSkip - st::colorSliderSkip) + _hueSlider->width() + st::colorSampleSize.width(); auto left = (width() - fullwidth) / 2; _picker->moveToLeft(left, st::colorEditSkip); _hueSlider->setGeometryToLeft(_picker->x() + _picker->width() + st::colorEditSkip - st::colorSliderSkip, st::colorEditSkip - st::colorSliderSkip, _hueSlider->width(), st::colorPickerSize + 2 * st::colorSliderSkip); _opacitySlider->setGeometryToLeft(_picker->x() - st::colorSliderSkip, _picker->y() + _picker->height() + st::colorEditSkip - st::colorSliderSkip, _picker->width() + 2 * st::colorSliderSkip, _opacitySlider->height()); auto fieldLeft = _hueSlider->x() + _hueSlider->width() - st::colorSliderSkip + st::colorEditSkip; auto fieldWidth = st::colorSampleSize.width(); auto fieldHeight = _hueField->height(); _newRect = QRect(fieldLeft, st::colorEditSkip, fieldWidth, st::colorSampleSize.height()); _currentRect = _newRect.translated(0, st::colorSampleSize.height()); _hueField->setGeometryToLeft(fieldLeft, _currentRect.y() + _currentRect.height() + st::colorFieldSkip, fieldWidth, fieldHeight); _saturationField->setGeometryToLeft(fieldLeft, _hueField->y() + _hueField->height(), fieldWidth, fieldHeight); _brightnessField->setGeometryToLeft(fieldLeft, _saturationField->y() + _saturationField->height(), fieldWidth, fieldHeight); _redField->setGeometryToLeft(fieldLeft, _brightnessField->y() + _brightnessField->height() + st::colorFieldSkip, fieldWidth, fieldHeight); _greenField->setGeometryToLeft(fieldLeft, _redField->y() + _redField->height(), fieldWidth, fieldHeight); _blueField->setGeometryToLeft(fieldLeft, _greenField->y() + _greenField->height(), fieldWidth, fieldHeight); _result->setGeometryToLeft(fieldLeft - (st::colorEditSkip + st::colorSliderWidth), _opacitySlider->y() + _opacitySlider->height() - st::colorSliderSkip - _result->height(), fieldWidth + (st::colorEditSkip + st::colorSliderWidth), fieldHeight); } void EditColorBox::paintEvent(QPaintEvent *e) { BoxContent::paintEvent(e); Painter p(this); Ui::Shadow::paint(p, _picker->geometry(), width(), st::defaultRoundShadow); Ui::Shadow::paint(p, QRect(_newRect.x(), _newRect.y(), _newRect.width(), _newRect.height() + _currentRect.height()), width(), st::defaultRoundShadow); if (_new.alphaF() < 1.) { p.fillRect(myrtlrect(_newRect), _transparent); } p.fillRect(myrtlrect(_newRect), _new); if (_current.alphaF() < 1.) { p.fillRect(myrtlrect(_currentRect), _transparent); } p.fillRect(myrtlrect(_currentRect), _current); } void EditColorBox::mousePressEvent(QMouseEvent *e) { if (myrtlrect(_currentRect).contains(e->pos())) { updateFromColor(_current); } } void EditColorBox::updateFromColor(QColor color) { _new = color; updateControlsFromColor(); updateRGBFields(); updateHSVFields(); updateResultField(); update(); } void EditColorBox::updateFromControls() { auto hue = qRound((1. - _hueSlider->value()) * 360); auto saturation = qRound(_picker->valueX() * 255); auto brightness = qRound((1. - _picker->valueY()) * 255); auto alpha = qRound(_opacitySlider->value() * 255); setHSV(hue, saturation, brightness, alpha); updateHSVFields(); updateControlsFromHSV(hue, saturation, brightness); } void EditColorBox::updateFromHSVFields() { auto hue = _hueField->value(); auto saturation = percentToByte(_saturationField->value()); auto brightness = percentToByte(_brightnessField->value()); auto alpha = qRound(_opacitySlider->value() * 255); setHSV(hue, saturation, brightness, alpha); updateControlsFromHSV(hue, saturation, brightness); } void EditColorBox::updateFromRGBFields() { auto red = _redField->value(); auto blue = _blueField->value(); auto green = _greenField->value(); auto alpha = qRound(_opacitySlider->value() * 255); setRGB(red, green, blue, alpha); updateResultField(); } void EditColorBox::updateFromResultField() { auto text = _result->getLastText(); if (text.size() != 6 && text.size() != 8) { return; } auto fromHex = [](QChar hex) { auto code = hex.unicode(); if (code >= 'A' && code <= 'F') { return (code - 'A' + 10); } else if (code >= 'a' && code <= 'f') { return (code - 'a' + 10); } return code - '0'; }; auto fromChars = [fromHex](QChar a, QChar b) { return fromHex(a) * 0x10 + fromHex(b); }; auto red = fromChars(text[0], text[1]); auto green = fromChars(text[2], text[3]); auto blue = fromChars(text[4], text[5]); auto alpha = (text.size() == 8) ? fromChars(text[6], text[7]) : 255; setRGB(red, green, blue, alpha); updateRGBFields(); } void EditColorBox::updateControlsFromHSV(int hue, int saturation, int brightness) { _picker->setHSV(hue, saturation, brightness); _hueSlider->setHSV(hue, saturation, brightness); _opacitySlider->setHSV(hue, saturation, brightness); } void EditColorBox::updateControlsFromColor() { auto red = _new.red(); auto green = _new.green(); auto blue = _new.blue(); auto alpha = _new.alpha(); _picker->setRGB(red, green, blue); _hueSlider->setRGB(red, green, blue); _opacitySlider->setRGB(red, green, blue); _opacitySlider->setAlpha(alpha); } void EditColorBox::setHSV(int hue, int saturation, int value, int alpha) { _new.setHsv(hue, saturation, value, alpha); updateRGBFields(); updateResultField(); update(); } void EditColorBox::setRGB(int red, int green, int blue, int alpha) { _new.setRgb(red, green, blue, alpha); updateControlsFromColor(); updateHSVFields(); update(); }����������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/edit_color_box.h�����������������������������������������0000664�0000000�0000000�00000004271�13262451251�0023713�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "boxes/abstract_box.h" class EditColorBox : public BoxContent { Q_OBJECT public: EditColorBox(QWidget*, const QString &title, QColor current = QColor(255, 255, 255)); void setSaveCallback(base::lambda<void(QColor)> callback) { _saveCallback = std::move(callback); } void setCancelCallback(base::lambda<void()> callback) { _cancelCallback = std::move(callback); } void showColor(QColor color) { updateFromColor(color); } protected: void prepare() override; void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void setInnerFocus() override; private slots: void onFieldChanged(); void onFieldSubmitted(); private: void saveColor(); void updateFromColor(QColor color); void updateControlsFromColor(); void updateControlsFromHSV(int hue, int saturation, int brightness); void updateHSVFields(); void updateRGBFields(); void updateResultField(); void updateFromControls(); void updateFromHSVFields(); void updateFromRGBFields(); void updateFromResultField(); void setHSV(int hue, int saturation, int brightness, int alpha); void setRGB(int red, int green, int blue, int alpha); int percentFromByte(int byte) { return snap(qRound(byte * 100 / 255.), 0, 100); } int percentToByte(int percent) { return snap(qRound(percent * 255 / 100.), 0, 255); } class Picker; class Slider; class Field; class ResultField; QString _title; object_ptr<Picker> _picker; object_ptr<Slider> _hueSlider; object_ptr<Slider> _opacitySlider; object_ptr<Field> _hueField; object_ptr<Field> _saturationField; object_ptr<Field> _brightnessField; object_ptr<Field> _redField; object_ptr<Field> _greenField; object_ptr<Field> _blueField; object_ptr<ResultField> _result; QBrush _transparent; QColor _current; QColor _new; QRect _currentRect; QRect _newRect; base::lambda<void(QColor)> _saveCallback; base::lambda<void()> _cancelCallback; }; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/edit_participant_box.cpp���������������������������������0000664�0000000�0000000�00000043656�13262451251�0025460�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/edit_participant_box.h" #include "lang/lang_keys.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/labels.h" #include "ui/widgets/buttons.h" #include "ui/text_options.h" #include "ui/special_buttons.h" #include "boxes/calendar_box.h" #include "data/data_peer_values.h" #include "styles/style_boxes.h" namespace { constexpr auto kMaxRestrictDelayDays = 366; constexpr auto kSecondsInDay = 24 * 60 * 60; constexpr auto kSecondsInWeek = 7 * kSecondsInDay; template <typename CheckboxesMap, typename DependenciesMap> void ApplyDependencies(CheckboxesMap &checkboxes, DependenciesMap &dependencies, QPointer<Ui::Checkbox> changed) { auto checkAndApply = [&checkboxes](auto &¤t, auto dependency, bool isChecked) { for (auto &&checkbox : checkboxes) { if ((checkbox.first & dependency) && (checkbox.second->checked() == isChecked)) { current->setChecked(isChecked); return true; } } return false; }; auto applySomeDependency = [&checkboxes, &dependencies, &changed, checkAndApply] { auto result = false; for (auto &&entry : checkboxes) { if (entry.second == changed) { continue; } auto isChecked = entry.second->checked(); for (auto &&dependency : dependencies) { if (entry.first & (isChecked ? dependency.first : dependency.second)) { if (checkAndApply(entry.second, (isChecked ? dependency.second : dependency.first), !isChecked)) { result = true; break; } } } } return result; }; while (true) { if (!applySomeDependency()) { break; } }; } } // namespace class EditParticipantBox::Inner : public TWidget { public: Inner( QWidget *parent, not_null<Window::Controller*> controller, not_null<ChannelData*> channel, not_null<UserData*> user, bool hasAdminRights); template <typename Widget> QPointer<Widget> addControl(object_ptr<Widget> widget, QMargins margin) { doAddControl(std::move(widget), margin); return static_cast<Widget*>(_rows.back().widget.data()); } void removeControl(QPointer<TWidget> widget); protected: int resizeGetHeight(int newWidth) override; void paintEvent(QPaintEvent *e) override; private: void doAddControl(object_ptr<TWidget> widget, QMargins margin); not_null<ChannelData*> _channel; not_null<UserData*> _user; object_ptr<Ui::UserpicButton> _userPhoto; Text _userName; bool _hasAdminRights = false; struct Control { object_ptr<TWidget> widget; QMargins margin; }; std::vector<Control> _rows; }; EditParticipantBox::Inner::Inner( QWidget *parent, not_null<Window::Controller*> controller, not_null<ChannelData*> channel, not_null<UserData*> user, bool hasAdminRights) : TWidget(parent) , _channel(channel) , _user(user) , _userPhoto( this, controller, _user, Ui::UserpicButton::Role::Custom, st::rightsPhotoButton) , _hasAdminRights(hasAdminRights) { _userPhoto->setPointerCursor(false); _userName.setText( st::rightsNameStyle, App::peerName(_user), Ui::NameTextOptions()); } void EditParticipantBox::Inner::removeControl(QPointer<TWidget> widget) { auto row = std::find_if(_rows.begin(), _rows.end(), [widget](auto &&row) { return (row.widget == widget); }); Assert(row != _rows.end()); row->widget.destroy(); _rows.erase(row); } void EditParticipantBox::Inner::doAddControl(object_ptr<TWidget> widget, QMargins margin) { widget->setParent(this); _rows.push_back({ std::move(widget), margin }); _rows.back().widget->show(); } int EditParticipantBox::Inner::resizeGetHeight(int newWidth) { _userPhoto->moveToLeft(st::rightsPhotoMargin.left(), st::rightsPhotoMargin.top()); auto newHeight = st::rightsPhotoMargin.top() + st::rightsPhotoButton.size.height() + st::rightsPhotoMargin.bottom(); for (auto &&row : _rows) { auto rowWidth = newWidth - row.margin.left() - row.margin.right(); newHeight += row.margin.top(); row.widget->resizeToNaturalWidth(rowWidth); row.widget->moveToLeft(row.margin.left(), newHeight); newHeight += row.widget->heightNoMargins() + row.margin.bottom(); } return newHeight; } void EditParticipantBox::Inner::paintEvent(QPaintEvent *e) { Painter p(this); p.fillRect(e->rect(), st::boxBg); p.setPen(st::contactsNameFg); auto namex = st::rightsPhotoMargin.left() + st::rightsPhotoButton.size .width() + st::rightsPhotoMargin.right(); auto namew = width() - namex - st::rightsPhotoMargin.right(); _userName.drawLeftElided(p, namex, st::rightsPhotoMargin.top() + st::rightsNameTop, namew, width()); auto statusText = [this] { if (_user->botInfo) { auto seesAllMessages = (_user->botInfo->readsAllHistory || _hasAdminRights); return lang(seesAllMessages ? lng_status_bot_reads_all : lng_status_bot_not_reads_all); } return Data::OnlineText(_user->onlineTill, unixtime()); }; p.setFont(st::contactsStatusFont); p.setPen(st::contactsStatusFg); p.drawTextLeft(namex, st::rightsPhotoMargin.top() + st::rightsStatusTop, width(), statusText()); } EditParticipantBox::EditParticipantBox(QWidget*, not_null<ChannelData*> channel, not_null<UserData*> user, bool hasAdminRights) : BoxContent() , _channel(channel) , _user(user) , _hasAdminRights(hasAdminRights) { } void EditParticipantBox::prepare() { _inner = setInnerWidget(object_ptr<Inner>( this, controller(), _channel, _user, hasAdminRights())); } template <typename Widget> QPointer<Widget> EditParticipantBox::addControl(object_ptr<Widget> widget, QMargins margin) { Expects(_inner != nullptr); return _inner->addControl(std::move(widget), margin); } void EditParticipantBox::removeControl(QPointer<TWidget> widget) { Expects(_inner != nullptr); return _inner->removeControl(widget); } void EditParticipantBox::resizeToContent() { _inner->resizeToWidth(st::boxWideWidth); setDimensions(_inner->width(), qMin(_inner->height(), st::boxMaxListHeight)); } EditAdminBox::EditAdminBox(QWidget*, not_null<ChannelData*> channel, not_null<UserData*> user, const MTPChannelAdminRights &rights) : EditParticipantBox(nullptr, channel, user, (rights.c_channelAdminRights().vflags.v != 0)) , _oldRights(rights) { auto dependency = [this](Flag dependent, Flag dependency) { _dependencies.push_back(std::make_pair(dependent, dependency)); }; dependency(Flag::f_invite_link, Flag::f_invite_users); // invite_link <-> invite_users dependency(Flag::f_invite_users, Flag::f_invite_link); } MTPChannelAdminRights EditAdminBox::DefaultRights(not_null<ChannelData*> channel) { auto defaultRights = channel->isMegagroup() ? (Flag::f_change_info | Flag::f_delete_messages | Flag::f_ban_users | Flag::f_invite_users | Flag::f_invite_link | Flag::f_pin_messages) : (Flag::f_change_info | Flag::f_post_messages | Flag::f_edit_messages | Flag::f_delete_messages | Flag::f_invite_users | Flag::f_invite_link); return MTP_channelAdminRights(MTP_flags(defaultRights)); } void EditAdminBox::prepare() { EditParticipantBox::prepare(); auto hadRights = _oldRights.c_channelAdminRights().vflags.v; setTitle(langFactory(hadRights ? lng_rights_edit_admin : lng_channel_add_admin)); addControl(object_ptr<BoxContentDivider>(this), QMargins()); addControl(object_ptr<Ui::FlatLabel>(this, lang(lng_rights_edit_admin_header), Ui::FlatLabel::InitType::Simple, st::rightsHeaderLabel), st::rightsHeaderMargin); auto prepareRights = (hadRights ? _oldRights : DefaultRights(channel())); auto addCheckbox = [this, &prepareRights](Flags flags, const QString &text) { auto checked = (prepareRights.c_channelAdminRights().vflags.v & flags) != 0; auto control = addControl(object_ptr<Ui::Checkbox>(this, text, checked, st::rightsCheckbox, st::rightsToggle), st::rightsToggleMargin); subscribe(control->checkedChanged, [this, control](bool checked) { InvokeQueued(this, [this, control] { applyDependencies(control); }); }); if (!channel()->amCreator()) { if (!(channel()->adminRights() & flags)) { control->setDisabled(true); // Grey out options that we don't have ourselves. } } if (!canSave()) { control->setDisabled(true); } _checkboxes.emplace(flags, control); }; if (channel()->isMegagroup()) { addCheckbox(Flag::f_change_info, lang(lng_rights_group_info)); addCheckbox(Flag::f_delete_messages, lang(lng_rights_group_delete)); addCheckbox(Flag::f_ban_users, lang(lng_rights_group_ban)); addCheckbox(Flag::f_invite_users | Flag::f_invite_link, lang(channel()->anyoneCanAddMembers() ? lng_rights_group_invite_link : lng_rights_group_invite)); addCheckbox(Flag::f_pin_messages, lang(lng_rights_group_pin)); addCheckbox(Flag::f_add_admins, lang(lng_rights_add_admins)); } else { addCheckbox(Flag::f_change_info, lang(lng_rights_channel_info)); addCheckbox(Flag::f_post_messages, lang(lng_rights_channel_post)); addCheckbox(Flag::f_edit_messages, lang(lng_rights_channel_edit)); addCheckbox(Flag::f_delete_messages, lang(lng_rights_channel_delete)); addCheckbox(Flag::f_invite_users | Flag::f_invite_link, lang(lng_rights_group_invite)); addCheckbox(Flag::f_add_admins, lang(lng_rights_add_admins)); } auto addAdmins = _checkboxes.find(Flag::f_add_admins); if (addAdmins != _checkboxes.end()) { _aboutAddAdmins = addControl(object_ptr<Ui::FlatLabel>(this, st::boxLabel), st::rightsAboutMargin); Assert(addAdmins != _checkboxes.end()); subscribe(addAdmins->second->checkedChanged, [this](bool checked) { refreshAboutAddAdminsText(); }); refreshAboutAddAdminsText(); } if (canSave()) { addButton(langFactory(lng_settings_save), [this] { if (!_saveCallback) { return; } auto newFlags = MTPDchannelAdminRights::Flags(0); for (auto &&checkbox : _checkboxes) { if (checkbox.second->checked()) { newFlags |= checkbox.first; } else { newFlags &= ~checkbox.first; } } if (!channel()->amCreator()) { // Leave only rights that we have so we could save them. newFlags &= channel()->adminRights(); } _saveCallback(_oldRights, MTP_channelAdminRights(MTP_flags(newFlags))); }); addButton(langFactory(lng_cancel), [this] { closeBox(); }); } else { addButton(langFactory(lng_box_ok), [this] { closeBox(); }); } applyDependencies(nullptr); for (auto &&checkbox : _checkboxes) { checkbox.second->finishAnimating(); } resizeToContent(); } void EditAdminBox::applyDependencies(QPointer<Ui::Checkbox> changed) { ApplyDependencies(_checkboxes, _dependencies, changed); } void EditAdminBox::refreshAboutAddAdminsText() { auto addAdmins = _checkboxes.find(Flag::f_add_admins); Assert(addAdmins != _checkboxes.end()); auto text = [this, addAdmins] { if (!canSave()) { return lang(lng_rights_about_admin_cant_edit); } else if (addAdmins->second->checked()) { return lang(lng_rights_about_add_admins_yes); } return lang(lng_rights_about_add_admins_no); }; _aboutAddAdmins->setText(text()); resizeToContent(); } EditRestrictedBox::EditRestrictedBox(QWidget*, not_null<ChannelData*> channel, not_null<UserData*> user, bool hasAdminRights, const MTPChannelBannedRights &rights) : EditParticipantBox(nullptr, channel, user, hasAdminRights) , _oldRights(rights) { auto dependency = [this](Flag dependent, Flag dependency) { _dependencies.push_back(std::make_pair(dependent, dependency)); }; dependency(Flag::f_send_gifs, Flag::f_send_stickers); // stickers <-> gifs dependency(Flag::f_send_stickers, Flag::f_send_gifs); dependency(Flag::f_send_games, Flag::f_send_stickers); // stickers <-> games dependency(Flag::f_send_stickers, Flag::f_send_games); dependency(Flag::f_send_inline, Flag::f_send_stickers); // stickers <-> inline dependency(Flag::f_send_stickers, Flag::f_send_inline); dependency(Flag::f_send_stickers, Flag::f_send_media); // stickers -> send_media dependency(Flag::f_embed_links, Flag::f_send_media); // embed_links -> send_media dependency(Flag::f_send_media, Flag::f_send_messages); // send_media- > send_messages dependency(Flag::f_send_messages, Flag::f_view_messages); // send_messages -> view_messages } void EditRestrictedBox::prepare() { EditParticipantBox::prepare(); setTitle(langFactory(lng_rights_user_restrictions)); addControl(object_ptr<BoxContentDivider>(this), QMargins()); addControl(object_ptr<Ui::FlatLabel>(this, lang(lng_rights_user_restrictions_header), Ui::FlatLabel::InitType::Simple, st::rightsHeaderLabel), st::rightsHeaderMargin); auto prepareRights = (_oldRights.c_channelBannedRights().vflags.v ? _oldRights : DefaultRights(channel())); _until = prepareRights.c_channelBannedRights().vuntil_date.v; auto addCheckbox = [this, &prepareRights](Flags flags, const QString &text) { auto checked = (prepareRights.c_channelBannedRights().vflags.v & flags) == 0; auto control = addControl(object_ptr<Ui::Checkbox>(this, text, checked, st::rightsCheckbox, st::rightsToggle), st::rightsToggleMargin); subscribe(control->checkedChanged, [this, control](bool checked) { InvokeQueued(this, [this, control] { applyDependencies(control); }); }); if (!canSave()) { control->setDisabled(true); } _checkboxes.emplace(flags, control); }; addCheckbox(Flag::f_view_messages, lang(lng_rights_chat_read)); addCheckbox(Flag::f_send_messages, lang(lng_rights_chat_send_text)); addCheckbox(Flag::f_send_media, lang(lng_rights_chat_send_media)); addCheckbox(Flag::f_send_stickers | Flag::f_send_gifs | Flag::f_send_games | Flag::f_send_inline, lang(lng_rights_chat_send_stickers)); addCheckbox(Flag::f_embed_links, lang(lng_rights_chat_send_links)); addControl(object_ptr<BoxContentDivider>(this), st::rightsUntilMargin); addControl(object_ptr<Ui::FlatLabel>(this, lang(lng_rights_chat_banned_until_header), Ui::FlatLabel::InitType::Simple, st::rightsHeaderLabel), st::rightsHeaderMargin); setRestrictUntil(_until); //addControl(object_ptr<Ui::LinkButton>(this, lang(lng_rights_chat_banned_block), st::boxLinkButton)); if (canSave()) { addButton(langFactory(lng_settings_save), [this] { if (!_saveCallback) { return; } auto newFlags = MTPDchannelBannedRights::Flags(0); for (auto &&checkbox : _checkboxes) { if (checkbox.second->checked()) { newFlags &= ~checkbox.first; } else { newFlags |= checkbox.first; } } _saveCallback(_oldRights, MTP_channelBannedRights(MTP_flags(newFlags), MTP_int(getRealUntilValue()))); }); addButton(langFactory(lng_cancel), [this] { closeBox(); }); } else { addButton(langFactory(lng_box_ok), [this] { closeBox(); }); } applyDependencies(nullptr); for (auto &&checkbox : _checkboxes) { checkbox.second->finishAnimating(); } resizeToContent(); } void EditRestrictedBox::applyDependencies(QPointer<Ui::Checkbox> changed) { ApplyDependencies(_checkboxes, _dependencies, changed); } MTPChannelBannedRights EditRestrictedBox::DefaultRights(not_null<ChannelData*> channel) { auto defaultRights = Flag::f_send_messages | Flag::f_send_media | Flag::f_embed_links | Flag::f_send_stickers | Flag::f_send_gifs | Flag::f_send_games | Flag::f_send_inline; return MTP_channelBannedRights(MTP_flags(defaultRights), MTP_int(0)); } void EditRestrictedBox::showRestrictUntil() { auto tomorrow = QDate::currentDate().addDays(1); auto highlighted = isUntilForever() ? tomorrow : ParseDateTime(getRealUntilValue()).date(); auto month = highlighted; _restrictUntilBox = Ui::show( Box<CalendarBox>( month, highlighted, [this](const QDate &date) { setRestrictUntil(static_cast<int>(QDateTime(date).toTime_t())); }), LayerOption::KeepOther); _restrictUntilBox->setMaxDate(QDate::currentDate().addDays(kMaxRestrictDelayDays)); _restrictUntilBox->setMinDate(tomorrow); _restrictUntilBox->addLeftButton(langFactory(lng_rights_chat_banned_forever), [this] { setRestrictUntil(0); }); } void EditRestrictedBox::setRestrictUntil(TimeId until) { _until = until; if (_restrictUntilBox) { _restrictUntilBox->closeBox(); } clearVariants(); createUntilGroup(); createUntilVariants(); resizeToContent(); } void EditRestrictedBox::clearVariants() { for (auto &&widget : base::take(_untilVariants)) { removeControl(widget.data()); } } void EditRestrictedBox::createUntilGroup() { _untilGroup = std::make_shared<Ui::RadiobuttonGroup>(isUntilForever() ? 0 : _until); _untilGroup->setChangedCallback([this](int value) { if (value == kUntilCustom) { _untilGroup->setValue(_until); showRestrictUntil(); } else if (_until != value) { _until = value; } }); } void EditRestrictedBox::createUntilVariants() { auto addVariant = [this](int value, const QString &text) { if (!canSave() && _untilGroup->value() != value) { return; } _untilVariants.push_back(addControl(object_ptr<Ui::Radiobutton>(this, _untilGroup, value, text, st::defaultBoxCheckbox), st::rightsToggleMargin)); if (!canSave()) { _untilVariants.back()->setDisabled(true); } }; auto addCustomVariant = [addVariant](TimeId until, TimeId from, TimeId to) { if (!ChannelData::IsRestrictedForever(until) && until > from && until <= to) { addVariant( until, lng_rights_chat_banned_custom_date( lt_date, langDayOfMonthFull(ParseDateTime(until).date()))); } }; auto addCurrentVariant = [this, addCustomVariant](TimeId from, TimeId to) { auto oldUntil = _oldRights.c_channelBannedRights().vuntil_date.v; if (oldUntil < _until) { addCustomVariant(oldUntil, from, to); } addCustomVariant(_until, from, to); if (oldUntil > _until) { addCustomVariant(oldUntil, from, to); } }; addVariant(0, lang(lng_rights_chat_banned_forever)); auto now = unixtime(); auto nextDay = now + kSecondsInDay; auto nextWeek = now + kSecondsInWeek; addCurrentVariant(0, nextDay); addVariant(kUntilOneDay, lng_rights_chat_banned_day(lt_count, 1)); addCurrentVariant(nextDay, nextWeek); addVariant(kUntilOneWeek, lng_rights_chat_banned_week(lt_count, 1)); addCurrentVariant(nextWeek, INT_MAX); addVariant(kUntilCustom, lang(lng_rights_chat_banned_custom)); } TimeId EditRestrictedBox::getRealUntilValue() const { Expects(_until != kUntilCustom); if (_until == kUntilOneDay) { return unixtime() + kSecondsInDay; } else if (_until == kUntilOneWeek) { return unixtime() + kSecondsInWeek; } Assert(_until >= 0); return _until; } ����������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/edit_participant_box.h�����������������������������������0000664�0000000�0000000�00000007162�13262451251�0025115�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "boxes/abstract_box.h" namespace Ui { class FlatLabel; class LinkButton; class Checkbox; class Radiobutton; class RadiobuttonGroup; } // namespace Ui class CalendarBox; class EditParticipantBox : public BoxContent { public: EditParticipantBox(QWidget*, not_null<ChannelData*> channel, not_null<UserData*> user, bool hasAdminRights); protected: void prepare() override; void resizeToContent(); not_null<UserData*> user() const { return _user; } not_null<ChannelData*> channel() const { return _channel; } template <typename Widget> QPointer<Widget> addControl(object_ptr<Widget> widget, QMargins margin); void removeControl(QPointer<TWidget> widget); bool hasAdminRights() const { return _hasAdminRights; } private: not_null<ChannelData*> _channel; not_null<UserData*> _user; bool _hasAdminRights = false; class Inner; QPointer<Inner> _inner; }; class EditAdminBox : public EditParticipantBox { public: EditAdminBox(QWidget*, not_null<ChannelData*> channel, not_null<UserData*> user, const MTPChannelAdminRights &rights); void setSaveCallback(base::lambda<void(MTPChannelAdminRights, MTPChannelAdminRights)> callback) { _saveCallback = std::move(callback); } protected: void prepare() override; private: using Flag = MTPDchannelAdminRights::Flag; using Flags = MTPDchannelAdminRights::Flags; static MTPChannelAdminRights DefaultRights(not_null<ChannelData*> channel); bool canSave() const { return !!_saveCallback; } void applyDependencies(QPointer<Ui::Checkbox> changed); void refreshAboutAddAdminsText(); const MTPChannelAdminRights _oldRights; std::vector<std::pair<Flag, Flag>> _dependencies; base::lambda<void(MTPChannelAdminRights, MTPChannelAdminRights)> _saveCallback; std::map<Flags, QPointer<Ui::Checkbox>> _checkboxes; QPointer<Ui::FlatLabel> _aboutAddAdmins; }; // Restricted box works with flags in the opposite way. // If some flag is set in the rights then the checkbox is unchecked. class EditRestrictedBox : public EditParticipantBox { public: EditRestrictedBox(QWidget*, not_null<ChannelData*> channel, not_null<UserData*> user, bool hasAdminRights, const MTPChannelBannedRights &rights); void setSaveCallback(base::lambda<void(MTPChannelBannedRights, MTPChannelBannedRights)> callback) { _saveCallback = std::move(callback); } protected: void prepare() override; private: using Flag = MTPDchannelBannedRights::Flag; using Flags = MTPDchannelBannedRights::Flags; static MTPChannelBannedRights DefaultRights(not_null<ChannelData*> channel); bool canSave() const { return !!_saveCallback; } void applyDependencies(QPointer<Ui::Checkbox> changed); void showRestrictUntil(); void setRestrictUntil(TimeId until); bool isUntilForever() { return ChannelData::IsRestrictedForever(_until); } void clearVariants(); void createUntilGroup(); void createUntilVariants(); TimeId getRealUntilValue() const; const MTPChannelBannedRights _oldRights; TimeId _until = 0; std::vector<std::pair<Flag, Flag>> _dependencies; base::lambda<void(MTPChannelBannedRights, MTPChannelBannedRights)> _saveCallback; std::map<Flags, QPointer<Ui::Checkbox>> _checkboxes; std::shared_ptr<Ui::RadiobuttonGroup> _untilGroup; QVector<QPointer<Ui::Radiobutton>> _untilVariants; QPointer<CalendarBox> _restrictUntilBox; static constexpr auto kUntilOneDay = -1; static constexpr auto kUntilOneWeek = -2; static constexpr auto kUntilCustom = -3; }; ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/edit_privacy_box.cpp�������������������������������������0000664�0000000�0000000�00000032740�13262451251�0024607�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/edit_privacy_box.h" #include "styles/style_boxes.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/labels.h" #include "ui/widgets/buttons.h" #include "ui/wrap/slide_wrap.h" #include "history/history.h" #include "boxes/peer_list_controllers.h" #include "apiwrap.h" #include "auth_session.h" #include "lang/lang_keys.h" namespace { class PrivacyExceptionsBoxController : public ChatsListBoxController { public: PrivacyExceptionsBoxController(base::lambda<QString()> titleFactory, const std::vector<not_null<UserData*>> &selected); void rowClicked(not_null<PeerListRow*> row) override; std::vector<not_null<UserData*>> getResult() const; protected: void prepareViewHook() override; std::unique_ptr<Row> createRow(not_null<History*> history) override; private: base::lambda<QString()> _titleFactory; std::vector<not_null<UserData*>> _selected; }; PrivacyExceptionsBoxController::PrivacyExceptionsBoxController(base::lambda<QString()> titleFactory, const std::vector<not_null<UserData*>> &selected) : _titleFactory(std::move(titleFactory)) , _selected(selected) { } void PrivacyExceptionsBoxController::prepareViewHook() { delegate()->peerListSetTitle(_titleFactory); delegate()->peerListAddSelectedRows(_selected); } std::vector<not_null<UserData*>> PrivacyExceptionsBoxController::getResult() const { auto peers = delegate()->peerListCollectSelectedRows(); auto users = std::vector<not_null<UserData*>>(); if (!peers.empty()) { users.reserve(peers.size()); for_const (auto peer, peers) { auto user = peer->asUser(); Assert(user != nullptr); users.push_back(user); } } return users; } void PrivacyExceptionsBoxController::rowClicked(not_null<PeerListRow*> row) { delegate()->peerListSetRowChecked(row, !row->checked()); } std::unique_ptr<PrivacyExceptionsBoxController::Row> PrivacyExceptionsBoxController::createRow(not_null<History*> history) { if (history->peer->isSelf()) { return nullptr; } if (auto user = history->peer->asUser()) { return std::make_unique<Row>(history); } return nullptr; } } // namespace EditPrivacyBox::EditPrivacyBox(QWidget*, std::unique_ptr<Controller> controller) : BoxContent() , _controller(std::move(controller)) , _loading(this, lang(lng_contacts_loading), Ui::FlatLabel::InitType::Simple, st::membersAbout) { } void EditPrivacyBox::prepare() { _controller->setView(this); setTitle([this] { return _controller->title(); }); addButton(langFactory(lng_cancel), [this] { closeBox(); }); loadData(); setDimensions(st::boxWideWidth, countDefaultHeight(st::boxWideWidth)); } int EditPrivacyBox::resizeGetHeight(int newWidth) { auto top = 0; auto layoutRow = [&](auto &widget, style::margins padding) { if (!widget) return; widget->resizeToNaturalWidth(newWidth - padding.left() - padding.right()); widget->moveToLeft(padding.left(), top + padding.top()); top = widget->bottomNoMargins() + padding.bottom(); }; layoutRow(_description, st::editPrivacyPadding); layoutRow(_everyone, st::editPrivacyOptionMargin); layoutRow(_contacts, st::editPrivacyOptionMargin); layoutRow(_nobody, st::editPrivacyOptionMargin); layoutRow(_warning, st::editPrivacyWarningPadding); layoutRow(_exceptionsTitle, st::editPrivacyTitlePadding); auto linksTop = top; layoutRow(_alwaysLink, st::editPrivacyPadding); layoutRow(_neverLink, st::editPrivacyPadding); auto linksHeight = top - linksTop; layoutRow(_exceptionsDescription, st::editPrivacyPadding); // Add full width of both links in any case auto linkMargins = exceptionLinkMargins(); top -= linksHeight; top += linkMargins.top() + st::boxLinkButton.font->height + linkMargins.bottom(); top += linkMargins.top() + st::boxLinkButton.font->height + linkMargins.bottom(); return top; } void EditPrivacyBox::resizeEvent(QResizeEvent *e) { if (_loading) { _loading->moveToLeft((width() - _loading->width()) / 2, height() / 3); } } int EditPrivacyBox::countDefaultHeight(int newWidth) { auto height = 0; auto optionHeight = [this](Option option) { if (!_controller->hasOption(option)) { return 0; } return st::editPrivacyOptionMargin.top() + st::defaultCheck.diameter + st::editPrivacyOptionMargin.bottom(); }; auto labelHeight = [newWidth](const QString &text, const style::FlatLabel &st, style::margins padding) { if (text.isEmpty()) { return 0; } auto fake = object_ptr<Ui::FlatLabel>(nullptr, text, Ui::FlatLabel::InitType::Simple, st); fake->resizeToNaturalWidth(newWidth - padding.left() - padding.right()); return padding.top() + fake->heightNoMargins() + padding.bottom(); }; auto linkHeight = [this]() { auto linkMargins = exceptionLinkMargins(); return linkMargins.top() + st::boxLinkButton.font->height + linkMargins.bottom(); }; height += labelHeight(_controller->description(), st::editPrivacyLabel, st::editPrivacyPadding); height += optionHeight(Option::Everyone); height += optionHeight(Option::Contacts); height += optionHeight(Option::Nobody); height += labelHeight(_controller->warning(), st::editPrivacyLabel, st::editPrivacyWarningPadding); height += labelHeight(lang(lng_edit_privacy_exceptions), st::editPrivacyTitle, st::editPrivacyTitlePadding); height += linkHeight(); height += linkHeight(); height += labelHeight(_controller->exceptionsDescription(), st::editPrivacyLabel, st::editPrivacyPadding); return height; } void EditPrivacyBox::editExceptionUsers(Exception exception) { auto controller = std::make_unique<PrivacyExceptionsBoxController>(base::lambda_guarded(this, [this, exception] { return _controller->exceptionBoxTitle(exception); }), exceptionUsers(exception)); auto initBox = [this, exception, controller = controller.get()](not_null<PeerListBox*> box) { box->addButton(langFactory(lng_settings_save), base::lambda_guarded(this, [this, box, exception, controller] { exceptionUsers(exception) = controller->getResult(); exceptionLink(exception)->entity()->setText(exceptionLinkText(exception)); auto removeFrom = ([exception] { switch (exception) { case Exception::Always: return Exception::Never; case Exception::Never: return Exception::Always; } Unexpected("Invalid exception value."); })(); auto &removeFromUsers = exceptionUsers(removeFrom); auto removedSome = false; for (auto user : exceptionUsers(exception)) { auto removedStart = std::remove(removeFromUsers.begin(), removeFromUsers.end(), user); if (removedStart != removeFromUsers.end()) { removeFromUsers.erase(removedStart, removeFromUsers.end()); removedSome = true; } } if (removedSome) { exceptionLink(removeFrom)->entity()->setText(exceptionLinkText(removeFrom)); } box->closeBox(); })); box->addButton(langFactory(lng_cancel), [box] { box->closeBox(); }); }; Ui::show( Box<PeerListBox>(std::move(controller), std::move(initBox)), LayerOption::KeepOther); } QString EditPrivacyBox::exceptionLinkText(Exception exception) { return _controller->exceptionLinkText(exception, exceptionUsers(exception).size()); } QVector<MTPInputPrivacyRule> EditPrivacyBox::collectResult() { auto collectInputUsers = [](auto &users) { auto result = QVector<MTPInputUser>(); result.reserve(users.size()); for (auto user : users) { result.push_back(user->inputUser); } return result; }; constexpr auto kMaxRules = 3; // allow users, disallow users, option auto result = QVector<MTPInputPrivacyRule>(); result.reserve(kMaxRules); if (showExceptionLink(Exception::Always) && !_alwaysUsers.empty()) { result.push_back(MTP_inputPrivacyValueAllowUsers(MTP_vector<MTPInputUser>(collectInputUsers(_alwaysUsers)))); } if (showExceptionLink(Exception::Never) && !_neverUsers.empty()) { result.push_back(MTP_inputPrivacyValueDisallowUsers(MTP_vector<MTPInputUser>(collectInputUsers(_neverUsers)))); } switch (_option) { case Option::Everyone: result.push_back(MTP_inputPrivacyValueAllowAll()); break; case Option::Contacts: result.push_back(MTP_inputPrivacyValueAllowContacts()); break; case Option::Nobody: result.push_back(MTP_inputPrivacyValueDisallowAll()); break; } return result; } style::margins EditPrivacyBox::exceptionLinkMargins() const { return st::editPrivacyLinkMargin; } std::vector<not_null<UserData*>> &EditPrivacyBox::exceptionUsers(Exception exception) { switch (exception) { case Exception::Always: return _alwaysUsers; case Exception::Never: return _neverUsers; } Unexpected("Invalid exception value."); } object_ptr<Ui::SlideWrap<Ui::LinkButton>> &EditPrivacyBox::exceptionLink(Exception exception) { switch (exception) { case Exception::Always: return _alwaysLink; case Exception::Never: return _neverLink; } Unexpected("Invalid exception value."); } bool EditPrivacyBox::showExceptionLink(Exception exception) const { switch (exception) { case Exception::Always: return (_option == Option::Contacts) || (_option == Option::Nobody); case Exception::Never: return (_option == Option::Everyone) || (_option == Option::Contacts); } Unexpected("Invalid exception value."); } void EditPrivacyBox::createWidgets() { _loading.destroy(); _optionGroup = std::make_shared<Ui::RadioenumGroup<Option>>(_option); auto createOption = [this](object_ptr<Ui::Radioenum<Option>> &widget, Option option, const QString &label) { if (_controller->hasOption(option) || (_option == option)) { widget.create(this, _optionGroup, option, label, st::defaultBoxCheckbox); } }; auto createLabel = [this](object_ptr<Ui::FlatLabel> &widget, const QString &text, const style::FlatLabel &st) { if (text.isEmpty()) { return; } widget.create(this, text, Ui::FlatLabel::InitType::Simple, st); }; auto createExceptionLink = [this](Exception exception) { exceptionLink(exception).create(this, object_ptr<Ui::LinkButton>(this, exceptionLinkText(exception)), exceptionLinkMargins()); exceptionLink(exception)->heightValue( ) | rpl::start_with_next([this] { resizeToWidth(width()); }, lifetime()); exceptionLink(exception)->entity()->setClickedCallback([this, exception] { editExceptionUsers(exception); }); }; createLabel(_description, _controller->description(), st::editPrivacyLabel); createOption(_everyone, Option::Everyone, lang(lng_edit_privacy_everyone)); createOption(_contacts, Option::Contacts, lang(lng_edit_privacy_contacts)); createOption(_nobody, Option::Nobody, lang(lng_edit_privacy_nobody)); createLabel(_warning, _controller->warning(), st::editPrivacyLabel); createLabel(_exceptionsTitle, lang(lng_edit_privacy_exceptions), st::editPrivacyTitle); createExceptionLink(Exception::Always); createExceptionLink(Exception::Never); createLabel(_exceptionsDescription, _controller->exceptionsDescription(), st::editPrivacyLabel); clearButtons(); addButton(langFactory(lng_settings_save), [this] { auto someAreDisallowed = (_option != Option::Everyone) || !_neverUsers.empty(); _controller->confirmSave(someAreDisallowed, base::lambda_guarded(this, [this] { Auth().api().savePrivacy(_controller->key(), collectResult()); closeBox(); })); }); addButton(langFactory(lng_cancel), [this] { closeBox(); }); _optionGroup->setChangedCallback([this](Option value) { _option = value; _alwaysLink->toggle( showExceptionLink(Exception::Always), anim::type::normal); _neverLink->toggle( showExceptionLink(Exception::Never), anim::type::normal); }); showChildren(); _alwaysLink->toggle( showExceptionLink(Exception::Always), anim::type::instant); _neverLink->toggle( showExceptionLink(Exception::Never), anim::type::instant); setDimensions(st::boxWideWidth, resizeGetHeight(st::boxWideWidth)); } void EditPrivacyBox::loadData() { request(MTPaccount_GetPrivacy(_controller->key())).done([this](const MTPaccount_PrivacyRules &result) { Expects(result.type() == mtpc_account_privacyRules); auto &rules = result.c_account_privacyRules(); App::feedUsers(rules.vusers); // This is simplified version of privacy rules interpretation. // But it should be fine for all the apps that use the same subset of features. auto optionSet = false; auto setOption = [this, &optionSet](Option option) { if (optionSet) return; optionSet = true; _option = option; }; auto feedRule = [this, &setOption](const MTPPrivacyRule &rule) { switch (rule.type()) { case mtpc_privacyValueAllowAll: setOption(Option::Everyone); break; case mtpc_privacyValueAllowContacts: setOption(Option::Contacts); break; case mtpc_privacyValueAllowUsers: { auto &users = rule.c_privacyValueAllowUsers().vusers.v; _alwaysUsers.reserve(_alwaysUsers.size() + users.size()); for (auto &userId : users) { auto user = App::user(UserId(userId.v)); if (!base::contains(_neverUsers, user) && !base::contains(_alwaysUsers, user)) { _alwaysUsers.push_back(user); } } } break; case mtpc_privacyValueDisallowContacts: // not supported, fall through case mtpc_privacyValueDisallowAll: setOption(Option::Nobody); break; case mtpc_privacyValueDisallowUsers: { auto &users = rule.c_privacyValueDisallowUsers().vusers.v; _neverUsers.reserve(_neverUsers.size() + users.size()); for (auto &userId : users) { auto user = App::user(UserId(userId.v)); if (!base::contains(_alwaysUsers, user) && !base::contains(_neverUsers, user)) { _neverUsers.push_back(user); } } } break; } }; for (auto &rule : rules.vrules.v) { feedRule(rule); } feedRule(MTP_privacyValueDisallowAll()); // disallow by default. createWidgets(); }).send(); } ��������������������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/edit_privacy_box.h���������������������������������������0000664�0000000�0000000�00000005667�13262451251�0024264�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "boxes/abstract_box.h" #include "mtproto/sender.h" namespace Ui { class FlatLabel; class LinkButton; template <typename Enum> class RadioenumGroup; template <typename Enum> class Radioenum; template <typename Widget> class SlideWrap; } // namespace Ui class EditPrivacyBox : public BoxContent, private MTP::Sender { public: enum class Option { Everyone, Contacts, Nobody, }; enum class Exception { Always, Never, }; class Controller { public: virtual MTPInputPrivacyKey key() = 0; virtual QString title() = 0; virtual bool hasOption(Option option) { return true; } virtual QString description() = 0; virtual QString warning() { return QString(); } virtual QString exceptionLinkText(Exception exception, int count) = 0; virtual QString exceptionBoxTitle(Exception exception) = 0; virtual QString exceptionsDescription() = 0; virtual void confirmSave(bool someAreDisallowed, base::lambda_once<void()> saveCallback) { saveCallback(); } virtual ~Controller() = default; protected: EditPrivacyBox *view() const { return _view; } private: void setView(EditPrivacyBox *box) { _view = box; } EditPrivacyBox *_view = nullptr; friend class EditPrivacyBox; }; EditPrivacyBox(QWidget*, std::unique_ptr<Controller> controller); protected: void prepare() override; int resizeGetHeight(int newWidth) override; void resizeEvent(QResizeEvent *e) override; private: style::margins exceptionLinkMargins() const; bool showExceptionLink(Exception exception) const; void createWidgets(); QVector<MTPInputPrivacyRule> collectResult(); void loadData(); int countDefaultHeight(int newWidth); void editExceptionUsers(Exception exception); QString exceptionLinkText(Exception exception); std::vector<not_null<UserData*>> &exceptionUsers(Exception exception); object_ptr<Ui::SlideWrap<Ui::LinkButton>> &exceptionLink(Exception exception); std::unique_ptr<Controller> _controller; Option _option = Option::Everyone; std::shared_ptr<Ui::RadioenumGroup<Option>> _optionGroup; object_ptr<Ui::FlatLabel> _loading; object_ptr<Ui::FlatLabel> _description = { nullptr }; object_ptr<Ui::Radioenum<Option>> _everyone = { nullptr }; object_ptr<Ui::Radioenum<Option>> _contacts = { nullptr }; object_ptr<Ui::Radioenum<Option>> _nobody = { nullptr }; object_ptr<Ui::FlatLabel> _warning = { nullptr }; object_ptr<Ui::FlatLabel> _exceptionsTitle = { nullptr }; object_ptr<Ui::SlideWrap<Ui::LinkButton>> _alwaysLink = { nullptr }; object_ptr<Ui::SlideWrap<Ui::LinkButton>> _neverLink = { nullptr }; object_ptr<Ui::FlatLabel> _exceptionsDescription = { nullptr }; std::vector<not_null<UserData*>> _alwaysUsers; std::vector<not_null<UserData*>> _neverUsers; }; �������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/language_box.cpp�����������������������������������������0000664�0000000�0000000�00000010676�13262451251�0023714�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/language_box.h" #include "lang/lang_keys.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/buttons.h" #include "storage/localstorage.h" #include "boxes/confirm_box.h" #include "mainwidget.h" #include "mainwindow.h" #include "lang/lang_instance.h" #include "lang/lang_cloud_manager.h" #include "styles/style_boxes.h" class LanguageBox::Inner : public TWidget, private base::Subscriber { public: Inner(QWidget *parent, not_null<Languages*> languages); void setSelected(int index); void refresh(); private: void activateCurrent(); void languageChanged(int languageIndex); not_null<Languages*> _languages; std::shared_ptr<Ui::RadiobuttonGroup> _group; std::vector<object_ptr<Ui::Radiobutton>> _buttons; }; LanguageBox::Inner::Inner(QWidget *parent, not_null<Languages*> languages) : TWidget(parent) , _languages(languages) { _group = std::make_shared<Ui::RadiobuttonGroup>(0); _group->setChangedCallback([this](int value) { languageChanged(value); }); subscribe(Lang::Current().updated(), [this] { activateCurrent(); refresh(); }); } void LanguageBox::Inner::setSelected(int index) { _group->setValue(index); } void LanguageBox::Inner::refresh() { for (auto &button : _buttons) { button.destroy(); } _buttons.clear(); auto y = st::boxOptionListPadding.top() + st::langsButton.margin.top(); _buttons.reserve(_languages->size()); auto index = 0; for_const (auto &language, *_languages) { _buttons.emplace_back(this, _group, index++, language.nativeName, st::langsButton); auto button = _buttons.back().data(); button->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), y); button->show(); y += button->heightNoMargins() + st::boxOptionListSkip; } auto newHeight = y - st::boxOptionListSkip + st::boxOptionListPadding.bottom() + st::langsButton.margin.bottom(); resize(st::langsWidth, newHeight); } void LanguageBox::Inner::languageChanged(int languageIndex) { Expects(languageIndex >= 0 && languageIndex < _languages->size()); activateCurrent(); auto languageId = (*_languages)[languageIndex].id; if (Lang::Current().id() != languageId) { // "custom" is applied each time it is passed to switchToLanguage(). // So we check that the language really has changed. Lang::CurrentCloudManager().switchToLanguage(languageId); } } void LanguageBox::Inner::activateCurrent() { auto currentId = Lang::Current().id(); for (auto i = 0, count = _languages->size(); i != count; ++i) { auto languageId = (*_languages)[i].id; auto isCurrent = (languageId == currentId) || (languageId == Lang::DefaultLanguageId() && currentId.isEmpty()); if (isCurrent) { _group->setValue(i); return; } } } void LanguageBox::prepare() { refreshLang(); subscribe(Lang::Current().updated(), [this] { refreshLang(); }); _inner = setInnerWidget(object_ptr<Inner>(this, &_languages), st::boxLayerScroll); refresh(); subscribe(Lang::CurrentCloudManager().languageListChanged(), [this] { refresh(); }); } void LanguageBox::refreshLang() { clearButtons(); addButton(langFactory(lng_box_ok), [this] { closeBox(); }); setTitle(langFactory(lng_languages)); update(); } void LanguageBox::refresh() { refreshLanguages(); _inner->refresh(); setDimensions(st::langsWidth, qMin(_inner->height(), st::boxMaxListHeight)); } void LanguageBox::refreshLanguages() { _languages = Languages(); auto list = Lang::CurrentCloudManager().languageList(); _languages.reserve(list.size() + 1); auto currentId = Lang::Current().id(); auto currentIndex = -1; _languages.push_back({ qsl("en"), qsl("English"), qsl("English") }); for (auto &language : list) { auto isCurrent = (language.id == currentId) || (language.id == Lang::DefaultLanguageId() && currentId.isEmpty()); if (language.id != qstr("en")) { if (isCurrent) { currentIndex = _languages.size(); } _languages.push_back(language); } else if (isCurrent) { currentIndex = 0; } } if (currentId == qstr("custom")) { _languages.insert(_languages.begin(), { currentId, qsl("Custom LangPack"), qsl("Custom LangPack") }); currentIndex = 0; } else if (currentIndex < 0) { currentIndex = _languages.size(); _languages.push_back({ currentId, lang(lng_language_name), lang(lng_language_name) }); } _inner->setSelected(currentIndex); } ������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/language_box.h�������������������������������������������0000664�0000000�0000000�00000001366�13262451251�0023355�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "lang/lang_cloud_manager.h" #include "boxes/abstract_box.h" #include "mtproto/sender.h" namespace Ui { class RadiobuttonGroup; class Radiobutton; } // namespace Ui class LanguageBox : public BoxContent, private MTP::Sender { public: LanguageBox(QWidget*) { } protected: void prepare() override; private: using Languages = Lang::CloudManager::Languages; void refresh(); void refreshLanguages(); void refreshLang(); Languages _languages; class Inner; QPointer<Inner> _inner; }; ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/local_storage_box.cpp������������������������������������0000664�0000000�0000000�00000007432�13262451251�0024743�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/local_storage_box.h" #include "styles/style_boxes.h" #include "ui/widgets/buttons.h" #include "storage/localstorage.h" #include "lang/lang_keys.h" #include "mainwindow.h" #include "auth_session.h" #include "layout.h" LocalStorageBox::LocalStorageBox(QWidget *parent) : _clear(this, lang(lng_local_storage_clear), st::boxLinkButton) { } void LocalStorageBox::prepare() { setTitle(langFactory(lng_local_storage_title)); addButton(langFactory(lng_box_ok), [this] { closeBox(); }); _clear->setClickedCallback([this] { clearStorage(); }); connect(App::wnd(), SIGNAL(tempDirCleared(int)), this, SLOT(onTempDirCleared(int))); connect(App::wnd(), SIGNAL(tempDirClearFailed(int)), this, SLOT(onTempDirClearFailed(int))); subscribe(Auth().downloaderTaskFinished(), [this] { update(); }); updateControls(); checkLocalStoredCounts(); } void LocalStorageBox::updateControls() { auto rowsHeight = 0; if (_imagesCount > 0 && _audiosCount > 0) { rowsHeight = 2 * (st::linkFont->height + st::localStorageBoxSkip); } else { rowsHeight = st::linkFont->height + st::localStorageBoxSkip; } _clear->setVisible(_imagesCount > 0 || _audiosCount > 0); setDimensions(st::boxWidth, st::localStorageBoxSkip + rowsHeight + _clear->height()); _clear->moveToLeft(st::boxPadding.left(), st::localStorageBoxSkip + rowsHeight); update(); } void LocalStorageBox::checkLocalStoredCounts() { int imagesCount = Local::hasImages() + Local::hasStickers() + Local::hasWebFiles(); int audiosCount = Local::hasAudios(); if (imagesCount != _imagesCount || audiosCount != _audiosCount) { _imagesCount = imagesCount; _audiosCount = audiosCount; if (_imagesCount > 0 || _audiosCount > 0) { _state = State::Normal; } updateControls(); } } void LocalStorageBox::paintEvent(QPaintEvent *e) { BoxContent::paintEvent(e); Painter p(this); p.setFont(st::boxTextFont); p.setPen(st::windowFg); checkLocalStoredCounts(); auto top = st::localStorageBoxSkip; if (_imagesCount > 0) { auto text = lng_settings_images_cached(lt_count, _imagesCount, lt_size, formatSizeText(Local::storageImagesSize() + Local::storageStickersSize() + Local::storageWebFilesSize())); p.drawTextLeft(st::boxPadding.left(), top, width(), text); top += st::boxTextFont->height + st::localStorageBoxSkip; } if (_audiosCount > 0) { auto text = lng_settings_audios_cached(lt_count, _audiosCount, lt_size, formatSizeText(Local::storageAudiosSize())); p.drawTextLeft(st::boxPadding.left(), top, width(), text); top += st::boxTextFont->height + st::localStorageBoxSkip; } else if (_imagesCount <= 0) { p.drawTextLeft(st::boxPadding.left(), top, width(), lang(lng_settings_no_data_cached)); top += st::boxTextFont->height + st::localStorageBoxSkip; } auto text = ([this]() -> QString { switch (_state) { case State::Clearing: return lang(lng_local_storage_clearing); case State::Cleared: return lang(lng_local_storage_cleared); case State::ClearFailed: return Lang::Hard::ClearPathFailed(); } return QString(); })(); if (!text.isEmpty()) { p.drawTextLeft(st::boxPadding.left(), top, width(), text); top += st::boxTextFont->height + st::localStorageBoxSkip; } } void LocalStorageBox::clearStorage() { App::wnd()->tempDirDelete(Local::ClearManagerStorage); _state = State::Clearing; updateControls(); } void LocalStorageBox::onTempDirCleared(int task) { if (task & Local::ClearManagerStorage) { _state = State::Cleared; } updateControls(); } void LocalStorageBox::onTempDirClearFailed(int task) { if (task & Local::ClearManagerStorage) { _state = State::ClearFailed; } updateControls(); } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/local_storage_box.h��������������������������������������0000664�0000000�0000000�00000001562�13262451251�0024406�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "boxes/abstract_box.h" namespace Ui { class LinkButton; } // namespace Ui class LocalStorageBox : public BoxContent { Q_OBJECT public: LocalStorageBox(QWidget*); private slots: void onTempDirCleared(int task); void onTempDirClearFailed(int task); protected: void prepare() override; void paintEvent(QPaintEvent *e) override; private: void clearStorage(); void updateControls(); void checkLocalStoredCounts(); enum class State { Normal, Clearing, Cleared, ClearFailed, }; State _state = State::Normal; object_ptr<Ui::LinkButton> _clear; int _imagesCount = -1; int _audiosCount = -1; }; ����������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/mute_settings_box.cpp������������������������������������0000664�0000000�0000000�00000004741�13262451251�0025017�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. This code is in Public Domain, see license terms in .github/CONTRIBUTING.md Copyright (C) 2017, Nicholas Guriev <guriev-ns@ya.ru> */ #include "boxes/mute_settings_box.h" #include "lang/lang_keys.h" #include "mainwidget.h" #include "styles/style_boxes.h" #include "ui/special_buttons.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/labels.h" namespace { constexpr auto kForeverHours = 24 * 365; } // namespace void MuteSettingsBox::prepare() { setTitle(langFactory(lng_disable_notifications_from_tray)); auto y = 0; object_ptr<Ui::FlatLabel> info(this, st::boxLabel); info->setText(lang(lng_mute_box_tip)); info->moveToLeft(st::boxPadding.left(), y); y += info->height() + st::boxLittleSkip; const auto icon = object_ptr<Ui::UserpicButton>( this, controller(), _peer, Ui::UserpicButton::Role::Custom, st::mutePhotoButton); icon->setPointerCursor(false); icon->moveToLeft(st::boxPadding.left(), y); object_ptr<Ui::FlatLabel> title(this, st::muteChatTitle); title->setText(App::peerName(_peer, true)); title->moveToLeft( st::boxPadding.left() + st::muteChatTitleLeft, y + (icon->height() / 2) - (title->height() / 2)); // the icon is always higher than this chat title y += icon->height() + st::boxMediumSkip; // in fact, this is mute only for 1 year const auto group = std::make_shared<Ui::RadiobuttonGroup>(kForeverHours); y += st::boxOptionListPadding.top(); for (const auto hours : { 1, 4, 18, 72, kForeverHours }) { const auto text = [&] { if (hours < 24) { return lng_mute_duration_hours(lt_count, hours); } else if (hours < kForeverHours) { return lng_rights_chat_banned_day(lt_count, hours / 24); } else { return lang(lng_rights_chat_banned_forever); } }(); object_ptr<Ui::Radiobutton> option(this, group, hours, text); option->moveToLeft(st::boxPadding.left(), y); y += option->heightNoMargins() + st::boxOptionListSkip; } y += st::boxOptionListPadding.bottom() - st::boxOptionListSkip + st::defaultCheckbox.margin.bottom(); addButton(langFactory(lng_box_ok), [this, group] { auto muteForSeconds = group->value() * 3600; App::main()->updateNotifySettings( _peer, Data::NotifySettings::MuteChange::Mute, Data::NotifySettings::SilentPostsChange::Ignore, muteForSeconds); closeBox(); }); addButton(langFactory(lng_cancel), [this] { closeBox(); }); setDimensions(st::boxWidth, y); } // vi: ts=4 tw=80 �������������������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/mute_settings_box.h��������������������������������������0000664�0000000�0000000�00000001340�13262451251�0024454�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. This code is in Public Domain, see license terms in .github/CONTRIBUTING.md Copyright (C) 2017, Nicholas Guriev <guriev-ns@ya.ru> */ #pragma once #include "boxes/abstract_box.h" /* This class implements a dialog-box with radio-buttons for pick duration of * turning off notifications from a chat. The widget is opened by a context menu * in the left list of dialogues. */ class MuteSettingsBox : public BoxContent { Q_OBJECT public: MuteSettingsBox(QWidget *parent, not_null<PeerData*> peer) : _peer(peer) { } protected: void prepare() override; private: not_null<PeerData*> _peer; }; // vi: ts=4 tw=80 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/notifications_box.cpp������������������������������������0000664�0000000�0000000�00000035561�13262451251�0025002�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/notifications_box.h" #include "lang/lang_keys.h" #include "ui/widgets/buttons.h" #include "ui/widgets/discrete_sliders.h" #include "styles/style_boxes.h" #include "styles/style_dialogs.h" #include "styles/style_window.h" #include "messenger.h" #include "storage/localstorage.h" #include "auth_session.h" #include "window/notifications_manager.h" #include "platform/platform_specific.h" namespace { constexpr int kMaxNotificationsCount = 5; using ChangeType = Window::Notifications::ChangeType; } // namespace class NotificationsBox::SampleWidget : public QWidget { public: SampleWidget(NotificationsBox *owner, const QPixmap &cache) : QWidget(nullptr) , _owner(owner) , _cache(cache) { resize(cache.width() / cache.devicePixelRatio(), cache.height() / cache.devicePixelRatio()); setWindowFlags(Qt::WindowFlags(Qt::FramelessWindowHint) | Qt::WindowStaysOnTopHint | Qt::BypassWindowManagerHint | Qt::NoDropShadowWindowHint | Qt::Tool); setAttribute(Qt::WA_MacAlwaysShowToolWindow); setAttribute(Qt::WA_TransparentForMouseEvents); setAttribute(Qt::WA_OpaquePaintEvent); setWindowOpacity(0.); show(); } void detach() { _owner = nullptr; hideFast(); } void showFast() { _hiding = false; startAnimation(); } void hideFast() { _hiding = true; startAnimation(); } protected: virtual void paintEvent(QPaintEvent *e) { Painter p(this); p.drawPixmap(0, 0, _cache); } private: void startAnimation() { _opacity.start([this] { animationCallback(); }, _hiding ? 1. : 0., _hiding ? 0. : 1., st::notifyFastAnim); } void animationCallback() { setWindowOpacity(_opacity.current(_hiding ? 0. : 1.)); if (!_opacity.animating() && _hiding) { if (_owner) { _owner->removeSample(this); } hide(); destroyDelayed(); } } void destroyDelayed() { if (_deleted) return; _deleted = true; // Ubuntu has a lag if deleteLater() called immediately. #if defined Q_OS_LINUX32 || defined Q_OS_LINUX64 QTimer::singleShot(1000, [this] { delete this; }); #else // Q_OS_LINUX32 || Q_OS_LINUX64 deleteLater(); #endif // Q_OS_LINUX32 || Q_OS_LINUX64 } NotificationsBox *_owner; QPixmap _cache; Animation _opacity; bool _hiding = false; bool _deleted = false; }; NotificationsBox::NotificationsBox(QWidget *parent) : _chosenCorner(Global::NotificationsCorner()) , _oldCount(snap(Global::NotificationsCount(), 1, kMaxNotificationsCount)) , _countSlider(this) { } void NotificationsBox::prepare() { addButton(langFactory(lng_close), [this] { closeBox(); }); _sampleOpacities.reserve(kMaxNotificationsCount); for (int i = 0; i != kMaxNotificationsCount; ++i) { _countSlider->addSection(QString::number(i + 1)); _sampleOpacities.push_back(Animation()); } _countSlider->setActiveSectionFast(_oldCount - 1); _countSlider->sectionActivated( ) | rpl::start_with_next( [this] { countChanged(); }, lifetime()); setMouseTracking(true); prepareNotificationSampleSmall(); prepareNotificationSampleLarge(); setDimensions(st::boxWideWidth, st::notificationsBoxHeight); } void NotificationsBox::paintEvent(QPaintEvent *e) { BoxContent::paintEvent(e); Painter p(this); auto contentLeft = getContentLeft(); p.setFont(st::boxTitleFont); p.setPen(st::boxTitleFg); p.drawTextLeft(contentLeft, st::boxTitlePosition.y(), width(), lang(lng_settings_notifications_position)); auto screenRect = getScreenRect(); p.fillRect(screenRect.x(), screenRect.y(), st::notificationsBoxScreenSize.width(), st::notificationsBoxScreenSize.height(), st::notificationsBoxScreenBg); auto monitorTop = st::notificationsBoxMonitorTop; st::notificationsBoxMonitor.paint(p, contentLeft, monitorTop, width()); for (int corner = 0; corner != 4; ++corner) { auto screenCorner = static_cast<Notify::ScreenCorner>(corner); auto isLeft = Notify::IsLeftCorner(screenCorner); auto isTop = Notify::IsTopCorner(screenCorner); auto sampleLeft = isLeft ? (screenRect.x() + st::notificationsSampleSkip) : (screenRect.x() + screenRect.width() - st::notificationsSampleSkip - st::notificationSampleSize.width()); auto sampleTop = isTop ? (screenRect.y() + st::notificationsSampleTopSkip) : (screenRect.y() + screenRect.height() - st::notificationsSampleBottomSkip - st::notificationSampleSize.height()); if (corner == static_cast<int>(_chosenCorner)) { auto count = currentCount(); for (int i = 0; i != kMaxNotificationsCount; ++i) { auto opacity = _sampleOpacities[i].current(getms(), (i < count) ? 1. : 0.); p.setOpacity(opacity); p.drawPixmapLeft(sampleLeft, sampleTop, width(), _notificationSampleSmall); sampleTop += (isTop ? 1 : -1) * (st::notificationSampleSize.height() + st::notificationsSampleMargin); } p.setOpacity(1.); } else { p.setOpacity(st::notificationSampleOpacity); p.drawPixmapLeft(sampleLeft, sampleTop, width(), _notificationSampleSmall); p.setOpacity(1.); } } auto labelTop = screenRect.y() + screenRect.height() + st::notificationsBoxCountLabelTop; p.setFont(st::boxTitleFont); p.setPen(st::boxTitleFg); p.drawTextLeft(contentLeft, labelTop, width(), lang(lng_settings_notifications_count)); } void NotificationsBox::countChanged() { auto count = currentCount(); auto moreSamples = (count > _oldCount); auto from = moreSamples ? 0. : 1.; auto to = moreSamples ? 1. : 0.; auto indexDelta = moreSamples ? 1 : -1; auto animatedDelta = moreSamples ? 0 : -1; for (; _oldCount != count; _oldCount += indexDelta) { _sampleOpacities[_oldCount + animatedDelta].start([this] { update(); }, from, to, st::notifyFastAnim); } if (currentCount() != Global::NotificationsCount()) { Global::SetNotificationsCount(currentCount()); Auth().notifications().settingsChanged().notify(ChangeType::MaxCount); Local::writeUserSettings(); } } int NotificationsBox::getContentLeft() const { return (width() - st::notificationsBoxMonitor.width()) / 2; } QRect NotificationsBox::getScreenRect() const { auto screenLeft = (width() - st::notificationsBoxScreenSize.width()) / 2; auto screenTop = st::notificationsBoxMonitorTop + st::notificationsBoxScreenTop; return QRect(screenLeft, screenTop, st::notificationsBoxScreenSize.width(), st::notificationsBoxScreenSize.height()); } void NotificationsBox::resizeEvent(QResizeEvent *e) { BoxContent::resizeEvent(e); auto screenRect = getScreenRect(); auto sliderTop = screenRect.y() + screenRect.height() + st::notificationsBoxCountLabelTop + st::notificationsBoxCountTop; auto contentLeft = getContentLeft(); _countSlider->resizeToWidth(width() - 2 * contentLeft); _countSlider->move(contentLeft, sliderTop); } void NotificationsBox::prepareNotificationSampleSmall() { auto width = st::notificationSampleSize.width(); auto height = st::notificationSampleSize.height(); auto sampleImage = QImage(width * cIntRetinaFactor(), height * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); sampleImage.setDevicePixelRatio(cRetinaFactor()); sampleImage.fill(st::notificationBg->c); { Painter p(&sampleImage); PainterHighQualityEnabler hq(p); p.setPen(Qt::NoPen); auto padding = height / 8; auto userpicSize = height - 2 * padding; p.setBrush(st::notificationSampleUserpicFg); p.drawEllipse(rtlrect(padding, padding, userpicSize, userpicSize, width)); auto rowLeft = height; auto rowHeight = padding; auto nameTop = (height - 5 * padding) / 2; auto nameWidth = height; p.setBrush(st::notificationSampleNameFg); p.drawRoundedRect(rtlrect(rowLeft, nameTop, nameWidth, rowHeight, width), rowHeight / 2, rowHeight / 2); auto rowWidth = (width - rowLeft - 3 * padding); auto rowTop = nameTop + rowHeight + padding; p.setBrush(st::notificationSampleTextFg); p.drawRoundedRect(rtlrect(rowLeft, rowTop, rowWidth, rowHeight, width), rowHeight / 2, rowHeight / 2); rowTop += rowHeight + padding; p.drawRoundedRect(rtlrect(rowLeft, rowTop, rowWidth, rowHeight, width), rowHeight / 2, rowHeight / 2); auto closeLeft = width - 2 * padding; p.fillRect(rtlrect(closeLeft, padding, padding, padding, width), st::notificationSampleCloseFg); } _notificationSampleSmall = App::pixmapFromImageInPlace(std::move(sampleImage)); _notificationSampleSmall.setDevicePixelRatio(cRetinaFactor()); } void NotificationsBox::prepareNotificationSampleUserpic() { if (_notificationSampleUserpic.isNull()) { _notificationSampleUserpic = App::pixmapFromImageInPlace(Messenger::Instance().logoNoMargin().scaled(st::notifyPhotoSize * cIntRetinaFactor(), st::notifyPhotoSize * cIntRetinaFactor(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); _notificationSampleUserpic.setDevicePixelRatio(cRetinaFactor()); } } void NotificationsBox::prepareNotificationSampleLarge() { int w = st::notifyWidth, h = st::notifyMinHeight; auto sampleImage = QImage(w * cIntRetinaFactor(), h * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); sampleImage.setDevicePixelRatio(cRetinaFactor()); sampleImage.fill(st::notificationBg->c); { Painter p(&sampleImage); p.fillRect(0, 0, w - st::notifyBorderWidth, st::notifyBorderWidth, st::notifyBorder->b); p.fillRect(w - st::notifyBorderWidth, 0, st::notifyBorderWidth, h - st::notifyBorderWidth, st::notifyBorder->b); p.fillRect(st::notifyBorderWidth, h - st::notifyBorderWidth, w - st::notifyBorderWidth, st::notifyBorderWidth, st::notifyBorder->b); p.fillRect(0, st::notifyBorderWidth, st::notifyBorderWidth, h - st::notifyBorderWidth, st::notifyBorder->b); prepareNotificationSampleUserpic(); p.drawPixmap(st::notifyPhotoPos.x(), st::notifyPhotoPos.y(), _notificationSampleUserpic); int itemWidth = w - st::notifyPhotoPos.x() - st::notifyPhotoSize - st::notifyTextLeft - st::notifyClosePos.x() - st::notifyClose.width; auto rectForName = rtlrect(st::notifyPhotoPos.x() + st::notifyPhotoSize + st::notifyTextLeft, st::notifyTextTop, itemWidth, st::msgNameFont->height, w); auto notifyText = st::dialogsTextFont->elided(lang(lng_notification_sample), itemWidth); p.setFont(st::dialogsTextFont); p.setPen(st::dialogsTextFgService); p.drawText(st::notifyPhotoPos.x() + st::notifyPhotoSize + st::notifyTextLeft, st::notifyItemTop + st::msgNameFont->height + st::dialogsTextFont->ascent, notifyText); p.setPen(st::dialogsNameFg); p.setFont(st::msgNameFont); auto notifyTitle = st::msgNameFont->elided(qsl("Telegram Desktop"), rectForName.width()); p.drawText(rectForName.left(), rectForName.top() + st::msgNameFont->ascent, notifyTitle); st::notifyClose.icon.paint(p, w - st::notifyClosePos.x() - st::notifyClose.width + st::notifyClose.iconPosition.x(), st::notifyClosePos.y() + st::notifyClose.iconPosition.y(), w); } _notificationSampleLarge = App::pixmapFromImageInPlace(std::move(sampleImage)); } void NotificationsBox::removeSample(SampleWidget *widget) { for (auto &samples : _cornerSamples) { for (int i = 0, size = samples.size(); i != size; ++i) { if (samples[i] == widget) { for (int j = i + 1; j != size; ++j) { samples[j]->detach(); } samples.resize(i); break; } } } } void NotificationsBox::mouseMoveEvent(QMouseEvent *e) { auto screenRect = getScreenRect(); auto cornerWidth = screenRect.width() / 3; auto cornerHeight = screenRect.height() / 3; auto topLeft = rtlrect(screenRect.x(), screenRect.y(), cornerWidth, cornerHeight, width()); auto topRight = rtlrect(screenRect.x() + screenRect.width() - cornerWidth, screenRect.y(), cornerWidth, cornerHeight, width()); auto bottomRight = rtlrect(screenRect.x() + screenRect.width() - cornerWidth, screenRect.y() + screenRect.height() - cornerHeight, cornerWidth, cornerHeight, width()); auto bottomLeft = rtlrect(screenRect.x(), screenRect.y() + screenRect.height() - cornerHeight, cornerWidth, cornerHeight, width()); if (topLeft.contains(e->pos())) { setOverCorner(Notify::ScreenCorner::TopLeft); } else if (topRight.contains(e->pos())) { setOverCorner(Notify::ScreenCorner::TopRight); } else if (bottomRight.contains(e->pos())) { setOverCorner(Notify::ScreenCorner::BottomRight); } else if (bottomLeft.contains(e->pos())) { setOverCorner(Notify::ScreenCorner::BottomLeft); } else { clearOverCorner(); } } void NotificationsBox::leaveEventHook(QEvent *e) { clearOverCorner(); } void NotificationsBox::setOverCorner(Notify::ScreenCorner corner) { if (_isOverCorner) { if (corner == _overCorner) { return; } for_const (auto widget, _cornerSamples[static_cast<int>(_overCorner)]) { widget->hideFast(); } } else { _isOverCorner = true; setCursor(style::cur_pointer); Global::SetNotificationsDemoIsShown(true); Auth().notifications().settingsChanged().notify(ChangeType::DemoIsShown); } _overCorner = corner; auto &samples = _cornerSamples[static_cast<int>(_overCorner)]; auto samplesAlready = samples.size(); auto samplesNeeded = currentCount(); auto samplesLeave = qMin(samplesAlready, samplesNeeded); for (int i = 0; i != samplesLeave; ++i) { samples[i]->showFast(); } if (samplesNeeded > samplesLeave) { auto r = psDesktopRect(); auto isLeft = Notify::IsLeftCorner(_overCorner); auto isTop = Notify::IsTopCorner(_overCorner); auto sampleLeft = (isLeft == rtl()) ? (r.x() + r.width() - st::notifyWidth - st::notifyDeltaX) : (r.x() + st::notifyDeltaX); auto sampleTop = isTop ? (r.y() + st::notifyDeltaY) : (r.y() + r.height() - st::notifyDeltaY - st::notifyMinHeight); for (int i = samplesLeave; i != samplesNeeded; ++i) { auto widget = std::make_unique<SampleWidget>(this, _notificationSampleLarge); widget->move(sampleLeft, sampleTop + (isTop ? 1 : -1) * i * (st::notifyMinHeight + st::notifyDeltaY)); widget->showFast(); samples.push_back(widget.release()); } } else { for (int i = samplesLeave; i != samplesAlready; ++i) { samples[i]->hideFast(); } } } void NotificationsBox::clearOverCorner() { if (_isOverCorner) { _isOverCorner = false; setCursor(style::cur_default); Global::SetNotificationsDemoIsShown(false); Auth().notifications().settingsChanged().notify(ChangeType::DemoIsShown); for_const (auto &samples, _cornerSamples) { for_const (auto widget, samples) { widget->hideFast(); } } } } int NotificationsBox::currentCount() const { return _countSlider->activeSection() + 1; } void NotificationsBox::mousePressEvent(QMouseEvent *e) { _isDownCorner = _isOverCorner; _downCorner = _overCorner; } void NotificationsBox::mouseReleaseEvent(QMouseEvent *e) { auto isDownCorner = base::take(_isDownCorner); if (isDownCorner && _isOverCorner && _downCorner == _overCorner && _downCorner != _chosenCorner) { _chosenCorner = _downCorner; update(); if (_chosenCorner != Global::NotificationsCorner()) { Global::SetNotificationsCorner(_chosenCorner); Auth().notifications().settingsChanged().notify(ChangeType::Corner); Local::writeUserSettings(); } } } NotificationsBox::~NotificationsBox() { for_const (auto &samples, _cornerSamples) { for_const (auto widget, samples) { widget->detach(); } } clearOverCorner(); } �����������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/notifications_box.h��������������������������������������0000664�0000000�0000000�00000003157�13262451251�0024443�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "boxes/abstract_box.h" namespace Ui { class LinkButton; class SettingsSlider; } // namespace Ui class NotificationsBox : public BoxContent { public: NotificationsBox(QWidget*); ~NotificationsBox(); protected: void prepare() override; void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void leaveEventHook(QEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; private: using ScreenCorner = Notify::ScreenCorner; void countChanged(); void setOverCorner(ScreenCorner corner); void clearOverCorner(); class SampleWidget; void removeSample(SampleWidget *widget); int currentCount() const; QRect getScreenRect() const; int getContentLeft() const; void prepareNotificationSampleSmall(); void prepareNotificationSampleLarge(); void prepareNotificationSampleUserpic(); QPixmap _notificationSampleUserpic; QPixmap _notificationSampleSmall; QPixmap _notificationSampleLarge; ScreenCorner _chosenCorner; std::vector<Animation> _sampleOpacities; bool _isOverCorner = false; ScreenCorner _overCorner = ScreenCorner::TopLeft; bool _isDownCorner = false; ScreenCorner _downCorner = ScreenCorner::TopLeft; int _oldCount; object_ptr<Ui::SettingsSlider> _countSlider; QVector<SampleWidget*> _cornerSamples[4]; }; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/passcode_box.cpp�����������������������������������������0000664�0000000�0000000�00000046166�13262451251�0023735�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/passcode_box.h" #include "lang/lang_keys.h" #include "boxes/confirm_box.h" #include "mainwindow.h" #include "storage/localstorage.h" #include "styles/style_boxes.h" #include "ui/widgets/buttons.h" #include "ui/widgets/input_fields.h" PasscodeBox::PasscodeBox(QWidget*, bool turningOff) : _turningOff(turningOff) , _about(st::boxWidth - st::boxPadding.left() * 1.5) , _oldPasscode(this, st::defaultInputField, langFactory(lng_passcode_enter_old)) , _newPasscode(this, st::defaultInputField, langFactory(Global::LocalPasscode() ? lng_passcode_enter_new : lng_passcode_enter_first)) , _reenterPasscode(this, st::defaultInputField, langFactory(lng_passcode_confirm_new)) , _passwordHint(this, st::defaultInputField, langFactory(lng_cloud_password_hint)) , _recoverEmail(this, st::defaultInputField, langFactory(lng_cloud_password_email)) , _recover(this, lang(lng_signin_recover)) { } PasscodeBox::PasscodeBox(QWidget*, const QByteArray &newSalt, const QByteArray &curSalt, bool hasRecovery, const QString &hint, bool turningOff) : _turningOff(turningOff) , _cloudPwd(true) , _newSalt(newSalt) , _curSalt(curSalt) , _hasRecovery(hasRecovery) , _about(st::boxWidth - st::boxPadding.left() * 1.5) , _oldPasscode(this, st::defaultInputField, langFactory(lng_cloud_password_enter_old)) , _newPasscode(this, st::defaultInputField, langFactory(curSalt.isEmpty() ? lng_cloud_password_enter_first : lng_cloud_password_enter_new)) , _reenterPasscode(this, st::defaultInputField, langFactory(lng_cloud_password_confirm_new)) , _passwordHint(this, st::defaultInputField, langFactory(curSalt.isEmpty() ? lng_cloud_password_hint : lng_cloud_password_change_hint)) , _recoverEmail(this, st::defaultInputField, langFactory(lng_cloud_password_email)) , _recover(this, lang(lng_signin_recover)) { if (!hint.isEmpty()) _hintText.setText(st::passcodeTextStyle, lng_signin_hint(lt_password_hint, hint)); } void PasscodeBox::prepare() { addButton(langFactory(_turningOff ? lng_passcode_remove_button : lng_settings_save), [this] { onSave(); }); addButton(langFactory(lng_cancel), [this] { closeBox(); }); _about.setRichText(st::passcodeTextStyle, lang(_cloudPwd ? lng_cloud_password_about : lng_passcode_about)); _aboutHeight = _about.countHeight(st::boxWidth - st::boxPadding.left() * 1.5); if (_turningOff) { _oldPasscode->show(); setTitle(langFactory(_cloudPwd ? lng_cloud_password_remove : lng_passcode_remove)); setDimensions(st::boxWidth, st::passcodePadding.top() + _oldPasscode->height() + st::passcodeTextLine + ((_hasRecovery && !_hintText.isEmpty()) ? st::passcodeTextLine : 0) + st::passcodeAboutSkip + _aboutHeight + st::passcodePadding.bottom()); } else { auto has = _cloudPwd ? (!_curSalt.isEmpty()) : Global::LocalPasscode(); if (has) { _oldPasscode->show(); setTitle(langFactory(_cloudPwd ? lng_cloud_password_change : lng_passcode_change)); setDimensions(st::boxWidth, st::passcodePadding.top() + _oldPasscode->height() + st::passcodeTextLine + ((_hasRecovery && !_hintText.isEmpty()) ? st::passcodeTextLine : 0) + _newPasscode->height() + st::passcodeLittleSkip + _reenterPasscode->height() + st::passcodeSkip + (_cloudPwd ? _passwordHint->height() + st::passcodeLittleSkip : 0) + st::passcodeAboutSkip + _aboutHeight + st::passcodePadding.bottom()); } else { _oldPasscode->hide(); setTitle(langFactory(_cloudPwd ? lng_cloud_password_create : lng_passcode_create)); setDimensions(st::boxWidth, st::passcodePadding.top() + _newPasscode->height() + st::passcodeLittleSkip + _reenterPasscode->height() + st::passcodeSkip + (_cloudPwd ? _passwordHint->height() + st::passcodeLittleSkip : 0) + st::passcodeAboutSkip + _aboutHeight + (_cloudPwd ? (st::passcodeLittleSkip + _recoverEmail->height() + st::passcodeSkip) : st::passcodePadding.bottom())); } } connect(_oldPasscode, SIGNAL(changed()), this, SLOT(onOldChanged())); connect(_newPasscode, SIGNAL(changed()), this, SLOT(onNewChanged())); connect(_reenterPasscode, SIGNAL(changed()), this, SLOT(onNewChanged())); connect(_passwordHint, SIGNAL(changed()), this, SLOT(onNewChanged())); connect(_recoverEmail, SIGNAL(changed()), this, SLOT(onEmailChanged())); connect(_oldPasscode, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); connect(_newPasscode, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); connect(_reenterPasscode, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); connect(_passwordHint, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); connect(_recoverEmail, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); connect(_recover, SIGNAL(clicked()), this, SLOT(onRecoverByEmail())); bool has = _cloudPwd ? (!_curSalt.isEmpty()) : Global::LocalPasscode(); _oldPasscode->setVisible(_turningOff || has); _recover->setVisible((_turningOff || has) && _cloudPwd && _hasRecovery); _newPasscode->setVisible(!_turningOff); _reenterPasscode->setVisible(!_turningOff); _passwordHint->setVisible(!_turningOff && _cloudPwd); _recoverEmail->setVisible(!_turningOff && _cloudPwd && _curSalt.isEmpty()); } void PasscodeBox::onSubmit() { bool has = _cloudPwd ? (!_curSalt.isEmpty()) : Global::LocalPasscode(); if (_oldPasscode->hasFocus()) { if (_turningOff) { onSave(); } else { _newPasscode->setFocus(); } } else if (_newPasscode->hasFocus()) { _reenterPasscode->setFocus(); } else if (_reenterPasscode->hasFocus()) { if (has && _oldPasscode->text().isEmpty()) { _oldPasscode->setFocus(); _oldPasscode->showError(); } else if (_newPasscode->text().isEmpty()) { _newPasscode->setFocus(); _newPasscode->showError(); } else if (_reenterPasscode->text().isEmpty()) { _reenterPasscode->showError(); } else if (!_passwordHint->isHidden()) { _passwordHint->setFocus(); } else { onSave(); } } else if (_passwordHint->hasFocus()) { if (_recoverEmail->isHidden()) { onSave(); } else { _recoverEmail->setFocus(); } } else if (_recoverEmail->hasFocus()) { onSave(); } } void PasscodeBox::paintEvent(QPaintEvent *e) { BoxContent::paintEvent(e); Painter p(this); int32 w = st::boxWidth - st::boxPadding.left() * 1.5; int32 abouty = (_passwordHint->isHidden() ? ((_reenterPasscode->isHidden() ? (_oldPasscode->y() + (_hasRecovery && !_hintText.isEmpty() ? st::passcodeTextLine : 0)) : _reenterPasscode->y()) + st::passcodeSkip) : _passwordHint->y()) + _oldPasscode->height() + st::passcodeLittleSkip + st::passcodeAboutSkip; p.setPen(st::boxTextFg); _about.drawLeft(p, st::boxPadding.left(), abouty, w, width()); if (!_hintText.isEmpty() && _oldError.isEmpty()) { _hintText.drawLeftElided(p, st::boxPadding.left(), _oldPasscode->y() + _oldPasscode->height() + ((st::passcodeTextLine - st::normalFont->height) / 2), w, width(), 1, style::al_topleft); } if (!_oldError.isEmpty()) { p.setPen(st::boxTextFgError); p.drawText(QRect(st::boxPadding.left(), _oldPasscode->y() + _oldPasscode->height(), w, st::passcodeTextLine), _oldError, style::al_left); } if (!_newError.isEmpty()) { p.setPen(st::boxTextFgError); p.drawText(QRect(st::boxPadding.left(), _reenterPasscode->y() + _reenterPasscode->height(), w, st::passcodeTextLine), _newError, style::al_left); } if (!_emailError.isEmpty()) { p.setPen(st::boxTextFgError); p.drawText(QRect(st::boxPadding.left(), _recoverEmail->y() + _recoverEmail->height(), w, st::passcodeTextLine), _emailError, style::al_left); } } void PasscodeBox::resizeEvent(QResizeEvent *e) { BoxContent::resizeEvent(e); bool has = _cloudPwd ? (!_curSalt.isEmpty()) : Global::LocalPasscode(); int32 w = st::boxWidth - st::boxPadding.left() - st::boxPadding.right(); _oldPasscode->resize(w, _oldPasscode->height()); _oldPasscode->moveToLeft(st::boxPadding.left(), st::passcodePadding.top()); _newPasscode->resize(w, _newPasscode->height()); _newPasscode->moveToLeft(st::boxPadding.left(), _oldPasscode->y() + ((_turningOff || has) ? (_oldPasscode->height() + st::passcodeTextLine + ((_hasRecovery && !_hintText.isEmpty()) ? st::passcodeTextLine : 0)) : 0)); _reenterPasscode->resize(w, _reenterPasscode->height()); _reenterPasscode->moveToLeft(st::boxPadding.left(), _newPasscode->y() + _newPasscode->height() + st::passcodeLittleSkip); _passwordHint->resize(w, _passwordHint->height()); _passwordHint->moveToLeft(st::boxPadding.left(), _reenterPasscode->y() + _reenterPasscode->height() + st::passcodeSkip); _recoverEmail->resize(w, _passwordHint->height()); _recoverEmail->moveToLeft(st::boxPadding.left(), _passwordHint->y() + _passwordHint->height() + st::passcodeLittleSkip + _aboutHeight + st::passcodeLittleSkip); if (!_recover->isHidden()) { _recover->moveToLeft(st::boxPadding.left(), _oldPasscode->y() + _oldPasscode->height() + (_hintText.isEmpty() ? ((st::passcodeTextLine - _recover->height()) / 2) : st::passcodeTextLine)); } } void PasscodeBox::setInnerFocus() { if (_skipEmailWarning && !_recoverEmail->isHidden()) { _recoverEmail->setFocusFast(); } else if (_oldPasscode->isHidden()) { _newPasscode->setFocusFast(); } else { _oldPasscode->setFocusFast(); } } void PasscodeBox::setPasswordDone(const MTPBool &result) { _setRequest = 0; emit reloadPassword(); auto text = lang(_reenterPasscode->isHidden() ? lng_cloud_password_removed : (_oldPasscode->isHidden() ? lng_cloud_password_was_set : lng_cloud_password_updated)); Ui::show(Box<InformBox>(text)); } void PasscodeBox::closeReplacedBy() { if (isHidden()) { if (_replacedBy && !_replacedBy->isHidden()) { _replacedBy->closeBox(); } } } bool PasscodeBox::setPasswordFail(const RPCError &error) { if (MTP::isFloodError(error)) { if (_oldPasscode->isHidden()) return false; closeReplacedBy(); _setRequest = 0; _oldPasscode->selectAll(); _oldPasscode->setFocus(); _oldPasscode->showError(); _oldError = lang(lng_flood_error); if (_hasRecovery && _hintText.isEmpty()) { _recover->hide(); } update(); return true; } if (MTP::isDefaultHandledError(error)) return false; closeReplacedBy(); _setRequest = 0; QString err = error.type(); if (err == qstr("PASSWORD_HASH_INVALID")) { if (_oldPasscode->isHidden()) { emit reloadPassword(); closeBox(); } else { onBadOldPasscode(); } } else if (err == qstr("NEW_PASSWORD_BAD")) { _newPasscode->setFocus(); _newPasscode->showError(); _newError = lang(lng_cloud_password_bad); update(); } else if (err == qstr("NEW_SALT_INVALID")) { emit reloadPassword(); closeBox(); } else if (err == qstr("EMAIL_INVALID")) { _emailError = lang(lng_cloud_password_bad_email); _recoverEmail->setFocus(); _recoverEmail->showError(); update(); } else if (err == qstr("EMAIL_UNCONFIRMED")) { Ui::show(Box<InformBox>(lang(lng_cloud_password_almost))); emit reloadPassword(); } return true; } void PasscodeBox::onSave(bool force) { if (_setRequest) return; QString old = _oldPasscode->text(), pwd = _newPasscode->text(), conf = _reenterPasscode->text(); bool has = _cloudPwd ? (!_curSalt.isEmpty()) : Global::LocalPasscode(); if (!_cloudPwd && (_turningOff || has)) { if (!passcodeCanTry()) { _oldError = lang(lng_flood_error); _oldPasscode->setFocus(); _oldPasscode->showError(); update(); return; } if (Local::checkPasscode(old.toUtf8())) { cSetPasscodeBadTries(0); if (_turningOff) pwd = conf = QString(); } else { cSetPasscodeBadTries(cPasscodeBadTries() + 1); cSetPasscodeLastTry(getms(true)); onBadOldPasscode(); return; } } if (!_turningOff && pwd.isEmpty()) { _newPasscode->setFocus(); _newPasscode->showError(); closeReplacedBy(); return; } if (pwd != conf) { _reenterPasscode->selectAll(); _reenterPasscode->setFocus(); _reenterPasscode->showError(); if (!conf.isEmpty()) { _newError = lang(_cloudPwd ? lng_cloud_password_differ : lng_passcode_differ); update(); } closeReplacedBy(); } else if (!_turningOff && has && old == pwd) { _newPasscode->setFocus(); _newPasscode->showError(); _newError = lang(_cloudPwd ? lng_cloud_password_is_same : lng_passcode_is_same); update(); closeReplacedBy(); } else if (_cloudPwd) { QString hint = _passwordHint->getLastText(), email = _recoverEmail->getLastText().trimmed(); if (_cloudPwd && pwd == hint && !_passwordHint->isHidden() && !_newPasscode->isHidden()) { _newPasscode->setFocus(); _newPasscode->showError(); _newError = lang(lng_cloud_password_bad); update(); closeReplacedBy(); return; } if (!_recoverEmail->isHidden() && email.isEmpty() && !force) { _skipEmailWarning = true; _replacedBy = Ui::show(Box<ConfirmBox>(lang(lng_cloud_password_about_recover), lang(lng_cloud_password_skip_email), st::attentionBoxButton, base::lambda_guarded(this, [this] { onSave(true); })), LayerOption::KeepOther); } else { QByteArray newPasswordData = pwd.isEmpty() ? QByteArray() : (_newSalt + pwd.toUtf8() + _newSalt); QByteArray newPasswordHash = pwd.isEmpty() ? QByteArray() : QByteArray(32, Qt::Uninitialized); if (pwd.isEmpty()) { hint = QString(); email = QString(); } else { hashSha256(newPasswordData.constData(), newPasswordData.size(), newPasswordHash.data()); } QByteArray oldPasswordData = _oldPasscode->isHidden() ? QByteArray() : (_curSalt + old.toUtf8() + _curSalt); QByteArray oldPasswordHash = _oldPasscode->isHidden() ? QByteArray() : QByteArray(32, Qt::Uninitialized); if (!_oldPasscode->isHidden()) { hashSha256(oldPasswordData.constData(), oldPasswordData.size(), oldPasswordHash.data()); } auto flags = MTPDaccount_passwordInputSettings::Flag::f_new_salt | MTPDaccount_passwordInputSettings::Flag::f_new_password_hash | MTPDaccount_passwordInputSettings::Flag::f_hint; if (_oldPasscode->isHidden() || _newPasscode->isHidden()) { flags |= MTPDaccount_passwordInputSettings::Flag::f_email; } MTPaccount_PasswordInputSettings settings(MTP_account_passwordInputSettings(MTP_flags(flags), MTP_bytes(_newSalt), MTP_bytes(newPasswordHash), MTP_string(hint), MTP_string(email))); _setRequest = MTP::send(MTPaccount_UpdatePasswordSettings(MTP_bytes(oldPasswordHash), settings), rpcDone(&PasscodeBox::setPasswordDone), rpcFail(&PasscodeBox::setPasswordFail)); } } else { cSetPasscodeBadTries(0); Local::setPasscode(pwd.toUtf8()); Auth().checkAutoLock(); closeBox(); } } void PasscodeBox::onBadOldPasscode() { _oldPasscode->selectAll(); _oldPasscode->setFocus(); _oldPasscode->showError(); _oldError = lang(_cloudPwd ? lng_cloud_password_wrong : lng_passcode_wrong); if (_hasRecovery && _hintText.isEmpty()) { _recover->hide(); } update(); } void PasscodeBox::onOldChanged() { if (!_oldError.isEmpty()) { _oldError = QString(); if (_hasRecovery && _hintText.isEmpty()) { _recover->show(); } update(); } } void PasscodeBox::onNewChanged() { if (!_newError.isEmpty()) { _newError = QString(); update(); } } void PasscodeBox::onEmailChanged() { if (!_emailError.isEmpty()) { _emailError = QString(); update(); } } void PasscodeBox::onRecoverByEmail() { if (_pattern.isEmpty()) { _pattern = "-"; MTP::send(MTPauth_RequestPasswordRecovery(), rpcDone(&PasscodeBox::recoverStarted), rpcFail(&PasscodeBox::recoverStartFail)); } else { recover(); } } void PasscodeBox::onRecoverExpired() { _pattern = QString(); } void PasscodeBox::recover() { if (_pattern == "-") return; _replacedBy = Ui::show( Box<RecoverBox>(_pattern), LayerOption::KeepOther); connect(_replacedBy, SIGNAL(reloadPassword()), this, SIGNAL(reloadPassword())); connect(_replacedBy, SIGNAL(recoveryExpired()), this, SLOT(onRecoverExpired())); } void PasscodeBox::recoverStarted(const MTPauth_PasswordRecovery &result) { _pattern = qs(result.c_auth_passwordRecovery().vemail_pattern); recover(); } bool PasscodeBox::recoverStartFail(const RPCError &error) { if (MTP::isDefaultHandledError(error)) return false; _pattern = QString(); closeBox(); return true; } RecoverBox::RecoverBox(QWidget*, const QString &pattern) : _pattern(st::normalFont->elided(lng_signin_recover_hint(lt_recover_email, pattern), st::boxWidth - st::boxPadding.left() * 1.5)) , _recoverCode(this, st::defaultInputField, langFactory(lng_signin_code)) { } void RecoverBox::prepare() { setTitle(langFactory(lng_signin_recover_title)); addButton(langFactory(lng_passcode_submit), [this] { onSubmit(); }); addButton(langFactory(lng_cancel), [this] { closeBox(); }); setDimensions(st::boxWidth, st::passcodePadding.top() + st::passcodePadding.bottom() + st::passcodeTextLine + _recoverCode->height() + st::passcodeTextLine); connect(_recoverCode, SIGNAL(changed()), this, SLOT(onCodeChanged())); connect(_recoverCode, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); } void RecoverBox::paintEvent(QPaintEvent *e) { BoxContent::paintEvent(e); Painter p(this); p.setFont(st::normalFont); p.setPen(st::boxTextFg); int32 w = st::boxWidth - st::boxPadding.left() * 1.5; p.drawText(QRect(st::boxPadding.left(), _recoverCode->y() - st::passcodeTextLine - st::passcodePadding.top(), w, st::passcodePadding.top() + st::passcodeTextLine), _pattern, style::al_left); if (!_error.isEmpty()) { p.setPen(st::boxTextFgError); p.drawText(QRect(st::boxPadding.left(), _recoverCode->y() + _recoverCode->height(), w, st::passcodeTextLine), _error, style::al_left); } } void RecoverBox::resizeEvent(QResizeEvent *e) { BoxContent::resizeEvent(e); _recoverCode->resize(st::boxWidth - st::boxPadding.left() - st::boxPadding.right(), _recoverCode->height()); _recoverCode->moveToLeft(st::boxPadding.left(), st::passcodePadding.top() + st::passcodePadding.bottom() + st::passcodeTextLine); } void RecoverBox::setInnerFocus() { _recoverCode->setFocusFast(); } void RecoverBox::onSubmit() { if (_submitRequest) return; QString code = _recoverCode->getLastText().trimmed(); if (code.isEmpty()) { _recoverCode->setFocus(); _recoverCode->showError(); return; } _submitRequest = MTP::send(MTPauth_RecoverPassword(MTP_string(code)), rpcDone(&RecoverBox::codeSubmitDone, true), rpcFail(&RecoverBox::codeSubmitFail)); } void RecoverBox::onCodeChanged() { _error = QString(); update(); } void RecoverBox::codeSubmitDone(bool recover, const MTPauth_Authorization &result) { _submitRequest = 0; emit reloadPassword(); Ui::show(Box<InformBox>(lang(lng_cloud_password_removed))); } bool RecoverBox::codeSubmitFail(const RPCError &error) { if (MTP::isFloodError(error)) { _submitRequest = 0; _error = lang(lng_flood_error); update(); _recoverCode->showError(); return true; } if (MTP::isDefaultHandledError(error)) return false; _submitRequest = 0; const QString &err = error.type(); if (err == qstr("PASSWORD_EMPTY")) { emit reloadPassword(); Ui::show(Box<InformBox>(lang(lng_cloud_password_removed))); return true; } else if (err == qstr("PASSWORD_RECOVERY_NA")) { closeBox(); return true; } else if (err == qstr("PASSWORD_RECOVERY_EXPIRED")) { emit recoveryExpired(); closeBox(); return true; } else if (err == qstr("CODE_INVALID")) { _error = lang(lng_signin_wrong_code); update(); _recoverCode->selectAll(); _recoverCode->setFocus(); _recoverCode->showError(); return true; } if (cDebug()) { // internal server error _error = err + ": " + error.description(); } else { _error = Lang::Hard::ServerError(); } update(); _recoverCode->setFocus(); return false; } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/passcode_box.h�������������������������������������������0000664�0000000�0000000�00000004756�13262451251�0023401�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "boxes/abstract_box.h" namespace Ui { class InputField; class PasswordInput; class LinkButton; } // namespace Ui class PasscodeBox : public BoxContent, public RPCSender { Q_OBJECT public: PasscodeBox(QWidget*, bool turningOff); PasscodeBox(QWidget*, const QByteArray &newSalt, const QByteArray &curSalt, bool hasRecovery, const QString &hint, bool turningOff = false); private slots: void onSave(bool force = false); void onBadOldPasscode(); void onOldChanged(); void onNewChanged(); void onEmailChanged(); void onRecoverByEmail(); void onRecoverExpired(); void onSubmit(); signals: void reloadPassword(); protected: void prepare() override; void setInnerFocus() override; void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; private: void closeReplacedBy(); void setPasswordDone(const MTPBool &result); bool setPasswordFail(const RPCError &error); void recoverStarted(const MTPauth_PasswordRecovery &result); bool recoverStartFail(const RPCError &error); void recover(); QString _pattern; QPointer<BoxContent> _replacedBy; bool _turningOff = false; bool _cloudPwd = false; mtpRequestId _setRequest = 0; QByteArray _newSalt, _curSalt; bool _hasRecovery = false; bool _skipEmailWarning = false; int _aboutHeight = 0; Text _about, _hintText; object_ptr<Ui::PasswordInput> _oldPasscode; object_ptr<Ui::PasswordInput> _newPasscode; object_ptr<Ui::PasswordInput> _reenterPasscode; object_ptr<Ui::InputField> _passwordHint; object_ptr<Ui::InputField> _recoverEmail; object_ptr<Ui::LinkButton> _recover; QString _oldError, _newError, _emailError; }; class RecoverBox : public BoxContent, public RPCSender { Q_OBJECT public: RecoverBox(QWidget*, const QString &pattern); public slots: void onSubmit(); void onCodeChanged(); signals: void reloadPassword(); void recoveryExpired(); protected: void prepare() override; void setInnerFocus() override; void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; private: void codeSubmitDone(bool recover, const MTPauth_Authorization &result); bool codeSubmitFail(const RPCError &error); mtpRequestId _submitRequest = 0; QString _pattern; object_ptr<Ui::InputField> _recoverCode; QString _error; }; ������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/peer_list_box.cpp����������������������������������������0000664�0000000�0000000�00000125665�13262451251�0024124�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/peer_list_box.h" #include <rpl/range.h> #include "styles/style_boxes.h" #include "styles/style_dialogs.h" #include "styles/style_widgets.h" #include "auth_session.h" #include "mainwidget.h" #include "ui/widgets/multi_select.h" #include "ui/widgets/labels.h" #include "ui/widgets/scroll_area.h" #include "ui/widgets/popup_menu.h" #include "ui/effects/round_checkbox.h" #include "ui/effects/ripple_animation.h" #include "ui/empty_userpic.h" #include "ui/wrap/slide_wrap.h" #include "ui/text_options.h" #include "lang/lang_keys.h" #include "observer_peer.h" #include "storage/file_download.h" #include "data/data_peer_values.h" #include "window/themes/window_theme.h" PeerListBox::PeerListBox( QWidget*, std::unique_ptr<PeerListController> controller, base::lambda<void(not_null<PeerListBox*>)> init) : _controller(std::move(controller)) , _init(std::move(init)) { Expects(_controller != nullptr); } void PeerListBox::createMultiSelect() { Expects(_select == nullptr); auto entity = object_ptr<Ui::MultiSelect>(this, st::contactsMultiSelect, langFactory(lng_participant_filter)); _select.create(this, std::move(entity)); _select->heightValue( ) | rpl::start_with_next( [this] { updateScrollSkips(); }, lifetime()); _select->entity()->setSubmittedCallback([this](bool chtrlShiftEnter) { content()->submitted(); }); _select->entity()->setQueryChangedCallback([this](const QString &query) { searchQueryChanged(query); }); _select->entity()->setItemRemovedCallback([this](uint64 itemId) { if (auto peer = App::peerLoaded(itemId)) { if (auto row = peerListFindRow(peer->id)) { content()->changeCheckState(row, false, PeerListRow::SetStyle::Animated); update(); } _controller->itemDeselectedHook(peer); } }); _select->resizeToWidth(st::boxWideWidth); _select->moveToLeft(0, 0); } int PeerListBox::getTopScrollSkip() const { auto result = 0; if (_select && !_select->isHidden()) { result += _select->height(); } return result; } void PeerListBox::updateScrollSkips() { // If we show / hide the search field scroll top is fixed. // If we resize search field by bubbles scroll bottom is fixed. setInnerTopSkip(getTopScrollSkip(), _scrollBottomFixed); if (!_select->animating()) { _scrollBottomFixed = true; } } void PeerListBox::prepare() { setContent(setInnerWidget( object_ptr<PeerListContent>( this, _controller.get(), st::peerListBox), st::boxLayerScroll)); content()->resizeToWidth(st::boxWideWidth); _controller->setDelegate(this); setDimensions(st::boxWideWidth, st::boxMaxListHeight); if (_select) { _select->finishAnimating(); Ui::SendPendingMoveResizeEvents(_select); _scrollBottomFixed = true; onScrollToY(0); } content()->scrollToRequests( ) | rpl::start_with_next([this](Ui::ScrollToRequest request) { onScrollToY(request.ymin, request.ymax); }, lifetime()); if (_init) { _init(this); } } void PeerListBox::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Down) { content()->selectSkip(1); } else if (e->key() == Qt::Key_Up) { content()->selectSkip(-1); } else if (e->key() == Qt::Key_PageDown) { content()->selectSkipPage(height(), 1); } else if (e->key() == Qt::Key_PageUp) { content()->selectSkipPage(height(), -1); } else if (e->key() == Qt::Key_Escape && _select && !_select->entity()->getQuery().isEmpty()) { _select->entity()->clearQuery(); } else { BoxContent::keyPressEvent(e); } } void PeerListBox::searchQueryChanged(const QString &query) { onScrollToY(0); content()->searchQueryChanged(query); } void PeerListBox::resizeEvent(QResizeEvent *e) { BoxContent::resizeEvent(e); if (_select) { _select->resizeToWidth(width()); _select->moveToLeft(0, 0); updateScrollSkips(); } content()->resizeToWidth(width()); } void PeerListBox::paintEvent(QPaintEvent *e) { Painter p(this); for (auto rect : e->region().rects()) { p.fillRect(rect, st::contactsBg); } } void PeerListBox::setInnerFocus() { if (!_select || !_select->toggled()) { content()->setFocus(); } else { _select->entity()->setInnerFocus(); } } void PeerListBox::peerListSetRowChecked( not_null<PeerListRow*> row, bool checked) { auto peer = row->peer(); if (checked) { addSelectItem(peer, PeerListRow::SetStyle::Animated); PeerListContentDelegate::peerListSetRowChecked(row, checked); peerListUpdateRow(row); // This call deletes row from _searchRows. _select->entity()->clearQuery(); } else { // The itemRemovedCallback will call changeCheckState() here. _select->entity()->removeItem(peer->id); peerListUpdateRow(row); } } void PeerListBox::peerListScrollToTop() { onScrollToY(0); } void PeerListBox::peerListSetSearchMode(PeerListSearchMode mode) { PeerListContentDelegate::peerListSetSearchMode(mode); auto selectVisible = (mode != PeerListSearchMode::Disabled); if (selectVisible && !_select) { createMultiSelect(); _select->toggle(!selectVisible, anim::type::instant); } if (_select) { _select->toggle(selectVisible, anim::type::normal); _scrollBottomFixed = false; setInnerFocus(); } } PeerListController::PeerListController(std::unique_ptr<PeerListSearchController> searchController) : _searchController(std::move(searchController)) { if (_searchController) { _searchController->setDelegate(this); } } bool PeerListController::hasComplexSearch() const { return (_searchController != nullptr); } void PeerListController::search(const QString &query) { Expects(hasComplexSearch()); _searchController->searchQuery(query); } void PeerListController::peerListSearchAddRow(not_null<PeerData*> peer) { if (auto row = delegate()->peerListFindRow(peer->id)) { Assert(row->id() == row->peer()->id); delegate()->peerListAppendFoundRow(row); } else if (auto row = createSearchRow(peer)) { Assert(row->id() == row->peer()->id); delegate()->peerListAppendSearchRow(std::move(row)); } } void PeerListController::peerListSearchRefreshRows() { delegate()->peerListRefreshRows(); } rpl::producer<int> PeerListController::onlineCountValue() const { return rpl::single(0); } void PeerListController::setDescriptionText(const QString &text) { if (text.isEmpty()) { setDescription(nullptr); } else { setDescription(object_ptr<Ui::FlatLabel>(nullptr, text, Ui::FlatLabel::InitType::Simple, st::membersAbout)); } } void PeerListController::setSearchLoadingText(const QString &text) { if (text.isEmpty()) { setSearchLoading(nullptr); } else { setSearchLoading(object_ptr<Ui::FlatLabel>(nullptr, text, Ui::FlatLabel::InitType::Simple, st::membersAbout)); } } void PeerListController::setSearchNoResultsText(const QString &text) { if (text.isEmpty()) { setSearchNoResults(nullptr); } else { setSearchNoResults(object_ptr<Ui::FlatLabel>(nullptr, text, Ui::FlatLabel::InitType::Simple, st::membersAbout)); } } base::unique_qptr<Ui::PopupMenu> PeerListController::rowContextMenu( not_null<PeerListRow*> row) { return nullptr; } std::unique_ptr<PeerListState> PeerListController::saveState() const { return delegate()->peerListSaveState(); } void PeerListController::restoreState( std::unique_ptr<PeerListState> state) { delegate()->peerListRestoreState(std::move(state)); } void PeerListBox::addSelectItem(not_null<PeerData*> peer, PeerListRow::SetStyle style) { if (!_select) { createMultiSelect(); _select->hide(anim::type::instant); } const auto respect = _controller->respectSavedMessagesChat(); const auto text = (respect && peer->isSelf()) ? lang(lng_saved_short) : peer->shortName(); const auto callback = PaintUserpicCallback(peer, respect); if (style == PeerListRow::SetStyle::Fast) { _select->entity()->addItemInBunch( peer->id, text, st::activeButtonBg, std::move(callback)); } else { _select->entity()->addItem( peer->id, text, st::activeButtonBg, std::move(callback)); } } void PeerListBox::peerListFinishSelectedRowsBunch() { Expects(_select != nullptr); _select->entity()->finishItemsBunch(); } bool PeerListBox::peerListIsRowSelected(not_null<PeerData*> peer) { return _select ? _select->entity()->hasItem(peer->id) : false; } int PeerListBox::peerListSelectedRowsCount() { return _select ? _select->entity()->getItemsCount() : 0; } std::vector<not_null<PeerData*>> PeerListBox::peerListCollectSelectedRows() { auto result = std::vector<not_null<PeerData*>> {}; auto items = _select ? _select->entity()->getItems() : QVector<uint64> {}; if (!items.empty()) { result.reserve(items.size()); for_const (auto itemId, items) { result.push_back(App::peer(itemId)); } } return result; } PeerListRow::PeerListRow(not_null<PeerData*> peer) : PeerListRow(peer, peer->id) { } PeerListRow::PeerListRow(not_null<PeerData*> peer, PeerListRowId id) : _id(id) , _peer(peer) , _initialized(false) , _isSearchResult(false) , _isSavedMessagesChat(false) { } bool PeerListRow::checked() const { return _checkbox && _checkbox->checked(); } void PeerListRow::setCustomStatus(const QString &status) { setStatusText(status); _statusType = StatusType::Custom; _statusValidTill = 0; } void PeerListRow::clearCustomStatus() { _statusType = StatusType::Online; refreshStatus(); } void PeerListRow::refreshStatus() { if (!_initialized || _statusType == StatusType::Custom) { return; } _statusType = StatusType::LastSeen; _statusValidTill = 0; if (auto user = peer()->asUser()) { if (_isSavedMessagesChat) { setStatusText(lang(lng_saved_forward_here)); } else { auto time = unixtime(); setStatusText(Data::OnlineText(user, time)); if (Data::OnlineTextActive(user, time)) { _statusType = StatusType::Online; } _statusValidTill = getms() + Data::OnlineChangeTimeout(user, time); } } else if (auto chat = peer()->asChat()) { if (!chat->amIn()) { setStatusText(lang(lng_chat_status_unaccessible)); } else if (chat->count > 0) { setStatusText(lng_chat_status_members(lt_count, chat->count)); } else { setStatusText(lang(lng_group_status)); } } else if (peer()->isMegagroup()) { setStatusText(lang(lng_group_status)); } else if (peer()->isChannel()) { setStatusText(lang(lng_channel_status)); } } TimeMs PeerListRow::refreshStatusTime() const { return _statusValidTill; } void PeerListRow::refreshName(const style::PeerListItem &st) { if (!_initialized) { return; } const auto text = _isSavedMessagesChat ? lang(lng_saved_messages) : peer()->name; _name.setText(st.nameStyle, text, Ui::NameTextOptions()); } PeerListRow::~PeerListRow() = default; void PeerListRow::invalidatePixmapsCache() { if (_checkbox) { _checkbox->invalidateCache(); } } int PeerListRow::nameIconWidth() const { return _peer->isVerified() ? st::dialogsVerifiedIcon.width() : 0; } void PeerListRow::paintNameIcon( Painter &p, int x, int y, int outerWidth, bool selected) { st::dialogsVerifiedIcon.paint(p, x, y, outerWidth); } void PeerListRow::paintStatusText( Painter &p, const style::PeerListItem &st, int x, int y, int availableWidth, int outerWidth, bool selected) { auto statusHasOnlineColor = (_statusType == PeerListRow::StatusType::Online); p.setFont(st::contactsStatusFont); p.setPen(statusHasOnlineColor ? st.statusFgActive : (selected ? st.statusFgOver : st.statusFg)); _status.drawLeftElided(p, x, y, availableWidth, outerWidth); } template <typename UpdateCallback> void PeerListRow::addRipple(const style::PeerListItem &st, QSize size, QPoint point, UpdateCallback updateCallback) { if (!_ripple) { auto mask = Ui::RippleAnimation::rectMask(size); _ripple = std::make_unique<Ui::RippleAnimation>(st.button.ripple, std::move(mask), std::move(updateCallback)); } _ripple->add(point); } void PeerListRow::stopLastRipple() { if (_ripple) { _ripple->lastStop(); } } void PeerListRow::paintRipple(Painter &p, TimeMs ms, int x, int y, int outerWidth) { if (_ripple) { _ripple->paint(p, x, y, outerWidth, ms); if (_ripple->empty()) { _ripple.reset(); } } } void PeerListRow::paintUserpic( Painter &p, const style::PeerListItem &st, TimeMs ms, int x, int y, int outerWidth) { if (_disabledState == State::DisabledChecked) { paintDisabledCheckUserpic(p, st, x, y, outerWidth); } else if (_checkbox) { _checkbox->paint(p, ms, x, y, outerWidth); } else if (_isSavedMessagesChat) { Ui::EmptyUserpic::PaintSavedMessages(p, x, y, outerWidth, st.photoSize); } else { peer()->paintUserpicLeft(p, x, y, outerWidth, st.photoSize); } } // Emulates Ui::RoundImageCheckbox::paint() in a checked state. void PeerListRow::paintDisabledCheckUserpic( Painter &p, const style::PeerListItem &st, int x, int y, int outerWidth) const { auto userpicRadius = st::contactsPhotoCheckbox.imageSmallRadius; auto userpicShift = st::contactsPhotoCheckbox.imageRadius - userpicRadius; auto userpicDiameter = st::contactsPhotoCheckbox.imageRadius * 2; auto userpicLeft = x + userpicShift; auto userpicTop = y + userpicShift; auto userpicEllipse = rtlrect(x, y, userpicDiameter, userpicDiameter, outerWidth); auto userpicBorderPen = st::contactsPhotoDisabledCheckFg->p; userpicBorderPen.setWidth(st::contactsPhotoCheckbox.selectWidth); auto iconDiameter = st::contactsPhotoCheckbox.check.size; auto iconLeft = x + userpicDiameter + st::contactsPhotoCheckbox.selectWidth - iconDiameter; auto iconTop = y + userpicDiameter + st::contactsPhotoCheckbox.selectWidth - iconDiameter; auto iconEllipse = rtlrect(iconLeft, iconTop, iconDiameter, iconDiameter, outerWidth); auto iconBorderPen = st::contactsPhotoCheckbox.check.border->p; iconBorderPen.setWidth(st::contactsPhotoCheckbox.selectWidth); if (_isSavedMessagesChat) { Ui::EmptyUserpic::PaintSavedMessages(p, userpicLeft, userpicTop, outerWidth, userpicRadius * 2); } else { peer()->paintUserpicLeft(p, userpicLeft, userpicTop, outerWidth, userpicRadius * 2); } { PainterHighQualityEnabler hq(p); p.setPen(userpicBorderPen); p.setBrush(Qt::NoBrush); p.drawEllipse(userpicEllipse); p.setPen(iconBorderPen); p.setBrush(st::contactsPhotoDisabledCheckFg); p.drawEllipse(iconEllipse); } st::contactsPhotoCheckbox.check.check.paint(p, iconEllipse.topLeft(), outerWidth); } void PeerListRow::setStatusText(const QString &text) { _status.setText(st::defaultTextStyle, text, Ui::NameTextOptions()); } float64 PeerListRow::checkedRatio() { return _checkbox ? _checkbox->checkedAnimationRatio() : 0.; } void PeerListRow::lazyInitialize(const style::PeerListItem &st) { if (_initialized) { return; } _initialized = true; refreshName(st); refreshStatus(); } void PeerListRow::createCheckbox(base::lambda<void()> updateCallback) { _checkbox = std::make_unique<Ui::RoundImageCheckbox>( st::contactsPhotoCheckbox, std::move(updateCallback), PaintUserpicCallback(_peer, _isSavedMessagesChat)); } void PeerListRow::setCheckedInternal(bool checked, SetStyle style) { Expects(_checkbox != nullptr); using CheckboxStyle = Ui::RoundCheckbox::SetStyle; auto speed = (style == SetStyle::Animated) ? CheckboxStyle::Animated : CheckboxStyle::Fast; _checkbox->setChecked(checked, speed); } PeerListContent::PeerListContent( QWidget *parent, not_null<PeerListController*> controller, const style::PeerList &st) : RpWidget(parent) , _st(st) , _controller(controller) , _rowHeight(_st.item.height) { subscribe(Auth().downloaderTaskFinished(), [this] { update(); }); using UpdateFlag = Notify::PeerUpdate::Flag; auto changes = UpdateFlag::NameChanged | UpdateFlag::PhotoChanged; subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(changes, [this](const Notify::PeerUpdate &update) { if (update.flags & UpdateFlag::PhotoChanged) { this->update(); } else if (update.flags & UpdateFlag::NameChanged) { handleNameChanged(update); } })); subscribe(Window::Theme::Background(), [this](const Window::Theme::BackgroundUpdate &update) { if (update.paletteChanged()) { invalidatePixmapsCache(); } }); _repaintByStatus.setCallback([this] { update(); }); } void PeerListContent::appendRow(std::unique_ptr<PeerListRow> row) { Expects(row != nullptr); if (_rowsById.find(row->id()) == _rowsById.cend()) { row->setAbsoluteIndex(_rows.size()); addRowEntry(row.get()); _rows.push_back(std::move(row)); } } void PeerListContent::appendSearchRow(std::unique_ptr<PeerListRow> row) { Expects(row != nullptr); Expects(showingSearch()); if (_rowsById.find(row->id()) == _rowsById.cend()) { row->setAbsoluteIndex(_searchRows.size()); row->setIsSearchResult(true); addRowEntry(row.get()); _filterResults.push_back(row.get()); _searchRows.push_back(std::move(row)); } } void PeerListContent::appendFoundRow(not_null<PeerListRow*> row) { Expects(showingSearch()); auto index = findRowIndex(row); if (index.value < 0) { _filterResults.push_back(row); } } void PeerListContent::changeCheckState( not_null<PeerListRow*> row, bool checked, PeerListRow::SetStyle style) { row->setChecked( checked, style, [this, row] { updateRow(row); }); } void PeerListContent::addRowEntry(not_null<PeerListRow*> row) { if (_controller->respectSavedMessagesChat() && row->peer()->isSelf()) { row->setIsSavedMessagesChat(true); } _rowsById.emplace(row->id(), row); _rowsByPeer[row->peer()].push_back(row); if (addingToSearchIndex()) { addToSearchIndex(row); } if (_controller->isRowSelected(row->peer())) { Assert(row->id() == row->peer()->id); changeCheckState(row, true, PeerListRow::SetStyle::Fast); } } void PeerListContent::invalidatePixmapsCache() { auto invalidate = [](auto &&row) { row->invalidatePixmapsCache(); }; ranges::for_each(_rows, invalidate); ranges::for_each(_searchRows, invalidate); } bool PeerListContent::addingToSearchIndex() const { // If we started indexing already, we continue. return (_searchMode != PeerListSearchMode::Disabled) || !_searchIndex.empty(); } void PeerListContent::addToSearchIndex(not_null<PeerListRow*> row) { if (row->isSearchResult()) { return; } removeFromSearchIndex(row); row->setNameFirstLetters(row->peer()->nameFirstLetters()); for (auto ch : row->nameFirstLetters()) { _searchIndex[ch].push_back(row); } } void PeerListContent::removeFromSearchIndex(not_null<PeerListRow*> row) { const auto &nameFirstLetters = row->nameFirstLetters(); if (!nameFirstLetters.empty()) { for (auto ch : row->nameFirstLetters()) { auto it = _searchIndex.find(ch); if (it != _searchIndex.cend()) { auto &entry = it->second; entry.erase(std::remove(entry.begin(), entry.end(), row), entry.end()); if (entry.empty()) { _searchIndex.erase(it); } } } row->setNameFirstLetters({}); } } void PeerListContent::prependRow(std::unique_ptr<PeerListRow> row) { Expects(row != nullptr); if (_rowsById.find(row->id()) == _rowsById.cend()) { addRowEntry(row.get()); _rows.insert(_rows.begin(), std::move(row)); refreshIndices(); } } void PeerListContent::prependRowFromSearchResult(not_null<PeerListRow*> row) { if (!row->isSearchResult()) { return; } Assert(_rowsById.find(row->id()) != _rowsById.cend()); auto index = row->absoluteIndex(); Assert(index >= 0 && index < _searchRows.size()); Assert(_searchRows[index].get() == row); row->setIsSearchResult(false); _rows.insert(_rows.begin(), std::move(_searchRows[index])); refreshIndices(); removeRowAtIndex(_searchRows, index); if (addingToSearchIndex()) { addToSearchIndex(row); } } void PeerListContent::refreshIndices() { auto index = 0; for (auto &row : _rows) { row->setAbsoluteIndex(index++); } } void PeerListContent::removeRowAtIndex( std::vector<std::unique_ptr<PeerListRow>> &from, int index) { from.erase(from.begin() + index); for (auto i = index, count = int(from.size()); i != count; ++i) { from[i]->setAbsoluteIndex(i); } } PeerListRow *PeerListContent::findRow(PeerListRowId id) { auto it = _rowsById.find(id); return (it == _rowsById.cend()) ? nullptr : it->second.get(); } void PeerListContent::removeRow(not_null<PeerListRow*> row) { auto index = row->absoluteIndex(); auto isSearchResult = row->isSearchResult(); auto &eraseFrom = isSearchResult ? _searchRows : _rows; Assert(index >= 0 && index < eraseFrom.size()); Assert(eraseFrom[index].get() == row); auto pressedData = saveSelectedData(_pressed); auto contextedData = saveSelectedData(_contexted); setSelected(Selected()); setPressed(Selected()); setContexted(Selected()); _rowsById.erase(row->id()); auto &byPeer = _rowsByPeer[row->peer()]; byPeer.erase(std::remove(byPeer.begin(), byPeer.end(), row), byPeer.end()); removeFromSearchIndex(row); _filterResults.erase( std::find(_filterResults.begin(), _filterResults.end(), row), _filterResults.end()); removeRowAtIndex(eraseFrom, index); restoreSelection(); setPressed(restoreSelectedData(pressedData)); setContexted(restoreSelectedData(contextedData)); } void PeerListContent::clearAllContent() { setSelected(Selected()); setPressed(Selected()); setContexted(Selected()); _rowsById.clear(); _rowsByPeer.clear(); _filterResults.clear(); _searchIndex.clear(); _rows.clear(); _searchRows.clear(); _searchQuery = _normalizedSearchQuery = _mentionHighlight = QString(); } void PeerListContent::convertRowToSearchResult(not_null<PeerListRow*> row) { if (row->isSearchResult()) { return; } else if (!showingSearch() || !_controller->hasComplexSearch()) { return removeRow(row); } auto index = row->absoluteIndex(); Assert(index >= 0 && index < _rows.size()); Assert(_rows[index].get() == row); removeFromSearchIndex(row); row->setIsSearchResult(true); row->setAbsoluteIndex(_searchRows.size()); _searchRows.push_back(std::move(_rows[index])); removeRowAtIndex(_rows, index); } int PeerListContent::fullRowsCount() const { return _rows.size(); } not_null<PeerListRow*> PeerListContent::rowAt(int index) const { Expects(index >= 0 && index < _rows.size()); return _rows[index].get(); } void PeerListContent::setDescription(object_ptr<Ui::FlatLabel> description) { _description = std::move(description); if (_description) { _description->setParent(this); } } void PeerListContent::setSearchLoading(object_ptr<Ui::FlatLabel> loading) { _searchLoading = std::move(loading); if (_searchLoading) { _searchLoading->setParent(this); } } void PeerListContent::setSearchNoResults(object_ptr<Ui::FlatLabel> noResults) { _searchNoResults = std::move(noResults); if (_searchNoResults) { _searchNoResults->setParent(this); } } void PeerListContent::setAboveWidget(object_ptr<TWidget> aboveWidget) { _aboveWidget = std::move(aboveWidget); if (_aboveWidget) { _aboveWidget->setParent(this); } } int PeerListContent::labelHeight() const { auto computeLabelHeight = [](auto &label) { if (!label) { return 0; } return st::membersAboutLimitPadding.top() + label->height() + st::membersAboutLimitPadding.bottom(); }; if (showingSearch()) { if (!_filterResults.empty()) { return 0; } if (_controller->isSearchLoading()) { return computeLabelHeight(_searchLoading); } return computeLabelHeight(_searchNoResults); } return computeLabelHeight(_description); } void PeerListContent::refreshRows() { resizeToWidth(width()); if (_visibleBottom > 0) { checkScrollForPreload(); } updateSelection(); update(); } void PeerListContent::setSearchMode(PeerListSearchMode mode) { if (_searchMode != mode) { if (!addingToSearchIndex()) { for_const (auto &row, _rows) { addToSearchIndex(row.get()); } } _searchMode = mode; if (_controller->hasComplexSearch()) { if (!_searchLoading) { setSearchLoading(object_ptr<Ui::FlatLabel>( this, lang(lng_contacts_loading), Ui::FlatLabel::InitType::Simple, st::membersAbout)); } } else { clearSearchRows(); } } } void PeerListContent::clearSearchRows() { while (!_searchRows.empty()) { removeRow(_searchRows.back().get()); } } void PeerListContent::paintEvent(QPaintEvent *e) { Painter p(this); auto clip = e->rect(); p.fillRect(clip, _st.item.button.textBg); auto repaintByStatusAfter = _repaintByStatus.remainingTime(); auto repaintAfterMin = repaintByStatusAfter; auto rowsTopCached = rowsTop(); auto ms = getms(); auto yFrom = clip.y() - rowsTopCached; auto yTo = clip.y() + clip.height() - rowsTopCached; p.translate(0, rowsTopCached); auto count = shownRowsCount(); if (count > 0) { auto from = floorclamp(yFrom, _rowHeight, 0, count); auto to = ceilclamp(yTo, _rowHeight, 0, count); p.translate(0, from * _rowHeight); for (auto index = from; index != to; ++index) { auto repaintAfter = paintRow(p, ms, RowIndex(index)); if (repaintAfter >= 0 && (repaintAfterMin < 0 || repaintAfterMin > repaintAfter)) { repaintAfterMin = repaintAfter; } p.translate(0, _rowHeight); } } if (repaintAfterMin != repaintByStatusAfter) { Assert(repaintAfterMin >= 0); _repaintByStatus.callOnce(repaintAfterMin); } } int PeerListContent::resizeGetHeight(int newWidth) { _aboveHeight = 0; if (_aboveWidget) { _aboveWidget->resizeToWidth(newWidth); _aboveWidget->moveToLeft(0, 0, newWidth); if (showingSearch()) { _aboveWidget->hide(); } else { _aboveWidget->show(); _aboveHeight = _aboveWidget->height(); } } auto rowsCount = shownRowsCount(); auto labelTop = rowsTop() + qMax(1, shownRowsCount()) * _rowHeight; auto labelWidth = newWidth - 2 * st::contactsPadding.left(); if (_description) { _description->resizeToWidth(labelWidth); _description->moveToLeft(st::contactsPadding.left(), labelTop + st::membersAboutLimitPadding.top(), newWidth); _description->setVisible(!showingSearch()); } if (_searchNoResults) { _searchNoResults->resizeToWidth(labelWidth); _searchNoResults->moveToLeft(st::contactsPadding.left(), labelTop + st::membersAboutLimitPadding.top(), newWidth); _searchNoResults->setVisible(showingSearch() && _filterResults.empty() && !_controller->isSearchLoading()); } if (_searchLoading) { _searchLoading->resizeToWidth(labelWidth); _searchLoading->moveToLeft(st::contactsPadding.left(), labelTop + st::membersAboutLimitPadding.top(), newWidth); _searchLoading->setVisible(showingSearch() && _filterResults.empty() && _controller->isSearchLoading()); } auto label = labelHeight(); return ((label > 0 || rowsCount > 0) ? (labelTop + label) : 0) + _st.padding.bottom(); } void PeerListContent::enterEventHook(QEvent *e) { setMouseTracking(true); } void PeerListContent::leaveEventHook(QEvent *e) { _mouseSelection = false; setMouseTracking(false); setSelected(Selected()); } void PeerListContent::mouseMoveEvent(QMouseEvent *e) { handleMouseMove(e->globalPos()); } void PeerListContent::handleMouseMove(QPoint position) { if (_mouseSelection || _lastMousePosition != position) { _lastMousePosition = position; _mouseSelection = true; updateSelection(); } } void PeerListContent::mousePressEvent(QMouseEvent *e) { _pressButton = e->button(); _mouseSelection = true; _lastMousePosition = e->globalPos(); updateSelection(); setPressed(_selected); if (auto row = getRow(_selected.index)) { auto updateCallback = [this, row, hint = _selected.index] { updateRow(row, hint); }; if (_selected.action) { auto actionRect = getActionRect(row, _selected.index); if (!actionRect.isEmpty()) { auto point = mapFromGlobal(QCursor::pos()) - actionRect.topLeft(); row->addActionRipple(point, std::move(updateCallback)); } } else { auto size = QSize(width(), _rowHeight); auto point = mapFromGlobal(QCursor::pos()) - QPoint(0, getRowTop(_selected.index)); row->addRipple(_st.item, size, point, std::move(updateCallback)); } } } void PeerListContent::mouseReleaseEvent(QMouseEvent *e) { mousePressReleased(e->button()); } void PeerListContent::mousePressReleased(Qt::MouseButton button) { updateRow(_pressed.index); updateRow(_selected.index); auto pressed = _pressed; setPressed(Selected()); if (button == Qt::LeftButton && pressed == _selected) { if (auto row = getRow(pressed.index)) { if (pressed.action) { _controller->rowActionClicked(row); } else { _controller->rowClicked(row); } } } } void PeerListContent::contextMenuEvent(QContextMenuEvent *e) { if (_contextMenu) { _contextMenu->deleteLater(); _contextMenu = nullptr; } setContexted(Selected()); if (e->reason() == QContextMenuEvent::Mouse) { handleMouseMove(e->globalPos()); } setContexted(_selected); if (_pressButton != Qt::LeftButton) { mousePressReleased(_pressButton); } if (const auto row = getRow(_contexted.index)) { _contextMenu = _controller->rowContextMenu(row); if (_contextMenu) { _contextMenu->setDestroyedCallback(base::lambda_guarded( this, [this] { setContexted(Selected()); handleMouseMove(QCursor::pos()); })); _contextMenu->popup(e->globalPos()); e->accept(); } } } void PeerListContent::setPressed(Selected pressed) { if (auto row = getRow(_pressed.index)) { row->stopLastRipple(); row->stopLastActionRipple(); } _pressed = pressed; } TimeMs PeerListContent::paintRow(Painter &p, TimeMs ms, RowIndex index) { auto row = getRow(index); Assert(row != nullptr); row->lazyInitialize(_st.item); auto refreshStatusAt = row->refreshStatusTime(); if (refreshStatusAt >= 0 && ms >= refreshStatusAt) { row->refreshStatus(); refreshStatusAt = row->refreshStatusTime(); } auto peer = row->peer(); auto user = peer->asUser(); auto active = (_contexted.index.value >= 0) ? _contexted : (_pressed.index.value >= 0) ? _pressed : _selected; auto selected = (active.index == index); auto actionSelected = (selected && active.action); auto &bg = selected ? _st.item.button.textBgOver : _st.item.button.textBg; p.fillRect(0, 0, width(), _rowHeight, bg); row->paintRipple(p, ms, 0, 0, width()); row->paintUserpic( p, _st.item, ms, _st.item.photoPosition.x(), _st.item.photoPosition.y(), width()); p.setPen(st::contactsNameFg); auto skipRight = _st.item.photoPosition.x(); auto actionSize = row->actionSize(); auto actionMargins = actionSize.isEmpty() ? QMargins() : row->actionMargins(); auto &name = row->name(); auto namex = _st.item.namePosition.x(); auto namew = width() - namex - skipRight; if (!actionSize.isEmpty()) { namew -= actionMargins.left() + actionSize.width() + actionMargins.right() - skipRight; } auto statusw = namew; if (auto iconWidth = row->nameIconWidth()) { namew -= iconWidth; row->paintNameIcon( p, namex + qMin(name.maxWidth(), namew), _st.item.namePosition.y(), width(), selected); } auto nameCheckedRatio = row->disabled() ? 0. : row->checkedRatio(); p.setPen(anim::pen(st::contactsNameFg, st::contactsNameCheckedFg, nameCheckedRatio)); name.drawLeftElided(p, namex, _st.item.namePosition.y(), namew, width()); if (!actionSize.isEmpty()) { auto actionLeft = width() - actionMargins.right() - actionSize.width(); auto actionTop = actionMargins.top(); row->paintAction( p, ms, actionLeft, actionTop, width(), selected, actionSelected); } p.setFont(st::contactsStatusFont); if (row->isSearchResult() && !_mentionHighlight.isEmpty() && peer->userName().startsWith(_mentionHighlight, Qt::CaseInsensitive)) { auto username = peer->userName(); auto availableWidth = statusw; auto highlightedPart = '@' + username.mid(0, _mentionHighlight.size()); auto grayedPart = username.mid(_mentionHighlight.size()); auto highlightedWidth = st::contactsStatusFont->width(highlightedPart); if (highlightedWidth >= availableWidth || grayedPart.isEmpty()) { if (highlightedWidth > availableWidth) { highlightedPart = st::contactsStatusFont->elided(highlightedPart, availableWidth); } p.setPen(_st.item.statusFgActive); p.drawTextLeft(_st.item.statusPosition.x(), _st.item.statusPosition.y(), width(), highlightedPart); } else { grayedPart = st::contactsStatusFont->elided(grayedPart, availableWidth - highlightedWidth); auto grayedWidth = st::contactsStatusFont->width(grayedPart); p.setPen(_st.item.statusFgActive); p.drawTextLeft(_st.item.statusPosition.x(), _st.item.statusPosition.y(), width(), highlightedPart); p.setPen(selected ? _st.item.statusFgOver : _st.item.statusFg); p.drawTextLeft(_st.item.statusPosition.x() + highlightedWidth, _st.item.statusPosition.y(), width(), grayedPart); } } else { row->paintStatusText(p, _st.item, _st.item.statusPosition.x(), _st.item.statusPosition.y(), statusw, width(), selected); } return (refreshStatusAt - ms); } void PeerListContent::selectSkip(int direction) { if (_pressed.index.value >= 0) { return; } _mouseSelection = false; auto newSelectedIndex = _selected.index.value + direction; auto rowsCount = shownRowsCount(); auto index = 0; auto firstEnabled = -1, lastEnabled = -1; enumerateShownRows([&firstEnabled, &lastEnabled, &index](not_null<PeerListRow*> row) { if (!row->disabled()) { if (firstEnabled < 0) { firstEnabled = index; } lastEnabled = index; } ++index; return true; }); if (firstEnabled < 0) { firstEnabled = rowsCount; lastEnabled = firstEnabled - 1; } Assert(lastEnabled < rowsCount); Assert(firstEnabled - 1 <= lastEnabled); // Always pass through the first enabled item when changing from / to none selected. if ((_selected.index.value > firstEnabled && newSelectedIndex < firstEnabled) || (_selected.index.value < firstEnabled && newSelectedIndex > firstEnabled)) { newSelectedIndex = firstEnabled; } // Snap the index. newSelectedIndex = snap(newSelectedIndex, firstEnabled - 1, lastEnabled); // Skip the disabled rows. if (newSelectedIndex < firstEnabled) { newSelectedIndex = -1; } else if (newSelectedIndex > lastEnabled) { newSelectedIndex = lastEnabled; } else if (getRow(RowIndex(newSelectedIndex))->disabled()) { auto delta = (direction > 0) ? 1 : -1; for (newSelectedIndex += delta; ; newSelectedIndex += delta) { // We must find an enabled row, firstEnabled <= us <= lastEnabled. Assert(newSelectedIndex >= 0 && newSelectedIndex < rowsCount); if (!getRow(RowIndex(newSelectedIndex))->disabled()) { break; } } } _selected.index.value = newSelectedIndex; _selected.action = false; if (newSelectedIndex >= 0) { auto top = (newSelectedIndex > 0) ? getRowTop(RowIndex(newSelectedIndex)) : 0; auto bottom = (newSelectedIndex + 1 < rowsCount) ? getRowTop(RowIndex(newSelectedIndex + 1)) : height(); _scrollToRequests.fire({ top, bottom }); } update(); } void PeerListContent::selectSkipPage(int height, int direction) { auto rowsToSkip = height / _rowHeight; if (!rowsToSkip) return; selectSkip(rowsToSkip * direction); } void PeerListContent::loadProfilePhotos() { if (_visibleTop >= _visibleBottom) return; auto yFrom = _visibleTop; auto yTo = _visibleBottom + (_visibleBottom - _visibleTop) * PreloadHeightsCount; Auth().downloader().clearPriorities(); if (yTo < 0) return; if (yFrom < 0) yFrom = 0; auto rowsCount = shownRowsCount(); if (rowsCount > 0) { auto from = yFrom / _rowHeight; if (from < 0) from = 0; if (from < rowsCount) { auto to = (yTo / _rowHeight) + 1; if (to > rowsCount) to = rowsCount; for (auto index = from; index != to; ++index) { getRow(RowIndex(index))->peer()->loadUserpic(); } } } } void PeerListContent::checkScrollForPreload() { if (_visibleBottom + PreloadHeightsCount * (_visibleBottom - _visibleTop) >= height()) { _controller->loadMoreRows(); } } void PeerListContent::searchQueryChanged(QString query) { auto searchWordsList = TextUtilities::PrepareSearchWords(query); auto normalizedQuery = searchWordsList.join(' '); if (_normalizedSearchQuery != normalizedQuery) { setSearchQuery(query, normalizedQuery); if (_controller->searchInLocal() && !searchWordsList.isEmpty()) { auto minimalList = (const std::vector<not_null<PeerListRow*>>*)nullptr; for_const (auto &searchWord, searchWordsList) { auto searchWordStart = searchWord[0].toLower(); auto it = _searchIndex.find(searchWordStart); if (it == _searchIndex.cend()) { // Some word can't be found in any row. minimalList = nullptr; break; } else if (!minimalList || minimalList->size() > it->second.size()) { minimalList = &it->second; } } if (minimalList) { auto searchWordInNames = []( not_null<PeerData*> peer, const QString &searchWord) { for (auto &nameWord : peer->nameWords()) { if (nameWord.startsWith(searchWord)) { return true; } } return false; }; auto allSearchWordsInNames = [&]( not_null<PeerData*> peer) { for_const (auto &searchWord, searchWordsList) { if (!searchWordInNames(peer, searchWord)) { return false; } } return true; }; _filterResults.reserve(minimalList->size()); for_const (auto row, *minimalList) { if (allSearchWordsInNames(row->peer())) { _filterResults.push_back(row); } } } } if (_controller->hasComplexSearch()) { _controller->search(_searchQuery); } refreshRows(); restoreSelection(); } } std::unique_ptr<PeerListState> PeerListContent::saveState() const { auto result = std::make_unique<PeerListState>(); result->controllerState = std::make_unique<PeerListController::SavedStateBase>(); result->list.reserve(_rows.size()); for (auto &row : _rows) { result->list.push_back(row->peer()); } result->filterResults.reserve(_filterResults.size()); for (auto &row : _filterResults) { result->filterResults.push_back(row->peer()); } result->searchQuery = _searchQuery; return result; } void PeerListContent::restoreState( std::unique_ptr<PeerListState> state) { if (!state || !state->controllerState) { return; } clearAllContent(); for (auto peer : state->list) { if (auto row = _controller->createRestoredRow(peer)) { appendRow(std::move(row)); } } auto query = state->searchQuery; auto searchWords = TextUtilities::PrepareSearchWords(query); setSearchQuery(query, searchWords.join(' ')); for (auto peer : state->filterResults) { if (auto existingRow = findRow(peer->id)) { _filterResults.push_back(existingRow); } else if (auto row = _controller->createSearchRow(peer)) { appendSearchRow(std::move(row)); } } refreshRows(); } void PeerListContent::setSearchQuery( const QString &query, const QString &normalizedQuery) { setSelected(Selected()); setPressed(Selected()); setContexted(Selected()); _searchQuery = query; _normalizedSearchQuery = normalizedQuery; _mentionHighlight = _searchQuery.startsWith('@') ? _searchQuery.mid(1) : _searchQuery; _filterResults.clear(); clearSearchRows(); } void PeerListContent::submitted() { if (const auto row = getRow(_selected.index)) { _controller->rowClicked(row); } else if (showingSearch()) { if (const auto row = getRow(RowIndex(0))) { _controller->rowClicked(row); } } } void PeerListContent::visibleTopBottomUpdated( int visibleTop, int visibleBottom) { _visibleTop = visibleTop; _visibleBottom = visibleBottom; loadProfilePhotos(); checkScrollForPreload(); } void PeerListContent::setSelected(Selected selected) { updateRow(_selected.index); if (_selected != selected) { _selected = selected; updateRow(_selected.index); setCursor(_selected.action ? style::cur_pointer : style::cur_default); } } void PeerListContent::setContexted(Selected contexted) { updateRow(_contexted.index); if (_contexted != contexted) { _contexted = contexted; updateRow(_contexted.index); } } void PeerListContent::restoreSelection() { _lastMousePosition = QCursor::pos(); updateSelection(); } auto PeerListContent::saveSelectedData(Selected from) -> SelectedSaved { if (auto row = getRow(from.index)) { return { row->id(), from }; } return { PeerListRowId(0), from }; } auto PeerListContent::restoreSelectedData(SelectedSaved from) -> Selected { auto result = from.old; if (auto row = findRow(from.id)) { result.index = findRowIndex(row, result.index); } else { result.index.value = -1; } return result; } void PeerListContent::updateSelection() { if (!_mouseSelection) return; auto point = mapFromGlobal(_lastMousePosition); auto in = parentWidget()->rect().contains(parentWidget()->mapFromGlobal(_lastMousePosition)); auto selected = Selected(); auto rowsPointY = point.y() - rowsTop(); selected.index.value = (in && rowsPointY >= 0 && rowsPointY < shownRowsCount() * _rowHeight) ? (rowsPointY / _rowHeight) : -1; if (selected.index.value >= 0) { auto row = getRow(selected.index); if (row->disabled()) { selected = Selected(); } else { if (getActionRect(row, selected.index).contains(point)) { selected.action = true; } } } setSelected(selected); } QRect PeerListContent::getActionRect(not_null<PeerListRow*> row, RowIndex index) const { auto actionSize = row->actionSize(); if (actionSize.isEmpty()) { return QRect(); } auto actionMargins = row->actionMargins(); auto actionRight = actionMargins.right(); auto actionTop = actionMargins.top(); auto actionLeft = width() - actionRight - actionSize.width(); auto rowTop = getRowTop(index); return myrtlrect(actionLeft, rowTop + actionTop, actionSize.width(), actionSize.height()); } int PeerListContent::rowsTop() const { return _aboveHeight + _st.padding.top(); } int PeerListContent::getRowTop(RowIndex index) const { if (index.value >= 0) { return rowsTop() + index.value * _rowHeight; } return -1; } void PeerListContent::updateRow(not_null<PeerListRow*> row, RowIndex hint) { updateRow(findRowIndex(row, hint)); } void PeerListContent::updateRow(RowIndex index) { if (index.value < 0) { return; } auto row = getRow(index); if (row->disabled()) { if (index == _selected.index) { setSelected(Selected()); } if (index == _pressed.index) { setPressed(Selected()); } if (index == _contexted.index) { setContexted(Selected()); } } update(0, getRowTop(index), width(), _rowHeight); } template <typename Callback> bool PeerListContent::enumerateShownRows(Callback callback) { return enumerateShownRows(0, shownRowsCount(), std::move(callback)); } template <typename Callback> bool PeerListContent::enumerateShownRows(int from, int to, Callback callback) { Assert(0 <= from); Assert(from <= to); if (showingSearch()) { Assert(to <= _filterResults.size()); for (auto i = from; i != to; ++i) { if (!callback(_filterResults[i])) { return false; } } } else { Assert(to <= _rows.size()); for (auto i = from; i != to; ++i) { if (!callback(_rows[i].get())) { return false; } } } return true; } PeerListRow *PeerListContent::getRow(RowIndex index) { if (index.value >= 0) { if (showingSearch()) { if (index.value < _filterResults.size()) { return _filterResults[index.value]; } } else if (index.value < _rows.size()) { return _rows[index.value].get(); } } return nullptr; } PeerListContent::RowIndex PeerListContent::findRowIndex( not_null<PeerListRow*> row, RowIndex hint) { if (!showingSearch()) { Assert(!row->isSearchResult()); return RowIndex(row->absoluteIndex()); } auto result = hint; if (getRow(result) == row) { return result; } auto count = shownRowsCount(); for (result.value = 0; result.value != count; ++result.value) { if (getRow(result) == row) { return result; } } result.value = -1; return result; } void PeerListContent::handleNameChanged(const Notify::PeerUpdate &update) { auto byPeer = _rowsByPeer.find(update.peer); if (byPeer != _rowsByPeer.cend()) { for (auto row : byPeer->second) { if (addingToSearchIndex()) { addToSearchIndex(row); } row->refreshName(_st.item); updateRow(row); } } } ���������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/peer_list_box.h������������������������������������������0000664�0000000�0000000�00000053461�13262451251�0023563�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/event_stream.h> #include "ui/rp_widget.h" #include "ui/empty_userpic.h" #include "boxes/abstract_box.h" #include "mtproto/sender.h" #include "base/timer.h" namespace style { struct PeerList; struct PeerListItem; } // namespace style namespace Ui { class RippleAnimation; class RoundImageCheckbox; class MultiSelect; template <typename Widget> class SlideWrap; class FlatLabel; struct ScrollToRequest; class PopupMenu; } // namespace Ui namespace Notify { struct PeerUpdate; } // namespace Notify inline auto PaintUserpicCallback( not_null<PeerData*> peer, bool respectSavedMessagesChat) ->base::lambda<void(Painter &p, int x, int y, int outerWidth, int size)> { if (respectSavedMessagesChat && peer->isSelf()) { return [](Painter &p, int x, int y, int outerWidth, int size) { Ui::EmptyUserpic::PaintSavedMessages(p, x, y, outerWidth, size); }; } return [peer](Painter &p, int x, int y, int outerWidth, int size) { peer->paintUserpicLeft(p, x, y, outerWidth, size); }; } using PeerListRowId = uint64; class PeerListRow { public: PeerListRow(not_null<PeerData*> peer); PeerListRow(not_null<PeerData*> peer, PeerListRowId id); enum class State { Active, Disabled, DisabledChecked, }; void setDisabledState(State state) { _disabledState = state; } // Checked state is controlled by the box with multiselect, // not by the row itself, so there is no setChecked() method. // We can query the checked state from row, but before it is // added to the box it is always false. bool checked() const; not_null<PeerData*> peer() const { return _peer; } PeerListRowId id() const { return _id; } void setCustomStatus(const QString &status); void clearCustomStatus(); virtual ~PeerListRow(); // Box interface. virtual int nameIconWidth() const; virtual void paintNameIcon( Painter &p, int x, int y, int outerWidth, bool selected); virtual QSize actionSize() const { return QSize(); } virtual QMargins actionMargins() const { return QMargins(); } virtual void addActionRipple(QPoint point, base::lambda<void()> updateCallback) { } virtual void stopLastActionRipple() { } virtual void paintAction( Painter &p, TimeMs ms, int x, int y, int outerWidth, bool selected, bool actionSelected) { } void refreshName(const style::PeerListItem &st); const Text &name() const { return _name; } enum class StatusType { Online, LastSeen, Custom, }; void refreshStatus(); TimeMs refreshStatusTime() const; void setAbsoluteIndex(int index) { _absoluteIndex = index; } int absoluteIndex() const { return _absoluteIndex; } bool disabled() const { return (_disabledState != State::Active); } bool isSearchResult() const { return _isSearchResult; } void setIsSearchResult(bool isSearchResult) { _isSearchResult = isSearchResult; } bool isSavedMessagesChat() const { return _isSavedMessagesChat; } void setIsSavedMessagesChat(bool isSavedMessagesChat) { _isSavedMessagesChat = isSavedMessagesChat; } enum class SetStyle { Animated, Fast, }; template <typename UpdateCallback> void setChecked( bool checked, SetStyle style, UpdateCallback callback) { if (checked && !_checkbox) { createCheckbox(std::move(callback)); } setCheckedInternal(checked, style); } void invalidatePixmapsCache(); template <typename UpdateCallback> void addRipple( const style::PeerListItem &st, QSize size, QPoint point, UpdateCallback updateCallback); void stopLastRipple(); void paintRipple(Painter &p, TimeMs ms, int x, int y, int outerWidth); void paintUserpic( Painter &p, const style::PeerListItem &st, TimeMs ms, int x, int y, int outerWidth); float64 checkedRatio(); void setNameFirstLetters(const base::flat_set<QChar> &firstLetters) { _nameFirstLetters = firstLetters; } const base::flat_set<QChar> &nameFirstLetters() const { return _nameFirstLetters; } virtual void lazyInitialize(const style::PeerListItem &st); virtual void paintStatusText( Painter &p, const style::PeerListItem &st, int x, int y, int availableWidth, int outerWidth, bool selected); protected: bool isInitialized() const { return _initialized; } private: void createCheckbox(base::lambda<void()> updateCallback); void setCheckedInternal(bool checked, SetStyle style); void paintDisabledCheckUserpic( Painter &p, const style::PeerListItem &st, int x, int y, int outerWidth) const; void setStatusText(const QString &text); PeerListRowId _id = 0; not_null<PeerData*> _peer; std::unique_ptr<Ui::RippleAnimation> _ripple; std::unique_ptr<Ui::RoundImageCheckbox> _checkbox; Text _name; Text _status; StatusType _statusType = StatusType::Online; TimeMs _statusValidTill = 0; base::flat_set<QChar> _nameFirstLetters; int _absoluteIndex = -1; State _disabledState = State::Active; bool _initialized : 1; bool _isSearchResult : 1; bool _isSavedMessagesChat : 1; }; enum class PeerListSearchMode { Disabled, Enabled, }; struct PeerListState; class PeerListDelegate { public: virtual void peerListSetTitle(base::lambda<QString()> title) = 0; virtual void peerListSetAdditionalTitle(base::lambda<QString()> title) = 0; virtual void peerListSetDescription(object_ptr<Ui::FlatLabel> description) = 0; virtual void peerListSetSearchLoading(object_ptr<Ui::FlatLabel> loading) = 0; virtual void peerListSetSearchNoResults(object_ptr<Ui::FlatLabel> noResults) = 0; virtual void peerListSetAboveWidget(object_ptr<TWidget> aboveWidget) = 0; virtual void peerListSetSearchMode(PeerListSearchMode mode) = 0; virtual void peerListAppendRow(std::unique_ptr<PeerListRow> row) = 0; virtual void peerListAppendSearchRow(std::unique_ptr<PeerListRow> row) = 0; virtual void peerListAppendFoundRow(not_null<PeerListRow*> row) = 0; virtual void peerListPrependRow(std::unique_ptr<PeerListRow> row) = 0; virtual void peerListPrependRowFromSearchResult(not_null<PeerListRow*> row) = 0; virtual void peerListUpdateRow(not_null<PeerListRow*> row) = 0; virtual void peerListRemoveRow(not_null<PeerListRow*> row) = 0; virtual void peerListConvertRowToSearchResult(not_null<PeerListRow*> row) = 0; virtual bool peerListIsRowSelected(not_null<PeerData*> peer) = 0; virtual void peerListSetRowChecked(not_null<PeerListRow*> row, bool checked) = 0; virtual not_null<PeerListRow*> peerListRowAt(int index) = 0; virtual void peerListRefreshRows() = 0; virtual void peerListScrollToTop() = 0; virtual int peerListFullRowsCount() = 0; virtual PeerListRow *peerListFindRow(PeerListRowId id) = 0; virtual void peerListSortRows(base::lambda<bool(const PeerListRow &a, const PeerListRow &b)> compare) = 0; virtual int peerListPartitionRows(base::lambda<bool(const PeerListRow &a)> border) = 0; template <typename PeerDataRange> void peerListAddSelectedRows(PeerDataRange &&range) { for (auto peer : range) { peerListAddSelectedRowInBunch(peer); } peerListFinishSelectedRowsBunch(); } virtual int peerListSelectedRowsCount() = 0; virtual std::vector<not_null<PeerData*>> peerListCollectSelectedRows() = 0; virtual std::unique_ptr<PeerListState> peerListSaveState() const = 0; virtual void peerListRestoreState( std::unique_ptr<PeerListState> state) = 0; virtual ~PeerListDelegate() = default; private: virtual void peerListAddSelectedRowInBunch(not_null<PeerData*> peer) = 0; virtual void peerListFinishSelectedRowsBunch() = 0; }; class PeerListSearchDelegate { public: virtual void peerListSearchAddRow(not_null<PeerData*> peer) = 0; virtual void peerListSearchRefreshRows() = 0; virtual ~PeerListSearchDelegate() = default; }; class PeerListSearchController { public: struct SavedStateBase { virtual ~SavedStateBase() = default; }; virtual void searchQuery(const QString &query) = 0; virtual bool isLoading() = 0; virtual bool loadMoreRows() = 0; virtual ~PeerListSearchController() = default; void setDelegate(not_null<PeerListSearchDelegate*> delegate) { _delegate = delegate; } virtual std::unique_ptr<SavedStateBase> saveState() const { return nullptr; } virtual void restoreState( std::unique_ptr<SavedStateBase> state) { } protected: not_null<PeerListSearchDelegate*> delegate() const { return _delegate; } private: PeerListSearchDelegate *_delegate = nullptr; }; class PeerListController : public PeerListSearchDelegate { public: struct SavedStateBase { virtual ~SavedStateBase() = default; }; // Search works only with RowId == peer->id. PeerListController(std::unique_ptr<PeerListSearchController> searchController = nullptr); void setDelegate(not_null<PeerListDelegate*> delegate) { _delegate = delegate; prepare(); } virtual void prepare() = 0; virtual void rowClicked(not_null<PeerListRow*> row) = 0; virtual void rowActionClicked(not_null<PeerListRow*> row) { } virtual void loadMoreRows() { } virtual void itemDeselectedHook(not_null<PeerData*> peer) { } virtual base::unique_qptr<Ui::PopupMenu> rowContextMenu( not_null<PeerListRow*> row); bool isSearchLoading() const { return _searchController ? _searchController->isLoading() : false; } virtual std::unique_ptr<PeerListRow> createSearchRow( not_null<PeerData*> peer) { return nullptr; } virtual std::unique_ptr<PeerListRow> createRestoredRow( not_null<PeerData*> peer) { return nullptr; } virtual std::unique_ptr<PeerListState> saveState() const; virtual void restoreState( std::unique_ptr<PeerListState> state); bool isRowSelected(not_null<PeerData*> peer) { return delegate()->peerListIsRowSelected(peer); } virtual bool searchInLocal() { return true; } bool hasComplexSearch() const; void search(const QString &query); void peerListSearchAddRow(not_null<PeerData*> peer) override; void peerListSearchRefreshRows() override; virtual bool respectSavedMessagesChat() const { return false; } virtual rpl::producer<int> onlineCountValue() const; rpl::lifetime &lifetime() { return _lifetime; } virtual ~PeerListController() = default; protected: not_null<PeerListDelegate*> delegate() const { return _delegate; } PeerListSearchController *searchController() const { return _searchController.get(); } void setDescriptionText(const QString &text); void setSearchLoadingText(const QString &text); void setSearchNoResultsText(const QString &text); void setDescription(object_ptr<Ui::FlatLabel> description) { delegate()->peerListSetDescription(std::move(description)); } void setSearchLoading(object_ptr<Ui::FlatLabel> loading) { delegate()->peerListSetSearchLoading(std::move(loading)); } void setSearchNoResults(object_ptr<Ui::FlatLabel> noResults) { delegate()->peerListSetSearchNoResults(std::move(noResults)); } private: PeerListDelegate *_delegate = nullptr; std::unique_ptr<PeerListSearchController> _searchController = nullptr; rpl::lifetime _lifetime; }; struct PeerListState { PeerListState() = default; PeerListState(PeerListState &&other) = delete; PeerListState &operator=(PeerListState &&other) = delete; std::unique_ptr<PeerListController::SavedStateBase> controllerState; std::vector<not_null<PeerData*>> list; std::vector<not_null<PeerData*>> filterResults; QString searchQuery; }; class PeerListContent : public Ui::RpWidget , private base::Subscriber { public: PeerListContent( QWidget *parent, not_null<PeerListController*> controller, const style::PeerList &st); void selectSkip(int direction); void selectSkipPage(int height, int direction); void clearSelection(); void searchQueryChanged(QString query); void submitted(); // Interface for the controller. void appendRow(std::unique_ptr<PeerListRow> row); void appendSearchRow(std::unique_ptr<PeerListRow> row); void appendFoundRow(not_null<PeerListRow*> row); void prependRow(std::unique_ptr<PeerListRow> row); void prependRowFromSearchResult(not_null<PeerListRow*> row); PeerListRow *findRow(PeerListRowId id); void updateRow(not_null<PeerListRow*> row) { updateRow(row, RowIndex()); } void removeRow(not_null<PeerListRow*> row); void convertRowToSearchResult(not_null<PeerListRow*> row); int fullRowsCount() const; not_null<PeerListRow*> rowAt(int index) const; void setDescription(object_ptr<Ui::FlatLabel> description); void setSearchLoading(object_ptr<Ui::FlatLabel> loading); void setSearchNoResults(object_ptr<Ui::FlatLabel> noResults); void setAboveWidget(object_ptr<TWidget> aboveWidget); void refreshRows(); void setSearchMode(PeerListSearchMode mode); void changeCheckState(not_null<PeerListRow*> row, bool checked, PeerListRow::SetStyle style); template <typename ReorderCallback> void reorderRows(ReorderCallback &&callback) { callback(_rows.begin(), _rows.end()); for (auto &searchEntity : _searchIndex) { callback(searchEntity.second.begin(), searchEntity.second.end()); } refreshIndices(); update(); } std::unique_ptr<PeerListState> saveState() const; void restoreState(std::unique_ptr<PeerListState> state); auto scrollToRequests() const { return _scrollToRequests.events(); } protected: int resizeGetHeight(int newWidth) override; void visibleTopBottomUpdated( int visibleTop, int visibleBottom) override; void paintEvent(QPaintEvent *e) override; void enterEventHook(QEvent *e) override; void leaveEventHook(QEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void contextMenuEvent(QContextMenuEvent *e) override; private: void refreshIndices(); void removeRowAtIndex(std::vector<std::unique_ptr<PeerListRow>> &from, int index); void handleNameChanged(const Notify::PeerUpdate &update); void invalidatePixmapsCache(); struct RowIndex { RowIndex() { } explicit RowIndex(int value) : value(value) { } int value = -1; }; friend inline bool operator==(RowIndex a, RowIndex b) { return (a.value == b.value); } friend inline bool operator!=(RowIndex a, RowIndex b) { return !(a == b); } struct Selected { Selected() { } Selected(RowIndex index, bool action) : index(index), action(action) { } Selected(int index, bool action) : index(index), action(action) { } RowIndex index; bool action = false; }; friend inline bool operator==(Selected a, Selected b) { return (a.index == b.index) && (a.action == b.action); } friend inline bool operator!=(Selected a, Selected b) { return !(a == b); } struct SelectedSaved { SelectedSaved(PeerListRowId id, Selected old) : id(id), old(old) { } PeerListRowId id = 0; Selected old; }; void setSelected(Selected selected); void setPressed(Selected pressed); void setContexted(Selected contexted); void restoreSelection(); SelectedSaved saveSelectedData(Selected from); Selected restoreSelectedData(SelectedSaved from); void updateSelection(); void loadProfilePhotos(); void checkScrollForPreload(); void updateRow(not_null<PeerListRow*> row, RowIndex hint); void updateRow(RowIndex row); int getRowTop(RowIndex row) const; PeerListRow *getRow(RowIndex element); RowIndex findRowIndex(not_null<PeerListRow*> row, RowIndex hint = RowIndex()); QRect getActionRect(not_null<PeerListRow*> row, RowIndex index) const; TimeMs paintRow(Painter &p, TimeMs ms, RowIndex index); void addRowEntry(not_null<PeerListRow*> row); void addToSearchIndex(not_null<PeerListRow*> row); bool addingToSearchIndex() const; void removeFromSearchIndex(not_null<PeerListRow*> row); void setSearchQuery(const QString &query, const QString &normalizedQuery); bool showingSearch() const { return !_searchQuery.isEmpty(); } int shownRowsCount() const { return showingSearch() ? _filterResults.size() : _rows.size(); } template <typename Callback> bool enumerateShownRows(Callback callback); template <typename Callback> bool enumerateShownRows(int from, int to, Callback callback); int rowsTop() const; int labelHeight() const; void clearSearchRows(); void clearAllContent(); void handleMouseMove(QPoint position); void mousePressReleased(Qt::MouseButton button); const style::PeerList &_st; not_null<PeerListController*> _controller; PeerListSearchMode _searchMode = PeerListSearchMode::Disabled; int _rowHeight = 0; int _visibleTop = 0; int _visibleBottom = 0; Selected _selected; Selected _pressed; Selected _contexted; bool _mouseSelection = false; Qt::MouseButton _pressButton = Qt::LeftButton; rpl::event_stream<Ui::ScrollToRequest> _scrollToRequests; std::vector<std::unique_ptr<PeerListRow>> _rows; std::map<PeerListRowId, not_null<PeerListRow*>> _rowsById; std::map<PeerData*, std::vector<not_null<PeerListRow*>>> _rowsByPeer; std::map<QChar, std::vector<not_null<PeerListRow*>>> _searchIndex; QString _searchQuery; QString _normalizedSearchQuery; QString _mentionHighlight; std::vector<not_null<PeerListRow*>> _filterResults; int _aboveHeight = 0; object_ptr<TWidget> _aboveWidget = { nullptr }; object_ptr<Ui::FlatLabel> _description = { nullptr }; object_ptr<Ui::FlatLabel> _searchNoResults = { nullptr }; object_ptr<Ui::FlatLabel> _searchLoading = { nullptr }; QPoint _lastMousePosition; std::vector<std::unique_ptr<PeerListRow>> _searchRows; base::Timer _repaintByStatus; base::unique_qptr<Ui::PopupMenu> _contextMenu; }; class PeerListContentDelegate : public PeerListDelegate { public: void setContent(PeerListContent *content) { _content = content; } void peerListAppendRow( std::unique_ptr<PeerListRow> row) override { _content->appendRow(std::move(row)); } void peerListAppendSearchRow( std::unique_ptr<PeerListRow> row) override { _content->appendSearchRow(std::move(row)); } void peerListAppendFoundRow( not_null<PeerListRow*> row) override { _content->appendFoundRow(row); } void peerListPrependRow( std::unique_ptr<PeerListRow> row) override { _content->prependRow(std::move(row)); } void peerListPrependRowFromSearchResult( not_null<PeerListRow*> row) override { _content->prependRowFromSearchResult(row); } PeerListRow *peerListFindRow(PeerListRowId id) override { return _content->findRow(id); } void peerListUpdateRow(not_null<PeerListRow*> row) override { _content->updateRow(row); } void peerListRemoveRow(not_null<PeerListRow*> row) override { _content->removeRow(row); } void peerListConvertRowToSearchResult( not_null<PeerListRow*> row) override { _content->convertRowToSearchResult(row); } void peerListSetRowChecked( not_null<PeerListRow*> row, bool checked) override { _content->changeCheckState( row, checked, PeerListRow::SetStyle::Animated); } int peerListFullRowsCount() override { return _content->fullRowsCount(); } not_null<PeerListRow*> peerListRowAt(int index) override { return _content->rowAt(index); } void peerListRefreshRows() override { _content->refreshRows(); } void peerListSetDescription(object_ptr<Ui::FlatLabel> description) override { _content->setDescription(std::move(description)); } void peerListSetSearchLoading(object_ptr<Ui::FlatLabel> loading) override { _content->setSearchLoading(std::move(loading)); } void peerListSetSearchNoResults(object_ptr<Ui::FlatLabel> noResults) override { _content->setSearchNoResults(std::move(noResults)); } void peerListSetAboveWidget(object_ptr<TWidget> aboveWidget) override { _content->setAboveWidget(std::move(aboveWidget)); } void peerListSetSearchMode(PeerListSearchMode mode) override { _content->setSearchMode(mode); } void peerListSortRows( base::lambda<bool(const PeerListRow &a, const PeerListRow &b)> compare) override { _content->reorderRows([&]( auto &&begin, auto &&end) { std::sort(begin, end, [&](auto &&a, auto &&b) { return compare(*a, *b); }); }); } int peerListPartitionRows( base::lambda<bool(const PeerListRow &a)> border) override { auto result = 0; _content->reorderRows([&]( auto &&begin, auto &&end) { auto edge = std::stable_partition(begin, end, [&]( auto &¤t) { return border(*current); }); result = (edge - begin); }); return result; } std::unique_ptr<PeerListState> peerListSaveState() const override { return _content->saveState(); } void peerListRestoreState( std::unique_ptr<PeerListState> state) override { _content->restoreState(std::move(state)); } protected: not_null<PeerListContent*> content() const { return _content; } private: PeerListContent *_content = nullptr; }; class PeerListBox : public BoxContent , public PeerListContentDelegate { public: PeerListBox( QWidget*, std::unique_ptr<PeerListController> controller, base::lambda<void(not_null<PeerListBox*>)> init); void peerListSetTitle(base::lambda<QString()> title) override { setTitle(std::move(title)); } void peerListSetAdditionalTitle( base::lambda<QString()> title) override { setAdditionalTitle(std::move(title)); } void peerListSetSearchMode(PeerListSearchMode mode) override; void peerListSetRowChecked( not_null<PeerListRow*> row, bool checked) override; bool peerListIsRowSelected(not_null<PeerData*> peer) override; int peerListSelectedRowsCount() override; std::vector<not_null<PeerData*>> peerListCollectSelectedRows() override; void peerListScrollToTop() override; protected: void prepare() override; void setInnerFocus() override; void keyPressEvent(QKeyEvent *e) override; void resizeEvent(QResizeEvent *e) override; void paintEvent(QPaintEvent *e) override; private: void peerListAddSelectedRowInBunch( not_null<PeerData*> peer) override { addSelectItem(peer, PeerListRow::SetStyle::Fast); } void peerListFinishSelectedRowsBunch() override; void addSelectItem( not_null<PeerData*> peer, PeerListRow::SetStyle style); void createMultiSelect(); int getTopScrollSkip() const; void updateScrollSkips(); void searchQueryChanged(const QString &query); object_ptr<Ui::SlideWrap<Ui::MultiSelect>> _select = { nullptr }; std::unique_ptr<PeerListController> _controller; base::lambda<void(PeerListBox*)> _init; bool _scrollBottomFixed = false; }; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/peer_list_controllers.cpp��������������������������������0000664�0000000�0000000�00000066721�13262451251�0025677�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/peer_list_controllers.h" #include "styles/style_boxes.h" #include "styles/style_profile.h" #include "boxes/confirm_box.h" #include "observer_peer.h" #include "ui/widgets/checkbox.h" #include "auth_session.h" #include "data/data_session.h" #include "apiwrap.h" #include "mainwidget.h" #include "lang/lang_keys.h" #include "history/history.h" #include "dialogs/dialogs_indexed_list.h" namespace { base::flat_set<not_null<UserData*>> GetAlreadyInFromPeer(PeerData *peer) { if (!peer) { return {}; } if (auto chat = peer->asChat()) { auto participants = ( chat->participants ) | ranges::view::transform([](auto &&pair) -> not_null<UserData*> { return pair.first; }); return { participants.begin(), participants.end() }; } else if (auto channel = peer->asChannel()) { if (channel->isMegagroup()) { auto &participants = channel->mgInfo->lastParticipants; return { participants.cbegin(), participants.cend() }; } } return {}; } } // namespace // Not used for now. // //MembersAddButton::MembersAddButton(QWidget *parent, const style::TwoIconButton &st) : RippleButton(parent, st.ripple) //, _st(st) { // resize(_st.width, _st.height); // setCursor(style::cur_pointer); //} // //void MembersAddButton::paintEvent(QPaintEvent *e) { // Painter p(this); // // auto ms = getms(); // auto over = isOver(); // auto down = isDown(); // // ((over || down) ? _st.iconBelowOver : _st.iconBelow).paint(p, _st.iconPosition, width()); // paintRipple(p, _st.rippleAreaPosition.x(), _st.rippleAreaPosition.y(), ms); // ((over || down) ? _st.iconAboveOver : _st.iconAbove).paint(p, _st.iconPosition, width()); //} // //QImage MembersAddButton::prepareRippleMask() const { // return Ui::RippleAnimation::ellipseMask(QSize(_st.rippleAreaSize, _st.rippleAreaSize)); //} // //QPoint MembersAddButton::prepareRippleStartPosition() const { // return mapFromGlobal(QCursor::pos()) - _st.rippleAreaPosition; //} class EditChatAdminsBoxController::LabeledCheckbox : public TWidget, private base::Subscriber { public: LabeledCheckbox(QWidget *parent, const QString &text, bool checked = false, const style::Checkbox &st = st::defaultCheckbox, const style::Check &checkSt = st::defaultCheck); base::Observable<bool> checkedChanged; bool checked() const { return _checkbox->checked(); } void setLabelText( bool checked, const style::TextStyle &st, const QString &text, const TextParseOptions &options = _defaultOptions, int minResizeWidth = QFIXED_MAX); protected: int resizeGetHeight(int newWidth) override; void paintEvent(QPaintEvent *e) override; private: object_ptr<Ui::Checkbox> _checkbox; Text _labelUnchecked; Text _labelChecked; int _labelWidth = 0; }; void PeerListRowWithLink::setActionLink(const QString &action) { _action = action; refreshActionLink(); } void PeerListRowWithLink::refreshActionLink() { if (!isInitialized()) return; _actionWidth = _action.isEmpty() ? 0 : st::normalFont->width(_action); } void PeerListRowWithLink::lazyInitialize(const style::PeerListItem &st) { PeerListRow::lazyInitialize(st); refreshActionLink(); } QSize PeerListRowWithLink::actionSize() const { return QSize(_actionWidth, st::normalFont->height); } QMargins PeerListRowWithLink::actionMargins() const { return QMargins( st::contactsCheckPosition.x(), (st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom() - st::normalFont->height) / 2, st::defaultPeerListItem.photoPosition.x() + st::contactsCheckPosition.x(), 0); } void PeerListRowWithLink::paintAction( Painter &p, TimeMs ms, int x, int y, int outerWidth, bool selected, bool actionSelected) { p.setFont(actionSelected ? st::linkOverFont : st::linkFont); p.setPen(actionSelected ? st::defaultLinkButton.overColor : st::defaultLinkButton.color); p.drawTextLeft(x, y, outerWidth, _action, _actionWidth); } PeerListGlobalSearchController::PeerListGlobalSearchController() { _timer.setCallback([this] { searchOnServer(); }); } void PeerListGlobalSearchController::searchQuery(const QString &query) { if (_query != query) { _query = query; _requestId = 0; if (!_query.isEmpty() && !searchInCache()) { _timer.callOnce(AutoSearchTimeout); } else { _timer.cancel(); } } } bool PeerListGlobalSearchController::searchInCache() { auto it = _cache.find(_query); if (it != _cache.cend()) { _requestId = 0; searchDone(it->second, _requestId); return true; } return false; } void PeerListGlobalSearchController::searchOnServer() { _requestId = request(MTPcontacts_Search( MTP_string(_query), MTP_int(SearchPeopleLimit) )).done([=](const MTPcontacts_Found &result, mtpRequestId requestId) { searchDone(result, requestId); }).fail([=](const RPCError &error, mtpRequestId requestId) { if (_requestId == requestId) { _requestId = 0; delegate()->peerListSearchRefreshRows(); } }).send(); _queries.emplace(_requestId, _query); } void PeerListGlobalSearchController::searchDone( const MTPcontacts_Found &result, mtpRequestId requestId) { Expects(result.type() == mtpc_contacts_found); auto &contacts = result.c_contacts_found(); auto query = _query; if (requestId) { App::feedUsers(contacts.vusers); App::feedChats(contacts.vchats); auto it = _queries.find(requestId); if (it != _queries.cend()) { query = it->second; _cache[query] = result; _queries.erase(it); } } const auto feedList = [&](const MTPVector<MTPPeer> &list) { for (const auto &mtpPeer : list.v) { if (const auto peer = App::peerLoaded(peerFromMTP(mtpPeer))) { delegate()->peerListSearchAddRow(peer); } } }; if (_requestId == requestId) { _requestId = 0; feedList(contacts.vmy_results); feedList(contacts.vresults); delegate()->peerListSearchRefreshRows(); } } bool PeerListGlobalSearchController::isLoading() { return _timer.isActive() || _requestId; } ChatsListBoxController::Row::Row(not_null<History*> history) : PeerListRow(history->peer) , _history(history) { } ChatsListBoxController::ChatsListBoxController( std::unique_ptr<PeerListSearchController> searchController) : PeerListController(std::move(searchController)) { } void ChatsListBoxController::prepare() { setSearchNoResultsText(lang(lng_blocked_list_not_found)); delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled); prepareViewHook(); rebuildRows(); auto &sessionData = Auth().data(); subscribe(sessionData.contactsLoaded(), [this](bool loaded) { rebuildRows(); }); subscribe(sessionData.moreChatsLoaded(), [this] { rebuildRows(); }); subscribe(sessionData.allChatsLoaded(), [this](bool loaded) { checkForEmptyRows(); }); } void ChatsListBoxController::rebuildRows() { auto wasEmpty = !delegate()->peerListFullRowsCount(); auto appendList = [this](auto chats) { auto count = 0; for (const auto row : chats->all()) { if (const auto history = row->history()) { if (appendRow(history)) { ++count; } } } return count; }; auto added = 0; if (respectSavedMessagesChat()) { if (auto self = App::self()) { if (appendRow(App::history(self))) { ++added; } } } added += appendList(App::main()->dialogsList()); added += appendList(App::main()->contactsNoDialogsList()); if (!wasEmpty && added > 0) { // Place dialogs list before contactsNoDialogs list. delegate()->peerListPartitionRows([](const PeerListRow &a) { auto history = static_cast<const Row&>(a).history(); return history->inChatList(Dialogs::Mode::All); }); if (respectSavedMessagesChat()) { delegate()->peerListPartitionRows([](const PeerListRow &a) { return a.peer()->isSelf(); }); } } checkForEmptyRows(); delegate()->peerListRefreshRows(); } void ChatsListBoxController::checkForEmptyRows() { if (delegate()->peerListFullRowsCount()) { setDescriptionText(QString()); } else { auto &sessionData = Auth().data(); auto loaded = sessionData.contactsLoaded().value() && sessionData.allChatsLoaded().value(); setDescriptionText(loaded ? emptyBoxText() : lang(lng_contacts_loading)); } } QString ChatsListBoxController::emptyBoxText() const { return lang(lng_contacts_not_found); } std::unique_ptr<PeerListRow> ChatsListBoxController::createSearchRow(not_null<PeerData*> peer) { return createRow(App::history(peer)); } bool ChatsListBoxController::appendRow(not_null<History*> history) { if (auto row = delegate()->peerListFindRow(history->peer->id)) { updateRowHook(static_cast<Row*>(row)); return false; } if (auto row = createRow(history)) { delegate()->peerListAppendRow(std::move(row)); return true; } return false; } ContactsBoxController::ContactsBoxController(std::unique_ptr<PeerListSearchController> searchController) : PeerListController(std::move(searchController)) { } void ContactsBoxController::prepare() { setSearchNoResultsText(lang(lng_blocked_list_not_found)); delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled); delegate()->peerListSetTitle(langFactory(lng_contacts_header)); prepareViewHook(); rebuildRows(); auto &sessionData = Auth().data(); subscribe(sessionData.contactsLoaded(), [this](bool loaded) { rebuildRows(); }); } void ContactsBoxController::rebuildRows() { auto appendList = [this](auto chats) { auto count = 0; for (const auto row : chats->all()) { if (const auto history = row->history()) { if (const auto user = history->peer->asUser()) { if (appendRow(user)) { ++count; } } } } return count; }; appendList(App::main()->contactsList()); checkForEmptyRows(); delegate()->peerListRefreshRows(); } void ContactsBoxController::checkForEmptyRows() { if (delegate()->peerListFullRowsCount()) { setDescriptionText(QString()); } else { auto &sessionData = Auth().data(); auto loaded = sessionData.contactsLoaded().value(); setDescriptionText(lang(loaded ? lng_contacts_not_found : lng_contacts_loading)); } } std::unique_ptr<PeerListRow> ContactsBoxController::createSearchRow( not_null<PeerData*> peer) { if (const auto user = peer->asUser()) { return createRow(user); } return nullptr; } void ContactsBoxController::rowClicked(not_null<PeerListRow*> row) { Ui::showPeerHistory(row->peer(), ShowAtUnreadMsgId); } bool ContactsBoxController::appendRow(not_null<UserData*> user) { if (auto row = delegate()->peerListFindRow(user->id)) { updateRowHook(row); return false; } if (auto row = createRow(user)) { delegate()->peerListAppendRow(std::move(row)); return true; } return false; } std::unique_ptr<PeerListRow> ContactsBoxController::createRow(not_null<UserData*> user) { return std::make_unique<PeerListRow>(user); } AddParticipantsBoxController::AddParticipantsBoxController(PeerData *peer) : ContactsBoxController(std::make_unique<PeerListGlobalSearchController>()) , _peer(peer) , _alreadyIn(GetAlreadyInFromPeer(peer)) { } AddParticipantsBoxController::AddParticipantsBoxController( not_null<ChannelData*> channel, base::flat_set<not_null<UserData*>> &&alreadyIn) : ContactsBoxController(std::make_unique<PeerListGlobalSearchController>()) , _peer(channel) , _alreadyIn(std::move(alreadyIn)) { } void AddParticipantsBoxController::rowClicked(not_null<PeerListRow*> row) { auto count = fullCount(); auto limit = (_peer && _peer->isMegagroup()) ? Global::MegagroupSizeMax() : Global::ChatSizeMax(); if (count < limit || row->checked()) { delegate()->peerListSetRowChecked(row, !row->checked()); updateTitle(); } else if (auto channel = _peer ? _peer->asChannel() : nullptr) { if (!_peer->isMegagroup()) { Ui::show( Box<MaxInviteBox>(_peer->asChannel()), LayerOption::KeepOther); } } else if (count >= Global::ChatSizeMax() && count < Global::MegagroupSizeMax()) { Ui::show( Box<InformBox>(lng_profile_add_more_after_upgrade(lt_count, Global::MegagroupSizeMax())), LayerOption::KeepOther); } } void AddParticipantsBoxController::itemDeselectedHook(not_null<PeerData*> peer) { updateTitle(); } void AddParticipantsBoxController::prepareViewHook() { updateTitle(); } int AddParticipantsBoxController::alreadyInCount() const { if (!_peer) { return 1; // self } if (auto chat = _peer->asChat()) { return qMax(chat->count, 1); } else if (auto channel = _peer->asChannel()) { return qMax(channel->membersCount(), int(_alreadyIn.size())); } Unexpected("User in AddParticipantsBoxController::alreadyInCount"); } bool AddParticipantsBoxController::isAlreadyIn(not_null<UserData*> user) const { if (!_peer) { return false; } if (auto chat = _peer->asChat()) { return chat->participants.contains(user); } else if (auto channel = _peer->asChannel()) { return _alreadyIn.contains(user) || (channel->isMegagroup() && base::contains(channel->mgInfo->lastParticipants, user)); } Unexpected("User in AddParticipantsBoxController::isAlreadyIn"); } int AddParticipantsBoxController::fullCount() const { return alreadyInCount() + delegate()->peerListSelectedRowsCount(); } std::unique_ptr<PeerListRow> AddParticipantsBoxController::createRow(not_null<UserData*> user) { if (user->isSelf()) { return nullptr; } auto result = std::make_unique<PeerListRow>(user); if (isAlreadyIn(user)) { result->setDisabledState(PeerListRow::State::DisabledChecked); } return result; } void AddParticipantsBoxController::updateTitle() { auto additional = (_peer && _peer->isChannel() && !_peer->isMegagroup()) ? QString() : QString("%1 / %2").arg(fullCount()).arg(Global::MegagroupSizeMax()); delegate()->peerListSetTitle(langFactory(lng_profile_add_participant)); delegate()->peerListSetAdditionalTitle([additional] { return additional; }); } void AddParticipantsBoxController::Start(not_null<ChatData*> chat) { auto initBox = [chat](not_null<PeerListBox*> box) { box->addButton(langFactory(lng_participant_invite), [box, chat] { auto rows = box->peerListCollectSelectedRows(); if (!rows.empty()) { auto users = std::vector<not_null<UserData*>>(); for (auto peer : rows) { auto user = peer->asUser(); Assert(user != nullptr); Assert(!user->isSelf()); users.push_back(peer->asUser()); } App::main()->addParticipants(chat, users); Ui::showPeerHistory(chat, ShowAtTheEndMsgId); } }); box->addButton(langFactory(lng_cancel), [box] { box->closeBox(); }); }; Ui::show(Box<PeerListBox>(std::make_unique<AddParticipantsBoxController>(chat), std::move(initBox))); } void AddParticipantsBoxController::Start( not_null<ChannelData*> channel, base::flat_set<not_null<UserData*>> &&alreadyIn, bool justCreated) { auto initBox = [channel, justCreated](not_null<PeerListBox*> box) { auto subscription = std::make_shared<base::Subscription>(); box->addButton(langFactory(lng_participant_invite), [box, channel, subscription] { auto rows = box->peerListCollectSelectedRows(); if (!rows.empty()) { auto users = std::vector<not_null<UserData*>>(); for (auto peer : rows) { auto user = peer->asUser(); Assert(user != nullptr); Assert(!user->isSelf()); users.push_back(peer->asUser()); } App::main()->addParticipants(channel, users); if (channel->isMegagroup()) { Ui::showPeerHistory(channel, ShowAtTheEndMsgId); } else { box->closeBox(); } } }); box->addButton(langFactory(justCreated ? lng_create_group_skip : lng_cancel), [box] { box->closeBox(); }); if (justCreated) { *subscription = box->boxClosing.add_subscription([channel] { Ui::showPeerHistory(channel, ShowAtTheEndMsgId); }); } }; Ui::show(Box<PeerListBox>(std::make_unique<AddParticipantsBoxController>(channel, std::move(alreadyIn)), std::move(initBox))); } void AddParticipantsBoxController::Start( not_null<ChannelData*> channel, base::flat_set<not_null<UserData*>> &&alreadyIn) { Start(channel, std::move(alreadyIn), false); } void AddParticipantsBoxController::Start(not_null<ChannelData*> channel) { Start(channel, {}, true); } EditChatAdminsBoxController::LabeledCheckbox::LabeledCheckbox( QWidget *parent, const QString &text, bool checked, const style::Checkbox &st, const style::Check &checkSt) : TWidget(parent) , _checkbox(this, text, checked, st, checkSt) { subscribe(_checkbox->checkedChanged, [this](bool value) { checkedChanged.notify(value, true); }); } void EditChatAdminsBoxController::LabeledCheckbox::setLabelText( bool checked, const style::TextStyle &st, const QString &text, const TextParseOptions &options, int minResizeWidth) { auto &label = (checked ? _labelChecked : _labelUnchecked); label = Text(st, text, options, minResizeWidth); } int EditChatAdminsBoxController::LabeledCheckbox::resizeGetHeight(int newWidth) { _labelWidth = newWidth - st::contactsPadding.left() - st::contactsPadding.right(); _checkbox->resizeToNaturalWidth(_labelWidth); _checkbox->moveToLeft(st::contactsPadding.left(), st::contactsAllAdminsTop); auto labelHeight = qMax( _labelChecked.countHeight(_labelWidth), _labelUnchecked.countHeight(_labelWidth)); return st::contactsAboutTop + labelHeight + st::contactsAboutBottom; } void EditChatAdminsBoxController::LabeledCheckbox::paintEvent(QPaintEvent *e) { Painter p(this); auto infoTop = _checkbox->bottomNoMargins() + st::contactsAllAdminsTop - st::lineWidth; auto infoRect = rtlrect(0, infoTop, width(), height() - infoTop - st::contactsPadding.bottom(), width()); p.fillRect(infoRect, st::contactsAboutBg); auto dividerFillTop = rtlrect(0, infoRect.y(), width(), st::profileDividerTop.height(), width()); st::profileDividerTop.fill(p, dividerFillTop); auto dividerFillBottom = rtlrect(0, infoRect.y() + infoRect.height() - st::profileDividerBottom.height(), width(), st::profileDividerBottom.height(), width()); st::profileDividerBottom.fill(p, dividerFillBottom); p.setPen(st::contactsAboutFg); (checked() ? _labelChecked : _labelUnchecked).draw(p, st::contactsPadding.left(), st::contactsAboutTop, _labelWidth); } EditChatAdminsBoxController::EditChatAdminsBoxController(not_null<ChatData*> chat) : PeerListController() , _chat(chat) { } bool EditChatAdminsBoxController::allAreAdmins() const { return _allAdmins->checked(); } void EditChatAdminsBoxController::prepare() { createAllAdminsCheckbox(); setSearchNoResultsText(lang(lng_blocked_list_not_found)); delegate()->peerListSetSearchMode(allAreAdmins() ? PeerListSearchMode::Disabled : PeerListSearchMode::Enabled); delegate()->peerListSetTitle(langFactory(lng_channel_admins)); rebuildRows(); if (!delegate()->peerListFullRowsCount()) { Auth().api().requestFullPeer(_chat); _adminsUpdatedSubscription = subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler( Notify::PeerUpdate::Flag::AdminsChanged, [this]( const Notify::PeerUpdate &update) { if (update.peer == _chat) { rebuildRows(); if (delegate()->peerListFullRowsCount()) { unsubscribe(_adminsUpdatedSubscription); } } })); } subscribe(_allAdmins->checkedChanged, [this](bool checked) { delegate()->peerListSetSearchMode(checked ? PeerListSearchMode::Disabled : PeerListSearchMode::Enabled); for (auto i = 0, count = delegate()->peerListFullRowsCount(); i != count; ++i) { auto row = delegate()->peerListRowAt(i); auto user = row->peer()->asUser(); if (checked || user->id == peerFromUser(_chat->creator)) { row->setDisabledState(PeerListRow::State::DisabledChecked); } else { row->setDisabledState(PeerListRow::State::Active); } } }); } void EditChatAdminsBoxController::createAllAdminsCheckbox() { auto labelWidth = st::boxWideWidth - st::contactsPadding.left() - st::contactsPadding.right(); auto checkbox = object_ptr<LabeledCheckbox>(nullptr, lang(lng_chat_all_members_admins), !_chat->adminsEnabled(), st::defaultBoxCheckbox); checkbox->setLabelText(true, st::defaultTextStyle, lang(lng_chat_about_all_admins), _defaultOptions, labelWidth); checkbox->setLabelText(false, st::defaultTextStyle, lang(lng_chat_about_admins), _defaultOptions, labelWidth); _allAdmins = checkbox; delegate()->peerListSetAboveWidget(std::move(checkbox)); } void EditChatAdminsBoxController::rebuildRows() { if (_chat->participants.empty()) { return; } auto allAdmins = allAreAdmins(); auto admins = std::vector<not_null<UserData*>>(); auto others = admins; admins.reserve(allAdmins ? _chat->participants.size() : _chat->admins.size()); others.reserve(_chat->participants.size()); for (auto [user, version] : _chat->participants) { if (user->id == peerFromUser(_chat->creator)) continue; if (_chat->admins.contains(user)) { admins.push_back(user); } else { others.push_back(user); } } if (!admins.empty()) { delegate()->peerListAddSelectedRows(admins); } if (allAdmins) { admins.insert(admins.end(), others.begin(), others.end()); others.clear(); } auto sortByName = [](not_null<UserData*> a, auto b) { return (a->name.compare(b->name, Qt::CaseInsensitive) < 0); }; ranges::sort(admins, sortByName); ranges::sort(others, sortByName); auto addOne = [this](not_null<UserData*> user) { if (auto row = createRow(user)) { delegate()->peerListAppendRow(std::move(row)); } }; if (auto creator = App::userLoaded(_chat->creator)) { if (_chat->participants.contains(creator)) { addOne(creator); } } ranges::for_each(admins, addOne); ranges::for_each(others, addOne); delegate()->peerListRefreshRows(); } std::unique_ptr<PeerListRow> EditChatAdminsBoxController::createRow(not_null<UserData*> user) { auto result = std::make_unique<PeerListRow>(user); if (allAreAdmins() || user->id == peerFromUser(_chat->creator)) { result->setDisabledState(PeerListRow::State::DisabledChecked); } return result; } void EditChatAdminsBoxController::rowClicked(not_null<PeerListRow*> row) { delegate()->peerListSetRowChecked(row, !row->checked()); } void EditChatAdminsBoxController::Start(not_null<ChatData*> chat) { auto controller = std::make_unique<EditChatAdminsBoxController>(chat); auto initBox = [chat, controller = controller.get()](not_null<PeerListBox*> box) { box->addButton(langFactory(lng_settings_save), [box, chat, controller] { auto rows = box->peerListCollectSelectedRows(); auto users = std::vector<not_null<UserData*>>(); for (auto peer : rows) { auto user = peer->asUser(); Assert(user != nullptr); Assert(!user->isSelf()); users.push_back(peer->asUser()); } Auth().api().editChatAdmins(chat, !controller->allAreAdmins(), { users.cbegin(), users.cend() }); box->closeBox(); }); box->addButton(langFactory(lng_cancel), [box] { box->closeBox(); }); }; Ui::show( Box<PeerListBox>(std::move(controller), std::move(initBox)), LayerOption::KeepOther); } void AddBotToGroupBoxController::Start(not_null<UserData*> bot) { auto initBox = [=](not_null<PeerListBox*> box) { box->addButton(langFactory(lng_cancel), [box] { box->closeBox(); }); }; Ui::show(Box<PeerListBox>(std::make_unique<AddBotToGroupBoxController>(bot), std::move(initBox))); } AddBotToGroupBoxController::AddBotToGroupBoxController(not_null<UserData*> bot) : ChatsListBoxController(SharingBotGame(bot) ? std::make_unique<PeerListGlobalSearchController>() : nullptr) , _bot(bot) { } void AddBotToGroupBoxController::rowClicked(not_null<PeerListRow*> row) { if (sharingBotGame()) { shareBotGame(row->peer()); } else { addBotToGroup(row->peer()); } } void AddBotToGroupBoxController::shareBotGame(not_null<PeerData*> chat) { auto send = [weak = base::make_weak(this), bot = _bot, chat] { if (!weak) { return; } const auto history = App::historyLoaded(chat); const auto randomId = rand_value<uint64>(); const auto requestId = MTP::send( MTPmessages_SendMedia( MTP_flags(0), chat->input, MTP_int(0), MTP_inputMediaGame( MTP_inputGameShortName( bot->inputUser, MTP_string(bot->botInfo->shareGameShortName))), MTP_string(""), MTP_long(randomId), MTPnullMarkup, MTPnullEntities), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::sendMessageFail), 0, 0, history ? history->sendRequestId : 0); if (history) { history->sendRequestId = requestId; } Ui::hideLayer(); Ui::showPeerHistory(chat, ShowAtUnreadMsgId); }; auto confirmText = [chat] { if (chat->isUser()) { return lng_bot_sure_share_game(lt_user, App::peerName(chat)); } return lng_bot_sure_share_game_group(lt_group, chat->name); }(); Ui::show( Box<ConfirmBox>(confirmText, std::move(send)), LayerOption::KeepOther); } void AddBotToGroupBoxController::addBotToGroup(not_null<PeerData*> chat) { if (auto megagroup = chat->asMegagroup()) { if (!megagroup->canAddMembers()) { Ui::show( Box<InformBox>(lang(lng_error_cant_add_member)), LayerOption::KeepOther); return; } } auto send = [weak = base::make_weak(this), bot = _bot, chat] { if (!weak) { return; } if (auto &info = bot->botInfo) { if (!info->startGroupToken.isEmpty()) { MTP::send( MTPmessages_StartBot( bot->inputUser, chat->input, MTP_long(rand_value<uint64>()), MTP_string(info->startGroupToken)), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail( &MainWidget::addParticipantFail, { bot, chat })); } else { App::main()->addParticipants( chat, { 1, bot }); } } else { App::main()->addParticipants( chat, { 1, bot }); } Ui::hideLayer(); Ui::showPeerHistory(chat, ShowAtUnreadMsgId); }; auto confirmText = lng_bot_sure_invite(lt_group, chat->name); Ui::show( Box<ConfirmBox>(confirmText, send), LayerOption::KeepOther); } std::unique_ptr<ChatsListBoxController::Row> AddBotToGroupBoxController::createRow(not_null<History*> history) { if (!needToCreateRow(history->peer)) { return nullptr; } return std::make_unique<Row>(history); } bool AddBotToGroupBoxController::needToCreateRow(not_null<PeerData*> peer) const { if (sharingBotGame()) { if (!peer->canWrite()) { return false; } if (auto group = peer->asMegagroup()) { if (group->restricted(ChannelRestriction::f_send_games)) { return false; } } return true; } if (auto chat = peer->asChat()) { if (chat->canEdit()) { return true; } } else if (auto group = peer->asMegagroup()) { return group->canAddMembers(); } return false; } bool AddBotToGroupBoxController::SharingBotGame(not_null<UserData*> bot) { auto &info = bot->botInfo; return (info && !info->shareGameShortName.isEmpty()); } bool AddBotToGroupBoxController::sharingBotGame() const { return SharingBotGame(_bot); } QString AddBotToGroupBoxController::emptyBoxText() const { return lang(Auth().data().allChatsLoaded().value() ? (sharingBotGame() ? lng_bot_no_chats : lng_bot_no_groups) : lng_contacts_loading); } QString AddBotToGroupBoxController::noResultsText() const { return lang(Auth().data().allChatsLoaded().value() ? (sharingBotGame() ? lng_bot_chats_not_found : lng_bot_groups_not_found) : lng_contacts_loading); } void AddBotToGroupBoxController::updateLabels() { setSearchNoResultsText(noResultsText()); } void AddBotToGroupBoxController::prepareViewHook() { delegate()->peerListSetTitle(langFactory(sharingBotGame() ? lng_bot_choose_chat : lng_bot_choose_group)); updateLabels(); subscribe(Auth().data().allChatsLoaded(), [this](bool) { updateLabels(); }); } ChooseRecipientBoxController::ChooseRecipientBoxController( base::lambda_once<void(not_null<PeerData*>)> callback) : _callback(std::move(callback)) { } void ChooseRecipientBoxController::prepareViewHook() { delegate()->peerListSetTitle(langFactory(lng_forward_choose)); } void ChooseRecipientBoxController::rowClicked(not_null<PeerListRow*> row) { _callback(row->peer()); } auto ChooseRecipientBoxController::createRow( not_null<History*> history) -> std::unique_ptr<Row> { return std::make_unique<Row>(history); } �����������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/peer_list_controllers.h����������������������������������0000664�0000000�0000000�00000014176�13262451251�0025341�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "boxes/peer_list_box.h" #include "base/flat_set.h" #include "base/weak_ptr.h" // Not used for now. // //class MembersAddButton : public Ui::RippleButton { //public: // MembersAddButton(QWidget *parent, const style::TwoIconButton &st); // //protected: // void paintEvent(QPaintEvent *e) override; // // QImage prepareRippleMask() const override; // QPoint prepareRippleStartPosition() const override; // //private: // const style::TwoIconButton &_st; // //}; class PeerListRowWithLink : public PeerListRow { public: using PeerListRow::PeerListRow; void setActionLink(const QString &action); void lazyInitialize(const style::PeerListItem &st) override; private: void refreshActionLink(); QSize actionSize() const override; QMargins actionMargins() const override; void paintAction( Painter &p, TimeMs ms, int x, int y, int outerWidth, bool selected, bool actionSelected) override; QString _action; int _actionWidth = 0; }; class PeerListGlobalSearchController : public PeerListSearchController, private MTP::Sender { public: PeerListGlobalSearchController(); void searchQuery(const QString &query) override; bool isLoading() override; bool loadMoreRows() override { return false; } private: bool searchInCache(); void searchOnServer(); void searchDone(const MTPcontacts_Found &result, mtpRequestId requestId); base::Timer _timer; QString _query; mtpRequestId _requestId = 0; std::map<QString, MTPcontacts_Found> _cache; std::map<mtpRequestId, QString> _queries; }; class ChatsListBoxController : public PeerListController , protected base::Subscriber { public: ChatsListBoxController( std::unique_ptr<PeerListSearchController> searchController = std::make_unique<PeerListGlobalSearchController>()); void prepare() override final; std::unique_ptr<PeerListRow> createSearchRow(not_null<PeerData*> peer) override final; protected: class Row : public PeerListRow { public: Row(not_null<History*> history); not_null<History*> history() const { return _history; } private: not_null<History*> _history; }; virtual std::unique_ptr<Row> createRow(not_null<History*> history) = 0; virtual void prepareViewHook() = 0; virtual void updateRowHook(not_null<Row*> row) { } virtual QString emptyBoxText() const; private: void rebuildRows(); void checkForEmptyRows(); bool appendRow(not_null<History*> history); }; class ContactsBoxController : public PeerListController, protected base::Subscriber { public: ContactsBoxController(std::unique_ptr<PeerListSearchController> searchController = std::make_unique<PeerListGlobalSearchController>()); void prepare() override final; std::unique_ptr<PeerListRow> createSearchRow(not_null<PeerData*> peer) override final; void rowClicked(not_null<PeerListRow*> row) override; protected: virtual std::unique_ptr<PeerListRow> createRow(not_null<UserData*> user); virtual void prepareViewHook() { } virtual void updateRowHook(not_null<PeerListRow*> row) { } private: void rebuildRows(); void checkForEmptyRows(); bool appendRow(not_null<UserData*> user); }; class EditChatAdminsBoxController : public PeerListController, private base::Subscriber { public: static void Start(not_null<ChatData*> chat); EditChatAdminsBoxController(not_null<ChatData*> chat); bool allAreAdmins() const; void prepare() override; void rowClicked(not_null<PeerListRow*> row) override; private: void createAllAdminsCheckbox(); void rebuildRows(); std::unique_ptr<PeerListRow> createRow(not_null<UserData*> user); not_null<ChatData*> _chat; int _adminsUpdatedSubscription = 0; class LabeledCheckbox; QPointer<LabeledCheckbox> _allAdmins; }; class AddParticipantsBoxController : public ContactsBoxController { public: static void Start(not_null<ChatData*> chat); static void Start(not_null<ChannelData*> channel); static void Start( not_null<ChannelData*> channel, base::flat_set<not_null<UserData*>> &&alreadyIn); AddParticipantsBoxController(PeerData *peer); AddParticipantsBoxController( not_null<ChannelData*> channel, base::flat_set<not_null<UserData*>> &&alreadyIn); using ContactsBoxController::ContactsBoxController; void rowClicked(not_null<PeerListRow*> row) override; void itemDeselectedHook(not_null<PeerData*> peer) override; protected: void prepareViewHook() override; std::unique_ptr<PeerListRow> createRow(not_null<UserData*> user) override; private: static void Start( not_null<ChannelData*> channel, base::flat_set<not_null<UserData*>> &&alreadyIn, bool justCreated); int alreadyInCount() const; bool isAlreadyIn(not_null<UserData*> user) const; int fullCount() const; void updateTitle(); PeerData *_peer = nullptr; base::flat_set<not_null<UserData*>> _alreadyIn; }; class AddBotToGroupBoxController : public ChatsListBoxController, public base::has_weak_ptr { public: static void Start(not_null<UserData*> bot); AddBotToGroupBoxController(not_null<UserData*> bot); void rowClicked(not_null<PeerListRow*> row) override; protected: std::unique_ptr<Row> createRow(not_null<History*> history) override; void prepareViewHook() override; QString emptyBoxText() const override; private: static bool SharingBotGame(not_null<UserData*> bot); bool needToCreateRow(not_null<PeerData*> peer) const; bool sharingBotGame() const; QString noResultsText() const; QString descriptionText() const; void updateLabels(); void shareBotGame(not_null<PeerData*> chat); void addBotToGroup(not_null<PeerData*> chat); not_null<UserData*> _bot; }; class ChooseRecipientBoxController : public ChatsListBoxController { public: ChooseRecipientBoxController( base::lambda_once<void(not_null<PeerData*>)> callback); void rowClicked(not_null<PeerListRow*> row) override; bool respectSavedMessagesChat() const override { return true; } protected: void prepareViewHook() override; std::unique_ptr<Row> createRow( not_null<History*> history) override; private: base::lambda_once<void(not_null<PeerData*>)> _callback; }; ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/peers/���������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0021661�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp�����������������������������0000664�0000000�0000000�00000114413�13262451251�0026214�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/peers/edit_peer_info_box.h" #include <rpl/range.h> #include <rpl/flatten_latest.h> #include "info/profile/info_profile_button.h" #include "ui/wrap/vertical_layout.h" #include "ui/wrap/padding_wrap.h" #include "ui/wrap/slide_wrap.h" #include "ui/widgets/input_fields.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/labels.h" #include "ui/toast/toast.h" #include "ui/special_buttons.h" #include "boxes/confirm_box.h" #include "boxes/photo_crop_box.h" #include "boxes/add_contact_box.h" #include "boxes/stickers_box.h" #include "boxes/peer_list_controllers.h" #include "mtproto/sender.h" #include "lang/lang_keys.h" #include "mainwidget.h" #include "messenger.h" #include "apiwrap.h" #include "application.h" #include "auth_session.h" #include "observer_peer.h" #include "styles/style_boxes.h" #include "styles/style_info.h" namespace { constexpr auto kUsernameCheckTimeout = TimeMs(200); constexpr auto kMinUsernameLength = 5; constexpr auto kMaxChannelDescription = 255; // See also add_contact_box. class Controller : private MTP::Sender , private base::has_weak_ptr { public: Controller( not_null<BoxContent*> box, not_null<PeerData*> peer); object_ptr<Ui::VerticalLayout> createContent(); void setFocus(); private: enum class Privacy { Public, Private, }; enum class Invites { Everyone, OnlyAdmins, }; enum class HistoryVisibility { Visible, Hidden, }; enum class UsernameState { Normal, TooMany, NotAvailable, }; struct Controls { Ui::InputField *title = nullptr; Ui::InputArea *description = nullptr; Ui::UserpicButton *photo = nullptr; rpl::lifetime initialPhotoImageWaiting; std::shared_ptr<Ui::RadioenumGroup<Privacy>> privacy; Ui::SlideWrap<Ui::RpWidget> *usernameWrap = nullptr; Ui::UsernameInput *username = nullptr; base::unique_qptr<Ui::FlatLabel> usernameResult; const style::FlatLabel *usernameResultStyle = nullptr; Ui::SlideWrap<Ui::RpWidget> *createInviteLinkWrap = nullptr; Ui::SlideWrap<Ui::RpWidget> *editInviteLinkWrap = nullptr; Ui::FlatLabel *inviteLink = nullptr; std::shared_ptr<Ui::RadioenumGroup<HistoryVisibility>> historyVisibility; Ui::SlideWrap<Ui::RpWidget> *historyVisibilityWrap = nullptr; std::shared_ptr<Ui::RadioenumGroup<Invites>> invites; Ui::Checkbox *signatures = nullptr; }; struct Saving { base::optional<QString> username; base::optional<QString> title; base::optional<QString> description; base::optional<bool> hiddenPreHistory; base::optional<bool> signatures; base::optional<bool> everyoneInvites; }; base::lambda<QString()> computeTitle() const; object_ptr<Ui::RpWidget> createPhotoAndTitleEdit(); object_ptr<Ui::RpWidget> createTitleEdit(); object_ptr<Ui::RpWidget> createPhotoEdit(); object_ptr<Ui::RpWidget> createDescriptionEdit(); object_ptr<Ui::RpWidget> createPrivaciesEdit(); object_ptr<Ui::RpWidget> createUsernameEdit(); object_ptr<Ui::RpWidget> createInviteLinkCreate(); object_ptr<Ui::RpWidget> createInviteLinkEdit(); object_ptr<Ui::RpWidget> createHistoryVisibilityEdit(); object_ptr<Ui::RpWidget> createSignaturesEdit(); object_ptr<Ui::RpWidget> createInvitesEdit(); object_ptr<Ui::RpWidget> createStickersEdit(); object_ptr<Ui::RpWidget> createManageAdminsButton(); object_ptr<Ui::RpWidget> createUpgradeButton(); object_ptr<Ui::RpWidget> createDeleteButton(); QString inviteLinkText() const; void submitTitle(); void submitDescription(); void deleteWithConfirmation(); void privacyChanged(Privacy value); void checkUsernameAvailability(); void askUsernameRevoke(); void usernameChanged(); void showUsernameError(rpl::producer<QString> &&error); void showUsernameGood(); void showUsernameResult( rpl::producer<QString> &&text, not_null<const style::FlatLabel*> st); bool canEditInviteLink() const; bool inviteLinkShown() const; void refreshEditInviteLink(); void refreshCreateInviteLink(); void refreshHistoryVisibility(); void createInviteLink(); void revokeInviteLink(); void exportInviteLink(const QString &confirmation); base::optional<Saving> validate() const; bool validateUsername(Saving &to) const; bool validateTitle(Saving &to) const; bool validateDescription(Saving &to) const; bool validateHistoryVisibility(Saving &to) const; bool validateInvites(Saving &to) const; bool validateSignatures(Saving &to) const; void save(); void saveUsername(); void saveTitle(); void saveDescription(); void saveHistoryVisibility(); void saveInvites(); void saveSignatures(); void savePhoto(); void pushSaveStage(base::lambda_once<void()> &&lambda); void continueSave(); void cancelSave(); not_null<BoxContent*> _box; not_null<PeerData*> _peer; bool _isGroup = false; base::unique_qptr<Ui::VerticalLayout> _wrap; Controls _controls; base::Timer _checkUsernameTimer; mtpRequestId _checkUsernameRequestId = 0; UsernameState _usernameState = UsernameState::Normal; rpl::event_stream<rpl::producer<QString>> _usernameResultTexts; std::deque<base::lambda_once<void()>> _saveStagesQueue; Saving _savingData; }; Controller::Controller( not_null<BoxContent*> box, not_null<PeerData*> peer) : _box(box) , _peer(peer) , _isGroup(_peer->isChat() || _peer->isMegagroup()) , _checkUsernameTimer([this] { checkUsernameAvailability(); }) { _box->setTitle(computeTitle()); _box->addButton(langFactory(lng_settings_save), [this] { save(); }); _box->addButton(langFactory(lng_cancel), [this] { _box->closeBox(); }); } base::lambda<QString()> Controller::computeTitle() const { return langFactory(_isGroup ? lng_edit_group : lng_edit_channel_title); } object_ptr<Ui::VerticalLayout> Controller::createContent() { auto result = object_ptr<Ui::VerticalLayout>(_box); _wrap.reset(result.data()); _controls = Controls(); _wrap->add(createPhotoAndTitleEdit()); _wrap->add(createDescriptionEdit()); _wrap->add(createPrivaciesEdit()); _wrap->add(createInviteLinkCreate()); _wrap->add(createInviteLinkEdit()); _wrap->add(createHistoryVisibilityEdit()); _wrap->add(createSignaturesEdit()); _wrap->add(createInvitesEdit()); _wrap->add(createStickersEdit()); _wrap->add(createManageAdminsButton()); _wrap->add(createUpgradeButton()); _wrap->add(createDeleteButton()); _wrap->resizeToWidth(st::boxWideWidth); return result; } void Controller::setFocus() { if (_controls.title) { _controls.title->setFocusFast(); } } object_ptr<Ui::RpWidget> Controller::createPhotoAndTitleEdit() { Expects(_wrap != nullptr); auto canEdit = [&] { if (auto channel = _peer->asChannel()) { return channel->canEditInformation(); } else if (auto chat = _peer->asChat()) { return chat->canEdit(); } return false; }(); if (!canEdit) { return nullptr; } auto result = object_ptr<Ui::RpWidget>(_wrap); auto container = result.data(); auto photoWrap = Ui::AttachParentChild( container, createPhotoEdit()); auto titleEdit = Ui::AttachParentChild( container, createTitleEdit()); photoWrap->heightValue( ) | rpl::start_with_next([container](int height) { container->resize(container->width(), height); }, photoWrap->lifetime()); container->widthValue( ) | rpl::start_with_next([titleEdit](int width) { auto left = st::editPeerPhotoMargins.left() + st::defaultUserpicButton.size.width(); titleEdit->resizeToWidth(width - left); titleEdit->moveToLeft(left, 0, width); }, titleEdit->lifetime()); return result; } object_ptr<Ui::RpWidget> Controller::createPhotoEdit() { Expects(_wrap != nullptr); using PhotoWrap = Ui::PaddingWrap<Ui::UserpicButton>; auto photoWrap = object_ptr<PhotoWrap>( _wrap, object_ptr<Ui::UserpicButton>( _wrap, _box->controller(), _peer, Ui::UserpicButton::Role::ChangePhoto, st::defaultUserpicButton), st::editPeerPhotoMargins); _controls.photo = photoWrap->entity(); return photoWrap; } object_ptr<Ui::RpWidget> Controller::createTitleEdit() { Expects(_wrap != nullptr); auto result = object_ptr<Ui::PaddingWrap<Ui::InputField>>( _wrap, object_ptr<Ui::InputField>( _wrap, st::defaultInputField, langFactory(_isGroup ? lng_dlg_new_group_name : lng_dlg_new_channel_name), _peer->name), st::editPeerTitleMargins); QObject::connect( result->entity(), &Ui::InputField::submitted, [this] { submitTitle(); }); _controls.title = result->entity(); return std::move(result); } object_ptr<Ui::RpWidget> Controller::createDescriptionEdit() { Expects(_wrap != nullptr); auto channel = _peer->asChannel(); if (!channel || !channel->canEditInformation()) { return nullptr; } auto result = object_ptr<Ui::PaddingWrap<Ui::InputArea>>( _wrap, object_ptr<Ui::InputArea>( _wrap, st::editPeerDescription, langFactory(lng_create_group_description), channel->about()), st::editPeerDescriptionMargins); result->entity()->setMaxLength(kMaxChannelDescription); QObject::connect( result->entity(), &Ui::InputArea::submitted, [this] { submitDescription(); }); _controls.description = result->entity(); return std::move(result); } object_ptr<Ui::RpWidget> Controller::createPrivaciesEdit() { Expects(_wrap != nullptr); auto channel = _peer->asChannel(); if (!channel || !channel->canEditUsername()) { return nullptr; } auto result = object_ptr<Ui::PaddingWrap<Ui::VerticalLayout>>( _wrap, object_ptr<Ui::VerticalLayout>(_wrap), st::editPeerPrivaciesMargins); auto container = result->entity(); _controls.privacy = std::make_shared<Ui::RadioenumGroup<Privacy>>( channel->isPublic() ? Privacy::Public : Privacy::Private); auto addButton = [&]( Privacy value, LangKey groupTextKey, LangKey channelTextKey, LangKey groupAboutKey, LangKey channelAboutKey) { container->add(object_ptr<Ui::FixedHeightWidget>( container, st::editPeerPrivacyTopSkip)); container->add(object_ptr<Ui::Radioenum<Privacy>>( container, _controls.privacy, value, lang(_isGroup ? groupTextKey : channelTextKey), st::defaultBoxCheckbox)); container->add(object_ptr<Ui::PaddingWrap<Ui::FlatLabel>>( container, object_ptr<Ui::FlatLabel>( container, Lang::Viewer(_isGroup ? groupAboutKey : channelAboutKey), st::editPeerPrivacyLabel), st::editPeerPrivacyLabelMargins)); container->add(object_ptr<Ui::FixedHeightWidget>( container, st::editPeerPrivacyBottomSkip)); }; addButton( Privacy::Public, lng_create_public_group_title, lng_create_public_channel_title, lng_create_public_group_about, lng_create_public_channel_about); addButton( Privacy::Private, lng_create_private_group_title, lng_create_private_channel_title, lng_create_private_group_about, lng_create_private_channel_about); container->add(createUsernameEdit()); _controls.privacy->setChangedCallback([this](Privacy value) { privacyChanged(value); }); if (!channel->isPublic()) { checkUsernameAvailability(); } return std::move(result); } object_ptr<Ui::RpWidget> Controller::createUsernameEdit() { Expects(_wrap != nullptr); auto channel = _peer->asChannel(); Assert(channel != nullptr); auto result = object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>( _wrap, object_ptr<Ui::VerticalLayout>(_wrap), st::editPeerUsernameMargins); _controls.usernameWrap = result.data(); auto container = result->entity(); container->add(object_ptr<Ui::FlatLabel>( container, Lang::Viewer(lng_create_group_link), st::editPeerSectionLabel)); auto placeholder = container->add(object_ptr<Ui::RpWidget>( container)); placeholder->setAttribute(Qt::WA_TransparentForMouseEvents); _controls.username = Ui::AttachParentChild( container, object_ptr<Ui::UsernameInput>( container, st::setupChannelLink, base::lambda<QString()>(), channel->username, true)); _controls.username->heightValue( ) | rpl::start_with_next([placeholder](int height) { placeholder->resize(placeholder->width(), height); }, placeholder->lifetime()); placeholder->widthValue( ) | rpl::start_with_next([this](int width) { _controls.username->resize( width, _controls.username->height()); }, placeholder->lifetime()); _controls.username->move(placeholder->pos()); QObject::connect( _controls.username, &Ui::UsernameInput::changed, [this] { usernameChanged(); }); auto shown = (_controls.privacy->value() == Privacy::Public); result->toggle(shown, anim::type::instant); return std::move(result); } void Controller::privacyChanged(Privacy value) { auto toggleEditUsername = [&] { _controls.usernameWrap->toggle( (value == Privacy::Public), anim::type::instant); }; auto refreshVisibilities = [&] { // First we need to show everything, then hide anything. // Otherwise the scroll position could jump up undesirably. if (value == Privacy::Public) { toggleEditUsername(); } refreshCreateInviteLink(); refreshEditInviteLink(); refreshHistoryVisibility(); if (value == Privacy::Public) { _controls.usernameResult = nullptr; checkUsernameAvailability(); } else { toggleEditUsername(); } }; if (value == Privacy::Public) { if (_usernameState == UsernameState::TooMany) { askUsernameRevoke(); return; } else if (_usernameState == UsernameState::NotAvailable) { _controls.privacy->setValue(Privacy::Private); return; } refreshVisibilities(); _controls.username->setDisplayFocused(true); _controls.username->setFocus(); _box->scrollToWidget(_controls.username); } else { request(base::take(_checkUsernameRequestId)).cancel(); _checkUsernameTimer.cancel(); refreshVisibilities(); setFocus(); } } void Controller::checkUsernameAvailability() { if (!_controls.username) { return; } auto channel = _peer->asChannel(); Assert(channel != nullptr); auto initial = (_controls.privacy->value() != Privacy::Public); auto checking = initial ? qsl(".bad.") : _controls.username->getLastText().trimmed(); if (checking.size() < kMinUsernameLength) { return; } if (_checkUsernameRequestId) { request(_checkUsernameRequestId).cancel(); } _checkUsernameRequestId = request(MTPchannels_CheckUsername( channel->inputChannel, MTP_string(checking) )).done([=](const MTPBool &result) { _checkUsernameRequestId = 0; if (initial) { return; } if (!mtpIsTrue(result) && checking != channel->username) { showUsernameError( Lang::Viewer(lng_create_channel_link_occupied)); } else { showUsernameGood(); } }).fail([=](const RPCError &error) { _checkUsernameRequestId = 0; auto type = error.type(); _usernameState = UsernameState::Normal; if (type == qstr("CHANNEL_PUBLIC_GROUP_NA")) { _usernameState = UsernameState::NotAvailable; _controls.privacy->setValue(Privacy::Private); } else if (type == qstr("CHANNELS_ADMIN_PUBLIC_TOO_MUCH")) { _usernameState = UsernameState::TooMany; if (_controls.privacy->value() == Privacy::Public) { askUsernameRevoke(); } } else if (initial) { if (_controls.privacy->value() == Privacy::Public) { _controls.usernameResult = nullptr; _controls.username->setFocus(); _box->scrollToWidget(_controls.username); } } else if (type == qstr("USERNAME_INVALID")) { showUsernameError( Lang::Viewer(lng_create_channel_link_invalid)); } else if (type == qstr("USERNAME_OCCUPIED") && checking != channel->username) { showUsernameError( Lang::Viewer(lng_create_channel_link_occupied)); } }).send(); } void Controller::askUsernameRevoke() { _controls.privacy->setValue(Privacy::Private); auto revokeCallback = base::lambda_guarded(this, [this] { _usernameState = UsernameState::Normal; _controls.privacy->setValue(Privacy::Public); checkUsernameAvailability(); }); Ui::show( Box<RevokePublicLinkBox>(std::move(revokeCallback)), LayerOption::KeepOther); } void Controller::usernameChanged() { auto username = _controls.username->getLastText().trimmed(); if (username.isEmpty()) { _controls.usernameResult = nullptr; _checkUsernameTimer.cancel(); return; } auto bad = ranges::find_if(username, [](QChar ch) { return (ch < 'A' || ch > 'Z') && (ch < 'a' || ch > 'z') && (ch < '0' || ch > '9') && (ch != '_'); }) != username.end(); if (bad) { showUsernameError( Lang::Viewer(lng_create_channel_link_bad_symbols)); } else if (username.size() < kMinUsernameLength) { showUsernameError( Lang::Viewer(lng_create_channel_link_too_short)); } else { _controls.usernameResult = nullptr; _checkUsernameTimer.callOnce(kUsernameCheckTimeout); } } void Controller::showUsernameError(rpl::producer<QString> &&error) { showUsernameResult(std::move(error), &st::editPeerUsernameError); } void Controller::showUsernameGood() { showUsernameResult( Lang::Viewer(lng_create_channel_link_available), &st::editPeerUsernameGood); } void Controller::showUsernameResult( rpl::producer<QString> &&text, not_null<const style::FlatLabel*> st) { if (!_controls.usernameResult || _controls.usernameResultStyle != st) { _controls.usernameResultStyle = st; _controls.usernameResult = base::make_unique_q<Ui::FlatLabel>( _controls.usernameWrap, _usernameResultTexts.events() | rpl::flatten_latest(), *st); auto label = _controls.usernameResult.get(); label->show(); label->widthValue( ) | rpl::start_with_next([label] { label->moveToRight( st::editPeerUsernamePosition.x(), st::editPeerUsernamePosition.y()); }, label->lifetime()); } _usernameResultTexts.fire(std::move(text)); } void Controller::createInviteLink() { exportInviteLink(lang(_isGroup ? lng_group_invite_about : lng_group_invite_about_channel)); } void Controller::revokeInviteLink() { exportInviteLink(lang(lng_group_invite_about_new)); } void Controller::exportInviteLink(const QString &confirmation) { auto boxPointer = std::make_shared<QPointer<ConfirmBox>>(); auto callback = base::lambda_guarded(this, [=] { if (auto strong = *boxPointer) { strong->closeBox(); } Auth().api().exportInviteLink(_peer); }); auto box = Box<ConfirmBox>( confirmation, std::move(callback)); *boxPointer = Ui::show(std::move(box), LayerOption::KeepOther); } bool Controller::canEditInviteLink() const { if (auto channel = _peer->asChannel()) { if (channel->canEditUsername()) { return true; } return (!channel->isPublic() && channel->canAddMembers()); } else if (auto chat = _peer->asChat()) { return !chat->inviteLink().isEmpty() || chat->canEdit(); } return false; } bool Controller::inviteLinkShown() const { return !_controls.privacy || (_controls.privacy->value() == Privacy::Private); } QString Controller::inviteLinkText() const { if (auto channel = _peer->asChannel()) { return channel->inviteLink(); } else if (auto chat = _peer->asChat()) { return chat->inviteLink(); } return QString(); } object_ptr<Ui::RpWidget> Controller::createInviteLinkEdit() { Expects(_wrap != nullptr); if (!canEditInviteLink()) { return nullptr; } auto result = object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>( _wrap, object_ptr<Ui::VerticalLayout>(_wrap), st::editPeerInviteLinkMargins); _controls.editInviteLinkWrap = result.data(); auto container = result->entity(); container->add(object_ptr<Ui::FlatLabel>( container, Lang::Viewer(lng_profile_invite_link_section), st::editPeerSectionLabel)); container->add(object_ptr<Ui::FixedHeightWidget>( container, st::editPeerInviteLinkSkip)); _controls.inviteLink = container->add(object_ptr<Ui::FlatLabel>( container, st::editPeerInviteLink)); _controls.inviteLink->setSelectable(true); _controls.inviteLink->setContextCopyText(QString()); _controls.inviteLink->setBreakEverywhere(true); _controls.inviteLink->setClickHandlerHook([this](auto&&...) { Application::clipboard()->setText(inviteLinkText()); Ui::Toast::Show(lang(lng_group_invite_copied)); return false; }); container->add(object_ptr<Ui::FixedHeightWidget>( container, st::editPeerInviteLinkSkip)); container->add(object_ptr<Ui::LinkButton>( container, lang(lng_group_invite_create_new), st::editPeerInviteLinkButton) )->addClickHandler([this] { revokeInviteLink(); }); Notify::PeerUpdateValue( _peer, Notify::PeerUpdate::Flag::InviteLinkChanged ) | rpl::start_with_next([this] { refreshEditInviteLink(); }, _controls.editInviteLinkWrap->lifetime()); return std::move(result); } void Controller::refreshEditInviteLink() { auto link = inviteLinkText(); auto text = TextWithEntities(); if (!link.isEmpty()) { text.text = link; auto remove = qstr("https://"); if (text.text.startsWith(remove)) { text.text.remove(0, remove.size()); } text.entities.push_back(EntityInText( EntityInTextCustomUrl, 0, text.text.size(), link)); } _controls.inviteLink->setMarkedText(text); // Hack to expand FlatLabel width to naturalWidth again. _controls.editInviteLinkWrap->resizeToWidth(st::boxWideWidth); _controls.editInviteLinkWrap->toggle( inviteLinkShown() && !link.isEmpty(), anim::type::instant); } object_ptr<Ui::RpWidget> Controller::createInviteLinkCreate() { Expects(_wrap != nullptr); if (!canEditInviteLink()) { return nullptr; } auto result = object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>( _wrap, object_ptr<Ui::VerticalLayout>(_wrap), st::editPeerInviteLinkMargins); auto container = result->entity(); container->add(object_ptr<Ui::FlatLabel>( container, Lang::Viewer(lng_profile_invite_link_section), st::editPeerSectionLabel)); container->add(object_ptr<Ui::FixedHeightWidget>( container, st::editPeerInviteLinkSkip)); container->add(object_ptr<Ui::LinkButton>( _wrap, lang(lng_group_invite_create), st::editPeerInviteLinkButton) )->addClickHandler([this] { createInviteLink(); }); _controls.createInviteLinkWrap = result.data(); Notify::PeerUpdateValue( _peer, Notify::PeerUpdate::Flag::InviteLinkChanged ) | rpl::start_with_next([this] { refreshCreateInviteLink(); }, _controls.createInviteLinkWrap->lifetime()); return std::move(result); } void Controller::refreshCreateInviteLink() { _controls.createInviteLinkWrap->toggle( inviteLinkShown() && inviteLinkText().isEmpty(), anim::type::instant); } object_ptr<Ui::RpWidget> Controller::createHistoryVisibilityEdit() { Expects(_wrap != nullptr); auto channel = _peer->asChannel(); if (!channel || !channel->canEditPreHistoryHidden() || !channel->isMegagroup() || (channel->isPublic() && !channel->canEditUsername())) { return nullptr; } auto result = object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>( _wrap, object_ptr<Ui::VerticalLayout>(_wrap), st::editPeerInvitesMargins); _controls.historyVisibilityWrap = result.data(); auto container = result->entity(); _controls.historyVisibility = std::make_shared<Ui::RadioenumGroup<HistoryVisibility>>( channel->hiddenPreHistory() ? HistoryVisibility::Hidden : HistoryVisibility::Visible); auto addButton = [&]( HistoryVisibility value, LangKey groupTextKey, LangKey groupAboutKey) { container->add(object_ptr<Ui::FixedHeightWidget>( container, st::editPeerPrivacyTopSkip + st::editPeerPrivacyBottomSkip)); container->add(object_ptr<Ui::Radioenum<HistoryVisibility>>( container, _controls.historyVisibility, value, lang(groupTextKey), st::defaultBoxCheckbox)); container->add(object_ptr<Ui::PaddingWrap<Ui::FlatLabel>>( container, object_ptr<Ui::FlatLabel>( container, Lang::Viewer(groupAboutKey), st::editPeerPrivacyLabel), st::editPeerPrivacyLabelMargins)); }; container->add(object_ptr<Ui::FlatLabel>( container, Lang::Viewer(lng_manage_history_visibility_title), st::editPeerSectionLabel)); addButton( HistoryVisibility::Visible, lng_manage_history_visibility_shown, lng_manage_history_visibility_shown_about); addButton( HistoryVisibility::Hidden, lng_manage_history_visibility_hidden, lng_manage_history_visibility_hidden_about); refreshHistoryVisibility(); return std::move(result); } void Controller::refreshHistoryVisibility() { if (!_controls.historyVisibilityWrap) { return; } auto historyVisibilityShown = !_controls.privacy || (_controls.privacy->value() == Privacy::Private); _controls.historyVisibilityWrap->toggle( historyVisibilityShown, anim::type::instant); } object_ptr<Ui::RpWidget> Controller::createSignaturesEdit() { Expects(_wrap != nullptr); auto channel = _peer->asChannel(); if (!channel || !channel->canEditSignatures() || channel->isMegagroup()) { return nullptr; } auto result = object_ptr<Ui::VerticalLayout>(_wrap); auto container = result.data(); container->add(object_ptr<Ui::FixedHeightWidget>( container, st::defaultBoxCheckbox.margin.top())); _controls.signatures = container->add( object_ptr<Ui::PaddingWrap<Ui::Checkbox>>( container, object_ptr<Ui::Checkbox>( container, lang(lng_edit_sign_messages), channel->addsSignature(), st::defaultBoxCheckbox), st::editPeerSignaturesMargins))->entity(); container->add(object_ptr<Ui::FixedHeightWidget>( container, st::defaultBoxCheckbox.margin.bottom())); return std::move(result); } object_ptr<Ui::RpWidget> Controller::createInvitesEdit() { Expects(_wrap != nullptr); auto channel = _peer->asChannel(); if (!channel || !channel->canEditInvites() || !channel->isMegagroup()) { return nullptr; } auto result = object_ptr<Ui::PaddingWrap<Ui::VerticalLayout>>( _wrap, object_ptr<Ui::VerticalLayout>(_wrap), st::editPeerInvitesMargins); auto container = result->entity(); container->add(object_ptr<Ui::FlatLabel>( container, Lang::Viewer(lng_edit_group_who_invites), st::editPeerSectionLabel)); _controls.invites = std::make_shared<Ui::RadioenumGroup<Invites>>( channel->anyoneCanAddMembers() ? Invites::Everyone : Invites::OnlyAdmins); auto addButton = [&]( Invites value, LangKey textKey) { container->add(object_ptr<Ui::FixedHeightWidget>( container, st::editPeerInvitesTopSkip + st::editPeerInvitesSkip)); container->add(object_ptr<Ui::Radioenum<Invites>>( container, _controls.invites, value, lang(textKey), st::defaultBoxCheckbox)); }; addButton( Invites::Everyone, lng_edit_group_invites_everybody); addButton( Invites::OnlyAdmins, lng_edit_group_invites_only_admins); container->add(object_ptr<Ui::FixedHeightWidget>( container, st::editPeerInvitesSkip)); return std::move(result); } object_ptr<Ui::RpWidget> Controller::createStickersEdit() { Expects(_wrap != nullptr); auto channel = _peer->asChannel(); if (!channel || !channel->canEditStickers()) { return nullptr; } auto result = object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>( _wrap, object_ptr<Ui::VerticalLayout>(_wrap), st::editPeerInviteLinkMargins); auto container = result->entity(); container->add(object_ptr<Ui::FlatLabel>( container, Lang::Viewer(lng_group_stickers), st::editPeerSectionLabel)); container->add(object_ptr<Ui::FixedHeightWidget>( container, st::editPeerInviteLinkSkip)); container->add(object_ptr<Ui::FlatLabel>( container, Lang::Viewer(lng_group_stickers_description), st::editPeerPrivacyLabel)); container->add(object_ptr<Ui::FixedHeightWidget>( container, st::editPeerInviteLinkSkip)); container->add(object_ptr<Ui::LinkButton>( _wrap, lang(lng_group_stickers_add), st::editPeerInviteLinkButton) )->addClickHandler([channel] { Ui::show(Box<StickersBox>(channel), LayerOption::KeepOther); }); return std::move(result); } object_ptr<Ui::RpWidget> Controller::createManageAdminsButton() { Expects(_wrap != nullptr); auto chat = _peer->asChat(); if (!chat || !chat->amCreator() || chat->isDeactivated()) { return nullptr; } auto result = object_ptr<Ui::PaddingWrap<Ui::LinkButton>>( _wrap, object_ptr<Ui::LinkButton>( _wrap, lang(lng_profile_manage_admins), st::editPeerInviteLinkButton), st::editPeerDeleteButtonMargins); result->entity()->addClickHandler([=] { EditChatAdminsBoxController::Start(chat); }); return std::move(result); } object_ptr<Ui::RpWidget> Controller::createUpgradeButton() { Expects(_wrap != nullptr); auto chat = _peer->asChat(); if (!chat || !chat->amCreator() || chat->isDeactivated()) { return nullptr; } auto result = object_ptr<Ui::PaddingWrap<Ui::LinkButton>>( _wrap, object_ptr<Ui::LinkButton>( _wrap, lang(lng_profile_migrate_button), st::editPeerInviteLinkButton), st::editPeerDeleteButtonMargins); result->entity()->addClickHandler([=] { Ui::show(Box<ConvertToSupergroupBox>(chat), LayerOption::KeepOther); }); return std::move(result); } object_ptr<Ui::RpWidget> Controller::createDeleteButton() { Expects(_wrap != nullptr); auto channel = _peer->asChannel(); if (!channel || !channel->canDelete()) { return nullptr; } auto text = lang(_isGroup ? lng_profile_delete_group : lng_profile_delete_channel); auto result = object_ptr<Ui::PaddingWrap<Ui::LinkButton>>( _wrap, object_ptr<Ui::LinkButton>( _wrap, text, st::editPeerDeleteButton), st::editPeerDeleteButtonMargins); result->entity()->addClickHandler([this] { deleteWithConfirmation(); }); return std::move(result); } void Controller::submitTitle() { Expects(_controls.title != nullptr); if (_controls.title->getLastText().isEmpty()) { _controls.title->showError(); _box->scrollToWidget(_controls.title); } else if (_controls.description) { _controls.description->setFocus(); _box->scrollToWidget(_controls.description); } } void Controller::submitDescription() { Expects(_controls.title != nullptr); Expects(_controls.description != nullptr); if (_controls.title->getLastText().isEmpty()) { _controls.title->showError(); _box->scrollToWidget(_controls.title); } else { save(); } } base::optional<Controller::Saving> Controller::validate() const { auto result = Saving(); if (validateUsername(result) && validateTitle(result) && validateDescription(result) && validateHistoryVisibility(result) && validateInvites(result) && validateSignatures(result)) { return result; } return {}; } bool Controller::validateUsername(Saving &to) const { if (!_controls.privacy) { return true; } else if (_controls.privacy->value() == Privacy::Private) { to.username = QString(); return true; } auto username = _controls.username->getLastText().trimmed(); if (username.isEmpty()) { _controls.username->showError(); _box->scrollToWidget(_controls.username); return false; } to.username = username; return true; } bool Controller::validateTitle(Saving &to) const { if (!_controls.title) { return true; } auto title = _controls.title->getLastText().trimmed(); if (title.isEmpty()) { _controls.title->showError(); _box->scrollToWidget(_controls.title); return false; } to.title = title; return true; } bool Controller::validateDescription(Saving &to) const { if (!_controls.description) { return true; } to.description = _controls.description->getLastText().trimmed(); return true; } bool Controller::validateHistoryVisibility(Saving &to) const { if (!_controls.historyVisibility || (_controls.privacy && _controls.privacy->value() == Privacy::Public)) { return true; } to.hiddenPreHistory = (_controls.historyVisibility->value() == HistoryVisibility::Hidden); return true; } bool Controller::validateInvites(Saving &to) const { if (!_controls.invites) { return true; } to.everyoneInvites = (_controls.invites->value() == Invites::Everyone); return true; } bool Controller::validateSignatures(Saving &to) const { if (!_controls.signatures) { return true; } to.signatures = _controls.signatures->checked(); return true; } void Controller::save() { Expects(_wrap != nullptr); if (!_saveStagesQueue.empty()) { return; } if (auto saving = validate()) { _savingData = *saving; pushSaveStage([this] { saveUsername(); }); pushSaveStage([this] { saveTitle(); }); pushSaveStage([this] { saveDescription(); }); pushSaveStage([this] { saveHistoryVisibility(); }); pushSaveStage([this] { saveInvites(); }); pushSaveStage([this] { saveSignatures(); }); pushSaveStage([this] { savePhoto(); }); continueSave(); } } void Controller::pushSaveStage(base::lambda_once<void()> &&lambda) { _saveStagesQueue.push_back(std::move(lambda)); } void Controller::continueSave() { if (!_saveStagesQueue.empty()) { auto next = std::move(_saveStagesQueue.front()); _saveStagesQueue.pop_front(); next(); } } void Controller::cancelSave() { _saveStagesQueue.clear(); } void Controller::saveUsername() { auto channel = _peer->asChannel(); if (!_savingData.username || !channel || *_savingData.username == channel->username) { return continueSave(); } request(MTPchannels_UpdateUsername( channel->inputChannel, MTP_string(*_savingData.username) )).done([=](const MTPBool &result) { channel->setName( TextUtilities::SingleLine(channel->name), *_savingData.username); continueSave(); }).fail([=](const RPCError &error) { auto type = error.type(); if (type == qstr("USERNAME_NOT_MODIFIED")) { channel->setName( TextUtilities::SingleLine(channel->name), TextUtilities::SingleLine(*_savingData.username)); continueSave(); return; } auto errorKey = [&] { if (type == qstr("USERNAME_INVALID")) { return lng_create_channel_link_invalid; } else if (type == qstr("USERNAME_OCCUPIED") || type == qstr("USERNAMES_UNAVAILABLE")) { return lng_create_channel_link_invalid; } return lng_create_channel_link_invalid; }(); _controls.username->showError(); _box->scrollToWidget(_controls.username); showUsernameError(Lang::Viewer(errorKey)); cancelSave(); }).send(); } void Controller::saveTitle() { if (!_savingData.title || *_savingData.title == _peer->name) { return continueSave(); } auto onDone = [this](const MTPUpdates &result) { Auth().api().applyUpdates(result); continueSave(); }; auto onFail = [this](const RPCError &error) { auto type = error.type(); if (type == qstr("CHAT_NOT_MODIFIED") || type == qstr("CHAT_TITLE_NOT_MODIFIED")) { if (auto channel = _peer->asChannel()) { channel->setName(*_savingData.title, channel->username); } else if (auto chat = _peer->asChat()) { chat->setName(*_savingData.title); } continueSave(); return; } _controls.title->showError(); if (type == qstr("NO_CHAT_TITLE")) { _box->scrollToWidget(_controls.title); } cancelSave(); }; if (auto channel = _peer->asChannel()) { request(MTPchannels_EditTitle( channel->inputChannel, MTP_string(*_savingData.title) )).done(std::move(onDone) ).fail(std::move(onFail) ).send(); } else if (auto chat = _peer->asChat()) { request(MTPmessages_EditChatTitle( chat->inputChat, MTP_string(*_savingData.title) )).done(std::move(onDone) ).fail(std::move(onFail) ).send(); } else { continueSave(); } } void Controller::saveDescription() { auto channel = _peer->asChannel(); if (!_savingData.description || !channel || *_savingData.description == channel->about()) { return continueSave(); } auto successCallback = [=] { channel->setAbout(*_savingData.description); continueSave(); }; request(MTPchannels_EditAbout( channel->inputChannel, MTP_string(*_savingData.description) )).done([=](const MTPBool &result) { successCallback(); }).fail([=](const RPCError &error) { auto type = error.type(); if (type == qstr("CHAT_ABOUT_NOT_MODIFIED")) { successCallback(); return; } _controls.description->showError(); cancelSave(); }).send(); } void Controller::saveHistoryVisibility() { auto channel = _peer->asChannel(); if (!_savingData.hiddenPreHistory || !channel || *_savingData.hiddenPreHistory == channel->hiddenPreHistory()) { return continueSave(); } request(MTPchannels_TogglePreHistoryHidden( channel->inputChannel, MTP_bool(*_savingData.hiddenPreHistory) )).done([=](const MTPUpdates &result) { // Update in the result doesn't contain the // channelFull:flags field which holds this value. // So after saving we need to update it manually. channel->updateFullForced(); Auth().api().applyUpdates(result); continueSave(); }).fail([this](const RPCError &error) { if (error.type() == qstr("CHAT_NOT_MODIFIED")) { continueSave(); } else { cancelSave(); } }).send(); } void Controller::saveInvites() { auto channel = _peer->asChannel(); if (!_savingData.everyoneInvites || !channel || *_savingData.everyoneInvites == channel->anyoneCanAddMembers()) { return continueSave(); } request(MTPchannels_ToggleInvites( channel->inputChannel, MTP_bool(*_savingData.everyoneInvites) )).done([this](const MTPUpdates &result) { Auth().api().applyUpdates(result); continueSave(); }).fail([this](const RPCError &error) { if (error.type() == qstr("CHAT_NOT_MODIFIED")) { continueSave(); } else { cancelSave(); } }).send(); } void Controller::saveSignatures() { auto channel = _peer->asChannel(); if (!_savingData.signatures || !channel || *_savingData.signatures == channel->addsSignature()) { return continueSave(); } request(MTPchannels_ToggleSignatures( channel->inputChannel, MTP_bool(*_savingData.signatures) )).done([this](const MTPUpdates &result) { Auth().api().applyUpdates(result); continueSave(); }).fail([this](const RPCError &error) { if (error.type() == qstr("CHAT_NOT_MODIFIED")) { continueSave(); } else { cancelSave(); } }).send(); } void Controller::savePhoto() { auto image = _controls.photo ? _controls.photo->takeResultImage() : QImage(); if (!image.isNull()) { Messenger::Instance().uploadProfilePhoto( std::move(image), _peer->id); } _box->closeBox(); } void Controller::deleteWithConfirmation() { auto channel = _peer->asChannel(); Assert(channel != nullptr); auto text = lang(_isGroup ? lng_sure_delete_group : lng_sure_delete_channel); auto deleteCallback = [=] { Ui::hideLayer(); Ui::showChatsList(); if (auto chat = channel->migrateFrom()) { App::main()->deleteAndExit(chat); } MTP::send( MTPchannels_DeleteChannel(channel->inputChannel), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::deleteChannelFailed)); }; Ui::show(Box<ConfirmBox>( text, lang(lng_box_delete), st::attentionBoxButton, std::move(deleteCallback)), LayerOption::KeepOther); } } // namespace EditPeerInfoBox::EditPeerInfoBox( QWidget*, not_null<PeerData*> peer) : _peer(peer) { } void EditPeerInfoBox::prepare() { auto controller = std::make_unique<Controller>(this, _peer); _focusRequests.events( ) | rpl::start_with_next( [c = controller.get()] { c->setFocus(); }, lifetime()); auto content = controller->createContent(); content->heightValue( ) | rpl::start_with_next([this](int height) { setDimensions(st::boxWideWidth, height); }, content->lifetime()); setInnerWidget(object_ptr<Ui::IgnoreMargins>( this, std::move(content))); Ui::AttachAsChild(this, std::move(controller)); } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.h�������������������������������0000664�0000000�0000000�00000001145�13262451251�0025656�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/event_stream.h> #include "boxes/peers/manage_peer_box.h" class EditPeerInfoBox : public BoxContent { public: EditPeerInfoBox(QWidget*, not_null<PeerData*> peer); void setInnerFocus() override { _focusRequests.fire({}); } protected: void prepare() override; private: not_null<PeerData*> _peer; rpl::event_stream<> _focusRequests; }; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/peers/manage_peer_box.cpp��������������������������������0000664�0000000�0000000�00000013763�13262451251�0025512�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/peers/manage_peer_box.h" #include <rpl/combine.h> #include "lang/lang_keys.h" #include "boxes/peers/edit_peer_info_box.h" #include "ui/wrap/vertical_layout.h" #include "ui/widgets/labels.h" #include "history/admin_log/history_admin_log_section.h" #include "window/window_controller.h" #include "profile/profile_channel_controllers.h" #include "info/profile/info_profile_button.h" #include "info/profile/info_profile_icon.h" #include "info/profile/info_profile_values.h" #include "styles/style_boxes.h" #include "styles/style_info.h" namespace { base::lambda<QString()> ManagePeerTitle( not_null<ChannelData*> channel) { return langFactory(channel->isMegagroup() ? lng_manage_group_title : lng_manage_channel_title); } auto ToPositiveNumberString() { return rpl::map([](int count) { return count ? QString::number(count) : QString(); }); } template <typename Callback> Info::Profile::Button *AddButton( not_null<Ui::VerticalLayout*> parent, rpl::producer<QString> &&text, Callback callback, const style::icon &icon) { auto button = parent->add( object_ptr<Info::Profile::Button>( parent, std::move(text), st::managePeerButton)); button->addClickHandler(std::forward<Callback>(callback)); Ui::CreateChild<Info::Profile::FloatingIcon>( button, icon, st::managePeerButtonIconPosition); return button; } template <typename Callback> void AddButtonWithCount( not_null<Ui::VerticalLayout*> parent, rpl::producer<QString> &&text, rpl::producer<QString> &&count, Callback callback, const style::icon &icon) { auto button = AddButton( parent, std::move(text), std::forward<Callback>(callback), icon); auto label = Ui::CreateChild<Ui::FlatLabel>( button, std::move(count), st::managePeerButtonLabel); label->setAttribute(Qt::WA_TransparentForMouseEvents); rpl::combine( button->widthValue(), label->widthValue() ) | rpl::start_with_next([label](int outerWidth, int width) { label->moveToRight( st::managePeerButtonLabelPosition.x(), st::managePeerButtonLabelPosition.y(), outerWidth); }, label->lifetime()); } bool HasRecentActions(not_null<ChannelData*> channel) { return channel->hasAdminRights() || channel->amCreator(); } void ShowRecentActions( not_null<Window::Controller*> controller, not_null<ChannelData*> channel) { controller->showSection(AdminLog::SectionMemento(channel)); } bool HasEditInfoBox(not_null<ChannelData*> channel) { if (channel->canEditInformation()) { return true; } else if (!channel->isPublic() && channel->canAddMembers()) { // Edit invite link. return true; } return false; } void FillManageBox( not_null<Window::Controller*> controller, not_null<ChannelData*> channel, not_null<Ui::VerticalLayout*> content) { using Profile::ParticipantsBoxController; auto isGroup = channel->isMegagroup(); if (HasEditInfoBox(channel)) { AddButton( content, Lang::Viewer(isGroup ? lng_manage_group_info : lng_manage_channel_info), [=] { Ui::show(Box<EditPeerInfoBox>(channel)); }, st::infoIconInformation); } if (HasRecentActions(channel)) { AddButton( content, Lang::Viewer(lng_manage_peer_recent_actions), [=] { ShowRecentActions(controller, channel); }, st::infoIconRecentActions); } if (channel->canViewMembers()) { AddButtonWithCount( content, Lang::Viewer(lng_manage_peer_members), Info::Profile::MembersCountValue(channel) | ToPositiveNumberString(), [=] { ParticipantsBoxController::Start( controller, channel, ParticipantsBoxController::Role::Members); }, st::infoIconMembers); } if (channel->canViewAdmins()) { AddButtonWithCount( content, Lang::Viewer(lng_manage_peer_administrators), Info::Profile::AdminsCountValue(channel) | ToPositiveNumberString(), [=] { ParticipantsBoxController::Start( controller, channel, ParticipantsBoxController::Role::Admins); }, st::infoIconAdministrators); } if (channel->canViewBanned()) { if (channel->isMegagroup()) { AddButtonWithCount( content, Lang::Viewer(lng_manage_peer_restricted_users), Info::Profile::RestrictedCountValue(channel) | ToPositiveNumberString(), [=] { ParticipantsBoxController::Start( controller, channel, ParticipantsBoxController::Role::Restricted); }, st::infoIconRestrictedUsers); } AddButtonWithCount( content, Lang::Viewer(lng_manage_peer_banned_users), Info::Profile::KickedCountValue(channel) | ToPositiveNumberString(), [=] { ParticipantsBoxController::Start( controller, channel, ParticipantsBoxController::Role::Kicked); }, st::infoIconBlacklist); } } } // namespace ManagePeerBox::ManagePeerBox( QWidget*, not_null<ChannelData*> channel) : _channel(channel) { } bool ManagePeerBox::Available(not_null<ChannelData*> channel) { // canViewMembers() is removed, because in supergroups you // see them in profile and in channels only admins can see them. // canViewAdmins() is removed, because in supergroups it is // always true and in channels it is equal to canViewBanned(). return false // || channel->canViewMembers() // || channel->canViewAdmins() || channel->canViewBanned() || channel->canEditInformation() || HasRecentActions(channel); } void ManagePeerBox::prepare() { _channel->updateFull(); setTitle(ManagePeerTitle(_channel)); addButton(langFactory(lng_cancel), [this] { closeBox(); }); setupContent(); } void ManagePeerBox::setupContent() { auto content = Ui::CreateChild<Ui::VerticalLayout>(this); FillManageBox(controller(), _channel, content); widthValue( ) | rpl::start_with_next([=](int width) { content->resizeToWidth(width); }, content->lifetime()); content->heightValue( ) | rpl::start_with_next([=](int height) { setDimensions(st::boxWidth, height); }, content->lifetime()); } �������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/peers/manage_peer_box.h����������������������������������0000664�0000000�0000000�00000001061�13262451251�0025143�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "boxes/abstract_box.h" class ManagePeerBox : public BoxContent { public: ManagePeerBox(QWidget*, not_null<ChannelData*> channel); static bool Available(not_null<ChannelData*> channel); protected: void prepare() override; private: void setupContent(); not_null<ChannelData*> _channel; }; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/photo_crop_box.cpp���������������������������������������0000664�0000000�0000000�00000021232�13262451251�0024273�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/photo_crop_box.h" #include "lang/lang_keys.h" #include "ui/widgets/buttons.h" #include "styles/style_boxes.h" PhotoCropBox::PhotoCropBox(QWidget*, const QImage &img, const PeerId &peer) : _img(img) , _peerId(peer) { init(img, nullptr); } PhotoCropBox::PhotoCropBox(QWidget*, const QImage &img, not_null<PeerData*> peer) : _img(img) , _peerId(peer->id) { init(img, peer); } void PhotoCropBox::init(const QImage &img, PeerData *peer) { if (peerIsChat(_peerId) || (peer && peer->isMegagroup())) { _title = lang(lng_create_group_crop); } else if (peerIsChannel(_peerId)) { _title = lang(lng_create_channel_crop); } else { _title = lang(lng_settings_crop_profile); } } void PhotoCropBox::prepare() { addButton(langFactory(lng_settings_save), [this] { sendPhoto(); }); addButton(langFactory(lng_cancel), [this] { closeBox(); }); int32 s = st::boxWideWidth - st::boxPhotoPadding.left() - st::boxPhotoPadding.right(); _thumb = App::pixmapFromImageInPlace(_img.scaled(s * cIntRetinaFactor(), s * cIntRetinaFactor(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); _thumb.setDevicePixelRatio(cRetinaFactor()); _mask = QImage(_thumb.size(), QImage::Format_ARGB32_Premultiplied); _mask.setDevicePixelRatio(cRetinaFactor()); _fade = QImage(_thumb.size(), QImage::Format_ARGB32_Premultiplied); _fade.setDevicePixelRatio(cRetinaFactor()); _thumbw = _thumb.width() / cIntRetinaFactor(); _thumbh = _thumb.height() / cIntRetinaFactor(); if (_thumbw > _thumbh) { _cropw = _thumbh - 20; } else { _cropw = _thumbw - 20; } _cropx = (_thumbw - _cropw) / 2; _cropy = (_thumbh - _cropw) / 2; _thumbx = (st::boxWideWidth - _thumbw) / 2; _thumby = st::boxPhotoPadding.top(); setMouseTracking(true); setDimensions(st::boxWideWidth, st::boxPhotoPadding.top() + _thumbh + st::boxPhotoPadding.bottom() + st::boxTextFont->height + st::cropSkip); } void PhotoCropBox::mousePressEvent(QMouseEvent *e) { if (e->button() == Qt::LeftButton) { _downState = mouseState(e->pos()); _fromposx = e->pos().x(); _fromposy = e->pos().y(); _fromcropx = _cropx; _fromcropy = _cropy; _fromcropw = _cropw; } return BoxContent::mousePressEvent(e); } int PhotoCropBox::mouseState(QPoint p) { p -= QPoint(_thumbx, _thumby); int32 delta = st::cropPointSize, mdelta(-delta / 2); if (QRect(_cropx + mdelta, _cropy + mdelta, delta, delta).contains(p)) { return 1; } else if (QRect(_cropx + _cropw + mdelta, _cropy + mdelta, delta, delta).contains(p)) { return 2; } else if (QRect(_cropx + _cropw + mdelta, _cropy + _cropw + mdelta, delta, delta).contains(p)) { return 3; } else if (QRect(_cropx + mdelta, _cropy + _cropw + mdelta, delta, delta).contains(p)) { return 4; } else if (QRect(_cropx, _cropy, _cropw, _cropw).contains(p)) { return 5; } return 0; } rpl::producer<QImage> PhotoCropBox::ready() const { return _readyImages.events(); } void PhotoCropBox::mouseReleaseEvent(QMouseEvent *e) { if (_downState) { _downState = 0; mouseMoveEvent(e); } } void PhotoCropBox::mouseMoveEvent(QMouseEvent *e) { if (_downState && !(e->buttons() & Qt::LeftButton)) { mouseReleaseEvent(e); } if (_downState) { if (_downState == 1) { int32 dx = e->pos().x() - _fromposx, dy = e->pos().y() - _fromposy, d = (dx < dy) ? dx : dy; if (_fromcropx + d < 0) { d = -_fromcropx; } if (_fromcropy + d < 0) { d = -_fromcropy; } if (_fromcropw - d < st::cropMinSize) { d = _fromcropw - st::cropMinSize; } if (_cropx != _fromcropx + d || _cropy != _fromcropy + d || _cropw != _fromcropw - d) { _cropx = _fromcropx + d; _cropy = _fromcropy + d; _cropw = _fromcropw - d; update(); } } else if (_downState == 2) { int32 dx = _fromposx - e->pos().x(), dy = e->pos().y() - _fromposy, d = (dx < dy) ? dx : dy; if (_fromcropx + _fromcropw - d > _thumbw) { d = _fromcropx + _fromcropw - _thumbw; } if (_fromcropy + d < 0) { d = -_fromcropy; } if (_fromcropw - d < st::cropMinSize) { d = _fromcropw - st::cropMinSize; } if (_cropy != _fromcropy + d || _cropw != _fromcropw - d) { _cropy = _fromcropy + d; _cropw = _fromcropw - d; update(); } } else if (_downState == 3) { int32 dx = _fromposx - e->pos().x(), dy = _fromposy - e->pos().y(), d = (dx < dy) ? dx : dy; if (_fromcropx + _fromcropw - d > _thumbw) { d = _fromcropx + _fromcropw - _thumbw; } if (_fromcropy + _fromcropw - d > _thumbh) { d = _fromcropy + _fromcropw - _thumbh; } if (_fromcropw - d < st::cropMinSize) { d = _fromcropw - st::cropMinSize; } if (_cropw != _fromcropw - d) { _cropw = _fromcropw - d; update(); } } else if (_downState == 4) { int32 dx = e->pos().x() - _fromposx, dy = _fromposy - e->pos().y(), d = (dx < dy) ? dx : dy; if (_fromcropx + d < 0) { d = -_fromcropx; } if (_fromcropy + _fromcropw - d > _thumbh) { d = _fromcropy + _fromcropw - _thumbh; } if (_fromcropw - d < st::cropMinSize) { d = _fromcropw - st::cropMinSize; } if (_cropx != _fromcropx + d || _cropw != _fromcropw - d) { _cropx = _fromcropx + d; _cropw = _fromcropw - d; update(); } } else if (_downState == 5) { int32 dx = e->pos().x() - _fromposx, dy = e->pos().y() - _fromposy; if (_fromcropx + dx < 0) { dx = -_fromcropx; } else if (_fromcropx + _fromcropw + dx > _thumbw) { dx = _thumbw - _fromcropx - _fromcropw; } if (_fromcropy + dy < 0) { dy = -_fromcropy; } else if (_fromcropy + _fromcropw + dy > _thumbh) { dy = _thumbh - _fromcropy - _fromcropw; } if (_cropx != _fromcropx + dx || _cropy != _fromcropy + dy) { _cropx = _fromcropx + dx; _cropy = _fromcropy + dy; update(); } } } int32 cursorState = _downState ? _downState : mouseState(e->pos()); QCursor cur(style::cur_default); if (cursorState == 1 || cursorState == 3) { cur = style::cur_sizefdiag; } else if (cursorState == 2 || cursorState == 4) { cur = style::cur_sizebdiag; } else if (cursorState == 5) { cur = style::cur_sizeall; } setCursor(cur); } void PhotoCropBox::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) { sendPhoto(); } else { BoxContent::keyPressEvent(e); } } void PhotoCropBox::paintEvent(QPaintEvent *e) { BoxContent::paintEvent(e); Painter p(this); p.setFont(st::boxTextFont); p.setPen(st::boxPhotoTextFg); p.drawText(QRect(st::boxPhotoPadding.left(), st::boxPhotoPadding.top() + _thumbh + st::boxPhotoPadding.bottom(), width() - st::boxPhotoPadding.left() - st::boxPhotoPadding.right(), st::boxTextFont->height), _title, style::al_top); p.translate(_thumbx, _thumby); p.drawPixmap(0, 0, _thumb); _mask.fill(Qt::white); { Painter p(&_mask); PainterHighQualityEnabler hq(p); p.setPen(Qt::NoPen); p.setBrush(Qt::black); p.drawEllipse(_cropx, _cropy, _cropw, _cropw); } style::colorizeImage(_mask, st::photoCropFadeBg->c, &_fade); p.drawImage(0, 0, _fade); int delta = st::cropPointSize; int mdelta = -delta / 2; p.fillRect(QRect(_cropx + mdelta, _cropy + mdelta, delta, delta), st::photoCropPointFg); p.fillRect(QRect(_cropx + _cropw + mdelta, _cropy + mdelta, delta, delta), st::photoCropPointFg); p.fillRect(QRect(_cropx + _cropw + mdelta, _cropy + _cropw + mdelta, delta, delta), st::photoCropPointFg); p.fillRect(QRect(_cropx + mdelta, _cropy + _cropw + mdelta, delta, delta), st::photoCropPointFg); } void PhotoCropBox::sendPhoto() { auto from = _img; if (_img.width() < _thumb.width()) { from = _thumb.toImage(); } float64 x = float64(_cropx) / _thumbw, y = float64(_cropy) / _thumbh, w = float64(_cropw) / _thumbw; int32 ix = int32(x * from.width()), iy = int32(y * from.height()), iw = int32(w * from.width()); if (ix < 0) { ix = 0; } if (ix + iw > from.width()) { iw = from.width() - ix; } if (iy < 0) { iy = 0; } if (iy + iw > from.height()) { iw = from.height() - iy; } int32 offset = ix * from.depth() / 8 + iy * from.bytesPerLine(); QImage cropped(from.constBits() + offset, iw, iw, from.bytesPerLine(), from.format()), tosend; if (from.format() == QImage::Format_Indexed8) { cropped.setColorCount(from.colorCount()); cropped.setColorTable(from.colorTable()); } if (cropped.width() > 1280) { tosend = cropped.scaled(1280, 1280, Qt::KeepAspectRatio, Qt::SmoothTransformation); } else if (cropped.width() < 320) { tosend = cropped.scaled(320, 320, Qt::KeepAspectRatio, Qt::SmoothTransformation); } else { tosend = cropped.copy(); } auto weak = make_weak(this); _readyImages.fire(std::move(tosend)); if (weak) { closeBox(); } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/photo_crop_box.h�����������������������������������������0000664�0000000�0000000�00000002241�13262451251�0023737�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "boxes/abstract_box.h" class PhotoCropBox : public BoxContent { public: PhotoCropBox(QWidget*, const QImage &img, const PeerId &peer); PhotoCropBox(QWidget*, const QImage &img, not_null<PeerData*> peer); int32 mouseState(QPoint p); rpl::producer<QImage> ready() const; protected: void prepare() override; void keyPressEvent(QKeyEvent *e) override; void paintEvent(QPaintEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; private: void init(const QImage &img, PeerData *peer); void sendPhoto(); QString _title; int32 _downState = 0; int32 _thumbx, _thumby, _thumbw, _thumbh; int32 _cropx, _cropy, _cropw; int32 _fromposx, _fromposy, _fromcropx, _fromcropy, _fromcropw; QImage _img; QPixmap _thumb; QImage _mask, _fade; PeerId _peerId = 0; rpl::event_stream<QImage> _readyImages; }; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/rate_call_box.cpp����������������������������������������0000664�0000000�0000000�00000007274�13262451251�0024057�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/rate_call_box.h" #include "lang/lang_keys.h" #include "styles/style_boxes.h" #include "styles/style_calls.h" #include "boxes/confirm_box.h" #include "ui/widgets/labels.h" #include "ui/widgets/buttons.h" #include "ui/widgets/input_fields.h" #include "mainwindow.h" #include "mainwidget.h" namespace { constexpr auto kMaxRating = 5; } // namespace RateCallBox::RateCallBox(QWidget*, uint64 callId, uint64 callAccessHash) : _callId(callId) , _callAccessHash(callAccessHash) { } void RateCallBox::prepare() { setTitle(langFactory(lng_call_rate_label)); addButton(langFactory(lng_cancel), [this] { closeBox(); }); for (auto i = 0; i < kMaxRating; ++i) { _stars.push_back(object_ptr<Ui::IconButton>(this, st::callRatingStar)); _stars.back()->setClickedCallback([this, value = i + 1] { ratingChanged(value); }); _stars.back()->show(); } updateMaxHeight(); } void RateCallBox::resizeEvent(QResizeEvent *e) { BoxContent::resizeEvent(e); auto starsWidth = (_stars.size() * st::callRatingStar.width); auto starLeft = (width() - starsWidth) / 2; auto starTop = st::callRatingStarTop; for (auto &star : _stars) { star->moveToLeft(starLeft, starTop); starLeft += star->width(); } if (_comment) { _comment->moveToLeft(st::callRatingPadding.left(), _stars.back()->bottomNoMargins() + st::callRatingCommentTop); } } void RateCallBox::ratingChanged(int value) { Expects(value > 0 && value <= kMaxRating); if (!_rating) { clearButtons(); addButton(langFactory(lng_send_button), [this] { onSend(); }); addButton(langFactory(lng_cancel), [this] { closeBox(); }); } _rating = value; for (auto i = 0; i < kMaxRating; ++i) { _stars[i]->setIconOverride((i < value) ? &st::callRatingStarFilled : nullptr); _stars[i]->setRippleColorOverride((i < value) ? &st::lightButtonBgOver : nullptr); } if (value < kMaxRating) { if (!_comment) { _comment.create(this, st::callRatingComment, langFactory(lng_call_rate_comment)); _comment->show(); _comment->setCtrlEnterSubmit(Ui::CtrlEnterSubmit::Both); _comment->setMaxLength(MaxPhotoCaption); _comment->resize(width() - (st::callRatingPadding.left() + st::callRatingPadding.right()), _comment->height()); updateMaxHeight(); connect(_comment, SIGNAL(resized()), this, SLOT(onCommentResized())); connect(_comment, SIGNAL(submitted(bool)), this, SLOT(onSend())); connect(_comment, SIGNAL(cancelled()), this, SLOT(onClose())); } _comment->setFocusFast(); } else if (_comment) { _comment.destroy(); updateMaxHeight(); } } void RateCallBox::setInnerFocus() { if (_comment) { _comment->setFocusFast(); } else { setFocus(); } } void RateCallBox::onCommentResized() { updateMaxHeight(); update(); } void RateCallBox::onSend() { Expects(_rating > 0 && _rating <= kMaxRating); if (_requestId) { return; } auto comment = _comment ? _comment->getLastText().trimmed() : QString(); _requestId = request(MTPphone_SetCallRating(MTP_inputPhoneCall(MTP_long(_callId), MTP_long(_callAccessHash)), MTP_int(_rating), MTP_string(comment))).done([this](const MTPUpdates &updates) { App::main()->sentUpdatesReceived(updates); closeBox(); }).fail([this](const RPCError &error) { closeBox(); }).send(); } void RateCallBox::updateMaxHeight() { auto newHeight = st::callRatingPadding.top() + st::callRatingStarTop + _stars.back()->heightNoMargins() + st::callRatingPadding.bottom(); if (_comment) { newHeight += st::callRatingCommentTop + _comment->height(); } setDimensions(st::boxWideWidth, newHeight); } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/rate_call_box.h������������������������������������������0000664�0000000�0000000�00000002000�13262451251�0023502�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "boxes/abstract_box.h" #include "mtproto/sender.h" namespace Ui { class InputArea; class FlatLabel; class IconButton; } // namespace Ui class RateCallBox : public BoxContent, private MTP::Sender { Q_OBJECT public: RateCallBox(QWidget*, uint64 callId, uint64 callAccessHash); private slots: void onSend(); void onCommentResized(); void onClose() { closeBox(); } protected: void prepare() override; void setInnerFocus() override; void resizeEvent(QResizeEvent *e) override; private: void updateMaxHeight(); void ratingChanged(int value); uint64 _callId = 0; uint64 _callAccessHash = 0; int _rating = 0; std::vector<object_ptr<Ui::IconButton>> _stars; object_ptr<Ui::InputArea> _comment = { nullptr }; mtpRequestId _requestId = 0; }; tdesktop-1.2.17/Telegram/SourceFiles/boxes/report_box.cpp�������������������������������������������0000664�0000000�0000000�00000011630�13262451251�0023433�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/report_box.h" #include "lang/lang_keys.h" #include "styles/style_boxes.h" #include "styles/style_profile.h" #include "boxes/confirm_box.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/buttons.h" #include "ui/widgets/input_fields.h" #include "mainwindow.h" ReportBox::ReportBox(QWidget*, PeerData *peer) : _peer(peer) , _reasonGroup(std::make_shared<Ui::RadioenumGroup<Reason>>(Reason::Spam)) , _reasonSpam(this, _reasonGroup, Reason::Spam, lang(lng_report_reason_spam), st::defaultBoxCheckbox) , _reasonViolence(this, _reasonGroup, Reason::Violence, lang(lng_report_reason_violence), st::defaultBoxCheckbox) , _reasonPornography(this, _reasonGroup, Reason::Pornography, lang(lng_report_reason_pornography), st::defaultBoxCheckbox) , _reasonOther(this, _reasonGroup, Reason::Other, lang(lng_report_reason_other), st::defaultBoxCheckbox) { } void ReportBox::prepare() { setTitle(langFactory(_peer->isUser() ? lng_report_bot_title : (_peer->isMegagroup() ? lng_report_group_title : lng_report_title))); addButton(langFactory(lng_report_button), [this] { onReport(); }); addButton(langFactory(lng_cancel), [this] { closeBox(); }); _reasonGroup->setChangedCallback([this](Reason value) { reasonChanged(value); }); updateMaxHeight(); } void ReportBox::resizeEvent(QResizeEvent *e) { BoxContent::resizeEvent(e); _reasonSpam->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), st::boxOptionListPadding.top() + _reasonSpam->getMargins().top()); _reasonViolence->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _reasonSpam->bottomNoMargins() + st::boxOptionListSkip); _reasonPornography->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _reasonViolence->bottomNoMargins() + st::boxOptionListSkip); _reasonOther->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _reasonPornography->bottomNoMargins() + st::boxOptionListSkip); if (_reasonOtherText) { _reasonOtherText->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left() - st::defaultInputField.textMargins.left(), _reasonOther->bottomNoMargins() + st::newGroupDescriptionPadding.top()); } } void ReportBox::reasonChanged(Reason reason) { if (reason == Reason::Other) { if (!_reasonOtherText) { _reasonOtherText.create(this, st::profileReportReasonOther, langFactory(lng_report_reason_description)); _reasonOtherText->show(); _reasonOtherText->setCtrlEnterSubmit(Ui::CtrlEnterSubmit::Both); _reasonOtherText->setMaxLength(MaxPhotoCaption); _reasonOtherText->resize(width() - (st::boxPadding.left() + st::boxOptionListPadding.left() + st::boxPadding.right()), _reasonOtherText->height()); updateMaxHeight(); connect(_reasonOtherText, SIGNAL(resized()), this, SLOT(onReasonResized())); connect(_reasonOtherText, SIGNAL(submitted(bool)), this, SLOT(onReport())); connect(_reasonOtherText, SIGNAL(cancelled()), this, SLOT(onClose())); } _reasonOtherText->setFocusFast(); } else if (_reasonOtherText) { _reasonOtherText.destroy(); updateMaxHeight(); } } void ReportBox::setInnerFocus() { if (_reasonOtherText) { _reasonOtherText->setFocusFast(); } else { setFocus(); } } void ReportBox::onReasonResized() { updateMaxHeight(); update(); } void ReportBox::onReport() { if (_requestId) return; if (_reasonOtherText && _reasonOtherText->getLastText().trimmed().isEmpty()) { _reasonOtherText->showError(); return; } auto getReason = [this]() { switch (_reasonGroup->value()) { case Reason::Spam: return MTP_inputReportReasonSpam(); case Reason::Violence: return MTP_inputReportReasonViolence(); case Reason::Pornography: return MTP_inputReportReasonPornography(); case Reason::Other: return MTP_inputReportReasonOther(MTP_string(_reasonOtherText->getLastText())); } Unexpected("Bad reason group value."); }; _requestId = MTP::send(MTPaccount_ReportPeer(_peer->input, getReason()), rpcDone(&ReportBox::reportDone), rpcFail(&ReportBox::reportFail)); } void ReportBox::reportDone(const MTPBool &result) { _requestId = 0; Ui::show(Box<InformBox>(lang(lng_report_thanks))); } bool ReportBox::reportFail(const RPCError &error) { if (MTP::isDefaultHandledError(error)) return false; _requestId = 0; if (_reasonOtherText) { _reasonOtherText->showError(); } return true; } void ReportBox::updateMaxHeight() { auto newHeight = st::boxOptionListPadding.top() + _reasonSpam->getMargins().top() + 4 * _reasonSpam->heightNoMargins() + 3 * st::boxOptionListSkip + _reasonSpam->getMargins().bottom() + st::boxOptionListPadding.bottom(); if (_reasonOtherText) { newHeight += st::newGroupDescriptionPadding.top() + _reasonOtherText->height() + st::newGroupDescriptionPadding.bottom(); } setDimensions(st::boxWidth, newHeight); } ��������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/report_box.h���������������������������������������������0000664�0000000�0000000�00000002506�13262451251�0023102�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "boxes/abstract_box.h" namespace Ui { template <typename Enum> class RadioenumGroup; template <typename Enum> class Radioenum; class InputArea; } // namespace Ui class ReportBox : public BoxContent, public RPCSender { Q_OBJECT public: ReportBox(QWidget*, PeerData *peer); private slots: void onReport(); void onReasonResized(); void onClose() { closeBox(); } protected: void prepare() override; void setInnerFocus() override; void resizeEvent(QResizeEvent *e) override; private: enum class Reason { Spam, Violence, Pornography, Other, }; void reasonChanged(Reason reason); void updateMaxHeight(); void reportDone(const MTPBool &result); bool reportFail(const RPCError &error); PeerData *_peer; std::shared_ptr<Ui::RadioenumGroup<Reason>> _reasonGroup; object_ptr<Ui::Radioenum<Reason>> _reasonSpam; object_ptr<Ui::Radioenum<Reason>> _reasonViolence; object_ptr<Ui::Radioenum<Reason>> _reasonPornography; object_ptr<Ui::Radioenum<Reason>> _reasonOther; object_ptr<Ui::InputArea> _reasonOtherText = { nullptr }; mtpRequestId _requestId = 0; }; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/self_destruction_box.cpp���������������������������������0000664�0000000�0000000�00000005233�13262451251�0025476�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/self_destruction_box.h" #include "lang/lang_keys.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/labels.h" #include "styles/style_boxes.h" void SelfDestructionBox::prepare() { setTitle(langFactory(lng_self_destruct_title)); _ttlValues = { 30, 90, 180, 365 }; auto fake = object_ptr<Ui::FlatLabel>(this, lang(lng_self_destruct_description), Ui::FlatLabel::InitType::Simple, st::boxLabel); auto boxHeight = st::boxOptionListPadding.top() + fake->height() + st::boxMediumSkip + _ttlValues.size() * (st::defaultRadio.diameter + st::boxOptionListSkip) - st::boxOptionListSkip + st::boxOptionListPadding.bottom() + st::boxPadding.bottom(); fake.destroy(); setDimensions(st::boxWidth, boxHeight); auto loading = object_ptr<Ui::FlatLabel>(this, lang(lng_contacts_loading), Ui::FlatLabel::InitType::Simple, st::membersAbout); loading->moveToLeft((st::boxWidth - loading->width()) / 2, boxHeight / 3); addButton(langFactory(lng_cancel), [this] { closeBox(); }); request(MTPaccount_GetAccountTTL()).done([this, loading = std::move(loading)](const MTPAccountDaysTTL &result) mutable { Expects(result.type() == mtpc_accountDaysTTL); Expects(!_ttlValues.empty()); loading.destroy(); auto y = st::boxOptionListPadding.top(); _description.create(this, lang(lng_self_destruct_description), Ui::FlatLabel::InitType::Simple, st::boxLabel); _description->moveToLeft(st::boxPadding.left(), y); y += _description->height() + st::boxMediumSkip; auto current = result.c_accountDaysTTL().vdays.v; auto currentAdjusted = _ttlValues[0]; for (auto days : _ttlValues) { if (qAbs(current - days) < qAbs(current - currentAdjusted)) { currentAdjusted = days; } } auto group = std::make_shared<Ui::RadiobuttonGroup>(currentAdjusted); auto count = int(_ttlValues.size()); _options.reserve(count); for (auto days : _ttlValues) { _options.emplace_back(this, group, days, (days > 364) ? lng_self_destruct_years(lt_count, days / 365) : lng_self_destruct_months(lt_count, qMax(days / 30, 1)), st::langsButton); _options.back()->moveToLeft(st::boxPadding.left(), y); y += _options.back()->heightNoMargins() + st::boxOptionListSkip; } showChildren(); clearButtons(); addButton(langFactory(lng_settings_save), [this, group] { MTP::send(MTPaccount_SetAccountTTL(MTP_accountDaysTTL(MTP_int(group->value())))); closeBox(); }); addButton(langFactory(lng_cancel), [this] { closeBox(); }); }).send(); } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/self_destruction_box.h�����������������������������������0000664�0000000�0000000�00000001402�13262451251�0025135�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "boxes/abstract_box.h" #include "mtproto/sender.h" namespace Ui { class RadiobuttonGroup; class Radiobutton; class FlatLabel; } // namespace Ui class SelfDestructionBox : public BoxContent, private MTP::Sender { Q_OBJECT public: SelfDestructionBox(QWidget*) { } protected: void prepare() override; private: std::vector<int> _ttlValues; object_ptr<Ui::FlatLabel> _description = { nullptr }; std::shared_ptr<Ui::RadiobuttonGroup> _ttlGroup; std::vector<object_ptr<Ui::Radiobutton>> _options; }; ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/send_files_box.cpp���������������������������������������0000664�0000000�0000000�00000145174�13262451251�0024246�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/send_files_box.h" #include "lang/lang_keys.h" #include "storage/localstorage.h" #include "storage/storage_media_prepare.h" #include "mainwidget.h" #include "history/history_media_types.h" #include "core/file_utilities.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/buttons.h" #include "ui/widgets/input_fields.h" #include "ui/widgets/scroll_area.h" #include "ui/wrap/fade_wrap.h" #include "ui/grouped_layout.h" #include "ui/text_options.h" #include "media/media_clip_reader.h" #include "window/window_controller.h" #include "styles/style_history.h" #include "styles/style_boxes.h" #include "layout.h" namespace { constexpr auto kMinPreviewWidth = 20; constexpr auto kShrinkDuration = TimeMs(150); constexpr auto kDragDuration = TimeMs(200); class SingleMediaPreview : public Ui::RpWidget { public: static SingleMediaPreview *Create( QWidget *parent, not_null<Window::Controller*> controller, const Storage::PreparedFile &file); SingleMediaPreview( QWidget *parent, not_null<Window::Controller*> controller, QImage preview, bool animated, const QString &animatedPreviewPath); bool canSendAsPhoto() const { return _canSendAsPhoto; } rpl::producer<int> desiredHeightValue() const override; protected: void paintEvent(QPaintEvent *e) override; private: void preparePreview( QImage preview, const QString &animatedPreviewPath); void prepareAnimatedPreview(const QString &animatedPreviewPath); void clipCallback(Media::Clip::Notification notification); not_null<Window::Controller*> _controller; bool _animated = false; bool _canSendAsPhoto = false; QPixmap _preview; int _previewLeft = 0; int _previewWidth = 0; int _previewHeight = 0; Media::Clip::ReaderPointer _gifPreview; }; class SingleFilePreview : public Ui::RpWidget { public: SingleFilePreview( QWidget *parent, const Storage::PreparedFile &file); rpl::producer<int> desiredHeightValue() const override; protected: void paintEvent(QPaintEvent *e) override; private: void preparePreview(const Storage::PreparedFile &file); void prepareThumb(const QImage &preview); QPixmap _fileThumb; Text _nameText; bool _fileIsAudio = false; bool _fileIsImage = false; QString _statusText; int _statusWidth = 0; }; class AlbumThumb { public: AlbumThumb( const Storage::PreparedFile &file, const Ui::GroupMediaLayout &layout); void moveToLayout(const Ui::GroupMediaLayout &layout); void animateLayoutToInitial(); void resetLayoutAnimation(); int photoHeight() const; void paintInAlbum( Painter &p, int left, int top, float64 shrinkProgress, float64 moveProgress, TimeMs ms); void paintPhoto(Painter &p, int left, int top, int outerWidth); void paintFile(Painter &p, int left, int top, int outerWidth); bool containsPoint(QPoint position) const; int distanceTo(QPoint position) const; bool isPointAfter(QPoint position) const; void moveInAlbum(QPoint to); QPoint center() const; void suggestMove(float64 delta, base::lambda<void()> callback); void finishAnimations(); private: QRect countRealGeometry() const; QRect countCurrentGeometry(float64 progress) const; void prepareCache(QSize size, int shrink); void drawSimpleFrame(Painter &p, QRect to, QSize size) const; Ui::GroupMediaLayout _layout; base::optional<QRect> _animateFromGeometry; const QImage _fullPreview; const int _shrinkSize = 0; QPixmap _albumImage; QImage _albumCache; QPoint _albumPosition; RectParts _albumCorners = RectPart::None; QPixmap _photo; QPixmap _fileThumb; QString _name; QString _status; int _nameWidth = 0; int _statusWidth = 0; bool _isVideo = false; float64 _suggestedMove = 0.; Animation _suggestedMoveAnimation; int _lastShrinkValue = 0; }; AlbumThumb::AlbumThumb( const Storage::PreparedFile &file, const Ui::GroupMediaLayout &layout) : _layout(layout) , _fullPreview(file.preview) , _shrinkSize(int(std::ceil(st::historyMessageRadius / 1.4))) , _isVideo(file.type == Storage::PreparedFile::AlbumType::Video) { Expects(!_fullPreview.isNull()); moveToLayout(layout); using Option = Images::Option; const auto previewWidth = _fullPreview.width(); const auto previewHeight = _fullPreview.height(); const auto imageWidth = std::max( previewWidth / cIntRetinaFactor(), st::minPhotoSize); const auto imageHeight = std::max( previewHeight / cIntRetinaFactor(), st::minPhotoSize); _photo = App::pixmapFromImageInPlace(Images::prepare( _fullPreview, previewWidth, previewHeight, Option::RoundedLarge | Option::RoundedAll, imageWidth, imageHeight)); const auto idealSize = st::sendMediaFileThumbSize * cIntRetinaFactor(); const auto fileThumbSize = (previewWidth > previewHeight) ? QSize(previewWidth * idealSize / previewHeight, idealSize) : QSize(idealSize, previewHeight * idealSize / previewWidth); _fileThumb = App::pixmapFromImageInPlace(Images::prepare( _fullPreview, fileThumbSize.width(), fileThumbSize.height(), Option::RoundedSmall | Option::RoundedAll, st::sendMediaFileThumbSize, st::sendMediaFileThumbSize )); const auto availableFileWidth = st::sendMediaPreviewSize - st::sendMediaFileThumbSkip - st::sendMediaFileThumbSize; const auto filepath = file.path; if (filepath.isEmpty()) { _name = filedialogDefaultName( qsl("image"), qsl(".png"), QString(), true); _status = qsl("%1x%2").arg( _fullPreview.width() ).arg( _fullPreview.height() ); } else { auto fileinfo = QFileInfo(filepath); _name = fileinfo.fileName(); _status = formatSizeText(fileinfo.size()); } _nameWidth = st::semiboldFont->width(_name); if (_nameWidth > availableFileWidth) { _name = st::semiboldFont->elided( _name, Qt::ElideMiddle); _nameWidth = st::semiboldFont->width(_name); } _statusWidth = st::normalFont->width(_status); } void AlbumThumb::resetLayoutAnimation() { _animateFromGeometry = base::none; } void AlbumThumb::animateLayoutToInitial() { _animateFromGeometry = countRealGeometry(); _suggestedMove = 0.; _albumPosition = QPoint(0, 0); } void AlbumThumb::moveToLayout(const Ui::GroupMediaLayout &layout) { animateLayoutToInitial(); _layout = layout; const auto width = _layout.geometry.width(); const auto height = _layout.geometry.height(); _albumCorners = Ui::GetCornersFromSides(_layout.sides); using Option = Images::Option; const auto options = Option::Smooth | Option::RoundedLarge | ((_albumCorners & RectPart::TopLeft) ? Option::RoundedTopLeft : Option::None) | ((_albumCorners & RectPart::TopRight) ? Option::RoundedTopRight : Option::None) | ((_albumCorners & RectPart::BottomLeft) ? Option::RoundedBottomLeft : Option::None) | ((_albumCorners & RectPart::BottomRight) ? Option::RoundedBottomRight : Option::None); const auto pixSize = Ui::GetImageScaleSizeForGeometry( { _fullPreview.width(), _fullPreview.height() }, { width, height }); const auto pixWidth = pixSize.width() * cIntRetinaFactor(); const auto pixHeight = pixSize.height() * cIntRetinaFactor(); _albumImage = App::pixmapFromImageInPlace(Images::prepare( _fullPreview, pixWidth, pixHeight, options, width, height)); } int AlbumThumb::photoHeight() const { return _photo.height() / cIntRetinaFactor(); } void AlbumThumb::paintInAlbum( Painter &p, int left, int top, float64 shrinkProgress, float64 moveProgress, TimeMs ms) { const auto shrink = anim::interpolate(0, _shrinkSize, shrinkProgress); _suggestedMoveAnimation.step(ms); _lastShrinkValue = shrink; const auto geometry = countCurrentGeometry(moveProgress); const auto x = left + geometry.x(); const auto y = top + geometry.y(); if (shrink > 0 || moveProgress < 1.) { const auto size = geometry.size(); if (shrinkProgress < 1 && _albumCorners != RectPart::None) { prepareCache(size, shrink); p.drawImage(x, y, _albumCache); } else { const auto to = QRect({ x, y }, size).marginsRemoved( { shrink, shrink, shrink, shrink } ); drawSimpleFrame(p, to, size); } } else { p.drawPixmap(x, y, _albumImage); } if (_isVideo) { const auto inner = QRect( x + (geometry.width() - st::msgFileSize) / 2, y + (geometry.height() - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize); { PainterHighQualityEnabler hq(p); p.setPen(Qt::NoPen); p.setBrush(st::msgDateImgBg); p.drawEllipse(inner); } st::historyFileThumbPlay.paintInCenter(p, inner); } } void AlbumThumb::prepareCache(QSize size, int shrink) { const auto width = std::max( _layout.geometry.width(), _animateFromGeometry ? _animateFromGeometry->width() : 0); const auto height = std::max( _layout.geometry.height(), _animateFromGeometry ? _animateFromGeometry->height() : 0); const auto cacheSize = QSize(width, height) * cIntRetinaFactor(); if (_albumCache.width() < cacheSize.width() || _albumCache.height() < cacheSize.height()) { _albumCache = QImage(cacheSize, QImage::Format_ARGB32_Premultiplied); } _albumCache.fill(Qt::transparent); { Painter p(&_albumCache); const auto to = QRect(QPoint(), size).marginsRemoved( { shrink, shrink, shrink, shrink } ); drawSimpleFrame(p, to, size); } Images::prepareRound( _albumCache, ImageRoundRadius::Large, _albumCorners, QRect(QPoint(), size * cIntRetinaFactor())); } void AlbumThumb::drawSimpleFrame(Painter &p, QRect to, QSize size) const { const auto fullWidth = _fullPreview.width(); const auto fullHeight = _fullPreview.height(); const auto previewSize = Ui::GetImageScaleSizeForGeometry( { fullWidth, fullHeight }, { size.width(), size.height() }); const auto previewWidth = previewSize.width() * cIntRetinaFactor(); const auto previewHeight = previewSize.height() * cIntRetinaFactor(); const auto width = size.width() * cIntRetinaFactor(); const auto height = size.height() * cIntRetinaFactor(); const auto scaleWidth = to.width() / float64(width); const auto scaleHeight = to.height() / float64(height); const auto Round = [](float64 value) { return int(std::round(value)); }; const auto [from, fillBlack] = [&] { if (previewWidth < width && previewHeight < height) { const auto toWidth = Round(previewWidth * scaleWidth); const auto toHeight = Round(previewHeight * scaleHeight); return std::make_pair( QRect(0, 0, fullWidth, fullHeight), QMargins( (to.width() - toWidth) / 2, (to.height() - toHeight) / 2, to.width() - toWidth - (to.width() - toWidth) / 2, to.height() - toHeight - (to.height() - toHeight) / 2)); } else if (previewWidth * height > previewHeight * width) { if (previewHeight >= height) { const auto takeWidth = previewWidth * height / previewHeight; const auto useWidth = fullWidth * width / takeWidth; return std::make_pair( QRect( (fullWidth - useWidth) / 2, 0, useWidth, fullHeight), QMargins(0, 0, 0, 0)); } else { const auto takeWidth = previewWidth; const auto useWidth = fullWidth * width / takeWidth; const auto toHeight = Round(previewHeight * scaleHeight); const auto toSkip = (to.height() - toHeight) / 2; return std::make_pair( QRect( (fullWidth - useWidth) / 2, 0, useWidth, fullHeight), QMargins( 0, toSkip, 0, to.height() - toHeight - toSkip)); } } else { if (previewWidth >= width) { const auto takeHeight = previewHeight * width / previewWidth; const auto useHeight = fullHeight * height / takeHeight; return std::make_pair( QRect( 0, (fullHeight - useHeight) / 2, fullWidth, useHeight), QMargins(0, 0, 0, 0)); } else { const auto takeHeight = previewHeight; const auto useHeight = fullHeight * height / takeHeight; const auto toWidth = Round(previewWidth * scaleWidth); const auto toSkip = (to.width() - toWidth) / 2; return std::make_pair( QRect( 0, (fullHeight - useHeight) / 2, fullWidth, useHeight), QMargins( toSkip, 0, to.width() - toWidth - toSkip, 0)); } } }(); p.drawImage(to.marginsRemoved(fillBlack), _fullPreview, from); if (fillBlack.top() > 0) { p.fillRect(to.x(), to.y(), to.width(), fillBlack.top(), st::imageBg); } if (fillBlack.bottom() > 0) { p.fillRect( to.x(), to.y() + to.height() - fillBlack.bottom(), to.width(), fillBlack.bottom(), st::imageBg); } if (fillBlack.left() > 0) { p.fillRect( to.x(), to.y() + fillBlack.top(), fillBlack.left(), to.height() - fillBlack.top() - fillBlack.bottom(), st::imageBg); } if (fillBlack.right() > 0) { p.fillRect( to.x() + to.width() - fillBlack.right(), to.y() + fillBlack.top(), fillBlack.right(), to.height() - fillBlack.top() - fillBlack.bottom(), st::imageBg); } } void AlbumThumb::paintPhoto(Painter &p, int left, int top, int outerWidth) { const auto width = _photo.width() / cIntRetinaFactor(); p.drawPixmapLeft( left + (st::sendMediaPreviewSize - width) / 2, top, outerWidth, _photo); } void AlbumThumb::paintFile(Painter &p, int left, int top, int outerWidth) { const auto textLeft = left + st::sendMediaFileThumbSize + st::sendMediaFileThumbSkip; p.drawPixmap(left, top, _fileThumb); p.setFont(st::semiboldFont); p.setPen(st::historyFileNameInFg); p.drawTextLeft( textLeft, top + st::sendMediaFileNameTop, outerWidth, _name, _nameWidth); p.setFont(st::normalFont); p.setPen(st::mediaInFg); p.drawTextLeft( textLeft, top + st::sendMediaFileStatusTop, outerWidth, _status, _statusWidth); } bool AlbumThumb::containsPoint(QPoint position) const { return _layout.geometry.contains(position); } int AlbumThumb::distanceTo(QPoint position) const { const auto delta = (_layout.geometry.center() - position); return QPoint::dotProduct(delta, delta); } bool AlbumThumb::isPointAfter(QPoint position) const { return position.x() > _layout.geometry.center().x(); } void AlbumThumb::moveInAlbum(QPoint to) { _albumPosition = to; } QPoint AlbumThumb::center() const { auto realGeometry = _layout.geometry; realGeometry.moveTopLeft(realGeometry.topLeft() + _albumPosition); return realGeometry.center(); } void AlbumThumb::suggestMove(float64 delta, base::lambda<void()> callback) { if (_suggestedMove != delta) { _suggestedMoveAnimation.start( std::move(callback), _suggestedMove, delta, kShrinkDuration); _suggestedMove = delta; } } QRect AlbumThumb::countRealGeometry() const { const auto addLeft = int(std::round( _suggestedMoveAnimation.current(_suggestedMove) * _lastShrinkValue)); const auto current = _layout.geometry; const auto realTopLeft = current.topLeft() + _albumPosition + QPoint(addLeft, 0); return { realTopLeft, current.size() }; } QRect AlbumThumb::countCurrentGeometry(float64 progress) const { const auto now = countRealGeometry(); if (_animateFromGeometry && progress < 1.) { return { anim::interpolate(_animateFromGeometry->x(), now.x(), progress), anim::interpolate(_animateFromGeometry->y(), now.y(), progress), anim::interpolate(_animateFromGeometry->width(), now.width(), progress), anim::interpolate(_animateFromGeometry->height(), now.height(), progress) }; } return now; } void AlbumThumb::finishAnimations() { _suggestedMoveAnimation.finish(); } SingleMediaPreview *SingleMediaPreview::Create( QWidget *parent, not_null<Window::Controller*> controller, const Storage::PreparedFile &file) { auto preview = QImage(); bool animated = false; bool animationPreview = false; if (const auto image = base::get_if<FileMediaInformation::Image>( &file.information->media)) { preview = image->data; animated = animationPreview = image->animated; } else if (const auto video = base::get_if<FileMediaInformation::Video>( &file.information->media)) { preview = video->thumbnail; animated = true; animationPreview = video->isGifv; } if (preview.isNull()) { return nullptr; } else if (!animated && !Storage::ValidateThumbDimensions( preview.width(), preview.height())) { return nullptr; } return Ui::CreateChild<SingleMediaPreview>( parent, controller, preview, animated, animationPreview ? file.path : QString()); } SingleMediaPreview::SingleMediaPreview( QWidget *parent, not_null<Window::Controller*> controller, QImage preview, bool animated, const QString &animatedPreviewPath) : RpWidget(parent) , _controller(controller) , _animated(animated) { Expects(!preview.isNull()); _canSendAsPhoto = !_animated && Storage::ValidateThumbDimensions( preview.width(), preview.height()); preparePreview(preview, animatedPreviewPath); } void SingleMediaPreview::preparePreview( QImage preview, const QString &animatedPreviewPath) { auto maxW = 0; auto maxH = 0; if (_animated) { auto limitW = st::sendMediaPreviewSize; auto limitH = st::confirmMaxHeight; maxW = qMax(preview.width(), 1); maxH = qMax(preview.height(), 1); if (maxW * limitH > maxH * limitW) { if (maxW < limitW) { maxH = maxH * limitW / maxW; maxW = limitW; } } else { if (maxH < limitH) { maxW = maxW * limitH / maxH; maxH = limitH; } } preview = Images::prepare( preview, maxW * cIntRetinaFactor(), maxH * cIntRetinaFactor(), Images::Option::Smooth | Images::Option::Blurred, maxW, maxH); } auto originalWidth = preview.width(); auto originalHeight = preview.height(); if (!originalWidth || !originalHeight) { originalWidth = originalHeight = 1; } _previewWidth = st::sendMediaPreviewSize; if (preview.width() < _previewWidth) { _previewWidth = qMax(preview.width(), kMinPreviewWidth); } auto maxthumbh = qMin(qRound(1.5 * _previewWidth), st::confirmMaxHeight); _previewHeight = qRound(originalHeight * float64(_previewWidth) / originalWidth); if (_previewHeight > maxthumbh) { _previewWidth = qRound(_previewWidth * float64(maxthumbh) / _previewHeight); accumulate_max(_previewWidth, kMinPreviewWidth); _previewHeight = maxthumbh; } _previewLeft = (st::boxWideWidth - _previewWidth) / 2; preview = std::move(preview).scaled( _previewWidth * cIntRetinaFactor(), _previewHeight * cIntRetinaFactor(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); preview = Images::prepareOpaque(std::move(preview)); _preview = App::pixmapFromImageInPlace(std::move(preview)); _preview.setDevicePixelRatio(cRetinaFactor()); prepareAnimatedPreview(animatedPreviewPath); } void SingleMediaPreview::prepareAnimatedPreview( const QString &animatedPreviewPath) { if (!animatedPreviewPath.isEmpty()) { auto callback = [this](Media::Clip::Notification notification) { clipCallback(notification); }; _gifPreview = Media::Clip::MakeReader( animatedPreviewPath, std::move(callback)); if (_gifPreview) _gifPreview->setAutoplay(); } } void SingleMediaPreview::clipCallback(Media::Clip::Notification notification) { using namespace Media::Clip; switch (notification) { case NotificationReinit: { if (_gifPreview && _gifPreview->state() == State::Error) { _gifPreview.setBad(); } if (_gifPreview && _gifPreview->ready() && !_gifPreview->started()) { auto s = QSize(_previewWidth, _previewHeight); _gifPreview->start(s.width(), s.height(), s.width(), s.height(), ImageRoundRadius::None, RectPart::None); } update(); } break; case NotificationRepaint: { if (_gifPreview && !_gifPreview->currentDisplayed()) { update(); } } break; } } void SingleMediaPreview::paintEvent(QPaintEvent *e) { Painter p(this); if (_previewLeft > st::boxPhotoPadding.left()) { p.fillRect(st::boxPhotoPadding.left(), st::boxPhotoPadding.top(), _previewLeft - st::boxPhotoPadding.left(), _previewHeight, st::confirmBg); } if (_previewLeft + _previewWidth < width() - st::boxPhotoPadding.right()) { p.fillRect(_previewLeft + _previewWidth, st::boxPhotoPadding.top(), width() - st::boxPhotoPadding.right() - _previewLeft - _previewWidth, _previewHeight, st::confirmBg); } if (_gifPreview && _gifPreview->started()) { auto s = QSize(_previewWidth, _previewHeight); auto paused = _controller->isGifPausedAtLeastFor(Window::GifPauseReason::Layer); auto frame = _gifPreview->current(s.width(), s.height(), s.width(), s.height(), ImageRoundRadius::None, RectPart::None, paused ? 0 : getms()); p.drawPixmap(_previewLeft, st::boxPhotoPadding.top(), frame); } else { p.drawPixmap(_previewLeft, st::boxPhotoPadding.top(), _preview); } if (_animated && !_gifPreview) { auto inner = QRect(_previewLeft + (_previewWidth - st::msgFileSize) / 2, st::boxPhotoPadding.top() + (_previewHeight - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize); p.setPen(Qt::NoPen); p.setBrush(st::msgDateImgBg); { PainterHighQualityEnabler hq(p); p.drawEllipse(inner); } auto icon = &st::historyFileInPlay; icon->paintInCenter(p, inner); } } rpl::producer<int> SingleMediaPreview::desiredHeightValue() const { return rpl::single(st::boxPhotoPadding.top() + _previewHeight); } SingleFilePreview::SingleFilePreview( QWidget *parent, const Storage::PreparedFile &file) : RpWidget(parent) { preparePreview(file); } void SingleFilePreview::prepareThumb(const QImage &preview) { if (preview.isNull()) { return; } auto originalWidth = preview.width(); auto originalHeight = preview.height(); auto thumbWidth = st::msgFileThumbSize; if (originalWidth > originalHeight) { thumbWidth = (originalWidth * st::msgFileThumbSize) / originalHeight; } auto options = Images::Option::Smooth | Images::Option::RoundedSmall | Images::Option::RoundedTopLeft | Images::Option::RoundedTopRight | Images::Option::RoundedBottomLeft | Images::Option::RoundedBottomRight; _fileThumb = Images::pixmap( preview, thumbWidth * cIntRetinaFactor(), 0, options, st::msgFileThumbSize, st::msgFileThumbSize); } void SingleFilePreview::preparePreview(const Storage::PreparedFile &file) { auto preview = QImage(); if (const auto image = base::get_if<FileMediaInformation::Image>( &file.information->media)) { preview = image->data; } else if (const auto video = base::get_if<FileMediaInformation::Video>( &file.information->media)) { preview = video->thumbnail; } prepareThumb(preview); const auto filepath = file.path; if (filepath.isEmpty()) { auto filename = filedialogDefaultName( qsl("image"), qsl(".png"), QString(), true); _nameText.setText( st::semiboldTextStyle, filename, Ui::NameTextOptions()); _statusText = qsl("%1x%2").arg(preview.width()).arg(preview.height()); _statusWidth = qMax(_nameText.maxWidth(), st::normalFont->width(_statusText)); _fileIsImage = true; } else { auto fileinfo = QFileInfo(filepath); auto filename = fileinfo.fileName(); _fileIsImage = fileIsImage(filename, mimeTypeForFile(fileinfo).name()); auto songTitle = QString(); auto songPerformer = QString(); if (file.information) { if (const auto song = base::get_if<FileMediaInformation::Song>( &file.information->media)) { songTitle = song->title; songPerformer = song->performer; _fileIsAudio = true; } } const auto nameString = DocumentData::ComposeNameString( filename, songTitle, songPerformer); _nameText.setText( st::semiboldTextStyle, nameString, Ui::NameTextOptions()); _statusText = formatSizeText(fileinfo.size()); _statusWidth = qMax( _nameText.maxWidth(), st::normalFont->width(_statusText)); } } void SingleFilePreview::paintEvent(QPaintEvent *e) { Painter p(this); auto w = width() - st::boxPhotoPadding.left() - st::boxPhotoPadding.right(); auto h = _fileThumb.isNull() ? (st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom()) : (st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom()); auto nameleft = 0, nametop = 0, nameright = 0, statustop = 0, linktop = 0; if (_fileThumb.isNull()) { nameleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right(); nametop = st::msgFileNameTop; nameright = st::msgFilePadding.left(); statustop = st::msgFileStatusTop; } else { nameleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right(); nametop = st::msgFileThumbNameTop; nameright = st::msgFileThumbPadding.left(); statustop = st::msgFileThumbStatusTop; linktop = st::msgFileThumbLinkTop; } auto namewidth = w - nameleft - (_fileThumb.isNull() ? st::msgFilePadding.left() : st::msgFileThumbPadding.left()); int32 x = (width() - w) / 2, y = st::boxPhotoPadding.top(); App::roundRect(p, x, y, w, h, st::msgOutBg, MessageOutCorners, &st::msgOutShadow); if (_fileThumb.isNull()) { QRect inner(rtlrect(x + st::msgFilePadding.left(), y + st::msgFilePadding.top(), st::msgFileSize, st::msgFileSize, width())); p.setPen(Qt::NoPen); p.setBrush(st::msgFileOutBg); { PainterHighQualityEnabler hq(p); p.drawEllipse(inner); } auto &icon = _fileIsAudio ? st::historyFileOutPlay : _fileIsImage ? st::historyFileOutImage : st::historyFileOutDocument; icon.paintInCenter(p, inner); } else { QRect rthumb(rtlrect(x + st::msgFileThumbPadding.left(), y + st::msgFileThumbPadding.top(), st::msgFileThumbSize, st::msgFileThumbSize, width())); p.drawPixmap(rthumb.topLeft(), _fileThumb); } p.setFont(st::semiboldFont); p.setPen(st::historyFileNameOutFg); _nameText.drawLeftElided(p, x + nameleft, y + nametop, namewidth, width()); auto &status = st::mediaOutFg; p.setFont(st::normalFont); p.setPen(status); p.drawTextLeft(x + nameleft, y + statustop, width(), _statusText); } rpl::producer<int> SingleFilePreview::desiredHeightValue() const { auto h = _fileThumb.isNull() ? (st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom()) : (st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom()); return rpl::single(st::boxPhotoPadding.top() + h + st::msgShadow); } base::lambda<QString()> FieldPlaceholder(const Storage::PreparedList &list) { return langFactory(list.files.size() > 1 ? lng_photos_comment : lng_photo_caption); } } // namespace class SendFilesBox::AlbumPreview : public Ui::RpWidget { public: AlbumPreview( QWidget *parent, const Storage::PreparedList &list, SendFilesWay way); void setSendWay(SendFilesWay way); std::vector<int> takeOrder(); protected: void paintEvent(QPaintEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; private: int countLayoutHeight( const std::vector<Ui::GroupMediaLayout> &layout) const; std::vector<Ui::GroupMediaLayout> generateOrderedLayout() const; std::vector<int> defaultOrder() const; void prepareThumbs(); void updateSizeAnimated(const std::vector<Ui::GroupMediaLayout> &layout); void updateSize(); void paintAlbum(Painter &p) const; void paintPhotos(Painter &p, QRect clip) const; void paintFiles(Painter &p, QRect clip) const; void applyCursor(style::cursor cursor); int contentLeft() const; int contentTop() const; AlbumThumb *findThumb(QPoint position) const; not_null<AlbumThumb*> findClosestThumb(QPoint position) const; void updateSuggestedDrag(QPoint position); int orderIndex(not_null<AlbumThumb*> thumb) const; void cancelDrag(); void finishDrag(); const Storage::PreparedList &_list; SendFilesWay _sendWay = SendFilesWay::Files; style::cursor _cursor = style::cur_default; std::vector<int> _order; std::vector<std::unique_ptr<AlbumThumb>> _thumbs; int _thumbsHeight = 0; int _photosHeight = 0; int _filesHeight = 0; AlbumThumb *_draggedThumb = nullptr; AlbumThumb *_suggestedThumb = nullptr; AlbumThumb *_paintedAbove = nullptr; QPoint _draggedStartPosition; mutable Animation _thumbsHeightAnimation; mutable Animation _shrinkAnimation; mutable Animation _finishDragAnimation; }; SendFilesBox::AlbumPreview::AlbumPreview( QWidget *parent, const Storage::PreparedList &list, SendFilesWay way) : RpWidget(parent) , _list(list) , _sendWay(way) { setMouseTracking(true); prepareThumbs(); updateSize(); } void SendFilesBox::AlbumPreview::setSendWay(SendFilesWay way) { if (_sendWay != way) { cancelDrag(); _sendWay = way; } updateSize(); update(); } std::vector<int> SendFilesBox::AlbumPreview::takeOrder() { auto reordered = std::vector<std::unique_ptr<AlbumThumb>>(); reordered.reserve(_thumbs.size()); for (auto index : _order) { reordered.push_back(std::move(_thumbs[index])); } _thumbs = std::move(reordered); return std::exchange(_order, defaultOrder()); } auto SendFilesBox::AlbumPreview::generateOrderedLayout() const -> std::vector<Ui::GroupMediaLayout> { auto sizes = ranges::view::all( _order ) | ranges::view::transform([&](int index) { return _list.files[index].preview.size() / cIntRetinaFactor(); }) | ranges::to_vector; auto layout = Ui::LayoutMediaGroup( sizes, st::sendMediaPreviewSize, st::historyGroupWidthMin / 2, st::historyGroupSkip / 2); Assert(layout.size() == _order.size()); return layout; } std::vector<int> SendFilesBox::AlbumPreview::defaultOrder() const { const auto count = int(_list.files.size()); return ranges::view::ints(0, count) | ranges::to_vector; } void SendFilesBox::AlbumPreview::prepareThumbs() { _order = defaultOrder(); const auto count = int(_list.files.size()); const auto layout = generateOrderedLayout(); _thumbs.reserve(count); for (auto i = 0; i != count; ++i) { _thumbs.push_back(std::make_unique<AlbumThumb>( _list.files[i], layout[i])); } _thumbsHeight = countLayoutHeight(layout); _photosHeight = ranges::accumulate(ranges::view::all( _thumbs ) | ranges::view::transform([](const auto &thumb) { return thumb->photoHeight(); }), 0) + (count - 1) * st::sendMediaPreviewPhotoSkip; _filesHeight = count * st::sendMediaFileThumbSize + (count - 1) * st::sendMediaFileThumbSkip; } int SendFilesBox::AlbumPreview::contentLeft() const { return (st::boxWideWidth - st::sendMediaPreviewSize) / 2; } int SendFilesBox::AlbumPreview::contentTop() const { return 0; } AlbumThumb *SendFilesBox::AlbumPreview::findThumb(QPoint position) const { position -= QPoint(contentLeft(), contentTop()); const auto i = ranges::find_if(_thumbs, [&](const auto &thumb) { return thumb->containsPoint(position); }); return (i == _thumbs.end()) ? nullptr : i->get(); } not_null<AlbumThumb*> SendFilesBox::AlbumPreview::findClosestThumb( QPoint position) const { Expects(_draggedThumb != nullptr); if (const auto exact = findThumb(position)) { return exact; } auto result = _draggedThumb; auto distance = _draggedThumb->distanceTo(position); for (const auto &thumb : _thumbs) { const auto check = thumb->distanceTo(position); if (check < distance) { distance = check; result = thumb.get(); } } return result; } int SendFilesBox::AlbumPreview::orderIndex( not_null<AlbumThumb*> thumb) const { const auto i = ranges::find_if(_order, [&](int index) { return (_thumbs[index].get() == thumb); }); Assert(i != _order.end()); return int(i - _order.begin()); } void SendFilesBox::AlbumPreview::cancelDrag() { _thumbsHeightAnimation.finish(); _finishDragAnimation.finish(); _shrinkAnimation.finish(); if (_draggedThumb) { _draggedThumb->moveInAlbum({ 0, 0 }); _draggedThumb = nullptr; } if (_suggestedThumb) { const auto suggestedIndex = orderIndex(_suggestedThumb); if (suggestedIndex > 0) { _thumbs[_order[suggestedIndex - 1]]->suggestMove(0., [] {}); } if (suggestedIndex < int(_order.size() - 1)) { _thumbs[_order[suggestedIndex + 1]]->suggestMove(0., [] {}); } _suggestedThumb->suggestMove(0., [] {}); _suggestedThumb->finishAnimations(); _suggestedThumb = nullptr; } _paintedAbove = nullptr; update(); } void SendFilesBox::AlbumPreview::finishDrag() { Expects(_draggedThumb != nullptr); Expects(_suggestedThumb != nullptr); if (_suggestedThumb != _draggedThumb) { const auto currentIndex = orderIndex(_draggedThumb); const auto newIndex = orderIndex(_suggestedThumb); const auto delta = (currentIndex < newIndex) ? 1 : -1; const auto realIndex = _order[currentIndex]; for (auto i = currentIndex; i != newIndex; i += delta) { _order[i] = _order[i + delta]; } _order[newIndex] = realIndex; const auto layout = generateOrderedLayout(); for (auto i = 0, count = int(_order.size()); i != count; ++i) { _thumbs[_order[i]]->moveToLayout(layout[i]); } _finishDragAnimation.start([=] { update(); }, 0., 1., kDragDuration); updateSizeAnimated(layout); } else { for (const auto &thumb : _thumbs) { thumb->resetLayoutAnimation(); } _draggedThumb->animateLayoutToInitial(); _finishDragAnimation.start([=] { update(); }, 0., 1., kDragDuration); } } int SendFilesBox::AlbumPreview::countLayoutHeight( const std::vector<Ui::GroupMediaLayout> &layout) const { const auto accumulator = [](int current, const auto &item) { return std::max(current, item.geometry.y() + item.geometry.height()); }; return ranges::accumulate(layout, 0, accumulator); } void SendFilesBox::AlbumPreview::updateSizeAnimated( const std::vector<Ui::GroupMediaLayout> &layout) { const auto newHeight = countLayoutHeight(layout); if (newHeight != _thumbsHeight) { _thumbsHeightAnimation.start( [=] { updateSize(); }, _thumbsHeight, newHeight, kDragDuration); _thumbsHeight = newHeight; } } void SendFilesBox::AlbumPreview::updateSize() { const auto newHeight = [&] { switch (_sendWay) { case SendFilesWay::Album: return int(std::round(_thumbsHeightAnimation.current( _thumbsHeight))); case SendFilesWay::Photos: return _photosHeight; case SendFilesWay::Files: return _filesHeight; } Unexpected("Send way in SendFilesBox::AlbumPreview::updateSize"); }(); if (height() != newHeight) { resize(st::boxWideWidth, newHeight); } } void SendFilesBox::AlbumPreview::paintEvent(QPaintEvent *e) { Painter p(this); switch (_sendWay) { case SendFilesWay::Album: paintAlbum(p); break; case SendFilesWay::Photos: paintPhotos(p, e->rect()); break; case SendFilesWay::Files: paintFiles(p, e->rect()); break; } } void SendFilesBox::AlbumPreview::paintAlbum(Painter &p) const { const auto ms = getms(); const auto shrink = _shrinkAnimation.current( ms, _draggedThumb ? 1. : 0.); const auto moveProgress = _finishDragAnimation.current(ms, 1.); const auto left = contentLeft(); const auto top = contentTop(); for (const auto &thumb : _thumbs) { if (thumb.get() != _paintedAbove) { thumb->paintInAlbum(p, left, top, shrink, moveProgress, ms); } } if (_paintedAbove) { _paintedAbove->paintInAlbum(p, left, top, shrink, moveProgress, ms); } } void SendFilesBox::AlbumPreview::paintPhotos(Painter &p, QRect clip) const { const auto left = (st::boxWideWidth - st::sendMediaPreviewSize) / 2; auto top = 0; const auto outerWidth = width(); for (const auto &thumb : _thumbs) { const auto bottom = top + thumb->photoHeight(); const auto guard = gsl::finally([&] { top = bottom + st::sendMediaPreviewPhotoSkip; }); if (top >= clip.y() + clip.height()) { break; } else if (bottom <= clip.y()) { continue; } thumb->paintPhoto(p, left, top, outerWidth); } } void SendFilesBox::AlbumPreview::paintFiles(Painter &p, QRect clip) const { const auto fileHeight = st::sendMediaFileThumbSize + st::sendMediaFileThumbSkip; const auto bottom = clip.y() + clip.height(); const auto from = floorclamp(clip.y(), fileHeight, 0, _thumbs.size()); const auto till = ceilclamp(bottom, fileHeight, 0, _thumbs.size()); const auto left = (st::boxWideWidth - st::sendMediaPreviewSize) / 2; const auto outerWidth = width(); auto top = from * fileHeight; for (auto i = from; i != till; ++i) { _thumbs[i]->paintFile(p, left, top, outerWidth); top += fileHeight; } } void SendFilesBox::AlbumPreview::mousePressEvent(QMouseEvent *e) { if (_finishDragAnimation.animating()) { return; } const auto position = e->pos(); cancelDrag(); if (const auto thumb = findThumb(position)) { _paintedAbove = _suggestedThumb = _draggedThumb = thumb; _draggedStartPosition = position; _shrinkAnimation.start([=] { update(); }, 0., 1., kShrinkDuration); } } void SendFilesBox::AlbumPreview::mouseMoveEvent(QMouseEvent *e) { if (_sendWay != SendFilesWay::Album) { applyCursor(style::cur_default); return; } if (_draggedThumb) { const auto position = e->pos(); _draggedThumb->moveInAlbum(position - _draggedStartPosition); updateSuggestedDrag(_draggedThumb->center()); update(); } else { const auto cursor = findThumb(e->pos()) ? style::cur_sizeall : style::cur_default; applyCursor(cursor); } } void SendFilesBox::AlbumPreview::applyCursor(style::cursor cursor) { if (_cursor != cursor) { _cursor = cursor; setCursor(_cursor); } } void SendFilesBox::AlbumPreview::updateSuggestedDrag(QPoint position) { auto closest = findClosestThumb(position); auto closestIndex = orderIndex(closest); const auto draggedIndex = orderIndex(_draggedThumb); const auto closestIsBeforePoint = closest->isPointAfter(position); if (closestIndex < draggedIndex && closestIsBeforePoint) { closest = _thumbs[_order[++closestIndex]].get(); } else if (closestIndex > draggedIndex && !closestIsBeforePoint) { closest = _thumbs[_order[--closestIndex]].get(); } if (_suggestedThumb == closest) { return; } const auto last = int(_order.size()) - 1; if (_suggestedThumb) { const auto suggestedIndex = orderIndex(_suggestedThumb); if (suggestedIndex < draggedIndex && suggestedIndex > 0) { const auto previous = _thumbs[_order[suggestedIndex - 1]].get(); previous->suggestMove(0., [=] { update(); }); } else if (suggestedIndex > draggedIndex && suggestedIndex < last) { const auto next = _thumbs[_order[suggestedIndex + 1]].get(); next->suggestMove(0., [=] { update(); }); } _suggestedThumb->suggestMove(0., [=] { update(); }); } _suggestedThumb = closest; const auto suggestedIndex = closestIndex; if (_suggestedThumb != _draggedThumb) { const auto delta = (suggestedIndex < draggedIndex) ? 1. : -1.; if (delta > 0. && suggestedIndex > 0) { const auto previous = _thumbs[_order[suggestedIndex - 1]].get(); previous->suggestMove(-delta, [=] { update(); }); } else if (delta < 0. && suggestedIndex < last) { const auto next = _thumbs[_order[suggestedIndex + 1]].get(); next->suggestMove(-delta, [=] { update(); }); } _suggestedThumb->suggestMove(delta, [=] { update(); }); } } void SendFilesBox::AlbumPreview::mouseReleaseEvent(QMouseEvent *e) { if (_draggedThumb) { finishDrag(); _shrinkAnimation.start([=] { update(); }, 1., 0., kShrinkDuration); _draggedThumb = nullptr; _suggestedThumb = nullptr; update(); } } SendFilesBox::SendFilesBox( QWidget*, Storage::PreparedList &&list, CompressConfirm compressed) : _list(std::move(list)) , _compressConfirmInitial(compressed) , _compressConfirm(compressed) { } void SendFilesBox::initPreview(rpl::producer<int> desiredPreviewHeight) { setupControls(); updateBoxSize(); using namespace rpl::mappers; rpl::combine( std::move(desiredPreviewHeight), _footerHeight.value(), _titleHeight + _1 + _2 ) | rpl::start_with_next([this](int height) { setDimensions( st::boxWideWidth, std::min(st::sendMediaPreviewHeightMax, height)); }, lifetime()); if (_preview) { _preview->show(); } } void SendFilesBox::prepareSingleFilePreview() { Expects(_list.files.size() == 1); const auto &file = _list.files[0]; const auto media = SingleMediaPreview::Create(this, controller(), file); if (media) { if (!media->canSendAsPhoto()) { _compressConfirm = CompressConfirm::None; } _preview = media; initPreview(media->desiredHeightValue()); } else { const auto preview = Ui::CreateChild<SingleFilePreview>(this, file); _compressConfirm = CompressConfirm::None; _preview = preview; initPreview(preview->desiredHeightValue()); } } void SendFilesBox::prepareAlbumPreview() { Expects(_sendWay != nullptr); const auto wrap = Ui::CreateChild<Ui::ScrollArea>( this, st::boxLayerScroll); _albumPreview = wrap->setOwnedWidget(object_ptr<AlbumPreview>( this, _list, _sendWay->value())); _preview = wrap; _albumPreview->show(); setupShadows(wrap, _albumPreview); initPreview(_albumPreview->desiredHeightValue()); } void SendFilesBox::setupShadows( not_null<Ui::ScrollArea*> wrap, not_null<AlbumPreview*> content) { using namespace rpl::mappers; const auto topShadow = Ui::CreateChild<Ui::FadeShadow>(this); const auto bottomShadow = Ui::CreateChild<Ui::FadeShadow>(this); wrap->geometryValue( ) | rpl::start_with_next_done([=](const QRect &geometry) { topShadow->resizeToWidth(geometry.width()); topShadow->move( geometry.x(), geometry.y()); bottomShadow->resizeToWidth(geometry.width()); bottomShadow->move( geometry.x(), geometry.y() + geometry.height() - st::lineWidth); }, [t = make_weak(topShadow), b = make_weak(bottomShadow)] { Ui::DestroyChild(t.data()); Ui::DestroyChild(b.data()); }, topShadow->lifetime()); topShadow->toggleOn(wrap->scrollTopValue() | rpl::map(_1 > 0)); bottomShadow->toggleOn(rpl::combine( wrap->scrollTopValue(), wrap->heightValue(), content->heightValue(), _1 + _2 < _3)); } void SendFilesBox::prepare() { Expects(controller() != nullptr); _send = addButton(langFactory(lng_send_button), [this] { send(); }); addButton(langFactory(lng_cancel), [this] { closeBox(); }); initSendWay(); preparePreview(); subscribe(boxClosing, [this] { if (!_confirmed && _cancelledCallback) { _cancelledCallback(); } }); } void SendFilesBox::initSendWay() { refreshAlbumMediaCount(); const auto value = [&] { if (_compressConfirm == CompressConfirm::None) { return SendFilesWay::Files; } else if (_compressConfirm == CompressConfirm::No) { return SendFilesWay::Files; } else if (_compressConfirm == CompressConfirm::Yes) { return _list.albumIsPossible ? SendFilesWay::Album : SendFilesWay::Photos; } const auto currentWay = Auth().settings().sendFilesWay(); if (currentWay == SendFilesWay::Files) { return currentWay; } else if (currentWay == SendFilesWay::Album) { return _list.albumIsPossible ? SendFilesWay::Album : SendFilesWay::Photos; } return (_list.albumIsPossible && !_albumPhotosCount) ? SendFilesWay::Album : SendFilesWay::Photos; }(); _sendWay = std::make_shared<Ui::RadioenumGroup<SendFilesWay>>(value); _sendWay->setChangedCallback([this](SendFilesWay value) { applyAlbumOrder(); if (_albumPreview) { _albumPreview->setSendWay(value); } setInnerFocus(); }); } void SendFilesBox::refreshAlbumMediaCount() { _albumVideosCount = _list.albumIsPossible ? ranges::count( _list.files, Storage::PreparedFile::AlbumType::Video, [](const Storage::PreparedFile &file) { return file.type; }) : 0; _albumPhotosCount = _list.albumIsPossible ? (_list.files.size() - _albumVideosCount) : 0; } void SendFilesBox::preparePreview() { if (_list.files.size() == 1) { prepareSingleFilePreview(); } else { if (_list.albumIsPossible) { prepareAlbumPreview(); } else { auto desiredPreviewHeight = rpl::single(0); initPreview(std::move(desiredPreviewHeight)); } } } void SendFilesBox::setupControls() { setupTitleText(); setupSendWayControls(); setupCaption(); } void SendFilesBox::setupSendWayControls() { _sendAlbum.destroy(); _sendPhotos.destroy(); _sendFiles.destroy(); if (_compressConfirm == CompressConfirm::None) { return; } const auto addRadio = [&]( object_ptr<Ui::Radioenum<SendFilesWay>> &button, SendFilesWay value, const QString &text) { const auto &style = st::defaultBoxCheckbox; button.create(this, _sendWay, value, text, style); button->show(); }; if (_list.albumIsPossible) { addRadio(_sendAlbum, SendFilesWay::Album, lang(lng_send_album)); } if (!_list.albumIsPossible || _albumPhotosCount > 0) { addRadio(_sendPhotos, SendFilesWay::Photos, (_list.files.size() == 1) ? lang(lng_send_photo) : (_albumVideosCount > 0) ? lang(lng_send_separate_photos_videos) : (_list.albumIsPossible ? lang(lng_send_separate_photos) : lng_send_photos(lt_count, _list.files.size()))); } addRadio(_sendFiles, SendFilesWay::Files, (_list.files.size() == 1) ? lang(lng_send_file) : lng_send_files(lt_count, _list.files.size())); } void SendFilesBox::applyAlbumOrder() { if (!_albumPreview) { return; } const auto order = _albumPreview->takeOrder(); const auto isDefault = [&] { for (auto i = 0, count = int(order.size()); i != count; ++i) { if (order[i] != i) { return false; } } return true; }(); if (isDefault) { return; } _list = Storage::PreparedList::Reordered(std::move(_list), order); } void SendFilesBox::setupCaption() { if (_caption) { _caption->setPlaceholder(FieldPlaceholder(_list)); return; } _caption.create(this, st::confirmCaptionArea, FieldPlaceholder(_list)); _caption->setMaxLength(MaxPhotoCaption); _caption->setCtrlEnterSubmit(Ui::CtrlEnterSubmit::Both); connect(_caption, &Ui::InputArea::resized, this, [this] { captionResized(); }); connect(_caption, &Ui::InputArea::submitted, this, [this]( bool ctrlShiftEnter) { send(ctrlShiftEnter); }); connect(_caption, &Ui::InputArea::cancelled, this, [this] { closeBox(); }); _caption->setMimeDataHook([this]( not_null<const QMimeData*> data, Ui::InputArea::MimeAction action) { if (action == Ui::InputArea::MimeAction::Check) { return canAddFiles(data); } else if (action == Ui::InputArea::MimeAction::Insert) { return addFiles(data); } Unexpected("action in MimeData hook."); }); } void SendFilesBox::captionResized() { updateBoxSize(); updateControlsGeometry(); update(); } bool SendFilesBox::canAddUrls(const QList<QUrl> &urls) const { return !urls.isEmpty() && ranges::find_if( urls, [](const QUrl &url) { return !url.isLocalFile(); } ) == urls.end(); } bool SendFilesBox::canAddFiles(not_null<const QMimeData*> data) const { const auto urls = data->hasUrls() ? data->urls() : QList<QUrl>(); auto filesCount = canAddUrls(urls) ? urls.size() : 0; if (!filesCount && data->hasImage()) { ++filesCount; } if (_list.files.size() + filesCount > Storage::MaxAlbumItems()) { return false; } else if (_list.files.size() > 1 && !_albumPreview) { return false; } else if (_list.files.front().type == Storage::PreparedFile::AlbumType::None) { return false; } return true; } bool SendFilesBox::addFiles(not_null<const QMimeData*> data) { auto list = [&] { const auto urls = data->hasUrls() ? data->urls() : QList<QUrl>(); auto result = canAddUrls(urls) ? Storage::PrepareMediaList(urls, st::sendMediaPreviewSize) : Storage::PreparedList( Storage::PreparedList::Error::EmptyFile, QString()); if (result.error == Storage::PreparedList::Error::None) { return result; } else if (data->hasImage()) { auto image = qvariant_cast<QImage>(data->imageData()); if (!image.isNull()) { return Storage::PrepareMediaFromImage( std::move(image), QByteArray(), st::sendMediaPreviewSize); } } return result; }(); if (_list.files.size() + list.files.size() > Storage::MaxAlbumItems()) { return false; } else if (list.error != Storage::PreparedList::Error::None) { return false; } else if (list.files.size() != 1 && !list.albumIsPossible) { return false; } else if (list.files.front().type == Storage::PreparedFile::AlbumType::None) { return false; } else if (_list.files.size() > 1 && !_albumPreview) { return false; } else if (_list.files.front().type == Storage::PreparedFile::AlbumType::None) { return false; } applyAlbumOrder(); delete base::take(_preview); _albumPreview = nullptr; if (_list.files.size() == 1 && _sendWay->value() == SendFilesWay::Photos) { _sendWay->setValue(SendFilesWay::Album); } _list.mergeToEnd(std::move(list)); _compressConfirm = _compressConfirmInitial; refreshAlbumMediaCount(); preparePreview(); updateControlsGeometry(); return true; } void SendFilesBox::setupTitleText() { if (_list.files.size() > 1) { const auto onlyImages = (_compressConfirm != CompressConfirm::None) && (_albumVideosCount == 0); _titleText = onlyImages ? lng_send_images_selected(lt_count, _list.files.size()) : lng_send_files_selected(lt_count, _list.files.size()); _titleHeight = st::boxTitleHeight; } else { _titleText = QString(); _titleHeight = 0; } } void SendFilesBox::updateBoxSize() { auto footerHeight = 0; if (_caption) { footerHeight += st::boxPhotoCaptionSkip + _caption->height(); } const auto pointers = { _sendAlbum.data(), _sendPhotos.data(), _sendFiles.data() }; for (auto pointer : pointers) { if (pointer) { footerHeight += st::boxPhotoCompressedSkip + pointer->heightNoMargins(); } } _footerHeight = footerHeight; } void SendFilesBox::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) { const auto modifiers = e->modifiers(); const auto ctrl = modifiers.testFlag(Qt::ControlModifier) || modifiers.testFlag(Qt::MetaModifier); const auto shift = modifiers.testFlag(Qt::ShiftModifier); send(ctrl && shift); } else { BoxContent::keyPressEvent(e); } } void SendFilesBox::paintEvent(QPaintEvent *e) { BoxContent::paintEvent(e); if (!_titleText.isEmpty()) { Painter p(this); p.setFont(st::boxPhotoTitleFont); p.setPen(st::boxTitleFg); p.drawTextLeft( st::boxPhotoTitlePosition.x(), st::boxPhotoTitlePosition.y(), width(), _titleText); } } void SendFilesBox::resizeEvent(QResizeEvent *e) { BoxContent::resizeEvent(e); updateControlsGeometry(); } void SendFilesBox::updateControlsGeometry() { auto bottom = height(); if (_caption) { _caption->resize(st::sendMediaPreviewSize, _caption->height()); _caption->moveToLeft( st::boxPhotoPadding.left(), bottom - _caption->height()); bottom -= st::boxPhotoCaptionSkip + _caption->height(); } const auto pointers = { _sendAlbum.data(), _sendPhotos.data(), _sendFiles.data() }; for (auto pointer : base::reversed(pointers)) { if (pointer) { pointer->moveToLeft( st::boxPhotoPadding.left(), bottom - pointer->heightNoMargins()); bottom -= st::boxPhotoCompressedSkip + pointer->heightNoMargins(); } } if (_preview) { _preview->resize(width(), bottom - _titleHeight); _preview->move(0, _titleHeight); } } void SendFilesBox::setInnerFocus() { if (!_caption || _caption->isHidden()) { setFocus(); } else { _caption->setFocusFast(); } } void SendFilesBox::send(bool ctrlShiftEnter) { using Way = SendFilesWay; const auto way = _sendWay ? _sendWay->value() : Way::Files; if (_compressConfirm == CompressConfirm::Auto) { const auto oldWay = Auth().settings().sendFilesWay(); if (way != oldWay) { // Check if the user _could_ use the old value, but didn't. if ((oldWay == Way::Album && _sendAlbum) || (oldWay == Way::Photos && _sendPhotos) || (oldWay == Way::Files && _sendFiles) || (way == Way::Files && (_sendAlbum || _sendPhotos))) { // And in that case save it to settings. Auth().settings().setSendFilesWay(way); Auth().saveSettingsDelayed(); } } } applyAlbumOrder(); _confirmed = true; if (_confirmedCallback) { auto caption = _caption ? TextUtilities::PrepareForSending( _caption->getLastText(), TextUtilities::PrepareTextOption::CheckLinks) : QString(); _confirmedCallback( std::move(_list), way, std::move(caption), ctrlShiftEnter); } closeBox(); } SendFilesBox::~SendFilesBox() = default; ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/send_files_box.h�����������������������������������������0000664�0000000�0000000�00000005641�13262451251�0023705�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/variable.h> #include "boxes/abstract_box.h" #include "storage/localimageloader.h" #include "storage/storage_media_prepare.h" namespace Ui { template <typename Enum> class Radioenum; template <typename Enum> class RadioenumGroup; class RoundButton; class InputArea; struct GroupMediaLayout; } // namespace Ui enum class SendFilesWay { Album, Photos, Files, }; class SendFilesBox : public BoxContent { public: SendFilesBox( QWidget*, Storage::PreparedList &&list, CompressConfirm compressed); void setConfirmedCallback( base::lambda<void( Storage::PreparedList &&list, SendFilesWay way, const QString &caption, bool ctrlShiftEnter)> callback) { _confirmedCallback = std::move(callback); } void setCancelledCallback(base::lambda<void()> callback) { _cancelledCallback = std::move(callback); } ~SendFilesBox(); protected: void prepare() override; void setInnerFocus() override; void keyPressEvent(QKeyEvent *e) override; void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; private: class AlbumPreview; void initSendWay(); void initPreview(rpl::producer<int> desiredPreviewHeight); void setupControls(); void setupSendWayControls(); void setupCaption(); void setupShadows( not_null<Ui::ScrollArea*> wrap, not_null<AlbumPreview*> content); void refreshAlbumMediaCount(); void preparePreview(); void prepareSingleFilePreview(); void prepareAlbumPreview(); void applyAlbumOrder(); void send(bool ctrlShiftEnter = false); void captionResized(); void setupTitleText(); void updateBoxSize(); void updateControlsGeometry(); bool canAddFiles(not_null<const QMimeData*> data) const; bool canAddUrls(const QList<QUrl> &urls) const; bool addFiles(not_null<const QMimeData*> data); QString _titleText; int _titleHeight = 0; Storage::PreparedList _list; CompressConfirm _compressConfirmInitial = CompressConfirm::None; CompressConfirm _compressConfirm = CompressConfirm::None; base::lambda<void( Storage::PreparedList &&list, SendFilesWay way, const QString &caption, bool ctrlShiftEnter)> _confirmedCallback; base::lambda<void()> _cancelledCallback; bool _confirmed = false; object_ptr<Ui::InputArea> _caption = { nullptr }; object_ptr<Ui::Radioenum<SendFilesWay>> _sendAlbum = { nullptr }; object_ptr<Ui::Radioenum<SendFilesWay>> _sendPhotos = { nullptr }; object_ptr<Ui::Radioenum<SendFilesWay>> _sendFiles = { nullptr }; std::shared_ptr<Ui::RadioenumGroup<SendFilesWay>> _sendWay; rpl::variable<int> _footerHeight = 0; QWidget *_preview = nullptr; AlbumPreview *_albumPreview = nullptr; int _albumVideosCount = 0; int _albumPhotosCount = 0; QPointer<Ui::RoundButton> _send; }; �����������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/sessions_box.cpp�����������������������������������������0000664�0000000�0000000�00000033376�13262451251�0024001�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/sessions_box.h" #include "lang/lang_keys.h" #include "storage/localstorage.h" #include "mainwidget.h" #include "mainwindow.h" #include "countries.h" #include "boxes/confirm_box.h" #include "ui/widgets/buttons.h" #include "ui/widgets/scroll_area.h" #include "styles/style_boxes.h" SessionsBox::SessionsBox(QWidget*) : _shortPollTimer(this) { } void SessionsBox::prepare() { setTitle(langFactory(lng_sessions_other_header)); addButton(langFactory(lng_close), [this] { closeBox(); }); setDimensions(st::boxWideWidth, st::sessionsHeight); connect(_inner, SIGNAL(oneTerminated()), this, SLOT(onOneTerminated())); connect(_inner, SIGNAL(allTerminated()), this, SLOT(onAllTerminated())); connect(_inner, SIGNAL(terminateAll()), this, SLOT(onTerminateAll())); connect(App::wnd(), SIGNAL(checkNewAuthorization()), this, SLOT(onCheckNewAuthorization())); connect(_shortPollTimer, SIGNAL(timeout()), this, SLOT(onShortPollAuthorizations())); _inner = setInnerWidget(object_ptr<Inner>(this, &_list, &_current), st::sessionsScroll); _inner->resize(width(), st::noContactsHeight); setLoading(true); MTP::send(MTPaccount_GetAuthorizations(), rpcDone(&SessionsBox::gotAuthorizations)); } void SessionsBox::setLoading(bool loading) { if (_loading != loading) { _loading = loading; setInnerVisible(!_loading); } } void SessionsBox::resizeEvent(QResizeEvent *e) { BoxContent::resizeEvent(e); _inner->resize(width(), _inner->height()); } void SessionsBox::paintEvent(QPaintEvent *e) { BoxContent::paintEvent(e); Painter p(this); if (_loading) { p.setFont(st::noContactsFont); p.setPen(st::noContactsColor); p.drawText(QRect(0, 0, width(), st::noContactsHeight), lang(lng_contacts_loading), style::al_center); } } void SessionsBox::gotAuthorizations(const MTPaccount_Authorizations &result) { _shortPollRequest = 0; setLoading(false); auto availCurrent = st::boxWideWidth - st::sessionPadding.left() - st::sessionTerminateSkip; auto availOther = availCurrent - st::sessionTerminate.iconPosition.x();// -st::sessionTerminate.width - st::sessionTerminateSkip; _list.clear(); if (result.type() != mtpc_account_authorizations) { return; } auto &v = result.c_account_authorizations().vauthorizations.v; _list.reserve(v.size()); const CountriesByISO2 &countries(countriesByISO2()); for_const (auto &auth, v) { if (auth.type() != mtpc_authorization) { continue; } auto &d = auth.c_authorization(); Data data; data.hash = d.vhash.v; QString appName, appVer = qs(d.vapp_version), systemVer = qs(d.vsystem_version), deviceModel = qs(d.vdevice_model); if (d.vapi_id.v == 2040 || d.vapi_id.v == 17349) { appName = (d.vapi_id.v == 2040) ? qstr("Telegram Desktop") : qstr("Telegram Desktop (GitHub)"); // if (systemVer == qstr("windows")) { // deviceModel = qsl("Windows"); // } else if (systemVer == qstr("os x")) { // deviceModel = qsl("OS X"); // } else if (systemVer == qstr("linux")) { // deviceModel = qsl("Linux"); // } if (appVer == QString::number(appVer.toInt())) { int32 ver = appVer.toInt(); appVer = QString("%1.%2").arg(ver / 1000000).arg((ver % 1000000) / 1000) + ((ver % 1000) ? ('.' + QString::number(ver % 1000)) : QString()); //} else { // appVer = QString(); } } else { appName = qs(d.vapp_name);// +qsl(" for ") + qs(d.vplatform); if (appVer.indexOf('(') >= 0) appVer = appVer.mid(appVer.indexOf('(')); } data.name = appName; if (!appVer.isEmpty()) data.name += ' ' + appVer; data.nameWidth = st::sessionNameFont->width(data.name); QString country = qs(d.vcountry), platform = qs(d.vplatform); //CountriesByISO2::const_iterator j = countries.constFind(country); //if (j != countries.cend()) country = QString::fromUtf8(j.value()->name); const auto active = data.activeTime = d.vdate_active.v ? d.vdate_active.v : d.vdate_created.v; data.info = qs(d.vdevice_model) + qstr(", ") + (platform.isEmpty() ? QString() : platform + ' ') + qs(d.vsystem_version); data.ip = qs(d.vip) + (country.isEmpty() ? QString() : QString::fromUtf8(" \xe2\x80\x93 ") + country); if (!data.hash || (d.vflags.v & 1)) { data.active = lang(lng_sessions_header); data.activeWidth = st::sessionWhenFont->width(lang(lng_sessions_header)); int32 availForName = availCurrent - st::sessionPadding.right() - data.activeWidth; if (data.nameWidth > availForName) { data.name = st::sessionNameFont->elided(data.name, availForName); data.nameWidth = st::sessionNameFont->width(data.name); } data.infoWidth = st::sessionInfoFont->width(data.info); if (data.infoWidth > availCurrent) { data.info = st::sessionInfoFont->elided(data.info, availCurrent); data.infoWidth = st::sessionInfoFont->width(data.info); } data.ipWidth = st::sessionInfoFont->width(data.ip); if (data.ipWidth > availCurrent) { data.ip = st::sessionInfoFont->elided(data.ip, availCurrent); data.ipWidth = st::sessionInfoFont->width(data.ip); } _current = data; } else { const auto now = QDateTime::currentDateTime(); const auto lastTime = ParseDateTime(active); const auto nowDate = now.date(); const auto lastDate = lastTime.date(); QString dt; if (lastDate == nowDate) { data.active = lastTime.toString(cTimeFormat()); } else if (lastDate.year() == nowDate.year() && lastDate.weekNumber() == nowDate.weekNumber()) { data.active = langDayOfWeek(lastDate); } else { data.active = lastDate.toString(qsl("d.MM.yy")); } data.activeWidth = st::sessionWhenFont->width(data.active); int32 availForName = availOther - st::sessionPadding.right() - data.activeWidth; if (data.nameWidth > availForName) { data.name = st::sessionNameFont->elided(data.name, availForName); data.nameWidth = st::sessionNameFont->width(data.name); } data.infoWidth = st::sessionInfoFont->width(data.info); if (data.infoWidth > availOther) { data.info = st::sessionInfoFont->elided(data.info, availOther); data.infoWidth = st::sessionInfoFont->width(data.info); } data.ipWidth = st::sessionInfoFont->width(data.ip); if (data.ipWidth > availOther) { data.ip = st::sessionInfoFont->elided(data.ip, availOther); data.ipWidth = st::sessionInfoFont->width(data.ip); } _list.push_back(data); for (int32 i = _list.size(); i > 1;) { --i; if (_list.at(i).activeTime > _list.at(i - 1).activeTime) { qSwap(_list[i], _list[i - 1]); } } } } _inner->listUpdated(); update(); _shortPollTimer->start(SessionsShortPollTimeout); } void SessionsBox::onOneTerminated() { update(); } void SessionsBox::onShortPollAuthorizations() { if (!_shortPollRequest) { _shortPollRequest = MTP::send(MTPaccount_GetAuthorizations(), rpcDone(&SessionsBox::gotAuthorizations)); update(); } } void SessionsBox::onCheckNewAuthorization() { onShortPollAuthorizations(); // _shortPollTimer.start(1000); } void SessionsBox::onAllTerminated() { MTP::send(MTPaccount_GetAuthorizations(), rpcDone(&SessionsBox::gotAuthorizations)); if (_shortPollRequest) { MTP::cancel(_shortPollRequest); _shortPollRequest = 0; } } void SessionsBox::onTerminateAll() { setLoading(true); } SessionsBox::Inner::Inner(QWidget *parent, SessionsBox::List *list, SessionsBox::Data *current) : TWidget(parent) , _list(list) , _current(current) , _terminateAll(this, lang(lng_sessions_terminate_all), st::sessionTerminateAllButton) { connect(_terminateAll, SIGNAL(clicked()), this, SLOT(onTerminateAll())); _terminateAll->hide(); setAttribute(Qt::WA_OpaquePaintEvent); } void SessionsBox::Inner::paintEvent(QPaintEvent *e) { QRect r(e->rect()); Painter p(this); p.fillRect(r, st::boxBg); int32 x = st::sessionPadding.left(), xact = st::sessionTerminateSkip + st::sessionTerminate.iconPosition.x();// st::sessionTerminateSkip + st::sessionTerminate.width + st::sessionTerminateSkip; int32 w = width(); if (_current->active.isEmpty() && _list->isEmpty()) { p.setFont(st::noContactsFont->f); p.setPen(st::noContactsColor->p); p.drawText(QRect(0, 0, width(), st::noContactsHeight), lang(lng_contacts_loading), style::al_center); return; } if (r.y() <= st::sessionCurrentHeight) { p.translate(0, st::sessionCurrentPadding.top()); p.setFont(st::sessionNameFont); p.setPen(st::sessionNameFg); p.drawTextLeft(x, st::sessionPadding.top(), w, _current->name, _current->nameWidth); p.setFont(st::sessionWhenFont); p.setPen(st::sessionWhenFg); p.drawTextRight(x, st::sessionPadding.top(), w, _current->active, _current->activeWidth); p.setFont(st::sessionInfoFont); p.setPen(st::boxTextFg); p.drawTextLeft(x, st::sessionPadding.top() + st::sessionNameFont->height, w, _current->info, _current->infoWidth); p.setPen(st::sessionInfoFg); p.drawTextLeft(x, st::sessionPadding.top() + st::sessionNameFont->height + st::sessionInfoFont->height, w, _current->ip, _current->ipWidth); } p.translate(0, st::sessionCurrentHeight - st::sessionCurrentPadding.top()); if (_list->isEmpty()) { p.setFont(st::sessionInfoFont); p.setPen(st::sessionInfoFg); p.drawText(QRect(st::sessionPadding.left(), 0, width() - st::sessionPadding.left() - st::sessionPadding.right(), st::noContactsHeight), lang(lng_sessions_other_desc), style::al_topleft); return; } p.setFont(st::linkFont->f); int32 count = _list->size(); int32 from = floorclamp(r.y() - st::sessionCurrentHeight, st::sessionHeight, 0, count); int32 to = ceilclamp(r.y() + r.height() - st::sessionCurrentHeight, st::sessionHeight, 0, count); p.translate(0, from * st::sessionHeight); for (int32 i = from; i < to; ++i) { const SessionsBox::Data &auth(_list->at(i)); p.setFont(st::sessionNameFont); p.setPen(st::sessionNameFg); p.drawTextLeft(x, st::sessionPadding.top(), w, auth.name, auth.nameWidth); p.setFont(st::sessionWhenFont); p.setPen(st::sessionWhenFg); p.drawTextRight(xact, st::sessionPadding.top(), w, auth.active, auth.activeWidth); p.setFont(st::sessionInfoFont); p.setPen(st::boxTextFg); p.drawTextLeft(x, st::sessionPadding.top() + st::sessionNameFont->height, w, auth.info, auth.infoWidth); p.setPen(st::sessionInfoFg); p.drawTextLeft(x, st::sessionPadding.top() + st::sessionNameFont->height + st::sessionInfoFont->height, w, auth.ip, auth.ipWidth); p.translate(0, st::sessionHeight); } } void SessionsBox::Inner::onTerminate() { for (auto i = _terminateButtons.begin(), e = _terminateButtons.end(); i != e; ++i) { if (i.value()->isOver()) { if (_terminateBox) _terminateBox->deleteLater(); _terminateBox = Ui::show(Box<ConfirmBox>(lang(lng_settings_reset_one_sure), lang(lng_settings_reset_button), st::attentionBoxButton, base::lambda_guarded(this, [this, terminating = i.key()] { if (_terminateBox) { _terminateBox->closeBox(); _terminateBox = nullptr; } MTP::send(MTPaccount_ResetAuthorization(MTP_long(terminating)), rpcDone(&Inner::terminateDone, terminating), rpcFail(&Inner::terminateFail, terminating)); auto i = _terminateButtons.find(terminating); if (i != _terminateButtons.cend()) { i.value()->clearState(); i.value()->hide(); } })), LayerOption::KeepOther); } } } void SessionsBox::Inner::onTerminateAll() { if (_terminateBox) _terminateBox->deleteLater(); _terminateBox = Ui::show(Box<ConfirmBox>(lang(lng_settings_reset_sure), lang(lng_settings_reset_button), st::attentionBoxButton, base::lambda_guarded(this, [this] { if (_terminateBox) { _terminateBox->closeBox(); _terminateBox = nullptr; } MTP::send(MTPauth_ResetAuthorizations(), rpcDone(&Inner::terminateAllDone), rpcFail(&Inner::terminateAllFail)); emit terminateAll(); })), LayerOption::KeepOther); } void SessionsBox::Inner::terminateDone(uint64 hash, const MTPBool &result) { for (int32 i = 0, l = _list->size(); i < l; ++i) { if (_list->at(i).hash == hash) { _list->removeAt(i); break; } } listUpdated(); emit oneTerminated(); } bool SessionsBox::Inner::terminateFail(uint64 hash, const RPCError &error) { if (MTP::isDefaultHandledError(error)) return false; TerminateButtons::iterator i = _terminateButtons.find(hash); if (i != _terminateButtons.end()) { i.value()->show(); return true; } return false; } void SessionsBox::Inner::terminateAllDone(const MTPBool &result) { emit allTerminated(); } bool SessionsBox::Inner::terminateAllFail(const RPCError &error) { if (MTP::isDefaultHandledError(error)) return false; emit allTerminated(); return true; } void SessionsBox::Inner::resizeEvent(QResizeEvent *e) { _terminateAll->moveToLeft(st::sessionPadding.left(), st::sessionCurrentPadding.top() + st::sessionHeight + st::sessionCurrentPadding.bottom()); } void SessionsBox::Inner::listUpdated() { if (_list->isEmpty()) { _terminateAll->hide(); } else { _terminateAll->show(); } for (TerminateButtons::iterator i = _terminateButtons.begin(), e = _terminateButtons.end(); i != e; ++i) { i.value()->move(0, -1); } for (int32 i = 0, l = _list->size(); i < l; ++i) { TerminateButtons::iterator j = _terminateButtons.find(_list->at(i).hash); if (j == _terminateButtons.cend()) { j = _terminateButtons.insert(_list->at(i).hash, new Ui::IconButton(this, st::sessionTerminate)); connect(j.value(), SIGNAL(clicked()), this, SLOT(onTerminate())); } j.value()->moveToRight(st::sessionTerminateSkip, st::sessionCurrentHeight + i * st::sessionHeight + st::sessionTerminateTop, width()); j.value()->show(); } for (TerminateButtons::iterator i = _terminateButtons.begin(); i != _terminateButtons.cend();) { if (i.value()->y() >= 0) { ++i; } else { delete i.value(); i = _terminateButtons.erase(i); } } resize(width(), _list->isEmpty() ? (st::sessionCurrentHeight + st::noContactsHeight) : (st::sessionCurrentHeight + _list->size() * st::sessionHeight)); update(); } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/sessions_box.h�������������������������������������������0000664�0000000�0000000�00000004221�13262451251�0023431�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "boxes/abstract_box.h" #include "core/single_timer.h" class ConfirmBox; namespace Ui { class IconButton; class LinkButton; } // namespace Ui class SessionsBox : public BoxContent, public RPCSender { Q_OBJECT public: SessionsBox(QWidget*); protected: void prepare() override; void resizeEvent(QResizeEvent *e) override; void paintEvent(QPaintEvent *e) override; private slots: void onOneTerminated(); void onAllTerminated(); void onTerminateAll(); void onShortPollAuthorizations(); void onCheckNewAuthorization(); private: void setLoading(bool loading); struct Data { uint64 hash; int32 activeTime; int32 nameWidth, activeWidth, infoWidth, ipWidth; QString name, active, info, ip; }; using List = QList<Data>; void gotAuthorizations(const MTPaccount_Authorizations &result); bool _loading = false; Data _current; List _list; class Inner; QPointer<Inner> _inner; object_ptr<SingleTimer> _shortPollTimer; mtpRequestId _shortPollRequest = 0; }; // This class is hold in header because it requires Qt preprocessing. class SessionsBox::Inner : public TWidget, public RPCSender { Q_OBJECT public: Inner(QWidget *parent, SessionsBox::List *list, SessionsBox::Data *current); void listUpdated(); protected: void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; signals: void oneTerminated(); void allTerminated(); void terminateAll(); public slots: void onTerminate(); void onTerminateAll(); private: void terminateDone(uint64 hash, const MTPBool &result); bool terminateFail(uint64 hash, const RPCError &error); void terminateAllDone(const MTPBool &res); bool terminateAllFail(const RPCError &error); SessionsBox::List *_list; SessionsBox::Data *_current; typedef QMap<uint64, Ui::IconButton*> TerminateButtons; TerminateButtons _terminateButtons; object_ptr<Ui::LinkButton> _terminateAll; QPointer<ConfirmBox> _terminateBox; }; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/share_box.cpp��������������������������������������������0000664�0000000�0000000�00000066412�13262451251�0023232�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/share_box.h" #include "dialogs/dialogs_indexed_list.h" #include "styles/style_boxes.h" #include "styles/style_history.h" #include "observer_peer.h" #include "lang/lang_keys.h" #include "mainwindow.h" #include "mainwidget.h" #include "base/qthelp_url.h" #include "storage/localstorage.h" #include "boxes/confirm_box.h" #include "apiwrap.h" #include "ui/toast/toast.h" #include "ui/widgets/multi_select.h" #include "ui/widgets/buttons.h" #include "ui/widgets/scroll_area.h" #include "ui/text_options.h" #include "history/history.h" #include "history/history_media_types.h" #include "history/history_message.h" #include "window/themes/window_theme.h" #include "boxes/peer_list_box.h" #include "auth_session.h" #include "messenger.h" ShareBox::ShareBox(QWidget*, CopyCallback &©Callback, SubmitCallback &&submitCallback, FilterCallback &&filterCallback) : _copyCallback(std::move(copyCallback)) , _submitCallback(std::move(submitCallback)) , _filterCallback(std::move(filterCallback)) , _select(this, st::contactsMultiSelect, langFactory(lng_participant_filter)) , _searchTimer(this) { } void ShareBox::prepare() { _select->resizeToWidth(st::boxWideWidth); Ui::SendPendingMoveResizeEvents(_select); setTitle(langFactory(lng_share_title)); _inner = setInnerWidget(object_ptr<Inner>(this, std::move(_filterCallback)), getTopScrollSkip()); connect(_inner, SIGNAL(mustScrollTo(int,int)), this, SLOT(onMustScrollTo(int,int))); createButtons(); setDimensions(st::boxWideWidth, st::boxMaxListHeight); _select->setQueryChangedCallback([this](const QString &query) { onFilterUpdate(query); }); _select->setItemRemovedCallback([this](uint64 itemId) { if (auto peer = App::peerLoaded(itemId)) { _inner->peerUnselected(peer); onSelectedChanged(); update(); } }); _select->setResizedCallback([this] { updateScrollSkips(); }); _select->setSubmittedCallback([this](bool) { _inner->onSelectActive(); }); connect(_inner, SIGNAL(searchByUsername()), this, SLOT(onNeedSearchByUsername())); _inner->setPeerSelectedChangedCallback([this](PeerData *peer, bool checked) { onPeerSelectedChanged(peer, checked); }); _searchTimer->setSingleShot(true); connect(_searchTimer, SIGNAL(timeout()), this, SLOT(onSearchByUsername())); _select->raise(); } int ShareBox::getTopScrollSkip() const { auto result = 0; if (!_select->isHidden()) { result += _select->height(); } return result; } void ShareBox::updateScrollSkips() { setInnerTopSkip(getTopScrollSkip(), true); } bool ShareBox::onSearchByUsername(bool searchCache) { auto query = _select->getQuery(); if (query.isEmpty()) { if (_peopleRequest) { _peopleRequest = 0; } return true; } if (!query.isEmpty()) { if (searchCache) { auto i = _peopleCache.constFind(query); if (i != _peopleCache.cend()) { _peopleQuery = query; _peopleRequest = 0; peopleReceived(i.value(), 0); return true; } } else if (_peopleQuery != query) { _peopleQuery = query; _peopleFull = false; _peopleRequest = MTP::send( MTPcontacts_Search( MTP_string(_peopleQuery), MTP_int(SearchPeopleLimit)), rpcDone(&ShareBox::peopleReceived), rpcFail(&ShareBox::peopleFailed)); _peopleQueries.insert(_peopleRequest, _peopleQuery); } } return false; } void ShareBox::onNeedSearchByUsername() { if (!onSearchByUsername(true)) { _searchTimer->start(AutoSearchTimeout); } } void ShareBox::peopleReceived( const MTPcontacts_Found &result, mtpRequestId requestId) { Expects(result.type() == mtpc_contacts_found); auto query = _peopleQuery; auto i = _peopleQueries.find(requestId); if (i != _peopleQueries.cend()) { query = i.value(); _peopleCache[query] = result; _peopleQueries.erase(i); } if (_peopleRequest == requestId) { switch (result.type()) { case mtpc_contacts_found: { auto &found = result.c_contacts_found(); App::feedUsers(found.vusers); App::feedChats(found.vchats); _inner->peopleReceived( query, found.vmy_results.v, found.vresults.v); } break; } _peopleRequest = 0; } } bool ShareBox::peopleFailed(const RPCError &error, mtpRequestId requestId) { if (MTP::isDefaultHandledError(error)) return false; if (_peopleRequest == requestId) { _peopleRequest = 0; _peopleFull = true; } return true; } void ShareBox::setInnerFocus() { _select->setInnerFocus(); } void ShareBox::resizeEvent(QResizeEvent *e) { BoxContent::resizeEvent(e); _select->resizeToWidth(width()); _select->moveToLeft(0, 0); updateScrollSkips(); _inner->resizeToWidth(width()); } void ShareBox::keyPressEvent(QKeyEvent *e) { auto focused = focusWidget(); if (_select == focused || _select->isAncestorOf(focusWidget())) { if (e->key() == Qt::Key_Up) { _inner->activateSkipColumn(-1); } else if (e->key() == Qt::Key_Down) { _inner->activateSkipColumn(1); } else if (e->key() == Qt::Key_PageUp) { _inner->activateSkipPage(height() - getTopScrollSkip(), -1); } else if (e->key() == Qt::Key_PageDown) { _inner->activateSkipPage(height() - getTopScrollSkip(), 1); } else { BoxContent::keyPressEvent(e); } } else { BoxContent::keyPressEvent(e); } } void ShareBox::updateButtons() { auto hasSelected = _inner->hasSelected(); if (_hasSelected != hasSelected) { _hasSelected = hasSelected; createButtons(); } } void ShareBox::createButtons() { clearButtons(); if (_hasSelected) { addButton(langFactory(lng_share_confirm), [this] { onSubmit(); }); } else if (_copyCallback) { addButton(langFactory(lng_share_copy_link), [this] { onCopyLink(); }); } addButton(langFactory(lng_cancel), [this] { closeBox(); }); } void ShareBox::onFilterUpdate(const QString &query) { onScrollToY(0); _inner->updateFilter(query); } void ShareBox::addPeerToMultiSelect(PeerData *peer, bool skipAnimation) { using AddItemWay = Ui::MultiSelect::AddItemWay; auto addItemWay = skipAnimation ? AddItemWay::SkipAnimation : AddItemWay::Default; _select->addItem( peer->id, peer->isSelf() ? lang(lng_saved_short) : peer->shortName(), st::activeButtonBg, PaintUserpicCallback(peer, true), addItemWay); } void ShareBox::onPeerSelectedChanged(PeerData *peer, bool checked) { if (checked) { addPeerToMultiSelect(peer); _select->clearQuery(); } else { _select->removeItem(peer->id); } onSelectedChanged(); update(); } void ShareBox::onSubmit() { if (_submitCallback) { _submitCallback(_inner->selected()); } } void ShareBox::onCopyLink() { if (_copyCallback) { _copyCallback(); } } void ShareBox::onSelectedChanged() { updateButtons(); update(); } void ShareBox::onMustScrollTo(int top, int bottom) { onScrollToY(top, bottom); //auto scrollTop = scrollArea()->scrollTop(), scrollBottom = scrollTop + scrollArea()->height(); //auto from = scrollTop, to = scrollTop; //if (scrollTop > top) { // to = top; //} else if (scrollBottom < bottom) { // to = bottom - (scrollBottom - scrollTop); //} //if (from != to) { // _scrollAnimation.start([this]() { scrollAnimationCallback(); }, from, to, st::shareScrollDuration, anim::sineInOut); //} } void ShareBox::scrollAnimationCallback() { //auto scrollTop = qRound(_scrollAnimation.current(scrollArea()->scrollTop())); //scrollArea()->scrollToY(scrollTop); } ShareBox::Inner::Inner(QWidget *parent, ShareBox::FilterCallback &&filterCallback) : TWidget(parent) , _filterCallback(std::move(filterCallback)) , _chatsIndexed(std::make_unique<Dialogs::IndexedList>(Dialogs::SortMode::Add)) { _rowsTop = st::shareRowsTop; _rowHeight = st::shareRowHeight; setAttribute(Qt::WA_OpaquePaintEvent); const auto dialogs = App::main()->dialogsList(); if (const auto self = App::self()) { if (_filterCallback(App::self())) { _chatsIndexed->addToEnd(App::history(self)); } } for (const auto row : dialogs->all()) { if (const auto history = row->history()) { if (!history->peer->isSelf() && _filterCallback(history->peer)) { _chatsIndexed->addToEnd(history); } } } _filter = qsl("a"); updateFilter(); using UpdateFlag = Notify::PeerUpdate::Flag; auto observeEvents = UpdateFlag::NameChanged | UpdateFlag::PhotoChanged; subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(observeEvents, [this](const Notify::PeerUpdate &update) { notifyPeerUpdated(update); })); subscribe(Auth().downloaderTaskFinished(), [this] { update(); }); subscribe(Window::Theme::Background(), [this](const Window::Theme::BackgroundUpdate &update) { if (update.paletteChanged()) { invalidateCache(); } }); } void ShareBox::Inner::invalidateCache() { for_const (auto data, _dataMap) { data->checkbox.invalidateCache(); } } void ShareBox::Inner::visibleTopBottomUpdated( int visibleTop, int visibleBottom) { loadProfilePhotos(visibleTop); } void ShareBox::Inner::activateSkipRow(int direction) { activateSkipColumn(direction * _columnCount); } int ShareBox::Inner::displayedChatsCount() const { return _filter.isEmpty() ? _chatsIndexed->size() : (_filtered.size() + d_byUsernameFiltered.size()); } void ShareBox::Inner::activateSkipColumn(int direction) { if (_active < 0) { if (direction > 0) { setActive(0); } return; } auto count = displayedChatsCount(); auto active = _active + direction; if (active < 0) { active = (_active > 0) ? 0 : -1; } if (active >= count) { active = count - 1; } setActive(active); } void ShareBox::Inner::activateSkipPage(int pageHeight, int direction) { activateSkipRow(direction * (pageHeight / _rowHeight)); } void ShareBox::Inner::notifyPeerUpdated(const Notify::PeerUpdate &update) { if (update.flags & Notify::PeerUpdate::Flag::NameChanged) { _chatsIndexed->peerNameChanged( update.peer, update.oldNameFirstLetters); } updateChat(update.peer); } void ShareBox::Inner::updateChat(not_null<PeerData*> peer) { auto i = _dataMap.find(peer); if (i != _dataMap.cend()) { updateChatName(i.value(), peer); repaintChat(peer); } } void ShareBox::Inner::updateChatName( not_null<Chat*> chat, not_null<PeerData*> peer) { const auto text = peer->isSelf() ? lang(lng_saved_messages) : peer->name; chat->name.setText(st::shareNameStyle, text, Ui::NameTextOptions()); } void ShareBox::Inner::repaintChatAtIndex(int index) { if (index < 0) return; auto row = index / _columnCount; auto column = index % _columnCount; update(rtlrect(_rowsLeft + qFloor(column * _rowWidthReal), row * _rowHeight, _rowWidth, _rowHeight, width())); } ShareBox::Inner::Chat *ShareBox::Inner::getChatAtIndex(int index) { if (index < 0) return nullptr; auto row = ([this, index]() -> Dialogs::Row* { if (_filter.isEmpty()) return _chatsIndexed->rowAtY(index, 1); return (index < _filtered.size()) ? _filtered[index] : nullptr; })(); if (row) { return static_cast<Chat*>(row->attached); } if (!_filter.isEmpty()) { index -= _filtered.size(); if (index >= 0 && index < d_byUsernameFiltered.size()) { return d_byUsernameFiltered[index]; } } return nullptr; } void ShareBox::Inner::repaintChat(not_null<PeerData*> peer) { repaintChatAtIndex(chatIndex(peer)); } int ShareBox::Inner::chatIndex(not_null<PeerData*> peer) const { int index = 0; if (_filter.isEmpty()) { for (const auto row : _chatsIndexed->all()) { if (const auto history = row->history()) { if (history->peer == peer) { return index; } } ++index; } } else { for (const auto row : _filtered) { if (const auto history = row->history()) { if (history->peer == peer) { return index; } } ++index; } for (const auto row : d_byUsernameFiltered) { if (row->peer == peer) { return index; } ++index; } } return -1; } void ShareBox::Inner::loadProfilePhotos(int yFrom) { if (!parentWidget()) return; if (yFrom < 0) { yFrom = 0; } if (auto part = (yFrom % _rowHeight)) { yFrom -= part; } int yTo = yFrom + parentWidget()->height() * 5 * _columnCount; if (!yTo) { return; } yFrom *= _columnCount; yTo *= _columnCount; Auth().downloader().clearPriorities(); if (_filter.isEmpty()) { if (!_chatsIndexed->isEmpty()) { auto i = _chatsIndexed->cfind(yFrom, _rowHeight); for (auto end = _chatsIndexed->cend(); i != end; ++i) { if (((*i)->pos() * _rowHeight) >= yTo) { break; } (*i)->entry()->loadUserpic(); } } } else if (!_filtered.isEmpty()) { int from = yFrom / _rowHeight; if (from < 0) from = 0; if (from < _filtered.size()) { int to = (yTo / _rowHeight) + 1; if (to > _filtered.size()) to = _filtered.size(); for (; from < to; ++from) { _filtered[from]->entry()->loadUserpic(); } } } } ShareBox::Inner::Chat *ShareBox::Inner::getChat(Dialogs::Row *row) { Expects(row->history() != nullptr); auto data = static_cast<Chat*>(row->attached); if (!data) { auto peer = row->history()->peer; auto i = _dataMap.constFind(peer); if (i == _dataMap.cend()) { data = new Chat(peer, [this, peer] { repaintChat(peer); }); _dataMap.insert(peer, data); updateChatName(data, peer); } else { data = i.value(); } row->attached = data; } return data; } void ShareBox::Inner::setActive(int active) { if (active != _active) { auto changeNameFg = [this](int index, float64 from, float64 to) { if (auto chat = getChatAtIndex(index)) { chat->nameActive.start([this, peer = chat->peer] { repaintChat(peer); }, from, to, st::shareActivateDuration); } }; changeNameFg(_active, 1., 0.); _active = active; changeNameFg(_active, 0., 1.); } auto y = (_active < _columnCount) ? 0 : (_rowsTop + ((_active / _columnCount) * _rowHeight)); emit mustScrollTo(y, y + _rowHeight); } void ShareBox::Inner::paintChat( Painter &p, TimeMs ms, not_null<Chat*> chat, int index) { auto x = _rowsLeft + qFloor((index % _columnCount) * _rowWidthReal); auto y = _rowsTop + (index / _columnCount) * _rowHeight; auto outerWidth = width(); auto photoLeft = (_rowWidth - (st::sharePhotoCheckbox.imageRadius * 2)) / 2; auto photoTop = st::sharePhotoTop; chat->checkbox.paint(p, ms, x + photoLeft, y + photoTop, outerWidth); auto nameActive = chat->nameActive.current(ms, (index == _active) ? 1. : 0.); p.setPen(anim::pen(st::shareNameFg, st::shareNameActiveFg, nameActive)); auto nameWidth = (_rowWidth - st::shareColumnSkip); auto nameLeft = st::shareColumnSkip / 2; auto nameTop = photoTop + st::sharePhotoCheckbox.imageRadius * 2 + st::shareNameTop; chat->name.drawLeftElided(p, x + nameLeft, y + nameTop, nameWidth, outerWidth, 2, style::al_top, 0, -1, 0, true); } ShareBox::Inner::Chat::Chat(PeerData *peer, base::lambda<void()> updateCallback) : peer(peer) , checkbox(st::sharePhotoCheckbox, updateCallback, PaintUserpicCallback(peer, true)) , name(st::sharePhotoCheckbox.imageRadius * 2) { } void ShareBox::Inner::paintEvent(QPaintEvent *e) { Painter p(this); auto ms = getms(); auto r = e->rect(); p.setClipRect(r); p.fillRect(r, st::boxBg); auto yFrom = r.y(), yTo = r.y() + r.height(); auto rowFrom = yFrom / _rowHeight; auto rowTo = (yTo + _rowHeight - 1) / _rowHeight; auto indexFrom = rowFrom * _columnCount; auto indexTo = rowTo * _columnCount; if (_filter.isEmpty()) { if (!_chatsIndexed->isEmpty()) { auto i = _chatsIndexed->cfind(indexFrom, 1); for (auto end = _chatsIndexed->cend(); i != end; ++i) { if (indexFrom >= indexTo) { break; } paintChat(p, ms, getChat(*i), indexFrom); ++indexFrom; } } else { p.setFont(st::noContactsFont); p.setPen(st::noContactsColor); p.drawText( rect().marginsRemoved(st::boxPadding), lang(lng_bot_no_chats), style::al_center); } } else { if (_filtered.isEmpty() && _byUsernameFiltered.empty() && !_searching) { p.setFont(st::noContactsFont); p.setPen(st::noContactsColor); p.drawText( rect().marginsRemoved(st::boxPadding), lang(lng_bot_chats_not_found), style::al_center); } else { auto filteredSize = _filtered.size(); if (filteredSize) { if (indexFrom < 0) indexFrom = 0; while (indexFrom < indexTo) { if (indexFrom >= _filtered.size()) { break; } paintChat(p, ms, getChat(_filtered[indexFrom]), indexFrom); ++indexFrom; } indexFrom -= filteredSize; indexTo -= filteredSize; } if (!_byUsernameFiltered.empty()) { if (indexFrom < 0) indexFrom = 0; while (indexFrom < indexTo) { if (indexFrom >= d_byUsernameFiltered.size()) { break; } paintChat(p, ms, d_byUsernameFiltered[indexFrom], filteredSize + indexFrom); ++indexFrom; } } } } } void ShareBox::Inner::enterEventHook(QEvent *e) { setMouseTracking(true); } void ShareBox::Inner::leaveEventHook(QEvent *e) { setMouseTracking(false); } void ShareBox::Inner::mouseMoveEvent(QMouseEvent *e) { updateUpon(e->pos()); setCursor((_upon >= 0) ? style::cur_pointer : style::cur_default); } void ShareBox::Inner::updateUpon(const QPoint &pos) { auto x = pos.x(), y = pos.y(); auto row = (y - _rowsTop) / _rowHeight; auto column = qFloor((x - _rowsLeft) / _rowWidthReal); auto left = _rowsLeft + qFloor(column * _rowWidthReal) + st::shareColumnSkip / 2; auto top = _rowsTop + row * _rowHeight + st::sharePhotoTop; auto xupon = (x >= left) && (x < left + (_rowWidth - st::shareColumnSkip)); auto yupon = (y >= top) && (y < top + st::sharePhotoCheckbox.imageRadius * 2 + st::shareNameTop + st::shareNameStyle.font->height * 2); auto upon = (xupon && yupon) ? (row * _columnCount + column) : -1; if (upon >= displayedChatsCount()) { upon = -1; } _upon = upon; } void ShareBox::Inner::mousePressEvent(QMouseEvent *e) { if (e->button() == Qt::LeftButton) { updateUpon(e->pos()); changeCheckState(getChatAtIndex(_upon)); } } void ShareBox::Inner::onSelectActive() { changeCheckState(getChatAtIndex(_active > 0 ? _active : 0)); } void ShareBox::Inner::resizeEvent(QResizeEvent *e) { _columnSkip = (width() - _columnCount * st::sharePhotoCheckbox.imageRadius * 2) / float64(_columnCount + 1); _rowWidthReal = st::sharePhotoCheckbox.imageRadius * 2 + _columnSkip; _rowsLeft = qFloor(_columnSkip / 2); _rowWidth = qFloor(_rowWidthReal); update(); } void ShareBox::Inner::changeCheckState(Chat *chat) { if (!chat) return; if (!_filter.isEmpty()) { const auto history = App::history(chat->peer); auto row = _chatsIndexed->getRow(history); if (!row) { const auto rowsByLetter = _chatsIndexed->addToEnd(history); const auto it = rowsByLetter.find(0); Assert(it != rowsByLetter.cend()); row = it->second; } chat = getChat(row); if (!chat->checkbox.checked()) { _chatsIndexed->moveToTop(history); } } changePeerCheckState(chat, !chat->checkbox.checked()); } void ShareBox::Inner::peerUnselected(not_null<PeerData*> peer) { if (auto chat = _dataMap.value(peer, nullptr)) { changePeerCheckState(chat, false, ChangeStateWay::SkipCallback); } } void ShareBox::Inner::setPeerSelectedChangedCallback(base::lambda<void(PeerData *peer, bool selected)> callback) { _peerSelectedChangedCallback = std::move(callback); } void ShareBox::Inner::changePeerCheckState( not_null<Chat*> chat, bool checked, ChangeStateWay useCallback) { chat->checkbox.setChecked(checked); if (checked) { _selected.insert(chat->peer); setActive(chatIndex(chat->peer)); } else { _selected.remove(chat->peer); } if (useCallback != ChangeStateWay::SkipCallback && _peerSelectedChangedCallback) { _peerSelectedChangedCallback(chat->peer, checked); } } bool ShareBox::Inner::hasSelected() const { return _selected.size(); } void ShareBox::Inner::updateFilter(QString filter) { _lastQuery = filter.toLower().trimmed(); auto words = TextUtilities::PrepareSearchWords(_lastQuery); filter = words.isEmpty() ? QString() : words.join(' '); if (_filter != filter) { _filter = filter; _byUsernameFiltered.clear(); for (int i = 0, l = d_byUsernameFiltered.size(); i < l; ++i) { delete d_byUsernameFiltered[i]; } d_byUsernameFiltered.clear(); if (_filter.isEmpty()) { refresh(); } else { QStringList::const_iterator fb = words.cbegin(), fe = words.cend(), fi; _filtered.clear(); if (!words.isEmpty()) { const Dialogs::List *toFilter = nullptr; if (!_chatsIndexed->isEmpty()) { for (fi = fb; fi != fe; ++fi) { auto found = _chatsIndexed->filtered(fi->at(0)); if (found->isEmpty()) { toFilter = nullptr; break; } if (!toFilter || toFilter->size() > found->size()) { toFilter = found; } } } if (toFilter) { _filtered.reserve(toFilter->size()); for (const auto row : *toFilter) { auto &nameWords = row->entry()->chatsListNameWords(); auto nb = nameWords.cbegin(), ne = nameWords.cend(), ni = nb; for (fi = fb; fi != fe; ++fi) { auto filterName = *fi; for (ni = nb; ni != ne; ++ni) { if (ni->startsWith(*fi)) { break; } } if (ni == ne) { break; } } if (fi == fe) { _filtered.push_back(row); } } } } refresh(); _searching = true; emit searchByUsername(); } setActive(-1); update(); loadProfilePhotos(0); } } void ShareBox::Inner::peopleReceived( const QString &query, const QVector<MTPPeer> &my, const QVector<MTPPeer> &people) { _lastQuery = query.toLower().trimmed(); if (_lastQuery.at(0) == '@') { _lastQuery = _lastQuery.mid(1); } int32 already = _byUsernameFiltered.size(); _byUsernameFiltered.reserve(already + my.size() + people.size()); d_byUsernameFiltered.reserve(already + my.size() + people.size()); const auto feedList = [&](const QVector<MTPPeer> &list) { for (const auto &mtpPeer : list) { if (const auto peer = App::peerLoaded(peerFromMTP(mtpPeer))) { const auto history = App::historyLoaded(peer); if (!_filterCallback(peer)) { continue; } else if (history && _chatsIndexed->getRow(history)) { continue; } else if (base::contains(_byUsernameFiltered, peer)) { continue; } auto chat = new Chat(peer, [=] { repaintChat(peer); }); updateChatName(chat, peer); _byUsernameFiltered.push_back(peer); d_byUsernameFiltered.push_back(chat); } } }; feedList(my); feedList(people); _searching = false; refresh(); } void ShareBox::Inner::refresh() { auto count = displayedChatsCount(); if (count) { auto rows = (count / _columnCount) + (count % _columnCount ? 1 : 0); resize(width(), _rowsTop + rows * _rowHeight); } else { resize(width(), st::noContactsHeight); } update(); } ShareBox::Inner::~Inner() { for_const (auto chat, _dataMap) { delete chat; } } QVector<PeerData*> ShareBox::Inner::selected() const { QVector<PeerData*> result; result.reserve(_dataMap.size()); for_const (auto chat, _dataMap) { if (chat->checkbox.checked()) { result.push_back(chat->peer); } } return result; } QString AppendShareGameScoreUrl(const QString &url, const FullMsgId &fullId) { auto shareHashData = QByteArray(0x10, Qt::Uninitialized); auto shareHashDataInts = reinterpret_cast<int32*>(shareHashData.data()); auto channel = fullId.channel ? App::channelLoaded(fullId.channel) : static_cast<ChannelData*>(nullptr); auto channelAccessHash = channel ? channel->access : 0ULL; auto channelAccessHashInts = reinterpret_cast<int32*>(&channelAccessHash); shareHashDataInts[0] = Auth().userId(); shareHashDataInts[1] = fullId.channel; shareHashDataInts[2] = fullId.msg; shareHashDataInts[3] = channelAccessHashInts[0]; // Count SHA1() of data. auto key128Size = 0x10; auto shareHashEncrypted = QByteArray(key128Size + shareHashData.size(), Qt::Uninitialized); hashSha1(shareHashData.constData(), shareHashData.size(), shareHashEncrypted.data()); // Mix in channel access hash to the first 64 bits of SHA1 of data. *reinterpret_cast<uint64*>(shareHashEncrypted.data()) ^= *reinterpret_cast<uint64*>(channelAccessHashInts); // Encrypt data. if (!Local::encrypt(shareHashData.constData(), shareHashEncrypted.data() + key128Size, shareHashData.size(), shareHashEncrypted.constData())) { return url; } auto shareHash = shareHashEncrypted.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); auto shareUrl = qsl("tg://share_game_score?hash=") + QString::fromLatin1(shareHash); auto shareComponent = qsl("tgShareScoreUrl=") + qthelp::url_encode(shareUrl); auto hashPosition = url.indexOf('#'); if (hashPosition < 0) { return url + '#' + shareComponent; } auto hash = url.mid(hashPosition + 1); if (hash.indexOf('=') >= 0 || hash.indexOf('?') >= 0) { return url + '&' + shareComponent; } if (!hash.isEmpty()) { return url + '?' + shareComponent; } return url + shareComponent; } void ShareGameScoreByHash(const QString &hash) { auto key128Size = 0x10; auto hashEncrypted = QByteArray::fromBase64(hash.toLatin1(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); if (hashEncrypted.size() <= key128Size || (hashEncrypted.size() % 0x10) != 0) { Ui::show(Box<InformBox>(lang(lng_confirm_phone_link_invalid))); return; } // Decrypt data. auto hashData = QByteArray(hashEncrypted.size() - key128Size, Qt::Uninitialized); if (!Local::decrypt(hashEncrypted.constData() + key128Size, hashData.data(), hashEncrypted.size() - key128Size, hashEncrypted.constData())) { return; } // Count SHA1() of data. char dataSha1[20] = { 0 }; hashSha1(hashData.constData(), hashData.size(), dataSha1); // Mix out channel access hash from the first 64 bits of SHA1 of data. auto channelAccessHash = *reinterpret_cast<uint64*>(hashEncrypted.data()) ^ *reinterpret_cast<uint64*>(dataSha1); // Check next 64 bits of SHA1() of data. auto skipSha1Part = sizeof(channelAccessHash); if (memcmp(dataSha1 + skipSha1Part, hashEncrypted.constData() + skipSha1Part, key128Size - skipSha1Part) != 0) { Ui::show(Box<InformBox>(lang(lng_share_wrong_user))); return; } auto hashDataInts = reinterpret_cast<int32*>(hashData.data()); if (!AuthSession::Exists() || hashDataInts[0] != Auth().userId()) { Ui::show(Box<InformBox>(lang(lng_share_wrong_user))); return; } // Check first 32 bits of channel access hash. auto channelAccessHashInts = reinterpret_cast<int32*>(&channelAccessHash); if (channelAccessHashInts[0] != hashDataInts[3]) { Ui::show(Box<InformBox>(lang(lng_share_wrong_user))); return; } auto channelId = hashDataInts[1]; auto msgId = hashDataInts[2]; if (!channelId && channelAccessHash) { // If there is no channel id, there should be no channel access_hash. Ui::show(Box<InformBox>(lang(lng_share_wrong_user))); return; } if (auto item = App::histItemById(channelId, msgId)) { FastShareMessage(item); } else { auto resolveMessageAndShareScore = [msgId](ChannelData *channel) { Auth().api().requestMessageData(channel, msgId, [](ChannelData *channel, MsgId msgId) { if (auto item = App::histItemById(channel, msgId)) { FastShareMessage(item); } else { Ui::show(Box<InformBox>(lang(lng_edit_deleted))); } }); }; auto channel = channelId ? App::channelLoaded(channelId) : nullptr; if (channel || !channelId) { resolveMessageAndShareScore(channel); } else { auto requestChannelIds = MTP_vector<MTPInputChannel>(1, MTP_inputChannel(MTP_int(channelId), MTP_long(channelAccessHash))); auto requestChannel = MTPchannels_GetChannels(requestChannelIds); MTP::send(requestChannel, rpcDone([channelId, resolveMessageAndShareScore](const MTPmessages_Chats &result) { if (auto chats = Api::getChatsFromMessagesChats(result)) { App::feedChats(*chats); } if (auto channel = App::channelLoaded(channelId)) { resolveMessageAndShareScore(channel); } })); } } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/share_box.h����������������������������������������������0000664�0000000�0000000�00000012543�13262451251�0022673�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "boxes/abstract_box.h" #include "base/observer.h" #include "ui/effects/round_checkbox.h" namespace Dialogs { class Row; class IndexedList; } // namespace Dialogs namespace Notify { struct PeerUpdate; } // namespace Notify namespace Ui { class MultiSelect; } // namespace Ui QString AppendShareGameScoreUrl(const QString &url, const FullMsgId &fullId); void ShareGameScoreByHash(const QString &hash); class ShareBox : public BoxContent, public RPCSender { Q_OBJECT public: using CopyCallback = base::lambda<void()>; using SubmitCallback = base::lambda<void(const QVector<PeerData*> &)>; using FilterCallback = base::lambda<bool(PeerData*)>; ShareBox(QWidget*, CopyCallback &©Callback, SubmitCallback &&submitCallback, FilterCallback &&filterCallback); protected: void prepare() override; void setInnerFocus() override; void resizeEvent(QResizeEvent *e) override; void keyPressEvent(QKeyEvent *e) override; private slots: bool onSearchByUsername(bool searchCache = false); void onNeedSearchByUsername(); void onSubmit(); void onCopyLink(); void onMustScrollTo(int top, int bottom); private: void scrollAnimationCallback(); void onFilterUpdate(const QString &query); void onSelectedChanged(); void updateButtons(); void createButtons(); int getTopScrollSkip() const; void updateScrollSkips(); void addPeerToMultiSelect(PeerData *peer, bool skipAnimation = false); void onPeerSelectedChanged(PeerData *peer, bool checked); void peopleReceived( const MTPcontacts_Found &result, mtpRequestId requestId); bool peopleFailed(const RPCError &error, mtpRequestId requestId); CopyCallback _copyCallback; SubmitCallback _submitCallback; FilterCallback _filterCallback; object_ptr<Ui::MultiSelect> _select; class Inner; QPointer<Inner> _inner; bool _hasSelected = false; object_ptr<QTimer> _searchTimer; QString _peopleQuery; bool _peopleFull = false; mtpRequestId _peopleRequest = 0; using PeopleCache = QMap<QString, MTPcontacts_Found>; PeopleCache _peopleCache; using PeopleQueries = QMap<mtpRequestId, QString>; PeopleQueries _peopleQueries; Animation _scrollAnimation; }; // This class is hold in header because it requires Qt preprocessing. class ShareBox::Inner : public TWidget, public RPCSender, private base::Subscriber { Q_OBJECT public: Inner(QWidget *parent, ShareBox::FilterCallback &&filterCallback); void setPeerSelectedChangedCallback(base::lambda<void(PeerData *peer, bool selected)> callback); void peerUnselected(not_null<PeerData*> peer); QVector<PeerData*> selected() const; bool hasSelected() const; void peopleReceived( const QString &query, const QVector<MTPPeer> &my, const QVector<MTPPeer> &people); void activateSkipRow(int direction); void activateSkipColumn(int direction); void activateSkipPage(int pageHeight, int direction); void updateFilter(QString filter = QString()); ~Inner(); public slots: void onSelectActive(); signals: void mustScrollTo(int ymin, int ymax); void searchByUsername(); protected: void visibleTopBottomUpdated( int visibleTop, int visibleBottom) override; void paintEvent(QPaintEvent *e) override; void enterEventHook(QEvent *e) override; void leaveEventHook(QEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void resizeEvent(QResizeEvent *e) override; private: // Observed notifications. void notifyPeerUpdated(const Notify::PeerUpdate &update); void invalidateCache(); int displayedChatsCount() const; struct Chat { Chat(PeerData *peer, base::lambda<void()> updateCallback); PeerData *peer; Ui::RoundImageCheckbox checkbox; Text name; Animation nameActive; }; void paintChat(Painter &p, TimeMs ms, not_null<Chat*> chat, int index); void updateChat(not_null<PeerData*> peer); void updateChatName(not_null<Chat*> chat, not_null<PeerData*> peer); void repaintChat(not_null<PeerData*> peer); int chatIndex(not_null<PeerData*> peer) const; void repaintChatAtIndex(int index); Chat *getChatAtIndex(int index); void loadProfilePhotos(int yFrom); void changeCheckState(Chat *chat); enum class ChangeStateWay { Default, SkipCallback, }; void changePeerCheckState( not_null<Chat*> chat, bool checked, ChangeStateWay useCallback = ChangeStateWay::Default); Chat *getChat(Dialogs::Row *row); void setActive(int active); void updateUpon(const QPoint &pos); void refresh(); float64 _columnSkip = 0.; float64 _rowWidthReal = 0.; int _rowsLeft = 0; int _rowsTop = 0; int _rowWidth = 0; int _rowHeight = 0; int _columnCount = 4; int _active = -1; int _upon = -1; ShareBox::FilterCallback _filterCallback; std::unique_ptr<Dialogs::IndexedList> _chatsIndexed; QString _filter; using FilteredDialogs = QVector<Dialogs::Row*>; FilteredDialogs _filtered; using DataMap = QMap<PeerData*, Chat*>; DataMap _dataMap; using SelectedChats = OrderedSet<PeerData*>; SelectedChats _selected; base::lambda<void(PeerData *peer, bool selected)> _peerSelectedChangedCallback; ChatData *data(Dialogs::Row *row); bool _searching = false; QString _lastQuery; std::vector<PeerData*> _byUsernameFiltered; std::vector<Chat*> d_byUsernameFiltered; }; �������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/sticker_set_box.cpp��������������������������������������0000664�0000000�0000000�00000034051�13262451251�0024441�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/sticker_set_box.h" #include "data/data_document.h" #include "data/data_session.h" #include "lang/lang_keys.h" #include "mainwidget.h" #include "mainwindow.h" #include "chat_helpers/stickers.h" #include "boxes/confirm_box.h" #include "apiwrap.h" #include "storage/localstorage.h" #include "dialogs/dialogs_layout.h" #include "styles/style_boxes.h" #include "styles/style_chat_helpers.h" #include "ui/widgets/buttons.h" #include "ui/widgets/scroll_area.h" #include "auth_session.h" #include "messenger.h" namespace { constexpr auto kStickersPanelPerRow = 5; } // namespace StickerSetBox::StickerSetBox(QWidget*, const MTPInputStickerSet &set) : _set(set) { } void StickerSetBox::prepare() { setTitle(langFactory(lng_contacts_loading)); _inner = setInnerWidget(object_ptr<Inner>(this, _set), st::stickersScroll); Auth().data().stickersUpdated( ) | rpl::start_with_next( [this] { updateButtons(); }, lifetime()); setDimensions(st::boxWideWidth, st::stickersMaxHeight); onUpdateButtons(); connect(_inner, SIGNAL(updateButtons()), this, SLOT(onUpdateButtons())); _inner->setInstalled( ) | rpl::start_with_next([this](auto &&setId) { Auth().api().stickerSetInstalled(setId); this->closeBox(); }, lifetime()); } void StickerSetBox::onAddStickers() { _inner->install(); } void StickerSetBox::onShareStickers() { auto url = Messenger::Instance().createInternalLinkFull(qsl("addstickers/") + _inner->shortName()); QApplication::clipboard()->setText(url); Ui::show(Box<InformBox>(lang(lng_stickers_copied))); } void StickerSetBox::onUpdateButtons() { setTitle(_inner->title()); updateButtons(); } void StickerSetBox::updateButtons() { clearButtons(); if (_inner->loaded()) { if (_inner->notInstalled()) { addButton(langFactory(lng_stickers_add_pack), [this] { onAddStickers(); }); addButton(langFactory(lng_cancel), [this] { closeBox(); }); } else if (_inner->official()) { addButton(langFactory(lng_about_done), [this] { closeBox(); }); } else { addButton(langFactory(lng_stickers_share_pack), [this] { onShareStickers(); }); addButton(langFactory(lng_cancel), [this] { closeBox(); }); } } else { addButton(langFactory(lng_cancel), [this] { closeBox(); }); } update(); } void StickerSetBox::resizeEvent(QResizeEvent *e) { BoxContent::resizeEvent(e); _inner->resize(width(), _inner->height()); } StickerSetBox::Inner::Inner(QWidget *parent, const MTPInputStickerSet &set) : TWidget(parent) , _input(set) { switch (set.type()) { case mtpc_inputStickerSetID: _setId = set.c_inputStickerSetID().vid.v; _setAccess = set.c_inputStickerSetID().vaccess_hash.v; break; case mtpc_inputStickerSetShortName: _setShortName = qs(set.c_inputStickerSetShortName().vshort_name); break; } MTP::send(MTPmessages_GetStickerSet(_input), rpcDone(&Inner::gotSet), rpcFail(&Inner::failedSet)); Auth().api().updateStickers(); subscribe(Auth().downloaderTaskFinished(), [this] { update(); }); setMouseTracking(true); _previewTimer.setSingleShot(true); connect(&_previewTimer, SIGNAL(timeout()), this, SLOT(onPreview())); } void StickerSetBox::Inner::gotSet(const MTPmessages_StickerSet &set) { _pack.clear(); _emoji.clear(); _packOvers.clear(); _selected = -1; setCursor(style::cur_default); if (set.type() == mtpc_messages_stickerSet) { auto &d = set.c_messages_stickerSet(); auto &v = d.vdocuments.v; _pack.reserve(v.size()); _packOvers.reserve(v.size()); for (int i = 0, l = v.size(); i < l; ++i) { auto doc = Auth().data().document(v.at(i)); if (!doc->sticker()) continue; _pack.push_back(doc); _packOvers.push_back(Animation()); } auto &packs = d.vpacks.v; for (auto i = 0, l = packs.size(); i != l; ++i) { if (packs.at(i).type() != mtpc_stickerPack) continue; auto &pack = packs.at(i).c_stickerPack(); if (auto emoji = Ui::Emoji::Find(qs(pack.vemoticon))) { emoji = emoji->original(); auto &stickers = pack.vdocuments.v; Stickers::Pack p; p.reserve(stickers.size()); for (auto j = 0, c = stickers.size(); j != c; ++j) { auto doc = Auth().data().document(stickers[j].v); if (!doc || !doc->sticker()) continue; p.push_back(doc); } _emoji.insert(emoji, p); } } if (d.vset.type() == mtpc_stickerSet) { auto &s = d.vset.c_stickerSet(); _setTitle = Stickers::GetSetTitle(s); _setShortName = qs(s.vshort_name); _setId = s.vid.v; _setAccess = s.vaccess_hash.v; _setCount = s.vcount.v; _setHash = s.vhash.v; _setFlags = s.vflags.v; _setInstallDate = s.has_installed_date() ? s.vinstalled_date.v : TimeId(0); auto &sets = Auth().data().stickerSetsRef(); auto it = sets.find(_setId); if (it != sets.cend()) { auto clientFlags = it->flags & (MTPDstickerSet_ClientFlag::f_featured | MTPDstickerSet_ClientFlag::f_not_loaded | MTPDstickerSet_ClientFlag::f_unread | MTPDstickerSet_ClientFlag::f_special); _setFlags |= clientFlags; it->flags = _setFlags; it->installDate = _setInstallDate; it->stickers = _pack; it->emoji = _emoji; } } } if (_pack.isEmpty()) { Ui::show(Box<InformBox>(lang(lng_stickers_not_found))); } else { int32 rows = _pack.size() / kStickersPanelPerRow + ((_pack.size() % kStickersPanelPerRow) ? 1 : 0); resize(st::stickersPadding.left() + kStickersPanelPerRow * st::stickersSize.width(), st::stickersPadding.top() + rows * st::stickersSize.height() + st::stickersPadding.bottom()); } _loaded = true; updateSelected(); emit updateButtons(); } bool StickerSetBox::Inner::failedSet(const RPCError &error) { if (MTP::isDefaultHandledError(error)) return false; _loaded = true; Ui::show(Box<InformBox>(lang(lng_stickers_not_found))); return true; } void StickerSetBox::Inner::installDone(const MTPmessages_StickerSetInstallResult &result) { auto &sets = Auth().data().stickerSetsRef(); bool wasArchived = (_setFlags & MTPDstickerSet::Flag::f_archived); if (wasArchived) { auto index = Auth().data().archivedStickerSetsOrderRef().indexOf(_setId); if (index >= 0) { Auth().data().archivedStickerSetsOrderRef().removeAt(index); } } _setInstallDate = unixtime(); _setFlags &= ~MTPDstickerSet::Flag::f_archived; _setFlags |= MTPDstickerSet::Flag::f_installed_date; auto it = sets.find(_setId); if (it == sets.cend()) { it = sets.insert( _setId, Stickers::Set( _setId, _setAccess, _setTitle, _setShortName, _setCount, _setHash, _setFlags, _setInstallDate)); } else { it->flags = _setFlags; it->installDate = _setInstallDate; } it->stickers = _pack; it->emoji = _emoji; auto &order = Auth().data().stickerSetsOrderRef(); int insertAtIndex = 0, currentIndex = order.indexOf(_setId); if (currentIndex != insertAtIndex) { if (currentIndex > 0) { order.removeAt(currentIndex); } order.insert(insertAtIndex, _setId); } auto custom = sets.find(Stickers::CustomSetId); if (custom != sets.cend()) { for_const (auto sticker, _pack) { int removeIndex = custom->stickers.indexOf(sticker); if (removeIndex >= 0) custom->stickers.removeAt(removeIndex); } if (custom->stickers.isEmpty()) { sets.erase(custom); } } if (result.type() == mtpc_messages_stickerSetInstallResultArchive) { Stickers::ApplyArchivedResult(result.c_messages_stickerSetInstallResultArchive()); } else { if (wasArchived) { Local::writeArchivedStickers(); } Local::writeInstalledStickers(); Auth().data().notifyStickersUpdated(); } _setInstalled.fire_copy(_setId); } bool StickerSetBox::Inner::installFail(const RPCError &error) { if (MTP::isDefaultHandledError(error)) return false; Ui::show(Box<InformBox>(lang(lng_stickers_not_found))); return true; } void StickerSetBox::Inner::mousePressEvent(QMouseEvent *e) { int index = stickerFromGlobalPos(e->globalPos()); if (index >= 0 && index < _pack.size()) { _previewTimer.start(QApplication::startDragTime()); } } void StickerSetBox::Inner::mouseMoveEvent(QMouseEvent *e) { updateSelected(); if (_previewShown >= 0) { int index = stickerFromGlobalPos(e->globalPos()); if (index >= 0 && index < _pack.size() && index != _previewShown) { _previewShown = index; Ui::showMediaPreview(_pack.at(_previewShown)); } } } void StickerSetBox::Inner::leaveEventHook(QEvent *e) { setSelected(-1); } void StickerSetBox::Inner::mouseReleaseEvent(QMouseEvent *e) { if (_previewShown >= 0) { _previewShown = -1; return; } if (_previewTimer.isActive()) { _previewTimer.stop(); int index = stickerFromGlobalPos(e->globalPos()); if (index >= 0 && index < _pack.size() && !isMasksSet()) { if (auto main = App::main()) { if (main->onSendSticker(_pack.at(index))) { Ui::hideSettingsAndLayer(); } } } } } void StickerSetBox::Inner::updateSelected() { auto selected = stickerFromGlobalPos(QCursor::pos()); setSelected(isMasksSet() ? -1 : selected); } void StickerSetBox::Inner::setSelected(int selected) { if (_selected != selected) { startOverAnimation(_selected, 1., 0.); _selected = selected; startOverAnimation(_selected, 0., 1.); setCursor(_selected >= 0 ? style::cur_pointer : style::cur_default); } } void StickerSetBox::Inner::startOverAnimation(int index, float64 from, float64 to) { if (index >= 0 && index < _packOvers.size()) { _packOvers[index].start([this, index] { int row = index / kStickersPanelPerRow; int column = index % kStickersPanelPerRow; int left = st::stickersPadding.left() + column * st::stickersSize.width(); int top = st::stickersPadding.top() + row * st::stickersSize.height(); rtlupdate(left, top, st::stickersSize.width(), st::stickersSize.height()); }, from, to, st::emojiPanDuration); } } void StickerSetBox::Inner::onPreview() { int index = stickerFromGlobalPos(QCursor::pos()); if (index >= 0 && index < _pack.size()) { _previewShown = index; Ui::showMediaPreview(_pack.at(_previewShown)); } } int32 StickerSetBox::Inner::stickerFromGlobalPos(const QPoint &p) const { QPoint l(mapFromGlobal(p)); if (rtl()) l.setX(width() - l.x()); int32 row = (l.y() >= st::stickersPadding.top()) ? qFloor((l.y() - st::stickersPadding.top()) / st::stickersSize.height()) : -1; int32 col = (l.x() >= st::stickersPadding.left()) ? qFloor((l.x() - st::stickersPadding.left()) / st::stickersSize.width()) : -1; if (row >= 0 && col >= 0 && col < kStickersPanelPerRow) { int32 result = row * kStickersPanelPerRow + col; return (result < _pack.size()) ? result : -1; } return -1; } void StickerSetBox::Inner::paintEvent(QPaintEvent *e) { QRect r(e->rect()); Painter p(this); if (_pack.isEmpty()) return; auto ms = getms(); int32 rows = _pack.size() / kStickersPanelPerRow + ((_pack.size() % kStickersPanelPerRow) ? 1 : 0); int32 from = qFloor(e->rect().top() / st::stickersSize.height()), to = qFloor(e->rect().bottom() / st::stickersSize.height()) + 1; for (int32 i = from; i < to; ++i) { for (int32 j = 0; j < kStickersPanelPerRow; ++j) { int32 index = i * kStickersPanelPerRow + j; if (index >= _pack.size()) break; Assert(index < _packOvers.size()); DocumentData *doc = _pack.at(index); QPoint pos(st::stickersPadding.left() + j * st::stickersSize.width(), st::stickersPadding.top() + i * st::stickersSize.height()); if (auto over = _packOvers[index].current(ms, (index == _selected) ? 1. : 0.)) { p.setOpacity(over); QPoint tl(pos); if (rtl()) tl.setX(width() - tl.x() - st::stickersSize.width()); App::roundRect(p, QRect(tl, st::stickersSize), st::emojiPanHover, StickerHoverCorners); p.setOpacity(1); } bool goodThumb = !doc->thumb->isNull() && ((doc->thumb->width() >= 128) || (doc->thumb->height() >= 128)); if (goodThumb) { doc->thumb->load(); } else { if (doc->status == FileReady) { doc->automaticLoad(0); } if (doc->sticker()->img->isNull() && doc->loaded(DocumentData::FilePathResolveChecked)) { doc->sticker()->img = doc->data().isEmpty() ? ImagePtr(doc->filepath()) : ImagePtr(doc->data()); } } float64 coef = qMin((st::stickersSize.width() - st::buttonRadius * 2) / float64(doc->dimensions.width()), (st::stickersSize.height() - st::buttonRadius * 2) / float64(doc->dimensions.height())); if (coef > 1) coef = 1; int32 w = qRound(coef * doc->dimensions.width()), h = qRound(coef * doc->dimensions.height()); if (w < 1) w = 1; if (h < 1) h = 1; QPoint ppos = pos + QPoint((st::stickersSize.width() - w) / 2, (st::stickersSize.height() - h) / 2); if (goodThumb) { p.drawPixmapLeft(ppos, width(), doc->thumb->pix(w, h)); } else if (!doc->sticker()->img->isNull()) { p.drawPixmapLeft(ppos, width(), doc->sticker()->img->pix(w, h)); } } } } bool StickerSetBox::Inner::loaded() const { return _loaded && !_pack.isEmpty(); } bool StickerSetBox::Inner::notInstalled() const { if (!_loaded) { return false; } const auto it = Auth().data().stickerSets().constFind(_setId); if ((it == Auth().data().stickerSets().cend()) || !(it->flags & MTPDstickerSet::Flag::f_installed_date) || (it->flags & MTPDstickerSet::Flag::f_archived)) { return _pack.size() > 0; } return false; } bool StickerSetBox::Inner::official() const { return _loaded && _setShortName.isEmpty(); } base::lambda<TextWithEntities()> StickerSetBox::Inner::title() const { auto text = TextWithEntities { _setTitle }; if (_loaded) { if (_pack.isEmpty()) { return [] { return TextWithEntities { lang(lng_attach_failed), EntitiesInText() }; }; } else { TextUtilities::ParseEntities(text, TextParseMentions); } } else { return [] { return TextWithEntities { lang(lng_contacts_loading), EntitiesInText() }; }; } return [text] { return text; }; } QString StickerSetBox::Inner::shortName() const { return _setShortName; } void StickerSetBox::Inner::install() { if (isMasksSet()) { Ui::show( Box<InformBox>(lang(lng_stickers_masks_pack)), LayerOption::KeepOther); return; } if (_installRequest) return; _installRequest = MTP::send(MTPmessages_InstallStickerSet(_input, MTP_bool(false)), rpcDone(&Inner::installDone), rpcFail(&Inner::installFail)); } StickerSetBox::Inner::~Inner() { } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/sticker_set_box.h����������������������������������������0000664�0000000�0000000�00000004777�13262451251�0024122�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "boxes/abstract_box.h" #include "chat_helpers/stickers.h" class ConfirmBox; namespace Ui { class PlainShadow; } // namespace Ui class StickerSetBox : public BoxContent, public RPCSender { Q_OBJECT public: StickerSetBox(QWidget*, const MTPInputStickerSet &set); protected: void prepare() override; void resizeEvent(QResizeEvent *e) override; private slots: void onAddStickers(); void onShareStickers(); void onUpdateButtons(); private: void updateButtons(); MTPInputStickerSet _set; class Inner; QPointer<Inner> _inner; }; // This class is hold in header because it requires Qt preprocessing. class StickerSetBox::Inner : public TWidget, public RPCSender, private base::Subscriber { Q_OBJECT public: Inner(QWidget *parent, const MTPInputStickerSet &set); bool loaded() const; bool notInstalled() const; bool official() const; base::lambda<TextWithEntities()> title() const; QString shortName() const; void install(); auto setInstalled() const { return _setInstalled.events(); } ~Inner(); protected: void mousePressEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void paintEvent(QPaintEvent *e) override; void leaveEventHook(QEvent *e) override; private slots: void onPreview(); signals: void updateButtons(); private: void updateSelected(); void setSelected(int selected); void startOverAnimation(int index, float64 from, float64 to); int stickerFromGlobalPos(const QPoint &p) const; void gotSet(const MTPmessages_StickerSet &set); bool failedSet(const RPCError &error); void installDone(const MTPmessages_StickerSetInstallResult &result); bool installFail(const RPCError &error); bool isMasksSet() const { return (_setFlags & MTPDstickerSet::Flag::f_masks); } std::vector<Animation> _packOvers; Stickers::Pack _pack; Stickers::ByEmojiMap _emoji; bool _loaded = false; uint64 _setId = 0; uint64 _setAccess = 0; QString _setTitle, _setShortName; int _setCount = 0; int32 _setHash = 0; MTPDstickerSet::Flags _setFlags = 0; TimeId _setInstallDate = TimeId(0); MTPInputStickerSet _input; mtpRequestId _installRequest = 0; int _selected = -1; QTimer _previewTimer; int _previewShown = -1; rpl::event_stream<uint64> _setInstalled; }; �tdesktop-1.2.17/Telegram/SourceFiles/boxes/stickers_box.cpp�����������������������������������������0000664�0000000�0000000�00000152755�13262451251�0023765�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "stickers_box.h" #include "data/data_document.h" #include "data/data_session.h" #include "lang/lang_keys.h" #include "mainwidget.h" #include "chat_helpers/stickers.h" #include "boxes/confirm_box.h" #include "boxes/sticker_set_box.h" #include "apiwrap.h" #include "storage/localstorage.h" #include "dialogs/dialogs_layout.h" #include "styles/style_boxes.h" #include "styles/style_chat_helpers.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" #include "ui/widgets/scroll_area.h" #include "ui/widgets/shadow.h" #include "ui/effects/ripple_animation.h" #include "ui/effects/slide_animation.h" #include "ui/widgets/discrete_sliders.h" #include "ui/widgets/input_fields.h" #include "auth_session.h" #include "messenger.h" namespace { constexpr auto kArchivedLimitFirstRequest = 10; constexpr auto kArchivedLimitPerPage = 30; constexpr auto kHandleMegagroupSetAddressChangeTimeout = TimeMs(1000); } // namespace int stickerPacksCount(bool includeArchivedOfficial) { auto result = 0; auto &order = Auth().data().stickerSetsOrder(); auto &sets = Auth().data().stickerSets(); for (auto i = 0, l = order.size(); i < l; ++i) { auto it = sets.constFind(order.at(i)); if (it != sets.cend()) { if (!(it->flags & MTPDstickerSet::Flag::f_archived) || ((it->flags & MTPDstickerSet::Flag::f_official) && includeArchivedOfficial)) { ++result; } } } return result; } class StickersBox::CounterWidget : public Ui::RpWidget { public: CounterWidget(QWidget *parent); protected: void paintEvent(QPaintEvent *e) override; private: void setCounter(int counter); QString _text; Dialogs::Layout::UnreadBadgeStyle _st; }; StickersBox::CounterWidget::CounterWidget(QWidget *parent) : RpWidget(parent) { setAttribute(Qt::WA_TransparentForMouseEvents); _st.sizeId = Dialogs::Layout::UnreadBadgeInStickersBox; _st.textTop = st::stickersFeaturedBadgeTextTop; _st.size = st::stickersFeaturedBadgeSize; _st.padding = st::stickersFeaturedBadgePadding; _st.font = st::stickersFeaturedBadgeFont; Auth().data().featuredStickerSetsUnreadCountValue( ) | rpl::start_with_next([this](int count) { setCounter(count); update(); }, lifetime()); } void StickersBox::CounterWidget::setCounter(int counter) { _text = (counter > 0) ? QString::number(counter) : QString(); auto dummy = QImage(1, 1, QImage::Format_ARGB32_Premultiplied); Painter p(&dummy); auto newWidth = 0; Dialogs::Layout::paintUnreadCount(p, _text, 0, 0, _st, &newWidth); resize(newWidth, st::stickersFeaturedBadgeSize); } void StickersBox::CounterWidget::paintEvent(QPaintEvent *e) { Painter p(this); if (!_text.isEmpty()) { auto unreadRight = rtl() ? 0 : width(); auto unreadTop = 0; Dialogs::Layout::paintUnreadCount(p, _text, unreadRight, unreadTop, _st); } } template <typename ...Args> StickersBox::Tab::Tab(int index, Args&&... args) : _index(index) , _widget(std::forward<Args>(args)...) , _weak(_widget) { } object_ptr<StickersBox::Inner> StickersBox::Tab::takeWidget() { return std::move(_widget); } void StickersBox::Tab::returnWidget(object_ptr<Inner> widget) { _widget = std::move(widget); Assert(_widget == _weak); } void StickersBox::Tab::saveScrollTop() { _scrollTop = widget()->getVisibleTop(); } StickersBox::StickersBox(QWidget*, Section section) : _tabs(this, st::stickersTabs) , _unreadBadge(this) , _section(section) , _installed(0, this, Section::Installed) , _featured(1, this, Section::Featured) , _archived(2, this, Section::Archived) { _tabs->setRippleTopRoundRadius(st::boxRadius); } StickersBox::StickersBox(QWidget*, not_null<ChannelData*> megagroup) : _section(Section::Installed) , _installed(0, this, megagroup) , _megagroupSet(megagroup) { subscribe(_installed.widget()->scrollToY, [this](int y) { onScrollToY(y); }); } void StickersBox::getArchivedDone(uint64 offsetId, const MTPmessages_ArchivedStickers &result) { _archivedRequestId = 0; _archivedLoaded = true; if (result.type() != mtpc_messages_archivedStickers) { return; } auto &stickers = result.c_messages_archivedStickers(); auto &archived = Auth().data().archivedStickerSetsOrderRef(); if (offsetId) { auto index = archived.indexOf(offsetId); if (index >= 0) { archived = archived.mid(0, index + 1); } } else { archived.clear(); } auto addedSet = false; auto changedSets = false; for_const (const auto &stickerSet, stickers.vsets.v) { const MTPDstickerSet *setData = nullptr; switch (stickerSet.type()) { case mtpc_stickerSetCovered: { auto &d = stickerSet.c_stickerSetCovered(); if (d.vset.type() == mtpc_stickerSet) { setData = &d.vset.c_stickerSet(); } } break; case mtpc_stickerSetMultiCovered: { auto &d = stickerSet.c_stickerSetMultiCovered(); if (d.vset.type() == mtpc_stickerSet) { setData = &d.vset.c_stickerSet(); } } break; } if (!setData) continue; if (auto set = Stickers::FeedSet(*setData)) { auto index = archived.indexOf(set->id); if (archived.isEmpty() || index != archived.size() - 1) { changedSets = true; if (index < archived.size() - 1) { archived.removeAt(index); } archived.push_back(set->id); } if (_archived.widget()->appendSet(*set)) { addedSet = true; if (set->stickers.isEmpty() || (set->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) { Auth().api().scheduleStickerSetRequest(set->id, set->access); } } } } if (addedSet) { _archived.widget()->updateSize(); } else { _allArchivedLoaded = stickers.vsets.v.isEmpty() || (!changedSets && offsetId != 0); if (changedSets) { loadMoreArchived(); } } refreshTabs(); _someArchivedLoaded = true; if (_section == Section::Archived && addedSet) { Auth().api().requestStickerSets(); } } void StickersBox::prepare() { if (_section == Section::Installed) { if (_tabs) { Local::readArchivedStickers(); } else { setTitle(langFactory(lng_stickers_group_set)); } } else if (_section == Section::Archived) { requestArchivedSets(); } if (_tabs) { if (Auth().data().archivedStickerSetsOrder().isEmpty()) { preloadArchivedSets(); } setNoContentMargin(true); _tabs->sectionActivated( ) | rpl::start_with_next( [this] { switchTab(); }, lifetime()); refreshTabs(); } if (_installed.widget() && _section != Section::Installed) _installed.widget()->hide(); if (_featured.widget() && _section != Section::Featured) _featured.widget()->hide(); if (_archived.widget() && _section != Section::Archived) _archived.widget()->hide(); if (_featured.widget()) { _featured.widget()->setInstallSetCallback([this](uint64 setId) { installSet(setId); }); } if (_archived.widget()) { _archived.widget()->setInstallSetCallback([this](uint64 setId) { installSet(setId); }); _archived.widget()->setLoadMoreCallback([this] { loadMoreArchived(); }); } if (_megagroupSet) { addButton(langFactory(lng_settings_save), [this] { _installed.widget()->saveGroupSet(); closeBox(); }); addButton(langFactory(lng_cancel), [this] { closeBox(); }); } else { addButton(langFactory(lng_about_done), [this] { closeBox(); }); } if (_section == Section::Installed) { _tab = &_installed; } else if (_section == Section::Archived) { _tab = &_archived; } else { // _section == Section::Featured _tab = &_featured; } setInnerWidget(_tab->takeWidget(), getTopSkip()); setDimensions(st::boxWideWidth, st::boxMaxListHeight); Auth().data().stickersUpdated( ) | rpl::start_with_next( [this] { handleStickersUpdated(); }, lifetime()); Auth().api().updateStickers(); if (_installed.widget()) { connect(_installed.widget(), SIGNAL(draggingScrollDelta(int)), this, SLOT(onDraggingScrollDelta(int))); if (!_megagroupSet) { subscribe(boxClosing, [this] { saveChanges(); }); } } if (_tabs) { _tabs->raise(); _unreadBadge->raise(); } rebuildList(); } void StickersBox::refreshTabs() { if (!_tabs) return; _tabIndices.clear(); auto sections = QStringList(); sections.push_back(lang(lng_stickers_installed_tab).toUpper()); _tabIndices.push_back(Section::Installed); if (!Auth().data().featuredStickerSetsOrder().isEmpty()) { sections.push_back(lang(lng_stickers_featured_tab).toUpper()); _tabIndices.push_back(Section::Featured); } if (!Auth().data().archivedStickerSetsOrder().isEmpty()) { sections.push_back(lang(lng_stickers_archived_tab).toUpper()); _tabIndices.push_back(Section::Archived); } _tabs->setSections(sections); if ((_tab == &_archived && !_tabIndices.contains(Section::Archived)) || (_tab == &_featured && !_tabIndices.contains(Section::Featured))) { switchTab(); } else if (_tab == &_archived) { _tabs->setActiveSectionFast(_tabIndices.indexOf(Section::Archived)); } else if (_tab == &_featured) { _tabs->setActiveSectionFast(_tabIndices.indexOf(Section::Featured)); } updateTabsGeometry(); } void StickersBox::loadMoreArchived() { if (_section != Section::Archived || _allArchivedLoaded || _archivedRequestId) { return; } uint64 lastId = 0; for (auto setIt = Auth().data().archivedStickerSetsOrder().cend(), e = Auth().data().archivedStickerSetsOrder().cbegin(); setIt != e;) { --setIt; auto it = Auth().data().stickerSets().constFind(*setIt); if (it != Auth().data().stickerSets().cend()) { if (it->flags & MTPDstickerSet::Flag::f_archived) { lastId = it->id; break; } } } _archivedRequestId = MTP::send(MTPmessages_GetArchivedStickers(MTP_flags(0), MTP_long(lastId), MTP_int(kArchivedLimitPerPage)), rpcDone(&StickersBox::getArchivedDone, lastId)); } void StickersBox::paintEvent(QPaintEvent *e) { BoxContent::paintEvent(e); Painter p(this); if (_slideAnimation) { _slideAnimation->paintFrame(p, 0, getTopSkip(), width(), getms()); if (!_slideAnimation->animating()) { _slideAnimation.reset(); setInnerVisible(true); update(); } } } void StickersBox::updateTabsGeometry() { if (!_tabs) return; _tabs->resizeToWidth(_tabIndices.size() * width() / 3); _unreadBadge->setVisible(_tabIndices.contains(Section::Featured)); setInnerTopSkip(getTopSkip()); auto featuredLeft = width() / 3; auto featuredRight = 2 * width() / 3; auto featuredTextWidth = st::stickersTabs.labelFont->width(lang(lng_stickers_featured_tab).toUpper()); auto featuredTextRight = featuredLeft + (featuredRight - featuredLeft - featuredTextWidth) / 2 + featuredTextWidth; auto unreadBadgeLeft = featuredTextRight - st::stickersFeaturedBadgeSkip; auto unreadBadgeTop = st::stickersFeaturedBadgeTop; if (unreadBadgeLeft + _unreadBadge->width() > featuredRight) { unreadBadgeLeft = featuredRight - _unreadBadge->width(); } _unreadBadge->moveToLeft(unreadBadgeLeft, unreadBadgeTop); _tabs->moveToLeft(0, 0); } int StickersBox::getTopSkip() const { return _tabs ? (_tabs->height() - st::lineWidth) : 0; } void StickersBox::switchTab() { if (!_tabs) return; auto tab = _tabs->activeSection(); Assert(tab >= 0 && tab < _tabIndices.size()); auto newSection = _tabIndices[tab]; auto newTab = _tab; if (newSection == Section::Installed) { newTab = &_installed; } else if (newSection == Section::Featured) { newTab = &_featured; } else if (newSection == Section::Archived) { newTab = &_archived; requestArchivedSets(); } if (_tab == newTab) { return; } if (_tab == &_installed) { _localOrder = _tab->widget()->getFullOrder(); _localRemoved = _tab->widget()->getRemovedSets(); } auto wasCache = grabContentCache(); auto wasIndex = _tab->index(); _tab->saveScrollTop(); auto widget = takeInnerWidget<Inner>(); widget->setParent(this); widget->hide(); _tab->returnWidget(std::move(widget)); _tab = newTab; _section = newSection; setInnerWidget(_tab->takeWidget(), getTopSkip()); _tabs->raise(); _unreadBadge->raise(); _tab->widget()->show(); rebuildList(); onScrollToY(_tab->getScrollTop()); setInnerVisible(true); auto nowCache = grabContentCache(); auto nowIndex = _tab->index(); _slideAnimation = std::make_unique<Ui::SlideAnimation>(); _slideAnimation->setSnapshots(std::move(wasCache), std::move(nowCache)); auto slideLeft = wasIndex > nowIndex; _slideAnimation->start(slideLeft, [this] { update(); }, st::slideDuration); setInnerVisible(false); setFocus(); update(); } QPixmap StickersBox::grabContentCache() { _tabs->hide(); auto result = grabInnerCache(); _tabs->show(); return result; } void StickersBox::installSet(uint64 setId) { auto &sets = Auth().data().stickerSetsRef(); auto it = sets.find(setId); if (it == sets.cend()) { rebuildList(); return; } if (_localRemoved.contains(setId)) { _localRemoved.removeOne(setId); if (_installed.widget()) _installed.widget()->setRemovedSets(_localRemoved); if (_featured.widget()) _featured.widget()->setRemovedSets(_localRemoved); _archived.widget()->setRemovedSets(_localRemoved); } if (!(it->flags & MTPDstickerSet::Flag::f_installed_date) || (it->flags & MTPDstickerSet::Flag::f_archived)) { MTP::send( MTPmessages_InstallStickerSet( Stickers::inputSetId(*it), MTP_boolFalse()), rpcDone(&StickersBox::installDone), rpcFail(&StickersBox::installFail, setId)); Stickers::InstallLocally(setId); } } void StickersBox::installDone(const MTPmessages_StickerSetInstallResult &result) { if (result.type() == mtpc_messages_stickerSetInstallResultArchive) { Stickers::ApplyArchivedResult( result.c_messages_stickerSetInstallResultArchive()); } } bool StickersBox::installFail(uint64 setId, const RPCError &error) { if (MTP::isDefaultHandledError(error)) return false; auto &sets = Auth().data().stickerSetsRef(); auto it = sets.find(setId); if (it == sets.cend()) { rebuildList(); return true; } Stickers::UndoInstallLocally(setId); return true; } void StickersBox::preloadArchivedSets() { if (!_tabs) return; if (!_archivedRequestId) { _archivedRequestId = MTP::send(MTPmessages_GetArchivedStickers(MTP_flags(0), MTP_long(0), MTP_int(kArchivedLimitFirstRequest)), rpcDone(&StickersBox::getArchivedDone, 0ULL)); } } void StickersBox::requestArchivedSets() { // Reload the archived list. if (!_archivedLoaded) { preloadArchivedSets(); } auto &sets = Auth().data().stickerSets(); for_const (auto setId, Auth().data().archivedStickerSetsOrder()) { auto it = sets.constFind(setId); if (it != sets.cend()) { if (it->stickers.isEmpty() && (it->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) { Auth().api().scheduleStickerSetRequest(setId, it->access); } } } Auth().api().requestStickerSets(); } void StickersBox::resizeEvent(QResizeEvent *e) { BoxContent::resizeEvent(e); if (_tabs) { updateTabsGeometry(); } if (_titleShadow) { _titleShadow->setGeometry(0, 0, width(), st::lineWidth); } if (_installed.widget()) _installed.widget()->resize(width(), _installed.widget()->height()); if (_featured.widget()) _featured.widget()->resize(width(), _featured.widget()->height()); if (_archived.widget()) _archived.widget()->resize(width(), _archived.widget()->height()); } void StickersBox::handleStickersUpdated() { if (_section == Section::Installed || _section == Section::Featured) { rebuildList(); } else { _tab->widget()->updateRows(); } if (Auth().data().archivedStickerSetsOrder().isEmpty()) { preloadArchivedSets(); } else { refreshTabs(); } } void StickersBox::rebuildList(Tab *tab) { if (!tab) tab = _tab; if (tab == &_installed) { _localOrder = tab->widget()->getFullOrder(); _localRemoved = tab->widget()->getRemovedSets(); } tab->widget()->rebuild(); if (tab == &_installed) { tab->widget()->setFullOrder(_localOrder); } tab->widget()->setRemovedSets(_localRemoved); } void StickersBox::saveChanges() { // Make sure that our changes in other tabs are applied in the Installed tab. rebuildList(&_installed); if (_someArchivedLoaded) { Local::writeArchivedStickers(); } if (AuthSession::Exists()) { Auth().api().saveStickerSets(_installed.widget()->getOrder(), _installed.widget()->getRemovedSets()); } } void StickersBox::setInnerFocus() { if (_megagroupSet) { _installed.widget()->setInnerFocus(); } } StickersBox::~StickersBox() = default; StickersBox::Inner::Row::Row(uint64 id, DocumentData *sticker, int32 count, const QString &title, int titleWidth, bool installed, bool official, bool unread, bool archived, bool removed, int32 pixw, int32 pixh) : id(id) , sticker(sticker) , count(count) , title(title) , titleWidth(titleWidth) , installed(installed) , official(official) , unread(unread) , archived(archived) , removed(removed) , pixw(pixw) , pixh(pixh) { } StickersBox::Inner::Row::~Row() = default; StickersBox::Inner::Inner(QWidget *parent, StickersBox::Section section) : TWidget(parent) , _section(section) , _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) , _a_shifting(animation(this, &Inner::step_shifting)) , _itemsTop(st::membersMarginTop) , _addText(lang(lng_stickers_featured_add).toUpper()) , _addWidth(st::stickersTrendingAdd.font->width(_addText)) , _undoText(lang(lng_stickers_return).toUpper()) , _undoWidth(st::stickersUndoRemove.font->width(_undoText)) { setup(); } StickersBox::Inner::Inner(QWidget *parent, not_null<ChannelData*> megagroup) : TWidget(parent) , _section(StickersBox::Section::Installed) , _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) , _a_shifting(animation(this, &Inner::step_shifting)) , _itemsTop(st::membersMarginTop) , _megagroupSet(megagroup) , _megagroupSetInput(_megagroupSet->mgInfo->stickerSet) , _megagroupSetField(this, st::groupStickersField, [] { return qsl("stickerset"); }, QString(), true) , _megagroupDivider(this) , _megagroupSubTitle(this, lang(lng_stickers_group_from_your), Ui::FlatLabel::InitType::Simple, st::boxTitle) { _megagroupSetField->setLinkPlaceholder(Messenger::Instance().createInternalLink(qsl("addstickers/"))); _megagroupSetField->setPlaceholderHidden(false); _megagroupSetAddressChangedTimer.setCallback([this] { handleMegagroupSetAddressChange(); }); connect( _megagroupSetField, &Ui::MaskedInputField::changed, [this] { _megagroupSetAddressChangedTimer.callOnce( kHandleMegagroupSetAddressChangeTimeout); }); connect( _megagroupSetField, &Ui::MaskedInputField::submitted, [this] { _megagroupSetAddressChangedTimer.cancel(); handleMegagroupSetAddressChange(); }); setup(); } void StickersBox::Inner::setup() { subscribe(Auth().downloaderTaskFinished(), [this] { update(); readVisibleSets(); }); setMouseTracking(true); } void StickersBox::Inner::setInnerFocus() { if (_megagroupSetField) { _megagroupSetField->setFocusFast(); } } void StickersBox::Inner::paintEvent(QPaintEvent *e) { Painter p(this); _a_shifting.step(); auto clip = e->rect(); auto ms = getms(); p.fillRect(clip, st::boxBg); p.setClipRect(clip); if (_megagroupSelectedSet) { auto setTop = _megagroupDivider->y() - _rowHeight; p.translate(0, setTop); paintRow(p, _megagroupSelectedSet.get(), -1, ms); p.translate(0, -setTop); } auto y = _itemsTop; if (_rows.empty()) { p.setFont(st::noContactsFont); p.setPen(st::noContactsColor); p.drawText(QRect(0, y, width(), st::noContactsHeight), lang(lng_contacts_loading), style::al_center); } else { p.translate(0, _itemsTop); int32 yFrom = clip.y() - _itemsTop, yTo = clip.y() + clip.height() - _itemsTop; int32 from = floorclamp(yFrom - _rowHeight, _rowHeight, 0, _rows.size()); int32 to = ceilclamp(yTo + _rowHeight, _rowHeight, 0, _rows.size()); p.translate(0, from * _rowHeight); for (int32 i = from; i < to; ++i) { if (i != _above) { paintRow(p, _rows[i].get(), i, ms); } p.translate(0, _rowHeight); } if (from <= _above && _above < to) { p.translate(0, (_above - to) * _rowHeight); paintRow(p, _rows[_above].get(), _above, ms); } } } void StickersBox::Inner::resizeEvent(QResizeEvent *e) { updateControlsGeometry(); } void StickersBox::Inner::updateControlsGeometry() { if (_megagroupSet) { auto top = st::groupStickersFieldPadding.top(); auto fieldLeft = st::boxLayerTitlePosition.x(); _megagroupSetField->setGeometryToLeft(fieldLeft, top, width() - fieldLeft - st::groupStickersFieldPadding.right(), _megagroupSetField->height()); top += _megagroupSetField->height() + st::groupStickersFieldPadding.bottom(); if (_megagroupSelectedRemove) { _megagroupSelectedShadow->setGeometryToLeft(0, top, width(), st::lineWidth); top += st::lineWidth; _megagroupSelectedRemove->moveToRight(st::groupStickersRemovePosition.x(), top + st::groupStickersRemovePosition.y()); top += _rowHeight; } _megagroupDivider->setGeometryToLeft(0, top, width(), _megagroupDivider->height()); top += _megagroupDivider->height(); _megagroupSubTitle->resizeToNaturalWidth(width() - 2 * st::boxLayerTitlePosition.x()); _megagroupSubTitle->moveToLeft(st::boxLayerTitlePosition.x(), top + st::boxLayerTitlePosition.y()); } } QRect StickersBox::Inner::relativeButtonRect(bool removeButton) const { auto buttonw = st::stickersRemove.width; auto buttonh = st::stickersRemove.height; auto buttonshift = st::stickersRemoveSkip; if (!removeButton) { auto &st = (_section == Section::Installed) ? st::stickersUndoRemove : st::stickersTrendingAdd; auto textWidth = (_section == Section::Installed) ? _undoWidth : _addWidth; buttonw = textWidth - st.width; buttonh = st.height; buttonshift = 0; } auto buttonx = width() - st::contactsPadding.right() - st::contactsCheckPosition.x() - buttonw + buttonshift; auto buttony = st::contactsPadding.top() + (st::contactsPhotoSize - buttonh) / 2; return QRect(buttonx, buttony, buttonw, buttonh); } void StickersBox::Inner::paintRow(Painter &p, Row *set, int index, TimeMs ms) { auto xadd = 0, yadd = qRound(set->yadd.current()); if (xadd || yadd) p.translate(xadd, yadd); if (_megagroupSet) { auto selectedIndex = [&] { if (auto index = base::get_if<int>(&_selected)) { return *index; } return -1; }(); if (index >= 0 && index == selectedIndex) { p.fillRect(0, 0, width(), _rowHeight, st::contactsBgOver); if (set->ripple) { set->ripple->paint(p, 0, 0, width(), ms); } } } if (_section == Section::Installed) { if (index >= 0 && index == _above) { auto current = _aboveShadowFadeOpacity.current(); if (_started >= 0) { auto reachedOpacity = aboveShadowOpacity(); if (reachedOpacity > current) { _aboveShadowFadeOpacity = anim::value(reachedOpacity, reachedOpacity); current = reachedOpacity; } } auto row = myrtlrect(st::contactsPadding.left() / 2, st::contactsPadding.top() / 2, width() - (st::contactsPadding.left() / 2) - _scrollbar - st::contactsPadding.left() / 2, _rowHeight - ((st::contactsPadding.top() + st::contactsPadding.bottom()) / 2)); p.setOpacity(current); Ui::Shadow::paint(p, row, width(), st::boxRoundShadow); p.setOpacity(1); App::roundRect(p, row, st::boxBg, BoxCorners); p.setOpacity(1. - current); paintFakeButton(p, set, index, ms); p.setOpacity(1.); } else if (!_megagroupSet) { paintFakeButton(p, set, index, ms); } } else if (!_megagroupSet) { paintFakeButton(p, set, index, ms); } if (set->removed && _section == Section::Installed) { p.setOpacity(st::stickersRowDisabledOpacity); } auto stickerx = st::contactsPadding.left(); if (!_megagroupSet && _section == Section::Installed) { stickerx += st::stickersReorderIcon.width() + st::stickersReorderSkip; if (!set->isRecentSet()) { st::stickersReorderIcon.paint(p, st::contactsPadding.left(), (_rowHeight - st::stickersReorderIcon.height()) / 2, width()); } } if (set->sticker) { set->sticker->thumb->load(); auto pix = set->sticker->thumb->pix(set->pixw, set->pixh); p.drawPixmapLeft(stickerx + (st::contactsPhotoSize - set->pixw) / 2, st::contactsPadding.top() + (st::contactsPhotoSize - set->pixh) / 2, width(), pix); } int namex = stickerx + st::contactsPhotoSize + st::contactsPadding.left(); int namey = st::contactsPadding.top() + st::contactsNameTop; int statusx = namex; int statusy = st::contactsPadding.top() + st::contactsStatusTop; p.setFont(st::contactsNameStyle.font); p.setPen(st::contactsNameFg); p.drawTextLeft(namex, namey, width(), set->title, set->titleWidth); if (set->unread) { p.setPen(Qt::NoPen); p.setBrush(st::stickersFeaturedUnreadBg); { PainterHighQualityEnabler hq(p); p.drawEllipse(rtlrect(namex + set->titleWidth + st::stickersFeaturedUnreadSkip, namey + st::stickersFeaturedUnreadTop, st::stickersFeaturedUnreadSize, st::stickersFeaturedUnreadSize, width())); } } auto statusText = (set->count > 0) ? lng_stickers_count(lt_count, set->count) : lang(lng_contacts_loading); p.setFont(st::contactsStatusFont); p.setPen(st::contactsStatusFg); p.drawTextLeft(statusx, statusy, width(), statusText); p.setOpacity(1); if (xadd || yadd) p.translate(-xadd, -yadd); } void StickersBox::Inner::paintFakeButton(Painter &p, Row *set, int index, TimeMs ms) { auto removeButton = (_section == Section::Installed && !set->removed); auto rect = relativeButtonRect(removeButton); if (_section != Section::Installed && set->installed && !set->archived && !set->removed) { // Checkbox after installed from Trending or Archived. int checkx = width() - (st::contactsPadding.right() + st::contactsCheckPosition.x() + (rect.width() + st::stickersFeaturedInstalled.width()) / 2); int checky = st::contactsPadding.top() + (st::contactsPhotoSize - st::stickersFeaturedInstalled.height()) / 2; st::stickersFeaturedInstalled.paint(p, QPoint(checkx, checky), width()); } else { auto selected = (index == _actionSel && _actionDown < 0) || (index == _actionDown); if (removeButton) { // Trash icon button when not disabled in Installed. if (set->ripple) { set->ripple->paint(p, rect.x(), rect.y(), width(), ms); if (set->ripple->empty()) { set->ripple.reset(); } } auto &icon = selected ? st::stickersRemove.iconOver : st::stickersRemove.icon; auto position = st::stickersRemove.iconPosition; if (position.x() < 0) position.setX((rect.width() - icon.width()) / 2); if (position.y() < 0) position.setY((rect.height() - icon.height()) / 2); icon.paint(p, rect.topLeft() + position, ms); } else { // Round button ADD when not installed from Trending or Archived. // Or round button UNDO after disabled from Installed. auto &st = (_section == Section::Installed) ? st::stickersUndoRemove : st::stickersTrendingAdd; auto textWidth = (_section == Section::Installed) ? _undoWidth : _addWidth; auto &text = (_section == Section::Installed) ? _undoText : _addText; auto &textBg = selected ? st.textBgOver : st.textBg; App::roundRect(p, myrtlrect(rect), textBg, ImageRoundRadius::Small); if (set->ripple) { set->ripple->paint(p, rect.x(), rect.y(), width(), ms); if (set->ripple->empty()) { set->ripple.reset(); } } p.setFont(st.font); p.setPen(selected ? st.textFgOver : st.textFg); p.drawTextLeft(rect.x() - (st.width / 2), rect.y() + st.textTop, width(), text, textWidth); } } } void StickersBox::Inner::mousePressEvent(QMouseEvent *e) { if (_dragging >= 0) mouseReleaseEvent(e); _mouse = e->globalPos(); onUpdateSelected(); setPressed(_selected); if (_actionSel >= 0) { setActionDown(_actionSel); update(0, _itemsTop + _actionSel * _rowHeight, width(), _rowHeight); } else if (auto selectedIndex = base::get_if<int>(&_selected)) { if (_section == Section::Installed && !_rows[*selectedIndex]->isRecentSet() && _inDragArea) { _above = _dragging = _started = *selectedIndex; _dragStart = mapFromGlobal(_mouse); } } } void StickersBox::Inner::setActionDown(int newActionDown) { if (_actionDown == newActionDown) { return; } if (_actionDown >= 0 && _actionDown < _rows.size()) { update(0, _itemsTop + _actionDown * _rowHeight, width(), _rowHeight); auto &set = _rows[_actionDown]; if (set->ripple) { set->ripple->lastStop(); } } _actionDown = newActionDown; if (_actionDown >= 0 && _actionDown < _rows.size()) { update(0, _itemsTop + _actionDown * _rowHeight, width(), _rowHeight); auto &set = _rows[_actionDown]; auto removeButton = (_section == Section::Installed && !set->removed); if (!set->ripple) { if (_section == Section::Installed) { if (set->removed) { auto rippleSize = QSize(_undoWidth - st::stickersUndoRemove.width, st::stickersUndoRemove.height); auto rippleMask = Ui::RippleAnimation::roundRectMask(rippleSize, st::buttonRadius); ensureRipple(st::stickersUndoRemove.ripple, std::move(rippleMask), removeButton); } else { auto rippleSize = st::stickersRemove.rippleAreaSize; auto rippleMask = Ui::RippleAnimation::ellipseMask(QSize(rippleSize, rippleSize)); ensureRipple(st::stickersRemove.ripple, std::move(rippleMask), removeButton); } } else if (!set->installed || set->archived || set->removed) { auto rippleSize = QSize(_addWidth - st::stickersTrendingAdd.width, st::stickersTrendingAdd.height); auto rippleMask = Ui::RippleAnimation::roundRectMask(rippleSize, st::buttonRadius); ensureRipple(st::stickersTrendingAdd.ripple, std::move(rippleMask), removeButton); } } if (set->ripple) { auto rect = relativeButtonRect(removeButton); set->ripple->add(mapFromGlobal(QCursor::pos()) - QPoint(myrtlrect(rect).x(), _itemsTop + _actionDown * _rowHeight + rect.y())); } } } void StickersBox::Inner::setSelected(SelectedRow selected) { if (_selected == selected) { return; } if ((_megagroupSet || _section != Section::Installed) && ((_selected.has_value() || _pressed.has_value()) != (selected.has_value() || _pressed.has_value()))) { if (!_inDragArea) { setCursor((selected.has_value() || _pressed.has_value()) ? style::cur_pointer : style::cur_default); } } auto countSelectedIndex = [&] { if (auto index = base::get_if<int>(&_selected)) { return *index; } return -1; }; auto selectedIndex = countSelectedIndex(); if (_megagroupSet && selectedIndex >= 0 && selectedIndex < _rows.size()) { update(0, _itemsTop + selectedIndex * _rowHeight, width(), _rowHeight); } _selected = selected; selectedIndex = countSelectedIndex(); if (_megagroupSet && selectedIndex >= 0 && selectedIndex < _rows.size()) { update(0, _itemsTop + selectedIndex * _rowHeight, width(), _rowHeight); } } void StickersBox::Inner::setPressed(SelectedRow pressed) { if (_pressed == pressed) { return; } auto countPressedIndex = [&] { if (auto index = base::get_if<int>(&_pressed)) { return *index; } return -1; }; auto pressedIndex = countPressedIndex(); if (_megagroupSet && pressedIndex >= 0 && pressedIndex < _rows.size()) { update(0, _itemsTop + pressedIndex * _rowHeight, width(), _rowHeight); auto &set = _rows[pressedIndex]; if (set->ripple) { set->ripple->lastStop(); } } _pressed = pressed; pressedIndex = countPressedIndex(); if (_megagroupSet && pressedIndex >= 0 && pressedIndex < _rows.size()) { update(0, _itemsTop + pressedIndex * _rowHeight, width(), _rowHeight); auto &set = _rows[pressedIndex]; auto rippleMask = Ui::RippleAnimation::rectMask(QSize(width(), _rowHeight)); if (!_rows[pressedIndex]->ripple) { _rows[pressedIndex]->ripple = std::make_unique<Ui::RippleAnimation>(st::contactsRipple, std::move(rippleMask), [this, pressedIndex] { update(0, _itemsTop + pressedIndex * _rowHeight, width(), _rowHeight); }); } _rows[pressedIndex]->ripple->add(mapFromGlobal(QCursor::pos()) - QPoint(0, _itemsTop + pressedIndex * _rowHeight)); } } void StickersBox::Inner::ensureRipple(const style::RippleAnimation &st, QImage mask, bool removeButton) { _rows[_actionDown]->ripple = std::make_unique<Ui::RippleAnimation>(st, std::move(mask), [this, index = _actionDown, removeButton] { update(myrtlrect(relativeButtonRect(removeButton).translated(0, _itemsTop + index * _rowHeight))); }); } void StickersBox::Inner::mouseMoveEvent(QMouseEvent *e) { _mouse = e->globalPos(); onUpdateSelected(); } void StickersBox::Inner::onUpdateSelected() { auto local = mapFromGlobal(_mouse); if (_dragging >= 0) { auto shift = 0; auto ms = getms(); int firstSetIndex = 0; if (_rows.at(firstSetIndex)->isRecentSet()) { ++firstSetIndex; } if (_dragStart.y() > local.y() && _dragging > 0) { shift = -floorclamp(_dragStart.y() - local.y() + (_rowHeight / 2), _rowHeight, 0, _dragging - firstSetIndex); for (int32 from = _dragging, to = _dragging + shift; from > to; --from) { qSwap(_rows[from], _rows[from - 1]); _rows[from]->yadd = anim::value(_rows[from]->yadd.current() - _rowHeight, 0); _animStartTimes[from] = ms; } } else if (_dragStart.y() < local.y() && _dragging + 1 < _rows.size()) { shift = floorclamp(local.y() - _dragStart.y() + (_rowHeight / 2), _rowHeight, 0, _rows.size() - _dragging - 1); for (int32 from = _dragging, to = _dragging + shift; from < to; ++from) { qSwap(_rows[from], _rows[from + 1]); _rows[from]->yadd = anim::value(_rows[from]->yadd.current() + _rowHeight, 0); _animStartTimes[from] = ms; } } if (shift) { _dragging += shift; _above = _dragging; _dragStart.setY(_dragStart.y() + shift * _rowHeight); if (!_a_shifting.animating()) { _a_shifting.start(); } } _rows[_dragging]->yadd = anim::value(local.y() - _dragStart.y(), local.y() - _dragStart.y()); _animStartTimes[_dragging] = 0; _a_shifting.step(ms, true); auto countDraggingScrollDelta = [this, local] { if (local.y() < _visibleTop) { return local.y() - _visibleTop; } else if (local.y() >= _visibleBottom) { return local.y() + 1 - _visibleBottom; } return 0; }; emit draggingScrollDelta(countDraggingScrollDelta()); } else { bool in = rect().marginsRemoved(QMargins(0, _itemsTop, 0, st::membersMarginBottom)).contains(local); auto selected = SelectedRow(); auto actionSel = -1; auto inDragArea = false; if (in && !_rows.empty()) { auto selectedIndex = floorclamp(local.y() - _itemsTop, _rowHeight, 0, _rows.size() - 1); selected = selectedIndex; local.setY(local.y() - _itemsTop - selectedIndex * _rowHeight); auto &set = _rows[selectedIndex]; if (!_megagroupSet && (_section == Section::Installed || !set->installed || set->archived || set->removed)) { auto removeButton = (_section == Section::Installed && !set->removed); auto rect = myrtlrect(relativeButtonRect(removeButton)); actionSel = rect.contains(local) ? selectedIndex : -1; } else { actionSel = -1; } if (!_megagroupSet && _section == Section::Installed && !set->isRecentSet()) { auto dragAreaWidth = st::contactsPadding.left() + st::stickersReorderIcon.width() + st::stickersReorderSkip; auto dragArea = myrtlrect(0, 0, dragAreaWidth, _rowHeight); inDragArea = dragArea.contains(local); } } else if (_megagroupSelectedSet) { auto setTop = _megagroupDivider->y() - _rowHeight; if (QRect(0, setTop, width(), _rowHeight).contains(local)) { selected = MegagroupSet(); } } setSelected(selected); if (_inDragArea != inDragArea) { _inDragArea = inDragArea; setCursor(_inDragArea ? style::cur_sizeall : ((_selected.has_value() || _pressed.has_value()) ? style::cur_pointer : style::cur_default)); } setActionSel(actionSel); emit draggingScrollDelta(0); } } float64 StickersBox::Inner::aboveShadowOpacity() const { if (_above < 0) return 0; auto dx = 0; auto dy = qAbs(_above * _rowHeight + qRound(_rows[_above]->yadd.current()) - _started * _rowHeight); return qMin((dx + dy) * 2. / _rowHeight, 1.); } void StickersBox::Inner::mouseReleaseEvent(QMouseEvent *e) { auto pressed = std::exchange(_pressed, SelectedRow()); if (_section != Section::Installed && !_selected.has_value() && pressed.has_value()) { setCursor(style::cur_default); } _mouse = e->globalPos(); onUpdateSelected(); if (_actionDown == _actionSel && _actionSel >= 0) { if (_section == Section::Installed) { setRowRemoved(_actionDown, !_rows[_actionDown]->removed); } else if (_installSetCallback) { _installSetCallback(_rows[_actionDown]->id); } } else if (_dragging >= 0) { QPoint local(mapFromGlobal(_mouse)); _rows[_dragging]->yadd.start(0.); _aboveShadowFadeStart = _animStartTimes[_dragging] = getms(); _aboveShadowFadeOpacity = anim::value(aboveShadowOpacity(), 0); if (!_a_shifting.animating()) { _a_shifting.start(); } _dragging = _started = -1; } else if (pressed == _selected && _actionSel < 0 && _actionDown < 0) { auto selectedIndex = [&] { if (auto index = base::get_if<int>(&_selected)) { return *index; } return -1; }(); auto getSetByRow = [&](const Row &row) -> const Stickers::Set* { auto &sets = Auth().data().stickerSetsRef(); if (!row.isRecentSet()) { auto it = sets.find(row.id); if (it != sets.cend()) { return &*it; } } return nullptr; }; auto showSetByRow = [&](const Row &row) { if (auto set = getSetByRow(row)) { setSelected(SelectedRow()); Ui::show( Box<StickerSetBox>(Stickers::inputSetId(*set)), LayerOption::KeepOther); } }; if (selectedIndex >= 0 && !_inDragArea) { auto &row = *_rows[selectedIndex]; if (_megagroupSet) { if (auto set = getSetByRow(row)) { setMegagroupSelectedSet(MTP_inputStickerSetID( MTP_long(set->id), MTP_long(set->access))); } } else { showSetByRow(row); } } else if (_megagroupSelectedSet && _selected.is<MegagroupSet>()) { showSetByRow(*_megagroupSelectedSet); } } setActionDown(-1); } void StickersBox::Inner::saveGroupSet() { Expects(_megagroupSet != nullptr); auto oldId = (_megagroupSet->mgInfo->stickerSet.type() == mtpc_inputStickerSetID) ? _megagroupSet->mgInfo->stickerSet.c_inputStickerSetID().vid.v : 0; auto newId = (_megagroupSetInput.type() == mtpc_inputStickerSetID) ? _megagroupSetInput.c_inputStickerSetID().vid.v : 0; if (newId != oldId) { Auth().api().setGroupStickerSet(_megagroupSet, _megagroupSetInput); Auth().api().stickerSetInstalled(Stickers::MegagroupSetId); } } void StickersBox::Inner::setRowRemoved(int index, bool removed) { auto &row = _rows[index]; if (row->removed != removed) { row->removed = removed; row->ripple.reset(); update(0, _itemsTop + index * _rowHeight, width(), _rowHeight); onUpdateSelected(); } } void StickersBox::Inner::leaveEventHook(QEvent *e) { _mouse = QPoint(-1, -1); onUpdateSelected(); } void StickersBox::Inner::leaveToChildEvent(QEvent *e, QWidget *child) { _mouse = QPoint(-1, -1); onUpdateSelected(); } void StickersBox::Inner::step_shifting(TimeMs ms, bool timer) { auto animating = false; auto updateMin = -1; auto updateMax = 0; for (auto i = 0, l = _animStartTimes.size(); i < l; ++i) { auto start = _animStartTimes.at(i); if (start) { if (updateMin < 0) updateMin = i; updateMax = i; if (start + st::stickersRowDuration > ms && ms >= start) { _rows[i]->yadd.update(float64(ms - start) / st::stickersRowDuration, anim::sineInOut); animating = true; } else { _rows[i]->yadd.finish(); _animStartTimes[i] = 0; } } } if (_aboveShadowFadeStart) { if (updateMin < 0 || updateMin > _above) updateMin = _above; if (updateMax < _above) updateMin = _above; if (_aboveShadowFadeStart + st::stickersRowDuration > ms && ms > _aboveShadowFadeStart) { _aboveShadowFadeOpacity.update(float64(ms - _aboveShadowFadeStart) / st::stickersRowDuration, anim::sineInOut); animating = true; } else { _aboveShadowFadeOpacity.finish(); _aboveShadowFadeStart = 0; } } if (timer) { if (_dragging >= 0) { if (updateMin < 0 || updateMin > _dragging) { updateMin = _dragging; } if (updateMax < _dragging) updateMax = _dragging; } if (updateMin == 1 && _rows[0]->isRecentSet()) { updateMin = 0; // Repaint from the very top of the content. } if (updateMin >= 0) { update(0, _itemsTop + _rowHeight * (updateMin - 1), width(), _rowHeight * (updateMax - updateMin + 3)); } } if (!animating) { _above = _dragging; _a_shifting.stop(); } } void StickersBox::Inner::clear() { _rows.clear(); _animStartTimes.clear(); _aboveShadowFadeStart = 0; _aboveShadowFadeOpacity = anim::value(); _a_shifting.stop(); _above = _dragging = _started = -1; setSelected(SelectedRow()); setPressed(SelectedRow()); setActionSel(-1); setActionDown(-1); update(); } void StickersBox::Inner::setActionSel(int32 actionSel) { if (actionSel != _actionSel) { if (_actionSel >= 0) update(0, _itemsTop + _actionSel * _rowHeight, width(), _rowHeight); _actionSel = actionSel; if (_actionSel >= 0) update(0, _itemsTop + _actionSel * _rowHeight, width(), _rowHeight); if (_section == Section::Installed) { setCursor((_actionSel >= 0 && (_actionDown < 0 || _actionDown == _actionSel)) ? style::cur_pointer : style::cur_default); } } } void StickersBox::Inner::AddressField::correctValue( const QString &was, int wasCursor, QString &now, int &nowCursor) { auto newText = now; auto newCursor = nowCursor; auto removeFromBeginning = { qstr("http://"), qstr("https://"), qstr("www.t.me/"), qstr("www.telegram.me/"), qstr("www.telegram.dog/"), qstr("t.me/"), qstr("telegram.me/"), qstr("telegram.dog/"), qstr("addstickers/"), }; for (auto &removePhrase : removeFromBeginning) { if (newText.startsWith(removePhrase)) { newText = newText.mid(removePhrase.size()); newCursor = newText.size(); } } setCorrectedText(now, nowCursor, newText, newCursor); } void StickersBox::Inner::handleMegagroupSetAddressChange() { auto text = _megagroupSetField->getLastText().trimmed(); if (text.isEmpty()) { if (_megagroupSelectedSet) { auto it = Auth().data().stickerSets().constFind(_megagroupSelectedSet->id); if (it != Auth().data().stickerSets().cend() && !it->shortName.isEmpty()) { setMegagroupSelectedSet(MTP_inputStickerSetEmpty()); } } } else if (!_megagroupSetRequestId) { _megagroupSetRequestId = request(MTPmessages_GetStickerSet(MTP_inputStickerSetShortName(MTP_string(text)))).done([this](const MTPmessages_StickerSet &result) { _megagroupSetRequestId = 0; auto set = Stickers::FeedSetFull(result); setMegagroupSelectedSet(MTP_inputStickerSetID(MTP_long(set->id), MTP_long(set->access))); }).fail([this](const RPCError &error) { _megagroupSetRequestId = 0; setMegagroupSelectedSet(MTP_inputStickerSetEmpty()); }).send(); } else { _megagroupSetAddressChangedTimer.callOnce(kHandleMegagroupSetAddressChangeTimeout); } } void StickersBox::Inner::rebuildMegagroupSet() { Expects(_megagroupSet != nullptr); if (_megagroupSetInput.type() != mtpc_inputStickerSetID) { if (_megagroupSelectedSet) { _megagroupSetField->setText(QString()); _megagroupSetField->finishAnimating(); } _megagroupSelectedSet.reset(); _megagroupSelectedRemove.destroy(); _megagroupSelectedShadow.destroy(); return; } auto &set = _megagroupSetInput.c_inputStickerSetID(); auto setId = set.vid.v; auto &sets = Auth().data().stickerSets(); auto it = sets.find(setId); if (it == sets.cend() || (it->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) { Auth().api().scheduleStickerSetRequest(set.vid.v, set.vaccess_hash.v); return; } auto maxNameWidth = countMaxNameWidth(); auto titleWidth = 0; auto title = fillSetTitle(*it, maxNameWidth, &titleWidth); auto count = fillSetCount(*it); auto sticker = (DocumentData*)nullptr; auto pixw = 0, pixh = 0; fillSetCover(*it, &sticker, &pixw, &pixh); auto installed = true, official = false, unread = false, archived = false, removed = false; if (!_megagroupSelectedSet || _megagroupSelectedSet->id != it->id) { _megagroupSetField->setText(it->shortName); _megagroupSetField->finishAnimating(); } _megagroupSelectedSet = std::make_unique<Row>(it->id, sticker, count, title, titleWidth, installed, official, unread, archived, removed, pixw, pixh); _itemsTop += st::lineWidth + _rowHeight; if (!_megagroupSelectedRemove) { _megagroupSelectedRemove.create(this, st::groupStickersRemove); _megagroupSelectedRemove->show(anim::type::instant); _megagroupSelectedRemove->setClickedCallback([this] { setMegagroupSelectedSet(MTP_inputStickerSetEmpty()); }); _megagroupSelectedShadow.create(this); updateControlsGeometry(); } } void StickersBox::Inner::rebuild() { _itemsTop = st::membersMarginTop; if (_megagroupSet) { _itemsTop += st::groupStickersFieldPadding.top() + _megagroupSetField->height() + st::groupStickersFieldPadding.bottom(); _itemsTop += _megagroupDivider->height() + st::groupStickersSubTitleHeight; rebuildMegagroupSet(); } auto maxNameWidth = countMaxNameWidth(); clear(); auto &order = ([&]() -> const Stickers::Order & { if (_section == Section::Installed) { auto &result = Auth().data().stickerSetsOrder(); if (_megagroupSet && result.empty()) { return Auth().data().featuredStickerSetsOrder(); } return result; } else if (_section == Section::Featured) { return Auth().data().featuredStickerSetsOrder(); } return Auth().data().archivedStickerSetsOrder(); })(); _rows.reserve(order.size() + 1); _animStartTimes.reserve(order.size() + 1); auto &sets = Auth().data().stickerSets(); if (_megagroupSet) { auto usingFeatured = Auth().data().stickerSetsOrder().empty(); _megagroupSubTitle->setText(lang(usingFeatured ? lng_stickers_group_from_featured : lng_stickers_group_from_your)); updateControlsGeometry(); } else if (_section == Section::Installed) { auto cloudIt = sets.constFind(Stickers::CloudRecentSetId); if (cloudIt != sets.cend() && !cloudIt->stickers.isEmpty()) { rebuildAppendSet(cloudIt.value(), maxNameWidth); } } for_const (auto setId, order) { auto it = sets.constFind(setId); if (it == sets.cend()) { continue; } rebuildAppendSet(it.value(), maxNameWidth); if (it->stickers.isEmpty() || (it->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) { Auth().api().scheduleStickerSetRequest(it->id, it->access); } } Auth().api().requestStickerSets(); updateSize(); } void StickersBox::Inner::setMegagroupSelectedSet(const MTPInputStickerSet &set) { _megagroupSetInput = set; rebuild(); scrollToY.notify(0, true); onUpdateSelected(); } void StickersBox::Inner::setMinHeight(int newWidth, int minHeight) { _minHeight = minHeight; updateSize(newWidth); } void StickersBox::Inner::updateSize(int newWidth) { auto naturalHeight = _itemsTop + int(_rows.size()) * _rowHeight + st::membersMarginBottom; resize(newWidth ? newWidth : width(), qMax(_minHeight, naturalHeight)); updateControlsGeometry(); checkLoadMore(); } void StickersBox::Inner::updateRows() { int maxNameWidth = countMaxNameWidth(); auto &sets = Auth().data().stickerSets(); for_const (auto &row, _rows) { auto it = sets.constFind(row->id); if (it != sets.cend()) { auto &set = it.value(); if (!row->sticker) { auto sticker = (DocumentData*)nullptr; auto pixw = 0, pixh = 0; fillSetCover(set, &sticker, &pixw, &pixh); if (sticker) { row->sticker = sticker; row->pixw = pixw; row->pixh = pixh; } } if (!row->isRecentSet()) { auto wasInstalled = row->installed; auto wasArchived = row->archived; fillSetFlags(set, &row->installed, &row->official, &row->unread, &row->archived); if (_section == Section::Installed) { row->archived = false; } if (row->installed != wasInstalled || row->archived != wasArchived) { row->ripple.reset(); } } row->title = fillSetTitle(set, maxNameWidth, &row->titleWidth); row->count = fillSetCount(set); } } update(); } bool StickersBox::Inner::appendSet(const Stickers::Set &set) { for_const (auto &row, _rows) { if (row->id == set.id) { return false; } } rebuildAppendSet(set, countMaxNameWidth()); return true; } int StickersBox::Inner::countMaxNameWidth() const { int namex = st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left(); if (!_megagroupSet && _section == Section::Installed) { namex += st::stickersReorderIcon.width() + st::stickersReorderSkip; } int namew = st::boxWideWidth - namex - st::contactsPadding.right() - st::contactsCheckPosition.x(); if (_section == Section::Installed) { if (!_megagroupSet) { namew -= _undoWidth - st::stickersUndoRemove.width; } } else { namew -= _addWidth - st::stickersTrendingAdd.width; if (_section == Section::Featured) { namew -= st::stickersFeaturedUnreadSize + st::stickersFeaturedUnreadSkip; } } return namew; } void StickersBox::Inner::rebuildAppendSet(const Stickers::Set &set, int maxNameWidth) { bool installed = true, official = true, unread = false, archived = false, removed = false; if (set.id != Stickers::CloudRecentSetId) { fillSetFlags(set, &installed, &official, &unread, &archived); } if (_section == Section::Installed && archived) { return; } DocumentData *sticker = nullptr; int pixw = 0, pixh = 0; fillSetCover(set, &sticker, &pixw, &pixh); int titleWidth = 0; QString title = fillSetTitle(set, maxNameWidth, &titleWidth); int count = fillSetCount(set); _rows.push_back(std::make_unique<Row>(set.id, sticker, count, title, titleWidth, installed, official, unread, archived, removed, pixw, pixh)); _animStartTimes.push_back(0); } void StickersBox::Inner::fillSetCover(const Stickers::Set &set, DocumentData **outSticker, int *outWidth, int *outHeight) const { if (set.stickers.isEmpty()) { *outSticker = nullptr; *outWidth = *outHeight = 0; return; } auto sticker = *outSticker = set.stickers.front(); auto pixw = sticker->thumb->width(); auto pixh = sticker->thumb->height(); if (pixw > st::contactsPhotoSize) { if (pixw > pixh) { pixh = (pixh * st::contactsPhotoSize) / pixw; pixw = st::contactsPhotoSize; } else { pixw = (pixw * st::contactsPhotoSize) / pixh; pixh = st::contactsPhotoSize; } } else if (pixh > st::contactsPhotoSize) { pixw = (pixw * st::contactsPhotoSize) / pixh; pixh = st::contactsPhotoSize; } *outWidth = pixw; *outHeight = pixh; } int StickersBox::Inner::fillSetCount(const Stickers::Set &set) const { int result = set.stickers.isEmpty() ? set.count : set.stickers.size(), added = 0; if (set.id == Stickers::CloudRecentSetId) { auto customIt = Auth().data().stickerSets().constFind(Stickers::CustomSetId); if (customIt != Auth().data().stickerSets().cend()) { added = customIt->stickers.size(); for_const (auto &sticker, Stickers::GetRecentPack()) { if (customIt->stickers.indexOf(sticker.first) < 0) { ++added; } } } else { added = Stickers::GetRecentPack().size(); } } return result + added; } QString StickersBox::Inner::fillSetTitle(const Stickers::Set &set, int maxNameWidth, int *outTitleWidth) const { auto result = set.title; int titleWidth = st::contactsNameStyle.font->width(result); if (titleWidth > maxNameWidth) { result = st::contactsNameStyle.font->elided(result, maxNameWidth); titleWidth = st::contactsNameStyle.font->width(result); } if (outTitleWidth) { *outTitleWidth = titleWidth; } return result; } void StickersBox::Inner::fillSetFlags( const Stickers::Set &set, bool *outInstalled, bool *outOfficial, bool *outUnread, bool *outArchived) { *outInstalled = (set.flags & MTPDstickerSet::Flag::f_installed_date); *outOfficial = (set.flags & MTPDstickerSet::Flag::f_official); *outArchived = (set.flags & MTPDstickerSet::Flag::f_archived); if (_section == Section::Featured) { *outUnread = (set.flags & MTPDstickerSet_ClientFlag::f_unread); } else { *outUnread = false; } } template <typename Check> Stickers::Order StickersBox::Inner::collectSets(Check check) const { Stickers::Order result; result.reserve(_rows.size()); for_const (auto &row, _rows) { if (check(row.get())) { result.push_back(row->id); } } return result; } Stickers::Order StickersBox::Inner::getOrder() const { return collectSets([](Row *row) { return !row->archived && !row->removed && !row->isRecentSet(); }); } Stickers::Order StickersBox::Inner::getFullOrder() const { return collectSets([](Row *row) { return !row->isRecentSet(); }); } Stickers::Order StickersBox::Inner::getRemovedSets() const { return collectSets([](Row *row) { return row->removed; }); } int StickersBox::Inner::getRowIndex(uint64 setId) const { for (auto i = 0, count = int(_rows.size()); i != count; ++i) { auto &row = _rows[i]; if (row->id == setId) { return i; } } return -1; } void StickersBox::Inner::setFullOrder(const Stickers::Order &order) { for_const (auto setId, order) { auto index = getRowIndex(setId); if (index >= 0) { auto row = std::move(_rows[index]); auto count = _rows.size(); for (auto i = index + 1; i != count; ++i) { _rows[i - 1] = std::move(_rows[i]); } _rows[count - 1] = std::move(row); } } } void StickersBox::Inner::setRemovedSets(const Stickers::Order &removed) { for (auto i = 0, count = int(_rows.size()); i != count; ++i) { setRowRemoved(i, removed.contains(_rows[i]->id)); } } void StickersBox::Inner::visibleTopBottomUpdated( int visibleTop, int visibleBottom) { _visibleTop = visibleTop; _visibleBottom = visibleBottom; updateScrollbarWidth(); if (_section == Section::Featured) { readVisibleSets(); } checkLoadMore(); } void StickersBox::Inner::checkLoadMore() { if (_loadMoreCallback) { auto scrollHeight = (_visibleBottom - _visibleTop); int scrollTop = _visibleTop, scrollTopMax = height() - scrollHeight; if (scrollTop + PreloadHeightsCount * scrollHeight >= scrollTopMax) { _loadMoreCallback(); } } } void StickersBox::Inner::readVisibleSets() { auto itemsVisibleTop = _visibleTop - _itemsTop; auto itemsVisibleBottom = _visibleBottom - _itemsTop; int rowFrom = floorclamp(itemsVisibleTop, _rowHeight, 0, _rows.size()); int rowTo = ceilclamp(itemsVisibleBottom, _rowHeight, 0, _rows.size()); for (int i = rowFrom; i < rowTo; ++i) { if (!_rows[i]->unread) { continue; } if (i * _rowHeight < itemsVisibleTop || (i + 1) * _rowHeight > itemsVisibleBottom) { continue; } if (!_rows[i]->sticker || _rows[i]->sticker->thumb->loaded() || _rows[i]->sticker->loaded()) { Auth().api().readFeaturedSetDelayed(_rows[i]->id); } } } void StickersBox::Inner::updateScrollbarWidth() { auto width = (_visibleBottom - _visibleTop < height()) ? (st::boxLayerScroll.width - st::boxLayerScroll.deltax) : 0; if (_scrollbar != width) { _scrollbar = width; update(); } } StickersBox::Inner::~Inner() { clear(); } �������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/stickers_box.h�������������������������������������������0000664�0000000�0000000�00000020130�13262451251�0023407�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "boxes/abstract_box.h" #include "base/timer.h" #include "mtproto/sender.h" #include "chat_helpers/stickers.h" #include "ui/widgets/input_fields.h" class ConfirmBox; namespace style { struct RippleAnimation; } // namespace style namespace Ui { class PlainShadow; class RippleAnimation; class SettingsSlider; class SlideAnimation; class CrossButton; } // namespace Ui class StickersBox : public BoxContent, public RPCSender { public: enum class Section { Installed, Featured, Archived, }; StickersBox(QWidget*, Section section); StickersBox(QWidget*, not_null<ChannelData*> megagroup); void setInnerFocus() override; ~StickersBox(); protected: void prepare() override; void resizeEvent(QResizeEvent *e) override; void paintEvent(QPaintEvent *e) override; private: class Inner; class Tab { public: Tab() = default; template <typename ...Args> Tab(int index, Args&&... args); object_ptr<Inner> takeWidget(); void returnWidget(object_ptr<Inner> widget); Inner *widget() { return _weak; } int index() const { return _index; } void saveScrollTop(); int getScrollTop() const { return _scrollTop; } private: int _index = 0; object_ptr<Inner> _widget = { nullptr }; QPointer<Inner> _weak; int _scrollTop = 0; }; void handleStickersUpdated(); void refreshTabs(); void rebuildList(Tab *tab = nullptr); void updateTabsGeometry(); void switchTab(); void installSet(uint64 setId); int getTopSkip() const; void saveChanges(); QPixmap grabContentCache(); void installDone(const MTPmessages_StickerSetInstallResult &result); bool installFail(uint64 setId, const RPCError &error); void preloadArchivedSets(); void requestArchivedSets(); void loadMoreArchived(); void getArchivedDone(uint64 offsetId, const MTPmessages_ArchivedStickers &result); object_ptr<Ui::SettingsSlider> _tabs = { nullptr }; QList<Section> _tabIndices; class CounterWidget; object_ptr<CounterWidget> _unreadBadge = { nullptr }; Section _section; Tab _installed; Tab _featured; Tab _archived; Tab *_tab = nullptr; ChannelData *_megagroupSet = nullptr; std::unique_ptr<Ui::SlideAnimation> _slideAnimation; object_ptr<Ui::PlainShadow> _titleShadow = { nullptr }; mtpRequestId _archivedRequestId = 0; bool _archivedLoaded = false; bool _allArchivedLoaded = false; bool _someArchivedLoaded = false; Stickers::Order _localOrder; Stickers::Order _localRemoved; }; int stickerPacksCount(bool includeArchivedOfficial = false); // This class is hold in header because it requires Qt preprocessing. class StickersBox::Inner : public TWidget, private base::Subscriber, private MTP::Sender { Q_OBJECT public: using Section = StickersBox::Section; Inner(QWidget *parent, Section section); Inner(QWidget *parent, not_null<ChannelData*> megagroup); base::Observable<int> scrollToY; void setInnerFocus(); void saveGroupSet(); void rebuild(); void updateSize(int newWidth = 0); void updateRows(); // refresh only pack cover stickers bool appendSet(const Stickers::Set &set); Stickers::Order getOrder() const; Stickers::Order getFullOrder() const; Stickers::Order getRemovedSets() const; void setFullOrder(const Stickers::Order &order); void setRemovedSets(const Stickers::Order &removed); void setInstallSetCallback(base::lambda<void(uint64 setId)> callback) { _installSetCallback = std::move(callback); } void setLoadMoreCallback(base::lambda<void()> callback) { _loadMoreCallback = std::move(callback); } void setMinHeight(int newWidth, int minHeight); int getVisibleTop() const { return _visibleTop; } ~Inner(); protected: void visibleTopBottomUpdated( int visibleTop, int visibleBottom) override; void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void leaveEventHook(QEvent *e) override; void leaveToChildEvent(QEvent *e, QWidget *child) override; signals: void draggingScrollDelta(int delta); public slots: void onUpdateSelected(); private: struct Row { Row(uint64 id, DocumentData *sticker, int32 count, const QString &title, int titleWidth, bool installed, bool official, bool unread, bool archived, bool removed, int32 pixw, int32 pixh); bool isRecentSet() const { return (id == Stickers::CloudRecentSetId); } ~Row(); uint64 id = 0; DocumentData *sticker = nullptr; int32 count = 0; QString title; int titleWidth = 0; bool installed = false; bool official = false; bool unread = false; bool archived = false; bool removed = false; int32 pixw = 0; int32 pixh = 0; anim::value yadd; std::unique_ptr<Ui::RippleAnimation> ripple; }; struct MegagroupSet { inline bool operator==(const MegagroupSet &other) const { return true; } inline bool operator!=(const MegagroupSet &other) const { return false; } }; using SelectedRow = base::optional_variant<MegagroupSet, int>; class AddressField : public Ui::UsernameInput { public: using UsernameInput::UsernameInput; protected: void correctValue( const QString &was, int wasCursor, QString &now, int &nowCursor) override; }; template <typename Check> Stickers::Order collectSets(Check check) const; void checkLoadMore(); void updateScrollbarWidth(); int getRowIndex(uint64 setId) const; void setRowRemoved(int index, bool removed); void setSelected(SelectedRow selected); void setActionDown(int newActionDown); void setPressed(SelectedRow pressed); void setup(); QRect relativeButtonRect(bool removeButton) const; void ensureRipple(const style::RippleAnimation &st, QImage mask, bool removeButton); void step_shifting(TimeMs ms, bool timer); void paintRow(Painter &p, Row *set, int index, TimeMs ms); void paintFakeButton(Painter &p, Row *set, int index, TimeMs ms); void clear(); void setActionSel(int32 actionSel); float64 aboveShadowOpacity() const; void readVisibleSets(); void updateControlsGeometry(); void rebuildAppendSet(const Stickers::Set &set, int maxNameWidth); void fillSetCover(const Stickers::Set &set, DocumentData **outSticker, int *outWidth, int *outHeight) const; int fillSetCount(const Stickers::Set &set) const; QString fillSetTitle(const Stickers::Set &set, int maxNameWidth, int *outTitleWidth) const; void fillSetFlags(const Stickers::Set &set, bool *outInstalled, bool *outOfficial, bool *outUnread, bool *outArchived); void rebuildMegagroupSet(); void fixupMegagroupSetAddress(); void handleMegagroupSetAddressChange(); void setMegagroupSelectedSet(const MTPInputStickerSet &set); int countMaxNameWidth() const; Section _section; int32 _rowHeight; std::vector<std::unique_ptr<Row>> _rows; QList<TimeMs> _animStartTimes; TimeMs _aboveShadowFadeStart = 0; anim::value _aboveShadowFadeOpacity; BasicAnimation _a_shifting; base::lambda<void(uint64 setId)> _installSetCallback; base::lambda<void()> _loadMoreCallback; int _visibleTop = 0; int _visibleBottom = 0; int _itemsTop = 0; int _actionSel = -1; int _actionDown = -1; QString _addText; int _addWidth = 0; QString _undoText; int _undoWidth = 0; int _buttonHeight = 0; QPoint _mouse; bool _inDragArea = false; SelectedRow _selected; SelectedRow _pressed; QPoint _dragStart; int _started = -1; int _dragging = -1; int _above = -1; int _minHeight = 0; int _scrollbar = 0; ChannelData *_megagroupSet = nullptr; MTPInputStickerSet _megagroupSetInput = MTP_inputStickerSetEmpty(); std::unique_ptr<Row> _megagroupSelectedSet; object_ptr<AddressField> _megagroupSetField = { nullptr }; object_ptr<Ui::PlainShadow> _megagroupSelectedShadow = { nullptr }; object_ptr<Ui::CrossButton> _megagroupSelectedRemove = { nullptr }; object_ptr<BoxContentDivider> _megagroupDivider = { nullptr }; object_ptr<Ui::FlatLabel> _megagroupSubTitle = { nullptr }; base::Timer _megagroupSetAddressChangedTimer; mtpRequestId _megagroupSetRequestId = 0; }; ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/username_box.cpp�����������������������������������������0000664�0000000�0000000�00000020375�13262451251�0023745�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/username_box.h" #include "lang/lang_keys.h" #include "application.h" #include "mainwidget.h" #include "mainwindow.h" #include "ui/widgets/buttons.h" #include "ui/widgets/input_fields.h" #include "ui/toast/toast.h" #include "styles/style_boxes.h" #include "messenger.h" namespace { constexpr auto kMinUsernameLength = 5; } // namespace UsernameBox::UsernameBox(QWidget*) : _username(this, st::defaultInputField, [] { return qsl("@username"); }, App::self()->username, false) , _link(this, QString(), st::boxLinkButton) , _about(st::boxWidth - st::usernamePadding.left()) , _checkTimer(this) { } void UsernameBox::prepare() { _goodText = App::self()->username.isEmpty() ? QString() : lang(lng_username_available); setTitle(langFactory(lng_username_title)); addButton(langFactory(lng_settings_save), [this] { onSave(); }); addButton(langFactory(lng_cancel), [this] { closeBox(); }); connect(_username, SIGNAL(changed()), this, SLOT(onChanged())); connect(_username, SIGNAL(submitted(bool)), this, SLOT(onSave())); connect(_link, SIGNAL(clicked()), this, SLOT(onLinkClick())); _about.setRichText(st::usernameTextStyle, lang(lng_username_about)); setDimensions(st::boxWidth, st::usernamePadding.top() + _username->height() + st::usernameSkip + _about.countHeight(st::boxWidth - st::usernamePadding.left()) + 3 * st::usernameTextStyle.lineHeight + st::usernamePadding.bottom()); _checkTimer->setSingleShot(true); connect(_checkTimer, SIGNAL(timeout()), this, SLOT(onCheck())); updateLinkText(); } void UsernameBox::setInnerFocus() { _username->setFocusFast(); } void UsernameBox::paintEvent(QPaintEvent *e) { BoxContent::paintEvent(e); Painter p(this); p.setFont(st::boxTextFont); if (!_errorText.isEmpty()) { p.setPen(st::boxTextFgError); p.drawTextLeft(st::usernamePadding.left(), _username->y() + _username->height() + ((st::usernameSkip - st::boxTextFont->height) / 2), width(), _errorText); } else if (!_goodText.isEmpty()) { p.setPen(st::boxTextFgGood); p.drawTextLeft(st::usernamePadding.left(), _username->y() + _username->height() + ((st::usernameSkip - st::boxTextFont->height) / 2), width(), _goodText); } else { p.setPen(st::usernameDefaultFg); p.drawTextLeft(st::usernamePadding.left(), _username->y() + _username->height() + ((st::usernameSkip - st::boxTextFont->height) / 2), width(), lang(lng_username_choose)); } p.setPen(st::boxTextFg); int32 availw = st::boxWidth - st::usernamePadding.left(), h = _about.countHeight(availw); _about.drawLeft(p, st::usernamePadding.left(), _username->y() + _username->height() + st::usernameSkip, availw, width()); int32 linky = _username->y() + _username->height() + st::usernameSkip + h + st::usernameTextStyle.lineHeight + ((st::usernameTextStyle.lineHeight - st::boxTextFont->height) / 2); if (_link->isHidden()) { p.drawTextLeft(st::usernamePadding.left(), linky, width(), lang(lng_username_link_willbe)); p.setPen(st::usernameDefaultFg); p.drawTextLeft(st::usernamePadding.left(), linky + st::usernameTextStyle.lineHeight + ((st::usernameTextStyle.lineHeight - st::boxTextFont->height) / 2), width(), Messenger::Instance().createInternalLinkFull(qsl("username"))); } else { p.drawTextLeft(st::usernamePadding.left(), linky, width(), lang(lng_username_link)); } } void UsernameBox::resizeEvent(QResizeEvent *e) { BoxContent::resizeEvent(e); _username->resize(width() - st::usernamePadding.left() - st::usernamePadding.right(), _username->height()); _username->moveToLeft(st::usernamePadding.left(), st::usernamePadding.top()); int32 availw = st::boxWidth - st::usernamePadding.left(), h = _about.countHeight(availw); int32 linky = _username->y() + _username->height() + st::usernameSkip + h + st::usernameTextStyle.lineHeight + ((st::usernameTextStyle.lineHeight - st::boxTextFont->height) / 2); _link->moveToLeft(st::usernamePadding.left(), linky + st::usernameTextStyle.lineHeight + ((st::usernameTextStyle.lineHeight - st::boxTextFont->height) / 2)); } void UsernameBox::onSave() { if (_saveRequestId) return; _sentUsername = getName(); _saveRequestId = MTP::send(MTPaccount_UpdateUsername(MTP_string(_sentUsername)), rpcDone(&UsernameBox::onUpdateDone), rpcFail(&UsernameBox::onUpdateFail)); } void UsernameBox::onCheck() { if (_checkRequestId) { MTP::cancel(_checkRequestId); } QString name = getName(); if (name.size() >= kMinUsernameLength) { _checkUsername = name; _checkRequestId = MTP::send( MTPaccount_CheckUsername( MTP_string(name)), rpcDone(&UsernameBox::onCheckDone), rpcFail(&UsernameBox::onCheckFail)); } } void UsernameBox::onChanged() { updateLinkText(); QString name = getName(); if (name.isEmpty()) { if (!_errorText.isEmpty() || !_goodText.isEmpty()) { _errorText = _goodText = QString(); update(); } _checkTimer->stop(); } else { int32 len = name.size(); for (int32 i = 0; i < len; ++i) { QChar ch = name.at(i); if ((ch < 'A' || ch > 'Z') && (ch < 'a' || ch > 'z') && (ch < '0' || ch > '9') && ch != '_' && (ch != '@' || i > 0)) { if (_errorText != lang(lng_username_bad_symbols)) { _errorText = lang(lng_username_bad_symbols); update(); } _checkTimer->stop(); return; } } if (name.size() < kMinUsernameLength) { if (_errorText != lang(lng_username_too_short)) { _errorText = lang(lng_username_too_short); update(); } _checkTimer->stop(); } else { if (!_errorText.isEmpty() || !_goodText.isEmpty()) { _errorText = _goodText = QString(); update(); } _checkTimer->start(UsernameCheckTimeout); } } } void UsernameBox::onLinkClick() { Application::clipboard()->setText(Messenger::Instance().createInternalLinkFull(getName())); Ui::Toast::Show(lang(lng_username_copied)); } void UsernameBox::onUpdateDone(const MTPUser &user) { App::feedUsers(MTP_vector<MTPUser>(1, user)); closeBox(); } bool UsernameBox::onUpdateFail(const RPCError &error) { if (MTP::isDefaultHandledError(error)) return false; _saveRequestId = 0; QString err(error.type()); if (err == qstr("USERNAME_NOT_MODIFIED") || _sentUsername == App::self()->username) { App::self()->setName(TextUtilities::SingleLine(App::self()->firstName), TextUtilities::SingleLine(App::self()->lastName), TextUtilities::SingleLine(App::self()->nameOrPhone), TextUtilities::SingleLine(_sentUsername)); closeBox(); return true; } else if (err == qstr("USERNAME_INVALID")) { _username->setFocus(); _username->showError(); _errorText = lang(lng_username_invalid); update(); return true; } else if (err == qstr("USERNAME_OCCUPIED") || err == qstr("USERNAMES_UNAVAILABLE")) { _username->setFocus(); _username->showError(); _errorText = lang(lng_username_occupied); update(); return true; } _username->setFocus(); return true; } void UsernameBox::onCheckDone(const MTPBool &result) { _checkRequestId = 0; QString newError = (mtpIsTrue(result) || _checkUsername == App::self()->username) ? QString() : lang(lng_username_occupied); QString newGood = newError.isEmpty() ? lang(lng_username_available) : QString(); if (_errorText != newError || _goodText != newGood) { _errorText = newError; _goodText = newGood; update(); } } bool UsernameBox::onCheckFail(const RPCError &error) { if (MTP::isDefaultHandledError(error)) return false; _checkRequestId = 0; QString err(error.type()); if (err == qstr("USERNAME_INVALID")) { _errorText = lang(lng_username_invalid); update(); return true; } else if (err == qstr("USERNAME_OCCUPIED") && _checkUsername != App::self()->username) { _errorText = lang(lng_username_occupied); update(); return true; } _goodText = QString(); _username->setFocus(); return true; } QString UsernameBox::getName() const { return _username->text().replace('@', QString()).trimmed(); } void UsernameBox::updateLinkText() { QString uname = getName(); _link->setText(st::boxTextFont->elided(Messenger::Instance().createInternalLinkFull(uname), st::boxWidth - st::usernamePadding.left() - st::usernamePadding.right())); if (uname.isEmpty()) { if (!_link->isHidden()) { _link->hide(); update(); } } else { if (_link->isHidden()) { _link->show(); update(); } } }�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/boxes/username_box.h�������������������������������������������0000664�0000000�0000000�00000002266�13262451251�0023411�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "boxes/abstract_box.h" namespace Ui { class UsernameInput; class LinkButton; } // namespace Ui class UsernameBox : public BoxContent, public RPCSender { Q_OBJECT public: UsernameBox(QWidget*); protected: void prepare() override; void setInnerFocus() override; void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; private slots: void onSave(); void onCheck(); void onChanged(); void onLinkClick(); private: void onUpdateDone(const MTPUser &result); bool onUpdateFail(const RPCError &error); void onCheckDone(const MTPBool &result); bool onCheckFail(const RPCError &error); QString getName() const; void updateLinkText(); object_ptr<Ui::UsernameInput> _username; object_ptr<Ui::LinkButton> _link; mtpRequestId _saveRequestId = 0; mtpRequestId _checkRequestId = 0; QString _sentUsername, _checkUsername, _errorText, _goodText; Text _about; object_ptr<QTimer> _checkTimer; }; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/calls/���������������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0020521�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/calls/calls.style����������������������������������������������0000664�0000000�0000000�00000011531�13262451251�0022702�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ using "basic.style"; using "ui/widgets/widgets.style"; using "window/window.style"; callWidth: 300px; callHeight: 470px; callShadow: Shadow { left: icon {{ "call_shadow_left", windowShadowFg }}; topLeft: icon {{ "call_shadow_top_left", windowShadowFg }}; top: icon {{ "call_shadow_top", windowShadowFg }}; topRight: icon {{ "call_shadow_top_left-flip_horizontal", windowShadowFg }}; right: icon {{ "call_shadow_left-flip_horizontal", windowShadowFg }}; bottomRight: icon {{ "call_shadow_top_left-flip_vertical-flip_horizontal", windowShadowFg }}; bottom: icon {{ "call_shadow_top-flip_vertical", windowShadowFg }}; bottomLeft: icon {{ "call_shadow_top_left-flip_vertical", windowShadowFg }}; extend: margins(9px, 8px, 9px, 10px); fallback: windowShadowFgFallback; } callButton: IconButton { width: 72px; height: 72px; iconPosition: point(-1px, -1px); rippleAreaPosition: point(12px, 12px); rippleAreaSize: 48px; ripple: defaultRippleAnimation; } callAnswer: CallButton { button: IconButton(callButton) { icon: icon {{ "call_answer", callIconFg }}; ripple: RippleAnimation(defaultRippleAnimation) { color: callAnswerRipple; } } bg: callAnswerBg; angle: 135.; outerRadius: 12px; outerBg: callAnswerBgOuter; } callHangup: CallButton { button: IconButton(callButton) { icon: icon {{ "call_discard", callIconFg }}; ripple: RippleAnimation(defaultRippleAnimation) { color: callHangupRipple; } } bg: callHangupBg; outerBg: callHangupBg; } callCancel: CallButton { button: IconButton(callButton) { icon: icon {{ "box_button_close", callCancelFg }}; ripple: RippleAnimation(defaultRippleAnimation) { color: callCancelRipple; } } bg: callCancelBg; outerBg: callCancelBg; } callMuteToggle: IconButton(callButton) { icon: icon {{ "call_record_active", callIconFg }}; ripple: RippleAnimation(defaultRippleAnimation) { color: callMuteRipple; } } callUnmuteIcon: icon {{ "call_record_muted", callIconFg }}; callControlsTop: 80px; callControlsSkip: 0px; callMuteRight: 8px; callNameTop: 15px; callName: FlatLabel(defaultFlatLabel) { minWidth: 260px; maxHeight: 30px; textFg: callNameFg; align: align(top); style: TextStyle(defaultTextStyle) { font: font(21px semibold); linkFont: font(21px semibold); linkFontOver: font(21px semibold underline); } } callStatusTop: 46px; callStatus: FlatLabel(defaultFlatLabel) { minWidth: 260px; maxHeight: 20px; textFg: callStatusFg; align: align(top); style: TextStyle(defaultTextStyle) { font: font(14px); linkFont: font(14px); linkFontOver: font(14px underline); } } callFingerprintPadding: margins(9px, 4px, 9px, 5px); callFingerprintSkip: 3px; callFingerprintBottom: 8px; callBarHeight: 38px; callBarMuteToggle: IconButton { width: 41px; height: 38px; icon: icon {{ "call_record_active", callBarFg }}; iconPosition: point(9px, 8px); ripple: RippleAnimation(defaultRippleAnimation) { color: callBarMuteRipple; } rippleAreaPosition: point(5px, 3px); rippleAreaSize: 32px; } callBarUnmuteIcon: icon {{ "call_record_muted", callBarFg }}; callBarRightSkip: 12px; callBarSkip: 10px; callBarHangup: IconButton(callBarMuteToggle) { icon: icon {{ "call_discard", callBarFg }}; iconPosition: point(9px, 11px); } callBarLabel: LabelSimple(defaultLabelSimple) { font: semiboldFont; textFg: callBarFg; } callBarInfoLabel: FlatLabel(defaultFlatLabel) { maxHeight: 28px; align: align(top); textFg: callBarFg; style: TextStyle(defaultTextStyle) { font: semiboldFont; } } callBarLabelTop: 10px; callArrowPosition: point(-2px, 1px); callArrowIn: icon {{ "call_arrow_in", callArrowFg }}; callArrowOut: icon {{ "call_arrow_out", callArrowFg }}; callArrowMissed: icon {{ "call_arrow_in", callArrowMissedFg }}; callArrowSkip: 4px; callReDial: IconButton { width: 40px; height: 56px; icon: mainMenuCalls; iconOver: mainMenuCallsOver; iconPosition: point(-1px, -1px); ripple: defaultRippleAnimation; rippleAreaPosition: point(0px, 8px); rippleAreaSize: 40px; } callRatingPadding: margins(24px, 12px, 24px, 0px); callRatingStar: IconButton { width: 36px; height: 36px; icon: icon {{ "call_rating", windowSubTextFg }}; iconPosition: point(-1px, -1px); ripple: RippleAnimation(defaultRippleAnimation) { color: windowBgOver; } rippleAreaPosition: point(0px, 0px); rippleAreaSize: 36px; } callRatingStarFilled: icon {{ "call_rating_filled", lightButtonFg }}; callRatingStarTop: 4px; callRatingComment: InputField(defaultInputField) { textMargins: margins(1px, 26px, 1px, 4px); heightMax: 135px; } callRatingCommentTop: 8px; callDebugPadding: margins(24px, 0px, 24px, 0px); callDebugLabel: FlatLabel(defaultFlatLabel) { margin: callDebugPadding; } callPanelDuration: 150; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/calls/calls_box_controller.cpp���������������������������������0000664�0000000�0000000�00000025465�13262451251�0025452�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "calls/calls_box_controller.h" #include "styles/style_calls.h" #include "styles/style_boxes.h" #include "lang/lang_keys.h" #include "observer_peer.h" #include "ui/effects/ripple_animation.h" #include "calls/calls_instance.h" #include "history/history.h" #include "history/history_item.h" #include "mainwidget.h" #include "auth_session.h" #include "data/data_session.h" #include "data/data_media_types.h" namespace Calls { namespace { constexpr auto kFirstPageCount = 20; constexpr auto kPerPageCount = 100; } // namespace class BoxController::Row : public PeerListRow { public: Row(not_null<HistoryItem*> item); enum class Type { Out, In, Missed, }; bool canAddItem(not_null<const HistoryItem*> item) const { return (ComputeType(item) == _type) && (ItemDateTime(item).date() == _date); } void addItem(not_null<HistoryItem*> item) { Expects(canAddItem(item)); _items.push_back(item); ranges::sort(_items, [](not_null<HistoryItem*> a, auto b) { return (a->id > b->id); }); refreshStatus(); } void itemRemoved(not_null<const HistoryItem*> item) { if (hasItems() && item->id >= minItemId() && item->id <= maxItemId()) { _items.erase(std::remove(_items.begin(), _items.end(), item), _items.end()); refreshStatus(); } } bool hasItems() const { return !_items.empty(); } MsgId minItemId() const { Expects(hasItems()); return _items.back()->id; } MsgId maxItemId() const { Expects(hasItems()); return _items.front()->id; } void paintStatusText( Painter &p, const style::PeerListItem &st, int x, int y, int availableWidth, int outerWidth, bool selected) override; void addActionRipple(QPoint point, base::lambda<void()> updateCallback) override; void stopLastActionRipple() override; int nameIconWidth() const override { return 0; } QSize actionSize() const override { return peer()->isUser() ? QSize(st::callReDial.width, st::callReDial.height) : QSize(); } QMargins actionMargins() const override { return QMargins( 0, 0, st::defaultPeerListItem.photoPosition.x(), 0); } void paintAction( Painter &p, TimeMs ms, int x, int y, int outerWidth, bool selected, bool actionSelected) override; private: void refreshStatus(); static Type ComputeType(not_null<const HistoryItem*> item); std::vector<not_null<HistoryItem*>> _items; QDate _date; Type _type; std::unique_ptr<Ui::RippleAnimation> _actionRipple; }; BoxController::Row::Row(not_null<HistoryItem*> item) : PeerListRow(item->history()->peer, item->id) , _items(1, item) , _date(ItemDateTime(item).date()) , _type(ComputeType(item)) { refreshStatus(); } void BoxController::Row::paintStatusText(Painter &p, const style::PeerListItem &st, int x, int y, int availableWidth, int outerWidth, bool selected) { auto icon = ([this] { switch (_type) { case Type::In: return &st::callArrowIn; case Type::Out: return &st::callArrowOut; case Type::Missed: return &st::callArrowMissed; } Unexpected("_type in Calls::BoxController::Row::paintStatusText()."); })(); icon->paint(p, x + st::callArrowPosition.x(), y + st::callArrowPosition.y(), outerWidth); auto shift = st::callArrowPosition.x() + icon->width() + st::callArrowSkip; x += shift; availableWidth -= shift; PeerListRow::paintStatusText(p, st, x, y, availableWidth, outerWidth, selected); } void BoxController::Row::paintAction( Painter &p, TimeMs ms, int x, int y, int outerWidth, bool selected, bool actionSelected) { auto size = actionSize(); if (_actionRipple) { _actionRipple->paint(p, x + st::callReDial.rippleAreaPosition.x(), y + st::callReDial.rippleAreaPosition.y(), outerWidth, ms); if (_actionRipple->empty()) { _actionRipple.reset(); } } st::callReDial.icon.paintInCenter(p, rtlrect(x, y, size.width(), size.height(), outerWidth)); } void BoxController::Row::refreshStatus() { if (!hasItems()) { return; } auto text = [this] { auto time = ItemDateTime(_items.front()).time().toString(cTimeFormat()); auto today = QDateTime::currentDateTime().date(); if (_date == today) { return lng_call_box_status_today(lt_time, time); } else if (_date.addDays(1) == today) { return lng_call_box_status_yesterday(lt_time, time); } return lng_call_box_status_date(lt_date, langDayOfMonthFull(_date), lt_time, time); }; setCustomStatus((_items.size() > 1) ? lng_call_box_status_group(lt_count, QString::number(_items.size()), lt_status, text()) : text()); } BoxController::Row::Type BoxController::Row::ComputeType( not_null<const HistoryItem*> item) { if (item->out()) { return Type::Out; } else if (auto media = item->media()) { if (const auto call = media->call()) { const auto reason = call->finishReason; if (reason == Data::Call::FinishReason::Busy || reason == Data::Call::FinishReason::Missed) { return Type::Missed; } } } return Type::In; } void BoxController::Row::addActionRipple(QPoint point, base::lambda<void()> updateCallback) { if (!_actionRipple) { auto mask = Ui::RippleAnimation::ellipseMask(QSize(st::callReDial.rippleAreaSize, st::callReDial.rippleAreaSize)); _actionRipple = std::make_unique<Ui::RippleAnimation>(st::callReDial.ripple, std::move(mask), std::move(updateCallback)); } _actionRipple->add(point - st::callReDial.rippleAreaPosition); } void BoxController::Row::stopLastActionRipple() { if (_actionRipple) { _actionRipple->lastStop(); } } void BoxController::prepare() { Auth().data().itemRemoved( ) | rpl::start_with_next([this](auto item) { if (auto row = rowForItem(item)) { row->itemRemoved(item); if (!row->hasItems()) { delegate()->peerListRemoveRow(row); if (!delegate()->peerListFullRowsCount()) { refreshAbout(); } } delegate()->peerListRefreshRows(); } }, lifetime()); subscribe(Current().newServiceMessage(), [this](const FullMsgId &msgId) { if (auto item = App::histItemById(msgId)) { insertRow(item, InsertWay::Prepend); } }); delegate()->peerListSetTitle(langFactory(lng_call_box_title)); setDescriptionText(lang(lng_contacts_loading)); delegate()->peerListRefreshRows(); loadMoreRows(); } void BoxController::loadMoreRows() { if (_loadRequestId || _allLoaded) { return; } _loadRequestId = request(MTPmessages_Search( MTP_flags(0), MTP_inputPeerEmpty(), MTP_string(QString()), MTP_inputUserEmpty(), MTP_inputMessagesFilterPhoneCalls(MTP_flags(0)), MTP_int(0), MTP_int(0), MTP_int(_offsetId), MTP_int(0), MTP_int(_offsetId ? kFirstPageCount : kPerPageCount), MTP_int(0), MTP_int(0), MTP_int(0) )).done([this](const MTPmessages_Messages &result) { _loadRequestId = 0; auto handleResult = [this](auto &data) { App::feedUsers(data.vusers); App::feedChats(data.vchats); receivedCalls(data.vmessages.v); }; switch (result.type()) { case mtpc_messages_messages: handleResult(result.c_messages_messages()); _allLoaded = true; break; case mtpc_messages_messagesSlice: handleResult(result.c_messages_messagesSlice()); break; case mtpc_messages_channelMessages: { LOG(("API Error: received messages.channelMessages! (Calls::BoxController::preloadRows)")); handleResult(result.c_messages_channelMessages()); } break; case mtpc_messages_messagesNotModified: { LOG(("API Error: received messages.messagesNotModified! (Calls::BoxController::preloadRows)")); } break; default: Unexpected("Type of messages.Messages (Calls::BoxController::preloadRows)"); } }).fail([this](const RPCError &error) { _loadRequestId = 0; }).send(); } void BoxController::refreshAbout() { setDescriptionText(delegate()->peerListFullRowsCount() ? QString() : lang(lng_call_box_about)); } void BoxController::rowClicked(not_null<PeerListRow*> row) { auto itemsRow = static_cast<Row*>(row.get()); auto itemId = itemsRow->maxItemId(); InvokeQueued(App::main(), [peerId = row->peer()->id, itemId] { Ui::showPeerHistory(peerId, itemId); }); } void BoxController::rowActionClicked(not_null<PeerListRow*> row) { auto user = row->peer()->asUser(); Assert(user != nullptr); Current().startOutgoingCall(user); } void BoxController::receivedCalls(const QVector<MTPMessage> &result) { if (result.empty()) { _allLoaded = true; } for_const (auto &message, result) { auto msgId = idFromMessage(message); auto peerId = peerFromMessage(message); if (auto peer = App::peerLoaded(peerId)) { auto item = App::histories().addNewMessage(message, NewMessageExisting); insertRow(item, InsertWay::Append); } else { LOG(("API Error: a search results with not loaded peer %1").arg(peerId)); } _offsetId = msgId; } refreshAbout(); delegate()->peerListRefreshRows(); } bool BoxController::insertRow( not_null<HistoryItem*> item, InsertWay way) { if (auto row = rowForItem(item)) { if (row->canAddItem(item)) { row->addItem(item); return false; } } (way == InsertWay::Append) ? delegate()->peerListAppendRow(createRow(item)) : delegate()->peerListPrependRow(createRow(item)); delegate()->peerListSortRows([]( const PeerListRow &a, const PeerListRow &b) { return static_cast<const Row&>(a).maxItemId() > static_cast<const Row&>(b).maxItemId(); }); return true; } BoxController::Row *BoxController::rowForItem(not_null<const HistoryItem*> item) { auto v = delegate(); if (auto fullRowsCount = v->peerListFullRowsCount()) { auto itemId = item->id; auto lastRow = static_cast<Row*>(v->peerListRowAt(fullRowsCount - 1).get()); if (itemId < lastRow->minItemId()) { return lastRow; } auto firstRow = static_cast<Row*>(v->peerListRowAt(0).get()); if (itemId > firstRow->maxItemId()) { return firstRow; } // Binary search. Invariant: // 1. rowAt(left)->maxItemId() >= itemId. // 2. (left + 1 == fullRowsCount) OR rowAt(left + 1)->maxItemId() < itemId. auto left = 0; auto right = fullRowsCount; while (left + 1 < right) { auto middle = (right + left) / 2; auto middleRow = static_cast<Row*>(v->peerListRowAt(middle).get()); if (middleRow->maxItemId() >= itemId) { left = middle; } else { right = middle; } } auto result = static_cast<Row*>(v->peerListRowAt(left).get()); // Check for rowAt(left)->minItemId > itemId > rowAt(left + 1)->maxItemId. // In that case we sometimes need to return rowAt(left + 1), not rowAt(left). if (result->minItemId() > itemId && left + 1 < fullRowsCount) { auto possibleResult = static_cast<Row*>(v->peerListRowAt(left + 1).get()); Assert(possibleResult->maxItemId() < itemId); if (possibleResult->canAddItem(item)) { return possibleResult; } } return result; } return nullptr; } std::unique_ptr<PeerListRow> BoxController::createRow( not_null<HistoryItem*> item) const { auto row = std::make_unique<Row>(item); return std::move(row); } } // namespace Calls �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/calls/calls_box_controller.h�����������������������������������0000664�0000000�0000000�00000002023�13262451251�0025100�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "boxes/peer_list_box.h" namespace Calls { class BoxController : public PeerListController , private base::Subscriber , private MTP::Sender { public: void prepare() override; void rowClicked(not_null<PeerListRow*> row) override; void rowActionClicked(not_null<PeerListRow*> row) override; void loadMoreRows() override; private: void receivedCalls(const QVector<MTPMessage> &result); void refreshAbout(); class Row; Row *rowForItem(not_null<const HistoryItem*> item); enum class InsertWay { Append, Prepend, }; bool insertRow(not_null<HistoryItem*> item, InsertWay way); std::unique_ptr<PeerListRow> createRow( not_null<HistoryItem*> item) const; MsgId _offsetId = 0; mtpRequestId _loadRequestId = 0; bool _allLoaded = false; }; } // namespace Calls �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/calls/calls_call.cpp�������������������������������������������0000664�0000000�0000000�00000053246�13262451251�0023330�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "calls/calls_call.h" #include "auth_session.h" #include "mainwidget.h" #include "lang/lang_keys.h" #include "boxes/confirm_box.h" #include "boxes/rate_call_box.h" #include "calls/calls_instance.h" #include "base/openssl_help.h" #include "mtproto/connection.h" #include "media/media_audio_track.h" #include "calls/calls_panel.h" #ifdef slots #undef slots #define NEED_TO_RESTORE_SLOTS #endif // slots #include <VoIPController.h> #include <VoIPServerConfig.h> #ifdef NEED_TO_RESTORE_SLOTS #define slots Q_SLOTS #undef NEED_TO_RESTORE_SLOTS #endif // NEED_TO_RESTORE_SLOTS namespace Calls { namespace { constexpr auto kMinLayer = 65; constexpr auto kMaxLayer = 65; // MTP::CurrentLayer? constexpr auto kHangupTimeoutMs = 5000; using tgvoip::Endpoint; void ConvertEndpoint(std::vector<tgvoip::Endpoint> &ep, const MTPDphoneConnection &mtc) { if (mtc.vpeer_tag.v.length() != 16) { return; } auto ipv4 = tgvoip::IPv4Address(std::string(mtc.vip.v.constData(), mtc.vip.v.size())); auto ipv6 = tgvoip::IPv6Address(std::string(mtc.vipv6.v.constData(), mtc.vipv6.v.size())); ep.push_back(Endpoint((int64_t)mtc.vid.v, (uint16_t)mtc.vport.v, ipv4, ipv6, EP_TYPE_UDP_RELAY, (unsigned char*)mtc.vpeer_tag.v.data())); } constexpr auto kFingerprintDataSize = 256; uint64 ComputeFingerprint(const std::array<gsl::byte, kFingerprintDataSize> &authKey) { auto hash = openssl::Sha1(authKey); return (gsl::to_integer<uint64>(hash[19]) << 56) | (gsl::to_integer<uint64>(hash[18]) << 48) | (gsl::to_integer<uint64>(hash[17]) << 40) | (gsl::to_integer<uint64>(hash[16]) << 32) | (gsl::to_integer<uint64>(hash[15]) << 24) | (gsl::to_integer<uint64>(hash[14]) << 16) | (gsl::to_integer<uint64>(hash[13]) << 8) | (gsl::to_integer<uint64>(hash[12])); } } // namespace Call::Call(not_null<Delegate*> delegate, not_null<UserData*> user, Type type) : _delegate(delegate) , _user(user) , _type(type) { _discardByTimeoutTimer.setCallback([this] { hangup(); }); if (_type == Type::Outgoing) { setState(State::Requesting); } else { startWaitingTrack(); } } void Call::generateModExpFirst(base::const_byte_span randomSeed) { auto first = MTP::CreateModExp(_dhConfig.g, _dhConfig.p, randomSeed); if (first.modexp.empty()) { LOG(("Call Error: Could not compute mod-exp first.")); finish(FinishType::Failed); return; } _randomPower = first.randomPower; if (_type == Type::Incoming) { _gb = std::move(first.modexp); } else { _ga = std::move(first.modexp); _gaHash = openssl::Sha256(_ga); } } bool Call::isIncomingWaiting() const { if (type() != Call::Type::Incoming) { return false; } return (_state == State::Starting) || (_state == State::WaitingIncoming); } void Call::start(base::const_byte_span random) { // Save config here, because it is possible that it changes between // different usages inside the same call. _dhConfig = _delegate->getDhConfig(); Assert(_dhConfig.g != 0); Assert(!_dhConfig.p.empty()); generateModExpFirst(random); if (_state == State::Starting || _state == State::Requesting) { if (_type == Type::Outgoing) { startOutgoing(); } else { startIncoming(); } } else if (_state == State::ExchangingKeys && _answerAfterDhConfigReceived) { answer(); } } void Call::startOutgoing() { Expects(_type == Type::Outgoing); Expects(_state == State::Requesting); request(MTPphone_RequestCall( _user->inputUser, MTP_int(rand_value<int32>()), MTP_bytes(_gaHash), MTP_phoneCallProtocol( MTP_flags(MTPDphoneCallProtocol::Flag::f_udp_p2p | MTPDphoneCallProtocol::Flag::f_udp_reflector), MTP_int(kMinLayer), MTP_int(kMaxLayer)) )).done([this](const MTPphone_PhoneCall &result) { Expects(result.type() == mtpc_phone_phoneCall); setState(State::Waiting); auto &call = result.c_phone_phoneCall(); App::feedUsers(call.vusers); if (call.vphone_call.type() != mtpc_phoneCallWaiting) { LOG(("Call Error: Expected phoneCallWaiting in response to phone.requestCall()")); finish(FinishType::Failed); return; } auto &phoneCall = call.vphone_call; auto &waitingCall = phoneCall.c_phoneCallWaiting(); _id = waitingCall.vid.v; _accessHash = waitingCall.vaccess_hash.v; if (_finishAfterRequestingCall != FinishType::None) { if (_finishAfterRequestingCall == FinishType::Failed) { finish(_finishAfterRequestingCall); } else { hangup(); } return; } _discardByTimeoutTimer.callOnce(Global::CallReceiveTimeoutMs()); handleUpdate(phoneCall); }).fail([this](const RPCError &error) { handleRequestError(error); }).send(); } void Call::startIncoming() { Expects(_type == Type::Incoming); Expects(_state == State::Starting); request(MTPphone_ReceivedCall(MTP_inputPhoneCall(MTP_long(_id), MTP_long(_accessHash)))).done([this](const MTPBool &result) { if (_state == State::Starting) { setState(State::WaitingIncoming); } }).fail([this](const RPCError &error) { handleRequestError(error); }).send(); } void Call::answer() { Expects(_type == Type::Incoming); if (_state != State::Starting && _state != State::WaitingIncoming) { if (_state != State::ExchangingKeys || !_answerAfterDhConfigReceived) { return; } } setState(State::ExchangingKeys); if (_gb.empty()) { _answerAfterDhConfigReceived = true; return; } else { _answerAfterDhConfigReceived = false; } request(MTPphone_AcceptCall( MTP_inputPhoneCall(MTP_long(_id), MTP_long(_accessHash)), MTP_bytes(_gb), MTP_phoneCallProtocol( MTP_flags(MTPDphoneCallProtocol::Flag::f_udp_p2p | MTPDphoneCallProtocol::Flag::f_udp_reflector), MTP_int(kMinLayer), MTP_int(kMaxLayer)) )).done([this](const MTPphone_PhoneCall &result) { Expects(result.type() == mtpc_phone_phoneCall); auto &call = result.c_phone_phoneCall(); App::feedUsers(call.vusers); if (call.vphone_call.type() != mtpc_phoneCallWaiting) { LOG(("Call Error: Expected phoneCallWaiting in response to phone.acceptCall()")); finish(FinishType::Failed); return; } handleUpdate(call.vphone_call); }).fail([this](const RPCError &error) { handleRequestError(error); }).send(); } void Call::setMute(bool mute) { _mute = mute; if (_controller) { _controller->SetMicMute(_mute); } _muteChanged.notify(_mute); } TimeMs Call::getDurationMs() const { return _startTime ? (getms(true) - _startTime) : 0; } void Call::hangup() { if (_state == State::Busy) { _delegate->callFinished(this); } else { auto missed = (_state == State::Ringing || (_state == State::Waiting && _type == Type::Outgoing)); auto declined = isIncomingWaiting(); auto reason = missed ? MTP_phoneCallDiscardReasonMissed() : declined ? MTP_phoneCallDiscardReasonBusy() : MTP_phoneCallDiscardReasonHangup(); finish(FinishType::Ended, reason); } } void Call::redial() { if (_state != State::Busy) { return; } Assert(_controller == nullptr); _type = Type::Outgoing; setState(State::Requesting); _answerAfterDhConfigReceived = false; startWaitingTrack(); _delegate->callRedial(this); } QString Call::getDebugLog() const { constexpr auto kDebugLimit = 4096; auto bytes = base::byte_vector(kDebugLimit, gsl::byte {}); _controller->GetDebugString(reinterpret_cast<char*>(bytes.data()), bytes.size()); auto end = std::find(bytes.begin(), bytes.end(), gsl::byte {}); auto size = (end - bytes.begin()); return QString::fromUtf8(reinterpret_cast<const char*>(bytes.data()), size); } void Call::startWaitingTrack() { _waitingTrack = Media::Audio::Current().createTrack(); auto trackFileName = Auth().settings().getSoundPath( (_type == Type::Outgoing) ? qsl("call_outgoing") : qsl("call_incoming")); _waitingTrack->samplePeakEach(kSoundSampleMs); _waitingTrack->fillFromFile(trackFileName); _waitingTrack->playInLoop(); } float64 Call::getWaitingSoundPeakValue() const { if (_waitingTrack) { auto when = getms() + kSoundSampleMs / 4; return _waitingTrack->getPeakValue(when); } return 0.; } bool Call::isKeyShaForFingerprintReady() const { return (_keyFingerprint != 0); } base::byte_array<Call::kSha256Size> Call::getKeyShaForFingerprint() const { Expects(isKeyShaForFingerprintReady()); Expects(!_ga.empty()); auto encryptedChatAuthKey = base::byte_vector(_authKey.size() + _ga.size(), gsl::byte {}); base::copy_bytes(gsl::make_span(encryptedChatAuthKey).subspan(0, _authKey.size()), _authKey); base::copy_bytes(gsl::make_span(encryptedChatAuthKey).subspan(_authKey.size(), _ga.size()), _ga); return openssl::Sha256(encryptedChatAuthKey); } bool Call::handleUpdate(const MTPPhoneCall &call) { switch (call.type()) { case mtpc_phoneCallRequested: { auto &data = call.c_phoneCallRequested(); if (_type != Type::Incoming || _id != 0 || peerToUser(_user->id) != data.vadmin_id.v) { Unexpected("phoneCallRequested call inside an existing call handleUpdate()"); } if (Auth().userId() != data.vparticipant_id.v) { LOG(("Call Error: Wrong call participant_id %1, expected %2.").arg(data.vparticipant_id.v).arg(Auth().userId())); finish(FinishType::Failed); return true; } _id = data.vid.v; _accessHash = data.vaccess_hash.v; auto gaHashBytes = bytesFromMTP(data.vg_a_hash); if (gaHashBytes.size() != _gaHash.size()) { LOG(("Call Error: Wrong g_a_hash size %1, expected %2.").arg(gaHashBytes.size()).arg(_gaHash.size())); finish(FinishType::Failed); return true; } base::copy_bytes(gsl::make_span(_gaHash), gaHashBytes); } return true; case mtpc_phoneCallEmpty: { auto &data = call.c_phoneCallEmpty(); if (data.vid.v != _id) { return false; } LOG(("Call Error: phoneCallEmpty received.")); finish(FinishType::Failed); } return true; case mtpc_phoneCallWaiting: { auto &data = call.c_phoneCallWaiting(); if (data.vid.v != _id) { return false; } if (_type == Type::Outgoing && _state == State::Waiting && data.vreceive_date.v != 0) { _discardByTimeoutTimer.callOnce(Global::CallRingTimeoutMs()); setState(State::Ringing); startWaitingTrack(); } } return true; case mtpc_phoneCall: { auto &data = call.c_phoneCall(); if (data.vid.v != _id) { return false; } if (_type == Type::Incoming && _state == State::ExchangingKeys) { startConfirmedCall(data); } } return true; case mtpc_phoneCallDiscarded: { auto &data = call.c_phoneCallDiscarded(); if (data.vid.v != _id) { return false; } if (data.is_need_debug()) { auto debugLog = _controller ? _controller->GetDebugLog() : std::string(); if (!debugLog.empty()) { MTP::send(MTPphone_SaveCallDebug(MTP_inputPhoneCall(MTP_long(_id), MTP_long(_accessHash)), MTP_dataJSON(MTP_string(debugLog)))); } } if (data.is_need_rating() && _id && _accessHash) { Ui::show(Box<RateCallBox>(_id, _accessHash)); } if (data.has_reason() && data.vreason.type() == mtpc_phoneCallDiscardReasonDisconnect) { LOG(("Call Info: Discarded with DISCONNECT reason.")); } if (data.has_reason() && data.vreason.type() == mtpc_phoneCallDiscardReasonBusy) { setState(State::Busy); } else if (_type == Type::Outgoing || _state == State::HangingUp) { setState(State::Ended); } else { setState(State::EndedByOtherDevice); } } return true; case mtpc_phoneCallAccepted: { auto &data = call.c_phoneCallAccepted(); if (data.vid.v != _id) { return false; } if (_type != Type::Outgoing) { LOG(("Call Error: Unexpected phoneCallAccepted for an incoming call.")); finish(FinishType::Failed); } else if (checkCallFields(data)) { confirmAcceptedCall(data); } } return true; } Unexpected("phoneCall type inside an existing call handleUpdate()"); } void Call::confirmAcceptedCall(const MTPDphoneCallAccepted &call) { Expects(_type == Type::Outgoing); auto firstBytes = bytesFromMTP(call.vg_b); auto computedAuthKey = MTP::CreateAuthKey(firstBytes, _randomPower, _dhConfig.p); if (computedAuthKey.empty()) { LOG(("Call Error: Could not compute mod-exp final.")); finish(FinishType::Failed); return; } MTP::AuthKey::FillData(_authKey, computedAuthKey); _keyFingerprint = ComputeFingerprint(_authKey); setState(State::ExchangingKeys); request(MTPphone_ConfirmCall( MTP_inputPhoneCall(MTP_long(_id), MTP_long(_accessHash)), MTP_bytes(_ga), MTP_long(_keyFingerprint), MTP_phoneCallProtocol( MTP_flags(MTPDphoneCallProtocol::Flag::f_udp_p2p | MTPDphoneCallProtocol::Flag::f_udp_reflector), MTP_int(kMinLayer), MTP_int(kMaxLayer)) )).done([this](const MTPphone_PhoneCall &result) { Expects(result.type() == mtpc_phone_phoneCall); auto &call = result.c_phone_phoneCall(); App::feedUsers(call.vusers); if (call.vphone_call.type() != mtpc_phoneCall) { LOG(("Call Error: Expected phoneCall in response to phone.confirmCall()")); finish(FinishType::Failed); return; } createAndStartController(call.vphone_call.c_phoneCall()); }).fail([this](const RPCError &error) { handleRequestError(error); }).send(); } void Call::startConfirmedCall(const MTPDphoneCall &call) { Expects(_type == Type::Incoming); auto firstBytes = bytesFromMTP(call.vg_a_or_b); if (_gaHash != openssl::Sha256(firstBytes)) { LOG(("Call Error: Wrong g_a hash received.")); finish(FinishType::Failed); return; } _ga = base::byte_vector(firstBytes.begin(), firstBytes.end()); auto computedAuthKey = MTP::CreateAuthKey(firstBytes, _randomPower, _dhConfig.p); if (computedAuthKey.empty()) { LOG(("Call Error: Could not compute mod-exp final.")); finish(FinishType::Failed); return; } MTP::AuthKey::FillData(_authKey, computedAuthKey); _keyFingerprint = ComputeFingerprint(_authKey); createAndStartController(call); } void Call::createAndStartController(const MTPDphoneCall &call) { _discardByTimeoutTimer.cancel(); if (!checkCallFields(call)) { return; } voip_config_t config = { 0 }; config.data_saving = DATA_SAVING_NEVER; #ifdef Q_OS_MAC config.enableAEC = (QSysInfo::macVersion() < QSysInfo::MV_10_7); #else // Q_OS_MAC config.enableAEC = true; #endif // Q_OS_MAC config.enableNS = true; config.enableAGC = true; config.init_timeout = Global::CallConnectTimeoutMs() / 1000; config.recv_timeout = Global::CallPacketTimeoutMs() / 1000; if (cDebug()) { auto callLogFolder = cWorkingDir() + qsl("DebugLogs"); auto callLogPath = callLogFolder + qsl("/last_call_log.txt"); auto callLogNative = QFile::encodeName(QDir::toNativeSeparators(callLogPath)); auto callLogBytesSrc = gsl::as_bytes(gsl::make_span(callLogNative)); auto callLogBytesDst = gsl::as_writeable_bytes(gsl::make_span(config.logFilePath)); if (callLogBytesSrc.size() + 1 <= callLogBytesDst.size()) { // +1 - zero-terminator QFile(callLogPath).remove(); QDir().mkpath(callLogFolder); base::copy_bytes(callLogBytesDst, callLogBytesSrc); } } std::vector<Endpoint> endpoints; ConvertEndpoint(endpoints, call.vconnection.c_phoneConnection()); for (int i = 0; i < call.valternative_connections.v.length(); i++) { ConvertEndpoint(endpoints, call.valternative_connections.v[i].c_phoneConnection()); } _controller = std::make_unique<tgvoip::VoIPController>(); if (_mute) { _controller->SetMicMute(_mute); } _controller->implData = static_cast<void*>(this); _controller->SetRemoteEndpoints(endpoints, true); _controller->SetConfig(&config); _controller->SetEncryptionKey(reinterpret_cast<char*>(_authKey.data()), (_type == Type::Outgoing)); _controller->SetStateCallback([](tgvoip::VoIPController *controller, int state) { static_cast<Call*>(controller->implData)->handleControllerStateChange(controller, state); }); _controller->Start(); _controller->Connect(); } void Call::handleControllerStateChange(tgvoip::VoIPController *controller, int state) { // NB! Can be called from an arbitrary thread! // Expects(controller == _controller.get()); This can be called from ~VoIPController()! Expects(controller->implData == static_cast<void*>(this)); switch (state) { case STATE_WAIT_INIT: { DEBUG_LOG(("Call Info: State changed to WaitingInit.")); setStateQueued(State::WaitingInit); } break; case STATE_WAIT_INIT_ACK: { DEBUG_LOG(("Call Info: State changed to WaitingInitAck.")); setStateQueued(State::WaitingInitAck); } break; case STATE_ESTABLISHED: { DEBUG_LOG(("Call Info: State changed to Established.")); setStateQueued(State::Established); } break; case STATE_FAILED: { auto error = controller->GetLastError(); LOG(("Call Info: State changed to Failed, error: %1.").arg(error)); setFailedQueued(error); } break; default: LOG(("Call Error: Unexpected state in handleStateChange: %1").arg(state)); } } template <typename T> bool Call::checkCallCommonFields(const T &call) { auto checkFailed = [this] { finish(FinishType::Failed); return false; }; if (call.vaccess_hash.v != _accessHash) { LOG(("Call Error: Wrong call access_hash.")); return checkFailed(); } auto adminId = (_type == Type::Outgoing) ? Auth().userId() : peerToUser(_user->id); auto participantId = (_type == Type::Outgoing) ? peerToUser(_user->id) : Auth().userId(); if (call.vadmin_id.v != adminId) { LOG(("Call Error: Wrong call admin_id %1, expected %2.").arg(call.vadmin_id.v).arg(adminId)); return checkFailed(); } if (call.vparticipant_id.v != participantId) { LOG(("Call Error: Wrong call participant_id %1, expected %2.").arg(call.vparticipant_id.v).arg(participantId)); return checkFailed(); } return true; } bool Call::checkCallFields(const MTPDphoneCall &call) { if (!checkCallCommonFields(call)) { return false; } if (call.vkey_fingerprint.v != _keyFingerprint) { LOG(("Call Error: Wrong call fingerprint.")); finish(FinishType::Failed); return false; } return true; } bool Call::checkCallFields(const MTPDphoneCallAccepted &call) { return checkCallCommonFields(call); } void Call::setState(State state) { if (_state == State::Failed) { return; } if (_state == State::FailedHangingUp && state != State::Failed) { return; } if (_state != state) { _state = state; _stateChanged.notify(state, true); if (true && _state != State::Starting && _state != State::Requesting && _state != State::Waiting && _state != State::WaitingIncoming && _state != State::Ringing) { _waitingTrack.reset(); } if (false || _state == State::Ended || _state == State::EndedByOtherDevice || _state == State::Failed || _state == State::Busy) { // Destroy controller before destroying Call Panel, // so that the panel hide animation is smooth. destroyController(); } switch (_state) { case State::Established: _startTime = getms(true); break; case State::ExchangingKeys: _delegate->playSound(Delegate::Sound::Connecting); break; case State::Ended: _delegate->playSound(Delegate::Sound::Ended); [[fallthrough]]; case State::EndedByOtherDevice: _delegate->callFinished(this); break; case State::Failed: _delegate->playSound(Delegate::Sound::Ended); _delegate->callFailed(this); break; case State::Busy: _delegate->playSound(Delegate::Sound::Busy); break; } } } void Call::finish(FinishType type, const MTPPhoneCallDiscardReason &reason) { Expects(type != FinishType::None); auto finalState = (type == FinishType::Ended) ? State::Ended : State::Failed; auto hangupState = (type == FinishType::Ended) ? State::HangingUp : State::FailedHangingUp; if (_state == State::Requesting) { _finishByTimeoutTimer.call(kHangupTimeoutMs, [this, finalState] { setState(finalState); }); _finishAfterRequestingCall = type; return; } if (_state == State::HangingUp || _state == State::FailedHangingUp || _state == State::EndedByOtherDevice || _state == State::Ended || _state == State::Failed) { return; } if (!_id) { setState(finalState); return; } setState(hangupState); auto duration = getDurationMs() / 1000; auto connectionId = _controller ? _controller->GetPreferredRelayID() : 0; _finishByTimeoutTimer.call(kHangupTimeoutMs, [this, finalState] { setState(finalState); }); request(MTPphone_DiscardCall(MTP_inputPhoneCall(MTP_long(_id), MTP_long(_accessHash)), MTP_int(duration), reason, MTP_long(connectionId))).done([this, finalState](const MTPUpdates &result) { // This could be destroyed by updates, so we set Ended after // updates being handled, but in a guarded way. InvokeQueued(this, [this, finalState] { setState(finalState); }); App::main()->sentUpdatesReceived(result); }).fail([this, finalState](const RPCError &error) { setState(finalState); }).send(); } void Call::setStateQueued(State state) { InvokeQueued(this, [this, state] { setState(state); }); } void Call::setFailedQueued(int error) { InvokeQueued(this, [this, error] { handleControllerError(error); }); } void Call::handleRequestError(const RPCError &error) { if (error.type() == qstr("USER_PRIVACY_RESTRICTED")) { Ui::show(Box<InformBox>(lng_call_error_not_available(lt_user, App::peerName(_user)))); } else if (error.type() == qstr("PARTICIPANT_VERSION_OUTDATED")) { Ui::show(Box<InformBox>(lng_call_error_outdated(lt_user, App::peerName(_user)))); } else if (error.type() == qstr("CALL_PROTOCOL_LAYER_INVALID")) { Ui::show(Box<InformBox>(Lang::Hard::CallErrorIncompatible().replace("{user}", App::peerName(_user)))); } finish(FinishType::Failed); } void Call::handleControllerError(int error) { if (error == TGVOIP_ERROR_INCOMPATIBLE) { Ui::show(Box<InformBox>(Lang::Hard::CallErrorIncompatible().replace("{user}", App::peerName(_user)))); } else if (error == TGVOIP_ERROR_AUDIO_IO) { Ui::show(Box<InformBox>(lang(lng_call_error_audio_io))); } finish(FinishType::Failed); } void Call::destroyController() { if (_controller) { DEBUG_LOG(("Call Info: Destroying call controller..")); _controller.reset(); DEBUG_LOG(("Call Info: Call controller destroyed.")); } } Call::~Call() { destroyController(); } void UpdateConfig(const std::map<std::string, std::string> &data) { tgvoip::ServerConfig::GetSharedInstance()->Update(data); } } // namespace Calls ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/calls/calls_call.h���������������������������������������������0000664�0000000�0000000�00000010127�13262451251�0022764�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "base/weak_ptr.h" #include "base/timer.h" #include "mtproto/sender.h" #include "mtproto/auth_key.h" namespace Media { namespace Audio { class Track; } // namespace Audio } // namespace Media namespace tgvoip { class VoIPController; } // namespace tgvoip namespace Calls { struct DhConfig { int32 version = 0; int32 g = 0; std::vector<gsl::byte> p; }; class Call : public base::has_weak_ptr, private MTP::Sender { public: class Delegate { public: virtual DhConfig getDhConfig() const = 0; virtual void callFinished(not_null<Call*> call) = 0; virtual void callFailed(not_null<Call*> call) = 0; virtual void callRedial(not_null<Call*> call) = 0; enum class Sound { Connecting, Busy, Ended, }; virtual void playSound(Sound sound) = 0; }; static constexpr auto kRandomPowerSize = 256; static constexpr auto kSha256Size = 32; static constexpr auto kSoundSampleMs = 100; enum class Type { Incoming, Outgoing, }; Call(not_null<Delegate*> delegate, not_null<UserData*> user, Type type); Type type() const { return _type; } not_null<UserData*> user() const { return _user; } bool isIncomingWaiting() const; void start(base::const_byte_span random); bool handleUpdate(const MTPPhoneCall &call); enum State { Starting, WaitingInit, WaitingInitAck, Established, FailedHangingUp, Failed, HangingUp, Ended, EndedByOtherDevice, ExchangingKeys, Waiting, Requesting, WaitingIncoming, Ringing, Busy, }; State state() const { return _state; } base::Observable<State> &stateChanged() { return _stateChanged; } void setMute(bool mute); bool isMute() const { return _mute; } base::Observable<bool> &muteChanged() { return _muteChanged; } TimeMs getDurationMs() const; float64 getWaitingSoundPeakValue() const; void answer(); void hangup(); void redial(); bool isKeyShaForFingerprintReady() const; std::array<gsl::byte, kSha256Size> getKeyShaForFingerprint() const; QString getDebugLog() const; ~Call(); private: enum class FinishType { None, Ended, Failed, }; void handleRequestError(const RPCError &error); void handleControllerError(int error); void finish(FinishType type, const MTPPhoneCallDiscardReason &reason = MTP_phoneCallDiscardReasonDisconnect()); void startOutgoing(); void startIncoming(); void startWaitingTrack(); void generateModExpFirst(base::const_byte_span randomSeed); void handleControllerStateChange(tgvoip::VoIPController *controller, int state); void createAndStartController(const MTPDphoneCall &call); template <typename T> bool checkCallCommonFields(const T &call); bool checkCallFields(const MTPDphoneCall &call); bool checkCallFields(const MTPDphoneCallAccepted &call); void confirmAcceptedCall(const MTPDphoneCallAccepted &call); void startConfirmedCall(const MTPDphoneCall &call); void setState(State state); void setStateQueued(State state); void setFailedQueued(int error); void destroyController(); not_null<Delegate*> _delegate; not_null<UserData*> _user; Type _type = Type::Outgoing; State _state = State::Starting; FinishType _finishAfterRequestingCall = FinishType::None; bool _answerAfterDhConfigReceived = false; base::Observable<State> _stateChanged; TimeMs _startTime = 0; base::DelayedCallTimer _finishByTimeoutTimer; base::Timer _discardByTimeoutTimer; bool _mute = false; base::Observable<bool> _muteChanged; DhConfig _dhConfig; std::vector<gsl::byte> _ga; std::vector<gsl::byte> _gb; std::array<gsl::byte, kSha256Size> _gaHash; std::array<gsl::byte, kRandomPowerSize> _randomPower; MTP::AuthKey::Data _authKey; MTPPhoneCallProtocol _protocol; uint64 _id = 0; uint64 _accessHash = 0; uint64 _keyFingerprint = 0; std::unique_ptr<tgvoip::VoIPController> _controller; std::unique_ptr<Media::Audio::Track> _waitingTrack; }; void UpdateConfig(const std::map<std::string, std::string> &data); } // namespace Calls �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/calls/calls_emoji_fingerprint.cpp������������������������������0000664�0000000�0000000�00000021221�13262451251�0026113�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "calls/calls_emoji_fingerprint.h" #include "calls/calls_call.h" namespace Calls { namespace { ushort Data[] = { 0xd83d, 0xde09, 0xd83d, 0xde0d, 0xd83d, 0xde1b, 0xd83d, 0xde2d, 0xd83d, 0xde31, 0xd83d, 0xde21, 0xd83d, 0xde0e, 0xd83d, 0xde34, 0xd83d, 0xde35, 0xd83d, 0xde08, 0xd83d, 0xde2c, 0xd83d, 0xde07, 0xd83d, 0xde0f, 0xd83d, 0xdc6e, 0xd83d, 0xdc77, 0xd83d, 0xdc82, 0xd83d, 0xdc76, 0xd83d, 0xdc68, 0xd83d, 0xdc69, 0xd83d, 0xdc74, 0xd83d, 0xdc75, 0xd83d, 0xde3b, 0xd83d, 0xde3d, 0xd83d, 0xde40, 0xd83d, 0xdc7a, 0xd83d, 0xde48, 0xd83d, 0xde49, 0xd83d, 0xde4a, 0xd83d, 0xdc80, 0xd83d, 0xdc7d, 0xd83d, 0xdca9, 0xd83d, 0xdd25, 0xd83d, 0xdca5, 0xd83d, 0xdca4, 0xd83d, 0xdc42, 0xd83d, 0xdc40, 0xd83d, 0xdc43, 0xd83d, 0xdc45, 0xd83d, 0xdc44, 0xd83d, 0xdc4d, 0xd83d, 0xdc4e, 0xd83d, 0xdc4c, 0xd83d, 0xdc4a, 0x270c, 0x270b, 0xd83d, 0xdc50, 0xd83d, 0xdc46, 0xd83d, 0xdc47, 0xd83d, 0xdc49, 0xd83d, 0xdc48, 0xd83d, 0xde4f, 0xd83d, 0xdc4f, 0xd83d, 0xdcaa, 0xd83d, 0xdeb6, 0xd83c, 0xdfc3, 0xd83d, 0xdc83, 0xd83d, 0xdc6b, 0xd83d, 0xdc6a, 0xd83d, 0xdc6c, 0xd83d, 0xdc6d, 0xd83d, 0xdc85, 0xd83c, 0xdfa9, 0xd83d, 0xdc51, 0xd83d, 0xdc52, 0xd83d, 0xdc5f, 0xd83d, 0xdc5e, 0xd83d, 0xdc60, 0xd83d, 0xdc55, 0xd83d, 0xdc57, 0xd83d, 0xdc56, 0xd83d, 0xdc59, 0xd83d, 0xdc5c, 0xd83d, 0xdc53, 0xd83c, 0xdf80, 0xd83d, 0xdc84, 0xd83d, 0xdc9b, 0xd83d, 0xdc99, 0xd83d, 0xdc9c, 0xd83d, 0xdc9a, 0xd83d, 0xdc8d, 0xd83d, 0xdc8e, 0xd83d, 0xdc36, 0xd83d, 0xdc3a, 0xd83d, 0xdc31, 0xd83d, 0xdc2d, 0xd83d, 0xdc39, 0xd83d, 0xdc30, 0xd83d, 0xdc38, 0xd83d, 0xdc2f, 0xd83d, 0xdc28, 0xd83d, 0xdc3b, 0xd83d, 0xdc37, 0xd83d, 0xdc2e, 0xd83d, 0xdc17, 0xd83d, 0xdc34, 0xd83d, 0xdc11, 0xd83d, 0xdc18, 0xd83d, 0xdc3c, 0xd83d, 0xdc27, 0xd83d, 0xdc25, 0xd83d, 0xdc14, 0xd83d, 0xdc0d, 0xd83d, 0xdc22, 0xd83d, 0xdc1b, 0xd83d, 0xdc1d, 0xd83d, 0xdc1c, 0xd83d, 0xdc1e, 0xd83d, 0xdc0c, 0xd83d, 0xdc19, 0xd83d, 0xdc1a, 0xd83d, 0xdc1f, 0xd83d, 0xdc2c, 0xd83d, 0xdc0b, 0xd83d, 0xdc10, 0xd83d, 0xdc0a, 0xd83d, 0xdc2b, 0xd83c, 0xdf40, 0xd83c, 0xdf39, 0xd83c, 0xdf3b, 0xd83c, 0xdf41, 0xd83c, 0xdf3e, 0xd83c, 0xdf44, 0xd83c, 0xdf35, 0xd83c, 0xdf34, 0xd83c, 0xdf33, 0xd83c, 0xdf1e, 0xd83c, 0xdf1a, 0xd83c, 0xdf19, 0xd83c, 0xdf0e, 0xd83c, 0xdf0b, 0x26a1, 0x2614, 0x2744, 0x26c4, 0xd83c, 0xdf00, 0xd83c, 0xdf08, 0xd83c, 0xdf0a, 0xd83c, 0xdf93, 0xd83c, 0xdf86, 0xd83c, 0xdf83, 0xd83d, 0xdc7b, 0xd83c, 0xdf85, 0xd83c, 0xdf84, 0xd83c, 0xdf81, 0xd83c, 0xdf88, 0xd83d, 0xdd2e, 0xd83c, 0xdfa5, 0xd83d, 0xdcf7, 0xd83d, 0xdcbf, 0xd83d, 0xdcbb, 0x260e, 0xd83d, 0xdce1, 0xd83d, 0xdcfa, 0xd83d, 0xdcfb, 0xd83d, 0xdd09, 0xd83d, 0xdd14, 0x23f3, 0x23f0, 0x231a, 0xd83d, 0xdd12, 0xd83d, 0xdd11, 0xd83d, 0xdd0e, 0xd83d, 0xdca1, 0xd83d, 0xdd26, 0xd83d, 0xdd0c, 0xd83d, 0xdd0b, 0xd83d, 0xdebf, 0xd83d, 0xdebd, 0xd83d, 0xdd27, 0xd83d, 0xdd28, 0xd83d, 0xdeaa, 0xd83d, 0xdeac, 0xd83d, 0xdca3, 0xd83d, 0xdd2b, 0xd83d, 0xdd2a, 0xd83d, 0xdc8a, 0xd83d, 0xdc89, 0xd83d, 0xdcb0, 0xd83d, 0xdcb5, 0xd83d, 0xdcb3, 0x2709, 0xd83d, 0xdceb, 0xd83d, 0xdce6, 0xd83d, 0xdcc5, 0xd83d, 0xdcc1, 0x2702, 0xd83d, 0xdccc, 0xd83d, 0xdcce, 0x2712, 0x270f, 0xd83d, 0xdcd0, 0xd83d, 0xdcda, 0xd83d, 0xdd2c, 0xd83d, 0xdd2d, 0xd83c, 0xdfa8, 0xd83c, 0xdfac, 0xd83c, 0xdfa4, 0xd83c, 0xdfa7, 0xd83c, 0xdfb5, 0xd83c, 0xdfb9, 0xd83c, 0xdfbb, 0xd83c, 0xdfba, 0xd83c, 0xdfb8, 0xd83d, 0xdc7e, 0xd83c, 0xdfae, 0xd83c, 0xdccf, 0xd83c, 0xdfb2, 0xd83c, 0xdfaf, 0xd83c, 0xdfc8, 0xd83c, 0xdfc0, 0x26bd, 0x26be, 0xd83c, 0xdfbe, 0xd83c, 0xdfb1, 0xd83c, 0xdfc9, 0xd83c, 0xdfb3, 0xd83c, 0xdfc1, 0xd83c, 0xdfc7, 0xd83c, 0xdfc6, 0xd83c, 0xdfca, 0xd83c, 0xdfc4, 0x2615, 0xd83c, 0xdf7c, 0xd83c, 0xdf7a, 0xd83c, 0xdf77, 0xd83c, 0xdf74, 0xd83c, 0xdf55, 0xd83c, 0xdf54, 0xd83c, 0xdf5f, 0xd83c, 0xdf57, 0xd83c, 0xdf71, 0xd83c, 0xdf5a, 0xd83c, 0xdf5c, 0xd83c, 0xdf61, 0xd83c, 0xdf73, 0xd83c, 0xdf5e, 0xd83c, 0xdf69, 0xd83c, 0xdf66, 0xd83c, 0xdf82, 0xd83c, 0xdf70, 0xd83c, 0xdf6a, 0xd83c, 0xdf6b, 0xd83c, 0xdf6d, 0xd83c, 0xdf6f, 0xd83c, 0xdf4e, 0xd83c, 0xdf4f, 0xd83c, 0xdf4a, 0xd83c, 0xdf4b, 0xd83c, 0xdf52, 0xd83c, 0xdf47, 0xd83c, 0xdf49, 0xd83c, 0xdf53, 0xd83c, 0xdf51, 0xd83c, 0xdf4c, 0xd83c, 0xdf50, 0xd83c, 0xdf4d, 0xd83c, 0xdf46, 0xd83c, 0xdf45, 0xd83c, 0xdf3d, 0xd83c, 0xdfe1, 0xd83c, 0xdfe5, 0xd83c, 0xdfe6, 0x26ea, 0xd83c, 0xdff0, 0x26fa, 0xd83c, 0xdfed, 0xd83d, 0xddfb, 0xd83d, 0xddfd, 0xd83c, 0xdfa0, 0xd83c, 0xdfa1, 0x26f2, 0xd83c, 0xdfa2, 0xd83d, 0xdea2, 0xd83d, 0xdea4, 0x2693, 0xd83d, 0xde80, 0x2708, 0xd83d, 0xde81, 0xd83d, 0xde82, 0xd83d, 0xde8b, 0xd83d, 0xde8e, 0xd83d, 0xde8c, 0xd83d, 0xde99, 0xd83d, 0xde97, 0xd83d, 0xde95, 0xd83d, 0xde9b, 0xd83d, 0xdea8, 0xd83d, 0xde94, 0xd83d, 0xde92, 0xd83d, 0xde91, 0xd83d, 0xdeb2, 0xd83d, 0xdea0, 0xd83d, 0xde9c, 0xd83d, 0xdea6, 0x26a0, 0xd83d, 0xdea7, 0x26fd, 0xd83c, 0xdfb0, 0xd83d, 0xddff, 0xd83c, 0xdfaa, 0xd83c, 0xdfad, 0xd83c, 0xddef, 0xd83c, 0xddf5, 0xd83c, 0xddf0, 0xd83c, 0xddf7, 0xd83c, 0xdde9, 0xd83c, 0xddea, 0xd83c, 0xdde8, 0xd83c, 0xddf3, 0xd83c, 0xddfa, 0xd83c, 0xddf8, 0xd83c, 0xddeb, 0xd83c, 0xddf7, 0xd83c, 0xddea, 0xd83c, 0xddf8, 0xd83c, 0xddee, 0xd83c, 0xddf9, 0xd83c, 0xddf7, 0xd83c, 0xddfa, 0xd83c, 0xddec, 0xd83c, 0xdde7, 0x0031, 0x20e3, 0x0032, 0x20e3, 0x0033, 0x20e3, 0x0034, 0x20e3, 0x0035, 0x20e3, 0x0036, 0x20e3, 0x0037, 0x20e3, 0x0038, 0x20e3, 0x0039, 0x20e3, 0x0030, 0x20e3, 0xd83d, 0xdd1f, 0x2757, 0x2753, 0x2665, 0x2666, 0xd83d, 0xdcaf, 0xd83d, 0xdd17, 0xd83d, 0xdd31, 0xd83d, 0xdd34, 0xd83d, 0xdd35, 0xd83d, 0xdd36, 0xd83d, 0xdd37 }; ushort Offsets[] = { 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 87, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 166, 168, 170, 172, 174, 176, 178, 180, 182, 184, 186, 188, 190, 192, 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220, 222, 224, 226, 228, 230, 232, 234, 236, 238, 240, 242, 244, 246, 248, 250, 252, 254, 256, 258, 259, 260, 261, 262, 264, 266, 268, 270, 272, 274, 276, 278, 280, 282, 284, 286, 288, 290, 292, 294, 295, 297, 299, 301, 303, 305, 306, 307, 308, 310, 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 332, 334, 336, 338, 340, 342, 344, 346, 348, 350, 351, 353, 355, 357, 359, 360, 362, 364, 365, 366, 368, 370, 372, 374, 376, 378, 380, 382, 384, 386, 388, 390, 392, 394, 396, 398, 400, 402, 404, 406, 407, 408, 410, 412, 414, 416, 418, 420, 422, 424, 426, 427, 429, 431, 433, 435, 437, 439, 441, 443, 445, 447, 449, 451, 453, 455, 457, 459, 461, 463, 465, 467, 469, 471, 473, 475, 477, 479, 481, 483, 485, 487, 489, 491, 493, 495, 497, 499, 501, 503, 505, 507, 508, 510, 511, 513, 515, 517, 519, 521, 522, 524, 526, 528, 529, 531, 532, 534, 536, 538, 540, 542, 544, 546, 548, 550, 552, 554, 556, 558, 560, 562, 564, 566, 567, 569, 570, 572, 574, 576, 578, 582, 586, 590, 594, 598, 602, 606, 610, 614, 618, 620, 622, 624, 626, 628, 630, 632, 634, 636, 638, 640, 641, 642, 643, 644, 646, 648, 650, 652, 654, 656, 658 }; uint64 ComputeEmojiIndex(base::const_byte_span bytes) { Expects(bytes.size() == 8); return ((gsl::to_integer<uint64>(bytes[0]) & 0x7F) << 56) | (gsl::to_integer<uint64>(bytes[1]) << 48) | (gsl::to_integer<uint64>(bytes[2]) << 40) | (gsl::to_integer<uint64>(bytes[3]) << 32) | (gsl::to_integer<uint64>(bytes[4]) << 24) | (gsl::to_integer<uint64>(bytes[5]) << 16) | (gsl::to_integer<uint64>(bytes[6]) << 8) | (gsl::to_integer<uint64>(bytes[7])); } } // namespace std::vector<EmojiPtr> ComputeEmojiFingerprint(not_null<Call*> call) { auto result = std::vector<EmojiPtr>(); constexpr auto EmojiCount = (base::array_size(Offsets) - 1); for (auto index = 0; index != EmojiCount; ++index) { auto offset = Offsets[index]; auto size = Offsets[index + 1] - offset; auto string = QString::fromRawData(reinterpret_cast<QChar*>(Data + offset), size); auto emoji = Ui::Emoji::Find(string); Assert(emoji != nullptr); } if (call->isKeyShaForFingerprintReady()) { auto sha256 = call->getKeyShaForFingerprint(); constexpr auto kPartSize = 8; for (auto partOffset = 0; partOffset != sha256.size(); partOffset += kPartSize) { auto value = ComputeEmojiIndex(gsl::make_span(sha256).subspan(partOffset, kPartSize)); auto index = value % EmojiCount; auto offset = Offsets[index]; auto size = Offsets[index + 1] - offset; auto string = QString::fromRawData(reinterpret_cast<QChar*>(Data + offset), size); auto emoji = Ui::Emoji::Find(string); Assert(emoji != nullptr); result.push_back(emoji); } } return result; } } // namespace Calls �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/calls/calls_emoji_fingerprint.h��������������������������������0000664�0000000�0000000�00000000571�13262451251�0025565�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace Calls { class Call; std::vector<EmojiPtr> ComputeEmojiFingerprint(not_null<Call*> call); } // namespace Calls ���������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/calls/calls_instance.cpp���������������������������������������0000664�0000000�0000000�00000021374�13262451251�0024216�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "calls/calls_instance.h" #include "mtproto/connection.h" #include "messenger.h" #include "auth_session.h" #include "apiwrap.h" #include "lang/lang_keys.h" #include "boxes/confirm_box.h" #include "calls/calls_call.h" #include "calls/calls_panel.h" #include "media/media_audio_track.h" #include "boxes/rate_call_box.h" namespace Calls { namespace { constexpr auto kServerConfigUpdateTimeoutMs = 24 * 3600 * TimeMs(1000); } // namespace Instance::Instance() = default; void Instance::startOutgoingCall(not_null<UserData*> user) { if (alreadyInCall()) { // Already in a call. _currentCallPanel->showAndActivate(); return; } if (user->callsStatus() == UserData::CallsStatus::Private) { // Request full user once more to refresh the setting in case it was changed. Auth().api().requestFullPeer(user); Ui::show(Box<InformBox>(lng_call_error_not_available(lt_user, App::peerName(user)))); return; } createCall(user, Call::Type::Outgoing); } void Instance::callFinished(not_null<Call*> call) { destroyCall(call); } void Instance::callFailed(not_null<Call*> call) { destroyCall(call); } void Instance::callRedial(not_null<Call*> call) { if (_currentCall.get() == call) { refreshDhConfig(); } } void Instance::playSound(Sound sound) { switch (sound) { case Sound::Busy: { if (!_callBusyTrack) { _callBusyTrack = Media::Audio::Current().createTrack(); _callBusyTrack->fillFromFile( Auth().settings().getSoundPath(qsl("call_busy"))); } _callBusyTrack->playOnce(); } break; case Sound::Ended: { if (!_callEndedTrack) { _callEndedTrack = Media::Audio::Current().createTrack(); _callEndedTrack->fillFromFile( Auth().settings().getSoundPath(qsl("call_end"))); } _callEndedTrack->playOnce(); } break; case Sound::Connecting: { if (!_callConnectingTrack) { _callConnectingTrack = Media::Audio::Current().createTrack(); _callConnectingTrack->fillFromFile( Auth().settings().getSoundPath(qsl("call_connect"))); } _callConnectingTrack->playOnce(); } break; } } void Instance::destroyCall(not_null<Call*> call) { if (_currentCall.get() == call) { destroyCurrentPanel(); _currentCall.reset(); _currentCallChanged.notify(nullptr, true); if (App::quitting()) { LOG(("Calls::Instance doesn't prevent quit any more.")); } Messenger::Instance().quitPreventFinished(); } } void Instance::destroyCurrentPanel() { _pendingPanels.erase(std::remove_if(_pendingPanels.begin(), _pendingPanels.end(), [](auto &&panel) { return !panel; }), _pendingPanels.end()); _pendingPanels.push_back(_currentCallPanel.release()); _pendingPanels.back()->hideAndDestroy(); // Always queues the destruction. } void Instance::createCall(not_null<UserData*> user, Call::Type type) { auto call = std::make_unique<Call>(getCallDelegate(), user, type);; if (_currentCall) { _currentCallPanel->replaceCall(call.get()); std::swap(_currentCall, call); call->hangup(); } else { _currentCallPanel = std::make_unique<Panel>(call.get()); _currentCall = std::move(call); } _currentCallChanged.notify(_currentCall.get(), true); refreshServerConfig(); refreshDhConfig(); } void Instance::refreshDhConfig() { Expects(_currentCall != nullptr); request(MTPmessages_GetDhConfig( MTP_int(_dhConfig.version), MTP_int(Call::kRandomPowerSize) )).done([this, call = base::make_weak(_currentCall)]( const MTPmessages_DhConfig &result) { auto random = base::const_byte_span(); switch (result.type()) { case mtpc_messages_dhConfig: { auto &config = result.c_messages_dhConfig(); if (!MTP::IsPrimeAndGood(bytesFromMTP(config.vp), config.vg.v)) { LOG(("API Error: bad p/g received in dhConfig.")); callFailed(call.get()); return; } _dhConfig.g = config.vg.v; _dhConfig.p = byteVectorFromMTP(config.vp); random = bytesFromMTP(config.vrandom); } break; case mtpc_messages_dhConfigNotModified: { auto &config = result.c_messages_dhConfigNotModified(); random = bytesFromMTP(config.vrandom); if (!_dhConfig.g || _dhConfig.p.empty()) { LOG(("API Error: dhConfigNotModified on zero version.")); callFailed(call.get()); return; } } break; default: Unexpected("Type in messages.getDhConfig"); } if (random.size() != Call::kRandomPowerSize) { LOG(("API Error: dhConfig random bytes wrong size: %1").arg(random.size())); callFailed(call.get()); return; } if (call) { call->start(random); } }).fail([this, call = base::make_weak(_currentCall)]( const RPCError &error) { if (!call) { DEBUG_LOG(("API Warning: call was destroyed before got dhConfig.")); return; } callFailed(call.get()); }).send(); } void Instance::refreshServerConfig() { if (_serverConfigRequestId) { return; } if (_lastServerConfigUpdateTime && (getms(true) - _lastServerConfigUpdateTime) < kServerConfigUpdateTimeoutMs) { return; } _serverConfigRequestId = request(MTPphone_GetCallConfig()).done([this](const MTPDataJSON &result) { _serverConfigRequestId = 0; _lastServerConfigUpdateTime = getms(true); auto configUpdate = std::map<std::string, std::string>(); auto bytes = bytesFromMTP(result.c_dataJSON().vdata); auto error = QJsonParseError { 0, QJsonParseError::NoError }; auto document = QJsonDocument::fromJson(QByteArray::fromRawData(reinterpret_cast<const char*>(bytes.data()), bytes.size()), &error); if (error.error != QJsonParseError::NoError) { LOG(("API Error: Failed to parse call config JSON, error: %1").arg(error.errorString())); return; } else if (!document.isObject()) { LOG(("API Error: Not an object received in call config JSON.")); return; } auto parseValue = [](QJsonValueRef data) -> std::string { switch (data.type()) { case QJsonValue::String: return data.toString().toStdString(); case QJsonValue::Double: return QString::number(data.toDouble(), 'f').toStdString(); case QJsonValue::Bool: return data.toBool() ? "true" : "false"; case QJsonValue::Null: { LOG(("API Warning: null field in call config JSON.")); } return "null"; case QJsonValue::Undefined: { LOG(("API Warning: undefined field in call config JSON.")); } return "undefined"; case QJsonValue::Object: case QJsonValue::Array: { LOG(("API Warning: complex field in call config JSON.")); QJsonDocument serializer; if (data.isArray()) { serializer.setArray(data.toArray()); } else { serializer.setObject(data.toObject()); } auto byteArray = serializer.toJson(QJsonDocument::Compact); return std::string(byteArray.constData(), byteArray.size()); } break; } Unexpected("Type in Json parse."); }; auto object = document.object(); for (auto i = object.begin(), e = object.end(); i != e; ++i) { auto key = i.key().toStdString(); auto value = parseValue(i.value()); configUpdate[key] = value; } UpdateConfig(configUpdate); }).fail([this](const RPCError &error) { _serverConfigRequestId = 0; }).send(); } void Instance::handleUpdate(const MTPDupdatePhoneCall& update) { handleCallUpdate(update.vphone_call); } void Instance::showInfoPanel(not_null<Call*> call) { if (_currentCall.get() == call) { _currentCallPanel->showAndActivate(); } } bool Instance::isQuitPrevent() { if (!_currentCall || _currentCall->isIncomingWaiting()) { return false; } _currentCall->hangup(); if (!_currentCall) { return false; } LOG(("Calls::Instance prevents quit, saving drafts...")); return true; } void Instance::handleCallUpdate(const MTPPhoneCall &call) { if (call.type() == mtpc_phoneCallRequested) { auto &phoneCall = call.c_phoneCallRequested(); auto user = App::userLoaded(phoneCall.vadmin_id.v); if (!user) { LOG(("API Error: User not loaded for phoneCallRequested.")); } else if (user->isSelf()) { LOG(("API Error: Self found in phoneCallRequested.")); } if (alreadyInCall() || !user || user->isSelf()) { request(MTPphone_DiscardCall(MTP_inputPhoneCall(phoneCall.vid, phoneCall.vaccess_hash), MTP_int(0), MTP_phoneCallDiscardReasonBusy(), MTP_long(0))).send(); } else if (phoneCall.vdate.v + (Global::CallRingTimeoutMs() / 1000) < unixtime()) { LOG(("Ignoring too old call.")); } else { createCall(user, Call::Type::Incoming); _currentCall->handleUpdate(call); } } else if (!_currentCall || !_currentCall->handleUpdate(call)) { DEBUG_LOG(("API Warning: unexpected phone call update %1").arg(call.type())); } } bool Instance::alreadyInCall() { return (_currentCall && _currentCall->state() != Call::State::Busy); } Instance::~Instance() { for (auto panel : _pendingPanels) { if (panel) { delete panel; } } } Instance &Current() { return Auth().calls(); } } // namespace Calls ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/calls/calls_instance.h�����������������������������������������0000664�0000000�0000000�00000004072�13262451251�0023657�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "mtproto/sender.h" #include "calls/calls_call.h" namespace Media { namespace Audio { class Track; } // namespace Audio } // namespace Media namespace Calls { class Panel; class Instance : private MTP::Sender, private Call::Delegate, private base::Subscriber { public: Instance(); void startOutgoingCall(not_null<UserData*> user); void handleUpdate(const MTPDupdatePhoneCall &update); void showInfoPanel(not_null<Call*> call); base::Observable<Call*> ¤tCallChanged() { return _currentCallChanged; } base::Observable<FullMsgId> &newServiceMessage() { return _newServiceMessage; } bool isQuitPrevent(); ~Instance(); private: not_null<Call::Delegate*> getCallDelegate() { return static_cast<Call::Delegate*>(this); } DhConfig getDhConfig() const override { return _dhConfig; } void callFinished(not_null<Call*> call) override; void callFailed(not_null<Call*> call) override; void callRedial(not_null<Call*> call) override; using Sound = Call::Delegate::Sound; void playSound(Sound sound) override; void createCall(not_null<UserData*> user, Call::Type type); void destroyCall(not_null<Call*> call); void destroyCurrentPanel(); void refreshDhConfig(); void refreshServerConfig(); bool alreadyInCall(); void handleCallUpdate(const MTPPhoneCall &call); DhConfig _dhConfig; TimeMs _lastServerConfigUpdateTime = 0; mtpRequestId _serverConfigRequestId = 0; std::unique_ptr<Call> _currentCall; std::unique_ptr<Panel> _currentCallPanel; base::Observable<Call*> _currentCallChanged; base::Observable<FullMsgId> _newServiceMessage; std::vector<QPointer<Panel>> _pendingPanels; std::unique_ptr<Media::Audio::Track> _callConnectingTrack; std::unique_ptr<Media::Audio::Track> _callEndedTrack; std::unique_ptr<Media::Audio::Track> _callBusyTrack; }; Instance &Current(); } // namespace Calls ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/calls/calls_panel.cpp������������������������������������������0000664�0000000�0000000�00000062332�13262451251�0023510�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "calls/calls_panel.h" #include "data/data_photo.h" #include "data/data_session.h" #include "calls/calls_emoji_fingerprint.h" #include "styles/style_calls.h" #include "styles/style_history.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" #include "ui/widgets/shadow.h" #include "ui/effects/ripple_animation.h" #include "ui/wrap/fade_wrap.h" #include "ui/empty_userpic.h" #include "messenger.h" #include "mainwindow.h" #include "lang/lang_keys.h" #include "auth_session.h" #include "apiwrap.h" #include "observer_peer.h" #include "platform/platform_specific.h" #include "window/main_window.h" #include "layout.h" namespace Calls { namespace { constexpr auto kTooltipShowTimeoutMs = 1000; } // namespace class Panel::Button : public Ui::RippleButton { public: Button(QWidget *parent, const style::CallButton &stFrom, const style::CallButton *stTo = nullptr); void setProgress(float64 progress); void setOuterValue(float64 value); protected: void paintEvent(QPaintEvent *e) override; void onStateChanged(State was, StateChangeSource source) override; QImage prepareRippleMask() const override; QPoint prepareRippleStartPosition() const override; private: QPoint iconPosition(not_null<const style::CallButton*> st) const; void mixIconMasks(); not_null<const style::CallButton*> _stFrom; const style::CallButton *_stTo = nullptr; float64 _progress = 0.; QImage _bgMask, _bg; QPixmap _bgFrom, _bgTo; QImage _iconMixedMask, _iconFrom, _iconTo, _iconMixed; float64 _outerValue = 0.; Animation _outerAnimation; }; Panel::Button::Button(QWidget *parent, const style::CallButton &stFrom, const style::CallButton *stTo) : Ui::RippleButton(parent, stFrom.button.ripple) , _stFrom(&stFrom) , _stTo(stTo) { resize(_stFrom->button.width, _stFrom->button.height); _bgMask = prepareRippleMask(); _bgFrom = App::pixmapFromImageInPlace(style::colorizeImage(_bgMask, _stFrom->bg)); if (_stTo) { Assert(_stFrom->button.width == _stTo->button.width); Assert(_stFrom->button.height == _stTo->button.height); Assert(_stFrom->button.rippleAreaPosition == _stTo->button.rippleAreaPosition); Assert(_stFrom->button.rippleAreaSize == _stTo->button.rippleAreaSize); _bg = QImage(_bgMask.size(), QImage::Format_ARGB32_Premultiplied); _bg.setDevicePixelRatio(cRetinaFactor()); _bgTo = App::pixmapFromImageInPlace(style::colorizeImage(_bgMask, _stTo->bg)); _iconMixedMask = QImage(_bgMask.size(), QImage::Format_ARGB32_Premultiplied); _iconMixedMask.setDevicePixelRatio(cRetinaFactor()); _iconFrom = QImage(_bgMask.size(), QImage::Format_ARGB32_Premultiplied); _iconFrom.setDevicePixelRatio(cRetinaFactor()); _iconFrom.fill(Qt::black); { Painter p(&_iconFrom); p.drawImage((_stFrom->button.rippleAreaSize - _stFrom->button.icon.width()) / 2, (_stFrom->button.rippleAreaSize - _stFrom->button.icon.height()) / 2, _stFrom->button.icon.instance(Qt::white)); } _iconTo = QImage(_bgMask.size(), QImage::Format_ARGB32_Premultiplied); _iconTo.setDevicePixelRatio(cRetinaFactor()); _iconTo.fill(Qt::black); { Painter p(&_iconTo); p.drawImage((_stTo->button.rippleAreaSize - _stTo->button.icon.width()) / 2, (_stTo->button.rippleAreaSize - _stTo->button.icon.height()) / 2, _stTo->button.icon.instance(Qt::white)); } _iconMixed = QImage(_bgMask.size(), QImage::Format_ARGB32_Premultiplied); _iconMixed.setDevicePixelRatio(cRetinaFactor()); } } void Panel::Button::setOuterValue(float64 value) { if (_outerValue != value) { _outerAnimation.start([this] { if (_progress == 0. || _progress == 1.) { update(); } }, _outerValue, value, Call::kSoundSampleMs); _outerValue = value; } } void Panel::Button::setProgress(float64 progress) { _progress = progress; update(); } void Panel::Button::paintEvent(QPaintEvent *e) { Painter p(this); auto ms = getms(); auto bgPosition = myrtlpoint(_stFrom->button.rippleAreaPosition); auto paintFrom = (_progress == 0.) || !_stTo; auto paintTo = !paintFrom && (_progress == 1.); auto outerValue = _outerAnimation.current(ms, _outerValue); if (outerValue > 0.) { auto outerRadius = paintFrom ? _stFrom->outerRadius : paintTo ? _stTo->outerRadius : (_stFrom->outerRadius * (1. - _progress) + _stTo->outerRadius * _progress); auto outerPixels = outerValue * outerRadius; auto outerRect = QRectF(myrtlrect(bgPosition.x(), bgPosition.y(), _stFrom->button.rippleAreaSize, _stFrom->button.rippleAreaSize)); outerRect = outerRect.marginsAdded(QMarginsF(outerPixels, outerPixels, outerPixels, outerPixels)); PainterHighQualityEnabler hq(p); if (paintFrom) { p.setBrush(_stFrom->outerBg); } else if (paintTo) { p.setBrush(_stTo->outerBg); } else { p.setBrush(anim::brush(_stFrom->outerBg, _stTo->outerBg, _progress)); } p.setPen(Qt::NoPen); p.drawEllipse(outerRect); } if (paintFrom) { p.drawPixmap(bgPosition, _bgFrom); } else if (paintTo) { p.drawPixmap(bgPosition, _bgTo); } else { style::colorizeImage(_bgMask, anim::color(_stFrom->bg, _stTo->bg, _progress), &_bg); p.drawImage(bgPosition, _bg); } auto rippleColorInterpolated = QColor(); auto rippleColorOverride = &rippleColorInterpolated; if (paintFrom) { rippleColorOverride = nullptr; } else if (paintTo) { rippleColorOverride = &_stTo->button.ripple.color->c; } else { rippleColorInterpolated = anim::color(_stFrom->button.ripple.color, _stTo->button.ripple.color, _progress); } paintRipple(p, _stFrom->button.rippleAreaPosition.x(), _stFrom->button.rippleAreaPosition.y(), ms, rippleColorOverride); auto positionFrom = iconPosition(_stFrom); if (paintFrom) { _stFrom->button.icon.paint(p, positionFrom, width()); } else { auto positionTo = iconPosition(_stTo); if (paintTo) { _stTo->button.icon.paint(p, positionTo, width()); } else { mixIconMasks(); style::colorizeImage(_iconMixedMask, st::callIconFg->c, &_iconMixed); p.drawImage(myrtlpoint(_stFrom->button.rippleAreaPosition), _iconMixed); } } } QPoint Panel::Button::iconPosition(not_null<const style::CallButton*> st) const { auto result = st->button.iconPosition; if (result.x() < 0) { result.setX((width() - st->button.icon.width()) / 2); } if (result.y() < 0) { result.setY((height() - st->button.icon.height()) / 2); } return result; } void Panel::Button::mixIconMasks() { _iconMixedMask.fill(Qt::black); Painter p(&_iconMixedMask); PainterHighQualityEnabler hq(p); auto paintIconMask = [this, &p](const QImage &mask, float64 angle) { auto skipFrom = _stFrom->button.rippleAreaSize / 2; p.translate(skipFrom, skipFrom); p.rotate(angle); p.translate(-skipFrom, -skipFrom); p.drawImage(0, 0, mask); }; p.save(); paintIconMask(_iconFrom, (_stFrom->angle - _stTo->angle) * _progress); p.restore(); p.setOpacity(_progress); paintIconMask(_iconTo, (_stTo->angle - _stFrom->angle) * (1. - _progress)); } void Panel::Button::onStateChanged(State was, StateChangeSource source) { RippleButton::onStateChanged(was, source); auto over = isOver(); auto wasOver = static_cast<bool>(was & StateFlag::Over); if (over != wasOver) { update(); } } QPoint Panel::Button::prepareRippleStartPosition() const { return mapFromGlobal(QCursor::pos()) - _stFrom->button.rippleAreaPosition; } QImage Panel::Button::prepareRippleMask() const { return Ui::RippleAnimation::ellipseMask(QSize(_stFrom->button.rippleAreaSize, _stFrom->button.rippleAreaSize)); } Panel::Panel(not_null<Call*> call) : _call(call) , _user(call->user()) , _answerHangupRedial(this, st::callAnswer, &st::callHangup) , _decline(this, object_ptr<Button>(this, st::callHangup)) , _cancel(this, object_ptr<Button>(this, st::callCancel)) , _mute(this, st::callMuteToggle) , _name(this, st::callName) , _status(this, st::callStatus) { _decline->setDuration(st::callPanelDuration); _cancel->setDuration(st::callPanelDuration); setMouseTracking(true); setWindowIcon(Window::CreateIcon()); initControls(); initLayout(); showAndActivate(); } void Panel::showAndActivate() { toggleOpacityAnimation(true); raise(); setWindowState(windowState() | Qt::WindowActive); activateWindow(); setFocus(); } void Panel::replaceCall(not_null<Call*> call) { _call = call; _user = call->user(); reinitControls(); updateControlsGeometry(); } bool Panel::eventHook(QEvent *e) { if (e->type() == QEvent::WindowDeactivate) { if (_call && _call->state() == State::Established) { hideDeactivated(); } } return RpWidget::eventHook(e); } void Panel::hideDeactivated() { toggleOpacityAnimation(false); } void Panel::initControls() { _hangupShown = (_call->type() == Type::Outgoing); _mute->setClickedCallback([this] { if (_call) { _call->setMute(!_call->isMute()); } }); subscribe(_call->muteChanged(), [this](bool mute) { _mute->setIconOverride(mute ? &st::callUnmuteIcon : nullptr); }); subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(Notify::PeerUpdate::Flag::NameChanged, [this](const Notify::PeerUpdate &update) { if (!_call || update.peer != _call->user()) { return; } _name->setText(App::peerName(_call->user())); updateControlsGeometry(); })); _updateDurationTimer.setCallback([this] { if (_call) { updateStatusText(_call->state()); } }); _updateOuterRippleTimer.setCallback([this] { if (_call) { _answerHangupRedial->setOuterValue(_call->getWaitingSoundPeakValue()); } else { _answerHangupRedial->setOuterValue(0.); _updateOuterRippleTimer.cancel(); } }); _answerHangupRedial->setClickedCallback([this] { if (!_call || _hangupShownProgress.animating()) { return; } auto state = _call->state(); if (state == State::Busy) { _call->redial(); } else if (_call->isIncomingWaiting()) { _call->answer(); } else { _call->hangup(); } }); auto hangupCallback = [this] { if (_call) { _call->hangup(); } }; _decline->entity()->setClickedCallback(hangupCallback); _cancel->entity()->setClickedCallback(hangupCallback); reinitControls(); _decline->finishAnimating(); _cancel->finishAnimating(); } void Panel::reinitControls() { Expects(_call != nullptr); unsubscribe(_stateChangedSubscription); _stateChangedSubscription = subscribe(_call->stateChanged(), [this](State state) { stateChanged(state); }); stateChanged(_call->state()); _name->setText(App::peerName(_call->user())); updateStatusText(_call->state()); } void Panel::initLayout() { setWindowFlags(Qt::WindowFlags(Qt::FramelessWindowHint) | Qt::WindowStaysOnTopHint | Qt::BypassWindowManagerHint | Qt::NoDropShadowWindowHint | Qt::Dialog); setAttribute(Qt::WA_MacAlwaysShowToolWindow); setAttribute(Qt::WA_NoSystemBackground, true); setAttribute(Qt::WA_TranslucentBackground, true); initGeometry(); Notify::PeerUpdateValue( _user, Notify::PeerUpdate::Flag::PhotoChanged ) | rpl::start_with_next( [this] { processUserPhoto(); }, lifetime()); subscribe(Auth().downloaderTaskFinished(), [this] { refreshUserPhoto(); }); createDefaultCacheImage(); Platform::InitOnTopPanel(this); } void Panel::toggleOpacityAnimation(bool visible) { if (!_call || _visible == visible) { return; } _visible = visible; if (_useTransparency) { if (_animationCache.isNull()) { showControls(); _animationCache = Ui::GrabWidget(this); hideChildren(); } _opacityAnimation.start( [this] { update(); }, _visible ? 0. : 1., _visible ? 1. : 0., st::callPanelDuration, _visible ? anim::easeOutCirc : anim::easeInCirc); } if (isHidden() && _visible) { show(); } } void Panel::finishAnimating() { _animationCache = QPixmap(); if (_call) { if (!_visible) { hide(); } else { showControls(); } } else { destroyDelayed(); } } void Panel::showControls() { Expects(_call != nullptr); showChildren(); _decline->setVisible(_decline->toggled()); _cancel->setVisible(_cancel->toggled()); } void Panel::destroyDelayed() { hide(); crl::on_main(this, [=] { delete this; }); } void Panel::hideAndDestroy() { toggleOpacityAnimation(false); _call = nullptr; if (_animationCache.isNull()) { destroyDelayed(); } } void Panel::processUserPhoto() { if (!_user->userpicLoaded()) { _user->loadUserpic(true); } const auto photo = _user->userpicPhotoId() ? Auth().data().photo(_user->userpicPhotoId()).get() : nullptr; if (isGoodUserPhoto(photo)) { photo->full->load(true); } else if (_user->userpicPhotoUnknown() || (photo && !photo->date)) { Auth().api().requestFullPeer(_user); } refreshUserPhoto(); } void Panel::refreshUserPhoto() { const auto photo = _user->userpicPhotoId() ? Auth().data().photo(_user->userpicPhotoId()).get() : nullptr; const auto isNewPhoto = [&](not_null<PhotoData*> photo) { return photo->full->loaded() && (photo->id != _userPhotoId || !_userPhotoFull); }; if (isGoodUserPhoto(photo) && isNewPhoto(photo)) { _userPhotoId = photo->id; _userPhotoFull = true; createUserpicCache(photo->full); } else if (_userPhoto.isNull()) { createUserpicCache(_user->currentUserpic()); } } void Panel::createUserpicCache(ImagePtr image) { auto size = st::callWidth * cIntRetinaFactor(); auto options = _useTransparency ? (Images::Option::RoundedLarge | Images::Option::RoundedTopLeft | Images::Option::RoundedTopRight | Images::Option::Smooth) : Images::Option::None; if (image) { auto width = image->width(); auto height = image->height(); if (width > height) { width = qMax((width * size) / height, 1); height = size; } else { height = qMax((height * size) / width, 1); width = size; } _userPhoto = image->pixNoCache(width, height, options, st::callWidth, st::callWidth); if (cRetina()) _userPhoto.setDevicePixelRatio(cRetinaFactor()); } else { auto filled = QImage(QSize(st::callWidth, st::callWidth) * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); filled.setDevicePixelRatio(cRetinaFactor()); { Painter p(&filled); Ui::EmptyUserpic( Data::PeerUserpicColor(_user->id), _user->name ).paintSquare(p, 0, 0, st::callWidth, st::callWidth); } Images::prepareRound(filled, ImageRoundRadius::Large, RectPart::TopLeft | RectPart::TopRight); _userPhoto = App::pixmapFromImageInPlace(std::move(filled)); } refreshCacheImageUserPhoto(); update(); } bool Panel::isGoodUserPhoto(PhotoData *photo) { if (!photo || !photo->date) { return false; } auto badAspect = [](int a, int b) { return a > 10 * b; }; auto width = photo->full->width(); auto height = photo->full->height(); return !badAspect(width, height) && !badAspect(height, width); } void Panel::initGeometry() { auto center = Messenger::Instance().getPointForCallPanelCenter(); _useTransparency = Platform::TranslucentWindowsSupported(center); setAttribute(Qt::WA_OpaquePaintEvent, !_useTransparency); _padding = _useTransparency ? st::callShadow.extend : style::margins(st::lineWidth, st::lineWidth, st::lineWidth, st::lineWidth); _contentTop = _padding.top() + st::callWidth; auto screen = QApplication::desktop()->screenGeometry(center); auto rect = QRect(0, 0, st::callWidth, st::callHeight); setGeometry(rect.translated(center - rect.center()).marginsAdded(_padding)); createBottomImage(); updateControlsGeometry(); } void Panel::createBottomImage() { if (!_useTransparency) { return; } auto bottomWidth = width(); auto bottomHeight = height() - _padding.top() - st::callWidth; auto image = QImage(QSize(bottomWidth, bottomHeight) * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); image.fill(Qt::transparent); { Painter p(&image); Ui::Shadow::paint(p, QRect(_padding.left(), 0, st::callWidth, bottomHeight - _padding.bottom()), width(), st::callShadow, RectPart::Left | RectPart::Right | RectPart::Bottom); p.setCompositionMode(QPainter::CompositionMode_Source); p.setBrush(st::callBg); p.setPen(Qt::NoPen); PainterHighQualityEnabler hq(p); p.drawRoundedRect(myrtlrect(_padding.left(), -st::historyMessageRadius, st::callWidth, bottomHeight - _padding.bottom() + st::historyMessageRadius), st::historyMessageRadius, st::historyMessageRadius); } _bottomCache = App::pixmapFromImageInPlace(std::move(image)); } void Panel::createDefaultCacheImage() { if (!_useTransparency || !_cache.isNull()) { return; } auto cache = QImage(size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); cache.setDevicePixelRatio(cRetinaFactor()); cache.fill(Qt::transparent); { Painter p(&cache); auto inner = rect().marginsRemoved(_padding); Ui::Shadow::paint(p, inner, width(), st::callShadow); p.setCompositionMode(QPainter::CompositionMode_Source); p.setBrush(st::callBg); p.setPen(Qt::NoPen); PainterHighQualityEnabler hq(p); p.drawRoundedRect(myrtlrect(inner), st::historyMessageRadius, st::historyMessageRadius); } _cache = App::pixmapFromImageInPlace(std::move(cache)); } void Panel::refreshCacheImageUserPhoto() { auto cache = QImage(size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); cache.setDevicePixelRatio(cRetinaFactor()); cache.fill(Qt::transparent); { Painter p(&cache); Ui::Shadow::paint(p, QRect(_padding.left(), _padding.top(), st::callWidth, st::callWidth), width(), st::callShadow, RectPart::Top | RectPart::Left | RectPart::Right); p.drawPixmapLeft(_padding.left(), _padding.top(), width(), _userPhoto); p.drawPixmapLeft(0, _padding.top() + st::callWidth, width(), _bottomCache); } _cache = App::pixmapFromImageInPlace(std::move(cache)); } void Panel::resizeEvent(QResizeEvent *e) { updateControlsGeometry(); } void Panel::updateControlsGeometry() { _name->moveToLeft((width() - _name->width()) / 2, _contentTop + st::callNameTop); updateStatusGeometry(); auto controlsTop = _contentTop + st::callControlsTop; auto bothWidth = _answerHangupRedial->width() + st::callControlsSkip + st::callCancel.button.width; _decline->moveToLeft((width() - bothWidth) / 2, controlsTop); _cancel->moveToLeft((width() - bothWidth) / 2, controlsTop); updateHangupGeometry(); _mute->moveToRight(_padding.right() + st::callMuteRight, controlsTop); } void Panel::updateHangupGeometry() { auto singleWidth = _answerHangupRedial->width(); auto bothWidth = singleWidth + st::callControlsSkip + st::callCancel.button.width; auto rightFrom = (width() - bothWidth) / 2; auto rightTo = (width() - singleWidth) / 2; auto hangupProgress = _hangupShownProgress.current(_hangupShown ? 1. : 0.); auto hangupRight = anim::interpolate(rightFrom, rightTo, hangupProgress); auto controlsTop = _contentTop + st::callControlsTop; _answerHangupRedial->moveToRight(hangupRight, controlsTop); _answerHangupRedial->setProgress(hangupProgress); } void Panel::updateStatusGeometry() { _status->moveToLeft((width() - _status->width()) / 2, _contentTop + st::callStatusTop); } void Panel::paintEvent(QPaintEvent *e) { Painter p(this); if (!_animationCache.isNull()) { auto opacity = _opacityAnimation.current(getms(), _call ? 1. : 0.); if (!_opacityAnimation.animating()) { finishAnimating(); if (!_call || isHidden()) return; } else { Platform::StartTranslucentPaint(p, e); p.setOpacity(opacity); PainterHighQualityEnabler hq(p); auto marginRatio = (1. - opacity) / 5; auto marginWidth = qRound(width() * marginRatio); auto marginHeight = qRound(height() * marginRatio); p.drawPixmap(rect().marginsRemoved(QMargins(marginWidth, marginHeight, marginWidth, marginHeight)), _animationCache, QRect(QPoint(0, 0), _animationCache.size())); return; } } if (_useTransparency) { Platform::StartTranslucentPaint(p, e); p.drawPixmapLeft(0, 0, width(), _cache); } else { p.drawPixmapLeft(_padding.left(), _padding.top(), width(), _userPhoto); auto callBgOpaque = st::callBg->c; callBgOpaque.setAlpha(255); auto brush = QBrush(callBgOpaque); p.fillRect(0, 0, width(), _padding.top(), brush); p.fillRect(myrtlrect(0, _padding.top(), _padding.left(), _contentTop - _padding.top()), brush); p.fillRect(myrtlrect(width() - _padding.right(), _padding.top(), _padding.right(), _contentTop - _padding.top()), brush); p.fillRect(0, _contentTop, width(), height() - _contentTop, brush); } if (!_fingerprint.empty()) { App::roundRect(p, _fingerprintArea, st::callFingerprintBg, ImageRoundRadius::Small); auto realSize = Ui::Emoji::Size(Ui::Emoji::Index() + 1); auto size = realSize / cIntRetinaFactor(); auto left = _fingerprintArea.left() + st::callFingerprintPadding.left(); auto top = _fingerprintArea.top() + st::callFingerprintPadding.top(); for (auto emoji : _fingerprint) { p.drawPixmap(QPoint(left, top), App::emojiLarge(), QRect(emoji->x() * realSize, emoji->y() * realSize, realSize, realSize)); left += st::callFingerprintSkip + size; } } } void Panel::closeEvent(QCloseEvent *e) { if (_call) { _call->hangup(); } } void Panel::mousePressEvent(QMouseEvent *e) { auto dragArea = myrtlrect(_padding.left(), _padding.top(), st::callWidth, st::callWidth); if (e->button() == Qt::LeftButton) { if (dragArea.contains(e->pos())) { _dragging = true; _dragStartMousePosition = e->globalPos(); _dragStartMyPosition = QPoint(x(), y()); } else if (!rect().contains(e->pos())) { if (_call && _call->state() == State::Established) { hideDeactivated(); } } } } void Panel::mouseMoveEvent(QMouseEvent *e) { if (_dragging) { Ui::Tooltip::Hide(); if (!(e->buttons() & Qt::LeftButton)) { _dragging = false; } else { move(_dragStartMyPosition + (e->globalPos() - _dragStartMousePosition)); } } else if (_fingerprintArea.contains(e->pos())) { Ui::Tooltip::Show(kTooltipShowTimeoutMs, this); } else { Ui::Tooltip::Hide(); } } void Panel::mouseReleaseEvent(QMouseEvent *e) { if (e->button() == Qt::LeftButton) { _dragging = false; } } void Panel::leaveEventHook(QEvent *e) { Ui::Tooltip::Hide(); } void Panel::leaveToChildEvent(QEvent *e, QWidget *child) { Ui::Tooltip::Hide(); } QString Panel::tooltipText() const { return lng_call_fingerprint_tooltip(lt_user, App::peerName(_user)); } QPoint Panel::tooltipPos() const { return QCursor::pos(); } bool Panel::tooltipWindowActive() const { return !isHidden(); } void Panel::stateChanged(State state) { updateStatusText(state); if (_call) { if ((state != State::HangingUp) && (state != State::Ended) && (state != State::EndedByOtherDevice) && (state != State::FailedHangingUp) && (state != State::Failed)) { auto toggleButton = [this](auto &&button, bool visible) { button->toggle( visible, isHidden() ? anim::type::instant : anim::type::normal); }; auto incomingWaiting = _call->isIncomingWaiting(); if (incomingWaiting) { _updateOuterRippleTimer.callEach(Call::kSoundSampleMs); } toggleButton(_decline, incomingWaiting); toggleButton(_cancel, (state == State::Busy)); auto hangupShown = !_decline->toggled() && !_cancel->toggled(); if (_hangupShown != hangupShown) { _hangupShown = hangupShown; _hangupShownProgress.start([this] { updateHangupGeometry(); }, _hangupShown ? 0. : 1., _hangupShown ? 1. : 0., st::callPanelDuration, anim::sineInOut); } if (_fingerprint.empty() && _call->isKeyShaForFingerprintReady()) { fillFingerprint(); } } } if (windowHandle()) { // First stateChanged() is called before the first Platform::InitOnTopPanel(this). if ((state == State::Starting) || (state == State::WaitingIncoming)) { Platform::ReInitOnTopPanel(this); } else { Platform::DeInitOnTopPanel(this); } } if (state == State::Established) { if (!isActiveWindow()) { hideDeactivated(); } } } void Panel::fillFingerprint() { Expects(_call != nullptr); _fingerprint = ComputeEmojiFingerprint(_call); auto realSize = Ui::Emoji::Size(Ui::Emoji::Index() + 1); auto size = realSize / cIntRetinaFactor(); auto count = _fingerprint.size(); auto rectWidth = count * size + (count - 1) * st::callFingerprintSkip; auto rectHeight = size; auto left = (width() - rectWidth) / 2; auto top = _contentTop - st::callFingerprintBottom - st::callFingerprintPadding.bottom() - size; _fingerprintArea = QRect(left, top, rectWidth, rectHeight).marginsAdded(st::callFingerprintPadding); update(); } void Panel::updateStatusText(State state) { auto statusText = [this, state]() -> QString { switch (state) { case State::Starting: case State::WaitingInit: case State::WaitingInitAck: return lang(lng_call_status_connecting); case State::Established: { if (_call) { auto durationMs = _call->getDurationMs(); auto durationSeconds = durationMs / 1000; startDurationUpdateTimer(durationMs); return formatDurationText(durationSeconds); } return lang(lng_call_status_ended); } break; case State::FailedHangingUp: case State::Failed: return lang(lng_call_status_failed); case State::HangingUp: return lang(lng_call_status_hanging); case State::Ended: case State::EndedByOtherDevice: return lang(lng_call_status_ended); case State::ExchangingKeys: return lang(lng_call_status_exchanging); case State::Waiting: return lang(lng_call_status_waiting); case State::Requesting: return lang(lng_call_status_requesting); case State::WaitingIncoming: return lang(lng_call_status_incoming); case State::Ringing: return lang(lng_call_status_ringing); case State::Busy: return lang(lng_call_status_busy); } Unexpected("State in stateChanged()"); }; _status->setText(statusText()); updateStatusGeometry(); } void Panel::startDurationUpdateTimer(TimeMs currentDuration) { auto msTillNextSecond = 1000 - (currentDuration % 1000); _updateDurationTimer.callOnce(msTillNextSecond + 5); } } // namespace Calls ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/calls/calls_panel.h��������������������������������������������0000664�0000000�0000000�00000005742�13262451251�0023157�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "base/weak_ptr.h" #include "base/timer.h" #include "calls/calls_call.h" #include "ui/widgets/tooltip.h" #include "ui/rp_widget.h" namespace Ui { class IconButton; class FlatLabel; template <typename Widget> class FadeWrap; } // namespace Ui namespace Calls { class Panel : public Ui::RpWidget , private base::Subscriber , private Ui::AbstractTooltipShower { public: Panel(not_null<Call*> call); void showAndActivate(); void replaceCall(not_null<Call*> call); void hideAndDestroy(); protected: void paintEvent(QPaintEvent *e) override; void closeEvent(QCloseEvent *e) override; void resizeEvent(QResizeEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void leaveEventHook(QEvent *e) override; void leaveToChildEvent(QEvent *e, QWidget *child) override; bool eventHook(QEvent *e) override; private: using State = Call::State; using Type = Call::Type; // AbstractTooltipShower interface QString tooltipText() const override; QPoint tooltipPos() const override; bool tooltipWindowActive() const override; void initControls(); void reinitControls(); void initLayout(); void initGeometry(); void hideDeactivated(); void createBottomImage(); void createDefaultCacheImage(); void refreshCacheImageUserPhoto(); void processUserPhoto(); void refreshUserPhoto(); bool isGoodUserPhoto(PhotoData *photo); void createUserpicCache(ImagePtr image); void updateControlsGeometry(); void updateHangupGeometry(); void updateStatusGeometry(); void stateChanged(State state); void showControls(); void updateStatusText(State state); void startDurationUpdateTimer(TimeMs currentDuration); void fillFingerprint(); void toggleOpacityAnimation(bool visible); void finishAnimating(); void destroyDelayed(); Call *_call = nullptr; not_null<UserData*> _user; bool _useTransparency = true; style::margins _padding; int _contentTop = 0; bool _dragging = false; QPoint _dragStartMousePosition; QPoint _dragStartMyPosition; int _stateChangedSubscription = 0; class Button; object_ptr<Button> _answerHangupRedial; object_ptr<Ui::FadeWrap<Button>> _decline; object_ptr<Ui::FadeWrap<Button>> _cancel; bool _hangupShown = false; Animation _hangupShownProgress; object_ptr<Ui::IconButton> _mute; object_ptr<Ui::FlatLabel> _name; object_ptr<Ui::FlatLabel> _status; std::vector<EmojiPtr> _fingerprint; QRect _fingerprintArea; base::Timer _updateDurationTimer; base::Timer _updateOuterRippleTimer; bool _visible = false; QPixmap _userPhoto; PhotoId _userPhotoId = 0; bool _userPhotoFull = false; Animation _opacityAnimation; QPixmap _animationCache; QPixmap _bottomCache; QPixmap _cache; }; } // namespace Calls ������������������������������tdesktop-1.2.17/Telegram/SourceFiles/calls/calls_top_bar.cpp����������������������������������������0000664�0000000�0000000�00000013222�13262451251�0024031�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "calls/calls_top_bar.h" #include "styles/style_calls.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" #include "ui/wrap/padding_wrap.h" #include "lang/lang_keys.h" #include "calls/calls_call.h" #include "calls/calls_instance.h" #include "styles/style_boxes.h" #include "observer_peer.h" #include "boxes/abstract_box.h" #include "base/timer.h" #include "layout.h" namespace Calls { namespace { constexpr auto kUpdateDebugTimeoutMs = TimeMs(500); class DebugInfoBox : public BoxContent { public: DebugInfoBox(QWidget*, base::weak_ptr<Call> call); protected: void prepare() override; private: void updateText(); base::weak_ptr<Call> _call; QPointer<Ui::FlatLabel> _text; base::Timer _updateTextTimer; }; DebugInfoBox::DebugInfoBox(QWidget*, base::weak_ptr<Call> call) : _call(call) { } void DebugInfoBox::prepare() { setTitle([] { return QString("Call Debug"); }); addButton(langFactory(lng_close), [this] { closeBox(); }); _text = setInnerWidget( object_ptr<Ui::PaddingWrap<Ui::FlatLabel>>( this, object_ptr<Ui::FlatLabel>(this, st::callDebugLabel), st::callDebugPadding))->entity(); _text->setSelectable(true); updateText(); _updateTextTimer.setCallback([this] { updateText(); }); _updateTextTimer.callEach(kUpdateDebugTimeoutMs); setDimensions(st::boxWideWidth, st::boxMaxListHeight); } void DebugInfoBox::updateText() { if (auto call = _call.get()) { _text->setText(call->getDebugLog()); } } } // namespace TopBar::TopBar( QWidget *parent, const base::weak_ptr<Call> &call) : RpWidget(parent) , _call(call) , _durationLabel(this, st::callBarLabel) , _fullInfoLabel(this, st::callBarInfoLabel) , _shortInfoLabel(this, st::callBarInfoLabel) , _hangupLabel(this, st::callBarLabel, lang(lng_call_bar_hangup).toUpper()) , _mute(this, st::callBarMuteToggle) , _info(this) , _hangup(this, st::callBarHangup) { initControls(); resize(width(), st::callBarHeight); } void TopBar::initControls() { _mute->setClickedCallback([this] { if (auto call = _call.get()) { call->setMute(!call->isMute()); } }); setMuted(_call->isMute()); subscribe(_call->muteChanged(), [this](bool mute) { setMuted(mute); update(); }); subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(Notify::PeerUpdate::Flag::NameChanged, [this](const Notify::PeerUpdate &update) { if (auto call = _call.get()) { if (update.peer == call->user()) { updateInfoLabels(); } } })); setInfoLabels(); _info->setClickedCallback([this] { if (auto call = _call.get()) { if (cDebug() && (_info->clickModifiers() & Qt::ControlModifier)) { Ui::show(Box<DebugInfoBox>(_call)); } else { Current().showInfoPanel(call); } } }); _hangup->setClickedCallback([this] { if (auto call = _call.get()) { call->hangup(); } }); _updateDurationTimer.setCallback([this] { updateDurationText(); }); updateDurationText(); } void TopBar::updateInfoLabels() { setInfoLabels(); updateControlsGeometry(); } void TopBar::setInfoLabels() { if (auto call = _call.get()) { auto user = call->user(); auto fullName = App::peerName(user); auto shortName = user->firstName; _fullInfoLabel->setText(fullName.toUpper()); _shortInfoLabel->setText(shortName.toUpper()); } } void TopBar::setMuted(bool mute) { _mute->setIconOverride(mute ? &st::callBarUnmuteIcon : nullptr); _mute->setRippleColorOverride(mute ? &st::callBarUnmuteRipple : nullptr); _hangup->setRippleColorOverride(mute ? &st::callBarUnmuteRipple : nullptr); _muted = mute; } void TopBar::updateDurationText() { if (!_call) { return; } auto wasWidth = _durationLabel->width(); auto durationMs = _call->getDurationMs(); auto durationSeconds = durationMs / 1000; startDurationUpdateTimer(durationMs); _durationLabel->setText(formatDurationText(durationSeconds)); if (_durationLabel->width() != wasWidth) { updateControlsGeometry(); } } void TopBar::startDurationUpdateTimer(TimeMs currentDuration) { auto msTillNextSecond = 1000 - (currentDuration % 1000); _updateDurationTimer.callOnce(msTillNextSecond + 5); } void TopBar::resizeEvent(QResizeEvent *e) { updateControlsGeometry(); } void TopBar::updateControlsGeometry() { auto left = 0; _mute->moveToLeft(left, 0); left += _mute->width(); _durationLabel->moveToLeft(left, st::callBarLabelTop); left += _durationLabel->width() + st::callBarSkip; auto right = st::callBarRightSkip; _hangupLabel->moveToRight(right, st::callBarLabelTop); right += _hangupLabel->width(); right += st::callBarHangup.width; _hangup->setGeometryToRight(0, 0, right, height()); _info->setGeometryToLeft(_mute->width(), 0, width() - _mute->width() - _hangup->width(), height()); auto fullWidth = _fullInfoLabel->naturalWidth(); auto showFull = (left + fullWidth + right <= width()); _fullInfoLabel->setVisible(showFull); _shortInfoLabel->setVisible(!showFull); auto setInfoLabelGeometry = [this, left, right](auto &&infoLabel) { auto minPadding = qMax(left, right); auto infoWidth = infoLabel->naturalWidth(); auto infoLeft = (width() - infoWidth) / 2; if (infoLeft < minPadding) { infoLeft = left; infoWidth = width() - left - right; } infoLabel->setGeometryToLeft(infoLeft, st::callBarLabelTop, infoWidth, st::callBarInfoLabel.style.font->height); }; setInfoLabelGeometry(_fullInfoLabel); setInfoLabelGeometry(_shortInfoLabel); } void TopBar::paintEvent(QPaintEvent *e) { Painter p(this); p.fillRect(e->rect(), _muted ? st::callBarBgMuted : st::callBarBg); } TopBar::~TopBar() = default; } // namespace Calls ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/calls/calls_top_bar.h������������������������������������������0000664�0000000�0000000�00000002477�13262451251�0023510�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "base/weak_ptr.h" #include "base/timer.h" #include "ui/rp_widget.h" namespace Ui { class IconButton; class AbstractButton; class LabelSimple; class FlatLabel; } // namespace Ui namespace Calls { class Call; class TopBar : public Ui::RpWidget, private base::Subscriber { public: TopBar(QWidget *parent, const base::weak_ptr<Call> &call); ~TopBar(); protected: void resizeEvent(QResizeEvent *e) override; void paintEvent(QPaintEvent *e) override; private: void initControls(); void updateInfoLabels(); void setInfoLabels(); void updateDurationText(); void updateControlsGeometry(); void startDurationUpdateTimer(TimeMs currentDuration); void setMuted(bool mute); base::weak_ptr<Call> _call; bool _muted = false; object_ptr<Ui::LabelSimple> _durationLabel; object_ptr<Ui::FlatLabel> _fullInfoLabel; object_ptr<Ui::FlatLabel> _shortInfoLabel; object_ptr<Ui::LabelSimple> _hangupLabel; object_ptr<Ui::IconButton> _mute; object_ptr<Ui::AbstractButton> _info; object_ptr<Ui::IconButton> _hangup; base::Timer _updateDurationTimer; }; } // namespace Calls �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/chat_helpers/��������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0022064�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/chat_helpers/bot_keyboard.cpp����������������������������������0000664�0000000�0000000�00000016171�13262451251�0025242�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "chat_helpers/bot_keyboard.h" #include "history/history.h" #include "history/history_item_components.h" #include "styles/style_widgets.h" #include "styles/style_history.h" namespace { class Style : public ReplyKeyboard::Style { public: Style( not_null<BotKeyboard*> parent, const style::BotKeyboardButton &st); int buttonRadius() const override; void startPaint(Painter &p) const override; const style::TextStyle &textStyle() const override; void repaint(not_null<const HistoryItem*> item) const override; protected: void paintButtonBg( Painter &p, const QRect &rect, float64 howMuchOver) const override; void paintButtonIcon( Painter &p, const QRect &rect, int outerWidth, HistoryMessageMarkupButton::Type type) const override; void paintButtonLoading(Painter &p, const QRect &rect) const override; int minButtonWidth(HistoryMessageMarkupButton::Type type) const override; private: not_null<BotKeyboard*> _parent; }; Style::Style( not_null<BotKeyboard*> parent, const style::BotKeyboardButton &st) : ReplyKeyboard::Style(st), _parent(parent) { } void Style::startPaint(Painter &p) const { p.setPen(st::botKbColor); p.setFont(st::botKbStyle.font); } const style::TextStyle &Style::textStyle() const { return st::botKbStyle; } void Style::repaint(not_null<const HistoryItem*> item) const { _parent->update(); } int Style::buttonRadius() const { return st::buttonRadius; } void Style::paintButtonBg( Painter &p, const QRect &rect, float64 howMuchOver) const { App::roundRect(p, rect, st::botKbBg, BotKeyboardCorners); } void Style::paintButtonIcon( Painter &p, const QRect &rect, int outerWidth, HistoryMessageMarkupButton::Type type) const { // Buttons with icons should not appear here. } void Style::paintButtonLoading(Painter &p, const QRect &rect) const { // Buttons with loading progress should not appear here. } int Style::minButtonWidth(HistoryMessageMarkupButton::Type type) const { int result = 2 * buttonPadding(); return result; } } // namespace BotKeyboard::BotKeyboard(QWidget *parent) : TWidget(parent) , _st(&st::botKbButton) { setGeometry(0, 0, _st->margin, st::botKbScroll.deltat); _height = st::botKbScroll.deltat; setMouseTracking(true); } void BotKeyboard::paintEvent(QPaintEvent *e) { Painter p(this); auto clip = e->rect(); p.fillRect(clip, st::historyComposeAreaBg); if (_impl) { int x = rtl() ? st::botKbScroll.width : _st->margin; p.translate(x, st::botKbScroll.deltat); _impl->paint(p, width(), clip.translated(-x, -st::botKbScroll.deltat), getms()); } } void BotKeyboard::mousePressEvent(QMouseEvent *e) { _lastMousePos = e->globalPos(); updateSelected(); ClickHandler::pressed(); } void BotKeyboard::mouseMoveEvent(QMouseEvent *e) { _lastMousePos = e->globalPos(); updateSelected(); } void BotKeyboard::mouseReleaseEvent(QMouseEvent *e) { _lastMousePos = e->globalPos(); updateSelected(); if (ClickHandlerPtr activated = ClickHandler::unpressed()) { App::activateClickHandler(activated, e->button()); } } void BotKeyboard::enterEventHook(QEvent *e) { _lastMousePos = QCursor::pos(); updateSelected(); } void BotKeyboard::leaveEventHook(QEvent *e) { clearSelection(); } bool BotKeyboard::moderateKeyActivate(int key) { if (const auto item = App::histItemById(_wasForMsgId)) { if (const auto markup = item->Get<HistoryMessageReplyMarkup>()) { if (key >= Qt::Key_1 && key <= Qt::Key_9) { const auto index = int(key - Qt::Key_1); if (!markup->rows.empty() && index >= 0 && index < int(markup->rows.front().size())) { App::activateBotCommand(item, 0, index); return true; } } else if (key == Qt::Key_Q) { if (const auto user = item->history()->peer->asUser()) { if (user->botInfo && item->from() == user) { App::sendBotCommand(user, user, qsl("/translate")); return true; } } } } } return false; } void BotKeyboard::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { if (!_impl) return; _impl->clickHandlerActiveChanged(p, active); } void BotKeyboard::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) { if (!_impl) return; _impl->clickHandlerPressedChanged(p, pressed); } bool BotKeyboard::updateMarkup(HistoryItem *to, bool force) { if (!to || !to->definesReplyKeyboard()) { if (_wasForMsgId.msg) { _maximizeSize = _singleUse = _forceReply = false; _wasForMsgId = FullMsgId(); _impl = nullptr; return true; } return false; } if (_wasForMsgId == FullMsgId(to->channelId(), to->id) && !force) { return false; } _wasForMsgId = FullMsgId(to->channelId(), to->id); auto markupFlags = to->replyKeyboardFlags(); _forceReply = markupFlags & MTPDreplyKeyboardMarkup_ClientFlag::f_force_reply; _maximizeSize = !(markupFlags & MTPDreplyKeyboardMarkup::Flag::f_resize); _singleUse = _forceReply || (markupFlags & MTPDreplyKeyboardMarkup::Flag::f_single_use); _impl = nullptr; if (auto markup = to->Get<HistoryMessageReplyMarkup>()) { if (!markup->rows.empty()) { _impl = std::make_unique<ReplyKeyboard>( to, std::make_unique<Style>(this, *_st)); } } resizeToWidth(width(), _maxOuterHeight); return true; } bool BotKeyboard::hasMarkup() const { return _impl != nullptr; } bool BotKeyboard::forceReply() const { return _forceReply; } int BotKeyboard::resizeGetHeight(int newWidth) { updateStyle(newWidth); _height = st::botKbScroll.deltat + st::botKbScroll.deltab + (_impl ? _impl->naturalHeight() : 0); if (_maximizeSize) { accumulate_max(_height, _maxOuterHeight); } if (_impl) { int implWidth = newWidth - _st->margin - st::botKbScroll.width; int implHeight = _height - (st::botKbScroll.deltat + st::botKbScroll.deltab); _impl->resize(implWidth, implHeight); } return _height; } bool BotKeyboard::maximizeSize() const { return _maximizeSize; } bool BotKeyboard::singleUse() const { return _singleUse; } void BotKeyboard::updateStyle(int newWidth) { if (!_impl) return; int implWidth = newWidth - st::botKbButton.margin - st::botKbScroll.width; _st = _impl->isEnoughSpace(implWidth, st::botKbButton) ? &st::botKbButton : &st::botKbTinyButton; _impl->setStyle(std::make_unique<Style>(this, *_st)); } void BotKeyboard::clearSelection() { if (_impl) { if (ClickHandler::setActive(ClickHandlerPtr(), this)) { Ui::Tooltip::Hide(); setCursor(style::cur_default); } } } QPoint BotKeyboard::tooltipPos() const { return _lastMousePos; } QString BotKeyboard::tooltipText() const { if (ClickHandlerPtr lnk = ClickHandler::getActive()) { return lnk->tooltip(); } return QString(); } void BotKeyboard::updateSelected() { Ui::Tooltip::Show(1000, this); if (!_impl) return; auto p = mapFromGlobal(_lastMousePos); auto x = rtl() ? st::botKbScroll.width : _st->margin; auto link = _impl->getLink(p - QPoint(x, _st->margin)); if (ClickHandler::setActive(link, this)) { Ui::Tooltip::Hide(); setCursor(link ? style::cur_pointer : style::cur_default); } } BotKeyboard::~BotKeyboard() = default; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/chat_helpers/bot_keyboard.h������������������������������������0000664�0000000�0000000�00000004037�13262451251�0024705�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/widgets/tooltip.h" namespace style { struct BotKeyboardButton; } // namespace style class ReplyKeyboard; class BotKeyboard : public TWidget , public Ui::AbstractTooltipShower , public ClickHandlerHost { public: BotKeyboard(QWidget *parent); bool moderateKeyActivate(int index); // With force=true the markup is updated even if it is // already shown for the passed history item. bool updateMarkup(HistoryItem *last, bool force = false); bool hasMarkup() const; bool forceReply() const; void step_selected(TimeMs ms, bool timer); void resizeToWidth(int newWidth, int maxOuterHeight) { _maxOuterHeight = maxOuterHeight; return TWidget::resizeToWidth(newWidth); } bool maximizeSize() const; bool singleUse() const; FullMsgId forMsgId() const { return _wasForMsgId; } // AbstractTooltipShower interface QString tooltipText() const override; QPoint tooltipPos() const override; // ClickHandlerHost interface void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override; ~BotKeyboard(); protected: int resizeGetHeight(int newWidth) override; void paintEvent(QPaintEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void enterEventHook(QEvent *e) override; void leaveEventHook(QEvent *e) override; private: void updateSelected(); void updateStyle(int newWidth); void clearSelection(); FullMsgId _wasForMsgId; int _height = 0; int _maxOuterHeight = 0; bool _maximizeSize = false; bool _singleUse = false; bool _forceReply = false; QPoint _lastMousePos; std::unique_ptr<ReplyKeyboard> _impl; const style::BotKeyboardButton *_st = nullptr; }; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/chat_helpers/chat_helpers.style��������������������������������0000664�0000000�0000000�00000017116�13262451251�0025615�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ using "basic.style"; using "boxes/boxes.style"; using "ui/widgets/widgets.style"; switchPmButton: RoundButton(defaultBoxButton) { width: 320px; height: 34px; textTop: 7px; } stickersRestrictedLabel: FlatLabel(defaultFlatLabel) { minWidth: 200px; align: align(center); textFg: noContactsColor; } stickersTrendingHeader: 56px; stickersTrendingSkip: 4px; stickersTrendingHeaderFont: semiboldFont; stickersTrendingHeaderFg: windowFg; stickersTrendingHeaderTop: 11px; stickersTrendingSubheaderFont: normalFont; stickersTrendingSubheaderFg: windowSubTextFg; stickersTrendingSubheaderTop: 31px; stickersTrendingAddTop: 14px; stickersTrendingAdd: RoundButton(defaultActiveButton) { width: -16px; height: 26px; textTop: 4px; } stickersRemove: IconButton(defaultIconButton) { width: 40px; height: 40px; icon: icon {{ "stickers_remove", menuIconFg }}; iconOver: icon {{ "stickers_remove", menuIconFgOver }}; rippleAreaSize: 40px; rippleAreaPosition: point(0px, 0px); ripple: RippleAnimation(defaultRippleAnimation) { color: windowBgOver; } } stickersUndoRemove: RoundButton(defaultLightButton) { width: -16px; height: 26px; textTop: 4px; } stickersRemoveSkip: 4px; stickersReorderIcon: icon {{ "stickers_reorder", menuIconFg }}; stickersReorderSkip: 13px; stickersTabs: defaultTabsSlider; stickerEmojiSkip: 5px; stickersFeaturedBadgeFont: font(12px bold); stickersFeaturedBadgeSize: 15px; stickersFeaturedBadgeTextTop: -1px; stickersFeaturedBadgePadding: 4px; stickersFeaturedBadgeSkip: 4px; stickersFeaturedBadgeTop: 9px; stickersFeaturedPen: lightButtonFg; stickersFeaturedUnreadBg: msgFileInBg; stickersFeaturedUnreadSize: 5px; stickersFeaturedUnreadSkip: 5px; stickersFeaturedUnreadTop: 7px; stickersFeaturedInstalled: icon {{ "send_control_save", lightButtonFg }}; stickersMaxHeight: 320px; stickersPadding: margins(19px, 13px, 19px, 13px); stickersSize: size(64px, 64px); stickersScroll: ScrollArea(boxLayerScroll) { deltat: 19px; deltab: 9px; } stickersReorderPadding: margins(0px, 12px, 0px, 12px); stickersReorderFg: windowSubTextFg; stickersRowDisabledOpacity: 0.4; stickersRowDuration: 200; stickersSettings: icon {{ "emoji_settings", emojiIconFg }}; stickersTrending: icon {{ "emoji_trending", emojiIconFg }}; stickersSearch: icon {{ "stickers_search", emojiIconFg, point(0px, 1px) }}; stickersSettingsUnreadSize: 17px; stickersSettingsUnreadPosition: point(4px, 5px); emojiPanMargins: margins(10px, 10px, 10px, 10px); emojiTabs: SettingsSlider(defaultTabsSlider) { height: 55px; barTop: 52px; labelTop: 19px; } emojiScroll: defaultSolidScroll; emojiRecent: icon {{ "emoji_recent", emojiIconFg }}; emojiRecentActive: icon {{ "emoji_recent", emojiIconFgActive }}; emojiPeople: icon {{ "emoji_people", emojiIconFg }}; emojiPeopleActive: icon {{ "emoji_people", emojiIconFgActive }}; emojiNature: icon {{ "emoji_nature", emojiIconFg }}; emojiNatureActive: icon {{ "emoji_nature", emojiIconFgActive }}; emojiFood: icon {{ "emoji_food", emojiIconFg }}; emojiFoodActive: icon {{ "emoji_food", emojiIconFgActive }}; emojiActivity: icon {{ "emoji_activity", emojiIconFg }}; emojiActivityActive: icon {{ "emoji_activity", emojiIconFgActive }}; emojiTravel: icon {{ "emoji_travel", emojiIconFg }}; emojiTravelActive: icon {{ "emoji_travel", emojiIconFgActive }}; emojiObjects: icon {{ "emoji_objects", emojiIconFg }}; emojiObjectsActive: icon {{ "emoji_objects", emojiIconFgActive }}; emojiSymbols: icon {{ "emoji_symbols", emojiIconFg }}; emojiSymbolsActive: icon {{ "emoji_symbols", emojiIconFgActive }}; emojiFooterHeight: 46px; emojiCategorySkip: 4px; emojiCategory: IconButton { width: 42px; height: emojiFooterHeight; iconPosition: point(-1px, 9px); } emojiCategoryRecent: IconButton(emojiCategory) { icon: emojiRecent; } emojiCategoryPeople: IconButton(emojiCategory) { icon: emojiPeople; } emojiCategoryNature: IconButton(emojiCategory) { icon: emojiNature; } emojiCategoryFood: IconButton(emojiCategory) { icon: emojiFood; } emojiCategoryActivity: IconButton(emojiCategory) { icon: emojiActivity; } emojiCategoryTravel: IconButton(emojiCategory) { icon: emojiTravel; } emojiCategoryObjects: IconButton(emojiCategory) { icon: emojiObjects; } emojiCategorySymbols: IconButton(emojiCategory) { icon: emojiSymbols; } emojiPanAnimation: PanelAnimation(defaultPanelAnimation) { fadeBg: emojiPanBg; } emojiPanPadding: 12px; emojiPanWidth: 345px; emojiPanMinHeight: 278px; emojiPanMaxHeight: 640px; emojiPanHeightRatio: 0.75; emojiPanShowDuration: 200; emojiPanDuration: 200; emojiPanHover: windowBgOver; emojiPanSlideDuration: 200; emojiPanDesiredSize: 45px; emojiPanHeader: 42px; emojiPanHeaderFont: semiboldFont; emojiPanHeaderLeft: 22px; emojiPanHeaderTop: 12px; emojiColorsPadding: 5px; emojiColorsSep: 1px; emojiColorsSepColor: shadowFg; emojiSwitchSkip: 27px; emojiSwitchImgSkip: 21px; emojiSwitchColor: windowActiveTextFg; emojiSwitchStickers: icon {{ "emoji_switch", emojiSwitchColor }}; emojiSwitchEmoji: icon {{ "emoji_switch-flip_horizontal", emojiSwitchColor }}; hashtagClose: IconButton { width: 30px; height: 30px; icon: smallCloseIcon; iconOver: smallCloseIconOver; iconPosition: point(10px, 10px); rippleAreaPosition: point(5px, 5px); rippleAreaSize: 20px; ripple: RippleAnimation(defaultRippleAnimation) { color: windowBgOver; } } stickerPanWidthMin: 64px; stickerPanSize: size(stickerPanWidthMin, stickerPanWidthMin); stickerPanPadding: 11px; stickerPanDeleteIconBg: icon {{ "emoji_delete_bg", stickerPanDeleteBg }}; stickerPanDeleteIconFg: icon {{ "emoji_delete", stickerPanDeleteFg }}; stickerPanDeleteOpacityBg: 0.3; stickerPanDeleteOpacityBgOver: 0.5; stickerPanDeleteOpacityFg: 0.8; stickerPanDeleteOpacityFgOver: 1.; stickerPanRemoveSet: hashtagClose; stickerIconWidth: 42px; stickerIconHeight: emojiFooterHeight; stickerIconPadding: 5px; stickerIconOpacity: 0.7; stickerIconSel: 2px; stickerIconSelColor: emojiIconFgActive; stickerIconMove: 400; stickerPreviewDuration: 150; stickerPreviewMin: 0.1; stickerGroupCategorySize: 28px; stickerGroupCategoryAbout: defaultTextStyle; stickerGroupCategoryAddMargin: margins(0px, 10px, 0px, 5px); stickerGroupCategoryAdd: stickersTrendingAdd; stickersToastMaxWidth: 340px; stickersToastPadding: margins(16px, 13px, 16px, 12px); stickersEmpty: icon {{ "stickers_empty", windowSubTextFg }}; inlineBotsScroll: ScrollArea(defaultSolidScroll) { deltat: stickerPanPadding; deltab: stickerPanPadding; } gifsSearchField: contactsSearchField; gifsSearchFieldPosition: point(42px, 7px); gifsSearchCancel: contactsSearchCancel; gifsSearchCancelPosition: point(1px, 1px); gifsSearchIcon: boxFieldSearchIcon; gifsSearchIconPosition: point(6px, 7px); emojiSuggestionsDropdown: InnerDropdown(defaultInnerDropdown) { scroll: ScrollArea(defaultSolidScroll) { deltat: 0px; deltab: 0px; round: 1px; width: 8px; deltax: 3px; } scrollMargin: margins(0px, 5px, 0px, 5px); scrollPadding: margins(0px, 3px, 0px, 3px); } emojiSuggestionsMenu: Menu(defaultMenu) { itemPadding: margins(48px, 8px, 17px, 7px); widthMax: 512px; } mentionHeight: 40px; mentionScroll: ScrollArea(defaultScrollArea) { topsh: 0px; bottomsh: 0px; } mentionPadding: margins(8px, 5px, 8px, 5px); mentionTop: 11px; mentionFont: linkFont; mentionNameFg: windowFg; mentionNameFgOver: windowFgOver; mentionPhotoSize: msgPhotoSize; mentionBg: windowBg; mentionBgOver: windowBgOver; mentionFg: windowSubTextFg; mentionFgOver: windowSubTextFgOver; mentionFgActive: windowActiveTextFg; mentionFgOverActive: windowActiveTextFg; ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp�����������������������������0000664�0000000�0000000�00000053160�13262451251�0026276�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "chat_helpers/emoji_list_widget.h" #include "ui/widgets/buttons.h" #include "styles/style_chat_helpers.h" #include "ui/widgets/shadow.h" #include "lang/lang_keys.h" namespace ChatHelpers { class EmojiListWidget::Footer : public TabbedSelector::InnerFooter { public: Footer(not_null<EmojiListWidget*> parent); void setCurrentSectionIcon(Section section); protected: void processPanelHideFinished() override; void resizeEvent(QResizeEvent *e) override; private: void prepareSection(int &left, int top, int _width, Ui::IconButton *sectionIcon, Section section); void setActiveSection(Section section); not_null<EmojiListWidget*> _pan; std::array<object_ptr<Ui::IconButton>, kEmojiSectionCount> _sections; }; EmojiListWidget::Footer::Footer(not_null<EmojiListWidget*> parent) : InnerFooter(parent) , _pan(parent) , _sections { { object_ptr<Ui::IconButton>(this, st::emojiCategoryRecent), object_ptr<Ui::IconButton>(this, st::emojiCategoryPeople), object_ptr<Ui::IconButton>(this, st::emojiCategoryNature), object_ptr<Ui::IconButton>(this, st::emojiCategoryFood), object_ptr<Ui::IconButton>(this, st::emojiCategoryActivity), object_ptr<Ui::IconButton>(this, st::emojiCategoryTravel), object_ptr<Ui::IconButton>(this, st::emojiCategoryObjects), object_ptr<Ui::IconButton>(this, st::emojiCategorySymbols), } } { for (auto i = 0; i != _sections.size(); ++i) { auto value = static_cast<Section>(i); _sections[i]->setClickedCallback([=] { setActiveSection(value); }); } setCurrentSectionIcon(Section::Recent); } void EmojiListWidget::Footer::resizeEvent(QResizeEvent *e) { auto availableWidth = (width() - st::emojiCategorySkip * 2); auto buttonWidth = availableWidth / _sections.size(); auto buttonsWidth = buttonWidth * _sections.size(); auto left = (width() - buttonsWidth) / 2; for (auto &button : _sections) { button->resizeToWidth(buttonWidth); button->moveToLeft(left, 0); left += button->width(); } } void EmojiListWidget::Footer::processPanelHideFinished() { // Preserve panel state through visibility toggles. //setCurrentSectionIcon(Section::Recent); } void EmojiListWidget::Footer::setCurrentSectionIcon(Section section) { std::array<const style::icon *, kEmojiSectionCount> overrides = { { &st::emojiRecentActive, &st::emojiPeopleActive, &st::emojiNatureActive, &st::emojiFoodActive, &st::emojiActivityActive, &st::emojiTravelActive, &st::emojiObjectsActive, &st::emojiSymbolsActive, } }; for (auto i = 0; i != _sections.size(); ++i) { _sections[i]->setIconOverride((section == static_cast<Section>(i)) ? overrides[i] : nullptr); } } void EmojiListWidget::Footer::setActiveSection(Ui::Emoji::Section section) { _pan->showEmojiSection(section); } EmojiColorPicker::EmojiColorPicker(QWidget *parent) : TWidget(parent) { setMouseTracking(true); _hideTimer.setSingleShot(true); connect(&_hideTimer, SIGNAL(timeout()), this, SLOT(hideAnimated())); } void EmojiColorPicker::showEmoji(EmojiPtr emoji) { if (!emoji || !emoji->hasVariants()) { return; } _ignoreShow = false; _variants.resize(emoji->variantsCount() + 1); for (auto i = 0, size = _variants.size(); i != size; ++i) { _variants[i] = emoji->variant(i); } updateSize(); if (!_cache.isNull()) _cache = QPixmap(); showAnimated(); } void EmojiColorPicker::updateSize() { auto width = st::emojiPanMargins.left() + _singleSize.width() * _variants.size() + (_variants.size() - 2) * st::emojiColorsPadding + st::emojiColorsSep + st::emojiPanMargins.right(); auto height = st::emojiPanMargins.top() + 2 * st::emojiColorsPadding + _singleSize.height() + st::emojiPanMargins.bottom(); resize(width, height); update(); updateSelected(); } void EmojiColorPicker::paintEvent(QPaintEvent *e) { Painter p(this); auto opacity = _a_opacity.current(getms(), _hiding ? 0. : 1.); if (opacity < 1.) { if (opacity > 0.) { p.setOpacity(opacity); } else { return; } } if (e->rect() != rect()) { p.setClipRect(e->rect()); } auto inner = rect().marginsRemoved(st::emojiPanMargins); if (!_cache.isNull()) { p.drawPixmap(0, 0, _cache); return; } Ui::Shadow::paint(p, inner, width(), st::defaultRoundShadow); App::roundRect(p, inner, st::boxBg, BoxCorners); auto x = st::emojiPanMargins.left() + 2 * st::emojiColorsPadding + _singleSize.width(); if (rtl()) x = width() - x - st::emojiColorsSep; p.fillRect(x, st::emojiPanMargins.top() + st::emojiColorsPadding, st::emojiColorsSep, inner.height() - st::emojiColorsPadding * 2, st::emojiColorsSepColor); if (_variants.isEmpty()) return; for (auto i = 0, count = _variants.size(); i != count; ++i) { drawVariant(p, i); } } void EmojiColorPicker::enterEventHook(QEvent *e) { _hideTimer.stop(); if (_hiding) showAnimated(); TWidget::enterEventHook(e); } void EmojiColorPicker::leaveEventHook(QEvent *e) { TWidget::leaveEventHook(e); } void EmojiColorPicker::mousePressEvent(QMouseEvent *e) { if (e->button() != Qt::LeftButton) { return; } _lastMousePos = e->globalPos(); updateSelected(); _pressedSel = _selected; } void EmojiColorPicker::mouseReleaseEvent(QMouseEvent *e) { handleMouseRelease(e->globalPos()); } void EmojiColorPicker::handleMouseRelease(QPoint globalPos) { _lastMousePos = globalPos; int32 pressed = _pressedSel; _pressedSel = -1; updateSelected(); if (_selected >= 0 && (pressed < 0 || _selected == pressed)) { emit emojiSelected(_variants[_selected]); } _ignoreShow = true; hideAnimated(); } void EmojiColorPicker::setSingleSize(QSize size) { _singleSize = size; updateSize(); } void EmojiColorPicker::handleMouseMove(QPoint globalPos) { _lastMousePos = globalPos; updateSelected(); } void EmojiColorPicker::mouseMoveEvent(QMouseEvent *e) { handleMouseMove(e->globalPos()); } void EmojiColorPicker::animationCallback() { update(); if (!_a_opacity.animating()) { _cache = QPixmap(); if (_hiding) { hide(); emit hidden(); } else { _lastMousePos = QCursor::pos(); updateSelected(); } } } void EmojiColorPicker::hideFast() { clearSelection(); _a_opacity.finish(); _cache = QPixmap(); hide(); emit hidden(); } void EmojiColorPicker::hideAnimated() { if (_cache.isNull()) { _cache = Ui::GrabWidget(this); clearSelection(); } _hiding = true; _a_opacity.start([this] { animationCallback(); }, 1., 0., st::emojiPanDuration); } void EmojiColorPicker::showAnimated() { if (_ignoreShow) return; if (!isHidden() && !_hiding) { return; } _hiding = false; if (_cache.isNull()) { _cache = Ui::GrabWidget(this); clearSelection(); } show(); _a_opacity.start([this] { animationCallback(); }, 0., 1., st::emojiPanDuration); } void EmojiColorPicker::clearSelection() { _pressedSel = -1; setSelected(-1); _lastMousePos = mapToGlobal(QPoint(-10, -10)); } void EmojiColorPicker::updateSelected() { auto newSelected = -1; auto p = mapFromGlobal(_lastMousePos); auto sx = rtl() ? (width() - p.x()) : p.x(), y = p.y() - st::emojiPanMargins.top() - st::emojiColorsPadding; if (y >= 0 && y < _singleSize.height()) { auto x = sx - st::emojiPanMargins.left() - st::emojiColorsPadding; if (x >= 0 && x < _singleSize.width()) { newSelected = 0; } else { x -= _singleSize.width() + 2 * st::emojiColorsPadding + st::emojiColorsSep; if (x >= 0 && x < _singleSize.width() * (_variants.size() - 1)) { newSelected = (x / _singleSize.width()) + 1; } } } setSelected(newSelected); } void EmojiColorPicker::setSelected(int newSelected) { if (_selected == newSelected) { return; } auto updateSelectedRect = [this] { if (_selected < 0) return; auto addedSkip = (_selected > 0) ? (2 * st::emojiColorsPadding + st::emojiColorsSep) : 0; auto left = st::emojiPanMargins.left() + st::emojiColorsPadding + _selected * _singleSize.width() + addedSkip; rtlupdate( left, st::emojiPanMargins.top() + st::emojiColorsPadding, _singleSize.width(), _singleSize.height()); }; updateSelectedRect(); _selected = newSelected; updateSelectedRect(); setCursor((_selected >= 0) ? style::cur_pointer : style::cur_default); } void EmojiColorPicker::drawVariant(Painter &p, int variant) { QPoint w(st::emojiPanMargins.left() + st::emojiColorsPadding + variant * _singleSize.width() + (variant ? 2 * st::emojiColorsPadding + st::emojiColorsSep : 0), st::emojiPanMargins.top() + st::emojiColorsPadding); if (variant == _selected) { QPoint tl(w); if (rtl()) tl.setX(width() - tl.x() - _singleSize.width()); App::roundRect(p, QRect(tl, _singleSize), st::emojiPanHover, StickerHoverCorners); } auto esize = Ui::Emoji::Size(Ui::Emoji::Index() + 1); p.drawPixmapLeft(w.x() + (_singleSize.width() - (esize / cIntRetinaFactor())) / 2, w.y() + (_singleSize.height() - (esize / cIntRetinaFactor())) / 2, width(), App::emojiLarge(), QRect(_variants[variant]->x() * esize, _variants[variant]->y() * esize, esize, esize)); } EmojiListWidget::EmojiListWidget(QWidget *parent, not_null<Window::Controller*> controller) : Inner(parent, controller) , _picker(this) { setMouseTracking(true); setAttribute(Qt::WA_OpaquePaintEvent); _picker->hide(); _esize = Ui::Emoji::Size(Ui::Emoji::Index() + 1); for (auto i = 0; i != kEmojiSectionCount; ++i) { _counts[i] = Ui::Emoji::GetSectionCount(static_cast<Section>(i)); } _showPickerTimer.setSingleShot(true); connect(&_showPickerTimer, SIGNAL(timeout()), this, SLOT(onShowPicker())); connect(_picker, SIGNAL(emojiSelected(EmojiPtr)), this, SLOT(onColorSelected(EmojiPtr))); connect(_picker, SIGNAL(hidden()), this, SLOT(onPickerHidden())); } void EmojiListWidget::visibleTopBottomUpdated( int visibleTop, int visibleBottom) { Inner::visibleTopBottomUpdated(visibleTop, visibleBottom); if (_footer) { _footer->setCurrentSectionIcon(currentSection(visibleTop)); } } object_ptr<TabbedSelector::InnerFooter> EmojiListWidget::createFooter() { Expects(_footer == nullptr); auto result = object_ptr<Footer>(this); _footer = result; return std::move(result); } template <typename Callback> bool EmojiListWidget::enumerateSections(Callback callback) const { Expects(_columnCount > 0); auto info = SectionInfo(); for (auto i = 0; i != kEmojiSectionCount; ++i) { info.section = i; info.count = _counts[i]; info.rowsCount = (info.count / _columnCount) + ((info.count % _columnCount) ? 1 : 0); info.rowsTop = info.top + (i == 0 ? st::emojiPanPadding : st::emojiPanHeader); info.rowsBottom = info.rowsTop + info.rowsCount * _singleSize.height(); if (!callback(info)) { return false; } info.top = info.rowsBottom; } return true; } EmojiListWidget::SectionInfo EmojiListWidget::sectionInfo(int section) const { Expects(section >= 0 && section < kEmojiSectionCount); auto result = SectionInfo(); enumerateSections([searchForSection = section, &result](const SectionInfo &info) { if (info.section == searchForSection) { result = info; return false; } return true; }); return result; } EmojiListWidget::SectionInfo EmojiListWidget::sectionInfoByOffset(int yOffset) const { auto result = SectionInfo(); enumerateSections([&result, yOffset](const SectionInfo &info) { if (yOffset < info.rowsBottom || info.section == kEmojiSectionCount - 1) { result = info; return false; } return true; }); return result; } int EmojiListWidget::countDesiredHeight(int newWidth) { auto fullWidth = (st::buttonRadius + newWidth + st::emojiScroll.width); _columnCount = std::max( (fullWidth - st::emojiPadding * 2) / st::emojiPanDesiredSize, 1); _rowsLeft = fullWidth / (_columnCount * 4 + 2); auto rowsRight = std::max(_rowsLeft, st::emojiScroll.width); auto singleWidth = (fullWidth - _rowsLeft - rowsRight) / _columnCount; _rowsLeft -= st::buttonRadius; _singleSize = QSize(singleWidth, singleWidth - 4 * st::lineWidth); _picker->setSingleSize(_singleSize); return sectionInfo(kEmojiSectionCount - 1).rowsBottom + st::emojiPanPadding; } void EmojiListWidget::ensureLoaded(int section) { Expects(section >= 0 && section < kEmojiSectionCount); if (!_emoji[section].isEmpty()) { return; } _emoji[section] = Ui::Emoji::GetSection(static_cast<Section>(section)); _counts[section] = _emoji[section].size(); if (static_cast<Section>(section) == Section::Recent) { return; } for (auto &emoji : _emoji[section]) { if (emoji->hasVariants()) { auto j = cEmojiVariants().constFind(emoji->nonColoredId()); if (j != cEmojiVariants().cend()) { emoji = emoji->variant(j.value()); } } } } void EmojiListWidget::paintEvent(QPaintEvent *e) { Painter p(this); QRect r = e ? e->rect() : rect(); if (r != rect()) { p.setClipRect(r); } p.fillRect(r, st::emojiPanBg); auto fromColumn = floorclamp(r.x() - _rowsLeft, _singleSize.width(), 0, _columnCount); auto toColumn = ceilclamp(r.x() + r.width() - _rowsLeft, _singleSize.width(), 0, _columnCount); if (rtl()) { qSwap(fromColumn, toColumn); fromColumn = _columnCount - fromColumn; toColumn = _columnCount - toColumn; } enumerateSections([this, &p, r, fromColumn, toColumn](const SectionInfo &info) { if (r.top() >= info.rowsBottom) { return true; } else if (r.top() + r.height() <= info.top) { return false; } if (info.section > 0 && r.top() < info.rowsTop) { p.setFont(st::emojiPanHeaderFont); p.setPen(st::emojiPanHeaderFg); p.drawTextLeft(st::emojiPanHeaderLeft - st::buttonRadius, info.top + st::emojiPanHeaderTop, width(), lang(LangKey(lng_emoji_category0 + info.section))); } if (r.top() + r.height() > info.rowsTop) { ensureLoaded(info.section); auto fromRow = floorclamp(r.y() - info.rowsTop, _singleSize.height(), 0, info.rowsCount); auto toRow = ceilclamp(r.y() + r.height() - info.rowsTop, _singleSize.height(), 0, info.rowsCount); for (auto i = fromRow; i < toRow; ++i) { for (auto j = fromColumn; j < toColumn; ++j) { auto index = i * _columnCount + j; if (index >= info.count) break; auto selected = (!_picker->isHidden() && info.section * MatrixRowShift + index == _pickerSel) || (info.section * MatrixRowShift + index == _selected); auto w = QPoint(_rowsLeft + j * _singleSize.width(), info.rowsTop + i * _singleSize.height()); if (selected) { auto tl = w; if (rtl()) tl.setX(width() - tl.x() - _singleSize.width()); App::roundRect(p, QRect(tl, _singleSize), st::emojiPanHover, StickerHoverCorners); } auto sourceRect = QRect(_emoji[info.section][index]->x() * _esize, _emoji[info.section][index]->y() * _esize, _esize, _esize); auto imageLeft = w.x() + (_singleSize.width() - (_esize / cIntRetinaFactor())) / 2; auto imageTop = w.y() + (_singleSize.height() - (_esize / cIntRetinaFactor())) / 2; p.drawPixmapLeft(imageLeft, imageTop, width(), App::emojiLarge(), sourceRect); } } } return true; }); } bool EmojiListWidget::checkPickerHide() { if (!_picker->isHidden() && _pickerSel >= 0) { _picker->hideAnimated(); _pickerSel = -1; updateSelected(); return true; } return false; } void EmojiListWidget::mousePressEvent(QMouseEvent *e) { _lastMousePos = e->globalPos(); updateSelected(); if (checkPickerHide() || e->button() != Qt::LeftButton) { return; } _pressedSel = _selected; if (_selected >= 0) { auto section = (_selected / MatrixRowShift); auto sel = _selected % MatrixRowShift; if (section < kEmojiSectionCount && sel < _emoji[section].size() && _emoji[section][sel]->hasVariants()) { _pickerSel = _selected; setCursor(style::cur_default); if (!cEmojiVariants().contains(_emoji[section][sel]->nonColoredId())) { onShowPicker(); } else { _showPickerTimer.start(500); } } } } void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) { int32 pressed = _pressedSel; _pressedSel = -1; _lastMousePos = e->globalPos(); if (!_picker->isHidden()) { if (_picker->rect().contains(_picker->mapFromGlobal(_lastMousePos))) { return _picker->handleMouseRelease(QCursor::pos()); } else if (_pickerSel >= 0) { auto section = (_pickerSel / MatrixRowShift); auto sel = _pickerSel % MatrixRowShift; if (section < kEmojiSectionCount && sel < _emoji[section].size() && _emoji[section][sel]->hasVariants()) { if (cEmojiVariants().contains(_emoji[section][sel]->nonColoredId())) { _picker->hideAnimated(); _pickerSel = -1; } } } } updateSelected(); if (_showPickerTimer.isActive()) { _showPickerTimer.stop(); _pickerSel = -1; _picker->hide(); } if (_selected < 0 || _selected != pressed) return; if (_selected >= kEmojiSectionCount * MatrixRowShift) { return; } auto section = (_selected / MatrixRowShift); auto sel = _selected % MatrixRowShift; if (sel < _emoji[section].size()) { auto emoji = _emoji[section][sel]; if (emoji->hasVariants() && !_picker->isHidden()) return; selectEmoji(emoji); } } void EmojiListWidget::selectEmoji(EmojiPtr emoji) { Ui::Emoji::AddRecent(emoji); emit selected(emoji); } void EmojiListWidget::onShowPicker() { if (_pickerSel < 0) return; auto section = (_pickerSel / MatrixRowShift); auto sel = _pickerSel % MatrixRowShift; if (section < kEmojiSectionCount && sel < _emoji[section].size() && _emoji[section][sel]->hasVariants()) { _picker->showEmoji(_emoji[section][sel]); auto y = emojiRect(section, sel).y(); y -= _picker->height() - st::buttonRadius + getVisibleTop(); if (y < st::emojiPanHeader) { y += _picker->height() - st::buttonRadius + _singleSize.height() - st::buttonRadius; } auto xmax = width() - _picker->width(); auto coef = float64(sel % _columnCount) / float64(_columnCount - 1); if (rtl()) coef = 1. - coef; _picker->move(qRound(xmax * coef), y); emit disableScroll(true); } } void EmojiListWidget::onPickerHidden() { _pickerSel = -1; update(); emit disableScroll(false); _lastMousePos = QCursor::pos(); updateSelected(); } QRect EmojiListWidget::emojiRect(int section, int sel) { Expects(_columnCount > 0); auto info = sectionInfo(section); auto countTillItem = (sel - (sel % _columnCount)); auto rowsToSkip = (countTillItem / _columnCount) + ((countTillItem % _columnCount) ? 1 : 0); auto x = _rowsLeft + ((sel % _columnCount) * _singleSize.width()); auto y = info.rowsTop + rowsToSkip * _singleSize.height(); return QRect(x, y, _singleSize.width(), _singleSize.height()); } void EmojiListWidget::onColorSelected(EmojiPtr emoji) { if (emoji->hasVariants()) { cRefEmojiVariants().insert(emoji->nonColoredId(), emoji->variantIndex(emoji)); } if (_pickerSel >= 0) { auto section = (_pickerSel / MatrixRowShift); auto sel = _pickerSel % MatrixRowShift; if (section >= 0 && section < kEmojiSectionCount) { _emoji[section][sel] = emoji; rtlupdate(emojiRect(section, sel)); } } selectEmoji(emoji); _picker->hideAnimated(); } void EmojiListWidget::mouseMoveEvent(QMouseEvent *e) { _lastMousePos = e->globalPos(); if (!_picker->isHidden()) { if (_picker->rect().contains(_picker->mapFromGlobal(_lastMousePos))) { return _picker->handleMouseMove(QCursor::pos()); } else { _picker->clearSelection(); } } updateSelected(); } void EmojiListWidget::leaveEventHook(QEvent *e) { clearSelection(); } void EmojiListWidget::leaveToChildEvent(QEvent *e, QWidget *child) { clearSelection(); } void EmojiListWidget::enterFromChildEvent(QEvent *e, QWidget *child) { _lastMousePos = QCursor::pos(); updateSelected(); } void EmojiListWidget::clearSelection() { _lastMousePos = mapToGlobal(QPoint(-10, -10)); _pressedSel = -1; setSelected(-1); } Ui::Emoji::Section EmojiListWidget::currentSection(int yOffset) const { return static_cast<Section>(sectionInfoByOffset(yOffset).section); } TabbedSelector::InnerFooter *EmojiListWidget::getFooter() const { return _footer; } void EmojiListWidget::processHideFinished() { if (!_picker->isHidden()) { _picker->hideFast(); _pickerSel = -1; } clearSelection(); } void EmojiListWidget::refreshRecent() { clearSelection(); _emoji[0] = Ui::Emoji::GetSection(Section::Recent); _counts[0] = _emoji[0].size(); resizeToWidth(width()); } bool EmojiListWidget::eventHook(QEvent *e) { if (e->type() == QEvent::ParentChange) { if (_picker->parentWidget() != parentWidget()) { _picker->setParent(parentWidget()); } _picker->raise(); } return Inner::eventHook(e); } void EmojiListWidget::updateSelected() { if (_pressedSel >= 0 || _pickerSel >= 0) return; auto newSelected = -1; auto p = mapFromGlobal(_lastMousePos); auto info = sectionInfoByOffset(p.y()); if (p.y() >= info.rowsTop && p.y() < info.rowsBottom) { auto sx = (rtl() ? width() - p.x() : p.x()) - _rowsLeft; if (sx >= 0 && sx < _columnCount * _singleSize.width()) { newSelected = qFloor((p.y() - info.rowsTop) / _singleSize.height()) * _columnCount + qFloor(sx / _singleSize.width()); if (newSelected >= _emoji[info.section].size()) { newSelected = -1; } else { newSelected += info.section * MatrixRowShift; } } } setSelected(newSelected); } void EmojiListWidget::setSelected(int newSelected) { if (_selected == newSelected) { return; } auto updateSelected = [this]() { if (_selected < 0) return; rtlupdate(emojiRect(_selected / MatrixRowShift, _selected % MatrixRowShift)); }; updateSelected(); _selected = newSelected; updateSelected(); setCursor((_selected >= 0) ? style::cur_pointer : style::cur_default); if (_selected >= 0 && !_picker->isHidden()) { if (_selected != _pickerSel) { _picker->hideAnimated(); } else { _picker->showAnimated(); } } } void EmojiListWidget::showEmojiSection(Section section) { clearSelection(); refreshRecent(); auto y = 0; enumerateSections([&y, sectionForSearch = section](const SectionInfo &info) { if (static_cast<Section>(info.section) == sectionForSearch) { y = info.top; return false; } return true; }); emit scrollToY(y); _lastMousePos = QCursor::pos(); update(); } } // namespace ChatHelpers ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h�������������������������������0000664�0000000�0000000�00000007147�13262451251�0025747�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "chat_helpers/tabbed_selector.h" namespace Window { class Controller; } // namespace Window namespace ChatHelpers { constexpr auto kEmojiSectionCount = 8; class EmojiColorPicker : public TWidget { Q_OBJECT public: EmojiColorPicker(QWidget *parent); void showEmoji(EmojiPtr emoji); void clearSelection(); void handleMouseMove(QPoint globalPos); void handleMouseRelease(QPoint globalPos); void setSingleSize(QSize size); void hideFast(); public slots: void showAnimated(); void hideAnimated(); signals: void emojiSelected(EmojiPtr emoji); void hidden(); protected: void paintEvent(QPaintEvent *e) override; void enterEventHook(QEvent *e) override; void leaveEventHook(QEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; private: void animationCallback(); void updateSize(); void drawVariant(Painter &p, int variant); void updateSelected(); void setSelected(int newSelected); bool _ignoreShow = false; QVector<EmojiPtr> _variants; int _selected = -1; int _pressedSel = -1; QPoint _lastMousePos; QSize _singleSize; bool _hiding = false; QPixmap _cache; Animation _a_opacity; QTimer _hideTimer; }; class EmojiListWidget : public TabbedSelector::Inner { Q_OBJECT public: EmojiListWidget(QWidget *parent, not_null<Window::Controller*> controller); using Section = Ui::Emoji::Section; void refreshRecent() override; void clearSelection() override; object_ptr<TabbedSelector::InnerFooter> createFooter() override; void showEmojiSection(Section section); Section currentSection(int yOffset) const; public slots: void onShowPicker(); void onPickerHidden(); void onColorSelected(EmojiPtr emoji); bool checkPickerHide(); signals: void selected(EmojiPtr emoji); void switchToStickers(); protected: void visibleTopBottomUpdated( int visibleTop, int visibleBottom) override; void mousePressEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void paintEvent(QPaintEvent *e) override; void leaveEventHook(QEvent *e) override; void leaveToChildEvent(QEvent *e, QWidget *child) override; void enterFromChildEvent(QEvent *e, QWidget *child) override; bool eventHook(QEvent *e) override; TabbedSelector::InnerFooter *getFooter() const override; void processHideFinished() override; int countDesiredHeight(int newWidth) override; private: class Footer; struct SectionInfo { int section = 0; int count = 0; int top = 0; int rowsCount = 0; int rowsTop = 0; int rowsBottom = 0; }; template <typename Callback> bool enumerateSections(Callback callback) const; SectionInfo sectionInfo(int section) const; SectionInfo sectionInfoByOffset(int yOffset) const; void ensureLoaded(int section); int countSectionTop(int section) const; void updateSelected(); void setSelected(int newSelected); void selectEmoji(EmojiPtr emoji); QRect emojiRect(int section, int sel); Footer *_footer = nullptr; int _counts[kEmojiSectionCount]; QVector<EmojiPtr> _emoji[kEmojiSectionCount]; int _rowsLeft = 0; int _columnCount = 1; QSize _singleSize; int _esize = 0; int _selected = -1; int _pressedSel = -1; int _pickerSel = -1; QPoint _lastMousePos; object_ptr<EmojiColorPicker> _picker; QTimer _showPickerTimer; }; } // namespace ChatHelpers �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/chat_helpers/emoji_suggestions_helper.h������������������������0000664�0000000�0000000�00000001402�13262451251�0027326�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "emoji_suggestions.h" #include "emoji_suggestions_data.h" namespace Ui { namespace Emoji { inline utf16string QStringToUTF16(const QString &string) { return utf16string(reinterpret_cast<const utf16char*>(string.constData()), string.size()); } inline QString QStringFromUTF16(utf16string string) { return QString::fromRawData(reinterpret_cast<const QChar*>(string.data()), string.size()); } constexpr auto kSuggestionMaxLength = internal::kReplacementMaxLength; } // namespace Emoji } // namespace Ui ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp����������������������0000664�0000000�0000000�00000041327�13262451251�0027677�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "chat_helpers/emoji_suggestions_widget.h" #include "chat_helpers/emoji_suggestions_helper.h" #include "ui/effects/ripple_animation.h" #include "ui/widgets/shadow.h" #include "platform/platform_specific.h" #include "styles/style_chat_helpers.h" #include "ui/widgets/inner_dropdown.h" namespace Ui { namespace Emoji { namespace { constexpr auto kRowLimit = 5; } // namespace class SuggestionsWidget::Row { public: Row(not_null<EmojiPtr> emoji, const QString &label, const QString &replacement); Row(const Row &other) = delete; Row &operator=(const Row &other) = delete; Row(Row &&other) = default; Row &operator=(Row &&other) = default; ~Row(); not_null<EmojiPtr> emoji() const { return _emoji; } const QString &label() const { return _label; } const QString &replacement() const { return _replacement; } RippleAnimation *ripple() const { return _ripple.get(); } void setRipple(std::unique_ptr<RippleAnimation> ripple) { _ripple = std::move(ripple); } void resetRipple() { _ripple.reset(); } private: not_null<EmojiPtr> _emoji; QString _label; QString _replacement; std::unique_ptr<RippleAnimation> _ripple; }; SuggestionsWidget::Row::Row(not_null<EmojiPtr> emoji, const QString &label, const QString &replacement) : _emoji(emoji) , _label(label) , _replacement(replacement) { } SuggestionsWidget::Row::~Row() = default; SuggestionsWidget::SuggestionsWidget(QWidget *parent, const style::Menu &st) : TWidget(parent) , _st(&st) , _rowHeight(_st->itemPadding.top() + _st->itemFont->height + _st->itemPadding.bottom()) { setMouseTracking(true); } void SuggestionsWidget::showWithQuery(const QString &query) { if (_query == query) { return; } _query = query; auto rows = getRowsByQuery(); if (rows.empty()) { toggleAnimated.notify(false, true); } clearSelection(); _rows = std::move(rows); resizeToRows(); update(); if (!_rows.empty()) { setSelected(0); } if (!_rows.empty()) { toggleAnimated.notify(true, true); } } std::vector<SuggestionsWidget::Row> SuggestionsWidget::getRowsByQuery() const { auto result = std::vector<Row>(); if (_query.isEmpty()) { return result; } auto suggestions = GetSuggestions(QStringToUTF16(_query)); if (suggestions.empty()) { return result; } auto count = suggestions.size(); auto suggestionsEmoji = std::vector<EmojiPtr>(count, nullptr); for (auto i = 0; i != count; ++i) { suggestionsEmoji[i] = Find(QStringFromUTF16(suggestions[i].emoji())); } auto recents = 0; auto &recent = GetRecent(); for (auto &item : recent) { auto emoji = item.first->original(); if (!emoji) emoji = item.first; auto it = std::find(suggestionsEmoji.begin(), suggestionsEmoji.end(), emoji); if (it != suggestionsEmoji.end()) { auto index = (it - suggestionsEmoji.begin()); if (index >= recents) { if (index > recents) { auto recentEmoji = suggestionsEmoji[index]; auto recentSuggestion = suggestions[index]; for (auto i = index; i != recents; --i) { suggestionsEmoji[i] = suggestionsEmoji[i - 1]; suggestions[i] = suggestions[i - 1]; } suggestionsEmoji[recents] = recentEmoji; suggestions[recents] = recentSuggestion; } ++recents; } } } result.reserve(kRowLimit); auto index = 0; for (auto &item : suggestions) { if (auto emoji = suggestionsEmoji[index++]) { if (emoji->hasVariants()) { auto it = cEmojiVariants().constFind(emoji->nonColoredId()); if (it != cEmojiVariants().cend()) { emoji = emoji->variant(it.value()); } } result.emplace_back(emoji, QStringFromUTF16(item.label()), QStringFromUTF16(item.replacement())); if (result.size() == kRowLimit) { break; } } } return result; } void SuggestionsWidget::resizeToRows() { auto newWidth = 0; for (auto &row : _rows) { accumulate_max(newWidth, countWidth(row)); } newWidth = snap(newWidth, _st->widthMin, _st->widthMax); auto newHeight = _st->skip + (_rows.size() * _rowHeight) + _st->skip; resize(newWidth, newHeight); } int SuggestionsWidget::countWidth(const Row &row) { auto textw = _st->itemFont->width(row.label()); return _st->itemPadding.left() + textw + _st->itemPadding.right(); } void SuggestionsWidget::paintEvent(QPaintEvent *e) { Painter p(this); auto ms = getms(); auto clip = e->rect(); auto topskip = QRect(0, 0, width(), _st->skip); auto bottomskip = QRect(0, height() - _st->skip, width(), _st->skip); if (clip.intersects(topskip)) p.fillRect(clip.intersected(topskip), _st->itemBg); if (clip.intersects(bottomskip)) p.fillRect(clip.intersected(bottomskip), _st->itemBg); auto top = _st->skip; p.setFont(_st->itemFont); auto from = floorclamp(clip.top() - top, _rowHeight, 0, _rows.size()); auto to = ceilclamp(clip.top() + clip.height() - top, _rowHeight, 0, _rows.size()); p.translate(0, top + from * _rowHeight); for (auto i = from; i != to; ++i) { auto &row = _rows[i]; auto selected = (i == _selected || i == _pressed); p.fillRect(0, 0, width(), _rowHeight, selected ? _st->itemBgOver : _st->itemBg); if (auto ripple = row.ripple()) { ripple->paint(p, 0, 0, width(), ms); if (ripple->empty()) { row.resetRipple(); } } auto emoji = row.emoji(); auto esize = Ui::Emoji::Size(Ui::Emoji::Index() + 1); p.drawPixmapLeft((_st->itemPadding.left() - (esize / cIntRetinaFactor())) / 2, (_rowHeight - (esize / cIntRetinaFactor())) / 2, width(), App::emojiLarge(), QRect(emoji->x() * esize, emoji->y() * esize, esize, esize)); p.setPen(selected ? _st->itemFgOver : _st->itemFg); p.drawTextLeft(_st->itemPadding.left(), _st->itemPadding.top(), width(), row.label()); p.translate(0, _rowHeight); } } void SuggestionsWidget::keyPressEvent(QKeyEvent *e) { handleKeyEvent(e->key()); } void SuggestionsWidget::handleKeyEvent(int key) { if (key == Qt::Key_Enter || key == Qt::Key_Return || key == Qt::Key_Tab) { triggerSelectedRow(); return; } if ((key != Qt::Key_Up && key != Qt::Key_Down) || _rows.size() < 1) { return; } auto delta = (key == Qt::Key_Down ? 1 : -1), start = _selected; if (start < 0 || start >= _rows.size()) { start = (delta > 0) ? (_rows.size() - 1) : 0; } auto newSelected = start + delta; if (newSelected < 0) { newSelected += _rows.size(); } else if (newSelected >= _rows.size()) { newSelected -= _rows.size(); } _mouseSelection = false; setSelected(newSelected); } void SuggestionsWidget::setSelected(int selected) { if (selected >= _rows.size()) { selected = -1; } if (_selected != selected) { updateSelectedItem(); _selected = selected; updateSelectedItem(); } } void SuggestionsWidget::setPressed(int pressed) { if (pressed >= _rows.size()) { pressed = -1; } if (_pressed != pressed) { _pressed = pressed; } } void SuggestionsWidget::clearMouseSelection() { if (_mouseSelection) { clearSelection(); } } void SuggestionsWidget::clearSelection() { _mouseSelection = false; setSelected(-1); } int SuggestionsWidget::itemTop(int index) { if (index > _rows.size()) { index = _rows.size(); } return _st->skip + (_rowHeight * index); } void SuggestionsWidget::updateItem(int index) { if (index >= 0 && index < _rows.size()) { update(0, itemTop(index), width(), _rowHeight); } } void SuggestionsWidget::updateSelectedItem() { updateItem(_selected); } void SuggestionsWidget::mouseMoveEvent(QMouseEvent *e) { auto inner = rect().marginsRemoved(QMargins(0, _st->skip, 0, _st->skip)); auto localPosition = e->pos(); if (inner.contains(localPosition)) { _mouseSelection = true; updateSelection(e->globalPos()); } else { clearMouseSelection(); } } void SuggestionsWidget::updateSelection(QPoint globalPosition) { if (!_mouseSelection) return; auto p = mapFromGlobal(globalPosition) - QPoint(0, _st->skip); auto selected = (p.y() >= 0) ? (p.y() / _rowHeight) : -1; setSelected((selected >= 0 && selected < _rows.size()) ? selected : -1); } void SuggestionsWidget::mousePressEvent(QMouseEvent *e) { if (!_mouseSelection) { return; } if (_selected >= 0 && _selected < _rows.size()) { setPressed(_selected); if (!_rows[_pressed].ripple()) { auto mask = RippleAnimation::rectMask(QSize(width(), _rowHeight)); _rows[_pressed].setRipple(std::make_unique<RippleAnimation>(_st->ripple, std::move(mask), [this, selected = _pressed] { updateItem(selected); })); } _rows[_pressed].ripple()->add(mapFromGlobal(QCursor::pos()) - QPoint(0, itemTop(_pressed))); } } void SuggestionsWidget::mouseReleaseEvent(QMouseEvent *e) { if (_pressed >= 0 && _pressed < _rows.size()) { auto pressed = _pressed; setPressed(-1); if (_rows[pressed].ripple()) { _rows[pressed].ripple()->lastStop(); } if (pressed == _selected) { triggerRow(_rows[_selected]); } } } void SuggestionsWidget::triggerSelectedRow() { if (_selected >= 0 && _selected < _rows.size()) { triggerRow(_rows[_selected]); } } void SuggestionsWidget::triggerRow(const Row &row) { triggered.notify(row.emoji()->text(), true); } void SuggestionsWidget::enterEventHook(QEvent *e) { auto mouse = QCursor::pos(); if (!rect().marginsRemoved(QMargins(0, _st->skip, 0, _st->skip)).contains(mapFromGlobal(mouse))) { clearMouseSelection(); } return TWidget::enterEventHook(e); } void SuggestionsWidget::leaveEventHook(QEvent *e) { clearMouseSelection(); return TWidget::leaveEventHook(e); } SuggestionsController::SuggestionsController(QWidget *parent, not_null<QTextEdit*> field) : QObject(nullptr) , _field(field) , _container(parent, st::emojiSuggestionsDropdown) , _suggestions(_container->setOwnedWidget(object_ptr<Ui::Emoji::SuggestionsWidget>(parent, st::emojiSuggestionsMenu))) { _container->setAutoHiding(false); _field->installEventFilter(this); connect(_field, &QTextEdit::textChanged, this, [this] { handleTextChange(); }); connect(_field, &QTextEdit::cursorPositionChanged, this, [this] { handleCursorPositionChange(); }); subscribe(_suggestions->toggleAnimated, [this](bool visible) { suggestionsUpdated(visible); }); subscribe(_suggestions->triggered, [this](QString replacement) { replaceCurrent(replacement); }); updateForceHidden(); handleTextChange(); } void SuggestionsController::handleTextChange() { _ignoreCursorPositionChange = true; InvokeQueued(this, [this] { _ignoreCursorPositionChange = false; }); auto query = getEmojiQuery(); if (query.isEmpty() || _textChangeAfterKeyPress) { _suggestions->showWithQuery(query); } } QString SuggestionsController::getEmojiQuery() { if (!cReplaceEmojis()) { return QString(); } auto cursor = _field->textCursor(); auto position = _field->textCursor().position(); if (cursor.anchor() != position) { return QString(); } auto findTextPart = [this, &position] { auto document = _field->document(); auto block = document->findBlock(position); for (auto i = block.begin(); !i.atEnd(); ++i) { auto fragment = i.fragment(); if (!fragment.isValid()) continue; auto from = fragment.position(); auto till = from + fragment.length(); if (from >= position || till < position) { continue; } if (fragment.charFormat().isImageFormat()) { continue; } position -= from; _queryStartPosition = from; return fragment.text(); } return QString(); }; auto text = findTextPart(); if (text.isEmpty()) { return QString(); } const auto isUpperCaseLetter = [](QChar ch) { return (ch >= 'A' && ch <= 'Z'); }; const auto isLetter = [](QChar ch) { return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9'); }; const auto isSuggestionChar = [](QChar ch) { return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || (ch == '_') || (ch == '-') || (ch == '+'); }; const auto isGoodCharBeforeSuggestion = [&](QChar ch) { return !isSuggestionChar(ch) || (ch == 0); }; Assert(position > 0 && position <= text.size()); for (auto i = position; i != 0;) { auto ch = text[--i]; if (ch == ':') { auto beforeColon = (i < 1) ? QChar(0) : text[i - 1]; if (isGoodCharBeforeSuggestion(beforeColon)) { // At least one letter after colon. if (position > i + 1) { // Skip colon and the first letter. _queryStartPosition += i + 2; const auto length = position - i; auto result = text.mid(i, length); const auto upperCaseLetters = std::count_if( result.begin(), result.end(), isUpperCaseLetter); const auto letters = std::count_if( result.begin(), result.end(), isLetter); if (letters == upperCaseLetters && letters == 1) { // No upper case single letter suggestions. // We don't want to suggest emoji on :D and :-P return QString(); } return result.toLower(); } } return QString(); } if (position - i > kSuggestionMaxLength) { return QString(); } if (!isSuggestionChar(ch)) { return QString(); } } return QString(); } void SuggestionsController::replaceCurrent(const QString &replacement) { auto cursor = _field->textCursor(); auto suggestion = getEmojiQuery(); if (suggestion.isEmpty()) { _suggestions->showWithQuery(QString()); } else { cursor.setPosition(cursor.position() - suggestion.size(), QTextCursor::KeepAnchor); cursor.insertText(replacement); } if (auto emoji = Find(replacement)) { if (emoji->hasVariants()) { auto it = cEmojiVariants().constFind(emoji->nonColoredId()); if (it != cEmojiVariants().cend()) { emoji = emoji->variant(it.value()); } } AddRecent(emoji); } } void SuggestionsController::handleCursorPositionChange() { InvokeQueued(this, [this] { if (_ignoreCursorPositionChange) { return; } _suggestions->showWithQuery(QString()); }); } void SuggestionsController::suggestionsUpdated(bool visible) { _shown = visible; if (_shown) { _container->resizeToContent(); updateGeometry(); if (!_forceHidden) { _container->showAnimated(Ui::PanelAnimation::Origin::BottomLeft); } } else if (!_forceHidden) { _container->hideAnimated(); } } void SuggestionsController::updateGeometry() { auto cursor = _field->textCursor(); cursor.setPosition(_queryStartPosition); auto aroundRect = _field->cursorRect(cursor); aroundRect.setTopLeft(_field->viewport()->mapToGlobal(aroundRect.topLeft())); aroundRect.setTopLeft(_container->parentWidget()->mapFromGlobal(aroundRect.topLeft())); auto boundingRect = _container->parentWidget()->rect(); auto origin = rtl() ? PanelAnimation::Origin::BottomRight : PanelAnimation::Origin::BottomLeft; auto point = rtl() ? (aroundRect.topLeft() + QPoint(aroundRect.width(), 0)) : aroundRect.topLeft(); point -= rtl() ? QPoint(_container->width() - st::emojiSuggestionsDropdown.padding.right(), _container->height()) : QPoint(st::emojiSuggestionsDropdown.padding.left(), _container->height()); if (rtl()) { if (point.x() < boundingRect.x()) { point.setX(boundingRect.x()); } if (point.x() + _container->width() > boundingRect.x() + boundingRect.width()) { point.setX(boundingRect.x() + boundingRect.width() - _container->width()); } } else { if (point.x() + _container->width() > boundingRect.x() + boundingRect.width()) { point.setX(boundingRect.x() + boundingRect.width() - _container->width()); } if (point.x() < boundingRect.x()) { point.setX(boundingRect.x()); } } if (point.y() < boundingRect.y()) { point.setY(aroundRect.y() + aroundRect.height()); origin = (origin == PanelAnimation::Origin::BottomRight) ? PanelAnimation::Origin::TopRight : PanelAnimation::Origin::TopLeft; } _container->move(point); } void SuggestionsController::updateForceHidden() { _forceHidden = !_field->isVisible(); if (_forceHidden) { _container->hideFast(); } else if (_shown) { _container->showFast(); } } bool SuggestionsController::eventFilter(QObject *object, QEvent *event) { if (object == _field) { auto type = event->type(); switch (type) { case QEvent::Move: case QEvent::Resize: { if (_shown) { updateGeometry(); } } break; case QEvent::Show: case QEvent::ShowToParent: case QEvent::Hide: case QEvent::HideToParent: { updateForceHidden(); } break; case QEvent::KeyPress: { auto key = static_cast<QKeyEvent*>(event)->key(); switch (key) { case Qt::Key_Enter: case Qt::Key_Return: case Qt::Key_Tab: case Qt::Key_Up: case Qt::Key_Down: if (_shown && !_forceHidden) { _suggestions->handleKeyEvent(key); return true; } break; case Qt::Key_Escape: if (_shown && !_forceHidden) { _suggestions->showWithQuery(QString()); return true; } break; } _textChangeAfterKeyPress = true; InvokeQueued(this, [this] { _textChangeAfterKeyPress = false; }); } break; } } return QObject::eventFilter(object, event); } void SuggestionsController::raise() { _container->raise(); } } // namespace Emoji } // namespace Ui ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.h������������������������0000664�0000000�0000000�00000004476�13262451251�0027350�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/effects/panel_animation.h" namespace Ui { class InnerDropdown; class FlatTextarea; namespace Emoji { class SuggestionsWidget : public TWidget { public: SuggestionsWidget(QWidget *parent, const style::Menu &st); void showWithQuery(const QString &query); void handleKeyEvent(int key); base::Observable<bool> toggleAnimated; base::Observable<QString> triggered; protected: void paintEvent(QPaintEvent *e) override; void keyPressEvent(QKeyEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void enterEventHook(QEvent *e) override; void leaveEventHook(QEvent *e) override; private: class Row; std::vector<Row> getRowsByQuery() const; void resizeToRows(); int countWidth(const Row &row); void setSelected(int selected); void setPressed(int pressed); void clearMouseSelection(); void clearSelection(); void updateSelectedItem(); int itemTop(int index); void updateItem(int index); void updateSelection(QPoint globalPosition); void triggerSelectedRow(); void triggerRow(const Row &row); not_null<const style::Menu*> _st; QString _query; std::vector<Row> _rows; int _rowHeight = 0; bool _mouseSelection = false; int _selected = -1; int _pressed = -1; }; class SuggestionsController : public QObject, private base::Subscriber { public: SuggestionsController(QWidget *parent, not_null<QTextEdit*> field); void raise(); protected: bool eventFilter(QObject *object, QEvent *event) override; private: void handleCursorPositionChange(); void handleTextChange(); QString getEmojiQuery(); void suggestionsUpdated(bool visible); void updateGeometry(); void updateForceHidden(); void replaceCurrent(const QString &replacement); bool _shown = false; bool _forceHidden = false; int _queryStartPosition = 0; bool _ignoreCursorPositionChange = false; bool _textChangeAfterKeyPress = false; QPointer<QTextEdit> _field; object_ptr<InnerDropdown> _container; QPointer<SuggestionsWidget> _suggestions; }; } // namespace Emoji } // namespace Ui ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp����������������������������0000664�0000000�0000000�00000075666�13262451251�0026460�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "chat_helpers/field_autocomplete.h" #include "data/data_document.h" #include "data/data_peer_values.h" #include "mainwindow.h" #include "apiwrap.h" #include "storage/localstorage.h" #include "ui/widgets/scroll_area.h" #include "styles/style_history.h" #include "styles/style_widgets.h" #include "styles/style_chat_helpers.h" #include "auth_session.h" #include "chat_helpers/stickers.h" FieldAutocomplete::FieldAutocomplete(QWidget *parent) : TWidget(parent) , _scroll(this, st::mentionScroll) { _scroll->setGeometry(rect()); _inner = _scroll->setOwnedWidget(object_ptr<internal::FieldAutocompleteInner>(this, &_mrows, &_hrows, &_brows, &_srows)); _inner->setGeometry(rect()); connect(_inner, SIGNAL(mentionChosen(UserData*, FieldAutocomplete::ChooseMethod)), this, SIGNAL(mentionChosen(UserData*, FieldAutocomplete::ChooseMethod))); connect(_inner, SIGNAL(hashtagChosen(QString, FieldAutocomplete::ChooseMethod)), this, SIGNAL(hashtagChosen(QString, FieldAutocomplete::ChooseMethod))); connect(_inner, SIGNAL(botCommandChosen(QString, FieldAutocomplete::ChooseMethod)), this, SIGNAL(botCommandChosen(QString, FieldAutocomplete::ChooseMethod))); connect(_inner, SIGNAL(stickerChosen(DocumentData*, FieldAutocomplete::ChooseMethod)), this, SIGNAL(stickerChosen(DocumentData*, FieldAutocomplete::ChooseMethod))); connect(_inner, SIGNAL(mustScrollTo(int, int)), _scroll, SLOT(scrollToY(int, int))); _scroll->show(); _inner->show(); hide(); connect(_scroll, SIGNAL(geometryChanged()), _inner, SLOT(onParentGeometryChanged())); } void FieldAutocomplete::paintEvent(QPaintEvent *e) { Painter p(this); auto opacity = _a_opacity.current(getms(), _hiding ? 0. : 1.); if (opacity < 1.) { if (opacity > 0.) { p.setOpacity(opacity); p.drawPixmap(0, 0, _cache); } else if (_hiding) { } return; } p.fillRect(rect(), st::mentionBg); } void FieldAutocomplete::showFiltered(PeerData *peer, QString query, bool addInlineBots) { _chat = peer->asChat(); _user = peer->asUser(); _channel = peer->asChannel(); if (query.isEmpty()) { _type = Type::Mentions; rowsUpdated(internal::MentionRows(), internal::HashtagRows(), internal::BotCommandRows(), _srows, false); return; } _emoji = nullptr; query = query.toLower(); auto type = Type::Stickers; auto plainQuery = query.midRef(0); switch (query.at(0).unicode()) { case '@': type = Type::Mentions; plainQuery = query.midRef(1); break; case '#': type = Type::Hashtags; plainQuery = query.midRef(1); break; case '/': type = Type::BotCommands; plainQuery = query.midRef(1); break; } bool resetScroll = (_type != type || _filter != plainQuery); if (resetScroll) { _type = type; _filter = TextUtilities::RemoveAccents(plainQuery.toString()); } _addInlineBots = addInlineBots; updateFiltered(resetScroll); } void FieldAutocomplete::showStickers(EmojiPtr emoji) { bool resetScroll = (_emoji != emoji); _emoji = emoji; _type = Type::Stickers; if (!emoji) { rowsUpdated(_mrows, _hrows, _brows, internal::StickerRows(), false); return; } _chat = 0; _user = 0; _channel = 0; updateFiltered(resetScroll); } bool FieldAutocomplete::clearFilteredBotCommands() { if (_brows.isEmpty()) return false; _brows.clear(); return true; } namespace { template <typename T, typename U> inline int indexOfInFirstN(const T &v, const U &elem, int last) { for (auto b = v.cbegin(), i = b, e = b + qMax(v.size(), last); i != e; ++i) { if (*i == elem) { return (i - b); } } return -1; } } void FieldAutocomplete::updateFiltered(bool resetScroll) { int32 now = unixtime(), recentInlineBots = 0; internal::MentionRows mrows; internal::HashtagRows hrows; internal::BotCommandRows brows; internal::StickerRows srows; if (_emoji) { srows = Stickers::GetListByEmoji(_emoji, _stickersSeed); } else if (_type == Type::Mentions) { int maxListSize = _addInlineBots ? cRecentInlineBots().size() : 0; if (_chat) { maxListSize += (_chat->participants.empty() ? _chat->lastAuthors.size() : _chat->participants.size()); } else if (_channel && _channel->isMegagroup()) { if (_channel->mgInfo->lastParticipants.empty() || _channel->lastParticipantsCountOutdated()) { } else { maxListSize += _channel->mgInfo->lastParticipants.size(); } } if (maxListSize) { mrows.reserve(maxListSize); } auto filterNotPassedByUsername = [this](UserData *user) -> bool { if (user->username.startsWith(_filter, Qt::CaseInsensitive)) { bool exactUsername = (user->username.size() == _filter.size()); return exactUsername; } return true; }; auto filterNotPassedByName = [&](UserData *user) -> bool { for (const auto &nameWord : user->nameWords()) { if (nameWord.startsWith(_filter, Qt::CaseInsensitive)) { auto exactUsername = (user->username.compare(_filter, Qt::CaseInsensitive) == 0); return exactUsername; } } return filterNotPassedByUsername(user); }; bool listAllSuggestions = _filter.isEmpty(); if (_addInlineBots) { for_const (auto user, cRecentInlineBots()) { if (user->isInaccessible()) continue; if (!listAllSuggestions && filterNotPassedByUsername(user)) continue; mrows.push_back(user); ++recentInlineBots; } } if (_chat) { auto ordered = QMultiMap<TimeId, not_null<UserData*>>(); const auto byOnline = [&](not_null<UserData*> user) { return Data::SortByOnlineValue(user, now); }; mrows.reserve(mrows.size() + (_chat->participants.empty() ? _chat->lastAuthors.size() : _chat->participants.size())); if (_chat->noParticipantInfo()) { Auth().api().requestFullPeer(_chat); } else if (!_chat->participants.empty()) { for (const auto [user, v] : _chat->participants) { if (user->isInaccessible()) continue; if (!listAllSuggestions && filterNotPassedByName(user)) continue; if (indexOfInFirstN(mrows, user, recentInlineBots) >= 0) continue; ordered.insertMulti(byOnline(user), user); } } for (const auto user : _chat->lastAuthors) { if (user->isInaccessible()) continue; if (!listAllSuggestions && filterNotPassedByName(user)) continue; if (indexOfInFirstN(mrows, user, recentInlineBots) >= 0) continue; mrows.push_back(user); if (!ordered.isEmpty()) { ordered.remove(byOnline(user), user); } } if (!ordered.isEmpty()) { for (auto i = ordered.cend(), b = ordered.cbegin(); i != b;) { --i; mrows.push_back(i.value()); } } } else if (_channel && _channel->isMegagroup()) { QMultiMap<int32, UserData*> ordered; if (_channel->mgInfo->lastParticipants.empty() || _channel->lastParticipantsCountOutdated()) { Auth().api().requestLastParticipants(_channel); } else { mrows.reserve(mrows.size() + _channel->mgInfo->lastParticipants.size()); for (const auto user : _channel->mgInfo->lastParticipants) { if (user->isInaccessible()) continue; if (!listAllSuggestions && filterNotPassedByName(user)) continue; if (indexOfInFirstN(mrows, user, recentInlineBots) >= 0) continue; mrows.push_back(user); } } } } else if (_type == Type::Hashtags) { bool listAllSuggestions = _filter.isEmpty(); auto &recent(cRecentWriteHashtags()); hrows.reserve(recent.size()); for (auto i = recent.cbegin(), e = recent.cend(); i != e; ++i) { if (!listAllSuggestions && (!i->first.startsWith(_filter, Qt::CaseInsensitive) || i->first.size() == _filter.size())) { continue; } hrows.push_back(i->first); } } else if (_type == Type::BotCommands) { bool listAllSuggestions = _filter.isEmpty(); bool hasUsername = _filter.indexOf('@') > 0; QMap<UserData*, bool> bots; int32 cnt = 0; if (_chat) { if (_chat->noParticipantInfo()) { Auth().api().requestFullPeer(_chat); } else if (!_chat->participants.empty()) { for (const auto [user, version] : _chat->participants) { if (!user->botInfo) continue; if (!user->botInfo->inited) { Auth().api().requestFullPeer(user); } if (user->botInfo->commands.isEmpty()) continue; bots.insert(user, true); cnt += user->botInfo->commands.size(); } } } else if (_user && _user->botInfo) { if (!_user->botInfo->inited) { Auth().api().requestFullPeer(_user); } cnt = _user->botInfo->commands.size(); bots.insert(_user, true); } else if (_channel && _channel->isMegagroup()) { if (_channel->mgInfo->bots.empty()) { if (!_channel->mgInfo->botStatus) { Auth().api().requestBots(_channel); } } else { for_const (auto user, _channel->mgInfo->bots) { if (!user->botInfo) continue; if (!user->botInfo->inited) { Auth().api().requestFullPeer(user); } if (user->botInfo->commands.isEmpty()) continue; bots.insert(user, true); cnt += user->botInfo->commands.size(); } } } if (cnt) { brows.reserve(cnt); int32 botStatus = _chat ? _chat->botStatus : ((_channel && _channel->isMegagroup()) ? _channel->mgInfo->botStatus : -1); if (_chat) { for (auto i = _chat->lastAuthors.cbegin(), e = _chat->lastAuthors.cend(); i != e; ++i) { auto user = *i; if (!user->botInfo) continue; if (!bots.contains(user)) continue; if (!user->botInfo->inited) { Auth().api().requestFullPeer(user); } if (user->botInfo->commands.isEmpty()) continue; bots.remove(user); for (auto j = 0, l = user->botInfo->commands.size(); j != l; ++j) { if (!listAllSuggestions) { auto toFilter = (hasUsername || botStatus == 0 || botStatus == 2) ? user->botInfo->commands.at(j).command + '@' + user->username : user->botInfo->commands.at(j).command; if (!toFilter.startsWith(_filter, Qt::CaseInsensitive)/* || toFilter.size() == _filter.size()*/) { continue; } } brows.push_back(qMakePair(user, &user->botInfo->commands.at(j))); } } } if (!bots.isEmpty()) { for (QMap<UserData*, bool>::const_iterator i = bots.cbegin(), e = bots.cend(); i != e; ++i) { UserData *user = i.key(); for (int32 j = 0, l = user->botInfo->commands.size(); j < l; ++j) { if (!listAllSuggestions) { QString toFilter = (hasUsername || botStatus == 0 || botStatus == 2) ? user->botInfo->commands.at(j).command + '@' + user->username : user->botInfo->commands.at(j).command; if (!toFilter.startsWith(_filter, Qt::CaseInsensitive)/* || toFilter.size() == _filter.size()*/) continue; } brows.push_back(qMakePair(user, &user->botInfo->commands.at(j))); } } } } } rowsUpdated(mrows, hrows, brows, srows, resetScroll); _inner->setRecentInlineBotsInRows(recentInlineBots); } void FieldAutocomplete::rowsUpdated(const internal::MentionRows &mrows, const internal::HashtagRows &hrows, const internal::BotCommandRows &brows, const internal::StickerRows &srows, bool resetScroll) { if (mrows.isEmpty() && hrows.isEmpty() && brows.isEmpty() && srows.empty()) { if (!isHidden()) { hideAnimated(); } _scroll->scrollToY(0); _mrows.clear(); _hrows.clear(); _brows.clear(); _srows.clear(); } else { _mrows = mrows; _hrows = hrows; _brows = brows; _srows = srows; bool hidden = _hiding || isHidden(); if (hidden) { show(); _scroll->show(); } recount(resetScroll); update(); if (hidden) { hide(); showAnimated(); } } } void FieldAutocomplete::setBoundings(QRect boundings) { _boundings = boundings; recount(); } void FieldAutocomplete::recount(bool resetScroll) { int32 h = 0, oldst = _scroll->scrollTop(), st = oldst, maxh = 4.5 * st::mentionHeight; if (!_srows.empty()) { int32 stickersPerRow = qMax(1, int32(_boundings.width() - 2 * st::stickerPanPadding) / int32(st::stickerPanSize.width())); int32 rows = rowscount(_srows.size(), stickersPerRow); h = st::stickerPanPadding + rows * st::stickerPanSize.height(); } else if (!_mrows.isEmpty()) { h = _mrows.size() * st::mentionHeight; } else if (!_hrows.isEmpty()) { h = _hrows.size() * st::mentionHeight; } else if (!_brows.isEmpty()) { h = _brows.size() * st::mentionHeight; } if (_inner->width() != _boundings.width() || _inner->height() != h) { _inner->resize(_boundings.width(), h); } if (h > _boundings.height()) h = _boundings.height(); if (h > maxh) h = maxh; if (width() != _boundings.width() || height() != h) { setGeometry(_boundings.x(), _boundings.y() + _boundings.height() - h, _boundings.width(), h); _scroll->resize(_boundings.width(), h); } else if (y() != _boundings.y() + _boundings.height() - h) { move(_boundings.x(), _boundings.y() + _boundings.height() - h); } if (resetScroll) st = 0; if (st != oldst) _scroll->scrollToY(st); if (resetScroll) _inner->clearSel(); } void FieldAutocomplete::hideFast() { _a_opacity.finish(); hideFinish(); } void FieldAutocomplete::hideAnimated() { if (isHidden() || _hiding) { return; } if (_cache.isNull()) { _scroll->show(); _cache = Ui::GrabWidget(this); } _scroll->hide(); _hiding = true; _a_opacity.start([this] { animationCallback(); }, 1., 0., st::emojiPanDuration); setAttribute(Qt::WA_OpaquePaintEvent, false); } void FieldAutocomplete::hideFinish() { hide(); _hiding = false; _filter = qsl("-"); _inner->clearSel(true); } void FieldAutocomplete::showAnimated() { if (!isHidden() && !_hiding) { return; } if (_cache.isNull()) { _stickersSeed = rand_value<uint64>(); _scroll->show(); _cache = Ui::GrabWidget(this); } _scroll->hide(); _hiding = false; show(); _a_opacity.start([this] { animationCallback(); }, 0., 1., st::emojiPanDuration); setAttribute(Qt::WA_OpaquePaintEvent, false); } void FieldAutocomplete::animationCallback() { update(); if (!_a_opacity.animating()) { _cache = QPixmap(); setAttribute(Qt::WA_OpaquePaintEvent); if (_hiding) { hideFinish(); } else { _scroll->show(); _inner->clearSel(); } } } const QString &FieldAutocomplete::filter() const { return _filter; } ChatData *FieldAutocomplete::chat() const { return _chat; } ChannelData *FieldAutocomplete::channel() const { return _channel; } UserData *FieldAutocomplete::user() const { return _user; } int32 FieldAutocomplete::innerTop() { return _scroll->scrollTop(); } int32 FieldAutocomplete::innerBottom() { return _scroll->scrollTop() + _scroll->height(); } bool FieldAutocomplete::chooseSelected(ChooseMethod method) const { return _inner->chooseSelected(method); } bool FieldAutocomplete::eventFilter(QObject *obj, QEvent *e) { auto hidden = isHidden(); auto moderate = Global::ModerateModeEnabled(); if (hidden && !moderate) return QWidget::eventFilter(obj, e); if (e->type() == QEvent::KeyPress) { QKeyEvent *ev = static_cast<QKeyEvent*>(e); if (!(ev->modifiers() & (Qt::AltModifier | Qt::ControlModifier | Qt::ShiftModifier | Qt::MetaModifier))) { if (!hidden) { if (ev->key() == Qt::Key_Up || ev->key() == Qt::Key_Down || (!_srows.empty() && (ev->key() == Qt::Key_Left || ev->key() == Qt::Key_Right))) { return _inner->moveSel(ev->key()); } else if (ev->key() == Qt::Key_Enter || ev->key() == Qt::Key_Return) { return _inner->chooseSelected(ChooseMethod::ByEnter); } } if (moderate && ((ev->key() >= Qt::Key_1 && ev->key() <= Qt::Key_9) || ev->key() == Qt::Key_Q)) { bool handled = false; emit moderateKeyActivate(ev->key(), &handled); return handled; } } } return QWidget::eventFilter(obj, e); } FieldAutocomplete::~FieldAutocomplete() { } namespace internal { FieldAutocompleteInner::FieldAutocompleteInner(FieldAutocomplete *parent, MentionRows *mrows, HashtagRows *hrows, BotCommandRows *brows, StickerRows *srows) : _parent(parent) , _mrows(mrows) , _hrows(hrows) , _brows(brows) , _srows(srows) , _stickersPerRow(1) , _recentInlineBotsInRows(0) , _sel(-1) , _down(-1) , _mouseSel(false) , _overDelete(false) , _previewShown(false) { _previewTimer.setSingleShot(true); connect(&_previewTimer, SIGNAL(timeout()), this, SLOT(onPreview())); subscribe(Auth().downloaderTaskFinished(), [this] { update(); }); } void FieldAutocompleteInner::paintEvent(QPaintEvent *e) { Painter p(this); QRect r(e->rect()); if (r != rect()) p.setClipRect(r); int32 atwidth = st::mentionFont->width('@'), hashwidth = st::mentionFont->width('#'); int32 mentionleft = 2 * st::mentionPadding.left() + st::mentionPhotoSize; int32 mentionwidth = width() - mentionleft - 2 * st::mentionPadding.right(); int32 htagleft = st::historyAttach.width + st::historyComposeField.textMrg.left() - st::lineWidth, htagwidth = width() - st::mentionPadding.right() - htagleft - st::mentionScroll.width; if (!_srows->empty()) { int32 rows = rowscount(_srows->size(), _stickersPerRow); int32 fromrow = floorclamp(r.y() - st::stickerPanPadding, st::stickerPanSize.height(), 0, rows); int32 torow = ceilclamp(r.y() + r.height() - st::stickerPanPadding, st::stickerPanSize.height(), 0, rows); int32 fromcol = floorclamp(r.x() - st::stickerPanPadding, st::stickerPanSize.width(), 0, _stickersPerRow); int32 tocol = ceilclamp(r.x() + r.width() - st::stickerPanPadding, st::stickerPanSize.width(), 0, _stickersPerRow); for (int32 row = fromrow; row < torow; ++row) { for (int32 col = fromcol; col < tocol; ++col) { int32 index = row * _stickersPerRow + col; if (index >= _srows->size()) break; DocumentData *sticker = _srows->at(index); if (!sticker->sticker()) continue; QPoint pos(st::stickerPanPadding + col * st::stickerPanSize.width(), st::stickerPanPadding + row * st::stickerPanSize.height()); if (_sel == index) { QPoint tl(pos); if (rtl()) tl.setX(width() - tl.x() - st::stickerPanSize.width()); App::roundRect(p, QRect(tl, st::stickerPanSize), st::emojiPanHover, StickerHoverCorners); } bool goodThumb = !sticker->thumb->isNull() && ((sticker->thumb->width() >= 128) || (sticker->thumb->height() >= 128)); if (goodThumb) { sticker->thumb->load(); } else { sticker->checkSticker(); } float64 coef = qMin((st::stickerPanSize.width() - st::buttonRadius * 2) / float64(sticker->dimensions.width()), (st::stickerPanSize.height() - st::buttonRadius * 2) / float64(sticker->dimensions.height())); if (coef > 1) coef = 1; int32 w = qRound(coef * sticker->dimensions.width()), h = qRound(coef * sticker->dimensions.height()); if (w < 1) w = 1; if (h < 1) h = 1; QPoint ppos = pos + QPoint((st::stickerPanSize.width() - w) / 2, (st::stickerPanSize.height() - h) / 2); if (goodThumb) { p.drawPixmapLeft(ppos, width(), sticker->thumb->pix(w, h)); } else if (!sticker->sticker()->img->isNull()) { p.drawPixmapLeft(ppos, width(), sticker->sticker()->img->pix(w, h)); } } } } else { int32 from = qFloor(e->rect().top() / st::mentionHeight), to = qFloor(e->rect().bottom() / st::mentionHeight) + 1; int32 last = _mrows->isEmpty() ? (_hrows->isEmpty() ? _brows->size() : _hrows->size()) : _mrows->size(); auto filter = _parent->filter(); bool hasUsername = filter.indexOf('@') > 0; int filterSize = filter.size(); bool filterIsEmpty = filter.isEmpty(); for (int32 i = from; i < to; ++i) { if (i >= last) break; bool selected = (i == _sel); if (selected) { p.fillRect(0, i * st::mentionHeight, width(), st::mentionHeight, st::mentionBgOver); int skip = (st::mentionHeight - st::smallCloseIconOver.height()) / 2; if (!_hrows->isEmpty() || (!_mrows->isEmpty() && i < _recentInlineBotsInRows)) { st::smallCloseIconOver.paint(p, QPoint(width() - st::smallCloseIconOver.width() - skip, i * st::mentionHeight + skip), width()); } } if (!_mrows->isEmpty()) { UserData *user = _mrows->at(i); QString first = (!filterIsEmpty && user->username.startsWith(filter, Qt::CaseInsensitive)) ? ('@' + user->username.mid(0, filterSize)) : QString(); QString second = first.isEmpty() ? (user->username.isEmpty() ? QString() : ('@' + user->username)) : user->username.mid(filterSize); int32 firstwidth = st::mentionFont->width(first), secondwidth = st::mentionFont->width(second), unamewidth = firstwidth + secondwidth, namewidth = user->nameText.maxWidth(); if (mentionwidth < unamewidth + namewidth) { namewidth = (mentionwidth * namewidth) / (namewidth + unamewidth); unamewidth = mentionwidth - namewidth; if (firstwidth < unamewidth + st::mentionFont->elidew) { if (firstwidth < unamewidth) { first = st::mentionFont->elided(first, unamewidth); } else if (!second.isEmpty()) { first = st::mentionFont->elided(first + second, unamewidth); second = QString(); } } else { second = st::mentionFont->elided(second, unamewidth - firstwidth); } } user->loadUserpic(); user->paintUserpicLeft(p, st::mentionPadding.left(), i * st::mentionHeight + st::mentionPadding.top(), width(), st::mentionPhotoSize); p.setPen(selected ? st::mentionNameFgOver : st::mentionNameFg); user->nameText.drawElided(p, 2 * st::mentionPadding.left() + st::mentionPhotoSize, i * st::mentionHeight + st::mentionTop, namewidth); p.setFont(st::mentionFont); p.setPen(selected ? st::mentionFgOverActive : st::mentionFgActive); p.drawText(mentionleft + namewidth + st::mentionPadding.right(), i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, first); if (!second.isEmpty()) { p.setPen(selected ? st::mentionFgOver : st::mentionFg); p.drawText(mentionleft + namewidth + st::mentionPadding.right() + firstwidth, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, second); } } else if (!_hrows->isEmpty()) { QString hrow = _hrows->at(i); QString first = filterIsEmpty ? QString() : ('#' + hrow.mid(0, filterSize)); QString second = filterIsEmpty ? ('#' + hrow) : hrow.mid(filterSize); int32 firstwidth = st::mentionFont->width(first), secondwidth = st::mentionFont->width(second); if (htagwidth < firstwidth + secondwidth) { if (htagwidth < firstwidth + st::mentionFont->elidew) { first = st::mentionFont->elided(first + second, htagwidth); second = QString(); } else { second = st::mentionFont->elided(second, htagwidth - firstwidth); } } p.setFont(st::mentionFont); if (!first.isEmpty()) { p.setPen((selected ? st::mentionFgOverActive : st::mentionFgActive)->p); p.drawText(htagleft, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, first); } if (!second.isEmpty()) { p.setPen((selected ? st::mentionFgOver : st::mentionFg)->p); p.drawText(htagleft + firstwidth, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, second); } } else { UserData *user = _brows->at(i).first; const BotCommand *command = _brows->at(i).second; QString toHighlight = command->command; int32 botStatus = _parent->chat() ? _parent->chat()->botStatus : ((_parent->channel() && _parent->channel()->isMegagroup()) ? _parent->channel()->mgInfo->botStatus : -1); if (hasUsername || botStatus == 0 || botStatus == 2) { toHighlight += '@' + user->username; } user->loadUserpic(); user->paintUserpicLeft(p, st::mentionPadding.left(), i * st::mentionHeight + st::mentionPadding.top(), width(), st::mentionPhotoSize); auto commandText = '/' + toHighlight; p.setPen(selected ? st::mentionNameFgOver : st::mentionNameFg); p.setFont(st::semiboldFont); p.drawText(2 * st::mentionPadding.left() + st::mentionPhotoSize, i * st::mentionHeight + st::mentionTop + st::semiboldFont->ascent, commandText); auto commandTextWidth = st::semiboldFont->width(commandText); auto addleft = commandTextWidth + st::mentionPadding.left(); auto widthleft = mentionwidth - addleft; if (widthleft > st::mentionFont->elidew && !command->descriptionText().isEmpty()) { p.setPen((selected ? st::mentionFgOver : st::mentionFg)->p); command->descriptionText().drawElided(p, mentionleft + addleft, i * st::mentionHeight + st::mentionTop, widthleft); } } } p.fillRect(Adaptive::OneColumn() ? 0 : st::lineWidth, _parent->innerBottom() - st::lineWidth, width() - (Adaptive::OneColumn() ? 0 : st::lineWidth), st::lineWidth, st::shadowFg); } p.fillRect(Adaptive::OneColumn() ? 0 : st::lineWidth, _parent->innerTop(), width() - (Adaptive::OneColumn() ? 0 : st::lineWidth), st::lineWidth, st::shadowFg); } void FieldAutocompleteInner::resizeEvent(QResizeEvent *e) { _stickersPerRow = qMax(1, int32(width() - 2 * st::stickerPanPadding) / int32(st::stickerPanSize.width())); } void FieldAutocompleteInner::mouseMoveEvent(QMouseEvent *e) { _mousePos = mapToGlobal(e->pos()); _mouseSel = true; onUpdateSelected(true); } void FieldAutocompleteInner::clearSel(bool hidden) { _mouseSel = _overDelete = false; setSel((_mrows->isEmpty() && _brows->isEmpty() && _hrows->isEmpty()) ? -1 : 0); if (hidden) { _down = -1; _previewShown = false; } } bool FieldAutocompleteInner::moveSel(int key) { _mouseSel = false; int32 maxSel = (_mrows->isEmpty() ? (_hrows->isEmpty() ? (_brows->isEmpty() ? _srows->size() : _brows->size()) : _hrows->size()) : _mrows->size()); int32 direction = (key == Qt::Key_Up) ? -1 : (key == Qt::Key_Down ? 1 : 0); if (!_srows->empty()) { if (key == Qt::Key_Left) { direction = -1; } else if (key == Qt::Key_Right) { direction = 1; } else { direction *= _stickersPerRow; } } if (_sel >= maxSel || _sel < 0) { if (direction < -1) { setSel(((maxSel - 1) / _stickersPerRow) * _stickersPerRow, true); } else if (direction < 0) { setSel(maxSel - 1, true); } else { setSel(0, true); } return (_sel >= 0 && _sel < maxSel); } setSel((_sel + direction >= maxSel || _sel + direction < 0) ? -1 : (_sel + direction), true); return true; } bool FieldAutocompleteInner::chooseSelected(FieldAutocomplete::ChooseMethod method) const { if (!_srows->empty()) { if (_sel >= 0 && _sel < _srows->size()) { emit stickerChosen(_srows->at(_sel), method); return true; } } else if (!_mrows->isEmpty()) { if (_sel >= 0 && _sel < _mrows->size()) { emit mentionChosen(_mrows->at(_sel), method); return true; } } else if (!_hrows->isEmpty()) { if (_sel >= 0 && _sel < _hrows->size()) { emit hashtagChosen('#' + _hrows->at(_sel), method); return true; } } else if (!_brows->isEmpty()) { if (_sel >= 0 && _sel < _brows->size()) { UserData *user = _brows->at(_sel).first; const BotCommand *command(_brows->at(_sel).second); int32 botStatus = _parent->chat() ? _parent->chat()->botStatus : ((_parent->channel() && _parent->channel()->isMegagroup()) ? _parent->channel()->mgInfo->botStatus : -1); if (botStatus == 0 || botStatus == 2 || _parent->filter().indexOf('@') > 0) { emit botCommandChosen('/' + command->command + '@' + user->username, method); } else { emit botCommandChosen('/' + command->command, method); } return true; } } return false; } void FieldAutocompleteInner::setRecentInlineBotsInRows(int32 bots) { _recentInlineBotsInRows = bots; } void FieldAutocompleteInner::mousePressEvent(QMouseEvent *e) { _mousePos = mapToGlobal(e->pos()); _mouseSel = true; onUpdateSelected(true); if (e->button() == Qt::LeftButton) { if (_overDelete && _sel >= 0 && _sel < (_mrows->isEmpty() ? _hrows->size() : _recentInlineBotsInRows)) { _mousePos = mapToGlobal(e->pos()); bool removed = false; if (_mrows->isEmpty()) { QString toRemove = _hrows->at(_sel); RecentHashtagPack &recent(cRefRecentWriteHashtags()); for (RecentHashtagPack::iterator i = recent.begin(); i != recent.cend();) { if (i->first == toRemove) { i = recent.erase(i); removed = true; } else { ++i; } } } else { UserData *toRemove = _mrows->at(_sel); RecentInlineBots &recent(cRefRecentInlineBots()); int32 index = recent.indexOf(toRemove); if (index >= 0) { recent.remove(index); removed = true; } } if (removed) { Local::writeRecentHashtagsAndBots(); } _parent->updateFiltered(); _mouseSel = true; onUpdateSelected(true); } else if (_srows->empty()) { chooseSelected(FieldAutocomplete::ChooseMethod::ByClick); } else { _down = _sel; _previewTimer.start(QApplication::startDragTime()); } } } void FieldAutocompleteInner::mouseReleaseEvent(QMouseEvent *e) { _previewTimer.stop(); int32 pressed = _down; _down = -1; _mousePos = mapToGlobal(e->pos()); _mouseSel = true; onUpdateSelected(true); if (_previewShown) { _previewShown = false; return; } if (_sel < 0 || _sel != pressed || _srows->empty()) return; chooseSelected(FieldAutocomplete::ChooseMethod::ByClick); } void FieldAutocompleteInner::enterEventHook(QEvent *e) { setMouseTracking(true); _mousePos = QCursor::pos(); onUpdateSelected(true); } void FieldAutocompleteInner::leaveEventHook(QEvent *e) { setMouseTracking(false); if (_sel >= 0) { setSel(-1); } } void FieldAutocompleteInner::updateSelectedRow() { if (_sel >= 0) { if (_srows->empty()) { update(0, _sel * st::mentionHeight, width(), st::mentionHeight); } else { int32 row = _sel / _stickersPerRow, col = _sel % _stickersPerRow; update(st::stickerPanPadding + col * st::stickerPanSize.width(), st::stickerPanPadding + row * st::stickerPanSize.height(), st::stickerPanSize.width(), st::stickerPanSize.height()); } } } void FieldAutocompleteInner::setSel(int sel, bool scroll) { updateSelectedRow(); _sel = sel; updateSelectedRow(); if (scroll && _sel >= 0) { if (_srows->empty()) { emit mustScrollTo(_sel * st::mentionHeight, (_sel + 1) * st::mentionHeight); } else { int32 row = _sel / _stickersPerRow; emit mustScrollTo(st::stickerPanPadding + row * st::stickerPanSize.height(), st::stickerPanPadding + (row + 1) * st::stickerPanSize.height()); } } } void FieldAutocompleteInner::onUpdateSelected(bool force) { QPoint mouse(mapFromGlobal(_mousePos)); if ((!force && !rect().contains(mouse)) || !_mouseSel) return; if (_down >= 0 && !_previewShown) return; int32 sel = -1, maxSel = 0; if (!_srows->empty()) { int32 rows = rowscount(_srows->size(), _stickersPerRow); int32 row = (mouse.y() >= st::stickerPanPadding) ? ((mouse.y() - st::stickerPanPadding) / st::stickerPanSize.height()) : -1; int32 col = (mouse.x() >= st::stickerPanPadding) ? ((mouse.x() - st::stickerPanPadding) / st::stickerPanSize.width()) : -1; if (row >= 0 && col >= 0) { sel = row * _stickersPerRow + col; } maxSel = _srows->size(); _overDelete = false; } else { sel = mouse.y() / int32(st::mentionHeight); maxSel = _mrows->isEmpty() ? (_hrows->isEmpty() ? _brows->size() : _hrows->size()) : _mrows->size(); _overDelete = (!_hrows->isEmpty() || (!_mrows->isEmpty() && sel < _recentInlineBotsInRows)) ? (mouse.x() >= width() - st::mentionHeight) : false; } if (sel < 0 || sel >= maxSel) { sel = -1; } if (sel != _sel) { setSel(sel); if (_down >= 0 && _sel >= 0 && _down != _sel) { _down = _sel; if (_down >= 0 && _down < _srows->size()) { Ui::showMediaPreview(_srows->at(_down)); } } } } void FieldAutocompleteInner::onParentGeometryChanged() { _mousePos = QCursor::pos(); if (rect().contains(mapFromGlobal(_mousePos))) { setMouseTracking(true); onUpdateSelected(true); } } void FieldAutocompleteInner::onPreview() { if (_down >= 0 && _down < _srows->size()) { Ui::showMediaPreview(_srows->at(_down)); _previewShown = true; } } } // namespace internal ��������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/chat_helpers/field_autocomplete.h������������������������������0000664�0000000�0000000�00000011231�13262451251�0026077�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/twidget.h" #include "chat_helpers/stickers.h" namespace Ui { class ScrollArea; } // namespace Ui namespace internal { using MentionRows = QList<UserData*>; using HashtagRows = QList<QString>; using BotCommandRows = QList<QPair<UserData*, const BotCommand*>>; using StickerRows = std::vector<not_null<DocumentData*>>; class FieldAutocompleteInner; } // namespace internal class FieldAutocomplete final : public TWidget { Q_OBJECT public: FieldAutocomplete(QWidget *parent); bool clearFilteredBotCommands(); void showFiltered(PeerData *peer, QString query, bool addInlineBots); void showStickers(EmojiPtr emoji); void setBoundings(QRect boundings); const QString &filter() const; ChatData *chat() const; ChannelData *channel() const; UserData *user() const; int32 innerTop(); int32 innerBottom(); bool eventFilter(QObject *obj, QEvent *e) override; enum class ChooseMethod { ByEnter, ByTab, ByClick, }; bool chooseSelected(ChooseMethod method) const; bool stickersShown() const { return !_srows.empty(); } bool overlaps(const QRect &globalRect) { if (isHidden() || !testAttribute(Qt::WA_OpaquePaintEvent)) return false; return rect().contains(QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size())); } void hideFast(); ~FieldAutocomplete(); signals: void mentionChosen(UserData *user, FieldAutocomplete::ChooseMethod method) const; void hashtagChosen(QString hashtag, FieldAutocomplete::ChooseMethod method) const; void botCommandChosen(QString command, FieldAutocomplete::ChooseMethod method) const; void stickerChosen(DocumentData *sticker, FieldAutocomplete::ChooseMethod method) const; void moderateKeyActivate(int key, bool *outHandled) const; public slots: void showAnimated(); void hideAnimated(); protected: void paintEvent(QPaintEvent *e) override; private: void animationCallback(); void hideFinish(); void updateFiltered(bool resetScroll = false); void recount(bool resetScroll = false); QPixmap _cache; internal::MentionRows _mrows; internal::HashtagRows _hrows; internal::BotCommandRows _brows; internal::StickerRows _srows; void rowsUpdated(const internal::MentionRows &mrows, const internal::HashtagRows &hrows, const internal::BotCommandRows &brows, const internal::StickerRows &srows, bool resetScroll); object_ptr<Ui::ScrollArea> _scroll; QPointer<internal::FieldAutocompleteInner> _inner; ChatData *_chat = nullptr; UserData *_user = nullptr; ChannelData *_channel = nullptr; EmojiPtr _emoji; uint64 _stickersSeed = 0; enum class Type { Mentions, Hashtags, BotCommands, Stickers, }; Type _type = Type::Mentions; QString _filter; QRect _boundings; bool _addInlineBots; int32 _width, _height; bool _hiding = false; Animation _a_opacity; friend class internal::FieldAutocompleteInner; }; namespace internal { class FieldAutocompleteInner final : public TWidget, private base::Subscriber { Q_OBJECT public: FieldAutocompleteInner(FieldAutocomplete *parent, MentionRows *mrows, HashtagRows *hrows, BotCommandRows *brows, StickerRows *srows); void clearSel(bool hidden = false); bool moveSel(int key); bool chooseSelected(FieldAutocomplete::ChooseMethod method) const; void setRecentInlineBotsInRows(int32 bots); signals: void mentionChosen(UserData *user, FieldAutocomplete::ChooseMethod method) const; void hashtagChosen(QString hashtag, FieldAutocomplete::ChooseMethod method) const; void botCommandChosen(QString command, FieldAutocomplete::ChooseMethod method) const; void stickerChosen(DocumentData *sticker, FieldAutocomplete::ChooseMethod method) const; void mustScrollTo(int scrollToTop, int scrollToBottom); public slots: void onParentGeometryChanged(); void onUpdateSelected(bool force = false); void onPreview(); private: void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; void enterEventHook(QEvent *e) override; void leaveEventHook(QEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void updateSelectedRow(); void setSel(int sel, bool scroll = false); FieldAutocomplete *_parent; MentionRows *_mrows; HashtagRows *_hrows; BotCommandRows *_brows; StickerRows *_srows; int32 _stickersPerRow, _recentInlineBotsInRows; int32 _sel, _down; bool _mouseSel; QPoint _mousePos; bool _overDelete; bool _previewShown; QTimer _previewTimer; }; } // namespace internal �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp������������������������������0000664�0000000�0000000�00000063534�13262451251�0026131�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "chat_helpers/gifs_list_widget.h" #include "data/data_photo.h" #include "data/data_document.h" #include "data/data_session.h" #include "styles/style_chat_helpers.h" #include "ui/widgets/buttons.h" #include "ui/widgets/input_fields.h" #include "ui/effects/ripple_animation.h" #include "boxes/stickers_box.h" #include "inline_bots/inline_bot_result.h" #include "chat_helpers/stickers.h" #include "storage/localstorage.h" #include "lang/lang_keys.h" #include "mainwindow.h" #include "window/window_controller.h" #include "history/view/history_view_cursor_state.h" namespace ChatHelpers { namespace { constexpr auto kSaveChosenTabTimeout = 1000; constexpr auto kSearchRequestDelay = 400; constexpr auto kInlineItemsMaxPerRow = 5; constexpr auto kSearchBotUsername = str_const("gif"); } // namespace class GifsListWidget::Footer : public TabbedSelector::InnerFooter { public: Footer(not_null<GifsListWidget*> parent); void stealFocus(); void returnFocus(); void setLoading(bool loading) { _cancel->setLoadingAnimation(loading); } protected: void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; void processPanelHideFinished() override; private: not_null<GifsListWidget*> _pan; object_ptr<Ui::InputField> _field; object_ptr<Ui::CrossButton> _cancel; QPointer<QWidget> _focusTakenFrom; }; GifsListWidget::Footer::Footer(not_null<GifsListWidget*> parent) : InnerFooter(parent) , _pan(parent) , _field(this, st::gifsSearchField, langFactory(lng_gifs_search)) , _cancel(this, st::gifsSearchCancel) { connect(_field, &Ui::InputField::submitted, this, [this](bool ctrlShiftEnter) { _pan->sendInlineRequest(); }); connect(_field, &Ui::InputField::cancelled, this, [this] { if (_field->getLastText().isEmpty()) { emit _pan->cancelled(); } else { _field->setText(QString()); } }); connect(_field, &Ui::InputField::changed, this, [this] { _cancel->toggle( !_field->getLastText().isEmpty(), anim::type::normal); _pan->searchForGifs(_field->getLastText()); }); _cancel->setClickedCallback([this] { _field->setText(QString()); }); } void GifsListWidget::Footer::stealFocus() { if (!_focusTakenFrom) { _focusTakenFrom = QApplication::focusWidget(); } _field->setFocus(); } void GifsListWidget::Footer::returnFocus() { if (_focusTakenFrom) { if (_field->hasFocus()) { _focusTakenFrom->setFocus(); } _focusTakenFrom = nullptr; } } void GifsListWidget::Footer::paintEvent(QPaintEvent *e) { Painter p(this); st::gifsSearchIcon.paint(p, st::gifsSearchIconPosition.x(), st::gifsSearchIconPosition.y(), width()); } void GifsListWidget::Footer::resizeEvent(QResizeEvent *e) { auto fieldWidth = width() - st::gifsSearchFieldPosition.x() - st::gifsSearchCancelPosition.x() - st::gifsSearchCancel.width; _field->resizeToWidth(fieldWidth); _field->moveToLeft(st::gifsSearchFieldPosition.x(), st::gifsSearchFieldPosition.y()); _cancel->moveToRight(st::gifsSearchCancelPosition.x(), st::gifsSearchCancelPosition.y()); } void GifsListWidget::Footer::processPanelHideFinished() { // Preserve panel state through visibility toggles. //_field->setText(QString()); } GifsListWidget::GifsListWidget( QWidget *parent, not_null<Window::Controller*> controller) : Inner(parent, controller) , _section(Section::Gifs) { setMouseTracking(true); setAttribute(Qt::WA_OpaquePaintEvent); _previewTimer.setSingleShot(true); connect(&_previewTimer, SIGNAL(timeout()), this, SLOT(onPreview())); _updateInlineItems.setSingleShot(true); connect(&_updateInlineItems, SIGNAL(timeout()), this, SLOT(onUpdateInlineItems())); _inlineRequestTimer.setSingleShot(true); connect(&_inlineRequestTimer, &QTimer::timeout, this, [this] { sendInlineRequest(); }); Auth().data().savedGifsUpdated( ) | rpl::start_with_next([this] { refreshSavedGifs(); }, lifetime()); subscribe(Auth().downloaderTaskFinished(), [this] { update(); }); subscribe(controller->gifPauseLevelChanged(), [this] { if (!this->controller()->isGifPausedAtLeastFor(Window::GifPauseReason::SavedGifs)) { update(); } }); } object_ptr<TabbedSelector::InnerFooter> GifsListWidget::createFooter() { Expects(_footer == nullptr); auto result = object_ptr<Footer>(this); _footer = result; return std::move(result); } void GifsListWidget::visibleTopBottomUpdated( int visibleTop, int visibleBottom) { auto top = getVisibleTop(); Inner::visibleTopBottomUpdated(visibleTop, visibleBottom); if (top != getVisibleTop()) { _lastScrolled = getms(); } checkLoadMore(); } void GifsListWidget::checkLoadMore() { auto visibleHeight = (getVisibleBottom() - getVisibleTop()); if (getVisibleBottom() + visibleHeight > height()) { sendInlineRequest(); } } int GifsListWidget::countDesiredHeight(int newWidth) { auto result = st::stickerPanPadding; for (int i = 0, l = _rows.count(); i < l; ++i) { layoutInlineRow(_rows[i], newWidth); result += _rows[i].height; } return result + st::stickerPanPadding; } GifsListWidget::~GifsListWidget() { clearInlineRows(true); deleteUnusedGifLayouts(); deleteUnusedInlineLayouts(); } void GifsListWidget::cancelGifsSearch() { _footer->setLoading(false); if (_inlineRequestId) { request(_inlineRequestId).cancel(); _inlineRequestId = 0; } _inlineRequestTimer.stop(); _inlineQuery = _inlineNextQuery = _inlineNextOffset = QString(); _inlineCache.clear(); refreshInlineRows(nullptr, true); } void GifsListWidget::inlineResultsDone(const MTPmessages_BotResults &result) { _footer->setLoading(false); _inlineRequestId = 0; auto it = _inlineCache.find(_inlineQuery); auto adding = (it != _inlineCache.cend()); if (result.type() == mtpc_messages_botResults) { auto &d = result.c_messages_botResults(); App::feedUsers(d.vusers); auto &v = d.vresults.v; auto queryId = d.vquery_id.v; if (it == _inlineCache.cend()) { it = _inlineCache.emplace(_inlineQuery, std::make_unique<InlineCacheEntry>()).first; } auto entry = it->second.get(); entry->nextOffset = qs(d.vnext_offset); if (auto count = v.size()) { entry->results.reserve(entry->results.size() + count); } auto added = 0; for_const (const auto &res, v) { if (auto result = InlineBots::Result::create(queryId, res)) { ++added; entry->results.push_back(std::move(result)); } } if (!added) { entry->nextOffset = QString(); } } else if (adding) { it->second->nextOffset = QString(); } if (!showInlineRows(!adding)) { it->second->nextOffset = QString(); } checkLoadMore(); } void GifsListWidget::paintEvent(QPaintEvent *e) { Painter p(this); auto clip = e->rect(); p.fillRect(clip, st::emojiPanBg); paintInlineItems(p, clip); } void GifsListWidget::paintInlineItems(Painter &p, QRect clip) { if (_rows.isEmpty()) { p.setFont(st::normalFont); p.setPen(st::noContactsColor); auto text = lang(_inlineQuery.isEmpty() ? lng_gifs_no_saved : lng_inline_bot_no_results); p.drawText(QRect(0, 0, width(), (height() / 3) * 2 + st::normalFont->height), text, style::al_center); return; } auto gifPaused = controller()->isGifPausedAtLeastFor(Window::GifPauseReason::SavedGifs); InlineBots::Layout::PaintContext context(getms(), false, gifPaused, false); auto top = st::stickerPanPadding; auto fromx = rtl() ? (width() - clip.x() - clip.width()) : clip.x(); auto tox = rtl() ? (width() - clip.x()) : (clip.x() + clip.width()); for (auto row = 0, rows = _rows.size(); row != rows; ++row) { auto &inlineRow = _rows[row]; if (top >= clip.top() + clip.height()) { break; } if (top + inlineRow.height > clip.top()) { auto left = st::inlineResultsLeft - st::buttonRadius; if (row == rows - 1) context.lastRow = true; for (int col = 0, cols = inlineRow.items.size(); col < cols; ++col) { if (left >= tox) break; auto item = inlineRow.items.at(col); auto w = item->width(); if (left + w > fromx) { p.translate(left, top); item->paint(p, clip.translated(-left, -top), &context); p.translate(-left, -top); } left += w; if (item->hasRightSkip()) { left += st::inlineResultsSkip; } } } top += inlineRow.height; } } void GifsListWidget::mousePressEvent(QMouseEvent *e) { if (e->button() != Qt::LeftButton) { return; } _lastMousePos = e->globalPos(); updateSelected(); _pressed = _selected; ClickHandler::pressed(); _previewTimer.start(QApplication::startDragTime()); } void GifsListWidget::mouseReleaseEvent(QMouseEvent *e) { _previewTimer.stop(); auto pressed = std::exchange(_pressed, -1); auto activated = ClickHandler::unpressed(); if (_previewShown) { _previewShown = false; return; } _lastMousePos = e->globalPos(); updateSelected(); if (_selected < 0 || _selected != pressed || !activated) { return; } if (dynamic_cast<InlineBots::Layout::SendClickHandler*>(activated.get())) { int row = _selected / MatrixRowShift, column = _selected % MatrixRowShift; selectInlineResult(row, column); } else { App::activateClickHandler(activated, e->button()); } } void GifsListWidget::selectInlineResult(int row, int column) { if (row >= _rows.size() || column >= _rows[row].items.size()) { return; } auto item = _rows[row].items[column]; if (auto photo = item->getPhoto()) { if (photo->medium->loaded() || photo->thumb->loaded()) { emit selected(photo); } else if (!photo->medium->loading()) { photo->thumb->loadEvenCancelled(); photo->medium->loadEvenCancelled(); } } else if (auto document = item->getDocument()) { if (document->loaded()) { emit selected(document); } else if (document->loading()) { document->cancel(); } else { DocumentOpenClickHandler::doOpen(document, nullptr, ActionOnLoadNone); } } else if (auto inlineResult = item->getResult()) { if (inlineResult->onChoose(item)) { emit selected(inlineResult, _searchBot); } } } void GifsListWidget::mouseMoveEvent(QMouseEvent *e) { _lastMousePos = e->globalPos(); updateSelected(); } void GifsListWidget::leaveEventHook(QEvent *e) { clearSelection(); } void GifsListWidget::leaveToChildEvent(QEvent *e, QWidget *child) { clearSelection(); } void GifsListWidget::enterFromChildEvent(QEvent *e, QWidget *child) { _lastMousePos = QCursor::pos(); updateSelected(); } void GifsListWidget::clearSelection() { if (_selected >= 0) { int srow = _selected / MatrixRowShift, scol = _selected % MatrixRowShift; Assert(srow >= 0 && srow < _rows.size() && scol >= 0 && scol < _rows[srow].items.size()); ClickHandler::clearActive(_rows[srow].items[scol]); setCursor(style::cur_default); } _selected = _pressed = -1; update(); } TabbedSelector::InnerFooter *GifsListWidget::getFooter() const { return _footer; } void GifsListWidget::processHideFinished() { clearSelection(); } void GifsListWidget::processPanelHideFinished() { auto itemForget = [](auto &item) { if (auto document = item->getDocument()) { document->forget(); } if (auto photo = item->getPhoto()) { photo->forget(); } if (auto result = item->getResult()) { result->forget(); } }; // Preserve panel state through visibility toggles. //clearInlineRows(false); for_const (auto &item, _gifLayouts) { itemForget(item.second); } for_const (auto &item, _inlineLayouts) { itemForget(item.second); } } bool GifsListWidget::inlineRowsAddItem(DocumentData *savedGif, InlineResult *result, Row &row, int32 &sumWidth) { LayoutItem *layout = nullptr; if (savedGif) { layout = layoutPrepareSavedGif(savedGif, (_rows.size() * MatrixRowShift) + row.items.size()); } else if (result) { layout = layoutPrepareInlineResult(result, (_rows.size() * MatrixRowShift) + row.items.size()); } if (!layout) return false; layout->preload(); if (inlineRowFinalize(row, sumWidth, layout->isFullLine())) { layout->setPosition(_rows.size() * MatrixRowShift); } sumWidth += layout->maxWidth(); if (!row.items.isEmpty() && row.items.back()->hasRightSkip()) { sumWidth += st::inlineResultsSkip; } row.items.push_back(layout); return true; } bool GifsListWidget::inlineRowFinalize(Row &row, int32 &sumWidth, bool force) { if (row.items.isEmpty()) return false; auto full = (row.items.size() >= kInlineItemsMaxPerRow); // Currently use the same GIFs layout for all widget sizes. // auto big = (sumWidth >= st::buttonRadius + width() - st::inlineResultsLeft); auto big = (sumWidth >= st::emojiPanWidth - st::inlineResultsLeft); if (full || big || force) { row.maxWidth = (full || big) ? sumWidth : 0; layoutInlineRow( row, width()); _rows.push_back(row); row = Row(); row.items.reserve(kInlineItemsMaxPerRow); sumWidth = 0; return true; } return false; } void GifsListWidget::refreshSavedGifs() { if (_section == Section::Gifs) { clearInlineRows(false); auto &saved = Auth().data().savedGifs(); if (!saved.isEmpty()) { _rows.reserve(saved.size()); auto row = Row(); row.items.reserve(kInlineItemsMaxPerRow); auto sumWidth = 0; for_const (auto &gif, saved) { inlineRowsAddItem(gif, 0, row, sumWidth); } inlineRowFinalize(row, sumWidth, true); } deleteUnusedGifLayouts(); resizeToWidth(width()); update(); } if (isVisible()) { updateSelected(); } else { preloadImages(); } } void GifsListWidget::clearInlineRows(bool resultsDeleted) { if (resultsDeleted) { _selected = _pressed = -1; } else { clearSelection(); for_const (auto &row, _rows) { for_const (auto &item, row.items) { item->setPosition(-1); } } } _rows.clear(); } GifsListWidget::LayoutItem *GifsListWidget::layoutPrepareSavedGif(DocumentData *doc, int32 position) { auto it = _gifLayouts.find(doc); if (it == _gifLayouts.cend()) { if (auto layout = LayoutItem::createLayoutGif(this, doc)) { it = _gifLayouts.emplace(doc, std::move(layout)).first; it->second->initDimensions(); } else { return nullptr; } } if (!it->second->maxWidth()) return nullptr; it->second->setPosition(position); return it->second.get(); } GifsListWidget::LayoutItem *GifsListWidget::layoutPrepareInlineResult(InlineResult *result, int32 position) { auto it = _inlineLayouts.find(result); if (it == _inlineLayouts.cend()) { if (auto layout = LayoutItem::createLayout(this, result, _inlineWithThumb)) { it = _inlineLayouts.emplace(result, std::move(layout)).first; it->second->initDimensions(); } else { return nullptr; } } if (!it->second->maxWidth()) return nullptr; it->second->setPosition(position); return it->second.get(); } void GifsListWidget::deleteUnusedGifLayouts() { if (_rows.isEmpty() || _section != Section::Gifs) { // delete all _gifLayouts.clear(); } else { for (auto i = _gifLayouts.begin(); i != _gifLayouts.cend();) { if (i->second->position() < 0) { i = _gifLayouts.erase(i); } else { ++i; } } } } void GifsListWidget::deleteUnusedInlineLayouts() { if (_rows.isEmpty() || _section == Section::Gifs) { // delete all _inlineLayouts.clear(); } else { for (auto i = _inlineLayouts.begin(); i != _inlineLayouts.cend();) { if (i->second->position() < 0) { i = _inlineLayouts.erase(i); } else { ++i; } } } } void GifsListWidget::layoutInlineRow(Row &row, int fullWidth) { auto count = int(row.items.size()); Assert(count <= kInlineItemsMaxPerRow); // enumerate items in the order of growing maxWidth() // for that sort item indices by maxWidth() int indices[kInlineItemsMaxPerRow]; for (auto i = 0; i != count; ++i) { indices[i] = i; } std::sort(indices, indices + count, [&](int a, int b) { return row.items[a]->maxWidth() < row.items[b]->maxWidth(); }); auto desiredWidth = row.maxWidth; row.height = 0; int availw = fullWidth - (st::inlineResultsLeft - st::buttonRadius); for (int i = 0; i < count; ++i) { const auto index = indices[i]; const auto &item = row.items[index]; const auto w = desiredWidth ? (item->maxWidth() * availw / desiredWidth) : item->maxWidth(); auto actualw = qMax(w, st::inlineResultsMinWidth); row.height = qMax(row.height, item->resizeGetHeight(actualw)); if (desiredWidth) { availw -= actualw; desiredWidth -= row.items[index]->maxWidth(); if (index > 0 && row.items[index - 1]->hasRightSkip()) { availw -= st::inlineResultsSkip; desiredWidth -= st::inlineResultsSkip; } } } } void GifsListWidget::preloadImages() { for (auto row = 0, rows = _rows.size(); row != rows; ++row) { for (auto col = 0, cols = _rows[row].items.size(); col != cols; ++col) { _rows[row].items[col]->preload(); } } } void GifsListWidget::switchToSavedGifs() { clearInlineRows(false); _section = Section::Gifs; refreshSavedGifs(); emit scrollToY(0); emit scrollUpdated(); } int GifsListWidget::refreshInlineRows(const InlineCacheEntry *entry, bool resultsDeleted) { if (!entry) { if (resultsDeleted) { clearInlineRows(true); deleteUnusedInlineLayouts(); } switchToSavedGifs(); return 0; } clearSelection(); _section = Section::Inlines; auto count = int(entry->results.size()); auto from = validateExistingInlineRows(entry->results); auto added = 0; if (count) { _rows.reserve(count); auto row = Row(); row.items.reserve(kInlineItemsMaxPerRow); auto sumWidth = 0; for (auto i = from; i != count; ++i) { if (inlineRowsAddItem(0, entry->results[i].get(), row, sumWidth)) { ++added; } } inlineRowFinalize(row, sumWidth, true); } resizeToWidth(width()); update(); _lastMousePos = QCursor::pos(); updateSelected(); return added; } int GifsListWidget::validateExistingInlineRows(const InlineResults &results) { int count = results.size(), until = 0, untilrow = 0, untilcol = 0; for (; until < count;) { if (untilrow >= _rows.size() || _rows[untilrow].items[untilcol]->getResult() != results[until].get()) { break; } ++until; if (++untilcol == _rows[untilrow].items.size()) { ++untilrow; untilcol = 0; } } if (until == count) { // all items are layed out if (untilrow == _rows.size()) { // nothing changed return until; } for (int i = untilrow, l = _rows.size(), skip = untilcol; i < l; ++i) { for (int j = 0, s = _rows[i].items.size(); j < s; ++j) { if (skip) { --skip; } else { _rows[i].items[j]->setPosition(-1); } } } if (!untilcol) { // all good rows are filled _rows.resize(untilrow); return until; } _rows.resize(untilrow + 1); _rows[untilrow].items.resize(untilcol); _rows[untilrow].maxWidth = std::accumulate( _rows[untilrow].items.begin(), _rows[untilrow].items.end(), 0, [](int w, auto &row) { return w + row->maxWidth(); }); layoutInlineRow(_rows[untilrow], width()); return until; } if (untilrow && !untilcol) { // remove last row, maybe it is not full --untilrow; untilcol = _rows[untilrow].items.size(); } until -= untilcol; for (int i = untilrow, l = _rows.size(); i < l; ++i) { for (int j = 0, s = _rows[i].items.size(); j < s; ++j) { _rows[i].items[j]->setPosition(-1); } } _rows.resize(untilrow); if (_rows.isEmpty()) { _inlineWithThumb = false; for (int i = until; i < count; ++i) { if (results.at(i)->hasThumbDisplay()) { _inlineWithThumb = true; break; } } } return until; } void GifsListWidget::inlineItemLayoutChanged(const InlineBots::Layout::ItemBase *layout) { if (_selected < 0 || !isVisible()) { return; } int row = _selected / MatrixRowShift, col = _selected % MatrixRowShift; if (row < _rows.size() && col < _rows[row].items.size()) { if (layout == _rows[row].items[col]) { updateSelected(); } } } void GifsListWidget::inlineItemRepaint(const InlineBots::Layout::ItemBase *layout) { auto ms = getms(); if (_lastScrolled + 100 <= ms) { update(); } else { _updateInlineItems.start(_lastScrolled + 100 - ms); } } bool GifsListWidget::inlineItemVisible(const InlineBots::Layout::ItemBase *layout) { auto position = layout->position(); if (position < 0 || !isVisible()) { return false; } auto row = position / MatrixRowShift; auto col = position % MatrixRowShift; Assert((row < _rows.size()) && (col < _rows[row].items.size())); auto &inlineItems = _rows[row].items; auto top = 0; for (auto i = 0; i != row; ++i) { top += _rows[i].height; } return (top < getVisibleBottom()) && (top + _rows[row].items[col]->height() > getVisibleTop()); } void GifsListWidget::afterShown() { if (_footer) { _footer->stealFocus(); } } void GifsListWidget::beforeHiding() { if (_footer) { _footer->returnFocus(); } } bool GifsListWidget::refreshInlineRows(int32 *added) { auto it = _inlineCache.find(_inlineQuery); const InlineCacheEntry *entry = nullptr; if (it != _inlineCache.cend()) { entry = it->second.get(); _inlineNextOffset = it->second->nextOffset; } auto result = refreshInlineRows(entry, false); if (added) *added = result; return (entry != nullptr); } int32 GifsListWidget::showInlineRows(bool newResults) { auto added = 0; auto clear = !refreshInlineRows(&added); if (newResults) { scrollToY(0); } return added; } void GifsListWidget::searchForGifs(const QString &query) { if (query.isEmpty()) { cancelGifsSearch(); return; } if (_inlineQuery != query) { _footer->setLoading(false); if (_inlineRequestId) { request(_inlineRequestId).cancel(); _inlineRequestId = 0; } if (_inlineCache.find(query) != _inlineCache.cend()) { _inlineRequestTimer.stop(); _inlineQuery = _inlineNextQuery = query; showInlineRows(true); } else { _inlineNextQuery = query; _inlineRequestTimer.start(kSearchRequestDelay); } } if (!_searchBot && !_searchBotRequestId) { auto username = str_const_toString(kSearchBotUsername); _searchBotRequestId = request(MTPcontacts_ResolveUsername(MTP_string(username))).done([this](const MTPcontacts_ResolvedPeer &result) { Expects(result.type() == mtpc_contacts_resolvedPeer); auto &data = result.c_contacts_resolvedPeer(); App::feedUsers(data.vusers); App::feedChats(data.vchats); if (auto peer = App::peerLoaded(peerFromMTP(data.vpeer))) { if (auto user = peer->asUser()) { _searchBot = user; } } }).send(); } } void GifsListWidget::sendInlineRequest() { if (_inlineRequestId || !_inlineQueryPeer || _inlineNextQuery.isEmpty()) { return; } if (!_searchBot) { // Wait for the bot being resolved. _footer->setLoading(true); _inlineRequestTimer.start(kSearchRequestDelay); return; } _inlineRequestTimer.stop(); _inlineQuery = _inlineNextQuery; auto nextOffset = QString(); auto it = _inlineCache.find(_inlineQuery); if (it != _inlineCache.cend()) { nextOffset = it->second->nextOffset; if (nextOffset.isEmpty()) { _footer->setLoading(false); return; } } _footer->setLoading(true); _inlineRequestId = request(MTPmessages_GetInlineBotResults(MTP_flags(0), _searchBot->inputUser, _inlineQueryPeer->input, MTPInputGeoPoint(), MTP_string(_inlineQuery), MTP_string(nextOffset))).done([this](const MTPmessages_BotResults &result, mtpRequestId requestId) { inlineResultsDone(result); }).fail([this](const RPCError &error) { // show error? _footer->setLoading(false); _inlineRequestId = 0; }).handleAllErrors().send(); } void GifsListWidget::refreshRecent() { if (_section == Section::Gifs) { refreshSavedGifs(); } } void GifsListWidget::updateSelected() { if (_pressed >= 0 && !_previewShown) { return; } auto newSelected = -1; auto p = mapFromGlobal(_lastMousePos); int sx = (rtl() ? width() - p.x() : p.x()) - (st::inlineResultsLeft - st::buttonRadius); int sy = p.y() - st::stickerPanPadding; int row = -1, col = -1, sel = -1; ClickHandlerPtr lnk; ClickHandlerHost *lnkhost = nullptr; HistoryView::CursorState cursor = HistoryView::CursorState::None; if (sy >= 0) { row = 0; for (int rows = _rows.size(); row < rows; ++row) { if (sy < _rows[row].height) { break; } sy -= _rows[row].height; } } if (sx >= 0 && row >= 0 && row < _rows.size()) { auto &inlineItems = _rows[row].items; col = 0; for (int cols = inlineItems.size(); col < cols; ++col) { int width = inlineItems[col]->width(); if (sx < width) { break; } sx -= width; if (inlineItems[col]->hasRightSkip()) { sx -= st::inlineResultsSkip; } } if (col < inlineItems.size()) { sel = row * MatrixRowShift + col; auto result = inlineItems[col]->getState( QPoint(sx, sy), HistoryView::StateRequest()); lnk = result.link; cursor = result.cursor; lnkhost = inlineItems[col]; } else { row = col = -1; } } else { row = col = -1; } int srow = (_selected >= 0) ? (_selected / MatrixRowShift) : -1; int scol = (_selected >= 0) ? (_selected % MatrixRowShift) : -1; if (_selected != sel) { if (srow >= 0 && scol >= 0) { Assert(srow >= 0 && srow < _rows.size() && scol >= 0 && scol < _rows[srow].items.size()); _rows[srow].items[scol]->update(); } _selected = sel; if (row >= 0 && col >= 0) { Assert(row >= 0 && row < _rows.size() && col >= 0 && col < _rows[row].items.size()); _rows[row].items[col]->update(); } if (_previewShown && _selected >= 0 && _pressed != _selected) { _pressed = _selected; if (row >= 0 && col >= 0) { auto layout = _rows[row].items[col]; if (auto previewDocument = layout->getPreviewDocument()) { Ui::showMediaPreview(previewDocument); } else if (auto previewPhoto = layout->getPreviewPhoto()) { Ui::showMediaPreview(previewPhoto); } } } } if (ClickHandler::setActive(lnk, lnkhost)) { setCursor(lnk ? style::cur_pointer : style::cur_default); } } void GifsListWidget::onPreview() { if (_pressed < 0) return; int row = _pressed / MatrixRowShift, col = _pressed % MatrixRowShift; if (row < _rows.size() && col < _rows[row].items.size()) { auto layout = _rows[row].items[col]; if (auto previewDocument = layout->getPreviewDocument()) { Ui::showMediaPreview(previewDocument); _previewShown = true; } else if (auto previewPhoto = layout->getPreviewPhoto()) { Ui::showMediaPreview(previewPhoto); _previewShown = true; } } } void GifsListWidget::onUpdateInlineItems() { auto ms = getms(); if (_lastScrolled + 100 <= ms) { update(); } else { _updateInlineItems.start(_lastScrolled + 100 - ms); } } } // namespace ChatHelpers ��������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/chat_helpers/gifs_list_widget.h��������������������������������0000664�0000000�0000000�00000010674�13262451251�0025573�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "chat_helpers/tabbed_selector.h" #include "inline_bots/inline_bot_layout_item.h" namespace InlineBots { namespace Layout { class ItemBase; } // namespace Layout class Result; } // namespace InlineBots namespace Ui { class RoundButton; } // namespace Ui namespace Window { class Controller; } // namespace Window namespace ChatHelpers { class GifsListWidget : public TabbedSelector::Inner , public InlineBots::Layout::Context , private base::Subscriber , private MTP::Sender { Q_OBJECT public: GifsListWidget(QWidget *parent, not_null<Window::Controller*> controller); void refreshRecent() override; void preloadImages() override; void clearSelection() override; object_ptr<TabbedSelector::InnerFooter> createFooter() override; void inlineItemLayoutChanged(const InlineBots::Layout::ItemBase *layout) override; void inlineItemRepaint(const InlineBots::Layout::ItemBase *layout) override; bool inlineItemVisible(const InlineBots::Layout::ItemBase *layout) override; void afterShown() override; void beforeHiding() override; void setInlineQueryPeer(PeerData *peer) { _inlineQueryPeer = peer; } void searchForGifs(const QString &query); void sendInlineRequest(); ~GifsListWidget(); protected: void visibleTopBottomUpdated( int visibleTop, int visibleBottom) override; void mousePressEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void paintEvent(QPaintEvent *e) override; void leaveEventHook(QEvent *e) override; void leaveToChildEvent(QEvent *e, QWidget *child) override; void enterFromChildEvent(QEvent *e, QWidget *child) override; TabbedSelector::InnerFooter *getFooter() const override; void processHideFinished() override; void processPanelHideFinished() override; int countDesiredHeight(int newWidth) override; private slots: void onPreview(); void onUpdateInlineItems(); signals: void selected(DocumentData *sticker); void selected(PhotoData *photo); void selected(InlineBots::Result *result, UserData *bot); void cancelled(); void emptyInlineRows(); void scrollUpdated(); private: enum class Section { Inlines, Gifs, }; class Footer; using InlineResult = InlineBots::Result; using InlineResults = std::vector<std::unique_ptr<InlineResult>>; using LayoutItem = InlineBots::Layout::ItemBase; struct InlineCacheEntry { QString nextOffset; InlineResults results; }; void cancelGifsSearch(); void switchToSavedGifs(); void refreshSavedGifs(); int refreshInlineRows(const InlineCacheEntry *results, bool resultsDeleted); void checkLoadMore(); int32 showInlineRows(bool newResults); bool refreshInlineRows(int32 *added = 0); void inlineResultsDone(const MTPmessages_BotResults &result); void updateSelected(); void paintInlineItems(Painter &p, QRect clip); Section _section = Section::Gifs; TimeMs _lastScrolled = 0; QTimer _updateInlineItems; bool _inlineWithThumb = false; struct Row { int maxWidth = 0; int height = 0; QVector<LayoutItem*> items; }; QVector<Row> _rows; void clearInlineRows(bool resultsDeleted); std::map<DocumentData*, std::unique_ptr<LayoutItem>> _gifLayouts; LayoutItem *layoutPrepareSavedGif(DocumentData *doc, int32 position); std::map<InlineResult*, std::unique_ptr<LayoutItem>> _inlineLayouts; LayoutItem *layoutPrepareInlineResult(InlineResult *result, int32 position); bool inlineRowsAddItem(DocumentData *savedGif, InlineResult *result, Row &row, int32 &sumWidth); bool inlineRowFinalize(Row &row, int32 &sumWidth, bool force = false); void layoutInlineRow(Row &row, int fullWidth); void deleteUnusedGifLayouts(); void deleteUnusedInlineLayouts(); int validateExistingInlineRows(const InlineResults &results); void selectInlineResult(int row, int column); Footer *_footer = nullptr; int _selected = -1; int _pressed = -1; QPoint _lastMousePos; QTimer _previewTimer; bool _previewShown = false; std::map<QString, std::unique_ptr<InlineCacheEntry>> _inlineCache; QTimer _inlineRequestTimer; UserData *_searchBot = nullptr; mtpRequestId _searchBotRequestId = 0; PeerData *_inlineQueryPeer = nullptr; QString _inlineQuery, _inlineNextQuery, _inlineNextOffset; mtpRequestId _inlineRequestId = 0; }; } // namespace ChatHelpers ��������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/chat_helpers/message_field.cpp���������������������������������0000664�0000000�0000000�00000011356�13262451251�0025365�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "chat_helpers/message_field.h" #include "history/history_widget.h" #include "base/qthelp_regex.h" #include "styles/style_history.h" #include "window/window_controller.h" #include "mainwindow.h" #include "auth_session.h" namespace { // For mention tags save and validate userId, ignore tags for different userId. class FieldTagMimeProcessor : public Ui::FlatTextarea::TagMimeProcessor { public: QString mimeTagFromTag(const QString &tagId) override { return ConvertTagToMimeTag(tagId); } QString tagFromMimeTag(const QString &mimeTag) override { if (mimeTag.startsWith(qstr("mention://"))) { auto match = QRegularExpression(":(\\d+)$").match(mimeTag); if (!match.hasMatch() || match.capturedRef(1).toInt() != Auth().userId()) { return QString(); } return mimeTag.mid(0, mimeTag.size() - match.capturedLength()); } return mimeTag; } }; } // namespace QString ConvertTagToMimeTag(const QString &tagId) { if (tagId.startsWith(qstr("mention://"))) { return tagId + ':' + QString::number(Auth().userId()); } return tagId; } EntitiesInText ConvertTextTagsToEntities(const TextWithTags::Tags &tags) { EntitiesInText result; if (tags.isEmpty()) { return result; } result.reserve(tags.size()); auto mentionStart = qstr("mention://user."); for_const (auto &tag, tags) { if (tag.id.startsWith(mentionStart)) { if (auto match = qthelp::regex_match("^(\\d+\\.\\d+)(/|$)", tag.id.midRef(mentionStart.size()))) { result.push_back(EntityInText(EntityInTextMentionName, tag.offset, tag.length, match->captured(1))); } } } return result; } TextWithTags::Tags ConvertEntitiesToTextTags(const EntitiesInText &entities) { TextWithTags::Tags result; if (entities.isEmpty()) { return result; } result.reserve(entities.size()); for_const (auto &entity, entities) { if (entity.type() == EntityInTextMentionName) { auto match = QRegularExpression("^(\\d+\\.\\d+)$").match(entity.data()); if (match.hasMatch()) { result.push_back({ entity.offset(), entity.length(), qstr("mention://user.") + entity.data() }); } } } return result; } std::unique_ptr<QMimeData> MimeDataFromTextWithEntities( const TextWithEntities &forClipboard) { if (forClipboard.text.isEmpty()) { return nullptr; } auto result = std::make_unique<QMimeData>(); result->setText(forClipboard.text); auto tags = ConvertEntitiesToTextTags(forClipboard.entities); if (!tags.isEmpty()) { for (auto &tag : tags) { tag.id = ConvertTagToMimeTag(tag.id); } result->setData( Ui::FlatTextarea::tagsMimeType(), Ui::FlatTextarea::serializeTagsList(tags)); } return result; } void SetClipboardWithEntities( const TextWithEntities &forClipboard, QClipboard::Mode mode) { if (auto data = MimeDataFromTextWithEntities(forClipboard)) { QApplication::clipboard()->setMimeData(data.release(), mode); } } MessageField::MessageField(QWidget *parent, not_null<Window::Controller*> controller, const style::FlatTextarea &st, base::lambda<QString()> placeholderFactory, const QString &val) : Ui::FlatTextarea(parent, st, std::move(placeholderFactory), val) , _controller(controller) { setMinHeight(st::historySendSize.height() - 2 * st::historySendPadding); setMaxHeight(st::historyComposeFieldMaxHeight); setTagMimeProcessor(std::make_unique<FieldTagMimeProcessor>()); } bool MessageField::hasSendText() const { auto &text = getTextWithTags().text; for (auto *ch = text.constData(), *e = ch + text.size(); ch != e; ++ch) { auto code = ch->unicode(); if (code != ' ' && code != '\n' && code != '\r' && !chReplacedBySpace(code)) { return true; } } return false; } void MessageField::onEmojiInsert(EmojiPtr emoji) { if (isHidden()) return; insertEmoji(emoji, textCursor()); } void MessageField::dropEvent(QDropEvent *e) { FlatTextarea::dropEvent(e); if (e->isAccepted()) { _controller->window()->activateWindow(); } } bool MessageField::canInsertFromMimeData(const QMimeData *source) const { if (source->hasUrls()) { int32 files = 0; for (int32 i = 0; i < source->urls().size(); ++i) { if (source->urls().at(i).isLocalFile()) { ++files; } } if (files > 1) return false; // multiple confirm with "compressed" checkbox } if (source->hasImage()) return true; return FlatTextarea::canInsertFromMimeData(source); } void MessageField::insertFromMimeData(const QMimeData *source) { if (_insertFromMimeDataHook && _insertFromMimeDataHook(source)) { return; } FlatTextarea::insertFromMimeData(source); } void MessageField::focusInEvent(QFocusEvent *e) { FlatTextarea::focusInEvent(e); emit focused(); } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/chat_helpers/message_field.h�����������������������������������0000664�0000000�0000000�00000003204�13262451251�0025023�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/widgets/input_fields.h" class HistoryWidget; namespace Window { class Controller; } // namespace Window QString ConvertTagToMimeTag(const QString &tagId); EntitiesInText ConvertTextTagsToEntities(const TextWithTags::Tags &tags); TextWithTags::Tags ConvertEntitiesToTextTags( const EntitiesInText &entities); std::unique_ptr<QMimeData> MimeDataFromTextWithEntities( const TextWithEntities &forClipboard); void SetClipboardWithEntities( const TextWithEntities &forClipboard, QClipboard::Mode mode = QClipboard::Clipboard); class MessageField final : public Ui::FlatTextarea { Q_OBJECT public: MessageField(QWidget *parent, not_null<Window::Controller*> controller, const style::FlatTextarea &st, base::lambda<QString()> placeholderFactory = base::lambda<QString()>(), const QString &val = QString()); bool hasSendText() const; void setInsertFromMimeDataHook(base::lambda<bool(const QMimeData *data)> hook) { _insertFromMimeDataHook = std::move(hook); } public slots: void onEmojiInsert(EmojiPtr emoji); signals: void focused(); protected: void focusInEvent(QFocusEvent *e) override; void dropEvent(QDropEvent *e) override; bool canInsertFromMimeData(const QMimeData *source) const override; void insertFromMimeData(const QMimeData *source) override; private: not_null<Window::Controller*> _controller; base::lambda<bool(const QMimeData *data)> _insertFromMimeDataHook; }; ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/chat_helpers/stickers.cpp��������������������������������������0000664�0000000�0000000�00000072165�13262451251�0024432�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "stickers.h" #include "data/data_document.h" #include "data/data_session.h" #include "boxes/stickers_box.h" #include "boxes/confirm_box.h" #include "lang/lang_keys.h" #include "apiwrap.h" #include "storage/localstorage.h" #include "mainwidget.h" #include "auth_session.h" #include "mainwindow.h" #include "ui/toast/toast.h" #include "styles/style_chat_helpers.h" namespace Stickers { void ApplyArchivedResult(const MTPDmessages_stickerSetInstallResultArchive &d) { auto &v = d.vsets.v; auto &order = Auth().data().stickerSetsOrderRef(); Order archived; archived.reserve(v.size()); QMap<uint64, uint64> setsToRequest; for_const (auto &stickerSet, v) { const MTPDstickerSet *setData = nullptr; switch (stickerSet.type()) { case mtpc_stickerSetCovered: { auto &d = stickerSet.c_stickerSetCovered(); if (d.vset.type() == mtpc_stickerSet) { setData = &d.vset.c_stickerSet(); } } break; case mtpc_stickerSetMultiCovered: { auto &d = stickerSet.c_stickerSetMultiCovered(); if (d.vset.type() == mtpc_stickerSet) { setData = &d.vset.c_stickerSet(); } } break; } if (setData) { auto set = FeedSet(*setData); if (set->stickers.isEmpty()) { setsToRequest.insert(set->id, set->access); } auto index = order.indexOf(set->id); if (index >= 0) { order.removeAt(index); } archived.push_back(set->id); } } if (!setsToRequest.isEmpty()) { for (auto i = setsToRequest.cbegin(), e = setsToRequest.cend(); i != e; ++i) { Auth().api().scheduleStickerSetRequest(i.key(), i.value()); } Auth().api().requestStickerSets(); } Local::writeInstalledStickers(); Local::writeArchivedStickers(); Ui::Toast::Config toast; toast.text = lang(lng_stickers_packs_archived); toast.maxWidth = st::stickersToastMaxWidth; toast.padding = st::stickersToastPadding; Ui::Toast::Show(toast); // Ui::show(Box<StickersBox>(archived), LayerOption::KeepOther); Auth().data().notifyStickersUpdated(); } // For testing: Just apply random subset or your sticker sets as archived. bool ApplyArchivedResultFake() { auto sets = QVector<MTPStickerSetCovered>(); for (auto &set : Auth().data().stickerSetsRef()) { if ((set.flags & MTPDstickerSet::Flag::f_installed_date) && !(set.flags & MTPDstickerSet_ClientFlag::f_special)) { if (rand_value<uint32>() % 128 < 64) { auto data = MTP_stickerSet( MTP_flags(set.flags | MTPDstickerSet::Flag::f_archived), MTP_int(set.installDate), MTP_long(set.id), MTP_long(set.access), MTP_string(set.title), MTP_string(set.shortName), MTP_int(set.count), MTP_int(set.hash)); sets.push_back(MTP_stickerSetCovered( data, MTP_documentEmpty(MTP_long(0)))); } } } if (sets.size() > 3) { sets = sets.mid(0, 3); } auto fakeResult = MTP_messages_stickerSetInstallResultArchive( MTP_vector<MTPStickerSetCovered>(sets)); ApplyArchivedResult(fakeResult.c_messages_stickerSetInstallResultArchive()); return true; } void InstallLocally(uint64 setId) { auto &sets = Auth().data().stickerSetsRef(); auto it = sets.find(setId); if (it == sets.end()) { return; } auto flags = it->flags; it->flags &= ~(MTPDstickerSet::Flag::f_archived | MTPDstickerSet_ClientFlag::f_unread); it->flags |= MTPDstickerSet::Flag::f_installed_date; it->installDate = unixtime(); auto changedFlags = flags ^ it->flags; auto &order = Auth().data().stickerSetsOrderRef(); int insertAtIndex = 0, currentIndex = order.indexOf(setId); if (currentIndex != insertAtIndex) { if (currentIndex > 0) { order.removeAt(currentIndex); } order.insert(insertAtIndex, setId); } auto custom = sets.find(CustomSetId); if (custom != sets.cend()) { for_const (auto sticker, it->stickers) { int removeIndex = custom->stickers.indexOf(sticker); if (removeIndex >= 0) custom->stickers.removeAt(removeIndex); } if (custom->stickers.isEmpty()) { sets.erase(custom); } } Local::writeInstalledStickers(); if (changedFlags & MTPDstickerSet_ClientFlag::f_unread) { Local::writeFeaturedStickers(); } if (changedFlags & MTPDstickerSet::Flag::f_archived) { auto index = Auth().data().archivedStickerSetsOrderRef().indexOf(setId); if (index >= 0) { Auth().data().archivedStickerSetsOrderRef().removeAt(index); Local::writeArchivedStickers(); } } Auth().data().notifyStickersUpdated(); } void UndoInstallLocally(uint64 setId) { auto &sets = Auth().data().stickerSetsRef(); auto it = sets.find(setId); if (it == sets.end()) { return; } it->flags &= ~MTPDstickerSet::Flag::f_installed_date; it->installDate = TimeId(0); auto &order = Auth().data().stickerSetsOrderRef(); int currentIndex = order.indexOf(setId); if (currentIndex >= 0) { order.removeAt(currentIndex); } Local::writeInstalledStickers(); Auth().data().notifyStickersUpdated(); Ui::show( Box<InformBox>(lang(lng_stickers_not_found)), LayerOption::KeepOther); } bool IsFaved(not_null<DocumentData*> document) { auto it = Auth().data().stickerSets().constFind(FavedSetId); return (it != Auth().data().stickerSets().cend()) && it->stickers.contains(document); } void CheckFavedLimit(Set &set) { if (set.stickers.size() <= Global::StickersFavedLimit()) { return; } auto removing = set.stickers.back(); set.stickers.pop_back(); for (auto i = set.emoji.begin(); i != set.emoji.end();) { auto index = i->indexOf(removing); if (index >= 0) { i->removeAt(index); if (i->empty()) { i = set.emoji.erase(i); continue; } } ++i; } } void PushFavedToFront( Set &set, not_null<DocumentData*> document, const std::vector<not_null<EmojiPtr>> &emojiList) { set.stickers.push_front(document); for (auto emoji : emojiList) { set.emoji[emoji].push_front(document); } CheckFavedLimit(set); } void MoveFavedToFront(Set &set, int index) { Expects(index > 0 && index < set.stickers.size()); auto document = set.stickers[index]; while (index-- != 0) { set.stickers[index + 1] = set.stickers[index]; } set.stickers[0] = document; for (auto &list : set.emoji) { auto index = list.indexOf(document); if (index > 0) { while (index-- != 0) { list[index + 1] = list[index]; } list[0] = document; } } } void RequestSetToPushFaved(not_null<DocumentData*> document); void SetIsFaved(not_null<DocumentData*> document, base::optional<std::vector<not_null<EmojiPtr>>> emojiList = base::none) { auto &sets = Auth().data().stickerSetsRef(); auto it = sets.find(FavedSetId); if (it == sets.end()) { it = sets.insert(FavedSetId, Set( FavedSetId, uint64(0), Lang::Hard::FavedSetTitle(), QString(), 0, // count 0, // hash MTPDstickerSet_ClientFlag::f_special | 0, TimeId(0))); } auto index = it->stickers.indexOf(document); if (index == 0) { return; } if (index > 0) { MoveFavedToFront(*it, index); } else if (emojiList) { PushFavedToFront(*it, document, *emojiList); } else if (auto list = GetEmojiListFromSet(document)) { PushFavedToFront(*it, document, *list); } else { RequestSetToPushFaved(document); return; } Local::writeFavedStickers(); Auth().data().notifyStickersUpdated(); Auth().api().stickerSetInstalled(FavedSetId); } void RequestSetToPushFaved(not_null<DocumentData*> document) { auto addAnyway = [document](std::vector<not_null<EmojiPtr>> list) { if (list.empty()) { if (auto sticker = document->sticker()) { if (auto emoji = Ui::Emoji::Find(sticker->alt)) { list.push_back(emoji); } } } SetIsFaved(document, std::move(list)); }; MTP::send(MTPmessages_GetStickerSet(document->sticker()->set), rpcDone([document, addAnyway](const MTPmessages_StickerSet &result) { Expects(result.type() == mtpc_messages_stickerSet); auto list = std::vector<not_null<EmojiPtr>>(); auto &d = result.c_messages_stickerSet(); list.reserve(d.vpacks.v.size()); for_const (auto &mtpPack, d.vpacks.v) { auto &pack = mtpPack.c_stickerPack(); for_const (auto &documentId, pack.vdocuments.v) { if (documentId.v == document->id) { if (auto emoji = Ui::Emoji::Find(qs(mtpPack.c_stickerPack().vemoticon))) { list.push_back(emoji); } break; } } } addAnyway(std::move(list)); }), rpcFail([addAnyway](const RPCError &error) { if (MTP::isDefaultHandledError(error)) { return false; } // Perhaps this is a deleted sticker pack. Add anyway. addAnyway({}); return true; })); } void SetIsNotFaved(not_null<DocumentData*> document) { auto &sets = Auth().data().stickerSetsRef(); auto it = sets.find(FavedSetId); if (it == sets.end()) { return; } auto index = it->stickers.indexOf(document); if (index < 0) { return; } it->stickers.removeAt(index); for (auto i = it->emoji.begin(); i != it->emoji.end();) { auto index = i->indexOf(document); if (index >= 0) { i->removeAt(index); if (i->empty()) { i = it->emoji.erase(i); continue; } } ++i; } if (it->stickers.empty()) { sets.erase(it); } Local::writeFavedStickers(); Auth().data().notifyStickersUpdated(); } void SetFaved(not_null<DocumentData*> document, bool faved) { if (faved) { SetIsFaved(document); } else { SetIsNotFaved(document); } } void SetsReceived(const QVector<MTPStickerSet> &data, int32 hash) { auto &setsOrder = Auth().data().stickerSetsOrderRef(); setsOrder.clear(); auto &sets = Auth().data().stickerSetsRef(); QMap<uint64, uint64> setsToRequest; for (auto &set : sets) { if (!(set.flags & MTPDstickerSet::Flag::f_archived)) { // Mark for removing. set.flags &= ~MTPDstickerSet::Flag::f_installed_date; set.installDate = 0; } } for_const (auto &setData, data) { if (setData.type() == mtpc_stickerSet) { auto set = FeedSet(setData.c_stickerSet()); if (!(set->flags & MTPDstickerSet::Flag::f_archived) || (set->flags & MTPDstickerSet::Flag::f_official)) { setsOrder.push_back(set->id); if (set->stickers.isEmpty() || (set->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) { setsToRequest.insert(set->id, set->access); } } } } auto writeRecent = false; auto &recent = GetRecentPack(); for (auto it = sets.begin(), e = sets.end(); it != e;) { bool installed = (it->flags & MTPDstickerSet::Flag::f_installed_date); bool featured = (it->flags & MTPDstickerSet_ClientFlag::f_featured); bool special = (it->flags & MTPDstickerSet_ClientFlag::f_special); bool archived = (it->flags & MTPDstickerSet::Flag::f_archived); if (!installed) { // remove not mine sets from recent stickers for (auto i = recent.begin(); i != recent.cend();) { if (it->stickers.indexOf(i->first) >= 0) { i = recent.erase(i); writeRecent = true; } else { ++i; } } } if (installed || featured || special || archived) { ++it; } else { it = sets.erase(it); } } if (!setsToRequest.isEmpty()) { auto &api = Auth().api(); for (auto i = setsToRequest.cbegin(), e = setsToRequest.cend(); i != e; ++i) { api.scheduleStickerSetRequest(i.key(), i.value()); } api.requestStickerSets(); } Local::writeInstalledStickers(); if (writeRecent) Local::writeUserSettings(); if (Local::countStickersHash() != hash) { LOG(("API Error: received stickers hash %1 while counted hash is %2").arg(hash).arg(Local::countStickersHash())); } Auth().data().notifyStickersUpdated(); } void SetPackAndEmoji( Set &set, Pack &&pack, const std::vector<TimeId> &&dates, const QVector<MTPStickerPack> &packs) { set.stickers = std::move(pack); set.dates = std::move(dates); set.emoji.clear(); for_const (auto &mtpPack, packs) { Assert(mtpPack.type() == mtpc_stickerPack); auto &pack = mtpPack.c_stickerPack(); if (auto emoji = Ui::Emoji::Find(qs(pack.vemoticon))) { emoji = emoji->original(); auto &stickers = pack.vdocuments.v; auto p = Pack(); p.reserve(stickers.size()); for (auto j = 0, c = stickers.size(); j != c; ++j) { auto document = Auth().data().document(stickers[j].v); if (!document || !document->sticker()) continue; p.push_back(document); } set.emoji.insert(emoji, p); } } } void SpecialSetReceived( uint64 setId, const QString &setTitle, const QVector<MTPDocument> &items, int32 hash, const QVector<MTPStickerPack> &packs, const QVector<MTPint> &usageDates) { auto &sets = Auth().data().stickerSetsRef(); auto it = sets.find(setId); if (items.isEmpty()) { if (it != sets.cend()) { sets.erase(it); } } else { if (it == sets.cend()) { it = sets.insert(setId, Set( setId, uint64(0), setTitle, QString(), 0, // count 0, // hash MTPDstickerSet_ClientFlag::f_special | 0, TimeId(0))); } else { it->title = setTitle; } it->hash = hash; auto dates = std::vector<TimeId>(); auto dateIndex = 0; auto datesAvailable = (items.size() == usageDates.size()) && (setId == CloudRecentSetId); auto custom = sets.find(CustomSetId); auto pack = Pack(); pack.reserve(items.size()); for_const (auto &mtpDocument, items) { ++dateIndex; auto document = Auth().data().document(mtpDocument); if (!document->sticker()) { continue; } pack.push_back(document); if (datesAvailable) { dates.push_back(TimeId(usageDates[dateIndex - 1].v)); } if (custom != sets.cend()) { auto index = custom->stickers.indexOf(document); if (index >= 0) { custom->stickers.removeAt(index); } } } if (custom != sets.cend() && custom->stickers.isEmpty()) { sets.erase(custom); custom = sets.end(); } auto writeRecent = false; auto &recent = GetRecentPack(); for (auto i = recent.begin(); i != recent.cend();) { if (it->stickers.indexOf(i->first) >= 0 && pack.indexOf(i->first) < 0) { i = recent.erase(i); writeRecent = true; } else { ++i; } } if (pack.isEmpty()) { sets.erase(it); } else { SetPackAndEmoji(*it, std::move(pack), std::move(dates), packs); } if (writeRecent) { Local::writeUserSettings(); } } switch (setId) { case CloudRecentSetId: { if (Local::countRecentStickersHash() != hash) { LOG(("API Error: received recent stickers hash %1 while counted hash is %2").arg(hash).arg(Local::countRecentStickersHash())); } Local::writeRecentStickers(); } break; case FavedSetId: { if (Local::countFavedStickersHash() != hash) { LOG(("API Error: received faved stickers hash %1 while counted hash is %2").arg(hash).arg(Local::countFavedStickersHash())); } Local::writeFavedStickers(); } break; default: Unexpected("setId in SpecialSetReceived()"); } Auth().data().notifyStickersUpdated(); } void FeaturedSetsReceived(const QVector<MTPStickerSetCovered> &data, const QVector<MTPlong> &unread, int32 hash) { OrderedSet<uint64> unreadMap; for_const (auto &unreadSetId, unread) { unreadMap.insert(unreadSetId.v); } auto &setsOrder = Auth().data().featuredStickerSetsOrderRef(); setsOrder.clear(); auto &sets = Auth().data().stickerSetsRef(); QMap<uint64, uint64> setsToRequest; for (auto &set : sets) { set.flags &= ~MTPDstickerSet_ClientFlag::f_featured; // mark for removing } for (int i = 0, l = data.size(); i != l; ++i) { auto &setData = data[i]; const MTPDstickerSet *set = nullptr; switch (setData.type()) { case mtpc_stickerSetCovered: { auto &d = setData.c_stickerSetCovered(); if (d.vset.type() == mtpc_stickerSet) { set = &d.vset.c_stickerSet(); } } break; case mtpc_stickerSetMultiCovered: { auto &d = setData.c_stickerSetMultiCovered(); if (d.vset.type() == mtpc_stickerSet) { set = &d.vset.c_stickerSet(); } } break; } if (set) { auto it = sets.find(set->vid.v); const auto title = GetSetTitle(*set); const auto installDate = set->has_installed_date() ? set->vinstalled_date.v : TimeId(0); if (it == sets.cend()) { auto setClientFlags = MTPDstickerSet_ClientFlag::f_featured | MTPDstickerSet_ClientFlag::f_not_loaded; if (unreadMap.contains(set->vid.v)) { setClientFlags |= MTPDstickerSet_ClientFlag::f_unread; } it = sets.insert(set->vid.v, Set( set->vid.v, set->vaccess_hash.v, title, qs(set->vshort_name), set->vcount.v, set->vhash.v, set->vflags.v | setClientFlags, installDate)); } else { it->access = set->vaccess_hash.v; it->title = title; it->shortName = qs(set->vshort_name); auto clientFlags = it->flags & (MTPDstickerSet_ClientFlag::f_featured | MTPDstickerSet_ClientFlag::f_unread | MTPDstickerSet_ClientFlag::f_not_loaded | MTPDstickerSet_ClientFlag::f_special); it->flags = set->vflags.v | clientFlags; it->flags |= MTPDstickerSet_ClientFlag::f_featured; it->installDate = installDate; if (unreadMap.contains(it->id)) { it->flags |= MTPDstickerSet_ClientFlag::f_unread; } else { it->flags &= ~MTPDstickerSet_ClientFlag::f_unread; } if (it->count != set->vcount.v || it->hash != set->vhash.v || it->emoji.isEmpty()) { it->count = set->vcount.v; it->hash = set->vhash.v; it->flags |= MTPDstickerSet_ClientFlag::f_not_loaded; // need to request this set } } setsOrder.push_back(set->vid.v); if (it->stickers.isEmpty() || (it->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) { setsToRequest.insert(set->vid.v, set->vaccess_hash.v); } } } auto unreadCount = 0; for (auto it = sets.begin(), e = sets.end(); it != e;) { bool installed = (it->flags & MTPDstickerSet::Flag::f_installed_date); bool featured = (it->flags & MTPDstickerSet_ClientFlag::f_featured); bool special = (it->flags & MTPDstickerSet_ClientFlag::f_special); bool archived = (it->flags & MTPDstickerSet::Flag::f_archived); if (installed || featured || special || archived) { if (featured && (it->flags & MTPDstickerSet_ClientFlag::f_unread)) { ++unreadCount; } ++it; } else { it = sets.erase(it); } } Auth().data().setFeaturedStickerSetsUnreadCount(unreadCount); if (Local::countFeaturedStickersHash() != hash) { LOG(("API Error: received featured stickers hash %1 while counted hash is %2").arg(hash).arg(Local::countFeaturedStickersHash())); } if (!setsToRequest.isEmpty()) { auto &api = Auth().api(); for (auto i = setsToRequest.cbegin(), e = setsToRequest.cend(); i != e; ++i) { api.scheduleStickerSetRequest(i.key(), i.value()); } api.requestStickerSets(); } Local::writeFeaturedStickers(); Auth().data().notifyStickersUpdated(); } void GifsReceived(const QVector<MTPDocument> &items, int32 hash) { auto &saved = Auth().data().savedGifsRef(); saved.clear(); saved.reserve(items.size()); for_const (auto &gif, items) { auto document = Auth().data().document(gif); if (!document->isGifv()) { LOG(("API Error: bad document returned in HistoryWidget::savedGifsGot!")); continue; } saved.push_back(document); } if (Local::countSavedGifsHash() != hash) { LOG(("API Error: received saved gifs hash %1 while counted hash is %2").arg(hash).arg(Local::countSavedGifsHash())); } Local::writeSavedGifs(); Auth().data().notifySavedGifsUpdated(); } std::vector<not_null<DocumentData*>> GetListByEmoji( not_null<EmojiPtr> emoji, uint64 seed) { const auto original = emoji->original(); struct StickerWithDate { not_null<DocumentData*> document; TimeId date = 0; }; auto result = std::vector<StickerWithDate>(); auto &sets = Auth().data().stickerSetsRef(); auto setsToRequest = base::flat_map<uint64, uint64>(); const auto add = [&](not_null<DocumentData*> document, TimeId date) { if (ranges::find(result, document, [](const StickerWithDate &data) { return data.document; }) == result.end()) { result.push_back({ document, date }); } }; constexpr auto kSlice = 65536; const auto CreateSortKey = [&]( not_null<DocumentData*> document, int base) { return TimeId(base + int((document->id ^ seed) % kSlice)); }; const auto CreateRecentSortKey = [&](not_null<DocumentData*> document) { return CreateSortKey(document, kSlice * 4); }; auto myCounter = 0; const auto CreateMySortKey = [&] { return (kSlice * 4 - (++myCounter)); }; const auto CreateFeaturedSortKey = [&](not_null<DocumentData*> document) { return CreateSortKey(document, kSlice * 2); }; const auto CreateOtherSortKey = [&](not_null<DocumentData*> document) { return CreateSortKey(document, kSlice); }; const auto InstallDate = [&](not_null<DocumentData*> document) { Expects(document->sticker() != nullptr); const auto sticker = document->sticker(); if (sticker->set.type() == mtpc_inputStickerSetID) { const auto setId = sticker->set.c_inputStickerSetID().vid.v; const auto setIt = sets.find(setId); if (setIt != sets.end()) { return setIt->installDate; } } return TimeId(0); }; auto recentIt = sets.find(Stickers::CloudRecentSetId); if (recentIt != sets.cend()) { auto i = recentIt->emoji.constFind(original); if (i != recentIt->emoji.cend()) { result.reserve(i->size()); for (const auto document : *i) { const auto usageDate = [&] { if (recentIt->dates.empty()) { return TimeId(0); } const auto index = recentIt->stickers.indexOf(document); if (index < 0) { return TimeId(0); } Assert(index < recentIt->dates.size()); return recentIt->dates[index]; }(); const auto date = usageDate ? usageDate : InstallDate(document); result.push_back({ document, date ? date : CreateRecentSortKey(document) }); } } } const auto addList = [&](const Order &order, MTPDstickerSet::Flag skip) { for (const auto setId : order) { auto it = sets.find(setId); if (it == sets.cend() || (it->flags & skip)) { continue; } if (it->emoji.isEmpty()) { setsToRequest.emplace(it->id, it->access); it->flags |= MTPDstickerSet_ClientFlag::f_not_loaded; continue; } auto i = it->emoji.constFind(original); if (i == it->emoji.cend()) { continue; } const auto my = (it->flags & MTPDstickerSet::Flag::f_installed_date); result.reserve(result.size() + i->size()); for (const auto document : *i) { const auto installDate = my ? it->installDate : TimeId(0); const auto date = (installDate > 1) ? installDate : my ? CreateMySortKey() : CreateFeaturedSortKey(document); add(document, date); } } }; addList( Auth().data().stickerSetsOrder(), MTPDstickerSet::Flag::f_archived); //addList( // Auth().data().featuredStickerSetsOrder(), // MTPDstickerSet::Flag::f_installed_date); if (!setsToRequest.empty()) { for (const auto [setId, accessHash] : setsToRequest) { Auth().api().scheduleStickerSetRequest(setId, accessHash); } Auth().api().requestStickerSets(); } if (Global::SuggestStickersByEmoji()) { const auto others = Auth().api().stickersByEmoji(original); if (!others) { return {}; } result.reserve(result.size() + others->size()); for (const auto document : *others) { add(document, CreateOtherSortKey(document)); } } ranges::action::sort( result, std::greater<>(), [](const StickerWithDate &data) { return data.date; }); return ranges::view::all( result ) | ranges::view::transform([](const StickerWithDate &data) { return data.document; }) | ranges::to_vector; } base::optional<std::vector<not_null<EmojiPtr>>> GetEmojiListFromSet( not_null<DocumentData*> document) { if (auto sticker = document->sticker()) { auto &inputSet = sticker->set; if (inputSet.type() != mtpc_inputStickerSetID) { return base::none; } auto &sets = Auth().data().stickerSets(); auto it = sets.constFind(inputSet.c_inputStickerSetID().vid.v); if (it == sets.cend()) { return base::none; } auto result = std::vector<not_null<EmojiPtr>>(); for (auto i = it->emoji.cbegin(), e = it->emoji.cend(); i != e; ++i) { if (i->contains(document)) { result.push_back(i.key()); } } if (result.empty()) { return base::none; } return std::move(result); } return base::none; } Set *FeedSet(const MTPDstickerSet &set) { auto &sets = Auth().data().stickerSetsRef(); auto it = sets.find(set.vid.v); auto title = GetSetTitle(set); auto flags = MTPDstickerSet::Flags(0); if (it == sets.cend()) { it = sets.insert(set.vid.v, Stickers::Set( set.vid.v, set.vaccess_hash.v, title, qs(set.vshort_name), set.vcount.v, set.vhash.v, set.vflags.v | MTPDstickerSet_ClientFlag::f_not_loaded, set.has_installed_date() ? set.vinstalled_date.v : TimeId(0))); } else { it->access = set.vaccess_hash.v; it->title = title; it->shortName = qs(set.vshort_name); flags = it->flags; auto clientFlags = it->flags & (MTPDstickerSet_ClientFlag::f_featured | MTPDstickerSet_ClientFlag::f_unread | MTPDstickerSet_ClientFlag::f_not_loaded | MTPDstickerSet_ClientFlag::f_special); it->flags = set.vflags.v | clientFlags; it->installDate = set.has_installed_date() ? set.vinstalled_date.v : TimeId(0); if (it->count != set.vcount.v || it->hash != set.vhash.v || it->emoji.isEmpty()) { it->count = set.vcount.v; it->hash = set.vhash.v; it->flags |= MTPDstickerSet_ClientFlag::f_not_loaded; // need to request this set } } auto changedFlags = (flags ^ it->flags); if (changedFlags & MTPDstickerSet::Flag::f_archived) { auto index = Auth().data().archivedStickerSetsOrder().indexOf(it->id); if (it->flags & MTPDstickerSet::Flag::f_archived) { if (index < 0) { Auth().data().archivedStickerSetsOrderRef().push_front(it->id); } } else if (index >= 0) { Auth().data().archivedStickerSetsOrderRef().removeAt(index); } } return &it.value(); } Set *FeedSetFull(const MTPmessages_StickerSet &data) { Expects(data.type() == mtpc_messages_stickerSet); Expects(data.c_messages_stickerSet().vset.type() == mtpc_stickerSet); auto &d = data.c_messages_stickerSet(); auto set = FeedSet(d.vset.c_stickerSet()); set->flags &= ~MTPDstickerSet_ClientFlag::f_not_loaded; auto &sets = Auth().data().stickerSetsRef(); auto &d_docs = d.vdocuments.v; auto custom = sets.find(Stickers::CustomSetId); auto pack = Pack(); pack.reserve(d_docs.size()); for (auto i = 0, l = d_docs.size(); i != l; ++i) { auto doc = Auth().data().document(d_docs.at(i)); if (!doc->sticker()) continue; pack.push_back(doc); if (custom != sets.cend()) { auto index = custom->stickers.indexOf(doc); if (index >= 0) { custom->stickers.removeAt(index); } } } if (custom != sets.cend() && custom->stickers.isEmpty()) { sets.erase(custom); custom = sets.end(); } auto writeRecent = false; auto &recent = GetRecentPack(); for (auto i = recent.begin(); i != recent.cend();) { if (set->stickers.indexOf(i->first) >= 0 && pack.indexOf(i->first) < 0) { i = recent.erase(i); writeRecent = true; } else { ++i; } } if (pack.isEmpty()) { int removeIndex = Auth().data().stickerSetsOrder().indexOf(set->id); if (removeIndex >= 0) Auth().data().stickerSetsOrderRef().removeAt(removeIndex); sets.remove(set->id); set = nullptr; } else { set->stickers = pack; set->emoji.clear(); auto &v = d.vpacks.v; for (auto i = 0, l = v.size(); i != l; ++i) { if (v[i].type() != mtpc_stickerPack) continue; auto &pack = v[i].c_stickerPack(); if (auto emoji = Ui::Emoji::Find(qs(pack.vemoticon))) { emoji = emoji->original(); auto &stickers = pack.vdocuments.v; Pack p; p.reserve(stickers.size()); for (auto j = 0, c = stickers.size(); j != c; ++j) { auto doc = Auth().data().document(stickers[j].v); if (!doc || !doc->sticker()) continue; p.push_back(doc); } set->emoji.insert(emoji, p); } } } if (writeRecent) { Local::writeUserSettings(); } if (set) { if (set->flags & MTPDstickerSet::Flag::f_installed_date) { if (!(set->flags & MTPDstickerSet::Flag::f_archived)) { Local::writeInstalledStickers(); } } if (set->flags & MTPDstickerSet_ClientFlag::f_featured) { Local::writeFeaturedStickers(); } } Auth().data().notifyStickersUpdated(); return set; } QString GetSetTitle(const MTPDstickerSet &s) { auto title = qs(s.vtitle); if ((s.vflags.v & MTPDstickerSet::Flag::f_official) && !title.compare(qstr("Great Minds"), Qt::CaseInsensitive)) { return lang(lng_stickers_default_set); } return title; } RecentStickerPack &GetRecentPack() { if (cRecentStickers().isEmpty() && !cRecentStickersPreload().isEmpty()) { const auto p = cRecentStickersPreload(); cSetRecentStickersPreload(RecentStickerPreload()); auto &recent = cRefRecentStickers(); recent.reserve(p.size()); for (const auto &preloaded : p) { const auto document = Auth().data().document(preloaded.first); if (!document || !document->sticker()) continue; recent.push_back(qMakePair(document, preloaded.second)); } } return cRefRecentStickers(); } void IncrementRecentHashtag(RecentHashtagPack &recent, const QString &tag) { auto i = recent.begin(), e = recent.end(); for (; i != e; ++i) { if (i->first == tag) { ++i->second; if (qAbs(i->second) > 0x4000) { for (auto j = recent.begin(); j != e; ++j) { if (j->second > 1) { j->second /= 2; } else if (j->second > 0) { j->second = 1; } } } for (; i != recent.begin(); --i) { if (qAbs((i - 1)->second) > qAbs(i->second)) { break; } qSwap(*i, *(i - 1)); } break; } } if (i == e) { while (recent.size() >= 64) recent.pop_back(); recent.push_back(qMakePair(tag, 1)); for (i = recent.end() - 1; i != recent.begin(); --i) { if ((i - 1)->second > i->second) { break; } qSwap(*i, *(i - 1)); } } } } // namespace Stickers �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/chat_helpers/stickers.h����������������������������������������0000664�0000000�0000000�00000006307�13262451251�0024072�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "mtproto/sender.h" namespace Stickers { constexpr auto DefaultSetId = 0; // for backward compatibility constexpr auto CustomSetId = 0xFFFFFFFFFFFFFFFFULL; constexpr auto RecentSetId = 0xFFFFFFFFFFFFFFFEULL; // for emoji/stickers panel, should not appear in Sets constexpr auto NoneSetId = 0xFFFFFFFFFFFFFFFDULL; // for emoji/stickers panel, should not appear in Sets constexpr auto CloudRecentSetId = 0xFFFFFFFFFFFFFFFCULL; // for cloud-stored recent stickers constexpr auto FeaturedSetId = 0xFFFFFFFFFFFFFFFBULL; // for emoji/stickers panel, should not appear in Sets constexpr auto FavedSetId = 0xFFFFFFFFFFFFFFFAULL; // for cloud-stored faved stickers constexpr auto MegagroupSetId = 0xFFFFFFFFFFFFFFEFULL; // for setting up megagroup sticker set using Order = QList<uint64>; using SavedGifs = QVector<DocumentData*>; using Pack = QVector<DocumentData*>; using ByEmojiMap = QMap<EmojiPtr, Pack>; struct Set { Set( uint64 id, uint64 access, const QString &title, const QString &shortName, int count, int32 hash, MTPDstickerSet::Flags flags, TimeId installDate) : id(id) , access(access) , title(title) , shortName(shortName) , count(count) , hash(hash) , flags(flags) , installDate(installDate) { } uint64 id = 0; uint64 access = 0; QString title, shortName; int count = 0; int32 hash = 0; MTPDstickerSet::Flags flags; TimeId installDate = 0; Pack stickers; std::vector<TimeId> dates; Pack covers; ByEmojiMap emoji; }; using Sets = QMap<uint64, Set>; inline MTPInputStickerSet inputSetId(const Set &set) { if (set.id && set.access) { return MTP_inputStickerSetID(MTP_long(set.id), MTP_long(set.access)); } return MTP_inputStickerSetShortName(MTP_string(set.shortName)); } void ApplyArchivedResult(const MTPDmessages_stickerSetInstallResultArchive &d); bool ApplyArchivedResultFake(); // For testing. void InstallLocally(uint64 setId); void UndoInstallLocally(uint64 setId); bool IsFaved(not_null<DocumentData*> document); void SetFaved(not_null<DocumentData*> document, bool faved); void SetsReceived(const QVector<MTPStickerSet> &data, int32 hash); void SpecialSetReceived( uint64 setId, const QString &setTitle, const QVector<MTPDocument> &items, int32 hash, const QVector<MTPStickerPack> &packs = QVector<MTPStickerPack>(), const QVector<MTPint> &usageDates = QVector<MTPint>()); void FeaturedSetsReceived( const QVector<MTPStickerSetCovered> &data, const QVector<MTPlong> &unread, int32 hash); void GifsReceived(const QVector<MTPDocument> &items, int32 hash); std::vector<not_null<DocumentData*>> GetListByEmoji( not_null<EmojiPtr> emoji, uint64 seed); base::optional<std::vector<not_null<EmojiPtr>>> GetEmojiListFromSet( not_null<DocumentData*> document); Set *FeedSet(const MTPDstickerSet &data); Set *FeedSetFull(const MTPmessages_StickerSet &data); QString GetSetTitle(const MTPDstickerSet &s); RecentStickerPack &GetRecentPack(); void IncrementRecentHashtag(RecentHashtagPack &recent, const QString &tag); } // namespace Stickers �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp��������������������������0000664�0000000�0000000�00000217123�13262451251�0027023�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "chat_helpers/stickers_list_widget.h" #include "data/data_document.h" #include "data/data_session.h" #include "ui/widgets/buttons.h" #include "ui/effects/ripple_animation.h" #include "boxes/stickers_box.h" #include "inline_bots/inline_bot_result.h" #include "chat_helpers/stickers.h" #include "storage/localstorage.h" #include "lang/lang_keys.h" #include "mainwindow.h" #include "dialogs/dialogs_layout.h" #include "boxes/sticker_set_box.h" #include "boxes/stickers_box.h" #include "boxes/confirm_box.h" #include "auth_session.h" #include "observer_peer.h" #include "apiwrap.h" #include "styles/style_chat_helpers.h" #include "styles/style_window.h" namespace ChatHelpers { namespace { constexpr auto kInlineItemsMaxPerRow = 5; constexpr auto kSearchRequestDelay = 400; constexpr auto kRecentDisplayLimit = 20; bool SetInMyList(MTPDstickerSet::Flags flags) { return (flags & MTPDstickerSet::Flag::f_installed_date) && !(flags & MTPDstickerSet::Flag::f_archived); } } // namespace struct StickerIcon { StickerIcon(uint64 setId) : setId(setId) { } StickerIcon(uint64 setId, DocumentData *sticker, int pixw, int pixh) : setId(setId) , sticker(sticker) , pixw(pixw) , pixh(pixh) { } uint64 setId = 0; DocumentData *sticker = nullptr; ChannelData *megagroup = nullptr; int pixw = 0; int pixh = 0; }; class StickersListWidget::Footer : public TabbedSelector::InnerFooter, private base::Subscriber { public: Footer(not_null<StickersListWidget*> parent); void preloadImages(); void validateSelectedIcon( uint64 setId, ValidateIconAnimations animations); void refreshIcons(ValidateIconAnimations animations); bool hasOnlyFeaturedSets() const; void leaveToChildEvent(QEvent *e, QWidget *child) override; void stealFocus(); void returnFocus(); void setLoading(bool loading); protected: void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; bool event(QEvent *e) override; void processHideFinished() override; private: enum class SpecialOver { None, Search, Settings, }; using OverState = base::variant<SpecialOver, int>; template <typename Callback> void enumerateVisibleIcons(Callback callback); void step_icons(TimeMs ms, bool timer); void setSelectedIcon( int newSelected, ValidateIconAnimations animations); void refreshIconsGeometry(ValidateIconAnimations animations); void updateSelected(); void finishDragging(); void paintStickerSettingsIcon(Painter &p) const; void paintSearchIcon(Painter &p) const; void paintFeaturedStickerSetsBadge(Painter &p, int iconLeft) const; void paintSetIcon(Painter &p, const StickerIcon &icon, int x) const; void paintSelectionBar(Painter &p) const; void paintLeftRightFading(Painter &p) const; void initSearch(); void toggleSearch(bool visible); void resizeSearchControls(); void scrollByWheelEvent(not_null<QWheelEvent*> e); not_null<StickersListWidget*> _pan; static constexpr auto kVisibleIconsCount = 8; QList<StickerIcon> _icons; OverState _iconOver = SpecialOver::None; int _iconSel = 0; OverState _iconDown = SpecialOver::None; bool _iconsDragging = false; BasicAnimation _a_icons; QPoint _iconsMousePos, _iconsMouseDown; int _iconsLeft = 0; int _iconsRight = 0; int _iconsTop = 0; int _iconsStartX = 0; int _iconsMax = 0; anim::value _iconsX; anim::value _iconSelX; TimeMs _iconsStartAnim = 0; bool _horizontal = false; bool _searchShown = false; object_ptr<Ui::InputField> _searchField = { nullptr }; object_ptr<Ui::CrossButton> _searchCancel = { nullptr }; QPointer<QWidget> _focusTakenFrom; }; StickersListWidget::Set::Set( uint64 id, MTPDstickerSet::Flags flags, const QString &title, const QString &shortName, bool externalLayout, int count, const Stickers::Pack &pack) : id(id) , flags(flags) , title(title) , shortName(shortName) , pack(pack) , externalLayout(externalLayout) , count(count) { } StickersListWidget::Set::Set(Set &&other) = default; StickersListWidget::Set &StickersListWidget::Set::operator=( Set &&other) = default; StickersListWidget::Set::~Set() = default; StickersListWidget::Footer::Footer(not_null<StickersListWidget*> parent) : InnerFooter(parent) , _pan(parent) , _a_icons(animation(this, &Footer::step_icons)) { setMouseTracking(true); _iconsLeft = _iconsRight = st::emojiCategorySkip + st::stickerIconWidth; subscribe(Auth().downloaderTaskFinished(), [this] { update(); }); } void StickersListWidget::Footer::initSearch() { _searchField.create( this, st::gifsSearchField, langFactory(lng_stickers_search_sets)); _searchCancel.create(this, st::gifsSearchCancel); _searchField->show(); _searchCancel->show(anim::type::instant); const auto cancelSearch = [=] { if (_searchField->getLastText().isEmpty()) { toggleSearch(false); } else { _searchField->setText(QString()); } }; connect(_searchField, &Ui::InputField::submitted, this, [this](bool ctrlShiftEnter) { _pan->sendSearchRequest(); }); connect(_searchField, &Ui::InputField::cancelled, this, cancelSearch); connect(_searchField, &Ui::InputField::changed, this, [this] { _pan->searchForSets(_searchField->getLastText()); }); _searchCancel->setClickedCallback(cancelSearch); resizeSearchControls(); } void StickersListWidget::Footer::toggleSearch(bool visible) { if (_searchShown == visible) { return; } _searchShown = visible; if (_searchShown) { initSearch(); stealFocus(); } else if (_searchField) { returnFocus(); _searchField.destroy(); _searchCancel.destroy(); _focusTakenFrom = nullptr; } update(); } void StickersListWidget::Footer::stealFocus() { if (_searchField) { if (!_focusTakenFrom) { _focusTakenFrom = QApplication::focusWidget(); } _searchField->setFocus(); } } void StickersListWidget::Footer::returnFocus() { if (_searchField && _focusTakenFrom) { if (_searchField->hasFocus()) { _focusTakenFrom->setFocus(); } _focusTakenFrom = nullptr; } } template <typename Callback> void StickersListWidget::Footer::enumerateVisibleIcons(Callback callback) { auto iconsX = qRound(_iconsX.current()); auto index = iconsX / st::stickerIconWidth; auto x = _iconsLeft - (iconsX % st::stickerIconWidth); auto first = floorclamp(iconsX, st::stickerIconWidth, 0, _icons.size()); auto last = ceilclamp(iconsX + width(), st::stickerIconWidth, 0, _icons.size()); for (auto index = first; index != last; ++index) { callback(_icons[index], x); x += st::stickerIconWidth; } } void StickersListWidget::Footer::preloadImages() { enumerateVisibleIcons([](const StickerIcon &icon, int x) { if (auto sticker = icon.sticker) { sticker->thumb->load(); } }); } void StickersListWidget::Footer::validateSelectedIcon( uint64 setId, ValidateIconAnimations animations) { auto newSelected = 0; for (auto i = 0, l = _icons.size(); i != l; ++i) { if (_icons[i].setId == setId) { newSelected = i; break; } } setSelectedIcon(newSelected, animations); } void StickersListWidget::Footer::setSelectedIcon( int newSelected, ValidateIconAnimations animations) { if (_iconSel == newSelected) { return; } _iconSel = newSelected; auto iconSelXFinal = _iconSel * st::stickerIconWidth; if (animations == ValidateIconAnimations::Full) { _iconSelX.start(iconSelXFinal); } else { _iconSelX = anim::value(iconSelXFinal, iconSelXFinal); } auto iconsCountForCentering = (2 * _iconSel + 1); auto iconsWidthForCentering = iconsCountForCentering * st::stickerIconWidth; auto iconsXFinal = snap( (_iconsLeft + iconsWidthForCentering + _iconsRight - width()) / 2, 0, _iconsMax); if (animations == ValidateIconAnimations::None) { _iconsX = anim::value(iconsXFinal, iconsXFinal); _a_icons.stop(); } else { _iconsX.start(iconsXFinal); _iconsStartAnim = getms(); _a_icons.start(); } updateSelected(); update(); } void StickersListWidget::Footer::processHideFinished() { _iconOver = _iconDown = SpecialOver::None; _iconsStartAnim = 0; _a_icons.stop(); _iconsX.finish(); _iconSelX.finish(); _horizontal = false; } void StickersListWidget::Footer::leaveToChildEvent(QEvent *e, QWidget *child) { _iconsMousePos = QCursor::pos(); updateSelected(); } void StickersListWidget::Footer::setLoading(bool loading) { if (_searchCancel) { _searchCancel->setLoadingAnimation(loading); } } void StickersListWidget::Footer::paintEvent(QPaintEvent *e) { Painter p(this); paintSearchIcon(p); if (_icons.isEmpty() || _searchShown) { return; } if (!hasOnlyFeaturedSets()) { paintStickerSettingsIcon(p); } auto clip = QRect( _iconsLeft, _iconsTop, width() - _iconsLeft - _iconsRight, st::emojiFooterHeight); if (rtl()) { clip.moveLeft(width() - _iconsLeft - clip.width()); } p.setClipRect(clip); enumerateVisibleIcons([&](const StickerIcon &icon, int x) { paintSetIcon(p, icon, x); }); paintSelectionBar(p); paintLeftRightFading(p); } void StickersListWidget::Footer::paintSelectionBar(Painter &p) const { auto selxrel = _iconsLeft + qRound(_iconSelX.current()); auto selx = selxrel - qRound(_iconsX.current()); if (rtl()) { selx = width() - selx - st::stickerIconWidth; } p.fillRect( selx, _iconsTop + st::emojiFooterHeight - st::stickerIconPadding, st::stickerIconWidth, st::stickerIconSel, st::stickerIconSelColor); } void StickersListWidget::Footer::paintLeftRightFading(Painter &p) const { auto o_left = snap(_iconsX.current() / st::stickerIconLeft.width(), 0., 1.); if (o_left > 0) { p.setOpacity(o_left); st::stickerIconLeft.fill(p, rtlrect(_iconsLeft, _iconsTop, st::stickerIconLeft.width(), st::emojiFooterHeight, width())); p.setOpacity(1.); } auto o_right = snap((_iconsMax - _iconsX.current()) / st::stickerIconRight.width(), 0., 1.); if (o_right > 0) { p.setOpacity(o_right); st::stickerIconRight.fill(p, rtlrect(width() - _iconsRight - st::stickerIconRight.width(), _iconsTop, st::stickerIconRight.width(), st::emojiFooterHeight, width())); p.setOpacity(1.); } } void StickersListWidget::Footer::resizeEvent(QResizeEvent *e) { if (_searchField) { resizeSearchControls(); } refreshIconsGeometry(ValidateIconAnimations::None); } void StickersListWidget::Footer::resizeSearchControls() { Expects(_searchField != nullptr); Expects(_searchCancel != nullptr); const auto fieldWidth = width() - st::gifsSearchFieldPosition.x() - st::gifsSearchCancelPosition.x() - st::gifsSearchCancel.width; _searchField->resizeToWidth(fieldWidth); _searchField->moveToLeft(st::gifsSearchFieldPosition.x(), st::gifsSearchFieldPosition.y()); _searchCancel->moveToRight(st::gifsSearchCancelPosition.x(), st::gifsSearchCancelPosition.y()); } void StickersListWidget::Footer::mousePressEvent(QMouseEvent *e) { if (e->button() != Qt::LeftButton) { return; } _iconsMousePos = e ? e->globalPos() : QCursor::pos(); updateSelected(); if (_iconOver == SpecialOver::Settings) { Ui::show(Box<StickersBox>(hasOnlyFeaturedSets() ? StickersBox::Section::Featured : StickersBox::Section::Installed)); } else if (_iconOver == SpecialOver::Search) { toggleSearch(true); } else { _iconDown = _iconOver; _iconsMouseDown = _iconsMousePos; _iconsStartX = qRound(_iconsX.current()); } } void StickersListWidget::Footer::mouseMoveEvent(QMouseEvent *e) { _iconsMousePos = e ? e->globalPos() : QCursor::pos(); updateSelected(); if (!_iconsDragging && !_icons.isEmpty() && base::get_if<int>(&_iconDown) != nullptr) { if ((_iconsMousePos - _iconsMouseDown).manhattanLength() >= QApplication::startDragDistance()) { _iconsDragging = true; } } if (_iconsDragging) { auto newX = snap(_iconsStartX + (rtl() ? -1 : 1) * (_iconsMouseDown.x() - _iconsMousePos.x()), 0, _iconsMax); if (newX != qRound(_iconsX.current())) { _iconsX = anim::value(newX, newX); _iconsStartAnim = 0; _a_icons.stop(); update(); } } } void StickersListWidget::Footer::mouseReleaseEvent(QMouseEvent *e) { if (_icons.isEmpty()) { return; } const auto wasDown = std::exchange(_iconDown, SpecialOver::None); _iconsMousePos = e ? e->globalPos() : QCursor::pos(); if (_iconsDragging) { finishDragging(); return; } updateSelected(); if (wasDown == _iconOver) { if (const auto index = base::get_if<int>(&_iconOver)) { _iconSelX = anim::value( *index * st::stickerIconWidth, *index * st::stickerIconWidth); _pan->showStickerSet(_icons[*index].setId); } } } void StickersListWidget::Footer::finishDragging() { auto newX = snap(_iconsStartX + _iconsMouseDown.x() - _iconsMousePos.x(), 0, _iconsMax); if (newX != qRound(_iconsX.current())) { _iconsX = anim::value(newX, newX); _iconsStartAnim = 0; _a_icons.stop(); update(); } _iconsDragging = false; updateSelected(); } bool StickersListWidget::Footer::event(QEvent *e) { if (e->type() == QEvent::TouchBegin) { } else if (e->type() == QEvent::Wheel) { if (!_icons.isEmpty() && (base::get_if<int>(&_iconOver) != nullptr) && (_iconDown == SpecialOver::None)) { scrollByWheelEvent(static_cast<QWheelEvent*>(e)); } } return InnerFooter::event(e); } void StickersListWidget::Footer::scrollByWheelEvent( not_null<QWheelEvent*> e) { auto horizontal = (e->angleDelta().x() != 0 || e->orientation() == Qt::Horizontal); auto vertical = (e->angleDelta().y() != 0 || e->orientation() == Qt::Vertical); if (horizontal) { _horizontal = true; } auto newX = qRound(_iconsX.current()); if (/*_horizontal && */horizontal) { newX = snap(newX - (rtl() ? -1 : 1) * (e->pixelDelta().x() ? e->pixelDelta().x() : e->angleDelta().x()), 0, _iconsMax); } else if (/*!_horizontal && */vertical) { newX = snap(newX - (e->pixelDelta().y() ? e->pixelDelta().y() : e->angleDelta().y()), 0, _iconsMax); } if (newX != qRound(_iconsX.current())) { _iconsX = anim::value(newX, newX); _iconsStartAnim = 0; _a_icons.stop(); updateSelected(); update(); } } void StickersListWidget::Footer::updateSelected() { if (_iconDown >= 0) { return; } auto p = mapFromGlobal(_iconsMousePos); auto x = p.x(), y = p.y(); if (rtl()) x = width() - x; const auto settingsLeft = width() - _iconsRight; const auto searchLeft = _iconsLeft - st::stickerIconWidth; auto newOver = OverState(SpecialOver::None); if (x >= searchLeft && x < searchLeft + st::stickerIconWidth && y >= _iconsTop && y < _iconsTop + st::emojiFooterHeight) { newOver = SpecialOver::Search; } else if (x >= settingsLeft && x < settingsLeft + st::stickerIconWidth && y >= _iconsTop && y < _iconsTop + st::emojiFooterHeight) { if (!_icons.isEmpty() && !hasOnlyFeaturedSets()) { newOver = SpecialOver::Settings; } } else if (!_icons.isEmpty()) { if (y >= _iconsTop && y < _iconsTop + st::emojiFooterHeight && x >= _iconsLeft && x < width() - _iconsRight) { x += qRound(_iconsX.current()) - _iconsLeft; if (x < _icons.size() * st::stickerIconWidth) { newOver = qFloor(x / st::stickerIconWidth); } } } if (newOver != _iconOver) { if (newOver == SpecialOver::None) { setCursor(style::cur_default); } else if (_iconOver == SpecialOver::None) { setCursor(style::cur_pointer); } _iconOver = newOver; } } void StickersListWidget::Footer::refreshIcons( ValidateIconAnimations animations) { _pan->fillIcons(_icons); refreshIconsGeometry(animations); } void StickersListWidget::Footer::refreshIconsGeometry( ValidateIconAnimations animations) { _iconOver = _iconDown = SpecialOver::None; _iconsX.finish(); _iconSelX.finish(); _iconsStartAnim = 0; _a_icons.stop(); _iconsMax = std::max( _iconsLeft + int(_icons.size()) * st::stickerIconWidth + _iconsRight - width(), 0); if (_iconsX.current() > _iconsMax) { _iconsX = anim::value(_iconsMax, _iconsMax); } updateSelected(); _pan->validateSelectedIcon(animations); update(); } bool StickersListWidget::Footer::hasOnlyFeaturedSets() const { return (_icons.size() == 1) && (_icons[0].setId == Stickers::FeaturedSetId); } void StickersListWidget::Footer::paintStickerSettingsIcon(Painter &p) const { const auto settingsLeft = width() - _iconsRight; st::stickersSettings.paint(p, settingsLeft + (st::stickerIconWidth - st::stickersSettings.width()) / 2, _iconsTop + st::emojiCategory.iconPosition.y(), width()); } void StickersListWidget::Footer::paintSearchIcon(Painter &p) const { const auto searchLeft = _iconsLeft - st::stickerIconWidth; st::stickersSearch.paint(p, searchLeft + (st::stickerIconWidth - st::stickersSearch.width()) / 2, _iconsTop + st::emojiCategory.iconPosition.y(), width()); } void StickersListWidget::Footer::paintFeaturedStickerSetsBadge(Painter &p, int iconLeft) const { if (auto unread = Auth().data().featuredStickerSetsUnreadCount()) { Dialogs::Layout::UnreadBadgeStyle unreadSt; unreadSt.sizeId = Dialogs::Layout::UnreadBadgeInStickersPanel; unreadSt.size = st::stickersSettingsUnreadSize; int unreadRight = iconLeft + st::stickerIconWidth - st::stickersSettingsUnreadPosition.x(); if (rtl()) unreadRight = width() - unreadRight; int unreadTop = _iconsTop + st::stickersSettingsUnreadPosition.y(); Dialogs::Layout::paintUnreadCount(p, QString::number(unread), unreadRight, unreadTop, unreadSt); } } void StickersListWidget::Footer::paintSetIcon( Painter &p, const StickerIcon &icon, int x) const { if (icon.sticker) { icon.sticker->thumb->load(); auto pix = icon.sticker->thumb->pix(icon.pixw, icon.pixh); p.drawPixmapLeft(x + (st::stickerIconWidth - icon.pixw) / 2, _iconsTop + (st::emojiFooterHeight - icon.pixh) / 2, width(), pix); } else if (icon.megagroup) { icon.megagroup->paintUserpicLeft(p, x + (st::stickerIconWidth - st::stickerGroupCategorySize) / 2, _iconsTop + (st::emojiFooterHeight - st::stickerGroupCategorySize) / 2, width(), st::stickerGroupCategorySize); } else { auto getSpecialSetIcon = [](uint64 setId) { if (setId == Stickers::FeaturedSetId) { return &st::stickersTrending; //} else if (setId == Stickers::FavedSetId) { // return &st::stickersFaved; } return &st::emojiRecent; }; auto paintedIcon = getSpecialSetIcon(icon.setId); paintedIcon->paint(p, x + (st::stickerIconWidth - paintedIcon->width()) / 2, _iconsTop + st::emojiCategory.iconPosition.y(), width()); if (icon.setId == Stickers::FeaturedSetId) { paintFeaturedStickerSetsBadge(p, x); } } } void StickersListWidget::Footer::step_icons(TimeMs ms, bool timer) { if (_iconsStartAnim) { auto dt = (ms - _iconsStartAnim) / float64(st::stickerIconMove); if (dt >= 1) { _iconsStartAnim = 0; _iconsX.finish(); _iconSelX.finish(); } else { _iconsX.update(dt, anim::linear); _iconSelX.update(dt, anim::linear); } } if (timer) update(); if (!_iconsStartAnim) { _a_icons.stop(); } } StickersListWidget::StickersListWidget(QWidget *parent, not_null<Window::Controller*> controller) : Inner(parent, controller) , _section(Section::Stickers) , _megagroupSetAbout(st::columnMinimalWidthThird - st::emojiScroll.width - st::emojiPanHeaderLeft) , _addText(lang(lng_stickers_featured_add).toUpper()) , _addWidth(st::stickersTrendingAdd.font->width(_addText)) , _settings(this, lang(lng_stickers_you_have)) , _searchRequestTimer([=] { sendSearchRequest(); }) { setMouseTracking(true); setAttribute(Qt::WA_OpaquePaintEvent); connect(_settings, SIGNAL(clicked()), this, SLOT(onSettings())); _previewTimer.setSingleShot(true); connect(&_previewTimer, SIGNAL(timeout()), this, SLOT(onPreview())); subscribe(Auth().downloaderTaskFinished(), [this] { update(); readVisibleSets(); }); subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(Notify::PeerUpdate::Flag::ChannelStickersChanged, [this](const Notify::PeerUpdate &update) { if (update.peer == _megagroupSet) { refreshStickers(); } })); } object_ptr<TabbedSelector::InnerFooter> StickersListWidget::createFooter() { Expects(_footer == nullptr); auto result = object_ptr<Footer>(this); _footer = result; return std::move(result); } void StickersListWidget::visibleTopBottomUpdated( int visibleTop, int visibleBottom) { auto top = getVisibleTop(); Inner::visibleTopBottomUpdated(visibleTop, visibleBottom); if (_section == Section::Featured) { readVisibleSets(); } validateSelectedIcon(ValidateIconAnimations::Full); } void StickersListWidget::readVisibleSets() { auto itemsVisibleTop = getVisibleTop(); auto itemsVisibleBottom = getVisibleBottom(); auto rowHeight = featuredRowHeight(); int rowFrom = floorclamp(itemsVisibleTop, rowHeight, 0, _featuredSets.size()); int rowTo = ceilclamp(itemsVisibleBottom, rowHeight, 0, _featuredSets.size()); for (int i = rowFrom; i < rowTo; ++i) { auto &set = _featuredSets[i]; if (!(set.flags & MTPDstickerSet_ClientFlag::f_unread)) { continue; } if (i * rowHeight < itemsVisibleTop || (i + 1) * rowHeight > itemsVisibleBottom) { continue; } int count = qMin(set.pack.size(), _columnCount); int loaded = 0; for (int j = 0; j < count; ++j) { if (set.pack[j]->thumb->loaded() || set.pack[j]->loaded()) { ++loaded; } } if (loaded == count) { Auth().api().readFeaturedSetDelayed(set.id); } } } int StickersListWidget::featuredRowHeight() const { return st::stickersTrendingHeader + _singleSize.height() + st::stickersTrendingSkip; } template <typename Callback> bool StickersListWidget::enumerateSections(Callback callback) const { auto info = SectionInfo(); const auto &sets = shownSets(); for (auto i = 0; i != sets.size(); ++i) { auto &set = sets[i]; info.section = i; info.count = set.pack.size(); const auto titleSkip = set.externalLayout ? st::stickersTrendingHeader : setHasTitle(set) ? st::emojiPanHeader : st::stickerPanPadding; info.rowsTop = info.top + titleSkip; if (set.externalLayout) { info.rowsCount = 1; info.rowsBottom = info.top + featuredRowHeight(); } else if (set.id == Stickers::MegagroupSetId && !info.count) { info.rowsCount = 0; info.rowsBottom = info.rowsTop + _megagroupSetButtonRect.y() + _megagroupSetButtonRect.height() + st::stickerGroupCategoryAddMargin.bottom(); } else { info.rowsCount = (info.count / _columnCount) + ((info.count % _columnCount) ? 1 : 0); info.rowsBottom = info.rowsTop + info.rowsCount * _singleSize.height(); } if (!callback(info)) { return false; } info.top = info.rowsBottom; } return true; } StickersListWidget::SectionInfo StickersListWidget::sectionInfo(int section) const { Expects(section >= 0 && section < shownSets().size()); auto result = SectionInfo(); enumerateSections([searchForSection = section, &result](const SectionInfo &info) { if (info.section == searchForSection) { result = info; return false; } return true; }); return result; } StickersListWidget::SectionInfo StickersListWidget::sectionInfoByOffset(int yOffset) const { auto result = SectionInfo(); enumerateSections([this, &result, yOffset](const SectionInfo &info) { if (yOffset < info.rowsBottom || info.section == shownSets().size() - 1) { result = info; return false; } return true; }); return result; } int StickersListWidget::countDesiredHeight(int newWidth) { if (newWidth <= st::stickerPanWidthMin) { return 0; } auto availableWidth = newWidth - (st::stickerPanPadding - st::buttonRadius); auto columnCount = availableWidth / st::stickerPanWidthMin; auto singleWidth = availableWidth / columnCount; auto fullWidth = (st::buttonRadius + newWidth + st::emojiScroll.width); auto rowsRight = (fullWidth - columnCount * singleWidth) / 2; accumulate_max(rowsRight, st::emojiScroll.width); _rowsLeft = fullWidth - columnCount * singleWidth - rowsRight - st::buttonRadius; _singleSize = QSize(singleWidth, singleWidth); setColumnCount(columnCount); auto visibleHeight = minimalHeight(); auto minimalHeight = (visibleHeight - st::stickerPanPadding); auto countResult = [this](int minimalLastHeight) { const auto &sets = shownSets(); if (sets.empty()) { return 0; } const auto info = sectionInfo(sets.size() - 1); return info.top + qMax(info.rowsBottom - info.top, minimalLastHeight); }; const auto minimalLastHeight = (_section == Section::Stickers) ? minimalHeight : 0; return qMax(minimalHeight, countResult(minimalLastHeight)) + st::stickerPanPadding; } void StickersListWidget::installedLocally(uint64 setId) { _installedLocallySets.insert(setId); } void StickersListWidget::notInstalledLocally(uint64 setId) { _installedLocallySets.remove(setId); } void StickersListWidget::clearInstalledLocally() { if (!_installedLocallySets.empty()) { _installedLocallySets.clear(); refreshStickers(); } } void StickersListWidget::sendSearchRequest() { if (_searchRequestId || _searchNextQuery.isEmpty()) { return; } _searchRequestTimer.cancel(); _searchQuery = _searchNextQuery; auto it = _searchCache.find(_searchQuery); if (it != _searchCache.cend()) { _footer->setLoading(false); return; } _footer->setLoading(true); const auto hash = int32(0); _searchRequestId = request(MTPmessages_SearchStickerSets( MTP_flags(0), MTP_string(_searchQuery), MTP_int(hash) )).done([=](const MTPmessages_FoundStickerSets &result) { searchResultsDone(result); }).fail([this](const RPCError &error) { // show error? _footer->setLoading(false); _searchRequestId = 0; }).handleAllErrors().send(); } void StickersListWidget::searchForSets(const QString &query) { const auto cleaned = query.trimmed(); if (cleaned.isEmpty()) { cancelSetsSearch(); return; } if (_searchQuery != cleaned) { _footer->setLoading(false); if (const auto requestId = base::take(_searchRequestId)) { request(requestId).cancel(); } if (_searchCache.find(cleaned) != _searchCache.cend()) { _searchRequestTimer.cancel(); _searchQuery = _searchNextQuery = cleaned; } else { _searchNextQuery = cleaned; _searchRequestTimer.callOnce(kSearchRequestDelay); } showSearchResults(); } } void StickersListWidget::cancelSetsSearch() { _footer->setLoading(false); if (const auto requestId = base::take(_searchRequestId)) { request(requestId).cancel(); } _searchRequestTimer.cancel(); _searchQuery = _searchNextQuery = QString(); _searchCache.clear(); refreshSearchRows(nullptr); } void StickersListWidget::showSearchResults() { refreshSearchRows(); scrollToY(0); } void StickersListWidget::refreshSearchRows() { auto it = _searchCache.find(_searchQuery); auto sets = (it != end(_searchCache)) ? &it->second : nullptr; refreshSearchRows(sets); } void StickersListWidget::refreshSearchRows( const std::vector<Stickers::Set*> *cloudSets) { clearSelection(); _searchSets.clear(); fillLocalSearchRows(_searchNextQuery); if (!cloudSets && _searchNextQuery.isEmpty()) { showStickerSet(!_mySets.empty() ? _mySets[0].id : Stickers::FeaturedSetId); return; } if (_section != Section::Search) { _section = Section::Search; } if (cloudSets) { fillCloudSearchRows(*cloudSets); } if (_footer) { _footer->refreshIcons(ValidateIconAnimations::Scroll); } _lastMousePosition = QCursor::pos(); resizeToWidth(width()); updateSelected(); } void StickersListWidget::fillLocalSearchRows(const QString &query) { const auto searchWordsList = TextUtilities::PrepareSearchWords(query); if (searchWordsList.isEmpty()) { return; } auto searchWordInTitle = []( const QStringList &titleWords, const QString &searchWord) { for (const auto &titleWord : titleWords) { if (titleWord.startsWith(searchWord)) { return true; } } return false; }; auto allSearchWordsInTitle = [&]( const QStringList &titleWords) { for (const auto &searchWord : searchWordsList) { if (!searchWordInTitle(titleWords, searchWord)) { return false; } } return true; }; const auto &sets = Auth().data().stickerSets(); for (const auto &[setId, titleWords] : _searchIndex) { if (allSearchWordsInTitle(titleWords)) { if (const auto it = sets.find(setId); it != sets.end()) { addSearchRow(&*it); } } } } void StickersListWidget::fillCloudSearchRows( const std::vector<Stickers::Set*> &sets) { for (const auto set : sets) { addSearchRow(set); } } void StickersListWidget::addSearchRow(not_null<const Stickers::Set*> set) { _searchSets.push_back(Set( set->id, set->flags, set->title, set->shortName, !SetInMyList(set->flags), set->count, set->stickers.empty() ? set->covers : set->stickers)); } auto StickersListWidget::shownSets() const -> const std::vector<Set> & { switch (_section) { case Section::Featured: return _featuredSets; case Section::Search: return _searchSets; case Section::Stickers: return _mySets; } Unexpected("Section in StickersListWidget."); } auto StickersListWidget::shownSets() -> std::vector<Set> & { switch (_section) { case Section::Featured: return _featuredSets; case Section::Search: return _searchSets; case Section::Stickers: return _mySets; } Unexpected("Section in StickersListWidget."); } void StickersListWidget::searchResultsDone( const MTPmessages_FoundStickerSets &result) { _footer->setLoading(false); _searchRequestId = 0; if (result.type() == mtpc_messages_foundStickerSetsNotModified) { LOG(("API Error: " "messages.foundStickerSetsNotModified not expected.")); return; } Assert(result.type() == mtpc_messages_foundStickerSets); auto it = _searchCache.find(_searchQuery); if (it == _searchCache.cend()) { it = _searchCache.emplace( _searchQuery, std::vector<Stickers::Set*>()).first; } auto &d = result.c_messages_foundStickerSets(); for_const (const auto &stickerSet, d.vsets.v) { const MTPDstickerSet *setData = nullptr; Stickers::Pack covers; switch (stickerSet.type()) { case mtpc_stickerSetCovered: { auto &d = stickerSet.c_stickerSetCovered(); if (d.vset.type() == mtpc_stickerSet) { setData = &d.vset.c_stickerSet(); } } break; case mtpc_stickerSetMultiCovered: { auto &d = stickerSet.c_stickerSetMultiCovered(); if (d.vset.type() == mtpc_stickerSet) { setData = &d.vset.c_stickerSet(); } for (const auto &cover : d.vcovers.v) { const auto document = Auth().data().document(cover); if (document->sticker()) { covers.push_back(document); } } } break; } if (!setData) continue; if (const auto set = Stickers::FeedSet(*setData)) { if (!covers.empty()) { set->covers = covers; } if (set->stickers.empty() && set->covers.empty()) { continue; } it->second.push_back(set); } } showSearchResults(); } int StickersListWidget::stickersLeft() const { return _rowsLeft; } QRect StickersListWidget::stickerRect(int section, int sel) { auto info = sectionInfo(section); if (sel >= shownSets()[section].pack.size()) { sel -= shownSets()[section].pack.size(); } auto countTillItem = (sel - (sel % _columnCount)); auto rowsToSkip = (countTillItem / _columnCount) + ((countTillItem % _columnCount) ? 1 : 0); auto x = stickersLeft() + ((sel % _columnCount) * _singleSize.width()); auto y = info.rowsTop + rowsToSkip * _singleSize.height(); return QRect(QPoint(x, y), _singleSize); } void StickersListWidget::paintEvent(QPaintEvent *e) { Painter p(this); auto clip = e->rect(); p.fillRect(clip, st::emojiPanBg); paintStickers(p, clip); } void StickersListWidget::paintStickers(Painter &p, QRect clip) { auto fromColumn = floorclamp(clip.x() - stickersLeft(), _singleSize.width(), 0, _columnCount); auto toColumn = ceilclamp(clip.x() + clip.width() - stickersLeft(), _singleSize.width(), 0, _columnCount); if (rtl()) { qSwap(fromColumn, toColumn); fromColumn = _columnCount - fromColumn; toColumn = _columnCount - toColumn; } auto ms = getms(); auto &sets = shownSets(); auto selectedSticker = base::get_if<OverSticker>(&_selected); auto selectedButton = base::get_if<OverButton>(_pressed ? &_pressed : &_selected); if (sets.empty() && _section == Section::Search) { paintEmptySearchResults(p); } enumerateSections([&](const SectionInfo &info) { if (clip.top() >= info.rowsBottom) { return true; } else if (clip.top() + clip.height() <= info.top) { return false; } auto &set = sets[info.section]; if (set.externalLayout) { const auto size = (set.flags & MTPDstickerSet_ClientFlag::f_not_loaded) ? set.count : int(set.pack.size()); auto widthForTitle = stickersRight() - (st::emojiPanHeaderLeft - st::buttonRadius); if (featuredHasAddButton(info.section)) { auto add = featuredAddRect(info.section); auto selected = selectedButton ? (selectedButton->section == info.section) : false; auto &textBg = selected ? st::stickersTrendingAdd.textBgOver : st::stickersTrendingAdd.textBg; App::roundRect(p, myrtlrect(add), textBg, ImageRoundRadius::Small); if (set.ripple) { set.ripple->paint(p, add.x(), add.y(), width(), ms); if (set.ripple->empty()) { set.ripple.reset(); } } p.setFont(st::stickersTrendingAdd.font); p.setPen(selected ? st::stickersTrendingAdd.textFgOver : st::stickersTrendingAdd.textFg); p.drawTextLeft(add.x() - (st::stickersTrendingAdd.width / 2), add.y() + st::stickersTrendingAdd.textTop, width(), _addText, _addWidth); widthForTitle -= add.width() - (st::stickersTrendingAdd.width / 2); } else { auto add = featuredAddRect(info.section); int checkx = add.left() + (add.width() - st::stickersFeaturedInstalled.width()) / 2; int checky = add.top() + (add.height() - st::stickersFeaturedInstalled.height()) / 2; st::stickersFeaturedInstalled.paint(p, QPoint(checkx, checky), width()); } if (set.flags & MTPDstickerSet_ClientFlag::f_unread) { widthForTitle -= st::stickersFeaturedUnreadSize + st::stickersFeaturedUnreadSkip; } auto titleText = set.title; auto titleWidth = st::stickersTrendingHeaderFont->width(titleText); if (titleWidth > widthForTitle) { titleText = st::stickersTrendingHeaderFont->elided(titleText, widthForTitle); titleWidth = st::stickersTrendingHeaderFont->width(titleText); } p.setFont(st::stickersTrendingHeaderFont); p.setPen(st::stickersTrendingHeaderFg); p.drawTextLeft(st::emojiPanHeaderLeft - st::buttonRadius, info.top + st::stickersTrendingHeaderTop, width(), titleText, titleWidth); if (set.flags & MTPDstickerSet_ClientFlag::f_unread) { p.setPen(Qt::NoPen); p.setBrush(st::stickersFeaturedUnreadBg); { PainterHighQualityEnabler hq(p); p.drawEllipse(rtlrect(st::emojiPanHeaderLeft - st::buttonRadius + titleWidth + st::stickersFeaturedUnreadSkip, info.top + st::stickersTrendingHeaderTop + st::stickersFeaturedUnreadTop, st::stickersFeaturedUnreadSize, st::stickersFeaturedUnreadSize, width())); } } auto statusText = (size > 0) ? lng_stickers_count(lt_count, size) : lang(lng_contacts_loading); p.setFont(st::stickersTrendingSubheaderFont); p.setPen(st::stickersTrendingSubheaderFg); p.drawTextLeft(st::emojiPanHeaderLeft - st::buttonRadius, info.top + st::stickersTrendingSubheaderTop, width(), statusText); if (info.rowsTop >= clip.y() + clip.height()) { return true; } for (int j = fromColumn; j < toColumn; ++j) { int index = j; if (index >= size) break; auto selected = selectedSticker ? (selectedSticker->section == info.section && selectedSticker->index == index) : false; auto deleteSelected = false; paintSticker(p, set, info.rowsTop, index, selected, deleteSelected); } return true; } if (setHasTitle(set) && clip.top() < info.rowsTop) { auto titleText = set.title; auto titleWidth = st::stickersTrendingHeaderFont->width(titleText); auto widthForTitle = stickersRight() - (st::emojiPanHeaderLeft - st::buttonRadius); if (hasRemoveButton(info.section)) { auto remove = removeButtonRect(info.section); auto selected = selectedButton ? (selectedButton->section == info.section) : false; if (set.ripple) { set.ripple->paint(p, remove.x() + st::stickerPanRemoveSet.rippleAreaPosition.x(), remove.y() + st::stickerPanRemoveSet.rippleAreaPosition.y(), width(), ms); if (set.ripple->empty()) { set.ripple.reset(); } } (selected ? st::stickerPanRemoveSet.iconOver : st::stickerPanRemoveSet.icon).paint(p, remove.topLeft() + st::stickerPanRemoveSet.iconPosition, width()); widthForTitle -= remove.width(); } if (titleWidth > widthForTitle) { titleText = st::stickersTrendingHeaderFont->elided(titleText, widthForTitle); titleWidth = st::stickersTrendingHeaderFont->width(titleText); } p.setFont(st::emojiPanHeaderFont); p.setPen(st::emojiPanHeaderFg); p.drawTextLeft(st::emojiPanHeaderLeft - st::buttonRadius, info.top + st::emojiPanHeaderTop, width(), titleText, titleWidth); } if (clip.top() + clip.height() > info.rowsTop) { if (set.id == Stickers::MegagroupSetId && set.pack.empty()) { auto buttonSelected = (base::get_if<OverGroupAdd>(&_selected) != nullptr); paintMegagroupEmptySet(p, info.rowsTop, buttonSelected, ms); } else { auto special = (set.flags & MTPDstickerSet::Flag::f_official) != 0; auto fromRow = floorclamp(clip.y() - info.rowsTop, _singleSize.height(), 0, info.rowsCount); auto toRow = ceilclamp(clip.y() + clip.height() - info.rowsTop, _singleSize.height(), 0, info.rowsCount); for (int i = fromRow; i < toRow; ++i) { for (int j = fromColumn; j < toColumn; ++j) { int index = i * _columnCount + j; if (index >= info.count) break; auto selected = selectedSticker ? (selectedSticker->section == info.section && selectedSticker->index == index) : false; auto deleteSelected = selected && selectedSticker->overDelete; paintSticker(p, set, info.rowsTop, index, selected, deleteSelected); } } } } return true; }); } void StickersListWidget::paintEmptySearchResults(Painter &p) { const auto iconLeft = (width() - st::stickersEmpty.width()) / 2; const auto iconTop = (height() / 3) - (st::stickersEmpty.height() / 2); st::stickersEmpty.paint(p, iconLeft, iconTop, width()); const auto text = lang(lng_stickers_nothing_found); const auto textWidth = st::normalFont->width(text); p.setFont(st::normalFont); p.setPen(st::windowSubTextFg); p.drawTextLeft( (width() - textWidth) / 2, iconTop + st::stickersEmpty.height() - st::normalFont->height, width(), text, textWidth); } int StickersListWidget::megagroupSetInfoLeft() const { return st::emojiPanHeaderLeft - st::buttonRadius; } void StickersListWidget::paintMegagroupEmptySet(Painter &p, int y, bool buttonSelected, TimeMs ms) { auto infoLeft = megagroupSetInfoLeft(); _megagroupSetAbout.drawLeft(p, infoLeft, y, width() - infoLeft, width()); auto &textBg = buttonSelected ? st::stickerGroupCategoryAdd.textBgOver : st::stickerGroupCategoryAdd.textBg; auto button = _megagroupSetButtonRect.translated(0, y); App::roundRect(p, myrtlrect(button), textBg, ImageRoundRadius::Small); if (_megagroupSetButtonRipple) { _megagroupSetButtonRipple->paint(p, button.x(), button.y(), width(), ms); if (_megagroupSetButtonRipple->empty()) { _megagroupSetButtonRipple.reset(); } } p.setFont(st::stickerGroupCategoryAdd.font); p.setPen(buttonSelected ? st::stickerGroupCategoryAdd.textFgOver : st::stickerGroupCategoryAdd.textFg); p.drawTextLeft(button.x() - (st::stickerGroupCategoryAdd.width / 2), button.y() + st::stickerGroupCategoryAdd.textTop, width(), _megagroupSetButtonText, _megagroupSetButtonTextWidth); } void StickersListWidget::paintSticker(Painter &p, Set &set, int y, int index, bool selected, bool deleteSelected) { auto sticker = set.pack[index]; if (!sticker->sticker()) return; int row = (index / _columnCount), col = (index % _columnCount); auto pos = QPoint(stickersLeft() + col * _singleSize.width(), y + row * _singleSize.height()); if (selected) { auto tl = pos; if (rtl()) tl.setX(width() - tl.x() - _singleSize.width()); App::roundRect(p, QRect(tl, _singleSize), st::emojiPanHover, StickerHoverCorners); } auto goodThumb = !sticker->thumb->isNull() && ((sticker->thumb->width() >= 128) || (sticker->thumb->height() >= 128)); if (goodThumb) { sticker->thumb->load(); } else { sticker->checkSticker(); } auto coef = qMin((_singleSize.width() - st::buttonRadius * 2) / float64(sticker->dimensions.width()), (_singleSize.height() - st::buttonRadius * 2) / float64(sticker->dimensions.height())); if (coef > 1) coef = 1; auto w = qMax(qRound(coef * sticker->dimensions.width()), 1); auto h = qMax(qRound(coef * sticker->dimensions.height()), 1); auto ppos = pos + QPoint((_singleSize.width() - w) / 2, (_singleSize.height() - h) / 2); auto paintImage = [&](ImagePtr image) { if (image->loaded()) { p.drawPixmapLeft( ppos, width(), image->pixSingle(w, h, w, h, ImageRoundRadius::None)); } }; if (goodThumb) { paintImage(sticker->thumb); } else if (!sticker->sticker()->img->isNull()) { paintImage(sticker->sticker()->img); } if (selected && stickerHasDeleteButton(set, index)) { auto xPos = pos + QPoint(_singleSize.width() - st::stickerPanDeleteIconBg.width(), 0); p.setOpacity(deleteSelected ? st::stickerPanDeleteOpacityBgOver : st::stickerPanDeleteOpacityBg); st::stickerPanDeleteIconBg.paint(p, xPos, width()); p.setOpacity(deleteSelected ? st::stickerPanDeleteOpacityFgOver : st::stickerPanDeleteOpacityFg); st::stickerPanDeleteIconFg.paint(p, xPos, width()); p.setOpacity(1.); } } int StickersListWidget::stickersRight() const { return stickersLeft() + (_columnCount * _singleSize.width()); } bool StickersListWidget::featuredHasAddButton(int index) const { if (index < 0 || index >= shownSets().size() || !shownSets()[index].externalLayout) { return false; } const auto flags = shownSets()[index].flags; return !SetInMyList(flags); } QRect StickersListWidget::featuredAddRect(int index) const { auto addw = _addWidth - st::stickersTrendingAdd.width; auto addh = st::stickersTrendingAdd.height; auto addx = stickersRight() - addw; auto addy = sectionInfo(index).top + st::stickersTrendingAddTop; return QRect(addx, addy, addw, addh); } bool StickersListWidget::hasRemoveButton(int index) const { if (index < 0 || index >= shownSets().size()) { return false; } auto &set = shownSets()[index]; if (set.externalLayout) { return false; } auto flags = set.flags; if (!(flags & MTPDstickerSet_ClientFlag::f_special)) { return true; } if (set.id == Stickers::MegagroupSetId) { Assert(_megagroupSet != nullptr); if (index + 1 != shownSets().size()) { return true; } return !set.pack.empty() && _megagroupSet->canEditStickers(); } return false; } QRect StickersListWidget::removeButtonRect(int index) const { auto buttonw = st::stickerPanRemoveSet.width; auto buttonh = st::stickerPanRemoveSet.height; auto buttonx = stickersRight() - buttonw; auto buttony = sectionInfo(index).top + (st::emojiPanHeader - buttonh) / 2; return QRect(buttonx, buttony, buttonw, buttonh); } void StickersListWidget::mousePressEvent(QMouseEvent *e) { if (e->button() != Qt::LeftButton) { return; } _lastMousePosition = e->globalPos(); updateSelected(); setPressed(_selected); ClickHandler::pressed(); _previewTimer.start(QApplication::startDragTime()); } void StickersListWidget::setPressed(OverState newPressed) { if (auto button = base::get_if<OverButton>(&_pressed)) { auto &sets = shownSets(); Assert(button->section >= 0 && button->section < sets.size()); auto &set = sets[button->section]; if (set.ripple) { set.ripple->lastStop(); } } else if (base::get_if<OverGroupAdd>(&_pressed)) { if (_megagroupSetButtonRipple) { _megagroupSetButtonRipple->lastStop(); } } _pressed = newPressed; if (auto button = base::get_if<OverButton>(&_pressed)) { auto &sets = shownSets(); Assert(button->section >= 0 && button->section < sets.size()); auto &set = sets[button->section]; if (!set.ripple) { set.ripple = createButtonRipple(button->section); } set.ripple->add(mapFromGlobal(QCursor::pos()) - buttonRippleTopLeft(button->section)); } else if (base::get_if<OverGroupAdd>(&_pressed)) { if (!_megagroupSetButtonRipple) { auto maskSize = _megagroupSetButtonRect.size(); auto mask = Ui::RippleAnimation::roundRectMask(maskSize, st::buttonRadius); _megagroupSetButtonRipple = std::make_unique<Ui::RippleAnimation>(st::stickerGroupCategoryAdd.ripple, std::move(mask), [this] { rtlupdate(megagroupSetButtonRectFinal()); }); } _megagroupSetButtonRipple->add(mapFromGlobal(QCursor::pos()) - myrtlrect(megagroupSetButtonRectFinal()).topLeft()); } } QRect StickersListWidget::megagroupSetButtonRectFinal() const { auto result = QRect(); if (_section == Section::Stickers) { enumerateSections([this, &result](const SectionInfo &info) { if (shownSets()[info.section].id == Stickers::MegagroupSetId) { result = _megagroupSetButtonRect.translated(0, info.rowsTop); return false; } return true; }); } return result; } std::unique_ptr<Ui::RippleAnimation> StickersListWidget::createButtonRipple(int section) { Expects(section >= 0 && section < shownSets().size()); if (shownSets()[section].externalLayout) { auto maskSize = QSize(_addWidth - st::stickersTrendingAdd.width, st::stickersTrendingAdd.height); auto mask = Ui::RippleAnimation::roundRectMask(maskSize, st::buttonRadius); return std::make_unique<Ui::RippleAnimation>( st::stickersTrendingAdd.ripple, std::move(mask), [this, section] { rtlupdate(featuredAddRect(section)); }); } auto maskSize = QSize(st::stickerPanRemoveSet.rippleAreaSize, st::stickerPanRemoveSet.rippleAreaSize); auto mask = Ui::RippleAnimation::ellipseMask(maskSize); return std::make_unique<Ui::RippleAnimation>( st::stickerPanRemoveSet.ripple, std::move(mask), [this, section] { rtlupdate(removeButtonRect(section)); }); } QPoint StickersListWidget::buttonRippleTopLeft(int section) const { Expects(section >= 0 && section < shownSets().size()); if (shownSets()[section].externalLayout) { return myrtlrect(featuredAddRect(section)).topLeft(); } return myrtlrect(removeButtonRect(section)).topLeft() + st::stickerPanRemoveSet.rippleAreaPosition; } void StickersListWidget::mouseReleaseEvent(QMouseEvent *e) { _previewTimer.stop(); auto pressed = _pressed; setPressed(base::none); if (pressed != _selected) { update(); } auto activated = ClickHandler::unpressed(); if (_previewShown) { _previewShown = false; return; } _lastMousePosition = e->globalPos(); updateSelected(); auto &sets = shownSets(); if (pressed && pressed == _selected) { if (auto sticker = base::get_if<OverSticker>(&pressed)) { Assert(sticker->section >= 0 && sticker->section < sets.size()); auto &set = sets[sticker->section]; Assert(sticker->index >= 0 && sticker->index < set.pack.size()); if (stickerHasDeleteButton(set, sticker->index) && sticker->overDelete) { if (set.id == Stickers::RecentSetId) { removeRecentSticker(sticker->section, sticker->index); } else if (set.id == Stickers::FavedSetId) { removeFavedSticker(sticker->section, sticker->index); } else { Unexpected("Single sticker delete click."); } return; } emit selected(set.pack[sticker->index]); } else if (auto set = base::get_if<OverSet>(&pressed)) { Assert(set->section >= 0 && set->section < sets.size()); displaySet(sets[set->section].id); } else if (auto button = base::get_if<OverButton>(&pressed)) { Assert(button->section >= 0 && button->section < sets.size()); if (sets[button->section].externalLayout) { installSet(sets[button->section].id); } else if (sets[button->section].id == Stickers::MegagroupSetId) { auto removeLocally = sets[button->section].pack.empty() || !_megagroupSet->canEditStickers(); removeMegagroupSet(removeLocally); } else { removeSet(sets[button->section].id); } } else if (base::get_if<OverGroupAdd>(&pressed)) { Ui::show(Box<StickersBox>(_megagroupSet)); } } } void StickersListWidget::validateSelectedIcon( ValidateIconAnimations animations) { if (_footer) { _footer->validateSelectedIcon( currentSet(getVisibleTop()), animations); } } void StickersListWidget::removeRecentSticker(int section, int index) { if ((_section != Section::Stickers) || (section >= int(_mySets.size())) || (_mySets[section].id != Stickers::RecentSetId)) { return; } clearSelection(); bool refresh = false; auto sticker = _mySets[section].pack[index]; auto &recent = Stickers::GetRecentPack(); for (int32 i = 0, l = recent.size(); i < l; ++i) { if (recent.at(i).first == sticker) { recent.removeAt(i); Local::writeUserSettings(); refresh = true; break; } } auto &sets = Auth().data().stickerSetsRef(); auto it = sets.find(Stickers::CustomSetId); if (it != sets.cend()) { for (int i = 0, l = it->stickers.size(); i < l; ++i) { if (it->stickers.at(i) == sticker) { it->stickers.removeAt(i); if (it->stickers.isEmpty()) { sets.erase(it); } Local::writeInstalledStickers(); refresh = true; break; } } } if (refresh) { refreshRecentStickers(); updateSelected(); update(); } } void StickersListWidget::removeFavedSticker(int section, int index) { if ((_section != Section::Stickers) || (section >= int(_mySets.size())) || (_mySets[section].id != Stickers::FavedSetId)) { return; } clearSelection(); auto sticker = _mySets[section].pack[index]; Stickers::SetFaved(sticker, false); auto unfave = true; MTP::send(MTPmessages_FaveSticker(sticker->mtpInput(), MTP_bool(unfave))); } void StickersListWidget::setColumnCount(int count) { Expects(count > 0); if (_columnCount != count) { _columnCount = count; refreshFooterIcons(); } } void StickersListWidget::mouseMoveEvent(QMouseEvent *e) { _lastMousePosition = e->globalPos(); updateSelected(); } void StickersListWidget::resizeEvent(QResizeEvent *e) { _settings->moveToLeft( (width() - _settings->width()) / 2, height() / 3); if (!_megagroupSetAbout.isEmpty()) { refreshMegagroupSetGeometry(); } } void StickersListWidget::leaveEventHook(QEvent *e) { clearSelection(); } void StickersListWidget::leaveToChildEvent(QEvent *e, QWidget *child) { clearSelection(); } void StickersListWidget::enterFromChildEvent(QEvent *e, QWidget *child) { _lastMousePosition = QCursor::pos(); updateSelected(); } void StickersListWidget::clearSelection() { setPressed(base::none); setSelected(base::none); update(); } TabbedSelector::InnerFooter *StickersListWidget::getFooter() const { return _footer; } void StickersListWidget::processHideFinished() { clearSelection(); } void StickersListWidget::processPanelHideFinished() { clearInstalledLocally(); // Preserve panel state through visibility toggles. //// Reset to the recent stickers section. //if (_section == Section::Featured && (!_footer || !_footer->hasOnlyFeaturedSets())) { // _section = Section::Stickers; // validateSelectedIcon(ValidateIconAnimations::None); //} } void StickersListWidget::refreshStickers() { clearSelection(); _mySets.clear(); _favedStickersMap.clear(); _mySets.reserve(Auth().data().stickerSetsOrder().size() + 3); refreshFavedStickers(); refreshRecentStickers(false); refreshMegagroupStickers(GroupStickersPlace::Visible); for_const (auto setId, Auth().data().stickerSetsOrder()) { const auto externalLayout = false; appendSet(_mySets, setId, externalLayout, AppendSkip::Archived); } refreshMegagroupStickers(GroupStickersPlace::Hidden); _featuredSets.clear(); _featuredSets.reserve(Auth().data().featuredStickerSetsOrder().size()); for_const (auto setId, Auth().data().featuredStickerSetsOrder()) { const auto externalLayout = true; appendSet(_featuredSets, setId, externalLayout, AppendSkip::Installed); } refreshSearchSets(); resizeToWidth(width()); if (_footer) { refreshFooterIcons(); } refreshSettingsVisibility(); _lastMousePosition = QCursor::pos(); updateSelected(); update(); } void StickersListWidget::refreshSearchSets() { refreshSearchIndex(); const auto &sets = Auth().data().stickerSets(); for (auto &set : _searchSets) { if (const auto it = sets.find(set.id); it != sets.end()) { set.flags = it->flags; if (!it->stickers.empty()) { set.pack = it->stickers; } if (!SetInMyList(set.flags)) { _installedLocallySets.remove(set.id); set.externalLayout = true; } } } } void StickersListWidget::refreshSearchIndex() { _searchIndex.clear(); for (const auto &set : _mySets) { if (set.flags & MTPDstickerSet_ClientFlag::f_special) { continue; } const auto string = set.title + ' ' + set.shortName; const auto list = TextUtilities::PrepareSearchWords(string); _searchIndex.emplace_back(set.id, list); } } void StickersListWidget::refreshSettingsVisibility() { const auto visible = (_section == Section::Stickers) && _mySets.empty(); _settings->setVisible(visible); } void StickersListWidget::refreshFooterIcons() { _footer->refreshIcons(ValidateIconAnimations::None); if (_footer->hasOnlyFeaturedSets() && _section != Section::Featured) { showStickerSet(Stickers::FeaturedSetId); } } void StickersListWidget::preloadImages() { auto &sets = shownSets(); for (int i = 0, l = sets.size(), k = 0; i < l; ++i) { int count = sets[i].pack.size(); if (sets[i].externalLayout) { accumulate_min(count, _columnCount); } for (int j = 0; j != count; ++j) { if (++k > _columnCount * (_columnCount + 1)) break; auto sticker = sets[i].pack.at(j); if (!sticker || !sticker->sticker()) continue; bool goodThumb = !sticker->thumb->isNull() && ((sticker->thumb->width() >= 128) || (sticker->thumb->height() >= 128)); if (goodThumb) { sticker->thumb->load(); } else { sticker->automaticLoad(0); } } if (k > _columnCount * (_columnCount + 1)) break; } if (_footer) { _footer->preloadImages(); } } uint64 StickersListWidget::currentSet(int yOffset) const { if (_section == Section::Featured) { return Stickers::FeaturedSetId; } const auto &sets = shownSets(); return sets.empty() ? Stickers::RecentSetId : sets[sectionInfoByOffset(yOffset).section].id; } void StickersListWidget::appendSet( std::vector<Set> &to, uint64 setId, bool externalLayout, AppendSkip skip) { auto &sets = Auth().data().stickerSets(); auto it = sets.constFind(setId); if (it == sets.cend() || it->stickers.isEmpty()) { return; } if ((skip == AppendSkip::Archived) && (it->flags & MTPDstickerSet::Flag::f_archived)) { return; } if ((skip == AppendSkip::Installed) && (it->flags & MTPDstickerSet::Flag::f_installed_date) && !(it->flags & MTPDstickerSet::Flag::f_archived)) { if (!_installedLocallySets.contains(setId)) { return; } } to.push_back(Set( it->id, it->flags, it->title, it->shortName, externalLayout, it->count, it->stickers)); } void StickersListWidget::refreshRecent() { if (_section == Section::Stickers) { refreshRecentStickers(); } if (_footer && _footer->hasOnlyFeaturedSets() && _section != Section::Featured) { showStickerSet(Stickers::FeaturedSetId); } } Stickers::Pack StickersListWidget::collectRecentStickers() { _custom.clear(); auto result = Stickers::Pack(); auto &sets = Auth().data().stickerSets(); auto &recent = Stickers::GetRecentPack(); auto customIt = sets.constFind(Stickers::CustomSetId); auto cloudIt = sets.constFind(Stickers::CloudRecentSetId); const auto customCount = (customIt != sets.cend()) ? customIt->stickers.size() : 0; const auto cloudCount = (cloudIt != sets.cend()) ? cloudIt->stickers.size() : 0; result.reserve(cloudCount + recent.size() + customCount); _custom.reserve(cloudCount + recent.size() + customCount); auto add = [&](not_null<DocumentData*> document, bool custom) { if (result.size() >= kRecentDisplayLimit) { return; } const auto index = result.indexOf(document); if (index >= 0) { if (index >= cloudCount && custom) { // Mark stickers from local recent as custom. _custom[index] = true; } } else if (!_favedStickersMap.contains(document)) { result.push_back(document); _custom.push_back(custom); } }; if (cloudCount > 0) { for_const (auto document, cloudIt->stickers) { add(document, false); } } for_const (auto &recentSticker, recent) { add(recentSticker.first, false); } if (customCount > 0) { for_const (auto document, customIt->stickers) { add(document, true); } } return result; } void StickersListWidget::refreshRecentStickers(bool performResize) { clearSelection(); auto recentPack = collectRecentStickers(); auto recentIt = std::find_if(_mySets.begin(), _mySets.end(), [](auto &set) { return set.id == Stickers::RecentSetId; }); if (!recentPack.empty()) { if (recentIt == _mySets.end()) { const auto shortName = QString(); const auto externalLayout = false; _mySets.push_back(Set( Stickers::RecentSetId, (MTPDstickerSet::Flag::f_official | MTPDstickerSet_ClientFlag::f_special), lang(lng_recent_stickers), shortName, externalLayout, recentPack.size(), recentPack)); } else { recentIt->pack = recentPack; } } else if (recentIt != _mySets.end()) { _mySets.erase(recentIt); } if (performResize && (_section == Section::Stickers || _section == Section::Featured)) { resizeToWidth(width()); updateSelected(); } } void StickersListWidget::refreshFavedStickers() { clearSelection(); auto &sets = Auth().data().stickerSets(); auto it = sets.constFind(Stickers::FavedSetId); if (it == sets.cend() || it->stickers.isEmpty()) { return; } const auto externalLayout = false; const auto shortName = QString(); _mySets.push_back(Set( Stickers::FavedSetId, (MTPDstickerSet::Flag::f_official | MTPDstickerSet_ClientFlag::f_special), Lang::Hard::FavedSetTitle(), shortName, externalLayout, it->count, it->stickers)); _favedStickersMap = base::flat_set<not_null<DocumentData*>> { it->stickers.begin(), it->stickers.end() }; } void StickersListWidget::refreshMegagroupStickers(GroupStickersPlace place) { if (!_megagroupSet) { return; } auto canEdit = _megagroupSet->canEditStickers(); auto isShownHere = [place](bool hidden) { return (hidden == (place == GroupStickersPlace::Hidden)); }; if (_megagroupSet->mgInfo->stickerSet.type() == mtpc_inputStickerSetEmpty) { if (canEdit) { auto hidden = Auth().settings().isGroupStickersSectionHidden(_megagroupSet->id); if (isShownHere(hidden)) { const auto shortName = QString(); const auto externalLayout = false; const auto count = 0; _mySets.push_back(Set( Stickers::MegagroupSetId, MTPDstickerSet_ClientFlag::f_special | 0, lang(lng_group_stickers), QString(), externalLayout, count)); } } return; } auto hidden = Auth().settings().isGroupStickersSectionHidden(_megagroupSet->id); auto removeHiddenForGroup = [this, &hidden] { if (hidden) { Auth().settings().removeGroupStickersSectionHidden(_megagroupSet->id); Local::writeUserSettings(); hidden = false; } }; if (canEdit && hidden) { removeHiddenForGroup(); } if (_megagroupSet->mgInfo->stickerSet.type() == mtpc_inputStickerSetID) { auto &set = _megagroupSet->mgInfo->stickerSet.c_inputStickerSetID(); auto &sets = Auth().data().stickerSets(); auto it = sets.constFind(set.vid.v); if (it != sets.cend()) { auto isInstalled = (it->flags & MTPDstickerSet::Flag::f_installed_date) && !(it->flags & MTPDstickerSet::Flag::f_archived); if (isInstalled && !canEdit) { removeHiddenForGroup(); } else if (isShownHere(hidden)) { const auto shortName = QString(); const auto externalLayout = false; _mySets.push_back(Set( Stickers::MegagroupSetId, MTPDstickerSet_ClientFlag::f_special | 0, lang(lng_group_stickers), shortName, externalLayout, it->count, it->stickers)); } return; } } if (!isShownHere(hidden)) { return; } request(MTPmessages_GetStickerSet(_megagroupSet->mgInfo->stickerSet)).done([this](const MTPmessages_StickerSet &result) { if (auto set = Stickers::FeedSetFull(result)) { refreshStickers(); } }).send(); } void StickersListWidget::fillIcons(QList<StickerIcon> &icons) { icons.clear(); icons.reserve(_mySets.size() + 1); if (Auth().data().featuredStickerSetsUnreadCount() && !_featuredSets.empty()) { icons.push_back(StickerIcon(Stickers::FeaturedSetId)); } auto i = 0; if (i != _mySets.size() && _mySets[i].id == Stickers::FavedSetId) { ++i; icons.push_back(StickerIcon(Stickers::FavedSetId)); } if (i != _mySets.size() && _mySets[i].id == Stickers::RecentSetId) { ++i; if (!icons.empty() && icons.back().setId != Stickers::FavedSetId) { icons.push_back(StickerIcon(Stickers::RecentSetId)); } } for (auto l = _mySets.size(); i != l; ++i) { if (_mySets[i].id == Stickers::MegagroupSetId) { icons.push_back(StickerIcon(Stickers::MegagroupSetId)); icons.back().megagroup = _megagroupSet; continue; } auto s = _mySets[i].pack[0]; auto availw = st::stickerIconWidth - 2 * st::stickerIconPadding, availh = st::emojiFooterHeight - 2 * st::stickerIconPadding; auto thumbw = s->thumb->width(), thumbh = s->thumb->height(), pixw = 1, pixh = 1; if (availw * thumbh > availh * thumbw) { pixh = availh; pixw = (pixh * thumbw) / thumbh; } else { pixw = availw; pixh = thumbw ? ((pixw * thumbh) / thumbw) : 1; } if (pixw < 1) pixw = 1; if (pixh < 1) pixh = 1; icons.push_back(StickerIcon(_mySets[i].id, s, pixw, pixh)); } if (!Auth().data().featuredStickerSetsUnreadCount() && !_featuredSets.empty()) { icons.push_back(StickerIcon(Stickers::FeaturedSetId)); } } bool StickersListWidget::preventAutoHide() { return _removingSetId != 0 || _displayingSetId != 0; } void StickersListWidget::updateSelected() { if (_pressed && !_previewShown) { return; } auto newSelected = OverState { base::none }; auto p = mapFromGlobal(_lastMousePosition); if (!rect().contains(p) || p.y() < getVisibleTop() || p.y() >= getVisibleBottom() || !isVisible()) { clearSelection(); return; } auto &sets = shownSets(); auto sx = (rtl() ? width() - p.x() : p.x()) - stickersLeft(); if (!shownSets().empty()) { auto info = sectionInfoByOffset(p.y()); auto section = info.section; if (p.y() >= info.top && p.y() < info.rowsTop) { if (hasRemoveButton(section) && myrtlrect(removeButtonRect(section)).contains(p.x(), p.y())) { newSelected = OverButton { section }; } else if (featuredHasAddButton(section) && myrtlrect(featuredAddRect(section)).contains(p.x(), p.y())) { newSelected = OverButton{ section }; } else if (!(sets[section].flags & MTPDstickerSet_ClientFlag::f_special)) { newSelected = OverSet { section }; } else if (sets[section].id == Stickers::MegagroupSetId && (_megagroupSet->canEditStickers() || !sets[section].pack.empty())) { newSelected = OverSet { section }; } } else if (p.y() >= info.rowsTop && p.y() < info.rowsBottom && sx >= 0) { auto yOffset = p.y() - info.rowsTop; auto &set = sets[section]; if (set.id == Stickers::MegagroupSetId && set.pack.empty()) { if (_megagroupSetButtonRect.contains(stickersLeft() + sx, yOffset)) { newSelected = OverGroupAdd {}; } } else { auto special = ((set.flags & MTPDstickerSet::Flag::f_official) != 0); auto rowIndex = qFloor(yOffset / _singleSize.height()); auto columnIndex = qFloor(sx / _singleSize.width()); auto index = rowIndex * _columnCount + columnIndex; if (index >= 0 && index < set.pack.size()) { auto overDelete = false; if (stickerHasDeleteButton(set, index)) { auto inx = sx - (columnIndex * _singleSize.width()); auto iny = yOffset - (rowIndex * _singleSize.height()); if (inx >= _singleSize.width() - st::stickerPanDeleteIconBg.width() && iny < st::stickerPanDeleteIconBg.height()) { overDelete = true; } } newSelected = OverSticker { section, index, overDelete }; } } } } setSelected(newSelected); } bool StickersListWidget::setHasTitle(const Set &set) const { if (set.id == Stickers::FavedSetId) { return false; } else if (set.id == Stickers::RecentSetId) { return !_mySets.empty() && _mySets[0].id == Stickers::FavedSetId; } return true; } bool StickersListWidget::stickerHasDeleteButton(const Set &set, int index) const { if (set.id == Stickers::RecentSetId) { Assert(index >= 0 && index < _custom.size()); return _custom[index]; } return (set.id == Stickers::FavedSetId); } void StickersListWidget::setSelected(OverState newSelected) { if (_selected != newSelected) { setCursor(newSelected ? style::cur_pointer : style::cur_default); auto &sets = shownSets(); auto updateSelected = [&]() { if (auto sticker = base::get_if<OverSticker>(&_selected)) { rtlupdate(stickerRect(sticker->section, sticker->index)); } else if (auto button = base::get_if<OverButton>(&_selected)) { if (button->section >= 0 && button->section < sets.size() && sets[button->section].externalLayout) { rtlupdate(featuredAddRect(button->section)); } else { rtlupdate(removeButtonRect(button->section)); } } else if (base::get_if<OverGroupAdd>(&_selected)) { rtlupdate(megagroupSetButtonRectFinal()); } }; updateSelected(); _selected = newSelected; updateSelected(); if (_previewShown && _pressed != _selected) { if (auto sticker = base::get_if<OverSticker>(&_selected)) { _pressed = _selected; Assert(sticker->section >= 0 && sticker->section < sets.size()); auto &set = sets[sticker->section]; Assert(sticker->index >= 0 && sticker->index < set.pack.size()); Ui::showMediaPreview(set.pack[sticker->index]); } } } } void StickersListWidget::onSettings() { Ui::show(Box<StickersBox>(StickersBox::Section::Installed)); } void StickersListWidget::onPreview() { if (auto sticker = base::get_if<OverSticker>(&_pressed)) { auto &sets = shownSets(); Assert(sticker->section >= 0 && sticker->section < sets.size()); auto &set = sets[sticker->section]; Assert(sticker->index >= 0 && sticker->index < set.pack.size()); Ui::showMediaPreview(set.pack[sticker->index]); _previewShown = true; } } void StickersListWidget::showStickerSet(uint64 setId) { clearSelection(); if (setId == Stickers::FeaturedSetId) { if (_section != Section::Featured) { _section = Section::Featured; refreshRecentStickers(true); refreshSettingsVisibility(); if (_footer) { _footer->refreshIcons(ValidateIconAnimations::Scroll); } update(); } emit scrollToY(0); emit scrollUpdated(); return; } auto needRefresh = (_section != Section::Stickers); if (needRefresh) { _section = Section::Stickers; refreshRecentStickers(true); refreshSettingsVisibility(); } auto y = 0; enumerateSections([this, setId, &y](const SectionInfo &info) { if (shownSets()[info.section].id == setId) { y = info.top; return false; } return true; }); emit scrollToY(y); emit scrollUpdated(); if (needRefresh && _footer) { _footer->refreshIcons(ValidateIconAnimations::Scroll); } _lastMousePosition = QCursor::pos(); update(); } void StickersListWidget::refreshMegagroupSetGeometry() { auto left = megagroupSetInfoLeft(); auto availableWidth = (width() - left); auto top = _megagroupSetAbout.countHeight(availableWidth) + st::stickerGroupCategoryAddMargin.top(); _megagroupSetButtonTextWidth = st::stickerGroupCategoryAdd.font->width(_megagroupSetButtonText); auto buttonWidth = _megagroupSetButtonTextWidth - st::stickerGroupCategoryAdd.width; _megagroupSetButtonRect = QRect(left, top, buttonWidth, st::stickerGroupCategoryAdd.height); } void StickersListWidget::showMegagroupSet(ChannelData *megagroup) { Expects(!megagroup || megagroup->isMegagroup()); if (_megagroupSet != megagroup) { _megagroupSet = megagroup; if (_megagroupSetAbout.isEmpty()) { _megagroupSetAbout.setText( st::stickerGroupCategoryAbout, lang(lng_group_stickers_description)); _megagroupSetButtonText = lang(lng_group_stickers_add).toUpper(); refreshMegagroupSetGeometry(); } _megagroupSetButtonRipple.reset(); refreshStickers(); } } void StickersListWidget::afterShown() { if (_footer) { _footer->stealFocus(); } } void StickersListWidget::beforeHiding() { if (_footer) { _footer->returnFocus(); } } void StickersListWidget::displaySet(uint64 setId) { if (setId == Stickers::MegagroupSetId) { if (_megagroupSet->canEditStickers()) { _displayingSetId = setId; auto box = Ui::show(Box<StickersBox>(_megagroupSet)); connect(box, &QObject::destroyed, this, [this] { _displayingSetId = 0; emit checkForHide(); }); return; } else if (_megagroupSet->mgInfo->stickerSet.type() == mtpc_inputStickerSetID) { setId = _megagroupSet->mgInfo->stickerSet.c_inputStickerSetID().vid.v; } else { return; } } auto &sets = Auth().data().stickerSets(); auto it = sets.constFind(setId); if (it != sets.cend()) { _displayingSetId = setId; auto box = Ui::show( Box<StickerSetBox>(Stickers::inputSetId(*it)), LayerOption::KeepOther); connect(box, &QObject::destroyed, this, [this] { _displayingSetId = 0; emit checkForHide(); }); } } void StickersListWidget::installSet(uint64 setId) { auto &sets = Auth().data().stickerSets(); auto it = sets.constFind(setId); if (it != sets.cend()) { const auto input = Stickers::inputSetId(*it); if ((it->flags & MTPDstickerSet_ClientFlag::f_not_loaded) || it->stickers.empty()) { request(MTPmessages_GetStickerSet( input )).done([=](const MTPmessages_StickerSet &result) { Stickers::FeedSetFull(result); sendInstallRequest(setId, input); }).send(); } else { sendInstallRequest(setId, input); } } } void StickersListWidget::sendInstallRequest( uint64 setId, const MTPInputStickerSet &input) { request(MTPmessages_InstallStickerSet( input, MTP_bool(false) )).done([=](const MTPmessages_StickerSetInstallResult &result) { if (result.type() == mtpc_messages_stickerSetInstallResultArchive) { Stickers::ApplyArchivedResult(result.c_messages_stickerSetInstallResultArchive()); } }).fail([=](const RPCError &error) { notInstalledLocally(setId); Stickers::UndoInstallLocally(setId); }).send(); installedLocally(setId); Stickers::InstallLocally(setId); } void StickersListWidget::removeMegagroupSet(bool locally) { if (locally) { Auth().settings().setGroupStickersSectionHidden(_megagroupSet->id); Local::writeUserSettings(); refreshStickers(); return; } _removingSetId = Stickers::MegagroupSetId; Ui::show(Box<ConfirmBox>(lang(lng_stickers_remove_group_set), base::lambda_guarded(this, [this, group = _megagroupSet] { Expects(group->mgInfo != nullptr); if (group->mgInfo->stickerSet.type() != mtpc_inputStickerSetEmpty) { Auth().api().setGroupStickerSet(group, MTP_inputStickerSetEmpty()); } Ui::hideLayer(); _removingSetId = 0; emit checkForHide(); }), base::lambda_guarded(this, [this] { _removingSetId = 0; emit checkForHide(); }))); } void StickersListWidget::removeSet(uint64 setId) { auto &sets = Auth().data().stickerSets(); auto it = sets.constFind(setId); if (it != sets.cend()) { _removingSetId = it->id; auto text = lng_stickers_remove_pack(lt_sticker_pack, it->title); Ui::show(Box<ConfirmBox>(text, lang(lng_stickers_remove_pack_confirm), base::lambda_guarded(this, [this] { Ui::hideLayer(); auto &sets = Auth().data().stickerSetsRef(); auto it = sets.find(_removingSetId); if (it != sets.cend()) { if (it->id && it->access) { request(MTPmessages_UninstallStickerSet(MTP_inputStickerSetID(MTP_long(it->id), MTP_long(it->access)))).send(); } else if (!it->shortName.isEmpty()) { request(MTPmessages_UninstallStickerSet(MTP_inputStickerSetShortName(MTP_string(it->shortName)))).send(); } auto writeRecent = false; auto &recent = Stickers::GetRecentPack(); for (auto i = recent.begin(); i != recent.cend();) { if (it->stickers.indexOf(i->first) >= 0) { i = recent.erase(i); writeRecent = true; } else { ++i; } } it->flags &= ~MTPDstickerSet::Flag::f_installed_date; it->installDate = TimeId(0); // // Set can be in search results. // //if (!(it->flags & MTPDstickerSet_ClientFlag::f_featured) // && !(it->flags & MTPDstickerSet_ClientFlag::f_special)) { // sets.erase(it); //} int removeIndex = Auth().data().stickerSetsOrder().indexOf(_removingSetId); if (removeIndex >= 0) Auth().data().stickerSetsOrderRef().removeAt(removeIndex); refreshStickers(); Local::writeInstalledStickers(); if (writeRecent) Local::writeUserSettings(); } _removingSetId = 0; emit checkForHide(); }), base::lambda_guarded(this, [this] { _removingSetId = 0; emit checkForHide(); }))); } } StickersListWidget::~StickersListWidget() = default; } // namespace ChatHelpers ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h����������������������������0000664�0000000�0000000�00000016666�13262451251�0026501�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "chat_helpers/tabbed_selector.h" #include "chat_helpers/stickers.h" #include "base/variant.h" #include "base/timer.h" namespace Window { class Controller; } // namespace Window namespace Ui { class LinkButton; class RippleAnimation; } // namespace Ui namespace ChatHelpers { struct StickerIcon; class StickersListWidget : public TabbedSelector::Inner , private base::Subscriber , private MTP::Sender { Q_OBJECT public: StickersListWidget( QWidget *parent, not_null<Window::Controller*> controller); void refreshRecent() override; void preloadImages() override; void clearSelection() override; object_ptr<TabbedSelector::InnerFooter> createFooter() override; void showStickerSet(uint64 setId); void showMegagroupSet(ChannelData *megagroup); void afterShown() override; void beforeHiding() override; void refreshStickers(); void fillIcons(QList<StickerIcon> &icons); bool preventAutoHide(); uint64 currentSet(int yOffset) const; void installedLocally(uint64 setId); void notInstalledLocally(uint64 setId); void clearInstalledLocally(); void sendSearchRequest(); void searchForSets(const QString &query); ~StickersListWidget(); protected: void visibleTopBottomUpdated( int visibleTop, int visibleBottom) override; void mousePressEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void resizeEvent(QResizeEvent *e) override; void paintEvent(QPaintEvent *e) override; void leaveEventHook(QEvent *e) override; void leaveToChildEvent(QEvent *e, QWidget *child) override; void enterFromChildEvent(QEvent *e, QWidget *child) override; TabbedSelector::InnerFooter *getFooter() const override; void processHideFinished() override; void processPanelHideFinished() override; int countDesiredHeight(int newWidth) override; private slots: void onSettings(); void onPreview(); signals: void selected(DocumentData *sticker); void scrollUpdated(); void checkForHide(); private: class Footer; enum class Section { Featured, Stickers, Search, }; struct OverSticker { int section; int index; bool overDelete; }; struct OverSet { int section; }; struct OverButton { int section; }; struct OverGroupAdd { }; friend inline bool operator==(OverSticker a, OverSticker b) { return (a.section == b.section) && (a.index == b.index) && (a.overDelete == b.overDelete); } friend inline bool operator==(OverSet a, OverSet b) { return (a.section == b.section); } friend inline bool operator==(OverButton a, OverButton b) { return (a.section == b.section); } friend inline bool operator==(OverGroupAdd a, OverGroupAdd b) { return true; } using OverState = base::optional_variant<OverSticker, OverSet, OverButton, OverGroupAdd>; struct SectionInfo { int section = 0; int count = 0; int top = 0; int rowsCount = 0; int rowsTop = 0; int rowsBottom = 0; }; struct Set { Set( uint64 id, MTPDstickerSet::Flags flags, const QString &title, const QString &shortName, bool externalLayout, int count, const Stickers::Pack &pack = Stickers::Pack()); Set(Set &&other); Set &operator=(Set &&other); ~Set(); uint64 id = 0; MTPDstickerSet::Flags flags = MTPDstickerSet::Flags(); QString title; QString shortName; Stickers::Pack pack; std::unique_ptr<Ui::RippleAnimation> ripple; bool externalLayout = false; int count = 0; }; template <typename Callback> bool enumerateSections(Callback callback) const; SectionInfo sectionInfo(int section) const; SectionInfo sectionInfoByOffset(int yOffset) const; void displaySet(uint64 setId); void installSet(uint64 setId); void removeMegagroupSet(bool locally); void removeSet(uint64 setId); void sendInstallRequest( uint64 setId, const MTPInputStickerSet &input); void refreshSearchSets(); void refreshSearchIndex(); bool setHasTitle(const Set &set) const; bool stickerHasDeleteButton(const Set &set, int index) const; Stickers::Pack collectRecentStickers(); void refreshRecentStickers(bool resize = true); void refreshFavedStickers(); enum class GroupStickersPlace { Visible, Hidden, }; void refreshMegagroupStickers(GroupStickersPlace place); void refreshSettingsVisibility(); void updateSelected(); void setSelected(OverState newSelected); void setPressed(OverState newPressed); std::unique_ptr<Ui::RippleAnimation> createButtonRipple(int section); QPoint buttonRippleTopLeft(int section) const; enum class ValidateIconAnimations { Full, Scroll, None, }; void validateSelectedIcon(ValidateIconAnimations animations); std::vector<Set> &shownSets(); const std::vector<Set> &shownSets() const; int featuredRowHeight() const; void readVisibleSets(); void paintFeaturedStickers(Painter &p, QRect clip); void paintStickers(Painter &p, QRect clip); void paintMegagroupEmptySet(Painter &p, int y, bool buttonSelected, TimeMs ms); void paintSticker(Painter &p, Set &set, int y, int index, bool selected, bool deleteSelected); void paintEmptySearchResults(Painter &p); int stickersRight() const; bool featuredHasAddButton(int index) const; QRect featuredAddRect(int index) const; bool hasRemoveButton(int index) const; QRect removeButtonRect(int index) const; int megagroupSetInfoLeft() const; void refreshMegagroupSetGeometry(); QRect megagroupSetButtonRectFinal() const; enum class AppendSkip { None, Archived, Installed, }; void appendSet( std::vector<Set> &to, uint64 setId, bool externalLayout, AppendSkip skip = AppendSkip::None); void selectEmoji(EmojiPtr emoji); int stickersLeft() const; QRect stickerRect(int section, int sel); void removeRecentSticker(int section, int index); void removeFavedSticker(int section, int index); void setColumnCount(int count); void refreshFooterIcons(); void cancelSetsSearch(); void showSearchResults(); void searchResultsDone(const MTPmessages_FoundStickerSets &result); void refreshSearchRows(); void refreshSearchRows(const std::vector<Stickers::Set*> *cloudSets); void fillLocalSearchRows(const QString &query); void fillCloudSearchRows(const std::vector<Stickers::Set*> &sets); void addSearchRow(not_null<const Stickers::Set*> set); ChannelData *_megagroupSet = nullptr; std::vector<Set> _mySets; std::vector<Set> _featuredSets; std::vector<Set> _searchSets; base::flat_set<uint64> _installedLocallySets; std::vector<bool> _custom; base::flat_set<not_null<DocumentData*>> _favedStickersMap; Section _section = Section::Stickers; uint64 _displayingSetId = 0; uint64 _removingSetId = 0; Footer *_footer = nullptr; int _rowsLeft = 0; int _columnCount = 1; QSize _singleSize; OverState _selected; OverState _pressed; QPoint _lastMousePosition; Text _megagroupSetAbout; QString _megagroupSetButtonText; int _megagroupSetButtonTextWidth = 0; QRect _megagroupSetButtonRect; std::unique_ptr<Ui::RippleAnimation> _megagroupSetButtonRipple; QString _addText; int _addWidth; object_ptr<Ui::LinkButton> _settings; QTimer _previewTimer; bool _previewShown = false; std::map<QString, std::vector<Stickers::Set*>> _searchCache; std::vector<std::pair<uint64, QStringList>> _searchIndex; base::Timer _searchRequestTimer; QString _searchQuery, _searchNextQuery; mtpRequestId _searchRequestId = 0; }; } // namespace ChatHelpers ��������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/chat_helpers/tabbed_panel.cpp����������������������������������0000664�0000000�0000000�00000024552�13262451251�0025200�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "chat_helpers/tabbed_panel.h" #include "ui/widgets/shadow.h" #include "styles/style_chat_helpers.h" #include "chat_helpers/tabbed_selector.h" #include "window/window_controller.h" #include "mainwindow.h" #include "messenger.h" namespace ChatHelpers { namespace { constexpr auto kHideTimeoutMs = 300; constexpr auto kDelayedHideTimeoutMs = 3000; } // namespace TabbedPanel::TabbedPanel( QWidget *parent, not_null<Window::Controller*> controller) : TabbedPanel( parent, controller, object_ptr<TabbedSelector>(nullptr, controller)) { } TabbedPanel::TabbedPanel( QWidget *parent, not_null<Window::Controller*> controller, object_ptr<TabbedSelector> selector) : RpWidget(parent) , _controller(controller) , _selector(std::move(selector)) { _selector->setParent(this); _selector->setRoundRadius(st::buttonRadius); _selector->setAfterShownCallback([this](SelectorTab tab) { if (tab == SelectorTab::Gifs) { _controller->enableGifPauseReason(Window::GifPauseReason::SavedGifs); } }); _selector->setBeforeHidingCallback([this](SelectorTab tab) { if (tab == SelectorTab::Gifs) { _controller->disableGifPauseReason(Window::GifPauseReason::SavedGifs); } }); _selector->showRequests( ) | rpl::start_with_next([this] { this->showFromSelector(); }, lifetime()); resize(QRect(0, 0, st::emojiPanWidth, st::emojiPanMaxHeight).marginsAdded(innerPadding()).size()); _contentMaxHeight = st::emojiPanMaxHeight; _contentHeight = _contentMaxHeight; _selector->resize(st::emojiPanWidth, _contentHeight); _selector->move(innerRect().topLeft()); _hideTimer.setCallback([this] { hideByTimerOrLeave(); }); connect(_selector, &TabbedSelector::checkForHide, this, [this] { if (!rect().contains(mapFromGlobal(QCursor::pos()))) { _hideTimer.callOnce(kDelayedHideTimeoutMs); } }); connect(_selector, &TabbedSelector::cancelled, this, [this] { hideAnimated(); }); connect(_selector, &TabbedSelector::slideFinished, this, [this] { InvokeQueued(this, [this] { if (_hideAfterSlide) { startOpacityAnimation(true); } }); }); if (cPlatform() == dbipMac || cPlatform() == dbipMacOld) { connect(App::wnd()->windowHandle(), SIGNAL(activeChanged()), this, SLOT(onWndActiveChanged())); } setAttribute(Qt::WA_OpaquePaintEvent, false); hideChildren(); } void TabbedPanel::moveBottom(int bottom) { _bottom = bottom; updateContentHeight(); } void TabbedPanel::updateContentHeight() { if (isDestroying()) { return; } auto addedHeight = innerPadding().top() + innerPadding().bottom(); auto marginsHeight = _selector->marginTop() + _selector->marginBottom(); auto availableHeight = _bottom - marginsHeight; auto wantedContentHeight = qRound(st::emojiPanHeightRatio * availableHeight) - addedHeight; auto contentHeight = marginsHeight + snap(wantedContentHeight, st::emojiPanMinHeight, st::emojiPanMaxHeight); auto resultTop = _bottom - addedHeight - contentHeight; if (contentHeight == _contentHeight) { move(x(), resultTop); return; } auto was = _contentHeight; _contentHeight = contentHeight; resize(QRect(0, 0, innerRect().width(), _contentHeight).marginsAdded(innerPadding()).size()); move(x(), resultTop); _selector->resize(innerRect().width(), _contentHeight); update(); } void TabbedPanel::onWndActiveChanged() { if (!App::wnd()->windowHandle()->isActive() && !isHidden() && !preventAutoHide()) { hideAnimated(); } } void TabbedPanel::paintEvent(QPaintEvent *e) { Painter p(this); auto ms = getms(); // This call can finish _a_show animation and destroy _showAnimation. auto opacityAnimating = _a_opacity.animating(ms); auto showAnimating = _a_show.animating(ms); if (_showAnimation && !showAnimating) { _showAnimation.reset(); if (!opacityAnimating && !isDestroying()) { showChildren(); _selector->afterShown(); } } if (showAnimating) { Assert(_showAnimation != nullptr); if (auto opacity = _a_opacity.current(_hiding ? 0. : 1.)) { _showAnimation->paintFrame(p, 0, 0, width(), _a_show.current(1.), opacity); } } else if (opacityAnimating) { p.setOpacity(_a_opacity.current(_hiding ? 0. : 1.)); p.drawPixmap(0, 0, _cache); } else if (_hiding || isHidden()) { hideFinished(); } else { if (!_cache.isNull()) _cache = QPixmap(); Ui::Shadow::paint(p, innerRect(), width(), st::emojiPanAnimation.shadow); } } void TabbedPanel::moveByBottom() { moveToRight(0, y()); updateContentHeight(); } void TabbedPanel::enterEventHook(QEvent *e) { Messenger::Instance().registerLeaveSubscription(this); showAnimated(); } bool TabbedPanel::preventAutoHide() const { if (isDestroying()) { return false; } return _selector->preventAutoHide(); } void TabbedPanel::leaveEventHook(QEvent *e) { Messenger::Instance().unregisterLeaveSubscription(this); if (preventAutoHide()) { return; } auto ms = getms(); if (_a_show.animating(ms) || _a_opacity.animating(ms)) { hideAnimated(); } else { _hideTimer.callOnce(kHideTimeoutMs); } return TWidget::leaveEventHook(e); } void TabbedPanel::otherEnter() { showAnimated(); } void TabbedPanel::otherLeave() { if (preventAutoHide()) { return; } auto ms = getms(); if (_a_opacity.animating(ms)) { hideByTimerOrLeave(); } else { _hideTimer.callOnce(0); } } void TabbedPanel::hideFast() { if (isHidden()) return; _hideTimer.cancel(); _hiding = false; _a_opacity.finish(); hideFinished(); } void TabbedPanel::opacityAnimationCallback() { update(); if (!_a_opacity.animating()) { if (_hiding || isDestroying()) { _hiding = false; hideFinished(); } else if (!_a_show.animating()) { showChildren(); _selector->afterShown(); } } } void TabbedPanel::hideByTimerOrLeave() { if (isHidden() || preventAutoHide()) return; hideAnimated(); } void TabbedPanel::prepareCache() { if (_a_opacity.animating()) return; auto showAnimation = base::take(_a_show); auto showAnimationData = base::take(_showAnimation); showChildren(); _cache = Ui::GrabWidget(this); _showAnimation = base::take(showAnimationData); _a_show = base::take(showAnimation); if (_a_show.animating()) { hideChildren(); } } void TabbedPanel::startOpacityAnimation(bool hiding) { if (_selector && !_selector->isHidden()) { _selector->beforeHiding(); } _hiding = false; prepareCache(); _hiding = hiding; hideChildren(); _a_opacity.start([this] { opacityAnimationCallback(); }, _hiding ? 1. : 0., _hiding ? 0. : 1., st::emojiPanDuration); } void TabbedPanel::startShowAnimation() { if (!_a_show.animating()) { auto image = grabForAnimation(); _showAnimation = std::make_unique<Ui::PanelAnimation>(st::emojiPanAnimation, Ui::PanelAnimation::Origin::BottomRight); auto inner = rect().marginsRemoved(st::emojiPanMargins); _showAnimation->setFinalImage(std::move(image), QRect(inner.topLeft() * cIntRetinaFactor(), inner.size() * cIntRetinaFactor())); auto corners = App::cornersMask(ImageRoundRadius::Small); _showAnimation->setCornerMasks(corners[0], corners[1], corners[2], corners[3]); _showAnimation->start(); } hideChildren(); _a_show.start([this] { update(); }, 0., 1., st::emojiPanShowDuration); } QImage TabbedPanel::grabForAnimation() { auto cache = base::take(_cache); auto opacityAnimation = base::take(_a_opacity); auto showAnimationData = base::take(_showAnimation); auto showAnimation = base::take(_a_show); showChildren(); Ui::SendPendingMoveResizeEvents(this); auto result = QImage(size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); result.setDevicePixelRatio(cRetinaFactor()); result.fill(Qt::transparent); if (_selector) { _selector->render(&result, _selector->geometry().topLeft()); } _a_show = base::take(showAnimation); _showAnimation = base::take(showAnimationData); _a_opacity = base::take(opacityAnimation); _cache = base::take(_cache); return result; } void TabbedPanel::hideAnimated() { if (isHidden() || _hiding) { return; } _hideTimer.cancel(); if (!isDestroying() && _selector->isSliding()) { _hideAfterSlide = true; } else { startOpacityAnimation(true); } } void TabbedPanel::toggleAnimated() { if (isDestroying()) { return; } if (isHidden() || _hiding || _hideAfterSlide) { showAnimated(); } else { hideAnimated(); } } object_ptr<TabbedSelector> TabbedPanel::takeSelector() { if (!isHidden() && !_hiding) { startOpacityAnimation(true); } return std::move(_selector); } QPointer<TabbedSelector> TabbedPanel::getSelector() const { return _selector.data(); } void TabbedPanel::hideFinished() { hide(); _a_show.finish(); _showAnimation.reset(); _cache = QPixmap(); _hiding = false; if (isDestroying()) { deleteLater(); } else { _selector->hideFinished(); } } void TabbedPanel::showAnimated() { _hideTimer.cancel(); _hideAfterSlide = false; showStarted(); } void TabbedPanel::showStarted() { if (isDestroying()) { return; } if (isHidden()) { _selector->showStarted(); moveByBottom(); show(); startShowAnimation(); } else if (_hiding) { startOpacityAnimation(false); } } bool TabbedPanel::eventFilter(QObject *obj, QEvent *e) { if (isDestroying()) { return false; } if (e->type() == QEvent::Enter) { otherEnter(); } else if (e->type() == QEvent::Leave) { otherLeave(); } return false; } void TabbedPanel::showFromSelector() { if (isHidden()) { moveByBottom(); startShowAnimation(); show(); } showChildren(); showAnimated(); } style::margins TabbedPanel::innerPadding() const { return st::emojiPanMargins; } QRect TabbedPanel::innerRect() const { return rect().marginsRemoved(innerPadding()); } QRect TabbedPanel::horizontalRect() const { return innerRect().marginsRemoved(style::margins(0, st::buttonRadius, 0, st::buttonRadius)); } QRect TabbedPanel::verticalRect() const { return innerRect().marginsRemoved(style::margins(st::buttonRadius, 0, st::buttonRadius, 0)); } bool TabbedPanel::overlaps(const QRect &globalRect) const { if (isHidden() || !_cache.isNull()) return false; auto testRect = QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size()); auto inner = rect().marginsRemoved(st::emojiPanMargins); return inner.marginsRemoved(QMargins(st::buttonRadius, 0, st::buttonRadius, 0)).contains(testRect) || inner.marginsRemoved(QMargins(0, st::buttonRadius, 0, st::buttonRadius)).contains(testRect); } TabbedPanel::~TabbedPanel() = default; } // namespace ChatHelpers ������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/chat_helpers/tabbed_panel.h������������������������������������0000664�0000000�0000000�00000004713�13262451251�0024642�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/rp_widget.h" #include "base/timer.h" namespace Window { class Controller; } // namespace Window namespace Ui { class PanelAnimation; } // namespace Ui namespace ChatHelpers { class TabbedSelector; class TabbedPanel : public Ui::RpWidget{ Q_OBJECT public: TabbedPanel(QWidget *parent, not_null<Window::Controller*> controller); TabbedPanel(QWidget *parent, not_null<Window::Controller*> controller, object_ptr<TabbedSelector> selector); object_ptr<TabbedSelector> takeSelector(); QPointer<TabbedSelector> getSelector() const; void moveBottom(int bottom); void hideFast(); bool hiding() const { return _hiding || _hideTimer.isActive(); } bool overlaps(const QRect &globalRect) const; void showAnimated(); void hideAnimated(); void toggleAnimated(); ~TabbedPanel(); protected: void enterEventHook(QEvent *e) override; void leaveEventHook(QEvent *e) override; void otherEnter(); void otherLeave(); void paintEvent(QPaintEvent *e) override; bool eventFilter(QObject *obj, QEvent *e) override; private slots: void onWndActiveChanged(); private: void hideByTimerOrLeave(); void moveByBottom(); bool isDestroying() const { return !_selector; } void showFromSelector(); style::margins innerPadding() const; // Rounded rect which has shadow around it. QRect innerRect() const; // Inner rect with removed st::buttonRadius from top and bottom. // This one is allowed to be not rounded. QRect horizontalRect() const; // Inner rect with removed st::buttonRadius from left and right. // This one is allowed to be not rounded. QRect verticalRect() const; QImage grabForAnimation(); void startShowAnimation(); void startOpacityAnimation(bool hiding); void prepareCache(); void opacityAnimationCallback(); void hideFinished(); void showStarted(); bool preventAutoHide() const; void updateContentHeight(); not_null<Window::Controller*> _controller; object_ptr<TabbedSelector> _selector; int _contentMaxHeight = 0; int _contentHeight = 0; int _bottom = 0; std::unique_ptr<Ui::PanelAnimation> _showAnimation; Animation _a_show; bool _hiding = false; bool _hideAfterSlide = false; QPixmap _cache; Animation _a_opacity; base::Timer _hideTimer; }; } // namespace ChatHelpers �����������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/chat_helpers/tabbed_section.cpp��������������������������������0000664�0000000�0000000�00000005645�13262451251�0025547�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "chat_helpers/tabbed_section.h" #include "styles/style_chat_helpers.h" #include "chat_helpers/tabbed_selector.h" namespace ChatHelpers { TabbedMemento::TabbedMemento( object_ptr<TabbedSelector> selector, base::lambda<void(object_ptr<TabbedSelector>)> returnMethod) : _selector(std::move(selector)) , _returnMethod(std::move(returnMethod)) { } object_ptr<Window::SectionWidget> TabbedMemento::createWidget( QWidget *parent, not_null<Window::Controller*> controller, Window::Column column, const QRect &geometry) { auto result = object_ptr<TabbedSection>( parent, controller, std::move(_selector), std::move(_returnMethod)); result->setGeometry(geometry); return std::move(result); } TabbedMemento::~TabbedMemento() { if (_returnMethod && _selector) { _returnMethod(std::move(_selector)); } } TabbedSection::TabbedSection( QWidget *parent, not_null<Window::Controller*> controller) : TabbedSection( parent, controller, object_ptr<TabbedSelector>(this, controller), base::lambda<void(object_ptr<TabbedSelector>)>()) { } TabbedSection::TabbedSection( QWidget *parent, not_null<Window::Controller*> controller, object_ptr<TabbedSelector> selector, base::lambda<void(object_ptr<TabbedSelector>)> returnMethod) : Window::SectionWidget(parent, controller) , _selector(std::move(selector)) , _returnMethod(std::move(returnMethod)) { _selector->setParent(this); _selector->setRoundRadius(0); _selector->setGeometry(rect()); _selector->showStarted(); _selector->show(); connect(_selector, &TabbedSelector::cancelled, this, [this] { if (_cancelledCallback) { _cancelledCallback(); } }); _selector->setAfterShownCallback(base::lambda<void(SelectorTab)>()); _selector->setBeforeHidingCallback(base::lambda<void(SelectorTab)>()); setAttribute(Qt::WA_OpaquePaintEvent, true); } void TabbedSection::beforeHiding() { _selector->beforeHiding(); } void TabbedSection::afterShown() { _selector->afterShown(); } void TabbedSection::resizeEvent(QResizeEvent *e) { _selector->setGeometry(rect()); } object_ptr<TabbedSelector> TabbedSection::takeSelector() { _selector->beforeHiding(); return std::move(_selector); } QPointer<TabbedSelector> TabbedSection::getSelector() const { return _selector.data(); } bool TabbedSection::showInternal( not_null<Window::SectionMemento*> memento, const Window::SectionShow ¶ms) { return false; } bool TabbedSection::wheelEventFromFloatPlayer(QEvent *e) { return _selector->wheelEventFromFloatPlayer(e); } QRect TabbedSection::rectForFloatPlayer() const { return _selector->rectForFloatPlayer(); } TabbedSection::~TabbedSection() { beforeHiding(); if (_returnMethod) { _returnMethod(takeSelector()); } } } // namespace ChatHelpers �������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/chat_helpers/tabbed_section.h����������������������������������0000664�0000000�0000000�00000004154�13262451251�0025206�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "window/section_widget.h" #include "window/section_memento.h" namespace ChatHelpers { class TabbedSelector; class TabbedMemento : public Window::SectionMemento { public: TabbedMemento( object_ptr<TabbedSelector> selector, base::lambda<void(object_ptr<TabbedSelector>)> returnMethod); TabbedMemento(TabbedMemento &&other) = default; TabbedMemento &operator=(TabbedMemento &&other) = default; object_ptr<Window::SectionWidget> createWidget( QWidget *parent, not_null<Window::Controller*> controller, Window::Column column, const QRect &geometry) override; ~TabbedMemento(); private: object_ptr<TabbedSelector> _selector; base::lambda<void(object_ptr<TabbedSelector>)> _returnMethod; }; class TabbedSection : public Window::SectionWidget { public: TabbedSection( QWidget *parent, not_null<Window::Controller*> controller); TabbedSection( QWidget *parent, not_null<Window::Controller*> controller, object_ptr<TabbedSelector> selector, base::lambda<void(object_ptr<TabbedSelector>)> returnMethod); void beforeHiding(); void afterShown(); void setCancelledCallback(base::lambda<void()> callback) { _cancelledCallback = std::move(callback); } object_ptr<TabbedSelector> takeSelector(); QPointer<TabbedSelector> getSelector() const; bool showInternal( not_null<Window::SectionMemento*> memento, const Window::SectionShow ¶ms) override; bool forceAnimateBack() const override { return true; } // Float player interface. bool wheelEventFromFloatPlayer(QEvent *e) override; QRect rectForFloatPlayer() const override; ~TabbedSection(); protected: void resizeEvent(QResizeEvent *e) override; void showFinishedHook() override { afterShown(); } private: object_ptr<TabbedSelector> _selector; base::lambda<void()> _cancelledCallback; base::lambda<void(object_ptr<TabbedSelector>)> _returnMethod; }; } // namespace ChatHelpers ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp�������������������������������0000664�0000000�0000000�00000060577�13262451251�0025730�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "chat_helpers/tabbed_selector.h" #include "chat_helpers/emoji_list_widget.h" #include "chat_helpers/stickers_list_widget.h" #include "chat_helpers/gifs_list_widget.h" #include "chat_helpers/stickers.h" #include "styles/style_chat_helpers.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" #include "ui/widgets/shadow.h" #include "ui/widgets/discrete_sliders.h" #include "ui/widgets/scroll_area.h" #include "storage/localstorage.h" #include "lang/lang_keys.h" #include "mainwindow.h" #include "observer_peer.h" #include "apiwrap.h" namespace ChatHelpers { class TabbedSelector::SlideAnimation : public Ui::RoundShadowAnimation { public: enum class Direction { LeftToRight, RightToLeft, }; void setFinalImages(Direction direction, QImage &&left, QImage &&right, QRect inner, bool wasSectionIcons); void start(); void paintFrame(QPainter &p, float64 dt, float64 opacity); private: Direction _direction = Direction::LeftToRight; QPixmap _leftImage, _rightImage; int _width = 0; int _height = 0; int _innerLeft = 0; int _innerTop = 0; int _innerRight = 0; int _innerBottom = 0; int _innerWidth = 0; int _innerHeight = 0; int _painterInnerLeft = 0; int _painterInnerTop = 0; int _painterInnerWidth = 0; int _painterInnerBottom = 0; int _painterCategoriesTop = 0; int _painterInnerHeight = 0; int _painterInnerRight = 0; int _frameIntsPerLineAdd = 0; bool _wasSectionIcons = false; }; void TabbedSelector::SlideAnimation::setFinalImages(Direction direction, QImage &&left, QImage &&right, QRect inner, bool wasSectionIcons) { Expects(!started()); _direction = direction; _leftImage = QPixmap::fromImage(std::move(left).convertToFormat(QImage::Format_ARGB32_Premultiplied), Qt::ColorOnly); _rightImage = QPixmap::fromImage(std::move(right).convertToFormat(QImage::Format_ARGB32_Premultiplied), Qt::ColorOnly); Assert(!_leftImage.isNull()); Assert(!_rightImage.isNull()); _width = _leftImage.width(); _height = _rightImage.height(); Assert(!(_width % cIntRetinaFactor())); Assert(!(_height % cIntRetinaFactor())); Assert(_leftImage.devicePixelRatio() == _rightImage.devicePixelRatio()); Assert(_rightImage.width() == _width); Assert(_rightImage.height() == _height); Assert(QRect(0, 0, _width, _height).contains(inner)); _innerLeft = inner.x(); _innerTop = inner.y(); _innerWidth = inner.width(); _innerHeight = inner.height(); Assert(!(_innerLeft % cIntRetinaFactor())); Assert(!(_innerTop % cIntRetinaFactor())); Assert(!(_innerWidth % cIntRetinaFactor())); Assert(!(_innerHeight % cIntRetinaFactor())); _innerRight = _innerLeft + _innerWidth; _innerBottom = _innerTop + _innerHeight; _painterInnerLeft = _innerLeft / cIntRetinaFactor(); _painterInnerTop = _innerTop / cIntRetinaFactor(); _painterInnerRight = _innerRight / cIntRetinaFactor(); _painterInnerBottom = _innerBottom / cIntRetinaFactor(); _painterInnerWidth = _innerWidth / cIntRetinaFactor(); _painterInnerHeight = _innerHeight / cIntRetinaFactor(); _painterCategoriesTop = _painterInnerBottom - st::emojiFooterHeight; _wasSectionIcons = wasSectionIcons; } void TabbedSelector::SlideAnimation::start() { Assert(!_leftImage.isNull()); Assert(!_rightImage.isNull()); RoundShadowAnimation::start(_width, _height, _leftImage.devicePixelRatio()); auto checkCorner = [this](const Corner &corner) { if (!corner.valid()) return; Assert(corner.width <= _innerWidth); Assert(corner.height <= _innerHeight); }; checkCorner(_topLeft); checkCorner(_topRight); checkCorner(_bottomLeft); checkCorner(_bottomRight); _frameIntsPerLineAdd = (_width - _innerWidth) + _frameIntsPerLineAdded; } void TabbedSelector::SlideAnimation::paintFrame(QPainter &p, float64 dt, float64 opacity) { Expects(started()); Expects(dt >= 0.); _frameAlpha = anim::interpolate(1, 256, opacity); auto frameInts = _frameInts + _innerLeft + _innerTop * _frameIntsPerLine; auto leftToRight = (_direction == Direction::LeftToRight); auto easeOut = anim::easeOutCirc(1., dt); auto easeIn = anim::easeInCirc(1., dt); auto arrivingCoord = anim::interpolate(_innerWidth, 0, easeOut); auto departingCoord = anim::interpolate(0, _innerWidth, easeIn); if (auto decrease = (arrivingCoord % cIntRetinaFactor())) { arrivingCoord -= decrease; } if (auto decrease = (departingCoord % cIntRetinaFactor())) { departingCoord -= decrease; } auto arrivingAlpha = easeIn; auto departingAlpha = 1. - easeOut; auto leftCoord = (leftToRight ? arrivingCoord : departingCoord) * -1; auto leftAlpha = (leftToRight ? arrivingAlpha : departingAlpha); auto rightCoord = (leftToRight ? departingCoord : arrivingCoord); auto rightAlpha = (leftToRight ? departingAlpha : arrivingAlpha); // _innerLeft ..(left).. leftTo ..(both).. bothTo ..(none).. noneTo ..(right).. _innerRight auto leftTo = _innerLeft + snap(_innerWidth + leftCoord, 0, _innerWidth); auto rightFrom = _innerLeft + snap(rightCoord, 0, _innerWidth); auto painterRightFrom = rightFrom / cIntRetinaFactor(); if (opacity < 1.) { _frame.fill(Qt::transparent); } { Painter p(&_frame); p.setOpacity(opacity); p.fillRect(_painterInnerLeft, _painterInnerTop, _painterInnerWidth, _painterCategoriesTop - _painterInnerTop, st::emojiPanBg); p.fillRect(_painterInnerLeft, _painterCategoriesTop, _painterInnerWidth, _painterInnerBottom - _painterCategoriesTop, _wasSectionIcons ? st::emojiPanCategories : st::emojiPanBg); p.setCompositionMode(QPainter::CompositionMode_SourceOver); if (leftTo > _innerLeft) { p.setOpacity(opacity * leftAlpha); p.drawPixmap(_painterInnerLeft, _painterInnerTop, _leftImage, _innerLeft - leftCoord, _innerTop, leftTo - _innerLeft, _innerHeight); } if (rightFrom < _innerRight) { p.setOpacity(opacity * rightAlpha); p.drawPixmap(painterRightFrom, _painterInnerTop, _rightImage, _innerLeft, _innerTop, _innerRight - rightFrom, _innerHeight); } } // Draw corners //paintCorner(_topLeft, _innerLeft, _innerTop); //paintCorner(_topRight, _innerRight - _topRight.width, _innerTop); paintCorner(_bottomLeft, _innerLeft, _innerBottom - _bottomLeft.height); paintCorner(_bottomRight, _innerRight - _bottomRight.width, _innerBottom - _bottomRight.height); // Draw shadow upon the transparent auto outerLeft = _innerLeft; auto outerTop = _innerTop; auto outerRight = _innerRight; auto outerBottom = _innerBottom; if (_shadow.valid()) { outerLeft -= _shadow.extend.left(); outerTop -= _shadow.extend.top(); outerRight += _shadow.extend.right(); outerBottom += _shadow.extend.bottom(); } if (cIntRetinaFactor() > 1) { if (auto skipLeft = (outerLeft % cIntRetinaFactor())) { outerLeft -= skipLeft; } if (auto skipTop = (outerTop % cIntRetinaFactor())) { outerTop -= skipTop; } if (auto skipRight = (outerRight % cIntRetinaFactor())) { outerRight += (cIntRetinaFactor() - skipRight); } if (auto skipBottom = (outerBottom % cIntRetinaFactor())) { outerBottom += (cIntRetinaFactor() - skipBottom); } } if (opacity == 1.) { // Fill above the frame top with transparent. auto fillTopInts = (_frameInts + outerTop * _frameIntsPerLine + outerLeft); auto fillWidth = (outerRight - outerLeft) * sizeof(uint32); for (auto fillTop = _innerTop - outerTop; fillTop != 0; --fillTop) { memset(fillTopInts, 0, fillWidth); fillTopInts += _frameIntsPerLine; } // Fill to the left and to the right of the frame with transparent. auto fillLeft = (_innerLeft - outerLeft) * sizeof(uint32); auto fillRight = (outerRight - _innerRight) * sizeof(uint32); if (fillLeft || fillRight) { auto fillInts = _frameInts + _innerTop * _frameIntsPerLine; for (auto y = _innerTop; y != _innerBottom; ++y) { memset(fillInts + outerLeft, 0, fillLeft); memset(fillInts + _innerRight, 0, fillRight); fillInts += _frameIntsPerLine; } } // Fill below the frame bottom with transparent. auto fillBottomInts = (_frameInts + _innerBottom * _frameIntsPerLine + outerLeft); for (auto fillBottom = outerBottom - _innerBottom; fillBottom != 0; --fillBottom) { memset(fillBottomInts, 0, fillWidth); fillBottomInts += _frameIntsPerLine; } } if (_shadow.valid()) { paintShadow(outerLeft, outerTop, outerRight, outerBottom); } // Debug //frameInts = _frameInts; //auto pattern = anim::shifted((static_cast<uint32>(0xFF) << 24) | (static_cast<uint32>(0xFF) << 16) | (static_cast<uint32>(0xFF) << 8) | static_cast<uint32>(0xFF)); //for (auto y = 0; y != _finalHeight; ++y) { // for (auto x = 0; x != _finalWidth; ++x) { // auto source = *frameInts; // auto sourceAlpha = (source >> 24); // *frameInts = anim::unshifted(anim::shifted(source) * 256 + pattern * (256 - sourceAlpha)); // ++frameInts; // } // frameInts += _frameIntsPerLineAdded; //} p.drawImage(outerLeft / cIntRetinaFactor(), outerTop / cIntRetinaFactor(), _frame, outerLeft, outerTop, outerRight - outerLeft, outerBottom - outerTop); } TabbedSelector::Tab::Tab(SelectorTab type, object_ptr<Inner> widget) : _type(type) , _widget(std::move(widget)) , _weak(_widget) , _footer(_widget->createFooter()) { _footer->setParent(_widget->parentWidget()); } object_ptr<TabbedSelector::Inner> TabbedSelector::Tab::takeWidget() { return std::move(_widget); } void TabbedSelector::Tab::returnWidget(object_ptr<Inner> widget) { _widget = std::move(widget); Ensures(_widget == _weak); } void TabbedSelector::Tab::saveScrollTop() { _scrollTop = widget()->getVisibleTop(); } TabbedSelector::TabbedSelector(QWidget *parent, not_null<Window::Controller*> controller) : RpWidget(parent) , _tabsSlider(this, st::emojiTabs) , _topShadow(this) , _bottomShadow(this) , _scroll(this, st::emojiScroll) , _tabs { { Tab { SelectorTab::Emoji, object_ptr<EmojiListWidget>(this, controller) }, Tab { SelectorTab::Stickers, object_ptr<StickersListWidget>(this, controller) }, Tab { SelectorTab::Gifs, object_ptr<GifsListWidget>(this, controller) }, } } , _currentTabType(Auth().settings().selectorTab()) { resize(st::emojiPanWidth, st::emojiPanMaxHeight); for (auto &tab : _tabs) { tab.footer()->hide(); tab.widget()->hide(); } createTabsSlider(); setWidgetToScrollArea(); _bottomShadow->setGeometry(_tabsSlider->x(), _scroll->y() + _scroll->height() - st::lineWidth, _tabsSlider->width(), st::lineWidth); for (auto &tab : _tabs) { auto widget = tab.widget(); connect(widget, &Inner::scrollToY, this, [this, tab = &tab](int y) { if (tab == currentTab()) { scrollToY(y); } else { tab->saveScrollTop(y); } }); connect(widget, &Inner::disableScroll, this, [this, tab = &tab](bool disabled) { if (tab == currentTab()) { _scroll->disableScroll(disabled); } }); } connect(stickers(), SIGNAL(scrollUpdated()), this, SLOT(onScroll())); connect(_scroll, SIGNAL(scrolled()), this, SLOT(onScroll())); connect(emoji(), SIGNAL(selected(EmojiPtr)), this, SIGNAL(emojiSelected(EmojiPtr))); connect(stickers(), SIGNAL(selected(DocumentData*)), this, SIGNAL(stickerSelected(DocumentData*))); connect(stickers(), SIGNAL(checkForHide()), this, SIGNAL(checkForHide())); connect(gifs(), SIGNAL(selected(DocumentData*)), this, SIGNAL(stickerSelected(DocumentData*))); connect(gifs(), SIGNAL(selected(PhotoData*)), this, SIGNAL(photoSelected(PhotoData*))); connect(gifs(), SIGNAL(selected(InlineBots::Result*, UserData*)), this, SIGNAL(inlineResultSelected(InlineBots::Result*, UserData*))); connect(gifs(), SIGNAL(cancelled()), this, SIGNAL(cancelled())); _topShadow->raise(); _bottomShadow->raise(); _tabsSlider->raise(); subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(Notify::PeerUpdate::Flag::ChannelRightsChanged, [this](const Notify::PeerUpdate &update) { if (update.peer == _currentPeer) { checkRestrictedPeer(); } })); Auth().api().stickerSetInstalled( ) | rpl::start_with_next([this](uint64 setId) { _tabsSlider->setActiveSection( static_cast<int>(SelectorTab::Stickers)); stickers()->showStickerSet(setId); _showRequests.fire({}); }, lifetime()); // setAttribute(Qt::WA_AcceptTouchEvents); setAttribute(Qt::WA_OpaquePaintEvent, false); showAll(); } void TabbedSelector::resizeEvent(QResizeEvent *e) { _tabsSlider->resizeToWidth(width()); _tabsSlider->moveToLeft(0, 0); _topShadow->setGeometry( _tabsSlider->x(), _tabsSlider->bottomNoMargins() - st::lineWidth, _tabsSlider->width(), st::lineWidth); auto scrollWidth = width() - st::buttonRadius; auto scrollHeight = height() - marginTop() - marginBottom(); auto inner = currentTab()->widget(); auto innerWidth = scrollWidth - st::emojiScroll.width; auto updateScrollGeometry = [&] { _scroll->setGeometryToLeft( st::buttonRadius, marginTop(), scrollWidth, scrollHeight); }; auto updateInnerGeometry = [&] { auto scrollTop = _scroll->scrollTop(); auto scrollBottom = scrollTop + scrollHeight; inner->setMinimalHeight(innerWidth, scrollHeight); inner->setVisibleTopBottom(scrollTop, scrollBottom); }; if (e->oldSize().height() > height()) { updateScrollGeometry(); updateInnerGeometry(); } else { updateInnerGeometry(); updateScrollGeometry(); } _bottomShadow->setGeometry(_tabsSlider->x(), _scroll->y() + _scroll->height() - st::lineWidth, _tabsSlider->width(), st::lineWidth); updateRestrictedLabelGeometry(); _footerTop = height() - st::emojiFooterHeight; for (auto &tab : _tabs) { tab.footer()->resizeToWidth(width()); tab.footer()->moveToLeft(0, _footerTop); } update(); } void TabbedSelector::updateRestrictedLabelGeometry() { if (!_restrictedLabel) { return; } auto labelWidth = width() - st::stickerPanPadding * 2; _restrictedLabel->resizeToWidth(labelWidth); _restrictedLabel->moveToLeft( (width() - _restrictedLabel->width()) / 2, (height() / 3 - _restrictedLabel->height() / 2)); } void TabbedSelector::paintEvent(QPaintEvent *e) { Painter p(this); auto ms = getms(); auto switching = (_slideAnimation != nullptr); if (switching) { paintSlideFrame(p, ms); if (!_a_slide.animating()) { _slideAnimation.reset(); afterShown(); emit slideFinished(); } } else { paintContent(p); } } void TabbedSelector::paintSlideFrame(Painter &p, TimeMs ms) { if (_roundRadius > 0) { auto topPart = QRect(0, 0, width(), _tabsSlider->height() + _roundRadius); App::roundRect(p, topPart, st::emojiPanBg, ImageRoundRadius::Small, RectPart::FullTop | RectPart::NoTopBottom); } else { p.fillRect(0, 0, width(), _tabsSlider->height(), st::emojiPanBg); } auto slideDt = _a_slide.current(ms, 1.); _slideAnimation->paintFrame(p, slideDt, 1.); } void TabbedSelector::paintContent(Painter &p) { auto &bottomBg = hasSectionIcons() ? st::emojiPanCategories : st::emojiPanBg; if (_roundRadius > 0) { auto topPart = QRect(0, 0, width(), _tabsSlider->height() + _roundRadius); App::roundRect(p, topPart, st::emojiPanBg, ImageRoundRadius::Small, RectPart::FullTop | RectPart::NoTopBottom); auto bottomPart = QRect(0, _footerTop - _roundRadius, width(), st::emojiFooterHeight + _roundRadius); auto bottomParts = RectPart::NoTopBottom | RectPart::FullBottom; App::roundRect(p, bottomPart, bottomBg, ImageRoundRadius::Small, bottomParts); } else { p.fillRect(0, 0, width(), _tabsSlider->height(), st::emojiPanBg); p.fillRect(0, _footerTop, width(), st::emojiFooterHeight, bottomBg); } auto sidesTop = marginTop(); auto sidesHeight = height() - sidesTop - marginBottom(); if (_restrictedLabel) { p.fillRect(0, sidesTop, width(), sidesHeight, st::emojiPanBg); } else { p.fillRect(myrtlrect(width() - st::emojiScroll.width, sidesTop, st::emojiScroll.width, sidesHeight), st::emojiPanBg); p.fillRect(myrtlrect(0, sidesTop, st::buttonRadius, sidesHeight), st::emojiPanBg); } } int TabbedSelector::marginTop() const { return _tabsSlider->height() - st::lineWidth; } int TabbedSelector::marginBottom() const { return st::emojiFooterHeight; } void TabbedSelector::refreshStickers() { stickers()->refreshStickers(); if (isHidden() || _currentTabType != SelectorTab::Stickers) { stickers()->preloadImages(); } } bool TabbedSelector::preventAutoHide() const { return stickers()->preventAutoHide(); } QImage TabbedSelector::grabForAnimation() { auto slideAnimationData = base::take(_slideAnimation); auto slideAnimation = base::take(_a_slide); showAll(); _topShadow->hide(); _tabsSlider->hide(); Ui::SendPendingMoveResizeEvents(this); auto result = QImage(size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); result.setDevicePixelRatio(cRetinaFactor()); result.fill(Qt::transparent); render(&result); _a_slide = base::take(slideAnimation); _slideAnimation = base::take(slideAnimationData); return result; } bool TabbedSelector::wheelEventFromFloatPlayer(QEvent *e) { return _scroll->viewportEvent(e); } QRect TabbedSelector::rectForFloatPlayer() const { return mapToGlobal(_scroll->geometry()); } TabbedSelector::~TabbedSelector() = default; void TabbedSelector::hideFinished() { for (auto &tab : _tabs) { tab.widget()->panelHideFinished(); } _a_slide.finish(); _slideAnimation.reset(); } void TabbedSelector::showStarted() { Auth().api().updateStickers(); currentTab()->widget()->refreshRecent(); currentTab()->widget()->preloadImages(); _a_slide.finish(); _slideAnimation.reset(); showAll(); } void TabbedSelector::beforeHiding() { if (!_scroll->isHidden()) { currentTab()->widget()->beforeHiding(); if (_beforeHidingCallback) { _beforeHidingCallback(_currentTabType); } } } void TabbedSelector::afterShown() { if (!_a_slide.animating()) { showAll(); currentTab()->widget()->afterShown(); if (_afterShownCallback) { _afterShownCallback(_currentTabType); } } } void TabbedSelector::showMegagroupSet(ChannelData *megagroup) { stickers()->showMegagroupSet(megagroup); } void TabbedSelector::setCurrentPeer(PeerData *peer) { gifs()->setInlineQueryPeer(peer); _currentPeer = peer; checkRestrictedPeer(); } void TabbedSelector::checkRestrictedPeer() { if (auto megagroup = _currentPeer ? _currentPeer->asMegagroup() : nullptr) { auto restricted = (_currentTabType == SelectorTab::Stickers) ? megagroup->restricted(ChannelRestriction::f_send_stickers) : (_currentTabType == SelectorTab::Gifs) ? megagroup->restricted(ChannelRestriction::f_send_gifs) : false; if (restricted) { if (!_restrictedLabel) { auto text = (_currentTabType == SelectorTab::Stickers) ? lang(lng_restricted_send_stickers) : (_currentTabType == SelectorTab::Gifs) ? lang(lng_restricted_send_gifs) : QString(); _restrictedLabel.create( this, text, Ui::FlatLabel::InitType::Simple, st::stickersRestrictedLabel); _restrictedLabel->show(); updateRestrictedLabelGeometry(); currentTab()->footer()->hide(); _scroll->hide(); _bottomShadow->hide(); update(); } return; } } if (_restrictedLabel) { _restrictedLabel.destroy(); if (!_a_slide.animating()) { currentTab()->footer()->show(); _scroll->show(); _bottomShadow->setVisible(_currentTabType == SelectorTab::Gifs); update(); } } } bool TabbedSelector::isRestrictedView() { checkRestrictedPeer(); return (_restrictedLabel != nullptr); } void TabbedSelector::showAll() { if (isRestrictedView()) { _restrictedLabel->show(); } else { currentTab()->footer()->show(); _scroll->show(); _bottomShadow->setVisible(_currentTabType == SelectorTab::Gifs); } _topShadow->show(); _tabsSlider->show(); } void TabbedSelector::hideForSliding() { hideChildren(); _tabsSlider->show(); _topShadow->show(); currentTab()->widget()->clearSelection(); } void TabbedSelector::onScroll() { auto scrollTop = _scroll->scrollTop(); auto scrollBottom = scrollTop + _scroll->height(); currentTab()->widget()->setVisibleTopBottom(scrollTop, scrollBottom); } void TabbedSelector::setRoundRadius(int radius) { _roundRadius = radius; _tabsSlider->setRippleTopRoundRadius(_roundRadius); } void TabbedSelector::createTabsSlider() { auto sections = QStringList(); sections.push_back(lang(lng_switch_emoji).toUpper()); sections.push_back(lang(lng_switch_stickers).toUpper()); sections.push_back(lang(lng_switch_gifs).toUpper()); _tabsSlider->setSections(sections); _tabsSlider->setActiveSectionFast(static_cast<int>(_currentTabType)); _tabsSlider->sectionActivated( ) | rpl::start_with_next( [this] { switchTab(); }, lifetime()); } bool TabbedSelector::hasSectionIcons() const { return (_currentTabType != SelectorTab::Gifs) && !_restrictedLabel; } void TabbedSelector::switchTab() { auto tab = _tabsSlider->activeSection(); Assert(tab >= 0 && tab < Tab::kCount); auto newTabType = static_cast<SelectorTab>(tab); if (_currentTabType == newTabType) { return; } auto wasSectionIcons = hasSectionIcons(); auto wasTab = _currentTabType; currentTab()->saveScrollTop(); beforeHiding(); auto wasCache = grabForAnimation(); auto widget = _scroll->takeWidget<Inner>(); widget->setParent(this); widget->hide(); currentTab()->footer()->hide(); currentTab()->returnWidget(std::move(widget)); _currentTabType = newTabType; _restrictedLabel.destroy(); checkRestrictedPeer(); currentTab()->widget()->refreshRecent(); currentTab()->widget()->preloadImages(); setWidgetToScrollArea(); auto nowCache = grabForAnimation(); auto direction = (wasTab > _currentTabType) ? SlideAnimation::Direction::LeftToRight : SlideAnimation::Direction::RightToLeft; if (direction == SlideAnimation::Direction::LeftToRight) { std::swap(wasCache, nowCache); } _slideAnimation = std::make_unique<SlideAnimation>(); auto slidingRect = QRect(_tabsSlider->x() * cIntRetinaFactor(), _scroll->y() * cIntRetinaFactor(), _tabsSlider->width() * cIntRetinaFactor(), (height() - _scroll->y()) * cIntRetinaFactor()); _slideAnimation->setFinalImages(direction, std::move(wasCache), std::move(nowCache), slidingRect, wasSectionIcons); auto corners = App::cornersMask(ImageRoundRadius::Small); _slideAnimation->setCornerMasks(corners[0], corners[1], corners[2], corners[3]); _slideAnimation->start(); hideForSliding(); getTab(wasTab)->widget()->hideFinished(); _a_slide.start([this] { update(); }, 0., 1., st::emojiPanSlideDuration, anim::linear); update(); Auth().settings().setSelectorTab(_currentTabType); Auth().saveSettingsDelayed(); } not_null<EmojiListWidget*> TabbedSelector::emoji() const { return static_cast<EmojiListWidget*>(getTab(SelectorTab::Emoji)->widget().get()); } not_null<StickersListWidget*> TabbedSelector::stickers() const { return static_cast<StickersListWidget*>(getTab(SelectorTab::Stickers)->widget().get()); } not_null<GifsListWidget*> TabbedSelector::gifs() const { return static_cast<GifsListWidget*>(getTab(SelectorTab::Gifs)->widget().get()); } void TabbedSelector::setWidgetToScrollArea() { auto inner = _scroll->setOwnedWidget(currentTab()->takeWidget()); auto innerWidth = _scroll->width() - st::emojiScroll.width; auto scrollHeight = _scroll->height(); inner->setMinimalHeight(innerWidth, scrollHeight); inner->moveToLeft(0, 0); inner->show(); _scroll->disableScroll(false); scrollToY(currentTab()->getScrollTop()); onScroll(); } void TabbedSelector::scrollToY(int y) { _scroll->scrollToY(y); // Qt render glitch workaround, shadow sometimes disappears if we just scroll to y. _topShadow->update(); } TabbedSelector::Inner::Inner( QWidget *parent, not_null<Window::Controller*> controller) : RpWidget(parent) , _controller(controller) { } void TabbedSelector::Inner::visibleTopBottomUpdated(int visibleTop, int visibleBottom) { _visibleTop = visibleTop; _visibleBottom = visibleBottom; } void TabbedSelector::Inner::setMinimalHeight( int newWidth, int newMinimalHeight) { if (_minimalHeight != newMinimalHeight) { _minimalHeight = newMinimalHeight; resizeToWidth(newWidth); } else if (newWidth != width()) { resizeToWidth(newWidth); } } int TabbedSelector::Inner::resizeGetHeight(int newWidth) { auto result = std::max( countDesiredHeight(newWidth), minimalHeight()); if (result != height()) { update(); } return result; } int TabbedSelector::Inner::minimalHeight() const { auto result = _minimalHeight; return (_minimalHeight > 0) ? _minimalHeight : (st::emojiPanMaxHeight - st::emojiFooterHeight); } void TabbedSelector::Inner::hideFinished() { processHideFinished(); if (auto footer = getFooter()) { footer->processHideFinished(); } } void TabbedSelector::Inner::panelHideFinished() { hideFinished(); processPanelHideFinished(); if (auto footer = getFooter()) { footer->processPanelHideFinished(); } } TabbedSelector::InnerFooter::InnerFooter(QWidget *parent) : TWidget(parent) { resize(st::emojiPanWidth, st::emojiFooterHeight); } } // namespace ChatHelpers ���������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/chat_helpers/tabbed_selector.h���������������������������������0000664�0000000�0000000�00000013342�13262451251�0025361�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/rp_widget.h" #include "ui/effects/panel_animation.h" #include "mtproto/sender.h" #include "auth_session.h" namespace InlineBots { class Result; } // namespace InlineBots namespace Ui { class PlainShadow; class ScrollArea; class SettingsSlider; class FlatLabel; } // namesapce Ui namespace Window { class Controller; } // namespace Window namespace ChatHelpers { enum class SelectorTab { Emoji, Stickers, Gifs, }; class EmojiListWidget; class StickersListWidget; class GifsListWidget; class TabbedSelector : public Ui::RpWidget, private base::Subscriber { Q_OBJECT public: TabbedSelector(QWidget *parent, not_null<Window::Controller*> controller); void setRoundRadius(int radius); void refreshStickers(); void showMegagroupSet(ChannelData *megagroup); void setCurrentPeer(PeerData *peer); void hideFinished(); void showStarted(); void beforeHiding(); void afterShown(); int marginTop() const; int marginBottom() const; bool preventAutoHide() const; bool isSliding() const { return _a_slide.animating(); } void setAfterShownCallback(base::lambda<void(SelectorTab)> callback) { _afterShownCallback = std::move(callback); } void setBeforeHidingCallback(base::lambda<void(SelectorTab)> callback) { _beforeHidingCallback = std::move(callback); } // Float player interface. bool wheelEventFromFloatPlayer(QEvent *e); QRect rectForFloatPlayer() const; auto showRequests() const { return _showRequests.events(); } ~TabbedSelector(); class Inner; class InnerFooter; protected: void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; private slots: void onScroll(); signals: void emojiSelected(EmojiPtr emoji); void stickerSelected(DocumentData *sticker); void photoSelected(PhotoData *photo); void inlineResultSelected(InlineBots::Result *result, UserData *bot); void cancelled(); void slideFinished(); void checkForHide(); private: class Tab { public: static constexpr auto kCount = 3; Tab(SelectorTab type, object_ptr<Inner> widget); object_ptr<Inner> takeWidget(); void returnWidget(object_ptr<Inner> widget); SelectorTab type() const { return _type; } not_null<Inner*> widget() const { return _weak; } not_null<InnerFooter*> footer() const { return _footer; } void saveScrollTop(); void saveScrollTop(int scrollTop) { _scrollTop = scrollTop; } int getScrollTop() const { return _scrollTop; } private: SelectorTab _type = SelectorTab::Emoji; object_ptr<Inner> _widget = { nullptr }; QPointer<Inner> _weak; object_ptr<InnerFooter> _footer; int _scrollTop = 0; }; void paintSlideFrame(Painter &p, TimeMs ms); void paintContent(Painter &p); void checkRestrictedPeer(); bool isRestrictedView(); void updateRestrictedLabelGeometry(); QImage grabForAnimation(); void scrollToY(int y); void showAll(); void hideForSliding(); bool hasSectionIcons() const; void setWidgetToScrollArea(); void createTabsSlider(); void switchTab(); not_null<Tab*> getTab(SelectorTab type) { return &_tabs[static_cast<int>(type)]; } not_null<const Tab*> getTab(SelectorTab type) const { return &_tabs[static_cast<int>(type)]; } not_null<Tab*> currentTab() { return getTab(_currentTabType); } not_null<const Tab*> currentTab() const { return getTab(_currentTabType); } not_null<EmojiListWidget*> emoji() const; not_null<StickersListWidget*> stickers() const; not_null<GifsListWidget*> gifs() const; int _roundRadius = 0; int _footerTop = 0; PeerData *_currentPeer = nullptr; class SlideAnimation; std::unique_ptr<SlideAnimation> _slideAnimation; Animation _a_slide; object_ptr<Ui::SettingsSlider> _tabsSlider = { nullptr }; object_ptr<Ui::PlainShadow> _topShadow; object_ptr<Ui::PlainShadow> _bottomShadow; object_ptr<Ui::ScrollArea> _scroll; object_ptr<Ui::FlatLabel> _restrictedLabel = { nullptr }; std::array<Tab, Tab::kCount> _tabs; SelectorTab _currentTabType = SelectorTab::Emoji; base::lambda<void(SelectorTab)> _afterShownCallback; base::lambda<void(SelectorTab)> _beforeHidingCallback; rpl::event_stream<> _showRequests; }; class TabbedSelector::Inner : public Ui::RpWidget { Q_OBJECT public: Inner(QWidget *parent, not_null<Window::Controller*> controller); int getVisibleTop() const { return _visibleTop; } int getVisibleBottom() const { return _visibleBottom; } void setMinimalHeight(int newWidth, int newMinimalHeight); virtual void refreshRecent() = 0; virtual void preloadImages() { } void hideFinished(); void panelHideFinished(); virtual void clearSelection() = 0; virtual void afterShown() { } virtual void beforeHiding() { } virtual object_ptr<InnerFooter> createFooter() = 0; signals: void scrollToY(int y); void disableScroll(bool disabled); protected: void visibleTopBottomUpdated( int visibleTop, int visibleBottom) override; int minimalHeight() const; int resizeGetHeight(int newWidth) override final; not_null<Window::Controller*> controller() const { return _controller; } virtual int countDesiredHeight(int newWidth) = 0; virtual InnerFooter *getFooter() const = 0; virtual void processHideFinished() { } virtual void processPanelHideFinished() { } private: not_null<Window::Controller*> _controller; int _visibleTop = 0; int _visibleBottom = 0; int _minimalHeight = 0; }; class TabbedSelector::InnerFooter : public TWidget { public: InnerFooter(QWidget *parent); protected: virtual void processHideFinished() { } virtual void processPanelHideFinished() { } friend class Inner; }; } // namespace ChatHelpers ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/�������������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0021027�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/common/������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0022317�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/common/basic_tokenized_file.cpp������������������������0000664�0000000�0000000�00000020425�13262451251�0027162�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "codegen/common/basic_tokenized_file.h" #include "codegen/common/logging.h" #include "codegen/common/clean_file_reader.h" #include "codegen/common/checked_utf8_string.h" using Token = codegen::common::BasicTokenizedFile::Token; using Type = Token::Type; namespace codegen { namespace common { namespace { constexpr int kErrorUnterminatedStringLiteral = 201; constexpr int kErrorIncorrectUtf8String = 202; constexpr int kErrorIncorrectToken = 203; constexpr int kErrorUnexpectedToken = 204; bool isDigitChar(char ch) { return (ch >= '0') && (ch <= '9'); } bool isNameChar(char ch) { return isDigitChar(ch) || ((ch >= 'a') && (ch <= 'z')) || ((ch >= 'A') && (ch <= 'Z')) || (ch == '_'); } bool isWhitespaceChar(char ch) { return (ch == '\n' || ch == '\r' || ch == ' ' || ch == '\t'); } Token invalidToken() { return { Type::Invalid, QString(), ConstUtf8String(nullptr, 0), false }; } } // namespace BasicTokenizedFile::BasicTokenizedFile(const QString &filepath) : reader_(filepath) { } BasicTokenizedFile::BasicTokenizedFile(const QByteArray &content, const QString &filepath) : reader_(content, filepath) { } bool BasicTokenizedFile::putBack() { if (currentToken_ > 0) { --currentToken_; return true; } return false; } Token BasicTokenizedFile::getAnyToken() { if (currentToken_ >= tokens_.size()) { if (readToken() == Type::Invalid) { return invalidToken(); } } return tokens_.at(currentToken_++); } Token BasicTokenizedFile::getToken(Type typeCondition) { if (auto token = getAnyToken()) { if (token.type == typeCondition) { return token; } putBack(); } return invalidToken(); } Type BasicTokenizedFile::readToken() { auto result = readOneToken(StartWithWhitespace::Allow); // Try to read double token. if (result == Type::Int) { if (readOneToken(StartWithWhitespace::Deny) == Type::Dot) { // We got int and dot, so it is double already. result = uniteLastTokens(Type::Double); // Try to read one more int (after dot). if (readOneToken(StartWithWhitespace::Deny) == Type::Int) { result = uniteLastTokens(Type::Double); } } } else if (result == Type::Dot) { if (readOneToken(StartWithWhitespace::Deny) == Type::Int) { //We got dot and int, so it is double. result = uniteLastTokens(Type::Double); } } return result; } Type BasicTokenizedFile::readOneToken(StartWithWhitespace condition) { skipWhitespaces(); if (tokenStartWhitespace_ && condition == StartWithWhitespace::Deny) { return Type::Invalid; } if (reader_.atEnd()) { return Type::Invalid; } auto ch = reader_.currentChar(); if (ch == '"') { return readString(); } else if (isNameChar(ch)) { return readNameOrNumber(); } return readSingleLetter(); } Type BasicTokenizedFile::saveToken(Type type, const QString &value) { ConstUtf8String original = { tokenStart_, reader_.currentPtr() }; tokens_.push_back({ type, value, original, tokenStartWhitespace_ }); return type; } Type BasicTokenizedFile::uniteLastTokens(Type type) { auto size = tokens_.size(); if (size < 2) { return Type::Invalid; } auto &token(tokens_[size - 2]); auto originalFrom = token.original.data(); auto originalTill = tokens_.back().original.end(); token.type = type; token.original = { originalFrom, originalTill }; token.value += tokens_.back().value; tokens_.pop_back(); return type; } QString BasicTokenizedFile::getCurrentLineComment() { if (lineNumber_ > singleLineComments_.size()) { reader_.logError(kErrorInternal, lineNumber_) << "internal tokenizer error (line number larger than comments list size)."; failed_ = true; return QString(); } auto commentBytes = singleLineComments_[lineNumber_ - 1].mid(2); // Skip "//" CheckedUtf8String comment(commentBytes); if (!comment.isValid()) { reader_.logError(kErrorIncorrectUtf8String, lineNumber_) << "incorrect UTF-8 string in the comment."; failed_ = true; return QString(); } return comment.toString().trimmed(); } Type BasicTokenizedFile::readNameOrNumber() { while (!reader_.atEnd()) { if (!isDigitChar(reader_.currentChar())) { break; } reader_.skipChar(); } bool onlyDigits = true; while (!reader_.atEnd()) { if (!isNameChar(reader_.currentChar())) { break; } onlyDigits = false; reader_.skipChar(); } return saveToken(onlyDigits ? Type::Int : Type::Name); } Type BasicTokenizedFile::readString() { reader_.skipChar(); auto offset = reader_.currentPtr(); QByteArray value; while (!reader_.atEnd()) { auto ch = reader_.currentChar(); if (ch == '"') { if (reader_.currentPtr() > offset) { value.append(offset, reader_.currentPtr() - offset); } break; } if (ch == '\n') { reader_.logError(kErrorUnterminatedStringLiteral, lineNumber_) << "unterminated string literal."; failed_ = true; return Type::Invalid; } if (ch == '\\') { if (reader_.currentPtr() > offset) { value.append(offset, reader_.currentPtr() - offset); } reader_.skipChar(); ch = reader_.currentChar(); if (reader_.atEnd() || ch == '\n') { reader_.logError(kErrorUnterminatedStringLiteral, lineNumber_) << "unterminated string literal."; failed_ = true; return Type::Invalid; } offset = reader_.currentPtr() + 1; if (ch == 'n') { value.append('\n'); } else if (ch == 't') { value.append('\t'); } else if (ch == '"') { value.append('"'); } else if (ch == '\\') { value.append('\\'); } } reader_.skipChar(); } if (reader_.atEnd()) { reader_.logError(kErrorUnterminatedStringLiteral, lineNumber_) << "unterminated string literal."; failed_ = true; return Type::Invalid; } CheckedUtf8String checked(value); if (!checked.isValid()) { reader_.logError(kErrorIncorrectUtf8String, lineNumber_) << "incorrect UTF-8 string literal."; failed_ = true; return Type::Invalid; } reader_.skipChar(); return saveToken(Type::String, checked.toString()); } Type BasicTokenizedFile::readSingleLetter() { auto type = singleLetterTokens_.value(reader_.currentChar(), Type::Invalid); if (type == Type::Invalid) { reader_.logError(kErrorIncorrectToken, lineNumber_) << "incorrect token '" << reader_.currentChar() << "'"; return Type::Invalid; } reader_.skipChar(); return saveToken(type); } void BasicTokenizedFile::skipWhitespaces() { if (reader_.atEnd()) return; auto ch = reader_.currentChar(); tokenStartWhitespace_ = isWhitespaceChar(ch); if (tokenStartWhitespace_) { do { if (ch == '\n') { ++lineNumber_; } reader_.skipChar(); ch = reader_.currentChar(); } while (!reader_.atEnd() && isWhitespaceChar(ch)); } tokenStart_ = reader_.currentPtr(); } LogStream operator<<(LogStream &&stream, BasicTokenizedFile::Token::Type type) { const char *value = "'invalid'"; switch (type) { case Type::Invalid: break; case Type::Int: value = "'int'"; break; case Type::Double: value = "'double'"; break; case Type::String: value = "'string'"; break; case Type::LeftParenthesis: value = "'('"; break; case Type::RightParenthesis: value = "')'"; break; case Type::LeftBrace: value = "'{'"; break; case Type::RightBrace: value = "'}'"; break; case Type::LeftBracket: value = "'['"; break; case Type::RightBracket: value = "']'"; break; case Type::Colon: value = "':'"; break; case Type::Semicolon: value = "';'"; break; case Type::Comma: value = "','"; break; case Type::Dot: value = "'.'"; break; case Type::Number: value = "'#'"; break; case Type::Plus: value = "'+'"; break; case Type::Minus: value = "'-'"; break; case Type::Equals: value = "'='"; break; case Type::Name: value = "'identifier'"; break; } return std::forward<LogStream>(stream) << value; } LogStream BasicTokenizedFile::logError(int code) const { return reader_.logError(code, lineNumber_); } LogStream BasicTokenizedFile::logErrorUnexpectedToken() const { if (currentToken_ < tokens_.size()) { auto token = tokens_.at(currentToken_).original.toStdString(); return logError(kErrorUnexpectedToken) << "unexpected token '" << token << "', expected "; } return logError(kErrorUnexpectedToken) << "unexpected token, expected "; } BasicTokenizedFile::~BasicTokenizedFile() = default; } // namespace common } // namespace codegen �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/common/basic_tokenized_file.h��������������������������0000664�0000000�0000000�00000007216�13262451251�0026632�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <memory> #include <QtCore/QMap> #include <QtCore/QString> #include <QtCore/QList> #include "codegen/common/const_utf8_string.h" #include "codegen/common/clean_file_reader.h" namespace codegen { namespace common { class LogStream; // Interface for reading a cleaned from comments file by basic tokens. class BasicTokenizedFile { public: explicit BasicTokenizedFile(const QString &filepath); explicit BasicTokenizedFile(const QByteArray &content, const QString &filepath = QString()); BasicTokenizedFile(const BasicTokenizedFile &other) = delete; BasicTokenizedFile &operator=(const BasicTokenizedFile &other) = delete; struct Token { // String - utf8 string converted to QString. enum class Type { Invalid = 0, Int, Double, String, LeftParenthesis, RightParenthesis, LeftBrace, RightBrace, LeftBracket, RightBracket, Colon, Semicolon, Comma, Dot, Number, Plus, Minus, Equals, And, Or, Name, // [0-9a-zA-Z_]+ with at least one letter. }; Type type; QString value; ConstUtf8String original; bool hasLeftWhitespace; explicit operator bool() const { return (type != Type::Invalid); } }; bool read() { if (reader_.read()) { singleLineComments_ = reader_.singleLineComments(); return true; } return false; } bool atEnd() const { return reader_.atEnd(); } Token getAnyToken(); Token getToken(Token::Type typeCondition); bool putBack(); bool failed() const { return failed_; } QString getCurrentLineComment(); // Log error to std::cerr with 'code' at the current position in file. LogStream logError(int code) const; LogStream logErrorUnexpectedToken() const; ~BasicTokenizedFile(); private: using Type = Token::Type; void skipWhitespaces(); // Reads a token, including complex tokens, like double numbers. Type readToken(); // Read exactly one token, applying condition on the whitespaces. enum class StartWithWhitespace { Allow, Deny, }; Type readOneToken(StartWithWhitespace condition); // helpers Type readNameOrNumber(); Type readString(); Type readSingleLetter(); Type saveToken(Type type, const QString &value = QString()); Type uniteLastTokens(Type type); CleanFileReader reader_; QList<Token> tokens_; int currentToken_ = 0; int lineNumber_ = 1; bool failed_ = false; QVector<QByteArray> singleLineComments_; // Where the last (currently read) token has started. const char *tokenStart_ = nullptr; // Did the last (currently read) token start with a whitespace. bool tokenStartWhitespace_ = false; const QMap<char, Type> singleLetterTokens_ = { { '(', Type::LeftParenthesis }, { ')', Type::RightParenthesis }, { '{', Type::LeftBrace }, { '}', Type::RightBrace }, { '[', Type::LeftBracket }, { ']', Type::RightBracket }, { ':', Type::Colon }, { ';', Type::Semicolon }, { ',', Type::Comma }, { '.', Type::Dot }, { '#', Type::Number }, { '+', Type::Plus }, { '-', Type::Minus }, { '=', Type::Equals }, { '&', Type::And }, { '|', Type::Or }, }; }; LogStream operator<<(LogStream &&stream, BasicTokenizedFile::Token::Type type); template <> LogStream operator<< <BasicTokenizedFile::Token::Type>(LogStream &&stream, BasicTokenizedFile::Token::Type &&value) = delete; template <> LogStream operator<< <const BasicTokenizedFile::Token::Type&>(LogStream &&stream, const BasicTokenizedFile::Token::Type &value) = delete; } // namespace common } // namespace codegen ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/common/checked_utf8_string.cpp�������������������������0000664�0000000�0000000�00000002066�13262451251�0026751�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "codegen/common/checked_utf8_string.h" #include <iostream> #include <QtCore/QTextCodec> #include "codegen/common/const_utf8_string.h" namespace codegen { namespace common { CheckedUtf8String::CheckedUtf8String(const char *string, int size) { if (size < 0) { size = strlen(string); } if (!size) { // Valid empty string return; } QTextCodec::ConverterState state; QTextCodec *codec = QTextCodec::codecForName("UTF-8"); string_ = codec->toUnicode(string, size, &state); if (state.invalidChars > 0) { valid_ = false; } } CheckedUtf8String::CheckedUtf8String(const QByteArray &string) : CheckedUtf8String(string.constData(), string.size()) { } CheckedUtf8String::CheckedUtf8String(const ConstUtf8String &string) : CheckedUtf8String(string.data(), string.size()) { } } // namespace common } // namespace codegen ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/common/checked_utf8_string.h���������������������������0000664�0000000�0000000�00000002014�13262451251�0026407�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <QtCore/QString> class QByteArray; namespace codegen { namespace common { class ConstUtf8String; // Parses a char sequence to a QString using UTF-8 codec. // You can check for invalid UTF-8 sequence by isValid() method. class CheckedUtf8String { public: CheckedUtf8String(const CheckedUtf8String &other) = default; CheckedUtf8String &operator=(const CheckedUtf8String &other) = default; explicit CheckedUtf8String(const char *string, int size = -1); explicit CheckedUtf8String(const QByteArray &string); explicit CheckedUtf8String(const ConstUtf8String &string); bool isValid() const { return valid_; } const QString &toString() const { return string_; } private: QString string_; bool valid_ = true; }; } // namespace common } // namespace codegen ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/common/clean_file.cpp����������������������������������0000664�0000000�0000000�00000010636�13262451251�0025112�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "codegen/common/clean_file.h" #include <iostream> #include <QtCore/QDir> #include "codegen/common/logging.h" namespace codegen { namespace common { namespace { bool readFile(const QString &filepath, QByteArray *outResult) { QFile f(filepath); if (!f.exists()) { logError(kErrorFileNotFound, filepath) << ": error: file does not exist."; return false; } auto limit = CleanFile::MaxSize; if (f.size() > limit) { logError(kErrorFileTooLarge, filepath) << "' is too large, size=" << f.size() << " > maxsize=" << limit; return false; } if (!f.open(QIODevice::ReadOnly)) { logError(kErrorFileNotOpened, filepath) << "' for read."; return false; } *outResult = f.readAll(); return true; } } // namespace CleanFile::CleanFile(const QString &filepath) : filepath_(filepath) , read_(true) { } CleanFile::CleanFile(const QByteArray &content, const QString &filepath) : filepath_(filepath) , content_(content) , read_(false) { } bool CleanFile::read() { if (read_) { if (!readFile(filepath_, &content_)) { return false; } } filepath_ = QFileInfo(filepath_).absoluteFilePath(); enum class InsideComment { None, SingleLine, MultiLine, }; auto insideComment = InsideComment::None; bool insideString = false; const char *begin = content_.cbegin(), *end = content_.cend(), *offset = begin; auto feedContent = [this, &offset, end](const char *ch) { if (ch > offset) { if (result_.isEmpty()) result_.reserve(end - offset - 2); result_.append(offset, ch - offset); offset = ch; } }; auto lineNumber = 0; auto feedComment = [this, &offset, end, &lineNumber](const char *ch, bool save = false) { if (ch > offset) { if (save) { singleLineComments_.resize(lineNumber + 1); singleLineComments_[lineNumber] = QByteArray(offset, ch - offset); } if (result_.isEmpty()) { result_.reserve(end - offset - 2); } result_.append(' '); offset = ch; } }; for (const char *ch = offset; ch != end;) { char currentChar = *ch; char nextChar = (ch + 1 == end) ? 0 : *(ch + 1); if (insideComment == InsideComment::None && currentChar == '"') { bool escaped = ((ch > begin) && *(ch - 1) == '\\') && ((ch - 1 < begin) || *(ch - 2) != '\\'); if (!escaped) { insideString = !insideString; } } if (insideString) { if (currentChar == '\n') { ++lineNumber; } ++ch; continue; } if (insideComment == InsideComment::None && currentChar == '/' && nextChar == '/') { feedContent(ch); insideComment = InsideComment::SingleLine; ch += 2; } else if (insideComment == InsideComment::SingleLine && currentChar == '\r' && nextChar == '\n') { feedComment(ch, true); ch += 2; ++lineNumber; insideComment = InsideComment::None; } else if (insideComment == InsideComment::SingleLine && currentChar == '\n') { feedComment(ch, true); ++ch; ++lineNumber; insideComment = InsideComment::None; } else if (insideComment == InsideComment::None && currentChar == '/' && nextChar == '*') { feedContent(ch); ch += 2; insideComment = InsideComment::MultiLine; } else if (insideComment == InsideComment::MultiLine && currentChar == '*' && nextChar == '/') { ch += 2; feedComment(ch); insideComment = InsideComment::None; } else if (insideComment == InsideComment::MultiLine && currentChar == '\r' && nextChar == '\n') { feedComment(ch); ch += 2; ++lineNumber; feedContent(ch); } else if (insideComment == InsideComment::MultiLine && currentChar == '\n') { feedComment(ch); ++ch; ++lineNumber; feedContent(ch); } else { if (currentChar == '\n') { ++lineNumber; } ++ch; } } singleLineComments_.resize(lineNumber + 1); if (insideComment == InsideComment::MultiLine) { common::logError(kErrorUnexpectedEndOfFile, filepath_); return false; } if (insideComment == InsideComment::None && end > offset) { if (result_.isEmpty()) { result_ = content_; } else { result_.append(offset, end - offset); } } return true; } QVector<QByteArray> CleanFile::singleLineComments() const { return singleLineComments_; } LogStream CleanFile::logError(int code, int line) const { return common::logError(code, filepath_, line); } } // namespace common } // namespace codegen ��������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/common/clean_file.h������������������������������������0000664�0000000�0000000�00000002331�13262451251�0024550�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <QtCore/QString> #include <QtCore/QByteArray> #include <QtCore/QVector> #include "codegen/common/logging.h" namespace codegen { namespace common { // Reads a file removing all C-style comments. class CleanFile { public: explicit CleanFile(const QString &filepath); explicit CleanFile(const QByteArray &content, const QString &filepath = QString()); CleanFile(const CleanFile &other) = delete; CleanFile &operator=(const CleanFile &other) = delete; bool read(); QVector<QByteArray> singleLineComments() const; const char *data() const { return result_.constData(); } const char *end() const { return result_.constEnd(); } static constexpr int MaxSize = 10 * 1024 * 1024; // Log error to std::cerr with 'code' at line number 'line' in data(). LogStream logError(int code, int line) const; private: QString filepath_; QByteArray content_, result_; bool read_; QVector<QByteArray> singleLineComments_; }; } // namespace common } // namespace codegen �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/common/clean_file_reader.h�����������������������������0000664�0000000�0000000�00000002737�13262451251�0026104�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <QtCore/QString> #include "codegen/common/clean_file.h" namespace codegen { namespace common { // Wrapper allows you to read forward the CleanFile without overflow checks. class CleanFileReader { public: explicit CleanFileReader(const QString &filepath) : file_(filepath) { } explicit CleanFileReader(const QByteArray &content, const QString &filepath = QString()) : file_(content, filepath) { } bool read() { if (!file_.read()) { return false; } pos_ = file_.data(); end_ = file_.end(); return true; } bool atEnd() const { return (pos_ == end_); } char currentChar() const { return atEnd() ? 0 : *pos_; } bool skipChar() { if (atEnd()) { return false; } ++pos_; return true; } const char *currentPtr() const { return pos_; } int charsLeft() const { return (end_ - pos_); } QVector<QByteArray> singleLineComments() const { return file_.singleLineComments(); } // Log error to std::cerr with 'code' at line number 'line' in data(). LogStream logError(int code, int line) const { return std::forward<LogStream>(file_.logError(code, line)); } private: CleanFile file_; const char *pos_ = nullptr; const char *end_ = nullptr; }; } // namespace common } // namespace codegen ���������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/common/const_utf8_string.h�����������������������������0000664�0000000�0000000�00000002603�13262451251�0026153�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <string> #include <QtCore/QString> #include <QtCore/QByteArray> namespace codegen { namespace common { // This is a simple wrapper around (const char*, size). // Not null-terminated! It does not hold any ownership. class ConstUtf8String { public: explicit ConstUtf8String(const char *string, int size = -1) : string_(string) { if (size < 0) { size = strlen(string); } size_ = size; } ConstUtf8String(const char *string, const char *end) : ConstUtf8String(string, end - string) { } QByteArray toByteArray() const { return QByteArray(string_, size_); } std::string toStdString() const { return std::string(string_, size_); } QString toStringUnchecked() const { return QString::fromUtf8(string_, size_); } bool empty() const { return size_ == 0; } const char *data() const { return string_; } int size() const { return size_; } const char *end() const { return data() + size(); } ConstUtf8String mid(int pos, int size = -1) { return ConstUtf8String(string_ + pos, std::max(std::min(size, size_ - pos), 0)); } private: const char *string_; int size_; }; } // namespace common } // namespace codegen �����������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/common/cpp_file.cpp������������������������������������0000664�0000000�0000000�00000004450�13262451251�0024607�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "codegen/common/cpp_file.h" #include <QtCore/QFileInfo> #include <QtCore/QDir> namespace codegen { namespace common { namespace { void writeLicense(QTextStream &stream, const ProjectInfo &project) { stream << "\ /*\n\ WARNING! All changes made in this file will be lost!\n\ Created from '" << project.source << "' by '" << project.name << "'\n\ \n\ This file is part of Telegram Desktop,\n\ the official desktop application for the Telegram messaging service.\n\ \n\ For license and copyright information please follow this link:\n\ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL\n\ */\n"; } } // namespace CppFile::CppFile(const QString &path, const ProjectInfo &project) : stream_(&content_) , forceReGenerate_(project.forceReGenerate) { bool cpp = path.toLower().endsWith(".cpp"); QFileInfo info(path); info.dir().mkpath("."); filepath_ = info.absoluteFilePath(); writeLicense(stream_, project); if (cpp) { include(info.baseName() + ".h").newline(); } else { stream() << "#pragma once"; newline().newline(); } } CppFile &CppFile::include(const QString &header) { stream() << "#include \"" << header << "\""; return newline(); } CppFile &CppFile::pushNamespace(const QString &name) { namespaces_.push_back(name); stream() << "namespace"; if (!name.isEmpty()) { stream() << ' ' << name; } stream() << " {"; return newline(); } CppFile &CppFile::popNamespace() { if (namespaces_.isEmpty()) { return *this; } auto name = namespaces_.back(); namespaces_.pop_back(); stream() << "} // namespace"; if (!name.isEmpty()) { stream() << ' ' << name; } return newline(); } bool CppFile::finalize() { while (!namespaces_.isEmpty()) { popNamespace(); } stream_.flush(); QFile file(filepath_); if (!forceReGenerate_ && file.open(QIODevice::ReadOnly)) { if (file.readAll() == content_) { file.close(); return true; } file.close(); } if (!file.open(QIODevice::WriteOnly)) { return false; } if (file.write(content_) != content_.size()) { return false; } return true; } } // namespace common } // namespace codegen������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/common/cpp_file.h��������������������������������������0000664�0000000�0000000�00000002306�13262451251�0024252�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <QtCore/QString> #include <QtCore/QVector> #include <QtCore/QTextStream> namespace codegen { namespace common { struct ProjectInfo { QString name; QString source; bool forceReGenerate; }; // Creates a file with license header and codegen warning. class CppFile { public: // If "basepath" is empty the folder containing "path" will be chosen. // File ending with .cpp will be treated as source, otherwise like header. CppFile(const QString &path, const ProjectInfo &project); QTextStream &stream() { return stream_; } CppFile &newline() { stream() << "\n"; return *this; } CppFile &include(const QString &header); // Empty name adds anonymous namespace. CppFile &pushNamespace(const QString &name = QString()); CppFile &popNamespace(); bool finalize(); private: QString filepath_; QByteArray content_; QTextStream stream_; QVector<QString> namespaces_; bool forceReGenerate_; }; } // namespace common } // namespace codegen ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/common/logging.cpp�������������������������������������0000664�0000000�0000000�00000001664�13262451251�0024460�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "codegen/common/logging.h" #include <iostream> #include <QtCore/QDir> namespace codegen { namespace common { namespace { QString WorkingPath = "."; std::string relativeLocalPath(const QString &filepath) { auto name = QFile::encodeName(QDir(WorkingPath).relativeFilePath(filepath)); return name.constData(); } } // namespace LogStream logError(int code, const QString &filepath, int line) { std::cerr << relativeLocalPath(filepath); if (line > 0) { std::cerr << '(' << line << ')'; } std::cerr << ": error " << code << ": "; return LogStream(std::cerr); } void logSetWorkingPath(const QString &workingpath) { WorkingPath = workingpath; } } // namespace common } // namespace codegen����������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/common/logging.h���������������������������������������0000664�0000000�0000000�00000003163�13262451251�0024121�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <QtCore/QString> #include <iostream> namespace codegen { namespace common { // Common error codes. constexpr int kErrorFileNotFound = 101; constexpr int kErrorFileTooLarge = 102; constexpr int kErrorFileNotOpened = 103; constexpr int kErrorUnexpectedEndOfFile = 104; // Wrapper around std::ostream that adds '\n' to the end of the logging line. class LogStream { public: enum NullType { Null, }; explicit LogStream(NullType) : final_(false) { } explicit LogStream(std::ostream &stream) : stream_(&stream) { } LogStream(LogStream &&other) : stream_(other.stream_), final_(other.final_) { other.final_ = false; } std::ostream *stream() const { return stream_; } ~LogStream() { if (final_) { *stream_ << '\n'; } } private: std::ostream *stream_ = nullptr; bool final_ = true; }; template <typename T> LogStream operator<<(LogStream &&stream, T &&value) { if (auto ostream = stream.stream()) { *ostream << std::forward<T>(value); } return std::forward<LogStream>(stream); } // Outputs file name, line number and error code to std::err. Usage: // logError(kErrorFileTooLarge, filepath) << "file too large, size=" << size; LogStream logError(int code, const QString &filepath, int line = 0); void logSetWorkingPath(const QString &workingpath); static constexpr int kErrorInternal = 666; } // namespace common } // namespace codegen �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/emoji/�������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0022132�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/emoji/data.cpp�����������������������������������������0000664�0000000�0000000�00000136237�13262451251�0023563�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "codegen/emoji/data.h" namespace codegen { namespace emoji { namespace { using uint16 = quint16; using uint32 = quint32; using uint64 = quint64; using std::vector; using std::map; using std::find; using std::make_pair; using std::move; using std::begin; using std::end; using InputId = vector<uint32>; using InputCategory = vector<InputId>; // copied from emoji_box.cpp struct Replace { InputId code; const char *replace; }; Replace Replaces[] = { { { 0xD83DDE0AU }, ":-)" }, { { 0xD83DDE0DU }, "8-)" }, { { 0x2764U }, "<3" }, // { { 0xD83DDC8BU }, ":kiss:" }, // { { 0xD83DDE01U }, ":grin:" }, // { { 0xD83DDE02U }, ":joy:" }, { { 0xD83DDE1AU }, ":-*" }, { { 0xD83DDE06U }, "xD" }, // { { 0xD83DDC4DU }, ":like:" }, // { { 0xD83DDC4EU }, ":dislike:" }, // { { 0x261DU }, ":up:" }, // { { 0x270CU }, ":v:" }, // { { 0xD83DDC4CU }, ":ok:" }, { { 0xD83DDE0EU }, "B-)" }, { { 0xD83DDE03U }, ":-D" }, { { 0xD83DDE09U }, ";-)" }, { { 0xD83DDE1CU }, ";-P" }, { { 0xD83DDE0BU }, ":-p" }, { { 0xD83DDE14U }, "3(" }, { { 0xD83DDE1EU }, ":-(" }, { { 0xD83DDE0FU }, ":]" }, { { 0xD83DDE22U }, ":'(" }, { { 0xD83DDE2DU }, ":_(" }, { { 0xD83DDE29U }, ":((" }, { { 0xD83DDE28U }, ":o" }, { { 0xD83DDE10U }, ":|" }, { { 0xD83DDE0CU }, "3-)" }, { { 0xD83DDE20U }, ">(" }, { { 0xD83DDE21U }, ">((" }, { { 0xD83DDE07U }, "O:)" }, { { 0xD83DDE30U }, ";o" }, { { 0xD83DDE33U }, "8|" }, { { 0xD83DDE32U }, "8o" }, { { 0xD83DDE37U }, ":X" }, { { 0xD83DDE08U }, "}:)" }, }; using ColorId = uint32; ColorId Colors[] = { 0xD83CDFFBU, 0xD83CDFFCU, 0xD83CDFFDU, 0xD83CDFFEU, 0xD83CDFFFU, }; constexpr auto ColorMask = 0xD83CDFFBU; InputCategory ColoredEmoji = { { 0xD83DDC50U, 0xD83CDFFBU, }, { 0xD83DDE4CU, 0xD83CDFFBU, }, { 0xD83DDC4FU, 0xD83CDFFBU, }, { 0xD83DDE4FU, 0xD83CDFFBU, }, { 0xD83DDC4DU, 0xD83CDFFBU, }, { 0xD83DDC4EU, 0xD83CDFFBU, }, { 0xD83DDC4AU, 0xD83CDFFBU, }, { 0x270AU, 0xD83CDFFBU, }, { 0xD83EDD1BU, 0xD83CDFFBU, }, { 0xD83EDD1CU, 0xD83CDFFBU, }, { 0xD83EDD1EU, 0xD83CDFFBU, }, { 0x270CU, 0xD83CDFFBU, }, { 0xD83EDD18U, 0xD83CDFFBU, }, { 0xD83DDC4CU, 0xD83CDFFBU, }, { 0xD83DDC48U, 0xD83CDFFBU, }, { 0xD83DDC49U, 0xD83CDFFBU, }, { 0xD83DDC46U, 0xD83CDFFBU, }, { 0xD83DDC47U, 0xD83CDFFBU, }, { 0x261DU, 0xD83CDFFBU, }, { 0x270BU, 0xD83CDFFBU, }, { 0xD83EDD1AU, 0xD83CDFFBU, }, { 0xD83DDD90U, 0xD83CDFFBU, }, { 0xD83DDD96U, 0xD83CDFFBU, }, { 0xD83DDC4BU, 0xD83CDFFBU, }, { 0xD83EDD19U, 0xD83CDFFBU, }, { 0xD83DDCAAU, 0xD83CDFFBU, }, { 0xD83DDD95U, 0xD83CDFFBU, }, { 0x270DU, 0xD83CDFFBU, }, { 0xD83EDD33U, 0xD83CDFFBU, }, { 0xD83DDC85U, 0xD83CDFFBU, }, { 0xD83DDC42U, 0xD83CDFFBU, }, { 0xD83DDC43U, 0xD83CDFFBU, }, { 0xD83DDC76U, 0xD83CDFFBU, }, { 0xD83DDC66U, 0xD83CDFFBU, }, { 0xD83DDC67U, 0xD83CDFFBU, }, { 0xD83DDC68U, 0xD83CDFFBU, }, { 0xD83DDC69U, 0xD83CDFFBU, }, { 0xD83DDC71U, 0xD83CDFFBU, 0x200DU, 0x2640U, 0xFE0FU, }, { 0xD83DDC71U, 0xD83CDFFBU, }, { 0xD83DDC74U, 0xD83CDFFBU, }, { 0xD83DDC75U, 0xD83CDFFBU, }, { 0xD83DDC72U, 0xD83CDFFBU, }, { 0xD83DDC73U, 0xD83CDFFBU, 0x200DU, 0x2640U, 0xFE0FU, }, { 0xD83DDC73U, 0xD83CDFFBU, }, { 0xD83DDC6EU, 0xD83CDFFBU, 0x200DU, 0x2640U, 0xFE0FU, }, { 0xD83DDC6EU, 0xD83CDFFBU, }, { 0xD83DDC77U, 0xD83CDFFBU, 0x200DU, 0x2640U, 0xFE0FU, }, { 0xD83DDC77U, 0xD83CDFFBU, }, { 0xD83DDC82U, 0xD83CDFFBU, 0x200DU, 0x2640U, 0xFE0FU, }, { 0xD83DDC82U, 0xD83CDFFBU, }, { 0xD83DDD75U, 0xD83CDFFBU, 0x200DU, 0x2640U, 0xFE0FU, }, { 0xD83DDD75U, 0xD83CDFFBU, }, { 0xD83DDC69U, 0xD83CDFFBU, 0x200DU, 0x2695U, 0xFE0FU, }, { 0xD83DDC68U, 0xD83CDFFBU, 0x200DU, 0x2695U, 0xFE0FU, }, { 0xD83DDC69U, 0xD83CDFFBU, 0x200DU, 0xD83CDF3EU, }, { 0xD83DDC68U, 0xD83CDFFBU, 0x200DU, 0xD83CDF3EU, }, { 0xD83DDC69U, 0xD83CDFFBU, 0x200DU, 0xD83CDF73U, }, { 0xD83DDC68U, 0xD83CDFFBU, 0x200DU, 0xD83CDF73U, }, { 0xD83DDC69U, 0xD83CDFFBU, 0x200DU, 0xD83CDF93U, }, { 0xD83DDC68U, 0xD83CDFFBU, 0x200DU, 0xD83CDF93U, }, { 0xD83DDC69U, 0xD83CDFFBU, 0x200DU, 0xD83CDFA4U, }, { 0xD83DDC68U, 0xD83CDFFBU, 0x200DU, 0xD83CDFA4U, }, { 0xD83DDC69U, 0xD83CDFFBU, 0x200DU, 0xD83CDFEBU, }, { 0xD83DDC68U, 0xD83CDFFBU, 0x200DU, 0xD83CDFEBU, }, { 0xD83DDC69U, 0xD83CDFFBU, 0x200DU, 0xD83CDFEDU, }, { 0xD83DDC68U, 0xD83CDFFBU, 0x200DU, 0xD83CDFEDU, }, { 0xD83DDC69U, 0xD83CDFFBU, 0x200DU, 0xD83DDCBBU, }, { 0xD83DDC68U, 0xD83CDFFBU, 0x200DU, 0xD83DDCBBU, }, { 0xD83DDC69U, 0xD83CDFFBU, 0x200DU, 0xD83DDCBCU, }, { 0xD83DDC68U, 0xD83CDFFBU, 0x200DU, 0xD83DDCBCU, }, { 0xD83DDC69U, 0xD83CDFFBU, 0x200DU, 0xD83DDD27U, }, { 0xD83DDC68U, 0xD83CDFFBU, 0x200DU, 0xD83DDD27U, }, { 0xD83DDC69U, 0xD83CDFFBU, 0x200DU, 0xD83DDD2CU, }, { 0xD83DDC68U, 0xD83CDFFBU, 0x200DU, 0xD83DDD2CU, }, { 0xD83DDC69U, 0xD83CDFFBU, 0x200DU, 0xD83CDFA8U, }, { 0xD83DDC68U, 0xD83CDFFBU, 0x200DU, 0xD83CDFA8U, }, { 0xD83DDC69U, 0xD83CDFFBU, 0x200DU, 0xD83DDE92U, }, { 0xD83DDC68U, 0xD83CDFFBU, 0x200DU, 0xD83DDE92U, }, { 0xD83DDC69U, 0xD83CDFFBU, 0x200DU, 0x2708U, 0xFE0FU, }, { 0xD83DDC68U, 0xD83CDFFBU, 0x200DU, 0x2708U, 0xFE0FU, }, { 0xD83DDC69U, 0xD83CDFFBU, 0x200DU, 0xD83DDE80U, }, { 0xD83DDC68U, 0xD83CDFFBU, 0x200DU, 0xD83DDE80U, }, { 0xD83DDC69U, 0xD83CDFFBU, 0x200DU, 0x2696U, 0xFE0FU, }, { 0xD83DDC68U, 0xD83CDFFBU, 0x200DU, 0x2696U, 0xFE0FU, }, { 0xD83EDD36U, 0xD83CDFFBU, }, { 0xD83CDF85U, 0xD83CDFFBU, }, { 0xD83DDC78U, 0xD83CDFFBU, }, { 0xD83EDD34U, 0xD83CDFFBU, }, { 0xD83DDC70U, 0xD83CDFFBU, }, { 0xD83EDD35U, 0xD83CDFFBU, }, { 0xD83DDC7CU, 0xD83CDFFBU, }, { 0xD83EDD30U, 0xD83CDFFBU, }, { 0xD83DDE47U, 0xD83CDFFBU, 0x200DU, 0x2640U, 0xFE0FU, }, { 0xD83DDE47U, 0xD83CDFFBU, }, { 0xD83DDC81U, 0xD83CDFFBU, }, { 0xD83DDC81U, 0xD83CDFFBU, 0x200DU, 0x2642U, 0xFE0FU, }, { 0xD83DDE45U, 0xD83CDFFBU, }, { 0xD83DDE45U, 0xD83CDFFBU, 0x200DU, 0x2642U, 0xFE0FU, }, { 0xD83DDE46U, 0xD83CDFFBU, }, { 0xD83DDE46U, 0xD83CDFFBU, 0x200DU, 0x2642U, 0xFE0FU, }, { 0xD83DDE4BU, 0xD83CDFFBU, }, { 0xD83DDE4BU, 0xD83CDFFBU, 0x200DU, 0x2642U, 0xFE0FU, }, { 0xD83EDD26U, 0xD83CDFFBU, 0x200DU, 0x2640U, 0xFE0FU, }, { 0xD83EDD26U, 0xD83CDFFBU, 0x200DU, 0x2642U, 0xFE0FU, }, { 0xD83EDD37U, 0xD83CDFFBU, 0x200DU, 0x2640U, 0xFE0FU, }, { 0xD83EDD37U, 0xD83CDFFBU, 0x200DU, 0x2642U, 0xFE0FU, }, { 0xD83DDE4EU, 0xD83CDFFBU, }, { 0xD83DDE4EU, 0xD83CDFFBU, 0x200DU, 0x2642U, 0xFE0FU, }, { 0xD83DDE4DU, 0xD83CDFFBU, }, { 0xD83DDE4DU, 0xD83CDFFBU, 0x200DU, 0x2642U, 0xFE0FU, }, { 0xD83DDC87U, 0xD83CDFFBU, }, { 0xD83DDC87U, 0xD83CDFFBU, 0x200DU, 0x2642U, 0xFE0FU, }, { 0xD83DDC86U, 0xD83CDFFBU, }, { 0xD83DDC86U, 0xD83CDFFBU, 0x200DU, 0x2642U, 0xFE0FU, }, { 0xD83DDD74U, 0xD83CDFFBU, }, { 0xD83DDC83U, 0xD83CDFFBU, }, { 0xD83DDD7AU, 0xD83CDFFBU, }, { 0xD83DDEB6U, 0xD83CDFFBU, 0x200DU, 0x2640U, 0xFE0FU, }, { 0xD83DDEB6U, 0xD83CDFFBU, }, { 0xD83CDFC3U, 0xD83CDFFBU, 0x200DU, 0x2640U, 0xFE0FU, }, { 0xD83CDFC3U, 0xD83CDFFBU, }, { 0xD83CDFCBU, 0xD83CDFFBU, 0x200DU, 0x2640U, 0xFE0FU, }, { 0xD83CDFCBU, 0xD83CDFFBU, }, { 0xD83EDD38U, 0xD83CDFFBU, 0x200DU, 0x2640U, 0xFE0FU, }, { 0xD83EDD38U, 0xD83CDFFBU, 0x200DU, 0x2642U, 0xFE0FU, }, { 0x26F9U, 0xD83CDFFBU, 0x200DU, 0x2640U, 0xFE0FU, }, { 0x26F9U, 0xD83CDFFBU, }, { 0xD83EDD3EU, 0xD83CDFFBU, 0x200DU, 0x2640U, 0xFE0FU, }, { 0xD83EDD3EU, 0xD83CDFFBU, 0x200DU, 0x2642U, 0xFE0FU, }, { 0xD83CDFCCU, 0xD83CDFFBU, 0x200DU, 0x2640U, 0xFE0FU, }, { 0xD83CDFCCU, 0xD83CDFFBU, }, { 0xD83CDFC4U, 0xD83CDFFBU, 0x200DU, 0x2640U, 0xFE0FU, }, { 0xD83CDFC4U, 0xD83CDFFBU, }, { 0xD83CDFCAU, 0xD83CDFFBU, 0x200DU, 0x2640U, 0xFE0FU, }, { 0xD83CDFCAU, 0xD83CDFFBU, }, { 0xD83EDD3DU, 0xD83CDFFBU, 0x200DU, 0x2640U, 0xFE0FU, }, { 0xD83EDD3DU, 0xD83CDFFBU, 0x200DU, 0x2642U, 0xFE0FU, }, { 0xD83DDEA3U, 0xD83CDFFBU, 0x200DU, 0x2640U, 0xFE0FU, }, { 0xD83DDEA3U, 0xD83CDFFBU, }, { 0xD83CDFC7U, 0xD83CDFFBU, }, { 0xD83DDEB4U, 0xD83CDFFBU, 0x200DU, 0x2640U, 0xFE0FU, }, { 0xD83DDEB4U, 0xD83CDFFBU, }, { 0xD83DDEB5U, 0xD83CDFFBU, 0x200DU, 0x2640U, 0xFE0FU, }, { 0xD83DDEB5U, 0xD83CDFFBU, }, { 0xD83EDD39U, 0xD83CDFFBU, 0x200DU, 0x2640U, 0xFE0FU, }, { 0xD83EDD39U, 0xD83CDFFBU, 0x200DU, 0x2642U, 0xFE0FU, }, { 0xD83DDEC0U, 0xD83CDFFBU, }, }; InputCategory Category1 = { { 0xD83DDE00U, }, { 0xD83DDE03U, }, { 0xD83DDE04U, }, { 0xD83DDE01U, }, { 0xD83DDE06U, }, { 0xD83DDE05U, }, { 0xD83DDE02U, }, { 0xD83EDD23U, }, { 0x263AU, 0xFE0FU, }, { 0xD83DDE0AU, }, { 0xD83DDE07U, }, { 0xD83DDE42U, }, { 0xD83DDE43U, }, { 0xD83DDE09U, }, { 0xD83DDE0CU, }, { 0xD83DDE0DU, }, { 0xD83DDE18U, }, { 0xD83DDE17U, }, { 0xD83DDE19U, }, { 0xD83DDE1AU, }, { 0xD83DDE0BU, }, { 0xD83DDE1CU, }, { 0xD83DDE1DU, }, { 0xD83DDE1BU, }, { 0xD83EDD11U, }, { 0xD83EDD17U, }, { 0xD83EDD13U, }, { 0xD83DDE0EU, }, { 0xD83EDD21U, }, { 0xD83EDD20U, }, { 0xD83DDE0FU, }, { 0xD83DDE12U, }, { 0xD83DDE1EU, }, { 0xD83DDE14U, }, { 0xD83DDE1FU, }, { 0xD83DDE15U, }, { 0xD83DDE41U, }, { 0x2639U, 0xFE0FU, }, { 0xD83DDE23U, }, { 0xD83DDE16U, }, { 0xD83DDE2BU, }, { 0xD83DDE29U, }, { 0xD83DDE24U, }, { 0xD83DDE20U, }, { 0xD83DDE21U, }, { 0xD83DDE36U, }, { 0xD83DDE10U, }, { 0xD83DDE11U, }, { 0xD83DDE2FU, }, { 0xD83DDE26U, }, { 0xD83DDE27U, }, { 0xD83DDE2EU, }, { 0xD83DDE32U, }, { 0xD83DDE35U, }, { 0xD83DDE33U, }, { 0xD83DDE31U, }, { 0xD83DDE28U, }, { 0xD83DDE30U, }, { 0xD83DDE22U, }, { 0xD83DDE25U, }, { 0xD83EDD24U, }, { 0xD83DDE2DU, }, { 0xD83DDE13U, }, { 0xD83DDE2AU, }, { 0xD83DDE34U, }, { 0xD83DDE44U, }, { 0xD83EDD14U, }, { 0xD83EDD25U, }, { 0xD83DDE2CU, }, { 0xD83EDD10U, }, { 0xD83EDD22U, }, { 0xD83EDD27U, }, { 0xD83DDE37U, }, { 0xD83EDD12U, }, { 0xD83EDD15U, }, { 0xD83DDE08U, }, { 0xD83DDC7FU, }, { 0xD83DDC79U, }, { 0xD83DDC7AU, }, { 0xD83DDCA9U, }, { 0xD83DDC7BU, }, { 0xD83DDC80U, }, { 0x2620U, 0xFE0FU, }, { 0xD83DDC7DU, }, { 0xD83DDC7EU, }, { 0xD83EDD16U, }, { 0xD83CDF83U, }, { 0xD83DDE3AU, }, { 0xD83DDE38U, }, { 0xD83DDE39U, }, { 0xD83DDE3BU, }, { 0xD83DDE3CU, }, { 0xD83DDE3DU, }, { 0xD83DDE40U, }, { 0xD83DDE3FU, }, { 0xD83DDE3EU, }, { 0xD83DDC50U, }, { 0xD83DDE4CU, }, { 0xD83DDC4FU, }, { 0xD83DDE4FU, }, { 0xD83EDD1DU, }, { 0xD83DDC4DU, }, { 0xD83DDC4EU, }, { 0xD83DDC4AU, }, { 0x270AU, 0xFE0FU, }, { 0xD83EDD1BU, }, { 0xD83EDD1CU, }, { 0xD83EDD1EU, }, { 0x270CU, 0xFE0FU, }, { 0xD83EDD18U, }, { 0xD83DDC4CU, }, { 0xD83DDC48U, }, { 0xD83DDC49U, }, { 0xD83DDC46U, }, { 0xD83DDC47U, }, { 0x261DU, 0xFE0FU, }, { 0x270BU, 0xFE0FU, }, { 0xD83EDD1AU, }, { 0xD83DDD90U, }, { 0xD83DDD96U, }, { 0xD83DDC4BU, }, { 0xD83EDD19U, }, { 0xD83DDCAAU, }, { 0xD83DDD95U, }, { 0x270DU, 0xFE0FU, }, { 0xD83EDD33U, }, { 0xD83DDC85U, }, { 0xD83DDC8DU, }, { 0xD83DDC84U, }, { 0xD83DDC8BU, }, { 0xD83DDC44U, }, { 0xD83DDC45U, }, { 0xD83DDC42U, }, { 0xD83DDC43U, }, { 0xD83DDC63U, }, { 0xD83DDC41U, }, { 0xD83DDC40U, }, { 0xD83DDDE3U, }, { 0xD83DDC64U, }, { 0xD83DDC65U, }, { 0xD83DDC76U, }, { 0xD83DDC66U, }, { 0xD83DDC67U, }, { 0xD83DDC68U, }, { 0xD83DDC69U, }, { 0xD83DDC71U, 0x200DU, 0x2640U, 0xFE0FU, }, { 0xD83DDC71U, }, { 0xD83DDC74U, }, { 0xD83DDC75U, }, { 0xD83DDC72U, }, { 0xD83DDC73U, 0x200DU, 0x2640U, 0xFE0FU, }, { 0xD83DDC73U, }, { 0xD83DDC6EU, 0x200DU, 0x2640U, 0xFE0FU, }, { 0xD83DDC6EU, }, { 0xD83DDC77U, 0x200DU, 0x2640U, 0xFE0FU, }, { 0xD83DDC77U, }, { 0xD83DDC82U, 0x200DU, 0x2640U, 0xFE0FU, }, { 0xD83DDC82U, }, { 0xD83DDD75U, 0xFE0FU, 0x200DU, 0x2640U, 0xFE0FU, }, { 0xD83DDD75U, 0xFE0FU, }, { 0xD83DDC69U, 0x200DU, 0x2695U, 0xFE0FU, }, { 0xD83DDC68U, 0x200DU, 0x2695U, 0xFE0FU, }, { 0xD83DDC69U, 0x200DU, 0xD83CDF3EU, }, { 0xD83DDC68U, 0x200DU, 0xD83CDF3EU, }, { 0xD83DDC69U, 0x200DU, 0xD83CDF73U, }, { 0xD83DDC68U, 0x200DU, 0xD83CDF73U, }, { 0xD83DDC69U, 0x200DU, 0xD83CDF93U, }, { 0xD83DDC68U, 0x200DU, 0xD83CDF93U, }, { 0xD83DDC69U, 0x200DU, 0xD83CDFA4U, }, { 0xD83DDC68U, 0x200DU, 0xD83CDFA4U, }, { 0xD83DDC69U, 0x200DU, 0xD83CDFEBU, }, { 0xD83DDC68U, 0x200DU, 0xD83CDFEBU, }, { 0xD83DDC69U, 0x200DU, 0xD83CDFEDU, }, { 0xD83DDC68U, 0x200DU, 0xD83CDFEDU, }, { 0xD83DDC69U, 0x200DU, 0xD83DDCBBU, }, { 0xD83DDC68U, 0x200DU, 0xD83DDCBBU, }, { 0xD83DDC69U, 0x200DU, 0xD83DDCBCU, }, { 0xD83DDC68U, 0x200DU, 0xD83DDCBCU, }, { 0xD83DDC69U, 0x200DU, 0xD83DDD27U, }, { 0xD83DDC68U, 0x200DU, 0xD83DDD27U, }, { 0xD83DDC69U, 0x200DU, 0xD83DDD2CU, }, { 0xD83DDC68U, 0x200DU, 0xD83DDD2CU, }, { 0xD83DDC69U, 0x200DU, 0xD83CDFA8U, }, { 0xD83DDC68U, 0x200DU, 0xD83CDFA8U, }, { 0xD83DDC69U, 0x200DU, 0xD83DDE92U, }, { 0xD83DDC68U, 0x200DU, 0xD83DDE92U, }, { 0xD83DDC69U, 0x200DU, 0x2708U, 0xFE0FU, }, { 0xD83DDC68U, 0x200DU, 0x2708U, 0xFE0FU, }, { 0xD83DDC69U, 0x200DU, 0xD83DDE80U, }, { 0xD83DDC68U, 0x200DU, 0xD83DDE80U, }, { 0xD83DDC69U, 0x200DU, 0x2696U, 0xFE0FU, }, { 0xD83DDC68U, 0x200DU, 0x2696U, 0xFE0FU, }, { 0xD83EDD36U, }, { 0xD83CDF85U, }, { 0xD83DDC78U, }, { 0xD83EDD34U, }, { 0xD83DDC70U, }, { 0xD83EDD35U, }, { 0xD83DDC7CU, }, { 0xD83EDD30U, }, { 0xD83DDE47U, 0x200DU, 0x2640U, 0xFE0FU, }, { 0xD83DDE47U, }, { 0xD83DDC81U, }, { 0xD83DDC81U, 0x200DU, 0x2642U, 0xFE0FU, }, { 0xD83DDE45U, }, { 0xD83DDE45U, 0x200DU, 0x2642U, 0xFE0FU, }, { 0xD83DDE46U, }, { 0xD83DDE46U, 0x200DU, 0x2642U, 0xFE0FU, }, { 0xD83DDE4BU, }, { 0xD83DDE4BU, 0x200DU, 0x2642U, 0xFE0FU, }, { 0xD83EDD26U, 0x200DU, 0x2640U, 0xFE0FU, }, { 0xD83EDD26U, 0x200DU, 0x2642U, 0xFE0FU, }, { 0xD83EDD37U, 0x200DU, 0x2640U, 0xFE0FU, }, { 0xD83EDD37U, 0x200DU, 0x2642U, 0xFE0FU, }, { 0xD83DDE4EU, }, { 0xD83DDE4EU, 0x200DU, 0x2642U, 0xFE0FU, }, { 0xD83DDE4DU, }, { 0xD83DDE4DU, 0x200DU, 0x2642U, 0xFE0FU, }, { 0xD83DDC87U, }, { 0xD83DDC87U, 0x200DU, 0x2642U, 0xFE0FU, }, { 0xD83DDC86U, }, { 0xD83DDC86U, 0x200DU, 0x2642U, 0xFE0FU, }, { 0xD83DDD74U, }, { 0xD83DDC83U, }, { 0xD83DDD7AU, }, { 0xD83DDC6FU, }, { 0xD83DDC6FU, 0x200DU, 0x2642U, 0xFE0FU, }, { 0xD83DDEB6U, 0x200DU, 0x2640U, 0xFE0FU, }, { 0xD83DDEB6U, }, { 0xD83CDFC3U, 0x200DU, 0x2640U, 0xFE0FU, }, { 0xD83CDFC3U, }, { 0xD83DDC6BU, }, { 0xD83DDC6DU, }, { 0xD83DDC6CU, }, { 0xD83DDC91U, }, { 0xD83DDC69U, 0x200DU, 0x2764U, 0xFE0FU, 0x200DU, 0xD83DDC69U, }, { 0xD83DDC68U, 0x200DU, 0x2764U, 0xFE0FU, 0x200DU, 0xD83DDC68U, }, { 0xD83DDC8FU, }, { 0xD83DDC69U, 0x200DU, 0x2764U, 0xFE0FU, 0x200DU, 0xD83DDC8BU, 0x200DU, 0xD83DDC69U, }, { 0xD83DDC68U, 0x200DU, 0x2764U, 0xFE0FU, 0x200DU, 0xD83DDC8BU, 0x200DU, 0xD83DDC68U, }, { 0xD83DDC6AU, }, { 0xD83DDC68U, 0x200DU, 0xD83DDC69U, 0x200DU, 0xD83DDC67U, }, { 0xD83DDC68U, 0x200DU, 0xD83DDC69U, 0x200DU, 0xD83DDC67U, 0x200DU, 0xD83DDC66U, }, { 0xD83DDC68U, 0x200DU, 0xD83DDC69U, 0x200DU, 0xD83DDC66U, 0x200DU, 0xD83DDC66U, }, { 0xD83DDC68U, 0x200DU, 0xD83DDC69U, 0x200DU, 0xD83DDC67U, 0x200DU, 0xD83DDC67U, }, { 0xD83DDC69U, 0x200DU, 0xD83DDC69U, 0x200DU, 0xD83DDC66U, }, { 0xD83DDC69U, 0x200DU, 0xD83DDC69U, 0x200DU, 0xD83DDC67U, }, { 0xD83DDC69U, 0x200DU, 0xD83DDC69U, 0x200DU, 0xD83DDC67U, 0x200DU, 0xD83DDC66U, }, { 0xD83DDC69U, 0x200DU, 0xD83DDC69U, 0x200DU, 0xD83DDC66U, 0x200DU, 0xD83DDC66U, }, { 0xD83DDC69U, 0x200DU, 0xD83DDC69U, 0x200DU, 0xD83DDC67U, 0x200DU, 0xD83DDC67U, }, { 0xD83DDC68U, 0x200DU, 0xD83DDC68U, 0x200DU, 0xD83DDC66U, }, { 0xD83DDC68U, 0x200DU, 0xD83DDC68U, 0x200DU, 0xD83DDC67U, }, { 0xD83DDC68U, 0x200DU, 0xD83DDC68U, 0x200DU, 0xD83DDC67U, 0x200DU, 0xD83DDC66U, }, { 0xD83DDC68U, 0x200DU, 0xD83DDC68U, 0x200DU, 0xD83DDC66U, 0x200DU, 0xD83DDC66U, }, { 0xD83DDC68U, 0x200DU, 0xD83DDC68U, 0x200DU, 0xD83DDC67U, 0x200DU, 0xD83DDC67U, }, { 0xD83DDC69U, 0x200DU, 0xD83DDC66U, }, { 0xD83DDC69U, 0x200DU, 0xD83DDC67U, }, { 0xD83DDC69U, 0x200DU, 0xD83DDC67U, 0x200DU, 0xD83DDC66U, }, { 0xD83DDC69U, 0x200DU, 0xD83DDC66U, 0x200DU, 0xD83DDC66U, }, { 0xD83DDC69U, 0x200DU, 0xD83DDC67U, 0x200DU, 0xD83DDC67U, }, { 0xD83DDC68U, 0x200DU, 0xD83DDC66U, }, { 0xD83DDC68U, 0x200DU, 0xD83DDC67U, }, { 0xD83DDC68U, 0x200DU, 0xD83DDC67U, 0x200DU, 0xD83DDC66U, }, { 0xD83DDC68U, 0x200DU, 0xD83DDC66U, 0x200DU, 0xD83DDC66U, }, { 0xD83DDC68U, 0x200DU, 0xD83DDC67U, 0x200DU, 0xD83DDC67U, }, { 0xD83DDC5AU, }, { 0xD83DDC55U, }, { 0xD83DDC56U, }, { 0xD83DDC54U, }, { 0xD83DDC57U, }, { 0xD83DDC59U, }, { 0xD83DDC58U, }, { 0xD83DDC60U, }, { 0xD83DDC61U, }, { 0xD83DDC62U, }, { 0xD83DDC5EU, }, { 0xD83DDC5FU, }, { 0xD83DDC52U, }, { 0xD83CDFA9U, }, { 0xD83CDF93U, }, { 0xD83DDC51U, }, { 0x26D1U, }, { 0xD83CDF92U, }, { 0xD83DDC5DU, }, { 0xD83DDC5BU, }, { 0xD83DDC5CU, }, { 0xD83DDCBCU, }, { 0xD83DDC53U, }, { 0xD83DDD76U, }, { 0xD83CDF02U, }, { 0x2602U, 0xFE0FU, }, }; InputCategory Category2 = { { 0xD83DDC36U, }, { 0xD83DDC31U, }, { 0xD83DDC2DU, }, { 0xD83DDC39U, }, { 0xD83DDC30U, }, { 0xD83EDD8AU, }, { 0xD83DDC3BU, }, { 0xD83DDC3CU, }, { 0xD83DDC28U, }, { 0xD83DDC2FU, }, { 0xD83EDD81U, }, { 0xD83DDC2EU, }, { 0xD83DDC37U, }, { 0xD83DDC3DU, }, { 0xD83DDC38U, }, { 0xD83DDC35U, }, { 0xD83DDE48U, }, { 0xD83DDE49U, }, { 0xD83DDE4AU, }, { 0xD83DDC12U, }, { 0xD83DDC14U, }, { 0xD83DDC27U, }, { 0xD83DDC26U, }, { 0xD83DDC24U, }, { 0xD83DDC23U, }, { 0xD83DDC25U, }, { 0xD83EDD86U, }, { 0xD83EDD85U, }, { 0xD83EDD89U, }, { 0xD83EDD87U, }, { 0xD83DDC3AU, }, { 0xD83DDC17U, }, { 0xD83DDC34U, }, { 0xD83EDD84U, }, { 0xD83DDC1DU, }, { 0xD83DDC1BU, }, { 0xD83EDD8BU, }, { 0xD83DDC0CU, }, { 0xD83DDC1AU, }, { 0xD83DDC1EU, }, { 0xD83DDC1CU, }, { 0xD83DDD77U, }, { 0xD83DDD78U, }, { 0xD83DDC22U, }, { 0xD83DDC0DU, }, { 0xD83EDD8EU, }, { 0xD83EDD82U, }, { 0xD83EDD80U, }, { 0xD83EDD91U, }, { 0xD83DDC19U, }, { 0xD83EDD90U, }, { 0xD83DDC20U, }, { 0xD83DDC1FU, }, { 0xD83DDC21U, }, { 0xD83DDC2CU, }, { 0xD83EDD88U, }, { 0xD83DDC33U, }, { 0xD83DDC0BU, }, { 0xD83DDC0AU, }, { 0xD83DDC06U, }, { 0xD83DDC05U, }, { 0xD83DDC03U, }, { 0xD83DDC02U, }, { 0xD83DDC04U, }, { 0xD83EDD8CU, }, { 0xD83DDC2AU, }, { 0xD83DDC2BU, }, { 0xD83DDC18U, }, { 0xD83EDD8FU, }, { 0xD83EDD8DU, }, { 0xD83DDC0EU, }, { 0xD83DDC16U, }, { 0xD83DDC10U, }, { 0xD83DDC0FU, }, { 0xD83DDC11U, }, { 0xD83DDC15U, }, { 0xD83DDC29U, }, { 0xD83DDC08U, }, { 0xD83DDC13U, }, { 0xD83EDD83U, }, { 0xD83DDD4AU, }, { 0xD83DDC07U, }, { 0xD83DDC01U, }, { 0xD83DDC00U, }, { 0xD83DDC3FU, }, { 0xD83DDC3EU, }, { 0xD83DDC09U, }, { 0xD83DDC32U, }, { 0xD83CDF35U, }, { 0xD83CDF84U, }, { 0xD83CDF32U, }, { 0xD83CDF33U, }, { 0xD83CDF34U, }, { 0xD83CDF31U, }, { 0xD83CDF3FU, }, { 0x2618U, 0xFE0FU, }, { 0xD83CDF40U, }, { 0xD83CDF8DU, }, { 0xD83CDF8BU, }, { 0xD83CDF43U, }, { 0xD83CDF42U, }, { 0xD83CDF41U, }, { 0xD83CDF44U, }, { 0xD83CDF3EU, }, { 0xD83DDC90U, }, { 0xD83CDF37U, }, { 0xD83CDF39U, }, { 0xD83EDD40U, }, { 0xD83CDF3BU, }, { 0xD83CDF3CU, }, { 0xD83CDF38U, }, { 0xD83CDF3AU, }, { 0xD83CDF0EU, }, { 0xD83CDF0DU, }, { 0xD83CDF0FU, }, { 0xD83CDF15U, }, { 0xD83CDF16U, }, { 0xD83CDF17U, }, { 0xD83CDF18U, }, { 0xD83CDF11U, }, { 0xD83CDF12U, }, { 0xD83CDF13U, }, { 0xD83CDF14U, }, { 0xD83CDF1AU, }, { 0xD83CDF1DU, }, { 0xD83CDF1EU, }, { 0xD83CDF1BU, }, { 0xD83CDF1CU, }, { 0xD83CDF19U, }, { 0xD83DDCABU, }, { 0x2B50U, 0xFE0FU, }, { 0xD83CDF1FU, }, { 0x2728U, }, { 0x26A1U, 0xFE0FU, }, { 0xD83DDD25U, }, { 0xD83DDCA5U, }, { 0x2604U, 0xFE0FU, }, { 0x2600U, 0xFE0FU, }, { 0xD83CDF24U, }, { 0x26C5U, 0xFE0FU, }, { 0xD83CDF25U, }, { 0xD83CDF26U, }, { 0xD83CDF08U, }, { 0x2601U, 0xFE0FU, }, { 0xD83CDF27U, }, { 0x26C8U, }, { 0xD83CDF29U, }, { 0xD83CDF28U, }, { 0x2603U, 0xFE0FU, }, { 0x26C4U, 0xFE0FU, }, { 0x2744U, 0xFE0FU, }, { 0xD83CDF2CU, }, { 0xD83DDCA8U, }, { 0xD83CDF2AU, }, { 0xD83CDF2BU, }, { 0xD83CDF0AU, }, { 0xD83DDCA7U, }, { 0xD83DDCA6U, }, { 0x2614U, 0xFE0FU, }, }; InputCategory Category3 = { { 0xD83CDF4FU, }, { 0xD83CDF4EU, }, { 0xD83CDF50U, }, { 0xD83CDF4AU, }, { 0xD83CDF4BU, }, { 0xD83CDF4CU, }, { 0xD83CDF49U, }, { 0xD83CDF47U, }, { 0xD83CDF53U, }, { 0xD83CDF48U, }, { 0xD83CDF52U, }, { 0xD83CDF51U, }, { 0xD83CDF4DU, }, { 0xD83EDD5DU, }, { 0xD83EDD51U, }, { 0xD83CDF45U, }, { 0xD83CDF46U, }, { 0xD83EDD52U, }, { 0xD83EDD55U, }, { 0xD83CDF3DU, }, { 0xD83CDF36U, }, { 0xD83EDD54U, }, { 0xD83CDF60U, }, { 0xD83CDF30U, }, { 0xD83EDD5CU, }, { 0xD83CDF6FU, }, { 0xD83EDD50U, }, { 0xD83CDF5EU, }, { 0xD83EDD56U, }, { 0xD83EDDC0U, }, { 0xD83EDD5AU, }, { 0xD83CDF73U, }, { 0xD83EDD53U, }, { 0xD83EDD5EU, }, { 0xD83CDF64U, }, { 0xD83CDF57U, }, { 0xD83CDF56U, }, { 0xD83CDF55U, }, { 0xD83CDF2DU, }, { 0xD83CDF54U, }, { 0xD83CDF5FU, }, { 0xD83EDD59U, }, { 0xD83CDF2EU, }, { 0xD83CDF2FU, }, { 0xD83EDD57U, }, { 0xD83EDD58U, }, { 0xD83CDF5DU, }, { 0xD83CDF5CU, }, { 0xD83CDF72U, }, { 0xD83CDF65U, }, { 0xD83CDF63U, }, { 0xD83CDF71U, }, { 0xD83CDF5BU, }, { 0xD83CDF59U, }, { 0xD83CDF5AU, }, { 0xD83CDF58U, }, { 0xD83CDF62U, }, { 0xD83CDF61U, }, { 0xD83CDF67U, }, { 0xD83CDF68U, }, { 0xD83CDF66U, }, { 0xD83CDF70U, }, { 0xD83CDF82U, }, { 0xD83CDF6EU, }, { 0xD83CDF6DU, }, { 0xD83CDF6CU, }, { 0xD83CDF6BU, }, { 0xD83CDF7FU, }, { 0xD83CDF69U, }, { 0xD83CDF6AU, }, { 0xD83EDD5BU, }, { 0xD83CDF7CU, }, { 0x2615U, 0xFE0FU, }, { 0xD83CDF75U, }, { 0xD83CDF76U, }, { 0xD83CDF7AU, }, { 0xD83CDF7BU, }, { 0xD83EDD42U, }, { 0xD83CDF77U, }, { 0xD83EDD43U, }, { 0xD83CDF78U, }, { 0xD83CDF79U, }, { 0xD83CDF7EU, }, { 0xD83EDD44U, }, { 0xD83CDF74U, }, { 0xD83CDF7DU, }, }; InputCategory Category4 = { { 0x26BDU, 0xFE0FU, }, { 0xD83CDFC0U, }, { 0xD83CDFC8U, }, { 0x26BEU, 0xFE0FU, }, { 0xD83CDFBEU, }, { 0xD83CDFD0U, }, { 0xD83CDFC9U, }, { 0xD83CDFB1U, }, { 0xD83CDFD3U, }, { 0xD83CDFF8U, }, { 0xD83EDD45U, }, { 0xD83CDFD2U, }, { 0xD83CDFD1U, }, { 0xD83CDFCFU, }, { 0x26F3U, 0xFE0FU, }, { 0xD83CDFF9U, }, { 0xD83CDFA3U, }, { 0xD83EDD4AU, }, { 0xD83EDD4BU, }, { 0x26F8U, }, { 0xD83CDFBFU, }, { 0x26F7U, }, { 0xD83CDFC2U, }, { 0xD83CDFCBU, 0xFE0FU, 0x200DU, 0x2640U, 0xFE0FU, }, { 0xD83CDFCBU, 0xFE0FU, }, { 0xD83EDD3AU, }, { 0xD83EDD3CU, 0x200DU, 0x2640U, 0xFE0FU, }, { 0xD83EDD3CU, 0x200DU, 0x2642U, 0xFE0FU, }, { 0xD83EDD38U, 0x200DU, 0x2640U, 0xFE0FU, }, { 0xD83EDD38U, 0x200DU, 0x2642U, 0xFE0FU, }, { 0x26F9U, 0xFE0FU, 0x200DU, 0x2640U, 0xFE0FU, }, { 0x26F9U, 0xFE0FU, }, { 0xD83EDD3EU, 0x200DU, 0x2640U, 0xFE0FU, }, { 0xD83EDD3EU, 0x200DU, 0x2642U, 0xFE0FU, }, { 0xD83CDFCCU, 0xFE0FU, 0x200DU, 0x2640U, 0xFE0FU, }, { 0xD83CDFCCU, 0xFE0FU, }, { 0xD83CDFC4U, 0x200DU, 0x2640U, 0xFE0FU, }, { 0xD83CDFC4U, }, { 0xD83CDFCAU, 0x200DU, 0x2640U, 0xFE0FU, }, { 0xD83CDFCAU, }, { 0xD83EDD3DU, 0x200DU, 0x2640U, 0xFE0FU, }, { 0xD83EDD3DU, 0x200DU, 0x2642U, 0xFE0FU, }, { 0xD83DDEA3U, 0x200DU, 0x2640U, 0xFE0FU, }, { 0xD83DDEA3U, }, { 0xD83CDFC7U, }, { 0xD83DDEB4U, 0x200DU, 0x2640U, 0xFE0FU, }, { 0xD83DDEB4U, }, { 0xD83DDEB5U, 0x200DU, 0x2640U, 0xFE0FU, }, { 0xD83DDEB5U, }, { 0xD83CDFBDU, }, { 0xD83CDFC5U, }, { 0xD83CDF96U, }, { 0xD83EDD47U, }, { 0xD83EDD48U, }, { 0xD83EDD49U, }, { 0xD83CDFC6U, }, { 0xD83CDFF5U, }, { 0xD83CDF97U, }, { 0xD83CDFABU, }, { 0xD83CDF9FU, }, { 0xD83CDFAAU, }, { 0xD83EDD39U, 0x200DU, 0x2640U, 0xFE0FU, }, { 0xD83EDD39U, 0x200DU, 0x2642U, 0xFE0FU, }, { 0xD83CDFADU, }, { 0xD83CDFA8U, }, { 0xD83CDFACU, }, { 0xD83CDFA4U, }, { 0xD83CDFA7U, }, { 0xD83CDFBCU, }, { 0xD83CDFB9U, }, { 0xD83EDD41U, }, { 0xD83CDFB7U, }, { 0xD83CDFBAU, }, { 0xD83CDFB8U, }, { 0xD83CDFBBU, }, { 0xD83CDFB2U, }, { 0xD83CDFAFU, }, { 0xD83CDFB3U, }, { 0xD83CDFAEU, }, { 0xD83CDFB0U, }, }; InputCategory Category5 = { { 0xD83DDE97U, }, { 0xD83DDE95U, }, { 0xD83DDE99U, }, { 0xD83DDE8CU, }, { 0xD83DDE8EU, }, { 0xD83CDFCEU, }, { 0xD83DDE93U, }, { 0xD83DDE91U, }, { 0xD83DDE92U, }, { 0xD83DDE90U, }, { 0xD83DDE9AU, }, { 0xD83DDE9BU, }, { 0xD83DDE9CU, }, { 0xD83DDEF4U, }, { 0xD83DDEB2U, }, { 0xD83DDEF5U, }, { 0xD83CDFCDU, }, { 0xD83DDEA8U, }, { 0xD83DDE94U, }, { 0xD83DDE8DU, }, { 0xD83DDE98U, }, { 0xD83DDE96U, }, { 0xD83DDEA1U, }, { 0xD83DDEA0U, }, { 0xD83DDE9FU, }, { 0xD83DDE83U, }, { 0xD83DDE8BU, }, { 0xD83DDE9EU, }, { 0xD83DDE9DU, }, { 0xD83DDE84U, }, { 0xD83DDE85U, }, { 0xD83DDE88U, }, { 0xD83DDE82U, }, { 0xD83DDE86U, }, { 0xD83DDE87U, }, { 0xD83DDE8AU, }, { 0xD83DDE89U, }, { 0xD83DDE81U, }, { 0xD83DDEE9U, }, { 0x2708U, 0xFE0FU, }, { 0xD83DDEEBU, }, { 0xD83DDEECU, }, { 0xD83DDE80U, }, { 0xD83DDEF0U, }, { 0xD83DDCBAU, }, { 0xD83DDEF6U, }, { 0x26F5U, 0xFE0FU, }, { 0xD83DDEE5U, }, { 0xD83DDEA4U, }, { 0xD83DDEF3U, }, { 0x26F4U, }, { 0xD83DDEA2U, }, { 0x2693U, 0xFE0FU, }, { 0xD83DDEA7U, }, { 0x26FDU, 0xFE0FU, }, { 0xD83DDE8FU, }, { 0xD83DDEA6U, }, { 0xD83DDEA5U, }, { 0xD83DDDFAU, }, { 0xD83DDDFFU, }, { 0xD83DDDFDU, }, { 0x26F2U, 0xFE0FU, }, { 0xD83DDDFCU, }, { 0xD83CDFF0U, }, { 0xD83CDFEFU, }, { 0xD83CDFDFU, }, { 0xD83CDFA1U, }, { 0xD83CDFA2U, }, { 0xD83CDFA0U, }, { 0x26F1U, }, { 0xD83CDFD6U, }, { 0xD83CDFDDU, }, { 0x26F0U, }, { 0xD83CDFD4U, }, { 0xD83DDDFBU, }, { 0xD83CDF0BU, }, { 0xD83CDFDCU, }, { 0xD83CDFD5U, }, { 0x26FAU, 0xFE0FU, }, { 0xD83DDEE4U, }, { 0xD83DDEE3U, }, { 0xD83CDFD7U, }, { 0xD83CDFEDU, }, { 0xD83CDFE0U, }, { 0xD83CDFE1U, }, { 0xD83CDFD8U, }, { 0xD83CDFDAU, }, { 0xD83CDFE2U, }, { 0xD83CDFECU, }, { 0xD83CDFE3U, }, { 0xD83CDFE4U, }, { 0xD83CDFE5U, }, { 0xD83CDFE6U, }, { 0xD83CDFE8U, }, { 0xD83CDFEAU, }, { 0xD83CDFEBU, }, { 0xD83CDFE9U, }, { 0xD83DDC92U, }, { 0xD83CDFDBU, }, { 0x26EAU, 0xFE0FU, }, { 0xD83DDD4CU, }, { 0xD83DDD4DU, }, { 0xD83DDD4BU, }, { 0x26E9U, }, { 0xD83DDDFEU, }, { 0xD83CDF91U, }, { 0xD83CDFDEU, }, { 0xD83CDF05U, }, { 0xD83CDF04U, }, { 0xD83CDF20U, }, { 0xD83CDF87U, }, { 0xD83CDF86U, }, { 0xD83CDF07U, }, { 0xD83CDF06U, }, { 0xD83CDFD9U, }, { 0xD83CDF03U, }, { 0xD83CDF0CU, }, { 0xD83CDF09U, }, { 0xD83CDF01U, }, }; InputCategory Category6 = { { 0x231AU, 0xFE0FU, }, { 0xD83DDCF1U, }, { 0xD83DDCF2U, }, { 0xD83DDCBBU, }, { 0x2328U, 0xFE0FU, }, { 0xD83DDDA5U, }, { 0xD83DDDA8U, }, { 0xD83DDDB1U, }, { 0xD83DDDB2U, }, { 0xD83DDD79U, }, { 0xD83DDDDCU, }, { 0xD83DDCBDU, }, { 0xD83DDCBEU, }, { 0xD83DDCBFU, }, { 0xD83DDCC0U, }, { 0xD83DDCFCU, }, { 0xD83DDCF7U, }, { 0xD83DDCF8U, }, { 0xD83DDCF9U, }, { 0xD83CDFA5U, }, { 0xD83DDCFDU, }, { 0xD83CDF9EU, }, { 0xD83DDCDEU, }, { 0x260EU, 0xFE0FU, }, { 0xD83DDCDFU, }, { 0xD83DDCE0U, }, { 0xD83DDCFAU, }, { 0xD83DDCFBU, }, { 0xD83CDF99U, }, { 0xD83CDF9AU, }, { 0xD83CDF9BU, }, { 0x23F1U, }, { 0x23F2U, }, { 0x23F0U, }, { 0xD83DDD70U, }, { 0x231BU, 0xFE0FU, }, { 0x23F3U, }, { 0xD83DDCE1U, }, { 0xD83DDD0BU, }, { 0xD83DDD0CU, }, { 0xD83DDCA1U, }, { 0xD83DDD26U, }, { 0xD83DDD6FU, }, { 0xD83DDDD1U, }, { 0xD83DDEE2U, }, { 0xD83DDCB8U, }, { 0xD83DDCB5U, }, { 0xD83DDCB4U, }, { 0xD83DDCB6U, }, { 0xD83DDCB7U, }, { 0xD83DDCB0U, }, { 0xD83DDCB3U, }, { 0xD83DDC8EU, }, { 0x2696U, 0xFE0FU, }, { 0xD83DDD27U, }, { 0xD83DDD28U, }, { 0x2692U, }, { 0xD83DDEE0U, }, { 0x26CFU, }, { 0xD83DDD29U, }, { 0x2699U, 0xFE0FU, }, { 0x26D3U, }, { 0xD83DDD2BU, }, { 0xD83DDCA3U, }, { 0xD83DDD2AU, }, { 0xD83DDDE1U, }, { 0x2694U, 0xFE0FU, }, { 0xD83DDEE1U, }, { 0xD83DDEACU, }, { 0x26B0U, 0xFE0FU, }, { 0x26B1U, 0xFE0FU, }, { 0xD83CDFFAU, }, { 0xD83DDD2EU, }, { 0xD83DDCFFU, }, { 0xD83DDC88U, }, { 0x2697U, 0xFE0FU, }, { 0xD83DDD2DU, }, { 0xD83DDD2CU, }, { 0xD83DDD73U, }, { 0xD83DDC8AU, }, { 0xD83DDC89U, }, { 0xD83CDF21U, }, { 0xD83DDEBDU, }, { 0xD83DDEB0U, }, { 0xD83DDEBFU, }, { 0xD83DDEC1U, }, { 0xD83DDEC0U, }, { 0xD83DDECEU, }, { 0xD83DDD11U, }, { 0xD83DDDDDU, }, { 0xD83DDEAAU, }, { 0xD83DDECBU, }, { 0xD83DDECFU, }, { 0xD83DDECCU, }, { 0xD83DDDBCU, }, { 0xD83DDECDU, }, { 0xD83DDED2U, }, { 0xD83CDF81U, }, { 0xD83CDF88U, }, { 0xD83CDF8FU, }, { 0xD83CDF80U, }, { 0xD83CDF8AU, }, { 0xD83CDF89U, }, { 0xD83CDF8EU, }, { 0xD83CDFEEU, }, { 0xD83CDF90U, }, { 0x2709U, 0xFE0FU, }, { 0xD83DDCE9U, }, { 0xD83DDCE8U, }, { 0xD83DDCE7U, }, { 0xD83DDC8CU, }, { 0xD83DDCE5U, }, { 0xD83DDCE4U, }, { 0xD83DDCE6U, }, { 0xD83CDFF7U, }, { 0xD83DDCEAU, }, { 0xD83DDCEBU, }, { 0xD83DDCECU, }, { 0xD83DDCEDU, }, { 0xD83DDCEEU, }, { 0xD83DDCEFU, }, { 0xD83DDCDCU, }, { 0xD83DDCC3U, }, { 0xD83DDCC4U, }, { 0xD83DDCD1U, }, { 0xD83DDCCAU, }, { 0xD83DDCC8U, }, { 0xD83DDCC9U, }, { 0xD83DDDD2U, }, { 0xD83DDDD3U, }, { 0xD83DDCC6U, }, { 0xD83DDCC5U, }, { 0xD83DDCC7U, }, { 0xD83DDDC3U, }, { 0xD83DDDF3U, }, { 0xD83DDDC4U, }, { 0xD83DDCCBU, }, { 0xD83DDCC1U, }, { 0xD83DDCC2U, }, { 0xD83DDDC2U, }, { 0xD83DDDDEU, }, { 0xD83DDCF0U, }, { 0xD83DDCD3U, }, { 0xD83DDCD4U, }, { 0xD83DDCD2U, }, { 0xD83DDCD5U, }, { 0xD83DDCD7U, }, { 0xD83DDCD8U, }, { 0xD83DDCD9U, }, { 0xD83DDCDAU, }, { 0xD83DDCD6U, }, { 0xD83DDD16U, }, { 0xD83DDD17U, }, { 0xD83DDCCEU, }, { 0xD83DDD87U, }, { 0xD83DDCD0U, }, { 0xD83DDCCFU, }, { 0xD83DDCCCU, }, { 0xD83DDCCDU, }, { 0x2702U, 0xFE0FU, }, { 0xD83DDD8AU, }, { 0xD83DDD8BU, }, { 0x2712U, 0xFE0FU, }, { 0xD83DDD8CU, }, { 0xD83DDD8DU, }, { 0xD83DDCDDU, }, { 0x270FU, 0xFE0FU, }, { 0xD83DDD0DU, }, { 0xD83DDD0EU, }, { 0xD83DDD0FU, }, { 0xD83DDD10U, }, { 0xD83DDD12U, }, { 0xD83DDD13U, }, }; InputCategory Category7 = { { 0x2764U, 0xFE0FU, }, { 0xD83DDC9BU, }, { 0xD83DDC9AU, }, { 0xD83DDC99U, }, { 0xD83DDC9CU, }, { 0xD83DDDA4U, }, { 0xD83DDC94U, }, { 0x2763U, 0xFE0FU, }, { 0xD83DDC95U, }, { 0xD83DDC9EU, }, { 0xD83DDC93U, }, { 0xD83DDC97U, }, { 0xD83DDC96U, }, { 0xD83DDC98U, }, { 0xD83DDC9DU, }, { 0xD83DDC9FU, }, { 0x262EU, 0xFE0FU, }, { 0x271DU, 0xFE0FU, }, { 0x262AU, 0xFE0FU, }, { 0xD83DDD49U, }, { 0x2638U, 0xFE0FU, }, { 0x2721U, 0xFE0FU, }, { 0xD83DDD2FU, }, { 0xD83DDD4EU, }, { 0x262FU, 0xFE0FU, }, { 0x2626U, 0xFE0FU, }, { 0xD83DDED0U, }, { 0x26CEU, }, { 0x2648U, 0xFE0FU, }, { 0x2649U, 0xFE0FU, }, { 0x264AU, 0xFE0FU, }, { 0x264BU, 0xFE0FU, }, { 0x264CU, 0xFE0FU, }, { 0x264DU, 0xFE0FU, }, { 0x264EU, 0xFE0FU, }, { 0x264FU, 0xFE0FU, }, { 0x2650U, 0xFE0FU, }, { 0x2651U, 0xFE0FU, }, { 0x2652U, 0xFE0FU, }, { 0x2653U, 0xFE0FU, }, { 0xD83CDD94U, }, { 0x269BU, 0xFE0FU, }, { 0xD83CDE51U, }, { 0x2622U, 0xFE0FU, }, { 0x2623U, 0xFE0FU, }, { 0xD83DDCF4U, }, { 0xD83DDCF3U, }, { 0xD83CDE36U, }, { 0xD83CDE1AU, 0xFE0FU, }, { 0xD83CDE38U, }, { 0xD83CDE3AU, }, { 0xD83CDE37U, }, { 0x2734U, 0xFE0FU, }, { 0xD83CDD9AU, }, { 0xD83DDCAEU, }, { 0xD83CDE50U, }, { 0x3299U, 0xFE0FU, }, { 0x3297U, 0xFE0FU, }, { 0xD83CDE34U, }, { 0xD83CDE35U, }, { 0xD83CDE39U, }, { 0xD83CDE32U, }, { 0xD83CDD70U, 0xFE0FU, }, { 0xD83CDD71U, 0xFE0FU, }, { 0xD83CDD8EU, }, { 0xD83CDD91U, }, { 0xD83CDD7EU, 0xFE0FU, }, { 0xD83CDD98U, }, { 0x274CU, }, { 0x2B55U, 0xFE0FU, }, { 0xD83DDED1U, }, { 0x26D4U, 0xFE0FU, }, { 0xD83DDCDBU, }, { 0xD83DDEABU, }, { 0xD83DDCAFU, }, { 0xD83DDCA2U, }, { 0x2668U, 0xFE0FU, }, { 0xD83DDEB7U, }, { 0xD83DDEAFU, }, { 0xD83DDEB3U, }, { 0xD83DDEB1U, }, { 0xD83DDD1EU, }, { 0xD83DDCF5U, }, { 0xD83DDEADU, }, { 0x2757U, 0xFE0FU, }, { 0x2755U, }, { 0x2753U, }, { 0x2754U, }, { 0x203CU, 0xFE0FU, }, { 0x2049U, 0xFE0FU, }, { 0xD83DDD05U, }, { 0xD83DDD06U, }, { 0x303DU, 0xFE0FU, }, { 0x26A0U, 0xFE0FU, }, { 0xD83DDEB8U, }, { 0xD83DDD31U, }, { 0x269CU, 0xFE0FU, }, { 0xD83DDD30U, }, { 0x267BU, 0xFE0FU, }, { 0x2705U, }, { 0xD83CDE2FU, 0xFE0FU, }, { 0xD83DDCB9U, }, { 0x2747U, 0xFE0FU, }, { 0x2733U, 0xFE0FU, }, { 0x274EU, }, { 0xD83CDF10U, }, { 0xD83DDCA0U, }, { 0x24C2U, 0xFE0FU, }, { 0xD83CDF00U, }, { 0xD83DDCA4U, }, { 0xD83CDFE7U, }, { 0xD83DDEBEU, }, { 0x267FU, 0xFE0FU, }, { 0xD83CDD7FU, 0xFE0FU, }, { 0xD83CDE33U, }, { 0xD83CDE02U, }, { 0xD83DDEC2U, }, { 0xD83DDEC3U, }, { 0xD83DDEC4U, }, { 0xD83DDEC5U, }, { 0xD83DDEB9U, }, { 0xD83DDEBAU, }, { 0xD83DDEBCU, }, { 0xD83DDEBBU, }, { 0xD83DDEAEU, }, { 0xD83CDFA6U, }, { 0xD83DDCF6U, }, { 0xD83CDE01U, }, { 0xD83DDD23U, }, { 0x2139U, 0xFE0FU, }, { 0xD83DDD24U, }, { 0xD83DDD21U, }, { 0xD83DDD20U, }, { 0xD83CDD96U, }, { 0xD83CDD97U, }, { 0xD83CDD99U, }, { 0xD83CDD92U, }, { 0xD83CDD95U, }, { 0xD83CDD93U, }, { 0x30U, 0xFE0FU, 0x20E3U, }, { 0x31U, 0xFE0FU, 0x20E3U, }, { 0x32U, 0xFE0FU, 0x20E3U, }, { 0x33U, 0xFE0FU, 0x20E3U, }, { 0x34U, 0xFE0FU, 0x20E3U, }, { 0x35U, 0xFE0FU, 0x20E3U, }, { 0x36U, 0xFE0FU, 0x20E3U, }, { 0x37U, 0xFE0FU, 0x20E3U, }, { 0x38U, 0xFE0FU, 0x20E3U, }, { 0x39U, 0xFE0FU, 0x20E3U, }, { 0xD83DDD1FU, }, { 0xD83DDD22U, }, { 0x23U, 0xFE0FU, 0x20E3U, }, { 0x2AU, 0xFE0FU, 0x20E3U, }, { 0x25B6U, 0xFE0FU, }, { 0x23F8U, }, { 0x23EFU, }, { 0x23F9U, }, { 0x23FAU, }, { 0x23EDU, }, { 0x23EEU, }, { 0x23E9U, }, { 0x23EAU, }, { 0x23EBU, }, { 0x23ECU, }, { 0x25C0U, 0xFE0FU, }, { 0xD83DDD3CU, }, { 0xD83DDD3DU, }, { 0x27A1U, 0xFE0FU, }, { 0x2B05U, 0xFE0FU, }, { 0x2B06U, 0xFE0FU, }, { 0x2B07U, 0xFE0FU, }, { 0x2197U, 0xFE0FU, }, { 0x2198U, 0xFE0FU, }, { 0x2199U, 0xFE0FU, }, { 0x2196U, 0xFE0FU, }, { 0x2195U, 0xFE0FU, }, { 0x2194U, 0xFE0FU, }, { 0x21AAU, 0xFE0FU, }, { 0x21A9U, 0xFE0FU, }, { 0x2934U, 0xFE0FU, }, { 0x2935U, 0xFE0FU, }, { 0xD83DDD00U, }, { 0xD83DDD01U, }, { 0xD83DDD02U, }, { 0xD83DDD04U, }, { 0xD83DDD03U, }, { 0xD83CDFB5U, }, { 0xD83CDFB6U, }, { 0x2795U, }, { 0x2796U, }, { 0x2797U, }, { 0x2716U, 0xFE0FU, }, { 0xD83DDCB2U, }, { 0xD83DDCB1U, }, { 0x2122U, }, { 0xA9U, }, { 0xAEU, }, { 0x3030U, }, { 0x27B0U, }, { 0x27BFU, }, { 0xD83DDD1AU, }, { 0xD83DDD19U, }, { 0xD83DDD1BU, }, { 0xD83DDD1DU, }, { 0xD83DDD1CU, }, { 0x2714U, 0xFE0FU, }, { 0x2611U, 0xFE0FU, }, { 0xD83DDD18U, }, { 0x26AAU, 0xFE0FU, }, { 0x26ABU, 0xFE0FU, }, { 0xD83DDD34U, }, { 0xD83DDD35U, }, { 0xD83DDD3AU, }, { 0xD83DDD3BU, }, { 0xD83DDD38U, }, { 0xD83DDD39U, }, { 0xD83DDD36U, }, { 0xD83DDD37U, }, { 0xD83DDD33U, }, { 0xD83DDD32U, }, { 0x25AAU, 0xFE0FU, }, { 0x25ABU, 0xFE0FU, }, { 0x25FEU, 0xFE0FU, }, { 0x25FDU, 0xFE0FU, }, { 0x25FCU, 0xFE0FU, }, { 0x25FBU, 0xFE0FU, }, { 0x2B1BU, 0xFE0FU, }, { 0x2B1CU, 0xFE0FU, }, { 0xD83DDD08U, }, { 0xD83DDD07U, }, { 0xD83DDD09U, }, { 0xD83DDD0AU, }, { 0xD83DDD14U, }, { 0xD83DDD15U, }, { 0xD83DDCE3U, }, { 0xD83DDCE2U, }, { 0xD83DDC41U, 0x200DU, 0xD83DDDE8U, }, { 0xD83DDCACU, }, { 0xD83DDCADU, }, { 0xD83DDDEFU, }, { 0x2660U, 0xFE0FU, }, { 0x2663U, 0xFE0FU, }, { 0x2665U, 0xFE0FU, }, { 0x2666U, 0xFE0FU, }, { 0xD83CDCCFU, }, { 0xD83CDFB4U, }, { 0xD83CDC04U, 0xFE0FU, }, { 0xD83DDD50U, }, { 0xD83DDD51U, }, { 0xD83DDD52U, }, { 0xD83DDD53U, }, { 0xD83DDD54U, }, { 0xD83DDD55U, }, { 0xD83DDD56U, }, { 0xD83DDD57U, }, { 0xD83DDD58U, }, { 0xD83DDD59U, }, { 0xD83DDD5AU, }, { 0xD83DDD5BU, }, { 0xD83DDD5CU, }, { 0xD83DDD5DU, }, { 0xD83DDD5EU, }, { 0xD83DDD5FU, }, { 0xD83DDD60U, }, { 0xD83DDD61U, }, { 0xD83DDD62U, }, { 0xD83DDD63U, }, { 0xD83DDD64U, }, { 0xD83DDD65U, }, { 0xD83DDD66U, }, { 0xD83DDD67U, }, //}; // //InputCategory Category8 = { { 0xD83CDFF3U, }, { 0xD83CDFF4U, }, { 0xD83CDFC1U, }, { 0xD83DDEA9U, }, { 0xD83CDFF3U, 0xFE0FU, 0x200DU, 0xD83CDF08U, }, { 0xD83CDDE6U, 0xD83CDDEBU, }, { 0xD83CDDE6U, 0xD83CDDFDU, }, { 0xD83CDDE6U, 0xD83CDDF1U, }, { 0xD83CDDE9U, 0xD83CDDFFU, }, { 0xD83CDDE6U, 0xD83CDDF8U, }, { 0xD83CDDE6U, 0xD83CDDE9U, }, { 0xD83CDDE6U, 0xD83CDDF4U, }, { 0xD83CDDE6U, 0xD83CDDEEU, }, { 0xD83CDDE6U, 0xD83CDDF6U, }, { 0xD83CDDE6U, 0xD83CDDECU, }, { 0xD83CDDE6U, 0xD83CDDF7U, }, { 0xD83CDDE6U, 0xD83CDDF2U, }, { 0xD83CDDE6U, 0xD83CDDFCU, }, { 0xD83CDDE6U, 0xD83CDDFAU, }, { 0xD83CDDE6U, 0xD83CDDF9U, }, { 0xD83CDDE6U, 0xD83CDDFFU, }, { 0xD83CDDE7U, 0xD83CDDF8U, }, { 0xD83CDDE7U, 0xD83CDDEDU, }, { 0xD83CDDE7U, 0xD83CDDE9U, }, { 0xD83CDDE7U, 0xD83CDDE7U, }, { 0xD83CDDE7U, 0xD83CDDFEU, }, { 0xD83CDDE7U, 0xD83CDDEAU, }, { 0xD83CDDE7U, 0xD83CDDFFU, }, { 0xD83CDDE7U, 0xD83CDDEFU, }, { 0xD83CDDE7U, 0xD83CDDF2U, }, { 0xD83CDDE7U, 0xD83CDDF9U, }, { 0xD83CDDE7U, 0xD83CDDF4U, }, { 0xD83CDDE7U, 0xD83CDDE6U, }, { 0xD83CDDE7U, 0xD83CDDFCU, }, { 0xD83CDDE7U, 0xD83CDDF7U, }, { 0xD83CDDEEU, 0xD83CDDF4U, }, { 0xD83CDDFBU, 0xD83CDDECU, }, { 0xD83CDDE7U, 0xD83CDDF3U, }, { 0xD83CDDE7U, 0xD83CDDECU, }, { 0xD83CDDE7U, 0xD83CDDEBU, }, { 0xD83CDDE7U, 0xD83CDDEEU, }, { 0xD83CDDF0U, 0xD83CDDEDU, }, { 0xD83CDDE8U, 0xD83CDDF2U, }, { 0xD83CDDE8U, 0xD83CDDE6U, }, { 0xD83CDDEEU, 0xD83CDDE8U, }, { 0xD83CDDE8U, 0xD83CDDFBU, }, { 0xD83CDDE7U, 0xD83CDDF6U, }, { 0xD83CDDF0U, 0xD83CDDFEU, }, { 0xD83CDDE8U, 0xD83CDDEBU, }, { 0xD83CDDF9U, 0xD83CDDE9U, }, { 0xD83CDDE8U, 0xD83CDDF1U, }, { 0xD83CDDE8U, 0xD83CDDF3U, }, { 0xD83CDDE8U, 0xD83CDDFDU, }, { 0xD83CDDE8U, 0xD83CDDE8U, }, { 0xD83CDDE8U, 0xD83CDDF4U, }, { 0xD83CDDF0U, 0xD83CDDF2U, }, { 0xD83CDDE8U, 0xD83CDDECU, }, { 0xD83CDDE8U, 0xD83CDDE9U, }, { 0xD83CDDE8U, 0xD83CDDF0U, }, { 0xD83CDDE8U, 0xD83CDDF7U, }, { 0xD83CDDE8U, 0xD83CDDEEU, }, { 0xD83CDDEDU, 0xD83CDDF7U, }, { 0xD83CDDE8U, 0xD83CDDFAU, }, { 0xD83CDDE8U, 0xD83CDDFCU, }, { 0xD83CDDE8U, 0xD83CDDFEU, }, { 0xD83CDDE8U, 0xD83CDDFFU, }, { 0xD83CDDE9U, 0xD83CDDF0U, }, { 0xD83CDDE9U, 0xD83CDDEFU, }, { 0xD83CDDE9U, 0xD83CDDF2U, }, { 0xD83CDDE9U, 0xD83CDDF4U, }, { 0xD83CDDEAU, 0xD83CDDE8U, }, { 0xD83CDDEAU, 0xD83CDDECU, }, { 0xD83CDDF8U, 0xD83CDDFBU, }, { 0xD83CDDECU, 0xD83CDDF6U, }, { 0xD83CDDEAU, 0xD83CDDF7U, }, { 0xD83CDDEAU, 0xD83CDDEAU, }, { 0xD83CDDEAU, 0xD83CDDF9U, }, { 0xD83CDDEAU, 0xD83CDDFAU, }, { 0xD83CDDEBU, 0xD83CDDF0U, }, { 0xD83CDDEBU, 0xD83CDDF4U, }, { 0xD83CDDEBU, 0xD83CDDEFU, }, { 0xD83CDDEBU, 0xD83CDDEEU, }, { 0xD83CDDEBU, 0xD83CDDF7U, }, { 0xD83CDDECU, 0xD83CDDEBU, }, { 0xD83CDDF5U, 0xD83CDDEBU, }, { 0xD83CDDF9U, 0xD83CDDEBU, }, { 0xD83CDDECU, 0xD83CDDE6U, }, { 0xD83CDDECU, 0xD83CDDF2U, }, { 0xD83CDDECU, 0xD83CDDEAU, }, { 0xD83CDDE9U, 0xD83CDDEAU, }, { 0xD83CDDECU, 0xD83CDDEDU, }, { 0xD83CDDECU, 0xD83CDDEEU, }, { 0xD83CDDECU, 0xD83CDDF7U, }, { 0xD83CDDECU, 0xD83CDDF1U, }, { 0xD83CDDECU, 0xD83CDDE9U, }, { 0xD83CDDECU, 0xD83CDDF5U, }, { 0xD83CDDECU, 0xD83CDDFAU, }, { 0xD83CDDECU, 0xD83CDDF9U, }, { 0xD83CDDECU, 0xD83CDDECU, }, { 0xD83CDDECU, 0xD83CDDF3U, }, { 0xD83CDDECU, 0xD83CDDFCU, }, { 0xD83CDDECU, 0xD83CDDFEU, }, { 0xD83CDDEDU, 0xD83CDDF9U, }, { 0xD83CDDEDU, 0xD83CDDF3U, }, { 0xD83CDDEDU, 0xD83CDDF0U, }, { 0xD83CDDEDU, 0xD83CDDFAU, }, { 0xD83CDDEEU, 0xD83CDDF8U, }, { 0xD83CDDEEU, 0xD83CDDF3U, }, { 0xD83CDDEEU, 0xD83CDDE9U, }, { 0xD83CDDEEU, 0xD83CDDF7U, }, { 0xD83CDDEEU, 0xD83CDDF6U, }, { 0xD83CDDEEU, 0xD83CDDEAU, }, { 0xD83CDDEEU, 0xD83CDDF2U, }, { 0xD83CDDEEU, 0xD83CDDF1U, }, { 0xD83CDDEEU, 0xD83CDDF9U, }, { 0xD83CDDEFU, 0xD83CDDF2U, }, { 0xD83CDDEFU, 0xD83CDDF5U, }, { 0xD83CDF8CU, }, { 0xD83CDDEFU, 0xD83CDDEAU, }, { 0xD83CDDEFU, 0xD83CDDF4U, }, { 0xD83CDDF0U, 0xD83CDDFFU, }, { 0xD83CDDF0U, 0xD83CDDEAU, }, { 0xD83CDDF0U, 0xD83CDDEEU, }, { 0xD83CDDFDU, 0xD83CDDF0U, }, { 0xD83CDDF0U, 0xD83CDDFCU, }, { 0xD83CDDF0U, 0xD83CDDECU, }, { 0xD83CDDF1U, 0xD83CDDE6U, }, { 0xD83CDDF1U, 0xD83CDDFBU, }, { 0xD83CDDF1U, 0xD83CDDE7U, }, { 0xD83CDDF1U, 0xD83CDDF8U, }, { 0xD83CDDF1U, 0xD83CDDF7U, }, { 0xD83CDDF1U, 0xD83CDDFEU, }, { 0xD83CDDF1U, 0xD83CDDEEU, }, { 0xD83CDDF1U, 0xD83CDDF9U, }, { 0xD83CDDF1U, 0xD83CDDFAU, }, { 0xD83CDDF2U, 0xD83CDDF4U, }, { 0xD83CDDF2U, 0xD83CDDF0U, }, { 0xD83CDDF2U, 0xD83CDDECU, }, { 0xD83CDDF2U, 0xD83CDDFCU, }, { 0xD83CDDF2U, 0xD83CDDFEU, }, { 0xD83CDDF2U, 0xD83CDDFBU, }, { 0xD83CDDF2U, 0xD83CDDF1U, }, { 0xD83CDDF2U, 0xD83CDDF9U, }, { 0xD83CDDF2U, 0xD83CDDEDU, }, { 0xD83CDDF2U, 0xD83CDDF6U, }, { 0xD83CDDF2U, 0xD83CDDF7U, }, { 0xD83CDDF2U, 0xD83CDDFAU, }, { 0xD83CDDFEU, 0xD83CDDF9U, }, { 0xD83CDDF2U, 0xD83CDDFDU, }, { 0xD83CDDEBU, 0xD83CDDF2U, }, { 0xD83CDDF2U, 0xD83CDDE9U, }, { 0xD83CDDF2U, 0xD83CDDE8U, }, { 0xD83CDDF2U, 0xD83CDDF3U, }, { 0xD83CDDF2U, 0xD83CDDEAU, }, { 0xD83CDDF2U, 0xD83CDDF8U, }, { 0xD83CDDF2U, 0xD83CDDE6U, }, { 0xD83CDDF2U, 0xD83CDDFFU, }, { 0xD83CDDF2U, 0xD83CDDF2U, }, { 0xD83CDDF3U, 0xD83CDDE6U, }, { 0xD83CDDF3U, 0xD83CDDF7U, }, { 0xD83CDDF3U, 0xD83CDDF5U, }, { 0xD83CDDF3U, 0xD83CDDF1U, }, { 0xD83CDDF3U, 0xD83CDDE8U, }, { 0xD83CDDF3U, 0xD83CDDFFU, }, { 0xD83CDDF3U, 0xD83CDDEEU, }, { 0xD83CDDF3U, 0xD83CDDEAU, }, { 0xD83CDDF3U, 0xD83CDDECU, }, { 0xD83CDDF3U, 0xD83CDDFAU, }, { 0xD83CDDF3U, 0xD83CDDEBU, }, { 0xD83CDDF0U, 0xD83CDDF5U, }, { 0xD83CDDF2U, 0xD83CDDF5U, }, { 0xD83CDDF3U, 0xD83CDDF4U, }, { 0xD83CDDF4U, 0xD83CDDF2U, }, { 0xD83CDDF5U, 0xD83CDDF0U, }, { 0xD83CDDF5U, 0xD83CDDFCU, }, { 0xD83CDDF5U, 0xD83CDDF8U, }, { 0xD83CDDF5U, 0xD83CDDE6U, }, { 0xD83CDDF5U, 0xD83CDDECU, }, { 0xD83CDDF5U, 0xD83CDDFEU, }, { 0xD83CDDF5U, 0xD83CDDEAU, }, { 0xD83CDDF5U, 0xD83CDDEDU, }, { 0xD83CDDF5U, 0xD83CDDF3U, }, { 0xD83CDDF5U, 0xD83CDDF1U, }, { 0xD83CDDF5U, 0xD83CDDF9U, }, { 0xD83CDDF5U, 0xD83CDDF7U, }, { 0xD83CDDF6U, 0xD83CDDE6U, }, { 0xD83CDDF7U, 0xD83CDDEAU, }, { 0xD83CDDF7U, 0xD83CDDF4U, }, { 0xD83CDDF7U, 0xD83CDDFAU, }, { 0xD83CDDF7U, 0xD83CDDFCU, }, { 0xD83CDDFCU, 0xD83CDDF8U, }, { 0xD83CDDF8U, 0xD83CDDF2U, }, { 0xD83CDDF8U, 0xD83CDDF9U, }, { 0xD83CDDF8U, 0xD83CDDE6U, }, { 0xD83CDDF8U, 0xD83CDDF3U, }, { 0xD83CDDF7U, 0xD83CDDF8U, }, { 0xD83CDDF8U, 0xD83CDDE8U, }, { 0xD83CDDF8U, 0xD83CDDF1U, }, { 0xD83CDDF8U, 0xD83CDDECU, }, { 0xD83CDDF8U, 0xD83CDDFDU, }, { 0xD83CDDF8U, 0xD83CDDF0U, }, { 0xD83CDDF8U, 0xD83CDDEEU, }, { 0xD83CDDECU, 0xD83CDDF8U, }, { 0xD83CDDF8U, 0xD83CDDE7U, }, { 0xD83CDDF8U, 0xD83CDDF4U, }, { 0xD83CDDFFU, 0xD83CDDE6U, }, { 0xD83CDDF0U, 0xD83CDDF7U, }, { 0xD83CDDF8U, 0xD83CDDF8U, }, { 0xD83CDDEAU, 0xD83CDDF8U, }, { 0xD83CDDF1U, 0xD83CDDF0U, }, { 0xD83CDDE7U, 0xD83CDDF1U, }, { 0xD83CDDF8U, 0xD83CDDEDU, }, { 0xD83CDDF0U, 0xD83CDDF3U, }, { 0xD83CDDF1U, 0xD83CDDE8U, }, { 0xD83CDDF5U, 0xD83CDDF2U, }, { 0xD83CDDFBU, 0xD83CDDE8U, }, { 0xD83CDDF8U, 0xD83CDDE9U, }, { 0xD83CDDF8U, 0xD83CDDF7U, }, { 0xD83CDDF8U, 0xD83CDDFFU, }, { 0xD83CDDF8U, 0xD83CDDEAU, }, { 0xD83CDDE8U, 0xD83CDDEDU, }, { 0xD83CDDF8U, 0xD83CDDFEU, }, { 0xD83CDDF9U, 0xD83CDDFCU, }, { 0xD83CDDF9U, 0xD83CDDEFU, }, { 0xD83CDDF9U, 0xD83CDDFFU, }, { 0xD83CDDF9U, 0xD83CDDEDU, }, { 0xD83CDDF9U, 0xD83CDDF1U, }, { 0xD83CDDF9U, 0xD83CDDECU, }, { 0xD83CDDF9U, 0xD83CDDF0U, }, { 0xD83CDDF9U, 0xD83CDDF4U, }, { 0xD83CDDF9U, 0xD83CDDF9U, }, { 0xD83CDDF9U, 0xD83CDDF3U, }, { 0xD83CDDF9U, 0xD83CDDF7U, }, { 0xD83CDDF9U, 0xD83CDDF2U, }, { 0xD83CDDF9U, 0xD83CDDE8U, }, { 0xD83CDDF9U, 0xD83CDDFBU, }, { 0xD83CDDFBU, 0xD83CDDEEU, }, { 0xD83CDDFAU, 0xD83CDDECU, }, { 0xD83CDDFAU, 0xD83CDDE6U, }, { 0xD83CDDE6U, 0xD83CDDEAU, }, { 0xD83CDDECU, 0xD83CDDE7U, }, { 0xD83CDDFAU, 0xD83CDDF8U, }, { 0xD83CDDFAU, 0xD83CDDFEU, }, { 0xD83CDDFAU, 0xD83CDDFFU, }, { 0xD83CDDFBU, 0xD83CDDFAU, }, { 0xD83CDDFBU, 0xD83CDDE6U, }, { 0xD83CDDFBU, 0xD83CDDEAU, }, { 0xD83CDDFBU, 0xD83CDDF3U, }, { 0xD83CDDFCU, 0xD83CDDEBU, }, { 0xD83CDDEAU, 0xD83CDDEDU, }, { 0xD83CDDFEU, 0xD83CDDEAU, }, { 0xD83CDDFFU, 0xD83CDDF2U, }, { 0xD83CDDFFU, 0xD83CDDFCU, }, }; constexpr auto kErrorBadData = 401; void append(Id &id, uint32 code) { if (auto first = static_cast<uint16>((code >> 16) & 0xFFFFU)) { id.append(QChar(first)); } id.append(QChar(static_cast<uint16>(code & 0xFFFFU))); } using VariatedIds = map<Id, bool>; VariatedIds fillVariatedIds() { auto result = VariatedIds(); for (auto &row : ColoredEmoji) { auto variatedId = Id(); if (row.size() < 2) { logDataError() << "colored string should have at least two characters."; return VariatedIds(); } for (auto i = size_t(0), size = row.size(); i != size; ++i) { auto code = row[i]; if (i == 1) { if (code != ColorMask) { logDataError() << "color code should appear at index 1."; return VariatedIds(); } } else if (code == ColorMask) { logDataError() << "color code should appear only at index 1."; return VariatedIds(); } else if (code != kPostfix) { append(variatedId, code); } } result.insert(make_pair(variatedId, true)); } return result; } void appendCategory(Data &result, const InputCategory &category, const VariatedIds &variatedIds) { result.categories.push_back(vector<int>()); for (auto &id : category) { auto emoji = Emoji(); auto bareId = Id(); auto from = id.cbegin(), to = id.cend(); if (to - from == 2 && *(to - 1) == kPostfix) { emoji.postfixed = true; --to; } for (auto i = from; i != to; ++i) { auto code = *i; if (find(begin(Colors), end(Colors), code) != end(Colors)) { logDataError() << "color code found in a category emoji."; result = Data(); return; } append(emoji.id, code); if (code != kPostfix) { append(bareId, code); } } if (bareId.isEmpty()) { logDataError() << "empty emoji id found."; result = Data(); return; } auto it = result.map.find(bareId); if (it == result.map.cend()) { it = result.map.insert(make_pair(bareId, result.list.size())).first; result.list.push_back(move(emoji)); } else if (result.list[it->second].postfixed != emoji.postfixed) { logDataError() << "same emoji found with different postfixed property."; result = Data(); return; } else if (result.list[it->second].id != emoji.id) { logDataError() << "same emoji found with different id."; result = Data(); return; } if (variatedIds.find(bareId) != variatedIds.cend()) { result.list[it->second].variated = true; auto baseId = Id(); if (*from == kPostfix) { logDataError() << "bad first symbol in emoji."; result = Data(); return; } append(baseId, *from++); for (auto color : Colors) { auto colored = Emoji(); colored.id = baseId; colored.colored = true; append(colored.id, color); auto bareColoredId = colored.id; for (auto i = from; i != to; ++i) { append(colored.id, *i); if (*i != kPostfix) { append(bareColoredId, *i); } } auto it = result.map.find(bareColoredId); if (it == result.map.cend()) { it = result.map.insert(make_pair(bareColoredId, result.list.size())).first; result.list.push_back(move(colored)); } else if (result.list[it->second].postfixed != colored.postfixed) { logDataError() << "same emoji found with different postfixed property."; result = Data(); return; } else if (result.list[it->second].id != colored.id) { logDataError() << "same emoji found with different id."; result = Data(); return; } } } result.categories.back().push_back(it->second); } } void fillReplaces(Data &result) { for (auto &replace : Replaces) { auto id = Id(); for (auto code : replace.code) { append(id, code); } auto it = result.map.find(id); if (it == result.map.cend()) { logDataError() << "emoji from replaces not found in the map."; result = Data(); return; } result.replaces.insert(make_pair(QString::fromUtf8(replace.replace), it->second)); } } bool AddItemBeforeItem(const InputId &add, const InputId &before) { auto addToCategory = (InputCategory*)nullptr; auto addBeforeIterator = InputCategory::iterator(); for (auto category : { &Category1, &Category2, &Category3, &Category4, &Category5, &Category6, &Category7, }) { for (auto i = category->begin(), e = category->end(); i != e; ++i) { if (*i == add) { return true; } else if (*i == before) { addToCategory = category; addBeforeIterator = i; } } } if (!addToCategory) { return false; } addToCategory->insert(addBeforeIterator, add); return true; } } // namespace common::LogStream logDataError() { return common::logError(kErrorBadData, "input") << "Bad data: "; } Data PrepareData() { Data result; auto variatedIds = fillVariatedIds(); if (variatedIds.empty()) { return Data(); } // Manually add :speech_left: emoji before eye-with-speech emoji. if (!AddItemBeforeItem({ 0xD83DDDE8U }, { 0xD83DDC41U, 0x200DU, 0xD83DDDE8U })) { return Data(); } for (auto category : { &Category1, &Category2, &Category3, &Category4, &Category5, &Category6, &Category7, }) { appendCategory(result, *category, variatedIds); if (result.list.empty()) { return Data(); } } fillReplaces(result); if (result.list.empty()) { return Data(); } return result; } } // namespace emoji } // namespace codegen �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/emoji/data.h�������������������������������������������0000664�0000000�0000000�00000001623�13262451251�0023216�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "codegen/common/logging.h" #include <vector> #include <map> #include <memory> #include <functional> #include <QtCore/QString> #include <QtCore/QSet> #include <QtCore/QMap> namespace codegen { namespace emoji { using Id = QString; struct Emoji { Id id; bool postfixed = false; bool variated = false; bool colored = false; }; struct Data { std::vector<Emoji> list; std::map<Id, int, std::greater<Id>> map; std::vector<std::vector<int>> categories; std::map<QString, int, std::greater<QString>> replaces; }; Data PrepareData(); constexpr auto kPostfix = 0xFE0FU; common::LogStream logDataError(); } // namespace emoji } // namespace codegen �������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/emoji/generator.cpp������������������������������������0000664�0000000�0000000�00000066243�13262451251�0024637�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "codegen/emoji/generator.h" #include <QtCore/QtPlugin> #include <QtCore/QBuffer> #include <QtGui/QFontDatabase> #include <QtGui/QGuiApplication> #include <QtGui/QImage> #include <QtGui/QPainter> #include <QtCore/QDir> #ifdef SUPPORT_IMAGE_GENERATION Q_IMPORT_PLUGIN(QWebpPlugin) #ifdef Q_OS_MAC Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin) #elif defined Q_OS_WIN Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin) #else // !Q_OS_MAC && !Q_OS_WIN Q_IMPORT_PLUGIN(QXcbIntegrationPlugin) #endif // !Q_OS_MAC && !Q_OS_WIN #endif // SUPPORT_IMAGE_GENERATION namespace codegen { namespace emoji { namespace { constexpr auto kErrorCantWritePath = 851; constexpr auto kOriginalBits = 12; constexpr auto kIdSizeBits = 6; constexpr auto kColumnBits = 6; constexpr auto kRowBits = 6; common::ProjectInfo Project = { "codegen_emoji", "empty", false, // forceReGenerate }; QRect computeSourceRect(const QImage &image) { auto size = image.width(); auto result = QRect(2, 2, size - 4, size - 4); auto top = 1, bottom = 1, left = 1, right = 1; auto rgbBits = reinterpret_cast<const QRgb*>(image.constBits()); for (auto i = 0; i != size; ++i) { if (rgbBits[i] > 0 || rgbBits[(size - 1) * size + i] > 0 || rgbBits[i * size] > 0 || rgbBits[i * size + (size - 1)] > 0) { logDataError() << "Bad border."; return QRect(); } if (rgbBits[1 * size + i] > 0) { top = -1; } else if (top > 0 && rgbBits[2 * size + i] > 0) { top = 0; } if (rgbBits[(size - 2) * size + i] > 0) { bottom = -1; } else if (bottom > 0 && rgbBits[(size - 3) * size + i] > 0) { bottom = 0; } if (rgbBits[i * size + 1] > 0) { left = -1; } else if (left > 0 && rgbBits[i * size + 2] > 0) { left = 0; } if (rgbBits[i * size + (size - 2)] > 0) { right = -1; } else if (right > 0 && rgbBits[i * size + (size - 3)] > 0) { right = 0; } } if (top < 0) { if (bottom <= 0) { logDataError() << "Bad vertical :("; return QRect(); } else { result.setY(result.y() + 1); } } else if (bottom < 0) { if (top <= 0) { logDataError() << "Bad vertical :("; return QRect(); } else { result.setY(result.y() - 1); } } if (left < 0) { if (right <= 0) { logDataError() << "Bad horizontal :("; return QRect(); } else { result.setX(result.x() + 1); } } else if (right < 0) { if (left <= 0) { logDataError() << "Bad horizontal :("; return QRect(); } else { result.setX(result.x() - 1); } } return result; } uint32 Crc32Table[256]; class Crc32Initializer { public: Crc32Initializer() { uint32 poly = 0x04C11DB7U; for (auto i = 0; i != 256; ++i) { Crc32Table[i] = reflect(i, 8) << 24; for (auto j = 0; j != 8; ++j) { Crc32Table[i] = (Crc32Table[i] << 1) ^ (Crc32Table[i] & (1 << 31) ? poly : 0); } Crc32Table[i] = reflect(Crc32Table[i], 32); } } private: uint32 reflect(uint32 val, char ch) { uint32 result = 0; for (int i = 1; i < (ch + 1); ++i) { if (val & 1) { result |= 1 << (ch - i); } val >>= 1; } return result; } }; uint32 countCrc32(const void *data, std::size_t size) { static Crc32Initializer InitTable; auto buffer = static_cast<const unsigned char*>(data); auto result = uint32(0xFFFFFFFFU); for (auto i = std::size_t(0); i != size; ++i) { result = (result >> 8) ^ Crc32Table[(result & 0xFFU) ^ buffer[i]]; } return (result ^ 0xFFFFFFFFU); } } // namespace Generator::Generator(const Options &options) : project_(Project) #ifdef SUPPORT_IMAGE_GENERATION , writeImages_(options.writeImages) #endif // SUPPORT_IMAGE_GENERATION , data_(PrepareData()) , replaces_(PrepareReplaces(options.replacesPath)) { QDir dir(options.outputPath); if (!dir.mkpath(".")) { common::logError(kErrorCantWritePath, "Command Line") << "can not open path for writing: " << dir.absolutePath().toStdString(); data_ = Data(); } if (!CheckAndConvertReplaces(replaces_, data_)) { replaces_ = Replaces(replaces_.filename); } outputPath_ = dir.absolutePath() + "/emoji"; spritePath_ = dir.absolutePath() + "/emoji"; suggestionsPath_ = dir.absolutePath() + "/emoji_suggestions_data"; } int Generator::generate() { if (data_.list.empty() || replaces_.list.isEmpty()) { return -1; } #ifdef SUPPORT_IMAGE_GENERATION if (writeImages_) { return writeImages() ? 0 : -1; } #endif // SUPPORT_IMAGE_GENERATION if (!writeSource()) { return -1; } if (!writeHeader()) { return -1; } if (!writeSuggestionsSource()) { return -1; } if (!writeSuggestionsHeader()) { return -1; } return 0; } constexpr auto kVariantsCount = 5; constexpr auto kEmojiInRow = 40; #ifdef SUPPORT_IMAGE_GENERATION QImage Generator::generateImage(int variantIndex) { constexpr int kEmojiSizes[kVariantsCount + 1] = { 18, 22, 27, 36, 45, 180 }; constexpr bool kBadSizes[kVariantsCount] = { true, true, false, false, false }; constexpr int kEmojiFontSizes[kVariantsCount + 1] = { 14, 20, 27, 36, 45, 180 }; constexpr int kEmojiDeltas[kVariantsCount + 1] = { 15, 20, 25, 34, 42, 167 }; auto emojiCount = data_.list.size(); auto columnsCount = kEmojiInRow; auto rowsCount = (emojiCount / columnsCount) + ((emojiCount % columnsCount) ? 1 : 0); auto emojiSize = kEmojiSizes[variantIndex]; auto isBad = kBadSizes[variantIndex]; auto sourceSize = (isBad ? kEmojiSizes[kVariantsCount] : emojiSize); auto font = QGuiApplication::font(); font.setFamily(QStringLiteral("Apple Color Emoji")); font.setPixelSize(kEmojiFontSizes[isBad ? kVariantsCount : variantIndex]); auto singleSize = 4 + sourceSize; auto emojiImage = QImage(columnsCount * emojiSize, rowsCount * emojiSize, QImage::Format_ARGB32); emojiImage.fill(Qt::transparent); auto singleImage = QImage(singleSize, singleSize, QImage::Format_ARGB32); { QPainter p(&emojiImage); p.setRenderHint(QPainter::SmoothPixmapTransform); auto column = 0; auto row = 0; for (auto &emoji : data_.list) { { singleImage.fill(Qt::transparent); QPainter q(&singleImage); q.setPen(QColor(0, 0, 0, 255)); q.setFont(font); q.drawText(2, 2 + kEmojiDeltas[isBad ? kVariantsCount : variantIndex], emoji.id); } auto sourceRect = computeSourceRect(singleImage); if (sourceRect.isEmpty()) { return QImage(); } auto targetRect = QRect(column * emojiSize, row * emojiSize, emojiSize, emojiSize); if (isBad) { p.drawImage(targetRect, singleImage.copy(sourceRect).scaled(emojiSize, emojiSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); } else { p.drawImage(targetRect, singleImage, sourceRect); } ++column; if (column == columnsCount) { column = 0; ++row; } } } return emojiImage; } bool Generator::writeImages() { constexpr const char *variantPostfix[] = { "", "_125x", "_150x", "_200x", "_250x" }; for (auto variantIndex = 0; variantIndex != kVariantsCount; variantIndex++) { auto image = generateImage(variantIndex); auto postfix = variantPostfix[variantIndex]; auto filename = spritePath_ + postfix + ".webp"; auto bytes = QByteArray(); { QBuffer buffer(&bytes); if (!image.save(&buffer, "WEBP", (variantIndex < 3) ? 100 : 99)) { logDataError() << "Could not save 'emoji" << postfix << ".webp'."; return false; } } auto needResave = !QFileInfo(filename).exists(); if (!needResave) { QFile file(filename); if (!file.open(QIODevice::ReadOnly)) { needResave = true; } else { auto already = file.readAll(); if (already.size() != bytes.size() || memcmp(already.constData(), bytes.constData(), already.size())) { needResave = true; } } } if (needResave) { QFile file(filename); if (!file.open(QIODevice::WriteOnly)) { logDataError() << "Could not open 'emoji" << postfix << ".png'."; return false; } else { if (file.write(bytes) != bytes.size()) { logDataError() << "Could not write 'emoji" << postfix << ".png'."; return false; } } } } return true; } #endif // SUPPORT_IMAGE_GENERATION bool Generator::writeSource() { source_ = std::make_unique<common::CppFile>(outputPath_ + ".cpp", project_); source_->include("emoji_suggestions_data.h").newline(); source_->pushNamespace("Ui").pushNamespace("Emoji").pushNamespace(); source_->stream() << "\ \n\ std::vector<One> Items;\n\ \n"; if (!writeInitCode()) { return false; } if (!writeSections()) { return false; } if (!writeFindReplace()) { return false; } if (!writeFind()) { return false; } source_->popNamespace().newline().pushNamespace("internal"); source_->stream() << "\ \n\ EmojiPtr ByIndex(int index) {\n\ return (index >= 0 && index < Items.size()) ? &Items[index] : nullptr;\n\ }\n\ \n\ EmojiPtr FindReplace(const QChar *start, const QChar *end, int *outLength) {\n\ auto index = FindReplaceIndex(start, end, outLength);\n\ return index ? &Items[index - 1] : nullptr;\n\ }\n\ \n\ EmojiPtr Find(const QChar *start, const QChar *end, int *outLength) {\n\ auto index = FindIndex(start, end, outLength);\n\ return index ? &Items[index - 1] : nullptr;\n\ }\n\ \n\ void Init() {\n\ auto id = IdData;\n\ auto takeString = [&id](int size) {\n\ auto result = QString::fromRawData(reinterpret_cast<const QChar*>(id), size);\n\ id += size;\n\ return result;\n\ };\n\ \n\ Items.reserve(base::array_size(Data));\n\ for (auto &data : Data) {\n\ Items.emplace_back(takeString(data.idSize), uint16(data.column), uint16(data.row), bool(data.postfixed), bool(data.variated), data.original ? &Items[data.original - 1] : nullptr, One::CreationTag());\n\ }\n\ InitReplacements();\n\ }\n\ \n"; source_->popNamespace(); if (!writeGetSections()) { return false; } return source_->finalize(); } bool Generator::writeHeader() { auto header = std::make_unique<common::CppFile>(outputPath_ + ".h", project_); header->pushNamespace("Ui").pushNamespace("Emoji").pushNamespace("internal"); header->stream() << "\ \n\ void Init();\n\ \n\ EmojiPtr ByIndex(int index);\n\ \n\ EmojiPtr Find(const QChar *ch, const QChar *end, int *outLength = nullptr);\n\ \n\ inline bool IsReplaceEdge(const QChar *ch) {\n\ return true;\n\ \n\ // switch (ch->unicode()) {\n\ // case '.': case ',': case ':': case ';': case '!': case '?': case '#': case '@':\n\ // case '(': case ')': case '[': case ']': case '{': case '}': case '<': case '>':\n\ // case '+': case '=': case '-': case '_': case '*': case '/': case '\\\\': case '^': case '$':\n\ // case '\"': case '\\'':\n\ // case 8212: case 171: case 187: // --, <<, >>\n\ // return true;\n\ // }\n\ // return false;\n\ }\n\ \n\ EmojiPtr FindReplace(const QChar *ch, const QChar *end, int *outLength = nullptr);\n\ \n"; header->popNamespace().stream() << "\ \n\ enum class Section {\n\ Recent,\n\ People,\n\ Nature,\n\ Food,\n\ Activity,\n\ Travel,\n\ Objects,\n\ Symbols,\n\ };\n\ \n\ int Index();\n\ \n\ int GetSectionCount(Section section);\n\ EmojiPack GetSection(Section section);\n\ \n"; return header->finalize(); } template <typename Callback> bool Generator::enumerateWholeList(Callback callback) { auto column = 0; auto row = 0; auto index = 0; auto variated = -1; auto coloredCount = 0; for (auto &item : data_.list) { if (!callback(item.id, column, row, item.postfixed, item.variated, item.colored, variated)) { return false; } if (coloredCount > 0 && (item.variated || !item.colored)) { if (!colorsCount_) { colorsCount_ = coloredCount; } else if (colorsCount_ != coloredCount) { logDataError() << "different colored emoji count exist."; return false; } coloredCount = 0; } if (item.variated) { variated = index; } else if (item.colored) { if (variated <= 0) { logDataError() << "wrong order of colored items."; return false; } ++coloredCount; } else if (variated >= 0) { variated = -1; } if (++column == kEmojiInRow) { column = 0; ++row; } ++index; } return true; } bool Generator::writeInitCode() { source_->stream() << "\ struct DataStruct {\n\ ushort original : " << kOriginalBits << ";\n\ uchar idSize : " << kIdSizeBits << ";\n\ uchar column : " << kColumnBits << ";\n\ uchar row : " << kRowBits << ";\n\ bool postfixed : 1;\n\ bool variated : 1;\n\ };\n\ \n\ const ushort IdData[] = {"; startBinary(); if (!enumerateWholeList([this](Id id, int column, int row, bool isPostfixed, bool isVariated, bool isColored, int original) { return writeStringBinary(source_.get(), id); })) { return false; } if (_binaryFullLength >= std::numeric_limits<ushort>::max()) { logDataError() << "Too many IdData elements."; return false; } source_->stream() << " };\n\ \n\ const DataStruct Data[] = {\n"; if (!enumerateWholeList([this](Id id, int column, int row, bool isPostfixed, bool isVariated, bool isColored, int original) { if (original + 1 >= (1 << kOriginalBits)) { logDataError() << "Too many entries."; return false; } if (id.size() >= (1 << kIdSizeBits)) { logDataError() << "Too large id."; return false; } if (column >= (1 << kColumnBits) || row >= (1 << kRowBits)) { logDataError() << "Bad row-column."; return false; } source_->stream() << "\ { ushort(" << (isColored ? (original + 1) : 0) << "), uchar(" << id.size() << "), uchar(" << column << "), uchar(" << row << "), " << (isPostfixed ? "true" : "false") << ", " << (isVariated ? "true" : "false") << " },\n"; return true; })) { return false; } source_->stream() << "\ };\n"; return true; } bool Generator::writeSections() { source_->stream() << "\ const ushort SectionData[] = {"; startBinary(); for (auto &category : data_.categories) { for (auto index : category) { writeIntBinary(source_.get(), index); } } source_->stream() << " };\n\ \n\ EmojiPack FillSection(int offset, int size) {\n\ auto result = EmojiPack();\n\ result.reserve(size);\n\ for (auto index : gsl::make_span(SectionData + offset, size)) {\n\ result.push_back(&Items[index]);\n\ }\n\ return result;\n\ }\n\n"; return true; } bool Generator::writeGetSections() { constexpr const char *sectionNames[] = { "Section::People", "Section::Nature", "Section::Food", "Section::Activity", "Section::Travel", "Section::Objects", "Section::Symbols", }; source_->stream() << "\ \n\ int GetSectionCount(Section section) {\n\ switch (section) {\n\ case Section::Recent: return GetRecent().size();\n"; auto countIndex = 0; for (auto name : sectionNames) { if (countIndex >= int(data_.categories.size())) { logDataError() << "category " << countIndex << " not found."; return false; } source_->stream() << "\ case " << name << ": return " << data_.categories[countIndex++].size() << ";\n"; } source_->stream() << "\ }\n\ return 0;\n\ }\n\ \n\ EmojiPack GetSection(Section section) {\n\ switch (section) {\n\ case Section::Recent: {\n\ auto result = EmojiPack();\n\ result.reserve(GetRecent().size());\n\ for (auto &item : GetRecent()) {\n\ result.push_back(item.first);\n\ }\n\ return result;\n\ } break;\n"; auto index = 0; auto offset = 0; for (auto name : sectionNames) { if (index >= int(data_.categories.size())) { logDataError() << "category " << index << " not found."; return false; } auto &category = data_.categories[index++]; source_->stream() << "\ \n\ case " << name << ": {\n\ static auto result = FillSection(" << offset << ", " << category.size() << ");\n\ return result;\n\ } break;\n"; offset += category.size(); } source_->stream() << "\ }\n\ return EmojiPack();\n\ }\n\ \n"; return true; } bool Generator::writeFindReplace() { source_->stream() << "\ \n\ int FindReplaceIndex(const QChar *start, const QChar *end, int *outLength) {\n\ auto ch = start;\n\ \n"; if (!writeFindFromDictionary(data_.replaces)) { return false; } source_->stream() << "\ }\n"; return true; } bool Generator::writeFind() { source_->stream() << "\ \n\ int FindIndex(const QChar *start, const QChar *end, int *outLength) {\n\ auto ch = start;\n\ \n"; if (!writeFindFromDictionary(data_.map, true)) { return false; } source_->stream() << "\ }\n\ \n"; return true; } bool Generator::writeFindFromDictionary(const std::map<QString, int, std::greater<QString>> &dictionary, bool skipPostfixes) { auto tabs = [](int size) { return QString(size, '\t'); }; std::map<int, int> uniqueFirstChars; auto foundMax = 0, foundMin = 65535; for (auto &item : dictionary) { auto ch = item.first[0].unicode(); if (foundMax < ch) foundMax = ch; if (foundMin > ch) foundMin = ch; uniqueFirstChars[ch] = 0; } enum class UsedCheckType { Switch, If, }; auto checkTypes = QVector<UsedCheckType>(); auto chars = QString(); auto tabsUsed = 1; auto lengthsCounted = std::map<QString, bool>(); auto writeSkipPostfix = [this, &tabs, skipPostfixes](int tabsCount) { if (skipPostfixes) { source_->stream() << tabs(tabsCount) << "if (++ch != end && ch->unicode() == kPostfix) ++ch;\n"; } else { source_->stream() << tabs(tabsCount) << "++ch;\n"; } }; // Returns true if at least one check was finished. auto finishChecksTillKey = [this, &chars, &checkTypes, &tabsUsed, tabs](const QString &key) { auto result = false; while (!chars.isEmpty() && key.midRef(0, chars.size()) != chars) { result = true; auto wasType = checkTypes.back(); chars.resize(chars.size() - 1); checkTypes.pop_back(); if (wasType == UsedCheckType::Switch || wasType == UsedCheckType::If) { --tabsUsed; if (wasType == UsedCheckType::Switch) { source_->stream() << tabs(tabsUsed) << "break;\n"; } if ((!chars.isEmpty() && key.midRef(0, chars.size()) != chars) || key == chars) { source_->stream() << tabs(tabsUsed) << "}\n"; } } } return result; }; // Check if we can use "if" for a check on "charIndex" in "it" (otherwise only "switch") auto canUseIfForCheck = [](auto it, auto end, int charIndex) { auto key = it->first; auto i = it; auto keyStart = key.mid(0, charIndex); for (++i; i != end; ++i) { auto nextKey = i->first; if (nextKey.mid(0, charIndex) != keyStart) { return true; } else if (nextKey.size() > charIndex && nextKey[charIndex] != key[charIndex]) { return false; } } return true; }; for (auto i = dictionary.cbegin(), e = dictionary.cend(); i != e; ++i) { auto &item = *i; auto key = item.first; auto weContinueOldSwitch = finishChecksTillKey(key); while (chars.size() != key.size()) { auto checking = chars.size(); auto partialKey = key.mid(0, checking); if (dictionary.find(partialKey) != dictionary.cend()) { if (lengthsCounted.find(partialKey) == lengthsCounted.cend()) { lengthsCounted.insert(std::make_pair(partialKey, true)); source_->stream() << tabs(tabsUsed) << "if (outLength) *outLength = (ch - start);\n"; } } auto keyChar = key[checking]; auto keyCharString = "0x" + QString::number(keyChar.unicode(), 16); auto usedIfForCheck = !weContinueOldSwitch && canUseIfForCheck(i, e, checking); if (weContinueOldSwitch) { weContinueOldSwitch = false; } else if (!usedIfForCheck) { source_->stream() << tabs(tabsUsed) << "if (ch != end) switch (ch->unicode()) {\n"; } if (usedIfForCheck) { source_->stream() << tabs(tabsUsed) << "if (ch != end && ch->unicode() == " << keyCharString << ") {\n"; checkTypes.push_back(UsedCheckType::If); } else { source_->stream() << tabs(tabsUsed) << "case " << keyCharString << ":\n"; checkTypes.push_back(UsedCheckType::Switch); } writeSkipPostfix(++tabsUsed); chars.push_back(keyChar); } if (lengthsCounted.find(key) == lengthsCounted.cend()) { lengthsCounted.insert(std::make_pair(key, true)); source_->stream() << tabs(tabsUsed) << "if (outLength) *outLength = (ch - start);\n"; } // While IsReplaceEdge() currently is always true we just return the value. //source_->stream() << tabs(1 + chars.size()) << "if (ch + " << chars.size() << " == end || IsReplaceEdge(*(ch + " << chars.size() << ")) || (ch + " << chars.size() << ")->unicode() == ' ') {\n"; //source_->stream() << tabs(1 + chars.size()) << "\treturn &Items[" << item.second << "];\n"; //source_->stream() << tabs(1 + chars.size()) << "}\n"; source_->stream() << tabs(tabsUsed) << "return " << (item.second + 1) << ";\n"; } finishChecksTillKey(QString()); source_->stream() << "\ \n\ return 0;\n"; return true; } bool Generator::writeSuggestionsSource() { suggestionsSource_ = std::make_unique<common::CppFile>(suggestionsPath_ + ".cpp", project_); suggestionsSource_->stream() << "\ #include <map>\n\ \n"; suggestionsSource_->pushNamespace("Ui").pushNamespace("Emoji").pushNamespace("internal").pushNamespace(); suggestionsSource_->stream() << "\ \n"; if (!writeReplacements()) { return false; } suggestionsSource_->popNamespace().newline(); if (!writeGetReplacements()) { return false; } return suggestionsSource_->finalize(); } bool Generator::writeSuggestionsHeader() { auto maxLength = 0; for (auto &replace : replaces_.list) { if (maxLength < replace.replacement.size()) { maxLength = replace.replacement.size(); } } auto header = std::make_unique<common::CppFile>(suggestionsPath_ + ".h", project_); header->include("emoji_suggestions.h").newline(); header->pushNamespace("Ui").pushNamespace("Emoji").pushNamespace("internal"); header->stream() << "\ \n\ struct Replacement {\n\ utf16string emoji;\n\ utf16string replacement;\n\ std::vector<utf16string> words;\n\ };\n\ \n\ constexpr auto kReplacementMaxLength = " << maxLength << ";\n\ \n\ void InitReplacements();\n\ const std::vector<const Replacement*> *GetReplacements(utf16char first);\n\ utf16string GetReplacementEmoji(utf16string replacement);\n\ \n"; return header->finalize(); } bool Generator::writeReplacements() { QMap<QChar, QVector<int>> byCharIndices; suggestionsSource_->stream() << "\ struct ReplacementStruct {\n\ small emojiSize;\n\ small replacementSize;\n\ small wordsCount;\n\ };\n\ \n\ const utf16char ReplacementData[] = {"; startBinary(); for (auto i = 0, size = replaces_.list.size(); i != size; ++i) { auto &replace = replaces_.list[i]; if (!writeStringBinary(suggestionsSource_.get(), replace.id)) { return false; } if (!writeStringBinary(suggestionsSource_.get(), replace.replacement)) { return false; } for (auto &word : replace.words) { if (!writeStringBinary(suggestionsSource_.get(), word)) { return false; } auto &index = byCharIndices[word[0]]; if (index.isEmpty() || index.back() != i) { index.push_back(i); } } } suggestionsSource_->stream() << " };\n\ \n\ const small ReplacementWordLengths[] = {"; startBinary(); for (auto &replace : replaces_.list) { auto wordLengths = QStringList(); for (auto &word : replace.words) { writeIntBinary(suggestionsSource_.get(), word.size()); } } suggestionsSource_->stream() << " };\n\ \n\ const ReplacementStruct ReplacementInitData[] = {\n"; for (auto &replace : replaces_.list) { suggestionsSource_->stream() << "\ { small(" << replace.id.size() << "), small(" << replace.replacement.size() << "), small(" << replace.words.size() << ") },\n"; } suggestionsSource_->stream() << "};\n\ \n\ const medium ReplacementIndices[] = {"; startBinary(); for (auto &byCharIndex : byCharIndices) { for (auto index : byCharIndex) { writeIntBinary(suggestionsSource_.get(), index); } } suggestionsSource_->stream() << " };\n\ \n\ struct ReplacementIndexStruct {\n\ utf16char ch;\n\ medium count;\n\ };\n\ \n\ const internal::checksum ReplacementChecksums[] = {\n"; startBinary(); for (auto &replace : replaces_.list) { writeUintBinary(suggestionsSource_.get(), countCrc32(replace.replacement.constData(), replace.replacement.size() * sizeof(QChar))); } suggestionsSource_->stream() << " };\n\ \n\ const ReplacementIndexStruct ReplacementIndexData[] = {\n"; startBinary(); for (auto i = byCharIndices.cbegin(), e = byCharIndices.cend(); i != e; ++i) { suggestionsSource_->stream() << "\ { utf16char(" << i.key().unicode() << "), medium(" << i.value().size() << ") },\n"; } suggestionsSource_->stream() << "};\n\ \n\ std::vector<Replacement> Replacements;\n\ std::map<utf16char, std::vector<const Replacement*>> ReplacementsMap;\n\ std::map<internal::checksum, const Replacement*> ReplacementsHash;\n\ \n"; return true; } bool Generator::writeGetReplacements() { suggestionsSource_->stream() << "\ void InitReplacements() {\n\ if (!Replacements.empty()) {\n\ return;\n\ }\n\ auto data = ReplacementData;\n\ auto takeString = [&data](int size) {\n\ auto result = utf16string(data, size);\n\ data += size;\n\ return result;\n\ };\n\ auto wordSize = ReplacementWordLengths;\n\ \n\ Replacements.reserve(" << replaces_.list.size() << ");\n\ for (auto item : ReplacementInitData) {\n\ auto emoji = takeString(item.emojiSize);\n\ auto replacement = takeString(item.replacementSize);\n\ auto words = std::vector<utf16string>();\n\ words.reserve(item.wordsCount);\n\ for (auto i = 0; i != item.wordsCount; ++i) {\n\ words.push_back(takeString(*wordSize++));\n\ }\n\ Replacements.push_back({ std::move(emoji), std::move(replacement), std::move(words) });\n\ }\n\ \n\ auto indices = ReplacementIndices;\n\ auto items = &Replacements[0];\n\ for (auto item : ReplacementIndexData) {\n\ auto index = std::vector<const Replacement*>();\n\ index.reserve(item.count);\n\ for (auto i = 0; i != item.count; ++i) {\n\ index.push_back(items + (*indices++));\n\ }\n\ ReplacementsMap.emplace(item.ch, std::move(index));\n\ }\n\ \n\ for (auto checksum : ReplacementChecksums) {\n\ ReplacementsHash.emplace(checksum, items++);\n\ }\n\ }\n\ \n\ const std::vector<const Replacement*> *GetReplacements(utf16char first) {\n\ if (ReplacementsMap.empty()) {\n\ InitReplacements();\n\ }\n\ auto it = ReplacementsMap.find(first);\n\ return (it == ReplacementsMap.cend()) ? nullptr : &it->second;\n\ }\n\ \n\ utf16string GetReplacementEmoji(utf16string replacement) {\n\ auto code = internal::countChecksum(replacement.data(), replacement.size() * sizeof(utf16char));\n\ auto it = ReplacementsHash.find(code);\n\ return (it == ReplacementsHash.cend()) ? utf16string() : it->second->emoji;\n\ }\n\ \n"; return true; } void Generator::startBinary() { _binaryFullLength = _binaryCount = 0; } bool Generator::writeStringBinary(common::CppFile *source, const QString &string) { if (string.size() >= 256) { logDataError() << "Too long string: " << string.toStdString(); return false; } for (auto ch : string) { if (_binaryFullLength > 0) source->stream() << ","; if (!_binaryCount++) { source->stream() << "\n"; } else { if (_binaryCount == 12) { _binaryCount = 0; } source->stream() << " "; } source->stream() << "0x" << QString::number(ch.unicode(), 16); ++_binaryFullLength; } return true; } void Generator::writeIntBinary(common::CppFile *source, int data) { if (_binaryFullLength > 0) source->stream() << ","; if (!_binaryCount++) { source->stream() << "\n"; } else { if (_binaryCount == 12) { _binaryCount = 0; } source->stream() << " "; } source->stream() << data; ++_binaryFullLength; } void Generator::writeUintBinary(common::CppFile *source, uint32 data) { if (_binaryFullLength > 0) source->stream() << ","; if (!_binaryCount++) { source->stream() << "\n"; } else { if (_binaryCount == 12) { _binaryCount = 0; } source->stream() << " "; } source->stream() << "0x" << QString::number(data, 16).toUpper() << "U"; ++_binaryFullLength; } } // namespace emoji } // namespace codegen �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/emoji/generator.h��������������������������������������0000664�0000000�0000000�00000003726�13262451251�0024301�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <memory> #include <QtCore/QString> #include <QtCore/QSet> #include "codegen/common/cpp_file.h" #include "codegen/emoji/options.h" #include "codegen/emoji/data.h" #include "codegen/emoji/replaces.h" namespace codegen { namespace emoji { using uint32 = unsigned int; class Generator { public: Generator(const Options &options); Generator(const Generator &other) = delete; Generator &operator=(const Generator &other) = delete; int generate(); private: #ifdef SUPPORT_IMAGE_GENERATION QImage generateImage(int variantIndex); bool writeImages(); #endif // SUPPORT_IMAGE_GENERATION bool writeSource(); bool writeHeader(); bool writeSuggestionsSource(); bool writeSuggestionsHeader(); template <typename Callback> bool enumerateWholeList(Callback callback); bool writeInitCode(); bool writeSections(); bool writeReplacements(); bool writeGetSections(); bool writeFindReplace(); bool writeFind(); bool writeFindFromDictionary(const std::map<QString, int, std::greater<QString>> &dictionary, bool skipPostfixes = false); bool writeGetReplacements(); void startBinary(); bool writeStringBinary(common::CppFile *source, const QString &string); void writeIntBinary(common::CppFile *source, int data); void writeUintBinary(common::CppFile *source, uint32 data); const common::ProjectInfo &project_; int colorsCount_ = 0; #ifdef SUPPORT_IMAGE_GENERATION bool writeImages_ = false; #endif // SUPPORT_IMAGE_GENERATION QString outputPath_; QString spritePath_; std::unique_ptr<common::CppFile> source_; Data data_; QString suggestionsPath_; std::unique_ptr<common::CppFile> suggestionsSource_; Replaces replaces_; int _binaryFullLength = 0; int _binaryCount = 0; }; } // namespace emoji } // namespace codegen ������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/emoji/main.cpp�����������������������������������������0000664�0000000�0000000�00000001402�13262451251�0023557�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include <QtGui/QGuiApplication> #include "codegen/emoji/options.h" #include "codegen/emoji/generator.h" int main(int argc, char *argv[]) { #ifdef SUPPORT_IMAGE_GENERATION #ifndef Q_OS_MAC #error "Image generation is supported only on macOS" #endif // Q_OS_MAC QGuiApplication app(argc, argv); #else // SUPPORT_IMAGE_GENERATION QCoreApplication app(argc, argv); #endif // SUPPORT_IMAGE_GENERATION auto options = codegen::emoji::parseOptions(); codegen::emoji::Generator generator(options); return generator.generate(); } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/emoji/options.cpp��������������������������������������0000664�0000000�0000000�00000003411�13262451251�0024330�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "codegen/emoji/options.h" #include <ostream> #include <QtCore/QCoreApplication> #include "codegen/common/logging.h" namespace codegen { namespace emoji { namespace { constexpr int kErrorOutputPathExpected = 902; constexpr int kErrorReplacesPathExpected = 903; constexpr int kErrorOneReplacesPathExpected = 904; } // namespace using common::logError; Options parseOptions() { Options result; auto args = QCoreApplication::instance()->arguments(); for (int i = 1, count = args.size(); i < count; ++i) { // skip first auto &arg = args.at(i); // Output path if (arg == "-o") { if (++i == count) { logError(kErrorOutputPathExpected, "Command Line") << "output path expected after -o"; return Options(); } else { result.outputPath = args.at(i); } } else if (arg.startsWith("-o")) { result.outputPath = arg.mid(2); #ifdef SUPPORT_IMAGE_GENERATION } else if (arg == "--images") { result.writeImages = true; #endif // SUPPORT_IMAGE_GENERATION } else if (result.replacesPath.isEmpty()) { result.replacesPath = arg; } else { logError(kErrorOneReplacesPathExpected, "Command Line") << "only one replaces path expected"; return Options(); } } if (result.outputPath.isEmpty()) { logError(kErrorOutputPathExpected, "Command Line") << "output path expected"; return Options(); } else if (result.replacesPath.isEmpty()) { logError(kErrorReplacesPathExpected, "Command Line") << "replaces path expected"; return Options(); } return result; } } // namespace emoji } // namespace codegen �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/emoji/options.h����������������������������������������0000664�0000000�0000000�00000001177�13262451251�0024004�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <QtCore/QString> #include <QtCore/QStringList> namespace codegen { namespace emoji { struct Options { QString outputPath = "."; QString replacesPath; #ifdef SUPPORT_IMAGE_GENERATION bool writeImages = false; #endif // SUPPORT_IMAGE_GENERATION }; // Parsing failed if inputPath is empty in the result. Options parseOptions(); } // namespace emoji } // namespace codegen �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/emoji/replaces.cpp�������������������������������������0000664�0000000�0000000�00000040415�13262451251�0024440�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "codegen/emoji/replaces.h" #include "codegen/emoji/data.h" #include <QtCore/QFile> #include <QtCore/QJsonDocument> #include <QtCore/QJsonObject> #include <QtCore/QRegularExpression> namespace codegen { namespace emoji { namespace { constexpr auto kErrorBadReplaces = 402; common::LogStream logReplacesError(const QString &filename) { return common::logError(kErrorBadReplaces, filename) << "Bad data: "; } auto RegExpCode = QRegularExpression("^:[\\+\\-a-z0-9_]+:$"); auto RegExpTone = QRegularExpression("_tone[0-9]"); auto RegExpHex = QRegularExpression("^[0-9a-f]+$"); class ReplacementWords { public: ReplacementWords(const QString &string); QVector<QString> result() const; private: friend ReplacementWords operator+(const ReplacementWords &a, const ReplacementWords &b); QMap<QString, int> wordsWithCounts_; }; ReplacementWords::ReplacementWords(const QString &string) { auto feedWord = [this](QString &word) { if (!word.isEmpty()) { ++wordsWithCounts_[word]; word.clear(); } }; // Split by all non-letters-or-numbers. // Leave '-' and '+' inside a word only if they're followed by a number. auto word = QString(); for (auto i = string.cbegin(), e = string.cend(); i != e; ++i) { if (i->isLetterOrNumber()) { word.append(*i); continue; } else if (*i == '-' || *i == '+') { if (i + 1 != e && (i + 1)->isNumber()) { word.append(*i); continue; } } feedWord(word); } feedWord(word); } QVector<QString> ReplacementWords::result() const { auto result = QVector<QString>(); for (auto i = wordsWithCounts_.cbegin(), e = wordsWithCounts_.cend(); i != e; ++i) { for (auto j = 0, count = i.value(); j != count; ++j) { result.push_back(i.key()); } } return result; } ReplacementWords operator+(const ReplacementWords &a, const ReplacementWords &b) { ReplacementWords result = a; for (auto i = b.wordsWithCounts_.cbegin(), e = b.wordsWithCounts_.cend(); i != e; ++i) { auto j = result.wordsWithCounts_.constFind(i.key()); if (j == result.wordsWithCounts_.cend() || j.value() < i.value()) { result.wordsWithCounts_[i.key()] = i.value(); } } return result; } bool AddReplacement(Replaces &result, const Id &id, const QString &replacement, const QString &name) { auto replace = Replace(); replace.id = id; replace.replacement = replacement; replace.words = (ReplacementWords(replacement)).result();// + ReplacementWords(name)).result(); if (replace.words.isEmpty()) { logReplacesError(result.filename) << "Child '" << replacement.toStdString() << "' has no words."; return false; } result.list.push_back(replace); return true; } QString ComposeString(const std::initializer_list<QChar> &chars) { auto result = QString(); result.reserve(chars.size()); for (auto ch : chars) { result.append(ch); } return result; } const auto NotSupported = ([] { auto result = QSet<QString>(); auto insert = [&result](auto... args) { result.insert(ComposeString({ args... })); }; insert(0x0023, 0xFE0F); // :pound_symbol: insert(0x002A, 0xFE0F); // :asterisk_symbol: for (auto i = 0; i != 10; ++i) { insert(0x0030 + i, 0xFE0F); // :digit_zero: ... :digit_nine: } for (auto i = 0; i != 5; ++i) { insert(0xD83C, 0xDFFB + i); // :tone1: ... :tone5: } for (auto i = 0; i != 26; ++i) { insert(0xD83C, 0xDDE6 + i); // :regional_indicator_a: ... :regional_indicator_z: } insert(0xD83C, 0xDDFA, 0xD83C, 0xDDF3); // :united_nations: insert(0xD83C, 0xDFF4, 0xDB40, 0xDC67, 0xDB40, 0xDC62, 0xDB40, 0xDC65, 0xDB40, 0xDC6E, 0xDB40, 0xDC67, 0xDB40, 0xDC7F); // :england: insert(0xD83C, 0xDFF4, 0xDB40, 0xDC67, 0xDB40, 0xDC62, 0xDB40, 0xDC73, 0xDB40, 0xDC63, 0xDB40, 0xDC74, 0xDB40, 0xDC7F); // :scotland: insert(0xD83C, 0xDFF4, 0xDB40, 0xDC67, 0xDB40, 0xDC62, 0xDB40, 0xDC77, 0xDB40, 0xDC6C, 0xDB40, 0xDC73, 0xDB40, 0xDC7F); // :wales: insert(0xD83D, 0xDEF7); // :sled: insert(0xD83D, 0xDEF8); // :flying_saucer: insert(0xD83E, 0xDD1F); // :love_you_gesture: insert(0xD83E, 0xDD28); // :face_with_raised_eyebrow: insert(0xD83E, 0xDD29); // :star_struck: insert(0xD83E, 0xDD2A); // :crazy_face: insert(0xD83E, 0xDD2B); // :shushing_face: insert(0xD83E, 0xDD2C); // :face_with_symbols_over_mouth: insert(0xD83E, 0xDD2D); // :face_with_hand_over_mouth: insert(0xD83E, 0xDD2E); // :face_vomiting: insert(0xD83E, 0xDD2F); // :exploding_head: insert(0xD83E, 0xDD31); // :breast_feeding: insert(0xD83E, 0xDD32); // :palms_up_together: insert(0xD83E, 0xDD4C); // :curling_stone: insert(0xD83E, 0xDD5F); // :dumpling: insert(0xD83E, 0xDD60); // :fortune_cookie: insert(0xD83E, 0xDD61); // :takeout_box: insert(0xD83E, 0xDD62); // :chopsticks: insert(0xD83E, 0xDD63); // :bowl_with_spoon: insert(0xD83E, 0xDD64); // :cup_with_straw: insert(0xD83E, 0xDD65); // :coconut: insert(0xD83E, 0xDD66); // :broccoli: insert(0xD83E, 0xDD67); // :pie: insert(0xD83E, 0xDD68); // :pretzel: insert(0xD83E, 0xDD69); // :cut_of_meat: insert(0xD83E, 0xDD6A); // :sandwich: insert(0xD83E, 0xDD6B); // :canned_food: insert(0xD83E, 0xDD92); // :giraffe: insert(0xD83E, 0xDD93); // :zebra: insert(0xD83E, 0xDD94); // :hedgehog: insert(0xD83E, 0xDD95); // :sauropod: insert(0xD83E, 0xDD96); // :t_rex: insert(0xD83E, 0xDD97); // :cricket: insert(0xD83E, 0xDDD0); // :face_with_monocle: insert(0xD83E, 0xDDD1); // :adult: insert(0xD83E, 0xDDD2); // :child: insert(0xD83E, 0xDDD3); // :older_adult: insert(0xD83E, 0xDDD4); // :bearded_person: insert(0xD83E, 0xDDD5); // :woman_with_headscarf: insert(0xD83E, 0xDDD6); // :person_in_steamy_room: insert(0xD83E, 0xDDD6, 0x200D, 0x2640, 0xFE0F); // :woman_in_steamy_room: insert(0xD83E, 0xDDD6, 0x200D, 0x2642, 0xFE0F); // :man_in_steamy_room: insert(0xD83E, 0xDDD7); // :person_climbing: insert(0xD83E, 0xDDD7, 0x200D, 0x2640, 0xFE0F); // :woman_climbing: insert(0xD83E, 0xDDD7, 0x200D, 0x2642, 0xFE0F); // :man_climbing: insert(0xD83E, 0xDDD8); // :person_in_lotus_position: insert(0xD83E, 0xDDD8, 0x200D, 0x2640, 0xFE0F); // :woman_in_lotus_position: insert(0xD83E, 0xDDD8, 0x200D, 0x2642, 0xFE0F); // :man_in_lotus_position: insert(0xD83E, 0xDDD9); // :mage: insert(0xD83E, 0xDDD9, 0x200D, 0x2640, 0xFE0F); // :woman_mage: insert(0xD83E, 0xDDD9, 0x200D, 0x2642, 0xFE0F); // :man_mage: insert(0xD83E, 0xDDDA); // :fairy: insert(0xD83E, 0xDDDA, 0x200D, 0x2640, 0xFE0F); // :woman_fairy: insert(0xD83E, 0xDDDA, 0x200D, 0x2642, 0xFE0F); // :man_fairy: insert(0xD83E, 0xDDDB); // :vampire: insert(0xD83E, 0xDDDB, 0x200D, 0x2640, 0xFE0F); // :woman_vampire: insert(0xD83E, 0xDDDB, 0x200D, 0x2642, 0xFE0F); // :man_vampire: insert(0xD83E, 0xDDDC); // :merperson: insert(0xD83E, 0xDDDC, 0x200D, 0x2640, 0xFE0F); // :mermaid: insert(0xD83E, 0xDDDC, 0x200D, 0x2642, 0xFE0F); // :merman: insert(0xD83E, 0xDDDD); // :elf: insert(0xD83E, 0xDDDD, 0x200D, 0x2640, 0xFE0F); // :woman_elf: insert(0xD83E, 0xDDDD, 0x200D, 0x2642, 0xFE0F); // :man_elf: insert(0xD83E, 0xDDDE); // :genie: insert(0xD83E, 0xDDDE, 0x200D, 0x2640, 0xFE0F); // :woman_genie: insert(0xD83E, 0xDDDE, 0x200D, 0x2642, 0xFE0F); // :man_genie: insert(0xD83E, 0xDDDF); // :zombie: insert(0xD83E, 0xDDDF, 0x200D, 0x2640, 0xFE0F); // :woman_zombie: insert(0xD83E, 0xDDDF, 0x200D, 0x2642, 0xFE0F); // :man_zombie: insert(0xD83E, 0xDDE0); // :brain: insert(0xD83E, 0xDDE1); // :orange_heart: insert(0xD83E, 0xDDE2); // :billed_cap: insert(0xD83E, 0xDDE3); // :scarf: insert(0xD83E, 0xDDE4); // :gloves: insert(0xD83E, 0xDDE5); // :coat: insert(0xD83E, 0xDDE6); // :socks: insert(0x23CF, 0xFE0F); // :eject: insert(0x2640, 0xFE0F); // :female_sign: insert(0x2642, 0xFE0F); // :male_sign: insert(0x2695, 0xFE0F); // :medical_symbol: return result; })(); const auto ConvertMap = ([] { auto result = QMap<QString, QString>(); auto insert = [&result](const std::initializer_list<QChar> &from, const std::initializer_list<QChar> &to) { result.insert(ComposeString(from), ComposeString(to)); }; auto insertWithAdd = [&result](const std::initializer_list<QChar> &from, const QString &added) { auto code = ComposeString(from); result.insert(code, code + added); }; auto maleModifier = ComposeString({ 0x200D, 0x2642, 0xFE0F }); auto femaleModifier = ComposeString({ 0x200D, 0x2640, 0xFE0F }); insertWithAdd({ 0xD83E, 0xDD26 }, maleModifier); insertWithAdd({ 0xD83E, 0xDD37 }, femaleModifier); insertWithAdd({ 0xD83E, 0xDD38 }, maleModifier); insertWithAdd({ 0xD83E, 0xDD39 }, maleModifier); insertWithAdd({ 0xD83E, 0xDD3C }, maleModifier); insertWithAdd({ 0xD83E, 0xDD3D }, maleModifier); insertWithAdd({ 0xD83E, 0xDD3E }, femaleModifier); // :kiss_woman_man: insert({ 0xD83D, 0xDC69, 0x200D, 0x2764, 0xFE0F, 0x200D, 0xD83D, 0xDC8B, 0x200D, 0xD83D, 0xDC68 }, { 0xD83D, 0xDC8F }); // :family_man_woman_boy: insert({ 0xD83D, 0xDC68, 0x200D, 0xD83D, 0xDC69, 0x200D, 0xD83D, 0xDC66 }, { 0xD83D, 0xDC6A }); // :couple_with_heart_woman_man: insert({ 0xD83D, 0xDC69, 0x200D, 0x2764, 0xFE0F, 0x200D, 0xD83D, 0xDC68 }, { 0xD83D, 0xDC91 }); auto insertFlag = [insert](char ch1, char ch2, char ch3, char ch4) { insert({ 0xD83C, 0xDDE6 + (ch1 - 'a'), 0xD83C, 0xDDe6 + (ch2 - 'a') }, { 0xD83C, 0xDDE6 + (ch3 - 'a'), 0xD83C, 0xDDe6 + (ch4 - 'a') }); }; insertFlag('a', 'c', 's', 'h'); insertFlag('b', 'v', 'n', 'o'); insertFlag('c', 'p', 'f', 'r'); insertFlag('d', 'g', 'i', 'o'); insertFlag('e', 'a', 'e', 's'); insertFlag('h', 'm', 'a', 'u'); insertFlag('m', 'f', 'f', 'r'); insertFlag('s', 'j', 'n', 'o'); insertFlag('t', 'a', 's', 'h'); insertFlag('u', 'm', 'u', 's'); return result; })(); // Empty string result means we should skip this one. QString ConvertEmojiId(const Id &id, const QString &replacement) { if (RegExpTone.match(replacement).hasMatch()) { return QString(); } if (NotSupported.contains(id)) { return QString(); } return ConvertMap.value(id, id); } } // namespace Replaces PrepareReplaces(const QString &filename) { auto result = Replaces(filename); auto content = ([filename] { QFile f(filename); return f.open(QIODevice::ReadOnly) ? f.readAll() : QByteArray(); })(); if (content.isEmpty()) { logReplacesError(filename) << "Could not read data."; return result; } auto error = QJsonParseError(); auto document = QJsonDocument::fromJson(content, &error); if (error.error != QJsonParseError::NoError) { logReplacesError(filename) << "Could not parse data (" << int(error.error) << "): " << error.errorString().toStdString(); return result; } if (!document.isObject()) { logReplacesError(filename) << "Root object not found."; return result; } auto list = document.object(); for (auto i = list.constBegin(), e = list.constEnd(); i != e; ++i) { if (!(*i).isObject()) { logReplacesError(filename) << "Child object not found."; return Replaces(filename); } auto childKey = i.key(); auto child = (*i).toObject(); auto failed = false; auto getString = [filename, childKey, &child, &failed](const QString &key) { auto it = child.constFind(key); if (it == child.constEnd() || !(*it).isString()) { logReplacesError(filename) << "Child '" << childKey.toStdString() << "' field not found: " << key.toStdString(); failed = true; return QString(); } return (*it).toString(); }; auto idParts = getString("output").split('-'); auto name = getString("name"); auto replacement = getString("alpha_code"); auto aliases = getString("aliases").split('|'); if (aliases.size() == 1 && aliases[0].isEmpty()) { aliases.clear(); } if (failed) { return Replaces(filename); } if (!RegExpCode.match(replacement).hasMatch()) { logReplacesError(filename) << "Child '" << childKey.toStdString() << "' alpha_code invalid: " << replacement.toStdString(); return Replaces(filename); } for (auto &alias : aliases) { if (!RegExpCode.match(alias).hasMatch()) { logReplacesError(filename) << "Child '" << childKey.toStdString() << "' alias invalid: " << alias.toStdString(); return Replaces(filename); } } auto id = Id(); for (auto &idPart : idParts) { auto ok = true; auto utf32 = idPart.toInt(&ok, 0x10); if (!ok || !RegExpHex.match(idPart).hasMatch()) { logReplacesError(filename) << "Child '" << childKey.toStdString() << "' output part invalid: " << idPart.toStdString(); return Replaces(filename); } if (utf32 >= 0 && utf32 < 0x10000) { auto ch = QChar(ushort(utf32)); if (ch.isLowSurrogate() || ch.isHighSurrogate()) { logReplacesError(filename) << "Child '" << childKey.toStdString() << "' output part invalid: " << idPart.toStdString(); return Replaces(filename); } id.append(ch); } else if (utf32 >= 0x10000 && utf32 <= 0x10FFFF) { auto hi = ((utf32 - 0x10000) / 0x400) + 0xD800; auto lo = ((utf32 - 0x10000) % 0x400) + 0xDC00; id.append(QChar(ushort(hi))); id.append(QChar(ushort(lo))); } else { logReplacesError(filename) << "Child '" << childKey.toStdString() << "' output part invalid: " << idPart.toStdString(); return Replaces(filename); } } id = ConvertEmojiId(id, replacement); if (id.isEmpty()) { continue; } if (!AddReplacement(result, id, replacement, name)) { return Replaces(filename); } for (auto &alias : aliases) { if (!AddReplacement(result, id, alias, name)) { return Replaces(filename); } } } if (!AddReplacement(result, ComposeString({ 0xD83D, 0xDC4D }), ":like:", "thumbs up")) { return Replaces(filename); } if (!AddReplacement(result, ComposeString({ 0xD83D, 0xDC4E }), ":dislike:", "thumbs down")) { return Replaces(filename); } if (!AddReplacement(result, ComposeString({ 0xD83E, 0xDD14 }), ":hmm:", "thinking")) { return Replaces(filename); } return result; } bool CheckAndConvertReplaces(Replaces &replaces, const Data &data) { auto result = Replaces(replaces.filename); auto sorted = QMap<Id, Replace>(); auto findId = [&data](const Id &id) { return data.map.find(id) != data.map.cend(); }; auto findAndSort = [findId, &data, &sorted](Id id, const Replace &replace) { if (!findId(id)) { id.replace(QChar(0xFE0F), QString()); if (!findId(id)) { return false; } } auto it = data.map.find(id); id = data.list[it->second].id; if (data.list[it->second].postfixed) { id += QChar(kPostfix); } auto inserted = sorted.insertMulti(id, replace); inserted.value().id = id; return true; }; // Find all replaces in data.map, adjust id if necessary. // Store all replaces in sorted map to find them fast afterwards. auto maleModifier = ComposeString({ 0x200D, 0x2642, 0xFE0F }); auto femaleModifier = ComposeString({ 0x200D, 0x2640, 0xFE0F }); for (auto &replace : replaces.list) { if (findAndSort(replace.id, replace)) { continue; } if (replace.id.endsWith(maleModifier)) { auto defaultId = replace.id.mid(0, replace.id.size() - maleModifier.size()); if (findAndSort(defaultId, replace)) { continue; } } else if (replace.id.endsWith(femaleModifier)) { auto defaultId = replace.id.mid(0, replace.id.size() - femaleModifier.size()); if (findAndSort(defaultId, replace)) { continue; } } else if (findId(replace.id + maleModifier)) { if (findId(replace.id + femaleModifier)) { logReplacesError(replaces.filename) << "Replace '" << replace.replacement.toStdString() << "' ambiguous."; return false; } else { findAndSort(replace.id + maleModifier, replace); continue; } } else if (findAndSort(replace.id + femaleModifier, replace)) { continue; } logReplacesError(replaces.filename) << "Replace '" << replace.replacement.toStdString() << "' not found."; return false; } // Go through all categories and put all replaces in order of emoji in categories. result.list.reserve(replaces.list.size()); for (auto &category : data.categories) { for (auto index : category) { auto id = data.list[index].id; if (data.list[index].postfixed) { id += QChar(kPostfix); } for (auto it = sorted.find(id); it != sorted.cend(); sorted.erase(it), it = sorted.find(id)) { result.list.push_back(it.value()); } } } if (result.list.size() != replaces.list.size()) { logReplacesError(replaces.filename) << "Some were not found."; return false; } if (!sorted.isEmpty()) { logReplacesError(replaces.filename) << "Weird."; return false; } replaces = std::move(result); return true; } } // namespace emoji } // namespace codegen ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/emoji/replaces.h���������������������������������������0000664�0000000�0000000�00000001362�13262451251�0024103�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "codegen/common/logging.h" #include "codegen/emoji/data.h" #include <QtCore/QVector> namespace codegen { namespace emoji { struct Replace { Id id; QString replacement; QVector<QString> words; }; struct Replaces { Replaces(const QString &filename) : filename(filename) { } QString filename; QVector<Replace> list; }; Replaces PrepareReplaces(const QString &filename); bool CheckAndConvertReplaces(Replaces &replaces, const Data &data); } // namespace emoji } // namespace codegen ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/lang/��������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0021750�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/lang/generator.cpp�������������������������������������0000664�0000000�0000000�00000034234�13262451251�0024450�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "codegen/lang/generator.h" #include <memory> #include <functional> #include <QtCore/QDir> #include <QtCore/QSet> #include <QtGui/QImage> #include <QtGui/QPainter> namespace codegen { namespace lang { namespace { char hexChar(uchar ch) { if (ch < 10) { return '0' + ch; } else if (ch < 16) { return 'a' + (ch - 10); } return '0'; } char hexSecondChar(char ch) { return hexChar((*reinterpret_cast<uchar*>(&ch)) & 0x0F); } char hexFirstChar(char ch) { return hexChar((*reinterpret_cast<uchar*>(&ch)) >> 4); } QString stringToEncodedString(const QString &str) { QString result, lineBreak = "\\\n"; result.reserve(str.size() * 8); bool writingHexEscapedCharacters = false, startOnNewLine = false; int lastCutSize = 0; auto utf = str.toUtf8(); for (auto ch : utf) { if (result.size() - lastCutSize > 80) { startOnNewLine = true; result.append(lineBreak); lastCutSize = result.size(); } if (ch == '\n') { writingHexEscapedCharacters = false; result.append("\\n"); } else if (ch == '\t') { writingHexEscapedCharacters = false; result.append("\\t"); } else if (ch == '"' || ch == '\\') { writingHexEscapedCharacters = false; result.append('\\').append(ch); } else if (ch < 32 || static_cast<uchar>(ch) > 127) { writingHexEscapedCharacters = true; result.append("\\x").append(hexFirstChar(ch)).append(hexSecondChar(ch)); } else { if (writingHexEscapedCharacters) { writingHexEscapedCharacters = false; result.append("\"\""); } result.append(ch); } } return '"' + (startOnNewLine ? lineBreak : QString()) + result + '"'; } QString stringToEncodedString(const std::string &str) { return stringToEncodedString(QString::fromStdString(str)); } QString stringToBinaryArray(const std::string &str) { QStringList rows, chars; chars.reserve(13); rows.reserve(1 + (str.size() / 13)); for (uchar ch : str) { if (chars.size() > 12) { rows.push_back(chars.join(", ")); chars.clear(); } chars.push_back(QString("0x") + hexFirstChar(ch) + hexSecondChar(ch)); } if (!chars.isEmpty()) { rows.push_back(chars.join(", ")); } return QString("{") + ((rows.size() > 1) ? '\n' : ' ') + rows.join(",\n") + " }"; } } // namespace Generator::Generator(const LangPack &langpack, const QString &destBasePath, const common::ProjectInfo &project) : langpack_(langpack) , basePath_(destBasePath) , baseName_(QFileInfo(basePath_).baseName()) , project_(project) { } bool Generator::writeHeader() { header_ = std::make_unique<common::CppFile>(basePath_ + ".h", project_); header_->include("lang/lang_tag.h").newline().pushNamespace("Lang").stream() << "\ \n\ constexpr auto kTagsCount = " << langpack_.tags.size() << ";\n\ \n"; header_->popNamespace().newline(); auto index = 0; for (auto &tag : langpack_.tags) { header_->stream() << "enum lngtag_" << tag.tag << " { lt_" << tag.tag << " = " << index++ << " };\n"; } header_->stream() << "\ \n\ enum LangKey : int {\n"; for (auto &entry : langpack_.entries) { header_->stream() << "\t" << getFullKey(entry) << ",\n"; } header_->stream() << "\ \n\ kLangKeysCount,\n\ };\n\ \n\ QString lang(LangKey key);\n\ \n"; for (auto &entry : langpack_.entries) { auto isPlural = !entry.keyBase.isEmpty(); auto &key = entry.key; auto genericParams = QStringList(); auto params = QStringList(); auto applyTags = QStringList(); auto plural = QString(); auto nonPluralTagFound = false; for (auto &tagData : entry.tags) { auto &tag = tagData.tag; auto isPluralTag = isPlural && (tag == kPluralTag); genericParams.push_back("lngtag_" + tag + ", " + (isPluralTag ? "float64 " : "const ResultString &") + tag + "__val"); params.push_back("lngtag_" + tag + ", " + (isPluralTag ? "float64 " : "const QString &") + tag + "__val"); if (isPluralTag) { plural = "\tauto plural = Lang::Plural(" + key + ", " + kPluralTag + "__val);\n"; applyTags.push_back("\tresult = Lang::ReplaceTag<ResultString>::Call(std::move(result), lt_" + tag + ", Lang::StartReplacements<ResultString>::Call(std::move(plural.replacement)));\n"); } else { nonPluralTagFound = true; applyTags.push_back("\tresult = Lang::ReplaceTag<ResultString>::Call(std::move(result), lt_" + tag + ", " + tag + "__val);\n"); } } if (!entry.tags.empty() && (!isPlural || key == ComputePluralKey(entry.keyBase, 0))) { auto initialString = isPlural ? ("std::move(plural.string)") : ("lang(" + getFullKey(entry) + ")"); header_->stream() << "\ template <typename ResultString>\n\ inline ResultString " << (isPlural ? entry.keyBase : key) << "__generic(" << genericParams.join(QString(", ")) << ") {\n\ " << plural << "\ auto result = Lang::StartReplacements<ResultString>::Call(" << initialString << ");\n\ " << applyTags.join(QString()) << "\ return result;\n\ }\n\ constexpr auto " << (isPlural ? entry.keyBase : key) << " = &" << (isPlural ? entry.keyBase : key) << "__generic<QString>;\n\ \n"; } } header_->pushNamespace("Lang").stream() << "\ \n\ const char *GetKeyName(LangKey key);\n\ ushort GetTagIndex(QLatin1String tag);\n\ LangKey GetKeyIndex(QLatin1String key);\n\ bool IsTagReplaced(LangKey key, ushort tag);\n\ QString GetOriginalValue(LangKey key);\n\ \n"; return header_->finalize(); } bool Generator::writeSource() { source_ = std::make_unique<common::CppFile>(basePath_ + ".cpp", project_); source_->include("lang/lang_keys.h").pushNamespace("Lang").pushNamespace().stream() << "\ const char *KeyNames[kLangKeysCount] = {\n\ \n"; for (auto &entry : langpack_.entries) { source_->stream() << "\"" << entry.key << "\",\n"; } source_->stream() << "\ \n\ };\n\ \n\ QChar DefaultData[] = {"; auto count = 0; auto fulllength = 0; for (auto &entry : langpack_.entries) { for (auto ch : entry.value) { if (fulllength > 0) source_->stream() << ","; if (!count++) { source_->stream() << "\n"; } else { if (count == 12) { count = 0; } source_->stream() << " "; } source_->stream() << "0x" << QString::number(ch.unicode(), 16); ++fulllength; } } source_->stream() << " };\n\ \n\ int Offsets[] = {"; count = 0; auto offset = 0; auto writeOffset = [this, &count, &offset] { if (offset > 0) source_->stream() << ","; if (!count++) { source_->stream() << "\n"; } else { if (count == 12) { count = 0; } source_->stream() << " "; } source_->stream() << offset; }; for (auto &entry : langpack_.entries) { writeOffset(); offset += entry.value.size(); } writeOffset(); source_->stream() << " };\n"; source_->popNamespace().stream() << "\ \n\ const char *GetKeyName(LangKey key) {\n\ return (key < 0 || key >= kLangKeysCount) ? \"\" : KeyNames[key];\n\ }\n\ \n\ ushort GetTagIndex(QLatin1String tag) {\n\ auto size = tag.size();\n\ auto data = tag.data();\n"; auto tagsSet = std::set<QString, std::greater<QString>>(); for (auto &tag : langpack_.tags) { tagsSet.insert(tag.tag); } writeSetSearch(tagsSet, [](const QString &tag) { return "lt_" + tag; }, "kTagsCount"); source_->stream() << "\ }\n\ \n\ LangKey GetKeyIndex(QLatin1String key) {\n\ auto size = key.size();\n\ auto data = key.data();\n"; auto taggedKeys = std::map<QString, QString>(); auto keysSet = std::set<QString, std::greater<QString>>(); for (auto &entry : langpack_.entries) { if (!entry.keyBase.isEmpty()) { for (auto i = 0; i != kPluralPartCount; ++i) { auto keyName = entry.keyBase + '#' + kPluralParts[i]; taggedKeys.emplace(keyName, ComputePluralKey(entry.keyBase, i)); keysSet.insert(keyName); } } else { auto full = getFullKey(entry); if (full != entry.key) { taggedKeys.emplace(entry.key, full); } keysSet.insert(entry.key); } } writeSetSearch(keysSet, [&taggedKeys](const QString &key) { auto it = taggedKeys.find(key); return (it != taggedKeys.end()) ? it->second : key; }, "kLangKeysCount"); source_->stream() << "\ }\n\ \n\ bool IsTagReplaced(LangKey key, ushort tag) {\n\ switch (key) {\n"; auto lastWrittenPluralEntry = QString(); for (auto &entry : langpack_.entries) { if (entry.tags.empty()) { continue; } if (!entry.keyBase.isEmpty()) { if (entry.keyBase == lastWrittenPluralEntry) { continue; } lastWrittenPluralEntry = entry.keyBase; for (auto i = 0; i != kPluralPartCount; ++i) { source_->stream() << "\ case " << ComputePluralKey(entry.keyBase, i) << ":" << ((i + 1 == kPluralPartCount) ? " {" : "") << "\n"; } } else { source_->stream() << "\ case " << getFullKey(entry) << ": {\n"; } source_->stream() << "\ switch (tag) {\n"; for (auto &tag : entry.tags) { source_->stream() << "\ case lt_" << tag.tag << ":\n"; } source_->stream() << "\ return true;\n\ }\n\ } break;\n"; } source_->stream() << "\ }\ \n\ return false;\n\ }\n\ \n\ QString GetOriginalValue(LangKey key) {\n\ Expects(key >= 0 && key < kLangKeysCount);\n\ auto offset = Offsets[key];\n\ return QString::fromRawData(DefaultData + offset, Offsets[key + 1] - offset);\n\ }\n\ \n"; return source_->finalize(); } template <typename ComputeResult> void Generator::writeSetSearch(const std::set<QString, std::greater<QString>> &set, ComputeResult computeResult, const QString &invalidResult) { auto tabs = [](int size) { return QString(size, '\t'); }; enum class UsedCheckType { Switch, If, UpcomingIf, }; auto checkTypes = QVector<UsedCheckType>(); auto checkLengthHistory = QVector<int>(1, 0); auto chars = QString(); auto tabsUsed = 1; // Returns true if at least one check was finished. auto finishChecksTillKey = [this, &chars, &checkTypes, &checkLengthHistory, &tabsUsed, tabs](const QString &key) { auto result = false; while (!chars.isEmpty() && key.midRef(0, chars.size()) != chars) { result = true; auto wasType = checkTypes.back(); chars.resize(chars.size() - 1); checkTypes.pop_back(); checkLengthHistory.pop_back(); if (wasType == UsedCheckType::Switch || wasType == UsedCheckType::If) { --tabsUsed; if (wasType == UsedCheckType::Switch) { source_->stream() << tabs(tabsUsed) << "break;\n"; } if ((!chars.isEmpty() && key.midRef(0, chars.size()) != chars) || key == chars) { source_->stream() << tabs(tabsUsed) << "}\n"; } } } return result; }; // Check if we can use "if" for a check on "charIndex" in "it" (otherwise only "switch") auto canUseIfForCheck = [](auto it, auto end, int charIndex) { auto key = *it; auto i = it; auto keyStart = key.mid(0, charIndex); for (++i; i != end; ++i) { auto nextKey = *i; if (nextKey.mid(0, charIndex) != keyStart) { return true; } else if (nextKey.size() > charIndex && nextKey[charIndex] != key[charIndex]) { return false; } } return true; }; auto countMinimalLength = [](auto it, auto end, int charIndex) { auto key = *it; auto i = it; auto keyStart = key.mid(0, charIndex); auto result = key.size(); for (++i; i != end; ++i) { auto nextKey = *i; if (nextKey.mid(0, charIndex) != keyStart) { break; } else if (nextKey.size() > charIndex && result > nextKey.size()) { result = nextKey.size(); } } return result; }; for (auto i = set.begin(), e = set.end(); i != e; ++i) { // If we use just "auto" here and "name" becomes mutable, // the operator[] will return QCharRef instead of QChar, // and "auto ch = name[index]" will behave like "auto &ch =", // if you assign something to "ch" after that you'll change "name" (!) const auto name = *i; auto weContinueOldSwitch = finishChecksTillKey(name); while (chars.size() != name.size()) { auto checking = chars.size(); auto partialKey = name.mid(0, checking); auto keyChar = name[checking]; auto usedIfForCheckCount = 0; auto minimalLengthCheck = countMinimalLength(i, e, checking); for (; checking + usedIfForCheckCount != name.size(); ++usedIfForCheckCount) { if (!canUseIfForCheck(i, e, checking + usedIfForCheckCount) || countMinimalLength(i, e, checking + usedIfForCheckCount) != minimalLengthCheck) { break; } } auto usedIfForCheck = !weContinueOldSwitch && (usedIfForCheckCount > 0); auto checkLengthCondition = QString(); if (weContinueOldSwitch) { weContinueOldSwitch = false; } else { checkLengthCondition = (minimalLengthCheck > checkLengthHistory.back()) ? ("size >= " + QString::number(minimalLengthCheck)) : QString(); if (!usedIfForCheck) { source_->stream() << tabs(tabsUsed) << (checkLengthCondition.isEmpty() ? QString() : ("if (" + checkLengthCondition + ") ")) << "switch (data[" << checking << "]) {\n"; } } if (usedIfForCheck) { auto conditions = QStringList(); if (usedIfForCheckCount > 1) { conditions.push_back("!memcmp(data + " + QString::number(checking) + ", \"" + name.mid(checking, usedIfForCheckCount) + "\", " + QString::number(usedIfForCheckCount) + ")"); } else { conditions.push_back("data[" + QString::number(checking) + "] == '" + keyChar + "'"); } if (!checkLengthCondition.isEmpty()) { conditions.push_front(checkLengthCondition); } source_->stream() << tabs(tabsUsed) << "if (" << conditions.join(" && ") << ") {\n"; checkTypes.push_back(UsedCheckType::If); for (auto i = 1; i != usedIfForCheckCount; ++i) { checkTypes.push_back(UsedCheckType::UpcomingIf); chars.push_back(keyChar); checkLengthHistory.push_back(qMax(minimalLengthCheck, checkLengthHistory.back())); keyChar = name[checking + i]; } } else { source_->stream() << tabs(tabsUsed) << "case '" << keyChar << "':\n"; checkTypes.push_back(UsedCheckType::Switch); } ++tabsUsed; chars.push_back(keyChar); checkLengthHistory.push_back(qMax(minimalLengthCheck, checkLengthHistory.back())); } source_->stream() << tabs(tabsUsed) << "return (size == " << chars.size() << ") ? " << computeResult(name) << " : " << invalidResult << ";\n"; } finishChecksTillKey(QString()); source_->stream() << "\ \n\ return " << invalidResult << ";\n"; } QString Generator::getFullKey(const LangPack::Entry &entry) { if (!entry.keyBase.isEmpty() || entry.tags.empty()) { return entry.key; } return entry.key + "__tagged"; } } // namespace lang } // namespace codegen ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/lang/generator.h���������������������������������������0000664�0000000�0000000�00000002245�13262451251�0024112�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <memory> #include <map> #include <set> #include <functional> #include <QtCore/QString> #include <QtCore/QSet> #include "codegen/common/cpp_file.h" #include "codegen/lang/parsed_file.h" namespace codegen { namespace lang { class Generator { public: Generator(const LangPack &langpack, const QString &destBasePath, const common::ProjectInfo &project); Generator(const Generator &other) = delete; Generator &operator=(const Generator &other) = delete; bool writeHeader(); bool writeSource(); private: QString getFullKey(const LangPack::Entry &entry); template <typename ComputeResult> void writeSetSearch(const std::set<QString, std::greater<QString>> &set, ComputeResult computeResult, const QString &invalidResult); const LangPack &langpack_; QString basePath_, baseName_; const common::ProjectInfo &project_; std::unique_ptr<common::CppFile> source_, header_; }; } // namespace lang } // namespace codegen �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/lang/main.cpp������������������������������������������0000664�0000000�0000000�00000001122�13262451251�0023374�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include <QtCore/QCoreApplication> #include "codegen/lang/options.h" #include "codegen/lang/processor.h" int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); auto options = codegen::lang::parseOptions(); if (options.inputPath.isEmpty()) { return -1; } codegen::lang::Processor processor(options); return processor.launch(); } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/lang/options.cpp���������������������������������������0000664�0000000�0000000�00000003617�13262451251�0024156�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "codegen/lang/options.h" #include <ostream> #include <QtCore/QCoreApplication> #include <QtCore/QDir> #include "codegen/common/logging.h" namespace codegen { namespace lang { namespace { constexpr int kErrorOutputPathExpected = 902; constexpr int kErrorInputPathExpected = 903; constexpr int kErrorSingleInputPathExpected = 904; constexpr int kErrorWorkingPathExpected = 905; } // namespace using common::logError; Options parseOptions() { Options result; auto args = QCoreApplication::instance()->arguments(); for (int i = 1, count = args.size(); i < count; ++i) { // skip first auto &arg = args.at(i); // Output path if (arg == "-o") { if (++i == count) { logError(kErrorOutputPathExpected, "Command Line") << "output path expected after -o"; return Options(); } else { result.outputPath = args.at(i); } } else if (arg.startsWith("-o")) { result.outputPath = arg.mid(2); // Working path } else if (arg == "-w") { if (++i == count) { logError(kErrorWorkingPathExpected, "Command Line") << "working path expected after -w"; return Options(); } else { common::logSetWorkingPath(args.at(i)); } } else if (arg.startsWith("-w")) { common::logSetWorkingPath(arg.mid(2)); // Input path } else { if (result.inputPath.isEmpty()) { result.inputPath = arg; } else { logError(kErrorSingleInputPathExpected, "Command Line") << "only one input path expected"; return Options(); } } } if (result.inputPath.isEmpty()) { logError(kErrorInputPathExpected, "Command Line") << "input path expected"; return Options(); } return result; } } // namespace lang } // namespace codegen �����������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/lang/options.h�����������������������������������������0000664�0000000�0000000�00000001034�13262451251�0023612�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <QtCore/QString> #include <QtCore/QStringList> namespace codegen { namespace lang { struct Options { QString outputPath = "."; QString inputPath; }; // Parsing failed if inputPath is empty in the result. Options parseOptions(); } // namespace lang } // namespace codegen ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/lang/parsed_file.cpp�����������������������������������0000664�0000000�0000000�00000021745�13262451251�0024742�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "codegen/lang/parsed_file.h" #include <iostream> #include <QtCore/QMap> #include <QtCore/QDir> #include <QtCore/QRegularExpression> #include "codegen/common/basic_tokenized_file.h" #include "codegen/common/logging.h" namespace codegen { namespace lang { namespace { using BasicToken = codegen::common::BasicTokenizedFile::Token; using BasicType = BasicToken::Type; constexpr int kErrorBadString = 806; bool ValidateAnsiString(const QString &value) { for (auto ch : value) { if (ch.unicode() > 127) { return false; } } return true; } bool ValidateKey(const QString &key) { static const auto validator = QRegularExpression("^[a-z0-9_.-]+(#(one|other))?$", QRegularExpression::CaseInsensitiveOption); if (!validator.match(key).hasMatch()) { return false; } if (key.indexOf("__") >= 0) { return false; } return true; } bool ValidateTag(const QString &tag) { static const auto validator = QRegularExpression("^[a-z0-9_]+$", QRegularExpression::CaseInsensitiveOption); if (!validator.match(tag).hasMatch()) { return false; } if (tag.indexOf("__") >= 0) { return false; } return true; } QString PrepareCommandString(int index) { static const QChar TextCommand(0x0010); static const QChar TextCommandLangTag(0x0020); auto result = QString(4, TextCommand); result[1] = TextCommandLangTag; result[2] = QChar(0x0020 + ushort(index)); return result; } } // namespace const std::array<QString, kPluralPartCount> kPluralParts = { { "zero", "one", "two", "few", "many", "other", } }; const QString kPluralTag = "count"; QString ComputePluralKey(const QString &base, int index) { return base + "__plural" + QString::number(index); } ParsedFile::ParsedFile(const Options &options) : filePath_(options.inputPath) , file_(filePath_) , options_(options) { } bool ParsedFile::read() { if (!file_.read()) { return false; } do { if (auto keyToken = file_.getToken(BasicType::String)) { if (ValidateKey(keyToken.value)) { if (auto equals = file_.getToken(BasicType::Equals)) { if (auto valueToken = file_.getToken(BasicType::String)) { assertNextToken(BasicType::Semicolon); addEntity(keyToken.value, valueToken.value); continue; } else { logErrorUnexpectedToken() << "string value for '" << keyToken.value.toStdString() << "' key"; } } else { logErrorUnexpectedToken() << "'=' for '" << keyToken.value.toStdString() << "' key"; } } else { logErrorUnexpectedToken() << "string key name (/^[a-z0-9_.-]+(#(one|other))?$/i)"; } } if (file_.atEnd()) { break; } logErrorUnexpectedToken() << "ansi string key name"; } while (!failed()); fillPluralTags(); return !failed(); } void ParsedFile::fillPluralTags() { auto count = result_.entries.size(); for (auto i = 0; i != count;) { auto &baseEntry = result_.entries[i]; if (baseEntry.keyBase.isEmpty()) { ++i; continue; } logAssert(i + kPluralPartCount < count); // Accumulate all tags from all plural variants. auto tags = std::vector<LangPack::Tag>(); for (auto j = i; j != i + kPluralPartCount; ++j) { if (tags.empty()) { tags = result_.entries[j].tags; } else { for (auto &tag : result_.entries[j].tags) { if (std::find(tags.begin(), tags.end(), tag) == tags.end()) { tags.push_back(tag); } } } } logAssert(!tags.empty()); logAssert(tags.front().tag == kPluralTag); // Set this tags list to all plural variants. for (auto j = i; j != i + kPluralPartCount; ++j) { result_.entries[j].tags = tags; } i += kPluralPartCount; } } BasicToken ParsedFile::assertNextToken(BasicToken::Type type) { auto result = file_.getToken(type); if (!result) { logErrorUnexpectedToken() << type; } return result; } common::LogStream ParsedFile::logErrorBadString() { return logError(kErrorBadString); } QString ParsedFile::extractTagsData(const QString &value, LangPack *to) { auto tagStart = value.indexOf('{'); if (tagStart < 0) { return value; } auto tagEnd = 0; auto finalValue = QString(); finalValue.reserve(value.size() * 2); while (tagStart >= 0) { if (tagStart > tagEnd) { finalValue.append(value.midRef(tagEnd, tagStart - tagEnd)); } ++tagStart; tagEnd = value.indexOf('}', tagStart); if (tagEnd < 0) { logErrorBadString() << "unexpected end of value, end of tag expected."; return value; } finalValue.append(extractTagData(value.mid(tagStart, tagEnd - tagStart), to)); ++tagEnd; tagStart = value.indexOf('{', tagEnd); } if (tagEnd < value.size()) { finalValue.append(value.midRef(tagEnd)); } return finalValue; } QString ParsedFile::extractTagData(const QString &tagText, LangPack *to) { auto numericPart = tagText.indexOf(':'); auto tag = (numericPart > 0) ? tagText.mid(0, numericPart) : tagText; if (!ValidateTag(tag)) { logErrorBadString() << "bad tag characters: '" << tagText.toStdString() << "'"; return QString(); } for (auto &previousTag : to->tags) { if (previousTag.tag == tag) { logErrorBadString() << "duplicate found for tag '" << tagText.toStdString() << "'"; return QString(); } } auto index = 0; auto tagIndex = result_.tags.size(); for (auto &alreadyTag : result_.tags) { if (alreadyTag.tag == tag) { tagIndex = index; break; } ++index; } if (tagIndex == result_.tags.size()) { result_.tags.push_back({ tag }); } if (numericPart > 0) { auto numericParts = tagText.mid(numericPart + 1).split('|'); if (numericParts.size() != 3) { logErrorBadString() << "bad option count for plural key part in tag: '" << tagText.toStdString() << "'"; return QString(); } auto index = 0; for (auto &part : numericParts) { auto numericPartEntry = LangPack::Entry(); numericPartEntry.key = tag + QString::number(index++); if (part.indexOf('#') != part.lastIndexOf('#')) { logErrorBadString() << "bad option for plural key part in tag: '" << tagText.toStdString() << "', too many '#'."; return QString(); } numericPartEntry.value = part.replace('#', PrepareCommandString(tagIndex)); to->entries.push_back(numericPartEntry); } } to->tags.push_back({ tag }); return PrepareCommandString(tagIndex); } void ParsedFile::addEntity(QString key, const QString &value) { auto pluralPartOffset = key.indexOf('#'); auto pluralIndex = -1; if (pluralPartOffset >= 0) { auto pluralPart = key.mid(pluralPartOffset + 1); pluralIndex = std::find(kPluralParts.begin(), kPluralParts.end(), pluralPart) - kPluralParts.begin(); if (pluralIndex < 0 || pluralIndex >= kPluralParts.size()) { logError(kErrorBadString) << "bad plural part for key '" << key.toStdString() << "': '" << pluralPart.toStdString() << "'"; return; } key = key.mid(0, pluralPartOffset); } auto checkKey = [this](const QString &key) { for (auto &entry : result_.entries) { if (entry.key == key) { if (entry.keyBase.isEmpty() || !entry.tags.empty()) { // Empty tags in plural entry means it was not encountered yet. logError(kErrorBadString) << "duplicate found for key '" << key.toStdString() << "'"; return false; } } } return true; }; if (!checkKey(key)) { return; } auto tagsData = LangPack(); auto entry = LangPack::Entry(); entry.key = key; entry.value = extractTagsData(value, &tagsData); entry.tags = tagsData.tags; if (pluralIndex >= 0) { logAssert(tagsData.entries.empty()); entry.keyBase = entry.key; entry.key = ComputePluralKey(entry.keyBase, pluralIndex); if (!checkKey(entry.key)) { return; } auto baseIndex = -1; auto alreadyCount = result_.entries.size(); for (auto i = 0; i != alreadyCount; ++i) { if (result_.entries[i].keyBase == entry.keyBase) { // This is not the first appearance of this plural key. baseIndex = i; break; } } if (baseIndex < 0) { baseIndex = result_.entries.size(); for (auto i = 0; i != kPluralPartCount; ++i) { auto addingEntry = LangPack::Entry(); addingEntry.keyBase = entry.keyBase; addingEntry.key = ComputePluralKey(entry.keyBase, i); result_.entries.push_back(addingEntry); } } auto entryIndex = baseIndex + pluralIndex; logAssert(entryIndex < result_.entries.size()); auto &realEntry = result_.entries[entryIndex]; logAssert(realEntry.key == entry.key); realEntry.value = entry.value; // Add all new tags to the existing ones. realEntry.tags = std::vector<LangPack::Tag>(1, LangPack::Tag { kPluralTag }); for (auto &tag : entry.tags) { if (std::find(realEntry.tags.begin(), realEntry.tags.end(), tag) == realEntry.tags.end()) { realEntry.tags.push_back(tag); } } } else { result_.entries.push_back(entry); for (auto &pluralEntry : tagsData.entries) { auto taggedEntry = LangPack::Entry(); taggedEntry.key = key + "__" + pluralEntry.key; taggedEntry.value = pluralEntry.value; result_.entries.push_back(taggedEntry); } } } } // namespace lang } // namespace codegen ���������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/lang/parsed_file.h�������������������������������������0000664�0000000�0000000�00000004712�13262451251�0024402�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <memory> #include <string> #include <array> #include <functional> #include <QImage> #include "codegen/common/basic_tokenized_file.h" #include "codegen/lang/options.h" namespace codegen { namespace lang { constexpr auto kPluralPartCount = 6; extern const std::array<QString, kPluralPartCount> kPluralParts; extern const QString kPluralTag; QString ComputePluralKey(const QString &base, int index); struct LangPack { struct Tag { QString tag; }; struct Entry { QString key; QString value; QString keyBase; // Empty for not plural entries. std::vector<Tag> tags; }; std::vector<Entry> entries; std::vector<Tag> tags; }; inline bool operator==(const LangPack::Tag &a, const LangPack::Tag &b) { return a.tag == b.tag; } inline bool operator!=(const LangPack::Tag &a, const LangPack::Tag &b) { return !(a == b); } // Parses an input file to the internal struct. class ParsedFile { public: explicit ParsedFile(const Options &options); ParsedFile(const ParsedFile &other) = delete; ParsedFile &operator=(const ParsedFile &other) = delete; bool read(); LangPack getResult() { return result_; } private: bool failed() const { return failed_ || file_.failed(); } // Log error to std::cerr with 'code' at the current position in file. common::LogStream logError(int code) { failed_ = true; return file_.logError(code); } common::LogStream logErrorUnexpectedToken() { failed_ = true; return file_.logErrorUnexpectedToken(); } common::LogStream logErrorBadString(); common::LogStream logAssert(bool assertion) { if (!assertion) { return logError(common::kErrorInternal) << "internal - "; } return common::LogStream(common::LogStream::Null); } // Read next token and fire unexpected token error if it is not of "type". using BasicToken = common::BasicTokenizedFile::Token; BasicToken assertNextToken(BasicToken::Type type); void addEntity(QString key, const QString &value); QString extractTagsData(const QString &value, LangPack *to); QString extractTagData(const QString &tag, LangPack *to); void fillPluralTags(); QString filePath_; common::BasicTokenizedFile file_; Options options_; bool failed_ = false; LangPack result_; }; } // namespace lang } // namespace codegen ������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/lang/processor.cpp�������������������������������������0000664�0000000�0000000�00000003013�13262451251�0024470�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "codegen/lang/processor.h" #include <QtCore/QDir> #include <QtCore/QFileInfo> #include "codegen/common/cpp_file.h" #include "codegen/lang/parsed_file.h" #include "codegen/lang/generator.h" namespace codegen { namespace lang { namespace { constexpr int kErrorCantWritePath = 821; } // namespace Processor::Processor(const Options &options) : parser_(std::make_unique<ParsedFile>(options)) , options_(options) { } int Processor::launch() { if (!parser_->read()) { return -1; } if (!write(parser_->getResult())) { return -1; } return 0; } bool Processor::write(const LangPack &langpack) const { bool forceReGenerate = false; QDir dir(options_.outputPath); if (!dir.mkpath(".")) { common::logError(kErrorCantWritePath, "Command Line") << "can not open path for writing: " << dir.absolutePath().toStdString(); return false; } QFileInfo srcFile(options_.inputPath); QString dstFilePath = dir.absolutePath() + "/lang_auto"; common::ProjectInfo project = { "codegen_style", srcFile.fileName(), forceReGenerate }; Generator generator(langpack, dstFilePath, project); if (!generator.writeHeader()) { return false; } if (!generator.writeSource()) { return false; } return true; } Processor::~Processor() = default; } // namespace lang } // namespace codegen ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/lang/processor.h���������������������������������������0000664�0000000�0000000�00000001541�13262451251�0024141�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <memory> #include <QtCore/QString> #include "codegen/lang/options.h" namespace codegen { namespace lang { class ParsedFile; struct LangPack; // Walks through a file, parses it and generates the output. class Processor { public: explicit Processor(const Options &options); Processor(const Processor &other) = delete; Processor &operator=(const Processor &other) = delete; // Returns 0 on success. int launch(); ~Processor(); private: bool write(const LangPack &langpack) const; std::unique_ptr<ParsedFile> parser_; const Options &options_; }; } // namespace lang } // namespace codegen ���������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/numbers/�����������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0022502�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/numbers/generator.cpp����������������������������������0000664�0000000�0000000�00000006324�13262451251�0025201�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "codegen/numbers/generator.h" #include <QtCore/QDir> #include <QtCore/QSet> #include <functional> namespace codegen { namespace numbers { namespace { } // namespace Generator::Generator(const Rules &rules, const QString &destBasePath, const common::ProjectInfo &project) : rules_(rules) , basePath_(destBasePath) , project_(project) { } bool Generator::writeHeader() { header_ = std::make_unique<common::CppFile>(basePath_ + ".h", project_); header_->stream() << "QVector<int> phoneNumberParse(const QString &number);\n"; return header_->finalize(); } bool Generator::writeSource() { source_ = std::make_unique<common::CppFile>(basePath_ + ".cpp", project_); source_->stream() << "\ QVector<int> phoneNumberParse(const QString &number) {\n\ QVector<int> result;\n\ \n\ int32 len = number.size();\n\ if (len > 0) switch (number.at(0).unicode()) {\n"; QString already; for (auto i = rules_.data.cend(), e = rules_.data.cbegin(); i != e;) { --i; QString k = i.key(); bool onlyLastChanged = true; while (!already.isEmpty() && (already.size() > k.size() || !already.endsWith(k.at(already.size() - 1)))) { if (!onlyLastChanged) { source_->stream() << QString("\t").repeated(1 + already.size()) << "}\n"; source_->stream() << QString("\t").repeated(already.size()) << "break;\n"; } already = already.mid(0, already.size() - 1); onlyLastChanged = false; } if (already == k) { source_->stream() << QString("\t").repeated(1 + already.size()) << "}\n"; } else { bool onlyFirstCheck = true; while (already.size() < k.size()) { if (!onlyFirstCheck) source_->stream() << QString("\t").repeated(1 + already.size()) << "if (len > " << already.size() << ") switch (number.at(" << already.size() << ").unicode()) {\n"; source_->stream() << QString("\t").repeated(1 + already.size()) << "case '" << k.at(already.size()).toLatin1() << "':\n"; already.push_back(k.at(already.size())); onlyFirstCheck = false; } } if (i.value().isEmpty()) { source_->stream() << QString("\t").repeated(1 + already.size()) << "return QVector<int>(1, " << k.size() << ");\n"; } else { source_->stream() << QString("\t").repeated(1 + already.size()) << "result.reserve(" << (i.value().size() + 1) << ");\n"; source_->stream() << QString("\t").repeated(1 + already.size()) << "result.push_back(" << k.size() << ");\n"; for (int j = 0, l = i.value().size(); j < l; ++j) { source_->stream() << QString("\t").repeated(1 + already.size()) << "result.push_back(" << i.value().at(j) << ");\n"; } source_->stream() << QString("\t").repeated(1 + already.size()) << "return result;\n"; } } bool onlyLastChanged = true; while (!already.isEmpty()) { if (!onlyLastChanged) { source_->stream() << QString("\t").repeated(1 + already.size()) << "}\n"; } already = already.mid(0, already.size() - 1); onlyLastChanged = false; } source_->stream() << "\ }\n\ \n\ return result;\n\ }\n"; return source_->finalize(); } } // namespace numbers } // namespace codegen ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/numbers/generator.h������������������������������������0000664�0000000�0000000�00000001605�13262451251�0024643�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <memory> #include <QtCore/QString> #include <QtCore/QSet> #include "codegen/common/cpp_file.h" #include "codegen/numbers/parsed_file.h" namespace codegen { namespace numbers { class Generator { public: Generator(const Rules &rules, const QString &destBasePath, const common::ProjectInfo &project); Generator(const Generator &other) = delete; Generator &operator=(const Generator &other) = delete; bool writeHeader(); bool writeSource(); private: const Rules &rules_; QString basePath_; const common::ProjectInfo &project_; std::unique_ptr<common::CppFile> source_, header_; }; } // namespace numbers } // namespace codegen ���������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/numbers/main.cpp���������������������������������������0000664�0000000�0000000�00000001136�13262451251�0024133�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include <QtCore/QCoreApplication> #include "codegen/numbers/options.h" #include "codegen/numbers/processor.h" int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); auto options = codegen::numbers::parseOptions(); if (options.inputPath.isEmpty()) { return -1; } codegen::numbers::Processor processor(options); return processor.launch(); } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/numbers/options.cpp������������������������������������0000664�0000000�0000000�00000003602�13262451251�0024702�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "codegen/numbers/options.h" #include <ostream> #include <QtCore/QCoreApplication> #include "codegen/common/logging.h" namespace codegen { namespace numbers { namespace { constexpr int kErrorOutputPathExpected = 902; constexpr int kErrorInputPathExpected = 903; constexpr int kErrorSingleInputPathExpected = 904; constexpr int kErrorWorkingPathExpected = 905; } // namespace using common::logError; Options parseOptions() { Options result; auto args = QCoreApplication::instance()->arguments(); for (auto i = 1, count = args.size(); i < count; ++i) { // skip first auto &arg = args.at(i); // Output path if (arg == "-o") { if (++i == count) { logError(kErrorOutputPathExpected, "Command Line") << "output path expected after -o"; return Options(); } else { result.outputPath = args.at(i); } } else if (arg.startsWith("-o")) { result.outputPath = arg.mid(2); // Working path } else if (arg == "-w") { if (++i == count) { logError(kErrorWorkingPathExpected, "Command Line") << "working path expected after -w"; return Options(); } else { common::logSetWorkingPath(args.at(i)); } } else if (arg.startsWith("-w")) { common::logSetWorkingPath(arg.mid(2)); // Input path } else { if (result.inputPath.isEmpty()) { result.inputPath = arg; } else { logError(kErrorSingleInputPathExpected, "Command Line") << "only one input path expected"; return Options(); } } } if (result.inputPath.isEmpty()) { logError(kErrorInputPathExpected, "Command Line") << "input path expected"; return Options(); } return result; } } // namespace numbers } // namespace codegen ������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/numbers/options.h��������������������������������������0000664�0000000�0000000�00000001042�13262451251�0024343�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <QtCore/QString> #include <QtCore/QStringList> namespace codegen { namespace numbers { struct Options { QString outputPath = "."; QString inputPath; }; // Parsing failed if inputPath is empty in the result. Options parseOptions(); } // namespace numbers } // namespace codegen ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/numbers/parsed_file.cpp��������������������������������0000664�0000000�0000000�00000007037�13262451251�0025472�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "codegen/numbers/parsed_file.h" #include <iostream> #include <QtCore/QMap> #include <QtCore/QDir> #include <QtCore/QRegularExpression> #include "codegen/common/basic_tokenized_file.h" #include "codegen/common/logging.h" #include "codegen/common/clean_file_reader.h" #include "codegen/common/checked_utf8_string.h" using BasicToken = codegen::common::BasicTokenizedFile::Token; using BasicType = BasicToken::Type; namespace codegen { namespace numbers { namespace { QByteArray replaceStrings(const QString &filepath) { common::CleanFileReader reader(filepath); if (!reader.read()) { return QByteArray(); } common::CheckedUtf8String string(reader.currentPtr(), reader.charsLeft()); if (!string.isValid()) { return QByteArray(); } QStringList lines = string.toString().split('\n'); for (auto &line : lines) { auto match = QRegularExpression("^(\\d+;[A-Z]+;)([^;]+)(;.*)?$").match(line); if (match.hasMatch()) { line = match.captured(1) + '"' + match.captured(2) + '"' + match.captured(3); } } return lines.join('\n').toUtf8(); } } // namespace ParsedFile::ParsedFile(const Options &options) : content_(replaceStrings(options.inputPath)) , file_(content_, options.inputPath) , options_(options) { } bool ParsedFile::read() { if (content_.isEmpty() || !file_.read()) { return false; } auto filepath = QFileInfo(options_.inputPath).absoluteFilePath(); do { if (auto code = file_.getToken(BasicType::Int)) { if (!file_.getToken(BasicType::Semicolon)) { logErrorUnexpectedToken() << "';'"; return false; } if (!file_.getToken(BasicType::Name)) { logErrorUnexpectedToken() << "country code"; return false; } if (!file_.getToken(BasicType::Semicolon)) { logErrorUnexpectedToken() << "';'"; return false; } if (!file_.getToken(BasicType::String)) { logErrorUnexpectedToken() << "country name"; return false; } if (file_.getToken(BasicType::Semicolon)) { if (auto firstPart = file_.getToken(BasicType::Int)) { if (firstPart.original.toByteArray() != code.original.toByteArray()) { file_.putBack(); result_.data.insert(code.original.toStringUnchecked(), Rule()); continue; } Rule rule; while (auto part = file_.getToken(BasicType::Name)) { rule.push_back(part.original.size()); } result_.data.insert(code.original.toStringUnchecked(), rule); if (rule.isEmpty()) { logErrorUnexpectedToken() << "bad phone pattern"; return false; } if (!file_.getToken(BasicType::Semicolon)) { logErrorUnexpectedToken() << "';'"; return false; } if (!file_.getToken(BasicType::Int)) { logErrorUnexpectedToken() << "country phone len"; return false; } file_.getToken(BasicType::Semicolon); continue; } else { logErrorUnexpectedToken() << "country phone pattern"; return false; } } else if (file_.getToken(BasicType::Int)) { file_.putBack(); result_.data.insert(code.original.toStringUnchecked(), Rule()); continue; } else { logErrorUnexpectedToken() << "country phone pattern"; return false; } } if (file_.atEnd()) { break; } logErrorUnexpectedToken() << "numbers rule"; } while (!failed()); if (failed()) { result_.data.clear(); } return !failed(); } } // namespace numbers } // namespace codegen �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/numbers/parsed_file.h����������������������������������0000664�0000000�0000000�00000002412�13262451251�0025127�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <memory> #include <string> #include "codegen/common/basic_tokenized_file.h" #include "codegen/numbers/options.h" namespace codegen { namespace numbers { using Rule = QVector<int>; struct Rules { QMap<QString, Rule> data; }; // Parses an input file to the internal struct. class ParsedFile { public: explicit ParsedFile(const Options &options); ParsedFile(const ParsedFile &other) = delete; ParsedFile &operator=(const ParsedFile &other) = delete; bool read(); Rules getResult() { return result_; } private: bool failed() const { return failed_ || file_.failed(); } // Log error to std::cerr with 'code' at the current position in file. common::LogStream logError(int code) { failed_ = true; return file_.logError(code); } common::LogStream logErrorUnexpectedToken() { failed_ = true; return file_.logErrorUnexpectedToken(); } QByteArray content_; common::BasicTokenizedFile file_; Options options_; bool failed_ = false; Rules result_; }; } // namespace numbers } // namespace codegen ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/numbers/processor.cpp����������������������������������0000664�0000000�0000000�00000003022�13262451251�0025222�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "codegen/numbers/processor.h" #include <QtCore/QDir> #include <QtCore/QFileInfo> #include "codegen/common/cpp_file.h" #include "codegen/numbers/parsed_file.h" #include "codegen/numbers/generator.h" namespace codegen { namespace numbers { namespace { constexpr int kErrorCantWritePath = 851; } // namespace Processor::Processor(const Options &options) : parser_(std::make_unique<ParsedFile>(options)) , options_(options) { } int Processor::launch() { if (!parser_->read()) { return -1; } auto result = parser_->getResult(); if (!write(result)) { return -1; } return 0; } bool Processor::write(const Rules &rules) const { QDir dir(options_.outputPath); if (!dir.mkpath(".")) { common::logError(kErrorCantWritePath, "Command Line") << "can not open path for writing: " << dir.absolutePath().toStdString(); return false; } QFileInfo srcFile(options_.inputPath); QString dstFilePath = dir.absolutePath() + "/numbers"; common::ProjectInfo project = { "codegen_style", srcFile.fileName(), false, // forceReGenerate }; Generator generator(rules, dstFilePath, project); if (!generator.writeHeader()) { return false; } if (!generator.writeSource()) { return false; } return true; } Processor::~Processor() = default; } // namespace numbers } // namespace codegen ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/numbers/processor.h������������������������������������0000664�0000000�0000000�00000001547�13262451251�0024701�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <memory> #include <QtCore/QString> #include "codegen/numbers/options.h" namespace codegen { namespace numbers { class ParsedFile; struct Rules; // Walks through a file, parses it and generates number formatter. class Processor { public: explicit Processor(const Options &options); Processor(const Processor &other) = delete; Processor &operator=(const Processor &other) = delete; // Returns 0 on success. int launch(); ~Processor(); private: bool write(const Rules &rules) const; std::unique_ptr<ParsedFile> parser_; const Options &options_; }; } // namespace numbers } // namespace codegen ���������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/scheme/������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0022273�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/scheme/codegen_scheme.py�������������������������������0000664�0000000�0000000�00000117625�13262451251�0025611�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������''' This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL ''' import glob, re, binascii, os, sys input_file = '' output_path = '' next_output_path = False for arg in sys.argv[1:]: if next_output_path: next_output_path = False output_path = arg elif arg == '-o': next_output_path = True elif re.match(r'^-o(.+)', arg): output_path = arg[2:] else: input_file = arg if input_file == '': print('Input file required.') sys.exit(1) if output_path == '': print('Output path required.') sys.exit(1) output_header = output_path + '/scheme.h' output_source = output_path + '/scheme.cpp' # define some checked flag conversions # the key flag type should be a subset of the value flag type # with exact the same names, then the key flag can be implicitly # casted to the value flag type parentFlags = {}; parentFlagsList = []; def addChildParentFlags(child, parent): parentFlagsList.append(child); parentFlags[child] = parent; addChildParentFlags('MTPDmessageService', 'MTPDmessage'); addChildParentFlags('MTPDupdateShortMessage', 'MTPDmessage'); addChildParentFlags('MTPDupdateShortChatMessage', 'MTPDmessage'); addChildParentFlags('MTPDupdateShortSentMessage', 'MTPDmessage'); addChildParentFlags('MTPDreplyKeyboardHide', 'MTPDreplyKeyboardMarkup'); addChildParentFlags('MTPDreplyKeyboardForceReply', 'MTPDreplyKeyboardMarkup'); addChildParentFlags('MTPDinputPeerNotifySettings', 'MTPDpeerNotifySettings'); addChildParentFlags('MTPDpeerNotifySettings', 'MTPDinputPeerNotifySettings'); addChildParentFlags('MTPDchannelForbidden', 'MTPDchannel'); # this is a map (key flags -> map (flag name -> flag bit)) # each key flag of parentFlags should be a subset of the value flag here parentFlagsCheck = {}; layer = ''; funcs = 0 types = 0; consts = 0 funcsNow = 0 enums = []; funcsDict = {}; funcsList = []; typesDict = {}; TypesDict = {}; typesList = []; boxed = {}; funcsText = ''; typesText = ''; dataTexts = ''; creatorProxyText = ''; factories = ''; flagOperators = ''; methods = ''; inlineMethods = ''; textSerializeInit = ''; textSerializeMethods = ''; forwards = ''; forwTypedefs = ''; with open(input_file) as f: for line in f: layerline = re.match(r'// LAYER (\d+)', line) if (layerline): layer = 'constexpr auto CurrentLayer = mtpPrime(' + layerline.group(1) + ');'; nocomment = re.match(r'^(.*?)//', line) if (nocomment): line = nocomment.group(1); if (re.match(r'\-\-\-functions\-\-\-', line)): funcsNow = 1; continue; if (re.match(r'\-\-\-types\-\-\-', line)): funcsNow = 0; continue; if (re.match(r'^\s*$', line)): continue; nametype = re.match(r'([a-zA-Z\.0-9_]+)(#[0-9a-f]+)?([^=]*)=\s*([a-zA-Z\.<>0-9_]+);', line); if (not nametype): if (not re.match(r'vector#1cb5c415 \{t:Type\} # \[ t \] = Vector t;', line)): print('Bad line found: ' + line); continue; name = nametype.group(1); nameInd = name.find('.'); if (nameInd >= 0): Name = name[0:nameInd] + '_' + name[nameInd + 1:nameInd + 2].upper() + name[nameInd + 2:]; name = name.replace('.', '_'); else: Name = name[0:1].upper() + name[1:]; typeid = nametype.group(2); if (typeid and len(typeid) > 0): typeid = typeid[1:]; # Skip '#' while (typeid and len(typeid) > 0 and typeid[0] == '0'): typeid = typeid[1:]; cleanline = nametype.group(1) + nametype.group(3) + '= ' + nametype.group(4); cleanline = re.sub(r' [a-zA-Z0-9_]+\:flags\.[0-9]+\?true', '', cleanline); cleanline = cleanline.replace('<', ' ').replace('>', ' ').replace(' ', ' '); cleanline = re.sub(r'^ ', '', cleanline); cleanline = re.sub(r' $', '', cleanline); cleanline = cleanline.replace(':bytes ', ':string '); cleanline = cleanline.replace('?bytes ', '?string '); cleanline = cleanline.replace('{', ''); cleanline = cleanline.replace('}', ''); countTypeId = binascii.crc32(binascii.a2b_qp(cleanline)); if (countTypeId < 0): countTypeId += 2 ** 32; countTypeId = '0x' + re.sub(r'^0x|L$', '', hex(countTypeId)); if (typeid and len(typeid) > 0): typeid = '0x' + typeid; if (typeid != countTypeId): print('Warning: counted ' + countTypeId + ' mismatch with provided ' + typeid + ' (' + cleanline + ')'); continue; else: typeid = countTypeId; params = nametype.group(3); restype = nametype.group(4); if (restype.find('<') >= 0): templ = re.match(r'^([vV]ector<)([A-Za-z0-9\._]+)>$', restype); if (templ): vectemplate = templ.group(2); if (re.match(r'^[A-Z]', vectemplate) or re.match(r'^[a-zA-Z0-9]+_[A-Z]', vectemplate)): restype = templ.group(1) + 'MTP' + vectemplate.replace('.', '_') + '>'; elif (vectemplate == 'int' or vectemplate == 'long' or vectemplate == 'string'): restype = templ.group(1) + 'MTP' + vectemplate.replace('.', '_') + '>'; else: foundmeta = ''; for metatype in typesDict: for typedata in typesDict[metatype]: if (typedata[0] == vectemplate): foundmeta = metatype; break; if (len(foundmeta) > 0): break; if (len(foundmeta) > 0): ptype = templ.group(1) + 'MTP' + foundmeta.replace('.', '_') + '>'; else: print('Bad vector param: ' + vectemplate); continue; else: print('Bad template type: ' + restype); continue; resType = restype.replace('.', '_'); if (restype.find('.') >= 0): parts = re.match(r'([a-z]+)\.([A-Z][A-Za-z0-9<>\._]+)', restype) if (parts): restype = parts.group(1) + '_' + parts.group(2)[0:1].lower() + parts.group(2)[1:]; else: print('Bad result type name with dot: ' + restype); continue; else: if (re.match(r'^[A-Z]', restype)): restype = restype[:1].lower() + restype[1:]; else: print('Bad result type name: ' + restype); continue; boxed[resType] = restype; boxed[Name] = name; enums.append('\tmtpc_' + name + ' = ' + typeid); paramsList = params.strip().split(' '); prms = {}; conditions = {}; trivialConditions = {}; # true type prmsList = []; conditionsList = []; isTemplate = hasFlags = hasTemplate = ''; for param in paramsList: if (re.match(r'^\s*$', param)): continue; templ = re.match(r'^{([A-Za-z]+):Type}$', param); if (templ): hasTemplate = templ.group(1); continue; pnametype = re.match(r'([a-z_][a-z0-9_]*):([A-Za-z0-9<>\._]+|![a-zA-Z]+|\#|[a-z_][a-z0-9_]*\.[0-9]+\?[A-Za-z0-9<>\._]+)$', param); if (not pnametype): print('Bad param found: "' + param + '" in line: ' + line); continue; pname = pnametype.group(1); ptypewide = pnametype.group(2); if (re.match(r'^!([a-zA-Z]+)$', ptypewide)): if ('!' + hasTemplate == ptypewide): isTemplate = pname; ptype = 'TQueryType'; else: print('Bad template param name: "' + param + '" in line: ' + line); continue; elif (ptypewide == '#'): hasFlags = pname; if funcsNow: ptype = 'flags<MTP' + name + '::Flags>'; else: ptype = 'flags<MTPD' + name + '::Flags>'; else: ptype = ptypewide; if (ptype.find('?') >= 0): pmasktype = re.match(r'([a-z_][a-z0-9_]*)\.([0-9]+)\?([A-Za-z0-9<>\._]+)', ptype); if (not pmasktype or pmasktype.group(1) != hasFlags): print('Bad param found: "' + param + '" in line: ' + line); continue; ptype = pmasktype.group(3); if (ptype.find('<') >= 0): templ = re.match(r'^([vV]ector<)([A-Za-z0-9\._]+)>$', ptype); if (templ): vectemplate = templ.group(2); if (re.match(r'^[A-Z]', vectemplate) or re.match(r'^[a-zA-Z0-9]+_[A-Z]', vectemplate)): ptype = templ.group(1) + 'MTP' + vectemplate.replace('.', '_') + '>'; elif (vectemplate == 'int' or vectemplate == 'long' or vectemplate == 'string'): ptype = templ.group(1) + 'MTP' + vectemplate.replace('.', '_') + '>'; else: foundmeta = ''; for metatype in typesDict: for typedata in typesDict[metatype]: if (typedata[0] == vectemplate): foundmeta = metatype; break; if (len(foundmeta) > 0): break; if (len(foundmeta) > 0): ptype = templ.group(1) + 'MTP' + foundmeta.replace('.', '_') + '>'; else: print('Bad vector param: ' + vectemplate); continue; else: print('Bad template type: ' + ptype); continue; if (not pname in conditions): conditionsList.append(pname); conditions[pname] = pmasktype.group(2); if (ptype == 'true'): trivialConditions[pname] = 1; elif (ptype.find('<') >= 0): templ = re.match(r'^([vV]ector<)([A-Za-z0-9\._]+)>$', ptype); if (templ): vectemplate = templ.group(2); if (re.match(r'^[A-Z]', vectemplate) or re.match(r'^[a-zA-Z0-9]+_[A-Z]', vectemplate)): ptype = templ.group(1) + 'MTP' + vectemplate.replace('.', '_') + '>'; elif (vectemplate == 'int' or vectemplate == 'long' or vectemplate == 'string'): ptype = templ.group(1) + 'MTP' + vectemplate.replace('.', '_') + '>'; else: foundmeta = ''; for metatype in typesDict: for typedata in typesDict[metatype]: if (typedata[0] == vectemplate): foundmeta = metatype; break; if (len(foundmeta) > 0): break; if (len(foundmeta) > 0): ptype = templ.group(1) + 'MTP' + foundmeta.replace('.', '_') + '>'; else: print('Bad vector param: ' + vectemplate); continue; else: print('Bad template type: ' + ptype); continue; prmsList.append(pname); prms[pname] = ptype.replace('.', '_'); if (isTemplate == '' and resType == 'X'): print('Bad response type "X" in "' + name +'" in line: ' + line); continue; if funcsNow: methodBodies = '' if (isTemplate != ''): funcsText += '\ntemplate <typename TQueryType>'; funcsText += '\nclass MTP' + name + ' { // RPC method \'' + nametype.group(1) + '\'\n'; # class funcsText += 'public:\n'; prmsStr = []; prmsInit = []; prmsNames = []; if (hasFlags != ''): funcsText += '\tenum class Flag : uint32 {\n'; maxbit = 0; parentFlagsCheck['MTP' + name] = {}; for paramName in conditionsList: funcsText += '\t\tf_' + paramName + ' = (1U << ' + conditions[paramName] + '),\n'; parentFlagsCheck['MTP' + name][paramName] = conditions[paramName]; maxbit = max(maxbit, int(conditions[paramName])); if (maxbit > 0): funcsText += '\n'; funcsText += '\t\tMAX_FIELD = (1U << ' + str(maxbit) + '),\n'; funcsText += '\t};\n'; funcsText += '\tusing Flags = base::flags<Flag>;\n'; funcsText += '\tfriend inline constexpr auto is_flag_type(Flag) { return true; };\n'; funcsText += '\n'; if (len(conditions)): for paramName in conditionsList: if (paramName in trivialConditions): funcsText += '\tbool is_' + paramName + '() const { return v' + hasFlags + '.v & Flag::f_' + paramName + '; }\n'; else: funcsText += '\tbool has_' + paramName + '() const { return v' + hasFlags + '.v & Flag::f_' + paramName + '; }\n'; funcsText += '\n'; if (len(prms) > len(trivialConditions)): for paramName in prmsList: if (paramName in trivialConditions): continue; paramType = prms[paramName]; prmsInit.append('v' + paramName + '(_' + paramName + ')'); prmsNames.append('_' + paramName); if (paramName == isTemplate): ptypeFull = paramType; else: ptypeFull = 'MTP' + paramType; funcsText += '\t' + ptypeFull + ' v' + paramName + ';\n'; if (paramType in ['int', 'Int', 'bool', 'Bool', 'flags<Flags>']): prmsStr.append(ptypeFull + ' _' + paramName); else: prmsStr.append('const ' + ptypeFull + ' &_' + paramName); funcsText += '\n'; funcsText += '\tMTP' + name + '() = default;\n'; # constructor if (len(prms) > len(trivialConditions)): funcsText += '\tMTP' + name + '(' + ', '.join(prmsStr) + ') : ' + ', '.join(prmsInit) + ' {\n\t}\n'; funcsText += '\n'; funcsText += '\tuint32 innerLength() const;\n'; # count size if (isTemplate != ''): methodBodies += 'template <typename TQueryType>\n' methodBodies += 'uint32 MTP' + name + '<TQueryType>::innerLength() const {\n'; else: methodBodies += 'uint32 MTP' + name + '::innerLength() const {\n'; size = []; for k in prmsList: v = prms[k]; if (k in conditionsList): if (not k in trivialConditions): size.append('(has_' + k + '() ? v' + k + '.innerLength() : 0)'); else: size.append('v' + k + '.innerLength()'); if (not len(size)): size.append('0'); methodBodies += '\treturn ' + ' + '.join(size) + ';\n'; methodBodies += '}\n'; funcsText += '\tmtpTypeId type() const {\n\t\treturn mtpc_' + name + ';\n\t}\n'; # type id funcsText += '\tvoid read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_' + name + ');\n'; # read method if (isTemplate != ''): methodBodies += 'template <typename TQueryType>\n' methodBodies += 'void MTP' + name + '<TQueryType>::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) {\n'; else: methodBodies += 'void MTP' + name + '::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) {\n'; for k in prmsList: v = prms[k]; if (k in conditionsList): if (not k in trivialConditions): methodBodies += '\tif (has_' + k + '()) { v' + k + '.read(from, end); } else { v' + k + ' = MTP' + v + '(); }\n'; else: methodBodies += '\tv' + k + '.read(from, end);\n'; methodBodies += '}\n'; funcsText += '\tvoid write(mtpBuffer &to) const;\n'; # write method if (isTemplate != ''): methodBodies += 'template <typename TQueryType>\n' methodBodies += 'void MTP' + name + '<TQueryType>::write(mtpBuffer &to) const {\n'; else: methodBodies += 'void MTP' + name + '::write(mtpBuffer &to) const {\n'; for k in prmsList: v = prms[k]; if (k in conditionsList): if (not k in trivialConditions): methodBodies += '\tif (has_' + k + '()) v' + k + '.write(to);\n'; else: methodBodies += '\tv' + k + '.write(to);\n'; methodBodies += '}\n'; if (isTemplate != ''): funcsText += '\n\tusing ResponseType = typename TQueryType::ResponseType;\n'; inlineMethods += methodBodies; else: funcsText += '\n\tusing ResponseType = MTP' + resType + ';\n'; # method return type methods += methodBodies; funcsText += '};\n'; # class ending if (isTemplate != ''): funcsText += 'template <typename TQueryType>\n'; funcsText += 'using MTP' + Name + ' = MTPBoxed<MTP' + name + '<TQueryType>>;\n'; else: funcsText += 'using MTP' + Name + ' = MTPBoxed<MTP' + name + '>;\n'; funcs = funcs + 1; if (not restype in funcsDict): funcsList.append(restype); funcsDict[restype] = []; # TypesDict[restype] = resType; funcsDict[restype].append([name, typeid, prmsList, prms, hasFlags, conditionsList, conditions, trivialConditions]); else: if (isTemplate != ''): print('Template types not allowed: "' + resType + '" in line: ' + line); continue; if (not restype in typesDict): typesList.append(restype); typesDict[restype] = []; TypesDict[restype] = resType; typesDict[restype].append([name, typeid, prmsList, prms, hasFlags, conditionsList, conditions, trivialConditions]); consts = consts + 1; # text serialization: types and funcs def addTextSerialize(lst, dct, dataLetter): result = ''; for restype in lst: v = dct[restype]; for data in v: name = data[0]; prmsList = data[2]; prms = data[3]; hasFlags = data[4]; conditionsList = data[5]; conditions = data[6]; trivialConditions = data[7]; result += 'void Serialize_' + name + '(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, uint32 iflag) {\n'; if (len(conditions)): result += '\tauto flag = MTP' + dataLetter + name + '::Flags::from_raw(iflag);\n\n'; if (len(prms)): result += '\tif (stage) {\n'; result += '\t\tto.add(",\\n").addSpaces(lev);\n'; result += '\t} else {\n'; result += '\t\tto.add("{ ' + name + '");\n'; result += '\t\tto.add("\\n").addSpaces(lev);\n'; result += '\t}\n'; result += '\tswitch (stage) {\n'; stage = 0; for k in prmsList: v = prms[k]; result += '\tcase ' + str(stage) + ': to.add(" ' + k + ': "); ++stages.back(); '; if (k == hasFlags): result += 'if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; '; if (k in trivialConditions): result += 'if (flag & MTP' + dataLetter + name + '::Flag::f_' + k + ') { '; result += 'to.add("YES [ BY BIT ' + conditions[k] + ' IN FIELD ' + hasFlags + ' ]"); '; result += '} else { to.add("[ SKIPPED BY BIT ' + conditions[k] + ' IN FIELD ' + hasFlags + ' ]"); } '; else: if (k in conditions): result += 'if (flag & MTP' + dataLetter + name + '::Flag::f_' + k + ') { '; result += 'types.push_back('; vtypeget = re.match(r'^[Vv]ector<MTP([A-Za-z0-9\._]+)>', v); if (vtypeget): if (not re.match(r'^[A-Z]', v)): result += 'mtpc_vector'; else: result += '0'; restype = vtypeget.group(1); try: if boxed[restype]: restype = 0; except KeyError: if re.match(r'^[A-Z]', restype): restype = 0; else: restype = v; try: if boxed[restype]: restype = 0; except KeyError: if re.match(r'^[A-Z]', restype): restype = 0; if (restype): try: conses = typesDict[restype]; if (len(conses) > 1): print('Complex bare type found: "' + restype + '" trying to serialize "' + k + '" of type "' + v + '"'); continue; if (vtypeget): result += '); vtypes.push_back('; result += 'mtpc_' + conses[0][0]; if (not vtypeget): result += '); vtypes.push_back(0'; except KeyError: if (vtypeget): result += '); vtypes.push_back('; if (re.match(r'^flags<', restype)): result += 'mtpc_flags'; else: result += 'mtpc_' + restype + '+0'; if (not vtypeget): result += '); vtypes.push_back(0'; else: result += '0); vtypes.push_back(0'; result += '); stages.push_back(0); flags.push_back(0); '; if (k in conditions): result += '} else { to.add("[ SKIPPED BY BIT ' + conditions[k] + ' IN FIELD ' + hasFlags + ' ]"); } '; result += 'break;\n'; stage = stage + 1; result += '\tdefault: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;\n'; result += '\t}\n'; else: result += '\tto.add("{ ' + name + ' }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back();\n'; result += '}\n\n'; return result; # text serialization: types and funcs def addTextSerializeInit(lst, dct): result = ''; for restype in lst: v = dct[restype]; for data in v: name = data[0]; result += '\tresult.insert(mtpc_' + name + ', Serialize_' + name + ');\n'; return result; textSerializeMethods += addTextSerialize(typesList, typesDict, 'D'); textSerializeInit += addTextSerializeInit(typesList, typesDict) + '\n'; textSerializeMethods += addTextSerialize(funcsList, funcsDict, ''); textSerializeInit += addTextSerializeInit(funcsList, funcsDict) + '\n'; for restype in typesList: v = typesDict[restype]; resType = TypesDict[restype]; withData = 0; creatorsDeclarations = ''; creatorsBodies = ''; flagDeclarations = ''; constructsText = ''; constructsBodies = ''; forwards += 'class MTP' + restype + ';\n'; forwTypedefs += 'using MTP' + resType + ' = MTPBoxed<MTP' + restype + '>;\n'; withType = (len(v) > 1); switchLines = ''; friendDecl = ''; getters = ''; reader = ''; writer = ''; sizeList = []; sizeFast = ''; newFast = ''; sizeCases = ''; for data in v: name = data[0]; typeid = data[1]; prmsList = data[2]; prms = data[3]; hasFlags = data[4]; conditionsList = data[5]; conditions = data[6]; trivialConditions = data[7]; dataText = ''; dataText += '\nclass MTPD' + name + ' : public MTP::internal::TypeData {\n'; # data class dataText += 'public:\n'; sizeList = []; creatorParams = []; creatorParamsList = []; readText = ''; writeText = ''; if (hasFlags != ''): dataText += '\tenum class Flag : uint32 {\n'; maxbit = 0; parentFlagsCheck['MTPD' + name] = {}; for paramName in conditionsList: dataText += '\t\tf_' + paramName + ' = (1U << ' + conditions[paramName] + '),\n'; parentFlagsCheck['MTPD' + name][paramName] = conditions[paramName]; maxbit = max(maxbit, int(conditions[paramName])); if (maxbit > 0): dataText += '\n'; dataText += '\t\tMAX_FIELD = (1U << ' + str(maxbit) + '),\n'; dataText += '\t};\n'; dataText += '\tusing Flags = base::flags<Flag>;\n'; dataText += '\tfriend inline constexpr auto is_flag_type(Flag) { return true; };\n'; dataText += '\n'; if (len(conditions)): for paramName in conditionsList: if (paramName in trivialConditions): dataText += '\tbool is_' + paramName + '() const { return v' + hasFlags + '.v & Flag::f_' + paramName + '; }\n'; else: dataText += '\tbool has_' + paramName + '() const { return v' + hasFlags + '.v & Flag::f_' + paramName + '; }\n'; dataText += '\n'; dataText += '\tMTPD' + name + '() = default;\n'; # default constructor switchLines += '\t\tcase mtpc_' + name + ': '; # for by-type-id type constructor if (len(prms) > len(trivialConditions)): switchLines += 'setData(new MTPD' + name + '()); '; withData = 1; getters += '\tconst MTPD' + name + ' &c_' + name + '() const;\n'; # const getter constructsBodies += 'const MTPD' + name + ' &MTP' + restype + '::c_' + name + '() const {\n'; if (withType): constructsBodies += '\tAssert(_type == mtpc_' + name + ');\n'; constructsBodies += '\treturn queryData<MTPD' + name + '>();\n'; constructsBodies += '}\n'; constructsText += '\texplicit MTP' + restype + '(const MTPD' + name + ' *data);\n'; # by-data type constructor constructsBodies += 'MTP' + restype + '::MTP' + restype + '(const MTPD' + name + ' *data) : TypeDataOwner(data)'; if (withType): constructsBodies += ', _type(mtpc_' + name + ')'; constructsBodies += ' {\n}\n'; dataText += '\tMTPD' + name + '('; # params constructor prmsStr = []; prmsInit = []; for paramName in prmsList: if (paramName in trivialConditions): continue; paramType = prms[paramName]; if (paramType in ['int', 'Int', 'bool', 'Bool']): prmsStr.append('MTP' + paramType + ' _' + paramName); creatorParams.append('MTP' + paramType + ' _' + paramName); else: prmsStr.append('const MTP' + paramType + ' &_' + paramName); creatorParams.append('const MTP' + paramType + ' &_' + paramName); creatorParamsList.append('_' + paramName); prmsInit.append('v' + paramName + '(_' + paramName + ')'); if (withType): readText += '\t\t'; writeText += '\t\t'; if (paramName in conditions): readText += '\tif (v->has_' + paramName + '()) { v->v' + paramName + '.read(from, end); } else { v->v' + paramName + ' = MTP' + paramType + '(); }\n'; writeText += '\tif (v.has_' + paramName + '()) v.v' + paramName + '.write(to);\n'; sizeList.append('(v.has_' + paramName + '() ? v.v' + paramName + '.innerLength() : 0)'); else: readText += '\tv->v' + paramName + '.read(from, end);\n'; writeText += '\tv.v' + paramName + '.write(to);\n'; sizeList.append('v.v' + paramName + '.innerLength()'); forwards += 'class MTPD' + name + ';\n'; # data class forward declaration dataText += ', '.join(prmsStr) + ') : ' + ', '.join(prmsInit) + ' {\n\t}\n'; dataText += '\n'; for paramName in prmsList: # fields declaration if (paramName in trivialConditions): continue; paramType = prms[paramName]; dataText += '\tMTP' + paramType + ' v' + paramName + ';\n'; sizeCases += '\t\tcase mtpc_' + name + ': {\n'; sizeCases += '\t\t\tconst MTPD' + name + ' &v(c_' + name + '());\n'; sizeCases += '\t\t\treturn ' + ' + '.join(sizeList) + ';\n'; sizeCases += '\t\t}\n'; sizeFast = '\tconst MTPD' + name + ' &v(c_' + name + '());\n\treturn ' + ' + '.join(sizeList) + ';\n'; newFast = 'new MTPD' + name + '()'; else: sizeFast = '\treturn 0;\n'; switchLines += 'break;\n'; dataText += '};\n'; # class ending if (len(prms) > len(trivialConditions)): dataTexts += dataText; # add data class if (not friendDecl): friendDecl += '\tfriend class MTP::internal::TypeCreator;\n'; creatorProxyText += '\tinline static MTP' + restype + ' new_' + name + '(' + ', '.join(creatorParams) + ') {\n'; if (len(prms) > len(trivialConditions)): # creator with params creatorProxyText += '\t\treturn MTP' + restype + '(new MTPD' + name + '(' + ', '.join(creatorParamsList) + '));\n'; else: if (withType): # creator by type creatorProxyText += '\t\treturn MTP' + restype + '(mtpc_' + name + ');\n'; else: # single creator creatorProxyText += '\t\treturn MTP' + restype + '();\n'; creatorProxyText += '\t}\n'; creatorsDeclarations += 'MTP' + restype + ' MTP_' + name + '(' + ', '.join(creatorParams) + ');\n'; creatorsBodies += 'MTP' + restype + ' MTP_' + name + '(' + ', '.join(creatorParams) + ') {\n'; creatorsBodies += '\treturn MTP::internal::TypeCreator::new_' + name + '(' + ', '.join(creatorParamsList) + ');\n'; creatorsBodies += '}\n'; if (withType): reader += '\t\tcase mtpc_' + name + ': _type = cons; '; # read switch line if (len(prms) > len(trivialConditions)): reader += '{\n'; reader += '\t\t\tauto v = new MTPD' + name + '();\n'; reader += '\t\t\tsetData(v);\n'; reader += readText; reader += '\t\t} break;\n'; writer += '\t\tcase mtpc_' + name + ': {\n'; # write switch line writer += '\t\t\tauto &v = c_' + name + '();\n'; writer += writeText; writer += '\t\t} break;\n'; else: reader += 'break;\n'; else: if (len(prms) > len(trivialConditions)): reader += '\n\tauto v = new MTPD' + name + '();\n'; reader += '\tsetData(v);\n'; reader += readText; writer += '\tauto &v = c_' + name + '();\n'; writer += writeText; forwards += '\n'; typesText += '\nclass MTP' + restype; # type class declaration if (withData): typesText += ' : private MTP::internal::TypeDataOwner'; # if has data fields typesText += ' {\n'; typesText += 'public:\n'; typesText += '\tMTP' + restype + '()'; # default constructor inits = []; if not (withType): if (withData): inits.append('TypeDataOwner(' + newFast + ')'); if (withData and not withType): typesText += ';\n'; methods += '\nMTP' + restype + '::MTP' + restype + '()'; if (inits): methods += ' : ' + ', '.join(inits); methods += ' {\n}\n'; else: if (inits): typesText += ' : ' + ', '.join(inits); typesText += ' {\n\t}\n'; if (withData): typesText += getters; typesText += '\n\tuint32 innerLength() const;\n'; # size method methods += '\nuint32 MTP' + restype + '::innerLength() const {\n'; if (withType and sizeCases): methods += '\tswitch (_type) {\n'; methods += sizeCases; methods += '\t}\n'; methods += '\treturn 0;\n'; else: methods += sizeFast; methods += '}\n'; typesText += '\tmtpTypeId type() const;\n'; # type id method methods += 'mtpTypeId MTP' + restype + '::type() const {\n'; if (withType): methods += '\tAssert(_type != 0);\n'; methods += '\treturn _type;\n'; else: methods += '\treturn mtpc_' + v[0][0] + ';\n'; methods += '}\n'; typesText += '\tvoid read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons'; # read method if (not withType): typesText += ' = mtpc_' + name; typesText += ');\n'; methods += 'void MTP' + restype + '::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) {\n'; if (withData): if not (withType): methods += '\tif (cons != mtpc_' + v[0][0] + ') throw mtpErrorUnexpected(cons, "MTP' + restype + '");\n'; if (withType): methods += '\tswitch (cons) {\n' methods += reader; methods += '\t\tdefault: throw mtpErrorUnexpected(cons, "MTP' + restype + '");\n'; methods += '\t}\n'; else: methods += reader; methods += '}\n'; typesText += '\tvoid write(mtpBuffer &to) const;\n'; # write method methods += 'void MTP' + restype + '::write(mtpBuffer &to) const {\n'; if (withType and writer != ''): methods += '\tswitch (_type) {\n'; methods += writer; methods += '\t}\n'; else: methods += writer; methods += '}\n'; typesText += '\n\tusing ResponseType = void;\n'; # no response types declared typesText += '\nprivate:\n'; # private constructors if (withType): # by-type-id constructor typesText += '\texplicit MTP' + restype + '(mtpTypeId type);\n'; methods += 'MTP' + restype + '::MTP' + restype + '(mtpTypeId type) : '; methods += '_type(type)'; methods += ' {\n'; methods += '\tswitch (type) {\n'; # type id check methods += switchLines; methods += '\t\tdefault: throw mtpErrorBadTypeId(type, "MTP' + restype + '");\n\t}\n'; methods += '}\n'; # by-type-id constructor end if (withData): typesText += constructsText; methods += constructsBodies; if (friendDecl): typesText += '\n' + friendDecl; if (withType): typesText += '\n\tmtpTypeId _type = 0;\n'; # type field var typesText += '};\n'; # type class ended flagOperators += flagDeclarations; factories += creatorsDeclarations; methods += creatorsBodies; typesText += 'using MTP' + resType + ' = MTPBoxed<MTP' + restype + '>;\n'; # boxed type definition flagOperators += '\n' for childName in parentFlagsList: parentName = parentFlags[childName]; for flag in parentFlagsCheck[childName]: # # 'channelForbidden' has 'until_date' flag and 'channel' doesn't have it. # But as long as flags don't collide this is not a problem. # # if (not flag in parentFlagsCheck[parentName]): # print('Flag ' + flag + ' not found in ' + parentName + ' which should be a flags-parent of ' + childName); # error # if (flag in parentFlagsCheck[parentName]): if (parentFlagsCheck[childName][flag] != parentFlagsCheck[parentName][flag]): print('Flag ' + flag + ' has different value in ' + parentName + ' which should be a flags-parent of ' + childName); error else: parentFlagsCheck[parentName][flag] = parentFlagsCheck[childName][flag]; flagOperators += 'inline ' + parentName + '::Flags mtpCastFlags(' + childName + '::Flags flags) { return static_cast<' + parentName + '::Flag>(flags.value()); }\n'; flagOperators += 'inline ' + parentName + '::Flags mtpCastFlags(MTPflags<' + childName + '::Flags> flags) { return mtpCastFlags(flags.v); }\n'; # manual types added here textSerializeMethods += '\ void _serialize_rpc_result(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, uint32 iflag) {\n\ if (stage) {\n\ to.add(",\\n").addSpaces(lev);\n\ } else {\n\ to.add("{ rpc_result");\n\ to.add("\\n").addSpaces(lev);\n\ }\n\ switch (stage) {\n\ case 0: to.add(" req_msg_id: "); ++stages.back(); types.push_back(mtpc_long); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;\n\ case 1: to.add(" result: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;\n\ default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;\n\ }\n\ }\n\ \n\ void _serialize_msg_container(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, uint32 iflag) {\n\ if (stage) {\n\ to.add(",\\n").addSpaces(lev);\n\ } else {\n\ to.add("{ msg_container");\n\ to.add("\\n").addSpaces(lev);\n\ }\n\ switch (stage) {\n\ case 0: to.add(" messages: "); ++stages.back(); types.push_back(mtpc_vector); vtypes.push_back(mtpc_core_message); stages.push_back(0); flags.push_back(0); break;\n\ default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;\n\ }\n\ }\n\ \n\ void _serialize_core_message(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, uint32 iflag) {\n\ if (stage) {\n\ to.add(",\\n").addSpaces(lev);\n\ } else {\n\ to.add("{ core_message");\n\ to.add("\\n").addSpaces(lev);\n\ }\n\ switch (stage) {\n\ case 0: to.add(" msg_id: "); ++stages.back(); types.push_back(mtpc_long); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;\n\ case 1: to.add(" seq_no: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;\n\ case 2: to.add(" bytes: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;\n\ case 3: to.add(" body: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;\n\ default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;\n\ }\n\ }\n\ \n'; textSerializeInit += '\ result.insert(mtpc_rpc_result, _serialize_rpc_result);\n\ result.insert(mtpc_msg_container, _serialize_msg_container);\n\ result.insert(mtpc_core_message, _serialize_core_message);\n'; # module itself header = '\ /*\n\ WARNING! All changes made in this file will be lost!\n\ Created from \'' + os.path.basename(input_file) + '\' by \'codegen_scheme\'\n\ \n\ This file is part of Telegram Desktop,\n\ the official desktop application for the Telegram messaging service.\n\ \n\ For license and copyright information please follow this link:\n\ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL\n\ */\n\ #pragma once\n\ \n\ #include "mtproto/core_types.h"\n\ #include "base/flags.h"\n\ \n\ // Creator current layer and proxy class declaration\n\ namespace MTP {\n\ namespace internal {\n\ \n\ ' + layer + '\n\ \n\ class TypeCreator;\n\ \n\ } // namespace internal\n\ } // namespace MTP\n\ \n\ // Type id constants\n\ enum {\n\ ' + ',\n'.join(enums) + '\n\ };\n\ \n\ // Type forward declarations\n\ ' + forwards + '\n\ // Boxed types definitions\n\ ' + forwTypedefs + '\n\ // Type classes definitions\n\ ' + typesText + '\n\ // Type constructors with data\n\ ' + dataTexts + '\n\ // RPC methods\n\ ' + funcsText + '\n\ // Template methods definition\n\ ' + inlineMethods + '\n\ // Flag operators definition\n\ ' + flagOperators + '\n\ // Factory methods declaration\n\ ' + factories + '\n\ // Human-readable text serialization\n\ void mtpTextSerializeType(MTPStringLogger &to, const mtpPrime *&from, const mtpPrime *end, mtpPrime cons, uint32 level, mtpPrime vcons);\n' source = '\ /*\n\ WARNING! All changes made in this file will be lost!\n\ Created from \'' + os.path.basename(input_file) + '\' by \'codegen_scheme\'\n\ \n\ This file is part of Telegram Desktop,\n\ the official desktop application for the Telegram messaging service.\n\ \n\ For license and copyright information please follow this link:\n\ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL\n\ */\n\ #include "scheme.h"\n\ \n\ // Creator proxy class definition\n\ namespace MTP {\n\ namespace internal {\n\ \n\ class TypeCreator {\n\ public:\n\ ' + creatorProxyText + '\n\ };\n\ \n\ } // namespace internal\n\ } // namespace MTP\n\ \n\ // Methods definition\n\ ' + methods + '\n\ \n\ using Types = QVector<mtpTypeId>;\n\ using StagesFlags = QVector<int32>;\n\ \n\ ' + textSerializeMethods + '\n\ namespace {\n\ \n\ using TextSerializer = void (*)(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, uint32 iflag);\n\ using TextSerializers = QMap<mtpTypeId, TextSerializer>;\n\ \n\ QMap<mtpTypeId, TextSerializer> createTextSerializers() {\n\ auto result = QMap<mtpTypeId, TextSerializer>();\n\ \n\ ' + textSerializeInit + '\n\ \n\ return result;\n\ }\n\ \n\ } // namespace\n\ \n\ void mtpTextSerializeType(MTPStringLogger &to, const mtpPrime *&from, const mtpPrime *end, mtpPrime cons, uint32 level, mtpPrime vcons) {\n\ static auto serializers = createTextSerializers();\n\ \n\ QVector<mtpTypeId> types, vtypes;\n\ QVector<int32> stages, flags;\n\ types.reserve(20); vtypes.reserve(20); stages.reserve(20); flags.reserve(20);\n\ types.push_back(mtpTypeId(cons)); vtypes.push_back(mtpTypeId(vcons)); stages.push_back(0); flags.push_back(0);\n\ \n\ const mtpPrime *start = from;\n\ mtpTypeId type = cons, vtype = vcons;\n\ int32 stage = 0, flag = 0;\n\ \n\ while (!types.isEmpty()) {\n\ type = types.back();\n\ vtype = vtypes.back();\n\ stage = stages.back();\n\ flag = flags.back();\n\ if (!type) {\n\ if (from >= end) {\n\ throw Exception("from >= end");\n\ } else if (stage) {\n\ throw Exception("unknown type on stage > 0");\n\ }\n\ types.back() = type = *from;\n\ start = ++from;\n\ }\n\ \n\ int32 lev = level + types.size() - 1;\n\ auto it = serializers.constFind(type);\n\ if (it != serializers.cend()) {\n\ (*it.value())(to, stage, lev, types, vtypes, stages, flags, start, end, flag);\n\ } else {\n\ mtpTextSerializeCore(to, from, end, type, lev, vtype);\n\ types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back();\n\ }\n\ }\n\ }\n'; already_header = '' if os.path.isfile(output_header): with open(output_header, 'r') as already: already_header = already.read() if already_header != header: with open(output_header, 'w') as out: out.write(header) already_source = '' if os.path.isfile(output_source): with open(output_source, 'r') as already: already_source = already.read() if already_source != source: with open(output_source, 'w') as out: out.write(source) �����������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/style/�������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0022167�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/style/generator.cpp������������������������������������0000664�0000000�0000000�00000112720�13262451251�0024664�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "codegen/style/generator.h" #include <set> #include <memory> #include <functional> #include <QtCore/QDir> #include <QtCore/QSet> #include <QtCore/QBuffer> #include <QtGui/QImage> #include <QtGui/QPainter> #include "codegen/style/parsed_file.h" using Module = codegen::style::structure::Module; using Struct = codegen::style::structure::Struct; using Variable = codegen::style::structure::Variable; using Tag = codegen::style::structure::TypeTag; namespace codegen { namespace style { namespace { constexpr int kErrorBadIconSize = 861; constexpr int kErrorBadIconFormat = 862; // crc32 hash, taken somewhere from the internet class Crc32Table { public: Crc32Table() { quint32 poly = 0x04c11db7; for (auto i = 0; i != 256; ++i) { _data[i] = reflect(i, 8) << 24; for (auto j = 0; j != 8; ++j) { _data[i] = (_data[i] << 1) ^ (_data[i] & (1 << 31) ? poly : 0); } _data[i] = reflect(_data[i], 32); } } inline quint32 operator[](int index) const { return _data[index]; } private: quint32 reflect(quint32 val, char ch) { quint32 result = 0; for (int i = 1; i < (ch + 1); ++i) { if (val & 1) { result |= 1 << (ch - i); } val >>= 1; } return result; } quint32 _data[256]; }; qint32 hashCrc32(const void *data, int len) { static Crc32Table table; const uchar *buffer = static_cast<const uchar *>(data); quint32 crc = 0xffffffff; for (int i = 0; i != len; ++i) { crc = (crc >> 8) ^ table[(crc & 0xFF) ^ buffer[i]]; } return static_cast<qint32>(crc ^ 0xffffffff); } char hexChar(uchar ch) { if (ch < 10) { return '0' + ch; } else if (ch < 16) { return 'a' + (ch - 10); } return '0'; } char hexSecondChar(char ch) { return hexChar((*reinterpret_cast<uchar*>(&ch)) & 0x0F); } char hexFirstChar(char ch) { return hexChar((*reinterpret_cast<uchar*>(&ch)) >> 4); } QString stringToEncodedString(const QString &str) { QString result, lineBreak = "\\\n"; result.reserve(str.size() * 8); bool writingHexEscapedCharacters = false, startOnNewLine = false; int lastCutSize = 0; auto utf = str.toUtf8(); for (auto ch : utf) { if (result.size() - lastCutSize > 80) { startOnNewLine = true; result.append(lineBreak); lastCutSize = result.size(); } if (ch == '\n') { writingHexEscapedCharacters = false; result.append("\\n"); } else if (ch == '\t') { writingHexEscapedCharacters = false; result.append("\\t"); } else if (ch == '"' || ch == '\\') { writingHexEscapedCharacters = false; result.append('\\').append(ch); } else if (ch < 32 || static_cast<uchar>(ch) > 127) { writingHexEscapedCharacters = true; result.append("\\x").append(hexFirstChar(ch)).append(hexSecondChar(ch)); } else { if (writingHexEscapedCharacters) { writingHexEscapedCharacters = false; result.append("\"\""); } result.append(ch); } } return '"' + (startOnNewLine ? lineBreak : QString()) + result + '"'; } QString stringToEncodedString(const std::string &str) { return stringToEncodedString(QString::fromStdString(str)); } QString stringToBinaryArray(const std::string &str) { QStringList rows, chars; chars.reserve(13); rows.reserve(1 + (str.size() / 13)); for (uchar ch : str) { if (chars.size() > 12) { rows.push_back(chars.join(", ")); chars.clear(); } chars.push_back(QString("0x") + hexFirstChar(ch) + hexSecondChar(ch)); } if (!chars.isEmpty()) { rows.push_back(chars.join(", ")); } return QString("{") + ((rows.size() > 1) ? '\n' : ' ') + rows.join(",\n") + " }"; } QString pxValueName(int value) { QString result = "px"; if (value < 0) { value = -value; result += 'm'; } return result + QString::number(value); } QString moduleBaseName(const structure::Module &module) { auto moduleInfo = QFileInfo(module.filepath()); auto moduleIsPalette = (moduleInfo.suffix() == "palette"); return moduleIsPalette ? "palette" : "style_" + moduleInfo.baseName(); } QString colorFallbackName(structure::Value value) { auto copy = value.copyOf(); if (!copy.isEmpty()) { return copy.back(); } return value.Color().fallback; } QChar paletteColorPart(uchar part) { part = (part & 0x0F); if (part >= 10) { return 'a' + (part - 10); } return '0' + part; } QString paletteColorComponent(uchar value) { return QString() + paletteColorPart(value >> 4) + paletteColorPart(value); } QString paletteColorValue(const structure::data::color &value) { auto result = paletteColorComponent(value.red) + paletteColorComponent(value.green) + paletteColorComponent(value.blue); if (value.alpha != 255) result += paletteColorComponent(value.alpha); return result; } } // namespace Generator::Generator(const structure::Module &module, const QString &destBasePath, const common::ProjectInfo &project, bool isPalette) : module_(module) , basePath_(destBasePath) , baseName_(QFileInfo(basePath_).baseName()) , project_(project) , isPalette_(isPalette) { } bool Generator::writeHeader() { header_ = std::make_unique<common::CppFile>(basePath_ + ".h", project_); header_->include("ui/style/style_core.h").newline(); if (!writeHeaderStyleNamespace()) { return false; } if (!writeRefsDeclarations()) { return false; } return header_->finalize(); } bool Generator::writeSource() { source_ = std::make_unique<common::CppFile>(basePath_ + ".cpp", project_); writeIncludesInSource(); if (module_.hasVariables()) { source_->pushNamespace().newline(); source_->stream() << "\ bool inited = false;\n\ \n\ class Module_" << baseName_ << " : public style::internal::ModuleBase {\n\ public:\n\ Module_" << baseName_ << "() { style::internal::registerModule(this); }\n\ ~Module_" << baseName_ << "() { style::internal::unregisterModule(this); }\n\ \n\ void start() override {\n\ style::internal::init_" << baseName_ << "();\n\ }\n\ void stop() override {\n\ }\n\ };\n\ Module_" << baseName_ << " registrator;\n"; if (isPalette_) { source_->newline(); source_->stream() << "style::palette _palette;\n"; } else { if (!writeVariableDefinitions()) { return false; } } source_->newline().popNamespace(); source_->newline().pushNamespace("st"); if (!writeRefsDefinition()) { return false; } source_->popNamespace().newline().pushNamespace("style"); if (isPalette_) { writeSetPaletteColor(); } source_->pushNamespace("internal").newline(); if (!writeVariableInit()) { return false; } } return source_->finalize(); } // Empty result means an error. QString Generator::typeToString(structure::Type type) const { switch (type.tag) { case Tag::Invalid: return QString(); case Tag::Int: return "int"; case Tag::Double: return "double"; case Tag::Pixels: return "int"; case Tag::String: return "QString"; case Tag::Color: return "style::color"; case Tag::Point: return "style::point"; case Tag::Size: return "style::size"; case Tag::Align: return "style::align"; case Tag::Margins: return "style::margins"; case Tag::Font: return "style::font"; case Tag::Icon: return "style::icon"; case Tag::Struct: return "style::" + type.name.back(); } return QString(); } // Empty result means an error. QString Generator::typeToDefaultValue(structure::Type type) const { switch (type.tag) { case Tag::Invalid: return QString(); case Tag::Int: return "0"; case Tag::Double: return "0."; case Tag::Pixels: return "0"; case Tag::String: return "QString()"; case Tag::Color: return "{ Qt::Uninitialized }"; case Tag::Point: return "{ 0, 0 }"; case Tag::Size: return "{ 0, 0 }"; case Tag::Align: return "style::al_topleft"; case Tag::Margins: return "{ 0, 0, 0, 0 }"; case Tag::Font: return "{ Qt::Uninitialized }"; case Tag::Icon: return "{ Qt::Uninitialized }"; case Tag::Struct: { if (auto realType = module_.findStruct(type.name)) { QStringList fields; for (auto field : realType->fields) { fields.push_back(typeToDefaultValue(field.type)); } return "{ " + fields.join(", ") + " }"; } return QString(); } break; } return QString(); } // Empty result means an error. QString Generator::valueAssignmentCode(structure::Value value) const { auto copy = value.copyOf(); if (!copy.isEmpty()) { return "st::" + copy.back(); } switch (value.type().tag) { case Tag::Invalid: return QString(); case Tag::Int: return QString("%1").arg(value.Int()); case Tag::Double: return QString("%1").arg(value.Double()); case Tag::Pixels: return pxValueName(value.Int()); case Tag::String: return QString("QString::fromUtf8(%1)").arg(stringToEncodedString(value.String())); case Tag::Color: { auto v(value.Color()); if (v.red == v.green && v.red == v.blue && v.red == 0 && v.alpha == 255) { return QString("st::windowFg"); } else if (v.red == v.green && v.red == v.blue && v.red == 255 && v.alpha == 0) { return QString("st::transparent"); } else { common::logError(common::kErrorInternal, "") << "bad color value"; return QString(); } } break; case Tag::Point: { auto v(value.Point()); return QString("{ %1, %2 }").arg(pxValueName(v.x)).arg(pxValueName(v.y)); } break; case Tag::Size: { auto v(value.Size()); return QString("{ %1, %2 }").arg(pxValueName(v.width)).arg(pxValueName(v.height)); } break; case Tag::Align: return QString("style::al_%1").arg(value.String().c_str()); case Tag::Margins: { auto v(value.Margins()); return QString("{ %1, %2, %3, %4 }").arg(pxValueName(v.left)).arg(pxValueName(v.top)).arg(pxValueName(v.right)).arg(pxValueName(v.bottom)); } break; case Tag::Font: { auto v(value.Font()); QString family = "0"; if (!v.family.empty()) { auto familyIndex = fontFamilies_.value(v.family, -1); if (familyIndex < 0) { return QString(); } family = QString("font%1index").arg(familyIndex); } return QString("{ %1, %2, %3 }").arg(pxValueName(v.size)).arg(v.flags).arg(family); } break; case Tag::Icon: { auto v(value.Icon()); if (v.parts.empty()) return QString("{}"); QStringList parts; for (const auto &part : v.parts) { auto maskIndex = iconMasks_.value(part.filename, -1); if (maskIndex < 0) { return QString(); } auto color = valueAssignmentCode(part.color); auto offset = valueAssignmentCode(part.offset); parts.push_back(QString("MonoIcon{ &iconMask%1, %2, %3 }").arg(maskIndex).arg(color).arg(offset)); } return QString("{ %1 }").arg(parts.join(", ")); } break; case Tag::Struct: { if (!value.Fields()) return QString(); QStringList fields; for (auto field : *value.Fields()) { fields.push_back(valueAssignmentCode(field.variable.value)); } return "{ " + fields.join(", ") + " }"; } break; } return QString(); } bool Generator::writeHeaderStyleNamespace() { if (!module_.hasStructs() && !module_.hasVariables()) { return true; } header_->pushNamespace("style"); if (module_.hasVariables()) { header_->pushNamespace("internal").newline(); header_->stream() << "void init_" << baseName_ << "();\n\n"; header_->popNamespace(); } bool wroteForwardDeclarations = writeStructsForwardDeclarations(); if (module_.hasStructs()) { if (!wroteForwardDeclarations) { header_->newline(); } if (!writeStructsDefinitions()) { return false; } } else if (isPalette_) { if (!wroteForwardDeclarations) { header_->newline(); } if (!writePaletteDefinition()) { return false; } } header_->popNamespace().newline(); return true; } bool Generator::writePaletteDefinition() { header_->stream() << "\ class palette {\n\ public:\n\ palette() = default;\n\ palette(const palette &other) = delete;\n\ \n\ QByteArray save() const;\n\ bool load(const QByteArray &cache);\n\ \n\ enum class SetResult {\n\ Ok,\n\ KeyNotFound,\n\ ValueNotFound,\n\ Duplicate,\n\ };\n\ SetResult setColor(QLatin1String name, uchar r, uchar g, uchar b, uchar a);\n\ SetResult setColor(QLatin1String name, QLatin1String from);\n\ void reset() {\n\ clear();\n\ finalize();\n\ }\n\ \n\ // Created not inited, should be finalized before usage.\n\ void finalize();\n\ \n\ int indexOfColor(color c) const;\n\ color colorAtIndex(int index) const;\n\ \n\ inline const color &get_transparent() const { return _colors[0]; }; // special color\n"; int indexInPalette = 1; if (!module_.enumVariables([&](const Variable &variable) -> bool { auto name = variable.name.back(); if (variable.value.type().tag != structure::TypeTag::Color) { return false; } auto index = (indexInPalette++); header_->stream() << "\tinline const color &get_" << name << "() const { return _colors[" << index << "]; };\n"; return true; })) return false; auto count = indexInPalette; header_->stream() << "\ \n\ palette &operator=(const palette &other) {\n\ auto wasReady = _ready;\n\ for (int i = 0; i != kCount; ++i) {\n\ if (other._status[i] == Status::Loaded) {\n\ if (_status[i] == Status::Initial) {\n\ new (data(i)) internal::ColorData(*other.data(i));\n\ } else {\n\ *data(i) = *other.data(i);\n\ }\n\ } else if (_status[i] != Status::Initial) {\n\ data(i)->~ColorData();\n\ _status[i] = Status::Initial;\n\ _ready = false;\n\ }\n\ }\n\ if (wasReady && !_ready) {\n\ finalize();\n\ }\n\ return *this;\n\ }\n\ \n\ static int32 Checksum();\n\ \n\ ~palette() {\n\ clear();\n\ }\n\ \n\ private:\n\ static constexpr auto kCount = " << count << ";\n\ \n\ void clear() {\n\ for (int i = 0; i != kCount; ++i) {\n\ if (_status[i] != Status::Initial) {\n\ data(i)->~ColorData();\n\ _status[i] = Status::Initial;\n\ _ready = false;\n\ }\n\ }\n\ }\n\ \n\ struct TempColorData { uchar r, g, b, a; };\n\ void compute(int index, int fallbackIndex, TempColorData value) {\n\ if (_status[index] == Status::Initial) {\n\ if (fallbackIndex >= 0 && _status[fallbackIndex] == Status::Loaded) {\n\ _status[index] = Status::Loaded;\n\ new (data(index)) internal::ColorData(*data(fallbackIndex));\n\ } else {\n\ _status[index] = Status::Created;\n\ new (data(index)) internal::ColorData(value.r, value.g, value.b, value.a);\n\ }\n\ }\n\ }\n\ \n\ internal::ColorData *data(int index) {\n\ return reinterpret_cast<internal::ColorData*>(_data) + index;\n\ }\n\ \n\ const internal::ColorData *data(int index) const {\n\ return reinterpret_cast<const internal::ColorData*>(_data) + index;\n\ }\n\ \n\ void setData(int index, const internal::ColorData &value) {\n\ if (_status[index] == Status::Initial) {\n\ new (data(index)) internal::ColorData(value);\n\ } else {\n\ *data(index) = value;\n\ }\n\ _status[index] = Status::Loaded;\n\ }\n\ \n\ enum class Status {\n\ Initial,\n\ Created,\n\ Loaded,\n\ };\n\ \n\ alignas(alignof(internal::ColorData)) char _data[sizeof(internal::ColorData) * kCount];\n\ \n\ color _colors[kCount] = {\n"; for (int i = 0; i != count; ++i) { header_->stream() << "\t\tdata(" << i << "),\n"; } header_->stream() << "\ };\n\ Status _status[kCount] = { Status::Initial };\n\ bool _ready = false;\n\ \n\ };\n\ \n\ namespace main_palette {\n\ \n\ QByteArray save();\n\ bool load(const QByteArray &cache);\n\ palette::SetResult setColor(QLatin1String name, uchar r, uchar g, uchar b, uchar a);\n\ palette::SetResult setColor(QLatin1String name, QLatin1String from);\n\ void apply(const palette &other);\n\ void reset();\n\ int indexOfColor(color c);\n\ \n\ struct row {\n\ \tQLatin1String name;\n\ \tQLatin1String value;\n\ \tQLatin1String fallback;\n\ \tQLatin1String description;\n\ };\n\ QList<row> data();\n\ \n\ } // namespace main_palette\n"; return true; } bool Generator::writeStructsForwardDeclarations() { bool hasNoExternalStructs = module_.enumVariables([&](const Variable &value) -> bool { if (value.value.type().tag == structure::TypeTag::Struct) { if (!module_.findStructInModule(value.value.type().name, module_)) { return false; } } return true; }); if (hasNoExternalStructs) { return false; } header_->newline(); std::set<QString> alreadyDeclaredTypes; bool result = module_.enumVariables([&](const Variable &value) -> bool { if (value.value.type().tag == structure::TypeTag::Struct) { if (!module_.findStructInModule(value.value.type().name, module_)) { if (alreadyDeclaredTypes.find(value.value.type().name.back()) == alreadyDeclaredTypes.end()) { header_->stream() << "struct " << value.value.type().name.back() << ";\n"; alreadyDeclaredTypes.emplace(value.value.type().name.back()); } } } return true; }); header_->newline(); return result; } bool Generator::writeStructsDefinitions() { if (!module_.hasStructs()) { return true; } bool result = module_.enumStructs([&](const Struct &value) -> bool { header_->stream() << "\ struct " << value.name.back() << " {\n"; for (auto &field : value.fields) { auto type = typeToString(field.type); if (type.isEmpty()) { return false; } header_->stream() << "\t" << type << " " << field.name.back() << ";\n"; } header_->stream() << "\ };\n\n"; return true; }); return result; } bool Generator::writeRefsDeclarations() { if (!module_.hasVariables()) { return true; } header_->pushNamespace("st"); if (isPalette_) { header_->stream() << "extern const style::color &transparent; // special color\n"; } bool result = module_.enumVariables([&](const Variable &value) -> bool { auto name = value.name.back(); auto type = typeToString(value.value.type()); if (type.isEmpty()) { return false; } header_->stream() << "extern const " << type << " &" << name << ";\n"; return true; }); header_->popNamespace(); return result; } bool Generator::writeIncludesInSource() { if (!module_.hasIncludes()) { return true; } auto includes = QStringList(); std::function<bool(const Module&)> collector = [&](const Module &module) { module.enumIncludes(collector); auto base = moduleBaseName(module); if (!includes.contains(base)) { includes.push_back(base); } return true; }; auto result = module_.enumIncludes(collector); for (auto base : includes) { source_->include(base + ".h"); } source_->newline(); return result; } bool Generator::writeVariableDefinitions() { if (!module_.hasVariables()) { return true; } source_->newline(); bool result = module_.enumVariables([&](const Variable &variable) -> bool { auto name = variable.name.back(); auto type = typeToString(variable.value.type()); if (type.isEmpty()) { return false; } source_->stream() << type << " _" << name << " = " << typeToDefaultValue(variable.value.type()) << ";\n"; return true; }); return result; } bool Generator::writeRefsDefinition() { if (!module_.hasVariables()) { return true; } if (isPalette_) { source_->stream() << "const style::color &transparent(_palette.get_transparent()); // special color\n"; } bool result = module_.enumVariables([&](const Variable &variable) -> bool { auto name = variable.name.back(); auto type = typeToString(variable.value.type()); if (type.isEmpty()) { return false; } source_->stream() << "const " << type << " &" << name << "("; if (isPalette_) { source_->stream() << "_palette.get_" << name << "()"; } else { source_->stream() << "_" << name; } source_->stream() << ");\n"; return true; }); return result; } bool Generator::writeSetPaletteColor() { source_->newline(); source_->stream() << "\n\ int palette::indexOfColor(style::color c) const {\n\ auto start = data(0);\n\ if (c._data >= start && c._data < start + kCount) {\n\ return static_cast<int>(c._data - start);\n\ }\n\ return -1;\n\ }\n\ \n\ color palette::colorAtIndex(int index) const {\n\ Assert(_ready);\n\ Assert(index >= 0 && index < kCount);\n\ return _colors[index];\n\ }\n\ \n\ void palette::finalize() {\n\ if (_ready) return;\n\ _ready = true;\n\ \n\ compute(0, -1, { 255, 255, 255, 0}); // special color\n"; QList<structure::FullName> names; module_.enumVariables([&](const Variable &variable) -> bool { names.push_back(variable.name); return true; }); QString dataRows; int indexInPalette = 1; QByteArray checksumString; checksumString.append("&transparent:{ 255, 255, 255, 0 }"); auto result = module_.enumVariables([&](const Variable &variable) -> bool { auto name = variable.name.back(); auto index = indexInPalette++; paletteIndices_.emplace(name, index); if (variable.value.type().tag != structure::TypeTag::Color) { return false; } auto color = variable.value.Color(); auto fallbackIterator = paletteIndices_.find(colorFallbackName(variable.value)); auto fallbackIndex = (fallbackIterator == paletteIndices_.end()) ? -1 : fallbackIterator->second; auto assignment = QString("{ %1, %2, %3, %4 }").arg(color.red).arg(color.green).arg(color.blue).arg(color.alpha); source_->stream() << "\tcompute(" << index << ", " << fallbackIndex << ", " << assignment << ");\n"; checksumString.append('&' + name + ':' + assignment); auto isCopy = !variable.value.copyOf().isEmpty(); auto colorString = paletteColorValue(color); auto fallbackName = QString(); if (fallbackIndex > 0) { auto fallbackVariable = module_.findVariableInModule(names[fallbackIndex - 1], module_); if (fallbackVariable && fallbackVariable->value.type().tag == structure::TypeTag::Color) { fallbackName = fallbackVariable->name.back(); } } auto value = isCopy ? fallbackName : '#' + colorString; if (value.isEmpty()) { return false; } dataRows.append("\tresult.push_back({ qstr(\"" + name + "\"), qstr(\"" + value + "\"), qstr(\"" + (isCopy ? QString() : fallbackName) + "\"), qstr(" + stringToEncodedString(variable.description.toStdString()) + ") });\n"); return true; }); if (!result) { return false; } auto count = indexInPalette; auto checksum = hashCrc32(checksumString.constData(), checksumString.size()); source_->stream() << "\ }\n\ \n\ int32 palette::Checksum() {\n\ return " << checksum << ";\n\ }\n"; source_->newline().pushNamespace().newline(); source_->stream() << "\ int getPaletteIndex(QLatin1String name) {\n\ auto size = name.size();\n\ auto data = name.data();\n"; auto tabs = [](int size) { return QString(size, '\t'); }; enum class UsedCheckType { Switch, If, UpcomingIf, }; auto checkTypes = QVector<UsedCheckType>(); auto checkLengthHistory = QVector<int>(1, 0); auto chars = QString(); auto tabsUsed = 1; // Returns true if at least one check was finished. auto finishChecksTillKey = [&](const QString &key) { auto result = false; while (!chars.isEmpty() && key.midRef(0, chars.size()) != chars) { result = true; auto wasType = checkTypes.back(); chars.resize(chars.size() - 1); checkTypes.pop_back(); checkLengthHistory.pop_back(); if (wasType == UsedCheckType::Switch || wasType == UsedCheckType::If) { --tabsUsed; if (wasType == UsedCheckType::Switch) { source_->stream() << tabs(tabsUsed) << "break;\n"; } if ((!chars.isEmpty() && key.midRef(0, chars.size()) != chars) || key == chars) { source_->stream() << tabs(tabsUsed) << "}\n"; } } } return result; }; // Check if we can use "if" for a check on "charIndex" in "it" (otherwise only "switch") auto canUseIfForCheck = [](auto it, auto end, int charIndex) { auto key = it->first; auto i = it; auto keyStart = key.mid(0, charIndex); for (++i; i != end; ++i) { auto nextKey = i->first; if (nextKey.mid(0, charIndex) != keyStart) { return true; } else if (nextKey.size() > charIndex && nextKey[charIndex] != key[charIndex]) { return false; } } return true; }; auto countMinimalLength = [](auto it, auto end, int charIndex) { auto key = it->first; auto i = it; auto keyStart = key.mid(0, charIndex); auto result = key.size(); for (++i; i != end; ++i) { auto nextKey = i->first; if (nextKey.mid(0, charIndex) != keyStart) { break; } else if (nextKey.size() > charIndex && result > nextKey.size()) { result = nextKey.size(); } } return result; }; for (auto i = paletteIndices_.begin(), e = paletteIndices_.end(); i != e; ++i) { auto name = i->first; auto index = i->second; auto weContinueOldSwitch = finishChecksTillKey(name); while (chars.size() != name.size()) { auto checking = chars.size(); auto partialKey = name.mid(0, checking); auto keyChar = name[checking]; auto usedIfForCheckCount = 0; auto minimalLengthCheck = countMinimalLength(i, e, checking); for (; checking + usedIfForCheckCount != name.size(); ++usedIfForCheckCount) { if (!canUseIfForCheck(i, e, checking + usedIfForCheckCount) || countMinimalLength(i, e, checking + usedIfForCheckCount) != minimalLengthCheck) { break; } } auto usedIfForCheck = !weContinueOldSwitch && (usedIfForCheckCount > 0); auto checkLengthCondition = QString(); if (weContinueOldSwitch) { weContinueOldSwitch = false; } else { checkLengthCondition = (minimalLengthCheck > checkLengthHistory.back()) ? ("size >= " + QString::number(minimalLengthCheck)) : QString(); if (!usedIfForCheck) { source_->stream() << tabs(tabsUsed) << (checkLengthCondition.isEmpty() ? QString() : ("if (" + checkLengthCondition + ") ")) << "switch (data[" << checking << "]) {\n"; } } if (usedIfForCheck) { auto conditions = QStringList(); if (usedIfForCheckCount > 1) { conditions.push_back("!memcmp(data + " + QString::number(checking) + ", \"" + name.mid(checking, usedIfForCheckCount) + "\", " + QString::number(usedIfForCheckCount) + ")"); } else { conditions.push_back("data[" + QString::number(checking) + "] == '" + keyChar + "'"); } if (!checkLengthCondition.isEmpty()) { conditions.push_front(checkLengthCondition); } source_->stream() << tabs(tabsUsed) << "if (" << conditions.join(" && ") << ") {\n"; checkTypes.push_back(UsedCheckType::If); for (auto i = 1; i != usedIfForCheckCount; ++i) { checkTypes.push_back(UsedCheckType::UpcomingIf); chars.push_back(keyChar); checkLengthHistory.push_back(qMax(minimalLengthCheck, checkLengthHistory.back())); keyChar = name[checking + i]; } } else { source_->stream() << tabs(tabsUsed) << "case '" << keyChar << "':\n"; checkTypes.push_back(UsedCheckType::Switch); } ++tabsUsed; chars.push_back(keyChar); checkLengthHistory.push_back(qMax(minimalLengthCheck, checkLengthHistory.back())); } source_->stream() << tabs(tabsUsed) << "return (size == " << chars.size() << ") ? " << index << " : -1;\n"; } finishChecksTillKey(QString()); source_->stream() << "\ \n\ return -1;\n\ }\n"; source_->newline().popNamespace().newline(); source_->stream() << "\ QByteArray palette::save() const {\n\ if (!_ready) const_cast<palette*>(this)->finalize();\n\ \n\ auto result = QByteArray(" << (count * 4) << ", Qt::Uninitialized);\n\ for (auto i = 0, index = 0; i != " << count << "; ++i) {\n\ result[index++] = static_cast<uchar>(data(i)->c.red());\n\ result[index++] = static_cast<uchar>(data(i)->c.green());\n\ result[index++] = static_cast<uchar>(data(i)->c.blue());\n\ result[index++] = static_cast<uchar>(data(i)->c.alpha());\n\ }\n\ return result;\n\ }\n\ \n\ bool palette::load(const QByteArray &cache) {\n\ if (cache.size() != " << (count * 4) << ") return false;\n\ \n\ auto p = reinterpret_cast<const uchar*>(cache.constData());\n\ for (auto i = 0; i != " << count << "; ++i) {\n\ setData(i, { p[i * 4 + 0], p[i * 4 + 1], p[i * 4 + 2], p[i * 4 + 3] });\n\ }\n\ return true;\n\ }\n\ \n\ palette::SetResult palette::setColor(QLatin1String name, uchar r, uchar g, uchar b, uchar a) {\n\ auto nameIndex = getPaletteIndex(name);\n\ if (nameIndex < 0) return SetResult::KeyNotFound;\n\ auto duplicate = (_status[nameIndex] != Status::Initial);\n\ \n\ setData(nameIndex, { r, g, b, a });\n\ return duplicate ? SetResult::Duplicate : SetResult::Ok;\n\ }\n\ \n\ palette::SetResult palette::setColor(QLatin1String name, QLatin1String from) {\n\ auto nameIndex = getPaletteIndex(name);\n\ if (nameIndex < 0) return SetResult::KeyNotFound;\n\ auto duplicate = (_status[nameIndex] != Status::Initial);\n\ \n\ auto fromIndex = getPaletteIndex(from);\n\ if (fromIndex < 0 || _status[fromIndex] != Status::Loaded) return SetResult::ValueNotFound;\n\ \n\ setData(nameIndex, *data(fromIndex));\n\ return duplicate ? SetResult::Duplicate : SetResult::Ok;\n\ }\n\ \n\ namespace main_palette {\n\ \n\ QByteArray save() {\n\ return _palette.save();\n\ }\n\ \n\ bool load(const QByteArray &cache) {\n\ if (_palette.load(cache)) {\n\ style::internal::resetIcons();\n\ return true;\n\ }\n\ return false;\n\ }\n\ \n\ palette::SetResult setColor(QLatin1String name, uchar r, uchar g, uchar b, uchar a) {\n\ return _palette.setColor(name, r, g, b, a);\n\ }\n\ \n\ palette::SetResult setColor(QLatin1String name, QLatin1String from) {\n\ return _palette.setColor(name, from);\n\ }\n\ \n\ void apply(const palette &other) {\n\ _palette = other;\n\ style::internal::resetIcons();\n\ }\n\ \n\ void reset() {\n\ _palette.reset();\n\ style::internal::resetIcons();\n\ }\n\ \n\ int indexOfColor(color c) {\n\ return _palette.indexOfColor(c);\n\ }\n\ \n\ QList<row> data() {\n\ auto result = QList<row>();\n\ result.reserve(" << count << ");\n\ \n\ " << dataRows << "\n\ return result;\n\ }\n\ \n\ } // namespace main_palette\n\ \n"; return result; } bool Generator::writeVariableInit() { if (!module_.hasVariables()) { return true; } if (!collectUniqueValues()) { return false; } bool hasUniqueValues = (!pxValues_.isEmpty() || !fontFamilies_.isEmpty() || !iconMasks_.isEmpty()); if (hasUniqueValues) { source_->pushNamespace(); if (!writePxValuesInit()) { return false; } if (!writeFontFamiliesInit()) { return false; } if (!writeIconValues()) { return false; } source_->popNamespace().newline(); } source_->stream() << "\ void init_" << baseName_ << "() {\n\ if (inited) return;\n\ inited = true;\n\n"; if (module_.hasIncludes()) { bool writtenAtLeastOne = false; bool result = module_.enumIncludes([&](const Module &module) -> bool { if (module.hasVariables()) { source_->stream() << "\tinit_" + moduleBaseName(module) + "();\n"; writtenAtLeastOne = true; } return true; }); if (!result) { return false; } if (writtenAtLeastOne) { source_->newline(); } } if (!pxValues_.isEmpty() || !fontFamilies_.isEmpty()) { if (!pxValues_.isEmpty()) { source_->stream() << "\tinitPxValues();\n"; } if (!fontFamilies_.isEmpty()) { source_->stream() << "\tinitFontFamilies();\n"; } source_->newline(); } if (isPalette_) { source_->stream() << "\t_palette.finalize();\n"; } else if (!module_.enumVariables([&](const Variable &variable) -> bool { auto name = variable.name.back(); auto value = valueAssignmentCode(variable.value); if (value.isEmpty()) { return false; } source_->stream() << "\t_" << name << " = " << value << ";\n"; return true; })) { return false; } source_->stream() << "\ }\n\n"; return true; } bool Generator::writePxValuesInit() { if (pxValues_.isEmpty()) { return true; } for (auto i = pxValues_.cbegin(), e = pxValues_.cend(); i != e; ++i) { source_->stream() << "int " << pxValueName(i.key()) << " = " << i.key() << ";\n"; } source_->stream() << "\ void initPxValues() {\n\ if (cRetina()) return;\n\ \n\ switch (cScale()) {\n"; for (int i = 1, scalesCount = _scales.size(); i < scalesCount; ++i) { source_->stream() << "\tcase " << _scaleNames.at(i) << ":\n"; for (auto it = pxValues_.cbegin(), e = pxValues_.cend(); it != e; ++it) { auto value = it.key(); int adjusted = structure::data::pxAdjust(value, _scales.at(i)); if (adjusted != value) { source_->stream() << "\t\t" << pxValueName(value) << " = " << adjusted << ";\n"; } } source_->stream() << "\tbreak;\n"; } source_->stream() << "\ }\n\ }\n\n"; return true; } bool Generator::writeFontFamiliesInit() { if (fontFamilies_.isEmpty()) { return true; } for (auto familyIndex : fontFamilies_) { source_->stream() << "int font" << familyIndex << "index;\n"; } source_->stream() << "void initFontFamilies() {\n"; for (auto i = fontFamilies_.cbegin(), e = fontFamilies_.cend(); i != e; ++i) { auto family = stringToEncodedString(i.key()); source_->stream() << "\tfont" << i.value() << "index = style::internal::registerFontFamily(" << family << ");\n"; } source_->stream() << "}\n\n"; return true; } namespace { QByteArray iconMaskValueSize(int width, int height) { QByteArray result; QLatin1String generateTag("GENERATE:"); result.append(generateTag.data(), generateTag.size()); QLatin1String sizeTag("SIZE:"); result.append(sizeTag.data(), sizeTag.size()); { QDataStream stream(&result, QIODevice::Append); stream.setVersion(QDataStream::Qt_5_1); stream << qint32(width) << qint32(height); } return result; } QByteArray iconMaskValuePng(QString filepath) { QByteArray result; QFileInfo fileInfo(filepath); auto directory = fileInfo.dir(); auto nameAndModifiers = fileInfo.fileName().split('-'); filepath = directory.filePath(nameAndModifiers[0]); auto modifiers = nameAndModifiers.mid(1); QImage png100x(filepath + ".png"); QImage png200x(filepath + "@2x.png"); png100x.setDevicePixelRatio(1.); png200x.setDevicePixelRatio(1.); if (png100x.isNull()) { common::logError(common::kErrorFileNotOpened, filepath + ".png") << "could not open icon file"; return result; } if (png200x.isNull()) { common::logError(common::kErrorFileNotOpened, filepath + "@2x.png") << "could not open icon file"; return result; } if (png100x.format() != png200x.format()) { common::logError(kErrorBadIconFormat, filepath + ".png") << "1x and 2x icons have different format"; return result; } if (png100x.width() * 2 != png200x.width() || png100x.height() * 2 != png200x.height()) { common::logError(kErrorBadIconSize, filepath + ".png") << "bad icons size, 1x: " << png100x.width() << "x" << png100x.height() << ", 2x: " << png200x.width() << "x" << png200x.height(); return result; } for (auto modifierName : modifiers) { if (auto modifier = GetModifier(modifierName)) { modifier(png100x, png200x); } else { common::logError(common::kErrorInternal, filepath) << "modifier should be valid here, name: " << modifierName.toStdString(); return result; } } QImage png125x = png200x.scaled(structure::data::pxAdjust(png100x.width(), 5), structure::data::pxAdjust(png100x.height(), 5), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); QImage png150x = png200x.scaled(structure::data::pxAdjust(png100x.width(), 6), structure::data::pxAdjust(png100x.height(), 6), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); QImage composed(png200x.width() + png100x.width(), png200x.height() + png150x.height(), png100x.format()); { QPainter p(&composed); p.setCompositionMode(QPainter::CompositionMode_Source); p.fillRect(0, 0, composed.width(), composed.height(), QColor(0, 0, 0, 255)); p.drawImage(0, 0, png200x); p.drawImage(png200x.width(), 0, png100x); p.drawImage(0, png200x.height(), png150x); p.drawImage(png150x.width(), png200x.height(), png125x); } { QBuffer buffer(&result); composed.save(&buffer, "PNG"); } return result; } } // namespace bool Generator::writeIconValues() { if (iconMasks_.isEmpty()) { return true; } for (auto i = iconMasks_.cbegin(), e = iconMasks_.cend(); i != e; ++i) { QString filePath = i.key(); QByteArray maskData; QImage png100x, png200x; if (filePath.startsWith("size://")) { QStringList dimensions = filePath.mid(7).split(','); if (dimensions.size() < 2 || dimensions.at(0).toInt() <= 0 || dimensions.at(1).toInt() <= 0) { common::logError(common::kErrorFileNotOpened, filePath) << "bad dimensions"; return false; } maskData = iconMaskValueSize(dimensions.at(0).toInt(), dimensions.at(1).toInt()); } else { maskData = iconMaskValuePng(filePath); } if (maskData.isEmpty()) { return false; } source_->stream() << "const uchar iconMask" << i.value() << "Data[] = " << stringToBinaryArray(std::string(maskData.constData(), maskData.size())) << ";\n"; source_->stream() << "IconMask iconMask" << i.value() << "(iconMask" << i.value() << "Data);\n\n"; } return true; } bool Generator::collectUniqueValues() { int fontFamilyIndex = 0; int iconMaskIndex = 0; std::function<bool(const Variable&)> collector = [this, &collector, &fontFamilyIndex, &iconMaskIndex](const Variable &variable) { auto value = variable.value; if (!value.copyOf().isEmpty()) { return true; } switch (value.type().tag) { case Tag::Invalid: case Tag::Int: case Tag::Double: case Tag::String: case Tag::Color: case Tag::Align: break; case Tag::Pixels: pxValues_.insert(value.Int(), true); break; case Tag::Point: { auto v(value.Point()); pxValues_.insert(v.x, true); pxValues_.insert(v.y, true); } break; case Tag::Size: { auto v(value.Size()); pxValues_.insert(v.width, true); pxValues_.insert(v.height, true); } break; case Tag::Margins: { auto v(value.Margins()); pxValues_.insert(v.left, true); pxValues_.insert(v.top, true); pxValues_.insert(v.right, true); pxValues_.insert(v.bottom, true); } break; case Tag::Font: { auto v(value.Font()); pxValues_.insert(v.size, true); if (!v.family.empty() && !fontFamilies_.contains(v.family)) { fontFamilies_.insert(v.family, ++fontFamilyIndex); } } break; case Tag::Icon: { auto v(value.Icon()); for (auto &part : v.parts) { pxValues_.insert(part.offset.Point().x, true); pxValues_.insert(part.offset.Point().y, true); if (!iconMasks_.contains(part.filename)) { iconMasks_.insert(part.filename, ++iconMaskIndex); } } } break; case Tag::Struct: { auto fields = variable.value.Fields(); if (!fields) { return false; } for (auto field : *fields) { if (!collector(field.variable)) { return false; } } } break; } return true; }; return module_.enumVariables(collector); } } // namespace style } // namespace codegen ������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/style/generator.h��������������������������������������0000664�0000000�0000000�00000004052�13262451251�0024327�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <memory> #include <map> #include <functional> #include <QtCore/QString> #include <QtCore/QSet> #include "codegen/common/cpp_file.h" #include "codegen/style/structure_types.h" namespace codegen { namespace style { namespace structure { class Module; } // namespace structure class Generator { public: Generator(const structure::Module &module, const QString &destBasePath, const common::ProjectInfo &project, bool isPalette); Generator(const Generator &other) = delete; Generator &operator=(const Generator &other) = delete; bool writeHeader(); bool writeSource(); private: QString typeToString(structure::Type type) const; QString typeToDefaultValue(structure::Type type) const; QString valueAssignmentCode(structure::Value value) const; bool writeHeaderStyleNamespace(); bool writeStructsForwardDeclarations(); bool writeStructsDefinitions(); bool writePaletteDefinition(); bool writeRefsDeclarations(); bool writeIncludesInSource(); bool writeVariableDefinitions(); bool writeRefsDefinition(); bool writeSetPaletteColor(); bool writeVariableInit(); bool writePxValuesInit(); bool writeFontFamiliesInit(); bool writeIconValues(); bool writeIconsInit(); bool collectUniqueValues(); const structure::Module &module_; QString basePath_, baseName_; const common::ProjectInfo &project_; std::unique_ptr<common::CppFile> source_, header_; bool isPalette_ = false; QMap<int, bool> pxValues_; QMap<std::string, int> fontFamilies_; QMap<QString, int> iconMasks_; // icon file -> index std::map<QString, int, std::greater<QString>> paletteIndices_; std::vector<int> _scales = { 4, 5, 6, 8 }; // scale / 4 gives our 1.00, 1.25, 1.50, 2.00 std::vector<const char *> _scaleNames = { "dbisOne", "dbisOneAndQuarter", "dbisOneAndHalf", "dbisTwo" }; }; } // namespace style } // namespace codegen ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/style/main.cpp�����������������������������������������0000664�0000000�0000000�00000001126�13262451251�0023617�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include <QtCore/QCoreApplication> #include "codegen/style/options.h" #include "codegen/style/processor.h" int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); auto options = codegen::style::parseOptions(); if (options.inputPath.isEmpty()) { return -1; } codegen::style::Processor processor(options); return processor.launch(); } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/style/module.cpp���������������������������������������0000664�0000000�0000000�00000004325�13262451251�0024164�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "codegen/style/module.h" namespace codegen { namespace style { namespace structure { namespace { QString fullNameKey(const FullName &name) { return name.join('.'); } } // namespace Module::Module(const QString &fullpath) : fullpath_(fullpath) { } void Module::addIncluded(std::unique_ptr<Module> &&value) { included_.push_back(std::move(value)); } bool Module::addStruct(const Struct &value) { if (findStruct(value.name)) { return false; } structsByName_.insert(fullNameKey(value.name), structs_.size()); structs_.push_back(value); return true; } const Struct *Module::findStruct(const FullName &name) const { if (auto result = findStructInModule(name, *this)) { return result; } for (const auto &module : included_) { if (auto result = module->findStruct(name)) { return result; } } return nullptr; } bool Module::addVariable(const Variable &value) { if (findVariable(value.name)) { return false; } variablesByName_.insert(fullNameKey(value.name), variables_.size()); variables_.push_back(value); return true; } const Variable *Module::findVariable(const FullName &name, bool *outFromThisModule) const { if (auto result = findVariableInModule(name, *this)) { if (outFromThisModule) *outFromThisModule = true; return result; } for (const auto &module : included_) { if (auto result = module->findVariable(name)) { if (outFromThisModule) *outFromThisModule = false; return result; } } return nullptr; } const Struct *Module::findStructInModule(const FullName &name, const Module &module) const { auto index = module.structsByName_.value(fullNameKey(name), -1); if (index < 0) { return nullptr; } return &module.structs_.at(index); } const Variable *Module::findVariableInModule(const FullName &name, const Module &module) const { auto index = module.variablesByName_.value(fullNameKey(name), -1); if (index < 0) { return nullptr; } return &module.variables_.at(index); } } // namespace structure } // namespace style } // namespace codegen �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/style/module.h�����������������������������������������0000664�0000000�0000000�00000004522�13262451251�0023630�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <QtCore/QString> #include <QtCore/QList> #include <QtCore/QMap> #include <vector> #include "codegen/style/structure_types.h" namespace codegen { namespace style { namespace structure { class Module { public: explicit Module(const QString &fullpath); QString filepath() const { return fullpath_; } void addIncluded(std::unique_ptr<Module> &&value); bool hasIncludes() const { return !included_.empty(); } template <typename F> bool enumIncludes(F functor) const { for (const auto &module : included_) { if (!functor(*module)) { return false; } } return true; } // Returns false if there is a struct with such name already. bool addStruct(const Struct &value); // Returns nullptr if there is no such struct in result_ or any of included modules. const Struct *findStruct(const FullName &name) const; bool hasStructs() const { return !structs_.isEmpty(); } template <typename F> bool enumStructs(F functor) const { for (const auto &value : structs_) { if (!functor(value)) { return false; } } return true; } // Returns false if there is a variable with such name already. bool addVariable(const Variable &value); // Returns nullptr if there is no such variable in result_ or any of included modules. const Variable *findVariable(const FullName &name, bool *outFromThisModule = nullptr) const; bool hasVariables() const { return !variables_.isEmpty(); } template <typename F> bool enumVariables(F functor) const { for (const auto &value : variables_) { if (!functor(value)) { return false; } } return true; } explicit operator bool() const { return !fullpath_.isEmpty(); } const Struct *findStructInModule(const FullName &name, const Module &module) const; const Variable *findVariableInModule(const FullName &name, const Module &module) const; private: QString fullpath_; std::vector<std::unique_ptr<Module>> included_; QList<Struct> structs_; QList<Variable> variables_; QMap<QString, int> structsByName_; QMap<QString, int> variablesByName_; }; } // namespace structure } // namespace style } // namespace codegen ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/style/options.cpp��������������������������������������0000664�0000000�0000000�00000004533�13262451251�0024373�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "codegen/style/options.h" #include <ostream> #include <QtCore/QCoreApplication> #include <QtCore/QDir> #include "codegen/common/logging.h" namespace codegen { namespace style { namespace { constexpr int kErrorIncludePathExpected = 901; constexpr int kErrorOutputPathExpected = 902; constexpr int kErrorInputPathExpected = 903; constexpr int kErrorSingleInputPathExpected = 904; constexpr int kErrorWorkingPathExpected = 905; } // namespace using common::logError; Options parseOptions() { Options result; auto args = QCoreApplication::instance()->arguments(); for (int i = 1, count = args.size(); i < count; ++i) { // skip first auto &arg = args.at(i); // Include paths if (arg == "-I") { if (++i == count) { logError(kErrorIncludePathExpected, "Command Line") << "include path expected after -I"; return Options(); } else { result.includePaths.push_back(args.at(i)); } } else if (arg.startsWith("-I")) { result.includePaths.push_back(arg.mid(2)); // Output path } else if (arg == "-o") { if (++i == count) { logError(kErrorOutputPathExpected, "Command Line") << "output path expected after -o"; return Options(); } else { result.outputPath = args.at(i); } } else if (arg.startsWith("-o")) { result.outputPath = arg.mid(2); // Working path } else if (arg == "-w") { if (++i == count) { logError(kErrorWorkingPathExpected, "Command Line") << "working path expected after -w"; return Options(); } else { common::logSetWorkingPath(args.at(i)); } } else if (arg.startsWith("-w")) { common::logSetWorkingPath(arg.mid(2)); // Input path } else { if (result.inputPath.isEmpty()) { result.inputPath = arg; } else { logError(kErrorSingleInputPathExpected, "Command Line") << "only one input path expected"; return Options(); } } } if (result.inputPath.isEmpty()) { logError(kErrorInputPathExpected, "Command Line") << "input path expected"; return Options(); } result.isPalette = (QFileInfo(result.inputPath).suffix() == "palette"); return result; } } // namespace style } // namespace codegen ���������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/style/options.h����������������������������������������0000664�0000000�0000000�00000001134�13262451251�0024032�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <QtCore/QString> #include <QtCore/QStringList> namespace codegen { namespace style { struct Options { QStringList includePaths = { "." }; QString outputPath = "."; QString inputPath; bool isPalette = false; }; // Parsing failed if inputPath is empty in the result. Options parseOptions(); } // namespace style } // namespace codegen ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/style/parsed_file.cpp����������������������������������0000664�0000000�0000000�00000062252�13262451251�0025157�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "codegen/style/parsed_file.h" #include <iostream> #include <QtCore/QMap> #include <QtCore/QDir> #include <QtCore/QRegularExpression> #include "codegen/common/basic_tokenized_file.h" #include "codegen/common/logging.h" using BasicToken = codegen::common::BasicTokenizedFile::Token; using BasicType = BasicToken::Type; namespace codegen { namespace style { using structure::logFullName; namespace { constexpr int kErrorInIncluded = 801; constexpr int kErrorTypeMismatch = 802; constexpr int kErrorUnknownField = 803; constexpr int kErrorIdentifierNotFound = 804; constexpr int kErrorAlreadyDefined = 805; constexpr int kErrorBadString = 806; constexpr int kErrorIconDuplicate = 807; constexpr int kErrorBadIconModifier = 808; QString findInputFile(const Options &options) { for (const auto &dir : options.includePaths) { QString tryPath = QDir(dir).absolutePath() + '/' + options.inputPath; if (QFileInfo(tryPath).exists()) { return tryPath; } } return options.inputPath; } QString tokenValue(const BasicToken &token) { if (token.type == BasicType::String) { return token.value; } return token.original.toStringUnchecked(); } bool isValidColor(const QString &str) { auto len = str.size(); if (len != 6 && len != 8) { return false; } for (auto ch : str) { auto code = ch.unicode(); if ((code < '0' || code > '9') && (code < 'a' || code > 'f')) { return false; } } return true; } uchar toGray(uchar r, uchar g, uchar b) { return qMax(qMin(int(0.21 * r + 0.72 * g + 0.07 * b), 255), 0); } uchar readHexUchar(QChar ch) { auto code = ch.unicode(); return (code >= '0' && code <= '9') ? ((code - '0') & 0xFF) : ((code + 10 - 'a') & 0xFF); } uchar readHexUchar(QChar char1, QChar char2) { return ((readHexUchar(char1) & 0x0F) << 4) | (readHexUchar(char2) & 0x0F); } structure::data::color convertWebColor(const QString &str, const QString &fallback = QString()) { uchar r = 0, g = 0, b = 0, a = 255; if (isValidColor(str)) { r = readHexUchar(str.at(0), str.at(1)); g = readHexUchar(str.at(2), str.at(3)); b = readHexUchar(str.at(4), str.at(5)); if (str.size() == 8) { a = readHexUchar(str.at(6), str.at(7)); } } return { r, g, b, a, fallback }; } structure::data::color convertIntColor(int r, int g, int b, int a) { return { uchar(r & 0xFF), uchar(g & 0xFF), uchar(b & 0xFF), uchar(a & 0xFF) }; } std::string logType(const structure::Type &type) { if (type.tag == structure::TypeTag::Struct) { return "struct " + logFullName(type.name); } static auto builtInTypes = new QMap<structure::TypeTag, std::string> { { structure::TypeTag::Int , "int" }, { structure::TypeTag::Double , "double" }, { structure::TypeTag::Pixels , "pixels" }, { structure::TypeTag::String , "string" }, { structure::TypeTag::Color , "color" }, { structure::TypeTag::Point , "point" }, { structure::TypeTag::Size , "size" }, { structure::TypeTag::Align , "align" }, { structure::TypeTag::Margins , "margins" }, { structure::TypeTag::Font , "font" }, }; return builtInTypes->value(type.tag, "invalid"); } bool validateAnsiString(const QString &value) { for (auto ch : value) { if (ch.unicode() > 127) { return false; } } return true; } bool validateAlignString(const QString &value) { return QRegularExpression("^[a-z_]+$").match(value).hasMatch(); } } // namespace Modifier GetModifier(const QString &name) { static QMap<QString, Modifier> modifiers; if (modifiers.empty()) { modifiers.insert("invert", [](QImage &png100x, QImage &png200x) { png100x.invertPixels(); png200x.invertPixels(); }); modifiers.insert("flip_horizontal", [](QImage &png100x, QImage &png200x) { png100x = png100x.mirrored(true, false); png200x = png200x.mirrored(true, false); }); modifiers.insert("flip_vertical", [](QImage &png100x, QImage &png200x) { png100x = png100x.mirrored(false, true); png200x = png200x.mirrored(false, true); }); } return modifiers.value(name); } ParsedFile::ParsedFile(const Options &options) : filePath_(findInputFile(options)) , file_(filePath_) , options_(options) { } bool ParsedFile::read() { if (!file_.read()) { return false; } auto absolutePath = QFileInfo(filePath_).absoluteFilePath(); module_ = std::make_unique<structure::Module>(absolutePath); do { if (auto startToken = file_.getToken(BasicType::Name)) { if (tokenValue(startToken) == "using") { if (auto includedResult = readIncluded()) { module_->addIncluded(std::move(includedResult)); continue; } } else if (auto braceOpen = file_.getToken(BasicType::LeftBrace)) { if (auto structResult = readStruct(tokenValue(startToken))) { if (module_->addStruct(structResult)) { continue; } logError(kErrorAlreadyDefined) << "struct '" << logFullName(structResult.name) << "' already defined"; break; } } else if (auto colonToken = file_.getToken(BasicType::Colon)) { if (auto variableResult = readVariable(tokenValue(startToken))) { if (module_->addVariable(variableResult)) { continue; } logError(kErrorAlreadyDefined) << "variable '" << logFullName(variableResult.name) << "' already defined"; break; } } } if (file_.atEnd()) { break; } logErrorUnexpectedToken() << "using keyword, or struct definition, or variable definition"; } while (!failed()); if (failed()) { module_ = nullptr; } return !failed(); } common::LogStream ParsedFile::logErrorTypeMismatch() { return logError(kErrorTypeMismatch) << "type mismatch: "; } ParsedFile::ModulePtr ParsedFile::readIncluded() { if (auto usingFile = assertNextToken(BasicType::String)) { if (assertNextToken(BasicType::Semicolon)) { ParsedFile included(includedOptions(tokenValue(usingFile))); if (included.read()) { return included.getResult(); } else { logError(kErrorInIncluded) << "error while parsing '" << tokenValue(usingFile).toStdString() << "'"; } } } return nullptr; } structure::Struct ParsedFile::readStruct(const QString &name) { if (options_.isPalette) { logErrorUnexpectedToken() << "unique color variable for the palette"; return {}; } structure::Struct result = { composeFullName(name) }; do { if (auto fieldName = file_.getToken(BasicType::Name)) { if (auto field = readStructField(tokenValue(fieldName))) { result.fields.push_back(field); } } else if (assertNextToken(BasicType::RightBrace)) { if (result.fields.isEmpty()) { logErrorUnexpectedToken() << "at least one field in struct"; } break; } } while (!failed()); return result; } structure::Variable ParsedFile::readVariable(const QString &name) { structure::Variable result = { composeFullName(name) }; if (auto value = readValue()) { result.value = value; if (options_.isPalette && value.type().tag != structure::TypeTag::Color) { logErrorUnexpectedToken() << "unique color variable for the palette"; return {}; } if (value.type().tag != structure::TypeTag::Struct || !value.copyOf().empty()) { assertNextToken(BasicType::Semicolon); result.description = file_.getCurrentLineComment(); } } return result; } structure::StructField ParsedFile::readStructField(const QString &name) { structure::StructField result = { composeFullName(name) }; if (auto colonToken = assertNextToken(BasicType::Colon)) { if (auto type = readType()) { result.type = type; assertNextToken(BasicType::Semicolon); } } return result; } structure::Type ParsedFile::readType() { structure::Type result; if (auto nameToken = assertNextToken(BasicType::Name)) { auto name = tokenValue(nameToken); if (auto builtInType = typeNames_.value(name.toStdString())) { result = builtInType; } else { auto fullName = composeFullName(name); if (module_->findStruct(fullName)) { result.tag = structure::TypeTag::Struct; result.name = fullName; } else { logError(kErrorIdentifierNotFound) << "type name '" << logFullName(fullName) << "' not found"; } } } return result; } structure::Value ParsedFile::readValue() { if (auto colorValue = readColorValue()) { return colorValue; } else if (auto pointValue = readPointValue()) { return pointValue; } else if (auto sizeValue = readSizeValue()) { return sizeValue; } else if (auto alignValue = readAlignValue()) { return alignValue; } else if (auto marginsValue = readMarginsValue()) { return marginsValue; } else if (auto fontValue = readFontValue()) { return fontValue; } else if (auto iconValue = readIconValue()) { return iconValue; } else if (auto numericValue = readNumericValue()) { return numericValue; } else if (auto stringValue = readStringValue()) { return stringValue; } else if (auto structValue = readStructValue()) { return structValue; } else if (auto copyValue = readCopyValue()) { return copyValue; } else { logErrorUnexpectedToken() << "variable value"; } return {}; } structure::Value ParsedFile::readStructValue() { if (auto structName = file_.getToken(BasicType::Name)) { if (auto result = defaultConstructedStruct(composeFullName(tokenValue(structName)))) { if (file_.getToken(BasicType::LeftParenthesis)) { if (!readStructParents(result)) { return {}; } } if (assertNextToken(BasicType::LeftBrace)) { readStructValueInner(result); } return result; } file_.putBack(); } return {}; } structure::Value ParsedFile::defaultConstructedStruct(const structure::FullName &structName) { if (auto pattern = module_->findStruct(structName)) { QList<structure::data::field> fields; fields.reserve(pattern->fields.size()); for (const auto &fieldType : pattern->fields) { fields.push_back({ { // variable fieldType.name, { fieldType.type, Qt::Uninitialized }, // value }, structure::data::field::Status::Uninitialized, // status }); } return { structName, fields }; } return {}; } void ParsedFile::applyStructParent(structure::Value &result, const structure::FullName &parentName) { bool fromTheSameModule = false; if (auto parent = module_->findVariable(parentName, &fromTheSameModule)) { if (parent->value.type() != result.type()) { logErrorTypeMismatch() << "parent '" << logFullName(parentName) << "' has type '" << logType(parent->value.type()) << "' while child value has type " << logType(result.type()); return; } const auto *srcFields(parent->value.Fields()); auto *dstFields(result.Fields()); if (!srcFields || !dstFields) { logAssert(false) << "struct data check failed"; return; } logAssert(srcFields->size() == dstFields->size()) << "struct size check failed"; for (int i = 0, s = srcFields->size(); i != s; ++i) { const auto &srcField(srcFields->at(i)); auto &dstField((*dstFields)[i]); using Status = structure::data::field::Status; if (srcField.status == Status::Explicit || dstField.status == Status::Uninitialized) { const auto &srcValue(srcField.variable.value); auto &dstValue(dstField.variable.value); logAssert(srcValue.type() == dstValue.type()) << "struct field type check failed"; // Optimization: don't let the style files to contain unnamed inherited // icons from the other (included) style files, because they will // duplicate the binary data across different style c++ source files. // // Example: // a.style has "A: Struct { icon: icon { ..file.. } };" and // b.style has "B: Struct(A) { .. };" with non-overriden icon field. // Then both style_a.cpp and style_b.cpp will contain binary data of "file". if (!fromTheSameModule && srcValue.type().tag == structure::TypeTag::Icon && !srcValue.Icon().parts.empty() && srcValue.copyOf().isEmpty()) { logError(kErrorIconDuplicate) << "an unnamed icon field '" << logFullName(srcField.variable.name) << "' is inherited from parent '" << logFullName(parentName) << "'"; return; } dstValue = srcValue; dstField.status = Status::Implicit; } } } else { logError(kErrorIdentifierNotFound) << "parent '" << logFullName(parentName) << "' not found"; } } bool ParsedFile::readStructValueInner(structure::Value &result) { do { if (auto fieldName = file_.getToken(BasicType::Name)) { if (!assertNextToken(BasicType::Colon)) { return false; } if (auto field = readVariable(tokenValue(fieldName))) { if (!assignStructField(result, field)) { return false; } } } else if (assertNextToken(BasicType::RightBrace)) { return true; } } while (!failed()); return false; } bool ParsedFile::assignStructField(structure::Value &result, const structure::Variable &field) { auto *fields = result.Fields(); if (!fields) { logAssert(false) << "struct data check failed"; return false; } for (auto &already : *fields) { if (already.variable.name == field.name) { if (already.variable.value.type() == field.value.type()) { already.variable.value = field.value; already.status = structure::data::field::Status::Explicit; return true; } else { logErrorTypeMismatch() << "field '" << logFullName(already.variable.name) << "' has type '" << logType(already.variable.value.type()) << "' while value has type '" << logType(field.value.type()) << "'"; return false; } } } logError(kErrorUnknownField) << "field '" << logFullName(field.name) << "' was not found in struct of type '" << logType(result.type()) << "'"; return false; } bool ParsedFile::readStructParents(structure::Value &result) { do { if (auto parentName = assertNextToken(BasicType::Name)) { applyStructParent(result, composeFullName(tokenValue(parentName))); if (file_.getToken(BasicType::RightParenthesis)) { return true; } else { assertNextToken(BasicType::Comma); } } else { logErrorUnexpectedToken() << "struct variable parent"; } } while (!failed()); return false; } structure::Value ParsedFile::readPositiveValue() { auto numericToken = file_.getAnyToken(); if (numericToken.type == BasicType::Int) { return { structure::TypeTag::Int, tokenValue(numericToken).toInt() }; } else if (numericToken.type == BasicType::Double) { return { structure::TypeTag::Double, tokenValue(numericToken).toDouble() }; } else if (numericToken.type == BasicType::Name) { auto value = tokenValue(numericToken); auto match = QRegularExpression("^\\d+px$").match(value); if (match.hasMatch()) { return { structure::TypeTag::Pixels, value.mid(0, value.size() - 2).toInt() }; } } file_.putBack(); return {}; } structure::Value ParsedFile::readNumericValue() { if (auto value = readPositiveValue()) { return value; } else if (auto minusToken = file_.getToken(BasicType::Minus)) { if (auto positiveValue = readNumericValue()) { return { positiveValue.type().tag, -positiveValue.Int() }; } logErrorUnexpectedToken() << "numeric value"; } return {}; } structure::Value ParsedFile::readStringValue() { if (auto stringToken = file_.getToken(BasicType::String)) { auto value = tokenValue(stringToken); if (validateAnsiString(value)) { return { structure::TypeTag::String, stringToken.value.toStdString() }; } logError(kErrorBadString) << "unicode symbols are not supported"; } return {}; } structure::Value ParsedFile::readColorValue() { if (auto numberSign = file_.getToken(BasicType::Number)) { if (options_.isPalette) { auto color = file_.getAnyToken(); if (color.type == BasicType::Int || color.type == BasicType::Name) { auto chars = tokenValue(color).toLower(); if (isValidColor(chars)) { if (auto fallbackSeparator = file_.getToken(BasicType::Or)) { if (options_.isPalette) { if (auto fallbackName = file_.getToken(BasicType::Name)) { structure::FullName name = { tokenValue(fallbackName) }; if (auto variable = module_->findVariableInModule(name, *module_)) { return { convertWebColor(chars, tokenValue(fallbackName)) }; } else { logError(kErrorIdentifierNotFound) << "fallback color name"; } } else { logErrorUnexpectedToken() << "fallback color name"; } } else { logErrorUnexpectedToken() << "';', color fallbacks are only allowed in palette module"; } } else { return { convertWebColor(chars) }; } } } else { logErrorUnexpectedToken() << "color value in #ccc, #ccca, #cccccc or #ccccccaa format"; } } else { logErrorUnexpectedToken() << "color value alias, unique color values are only allowed in palette module"; } } else if (auto transparentName = file_.getToken(BasicType::Name)) { if (tokenValue(transparentName) == "transparent") { return { structure::data::color { 255, 255, 255, 0 } }; } file_.putBack(); } return {}; } structure::Value ParsedFile::readPointValue() { if (auto font = file_.getToken(BasicType::Name)) { if (tokenValue(font) == "point") { assertNextToken(BasicType::LeftParenthesis); auto x = readNumericOrNumericCopyValue(); assertNextToken(BasicType::Comma); auto y = readNumericOrNumericCopyValue(); if (x.type().tag != structure::TypeTag::Pixels || y.type().tag != structure::TypeTag::Pixels) { logErrorTypeMismatch() << "expected two px values for the point"; } assertNextToken(BasicType::RightParenthesis); return { structure::data::point { x.Int(), y.Int() } }; } file_.putBack(); } return {}; } structure::Value ParsedFile::readSizeValue() { if (auto font = file_.getToken(BasicType::Name)) { if (tokenValue(font) == "size") { assertNextToken(BasicType::LeftParenthesis); auto w = readNumericOrNumericCopyValue(); assertNextToken(BasicType::Comma); auto h = readNumericOrNumericCopyValue(); if (w.type().tag != structure::TypeTag::Pixels || h.type().tag != structure::TypeTag::Pixels) { logErrorTypeMismatch() << "expected two px values for the size"; } assertNextToken(BasicType::RightParenthesis); return { structure::data::size { w.Int(), h.Int() } }; } file_.putBack(); } return {}; } structure::Value ParsedFile::readAlignValue() { if (auto font = file_.getToken(BasicType::Name)) { if (tokenValue(font) == "align") { assertNextToken(BasicType::LeftParenthesis); auto align = tokenValue(assertNextToken(BasicType::Name)); assertNextToken(BasicType::RightParenthesis); if (validateAlignString(align)) { return { structure::TypeTag::Align, align.toStdString() }; } else { logError(kErrorBadString) << "bad align string"; } } file_.putBack(); } return {}; } structure::Value ParsedFile::readMarginsValue() { if (auto font = file_.getToken(BasicType::Name)) { if (tokenValue(font) == "margins") { assertNextToken(BasicType::LeftParenthesis); auto l = readNumericOrNumericCopyValue(); assertNextToken(BasicType::Comma); auto t = readNumericOrNumericCopyValue(); assertNextToken(BasicType::Comma); auto r = readNumericOrNumericCopyValue(); assertNextToken(BasicType::Comma); auto b = readNumericOrNumericCopyValue(); if (l.type().tag != structure::TypeTag::Pixels || t.type().tag != structure::TypeTag::Pixels || r.type().tag != structure::TypeTag::Pixels || b.type().tag != structure::TypeTag::Pixels) { logErrorTypeMismatch() << "expected four px values for the margins"; } assertNextToken(BasicType::RightParenthesis); return { structure::data::margins { l.Int(), t.Int(), r.Int(), b.Int() } }; } file_.putBack(); } return {}; } structure::Value ParsedFile::readFontValue() { if (auto font = file_.getToken(BasicType::Name)) { if (tokenValue(font) == "font") { assertNextToken(BasicType::LeftParenthesis); int flags = 0; structure::Value family, size; do { if (auto formatToken = file_.getToken(BasicType::Name)) { if (tokenValue(formatToken) == "bold") { flags |= structure::data::font::Bold; } else if (tokenValue(formatToken) == "italic") { flags |= structure::data::font::Italic; } else if (tokenValue(formatToken) == "underline") { flags |= structure::data::font::Underline; } else { file_.putBack(); } } if (auto familyValue = readStringOrStringCopyValue()) { family = familyValue; } else if (auto sizeValue = readNumericOrNumericCopyValue()) { size = sizeValue; } else if (file_.getToken(BasicType::RightParenthesis)) { break; } else { logErrorUnexpectedToken() << "font family, font size or ')'"; } } while (!failed()); if (size.type().tag != structure::TypeTag::Pixels) { logErrorTypeMismatch() << "px value for the font size expected"; } return { structure::data::font { family.String(), size.Int(), flags } }; } file_.putBack(); } return {}; } structure::Value ParsedFile::readIconValue() { if (auto font = file_.getToken(BasicType::Name)) { if (tokenValue(font) == "icon") { std::vector<structure::data::monoicon> parts; if (file_.getToken(BasicType::LeftBrace)) { // complex icon do { if (file_.getToken(BasicType::RightBrace)) { break; } else if (file_.getToken(BasicType::LeftBrace)) { if (auto part = readMonoIconFields()) { assertNextToken(BasicType::RightBrace); parts.push_back(part); file_.getToken(BasicType::Comma); continue; } return {}; } else { logErrorUnexpectedToken() << "icon part or '}'"; return {}; } } while (true); } else if (file_.getToken(BasicType::LeftParenthesis)) { // short icon if (auto theOnlyPart = readMonoIconFields()) { assertNextToken(BasicType::RightParenthesis); parts.push_back(theOnlyPart); } } return { structure::data::icon { parts } }; } file_.putBack(); } return {}; } structure::Value ParsedFile::readCopyValue() { if (auto copyName = file_.getToken(BasicType::Name)) { structure::FullName name = { tokenValue(copyName) }; if (auto variable = module_->findVariable(name)) { return variable->value.makeCopy(variable->name); } file_.putBack(); } return {}; } structure::Value ParsedFile::readNumericOrNumericCopyValue() { if (auto result = readNumericValue()) { return result; } else if (auto copy = readCopyValue()) { auto type = copy.type().tag; if (type == structure::TypeTag::Int || type == structure::TypeTag::Double || type == structure::TypeTag::Pixels) { return copy; } else { file_.putBack(); } } return {}; } structure::Value ParsedFile::readStringOrStringCopyValue() { if (auto result = readStringValue()) { return result; } else if (auto copy = readCopyValue()) { auto type = copy.type().tag; if (type == structure::TypeTag::String) { return copy; } else { file_.putBack(); } } return {}; } structure::data::monoicon ParsedFile::readMonoIconFields() { structure::data::monoicon result; result.filename = readMonoIconFilename(); if (!result.filename.isEmpty() && file_.getToken(BasicType::Comma)) { if (auto color = readValue()) { if (color.type().tag == structure::TypeTag::Color) { result.color = color; if (file_.getToken(BasicType::Comma)) { if (auto offset = readValue()) { if (offset.type().tag == structure::TypeTag::Point) { result.offset = offset; } else { logErrorUnexpectedToken() << "icon offset"; } } else { logErrorUnexpectedToken() << "icon offset"; } } else { result.offset = { structure::data::point { 0, 0 } }; } } else { logErrorUnexpectedToken() << "icon color"; } } else { logErrorUnexpectedToken() << "icon color"; } } return result; } QString ParsedFile::readMonoIconFilename() { if (auto filename = readValue()) { if (filename.type().tag == structure::TypeTag::String) { auto fullpath = QString::fromStdString(filename.String()); auto pathAndModifiers = fullpath.split('-'); auto filepath = pathAndModifiers[0]; auto modifiers = pathAndModifiers.mid(1); for (auto modifierName : modifiers) { if (!GetModifier(modifierName)) { logError(kErrorBadIconModifier) << "unknown modifier: " << modifierName.toStdString(); return QString(); } } for (auto &path : options_.includePaths) { QFileInfo fileinfo(path + '/' + filepath + ".png"); if (fileinfo.exists()) { return path + '/' + fullpath; } } for (auto &path : options_.includePaths) { QFileInfo fileinfo(path + "/icons/" + filepath + ".png"); if (fileinfo.exists()) { return path + "/icons/" + fullpath; } } logError(common::kErrorFileNotFound) << "could not open icon file '" << filename.String() << "'"; } else if (filename.type().tag == structure::TypeTag::Size) { return QString("size://%1,%2").arg(filename.Size().width).arg(filename.Size().height); } } logErrorUnexpectedToken() << "icon filename or rect size"; return QString(); } BasicToken ParsedFile::assertNextToken(BasicToken::Type type) { auto result = file_.getToken(type); if (!result) { logErrorUnexpectedToken() << type; } return result; } Options ParsedFile::includedOptions(const QString &filepath) { auto result = options_; result.inputPath = filepath; result.includePaths[0] = QFileInfo(filePath_).dir().absolutePath(); result.isPalette = (QFileInfo(filepath).suffix() == "palette"); return result; } // Compose context-dependent full name. structure::FullName ParsedFile::composeFullName(const QString &name) { return { name }; } } // namespace style } // namespace codegen ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/style/parsed_file.h������������������������������������0000664�0000000�0000000�00000007747�13262451251�0024634�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <memory> #include <string> #include <functional> #include <QImage> #include "codegen/common/basic_tokenized_file.h" #include "codegen/style/options.h" #include "codegen/style/module.h" namespace codegen { namespace style { using Modifier = std::function<void(QImage &png100x, QImage &png200x)>; Modifier GetModifier(const QString &name); // Parses an input file to the internal struct. class ParsedFile { public: explicit ParsedFile(const Options &options); ParsedFile(const ParsedFile &other) = delete; ParsedFile &operator=(const ParsedFile &other) = delete; bool read(); using ModulePtr = std::unique_ptr<structure::Module>; ModulePtr getResult() { return std::move(module_); } private: bool failed() const { return failed_ || file_.failed(); } // Log error to std::cerr with 'code' at the current position in file. common::LogStream logError(int code) { failed_ = true; return file_.logError(code); } common::LogStream logErrorUnexpectedToken() { failed_ = true; return file_.logErrorUnexpectedToken(); } common::LogStream logErrorTypeMismatch(); common::LogStream logAssert(bool assertion) { if (!assertion) { return logError(common::kErrorInternal) << "internal - "; } return common::LogStream(common::LogStream::Null); } // Helper methods for context-dependent reading. ModulePtr readIncluded(); structure::Struct readStruct(const QString &name); structure::Variable readVariable(const QString &name); structure::StructField readStructField(const QString &name); structure::Type readType(); structure::Value readValue(); structure::Value readStructValue(); structure::Value defaultConstructedStruct(const structure::FullName &name); void applyStructParent(structure::Value &result, const structure::FullName &parentName); bool readStructValueInner(structure::Value &result); bool assignStructField(structure::Value &result, const structure::Variable &field); bool readStructParents(structure::Value &result); // Simple methods for reading value types. structure::Value readPositiveValue(); structure::Value readNumericValue(); structure::Value readStringValue(); structure::Value readColorValue(); structure::Value readPointValue(); structure::Value readSizeValue(); structure::Value readAlignValue(); structure::Value readMarginsValue(); structure::Value readFontValue(); structure::Value readIconValue(); structure::Value readCopyValue(); structure::Value readNumericOrNumericCopyValue(); structure::Value readStringOrStringCopyValue(); structure::data::monoicon readMonoIconFields(); QString readMonoIconFilename(); // Read next token and fire unexpected token error if it is not of "type". using BasicToken = common::BasicTokenizedFile::Token; BasicToken assertNextToken(BasicToken::Type type); // Look through include directories in options_ and find absolute include path. Options includedOptions(const QString &filepath); // Compose context-dependent full name. structure::FullName composeFullName(const QString &name); QString filePath_; common::BasicTokenizedFile file_; Options options_; bool failed_ = false; ModulePtr module_; QMap<std::string, structure::Type> typeNames_ = { { "int" , { structure::TypeTag::Int } }, { "double" , { structure::TypeTag::Double } }, { "pixels" , { structure::TypeTag::Pixels } }, { "string" , { structure::TypeTag::String } }, { "color" , { structure::TypeTag::Color } }, { "point" , { structure::TypeTag::Point } }, { "size" , { structure::TypeTag::Size } }, { "align" , { structure::TypeTag::Align } }, { "margins" , { structure::TypeTag::Margins } }, { "font" , { structure::TypeTag::Font } }, { "icon" , { structure::TypeTag::Icon } }, }; }; } // namespace style } // namespace codegen �������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/style/processor.cpp������������������������������������0000664�0000000�0000000�00000003360�13262451251�0024714�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "codegen/style/processor.h" #include <QtCore/QDir> #include <QtCore/QFileInfo> #include "codegen/common/cpp_file.h" #include "codegen/style/parsed_file.h" #include "codegen/style/generator.h" namespace codegen { namespace style { namespace { constexpr int kErrorCantWritePath = 821; QString destFileBaseName(const structure::Module &module) { return "style_" + QFileInfo(module.filepath()).baseName(); } } // namespace Processor::Processor(const Options &options) : parser_(std::make_unique<ParsedFile>(options)) , options_(options) { } int Processor::launch() { if (!parser_->read()) { return -1; } auto module = parser_->getResult(); if (!write(*module)) { return -1; } return 0; } bool Processor::write(const structure::Module &module) const { bool forceReGenerate = false; QDir dir(options_.outputPath); if (!dir.mkpath(".")) { common::logError(kErrorCantWritePath, "Command Line") << "can not open path for writing: " << dir.absolutePath().toStdString(); return false; } QFileInfo srcFile(module.filepath()); QString dstFilePath = dir.absolutePath() + '/' + (options_.isPalette ? "palette" : destFileBaseName(module)); common::ProjectInfo project = { "codegen_style", srcFile.fileName(), forceReGenerate }; Generator generator(module, dstFilePath, project, options_.isPalette); if (!generator.writeHeader()) { return false; } if (!generator.writeSource()) { return false; } return true; } Processor::~Processor() = default; } // namespace style } // namespace codegen ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/style/processor.h��������������������������������������0000664�0000000�0000000�00000001734�13262451251�0024364�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <memory> #include <QtCore/QString> #include "codegen/style/options.h" namespace codegen { namespace style { namespace structure { class Module; } // namespace structure class ParsedFile; // Walks through a file, parses it and parses dependency files if necessary. // Uses Generator class to produce the final output. class Processor { public: explicit Processor(const Options &options); Processor(const Processor &other) = delete; Processor &operator=(const Processor &other) = delete; // Returns 0 on success. int launch(); ~Processor(); private: bool write(const structure::Module &module) const; std::unique_ptr<ParsedFile> parser_; const Options &options_; }; } // namespace style } // namespace codegen ������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/style/structure_types.cpp������������������������������0000664�0000000�0000000�00000012176�13262451251�0026166�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "codegen/style/structure_types.h" namespace codegen { namespace style { namespace structure { struct Value::DataTypes { class TInt : public DataBase { public: TInt(int value) : value_(value) { } int Int() const override { return value_; } private: int value_; }; class TDouble : public DataBase { public: TDouble(double value) : value_(value) { } double Double() const override { return value_; } private: double value_; }; class TString : public DataBase { public: TString(std::string value) : value_(value) { } std::string String() const override { return value_; } private: std::string value_; }; class TPoint : public DataBase { public: TPoint(data::point value) : value_(value) { } data::point Point() const override { return value_; } private: data::point value_; }; class TSize : public DataBase { public: TSize(data::size value) : value_(value) { } data::size Size() const override { return value_; } private: data::size value_; }; class TColor : public DataBase { public: TColor(data::color value) : value_(value) { } data::color Color() const override { return value_; } private: data::color value_; }; class TMargins : public DataBase { public: TMargins(data::margins value) : value_(value) { } data::margins Margins() const override { return value_; } private: data::margins value_; }; class TFont : public DataBase { public: TFont(data::font value) : value_(value) { } data::font Font() const override { return value_; } private: data::font value_; }; class TIcon : public DataBase { public: TIcon(data::icon value) : value_(value) { } data::icon Icon() const override { return value_; } private: data::icon value_; }; class TFields : public DataBase { public: TFields(data::fields value) : value_(value) { } const data::fields *Fields() const override { return &value_; } data::fields *Fields() override { return &value_; } private: data::fields value_; }; }; Value::Value() : Value(TypeTag::Invalid, std::make_shared<DataBase>()) { } Value::Value(data::point value) : Value(TypeTag::Point, std::make_shared<DataTypes::TPoint>(value)) { } Value::Value(data::size value) : Value(TypeTag::Size, std::make_shared<DataTypes::TSize>(value)) { } Value::Value(data::color value) : Value(TypeTag::Color, std::make_shared<DataTypes::TColor>(value)) { } Value::Value(data::margins value) : Value(TypeTag::Margins, std::make_shared<DataTypes::TMargins>(value)) { } Value::Value(data::font value) : Value(TypeTag::Font, std::make_shared<DataTypes::TFont>(value)) { } Value::Value(data::icon value) : Value(TypeTag::Icon, std::make_shared<DataTypes::TIcon>(value)) { } Value::Value(const FullName &type, data::fields value) : type_ { TypeTag::Struct, type } , data_(std::make_shared<DataTypes::TFields>(value)) { } Value::Value(TypeTag type, double value) : Value(type, std::make_shared<DataTypes::TDouble>(value)) { if (type_.tag != TypeTag::Double) { type_.tag = TypeTag::Invalid; data_ = std::make_shared<DataBase>(); } } Value::Value(TypeTag type, int value) : Value(type, std::make_shared<DataTypes::TInt>(value)) { if (type_.tag != TypeTag::Int && type_.tag != TypeTag::Pixels) { type_.tag = TypeTag::Invalid; data_ = std::make_shared<DataBase>(); } } Value::Value(TypeTag type, std::string value) : Value(type, std::make_shared<DataTypes::TString>(value)) { if (type_.tag != TypeTag::String && type_.tag != TypeTag::Align) { type_.tag = TypeTag::Invalid; data_ = std::make_shared<DataBase>(); } } Value::Value(Type type, Qt::Initialization) : type_(type) { switch (type_.tag) { case TypeTag::Invalid: data_ = std::make_shared<DataBase>(); break; case TypeTag::Int: data_ = std::make_shared<DataTypes::TInt>(0); break; case TypeTag::Double: data_ = std::make_shared<DataTypes::TDouble>(0.); break; case TypeTag::Pixels: data_ = std::make_shared<DataTypes::TInt>(0); break; case TypeTag::String: data_ = std::make_shared<DataTypes::TString>(""); break; case TypeTag::Color: data_ = std::make_shared<DataTypes::TColor>(data::color { 0, 0, 0, 255 }); break; case TypeTag::Point: data_ = std::make_shared<DataTypes::TPoint>(data::point { 0, 0 }); break; case TypeTag::Size: data_ = std::make_shared<DataTypes::TSize>(data::size { 0, 0 }); break; case TypeTag::Align: data_ = std::make_shared<DataTypes::TString>("topleft"); break; case TypeTag::Margins: data_ = std::make_shared<DataTypes::TMargins>(data::margins { 0, 0, 0, 0 }); break; case TypeTag::Font: data_ = std::make_shared<DataTypes::TFont>(data::font { "", 13, 0 }); break; case TypeTag::Icon: data_ = std::make_shared<DataTypes::TIcon>(data::icon {}); break; case TypeTag::Struct: data_ = std::make_shared<DataTypes::TFields>(data::fields {}); break; } } Value::Value(TypeTag type, std::shared_ptr<DataBase> &&data) : type_ { type }, data_(std::move(data)) { } } // namespace structure } // namespace style } // namespace codegen ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/codegen/style/structure_types.h��������������������������������0000664�0000000�0000000�00000011223�13262451251�0025623�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <memory> #include <vector> #include <QtCore/QString> #include <QtCore/QStringList> #include <QtCore/QtMath> namespace codegen { namespace style { namespace structure { // List of names, like overview.document.bg using FullName = QStringList; inline std::string logFullName(const FullName &name) { return name.join('.').toStdString(); } struct Variable; class Value; enum class TypeTag { Invalid, Int, Double, Pixels, String, Color, Point, Size, Align, Margins, Font, Icon, Struct, }; struct Type { TypeTag tag; FullName name; // only for type == ClassType::Struct explicit operator bool() const { return (tag != TypeTag::Invalid); } }; inline bool operator==(const Type &a, const Type &b) { return (a.tag == b.tag) && (a.name == b.name); } inline bool operator!=(const Type &a, const Type &b) { return !(a == b); } namespace data { inline int pxAdjust(int value, int scale) { if (value < 0) { return -pxAdjust(-value, scale); } return qFloor((value * scale / 4.) + 0.1); } struct point { int x, y; }; struct size { int width, height; }; struct color { uchar red, green, blue, alpha; QString fallback; }; struct margins { int left, top, right, bottom; }; struct font { enum Flag { Bold = 0x01, Italic = 0x02, Underline = 0x04, }; std::string family; int size; int flags; }; struct monoicon; struct icon { std::vector<monoicon> parts; }; struct field; // defined after Variable is defined using fields = QList<field>; } // namespace data class Value { public: Value(); Value(data::point value); Value(data::size value); Value(data::color value); Value(data::margins value); Value(data::font value); Value(data::icon value); Value(const FullName &type, data::fields value); // Can be only double. Value(TypeTag type, double value); // Can be int / pixels. Value(TypeTag type, int value); // Can be string / align. Value(TypeTag type, std::string value); // Default constructed value (uninitialized). Value(Type type, Qt::Initialization); Type type() const { return type_; } int Int() const { return data_->Int(); } double Double() const { return data_->Double(); } std::string String() const { return data_->String(); } data::point Point() const { return data_->Point(); } data::size Size() const { return data_->Size(); }; data::color Color() const { return data_->Color(); }; data::margins Margins() const { return data_->Margins(); }; data::font Font() const { return data_->Font(); }; data::icon Icon() const { return data_->Icon(); }; const data::fields *Fields() const { return data_->Fields(); }; data::fields *Fields() { return data_->Fields(); }; explicit operator bool() const { return type_.tag != TypeTag::Invalid; } Value makeCopy(const FullName ©Of) const { Value result(*this); result.copyOf_ = copyOf; return result; } const FullName ©Of() const { return copyOf_; } private: class DataBase { public: virtual int Int() const { return 0; } virtual double Double() const { return 0.; } virtual std::string String() const { return std::string(); } virtual data::point Point() const { return {}; }; virtual data::size Size() const { return {}; }; virtual data::color Color() const { return {}; }; virtual data::margins Margins() const { return {}; }; virtual data::font Font() const { return {}; }; virtual data::icon Icon() const { return {}; }; virtual const data::fields *Fields() const { return nullptr; }; virtual data::fields *Fields() { return nullptr; }; virtual ~DataBase() { } }; struct DataTypes; Value(TypeTag type, std::shared_ptr<DataBase> &&data); Type type_; std::shared_ptr<DataBase> data_; FullName copyOf_; // for copies of existing named values }; struct Variable { FullName name; Value value; QString description; explicit operator bool() const { return !name.isEmpty(); } }; namespace data { struct field { enum class Status { Uninitialized, Implicit, Explicit }; Variable variable; Status status; }; struct monoicon { QString filename; Value color; Value offset; explicit operator bool() const { return !filename.isEmpty(); } }; } // namespace data struct StructField { FullName name; Type type; explicit operator bool() const { return !name.isEmpty(); } }; struct Struct { FullName name; QList<StructField> fields; explicit operator bool() const { return !name.isEmpty(); } }; } // namespace structure } // namespace style } // namespace codegen �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/config.h�������������������������������������������������������0000664�0000000�0000000�00000025534�13262451251�0021052�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "core/version.h" #include "settings.h" constexpr str_const AppNameOld = "Telegram Win (Unofficial)"; constexpr str_const AppName = "Telegram Desktop"; constexpr str_const AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"; // used in updater.cpp and Setup.iss for Windows constexpr str_const AppFile = "Telegram"; enum { MTPShortBufferSize = 65535, // of ints, 256 kb MTPPacketSizeMax = 67108864, // 64 mb MTPIdsBufferSize = 400, // received msgIds and wereAcked msgIds count stored MTPCheckResendTimeout = 10000, // how much time passed from send till we resend request or check it's state, in ms MTPCheckResendWaiting = 1000, // how much time to wait for some more requests, when resending request or checking it's state, in ms MTPAckSendWaiting = 10000, // how much time to wait for some more requests, when sending msg acks MTPResendThreshold = 1, // how much ints should message contain for us not to resend, but to check it's state MTPContainerLives = 600, // container lives 10 minutes in haveSent map MTPMinReceiveDelay = 4000, // 4 seconds MTPMaxReceiveDelay = 64000, // 64 seconds MTPMinConnectDelay = 1000, // tcp connect should take less then 1 second MTPMaxConnectDelay = 8000, // tcp connect should take 8 seconds max MTPConnectionOldTimeout = 192000, // 192 seconds MTPTcpConnectionWaitTimeout = 2000, // 2 seconds waiting for tcp, until we accept http MTPIPv4ConnectionWaitTimeout = 1000, // 1 seconds waiting for ipv4, until we accept ipv6 MTPKillFileSessionTimeout = 5000, // how much time without upload / download causes additional session kill MTPDebugBufferSize = 1024 * 1024, // 1 mb start size MaxUsersPerInvite = 100, // max users in one super group invite request MTPPingDelayDisconnect = 60, // 1 min MTPPingSendAfterAuto = 30, // send new ping starting from 30 seconds (add to existing container) MTPPingSendAfter = 45, // send new ping after 45 seconds without ping MTPChannelGetDifferenceLimit = 100, MaxSelectedItems = 100, MaxPhoneCodeLength = 4, // max length of country phone code MaxPhoneTailLength = 32, // rest of the phone number, without country code (seen 12 at least), need more for service numbers MaxScrollSpeed = 37, // 37px per 15ms while select-by-drag FingerAccuracyThreshold = 3, // touch flick ignore 3px MaxScrollAccelerated = 4000, // 4000px per second MaxScrollFlick = 2500, // 2500px per second LocalEncryptIterCount = 4000, // key derivation iteration count LocalEncryptNoPwdIterCount = 4, // key derivation iteration count without pwd (not secure anyway) LocalEncryptSaltSize = 32, // 256 bit AnimationTimerDelta = 7, ClipThreadsCount = 8, AverageGifSize = 320 * 240, WaitBeforeGifPause = 200, // wait 200ms for gif draw before pausing it RecentInlineBotsLimit = 10, AVBlockSize = 4096, // 4Kb for ffmpeg blocksize SaveWindowPositionTimeout = 1000, // 1 sec AutoSearchTimeout = 900, // 0.9 secs SearchPerPage = 50, SearchManyPerPage = 100, LinksOverviewPerPage = 12, MediaOverviewStartPerPage = 5, AudioVoiceMsgMaxLength = 100 * 60, // 100 minutes AudioVoiceMsgUpdateView = 100, // 100ms AudioVoiceMsgChannels = 2, // stereo AudioVoiceMsgBufferSize = 256 * 1024, // 256 Kb buffers (1.3 - 3.0 secs) StickerMaxSize = 2048, // 2048x2048 is a max image size for sticker MaxZoomLevel = 7, // x8 ZoomToScreenLevel = 1024, // just constant ShortcutsCountLimit = 256, // how many shortcuts can be in json file PreloadHeightsCount = 3, // when 3 screens to scroll left make a preload request SearchPeopleLimit = 5, UsernameCheckTimeout = 200, MaxPhotoCaption = 200, MaxMessageSize = 4096, WriteMapTimeout = 1000, SaveDraftTimeout = 1000, // save draft after 1 secs of not changing text SaveDraftAnywayTimeout = 5000, // or save anyway each 5 secs SaveCloudDraftIdleTimeout = 14000, // save draft to the cloud after 14 more seconds SetOnlineAfterActivity = 30, // user with hidden last seen stays online for such amount of seconds in the interface ServiceUserId = 777000, WebPageUserId = 701000, CacheBackgroundTimeout = 3000, // cache background scaled image after 3s BackgroundsInRow = 3, UpdateDelayConstPart = 8 * 3600, // 8 hour min time between update check requests UpdateDelayRandPart = 8 * 3600, // 8 hour max - min time between update check requests WrongPasscodeTimeout = 1500, SessionsShortPollTimeout = 60000, ChoosePeerByDragTimeout = 1000, // 1 second mouse not moved to choose dialog when dragging a file }; inline bool isNotificationsUser(uint64 id) { return (id == 333000) || (id == ServiceUserId); } inline bool isServiceUser(uint64 id) { return !(id % 1000);// (id == 333000) || (id == ServiceUserId); } #ifdef Q_OS_WIN inline const GUID &cGUID() { #ifndef OS_MAC_STORE static const GUID gGuid = { 0x87a94ab0, 0xe370, 0x4cde, { 0x98, 0xd3, 0xac, 0xc1, 0x10, 0xc5, 0x96, 0x7d } }; #else // OS_MAC_STORE static const GUID gGuid = { 0xe51fb841, 0x8c0b, 0x4ef9, { 0x9e, 0x9e, 0x5a, 0x0, 0x78, 0x56, 0x76, 0x27 } }; #endif // OS_MAC_STORE return gGuid; } #endif inline const char *cGUIDStr() { #ifndef OS_MAC_STORE static const char *gGuidStr = "{87A94AB0-E370-4cde-98D3-ACC110C5967D}"; #else // OS_MAC_STORE static const char *gGuidStr = "{E51FB841-8C0B-4EF9-9E9E-5A0078567627}"; #endif // OS_MAC_STORE return gGuidStr; } struct BuiltInDc { int id; const char *ip; int port; }; static const BuiltInDc _builtInDcs[] = { { 1, "149.154.175.50", 443 }, { 2, "149.154.167.51", 443 }, { 3, "149.154.175.100", 443 }, { 4, "149.154.167.91", 443 }, { 5, "149.154.171.5", 443 } }; static const BuiltInDc _builtInDcsIPv6[] = { { 1, "2001:b28:f23d:f001::a", 443 }, { 2, "2001:67c:4e8:f002::a", 443 }, { 3, "2001:b28:f23d:f003::a", 443 }, { 4, "2001:67c:4e8:f004::a", 443 }, { 5, "2001:b28:f23f:f005::a", 443 } }; static const BuiltInDc _builtInTestDcs[] = { { 1, "149.154.175.10", 443 }, { 2, "149.154.167.40", 443 }, { 3, "149.154.175.117", 443 } }; static const BuiltInDc _builtInTestDcsIPv6[] = { { 1, "2001:b28:f23d:f001::e", 443 }, { 2, "2001:67c:4e8:f002::e", 443 }, { 3, "2001:b28:f23d:f003::e", 443 } }; inline const BuiltInDc *builtInDcs() { return cTestMode() ? _builtInTestDcs : _builtInDcs; } inline int builtInDcsCount() { return (cTestMode() ? sizeof(_builtInTestDcs) : sizeof(_builtInDcs)) / sizeof(BuiltInDc); } inline const BuiltInDc *builtInDcsIPv6() { return cTestMode() ? _builtInTestDcsIPv6 : _builtInDcsIPv6; } inline int builtInDcsCountIPv6() { return (cTestMode() ? sizeof(_builtInTestDcsIPv6) : sizeof(_builtInDcsIPv6)) / sizeof(BuiltInDc); } static const char *UpdatesPublicKey = "\ -----BEGIN RSA PUBLIC KEY-----\n\ MIGJAoGBAMA4ViQrjkPZ9xj0lrer3r23JvxOnrtE8nI69XLGSr+sRERz9YnUptnU\n\ BZpkIfKaRcl6XzNJiN28cVwO1Ui5JSa814UAiDHzWUqCaXUiUEQ6NmNTneiGx2sQ\n\ +9PKKlb8mmr3BB9A45ZNwLT6G9AK3+qkZLHojeSA+m84/a6GP4svAgMBAAE=\n\ -----END RSA PUBLIC KEY-----\ "; static const char *UpdatesPublicAlphaKey = "\ -----BEGIN RSA PUBLIC KEY-----\n\ MIGJAoGBALWu9GGs0HED7KG7BM73CFZ6o0xufKBRQsdnq3lwA8nFQEvmdu+g/I1j\n\ 0LQ+0IQO7GW4jAgzF/4+soPDb6uHQeNFrlVx1JS9DZGhhjZ5rf65yg11nTCIHZCG\n\ w/CVnbwQOw0g5GBwwFV3r0uTTvy44xx8XXxk+Qknu4eBCsmrAFNnAgMBAAE=\n\ -----END RSA PUBLIC KEY-----\ "; #ifdef CUSTOM_API_ID #include "../../../TelegramPrivate/custom_api_id.h" // Custom API id and API hash #else static const int32 ApiId = 17349; static const char *ApiHash = "344583e45741c457fe1862106095a5eb"; #endif #if Q_BYTE_ORDER == Q_BIG_ENDIAN #error "Only little endian is supported!" #endif // Q_BYTE_ORDER == Q_BIG_ENDIAN #ifndef BETA_VERSION_MACRO #error "Beta version macro is not defined." #endif #if (defined CUSTOM_API_ID) && (BETA_VERSION_MACRO > 0ULL) #include "../../../TelegramPrivate/beta_private.h" // private key for downloading closed betas #else static const char *BetaPrivateKey = ""; #endif inline const char *cApiDeviceModel() { #ifdef Q_OS_WIN return "PC"; #elif defined Q_OS_MAC return "Mac"; #elif defined Q_OS_LINUX return "PC"; #endif } inline const char *cApiSystemVersion() { #ifdef Q_OS_WIN return "Windows"; #elif defined Q_OS_MAC return "OS X"; #elif defined Q_OS_LINUX return "Linux"; #endif } extern QString gKeyFile; inline const QString &cDataFile() { if (!gKeyFile.isEmpty()) return gKeyFile; static const QString res(qsl("data")); return res; } inline const QString &cTempDir() { static const QString res = cWorkingDir() + qsl("tdata/tdld/"); return res; } enum { DialogsFirstLoad = 20, // first dialogs part size requested DialogsPerPage = 500, // next dialogs part size UseBigFilesFrom = 10 * 1024 * 1024, // mtp big files methods used for files greater than 10mb UploadPartSize = 32 * 1024, // 32kb for photo DocumentMaxPartsCount = 3000, // no more than 3000 parts DocumentUploadPartSize0 = 32 * 1024, // 32kb for tiny document ( < 1mb ) DocumentUploadPartSize1 = 64 * 1024, // 64kb for little document ( <= 32mb ) DocumentUploadPartSize2 = 128 * 1024, // 128kb for small document ( <= 375mb ) DocumentUploadPartSize3 = 256 * 1024, // 256kb for medium document ( <= 750mb ) DocumentUploadPartSize4 = 512 * 1024, // 512kb for large document ( <= 1500mb ) UploadRequestInterval = 500, // one part each half second, if not uploaded faster MaxPhotosInMemory = 50, // try to clear some memory after 50 photos are created NoUpdatesTimeout = 60 * 1000, // if nothing is received in 1 min we ping NoUpdatesAfterSleepTimeout = 60 * 1000, // if nothing is received in 1 min when was a sleepmode we ping WaitForSkippedTimeout = 1000, // 1s wait for skipped seq or pts in updates WaitForChannelGetDifference = 1000, // 1s wait after show channel history before sending getChannelDifference MemoryForImageCache = 64 * 1024 * 1024, // after 64mb of unpacked images we try to clear some memory NotifySettingSaveTimeout = 1000, // wait 1 second before saving notify setting to server UpdateChunk = 100 * 1024, // 100kb parts when downloading the update IdleMsecs = 60 * 1000, // after 60secs without user input we think we are idle SendViewsTimeout = 1000, // send views each second ForwardOnAdd = 100, // how many messages from chat history server should forward to user, that was added to this chat }; inline const QRegularExpression &cRussianLetters() { static QRegularExpression regexp(QString::fromUtf8("[а-яА-ЯёЁ]")); return regexp; } inline const QStringList &cImgExtensions() { static QStringList result; if (result.isEmpty()) { result.reserve(4); result.push_back(qsl(".jpg")); result.push_back(qsl(".jpeg")); result.push_back(qsl(".png")); result.push_back(qsl(".gif")); } return result; } inline const QStringList &cExtensionsForCompress() { static QStringList result; if (result.isEmpty()) { result.push_back(qsl(".jpg")); result.push_back(qsl(".jpeg")); result.push_back(qsl(".png")); } return result; } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/core/����������������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0020353�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/core/basic_types.h���������������������������������������������0000664�0000000�0000000�00000001327�13262451251�0023034�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <string> #include <exception> #include <memory> #include <ctime> #include "base/build_config.h" #include "base/ordered_set.h" using gsl::not_null; //using uchar = unsigned char; // Qt has uchar using int16 = qint16; using uint16 = quint16; using int32 = qint32; using uint32 = quint32; using int64 = qint64; using uint64 = quint64; using float32 = float; using float64 = double; #define qsl(s) QStringLiteral(s) #define qstr(s) QLatin1String(s, sizeof(s) - 1) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/core/changelogs.cpp��������������������������������������������0000664�0000000�0000000�00000012072�13262451251�0023173�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "core/changelogs.h" #include "storage/localstorage.h" #include "lang/lang_keys.h" #include "data/data_session.h" #include "mainwindow.h" #include "apiwrap.h" namespace Core { namespace { std::map<int, const char*> AlphaLogs() { return { { 1001024, "\xE2\x80\x94 Radically improved navigation. " "New side panel on the right with quick access to " "shared media and group members.\n" "\xE2\x80\x94 Pinned Messages. If you are a channel admin, " "pin messages to focus your subscribers\xE2\x80\x99 attention " "on important announcements.\n" "\xE2\x80\x94 Also supported clearing history in supergroups " "and added a host of minor improvements." }, { 1001026, "\xE2\x80\x94 Admin badges in supergroup messages.\n" "\xE2\x80\x94 Fix crashing on launch in OS X 10.6.\n" "\xE2\x80\x94 Bug fixes and other minor improvements." }, { 1001027, "\xE2\x80\x94 Saved Messages. Bookmark messages by forwarding them " "to \xE2\x80\x9C""Saved Messages\xE2\x80\x9D. " "Access them from the Chats list or from the side menu." }, { 1002002, "\xE2\x80\x94 Grouped photos and videos are displayed as albums." }, { 1002004, "\xE2\x80\x94 Group media into an album " "when sharing multiple photos and videos.\n" "\xE2\x80\x94 Bug fixes and other minor improvements." }, { 1002005, "\xE2\x80\x94 When viewing a photo from an album, " "you'll see other pictures from the same group " "as thumbnails in the lower part of the screen.\n" "\xE2\x80\x94 When composing an album paste " "additional media from the clipboard.\n" "\xE2\x80\x94 Bug fixes and other minor improvements." }, { 1002007, "\xE2\x80\x94 Use fast reply button in group chats.\n" "\xE2\x80\x94 Select a message you want to reply to by " "pressing Ctrl+Up and Ctrl+Down." }, { 1002009, "\xE2\x80\x94 Quick Reply. " "Double click next to any message for a quick reply.\n" "\xE2\x80\x94 Search for Stickers. " "Click on the new search icon to access " "your sticker sets or find new ones." } }; } QString FormatVersionDisplay(int version) { return QString::number(version / 1000000) + '.' + QString::number((version % 1000000) / 1000) + ((version % 1000) ? ('.' + QString::number(version % 1000)) : QString()); } QString FormatVersionPrecise(int version) { return QString::number(version / 1000000) + '.' + QString::number((version % 1000000) / 1000) + '.' + QString::number(version % 1000); } } // namespace Changelogs::Changelogs(not_null<AuthSession*> session, int oldVersion) : _session(session) , _oldVersion(oldVersion) { _chatsSubscription = subscribe( _session->data().moreChatsLoaded(), [this] { requestCloudLogs(); }); } std::unique_ptr<Changelogs> Changelogs::Create( not_null<AuthSession*> session) { const auto oldVersion = Local::oldMapVersion(); return (oldVersion > 0 && oldVersion < AppVersion) ? std::make_unique<Changelogs>(session, oldVersion) : nullptr; } void Changelogs::requestCloudLogs() { unsubscribe(base::take(_chatsSubscription)); const auto callback = [this](const MTPUpdates &result) { _session->api().applyUpdates(result); auto resultEmpty = true; switch (result.type()) { case mtpc_updateShortMessage: case mtpc_updateShortChatMessage: case mtpc_updateShort: resultEmpty = false; break; case mtpc_updatesCombined: resultEmpty = result.c_updatesCombined().vupdates.v.isEmpty(); break; case mtpc_updates: resultEmpty = result.c_updates().vupdates.v.isEmpty(); break; case mtpc_updatesTooLong: case mtpc_updateShortSentMessage: LOG(("API Error: Bad updates type in app changelog.")); break; } if (resultEmpty) { addLocalLogs(); } }; _session->api().requestChangelog( FormatVersionPrecise(_oldVersion), base::lambda_guarded(this, callback)); } void Changelogs::addLocalLogs() { if (cAlphaVersion() || cBetaVersion()) { addAlphaLogs(); } if (!_addedSomeLocal) { const auto text = lng_new_version_wrap( lt_version, str_const_toString(AppVersionStr), lt_changes, lang(lng_new_version_minor), lt_link, qsl("https://desktop.telegram.org/changelog")); addLocalLog(text.trimmed()); } } void Changelogs::addLocalLog(const QString &text) { auto textWithEntities = TextWithEntities{ text }; TextUtilities::ParseEntities(textWithEntities, TextParseLinks); App::wnd()->serviceNotification( textWithEntities, MTP_messageMediaEmpty(), unixtime()); _addedSomeLocal = true; }; void Changelogs::addAlphaLogs() { for (const auto[version, changes] : AlphaLogs()) { addAlphaLog(version, changes); } } void Changelogs::addAlphaLog(int changeVersion, const char *changes) { if (_oldVersion >= changeVersion) { return; } const auto version = FormatVersionDisplay(changeVersion); const auto text = qsl("New in version %1:\n\n").arg(version) + QString::fromUtf8(changes).trimmed(); addLocalLog(text); } } // namespace Core ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/core/changelogs.h����������������������������������������������0000664�0000000�0000000�00000001553�13262451251�0022642�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "base/weak_ptr.h" class AuthSession; namespace Core { class Changelogs : public base::has_weak_ptr, private base::Subscriber { public: Changelogs(not_null<AuthSession*> session, int oldVersion); static std::unique_ptr<Changelogs> Create( not_null<AuthSession*> session); private: void requestCloudLogs(); void addLocalLogs(); void addLocalLog(const QString &text); void addAlphaLogs(); void addAlphaLog(int changeVersion, const char *changes); const not_null<AuthSession*> _session; const int _oldVersion = 0; int _chatsSubscription = 0; bool _addedSomeLocal = false; }; } // namespace Core �����������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/core/click_handler.cpp�����������������������������������������0000664�0000000�0000000�00000003756�13262451251�0023654�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "core/click_handler.h" ClickHandlerHost::~ClickHandlerHost() { ClickHandler::hostDestroyed(this); } NeverFreedPointer<ClickHandlerPtr> ClickHandler::_active; NeverFreedPointer<ClickHandlerPtr> ClickHandler::_pressed; ClickHandlerHost *ClickHandler::_activeHost = nullptr; ClickHandlerHost *ClickHandler::_pressedHost = nullptr; bool ClickHandler::setActive(const ClickHandlerPtr &p, ClickHandlerHost *host) { if ((_active && (*_active == p)) || (!_active && !p)) { return false; } // emit clickHandlerActiveChanged only when there is no // other pressed click handler currently, if there is // this method will be called when it is unpressed if (_active && *_active) { const auto emitClickHandlerActiveChanged = false || !_pressed || !*_pressed || (*_pressed == *_active); const auto wasactive = base::take(*_active); if (_activeHost) { if (emitClickHandlerActiveChanged) { _activeHost->clickHandlerActiveChanged(wasactive, false); } _activeHost = nullptr; } } if (p) { _active.createIfNull(); *_active = p; if ((_activeHost = host)) { bool emitClickHandlerActiveChanged = (!_pressed || !*_pressed || *_pressed == *_active); if (emitClickHandlerActiveChanged) { _activeHost->clickHandlerActiveChanged(*_active, true); } } } return true; } QString ClickHandler::getExpandedLinkText(ExpandLinksMode mode, const QStringRef &textPart) const { return QString(); } TextWithEntities ClickHandler::getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const { return { QString(), EntitiesInText() }; } TextWithEntities ClickHandler::simpleTextWithEntity(const EntityInText &entity) const { TextWithEntities result; result.entities.push_back(entity); return result; } ������������������tdesktop-1.2.17/Telegram/SourceFiles/core/click_handler.h�������������������������������������������0000664�0000000�0000000�00000012020�13262451251�0023301�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once class ClickHandler; using ClickHandlerPtr = std::shared_ptr<ClickHandler>; enum ExpandLinksMode { ExpandLinksNone, ExpandLinksShortened, ExpandLinksAll, ExpandLinksUrlOnly, // For custom urls leaves only url instead of text. }; class ClickHandlerHost { protected: virtual void clickHandlerActiveChanged(const ClickHandlerPtr &action, bool active) { } virtual void clickHandlerPressedChanged(const ClickHandlerPtr &action, bool pressed) { } virtual ~ClickHandlerHost() = 0; friend class ClickHandler; }; class EntityInText; struct TextWithEntities; class ClickHandler { public: virtual ~ClickHandler() { } virtual void onClick(Qt::MouseButton) const = 0; // What text to show in a tooltip when mouse is over that click handler as a link in Text. virtual QString tooltip() const { return QString(); } // What to drop in the input fields when dragging that click handler as a link from Text. virtual QString dragText() const { return QString(); } // Copy to clipboard support. virtual QString copyToClipboardText() const { return QString(); } virtual QString copyToClipboardContextItemText() const { return QString(); } // Entities in text support. // This method returns empty string if just textPart should be used (nothing to expand). virtual QString getExpandedLinkText(ExpandLinksMode mode, const QStringRef &textPart) const; virtual TextWithEntities getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const; // This method should be called on mouse over a click handler. // It returns true if the active handler was changed or false otherwise. static bool setActive(const ClickHandlerPtr &p, ClickHandlerHost *host = nullptr); // This method should be called when mouse leaves the host. // It returns true if the active handler was changed or false otherwise. static bool clearActive(ClickHandlerHost *host = nullptr) { if (host && _activeHost != host) { return false; } return setActive(ClickHandlerPtr(), host); } // This method should be called on mouse press event. static void pressed() { unpressed(); if (!_active || !*_active) { return; } _pressed.createIfNull(); *_pressed = *_active; if ((_pressedHost = _activeHost)) { _pressedHost->clickHandlerPressedChanged(*_pressed, true); } } // This method should be called on mouse release event. // The activated click handler (if any) is returned. static ClickHandlerPtr unpressed() { if (_pressed && *_pressed) { const auto activated = (_active && *_active == *_pressed); const auto waspressed = base::take(*_pressed); if (_pressedHost) { _pressedHost->clickHandlerPressedChanged(waspressed, false); _pressedHost = nullptr; } if (activated) { return *_active; } else if (_active && *_active && _activeHost) { // emit clickHandlerActiveChanged for current active // click handler, which we didn't emit while we has // a pressed click handler _activeHost->clickHandlerActiveChanged(*_active, true); } } return ClickHandlerPtr(); } static ClickHandlerPtr getActive() { return _active ? *_active : ClickHandlerPtr(); } static ClickHandlerPtr getPressed() { return _pressed ? *_pressed : ClickHandlerPtr(); } static bool showAsActive(const ClickHandlerPtr &p) { if (!p || !_active || p != *_active) { return false; } return !_pressed || !*_pressed || (p == *_pressed); } static bool showAsPressed(const ClickHandlerPtr &p) { if (!p || !_active || p != *_active) { return false; } return _pressed && (p == *_pressed); } static void hostDestroyed(ClickHandlerHost *host) { if (_activeHost == host) { if (_active) { *_active = nullptr; } _activeHost = nullptr; } if (_pressedHost == host) { if (_pressed) { *_pressed = nullptr; } _pressedHost = nullptr; } } protected: // For click handlers like mention or hashtag in getExpandedLinkTextWithEntities() // we return just an empty string ("use original string part") with single entity. TextWithEntities simpleTextWithEntity(const EntityInText &entity) const; private: static NeverFreedPointer<ClickHandlerPtr> _active; static NeverFreedPointer<ClickHandlerPtr> _pressed; static ClickHandlerHost *_activeHost; static ClickHandlerHost *_pressedHost; }; class LeftButtonClickHandler : public ClickHandler { public: void onClick(Qt::MouseButton button) const override final { if (button != Qt::LeftButton) return; onClickImpl(); } protected: virtual void onClickImpl() const = 0; }; class LambdaClickHandler : public ClickHandler { public: LambdaClickHandler(base::lambda<void()> handler) : _handler(std::move(handler)) { } void onClick(Qt::MouseButton button) const override final { if (button == Qt::LeftButton && _handler) { _handler(); } } private: base::lambda<void()> _handler; }; ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/core/click_handler_types.cpp�����������������������������������0000664�0000000�0000000�00000024570�13262451251�0025075�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "core/click_handler_types.h" #include "lang/lang_keys.h" #include "messenger.h" #include "platform/platform_specific.h" #include "history/view/history_view_element.h" #include "history/history_item.h" #include "boxes/confirm_box.h" #include "base/qthelp_regex.h" #include "base/qthelp_url.h" #include "storage/localstorage.h" #include "ui/widgets/tooltip.h" #include "core/file_utilities.h" namespace { QString tryConvertUrlToLocal(QString url) { if (url.size() > 8192) url = url.mid(0, 8192); using namespace qthelp; auto matchOptions = RegExOption::CaseInsensitive; auto telegramMeMatch = regex_match(qsl("^https?://(www\\.)?(telegram\\.(me|dog)|t\\.me)/(.+)$"), url, matchOptions); if (telegramMeMatch) { auto query = telegramMeMatch->capturedRef(4); if (auto joinChatMatch = regex_match(qsl("^joinchat/([a-zA-Z0-9\\.\\_\\-]+)(\\?|$)"), query, matchOptions)) { return qsl("tg://join?invite=") + url_encode(joinChatMatch->captured(1)); } else if (auto stickerSetMatch = regex_match(qsl("^addstickers/([a-zA-Z0-9\\.\\_]+)(\\?|$)"), query, matchOptions)) { return qsl("tg://addstickers?set=") + url_encode(stickerSetMatch->captured(1)); } else if (auto shareUrlMatch = regex_match(qsl("^share/url/?\\?(.+)$"), query, matchOptions)) { return qsl("tg://msg_url?") + shareUrlMatch->captured(1); } else if (auto confirmPhoneMatch = regex_match(qsl("^confirmphone/?\\?(.+)"), query, matchOptions)) { return qsl("tg://confirmphone?") + confirmPhoneMatch->captured(1); } else if (auto ivMatch = regex_match(qsl("iv/?\\?(.+)(#|$)"), query, matchOptions)) { // // We need to show our t.me page, not the url directly. // //auto params = url_parse_params(ivMatch->captured(1), UrlParamNameTransform::ToLower); //auto previewedUrl = params.value(qsl("url")); //if (previewedUrl.startsWith(qstr("http://"), Qt::CaseInsensitive) // || previewedUrl.startsWith(qstr("https://"), Qt::CaseInsensitive)) { // return previewedUrl; //} return url; } else if (auto socksMatch = regex_match(qsl("socks/?\\?(.+)(#|$)"), query, matchOptions)) { return qsl("tg://socks?") + socksMatch->captured(1); } else if (auto usernameMatch = regex_match(qsl("^([a-zA-Z0-9\\.\\_]+)(/?\\?|/?$|/(\\d+)/?(?:\\?|$))"), query, matchOptions)) { auto params = query.mid(usernameMatch->captured(0).size()).toString(); auto postParam = QString(); if (auto postMatch = regex_match(qsl("^/\\d+/?(?:\\?|$)"), usernameMatch->captured(2))) { postParam = qsl("&post=") + usernameMatch->captured(3); } return qsl("tg://resolve/?domain=") + url_encode(usernameMatch->captured(1)) + postParam + (params.isEmpty() ? QString() : '&' + params); } } return url; } bool UrlRequiresConfirmation(const QUrl &url) { using namespace qthelp; return !regex_match(qsl("(^|\\.)(telegram\\.org|telegra\\.ph|telesco\\.pe)$"), url.host(), RegExOption::CaseInsensitive); } } // namespace UrlClickHandler::UrlClickHandler(const QString &url, bool fullDisplayed) : TextClickHandler(fullDisplayed) , _originalUrl(url) { if (isEmail()) { _readable = _originalUrl; } else { const auto original = QUrl(_originalUrl); const auto good = QUrl(original.isValid() ? original.toEncoded() : QString()); _readable = good.isValid() ? good.toDisplayString() : _originalUrl; } } QString UrlClickHandler::copyToClipboardContextItemText() const { return lang(isEmail() ? lng_context_copy_email : lng_context_copy_link); } QString UrlClickHandler::url() const { if (isEmail()) { return _originalUrl; } QUrl u(_originalUrl), good(u.isValid() ? u.toEncoded() : QString()); QString result(good.isValid() ? QString::fromUtf8(good.toEncoded()) : _originalUrl); if (!result.isEmpty() && !QRegularExpression(qsl("^[a-zA-Z]+:")).match(result).hasMatch()) { // no protocol return qsl("http://") + result; } return result; } void UrlClickHandler::doOpen(QString url) { Ui::Tooltip::Hide(); if (isEmail(url)) { File::OpenEmailLink(url); return; } url = tryConvertUrlToLocal(url); if (url.startsWith(qstr("tg://"), Qt::CaseInsensitive)) { Messenger::Instance().openLocalUrl(url); } else { QDesktopServices::openUrl(url); } } QString UrlClickHandler::getExpandedLinkText(ExpandLinksMode mode, const QStringRef &textPart) const { QString result; if (mode != ExpandLinksNone) { result = _originalUrl; } return result; } TextWithEntities UrlClickHandler::getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const { TextWithEntities result; auto entityType = isEmail(_originalUrl) ? EntityInTextEmail : EntityInTextUrl; int entityLength = textPart.size(); if (mode != ExpandLinksNone) { result.text = _originalUrl; entityLength = _originalUrl.size(); } result.entities.push_back({ entityType, entityOffset, entityLength }); return result; } void HiddenUrlClickHandler::doOpen(QString url) { auto urlText = tryConvertUrlToLocal(url); if (urlText.startsWith(qstr("tg://"), Qt::CaseInsensitive)) { Messenger::Instance().openLocalUrl(urlText); } else { auto parsedUrl = QUrl::fromUserInput(urlText); if (UrlRequiresConfirmation(urlText)) { auto displayUrl = parsedUrl.isValid() ? parsedUrl.toDisplayString() : urlText; Ui::show(Box<ConfirmBox>(lang(lng_open_this_link) + qsl("\n\n") + displayUrl, lang(lng_open_link), [urlText] { Ui::hideLayer(); UrlClickHandler::doOpen(urlText); }), LayerOption::KeepOther); } else { UrlClickHandler::doOpen(urlText); } } } void BotGameUrlClickHandler::onClick(Qt::MouseButton button) const { auto urlText = tryConvertUrlToLocal(url()); if (urlText.startsWith(qstr("tg://"), Qt::CaseInsensitive)) { Messenger::Instance().openLocalUrl(urlText); } else if (!_bot || _bot->isVerified() || Local::isBotTrusted(_bot)) { doOpen(urlText); } else { Ui::show(Box<ConfirmBox>(lng_allow_bot_pass(lt_bot_name, _bot->name), lang(lng_allow_bot), [bot = _bot, urlText] { Ui::hideLayer(); Local::makeBotTrusted(bot); UrlClickHandler::doOpen(urlText); })); } } QString HiddenUrlClickHandler::getExpandedLinkText(ExpandLinksMode mode, const QStringRef &textPart) const { QString result; if (mode == ExpandLinksAll) { result = textPart.toString() + qsl(" (") + url() + ')'; } else if (mode == ExpandLinksUrlOnly) { result = url(); } return result; } TextWithEntities HiddenUrlClickHandler::getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const { TextWithEntities result; if (mode == ExpandLinksUrlOnly) { result.text = url(); result.entities.push_back({ EntityInTextUrl, entityOffset, result.text.size() }); } else { result.entities.push_back({ EntityInTextCustomUrl, entityOffset, textPart.size(), url() }); if (mode == ExpandLinksAll) { result.text = textPart.toString() + qsl(" (") + url() + ')'; } } return result; } QString MentionClickHandler::copyToClipboardContextItemText() const { return lang(lng_context_copy_mention); } void MentionClickHandler::onClick(Qt::MouseButton button) const { if (button == Qt::LeftButton || button == Qt::MiddleButton) { App::openPeerByName(_tag.mid(1), ShowAtProfileMsgId); } } TextWithEntities MentionClickHandler::getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const { return simpleTextWithEntity({ EntityInTextMention, entityOffset, textPart.size() }); } void MentionNameClickHandler::onClick(Qt::MouseButton button) const { if (button == Qt::LeftButton || button == Qt::MiddleButton) { if (auto user = App::userLoaded(_userId)) { Ui::showPeerProfile(user); } } } TextWithEntities MentionNameClickHandler::getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const { auto data = QString::number(_userId) + '.' + QString::number(_accessHash); return simpleTextWithEntity({ EntityInTextMentionName, entityOffset, textPart.size(), data }); } QString MentionNameClickHandler::tooltip() const { if (auto user = App::userLoaded(_userId)) { auto name = App::peerName(user); if (name != _text) { return name; } } return QString(); } QString HashtagClickHandler::copyToClipboardContextItemText() const { return lang(lng_context_copy_hashtag); } void HashtagClickHandler::onClick(Qt::MouseButton button) const { if (button == Qt::LeftButton || button == Qt::MiddleButton) { App::searchByHashtag(_tag, Ui::getPeerForMouseAction()); } } TextWithEntities HashtagClickHandler::getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const { return simpleTextWithEntity({ EntityInTextHashtag, entityOffset, textPart.size() }); } QString CashtagClickHandler::copyToClipboardContextItemText() const { return lang(lng_context_copy_hashtag); } void CashtagClickHandler::onClick(Qt::MouseButton button) const { if (button == Qt::LeftButton || button == Qt::MiddleButton) { App::searchByHashtag(_tag, Ui::getPeerForMouseAction()); } } TextWithEntities CashtagClickHandler::getExpandedLinkTextWithEntities( ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const { return simpleTextWithEntity({ EntityInTextCashtag, entityOffset, textPart.size() }); } PeerData *BotCommandClickHandler::_peer = nullptr; UserData *BotCommandClickHandler::_bot = nullptr; void BotCommandClickHandler::onClick(Qt::MouseButton button) const { if (button == Qt::LeftButton || button == Qt::MiddleButton) { if (auto peer = peerForCommand()) { if (auto bot = peer->isUser() ? peer->asUser() : botForCommand()) { Ui::showPeerHistory(peer, ShowAtTheEndMsgId); App::sendBotCommand(peer, bot, _cmd); return; } } if (auto peer = Ui::getPeerForMouseAction()) { // old way auto bot = peer->isUser() ? peer->asUser() : nullptr; if (!bot) { if (const auto view = App::hoveredLinkItem()) { // may return nullptr bot = view->data()->fromOriginal()->asUser(); } } Ui::showPeerHistory(peer, ShowAtTheEndMsgId); App::sendBotCommand(peer, bot, _cmd); } else { App::insertBotCommand(_cmd); } } } TextWithEntities BotCommandClickHandler::getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const { return simpleTextWithEntity({ EntityInTextBotCommand, entityOffset, textPart.size() }); } ����������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/core/click_handler_types.h�������������������������������������0000664�0000000�0000000�00000013013�13262451251�0024530�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "core/click_handler.h" class TextClickHandler : public ClickHandler { public: TextClickHandler(bool fullDisplayed = true) : _fullDisplayed(fullDisplayed) { } QString copyToClipboardText() const override { return url(); } QString tooltip() const override { return _fullDisplayed ? QString() : readable(); } void setFullDisplayed(bool full) { _fullDisplayed = full; } protected: virtual QString url() const = 0; virtual QString readable() const { return url(); } bool _fullDisplayed; }; class UrlClickHandler : public TextClickHandler { public: UrlClickHandler(const QString &url, bool fullDisplayed = true); QString copyToClipboardContextItemText() const override; QString dragText() const override { return url(); } QString getExpandedLinkText( ExpandLinksMode mode, const QStringRef &textPart) const override; TextWithEntities getExpandedLinkTextWithEntities( ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const override; static void doOpen(QString url); void onClick(Qt::MouseButton button) const override { if (button == Qt::LeftButton || button == Qt::MiddleButton) { doOpen(url()); } } protected: QString url() const override; QString readable() const override { return _readable; } private: static bool isEmail(const QString &url) { int at = url.indexOf('@'), slash = url.indexOf('/'); return ((at > 0) && (slash < 0 || slash > at)); } bool isEmail() const { return isEmail(_originalUrl); } QString _originalUrl, _readable; }; class HiddenUrlClickHandler : public UrlClickHandler { public: HiddenUrlClickHandler(QString url) : UrlClickHandler(url, false) { } QString copyToClipboardContextItemText() const override { return url().isEmpty() ? QString() : UrlClickHandler::copyToClipboardContextItemText(); } static void doOpen(QString url); void onClick(Qt::MouseButton button) const override { if (button == Qt::LeftButton || button == Qt::MiddleButton) { doOpen(url()); } } QString getExpandedLinkText( ExpandLinksMode mode, const QStringRef &textPart) const override; TextWithEntities getExpandedLinkTextWithEntities( ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const override; }; class BotGameUrlClickHandler : public UrlClickHandler { public: BotGameUrlClickHandler(UserData *bot, QString url) : UrlClickHandler(url, false) , _bot(bot) { } void onClick(Qt::MouseButton button) const override; private: UserData *_bot; }; class MentionClickHandler : public TextClickHandler { public: MentionClickHandler(const QString &tag) : _tag(tag) { } void onClick(Qt::MouseButton button) const override; QString dragText() const override { return _tag; } QString copyToClipboardContextItemText() const override; TextWithEntities getExpandedLinkTextWithEntities( ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const override; protected: QString url() const override { return _tag; } private: QString _tag; }; class MentionNameClickHandler : public ClickHandler { public: MentionNameClickHandler(QString text, UserId userId, uint64 accessHash) : _text(text) , _userId(userId) , _accessHash(accessHash) { } void onClick(Qt::MouseButton button) const override; TextWithEntities getExpandedLinkTextWithEntities( ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const override; QString tooltip() const override; private: QString _text; UserId _userId; uint64 _accessHash; }; class HashtagClickHandler : public TextClickHandler { public: HashtagClickHandler(const QString &tag) : _tag(tag) { } void onClick(Qt::MouseButton button) const override; QString dragText() const override { return _tag; } QString copyToClipboardContextItemText() const override; TextWithEntities getExpandedLinkTextWithEntities( ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const override; protected: QString url() const override { return _tag; } private: QString _tag; }; class CashtagClickHandler : public TextClickHandler { public: CashtagClickHandler(const QString &tag) : _tag(tag) { } void onClick(Qt::MouseButton button) const override; QString dragText() const override { return _tag; } QString copyToClipboardContextItemText() const override; TextWithEntities getExpandedLinkTextWithEntities( ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const override; protected: QString url() const override { return _tag; } private: QString _tag; }; class PeerData; class UserData; class BotCommandClickHandler : public TextClickHandler { public: BotCommandClickHandler(const QString &cmd) : _cmd(cmd) { } void onClick(Qt::MouseButton button) const override; QString dragText() const override { return _cmd; } static void setPeerForCommand(PeerData *peer) { _peer = peer; } static void setBotForCommand(UserData *bot) { _bot = bot; } TextWithEntities getExpandedLinkTextWithEntities( ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const override; protected: QString url() const override { return _cmd; } static PeerData *peerForCommand() { return _peer; } static UserData *botForCommand() { return _bot; } private: QString _cmd; static PeerData *_peer; static UserData *_bot; }; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/core/crash_report_window.cpp�����������������������������������0000664�0000000�0000000�00000110703�13262451251�0025143�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "core/crash_report_window.h" #include "core/crash_reports.h" #include "window/main_window.h" #include "platform/platform_specific.h" #include "application.h" #include "base/zlib_help.h" #include "autoupdater.h" PreLaunchWindow *PreLaunchWindowInstance = nullptr; PreLaunchWindow::PreLaunchWindow(QString title) { Fonts::Start(); auto icon = Window::CreateIcon(); setWindowIcon(icon); setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowCloseButtonHint); setWindowTitle(title.isEmpty() ? qsl("Telegram") : title); QPalette p(palette()); p.setColor(QPalette::Background, QColor(255, 255, 255)); setPalette(p); QLabel tmp(this); tmp.setText(qsl("Tmp")); _size = tmp.sizeHint().height(); int paddingVertical = (_size / 2); int paddingHorizontal = _size; int borderRadius = (_size / 5); setStyleSheet(qsl("QPushButton { padding: %1px %2px; background-color: #ffffff; border-radius: %3px; }\nQPushButton#confirm:hover, QPushButton#cancel:hover { background-color: #e3f1fa; color: #2f9fea; }\nQPushButton#confirm { color: #2f9fea; }\nQPushButton#cancel { color: #aeaeae; }\nQLineEdit { border: 1px solid #e0e0e0; padding: 5px; }\nQLineEdit:focus { border: 2px solid #37a1de; padding: 4px; }").arg(paddingVertical).arg(paddingHorizontal).arg(borderRadius)); if (!PreLaunchWindowInstance) { PreLaunchWindowInstance = this; } } void PreLaunchWindow::activate() { setWindowState(windowState() & ~Qt::WindowMinimized); setVisible(true); psActivateProcess(); activateWindow(); } PreLaunchWindow *PreLaunchWindow::instance() { return PreLaunchWindowInstance; } PreLaunchWindow::~PreLaunchWindow() { if (PreLaunchWindowInstance == this) { PreLaunchWindowInstance = nullptr; } } PreLaunchLabel::PreLaunchLabel(QWidget *parent) : QLabel(parent) { QFont labelFont(font()); labelFont.setFamily(Fonts::GetOverride(qsl("Open Sans Semibold"))); labelFont.setPixelSize(static_cast<PreLaunchWindow*>(parent)->basicSize()); setFont(labelFont); QPalette p(palette()); p.setColor(QPalette::Foreground, QColor(0, 0, 0)); setPalette(p); show(); }; void PreLaunchLabel::setText(const QString &text) { QLabel::setText(text); updateGeometry(); resize(sizeHint()); } PreLaunchInput::PreLaunchInput(QWidget *parent, bool password) : QLineEdit(parent) { QFont logFont(font()); logFont.setFamily(Fonts::GetOverride(qsl("Open Sans"))); logFont.setPixelSize(static_cast<PreLaunchWindow*>(parent)->basicSize()); setFont(logFont); QPalette p(palette()); p.setColor(QPalette::Foreground, QColor(0, 0, 0)); setPalette(p); QLineEdit::setTextMargins(0, 0, 0, 0); setContentsMargins(0, 0, 0, 0); if (password) { setEchoMode(QLineEdit::Password); } show(); }; PreLaunchLog::PreLaunchLog(QWidget *parent) : QTextEdit(parent) { QFont logFont(font()); logFont.setFamily(Fonts::GetOverride(qsl("Open Sans"))); logFont.setPixelSize(static_cast<PreLaunchWindow*>(parent)->basicSize()); setFont(logFont); QPalette p(palette()); p.setColor(QPalette::Foreground, QColor(96, 96, 96)); setPalette(p); setReadOnly(true); setFrameStyle(QFrame::NoFrame | QFrame::Plain); viewport()->setAutoFillBackground(false); setContentsMargins(0, 0, 0, 0); document()->setDocumentMargin(0); show(); }; PreLaunchButton::PreLaunchButton(QWidget *parent, bool confirm) : QPushButton(parent) { setFlat(true); setObjectName(confirm ? "confirm" : "cancel"); QFont closeFont(font()); closeFont.setFamily(Fonts::GetOverride(qsl("Open Sans Semibold"))); closeFont.setPixelSize(static_cast<PreLaunchWindow*>(parent)->basicSize()); setFont(closeFont); setCursor(Qt::PointingHandCursor); show(); }; void PreLaunchButton::setText(const QString &text) { QPushButton::setText(text); updateGeometry(); resize(sizeHint()); } PreLaunchCheckbox::PreLaunchCheckbox(QWidget *parent) : QCheckBox(parent) { setTristate(false); setCheckState(Qt::Checked); QFont closeFont(font()); closeFont.setFamily(Fonts::GetOverride(qsl("Open Sans Semibold"))); closeFont.setPixelSize(static_cast<PreLaunchWindow*>(parent)->basicSize()); setFont(closeFont); setCursor(Qt::PointingHandCursor); show(); }; void PreLaunchCheckbox::setText(const QString &text) { QCheckBox::setText(text); updateGeometry(); resize(sizeHint()); } NotStartedWindow::NotStartedWindow() : _label(this) , _log(this) , _close(this) { _label.setText(qsl("Could not start Telegram Desktop!\nYou can see complete log below:")); _log.setPlainText(Logs::full()); connect(&_close, SIGNAL(clicked()), this, SLOT(close())); _close.setText(qsl("CLOSE")); QRect scr(QApplication::primaryScreen()->availableGeometry()); move(scr.x() + (scr.width() / 6), scr.y() + (scr.height() / 6)); updateControls(); show(); } void NotStartedWindow::updateControls() { _label.show(); _log.show(); _close.show(); QRect scr(QApplication::primaryScreen()->availableGeometry()); QSize s(scr.width() / 2, scr.height() / 2); if (s == size()) { resizeEvent(0); } else { resize(s); } } void NotStartedWindow::closeEvent(QCloseEvent *e) { deleteLater(); } void NotStartedWindow::resizeEvent(QResizeEvent *e) { int padding = _size; _label.setGeometry(padding, padding, width() - 2 * padding, _label.sizeHint().height()); _log.setGeometry(padding, padding * 2 + _label.sizeHint().height(), width() - 2 * padding, height() - 4 * padding - _label.height() - _close.height()); _close.setGeometry(width() - padding - _close.width(), height() - padding - _close.height(), _close.width(), _close.height()); } LastCrashedWindow::LastCrashedWindow() : _port(80) , _label(this) , _pleaseSendReport(this) , _yourReportName(this) , _minidump(this) , _report(this) , _send(this) , _sendSkip(this, false) , _networkSettings(this) , _continue(this) , _showReport(this) , _saveReport(this) , _getApp(this) , _includeUsername(this) , _reportText(QString::fromUtf8(Sandbox::LastCrashDump())) , _reportShown(false) , _reportSaved(false) , _sendingState(Sandbox::LastCrashDump().isEmpty() ? SendingNoReport : SendingUpdateCheck) , _updating(this) , _sendingProgress(0) , _sendingTotal(0) , _checkReply(0) , _sendReply(0) #ifndef TDESKTOP_DISABLE_AUTOUPDATE , _updatingCheck(this) , _updatingSkip(this, false) #endif // !TDESKTOP_DISABLE_AUTOUPDATE { excludeReportUsername(); if (!cAlphaVersion() && !cBetaVersion()) { // currently accept crash reports only from testers _sendingState = SendingNoReport; } if (_sendingState != SendingNoReport) { qint64 dumpsize = 0; QString dumpspath = cWorkingDir() + qsl("tdata/dumps"); #if defined Q_OS_MAC && !defined MAC_USE_BREAKPAD dumpspath += qsl("/completed"); #endif QString possibleDump = getReportField(qstr("minidump"), qstr("Minidump:")); if (!possibleDump.isEmpty()) { if (!possibleDump.startsWith('/')) { possibleDump = dumpspath + '/' + possibleDump; } if (!possibleDump.endsWith(qstr(".dmp"))) { possibleDump += qsl(".dmp"); } QFileInfo possibleInfo(possibleDump); if (possibleInfo.exists()) { _minidumpName = possibleInfo.fileName(); _minidumpFull = possibleInfo.absoluteFilePath(); dumpsize = possibleInfo.size(); } } if (_minidumpFull.isEmpty()) { QString maxDump, maxDumpFull; QDateTime maxDumpModified, workingModified = QFileInfo(cWorkingDir() + qsl("tdata/working")).lastModified(); QFileInfoList list = QDir(dumpspath).entryInfoList(); for (int32 i = 0, l = list.size(); i < l; ++i) { QString name = list.at(i).fileName(); if (name.endsWith(qstr(".dmp"))) { QDateTime modified = list.at(i).lastModified(); if (maxDump.isEmpty() || qAbs(workingModified.secsTo(modified)) < qAbs(workingModified.secsTo(maxDumpModified))) { maxDump = name; maxDumpModified = modified; maxDumpFull = list.at(i).absoluteFilePath(); dumpsize = list.at(i).size(); } } } if (!maxDump.isEmpty() && qAbs(workingModified.secsTo(maxDumpModified)) < 10) { _minidumpName = maxDump; _minidumpFull = maxDumpFull; } } if (_minidumpName.isEmpty()) { // currently don't accept crash reports without dumps from google libraries _sendingState = SendingNoReport; } else { _minidump.setText(qsl("+ %1 (%2 KB)").arg(_minidumpName).arg(dumpsize / 1024)); } } if (_sendingState != SendingNoReport) { QString version = getReportField(qstr("version"), qstr("Version:")); QString current = cBetaVersion() ? qsl("-%1").arg(cBetaVersion()) : QString::number(AppVersion); if (version != current) { // currently don't accept crash reports from not current app version _sendingState = SendingNoReport; } } _networkSettings.setText(qsl("NETWORK SETTINGS")); connect(&_networkSettings, SIGNAL(clicked()), this, SLOT(onNetworkSettings())); if (_sendingState == SendingNoReport) { _label.setText(qsl("Last time Telegram Desktop was not closed properly.")); } else { _label.setText(qsl("Last time Telegram Desktop crashed :(")); } #ifndef TDESKTOP_DISABLE_AUTOUPDATE _updatingCheck.setText(qsl("TRY AGAIN")); connect(&_updatingCheck, SIGNAL(clicked()), this, SLOT(onUpdateRetry())); _updatingSkip.setText(qsl("SKIP")); connect(&_updatingSkip, SIGNAL(clicked()), this, SLOT(onUpdateSkip())); Sandbox::connect(SIGNAL(updateChecking()), this, SLOT(onUpdateChecking())); Sandbox::connect(SIGNAL(updateLatest()), this, SLOT(onUpdateLatest())); Sandbox::connect(SIGNAL(updateProgress(qint64,qint64)), this, SLOT(onUpdateDownloading(qint64,qint64))); Sandbox::connect(SIGNAL(updateFailed()), this, SLOT(onUpdateFailed())); Sandbox::connect(SIGNAL(updateReady()), this, SLOT(onUpdateReady())); switch (Sandbox::updatingState()) { case Application::UpdatingDownload: setUpdatingState(UpdatingDownload, true); setDownloadProgress(Sandbox::updatingReady(), Sandbox::updatingSize()); break; case Application::UpdatingReady: setUpdatingState(UpdatingReady, true); break; default: setUpdatingState(UpdatingCheck, true); break; } cSetLastUpdateCheck(0); Sandbox::startUpdateCheck(); #else // !TDESKTOP_DISABLE_AUTOUPDATE _updating.setText(qsl("Please check if there is a new version available.")); if (_sendingState != SendingNoReport) { _sendingState = SendingNone; } #endif // else for !TDESKTOP_DISABLE_AUTOUPDATE _pleaseSendReport.setText(qsl("Please send us a crash report.")); _yourReportName.setText(qsl("Your Report Tag: %1\nYour User Tag: %2").arg(QString(_minidumpName).replace(".dmp", "")).arg(Sandbox::UserTag(), 0, 16)); _yourReportName.setCursor(style::cur_text); _yourReportName.setTextInteractionFlags(Qt::TextSelectableByMouse); _includeUsername.setText(qsl("Include username @%1 as your contact info").arg(_reportUsername)); _report.setPlainText(_reportTextNoUsername); _showReport.setText(qsl("VIEW REPORT")); connect(&_showReport, SIGNAL(clicked()), this, SLOT(onViewReport())); _saveReport.setText(qsl("SAVE TO FILE")); connect(&_saveReport, SIGNAL(clicked()), this, SLOT(onSaveReport())); _getApp.setText(qsl("GET THE LATEST OFFICIAL VERSION OF TELEGRAM DESKTOP")); connect(&_getApp, SIGNAL(clicked()), this, SLOT(onGetApp())); _send.setText(qsl("SEND CRASH REPORT")); connect(&_send, SIGNAL(clicked()), this, SLOT(onSendReport())); _sendSkip.setText(qsl("SKIP")); connect(&_sendSkip, SIGNAL(clicked()), this, SLOT(onContinue())); _continue.setText(qsl("CONTINUE")); connect(&_continue, SIGNAL(clicked()), this, SLOT(onContinue())); QRect scr(QApplication::primaryScreen()->availableGeometry()); move(scr.x() + (scr.width() / 6), scr.y() + (scr.height() / 6)); updateControls(); show(); } void LastCrashedWindow::onViewReport() { _reportShown = !_reportShown; updateControls(); } void LastCrashedWindow::onSaveReport() { QString to = QFileDialog::getSaveFileName(0, qsl("Telegram Crash Report"), QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + qsl("/report.telegramcrash"), qsl("Telegram crash report (*.telegramcrash)")); if (!to.isEmpty()) { QFile file(to); if (file.open(QIODevice::WriteOnly)) { file.write(getCrashReportRaw()); _reportSaved = true; updateControls(); } } } QByteArray LastCrashedWindow::getCrashReportRaw() const { QByteArray result(Sandbox::LastCrashDump()); if (!_reportUsername.isEmpty() && _includeUsername.checkState() != Qt::Checked) { result.replace((qsl("Username: ") + _reportUsername).toUtf8(), "Username: _not_included_"); } return result; } void LastCrashedWindow::onGetApp() { QDesktopServices::openUrl(qsl("https://desktop.telegram.org")); } void LastCrashedWindow::excludeReportUsername() { QString prefix = qstr("Username:"); QStringList lines = _reportText.split('\n'); for (int32 i = 0, l = lines.size(); i < l; ++i) { if (lines.at(i).trimmed().startsWith(prefix)) { _reportUsername = lines.at(i).trimmed().mid(prefix.size()).trimmed(); lines.removeAt(i); break; } } _reportTextNoUsername = _reportUsername.isEmpty() ? _reportText : lines.join('\n'); } QString LastCrashedWindow::getReportField(const QLatin1String &name, const QLatin1String &prefix) { QStringList lines = _reportText.split('\n'); for (int32 i = 0, l = lines.size(); i < l; ++i) { if (lines.at(i).trimmed().startsWith(prefix)) { QString data = lines.at(i).trimmed().mid(prefix.size()).trimmed(); if (name == qstr("version")) { if (data.endsWith(qstr(" beta"))) { data = QString::number(-data.replace(QRegularExpression(qsl("[^\\d]")), "").toLongLong()); } else { data = QString::number(data.replace(QRegularExpression(qsl("[^\\d]")), "").toLongLong()); } } return data; } } return QString(); } void LastCrashedWindow::addReportFieldPart(const QLatin1String &name, const QLatin1String &prefix, QHttpMultiPart *multipart) { QString data = getReportField(name, prefix); if (!data.isEmpty()) { QHttpPart reportPart; reportPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant(qsl("form-data; name=\"%1\"").arg(name))); reportPart.setBody(data.toUtf8()); multipart->append(reportPart); } } void LastCrashedWindow::onSendReport() { if (_checkReply) { _checkReply->deleteLater(); _checkReply = nullptr; } if (_sendReply) { _sendReply->deleteLater(); _sendReply = nullptr; } App::setProxySettings(_sendManager); QString apiid = getReportField(qstr("apiid"), qstr("ApiId:")), version = getReportField(qstr("version"), qstr("Version:")); _checkReply = _sendManager.get(QNetworkRequest(qsl("https://tdesktop.com/crash.php?act=query_report&apiid=%1&version=%2&dmp=%3&platform=%4").arg(apiid).arg(version).arg(minidumpFileName().isEmpty() ? 0 : 1).arg(cPlatformString()))); connect(_checkReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onSendingError(QNetworkReply::NetworkError))); connect(_checkReply, SIGNAL(finished()), this, SLOT(onCheckingFinished())); _pleaseSendReport.setText(qsl("Sending crash report...")); _sendingState = SendingProgress; _reportShown = false; updateControls(); } QString LastCrashedWindow::minidumpFileName() { QFileInfo dmpFile(_minidumpFull); if (dmpFile.exists() && dmpFile.size() > 0 && dmpFile.size() < 20 * 1024 * 1024 && QRegularExpression(qsl("^[a-zA-Z0-9\\-]{1,64}\\.dmp$")).match(dmpFile.fileName()).hasMatch()) { return dmpFile.fileName(); } return QString(); } void LastCrashedWindow::onCheckingFinished() { if (!_checkReply || _sendReply) return; QByteArray result = _checkReply->readAll().trimmed(); _checkReply->deleteLater(); _checkReply = nullptr; LOG(("Crash report check for sending done, result: %1").arg(QString::fromUtf8(result))); if (result == "Old") { _pleaseSendReport.setText(qsl("This report is about some old version of Telegram Desktop.")); _sendingState = SendingTooOld; updateControls(); return; } else if (result == "Unofficial") { _pleaseSendReport.setText(qsl("You use some custom version of Telegram Desktop.")); _sendingState = SendingUnofficial; updateControls(); return; } else if (result != "Report") { _pleaseSendReport.setText(qsl("Thank you for your report!")); _sendingState = SendingDone; updateControls(); CrashReports::Restart(); return; } auto multipart = new QHttpMultiPart(QHttpMultiPart::FormDataType); addReportFieldPart(qstr("platform"), qstr("Platform:"), multipart); addReportFieldPart(qstr("version"), qstr("Version:"), multipart); QHttpPart reportPart; reportPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream")); reportPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"report\"; filename=\"report.telegramcrash\"")); reportPart.setBody(getCrashReportRaw()); multipart->append(reportPart); QString dmpName = minidumpFileName(); if (!dmpName.isEmpty()) { QFile file(_minidumpFull); if (file.open(QIODevice::ReadOnly)) { QByteArray minidump = file.readAll(); file.close(); QString zipName = QString(dmpName).replace(qstr(".dmp"), qstr(".zip")); zlib::FileToWrite minidumpZip; zip_fileinfo zfi = { { 0, 0, 0, 0, 0, 0 }, 0, 0, 0 }; QByteArray dmpNameUtf = dmpName.toUtf8(); minidumpZip.openNewFile(dmpNameUtf.constData(), &zfi, nullptr, 0, nullptr, 0, nullptr, Z_DEFLATED, Z_DEFAULT_COMPRESSION); minidumpZip.writeInFile(minidump.constData(), minidump.size()); minidumpZip.closeFile(); minidumpZip.close(); if (minidumpZip.error() == ZIP_OK) { QHttpPart dumpPart; dumpPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream")); dumpPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant(qsl("form-data; name=\"dump\"; filename=\"%1\"").arg(zipName))); dumpPart.setBody(minidumpZip.result()); multipart->append(dumpPart); _minidump.setText(qsl("+ %1 (%2 KB)").arg(zipName).arg(minidumpZip.result().size() / 1024)); } } } _sendReply = _sendManager.post(QNetworkRequest(qsl("https://tdesktop.com/crash.php?act=report")), multipart); multipart->setParent(_sendReply); connect(_sendReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onSendingError(QNetworkReply::NetworkError))); connect(_sendReply, SIGNAL(finished()), this, SLOT(onSendingFinished())); connect(_sendReply, SIGNAL(uploadProgress(qint64,qint64)), this, SLOT(onSendingProgress(qint64,qint64))); updateControls(); } void LastCrashedWindow::updateControls() { int padding = _size, h = padding + _networkSettings.height() + padding; _label.show(); #ifndef TDESKTOP_DISABLE_AUTOUPDATE h += _networkSettings.height() + padding; if (_updatingState == UpdatingFail && (_sendingState == SendingNoReport || _sendingState == SendingUpdateCheck)) { _networkSettings.show(); _updatingCheck.show(); _updatingSkip.show(); _send.hide(); _sendSkip.hide(); _continue.hide(); _pleaseSendReport.hide(); _yourReportName.hide(); _includeUsername.hide(); _getApp.hide(); _showReport.hide(); _report.hide(); _minidump.hide(); _saveReport.hide(); h += padding + _updatingCheck.height() + padding; } else { if (_updatingState == UpdatingCheck || _sendingState == SendingFail || _sendingState == SendingProgress) { _networkSettings.show(); } else { _networkSettings.hide(); } if (_updatingState == UpdatingNone || _updatingState == UpdatingLatest || _updatingState == UpdatingFail) { h += padding + _updatingCheck.height() + padding; if (_sendingState == SendingNoReport) { _pleaseSendReport.hide(); _yourReportName.hide(); _includeUsername.hide(); _getApp.hide(); _showReport.hide(); _report.hide(); _minidump.hide(); _saveReport.hide(); _send.hide(); _sendSkip.hide(); _continue.show(); } else { h += _showReport.height() + padding + _yourReportName.height() + padding; _pleaseSendReport.show(); _yourReportName.show(); if (_reportUsername.isEmpty()) { _includeUsername.hide(); } else { h += _includeUsername.height() + padding; _includeUsername.show(); } if (_sendingState == SendingTooOld || _sendingState == SendingUnofficial) { QString verStr = getReportField(qstr("version"), qstr("Version:")); qint64 ver = verStr.isEmpty() ? 0 : verStr.toLongLong(); if (!ver || (ver == AppVersion) || (ver < 0 && (-ver / 1000) == AppVersion)) { h += _getApp.height() + padding; _getApp.show(); h -= _yourReportName.height() + padding; // hide report name _yourReportName.hide(); if (!_reportUsername.isEmpty()) { h -= _includeUsername.height() + padding; _includeUsername.hide(); } } else { _getApp.hide(); } _showReport.hide(); _report.hide(); _minidump.hide(); _saveReport.hide(); _send.hide(); _sendSkip.hide(); _continue.show(); } else { _getApp.hide(); if (_reportShown) { h += (_pleaseSendReport.height() * 12.5) + padding + (_minidumpName.isEmpty() ? 0 : (_minidump.height() + padding)); _report.show(); if (_minidumpName.isEmpty()) { _minidump.hide(); } else { _minidump.show(); } if (_reportSaved || _sendingState == SendingFail || _sendingState == SendingProgress || _sendingState == SendingUploading) { _saveReport.hide(); } else { _saveReport.show(); } _showReport.hide(); } else { _report.hide(); _minidump.hide(); _saveReport.hide(); if (_sendingState == SendingFail || _sendingState == SendingProgress || _sendingState == SendingUploading) { _showReport.hide(); } else { _showReport.show(); } } if (_sendingState == SendingTooMany || _sendingState == SendingDone) { _send.hide(); _sendSkip.hide(); _continue.show(); } else { if (_sendingState == SendingProgress || _sendingState == SendingUploading) { _send.hide(); } else { _send.show(); } _sendSkip.show(); _continue.hide(); } } } } else { _getApp.hide(); _pleaseSendReport.hide(); _yourReportName.hide(); _includeUsername.hide(); _showReport.hide(); _report.hide(); _minidump.hide(); _saveReport.hide(); _send.hide(); _sendSkip.hide(); _continue.hide(); } _updatingCheck.hide(); if (_updatingState == UpdatingCheck || _updatingState == UpdatingDownload) { h += padding + _updatingSkip.height() + padding; _updatingSkip.show(); } else { _updatingSkip.hide(); } } #else // !TDESKTOP_DISABLE_AUTOUPDATE h += _networkSettings.height() + padding; h += padding + _send.height() + padding; if (_sendingState == SendingNoReport) { _pleaseSendReport.hide(); _yourReportName.hide(); _includeUsername.hide(); _showReport.hide(); _report.hide(); _minidump.hide(); _saveReport.hide(); _send.hide(); _sendSkip.hide(); _continue.show(); _networkSettings.hide(); } else { h += _showReport.height() + padding + _yourReportName.height() + padding; _pleaseSendReport.show(); _yourReportName.show(); if (_reportUsername.isEmpty()) { _includeUsername.hide(); } else { h += _includeUsername.height() + padding; _includeUsername.show(); } if (_reportShown) { h += (_pleaseSendReport.height() * 12.5) + padding + (_minidumpName.isEmpty() ? 0 : (_minidump.height() + padding)); _report.show(); if (_minidumpName.isEmpty()) { _minidump.hide(); } else { _minidump.show(); } _showReport.hide(); if (_reportSaved || _sendingState == SendingFail || _sendingState == SendingProgress || _sendingState == SendingUploading) { _saveReport.hide(); } else { _saveReport.show(); } } else { _report.hide(); _minidump.hide(); _saveReport.hide(); if (_sendingState == SendingFail || _sendingState == SendingProgress || _sendingState == SendingUploading) { _showReport.hide(); } else { _showReport.show(); } } if (_sendingState == SendingDone) { _send.hide(); _sendSkip.hide(); _continue.show(); _networkSettings.hide(); } else { if (_sendingState == SendingProgress || _sendingState == SendingUploading) { _send.hide(); } else { _send.show(); } _sendSkip.show(); if (_sendingState == SendingFail) { _networkSettings.show(); } else { _networkSettings.hide(); } _continue.hide(); } } _getApp.show(); h += _networkSettings.height() + padding; #endif // else for !TDESKTOP_DISABLE_AUTOUPDATE QRect scr(QApplication::primaryScreen()->availableGeometry()); QSize s(2 * padding + QFontMetrics(_label.font()).width(qsl("Last time Telegram Desktop was not closed properly.")) + padding + _networkSettings.width(), h); if (s == size()) { resizeEvent(0); } else { resize(s); } } void LastCrashedWindow::onNetworkSettings() { auto &p = Sandbox::PreLaunchProxy(); NetworkSettingsWindow *box = new NetworkSettingsWindow(this, p.host, p.port ? p.port : 80, p.user, p.password); connect(box, SIGNAL(saved(QString, quint32, QString, QString)), this, SLOT(onNetworkSettingsSaved(QString, quint32, QString, QString))); box->show(); } void LastCrashedWindow::onNetworkSettingsSaved(QString host, quint32 port, QString username, QString password) { Sandbox::RefPreLaunchProxy().host = host; Sandbox::RefPreLaunchProxy().port = port ? port : 80; Sandbox::RefPreLaunchProxy().user = username; Sandbox::RefPreLaunchProxy().password = password; #ifndef TDESKTOP_DISABLE_AUTOUPDATE if ((_updatingState == UpdatingFail && (_sendingState == SendingNoReport || _sendingState == SendingUpdateCheck)) || (_updatingState == UpdatingCheck)) { Sandbox::stopUpdate(); cSetLastUpdateCheck(0); Sandbox::startUpdateCheck(); } else #endif // !TDESKTOP_DISABLE_AUTOUPDATE if (_sendingState == SendingFail || _sendingState == SendingProgress) { onSendReport(); } activate(); } #ifndef TDESKTOP_DISABLE_AUTOUPDATE void LastCrashedWindow::setUpdatingState(UpdatingState state, bool force) { if (_updatingState != state || force) { _updatingState = state; switch (state) { case UpdatingLatest: _updating.setText(qsl("Latest version is installed.")); if (_sendingState == SendingNoReport) { QTimer::singleShot(0, this, SLOT(onContinue())); } else { _sendingState = SendingNone; } break; case UpdatingReady: if (checkReadyUpdate()) { cSetRestartingUpdate(true); App::quit(); return; } else { setUpdatingState(UpdatingFail); return; } break; case UpdatingCheck: _updating.setText(qsl("Checking for updates...")); break; case UpdatingFail: _updating.setText(qsl("Update check failed :(")); break; } updateControls(); } } void LastCrashedWindow::setDownloadProgress(qint64 ready, qint64 total) { qint64 readyTenthMb = (ready * 10 / (1024 * 1024)), totalTenthMb = (total * 10 / (1024 * 1024)); QString readyStr = QString::number(readyTenthMb / 10) + '.' + QString::number(readyTenthMb % 10); QString totalStr = QString::number(totalTenthMb / 10) + '.' + QString::number(totalTenthMb % 10); QString res = qsl("Downloading update {ready} / {total} MB..").replace(qstr("{ready}"), readyStr).replace(qstr("{total}"), totalStr); if (_newVersionDownload != res) { _newVersionDownload = res; _updating.setText(_newVersionDownload); updateControls(); } } void LastCrashedWindow::onUpdateRetry() { cSetLastUpdateCheck(0); Sandbox::startUpdateCheck(); } void LastCrashedWindow::onUpdateSkip() { if (_sendingState == SendingNoReport) { onContinue(); } else { if (_updatingState == UpdatingCheck || _updatingState == UpdatingDownload) { Sandbox::stopUpdate(); setUpdatingState(UpdatingFail); } _sendingState = SendingNone; updateControls(); } } void LastCrashedWindow::onUpdateChecking() { setUpdatingState(UpdatingCheck); } void LastCrashedWindow::onUpdateLatest() { setUpdatingState(UpdatingLatest); } void LastCrashedWindow::onUpdateDownloading(qint64 ready, qint64 total) { setUpdatingState(UpdatingDownload); setDownloadProgress(ready, total); } void LastCrashedWindow::onUpdateReady() { setUpdatingState(UpdatingReady); } void LastCrashedWindow::onUpdateFailed() { setUpdatingState(UpdatingFail); } #endif // !TDESKTOP_DISABLE_AUTOUPDATE void LastCrashedWindow::onContinue() { if (CrashReports::Restart() == CrashReports::CantOpen) { new NotStartedWindow(); } else if (!Global::started()) { Sandbox::launch(); } close(); } void LastCrashedWindow::onSendingError(QNetworkReply::NetworkError e) { LOG(("Crash report sending error: %1").arg(e)); _pleaseSendReport.setText(qsl("Sending crash report failed :(")); _sendingState = SendingFail; if (_checkReply) { _checkReply->deleteLater(); _checkReply = nullptr; } if (_sendReply) { _sendReply->deleteLater(); _sendReply = nullptr; } updateControls(); } void LastCrashedWindow::onSendingFinished() { if (_sendReply) { QByteArray result = _sendReply->readAll(); LOG(("Crash report sending done, result: %1").arg(QString::fromUtf8(result))); _sendReply->deleteLater(); _sendReply = nullptr; _pleaseSendReport.setText(qsl("Thank you for your report!")); _sendingState = SendingDone; updateControls(); CrashReports::Restart(); } } void LastCrashedWindow::onSendingProgress(qint64 uploaded, qint64 total) { if (_sendingState != SendingProgress && _sendingState != SendingUploading) return; _sendingState = SendingUploading; if (total < 0) { _pleaseSendReport.setText(qsl("Sending crash report %1 KB...").arg(uploaded / 1024)); } else { _pleaseSendReport.setText(qsl("Sending crash report %1 / %2 KB...").arg(uploaded / 1024).arg(total / 1024)); } updateControls(); } void LastCrashedWindow::closeEvent(QCloseEvent *e) { deleteLater(); } void LastCrashedWindow::resizeEvent(QResizeEvent *e) { int padding = _size; _label.move(padding, padding + (_networkSettings.height() - _label.height()) / 2); _send.move(width() - padding - _send.width(), height() - padding - _send.height()); if (_sendingState == SendingProgress || _sendingState == SendingUploading) { _sendSkip.move(width() - padding - _sendSkip.width(), height() - padding - _sendSkip.height()); } else { _sendSkip.move(width() - padding - _send.width() - padding - _sendSkip.width(), height() - padding - _sendSkip.height()); } _updating.move(padding, padding * 2 + _networkSettings.height() + (_networkSettings.height() - _updating.height()) / 2); #ifndef TDESKTOP_DISABLE_AUTOUPDATE _pleaseSendReport.move(padding, padding * 2 + _networkSettings.height() + _networkSettings.height() + padding + (_showReport.height() - _pleaseSendReport.height()) / 2); _showReport.move(padding * 2 + _pleaseSendReport.width(), padding * 2 + _networkSettings.height() + _networkSettings.height() + padding); _yourReportName.move(padding, _showReport.y() + _showReport.height() + padding); _includeUsername.move(padding, _yourReportName.y() + _yourReportName.height() + padding); _getApp.move((width() - _getApp.width()) / 2, _showReport.y() + _showReport.height() + padding); if (_sendingState == SendingFail || _sendingState == SendingProgress) { _networkSettings.move(padding * 2 + _pleaseSendReport.width(), padding * 2 + _networkSettings.height() + _networkSettings.height() + padding); } else { _networkSettings.move(padding * 2 + _updating.width(), padding * 2 + _networkSettings.height()); } if (_updatingState == UpdatingCheck || _updatingState == UpdatingDownload) { _updatingCheck.move(width() - padding - _updatingCheck.width(), height() - padding - _updatingCheck.height()); _updatingSkip.move(width() - padding - _updatingSkip.width(), height() - padding - _updatingSkip.height()); } else { _updatingCheck.move(width() - padding - _updatingCheck.width(), height() - padding - _updatingCheck.height()); _updatingSkip.move(width() - padding - _updatingCheck.width() - padding - _updatingSkip.width(), height() - padding - _updatingSkip.height()); } #else // !TDESKTOP_DISABLE_AUTOUPDATE _getApp.move((width() - _getApp.width()) / 2, _updating.y() + _updating.height() + padding); _pleaseSendReport.move(padding, padding * 2 + _networkSettings.height() + _networkSettings.height() + padding + _getApp.height() + padding + (_showReport.height() - _pleaseSendReport.height()) / 2); _showReport.move(padding * 2 + _pleaseSendReport.width(), padding * 2 + _networkSettings.height() + _networkSettings.height() + padding + _getApp.height() + padding); _yourReportName.move(padding, _showReport.y() + _showReport.height() + padding); _includeUsername.move(padding, _yourReportName.y() + _yourReportName.height() + padding); _networkSettings.move(padding * 2 + _pleaseSendReport.width(), padding * 2 + _networkSettings.height() + _networkSettings.height() + padding + _getApp.height() + padding); #endif // else for !TDESKTOP_DISABLE_AUTOUPDATE if (_reportUsername.isEmpty()) { _report.setGeometry(padding, _yourReportName.y() + _yourReportName.height() + padding, width() - 2 * padding, _pleaseSendReport.height() * 12.5); } else { _report.setGeometry(padding, _includeUsername.y() + _includeUsername.height() + padding, width() - 2 * padding, _pleaseSendReport.height() * 12.5); } _minidump.move(padding, _report.y() + _report.height() + padding); _saveReport.move(_showReport.x(), _showReport.y()); _continue.move(width() - padding - _continue.width(), height() - padding - _continue.height()); } NetworkSettingsWindow::NetworkSettingsWindow(QWidget *parent, QString host, quint32 port, QString username, QString password) : PreLaunchWindow(qsl("HTTP Proxy Settings")) , _hostLabel(this) , _portLabel(this) , _usernameLabel(this) , _passwordLabel(this) , _hostInput(this) , _portInput(this) , _usernameInput(this) , _passwordInput(this, true) , _save(this) , _cancel(this, false) , _parent(parent) { setWindowModality(Qt::ApplicationModal); _hostLabel.setText(qsl("Hostname")); _portLabel.setText(qsl("Port")); _usernameLabel.setText(qsl("Username")); _passwordLabel.setText(qsl("Password")); _save.setText(qsl("SAVE")); connect(&_save, SIGNAL(clicked()), this, SLOT(onSave())); _cancel.setText(qsl("CANCEL")); connect(&_cancel, SIGNAL(clicked()), this, SLOT(close())); _hostInput.setText(host); _portInput.setText(QString::number(port)); _usernameInput.setText(username); _passwordInput.setText(password); QRect scr(QApplication::primaryScreen()->availableGeometry()); move(scr.x() + (scr.width() / 6), scr.y() + (scr.height() / 6)); updateControls(); show(); _hostInput.setFocus(); _hostInput.setCursorPosition(_hostInput.text().size()); } void NetworkSettingsWindow::resizeEvent(QResizeEvent *e) { int padding = _size; _hostLabel.move(padding, padding); _hostInput.setGeometry(_hostLabel.x(), _hostLabel.y() + _hostLabel.height(), 2 * _hostLabel.width(), _hostInput.height()); _portLabel.move(padding + _hostInput.width() + padding, padding); _portInput.setGeometry(_portLabel.x(), _portLabel.y() + _portLabel.height(), width() - padding - _portLabel.x(), _portInput.height()); _usernameLabel.move(padding, _hostInput.y() + _hostInput.height() + padding); _usernameInput.setGeometry(_usernameLabel.x(), _usernameLabel.y() + _usernameLabel.height(), (width() - 3 * padding) / 2, _usernameInput.height()); _passwordLabel.move(padding + _usernameInput.width() + padding, _usernameLabel.y()); _passwordInput.setGeometry(_passwordLabel.x(), _passwordLabel.y() + _passwordLabel.height(), width() - padding - _passwordLabel.x(), _passwordInput.height()); _save.move(width() - padding - _save.width(), height() - padding - _save.height()); _cancel.move(_save.x() - padding - _cancel.width(), _save.y()); } void NetworkSettingsWindow::onSave() { QString host = _hostInput.text().trimmed(), port = _portInput.text().trimmed(), username = _usernameInput.text().trimmed(), password = _passwordInput.text().trimmed(); if (!port.isEmpty() && !port.toUInt()) { _portInput.setFocus(); return; } else if (!host.isEmpty() && port.isEmpty()) { _portInput.setFocus(); return; } emit saved(host, port.toUInt(), username, password); close(); } void NetworkSettingsWindow::closeEvent(QCloseEvent *e) { } void NetworkSettingsWindow::updateControls() { _hostInput.updateGeometry(); _hostInput.resize(_hostInput.sizeHint()); _portInput.updateGeometry(); _portInput.resize(_portInput.sizeHint()); _usernameInput.updateGeometry(); _usernameInput.resize(_usernameInput.sizeHint()); _passwordInput.updateGeometry(); _passwordInput.resize(_passwordInput.sizeHint()); int padding = _size; int w = 2 * padding + _hostLabel.width() * 2 + padding + _portLabel.width() * 2 + padding; int h = padding + _hostLabel.height() + _hostInput.height() + padding + _usernameLabel.height() + _usernameInput.height() + padding + _save.height() + padding; if (w == width() && h == height()) { resizeEvent(0); } else { setGeometry(_parent->x() + (_parent->width() - w) / 2, _parent->y() + (_parent->height() - h) / 2, w, h); } } �������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/core/crash_report_window.h�������������������������������������0000664�0000000�0000000�00000011040�13262451251�0024602�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once class PreLaunchWindow : public QWidget { public: PreLaunchWindow(QString title = QString()); void activate(); int basicSize() const { return _size; } ~PreLaunchWindow(); static PreLaunchWindow *instance(); protected: int _size; }; class PreLaunchLabel : public QLabel { public: PreLaunchLabel(QWidget *parent); void setText(const QString &text); }; class PreLaunchInput : public QLineEdit { public: PreLaunchInput(QWidget *parent, bool password = false); }; class PreLaunchLog : public QTextEdit { public: PreLaunchLog(QWidget *parent); }; class PreLaunchButton : public QPushButton { public: PreLaunchButton(QWidget *parent, bool confirm = true); void setText(const QString &text); }; class PreLaunchCheckbox : public QCheckBox { public: PreLaunchCheckbox(QWidget *parent); void setText(const QString &text); }; class NotStartedWindow : public PreLaunchWindow { public: NotStartedWindow(); protected: void closeEvent(QCloseEvent *e); void resizeEvent(QResizeEvent *e); private: void updateControls(); PreLaunchLabel _label; PreLaunchLog _log; PreLaunchButton _close; }; class LastCrashedWindow : public PreLaunchWindow { Q_OBJECT public: LastCrashedWindow(); public slots: void onViewReport(); void onSaveReport(); void onSendReport(); void onGetApp(); void onNetworkSettings(); void onNetworkSettingsSaved(QString host, quint32 port, QString username, QString password); void onContinue(); void onCheckingFinished(); void onSendingError(QNetworkReply::NetworkError e); void onSendingFinished(); void onSendingProgress(qint64 uploaded, qint64 total); #ifndef TDESKTOP_DISABLE_AUTOUPDATE void onUpdateRetry(); void onUpdateSkip(); void onUpdateChecking(); void onUpdateLatest(); void onUpdateDownloading(qint64 ready, qint64 total); void onUpdateReady(); void onUpdateFailed(); #endif // !TDESKTOP_DISABLE_AUTOUPDATE protected: void closeEvent(QCloseEvent *e); void resizeEvent(QResizeEvent *e); private: QString minidumpFileName(); void updateControls(); QString _host, _username, _password; quint32 _port; PreLaunchLabel _label, _pleaseSendReport, _yourReportName, _minidump; PreLaunchLog _report; PreLaunchButton _send, _sendSkip, _networkSettings, _continue, _showReport, _saveReport, _getApp; PreLaunchCheckbox _includeUsername; QString _minidumpName, _minidumpFull, _reportText; QString _reportUsername, _reportTextNoUsername; QByteArray getCrashReportRaw() const; bool _reportShown, _reportSaved; void excludeReportUsername(); enum SendingState { SendingNoReport, SendingUpdateCheck, SendingNone, SendingTooOld, SendingTooMany, SendingUnofficial, SendingProgress, SendingUploading, SendingFail, SendingDone, }; SendingState _sendingState; PreLaunchLabel _updating; qint64 _sendingProgress, _sendingTotal; QNetworkAccessManager _sendManager; QNetworkReply *_checkReply, *_sendReply; #ifndef TDESKTOP_DISABLE_AUTOUPDATE PreLaunchButton _updatingCheck, _updatingSkip; enum UpdatingState { UpdatingNone, UpdatingCheck, UpdatingLatest, UpdatingDownload, UpdatingFail, UpdatingReady }; UpdatingState _updatingState; QString _newVersionDownload; void setUpdatingState(UpdatingState state, bool force = false); void setDownloadProgress(qint64 ready, qint64 total); #endif // !TDESKTOP_DISABLE_AUTOUPDATE QString getReportField(const QLatin1String &name, const QLatin1String &prefix); void addReportFieldPart(const QLatin1String &name, const QLatin1String &prefix, QHttpMultiPart *multipart); }; class NetworkSettingsWindow : public PreLaunchWindow { Q_OBJECT public: NetworkSettingsWindow(QWidget *parent, QString host, quint32 port, QString username, QString password); signals: void saved(QString host, quint32 port, QString username, QString password); public slots: void onSave(); protected: void closeEvent(QCloseEvent *e); void resizeEvent(QResizeEvent *e); private: void updateControls(); PreLaunchLabel _hostLabel, _portLabel, _usernameLabel, _passwordLabel; PreLaunchInput _hostInput, _portInput, _usernameInput, _passwordInput; PreLaunchButton _save, _cancel; QWidget *_parent; }; class ShowCrashReportWindow : public PreLaunchWindow { public: ShowCrashReportWindow(const QString &text); protected: void resizeEvent(QResizeEvent *e); void closeEvent(QCloseEvent *e); private: PreLaunchLog _log; }; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/core/crash_reports.cpp�����������������������������������������0000664�0000000�0000000�00000040123�13262451251�0023735�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "core/crash_reports.h" #include "platform/platform_specific.h" #include <signal.h> #include <new> #include <mutex> #ifndef TDESKTOP_DISABLE_CRASH_REPORTS // see https://blog.inventic.eu/2012/08/qt-and-google-breakpad/ #ifdef Q_OS_WIN #pragma warning(push) #pragma warning(disable:4091) #include "client/windows/handler/exception_handler.h" #pragma warning(pop) #elif defined Q_OS_MAC // Q_OS_WIN #include <execinfo.h> #include <signal.h> #include <sys/syscall.h> #include <dlfcn.h> #include <unistd.h> #ifdef MAC_USE_BREAKPAD #include "client/mac/handler/exception_handler.h" #else // MAC_USE_BREAKPAD #include "client/crashpad_client.h" #endif // else for MAC_USE_BREAKPAD #elif defined Q_OS_LINUX64 || defined Q_OS_LINUX32 // Q_OS_MAC #include <execinfo.h> #include <signal.h> #include <sys/syscall.h> #include "client/linux/handler/exception_handler.h" #endif // Q_OS_LINUX64 || Q_OS_LINUX32 #endif // !TDESKTOP_DISABLE_CRASH_REPORTS namespace CrashReports { namespace { using Annotations = std::map<std::string, std::string>; using AnnotationRefs = std::map<std::string, const QString*>; Annotations ProcessAnnotations; AnnotationRefs ProcessAnnotationRefs; #ifndef TDESKTOP_DISABLE_CRASH_REPORTS QString ReportPath; FILE *ReportFile = nullptr; int ReportFileNo = 0; char LaunchedDateTimeStr[32] = { 0 }; char LaunchedBinaryName[256] = { 0 }; void SafeWriteChar(char ch) { fwrite(&ch, 1, 1, ReportFile); } template <bool Unsigned, typename Type> struct writeNumberSignAndRemoveIt { static void call(Type &number) { if (number < 0) { SafeWriteChar('-'); number = -number; } } }; template <typename Type> struct writeNumberSignAndRemoveIt<true, Type> { static void call(Type &number) { } }; template <typename Type> const dump &SafeWriteNumber(const dump &stream, Type number) { if (!ReportFile) return stream; writeNumberSignAndRemoveIt<(Type(-1) > Type(0)), Type>::call(number); Type upper = 1, prev = number / 10; while (prev >= upper) { upper *= 10; } while (upper > 0) { int digit = (number / upper); SafeWriteChar('0' + digit); number -= digit * upper; upper /= 10; } return stream; } using ReservedMemoryChunk = std::array<gsl::byte, 1024 * 1024>; std::unique_ptr<ReservedMemoryChunk> ReservedMemory; void InstallOperatorNewHandler() { ReservedMemory = std::make_unique<ReservedMemoryChunk>(); std::set_new_handler([] { std::set_new_handler(nullptr); ReservedMemory.reset(); Unexpected("Could not allocate!"); }); } Qt::HANDLE ReportingThreadId = nullptr; bool ReportingHeaderWritten = false; QMutex ReportingMutex; const char *BreakpadDumpPath = nullptr; const wchar_t *BreakpadDumpPathW = nullptr; #if defined Q_OS_MAC || defined Q_OS_LINUX32 || defined Q_OS_LINUX64 struct sigaction SIG_def[32]; void SignalHandler(int signum, siginfo_t *info, void *ucontext) { if (signum > 0) { sigaction(signum, &SIG_def[signum], 0); } #else // Q_OS_MAC || Q_OS_LINUX32 || Q_OS_LINUX64 void SignalHandler(int signum) { #endif // else for Q_OS_MAC || Q_OS_LINUX || Q_OS_LINUX64 const char* name = 0; switch (signum) { case SIGABRT: name = "SIGABRT"; break; case SIGSEGV: name = "SIGSEGV"; break; case SIGILL: name = "SIGILL"; break; case SIGFPE: name = "SIGFPE"; break; #ifndef Q_OS_WIN case SIGBUS: name = "SIGBUS"; break; case SIGSYS: name = "SIGSYS"; break; #endif // !Q_OS_WIN } Qt::HANDLE thread = QThread::currentThreadId(); if (thread == ReportingThreadId) return; QMutexLocker lock(&ReportingMutex); ReportingThreadId = thread; if (!ReportingHeaderWritten) { ReportingHeaderWritten = true; auto dec2hex = [](int value) -> char { if (value >= 0 && value < 10) { return '0' + value; } else if (value >= 10 && value < 16) { return 'a' + (value - 10); } return '#'; }; for (const auto &i : ProcessAnnotationRefs) { QByteArray utf8 = i.second->toUtf8(); std::string wrapped; wrapped.reserve(4 * utf8.size()); for (auto ch : utf8) { auto uch = static_cast<uchar>(ch); wrapped.append("\\x", 2).append(1, dec2hex(uch >> 4)).append(1, dec2hex(uch & 0x0F)); } ProcessAnnotations[i.first] = wrapped; } const Annotations c_ProcessAnnotations(ProcessAnnotations); for (const auto &i : c_ProcessAnnotations) { dump() << i.first.c_str() << ": " << i.second.c_str() << "\n"; } psWriteDump(); dump() << "\n"; } if (name) { dump() << "Caught signal " << signum << " (" << name << ") in thread " << uint64(thread) << "\n"; } else if (signum == -1) { dump() << "Google Breakpad caught a crash, minidump written in thread " << uint64(thread) << "\n"; if (BreakpadDumpPath) { dump() << "Minidump: " << BreakpadDumpPath << "\n"; } else if (BreakpadDumpPathW) { dump() << "Minidump: " << BreakpadDumpPathW << "\n"; } } else { dump() << "Caught signal " << signum << " in thread " << uint64(thread) << "\n"; } // see https://github.com/benbjohnson/bandicoot #if defined Q_OS_MAC || defined Q_OS_LINUX32 || defined Q_OS_LINUX64 ucontext_t *uc = (ucontext_t*)ucontext; void *caller = 0; if (uc) { #if defined(__APPLE__) && !defined(MAC_OS_X_VERSION_10_6) /* OSX < 10.6 */ #if defined(__x86_64__) caller = (void*)uc->uc_mcontext->__ss.__rip; #elif defined(__i386__) caller = (void*)uc->uc_mcontext->__ss.__eip; #else caller = (void*)uc->uc_mcontext->__ss.__srr0; #endif #elif defined(__APPLE__) && defined(MAC_OS_X_VERSION_10_6) /* OSX >= 10.6 */ #if defined(_STRUCT_X86_THREAD_STATE64) && !defined(__i386__) caller = (void*)uc->uc_mcontext->__ss.__rip; #else caller = (void*)uc->uc_mcontext->__ss.__eip; #endif #elif defined(__linux__) /* Linux */ #if defined(__i386__) caller = (void*)uc->uc_mcontext.gregs[14]; /* Linux 32 */ #elif defined(__X86_64__) || defined(__x86_64__) caller = (void*)uc->uc_mcontext.gregs[16]; /* Linux 64 */ #elif defined(__ia64__) /* Linux IA64 */ caller = (void*)uc->uc_mcontext.sc_ip; #endif #endif } void *addresses[132] = { 0 }; size_t size = backtrace(addresses, 128); /* overwrite sigaction with caller's address */ if (caller) { for (int i = size; i > 1; --i) { addresses[i + 3] = addresses[i]; } addresses[2] = (void*)0x1; addresses[3] = caller; addresses[4] = (void*)0x1; } #ifdef Q_OS_MAC dump() << "\nBase image addresses:\n"; for (size_t i = 0; i < size; ++i) { Dl_info info; dump() << i << " "; if (dladdr(addresses[i], &info)) { dump() << uint64(info.dli_fbase) << " (" << info.dli_fname << ")\n"; } else { dump() << "_unknown_module_\n"; } } #endif // Q_OS_MAC dump() << "\nBacktrace:\n"; backtrace_symbols_fd(addresses, size, ReportFileNo); #else // Q_OS_MAC || Q_OS_LINUX32 || Q_OS_LINUX64 dump() << "\nBacktrace:\n"; psWriteStackTrace(); #endif // else for Q_OS_MAC || Q_OS_LINUX32 || Q_OS_LINUX64 dump() << "\n"; ReportingThreadId = nullptr; } bool SetSignalHandlers = true; bool CrashLogged = false; #if !defined Q_OS_MAC || defined MAC_USE_BREAKPAD google_breakpad::ExceptionHandler* BreakpadExceptionHandler = 0; #ifdef Q_OS_WIN bool DumpCallback(const wchar_t* _dump_dir, const wchar_t* _minidump_id, void* context, EXCEPTION_POINTERS* exinfo, MDRawAssertionInfo* assertion, bool success) #elif defined Q_OS_MAC // Q_OS_WIN bool DumpCallback(const char* _dump_dir, const char* _minidump_id, void *context, bool success) #elif defined Q_OS_LINUX64 || defined Q_OS_LINUX32 // Q_OS_MAC bool DumpCallback(const google_breakpad::MinidumpDescriptor &md, void *context, bool success) #endif // Q_OS_LINUX64 || Q_OS_LINUX32 { if (CrashLogged) return success; CrashLogged = true; #ifdef Q_OS_WIN BreakpadDumpPathW = _minidump_id; SignalHandler(-1); #else // Q_OS_WIN #ifdef Q_OS_MAC BreakpadDumpPath = _minidump_id; #else // Q_OS_MAC BreakpadDumpPath = md.path(); #endif // else for Q_OS_MAC SignalHandler(-1, 0, 0); #endif // else for Q_OS_WIN return success; } #endif // !Q_OS_MAC || MAC_USE_BREAKPAD #endif // !TDESKTOP_DISABLE_CRASH_REPORTS } // namespace void StartCatching() { #ifndef TDESKTOP_DISABLE_CRASH_REPORTS ProcessAnnotations["Binary"] = cExeName().toUtf8().constData(); ProcessAnnotations["ApiId"] = QString::number(ApiId).toUtf8().constData(); ProcessAnnotations["Version"] = (cBetaVersion() ? qsl("%1 beta").arg(cBetaVersion()) : (cAlphaVersion() ? qsl("%1 alpha") : qsl("%1")).arg(AppVersion)).toUtf8().constData(); ProcessAnnotations["Launched"] = QDateTime::currentDateTime().toString("dd.MM.yyyy hh:mm:ss").toUtf8().constData(); ProcessAnnotations["Platform"] = cPlatformString().toUtf8().constData(); ProcessAnnotations["UserTag"] = QString::number(Sandbox::UserTag(), 16).toUtf8().constData(); QString dumpspath = cWorkingDir() + qsl("tdata/dumps"); QDir().mkpath(dumpspath); #ifdef Q_OS_WIN BreakpadExceptionHandler = new google_breakpad::ExceptionHandler( dumpspath.toStdWString(), google_breakpad::ExceptionHandler::FilterCallback(nullptr), DumpCallback, (void*)nullptr, // callback_context google_breakpad::ExceptionHandler::HANDLER_ALL, MINIDUMP_TYPE(MiniDumpNormal), // MINIDUMP_TYPE(MiniDumpWithFullMemory | MiniDumpWithHandleData | MiniDumpWithThreadInfo | MiniDumpWithProcessThreadData | MiniDumpWithFullMemoryInfo | MiniDumpWithUnloadedModules | MiniDumpWithFullAuxiliaryState | MiniDumpIgnoreInaccessibleMemory | MiniDumpWithTokenInformation), (const wchar_t*)nullptr, // pipe_name (const google_breakpad::CustomClientInfo*)nullptr ); #elif defined Q_OS_MAC // Q_OS_WIN #ifdef MAC_USE_BREAKPAD #ifndef _DEBUG BreakpadExceptionHandler = new google_breakpad::ExceptionHandler( QFile::encodeName(dumpspath).toStdString(), /*FilterCallback*/ 0, DumpCallback, /*context*/ 0, true, 0 ); #endif // !_DEBUG SetSignalHandlers = false; #else // MAC_USE_BREAKPAD crashpad::CrashpadClient crashpad_client; std::string handler = (cExeDir() + cExeName() + qsl("/Contents/Helpers/crashpad_handler")).toUtf8().constData(); std::string database = QFile::encodeName(dumpspath).constData(); if (crashpad_client.StartHandler(base::FilePath(handler), base::FilePath(database), std::string(), ProcessAnnotations, std::vector<std::string>(), false)) { crashpad_client.UseHandler(); } #endif // else for MAC_USE_BREAKPAD #elif defined Q_OS_LINUX64 || defined Q_OS_LINUX32 BreakpadExceptionHandler = new google_breakpad::ExceptionHandler( google_breakpad::MinidumpDescriptor(QFile::encodeName(dumpspath).toStdString()), /*FilterCallback*/ 0, DumpCallback, /*context*/ 0, true, -1 ); #endif // Q_OS_LINUX64 || Q_OS_LINUX32 #endif // !TDESKTOP_DISABLE_CRASH_REPORTS } void FinishCatching() { #ifndef TDESKTOP_DISABLE_CRASH_REPORTS #if !defined Q_OS_MAC || defined MAC_USE_BREAKPAD delete base::take(BreakpadExceptionHandler); #endif // !Q_OS_MAC || MAC_USE_BREAKPAD #endif // !TDESKTOP_DISABLE_CRASH_REPORTS } Status Start() { #ifndef TDESKTOP_DISABLE_CRASH_REPORTS ReportPath = cWorkingDir() + qsl("tdata/working"); #ifdef Q_OS_WIN FILE *f = nullptr; if (_wfopen_s(&f, ReportPath.toStdWString().c_str(), L"rb") != 0) { f = nullptr; } else { #else // !Q_OS_WIN if (FILE *f = fopen(QFile::encodeName(ReportPath).constData(), "rb")) { #endif // else for !Q_OS_WIN QByteArray lastdump; char buffer[256 * 1024] = { 0 }; int32 read = fread(buffer, 1, 256 * 1024, f); if (read > 0) { lastdump.append(buffer, read); } fclose(f); Sandbox::SetLastCrashDump(lastdump); LOG(("Opened '%1' for reading, the previous Telegram Desktop launch was not finished properly :( Crash log size: %2").arg(ReportPath).arg(lastdump.size())); return LastCrashed; } #endif // !TDESKTOP_DISABLE_CRASH_REPORTS return Restart(); } Status Restart() { #ifndef TDESKTOP_DISABLE_CRASH_REPORTS if (ReportFile) { return Started; } #ifdef Q_OS_WIN if (_wfopen_s(&ReportFile, ReportPath.toStdWString().c_str(), L"wb") != 0) { ReportFile = nullptr; } #else // Q_OS_WIN ReportFile = fopen(QFile::encodeName(ReportPath).constData(), "wb"); #endif // else for Q_OS_WIN if (ReportFile) { #ifdef Q_OS_WIN ReportFileNo = _fileno(ReportFile); #else // Q_OS_WIN ReportFileNo = fileno(ReportFile); #endif // else for Q_OS_WIN if (SetSignalHandlers) { #ifndef Q_OS_WIN struct sigaction sigact; sigact.sa_sigaction = SignalHandler; sigemptyset(&sigact.sa_mask); sigact.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO; sigaction(SIGABRT, &sigact, &SIG_def[SIGABRT]); sigaction(SIGSEGV, &sigact, &SIG_def[SIGSEGV]); sigaction(SIGILL, &sigact, &SIG_def[SIGILL]); sigaction(SIGFPE, &sigact, &SIG_def[SIGFPE]); sigaction(SIGBUS, &sigact, &SIG_def[SIGBUS]); sigaction(SIGSYS, &sigact, &SIG_def[SIGSYS]); #else // !Q_OS_WIN signal(SIGABRT, SignalHandler); signal(SIGSEGV, SignalHandler); signal(SIGILL, SignalHandler); signal(SIGFPE, SignalHandler); #endif // else for !Q_OS_WIN } InstallOperatorNewHandler(); return Started; } LOG(("FATAL: Could not open '%1' for writing!").arg(ReportPath)); return CantOpen; #else // !TDESKTOP_DISABLE_CRASH_REPORTS return Started; #endif // else for !TDESKTOP_DISABLE_CRASH_REPORTS } void Finish() { #ifndef TDESKTOP_DISABLE_CRASH_REPORTS FinishCatching(); if (ReportFile) { fclose(ReportFile); ReportFile = nullptr; #ifdef Q_OS_WIN _wunlink(ReportPath.toStdWString().c_str()); #else // Q_OS_WIN unlink(ReportPath.toUtf8().constData()); #endif // else for Q_OS_WIN } #endif // !TDESKTOP_DISABLE_CRASH_REPORTS } void SetAnnotation(const std::string &key, const QString &value) { static QMutex mutex; QMutexLocker lock(&mutex); if (!value.trimmed().isEmpty()) { ProcessAnnotations[key] = value.toUtf8().constData(); } else { ProcessAnnotations.erase(key); } } void SetAnnotationHex(const std::string &key, const QString &value) { if (value.isEmpty()) { return SetAnnotation(key, value); } const auto utf = value.toUtf8(); auto buffer = std::string(); buffer.reserve(4 * utf.size()); const auto hexDigit = [](std::uint8_t value) { if (value >= 10) { return 'A' + (value - 10); } return '0' + value; }; const auto appendHex = [&](std::uint8_t value) { buffer.push_back('\\'); buffer.push_back('x'); buffer.push_back(hexDigit(value / 16)); buffer.push_back(hexDigit(value % 16)); }; for (const auto ch : utf) { appendHex(ch); } ProcessAnnotations[key] = std::move(buffer); } void SetAnnotationRef(const std::string &key, const QString *valuePtr) { if (valuePtr) { ProcessAnnotationRefs[key] = valuePtr; } else { ProcessAnnotationRefs.erase(key); } } #ifndef TDESKTOP_DISABLE_CRASH_REPORTS dump::~dump() { if (ReportFile) { fflush(ReportFile); } } const dump &operator<<(const dump &stream, const char *str) { if (!ReportFile) return stream; fwrite(str, 1, strlen(str), ReportFile); return stream; } const dump &operator<<(const dump &stream, const wchar_t *str) { if (!ReportFile) return stream; for (int i = 0, l = wcslen(str); i < l; ++i) { if ( #if !defined(__WCHAR_UNSIGNED__) str[i] >= 0 && #endif str[i] < 128) { SafeWriteChar(char(str[i])); } else { SafeWriteChar('?'); } } return stream; } const dump &operator<<(const dump &stream, int num) { return SafeWriteNumber(stream, num); } const dump &operator<<(const dump &stream, unsigned int num) { return SafeWriteNumber(stream, num); } const dump &operator<<(const dump &stream, unsigned long num) { return SafeWriteNumber(stream, num); } const dump &operator<<(const dump &stream, unsigned long long num) { return SafeWriteNumber(stream, num); } const dump &operator<<(const dump &stream, double num) { if (num < 0) { SafeWriteChar('-'); num = -num; } SafeWriteNumber(stream, uint64(floor(num))); SafeWriteChar('.'); num -= floor(num); for (int i = 0; i < 4; ++i) { num *= 10; int digit = int(floor(num)); SafeWriteChar('0' + digit); num -= digit; } return stream; } #endif // TDESKTOP_DISABLE_CRASH_REPORTS } // namespace CrashReports ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/core/crash_reports.h�������������������������������������������0000664�0000000�0000000�00000003667�13262451251�0023416�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace CrashReports { #ifndef TDESKTOP_DISABLE_CRASH_REPORTS struct dump { ~dump(); }; const dump &operator<<(const dump &stream, const char *str); const dump &operator<<(const dump &stream, const wchar_t *str); const dump &operator<<(const dump &stream, int num); const dump &operator<<(const dump &stream, unsigned int num); const dump &operator<<(const dump &stream, unsigned long num); const dump &operator<<(const dump &stream, unsigned long long num); const dump &operator<<(const dump &stream, double num); #endif // TDESKTOP_DISABLE_CRASH_REPORTS enum Status { CantOpen, LastCrashed, Started }; Status Start(); Status Restart(); // can be only CantOpen or Started void Finish(); void SetAnnotation(const std::string &key, const QString &value); void SetAnnotationHex(const std::string &key, const QString &value); inline void ClearAnnotation(const std::string &key) { SetAnnotation(key, QString()); } // Remembers value pointer and tries to add the value to the crash report. // Attention! You should call clearCrashAnnotationRef(key) before destroying value. void SetAnnotationRef(const std::string &key, const QString *valuePtr); inline void ClearAnnotationRef(const std::string &key) { SetAnnotationRef(key, nullptr); } void StartCatching(); void FinishCatching(); } // namespace CrashReports namespace base { namespace assertion { inline void log(const char *message, const char *file, int line) { const auto info = QStringLiteral("%1 %2:%3" ).arg(message ).arg(file ).arg(line ); const auto entry = QStringLiteral("Assertion Failed! ") + info; #ifdef LOG LOG((entry)); #endif // LOG CrashReports::SetAnnotation("Assertion", info); } } // namespace assertion } // namespace base �������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/core/event_filter.cpp������������������������������������������0000664�0000000�0000000�00000001373�13262451251�0023551�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "core/event_filter.h" namespace Core { EventFilter::EventFilter( not_null<QObject*> parent, base::lambda<bool(not_null<QEvent*>)> filter) : QObject(parent) , _filter(std::move(filter)) { parent->installEventFilter(this); } bool EventFilter::eventFilter(QObject *watched, QEvent *event) { return _filter(event); } not_null<QObject*> InstallEventFilter( not_null<QObject*> object, base::lambda<bool(not_null<QEvent*>)> filter) { return new EventFilter(object, std::move(filter)); } } // namespace Core ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/core/event_filter.h��������������������������������������������0000664�0000000�0000000�00000001240�13262451251�0023207�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace Core { class EventFilter : public QObject { public: EventFilter( not_null<QObject*> parent, base::lambda<bool(not_null<QEvent*>)> filter); protected: bool eventFilter(QObject *watched, QEvent *event); private: base::lambda<bool(not_null<QEvent*>)> _filter; }; not_null<QObject*> InstallEventFilter( not_null<QObject*> object, base::lambda<bool(not_null<QEvent*>)> filter); } // namespace Core ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/core/file_utilities.cpp����������������������������������������0000664�0000000�0000000�00000017421�13262451251�0024076�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "core/file_utilities.h" #include "mainwindow.h" #include "storage/localstorage.h" #include "platform/platform_file_utilities.h" #include "messenger.h" bool filedialogGetSaveFile( QString &file, const QString &caption, const QString &filter, const QString &initialPath) { QStringList files; QByteArray remoteContent; bool result = Platform::FileDialog::Get(files, remoteContent, caption, filter, FileDialog::internal::Type::WriteFile, initialPath); file = files.isEmpty() ? QString() : files.at(0); return result; } QString filedialogDefaultName( const QString &prefix, const QString &extension, const QString &path, bool skipExistance, TimeId fileTime) { auto directoryPath = path; if (directoryPath.isEmpty()) { if (cDialogLastPath().isEmpty()) { Platform::FileDialog::InitLastPath(); } directoryPath = cDialogLastPath(); } QString base; if (fileTime) { const auto date = ParseDateTime(fileTime); base = prefix + date.toString("_yyyy-MM-dd_HH-mm-ss"); } else { struct tm tm; time_t t = time(NULL); mylocaltime(&tm, &t); QChar zero('0'); base = prefix + qsl("_%1-%2-%3_%4-%5-%6").arg(tm.tm_year + 1900).arg(tm.tm_mon + 1, 2, 10, zero).arg(tm.tm_mday, 2, 10, zero).arg(tm.tm_hour, 2, 10, zero).arg(tm.tm_min, 2, 10, zero).arg(tm.tm_sec, 2, 10, zero); } QString name; if (skipExistance) { name = base + extension; } else { QDir dir(directoryPath); QString nameBase = dir.absolutePath() + '/' + base; name = nameBase + extension; for (int i = 0; QFileInfo(name).exists(); ++i) { name = nameBase + qsl(" (%1)").arg(i + 2) + extension; } } return name; } QString filedialogNextFilename( const QString &name, const QString &cur, const QString &path) { QDir dir(path.isEmpty() ? cDialogLastPath() : path); int32 extIndex = name.lastIndexOf('.'); QString prefix = name, extension; if (extIndex >= 0) { extension = name.mid(extIndex); prefix = name.mid(0, extIndex); } QString nameBase = dir.absolutePath() + '/' + prefix, result = nameBase + extension; for (int i = 0; result.toLower() != cur.toLower() && QFileInfo(result).exists(); ++i) { result = nameBase + qsl(" (%1)").arg(i + 2) + extension; } return result; } namespace File { void OpenEmailLink(const QString &email) { crl::on_main([=] { Platform::File::UnsafeOpenEmailLink(email); }); } void OpenWith(const QString &filepath, QPoint menuPosition) { InvokeQueued(QApplication::instance(), [=] { if (!Platform::File::UnsafeShowOpenWithDropdown(filepath, menuPosition)) { if (!Platform::File::UnsafeShowOpenWith(filepath)) { Platform::File::UnsafeLaunch(filepath); } } }); } void Launch(const QString &filepath) { crl::on_main([=] { Platform::File::UnsafeLaunch(filepath); }); } void ShowInFolder(const QString &filepath) { crl::on_main([=] { Platform::File::UnsafeShowInFolder(filepath); }); } namespace internal { void UnsafeOpenEmailLinkDefault(const QString &email) { auto url = QUrl(qstr("mailto:") + email); QDesktopServices::openUrl(url); } void UnsafeLaunchDefault(const QString &filepath) { QDesktopServices::openUrl(QUrl::fromLocalFile(filepath)); } } // namespace internal } // namespace File namespace FileDialog { void GetOpenPath( const QString &caption, const QString &filter, base::lambda<void(OpenResult &&result)> callback, base::lambda<void()> failed) { InvokeQueued(QApplication::instance(), [=] { auto files = QStringList(); auto remoteContent = QByteArray(); const auto success = Platform::FileDialog::Get( files, remoteContent, caption, filter, FileDialog::internal::Type::ReadFile); if (success && ((!files.isEmpty() && !files[0].isEmpty()) || !remoteContent.isEmpty())) { if (callback) { auto result = OpenResult(); if (!files.isEmpty() && !files[0].isEmpty()) { result.paths.push_back(files[0]); } result.remoteContent = remoteContent; callback(std::move(result)); } } else if (failed) { failed(); } }); } void GetOpenPaths( const QString &caption, const QString &filter, base::lambda<void(OpenResult &&result)> callback, base::lambda<void()> failed) { InvokeQueued(QApplication::instance(), [=] { auto files = QStringList(); auto remoteContent = QByteArray(); const auto success = Platform::FileDialog::Get( files, remoteContent, caption, filter, FileDialog::internal::Type::ReadFiles); if (success && (!files.isEmpty() || !remoteContent.isEmpty())) { if (callback) { auto result = OpenResult(); result.paths = files; result.remoteContent = remoteContent; callback(std::move(result)); } } else if (failed) { failed(); } }); } void GetWritePath( const QString &caption, const QString &filter, const QString &initialPath, base::lambda<void(QString &&result)> callback, base::lambda<void()> failed) { InvokeQueued(QApplication::instance(), [=] { auto file = QString(); if (filedialogGetSaveFile(file, caption, filter, initialPath)) { if (callback) { callback(std::move(file)); } } else if (failed) { failed(); } }); } void GetFolder( const QString &caption, const QString &initialPath, base::lambda<void(QString &&result)> callback, base::lambda<void()> failed) { InvokeQueued(QApplication::instance(), [=] { auto files = QStringList(); auto remoteContent = QByteArray(); const auto success = Platform::FileDialog::Get( files, remoteContent, caption, QString(), FileDialog::internal::Type::ReadFolder, initialPath); if (success && !files.isEmpty() && !files[0].isEmpty()) { if (callback) { callback(std::move(files[0])); } } else if (failed) { failed(); } }); } QString AllFilesFilter() { #ifdef Q_OS_WIN return qsl("All files (*.*)"); #else // Q_OS_WIN return qsl("All files (*)"); #endif // Q_OS_WIN } namespace internal { void InitLastPathDefault() { cSetDialogLastPath(QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)); } bool GetDefault(QStringList &files, QByteArray &remoteContent, const QString &caption, const QString &filter, FileDialog::internal::Type type, QString startFile = QString()) { if (cDialogLastPath().isEmpty()) { Platform::FileDialog::InitLastPath(); } remoteContent = QByteArray(); if (startFile.isEmpty() || startFile.at(0) != '/') { startFile = cDialogLastPath() + '/' + startFile; } QString file; if (type == Type::ReadFiles) { files = QFileDialog::getOpenFileNames(Messenger::Instance().getFileDialogParent(), caption, startFile, filter); QString path = files.isEmpty() ? QString() : QFileInfo(files.back()).absoluteDir().absolutePath(); if (!path.isEmpty() && path != cDialogLastPath()) { cSetDialogLastPath(path); Local::writeUserSettings(); } return !files.isEmpty(); } else if (type == Type::ReadFolder) { file = QFileDialog::getExistingDirectory(Messenger::Instance().getFileDialogParent(), caption, startFile); } else if (type == Type::WriteFile) { file = QFileDialog::getSaveFileName(Messenger::Instance().getFileDialogParent(), caption, startFile, filter); } else { file = QFileDialog::getOpenFileName(Messenger::Instance().getFileDialogParent(), caption, startFile, filter); } if (file.isEmpty()) { files = QStringList(); return false; } if (type != Type::ReadFolder) { // Save last used directory for all queries except directory choosing. auto path = QFileInfo(file).absoluteDir().absolutePath(); if (!path.isEmpty() && path != cDialogLastPath()) { cSetDialogLastPath(path); Local::writeUserSettings(); } } files = QStringList(file); return true; } } // namespace internal } // namespace FileDialog �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/core/file_utilities.h������������������������������������������0000664�0000000�0000000�00000004571�13262451251�0023545�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "base/observer.h" // legacy bool filedialogGetSaveFile( QString &file, const QString &caption, const QString &filter, const QString &initialPath); QString filedialogDefaultName( const QString &prefix, const QString &extension, const QString &path = QString(), bool skipExistance = false, TimeId fileTime = TimeId(0)); QString filedialogNextFilename( const QString &name, const QString &cur, const QString &path = QString()); namespace File { // Those functions are async wrappers to Platform::File::Unsafe* calls. void OpenEmailLink(const QString &email); void OpenWith(const QString &filepath, QPoint menuPosition); void Launch(const QString &filepath); void ShowInFolder(const QString &filepath); namespace internal { inline QString UrlToLocalDefault(const QUrl &url) { return url.toLocalFile(); } void UnsafeOpenEmailLinkDefault(const QString &email); void UnsafeLaunchDefault(const QString &filepath); } // namespace internal } // namespace File namespace FileDialog { struct OpenResult { QStringList paths; QByteArray remoteContent; }; void GetOpenPath( const QString &caption, const QString &filter, base::lambda<void(OpenResult &&result)> callback, base::lambda<void()> failed = base::lambda<void()>()); void GetOpenPaths( const QString &caption, const QString &filter, base::lambda<void(OpenResult &&result)> callback, base::lambda<void()> failed = base::lambda<void()>()); void GetWritePath( const QString &caption, const QString &filter, const QString &initialPath, base::lambda<void(QString &&result)> callback, base::lambda<void()> failed = base::lambda<void()>()); void GetFolder( const QString &caption, const QString &initialPath, base::lambda<void(QString &&result)> callback, base::lambda<void()> failed = base::lambda<void()>()); QString AllFilesFilter(); namespace internal { enum class Type { ReadFile, ReadFiles, ReadFolder, WriteFile, }; void InitLastPathDefault(); bool GetDefault( QStringList &files, QByteArray &remoteContent, const QString &caption, const QString &filter, ::FileDialog::internal::Type type, QString startFile); } // namespace internal } // namespace FileDialog ���������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/core/launcher.cpp����������������������������������������������0000664�0000000�0000000�00000014623�13262451251�0022666�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "core/launcher.h" #include "platform/platform_launcher.h" #include "platform/platform_specific.h" #include "core/crash_reports.h" #include "core/main_queue_processor.h" #include "application.h" namespace Core { std::unique_ptr<Launcher> Launcher::Create(int argc, char *argv[]) { return std::make_unique<Platform::Launcher>(argc, argv); } Launcher::Launcher(int argc, char *argv[]) : _argc(argc) , _argv(argv) { } void Launcher::init() { _arguments = readArguments(_argc, _argv); prepareSettings(); QCoreApplication::setApplicationName(qsl("TelegramDesktop")); #ifndef OS_MAC_OLD QCoreApplication::setAttribute(Qt::AA_DisableHighDpiScaling, true); #endif // OS_MAC_OLD initHook(); } int Launcher::exec() { init(); if (cLaunchMode() == LaunchModeFixPrevious) { return psFixPrevious(); } else if (cLaunchMode() == LaunchModeCleanup) { return psCleanup(); } // both are finished in Application::closeApplication Logs::start(this); // must be started before Platform is started Platform::start(); // must be started before QApplication is created auto result = executeApplication(); DEBUG_LOG(("Telegram finished, result: %1").arg(result)); #ifndef TDESKTOP_DISABLE_AUTOUPDATE if (cRestartingUpdate()) { DEBUG_LOG(("Application Info: executing updater to install update...")); if (!launchUpdater(UpdaterLaunch::PerformUpdate)) { psDeleteDir(cWorkingDir() + qsl("tupdates/temp")); } } else #endif // !TDESKTOP_DISABLE_AUTOUPDATE if (cRestarting()) { DEBUG_LOG(("Application Info: executing Telegram, because of restart...")); launchUpdater(UpdaterLaunch::JustRelaunch); } CrashReports::Finish(); Platform::finish(); Logs::finish(); return result; } QStringList Launcher::readArguments(int argc, char *argv[]) const { Expects(argc >= 0); if (const auto native = readArgumentsHook(argc, argv)) { return *native; } auto result = QStringList(); result.reserve(argc); for (auto i = 0; i != argc; ++i) { result.push_back(fromUtf8Safe(argv[i])); } return result; } QString Launcher::argumentsString() const { return _arguments.join(' '); } void Launcher::prepareSettings() { #ifdef Q_OS_MAC #ifndef OS_MAC_OLD if (QSysInfo::macVersion() >= QSysInfo::MV_10_11) { gIsElCapitan = true; } #else // OS_MAC_OLD if (QSysInfo::macVersion() < QSysInfo::MV_10_7) { gIsSnowLeopard = true; } #endif // OS_MAC_OLD #endif // Q_OS_MAC switch (cPlatform()) { case dbipWindows: gUpdateURL = QUrl(qsl("http://tdesktop.com/win/tupdates/current")); #ifndef OS_WIN_STORE gPlatformString = qsl("Windows"); #else // OS_WIN_STORE gPlatformString = qsl("WinStore"); #endif // OS_WIN_STORE break; case dbipMac: gUpdateURL = QUrl(qsl("http://tdesktop.com/mac/tupdates/current")); #ifndef OS_MAC_STORE gPlatformString = qsl("MacOS"); #else // OS_MAC_STORE gPlatformString = qsl("MacAppStore"); #endif // OS_MAC_STORE break; case dbipMacOld: gUpdateURL = QUrl(qsl("http://tdesktop.com/mac32/tupdates/current")); gPlatformString = qsl("MacOSold"); break; case dbipLinux64: gUpdateURL = QUrl(qsl("http://tdesktop.com/linux/tupdates/current")); gPlatformString = qsl("Linux64bit"); break; case dbipLinux32: gUpdateURL = QUrl(qsl("http://tdesktop.com/linux32/tupdates/current")); gPlatformString = qsl("Linux32bit"); break; } auto path = Platform::CurrentExecutablePath(_argc, _argv); LOG(("Executable path before check: %1").arg(path)); if (!path.isEmpty()) { auto info = QFileInfo(path); if (info.isSymLink()) { info = info.symLinkTarget(); } if (info.exists()) { gExeDir = info.absoluteDir().absolutePath() + '/'; gExeName = info.fileName(); } } if (cExeName().isEmpty()) { LOG(("WARNING: Could not compute executable path, some features will be disabled.")); } processArguments(); } void Launcher::processArguments() { enum class KeyFormat { NoValues, OneValue, AllLeftValues, }; auto parseMap = std::map<QByteArray, KeyFormat> { { "-testmode" , KeyFormat::NoValues }, { "-debug" , KeyFormat::NoValues }, { "-many" , KeyFormat::NoValues }, { "-key" , KeyFormat::OneValue }, { "-autostart" , KeyFormat::NoValues }, { "-fixprevious", KeyFormat::NoValues }, { "-cleanup" , KeyFormat::NoValues }, { "-noupdate" , KeyFormat::NoValues }, { "-tosettings" , KeyFormat::NoValues }, { "-startintray", KeyFormat::NoValues }, { "-sendpath" , KeyFormat::AllLeftValues }, { "-workdir" , KeyFormat::OneValue }, { "--" , KeyFormat::OneValue }, }; auto parseResult = QMap<QByteArray, QStringList>(); auto parsingKey = QByteArray(); auto parsingFormat = KeyFormat::NoValues; for (const auto &argument : _arguments) { switch (parsingFormat) { case KeyFormat::OneValue: { parseResult[parsingKey] = QStringList(argument.mid(0, 8192)); parsingFormat = KeyFormat::NoValues; } break; case KeyFormat::AllLeftValues: { parseResult[parsingKey].push_back(argument.mid(0, 8192)); } break; case KeyFormat::NoValues: { parsingKey = argument.toLatin1(); auto it = parseMap.find(parsingKey); if (it != parseMap.end()) { parsingFormat = it->second; parseResult[parsingKey] = QStringList(); } } break; } } gTestMode = parseResult.contains("-testmode"); gDebug = parseResult.contains("-debug"); gManyInstance = parseResult.contains("-many"); gKeyFile = parseResult.value("-key", QStringList()).join(QString()); gLaunchMode = parseResult.contains("-autostart") ? LaunchModeAutoStart : parseResult.contains("-fixprevious") ? LaunchModeFixPrevious : parseResult.contains("-cleanup") ? LaunchModeCleanup : LaunchModeNormal; gNoStartUpdate = parseResult.contains("-noupdate"); gStartToSettings = parseResult.contains("-tosettings"); gStartInTray = parseResult.contains("-startintray"); gSendPaths = parseResult.value("-sendpath", QStringList()); gWorkingDir = parseResult.value("-workdir", QStringList()).join(QString()); if (!gWorkingDir.isEmpty()) { if (QDir().exists(gWorkingDir)) { _customWorkingDir = true; } else { gWorkingDir = QString(); } } gStartUrl = parseResult.value("--", QStringList()).join(QString()); } int Launcher::executeApplication() { MainQueueProcessor processor; Application app(this, _argc, _argv); return app.exec(); } } // namespace Core �������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/core/launcher.h������������������������������������������������0000664�0000000�0000000�00000002115�13262451251�0022324�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace Core { class Launcher { public: Launcher(int argc, char *argv[]); static std::unique_ptr<Launcher> Create(int argc, char *argv[]); int exec(); QString argumentsString() const; bool customWorkingDir() const { return _customWorkingDir; } virtual ~Launcher() = default; protected: enum class UpdaterLaunch { PerformUpdate, JustRelaunch, }; private: void prepareSettings(); void processArguments(); QStringList readArguments(int argc, char *argv[]) const; virtual base::optional<QStringList> readArgumentsHook( int argc, char *argv[]) const { return base::none; } void init(); virtual void initHook() { } virtual bool launchUpdater(UpdaterLaunch action) = 0; int executeApplication(); int _argc; char **_argv; QStringList _arguments; bool _customWorkingDir = false; }; } // namespace Core ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/core/main_queue_processor.cpp����������������������������������0000664�0000000�0000000�00000003453�13262451251�0025313�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "core/main_queue_processor.h" namespace Core { namespace { QMutex ProcessorMutex; MainQueueProcessor *ProcessorInstance/* = nullptr*/; constexpr auto kProcessorEvent = QEvent::Type(QEvent::User + 1); static_assert(kProcessorEvent < QEvent::MaxUser); class ProcessorEvent : public QEvent { public: ProcessorEvent(void (*callable)(void*), void *argument); void process(); private: void (*_callable)(void*) = nullptr; void *_argument = nullptr; }; ProcessorEvent::ProcessorEvent(void (*callable)(void*), void *argument) : QEvent(kProcessorEvent) , _callable(callable) , _argument(argument) { } void ProcessorEvent::process() { _callable(_argument); } void ProcessMainQueue(void (*callable)(void*), void *argument) { QMutexLocker lock(&ProcessorMutex); if (ProcessorInstance) { const auto event = new ProcessorEvent(callable, argument); QCoreApplication::postEvent(ProcessorInstance, event); } } } // namespace MainQueueProcessor::MainQueueProcessor() { acquire(); crl::init_main_queue(ProcessMainQueue); } bool MainQueueProcessor::event(QEvent *event) { if (event->type() == kProcessorEvent) { static_cast<ProcessorEvent*>(event)->process(); return true; } return QObject::event(event); } void MainQueueProcessor::acquire() { Expects(ProcessorInstance == nullptr); QMutexLocker lock(&ProcessorMutex); ProcessorInstance = this; } void MainQueueProcessor::release() { Expects(ProcessorInstance == this); QMutexLocker lock(&ProcessorMutex); ProcessorInstance = nullptr; } MainQueueProcessor::~MainQueueProcessor() { release(); } } // namespace ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/core/main_queue_processor.h������������������������������������0000664�0000000�0000000�00000000752�13262451251�0024757�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace Core { class MainQueueProcessor : public QObject { public: MainQueueProcessor(); ~MainQueueProcessor(); protected: bool event(QEvent *event) override; private: void acquire(); void release(); }; } // namespace Core ����������������������tdesktop-1.2.17/Telegram/SourceFiles/core/single_timer.cpp������������������������������������������0000664�0000000�0000000�00000002433�13262451251�0023542�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "core/single_timer.h" #include "application.h" SingleTimer::SingleTimer(QObject *parent) : QTimer(parent) { QTimer::setSingleShot(true); Sandbox::connect(SIGNAL(adjustSingleTimers()), this, SLOT(adjust())); } void SingleTimer::setTimeoutHandler(base::lambda<void()> handler) { if (_handler && !handler) { disconnect(this, SIGNAL(timeout()), this, SLOT(onTimeout())); } else if (handler && !_handler) { connect(this, SIGNAL(timeout()), this, SLOT(onTimeout())); } _handler = std::move(handler); } void SingleTimer::adjust() { auto n = getms(true); if (isActive()) { if (n >= _finishing) { start(0); } else { start(_finishing - n); } } } void SingleTimer::onTimeout() { if (_handler) { _handler(); } } void SingleTimer::start(int msec) { _finishing = getms(true) + (msec < 0 ? 0 : msec); QTimer::start(msec); } void SingleTimer::startIfNotActive(int msec) { if (isActive()) { int remains = remainingTime(); if (remains > msec) { start(msec); } else if (!remains) { start(1); } } else { start(msec); } } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/core/single_timer.h��������������������������������������������0000664�0000000�0000000�00000001351�13262451251�0023205�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "core/basic_types.h" class SingleTimer : public QTimer { // single shot timer with check Q_OBJECT public: SingleTimer(QObject *parent = nullptr); void setSingleShot(bool); // is not available void start(); // is not available void setTimeoutHandler(base::lambda<void()> handler); public slots: void start(int msec); void startIfNotActive(int msec); private slots: void adjust(); void onTimeout(); private: TimeMs _finishing = 0; base::lambda<void()> _handler; }; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/core/tl_help.h�������������������������������������������������0000664�0000000�0000000�00000002727�13262451251�0022163�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace TLHelp { template <typename Callback> inline auto VisitChannelParticipant( const MTPChannelParticipant &p, Callback &&callback) { switch (p.type()) { case mtpc_channelParticipant: return callback(p.c_channelParticipant()); case mtpc_channelParticipantSelf: return callback(p.c_channelParticipantSelf()); case mtpc_channelParticipantAdmin: return callback(p.c_channelParticipantAdmin()); case mtpc_channelParticipantCreator: return callback(p.c_channelParticipantCreator()); case mtpc_channelParticipantBanned: return callback(p.c_channelParticipantBanned()); default: Unexpected("Type in VisitChannelParticipant()"); } } inline UserId ReadChannelParticipantUserId(const MTPChannelParticipant &p) { return VisitChannelParticipant(p, [](auto &&data) { return data.vuser_id.v; }); } template <typename Callback> inline auto VisitChannelParticipants( const MTPchannels_ChannelParticipants &p, Callback &&callback) { switch (p.type()) { case mtpc_channels_channelParticipants: return callback(p.c_channels_channelParticipants()); case mtpc_channels_channelParticipantsNotModified: return callback(p.type()); default: Unexpected("Type in VisitChannelParticipants()"); } } } // namespace TLHelp �����������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/core/utils.cpp�������������������������������������������������0000664�0000000�0000000�00000111624�13262451251�0022224�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "core/utils.h" #include <openssl/crypto.h> #include <openssl/sha.h> #include <openssl/err.h> #include <openssl/evp.h> #include <openssl/engine.h> #include <openssl/conf.h> #include <openssl/ssl.h> extern "C" { #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> } #include "application.h" #include "platform/platform_specific.h" uint64 _SharedMemoryLocation[4] = { 0x00, 0x01, 0x02, 0x03 }; #ifdef Q_OS_WIN #elif defined Q_OS_MAC #include <mach/mach_time.h> #else #include <time.h> #endif #include <openssl/rand.h> // Base types compile-time check static_assert(sizeof(char) == 1, "Basic types size check failed"); static_assert(sizeof(uchar) == 1, "Basic types size check failed"); static_assert(sizeof(int16) == 2, "Basic types size check failed"); static_assert(sizeof(uint16) == 2, "Basic types size check failed"); static_assert(sizeof(int32) == 4, "Basic types size check failed"); static_assert(sizeof(uint32) == 4, "Basic types size check failed"); static_assert(sizeof(int64) == 8, "Basic types size check failed"); static_assert(sizeof(uint64) == 8, "Basic types size check failed"); static_assert(sizeof(float32) == 4, "Basic types size check failed"); static_assert(sizeof(float64) == 8, "Basic types size check failed"); static_assert(sizeof(mtpPrime) == 4, "Basic types size check failed"); static_assert(sizeof(MTPint) == 4, "Basic types size check failed"); static_assert(sizeof(MTPlong) == 8, "Basic types size check failed"); static_assert(sizeof(MTPint128) == 16, "Basic types size check failed"); static_assert(sizeof(MTPint256) == 32, "Basic types size check failed"); static_assert(sizeof(MTPdouble) == 8, "Basic types size check failed"); // Unixtime functions namespace { QReadWriteLock unixtimeLock; volatile int32 unixtimeDelta = 0; volatile bool unixtimeWasSet = false; volatile uint64 _msgIdStart, _msgIdLocal = 0, _msgIdMsStart; int32 _reqId = 0; void _initMsgIdConstants() { #ifdef Q_OS_WIN LARGE_INTEGER li; QueryPerformanceCounter(&li); _msgIdMsStart = li.QuadPart; #elif defined Q_OS_MAC _msgIdMsStart = mach_absolute_time(); #else timespec ts; clock_gettime(CLOCK_REALTIME, &ts); _msgIdMsStart = 1000000000 * uint64(ts.tv_sec) + uint64(ts.tv_nsec); #endif uint32 msgIdRand; memset_rand(&msgIdRand, sizeof(uint32)); _msgIdStart = (((uint64)((uint32)unixtime()) << 32) | (uint64)msgIdRand); } } TimeId LocalUnixtime() { return (TimeId)time(NULL); } void unixtimeInit() { { QWriteLocker locker(&unixtimeLock); unixtimeWasSet = false; unixtimeDelta = 0; } _initMsgIdConstants(); } void unixtimeSet(int32 serverTime, bool force) { { QWriteLocker locker(&unixtimeLock); if (force) { DEBUG_LOG(("MTP Info: forced setting client unixtime to %1").arg(serverTime)); } else { if (unixtimeWasSet) return; DEBUG_LOG(("MTP Info: setting client unixtime to %1").arg(serverTime)); } unixtimeWasSet = true; unixtimeDelta = serverTime + 1 - LocalUnixtime(); DEBUG_LOG(("MTP Info: now unixtimeDelta is %1").arg(unixtimeDelta)); } _initMsgIdConstants(); } TimeId unixtime() { auto result = LocalUnixtime(); QReadLocker locker(&unixtimeLock); return result + unixtimeDelta; } QDateTime ParseDateTime(TimeId serverTime) { if (serverTime <= 0) { return QDateTime(); } QReadLocker locker(&unixtimeLock); return QDateTime::fromTime_t(serverTime - unixtimeDelta); } // Precise timing functions / rand init struct CRYPTO_dynlock_value { QMutex mutex; }; namespace { bool _sslInited = false; QMutex *_sslLocks = nullptr; void _sslLockingCallback(int mode, int type, const char *file, int line) { if (!_sslLocks) return; // not inited if (mode & CRYPTO_LOCK) { _sslLocks[type].lock(); } else { _sslLocks[type].unlock(); } } void _sslThreadId(CRYPTO_THREADID *id) { CRYPTO_THREADID_set_pointer(id, QThread::currentThreadId()); } CRYPTO_dynlock_value *_sslCreateFunction(const char *file, int line) { return new CRYPTO_dynlock_value(); } void _sslLockFunction(int mode, CRYPTO_dynlock_value *l, const char *file, int line) { if (mode & CRYPTO_LOCK) { l->mutex.lock(); } else { l->mutex.unlock(); } } void _sslDestroyFunction(CRYPTO_dynlock_value *l, const char *file, int line) { delete l; } int _ffmpegLockManager(void **mutex, AVLockOp op) { switch (op) { case AV_LOCK_CREATE: { Assert(*mutex == 0); *mutex = reinterpret_cast<void*>(new QMutex()); } break; case AV_LOCK_OBTAIN: { Assert(*mutex != 0); reinterpret_cast<QMutex*>(*mutex)->lock(); } break; case AV_LOCK_RELEASE: { Assert(*mutex != 0); reinterpret_cast<QMutex*>(*mutex)->unlock(); }; break; case AV_LOCK_DESTROY: { Assert(*mutex != 0); delete reinterpret_cast<QMutex*>(*mutex); *mutex = 0; } break; } return 0; } float64 _msFreq; float64 _msgIdCoef; TimeMs _msStart = 0, _msAddToMsStart = 0, _msAddToUnixtime = 0; int32 _timeStart = 0; class _MsInitializer { public: _MsInitializer() { #ifdef Q_OS_WIN LARGE_INTEGER li; QueryPerformanceFrequency(&li); _msFreq = 1000. / float64(li.QuadPart); // 0xFFFF0000L istead of 0x100000000L to make msgId grow slightly slower, than unixtime and we had time to reconfigure _msgIdCoef = float64(0xFFFF0000L) / float64(li.QuadPart); QueryPerformanceCounter(&li); _msStart = li.QuadPart; #elif defined Q_OS_MAC mach_timebase_info_data_t tb = { 0, 0 }; mach_timebase_info(&tb); _msFreq = (float64(tb.numer) / tb.denom) / 1000000.; _msgIdCoef = _msFreq * (float64(0xFFFF0000L) / 1000.); _msStart = mach_absolute_time(); #else timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); //_msFreq = 1 / 1000000.; _msgIdCoef = float64(0xFFFF0000L) / 1000000000.; _msStart = 1000LL * static_cast<TimeMs>(ts.tv_sec) + (static_cast<TimeMs>(ts.tv_nsec) / 1000000LL); #endif _timeStart = LocalUnixtime(); srand((uint32)(_msStart & 0xFFFFFFFFL)); } }; void _msInitialize() { static _MsInitializer _msInitializer; } class _MsStarter { public: _MsStarter() { getms(); } }; _MsStarter _msStarter; } namespace ThirdParty { void start() { Platform::ThirdParty::start(); if (!RAND_status()) { // should be always inited in all modern OS char buf[16]; memcpy(buf, &_msStart, 8); memcpy(buf + 8, &_msFreq, 8); uchar sha256Buffer[32]; RAND_seed(hashSha256(buf, 16, sha256Buffer), 32); if (!RAND_status()) { LOG(("MTP Error: Could not init OpenSSL rand, RAND_status() is 0...")); } } // Force OpenSSL loading if it is linked in Qt, // so that we won't mess with our OpenSSL locking with Qt OpenSSL locking. auto sslSupported = QSslSocket::supportsSsl(); if (!sslSupported) { LOG(("Error: current Qt build doesn't support SSL requests.")); } if (!CRYPTO_get_locking_callback()) { // Qt didn't initialize OpenSSL, so we will. auto numLocks = CRYPTO_num_locks(); if (numLocks) { _sslLocks = new QMutex[numLocks]; CRYPTO_set_locking_callback(_sslLockingCallback); } else { LOG(("MTP Error: Could not init OpenSSL threads, CRYPTO_num_locks() returned zero!")); } CRYPTO_THREADID_set_callback(_sslThreadId); } if (!CRYPTO_get_dynlock_create_callback()) { CRYPTO_set_dynlock_create_callback(_sslCreateFunction); CRYPTO_set_dynlock_lock_callback(_sslLockFunction); CRYPTO_set_dynlock_destroy_callback(_sslDestroyFunction); } else if (!CRYPTO_get_dynlock_lock_callback()) { LOG(("MTP Error: dynlock_create callback is set without dynlock_lock callback!")); } av_register_all(); avcodec_register_all(); av_lockmgr_register(_ffmpegLockManager); _sslInited = true; } void finish() { av_lockmgr_register(nullptr); CRYPTO_cleanup_all_ex_data(); #ifndef LIBRESSL_VERSION_NUMBER FIPS_mode_set(0); #endif ENGINE_cleanup(); CONF_modules_unload(1); ERR_remove_state(0); ERR_free_strings(); ERR_remove_thread_state(nullptr); EVP_cleanup(); delete[] base::take(_sslLocks); Platform::ThirdParty::finish(); } } bool checkms() { auto unixms = (LocalUnixtime() - _timeStart) * 1000LL + _msAddToUnixtime; auto ms = getms(true); if (ms > unixms + 1000LL) { _msAddToUnixtime = ((ms - unixms) / 1000LL) * 1000LL; } else if (unixms > ms + 1000LL) { _msAddToMsStart += ((unixms - ms) / 1000LL) * 1000LL; Sandbox::adjustSingleTimers(); return true; } return false; } TimeMs getms(bool checked) { _msInitialize(); #ifdef Q_OS_WIN LARGE_INTEGER li; QueryPerformanceCounter(&li); return ((li.QuadPart - _msStart) * _msFreq) + (checked ? _msAddToMsStart : 0LL); #elif defined Q_OS_MAC auto msCount = static_cast<TimeMs>(mach_absolute_time()); return ((msCount - _msStart) * _msFreq) + (checked ? _msAddToMsStart : 0LL); #else timespec ts; auto res = clock_gettime(CLOCK_MONOTONIC, &ts); if (res != 0) { LOG(("Bad clock_gettime result: %1").arg(res)); return 0; } auto msCount = 1000LL * static_cast<TimeMs>(ts.tv_sec) + (static_cast<TimeMs>(ts.tv_nsec) / 1000000LL); return (msCount - _msStart) + (checked ? _msAddToMsStart : 0LL); #endif } uint64 msgid() { #ifdef Q_OS_WIN LARGE_INTEGER li; QueryPerformanceCounter(&li); uint64 result = _msgIdStart + (uint64)floor((li.QuadPart - _msgIdMsStart) * _msgIdCoef); #elif defined Q_OS_MAC uint64 msCount = mach_absolute_time(); uint64 result = _msgIdStart + (uint64)floor((msCount - _msgIdMsStart) * _msgIdCoef); #else timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); uint64 msCount = 1000000000 * uint64(ts.tv_sec) + uint64(ts.tv_nsec); uint64 result = _msgIdStart + (uint64)floor((msCount - _msgIdMsStart) * _msgIdCoef); #endif result &= ~0x03L; return result + (_msgIdLocal += 4); } int32 reqid() { QWriteLocker locker(&unixtimeLock); if (_reqId == INT_MAX) { _reqId = 0; } return ++_reqId; } // crc32 hash, taken somewhere from the internet namespace { uint32 _crc32Table[256]; class _Crc32Initializer { public: _Crc32Initializer() { uint32 poly = 0x04c11db7; for (uint32 i = 0; i < 256; ++i) { _crc32Table[i] = reflect(i, 8) << 24; for (uint32 j = 0; j < 8; ++j) { _crc32Table[i] = (_crc32Table[i] << 1) ^ (_crc32Table[i] & (1 << 31) ? poly : 0); } _crc32Table[i] = reflect(_crc32Table[i], 32); } } private: uint32 reflect(uint32 val, char ch) { uint32 result = 0; for (int i = 1; i < (ch + 1); ++i) { if (val & 1) { result |= 1 << (ch - i); } val >>= 1; } return result; } }; } int32 hashCrc32(const void *data, uint32 len) { static _Crc32Initializer _crc32Initializer; const uchar *buf = (const uchar *)data; uint32 crc(0xffffffff); for (uint32 i = 0; i < len; ++i) { crc = (crc >> 8) ^ _crc32Table[(crc & 0xFF) ^ buf[i]]; } return crc ^ 0xffffffff; } int32 *hashSha1(const void *data, uint32 len, void *dest) { return (int32*)SHA1((const uchar*)data, (size_t)len, (uchar*)dest); } int32 *hashSha256(const void *data, uint32 len, void *dest) { return (int32*)SHA256((const uchar*)data, (size_t)len, (uchar*)dest); } // md5 hash, taken somewhere from the internet namespace { inline void _md5_decode(uint32 *output, const uchar *input, uint32 len) { for (uint32 i = 0, j = 0; j < len; i++, j += 4) { output[i] = ((uint32)input[j]) | (((uint32)input[j + 1]) << 8) | (((uint32)input[j + 2]) << 16) | (((uint32)input[j + 3]) << 24); } } inline void _md5_encode(uchar *output, const uint32 *input, uint32 len) { for (uint32 i = 0, j = 0; j < len; i++, j += 4) { output[j + 0] = (input[i]) & 0xFF; output[j + 1] = (input[i] >> 8) & 0xFF; output[j + 2] = (input[i] >> 16) & 0xFF; output[j + 3] = (input[i] >> 24) & 0xFF; } } inline uint32 _md5_rotate_left(uint32 x, int n) { return (x << n) | (x >> (32 - n)); } inline uint32 _md5_F(uint32 x, uint32 y, uint32 z) { return (x & y) | (~x & z); } inline uint32 _md5_G(uint32 x, uint32 y, uint32 z) { return (x & z) | (y & ~z); } inline uint32 _md5_H(uint32 x, uint32 y, uint32 z) { return x ^ y ^ z; } inline uint32 _md5_I(uint32 x, uint32 y, uint32 z) { return y ^ (x | ~z); } inline void _md5_FF(uint32 &a, uint32 b, uint32 c, uint32 d, uint32 x, uint32 s, uint32 ac) { a = _md5_rotate_left(a + _md5_F(b, c, d) + x + ac, s) + b; } inline void _md5_GG(uint32 &a, uint32 b, uint32 c, uint32 d, uint32 x, uint32 s, uint32 ac) { a = _md5_rotate_left(a + _md5_G(b, c, d) + x + ac, s) + b; } inline void _md5_HH(uint32 &a, uint32 b, uint32 c, uint32 d, uint32 x, uint32 s, uint32 ac) { a = _md5_rotate_left(a + _md5_H(b, c, d) + x + ac, s) + b; } inline void _md5_II(uint32 &a, uint32 b, uint32 c, uint32 d, uint32 x, uint32 s, uint32 ac) { a = _md5_rotate_left(a + _md5_I(b, c, d) + x + ac, s) + b; } static uchar _md5_padding[64] = { 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; } HashMd5::HashMd5(const void *input, uint32 length) : _finalized(false) { init(); if (input && length > 0) feed(input, length); } void HashMd5::feed(const void *input, uint32 length) { uint32 index = _count[0] / 8 % _md5_block_size; const uchar *buf = (const uchar *)input; if ((_count[0] += (length << 3)) < (length << 3)) { _count[1]++; } _count[1] += (length >> 29); uint32 firstpart = 64 - index; uint32 i; if (length >= firstpart) { memcpy(&_buffer[index], buf, firstpart); transform(_buffer); for (i = firstpart; i + _md5_block_size <= length; i += _md5_block_size) { transform(&buf[i]); } index = 0; } else { i = 0; } memcpy(&_buffer[index], &buf[i], length - i); } int32 *HashMd5::result() { if (!_finalized) finalize(); return (int32*)_digest; } void HashMd5::init() { _count[0] = 0; _count[1] = 0; _state[0] = 0x67452301; _state[1] = 0xefcdab89; _state[2] = 0x98badcfe; _state[3] = 0x10325476; } void HashMd5::finalize() { if (!_finalized) { uchar bits[8]; _md5_encode(bits, _count, 8); uint32 index = _count[0] / 8 % 64, paddingLen = (index < 56) ? (56 - index) : (120 - index); feed(_md5_padding, paddingLen); feed(bits, 8); _md5_encode(_digest, _state, 16); _finalized = true; } } void HashMd5::transform(const uchar *block) { uint32 a = _state[0], b = _state[1], c = _state[2], d = _state[3], x[16]; _md5_decode(x, block, _md5_block_size); _md5_FF(a, b, c, d, x[0] , 7 , 0xd76aa478); _md5_FF(d, a, b, c, x[1] , 12, 0xe8c7b756); _md5_FF(c, d, a, b, x[2] , 17, 0x242070db); _md5_FF(b, c, d, a, x[3] , 22, 0xc1bdceee); _md5_FF(a, b, c, d, x[4] , 7 , 0xf57c0faf); _md5_FF(d, a, b, c, x[5] , 12, 0x4787c62a); _md5_FF(c, d, a, b, x[6] , 17, 0xa8304613); _md5_FF(b, c, d, a, x[7] , 22, 0xfd469501); _md5_FF(a, b, c, d, x[8] , 7 , 0x698098d8); _md5_FF(d, a, b, c, x[9] , 12, 0x8b44f7af); _md5_FF(c, d, a, b, x[10], 17, 0xffff5bb1); _md5_FF(b, c, d, a, x[11], 22, 0x895cd7be); _md5_FF(a, b, c, d, x[12], 7 , 0x6b901122); _md5_FF(d, a, b, c, x[13], 12, 0xfd987193); _md5_FF(c, d, a, b, x[14], 17, 0xa679438e); _md5_FF(b, c, d, a, x[15], 22, 0x49b40821); _md5_GG(a, b, c, d, x[1] , 5 , 0xf61e2562); _md5_GG(d, a, b, c, x[6] , 9 , 0xc040b340); _md5_GG(c, d, a, b, x[11], 14, 0x265e5a51); _md5_GG(b, c, d, a, x[0] , 20, 0xe9b6c7aa); _md5_GG(a, b, c, d, x[5] , 5 , 0xd62f105d); _md5_GG(d, a, b, c, x[10], 9 , 0x2441453); _md5_GG(c, d, a, b, x[15], 14, 0xd8a1e681); _md5_GG(b, c, d, a, x[4] , 20, 0xe7d3fbc8); _md5_GG(a, b, c, d, x[9] , 5 , 0x21e1cde6); _md5_GG(d, a, b, c, x[14], 9 , 0xc33707d6); _md5_GG(c, d, a, b, x[3] , 14, 0xf4d50d87); _md5_GG(b, c, d, a, x[8] , 20, 0x455a14ed); _md5_GG(a, b, c, d, x[13], 5 , 0xa9e3e905); _md5_GG(d, a, b, c, x[2] , 9 , 0xfcefa3f8); _md5_GG(c, d, a, b, x[7] , 14, 0x676f02d9); _md5_GG(b, c, d, a, x[12], 20, 0x8d2a4c8a); _md5_HH(a, b, c, d, x[5] , 4 , 0xfffa3942); _md5_HH(d, a, b, c, x[8] , 11, 0x8771f681); _md5_HH(c, d, a, b, x[11], 16, 0x6d9d6122); _md5_HH(b, c, d, a, x[14], 23, 0xfde5380c); _md5_HH(a, b, c, d, x[1] , 4 , 0xa4beea44); _md5_HH(d, a, b, c, x[4] , 11, 0x4bdecfa9); _md5_HH(c, d, a, b, x[7] , 16, 0xf6bb4b60); _md5_HH(b, c, d, a, x[10], 23, 0xbebfbc70); _md5_HH(a, b, c, d, x[13], 4 , 0x289b7ec6); _md5_HH(d, a, b, c, x[0] , 11, 0xeaa127fa); _md5_HH(c, d, a, b, x[3] , 16, 0xd4ef3085); _md5_HH(b, c, d, a, x[6] , 23, 0x4881d05); _md5_HH(a, b, c, d, x[9] , 4 , 0xd9d4d039); _md5_HH(d, a, b, c, x[12], 11, 0xe6db99e5); _md5_HH(c, d, a, b, x[15], 16, 0x1fa27cf8); _md5_HH(b, c, d, a, x[2] , 23, 0xc4ac5665); _md5_II(a, b, c, d, x[0] , 6 , 0xf4292244); _md5_II(d, a, b, c, x[7] , 10, 0x432aff97); _md5_II(c, d, a, b, x[14], 15, 0xab9423a7); _md5_II(b, c, d, a, x[5] , 21, 0xfc93a039); _md5_II(a, b, c, d, x[12], 6 , 0x655b59c3); _md5_II(d, a, b, c, x[3] , 10, 0x8f0ccc92); _md5_II(c, d, a, b, x[10], 15, 0xffeff47d); _md5_II(b, c, d, a, x[1] , 21, 0x85845dd1); _md5_II(a, b, c, d, x[8] , 6 , 0x6fa87e4f); _md5_II(d, a, b, c, x[15], 10, 0xfe2ce6e0); _md5_II(c, d, a, b, x[6] , 15, 0xa3014314); _md5_II(b, c, d, a, x[13], 21, 0x4e0811a1); _md5_II(a, b, c, d, x[4] , 6 , 0xf7537e82); _md5_II(d, a, b, c, x[11], 10, 0xbd3af235); _md5_II(c, d, a, b, x[2] , 15, 0x2ad7d2bb); _md5_II(b, c, d, a, x[9] , 21, 0xeb86d391); _state[0] += a; _state[1] += b; _state[2] += c; _state[3] += d; } int32 *hashMd5(const void *data, uint32 len, void *dest) { HashMd5 md5(data, len); memcpy(dest, md5.result(), 16); return (int32*)dest; } char *hashMd5Hex(const int32 *hashmd5, void *dest) { char *md5To = (char*)dest; const uchar *res = (const uchar*)hashmd5; for (int i = 0; i < 16; ++i) { uchar ch(res[i]), high = (ch >> 4) & 0x0F, low = ch & 0x0F; md5To[i * 2 + 0] = high + ((high > 0x09) ? ('a' - 0x0A) : '0'); md5To[i * 2 + 1] = low + ((low > 0x09) ? ('a' - 0x0A) : '0'); } return md5To; } void memset_rand(void *data, uint32 len) { Assert(_sslInited); RAND_bytes((uchar*)data, len); } namespace { QMap<QString, QString> fastRusEng; QHash<QChar, QString> fastLetterRusEng; QMap<uint32, QString> fastDoubleLetterRusEng; QHash<QChar, QChar> fastRusKeyboardSwitch; } QString translitLetterRusEng(QChar letter, QChar next, int32 &toSkip) { if (fastDoubleLetterRusEng.isEmpty()) { fastDoubleLetterRusEng.insert((QString::fromUtf8("Ы").at(0).unicode() << 16) | QString::fromUtf8("й").at(0).unicode(), qsl("Y")); fastDoubleLetterRusEng.insert((QString::fromUtf8("и").at(0).unicode() << 16) | QString::fromUtf8("я").at(0).unicode(), qsl("ia")); fastDoubleLetterRusEng.insert((QString::fromUtf8("и").at(0).unicode() << 16) | QString::fromUtf8("й").at(0).unicode(), qsl("y")); fastDoubleLetterRusEng.insert((QString::fromUtf8("к").at(0).unicode() << 16) | QString::fromUtf8("с").at(0).unicode(), qsl("x")); fastDoubleLetterRusEng.insert((QString::fromUtf8("ы").at(0).unicode() << 16) | QString::fromUtf8("й").at(0).unicode(), qsl("y")); fastDoubleLetterRusEng.insert((QString::fromUtf8("ь").at(0).unicode() << 16) | QString::fromUtf8("е").at(0).unicode(), qsl("ye")); } QMap<uint32, QString>::const_iterator i = fastDoubleLetterRusEng.constFind((letter.unicode() << 16) | next.unicode()); if (i != fastDoubleLetterRusEng.cend()) { toSkip = 2; return i.value(); } toSkip = 1; if (fastLetterRusEng.isEmpty()) { fastLetterRusEng.insert(QString::fromUtf8("А").at(0), qsl("A")); fastLetterRusEng.insert(QString::fromUtf8("Б").at(0), qsl("B")); fastLetterRusEng.insert(QString::fromUtf8("В").at(0), qsl("V")); fastLetterRusEng.insert(QString::fromUtf8("Г").at(0), qsl("G")); fastLetterRusEng.insert(QString::fromUtf8("Ґ").at(0), qsl("G")); fastLetterRusEng.insert(QString::fromUtf8("Д").at(0), qsl("D")); fastLetterRusEng.insert(QString::fromUtf8("Е").at(0), qsl("E")); fastLetterRusEng.insert(QString::fromUtf8("Є").at(0), qsl("Ye")); fastLetterRusEng.insert(QString::fromUtf8("Ё").at(0), qsl("Yo")); fastLetterRusEng.insert(QString::fromUtf8("Ж").at(0), qsl("Zh")); fastLetterRusEng.insert(QString::fromUtf8("З").at(0), qsl("Z")); fastLetterRusEng.insert(QString::fromUtf8("И").at(0), qsl("I")); fastLetterRusEng.insert(QString::fromUtf8("Ї").at(0), qsl("Yi")); fastLetterRusEng.insert(QString::fromUtf8("І").at(0), qsl("I")); fastLetterRusEng.insert(QString::fromUtf8("Й").at(0), qsl("J")); fastLetterRusEng.insert(QString::fromUtf8("К").at(0), qsl("K")); fastLetterRusEng.insert(QString::fromUtf8("Л").at(0), qsl("L")); fastLetterRusEng.insert(QString::fromUtf8("М").at(0), qsl("M")); fastLetterRusEng.insert(QString::fromUtf8("Н").at(0), qsl("N")); fastLetterRusEng.insert(QString::fromUtf8("О").at(0), qsl("O")); fastLetterRusEng.insert(QString::fromUtf8("П").at(0), qsl("P")); fastLetterRusEng.insert(QString::fromUtf8("Р").at(0), qsl("R")); fastLetterRusEng.insert(QString::fromUtf8("С").at(0), qsl("S")); fastLetterRusEng.insert(QString::fromUtf8("Т").at(0), qsl("T")); fastLetterRusEng.insert(QString::fromUtf8("У").at(0), qsl("U")); fastLetterRusEng.insert(QString::fromUtf8("Ў").at(0), qsl("W")); fastLetterRusEng.insert(QString::fromUtf8("Ф").at(0), qsl("F")); fastLetterRusEng.insert(QString::fromUtf8("Х").at(0), qsl("Kh")); fastLetterRusEng.insert(QString::fromUtf8("Ц").at(0), qsl("Ts")); fastLetterRusEng.insert(QString::fromUtf8("Ч").at(0), qsl("Ch")); fastLetterRusEng.insert(QString::fromUtf8("Ш").at(0), qsl("Sh")); fastLetterRusEng.insert(QString::fromUtf8("Щ").at(0), qsl("Sch")); fastLetterRusEng.insert(QString::fromUtf8("Э").at(0), qsl("E")); fastLetterRusEng.insert(QString::fromUtf8("Ю").at(0), qsl("Yu")); fastLetterRusEng.insert(QString::fromUtf8("Я").at(0), qsl("Ya")); fastLetterRusEng.insert(QString::fromUtf8("Ў").at(0), qsl("W")); fastLetterRusEng.insert(QString::fromUtf8("а").at(0), qsl("a")); fastLetterRusEng.insert(QString::fromUtf8("б").at(0), qsl("b")); fastLetterRusEng.insert(QString::fromUtf8("в").at(0), qsl("v")); fastLetterRusEng.insert(QString::fromUtf8("г").at(0), qsl("g")); fastLetterRusEng.insert(QString::fromUtf8("ґ").at(0), qsl("g")); fastLetterRusEng.insert(QString::fromUtf8("д").at(0), qsl("d")); fastLetterRusEng.insert(QString::fromUtf8("е").at(0), qsl("e")); fastLetterRusEng.insert(QString::fromUtf8("є").at(0), qsl("ye")); fastLetterRusEng.insert(QString::fromUtf8("ё").at(0), qsl("yo")); fastLetterRusEng.insert(QString::fromUtf8("ж").at(0), qsl("zh")); fastLetterRusEng.insert(QString::fromUtf8("з").at(0), qsl("z")); fastLetterRusEng.insert(QString::fromUtf8("й").at(0), qsl("y")); fastLetterRusEng.insert(QString::fromUtf8("ї").at(0), qsl("yi")); fastLetterRusEng.insert(QString::fromUtf8("і").at(0), qsl("i")); fastLetterRusEng.insert(QString::fromUtf8("л").at(0), qsl("l")); fastLetterRusEng.insert(QString::fromUtf8("м").at(0), qsl("m")); fastLetterRusEng.insert(QString::fromUtf8("н").at(0), qsl("n")); fastLetterRusEng.insert(QString::fromUtf8("о").at(0), qsl("o")); fastLetterRusEng.insert(QString::fromUtf8("п").at(0), qsl("p")); fastLetterRusEng.insert(QString::fromUtf8("р").at(0), qsl("r")); fastLetterRusEng.insert(QString::fromUtf8("с").at(0), qsl("s")); fastLetterRusEng.insert(QString::fromUtf8("т").at(0), qsl("t")); fastLetterRusEng.insert(QString::fromUtf8("у").at(0), qsl("u")); fastLetterRusEng.insert(QString::fromUtf8("ў").at(0), qsl("w")); fastLetterRusEng.insert(QString::fromUtf8("ф").at(0), qsl("f")); fastLetterRusEng.insert(QString::fromUtf8("х").at(0), qsl("kh")); fastLetterRusEng.insert(QString::fromUtf8("ц").at(0), qsl("ts")); fastLetterRusEng.insert(QString::fromUtf8("ч").at(0), qsl("ch")); fastLetterRusEng.insert(QString::fromUtf8("ш").at(0), qsl("sh")); fastLetterRusEng.insert(QString::fromUtf8("щ").at(0), qsl("sch")); fastLetterRusEng.insert(QString::fromUtf8("ъ").at(0), qsl("")); fastLetterRusEng.insert(QString::fromUtf8("э").at(0), qsl("e")); fastLetterRusEng.insert(QString::fromUtf8("ю").at(0), qsl("yu")); fastLetterRusEng.insert(QString::fromUtf8("я").at(0), qsl("ya")); fastLetterRusEng.insert(QString::fromUtf8("ў").at(0), qsl("w")); fastLetterRusEng.insert(QString::fromUtf8("Ы").at(0), qsl("Y")); fastLetterRusEng.insert(QString::fromUtf8("и").at(0), qsl("i")); fastLetterRusEng.insert(QString::fromUtf8("к").at(0), qsl("k")); fastLetterRusEng.insert(QString::fromUtf8("ы").at(0), qsl("y")); fastLetterRusEng.insert(QString::fromUtf8("ь").at(0), qsl("")); } QHash<QChar, QString>::const_iterator j = fastLetterRusEng.constFind(letter); if (j != fastLetterRusEng.cend()) { return j.value(); } return QString(1, letter); } QString translitRusEng(const QString &rus) { if (fastRusEng.isEmpty()) { fastRusEng.insert(QString::fromUtf8("Александр"), qsl("Alexander")); fastRusEng.insert(QString::fromUtf8("александр"), qsl("alexander")); fastRusEng.insert(QString::fromUtf8("Филипп"), qsl("Philip")); fastRusEng.insert(QString::fromUtf8("филипп"), qsl("philip")); fastRusEng.insert(QString::fromUtf8("Пётр"), qsl("Petr")); fastRusEng.insert(QString::fromUtf8("пётр"), qsl("petr")); fastRusEng.insert(QString::fromUtf8("Гай"), qsl("Gai")); fastRusEng.insert(QString::fromUtf8("гай"), qsl("gai")); fastRusEng.insert(QString::fromUtf8("Ильин"), qsl("Ilyin")); fastRusEng.insert(QString::fromUtf8("ильин"), qsl("ilyin")); } QMap<QString, QString>::const_iterator i = fastRusEng.constFind(rus); if (i != fastRusEng.cend()) { return i.value(); } QString result; result.reserve(rus.size() * 2); int32 toSkip = 0; for (QString::const_iterator i = rus.cbegin(), e = rus.cend(); i != e; i += toSkip) { result += translitLetterRusEng(*i, (i + 1 == e) ? ' ' : *(i + 1), toSkip); } return result; } QString rusKeyboardLayoutSwitch(const QString &from) { if (fastRusKeyboardSwitch.isEmpty()) { fastRusKeyboardSwitch.insert('Q', QString::fromUtf8("Й").at(0)); fastRusKeyboardSwitch.insert('W', QString::fromUtf8("Ц").at(0)); fastRusKeyboardSwitch.insert('E', QString::fromUtf8("У").at(0)); fastRusKeyboardSwitch.insert('R', QString::fromUtf8("К").at(0)); fastRusKeyboardSwitch.insert('T', QString::fromUtf8("Е").at(0)); fastRusKeyboardSwitch.insert('Y', QString::fromUtf8("Н").at(0)); fastRusKeyboardSwitch.insert('U', QString::fromUtf8("Г").at(0)); fastRusKeyboardSwitch.insert('I', QString::fromUtf8("Ш").at(0)); fastRusKeyboardSwitch.insert('O', QString::fromUtf8("Щ").at(0)); fastRusKeyboardSwitch.insert('P', QString::fromUtf8("З").at(0)); fastRusKeyboardSwitch.insert('{', QString::fromUtf8("Х").at(0)); fastRusKeyboardSwitch.insert('}', QString::fromUtf8("Ъ").at(0)); fastRusKeyboardSwitch.insert('A', QString::fromUtf8("Ф").at(0)); fastRusKeyboardSwitch.insert('S', QString::fromUtf8("Ы").at(0)); fastRusKeyboardSwitch.insert('D', QString::fromUtf8("В").at(0)); fastRusKeyboardSwitch.insert('F', QString::fromUtf8("А").at(0)); fastRusKeyboardSwitch.insert('G', QString::fromUtf8("П").at(0)); fastRusKeyboardSwitch.insert('H', QString::fromUtf8("Р").at(0)); fastRusKeyboardSwitch.insert('J', QString::fromUtf8("О").at(0)); fastRusKeyboardSwitch.insert('K', QString::fromUtf8("Л").at(0)); fastRusKeyboardSwitch.insert('L', QString::fromUtf8("Д").at(0)); fastRusKeyboardSwitch.insert(':', QString::fromUtf8("Ж").at(0)); fastRusKeyboardSwitch.insert('"', QString::fromUtf8("Э").at(0)); fastRusKeyboardSwitch.insert('Z', QString::fromUtf8("Я").at(0)); fastRusKeyboardSwitch.insert('X', QString::fromUtf8("Ч").at(0)); fastRusKeyboardSwitch.insert('C', QString::fromUtf8("С").at(0)); fastRusKeyboardSwitch.insert('V', QString::fromUtf8("М").at(0)); fastRusKeyboardSwitch.insert('B', QString::fromUtf8("И").at(0)); fastRusKeyboardSwitch.insert('N', QString::fromUtf8("Т").at(0)); fastRusKeyboardSwitch.insert('M', QString::fromUtf8("Ь").at(0)); fastRusKeyboardSwitch.insert('<', QString::fromUtf8("Б").at(0)); fastRusKeyboardSwitch.insert('>', QString::fromUtf8("Ю").at(0)); fastRusKeyboardSwitch.insert('q', QString::fromUtf8("й").at(0)); fastRusKeyboardSwitch.insert('w', QString::fromUtf8("ц").at(0)); fastRusKeyboardSwitch.insert('e', QString::fromUtf8("у").at(0)); fastRusKeyboardSwitch.insert('r', QString::fromUtf8("к").at(0)); fastRusKeyboardSwitch.insert('t', QString::fromUtf8("е").at(0)); fastRusKeyboardSwitch.insert('y', QString::fromUtf8("н").at(0)); fastRusKeyboardSwitch.insert('u', QString::fromUtf8("г").at(0)); fastRusKeyboardSwitch.insert('i', QString::fromUtf8("ш").at(0)); fastRusKeyboardSwitch.insert('o', QString::fromUtf8("щ").at(0)); fastRusKeyboardSwitch.insert('p', QString::fromUtf8("з").at(0)); fastRusKeyboardSwitch.insert('[', QString::fromUtf8("х").at(0)); fastRusKeyboardSwitch.insert(']', QString::fromUtf8("ъ").at(0)); fastRusKeyboardSwitch.insert('a', QString::fromUtf8("ф").at(0)); fastRusKeyboardSwitch.insert('s', QString::fromUtf8("ы").at(0)); fastRusKeyboardSwitch.insert('d', QString::fromUtf8("в").at(0)); fastRusKeyboardSwitch.insert('f', QString::fromUtf8("а").at(0)); fastRusKeyboardSwitch.insert('g', QString::fromUtf8("п").at(0)); fastRusKeyboardSwitch.insert('h', QString::fromUtf8("р").at(0)); fastRusKeyboardSwitch.insert('j', QString::fromUtf8("о").at(0)); fastRusKeyboardSwitch.insert('k', QString::fromUtf8("л").at(0)); fastRusKeyboardSwitch.insert('l', QString::fromUtf8("д").at(0)); fastRusKeyboardSwitch.insert(';', QString::fromUtf8("ж").at(0)); fastRusKeyboardSwitch.insert('\'', QString::fromUtf8("э").at(0)); fastRusKeyboardSwitch.insert('z', QString::fromUtf8("я").at(0)); fastRusKeyboardSwitch.insert('x', QString::fromUtf8("ч").at(0)); fastRusKeyboardSwitch.insert('c', QString::fromUtf8("с").at(0)); fastRusKeyboardSwitch.insert('v', QString::fromUtf8("м").at(0)); fastRusKeyboardSwitch.insert('b', QString::fromUtf8("и").at(0)); fastRusKeyboardSwitch.insert('n', QString::fromUtf8("т").at(0)); fastRusKeyboardSwitch.insert('m', QString::fromUtf8("ь").at(0)); fastRusKeyboardSwitch.insert(',', QString::fromUtf8("б").at(0)); fastRusKeyboardSwitch.insert('.', QString::fromUtf8("ю").at(0)); fastRusKeyboardSwitch.insert(QString::fromUtf8("Й").at(0), 'Q'); fastRusKeyboardSwitch.insert(QString::fromUtf8("Ц").at(0), 'W'); fastRusKeyboardSwitch.insert(QString::fromUtf8("У").at(0), 'E'); fastRusKeyboardSwitch.insert(QString::fromUtf8("К").at(0), 'R'); fastRusKeyboardSwitch.insert(QString::fromUtf8("Е").at(0), 'T'); fastRusKeyboardSwitch.insert(QString::fromUtf8("Н").at(0), 'Y'); fastRusKeyboardSwitch.insert(QString::fromUtf8("Г").at(0), 'U'); fastRusKeyboardSwitch.insert(QString::fromUtf8("Ш").at(0), 'I'); fastRusKeyboardSwitch.insert(QString::fromUtf8("Щ").at(0), 'O'); fastRusKeyboardSwitch.insert(QString::fromUtf8("З").at(0), 'P'); fastRusKeyboardSwitch.insert(QString::fromUtf8("Х").at(0), '{'); fastRusKeyboardSwitch.insert(QString::fromUtf8("Ъ").at(0), '}'); fastRusKeyboardSwitch.insert(QString::fromUtf8("Ф").at(0), 'A'); fastRusKeyboardSwitch.insert(QString::fromUtf8("Ы").at(0), 'S'); fastRusKeyboardSwitch.insert(QString::fromUtf8("В").at(0), 'D'); fastRusKeyboardSwitch.insert(QString::fromUtf8("А").at(0), 'F'); fastRusKeyboardSwitch.insert(QString::fromUtf8("П").at(0), 'G'); fastRusKeyboardSwitch.insert(QString::fromUtf8("Р").at(0), 'H'); fastRusKeyboardSwitch.insert(QString::fromUtf8("О").at(0), 'J'); fastRusKeyboardSwitch.insert(QString::fromUtf8("Л").at(0), 'K'); fastRusKeyboardSwitch.insert(QString::fromUtf8("Д").at(0), 'L'); fastRusKeyboardSwitch.insert(QString::fromUtf8("Ж").at(0), ':'); fastRusKeyboardSwitch.insert(QString::fromUtf8("Э").at(0), '"'); fastRusKeyboardSwitch.insert(QString::fromUtf8("Я").at(0), 'Z'); fastRusKeyboardSwitch.insert(QString::fromUtf8("Ч").at(0), 'X'); fastRusKeyboardSwitch.insert(QString::fromUtf8("С").at(0), 'C'); fastRusKeyboardSwitch.insert(QString::fromUtf8("М").at(0), 'V'); fastRusKeyboardSwitch.insert(QString::fromUtf8("И").at(0), 'B'); fastRusKeyboardSwitch.insert(QString::fromUtf8("Т").at(0), 'N'); fastRusKeyboardSwitch.insert(QString::fromUtf8("Ь").at(0), 'M'); fastRusKeyboardSwitch.insert(QString::fromUtf8("Б").at(0), '<'); fastRusKeyboardSwitch.insert(QString::fromUtf8("Ю").at(0), '>'); fastRusKeyboardSwitch.insert(QString::fromUtf8("й").at(0), 'q'); fastRusKeyboardSwitch.insert(QString::fromUtf8("ц").at(0), 'w'); fastRusKeyboardSwitch.insert(QString::fromUtf8("у").at(0), 'e'); fastRusKeyboardSwitch.insert(QString::fromUtf8("к").at(0), 'r'); fastRusKeyboardSwitch.insert(QString::fromUtf8("е").at(0), 't'); fastRusKeyboardSwitch.insert(QString::fromUtf8("н").at(0), 'y'); fastRusKeyboardSwitch.insert(QString::fromUtf8("г").at(0), 'u'); fastRusKeyboardSwitch.insert(QString::fromUtf8("ш").at(0), 'i'); fastRusKeyboardSwitch.insert(QString::fromUtf8("щ").at(0), 'o'); fastRusKeyboardSwitch.insert(QString::fromUtf8("з").at(0), 'p'); fastRusKeyboardSwitch.insert(QString::fromUtf8("х").at(0), '['); fastRusKeyboardSwitch.insert(QString::fromUtf8("ъ").at(0), ']'); fastRusKeyboardSwitch.insert(QString::fromUtf8("ф").at(0), 'a'); fastRusKeyboardSwitch.insert(QString::fromUtf8("ы").at(0), 's'); fastRusKeyboardSwitch.insert(QString::fromUtf8("в").at(0), 'd'); fastRusKeyboardSwitch.insert(QString::fromUtf8("а").at(0), 'f'); fastRusKeyboardSwitch.insert(QString::fromUtf8("п").at(0), 'g'); fastRusKeyboardSwitch.insert(QString::fromUtf8("р").at(0), 'h'); fastRusKeyboardSwitch.insert(QString::fromUtf8("о").at(0), 'j'); fastRusKeyboardSwitch.insert(QString::fromUtf8("л").at(0), 'k'); fastRusKeyboardSwitch.insert(QString::fromUtf8("д").at(0), 'l'); fastRusKeyboardSwitch.insert(QString::fromUtf8("ж").at(0), ';'); fastRusKeyboardSwitch.insert(QString::fromUtf8("э").at(0), '\''); fastRusKeyboardSwitch.insert(QString::fromUtf8("я").at(0), 'z'); fastRusKeyboardSwitch.insert(QString::fromUtf8("ч").at(0), 'x'); fastRusKeyboardSwitch.insert(QString::fromUtf8("с").at(0), 'c'); fastRusKeyboardSwitch.insert(QString::fromUtf8("м").at(0), 'v'); fastRusKeyboardSwitch.insert(QString::fromUtf8("и").at(0), 'b'); fastRusKeyboardSwitch.insert(QString::fromUtf8("т").at(0), 'n'); fastRusKeyboardSwitch.insert(QString::fromUtf8("ь").at(0), 'm'); fastRusKeyboardSwitch.insert(QString::fromUtf8("б").at(0), ','); fastRusKeyboardSwitch.insert(QString::fromUtf8("ю").at(0), '.'); } QString result; result.reserve(from.size()); for (QString::const_iterator i = from.cbegin(), e = from.cend(); i != e; ++i) { QHash<QChar, QChar>::const_iterator j = fastRusKeyboardSwitch.constFind(*i); if (j == fastRusKeyboardSwitch.cend()) { result += *i; } else { result += j.value(); } } return result; } QStringList MimeType::globPatterns() const { switch (_type) { case Known::WebP: return QStringList(qsl("*.webp")); case Known::TDesktopTheme: return QStringList(qsl("*.tdesktop-theme")); case Known::TDesktopPalette: return QStringList(qsl("*.tdesktop-palette")); default: break; } return _typeStruct.globPatterns(); } QString MimeType::filterString() const { switch (_type) { case Known::WebP: return qsl("WebP image (*.webp)"); case Known::TDesktopTheme: return qsl("Theme files (*.tdesktop-theme)"); case Known::TDesktopPalette: return qsl("Palette files (*.tdesktop-palette)"); default: break; } return _typeStruct.filterString(); } QString MimeType::name() const { switch (_type) { case Known::WebP: return qsl("image/webp"); case Known::TDesktopTheme: return qsl("application/x-tdesktop-theme"); case Known::TDesktopPalette: return qsl("application/x-tdesktop-palette"); default: break; } return _typeStruct.name(); } MimeType mimeTypeForName(const QString &mime) { if (mime == qsl("image/webp")) { return MimeType(MimeType::Known::WebP); } else if (mime == qsl("application/x-tdesktop-theme")) { return MimeType(MimeType::Known::TDesktopTheme); } else if (mime == qsl("application/x-tdesktop-palette")) { return MimeType(MimeType::Known::TDesktopPalette); } return MimeType(QMimeDatabase().mimeTypeForName(mime)); } MimeType mimeTypeForFile(const QFileInfo &file) { QString path = file.absoluteFilePath(); if (path.endsWith(qstr(".webp"), Qt::CaseInsensitive)) { return MimeType(MimeType::Known::WebP); } else if (path.endsWith(qstr(".tdesktop-theme"), Qt::CaseInsensitive)) { return MimeType(MimeType::Known::TDesktopTheme); } else if (path.endsWith(qstr(".tdesktop-palette"), Qt::CaseInsensitive)) { return MimeType(MimeType::Known::TDesktopPalette); } { QFile f(path); if (f.open(QIODevice::ReadOnly)) { QByteArray magic = f.read(12); if (magic.size() >= 12) { if (!memcmp(magic.constData(), "RIFF", 4) && !memcmp(magic.constData() + 8, "WEBP", 4)) { return MimeType(MimeType::Known::WebP); } } f.close(); } } return MimeType(QMimeDatabase().mimeTypeForFile(file)); } MimeType mimeTypeForData(const QByteArray &data) { if (data.size() >= 12) { if (!memcmp(data.constData(), "RIFF", 4) && !memcmp(data.constData() + 8, "WEBP", 4)) { return MimeType(MimeType::Known::WebP); } } return MimeType(QMimeDatabase().mimeTypeForData(data)); } ������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/core/utils.h���������������������������������������������������0000664�0000000�0000000�00000036332�13262451251�0021673�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "core/basic_types.h" #include "base/flags.h" #include "base/algorithm.h" // Define specializations for QByteArray for Qt 5.3.2, because // QByteArray in Qt 5.3.2 doesn't declare "pointer" subtype. #ifdef OS_MAC_OLD namespace gsl { template <> inline span<char> make_span<QByteArray>(QByteArray &cont) { return span<char>(cont.data(), cont.size()); } template <> inline span<const char> make_span(const QByteArray &cont) { return span<const char>(cont.constData(), cont.size()); } } // namespace gsl #endif // OS_MAC_OLD namespace base { template <typename D, typename T> inline constexpr D up_cast(T object) { using DV = std::decay_t<decltype(*D())>; using TV = std::decay_t<decltype(*T())>; if constexpr (std::is_base_of_v<DV, TV>) { return object; } else { return nullptr; } } template <typename Container, typename T> inline bool contains(const Container &container, const T &value) { auto end = std::end(container); return std::find(std::begin(container), end, value) != end; } // We need a custom comparator for std::set<std::unique_ptr<T>>::find to work with pointers. // thanks to http://stackoverflow.com/questions/18939882/raw-pointer-lookup-for-sets-of-unique-ptrs template <typename T> struct pointer_comparator { using is_transparent = std::true_type; // helper does some magic in order to reduce the number of // pairs of types we need to know how to compare: it turns // everything into a pointer, and then uses `std::less<T*>` // to do the comparison: struct helper { T *ptr = nullptr; helper() = default; helper(const helper &other) = default; helper(T *p) : ptr(p) { } template <typename ...Ts> helper(const std::shared_ptr<Ts...> &other) : ptr(other.get()) { } template <typename ...Ts> helper(const std::unique_ptr<Ts...> &other) : ptr(other.get()) { } bool operator<(helper other) const { return std::less<T*>()(ptr, other.ptr); } }; // without helper, we'd need 2^n different overloads, where // n is the number of types we want to support (so, 8 with // raw pointers, unique pointers, and shared pointers). That // seems silly. // && helps enforce rvalue use only bool operator()(const helper &&lhs, const helper &&rhs) const { return lhs < rhs; } }; template <typename T> using set_of_unique_ptr = std::set<std::unique_ptr<T>, base::pointer_comparator<T>>; template <typename T> using set_of_shared_ptr = std::set<std::shared_ptr<T>, base::pointer_comparator<T>>; using byte_span = gsl::span<gsl::byte>; using const_byte_span = gsl::span<const gsl::byte>; using byte_vector = std::vector<gsl::byte>; template <size_t N> using byte_array = std::array<gsl::byte, N>; inline void copy_bytes(byte_span destination, const_byte_span source) { Expects(destination.size() >= source.size()); memcpy(destination.data(), source.data(), source.size()); } inline void move_bytes(byte_span destination, const_byte_span source) { Expects(destination.size() >= source.size()); memmove(destination.data(), source.data(), source.size()); } inline void set_bytes(byte_span destination, gsl::byte value) { memset(destination.data(), gsl::to_integer<unsigned char>(value), destination.size()); } inline int compare_bytes(const_byte_span a, const_byte_span b) { auto aSize = a.size(), bSize = b.size(); return (aSize > bSize) ? 1 : (aSize < bSize) ? -1 : memcmp(a.data(), b.data(), aSize); } // Thanks https://stackoverflow.com/a/28139075 template <typename Container> struct reversion_wrapper { Container &container; }; template <typename Container> auto begin(reversion_wrapper<Container> wrapper) { return std::rbegin(wrapper.container); } template <typename Container> auto end(reversion_wrapper<Container> wrapper) { return std::rend(wrapper.container); } template <typename Container> reversion_wrapper<Container> reversed(Container &&container) { return { container }; } template <typename Value, typename From, typename Till> inline bool in_range(Value &&value, From &&from, Till &&till) { return (value >= from) && (value < till); } } // namespace base // using for_const instead of plain range-based for loop to ensure usage of const_iterator // it is important for the copy-on-write Qt containers // if you have "QVector<T*> v" then "for (T * const p : v)" will still call QVector::detach(), // while "for_const (T *p, v)" won't and "for_const (T *&p, v)" won't compile #define for_const(range_declaration, range_expression) for (range_declaration : std::as_const(range_expression)) template <typename Lambda> inline void InvokeQueued(QObject *context, Lambda &&lambda) { QObject proxy; QObject::connect(&proxy, &QObject::destroyed, context, std::forward<Lambda>(lambda), Qt::QueuedConnection); } static const int32 ScrollMax = INT_MAX; extern uint64 _SharedMemoryLocation[]; template <typename T, unsigned int N> T *SharedMemoryLocation() { static_assert(N < 4, "Only 4 shared memory locations!"); return reinterpret_cast<T*>(_SharedMemoryLocation + N); } // see https://github.com/boostcon/cppnow_presentations_2012/blob/master/wed/schurr_cpp11_tools_for_class_authors.pdf class str_const { // constexpr string public: template<std::size_t N> constexpr str_const(const char(&a)[N]) : _str(a), _size(N - 1) { } constexpr char operator[](std::size_t n) const { return (n < _size) ? _str[n] : #ifndef OS_MAC_OLD throw std::out_of_range(""); #else // OS_MAC_OLD throw std::exception(); #endif // OS_MAC_OLD } constexpr std::size_t size() const { return _size; } const char *c_str() const { return _str; } private: const char* const _str; const std::size_t _size; }; inline QString str_const_toString(const str_const &str) { return QString::fromUtf8(str.c_str(), str.size()); } inline QByteArray str_const_toByteArray(const str_const &str) { return QByteArray::fromRawData(str.c_str(), str.size()); } template <typename T> inline void accumulate_max(T &a, const T &b) { if (a < b) a = b; } template <typename T> inline void accumulate_min(T &a, const T &b) { if (a > b) a = b; } class Exception : public std::exception { public: Exception(const QString &msg, bool isFatal = true) : _fatal(isFatal), _msg(msg.toUtf8()) { LOG(("Exception: %1").arg(msg)); } bool fatal() const { return _fatal; } virtual const char *what() const throw() { return _msg.constData(); } virtual ~Exception() throw() { } private: bool _fatal; QByteArray _msg; }; using TimeId = int32; void unixtimeInit(); void unixtimeSet(TimeId serverTime, bool force = false); TimeId unixtime(); uint64 msgid(); int32 reqid(); QDateTime ParseDateTime(TimeId serverTime); inline void mylocaltime(struct tm * _Tm, const time_t * _Time) { #ifdef Q_OS_WIN localtime_s(_Tm, _Time); #else localtime_r(_Time, _Tm); #endif } namespace ThirdParty { void start(); void finish(); } using TimeMs = int64; bool checkms(); // returns true if time has changed TimeMs getms(bool checked = false); const static uint32 _md5_block_size = 64; class HashMd5 { public: HashMd5(const void *input = 0, uint32 length = 0); void feed(const void *input, uint32 length); int32 *result(); private: void init(); void finalize(); void transform(const uchar *block); bool _finalized; uchar _buffer[_md5_block_size]; uint32 _count[2]; uint32 _state[4]; uchar _digest[16]; }; int32 hashCrc32(const void *data, uint32 len); int32 *hashSha1(const void *data, uint32 len, void *dest); // dest - ptr to 20 bytes, returns (int32*)dest inline std::array<char, 20> hashSha1(const void *data, int size) { auto result = std::array<char, 20>(); hashSha1(data, size, result.data()); return result; } int32 *hashSha256(const void *data, uint32 len, void *dest); // dest - ptr to 32 bytes, returns (int32*)dest inline std::array<char, 32> hashSha256(const void *data, int size) { auto result = std::array<char, 32>(); hashSha256(data, size, result.data()); return result; } int32 *hashMd5(const void *data, uint32 len, void *dest); // dest = ptr to 16 bytes, returns (int32*)dest inline std::array<char, 16> hashMd5(const void *data, int size) { auto result = std::array<char, 16>(); hashMd5(data, size, result.data()); return result; } char *hashMd5Hex(const int32 *hashmd5, void *dest); // dest = ptr to 32 bytes, returns (char*)dest inline char *hashMd5Hex(const void *data, uint32 len, void *dest) { // dest = ptr to 32 bytes, returns (char*)dest return hashMd5Hex(HashMd5(data, len).result(), dest); } inline std::array<char, 32> hashMd5Hex(const void *data, int size) { auto result = std::array<char, 32>(); hashMd5Hex(data, size, result.data()); return result; } // good random (using openssl implementation) void memset_rand(void *data, uint32 len); template <typename T> T rand_value() { T result; memset_rand(&result, sizeof(result)); return result; } inline void memset_rand_bad(void *data, uint32 len) { for (uchar *i = reinterpret_cast<uchar*>(data), *e = i + len; i != e; ++i) { *i = uchar(rand() & 0xFF); } } template <typename T> inline void memsetrnd_bad(T &value) { memset_rand_bad(&value, sizeof(value)); } class ReadLockerAttempt { public: ReadLockerAttempt(not_null<QReadWriteLock*> lock) : _lock(lock), _locked(_lock->tryLockForRead()) { } ReadLockerAttempt(const ReadLockerAttempt &other) = delete; ReadLockerAttempt &operator=(const ReadLockerAttempt &other) = delete; ReadLockerAttempt(ReadLockerAttempt &&other) : _lock(other._lock), _locked(base::take(other._locked)) { } ReadLockerAttempt &operator=(ReadLockerAttempt &&other) { _lock = other._lock; _locked = base::take(other._locked); return *this; } ~ReadLockerAttempt() { if (_locked) { _lock->unlock(); } } operator bool() const { return _locked; } private: not_null<QReadWriteLock*> _lock; bool _locked = false; }; inline QString fromUtf8Safe(const char *str, int32 size = -1) { if (!str || !size) return QString(); if (size < 0) size = int32(strlen(str)); QString result(QString::fromUtf8(str, size)); QByteArray back = result.toUtf8(); if (back.size() != size || memcmp(back.constData(), str, size)) return QString::fromLocal8Bit(str, size); return result; } inline QString fromUtf8Safe(const QByteArray &str) { return fromUtf8Safe(str.constData(), str.size()); } static const QRegularExpression::PatternOptions reMultiline(QRegularExpression::DotMatchesEverythingOption | QRegularExpression::MultilineOption); template <typename T> inline T snap(const T &v, const T &_min, const T &_max) { return (v < _min) ? _min : ((v > _max) ? _max : v); } template <typename T> class ManagedPtr { public: ManagedPtr() = default; ManagedPtr(T *p) : _data(p) { } T *operator->() const { return _data; } T *v() const { return _data; } explicit operator bool() const { return _data != nullptr; } protected: using Parent = ManagedPtr<T>; T *_data = nullptr; }; QString translitRusEng(const QString &rus); QString rusKeyboardLayoutSwitch(const QString &from); enum DBISendKey { dbiskEnter = 0, dbiskCtrlEnter = 1, }; enum DBINotifyView { dbinvShowPreview = 0, dbinvShowName = 1, dbinvShowNothing = 2, }; enum DBIWorkMode { dbiwmWindowAndTray = 0, dbiwmTrayOnly = 1, dbiwmWindowOnly = 2, }; enum DBIConnectionType { dbictAuto = 0, dbictHttpAuto = 1, // not used dbictHttpProxy = 2, dbictTcpProxy = 3, }; struct ProxyData { QString host; uint32 port = 0; QString user, password; }; enum DBIScale { dbisAuto = 0, dbisOne = 1, dbisOneAndQuarter = 2, dbisOneAndHalf = 3, dbisTwo = 4, dbisScaleCount = 5, }; static const int MatrixRowShift = 40000; enum DBIPlatform { dbipWindows = 0, dbipMac = 1, dbipLinux64 = 2, dbipLinux32 = 3, dbipMacOld = 4, }; enum DBIPeerReportSpamStatus { dbiprsNoButton = 0, // hidden, but not in the cloud settings yet dbiprsUnknown = 1, // contacts not loaded yet dbiprsShowButton = 2, // show report spam button, each show peer request setting from cloud dbiprsReportSent = 3, // report sent, but the report spam panel is not hidden yet dbiprsHidden = 4, // hidden in the cloud or not needed (bots, contacts, etc), no more requests dbiprsRequesting = 5, // requesting the cloud setting right now }; class MimeType { public: enum class Known { Unknown, TDesktopTheme, TDesktopPalette, WebP, }; MimeType(const QMimeType &type) : _typeStruct(type) { } MimeType(Known type) : _type(type) { } QStringList globPatterns() const; QString filterString() const; QString name() const; private: QMimeType _typeStruct; Known _type = Known::Unknown; }; MimeType mimeTypeForName(const QString &mime); MimeType mimeTypeForFile(const QFileInfo &file); MimeType mimeTypeForData(const QByteArray &data); #include <cmath> inline int rowscount(int fullCount, int countPerRow) { return (fullCount + countPerRow - 1) / countPerRow; } inline int floorclamp(int value, int step, int lowest, int highest) { return qMin(qMax(value / step, lowest), highest); } inline int floorclamp(float64 value, int step, int lowest, int highest) { return qMin(qMax(static_cast<int>(std::floor(value / step)), lowest), highest); } inline int ceilclamp(int value, int step, int lowest, int highest) { return qMax(qMin((value + step - 1) / step, highest), lowest); } inline int ceilclamp(float64 value, int32 step, int32 lowest, int32 highest) { return qMax(qMin(static_cast<int>(std::ceil(value / step)), highest), lowest); } static int32 FullArcLength = 360 * 16; static int32 QuarterArcLength = (FullArcLength / 4); static int32 MinArcLength = (FullArcLength / 360); static int32 AlmostFullArcLength = (FullArcLength - MinArcLength); // This pointer is used for global non-POD variables that are allocated // on demand by createIfNull(lambda) and are never automatically freed. template <typename T> class NeverFreedPointer { public: NeverFreedPointer() = default; NeverFreedPointer(const NeverFreedPointer<T> &other) = delete; NeverFreedPointer &operator=(const NeverFreedPointer<T> &other) = delete; template <typename... Args> void createIfNull(Args&&... args) { if (isNull()) { reset(new T(std::forward<Args>(args)...)); } }; T *data() const { return _p; } T *release() { return base::take(_p); } void reset(T *p = nullptr) { delete _p; _p = p; } bool isNull() const { return data() == nullptr; } void clear() { reset(); } T *operator->() const { return data(); } T &operator*() const { Assert(!isNull()); return *data(); } explicit operator bool() const { return !isNull(); } private: T *_p; }; // This pointer is used for static non-POD variables that are allocated // on first use by constructor and are never automatically freed. template <typename T> class StaticNeverFreedPointer { public: explicit StaticNeverFreedPointer(T *p) : _p(p) { } StaticNeverFreedPointer(const StaticNeverFreedPointer<T> &other) = delete; StaticNeverFreedPointer &operator=(const StaticNeverFreedPointer<T> &other) = delete; T *data() const { return _p; } T *release() { return base::take(_p); } void reset(T *p = nullptr) { delete _p; _p = p; } bool isNull() const { return data() == nullptr; } void clear() { reset(); } T *operator->() const { return data(); } T &operator*() const { Assert(!isNull()); return *data(); } explicit operator bool() const { return !isNull(); } private: T *_p = nullptr; }; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/core/version.h�������������������������������������������������0000664�0000000�0000000�00000000752�13262451251�0022215�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "core/utils.h" #define BETA_VERSION_MACRO (0ULL) constexpr int AppVersion = 1002017; constexpr str_const AppVersionStr = "1.2.17"; constexpr bool AppAlphaVersion = false; constexpr uint64 AppBetaVersion = BETA_VERSION_MACRO; ����������������������tdesktop-1.2.17/Telegram/SourceFiles/countries.h����������������������������������������������������0000664�0000000�0000000�00000023303�13262451251�0021610�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once struct CountryInfo { CountryInfo(const char *_name, const char *_iso2, const char *_code) : name(_name), iso2(_iso2), code(_code) { } const char *name, *iso2, *code; }; static const CountryInfo countries[] = { CountryInfo("Afghanistan", "AF", "93"), CountryInfo("Albania", "AL", "355"), CountryInfo("Algeria", "DZ", "213"), CountryInfo("American Samoa", "AS", "1684"), CountryInfo("Andorra", "AD", "376"), CountryInfo("Angola", "AO", "244"), CountryInfo("Anguilla", "AI", "1264"), CountryInfo("Antigua & Barbuda", "AG", "1268"), CountryInfo("Argentina", "AR", "54"), CountryInfo("Armenia", "AM", "374"), CountryInfo("Aruba", "AW", "297"), CountryInfo("Australia", "AU", "61"), CountryInfo("Austria", "AT", "43"), CountryInfo("Azerbaijan", "AZ", "994"), CountryInfo("Bahamas", "BS", "1242"), CountryInfo("Bahrain", "BH", "973"), CountryInfo("Bangladesh", "BD", "880"), CountryInfo("Barbados", "BB", "1246"), CountryInfo("Belarus", "BY", "375"), CountryInfo("Belgium", "BE", "32"), CountryInfo("Belize", "BZ", "501"), CountryInfo("Benin", "BJ", "229"), CountryInfo("Bermuda", "BM", "1441"), CountryInfo("Bhutan", "BT", "975"), CountryInfo("Bolivia", "BO", "591"), CountryInfo("Bonaire, Sint Eustatius & Saba", "BQ", "599"), CountryInfo("Bosnia & Herzegovina", "BA", "387"), CountryInfo("Botswana", "BW", "267"), CountryInfo("Brazil", "BR", "55"), CountryInfo("British Virgin Islands", "VG", "1284"), CountryInfo("Brunei Darussalam", "BN", "673"), CountryInfo("Bulgaria", "BG", "359"), CountryInfo("Burkina Faso", "BF", "226"), CountryInfo("Burundi", "BI", "257"), CountryInfo("Cambodia", "KH", "855"), CountryInfo("Cameroon", "CM", "237"), CountryInfo("Canada", "CA", "1"), CountryInfo("Cape Verde", "CV", "238"), CountryInfo("Cayman Islands", "KY", "1345"), CountryInfo("Central African Republic", "CF", "236"), CountryInfo("Chad", "TD", "235"), CountryInfo("Chile", "CL", "56"), CountryInfo("China", "CN", "86"), CountryInfo("Colombia", "CO", "57"), CountryInfo("Comoros", "KM", "269"), CountryInfo("Congo", "CG", "242"), CountryInfo("Congo, Democratic Republic", "CD", "243"), CountryInfo("Cook Islands", "CK", "682"), CountryInfo("Costa Rica", "CR", "506"), CountryInfo("Croatia", "HR", "385"), CountryInfo("Cuba", "CU", "53"), CountryInfo("Curaçao", "CW", "599"), CountryInfo("Cyprus", "CY", "357"), CountryInfo("Czech Republic", "CZ", "420"), CountryInfo("Côte d`Ivoire", "CI", "225"), CountryInfo("Denmark", "DK", "45"), CountryInfo("Diego Garcia", "IO", "246"), CountryInfo("Djibouti", "DJ", "253"), CountryInfo("Dominica", "DM", "1767"), CountryInfo("Dominican Republic", "DO", "1"), CountryInfo("Ecuador", "EC", "593"), CountryInfo("Egypt", "EG", "20"), CountryInfo("El Salvador", "SV", "503"), CountryInfo("Equatorial Guinea", "GQ", "240"), CountryInfo("Eritrea", "ER", "291"), CountryInfo("Estonia", "EE", "372"), CountryInfo("Ethiopia", "ET", "251"), CountryInfo("Falkland Islands", "FK", "500"), CountryInfo("Faroe Islands", "FO", "298"), CountryInfo("Fiji", "FJ", "679"), CountryInfo("Finland", "FI", "358"), CountryInfo("France", "FR", "33"), CountryInfo("French Guiana", "GF", "594"), CountryInfo("French Polynesia", "PF", "689"), CountryInfo("Gabon", "GA", "241"), CountryInfo("Gambia", "GM", "220"), CountryInfo("Georgia", "GE", "995"), CountryInfo("Germany", "DE", "49"), CountryInfo("Ghana", "GH", "233"), CountryInfo("Gibraltar", "GI", "350"), CountryInfo("Greece", "GR", "30"), CountryInfo("Greenland", "GL", "299"), CountryInfo("Grenada", "GD", "1473"), CountryInfo("Guadeloupe", "GP", "590"), CountryInfo("Guam", "GU", "1671"), CountryInfo("Guatemala", "GT", "502"), CountryInfo("Guinea", "GN", "224"), CountryInfo("Guinea-Bissau", "GW", "245"), CountryInfo("Guyana", "GY", "592"), CountryInfo("Haiti", "HT", "509"), CountryInfo("Honduras", "HN", "504"), CountryInfo("Hong Kong", "HK", "852"), CountryInfo("Hungary", "HU", "36"), CountryInfo("Iceland", "IS", "354"), CountryInfo("India", "IN", "91"), CountryInfo("Indonesia", "ID", "62"), CountryInfo("Iran", "IR", "98"), CountryInfo("Iraq", "IQ", "964"), CountryInfo("Ireland", "IE", "353"), CountryInfo("Israel", "IL", "972"), CountryInfo("Italy", "IT", "39"), CountryInfo("Jamaica", "JM", "1876"), CountryInfo("Japan", "JP", "81"), CountryInfo("Jordan", "JO", "962"), CountryInfo("Kazakhstan", "KZ", "7"), CountryInfo("Kenya", "KE", "254"), CountryInfo("Kiribati", "KI", "686"), CountryInfo("Kuwait", "KW", "965"), CountryInfo("Kyrgyzstan", "KG", "996"), CountryInfo("Laos", "LA", "856"), CountryInfo("Latvia", "LV", "371"), CountryInfo("Lebanon", "LB", "961"), CountryInfo("Lesotho", "LS", "266"), CountryInfo("Liberia", "LR", "231"), CountryInfo("Libya", "LY", "218"), CountryInfo("Liechtenstein", "LI", "423"), CountryInfo("Lithuania", "LT", "370"), CountryInfo("Luxembourg", "LU", "352"), CountryInfo("Macau", "MO", "853"), CountryInfo("Macedonia", "MK", "389"), CountryInfo("Madagascar", "MG", "261"), CountryInfo("Malawi", "MW", "265"), CountryInfo("Malaysia", "MY", "60"), CountryInfo("Maldives", "MV", "960"), CountryInfo("Mali", "ML", "223"), CountryInfo("Malta", "MT", "356"), CountryInfo("Marshall Islands", "MH", "692"), CountryInfo("Martinique", "MQ", "596"), CountryInfo("Mauritania", "MR", "222"), CountryInfo("Mauritius", "MU", "230"), CountryInfo("Mexico", "MX", "52"), CountryInfo("Micronesia", "FM", "691"), CountryInfo("Moldova", "MD", "373"), CountryInfo("Monaco", "MC", "377"), CountryInfo("Mongolia", "MN", "976"), CountryInfo("Montenegro", "ME", "382"), CountryInfo("Montserrat", "MS", "1664"), CountryInfo("Morocco", "MA", "212"), CountryInfo("Mozambique", "MZ", "258"), CountryInfo("Myanmar", "MM", "95"), CountryInfo("Namibia", "NA", "264"), CountryInfo("Nauru", "NR", "674"), CountryInfo("Nepal", "NP", "977"), CountryInfo("Netherlands", "NL", "31"), CountryInfo("New Caledonia", "NC", "687"), CountryInfo("New Zealand", "NZ", "64"), CountryInfo("Nicaragua", "NI", "505"), CountryInfo("Niger", "NE", "227"), CountryInfo("Nigeria", "NG", "234"), CountryInfo("Niue", "NU", "683"), CountryInfo("Norfolk Island", "NF", "672"), CountryInfo("North Korea", "KP", "850"), CountryInfo("Northern Mariana Islands", "MP", "1670"), CountryInfo("Norway", "NO", "47"), CountryInfo("Oman", "OM", "968"), CountryInfo("Pakistan", "PK", "92"), CountryInfo("Palau", "PW", "680"), CountryInfo("Palestine", "PS", "970"), CountryInfo("Panama", "PA", "507"), CountryInfo("Papua New Guinea", "PG", "675"), CountryInfo("Paraguay", "PY", "595"), CountryInfo("Peru", "PE", "51"), CountryInfo("Philippines", "PH", "63"), CountryInfo("Poland", "PL", "48"), CountryInfo("Portugal", "PT", "351"), CountryInfo("Puerto Rico", "PR", "1"), CountryInfo("Qatar", "QA", "974"), CountryInfo("Romania", "RO", "40"), CountryInfo("Russian Federation", "RU", "7"), CountryInfo("Rwanda", "RW", "250"), CountryInfo("Réunion", "RE", "262"), CountryInfo("Saint Helena", "SH", "247"), CountryInfo("Saint Helena", "SH2", "290"), CountryInfo("Saint Kitts & Nevis", "KN", "1869"), CountryInfo("Saint Lucia", "LC", "1758"), CountryInfo("Saint Pierre & Miquelon", "PM", "508"), CountryInfo("Saint Vincent & the Grenadines", "VC", "1784"), CountryInfo("Samoa", "WS", "685"), CountryInfo("San Marino", "SM", "378"), CountryInfo("Saudi Arabia", "SA", "966"), CountryInfo("Senegal", "SN", "221"), CountryInfo("Serbia", "RS", "381"), CountryInfo("Seychelles", "SC", "248"), CountryInfo("Sierra Leone", "SL", "232"), CountryInfo("Singapore", "SG", "65"), CountryInfo("Sint Maarten", "SX", "1721"), CountryInfo("Slovakia", "SK", "421"), CountryInfo("Slovenia", "SI", "386"), CountryInfo("Solomon Islands", "SB", "677"), CountryInfo("Somalia", "SO", "252"), CountryInfo("South Africa", "ZA", "27"), CountryInfo("South Korea", "KR", "82"), CountryInfo("South Sudan", "SS", "211"), CountryInfo("Spain", "ES", "34"), CountryInfo("Sri Lanka", "LK", "94"), CountryInfo("Sudan", "SD", "249"), CountryInfo("Suriname", "SR", "597"), CountryInfo("Swaziland", "SZ", "268"), CountryInfo("Sweden", "SE", "46"), CountryInfo("Switzerland", "CH", "41"), CountryInfo("Syrian Arab Republic", "SY", "963"), CountryInfo("São Tomé & Príncipe", "ST", "239"), CountryInfo("Taiwan", "TW", "886"), CountryInfo("Tajikistan", "TJ", "992"), CountryInfo("Tanzania", "TZ", "255"), CountryInfo("Thailand", "TH", "66"), CountryInfo("Timor-Leste", "TL", "670"), CountryInfo("Togo", "TG", "228"), CountryInfo("Tokelau", "TK", "690"), CountryInfo("Tonga", "TO", "676"), CountryInfo("Trinidad & Tobago", "TT", "1868"), CountryInfo("Tunisia", "TN", "216"), CountryInfo("Turkey", "TR", "90"), CountryInfo("Turkmenistan", "TM", "993"), CountryInfo("Turks & Caicos Islands", "TC", "1649"), CountryInfo("Tuvalu", "TV", "688"), CountryInfo("US Virgin Islands", "VI", "1340"), CountryInfo("USA", "US", "1"), CountryInfo("Uganda", "UG", "256"), CountryInfo("Ukraine", "UA", "380"), CountryInfo("United Arab Emirates", "AE", "971"), CountryInfo("United Kingdom", "GB", "44"), CountryInfo("Uruguay", "UY", "598"), CountryInfo("Uzbekistan", "UZ", "998"), CountryInfo("Vanuatu", "VU", "678"), CountryInfo("Venezuela", "VE", "58"), CountryInfo("Vietnam", "VN", "84"), CountryInfo("Wallis & Futuna", "WF", "681"), CountryInfo("Yemen", "YE", "967"), CountryInfo("Zambia", "ZM", "260"), CountryInfo("Zimbabwe", "ZW", "263"), }; typedef QHash<QString, const CountryInfo *> CountriesByCode; typedef QHash<QString, const CountryInfo *> CountriesByISO2; const CountriesByCode &countriesByCode(); const CountriesByISO2 &countriesByISO2(); �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/data/����������������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0020334�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/data/data_abstract_structure.cpp�������������������������������0000664�0000000�0000000�00000001364�13262451251�0025760�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "data/data_abstract_structure.h" namespace Data { namespace { using DataStructures = OrderedSet<AbstractStructure**>; NeverFreedPointer<DataStructures> structures; } // namespace namespace internal { void registerAbstractStructure(AbstractStructure **p) { structures.createIfNull(); structures->insert(p); } } // namespace internal void clearGlobalStructures() { if (!structures) return; for (auto &p : *structures) { delete (*p); *p = nullptr; } structures.clear(); } } // namespace Data ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/data/data_abstract_structure.h���������������������������������0000664�0000000�0000000�00000003403�13262451251�0025421�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace Data { // This module suggests a way to hold global data structures, that are // created on demand and deleted at the end of the app launch. // // Usage: // // class MyData : public Data::AbstractStruct { .. data .. }; // Data::GlobalStructurePointer<MyData> myData; // .. somewhere when needed .. // myData.createIfNull(); class AbstractStructure { public: virtual ~AbstractStructure() = 0; }; inline AbstractStructure::~AbstractStructure() = default; namespace internal { void registerAbstractStructure(AbstractStructure **p); } // namespace // Must be created in global scope! // Structure is derived from AbstractStructure. template <typename Structure> class GlobalStructurePointer { public: GlobalStructurePointer() = default; GlobalStructurePointer(const GlobalStructurePointer<Structure> &other) = delete; GlobalStructurePointer &operator=(const GlobalStructurePointer<Structure> &other) = delete; void createIfNull() { if (!_p) { _p = new Structure(); internal::registerAbstractStructure(&_p); } } Structure *operator->() { Assert(_p != nullptr); return static_cast<Structure*>(_p); } const Structure *operator->() const { Assert(_p != nullptr); return static_cast<const Structure*>(_p); } explicit operator bool() const { return _p != nullptr; } private: AbstractStructure *_p; }; // This method should be called at the end of the app launch. // It will destroy all data structures created by Data::GlobalStructurePointer. void clearGlobalStructures(); } // namespace Data �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/data/data_channel_admins.cpp�����������������������������������0000664�0000000�0000000�00000001664�13262451251�0025003�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "data/data_channel_admins.h" #include "history/history.h" namespace Data { ChannelAdminChanges::ChannelAdminChanges(not_null<ChannelData*> channel) : _channel(channel) , _admins(_channel->mgInfo->admins) { } void ChannelAdminChanges::feed(UserId userId, bool isAdmin) { if (isAdmin && !_admins.contains(userId)) { _admins.insert(userId); _changes.emplace(userId, true); } else if (!isAdmin && _admins.contains(userId)) { _admins.remove(userId); _changes.emplace(userId, false); } } ChannelAdminChanges::~ChannelAdminChanges() { if (!_changes.empty()) { if (auto history = App::historyLoaded(_channel)) { history->applyGroupAdminChanges(_changes); } } } } // namespace Data ����������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/data/data_channel_admins.h�������������������������������������0000664�0000000�0000000�00000001075�13262451251�0024444�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace Data { class ChannelAdminChanges { public: ChannelAdminChanges(not_null<ChannelData*> channel); void feed(UserId userId, bool isAdmin); ~ChannelAdminChanges(); private: not_null<ChannelData*> _channel; base::flat_set<UserId> &_admins; base::flat_map<UserId, bool> _changes; }; } // namespace Data �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/data/data_document.cpp�����������������������������������������0000664�0000000�0000000�00000105546�13262451251�0023662�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "data/data_document.h" #include "data/data_session.h" #include "lang/lang_keys.h" #include "inline_bots/inline_bot_layout_item.h" #include "mainwidget.h" #include "core/file_utilities.h" #include "media/media_audio.h" #include "storage/localstorage.h" #include "platform/platform_specific.h" #include "history/history.h" #include "history/history_item.h" #include "history/history_media_types.h" #include "window/window_controller.h" #include "auth_session.h" #include "mainwindow.h" #include "messenger.h" namespace { QString JoinStringList(const QStringList &list, const QString &separator) { const auto count = list.size(); if (!count) { return QString(); } auto result = QString(); auto fullsize = separator.size() * (count - 1); for (const auto &string : list) { fullsize += string.size(); } result.reserve(fullsize); result.append(list[0]); for (auto i = 1; i != count; ++i) { result.append(separator).append(list[i]); } return result; } } // namespace bool fileIsImage(const QString &name, const QString &mime) { QString lowermime = mime.toLower(), namelower = name.toLower(); if (lowermime.startsWith(qstr("image/"))) { return true; } else if (namelower.endsWith(qstr(".bmp")) || namelower.endsWith(qstr(".jpg")) || namelower.endsWith(qstr(".jpeg")) || namelower.endsWith(qstr(".gif")) || namelower.endsWith(qstr(".webp")) || namelower.endsWith(qstr(".tga")) || namelower.endsWith(qstr(".tiff")) || namelower.endsWith(qstr(".tif")) || namelower.endsWith(qstr(".psd")) || namelower.endsWith(qstr(".png"))) { return true; } return false; } QString FileNameUnsafe( const QString &title, const QString &filter, const QString &prefix, QString name, bool savingAs, const QDir &dir) { #ifdef Q_OS_WIN name = name.replace(QRegularExpression(qsl("[\\\\\\/\\:\\*\\?\\\"\\<\\>\\|]")), qsl("_")); #elif defined Q_OS_MAC name = name.replace(QRegularExpression(qsl("[\\:]")), qsl("_")); #elif defined Q_OS_LINUX name = name.replace(QRegularExpression(qsl("[\\/]")), qsl("_")); #endif if (Global::AskDownloadPath() || savingAs) { if (!name.isEmpty() && name.at(0) == QChar::fromLatin1('.')) { name = filedialogDefaultName(prefix, name); } else if (dir.path() != qsl(".")) { QString path = dir.absolutePath(); if (path != cDialogLastPath()) { cSetDialogLastPath(path); Local::writeUserSettings(); } } // check if extension of filename is present in filter // it should be in first filter section on the first place // place it there, if it is not QString ext = QFileInfo(name).suffix(), fil = filter, sep = qsl(";;"); if (!ext.isEmpty()) { if (QRegularExpression(qsl("^[a-zA-Z_0-9]+$")).match(ext).hasMatch()) { QStringList filters = filter.split(sep); if (filters.size() > 1) { QString first = filters.at(0); int32 start = first.indexOf(qsl("(*.")); if (start >= 0) { if (!QRegularExpression(qsl("\\(\\*\\.") + ext + qsl("[\\)\\s]"), QRegularExpression::CaseInsensitiveOption).match(first).hasMatch()) { QRegularExpressionMatch m = QRegularExpression(qsl(" \\*\\.") + ext + qsl("[\\)\\s]"), QRegularExpression::CaseInsensitiveOption).match(first); if (m.hasMatch() && m.capturedStart() > start + 3) { int32 oldpos = m.capturedStart(), oldend = m.capturedEnd(); fil = first.mid(0, start + 3) + ext + qsl(" *.") + first.mid(start + 3, oldpos - start - 3) + first.mid(oldend - 1) + sep + JoinStringList(filters.mid(1), sep); } else { fil = first.mid(0, start + 3) + ext + qsl(" *.") + first.mid(start + 3) + sep + JoinStringList(filters.mid(1), sep); } } } else { fil = QString(); } } else { fil = QString(); } } else { fil = QString(); } } return filedialogGetSaveFile(name, title, fil, name) ? name : QString(); } QString path; if (Global::DownloadPath().isEmpty()) { path = psDownloadPath(); } else if (Global::DownloadPath() == qsl("tmp")) { path = cTempDir(); } else { path = Global::DownloadPath(); } if (name.isEmpty()) name = qsl(".unknown"); if (name.at(0) == QChar::fromLatin1('.')) { if (!QDir().exists(path)) QDir().mkpath(path); return filedialogDefaultName(prefix, name, path); } if (dir.path() != qsl(".")) { path = dir.absolutePath() + '/'; } QString nameStart, extension; int32 extPos = name.lastIndexOf('.'); if (extPos >= 0) { nameStart = name.mid(0, extPos); extension = name.mid(extPos); } else { nameStart = name; } QString nameBase = path + nameStart; name = nameBase + extension; for (int i = 0; QFileInfo(name).exists(); ++i) { name = nameBase + QString(" (%1)").arg(i + 2) + extension; } if (!QDir().exists(path)) QDir().mkpath(path); return name; } QString FileNameForSave( const QString &title, const QString &filter, const QString &prefix, QString name, bool savingAs, const QDir &dir) { const auto result = FileNameUnsafe( title, filter, prefix, name, savingAs, dir); #ifdef Q_OS_WIN const auto lower = result.trimmed().toLower(); const auto kBadExtensions = { qstr(".lnk"), qstr(".scf") }; const auto kMaskExtension = qsl(".download"); for (const auto extension : kBadExtensions) { if (lower.endsWith(extension)) { return result + kMaskExtension; } } #endif // Q_OS_WIN return result; } QString documentSaveFilename(const DocumentData *data, bool forceSavingAs = false, const QString already = QString(), const QDir &dir = QDir()) { auto alreadySavingFilename = data->loadingFilePath(); if (!alreadySavingFilename.isEmpty()) { return alreadySavingFilename; } QString name, filter, caption, prefix; MimeType mimeType = mimeTypeForName(data->mimeString()); QStringList p = mimeType.globPatterns(); QString pattern = p.isEmpty() ? QString() : p.front(); if (data->isVoiceMessage()) { auto mp3 = data->hasMimeType(qstr("audio/mp3")); name = already.isEmpty() ? (mp3 ? qsl(".mp3") : qsl(".ogg")) : already; filter = mp3 ? qsl("MP3 Audio (*.mp3);;") : qsl("OGG Opus Audio (*.ogg);;"); filter += FileDialog::AllFilesFilter(); caption = lang(lng_save_audio); prefix = qsl("audio"); } else if (data->isVideoFile()) { name = already.isEmpty() ? data->filename() : already; if (name.isEmpty()) { name = pattern.isEmpty() ? qsl(".mov") : pattern.replace('*', QString()); } if (pattern.isEmpty()) { filter = qsl("MOV Video (*.mov);;") + FileDialog::AllFilesFilter(); } else { filter = mimeType.filterString() + qsl(";;") + FileDialog::AllFilesFilter(); } caption = lang(lng_save_video); prefix = qsl("video"); } else { name = already.isEmpty() ? data->filename() : already; if (name.isEmpty()) { name = pattern.isEmpty() ? qsl(".unknown") : pattern.replace('*', QString()); } if (pattern.isEmpty()) { filter = QString(); } else { filter = mimeType.filterString() + qsl(";;") + FileDialog::AllFilesFilter(); } caption = lang(data->isAudioFile() ? lng_save_audio_file : lng_save_file); prefix = qsl("doc"); } return FileNameForSave(caption, filter, prefix, name, forceSavingAs, dir); } void DocumentOpenClickHandler::doOpen( not_null<DocumentData*> data, HistoryItem *context, ActionOnLoad action) { if (!data->date) return; auto msgId = context ? context->fullId() : FullMsgId(); bool playVoice = data->isVoiceMessage(); bool playMusic = data->isAudioFile(); bool playVideo = data->isVideoFile(); bool playAnimation = data->isAnimation(); auto &location = data->location(true); if (data->isTheme()) { if (!location.isEmpty() && location.accessEnable()) { Messenger::Instance().showDocument(data, context); location.accessDisable(); return; } } if (!location.isEmpty() || (!data->data().isEmpty() && (playVoice || playMusic || playVideo || playAnimation))) { using State = Media::Player::State; if (playVoice) { auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice); if (state.id == AudioMsgId(data, msgId) && !Media::Player::IsStoppedOrStopping(state.state)) { if (Media::Player::IsPaused(state.state) || state.state == State::Pausing) { Media::Player::mixer()->resume(state.id); } else { Media::Player::mixer()->pause(state.id); } } else { auto audio = AudioMsgId(data, msgId); Media::Player::mixer()->play(audio); Media::Player::Updated().notify(audio); data->session()->data().markMediaRead(data); } } else if (playMusic) { auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Song); if (state.id == AudioMsgId(data, msgId) && !Media::Player::IsStoppedOrStopping(state.state)) { if (Media::Player::IsPaused(state.state) || state.state == State::Pausing) { Media::Player::mixer()->resume(state.id); } else { Media::Player::mixer()->pause(state.id); } } else { auto song = AudioMsgId(data, msgId); Media::Player::mixer()->play(song); Media::Player::Updated().notify(song); } } else if (playVideo) { if (!data->data().isEmpty()) { Messenger::Instance().showDocument(data, context); } else if (location.accessEnable()) { Messenger::Instance().showDocument(data, context); location.accessDisable(); } else { auto filepath = location.name(); if (documentIsValidMediaFile(filepath)) { File::Launch(filepath); } } data->session()->data().markMediaRead(data); } else if (data->isVoiceMessage() || data->isAudioFile() || data->isVideoFile()) { auto filepath = location.name(); if (documentIsValidMediaFile(filepath)) { File::Launch(filepath); } data->session()->data().markMediaRead(data); } else if (data->size < App::kImageSizeLimit) { if (!data->data().isEmpty() && playAnimation) { if (action == ActionOnLoadPlayInline && context) { data->session()->data().requestAnimationPlayInline(context); } else { Messenger::Instance().showDocument(data, context); } } else if (location.accessEnable()) { if (playAnimation || QImageReader(location.name()).canRead()) { if (playAnimation && action == ActionOnLoadPlayInline && context) { data->session()->data().requestAnimationPlayInline(context); } else { Messenger::Instance().showDocument(data, context); } } else { File::Launch(location.name()); } location.accessDisable(); } else { File::Launch(location.name()); } } else { File::Launch(location.name()); } return; } if (data->status != FileReady) return; QString filename; if (!data->saveToCache()) { filename = documentSaveFilename(data); if (filename.isEmpty()) return; } data->save(filename, action, msgId); } void DocumentOpenClickHandler::onClickImpl() const { const auto data = document(); const auto action = data->isVoiceMessage() ? ActionOnLoadNone : ActionOnLoadOpen; doOpen(data, getActionItem(), action); } void GifOpenClickHandler::onClickImpl() const { doOpen(document(), getActionItem(), ActionOnLoadPlayInline); } void DocumentSaveClickHandler::doSave( not_null<DocumentData*> data, bool forceSavingAs) { if (!data->date) return; auto filepath = data->filepath( DocumentData::FilePathResolveSaveFromDataSilent, forceSavingAs); if (!filepath.isEmpty() && !forceSavingAs) { File::OpenWith(filepath, QCursor::pos()); } else { auto fileinfo = QFileInfo(filepath); auto filedir = filepath.isEmpty() ? QDir() : fileinfo.dir(); auto filename = filepath.isEmpty() ? QString() : fileinfo.fileName(); auto newfname = documentSaveFilename(data, forceSavingAs, filename, filedir); if (!newfname.isEmpty()) { data->save(newfname, ActionOnLoadNone, FullMsgId()); } } } void DocumentSaveClickHandler::onClickImpl() const { doSave(document()); } void DocumentCancelClickHandler::onClickImpl() const { const auto data = document(); if (!data->date) return; if (data->uploading()) { if (const auto item = App::histItemById(context())) { App::main()->cancelUploadLayer(item); } } else { data->cancel(); } } VoiceData::~VoiceData() { if (!waveform.isEmpty() && waveform[0] == -1 && waveform.size() > int32(sizeof(TaskId))) { TaskId taskId = 0; memcpy(&taskId, waveform.constData() + 1, sizeof(taskId)); Local::cancelTask(taskId); } } DocumentData::DocumentData(DocumentId id, not_null<AuthSession*> session) : id(id) , _session(session) { } not_null<AuthSession*> DocumentData::session() const { return _session; } void DocumentData::setattributes(const QVector<MTPDocumentAttribute> &attributes) { for (int32 i = 0, l = attributes.size(); i < l; ++i) { switch (attributes[i].type()) { case mtpc_documentAttributeImageSize: { auto &d = attributes[i].c_documentAttributeImageSize(); dimensions = QSize(d.vw.v, d.vh.v); } break; case mtpc_documentAttributeAnimated: if (type == FileDocument || type == StickerDocument || type == VideoDocument) { type = AnimatedDocument; _additional = nullptr; } break; case mtpc_documentAttributeSticker: { auto &d = attributes[i].c_documentAttributeSticker(); if (type == FileDocument) { type = StickerDocument; _additional = std::make_unique<StickerData>(); } if (sticker()) { sticker()->alt = qs(d.valt); if (sticker()->set.type() != mtpc_inputStickerSetID || d.vstickerset.type() == mtpc_inputStickerSetID) { sticker()->set = d.vstickerset; } } } break; case mtpc_documentAttributeVideo: { auto &d = attributes[i].c_documentAttributeVideo(); if (type == FileDocument) { type = d.is_round_message() ? RoundVideoDocument : VideoDocument; } _duration = d.vduration.v; dimensions = QSize(d.vw.v, d.vh.v); } break; case mtpc_documentAttributeAudio: { auto &d = attributes[i].c_documentAttributeAudio(); if (type == FileDocument) { if (d.is_voice()) { type = VoiceDocument; _additional = std::make_unique<VoiceData>(); } else { type = SongDocument; _additional = std::make_unique<SongData>(); } } if (const auto voiceData = voice()) { voiceData->duration = d.vduration.v; VoiceWaveform waveform = documentWaveformDecode(qba(d.vwaveform)); uchar wavemax = 0; for (int32 i = 0, l = waveform.size(); i < l; ++i) { uchar waveat = waveform.at(i); if (wavemax < waveat) wavemax = waveat; } voiceData->waveform = waveform; voiceData->wavemax = wavemax; } else if (const auto songData = song()) { songData->duration = d.vduration.v; songData->title = qs(d.vtitle); songData->performer = qs(d.vperformer); } } break; case mtpc_documentAttributeFilename: { const auto &attribute = attributes[i]; _filename = qs( attribute.c_documentAttributeFilename().vfile_name); // We don't want LTR/RTL mark/embedding/override/isolate chars // in filenames, because they introduce a security issue, when // an executable "Fil[x]gepj.exe" may look like "Filexe.jpeg". QChar controls[] = { 0x200E, // LTR Mark 0x200F, // RTL Mark 0x202A, // LTR Embedding 0x202B, // RTL Embedding 0x202D, // LTR Override 0x202E, // RTL Override 0x2066, // LTR Isolate 0x2067, // RTL Isolate }; for (const auto ch : controls) { _filename = std::move(_filename).replace(ch, "_"); } } break; } } if (type == StickerDocument) { if (dimensions.width() <= 0 || dimensions.height() <= 0 || dimensions.width() > StickerMaxSize || dimensions.height() > StickerMaxSize || !saveToCache()) { type = FileDocument; _additional = nullptr; } } } bool DocumentData::saveToCache() const { return (type == StickerDocument && size < Storage::kMaxStickerInMemory) || (isAnimation() && size < Storage::kMaxAnimationInMemory) || (isVoiceMessage() && size < Storage::kMaxVoiceInMemory); } void DocumentData::forget() { thumb->forget(); if (sticker()) sticker()->img->forget(); replyPreview->forget(); _data.clear(); } void DocumentData::automaticLoad(const HistoryItem *item) { if (loaded() || status != FileReady) return; if (saveToCache() && _loader != CancelledMtpFileLoader) { if (type == StickerDocument) { save(QString(), _actionOnLoad, _actionOnLoadMsgId); } else if (isAnimation()) { bool loadFromCloud = false; if (item) { if (item->history()->peer->isUser()) { loadFromCloud = !(cAutoDownloadGif() & dbiadNoPrivate); } else { loadFromCloud = !(cAutoDownloadGif() & dbiadNoGroups); } } else { // if load at least anywhere loadFromCloud = !(cAutoDownloadGif() & dbiadNoPrivate) || !(cAutoDownloadGif() & dbiadNoGroups); } save(QString(), _actionOnLoad, _actionOnLoadMsgId, loadFromCloud ? LoadFromCloudOrLocal : LoadFromLocalOnly, true); } else if (isVoiceMessage()) { if (item) { bool loadFromCloud = false; if (item->history()->peer->isUser()) { loadFromCloud = !(cAutoDownloadAudio() & dbiadNoPrivate); } else { loadFromCloud = !(cAutoDownloadAudio() & dbiadNoGroups); } save(QString(), _actionOnLoad, _actionOnLoadMsgId, loadFromCloud ? LoadFromCloudOrLocal : LoadFromLocalOnly, true); } } } } void DocumentData::automaticLoadSettingsChanged() { if (loaded() || status != FileReady || (!isAnimation() && !isVoiceMessage()) || !saveToCache() || _loader != CancelledMtpFileLoader) { return; } _loader = nullptr; } void DocumentData::performActionOnLoad() { if (_actionOnLoad == ActionOnLoadNone) return; auto loc = location(true); auto already = loc.name(); auto item = _actionOnLoadMsgId.msg ? App::histItemById(_actionOnLoadMsgId) : nullptr; auto showImage = !isVideoFile() && (size < App::kImageSizeLimit); auto playVoice = isVoiceMessage() && (_actionOnLoad == ActionOnLoadPlayInline || _actionOnLoad == ActionOnLoadOpen); auto playMusic = isAudioFile() && (_actionOnLoad == ActionOnLoadPlayInline || _actionOnLoad == ActionOnLoadOpen); auto playAnimation = isAnimation() && (_actionOnLoad == ActionOnLoadPlayInline || _actionOnLoad == ActionOnLoadOpen) && showImage && item; if (auto applyTheme = isTheme()) { if (!loc.isEmpty() && loc.accessEnable()) { Messenger::Instance().showDocument(this, item); loc.accessDisable(); return; } } using State = Media::Player::State; if (playVoice) { if (loaded()) { auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice); if (state.id == AudioMsgId(this, _actionOnLoadMsgId) && !Media::Player::IsStoppedOrStopping(state.state)) { if (Media::Player::IsPaused(state.state) || state.state == State::Pausing) { Media::Player::mixer()->resume(state.id); } else { Media::Player::mixer()->pause(state.id); } } else if (Media::Player::IsStopped(state.state)) { Media::Player::mixer()->play(AudioMsgId(this, _actionOnLoadMsgId)); _session->data().markMediaRead(this); } } } else if (playMusic) { if (loaded()) { auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Song); if (state.id == AudioMsgId(this, _actionOnLoadMsgId) && !Media::Player::IsStoppedOrStopping(state.state)) { if (Media::Player::IsPaused(state.state) || state.state == State::Pausing) { Media::Player::mixer()->resume(state.id); } else { Media::Player::mixer()->pause(state.id); } } else if (Media::Player::IsStopped(state.state)) { auto song = AudioMsgId(this, _actionOnLoadMsgId); Media::Player::mixer()->play(song); Media::Player::Updated().notify(song); } } } else if (playAnimation) { if (loaded()) { if (_actionOnLoad == ActionOnLoadPlayInline && item) { _session->data().requestAnimationPlayInline(item); } else { Messenger::Instance().showDocument(this, item); } } } else { if (already.isEmpty()) return; if (_actionOnLoad == ActionOnLoadOpenWith) { File::OpenWith(already, QCursor::pos()); } else if (_actionOnLoad == ActionOnLoadOpen || _actionOnLoad == ActionOnLoadPlayInline) { if (isVoiceMessage() || isAudioFile() || isVideoFile()) { if (documentIsValidMediaFile(already)) { File::Launch(already); } _session->data().markMediaRead(this); } else if (loc.accessEnable()) { if (showImage && QImageReader(loc.name()).canRead()) { Messenger::Instance().showDocument(this, item); } else { File::Launch(already); } loc.accessDisable(); } else { File::Launch(already); } } } _actionOnLoad = ActionOnLoadNone; } bool DocumentData::loaded(FilePathResolveType type) const { if (loading() && _loader->finished()) { if (_loader->cancelled()) { destroyLoaderDelayed(CancelledMtpFileLoader); } else { auto that = const_cast<DocumentData*>(this); that->_location = FileLocation(_loader->fileName()); that->_data = _loader->bytes(); if (that->sticker() && !_loader->imagePixmap().isNull()) { that->sticker()->img = ImagePtr(_data, _loader->imageFormat(), _loader->imagePixmap()); } destroyLoaderDelayed(); } _session->data().notifyDocumentLayoutChanged(this); } return !data().isEmpty() || !filepath(type).isEmpty(); } void DocumentData::destroyLoaderDelayed(mtpFileLoader *newValue) const { _loader->stop(); auto loader = std::unique_ptr<FileLoader>(std::exchange(_loader, newValue)); _session->downloader().delayedDestroyLoader(std::move(loader)); } bool DocumentData::loading() const { return _loader && _loader != CancelledMtpFileLoader; } QString DocumentData::loadingFilePath() const { return loading() ? _loader->fileName() : QString(); } bool DocumentData::displayLoading() const { return loading() ? (!_loader->loadingLocal() || !_loader->autoLoading()) : (uploading() && !waitingForAlbum()); } float64 DocumentData::progress() const { if (uploading()) { if (uploadingData->size > 0) { const auto result = float64(uploadingData->offset) / uploadingData->size; return snap(result, 0., 1.); } return 0.; } return loading() ? _loader->currentProgress() : (loaded() ? 1. : 0.); } int32 DocumentData::loadOffset() const { return loading() ? _loader->currentOffset() : 0; } bool DocumentData::uploading() const { return (uploadingData != nullptr); } void DocumentData::setWaitingForAlbum() { if (uploading()) { uploadingData->waitingForAlbum = true; } } bool DocumentData::waitingForAlbum() const { return uploading() && uploadingData->waitingForAlbum; } void DocumentData::save( const QString &toFile, ActionOnLoad action, const FullMsgId &actionMsgId, LoadFromCloudSetting fromCloud, bool autoLoading) { if (loaded(FilePathResolveChecked)) { auto &l = location(true); if (!toFile.isEmpty()) { if (!_data.isEmpty()) { QFile f(toFile); f.open(QIODevice::WriteOnly); f.write(_data); f.close(); setLocation(FileLocation(toFile)); Local::writeFileLocation(mediaKey(), FileLocation(toFile)); } else if (l.accessEnable()) { auto alreadyName = l.name(); if (alreadyName != toFile) { QFile(toFile).remove(); QFile(alreadyName).copy(toFile); } l.accessDisable(); } } _actionOnLoad = action; _actionOnLoadMsgId = actionMsgId; performActionOnLoad(); return; } if (_loader == CancelledMtpFileLoader) _loader = nullptr; if (_loader) { if (!_loader->setFileName(toFile)) { cancel(); // changes _actionOnLoad _loader = nullptr; } } _actionOnLoad = action; _actionOnLoadMsgId = actionMsgId; if (_loader) { if (fromCloud == LoadFromCloudOrLocal) _loader->permitLoadFromCloud(); } else { status = FileReady; if (hasWebLocation()) { _loader = new mtpFileLoader(&_urlLocation, size, fromCloud, autoLoading); } else if (!_access && !_url.isEmpty()) { _loader = new webFileLoader(_url, toFile, fromCloud, autoLoading); } else { _loader = new mtpFileLoader(_dc, id, _access, _version, locationType(), toFile, size, (saveToCache() ? LoadToCacheAsWell : LoadToFileOnly), fromCloud, autoLoading); } _loader->connect(_loader, SIGNAL(progress(FileLoader*)), App::main(), SLOT(documentLoadProgress(FileLoader*))); _loader->connect(_loader, SIGNAL(failed(FileLoader*,bool)), App::main(), SLOT(documentLoadFailed(FileLoader*,bool))); _loader->start(); } _session->data().notifyDocumentLayoutChanged(this); } void DocumentData::cancel() { if (!loading()) { return; } auto loader = std::unique_ptr<FileLoader>(std::exchange(_loader, CancelledMtpFileLoader)); loader->cancel(); loader->stop(); _session->downloader().delayedDestroyLoader(std::move(loader)); _session->data().notifyDocumentLayoutChanged(this); if (auto main = App::main()) { main->documentLoadProgress(this); } _actionOnLoad = ActionOnLoadNone; } VoiceWaveform documentWaveformDecode(const QByteArray &encoded5bit) { auto bitsCount = static_cast<int>(encoded5bit.size() * 8); auto valuesCount = bitsCount / 5; if (!valuesCount) { return VoiceWaveform(); } // Read each 5 bit of encoded5bit as 0-31 unsigned char. // We count the index of the byte in which the desired 5-bit sequence starts. // And then we read a uint16 starting from that byte to guarantee to get all of those 5 bits. // // BUT! if it is the last byte we have, we're not allowed to read a uint16 starting with it. // Because it will be an overflow (we'll access one byte after the available memory). // We see, that only the last 5 bits could start in the last available byte and be problematic. // So we read in a general way all the entries in a general way except the last one. auto result = VoiceWaveform(valuesCount, 0); auto bitsData = encoded5bit.constData(); for (auto i = 0, l = valuesCount - 1; i != l; ++i) { auto byteIndex = (i * 5) / 8; auto bitShift = (i * 5) % 8; auto value = *reinterpret_cast<const uint16*>(bitsData + byteIndex); result[i] = static_cast<char>((value >> bitShift) & 0x1F); } auto lastByteIndex = ((valuesCount - 1) * 5) / 8; auto lastBitShift = ((valuesCount - 1) * 5) % 8; auto lastValue = (lastByteIndex == encoded5bit.size() - 1) ? static_cast<uint16>(*reinterpret_cast<const uchar*>(bitsData + lastByteIndex)) : *reinterpret_cast<const uint16*>(bitsData + lastByteIndex); result[valuesCount - 1] = static_cast<char>((lastValue >> lastBitShift) & 0x1F); return result; } QByteArray documentWaveformEncode5bit(const VoiceWaveform &waveform) { auto bitsCount = waveform.size() * 5; auto bytesCount = (bitsCount + 7) / 8; auto result = QByteArray(bytesCount + 1, 0); auto bitsData = result.data(); // Write each 0-31 unsigned char as 5 bit to result. // We reserve one extra byte to be able to dereference any of required bytes // as a uint16 without overflowing, even the byte with index "bytesCount - 1". for (auto i = 0, l = waveform.size(); i < l; ++i) { auto byteIndex = (i * 5) / 8; auto bitShift = (i * 5) % 8; auto value = (static_cast<uint16>(waveform[i]) & 0x1F) << bitShift; *reinterpret_cast<uint16*>(bitsData + byteIndex) |= value; } result.resize(bytesCount); return result; } QByteArray DocumentData::data() const { return _data; } const FileLocation &DocumentData::location(bool check) const { if (check && !_location.check()) { const_cast<DocumentData*>(this)->_location = Local::readFileLocation(mediaKey()); } return _location; } void DocumentData::setLocation(const FileLocation &loc) { if (loc.check()) { _location = loc; } } QString DocumentData::filepath(FilePathResolveType type, bool forceSavingAs) const { bool check = (type != FilePathResolveCached); QString result = (check && _location.name().isEmpty()) ? QString() : location(check).name(); bool saveFromData = result.isEmpty() && !data().isEmpty(); if (saveFromData) { if (type != FilePathResolveSaveFromData && type != FilePathResolveSaveFromDataSilent) { saveFromData = false; } else if (type == FilePathResolveSaveFromDataSilent && (Global::AskDownloadPath() || forceSavingAs)) { saveFromData = false; } } if (saveFromData) { QString filename = documentSaveFilename(this, forceSavingAs); if (!filename.isEmpty()) { QFile f(filename); if (f.open(QIODevice::WriteOnly)) { if (f.write(data()) == data().size()) { f.close(); const_cast<DocumentData*>(this)->_location = FileLocation(filename); Local::writeFileLocation(mediaKey(), _location); result = filename; } } } } return result; } bool DocumentData::isStickerSetInstalled() const { Expects(sticker() != nullptr); const auto &set = sticker()->set; const auto &sets = _session->data().stickerSets(); switch (set.type()) { case mtpc_inputStickerSetID: { auto it = sets.constFind(set.c_inputStickerSetID().vid.v); return (it != sets.cend()) && !(it->flags & MTPDstickerSet::Flag::f_archived) && (it->flags & MTPDstickerSet::Flag::f_installed_date); } break; case mtpc_inputStickerSetShortName: { auto name = qs(set.c_inputStickerSetShortName().vshort_name).toLower(); for (auto it = sets.cbegin(), e = sets.cend(); it != e; ++it) { if (it->shortName.toLower() == name) { return !(it->flags & MTPDstickerSet::Flag::f_archived) && (it->flags & MTPDstickerSet::Flag::f_installed_date); } } } break; } return false; } ImagePtr DocumentData::makeReplyPreview() { if (replyPreview->isNull() && !thumb->isNull()) { if (thumb->loaded()) { int w = thumb->width(), h = thumb->height(); if (w <= 0) w = 1; if (h <= 0) h = 1; auto thumbSize = (w > h) ? QSize(w * st::msgReplyBarSize.height() / h, st::msgReplyBarSize.height()) : QSize(st::msgReplyBarSize.height(), h * st::msgReplyBarSize.height() / w); thumbSize *= cIntRetinaFactor(); auto options = Images::Option::Smooth | (isVideoMessage() ? Images::Option::Circled : Images::Option::None) | Images::Option::TransparentBackground; auto outerSize = st::msgReplyBarSize.height(); auto image = thumb->pixNoCache(thumbSize.width(), thumbSize.height(), options, outerSize, outerSize); replyPreview = ImagePtr(image, "PNG"); } else { thumb->load(); } } return replyPreview; } StickerData *DocumentData::sticker() const { return (type == StickerDocument) ? static_cast<StickerData*>(_additional.get()) : nullptr; } void DocumentData::checkSticker() { const auto data = sticker(); if (!data) return; automaticLoad(nullptr); if (data->img->isNull() && loaded()) { if (_data.isEmpty()) { const FileLocation &loc(location(true)); if (loc.accessEnable()) { data->img = ImagePtr(loc.name()); loc.accessDisable(); } } else { data->img = ImagePtr(_data); } } } SongData *DocumentData::song() { return isSong() ? static_cast<SongData*>(_additional.get()) : nullptr; } const SongData *DocumentData::song() const { return const_cast<DocumentData*>(this)->song(); } VoiceData *DocumentData::voice() { return isVoiceMessage() ? static_cast<VoiceData*>(_additional.get()) : nullptr; } const VoiceData *DocumentData::voice() const { return const_cast<DocumentData*>(this)->voice(); } bool DocumentData::hasRemoteLocation() const { return (_dc != 0 && _access != 0); } bool DocumentData::hasWebLocation() const { return _urlLocation.dc() != 0 && _urlLocation.accessHash() != 0; } bool DocumentData::isValid() const { return hasRemoteLocation() || hasWebLocation() || !_url.isEmpty(); } MTPInputDocument DocumentData::mtpInput() const { if (_access) { return MTP_inputDocument( MTP_long(id), MTP_long(_access)); } return MTP_inputDocumentEmpty(); } QString DocumentData::filename() const { return _filename; } QString DocumentData::mimeString() const { return _mimeString; } bool DocumentData::hasMimeType(QLatin1String mime) const { return !_mimeString.compare(mime, Qt::CaseInsensitive); } void DocumentData::setMimeString(const QString &mime) { _mimeString = mime; } MediaKey DocumentData::mediaKey() const { return ::mediaKey(locationType(), _dc, id, _version); } QString DocumentData::composeNameString() const { if (auto songData = song()) { return ComposeNameString( _filename, songData->title, songData->performer); } return ComposeNameString(_filename, QString(), QString()); } LocationType DocumentData::locationType() const { return isVoiceMessage() ? AudioFileLocation : isVideoFile() ? VideoFileLocation : DocumentFileLocation; } bool DocumentData::isVoiceMessage() const { return (type == VoiceDocument); } bool DocumentData::isVideoMessage() const { return (type == RoundVideoDocument); } bool DocumentData::isAnimation() const { return (type == AnimatedDocument) || isVideoMessage() || hasMimeType(qstr("image/gif")); } bool DocumentData::isGifv() const { return (type == AnimatedDocument) && hasMimeType(qstr("video/mp4")); } bool DocumentData::isTheme() const { return _filename.endsWith( qstr(".tdesktop-theme"), Qt::CaseInsensitive) || _filename.endsWith( qstr(".tdesktop-palette"), Qt::CaseInsensitive); } bool DocumentData::isSong() const { return (type == SongDocument); } bool DocumentData::isAudioFile() const { if (isVoiceMessage()) { return false; } else if (isSong()) { return true; } return _mimeString.startsWith(qstr("audio/"), Qt::CaseInsensitive); } bool DocumentData::isSharedMediaMusic() const { if (const auto songData = song()) { return (songData->duration > 0); } return false; } bool DocumentData::isVideoFile() const { return (type == VideoDocument); } int32 DocumentData::duration() const { return (isAnimation() || isVideoFile()) ? _duration : -1; } bool DocumentData::isImage() const { return !isAnimation() && !isVideoFile() && (_duration > 0); } void DocumentData::recountIsImage() { if (isAnimation() || isVideoFile()) { return; } _duration = fileIsImage(filename(), mimeString()) ? 1 : -1; // hack } bool DocumentData::setRemoteVersion(int32 version) { if (_version == version) { return false; } _version = version; _location = FileLocation(); _data = QByteArray(); status = FileReady; if (loading()) { destroyLoaderDelayed(); } return true; } void DocumentData::setRemoteLocation(int32 dc, uint64 access) { if (_dc != dc || _access != access) { _dc = dc; _access = access; if (isValid()) { if (_location.check()) { Local::writeFileLocation(mediaKey(), _location); } else { _location = Local::readFileLocation(mediaKey()); } } } } void DocumentData::setContentUrl(const QString &url) { _url = url; } void DocumentData::setWebLocation(const WebFileLocation &location) { _urlLocation = location; } void DocumentData::collectLocalData(DocumentData *local) { if (local == this) return; if (!local->_data.isEmpty()) { _data = local->_data; if (isVoiceMessage()) { if (!Local::copyAudio(local->mediaKey(), mediaKey())) { Local::writeAudio(mediaKey(), _data); } } else { if (!Local::copyStickerImage(local->mediaKey(), mediaKey())) { Local::writeStickerImage(mediaKey(), _data); } } } if (!local->_location.isEmpty()) { _location = local->_location; Local::writeFileLocation(mediaKey(), _location); } } DocumentData::~DocumentData() { if (loading()) { destroyLoaderDelayed(); } } QString DocumentData::ComposeNameString( const QString &filename, const QString &songTitle, const QString &songPerformer) { if (songTitle.isEmpty() && songPerformer.isEmpty()) { return filename.isEmpty() ? qsl("Unknown File") : filename; } if (songPerformer.isEmpty()) { return songTitle; } auto trackTitle = (songTitle.isEmpty() ? qsl("Unknown Track") : songTitle); return songPerformer + QString::fromUtf8(" \xe2\x80\x93 ") + trackTitle; } ����������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/data/data_document.h�������������������������������������������0000664�0000000�0000000�00000016115�13262451251�0023320�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "data/data_types.h" class AuthSession; inline uint64 mediaMix32To64(int32 a, int32 b) { return (uint64(*reinterpret_cast<uint32*>(&a)) << 32) | uint64(*reinterpret_cast<uint32*>(&b)); } // Old method, should not be used anymore. //inline MediaKey mediaKey(LocationType type, int32 dc, const uint64 &id) { // return MediaKey(mediaMix32To64(type, dc), id); //} // New method when version was introduced, type is not relevant anymore (all files are Documents). inline MediaKey mediaKey( LocationType type, int32 dc, const uint64 &id, int32 version) { return (version > 0) ? MediaKey(mediaMix32To64(version, dc), id) : MediaKey(mediaMix32To64(type, dc), id); } inline StorageKey mediaKey(const MTPDfileLocation &location) { return storageKey( location.vdc_id.v, location.vvolume_id.v, location.vlocal_id.v); } struct DocumentAdditionalData { virtual ~DocumentAdditionalData() = default; }; struct StickerData : public DocumentAdditionalData { ImagePtr img; QString alt; MTPInputStickerSet set = MTP_inputStickerSetEmpty(); StorageImageLocation loc; // doc thumb location }; struct SongData : public DocumentAdditionalData { int32 duration = 0; QString title, performer; }; struct VoiceData : public DocumentAdditionalData { ~VoiceData(); int duration = 0; VoiceWaveform waveform; char wavemax = 0; }; bool fileIsImage(const QString &name, const QString &mime); namespace Serialize { class Document; } // namespace Serialize; class DocumentData { public: DocumentData(DocumentId id, not_null<AuthSession*> session); not_null<AuthSession*> session() const; void setattributes( const QVector<MTPDocumentAttribute> &attributes); void automaticLoad(const HistoryItem *item); // auto load sticker or video void automaticLoadSettingsChanged(); enum FilePathResolveType { FilePathResolveCached, FilePathResolveChecked, FilePathResolveSaveFromData, FilePathResolveSaveFromDataSilent, }; bool loaded( FilePathResolveType type = FilePathResolveCached) const; bool loading() const; QString loadingFilePath() const; bool displayLoading() const; void save( const QString &toFile, ActionOnLoad action = ActionOnLoadNone, const FullMsgId &actionMsgId = FullMsgId(), LoadFromCloudSetting fromCloud = LoadFromCloudOrLocal, bool autoLoading = false); void cancel(); float64 progress() const; int32 loadOffset() const; bool uploading() const; void setWaitingForAlbum(); bool waitingForAlbum() const; QByteArray data() const; const FileLocation &location(bool check = false) const; void setLocation(const FileLocation &loc); QString filepath( FilePathResolveType type = FilePathResolveCached, bool forceSavingAs = false) const; bool saveToCache() const; void performActionOnLoad(); void forget(); ImagePtr makeReplyPreview(); StickerData *sticker() const; void checkSticker(); bool isStickerSetInstalled() const; SongData *song(); const SongData *song() const; VoiceData *voice(); const VoiceData *voice() const; bool isVoiceMessage() const; bool isVideoMessage() const; bool isSong() const; bool isAudioFile() const; bool isVideoFile() const; bool isAnimation() const; bool isGifv() const; bool isTheme() const; bool isSharedMediaMusic() const; int32 duration() const; bool isImage() const; void recountIsImage(); void setData(const QByteArray &data) { _data = data; } bool setRemoteVersion(int32 version); // Returns true if version has changed. void setRemoteLocation(int32 dc, uint64 access); void setContentUrl(const QString &url); void setWebLocation(const WebFileLocation &location); bool hasRemoteLocation() const; bool hasWebLocation() const; bool isValid() const; MTPInputDocument mtpInput() const; // When we have some client-side generated document // (for example for displaying an external inline bot result) // and it has downloaded data, we can collect that data from it // to (this) received from the server "same" document. void collectLocalData(DocumentData *local); QString filename() const; QString mimeString() const; bool hasMimeType(QLatin1String mime) const; void setMimeString(const QString &mime); MediaKey mediaKey() const; static QString ComposeNameString( const QString &filename, const QString &songTitle, const QString &songPerformer); QString composeNameString() const; ~DocumentData(); DocumentId id = 0; DocumentType type = FileDocument; QSize dimensions; int32 date = 0; ImagePtr thumb, replyPreview; int32 size = 0; FileStatus status = FileReady; std::unique_ptr<Data::UploadState> uploadingData; private: friend class Serialize::Document; LocationType locationType() const; void destroyLoaderDelayed(mtpFileLoader *newValue = nullptr) const; // Two types of location: from MTProto by dc+access+version or from web by url int32 _dc = 0; uint64 _access = 0; int32 _version = 0; QString _url; QString _filename; QString _mimeString; WebFileLocation _urlLocation; not_null<AuthSession*> _session; FileLocation _location; QByteArray _data; std::unique_ptr<DocumentAdditionalData> _additional; int32 _duration = -1; ActionOnLoad _actionOnLoad = ActionOnLoadNone; FullMsgId _actionOnLoadMsgId; mutable FileLoader *_loader = nullptr; }; VoiceWaveform documentWaveformDecode(const QByteArray &encoded5bit); QByteArray documentWaveformEncode5bit(const VoiceWaveform &waveform); class DocumentClickHandler : public FileClickHandler { public: DocumentClickHandler( not_null<DocumentData*> document, FullMsgId context = FullMsgId()) : FileClickHandler(context) , _document(document) { } not_null<DocumentData*> document() const { return _document; } private: not_null<DocumentData*> _document; }; class DocumentSaveClickHandler : public DocumentClickHandler { public: using DocumentClickHandler::DocumentClickHandler; static void doSave( not_null<DocumentData*> document, bool forceSavingAs = false); protected: void onClickImpl() const override; }; class DocumentOpenClickHandler : public DocumentClickHandler { public: using DocumentClickHandler::DocumentClickHandler; static void doOpen( not_null<DocumentData*> document, HistoryItem *context, ActionOnLoad action = ActionOnLoadOpen); protected: void onClickImpl() const override; }; class DocumentCancelClickHandler : public DocumentClickHandler { public: using DocumentClickHandler::DocumentClickHandler; protected: void onClickImpl() const override; }; class GifOpenClickHandler : public DocumentOpenClickHandler { public: using DocumentOpenClickHandler::DocumentOpenClickHandler; protected: void onClickImpl() const override; }; class VoiceSeekClickHandler : public DocumentOpenClickHandler { public: using DocumentOpenClickHandler::DocumentOpenClickHandler; protected: void onClickImpl() const override { } }; QString FileNameForSave( const QString &title, const QString &filter, const QString &prefix, QString name, bool savingAs, const QDir &dir = QDir()); ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/data/data_drafts.cpp�������������������������������������������0000664�0000000�0000000�00000004136�13262451251�0023320�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "data/data_drafts.h" #include "ui/widgets/input_fields.h" #include "chat_helpers/message_field.h" #include "history/history.h" #include "history/history_widget.h" #include "mainwidget.h" #include "storage/localstorage.h" namespace Data { namespace { } // namespace Draft::Draft( const TextWithTags &textWithTags, MsgId msgId, const MessageCursor &cursor, bool previewCancelled, mtpRequestId saveRequestId) : textWithTags(textWithTags) , msgId(msgId) , cursor(cursor) , previewCancelled(previewCancelled) , saveRequestId(saveRequestId) { } Draft::Draft( not_null<const Ui::FlatTextarea*> field, MsgId msgId, bool previewCancelled, mtpRequestId saveRequestId) : textWithTags(field->getTextWithTags()) , msgId(msgId) , cursor(field) , previewCancelled(previewCancelled) { } void applyPeerCloudDraft(PeerId peerId, const MTPDdraftMessage &draft) { auto history = App::history(peerId); auto text = TextWithEntities { qs(draft.vmessage), draft.has_entities() ? TextUtilities::EntitiesFromMTP(draft.ventities.v) : EntitiesInText() }; auto textWithTags = TextWithTags { TextUtilities::ApplyEntities(text), ConvertEntitiesToTextTags(text.entities) }; auto replyTo = draft.has_reply_to_msg_id() ? draft.vreply_to_msg_id.v : MsgId(0); auto cloudDraft = std::make_unique<Draft>(textWithTags, replyTo, MessageCursor(QFIXED_MAX, QFIXED_MAX, QFIXED_MAX), draft.is_no_webpage()); cloudDraft->date = draft.vdate.v; history->setCloudDraft(std::move(cloudDraft)); history->createLocalDraftFromCloud(); history->updateChatListSortPosition(); if (auto main = App::main()) { main->applyCloudDraft(history); } } void clearPeerCloudDraft(PeerId peerId) { auto history = App::history(peerId); history->clearCloudDraft(); history->clearLocalDraft(); history->updateChatListSortPosition(); if (auto main = App::main()) { main->applyCloudDraft(history); } } } // namespace Data ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/data/data_drafts.h���������������������������������������������0000664�0000000�0000000�00000003043�13262451251�0022761�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace Ui { class FlatTextarea; } // namespace Ui namespace Data { void applyPeerCloudDraft(PeerId peerId, const MTPDdraftMessage &draft); void clearPeerCloudDraft(PeerId peerId); struct Draft { Draft() = default; Draft( const TextWithTags &textWithTags, MsgId msgId, const MessageCursor &cursor, bool previewCancelled, mtpRequestId saveRequestId = 0); Draft( not_null<const Ui::FlatTextarea*> field, MsgId msgId, bool previewCancelled, mtpRequestId saveRequestId = 0); TimeId date = 0; TextWithTags textWithTags; MsgId msgId = 0; // replyToId for message draft, editMsgId for edit draft MessageCursor cursor; bool previewCancelled = false; mtpRequestId saveRequestId = 0; }; inline bool draftStringIsEmpty(const QString &text) { for_const (auto ch, text) { if (!ch.isSpace()) { return false; } } return true; } inline bool draftIsNull(Draft *draft) { return !draft || (draftStringIsEmpty(draft->textWithTags.text) && !draft->msgId); } inline bool draftsAreEqual(Draft *a, Draft *b) { bool aIsNull = draftIsNull(a); bool bIsNull = draftIsNull(b); if (aIsNull) { return bIsNull; } else if (bIsNull) { return false; } return (a->textWithTags == b->textWithTags) && (a->msgId == b->msgId) && (a->previewCancelled == b->previewCancelled); } } // namespace Data ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/data/data_feed.cpp���������������������������������������������0000664�0000000�0000000�00000030451�13262451251�0022737�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "data/data_feed.h" #include "data/data_session.h" #include "dialogs/dialogs_key.h" #include "history/history.h" #include "history/history_item.h" #include "lang/lang_keys.h" #include "storage/storage_facade.h" #include "storage/storage_feed_messages.h" #include "auth_session.h" #include "apiwrap.h" #include "mainwidget.h" namespace Data { // #feed //MessagePosition FeedPositionFromMTP(const MTPFeedPosition &position) { // Expects(position.type() == mtpc_feedPosition); // // const auto &data = position.c_feedPosition(); // return MessagePosition(data.vdate.v, FullMsgId( // peerToChannel(peerFromMTP(data.vpeer)), // data.vid.v)); //} Feed::Feed(FeedId id, not_null<Data::Session*> parent) : Entry(this) , _id(id) , _parent(parent) , _name(lang(lng_feed_name)) { indexNameParts(); } FeedId Feed::id() const { return _id; } void Feed::indexNameParts() { _nameWords.clear(); _nameFirstLetters.clear(); auto toIndexList = QStringList(); auto appendToIndex = [&](const QString &value) { if (!value.isEmpty()) { toIndexList.push_back(TextUtilities::RemoveAccents(value)); } }; appendToIndex(_name); const auto appendTranslit = !toIndexList.isEmpty() && cRussianLetters().match(toIndexList.front()).hasMatch(); if (appendTranslit) { appendToIndex(translitRusEng(toIndexList.front())); } auto toIndex = toIndexList.join(' '); toIndex += ' ' + rusKeyboardLayoutSwitch(toIndex); const auto namesList = TextUtilities::PrepareSearchWords(toIndex); for (const auto &name : namesList) { _nameWords.insert(name); _nameFirstLetters.insert(name[0]); } } void Feed::registerOne(not_null<ChannelData*> channel) { const auto history = App::history(channel); if (!base::contains(_channels, history)) { const auto invisible = (_channels.size() < 2); _channels.push_back(history); _parent->session().storage().invalidate( Storage::FeedMessagesInvalidate(_id)); if (history->lastMessageKnown()) { if (const auto last = history->lastMessage()) { if (justUpdateLastMessage(last)) { updateChatListEntry(); } } } else if (lastMessageKnown()) { _parent->session().api().requestDialogEntry(history); } if (unreadCountKnown()) { if (history->unreadCountKnown()) { // If history unreadCount is known that means that we've // already had the channel information and if it was in the // feed already (not yet known) it wouldn't get here. // That means here we get if we add a new channel to feed. if (const auto count = history->unreadCount()) { unreadCountChanged(count, history->mute() ? count : 0); } } else if (!_settingChannels) { _parent->session().api().requestDialogEntry(this); } } if (invisible && _channels.size() > 1) { updateChatListExistence(); for (const auto history : _channels) { history->updateChatListExistence(); } } else { history->updateChatListExistence(); } _parent->notifyFeedUpdated(this, FeedUpdateFlag::Channels); } } void Feed::unregisterOne(not_null<ChannelData*> channel) { const auto history = App::history(channel); const auto i = ranges::remove(_channels, history); if (i != end(_channels)) { const auto visible = (_channels.size() > 1); _channels.erase(i, end(_channels)); _parent->session().storage().remove( Storage::FeedMessagesRemoveAll(_id, channel->bareId())); if (lastMessageKnown()) { if (const auto last = lastMessage()) { if (last->history() == history) { recountLastMessage(); } } } if (unreadCountKnown()) { if (history->unreadCountKnown()) { if (const auto delta = -history->unreadCount()) { unreadCountChanged(delta, history->mute() ? delta : 0); } } else { _parent->session().api().requestDialogEntry(this); } } if (visible && _channels.size() < 2) { updateChatListExistence(); for (const auto history : _channels) { history->updateChatListExistence(); } } else { history->updateChatListExistence(); } _parent->notifyFeedUpdated(this, FeedUpdateFlag::Channels); } } void Feed::updateLastMessage(not_null<HistoryItem*> item) { if (justUpdateLastMessage(item)) { if (_lastMessage && *_lastMessage) { setChatsListDate(ItemDateTime(*_lastMessage)); } } } void Feed::loadUserpic() { constexpr auto kPaintUserpicsCount = 4; auto load = kPaintUserpicsCount; for (const auto channel : _channels) { channel->peer->loadUserpic(); if (!--load) { break; } } } void Feed::paintUserpic( Painter &p, int x, int y, int size) const { const auto small = (size - st::lineWidth) / 2; const auto delta = size - small; auto index = 0; for (const auto channel : _channels) { channel->peer->paintUserpic(p, x, y, small); switch (++index) { case 1: case 3: x += delta; break; case 2: x -= delta; y += delta; break; case 4: return; } } } const std::vector<not_null<History*>> &Feed::channels() const { return _channels; } int32 Feed::channelsHash() const { const auto ordered = ranges::view::all( _channels ) | ranges::view::transform([](not_null<History*> history) { return history->peer->bareId(); }) | ranges::to_vector | ranges::action::sort; return Api::CountHash(ordered); } bool Feed::channelsLoaded() const { return _channelsLoaded; } void Feed::setChannelsLoaded(bool loaded) { if (_channelsLoaded != loaded) { _channelsLoaded = loaded; _parent->notifyFeedUpdated(this, FeedUpdateFlag::Channels); } } void Feed::setChannels(std::vector<not_null<ChannelData*>> channels) { const auto remove = ranges::view::all( _channels ) | ranges::view::transform([](not_null<History*> history) { return not_null<ChannelData*>(history->peer->asChannel()); }) | ranges::view::filter([&](not_null<ChannelData*> channel) { return !base::contains(channels, channel); }) | ranges::to_vector; const auto add = ranges::view::all( channels ) | ranges::view::filter([&](not_null<ChannelData*> channel) { return ranges::find( _channels, channel.get(), [](auto history) { return history->peer->asChannel(); } ) == end(_channels); }) | ranges::view::transform([](ChannelData *channel) { return not_null<ChannelData*>(channel); }) | ranges::to_vector; changeChannelsList(add, remove); setChannelsLoaded(true); } void Feed::changeChannelsList( const std::vector<not_null<ChannelData*>> &add, const std::vector<not_null<ChannelData*>> &remove) { _settingChannels = true; const auto restore = gsl::finally([&] { _settingChannels = false; }); for (const auto channel : remove) { channel->clearFeed(); } // We assume the last message was correct before requesting the list. // So we save it and don't allow channels from the list to change it. // After that we restore it. const auto oldLastMessage = base::take(_lastMessage); for (const auto channel : add) { _lastMessage = base::none; channel->setFeed(this); } _lastMessage = oldLastMessage; } bool Feed::justUpdateLastMessage(not_null<HistoryItem*> item) { if (!_lastMessage) { return false; } else if (*_lastMessage && item->position() <= (*_lastMessage)->position()) { return false; } _lastMessage = item; return true; } void Feed::messageRemoved(not_null<HistoryItem*> item) { if (lastMessage() == item) { recountLastMessage(); } } void Feed::historyCleared(not_null<History*> history) { if (const auto last = lastMessage()) { if (last->history() == history) { messageRemoved(last); } } } void Feed::recountLastMessage() { _lastMessage = base::none; for (const auto history : _channels) { if (!history->lastMessageKnown()) { _parent->session().api().requestDialogEntry(this); return; } } setLastMessageFromChannels(); } void Feed::setLastMessageFromChannels() { _lastMessage = nullptr; for (const auto history : _channels) { if (const auto last = history->lastMessage()) { justUpdateLastMessage(last); } } updateChatsListDate(); } void Feed::updateChatsListDate() { if (_lastMessage && *_lastMessage) { setChatsListDate(ItemDateTime(*_lastMessage)); } } HistoryItem *Feed::lastMessage() const { return _lastMessage ? *_lastMessage : nullptr; } bool Feed::lastMessageKnown() const { return !!_lastMessage; } int Feed::unreadCount() const { return _unreadCount ? *_unreadCount : 0; } rpl::producer<int> Feed::unreadCountValue() const { return rpl::single( unreadCount() ) | rpl::then(_unreadCountChanges.events()); } bool Feed::unreadCountKnown() const { return !!_unreadCount; } // #feed //void Feed::applyDialog(const MTPDdialogFeed &data) { // const auto addChannel = [&](ChannelId channelId) { // if (const auto channel = App::channelLoaded(channelId)) { // channel->setFeed(this); // } // }; // for (const auto &channelId : data.vfeed_other_channels.v) { // addChannel(channelId.v); // } // // _lastMessage = nullptr; // if (const auto peerId = peerFromMTP(data.vpeer)) { // if (const auto channelId = peerToChannel(peerId)) { // addChannel(channelId); // const auto fullId = FullMsgId(channelId, data.vtop_message.v); // if (const auto item = App::histItemById(fullId)) { // justUpdateLastMessage(item); // } // } // } // updateChatsListDate(); // // setUnreadCounts( // data.vunread_count.v, // data.vunread_muted_count.v); // if (data.has_read_max_position()) { // setUnreadPosition(FeedPositionFromMTP(data.vread_max_position)); // } //} void Feed::changedInChatListHook(Dialogs::Mode list, bool added) { if (list == Dialogs::Mode::All && unreadCount()) { const auto mutedCount = _unreadMutedCount; const auto nonMutedCount = unreadCount() - mutedCount; const auto mutedDelta = added ? mutedCount : -mutedCount; const auto nonMutedDelta = added ? nonMutedCount : -nonMutedCount; App::histories().unreadIncrement(nonMutedDelta, false); App::histories().unreadIncrement(mutedDelta, true); } } void Feed::setUnreadCounts(int unreadNonMutedCount, int unreadMutedCount) { if (unreadCountKnown() && (*_unreadCount == unreadNonMutedCount + unreadMutedCount) && (_unreadMutedCount == unreadMutedCount)) { return; } const auto unreadNonMutedCountDelta = _unreadCount | [&](int count) { return unreadNonMutedCount - (count - _unreadMutedCount); }; const auto unreadMutedCountDelta = _unreadCount | [&](int count) { return unreadMutedCount - _unreadMutedCount; }; _unreadCount = unreadNonMutedCount + unreadMutedCount; _unreadMutedCount = unreadMutedCount; _unreadCountChanges.fire(unreadCount()); updateChatListEntry(); if (inChatList(Dialogs::Mode::All)) { App::histories().unreadIncrement( unreadNonMutedCountDelta ? *unreadNonMutedCountDelta : unreadNonMutedCount, false); App::histories().unreadIncrement( unreadMutedCountDelta ? *unreadMutedCountDelta : unreadMutedCount, true); } } void Feed::setUnreadPosition(const MessagePosition &position) { if (_unreadPosition.current() < position) { _unreadPosition = position; } } void Feed::unreadCountChanged( int unreadCountDelta, int mutedCountDelta) { if (!unreadCountKnown()) { return; } accumulate_max(unreadCountDelta, -*_unreadCount); *_unreadCount += unreadCountDelta; mutedCountDelta = snap( mutedCountDelta, -_unreadMutedCount, *_unreadCount - _unreadMutedCount); _unreadMutedCount += mutedCountDelta; _unreadCountChanges.fire(unreadCount()); updateChatListEntry(); if (inChatList(Dialogs::Mode::All)) { App::histories().unreadIncrement( unreadCountDelta, false); App::histories().unreadMuteChanged(mutedCountDelta, true); } } MessagePosition Feed::unreadPosition() const { return _unreadPosition.current(); } rpl::producer<MessagePosition> Feed::unreadPositionChanges() const { return _unreadPosition.changes(); } bool Feed::toImportant() const { return false; // TODO feeds workmode } bool Feed::shouldBeInChatList() const { return _channels.size() > 1; } int Feed::chatListUnreadCount() const { return unreadCount(); } bool Feed::chatListMutedBadge() const { return _unreadCount ? (*_unreadCount <= _unreadMutedCount) : false; } HistoryItem *Feed::chatsListItem() const { return lastMessage(); } const QString &Feed::chatsListName() const { return _name; } const base::flat_set<QString> &Feed::chatsListNameWords() const { return _nameWords; } const base::flat_set<QChar> &Feed::chatsListFirstLetters() const { return _nameFirstLetters; } } // namespace Data �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/data/data_feed.h�����������������������������������������������0000664�0000000�0000000�00000006173�13262451251�0022410�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "dialogs/dialogs_entry.h" #include "data/data_messages.h" class ChannelData; namespace Data { class Session; class Feed; enum class FeedUpdateFlag { Channels, ChannelPhoto, }; struct FeedUpdate { not_null<Data::Feed*> feed; FeedUpdateFlag flag; }; //MessagePosition FeedPositionFromMTP(const MTPFeedPosition &position); // #feed class Feed : public Dialogs::Entry { public: static constexpr auto kId = 1; static constexpr auto kChannelsLimit = 1000; Feed(FeedId id, not_null<Data::Session*> parent); FeedId id() const; void registerOne(not_null<ChannelData*> channel); void unregisterOne(not_null<ChannelData*> channel); void updateLastMessage(not_null<HistoryItem*> item); void messageRemoved(not_null<HistoryItem*> item); void historyCleared(not_null<History*> history); //void applyDialog(const MTPDdialogFeed &data); // #feed void setUnreadCounts(int unreadNonMutedCount, int unreadMutedCount); void setUnreadPosition(const MessagePosition &position); void unreadCountChanged( int unreadCountDelta, int mutedCountDelta); rpl::producer<int> unreadCountValue() const; MessagePosition unreadPosition() const; rpl::producer<MessagePosition> unreadPositionChanges() const; HistoryItem *lastMessage() const; bool lastMessageKnown() const; int unreadCount() const; bool unreadCountKnown() const; bool toImportant() const override; bool shouldBeInChatList() const override; int chatListUnreadCount() const override; bool chatListMutedBadge() const override; HistoryItem *chatsListItem() const override; const QString &chatsListName() const override; const base::flat_set<QString> &chatsListNameWords() const override; const base::flat_set<QChar> &chatsListFirstLetters() const override; void changedInChatListHook(Dialogs::Mode list, bool added) override; void loadUserpic() override; void paintUserpic( Painter &p, int x, int y, int size) const override; const std::vector<not_null<History*>> &channels() const; int32 channelsHash() const; bool channelsLoaded() const; void setChannelsLoaded(bool loaded); void setChannels(std::vector<not_null<ChannelData*>> channels); private: void indexNameParts(); void recountLastMessage(); void setLastMessageFromChannels(); bool justUpdateLastMessage(not_null<HistoryItem*> item); void updateChatsListDate(); void changeChannelsList( const std::vector<not_null<ChannelData*>> &add, const std::vector<not_null<ChannelData*>> &remove); FeedId _id = 0; not_null<Data::Session*> _parent; std::vector<not_null<History*>> _channels; bool _settingChannels = false; bool _channelsLoaded = false; QString _name; base::flat_set<QString> _nameWords; base::flat_set<QChar> _nameFirstLetters; base::optional<HistoryItem*> _lastMessage; rpl::variable<MessagePosition> _unreadPosition; base::optional<int> _unreadCount; rpl::event_stream<int> _unreadCountChanges; int _unreadMutedCount = 0; }; } // namespace Data �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/data/data_feed_messages.cpp������������������������������������0000664�0000000�0000000�00000006464�13262451251�0024635�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "data/data_feed_messages.h" #include "apiwrap.h" #include "auth_session.h" #include "data/data_session.h" #include "storage/storage_feed_messages.h" namespace Data { rpl::producer<MessagesSlice> FeedMessagesViewer( Storage::FeedMessagesKey key, int limitBefore, int limitAfter) { Expects(IsServerMsgId(key.position.fullId.msg) || (key.position.fullId.msg == 0)); return [=](auto consumer) { auto lifetime = rpl::lifetime(); const auto builder = lifetime.make_state<MessagesSliceBuilder>( key.position, limitBefore, limitAfter); const auto feed = Auth().data().feed(key.feedId); using AroundData = MessagesSliceBuilder::AroundData; const auto requestMediaAround = [=](const AroundData &data) { if (data.aroundId || !key.position) { //Auth().api().requestFeedMessages( // #feed // feed, // data.aroundId, // data.direction); } }; builder->insufficientAround( ) | rpl::start_with_next(requestMediaAround, lifetime); const auto pushNextSnapshot = [=] { consumer.put_next(builder->snapshot()); }; using SliceUpdate = Storage::FeedMessagesSliceUpdate; Auth().storage().feedMessagesSliceUpdated( ) | rpl::filter([=](const SliceUpdate &update) { return (update.feedId == key.feedId); }) | rpl::filter([=](const SliceUpdate &update) { return builder->applyUpdate(update.data); }) | rpl::start_with_next(pushNextSnapshot, lifetime); using OneRemoved = Storage::FeedMessagesRemoveOne; Auth().storage().feedMessagesOneRemoved( ) | rpl::filter([=](const OneRemoved &update) { return (update.feedId == key.feedId); }) | rpl::filter([=](const OneRemoved &update) { return builder->removeOne(update.messageId); }) | rpl::start_with_next(pushNextSnapshot, lifetime); using AllRemoved = Storage::FeedMessagesRemoveAll; Auth().storage().feedMessagesAllRemoved( ) | rpl::filter([=](const AllRemoved &update) { return (update.feedId == key.feedId); }) | rpl::filter([=](const AllRemoved &update) { return builder->removeFromChannel(update.channelId); }) | rpl::start_with_next(pushNextSnapshot, lifetime); using Invalidate = Storage::FeedMessagesInvalidate; Auth().storage().feedMessagesInvalidated( ) | rpl::filter([=](const Invalidate &update) { return (update.feedId == key.feedId); }) | rpl::filter([=] { return builder->invalidated(); }) | rpl::start_with_next(pushNextSnapshot, lifetime); using InvalidateBottom = Storage::FeedMessagesInvalidateBottom; Auth().storage().feedMessagesBottomInvalidated( ) | rpl::filter([=](const InvalidateBottom &update) { return (update.feedId == key.feedId); }) | rpl::filter([=] { return builder->bottomInvalidated(); }) | rpl::start_with_next(pushNextSnapshot, lifetime); using Result = Storage::FeedMessagesResult; Auth().storage().query(Storage::FeedMessagesQuery( key, limitBefore, limitAfter )) | rpl::filter([=](const Result &result) { return builder->applyInitial(result); }) | rpl::start_with_next_done( pushNextSnapshot, [=] { builder->checkInsufficient(); }, lifetime); return lifetime; }; } } // namespace Data ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/data/data_feed_messages.h��������������������������������������0000664�0000000�0000000�00000001032�13262451251�0024264�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "data/data_feed.h" #include "data/data_messages.h" namespace Storage { struct FeedMessagesKey; } // namespace Storage namespace Data { rpl::producer<MessagesSlice> FeedMessagesViewer( Storage::FeedMessagesKey key, int limitBefore, int limitAfter); } // namespace Data ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/data/data_flags.h����������������������������������������������0000664�0000000�0000000�00000003027�13262451251�0022574�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/event_stream.h> namespace Data { template <typename FlagsType> using FlagsUnderlying = typename FlagsType::Type; template < typename FlagsType, FlagsUnderlying<FlagsType> kEssential = FlagsUnderlying<FlagsType>(-1)> class Flags { public: using Type = FlagsType; using Enum = typename Type::Enum; struct Change { using Type = FlagsType; using Enum = typename Type::Enum; Change(Type diff, Type value) : diff(diff) , value(value) { } Type diff = 0; Type value = 0; }; Flags() = default; Flags(Type value) : _value(value) { } void set(Type which) { if (auto diff = which ^ _value) { _value = which; updated(diff); } } void add(Type which) { if (auto diff = which & ~_value) { _value |= which; updated(diff); } } void remove(Type which) { if (auto diff = which & _value) { _value &= ~which; updated(diff); } } auto current() const { return _value; } auto changes() const { return _changes.events(); } auto value() const { return _changes.events_starting_with({ Type::from_raw(kEssential), _value }); } private: void updated(Type diff) { if ((diff &= Type::from_raw(kEssential))) { _changes.fire({ diff, _value }); } } Type _value = 0; rpl::event_stream<Change> _changes; }; } // namespace Data ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/data/data_game.h�����������������������������������������������0000664�0000000�0000000�00000001704�13262451251�0022411�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "data/data_photo.h" #include "data/data_document.h" struct GameData { GameData(const GameId &id) : id(id) { } GameData( const GameId &id, const uint64 &accessHash, const QString &shortName, const QString &title, const QString &description, PhotoData *photo, DocumentData *document) : id(id) , accessHash(accessHash) , shortName(shortName) , title(title) , description(description) , photo(photo) , document(document) { } void forget() { if (document) document->forget(); if (photo) photo->forget(); } GameId id = 0; uint64 accessHash = 0; QString shortName; QString title; QString description; PhotoData *photo = nullptr; DocumentData *document = nullptr; }; ������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/data/data_groups.cpp�������������������������������������������0000664�0000000�0000000�00000006425�13262451251�0023357�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "data/data_groups.h" #include "history/history_item.h" #include "data/data_media_types.h" #include "data/data_session.h" namespace Data { namespace { constexpr auto kMaxItemsInGroup = 10; } // namespace Groups::Groups(not_null<Session*> data) : _data(data) { } bool Groups::isGrouped(not_null<HistoryItem*> item) const { if (!item->groupId()) { return false; } const auto media = item->media(); return media && media->canBeGrouped(); } void Groups::registerMessage(not_null<HistoryItem*> item) { if (!isGrouped(item)) { return; } const auto i = _groups.emplace(item->groupId(), Group()).first; auto &items = i->second.items; if (items.size() < kMaxItemsInGroup) { items.insert(findPositionForItem(items, item), item); if (items.size() > 1) { refreshViews(items); } } } void Groups::unregisterMessage(not_null<const HistoryItem*> item) { const auto groupId = item->groupId(); if (!groupId) { return; } const auto i = _groups.find(groupId); if (i != end(_groups)) { auto &items = i->second.items; const auto removed = ranges::remove(items, item); const auto last = end(items); if (removed != last) { items.erase(removed, last); if (!items.empty()) { refreshViews(items); } else { _groups.erase(i); } } } } void Groups::refreshMessage(not_null<HistoryItem*> item) { if (!isGrouped(item)) { unregisterMessage(item); return; } if (!IsServerMsgId(item->id)) { return; } const auto groupId = item->groupId(); const auto i = _groups.find(groupId); if (i == end(_groups)) { registerMessage(item); return; } auto &items = i->second.items; const auto position = findPositionForItem(items, item); auto current = ranges::find(items, item); if (current == end(items)) { items.insert(position, item); } else if (position == current + 1) { return; } else if (position > current + 1) { for (++current; current != position; ++current) { std::swap(*(current - 1), *current); } } else if (position < current) { for (; current != position; --current) { std::swap(*(current - 1), *current); } } else { Unexpected("Position of item in Groups::refreshMessage()."); } refreshViews(items); } HistoryItemsList::const_iterator Groups::findPositionForItem( const HistoryItemsList &group, not_null<HistoryItem*> item) { const auto itemId = item->id; const auto last = end(group); if (!IsServerMsgId(itemId)) { return last; } for (auto result = begin(group); result != last; ++result) { const auto alreadyId = (*result)->id; if (IsServerMsgId(alreadyId) && alreadyId > itemId) { return result; } } return last; } const Group *Groups::find(not_null<HistoryItem*> item) const { const auto groupId = item->groupId(); if (!groupId) { return nullptr; } const auto i = _groups.find(groupId); if (i != _groups.end()) { const auto &result = i->second; if (result.items.size() > 1) { return &result; } } return nullptr; } void Groups::refreshViews(const HistoryItemsList &items) { for (const auto item : items) { _data->requestItemViewRefresh(item); } } } // namespace Data �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/data/data_groups.h���������������������������������������������0000664�0000000�0000000�00000001772�13262451251�0023024�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "data/data_types.h" namespace Data { class Session; struct Group { HistoryItemsList items; }; class Groups { public: Groups(not_null<Session*> data); bool isGrouped(not_null<HistoryItem*> item) const; void registerMessage(not_null<HistoryItem*> item); void unregisterMessage(not_null<const HistoryItem*> item); void refreshMessage(not_null<HistoryItem*> item); const Group *find(not_null<HistoryItem*> item) const; private: HistoryItemsList::const_iterator findPositionForItem( const HistoryItemsList &group, not_null<HistoryItem*> item); void refreshViews(const HistoryItemsList &items); not_null<Session*> _data; std::map<MessageGroupId, Group> _groups; std::map<MessageGroupId, MessageGroupId> _alias; }; } // namespace Data ������tdesktop-1.2.17/Telegram/SourceFiles/data/data_media_types.cpp��������������������������������������0000664�0000000�0000000�00000067760�13262451251�0024354�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "data/data_media_types.h" #include "history/history_media_types.h" #include "history/history_item.h" #include "history/history_location_manager.h" #include "history/view/history_view_element.h" #include "ui/text_options.h" #include "storage/storage_shared_media.h" #include "storage/localstorage.h" #include "data/data_session.h" #include "lang/lang_keys.h" #include "auth_session.h" #include "layout.h" namespace Data { namespace { Call ComputeCallData(const MTPDmessageActionPhoneCall &call) { auto result = Call(); result.finishReason = [&] { if (call.has_reason()) { switch (call.vreason.type()) { case mtpc_phoneCallDiscardReasonBusy: return CallFinishReason::Busy; case mtpc_phoneCallDiscardReasonDisconnect: return CallFinishReason::Disconnected; case mtpc_phoneCallDiscardReasonHangup: return CallFinishReason::Hangup; case mtpc_phoneCallDiscardReasonMissed: return CallFinishReason::Missed; } Unexpected("Call reason type."); } return CallFinishReason::Hangup; }(); result.duration = call.has_duration() ? call.vduration.v : 0;; return result; } Invoice ComputeInvoiceData(const MTPDmessageMediaInvoice &data) { auto result = Invoice(); result.isTest = data.is_test(); result.amount = data.vtotal_amount.v; result.currency = qs(data.vcurrency); result.description = qs(data.vdescription); result.title = TextUtilities::SingleLine(qs(data.vtitle)); if (data.has_receipt_msg_id()) { result.receiptMsgId = data.vreceipt_msg_id.v; } if (data.has_photo()) { const auto thumb = ImagePtr(); result.photo = Auth().data().photoFromWeb(data.vphoto, thumb); } return result; } QString WithCaptionDialogsText( const QString &attachType, const QString &caption) { if (caption.isEmpty()) { return textcmdLink(1, TextUtilities::Clean(attachType)); } auto captionText = TextUtilities::Clean(caption); auto attachTypeWrapped = textcmdLink(1, lng_dialogs_text_media_wrapped( lt_media, TextUtilities::Clean(attachType))); return lng_dialogs_text_media( lt_media_part, attachTypeWrapped, lt_caption, captionText); } QString WithCaptionNotificationText( const QString &attachType, const QString &caption) { if (caption.isEmpty()) { return attachType; } auto attachTypeWrapped = lng_dialogs_text_media_wrapped( lt_media, attachType); return lng_dialogs_text_media( lt_media_part, attachTypeWrapped, lt_caption, caption); } } // namespace TextWithEntities WithCaptionClipboardText( const QString &attachType, TextWithEntities &&caption) { TextWithEntities result; result.text.reserve(5 + attachType.size() + caption.text.size()); result.text.append(qstr("[ ")).append(attachType).append(qstr(" ]")); if (!caption.text.isEmpty()) { result.text.append(qstr("\n")); TextUtilities::Append(result, std::move(caption)); } return result; } Media::Media(not_null<HistoryItem*> parent) : _parent(parent) { } not_null<HistoryItem*> Media::parent() const { return _parent; } DocumentData *Media::document() const { return nullptr; } PhotoData *Media::photo() const { return nullptr; } WebPageData *Media::webpage() const { return nullptr; } const SharedContact *Media::sharedContact() const { return nullptr; } const Call *Media::call() const { return nullptr; } GameData *Media::game() const { return nullptr; } const Invoice *Media::invoice() const { return nullptr; } LocationData *Media::location() const { return nullptr; } bool Media::uploading() const { return false; } Storage::SharedMediaTypesMask Media::sharedMediaTypes() const { return {}; } bool Media::canBeGrouped() const { return false; } QString Media::chatsListText() const { auto result = notificationText(); return result.isEmpty() ? QString() : textcmdLink(1, TextUtilities::Clean(std::move(result))); } bool Media::hasReplyPreview() const { return false; } ImagePtr Media::replyPreview() const { return ImagePtr(); } bool Media::allowsForward() const { return true; } bool Media::allowsEdit() const { return allowsEditCaption(); } bool Media::allowsEditCaption() const { return false; } bool Media::allowsRevoke() const { return true; } bool Media::forwardedBecomesUnread() const { return false; } QString Media::errorTextForForward(not_null<ChannelData*> channel) const { return QString(); } bool Media::consumeMessageText(const TextWithEntities &text) { return false; } TextWithEntities Media::consumedMessageText() const { return {}; } std::unique_ptr<HistoryMedia> Media::createView( not_null<HistoryView::Element*> message) { return createView(message, message->data()); } MediaPhoto::MediaPhoto( not_null<HistoryItem*> parent, not_null<PhotoData*> photo) : Media(parent) , _photo(photo) { Auth().data().registerPhotoItem(_photo, parent); } MediaPhoto::MediaPhoto( not_null<HistoryItem*> parent, not_null<PeerData*> chat, not_null<PhotoData*> photo) : Media(parent) , _photo(photo) , _chat(chat) { Auth().data().registerPhotoItem(_photo, parent); } MediaPhoto::~MediaPhoto() { Auth().data().unregisterPhotoItem(_photo, parent()); } std::unique_ptr<Media> MediaPhoto::clone(not_null<HistoryItem*> parent) { return _chat ? std::make_unique<MediaPhoto>(parent, _chat, _photo) : std::make_unique<MediaPhoto>(parent, _photo); } PhotoData *MediaPhoto::photo() const { return _photo; } bool MediaPhoto::uploading() const { return _photo->uploading(); } Storage::SharedMediaTypesMask MediaPhoto::sharedMediaTypes() const { using Type = Storage::SharedMediaType; if (_chat) { return Type::ChatPhoto; } return Storage::SharedMediaTypesMask{} .added(Type::Photo) .added(Type::PhotoVideo); } bool MediaPhoto::canBeGrouped() const { return true; } bool MediaPhoto::hasReplyPreview() const { return !_photo->thumb->isNull(); } ImagePtr MediaPhoto::replyPreview() const { return _photo->makeReplyPreview(); } QString MediaPhoto::notificationText() const { return WithCaptionNotificationText( lang(lng_in_dlg_photo), parent()->originalText().text); //return WithCaptionNotificationText(lang(lng_in_dlg_album), _caption); } QString MediaPhoto::chatsListText() const { return WithCaptionDialogsText( lang(lng_in_dlg_photo), parent()->originalText().text); //return WithCaptionDialogsText(lang(lng_in_dlg_album), _caption); } QString MediaPhoto::pinnedTextSubstring() const { return lang(lng_action_pinned_media_photo); } TextWithEntities MediaPhoto::clipboardText() const { return WithCaptionClipboardText( lang(lng_in_dlg_photo), parent()->clipboardText()); } bool MediaPhoto::allowsEditCaption() const { return true; } QString MediaPhoto::errorTextForForward( not_null<ChannelData*> channel) const { if (channel->restricted(ChannelRestriction::f_send_media)) { return lang(lng_restricted_send_media); } return QString(); } bool MediaPhoto::updateInlineResultMedia(const MTPMessageMedia &media) { if (media.type() != mtpc_messageMediaPhoto) { return false; } auto &photo = media.c_messageMediaPhoto(); if (photo.has_photo() && !photo.has_ttl_seconds()) { if (auto existing = Auth().data().photo(photo.vphoto)) { if (existing == _photo) { return true; } else { // collect data } } } else { LOG(("API Error: " "Got MTPMessageMediaPhoto without photo " "or with ttl_seconds in updateInlineResultMedia()")); } // Can return false if we collect the data. return true; } bool MediaPhoto::updateSentMedia(const MTPMessageMedia &media) { if (media.type() != mtpc_messageMediaPhoto) { return false; } auto &mediaPhoto = media.c_messageMediaPhoto(); if (!mediaPhoto.has_photo() || mediaPhoto.has_ttl_seconds()) { LOG(("Api Error: " "Got MTPMessageMediaPhoto without photo " "or with ttl_seconds in updateSentMedia()")); return false; } const auto &photo = mediaPhoto.vphoto; Auth().data().photoConvert(_photo, photo); if (photo.type() != mtpc_photo) { return false; } auto &sizes = photo.c_photo().vsizes.v; auto max = 0; const MTPDfileLocation *maxLocation = 0; for (const auto &data : sizes) { char size = 0; const MTPFileLocation *loc = 0; switch (data.type()) { case mtpc_photoSize: { const auto &s = data.c_photoSize().vtype.v; loc = &data.c_photoSize().vlocation; if (s.size()) size = s[0]; } break; case mtpc_photoCachedSize: { const auto &s = data.c_photoCachedSize().vtype.v; loc = &data.c_photoCachedSize().vlocation; if (s.size()) size = s[0]; } break; } if (!loc || loc->type() != mtpc_fileLocation) { continue; } if (size == 's') { Local::writeImage(storageKey(loc->c_fileLocation()), _photo->thumb); } else if (size == 'm') { Local::writeImage(storageKey(loc->c_fileLocation()), _photo->medium); } else if (size == 'x' && max < 1) { max = 1; maxLocation = &loc->c_fileLocation(); } else if (size == 'y' && max < 2) { max = 2; maxLocation = &loc->c_fileLocation(); //} else if (size == 'w' && max < 3) { // max = 3; // maxLocation = &loc->c_fileLocation(); } } if (maxLocation) { Local::writeImage(storageKey(*maxLocation), _photo->full); } return true; } std::unique_ptr<HistoryMedia> MediaPhoto::createView( not_null<HistoryView::Element*> message, not_null<HistoryItem*> realParent) { if (_chat) { return std::make_unique<HistoryPhoto>( message, _chat, _photo, st::msgServicePhotoWidth); } return std::make_unique<HistoryPhoto>( message, realParent, _photo); } MediaFile::MediaFile( not_null<HistoryItem*> parent, not_null<DocumentData*> document) : Media(parent) , _document(document) , _emoji(document->sticker() ? document->sticker()->alt : QString()) { Auth().data().registerDocumentItem(_document, parent); if (!_emoji.isEmpty()) { if (const auto emoji = Ui::Emoji::Find(_emoji)) { _emoji = emoji->text(); } } } MediaFile::~MediaFile() { Auth().data().unregisterDocumentItem(_document, parent()); } std::unique_ptr<Media> MediaFile::clone(not_null<HistoryItem*> parent) { return std::make_unique<MediaFile>(parent, _document); } DocumentData *MediaFile::document() const { return _document; } bool MediaFile::uploading() const { return _document->uploading(); } Storage::SharedMediaTypesMask MediaFile::sharedMediaTypes() const { using Type = Storage::SharedMediaType; if (_document->sticker()) { return {}; } else if (_document->isVideoMessage()) { return Storage::SharedMediaTypesMask{} .added(Type::RoundFile) .added(Type::RoundVoiceFile); } else if (_document->isGifv()) { return Type::GIF; } else if (_document->isVideoFile()) { return Storage::SharedMediaTypesMask{} .added(Type::Video) .added(Type::PhotoVideo); } else if (_document->isVoiceMessage()) { return Storage::SharedMediaTypesMask{} .added(Type::VoiceFile) .added(Type::RoundVoiceFile); } else if (_document->isSharedMediaMusic()) { return Type::MusicFile; } return Type::File; } bool MediaFile::canBeGrouped() const { return _document->isVideoFile(); } bool MediaFile::hasReplyPreview() const { return !_document->thumb->isNull(); } ImagePtr MediaFile::replyPreview() const { return _document->makeReplyPreview(); } QString MediaFile::chatsListText() const { if (const auto sticker = _document->sticker()) { return Media::chatsListText(); } const auto type = [&] { if (_document->isVideoMessage()) { return lang(lng_in_dlg_video_message); } else if (_document->isAnimation()) { return qsl("GIF"); } else if (_document->isVideoFile()) { return lang(lng_in_dlg_video); } else if (_document->isVoiceMessage()) { return lang(lng_in_dlg_audio); } else if (const auto name = _document->composeNameString(); !name.isEmpty()) { return name; } else if (_document->isAudioFile()) { return lang(lng_in_dlg_audio_file); } return lang(lng_in_dlg_file); }(); return WithCaptionDialogsText(type, parent()->originalText().text); } QString MediaFile::notificationText() const { if (const auto sticker = _document->sticker()) { return _emoji.isEmpty() ? lang(lng_in_dlg_sticker) : lng_in_dlg_sticker_emoji(lt_emoji, _emoji); } const auto type = [&] { if (_document->isVideoMessage()) { return lang(lng_in_dlg_video_message); } else if (_document->isAnimation()) { return qsl("GIF"); } else if (_document->isVideoFile()) { return lang(lng_in_dlg_video); } else if (_document->isVoiceMessage()) { return lang(lng_in_dlg_audio); } else if (!_document->filename().isEmpty()) { return _document->filename(); } else if (_document->isAudioFile()) { return lang(lng_in_dlg_audio_file); } return lang(lng_in_dlg_file); }(); return WithCaptionNotificationText(type, parent()->originalText().text); } QString MediaFile::pinnedTextSubstring() const { if (const auto sticker = _document->sticker()) { if (!_emoji.isEmpty()) { return lng_action_pinned_media_emoji_sticker(lt_emoji, _emoji); } return lang(lng_action_pinned_media_sticker); } else if (_document->isAnimation()) { if (_document->isVideoMessage()) { return lang(lng_action_pinned_media_video_message); } return lang(lng_action_pinned_media_gif); } else if (_document->isVideoFile()) { return lang(lng_action_pinned_media_video); } else if (_document->isVoiceMessage()) { return lang(lng_action_pinned_media_voice); } else if (_document->isSong()) { return lang(lng_action_pinned_media_audio); } return lang(lng_action_pinned_media_file); } TextWithEntities MediaFile::clipboardText() const { const auto attachType = [&] { const auto name = _document->composeNameString(); const auto addName = !name.isEmpty() ? qstr(" : ") + name : QString(); if (const auto sticker = _document->sticker()) { if (!_emoji.isEmpty()) { return lng_in_dlg_sticker_emoji(lt_emoji, _emoji); } return lang(lng_in_dlg_sticker); } else if (_document->isAnimation()) { if (_document->isVideoMessage()) { return lang(lng_in_dlg_video_message); } return qsl("GIF"); } else if (_document->isVideoFile()) { return lang(lng_in_dlg_video); } else if (_document->isVoiceMessage()) { return lang(lng_in_dlg_audio) + addName; } else if (_document->isSong()) { return lang(lng_in_dlg_audio_file) + addName; } return lang(lng_in_dlg_file) + addName; }(); return WithCaptionClipboardText( attachType, parent()->clipboardText()); } bool MediaFile::allowsEditCaption() const { return !_document->isVideoMessage() && !_document->sticker(); } bool MediaFile::forwardedBecomesUnread() const { return _document->isVoiceMessage() //|| _document->isVideoFile() || _document->isVideoMessage(); } QString MediaFile::errorTextForForward( not_null<ChannelData*> channel) const { if (const auto sticker = _document->sticker()) { if (channel->restricted(ChannelRestriction::f_send_stickers)) { return lang(lng_restricted_send_stickers); } } else if (_document->isAnimation()) { if (_document->isVideoMessage()) { if (channel->restricted(ChannelRestriction::f_send_media)) { return lang(lng_restricted_send_media); } } else { if (channel->restricted(ChannelRestriction::f_send_gifs)) { return lang(lng_restricted_send_gifs); } } } else if (channel->restricted(ChannelRestriction::f_send_media)) { return lang(lng_restricted_send_media); } return QString(); } bool MediaFile::updateInlineResultMedia(const MTPMessageMedia &media) { if (media.type() != mtpc_messageMediaDocument) { return false; } auto &data = media.c_messageMediaDocument(); if (data.has_document() && !data.has_ttl_seconds()) { const auto document = Auth().data().document(data.vdocument); if (document == _document) { return false; } else { document->collectLocalData(_document); } } else { LOG(("API Error: " "Got MTPMessageMediaDocument without document " "or with ttl_seconds in updateInlineResultMedia()")); } return false; } bool MediaFile::updateSentMedia(const MTPMessageMedia &media) { if (media.type() != mtpc_messageMediaDocument) { return false; } auto &data = media.c_messageMediaDocument(); if (!data.has_document() || data.has_ttl_seconds()) { LOG(("Api Error: " "Got MTPMessageMediaDocument without document " "or with ttl_seconds in updateSentMedia()")); return false; } Auth().data().documentConvert(_document, data.vdocument); if (!_document->data().isEmpty()) { if (_document->isVoiceMessage()) { Local::writeAudio(_document->mediaKey(), _document->data()); } else { Local::writeStickerImage(_document->mediaKey(), _document->data()); } } return true; } std::unique_ptr<HistoryMedia> MediaFile::createView( not_null<HistoryView::Element*> message, not_null<HistoryItem*> realParent) { if (_document->sticker()) { return std::make_unique<HistorySticker>(message, _document); } else if (_document->isAnimation()) { return std::make_unique<HistoryGif>(message, _document); } else if (_document->isVideoFile()) { return std::make_unique<HistoryVideo>( message, realParent, _document); } return std::make_unique<HistoryDocument>(message, _document); } MediaContact::MediaContact( not_null<HistoryItem*> parent, UserId userId, const QString &firstName, const QString &lastName, const QString &phoneNumber) : Media(parent) { Auth().data().registerContactItem(userId, parent); _contact.userId = userId; _contact.firstName = firstName; _contact.lastName = lastName; _contact.phoneNumber = phoneNumber; } MediaContact::~MediaContact() { Auth().data().unregisterContactItem(_contact.userId, parent()); } std::unique_ptr<Media> MediaContact::clone(not_null<HistoryItem*> parent) { return std::make_unique<MediaContact>( parent, _contact.userId, _contact.firstName, _contact.lastName, _contact.phoneNumber); } const SharedContact *MediaContact::sharedContact() const { return &_contact; } QString MediaContact::notificationText() const { return lang(lng_in_dlg_contact); } QString MediaContact::pinnedTextSubstring() const { return lang(lng_action_pinned_media_contact); } TextWithEntities MediaContact::clipboardText() const { const auto text = qsl("[ ") + lang(lng_in_dlg_contact) + qsl(" ]\n") + lng_full_name( lt_first_name, _contact.firstName, lt_last_name, _contact.lastName).trimmed() + '\n' + _contact.phoneNumber; return { text, EntitiesInText() }; } bool MediaContact::updateInlineResultMedia(const MTPMessageMedia &media) { return false; } bool MediaContact::updateSentMedia(const MTPMessageMedia &media) { if (media.type() != mtpc_messageMediaContact) { return false; } if (_contact.userId != media.c_messageMediaContact().vuser_id.v) { Auth().data().unregisterContactItem(_contact.userId, parent()); _contact.userId = media.c_messageMediaContact().vuser_id.v; Auth().data().registerContactItem(_contact.userId, parent()); } return true; } std::unique_ptr<HistoryMedia> MediaContact::createView( not_null<HistoryView::Element*> message, not_null<HistoryItem*> realParent) { return std::make_unique<HistoryContact>( message, _contact.userId, _contact.firstName, _contact.lastName, _contact.phoneNumber); } MediaLocation::MediaLocation( not_null<HistoryItem*> parent, const LocationCoords &coords) : MediaLocation(parent, coords, QString(), QString()) { } MediaLocation::MediaLocation( not_null<HistoryItem*> parent, const LocationCoords &coords, const QString &title, const QString &description) : Media(parent) , _location(App::location(coords)) , _title(title) , _description(description) { } std::unique_ptr<Media> MediaLocation::clone(not_null<HistoryItem*> parent) { return std::make_unique<MediaLocation>( parent, _location->coords, _title, _description); } LocationData *MediaLocation::location() const { return _location; } QString MediaLocation::chatsListText() const { return WithCaptionDialogsText(lang(lng_maps_point), _title); } QString MediaLocation::notificationText() const { return WithCaptionNotificationText(lang(lng_maps_point), _title); } QString MediaLocation::pinnedTextSubstring() const { return lang(lng_action_pinned_media_location); } TextWithEntities MediaLocation::clipboardText() const { TextWithEntities result = { qsl("[ ") + lang(lng_maps_point) + qsl(" ]\n"), EntitiesInText() }; auto titleResult = TextUtilities::ParseEntities( TextUtilities::Clean(_title), Ui::WebpageTextTitleOptions().flags); auto descriptionResult = TextUtilities::ParseEntities( TextUtilities::Clean(_description), TextParseLinks | TextParseMultiline | TextParseRichText); if (!titleResult.text.isEmpty()) { TextUtilities::Append(result, std::move(titleResult)); result.text.append('\n'); } if (!descriptionResult.text.isEmpty()) { TextUtilities::Append(result, std::move(descriptionResult)); result.text.append('\n'); } result.text += LocationClickHandler(_location->coords).dragText(); return result; } bool MediaLocation::updateInlineResultMedia(const MTPMessageMedia &media) { return false; } bool MediaLocation::updateSentMedia(const MTPMessageMedia &media) { return false; } std::unique_ptr<HistoryMedia> MediaLocation::createView( not_null<HistoryView::Element*> message, not_null<HistoryItem*> realParent) { return std::make_unique<HistoryLocation>( message, _location, _title, _description); } MediaCall::MediaCall( not_null<HistoryItem*> parent, const MTPDmessageActionPhoneCall &call) : Media(parent) , _call(ComputeCallData(call)) { } std::unique_ptr<Media> MediaCall::clone(not_null<HistoryItem*> parent) { Unexpected("Clone of call media."); } const Call *MediaCall::call() const { return &_call; } QString MediaCall::notificationText() const { auto result = Text(parent(), _call.finishReason); if (_call.duration > 0) { result = lng_call_type_and_duration( lt_type, result, lt_duration, formatDurationWords(_call.duration)); } return result; } QString MediaCall::pinnedTextSubstring() const { return QString(); } TextWithEntities MediaCall::clipboardText() const { return { qsl("[ ") + notificationText() + qsl(" ]"), EntitiesInText() }; } bool MediaCall::allowsForward() const { return false; } bool MediaCall::allowsRevoke() const { return false; } bool MediaCall::updateInlineResultMedia(const MTPMessageMedia &media) { return false; } bool MediaCall::updateSentMedia(const MTPMessageMedia &media) { return false; } std::unique_ptr<HistoryMedia> MediaCall::createView( not_null<HistoryView::Element*> message, not_null<HistoryItem*> realParent) { return std::make_unique<HistoryCall>(message, &_call); } QString MediaCall::Text( not_null<HistoryItem*> item, CallFinishReason reason) { if (item->out()) { return lang(reason == CallFinishReason::Missed ? lng_call_cancelled : lng_call_outgoing); } else if (reason == CallFinishReason::Missed) { return lang(lng_call_missed); } else if (reason == CallFinishReason::Busy) { return lang(lng_call_declined); } return lang(lng_call_incoming); } MediaWebPage::MediaWebPage( not_null<HistoryItem*> parent, not_null<WebPageData*> page) : Media(parent) , _page(page) { Auth().data().registerWebPageItem(_page, parent); } MediaWebPage::~MediaWebPage() { Auth().data().unregisterWebPageItem(_page, parent()); } std::unique_ptr<Media> MediaWebPage::clone(not_null<HistoryItem*> parent) { return std::make_unique<MediaWebPage>(parent, _page); } DocumentData *MediaWebPage::document() const { return _page->document; } PhotoData *MediaWebPage::photo() const { return _page->photo; } WebPageData *MediaWebPage::webpage() const { return _page; } bool MediaWebPage::hasReplyPreview() const { if (const auto document = _page->document) { return !document->thumb->isNull(); } else if (const auto photo = _page->photo) { return !photo->thumb->isNull(); } return false; } ImagePtr MediaWebPage::replyPreview() const { if (const auto document = _page->document) { return document->makeReplyPreview(); } else if (const auto photo = _page->photo) { return photo->makeReplyPreview(); } return ImagePtr(); } QString MediaWebPage::chatsListText() const { return notificationText(); } QString MediaWebPage::notificationText() const { return parent()->originalText().text; } QString MediaWebPage::pinnedTextSubstring() const { return QString(); } TextWithEntities MediaWebPage::clipboardText() const { return TextWithEntities(); } bool MediaWebPage::allowsEdit() const { return true; } bool MediaWebPage::updateInlineResultMedia(const MTPMessageMedia &media) { return false; } bool MediaWebPage::updateSentMedia(const MTPMessageMedia &media) { return false; } std::unique_ptr<HistoryMedia> MediaWebPage::createView( not_null<HistoryView::Element*> message, not_null<HistoryItem*> realParent) { return std::make_unique<HistoryWebPage>(message, _page); } MediaGame::MediaGame( not_null<HistoryItem*> parent, not_null<GameData*> game) : Media(parent) , _game(game) { } std::unique_ptr<Media> MediaGame::clone(not_null<HistoryItem*> parent) { return std::make_unique<MediaGame>(parent, _game); } bool MediaGame::hasReplyPreview() const { if (const auto document = _game->document) { return !document->thumb->isNull(); } else if (const auto photo = _game->photo) { return !photo->thumb->isNull(); } return false; } ImagePtr MediaGame::replyPreview() const { if (const auto document = _game->document) { return document->makeReplyPreview(); } else if (const auto photo = _game->photo) { return photo->makeReplyPreview(); } return ImagePtr(); } QString MediaGame::notificationText() const { // Add a game controller emoji before game title. auto result = QString(); result.reserve(_game->title.size() + 3); result.append( QChar(0xD83C) ).append( QChar(0xDFAE) ).append( QChar(' ') ).append(_game->title); return result; } GameData *MediaGame::game() const { return _game; } QString MediaGame::pinnedTextSubstring() const { auto title = _game->title; return lng_action_pinned_media_game(lt_game, title); } TextWithEntities MediaGame::clipboardText() const { return TextWithEntities(); } QString MediaGame::errorTextForForward( not_null<ChannelData*> channel) const { if (channel->restricted(ChannelRestriction::f_send_games)) { return lang(lng_restricted_send_inline); } return QString(); } bool MediaGame::consumeMessageText(const TextWithEntities &text) { _consumedText = text; return true; } TextWithEntities MediaGame::consumedMessageText() const { return _consumedText; } bool MediaGame::updateInlineResultMedia(const MTPMessageMedia &media) { return updateSentMedia(media); } bool MediaGame::updateSentMedia(const MTPMessageMedia &media) { if (media.type() != mtpc_messageMediaGame) { return false; } Auth().data().gameConvert(_game, media.c_messageMediaGame().vgame); return true; } std::unique_ptr<HistoryMedia> MediaGame::createView( not_null<HistoryView::Element*> message, not_null<HistoryItem*> realParent) { return std::make_unique<HistoryGame>(message, _game, _consumedText); } MediaInvoice::MediaInvoice( not_null<HistoryItem*> parent, const MTPDmessageMediaInvoice &data) : Media(parent) , _invoice(ComputeInvoiceData(data)) { } MediaInvoice::MediaInvoice( not_null<HistoryItem*> parent, const Invoice &data) : Media(parent) , _invoice(data) { } std::unique_ptr<Media> MediaInvoice::clone(not_null<HistoryItem*> parent) { return std::make_unique<MediaInvoice>(parent, _invoice); } const Invoice *MediaInvoice::invoice() const { return &_invoice; } bool MediaInvoice::hasReplyPreview() const { if (const auto photo = _invoice.photo) { return !photo->thumb->isNull(); } return false; } ImagePtr MediaInvoice::replyPreview() const { if (const auto photo = _invoice.photo) { return photo->makeReplyPreview(); } return ImagePtr(); } QString MediaInvoice::notificationText() const { return _invoice.title; } QString MediaInvoice::pinnedTextSubstring() const { return QString(); } TextWithEntities MediaInvoice::clipboardText() const { return TextWithEntities(); } bool MediaInvoice::updateInlineResultMedia(const MTPMessageMedia &media) { return true; } bool MediaInvoice::updateSentMedia(const MTPMessageMedia &media) { return true; } std::unique_ptr<HistoryMedia> MediaInvoice::createView( not_null<HistoryView::Element*> message, not_null<HistoryItem*> realParent) { return std::make_unique<HistoryInvoice>(message, &_invoice); } } // namespace Data ����������������tdesktop-1.2.17/Telegram/SourceFiles/data/data_media_types.h����������������������������������������0000664�0000000�0000000�00000025411�13262451251�0024004�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once class HistoryItem; class HistoryMedia; namespace base { template <typename Enum> class enum_mask; } // namespace base namespace Storage { enum class SharedMediaType : signed char; using SharedMediaTypesMask = base::enum_mask<SharedMediaType>; } // namespace Storage namespace HistoryView { enum class Context : char; class Element; } // namespace HistoryView namespace Data { enum class CallFinishReason : char { Missed, Busy, Disconnected, Hangup, }; struct SharedContact { UserId userId = 0; QString firstName; QString lastName; QString phoneNumber; }; struct Call { using FinishReason = CallFinishReason; int duration = 0; FinishReason finishReason = FinishReason::Missed; }; struct Invoice { MsgId receiptMsgId = 0; uint64 amount = 0; QString currency; QString title; QString description; PhotoData *photo = nullptr; bool isTest = false; }; class Media { public: Media(not_null<HistoryItem*> parent); virtual ~Media() = default; not_null<HistoryItem*> parent() const; virtual std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) = 0; virtual DocumentData *document() const; virtual PhotoData *photo() const; virtual WebPageData *webpage() const; virtual const SharedContact *sharedContact() const; virtual const Call *call() const; virtual GameData *game() const; virtual const Invoice *invoice() const; virtual LocationData *location() const; virtual bool uploading() const; virtual Storage::SharedMediaTypesMask sharedMediaTypes() const; virtual bool canBeGrouped() const; virtual bool hasReplyPreview() const; virtual ImagePtr replyPreview() const; // Returns text with link-start and link-end commands for service-color highlighting. // Example: "[link1-start]You:[link1-end] [link1-start]Photo,[link1-end] caption text" virtual QString chatsListText() const; virtual QString notificationText() const = 0; virtual QString pinnedTextSubstring() const = 0; virtual TextWithEntities clipboardText() const = 0; virtual bool allowsForward() const; virtual bool allowsEdit() const; virtual bool allowsEditCaption() const; virtual bool allowsRevoke() const; virtual bool forwardedBecomesUnread() const; virtual QString errorTextForForward( not_null<ChannelData*> channel) const; [[nodiscard]] virtual bool consumeMessageText( const TextWithEntities &text); [[nodiscard]] virtual TextWithEntities consumedMessageText() const; // After sending an inline result we may want to completely recreate // the media (all media that was generated on client side, for example). virtual bool updateInlineResultMedia(const MTPMessageMedia &media) = 0; virtual bool updateSentMedia(const MTPMessageMedia &media) = 0; virtual std::unique_ptr<HistoryMedia> createView( not_null<HistoryView::Element*> message, not_null<HistoryItem*> realParent) = 0; std::unique_ptr<HistoryMedia> createView( not_null<HistoryView::Element*> message); private: const not_null<HistoryItem*> _parent; }; class MediaPhoto : public Media { public: MediaPhoto( not_null<HistoryItem*> parent, not_null<PhotoData*> photo); MediaPhoto( not_null<HistoryItem*> parent, not_null<PeerData*> chat, not_null<PhotoData*> photo); ~MediaPhoto(); std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override; PhotoData *photo() const override; bool uploading() const override; Storage::SharedMediaTypesMask sharedMediaTypes() const override; bool canBeGrouped() const override; bool hasReplyPreview() const override; ImagePtr replyPreview() const override; QString chatsListText() const override; QString notificationText() const override; QString pinnedTextSubstring() const override; TextWithEntities clipboardText() const override; bool allowsEditCaption() const override; QString errorTextForForward( not_null<ChannelData*> channel) const override; bool updateInlineResultMedia(const MTPMessageMedia &media) override; bool updateSentMedia(const MTPMessageMedia &media) override; std::unique_ptr<HistoryMedia> createView( not_null<HistoryView::Element*> message, not_null<HistoryItem*> realParent) override; private: not_null<PhotoData*> _photo; PeerData *_chat = nullptr; }; class MediaFile : public Media { public: MediaFile( not_null<HistoryItem*> parent, not_null<DocumentData*> document); ~MediaFile(); std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override; DocumentData *document() const override; bool uploading() const override; Storage::SharedMediaTypesMask sharedMediaTypes() const override; bool canBeGrouped() const override; bool hasReplyPreview() const override; ImagePtr replyPreview() const override; QString chatsListText() const override; QString notificationText() const override; QString pinnedTextSubstring() const override; TextWithEntities clipboardText() const override; bool allowsEditCaption() const override; bool forwardedBecomesUnread() const override; QString errorTextForForward( not_null<ChannelData*> channel) const override; bool updateInlineResultMedia(const MTPMessageMedia &media) override; bool updateSentMedia(const MTPMessageMedia &media) override; std::unique_ptr<HistoryMedia> createView( not_null<HistoryView::Element*> message, not_null<HistoryItem*> realParent) override; private: not_null<DocumentData*> _document; QString _emoji; }; class MediaContact : public Media { public: MediaContact( not_null<HistoryItem*> parent, UserId userId, const QString &firstName, const QString &lastName, const QString &phoneNumber); ~MediaContact(); std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override; const SharedContact *sharedContact() const override; QString notificationText() const override; QString pinnedTextSubstring() const override; TextWithEntities clipboardText() const override; bool updateInlineResultMedia(const MTPMessageMedia &media) override; bool updateSentMedia(const MTPMessageMedia &media) override; std::unique_ptr<HistoryMedia> createView( not_null<HistoryView::Element*> message, not_null<HistoryItem*> realParent) override; private: SharedContact _contact; }; class MediaLocation : public Media { public: MediaLocation( not_null<HistoryItem*> parent, const LocationCoords &coords); MediaLocation( not_null<HistoryItem*> parent, const LocationCoords &coords, const QString &title, const QString &description); std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override; LocationData *location() const override; QString chatsListText() const override; QString notificationText() const override; QString pinnedTextSubstring() const override; TextWithEntities clipboardText() const override; bool updateInlineResultMedia(const MTPMessageMedia &media) override; bool updateSentMedia(const MTPMessageMedia &media) override; std::unique_ptr<HistoryMedia> createView( not_null<HistoryView::Element*> message, not_null<HistoryItem*> realParent) override; private: not_null<LocationData*> _location; QString _title; QString _description; }; class MediaCall : public Media { public: MediaCall( not_null<HistoryItem*> parent, const MTPDmessageActionPhoneCall &call); std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override; const Call *call() const override; QString notificationText() const override; QString pinnedTextSubstring() const override; TextWithEntities clipboardText() const override; bool allowsForward() const override; bool allowsRevoke() const override; bool updateInlineResultMedia(const MTPMessageMedia &media) override; bool updateSentMedia(const MTPMessageMedia &media) override; std::unique_ptr<HistoryMedia> createView( not_null<HistoryView::Element*> message, not_null<HistoryItem*> realParent) override; static QString Text( not_null<HistoryItem*> item, CallFinishReason reason); private: Call _call; }; class MediaWebPage : public Media { public: MediaWebPage( not_null<HistoryItem*> parent, not_null<WebPageData*> page); ~MediaWebPage(); std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override; DocumentData *document() const override; PhotoData *photo() const override; WebPageData *webpage() const override; bool hasReplyPreview() const override; ImagePtr replyPreview() const override; QString chatsListText() const override; QString notificationText() const override; QString pinnedTextSubstring() const override; TextWithEntities clipboardText() const override; bool allowsEdit() const override; bool updateInlineResultMedia(const MTPMessageMedia &media) override; bool updateSentMedia(const MTPMessageMedia &media) override; std::unique_ptr<HistoryMedia> createView( not_null<HistoryView::Element*> message, not_null<HistoryItem*> realParent) override; private: not_null<WebPageData*> _page; }; class MediaGame : public Media { public: MediaGame( not_null<HistoryItem*> parent, not_null<GameData*> game); std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override; GameData *game() const override; bool hasReplyPreview() const override; ImagePtr replyPreview() const override; QString notificationText() const override; QString pinnedTextSubstring() const override; TextWithEntities clipboardText() const override; QString errorTextForForward( not_null<ChannelData*> channel) const override; bool consumeMessageText(const TextWithEntities &text) override; TextWithEntities consumedMessageText() const override; bool updateInlineResultMedia(const MTPMessageMedia &media) override; bool updateSentMedia(const MTPMessageMedia &media) override; std::unique_ptr<HistoryMedia> createView( not_null<HistoryView::Element*> message, not_null<HistoryItem*> realParent) override; private: not_null<GameData*> _game; TextWithEntities _consumedText; }; class MediaInvoice : public Media { public: MediaInvoice( not_null<HistoryItem*> parent, const MTPDmessageMediaInvoice &data); MediaInvoice( not_null<HistoryItem*> parent, const Invoice &data); std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override; const Invoice *invoice() const override; bool hasReplyPreview() const override; ImagePtr replyPreview() const override; QString notificationText() const override; QString pinnedTextSubstring() const override; TextWithEntities clipboardText() const override; bool updateInlineResultMedia(const MTPMessageMedia &media) override; bool updateSentMedia(const MTPMessageMedia &media) override; std::unique_ptr<HistoryMedia> createView( not_null<HistoryView::Element*> message, not_null<HistoryItem*> realParent) override; private: Invoice _invoice; }; TextWithEntities WithCaptionClipboardText( const QString &attachType, TextWithEntities &&caption); } // namespace Data �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/data/data_messages.cpp�����������������������������������������0000664�0000000�0000000�00000031506�13262451251�0023645�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "data/data_feed_messages.h" namespace Data { MessagesList::Slice::Slice( base::flat_set<MessagePosition> &&messages, MessagesRange range) : messages(std::move(messages)) , range(range) { } template <typename Range> void MessagesList::Slice::merge( const Range &moreMessages, MessagesRange moreNoSkipRange) { Expects(moreNoSkipRange.from <= range.till); Expects(range.from <= moreNoSkipRange.till); messages.merge(std::begin(moreMessages), std::end(moreMessages)); range = { qMin(range.from, moreNoSkipRange.from), qMax(range.till, moreNoSkipRange.till) }; } template <typename Range> int MessagesList::uniteAndAdd( MessagesSliceUpdate &update, base::flat_set<Slice>::iterator uniteFrom, base::flat_set<Slice>::iterator uniteTill, const Range &messages, MessagesRange noSkipRange) { auto uniteFromIndex = uniteFrom - _slices.begin(); auto was = uniteFrom->messages.size(); _slices.modify(uniteFrom, [&](Slice &slice) { slice.merge(messages, noSkipRange); }); auto firstToErase = uniteFrom + 1; if (firstToErase != uniteTill) { for (auto it = firstToErase; it != uniteTill; ++it) { _slices.modify(uniteFrom, [&](Slice &slice) { slice.merge(it->messages, it->range); }); } _slices.erase(firstToErase, uniteTill); uniteFrom = _slices.begin() + uniteFromIndex; } update.messages = &uniteFrom->messages; update.range = uniteFrom->range; return uniteFrom->messages.size() - was; } template <typename Range> int MessagesList::addRangeItemsAndCountNew( MessagesSliceUpdate &update, const Range &messages, MessagesRange noSkipRange) { Expects(noSkipRange.from <= noSkipRange.till); auto uniteFrom = ranges::lower_bound( _slices, noSkipRange.from, std::less<>(), [](const Slice &slice) { return slice.range.till; }); auto uniteTill = ranges::upper_bound( _slices, noSkipRange.till, std::less<>(), [](const Slice &slice) { return slice.range.from; }); if (uniteFrom < uniteTill) { return uniteAndAdd(update, uniteFrom, uniteTill, messages, noSkipRange); } auto sliceMessages = base::flat_set<MessagePosition> { std::begin(messages), std::end(messages) }; auto slice = _slices.emplace( std::move(sliceMessages), noSkipRange); update.messages = &slice->messages; update.range = slice->range; return slice->messages.size(); } template <typename Range> void MessagesList::addRange( const Range &messages, MessagesRange noSkipRange, base::optional<int> count, bool incrementCount) { Expects(!count || !incrementCount); auto wasCount = _count; auto update = MessagesSliceUpdate(); auto result = addRangeItemsAndCountNew( update, messages, noSkipRange); if (count) { _count = count; } else if (incrementCount && _count && result > 0) { *_count += result; } if (_slices.size() == 1) { if (_slices.front().range == FullMessagesRange) { _count = _slices.front().messages.size(); } } update.count = _count; _sliceUpdated.fire(std::move(update)); } void MessagesList::addNew(MessagePosition messageId) { auto range = { messageId }; addRange(range, { messageId, MaxMessagePosition }, base::none, true); } void MessagesList::addSlice( std::vector<MessagePosition> &&messageIds, MessagesRange noSkipRange, base::optional<int> count) { addRange(messageIds, noSkipRange, count); } void MessagesList::removeOne(MessagePosition messageId) { auto slice = ranges::lower_bound( _slices, messageId, std::less<>(), [](const Slice &slice) { return slice.range.till; }); if (slice != _slices.end() && slice->range.from <= messageId) { _slices.modify(slice, [&](Slice &slice) { return slice.messages.remove(messageId); }); } if (_count) { --*_count; } } void MessagesList::removeAll(ChannelId channelId) { auto removed = 0; for (auto i = begin(_slices); i != end(_slices); ++i) { _slices.modify(i, [&](Slice &slice) { auto &messages = slice.messages; for (auto j = begin(messages); j != end(messages);) { if (j->fullId.channel == channelId) { j = messages.erase(j); ++removed; } else { ++j; } } }); } if (removed && _count) { *_count -= removed; } } void MessagesList::invalidate() { _slices.clear(); _count = base::none; } void MessagesList::invalidateBottom() { if (!_slices.empty()) { const auto &last = _slices.back(); if (last.range.till == MaxMessagePosition) { _slices.modify(_slices.end() - 1, [](Slice &slice) { slice.range.till = slice.messages.empty() ? slice.range.from : slice.messages.back(); }); } } _count = base::none; } rpl::producer<MessagesResult> MessagesList::query( MessagesQuery &&query) const { return [this, query = std::move(query)](auto consumer) { auto slice = query.aroundId ? ranges::lower_bound( _slices, query.aroundId, std::less<>(), [](const Slice &slice) { return slice.range.till; }) : _slices.end(); if (slice != _slices.end() && slice->range.from <= query.aroundId) { consumer.put_next(queryFromSlice(query, *slice)); } consumer.put_done(); return rpl::lifetime(); }; } rpl::producer<MessagesSliceUpdate> MessagesList::sliceUpdated() const { return _sliceUpdated.events(); } MessagesResult MessagesList::queryFromSlice( const MessagesQuery &query, const Slice &slice) const { auto result = MessagesResult {}; auto position = ranges::lower_bound(slice.messages, query.aroundId); auto haveBefore = int(position - begin(slice.messages)); auto haveEqualOrAfter = int(end(slice.messages) - position); auto before = qMin(haveBefore, query.limitBefore); auto equalOrAfter = qMin(haveEqualOrAfter, query.limitAfter + 1); auto ids = std::vector<MessagePosition>(position - before, position + equalOrAfter); result.messageIds.merge(ids.begin(), ids.end()); if (slice.range.from == MinMessagePosition) { result.skippedBefore = haveBefore - before; } if (slice.range.till == MaxMessagePosition) { result.skippedAfter = haveEqualOrAfter - equalOrAfter; } if (_count) { result.count = _count; if (!result.skippedBefore && result.skippedAfter) { result.skippedBefore = *result.count - *result.skippedAfter - int(result.messageIds.size()); } else if (!result.skippedAfter && result.skippedBefore) { result.skippedAfter = *result.count - *result.skippedBefore - int(result.messageIds.size()); } } return result; } MessagesSliceBuilder::MessagesSliceBuilder( Key key, int limitBefore, int limitAfter) : _key(key) , _limitBefore(limitBefore) , _limitAfter(limitAfter) { } bool MessagesSliceBuilder::applyInitial(const MessagesResult &result) { mergeSliceData( result.count, result.messageIds, result.skippedBefore, result.skippedAfter); return true; } bool MessagesSliceBuilder::applyUpdate(const MessagesSliceUpdate &update) { auto intersects = [](MessagesRange range1, MessagesRange range2) { return (range1.from <= range2.till) && (range2.from <= range1.till); }; auto needMergeMessages = (update.messages != nullptr) && intersects(update.range, { _ids.empty() ? _key : _ids.front(), _ids.empty() ? _key : _ids.back() }); if (!needMergeMessages && !update.count) { return false; } auto skippedBefore = (update.range.from == MinMessagePosition) ? 0 : base::optional<int> {}; auto skippedAfter = (update.range.till == MaxMessagePosition) ? 0 : base::optional<int> {}; mergeSliceData( update.count, needMergeMessages ? *update.messages : base::flat_set<MessagePosition> {}, skippedBefore, skippedAfter); return true; } bool MessagesSliceBuilder::removeOne(MessagePosition messageId) { auto changed = false; if (_fullCount && *_fullCount > 0) { --*_fullCount; changed = true; } if (_ids.contains(messageId)) { _ids.remove(messageId); changed = true; } else if (!_ids.empty()) { if (_ids.front() > messageId && _skippedBefore && *_skippedBefore > 0) { --*_skippedBefore; changed = true; } else if (_ids.back() < messageId && _skippedAfter && *_skippedAfter > 0) { --*_skippedAfter; changed = true; } } return changed; } bool MessagesSliceBuilder::removeAll() { _ids = {}; _range = FullMessagesRange; _fullCount = 0; _skippedBefore = 0; _skippedAfter = 0; return true; } bool MessagesSliceBuilder::removeFromChannel(ChannelId channelId) { for (auto i = _ids.begin(); i != _ids.end();) { if ((*i).fullId.channel == channelId) { i = _ids.erase(i); if (_fullCount) { --*_fullCount; } } else { ++i; } } _skippedBefore = _skippedAfter = base::none; checkInsufficient(); return true; } bool MessagesSliceBuilder::invalidated() { _fullCount = _skippedBefore = _skippedAfter = base::none; _ids.clear(); checkInsufficient(); return false; } bool MessagesSliceBuilder::bottomInvalidated() { _fullCount = _skippedAfter = base::none; checkInsufficient(); return true; } void MessagesSliceBuilder::checkInsufficient() { sliceToLimits(); } void MessagesSliceBuilder::mergeSliceData( base::optional<int> count, const base::flat_set<MessagePosition> &messageIds, base::optional<int> skippedBefore, base::optional<int> skippedAfter) { if (messageIds.empty()) { if (count && _fullCount != count) { _fullCount = count; if (*_fullCount <= _ids.size()) { _fullCount = _ids.size(); _skippedBefore = _skippedAfter = 0; } } fillSkippedAndSliceToLimits(); return; } if (count) { _fullCount = count; } const auto impossible = MessagePosition(-1, FullMsgId()); auto wasMinId = _ids.empty() ? impossible : _ids.front(); auto wasMaxId = _ids.empty() ? impossible : _ids.back(); _ids.merge(messageIds.begin(), messageIds.end()); auto adjustSkippedBefore = [&](MessagePosition oldId, int oldSkippedBefore) { auto it = _ids.find(oldId); Assert(it != _ids.end()); _skippedBefore = oldSkippedBefore - (it - _ids.begin()); accumulate_max(*_skippedBefore, 0); }; if (skippedBefore) { adjustSkippedBefore(messageIds.front(), *skippedBefore); } else if (wasMinId != impossible && _skippedBefore) { adjustSkippedBefore(wasMinId, *_skippedBefore); } else { _skippedBefore = base::none; } auto adjustSkippedAfter = [&](MessagePosition oldId, int oldSkippedAfter) { auto it = _ids.find(oldId); Assert(it != _ids.end()); _skippedAfter = oldSkippedAfter - (_ids.end() - it - 1); accumulate_max(*_skippedAfter, 0); }; if (skippedAfter) { adjustSkippedAfter(messageIds.back(), *skippedAfter); } else if (wasMaxId != impossible && _skippedAfter) { adjustSkippedAfter(wasMaxId, *_skippedAfter); } else { _skippedAfter = base::none; } fillSkippedAndSliceToLimits(); } void MessagesSliceBuilder::fillSkippedAndSliceToLimits() { if (_fullCount) { if (_skippedBefore && !_skippedAfter) { _skippedAfter = *_fullCount - *_skippedBefore - int(_ids.size()); } else if (_skippedAfter && !_skippedBefore) { _skippedBefore = *_fullCount - *_skippedAfter - int(_ids.size()); } } sliceToLimits(); } void MessagesSliceBuilder::sliceToLimits() { if (!_key) { if (!_fullCount) { requestMessagesCount(); } return; } auto requestedSomething = false; auto aroundIt = ranges::lower_bound(_ids, _key); auto removeFromBegin = (aroundIt - _ids.begin() - _limitBefore); auto removeFromEnd = (_ids.end() - aroundIt - _limitAfter - 1); if (removeFromBegin > 0) { _ids.erase(_ids.begin(), _ids.begin() + removeFromBegin); if (_skippedBefore) { *_skippedBefore += removeFromBegin; } } else if (removeFromBegin < 0 && (!_skippedBefore || *_skippedBefore > 0)) { requestedSomething = true; requestMessages(RequestDirection::Before); } if (removeFromEnd > 0) { _ids.erase(_ids.end() - removeFromEnd, _ids.end()); if (_skippedAfter) { *_skippedAfter += removeFromEnd; } } else if (removeFromEnd < 0 && (!_skippedAfter || *_skippedAfter > 0)) { requestedSomething = true; requestMessages(RequestDirection::After); } if (!_fullCount && !requestedSomething) { requestMessagesCount(); } } void MessagesSliceBuilder::requestMessages(RequestDirection direction) { auto requestAroundData = [&]() -> AroundData { if (_ids.empty()) { return { _key, Data::LoadDirection::Around }; } else if (direction == RequestDirection::Before) { return { _ids.front(), Data::LoadDirection::Before }; } return { _ids.back(), Data::LoadDirection::After }; }; _insufficientAround.fire(requestAroundData()); } void MessagesSliceBuilder::requestMessagesCount() { _insufficientAround.fire({ MessagePosition(), Data::LoadDirection::Around }); } MessagesSlice MessagesSliceBuilder::snapshot() const { auto result = MessagesSlice(); result.ids.reserve(_ids.size()); for (const auto &position : _ids) { result.ids.push_back(position.fullId); } result.skippedBefore = _skippedBefore; result.skippedAfter = _skippedAfter; result.fullCount = _fullCount; return result; } } // namespace Data ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/data/data_messages.h�������������������������������������������0000664�0000000�0000000�00000013777�13262451251�0023324�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace Data { enum class LoadDirection : char { Around, Before, After, }; struct MessagePosition { constexpr MessagePosition() = default; constexpr MessagePosition(TimeId date, FullMsgId fullId) : fullId(fullId) , date(date) { } explicit operator bool() const { return (fullId.msg != 0); } inline constexpr bool operator<(const MessagePosition &other) const { if (date < other.date) { return true; } else if (other.date < date) { return false; } return (fullId < other.fullId); } inline constexpr bool operator>(const MessagePosition &other) const { return other < *this; } inline constexpr bool operator<=(const MessagePosition &other) const { return !(other < *this); } inline constexpr bool operator>=(const MessagePosition &other) const { return !(*this < other); } inline constexpr bool operator==(const MessagePosition &other) const { return (date == other.date) && (fullId == other.fullId); } inline constexpr bool operator!=(const MessagePosition &other) const { return !(*this == other); } FullMsgId fullId; TimeId date = 0; }; struct MessagesRange { constexpr MessagesRange() = default; constexpr MessagesRange(MessagePosition from, MessagePosition till) : from(from) , till(till) { } inline constexpr bool operator==(const MessagesRange &other) const { return (from == other.from) && (till == other.till); } inline constexpr bool operator!=(const MessagesRange &other) const { return !(*this == other); } MessagePosition from; MessagePosition till; }; constexpr auto MinDate = TimeId(0); constexpr auto MaxDate = std::numeric_limits<TimeId>::max(); constexpr auto MinMessagePosition = MessagePosition( MinDate, FullMsgId(NoChannel, 1)); constexpr auto MaxMessagePosition = MessagePosition( MaxDate, FullMsgId(NoChannel, ServerMaxMsgId - 1)); constexpr auto FullMessagesRange = MessagesRange( MinMessagePosition, MaxMessagePosition); constexpr auto UnreadMessagePosition = MessagePosition( MinDate, FullMsgId(NoChannel, ShowAtUnreadMsgId)); struct MessagesSlice { std::vector<FullMsgId> ids; base::optional<int> skippedBefore; base::optional<int> skippedAfter; base::optional<int> fullCount; }; struct MessagesQuery { MessagesQuery( MessagePosition aroundId, int limitBefore, int limitAfter) : aroundId(aroundId) , limitBefore(limitBefore) , limitAfter(limitAfter) { } MessagePosition aroundId; int limitBefore = 0; int limitAfter = 0; }; struct MessagesResult { base::optional<int> count; base::optional<int> skippedBefore; base::optional<int> skippedAfter; base::flat_set<MessagePosition> messageIds; }; struct MessagesSliceUpdate { const base::flat_set<MessagePosition> *messages = nullptr; MessagesRange range; base::optional<int> count; }; class MessagesList { public: void addNew(MessagePosition messageId); void addSlice( std::vector<MessagePosition> &&messageIds, MessagesRange noSkipRange, base::optional<int> count); void removeOne(MessagePosition messageId); void removeAll(ChannelId channelId); void invalidate(); void invalidateBottom(); rpl::producer<MessagesResult> query(MessagesQuery &&query) const; rpl::producer<MessagesSliceUpdate> sliceUpdated() const; private: struct Slice { Slice( base::flat_set<MessagePosition> &&messages, MessagesRange range); template <typename Range> void merge( const Range &moreMessages, MessagesRange moreNoSkipRange); base::flat_set<MessagePosition> messages; MessagesRange range; inline bool operator<(const Slice &other) const { return range.from < other.range.from; } }; template <typename Range> int uniteAndAdd( MessagesSliceUpdate &update, base::flat_set<Slice>::iterator uniteFrom, base::flat_set<Slice>::iterator uniteTill, const Range &messages, MessagesRange noSkipRange); template <typename Range> int addRangeItemsAndCountNew( MessagesSliceUpdate &update, const Range &messages, MessagesRange noSkipRange); template <typename Range> void addRange( const Range &messages, MessagesRange noSkipRange, base::optional<int> count, bool incrementCount = false); MessagesResult queryFromSlice( const MessagesQuery &query, const Slice &slice) const; base::optional<int> _count; base::flat_set<Slice> _slices; rpl::event_stream<MessagesSliceUpdate> _sliceUpdated; }; class MessagesSliceBuilder { public: using Key = MessagePosition; MessagesSliceBuilder(Key key, int limitBefore, int limitAfter); bool applyInitial(const MessagesResult &result); bool applyUpdate(const MessagesSliceUpdate &update); bool removeOne(MessagePosition messageId); bool removeFromChannel(ChannelId channelId); bool removeAll(); bool invalidated(); bool bottomInvalidated(); void checkInsufficient(); struct AroundData { MessagePosition aroundId; LoadDirection direction = LoadDirection::Around; inline bool operator<(const AroundData &other) const { return (aroundId < other.aroundId) || ((aroundId == other.aroundId) && (direction < other.direction)); } }; auto insufficientAround() const { return _insufficientAround.events(); } MessagesSlice snapshot() const; private: enum class RequestDirection { Before, After, }; void requestMessages(RequestDirection direction); void requestMessagesCount(); void fillSkippedAndSliceToLimits(); void sliceToLimits(); void mergeSliceData( base::optional<int> count, const base::flat_set<MessagePosition> &messageIds, base::optional<int> skippedBefore = base::none, base::optional<int> skippedAfter = base::none); MessagePosition _key; base::flat_set<MessagePosition> _ids; MessagesRange _range; base::optional<int> _fullCount; base::optional<int> _skippedBefore; base::optional<int> _skippedAfter; int _limitBefore = 0; int _limitAfter = 0; rpl::event_stream<AroundData> _insufficientAround; }; } // namespace Data �tdesktop-1.2.17/Telegram/SourceFiles/data/data_notify_settings.cpp����������������������������������0000664�0000000�0000000�00000011404�13262451251�0025261�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "data/data_notify_settings.h" namespace Data { namespace { MTPinputPeerNotifySettings DefaultSettings() { const auto flags = MTPDpeerNotifySettings::Flag::f_show_previews; const auto muteValue = TimeId(0); return MTP_inputPeerNotifySettings( MTP_flags(mtpCastFlags(flags)), MTP_int(muteValue), MTP_string("default")); } } // namespace class NotifySettingsValue { public: NotifySettingsValue(const MTPDpeerNotifySettings &data); using MuteChange = NotifySettings::MuteChange; using SilentPostsChange = NotifySettings::SilentPostsChange; bool change(const MTPDpeerNotifySettings &data); bool change( MuteChange mute, SilentPostsChange silent, int muteForSeconds); TimeMs muteFinishesIn() const; bool silentPosts() const; MTPinputPeerNotifySettings serialize() const; private: bool change( MTPDpeerNotifySettings::Flags flags, TimeId mute, QString sound); MTPDpeerNotifySettings::Flags _flags; TimeId _mute; QString _sound; }; NotifySettingsValue::NotifySettingsValue(const MTPDpeerNotifySettings &data) : _flags(data.vflags.v) , _mute(data.vmute_until.v) , _sound(qs(data.vsound)) { } bool NotifySettingsValue::silentPosts() const { return _flags & MTPDpeerNotifySettings::Flag::f_silent; } bool NotifySettingsValue::change(const MTPDpeerNotifySettings &data) { return change(data.vflags.v, data.vmute_until.v, qs(data.vsound)); } bool NotifySettingsValue::change( MuteChange mute, SilentPostsChange silent, int muteForSeconds) { const auto newFlags = [&] { auto result = _flags; if (silent == SilentPostsChange::Silent) { result |= MTPDpeerNotifySettings::Flag::f_silent; } else if (silent == SilentPostsChange::Notify) { result &= ~MTPDpeerNotifySettings::Flag::f_silent; } return result; }(); const auto newMute = (mute == MuteChange::Mute) ? (unixtime() + muteForSeconds) : (mute == MuteChange::Ignore) ? _mute : 0; const auto newSound = (newMute == 0 && _sound.isEmpty()) ? qsl("default") : _sound; return change(newFlags, newMute, newSound); } bool NotifySettingsValue::change( MTPDpeerNotifySettings::Flags flags, TimeId mute, QString sound) { if (_flags == flags && _mute == mute && _sound == sound) { return false; } _flags = flags; _mute = mute; _sound = sound; return true; } TimeMs NotifySettingsValue::muteFinishesIn() const { auto now = unixtime(); if (_mute > now) { return (_mute - now + 1) * 1000LL; } return 0; } MTPinputPeerNotifySettings NotifySettingsValue::serialize() const { return MTP_inputPeerNotifySettings( MTP_flags(mtpCastFlags(_flags)), MTP_int(_mute), MTP_string(_sound)); } bool NotifySettings::change(const MTPPeerNotifySettings &settings) { switch (settings.type()) { case mtpc_peerNotifySettingsEmpty: { if (!_known || _value) { _known = true; _value = nullptr; return true; } return false; } break; case mtpc_peerNotifySettings: { auto &data = settings.c_peerNotifySettings(); if (_value) { return _value->change(data); } _known = true; _value = std::make_unique<NotifySettingsValue>(data); return true; } break; } Unexpected("Type in NotifySettings::change()"); } NotifySettings::NotifySettings() = default; bool NotifySettings::change( MuteChange mute, SilentPostsChange silent, int muteForSeconds) { Expects(mute != MuteChange::Mute || muteForSeconds > 0); if (mute == MuteChange::Ignore && silent == SilentPostsChange::Ignore) { return false; } if (_value) { return _value->change(mute, silent, muteForSeconds); } const auto asEmpty = [&] { if (mute == MuteChange::Mute) { return false; } if (silent == SilentPostsChange::Silent) { return false; } return true; }(); if (asEmpty) { return change(MTP_peerNotifySettingsEmpty()); } const auto flags = MTPDpeerNotifySettings::Flag::f_show_previews | ((silent == SilentPostsChange::Silent) ? MTPDpeerNotifySettings::Flag::f_silent : MTPDpeerNotifySettings::Flag(0)); const auto muteUntil = (mute == MuteChange::Mute) ? (unixtime() + muteForSeconds) : 0; return change(MTP_peerNotifySettings( MTP_flags(flags), MTP_int(muteUntil), MTP_string("default"))); } TimeMs NotifySettings::muteFinishesIn() const { return _value ? _value->muteFinishesIn() : 0LL; } bool NotifySettings::settingsUnknown() const { return !_known; } bool NotifySettings::silentPosts() const { return _value ? _value->silentPosts() : false; } MTPinputPeerNotifySettings NotifySettings::serialize() const { return _value ? _value->serialize() : DefaultSettings(); } NotifySettings::~NotifySettings() = default; } // namespace Data ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/data/data_notify_settings.h������������������������������������0000664�0000000�0000000�00000001551�13262451251�0024730�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace Data { class NotifySettingsValue; class NotifySettings { public: NotifySettings(); enum class MuteChange { Ignore, Mute, Unmute, }; enum class SilentPostsChange { Ignore, Silent, Notify, }; bool change(const MTPPeerNotifySettings &settings); bool change( MuteChange mute, SilentPostsChange silent, int muteForSeconds); TimeMs muteFinishesIn() const; bool settingsUnknown() const; bool silentPosts() const; MTPinputPeerNotifySettings serialize() const; ~NotifySettings(); private: bool _known = false; std::unique_ptr<NotifySettingsValue> _value; }; } // namespace Data �������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/data/data_peer.cpp���������������������������������������������0000664�0000000�0000000�00000103041�13262451251�0022763�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "data/data_peer.h" #include <rpl/filter.h> #include <rpl/map.h> #include "data/data_peer_values.h" #include "data/data_channel_admins.h" #include "data/data_photo.h" #include "data/data_feed.h" #include "data/data_session.h" #include "history/history.h" #include "lang/lang_keys.h" #include "observer_peer.h" #include "mainwidget.h" #include "apiwrap.h" #include "boxes/confirm_box.h" #include "styles/style_history.h" #include "auth_session.h" #include "messenger.h" #include "mainwindow.h" #include "window/window_controller.h" #include "storage/localstorage.h" #include "ui/empty_userpic.h" #include "ui/text_options.h" namespace { constexpr auto kUpdateFullPeerTimeout = TimeMs(5000); // Not more than once in 5 seconds. constexpr auto kUserpicSize = 160; } // namespace namespace Data { int PeerColorIndex(int32 bareId) { const auto index = std::abs(bareId) % 7; const int map[] = { 0, 7, 4, 1, 6, 3, 5 }; return map[index]; } int PeerColorIndex(PeerId peerId) { return PeerColorIndex(peerToBareInt(peerId)); } style::color PeerUserpicColor(PeerId peerId) { const style::color colors[] = { st::historyPeer1UserpicBg, st::historyPeer2UserpicBg, st::historyPeer3UserpicBg, st::historyPeer4UserpicBg, st::historyPeer5UserpicBg, st::historyPeer6UserpicBg, st::historyPeer7UserpicBg, st::historyPeer8UserpicBg, }; return colors[PeerColorIndex(peerId)]; } } // namespace Data using UpdateFlag = Notify::PeerUpdate::Flag; PeerClickHandler::PeerClickHandler(not_null<PeerData*> peer) : _peer(peer) { } void PeerClickHandler::onClick(Qt::MouseButton button) const { if (button == Qt::LeftButton && App::wnd()) { auto controller = App::wnd()->controller(); if (_peer && _peer->isChannel() && controller->activeChatCurrent().peer() != _peer) { if (!_peer->asChannel()->isPublic() && !_peer->asChannel()->amIn()) { Ui::show(Box<InformBox>(lang(_peer->isMegagroup() ? lng_group_not_accessible : lng_channel_not_accessible))); } else { controller->showPeerHistory( _peer, Window::SectionShow::Way::Forward); } } else { Ui::showPeerProfile(_peer); } } } PeerData::PeerData(const PeerId &id) : id(id) , _userpicEmpty(createEmptyUserpic()) { nameText.setText(st::msgNameStyle, QString(), Ui::NameTextOptions()); } void PeerData::updateNameDelayed( const QString &newName, const QString &newNameOrPhone, const QString &newUsername) { if (name == newName) { if (isUser()) { if (asUser()->nameOrPhone == newNameOrPhone && asUser()->username == newUsername) { return; } } else if (isChannel()) { if (asChannel()->username == newUsername) { return; } } else if (isChat()) { return; } } ++nameVersion; name = newName; nameText.setText(st::msgNameStyle, name, Ui::NameTextOptions()); refreshEmptyUserpic(); Notify::PeerUpdate update(this); update.flags |= UpdateFlag::NameChanged; update.oldNameFirstLetters = nameFirstLetters(); if (isUser()) { if (asUser()->username != newUsername) { asUser()->username = newUsername; update.flags |= UpdateFlag::UsernameChanged; } asUser()->setNameOrPhone(newNameOrPhone); } else if (isChannel()) { if (asChannel()->username != newUsername) { asChannel()->username = newUsername; if (newUsername.isEmpty()) { asChannel()->removeFlags( MTPDchannel::Flag::f_username); } else { asChannel()->addFlags(MTPDchannel::Flag::f_username); } update.flags |= UpdateFlag::UsernameChanged; } } fillNames(); Notify::PeerUpdated().notify(update, true); } std::unique_ptr<Ui::EmptyUserpic> PeerData::createEmptyUserpic() const { return std::make_unique<Ui::EmptyUserpic>( Data::PeerUserpicColor(id), name); } void PeerData::refreshEmptyUserpic() const { _userpicEmpty = useEmptyUserpic() ? createEmptyUserpic() : nullptr; } ClickHandlerPtr PeerData::createOpenLink() { return std::make_shared<PeerClickHandler>(this); } void PeerData::setUserpic( PhotoId photoId, const StorageImageLocation &location, ImagePtr userpic) { _userpicPhotoId = photoId; _userpic = userpic; _userpicLocation = location; refreshEmptyUserpic(); } void PeerData::setUserpicPhoto(const MTPPhoto &data) { auto photoId = [&]() -> PhotoId { if (const auto photo = Auth().data().photo(data)) { photo->peer = this; return photo->id; } return 0; }(); if (_userpicPhotoId != photoId) { _userpicPhotoId = photoId; Notify::peerUpdatedDelayed(this, UpdateFlag::PhotoChanged); } } ImagePtr PeerData::currentUserpic() const { if (_userpic) { _userpic->load(); if (_userpic->loaded()) { if (!useEmptyUserpic()) { _userpicEmpty = nullptr; } return _userpic; } } return ImagePtr(); } void PeerData::paintUserpic(Painter &p, int x, int y, int size) const { if (auto userpic = currentUserpic()) { p.drawPixmap(x, y, userpic->pixCircled(size, size)); } else { _userpicEmpty->paint(p, x, y, x + size + x, size); } } void PeerData::paintUserpicRounded(Painter &p, int x, int y, int size) const { if (auto userpic = currentUserpic()) { p.drawPixmap(x, y, userpic->pixRounded(size, size, ImageRoundRadius::Small)); } else { _userpicEmpty->paintRounded(p, x, y, x + size + x, size); } } void PeerData::paintUserpicSquare(Painter &p, int x, int y, int size) const { if (auto userpic = currentUserpic()) { p.drawPixmap(x, y, userpic->pix(size, size)); } else { _userpicEmpty->paintSquare(p, x, y, x + size + x, size); } } StorageKey PeerData::userpicUniqueKey() const { if (useEmptyUserpic()) { return _userpicEmpty->uniqueKey(); } return storageKey(_userpicLocation); } void PeerData::saveUserpic(const QString &path, int size) const { genUserpic(size).save(path, "PNG"); } void PeerData::saveUserpicRounded(const QString &path, int size) const { genUserpicRounded(size).save(path, "PNG"); } QPixmap PeerData::genUserpic(int size) const { if (auto userpic = currentUserpic()) { return userpic->pixCircled(size, size); } auto result = QImage(QSize(size, size) * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); result.setDevicePixelRatio(cRetinaFactor()); result.fill(Qt::transparent); { Painter p(&result); paintUserpic(p, 0, 0, size); } return App::pixmapFromImageInPlace(std::move(result)); } QPixmap PeerData::genUserpicRounded(int size) const { if (auto userpic = currentUserpic()) { return userpic->pixRounded(size, size, ImageRoundRadius::Small); } auto result = QImage(QSize(size, size) * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); result.setDevicePixelRatio(cRetinaFactor()); result.fill(Qt::transparent); { Painter p(&result); paintUserpicRounded(p, 0, 0, size); } return App::pixmapFromImageInPlace(std::move(result)); } void PeerData::updateUserpic( PhotoId photoId, const MTPFileLocation &location) { const auto size = kUserpicSize; const auto loc = StorageImageLocation::FromMTP(size, size, location); const auto photo = loc.isNull() ? ImagePtr() : ImagePtr(loc); setUserpicChecked(photoId, loc, photo); } void PeerData::clearUserpic() { const auto photoId = PhotoId(0); const auto loc = StorageImageLocation(); const auto photo = [&] { if (id == peerFromUser(ServiceUserId)) { auto image = Messenger::Instance().logoNoMargin().scaledToWidth( kUserpicSize, Qt::SmoothTransformation); auto pixmap = App::pixmapFromImageInPlace(std::move(image)); return _userpic ? _userpic : ImagePtr(std::move(pixmap), "PNG"); } return ImagePtr(); }(); setUserpicChecked(photoId, loc, photo); } void PeerData::setUserpicChecked( PhotoId photoId, const StorageImageLocation &location, ImagePtr userpic) { if (_userpicPhotoId != photoId || _userpic.v() != userpic.v() || _userpicLocation != location) { setUserpic(photoId, location, userpic); Notify::peerUpdatedDelayed(this, UpdateFlag::PhotoChanged); if (const auto channel = asChannel()) { if (const auto feed = channel->feed()) { Auth().data().notifyFeedUpdated( feed, Data::FeedUpdateFlag::ChannelPhoto); } } } } void PeerData::fillNames() { _nameWords.clear(); _nameFirstLetters.clear(); auto toIndexList = QStringList(); auto appendToIndex = [&](const QString &value) { if (!value.isEmpty()) { toIndexList.push_back(TextUtilities::RemoveAccents(value)); } }; appendToIndex(name); const auto appendTranslit = !toIndexList.isEmpty() && cRussianLetters().match(toIndexList.front()).hasMatch(); if (appendTranslit) { appendToIndex(translitRusEng(toIndexList.front())); } if (const auto user = asUser()) { if (user->nameOrPhone != name) { appendToIndex(user->nameOrPhone); } appendToIndex(user->username); if (isSelf()) { appendToIndex(lang(lng_saved_messages)); } } else if (const auto channel = asChannel()) { appendToIndex(channel->username); } auto toIndex = toIndexList.join(' '); toIndex += ' ' + rusKeyboardLayoutSwitch(toIndex); const auto namesList = TextUtilities::PrepareSearchWords(toIndex); for (const auto &name : namesList) { _nameWords.insert(name); _nameFirstLetters.insert(name[0]); } } PeerData::~PeerData() = default; const Text &BotCommand::descriptionText() const { if (_descriptionText.isEmpty() && !_description.isEmpty()) { _descriptionText.setText( st::defaultTextStyle, _description, Ui::NameTextOptions()); } return _descriptionText; } bool UserData::canShareThisContact() const { return canShareThisContactFast() || !Auth().data().findContactPhone(peerToUser(id)).isEmpty(); } void UserData::setContactStatus(ContactStatus status) { if (_contactStatus != status) { const auto changed = (_contactStatus == ContactStatus::Contact) != (status == ContactStatus::Contact); _contactStatus = status; if (changed) { Notify::peerUpdatedDelayed( this, Notify::PeerUpdate::Flag::UserIsContact); } } if (_contactStatus == ContactStatus::Contact && cReportSpamStatuses().value(id, dbiprsHidden) != dbiprsHidden) { cRefReportSpamStatuses().insert(id, dbiprsHidden); Local::writeReportSpamStatuses(); } } // see Local::readPeer as well void UserData::setPhoto(const MTPUserProfilePhoto &photo) { if (photo.type() == mtpc_userProfilePhoto) { const auto &data = photo.c_userProfilePhoto(); updateUserpic(data.vphoto_id.v, data.vphoto_small); } else { clearUserpic(); } } bool UserData::setAbout(const QString &newAbout) { if (_about == newAbout) { return false; } _about = newAbout; Notify::peerUpdatedDelayed(this, UpdateFlag::AboutChanged); return true; } void UserData::setRestrictionReason(const QString &text) { if (_restrictionReason != text) { _restrictionReason = text; Notify::peerUpdatedDelayed(this, Notify::PeerUpdate::Flag::RestrictionReasonChanged); } } void UserData::setCommonChatsCount(int count) { if (_commonChatsCount != count) { _commonChatsCount = count; Notify::peerUpdatedDelayed(this, UpdateFlag::UserCommonChatsChanged); } } void UserData::setName(const QString &newFirstName, const QString &newLastName, const QString &newPhoneName, const QString &newUsername) { bool changeName = !newFirstName.isEmpty() || !newLastName.isEmpty(); QString newFullName; if (changeName && newFirstName.trimmed().isEmpty()) { firstName = newLastName; lastName = QString(); newFullName = firstName; } else { if (changeName) { firstName = newFirstName; lastName = newLastName; } newFullName = lastName.isEmpty() ? firstName : lng_full_name(lt_first_name, firstName, lt_last_name, lastName); } updateNameDelayed(newFullName, newPhoneName, newUsername); } void UserData::setPhone(const QString &newPhone) { _phone = newPhone; } void UserData::setBotInfoVersion(int version) { if (version < 0) { if (botInfo) { if (!botInfo->commands.isEmpty()) { botInfo->commands.clear(); Notify::botCommandsChanged(this); } botInfo = nullptr; Notify::userIsBotChanged(this); } } else if (!botInfo) { botInfo = std::make_unique<BotInfo>(); botInfo->version = version; Notify::userIsBotChanged(this); } else if (botInfo->version < version) { if (!botInfo->commands.isEmpty()) { botInfo->commands.clear(); Notify::botCommandsChanged(this); } botInfo->description.clear(); botInfo->version = version; botInfo->inited = false; } } void UserData::setBotInfo(const MTPBotInfo &info) { switch (info.type()) { case mtpc_botInfo: { const auto &d(info.c_botInfo()); if (peerFromUser(d.vuser_id.v) != id || !botInfo) return; QString desc = qs(d.vdescription); if (botInfo->description != desc) { botInfo->description = desc; botInfo->text = Text(st::msgMinWidth); } auto &v = d.vcommands.v; botInfo->commands.reserve(v.size()); auto changedCommands = false; int32 j = 0; for (int32 i = 0, l = v.size(); i < l; ++i) { if (v.at(i).type() != mtpc_botCommand) continue; QString cmd = qs(v.at(i).c_botCommand().vcommand), desc = qs(v.at(i).c_botCommand().vdescription); if (botInfo->commands.size() <= j) { botInfo->commands.push_back(BotCommand(cmd, desc)); changedCommands = true; } else { if (botInfo->commands[j].command != cmd) { botInfo->commands[j].command = cmd; changedCommands = true; } if (botInfo->commands[j].setDescription(desc)) { changedCommands = true; } } ++j; } while (j < botInfo->commands.size()) { botInfo->commands.pop_back(); changedCommands = true; } botInfo->inited = true; if (changedCommands) { Notify::botCommandsChanged(this); } } break; } } void UserData::setNameOrPhone(const QString &newNameOrPhone) { if (nameOrPhone != newNameOrPhone) { nameOrPhone = newNameOrPhone; phoneText.setText( st::msgNameStyle, nameOrPhone, Ui::NameTextOptions()); } } void UserData::madeAction(TimeId when) { if (botInfo || isServiceUser(id) || when <= 0) return; if (onlineTill <= 0 && -onlineTill < when) { onlineTill = -when - SetOnlineAfterActivity; Notify::peerUpdatedDelayed(this, Notify::PeerUpdate::Flag::UserOnlineChanged); } else if (onlineTill > 0 && onlineTill < when + 1) { onlineTill = when + SetOnlineAfterActivity; Notify::peerUpdatedDelayed(this, Notify::PeerUpdate::Flag::UserOnlineChanged); } } void UserData::setAccessHash(uint64 accessHash) { if (accessHash == kInaccessibleAccessHashOld) { _accessHash = 0; // _flags.add(MTPDuser_ClientFlag::f_inaccessible | 0); _flags.add(MTPDuser::Flag::f_deleted); } else { _accessHash = accessHash; } } void UserData::setBlockStatus(BlockStatus blockStatus) { if (blockStatus != _blockStatus) { _blockStatus = blockStatus; Notify::peerUpdatedDelayed(this, UpdateFlag::UserIsBlocked); } } void UserData::setCallsStatus(CallsStatus callsStatus) { if (callsStatus != _callsStatus) { _callsStatus = callsStatus; Notify::peerUpdatedDelayed(this, UpdateFlag::UserHasCalls); } } bool UserData::hasCalls() const { return (callsStatus() != CallsStatus::Disabled) && (callsStatus() != CallsStatus::Unknown); } void ChatData::setPhoto(const MTPChatPhoto &photo) { setPhoto(userpicPhotoId(), photo); } void ChatData::setPhoto(PhotoId photoId, const MTPChatPhoto &photo) { if (photo.type() == mtpc_chatPhoto) { const auto &data = photo.c_chatPhoto(); updateUserpic(photoId, data.vphoto_small); } else { clearUserpic(); } } void ChatData::setName(const QString &newName) { updateNameDelayed(newName.isEmpty() ? name : newName, QString(), QString()); } void ChatData::invalidateParticipants() { auto wasCanEdit = canEdit(); participants.clear(); admins.clear(); removeFlags(MTPDchat::Flag::f_admin); invitedByMe.clear(); botStatus = 0; if (wasCanEdit != canEdit()) { Notify::peerUpdatedDelayed(this, Notify::PeerUpdate::Flag::ChatCanEdit); } Notify::peerUpdatedDelayed(this, Notify::PeerUpdate::Flag::MembersChanged | Notify::PeerUpdate::Flag::AdminsChanged); } void ChatData::setInviteLink(const QString &newInviteLink) { if (newInviteLink != _inviteLink) { _inviteLink = newInviteLink; Notify::peerUpdatedDelayed(this, UpdateFlag::InviteLinkChanged); } } ChannelData::ChannelData(const PeerId &id) : PeerData(id) , inputChannel(MTP_inputChannel(MTP_int(bareId()), MTP_long(0))) { Data::PeerFlagValue( this, MTPDchannel::Flag::f_megagroup ) | rpl::start_with_next([this](bool megagroup) { if (megagroup) { if (!mgInfo) { mgInfo = std::make_unique<MegagroupInfo>(); } } else if (mgInfo) { mgInfo = nullptr; } }, _lifetime); } void ChannelData::setPhoto(const MTPChatPhoto &photo) { setPhoto(userpicPhotoId(), photo); } void ChannelData::setPhoto(PhotoId photoId, const MTPChatPhoto &photo) { if (photo.type() == mtpc_chatPhoto) { const auto &data = photo.c_chatPhoto(); updateUserpic(photoId, data.vphoto_small); } else { clearUserpic(); } } void ChannelData::setName(const QString &newName, const QString &newUsername) { updateNameDelayed(newName.isEmpty() ? name : newName, QString(), newUsername); } void PeerData::updateFull() { if (!_lastFullUpdate || getms(true) > _lastFullUpdate + kUpdateFullPeerTimeout) { updateFullForced(); } } void PeerData::updateFullForced() { Auth().api().requestFullPeer(this); if (auto channel = asChannel()) { if (!channel->amCreator() && !channel->inviter) { Auth().api().requestSelfParticipant(channel); } } } void PeerData::fullUpdated() { _lastFullUpdate = getms(true); } bool ChannelData::setAbout(const QString &newAbout) { if (_about == newAbout) { return false; } _about = newAbout; Notify::peerUpdatedDelayed(this, UpdateFlag::AboutChanged); return true; } void ChannelData::setInviteLink(const QString &newInviteLink) { if (newInviteLink != _inviteLink) { _inviteLink = newInviteLink; Notify::peerUpdatedDelayed(this, UpdateFlag::InviteLinkChanged); } } void ChannelData::setMembersCount(int newMembersCount) { if (_membersCount != newMembersCount) { if (isMegagroup() && !mgInfo->lastParticipants.empty()) { mgInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsCountOutdated; mgInfo->lastParticipantsCount = membersCount(); } _membersCount = newMembersCount; Notify::peerUpdatedDelayed(this, Notify::PeerUpdate::Flag::MembersChanged); } } void ChannelData::setAdminsCount(int newAdminsCount) { if (_adminsCount != newAdminsCount) { _adminsCount = newAdminsCount; Notify::peerUpdatedDelayed(this, Notify::PeerUpdate::Flag::AdminsChanged); } } void ChannelData::setRestrictedCount(int newRestrictedCount) { if (_restrictedCount != newRestrictedCount) { _restrictedCount = newRestrictedCount; Notify::peerUpdatedDelayed(this, Notify::PeerUpdate::Flag::BannedUsersChanged); } } void ChannelData::setKickedCount(int newKickedCount) { if (_kickedCount != newKickedCount) { _kickedCount = newKickedCount; Notify::peerUpdatedDelayed(this, Notify::PeerUpdate::Flag::BannedUsersChanged); } } MTPChannelBannedRights ChannelData::KickedRestrictedRights() { using Flag = MTPDchannelBannedRights::Flag; auto flags = Flag::f_view_messages | Flag::f_send_messages | Flag::f_send_media | Flag::f_embed_links | Flag::f_send_stickers | Flag::f_send_gifs | Flag::f_send_games | Flag::f_send_inline; return MTP_channelBannedRights(MTP_flags(flags), MTP_int(std::numeric_limits<int32>::max())); } void ChannelData::applyEditAdmin(not_null<UserData*> user, const MTPChannelAdminRights &oldRights, const MTPChannelAdminRights &newRights) { auto flags = Notify::PeerUpdate::Flag::AdminsChanged | Notify::PeerUpdate::Flag::None; if (mgInfo) { // If rights are empty - still add participant? TODO check if (!base::contains(mgInfo->lastParticipants, user)) { mgInfo->lastParticipants.push_front(user); setMembersCount(membersCount() + 1); if (user->botInfo && !mgInfo->bots.contains(user)) { mgInfo->bots.insert(user); if (mgInfo->botStatus != 0 && mgInfo->botStatus < 2) { mgInfo->botStatus = 2; } } } // If rights are empty - still remove restrictions? TODO check if (mgInfo->lastRestricted.contains(user)) { mgInfo->lastRestricted.remove(user); if (restrictedCount() > 0) { setRestrictedCount(restrictedCount() - 1); } } auto userId = peerToUser(user->id); auto it = mgInfo->lastAdmins.find(user); if (newRights.c_channelAdminRights().vflags.v != 0) { auto lastAdmin = MegagroupInfo::Admin { newRights }; lastAdmin.canEdit = true; if (it == mgInfo->lastAdmins.cend()) { mgInfo->lastAdmins.emplace(user, lastAdmin); setAdminsCount(adminsCount() + 1); } else { it->second = lastAdmin; } Data::ChannelAdminChanges(this).feed(userId, true); } else { if (it != mgInfo->lastAdmins.cend()) { mgInfo->lastAdmins.erase(it); if (adminsCount() > 0) { setAdminsCount(adminsCount() - 1); } } Data::ChannelAdminChanges(this).feed(userId, false); } } if (oldRights.c_channelAdminRights().vflags.v && !newRights.c_channelAdminRights().vflags.v) { // We removed an admin. if (adminsCount() > 1) { setAdminsCount(adminsCount() - 1); } if (!isMegagroup() && user->botInfo && membersCount() > 1) { // Removing bot admin removes it from channel. setMembersCount(membersCount() - 1); } } else if (!oldRights.c_channelAdminRights().vflags.v && newRights.c_channelAdminRights().vflags.v) { // We added an admin. setAdminsCount(adminsCount() + 1); updateFullForced(); } Notify::peerUpdatedDelayed(this, flags); } void ChannelData::applyEditBanned(not_null<UserData*> user, const MTPChannelBannedRights &oldRights, const MTPChannelBannedRights &newRights) { auto flags = Notify::PeerUpdate::Flag::BannedUsersChanged | Notify::PeerUpdate::Flag::None; auto isKicked = (newRights.c_channelBannedRights().vflags.v & MTPDchannelBannedRights::Flag::f_view_messages); auto isRestricted = !isKicked && (newRights.c_channelBannedRights().vflags.v != 0); if (mgInfo) { // If rights are empty - still remove admin? TODO check if (mgInfo->lastAdmins.contains(user)) { mgInfo->lastAdmins.remove(user); if (adminsCount() > 1) { setAdminsCount(adminsCount() - 1); } else { flags |= Notify::PeerUpdate::Flag::AdminsChanged; } } auto it = mgInfo->lastRestricted.find(user); if (isRestricted) { if (it == mgInfo->lastRestricted.cend()) { mgInfo->lastRestricted.emplace(user, MegagroupInfo::Restricted { newRights }); setRestrictedCount(restrictedCount() + 1); } else { it->second.rights = newRights; } } else { if (it != mgInfo->lastRestricted.cend()) { mgInfo->lastRestricted.erase(it); if (restrictedCount() > 0) { setRestrictedCount(restrictedCount() - 1); } } if (isKicked) { auto i = ranges::find(mgInfo->lastParticipants, user); if (i != mgInfo->lastParticipants.end()) { mgInfo->lastParticipants.erase(i); } if (membersCount() > 1) { setMembersCount(membersCount() - 1); } else { mgInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsCountOutdated; mgInfo->lastParticipantsCount = 0; } setKickedCount(kickedCount() + 1); if (mgInfo->bots.contains(user)) { mgInfo->bots.remove(user); if (mgInfo->bots.empty() && mgInfo->botStatus > 0) { mgInfo->botStatus = -1; } } flags |= Notify::PeerUpdate::Flag::MembersChanged; Auth().data().removeMegagroupParticipant(this, user); } } Data::ChannelAdminChanges(this).feed(peerToUser(user->id), false); } else { if (isKicked) { if (membersCount() > 1) { setMembersCount(membersCount() - 1); flags |= Notify::PeerUpdate::Flag::MembersChanged; } setKickedCount(kickedCount() + 1); } } Notify::peerUpdatedDelayed(this, flags); } bool ChannelData::isGroupAdmin(not_null<UserData*> user) const { if (auto info = mgInfo.get()) { return info->admins.contains(peerToUser(user->id)); } return false; } void ChannelData::setRestrictionReason(const QString &text) { if (_restrictionReason != text) { _restrictionReason = text; Notify::peerUpdatedDelayed(this, Notify::PeerUpdate::Flag::RestrictionReasonChanged); } } void ChannelData::setAvailableMinId(MsgId availableMinId) { if (_availableMinId != availableMinId) { _availableMinId = availableMinId; if (auto history = App::historyLoaded(this)) { history->clearUpTill(availableMinId); } if (_pinnedMessageId <= _availableMinId) { _pinnedMessageId = MsgId(0); Notify::peerUpdatedDelayed( this, Notify::PeerUpdate::Flag::ChannelPinnedChanged); } } } void ChannelData::setPinnedMessageId(MsgId messageId) { messageId = (messageId > _availableMinId) ? messageId : MsgId(0); if (_pinnedMessageId != messageId) { _pinnedMessageId = messageId; Notify::peerUpdatedDelayed( this, Notify::PeerUpdate::Flag::ChannelPinnedChanged); } } void ChannelData::setFeed(not_null<Data::Feed*> feed) { setFeedPointer(feed); } void ChannelData::clearFeed() { setFeedPointer(nullptr); } void ChannelData::setFeedPointer(Data::Feed *feed) { if (_feed != feed) { const auto was = _feed; _feed = feed; if (was) { was->unregisterOne(this); } if (_feed) { _feed->registerOne(this); } } } bool ChannelData::canBanMembers() const { return (adminRights() & AdminRight::f_ban_users) || amCreator(); } bool ChannelData::canEditMessages() const { return (adminRights() & AdminRight::f_edit_messages) || amCreator(); } bool ChannelData::canDeleteMessages() const { return (adminRights() & AdminRight::f_delete_messages) || amCreator(); } bool ChannelData::anyoneCanAddMembers() const { return (flags() & MTPDchannel::Flag::f_democracy); } bool ChannelData::hiddenPreHistory() const { return (fullFlags() & MTPDchannelFull::Flag::f_hidden_prehistory); } bool ChannelData::canAddMembers() const { return (adminRights() & AdminRight::f_invite_users) || amCreator() || (anyoneCanAddMembers() && amIn() && !hasRestrictions()); } bool ChannelData::canAddAdmins() const { return (adminRights() & AdminRight::f_add_admins) || amCreator(); } bool ChannelData::canPinMessages() const { if (isMegagroup()) { return (adminRights() & AdminRight::f_pin_messages) || amCreator(); } return (adminRights() & AdminRight::f_edit_messages) || amCreator(); } bool ChannelData::canPublish() const { return (adminRights() & AdminRight::f_post_messages) || amCreator(); } bool ChannelData::canWrite() const { // Duplicated in Data::CanWriteValue(). return amIn() && (canPublish() || (!isBroadcast() && !restricted(Restriction::f_send_messages))); } bool ChannelData::canViewMembers() const { return fullFlags() & MTPDchannelFull::Flag::f_can_view_participants; } bool ChannelData::canViewAdmins() const { return (isMegagroup() || hasAdminRights() || amCreator()); } bool ChannelData::canViewBanned() const { return (hasAdminRights() || amCreator()); } bool ChannelData::canEditInformation() const { return (adminRights() & AdminRight::f_change_info) || amCreator(); } bool ChannelData::canEditInvites() const { return canEditInformation(); } bool ChannelData::canEditSignatures() const { return canEditInformation(); } bool ChannelData::canEditPreHistoryHidden() const { return canEditInformation(); } bool ChannelData::canEditUsername() const { return amCreator() && (fullFlags() & MTPDchannelFull::Flag::f_can_set_username); } bool ChannelData::canEditStickers() const { return (fullFlags() & MTPDchannelFull::Flag::f_can_set_stickers); } bool ChannelData::canDelete() const { constexpr auto kDeleteChannelMembersLimit = 1000; return amCreator() && (membersCount() <= kDeleteChannelMembersLimit); } bool ChannelData::canEditLastAdmin(not_null<UserData*> user) const { // Duplicated in ParticipantsBoxController::canEditAdmin :( if (mgInfo) { auto i = mgInfo->lastAdmins.find(user); if (i != mgInfo->lastAdmins.cend()) { return i->second.canEdit; } return (user != mgInfo->creator); } return false; } bool ChannelData::canEditAdmin(not_null<UserData*> user) const { // Duplicated in ParticipantsBoxController::canEditAdmin :( if (user->isSelf()) { return false; } else if (amCreator()) { return true; } else if (!canEditLastAdmin(user)) { return false; } return adminRights() & AdminRight::f_add_admins; } bool ChannelData::canRestrictUser(not_null<UserData*> user) const { // Duplicated in ParticipantsBoxController::canRestrictUser :( if (user->isSelf()) { return false; } else if (amCreator()) { return true; } else if (!canEditLastAdmin(user)) { return false; } return adminRights() & AdminRight::f_ban_users; } void ChannelData::setAdminRights(const MTPChannelAdminRights &rights) { if (rights.c_channelAdminRights().vflags.v == adminRights()) { return; } _adminRights.set(rights.c_channelAdminRights().vflags.v); if (isMegagroup()) { if (hasAdminRights()) { if (!amCreator()) { auto me = MegagroupInfo::Admin { rights }; me.canEdit = false; mgInfo->lastAdmins.emplace(App::self(), me); } mgInfo->lastRestricted.remove(App::self()); } else { mgInfo->lastAdmins.remove(App::self()); } auto amAdmin = hasAdminRights() || amCreator(); Data::ChannelAdminChanges(this).feed(Auth().userId(), amAdmin); } Notify::peerUpdatedDelayed(this, UpdateFlag::ChannelRightsChanged | UpdateFlag::AdminsChanged | UpdateFlag::BannedUsersChanged); } void ChannelData::setRestrictedRights(const MTPChannelBannedRights &rights) { if (rights.c_channelBannedRights().vflags.v == restrictions() && rights.c_channelBannedRights().vuntil_date.v == _restrictedUntill) { return; } _restrictedUntill = rights.c_channelBannedRights().vuntil_date.v; _restrictions.set(rights.c_channelBannedRights().vflags.v); if (isMegagroup()) { if (hasRestrictions()) { if (!amCreator()) { auto me = MegagroupInfo::Restricted { rights }; mgInfo->lastRestricted.emplace(App::self(), me); } mgInfo->lastAdmins.remove(App::self()); Data::ChannelAdminChanges(this).feed(Auth().userId(), false); } else { mgInfo->lastRestricted.remove(App::self()); } } Notify::peerUpdatedDelayed(this, UpdateFlag::ChannelRightsChanged | UpdateFlag::AdminsChanged | UpdateFlag::BannedUsersChanged); } uint64 PtsWaiter::ptsKey(PtsSkippedQueue queue, int32 pts) { return _queue.insert(uint64(uint32(pts)) << 32 | (++_skippedKey), queue).key(); } void PtsWaiter::setWaitingForSkipped(ChannelData *channel, int32 ms) { if (ms >= 0) { if (App::main()) { App::main()->ptsWaiterStartTimerFor(channel, ms); } _waitingForSkipped = true; } else { _waitingForSkipped = false; checkForWaiting(channel); } } void PtsWaiter::setWaitingForShortPoll(ChannelData *channel, int32 ms) { if (ms >= 0) { if (App::main()) { App::main()->ptsWaiterStartTimerFor(channel, ms); } _waitingForShortPoll = true; } else { _waitingForShortPoll = false; checkForWaiting(channel); } } void PtsWaiter::checkForWaiting(ChannelData *channel) { if (!_waitingForSkipped && !_waitingForShortPoll && App::main()) { App::main()->ptsWaiterStartTimerFor(channel, -1); } } void PtsWaiter::applySkippedUpdates(ChannelData *channel) { if (!_waitingForSkipped) return; setWaitingForSkipped(channel, -1); if (_queue.isEmpty()) return; ++_applySkippedLevel; for (auto i = _queue.cbegin(), e = _queue.cend(); i != e; ++i) { switch (i.value()) { case SkippedUpdate: Auth().api().applyUpdateNoPtsCheck(_updateQueue.value(i.key())); break; case SkippedUpdates: Auth().api().applyUpdatesNoPtsCheck(_updatesQueue.value(i.key())); break; } } --_applySkippedLevel; clearSkippedUpdates(); } void PtsWaiter::clearSkippedUpdates() { _queue.clear(); _updateQueue.clear(); _updatesQueue.clear(); _applySkippedLevel = 0; } bool PtsWaiter::updated(ChannelData *channel, int32 pts, int32 count, const MTPUpdates &updates) { if (_requesting || _applySkippedLevel) { return true; } else if (pts <= _good && count > 0) { return false; } else if (check(channel, pts, count)) { return true; } _updatesQueue.insert(ptsKey(SkippedUpdates, pts), updates); return false; } bool PtsWaiter::updated(ChannelData *channel, int32 pts, int32 count, const MTPUpdate &update) { if (_requesting || _applySkippedLevel) { return true; } else if (pts <= _good && count > 0) { return false; } else if (check(channel, pts, count)) { return true; } _updateQueue.insert(ptsKey(SkippedUpdate, pts), update); return false; } bool PtsWaiter::updated(ChannelData *channel, int32 pts, int32 count) { if (_requesting || _applySkippedLevel) { return true; } else if (pts <= _good && count > 0) { return false; } return check(channel, pts, count); } bool PtsWaiter::updateAndApply(ChannelData *channel, int32 pts, int32 count, const MTPUpdates &updates) { if (!updated(channel, pts, count, updates)) { return false; } if (!_waitingForSkipped || _queue.isEmpty()) { // Optimization - no need to put in queue and back. Auth().api().applyUpdatesNoPtsCheck(updates); } else { _updatesQueue.insert(ptsKey(SkippedUpdates, pts), updates); applySkippedUpdates(channel); } return true; } bool PtsWaiter::updateAndApply(ChannelData *channel, int32 pts, int32 count, const MTPUpdate &update) { if (!updated(channel, pts, count, update)) { return false; } if (!_waitingForSkipped || _queue.isEmpty()) { // Optimization - no need to put in queue and back. Auth().api().applyUpdateNoPtsCheck(update); } else { _updateQueue.insert(ptsKey(SkippedUpdate, pts), update); applySkippedUpdates(channel); } return true; } bool PtsWaiter::updateAndApply(ChannelData *channel, int32 pts, int32 count) { if (!updated(channel, pts, count)) { return false; } applySkippedUpdates(channel); return true; } bool PtsWaiter::check(ChannelData *channel, int32 pts, int32 count) { // return false if need to save that update and apply later if (!inited()) { init(pts); return true; } _last = qMax(_last, pts); _count += count; if (_last == _count) { _good = _last; return true; } else if (_last < _count) { setWaitingForSkipped(channel, 1); } else { setWaitingForSkipped(channel, WaitForSkippedTimeout); } return !count; } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/data/data_peer.h�����������������������������������������������0000664�0000000�0000000�00000070202�13262451251�0022432�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "data/data_types.h" #include "data/data_flags.h" #include "data/data_notify_settings.h" namespace Ui { class EmptyUserpic; } // namespace Ui class PeerData; class UserData; class ChatData; class ChannelData; namespace Data { class Feed; int PeerColorIndex(PeerId peerId); int PeerColorIndex(int32 bareId); style::color PeerUserpicColor(PeerId peerId); } // namespace Data class PeerClickHandler : public ClickHandler { public: PeerClickHandler(not_null<PeerData*> peer); void onClick(Qt::MouseButton button) const override; not_null<PeerData*> peer() const { return _peer; } private: not_null<PeerData*> _peer; }; class PeerData { protected: PeerData(const PeerId &id); PeerData(const PeerData &other) = delete; PeerData &operator=(const PeerData &other) = delete; public: virtual ~PeerData(); bool isUser() const { return peerIsUser(id); } bool isChat() const { return peerIsChat(id); } bool isChannel() const { return peerIsChannel(id); } bool isSelf() const { return (input.type() == mtpc_inputPeerSelf); } bool isVerified() const; bool isMegagroup() const; TimeMs notifyMuteFinishesIn() const { return _notify.muteFinishesIn(); } bool notifyChange(const MTPPeerNotifySettings &settings) { return _notify.change(settings); } bool notifyChange( Data::NotifySettings::MuteChange mute, Data::NotifySettings::SilentPostsChange silent, int muteForSeconds) { return _notify.change(mute, silent, muteForSeconds); } bool notifySettingsUnknown() const { return _notify.settingsUnknown(); } bool notifySilentPosts() const { return _notify.silentPosts(); } MTPinputPeerNotifySettings notifySerialize() const { return _notify.serialize(); } bool isMuted() const { return (notifyMuteFinishesIn() > 0); } bool canWrite() const; UserData *asUser(); const UserData *asUser() const; ChatData *asChat(); const ChatData *asChat() const; ChannelData *asChannel(); const ChannelData *asChannel() const; ChannelData *asMegagroup(); const ChannelData *asMegagroup() const; ChatData *migrateFrom() const; ChannelData *migrateTo() const; Data::Feed *feed() const; void updateFull(); void updateFullForced(); void fullUpdated(); bool wasFullUpdated() const { return (_lastFullUpdate != 0); } const Text &dialogName() const; const QString &shortName() const; QString userName() const; const PeerId id; int32 bareId() const { return int32(uint32(id & 0xFFFFFFFFULL)); } QString name; Text nameText; const base::flat_set<QString> &nameWords() const { return _nameWords; } const base::flat_set<QChar> &nameFirstLetters() const { return _nameFirstLetters; } enum LoadedStatus { NotLoaded = 0x00, MinimalLoaded = 0x01, FullLoaded = 0x02, }; LoadedStatus loadedStatus = NotLoaded; MTPinputPeer input; void setUserpic( PhotoId photoId, const StorageImageLocation &location, ImagePtr userpic); void setUserpicPhoto(const MTPPhoto &data); void paintUserpic( Painter &p, int x, int y, int size) const; void paintUserpicLeft( Painter &p, int x, int y, int w, int size) const { paintUserpic(p, rtl() ? (w - x - size) : x, y, size); } void paintUserpicRounded( Painter &p, int x, int y, int size) const; void paintUserpicSquare( Painter &p, int x, int y, int size) const; void loadUserpic(bool loadFirst = false, bool prior = true) { _userpic->load(loadFirst, prior); } bool userpicLoaded() const { return _userpic->loaded(); } bool useEmptyUserpic() const { return _userpicLocation.isNull() || !_userpic || !_userpic->loaded(); } StorageKey userpicUniqueKey() const; void saveUserpic(const QString &path, int size) const; void saveUserpicRounded(const QString &path, int size) const; QPixmap genUserpic(int size) const; QPixmap genUserpicRounded(int size) const; StorageImageLocation userpicLocation() const { return _userpicLocation; } bool userpicPhotoUnknown() const { return (_userpicPhotoId == kUnknownPhotoId); } PhotoId userpicPhotoId() const { return userpicPhotoUnknown() ? 0 : _userpicPhotoId; } int nameVersion = 1; // if this string is not empty we must not allow to open the // conversation and we must show this string instead virtual QString restrictionReason() const { return QString(); } ClickHandlerPtr createOpenLink(); const ClickHandlerPtr &openLink() { if (!_openLink) { _openLink = createOpenLink(); } return _openLink; } ImagePtr currentUserpic() const; protected: void updateNameDelayed( const QString &newName, const QString &newNameOrPhone, const QString &newUsername); void updateUserpic(PhotoId photoId, const MTPFileLocation &location); void clearUserpic(); private: void fillNames(); std::unique_ptr<Ui::EmptyUserpic> createEmptyUserpic() const; void refreshEmptyUserpic() const; void setUserpicChecked( PhotoId photoId, const StorageImageLocation &location, ImagePtr userpic); static constexpr auto kUnknownPhotoId = PhotoId(0xFFFFFFFFFFFFFFFFULL); ImagePtr _userpic; PhotoId _userpicPhotoId = kUnknownPhotoId; mutable std::unique_ptr<Ui::EmptyUserpic> _userpicEmpty; StorageImageLocation _userpicLocation; Data::NotifySettings _notify; ClickHandlerPtr _openLink; base::flat_set<QString> _nameWords; // for filtering base::flat_set<QChar> _nameFirstLetters; TimeMs _lastFullUpdate = 0; }; class BotCommand { public: BotCommand( const QString &command, const QString &description) : command(command) , _description(description) { } QString command; bool setDescription(const QString &description) { if (_description != description) { _description = description; _descriptionText = Text(); return true; } return false; } const Text &descriptionText() const; private: QString _description; mutable Text _descriptionText; }; struct BotInfo { bool inited = false; bool readsAllHistory = false; bool cantJoinGroups = false; int version = 0; QString description, inlinePlaceholder; QList<BotCommand> commands; Text text = Text{ int(st::msgMinWidth) }; // description QString startToken, startGroupToken, shareGameShortName; PeerId inlineReturnPeerId = 0; }; class UserData : public PeerData { public: static constexpr auto kEssentialFlags = 0 | MTPDuser::Flag::f_self | MTPDuser::Flag::f_contact | MTPDuser::Flag::f_mutual_contact | MTPDuser::Flag::f_deleted | MTPDuser::Flag::f_bot | MTPDuser::Flag::f_bot_chat_history | MTPDuser::Flag::f_bot_nochats | MTPDuser::Flag::f_verified | MTPDuser::Flag::f_restricted | MTPDuser::Flag::f_bot_inline_geo; using Flags = Data::Flags< MTPDuser::Flags, kEssentialFlags.value()>; static constexpr auto kEssentialFullFlags = 0 | MTPDuserFull::Flag::f_blocked | MTPDuserFull::Flag::f_phone_calls_available | MTPDuserFull::Flag::f_phone_calls_private; using FullFlags = Data::Flags< MTPDuserFull::Flags, kEssentialFullFlags.value()>; UserData(const PeerId &id) : PeerData(id) { } void setPhoto(const MTPUserProfilePhoto &photo); void setName( const QString &newFirstName, const QString &newLastName, const QString &newPhoneName, const QString &newUsername); void setPhone(const QString &newPhone); void setBotInfoVersion(int version); void setBotInfo(const MTPBotInfo &info); void setNameOrPhone(const QString &newNameOrPhone); void madeAction(TimeId when); // pseudo-online uint64 accessHash() const { return _accessHash; } void setAccessHash(uint64 accessHash); void setFlags(MTPDuser::Flags which) { _flags.set(which); } void addFlags(MTPDuser::Flags which) { _flags.add(which); } void removeFlags(MTPDuser::Flags which) { _flags.remove(which); } auto flags() const { return _flags.current(); } auto flagsValue() const { return _flags.value(); } void setFullFlags(MTPDuserFull::Flags which) { _fullFlags.set(which); } void addFullFlags(MTPDuserFull::Flags which) { _fullFlags.add(which); } void removeFullFlags(MTPDuserFull::Flags which) { _fullFlags.remove(which); } auto fullFlags() const { return _fullFlags.current(); } auto fullFlagsValue() const { return _fullFlags.value(); } bool isVerified() const { return flags() & MTPDuser::Flag::f_verified; } bool isBotInlineGeo() const { return flags() & MTPDuser::Flag::f_bot_inline_geo; } bool isInaccessible() const { constexpr auto inaccessible = 0 | MTPDuser::Flag::f_deleted; // | MTPDuser_ClientFlag::f_inaccessible; return flags() & inaccessible; } bool canWrite() const { // Duplicated in Data::CanWriteValue(). return !isInaccessible(); } bool isContact() const { return (_contactStatus == ContactStatus::Contact); } bool canShareThisContact() const; bool canAddContact() const { return canShareThisContact() && !isContact(); } // In feedUsers() we check only that. // When actually trying to share contact we perform // a full check by canShareThisContact() call. bool canShareThisContactFast() const { return !_phone.isEmpty(); } MTPInputUser inputUser; QString firstName; QString lastName; QString username; const QString &phone() const { return _phone; } QString nameOrPhone; Text phoneText; TimeId onlineTill = 0; enum class ContactStatus : char { PhoneUnknown, CanAdd, Contact, }; ContactStatus contactStatus() const { return _contactStatus; } void setContactStatus(ContactStatus status); enum class BlockStatus : char { Unknown, Blocked, NotBlocked, }; BlockStatus blockStatus() const { return _blockStatus; } bool isBlocked() const { return (blockStatus() == BlockStatus::Blocked); } void setBlockStatus(BlockStatus blockStatus); enum class CallsStatus : char { Unknown, Enabled, Disabled, Private, }; CallsStatus callsStatus() const { return _callsStatus; } bool hasCalls() const; void setCallsStatus(CallsStatus callsStatus); bool setAbout(const QString &newAbout); const QString &about() const { return _about; } std::unique_ptr<BotInfo> botInfo; QString restrictionReason() const override { return _restrictionReason; } void setRestrictionReason(const QString &reason); int commonChatsCount() const { return _commonChatsCount; } void setCommonChatsCount(int count); private: Flags _flags; FullFlags _fullFlags; QString _restrictionReason; QString _about; QString _phone; ContactStatus _contactStatus = ContactStatus::PhoneUnknown; BlockStatus _blockStatus = BlockStatus::Unknown; CallsStatus _callsStatus = CallsStatus::Unknown; int _commonChatsCount = 0; uint64 _accessHash = 0; static constexpr auto kInaccessibleAccessHashOld = 0xFFFFFFFFFFFFFFFFULL; }; class ChatData : public PeerData { public: static constexpr auto kEssentialFlags = 0 | MTPDchat::Flag::f_creator | MTPDchat::Flag::f_kicked | MTPDchat::Flag::f_left | MTPDchat::Flag::f_admins_enabled | MTPDchat::Flag::f_admin | MTPDchat::Flag::f_deactivated | MTPDchat::Flag::f_migrated_to; using Flags = Data::Flags< MTPDchat::Flags, kEssentialFlags>; ChatData(const PeerId &id) : PeerData(id) , inputChat(MTP_int(bareId())) { } void setPhoto(const MTPChatPhoto &photo); void setPhoto(PhotoId photoId, const MTPChatPhoto &photo); void setName(const QString &newName); void invalidateParticipants(); bool noParticipantInfo() const { return (count > 0 || amIn()) && participants.empty(); } MTPint inputChat; ChannelData *migrateToPtr = nullptr; int count = 0; TimeId date = 0; int version = 0; UserId creator = 0; void setFlags(MTPDchat::Flags which) { _flags.set(which); } void addFlags(MTPDchat::Flags which) { _flags.add(which); } void removeFlags(MTPDchat::Flags which) { _flags.remove(which); } auto flags() const { return _flags.current(); } auto flagsValue() const { return _flags.value(); } bool isForbidden() const { return flags() & MTPDchat_ClientFlag::f_forbidden; } bool amIn() const { return !isForbidden() && !haveLeft() && !wasKicked(); } bool canEdit() const { return !isDeactivated() && (amCreator() || (adminsEnabled() ? amAdmin() : amIn())); } bool canWrite() const { // Duplicated in Data::CanWriteValue(). return !isDeactivated() && amIn(); } bool haveLeft() const { return flags() & MTPDchat::Flag::f_left; } bool wasKicked() const { return flags() & MTPDchat::Flag::f_kicked; } bool adminsEnabled() const { return flags() & MTPDchat::Flag::f_admins_enabled; } bool amCreator() const { return flags() & MTPDchat::Flag::f_creator; } bool amAdmin() const { return (flags() & MTPDchat::Flag::f_admin) && adminsEnabled(); } bool isDeactivated() const { return flags() & MTPDchat::Flag::f_deactivated; } bool isMigrated() const { return flags() & MTPDchat::Flag::f_migrated_to; } base::flat_map<not_null<UserData*>, int> participants; base::flat_set<not_null<UserData*>> invitedByMe; base::flat_set<not_null<UserData*>> admins; std::deque<not_null<UserData*>> lastAuthors; base::flat_set<not_null<PeerData*>> markupSenders; int botStatus = 0; // -1 - no bots, 0 - unknown, 1 - one bot, that sees all history, 2 - other // ImagePtr photoFull; void setInviteLink(const QString &newInviteLink); QString inviteLink() const { return _inviteLink; } private: void flagsUpdated(MTPDchat::Flags diff); Flags _flags; QString _inviteLink; }; enum PtsSkippedQueue { SkippedUpdate, SkippedUpdates, }; class PtsWaiter { public: PtsWaiter() = default; void init(int32 pts) { _good = _last = _count = pts; clearSkippedUpdates(); } bool inited() const { return _good > 0; } void setRequesting(bool isRequesting) { _requesting = isRequesting; if (_requesting) { clearSkippedUpdates(); } } bool requesting() const { return _requesting; } bool waitingForSkipped() const { return _waitingForSkipped; } bool waitingForShortPoll() const { return _waitingForShortPoll; } void setWaitingForSkipped(ChannelData *channel, int32 ms); // < 0 - not waiting void setWaitingForShortPoll(ChannelData *channel, int32 ms); // < 0 - not waiting int32 current() const{ return _good; } bool updated( ChannelData *channel, int32 pts, int32 count, const MTPUpdates &updates); bool updated( ChannelData *channel, int32 pts, int32 count, const MTPUpdate &update); bool updated( ChannelData *channel, int32 pts, int32 count); bool updateAndApply( ChannelData *channel, int32 pts, int32 count, const MTPUpdates &updates); bool updateAndApply( ChannelData *channel, int32 pts, int32 count, const MTPUpdate &update); bool updateAndApply( ChannelData *channel, int32 pts, int32 count); void applySkippedUpdates(ChannelData *channel); void clearSkippedUpdates(); private: bool check(ChannelData *channel, int32 pts, int32 count); // return false if need to save that update and apply later uint64 ptsKey(PtsSkippedQueue queue, int32 pts); void checkForWaiting(ChannelData *channel); QMap<uint64, PtsSkippedQueue> _queue; QMap<uint64, MTPUpdate> _updateQueue; QMap<uint64, MTPUpdates> _updatesQueue; int32 _good = 0; int32 _last = 0; int32 _count = 0; int32 _applySkippedLevel = 0; bool _requesting = false; bool _waitingForSkipped = false; bool _waitingForShortPoll = false; uint32 _skippedKey = 0; }; struct MegagroupInfo { struct Admin { explicit Admin(MTPChannelAdminRights rights) : rights(rights) { } Admin(MTPChannelAdminRights rights, bool canEdit) : rights(rights) , canEdit(canEdit) { } MTPChannelAdminRights rights; bool canEdit = false; }; struct Restricted { explicit Restricted(MTPChannelBannedRights rights) : rights(rights) { } MTPChannelBannedRights rights; }; std::deque<not_null<UserData*>> lastParticipants; base::flat_map<not_null<UserData*>, Admin> lastAdmins; base::flat_map<not_null<UserData*>, Restricted> lastRestricted; base::flat_set<not_null<PeerData*>> markupSenders; base::flat_set<not_null<UserData*>> bots; // For admin badges, full admins list. base::flat_set<UserId> admins; UserData *creator = nullptr; // nullptr means unknown int botStatus = 0; // -1 - no bots, 0 - unknown, 1 - one bot, that sees all history, 2 - other bool joinedMessageFound = false; MTPInputStickerSet stickerSet = MTP_inputStickerSetEmpty(); enum LastParticipantsStatus { LastParticipantsUpToDate = 0x00, LastParticipantsCountOutdated = 0x02, }; mutable int lastParticipantsStatus = LastParticipantsUpToDate; int lastParticipantsCount = 0; ChatData *migrateFromPtr = nullptr; }; using ChannelAdminRight = MTPDchannelAdminRights::Flag; using ChannelRestriction = MTPDchannelBannedRights::Flag; using ChannelAdminRights = MTPDchannelAdminRights::Flags; using ChannelRestrictions = MTPDchannelBannedRights::Flags; class ChannelData : public PeerData { public: static constexpr auto kEssentialFlags = 0 | MTPDchannel::Flag::f_creator | MTPDchannel::Flag::f_left | MTPDchannel::Flag::f_broadcast | MTPDchannel::Flag::f_verified | MTPDchannel::Flag::f_megagroup | MTPDchannel::Flag::f_restricted | MTPDchannel::Flag::f_democracy | MTPDchannel::Flag::f_signatures | MTPDchannel::Flag::f_username; using Flags = Data::Flags< MTPDchannel::Flags, kEssentialFlags>; static constexpr auto kEssentialFullFlags = 0 | MTPDchannelFull::Flag::f_can_view_participants | MTPDchannelFull::Flag::f_can_set_username | MTPDchannelFull::Flag::f_can_set_stickers; using FullFlags = Data::Flags< MTPDchannelFull::Flags, kEssentialFullFlags>; ChannelData(const PeerId &id); void setPhoto(const MTPChatPhoto &photo); void setPhoto(PhotoId photoId, const MTPChatPhoto &photo); void setName(const QString &name, const QString &username); void setFlags(MTPDchannel::Flags which) { _flags.set(which); } void addFlags(MTPDchannel::Flags which) { _flags.add(which); } void removeFlags(MTPDchannel::Flags which) { _flags.remove(which); } auto flags() const { return _flags.current(); } auto flagsValue() const { return _flags.value(); } void setFullFlags(MTPDchannelFull::Flags which) { _fullFlags.set(which); } void addFullFlags(MTPDchannelFull::Flags which) { _fullFlags.add(which); } void removeFullFlags(MTPDchannelFull::Flags which) { _fullFlags.remove(which); } auto fullFlags() const { return _fullFlags.current(); } auto fullFlagsValue() const { return _fullFlags.value(); } uint64 access = 0; MTPinputChannel inputChannel; QString username; // Returns true if about text was changed. bool setAbout(const QString &newAbout); const QString &about() const { return _about; } int membersCount() const { return _membersCount; } void setMembersCount(int newMembersCount); int adminsCount() const { return _adminsCount; } void setAdminsCount(int newAdminsCount); int restrictedCount() const { return _restrictedCount; } void setRestrictedCount(int newRestrictedCount); int kickedCount() const { return _kickedCount; } void setKickedCount(int newKickedCount); bool haveLeft() const { return flags() & MTPDchannel::Flag::f_left; } bool amIn() const { return !isForbidden() && !haveLeft(); } bool addsSignature() const { return flags() & MTPDchannel::Flag::f_signatures; } bool isForbidden() const { return flags() & MTPDchannel_ClientFlag::f_forbidden; } bool isVerified() const { return flags() & MTPDchannel::Flag::f_verified; } static MTPChannelBannedRights KickedRestrictedRights(); static constexpr auto kRestrictUntilForever = TimeId(INT_MAX); static bool IsRestrictedForever(TimeId until) { return !until || (until == kRestrictUntilForever); } void applyEditAdmin( not_null<UserData*> user, const MTPChannelAdminRights &oldRights, const MTPChannelAdminRights &newRights); void applyEditBanned( not_null<UserData*> user, const MTPChannelBannedRights &oldRights, const MTPChannelBannedRights &newRights); bool isGroupAdmin(not_null<UserData*> user) const; int32 date = 0; int version = 0; std::unique_ptr<MegagroupInfo> mgInfo; bool lastParticipantsCountOutdated() const { if (!mgInfo || !(mgInfo->lastParticipantsStatus & MegagroupInfo::LastParticipantsCountOutdated)) { return false; } if (mgInfo->lastParticipantsCount == membersCount()) { mgInfo->lastParticipantsStatus &= ~MegagroupInfo::LastParticipantsCountOutdated; return false; } return true; } bool isMegagroup() const { return flags() & MTPDchannel::Flag::f_megagroup; } bool isBroadcast() const { return flags() & MTPDchannel::Flag::f_broadcast; } bool isPublic() const { return flags() & MTPDchannel::Flag::f_username; } bool amCreator() const { return flags() & MTPDchannel::Flag::f_creator; } using AdminRight = ChannelAdminRight; using Restriction = ChannelRestriction; using AdminRights = ChannelAdminRights; using Restrictions = ChannelRestrictions; using AdminRightFlags = Data::Flags<AdminRights>; using RestrictionFlags = Data::Flags<Restrictions>; auto adminRights() const { return _adminRights.current(); } auto adminRightsValue() const { return _adminRights.value(); } void setAdminRights(const MTPChannelAdminRights &rights); bool hasAdminRights() const { return (adminRights() != 0); } auto restrictions() const { return _restrictions.current(); } auto restrictionsValue() const { return _restrictions.value(); } bool restricted(Restriction right) const { return restrictions() & right; } TimeId restrictedUntil() const { return _restrictedUntill; } void setRestrictedRights(const MTPChannelBannedRights &rights); bool hasRestrictions() const { return (restrictions() != 0); } bool hasRestrictions(TimeId now) const { return hasRestrictions() && (restrictedUntil() > now); } bool canBanMembers() const; bool canEditMessages() const; bool canDeleteMessages() const; bool anyoneCanAddMembers() const; bool hiddenPreHistory() const; bool canAddMembers() const; bool canAddAdmins() const; bool canPinMessages() const; bool canPublish() const; bool canWrite() const; bool canViewMembers() const; bool canViewAdmins() const; bool canViewBanned() const; bool canEditInformation() const; bool canEditInvites() const; bool canEditSignatures() const; bool canEditPreHistoryHidden() const; bool canEditUsername() const; bool canEditStickers() const; bool canDelete() const; bool canEditAdmin(not_null<UserData*> user) const; bool canRestrictUser(not_null<UserData*> user) const; void setInviteLink(const QString &newInviteLink); QString inviteLink() const { return _inviteLink; } bool canHaveInviteLink() const { return (adminRights() & AdminRight::f_invite_link) || amCreator(); } UserId inviter = 0; // > 0 - user who invited me to channel, < 0 - not in channel TimeId inviteDate = 0; void ptsInit(int32 pts) { _ptsWaiter.init(pts); } void ptsReceived(int32 pts) { _ptsWaiter.updateAndApply(this, pts, 0); } bool ptsUpdateAndApply(int32 pts, int32 count) { return _ptsWaiter.updateAndApply(this, pts, count); } bool ptsUpdateAndApply( int32 pts, int32 count, const MTPUpdate &update) { return _ptsWaiter.updateAndApply(this, pts, count, update); } bool ptsUpdateAndApply( int32 pts, int32 count, const MTPUpdates &updates) { return _ptsWaiter.updateAndApply(this, pts, count, updates); } int32 pts() const { return _ptsWaiter.current(); } bool ptsInited() const { return _ptsWaiter.inited(); } bool ptsRequesting() const { return _ptsWaiter.requesting(); } void ptsSetRequesting(bool isRequesting) { return _ptsWaiter.setRequesting(isRequesting); } void ptsWaitingForShortPoll(int32 ms) { // < 0 - not waiting return _ptsWaiter.setWaitingForShortPoll(this, ms); } bool ptsWaitingForSkipped() const { return _ptsWaiter.waitingForSkipped(); } bool ptsWaitingForShortPoll() const { return _ptsWaiter.waitingForShortPoll(); } QString restrictionReason() const override { return _restrictionReason; } void setRestrictionReason(const QString &reason); MsgId availableMinId() const { return _availableMinId; } void setAvailableMinId(MsgId availableMinId); MsgId pinnedMessageId() const { return _pinnedMessageId; } void setPinnedMessageId(MsgId messageId); void clearPinnedMessage() { setPinnedMessageId(0); } void setFeed(not_null<Data::Feed*> feed); void clearFeed(); Data::Feed *feed() const { return _feed; } private: void flagsUpdated(MTPDchannel::Flags diff); void fullFlagsUpdated(MTPDchannelFull::Flags diff); bool canEditLastAdmin(not_null<UserData*> user) const; void setFeedPointer(Data::Feed *feed); Flags _flags = Flags(MTPDchannel_ClientFlag::f_forbidden | 0); FullFlags _fullFlags; PtsWaiter _ptsWaiter; int _membersCount = 1; int _adminsCount = 1; int _restrictedCount = 0; int _kickedCount = 0; MsgId _availableMinId = 0; MsgId _pinnedMessageId = 0; AdminRightFlags _adminRights; RestrictionFlags _restrictions; TimeId _restrictedUntill; QString _restrictionReason; QString _about; QString _inviteLink; Data::Feed *_feed = nullptr; rpl::lifetime _lifetime; }; inline bool isUser(const PeerData *peer) { return peer ? peer->isUser() : false; } inline UserData *PeerData::asUser() { return isUser() ? static_cast<UserData*>(this) : nullptr; } inline UserData *asUser(PeerData *peer) { return peer ? peer->asUser() : nullptr; } inline const UserData *PeerData::asUser() const { return isUser() ? static_cast<const UserData*>(this) : nullptr; } inline const UserData *asUser(const PeerData *peer) { return peer ? peer->asUser() : nullptr; } inline bool isChat(const PeerData *peer) { return peer ? peer->isChat() : false; } inline ChatData *PeerData::asChat() { return isChat() ? static_cast<ChatData*>(this) : nullptr; } inline ChatData *asChat(PeerData *peer) { return peer ? peer->asChat() : nullptr; } inline const ChatData *PeerData::asChat() const { return isChat() ? static_cast<const ChatData*>(this) : nullptr; } inline const ChatData *asChat(const PeerData *peer) { return peer ? peer->asChat() : nullptr; } inline bool isChannel(const PeerData *peer) { return peer ? peer->isChannel() : false; } inline ChannelData *PeerData::asChannel() { return isChannel() ? static_cast<ChannelData*>(this) : nullptr; } inline ChannelData *asChannel(PeerData *peer) { return peer ? peer->asChannel() : nullptr; } inline const ChannelData *PeerData::asChannel() const { return isChannel() ? static_cast<const ChannelData*>(this) : nullptr; } inline const ChannelData *asChannel(const PeerData *peer) { return peer ? peer->asChannel() : nullptr; } inline ChannelData *PeerData::asMegagroup() { return isMegagroup() ? static_cast<ChannelData*>(this) : nullptr; } inline ChannelData *asMegagroup(PeerData *peer) { return peer ? peer->asMegagroup() : nullptr; } inline const ChannelData *PeerData::asMegagroup() const { return isMegagroup() ? static_cast<const ChannelData*>(this) : nullptr; } inline const ChannelData *asMegagroup(const PeerData *peer) { return peer ? peer->asMegagroup() : nullptr; } inline bool isMegagroup(const PeerData *peer) { return peer ? peer->isMegagroup() : false; } inline ChatData *PeerData::migrateFrom() const { if (const auto megagroup = asMegagroup()) { return megagroup->amIn() ? megagroup->mgInfo->migrateFromPtr : nullptr; } return nullptr; } inline ChannelData *PeerData::migrateTo() const { if (const auto chat = asChat()) { if (const auto migrateTo = chat->migrateToPtr) { return migrateTo->amIn() ? migrateTo : nullptr; } } return nullptr; } inline Data::Feed *PeerData::feed() const { if (const auto channel = asChannel()) { return channel->feed(); } return nullptr; } inline const Text &PeerData::dialogName() const { return migrateTo() ? migrateTo()->dialogName() : (isUser() && !asUser()->phoneText.isEmpty()) ? asUser()->phoneText : nameText; } inline const QString &PeerData::shortName() const { return isUser() ? asUser()->firstName : name; } inline QString PeerData::userName() const { return isUser() ? asUser()->username : isChannel() ? asChannel()->username : QString(); } inline bool PeerData::isVerified() const { return isUser() ? asUser()->isVerified() : isChannel() ? asChannel()->isVerified() : false; } inline bool PeerData::isMegagroup() const { return isChannel() ? asChannel()->isMegagroup() : false; } inline bool PeerData::canWrite() const { return isChannel() ? asChannel()->canWrite() : isChat() ? asChat()->canWrite() : isUser() ? asUser()->canWrite() : false; } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/data/data_peer_values.cpp��������������������������������������0000664�0000000�0000000�00000017733�13262451251�0024356�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "data/data_peer_values.h" #include "lang/lang_keys.h" namespace Data { namespace { constexpr auto kMinOnlineChangeTimeout = TimeMs(1000); constexpr auto kMaxOnlineChangeTimeout = 86400 * TimeMs(1000); constexpr auto kSecondsInDay = 86400; int OnlinePhraseChangeInSeconds(TimeId online, TimeId now) { if (online <= 0) { if (-online > now) { return (-online - now); } return std::numeric_limits<TimeId>::max(); } if (online > now) { return online - now; } const auto minutes = (now - online) / 60; if (minutes < 60) { return (minutes + 1) * 60 - (now - online); } const auto hours = (now - online) / 3600; if (hours < 12) { return (hours + 1) * 3600 - (now - online); } const auto nowFull = ParseDateTime(now); const auto tomorrow = QDateTime(nowFull.date().addDays(1)); return std::max(static_cast<TimeId>(nowFull.secsTo(tomorrow)), 0); } base::optional<QString> OnlineTextSpecial(not_null<UserData*> user) { if (isNotificationsUser(user->id)) { return lang(lng_status_service_notifications); } else if (user->botInfo) { return lang(lng_status_bot); } else if (isServiceUser(user->id)) { return lang(lng_status_support); } return base::none; } base::optional<QString> OnlineTextCommon(TimeId online, TimeId now) { if (online <= 0) { switch (online) { case 0: case -1: return lang(lng_status_offline); case -2: return lang(lng_status_recently); case -3: return lang(lng_status_last_week); case -4: return lang(lng_status_last_month); } return (-online > now) ? lang(lng_status_online) : lang(lng_status_recently); } else if (online > now) { return lang(lng_status_online); } return base::none; } } // namespace inline auto AdminRightsValue(not_null<ChannelData*> channel) { return channel->adminRightsValue(); } inline auto AdminRightsValue( not_null<ChannelData*> channel, MTPDchannelAdminRights::Flags mask) { return FlagsValueWithMask(AdminRightsValue(channel), mask); } inline auto AdminRightValue( not_null<ChannelData*> channel, MTPDchannelAdminRights::Flag flag) { return SingleFlagValue(AdminRightsValue(channel), flag); } inline auto RestrictionsValue(not_null<ChannelData*> channel) { return channel->restrictionsValue(); } inline auto RestrictionsValue( not_null<ChannelData*> channel, MTPDchannelBannedRights::Flags mask) { return FlagsValueWithMask(RestrictionsValue(channel), mask); } inline auto RestrictionValue( not_null<ChannelData*> channel, MTPDchannelBannedRights::Flag flag) { return SingleFlagValue(RestrictionsValue(channel), flag); } rpl::producer<bool> PeerFlagValue( ChatData *chat, MTPDchat_ClientFlag flag) { return PeerFlagValue(chat, static_cast<MTPDchat::Flag>(flag)); } rpl::producer<bool> PeerFlagValue( ChannelData *channel, MTPDchannel_ClientFlag flag) { return PeerFlagValue(channel, static_cast<MTPDchannel::Flag>(flag)); } rpl::producer<bool> CanWriteValue(UserData *user) { using namespace rpl::mappers; return PeerFlagValue(user, MTPDuser::Flag::f_deleted) | rpl::map(!_1); } rpl::producer<bool> CanWriteValue(ChatData *chat) { using namespace rpl::mappers; auto mask = 0 | MTPDchat::Flag::f_deactivated | MTPDchat_ClientFlag::f_forbidden | MTPDchat::Flag::f_left | MTPDchat::Flag::f_kicked; return PeerFlagsValue(chat, mask) | rpl::map(!_1); } rpl::producer<bool> CanWriteValue(ChannelData *channel) { auto mask = 0 | MTPDchannel::Flag::f_left | MTPDchannel_ClientFlag::f_forbidden | MTPDchannel::Flag::f_creator | MTPDchannel::Flag::f_broadcast; return rpl::combine( PeerFlagsValue(channel, mask), AdminRightValue( channel, MTPDchannelAdminRights::Flag::f_post_messages), RestrictionValue( channel, MTPDchannelBannedRights::Flag::f_send_messages), []( MTPDchannel::Flags flags, bool postMessagesRight, bool sendMessagesRestriction) { auto notAmInFlags = 0 | MTPDchannel::Flag::f_left | MTPDchannel_ClientFlag::f_forbidden; return !(flags & notAmInFlags) && (postMessagesRight || (flags & MTPDchannel::Flag::f_creator) || (!(flags & MTPDchannel::Flag::f_broadcast) && !sendMessagesRestriction)); }); } rpl::producer<bool> CanWriteValue(not_null<PeerData*> peer) { if (auto user = peer->asUser()) { return CanWriteValue(user); } else if (auto chat = peer->asChat()) { return CanWriteValue(chat); } else if (auto channel = peer->asChannel()) { return CanWriteValue(channel); } Unexpected("Bad peer value in CanWriteValue()"); } TimeId SortByOnlineValue(not_null<UserData*> user, TimeId now) { if (isServiceUser(user->id) || user->botInfo) { return -1; } const auto online = user->onlineTill; if (online <= 0) { switch (online) { case 0: case -1: return online; case -2: { return now - 3 * kSecondsInDay; } break; case -3: { return now - 7 * kSecondsInDay; } break; case -4: { return now - 30 * kSecondsInDay; } break; } return -online; } return online; } TimeMs OnlineChangeTimeout(TimeId online, TimeId now) { const auto result = OnlinePhraseChangeInSeconds(online, now); Assert(result >= 0); return snap( result * TimeMs(1000), kMinOnlineChangeTimeout, kMaxOnlineChangeTimeout); } TimeMs OnlineChangeTimeout(not_null<UserData*> user, TimeId now) { if (isServiceUser(user->id) || user->botInfo) { return kMaxOnlineChangeTimeout; } return OnlineChangeTimeout(user->onlineTill, now); } QString OnlineText(TimeId online, TimeId now) { if (const auto common = OnlineTextCommon(online, now)) { return *common; } const auto minutes = (now - online) / 60; if (!minutes) { return lang(lng_status_lastseen_now); } else if (minutes < 60) { return lng_status_lastseen_minutes(lt_count, minutes); } const auto hours = (now - online) / 3600; if (hours < 12) { return lng_status_lastseen_hours(lt_count, hours); } const auto onlineFull = ParseDateTime(online); const auto nowFull = ParseDateTime(now); if (onlineFull.date() == nowFull.date()) { const auto onlineTime = onlineFull.time().toString(cTimeFormat()); return lng_status_lastseen_today(lt_time, onlineTime); } else if (onlineFull.date().addDays(1) == nowFull.date()) { const auto onlineTime = onlineFull.time().toString(cTimeFormat()); return lng_status_lastseen_yesterday(lt_time, onlineTime); } const auto date = onlineFull.date().toString(qsl("dd.MM.yy")); return lng_status_lastseen_date(lt_date, date); } QString OnlineText(not_null<UserData*> user, TimeId now) { if (const auto special = OnlineTextSpecial(user)) { return *special; } return OnlineText(user->onlineTill, now); } QString OnlineTextFull(not_null<UserData*> user, TimeId now) { if (const auto special = OnlineTextSpecial(user)) { return *special; } else if (const auto common = OnlineTextCommon(user->onlineTill, now)) { return *common; } const auto onlineFull = ParseDateTime(user->onlineTill); const auto nowFull = ParseDateTime(now); if (onlineFull.date() == nowFull.date()) { const auto onlineTime = onlineFull.time().toString(cTimeFormat()); return lng_status_lastseen_today(lt_time, onlineTime); } else if (onlineFull.date().addDays(1) == nowFull.date()) { const auto onlineTime = onlineFull.time().toString(cTimeFormat()); return lng_status_lastseen_yesterday(lt_time, onlineTime); } const auto date = onlineFull.date().toString(qsl("dd.MM.yy")); const auto time = onlineFull.time().toString(cTimeFormat()); return lng_status_lastseen_date_time(lt_date, date, lt_time, time); } bool OnlineTextActive(TimeId online, TimeId now) { if (online <= 0) { switch (online) { case 0: case -1: case -2: case -3: case -4: return false; } return (-online > now); } return (online > now); } bool OnlineTextActive(not_null<UserData*> user, TimeId now) { if (isServiceUser(user->id) || user->botInfo) { return false; } return OnlineTextActive(user->onlineTill, now); } } // namespace Data �������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/data/data_peer_values.h����������������������������������������0000664�0000000�0000000�00000006544�13262451251�0024021�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/filter.h> #include <rpl/map.h> #include <rpl/combine.h> #include "data/data_peer.h" namespace Data { template <typename ChangeType, typename Error, typename Generator> inline auto FlagsValueWithMask( rpl::producer<ChangeType, Error, Generator> &&value, typename ChangeType::Type mask) { return std::move( value ) | rpl::filter([mask](const ChangeType &change) { return change.diff & mask; }) | rpl::map([mask](const ChangeType &change) { return change.value & mask; }); } template <typename ChangeType, typename Error, typename Generator> inline auto SingleFlagValue( rpl::producer<ChangeType, Error, Generator> &&value, typename ChangeType::Enum flag) { return FlagsValueWithMask( std::move(value), flag ) | rpl::map([](typename ChangeType::Type value) { return !!value; }); } template < typename PeerType, typename ChangeType = typename PeerType::Flags::Change> inline auto PeerFlagsValue(PeerType *peer) { Expects(peer != nullptr); return peer->flagsValue(); } template < typename PeerType, typename ChangeType = typename PeerType::Flags::Change> inline auto PeerFlagsValue( PeerType *peer, typename PeerType::Flags::Type mask) { return FlagsValueWithMask(PeerFlagsValue(peer), mask); } template < typename PeerType, typename ChangeType = typename PeerType::Flags::Change> inline auto PeerFlagValue( PeerType *peer, typename PeerType::Flags::Enum flag) { return SingleFlagValue(PeerFlagsValue(peer), flag); } // //inline auto PeerFlagValue( // UserData *user, // MTPDuser_ClientFlag flag) { // return PeerFlagValue(user, static_cast<MTPDuser::Flag>(flag)); //} rpl::producer<bool> PeerFlagValue( ChatData *chat, MTPDchat_ClientFlag flag); rpl::producer<bool> PeerFlagValue( ChannelData *channel, MTPDchannel_ClientFlag flag); template < typename PeerType, typename = typename PeerType::FullFlags::Change> inline auto PeerFullFlagsValue(PeerType *peer) { Expects(peer != nullptr); return peer->fullFlagsValue(); } template < typename PeerType, typename = typename PeerType::FullFlags::Change> inline auto PeerFullFlagsValue( PeerType *peer, typename PeerType::FullFlags::Type mask) { return FlagsValueWithMask(PeerFullFlagsValue(peer), mask); } template < typename PeerType, typename = typename PeerType::FullFlags::Change> inline auto PeerFullFlagValue( PeerType *peer, typename PeerType::FullFlags::Enum flag) { return SingleFlagValue(PeerFullFlagsValue(peer), flag); } rpl::producer<bool> CanWriteValue(UserData *user); rpl::producer<bool> CanWriteValue(ChatData *chat); rpl::producer<bool> CanWriteValue(ChannelData *channel); rpl::producer<bool> CanWriteValue(not_null<PeerData*> peer); TimeId SortByOnlineValue(not_null<UserData*> user, TimeId now); TimeMs OnlineChangeTimeout(TimeId online, TimeId now); TimeMs OnlineChangeTimeout(not_null<UserData*> user, TimeId now); QString OnlineText(TimeId online, TimeId now); QString OnlineText(not_null<UserData*> user, TimeId now); QString OnlineTextFull(not_null<UserData*> user, TimeId now); bool OnlineTextActive(TimeId online, TimeId now); bool OnlineTextActive(not_null<UserData*> user, TimeId now); } // namespace Data ������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/data/data_photo.cpp��������������������������������������������0000664�0000000�0000000�00000005662�13262451251�0023173�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "data/data_photo.h" #include "data/data_session.h" #include "mainwidget.h" #include "history/history_media_types.h" #include "auth_session.h" #include "messenger.h" PhotoData::PhotoData(const PhotoId &id, const uint64 &access, int32 date, const ImagePtr &thumb, const ImagePtr &medium, const ImagePtr &full) : id(id) , access(access) , date(date) , thumb(thumb) , medium(medium) , full(full) { } void PhotoData::automaticLoad(const HistoryItem *item) { full->automaticLoad(item); } void PhotoData::automaticLoadSettingsChanged() { full->automaticLoadSettingsChanged(); } void PhotoData::download() { full->loadEvenCancelled(); Auth().data().notifyPhotoLayoutChanged(this); } bool PhotoData::loaded() const { bool wasLoading = loading(); if (full->loaded()) { if (wasLoading) { Auth().data().notifyPhotoLayoutChanged(this); } return true; } return false; } bool PhotoData::loading() const { return full->loading(); } bool PhotoData::displayLoading() const { return full->loading() ? full->displayLoading() : (uploading() && !waitingForAlbum()); } void PhotoData::cancel() { full->cancel(); Auth().data().notifyPhotoLayoutChanged(this); } float64 PhotoData::progress() const { if (uploading()) { if (uploadingData->size > 0) { return float64(uploadingData->offset) / uploadingData->size; } return 0; } return full->progress(); } void PhotoData::setWaitingForAlbum() { if (uploading()) { uploadingData->waitingForAlbum = true; } } bool PhotoData::waitingForAlbum() const { return uploading() && uploadingData->waitingForAlbum; } int32 PhotoData::loadOffset() const { return full->loadOffset(); } bool PhotoData::uploading() const { return (uploadingData != nullptr); } void PhotoData::forget() { thumb->forget(); replyPreview->forget(); medium->forget(); full->forget(); } ImagePtr PhotoData::makeReplyPreview() { if (replyPreview->isNull() && !thumb->isNull()) { if (thumb->loaded()) { int w = thumb->width(), h = thumb->height(); if (w <= 0) w = 1; if (h <= 0) h = 1; replyPreview = ImagePtr(w > h ? thumb->pix(w * st::msgReplyBarSize.height() / h, st::msgReplyBarSize.height()) : thumb->pix(st::msgReplyBarSize.height()), "PNG"); } else { thumb->load(); } } return replyPreview; } void PhotoOpenClickHandler::onClickImpl() const { Messenger::Instance().showPhoto(this); } void PhotoSaveClickHandler::onClickImpl() const { auto data = photo(); if (!data->date) return; data->download(); } void PhotoCancelClickHandler::onClickImpl() const { auto data = photo(); if (!data->date) return; if (data->uploading()) { if (const auto item = App::histItemById(context())) { App::main()->cancelUploadLayer(item); } } else { data->cancel(); } } ������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/data/data_photo.h����������������������������������������������0000664�0000000�0000000�00000003772�13262451251�0022640�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "data/data_types.h" class PhotoData { public: PhotoData( const PhotoId &id, const uint64 &access = 0, int32 date = 0, const ImagePtr &thumb = ImagePtr(), const ImagePtr &medium = ImagePtr(), const ImagePtr &full = ImagePtr()); void automaticLoad(const HistoryItem *item); void automaticLoadSettingsChanged(); void download(); bool loaded() const; bool loading() const; bool displayLoading() const; void cancel(); float64 progress() const; int32 loadOffset() const; bool uploading() const; void setWaitingForAlbum(); bool waitingForAlbum() const; void forget(); ImagePtr makeReplyPreview(); PhotoId id; uint64 access; int32 date; ImagePtr thumb, replyPreview; ImagePtr medium; ImagePtr full; PeerData *peer = nullptr; // for chat and channel photos connection // geo, caption std::unique_ptr<Data::UploadState> uploadingData; }; class PhotoClickHandler : public FileClickHandler { public: PhotoClickHandler( not_null<PhotoData*> photo, FullMsgId context = FullMsgId(), PeerData *peer = nullptr) : FileClickHandler(context) , _photo(photo) , _peer(peer) { } not_null<PhotoData*> photo() const { return _photo; } PeerData *peer() const { return _peer; } private: not_null<PhotoData*> _photo; PeerData *_peer = nullptr; }; class PhotoOpenClickHandler : public PhotoClickHandler { public: using PhotoClickHandler::PhotoClickHandler; protected: void onClickImpl() const override; }; class PhotoSaveClickHandler : public PhotoClickHandler { public: using PhotoClickHandler::PhotoClickHandler; protected: void onClickImpl() const override; }; class PhotoCancelClickHandler : public PhotoClickHandler { public: using PhotoClickHandler::PhotoClickHandler; protected: void onClickImpl() const override; }; ������tdesktop-1.2.17/Telegram/SourceFiles/data/data_search_controller.cpp��������������������������������0000664�0000000�0000000�00000024745�13262451251�0025555�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "data/data_search_controller.h" #include "auth_session.h" #include "data/data_session.h" #include "data/data_messages.h" #include "history/history.h" #include "history/history_item.h" namespace Api { namespace { constexpr auto kSharedMediaLimit = 100; constexpr auto kDefaultSearchTimeoutMs = TimeMs(200); } // namespace MTPmessages_Search PrepareSearchRequest( not_null<PeerData*> peer, Storage::SharedMediaType type, const QString &query, MsgId messageId, Data::LoadDirection direction) { const auto filter = [&] { using Type = Storage::SharedMediaType; switch (type) { case Type::Photo: return MTP_inputMessagesFilterPhotos(); case Type::Video: return MTP_inputMessagesFilterVideo(); case Type::PhotoVideo: return MTP_inputMessagesFilterPhotoVideo(); case Type::MusicFile: return MTP_inputMessagesFilterMusic(); case Type::File: return MTP_inputMessagesFilterDocument(); case Type::VoiceFile: return MTP_inputMessagesFilterVoice(); case Type::RoundVoiceFile: return MTP_inputMessagesFilterRoundVoice(); case Type::RoundFile: return MTP_inputMessagesFilterRoundVideo(); case Type::GIF: return MTP_inputMessagesFilterGif(); case Type::Link: return MTP_inputMessagesFilterUrl(); case Type::ChatPhoto: return MTP_inputMessagesFilterChatPhotos(); } return MTP_inputMessagesFilterEmpty(); }(); const auto minId = 0; const auto maxId = 0; const auto limit = messageId ? kSharedMediaLimit : 0; const auto offsetId = [&] { switch (direction) { case Data::LoadDirection::Before: case Data::LoadDirection::Around: return messageId; case Data::LoadDirection::After: return messageId + 1; } Unexpected("Direction in PrepareSearchRequest"); }(); const auto addOffset = [&] { switch (direction) { case Data::LoadDirection::Before: return 0; case Data::LoadDirection::Around: return -limit / 2; case Data::LoadDirection::After: return -limit; } Unexpected("Direction in PrepareSearchRequest"); }(); const auto hash = int32(0); return MTPmessages_Search( MTP_flags(0), peer->input, MTP_string(query), MTP_inputUserEmpty(), filter, MTP_int(0), MTP_int(0), MTP_int(offsetId), MTP_int(addOffset), MTP_int(limit), MTP_int(maxId), MTP_int(minId), MTP_int(hash)); } SearchResult ParseSearchResult( not_null<PeerData*> peer, Storage::SharedMediaType type, MsgId messageId, Data::LoadDirection direction, const MTPmessages_Messages &data) { auto result = SearchResult(); result.noSkipRange = MsgRange{ messageId, messageId }; auto messages = [&] { switch (data.type()) { case mtpc_messages_messages: { auto &d = data.c_messages_messages(); App::feedUsers(d.vusers); App::feedChats(d.vchats); result.fullCount = d.vmessages.v.size(); return &d.vmessages.v; } break; case mtpc_messages_messagesSlice: { auto &d = data.c_messages_messagesSlice(); App::feedUsers(d.vusers); App::feedChats(d.vchats); result.fullCount = d.vcount.v; return &d.vmessages.v; } break; case mtpc_messages_channelMessages: { auto &d = data.c_messages_channelMessages(); if (auto channel = peer->asChannel()) { channel->ptsReceived(d.vpts.v); } else { LOG(("API Error: received messages.channelMessages when " "no channel was passed! (ParseSearchResult)")); } App::feedUsers(d.vusers); App::feedChats(d.vchats); result.fullCount = d.vcount.v; return &d.vmessages.v; } break; case mtpc_messages_messagesNotModified: { LOG(("API Error: received messages.messagesNotModified! " "(ParseSearchResult)")); return (const QVector<MTPMessage>*)nullptr; } break; } Unexpected("messages.Messages type in ParseSearchResult()"); }(); if (!messages) { return result; } auto addType = NewMessageExisting; result.messageIds.reserve(messages->size()); for (auto &message : *messages) { if (auto item = App::histories().addNewMessage(message, addType)) { auto itemId = item->id; if ((type == Storage::SharedMediaType::kCount) || item->sharedMediaTypes().test(type)) { result.messageIds.push_back(itemId); } accumulate_min(result.noSkipRange.from, itemId); accumulate_max(result.noSkipRange.till, itemId); } } if (messageId && result.messageIds.empty()) { result.noSkipRange = [&]() -> MsgRange { switch (direction) { case Data::LoadDirection::Before: // All old loaded. return { 0, result.noSkipRange.till }; case Data::LoadDirection::Around: // All loaded. return { 0, ServerMaxMsgId }; case Data::LoadDirection::After: // All new loaded. return { result.noSkipRange.from, ServerMaxMsgId }; } Unexpected("Direction in ParseSearchResult"); }(); } return result; } SearchController::CacheEntry::CacheEntry(const Query &query) : peerData(App::peer(query.peerId)) , migratedData(query.migratedPeerId ? base::make_optional(Data(App::peer(query.migratedPeerId))) : base::none) { } bool SearchController::hasInCache(const Query &query) const { return query.query.isEmpty() || _cache.contains(query); } void SearchController::setQuery(const Query &query) { if (query.query.isEmpty()) { _cache.clear(); _current = _cache.end(); } else { _current = _cache.find(query); } if (_current == _cache.end()) { _current = _cache.emplace( query, std::make_unique<CacheEntry>(query)).first; } } rpl::producer<SparseIdsMergedSlice> SearchController::idsSlice( SparseIdsMergedSlice::UniversalMsgId aroundId, int limitBefore, int limitAfter) { Expects(_current != _cache.cend()); auto query = (const Query&)_current->first; auto createSimpleViewer = [=]( PeerId peerId, SparseIdsSlice::Key simpleKey, int limitBefore, int limitAfter) { return simpleIdsSlice( peerId, simpleKey, query, limitBefore, limitAfter); }; return SparseIdsMergedSlice::CreateViewer( SparseIdsMergedSlice::Key( query.peerId, query.migratedPeerId, aroundId), limitBefore, limitAfter, std::move(createSimpleViewer)); } rpl::producer<SparseIdsSlice> SearchController::simpleIdsSlice( PeerId peerId, MsgId aroundId, const Query &query, int limitBefore, int limitAfter) { Expects(peerId != 0); Expects(IsServerMsgId(aroundId) || (aroundId == 0)); Expects((aroundId != 0) || (limitBefore == 0 && limitAfter == 0)); Expects((query.peerId == peerId) || (query.migratedPeerId == peerId)); auto it = _cache.find(query); if (it == _cache.end()) { return [=](auto) { return rpl::lifetime(); }; } auto listData = (peerId == query.peerId) ? &it->second->peerData : &*it->second->migratedData; return [=](auto consumer) { auto lifetime = rpl::lifetime(); auto builder = lifetime.make_state<SparseIdsSliceBuilder>( aroundId, limitBefore, limitAfter); builder->insufficientAround( ) | rpl::start_with_next([=]( const SparseIdsSliceBuilder::AroundData &data) { requestMore(data, query, listData); }, lifetime); auto pushNextSnapshot = [=] { consumer.put_next(builder->snapshot()); }; listData->list.sliceUpdated( ) | rpl::filter([=](const SliceUpdate &update) { return builder->applyUpdate(update); }) | rpl::start_with_next(pushNextSnapshot, lifetime); Auth().data().itemRemoved( ) | rpl::filter([=](not_null<const HistoryItem*> item) { return (item->history()->peer->id == peerId); }) | rpl::filter([=](not_null<const HistoryItem*> item) { return builder->removeOne(item->id); }) | rpl::start_with_next(pushNextSnapshot, lifetime); Auth().data().historyCleared( ) | rpl::filter([=](not_null<const History*> history) { return (history->peer->id == peerId); }) | rpl::filter([=] { return builder->removeAll(); }) | rpl::start_with_next(pushNextSnapshot, lifetime); using Result = Storage::SparseIdsListResult; listData->list.query(Storage::SparseIdsListQuery( aroundId, limitBefore, limitAfter )) | rpl::filter([=](const Result &result) { return builder->applyInitial(result); }) | rpl::start_with_next_done( pushNextSnapshot, [=] { builder->checkInsufficient(); }, lifetime); return lifetime; }; } auto SearchController::saveState() -> SavedState { auto result = SavedState(); if (_current != _cache.end()) { result.query = _current->first; result.peerList = std::move(_current->second->peerData.list); if (auto &migrated = _current->second->migratedData) { result.migratedList = std::move(migrated->list); } } return result; } void SearchController::restoreState(SavedState &&state) { if (!state.query.peerId) { return; } auto it = _cache.find(state.query); if (it == _cache.end()) { it = _cache.emplace( state.query, std::make_unique<CacheEntry>(state.query)).first; } auto replace = Data(it->second->peerData.peer); replace.list = std::move(state.peerList); it->second->peerData = std::move(replace); if (auto &migrated = state.migratedList) { Assert(it->second->migratedData.has_value()); auto replace = Data(it->second->migratedData->peer); replace.list = std::move(*migrated); it->second->migratedData = std::move(replace); } _current = it; } void SearchController::requestMore( const SparseIdsSliceBuilder::AroundData &key, const Query &query, Data *listData) { if (listData->requests.contains(key)) { return; } auto requestId = request(PrepareSearchRequest( listData->peer, query.type, query.query, key.aroundId, key.direction) ).done([=](const MTPmessages_Messages &result) { listData->requests.remove(key); auto parsed = ParseSearchResult( listData->peer, query.type, key.aroundId, key.direction, result); listData->list.addSlice( std::move(parsed.messageIds), parsed.noSkipRange, parsed.fullCount); }).send(); listData->requests.emplace(key, [=] { request(requestId).cancel(); }); } DelayedSearchController::DelayedSearchController() { _timer.setCallback([this] { setQueryFast(_nextQuery); }); } void DelayedSearchController::setQuery(const Query &query) { setQuery(query, kDefaultSearchTimeoutMs); } void DelayedSearchController::setQuery( const Query &query, TimeMs delay) { if (currentQuery() == query) { _timer.cancel(); return; } if (_controller.hasInCache(query)) { setQueryFast(query); } else { _nextQuery = query; _timer.callOnce(delay); } } void DelayedSearchController::setQueryFast(const Query &query) { _controller.setQuery(query); _currentQueryChanges.fire_copy(query.query); } } // namespace Api ���������������������������tdesktop-1.2.17/Telegram/SourceFiles/data/data_search_controller.h����������������������������������0000664�0000000�0000000�00000007420�13262451251�0025211�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "mtproto/sender.h" #include "data/data_sparse_ids.h" #include "storage/storage_sparse_ids_list.h" #include "storage/storage_shared_media.h" #include "base/value_ordering.h" #include "base/timer.h" namespace Data { enum class LoadDirection : char; } // namespace Data namespace Api { struct SearchResult { std::vector<MsgId> messageIds; MsgRange noSkipRange; int fullCount = 0; }; MTPmessages_Search PrepareSearchRequest( not_null<PeerData*> peer, Storage::SharedMediaType type, const QString &query, MsgId messageId, Data::LoadDirection direction); SearchResult ParseSearchResult( not_null<PeerData*> peer, Storage::SharedMediaType type, MsgId messageId, Data::LoadDirection direction, const MTPmessages_Messages &data); class SearchController : private MTP::Sender { public: using IdsList = Storage::SparseIdsList; struct Query { using MediaType = Storage::SharedMediaType; PeerId peerId = 0; PeerId migratedPeerId = 0; MediaType type = MediaType::kCount; QString query; // from_id, min_date, max_date friend inline auto value_ordering_helper(const Query &value) { return std::tie( value.peerId, value.migratedPeerId, value.type, value.query); } }; struct SavedState { Query query; IdsList peerList; base::optional<IdsList> migratedList; }; void setQuery(const Query &query); bool hasInCache(const Query &query) const; Query query() const { Expects(_current != _cache.cend()); return _current->first; } rpl::producer<SparseIdsMergedSlice> idsSlice( SparseIdsMergedSlice::UniversalMsgId aroundId, int limitBefore, int limitAfter); SavedState saveState(); void restoreState(SavedState &&state); private: struct Data { explicit Data(not_null<PeerData*> peer) : peer(peer) { } not_null<PeerData*> peer; IdsList list; base::flat_map< SparseIdsSliceBuilder::AroundData, rpl::lifetime> requests; }; using SliceUpdate = Storage::SparseIdsSliceUpdate; struct CacheEntry { CacheEntry(const Query &query); Data peerData; base::optional<Data> migratedData; }; struct CacheLess { inline bool operator()(const Query &a, const Query &b) const { return (a < b); } }; using Cache = base::flat_map< Query, std::unique_ptr<CacheEntry>, CacheLess>; rpl::producer<SparseIdsSlice> simpleIdsSlice( PeerId peerId, MsgId aroundId, const Query &query, int limitBefore, int limitAfter); void requestMore( const SparseIdsSliceBuilder::AroundData &key, const Query &query, Data *listData); Cache _cache; Cache::iterator _current = _cache.end(); }; class DelayedSearchController { public: DelayedSearchController(); using Query = SearchController::Query; using SavedState = SearchController::SavedState; void setQuery(const Query &query); void setQuery(const Query &query, TimeMs delay); void setQueryFast(const Query &query); Query currentQuery() const { return _controller.query(); } rpl::producer<SparseIdsMergedSlice> idsSlice( SparseIdsMergedSlice::UniversalMsgId aroundId, int limitBefore, int limitAfter) { return _controller.idsSlice( aroundId, limitBefore, limitAfter); } rpl::producer<QString> currentQueryValue() const { return _currentQueryChanges.events_starting_with( currentQuery().query); } SavedState saveState() { return _controller.saveState(); } void restoreState(SavedState &&state) { _controller.restoreState(std::move(state)); } private: SearchController _controller; Query _nextQuery; base::Timer _timer; rpl::event_stream<QString> _currentQueryChanges; }; } // namespace Api ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/data/data_session.cpp������������������������������������������0000664�0000000�0000000�00000117237�13262451251�0023527�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "data/data_session.h" #include "observer_peer.h" #include "auth_session.h" #include "apiwrap.h" #include "history/history.h" #include "history/history_item_components.h" #include "history/history_media.h" #include "history/view/history_view_element.h" #include "inline_bots/inline_bot_layout_item.h" #include "storage/localstorage.h" #include "data/data_media_types.h" #include "data/data_feed.h" #include "data/data_photo.h" #include "data/data_document.h" #include "data/data_web_page.h" #include "data/data_game.h" namespace Data { namespace { using ViewElement = HistoryView::Element; // s: box 100x100 // m: box 320x320 // x: box 800x800 // y: box 1280x1280 // w: box 2560x2560 // if loading this fix HistoryPhoto::updateFrom // a: crop 160x160 // b: crop 320x320 // c: crop 640x640 // d: crop 1280x1280 const auto ThumbLevels = QByteArray::fromRawData("sambcxydw", 9); const auto MediumLevels = QByteArray::fromRawData("mbcxasydw", 9); const auto FullLevels = QByteArray::fromRawData("yxwmsdcba", 9); void UpdateImage(ImagePtr &old, ImagePtr now) { if (now->isNull()) { return; } if (old->isNull()) { old = now; } else if (const auto delayed = old->toDelayedStorageImage()) { const auto location = now->location(); if (!location.isNull()) { delayed->setStorageLocation(location); } } } } // namespace Session::Session(not_null<AuthSession*> session) : _session(session) , _groups(this) { setupContactViewsViewer(); setupChannelLeavingViewer(); } void Session::setupContactViewsViewer() { Notify::PeerUpdateViewer( Notify::PeerUpdate::Flag::UserIsContact ) | rpl::map([](const Notify::PeerUpdate &update) { return update.peer->asUser(); }) | rpl::filter([](UserData *user) { return user != nullptr; }) | rpl::start_with_next([=](not_null<UserData*> user) { userIsContactUpdated(user); }, _lifetime); } void Session::setupChannelLeavingViewer() { Notify::PeerUpdateViewer( Notify::PeerUpdate::Flag::ChannelAmIn ) | rpl::map([](const Notify::PeerUpdate &update) { return update.peer->asChannel(); }) | rpl::filter([](ChannelData *channel) { return (channel != nullptr) && !(channel->amIn()) && (channel->feed() != nullptr); }) | rpl::start_with_next([=](not_null<ChannelData*> channel) { channel->clearFeed(); }, _lifetime); } Session::~Session() = default; template <typename Method> void Session::enumerateItemViews( not_null<const HistoryItem*> item, Method method) { if (const auto i = _views.find(item); i != _views.end()) { for (const auto view : i->second) { method(view); } } } void Session::photoLoadSettingsChanged() { for (const auto &[id, photo] : _photos) { photo->automaticLoadSettingsChanged(); } } void Session::voiceLoadSettingsChanged() { for (const auto &[id, document] : _documents) { if (document->isVoiceMessage()) { document->automaticLoadSettingsChanged(); } } } void Session::animationLoadSettingsChanged() { for (const auto &[id, document] : _documents) { if (document->isAnimation()) { document->automaticLoadSettingsChanged(); } } } void Session::notifyPhotoLayoutChanged(not_null<const PhotoData*> photo) { if (const auto i = _photoItems.find(photo); i != end(_photoItems)) { for (const auto item : i->second) { notifyItemLayoutChange(item); } } } void Session::notifyDocumentLayoutChanged( not_null<const DocumentData*> document) { const auto i = _documentItems.find(document); if (i != end(_documentItems)) { for (const auto item : i->second) { notifyItemLayoutChange(item); } } if (const auto items = InlineBots::Layout::documentItems()) { if (const auto i = items->find(document); i != items->end()) { for (const auto item : i->second) { item->layoutChanged(); } } } } void Session::requestDocumentViewRepaint( not_null<const DocumentData*> document) { const auto i = _documentItems.find(document); if (i != end(_documentItems)) { for (const auto item : i->second) { requestItemRepaint(item); } } } void Session::markMediaRead(not_null<const DocumentData*> document) { const auto i = _documentItems.find(document); if (i != end(_documentItems)) { _session->api().markMediaRead({ begin(i->second), end(i->second) }); } } void Session::notifyItemLayoutChange(not_null<const HistoryItem*> item) { _itemLayoutChanges.fire_copy(item); enumerateItemViews(item, [&](not_null<ViewElement*> view) { notifyViewLayoutChange(view); }); } rpl::producer<not_null<const HistoryItem*>> Session::itemLayoutChanged() const { return _itemLayoutChanges.events(); } void Session::notifyViewLayoutChange(not_null<const ViewElement*> view) { _viewLayoutChanges.fire_copy(view); } rpl::producer<not_null<const ViewElement*>> Session::viewLayoutChanged() const { return _viewLayoutChanges.events(); } void Session::notifyItemIdChange(IdChange event) { _itemIdChanges.fire_copy(event); const auto refreshViewDataId = [](not_null<ViewElement*> view) { view->refreshDataId(); }; enumerateItemViews(event.item, refreshViewDataId); if (const auto group = Auth().data().groups().find(event.item)) { const auto leader = group->items.back(); if (leader != event.item) { enumerateItemViews(leader, refreshViewDataId); } } } rpl::producer<Session::IdChange> Session::itemIdChanged() const { return _itemIdChanges.events(); } void Session::requestItemRepaint(not_null<const HistoryItem*> item) { _itemRepaintRequest.fire_copy(item); enumerateItemViews(item, [&](not_null<const ViewElement*> view) { requestViewRepaint(view); }); } rpl::producer<not_null<const HistoryItem*>> Session::itemRepaintRequest() const { return _itemRepaintRequest.events(); } void Session::requestViewRepaint(not_null<const ViewElement*> view) { _viewRepaintRequest.fire_copy(view); } rpl::producer<not_null<const ViewElement*>> Session::viewRepaintRequest() const { return _viewRepaintRequest.events(); } void Session::requestItemResize(not_null<const HistoryItem*> item) { _itemResizeRequest.fire_copy(item); enumerateItemViews(item, [&](not_null<ViewElement*> view) { requestViewResize(view); }); } rpl::producer<not_null<const HistoryItem*>> Session::itemResizeRequest() const { return _itemResizeRequest.events(); } void Session::requestViewResize(not_null<ViewElement*> view) { view->setPendingResize(); _viewResizeRequest.fire_copy(view); notifyViewLayoutChange(view); } rpl::producer<not_null<ViewElement*>> Session::viewResizeRequest() const { return _viewResizeRequest.events(); } void Session::requestItemViewRefresh(not_null<HistoryItem*> item) { if (const auto view = item->mainView()) { view->setPendingResize(); } _itemViewRefreshRequest.fire_copy(item); } rpl::producer<not_null<HistoryItem*>> Session::itemViewRefreshRequest() const { return _itemViewRefreshRequest.events(); } void Session::requestItemTextRefresh(not_null<HistoryItem*> item) { if (const auto i = _views.find(item); i != _views.end()) { for (const auto view : i->second) { if (const auto media = view->media()) { media->parentTextUpdated(); } } } } void Session::requestAnimationPlayInline(not_null<HistoryItem*> item) { _animationPlayInlineRequest.fire_copy(item); } rpl::producer<not_null<HistoryItem*>> Session::animationPlayInlineRequest() const { return _animationPlayInlineRequest.events(); } void Session::notifyItemRemoved(not_null<const HistoryItem*> item) { _itemRemoved.fire_copy(item); groups().unregisterMessage(item); } rpl::producer<not_null<const HistoryItem*>> Session::itemRemoved() const { return _itemRemoved.events(); } void Session::notifyViewRemoved(not_null<const ViewElement*> view) { _viewRemoved.fire_copy(view); } rpl::producer<not_null<const ViewElement*>> Session::viewRemoved() const { return _viewRemoved.events(); } void Session::notifyHistoryUnloaded(not_null<const History*> history) { _historyUnloaded.fire_copy(history); } rpl::producer<not_null<const History*>> Session::historyUnloaded() const { return _historyUnloaded.events(); } void Session::notifyHistoryCleared(not_null<const History*> history) { _historyCleared.fire_copy(history); } rpl::producer<not_null<const History*>> Session::historyCleared() const { return _historyCleared.events(); } void Session::notifyHistoryChangeDelayed(not_null<History*> history) { history->setHasPendingResizedItems(); _historiesChanged.insert(history); } rpl::producer<not_null<History*>> Session::historyChanged() const { return _historyChanged.events(); } void Session::sendHistoryChangeNotifications() { for (const auto history : base::take(_historiesChanged)) { _historyChanged.fire_copy(history); } } void Session::removeMegagroupParticipant( not_null<ChannelData*> channel, not_null<UserData*> user) { _megagroupParticipantRemoved.fire({ channel, user }); } auto Session::megagroupParticipantRemoved() const -> rpl::producer<MegagroupParticipant> { return _megagroupParticipantRemoved.events(); } rpl::producer<not_null<UserData*>> Session::megagroupParticipantRemoved( not_null<ChannelData*> channel) const { return megagroupParticipantRemoved( ) | rpl::filter([channel](auto updateChannel, auto user) { return (updateChannel == channel); }) | rpl::map([](auto updateChannel, auto user) { return user; }); } void Session::addNewMegagroupParticipant( not_null<ChannelData*> channel, not_null<UserData*> user) { _megagroupParticipantAdded.fire({ channel, user }); } auto Session::megagroupParticipantAdded() const -> rpl::producer<MegagroupParticipant> { return _megagroupParticipantAdded.events(); } rpl::producer<not_null<UserData*>> Session::megagroupParticipantAdded( not_null<ChannelData*> channel) const { return megagroupParticipantAdded( ) | rpl::filter([channel](auto updateChannel, auto user) { return (updateChannel == channel); }) | rpl::map([](auto updateChannel, auto user) { return user; }); } void Session::notifyFeedUpdated( not_null<Feed*> feed, FeedUpdateFlag update) { _feedUpdates.fire({ feed, update }); } rpl::producer<FeedUpdate> Session::feedUpdated() const { return _feedUpdates.events(); } void Session::notifyStickersUpdated() { _stickersUpdated.fire({}); } rpl::producer<> Session::stickersUpdated() const { return _stickersUpdated.events(); } void Session::notifySavedGifsUpdated() { _savedGifsUpdated.fire({}); } rpl::producer<> Session::savedGifsUpdated() const { return _savedGifsUpdated.events(); } void Session::userIsContactUpdated(not_null<UserData*> user) { const auto i = _contactViews.find(peerToUser(user->id)); if (i != _contactViews.end()) { for (const auto view : i->second) { requestViewResize(view); } } } HistoryItemsList Session::idsToItems( const MessageIdsList &ids) const { return ranges::view::all( ids ) | ranges::view::transform([](const FullMsgId &fullId) { return App::histItemById(fullId); }) | ranges::view::filter([](HistoryItem *item) { return item != nullptr; }) | ranges::view::transform([](HistoryItem *item) { return not_null<HistoryItem*>(item); }) | ranges::to_vector; } MessageIdsList Session::itemsToIds( const HistoryItemsList &items) const { return ranges::view::all( items ) | ranges::view::transform([](not_null<HistoryItem*> item) { return item->fullId(); }) | ranges::to_vector; } MessageIdsList Session::itemOrItsGroup(not_null<HistoryItem*> item) const { if (const auto group = groups().find(item)) { return itemsToIds(group->items); } return { 1, item->fullId() }; } void Session::setPinnedDialog(const Dialogs::Key &key, bool pinned) { setIsPinned(key, pinned); } void Session::applyPinnedDialogs(const QVector<MTPDialog> &list) { clearPinnedDialogs(); for (auto i = list.size(); i != 0;) { const auto &dialog = list[--i]; switch (dialog.type()) { case mtpc_dialog: { const auto &dialogData = dialog.c_dialog(); if (const auto peer = peerFromMTP(dialogData.vpeer)) { setPinnedDialog(App::history(peer), true); } } break; //case mtpc_dialogFeed: { // #feed // const auto &feedData = dialog.c_dialogFeed(); // const auto feedId = feedData.vfeed_id.v; // setPinnedDialog(feed(feedId), true); //} break; default: Unexpected("Type in ApiWrap::applyDialogsPinned."); } } } void Session::applyPinnedDialogs(const QVector<MTPDialogPeer> &list) { clearPinnedDialogs(); for (auto i = list.size(); i != 0;) { const auto &dialogPeer = list[--i]; switch (dialogPeer.type()) { case mtpc_dialogPeer: { const auto &peerData = dialogPeer.c_dialogPeer(); if (const auto peerId = peerFromMTP(peerData.vpeer)) { setPinnedDialog(App::history(peerId), true); } } break; //case mtpc_dialogPeerFeed: { // #feed // const auto &feedData = dialogPeer.c_dialogPeerFeed(); // const auto feedId = feedData.vfeed_id.v; // setPinnedDialog(feed(feedId), true); //} break; } } } int Session::pinnedDialogsCount() const { return _pinnedDialogs.size(); } const std::deque<Dialogs::Key> &Session::pinnedDialogsOrder() const { return _pinnedDialogs; } void Session::clearPinnedDialogs() { while (!_pinnedDialogs.empty()) { setPinnedDialog(_pinnedDialogs.back(), false); } } void Session::reorderTwoPinnedDialogs( const Dialogs::Key &key1, const Dialogs::Key &key2) { const auto &order = pinnedDialogsOrder(); const auto index1 = ranges::find(order, key1) - begin(order); const auto index2 = ranges::find(order, key2) - begin(order); Assert(index1 >= 0 && index1 < order.size()); Assert(index2 >= 0 && index2 < order.size()); Assert(index1 != index2); std::swap(_pinnedDialogs[index1], _pinnedDialogs[index2]); key1.entry()->cachePinnedIndex(index2 + 1); key2.entry()->cachePinnedIndex(index1 + 1); } void Session::setIsPinned(const Dialogs::Key &key, bool pinned) { const auto already = ranges::find(_pinnedDialogs, key); if (pinned) { if (already != end(_pinnedDialogs)) { auto saved = std::move(*already); const auto alreadyIndex = already - end(_pinnedDialogs); const auto count = int(size(_pinnedDialogs)); Assert(alreadyIndex < count); for (auto index = alreadyIndex + 1; index != count; ++index) { _pinnedDialogs[index - 1] = std::move(_pinnedDialogs[index]); _pinnedDialogs[index - 1].entry()->cachePinnedIndex(index); } _pinnedDialogs.back() = std::move(saved); _pinnedDialogs.back().entry()->cachePinnedIndex(count); } else { _pinnedDialogs.push_back(key); if (_pinnedDialogs.size() > Global::PinnedDialogsCountMax()) { _pinnedDialogs.front().entry()->cachePinnedIndex(0); _pinnedDialogs.pop_front(); auto index = 0; for (const auto &pinned : _pinnedDialogs) { pinned.entry()->cachePinnedIndex(++index); } } else { key.entry()->cachePinnedIndex(_pinnedDialogs.size()); } } } else if (!pinned && already != end(_pinnedDialogs)) { key.entry()->cachePinnedIndex(0); _pinnedDialogs.erase(already); auto index = 0; for (const auto &pinned : _pinnedDialogs) { pinned.entry()->cachePinnedIndex(++index); } } } not_null<PhotoData*> Session::photo(PhotoId id) { auto i = _photos.find(id); if (i == _photos.end()) { i = _photos.emplace(id, std::make_unique<PhotoData>(id)).first; } return i->second.get(); } not_null<PhotoData*> Session::photo(const MTPPhoto &data) { switch (data.type()) { case mtpc_photo: return photo(data.c_photo()); case mtpc_photoEmpty: return photo(data.c_photoEmpty().vid.v); } Unexpected("Type in Session::photo()."); } not_null<PhotoData*> Session::photo(const MTPDphoto &data) { const auto result = photo(data.vid.v); photoApplyFields(result, data); return result; } not_null<PhotoData*> Session::photo( const MTPPhoto &data, const PreparedPhotoThumbs &thumbs) { auto thumb = (const QPixmap*)nullptr; auto medium = (const QPixmap*)nullptr; auto full = (const QPixmap*)nullptr; auto thumbLevel = -1; auto mediumLevel = -1; auto fullLevel = -1; for (auto i = thumbs.cbegin(), e = thumbs.cend(); i != e; ++i) { const auto newThumbLevel = ThumbLevels.indexOf(i.key()); const auto newMediumLevel = MediumLevels.indexOf(i.key()); const auto newFullLevel = FullLevels.indexOf(i.key()); if (newThumbLevel < 0 || newMediumLevel < 0 || newFullLevel < 0) { continue; } if (thumbLevel < 0 || newThumbLevel < thumbLevel) { thumbLevel = newThumbLevel; thumb = &i.value(); } if (mediumLevel < 0 || newMediumLevel < mediumLevel) { mediumLevel = newMediumLevel; medium = &i.value(); } if (fullLevel < 0 || newFullLevel < fullLevel) { fullLevel = newFullLevel; full = &i.value(); } } if (!thumb || !medium || !full) { return photo(0); } switch (data.type()) { case mtpc_photo: return photo( data.c_photo().vid.v, data.c_photo().vaccess_hash.v, data.c_photo().vdate.v, ImagePtr(*thumb, "JPG"), ImagePtr(*medium, "JPG"), ImagePtr(*full, "JPG")); case mtpc_photoEmpty: return photo(data.c_photoEmpty().vid.v); } Unexpected("Type in Session::photo() with prepared thumbs."); } not_null<PhotoData*> Session::photo( PhotoId id, const uint64 &access, TimeId date, const ImagePtr &thumb, const ImagePtr &medium, const ImagePtr &full) { const auto result = photo(id); photoApplyFields( result, access, date, thumb, medium, full); return result; } void Session::photoConvert( not_null<PhotoData*> original, const MTPPhoto &data) { const auto id = [&] { switch (data.type()) { case mtpc_photo: return data.c_photo().vid.v; case mtpc_photoEmpty: return data.c_photoEmpty().vid.v; } Unexpected("Type in Session::photoConvert()."); }(); if (original->id != id) { auto i = _photos.find(id); if (i == _photos.end()) { const auto j = _photos.find(original->id); Assert(j != _photos.end()); auto owned = std::move(j->second); _photos.erase(j); i = _photos.emplace(id, std::move(owned)).first; } original->id = id; original->uploadingData = nullptr; if (i->second.get() != original) { photoApplyFields(i->second.get(), data); } } photoApplyFields(original, data); } PhotoData *Session::photoFromWeb( const MTPWebDocument &data, ImagePtr thumb) { const auto full = ImagePtr(data); if (full->isNull()) { return nullptr; } const auto width = full->width(); const auto height = full->height(); if (thumb->isNull()) { auto thumbsize = shrinkToKeepAspect(width, height, 100, 100); thumb = ImagePtr(thumbsize.width(), thumbsize.height()); } auto mediumsize = shrinkToKeepAspect(width, height, 320, 320); auto medium = ImagePtr(mediumsize.width(), mediumsize.height()); return photo( rand_value<PhotoId>(), uint64(0), unixtime(), thumb, medium, full); } void Session::photoApplyFields( not_null<PhotoData*> photo, const MTPPhoto &data) { if (data.type() == mtpc_photo) { photoApplyFields(photo, data.c_photo()); } } void Session::photoApplyFields( not_null<PhotoData*> photo, const MTPDphoto &data) { auto thumb = (const MTPPhotoSize*)nullptr; auto medium = (const MTPPhotoSize*)nullptr; auto full = (const MTPPhotoSize*)nullptr; auto thumbLevel = -1; auto mediumLevel = -1; auto fullLevel = -1; for (const auto &sizeData : data.vsizes.v) { const auto sizeLetter = [&] { switch (sizeData.type()) { case mtpc_photoSizeEmpty: return char(0); case mtpc_photoSize: { const auto &data = sizeData.c_photoSize(); return data.vtype.v.isEmpty() ? char(0) : data.vtype.v[0]; } break; case mtpc_photoCachedSize: { const auto &data = sizeData.c_photoCachedSize(); return data.vtype.v.isEmpty() ? char(0) : data.vtype.v[0]; } break; } Unexpected("Type in photo size."); }(); if (!sizeLetter) continue; const auto newThumbLevel = ThumbLevels.indexOf(sizeLetter); const auto newMediumLevel = MediumLevels.indexOf(sizeLetter); const auto newFullLevel = FullLevels.indexOf(sizeLetter); if (newThumbLevel < 0 || newMediumLevel < 0 || newFullLevel < 0) { continue; } if (thumbLevel < 0 || newThumbLevel < thumbLevel) { thumbLevel = newThumbLevel; thumb = &sizeData; } if (mediumLevel < 0 || newMediumLevel < mediumLevel) { mediumLevel = newMediumLevel; medium = &sizeData; } if (fullLevel < 0 || newFullLevel < fullLevel) { fullLevel = newFullLevel; full = &sizeData; } } if (thumb && medium && full) { photoApplyFields( photo, data.vaccess_hash.v, data.vdate.v, App::image(*thumb), App::image(*medium), App::image(*full)); } } void Session::photoApplyFields( not_null<PhotoData*> photo, const uint64 &access, TimeId date, const ImagePtr &thumb, const ImagePtr &medium, const ImagePtr &full) { if (!date) { return; } photo->access = access; photo->date = date; UpdateImage(photo->thumb, thumb); UpdateImage(photo->medium, medium); UpdateImage(photo->full, full); } not_null<DocumentData*> Session::document(DocumentId id) { auto i = _documents.find(id); if (i == _documents.cend()) { i = _documents.emplace( id, std::make_unique<DocumentData>(id, _session)).first; } return i->second.get(); } not_null<DocumentData*> Session::document(const MTPDocument &data) { switch (data.type()) { case mtpc_document: return document(data.c_document()); case mtpc_documentEmpty: return document(data.c_documentEmpty().vid.v); } Unexpected("Type in Session::document()."); } not_null<DocumentData*> Session::document(const MTPDdocument &data) { const auto result = document(data.vid.v); documentApplyFields(result, data); return result; } not_null<DocumentData*> Session::document( const MTPdocument &data, const QPixmap &thumb) { switch (data.type()) { case mtpc_documentEmpty: return document(data.c_documentEmpty().vid.v); case mtpc_document: { const auto &fields = data.c_document(); return document( fields.vid.v, fields.vaccess_hash.v, fields.vversion.v, fields.vdate.v, fields.vattributes.v, qs(fields.vmime_type), ImagePtr(thumb, "JPG"), fields.vdc_id.v, fields.vsize.v, StorageImageLocation()); } break; } Unexpected("Type in Session::document() with thumb."); } not_null<DocumentData*> Session::document( DocumentId id, const uint64 &access, int32 version, TimeId date, const QVector<MTPDocumentAttribute> &attributes, const QString &mime, const ImagePtr &thumb, int32 dc, int32 size, const StorageImageLocation &thumbLocation) { const auto result = document(id); documentApplyFields( result, access, version, date, attributes, mime, thumb, dc, size, thumbLocation); return result; } void Session::documentConvert( not_null<DocumentData*> original, const MTPDocument &data) { const auto id = [&] { switch (data.type()) { case mtpc_document: return data.c_document().vid.v; case mtpc_documentEmpty: return data.c_documentEmpty().vid.v; } Unexpected("Type in Session::documentConvert()."); }(); const auto oldKey = original->mediaKey(); const auto idChanged = (original->id != id); const auto sentSticker = idChanged && (original->sticker() != nullptr); if (idChanged) { auto i = _documents.find(id); if (i == _documents.end()) { const auto j = _documents.find(original->id); Assert(j != _documents.end()); auto owned = std::move(j->second); _documents.erase(j); i = _documents.emplace(id, std::move(owned)).first; } original->id = id; original->status = FileReady; original->uploadingData = nullptr; if (i->second.get() != original) { documentApplyFields(i->second.get(), data); } } documentApplyFields(original, data); if (idChanged) { const auto newKey = original->mediaKey(); if (oldKey != newKey) { if (original->isVoiceMessage()) { Local::copyAudio(oldKey, newKey); } else if (original->sticker() || original->isAnimation()) { Local::copyStickerImage(oldKey, newKey); } } if (savedGifs().indexOf(original) >= 0) { Local::writeSavedGifs(); } } } DocumentData *Session::documentFromWeb( const MTPWebDocument &data, ImagePtr thumb) { switch (data.type()) { case mtpc_webDocument: return documentFromWeb(data.c_webDocument(), thumb); case mtpc_webDocumentNoProxy: return documentFromWeb(data.c_webDocumentNoProxy(), thumb); } Unexpected("Type in Session::documentFromWeb."); } DocumentData *Session::documentFromWeb( const MTPDwebDocument &data, ImagePtr thumb) { const auto result = document( rand_value<DocumentId>(), uint64(0), int32(0), unixtime(), data.vattributes.v, data.vmime_type.v, thumb, MTP::maindc(), int32(0), // data.vsize.v StorageImageLocation()); result->setWebLocation(WebFileLocation( data.vdc_id.v, data.vurl.v, data.vaccess_hash.v)); return result; } DocumentData *Session::documentFromWeb( const MTPDwebDocumentNoProxy &data, ImagePtr thumb) { const auto result = document( rand_value<DocumentId>(), uint64(0), int32(0), unixtime(), data.vattributes.v, data.vmime_type.v, thumb, MTP::maindc(), int32(0), // data.vsize.v StorageImageLocation()); result->setContentUrl(qs(data.vurl)); return result; } void Session::documentApplyFields( not_null<DocumentData*> document, const MTPDocument &data) { if (data.type() == mtpc_document) { documentApplyFields(document, data.c_document()); } } void Session::documentApplyFields( not_null<DocumentData*> document, const MTPDdocument &data) { documentApplyFields( document, data.vaccess_hash.v, data.vversion.v, data.vdate.v, data.vattributes.v, qs(data.vmime_type), App::image(data.vthumb), data.vdc_id.v, data.vsize.v, StorageImageLocation::FromMTP(data.vthumb)); } void Session::documentApplyFields( not_null<DocumentData*> document, const uint64 &access, int32 version, TimeId date, const QVector<MTPDocumentAttribute> &attributes, const QString &mime, const ImagePtr &thumb, int32 dc, int32 size, const StorageImageLocation &thumbLocation) { if (!date) { return; } document->setattributes(attributes); document->setRemoteVersion(version); if (dc != 0 && access != 0) { document->setRemoteLocation(dc, access); } document->date = date; document->setMimeString(mime); if (!thumb->isNull() && (document->thumb->isNull() || document->thumb->width() < thumb->width() || document->thumb->height() < thumb->height())) { document->thumb = thumb; } document->size = size; document->recountIsImage(); if (document->sticker() && document->sticker()->loc.isNull() && !thumbLocation.isNull()) { document->sticker()->loc = thumbLocation; } } not_null<WebPageData*> Session::webpage(WebPageId id) { auto i = _webpages.find(id); if (i == _webpages.cend()) { i = _webpages.emplace(id, std::make_unique<WebPageData>(id)).first; } return i->second.get(); } not_null<WebPageData*> Session::webpage(const MTPWebPage &data) { switch (data.type()) { case mtpc_webPage: return webpage(data.c_webPage()); case mtpc_webPageEmpty: { const auto result = webpage(data.c_webPageEmpty().vid.v); if (result->pendingTill > 0) { result->pendingTill = -1; // failed } return result; } break; case mtpc_webPagePending: return webpage(data.c_webPagePending()); case mtpc_webPageNotModified: LOG(("API Error: " "webPageNotModified is unexpected in Session::webpage().")); return webpage(0); } Unexpected("Type in Session::webpage()."); } not_null<WebPageData*> Session::webpage(const MTPDwebPage &data) { const auto result = webpage(data.vid.v); webpageApplyFields(result, data); return result; } not_null<WebPageData*> Session::webpage(const MTPDwebPagePending &data) { constexpr auto kDefaultPendingTimeout = 60; const auto result = webpage(data.vid.v); webpageApplyFields( result, QString(), QString(), QString(), QString(), QString(), TextWithEntities(), nullptr, nullptr, 0, QString(), data.vdate.v ? data.vdate.v : (unixtime() + kDefaultPendingTimeout)); return result; } not_null<WebPageData*> Session::webpage( WebPageId id, const QString &siteName, const TextWithEntities &content) { return webpage( id, qsl("article"), QString(), QString(), siteName, QString(), content, nullptr, nullptr, 0, QString(), TimeId(0)); } not_null<WebPageData*> Session::webpage( WebPageId id, const QString &type, const QString &url, const QString &displayUrl, const QString &siteName, const QString &title, const TextWithEntities &description, PhotoData *photo, DocumentData *document, int duration, const QString &author, TimeId pendingTill) { const auto result = webpage(id); webpageApplyFields( result, type, url, displayUrl, siteName, title, description, photo, document, duration, author, pendingTill); return result; } void Session::webpageApplyFields( not_null<WebPageData*> page, const MTPDwebPage &data) { auto description = TextWithEntities { data.has_description() ? TextUtilities::Clean(qs(data.vdescription)) : QString() }; const auto siteName = data.has_site_name() ? qs(data.vsite_name) : QString(); auto parseFlags = TextParseLinks | TextParseMultiline | TextParseRichText; if (siteName == qstr("Twitter") || siteName == qstr("Instagram")) { parseFlags |= TextParseHashtags | TextParseMentions; } TextUtilities::ParseEntities(description, parseFlags); const auto pendingTill = TimeId(0); webpageApplyFields( page, data.has_type() ? qs(data.vtype) : qsl("article"), qs(data.vurl), qs(data.vdisplay_url), siteName, data.has_title() ? qs(data.vtitle) : QString(), description, data.has_photo() ? photo(data.vphoto).get() : nullptr, data.has_document() ? document(data.vdocument).get() : nullptr, data.has_duration() ? data.vduration.v : 0, data.has_author() ? qs(data.vauthor) : QString(), pendingTill); } void Session::webpageApplyFields( not_null<WebPageData*> page, const QString &type, const QString &url, const QString &displayUrl, const QString &siteName, const QString &title, const TextWithEntities &description, PhotoData *photo, DocumentData *document, int duration, const QString &author, TimeId pendingTill) { if (!page->pendingTill && pendingTill > 0) { _session->api().requestWebPageDelayed(page); } const auto changed = page->applyChanges( type, url, displayUrl, siteName, title, description, photo, document, duration, author, pendingTill); if (changed) { notifyWebPageUpdateDelayed(page); } } not_null<GameData*> Session::game(GameId id) { auto i = _games.find(id); if (i == _games.cend()) { i = _games.emplace(id, std::make_unique<GameData>(id)).first; } return i->second.get(); } not_null<GameData*> Session::game(const MTPDgame &data) { const auto result = game(data.vid.v); gameApplyFields(result, data); return result; } not_null<GameData*> Session::game( GameId id, const uint64 &accessHash, const QString &shortName, const QString &title, const QString &description, PhotoData *photo, DocumentData *document) { const auto result = game(id); gameApplyFields( result, accessHash, shortName, title, description, photo, document); return result; } void Session::gameConvert( not_null<GameData*> original, const MTPGame &data) { Expects(data.type() == mtpc_game); const auto id = data.c_game().vid.v; if (original->id != id) { auto i = _games.find(id); if (i == _games.end()) { const auto j = _games.find(original->id); Assert(j != _games.end()); auto owned = std::move(j->second); _games.erase(j); i = _games.emplace(id, std::move(owned)).first; } original->id = id; original->accessHash = 0; if (i->second.get() != original) { gameApplyFields(i->second.get(), data.c_game()); } } gameApplyFields(original, data.c_game()); } void Session::gameApplyFields( not_null<GameData*> game, const MTPDgame &data) { gameApplyFields( game, data.vaccess_hash.v, qs(data.vshort_name), qs(data.vtitle), qs(data.vdescription), photo(data.vphoto), data.has_document() ? document(data.vdocument).get() : nullptr); } void Session::gameApplyFields( not_null<GameData*> game, const uint64 &accessHash, const QString &shortName, const QString &title, const QString &description, PhotoData *photo, DocumentData *document) { if (game->accessHash) { return; } game->accessHash = accessHash; game->shortName = TextUtilities::Clean(shortName); game->title = TextUtilities::SingleLine(title); game->description = TextUtilities::Clean(description); game->photo = photo; game->document = document; notifyGameUpdateDelayed(game); } void Session::registerPhotoItem( not_null<const PhotoData*> photo, not_null<HistoryItem*> item) { _photoItems[photo].insert(item); } void Session::unregisterPhotoItem( not_null<const PhotoData*> photo, not_null<HistoryItem*> item) { const auto i = _photoItems.find(photo); if (i != _photoItems.end()) { auto &items = i->second; if (items.remove(item) && items.empty()) { _photoItems.erase(i); } } } void Session::registerDocumentItem( not_null<const DocumentData*> document, not_null<HistoryItem*> item) { _documentItems[document].insert(item); } void Session::unregisterDocumentItem( not_null<const DocumentData*> document, not_null<HistoryItem*> item) { const auto i = _documentItems.find(document); if (i != _documentItems.end()) { auto &items = i->second; if (items.remove(item) && items.empty()) { _documentItems.erase(i); } } } void Session::registerWebPageView( not_null<const WebPageData*> page, not_null<ViewElement*> view) { _webpageViews[page].insert(view); } void Session::unregisterWebPageView( not_null<const WebPageData*> page, not_null<ViewElement*> view) { const auto i = _webpageViews.find(page); if (i != _webpageViews.end()) { auto &items = i->second; if (items.remove(view) && items.empty()) { _webpageViews.erase(i); } } } void Session::registerWebPageItem( not_null<const WebPageData*> page, not_null<HistoryItem*> item) { _webpageItems[page].insert(item); } void Session::unregisterWebPageItem( not_null<const WebPageData*> page, not_null<HistoryItem*> item) { const auto i = _webpageItems.find(page); if (i != _webpageItems.end()) { auto &items = i->second; if (items.remove(item) && items.empty()) { _webpageItems.erase(i); } } } void Session::registerGameView( not_null<const GameData*> game, not_null<ViewElement*> view) { _gameViews[game].insert(view); } void Session::unregisterGameView( not_null<const GameData*> game, not_null<ViewElement*> view) { const auto i = _gameViews.find(game); if (i != _gameViews.end()) { auto &items = i->second; if (items.remove(view) && items.empty()) { _gameViews.erase(i); } } } void Session::registerContactView( UserId contactId, not_null<ViewElement*> view) { if (!contactId) { return; } _contactViews[contactId].insert(view); } void Session::unregisterContactView( UserId contactId, not_null<ViewElement*> view) { if (!contactId) { return; } const auto i = _contactViews.find(contactId); if (i != _contactViews.end()) { auto &items = i->second; if (items.remove(view) && items.empty()) { _contactViews.erase(i); } } } void Session::registerContactItem( UserId contactId, not_null<HistoryItem*> item) { if (!contactId) { return; } const auto contact = App::userLoaded(contactId); const auto canShare = contact ? contact->canShareThisContact() : false; _contactItems[contactId].insert(item); if (contact && canShare != contact->canShareThisContact()) { Notify::peerUpdatedDelayed( contact, Notify::PeerUpdate::Flag::UserCanShareContact); } if (const auto i = _views.find(item); i != _views.end()) { for (const auto view : i->second) { if (const auto media = view->media()) { media->updateSharedContactUserId(contactId); } } } } void Session::unregisterContactItem( UserId contactId, not_null<HistoryItem*> item) { if (!contactId) { return; } const auto contact = App::userLoaded(contactId); const auto canShare = contact ? contact->canShareThisContact() : false; const auto i = _contactItems.find(contactId); if (i != _contactItems.end()) { auto &items = i->second; if (items.remove(item) && items.empty()) { _contactItems.erase(i); } } if (contact && canShare != contact->canShareThisContact()) { Notify::peerUpdatedDelayed( contact, Notify::PeerUpdate::Flag::UserCanShareContact); } } void Session::registerAutoplayAnimation( not_null<::Media::Clip::Reader*> reader, not_null<ViewElement*> view) { _autoplayAnimations.emplace(reader, view); } void Session::unregisterAutoplayAnimation( not_null<::Media::Clip::Reader*> reader) { _autoplayAnimations.remove(reader); } void Session::stopAutoplayAnimations() { for (const auto [reader, view] : base::take(_autoplayAnimations)) { if (const auto media = view->media()) { media->stopAnimation(); } } } HistoryItem *Session::findWebPageItem(not_null<WebPageData*> page) const { const auto i = _webpageItems.find(page); if (i != _webpageItems.end()) { for (const auto item : i->second) { if (IsServerMsgId(item->id)) { return item; } } } return nullptr; } QString Session::findContactPhone(not_null<UserData*> contact) const { const auto result = contact->phone(); return result.isEmpty() ? findContactPhone(contact->bareId()) : App::formatPhone(result); } QString Session::findContactPhone(UserId contactId) const { const auto i = _contactItems.find(contactId); if (i != _contactItems.end()) { if (const auto media = (*begin(i->second))->media()) { if (const auto contact = media->sharedContact()) { return contact->phoneNumber; } } } return QString(); } void Session::notifyWebPageUpdateDelayed(not_null<WebPageData*> page) { const auto invoke = _webpagesUpdated.empty() && _gamesUpdated.empty(); _webpagesUpdated.insert(page); if (invoke) { crl::on_main(_session, [=] { sendWebPageGameNotifications(); }); } } void Session::notifyGameUpdateDelayed(not_null<GameData*> game) { const auto invoke = _webpagesUpdated.empty() && _gamesUpdated.empty(); _gamesUpdated.insert(game); if (invoke) { crl::on_main(_session, [=] { sendWebPageGameNotifications(); }); } } void Session::sendWebPageGameNotifications() { for (const auto page : base::take(_webpagesUpdated)) { const auto i = _webpageViews.find(page); if (i != _webpageViews.end()) { for (const auto view : i->second) { requestViewResize(view); } } } for (const auto game : base::take(_gamesUpdated)) { if (const auto i = _gameViews.find(game); i != _gameViews.end()) { for (const auto view : i->second) { requestViewResize(view); } } } } void Session::registerItemView(not_null<ViewElement*> view) { _views[view->data()].push_back(view); } void Session::unregisterItemView(not_null<ViewElement*> view) { const auto i = _views.find(view->data()); if (i != end(_views)) { auto &list = i->second; list.erase(ranges::remove(list, view), end(list)); if (list.empty()) { _views.erase(i); } } if (App::hoveredItem() == view) { App::hoveredItem(nullptr); } if (App::pressedItem() == view) { App::pressedItem(nullptr); } if (App::hoveredLinkItem() == view) { App::hoveredLinkItem(nullptr); } if (App::pressedLinkItem() == view) { App::pressedLinkItem(nullptr); } if (App::mousedItem() == view) { App::mousedItem(nullptr); } } not_null<Feed*> Session::feed(FeedId id) { if (const auto result = feedLoaded(id)) { return result; } const auto [it, ok] = _feeds.emplace( id, std::make_unique<Feed>(id, this)); return it->second.get(); } Feed *Session::feedLoaded(FeedId id) { const auto it = _feeds.find(id); return (it == end(_feeds)) ? nullptr : it->second.get(); } void Session::setDefaultFeedId(FeedId id) { _defaultFeedId = id; } FeedId Session::defaultFeedId() const { return _defaultFeedId.current(); } rpl::producer<FeedId> Session::defaultFeedIdValue() const { return _defaultFeedId.value(); } void Session::forgetMedia() { for (const auto &[id, photo] : _photos) { photo->forget(); } for (const auto &[id, document] : _documents) { document->forget(); } } void Session::setMimeForwardIds(MessageIdsList &&list) { _mimeForwardIds = std::move(list); } MessageIdsList Session::takeMimeForwardIds() { return std::move(_mimeForwardIds); } } // namespace Data �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/data/data_session.h��������������������������������������������0000664�0000000�0000000�00000042652�13262451251�0023172�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "chat_helpers/stickers.h" #include "dialogs/dialogs_key.h" #include "data/data_groups.h" class HistoryItem; namespace HistoryView { struct Group; class Element; } // namespace HistoryView class AuthSession; namespace Media { namespace Clip { class Reader; } // namespace Clip } // namespace Media namespace Data { class Feed; enum class FeedUpdateFlag; struct FeedUpdate; class Session final { public: using ViewElement = HistoryView::Element; explicit Session(not_null<AuthSession*> session); ~Session(); AuthSession &session() const { return *_session; } [[nodiscard]] base::Variable<bool> &contactsLoaded() { return _contactsLoaded; } [[nodiscard]] base::Variable<bool> &allChatsLoaded() { return _allChatsLoaded; } [[nodiscard]] base::Observable<void> &moreChatsLoaded() { return _moreChatsLoaded; } struct ItemVisibilityQuery { not_null<HistoryItem*> item; not_null<bool*> isVisible; }; [[nodiscard]] base::Observable<ItemVisibilityQuery> &queryItemVisibility() { return _queryItemVisibility; } struct IdChange { not_null<HistoryItem*> item; MsgId oldId = 0; }; void notifyItemIdChange(IdChange event); [[nodiscard]] rpl::producer<IdChange> itemIdChanged() const; void notifyItemLayoutChange(not_null<const HistoryItem*> item); [[nodiscard]] rpl::producer<not_null<const HistoryItem*>> itemLayoutChanged() const; void notifyViewLayoutChange(not_null<const ViewElement*> view); [[nodiscard]] rpl::producer<not_null<const ViewElement*>> viewLayoutChanged() const; void requestItemRepaint(not_null<const HistoryItem*> item); [[nodiscard]] rpl::producer<not_null<const HistoryItem*>> itemRepaintRequest() const; void requestViewRepaint(not_null<const ViewElement*> view); [[nodiscard]] rpl::producer<not_null<const ViewElement*>> viewRepaintRequest() const; void requestItemResize(not_null<const HistoryItem*> item); [[nodiscard]] rpl::producer<not_null<const HistoryItem*>> itemResizeRequest() const; void requestViewResize(not_null<ViewElement*> view); [[nodiscard]] rpl::producer<not_null<ViewElement*>> viewResizeRequest() const; void requestItemViewRefresh(not_null<HistoryItem*> item); [[nodiscard]] rpl::producer<not_null<HistoryItem*>> itemViewRefreshRequest() const; void requestItemTextRefresh(not_null<HistoryItem*> item); void requestAnimationPlayInline(not_null<HistoryItem*> item); [[nodiscard]] rpl::producer<not_null<HistoryItem*>> animationPlayInlineRequest() const; void notifyHistoryUnloaded(not_null<const History*> history); [[nodiscard]] rpl::producer<not_null<const History*>> historyUnloaded() const; void notifyItemRemoved(not_null<const HistoryItem*> item); [[nodiscard]] rpl::producer<not_null<const HistoryItem*>> itemRemoved() const; void notifyViewRemoved(not_null<const ViewElement*> view); [[nodiscard]] rpl::producer<not_null<const ViewElement*>> viewRemoved() const; void notifyHistoryCleared(not_null<const History*> history); [[nodiscard]] rpl::producer<not_null<const History*>> historyCleared() const; void notifyHistoryChangeDelayed(not_null<History*> history); [[nodiscard]] rpl::producer<not_null<History*>> historyChanged() const; void sendHistoryChangeNotifications(); using MegagroupParticipant = std::tuple< not_null<ChannelData*>, not_null<UserData*>>; void removeMegagroupParticipant( not_null<ChannelData*> channel, not_null<UserData*> user); [[nodiscard]] rpl::producer<MegagroupParticipant> megagroupParticipantRemoved() const; [[nodiscard]] rpl::producer<not_null<UserData*>> megagroupParticipantRemoved( not_null<ChannelData*> channel) const; void addNewMegagroupParticipant( not_null<ChannelData*> channel, not_null<UserData*> user); [[nodiscard]] rpl::producer<MegagroupParticipant> megagroupParticipantAdded() const; [[nodiscard]] rpl::producer<not_null<UserData*>> megagroupParticipantAdded( not_null<ChannelData*> channel) const; void notifyFeedUpdated(not_null<Feed*> feed, FeedUpdateFlag update); [[nodiscard]] rpl::producer<FeedUpdate> feedUpdated() const; void notifyStickersUpdated(); [[nodiscard]] rpl::producer<> stickersUpdated() const; void notifySavedGifsUpdated(); [[nodiscard]] rpl::producer<> savedGifsUpdated() const; bool stickersUpdateNeeded(TimeMs now) const { return stickersUpdateNeeded(_lastStickersUpdate, now); } void setLastStickersUpdate(TimeMs update) { _lastStickersUpdate = update; } bool recentStickersUpdateNeeded(TimeMs now) const { return stickersUpdateNeeded(_lastRecentStickersUpdate, now); } void setLastRecentStickersUpdate(TimeMs update) { _lastRecentStickersUpdate = update; } bool favedStickersUpdateNeeded(TimeMs now) const { return stickersUpdateNeeded(_lastFavedStickersUpdate, now); } void setLastFavedStickersUpdate(TimeMs update) { _lastFavedStickersUpdate = update; } bool featuredStickersUpdateNeeded(TimeMs now) const { return stickersUpdateNeeded(_lastFeaturedStickersUpdate, now); } void setLastFeaturedStickersUpdate(TimeMs update) { _lastFeaturedStickersUpdate = update; } bool savedGifsUpdateNeeded(TimeMs now) const { return stickersUpdateNeeded(_lastSavedGifsUpdate, now); } void setLastSavedGifsUpdate(TimeMs update) { _lastSavedGifsUpdate = update; } int featuredStickerSetsUnreadCount() const { return _featuredStickerSetsUnreadCount.current(); } void setFeaturedStickerSetsUnreadCount(int count) { _featuredStickerSetsUnreadCount = count; } [[nodiscard]] rpl::producer<int> featuredStickerSetsUnreadCountValue() const { return _featuredStickerSetsUnreadCount.value(); } const Stickers::Sets &stickerSets() const { return _stickerSets; } Stickers::Sets &stickerSetsRef() { return _stickerSets; } const Stickers::Order &stickerSetsOrder() const { return _stickerSetsOrder; } Stickers::Order &stickerSetsOrderRef() { return _stickerSetsOrder; } const Stickers::Order &featuredStickerSetsOrder() const { return _featuredStickerSetsOrder; } Stickers::Order &featuredStickerSetsOrderRef() { return _featuredStickerSetsOrder; } const Stickers::Order &archivedStickerSetsOrder() const { return _archivedStickerSetsOrder; } Stickers::Order &archivedStickerSetsOrderRef() { return _archivedStickerSetsOrder; } const Stickers::SavedGifs &savedGifs() const { return _savedGifs; } Stickers::SavedGifs &savedGifsRef() { return _savedGifs; } HistoryItemsList idsToItems(const MessageIdsList &ids) const; MessageIdsList itemsToIds(const HistoryItemsList &items) const; MessageIdsList itemOrItsGroup(not_null<HistoryItem*> item) const; int pinnedDialogsCount() const; const std::deque<Dialogs::Key> &pinnedDialogsOrder() const; void setPinnedDialog(const Dialogs::Key &key, bool pinned); void applyPinnedDialogs(const QVector<MTPDialog> &list); void applyPinnedDialogs(const QVector<MTPDialogPeer> &list); void reorderTwoPinnedDialogs( const Dialogs::Key &key1, const Dialogs::Key &key2); void photoLoadSettingsChanged(); void voiceLoadSettingsChanged(); void animationLoadSettingsChanged(); void notifyPhotoLayoutChanged(not_null<const PhotoData*> photo); void notifyDocumentLayoutChanged( not_null<const DocumentData*> document); void requestDocumentViewRepaint(not_null<const DocumentData*> document); void markMediaRead(not_null<const DocumentData*> document); not_null<PhotoData*> photo(PhotoId id); not_null<PhotoData*> photo(const MTPPhoto &data); not_null<PhotoData*> photo(const MTPDphoto &data); not_null<PhotoData*> photo( const MTPPhoto &data, const PreparedPhotoThumbs &thumbs); not_null<PhotoData*> photo( PhotoId id, const uint64 &access, TimeId date, const ImagePtr &thumb, const ImagePtr &medium, const ImagePtr &full); void photoConvert( not_null<PhotoData*> original, const MTPPhoto &data); PhotoData *photoFromWeb(const MTPWebDocument &data, ImagePtr thumb); not_null<DocumentData*> document(DocumentId id); not_null<DocumentData*> document(const MTPDocument &data); not_null<DocumentData*> document(const MTPDdocument &data); not_null<DocumentData*> document( const MTPdocument &data, const QPixmap &thumb); not_null<DocumentData*> document( DocumentId id, const uint64 &access, int32 version, TimeId date, const QVector<MTPDocumentAttribute> &attributes, const QString &mime, const ImagePtr &thumb, int32 dc, int32 size, const StorageImageLocation &thumbLocation); void documentConvert( not_null<DocumentData*> original, const MTPDocument &data); DocumentData *documentFromWeb( const MTPWebDocument &data, ImagePtr thumb); not_null<WebPageData*> webpage(WebPageId id); not_null<WebPageData*> webpage(const MTPWebPage &data); not_null<WebPageData*> webpage(const MTPDwebPage &data); not_null<WebPageData*> webpage(const MTPDwebPagePending &data); not_null<WebPageData*> webpage( WebPageId id, const QString &siteName, const TextWithEntities &content); not_null<WebPageData*> webpage( WebPageId id, const QString &type, const QString &url, const QString &displayUrl, const QString &siteName, const QString &title, const TextWithEntities &description, PhotoData *photo, DocumentData *document, int duration, const QString &author, TimeId pendingTill); not_null<GameData*> game(GameId id); not_null<GameData*> game(const MTPDgame &data); not_null<GameData*> game( GameId id, const uint64 &accessHash, const QString &shortName, const QString &title, const QString &description, PhotoData *photo, DocumentData *document); void gameConvert( not_null<GameData*> original, const MTPGame &data); void registerPhotoItem( not_null<const PhotoData*> photo, not_null<HistoryItem*> item); void unregisterPhotoItem( not_null<const PhotoData*> photo, not_null<HistoryItem*> item); void registerDocumentItem( not_null<const DocumentData*> document, not_null<HistoryItem*> item); void unregisterDocumentItem( not_null<const DocumentData*> document, not_null<HistoryItem*> item); void registerWebPageView( not_null<const WebPageData*> page, not_null<ViewElement*> view); void unregisterWebPageView( not_null<const WebPageData*> page, not_null<ViewElement*> view); void registerWebPageItem( not_null<const WebPageData*> page, not_null<HistoryItem*> item); void unregisterWebPageItem( not_null<const WebPageData*> page, not_null<HistoryItem*> item); void registerGameView( not_null<const GameData*> game, not_null<ViewElement*> view); void unregisterGameView( not_null<const GameData*> game, not_null<ViewElement*> view); void registerContactView( UserId contactId, not_null<ViewElement*> view); void unregisterContactView( UserId contactId, not_null<ViewElement*> view); void registerContactItem( UserId contactId, not_null<HistoryItem*> item); void unregisterContactItem( UserId contactId, not_null<HistoryItem*> item); void registerAutoplayAnimation( not_null<::Media::Clip::Reader*> reader, not_null<ViewElement*> view); void unregisterAutoplayAnimation( not_null<::Media::Clip::Reader*> reader); HistoryItem *findWebPageItem(not_null<WebPageData*> page) const; QString findContactPhone(not_null<UserData*> contact) const; QString findContactPhone(UserId contactId) const; void notifyWebPageUpdateDelayed(not_null<WebPageData*> page); void notifyGameUpdateDelayed(not_null<GameData*> game); void sendWebPageGameNotifications(); void stopAutoplayAnimations(); void registerItemView(not_null<ViewElement*> view); void unregisterItemView(not_null<ViewElement*> view); not_null<Feed*> feed(FeedId id); Feed *feedLoaded(FeedId id); void setDefaultFeedId(FeedId id); FeedId defaultFeedId() const; rpl::producer<FeedId> defaultFeedIdValue() const; void forgetMedia(); void setMimeForwardIds(MessageIdsList &&list); MessageIdsList takeMimeForwardIds(); Groups &groups() { return _groups; } const Groups &groups() const { return _groups; } private: void setupContactViewsViewer(); void setupChannelLeavingViewer(); void photoApplyFields( not_null<PhotoData*> photo, const MTPPhoto &data); void photoApplyFields( not_null<PhotoData*> photo, const MTPDphoto &data); void photoApplyFields( not_null<PhotoData*> photo, const uint64 &access, TimeId date, const ImagePtr &thumb, const ImagePtr &medium, const ImagePtr &full); void documentApplyFields( not_null<DocumentData*> document, const MTPDocument &data); void documentApplyFields( not_null<DocumentData*> document, const MTPDdocument &data); void documentApplyFields( not_null<DocumentData*> document, const uint64 &access, int32 version, TimeId date, const QVector<MTPDocumentAttribute> &attributes, const QString &mime, const ImagePtr &thumb, int32 dc, int32 size, const StorageImageLocation &thumbLocation); DocumentData *documentFromWeb( const MTPDwebDocument &data, ImagePtr thumb); DocumentData *documentFromWeb( const MTPDwebDocumentNoProxy &data, ImagePtr thumb); void webpageApplyFields( not_null<WebPageData*> page, const MTPDwebPage &data); void webpageApplyFields( not_null<WebPageData*> page, const QString &type, const QString &url, const QString &displayUrl, const QString &siteName, const QString &title, const TextWithEntities &description, PhotoData *photo, DocumentData *document, int duration, const QString &author, TimeId pendingTill); void gameApplyFields( not_null<GameData*> game, const MTPDgame &data); void gameApplyFields( not_null<GameData*> game, const uint64 &accessHash, const QString &shortName, const QString &title, const QString &description, PhotoData *photo, DocumentData *document); bool stickersUpdateNeeded(TimeMs lastUpdate, TimeMs now) const { constexpr auto kStickersUpdateTimeout = TimeMs(3600'000); return (lastUpdate == 0) || (now >= lastUpdate + kStickersUpdateTimeout); } void userIsContactUpdated(not_null<UserData*> user); void clearPinnedDialogs(); void setIsPinned(const Dialogs::Key &key, bool pinned); template <typename Method> void enumerateItemViews( not_null<const HistoryItem*> item, Method method); not_null<AuthSession*> _session; base::Variable<bool> _contactsLoaded = { false }; base::Variable<bool> _allChatsLoaded = { false }; base::Observable<void> _moreChatsLoaded; base::Observable<ItemVisibilityQuery> _queryItemVisibility; rpl::event_stream<IdChange> _itemIdChanges; rpl::event_stream<not_null<const HistoryItem*>> _itemLayoutChanges; rpl::event_stream<not_null<const ViewElement*>> _viewLayoutChanges; rpl::event_stream<not_null<const HistoryItem*>> _itemRepaintRequest; rpl::event_stream<not_null<const ViewElement*>> _viewRepaintRequest; rpl::event_stream<not_null<const HistoryItem*>> _itemResizeRequest; rpl::event_stream<not_null<ViewElement*>> _viewResizeRequest; rpl::event_stream<not_null<HistoryItem*>> _itemViewRefreshRequest; rpl::event_stream<not_null<HistoryItem*>> _itemTextRefreshRequest; rpl::event_stream<not_null<HistoryItem*>> _animationPlayInlineRequest; rpl::event_stream<not_null<const HistoryItem*>> _itemRemoved; rpl::event_stream<not_null<const ViewElement*>> _viewRemoved; rpl::event_stream<not_null<const History*>> _historyUnloaded; rpl::event_stream<not_null<const History*>> _historyCleared; base::flat_set<not_null<History*>> _historiesChanged; rpl::event_stream<not_null<History*>> _historyChanged; rpl::event_stream<MegagroupParticipant> _megagroupParticipantRemoved; rpl::event_stream<MegagroupParticipant> _megagroupParticipantAdded; rpl::event_stream<FeedUpdate> _feedUpdates; rpl::event_stream<> _stickersUpdated; rpl::event_stream<> _savedGifsUpdated; TimeMs _lastStickersUpdate = 0; TimeMs _lastRecentStickersUpdate = 0; TimeMs _lastFavedStickersUpdate = 0; TimeMs _lastFeaturedStickersUpdate = 0; TimeMs _lastSavedGifsUpdate = 0; rpl::variable<int> _featuredStickerSetsUnreadCount = 0; Stickers::Sets _stickerSets; Stickers::Order _stickerSetsOrder; Stickers::Order _featuredStickerSetsOrder; Stickers::Order _archivedStickerSetsOrder; Stickers::SavedGifs _savedGifs; std::unordered_map< PhotoId, std::unique_ptr<PhotoData>> _photos; std::map< not_null<const PhotoData*>, base::flat_set<not_null<HistoryItem*>>> _photoItems; std::unordered_map< DocumentId, std::unique_ptr<DocumentData>> _documents; std::map< not_null<const DocumentData*>, base::flat_set<not_null<HistoryItem*>>> _documentItems; std::unordered_map< WebPageId, std::unique_ptr<WebPageData>> _webpages; std::map< not_null<const WebPageData*>, base::flat_set<not_null<HistoryItem*>>> _webpageItems; std::map< not_null<const WebPageData*>, base::flat_set<not_null<ViewElement*>>> _webpageViews; std::unordered_map< GameId, std::unique_ptr<GameData>> _games; std::map< not_null<const GameData*>, base::flat_set<not_null<ViewElement*>>> _gameViews; std::map< UserId, base::flat_set<not_null<HistoryItem*>>> _contactItems; std::map< UserId, base::flat_set<not_null<ViewElement*>>> _contactViews; base::flat_map< not_null<::Media::Clip::Reader*>, not_null<ViewElement*>> _autoplayAnimations; base::flat_set<not_null<WebPageData*>> _webpagesUpdated; base::flat_set<not_null<GameData*>> _gamesUpdated; std::deque<Dialogs::Key> _pinnedDialogs; base::flat_map<FeedId, std::unique_ptr<Feed>> _feeds; rpl::variable<FeedId> _defaultFeedId = FeedId(); Groups _groups; std::map< not_null<const HistoryItem*>, std::vector<not_null<ViewElement*>>> _views; MessageIdsList _mimeForwardIds; rpl::lifetime _lifetime; }; } // namespace Data ��������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/data/data_shared_media.cpp�������������������������������������0000664�0000000�0000000�00000027040�13262451251�0024441�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "data/data_shared_media.h" #include <rpl/combine.h> #include "auth_session.h" #include "apiwrap.h" #include "storage/storage_facade.h" #include "storage/storage_shared_media.h" #include "history/history.h" #include "history/history_item.h" #include "history/history_media_types.h" #include "data/data_media_types.h" #include "data/data_sparse_ids.h" #include "data/data_session.h" #include "info/info_memento.h" #include "info/info_controller.h" #include "window/window_controller.h" #include "mainwindow.h" #include "core/crash_reports.h" namespace { using Type = Storage::SharedMediaType; } // namespace base::optional<Storage::SharedMediaType> SharedMediaOverviewType( Storage::SharedMediaType type) { switch (type) { case Type::Photo: case Type::Video: case Type::MusicFile: case Type::File: case Type::VoiceFile: case Type::Link: return type; } return base::none; } void SharedMediaShowOverview( Storage::SharedMediaType type, not_null<History*> history) { if (SharedMediaOverviewType(type)) { App::wnd()->controller()->showSection(Info::Memento( history->peer->id, Info::Section(type))); } } bool SharedMediaAllowSearch(Storage::SharedMediaType type) { switch (type) { case Type::MusicFile: case Type::File: case Type::Link: return true; default: return false; } } rpl::producer<SparseIdsSlice> SharedMediaViewer( Storage::SharedMediaKey key, int limitBefore, int limitAfter) { Expects(IsServerMsgId(key.messageId) || (key.messageId == 0)); Expects((key.messageId != 0) || (limitBefore == 0 && limitAfter == 0)); return [=](auto consumer) { auto lifetime = rpl::lifetime(); auto builder = lifetime.make_state<SparseIdsSliceBuilder>( key.messageId, limitBefore, limitAfter); auto requestMediaAround = [ peer = App::peer(key.peerId), type = key.type ](const SparseIdsSliceBuilder::AroundData &data) { Auth().api().requestSharedMedia( peer, type, data.aroundId, data.direction); }; builder->insufficientAround( ) | rpl::start_with_next(requestMediaAround, lifetime); auto pushNextSnapshot = [=] { consumer.put_next(builder->snapshot()); }; using SliceUpdate = Storage::SharedMediaSliceUpdate; Auth().storage().sharedMediaSliceUpdated( ) | rpl::filter([=](const SliceUpdate &update) { return (update.peerId == key.peerId) && (update.type == key.type); }) | rpl::filter([=](const SliceUpdate &update) { return builder->applyUpdate(update.data); }) | rpl::start_with_next(pushNextSnapshot, lifetime); using OneRemoved = Storage::SharedMediaRemoveOne; Auth().storage().sharedMediaOneRemoved( ) | rpl::filter([=](const OneRemoved &update) { return (update.peerId == key.peerId) && update.types.test(key.type); }) | rpl::filter([=](const OneRemoved &update) { return builder->removeOne(update.messageId); }) | rpl::start_with_next(pushNextSnapshot, lifetime); using AllRemoved = Storage::SharedMediaRemoveAll; Auth().storage().sharedMediaAllRemoved( ) | rpl::filter([=](const AllRemoved &update) { return (update.peerId == key.peerId); }) | rpl::filter([=] { return builder->removeAll(); }) | rpl::start_with_next(pushNextSnapshot, lifetime); using InvalidateBottom = Storage::SharedMediaInvalidateBottom; Auth().storage().sharedMediaBottomInvalidated( ) | rpl::filter([=](const InvalidateBottom &update) { return (update.peerId == key.peerId); }) | rpl::filter([=] { return builder->invalidateBottom(); }) | rpl::start_with_next(pushNextSnapshot, lifetime); using Result = Storage::SharedMediaResult; Auth().storage().query(Storage::SharedMediaQuery( key, limitBefore, limitAfter )) | rpl::filter([=](const Result &result) { return builder->applyInitial(result); }) | rpl::start_with_next_done( pushNextSnapshot, [=] { builder->checkInsufficient(); }, lifetime); return lifetime; }; } rpl::producer<SparseIdsMergedSlice> SharedMediaMergedViewer( SharedMediaMergedKey key, int limitBefore, int limitAfter) { auto createSimpleViewer = [=]( PeerId peerId, SparseIdsSlice::Key simpleKey, int limitBefore, int limitAfter) { return SharedMediaViewer( Storage::SharedMediaKey( peerId, key.type, simpleKey), limitBefore, limitAfter ); }; return SparseIdsMergedSlice::CreateViewer( key.mergedKey, limitBefore, limitAfter, std::move(createSimpleViewer)); } SharedMediaWithLastSlice::SharedMediaWithLastSlice(Key key) : SharedMediaWithLastSlice( key, SparseIdsMergedSlice(ViewerKey(key)), EndingSlice(key)) { } SharedMediaWithLastSlice::SharedMediaWithLastSlice( Key key, SparseIdsMergedSlice slice, base::optional<SparseIdsMergedSlice> ending) : _key(key) , _slice(std::move(slice)) , _ending(std::move(ending)) , _lastPhotoId(LastPeerPhotoId(key.peerId)) , _isolatedLastPhoto(_key.type == Type::ChatPhoto ? IsLastIsolated(_slice, _ending, _lastPhotoId) : false) { } base::optional<int> SharedMediaWithLastSlice::fullCount() const { return Add( _slice.fullCount(), _isolatedLastPhoto | [](bool isolated) { return isolated ? 1 : 0; }); } base::optional<int> SharedMediaWithLastSlice::skippedBeforeImpl() const { return _slice.skippedBefore(); } base::optional<int> SharedMediaWithLastSlice::skippedBefore() const { return _reversed ? skippedAfterImpl() : skippedBeforeImpl(); } base::optional<int> SharedMediaWithLastSlice::skippedAfterImpl() const { return isolatedInSlice() ? Add( _slice.skippedAfter(), lastPhotoSkip()) : (lastPhotoSkip() | [](int) { return 0; }); } base::optional<int> SharedMediaWithLastSlice::skippedAfter() const { return _reversed ? skippedBeforeImpl() : skippedAfterImpl(); } base::optional<int> SharedMediaWithLastSlice::indexOfImpl(Value value) const { return base::get_if<FullMsgId>(&value) ? _slice.indexOf(*base::get_if<FullMsgId>(&value)) : (isolatedInSlice() || !_lastPhotoId || (*base::get_if<not_null<PhotoData*>>(&value))->id != *_lastPhotoId) ? base::none : Add(_slice.size() - 1, lastPhotoSkip()); } base::optional<int> SharedMediaWithLastSlice::indexOf(Value value) const { const auto result = indexOfImpl(value); if (result && (*result < 0 || *result >= size())) { // Should not happen. auto info = QStringList(); info.push_back("slice:" + QString::number(_slice.size())); info.push_back(_slice.fullCount() ? QString::number(*_slice.fullCount()) : QString("-")); info.push_back(_slice.skippedBefore() ? QString::number(*_slice.skippedBefore()) : QString("-")); info.push_back(_slice.skippedAfter() ? QString::number(*_slice.skippedAfter()) : QString("-")); info.push_back("ending:" + (_ending ? QString::number(_ending->size()) : QString("-"))); info.push_back((_ending && _ending->fullCount()) ? QString::number(*_ending->fullCount()) : QString("-")); info.push_back((_ending && _ending->skippedBefore()) ? QString::number(*_ending->skippedBefore()) : QString("-")); info.push_back((_ending && _ending->skippedAfter()) ? QString::number(*_ending->skippedAfter()) : QString("-")); if (const auto msgId = base::get_if<FullMsgId>(&value)) { info.push_back("value:" + QString::number(msgId->channel)); info.push_back(QString::number(msgId->msg)); const auto index = _slice.indexOf(*base::get_if<FullMsgId>(&value)); info.push_back("index:" + (index ? QString::number(*index) : QString("-"))); } else if (const auto photo = base::get_if<not_null<PhotoData*>>(&value)) { info.push_back("value:" + QString::number((*photo)->id)); } else { info.push_back("value:bad"); } info.push_back("isolated:" + QString(Logs::b(isolatedInSlice()))); info.push_back("last:" + (_lastPhotoId ? QString::number(*_lastPhotoId) : QString("-"))); info.push_back("isolated_last:" + (_isolatedLastPhoto ? QString(Logs::b(*_isolatedLastPhoto)) : QString("-"))); info.push_back("skip:" + (lastPhotoSkip() ? QString::number(*lastPhotoSkip()) : QString("-"))); CrashReports::SetAnnotation("DebugInfo", info.join(',')); Unexpected("Result in SharedMediaWithLastSlice::indexOf"); } return _reversed ? (result | func::negate | func::add(size() - 1)) : result; } int SharedMediaWithLastSlice::size() const { return _slice.size() + ((!isolatedInSlice() && lastPhotoSkip() == 1) ? 1 : 0); } SharedMediaWithLastSlice::Value SharedMediaWithLastSlice::operator[](int index) const { Expects(index >= 0 && index < size()); if (_reversed) { index = size() - index - 1; } return (index < _slice.size()) ? Value(_slice[index]) : Value(Auth().data().photo(*_lastPhotoId)); } base::optional<int> SharedMediaWithLastSlice::distance( const Key &a, const Key &b) const { if (auto i = indexOf(ComputeId(a))) { if (auto j = indexOf(ComputeId(b))) { return *j - *i; } } return base::none; } void SharedMediaWithLastSlice::reverse() { _reversed = !_reversed; } base::optional<PhotoId> SharedMediaWithLastSlice::LastPeerPhotoId( PeerId peerId) { if (auto peer = App::peerLoaded(peerId)) { return peer->userpicPhotoUnknown() ? base::none : base::make_optional(peer->userpicPhotoId()); } return base::none; } base::optional<bool> SharedMediaWithLastSlice::IsLastIsolated( const SparseIdsMergedSlice &slice, const base::optional<SparseIdsMergedSlice> &ending, base::optional<PhotoId> lastPeerPhotoId) { if (!lastPeerPhotoId) { return base::none; } else if (!*lastPeerPhotoId) { return false; } return LastFullMsgId(ending ? *ending : slice) | [](FullMsgId msgId) { return App::histItemById(msgId); } | [](HistoryItem *item) { return item ? item->media() : nullptr; } | [](Data::Media *media) { return media ? media->photo() : nullptr; } | [](PhotoData *photo) { return photo ? photo->id : 0; } | [&](PhotoId photoId) { return *lastPeerPhotoId != photoId; }; } base::optional<FullMsgId> SharedMediaWithLastSlice::LastFullMsgId( const SparseIdsMergedSlice &slice) { if (slice.fullCount() == 0) { return FullMsgId(); } else if (slice.size() == 0 || slice.skippedAfter() != 0) { return base::none; } return slice[slice.size() - 1]; } rpl::producer<SharedMediaWithLastSlice> SharedMediaWithLastViewer( SharedMediaWithLastSlice::Key key, int limitBefore, int limitAfter) { return [=](auto consumer) { if (base::get_if<not_null<PhotoData*>>(&key.universalId)) { return SharedMediaMergedViewer( SharedMediaMergedKey( SharedMediaWithLastSlice::ViewerKey(key), key.type), limitBefore, limitAfter ) | rpl::start_with_next([=](SparseIdsMergedSlice &&update) { consumer.put_next(SharedMediaWithLastSlice( key, std::move(update), base::none)); }); } return rpl::combine( SharedMediaMergedViewer( SharedMediaMergedKey( SharedMediaWithLastSlice::ViewerKey(key), key.type), limitBefore, limitAfter), SharedMediaMergedViewer( SharedMediaMergedKey( SharedMediaWithLastSlice::EndingKey(key), key.type), 1, 1) ) | rpl::start_with_next([=]( SparseIdsMergedSlice &&viewer, SparseIdsMergedSlice &&ending) { consumer.put_next(SharedMediaWithLastSlice( key, std::move(viewer), std::move(ending))); }); }; } rpl::producer<SharedMediaWithLastSlice> SharedMediaWithLastReversedViewer( SharedMediaWithLastSlice::Key key, int limitBefore, int limitAfter) { return SharedMediaWithLastViewer( key, limitBefore, limitAfter ) | rpl::map([](SharedMediaWithLastSlice &&slice) { slice.reverse(); return std::move(slice); }); } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/data/data_shared_media.h���������������������������������������0000664�0000000�0000000�00000011743�13262451251�0024111�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "storage/storage_shared_media.h" #include "base/weak_ptr.h" #include "data/data_sparse_ids.h" base::optional<Storage::SharedMediaType> SharedMediaOverviewType( Storage::SharedMediaType type); void SharedMediaShowOverview( Storage::SharedMediaType type, not_null<History*> history); bool SharedMediaAllowSearch(Storage::SharedMediaType type); rpl::producer<SparseIdsSlice> SharedMediaViewer( Storage::SharedMediaKey key, int limitBefore, int limitAfter); struct SharedMediaMergedKey { using Type = Storage::SharedMediaType; SharedMediaMergedKey( SparseIdsMergedSlice::Key mergedKey, Type type) : mergedKey(mergedKey) , type(type) { } bool operator==(const SharedMediaMergedKey &other) const { return (mergedKey == other.mergedKey) && (type == other.type); } SparseIdsMergedSlice::Key mergedKey; Type type = Type::kCount; }; rpl::producer<SparseIdsMergedSlice> SharedMediaMergedViewer( SharedMediaMergedKey key, int limitBefore, int limitAfter); class SharedMediaWithLastSlice { public: using Type = Storage::SharedMediaType; using Value = base::variant<FullMsgId, not_null<PhotoData*>>; using MessageId = SparseIdsMergedSlice::UniversalMsgId; using UniversalMsgId = base::variant< MessageId, not_null<PhotoData*>>; struct Key { Key( PeerId peerId, PeerId migratedPeerId, Type type, UniversalMsgId universalId) : peerId(peerId) , migratedPeerId(migratedPeerId) , type(type) , universalId(universalId) { Expects(base::get_if<MessageId>(&universalId) != nullptr || type == Type::ChatPhoto); } bool operator==(const Key &other) const { return (peerId == other.peerId) && (migratedPeerId == other.migratedPeerId) && (type == other.type) && (universalId == other.universalId); } bool operator!=(const Key &other) const { return !(*this == other); } PeerId peerId = 0; PeerId migratedPeerId = 0; Type type = Type::kCount; UniversalMsgId universalId; }; SharedMediaWithLastSlice(Key key); SharedMediaWithLastSlice( Key key, SparseIdsMergedSlice slice, base::optional<SparseIdsMergedSlice> ending); base::optional<int> fullCount() const; base::optional<int> skippedBefore() const; base::optional<int> skippedAfter() const; base::optional<int> indexOf(Value fullId) const; int size() const; Value operator[](int index) const; base::optional<int> distance(const Key &a, const Key &b) const; void reverse(); static SparseIdsMergedSlice::Key ViewerKey(const Key &key) { return { key.peerId, key.migratedPeerId, base::get_if<MessageId>(&key.universalId) ? (*base::get_if<MessageId>(&key.universalId)) : ServerMaxMsgId - 1 }; } static SparseIdsMergedSlice::Key EndingKey(const Key &key) { return { key.peerId, key.migratedPeerId, ServerMaxMsgId - 1 }; } private: static base::optional<SparseIdsMergedSlice> EndingSlice(const Key &key) { return base::get_if<MessageId>(&key.universalId) ? base::make_optional(SparseIdsMergedSlice(EndingKey(key))) : base::none; } static base::optional<PhotoId> LastPeerPhotoId(PeerId peerId); static base::optional<bool> IsLastIsolated( const SparseIdsMergedSlice &slice, const base::optional<SparseIdsMergedSlice> &ending, base::optional<PhotoId> lastPeerPhotoId); static base::optional<FullMsgId> LastFullMsgId( const SparseIdsMergedSlice &slice); static base::optional<int> Add( const base::optional<int> &a, const base::optional<int> &b) { return (a && b) ? base::make_optional(*a + *b) : base::none; } static Value ComputeId(PeerId peerId, MsgId msgId) { return FullMsgId( peerIsChannel(peerId) ? peerToBareInt(peerId) : 0, msgId); } static Value ComputeId(const Key &key) { if (auto messageId = base::get_if<MessageId>(&key.universalId)) { return (*messageId >= 0) ? ComputeId(key.peerId, *messageId) : ComputeId(key.migratedPeerId, ServerMaxMsgId + *messageId); } return *base::get_if<not_null<PhotoData*>>(&key.universalId); } bool isolatedInSlice() const { return (_slice.skippedAfter() != 0); } base::optional<int> lastPhotoSkip() const { return _isolatedLastPhoto | [](bool isolated) { return isolated ? 1 : 0; }; } base::optional<int> skippedBeforeImpl() const; base::optional<int> skippedAfterImpl() const; base::optional<int> indexOfImpl(Value fullId) const; Key _key; SparseIdsMergedSlice _slice; base::optional<SparseIdsMergedSlice> _ending; base::optional<PhotoId> _lastPhotoId; base::optional<bool> _isolatedLastPhoto; bool _reversed = false; }; rpl::producer<SharedMediaWithLastSlice> SharedMediaWithLastViewer( SharedMediaWithLastSlice::Key key, int limitBefore, int limitAfter); rpl::producer<SharedMediaWithLastSlice> SharedMediaWithLastReversedViewer( SharedMediaWithLastSlice::Key key, int limitBefore, int limitAfter); �����������������������������tdesktop-1.2.17/Telegram/SourceFiles/data/data_sparse_ids.cpp���������������������������������������0000664�0000000�0000000�00000025546�13262451251�0024201�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "data/data_sparse_ids.h" #include <rpl/combine.h> #include "storage/storage_sparse_ids_list.h" SparseIdsSlice::SparseIdsSlice( const base::flat_set<MsgId> &ids, MsgRange range, base::optional<int> fullCount, base::optional<int> skippedBefore, base::optional<int> skippedAfter) : _ids(ids) , _range(range) , _fullCount(fullCount) , _skippedBefore(skippedBefore) , _skippedAfter(skippedAfter) { } base::optional<int> SparseIdsSlice::indexOf(MsgId msgId) const { auto it = _ids.find(msgId); if (it != _ids.end()) { return (it - _ids.begin()); } return base::none; } MsgId SparseIdsSlice::operator[](int index) const { Expects(index >= 0 && index < size()); return *(_ids.begin() + index); } base::optional<int> SparseIdsSlice::distance( MsgId a, MsgId b) const { if (auto i = indexOf(a)) { if (auto j = indexOf(b)) { return *j - *i; } } return base::none; } base::optional<MsgId> SparseIdsSlice::nearest(MsgId msgId) const { if (auto it = ranges::lower_bound(_ids, msgId); it != _ids.end()) { return *it; } else if (_ids.empty()) { return base::none; } return _ids.back(); } SparseIdsMergedSlice::SparseIdsMergedSlice(Key key) : SparseIdsMergedSlice( key, SparseIdsSlice(), MigratedSlice(key)) { } SparseIdsMergedSlice::SparseIdsMergedSlice( Key key, SparseIdsSlice part, base::optional<SparseIdsSlice> migrated) : _key(key) , _part(std::move(part)) , _migrated(std::move(migrated)) { } base::optional<int> SparseIdsMergedSlice::fullCount() const { return Add( _part.fullCount(), _migrated ? _migrated->fullCount() : 0); } base::optional<int> SparseIdsMergedSlice::skippedBefore() const { return Add( isolatedInMigrated() ? 0 : _part.skippedBefore(), _migrated ? (isolatedInPart() ? _migrated->fullCount() : _migrated->skippedBefore()) : 0 ); } base::optional<int> SparseIdsMergedSlice::skippedAfter() const { return Add( isolatedInMigrated() ? _part.fullCount() : _part.skippedAfter(), isolatedInPart() ? 0 : _migrated->skippedAfter() ); } base::optional<int> SparseIdsMergedSlice::indexOf( FullMsgId fullId) const { return isFromPart(fullId) ? (_part.indexOf(fullId.msg) | func::add(migratedSize())) : isolatedInPart() ? base::none : isFromMigrated(fullId) ? _migrated->indexOf(fullId.msg) : base::none; } int SparseIdsMergedSlice::size() const { return (isolatedInPart() ? 0 : migratedSize()) + (isolatedInMigrated() ? 0 : _part.size()); } FullMsgId SparseIdsMergedSlice::operator[](int index) const { Expects(index >= 0 && index < size()); if (auto size = migratedSize()) { if (index < size) { return ComputeId(_key.migratedPeerId, (*_migrated)[index]); } index -= size; } return ComputeId(_key.peerId, _part[index]); } base::optional<int> SparseIdsMergedSlice::distance( const Key &a, const Key &b) const { if (auto i = indexOf(ComputeId(a))) { if (auto j = indexOf(ComputeId(b))) { return *j - *i; } } return base::none; } auto SparseIdsMergedSlice::nearest( UniversalMsgId id) const -> base::optional<FullMsgId> { auto convertFromPartNearest = [&](MsgId result) { return ComputeId(_key.peerId, result); }; auto convertFromMigratedNearest = [&](MsgId result) { return ComputeId(_key.migratedPeerId, result); }; if (IsServerMsgId(id)) { if (auto partNearestId = _part.nearest(id)) { return partNearestId | convertFromPartNearest; } else if (isolatedInPart()) { return base::none; } return _migrated->nearest(ServerMaxMsgId - 1) | convertFromMigratedNearest; } if (auto migratedNearestId = _migrated ? _migrated->nearest(id + ServerMaxMsgId) : base::none) { return migratedNearestId | convertFromMigratedNearest; } else if (isolatedInMigrated()) { return base::none; } return _part.nearest(0) | convertFromPartNearest; } SparseIdsSliceBuilder::SparseIdsSliceBuilder( Key key, int limitBefore, int limitAfter) : _key(key) , _limitBefore(limitBefore) , _limitAfter(limitAfter) { } bool SparseIdsSliceBuilder::applyInitial( const Storage::SparseIdsListResult &result) { mergeSliceData( result.count, result.messageIds, result.skippedBefore, result.skippedAfter); return true; } bool SparseIdsSliceBuilder::applyUpdate( const Storage::SparseIdsSliceUpdate &update) { auto intersects = [](MsgRange range1, MsgRange range2) { return (range1.from <= range2.till) && (range2.from <= range1.till); }; auto needMergeMessages = (update.messages != nullptr) && intersects(update.range, { _ids.empty() ? _key : _ids.front(), _ids.empty() ? _key : _ids.back() }); if (!needMergeMessages && !update.count) { return false; } auto skippedBefore = (update.range.from == 0) ? 0 : base::optional<int> {}; auto skippedAfter = (update.range.till == ServerMaxMsgId) ? 0 : base::optional<int> {}; mergeSliceData( update.count, needMergeMessages ? *update.messages : base::flat_set<MsgId> {}, skippedBefore, skippedAfter); return true; } bool SparseIdsSliceBuilder::removeOne(MsgId messageId) { auto changed = false; if (_fullCount && *_fullCount > 0) { --*_fullCount; changed = true; } if (_ids.contains(messageId)) { _ids.remove(messageId); changed = true; } else if (!_ids.empty()) { if (_ids.front() > messageId && _skippedBefore && *_skippedBefore > 0) { --*_skippedBefore; changed = true; } else if (_ids.back() < messageId && _skippedAfter && *_skippedAfter > 0) { --*_skippedAfter; changed = true; } } if (changed) { checkInsufficient(); } return changed; } bool SparseIdsSliceBuilder::removeAll() { _ids = {}; _range = { 0, ServerMaxMsgId }; _fullCount = 0; _skippedBefore = 0; _skippedAfter = 0; return true; } bool SparseIdsSliceBuilder::invalidateBottom() { _fullCount = _skippedAfter = base::none; if (_range.till == ServerMaxMsgId) { _range.till = _ids.empty() ? _range.from : _ids.back(); } checkInsufficient(); return true; } void SparseIdsSliceBuilder::checkInsufficient() { sliceToLimits(); } void SparseIdsSliceBuilder::mergeSliceData( base::optional<int> count, const base::flat_set<MsgId> &messageIds, base::optional<int> skippedBefore, base::optional<int> skippedAfter) { if (messageIds.empty()) { if (count && _fullCount != count) { _fullCount = count; if (*_fullCount <= _ids.size()) { _fullCount = _ids.size(); _skippedBefore = _skippedAfter = 0; } } fillSkippedAndSliceToLimits(); return; } if (count) { _fullCount = count; } auto wasMinId = _ids.empty() ? -1 : _ids.front(); auto wasMaxId = _ids.empty() ? -1 : _ids.back(); _ids.merge(messageIds.begin(), messageIds.end()); auto adjustSkippedBefore = [&](MsgId oldId, int oldSkippedBefore) { auto it = _ids.find(oldId); Assert(it != _ids.end()); _skippedBefore = oldSkippedBefore - (it - _ids.begin()); accumulate_max(*_skippedBefore, 0); }; if (skippedBefore) { adjustSkippedBefore(messageIds.front(), *skippedBefore); } else if (wasMinId >= 0 && _skippedBefore) { adjustSkippedBefore(wasMinId, *_skippedBefore); } else { _skippedBefore = base::none; } auto adjustSkippedAfter = [&](MsgId oldId, int oldSkippedAfter) { auto it = _ids.find(oldId); Assert(it != _ids.end()); _skippedAfter = oldSkippedAfter - (_ids.end() - it - 1); accumulate_max(*_skippedAfter, 0); }; if (skippedAfter) { adjustSkippedAfter(messageIds.back(), *skippedAfter); } else if (wasMaxId >= 0 && _skippedAfter) { adjustSkippedAfter(wasMaxId, *_skippedAfter); } else { _skippedAfter = base::none; } fillSkippedAndSliceToLimits(); } void SparseIdsSliceBuilder::fillSkippedAndSliceToLimits() { if (_fullCount) { if (_skippedBefore && !_skippedAfter) { _skippedAfter = *_fullCount - *_skippedBefore - int(_ids.size()); } else if (_skippedAfter && !_skippedBefore) { _skippedBefore = *_fullCount - *_skippedAfter - int(_ids.size()); } } sliceToLimits(); } void SparseIdsSliceBuilder::sliceToLimits() { if (!_key) { if (!_fullCount) { requestMessagesCount(); } return; } auto requestedSomething = false; auto aroundIt = ranges::lower_bound(_ids, _key); auto removeFromBegin = (aroundIt - _ids.begin() - _limitBefore); auto removeFromEnd = (_ids.end() - aroundIt - _limitAfter - 1); if (removeFromBegin > 0) { _ids.erase(_ids.begin(), _ids.begin() + removeFromBegin); if (_skippedBefore) { *_skippedBefore += removeFromBegin; } } else if (removeFromBegin < 0 && (!_skippedBefore || *_skippedBefore > 0)) { requestedSomething = true; requestMessages(RequestDirection::Before); } if (removeFromEnd > 0) { _ids.erase(_ids.end() - removeFromEnd, _ids.end()); if (_skippedAfter) { *_skippedAfter += removeFromEnd; } } else if (removeFromEnd < 0 && (!_skippedAfter || *_skippedAfter > 0)) { requestedSomething = true; requestMessages(RequestDirection::After); } if (!_fullCount && !requestedSomething) { requestMessagesCount(); } } void SparseIdsSliceBuilder::requestMessages( RequestDirection direction) { auto requestAroundData = [&]() -> AroundData { if (_ids.empty()) { return { _key, Data::LoadDirection::Around }; } else if (direction == RequestDirection::Before) { return { _ids.front(), Data::LoadDirection::Before }; } return { _ids.back(), Data::LoadDirection::After }; }; _insufficientAround.fire(requestAroundData()); } void SparseIdsSliceBuilder::requestMessagesCount() { _insufficientAround.fire({ 0, Data::LoadDirection::Around }); } SparseIdsSlice SparseIdsSliceBuilder::snapshot() const { return SparseIdsSlice( _ids, _range, _fullCount, _skippedBefore, _skippedAfter); } rpl::producer<SparseIdsMergedSlice> SparseIdsMergedSlice::CreateViewer( SparseIdsMergedSlice::Key key, int limitBefore, int limitAfter, base::lambda<SimpleViewerFunction> simpleViewer) { Expects(IsServerMsgId(key.universalId) || (key.universalId == 0) || (IsServerMsgId(ServerMaxMsgId + key.universalId) && key.migratedPeerId != 0)); Expects((key.universalId != 0) || (limitBefore == 0 && limitAfter == 0)); return [=](auto consumer) { auto partViewer = simpleViewer( key.peerId, SparseIdsMergedSlice::PartKey(key), limitBefore, limitAfter ); if (!key.migratedPeerId) { return std::move( partViewer ) | rpl::start_with_next([=](SparseIdsSlice &&part) { consumer.put_next(SparseIdsMergedSlice( key, std::move(part), base::none)); }); } auto migratedViewer = simpleViewer( key.migratedPeerId, SparseIdsMergedSlice::MigratedKey(key), limitBefore, limitAfter); return rpl::combine( std::move(partViewer), std::move(migratedViewer) ) | rpl::start_with_next([=]( SparseIdsSlice &&part, SparseIdsSlice &&migrated) { consumer.put_next(SparseIdsMergedSlice( key, std::move(part), std::move(migrated))); }); }; }����������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/data/data_sparse_ids.h�����������������������������������������0000664�0000000�0000000�00000013417�13262451251�0023640�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "data/data_messages.h" namespace Storage { struct SparseIdsListResult; struct SparseIdsSliceUpdate; } // namespace Storage class SparseIdsSlice { public: using Key = MsgId; SparseIdsSlice() = default; SparseIdsSlice( const base::flat_set<MsgId> &ids, MsgRange range, base::optional<int> fullCount, base::optional<int> skippedBefore, base::optional<int> skippedAfter); base::optional<int> fullCount() const { return _fullCount; } base::optional<int> skippedBefore() const { return _skippedBefore; } base::optional<int> skippedAfter() const { return _skippedAfter; } base::optional<int> indexOf(MsgId msgId) const; int size() const { return _ids.size(); } MsgId operator[](int index) const; base::optional<int> distance(MsgId a, MsgId b) const; base::optional<MsgId> nearest(MsgId msgId) const; private: base::flat_set<MsgId> _ids; MsgRange _range; base::optional<int> _fullCount; base::optional<int> _skippedBefore; base::optional<int> _skippedAfter; }; class SparseIdsMergedSlice { public: using UniversalMsgId = MsgId; struct Key { Key( PeerId peerId, PeerId migratedPeerId, UniversalMsgId universalId) : peerId(peerId) , migratedPeerId(migratedPeerId) , universalId(universalId) { } bool operator==(const Key &other) const { return (peerId == other.peerId) && (migratedPeerId == other.migratedPeerId) && (universalId == other.universalId); } bool operator!=(const Key &other) const { return !(*this == other); } PeerId peerId = 0; PeerId migratedPeerId = 0; UniversalMsgId universalId = 0; }; SparseIdsMergedSlice(Key key); SparseIdsMergedSlice( Key key, SparseIdsSlice part, base::optional<SparseIdsSlice> migrated); base::optional<int> fullCount() const; base::optional<int> skippedBefore() const; base::optional<int> skippedAfter() const; base::optional<int> indexOf(FullMsgId fullId) const; int size() const; FullMsgId operator[](int index) const; base::optional<int> distance(const Key &a, const Key &b) const; base::optional<FullMsgId> nearest(UniversalMsgId id) const; using SimpleViewerFunction = rpl::producer<SparseIdsSlice>( PeerId peerId, SparseIdsSlice::Key simpleKey, int limitBefore, int limitAfter); static rpl::producer<SparseIdsMergedSlice> CreateViewer( SparseIdsMergedSlice::Key key, int limitBefore, int limitAfter, base::lambda<SimpleViewerFunction> simpleViewer); private: static SparseIdsSlice::Key PartKey(const Key &key) { return (key.universalId < 0) ? 1 : key.universalId; } static SparseIdsSlice::Key MigratedKey(const Key &key) { return (key.universalId < 0) ? (ServerMaxMsgId + key.universalId) : (key.universalId > 0) ? (ServerMaxMsgId - 1) : 0; } static base::optional<SparseIdsSlice> MigratedSlice(const Key &key) { return key.migratedPeerId ? base::make_optional(SparseIdsSlice()) : base::none; } static bool IsFromSlice(PeerId peerId, FullMsgId fullId) { return peerIsChannel(peerId) ? (peerId == peerFromChannel(fullId.channel)) : !fullId.channel; } static FullMsgId ComputeId(PeerId peerId, MsgId msgId) { return FullMsgId( peerIsChannel(peerId) ? peerToBareInt(peerId) : 0, msgId); } static FullMsgId ComputeId(const Key &key) { return (key.universalId >= 0) ? ComputeId(key.peerId, key.universalId) : ComputeId(key.migratedPeerId, ServerMaxMsgId + key.universalId); } static base::optional<int> Add( const base::optional<int> &a, const base::optional<int> &b) { return (a && b) ? base::make_optional(*a + *b) : base::none; } bool isFromPart(FullMsgId fullId) const { return IsFromSlice(_key.peerId, fullId); } bool isFromMigrated(FullMsgId fullId) const { return _migrated ? IsFromSlice(_key.migratedPeerId, fullId) : false; } int migratedSize() const { return isolatedInPart() ? 0 : _migrated->size(); } bool isolatedInPart() const { return IsServerMsgId(_key.universalId) && (!_migrated || _part.skippedBefore() != 0); } bool isolatedInMigrated() const { return IsServerMsgId(ServerMaxMsgId + _key.universalId) && (_migrated->skippedAfter() != 0); } Key _key; SparseIdsSlice _part; base::optional<SparseIdsSlice> _migrated; }; class SparseIdsSliceBuilder { public: using Key = SparseIdsSlice::Key; SparseIdsSliceBuilder(Key key, int limitBefore, int limitAfter); bool applyInitial(const Storage::SparseIdsListResult &result); bool applyUpdate(const Storage::SparseIdsSliceUpdate &update); bool removeOne(MsgId messageId); bool removeAll(); bool invalidateBottom(); void checkInsufficient(); struct AroundData { MsgId aroundId = 0; Data::LoadDirection direction = Data::LoadDirection::Around; inline bool operator<(const AroundData &other) const { return (aroundId < other.aroundId) || ((aroundId == other.aroundId) && (direction < other.direction)); } }; auto insufficientAround() const { return _insufficientAround.events(); } SparseIdsSlice snapshot() const; private: enum class RequestDirection { Before, After, }; void requestMessages(RequestDirection direction); void requestMessagesCount(); void fillSkippedAndSliceToLimits(); void sliceToLimits(); void mergeSliceData( base::optional<int> count, const base::flat_set<MsgId> &messageIds, base::optional<int> skippedBefore = base::none, base::optional<int> skippedAfter = base::none); Key _key; base::flat_set<MsgId> _ids; MsgRange _range; base::optional<int> _fullCount; base::optional<int> _skippedBefore; base::optional<int> _skippedAfter; int _limitBefore = 0; int _limitAfter = 0; rpl::event_stream<AroundData> _insufficientAround; }; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/data/data_types.cpp��������������������������������������������0000664�0000000�0000000�00000002377�13262451251�0023206�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "data/data_types.h" #include "data/data_document.h" void AudioMsgId::setTypeFromAudio() { if (_audio->isVoiceMessage() || _audio->isVideoMessage()) { _type = Type::Voice; } else if (_audio->isVideoFile()) { _type = Type::Video; } else if (_audio->isAudioFile()) { _type = Type::Song; } else { _type = Type::Unknown; } } void MessageCursor::fillFrom(const QTextEdit *edit) { QTextCursor c = edit->textCursor(); position = c.position(); anchor = c.anchor(); QScrollBar *s = edit->verticalScrollBar(); scroll = (s && (s->value() != s->maximum())) ? s->value() : QFIXED_MAX; } void MessageCursor::applyTo(QTextEdit *edit) { auto cursor = edit->textCursor(); cursor.setPosition(anchor, QTextCursor::MoveAnchor); cursor.setPosition(position, QTextCursor::KeepAnchor); edit->setTextCursor(cursor); if (auto scrollbar = edit->verticalScrollBar()) { scrollbar->setValue(scroll); } } HistoryItem *FileClickHandler::getActionItem() const { return context() ? App::histItemById(context()) : nullptr; } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/data/data_types.h����������������������������������������������0000664�0000000�0000000�00000025513�13262451251�0022650�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "base/value_ordering.h" class HistoryItem; using HistoryItemsList = std::vector<not_null<HistoryItem*>>; namespace Data { struct UploadState { UploadState(int size) : size(size) { } int offset = 0; int size = 0; bool waitingForAlbum = false; }; } // namespace Data struct MessageGroupId { using Underlying = uint64; enum Type : Underlying { None = 0, } value; MessageGroupId(Type value = None) : value(value) { } static MessageGroupId FromRaw(Underlying value) { return static_cast<Type>(value); } explicit operator bool() const { return value != None; } Underlying raw() const { return static_cast<Underlying>(value); } friend inline Type value_ordering_helper(MessageGroupId value) { return value.value; } }; class PeerData; class UserData; class ChatData; class ChannelData; using UserId = int32; using ChatId = int32; using ChannelId = int32; using FeedId = int32; constexpr auto NoChannel = ChannelId(0); using PeerId = uint64; constexpr auto PeerIdMask = PeerId(0xFFFFFFFFULL); constexpr auto PeerIdTypeMask = PeerId(0x300000000ULL); constexpr auto PeerIdUserShift = PeerId(0x000000000ULL); constexpr auto PeerIdChatShift = PeerId(0x100000000ULL); constexpr auto PeerIdChannelShift = PeerId(0x200000000ULL); inline bool peerIsUser(const PeerId &id) { return (id & PeerIdTypeMask) == PeerIdUserShift; } inline bool peerIsChat(const PeerId &id) { return (id & PeerIdTypeMask) == PeerIdChatShift; } inline bool peerIsChannel(const PeerId &id) { return (id & PeerIdTypeMask) == PeerIdChannelShift; } inline PeerId peerFromUser(UserId user_id) { return PeerIdUserShift | uint64(uint32(user_id)); } inline PeerId peerFromChat(ChatId chat_id) { return PeerIdChatShift | uint64(uint32(chat_id)); } inline PeerId peerFromChannel(ChannelId channel_id) { return PeerIdChannelShift | uint64(uint32(channel_id)); } inline PeerId peerFromUser(const MTPint &user_id) { return peerFromUser(user_id.v); } inline PeerId peerFromChat(const MTPint &chat_id) { return peerFromChat(chat_id.v); } inline PeerId peerFromChannel(const MTPint &channel_id) { return peerFromChannel(channel_id.v); } inline int32 peerToBareInt(const PeerId &id) { return int32(uint32(id & PeerIdMask)); } inline UserId peerToUser(const PeerId &id) { return peerIsUser(id) ? peerToBareInt(id) : 0; } inline ChatId peerToChat(const PeerId &id) { return peerIsChat(id) ? peerToBareInt(id) : 0; } inline ChannelId peerToChannel(const PeerId &id) { return peerIsChannel(id) ? peerToBareInt(id) : NoChannel; } inline MTPint peerToBareMTPInt(const PeerId &id) { return MTP_int(peerToBareInt(id)); } inline PeerId peerFromMTP(const MTPPeer &peer) { switch (peer.type()) { case mtpc_peerUser: return peerFromUser(peer.c_peerUser().vuser_id); case mtpc_peerChat: return peerFromChat(peer.c_peerChat().vchat_id); case mtpc_peerChannel: return peerFromChannel(peer.c_peerChannel().vchannel_id); } return 0; } inline MTPpeer peerToMTP(const PeerId &id) { if (peerIsUser(id)) { return MTP_peerUser(peerToBareMTPInt(id)); } else if (peerIsChat(id)) { return MTP_peerChat(peerToBareMTPInt(id)); } else if (peerIsChannel(id)) { return MTP_peerChannel(peerToBareMTPInt(id)); } return MTP_peerUser(MTP_int(0)); } using MsgId = int32; constexpr auto StartClientMsgId = MsgId(-0x7FFFFFFF); constexpr auto EndClientMsgId = MsgId(-0x40000000); constexpr auto ShowAtTheEndMsgId = MsgId(-0x40000000); constexpr auto SwitchAtTopMsgId = MsgId(-0x3FFFFFFF); constexpr auto ShowAtProfileMsgId = MsgId(-0x3FFFFFFE); constexpr auto ShowAndStartBotMsgId = MsgId(-0x3FFFFFD); constexpr auto ShowAtGameShareMsgId = MsgId(-0x3FFFFFC); constexpr auto ServerMaxMsgId = MsgId(0x3FFFFFFF); constexpr auto ShowAtUnreadMsgId = MsgId(0); constexpr inline bool IsClientMsgId(MsgId id) { return (id >= StartClientMsgId && id < EndClientMsgId); } constexpr inline bool IsServerMsgId(MsgId id) { return (id > 0 && id < ServerMaxMsgId); } struct MsgRange { MsgRange() = default; MsgRange(MsgId from, MsgId till) : from(from), till(till) { } MsgId from = 0; MsgId till = 0; }; inline bool operator==(const MsgRange &a, const MsgRange &b) { return (a.from == b.from) && (a.till == b.till); } inline bool operator!=(const MsgRange &a, const MsgRange &b) { return !(a == b); } struct FullMsgId { constexpr FullMsgId() = default; constexpr FullMsgId(ChannelId channel, MsgId msg) : channel(channel), msg(msg) { } explicit operator bool() const { return msg != 0; } inline constexpr bool operator<(const FullMsgId &other) const { if (channel < other.channel) { return true; } else if (channel > other.channel) { return false; } return msg < other.msg; } inline constexpr bool operator>(const FullMsgId &other) const { return other < *this; } inline constexpr bool operator<=(const FullMsgId &other) const { return !(other < *this); } inline constexpr bool operator>=(const FullMsgId &other) const { return !(*this < other); } inline constexpr bool operator==(const FullMsgId &other) const { return (channel == other.channel) && (msg == other.msg); } inline constexpr bool operator!=(const FullMsgId &other) const { return !(*this == other); } ChannelId channel = NoChannel; MsgId msg = 0; }; using MessageIdsList = std::vector<FullMsgId>; inline PeerId peerFromMessage(const MTPmessage &msg) { auto compute = [](auto &message) { auto from_id = message.has_from_id() ? peerFromUser(message.vfrom_id) : 0; auto to_id = peerFromMTP(message.vto_id); auto out = message.is_out(); return (out || !peerIsUser(to_id)) ? to_id : from_id; }; switch (msg.type()) { case mtpc_message: return compute(msg.c_message()); case mtpc_messageService: return compute(msg.c_messageService()); } return 0; } inline MTPDmessage::Flags flagsFromMessage(const MTPmessage &msg) { switch (msg.type()) { case mtpc_message: return msg.c_message().vflags.v; case mtpc_messageService: return mtpCastFlags(msg.c_messageService().vflags.v); } return 0; } inline MsgId idFromMessage(const MTPmessage &msg) { switch (msg.type()) { case mtpc_messageEmpty: return msg.c_messageEmpty().vid.v; case mtpc_message: return msg.c_message().vid.v; case mtpc_messageService: return msg.c_messageService().vid.v; } Unexpected("Type in idFromMessage()"); } inline TimeId dateFromMessage(const MTPmessage &msg) { switch (msg.type()) { case mtpc_message: return msg.c_message().vdate.v; case mtpc_messageService: return msg.c_messageService().vdate.v; } return 0; } class DocumentData; class PhotoData; struct WebPageData; struct GameData; class AudioMsgId; class PhotoClickHandler; class PhotoOpenClickHandler; class PhotoSaveClickHandler; class PhotoCancelClickHandler; class DocumentClickHandler; class DocumentSaveClickHandler; class DocumentOpenClickHandler; class DocumentCancelClickHandler; class GifOpenClickHandler; class VoiceSeekClickHandler; using PhotoId = uint64; using VideoId = uint64; using AudioId = uint64; using DocumentId = uint64; using WebPageId = uint64; using GameId = uint64; constexpr auto CancelledWebPageId = WebPageId(0xFFFFFFFFFFFFFFFFULL); using PreparedPhotoThumbs = QMap<char, QPixmap>; // [0] == -1 -- counting, [0] == -2 -- could not count using VoiceWaveform = QVector<signed char>; enum ActionOnLoad { ActionOnLoadNone, ActionOnLoadOpen, ActionOnLoadOpenWith, ActionOnLoadPlayInline }; enum LocationType { UnknownFileLocation = 0, // 1, 2, etc are used as "version" value in mediaKey() method. DocumentFileLocation = 0x4e45abe9, // mtpc_inputDocumentFileLocation AudioFileLocation = 0x74dc404d, // mtpc_inputAudioFileLocation VideoFileLocation = 0x3d0364ec, // mtpc_inputVideoFileLocation }; enum FileStatus { FileDownloadFailed = -2, FileUploadFailed = -1, FileReady = 1, }; // Don't change the values. This type is used for serialization. enum DocumentType { FileDocument = 0, VideoDocument = 1, SongDocument = 2, StickerDocument = 3, AnimatedDocument = 4, VoiceDocument = 5, RoundVideoDocument = 6, }; using MediaKey = QPair<uint64, uint64>; class AudioMsgId { public: enum class Type { Unknown, Voice, Song, Video, }; AudioMsgId() = default; AudioMsgId( DocumentData *audio, const FullMsgId &msgId, uint32 playId = 0) : _audio(audio) , _contextId(msgId) , _playId(playId) { setTypeFromAudio(); } Type type() const { return _type; } DocumentData *audio() const { return _audio; } FullMsgId contextId() const { return _contextId; } uint32 playId() const { return _playId; } explicit operator bool() const { return _audio != nullptr; } private: void setTypeFromAudio(); DocumentData *_audio = nullptr; Type _type = Type::Unknown; FullMsgId _contextId; uint32 _playId = 0; }; inline bool operator<(const AudioMsgId &a, const AudioMsgId &b) { if (quintptr(a.audio()) < quintptr(b.audio())) { return true; } else if (quintptr(b.audio()) < quintptr(a.audio())) { return false; } else if (a.contextId() < b.contextId()) { return true; } else if (b.contextId() < a.contextId()) { return false; } return (a.playId() < b.playId()); } inline bool operator==(const AudioMsgId &a, const AudioMsgId &b) { return (a.audio() == b.audio()) && (a.contextId() == b.contextId()) && (a.playId() == b.playId()); } inline bool operator!=(const AudioMsgId &a, const AudioMsgId &b) { return !(a == b); } inline MsgId clientMsgId() { static MsgId CurrentClientMsgId = StartClientMsgId; Assert(CurrentClientMsgId < EndClientMsgId); return CurrentClientMsgId++; } struct MessageCursor { MessageCursor() = default; MessageCursor(int position, int anchor, int scroll) : position(position) , anchor(anchor) , scroll(scroll) { } MessageCursor(const QTextEdit *edit) { fillFrom(edit); } void fillFrom(const QTextEdit *edit); void applyTo(QTextEdit *edit); int position = 0; int anchor = 0; int scroll = QFIXED_MAX; }; inline bool operator==( const MessageCursor &a, const MessageCursor &b) { return (a.position == b.position) && (a.anchor == b.anchor) && (a.scroll == b.scroll); } struct SendAction { enum class Type { Typing, RecordVideo, UploadVideo, RecordVoice, UploadVoice, RecordRound, UploadRound, UploadPhoto, UploadFile, ChooseLocation, ChooseContact, PlayGame, }; SendAction( Type type, TimeMs until, int progress = 0) : type(type) , until(until) , progress(progress) { } Type type = Type::Typing; TimeMs until = 0; int progress = 0; }; class FileClickHandler : public LeftButtonClickHandler { public: FileClickHandler(FullMsgId context) : _context(context) { } void setMessageId(FullMsgId context) { _context = context; } FullMsgId context() const { return _context; } protected: HistoryItem *getActionItem() const; private: FullMsgId _context; }; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/data/data_user_photos.cpp��������������������������������������0000664�0000000�0000000�00000013466�13262451251�0024415�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "data/data_user_photos.h" #include "auth_session.h" #include "apiwrap.h" #include "storage/storage_facade.h" #include "storage/storage_user_photos.h" class UserPhotosSliceBuilder { public: using Key = UserPhotosSlice::Key; UserPhotosSliceBuilder(Key key, int limitBefore, int limitAfter); bool applyUpdate(const Storage::UserPhotosResult &update); bool applyUpdate(const Storage::UserPhotosSliceUpdate &update); void checkInsufficientPhotos(); auto insufficientPhotosAround() const { return _insufficientPhotosAround.events(); } UserPhotosSlice snapshot() const; private: void mergeSliceData( base::optional<int> count, const std::deque<PhotoId> &photoIds, base::optional<int> skippedBefore, int skippedAfter); void sliceToLimits(); Key _key; std::deque<PhotoId> _ids; base::optional<int> _fullCount; base::optional<int> _skippedBefore; int _skippedAfter = 0; int _limitBefore = 0; int _limitAfter = 0; rpl::event_stream<PhotoId> _insufficientPhotosAround; }; UserPhotosSlice::UserPhotosSlice(Key key) : UserPhotosSlice( key, {}, base::none, base::none, base::none) { } UserPhotosSlice::UserPhotosSlice( Key key, std::deque<PhotoId> &&ids, base::optional<int> fullCount, base::optional<int> skippedBefore, base::optional<int> skippedAfter) : _key(key) , _ids(std::move(ids)) , _fullCount(fullCount) , _skippedBefore(skippedBefore) , _skippedAfter(skippedAfter) { } void UserPhotosSlice::reverse() { ranges::reverse(_ids); std::swap(_skippedBefore, _skippedAfter); } base::optional<int> UserPhotosSlice::indexOf(PhotoId photoId) const { auto it = ranges::find(_ids, photoId); if (it != _ids.end()) { return (it - _ids.begin()); } return base::none; } PhotoId UserPhotosSlice::operator[](int index) const { Expects(index >= 0 && index < size()); return *(_ids.begin() + index); } base::optional<int> UserPhotosSlice::distance(const Key &a, const Key &b) const { if (a.userId != _key.userId || b.userId != _key.userId) { return base::none; } if (auto i = indexOf(a.photoId)) { if (auto j = indexOf(b.photoId)) { return *j - *i; } } return base::none; } UserPhotosSliceBuilder::UserPhotosSliceBuilder( Key key, int limitBefore, int limitAfter) : _key(key) , _limitBefore(limitBefore) , _limitAfter(limitAfter) { } bool UserPhotosSliceBuilder::applyUpdate(const Storage::UserPhotosResult &update) { mergeSliceData( update.count, update.photoIds, update.skippedBefore, update.skippedAfter); return true; } bool UserPhotosSliceBuilder::applyUpdate(const Storage::UserPhotosSliceUpdate &update) { if (update.userId != _key.userId) { return false; } auto idsCount = update.photoIds ? int(update.photoIds->size()) : 0; mergeSliceData( update.count, update.photoIds ? *update.photoIds : std::deque<PhotoId> {}, update.count | func::add(-idsCount), 0); return true; } void UserPhotosSliceBuilder::checkInsufficientPhotos() { sliceToLimits(); } void UserPhotosSliceBuilder::mergeSliceData( base::optional<int> count, const std::deque<PhotoId> &photoIds, base::optional<int> skippedBefore, int skippedAfter) { if (photoIds.empty()) { if (_fullCount != count) { _fullCount = count; if (_fullCount && *_fullCount <= _ids.size()) { _fullCount = _ids.size(); _skippedBefore = _skippedAfter = 0; } } } else { if (count) { _fullCount = count; } _skippedAfter = skippedAfter; _ids = photoIds; if (_fullCount) { _skippedBefore = *_fullCount - _skippedAfter - int(_ids.size()); } } sliceToLimits(); } void UserPhotosSliceBuilder::sliceToLimits() { auto aroundIt = ranges::find(_ids, _key.photoId); auto removeFromBegin = (aroundIt - _ids.begin() - _limitBefore); auto removeFromEnd = (_ids.end() - aroundIt - _limitAfter - 1); if (removeFromEnd > 0) { _ids.erase(_ids.end() - removeFromEnd, _ids.end()); _skippedAfter += removeFromEnd; } if (removeFromBegin > 0) { _ids.erase(_ids.begin(), _ids.begin() + removeFromBegin); if (_skippedBefore) { *_skippedBefore += removeFromBegin; } } else if (removeFromBegin < 0 && (!_skippedBefore || *_skippedBefore > 0)) { _insufficientPhotosAround.fire(_ids.empty() ? 0 : _ids.front()); } } UserPhotosSlice UserPhotosSliceBuilder::snapshot() const { return UserPhotosSlice( _key, base::duplicate(_ids), _fullCount, _skippedBefore, _skippedAfter); } rpl::producer<UserPhotosSlice> UserPhotosViewer( UserPhotosSlice::Key key, int limitBefore, int limitAfter) { return [key, limitBefore, limitAfter](auto consumer) { auto lifetime = rpl::lifetime(); auto builder = lifetime.make_state<UserPhotosSliceBuilder>( key, limitBefore, limitAfter); auto applyUpdate = [=](auto &&update) { if (builder->applyUpdate(std::forward<decltype(update)>(update))) { consumer.put_next(builder->snapshot()); } }; auto requestPhotosAround = [user = App::user(key.userId)]( PhotoId photoId) { Auth().api().requestUserPhotos(user, photoId); }; builder->insufficientPhotosAround() | rpl::start_with_next(requestPhotosAround, lifetime); Auth().storage().userPhotosSliceUpdated() | rpl::start_with_next(applyUpdate, lifetime); Auth().storage().query(Storage::UserPhotosQuery( key, limitBefore, limitAfter )) | rpl::start_with_next_done( applyUpdate, [=] { builder->checkInsufficientPhotos(); }, lifetime); return lifetime; }; } rpl::producer<UserPhotosSlice> UserPhotosReversedViewer( UserPhotosSlice::Key key, int limitBefore, int limitAfter) { return UserPhotosViewer( key, limitBefore, limitAfter ) | rpl::map([](UserPhotosSlice &&slice) { slice.reverse(); return std::move(slice); }); }����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/data/data_user_photos.h����������������������������������������0000664�0000000�0000000�00000002701�13262451251�0024050�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "storage/storage_user_photos.h" #include "base/weak_ptr.h" class UserPhotosSlice { public: using Key = Storage::UserPhotosKey; UserPhotosSlice(Key key); UserPhotosSlice( Key key, std::deque<PhotoId> &&ids, base::optional<int> fullCount, base::optional<int> skippedBefore, base::optional<int> skippedAfter); void reverse(); const Key &key() const { return _key; } base::optional<int> fullCount() const { return _fullCount; } base::optional<int> skippedBefore() const { return _skippedBefore; } base::optional<int> skippedAfter() const { return _skippedAfter; } base::optional<int> indexOf(PhotoId msgId) const; int size() const { return _ids.size(); } PhotoId operator[](int index) const; base::optional<int> distance(const Key &a, const Key &b) const; private: Key _key; std::deque<PhotoId> _ids; base::optional<int> _fullCount; base::optional<int> _skippedBefore; base::optional<int> _skippedAfter; friend class UserPhotosSliceBuilder; }; rpl::producer<UserPhotosSlice> UserPhotosViewer( UserPhotosSlice::Key key, int limitBefore, int limitAfter); rpl::producer<UserPhotosSlice> UserPhotosReversedViewer( UserPhotosSlice::Key key, int limitBefore, int limitAfter); ���������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/data/data_web_page.cpp�����������������������������������������0000664�0000000�0000000�00000005736�13262451251�0023615�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "data/data_web_page.h" #include "auth_session.h" #include "apiwrap.h" #include "mainwidget.h" #include "ui/text/text_entity.h" namespace { QString SiteNameFromUrl(const QString &url) { QUrl u(url); QString pretty = u.isValid() ? u.toDisplayString() : url; QRegularExpressionMatch m = QRegularExpression(qsl("^[a-zA-Z0-9]+://")).match(pretty); if (m.hasMatch()) pretty = pretty.mid(m.capturedLength()); int32 slash = pretty.indexOf('/'); if (slash > 0) pretty = pretty.mid(0, slash); QStringList components = pretty.split('.', QString::SkipEmptyParts); if (components.size() >= 2) { components = components.mid(components.size() - 2); return components.at(0).at(0).toUpper() + components.at(0).mid(1) + '.' + components.at(1); } return QString(); } } // namespace bool WebPageData::applyChanges( const QString &newType, const QString &newUrl, const QString &newDisplayUrl, const QString &newSiteName, const QString &newTitle, const TextWithEntities &newDescription, PhotoData *newPhoto, DocumentData *newDocument, int newDuration, const QString &newAuthor, int newPendingTill) { if (newPendingTill != 0 && (!url.isEmpty() || pendingTill < 0) && (!pendingTill || pendingTill == newPendingTill || newPendingTill < -1)) { return false; } const auto resultType = toWebPageType(newType); const auto resultUrl = TextUtilities::Clean(newUrl); const auto resultDisplayUrl = TextUtilities::Clean( newDisplayUrl); const auto possibleSiteName = TextUtilities::Clean( newSiteName); const auto resultTitle = TextUtilities::SingleLine( newTitle); const auto resultAuthor = TextUtilities::Clean(newAuthor); const auto viewTitleText = resultTitle.isEmpty() ? TextUtilities::SingleLine(resultAuthor) : resultTitle; const auto resultSiteName = [&] { if (!possibleSiteName.isEmpty()) { return possibleSiteName; } else if (!newDescription.text.isEmpty() && viewTitleText.isEmpty() && !resultUrl.isEmpty()) { return SiteNameFromUrl(resultUrl); } return QString(); }(); if (type == resultType && url == resultUrl && displayUrl == resultDisplayUrl && siteName == resultSiteName && title == resultTitle && description.text == newDescription.text && photo == newPhoto && document == newDocument && duration == newDuration && author == resultAuthor && pendingTill == newPendingTill) { return false; } if (pendingTill > 0 && newPendingTill <= 0) { Auth().api().clearWebPageRequest(this); } type = resultType; url = resultUrl; displayUrl = resultDisplayUrl; siteName = resultSiteName; title = resultTitle; description = newDescription; photo = newPhoto; document = newDocument; duration = newDuration; author = resultAuthor; pendingTill = newPendingTill; ++version; return true; }����������������������������������tdesktop-1.2.17/Telegram/SourceFiles/data/data_web_page.h�������������������������������������������0000664�0000000�0000000�00000003651�13262451251�0023254�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "data/data_photo.h" #include "data/data_document.h" enum WebPageType { WebPagePhoto, WebPageVideo, WebPageProfile, WebPageArticle }; inline WebPageType toWebPageType(const QString &type) { if (type == qstr("photo")) return WebPagePhoto; if (type == qstr("video")) return WebPageVideo; if (type == qstr("profile")) return WebPageProfile; return WebPageArticle; } struct WebPageData { WebPageData(const WebPageId &id) : id(id) { } WebPageData( const WebPageId &id, WebPageType type, const QString &url, const QString &displayUrl, const QString &siteName, const QString &title, const TextWithEntities &description, DocumentData *document, PhotoData *photo, int duration, const QString &author, int pendingTill) : id(id) , type(type) , url(url) , displayUrl(displayUrl) , siteName(siteName) , title(title) , description(description) , duration(duration) , author(author) , photo(photo) , document(document) , pendingTill(pendingTill) { } void forget() { if (document) document->forget(); if (photo) photo->forget(); } bool applyChanges( const QString &newType, const QString &newUrl, const QString &newDisplayUrl, const QString &newSiteName, const QString &newTitle, const TextWithEntities &newDescription, PhotoData *newPhoto, DocumentData *newDocument, int newDuration, const QString &newAuthor, int newPendingTill); WebPageId id = 0; WebPageType type = WebPageArticle; QString url; QString displayUrl; QString siteName; QString title; TextWithEntities description; int duration = 0; QString author; PhotoData *photo = nullptr; DocumentData *document = nullptr; int pendingTill = 0; int version = 0; }; ���������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/dialogs/�������������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0021045�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/dialogs/dialogs.style������������������������������������������0000664�0000000�0000000�00000016513�13262451251�0023557�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ using "basic.style"; using "ui/widgets/widgets.style"; dialogsUnreadFont: font(12px bold); dialogsUnreadHeight: 19px; dialogsUnreadPadding: 5px; dialogsRipple: RippleAnimation(defaultRippleAnimation) { color: dialogsRippleBg; } dialogsTextFont: font(fsize); dialogsTextStyle: TextStyle(defaultTextStyle) { font: dialogsTextFont; linkFont: dialogsTextFont; linkFontOver: dialogsTextFont; } dialogsDateFont: font(13px); dialogsDateSkip: 5px; dialogsNameTop: 2px; dialogsRowHeight: 62px; dialogsFilterPadding: point(7px, 7px); dialogsFilterSkip: 4px; dialogsPhotoSize: 46px; dialogsPhotoPadding: 12px; dialogsPadding: point(10px, 8px); dialogsImportantBarHeight: 37px; dialogsSkip: 8px; dialogsWidthDuration: 120; dialogsTextWidthMin: 150px; dialogsScroll: ScrollArea(defaultScrollArea) { topsh: 0px; bottomsh: 0px; } dialogsTextPalette: TextPalette(defaultTextPalette) { linkFg: dialogsTextFgService; } dialogsTextPaletteOver: TextPalette(defaultTextPalette) { linkFg: dialogsTextFgServiceOver; } dialogsTextPaletteActive: TextPalette(defaultTextPalette) { linkFg: dialogsTextFgServiceActive; } dialogsTextPaletteDraft: TextPalette(defaultTextPalette) { linkFg: dialogsDraftFg; } dialogsTextPaletteDraftOver: TextPalette(defaultTextPalette) { linkFg: dialogsDraftFgOver; } dialogsTextPaletteDraftActive: TextPalette(defaultTextPalette) { linkFg: dialogsDraftFgActive; } dialogsMenuToggle: IconButton { width: 40px; height: 40px; icon: icon {{ "dialogs_menu", dialogsMenuIconFg }}; iconOver: icon {{ "dialogs_menu", dialogsMenuIconFgOver }}; iconPosition: point(10px, 10px); rippleAreaPosition: point(0px, 0px); rippleAreaSize: 40px; ripple: RippleAnimation(defaultRippleAnimation) { color: windowBgOver; } } dialogsLock: IconButton(dialogsMenuToggle) { icon: icon {{ "dialogs_lock", dialogsMenuIconFg }}; iconOver: icon {{ "dialogs_lock", dialogsMenuIconFgOver }}; } dialogsUnlockIcon: icon {{ "dialogs_unlock", dialogsMenuIconFg }}; dialogsUnlockIconOver: icon {{ "dialogs_unlock", dialogsMenuIconFgOver }}; dialogsCalendar: IconButton { width: 29px; height: 32px; icon: icon {{ "dialogs_calendar", dialogsMenuIconFg }}; iconOver: icon {{ "dialogs_calendar", dialogsMenuIconFgOver }}; iconPosition: point(0px, 5px); } dialogsSearchFrom: IconButton(dialogsCalendar) { width: 26px; icon: icon {{ "dialogs_search_from", dialogsMenuIconFg }}; iconOver: icon {{ "dialogs_search_from", dialogsMenuIconFgOver }}; } dialogsSearchFromPadding: margins(10px, 10px, 10px, 10px); dialogsFilter: FlatInput(defaultFlatInput) { font: font(fsize); width: 240px; height: 32px; textMrg: margins(12px, 3px, 30px, 3px); } dialogsCancelSearchInPeer: IconButton(dialogsMenuToggle) { icon: icon {{ "dialogs_cancel_search", dialogsMenuIconFg }}; iconOver: icon {{ "dialogs_cancel_search", dialogsMenuIconFgOver }}; iconPosition: point(11px, 11px); rippleAreaPosition: point(3px, 3px); rippleAreaSize: 34px; } dialogsCancelSearch: CrossButton { width: 32px; height: 32px; cross: CrossAnimation { size: 32px; skip: 10px; stroke: 2px; minScale: 0.3; } crossFg: dialogsMenuIconFg; crossFgOver: dialogsMenuIconFgOver; crossPosition: point(0px, 0px); duration: 150; loadingPeriod: 1000; ripple: emptyRippleAnimation; } dialogsChatTypeSkip: 22px; dialogsChatIcon: icon {{ "dialogs_chat", dialogsChatIconFg, point(1px, 4px) }}; dialogsChatIconOver: icon {{ "dialogs_chat", dialogsChatIconFgOver, point(1px, 4px) }}; dialogsChatIconActive: icon {{ "dialogs_chat", dialogsChatIconFgActive, point(1px, 4px) }}; dialogsChannelIcon: icon {{ "dialogs_channel", dialogsChatIconFg, point(3px, 4px) }}; dialogsChannelIconOver: icon {{ "dialogs_channel", dialogsChatIconFgOver, point(3px, 4px) }}; dialogsChannelIconActive: icon {{ "dialogs_channel", dialogsChatIconFgActive, point(3px, 4px) }}; dialogsFeedIcon: icon {{ "dialogs_feed", dialogsChatIconFg, point(4px, 4px) }}; dialogsFeedIconOver: icon {{ "dialogs_feed", dialogsChatIconFgOver, point(4px, 4px) }}; dialogsFeedIconActive: icon {{ "dialogs_feed", dialogsChatIconFgActive, point(4px, 4px) }}; dialogsSendStateSkip: 20px; dialogsSendingIcon: icon {{ "dialogs_sending", dialogsSendingIconFg, point(8px, 4px) }}; dialogsSendingIconOver: icon {{ "dialogs_sending", dialogsSendingIconFgOver, point(8px, 4px) }}; dialogsSendingIconActive: icon {{ "dialogs_sending", dialogsSendingIconFgActive, point(8px, 4px) }}; dialogsSentIcon: icon {{ "dialogs_sent", dialogsSentIconFg, point(10px, 4px) }}; dialogsSentIconOver: icon {{ "dialogs_sent", dialogsSentIconFgOver, point(10px, 4px) }}; dialogsSentIconActive: icon {{ "dialogs_sent", dialogsSentIconFgActive, point(10px, 4px) }}; dialogsReceivedIcon: icon {{ "dialogs_received", dialogsSentIconFg, point(5px, 4px) }}; dialogsReceivedIconOver: icon {{ "dialogs_received", dialogsSentIconFgOver, point(5px, 4px) }}; dialogsReceivedIconActive: icon {{ "dialogs_received", dialogsSentIconFgActive, point(5px, 4px) }}; dialogsPinnedIcon: icon {{ "dialogs_pinned", dialogsUnreadBgMuted }}; dialogsPinnedIconOver: icon {{ "dialogs_pinned", dialogsUnreadBgMutedOver }}; dialogsPinnedIconActive: icon {{ "dialogs_pinned", dialogsUnreadBgMutedActive }}; dialogsVerifiedIcon: icon { { "dialogs_verified_star", dialogsVerifiedIconBg, point(4px, 2px) }, { "dialogs_verified_check", dialogsVerifiedIconFg, point(7px, 7px) }, }; dialogsVerifiedIconOver: icon { { "dialogs_verified_star", dialogsVerifiedIconBgOver, point(4px, 2px) }, { "dialogs_verified_check", dialogsVerifiedIconFgOver, point(7px, 7px) }, }; dialogsVerifiedIconActive: icon { { "dialogs_verified_star", dialogsVerifiedIconBgActive, point(4px, 2px) }, { "dialogs_verified_check", dialogsVerifiedIconFgActive, point(7px, 7px) }, }; historySendingIcon: icon {{ "dialogs_sending", historySendingOutIconFg, point(5px, 5px) }}; historySendingInvertedIcon: icon {{ "dialogs_sending", historySendingInvertedIconFg, point(5px, 5px) }}; historyViewsSendingIcon: icon {{ "dialogs_sending", historySendingInIconFg, point(3px, 0px) }}; historyViewsSendingInvertedIcon: icon {{ "dialogs_sending", historySendingInvertedIconFg, point(3px, 0px) }}; dialogsUpdateButton: FlatButton { color: activeButtonFg; overColor: activeButtonFgOver; bgColor: activeButtonBg; overBgColor: activeButtonBgOver; width: -34px; height: 46px; textTop: 14px; font: semiboldFont; overFont: semiboldFont; ripple: RippleAnimation(defaultRippleAnimation) { color: activeButtonBgRipple; } } dialogsInstallUpdate: icon {{ "install_update", activeButtonFg }}; dialogsInstallUpdateOver: icon {{ "install_update", activeButtonFgOver }}; dialogsForwardHeight: 32px; dialogsForwardTextLeft: 35px; dialogsForwardTextTop: 6px; dialogsForwardCancel: IconButton { width: 34px; height: dialogsForwardHeight; icon: dialogsForwardCancelIcon; iconOver: dialogsForwardCancelIcon; iconPosition: point(12px, 11px); } dialogsForwardFont: semiboldFont; dialogsSearchInHeight: 52px; dialogsSearchInPhotoSize: 36px; dialogsSearchInPhotoPadding: 10px; dialogsSearchInSkip: 7px; dialogsSearchFromStyle: TextStyle(defaultTextStyle) { font: normalFont; linkFont: semiboldFont; linkFontOver: semiboldFont; } dialogsSearchFromPalette: TextPalette(defaultTextPalette) { linkFg: dialogsNameFg; } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/dialogs/dialogs_entry.cpp��������������������������������������0000664�0000000�0000000�00000010057�13262451251�0024417�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "dialogs/dialogs_entry.h" #include "dialogs/dialogs_key.h" #include "dialogs/dialogs_indexed_list.h" #include "mainwidget.h" #include "styles/style_dialogs.h" #include "history/history_item.h" #include "history/history.h" namespace Dialogs { namespace { auto DialogsPosToTopShift = 0; uint64 DialogPosFromDate(const QDateTime &date) { if (date.isNull()) { return 0; } return (uint64(date.toTime_t()) << 32) | (++DialogsPosToTopShift); } uint64 PinnedDialogPos(int pinnedIndex) { return 0xFFFFFFFF00000000ULL + pinnedIndex; } } // namespace Entry::Entry(const Key &key) : lastItemTextCache(st::dialogsTextWidthMin) , _key(key) { } void Entry::cachePinnedIndex(int index) { if (_pinnedIndex != index) { const auto wasPinned = isPinnedDialog(); _pinnedIndex = index; updateChatListSortPosition(); updateChatListEntry(); if (wasPinned != isPinnedDialog()) { changedChatListPinHook(); } } } bool Entry::needUpdateInChatList() const { return inChatList(Dialogs::Mode::All) || shouldBeInChatList(); } void Entry::updateChatListSortPosition() { _sortKeyInChatList = isPinnedDialog() ? PinnedDialogPos(_pinnedIndex) : DialogPosFromDate(adjustChatListDate()); if (needUpdateInChatList()) { setChatListExistence(true); } } void Entry::updateChatListExistence() { setChatListExistence(shouldBeInChatList()); } void Entry::setChatListExistence(bool exists) { if (const auto main = App::main()) { if (exists && _sortKeyInChatList) { main->createDialog(_key); updateChatListEntry(); } else { main->removeDialog(_key); } } } QDateTime Entry::adjustChatListDate() const { return chatsListDate(); } void Entry::changedInChatListHook(Dialogs::Mode list, bool added) { } void Entry::changedChatListPinHook() { } RowsByLetter &Entry::chatListLinks(Mode list) { return _chatListLinks[static_cast<int>(list)]; } const RowsByLetter &Entry::chatListLinks(Mode list) const { return _chatListLinks[static_cast<int>(list)]; } Row *Entry::mainChatListLink(Mode list) const { auto it = chatListLinks(list).find(0); Assert(it != chatListLinks(list).cend()); return it->second; } PositionChange Entry::adjustByPosInChatList( Mode list, not_null<IndexedList*> indexed) { const auto lnk = mainChatListLink(list); const auto movedFrom = lnk->pos(); indexed->adjustByPos(chatListLinks(list)); const auto movedTo = lnk->pos(); return { movedFrom, movedTo }; } void Entry::setChatsListDate(QDateTime date) { if (!_lastMessageDate.isNull() && _lastMessageDate >= date) { if (!inChatList(Dialogs::Mode::All)) { return; } } _lastMessageDate = date; updateChatListSortPosition(); } int Entry::posInChatList(Dialogs::Mode list) const { return mainChatListLink(list)->pos(); } not_null<Row*> Entry::addToChatList( Mode list, not_null<IndexedList*> indexed) { if (!inChatList(list)) { chatListLinks(list) = indexed->addToEnd(_key); changedInChatListHook(list, true); } return mainChatListLink(list); } void Entry::removeFromChatList( Dialogs::Mode list, not_null<Dialogs::IndexedList*> indexed) { if (inChatList(list)) { indexed->del(_key); chatListLinks(list).clear(); changedInChatListHook(list, false); } } void Entry::removeChatListEntryByLetter(Mode list, QChar letter) { Expects(letter != 0); if (inChatList(list)) { chatListLinks(list).remove(letter); } } void Entry::addChatListEntryByLetter( Mode list, QChar letter, not_null<Row*> row) { Expects(letter != 0); if (inChatList(list)) { chatListLinks(list).emplace(letter, row); } } void Entry::updateChatListEntry() const { if (const auto main = App::main()) { if (inChatList(Mode::All)) { main->repaintDialogRow( Mode::All, mainChatListLink(Mode::All)); if (inChatList(Mode::Important)) { main->repaintDialogRow( Mode::Important, mainChatListLink(Mode::Important)); } } } } } // namespace Dialogs ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/dialogs/dialogs_entry.h����������������������������������������0000664�0000000�0000000�00000005514�13262451251�0024066�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "base/flat_map.h" #include "dialogs/dialogs_key.h" namespace Dialogs { class Row; class IndexedList; using RowsByLetter = base::flat_map<QChar, not_null<Row*>>; enum class SortMode { Date = 0x00, Name = 0x01, Add = 0x02, }; enum class Mode { All = 0x00, Important = 0x01, }; struct PositionChange { int movedFrom; int movedTo; }; class Entry { public: Entry(const Key &key); PositionChange adjustByPosInChatList( Mode list, not_null<IndexedList*> indexed); bool inChatList(Mode list) const { return !chatListLinks(list).empty(); } int posInChatList(Mode list) const; not_null<Row*> addToChatList(Mode list, not_null<IndexedList*> indexed); void removeFromChatList(Mode list, not_null<IndexedList*> indexed); void removeChatListEntryByLetter(Mode list, QChar letter); void addChatListEntryByLetter( Mode list, QChar letter, not_null<Row*> row); void updateChatListEntry() const; bool isPinnedDialog() const { return _pinnedIndex > 0; } void cachePinnedIndex(int index); uint64 sortKeyInChatList() const { return _sortKeyInChatList; } void updateChatListSortPosition(); void setChatsListDate(QDateTime date); virtual void updateChatListExistence(); bool needUpdateInChatList() const; virtual bool toImportant() const = 0; virtual bool shouldBeInChatList() const = 0; virtual int chatListUnreadCount() const = 0; virtual bool chatListMutedBadge() const = 0; virtual HistoryItem *chatsListItem() const = 0; virtual const QString &chatsListName() const = 0; virtual const base::flat_set<QString> &chatsListNameWords() const = 0; virtual const base::flat_set<QChar> &chatsListFirstLetters() const = 0; virtual void loadUserpic() = 0; virtual void paintUserpic( Painter &p, int x, int y, int size) const = 0; void paintUserpicLeft( Painter &p, int x, int y, int w, int size) const { paintUserpic(p, rtl() ? (w - x - size) : x, y, size); } QDateTime chatsListDate() const { return _lastMessageDate; } virtual ~Entry() = default; mutable const HistoryItem *textCachedFor = nullptr; // cache mutable Text lastItemTextCache; private: virtual QDateTime adjustChatListDate() const; virtual void changedInChatListHook(Dialogs::Mode list, bool added); virtual void changedChatListPinHook(); void setChatListExistence(bool exists); RowsByLetter &chatListLinks(Mode list); const RowsByLetter &chatListLinks(Mode list) const; Row *mainChatListLink(Mode list) const; Dialogs::Key _key; RowsByLetter _chatListLinks[2]; uint64 _sortKeyInChatList = 0; int _pinnedIndex = 0; QDateTime _lastMessageDate; }; } // namespace Dialogs ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/dialogs/dialogs_indexed_list.cpp�������������������������������0000664�0000000�0000000�00000011540�13262451251�0025727�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "dialogs/dialogs_indexed_list.h" #include "auth_session.h" #include "data/data_session.h" #include "history/history.h" namespace Dialogs { IndexedList::IndexedList(SortMode sortMode) : _sortMode(sortMode) , _list(sortMode) , _empty(sortMode) { } RowsByLetter IndexedList::addToEnd(Key key) { RowsByLetter result; if (!_list.contains(key)) { result.emplace(0, _list.addToEnd(key)); for (auto ch : key.entry()->chatsListFirstLetters()) { auto j = _index.find(ch); if (j == _index.cend()) { j = _index.emplace( ch, std::make_unique<List>(_sortMode)).first; } result.emplace(ch, j->second->addToEnd(key)); } } return result; } Row *IndexedList::addByName(Key key) { if (const auto row = _list.getRow(key)) { return row; } Row *result = _list.addByName(key); for (auto ch : key.entry()->chatsListFirstLetters()) { auto j = _index.find(ch); if (j == _index.cend()) { j = _index.emplace( ch, std::make_unique<List>(_sortMode)).first; } j->second->addByName(key); } return result; } void IndexedList::adjustByPos(const RowsByLetter &links) { for (auto [ch, row] : links) { if (ch == QChar(0)) { _list.adjustByPos(row); } else { if (auto it = _index.find(ch); it != _index.cend()) { it->second->adjustByPos(row); } } } } void IndexedList::moveToTop(Key key) { if (_list.moveToTop(key)) { for (auto ch : key.entry()->chatsListFirstLetters()) { if (auto it = _index.find(ch); it != _index.cend()) { it->second->moveToTop(key); } } } } void IndexedList::movePinned(Row *row, int deltaSign) { auto swapPinnedIndexWith = find(row); Assert(swapPinnedIndexWith != cend()); if (deltaSign > 0) { ++swapPinnedIndexWith; } else { Assert(swapPinnedIndexWith != cbegin()); --swapPinnedIndexWith; } Auth().data().reorderTwoPinnedDialogs( row->key(), (*swapPinnedIndexWith)->key()); } void IndexedList::peerNameChanged( not_null<PeerData*> peer, const base::flat_set<QChar> &oldLetters) { Expects(_sortMode != SortMode::Date); if (const auto history = App::historyLoaded(peer)) { if (_sortMode == SortMode::Name) { adjustByName(history, oldLetters); } else { adjustNames(Dialogs::Mode::All, history, oldLetters); } } } void IndexedList::peerNameChanged( Mode list, not_null<PeerData*> peer, const base::flat_set<QChar> &oldLetters) { Expects(_sortMode == SortMode::Date); if (const auto history = App::historyLoaded(peer)) { adjustNames(list, history, oldLetters); } } void IndexedList::adjustByName( Key key, const base::flat_set<QChar> &oldLetters) { const auto mainRow = _list.adjustByName(key); if (!mainRow) return; auto toRemove = oldLetters; auto toAdd = base::flat_set<QChar>(); for (auto ch : key.entry()->chatsListFirstLetters()) { auto j = toRemove.find(ch); if (j == toRemove.cend()) { toAdd.insert(ch); } else { toRemove.erase(j); if (auto it = _index.find(ch); it != _index.cend()) { it->second->adjustByName(key); } } } for (auto ch : toRemove) { if (auto it = _index.find(ch); it != _index.cend()) { it->second->del(key, mainRow); } } if (!toAdd.empty()) { for (auto ch : toAdd) { auto j = _index.find(ch); if (j == _index.cend()) { j = _index.emplace( ch, std::make_unique<List>(_sortMode)).first; } j->second->addByName(key); } } } void IndexedList::adjustNames( Mode list, not_null<History*> history, const base::flat_set<QChar> &oldLetters) { const auto key = Dialogs::Key(history); auto mainRow = _list.getRow(key); if (!mainRow) return; auto toRemove = oldLetters; auto toAdd = base::flat_set<QChar>(); for (auto ch : key.entry()->chatsListFirstLetters()) { auto j = toRemove.find(ch); if (j == toRemove.cend()) { toAdd.insert(ch); } else { toRemove.erase(j); } } for (auto ch : toRemove) { if (_sortMode == SortMode::Date) { history->removeChatListEntryByLetter(list, ch); } if (auto it = _index.find(ch); it != _index.cend()) { it->second->del(key, mainRow); } } for (auto ch : toAdd) { auto j = _index.find(ch); if (j == _index.cend()) { j = _index.emplace( ch, std::make_unique<List>(_sortMode)).first; } auto row = j->second->addToEnd(key); if (_sortMode == SortMode::Date) { history->addChatListEntryByLetter(list, ch, row); } } } void IndexedList::del(Key key, Row *replacedBy) { if (_list.del(key, replacedBy)) { for (auto ch : key.entry()->chatsListFirstLetters()) { if (auto it = _index.find(ch); it != _index.cend()) { it->second->del(key, replacedBy); } } } } void IndexedList::clear() { _index.clear(); } IndexedList::~IndexedList() { clear(); } } // namespace Dialogs ����������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/dialogs/dialogs_indexed_list.h���������������������������������0000664�0000000�0000000�00000005113�13262451251�0025373�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "dialogs/dialogs_entry.h" #include "dialogs/dialogs_list.h" class History; namespace Dialogs { class IndexedList { public: IndexedList(SortMode sortMode); RowsByLetter addToEnd(Key key); Row *addByName(Key key); void adjustByPos(const RowsByLetter &links); void moveToTop(Key key); // row must belong to this indexed list all(). void movePinned(Row *row, int deltaSign); // For sortMode != SortMode::Date void peerNameChanged( not_null<PeerData*> peer, const base::flat_set<QChar> &oldChars); //For sortMode == SortMode::Date void peerNameChanged( Mode list, not_null<PeerData*> peer, const base::flat_set<QChar> &oldChars); void del(Key key, Row *replacedBy = nullptr); void clear(); const List &all() const { return _list; } const List *filtered(QChar ch) const { if (auto it = _index.find(ch); it != _index.cend()) { return it->second.get(); } return &_empty; } ~IndexedList(); // Part of List interface is duplicated here for all() list. int size() const { return all().size(); } bool isEmpty() const { return all().isEmpty(); } bool contains(Key key) const { return all().contains(key); } Row *getRow(Key key) const { return all().getRow(key); } Row *rowAtY(int32 y, int32 h) const { return all().rowAtY(y, h); } using iterator = List::iterator; using const_iterator = List::const_iterator; const_iterator cbegin() const { return all().cbegin(); } const_iterator cend() const { return all().cend(); } const_iterator begin() const { return all().cbegin(); } const_iterator end() const { return all().cend(); } iterator begin() { return all().begin(); } iterator end() { return all().end(); } const_iterator cfind(Row *value) const { return all().cfind(value); } const_iterator find(Row *value) const { return all().cfind(value); } iterator find(Row *value) { return all().find(value); } const_iterator cfind(int y, int h) const { return all().cfind(y, h); } const_iterator find(int y, int h) const { return all().cfind(y, h); } iterator find(int y, int h) { return all().find(y, h); } private: void adjustByName( Key key, const base::flat_set<QChar> &oldChars); void adjustNames( Mode list, not_null<History*> history, const base::flat_set<QChar> &oldChars); SortMode _sortMode; List _list, _empty; base::flat_map<QChar, std::unique_ptr<List>> _index; }; } // namespace Dialogs �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp�������������������������������0000664�0000000�0000000�00000246641�13262451251�0025746�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "dialogs/dialogs_inner_widget.h" #include "dialogs/dialogs_indexed_list.h" #include "dialogs/dialogs_layout.h" #include "dialogs/dialogs_search_from_controllers.h" #include "history/feed/history_feed_section.h" #include "history/history.h" #include "history/history_item.h" #include "styles/style_dialogs.h" #include "styles/style_chat_helpers.h" #include "styles/style_window.h" #include "ui/widgets/buttons.h" #include "ui/widgets/popup_menu.h" #include "ui/text_options.h" #include "data/data_drafts.h" #include "data/data_feed.h" #include "data/data_session.h" #include "lang/lang_keys.h" #include "mainwindow.h" #include "mainwidget.h" #include "storage/localstorage.h" #include "apiwrap.h" #include "window/themes/window_theme.h" #include "observer_peer.h" #include "chat_helpers/stickers.h" #include "auth_session.h" #include "window/notifications_manager.h" #include "window/window_controller.h" #include "window/window_peer_menu.h" #include "ui/widgets/multi_select.h" #include "ui/empty_userpic.h" namespace { constexpr auto kHashtagResultsLimit = 5; constexpr auto kStartReorderThreshold = 30; } // namespace struct DialogsInner::ImportantSwitch { Dialogs::RippleRow row; }; struct DialogsInner::HashtagResult { HashtagResult(const QString &tag) : tag(tag) { } QString tag; Dialogs::RippleRow row; }; struct DialogsInner::PeerSearchResult { PeerSearchResult(not_null<PeerData*> peer) : peer(peer) { } not_null<PeerData*> peer; Dialogs::RippleRow row; }; DialogsInner::DialogsInner(QWidget *parent, not_null<Window::Controller*> controller, QWidget *main) : SplittedWidget(parent) , _controller(controller) , _dialogs(std::make_unique<Dialogs::IndexedList>(Dialogs::SortMode::Date)) , _contactsNoDialogs(std::make_unique<Dialogs::IndexedList>(Dialogs::SortMode::Name)) , _contacts(std::make_unique<Dialogs::IndexedList>(Dialogs::SortMode::Name)) , _a_pinnedShifting(animation(this, &DialogsInner::step_pinnedShifting)) , _addContactLnk(this, lang(lng_add_contact_button)) , _cancelSearchInChat(this, st::dialogsCancelSearchInPeer) , _cancelSearchFromUser(this, st::dialogsCancelSearchInPeer) { #ifdef OS_MAC_OLD // Qt 5.3.2 build is working with glitches otherwise. setAttribute(Qt::WA_OpaquePaintEvent, false); #endif // OS_MAC_OLD if (Global::DialogsModeEnabled()) { _dialogsImportant = std::make_unique<Dialogs::IndexedList>(Dialogs::SortMode::Date); _importantSwitch = std::make_unique<ImportantSwitch>(); } connect(main, SIGNAL(dialogRowReplaced(Dialogs::Row*, Dialogs::Row*)), this, SLOT(onDialogRowReplaced(Dialogs::Row*, Dialogs::Row*))); connect(_addContactLnk, SIGNAL(clicked()), App::wnd(), SLOT(onShowAddContact())); _cancelSearchInChat->setClickedCallback([this] { cancelSearchInChat(); }); _cancelSearchInChat->hide(); _cancelSearchFromUser->setClickedCallback([this] { searchFromUserChanged.notify(nullptr); }); _cancelSearchFromUser->hide(); subscribe(Auth().downloaderTaskFinished(), [this] { update(); }); subscribe(Auth().data().contactsLoaded(), [this](bool) { refresh(); }); Auth().data().itemRemoved( ) | rpl::start_with_next( [this](auto item) { itemRemoved(item); }, lifetime()); Auth().data().itemRepaintRequest( ) | rpl::start_with_next([=](auto item) { const auto history = item->history(); if (history->textCachedFor == item) { history->updateChatListEntry(); } if (const auto feed = history->peer->feed()) { if (feed->textCachedFor == item) { feed->updateChatListEntry(); } } }, lifetime()); subscribe(App::histories().sendActionAnimationUpdated(), [this](const Histories::SendActionAnimationUpdate &update) { auto updateRect = Dialogs::Layout::RowPainter::sendActionAnimationRect(update.width, update.height, getFullWidth(), update.textUpdated); updateDialogRow( Dialogs::RowDescriptor(update.history, FullMsgId()), updateRect, UpdateRowSection::Default | UpdateRowSection::Filtered); }); subscribe(Window::Theme::Background(), [=](const Window::Theme::BackgroundUpdate &data) { if (data.paletteChanged()) { Dialogs::Layout::clearUnreadBadgesCache(); } }); using UpdateFlag = Notify::PeerUpdate::Flag; auto changes = UpdateFlag::PinnedChanged | UpdateFlag::NameChanged | UpdateFlag::PhotoChanged | UpdateFlag::UserIsContact; subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(changes, [this](const Notify::PeerUpdate &update) { if (update.flags & UpdateFlag::PinnedChanged) { stopReorderPinned(); } if (update.flags & UpdateFlag::NameChanged) { handlePeerNameChange(update.peer, update.oldNameFirstLetters); } if (update.flags & UpdateFlag::PhotoChanged) { this->update(); emit App::main()->dialogsUpdated(); } if (update.flags & UpdateFlag::UserIsContact) { if (const auto user = update.peer->asUser()) { userIsContactUpdated(user); } } })); Auth().data().feedUpdated( ) | rpl::start_with_next([=](const Data::FeedUpdate &update) { updateDialogRow( Dialogs::RowDescriptor(update.feed, FullMsgId()), QRect(0, 0, getFullWidth(), st::dialogsRowHeight)); }, lifetime()); _controller->activeChatEntryValue( ) | rpl::combine_previous( ) | rpl::start_with_next([=]( Dialogs::RowDescriptor previous, Dialogs::RowDescriptor next) { const auto rect = QRect(0, 0, getFullWidth(), st::dialogsRowHeight); updateDialogRow(previous, rect); updateDialogRow(next, rect); }, lifetime()); refresh(); } int DialogsInner::dialogsOffset() const { return _dialogsImportant ? st::dialogsImportantBarHeight : 0; } int DialogsInner::filteredOffset() const { return _hashtagResults.size() * st::mentionHeight; } int DialogsInner::peerSearchOffset() const { return filteredOffset() + (_filterResults.size() * st::dialogsRowHeight) + st::searchedBarHeight; } int DialogsInner::searchedOffset() const { auto result = peerSearchOffset() + (_peerSearchResults.empty() ? 0 : ((_peerSearchResults.size() * st::dialogsRowHeight) + st::searchedBarHeight)); if (_searchInChat) { result += searchInChatSkip(); } return result; } int DialogsInner::searchInChatSkip() const { auto result = st::searchedBarHeight + st::dialogsSearchInHeight; if (_searchFromUser) { result += st::lineWidth + st::dialogsSearchInHeight; } return result; } void DialogsInner::paintRegion(Painter &p, const QRegion ®ion, bool paintingOther) { QRegion original(rtl() ? region.translated(-otherWidth(), 0) : region); if (App::wnd() && App::wnd()->contentOverlapped(this, original)) return; if (!App::main()) return; auto r = region.boundingRect(); if (!paintingOther) { p.setClipRect(r); } const auto activeEntry = _controller->activeChatEntryCurrent(); auto fullWidth = getFullWidth(); auto ms = getms(); if (_state == State::Default) { _a_pinnedShifting.step(ms, false); auto rows = shownDialogs(); auto dialogsClip = r; if (_dialogsImportant) { auto selected = isPressed() ? _importantSwitchPressed : _importantSwitchSelected; Dialogs::Layout::paintImportantSwitch(p, Global::DialogsMode(), fullWidth, selected, paintingOther); dialogsClip.translate(0, -st::dialogsImportantBarHeight); p.translate(0, st::dialogsImportantBarHeight); } auto otherStart = rows->size() * st::dialogsRowHeight; const auto active = activeEntry.key; const auto selected = _menuKey ? _menuKey : (isPressed() ? (_pressed ? _pressed->key() : Dialogs::Key()) : (_selected ? _selected->key() : Dialogs::Key())); if (otherStart) { auto reorderingPinned = (_aboveIndex >= 0 && !_pinnedRows.empty()); auto &list = rows->all(); if (reorderingPinned) { dialogsClip = dialogsClip.marginsAdded(QMargins(0, st::dialogsRowHeight, 0, st::dialogsRowHeight)); } auto i = list.cfind(dialogsClip.top(), st::dialogsRowHeight); if (i != list.cend()) { auto lastPaintedPos = (*i)->pos(); // If we're reordering pinned chats we need to fill this area background first. if (reorderingPinned) { p.fillRect(0, 0, fullWidth, st::dialogsRowHeight * _pinnedRows.size(), st::dialogsBg); } p.translate(0, lastPaintedPos * st::dialogsRowHeight); for (auto e = list.cend(); i != e; ++i) { auto row = (*i); if (lastPaintedPos * st::dialogsRowHeight >= dialogsClip.top() + dialogsClip.height()) { break; } // Skip currently dragged chat to paint it above others after. if (lastPaintedPos != _aboveIndex) { paintDialog( p, row, fullWidth, active, selected, paintingOther, ms); } p.translate(0, st::dialogsRowHeight); ++lastPaintedPos; } // Paint the dragged chat above all others. if (_aboveIndex >= 0) { auto i = list.cfind(_aboveIndex, 1); auto pos = (i == list.cend()) ? -1 : (*i)->pos(); if (pos == _aboveIndex) { p.translate(0, (pos - lastPaintedPos) * st::dialogsRowHeight); paintDialog(p, *i, fullWidth, active, selected, paintingOther, ms); p.translate(0, (lastPaintedPos - pos) * st::dialogsRowHeight); } } } } if (!otherStart) { p.fillRect(dialogsClip, st::dialogsBg); if (!paintingOther) { p.setFont(st::noContactsFont); p.setPen(st::noContactsColor); p.drawText(QRect(0, 0, fullWidth, st::noContactsHeight - (Auth().data().contactsLoaded().value() ? st::noContactsFont->height : 0)), lang(Auth().data().contactsLoaded().value() ? lng_no_chats : lng_contacts_loading), style::al_center); } } } else if (_state == State::Filtered) { if (!_hashtagResults.empty()) { auto from = floorclamp(r.y(), st::mentionHeight, 0, _hashtagResults.size()); auto to = ceilclamp(r.y() + r.height(), st::mentionHeight, 0, _hashtagResults.size()); p.translate(0, from * st::mentionHeight); if (from < _hashtagResults.size()) { auto htagwidth = fullWidth - st::dialogsPadding.x() * 2; p.setFont(st::mentionFont); for (; from < to; ++from) { auto &result = _hashtagResults[from]; bool selected = (from == (isPressed() ? _hashtagPressed : _hashtagSelected)); p.fillRect(0, 0, fullWidth, st::mentionHeight, selected ? st::mentionBgOver : st::dialogsBg); result->row.paintRipple(p, 0, 0, fullWidth, ms); if (!paintingOther) { auto &tag = result->tag; if (selected) { int skip = (st::mentionHeight - st::smallCloseIconOver.height()) / 2; st::smallCloseIconOver.paint(p, QPoint(fullWidth - st::smallCloseIconOver.width() - skip, skip), width()); } auto first = (_hashtagFilter.size() < 2) ? QString() : ('#' + tag.mid(0, _hashtagFilter.size() - 1)); auto second = (_hashtagFilter.size() < 2) ? ('#' + tag) : tag.mid(_hashtagFilter.size() - 1); auto firstwidth = st::mentionFont->width(first); auto secondwidth = st::mentionFont->width(second); if (htagwidth < firstwidth + secondwidth) { if (htagwidth < firstwidth + st::mentionFont->elidew) { first = st::mentionFont->elided(first + second, htagwidth); second = QString(); } else { second = st::mentionFont->elided(second, htagwidth - firstwidth); } } p.setFont(st::mentionFont); if (!first.isEmpty()) { p.setPen(selected ? st::mentionFgOverActive : st::mentionFgActive); p.drawText(st::dialogsPadding.x(), st::mentionTop + st::mentionFont->ascent, first); } if (!second.isEmpty()) { p.setPen(selected ? st::mentionFgOver : st::mentionFg); p.drawText(st::dialogsPadding.x() + firstwidth, st::mentionTop + st::mentionFont->ascent, second); } } p.translate(0, st::mentionHeight); } } } if (!_filterResults.isEmpty()) { auto skip = filteredOffset(); auto from = floorclamp(r.y() - skip, st::dialogsRowHeight, 0, _filterResults.size()); auto to = ceilclamp(r.y() + r.height() - skip, st::dialogsRowHeight, 0, _filterResults.size()); p.translate(0, from * st::dialogsRowHeight); if (from < _filterResults.size()) { for (; from < to; ++from) { const auto row = _filterResults[from]; const auto key = row->key(); const auto active = (activeEntry.key == key) && !activeEntry.fullId; const auto selected = _menuKey ? (key == _menuKey) : (from == (isPressed() ? _filteredPressed : _filteredSelected)); Dialogs::Layout::RowPainter::paint( p, _filterResults[from], fullWidth, active, selected, paintingOther, ms); p.translate(0, st::dialogsRowHeight); } } } if (!_peerSearchResults.empty()) { p.fillRect(0, 0, fullWidth, st::searchedBarHeight, st::searchedBarBg); if (!paintingOther) { p.setFont(st::searchedBarFont); p.setPen(st::searchedBarFg); p.drawTextLeft(st::searchedBarPosition.x(), st::searchedBarPosition.y(), width(), lang(lng_search_global_results)); } p.translate(0, st::searchedBarHeight); auto skip = peerSearchOffset(); auto from = floorclamp(r.y() - skip, st::dialogsRowHeight, 0, _peerSearchResults.size()); auto to = ceilclamp(r.y() + r.height() - skip, st::dialogsRowHeight, 0, _peerSearchResults.size()); p.translate(0, from * st::dialogsRowHeight); if (from < _peerSearchResults.size()) { const auto activePeer = activeEntry.key.peer(); for (; from < to; ++from) { const auto &result = _peerSearchResults[from]; const auto peer = result->peer; const auto active = !activeEntry.fullId && activePeer && ((peer == activePeer) || (peer->migrateTo() == activePeer)); const auto selected = (from == (isPressed() ? _peerSearchPressed : _peerSearchSelected)); paintPeerSearchResult(p, result.get(), fullWidth, active, selected, paintingOther, ms); p.translate(0, st::dialogsRowHeight); } } } if (_searchInChat) { paintSearchInChat(p, fullWidth, paintingOther, ms); p.translate(0, searchInChatSkip()); if (_waitingForSearch && _searchResults.empty()) { p.fillRect( 0, 0, fullWidth, st::searchedBarHeight, st::searchedBarBg); if (!paintingOther) { p.setFont(st::searchedBarFont); p.setPen(st::searchedBarFg); p.drawTextLeft( st::searchedBarPosition.x(), st::searchedBarPosition.y(), width(), lang(lng_dlg_search_for_messages)); } p.translate(0, st::searchedBarHeight); } } if (!_waitingForSearch || !_searchResults.empty()) { const auto text = _searchResults.empty() ? lang(lng_search_no_results) : lng_search_found_results( lt_count, _searchedMigratedCount + _searchedCount); p.fillRect(0, 0, fullWidth, st::searchedBarHeight, st::searchedBarBg); if (!paintingOther) { p.setFont(st::searchedBarFont); p.setPen(st::searchedBarFg); p.drawTextLeft(st::searchedBarPosition.x(), st::searchedBarPosition.y(), width(), text); } p.translate(0, st::searchedBarHeight); auto skip = searchedOffset(); auto from = floorclamp(r.y() - skip, st::dialogsRowHeight, 0, _searchResults.size()); auto to = ceilclamp(r.y() + r.height() - skip, st::dialogsRowHeight, 0, _searchResults.size()); p.translate(0, from * st::dialogsRowHeight); if (from < _searchResults.size()) { const auto activePeer = activeEntry.key.peer(); for (; from < to; ++from) { const auto &result = _searchResults[from]; const auto item = result->item(); const auto peer = item->history()->peer; const auto active = (item->fullId() == activeEntry.fullId) || (peer->migrateTo() && (peer->migrateTo()->bareId() == activeEntry.fullId.channel) && (item->id == -activeEntry.fullId.msg)); const auto selected = (from == (isPressed() ? _searchedPressed : _searchedSelected)); Dialogs::Layout::RowPainter::paint( p, result.get(), fullWidth, active, selected, paintingOther, ms); p.translate(0, st::dialogsRowHeight); } } } } } void DialogsInner::paintDialog( Painter &p, not_null<Dialogs::Row*> row, int fullWidth, Dialogs::Key active, Dialogs::Key selected, bool onlyBackground, TimeMs ms) { auto pos = row->pos(); auto xadd = 0, yadd = 0; if (pos < _pinnedRows.size()) { yadd = qRound(_pinnedRows[pos].yadd.current()); } if (xadd || yadd) p.translate(xadd, yadd); const auto isActive = (row->key() == active); const auto isSelected = (row->key() == selected); Dialogs::Layout::RowPainter::paint( p, row, fullWidth, isActive, isSelected, onlyBackground, ms); if (xadd || yadd) p.translate(-xadd, -yadd); } void DialogsInner::paintPeerSearchResult( Painter &p, not_null<const PeerSearchResult*> result, int fullWidth, bool active, bool selected, bool onlyBackground, TimeMs ms) const { QRect fullRect(0, 0, fullWidth, st::dialogsRowHeight); p.fillRect(fullRect, active ? st::dialogsBgActive : (selected ? st::dialogsBgOver : st::dialogsBg)); if (!active) { result->row.paintRipple(p, 0, 0, fullWidth, ms); } if (onlyBackground) return; auto peer = result->peer; auto userpicPeer = (peer->migrateTo() ? peer->migrateTo() : peer); userpicPeer->paintUserpicLeft(p, st::dialogsPadding.x(), st::dialogsPadding.y(), getFullWidth(), st::dialogsPhotoSize); auto nameleft = st::dialogsPadding.x() + st::dialogsPhotoSize + st::dialogsPhotoPadding; auto namewidth = fullWidth - nameleft - st::dialogsPadding.x(); QRect rectForName(nameleft, st::dialogsPadding.y() + st::dialogsNameTop, namewidth, st::msgNameFont->height); // draw chat icon if (auto chatTypeIcon = Dialogs::Layout::ChatTypeIcon(peer, active, selected)) { chatTypeIcon->paint(p, rectForName.topLeft(), fullWidth); rectForName.setLeft(rectForName.left() + st::dialogsChatTypeSkip); } if (peer->isVerified()) { auto icon = &(active ? st::dialogsVerifiedIconActive : (selected ? st::dialogsVerifiedIconOver : st::dialogsVerifiedIcon)); rectForName.setWidth(rectForName.width() - icon->width()); icon->paint(p, rectForName.topLeft() + QPoint(qMin(peer->dialogName().maxWidth(), rectForName.width()), 0), fullWidth); } QRect tr(nameleft, st::dialogsPadding.y() + st::msgNameFont->height + st::dialogsSkip, namewidth, st::dialogsTextFont->height); p.setFont(st::dialogsTextFont); QString username = peer->userName(); if (!active && username.toLower().startsWith(_peerSearchQuery)) { auto first = '@' + username.mid(0, _peerSearchQuery.size()); auto second = username.mid(_peerSearchQuery.size()); auto w = st::dialogsTextFont->width(first); if (w >= tr.width()) { p.setPen(st::dialogsTextFgService); p.drawText(tr.left(), tr.top() + st::dialogsTextFont->ascent, st::dialogsTextFont->elided(first, tr.width())); } else { p.setPen(st::dialogsTextFgService); p.drawText(tr.left(), tr.top() + st::dialogsTextFont->ascent, first); p.setPen(st::dialogsTextFg); p.drawText(tr.left() + w, tr.top() + st::dialogsTextFont->ascent, st::dialogsTextFont->elided(second, tr.width() - w)); } } else { p.setPen(active ? st::dialogsTextFgActive : st::dialogsTextFgService); p.drawText(tr.left(), tr.top() + st::dialogsTextFont->ascent, st::dialogsTextFont->elided('@' + username, tr.width())); } p.setPen(active ? st::dialogsTextFgActive : st::dialogsNameFg); peer->dialogName().drawElided(p, rectForName.left(), rectForName.top(), rectForName.width()); } void DialogsInner::paintSearchInChat( Painter &p, int fullWidth, bool onlyBackground, TimeMs ms) const { auto height = searchInChatSkip(); auto top = st::searchedBarHeight; p.fillRect(0, 0, fullWidth, top, st::searchedBarBg); if (!onlyBackground) { p.setFont(st::searchedBarFont); p.setPen(st::searchedBarFg); p.drawTextLeft(st::searchedBarPosition.x(), st::searchedBarPosition.y(), width(), lang(lng_dlg_search_in)); } auto fullRect = QRect(0, top, fullWidth, height - top); p.fillRect(fullRect, st::dialogsBg); if (_searchFromUser) { p.fillRect(QRect(0, top + st::dialogsSearchInHeight, fullWidth, st::lineWidth), st::shadowFg); } if (onlyBackground) return; p.setPen(st::dialogsNameFg); if (const auto peer = _searchInChat.peer()) { if (peer->isSelf()) { paintSearchInSaved(p, top, fullWidth, _searchInChatText); } else { paintSearchInPeer(p, peer, top, fullWidth, _searchInChatText); } } else if (const auto feed = _searchInChat.feed()) { paintSearchInFeed(p, feed, top, fullWidth, _searchInChatText); } else { Unexpected("Empty Dialogs::Key in paintSearchInChat."); } if (const auto from = _searchFromUser) { top += st::dialogsSearchInHeight + st::lineWidth; p.setPen(st::dialogsTextFg); p.setTextPalette(st::dialogsSearchFromPalette); paintSearchInPeer(p, from, top, fullWidth, _searchFromUserText); p.restoreTextPalette(); } } template <typename PaintUserpic> void DialogsInner::paintSearchInFilter( Painter &p, PaintUserpic paintUserpic, int top, int fullWidth, const style::icon *icon, const Text &text) const { const auto savedPen = p.pen(); const auto userpicLeft = st::dialogsPadding.x(); const auto userpicTop = top + (st::dialogsSearchInHeight - st::dialogsSearchInPhotoSize) / 2; paintUserpic(p, userpicLeft, userpicTop, st::dialogsSearchInPhotoSize); const auto nameleft = st::dialogsPadding.x() + st::dialogsSearchInPhotoSize + st::dialogsSearchInPhotoPadding; const auto namewidth = fullWidth - nameleft - st::dialogsPadding.x() * 2 - st::dialogsCancelSearch.width; auto rectForName = QRect( nameleft, top + (st::dialogsSearchInHeight - st::msgNameFont->height) / 2, namewidth, st::msgNameFont->height); if (icon) { icon->paint(p, rectForName.topLeft(), fullWidth); rectForName.setLeft(rectForName.left() + st::dialogsChatTypeSkip); } p.setPen(savedPen); text.drawLeftElided( p, rectForName.left(), rectForName.top(), rectForName.width(), getFullWidth()); } void DialogsInner::paintSearchInPeer( Painter &p, not_null<PeerData*> peer, int top, int fullWidth, const Text &text) const { const auto paintUserpic = [&](Painter &p, int x, int y, int size) { peer->paintUserpicLeft(p, x, y, fullWidth, size); }; const auto icon = Dialogs::Layout::ChatTypeIcon(peer, false, false); paintSearchInFilter(p, paintUserpic, top, fullWidth, icon, text); } void DialogsInner::paintSearchInSaved( Painter &p, int top, int fullWidth, const Text &text) const { const auto paintUserpic = [&](Painter &p, int x, int y, int size) { Ui::EmptyUserpic::PaintSavedMessages(p, x, y, fullWidth, size); }; paintSearchInFilter(p, paintUserpic, top, fullWidth, nullptr, text); } void DialogsInner::paintSearchInFeed( Painter &p, not_null<Data::Feed*> feed, int top, int fullWidth, const Text &text) const { const auto paintUserpic = [&](Painter &p, int x, int y, int size) { feed->paintUserpicLeft(p, x, y, fullWidth, size); }; const auto icon = Dialogs::Layout::FeedTypeIcon(feed, false, false); paintSearchInFilter(p, paintUserpic, top, fullWidth, icon, text); } void DialogsInner::activate() { } void DialogsInner::mouseMoveEvent(QMouseEvent *e) { if (_mouseLastGlobalPosition != e->globalPos()) { _mouseLastGlobalPosition = e->globalPos(); _mouseSelection = true; } updateSelected(e->pos()); } void DialogsInner::clearIrrelevantState() { if (_state == State::Default) { _hashtagSelected = -1; setHashtagPressed(-1); _hashtagDeleteSelected = _hashtagDeletePressed = false; _filteredSelected = -1; setFilteredPressed(-1); _peerSearchSelected = -1; setPeerSearchPressed(-1); _searchedSelected = -1; setSearchedPressed(-1); } else if (_state == State::Filtered) { _importantSwitchSelected = false; setImportantSwitchPressed(false); _selected = nullptr; setPressed(nullptr); } } void DialogsInner::updateSelected(QPoint localPos) { if (updateReorderPinned(localPos)) { return; } if (!_mouseSelection) { return; } int w = width(), mouseY = localPos.y(); clearIrrelevantState(); if (_state == State::Default) { auto importantSwitchSelected = (_dialogsImportant && mouseY >= 0 && mouseY < dialogsOffset()); mouseY -= dialogsOffset(); auto selected = importantSwitchSelected ? nullptr : shownDialogs()->rowAtY(mouseY, st::dialogsRowHeight); if (_selected != selected || _importantSwitchSelected != importantSwitchSelected) { updateSelectedRow(); _selected = selected; _importantSwitchSelected = importantSwitchSelected; updateSelectedRow(); setCursor((_selected || _importantSwitchSelected) ? style::cur_pointer : style::cur_default); } } else if (_state == State::Filtered) { auto wasSelected = isSelected(); if (_hashtagResults.empty()) { _hashtagSelected = -1; _hashtagDeleteSelected = false; } else { auto skip = 0; auto hashtagSelected = (mouseY >= skip) ? ((mouseY - skip) / st::mentionHeight) : -1; if (hashtagSelected < 0 || hashtagSelected >= _hashtagResults.size()) { hashtagSelected = -1; } if (_hashtagSelected != hashtagSelected) { updateSelectedRow(); _hashtagSelected = hashtagSelected; updateSelectedRow(); } _hashtagDeleteSelected = (_hashtagSelected >= 0) && (localPos.x() >= w - st::mentionHeight); } if (!_filterResults.isEmpty()) { auto skip = filteredOffset(); auto filteredSelected = (mouseY >= skip) ? ((mouseY - skip) / st::dialogsRowHeight) : -1; if (filteredSelected < 0 || filteredSelected >= _filterResults.size()) { filteredSelected = -1; } if (_filteredSelected != filteredSelected) { updateSelectedRow(); _filteredSelected = filteredSelected; updateSelectedRow(); } } if (!_peerSearchResults.empty()) { auto skip = peerSearchOffset(); auto peerSearchSelected = (mouseY >= skip) ? ((mouseY - skip) / st::dialogsRowHeight) : -1; if (peerSearchSelected < 0 || peerSearchSelected >= _peerSearchResults.size()) { peerSearchSelected = -1; } if (_peerSearchSelected != peerSearchSelected) { updateSelectedRow(); _peerSearchSelected = peerSearchSelected; updateSelectedRow(); } } if (!_waitingForSearch && !_searchResults.empty()) { auto skip = searchedOffset(); auto searchedSelected = (mouseY >= skip) ? ((mouseY - skip) / st::dialogsRowHeight) : -1; if (searchedSelected < 0 || searchedSelected >= _searchResults.size()) { searchedSelected = -1; } if (_searchedSelected != searchedSelected) { updateSelectedRow(); _searchedSelected = searchedSelected; updateSelectedRow(); } } if (wasSelected != isSelected()) { setCursor(wasSelected ? style::cur_default : style::cur_pointer); } } } void DialogsInner::mousePressEvent(QMouseEvent *e) { _mouseSelection = true; updateSelected(e->pos()); _pressButton = e->button(); setPressed(_selected); setImportantSwitchPressed(_importantSwitchSelected); setHashtagPressed(_hashtagSelected); _hashtagDeletePressed = _hashtagDeleteSelected; setFilteredPressed(_filteredSelected); setPeerSearchPressed(_peerSearchSelected); setSearchedPressed(_searchedSelected); if (_importantSwitchPressed) { _importantSwitch->row.addRipple(e->pos(), QSize(getFullWidth(), st::dialogsImportantBarHeight), [this] { update(0, 0, getFullWidth(), st::dialogsImportantBarHeight); }); } else if (_pressed) { auto row = _pressed; row->addRipple(e->pos() - QPoint(0, dialogsOffset() + _pressed->pos() * st::dialogsRowHeight), QSize(getFullWidth(), st::dialogsRowHeight), [this, row] { if (!_a_pinnedShifting.animating()) { row->entry()->updateChatListEntry(); } }); _dragStart = e->pos(); } else if (base::in_range(_hashtagPressed, 0, _hashtagResults.size()) && !_hashtagDeletePressed) { auto row = &_hashtagResults[_hashtagPressed]->row; row->addRipple(e->pos(), QSize(getFullWidth(), st::mentionHeight), [this, index = _hashtagPressed] { update(0, index * st::mentionHeight, getFullWidth(), st::mentionHeight); }); } else if (base::in_range(_filteredPressed, 0, _filterResults.size())) { const auto row = _filterResults[_filteredPressed]; const auto list = Global::DialogsMode(); row->addRipple( e->pos() - QPoint(0, filteredOffset() + _filteredPressed * st::dialogsRowHeight), QSize(getFullWidth(), st::dialogsRowHeight), [=] { repaintDialogRow(list, row); }); } else if (base::in_range(_peerSearchPressed, 0, _peerSearchResults.size())) { auto &result = _peerSearchResults[_peerSearchPressed]; auto row = &result->row; row->addRipple( e->pos() - QPoint(0, peerSearchOffset() + _peerSearchPressed * st::dialogsRowHeight), QSize(getFullWidth(), st::dialogsRowHeight), [this, peer = result->peer] { updateSearchResult(peer); }); } else if (base::in_range(_searchedPressed, 0, _searchResults.size())) { auto &row = _searchResults[_searchedPressed]; row->addRipple(e->pos() - QPoint(0, searchedOffset() + _searchedPressed * st::dialogsRowHeight), QSize(getFullWidth(), st::dialogsRowHeight), [this, index = _searchedPressed] { rtlupdate(0, searchedOffset() + index * st::dialogsRowHeight, getFullWidth(), st::dialogsRowHeight); }); } } void DialogsInner::checkReorderPinnedStart(QPoint localPosition) { if (_pressed != nullptr && !_dragging && _state == State::Default) { if (qAbs(localPosition.y() - _dragStart.y()) >= convertScale(kStartReorderThreshold)) { _dragging = _pressed; if (updateReorderIndexGetCount() < 2) { _dragging = nullptr; } else { _pinnedOrder = Auth().data().pinnedDialogsOrder(); _pinnedRows[_draggingIndex].yadd = anim::value(0, localPosition.y() - _dragStart.y()); _pinnedRows[_draggingIndex].animStartTime = getms(); _a_pinnedShifting.start(); } } } } int DialogsInner::shownPinnedCount() const { auto result = 0; for_const (auto row, *shownDialogs()) { if (!row->entry()->isPinnedDialog()) { break; } ++result; } return result; } int DialogsInner::countPinnedIndex(Dialogs::Row *ofRow) { if (!ofRow || !ofRow->entry()->isPinnedDialog()) { return -1; } auto result = 0; for_const (auto row, *shownDialogs()) { if (!row->entry()->isPinnedDialog()) { break; } else if (row == ofRow) { return result; } ++result; } return -1; } void DialogsInner::savePinnedOrder() { const auto &newOrder = Auth().data().pinnedDialogsOrder(); if (newOrder.size() != _pinnedOrder.size()) { return; // Something has changed in the set of pinned chats. } for (const auto &pinned : newOrder) { if (!base::contains(_pinnedOrder, pinned)) { return; // Something has changed in the set of pinned chats. } } Auth().api().savePinnedOrder(); } void DialogsInner::finishReorderPinned() { auto wasDragging = (_dragging != nullptr); if (wasDragging) { savePinnedOrder(); _dragging = nullptr; } _draggingIndex = -1; if (!_a_pinnedShifting.animating()) { _pinnedRows.clear(); _aboveIndex = -1; } if (wasDragging) { emit draggingScrollDelta(0); } } void DialogsInner::stopReorderPinned() { _a_pinnedShifting.stop(); finishReorderPinned(); } int DialogsInner::updateReorderIndexGetCount() { auto index = countPinnedIndex(_dragging); if (index < 0) { finishReorderPinned(); return 0; } auto count = shownPinnedCount(); Assert(index < count); if (count < 2) { stopReorderPinned(); return 0; } _draggingIndex = index; _aboveIndex = _draggingIndex; while (count > _pinnedRows.size()) { _pinnedRows.push_back(PinnedRow()); } while (count < _pinnedRows.size()) { _pinnedRows.pop_back(); } return count; } bool DialogsInner::updateReorderPinned(QPoint localPosition) { checkReorderPinnedStart(localPosition); auto pinnedCount = updateReorderIndexGetCount(); if (pinnedCount < 2) { return false; } auto yaddWas = _pinnedRows[_draggingIndex].yadd.current(); auto shift = 0; auto ms = getms(); auto rowHeight = st::dialogsRowHeight; if (_dragStart.y() > localPosition.y() && _draggingIndex > 0) { shift = -floorclamp(_dragStart.y() - localPosition.y() + (rowHeight / 2), rowHeight, 0, _draggingIndex); for (auto from = _draggingIndex, to = _draggingIndex + shift; from > to; --from) { shownDialogs()->movePinned(_dragging, -1); std::swap(_pinnedRows[from], _pinnedRows[from - 1]); _pinnedRows[from].yadd = anim::value(_pinnedRows[from].yadd.current() - rowHeight, 0); _pinnedRows[from].animStartTime = ms; } } else if (_dragStart.y() < localPosition.y() && _draggingIndex + 1 < pinnedCount) { shift = floorclamp(localPosition.y() - _dragStart.y() + (rowHeight / 2), rowHeight, 0, pinnedCount - _draggingIndex - 1); for (auto from = _draggingIndex, to = _draggingIndex + shift; from < to; ++from) { shownDialogs()->movePinned(_dragging, 1); std::swap(_pinnedRows[from], _pinnedRows[from + 1]); _pinnedRows[from].yadd = anim::value(_pinnedRows[from].yadd.current() + rowHeight, 0); _pinnedRows[from].animStartTime = ms; } } if (shift) { _draggingIndex += shift; _aboveIndex = _draggingIndex; _dragStart.setY(_dragStart.y() + shift * rowHeight); if (!_a_pinnedShifting.animating()) { _a_pinnedShifting.start(); } } _aboveTopShift = qCeil(_pinnedRows[_aboveIndex].yadd.current()); _pinnedRows[_draggingIndex].yadd = anim::value(yaddWas - shift * rowHeight, localPosition.y() - _dragStart.y()); if (!_pinnedRows[_draggingIndex].animStartTime) { _pinnedRows[_draggingIndex].yadd.finish(); } _a_pinnedShifting.step(ms, true); auto countDraggingScrollDelta = [this, localPosition] { if (localPosition.y() < _visibleTop) { return localPosition.y() - _visibleTop; } return 0; }; emit draggingScrollDelta(countDraggingScrollDelta()); return true; } void DialogsInner::step_pinnedShifting(TimeMs ms, bool timer) { auto animating = false; auto updateMin = -1; auto updateMax = 0; for (auto i = 0, l = static_cast<int>(_pinnedRows.size()); i != l; ++i) { auto start = _pinnedRows[i].animStartTime; if (start) { if (updateMin < 0) updateMin = i; updateMax = i; if (start + st::stickersRowDuration > ms && ms >= start) { _pinnedRows[i].yadd.update(float64(ms - start) / st::stickersRowDuration, anim::sineInOut); animating = true; } else { _pinnedRows[i].yadd.finish(); _pinnedRows[i].animStartTime = 0; } } } if (timer) { updateReorderIndexGetCount(); if (_draggingIndex >= 0) { if (updateMin < 0 || updateMin > _draggingIndex) { updateMin = _draggingIndex; } if (updateMax < _draggingIndex) updateMax = _draggingIndex; } if (updateMin >= 0) { auto top = _dialogsImportant ? st::dialogsImportantBarHeight : 0; auto updateFrom = top + st::dialogsRowHeight * (updateMin - 1); auto updateHeight = st::dialogsRowHeight * (updateMax - updateMin + 3); if (base::in_range(_aboveIndex, 0, _pinnedRows.size())) { // Always include currently dragged chat in its current and old positions. auto aboveRowBottom = top + (_aboveIndex + 1) * st::dialogsRowHeight; auto aboveTopShift = qCeil(_pinnedRows[_aboveIndex].yadd.current()); accumulate_max(updateHeight, (aboveRowBottom - updateFrom) + _aboveTopShift); accumulate_max(updateHeight, (aboveRowBottom - updateFrom) + aboveTopShift); _aboveTopShift = aboveTopShift; } update(0, updateFrom, getFullWidth(), updateHeight); } } if (!animating) { _aboveIndex = _draggingIndex; _a_pinnedShifting.stop(); } } void DialogsInner::mouseReleaseEvent(QMouseEvent *e) { mousePressReleased(e->button()); } void DialogsInner::mousePressReleased(Qt::MouseButton button) { auto wasDragging = (_dragging != nullptr); if (wasDragging) { updateReorderIndexGetCount(); if (_draggingIndex >= 0) { auto localPosition = mapFromGlobal(QCursor::pos()); _pinnedRows[_draggingIndex].yadd.start(0.); _pinnedRows[_draggingIndex].animStartTime = getms(); if (!_a_pinnedShifting.animating()) { _a_pinnedShifting.start(); } } finishReorderPinned(); } auto importantSwitchPressed = _importantSwitchPressed; setImportantSwitchPressed(false); auto pressed = _pressed; setPressed(nullptr); auto hashtagPressed = _hashtagPressed; setHashtagPressed(-1); auto hashtagDeletePressed = _hashtagDeletePressed; _hashtagDeletePressed = false; auto filteredPressed = _filteredPressed; setFilteredPressed(-1); auto peerSearchPressed = _peerSearchPressed; setPeerSearchPressed(-1); auto searchedPressed = _searchedPressed; setSearchedPressed(-1); if (wasDragging) { updateSelected(); } updateSelectedRow(); if (!wasDragging && button == Qt::LeftButton) { if (importantSwitchPressed && importantSwitchPressed == _importantSwitchSelected) { chooseRow(); } else if (pressed && pressed == _selected) { chooseRow(); } else if (hashtagPressed >= 0 && hashtagPressed == _hashtagSelected && hashtagDeletePressed == _hashtagDeleteSelected) { chooseRow(); } else if (filteredPressed >= 0 && filteredPressed == _filteredSelected) { chooseRow(); } else if (peerSearchPressed >= 0 && peerSearchPressed == _peerSearchSelected) { chooseRow(); } else if (searchedPressed >= 0 && searchedPressed == _searchedSelected) { chooseRow(); } } } void DialogsInner::setImportantSwitchPressed(bool pressed) { if (_importantSwitchPressed != pressed) { if (_importantSwitchPressed) { _importantSwitch->row.stopLastRipple(); } _importantSwitchPressed = pressed; } } void DialogsInner::setPressed(Dialogs::Row *pressed) { if (_pressed != pressed) { if (_pressed) { _pressed->stopLastRipple(); } _pressed = pressed; } } void DialogsInner::setHashtagPressed(int pressed) { if (base::in_range(_hashtagPressed, 0, _hashtagResults.size())) { _hashtagResults[_hashtagPressed]->row.stopLastRipple(); } _hashtagPressed = pressed; } void DialogsInner::setFilteredPressed(int pressed) { if (base::in_range(_filteredPressed, 0, _filterResults.size())) { _filterResults[_filteredPressed]->stopLastRipple(); } _filteredPressed = pressed; } void DialogsInner::setPeerSearchPressed(int pressed) { if (base::in_range(_peerSearchPressed, 0, _peerSearchResults.size())) { _peerSearchResults[_peerSearchPressed]->row.stopLastRipple(); } _peerSearchPressed = pressed; } void DialogsInner::setSearchedPressed(int pressed) { if (base::in_range(_searchedPressed, 0, _searchResults.size())) { _searchResults[_searchedPressed]->stopLastRipple(); } _searchedPressed = pressed; } void DialogsInner::resizeEvent(QResizeEvent *e) { _addContactLnk->move((width() - _addContactLnk->width()) / 2, (st::noContactsHeight + st::noContactsFont->height) / 2); auto widthForCancelButton = qMax(width() + otherWidth(), st::columnMinimalWidthLeft); _cancelSearchInChat->moveToLeft(widthForCancelButton - st::dialogsSearchInSkip - _cancelSearchInChat->width(), st::searchedBarHeight + (st::dialogsSearchInHeight - st::dialogsCancelSearchInPeer.height) / 2); _cancelSearchFromUser->moveToLeft(widthForCancelButton - st::dialogsSearchInSkip - _cancelSearchFromUser->width(), st::searchedBarHeight + st::dialogsSearchInHeight + st::lineWidth + (st::dialogsSearchInHeight - st::dialogsCancelSearchInPeer.height) / 2); } void DialogsInner::onDialogRowReplaced( Dialogs::Row *oldRow, Dialogs::Row *newRow) { if (_state == State::Filtered) { for (auto i = _filterResults.begin(); i != _filterResults.end();) { if (*i == oldRow) { // this row is shown in filtered and maybe is in contacts! if (newRow) { *i = newRow; ++i; } else { i = _filterResults.erase(i); } } else { ++i; } } } if (_selected == oldRow) { _selected = newRow; } if (_pressed == oldRow) { setPressed(newRow); } if (_dragging == oldRow) { if (newRow) { _dragging = newRow; } else { stopReorderPinned(); } } } void DialogsInner::createDialog(Dialogs::Key key) { if (const auto history = key.history()) { if (history->peer->loadedStatus != PeerData::LoadedStatus::FullLoaded) { LOG(("API Error: " "DialogsInner::createDialog() called for a non loaded peer!" )); return; } } const auto entry = key.entry(); auto creating = !entry->inChatList(Dialogs::Mode::All); if (creating) { const auto mainRow = entry->addToChatList( Dialogs::Mode::All, _dialogs.get()); _contactsNoDialogs->del(key, mainRow); } if (_dialogsImportant && !entry->inChatList(Dialogs::Mode::Important) && entry->toImportant()) { if (Global::DialogsMode() == Dialogs::Mode::Important) { creating = true; } entry->addToChatList( Dialogs::Mode::Important, _dialogsImportant.get()); } auto changed = entry->adjustByPosInChatList( Dialogs::Mode::All, _dialogs.get()); if (_dialogsImportant) { if (!entry->toImportant()) { if (Global::DialogsMode() == Dialogs::Mode::Important) { return; } } else { const auto importantChanged = entry->adjustByPosInChatList( Dialogs::Mode::Important, _dialogsImportant.get()); if (Global::DialogsMode() == Dialogs::Mode::Important) { changed = importantChanged; } } } const auto from = dialogsOffset() + changed.movedFrom * st::dialogsRowHeight; const auto to = dialogsOffset() + changed.movedTo * st::dialogsRowHeight; if (!_dragging) { // Don't jump in chats list scroll position while dragging. emit dialogMoved(from, to); } if (creating) { refresh(); } else if (_state == State::Default && changed.movedFrom != changed.movedTo) { update(0, qMin(from, to), getFullWidth(), qAbs(from - to) + st::dialogsRowHeight); } } void DialogsInner::removeDialog(Dialogs::Key key) { if (key == _menuKey && _menu) { InvokeQueued(this, [this] { _menu = nullptr; }); } if (_selected && _selected->key() == key) { _selected = nullptr; } if (_pressed && _pressed->key() == key) { setPressed(nullptr); } const auto entry = key.entry(); entry->removeFromChatList( Dialogs::Mode::All, _dialogs.get()); if (_dialogsImportant) { entry->removeFromChatList( Dialogs::Mode::Important, _dialogsImportant.get()); } if (const auto history = key.history()) { Auth().notifications().clearFromHistory(history); Local::removeSavedPeer(history->peer); } if (_contacts->contains(key)) { if (!_contactsNoDialogs->contains(key)) { _contactsNoDialogs->addByName(key); } } emit App::main()->dialogsUpdated(); refresh(); } void DialogsInner::repaintDialogRow( Dialogs::Mode list, not_null<Dialogs::Row*> row) { if (_state == State::Default) { if (Global::DialogsMode() == list) { auto position = row->pos(); auto top = dialogsOffset(); if (base::in_range(position, 0, _pinnedRows.size())) { top += qRound(_pinnedRows[position].yadd.current()); } update(0, top + position * st::dialogsRowHeight, getFullWidth(), st::dialogsRowHeight); } } else if (_state == State::Filtered) { if (list == Dialogs::Mode::All) { for (auto i = 0, l = _filterResults.size(); i != l; ++i) { if (_filterResults[i]->key() == row->key()) { update( 0, filteredOffset() + i * st::dialogsRowHeight, getFullWidth(), st::dialogsRowHeight); break; } } } } } void DialogsInner::repaintDialogRow( not_null<History*> history, MsgId messageId) { updateDialogRow( Dialogs::RowDescriptor( history, FullMsgId(history->channelId(), messageId)), QRect(0, 0, getFullWidth(), st::dialogsRowHeight)); } void DialogsInner::updateSearchResult(not_null<PeerData*> peer) { if (_state == State::Filtered) { if (!_peerSearchResults.empty()) { auto index = 0, add = peerSearchOffset(); for_const (auto &result, _peerSearchResults) { if (result->peer == peer) { rtlupdate(0, add + index * st::dialogsRowHeight, getFullWidth(), st::dialogsRowHeight); break; } ++index; } } } } void DialogsInner::updateDialogRow( Dialogs::RowDescriptor row, QRect updateRect, UpdateRowSections sections) { if (IsServerMsgId(-row.fullId.msg)) { if (const auto peer = row.key.peer()) { if (const auto from = peer->migrateFrom()) { if (const auto migrated = App::historyLoaded(from)) { row = Dialogs::RowDescriptor( migrated, FullMsgId(0, -row.fullId.msg)); } } } } const auto updateRow = [&](int rowTop) { rtlupdate( updateRect.x(), rowTop + updateRect.y(), updateRect.width(), updateRect.height()); }; if (_state == State::Default) { if (sections & UpdateRowSection::Default) { if (const auto dialog = shownDialogs()->getRow(row.key)) { const auto position = dialog->pos(); auto top = dialogsOffset(); if (base::in_range(position, 0, _pinnedRows.size())) { top += qRound(_pinnedRows[position].yadd.current()); } updateRow(top + position * st::dialogsRowHeight); } } } else if (_state == State::Filtered) { if ((sections & UpdateRowSection::Filtered) && !_filterResults.isEmpty()) { const auto add = filteredOffset(); auto index = 0; for (const auto result : _filterResults) { if (result->key() == row.key) { updateRow(add + index * st::dialogsRowHeight); break; } ++index; } } if ((sections & UpdateRowSection::PeerSearch) && !_peerSearchResults.empty()) { if (const auto peer = row.key.peer()) { const auto add = peerSearchOffset(); auto index = 0; for (const auto &result : _peerSearchResults) { if (result->peer == peer) { updateRow(add + index * st::dialogsRowHeight); break; } ++index; } } } if ((sections & UpdateRowSection::MessageSearch) && !_searchResults.empty()) { const auto add = searchedOffset(); auto index = 0; for (const auto &result : _searchResults) { auto item = result->item(); if (item->fullId() == row.fullId) { updateRow(add + index * st::dialogsRowHeight); break; } ++index; } } } } void DialogsInner::enterEventHook(QEvent *e) { setMouseTracking(true); updateSelected(); } void DialogsInner::updateSelectedRow(Dialogs::Key key) { if (_state == State::Default) { if (key) { const auto entry = key.entry(); if (!entry->inChatList(Global::DialogsMode())) { return; } auto position = entry->posInChatList(Global::DialogsMode()); auto top = dialogsOffset(); if (base::in_range(position, 0, _pinnedRows.size())) { top += qRound(_pinnedRows[position].yadd.current()); } update(0, top + position * st::dialogsRowHeight, getFullWidth(), st::dialogsRowHeight); } else if (_selected) { update(0, dialogsOffset() + _selected->pos() * st::dialogsRowHeight, getFullWidth(), st::dialogsRowHeight); } else if (_importantSwitchSelected) { update(0, 0, getFullWidth(), st::dialogsImportantBarHeight); } } else if (_state == State::Filtered) { if (key) { for (auto i = 0, l = _filterResults.size(); i != l; ++i) { if (_filterResults[i]->key() == key) { update(0, filteredOffset() + i * st::dialogsRowHeight, getFullWidth(), st::dialogsRowHeight); break; } } } else if (_hashtagSelected >= 0) { update(0, _hashtagSelected * st::mentionHeight, getFullWidth(), st::mentionHeight); } else if (_filteredSelected >= 0) { update(0, filteredOffset() + _filteredSelected * st::dialogsRowHeight, getFullWidth(), st::dialogsRowHeight); } else if (_peerSearchSelected >= 0) { update(0, peerSearchOffset() + _peerSearchSelected * st::dialogsRowHeight, getFullWidth(), st::dialogsRowHeight); } else if (_searchedSelected >= 0) { update(0, searchedOffset() + _searchedSelected * st::dialogsRowHeight, getFullWidth(), st::dialogsRowHeight); } } } Dialogs::IndexedList *DialogsInner::shownDialogs() const { return (Global::DialogsMode() == Dialogs::Mode::Important) ? _dialogsImportant.get() : _dialogs.get(); } void DialogsInner::leaveEventHook(QEvent *e) { setMouseTracking(false); clearSelection(); } void DialogsInner::dragLeft() { setMouseTracking(false); clearSelection(); } void DialogsInner::clearSelection() { _mouseSelection = false; if (_importantSwitchSelected || _selected || _filteredSelected >= 0 || _hashtagSelected >= 0 || _peerSearchSelected >= 0 || _searchedSelected >= 0) { updateSelectedRow(); _importantSwitchSelected = false; _selected = nullptr; _filteredSelected = _searchedSelected = _peerSearchSelected = _hashtagSelected = -1; setCursor(style::cur_default); } } void DialogsInner::contextMenuEvent(QContextMenuEvent *e) { _menu = nullptr; if (e->reason() == QContextMenuEvent::Mouse) { _mouseSelection = true; updateSelected(); } const auto key = [&]() -> Dialogs::Key { if (_state == State::Default) { if (_selected) { return _selected->key(); } } else if (_state == State::Filtered) { if (base::in_range(_filteredSelected, 0, _filterResults.size())) { return _filterResults[_filteredSelected]->key(); } } return Dialogs::Key(); }(); if (!key) return; _menuKey = key; if (_pressButton != Qt::LeftButton) { mousePressReleased(_pressButton); } _menu = base::make_unique_q<Ui::PopupMenu>(nullptr); if (const auto history = key.history()) { Window::FillPeerMenu( _controller, history->peer, [&](const QString &text, base::lambda<void()> callback) { return _menu->addAction(text, std::move(callback)); }, Window::PeerMenuSource::ChatsList); } else if (const auto feed = key.feed()) { Window::FillFeedMenu( _controller, feed, [&](const QString &text, base::lambda<void()> callback) { return _menu->addAction(text, std::move(callback)); }, Window::PeerMenuSource::ChatsList); } connect(_menu.get(), &QObject::destroyed, [this] { if (_menuKey) { updateSelectedRow(base::take(_menuKey)); } auto localPos = mapFromGlobal(QCursor::pos()); if (rect().contains(localPos)) { _mouseSelection = true; setMouseTracking(true); updateSelected(localPos); } }); _menu->popup(e->globalPos()); e->accept(); } void DialogsInner::onParentGeometryChanged() { auto localPos = mapFromGlobal(QCursor::pos()); if (rect().contains(localPos)) { setMouseTracking(true); updateSelected(localPos); } } void DialogsInner::handlePeerNameChange( not_null<PeerData*> peer, const base::flat_set<QChar> &oldLetters) { _dialogs->peerNameChanged(Dialogs::Mode::All, peer, oldLetters); if (_dialogsImportant) { _dialogsImportant->peerNameChanged( Dialogs::Mode::Important, peer, oldLetters); } _contactsNoDialogs->peerNameChanged(peer, oldLetters); _contacts->peerNameChanged(peer, oldLetters); update(); } void DialogsInner::onFilterUpdate(QString newFilter, bool force) { const auto mentionsSearch = (newFilter == qstr("@")); const auto words = mentionsSearch ? QStringList(newFilter) : TextUtilities::PrepareSearchWords(newFilter); newFilter = words.isEmpty() ? QString() : words.join(' '); if (newFilter != _filter || force) { _filter = newFilter; if (_filter.isEmpty() && !_searchFromUser) { clearFilter(); } else { QStringList::const_iterator fb = words.cbegin(), fe = words.cend(), fi; _state = State::Filtered; _waitingForSearch = true; _filterResults.clear(); _filterResultsGlobal.clear(); if (!_searchInChat && !words.isEmpty()) { const Dialogs::List *toFilter = nullptr; if (!_dialogs->isEmpty()) { for (fi = fb; fi != fe; ++fi) { auto found = _dialogs->filtered(fi->at(0)); if (found->isEmpty()) { toFilter = nullptr; break; } if (!toFilter || toFilter->size() > found->size()) { toFilter = found; } } } const Dialogs::List *toFilterContacts = nullptr; if (!_contactsNoDialogs->isEmpty()) { for (fi = fb; fi != fe; ++fi) { auto found = _contactsNoDialogs->filtered(fi->at(0)); if (found->isEmpty()) { toFilterContacts = nullptr; break; } if (!toFilterContacts || toFilterContacts->size() > found->size()) { toFilterContacts = found; } } } _filterResults.reserve((toFilter ? toFilter->size() : 0) + (toFilterContacts ? toFilterContacts->size() : 0)); if (toFilter) { for (const auto row : *toFilter) { const auto &nameWords = row->entry()->chatsListNameWords(); auto nb = nameWords.cbegin(), ne = nameWords.cend(), ni = nb; for (fi = fb; fi != fe; ++fi) { auto filterWord = *fi; for (ni = nb; ni != ne; ++ni) { if (ni->startsWith(filterWord)) { break; } } if (ni == ne) { break; } } if (fi == fe) { _filterResults.push_back(row); } } } if (toFilterContacts) { for (const auto row : *toFilterContacts) { const auto &nameWords = row->entry()->chatsListNameWords(); auto nb = nameWords.cbegin(), ne = nameWords.cend(), ni = nb; for (fi = fb; fi != fe; ++fi) { auto filterWord = *fi; for (ni = nb; ni != ne; ++ni) { if (ni->startsWith(filterWord)) { break; } } if (ni == ne) { break; } } if (fi == fe) { _filterResults.push_back(row); } } } } refresh(true); } setMouseSelection(false, true); } if (_state != State::Default) { emit searchMessages(); } } void DialogsInner::onHashtagFilterUpdate(QStringRef newFilter) { if (newFilter.isEmpty() || newFilter.at(0) != '#' || _searchInChat) { _hashtagFilter = QString(); if (!_hashtagResults.empty()) { _hashtagResults.clear(); refresh(true); setMouseSelection(false, true); } return; } _hashtagFilter = newFilter.toString(); if (cRecentSearchHashtags().isEmpty() && cRecentWriteHashtags().isEmpty()) { Local::readRecentHashtagsAndBots(); } auto &recent = cRecentSearchHashtags(); _hashtagResults.clear(); if (!recent.isEmpty()) { _hashtagResults.reserve(qMin(recent.size(), kHashtagResultsLimit)); for (auto i = recent.cbegin(), e = recent.cend(); i != e; ++i) { if (i->first.startsWith(_hashtagFilter.midRef(1), Qt::CaseInsensitive) && i->first.size() + 1 != newFilter.size()) { _hashtagResults.push_back(std::make_unique<HashtagResult>(i->first)); if (_hashtagResults.size() == kHashtagResultsLimit) break; } } } refresh(true); setMouseSelection(false, true); } DialogsInner::~DialogsInner() { clearSearchResults(); } void DialogsInner::clearSearchResults(bool clearPeerSearchResults) { if (clearPeerSearchResults) _peerSearchResults.clear(); _searchResults.clear(); _searchedCount = _searchedMigratedCount = 0; _lastSearchDate = 0; _lastSearchPeer = 0; _lastSearchId = _lastSearchMigratedId = 0; } PeerData *DialogsInner::updateFromParentDrag(QPoint globalPos) { _mouseSelection = true; updateSelected(mapFromGlobal(globalPos)); const auto getPeerFromRow = [](Dialogs::Row *row) -> PeerData* { if (const auto history = row ? row->history() : nullptr) { return history->peer; } return nullptr; }; if (_state == State::Default) { return getPeerFromRow(_selected); } else if (_state == State::Filtered) { if (base::in_range(_filteredSelected, 0, _filterResults.size())) { return getPeerFromRow(_filterResults[_filteredSelected]); } else if (base::in_range(_peerSearchSelected, 0, _peerSearchResults.size())) { return _peerSearchResults[_peerSearchSelected]->peer; } else if (base::in_range(_searchedSelected, 0, _searchResults.size())) { return _searchResults[_searchedSelected]->item()->history()->peer; } } return nullptr; } void DialogsInner::visibleTopBottomUpdated( int visibleTop, int visibleBottom) { _visibleTop = visibleTop; _visibleBottom = visibleBottom; loadPeerPhotos(); if (_visibleTop + PreloadHeightsCount * (_visibleBottom - _visibleTop) >= height()) { if (_loadMoreCallback) { _loadMoreCallback(); } } } void DialogsInner::itemRemoved(not_null<const HistoryItem*> item) { int wasCount = _searchResults.size(); for (auto i = _searchResults.begin(); i != _searchResults.end();) { if ((*i)->item() == item) { i = _searchResults.erase(i); if (item->history() == _searchInMigrated) { if (_searchedMigratedCount > 0) --_searchedMigratedCount; } else { if (_searchedCount > 0) --_searchedCount; } } else { ++i; } } if (wasCount != _searchResults.size()) { refresh(); } } void DialogsInner::dialogsReceived(const QVector<MTPDialog> &added) { for (const auto &dialog : added) { switch (dialog.type()) { case mtpc_dialog: applyDialog(dialog.c_dialog()); break; //case mtpc_dialogFeed: applyFeedDialog(dialog.c_dialogFeed()); break; // #feed default: Unexpected("Type in DialogsInner::dialogsReceived"); } } refresh(); } void DialogsInner::applyDialog(const MTPDdialog &dialog) { const auto peerId = peerFromMTP(dialog.vpeer); if (!peerId) { return; } const auto history = App::history(peerId); history->applyDialog(dialog); if (!history->isPinnedDialog()) { const auto date = history->chatsListDate(); if (!date.isNull()) { addSavedPeersAfter(date); } } _contactsNoDialogs->del(history); if (const auto from = history->peer->migrateFrom()) { if (const auto historyFrom = App::historyLoaded(from)) { removeDialog(historyFrom); } } else if (const auto to = history->peer->migrateTo()) { if (to->amIn()) { removeDialog(history); } } } // #feed //void DialogsInner::applyFeedDialog(const MTPDdialogFeed &dialog) { // const auto feedId = dialog.vfeed_id.v; // const auto feed = Auth().data().feed(feedId); // feed->applyDialog(dialog); // // if (!feed->isPinnedDialog()) { // const auto date = feed->chatsListDate(); // if (!date.isNull()) { // addSavedPeersAfter(date); // } // } //} void DialogsInner::addSavedPeersAfter(const QDateTime &date) { auto &saved = cRefSavedPeersByTime(); while (!saved.isEmpty() && (date.isNull() || date < saved.lastKey())) { const auto history = App::history(saved.last()->id); history->setChatsListDate(saved.lastKey()); _contactsNoDialogs->del(history); saved.remove(saved.lastKey(), saved.last()); } } void DialogsInner::addAllSavedPeers() { addSavedPeersAfter(QDateTime()); } bool DialogsInner::searchReceived( const QVector<MTPMessage> &messages, DialogsSearchRequestType type, int fullCount) { if (type == DialogsSearchFromStart || type == DialogsSearchPeerFromStart) { clearSearchResults(false); } auto isGlobalSearch = (type == DialogsSearchFromStart || type == DialogsSearchFromOffset); auto isMigratedSearch = (type == DialogsSearchMigratedFromStart || type == DialogsSearchMigratedFromOffset); TimeId lastDateFound = 0; for_const (auto message, messages) { auto msgId = idFromMessage(message); auto peerId = peerFromMessage(message); auto lastDate = dateFromMessage(message); if (auto peer = App::peerLoaded(peerId)) { if (lastDate) { auto item = App::histories().addNewMessage(message, NewMessageExisting); _searchResults.push_back( std::make_unique<Dialogs::FakeRow>( _searchInChat, item)); lastDateFound = lastDate; if (isGlobalSearch) { _lastSearchDate = lastDateFound; } } if (isGlobalSearch) { _lastSearchPeer = peer; } } else { LOG(("API Error: a search results with not loaded peer %1").arg(peerId)); } if (isMigratedSearch) { _lastSearchMigratedId = msgId; } else { _lastSearchId = msgId; } } if (isMigratedSearch) { _searchedMigratedCount = fullCount; } else { _searchedCount = fullCount; } if (_waitingForSearch && (!_searchResults.empty() || !_searchInMigrated || type == DialogsSearchMigratedFromStart || type == DialogsSearchMigratedFromOffset)) { _waitingForSearch = false; } refresh(); return lastDateFound != 0; } void DialogsInner::peerSearchReceived( const QString &query, const QVector<MTPPeer> &my, const QVector<MTPPeer> &result) { if (_state != State::Filtered) { return; } const auto alreadyAdded = [&](not_null<PeerData*> peer) { for (const auto &row : _filterResults) { if (const auto history = row->history()) { if (history->peer == peer) { return true; } } } return false; }; _peerSearchQuery = query.toLower().trimmed(); _peerSearchResults.clear(); _peerSearchResults.reserve(result.size()); for (const auto &mtpPeer : my) { if (const auto peer = App::peerLoaded(peerFromMTP(mtpPeer))) { if (alreadyAdded(peer)) { continue; } const auto prev = nullptr, next = nullptr; const auto position = 0; auto row = std::make_unique<Dialogs::Row>( App::history(peer), prev, next, position); const auto [i, ok] = _filterResultsGlobal.emplace( peer, std::move(row)); _filterResults.push_back(i->second.get()); } else { LOG(("API Error: " "user %1 was not loaded in DialogsInner::peopleReceived()" ).arg(peer->id)); } } for (const auto &mtpPeer : result) { if (const auto peer = App::peerLoaded(peerFromMTP(mtpPeer))) { if (const auto history = App::historyLoaded(peer)) { if (history->inChatList(Dialogs::Mode::All)) { continue; // skip existing chats } } _peerSearchResults.push_back(std::make_unique<PeerSearchResult>( peer)); } else { LOG(("API Error: " "user %1 was not loaded in DialogsInner::peopleReceived()" ).arg(peer->id)); } } refresh(); } void DialogsInner::userIsContactUpdated(not_null<UserData*> user) { if (user->loadedStatus != PeerData::FullLoaded) { LOG(("API Error: " "notify_userIsContactChanged() called for a not loaded user!")); return; } if (user->contactStatus() == UserData::ContactStatus::Contact) { const auto history = App::history(user->id); _contacts->addByName(history); if (!shownDialogs()->getRow(history) && !_dialogs->contains(history)) { _contactsNoDialogs->addByName(history); } } else if (const auto history = App::historyLoaded(user)) { if (_selected && _selected->history() == history) { _selected = nullptr; } if (_pressed && _pressed->history() == history) { setPressed(nullptr); } _contactsNoDialogs->del(history); _contacts->del(history); } refresh(); } void DialogsInner::notify_historyMuteUpdated(History *history) { if (!_dialogsImportant || !history->inChatList(Dialogs::Mode::All)) return; if (history->mute()) { if (Global::DialogsMode() == Dialogs::Mode::Important) { if (_selected && _selected->history() == history) { _selected = nullptr; } if (_pressed && _pressed->history() == history) { setPressed(nullptr); } } history->removeFromChatList(Dialogs::Mode::Important, _dialogsImportant.get()); if (Global::DialogsMode() != Dialogs::Mode::Important) { return; } refresh(); } else { bool creating = !history->inChatList(Dialogs::Mode::Important); if (creating) { history->addToChatList(Dialogs::Mode::Important, _dialogsImportant.get()); } auto changed = history->adjustByPosInChatList(Dialogs::Mode::All, _dialogs.get()); if (Global::DialogsMode() != Dialogs::Mode::Important) { return; } int from = dialogsOffset() + changed.movedFrom * st::dialogsRowHeight; int to = dialogsOffset() + changed.movedTo * st::dialogsRowHeight; if (!_dragging) { // Don't jump in chats list scroll position while dragging. emit dialogMoved(from, to); } if (creating) { refresh(); } else if (_state == State::Default && changed.movedFrom != changed.movedTo) { update(0, qMin(from, to), getFullWidth(), qAbs(from - to) + st::dialogsRowHeight); } } } void DialogsInner::refresh(bool toTop) { int32 h = 0; if (_state == State::Default) { if (shownDialogs()->isEmpty()) { h = st::noContactsHeight; if (Auth().data().contactsLoaded().value()) { if (_addContactLnk->isHidden()) _addContactLnk->show(); } else { if (!_addContactLnk->isHidden()) _addContactLnk->hide(); } } else { h = dialogsOffset() + shownDialogs()->size() * st::dialogsRowHeight; if (!_addContactLnk->isHidden()) _addContactLnk->hide(); } } else if (_state == State::Filtered) { if (!_addContactLnk->isHidden()) _addContactLnk->hide(); if (_waitingForSearch) { h = searchedOffset() + (_searchResults.size() * st::dialogsRowHeight) + ((_searchResults.empty() && !_searchInChat) ? -st::searchedBarHeight : 0); } else { h = searchedOffset() + (_searchResults.size() * st::dialogsRowHeight); } } setHeight(h); if (toTop) { stopReorderPinned(); emit mustScrollTo(0, 0); loadPeerPhotos(); } _controller->dialogsListDisplayForced().set( _searchInChat || !_filter.isEmpty(), true); update(); } void DialogsInner::setMouseSelection(bool mouseSelection, bool toTop) { _mouseSelection = mouseSelection; if (!_mouseSelection && toTop) { if (_state == State::Default) { _selected = nullptr; _importantSwitchSelected = false; } else if (_state == State::Filtered) { _filteredSelected = _peerSearchSelected = _searchedSelected = _hashtagSelected = -1; } setCursor(style::cur_default); } } DialogsInner::State DialogsInner::state() const { return _state; } bool DialogsInner::hasFilteredResults() const { return !_filterResults.isEmpty() && _hashtagResults.empty(); } void DialogsInner::searchInChat(Dialogs::Key key, UserData *from) { _searchInMigrated = nullptr; if (const auto peer = key.peer()) { if (const auto migrateTo = peer->migrateTo()) { return searchInChat(App::history(migrateTo), from); } else if (const auto migrateFrom = peer->migrateFrom()) { _searchInMigrated = App::history(migrateFrom); } } _searchInChat = key; _searchFromUser = from; if (_searchInChat) { onHashtagFilterUpdate(QStringRef()); _cancelSearchInChat->show(); refreshSearchInChatLabel(); } else { _cancelSearchInChat->hide(); } if (_searchFromUser) { _cancelSearchFromUser->show(); } else { _cancelSearchFromUser->hide(); } _controller->dialogsListDisplayForced().set( _searchInChat || !_filter.isEmpty(), true); } void DialogsInner::refreshSearchInChatLabel() { const auto dialog = [&] { if (const auto peer = _searchInChat.peer()) { if (peer->isSelf()) { return lang(lng_saved_messages); } return peer->name; } else if (const auto feed = _searchInChat.feed()) { return feed->chatsListName(); } return QString(); }(); if (!dialog.isEmpty()) { _searchInChatText.setText( st::msgNameStyle, dialog, Ui::DialogTextOptions()); } const auto from = [&] { if (const auto from = _searchFromUser) { return App::peerName(from); } return QString(); }(); if (!from.isEmpty()) { const auto fromUserText = lng_dlg_search_from( lt_user, textcmdLink(1, from)); _searchFromUserText.setText( st::dialogsSearchFromStyle, fromUserText, Ui::DialogTextOptions()); } } void DialogsInner::clearFilter() { if (_state == State::Filtered || _searchInChat) { if (_searchInChat) { _state = State::Filtered; _waitingForSearch = true; } else { _state = State::Default; } _hashtagResults.clear(); _filterResults.clear(); _filterResultsGlobal.clear(); _peerSearchResults.clear(); _searchResults.clear(); _lastSearchDate = 0; _lastSearchPeer = 0; _lastSearchId = _lastSearchMigratedId = 0; _filter = QString(); refresh(true); } } void DialogsInner::selectSkip(int32 direction) { if (_state == State::Default) { if (_importantSwitchSelected) { if (!shownDialogs()->isEmpty() && direction > 0) { _selected = *shownDialogs()->cbegin(); _importantSwitchSelected = false; } else { return; } } else if (!_selected) { if (_dialogsImportant) { _importantSwitchSelected = true; } else if (!shownDialogs()->isEmpty() && direction > 0) { _selected = *shownDialogs()->cbegin(); } else { return; } } else if (direction > 0) { auto next = shownDialogs()->cfind(_selected); if (++next != shownDialogs()->cend()) { _selected = *next; } } else { auto prev = shownDialogs()->cfind(_selected); if (prev != shownDialogs()->cbegin()) { _selected = *(--prev); } else if (_dialogsImportant) { _importantSwitchSelected = true; _selected = nullptr; } } if (_importantSwitchSelected || _selected) { int fromY = _importantSwitchSelected ? 0 : (dialogsOffset() + _selected->pos() * st::dialogsRowHeight); emit mustScrollTo(fromY, fromY + st::dialogsRowHeight); } } else if (_state == State::Filtered) { if (_hashtagResults.empty() && _filterResults.isEmpty() && _peerSearchResults.empty() && _searchResults.empty()) return; if ((_hashtagSelected < 0 || _hashtagSelected >= _hashtagResults.size()) && (_filteredSelected < 0 || _filteredSelected >= _filterResults.size()) && (_peerSearchSelected < 0 || _peerSearchSelected >= _peerSearchResults.size()) && (_searchedSelected < 0 || _searchedSelected >= _searchResults.size())) { if (_hashtagResults.empty() && _filterResults.isEmpty() && _peerSearchResults.empty()) { _searchedSelected = 0; } else if (_hashtagResults.empty() && _filterResults.isEmpty()) { _peerSearchSelected = 0; } else if (_hashtagResults.empty()) { _filteredSelected = 0; } else { _hashtagSelected = 0; } } else { int32 cur = base::in_range(_hashtagSelected, 0, _hashtagResults.size()) ? _hashtagSelected : (base::in_range(_filteredSelected, 0, _filterResults.size()) ? (_hashtagResults.size() + _filteredSelected) : (base::in_range(_peerSearchSelected, 0, _peerSearchResults.size()) ? (_peerSearchSelected + _filterResults.size() + _hashtagResults.size()) : (_searchedSelected + _peerSearchResults.size() + _filterResults.size() + _hashtagResults.size()))); cur = snap(cur + direction, 0, static_cast<int>(_hashtagResults.size() + _filterResults.size() + _peerSearchResults.size() + _searchResults.size()) - 1); if (cur < _hashtagResults.size()) { _hashtagSelected = cur; _filteredSelected = _peerSearchSelected = _searchedSelected = -1; } else if (cur < _hashtagResults.size() + _filterResults.size()) { _filteredSelected = cur - _hashtagResults.size(); _hashtagSelected = _peerSearchSelected = _searchedSelected = -1; } else if (cur < _hashtagResults.size() + _filterResults.size() + _peerSearchResults.size()) { _peerSearchSelected = cur - _hashtagResults.size() - _filterResults.size(); _hashtagSelected = _filteredSelected = _searchedSelected = -1; } else { _hashtagSelected = _filteredSelected = _peerSearchSelected = -1; _searchedSelected = cur - _hashtagResults.size() - _filterResults.size() - _peerSearchResults.size(); } } if (base::in_range(_hashtagSelected, 0, _hashtagResults.size())) { emit mustScrollTo(_hashtagSelected * st::mentionHeight, (_hashtagSelected + 1) * st::mentionHeight); } else if (base::in_range(_filteredSelected, 0, _filterResults.size())) { emit mustScrollTo(filteredOffset() + _filteredSelected * st::dialogsRowHeight, filteredOffset() + (_filteredSelected + 1) * st::dialogsRowHeight); } else if (base::in_range(_peerSearchSelected, 0, _peerSearchResults.size())) { emit mustScrollTo(peerSearchOffset() + _peerSearchSelected * st::dialogsRowHeight + (_peerSearchSelected ? 0 : -st::searchedBarHeight), peerSearchOffset() + (_peerSearchSelected + 1) * st::dialogsRowHeight); } else { emit mustScrollTo(searchedOffset() + _searchedSelected * st::dialogsRowHeight + (_searchedSelected ? 0 : -st::searchedBarHeight), searchedOffset() + (_searchedSelected + 1) * st::dialogsRowHeight); } } update(); } void DialogsInner::scrollToPeer(not_null<History*> history, MsgId msgId) { int32 fromY = -1; if (_state == State::Default) { if (auto row = shownDialogs()->getRow(history)) { fromY = dialogsOffset() + row->pos() * st::dialogsRowHeight; } } else if (_state == State::Filtered) { if (msgId) { for (int32 i = 0, c = _searchResults.size(); i < c; ++i) { if (_searchResults[i]->item()->history() == history && _searchResults[i]->item()->id == msgId) { fromY = searchedOffset() + i * st::dialogsRowHeight; break; } } } if (fromY < 0) { for (auto i = 0, c = _filterResults.size(); i != c; ++i) { if (_filterResults[i]->history() == history) { fromY = filteredOffset() + (i * st::dialogsRowHeight); break; } } } } if (fromY >= 0) { emit mustScrollTo(fromY, fromY + st::dialogsRowHeight); } } void DialogsInner::selectSkipPage(int32 pixels, int32 direction) { int toSkip = pixels / int(st::dialogsRowHeight); if (_state == State::Default) { if (!_selected) { if (direction > 0 && !shownDialogs()->isEmpty()) { _selected = *shownDialogs()->cbegin(); _importantSwitchSelected = false; } else { return; } } if (direction > 0) { for (auto i = shownDialogs()->cfind(_selected), end = shownDialogs()->cend(); i != end && (toSkip--); ++i) { _selected = *i; } } else { for (auto i = shownDialogs()->cfind(_selected), b = shownDialogs()->cbegin(); i != b && (toSkip--);) { _selected = *(--i); } if (toSkip && _dialogsImportant) { _importantSwitchSelected = true; _selected = nullptr; } } if (_importantSwitchSelected || _selected) { int fromY = (_importantSwitchSelected ? 0 : (dialogsOffset() + _selected->pos() * st::dialogsRowHeight)); emit mustScrollTo(fromY, fromY + st::dialogsRowHeight); } } else { return selectSkip(direction * toSkip); } update(); } void DialogsInner::loadPeerPhotos() { if (!parentWidget()) return; auto yFrom = _visibleTop; auto yTo = _visibleTop + (_visibleBottom - _visibleTop) * (PreloadHeightsCount + 1); Auth().downloader().clearPriorities(); if (_state == State::Default) { auto otherStart = shownDialogs()->size() * st::dialogsRowHeight; if (yFrom < otherStart) { for (auto i = shownDialogs()->cfind(yFrom, st::dialogsRowHeight), end = shownDialogs()->cend(); i != end; ++i) { if (((*i)->pos() * st::dialogsRowHeight) >= yTo) { break; } (*i)->entry()->loadUserpic(); } yFrom = 0; } else { yFrom -= otherStart; } yTo -= otherStart; } else if (_state == State::Filtered) { int32 from = (yFrom - filteredOffset()) / st::dialogsRowHeight; if (from < 0) from = 0; if (from < _filterResults.size()) { int32 to = (yTo / int32(st::dialogsRowHeight)) + 1, w = width(); if (to > _filterResults.size()) to = _filterResults.size(); for (; from < to; ++from) { _filterResults[from]->entry()->loadUserpic(); } } from = (yFrom > filteredOffset() + st::searchedBarHeight ? ((yFrom - filteredOffset() - st::searchedBarHeight) / int32(st::dialogsRowHeight)) : 0) - _filterResults.size(); if (from < 0) from = 0; if (from < _peerSearchResults.size()) { int32 to = (yTo > filteredOffset() + st::searchedBarHeight ? ((yTo - filteredOffset() - st::searchedBarHeight) / int32(st::dialogsRowHeight)) : 0) - _filterResults.size() + 1, w = width(); if (to > _peerSearchResults.size()) to = _peerSearchResults.size(); for (; from < to; ++from) { _peerSearchResults[from]->peer->loadUserpic(); } } from = (yFrom > filteredOffset() + ((_peerSearchResults.empty() ? 0 : st::searchedBarHeight) + st::searchedBarHeight) ? ((yFrom - filteredOffset() - (_peerSearchResults.empty() ? 0 : st::searchedBarHeight) - st::searchedBarHeight) / int32(st::dialogsRowHeight)) : 0) - _filterResults.size() - _peerSearchResults.size(); if (from < 0) from = 0; if (from < _searchResults.size()) { int32 to = (yTo > filteredOffset() + (_peerSearchResults.empty() ? 0 : st::searchedBarHeight) + st::searchedBarHeight ? ((yTo - filteredOffset() - (_peerSearchResults.empty() ? 0 : st::searchedBarHeight) - st::searchedBarHeight) / int32(st::dialogsRowHeight)) : 0) - _filterResults.size() - _peerSearchResults.size() + 1, w = width(); if (to > _searchResults.size()) to = _searchResults.size(); for (; from < to; ++from) { _searchResults[from]->item()->history()->peer->loadUserpic(); } } } } bool DialogsInner::switchImportantChats() { if (!_importantSwitchSelected || !_dialogsImportant || (_state != State::Default)) { return false; } clearSelection(); if (Global::DialogsMode() == Dialogs::Mode::All) { Global::SetDialogsMode(Dialogs::Mode::Important); } else { Global::SetDialogsMode(Dialogs::Mode::All); } Local::writeUserSettings(); refresh(); _importantSwitchSelected = true; return true; } bool DialogsInner::chooseHashtag() { if (_state != State::Filtered) { return false; } else if ((_hashtagSelected < 0) || (_hashtagSelected >= _hashtagResults.size())) { return false; } const auto &hashtag = _hashtagResults[_hashtagSelected]; if (_hashtagDeleteSelected) { auto recent = cRecentSearchHashtags(); for (auto i = recent.begin(); i != recent.cend();) { if (i->first == hashtag->tag) { i = recent.erase(i); } else { ++i; } } cSetRecentSearchHashtags(recent); Local::writeRecentHashtagsAndBots(); emit refreshHashtags(); _mouseSelection = true; updateSelected(); } else { saveRecentHashtags('#' + hashtag->tag); emit completeHashtag(hashtag->tag); } return true; } DialogsInner::ChosenRow DialogsInner::computeChosenRow() const { auto msgId = ShowAtUnreadMsgId; if (_state == State::Default) { if (_selected) { return { _selected->key(), Data::UnreadMessagePosition }; } } else if (_state == State::Filtered) { if (base::in_range(_filteredSelected, 0, _filterResults.size())) { return { _filterResults[_filteredSelected]->key(), Data::UnreadMessagePosition }; } else if (base::in_range(_peerSearchSelected, 0, _peerSearchResults.size())) { return { App::history(_peerSearchResults[_peerSearchSelected]->peer), Data::UnreadMessagePosition }; } else if (base::in_range(_searchedSelected, 0, _searchResults.size())) { const auto result = _searchResults[_searchedSelected].get(); if (const auto feed = result->searchInChat().feed()) { return { feed, result->item()->position() }; } else { return { result->item()->history(), result->item()->position() }; } } } return ChosenRow(); } bool DialogsInner::chooseRow() { if (switchImportantChats()) { return true; } else if (chooseHashtag()) { return true; } const auto chosen = computeChosenRow(); if (chosen.key) { if (IsServerMsgId(chosen.message.fullId.msg)) { saveRecentHashtags(_filter); } const auto openSearchResult = !App::main()->selectingPeer(true) && (_state == State::Filtered) && base::in_range(_filteredSelected, 0, _filterResults.size()); if (const auto history = chosen.key.history()) { App::main()->choosePeer( history->peer->id, chosen.message.fullId.msg); } else if (const auto feed = chosen.key.feed()) { _controller->showSection( HistoryFeed::Memento(feed, chosen.message), Window::SectionShow::Way::ClearStack); } if (openSearchResult) { emit searchResultChosen(); } updateSelectedRow(); _selected = nullptr; _hashtagSelected = _filteredSelected = _peerSearchSelected = _searchedSelected = -1; return true; } return false; } void DialogsInner::saveRecentHashtags(const QString &text) { auto found = false; QRegularExpressionMatch m; auto recent = cRecentSearchHashtags(); for (int32 i = 0, next = 0; (m = TextUtilities::RegExpHashtag().match(text, i)).hasMatch(); i = next) { i = m.capturedStart(); next = m.capturedEnd(); if (m.hasMatch()) { if (!m.capturedRef(1).isEmpty()) { ++i; } if (!m.capturedRef(2).isEmpty()) { --next; } } const auto tag = text.mid(i + 1, next - i - 1); if (TextUtilities::RegExpHashtagExclude().match(tag).hasMatch()) { continue; } if (!found && cRecentWriteHashtags().isEmpty() && cRecentSearchHashtags().isEmpty()) { Local::readRecentHashtagsAndBots(); recent = cRecentSearchHashtags(); } found = true; Stickers::IncrementRecentHashtag(recent, tag); } if (found) { cSetRecentSearchHashtags(recent); Local::writeRecentHashtagsAndBots(); } } void DialogsInner::destroyData() { _selected = nullptr; _hashtagSelected = -1; _hashtagResults.clear(); _filteredSelected = -1; _filterResults.clear(); _filterResultsGlobal.clear(); _filter.clear(); _searchedSelected = _peerSearchSelected = -1; clearSearchResults(); _contacts = nullptr; _contactsNoDialogs = nullptr; _dialogs = nullptr; if (_dialogsImportant) { _dialogsImportant = nullptr; } } Dialogs::RowDescriptor DialogsInner::chatListEntryBefore( const Dialogs::RowDescriptor &which) const { if (!which.key) { return Dialogs::RowDescriptor(); } if (_state == State::Default) { if (const auto row = shownDialogs()->getRow(which.key)) { const auto i = shownDialogs()->cfind(row); if (i != shownDialogs()->cbegin()) { return Dialogs::RowDescriptor( (*(i - 1))->key(), FullMsgId(NoChannel, ShowAtUnreadMsgId)); } } return Dialogs::RowDescriptor(); } const auto whichHistory = which.key.history(); const auto whichFullId = which.fullId; if (!whichHistory) { return Dialogs::RowDescriptor(); } if (whichFullId && !_searchResults.empty()) { for (auto b = _searchResults.cbegin(), i = b + 1, e = _searchResults.cend(); i != e; ++i) { if ((*i)->item()->fullId() == whichFullId) { const auto j = i - 1; return Dialogs::RowDescriptor( (*j)->item()->history(), (*j)->item()->fullId()); } } if (_searchResults[0]->item()->fullId() == whichFullId) { if (_peerSearchResults.empty()) { if (_filterResults.isEmpty()) { return Dialogs::RowDescriptor(); } return Dialogs::RowDescriptor( _filterResults.back()->key(), FullMsgId(NoChannel, ShowAtUnreadMsgId)); } return Dialogs::RowDescriptor( App::history(_peerSearchResults.back()->peer), FullMsgId(NoChannel, ShowAtUnreadMsgId)); } } if (!_peerSearchResults.empty() && _peerSearchResults[0]->peer == whichHistory->peer) { if (_filterResults.isEmpty()) { return Dialogs::RowDescriptor(); } return Dialogs::RowDescriptor( _filterResults.back()->key(), FullMsgId(NoChannel, ShowAtUnreadMsgId)); } if (!_peerSearchResults.empty()) { for (auto b = _peerSearchResults.cbegin(), i = b + 1, e = _peerSearchResults.cend(); i != e; ++i) { if ((*i)->peer == whichHistory->peer) { return Dialogs::RowDescriptor( App::history((*(i - 1))->peer), FullMsgId(NoChannel, ShowAtUnreadMsgId)); } } } if (_filterResults.isEmpty() || _filterResults[0]->key() == which.key) { return Dialogs::RowDescriptor(); } for (auto b = _filterResults.cbegin(), i = b + 1, e = _filterResults.cend(); i != e; ++i) { if ((*i)->key() == which.key) { return Dialogs::RowDescriptor( (*(i - 1))->key(), FullMsgId(NoChannel, ShowAtUnreadMsgId)); } } return Dialogs::RowDescriptor(); } Dialogs::RowDescriptor DialogsInner::chatListEntryAfter( const Dialogs::RowDescriptor &which) const { if (!which.key) { return Dialogs::RowDescriptor(); } if (_state == State::Default) { if (const auto row = shownDialogs()->getRow(which.key)) { const auto i = shownDialogs()->cfind(row) + 1; if (i != shownDialogs()->cend()) { return Dialogs::RowDescriptor( (*i)->key(), FullMsgId(NoChannel, ShowAtUnreadMsgId)); } } return Dialogs::RowDescriptor(); } const auto whichHistory = which.key.history(); const auto whichFullId = which.fullId; if (!whichHistory) { return Dialogs::RowDescriptor(); } if (whichFullId) { for (auto i = _searchResults.cbegin(), e = _searchResults.cend(); i != e; ++i) { if ((*i)->item()->fullId() == whichFullId) { if (++i != e) { return Dialogs::RowDescriptor( (*i)->item()->history(), (*i)->item()->fullId()); } return Dialogs::RowDescriptor(); } } } for (auto i = _peerSearchResults.cbegin(), e = _peerSearchResults.cend(); i != e; ++i) { if ((*i)->peer == whichHistory->peer) { ++i; if (i != e) { return Dialogs::RowDescriptor( App::history((*i)->peer), FullMsgId(NoChannel, ShowAtUnreadMsgId)); } else if (!_searchResults.empty()) { return Dialogs::RowDescriptor( _searchResults.front()->item()->history(), _searchResults.front()->item()->fullId()); } return Dialogs::RowDescriptor(); } } for (auto i = _filterResults.cbegin(), e = _filterResults.cend(); i != e; ++i) { if ((*i)->key() == which.key) { ++i; if (i != e) { return Dialogs::RowDescriptor( (*i)->key(), FullMsgId(NoChannel, ShowAtUnreadMsgId)); } else if (!_peerSearchResults.empty()) { return Dialogs::RowDescriptor( App::history(_peerSearchResults.front()->peer), FullMsgId(NoChannel, ShowAtUnreadMsgId)); } else if (!_searchResults.empty()) { return Dialogs::RowDescriptor( _searchResults.front()->item()->history(), _searchResults.front()->item()->fullId()); } return Dialogs::RowDescriptor(); } } return Dialogs::RowDescriptor(); } Dialogs::IndexedList *DialogsInner::contactsList() { return _contacts.get(); } Dialogs::IndexedList *DialogsInner::dialogsList() { return _dialogs.get(); } Dialogs::IndexedList *DialogsInner::contactsNoDialogsList() { return _contactsNoDialogs.get(); } int32 DialogsInner::lastSearchDate() const { return _lastSearchDate; } PeerData *DialogsInner::lastSearchPeer() const { return _lastSearchPeer; } MsgId DialogsInner::lastSearchId() const { return _lastSearchId; } MsgId DialogsInner::lastSearchMigratedId() const { return _lastSearchMigratedId; } �����������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h���������������������������������0000664�0000000�0000000�00000023366�13262451251�0025410�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "dialogs/dialogs_widget.h" #include "dialogs/dialogs_key.h" #include "data/data_messages.h" #include "base/flags.h" namespace Dialogs { class Row; class FakeRow; class IndexedList; } // namespace Dialogs namespace Ui { class IconButton; class PopupMenu; class LinkButton; } // namespace Ui namespace Window { class Controller; } // namespace Window class DialogsInner : public Ui::SplittedWidget, public RPCSender, private base::Subscriber { Q_OBJECT public: DialogsInner(QWidget *parent, not_null<Window::Controller*> controller, QWidget *main); void dialogsReceived(const QVector<MTPDialog> &dialogs); void addSavedPeersAfter(const QDateTime &date); void addAllSavedPeers(); bool searchReceived( const QVector<MTPMessage> &result, DialogsSearchRequestType type, int fullCount); void peerSearchReceived( const QString &query, const QVector<MTPPeer> &my, const QVector<MTPPeer> &result); void showMore(int32 pixels); void activate(); void selectSkip(int32 direction); void selectSkipPage(int32 pixels, int32 direction); void createDialog(Dialogs::Key key); void removeDialog(Dialogs::Key key); void repaintDialogRow(Dialogs::Mode list, not_null<Dialogs::Row*> row); void repaintDialogRow(not_null<History*> history, MsgId messageId); void dragLeft(); void clearFilter(); void refresh(bool toTop = false); bool chooseRow(); void saveRecentHashtags(const QString &text); void destroyData(); Dialogs::RowDescriptor chatListEntryBefore( const Dialogs::RowDescriptor &which) const; Dialogs::RowDescriptor chatListEntryAfter( const Dialogs::RowDescriptor &which) const; void scrollToPeer(not_null<History*> history, MsgId msgId); Dialogs::IndexedList *contactsList(); Dialogs::IndexedList *dialogsList(); Dialogs::IndexedList *contactsNoDialogsList(); int32 lastSearchDate() const; PeerData *lastSearchPeer() const; MsgId lastSearchId() const; MsgId lastSearchMigratedId() const; void setMouseSelection(bool mouseSelection, bool toTop = false); enum class State { Default, Filtered, }; State state() const; bool waitingForSearch() const { return _waitingForSearch; } bool hasFilteredResults() const; void searchInChat(Dialogs::Key key, UserData *from); void onFilterUpdate(QString newFilter, bool force = false); void onHashtagFilterUpdate(QStringRef newFilter); PeerData *updateFromParentDrag(QPoint globalPos); void setLoadMoreCallback(base::lambda<void()> callback) { _loadMoreCallback = std::move(callback); } base::Observable<UserData*> searchFromUserChanged; void notify_historyMuteUpdated(History *history); ~DialogsInner(); public slots: void onParentGeometryChanged(); void onDialogRowReplaced(Dialogs::Row *oldRow, Dialogs::Row *newRow); signals: void draggingScrollDelta(int delta); void mustScrollTo(int scrollToTop, int scrollToBottom); void dialogMoved(int movedFrom, int movedTo); void searchMessages(); void searchResultChosen(); void cancelSearchInChat(); void completeHashtag(QString tag); void refreshHashtags(); protected: void visibleTopBottomUpdated( int visibleTop, int visibleBottom) override; void paintRegion(Painter &p, const QRegion ®ion, bool paintingOther) override; void mouseMoveEvent(QMouseEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void resizeEvent(QResizeEvent *e) override; void enterEventHook(QEvent *e) override; void leaveEventHook(QEvent *e) override; void contextMenuEvent(QContextMenuEvent *e) override; private: struct ImportantSwitch; using DialogsList = std::unique_ptr<Dialogs::IndexedList>; using FilteredDialogs = QVector<Dialogs::Row*>; using SearchResults = std::vector<std::unique_ptr<Dialogs::FakeRow>>; struct HashtagResult; using HashtagResults = std::vector<std::unique_ptr<HashtagResult>>; struct PeerSearchResult; using PeerSearchResults = std::vector<std::unique_ptr<PeerSearchResult>>; struct ChosenRow { Dialogs::Key key; Data::MessagePosition message; }; bool switchImportantChats(); bool chooseHashtag(); ChosenRow computeChosenRow() const; void userIsContactUpdated(not_null<UserData*> user); void mousePressReleased(Qt::MouseButton button); void clearIrrelevantState(); void updateSelected() { updateSelected(mapFromGlobal(QCursor::pos())); } void updateSelected(QPoint localPos); void loadPeerPhotos(); void setImportantSwitchPressed(bool pressed); void setPressed(Dialogs::Row *pressed); void setHashtagPressed(int pressed); void setFilteredPressed(int pressed); void setPeerSearchPressed(int pressed); void setSearchedPressed(int pressed); bool isPressed() const { return _importantSwitchPressed || _pressed || (_hashtagPressed >= 0) || (_filteredPressed >= 0) || (_peerSearchPressed >= 0) || (_searchedPressed >= 0); } bool isSelected() const { return _importantSwitchSelected || _selected || (_hashtagSelected >= 0) || (_filteredSelected >= 0) || (_peerSearchSelected >= 0) || (_searchedSelected >= 0); } void handlePeerNameChange( not_null<PeerData*> peer, const base::flat_set<QChar> &oldLetters); void applyDialog(const MTPDdialog &dialog); // void applyFeedDialog(const MTPDdialogFeed &dialog); // #feed void itemRemoved(not_null<const HistoryItem*> item); enum class UpdateRowSection { Default = (1 << 0), Filtered = (1 << 1), PeerSearch = (1 << 2), MessageSearch = (1 << 3), All = Default | Filtered | PeerSearch | MessageSearch, }; using UpdateRowSections = base::flags<UpdateRowSection>; friend inline constexpr auto is_flag_type(UpdateRowSection) { return true; }; void updateSearchResult(not_null<PeerData*> peer); void updateDialogRow( Dialogs::RowDescriptor row, QRect updateRect, UpdateRowSections sections = UpdateRowSection::All); int dialogsOffset() const; int filteredOffset() const; int peerSearchOffset() const; int searchedOffset() const; int searchInChatSkip() const; void paintDialog( Painter &p, not_null<Dialogs::Row*> row, int fullWidth, Dialogs::Key active, Dialogs::Key selected, bool onlyBackground, TimeMs ms); void paintPeerSearchResult( Painter &p, not_null<const PeerSearchResult*> result, int fullWidth, bool active, bool selected, bool onlyBackground, TimeMs ms) const; void paintSearchInChat( Painter &p, int fullWidth, bool onlyBackground, TimeMs ms) const; void paintSearchInPeer( Painter &p, not_null<PeerData*> peer, int top, int fullWidth, const Text &text) const; void paintSearchInSaved( Painter &p, int top, int fullWidth, const Text &text) const; void paintSearchInFeed( Painter &p, not_null<Data::Feed*> feed, int top, int fullWidth, const Text &text) const; template <typename PaintUserpic> void paintSearchInFilter( Painter &p, PaintUserpic paintUserpic, int top, int fullWidth, const style::icon *icon, const Text &text) const; void refreshSearchInChatLabel(); void clearSelection(); void clearSearchResults(bool clearPeerSearchResults = true); void updateSelectedRow(Dialogs::Key key = Dialogs::Key()); Dialogs::IndexedList *shownDialogs() const; void checkReorderPinnedStart(QPoint localPosition); int shownPinnedCount() const; int updateReorderIndexGetCount(); bool updateReorderPinned(QPoint localPosition); void finishReorderPinned(); void stopReorderPinned(); int countPinnedIndex(Dialogs::Row *ofRow); void savePinnedOrder(); void step_pinnedShifting(TimeMs ms, bool timer); not_null<Window::Controller*> _controller; DialogsList _dialogs; DialogsList _dialogsImportant; DialogsList _contactsNoDialogs; DialogsList _contacts; bool _mouseSelection = false; QPoint _mouseLastGlobalPosition; Qt::MouseButton _pressButton = Qt::LeftButton; std::unique_ptr<ImportantSwitch> _importantSwitch; bool _importantSwitchSelected = false; bool _importantSwitchPressed = false; Dialogs::Row *_selected = nullptr; Dialogs::Row *_pressed = nullptr; Dialogs::Row *_dragging = nullptr; int _draggingIndex = -1; int _aboveIndex = -1; QPoint _dragStart; struct PinnedRow { anim::value yadd; TimeMs animStartTime = 0; }; std::vector<PinnedRow> _pinnedRows; BasicAnimation _a_pinnedShifting; std::deque<Dialogs::Key> _pinnedOrder; // Remember the last currently dragged row top shift for updating area. int _aboveTopShift = -1; int _visibleTop = 0; int _visibleBottom = 0; QString _filter, _hashtagFilter; HashtagResults _hashtagResults; int _hashtagSelected = -1; int _hashtagPressed = -1; bool _hashtagDeleteSelected = false; bool _hashtagDeletePressed = false; FilteredDialogs _filterResults; base::flat_map< not_null<PeerData*>, std::unique_ptr<Dialogs::Row>> _filterResultsGlobal; int _filteredSelected = -1; int _filteredPressed = -1; bool _waitingForSearch = false; QString _peerSearchQuery; PeerSearchResults _peerSearchResults; int _peerSearchSelected = -1; int _peerSearchPressed = -1; SearchResults _searchResults; int _searchedCount = 0; int _searchedMigratedCount = 0; int _searchedSelected = -1; int _searchedPressed = -1; int _lastSearchDate = 0; PeerData *_lastSearchPeer = nullptr; MsgId _lastSearchId = 0; MsgId _lastSearchMigratedId = 0; State _state = State::Default; object_ptr<Ui::LinkButton> _addContactLnk; object_ptr<Ui::IconButton> _cancelSearchInChat; object_ptr<Ui::IconButton> _cancelSearchFromUser; Dialogs::Key _searchInChat; History *_searchInMigrated = nullptr; UserData *_searchFromUser = nullptr; Text _searchInChatText; Text _searchFromUserText; Dialogs::Key _menuKey; base::lambda<void()> _loadMoreCallback; base::unique_qptr<Ui::PopupMenu> _menu; }; ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/dialogs/dialogs_key.cpp����������������������������������������0000664�0000000�0000000�00000002026�13262451251�0024043�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "dialogs/dialogs_key.h" #include "data/data_feed.h" #include "history/history.h" namespace Dialogs { not_null<Entry*> Key::entry() const { if (const auto p = base::get_if<not_null<History*>>(&_value)) { return *p; } else if (const auto p = base::get_if<not_null<Data::Feed*>>(&_value)) { return *p; } Unexpected("Empty Dialogs::Key in Key::entry()."); } History *Key::history() const { if (const auto p = base::get_if<not_null<History*>>(&_value)) { return *p; } return nullptr; } Data::Feed *Key::feed() const { if (const auto p = base::get_if<not_null<Data::Feed*>>(&_value)) { return *p; } return nullptr; } PeerData *Key::peer() const { if (const auto p = base::get_if<not_null<History*>>(&_value)) { return (*p)->peer; } return nullptr; } } // namespace Dialogs ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/dialogs/dialogs_key.h������������������������������������������0000664�0000000�0000000�00000004641�13262451251�0023515�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "base/value_ordering.h" class History; namespace Data { class Feed; } // namespace Data namespace Dialogs { class Entry; class Key { public: Key() = default; Key(History *history) : _value(history) { } Key(not_null<History*> history) : _value(history) { } Key(Data::Feed *feed) : _value(feed) { } Key(not_null<Data::Feed*> feed) : _value(feed) { } explicit operator bool() const { return !!_value; } not_null<Entry*> entry() const; History *history() const; Data::Feed *feed() const; PeerData *peer() const; inline bool operator<(const Key &other) const { return _value < other._value; } inline bool operator>(const Key &other) const { return (other < *this); } inline bool operator<=(const Key &other) const { return !(other < *this); } inline bool operator>=(const Key &other) const { return !(*this < other); } inline bool operator==(const Key &other) const { return _value == other._value; } inline bool operator!=(const Key &other) const { return !(*this == other); } base::optional_variant< not_null<History*>, not_null<Data::Feed*>> raw() const { return _value; } // Not working :( //friend inline auto value_ordering_helper(const Key &key) { // return key.value; //} private: base::optional_variant<not_null<History*>, not_null<Data::Feed*>> _value; }; struct RowDescriptor { RowDescriptor() = default; RowDescriptor(Key key, FullMsgId fullId) : key(key), fullId(fullId) { } Key key; FullMsgId fullId; }; inline bool operator==(const RowDescriptor &a, const RowDescriptor &b) { return (a.key == b.key) && (a.fullId == b.fullId); } inline bool operator!=(const RowDescriptor &a, const RowDescriptor &b) { return !(a == b); } inline bool operator<(const RowDescriptor &a, const RowDescriptor &b) { if (a.key < b.key) { return true; } else if (a.key > b.key) { return false; } return a.fullId < b.fullId; } inline bool operator>(const RowDescriptor &a, const RowDescriptor &b) { return (b < a); } inline bool operator<=(const RowDescriptor &a, const RowDescriptor &b) { return !(b < a); } inline bool operator>=(const RowDescriptor &a, const RowDescriptor &b) { return !(a < b); } } // namespace Dialogs �����������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/dialogs/dialogs_layout.cpp�������������������������������������0000664�0000000�0000000�00000052506�13262451251�0024600�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "dialogs/dialogs_layout.h" #include "data/data_abstract_structure.h" #include "data/data_drafts.h" #include "dialogs/dialogs_list.h" #include "styles/style_dialogs.h" #include "storage/localstorage.h" #include "ui/empty_userpic.h" #include "ui/text_options.h" #include "lang/lang_keys.h" #include "history/history_item.h" #include "history/history.h" namespace Dialogs { namespace Layout { namespace { // Show all dates that are in the last 20 hours in time format. constexpr int kRecentlyInSeconds = 20 * 3600; void paintRowDate(Painter &p, QDateTime date, QRect &rectForName, bool active, bool selected) { auto now = QDateTime::currentDateTime(); auto lastTime = date; auto nowDate = now.date(); auto lastDate = lastTime.date(); QString dt; bool wasSameDay = (lastDate == nowDate); bool wasRecently = qAbs(lastTime.secsTo(now)) < kRecentlyInSeconds; if (wasSameDay || wasRecently) { dt = lastTime.toString(cTimeFormat()); } else if (lastDate.year() == nowDate.year() && lastDate.weekNumber() == nowDate.weekNumber()) { dt = langDayOfWeek(lastDate); } else { dt = lastDate.toString(qsl("d.MM.yy")); } int32 dtWidth = st::dialogsDateFont->width(dt); rectForName.setWidth(rectForName.width() - dtWidth - st::dialogsDateSkip); p.setFont(st::dialogsDateFont); p.setPen(active ? st::dialogsDateFgActive : (selected ? st::dialogsDateFgOver : st::dialogsDateFg)); p.drawText(rectForName.left() + rectForName.width() + st::dialogsDateSkip, rectForName.top() + st::msgNameFont->height - st::msgDateFont->descent, dt); } enum class Flag { Active = 0x01, Selected = 0x02, OnlyBackground = 0x04, SearchResult = 0x08, SavedMessages = 0x10, FeedSearchResult = 0x20, }; inline constexpr bool is_flag_type(Flag) { return true; } template <typename PaintItemCallback, typename PaintCounterCallback> void paintRow( Painter &p, not_null<const RippleRow*> row, not_null<Entry*> entry, Dialogs::Key chat, PeerData *from, HistoryItem *item, const Data::Draft *draft, QDateTime date, int fullWidth, base::flags<Flag> flags, TimeMs ms, PaintItemCallback &&paintItemCallback, PaintCounterCallback &&paintCounterCallback) { auto active = (flags & Flag::Active); auto selected = (flags & Flag::Selected); auto fullRect = QRect(0, 0, fullWidth, st::dialogsRowHeight); auto bg = active ? st::dialogsBgActive : (selected ? st::dialogsBgOver : st::dialogsBg); auto ripple = active ? st::dialogsRippleBgActive : st::dialogsRippleBg; p.fillRect(fullRect, bg); row->paintRipple(p, 0, 0, fullWidth, ms, &ripple->c); if (flags & Flag::OnlyBackground) { return; } if (flags & Flag::SavedMessages) { Ui::EmptyUserpic::PaintSavedMessages( p, st::dialogsPadding.x(), st::dialogsPadding.y(), fullWidth, st::dialogsPhotoSize); } else if (from) { from->paintUserpicLeft( p, st::dialogsPadding.x(), st::dialogsPadding.y(), fullWidth, st::dialogsPhotoSize); } else { entry->paintUserpicLeft( p, st::dialogsPadding.x(), st::dialogsPadding.y(), fullWidth, st::dialogsPhotoSize); } auto nameleft = st::dialogsPadding.x() + st::dialogsPhotoSize + st::dialogsPhotoPadding; if (fullWidth <= nameleft) { if (!draft && item && !item->isEmpty()) { paintCounterCallback(); } return; } const auto history = chat.history(); auto namewidth = fullWidth - nameleft - st::dialogsPadding.x(); auto rectForName = QRect( nameleft, st::dialogsPadding.y() + st::dialogsNameTop, namewidth, st::msgNameFont->height); if (from && !(flags & Flag::FeedSearchResult)) { if (const auto chatTypeIcon = ChatTypeIcon(from, active, selected)) { chatTypeIcon->paint(p, rectForName.topLeft(), fullWidth); rectForName.setLeft(rectForName.left() + st::dialogsChatTypeSkip); } } else if (const auto feed = chat.feed()) { if (const auto feedTypeIcon = FeedTypeIcon(feed, active, selected)) { feedTypeIcon->paint(p, rectForName.topLeft(), fullWidth); rectForName.setLeft(rectForName.left() + st::dialogsChatTypeSkip); } } auto texttop = st::dialogsPadding.y() + st::msgNameFont->height + st::dialogsSkip; if (draft) { paintRowDate(p, date, rectForName, active, selected); auto availableWidth = namewidth; if (entry->isPinnedDialog()) { auto &icon = (active ? st::dialogsPinnedIconActive : (selected ? st::dialogsPinnedIconOver : st::dialogsPinnedIcon)); icon.paint(p, fullWidth - st::dialogsPadding.x() - icon.width(), texttop, fullWidth); availableWidth -= icon.width() + st::dialogsUnreadPadding; } p.setFont(st::dialogsTextFont); auto &color = active ? st::dialogsTextFgServiceActive : (selected ? st::dialogsTextFgServiceOver : st::dialogsTextFgService); if (history && !history->paintSendAction(p, nameleft, texttop, availableWidth, fullWidth, color, ms)) { if (history->cloudDraftTextCache.isEmpty()) { auto draftWrapped = textcmdLink(1, lng_dialogs_text_from_wrapped(lt_from, lang(lng_from_draft))); auto draftText = lng_dialogs_text_with_from(lt_from_part, draftWrapped, lt_message, TextUtilities::Clean(draft->textWithTags.text)); history->cloudDraftTextCache.setText(st::dialogsTextStyle, draftText, Ui::DialogTextOptions()); } p.setPen(active ? st::dialogsTextFgActive : (selected ? st::dialogsTextFgOver : st::dialogsTextFg)); p.setTextPalette(active ? st::dialogsTextPaletteDraftActive : (selected ? st::dialogsTextPaletteDraftOver : st::dialogsTextPaletteDraft)); history->cloudDraftTextCache.drawElided(p, nameleft, texttop, availableWidth, 1); p.restoreTextPalette(); } } else if (!item) { auto availableWidth = namewidth; if (entry->isPinnedDialog()) { auto &icon = (active ? st::dialogsPinnedIconActive : (selected ? st::dialogsPinnedIconOver : st::dialogsPinnedIcon)); icon.paint(p, fullWidth - st::dialogsPadding.x() - icon.width(), texttop, fullWidth); availableWidth -= icon.width() + st::dialogsUnreadPadding; } auto &color = active ? st::dialogsTextFgServiceActive : (selected ? st::dialogsTextFgServiceOver : st::dialogsTextFgService); p.setFont(st::dialogsTextFont); if (history && !history->paintSendAction(p, nameleft, texttop, availableWidth, fullWidth, color, ms)) { // Empty history } } else if (!item->isEmpty()) { paintRowDate(p, date, rectForName, active, selected); paintItemCallback(nameleft, namewidth); } else if (entry->isPinnedDialog()) { auto availableWidth = namewidth; auto &icon = (active ? st::dialogsPinnedIconActive : (selected ? st::dialogsPinnedIconOver : st::dialogsPinnedIcon)); icon.paint(p, fullWidth - st::dialogsPadding.x() - icon.width(), texttop, fullWidth); availableWidth -= icon.width() + st::dialogsUnreadPadding; } auto sendStateIcon = [&]() -> const style::icon* { if (draft) { if (draft->saveRequestId) { return &(active ? st::dialogsSendingIconActive : (selected ? st::dialogsSendingIconOver : st::dialogsSendingIcon)); } } else if (item && !item->isEmpty() && item->needCheck()) { if (item->id > 0) { if (item->unread()) { return &(active ? st::dialogsSentIconActive : (selected ? st::dialogsSentIconOver : st::dialogsSentIcon)); } return &(active ? st::dialogsReceivedIconActive : (selected ? st::dialogsReceivedIconOver : st::dialogsReceivedIcon)); } return &(active ? st::dialogsSendingIconActive : (selected ? st::dialogsSendingIconOver : st::dialogsSendingIcon)); } return nullptr; }(); if (sendStateIcon && history) { rectForName.setWidth(rectForName.width() - st::dialogsSendStateSkip); sendStateIcon->paint(p, rectForName.topLeft() + QPoint(rectForName.width(), 0), fullWidth); } const auto nameFg = active ? st::dialogsNameFgActive : (selected ? st::dialogsNameFgOver : st::dialogsNameFg); p.setPen(nameFg); if (flags & Flag::SavedMessages) { p.setFont(st::msgNameFont); auto text = lang(lng_saved_messages); auto textWidth = st::msgNameFont->width(text); if (textWidth > rectForName.width()) { text = st::msgNameFont->elided(text, rectForName.width()); } p.drawTextLeft(rectForName.left(), rectForName.top(), fullWidth, text); } else if (from) { if (!(flags & Flag::SearchResult) && from->isVerified()) { auto icon = &(active ? st::dialogsVerifiedIconActive : (selected ? st::dialogsVerifiedIconOver : st::dialogsVerifiedIcon)); rectForName.setWidth(rectForName.width() - icon->width()); icon->paint(p, rectForName.topLeft() + QPoint(qMin(from->dialogName().maxWidth(), rectForName.width()), 0), fullWidth); } from->dialogName().drawElided(p, rectForName.left(), rectForName.top(), rectForName.width()); } else { p.setFont(st::msgNameFont); auto text = entry->chatsListName(); // TODO feed name with emoji auto textWidth = st::msgNameFont->width(text); if (textWidth > rectForName.width()) { text = st::msgNameFont->elided(text, rectForName.width()); } p.drawTextLeft(rectForName.left(), rectForName.top(), fullWidth, text); } } struct UnreadBadgeSizeData { QImage circle; QPixmap left[6], right[6]; }; class UnreadBadgeStyleData : public Data::AbstractStructure { public: UnreadBadgeSizeData sizes[UnreadBadgeSizesCount]; style::color bg[6] = { st::dialogsUnreadBg, st::dialogsUnreadBgOver, st::dialogsUnreadBgActive, st::dialogsUnreadBgMuted, st::dialogsUnreadBgMutedOver, st::dialogsUnreadBgMutedActive }; }; Data::GlobalStructurePointer<UnreadBadgeStyleData> unreadBadgeStyle; void createCircleMask(UnreadBadgeSizeData *data, int size) { if (!data->circle.isNull()) return; data->circle = style::createCircleMask(size); } QImage colorizeCircleHalf(UnreadBadgeSizeData *data, int size, int half, int xoffset, style::color color) { auto result = style::colorizeImage(data->circle, color, QRect(xoffset, 0, half, size)); result.setDevicePixelRatio(cRetinaFactor()); return result; } } // namepsace const style::icon *ChatTypeIcon( not_null<PeerData*> peer, bool active, bool selected) { if (peer->isChat() || peer->isMegagroup()) { return &(active ? st::dialogsChatIconActive : (selected ? st::dialogsChatIconOver : st::dialogsChatIcon)); } else if (peer->isChannel()) { return &(active ? st::dialogsChannelIconActive : (selected ? st::dialogsChannelIconOver : st::dialogsChannelIcon)); } return nullptr; } const style::icon *FeedTypeIcon( not_null<Data::Feed*> feed, bool active, bool selected) { return &(active ? st::dialogsFeedIconActive : (selected ? st::dialogsFeedIconOver : st::dialogsFeedIcon)); } void paintUnreadBadge(Painter &p, const QRect &rect, const UnreadBadgeStyle &st) { Assert(rect.height() == st.size); int index = (st.muted ? 0x03 : 0x00) + (st.active ? 0x02 : (st.selected ? 0x01 : 0x00)); int size = st.size, sizehalf = size / 2; unreadBadgeStyle.createIfNull(); auto badgeData = unreadBadgeStyle->sizes; if (st.sizeId > 0) { Assert(st.sizeId < UnreadBadgeSizesCount); badgeData = &unreadBadgeStyle->sizes[st.sizeId]; } auto bg = unreadBadgeStyle->bg[index]; if (badgeData->left[index].isNull()) { int imgsize = size * cIntRetinaFactor(), imgsizehalf = sizehalf * cIntRetinaFactor(); createCircleMask(badgeData, size); badgeData->left[index] = App::pixmapFromImageInPlace(colorizeCircleHalf(badgeData, imgsize, imgsizehalf, 0, bg)); badgeData->right[index] = App::pixmapFromImageInPlace(colorizeCircleHalf(badgeData, imgsize, imgsizehalf, imgsize - imgsizehalf, bg)); } int bar = rect.width() - 2 * sizehalf; p.drawPixmap(rect.x(), rect.y(), badgeData->left[index]); if (bar) { p.fillRect(rect.x() + sizehalf, rect.y(), bar, rect.height(), bg); } p.drawPixmap(rect.x() + sizehalf + bar, rect.y(), badgeData->right[index]); } UnreadBadgeStyle::UnreadBadgeStyle() : align(style::al_right) , active(false) , selected(false) , muted(false) , size(st::dialogsUnreadHeight) , padding(st::dialogsUnreadPadding) , sizeId(UnreadBadgeInDialogs) , font(st::dialogsUnreadFont) { } void paintUnreadCount(Painter &p, const QString &text, int x, int y, const UnreadBadgeStyle &st, int *outUnreadWidth) { int unreadWidth = st.font->width(text); int unreadRectWidth = unreadWidth + 2 * st.padding; int unreadRectHeight = st.size; accumulate_max(unreadRectWidth, unreadRectHeight); int unreadRectLeft = x; if ((st.align & Qt::AlignHorizontal_Mask) & style::al_center) { unreadRectLeft = (x - unreadRectWidth) / 2; } else if ((st.align & Qt::AlignHorizontal_Mask) & style::al_right) { unreadRectLeft = x - unreadRectWidth; } int unreadRectTop = y; if (outUnreadWidth) { *outUnreadWidth = unreadRectWidth; } paintUnreadBadge(p, QRect(unreadRectLeft, unreadRectTop, unreadRectWidth, unreadRectHeight), st); auto textTop = st.textTop ? st.textTop : (unreadRectHeight - st.font->height) / 2; p.setFont(st.font); p.setPen(st.active ? st::dialogsUnreadFgActive : (st.selected ? st::dialogsUnreadFgOver : st::dialogsUnreadFg)); p.drawText(unreadRectLeft + (unreadRectWidth - unreadWidth) / 2, unreadRectTop + textTop + st.font->ascent, text); } void RowPainter::paint( Painter &p, not_null<const Row*> row, int fullWidth, bool active, bool selected, bool onlyBackground, TimeMs ms) { const auto entry = row->entry(); const auto history = row->history(); const auto peer = history ? history->peer.get() : nullptr; const auto unreadCount = entry->chatListUnreadCount(); const auto unreadMuted = entry->chatListMutedBadge(); const auto item = entry->chatsListItem(); const auto cloudDraft = [&]() -> const Data::Draft*{ if (history && (!item || !unreadCount)) { // Draw item, if there are unread messages. if (const auto draft = history->cloudDraft()) { if (!Data::draftIsNull(draft)) { return draft; } } } return nullptr; }(); const auto displayDate = [item, cloudDraft] { if (item) { if (cloudDraft) { return (item->date() > cloudDraft->date) ? ItemDateTime(item) : ParseDateTime(cloudDraft->date); } return ItemDateTime(item); } return cloudDraft ? ParseDateTime(cloudDraft->date) : QDateTime(); }(); const auto from = history ? (history->peer->migrateTo() ? history->peer->migrateTo() : history->peer.get()) : nullptr; const auto flags = (active ? Flag::Active : Flag(0)) | (selected ? Flag::Selected : Flag(0)) | (onlyBackground ? Flag::OnlyBackground : Flag(0)) | (peer && peer->isSelf() ? Flag::SavedMessages : Flag(0)); const auto paintItemCallback = [&](int nameleft, int namewidth) { auto availableWidth = namewidth; auto texttop = st::dialogsPadding.y() + st::msgNameFont->height + st::dialogsSkip; auto hadOneBadge = false; const auto displayMentionBadge = history ? history->hasUnreadMentions() : false; const auto displayUnreadCounter = [&] { if (displayMentionBadge && unreadCount == 1 && item && item->isMediaUnread() && item->mentionsMe()) { return false; } return (unreadCount > 0); }(); const auto displayPinnedIcon = !displayUnreadCounter && !displayMentionBadge && entry->isPinnedDialog(); if (displayUnreadCounter) { auto counter = QString::number(unreadCount); auto unreadRight = fullWidth - st::dialogsPadding.x(); auto unreadTop = texttop + st::dialogsTextFont->ascent - st::dialogsUnreadFont->ascent - (st::dialogsUnreadHeight - st::dialogsUnreadFont->height) / 2; auto unreadWidth = 0; UnreadBadgeStyle st; st.active = active; st.muted = unreadMuted; paintUnreadCount(p, counter, unreadRight, unreadTop, st, &unreadWidth); availableWidth -= unreadWidth + st.padding; hadOneBadge = true; } else if (displayPinnedIcon) { auto &icon = (active ? st::dialogsPinnedIconActive : (selected ? st::dialogsPinnedIconOver : st::dialogsPinnedIcon)); icon.paint(p, fullWidth - st::dialogsPadding.x() - icon.width(), texttop, fullWidth); availableWidth -= icon.width() + st::dialogsUnreadPadding; hadOneBadge = true; } if (displayMentionBadge) { auto counter = qsl("@"); auto unreadRight = fullWidth - st::dialogsPadding.x() - (namewidth - availableWidth); auto unreadTop = texttop + st::dialogsTextFont->ascent - st::dialogsUnreadFont->ascent - (st::dialogsUnreadHeight - st::dialogsUnreadFont->height) / 2; auto unreadWidth = 0; UnreadBadgeStyle st; st.active = active; st.muted = false; st.padding = 0; st.textTop = 0; paintUnreadCount(p, counter, unreadRight, unreadTop, st, &unreadWidth); availableWidth -= unreadWidth + st.padding + (hadOneBadge ? st::dialogsUnreadPadding : 0); } const auto &color = active ? st::dialogsTextFgServiceActive : (selected ? st::dialogsTextFgServiceOver : st::dialogsTextFgService); const auto actionWasPainted = history ? history->paintSendAction( p, nameleft, texttop, availableWidth, fullWidth, color, ms) : false; if (!actionWasPainted) { auto itemRect = QRect( nameleft, texttop, availableWidth, st::dialogsTextFont->height); item->drawInDialog( p, itemRect, active, selected, HistoryItem::DrawInDialog::Normal, entry->textCachedFor, entry->lastItemTextCache); } }; const auto paintCounterCallback = [&] { if (unreadCount) { auto counter = QString::number(unreadCount); if (counter.size() > 4) { counter = qsl("..") + counter.mid(counter.size() - 3); } auto unreadRight = st::dialogsPadding.x() + st::dialogsPhotoSize; auto unreadTop = st::dialogsPadding.y() + st::dialogsPhotoSize - st::dialogsUnreadHeight; auto unreadWidth = 0; UnreadBadgeStyle st; st.active = active; st.muted = unreadMuted; paintUnreadCount(p, counter, unreadRight, unreadTop, st, &unreadWidth); } }; paintRow( p, row, entry, row->key(), from, item, cloudDraft, displayDate, fullWidth, flags, ms, paintItemCallback, paintCounterCallback); } void RowPainter::paint( Painter &p, not_null<const FakeRow*> row, int fullWidth, bool active, bool selected, bool onlyBackground, TimeMs ms) { auto item = row->item(); auto history = item->history(); auto cloudDraft = nullptr; const auto from = [&] { if (const auto searchChat = row->searchInChat()) { if (const auto peer = searchChat.peer()) { if (peer->isSelf()) { return item->senderOriginal().get(); } else if (!peer->isChannel() || peer->isMegagroup()) { return item->from().get(); } } } return history->peer->migrateTo() ? history->peer->migrateTo() : history->peer.get(); }(); const auto drawInDialogWay = [&] { if (const auto searchChat = row->searchInChat()) { if (const auto peer = searchChat.peer()) { if (!peer->isChannel() || peer->isMegagroup()) { return HistoryItem::DrawInDialog::WithoutSender; } } } return HistoryItem::DrawInDialog::Normal; }(); const auto paintItemCallback = [&](int nameleft, int namewidth) { auto lastWidth = namewidth; auto texttop = st::dialogsPadding.y() + st::msgNameFont->height + st::dialogsSkip; item->drawInDialog( p, QRect(nameleft, texttop, lastWidth, st::dialogsTextFont->height), active, selected, drawInDialogWay, row->_cacheFor, row->_cache); }; const auto paintCounterCallback = [] {}; const auto showSavedMessages = history->peer->isSelf() && !row->searchInChat(); const auto flags = (active ? Flag::Active : Flag(0)) | (selected ? Flag::Selected : Flag(0)) | (onlyBackground ? Flag::OnlyBackground : Flag(0)) | Flag::SearchResult | (showSavedMessages ? Flag::SavedMessages : Flag(0)) | (row->searchInChat().feed() ? Flag::FeedSearchResult : Flag(0)); paintRow( p, row, history, history, from, item, cloudDraft, ItemDateTime(item), fullWidth, flags, ms, paintItemCallback, paintCounterCallback); } QRect RowPainter::sendActionAnimationRect(int animationWidth, int animationHeight, int fullWidth, bool textUpdated) { auto nameleft = st::dialogsPadding.x() + st::dialogsPhotoSize + st::dialogsPhotoPadding; auto namewidth = fullWidth - nameleft - st::dialogsPadding.x(); auto texttop = st::dialogsPadding.y() + st::msgNameFont->height + st::dialogsSkip; return QRect(nameleft, texttop, textUpdated ? namewidth : animationWidth, animationHeight); } void paintImportantSwitch(Painter &p, Mode current, int fullWidth, bool selected, bool onlyBackground) { p.fillRect(0, 0, fullWidth, st::dialogsImportantBarHeight, selected ? st::dialogsBgOver : st::dialogsBg); if (onlyBackground) { return; } p.setFont(st::semiboldFont); p.setPen(st::dialogsNameFg); int unreadTop = (st::dialogsImportantBarHeight - st::dialogsUnreadHeight) / 2; bool mutedHidden = (current == Dialogs::Mode::Important); QString text = lang(mutedHidden ? lng_dialogs_show_all_chats : lng_dialogs_hide_muted_chats); int textBaseline = unreadTop + (st::dialogsUnreadHeight - st::dialogsUnreadFont->height) / 2 + st::dialogsUnreadFont->ascent; p.drawText(st::dialogsPadding.x(), textBaseline, text); if (mutedHidden) { if (int32 unread = App::histories().unreadMutedCount()) { int unreadRight = fullWidth - st::dialogsPadding.x(); UnreadBadgeStyle st; st.muted = true; paintUnreadCount(p, QString::number(unread), unreadRight, unreadTop, st, nullptr); } } } void clearUnreadBadgesCache() { if (unreadBadgeStyle) { for (auto &data : unreadBadgeStyle->sizes) { for (auto &left : data.left) { left = QPixmap(); } for (auto &right : data.right) { right = QPixmap(); } } } } } // namespace Layout } // namespace Dialogs ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/dialogs/dialogs_layout.h���������������������������������������0000664�0000000�0000000�00000003244�13262451251�0024240�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace Data { class Feed; } // namespace Data namespace Dialogs { class Row; class FakeRow; namespace Layout { const style::icon *ChatTypeIcon( not_null<PeerData*> peer, bool active, bool selected); const style::icon *FeedTypeIcon( not_null<Data::Feed*> feed, bool active, bool selected); class RowPainter { public: static void paint( Painter &p, not_null<const Row*> row, int fullWidth, bool active, bool selected, bool onlyBackground, TimeMs ms); static void paint( Painter &p, not_null<const FakeRow*> row, int fullWidth, bool active, bool selected, bool onlyBackground, TimeMs ms); static QRect sendActionAnimationRect( int animationWidth, int animationHeight, int fullWidth, bool textUpdated); }; void paintImportantSwitch( Painter &p, Mode current, int fullWidth, bool selected, bool onlyBackground); enum UnreadBadgeSize { UnreadBadgeInDialogs = 0, UnreadBadgeInHistoryToDown, UnreadBadgeInStickersPanel, UnreadBadgeInStickersBox, UnreadBadgeSizesCount }; struct UnreadBadgeStyle { UnreadBadgeStyle(); style::align align; bool active; bool selected; bool muted; int textTop = 0; int size; int padding; UnreadBadgeSize sizeId; style::font font; }; void paintUnreadCount( Painter &p, const QString &text, int x, int y, const UnreadBadgeStyle &st, int *outUnreadWidth = nullptr); void clearUnreadBadgesCache(); } // namespace Layout } // namespace Dialogs ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/dialogs/dialogs_list.cpp���������������������������������������0000664�0000000�0000000�00000012001�13262451251�0024220�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "dialogs/dialogs_list.h" #include "dialogs/dialogs_entry.h" #include "dialogs/dialogs_layout.h" #include "styles/style_dialogs.h" #include "mainwidget.h" namespace Dialogs { List::List(SortMode sortMode) : _last(std::make_unique<Row>(nullptr)) , _begin(_last.get()) , _end(_last.get()) , _sortMode(sortMode) , _current(_last.get()) { } void List::adjustCurrent(int32 y, int32 h) const { if (isEmpty()) return; int32 pos = (y > 0) ? (y / h) : 0; while (_current->_pos > pos && _current != _begin) { _current = _current->_prev; } while (_current->_pos + 1 <= pos && _current->_next != _end) { _current = _current->_next; } } Row *List::addToEnd(Key key) { const auto result = new Row(key, _end->_prev, _end, _end->_pos); _end->_pos++; if (_begin == _end) { _begin = _current = result; } else { _end->_prev->_next = result; } _rowByKey.emplace(key, result); ++_count; _end->_prev = result; if (_sortMode == SortMode::Date) { adjustByPos(result); } return result; } bool List::insertBefore(Row *row, Row *before) { if (row == before) { return false; } if (_current == row) { _current = row->_prev; } const auto updateTill = row->_prev; remove(row); // insert row row->_next = before; // update row row->_prev = before->_prev; row->_next->_prev = row; // update row->next if (row->_prev) { // update row->prev row->_prev->_next = row; } else { _begin = row; } // update pos for (auto n = row; n != updateTill; n = n->_next) { n->_next->_pos++; row->_pos--; } return true; } bool List::insertAfter(Row *row, Row *after) { if (row == after) { return false; } if (_current == row) { _current = row->_next; } const auto updateFrom = row->_next; remove(row); // insert row row->_prev = after; // update row row->_next = after->_next; row->_prev->_next = row; // update row->prev row->_next->_prev = row; // update row->next // update pos for (auto n = updateFrom; n != row; n = n->_next) { n->_pos--; row->_pos++; } return true; } Row *List::adjustByName(Key key) { if (_sortMode != SortMode::Name) return nullptr; const auto i = _rowByKey.find(key); if (i == _rowByKey.cend()) return nullptr; const auto row = i->second; const auto name = key.entry()->chatsListName(); auto change = row; while (change->_prev && change->_prev->entry()->chatsListName().compare(name, Qt::CaseInsensitive) < 0) { change = change->_prev; } if (!insertBefore(row, change)) { while (change->_next != _end && change->_next->entry()->chatsListName().compare(name, Qt::CaseInsensitive) < 0) { change = change->_next; } insertAfter(row, change); } return row; } Row *List::addByName(Key key) { if (_sortMode != SortMode::Name) { return nullptr; } const auto row = addToEnd(key); auto change = row; const auto name = key.entry()->chatsListName(); while (change->_prev && change->_prev->entry()->chatsListName().compare(name, Qt::CaseInsensitive) > 0) { change = change->_prev; } if (!insertBefore(row, change)) { while (change->_next != _end && change->_next->entry()->chatsListName().compare(name, Qt::CaseInsensitive) < 0) { change = change->_next; } insertAfter(row, change); } return row; } void List::adjustByPos(Row *row) { if (_sortMode != SortMode::Date || !_begin) return; Row *change = row; if (change != _begin && _begin->sortKey() < row->sortKey()) { change = _begin; } else { while (change->_prev && change->_prev->sortKey() < row->sortKey()) { change = change->_prev; } } if (!insertBefore(row, change)) { if (change->_next != _end && _end->_prev->sortKey() > row->sortKey()) { change = _end->_prev; } else { while (change->_next != _end && change->_next->sortKey() > row->sortKey()) { change = change->_next; } } insertAfter(row, change); } } bool List::moveToTop(Key key) { auto i = _rowByKey.find(key); if (i == _rowByKey.cend()) { return false; } insertBefore(i->second, _begin); return true; } bool List::del(Key key, Row *replacedBy) { auto i = _rowByKey.find(key); if (i == _rowByKey.cend()) { return false; } const auto row = i->second; if (App::main()) { emit App::main()->dialogRowReplaced(row, replacedBy); } if (row == _current) { _current = row->_next; } for (auto change = row->_next; change != _end; change = change->_next) { --change->_pos; } --_end->_pos; remove(row); delete row; --_count; _rowByKey.erase(i); return true; } void List::remove(Row *row) { row->_next->_prev = row->_prev; // update row->next if (row->_prev) { // update row->prev row->_prev->_next = row->_next; } else { _begin = row->_next; } } void List::clear() { while (_begin != _end) { _current = _begin; _begin = _begin->_next; delete _current; } _current = _begin; _rowByKey.clear(); _count = 0; } List::~List() { clear(); } } // namespace Dialogs �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/dialogs/dialogs_list.h�����������������������������������������0000664�0000000�0000000�00000007244�13262451251�0023702�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "dialogs/dialogs_row.h" class PeerData; namespace Dialogs { enum class SortMode; class List { public: List(SortMode sortMode); List(const List &other) = delete; List &operator=(const List &other) = delete; int size() const { return _count; } bool isEmpty() const { return size() == 0; } bool contains(Key key) const { return _rowByKey.find(key) != _rowByKey.end(); } Row *getRow(Key key) const { const auto i = _rowByKey.find(key); return (i == _rowByKey.end()) ? nullptr : i->second.get(); } Row *rowAtY(int32 y, int32 h) const { auto i = cfind(y, h); if (i == cend() || (*i)->pos() != ((y > 0) ? (y / h) : 0)) { return nullptr; } return *i; } Row *addToEnd(Key key); Row *adjustByName(Key key); Row *addByName(Key key); bool moveToTop(Key key); void adjustByPos(Row *row); bool del(Key key, Row *replacedBy = nullptr); void remove(Row *row); void clear(); class const_iterator { public: using value_type = Row*; using pointer = Row**; using reference = Row*&; explicit const_iterator(Row *p) : _p(p) { } inline Row* operator*() const { return _p; } inline Row* const* operator->() const { return &_p; } inline bool operator==(const const_iterator &other) const { return _p == other._p; } inline bool operator!=(const const_iterator &other) const { return !(*this == other); } inline const_iterator &operator++() { _p = next(_p); return *this; } inline const_iterator operator++(int) { const_iterator result(*this); ++(*this); return result; } inline const_iterator &operator--() { _p = prev(_p); return *this; } inline const_iterator operator--(int) { const_iterator result(*this); --(*this); return result; } inline const_iterator operator+(int j) const { const_iterator result = *this; return result += j; } inline const_iterator operator-(int j) const { const_iterator result = *this; return result -= j; } inline const_iterator &operator+=(int j) { if (j < 0) return (*this -= (-j)); while (j--) ++*this; return *this; } inline const_iterator &operator-=(int j) { if (j < 0) return (*this += (-j)); while (j--) --*this; return *this; } private: Row *_p; friend class List; }; friend class const_iterator; using iterator = const_iterator; const_iterator cbegin() const { return const_iterator(_begin); } const_iterator cend() const { return const_iterator(_end); } const_iterator begin() const { return cbegin(); } const_iterator end() const { return cend(); } iterator begin() { return iterator(_begin); } iterator end() { return iterator(_end); } const_iterator cfind(Row *value) const { return value ? const_iterator(value) : cend(); } const_iterator find(Row *value) const { return cfind(value); } iterator find(Row *value) { return value ? iterator(value) : end(); } const_iterator cfind(int y, int h) const { adjustCurrent(y, h); return iterator(_current); } const_iterator find(int y, int h) const { return cfind(y, h); } iterator find(int y, int h) { adjustCurrent(y, h); return iterator(_current); } ~List(); private: void adjustCurrent(int y, int h) const; bool insertBefore(Row *row, Row *before); bool insertAfter(Row *row, Row *after); static Row *next(Row *row) { return row->_next; } static Row *prev(Row *row) { return row->_prev; } std::unique_ptr<Row> _last; Row *_begin; Row *_end; SortMode _sortMode; int _count = 0; std::map<Key, not_null<Row*>> _rowByKey; mutable Row *_current; // cache }; } // namespace Dialogs ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/dialogs/dialogs_row.cpp����������������������������������������0000664�0000000�0000000�00000002537�13262451251�0024071�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "dialogs/dialogs_row.h" #include "styles/style_dialogs.h" #include "ui/effects/ripple_animation.h" #include "dialogs/dialogs_entry.h" #include "mainwidget.h" namespace Dialogs { RippleRow::RippleRow() = default; RippleRow::~RippleRow() = default; void RippleRow::addRipple(QPoint origin, QSize size, base::lambda<void()> updateCallback) { if (!_ripple) { auto mask = Ui::RippleAnimation::rectMask(size); _ripple = std::make_unique<Ui::RippleAnimation>(st::dialogsRipple, std::move(mask), std::move(updateCallback)); } _ripple->add(origin); } void RippleRow::stopLastRipple() { if (_ripple) { _ripple->lastStop(); } } void RippleRow::paintRipple(Painter &p, int x, int y, int outerWidth, TimeMs ms, const QColor *colorOverride) const { if (_ripple) { _ripple->paint(p, x, y, outerWidth, ms, colorOverride); if (_ripple->empty()) { _ripple.reset(); } } } uint64 Row::sortKey() const { return _id.entry()->sortKeyInChatList(); } FakeRow::FakeRow(Key searchInChat, not_null<HistoryItem*> item) : _searchInChat(searchInChat) , _item(item) , _cache(st::dialogsTextWidthMin) { } } // namespace Dialogs�����������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/dialogs/dialogs_row.h������������������������������������������0000664�0000000�0000000�00000003550�13262451251�0023532�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/text/text.h" #include "dialogs/dialogs_key.h" class History; class HistoryItem; namespace Ui { class RippleAnimation; } // namespace Ui namespace Dialogs { namespace Layout { class RowPainter; } // namespace Layout class RippleRow { public: RippleRow(); ~RippleRow(); void addRipple(QPoint origin, QSize size, base::lambda<void()> updateCallback); void stopLastRipple(); void paintRipple(Painter &p, int x, int y, int outerWidth, TimeMs ms, const QColor *colorOverride = nullptr) const; private: mutable std::unique_ptr<Ui::RippleAnimation> _ripple; }; class List; class Row : public RippleRow { public: explicit Row(std::nullptr_t) { } Row(Key key, Row *prev, Row *next, int pos) : _id(key) , _prev(prev) , _next(next) , _pos(pos) { } Key key() const { return _id; } History *history() const { return _id.history(); } Data::Feed *feed() const { return _id.feed(); } not_null<Entry*> entry() const { return _id.entry(); } int pos() const { return _pos; } uint64 sortKey() const; // for any attached data, for example View in contacts list void *attached = nullptr; private: friend class List; Key _id; Row *_prev = nullptr; Row *_next = nullptr; int _pos = 0; }; class FakeRow : public RippleRow { public: FakeRow(Key searchInChat, not_null<HistoryItem*> item); Key searchInChat() const { return _searchInChat; } not_null<HistoryItem*> item() const { return _item; } private: friend class Layout::RowPainter; Key _searchInChat; not_null<HistoryItem*> _item; mutable const HistoryItem *_cacheFor = nullptr; mutable Text _cache; }; } // namespace Dialogs ��������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/dialogs/dialogs_search_from_controllers.cpp��������������������0000664�0000000�0000000�00000010621�13262451251�0030171�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "dialogs/dialogs_search_from_controllers.h" #include "lang/lang_keys.h" #include "data/data_peer_values.h" #include "observer_peer.h" #include "auth_session.h" #include "apiwrap.h" namespace Dialogs { void ShowSearchFromBox( not_null<Window::Navigation*> navigation, not_null<PeerData*> peer, base::lambda<void(not_null<UserData*>)> callback, base::lambda<void()> closedCallback) { auto createController = [ navigation, peer, callback = std::move(callback) ]() -> std::unique_ptr<PeerListController> { if (peer) { if (auto chat = peer->asChat()) { return std::make_unique<Dialogs::ChatSearchFromController>( navigation, chat, std::move(callback)); } else if (auto group = peer->asMegagroup()) { return std::make_unique<Dialogs::ChannelSearchFromController>( navigation, group, std::move(callback)); } } return nullptr; }; if (auto controller = createController()) { auto subscription = std::make_shared<base::Subscription>(); auto box = Ui::show(Box<PeerListBox>(std::move(controller), [subscription](not_null<PeerListBox*> box) { box->addButton(langFactory(lng_cancel), [box, subscription] { box->closeBox(); }); }), LayerOption::KeepOther); *subscription = box->boxClosing.add_subscription(std::move(closedCallback)); } } ChatSearchFromController::ChatSearchFromController( not_null<Window::Navigation*> navigation, not_null<ChatData*> chat, base::lambda<void(not_null<UserData*>)> callback) : PeerListController() , _chat(chat) , _callback(std::move(callback)) { } void ChatSearchFromController::prepare() { setSearchNoResultsText(lang(lng_blocked_list_not_found)); delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled); delegate()->peerListSetTitle(langFactory(lng_search_messages_from)); rebuildRows(); subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(Notify::PeerUpdate::Flag::MembersChanged, [this](const Notify::PeerUpdate &update) { if (update.peer == _chat) { rebuildRows(); } })); } void ChatSearchFromController::rowClicked(not_null<PeerListRow*> row) { Expects(row->peer()->isUser()); _callback(row->peer()->asUser()); } void ChatSearchFromController::rebuildRows() { auto ms = getms(); auto wasEmpty = !delegate()->peerListFullRowsCount(); auto now = unixtime(); const auto byOnline = [&](not_null<UserData*> user) { return Data::SortByOnlineValue(user, now); }; auto ordered = QMultiMap<TimeId, not_null<UserData*>>(); if (_chat->noParticipantInfo()) { Auth().api().requestFullPeer(_chat); } else if (!_chat->participants.empty()) { for (const auto [user, version] : _chat->participants) { ordered.insertMulti(byOnline(user), user); } } for_const (auto user, _chat->lastAuthors) { if (user->isInaccessible()) continue; appendRow(user); if (!ordered.isEmpty()) { ordered.remove(byOnline(user), user); } } if (!ordered.isEmpty()) { for (auto i = ordered.cend(), b = ordered.cbegin(); i != b;) { appendRow(*(--i)); } } checkForEmptyRows(); delegate()->peerListRefreshRows(); } void ChatSearchFromController::checkForEmptyRows() { if (delegate()->peerListFullRowsCount()) { setDescriptionText(QString()); } else { setDescriptionText(lang(lng_contacts_loading)); } } void ChatSearchFromController::appendRow(not_null<UserData*> user) { if (!delegate()->peerListFindRow(user->id)) { delegate()->peerListAppendRow(std::make_unique<PeerListRow>(user)); } } ChannelSearchFromController::ChannelSearchFromController( not_null<Window::Navigation*> navigation, not_null<ChannelData*> channel, base::lambda<void(not_null<UserData*>)> callback) : ParticipantsBoxController( navigation, channel, ParticipantsBoxController::Role::Members) , _callback(std::move(callback)) { } void ChannelSearchFromController::prepare() { ParticipantsBoxController::prepare(); delegate()->peerListSetTitle(langFactory(lng_search_messages_from)); } void ChannelSearchFromController::rowClicked(not_null<PeerListRow*> row) { Expects(row->peer()->isUser()); _callback(row->peer()->asUser()); } std::unique_ptr<PeerListRow> ChannelSearchFromController::createRow(not_null<UserData*> user) const { return std::make_unique<PeerListRow>(user); } } // namespace Dialogs ���������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/dialogs/dialogs_search_from_controllers.h����������������������0000664�0000000�0000000�00000003050�13262451251�0027634�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "boxes/peer_list_box.h" #include "profile/profile_channel_controllers.h" namespace Dialogs { void ShowSearchFromBox( not_null<Window::Navigation*> navigation, not_null<PeerData*> peer, base::lambda<void(not_null<UserData*>)> callback, base::lambda<void()> closedCallback); class ChatSearchFromController : public PeerListController, protected base::Subscriber { public: ChatSearchFromController( not_null<Window::Navigation*> navigation, not_null<ChatData*> chat, base::lambda<void(not_null<UserData*>)> callback); void prepare() override; void rowClicked(not_null<PeerListRow*> row) override; private: void rebuildRows(); void checkForEmptyRows(); void appendRow(not_null<UserData*> user); not_null<ChatData*> _chat; base::lambda<void(not_null<UserData*>)> _callback; }; class ChannelSearchFromController : public Profile::ParticipantsBoxController { public: ChannelSearchFromController( not_null<Window::Navigation*> navigation, not_null<ChannelData*> channel, base::lambda<void(not_null<UserData*>)> callback); void prepare() override; void rowClicked(not_null<PeerListRow*> row) override; protected: std::unique_ptr<PeerListRow> createRow(not_null<UserData*> user) const override; private: base::lambda<void(not_null<UserData*>)> _callback; }; } // namespace Dialogs ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/dialogs/dialogs_widget.cpp�������������������������������������0000664�0000000�0000000�00000123227�13262451251�0024545�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "dialogs/dialogs_widget.h" #include "dialogs/dialogs_inner_widget.h" #include "dialogs/dialogs_search_from_controllers.h" #include "dialogs/dialogs_key.h" #include "dialogs/dialogs_entry.h" #include "history/history.h" #include "history/feed/history_feed_section.h" #include "ui/widgets/buttons.h" #include "ui/widgets/input_fields.h" #include "ui/wrap/fade_wrap.h" #include "lang/lang_keys.h" #include "application.h" #include "mainwindow.h" #include "mainwidget.h" #include "autoupdater.h" #include "auth_session.h" #include "apiwrap.h" #include "messenger.h" #include "boxes/peer_list_box.h" #include "window/window_controller.h" #include "window/window_slide_animation.h" #include "profile/profile_channel_controllers.h" #include "storage/storage_media_prepare.h" #include "data/data_session.h" #include "styles/style_dialogs.h" #include "styles/style_window.h" namespace { QString SwitchToChooseFromQuery() { return qsl("from:"); } } // namespace class DialogsWidget::UpdateButton : public Ui::RippleButton { public: UpdateButton(QWidget *parent); protected: void paintEvent(QPaintEvent *e) override; void onStateChanged(State was, StateChangeSource source) override; private: QString _text; const style::FlatButton &_st; }; DialogsWidget::UpdateButton::UpdateButton(QWidget *parent) : RippleButton(parent, st::dialogsUpdateButton.ripple) , _text(lang(lng_update_telegram).toUpper()) , _st(st::dialogsUpdateButton) { resize(st::columnMinimalWidthLeft, _st.height); } void DialogsWidget::UpdateButton::onStateChanged(State was, StateChangeSource source) { RippleButton::onStateChanged(was, source); update(); } void DialogsWidget::UpdateButton::paintEvent(QPaintEvent *e) { QPainter p(this); QRect r(0, height() - _st.height, width(), _st.height); p.fillRect(r, isOver() ? _st.overBgColor : _st.bgColor); paintRipple(p, 0, 0, getms()); p.setFont(isOver() ? _st.overFont : _st.font); p.setRenderHint(QPainter::TextAntialiasing); p.setPen(isOver() ? _st.overColor : _st.color); if (width() >= st::columnMinimalWidthLeft) { r.setTop(_st.textTop); p.drawText(r, _text, style::al_top); } else { (isOver() ? st::dialogsInstallUpdateOver : st::dialogsInstallUpdate).paintInCenter(p, r); } } DialogsWidget::DialogsWidget(QWidget *parent, not_null<Window::Controller*> controller) : Window::AbstractSectionWidget(parent, controller) , _mainMenuToggle(this, st::dialogsMenuToggle) , _filter(this, st::dialogsFilter, langFactory(lng_dlg_filter)) , _chooseFromUser( this, object_ptr<Ui::IconButton>(this, st::dialogsSearchFrom)) , _jumpToDate( this, object_ptr<Ui::IconButton>(this, st::dialogsCalendar)) , _cancelSearch(this, st::dialogsCancelSearch) , _lockUnlock(this, st::dialogsLock) , _scroll(this, st::dialogsScroll) { _inner = _scroll->setOwnedWidget(object_ptr<DialogsInner>(this, controller, parent)); connect(_inner, SIGNAL(draggingScrollDelta(int)), this, SLOT(onDraggingScrollDelta(int))); connect(_inner, SIGNAL(mustScrollTo(int,int)), _scroll, SLOT(scrollToY(int,int))); connect(_inner, SIGNAL(dialogMoved(int,int)), this, SLOT(onDialogMoved(int,int))); connect(_inner, SIGNAL(searchMessages()), this, SLOT(onNeedSearchMessages())); connect(_inner, SIGNAL(searchResultChosen()), this, SLOT(onCancel())); connect(_inner, SIGNAL(completeHashtag(QString)), this, SLOT(onCompleteHashtag(QString))); connect(_inner, SIGNAL(refreshHashtags()), this, SLOT(onFilterCursorMoved())); connect(_inner, SIGNAL(cancelSearchInChat()), this, SLOT(onCancelSearchInChat())); subscribe(_inner->searchFromUserChanged, [this](UserData *user) { setSearchInChat(_searchInChat, user); onFilterUpdate(true); }); connect(_scroll, SIGNAL(geometryChanged()), _inner, SLOT(onParentGeometryChanged())); connect(_scroll, SIGNAL(scrolled()), this, SLOT(onListScroll())); connect(_filter, SIGNAL(cancelled()), this, SLOT(onCancel())); connect(_filter, SIGNAL(changed()), this, SLOT(onFilterUpdate())); connect(_filter, SIGNAL(cursorPositionChanged(int,int)), this, SLOT(onFilterCursorMoved(int,int))); #ifndef TDESKTOP_DISABLE_AUTOUPDATE Sandbox::connect(SIGNAL(updateLatest()), this, SLOT(onCheckUpdateStatus())); Sandbox::connect(SIGNAL(updateFailed()), this, SLOT(onCheckUpdateStatus())); Sandbox::connect(SIGNAL(updateReady()), this, SLOT(onCheckUpdateStatus())); onCheckUpdateStatus(); #endif // !TDESKTOP_DISABLE_AUTOUPDATE subscribe(Adaptive::Changed(), [this] { updateForwardBar(); }); _cancelSearch->setClickedCallback([this] { onCancelSearch(); }); _jumpToDate->entity()->setClickedCallback([this] { showJumpToDate(); }); _chooseFromUser->entity()->setClickedCallback([this] { showSearchFrom(); }); _lockUnlock->setVisible(Global::LocalPasscode()); subscribe(Global::RefLocalPasscodeChanged(), [this] { updateLockUnlockVisibility(); }); _lockUnlock->setClickedCallback([this] { _lockUnlock->setIconOverride(&st::dialogsUnlockIcon, &st::dialogsUnlockIconOver); Messenger::Instance().setupPasscode(); _lockUnlock->setIconOverride(nullptr); }); _mainMenuToggle->setClickedCallback([this] { showMainMenu(); }); _chooseByDragTimer.setSingleShot(true); connect(&_chooseByDragTimer, SIGNAL(timeout()), this, SLOT(onChooseByDrag())); setAcceptDrops(true); _searchTimer.setSingleShot(true); connect(&_searchTimer, SIGNAL(timeout()), this, SLOT(onSearchMessages())); _inner->setLoadMoreCallback([this] { using State = DialogsInner::State; const auto state = _inner->state(); if (state == State::Filtered && (!_inner->waitingForSearch() || (_searchInMigrated && _searchFull && !_searchFullMigrated))) { onSearchMore(); } else { loadDialogs(); } }); _filter->setFocusPolicy(Qt::StrongFocus); _filter->customUpDown(true); updateJumpToDateVisibility(true); updateSearchFromVisibility(true); } #ifndef TDESKTOP_DISABLE_AUTOUPDATE void DialogsWidget::onCheckUpdateStatus() { if (Sandbox::updatingState() == Application::UpdatingReady) { if (_updateTelegram) return; _updateTelegram.create(this); _updateTelegram->show(); _updateTelegram->setClickedCallback([] { checkReadyUpdate(); App::restart(); }); } else { if (!_updateTelegram) return; _updateTelegram.destroy(); } updateControlsGeometry(); } #endif // TDESKTOP_DISABLE_AUTOUPDATE void DialogsWidget::activate() { _filter->setFocus(); _inner->activate(); } void DialogsWidget::createDialog(Dialogs::Key key) { const auto creating = !key.entry()->inChatList(Dialogs::Mode::All); _inner->createDialog(key); const auto history = key.history(); if (creating && history && history->peer->migrateFrom()) { if (const auto migrated = App::historyLoaded( history->peer->migrateFrom())) { if (migrated->inChatList(Dialogs::Mode::All)) { removeDialog(migrated); } } } } void DialogsWidget::repaintDialogRow( Dialogs::Mode list, not_null<Dialogs::Row*> row) { _inner->repaintDialogRow(list, row); } void DialogsWidget::repaintDialogRow( not_null<History*> history, MsgId messageId) { _inner->repaintDialogRow(history, messageId); } void DialogsWidget::dialogsToUp() { if (_filter->getLastText().trimmed().isEmpty()) { _scroll->scrollToY(0); } } void DialogsWidget::startWidthAnimation() { if (!_widthAnimationCache.isNull()) { return; } auto scrollGeometry = _scroll->geometry(); auto grabGeometry = QRect( scrollGeometry.x(), scrollGeometry.y(), st::columnMinimalWidthLeft, scrollGeometry.height()); _scroll->setGeometry(grabGeometry); Ui::SendPendingMoveResizeEvents(_scroll); auto image = QImage( grabGeometry.size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); image.setDevicePixelRatio(cRetinaFactor()); image.fill(Qt::transparent); _scroll->render(&image, QPoint(0, 0), QRect(QPoint(0, 0), grabGeometry.size()), QWidget::DrawChildren | QWidget::IgnoreMask); _widthAnimationCache = App::pixmapFromImageInPlace(std::move(image)); _scroll->setGeometry(scrollGeometry); _scroll->hide(); } void DialogsWidget::stopWidthAnimation() { _widthAnimationCache = QPixmap(); if (!_a_show.animating()) { _scroll->show(); } update(); } void DialogsWidget::showFast() { show(); updateForwardBar(); } void DialogsWidget::showAnimated(Window::SlideDirection direction, const Window::SectionSlideParams ¶ms) { _showDirection = direction; _a_show.finish(); _cacheUnder = params.oldContentCache; show(); updateForwardBar(); _cacheOver = App::main()->grabForShowAnimation(params); _scroll->hide(); _mainMenuToggle->hide(); if (_forwardCancel) _forwardCancel->hide(); _filter->hide(); _cancelSearch->hide(anim::type::instant); _jumpToDate->hide(anim::type::instant); _chooseFromUser->hide(anim::type::instant); _lockUnlock->hide(); int delta = st::slideShift; if (_showDirection == Window::SlideDirection::FromLeft) { std::swap(_cacheUnder, _cacheOver); } _a_show.start([this] { animationCallback(); }, 0., 1., st::slideDuration, Window::SlideAnimation::transition()); } bool DialogsWidget::wheelEventFromFloatPlayer(QEvent *e) { return _scroll->viewportEvent(e); } QRect DialogsWidget::rectForFloatPlayer() const { return mapToGlobal(_scroll->geometry()); } void DialogsWidget::animationCallback() { update(); if (!_a_show.animating()) { _cacheUnder = _cacheOver = QPixmap(); _scroll->show(); _mainMenuToggle->show(); if (_forwardCancel) _forwardCancel->show(); _filter->show(); updateLockUnlockVisibility(); updateJumpToDateVisibility(true); updateSearchFromVisibility(true); onFilterUpdate(); if (App::wnd()) App::wnd()->setInnerFocus(); } } void DialogsWidget::onCancel() { if (!onCancelSearch() || (!_searchInChat && !App::main()->selectingPeer())) { emit cancelled(); } } void DialogsWidget::notify_historyMuteUpdated(History *history) { _inner->notify_historyMuteUpdated(history); } void DialogsWidget::dialogsReceived( const MTPmessages_Dialogs &dialogs, mtpRequestId requestId) { if (_dialogsRequestId != requestId) return; const auto [dialogsList, messagesList] = [&] { const auto process = [&](const auto &data) { App::feedUsers(data.vusers); App::feedChats(data.vchats); return std::make_tuple(&data.vdialogs.v, &data.vmessages.v); }; switch (dialogs.type()) { case mtpc_messages_dialogs: _dialogsFull = true; return process(dialogs.c_messages_dialogs()); case mtpc_messages_dialogsSlice: return process(dialogs.c_messages_dialogsSlice()); } Unexpected("Type in DialogsWidget::dialogsReceived"); }(); updateDialogsOffset(*dialogsList, *messagesList); applyReceivedDialogs(*dialogsList, *messagesList); _dialogsRequestId = 0; loadDialogs(); Auth().data().moreChatsLoaded().notify(); if (_dialogsFull && _pinnedDialogsReceived) { Auth().data().allChatsLoaded().set(true); } Auth().api().requestContacts(); } void DialogsWidget::updateDialogsOffset( const QVector<MTPDialog> &dialogs, const QVector<MTPMessage> &messages) { auto lastDate = TimeId(0); auto lastPeer = PeerId(0); auto lastMsgId = MsgId(0); const auto fillFromDialog = [&](const auto &dialog) { const auto peer = peerFromMTP(dialog.vpeer); const auto msgId = dialog.vtop_message.v; if (!peer || !msgId) { return; } if (!lastPeer) { lastPeer = peer; } if (!lastMsgId) { lastMsgId = msgId; } for (auto j = messages.size(); j != 0;) { const auto &message = messages[--j]; if (idFromMessage(message) == msgId && peerFromMessage(message) == peer) { if (const auto date = dateFromMessage(message)) { lastDate = date; } return; } } }; for (auto i = dialogs.size(); i != 0;) { const auto &dialog = dialogs[--i]; switch (dialog.type()) { case mtpc_dialog: fillFromDialog(dialog.c_dialog()); break; // case mtpc_dialogFeed: fillFromDialog(dialog.c_dialogFeed()); break; // #feed default: Unexpected("Type in DialogsWidget::updateDialogsOffset"); } if (lastDate) { break; } } if (lastDate) { _dialogsOffsetDate = lastDate; _dialogsOffsetId = lastMsgId; _dialogsOffsetPeer = App::peer(lastPeer); } else { _dialogsFull = true; } } void DialogsWidget::pinnedDialogsReceived( const MTPmessages_PeerDialogs &result, mtpRequestId requestId) { Expects(result.type() == mtpc_messages_peerDialogs); if (_pinnedDialogsRequestId != requestId) return; auto &data = result.c_messages_peerDialogs(); App::feedUsers(data.vusers); App::feedChats(data.vchats); Auth().data().applyPinnedDialogs(data.vdialogs.v); applyReceivedDialogs(data.vdialogs.v, data.vmessages.v); _pinnedDialogsRequestId = 0; _pinnedDialogsReceived = true; Auth().data().moreChatsLoaded().notify(); if (_dialogsFull && _pinnedDialogsReceived) { Auth().data().allChatsLoaded().set(true); } } void DialogsWidget::applyReceivedDialogs( const QVector<MTPDialog> &dialogs, const QVector<MTPMessage> &messages) { App::feedMsgs(messages, NewMessageLast); _inner->dialogsReceived(dialogs); onListScroll(); } bool DialogsWidget::dialogsFailed(const RPCError &error, mtpRequestId requestId) { if (MTP::isDefaultHandledError(error)) return false; LOG(("RPC Error: %1 %2: %3").arg(error.code()).arg(error.type()).arg(error.description())); if (_dialogsRequestId == requestId) { _dialogsRequestId = 0; } else if (_pinnedDialogsRequestId == requestId) { _pinnedDialogsRequestId = 0; } return true; } void DialogsWidget::onDraggingScrollDelta(int delta) { _draggingScrollDelta = _scroll ? delta : 0; if (_draggingScrollDelta) { if (!_draggingScrollTimer) { _draggingScrollTimer.create(this); _draggingScrollTimer->setSingleShot(false); connect(_draggingScrollTimer, SIGNAL(timeout()), this, SLOT(onDraggingScrollTimer())); } _draggingScrollTimer->start(15); } else { _draggingScrollTimer.destroy(); } } void DialogsWidget::onDraggingScrollTimer() { auto delta = (_draggingScrollDelta > 0) ? qMin(_draggingScrollDelta * 3 / 20 + 1, int32(MaxScrollSpeed)) : qMax(_draggingScrollDelta * 3 / 20 - 1, -int32(MaxScrollSpeed)); _scroll->scrollToY(_scroll->scrollTop() + delta); } bool DialogsWidget::onSearchMessages(bool searchCache) { auto result = false; auto q = _filter->getLastText().trimmed(); if (q.isEmpty() && !_searchFromUser) { MTP::cancel(base::take(_searchRequest)); MTP::cancel(base::take(_peerSearchRequest)); return true; } if (searchCache) { const auto i = _searchCache.constFind(q); if (i != _searchCache.cend()) { _searchQuery = q; _searchQueryFrom = _searchFromUser; _searchFull = _searchFullMigrated = false; MTP::cancel(base::take(_searchRequest)); searchReceived( _searchInChat ? DialogsSearchPeerFromStart : DialogsSearchFromStart, i.value(), 0); result = true; } } else if (_searchQuery != q || _searchQueryFrom != _searchFromUser) { _searchQuery = q; _searchQueryFrom = _searchFromUser; _searchFull = _searchFullMigrated = false; MTP::cancel(base::take(_searchRequest)); if (const auto peer = _searchInChat.peer()) { const auto flags = _searchQueryFrom ? MTP_flags(MTPmessages_Search::Flag::f_from_id) : MTP_flags(0); _searchRequest = MTP::send( MTPmessages_Search( flags, peer->input, MTP_string(_searchQuery), _searchQueryFrom ? _searchQueryFrom->inputUser : MTP_inputUserEmpty(), MTP_inputMessagesFilterEmpty(), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(SearchPerPage), MTP_int(0), MTP_int(0), MTP_int(0)), rpcDone(&DialogsWidget::searchReceived, DialogsSearchPeerFromStart), rpcFail(&DialogsWidget::searchFailed, DialogsSearchPeerFromStart)); } else if (const auto feed = _searchInChat.feed()) { //_searchRequest = MTP::send( // #feed // MTPchannels_SearchFeed( // MTP_int(feed->id()), // MTP_string(_searchQuery), // MTP_int(0), // MTP_inputPeerEmpty(), // MTP_int(0), // MTP_int(SearchPerPage)), // rpcDone(&DialogsWidget::searchReceived, DialogsSearchFromStart), // rpcFail(&DialogsWidget::searchFailed, DialogsSearchFromStart)); } else { _searchRequest = MTP::send( MTPmessages_SearchGlobal( MTP_string(_searchQuery), MTP_int(0), MTP_inputPeerEmpty(), MTP_int(0), MTP_int(SearchPerPage)), rpcDone(&DialogsWidget::searchReceived, DialogsSearchFromStart), rpcFail(&DialogsWidget::searchFailed, DialogsSearchFromStart)); } _searchQueries.insert(_searchRequest, _searchQuery); } if (searchForPeersRequired(q)) { if (searchCache) { auto i = _peerSearchCache.constFind(q); if (i != _peerSearchCache.cend()) { _peerSearchQuery = q; _peerSearchRequest = 0; peerSearchReceived(i.value(), 0); result = true; } } else if (_peerSearchQuery != q) { _peerSearchQuery = q; _peerSearchFull = false; _peerSearchRequest = MTP::send( MTPcontacts_Search( MTP_string(_peerSearchQuery), MTP_int(SearchPeopleLimit)), rpcDone(&DialogsWidget::peerSearchReceived), rpcFail(&DialogsWidget::peopleFailed)); _peerSearchQueries.insert(_peerSearchRequest, _peerSearchQuery); } } else { _peerSearchQuery = q; _peerSearchFull = true; peerSearchReceived( MTP_contacts_found( MTP_vector<MTPPeer>(0), MTP_vector<MTPPeer>(0), MTP_vector<MTPChat>(0), MTP_vector<MTPUser>(0)), 0); } return result; } bool DialogsWidget::searchForPeersRequired(const QString &query) const { if (_searchInChat || query.isEmpty()) { return false; } return (query[0] != '#'); } void DialogsWidget::onNeedSearchMessages() { if (!onSearchMessages(true)) { _searchTimer.start(AutoSearchTimeout); } } void DialogsWidget::onChooseByDrag() { _inner->chooseRow(); } void DialogsWidget::showMainMenu() { App::wnd()->showMainMenu(); } void DialogsWidget::searchMessages( const QString &query, Dialogs::Key inChat) { auto inChatChanged = [&] { if (inChat == _searchInChat) { return false; } else if (const auto inPeer = inChat.peer()) { if (inPeer->migrateTo() == _searchInChat.peer()) { return false; } } return true; }(); if ((_filter->getLastText() != query) || inChatChanged) { if (inChat) { onCancelSearch(); setSearchInChat(inChat); } _filter->setText(query); _filter->updatePlaceholder(); onFilterUpdate(true); _searchTimer.stop(); onSearchMessages(); _inner->saveRecentHashtags(query); } } void DialogsWidget::onSearchMore() { if (!_searchRequest) { if (!_searchFull) { auto offsetDate = _inner->lastSearchDate(); auto offsetPeer = _inner->lastSearchPeer(); auto offsetId = _inner->lastSearchId(); if (const auto peer = _searchInChat.peer()) { auto flags = _searchQueryFrom ? MTP_flags(MTPmessages_Search::Flag::f_from_id) : MTP_flags(0); _searchRequest = MTP::send( MTPmessages_Search( flags, peer->input, MTP_string(_searchQuery), _searchQueryFrom ? _searchQueryFrom->inputUser : MTP_inputUserEmpty(), MTP_inputMessagesFilterEmpty(), MTP_int(0), MTP_int(0), MTP_int(offsetId), MTP_int(0), MTP_int(SearchPerPage), MTP_int(0), MTP_int(0), MTP_int(0)), rpcDone(&DialogsWidget::searchReceived, offsetId ? DialogsSearchPeerFromOffset : DialogsSearchPeerFromStart), rpcFail(&DialogsWidget::searchFailed, offsetId ? DialogsSearchPeerFromOffset : DialogsSearchPeerFromStart)); } else if (const auto feed = _searchInChat.feed()) { //_searchRequest = MTP::send( // #feed // MTPchannels_SearchFeed( // MTP_int(feed->id()), // MTP_string(_searchQuery), // MTP_int(offsetDate), // offsetPeer // ? offsetPeer->input // : MTP_inputPeerEmpty(), // MTP_int(offsetId), // MTP_int(SearchPerPage)), // rpcDone(&DialogsWidget::searchReceived, offsetId ? DialogsSearchFromOffset : DialogsSearchFromStart), // rpcFail(&DialogsWidget::searchFailed, offsetId ? DialogsSearchFromOffset : DialogsSearchFromStart)); } else { _searchRequest = MTP::send( MTPmessages_SearchGlobal( MTP_string(_searchQuery), MTP_int(offsetDate), offsetPeer ? offsetPeer->input : MTP_inputPeerEmpty(), MTP_int(offsetId), MTP_int(SearchPerPage)), rpcDone(&DialogsWidget::searchReceived, offsetId ? DialogsSearchFromOffset : DialogsSearchFromStart), rpcFail(&DialogsWidget::searchFailed, offsetId ? DialogsSearchFromOffset : DialogsSearchFromStart)); } if (!offsetId) { _searchQueries.insert(_searchRequest, _searchQuery); } } else if (_searchInMigrated && !_searchFullMigrated) { auto offsetMigratedId = _inner->lastSearchMigratedId(); auto flags = _searchQueryFrom ? MTP_flags(MTPmessages_Search::Flag::f_from_id) : MTP_flags(0); _searchRequest = MTP::send( MTPmessages_Search( flags, _searchInMigrated->peer->input, MTP_string(_searchQuery), _searchQueryFrom ? _searchQueryFrom->inputUser : MTP_inputUserEmpty(), MTP_inputMessagesFilterEmpty(), MTP_int(0), MTP_int(0), MTP_int(offsetMigratedId), MTP_int(0), MTP_int(SearchPerPage), MTP_int(0), MTP_int(0), MTP_int(0)), rpcDone(&DialogsWidget::searchReceived, offsetMigratedId ? DialogsSearchMigratedFromOffset : DialogsSearchMigratedFromStart), rpcFail(&DialogsWidget::searchFailed, offsetMigratedId ? DialogsSearchMigratedFromOffset : DialogsSearchMigratedFromStart)); } } } void DialogsWidget::loadDialogs() { if (_dialogsRequestId) return; if (_dialogsFull) { _inner->addAllSavedPeers(); return; } const auto firstLoad = !_dialogsOffsetDate; const auto loadCount = firstLoad ? DialogsFirstLoad : DialogsPerPage; const auto flags = MTPmessages_GetDialogs::Flag::f_exclude_pinned; const auto feedId = 0; _dialogsRequestId = MTP::send( MTPmessages_GetDialogs( MTP_flags(flags), //MTP_int(feedId), // #feed MTP_int(_dialogsOffsetDate), MTP_int(_dialogsOffsetId), _dialogsOffsetPeer ? _dialogsOffsetPeer->input : MTP_inputPeerEmpty(), MTP_int(loadCount)), rpcDone(&DialogsWidget::dialogsReceived), rpcFail(&DialogsWidget::dialogsFailed)); if (!_pinnedDialogsReceived) { loadPinnedDialogs(); } } void DialogsWidget::loadPinnedDialogs() { if (_pinnedDialogsRequestId) return; _pinnedDialogsReceived = false; _pinnedDialogsRequestId = MTP::send(MTPmessages_GetPinnedDialogs(), rpcDone(&DialogsWidget::pinnedDialogsReceived), rpcFail(&DialogsWidget::dialogsFailed)); } void DialogsWidget::searchReceived( DialogsSearchRequestType type, const MTPmessages_Messages &result, mtpRequestId requestId) { using State = DialogsInner::State; const auto state = _inner->state(); if (state == State::Filtered) { if (type == DialogsSearchFromStart || type == DialogsSearchPeerFromStart) { auto i = _searchQueries.find(requestId); if (i != _searchQueries.cend()) { _searchCache[i.value()] = result; _searchQueries.erase(i); } } } if (_searchRequest == requestId) { switch (result.type()) { case mtpc_messages_messages: { auto &d = result.c_messages_messages(); if (_searchRequest != 0) { // Don't apply cached data! App::feedUsers(d.vusers); App::feedChats(d.vchats); } auto &msgs = d.vmessages.v; if (!_inner->searchReceived(msgs, type, msgs.size())) { if (type == DialogsSearchMigratedFromStart || type == DialogsSearchMigratedFromOffset) { _searchFullMigrated = true; } else { _searchFull = true; } } } break; case mtpc_messages_messagesSlice: { auto &d = result.c_messages_messagesSlice(); if (_searchRequest != 0) { // Don't apply cached data! App::feedUsers(d.vusers); App::feedChats(d.vchats); } auto &msgs = d.vmessages.v; if (!_inner->searchReceived(msgs, type, d.vcount.v)) { if (type == DialogsSearchMigratedFromStart || type == DialogsSearchMigratedFromOffset) { _searchFullMigrated = true; } else { _searchFull = true; } } } break; case mtpc_messages_channelMessages: { auto &d = result.c_messages_channelMessages(); if (const auto peer = _searchInChat.peer()) { if (const auto channel = peer->asChannel()) { channel->ptsReceived(d.vpts.v); } else { LOG(("API Error: " "received messages.channelMessages when no channel " "was passed! (DialogsWidget::searchReceived)")); } } else { LOG(("API Error: " "received messages.channelMessages when no channel " "was passed! (DialogsWidget::searchReceived)")); } if (_searchRequest != 0) { // Don't apply cached data! App::feedUsers(d.vusers); App::feedChats(d.vchats); } auto &msgs = d.vmessages.v; if (!_inner->searchReceived(msgs, type, d.vcount.v)) { if (type == DialogsSearchMigratedFromStart || type == DialogsSearchMigratedFromOffset) { _searchFullMigrated = true; } else { _searchFull = true; } } } break; case mtpc_messages_messagesNotModified: { LOG(("API Error: received messages.messagesNotModified! (DialogsWidget::searchReceived)")); if (type == DialogsSearchMigratedFromStart || type == DialogsSearchMigratedFromOffset) { _searchFullMigrated = true; } else { _searchFull = true; } } break; } _searchRequest = 0; onListScroll(); update(); } } void DialogsWidget::peerSearchReceived( const MTPcontacts_Found &result, mtpRequestId requestId) { using State = DialogsInner::State; const auto state = _inner->state(); auto q = _peerSearchQuery; if (state == State::Filtered) { auto i = _peerSearchQueries.find(requestId); if (i != _peerSearchQueries.cend()) { q = i.value(); _peerSearchCache[q] = result; _peerSearchQueries.erase(i); } } if (_peerSearchRequest == requestId) { switch (result.type()) { case mtpc_contacts_found: { auto &d = result.c_contacts_found(); App::feedUsers(d.vusers); App::feedChats(d.vchats); _inner->peerSearchReceived(q, d.vmy_results.v, d.vresults.v); } break; } _peerSearchRequest = 0; onListScroll(); } } bool DialogsWidget::searchFailed( DialogsSearchRequestType type, const RPCError &error, mtpRequestId requestId) { if (MTP::isDefaultHandledError(error)) return false; if (_searchRequest == requestId) { _searchRequest = 0; if (type == DialogsSearchMigratedFromStart || type == DialogsSearchMigratedFromOffset) { _searchFullMigrated = true; } else { _searchFull = true; } } return true; } bool DialogsWidget::peopleFailed(const RPCError &error, mtpRequestId req) { if (MTP::isDefaultHandledError(error)) return false; if (_peerSearchRequest == req) { _peerSearchRequest = 0; _peerSearchFull = true; } return true; } void DialogsWidget::dragEnterEvent(QDragEnterEvent *e) { using namespace Storage; if (App::main()->selectingPeer()) return; const auto data = e->mimeData(); _dragInScroll = false; _dragForward = Adaptive::OneColumn() ? false : data->hasFormat(qsl("application/x-td-forward")); if (_dragForward) { e->setDropAction(Qt::CopyAction); e->accept(); updateDragInScroll(_scroll->geometry().contains(e->pos())); } else if (ComputeMimeDataState(data) != MimeDataState::None) { e->setDropAction(Qt::CopyAction); e->accept(); } _chooseByDragTimer.stop(); } void DialogsWidget::dragMoveEvent(QDragMoveEvent *e) { if (_scroll->geometry().contains(e->pos())) { if (_dragForward) { updateDragInScroll(true); } else { _chooseByDragTimer.start(ChoosePeerByDragTimeout); } if (_inner->updateFromParentDrag(mapToGlobal(e->pos()))) { e->setDropAction(Qt::CopyAction); } else { e->setDropAction(Qt::IgnoreAction); } } else { if (_dragForward) updateDragInScroll(false); _inner->dragLeft(); e->setDropAction(Qt::IgnoreAction); } e->accept(); } void DialogsWidget::dragLeaveEvent(QDragLeaveEvent *e) { if (_dragForward) { updateDragInScroll(false); } else { _chooseByDragTimer.stop(); } _inner->dragLeft(); e->accept(); } void DialogsWidget::updateDragInScroll(bool inScroll) { if (_dragInScroll != inScroll) { _dragInScroll = inScroll; if (_dragInScroll) { App::main()->showForwardLayer({}); } else { App::main()->dialogsCancelled(); } } } void DialogsWidget::dropEvent(QDropEvent *e) { _chooseByDragTimer.stop(); if (_scroll->geometry().contains(e->pos())) { if (auto peer = _inner->updateFromParentDrag(mapToGlobal(e->pos()))) { e->acceptProposedAction(); App::main()->onFilesOrForwardDrop(peer->id, e->mimeData()); controller()->window()->activateWindow(); } } } void DialogsWidget::onListScroll() { auto scrollTop = _scroll->scrollTop(); _inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height()); } void DialogsWidget::onFilterUpdate(bool force) { if (_a_show.animating() && !force) return; auto filterText = _filter->getLastText(); _inner->onFilterUpdate(filterText, force); if (filterText.isEmpty()) { clearSearchCache(); } _cancelSearch->toggle(!filterText.isEmpty(), anim::type::normal); updateJumpToDateVisibility(); if (filterText.isEmpty()) { _peerSearchCache.clear(); _peerSearchQueries.clear(); _peerSearchQuery = QString(); } if (_chooseFromUser->toggled() || _searchFromUser) { auto switchToChooseFrom = SwitchToChooseFromQuery(); if (_lastFilterText != switchToChooseFrom && switchToChooseFrom.startsWith(_lastFilterText) && filterText == switchToChooseFrom) { showSearchFrom(); } } _lastFilterText = filterText; } void DialogsWidget::searchInChat(Dialogs::Key chat) { onCancelSearch(); setSearchInChat(chat); onFilterUpdate(true); } void DialogsWidget::setSearchInChat(Dialogs::Key chat, UserData *from) { _searchInMigrated = nullptr; if (const auto peer = chat.peer()) { if (const auto migrateTo = peer->migrateTo()) { return setSearchInChat(App::history(migrateTo), from); } else if (const auto migrateFrom = peer->migrateFrom()) { _searchInMigrated = App::history(migrateFrom); } } const auto searchInPeerUpdated = (_searchInChat != chat); if (searchInPeerUpdated) { _searchInChat = chat; from = nullptr; controller()->searchInChat = _searchInChat; updateJumpToDateVisibility(); } else if (!_searchInChat) { from = nullptr; } if (_searchFromUser != from || searchInPeerUpdated) { _searchFromUser = from; updateSearchFromVisibility(); clearSearchCache(); } _inner->searchInChat(_searchInChat, _searchFromUser); if (_searchFromUser && _lastFilterText == SwitchToChooseFromQuery()) { onCancelSearch(); } _filter->setFocus(); } void DialogsWidget::clearSearchCache() { _searchCache.clear(); _searchQueries.clear(); _searchQuery = QString(); _searchQueryFrom = nullptr; MTP::cancel(base::take(_searchRequest)); } void DialogsWidget::showJumpToDate() { if (_searchInChat) { this->controller()->showJumpToDate(_searchInChat, QDate()); } } void DialogsWidget::showSearchFrom() { if (const auto peer = _searchInChat.peer()) { const auto chat = _searchInChat; Dialogs::ShowSearchFromBox( controller(), peer, base::lambda_guarded(this, [=]( not_null<UserData*> user) { Ui::hideLayer(); setSearchInChat(chat, user); onFilterUpdate(true); }), base::lambda_guarded(this, [this] { _filter->setFocus(); })); } } void DialogsWidget::onFilterCursorMoved(int from, int to) { if (to < 0) to = _filter->cursorPosition(); QString t = _filter->getLastText(); QStringRef r; for (int start = to; start > 0;) { --start; if (t.size() <= start) break; if (t.at(start) == '#') { r = t.midRef(start, to - start); break; } if (!t.at(start).isLetterOrNumber() && t.at(start) != '_') break; } _inner->onHashtagFilterUpdate(r); } void DialogsWidget::onCompleteHashtag(QString tag) { QString t = _filter->getLastText(), r; int cur = _filter->cursorPosition(); for (int start = cur; start > 0;) { --start; if (t.size() <= start) break; if (t.at(start) == '#') { if (cur == start + 1 || t.midRef(start + 1, cur - start - 1) == tag.midRef(0, cur - start - 1)) { for (; cur < t.size() && cur - start - 1 < tag.size(); ++cur) { if (t.at(cur) != tag.at(cur - start - 1)) break; } if (cur - start - 1 == tag.size() && cur < t.size() && t.at(cur) == ' ') ++cur; r = t.mid(0, start + 1) + tag + ' ' + t.mid(cur); _filter->setText(r); _filter->setCursorPosition(start + 1 + tag.size() + 1); onFilterUpdate(true); return; } break; } if (!t.at(start).isLetterOrNumber() && t.at(start) != '_') break; } _filter->setText(t.mid(0, cur) + '#' + tag + ' ' + t.mid(cur)); _filter->setCursorPosition(cur + 1 + tag.size() + 1); onFilterUpdate(true); } void DialogsWidget::resizeEvent(QResizeEvent *e) { updateControlsGeometry(); } void DialogsWidget::updateLockUnlockVisibility() { if (!_a_show.animating()) { _lockUnlock->setVisible(Global::LocalPasscode()); } updateControlsGeometry(); } void DialogsWidget::updateJumpToDateVisibility(bool fast) { if (_a_show.animating()) return; _jumpToDate->toggle( (_searchInChat && _filter->getLastText().isEmpty()), fast ? anim::type::instant : anim::type::normal); } void DialogsWidget::updateSearchFromVisibility(bool fast) { auto visible = [&] { if (const auto peer = _searchInChat.peer()) { if (peer->isChat() || peer->isMegagroup()) { return !_searchFromUser; } } return false; }(); auto changed = (visible == !_chooseFromUser->toggled()); _chooseFromUser->toggle( visible, fast ? anim::type::instant : anim::type::normal); if (changed) { auto margins = st::dialogsFilter.textMrg; if (visible) { margins.setRight(margins.right() + _chooseFromUser->width()); } _filter->setTextMrg(margins); } } void DialogsWidget::updateControlsGeometry() { auto filterAreaTop = 0; if (_forwardCancel) { _forwardCancel->moveToLeft(0, filterAreaTop); filterAreaTop += st::dialogsForwardHeight; } auto smallLayoutWidth = (st::dialogsPadding.x() + st::dialogsPhotoSize + st::dialogsPadding.x()); auto smallLayoutRatio = (width() < st::columnMinimalWidthLeft) ? (st::columnMinimalWidthLeft - width()) / float64(st::columnMinimalWidthLeft - smallLayoutWidth) : 0.; auto filterLeft = st::dialogsFilterPadding.x() + _mainMenuToggle->width() + st::dialogsFilterPadding.x(); auto filterRight = (Global::LocalPasscode() ? (st::dialogsFilterPadding.x() + _lockUnlock->width()) : st::dialogsFilterSkip) + st::dialogsFilterPadding.x(); auto filterWidth = qMax(width(), st::columnMinimalWidthLeft) - filterLeft - filterRight; auto filterAreaHeight = st::dialogsFilterPadding.y() + _mainMenuToggle->height() + st::dialogsFilterPadding.y(); auto filterTop = filterAreaTop + (filterAreaHeight - _filter->height()) / 2; filterLeft = anim::interpolate(filterLeft, smallLayoutWidth, smallLayoutRatio); _filter->setGeometryToLeft(filterLeft, filterTop, filterWidth, _filter->height()); auto mainMenuLeft = anim::interpolate(st::dialogsFilterPadding.x(), (smallLayoutWidth - _mainMenuToggle->width()) / 2, smallLayoutRatio); _mainMenuToggle->moveToLeft(mainMenuLeft, filterAreaTop + st::dialogsFilterPadding.y()); auto right = filterLeft + filterWidth; _lockUnlock->moveToLeft(right + st::dialogsFilterPadding.x(), filterAreaTop + st::dialogsFilterPadding.y()); _cancelSearch->moveToLeft(right - _cancelSearch->width(), _filter->y()); right -= _jumpToDate->width(); _jumpToDate->moveToLeft(right, _filter->y()); right -= _chooseFromUser->width(); _chooseFromUser->moveToLeft(right, _filter->y()); auto scrollTop = filterAreaTop + filterAreaHeight; auto addToScroll = App::main() ? App::main()->contentScrollAddToY() : 0; auto newScrollTop = _scroll->scrollTop() + addToScroll; auto scrollHeight = height() - scrollTop; if (_updateTelegram) { auto updateHeight = _updateTelegram->height(); _updateTelegram->setGeometry(0, height() - updateHeight, width(), updateHeight); scrollHeight -= updateHeight; } auto wasScrollHeight = _scroll->height(); _scroll->setGeometry(0, scrollTop, width(), scrollHeight); if (scrollHeight != wasScrollHeight) { controller()->floatPlayerAreaUpdated().notify(true); } if (addToScroll) { _scroll->scrollToY(newScrollTop); } else { onListScroll(); } } void DialogsWidget::updateForwardBar() { auto selecting = App::main()->selectingPeer(); auto oneColumnSelecting = (Adaptive::OneColumn() && selecting); if (!oneColumnSelecting == !_forwardCancel) { return; } if (oneColumnSelecting) { _forwardCancel.create(this, st::dialogsForwardCancel); _forwardCancel->setClickedCallback([] { Global::RefPeerChooseCancel().notify(true); }); if (!_a_show.animating()) _forwardCancel->show(); } else { _forwardCancel.destroyDelayed(); } updateControlsGeometry(); update(); } void DialogsWidget::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Escape) { e->ignore(); } else if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) { if (!_inner->chooseRow()) { using State = DialogsInner::State; const auto state = _inner->state(); if (state == State::Default || (state == State::Filtered && (!_inner->waitingForSearch() || _inner->hasFilteredResults()))) { _inner->selectSkip(1); _inner->chooseRow(); } else { onSearchMessages(); } } } else if (e->key() == Qt::Key_Down) { _inner->setMouseSelection(false); _inner->selectSkip(1); } else if (e->key() == Qt::Key_Up) { _inner->setMouseSelection(false); _inner->selectSkip(-1); } else if (e->key() == Qt::Key_PageDown) { _inner->setMouseSelection(false); _inner->selectSkipPage(_scroll->height(), 1); } else if (e->key() == Qt::Key_PageUp) { _inner->setMouseSelection(false); _inner->selectSkipPage(_scroll->height(), -1); } else { e->ignore(); } } void DialogsWidget::paintEvent(QPaintEvent *e) { if (App::wnd() && App::wnd()->contentOverlapped(this, e)) return; Painter p(this); QRect r(e->rect()); if (r != rect()) { p.setClipRect(r); } auto progress = _a_show.current(getms(), 1.); if (_a_show.animating()) { auto retina = cIntRetinaFactor(); auto fromLeft = (_showDirection == Window::SlideDirection::FromLeft); auto coordUnder = fromLeft ? anim::interpolate(-st::slideShift, 0, progress) : anim::interpolate(0, -st::slideShift, progress); auto coordOver = fromLeft ? anim::interpolate(0, width(), progress) : anim::interpolate(width(), 0, progress); auto shadow = fromLeft ? (1. - progress) : progress; if (coordOver > 0) { p.drawPixmap(QRect(0, 0, coordOver, _cacheUnder.height() / retina), _cacheUnder, QRect(-coordUnder * retina, 0, coordOver * retina, _cacheUnder.height())); p.setOpacity(shadow); p.fillRect(0, 0, coordOver, _cacheUnder.height() / retina, st::slideFadeOutBg); p.setOpacity(1); } p.drawPixmap(QRect(coordOver, 0, _cacheOver.width() / retina, _cacheOver.height() / retina), _cacheOver, QRect(0, 0, _cacheOver.width(), _cacheOver.height())); p.setOpacity(shadow); st::slideShadow.fill(p, QRect(coordOver - st::slideShadow.width(), 0, st::slideShadow.width(), _cacheOver.height() / retina)); return; } auto aboveTop = 0; if (_forwardCancel) { p.fillRect(0, aboveTop, width(), st::dialogsForwardHeight, st::dialogsForwardBg); p.setPen(st::dialogsForwardFg); p.setFont(st::dialogsForwardFont); p.drawTextLeft(st::dialogsForwardTextLeft, st::dialogsForwardTextTop, width(), lang(lng_forward_choose)); aboveTop += st::dialogsForwardHeight; } auto above = QRect(0, aboveTop, width(), _scroll->y() - aboveTop); if (above.intersects(r)) { p.fillRect(above.intersected(r), st::dialogsBg); } auto belowTop = _scroll->y() + qMin(_scroll->height(), _inner->height()); if (!_widthAnimationCache.isNull()) { p.drawPixmapLeft(0, _scroll->y(), width(), _widthAnimationCache); belowTop = _scroll->y() + (_widthAnimationCache.height() / cIntRetinaFactor()); } auto below = QRect(0, belowTop, width(), height() - belowTop); if (below.intersects(r)) { p.fillRect(below.intersected(r), st::dialogsBg); } } void DialogsWidget::destroyData() { _inner->destroyData(); } Dialogs::RowDescriptor DialogsWidget::chatListEntryBefore( const Dialogs::RowDescriptor &which) const { return _inner->chatListEntryBefore(which); } Dialogs::RowDescriptor DialogsWidget::chatListEntryAfter( const Dialogs::RowDescriptor &which) const { return _inner->chatListEntryAfter(which); } void DialogsWidget::scrollToPeer(not_null<History*> history, MsgId msgId) { _inner->scrollToPeer(history, msgId); } void DialogsWidget::removeDialog(Dialogs::Key key) { _inner->removeDialog(key); onFilterUpdate(); } Dialogs::IndexedList *DialogsWidget::contactsList() { return _inner->contactsList(); } Dialogs::IndexedList *DialogsWidget::dialogsList() { return _inner->dialogsList(); } Dialogs::IndexedList *DialogsWidget::contactsNoDialogsList() { return _inner->contactsNoDialogsList(); } bool DialogsWidget::onCancelSearch() { bool clearing = !_filter->getLastText().isEmpty(); if (_searchRequest) { MTP::cancel(_searchRequest); _searchRequest = 0; } if (_searchInChat && !clearing) { if (Adaptive::OneColumn()) { if (const auto peer = _searchInChat.peer()) { Ui::showPeerHistory(peer, ShowAtUnreadMsgId); } else if (const auto feed = _searchInChat.feed()) { controller()->showSection(HistoryFeed::Memento(feed)); } else { Unexpected("Empty key in onCancelSearch()."); } } setSearchInChat(Dialogs::Key()); clearing = true; } _inner->clearFilter(); _filter->clear(); _filter->updatePlaceholder(); onFilterUpdate(); return clearing; } void DialogsWidget::onCancelSearchInChat() { if (_searchRequest) { MTP::cancel(_searchRequest); _searchRequest = 0; } if (_searchInChat) { if (Adaptive::OneColumn() && !App::main()->selectingPeer()) { if (const auto peer = _searchInChat.peer()) { Ui::showPeerHistory(peer, ShowAtUnreadMsgId); } else if (const auto feed = _searchInChat.feed()) { controller()->showSection(HistoryFeed::Memento(feed)); } else { Unexpected("Empty key in onCancelSearchInPeer()."); } } setSearchInChat(Dialogs::Key()); } _inner->clearFilter(); _filter->clear(); _filter->updatePlaceholder(); onFilterUpdate(); if (!Adaptive::OneColumn() && !App::main()->selectingPeer()) { emit cancelled(); } } void DialogsWidget::onDialogMoved(int movedFrom, int movedTo) { int32 st = _scroll->scrollTop(); if (st > movedTo && st < movedFrom) { _scroll->scrollToY(st + st::dialogsRowHeight); } } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/dialogs/dialogs_widget.h���������������������������������������0000664�0000000�0000000�00000014511�13262451251�0024205�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "window/section_widget.h" #include "ui/widgets/scroll_area.h" #include "dialogs/dialogs_key.h" class DialogsInner; namespace Dialogs { struct RowDescriptor; class Row; class FakeRow; class IndexedList; class Key; } // namespace Dialogs namespace Ui { class IconButton; class PopupMenu; class DropdownMenu; class FlatButton; class FlatInput; class CrossButton; template <typename Widget> class FadeWrapScaled; } // namespace Ui namespace Window { class Controller; } // namespace Window enum DialogsSearchRequestType { DialogsSearchFromStart, DialogsSearchFromOffset, DialogsSearchPeerFromStart, DialogsSearchPeerFromOffset, DialogsSearchMigratedFromStart, DialogsSearchMigratedFromOffset, }; class DialogsWidget : public Window::AbstractSectionWidget, public RPCSender { Q_OBJECT public: DialogsWidget(QWidget *parent, not_null<Window::Controller*> controller); void updateDragInScroll(bool inScroll); void searchInChat(Dialogs::Key chat); void loadDialogs(); void loadPinnedDialogs(); void createDialog(Dialogs::Key key); void removeDialog(Dialogs::Key key); void repaintDialogRow(Dialogs::Mode list, not_null<Dialogs::Row*> row); void repaintDialogRow(not_null<History*> history, MsgId messageId); void dialogsToUp(); void startWidthAnimation(); void stopWidthAnimation(); bool hasTopBarShadow() const { return true; } void showAnimated(Window::SlideDirection direction, const Window::SectionSlideParams ¶ms); void showFast(); void destroyData(); Dialogs::RowDescriptor chatListEntryBefore( const Dialogs::RowDescriptor &which) const; Dialogs::RowDescriptor chatListEntryAfter( const Dialogs::RowDescriptor &which) const; void scrollToPeer(not_null<History*> history, MsgId msgId); Dialogs::IndexedList *contactsList(); Dialogs::IndexedList *dialogsList(); Dialogs::IndexedList *contactsNoDialogsList(); void searchMessages(const QString &query, Dialogs::Key inChat = {}); void onSearchMore(); // Float player interface. bool wheelEventFromFloatPlayer(QEvent *e) override; QRect rectForFloatPlayer() const override; void notify_historyMuteUpdated(History *history); signals: void cancelled(); public slots: void onDraggingScrollDelta(int delta); void onCancel(); void onListScroll(); void activate(); void onFilterUpdate(bool force = false); bool onCancelSearch(); void onCancelSearchInChat(); void onFilterCursorMoved(int from = -1, int to = -1); void onCompleteHashtag(QString tag); void onDialogMoved(int movedFrom, int movedTo); bool onSearchMessages(bool searchCache = false); void onNeedSearchMessages(); void onChooseByDrag(); private slots: void onDraggingScrollTimer(); #ifndef TDESKTOP_DISABLE_AUTOUPDATE void onCheckUpdateStatus(); #endif // TDESKTOP_DISABLE_AUTOUPDATE protected: void dragEnterEvent(QDragEnterEvent *e) override; void dragMoveEvent(QDragMoveEvent *e) override; void dragLeaveEvent(QDragLeaveEvent *e) override; void dropEvent(QDropEvent *e) override; void resizeEvent(QResizeEvent *e) override; void keyPressEvent(QKeyEvent *e) override; void paintEvent(QPaintEvent *e) override; private: void animationCallback(); void dialogsReceived( const MTPmessages_Dialogs &result, mtpRequestId requestId); void pinnedDialogsReceived( const MTPmessages_PeerDialogs &result, mtpRequestId requestId); void searchReceived( DialogsSearchRequestType type, const MTPmessages_Messages &result, mtpRequestId requestId); void peerSearchReceived( const MTPcontacts_Found &result, mtpRequestId requestId); void updateDialogsOffset( const QVector<MTPDialog> &dialogs, const QVector<MTPMessage> &messages); void applyReceivedDialogs( const QVector<MTPDialog> &dialogs, const QVector<MTPMessage> &messages); bool searchForPeersRequired(const QString &query) const; void setSearchInChat(Dialogs::Key chat, UserData *from = nullptr); void showJumpToDate(); void showSearchFrom(); void showMainMenu(); void clearSearchCache(); void updateLockUnlockVisibility(); void updateJumpToDateVisibility(bool fast = false); void updateSearchFromVisibility(bool fast = false); void updateControlsGeometry(); void updateForwardBar(); bool dialogsFailed(const RPCError &error, mtpRequestId req); bool searchFailed(DialogsSearchRequestType type, const RPCError &error, mtpRequestId req); bool peopleFailed(const RPCError &error, mtpRequestId req); bool _dragInScroll = false; bool _dragForward = false; QTimer _chooseByDragTimer; bool _dialogsFull = false; int32 _dialogsOffsetDate = 0; MsgId _dialogsOffsetId = 0; PeerData *_dialogsOffsetPeer = nullptr; mtpRequestId _dialogsRequestId = 0; mtpRequestId _pinnedDialogsRequestId = 0; bool _pinnedDialogsReceived = false; object_ptr<Ui::IconButton> _forwardCancel = { nullptr }; object_ptr<Ui::IconButton> _mainMenuToggle; object_ptr<Ui::FlatInput> _filter; object_ptr<Ui::FadeWrapScaled<Ui::IconButton>> _chooseFromUser; object_ptr<Ui::FadeWrapScaled<Ui::IconButton>> _jumpToDate; object_ptr<Ui::CrossButton> _cancelSearch; object_ptr<Ui::IconButton> _lockUnlock; object_ptr<Ui::ScrollArea> _scroll; QPointer<DialogsInner> _inner; class UpdateButton; object_ptr<UpdateButton> _updateTelegram = { nullptr }; Animation _a_show; Window::SlideDirection _showDirection; QPixmap _cacheUnder, _cacheOver; Dialogs::Key _searchInChat; History *_searchInMigrated = nullptr; UserData *_searchFromUser = nullptr; QString _lastFilterText; QTimer _searchTimer; QString _peerSearchQuery; bool _peerSearchFull = false; mtpRequestId _peerSearchRequest = 0; QString _searchQuery; UserData *_searchQueryFrom = nullptr; bool _searchFull = false; bool _searchFullMigrated = false; mtpRequestId _searchRequest = 0; using SearchCache = QMap<QString, MTPmessages_Messages>; SearchCache _searchCache; using SearchQueries = QMap<mtpRequestId, QString>; SearchQueries _searchQueries; using PeerSearchCache = QMap<QString, MTPcontacts_Found>; PeerSearchCache _peerSearchCache; using PeerSearchQueries = QMap<mtpRequestId, QString>; PeerSearchQueries _peerSearchQueries; QPixmap _widthAnimationCache; object_ptr<QTimer> _draggingScrollTimer = { nullptr }; int _draggingScrollDelta = 0; }; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/facades.cpp����������������������������������������������������0000664�0000000�0000000�00000047405�13262451251�0021527�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "facades.h" #include "info/info_memento.h" #include "core/click_handler_types.h" #include "media/media_clip_reader.h" #include "window/window_controller.h" #include "history/history_item_components.h" #include "observer_peer.h" #include "mainwindow.h" #include "mainwidget.h" #include "apiwrap.h" #include "messenger.h" #include "auth_session.h" #include "boxes/confirm_box.h" #include "window/layer_widget.h" #include "lang/lang_keys.h" #include "base/observer.h" #include "history/history.h" #include "history/history_item.h" #include "history/history_media.h" #include "styles/style_history.h" #include "data/data_session.h" namespace App { namespace internal { void CallDelayed(int duration, base::lambda_once<void()> &&lambda) { Messenger::Instance().callDelayed(duration, std::move(lambda)); } } // namespace internal void sendBotCommand(PeerData *peer, UserData *bot, const QString &cmd, MsgId replyTo) { if (auto m = main()) { m->sendBotCommand(peer, bot, cmd, replyTo); } } void hideSingleUseKeyboard(const HistoryItem *msg) { if (auto m = main()) { m->hideSingleUseKeyboard(msg->history()->peer, msg->id); } } bool insertBotCommand(const QString &cmd) { if (auto m = main()) { return m->insertBotCommand(cmd); } return false; } void activateBotCommand( not_null<const HistoryItem*> msg, int row, int column) { const HistoryMessageMarkupButton *button = nullptr; if (auto markup = msg->Get<HistoryMessageReplyMarkup>()) { if (row < markup->rows.size()) { auto &buttonRow = markup->rows[row]; if (column < buttonRow.size()) { button = &buttonRow[column]; } } } if (!button) return; using ButtonType = HistoryMessageMarkupButton::Type; switch (button->type) { case ButtonType::Default: { // Copy string before passing it to the sending method // because the original button can be destroyed inside. MsgId replyTo = (msg->id > 0) ? msg->id : 0; sendBotCommand(msg->history()->peer, msg->fromOriginal()->asUser(), QString(button->text), replyTo); } break; case ButtonType::Callback: case ButtonType::Game: { if (auto m = main()) { m->app_sendBotCallback(button, msg, row, column); } } break; case ButtonType::Buy: { Ui::show(Box<InformBox>(lang(lng_payments_not_supported))); } break; case ButtonType::Url: { auto url = QString::fromUtf8(button->data); auto skipConfirmation = false; if (auto bot = msg->getMessageBot()) { if (bot->isVerified()) { skipConfirmation = true; } } if (skipConfirmation) { UrlClickHandler::doOpen(url); } else { HiddenUrlClickHandler::doOpen(url); } } break; case ButtonType::RequestLocation: { hideSingleUseKeyboard(msg); Ui::show(Box<InformBox>(lang(lng_bot_share_location_unavailable))); } break; case ButtonType::RequestPhone: { hideSingleUseKeyboard(msg); const auto msgId = msg->id; const auto history = msg->history(); Ui::show(Box<ConfirmBox>(lang(lng_bot_share_phone), lang(lng_bot_share_phone_confirm), [=] { Ui::showPeerHistory(history, ShowAtTheEndMsgId); auto options = ApiWrap::SendOptions(history); options.replyTo = msgId; Auth().api().shareContact(App::self(), options); })); } break; case ButtonType::SwitchInlineSame: case ButtonType::SwitchInline: { if (auto m = App::main()) { if (auto bot = msg->getMessageBot()) { auto tryFastSwitch = [bot, &button, msgId = msg->id]() -> bool { auto samePeer = (button->type == ButtonType::SwitchInlineSame); if (samePeer) { Notify::switchInlineBotButtonReceived(QString::fromUtf8(button->data), bot, msgId); return true; } else if (bot->botInfo && bot->botInfo->inlineReturnPeerId) { if (Notify::switchInlineBotButtonReceived(QString::fromUtf8(button->data))) { return true; } } return false; }; if (!tryFastSwitch()) { m->inlineSwitchLayer('@' + bot->username + ' ' + QString::fromUtf8(button->data)); } } } } break; } } void searchByHashtag(const QString &tag, PeerData *inPeer) { if (const auto m = main()) { Ui::hideSettingsAndLayer(); Messenger::Instance().hideMediaView(); if (inPeer && (!inPeer->isChannel() || inPeer->isMegagroup())) { inPeer = nullptr; } m->searchMessages( tag + ' ', (inPeer ? App::history(inPeer).get() : Dialogs::Key())); } } void openPeerByName(const QString &username, MsgId msgId, const QString &startToken) { if (MainWidget *m = main()) m->openPeerByName(username, msgId, startToken); } void joinGroupByHash(const QString &hash) { if (MainWidget *m = main()) m->joinGroupByHash(hash); } void showSettings() { if (auto w = wnd()) { w->showSettings(); } } void activateClickHandler(ClickHandlerPtr handler, Qt::MouseButton button) { crl::on_main(wnd(), [handler, button] { handler->onClick(button); }); } void logOutDelayed() { InvokeQueued(QCoreApplication::instance(), [] { App::logOut(); }); } } // namespace App namespace Ui { namespace internal { void showBox( object_ptr<BoxContent> content, LayerOptions options, anim::type animated) { if (auto w = App::wnd()) { w->ui_showBox(std::move(content), options, animated); } } } // namespace internal void showMediaPreview(DocumentData *document) { if (auto w = App::wnd()) { w->ui_showMediaPreview(document); } } void showMediaPreview(PhotoData *photo) { if (auto w = App::wnd()) { w->ui_showMediaPreview(photo); } } void hideMediaPreview() { if (auto w = App::wnd()) { w->ui_hideMediaPreview(); } } void hideLayer(anim::type animated) { if (auto w = App::wnd()) { w->ui_showBox( { nullptr }, LayerOption::CloseOther, animated); } } void hideSettingsAndLayer(anim::type animated) { if (auto w = App::wnd()) { w->ui_hideSettingsAndLayer(animated); } } bool isLayerShown() { if (auto w = App::wnd()) return w->ui_isLayerShown(); return false; } void showPeerProfile(const PeerId &peer) { if (auto window = App::wnd()) { if (auto controller = window->controller()) { controller->showPeerInfo(peer); } } } void showPeerProfile(not_null<const History*> history) { showPeerProfile(history->peer->id); } void showPeerHistory( const PeerId &peer, MsgId msgId) { auto ms = getms(); if (auto m = App::main()) { m->ui_showPeerHistory( peer, Window::SectionShow::Way::ClearStack, msgId); } } void showPeerHistoryAtItem(not_null<const HistoryItem*> item) { showPeerHistory(item->history()->peer->id, item->id); } void showPeerHistory(not_null<const History*> history, MsgId msgId) { showPeerHistory(history->peer->id, msgId); } PeerData *getPeerForMouseAction() { return Messenger::Instance().ui_getPeerForMouseAction(); } bool skipPaintEvent(QWidget *widget, QPaintEvent *event) { if (auto w = App::wnd()) { if (w->contentOverlapped(widget, event)) { return true; } } return false; } } // namespace Ui namespace Notify { void userIsBotChanged(UserData *user) { if (MainWidget *m = App::main()) m->notify_userIsBotChanged(user); } void botCommandsChanged(UserData *user) { if (MainWidget *m = App::main()) { m->notify_botCommandsChanged(user); } peerUpdatedDelayed(user, PeerUpdate::Flag::BotCommandsChanged); } void inlineBotRequesting(bool requesting) { if (MainWidget *m = App::main()) m->notify_inlineBotRequesting(requesting); } void replyMarkupUpdated(const HistoryItem *item) { if (MainWidget *m = App::main()) { m->notify_replyMarkupUpdated(item); } } void inlineKeyboardMoved(const HistoryItem *item, int oldKeyboardTop, int newKeyboardTop) { if (MainWidget *m = App::main()) { m->notify_inlineKeyboardMoved(item, oldKeyboardTop, newKeyboardTop); } } bool switchInlineBotButtonReceived(const QString &query, UserData *samePeerBot, MsgId samePeerReplyTo) { if (auto main = App::main()) { return main->notify_switchInlineBotButtonReceived(query, samePeerBot, samePeerReplyTo); } return false; } void migrateUpdated(PeerData *peer) { if (MainWidget *m = App::main()) m->notify_migrateUpdated(peer); } void historyMuteUpdated(History *history) { if (MainWidget *m = App::main()) m->notify_historyMuteUpdated(history); } void unreadCounterUpdated() { Global::RefHandleUnreadCounterUpdate().call(); } } // namespace Notify #define DefineReadOnlyVar(Namespace, Type, Name) const Type &Name() { \ AssertCustom(Namespace##Data != nullptr, #Namespace "Data != nullptr in " #Namespace "::" #Name); \ return Namespace##Data->Name; \ } #define DefineRefVar(Namespace, Type, Name) DefineReadOnlyVar(Namespace, Type, Name) \ Type &Ref##Name() { \ AssertCustom(Namespace##Data != nullptr, #Namespace "Data != nullptr in " #Namespace "::Ref" #Name); \ return Namespace##Data->Name; \ } #define DefineVar(Namespace, Type, Name) DefineRefVar(Namespace, Type, Name) \ void Set##Name(const Type &Name) { \ AssertCustom(Namespace##Data != nullptr, #Namespace "Data != nullptr in " #Namespace "::Set" #Name); \ Namespace##Data->Name = Name; \ } namespace Sandbox { namespace internal { struct Data { QByteArray LastCrashDump; ProxyData PreLaunchProxy; }; } // namespace internal } // namespace Sandbox std::unique_ptr<Sandbox::internal::Data> SandboxData; uint64 SandboxUserTag = 0; namespace Sandbox { bool CheckBetaVersionDir() { QFile beta(cExeDir() + qsl("TelegramBeta_data/tdata/beta")); if (cBetaVersion()) { cForceWorkingDir(cExeDir() + qsl("TelegramBeta_data/")); QDir().mkpath(cWorkingDir() + qstr("tdata")); if (*BetaPrivateKey) { cSetBetaPrivateKey(QByteArray(BetaPrivateKey)); } if (beta.open(QIODevice::WriteOnly)) { QDataStream dataStream(&beta); dataStream.setVersion(QDataStream::Qt_5_3); dataStream << quint64(cRealBetaVersion()) << cBetaPrivateKey(); } else { LOG(("FATAL: Could not open '%1' for writing private key!").arg(beta.fileName())); return false; } } else if (beta.exists()) { cForceWorkingDir(cExeDir() + qsl("TelegramBeta_data/")); if (beta.open(QIODevice::ReadOnly)) { QDataStream dataStream(&beta); dataStream.setVersion(QDataStream::Qt_5_3); quint64 v; QByteArray k; dataStream >> v >> k; if (dataStream.status() == QDataStream::Ok) { cSetBetaVersion(qMax(v, AppVersion * 1000ULL)); cSetBetaPrivateKey(k); cSetRealBetaVersion(v); } else { LOG(("FATAL: '%1' is corrupted, reinstall private beta!").arg(beta.fileName())); return false; } } else { LOG(("FATAL: could not open '%1' for reading private key!").arg(beta.fileName())); return false; } } return true; } void WorkingDirReady() { if (QFile(cWorkingDir() + qsl("tdata/withtestmode")).exists()) { cSetTestMode(true); } if (!cDebug() && QFile(cWorkingDir() + qsl("tdata/withdebug")).exists()) { cSetDebug(true); } if (cBetaVersion()) { cSetAlphaVersion(false); } else if (!cAlphaVersion() && QFile(cWorkingDir() + qsl("tdata/devversion")).exists()) { cSetAlphaVersion(true); } else if (AppAlphaVersion) { QFile f(cWorkingDir() + qsl("tdata/devversion")); if (!f.exists() && f.open(QIODevice::WriteOnly)) { f.write("1"); } } srand((int32)time(NULL)); SandboxUserTag = 0; QFile usertag(cWorkingDir() + qsl("tdata/usertag")); if (usertag.open(QIODevice::ReadOnly)) { if (usertag.read(reinterpret_cast<char*>(&SandboxUserTag), sizeof(uint64)) != sizeof(uint64)) { SandboxUserTag = 0; } usertag.close(); } if (!SandboxUserTag) { do { memsetrnd_bad(SandboxUserTag); } while (!SandboxUserTag); if (usertag.open(QIODevice::WriteOnly)) { usertag.write(reinterpret_cast<char*>(&SandboxUserTag), sizeof(uint64)); usertag.close(); } } } void start() { SandboxData = std::make_unique<internal::Data>(); } bool started() { return (SandboxData != nullptr); } void finish() { SandboxData.reset(); } uint64 UserTag() { return SandboxUserTag; } DefineVar(Sandbox, QByteArray, LastCrashDump); DefineVar(Sandbox, ProxyData, PreLaunchProxy); } // namespace Sandbox namespace Global { namespace internal { struct Data { SingleQueuedInvokation HandleUnreadCounterUpdate = { [] { Messenger::Instance().call_handleUnreadCounterUpdate(); } }; SingleQueuedInvokation HandleDelayedPeerUpdates = { [] { Messenger::Instance().call_handleDelayedPeerUpdates(); } }; SingleQueuedInvokation HandleObservables = { [] { Messenger::Instance().call_handleObservables(); } }; Adaptive::WindowLayout AdaptiveWindowLayout = Adaptive::WindowLayout::Normal; Adaptive::ChatLayout AdaptiveChatLayout = Adaptive::ChatLayout::Normal; bool AdaptiveForWide = true; base::Observable<void> AdaptiveChanged; bool DialogsModeEnabled = false; Dialogs::Mode DialogsMode = Dialogs::Mode::All; bool ModerateModeEnabled = false; bool ScreenIsLocked = false; int32 DebugLoggingFlags = 0; float64 RememberedSongVolume = kDefaultVolume; float64 SongVolume = kDefaultVolume; base::Observable<void> SongVolumeChanged; float64 VideoVolume = kDefaultVolume; base::Observable<void> VideoVolumeChanged; // config int32 ChatSizeMax = 200; int32 MegagroupSizeMax = 10000; int32 ForwardedCountMax = 100; int32 OnlineUpdatePeriod = 120000; int32 OfflineBlurTimeout = 5000; int32 OfflineIdleTimeout = 30000; int32 OnlineFocusTimeout = 1000; int32 OnlineCloudTimeout = 300000; int32 NotifyCloudDelay = 30000; int32 NotifyDefaultDelay = 1500; int32 PushChatPeriod = 60000; int32 PushChatLimit = 2; int32 SavedGifsLimit = 200; int32 EditTimeLimit = 172800; int32 RevokeTimeLimit = 172800; int32 RevokePrivateTimeLimit = 172800; bool RevokePrivateInbox = false; int32 StickersRecentLimit = 30; int32 StickersFavedLimit = 5; int32 PinnedDialogsCountMax = 5; QString InternalLinksDomain = qsl("https://t.me/"); int32 ChannelsReadMediaPeriod = 86400 * 7; int32 CallReceiveTimeoutMs = 20000; int32 CallRingTimeoutMs = 90000; int32 CallConnectTimeoutMs = 30000; int32 CallPacketTimeoutMs = 10000; bool PhoneCallsEnabled = true; base::Observable<void> PhoneCallsEnabledChanged; HiddenPinnedMessagesMap HiddenPinnedMessages; Stickers::Sets StickerSets; Stickers::Order StickerSetsOrder; TimeMs LastStickersUpdate = 0; TimeMs LastRecentStickersUpdate = 0; TimeMs LastFavedStickersUpdate = 0; Stickers::Order FeaturedStickerSetsOrder; int FeaturedStickerSetsUnreadCount = 0; base::Observable<void> FeaturedStickerSetsUnreadCountChanged; TimeMs LastFeaturedStickersUpdate = 0; Stickers::Order ArchivedStickerSetsOrder; CircleMasksMap CircleMasks; base::Observable<void> SelfChanged; bool AskDownloadPath = false; QString DownloadPath; QByteArray DownloadPathBookmark; base::Observable<void> DownloadPathChanged; bool SuggestStickersByEmoji = true; bool SoundNotify = true; bool DesktopNotify = true; bool RestoreSoundNotifyFromTray = false; bool IncludeMuted = true; DBINotifyView NotifyView = dbinvShowPreview; bool NativeNotifications = false; int NotificationsCount = 3; Notify::ScreenCorner NotificationsCorner = Notify::ScreenCorner::BottomRight; bool NotificationsDemoIsShown = false; DBIConnectionType ConnectionType = dbictAuto; DBIConnectionType LastProxyType = dbictAuto; bool TryIPv6 = (cPlatform() == dbipWindows) ? false : true; ProxyData ConnectionProxy; base::Observable<void> ConnectionTypeChanged; int AutoLock = 3600; bool LocalPasscode = false; base::Observable<void> LocalPasscodeChanged; base::Variable<DBIWorkMode> WorkMode = { dbiwmWindowAndTray }; base::Observable<void> UnreadCounterUpdate; base::Observable<void> PeerChooseCancel; }; } // namespace internal } // namespace Global Global::internal::Data *GlobalData = nullptr; namespace Global { bool started() { return GlobalData != nullptr; } void start() { GlobalData = new internal::Data(); } void finish() { delete GlobalData; GlobalData = nullptr; } DefineRefVar(Global, SingleQueuedInvokation, HandleUnreadCounterUpdate); DefineRefVar(Global, SingleQueuedInvokation, HandleDelayedPeerUpdates); DefineRefVar(Global, SingleQueuedInvokation, HandleObservables); DefineVar(Global, Adaptive::WindowLayout, AdaptiveWindowLayout); DefineVar(Global, Adaptive::ChatLayout, AdaptiveChatLayout); DefineVar(Global, bool, AdaptiveForWide); DefineRefVar(Global, base::Observable<void>, AdaptiveChanged); DefineVar(Global, bool, DialogsModeEnabled); DefineVar(Global, Dialogs::Mode, DialogsMode); DefineVar(Global, bool, ModerateModeEnabled); DefineVar(Global, bool, ScreenIsLocked); DefineVar(Global, int32, DebugLoggingFlags); DefineVar(Global, float64, RememberedSongVolume); DefineVar(Global, float64, SongVolume); DefineRefVar(Global, base::Observable<void>, SongVolumeChanged); DefineVar(Global, float64, VideoVolume); DefineRefVar(Global, base::Observable<void>, VideoVolumeChanged); // config DefineVar(Global, int32, ChatSizeMax); DefineVar(Global, int32, MegagroupSizeMax); DefineVar(Global, int32, ForwardedCountMax); DefineVar(Global, int32, OnlineUpdatePeriod); DefineVar(Global, int32, OfflineBlurTimeout); DefineVar(Global, int32, OfflineIdleTimeout); DefineVar(Global, int32, OnlineFocusTimeout); DefineVar(Global, int32, OnlineCloudTimeout); DefineVar(Global, int32, NotifyCloudDelay); DefineVar(Global, int32, NotifyDefaultDelay); DefineVar(Global, int32, PushChatPeriod); DefineVar(Global, int32, PushChatLimit); DefineVar(Global, int32, SavedGifsLimit); DefineVar(Global, int32, EditTimeLimit); DefineVar(Global, int32, RevokeTimeLimit); DefineVar(Global, int32, RevokePrivateTimeLimit); DefineVar(Global, bool, RevokePrivateInbox); DefineVar(Global, int32, StickersRecentLimit); DefineVar(Global, int32, StickersFavedLimit); DefineVar(Global, int32, PinnedDialogsCountMax); DefineVar(Global, QString, InternalLinksDomain); DefineVar(Global, int32, ChannelsReadMediaPeriod); DefineVar(Global, int32, CallReceiveTimeoutMs); DefineVar(Global, int32, CallRingTimeoutMs); DefineVar(Global, int32, CallConnectTimeoutMs); DefineVar(Global, int32, CallPacketTimeoutMs); DefineVar(Global, bool, PhoneCallsEnabled); DefineRefVar(Global, base::Observable<void>, PhoneCallsEnabledChanged); DefineVar(Global, HiddenPinnedMessagesMap, HiddenPinnedMessages); DefineVar(Global, Stickers::Sets, StickerSets); DefineVar(Global, Stickers::Order, StickerSetsOrder); DefineVar(Global, TimeMs, LastStickersUpdate); DefineVar(Global, TimeMs, LastRecentStickersUpdate); DefineVar(Global, TimeMs, LastFavedStickersUpdate); DefineVar(Global, Stickers::Order, FeaturedStickerSetsOrder); DefineVar(Global, int, FeaturedStickerSetsUnreadCount); DefineRefVar(Global, base::Observable<void>, FeaturedStickerSetsUnreadCountChanged); DefineVar(Global, TimeMs, LastFeaturedStickersUpdate); DefineVar(Global, Stickers::Order, ArchivedStickerSetsOrder); DefineRefVar(Global, CircleMasksMap, CircleMasks); DefineRefVar(Global, base::Observable<void>, SelfChanged); DefineVar(Global, bool, AskDownloadPath); DefineVar(Global, QString, DownloadPath); DefineVar(Global, QByteArray, DownloadPathBookmark); DefineRefVar(Global, base::Observable<void>, DownloadPathChanged); DefineVar(Global, bool, SuggestStickersByEmoji); DefineVar(Global, bool, SoundNotify); DefineVar(Global, bool, DesktopNotify); DefineVar(Global, bool, RestoreSoundNotifyFromTray); DefineVar(Global, bool, IncludeMuted); DefineVar(Global, DBINotifyView, NotifyView); DefineVar(Global, bool, NativeNotifications); DefineVar(Global, int, NotificationsCount); DefineVar(Global, Notify::ScreenCorner, NotificationsCorner); DefineVar(Global, bool, NotificationsDemoIsShown); DefineVar(Global, DBIConnectionType, ConnectionType); DefineVar(Global, DBIConnectionType, LastProxyType); DefineVar(Global, bool, TryIPv6); DefineVar(Global, ProxyData, ConnectionProxy); DefineRefVar(Global, base::Observable<void>, ConnectionTypeChanged); DefineVar(Global, int, AutoLock); DefineVar(Global, bool, LocalPasscode); DefineRefVar(Global, base::Observable<void>, LocalPasscodeChanged); DefineRefVar(Global, base::Variable<DBIWorkMode>, WorkMode); DefineRefVar(Global, base::Observable<void>, UnreadCounterUpdate); DefineRefVar(Global, base::Observable<void>, PeerChooseCancel); } // namespace Global �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/facades.h������������������������������������������������������0000664�0000000�0000000�00000025225�13262451251�0021170�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "base/type_traits.h" #include "base/observer.h" class BoxContent; namespace Dialogs { enum class Mode; } // namespace Dialogs namespace InlineBots { namespace Layout { class ItemBase; } // namespace Layout } // namespace InlineBots namespace App { namespace internal { void CallDelayed(int duration, base::lambda_once<void()> &&lambda); } // namespace internal template <typename Lambda> inline void CallDelayed( int duration, base::lambda_internal::guard_with_QObject<Lambda> &&guarded) { return internal::CallDelayed( duration, std::move(guarded)); } template <typename Lambda> inline void CallDelayed( int duration, base::lambda_internal::guard_with_weak<Lambda> &&guarded) { return internal::CallDelayed( duration, std::move(guarded)); } template <typename Lambda> inline void CallDelayed( int duration, const QObject *object, Lambda &&lambda) { return internal::CallDelayed( duration, base::lambda_guarded(object, std::forward<Lambda>(lambda))); } template <typename Lambda> inline void CallDelayed( int duration, const base::has_weak_ptr *object, Lambda &&lambda) { return internal::CallDelayed( duration, base::lambda_guarded(object, std::forward<Lambda>(lambda))); } template <typename Lambda> inline auto LambdaDelayed( int duration, const QObject *object, Lambda &&lambda) { auto guarded = base::lambda_guarded( object, std::forward<Lambda>(lambda)); return [saved = std::move(guarded), duration] { auto copy = saved; internal::CallDelayed(duration, std::move(copy)); }; } template <typename Lambda> inline auto LambdaDelayed( int duration, const base::has_weak_ptr *object, Lambda &&lambda) { auto guarded = base::lambda_guarded( object, std::forward<Lambda>(lambda)); return [saved = std::move(guarded), duration] { auto copy = saved; internal::CallDelayed(duration, std::move(copy)); }; } template <typename Lambda> inline auto LambdaDelayedOnce( int duration, const QObject *object, Lambda &&lambda) { auto guarded = base::lambda_guarded( object, std::forward<Lambda>(lambda)); return [saved = std::move(guarded), duration]() mutable { internal::CallDelayed(duration, std::move(saved)); }; } template <typename Lambda> inline auto LambdaDelayedOnce( int duration, const base::has_weak_ptr *object, Lambda &&lambda) { auto guarded = base::lambda_guarded( object, std::forward<Lambda>(lambda)); return [saved = std::move(guarded), duration]() mutable { internal::CallDelayed(duration, std::move(saved)); }; } void sendBotCommand( PeerData *peer, UserData *bot, const QString &cmd, MsgId replyTo = 0); bool insertBotCommand(const QString &cmd); void activateBotCommand( not_null<const HistoryItem*> msg, int row, int column); void searchByHashtag(const QString &tag, PeerData *inPeer); void openPeerByName( const QString &username, MsgId msgId = ShowAtUnreadMsgId, const QString &startToken = QString()); void joinGroupByHash(const QString &hash); void showSettings(); void activateClickHandler(ClickHandlerPtr handler, Qt::MouseButton button); void logOutDelayed(); } // namespace App enum class LayerOption { CloseOther = (1 << 0), KeepOther = (1 << 1), ShowAfterOther = (1 << 2), }; using LayerOptions = base::flags<LayerOption>; inline constexpr auto is_flag_type(LayerOption) { return true; }; namespace Ui { namespace internal { void showBox( object_ptr<BoxContent> content, LayerOptions options, anim::type animated); } // namespace internal void showMediaPreview(DocumentData *document); void showMediaPreview(PhotoData *photo); void hideMediaPreview(); template <typename BoxType> QPointer<BoxType> show( object_ptr<BoxType> content, LayerOptions options = LayerOption::CloseOther, anim::type animated = anim::type::normal) { auto result = QPointer<BoxType>(content.data()); internal::showBox(std::move(content), options, animated); return result; } void hideLayer(anim::type animated = anim::type::normal); void hideSettingsAndLayer(anim::type animated = anim::type::normal); bool isLayerShown(); void showPeerProfile(const PeerId &peer); inline void showPeerProfile(const PeerData *peer) { showPeerProfile(peer->id); } void showPeerProfile(not_null<const History*> history); void showPeerHistory(const PeerId &peer, MsgId msgId); void showPeerHistoryAtItem(not_null<const HistoryItem*> item); inline void showPeerHistory(const PeerData *peer, MsgId msgId) { showPeerHistory(peer->id, msgId); } void showPeerHistory(not_null<const History*> history, MsgId msgId); inline void showChatsList() { showPeerHistory(PeerId(0), 0); } PeerData *getPeerForMouseAction(); bool skipPaintEvent(QWidget *widget, QPaintEvent *event); } // namespace Ui enum ClipStopperType { ClipStopperMediaview, ClipStopperSavedGifsPanel, }; namespace Notify { void userIsBotChanged(UserData *user); void botCommandsChanged(UserData *user); void inlineBotRequesting(bool requesting); void replyMarkupUpdated(const HistoryItem *item); void inlineKeyboardMoved(const HistoryItem *item, int oldKeyboardTop, int newKeyboardTop); bool switchInlineBotButtonReceived(const QString &query, UserData *samePeerBot = nullptr, MsgId samePeerReplyTo = 0); void migrateUpdated(PeerData *peer); void historyMuteUpdated(History *history); void unreadCounterUpdated(); enum class ScreenCorner { TopLeft = 0, TopRight = 1, BottomRight = 2, BottomLeft = 3, }; inline bool IsLeftCorner(ScreenCorner corner) { return (corner == ScreenCorner::TopLeft) || (corner == ScreenCorner::BottomLeft); } inline bool IsTopCorner(ScreenCorner corner) { return (corner == ScreenCorner::TopLeft) || (corner == ScreenCorner::TopRight); } } // namespace Notify #define DeclareReadOnlyVar(Type, Name) const Type &Name(); #define DeclareRefVar(Type, Name) DeclareReadOnlyVar(Type, Name) \ Type &Ref##Name(); #define DeclareVar(Type, Name) DeclareRefVar(Type, Name) \ void Set##Name(const Type &Name); namespace Sandbox { bool CheckBetaVersionDir(); void WorkingDirReady(); void MainThreadTaskAdded(); void start(); bool started(); void finish(); uint64 UserTag(); DeclareVar(QByteArray, LastCrashDump); DeclareVar(ProxyData, PreLaunchProxy); } // namespace Sandbox namespace Adaptive { enum class WindowLayout { OneColumn, Normal, ThreeColumn, }; enum class ChatLayout { Normal, Wide, }; } // namespace Adaptive namespace DebugLogging { enum Flags { FileLoaderFlag = 0x00000001, }; } // namespace DebugLogging namespace Global { bool started(); void start(); void finish(); DeclareRefVar(SingleQueuedInvokation, HandleUnreadCounterUpdate); DeclareRefVar(SingleQueuedInvokation, HandleDelayedPeerUpdates); DeclareRefVar(SingleQueuedInvokation, HandleObservables); DeclareVar(Adaptive::WindowLayout, AdaptiveWindowLayout); DeclareVar(Adaptive::ChatLayout, AdaptiveChatLayout); DeclareVar(bool, AdaptiveForWide); DeclareRefVar(base::Observable<void>, AdaptiveChanged); DeclareVar(bool, DialogsModeEnabled); DeclareVar(Dialogs::Mode, DialogsMode); DeclareVar(bool, ModerateModeEnabled); DeclareVar(bool, ScreenIsLocked); DeclareVar(int32, DebugLoggingFlags); constexpr float64 kDefaultVolume = 0.9; DeclareVar(float64, RememberedSongVolume); DeclareVar(float64, SongVolume); DeclareRefVar(base::Observable<void>, SongVolumeChanged); DeclareVar(float64, VideoVolume); DeclareRefVar(base::Observable<void>, VideoVolumeChanged); // config DeclareVar(int32, ChatSizeMax); DeclareVar(int32, MegagroupSizeMax); DeclareVar(int32, ForwardedCountMax); DeclareVar(int32, OnlineUpdatePeriod); DeclareVar(int32, OfflineBlurTimeout); DeclareVar(int32, OfflineIdleTimeout); DeclareVar(int32, OnlineFocusTimeout); // not from config DeclareVar(int32, OnlineCloudTimeout); DeclareVar(int32, NotifyCloudDelay); DeclareVar(int32, NotifyDefaultDelay); DeclareVar(int32, PushChatPeriod); DeclareVar(int32, PushChatLimit); DeclareVar(int32, SavedGifsLimit); DeclareVar(int32, EditTimeLimit); DeclareVar(int32, RevokeTimeLimit); DeclareVar(int32, RevokePrivateTimeLimit); DeclareVar(bool, RevokePrivateInbox); DeclareVar(int32, StickersRecentLimit); DeclareVar(int32, StickersFavedLimit); DeclareVar(int32, PinnedDialogsCountMax); DeclareVar(QString, InternalLinksDomain); DeclareVar(int32, ChannelsReadMediaPeriod); DeclareVar(int32, CallReceiveTimeoutMs); DeclareVar(int32, CallRingTimeoutMs); DeclareVar(int32, CallConnectTimeoutMs); DeclareVar(int32, CallPacketTimeoutMs); DeclareVar(bool, PhoneCallsEnabled); DeclareRefVar(base::Observable<void>, PhoneCallsEnabledChanged); typedef QMap<PeerId, MsgId> HiddenPinnedMessagesMap; DeclareVar(HiddenPinnedMessagesMap, HiddenPinnedMessages); typedef QMap<uint64, QPixmap> CircleMasksMap; DeclareRefVar(CircleMasksMap, CircleMasks); DeclareRefVar(base::Observable<void>, SelfChanged); DeclareVar(bool, AskDownloadPath); DeclareVar(QString, DownloadPath); DeclareVar(QByteArray, DownloadPathBookmark); DeclareRefVar(base::Observable<void>, DownloadPathChanged); DeclareVar(bool, SuggestStickersByEmoji); DeclareVar(bool, SoundNotify); DeclareVar(bool, DesktopNotify); DeclareVar(bool, RestoreSoundNotifyFromTray); DeclareVar(bool, IncludeMuted); DeclareVar(DBINotifyView, NotifyView); DeclareVar(bool, NativeNotifications); DeclareVar(int, NotificationsCount); DeclareVar(Notify::ScreenCorner, NotificationsCorner); DeclareVar(bool, NotificationsDemoIsShown); DeclareVar(DBIConnectionType, ConnectionType); DeclareVar(DBIConnectionType, LastProxyType); DeclareVar(bool, TryIPv6); DeclareVar(ProxyData, ConnectionProxy); DeclareRefVar(base::Observable<void>, ConnectionTypeChanged); DeclareVar(int, AutoLock); DeclareVar(bool, LocalPasscode); DeclareRefVar(base::Observable<void>, LocalPasscodeChanged); DeclareRefVar(base::Variable<DBIWorkMode>, WorkMode); DeclareRefVar(base::Observable<void>, UnreadCounterUpdate); DeclareRefVar(base::Observable<void>, PeerChooseCancel); } // namespace Global namespace Adaptive { inline base::Observable<void> &Changed() { return Global::RefAdaptiveChanged(); } inline bool OneColumn() { return Global::AdaptiveWindowLayout() == WindowLayout::OneColumn; } inline bool Normal() { return Global::AdaptiveWindowLayout() == WindowLayout::Normal; } inline bool ThreeColumn() { return Global::AdaptiveWindowLayout() == WindowLayout::ThreeColumn; } inline bool ChatNormal() { return !Global::AdaptiveForWide() || (Global::AdaptiveChatLayout() == ChatLayout::Normal); } inline bool ChatWide() { return !ChatNormal(); } } // namespace Adaptive namespace DebugLogging { inline bool FileLoader() { return (Global::DebugLoggingFlags() & FileLoaderFlag) != 0; } } // namespace DebugLogging ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/�������������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0021124�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/admin_log/���������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0023055�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/admin_log/history_admin_log_filter.cpp�����������������0000664�0000000�0000000�00000030125�13262451251�0030641�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/admin_log/history_admin_log_filter.h" #include "styles/style_boxes.h" #include "ui/widgets/checkbox.h" #include "ui/effects/ripple_animation.h" #include "lang/lang_keys.h" #include "data/data_peer_values.h" namespace AdminLog { namespace { class UserCheckbox : public Ui::RippleButton { public: UserCheckbox(QWidget *parent, not_null<UserData*> user, bool checked); bool checked() const { return _check->checked(); } base::Observable<bool> checkedChanged; enum class NotifyAboutChange { Notify, DontNotify, }; void setChecked(bool checked, NotifyAboutChange notify = NotifyAboutChange::Notify); void finishAnimating(); QMargins getMargins() const override { return _st.margin; } protected: void paintEvent(QPaintEvent *e) override; int resizeGetHeight(int newWidth) override; QImage prepareRippleMask() const override; QPoint prepareRippleStartPosition() const override; private: const style::Checkbox &_st; std::unique_ptr<Ui::AbstractCheckView> _check; QRect _checkRect; not_null<UserData*> _user; QString _statusText; bool _statusOnline = false; }; UserCheckbox::UserCheckbox(QWidget *parent, not_null<UserData*> user, bool checked) : Ui::RippleButton(parent, st::defaultBoxCheckbox.ripple) , _st(st::adminLogFilterUserCheckbox) , _check(std::make_unique<Ui::CheckView>(st::defaultCheck, checked, [this] { rtlupdate(_checkRect); })) , _user(user) { setCursor(style::cur_pointer); setClickedCallback([this] { if (isDisabled()) return; setChecked(!this->checked()); }); auto now = unixtime(); _statusText = Data::OnlineText(_user, now); _statusOnline = Data::OnlineTextActive(_user, now); auto checkSize = _check->getSize(); _checkRect = { QPoint(_st.margin.left(), (st::contactsPhotoSize - checkSize.height()) / 2), checkSize }; } void UserCheckbox::setChecked(bool checked, NotifyAboutChange notify) { if (_check->checked() != checked) { _check->setCheckedAnimated(checked); if (notify == NotifyAboutChange::Notify) { checkedChanged.notify(checked, true); } } } void UserCheckbox::paintEvent(QPaintEvent *e) { Painter p(this); auto ms = getms(); auto active = _check->currentAnimationValue(ms); auto color = anim::color(_st.rippleBg, _st.rippleBgActive, active); paintRipple(p, _st.rippleAreaPosition.x(), _st.rippleAreaPosition.y() + (_checkRect.y() - st::defaultBoxCheckbox.margin.top()), ms, &color); auto realCheckRect = myrtlrect(_checkRect); if (realCheckRect.intersects(e->rect())) { _check->paint(p, _checkRect.left(), _checkRect.top(), width()); } if (realCheckRect.contains(e->rect())) return; auto userpicLeft = _checkRect.x() + _checkRect.width() + st::adminLogFilterUserpicLeft; auto userpicTop = 0; _user->paintUserpicLeft(p, userpicLeft, userpicTop, width(), st::contactsPhotoSize); auto nameLeft = userpicLeft + st::contactsPhotoSize + st::contactsPadding.left(); auto nameTop = userpicTop + st::contactsNameTop; auto nameWidth = width() - nameLeft - st::contactsPadding.right(); p.setPen(st::contactsNameFg); _user->nameText.drawLeftElided(p, nameLeft, nameTop, nameWidth, width()); auto statusLeft = nameLeft; auto statusTop = userpicTop + st::contactsStatusTop; p.setFont(st::contactsStatusFont); p.setPen(_statusOnline ? st::contactsStatusFgOnline : st::contactsStatusFg); p.drawTextLeft(statusLeft, statusTop, width(), _statusText); } void UserCheckbox::finishAnimating() { _check->finishAnimating(); } int UserCheckbox::resizeGetHeight(int newWidth) { return st::contactsPhotoSize; } QImage UserCheckbox::prepareRippleMask() const { return _check->prepareRippleMask(); } QPoint UserCheckbox::prepareRippleStartPosition() const { auto position = mapFromGlobal(QCursor::pos()) - _st.rippleAreaPosition - QPoint(0, _checkRect.y() - st::defaultBoxCheckbox.margin.top()); return _check->checkRippleStartPosition(position) ? position : DisabledRippleStartPosition(); } } // namespace class FilterBox::Inner : public TWidget, private base::Subscriber { public: Inner(QWidget *parent, not_null<ChannelData*> channel, const std::vector<not_null<UserData*>> &admins, const FilterValue &filter, base::lambda<void()> changedCallback); template <typename Widget> QPointer<Widget> addRow(object_ptr<Widget> widget, int marginTop) { widget->setParent(this); widget->show(); auto row = Row(); row.widget = std::move(widget); row.marginTop = marginTop; _rows.push_back(std::move(row)); return static_cast<Widget*>(_rows.back().widget.data()); } bool canSave() const; FilterValue filter() const; protected: int resizeGetHeight(int newWidth) override; void resizeEvent(QResizeEvent *e) override; private: void createControls(const std::vector<not_null<UserData*>> &admins, const FilterValue &filter); void createAllActionsCheckbox(const FilterValue &filter); void createActionsCheckboxes(const FilterValue &filter); void createAllUsersCheckbox(const FilterValue &filter); void createAdminsCheckboxes(const std::vector<not_null<UserData*>> &admins, const FilterValue &filter); not_null<ChannelData*> _channel; QPointer<Ui::Checkbox> _allFlags; QMap<MTPDchannelAdminLogEventsFilter::Flags, QPointer<Ui::Checkbox>> _filterFlags; QPointer<Ui::Checkbox> _allUsers; QMap<not_null<UserData*>, QPointer<UserCheckbox>> _admins; bool _restoringInvariant = false; struct Row { object_ptr<TWidget> widget = { nullptr }; int marginTop = 0; }; std::vector<Row> _rows; base::lambda<void()> _changedCallback; }; FilterBox::Inner::Inner(QWidget *parent, not_null<ChannelData*> channel, const std::vector<not_null<UserData*>> &admins, const FilterValue &filter, base::lambda<void()> changedCallback) : TWidget(parent) , _channel(channel) , _changedCallback(std::move(changedCallback)) { createControls(admins, filter); } void FilterBox::Inner::createControls(const std::vector<not_null<UserData*>> &admins, const FilterValue &filter) { createAllActionsCheckbox(filter); createActionsCheckboxes(filter); createAllUsersCheckbox(filter); createAdminsCheckboxes(admins, filter); } void FilterBox::Inner::createAllActionsCheckbox(const FilterValue &filter) { auto checked = (filter.flags == 0); _allFlags = addRow(object_ptr<Ui::Checkbox>(this, lang(lng_admin_log_filter_all_actions), checked, st::adminLogFilterCheckbox), st::adminLogFilterCheckbox.margin.top()); subscribe(_allFlags->checkedChanged, [this](bool checked) { if (!std::exchange(_restoringInvariant, true)) { auto allChecked = _allFlags->checked(); for_const (auto &&checkbox, _filterFlags) { checkbox->setChecked(allChecked); } _restoringInvariant = false; if (_changedCallback) { _changedCallback(); } } }); } void FilterBox::Inner::createActionsCheckboxes(const FilterValue &filter) { using Flag = MTPDchannelAdminLogEventsFilter::Flag; using Flags = MTPDchannelAdminLogEventsFilter::Flags; auto addFlag = [this, &filter](Flags flag, QString &&text) { auto checked = (filter.flags == 0) || (filter.flags & flag); auto checkbox = addRow(object_ptr<Ui::Checkbox>(this, std::move(text), checked, st::defaultBoxCheckbox), st::adminLogFilterLittleSkip); _filterFlags.insert(flag, checkbox); subscribe(checkbox->checkedChanged, [this](bool checked) { if (!std::exchange(_restoringInvariant, true)) { auto allChecked = true; for_const (auto &&checkbox, _filterFlags) { if (!checkbox->checked()) { allChecked = false; break; } } _allFlags->setChecked(allChecked); _restoringInvariant = false; if (_changedCallback) { _changedCallback(); } } }); }; auto isGroup = _channel->isMegagroup(); if (isGroup) { addFlag(Flag::f_ban | Flag::f_unban | Flag::f_kick | Flag::f_unkick, lang(lng_admin_log_filter_restrictions)); } addFlag(Flag::f_promote | Flag::f_demote, lang(lng_admin_log_filter_admins_new)); addFlag(Flag::f_join | Flag::f_invite, lang(lng_admin_log_filter_members_new)); addFlag(Flag::f_info | Flag::f_settings, lang(_channel->isMegagroup() ? lng_admin_log_filter_info_group : lng_admin_log_filter_info_channel)); addFlag(Flag::f_delete, lang(lng_admin_log_filter_messages_deleted)); addFlag(Flag::f_edit, lang(lng_admin_log_filter_messages_edited)); if (isGroup) { addFlag(Flag::f_pinned, lang(lng_admin_log_filter_messages_pinned)); } addFlag(Flag::f_leave, lang(lng_admin_log_filter_members_removed)); } void FilterBox::Inner::createAllUsersCheckbox(const FilterValue &filter) { _allUsers = addRow(object_ptr<Ui::Checkbox>(this, lang(lng_admin_log_filter_all_admins), filter.allUsers, st::adminLogFilterCheckbox), st::adminLogFilterSkip); subscribe(_allUsers->checkedChanged, [this](bool checked) { if (checked && !std::exchange(_restoringInvariant, true)) { for_const (auto &&checkbox, _admins) { checkbox->setChecked(true); } _restoringInvariant = false; if (_changedCallback) { _changedCallback(); } } }); } void FilterBox::Inner::createAdminsCheckboxes(const std::vector<not_null<UserData*>> &admins, const FilterValue &filter) { for (auto user : admins) { auto checked = filter.allUsers || base::contains(filter.admins, user); auto checkbox = addRow(object_ptr<UserCheckbox>(this, user, checked), st::adminLogFilterLittleSkip); subscribe(checkbox->checkedChanged, [this](bool checked) { if (!std::exchange(_restoringInvariant, true)) { auto allChecked = true; for_const (auto &&checkbox, _admins) { if (!checkbox->checked()) { allChecked = false; break; } } if (!allChecked) { _allUsers->setChecked(allChecked); } _restoringInvariant = false; if (_changedCallback) { _changedCallback(); } } }); _admins.insert(user, checkbox); } } bool FilterBox::Inner::canSave() const { for (auto i = _filterFlags.cbegin(), e = _filterFlags.cend(); i != e; ++i) { if (i.value()->checked()) { return true; } } return false; } FilterValue FilterBox::Inner::filter() const { auto result = FilterValue(); auto allChecked = true; for (auto i = _filterFlags.cbegin(), e = _filterFlags.cend(); i != e; ++i) { if (i.value()->checked()) { result.flags |= i.key(); } else { allChecked = false; } } if (allChecked) { result.flags = 0; } result.allUsers = _allUsers->checked(); if (!result.allUsers) { result.admins.reserve(_admins.size()); for (auto i = _admins.cbegin(), e = _admins.cend(); i != e; ++i) { if (i.value()->checked()) { result.admins.push_back(i.key()); } } } return result; } int FilterBox::Inner::resizeGetHeight(int newWidth) { auto newHeight = 0; auto rowWidth = newWidth - st::boxPadding.left() - st::boxPadding.right(); for (auto &&row : _rows) { newHeight += row.marginTop; row.widget->resizeToNaturalWidth(rowWidth); newHeight += row.widget->heightNoMargins(); } return newHeight; } void FilterBox::Inner::resizeEvent(QResizeEvent *e) { auto top = 0; for (auto &&row : _rows) { top += row.marginTop; row.widget->moveToLeft(st::boxPadding.left(), top); top += row.widget->heightNoMargins(); } } FilterBox::FilterBox(QWidget*, not_null<ChannelData*> channel, const std::vector<not_null<UserData*>> &admins, const FilterValue &filter, base::lambda<void(FilterValue &&filter)> saveCallback) : BoxContent() , _channel(channel) , _admins(admins) , _initialFilter(filter) , _saveCallback(std::move(saveCallback)) { } void FilterBox::prepare() { setTitle(langFactory(lng_admin_log_filter_title)); _inner = setInnerWidget(object_ptr<Inner>(this, _channel, _admins, _initialFilter, [this] { refreshButtons(); })); _inner->resizeToWidth(st::boxWideWidth); refreshButtons(); setDimensions(st::boxWideWidth, qMin(_inner->height(), st::boxMaxListHeight)); } void FilterBox::refreshButtons() { clearButtons(); if (_inner->canSave()) { addButton(langFactory(lng_settings_save), [this] { if (_saveCallback) { _saveCallback(_inner->filter()); } }); } addButton(langFactory(lng_cancel), [this] { closeBox(); }); } void FilterBox::resizeToContent() { _inner->resizeToWidth(st::boxWideWidth); setDimensions(_inner->width(), _inner->height()); } } // namespace AdminLog �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/admin_log/history_admin_log_filter.h�������������������0000664�0000000�0000000�00000001636�13262451251�0030313�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "boxes/abstract_box.h" #include "history/admin_log/history_admin_log_section.h" namespace AdminLog { class FilterBox : public BoxContent { public: FilterBox(QWidget*, not_null<ChannelData*> channel, const std::vector<not_null<UserData*>> &admins, const FilterValue &filter, base::lambda<void(FilterValue &&filter)> saveCallback); protected: void prepare() override; private: void resizeToContent(); void refreshButtons(); not_null<ChannelData*> _channel; std::vector<not_null<UserData*>> _admins; FilterValue _initialFilter; base::lambda<void(FilterValue &&filter)> _saveCallback; class Inner; QPointer<Inner> _inner; }; } // namespace AdminLog ��������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp������������������0000664�0000000�0000000�00000152273�13262451251�0030500�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/admin_log/history_admin_log_inner.h" #include "styles/style_history.h" #include "history/history.h" #include "history/history_media_types.h" #include "history/history_message.h" #include "history/history_item_components.h" #include "history/history_item_text.h" #include "history/admin_log/history_admin_log_section.h" #include "history/admin_log/history_admin_log_filter.h" #include "history/view/history_view_message.h" #include "history/view/history_view_service_message.h" #include "history/view/history_view_cursor_state.h" #include "chat_helpers/message_field.h" #include "mainwindow.h" #include "mainwidget.h" #include "messenger.h" #include "apiwrap.h" #include "layout.h" #include "window/window_controller.h" #include "auth_session.h" #include "ui/widgets/popup_menu.h" #include "core/file_utilities.h" #include "core/tl_help.h" #include "base/overload.h" #include "lang/lang_keys.h" #include "boxes/edit_participant_box.h" #include "data/data_session.h" #include "data/data_media_types.h" namespace AdminLog { namespace { // If we require to support more admins we'll have to rewrite this anyway. constexpr auto kMaxChannelAdmins = 200; constexpr auto kScrollDateHideTimeout = 1000; constexpr auto kEventsFirstPage = 20; constexpr auto kEventsPerPage = 50; } // namespace template <InnerWidget::EnumItemsDirection direction, typename Method> void InnerWidget::enumerateItems(Method method) { constexpr auto TopToBottom = (direction == EnumItemsDirection::TopToBottom); // No displayed messages in this history. if (_items.empty()) { return; } if (_visibleBottom <= _itemsTop || _itemsTop + _itemsHeight <= _visibleTop) { return; } auto begin = std::rbegin(_items), end = std::rend(_items); auto from = TopToBottom ? std::lower_bound(begin, end, _visibleTop, [this](auto &elem, int top) { return this->itemTop(elem) + elem->height() <= top; }) : std::upper_bound(begin, end, _visibleBottom, [this](int bottom, auto &elem) { return this->itemTop(elem) + elem->height() >= bottom; }); auto wasEnd = (from == end); if (wasEnd) { --from; } if (TopToBottom) { Assert(itemTop(from->get()) + from->get()->height() > _visibleTop); } else { Assert(itemTop(from->get()) < _visibleBottom); } while (true) { auto item = from->get(); auto itemtop = itemTop(item); auto itembottom = itemtop + item->height(); // Binary search should've skipped all the items that are above / below the visible area. if (TopToBottom) { Assert(itembottom > _visibleTop); } else { Assert(itemtop < _visibleBottom); } if (!method(item, itemtop, itembottom)) { return; } // Skip all the items that are below / above the visible area. if (TopToBottom) { if (itembottom >= _visibleBottom) { return; } } else { if (itemtop <= _visibleTop) { return; } } if (TopToBottom) { if (++from == end) { break; } } else { if (from == begin) { break; } --from; } } } template <typename Method> void InnerWidget::enumerateUserpics(Method method) { // Find and remember the top of an attached messages pack // -1 means we didn't find an attached to next message yet. int lowestAttachedItemTop = -1; auto userpicCallback = [&](not_null<Element*> view, int itemtop, int itembottom) { // Skip all service messages. const auto message = view->data()->toHistoryMessage(); if (!message) return true; if (lowestAttachedItemTop < 0 && view->isAttachedToNext()) { lowestAttachedItemTop = itemtop + view->marginTop(); } // Call method on a userpic for all messages that have it and for those who are not showing it // because of their attachment to the next message if they are bottom-most visible. if (view->displayFromPhoto() || (view->hasFromPhoto() && itembottom >= _visibleBottom)) { if (lowestAttachedItemTop < 0) { lowestAttachedItemTop = itemtop + view->marginTop(); } // Attach userpic to the bottom of the visible area with the same margin as the last message. auto userpicMinBottomSkip = st::historyPaddingBottom + st::msgMargin.bottom(); auto userpicBottom = qMin(itembottom - view->marginBottom(), _visibleBottom - userpicMinBottomSkip); // Do not let the userpic go above the attached messages pack top line. userpicBottom = qMax(userpicBottom, lowestAttachedItemTop + st::msgPhotoSize); // Call the template callback function that was passed // and return if it finished everything it needed. if (!method(view, userpicBottom - st::msgPhotoSize)) { return false; } } // Forget the found top of the pack, search for the next one from scratch. if (!view->isAttachedToNext()) { lowestAttachedItemTop = -1; } return true; }; enumerateItems<EnumItemsDirection::TopToBottom>(userpicCallback); } template <typename Method> void InnerWidget::enumerateDates(Method method) { // Find and remember the bottom of an single-day messages pack // -1 means we didn't find a same-day with previous message yet. auto lowestInOneDayItemBottom = -1; auto dateCallback = [&](not_null<Element*> view, int itemtop, int itembottom) { const auto item = view->data(); if (lowestInOneDayItemBottom < 0 && view->isInOneDayWithPrevious()) { lowestInOneDayItemBottom = itembottom - view->marginBottom(); } // Call method on a date for all messages that have it and for those who are not showing it // because they are in a one day together with the previous message if they are top-most visible. if (view->displayDate() || (!item->isEmpty() && itemtop <= _visibleTop)) { if (lowestInOneDayItemBottom < 0) { lowestInOneDayItemBottom = itembottom - view->marginBottom(); } // Attach date to the top of the visible area with the same margin as it has in service message. auto dateTop = qMax(itemtop, _visibleTop) + st::msgServiceMargin.top(); // Do not let the date go below the single-day messages pack bottom line. auto dateHeight = st::msgServicePadding.bottom() + st::msgServiceFont->height + st::msgServicePadding.top(); dateTop = qMin(dateTop, lowestInOneDayItemBottom - dateHeight); // Call the template callback function that was passed // and return if it finished everything it needed. if (!method(view, itemtop, dateTop)) { return false; } } // Forget the found bottom of the pack, search for the next one from scratch. if (!view->isInOneDayWithPrevious()) { lowestInOneDayItemBottom = -1; } return true; }; enumerateItems<EnumItemsDirection::BottomToTop>(dateCallback); } InnerWidget::InnerWidget( QWidget *parent, not_null<Window::Controller*> controller, not_null<ChannelData*> channel) : RpWidget(parent) , _controller(controller) , _channel(channel) , _history(App::history(channel)) , _scrollDateCheck([this] { scrollDateCheck(); }) , _emptyText( st::historyAdminLogEmptyWidth - st::historyAdminLogEmptyPadding.left() - st::historyAdminLogEmptyPadding.left()) , _idManager(_history->adminLogIdManager()) { setMouseTracking(true); _scrollDateHideTimer.setCallback([this] { scrollDateHideByTimer(); }); Auth().data().viewRepaintRequest( ) | rpl::start_with_next([this](auto view) { if (view->delegate() == this) { repaintItem(view); } }, lifetime()); Auth().data().viewResizeRequest( ) | rpl::start_with_next([this](auto view) { if (view->delegate() == this) { resizeItem(view); } }, lifetime()); Auth().data().itemViewRefreshRequest( ) | rpl::start_with_next([this](auto item) { if (const auto view = viewForItem(item)) { refreshItem(view); } }, lifetime()); Auth().data().viewLayoutChanged( ) | rpl::start_with_next([this](auto view) { if (view->delegate() == this) { if (view->isUnderCursor()) { updateSelected(); } } }, lifetime()); Auth().data().animationPlayInlineRequest( ) | rpl::start_with_next([this](auto item) { if (const auto view = viewForItem(item)) { if (const auto media = view->media()) { media->playAnimation(); } } }, lifetime()); subscribe(Auth().data().queryItemVisibility(), [this](const Data::Session::ItemVisibilityQuery &query) { if (_history != query.item->history() || !query.item->isLogEntry() || !isVisible()) { return; } if (const auto view = viewForItem(query.item)) { auto top = itemTop(view); if (top >= 0 && top + view->height() > _visibleTop && top < _visibleBottom) { *query.isVisible = true; } } }); updateEmptyText(); requestAdmins(); } void InnerWidget::visibleTopBottomUpdated( int visibleTop, int visibleBottom) { auto scrolledUp = (visibleTop < _visibleTop); _visibleTop = visibleTop; _visibleBottom = visibleBottom; updateVisibleTopItem(); checkPreloadMore(); if (scrolledUp) { _scrollDateCheck.call(); } else { scrollDateHideByTimer(); } _controller->floatPlayerAreaUpdated().notify(true); } void InnerWidget::updateVisibleTopItem() { if (_visibleBottom == height()) { _visibleTopItem = nullptr; } else { auto begin = std::rbegin(_items), end = std::rend(_items); auto from = std::lower_bound(begin, end, _visibleTop, [this](auto &&elem, int top) { return this->itemTop(elem) + elem->height() <= top; }); if (from != end) { _visibleTopItem = *from; _visibleTopFromItem = _visibleTop - _visibleTopItem->y(); } else { _visibleTopItem = nullptr; _visibleTopFromItem = _visibleTop; } } } bool InnerWidget::displayScrollDate() const { return (_visibleTop <= height() - 2 * (_visibleBottom - _visibleTop)); } void InnerWidget::scrollDateCheck() { if (!_visibleTopItem) { _scrollDateLastItem = nullptr; _scrollDateLastItemTop = 0; scrollDateHide(); } else if (_visibleTopItem != _scrollDateLastItem || _visibleTopFromItem != _scrollDateLastItemTop) { // Show scroll date only if it is not the initial onScroll() event (with empty _scrollDateLastItem). if (_scrollDateLastItem && !_scrollDateShown) { toggleScrollDateShown(); } _scrollDateLastItem = _visibleTopItem; _scrollDateLastItemTop = _visibleTopFromItem; _scrollDateHideTimer.callOnce(kScrollDateHideTimeout); } } void InnerWidget::scrollDateHideByTimer() { _scrollDateHideTimer.cancel(); scrollDateHide(); } void InnerWidget::scrollDateHide() { if (_scrollDateShown) { toggleScrollDateShown(); } } void InnerWidget::toggleScrollDateShown() { _scrollDateShown = !_scrollDateShown; auto from = _scrollDateShown ? 0. : 1.; auto to = _scrollDateShown ? 1. : 0.; _scrollDateOpacity.start([this] { repaintScrollDateCallback(); }, from, to, st::historyDateFadeDuration); } void InnerWidget::repaintScrollDateCallback() { auto updateTop = _visibleTop; auto updateHeight = st::msgServiceMargin.top() + st::msgServicePadding.top() + st::msgServiceFont->height + st::msgServicePadding.bottom(); update(0, updateTop, width(), updateHeight); } void InnerWidget::checkPreloadMore() { if (_visibleTop + PreloadHeightsCount * (_visibleBottom - _visibleTop) > height()) { preloadMore(Direction::Down); } if (_visibleTop < PreloadHeightsCount * (_visibleBottom - _visibleTop)) { preloadMore(Direction::Up); } } void InnerWidget::applyFilter(FilterValue &&value) { if (_filter != value) { _filter = value; clearAndRequestLog(); } } void InnerWidget::applySearch(const QString &query) { auto clearQuery = query.trimmed(); if (_searchQuery != query) { _searchQuery = query; clearAndRequestLog(); } } void InnerWidget::requestAdmins() { auto participantsHash = 0; request(MTPchannels_GetParticipants( _channel->inputChannel, MTP_channelParticipantsAdmins(), MTP_int(0), MTP_int(kMaxChannelAdmins), MTP_int(participantsHash) )).done([this](const MTPchannels_ChannelParticipants &result) { auto readCanEdit = base::overload([](const MTPDchannelParticipantAdmin &v) { return v.is_can_edit(); }, [](auto &&) { return false; }); Auth().api().parseChannelParticipants(_channel, result, [&]( int availableCount, const QVector<MTPChannelParticipant> &list) { auto filtered = ( list ) | ranges::view::transform([&](const MTPChannelParticipant &p) { return std::make_pair( TLHelp::ReadChannelParticipantUserId(p), TLHelp::VisitChannelParticipant(p, readCanEdit)); }) | ranges::view::transform([&](auto &&pair) { return std::make_pair( App::userLoaded(pair.first), pair.second); }) | ranges::view::filter([&](auto &&pair) { return (pair.first != nullptr); }); for (auto [user, canEdit] : filtered) { _admins.push_back(user); if (canEdit) { _adminsCanEdit.push_back(user); } } }); if (_admins.empty()) { _admins.push_back(App::self()); } if (_showFilterCallback) { showFilter(std::move(_showFilterCallback)); } }).send(); } void InnerWidget::showFilter(base::lambda<void(FilterValue &&filter)> callback) { if (_admins.empty()) { _showFilterCallback = std::move(callback); } else { Ui::show(Box<FilterBox>(_channel, _admins, _filter, std::move(callback))); } } void InnerWidget::clearAndRequestLog() { request(base::take(_preloadUpRequestId)).cancel(); request(base::take(_preloadDownRequestId)).cancel(); _filterChanged = true; _upLoaded = false; _downLoaded = true; updateMinMaxIds(); preloadMore(Direction::Up); } void InnerWidget::updateEmptyText() { auto options = _defaultOptions; options.flags |= TextParseMarkdown; auto hasSearch = !_searchQuery.isEmpty(); auto hasFilter = (_filter.flags != 0) || !_filter.allUsers; auto text = TextWithEntities { lang((hasSearch || hasFilter) ? lng_admin_log_no_results_title : lng_admin_log_no_events_title) }; text.entities.append(EntityInText(EntityInTextBold, 0, text.text.size())); auto description = hasSearch ? lng_admin_log_no_results_search_text(lt_query, TextUtilities::Clean(_searchQuery)) : lang(hasFilter ? lng_admin_log_no_results_text : lng_admin_log_no_events_text); text.text.append(qstr("\n\n") + description); _emptyText.setMarkedText(st::defaultTextStyle, text, options); } QString InnerWidget::tooltipText() const { if (_mouseCursorState == CursorState::Date && _mouseAction == MouseAction::None) { if (const auto view = App::hoveredItem()) { auto dateText = view->dateTime().toString( QLocale::system().dateTimeFormat(QLocale::LongFormat)); return dateText; } } else if (_mouseCursorState == CursorState::Forwarded && _mouseAction == MouseAction::None) { if (const auto view = App::hoveredItem()) { if (const auto forwarded = view->data()->Get<HistoryMessageForwarded>()) { return forwarded->text.originalText(AllTextSelection, ExpandLinksNone); } } } else if (const auto lnk = ClickHandler::getActive()) { return lnk->tooltip(); } return QString(); } QPoint InnerWidget::tooltipPos() const { return _mousePosition; } HistoryView::Context InnerWidget::elementContext() { return HistoryView::Context::AdminLog; } std::unique_ptr<HistoryView::Element> InnerWidget::elementCreate( not_null<HistoryMessage*> message) { return std::make_unique<HistoryView::Message>(this, message); } std::unique_ptr<HistoryView::Element> InnerWidget::elementCreate( not_null<HistoryService*> message) { return std::make_unique<HistoryView::Service>(this, message); } bool InnerWidget::elementUnderCursor( not_null<const HistoryView::Element*> view) { return (App::hoveredItem() == view); } void InnerWidget::elementAnimationAutoplayAsync( not_null<const HistoryView::Element*> view) { crl::on_main(this, [this, msgId = view->data()->fullId()] { if (const auto item = App::histItemById(msgId)) { if (const auto view = viewForItem(item)) { if (const auto media = view->media()) { media->autoplayAnimation(); } } } }); } TimeMs InnerWidget::elementHighlightTime( not_null<const HistoryView::Element*> element) { return TimeMs(0); } bool InnerWidget::elementInSelectionMode() { return false; } void InnerWidget::saveState(not_null<SectionMemento*> memento) { memento->setFilter(std::move(_filter)); memento->setAdmins(std::move(_admins)); memento->setAdminsCanEdit(std::move(_adminsCanEdit)); memento->setSearchQuery(std::move(_searchQuery)); if (!_filterChanged) { memento->setItems(std::move(_items), std::move(_itemsByIds), _upLoaded, _downLoaded); memento->setIdManager(std::move(_idManager)); } _upLoaded = _downLoaded = true; // Don't load or handle anything anymore. } void InnerWidget::restoreState(not_null<SectionMemento*> memento) { _items = memento->takeItems(); for (auto &item : _items) { item.refreshView(this); } _itemsByIds = memento->takeItemsByIds(); if (auto manager = memento->takeIdManager()) { _idManager = std::move(manager); } _admins = memento->takeAdmins(); _adminsCanEdit = memento->takeAdminsCanEdit(); _filter = memento->takeFilter(); _searchQuery = memento->takeSearchQuery(); _upLoaded = memento->upLoaded(); _downLoaded = memento->downLoaded(); _filterChanged = false; updateMinMaxIds(); updateSize(); } void InnerWidget::preloadMore(Direction direction) { auto &requestId = (direction == Direction::Up) ? _preloadUpRequestId : _preloadDownRequestId; auto &loadedFlag = (direction == Direction::Up) ? _upLoaded : _downLoaded; if (requestId != 0 || loadedFlag) { return; } auto flags = MTPchannels_GetAdminLog::Flags(0); auto filter = MTP_channelAdminLogEventsFilter(MTP_flags(_filter.flags)); if (_filter.flags != 0) { flags |= MTPchannels_GetAdminLog::Flag::f_events_filter; } auto admins = QVector<MTPInputUser>(0); if (!_filter.allUsers) { if (!_filter.admins.empty()) { admins.reserve(_filter.admins.size()); for (auto &admin : _filter.admins) { admins.push_back(admin->inputUser); } } flags |= MTPchannels_GetAdminLog::Flag::f_admins; } auto maxId = (direction == Direction::Up) ? _minId : 0; auto minId = (direction == Direction::Up) ? 0 : _maxId; auto perPage = _items.empty() ? kEventsFirstPage : kEventsPerPage; requestId = request(MTPchannels_GetAdminLog(MTP_flags(flags), _channel->inputChannel, MTP_string(_searchQuery), filter, MTP_vector<MTPInputUser>(admins), MTP_long(maxId), MTP_long(minId), MTP_int(perPage))).done([this, &requestId, &loadedFlag, direction](const MTPchannels_AdminLogResults &result) { Expects(result.type() == mtpc_channels_adminLogResults); requestId = 0; auto &results = result.c_channels_adminLogResults(); App::feedUsers(results.vusers); App::feedChats(results.vchats); if (!loadedFlag) { addEvents(direction, results.vevents.v); } }).fail([this, &requestId, &loadedFlag](const RPCError &error) { requestId = 0; loadedFlag = true; update(); }).send(); } void InnerWidget::addEvents(Direction direction, const QVector<MTPChannelAdminLogEvent> &events) { if (_filterChanged) { clearAfterFilterChange(); } auto up = (direction == Direction::Up); if (events.empty()) { (up ? _upLoaded : _downLoaded) = true; update(); return; } // When loading items up we just add them to the back of the _items vector. // When loading items down we add them to a new vector and copy _items after them. auto newItemsForDownDirection = std::vector<OwnedItem>(); auto oldItemsCount = _items.size(); auto &addToItems = (direction == Direction::Up) ? _items : newItemsForDownDirection; addToItems.reserve(oldItemsCount + events.size() * 2); for_const (auto &event, events) { Assert(event.type() == mtpc_channelAdminLogEvent); const auto &data = event.c_channelAdminLogEvent(); const auto id = data.vid.v; if (_itemsByIds.find(id) != _itemsByIds.cend()) { continue; } auto count = 0; const auto addOne = [&](OwnedItem item) { _itemsByIds.emplace(id, item.get()); _itemsByData.emplace(item->data(), item.get()); addToItems.push_back(std::move(item)); ++count; }; GenerateItems( this, _history, _idManager.get(), data, addOne); if (count > 1) { // Reverse the inner order of the added messages, because we load events // from bottom to top but inside one event they go from top to bottom. auto full = addToItems.size(); auto from = full - count; for (auto i = 0, toReverse = count / 2; i != toReverse; ++i) { std::swap(addToItems[from + i], addToItems[full - i - 1]); } } } auto newItemsCount = _items.size() + ((direction == Direction::Up) ? 0 : newItemsForDownDirection.size()); if (newItemsCount != oldItemsCount) { if (direction == Direction::Down) { for (auto &item : _items) { newItemsForDownDirection.push_back(std::move(item)); } _items = std::move(newItemsForDownDirection); } updateMinMaxIds(); itemsAdded(direction, newItemsCount - oldItemsCount); } update(); } void InnerWidget::updateMinMaxIds() { if (_itemsByIds.empty() || _filterChanged) { _maxId = _minId = 0; } else { _maxId = (--_itemsByIds.end())->first; _minId = _itemsByIds.begin()->first; if (_minId == 1) { _upLoaded = true; } } } void InnerWidget::itemsAdded(Direction direction, int addedCount) { Expects(addedCount >= 0); auto checkFrom = (direction == Direction::Up) ? (_items.size() - addedCount) : 1; // Should be ": 0", but zero is skipped anyway. auto checkTo = (direction == Direction::Up) ? (_items.size() + 1) : (addedCount + 1); for (auto i = checkFrom; i != checkTo; ++i) { if (i > 0) { const auto view = _items[i - 1].get(); if (i < _items.size()) { const auto previous = _items[i].get(); view->setDisplayDate(view->dateTime().date() != previous->dateTime().date()); const auto attach = view->computeIsAttachToPrevious(previous); view->setAttachToPrevious(attach); previous->setAttachToNext(attach); } else { view->setDisplayDate(true); } } } updateSize(); } void InnerWidget::updateSize() { TWidget::resizeToWidth(width()); restoreScrollPosition(); updateVisibleTopItem(); checkPreloadMore(); } int InnerWidget::resizeGetHeight(int newWidth) { update(); const auto resizeAllItems = (_itemsWidth != newWidth); auto newHeight = 0; for (auto &item : base::reversed(_items)) { item->setY(newHeight); if (item->pendingResize() || resizeAllItems) { newHeight += item->resizeGetHeight(newWidth); } else { newHeight += item->height(); } } _itemsWidth = newWidth; _itemsHeight = newHeight; _itemsTop = (_minHeight > _itemsHeight + st::historyPaddingBottom) ? (_minHeight - _itemsHeight - st::historyPaddingBottom) : 0; return _itemsTop + _itemsHeight + st::historyPaddingBottom; } void InnerWidget::restoreScrollPosition() { auto newVisibleTop = _visibleTopItem ? (itemTop(_visibleTopItem) + _visibleTopFromItem) : ScrollMax; scrollToSignal.notify(newVisibleTop, true); } void InnerWidget::paintEvent(QPaintEvent *e) { if (Ui::skipPaintEvent(this, e)) { return; } Painter p(this); auto ms = getms(); auto clip = e->rect(); if (_items.empty() && _upLoaded && _downLoaded) { paintEmpty(p); } else { auto begin = std::rbegin(_items), end = std::rend(_items); auto from = std::lower_bound(begin, end, clip.top(), [this](auto &elem, int top) { return this->itemTop(elem) + elem->height() <= top; }); auto to = std::lower_bound(begin, end, clip.top() + clip.height(), [this](auto &elem, int bottom) { return this->itemTop(elem) < bottom; }); if (from != end) { auto top = itemTop(from->get()); p.translate(0, top); for (auto i = from; i != to; ++i) { const auto view = i->get(); const auto selection = (view == _selectedItem) ? _selectedText : TextSelection(); view->draw(p, clip.translated(0, -top), selection, ms); const auto item = view->data(); auto height = view->height(); top += height; p.translate(0, height); } p.translate(0, -top); enumerateUserpics([&](not_null<Element*> view, int userpicTop) { // stop the enumeration if the userpic is below the painted rect if (userpicTop >= clip.top() + clip.height()) { return false; } // paint the userpic if it intersects the painted rect if (userpicTop + st::msgPhotoSize > clip.top()) { const auto message = view->data()->toHistoryMessage(); Assert(message != nullptr); message->from()->paintUserpicLeft(p, st::historyPhotoLeft, userpicTop, view->width(), st::msgPhotoSize); } return true; }); auto dateHeight = st::msgServicePadding.bottom() + st::msgServiceFont->height + st::msgServicePadding.top(); auto scrollDateOpacity = _scrollDateOpacity.current(ms, _scrollDateShown ? 1. : 0.); enumerateDates([&](not_null<Element*> view, int itemtop, int dateTop) { // stop the enumeration if the date is above the painted rect if (dateTop + dateHeight <= clip.top()) { return false; } const auto displayDate = view->displayDate(); auto dateInPlace = displayDate; if (dateInPlace) { const auto correctDateTop = itemtop + st::msgServiceMargin.top(); dateInPlace = (dateTop < correctDateTop + dateHeight); } //bool noFloatingDate = (item->date.date() == lastDate && displayDate); //if (noFloatingDate) { // if (itemtop < showFloatingBefore) { // noFloatingDate = false; // } //} // paint the date if it intersects the painted rect if (dateTop < clip.top() + clip.height()) { auto opacity = (dateInPlace/* || noFloatingDate*/) ? 1. : scrollDateOpacity; if (opacity > 0.) { p.setOpacity(opacity); const auto dateY = /*noFloatingDate ? itemtop :*/ (dateTop - st::msgServiceMargin.top()); const auto width = view->width(); if (const auto date = view->Get<HistoryView::DateBadge>()) { date->paint(p, dateY, width); } else { HistoryView::ServiceMessagePainter::paintDate( p, view->dateTime(), dateY, width); } } } return true; }); } } } void InnerWidget::clearAfterFilterChange() { _visibleTopItem = nullptr; _visibleTopFromItem = 0; _scrollDateLastItem = nullptr; _scrollDateLastItemTop = 0; _mouseActionItem = nullptr; _selectedItem = nullptr; _selectedText = TextSelection(); _filterChanged = false; _items.clear(); _itemsByIds.clear(); _idManager = nullptr; _idManager = _history->adminLogIdManager(); updateEmptyText(); updateSize(); } auto InnerWidget::viewForItem(const HistoryItem *item) -> Element* { if (item) { const auto i = _itemsByData.find(item); if (i != _itemsByData.end()) { return i->second; } } return nullptr; } void InnerWidget::paintEmpty(Painter &p) { style::font font(st::msgServiceFont); auto rectWidth = st::historyAdminLogEmptyWidth; auto innerWidth = rectWidth - st::historyAdminLogEmptyPadding.left() - st::historyAdminLogEmptyPadding.right(); auto rectHeight = st::historyAdminLogEmptyPadding.top() + _emptyText.countHeight(innerWidth) + st::historyAdminLogEmptyPadding.bottom(); auto rect = QRect((width() - rectWidth) / 2, (height() - rectHeight) / 3, rectWidth, rectHeight); HistoryView::ServiceMessagePainter::paintBubble( p, rect.x(), rect.y(), rect.width(), rect.height()); p.setPen(st::msgServiceFg); _emptyText.draw(p, rect.x() + st::historyAdminLogEmptyPadding.left(), rect.y() + st::historyAdminLogEmptyPadding.top(), innerWidth, style::al_top); } TextWithEntities InnerWidget::getSelectedText() const { return _selectedItem ? _selectedItem->selectedText(_selectedText) : TextWithEntities(); } void InnerWidget::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Escape || e->key() == Qt::Key_Back) { cancelledSignal.notify(true); } else if (e == QKeySequence::Copy && _selectedItem != nullptr) { copySelectedText(); #ifdef Q_OS_MAC } else if (e->key() == Qt::Key_E && e->modifiers().testFlag(Qt::ControlModifier)) { SetClipboardWithEntities(getSelectedText(), QClipboard::FindBuffer); #endif // Q_OS_MAC } else { e->ignore(); } } void InnerWidget::mouseDoubleClickEvent(QMouseEvent *e) { mouseActionStart(e->globalPos(), e->button()); if (((_mouseAction == MouseAction::Selecting && _selectedItem != nullptr) || (_mouseAction == MouseAction::None)) && _mouseSelectType == TextSelectType::Letters && _mouseActionItem) { StateRequest request; request.flags |= Text::StateRequest::Flag::LookupSymbol; auto dragState = _mouseActionItem->textState(_dragStartPosition, request); if (dragState.cursor == CursorState::Text) { _mouseTextSymbol = dragState.symbol; _mouseSelectType = TextSelectType::Words; if (_mouseAction == MouseAction::None) { _mouseAction = MouseAction::Selecting; auto selection = TextSelection { dragState.symbol, dragState.symbol }; repaintItem(std::exchange(_selectedItem, _mouseActionItem)); _selectedText = selection; } mouseMoveEvent(e); _trippleClickPoint = e->globalPos(); _trippleClickTimer.callOnce(QApplication::doubleClickInterval()); } } } void InnerWidget::contextMenuEvent(QContextMenuEvent *e) { showContextMenu(e); } void InnerWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { if (e->reason() == QContextMenuEvent::Mouse) { mouseActionUpdate(e->globalPos()); } // -1 - has selection, but no over, 0 - no selection, 1 - over text auto isUponSelected = 0; auto hasSelected = 0; if (_selectedItem) { isUponSelected = -1; auto selFrom = _selectedText.from; auto selTo = _selectedText.to; hasSelected = (selTo > selFrom) ? 1 : 0; if (App::mousedItem() && App::mousedItem() == App::hoveredItem()) { auto mousePos = mapPointToItem( mapFromGlobal(_mousePosition), App::mousedItem()); StateRequest request; request.flags |= Text::StateRequest::Flag::LookupSymbol; auto dragState = App::mousedItem()->textState(mousePos, request); if (dragState.cursor == CursorState::Text && base::in_range(dragState.symbol, selFrom, selTo)) { isUponSelected = 1; } } } if (showFromTouch && hasSelected && isUponSelected < hasSelected) { isUponSelected = hasSelected; } _menu = base::make_unique_q<Ui::PopupMenu>(nullptr); const auto link = ClickHandler::getActive(); auto view = App::hoveredItem() ? App::hoveredItem() : App::hoveredLinkItem(); auto lnkPhoto = dynamic_cast<PhotoClickHandler*>(link.get()); auto lnkDocument = dynamic_cast<DocumentClickHandler*>(link.get()); auto lnkPeer = dynamic_cast<PeerClickHandler*>(link.get()); auto lnkIsVideo = lnkDocument ? lnkDocument->document()->isVideoFile() : false; auto lnkIsVoice = lnkDocument ? lnkDocument->document()->isVoiceMessage() : false; auto lnkIsAudio = lnkDocument ? lnkDocument->document()->isAudioFile() : false; if (lnkPhoto || lnkDocument) { if (isUponSelected > 0) { _menu->addAction(lang(lng_context_copy_selected), [=] { copySelectedText(); }); } if (lnkPhoto) { const auto photo = lnkPhoto->photo(); _menu->addAction(lang(lng_context_save_image), App::LambdaDelayed(st::defaultDropdownMenu.menu.ripple.hideDuration, this, [=] { savePhotoToFile(photo); })); _menu->addAction(lang(lng_context_copy_image), [=] { copyContextImage(photo); }); } else { auto document = lnkDocument->document(); if (document->loading()) { _menu->addAction(lang(lng_context_cancel_download), [=] { cancelContextDownload(document); }); } else { if (document->loaded() && document->isGifv()) { if (!cAutoPlayGif()) { const auto itemId = view ? view->data()->fullId() : FullMsgId(); _menu->addAction(lang(lng_context_open_gif), [=] { openContextGif(itemId); }); } } if (!document->filepath(DocumentData::FilePathResolveChecked).isEmpty()) { _menu->addAction(lang((cPlatform() == dbipMac || cPlatform() == dbipMacOld) ? lng_context_show_in_finder : lng_context_show_in_folder), [=] { showContextInFolder(document); }); } _menu->addAction(lang(lnkIsVideo ? lng_context_save_video : (lnkIsVoice ? lng_context_save_audio : (lnkIsAudio ? lng_context_save_audio_file : lng_context_save_file))), App::LambdaDelayed(st::defaultDropdownMenu.menu.ripple.hideDuration, this, [this, document] { saveDocumentToFile(document); })); } } } else if (lnkPeer) { // suggest to block if (auto user = lnkPeer->peer()->asUser()) { suggestRestrictUser(user); } } else { // maybe cursor on some text history item? const auto item = view ? view->data().get() : nullptr; const auto itemId = item ? item->fullId() : FullMsgId(); bool canDelete = item && item->canDelete() && (item->id > 0 || !item->serviceMsg()); bool canForward = item && item->allowsForward(); auto msg = dynamic_cast<HistoryMessage*>(item); if (isUponSelected > 0) { _menu->addAction(lang(lng_context_copy_selected), [this] { copySelectedText(); }); } else { if (item && !isUponSelected) { auto mediaHasTextForCopy = false; if (auto media = view->media()) { mediaHasTextForCopy = media->hasTextForCopy(); if (media->type() == MediaTypeWebPage && static_cast<HistoryWebPage*>(media)->attach()) { media = static_cast<HistoryWebPage*>(media)->attach(); } if (media->type() == MediaTypeSticker) { if (const auto document = media->getDocument()) { if (document->sticker() && document->sticker()->set.type() != mtpc_inputStickerSetEmpty) { _menu->addAction(lang(document->isStickerSetInstalled() ? lng_context_pack_info : lng_context_pack_add), [=] { showStickerPackInfo(document); }); } _menu->addAction(lang(lng_context_save_image), App::LambdaDelayed(st::defaultDropdownMenu.menu.ripple.hideDuration, this, [this, document] { saveDocumentToFile(document); })); } } else if (media->type() == MediaTypeGif && !link) { if (auto document = media->getDocument()) { if (document->loading()) { _menu->addAction(lang(lng_context_cancel_download), [=] { cancelContextDownload(document); }); } else { if (document->isGifv()) { if (!cAutoPlayGif()) { _menu->addAction(lang(lng_context_open_gif), [=] { openContextGif(itemId); }); } } if (!document->filepath(DocumentData::FilePathResolveChecked).isEmpty()) { _menu->addAction(lang((cPlatform() == dbipMac || cPlatform() == dbipMacOld) ? lng_context_show_in_finder : lng_context_show_in_folder), [=] { showContextInFolder(document); }); } _menu->addAction(lang(lng_context_save_file), App::LambdaDelayed(st::defaultDropdownMenu.menu.ripple.hideDuration, this, [this, document] { saveDocumentToFile(document); })); } } } } if (msg && !link && (view->hasVisibleText() || mediaHasTextForCopy)) { _menu->addAction(lang(lng_context_copy_text), [=] { copyContextText(itemId); }); } } } const auto actionText = link ? link->copyToClipboardContextItemText() : QString(); if (!actionText.isEmpty()) { _menu->addAction( actionText, [text = link->copyToClipboardText()] { QApplication::clipboard()->setText(text); }); } } if (_menu->actions().isEmpty()) { _menu = nullptr; } else { _menu->popup(e->globalPos()); e->accept(); } } void InnerWidget::savePhotoToFile(PhotoData *photo) { if (!photo || !photo->date || !photo->loaded()) return; auto filter = qsl("JPEG Image (*.jpg);;") + FileDialog::AllFilesFilter(); FileDialog::GetWritePath( lang(lng_save_photo), filter, filedialogDefaultName(qsl("photo"), qsl(".jpg")), base::lambda_guarded(this, [=](const QString &result) { if (!result.isEmpty()) { photo->full->pix().toImage().save(result, "JPG"); } })); } void InnerWidget::saveDocumentToFile(DocumentData *document) { DocumentSaveClickHandler::doSave(document, true); } void InnerWidget::copyContextImage(PhotoData *photo) { if (!photo || !photo->date || !photo->loaded()) return; QApplication::clipboard()->setPixmap(photo->full->pix()); } void InnerWidget::copySelectedText() { SetClipboardWithEntities(getSelectedText()); } void InnerWidget::showStickerPackInfo(not_null<DocumentData*> document) { if (auto sticker = document->sticker()) { if (sticker->set.type() != mtpc_inputStickerSetEmpty) { App::main()->stickersBox(sticker->set); } } } void InnerWidget::cancelContextDownload(not_null<DocumentData*> document) { document->cancel(); } void InnerWidget::showContextInFolder(not_null<DocumentData*> document) { const auto filepath = document->filepath( DocumentData::FilePathResolveChecked); if (!filepath.isEmpty()) { File::ShowInFolder(filepath); } } void InnerWidget::openContextGif(FullMsgId itemId) { if (const auto item = App::histItemById(itemId)) { if (auto media = item->media()) { if (auto document = media->document()) { Messenger::Instance().showDocument(document, item); } } } } void InnerWidget::copyContextText(FullMsgId itemId) { if (const auto item = App::histItemById(itemId)) { SetClipboardWithEntities(HistoryItemText(item)); } } void InnerWidget::suggestRestrictUser(not_null<UserData*> user) { Expects(_menu != nullptr); if (!_channel->isMegagroup() || !_channel->canBanMembers() || _admins.empty()) { return; } if (base::contains(_admins, user)) { if (!base::contains(_adminsCanEdit, user)) { return; } } _menu->addAction(lang(lng_context_restrict_user), [=] { auto editRestrictions = [=](bool hasAdminRights, const MTPChannelBannedRights ¤tRights) { auto weak = QPointer<InnerWidget>(this); auto weakBox = std::make_shared<QPointer<EditRestrictedBox>>(); auto box = Box<EditRestrictedBox>(_channel, user, hasAdminRights, currentRights); box->setSaveCallback([user, weak, weakBox](const MTPChannelBannedRights &oldRights, const MTPChannelBannedRights &newRights) { if (weak) { weak->restrictUser(user, oldRights, newRights); } if (*weakBox) { (*weakBox)->closeBox(); } }); *weakBox = Ui::show( std::move(box), LayerOption::KeepOther); }; if (base::contains(_admins, user)) { editRestrictions(true, MTP_channelBannedRights(MTP_flags(0), MTP_int(0))); } else { request(MTPchannels_GetParticipant(_channel->inputChannel, user->inputUser)).done([=](const MTPchannels_ChannelParticipant &result) { Expects(result.type() == mtpc_channels_channelParticipant); auto &participant = result.c_channels_channelParticipant(); App::feedUsers(participant.vusers); auto type = participant.vparticipant.type(); if (type == mtpc_channelParticipantBanned) { auto &banned = participant.vparticipant.c_channelParticipantBanned(); editRestrictions(false, banned.vbanned_rights); } else { auto hasAdminRights = (type == mtpc_channelParticipantAdmin) || (type == mtpc_channelParticipantCreator); auto bannedRights = MTP_channelBannedRights( MTP_flags(0), MTP_int(0)); editRestrictions(hasAdminRights, bannedRights); } }).fail([=](const RPCError &error) { auto bannedRights = MTP_channelBannedRights( MTP_flags(0), MTP_int(0)); editRestrictions(false, bannedRights); }).send(); } }); } void InnerWidget::restrictUser(not_null<UserData*> user, const MTPChannelBannedRights &oldRights, const MTPChannelBannedRights &newRights) { auto weak = QPointer<InnerWidget>(this); MTP::send(MTPchannels_EditBanned(_channel->inputChannel, user->inputUser, newRights), rpcDone([megagroup = _channel.get(), user, weak, oldRights, newRights](const MTPUpdates &result) { Auth().api().applyUpdates(result); megagroup->applyEditBanned(user, oldRights, newRights); if (weak) { weak->restrictUserDone(user, newRights); } })); } void InnerWidget::restrictUserDone(not_null<UserData*> user, const MTPChannelBannedRights &rights) { Expects(rights.type() == mtpc_channelBannedRights); if (rights.c_channelBannedRights().vflags.v) { _admins.erase(std::remove(_admins.begin(), _admins.end(), user), _admins.end()); _adminsCanEdit.erase(std::remove(_adminsCanEdit.begin(), _adminsCanEdit.end(), user), _adminsCanEdit.end()); } _downLoaded = false; checkPreloadMore(); } void InnerWidget::mousePressEvent(QMouseEvent *e) { if (_menu) { e->accept(); return; // ignore mouse press, that was hiding context menu } mouseActionStart(e->globalPos(), e->button()); } void InnerWidget::mouseMoveEvent(QMouseEvent *e) { auto buttonsPressed = (e->buttons() & (Qt::LeftButton | Qt::MiddleButton)); if (!buttonsPressed && _mouseAction != MouseAction::None) { mouseReleaseEvent(e); } mouseActionUpdate(e->globalPos()); } void InnerWidget::mouseReleaseEvent(QMouseEvent *e) { mouseActionFinish(e->globalPos(), e->button()); if (!rect().contains(e->pos())) { leaveEvent(e); } } void InnerWidget::enterEventHook(QEvent *e) { mouseActionUpdate(QCursor::pos()); return TWidget::enterEventHook(e); } void InnerWidget::leaveEventHook(QEvent *e) { if (const auto view = App::hoveredItem()) { repaintItem(view); App::hoveredItem(nullptr); } ClickHandler::clearActive(); Ui::Tooltip::Hide(); if (!ClickHandler::getPressed() && _cursor != style::cur_default) { _cursor = style::cur_default; setCursor(_cursor); } return TWidget::leaveEventHook(e); } void InnerWidget::mouseActionStart(const QPoint &screenPos, Qt::MouseButton button) { mouseActionUpdate(screenPos); if (button != Qt::LeftButton) return; ClickHandler::pressed(); if (App::pressedItem() != App::hoveredItem()) { repaintItem(App::pressedItem()); App::pressedItem(App::hoveredItem()); repaintItem(App::pressedItem()); } _mouseAction = MouseAction::None; _mouseActionItem = App::mousedItem(); _dragStartPosition = mapPointToItem( mapFromGlobal(screenPos), _mouseActionItem); _pressWasInactive = _controller->window()->wasInactivePress(); if (_pressWasInactive) _controller->window()->setInactivePress(false); if (ClickHandler::getPressed()) { _mouseAction = MouseAction::PrepareDrag; } if (_mouseAction == MouseAction::None && _mouseActionItem) { TextState dragState; if (_trippleClickTimer.isActive() && (screenPos - _trippleClickPoint).manhattanLength() < QApplication::startDragDistance()) { StateRequest request; request.flags = Text::StateRequest::Flag::LookupSymbol; dragState = _mouseActionItem->textState(_dragStartPosition, request); if (dragState.cursor == CursorState::Text) { auto selection = TextSelection { dragState.symbol, dragState.symbol }; repaintItem(std::exchange(_selectedItem, _mouseActionItem)); _selectedText = selection; _mouseTextSymbol = dragState.symbol; _mouseAction = MouseAction::Selecting; _mouseSelectType = TextSelectType::Paragraphs; mouseActionUpdate(_mousePosition); _trippleClickTimer.callOnce(QApplication::doubleClickInterval()); } } else if (App::pressedItem()) { StateRequest request; request.flags = Text::StateRequest::Flag::LookupSymbol; dragState = _mouseActionItem->textState(_dragStartPosition, request); } if (_mouseSelectType != TextSelectType::Paragraphs) { if (App::pressedItem()) { _mouseTextSymbol = dragState.symbol; auto uponSelected = (dragState.cursor == CursorState::Text); if (uponSelected) { if (!_selectedItem || _selectedItem != _mouseActionItem) { uponSelected = false; } else if (_mouseTextSymbol < _selectedText.from || _mouseTextSymbol >= _selectedText.to) { uponSelected = false; } } if (uponSelected) { _mouseAction = MouseAction::PrepareDrag; // start text drag } else if (!_pressWasInactive) { if (dragState.afterSymbol) ++_mouseTextSymbol; auto selection = TextSelection { _mouseTextSymbol, _mouseTextSymbol }; repaintItem(std::exchange(_selectedItem, _mouseActionItem)); _selectedText = selection; _mouseAction = MouseAction::Selecting; repaintItem(_mouseActionItem); } } } } if (!_mouseActionItem) { _mouseAction = MouseAction::None; } else if (_mouseAction == MouseAction::None) { _mouseActionItem = nullptr; } } void InnerWidget::mouseActionUpdate(const QPoint &screenPos) { _mousePosition = screenPos; updateSelected(); } void InnerWidget::mouseActionCancel() { _mouseActionItem = nullptr; _mouseAction = MouseAction::None; _dragStartPosition = QPoint(0, 0); _wasSelectedText = false; //_widget->noSelectingScroll(); // TODO } void InnerWidget::mouseActionFinish(const QPoint &screenPos, Qt::MouseButton button) { mouseActionUpdate(screenPos); auto activated = ClickHandler::unpressed(); if (_mouseAction == MouseAction::Dragging) { activated = nullptr; } if (const auto view = App::pressedItem()) { repaintItem(view); App::pressedItem(nullptr); } _wasSelectedText = false; if (activated) { mouseActionCancel(); App::activateClickHandler(activated, button); return; } if (_mouseAction == MouseAction::PrepareDrag && !_pressWasInactive && button != Qt::RightButton) { repaintItem(base::take(_selectedItem)); } else if (_mouseAction == MouseAction::Selecting) { if (_selectedItem && !_pressWasInactive) { if (_selectedText.from == _selectedText.to) { _selectedItem = nullptr; App::wnd()->setInnerFocus(); } } } _mouseAction = MouseAction::None; _mouseActionItem = nullptr; _mouseSelectType = TextSelectType::Letters; //_widget->noSelectingScroll(); // TODO #if defined Q_OS_LINUX32 || defined Q_OS_LINUX64 if (_selectedItem && _selectedText.from != _selectedText.to) { SetClipboardWithEntities( _selectedItem->selectedText(_selectedText), QClipboard::Selection); } #endif // Q_OS_LINUX32 || Q_OS_LINUX64 } void InnerWidget::updateSelected() { auto mousePosition = mapFromGlobal(_mousePosition); auto point = QPoint(snap(mousePosition.x(), 0, width()), snap(mousePosition.y(), _visibleTop, _visibleBottom)); auto itemPoint = QPoint(); auto begin = std::rbegin(_items), end = std::rend(_items); auto from = (point.y() >= _itemsTop && point.y() < _itemsTop + _itemsHeight) ? std::lower_bound(begin, end, point.y(), [this](auto &elem, int top) { return this->itemTop(elem) + elem->height() <= top; }) : end; const auto view = (from != end) ? from->get() : nullptr; const auto item = view ? view->data().get() : nullptr; if (item) { App::mousedItem(view); itemPoint = mapPointToItem(point, view); if (view->pointState(itemPoint) != PointState::Outside) { if (App::hoveredItem() != view) { repaintItem(App::hoveredItem()); App::hoveredItem(view); repaintItem(view); } } else if (const auto view = App::hoveredItem()) { repaintItem(view); App::hoveredItem(nullptr); } } TextState dragState; ClickHandlerHost *lnkhost = nullptr; auto selectingText = _selectedItem && (view == _mouseActionItem) && (view == App::hoveredItem()); if (view) { if (view != _mouseActionItem || (itemPoint - _dragStartPosition).manhattanLength() >= QApplication::startDragDistance()) { if (_mouseAction == MouseAction::PrepareDrag) { _mouseAction = MouseAction::Dragging; InvokeQueued(this, [this] { performDrag(); }); } } StateRequest request; if (_mouseAction == MouseAction::Selecting) { request.flags |= Text::StateRequest::Flag::LookupSymbol; } else { selectingText = false; } dragState = view->textState(itemPoint, request); lnkhost = view; if (!dragState.link && itemPoint.x() >= st::historyPhotoLeft && itemPoint.x() < st::historyPhotoLeft + st::msgPhotoSize) { if (auto message = item->toHistoryMessage()) { if (view->hasFromPhoto()) { enumerateUserpics([&](not_null<Element*> view, int userpicTop) { // stop enumeration if the userpic is below our point if (userpicTop > point.y()) { return false; } // stop enumeration if we've found a userpic under the cursor if (point.y() >= userpicTop && point.y() < userpicTop + st::msgPhotoSize) { const auto message = view->data()->toHistoryMessage(); Assert(message != nullptr); dragState.link = message->from()->openLink(); lnkhost = view; return false; } return true; }); } } } } auto lnkChanged = ClickHandler::setActive(dragState.link, lnkhost); if (lnkChanged || dragState.cursor != _mouseCursorState) { Ui::Tooltip::Hide(); } if (dragState.link || dragState.cursor == CursorState::Date || dragState.cursor == CursorState::Forwarded) { Ui::Tooltip::Show(1000, this); } auto cursor = style::cur_default; if (_mouseAction == MouseAction::None) { _mouseCursorState = dragState.cursor; if (dragState.link) { cursor = style::cur_pointer; } else if (_mouseCursorState == CursorState::Text) { cursor = style::cur_text; } else if (_mouseCursorState == CursorState::Date) { // cursor = style::cur_cross; } } else if (item) { if (_mouseAction == MouseAction::Selecting) { if (selectingText) { auto second = dragState.symbol; if (dragState.afterSymbol && _mouseSelectType == TextSelectType::Letters) { ++second; } auto selection = TextSelection { qMin(second, _mouseTextSymbol), qMax(second, _mouseTextSymbol) }; if (_mouseSelectType != TextSelectType::Letters) { selection = _mouseActionItem->adjustSelection( selection, _mouseSelectType); } if (_selectedText != selection) { _selectedText = selection; repaintItem(_mouseActionItem); } if (!_wasSelectedText && (selection.from != selection.to)) { _wasSelectedText = true; setFocus(); } } } else if (_mouseAction == MouseAction::Dragging) { } if (ClickHandler::getPressed()) { cursor = style::cur_pointer; } else if (_mouseAction == MouseAction::Selecting && _selectedItem) { cursor = style::cur_text; } } // Voice message seek support. if (const auto pressedView = App::pressedLinkItem()) { const auto adjustedPoint = mapPointToItem(point, pressedView); pressedView->updatePressed(adjustedPoint); } //if (_mouseAction == MouseAction::Selecting) { // _widget->checkSelectingScroll(mousePos); //} else { // _widget->noSelectingScroll(); //} // TODO if (_mouseAction == MouseAction::None && (lnkChanged || cursor != _cursor)) { setCursor(_cursor = cursor); } } void InnerWidget::performDrag() { if (_mouseAction != MouseAction::Dragging) return; auto uponSelected = false; //if (_mouseActionItem) { // if (!_selected.isEmpty() && _selected.cbegin().value() == FullSelection) { // uponSelected = _selected.contains(_mouseActionItem); // } else { // StateRequest request; // request.flags |= Text::StateRequest::Flag::LookupSymbol; // auto dragState = _mouseActionItem->textState(_dragStartPosition.x(), _dragStartPosition.y(), request); // uponSelected = (dragState.cursor == CursorState::Text); // if (uponSelected) { // if (_selected.isEmpty() || // _selected.cbegin().value() == FullSelection || // _selected.cbegin().key() != _mouseActionItem // ) { // uponSelected = false; // } else { // uint16 selFrom = _selected.cbegin().value().from, selTo = _selected.cbegin().value().to; // if (dragState.symbol < selFrom || dragState.symbol >= selTo) { // uponSelected = false; // } // } // } // } //} //auto pressedHandler = ClickHandler::getPressed(); //if (dynamic_cast<VoiceSeekClickHandler*>(pressedHandler.data())) { // return; //} //TextWithEntities sel; //QList<QUrl> urls; //if (uponSelected) { // sel = getSelectedText(); //} else if (pressedHandler) { // sel = { pressedHandler->dragText(), EntitiesInText() }; // //if (!sel.isEmpty() && sel.at(0) != '/' && sel.at(0) != '@' && sel.at(0) != '#') { // // urls.push_back(QUrl::fromEncoded(sel.toUtf8())); // Google Chrome crashes in Mac OS X O_o // //} //} //if (auto mimeData = mimeDataFromTextWithEntities(sel)) { // updateDragSelection(0, 0, false); // _widget->noSelectingScroll(); // if (!urls.isEmpty()) mimeData->setUrls(urls); // if (uponSelected && !Adaptive::OneColumn()) { // auto selectedState = getSelectionState(); // if (selectedState.count > 0 && selectedState.count == selectedState.canForwardCount) { // Auth().data().setMimeForwardIds(getSelectedItems()); // mimeData->setData(qsl("application/x-td-forward"), "1"); // } // } // _controller->window()->launchDrag(std::move(mimeData)); // return; //} else { // auto forwardMimeType = QString(); // auto pressedMedia = static_cast<HistoryMedia*>(nullptr); // if (auto pressedItem = App::pressedItem()) { // pressedMedia = pressedItem->media(); // if (_mouseCursorState == CursorState::Date // || (pressedMedia && pressedMedia->dragItem())) { // forwardMimeType = qsl("application/x-td-forward"); // Auth().data().setMimeForwardIds( // Auth().data().itemOrItsGroup(pressedItem->data())); // } // } // if (auto pressedLnkItem = App::pressedLinkItem()) { // if ((pressedMedia = pressedLnkItem->media())) { // if (forwardMimeType.isEmpty() // && pressedMedia->dragItemByHandler(pressedHandler)) { // forwardMimeType = qsl("application/x-td-forward"); // Auth().data().setMimeForwardIds( // { 1, pressedLnkItem->fullId() }); // } // } // } // if (!forwardMimeType.isEmpty()) { // auto mimeData = std::make_unique<QMimeData>(); // mimeData->setData(forwardMimeType, "1"); // if (auto document = (pressedMedia ? pressedMedia->getDocument() : nullptr)) { // auto filepath = document->filepath(DocumentData::FilePathResolveChecked); // if (!filepath.isEmpty()) { // QList<QUrl> urls; // urls.push_back(QUrl::fromLocalFile(filepath)); // mimeData->setUrls(urls); // } // } // // This call enters event loop and can destroy any QObject. // _controller->window()->launchDrag(std::move(mimeData)); // return; // } //} // TODO } int InnerWidget::itemTop(not_null<const Element*> view) const { return _itemsTop + view->y(); } void InnerWidget::repaintItem(const Element *view) { if (!view) { return; } update(0, itemTop(view), width(), view->height()); } void InnerWidget::resizeItem(not_null<Element*> view) { updateSize(); } void InnerWidget::refreshItem(not_null<const Element*> view) { // No need to refresh views in admin log. } QPoint InnerWidget::mapPointToItem(QPoint point, const Element *view) const { if (!view) { return QPoint(); } return point - QPoint(0, itemTop(view)); } InnerWidget::~InnerWidget() = default; } // namespace AdminLog �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h��������������������0000664�0000000�0000000�00000021021�13262451251�0030127�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "history/view/history_view_element.h" #include "history/admin_log/history_admin_log_item.h" #include "history/admin_log/history_admin_log_section.h" #include "ui/widgets/tooltip.h" #include "ui/rp_widget.h" #include "mtproto/sender.h" #include "base/timer.h" namespace HistoryView { class Element; struct TextState; struct StateRequest; enum class CursorState : char; enum class PointState : char; } // namespace HistoryView namespace Ui { class PopupMenu; } // namespace Ui namespace Window { class Controller; } // namespace Window namespace AdminLog { class SectionMemento; class InnerWidget final : public Ui::RpWidget , public Ui::AbstractTooltipShower , public HistoryView::ElementDelegate , private MTP::Sender , private base::Subscriber { public: InnerWidget( QWidget *parent, not_null<Window::Controller*> controller, not_null<ChannelData*> channel); base::Observable<void> showSearchSignal; base::Observable<int> scrollToSignal; base::Observable<void> cancelledSignal; not_null<ChannelData*> channel() const { return _channel; } // Set the correct scroll position after being resized. void restoreScrollPosition(); void resizeToWidth(int newWidth, int minHeight) { _minHeight = minHeight; return TWidget::resizeToWidth(newWidth); } void saveState(not_null<SectionMemento*> memento); void restoreState(not_null<SectionMemento*> memento); // Empty "flags" means all events. void applyFilter(FilterValue &&value); void applySearch(const QString &query); void showFilter(base::lambda<void(FilterValue &&filter)> callback); // Ui::AbstractTooltipShower interface. QString tooltipText() const override; QPoint tooltipPos() const override; // HistoryView::ElementDelegate interface. HistoryView::Context elementContext() override; std::unique_ptr<HistoryView::Element> elementCreate( not_null<HistoryMessage*> message) override; std::unique_ptr<HistoryView::Element> elementCreate( not_null<HistoryService*> message) override; bool elementUnderCursor( not_null<const HistoryView::Element*> view) override; void elementAnimationAutoplayAsync( not_null<const HistoryView::Element*> view) override; TimeMs elementHighlightTime( not_null<const HistoryView::Element*> element) override; bool elementInSelectionMode() override; ~InnerWidget(); protected: void visibleTopBottomUpdated( int visibleTop, int visibleBottom) override; void paintEvent(QPaintEvent *e) override; void keyPressEvent(QKeyEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void mouseDoubleClickEvent(QMouseEvent *e) override; void enterEventHook(QEvent *e) override; void leaveEventHook(QEvent *e) override; void contextMenuEvent(QContextMenuEvent *e) override; // Resizes content and counts natural widget height for the desired width. int resizeGetHeight(int newWidth) override; private: using Element = HistoryView::Element; enum class Direction { Up, Down, }; enum class MouseAction { None, PrepareDrag, Dragging, Selecting, }; enum class EnumItemsDirection { TopToBottom, BottomToTop, }; using TextState = HistoryView::TextState; using CursorState = HistoryView::CursorState; using PointState = HistoryView::PointState; using StateRequest = HistoryView::StateRequest; void mouseActionStart(const QPoint &screenPos, Qt::MouseButton button); void mouseActionUpdate(const QPoint &screenPos); void mouseActionFinish(const QPoint &screenPos, Qt::MouseButton button); void mouseActionCancel(); void updateSelected(); void performDrag(); int itemTop(not_null<const Element*> view) const; void repaintItem(const Element *view); void refreshItem(not_null<const Element*> view); void resizeItem(not_null<Element*> view); QPoint mapPointToItem(QPoint point, const Element *view) const; void showContextMenu(QContextMenuEvent *e, bool showFromTouch = false); void savePhotoToFile(PhotoData *photo); void saveDocumentToFile(DocumentData *document); void copyContextImage(PhotoData *photo); void showStickerPackInfo(not_null<DocumentData*> document); void cancelContextDownload(not_null<DocumentData*> document); void showContextInFolder(not_null<DocumentData*> document); void openContextGif(FullMsgId itemId); void copyContextText(FullMsgId itemId); void copySelectedText(); TextWithEntities getSelectedText() const; void suggestRestrictUser(not_null<UserData*> user); void restrictUser(not_null<UserData*> user, const MTPChannelBannedRights &oldRights, const MTPChannelBannedRights &newRights); void restrictUserDone(not_null<UserData*> user, const MTPChannelBannedRights &rights); void requestAdmins(); void checkPreloadMore(); void updateVisibleTopItem(); void preloadMore(Direction direction); void itemsAdded(Direction direction, int addedCount); void updateSize(); void updateMinMaxIds(); void updateEmptyText(); void paintEmpty(Painter &p); void clearAfterFilterChange(); void clearAndRequestLog(); void addEvents(Direction direction, const QVector<MTPChannelAdminLogEvent> &events); Element *viewForItem(const HistoryItem *item); void toggleScrollDateShown(); void repaintScrollDateCallback(); bool displayScrollDate() const; void scrollDateHide(); void scrollDateCheck(); void scrollDateHideByTimer(); // This function finds all history items that are displayed and calls template method // for each found message (in given direction) in the passed history with passed top offset. // // Method has "bool (*Method)(not_null<Element*> view, int itemtop, int itembottom)" signature // if it returns false the enumeration stops immidiately. template <EnumItemsDirection direction, typename Method> void enumerateItems(Method method); // This function finds all userpics on the left that are displayed and calls template method // for each found userpic (from the top to the bottom) using enumerateItems() method. // // Method has "bool (*Method)(not_null<Element*> view, int userpicTop)" signature // if it returns false the enumeration stops immidiately. template <typename Method> void enumerateUserpics(Method method); // This function finds all date elements that are displayed and calls template method // for each found date element (from the bottom to the top) using enumerateItems() method. // // Method has "bool (*Method)(not_null<HistoryItem*> item, int itemtop, int dateTop)" signature // if it returns false the enumeration stops immidiately. template <typename Method> void enumerateDates(Method method); not_null<Window::Controller*> _controller; not_null<ChannelData*> _channel; not_null<History*> _history; std::vector<OwnedItem> _items; std::map<uint64, not_null<Element*>> _itemsByIds; std::map<not_null<HistoryItem*>, not_null<Element*>, std::less<>> _itemsByData; int _itemsTop = 0; int _itemsWidth = 0; int _itemsHeight = 0; int _minHeight = 0; int _visibleTop = 0; int _visibleBottom = 0; Element *_visibleTopItem = nullptr; int _visibleTopFromItem = 0; bool _scrollDateShown = false; Animation _scrollDateOpacity; SingleQueuedInvokation _scrollDateCheck; base::Timer _scrollDateHideTimer; Element *_scrollDateLastItem = nullptr; int _scrollDateLastItemTop = 0; // Up - max, Down - min. uint64 _maxId = 0; uint64 _minId = 0; mtpRequestId _preloadUpRequestId = 0; mtpRequestId _preloadDownRequestId = 0; // Don't load anything until the memento was read. bool _upLoaded = true; bool _downLoaded = true; bool _filterChanged = false; Text _emptyText; MouseAction _mouseAction = MouseAction::None; TextSelectType _mouseSelectType = TextSelectType::Letters; QPoint _dragStartPosition; QPoint _mousePosition; Element *_mouseActionItem = nullptr; CursorState _mouseCursorState = CursorState(); uint16 _mouseTextSymbol = 0; bool _pressWasInactive = false; Element *_selectedItem = nullptr; TextSelection _selectedText; bool _wasSelectedText = false; // was some text selected in current drag action Qt::CursorShape _cursor = style::cur_default; base::unique_qptr<Ui::PopupMenu> _menu; QPoint _trippleClickPoint; base::Timer _trippleClickTimer; FilterValue _filter; QString _searchQuery; std::vector<not_null<UserData*>> _admins; std::vector<not_null<UserData*>> _adminsCanEdit; base::lambda<void(FilterValue &&filter)> _showFilterCallback; std::shared_ptr<LocalIdManager> _idManager; }; } // namespace AdminLog ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp�������������������0000664�0000000�0000000�00000055477�13262451251�0030333�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/admin_log/history_admin_log_item.h" #include "history/admin_log/history_admin_log_inner.h" #include "history/view/history_view_element.h" #include "history/history_service.h" #include "history/history_message.h" #include "history/history.h" #include "data/data_session.h" #include "lang/lang_keys.h" #include "boxes/sticker_set_box.h" #include "core/tl_help.h" #include "base/overload.h" #include "messenger.h" #include "auth_session.h" namespace AdminLog { namespace { TextWithEntities PrepareText(const QString &value, const QString &emptyValue) { auto result = TextWithEntities { TextUtilities::Clean(value) }; if (result.text.isEmpty()) { result.text = emptyValue; if (!emptyValue.isEmpty()) { result.entities.push_back(EntityInText(EntityInTextItalic, 0, emptyValue.size())); } } else { TextUtilities::ParseEntities(result, TextParseLinks | TextParseMentions | TextParseHashtags | TextParseBotCommands); } return result; } MTPMessage PrepareLogMessage(const MTPMessage &message, MsgId newId, int32 newDate) { switch (message.type()) { case mtpc_messageEmpty: return MTP_messageEmpty(MTP_int(newId)); case mtpc_messageService: { auto &data = message.c_messageService(); auto removeFlags = MTPDmessageService::Flag::f_out | MTPDmessageService::Flag::f_post /* | MTPDmessageService::Flag::f_reply_to_msg_id*/; auto flags = data.vflags.v & ~removeFlags; return MTP_messageService( MTP_flags(flags), MTP_int(newId), data.vfrom_id, data.vto_id, data.vreply_to_msg_id, MTP_int(newDate), data.vaction); } break; case mtpc_message: { auto &data = message.c_message(); auto removeFlags = MTPDmessage::Flag::f_out | MTPDmessage::Flag::f_post | MTPDmessage::Flag::f_reply_to_msg_id | MTPDmessage::Flag::f_edit_date | MTPDmessage::Flag::f_grouped_id; auto flags = data.vflags.v & ~removeFlags; return MTP_message( MTP_flags(flags), MTP_int(newId), data.vfrom_id, data.vto_id, data.vfwd_from, data.vvia_bot_id, data.vreply_to_msg_id, MTP_int(newDate), data.vmessage, data.vmedia, data.vreply_markup, data.ventities, data.vviews, data.vedit_date, MTP_string(""), data.vgrouped_id); } break; } Unexpected("Type in PrepareLogMessage()"); } bool MediaCanHaveCaption(const MTPMessage &message) { if (message.type() != mtpc_message) { return false; } auto &data = message.c_message(); auto mediaType = data.has_media() ? data.vmedia.type() : mtpc_messageMediaEmpty; return (mediaType == mtpc_messageMediaDocument || mediaType == mtpc_messageMediaPhoto); } TextWithEntities ExtractEditedText(const MTPMessage &message) { if (message.type() != mtpc_message) { return TextWithEntities(); } auto &data = message.c_message(); auto text = TextUtilities::Clean(qs(data.vmessage)); auto entities = data.has_entities() ? TextUtilities::EntitiesFromMTP(data.ventities.v) : EntitiesInText(); return { text, entities }; } PhotoData *GenerateChatPhoto(ChannelId channelId, uint64 logEntryId, TimeId date, const MTPDchatPhoto &photo) { // We try to make a unique photoId that will stay the same for each pair (channelId, logEntryId). static const auto RandomIdPart = rand_value<uint64>(); auto mixinIdPart = (static_cast<uint64>(static_cast<uint32>(channelId)) << 32) ^ logEntryId; auto photoId = RandomIdPart ^ mixinIdPart; auto photoSizes = QVector<MTPPhotoSize>(); photoSizes.reserve(2); photoSizes.push_back(MTP_photoSize(MTP_string("a"), photo.vphoto_small, MTP_int(160), MTP_int(160), MTP_int(0))); photoSizes.push_back(MTP_photoSize(MTP_string("c"), photo.vphoto_big, MTP_int(640), MTP_int(640), MTP_int(0))); return Auth().data().photo(MTP_photo( MTP_flags(0), MTP_long(photoId), MTP_long(0), MTP_int(date), MTP_vector<MTPPhotoSize>(photoSizes))); } const auto CollectChanges = [](auto &phraseMap, auto plusFlags, auto minusFlags) { auto withPrefix = [&phraseMap](auto flags, QChar prefix) { auto result = QString(); for (auto &phrase : phraseMap) { if (flags & phrase.first) { result.append('\n' + (prefix + lang(phrase.second))); } } return result; }; const auto kMinus = QChar(0x2212); return withPrefix(plusFlags & ~minusFlags, '+') + withPrefix(minusFlags & ~plusFlags, kMinus); }; auto GenerateAdminChangeText(not_null<ChannelData*> channel, const TextWithEntities &user, const MTPChannelAdminRights *newRights, const MTPChannelAdminRights *prevRights) { using Flag = MTPDchannelAdminRights::Flag; using Flags = MTPDchannelAdminRights::Flags; Expects(!newRights || newRights->type() == mtpc_channelAdminRights); Expects(!prevRights || prevRights->type() == mtpc_channelAdminRights); auto newFlags = newRights ? newRights->c_channelAdminRights().vflags.v : MTPDchannelAdminRights::Flags(0); auto prevFlags = prevRights ? prevRights->c_channelAdminRights().vflags.v : MTPDchannelAdminRights::Flags(0); auto result = lng_admin_log_promoted__generic(lt_user, user); auto inviteKey = Flag::f_invite_users | Flag::f_invite_link; auto useInviteLinkPhrase = channel->isMegagroup() && channel->anyoneCanAddMembers(); auto invitePhrase = (useInviteLinkPhrase ? lng_admin_log_admin_invite_link : lng_admin_log_admin_invite_users); static auto phraseMap = std::map<Flags, LangKey> { { Flag::f_change_info, lng_admin_log_admin_change_info }, { Flag::f_post_messages, lng_admin_log_admin_post_messages }, { Flag::f_edit_messages, lng_admin_log_admin_edit_messages }, { Flag::f_delete_messages, lng_admin_log_admin_delete_messages }, { Flag::f_ban_users, lng_admin_log_admin_ban_users }, { inviteKey, invitePhrase }, { Flag::f_pin_messages, lng_admin_log_admin_pin_messages }, { Flag::f_add_admins, lng_admin_log_admin_add_admins }, }; phraseMap[inviteKey] = invitePhrase; if (!channel->isMegagroup()) { // Don't display "Ban users" changes in channels. newFlags &= ~Flag::f_ban_users; prevFlags &= ~Flag::f_ban_users; } auto changes = CollectChanges(phraseMap, newFlags, prevFlags); if (!changes.isEmpty()) { result.text.append('\n' + changes); } return result; }; auto GenerateBannedChangeText(const TextWithEntities &user, const MTPChannelBannedRights *newRights, const MTPChannelBannedRights *prevRights) { using Flag = MTPDchannelBannedRights::Flag; using Flags = MTPDchannelBannedRights::Flags; Expects(!newRights || newRights->type() == mtpc_channelBannedRights); Expects(!prevRights || prevRights->type() == mtpc_channelBannedRights); auto newFlags = newRights ? newRights->c_channelBannedRights().vflags.v : MTPDchannelBannedRights::Flags(0); auto prevFlags = prevRights ? prevRights->c_channelBannedRights().vflags.v : MTPDchannelBannedRights::Flags(0); auto newUntil = newRights ? newRights->c_channelBannedRights().vuntil_date.v : TimeId(0); auto indefinitely = ChannelData::IsRestrictedForever(newUntil); if (newFlags & Flag::f_view_messages) { return lng_admin_log_banned__generic(lt_user, user); } auto untilText = indefinitely ? lang(lng_admin_log_restricted_forever) : lng_admin_log_restricted_until( lt_date, langDateTime(ParseDateTime(newUntil))); auto result = lng_admin_log_restricted__generic( lt_user, user, lt_until, TextWithEntities { untilText }); static auto phraseMap = std::map<Flags, LangKey> { { Flag::f_view_messages, lng_admin_log_banned_view_messages }, { Flag::f_send_messages, lng_admin_log_banned_send_messages }, { Flag::f_send_media, lng_admin_log_banned_send_media }, { Flag::f_send_stickers | Flag::f_send_gifs | Flag::f_send_inline | Flag::f_send_games, lng_admin_log_banned_send_stickers }, { Flag::f_embed_links, lng_admin_log_banned_embed_links }, }; auto changes = CollectChanges(phraseMap, prevFlags, newFlags); if (!changes.isEmpty()) { result.text.append('\n' + changes); } return result; }; auto GenerateUserString(MTPint userId) { // User name in "User name (@username)" format with entities. auto user = App::user(userId.v); auto name = TextWithEntities { App::peerName(user) }; auto entityData = QString::number(user->id) + '.' + QString::number(user->accessHash()); name.entities.push_back(EntityInText( EntityInTextMentionName, 0, name.text.size(), entityData)); auto username = user->userName(); if (username.isEmpty()) { return name; } auto mention = TextWithEntities { '@' + username }; mention.entities.push_back(EntityInText(EntityInTextMention, 0, mention.text.size())); return lng_admin_log_user_with_username__generic(lt_name, name, lt_mention, mention); } auto GenerateParticipantChangeTextInner( not_null<ChannelData*> channel, const MTPChannelParticipant &participant, const MTPChannelParticipant *oldParticipant) { auto oldType = oldParticipant ? oldParticipant->type() : 0; auto readResult = base::overload([&](const MTPDchannelParticipantCreator &data) { // No valid string here :( return lng_admin_log_invited__generic( lt_user, GenerateUserString(data.vuser_id)); }, [&](const MTPDchannelParticipantAdmin &data) { auto user = GenerateUserString(data.vuser_id); return GenerateAdminChangeText( channel, user, &data.vadmin_rights, (oldType == mtpc_channelParticipantAdmin) ? &oldParticipant->c_channelParticipantAdmin().vadmin_rights : nullptr); }, [&](const MTPDchannelParticipantBanned &data) { auto user = GenerateUserString(data.vuser_id); return GenerateBannedChangeText( user, &data.vbanned_rights, (oldType == mtpc_channelParticipantBanned) ? &oldParticipant->c_channelParticipantBanned().vbanned_rights : nullptr); }, [&](const auto &data) { auto user = GenerateUserString(data.vuser_id); if (oldType == mtpc_channelParticipantAdmin) { return GenerateAdminChangeText( channel, user, nullptr, &oldParticipant->c_channelParticipantAdmin().vadmin_rights); } else if (oldType == mtpc_channelParticipantBanned) { return GenerateBannedChangeText( user, nullptr, &oldParticipant->c_channelParticipantBanned().vbanned_rights); } return lng_admin_log_invited__generic(lt_user, user); }); return TLHelp::VisitChannelParticipant(participant, readResult); } TextWithEntities GenerateParticipantChangeText(not_null<ChannelData*> channel, const MTPChannelParticipant &participant, const MTPChannelParticipant *oldParticipant = nullptr) { auto result = GenerateParticipantChangeTextInner(channel, participant, oldParticipant); result.entities.push_front(EntityInText(EntityInTextItalic, 0, result.text.size())); return result; } } // namespace OwnedItem::OwnedItem( not_null<HistoryView::ElementDelegate*> delegate, not_null<HistoryItem*> data) : _data(data) , _view(_data->createView(delegate)) { } OwnedItem::OwnedItem(OwnedItem &&other) : _data(base::take(other._data)) , _view(base::take(other._view)) { } OwnedItem &OwnedItem::operator=(OwnedItem &&other) { _data = base::take(other._data); _view = base::take(other._view); return *this; } OwnedItem::~OwnedItem() { _view = nullptr; if (_data) { _data->destroy(); } } void OwnedItem::refreshView( not_null<HistoryView::ElementDelegate*> delegate) { _view = _data->createView(delegate); } void GenerateItems( not_null<HistoryView::ElementDelegate*> delegate, not_null<History*> history, not_null<LocalIdManager*> idManager, const MTPDchannelAdminLogEvent &event, base::lambda<void(OwnedItem item)> callback) { Expects(history->peer->isChannel()); auto id = event.vid.v; auto from = App::user(event.vuser_id.v); auto channel = history->peer->asChannel(); auto &action = event.vaction; auto date = event.vdate.v; auto addPart = [&](not_null<HistoryItem*> item) { return callback(OwnedItem(delegate, item)); }; using Flag = MTPDmessage::Flag; auto fromName = App::peerName(from); auto fromLink = from->createOpenLink(); auto fromLinkText = textcmdLink(1, fromName); auto addSimpleServiceMessage = [&](const QString &text, PhotoData *photo = nullptr) { auto message = HistoryService::PreparedText { text }; message.links.push_back(fromLink); addPart(new HistoryService(history, idManager->next(), date, message, 0, peerToUser(from->id), photo)); }; auto createChangeTitle = [&](const MTPDchannelAdminLogEventActionChangeTitle &action) { auto text = (channel->isMegagroup() ? lng_action_changed_title : lng_admin_log_changed_title_channel)(lt_from, fromLinkText, lt_title, qs(action.vnew_value)); addSimpleServiceMessage(text); }; auto createChangeAbout = [&](const MTPDchannelAdminLogEventActionChangeAbout &action) { auto newValue = qs(action.vnew_value); auto oldValue = qs(action.vprev_value); auto text = (channel->isMegagroup() ? (newValue.isEmpty() ? lng_admin_log_removed_description_group : lng_admin_log_changed_description_group) : (newValue.isEmpty() ? lng_admin_log_removed_description_channel : lng_admin_log_changed_description_channel) )(lt_from, fromLinkText); addSimpleServiceMessage(text); auto bodyFlags = Flag::f_entities | Flag::f_from_id; auto bodyReplyTo = 0; auto bodyViaBotId = 0; auto newDescription = PrepareText(newValue, QString()); auto body = new HistoryMessage(history, idManager->next(), bodyFlags, bodyReplyTo, bodyViaBotId, date, peerToUser(from->id), QString(), newDescription); if (!oldValue.isEmpty()) { auto oldDescription = PrepareText(oldValue, QString()); body->addLogEntryOriginal(id, lang(lng_admin_log_previous_description), oldDescription); } addPart(body); }; auto createChangeUsername = [&](const MTPDchannelAdminLogEventActionChangeUsername &action) { auto newValue = qs(action.vnew_value); auto oldValue = qs(action.vprev_value); auto text = (channel->isMegagroup() ? (newValue.isEmpty() ? lng_admin_log_removed_link_group : lng_admin_log_changed_link_group) : (newValue.isEmpty() ? lng_admin_log_removed_link_channel : lng_admin_log_changed_link_channel) )(lt_from, fromLinkText); addSimpleServiceMessage(text); auto bodyFlags = Flag::f_entities | Flag::f_from_id; auto bodyReplyTo = 0; auto bodyViaBotId = 0; auto newLink = newValue.isEmpty() ? TextWithEntities() : PrepareText(Messenger::Instance().createInternalLinkFull(newValue), QString()); auto body = new HistoryMessage(history, idManager->next(), bodyFlags, bodyReplyTo, bodyViaBotId, date, peerToUser(from->id), QString(), newLink); if (!oldValue.isEmpty()) { auto oldLink = PrepareText(Messenger::Instance().createInternalLinkFull(oldValue), QString()); body->addLogEntryOriginal(id, lang(lng_admin_log_previous_link), oldLink); } addPart(body); }; auto createChangePhoto = [&](const MTPDchannelAdminLogEventActionChangePhoto &action) { switch (action.vnew_photo.type()) { case mtpc_chatPhoto: { auto photo = GenerateChatPhoto(channel->bareId(), id, date, action.vnew_photo.c_chatPhoto()); auto text = (channel->isMegagroup() ? lng_admin_log_changed_photo_group : lng_admin_log_changed_photo_channel)(lt_from, fromLinkText); addSimpleServiceMessage(text, photo); } break; case mtpc_chatPhotoEmpty: { auto text = (channel->isMegagroup() ? lng_admin_log_removed_photo_group : lng_admin_log_removed_photo_channel)(lt_from, fromLinkText); addSimpleServiceMessage(text); } break; default: Unexpected("ChatPhoto type in createChangePhoto()"); } }; auto createToggleInvites = [&](const MTPDchannelAdminLogEventActionToggleInvites &action) { auto enabled = (action.vnew_value.type() == mtpc_boolTrue); auto text = (enabled ? lng_admin_log_invites_enabled : lng_admin_log_invites_disabled); addSimpleServiceMessage(text(lt_from, fromLinkText)); }; auto createToggleSignatures = [&](const MTPDchannelAdminLogEventActionToggleSignatures &action) { auto enabled = (action.vnew_value.type() == mtpc_boolTrue); auto text = (enabled ? lng_admin_log_signatures_enabled : lng_admin_log_signatures_disabled); addSimpleServiceMessage(text(lt_from, fromLinkText)); }; auto createUpdatePinned = [&](const MTPDchannelAdminLogEventActionUpdatePinned &action) { if (action.vmessage.type() == mtpc_messageEmpty) { auto text = lng_admin_log_unpinned_message(lt_from, fromLinkText); addSimpleServiceMessage(text); } else { auto text = lng_admin_log_pinned_message(lt_from, fromLinkText); addSimpleServiceMessage(text); auto detachExistingItem = false; addPart(history->createItem( PrepareLogMessage( action.vmessage, idManager->next(), date), detachExistingItem)); } }; auto createEditMessage = [&](const MTPDchannelAdminLogEventActionEditMessage &action) { auto newValue = ExtractEditedText(action.vnew_message); auto canHaveCaption = MediaCanHaveCaption(action.vnew_message); auto text = (canHaveCaption ? (newValue.text.isEmpty() ? lng_admin_log_removed_caption : lng_admin_log_edited_caption) : lng_admin_log_edited_message )(lt_from, fromLinkText); addSimpleServiceMessage(text); auto oldValue = ExtractEditedText(action.vprev_message); auto detachExistingItem = false; auto body = history->createItem( PrepareLogMessage( action.vnew_message, idManager->next(), date), detachExistingItem); if (oldValue.text.isEmpty()) { oldValue = PrepareText(QString(), lang(lng_admin_log_empty_text)); } body->addLogEntryOriginal(id, lang(canHaveCaption ? lng_admin_log_previous_caption : lng_admin_log_previous_message), oldValue); addPart(body); }; auto createDeleteMessage = [&](const MTPDchannelAdminLogEventActionDeleteMessage &action) { auto text = lng_admin_log_deleted_message(lt_from, fromLinkText); addSimpleServiceMessage(text); auto detachExistingItem = false; addPart(history->createItem( PrepareLogMessage(action.vmessage, idManager->next(), date), detachExistingItem)); }; auto createParticipantJoin = [&]() { auto text = (channel->isMegagroup() ? lng_admin_log_participant_joined : lng_admin_log_participant_joined_channel); addSimpleServiceMessage(text(lt_from, fromLinkText)); }; auto createParticipantLeave = [&]() { auto text = (channel->isMegagroup() ? lng_admin_log_participant_left : lng_admin_log_participant_left_channel); addSimpleServiceMessage(text(lt_from, fromLinkText)); }; auto createParticipantInvite = [&](const MTPDchannelAdminLogEventActionParticipantInvite &action) { auto bodyFlags = Flag::f_entities | Flag::f_from_id; auto bodyReplyTo = 0; auto bodyViaBotId = 0; auto bodyText = GenerateParticipantChangeText(channel, action.vparticipant); addPart(new HistoryMessage(history, idManager->next(), bodyFlags, bodyReplyTo, bodyViaBotId, date, peerToUser(from->id), QString(), bodyText)); }; auto createParticipantToggleBan = [&](const MTPDchannelAdminLogEventActionParticipantToggleBan &action) { auto bodyFlags = Flag::f_entities | Flag::f_from_id; auto bodyReplyTo = 0; auto bodyViaBotId = 0; auto bodyText = GenerateParticipantChangeText(channel, action.vnew_participant, &action.vprev_participant); addPart(new HistoryMessage(history, idManager->next(), bodyFlags, bodyReplyTo, bodyViaBotId, date, peerToUser(from->id), QString(), bodyText)); }; auto createParticipantToggleAdmin = [&](const MTPDchannelAdminLogEventActionParticipantToggleAdmin &action) { auto bodyFlags = Flag::f_entities | Flag::f_from_id; auto bodyReplyTo = 0; auto bodyViaBotId = 0; auto bodyText = GenerateParticipantChangeText(channel, action.vnew_participant, &action.vprev_participant); addPart(new HistoryMessage(history, idManager->next(), bodyFlags, bodyReplyTo, bodyViaBotId, date, peerToUser(from->id), QString(), bodyText)); }; auto createChangeStickerSet = [&](const MTPDchannelAdminLogEventActionChangeStickerSet &action) { auto set = action.vnew_stickerset; auto removed = (set.type() == mtpc_inputStickerSetEmpty); if (removed) { auto text = lng_admin_log_removed_stickers_group(lt_from, fromLinkText); addSimpleServiceMessage(text); } else { auto text = lng_admin_log_changed_stickers_group( lt_from, fromLinkText, lt_sticker_set, textcmdLink(2, lang(lng_admin_log_changed_stickers_set))); auto setLink = std::make_shared<LambdaClickHandler>([set] { Ui::show(Box<StickerSetBox>(set)); }); auto message = HistoryService::PreparedText { text }; message.links.push_back(fromLink); message.links.push_back(setLink); addPart(new HistoryService(history, idManager->next(), date, message, 0, peerToUser(from->id))); } }; auto createTogglePreHistoryHidden = [&](const MTPDchannelAdminLogEventActionTogglePreHistoryHidden &action) { auto hidden = (action.vnew_value.type() == mtpc_boolTrue); auto text = (hidden ? lng_admin_log_history_made_hidden : lng_admin_log_history_made_visible); addSimpleServiceMessage(text(lt_from, fromLinkText)); }; switch (action.type()) { case mtpc_channelAdminLogEventActionChangeTitle: createChangeTitle( action.c_channelAdminLogEventActionChangeTitle()); break; case mtpc_channelAdminLogEventActionChangeAbout: createChangeAbout( action.c_channelAdminLogEventActionChangeAbout()); break; case mtpc_channelAdminLogEventActionChangeUsername: createChangeUsername( action.c_channelAdminLogEventActionChangeUsername()); break; case mtpc_channelAdminLogEventActionChangePhoto: createChangePhoto( action.c_channelAdminLogEventActionChangePhoto()); break; case mtpc_channelAdminLogEventActionToggleInvites: createToggleInvites( action.c_channelAdminLogEventActionToggleInvites()); break; case mtpc_channelAdminLogEventActionToggleSignatures: createToggleSignatures( action.c_channelAdminLogEventActionToggleSignatures()); break; case mtpc_channelAdminLogEventActionUpdatePinned: createUpdatePinned( action.c_channelAdminLogEventActionUpdatePinned()); break; case mtpc_channelAdminLogEventActionEditMessage: createEditMessage( action.c_channelAdminLogEventActionEditMessage()); break; case mtpc_channelAdminLogEventActionDeleteMessage: createDeleteMessage( action.c_channelAdminLogEventActionDeleteMessage()); break; case mtpc_channelAdminLogEventActionParticipantJoin: createParticipantJoin(); break; case mtpc_channelAdminLogEventActionParticipantLeave: createParticipantLeave(); break; case mtpc_channelAdminLogEventActionParticipantInvite: createParticipantInvite( action.c_channelAdminLogEventActionParticipantInvite()); break; case mtpc_channelAdminLogEventActionParticipantToggleBan: createParticipantToggleBan( action.c_channelAdminLogEventActionParticipantToggleBan()); break; case mtpc_channelAdminLogEventActionParticipantToggleAdmin: createParticipantToggleAdmin( action.c_channelAdminLogEventActionParticipantToggleAdmin()); break; case mtpc_channelAdminLogEventActionChangeStickerSet: createChangeStickerSet( action.c_channelAdminLogEventActionChangeStickerSet()); break; case mtpc_channelAdminLogEventActionTogglePreHistoryHidden: createTogglePreHistoryHidden( action.c_channelAdminLogEventActionTogglePreHistoryHidden()); break; default: Unexpected("channelAdminLogEventAction type in AdminLog::Item::Item()"); } } } // namespace AdminLog �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/admin_log/history_admin_log_item.h���������������������0000664�0000000�0000000�00000002554�13262451251�0027764�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace HistoryView { class ElementDelegate; class Element; } // namespace HistoryView namespace AdminLog { class OwnedItem; class LocalIdManager; void GenerateItems( not_null<HistoryView::ElementDelegate*> delegate, not_null<History*> history, not_null<LocalIdManager*> idManager, const MTPDchannelAdminLogEvent &event, base::lambda<void(OwnedItem item)> callback); // Smart pointer wrapper for HistoryItem* that destroys the owned item. class OwnedItem { public: OwnedItem( not_null<HistoryView::ElementDelegate*> delegate, not_null<HistoryItem*> data); OwnedItem(const OwnedItem &other) = delete; OwnedItem &operator=(const OwnedItem &other) = delete; OwnedItem(OwnedItem &&other); OwnedItem &operator=(OwnedItem &&other); ~OwnedItem(); HistoryView::Element *get() const { return _view.get(); } HistoryView::Element *operator->() const { return get(); } operator HistoryView::Element*() const { return get(); } void refreshView(not_null<HistoryView::ElementDelegate*> delegate); private: HistoryItem *_data = nullptr; std::unique_ptr<HistoryView::Element> _view; }; } // namespace AdminLog ����������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp����������������0000664�0000000�0000000�00000027775�13262451251�0031041�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/admin_log/history_admin_log_section.h" #include "history/admin_log/history_admin_log_inner.h" #include "history/admin_log/history_admin_log_filter.h" #include "profile/profile_back_button.h" #include "styles/style_history.h" #include "styles/style_window.h" #include "styles/style_info.h" #include "ui/widgets/scroll_area.h" #include "ui/widgets/shadow.h" #include "ui/widgets/buttons.h" #include "ui/widgets/input_fields.h" #include "mainwidget.h" #include "mainwindow.h" #include "apiwrap.h" #include "window/themes/window_theme.h" #include "window/window_controller.h" #include "boxes/confirm_box.h" #include "base/timer.h" #include "lang/lang_keys.h" namespace AdminLog { class FixedBar final : public TWidget, private base::Subscriber { public: FixedBar( QWidget *parent, not_null<Window::Controller*> controller, not_null<ChannelData*> channel); base::Observable<void> showFilterSignal; base::Observable<void> searchCancelledSignal; base::Observable<QString> searchSignal; // When animating mode is enabled the content is hidden and the // whole fixed bar acts like a back button. void setAnimatingMode(bool enabled); void applyFilter(const FilterValue &value); void goBack(); void showSearch(); bool setSearchFocus() { if (_searchShown) { _field->setFocus(); return true; } return false; } protected: void mousePressEvent(QMouseEvent *e) override; void paintEvent(QPaintEvent *e) override; int resizeGetHeight(int newWidth) override; private: void toggleSearch(); void cancelSearch(); void searchUpdated(); void applySearch(); void searchAnimationCallback(); not_null<Window::Controller*> _controller; not_null<ChannelData*> _channel; object_ptr<Ui::FlatInput> _field; object_ptr<Profile::BackButton> _backButton; object_ptr<Ui::IconButton> _search; object_ptr<Ui::CrossButton> _cancel; object_ptr<Ui::RoundButton> _filter; Animation _searchShownAnimation; bool _searchShown = false; bool _animatingMode = false; base::Timer _searchTimer; }; object_ptr<Window::SectionWidget> SectionMemento::createWidget( QWidget *parent, not_null<Window::Controller*> controller, Window::Column column, const QRect &geometry) { if (column == Window::Column::Third) { return nullptr; } auto result = object_ptr<Widget>(parent, controller, _channel); result->setInternalState(geometry, this); return std::move(result); } FixedBar::FixedBar( QWidget *parent, not_null<Window::Controller*> controller, not_null<ChannelData*> channel) : TWidget(parent) , _controller(controller) , _channel(channel) , _field(this, st::historyAdminLogSearchField, langFactory(lng_dlg_filter)) , _backButton(this, lang(lng_admin_log_title_all)) , _search(this, st::topBarSearch) , _cancel(this, st::historyAdminLogCancelSearch) , _filter(this, langFactory(lng_admin_log_filter), st::topBarButton) { _backButton->moveToLeft(0, 0); _backButton->setClickedCallback([this] { goBack(); }); _filter->setClickedCallback([this] { showFilterSignal.notify(); }); _search->setClickedCallback([this] { showSearch(); }); _cancel->setClickedCallback([this] { cancelSearch(); }); _field->hide(); connect(_field, &Ui::FlatInput::cancelled, this, [this] { cancelSearch(); }); connect(_field, &Ui::FlatInput::changed, this, [this] { searchUpdated(); }); connect(_field, &Ui::FlatInput::submitted, this, [this] { applySearch(); }); _searchTimer.setCallback([this] { applySearch(); }); _cancel->hide(anim::type::instant); } void FixedBar::applyFilter(const FilterValue &value) { auto hasFilter = (value.flags != 0) || !value.allUsers; _backButton->setText(lang(hasFilter ? lng_admin_log_title_selected : lng_admin_log_title_all)); } void FixedBar::goBack() { _controller->showBackFromStack(); } void FixedBar::showSearch() { if (!_searchShown) { toggleSearch(); } } void FixedBar::toggleSearch() { _searchShown = !_searchShown; _cancel->toggle(_searchShown, anim::type::normal); _searchShownAnimation.start([this] { searchAnimationCallback(); }, _searchShown ? 0. : 1., _searchShown ? 1. : 0., st::historyAdminLogSearchSlideDuration); _search->setDisabled(_searchShown); if (_searchShown) { _field->show(); _field->setFocus(); } else { searchCancelledSignal.notify(true); } } void FixedBar::searchAnimationCallback() { if (!_searchShownAnimation.animating()) { _field->setVisible(_searchShown); _search->setIconOverride(_searchShown ? &st::topBarSearch.icon : nullptr, _searchShown ? &st::topBarSearch.icon : nullptr); _search->setRippleColorOverride(_searchShown ? &st::topBarBg : nullptr); _search->setCursor(_searchShown ? style::cur_default : style::cur_pointer); } resizeToWidth(width()); } void FixedBar::cancelSearch() { if (_searchShown) { if (!_field->getLastText().isEmpty()) { _field->setText(QString()); _field->updatePlaceholder(); _field->setFocus(); applySearch(); } else { toggleSearch(); } } } void FixedBar::searchUpdated() { if (_field->getLastText().isEmpty()) { applySearch(); } else { _searchTimer.callOnce(AutoSearchTimeout); } } void FixedBar::applySearch() { searchSignal.notify(_field->getLastText()); } int FixedBar::resizeGetHeight(int newWidth) { auto filterLeft = newWidth - _filter->width(); _filter->moveToLeft(filterLeft, 0); auto cancelLeft = filterLeft - _cancel->width(); _cancel->moveToLeft(cancelLeft, 0); auto searchShownLeft = st::topBarArrowPadding.left(); auto searchHiddenLeft = filterLeft - _search->width(); auto searchShown = _searchShownAnimation.current(_searchShown ? 1. : 0.); auto searchCurrentLeft = anim::interpolate(searchHiddenLeft, searchShownLeft, searchShown); _search->moveToLeft(searchCurrentLeft, 0); _backButton->resizeToWidth(searchCurrentLeft); _backButton->moveToLeft(0, 0); auto newHeight = _backButton->height(); auto fieldLeft = searchShownLeft + _search->width(); _field->setGeometryToLeft(fieldLeft, st::historyAdminLogSearchTop, cancelLeft - fieldLeft, _field->height()); return newHeight; } void FixedBar::setAnimatingMode(bool enabled) { if (_animatingMode != enabled) { _animatingMode = enabled; setCursor(_animatingMode ? style::cur_pointer : style::cur_default); if (_animatingMode) { setAttribute(Qt::WA_OpaquePaintEvent, false); hideChildren(); } else { setAttribute(Qt::WA_OpaquePaintEvent); showChildren(); _field->hide(); _cancel->setVisible(false); } show(); } } void FixedBar::paintEvent(QPaintEvent *e) { if (!_animatingMode) { Painter p(this); p.fillRect(e->rect(), st::topBarBg); } } void FixedBar::mousePressEvent(QMouseEvent *e) { if (e->button() == Qt::LeftButton) { goBack(); } else { TWidget::mousePressEvent(e); } } Widget::Widget(QWidget *parent, not_null<Window::Controller*> controller, not_null<ChannelData*> channel) : Window::SectionWidget(parent, controller) , _scroll(this, st::historyScroll, false) , _fixedBar(this, controller, channel) , _fixedBarShadow(this) , _whatIsThis(this, lang(lng_admin_log_about).toUpper(), st::historyComposeButton) { _fixedBar->move(0, 0); _fixedBar->resizeToWidth(width()); subscribe(_fixedBar->showFilterSignal, [this] { showFilter(); }); subscribe(_fixedBar->searchCancelledSignal, [this] { setInnerFocus(); }); subscribe(_fixedBar->searchSignal, [this](const QString &query) { _inner->applySearch(query); }); _fixedBar->show(); _fixedBarShadow->raise(); updateAdaptiveLayout(); subscribe(Adaptive::Changed(), [this] { updateAdaptiveLayout(); }); _inner = _scroll->setOwnedWidget(object_ptr<InnerWidget>(this, controller, channel)); subscribe(_inner->showSearchSignal, [this] { _fixedBar->showSearch(); }); subscribe(_inner->cancelledSignal, [this] { _fixedBar->goBack(); }); subscribe(_inner->scrollToSignal, [this](int top) { _scroll->scrollToY(top); }); _scroll->move(0, _fixedBar->height()); _scroll->show(); connect(_scroll, &Ui::ScrollArea::scrolled, this, [this] { onScroll(); }); _whatIsThis->setClickedCallback([=] { Ui::show(Box<InformBox>(lang(lng_admin_log_about_text))); }); } void Widget::showFilter() { _inner->showFilter([this](FilterValue &&filter) { applyFilter(std::move(filter)); Ui::hideLayer(); }); } void Widget::updateAdaptiveLayout() { _fixedBarShadow->moveToLeft(Adaptive::OneColumn() ? 0 : st::lineWidth, _fixedBar->height()); } not_null<ChannelData*> Widget::channel() const { return _inner->channel(); } Dialogs::RowDescriptor Widget::activeChat() const { return { App::history(channel()), FullMsgId(channel()->bareId(), ShowAtUnreadMsgId) }; } QPixmap Widget::grabForShowAnimation(const Window::SectionSlideParams ¶ms) { if (params.withTopBarShadow) _fixedBarShadow->hide(); auto result = Ui::GrabWidget(this); if (params.withTopBarShadow) _fixedBarShadow->show(); return result; } void Widget::doSetInnerFocus() { if (!_fixedBar->setSearchFocus()) { _inner->setFocus(); } } bool Widget::showInternal( not_null<Window::SectionMemento*> memento, const Window::SectionShow ¶ms) { if (auto logMemento = dynamic_cast<SectionMemento*>(memento.get())) { if (logMemento->getChannel() == channel()) { restoreState(logMemento); return true; } } return false; } void Widget::setInternalState(const QRect &geometry, not_null<SectionMemento*> memento) { setGeometry(geometry); Ui::SendPendingMoveResizeEvents(this); restoreState(memento); } bool Widget::cmd_search() { if (!inFocusChain()) { return false; } _fixedBar->showSearch(); return true; } std::unique_ptr<Window::SectionMemento> Widget::createMemento() { auto result = std::make_unique<SectionMemento>(channel()); saveState(result.get()); return std::move(result); } void Widget::saveState(not_null<SectionMemento*> memento) { memento->setScrollTop(_scroll->scrollTop()); _inner->saveState(memento); } void Widget::restoreState(not_null<SectionMemento*> memento) { _inner->restoreState(memento); auto scrollTop = memento->getScrollTop(); _scroll->scrollToY(scrollTop); _inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height()); } void Widget::resizeEvent(QResizeEvent *e) { if (!width() || !height()) { return; } auto contentWidth = width(); auto newScrollTop = _scroll->scrollTop() + topDelta(); _fixedBar->resizeToWidth(contentWidth); _fixedBarShadow->resize(contentWidth, st::lineWidth); auto bottom = height(); auto scrollHeight = bottom - _fixedBar->height() - _whatIsThis->height(); auto scrollSize = QSize(contentWidth, scrollHeight); if (_scroll->size() != scrollSize) { _scroll->resize(scrollSize); _inner->resizeToWidth(scrollSize.width(), _scroll->height()); _inner->restoreScrollPosition(); } if (!_scroll->isHidden()) { if (topDelta()) { _scroll->scrollToY(newScrollTop); } auto scrollTop = _scroll->scrollTop(); _inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height()); } auto fullWidthButtonRect = myrtlrect(0, bottom - _whatIsThis->height(), contentWidth, _whatIsThis->height()); _whatIsThis->setGeometry(fullWidthButtonRect); } void Widget::paintEvent(QPaintEvent *e) { if (animating()) { SectionWidget::paintEvent(e); return; } if (Ui::skipPaintEvent(this, e)) { return; } //if (hasPendingResizedItems()) { // updateListSize(); //} //auto ms = getms(); //_historyDownShown.step(ms); SectionWidget::PaintBackground(this, e); } void Widget::onScroll() { int scrollTop = _scroll->scrollTop(); _inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height()); } void Widget::showAnimatedHook( const Window::SectionSlideParams ¶ms) { _fixedBar->setAnimatingMode(true); if (params.withTopBarShadow) _fixedBarShadow->show(); } void Widget::showFinishedHook() { _fixedBar->setAnimatingMode(false); } bool Widget::wheelEventFromFloatPlayer(QEvent *e) { return _scroll->viewportEvent(e); } QRect Widget::rectForFloatPlayer() const { return mapToGlobal(_scroll->geometry()); } void Widget::applyFilter(FilterValue &&value) { _fixedBar->applyFilter(value); _inner->applyFilter(std::move(value)); } } // namespace AdminLog ���tdesktop-1.2.17/Telegram/SourceFiles/history/admin_log/history_admin_log_section.h������������������0000664�0000000�0000000�00000012114�13262451251�0030463�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "window/section_widget.h" #include "window/section_memento.h" #include "history/admin_log/history_admin_log_item.h" #include "mtproto/sender.h" namespace Notify { struct PeerUpdate; } // namespace Notify namespace Ui { class ScrollArea; class PlainShadow; class FlatButton; } // namespace Ui namespace Profile { class BackButton; } // namespace Profile namespace AdminLog { class FixedBar; class InnerWidget; class SectionMemento; struct FilterValue { // Empty "flags" means all events. MTPDchannelAdminLogEventsFilter::Flags flags = 0; std::vector<not_null<UserData*>> admins; bool allUsers = true; }; inline bool operator==(const FilterValue &a, const FilterValue &b) { return (a.flags == b.flags && a.admins == b.admins && a.allUsers == b.allUsers); } inline bool operator!=(const FilterValue &a, const FilterValue &b) { return !(a == b); } class LocalIdManager { public: LocalIdManager() = default; LocalIdManager(const LocalIdManager &other) = delete; LocalIdManager &operator=(const LocalIdManager &other) = delete; MsgId next() { return ++_counter; } private: MsgId _counter = ServerMaxMsgId; }; class Widget final : public Window::SectionWidget { public: Widget(QWidget *parent, not_null<Window::Controller*> controller, not_null<ChannelData*> channel); not_null<ChannelData*> channel() const; Dialogs::RowDescriptor activeChat() const override; bool hasTopBarShadow() const override { return true; } QPixmap grabForShowAnimation(const Window::SectionSlideParams ¶ms) override; bool showInternal( not_null<Window::SectionMemento*> memento, const Window::SectionShow ¶ms) override; std::unique_ptr<Window::SectionMemento> createMemento() override; void setInternalState(const QRect &geometry, not_null<SectionMemento*> memento); // Float player interface. bool wheelEventFromFloatPlayer(QEvent *e) override; QRect rectForFloatPlayer() const override; void applyFilter(FilterValue &&value); bool cmd_search() override; protected: void resizeEvent(QResizeEvent *e) override; void paintEvent(QPaintEvent *e) override; void showAnimatedHook( const Window::SectionSlideParams ¶ms) override; void showFinishedHook() override; void doSetInnerFocus() override; private: void showFilter(); void onScroll(); void updateAdaptiveLayout(); void saveState(not_null<SectionMemento*> memento); void restoreState(not_null<SectionMemento*> memento); object_ptr<Ui::ScrollArea> _scroll; QPointer<InnerWidget> _inner; object_ptr<FixedBar> _fixedBar; object_ptr<Ui::PlainShadow> _fixedBarShadow; object_ptr<Ui::FlatButton> _whatIsThis; }; class SectionMemento : public Window::SectionMemento { public: using Element = HistoryView::Element; SectionMemento(not_null<ChannelData*> channel) : _channel(channel) { } object_ptr<Window::SectionWidget> createWidget( QWidget *parent, not_null<Window::Controller*> controller, Window::Column column, const QRect &geometry) override; not_null<ChannelData*> getChannel() const { return _channel; } void setScrollTop(int scrollTop) { _scrollTop = scrollTop; } int getScrollTop() const { return _scrollTop; } void setAdmins(std::vector<not_null<UserData*>> admins) { _admins = std::move(admins); } void setAdminsCanEdit(std::vector<not_null<UserData*>> admins) { _adminsCanEdit = std::move(admins); } std::vector<not_null<UserData*>> takeAdmins() { return std::move(_admins); } std::vector<not_null<UserData*>> takeAdminsCanEdit() { return std::move(_adminsCanEdit); } void setItems( std::vector<OwnedItem> &&items, std::map<uint64, not_null<Element*>> &&itemsByIds, bool upLoaded, bool downLoaded) { _items = std::move(items); _itemsByIds = std::move(itemsByIds); _upLoaded = upLoaded; _downLoaded = downLoaded; } void setFilter(FilterValue &&filter) { _filter = std::move(filter); } void setSearchQuery(QString &&query) { _searchQuery = std::move(query); } void setIdManager(std::shared_ptr<LocalIdManager> &&manager) { _idManager = std::move(manager); } std::vector<OwnedItem> takeItems() { return std::move(_items); } std::map<uint64, not_null<Element*>> takeItemsByIds() { return std::move(_itemsByIds); } std::shared_ptr<LocalIdManager> takeIdManager() { return std::move(_idManager); } bool upLoaded() const { return _upLoaded; } bool downLoaded() const { return _downLoaded; } FilterValue takeFilter() { return std::move(_filter); } QString takeSearchQuery() { return std::move(_searchQuery); } private: not_null<ChannelData*> _channel; int _scrollTop = 0; std::vector<not_null<UserData*>> _admins; std::vector<not_null<UserData*>> _adminsCanEdit; std::vector<OwnedItem> _items; std::map<uint64, not_null<Element*>> _itemsByIds; bool _upLoaded = false; bool _downLoaded = true; std::shared_ptr<LocalIdManager> _idManager; FilterValue _filter; QString _searchQuery; }; } // namespace AdminLog ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/feed/��������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0022027�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/feed/history_feed_section.cpp��������������������������0000664�0000000�0000000�00000037051�13262451251�0026751�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/feed/history_feed_section.h" #include "history/view/history_view_top_bar_widget.h" #include "history/view/history_view_list_widget.h" #include "history/view/history_view_element.h" #include "history/view/history_view_message.h" #include "history/view/history_view_service_message.h" #include "history/history_item.h" #include "history/history_service.h" #include "history/history_inner_widget.h" #include "core/event_filter.h" #include "lang/lang_keys.h" #include "ui/widgets/buttons.h" #include "ui/widgets/shadow.h" #include "ui/widgets/scroll_area.h" #include "ui/widgets/popup_menu.h" #include "ui/special_buttons.h" #include "boxes/confirm_box.h" #include "window/window_controller.h" #include "window/window_peer_menu.h" #include "data/data_feed_messages.h" #include "data/data_photo.h" #include "data/data_document.h" #include "data/data_session.h" #include "storage/storage_feed_messages.h" #include "mainwidget.h" #include "apiwrap.h" #include "auth_session.h" #include "styles/style_widgets.h" #include "styles/style_history.h" namespace HistoryFeed { Memento::Memento( not_null<Data::Feed*> feed, Data::MessagePosition position) : _feed(feed) , _position(position) , _list(std::make_unique<HistoryView::ListMemento>(position)) { } Memento::~Memento() = default; object_ptr<Window::SectionWidget> Memento::createWidget( QWidget *parent, not_null<Window::Controller*> controller, Window::Column column, const QRect &geometry) { if (column == Window::Column::Third) { return nullptr; } auto result = object_ptr<Widget>(parent, controller, _feed); result->setInternalState(geometry, this); return std::move(result); } Widget::Widget( QWidget *parent, not_null<Window::Controller*> controller, not_null<Data::Feed*> feed) : Window::SectionWidget(parent, controller) , _feed(feed) , _scroll(this, st::historyScroll, false) , _topBar(this, controller) , _topBarShadow(this) , _showNext(nullptr) //, _showNext( // this, // lang(lng_feed_show_next).toUpper(), // st::historyComposeButton) , _scrollDown(_scroll, st::historyToDown) { _topBar->setActiveChat(_feed); _topBar->move(0, 0); _topBar->resizeToWidth(width()); _topBar->show(); _topBar->forwardSelectionRequest( ) | rpl::start_with_next([=] { forwardSelected(); }, _topBar->lifetime()); _topBar->deleteSelectionRequest( ) | rpl::start_with_next([=] { confirmDeleteSelected(); }, _topBar->lifetime()); _topBar->clearSelectionRequest( ) | rpl::start_with_next([=] { clearSelected(); }, _topBar->lifetime()); _topBarShadow->raise(); updateAdaptiveLayout(); subscribe(Adaptive::Changed(), [this] { updateAdaptiveLayout(); }); _inner = _scroll->setOwnedWidget( object_ptr<HistoryView::ListWidget>(this, controller, this)); _scroll->move(0, _topBar->height()); _scroll->show(); connect( _scroll, &Ui::ScrollArea::scrolled, this, [this] { onScroll(); }); //_showNext->setClickedCallback([this] { // // TODO feeds show next //}); _feed->unreadPositionChanges( ) | rpl::filter([=](const Data::MessagePosition &position) { return _undefinedAroundPosition && position; }) | rpl::start_with_next([=](const Data::MessagePosition &position) { auto memento = HistoryView::ListMemento(position); _inner->restoreState(&memento); }, lifetime()); rpl::single( Data::FeedUpdate{ _feed, Data::FeedUpdateFlag::Channels } ) | rpl::then( Auth().data().feedUpdated( ) | rpl::filter([=](const Data::FeedUpdate &update) { return (update.feed == _feed) && (update.flag == Data::FeedUpdateFlag::Channels); }) ) | rpl::start_with_next([=] { crl::on_main(this, [=] { checkForSingleChannelFeed(); }); }, lifetime()); setupScrollDownButton(); } void Widget::setupScrollDownButton() { _scrollDown->setClickedCallback([=] { scrollDownClicked(); }); Core::InstallEventFilter(_scrollDown, [=](not_null<QEvent*> event) { if (event->type() == QEvent::Wheel) { return _scroll->viewportEvent(event); } return false; }); updateScrollDownVisibility(); _feed->unreadCountValue( ) | rpl::start_with_next([=](int count) { _scrollDown->setUnreadCount(count); }, _scrollDown->lifetime()); } void Widget::scrollDownClicked() { _currentMessageId = Data::MaxMessagePosition.fullId; showAtPosition(Data::MaxMessagePosition); } void Widget::showAtPosition(Data::MessagePosition position) { if (showAtPositionNow(position)) { if (const auto highlight = base::take(_highlightMessageId)) { _inner->highlightMessage(highlight); } } else { _nextAnimatedScrollPosition = position; _nextAnimatedScrollDelta = _inner->isBelowPosition(position) ? -_scroll->height() : _inner->isAbovePosition(position) ? _scroll->height() : 0; auto memento = HistoryView::ListMemento(position); _inner->restoreState(&memento); } } bool Widget::showAtPositionNow(Data::MessagePosition position) { if (const auto scrollTop = _inner->scrollTopForPosition(position)) { const auto currentScrollTop = _scroll->scrollTop(); const auto wanted = snap(*scrollTop, 0, _scroll->scrollTopMax()); const auto fullDelta = (wanted - currentScrollTop); const auto limit = _scroll->height(); const auto scrollDelta = snap(fullDelta, -limit, limit); _inner->animatedScrollTo( wanted, position, scrollDelta, (std::abs(fullDelta) > limit ? HistoryView::ListWidget::AnimatedScroll::Part : HistoryView::ListWidget::AnimatedScroll::Full)); return true; } return false; } void Widget::updateScrollDownVisibility() { if (animating()) { return; } const auto scrollDownIsVisible = [&]() -> base::optional<bool> { const auto top = _scroll->scrollTop() + st::historyToDownShownAfter; if (top < _scroll->scrollTopMax()) { return true; } if (_inner->loadedAtBottomKnown()) { return !_inner->loadedAtBottom(); } return base::none; }; const auto scrollDownIsShown = scrollDownIsVisible(); if (!scrollDownIsShown) { return; } if (_scrollDownIsShown != *scrollDownIsShown) { _scrollDownIsShown = *scrollDownIsShown; _scrollDownShown.start( [=] { updateScrollDownPosition(); }, _scrollDownIsShown ? 0. : 1., _scrollDownIsShown ? 1. : 0., st::historyToDownDuration); } } void Widget::updateScrollDownPosition() { // _scrollDown is a child widget of _scroll, not me. auto top = anim::interpolate( 0, _scrollDown->height() + st::historyToDownPosition.y(), _scrollDownShown.current(_scrollDownIsShown ? 1. : 0.)); _scrollDown->moveToRight( st::historyToDownPosition.x(), _scroll->height() - top); auto shouldBeHidden = !_scrollDownIsShown && !_scrollDownShown.animating(); if (shouldBeHidden != _scrollDown->isHidden()) { _scrollDown->setVisible(!shouldBeHidden); } } void Widget::scrollDownAnimationFinish() { _scrollDownShown.finish(); updateScrollDownPosition(); } void Widget::checkForSingleChannelFeed() { const auto &channels = _feed->channels(); if (channels.size() > 1) { return; } else if (!channels.empty()) { controller()->showPeerHistory(channels[0]); } else { controller()->clearSectionStack(); } } Dialogs::RowDescriptor Widget::activeChat() const { return Dialogs::RowDescriptor(_feed, _currentMessageId); } void Widget::updateAdaptiveLayout() { _topBarShadow->moveToLeft( Adaptive::OneColumn() ? 0 : st::lineWidth, _topBar->height()); } QPixmap Widget::grabForShowAnimation(const Window::SectionSlideParams ¶ms) { if (params.withTopBarShadow) _topBarShadow->hide(); auto result = Ui::GrabWidget(this); if (params.withTopBarShadow) _topBarShadow->show(); return result; } void Widget::doSetInnerFocus() { _inner->setFocus(); } bool Widget::showInternal( not_null<Window::SectionMemento*> memento, const Window::SectionShow ¶ms) { if (const auto feedMemento = dynamic_cast<Memento*>(memento.get())) { if (feedMemento->feed() == _feed) { restoreState(feedMemento); return true; } } return false; } void Widget::setInternalState( const QRect &geometry, not_null<Memento*> memento) { setGeometry(geometry); Ui::SendPendingMoveResizeEvents(this); restoreState(memento); } bool Widget::cmd_search() { if (!inFocusChain()) { return false; } App::main()->searchInChat(_feed); return true; } HistoryView::Context Widget::listContext() { return HistoryView::Context::Feed; } void Widget::listScrollTo(int top) { if (_scroll->scrollTop() != top) { _scroll->scrollToY(top); } else { updateInnerVisibleArea(); } } void Widget::listCancelRequest() { controller()->showBackFromStack(); } void Widget::listDeleteRequest() { confirmDeleteSelected(); } rpl::producer<Data::MessagesSlice> Widget::listSource( Data::MessagePosition aroundId, int limitBefore, int limitAfter) { return Data::FeedMessagesViewer( Storage::FeedMessagesKey(_feed->id(), aroundId), limitBefore, limitAfter); } bool Widget::listAllowsMultiSelect() { return true; } bool Widget::listIsLessInOrder( not_null<HistoryItem*> first, not_null<HistoryItem*> second) { return first->position() < second->position(); } void Widget::listSelectionChanged(HistoryView::SelectedItems &&items) { HistoryView::TopBarWidget::SelectedState state; state.count = items.size(); for (const auto item : items) { if (item.canForward) { ++state.canForwardCount; } if (item.canDelete) { ++state.canDeleteCount; } } _topBar->showSelected(state); } void Widget::listVisibleItemsChanged(HistoryItemsList &&items) { const auto reversed = ranges::view::reverse(items); const auto good = ranges::find_if(reversed, [](auto item) { return IsServerMsgId(item->id); }); if (good != end(reversed)) { Auth().api().readFeed(_feed, (*good)->position()); } } base::optional<int> Widget::listUnreadBarView( const std::vector<not_null<Element*>> &elements) { const auto position = _feed->unreadPosition(); if (!position || elements.empty() || !_feed->unreadCount()) { return base::none; } const auto minimal = ranges::upper_bound( elements, position, std::less<>(), [](auto view) { return view->data()->position(); }); if (minimal == end(elements)) { return base::none; } const auto view = *minimal; const auto unreadMessagesHeight = elements.back()->y() + elements.back()->height() - view->y(); if (unreadMessagesHeight < _scroll->height()) { return base::none; } return base::make_optional(int(minimal - begin(elements))); } void Widget::validateEmptyTextItem() { if (!_inner->isEmpty()) { _emptyTextView = nullptr; _emptyTextItem = nullptr; update(); return; } else if (_emptyTextItem) { return; } const auto channels = _feed->channels(); if (channels.empty()) { return; } const auto history = channels[0]; _emptyTextItem.reset(new HistoryService( history, clientMsgId(), unixtime(), { lang(lng_feed_no_messages) })); _emptyTextView = _emptyTextItem->createView( HistoryInner::ElementDelegate()); updateControlsGeometry(); update(); } void Widget::listContentRefreshed() { validateEmptyTextItem(); if (!_nextAnimatedScrollPosition) { return; } const auto position = *base::take(_nextAnimatedScrollPosition); if (const auto scrollTop = _inner->scrollTopForPosition(position)) { const auto wanted = snap(*scrollTop, 0, _scroll->scrollTopMax()); _inner->animatedScrollTo( wanted, position, _nextAnimatedScrollDelta, HistoryView::ListWidget::AnimatedScroll::Part); if (const auto highlight = base::take(_highlightMessageId)) { _inner->highlightMessage(highlight); } } } ClickHandlerPtr Widget::listDateLink(not_null<Element*> view) { if (!_dateLink) { _dateLink = std::make_shared<Window::DateClickHandler>(_feed, view->dateTime().date()); } else { _dateLink->setDate(view->dateTime().date()); } return _dateLink; } std::unique_ptr<Window::SectionMemento> Widget::createMemento() { auto result = std::make_unique<Memento>(_feed); saveState(result.get()); return std::move(result); } void Widget::saveState(not_null<Memento*> memento) { _inner->saveState(memento->list()); } void Widget::restoreState(not_null<Memento*> memento) { const auto list = memento->list(); if (!list->aroundPosition()) { if (const auto position = _feed->unreadPosition()) { list->setAroundPosition(position); } } _undefinedAroundPosition = !list->aroundPosition(); _inner->restoreState(memento->list()); if (const auto position = memento->position()) { _currentMessageId = _highlightMessageId = position.fullId; showAtPosition(position); } } void Widget::resizeEvent(QResizeEvent *e) { if (!width() || !height()) { return; } updateControlsGeometry(); } void Widget::updateControlsGeometry() { const auto contentWidth = width(); const auto newScrollTop = _scroll->isHidden() ? base::none : base::make_optional(_scroll->scrollTop() + topDelta()); _topBar->resizeToWidth(contentWidth); _topBarShadow->resize(contentWidth, st::lineWidth); const auto bottom = height(); const auto scrollHeight = bottom - _topBar->height(); // - _showNext->height(); const auto scrollSize = QSize(contentWidth, scrollHeight); if (_scroll->size() != scrollSize) { _skipScrollEvent = true; _scroll->resize(scrollSize); _inner->resizeToWidth(scrollSize.width(), _scroll->height()); _skipScrollEvent = false; } if (!_scroll->isHidden()) { if (newScrollTop) { _scroll->scrollToY(*newScrollTop); } updateInnerVisibleArea(); } updateScrollDownPosition(); //const auto fullWidthButtonRect = myrtlrect( // 0, // bottom - _showNext->height(), // contentWidth, // _showNext->height()); //_showNext->setGeometry(fullWidthButtonRect); if (_emptyTextView) { _emptyTextView->resizeGetHeight(width()); } } void Widget::paintEvent(QPaintEvent *e) { if (animating()) { SectionWidget::paintEvent(e); return; } if (Ui::skipPaintEvent(this, e)) { return; } //if (hasPendingResizedItems()) { // updateListSize(); //} const auto ms = getms(); _scrollDownShown.step(ms); SectionWidget::PaintBackground(this, e); if (_emptyTextView) { Painter p(this); const auto clip = e->rect(); const auto left = 0; const auto top = (height() // - _showNext->height() - _emptyTextView->height()) / 2; p.translate(left, top); _emptyTextView->draw( p, clip.translated(-left, -top), TextSelection(), getms()); } } void Widget::onScroll() { if (_skipScrollEvent) { return; } updateInnerVisibleArea(); } void Widget::updateInnerVisibleArea() { const auto scrollTop = _scroll->scrollTop(); _inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height()); updateScrollDownVisibility(); } void Widget::showAnimatedHook( const Window::SectionSlideParams ¶ms) { _topBar->setAnimatingMode(true); if (params.withTopBarShadow) _topBarShadow->show(); } void Widget::showFinishedHook() { _topBar->setAnimatingMode(false); } bool Widget::wheelEventFromFloatPlayer(QEvent *e) { return _scroll->viewportEvent(e); } QRect Widget::rectForFloatPlayer() const { return mapToGlobal(_scroll->geometry()); } void Widget::forwardSelected() { auto items = _inner->getSelectedItems(); if (items.empty()) { return; } const auto weak = make_weak(this); Window::ShowForwardMessagesBox(std::move(items), [=] { if (const auto strong = weak.data()) { strong->clearSelected(); } }); } void Widget::confirmDeleteSelected() { auto items = _inner->getSelectedItems(); if (items.empty()) { return; } const auto weak = make_weak(this); const auto box = Ui::show(Box<DeleteMessagesBox>(std::move(items))); box->setDeleteConfirmedCallback([=] { if (const auto strong = weak.data()) { strong->clearSelected(); } }); } void Widget::clearSelected() { _inner->cancelSelection(); } } // namespace HistoryFeed ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/feed/history_feed_section.h����������������������������0000664�0000000�0000000�00000011376�13262451251�0026420�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "history/view/history_view_list_widget.h" #include "window/section_widget.h" #include "window/section_memento.h" #include "data/data_feed.h" #include "history/history_item.h" #include "history/admin_log/history_admin_log_item.h" namespace Ui { class ScrollArea; class PlainShadow; class FlatButton; class HistoryDownButton; } // namespace Ui namespace HistoryView { class ListWidget; class TopBarWidget; class Element; } // namespace HistoryView namespace Window { class DateClickHandler; } // namespace Window namespace HistoryFeed { class Memento; class Widget final : public Window::SectionWidget , public HistoryView::ListDelegate { public: using Element = HistoryView::Element; Widget( QWidget *parent, not_null<Window::Controller*> controller, not_null<Data::Feed*> feed); Dialogs::RowDescriptor activeChat() const override; bool hasTopBarShadow() const override { return true; } QPixmap grabForShowAnimation( const Window::SectionSlideParams ¶ms) override; bool showInternal( not_null<Window::SectionMemento*> memento, const Window::SectionShow ¶ms) override; std::unique_ptr<Window::SectionMemento> createMemento() override; void setInternalState( const QRect &geometry, not_null<Memento*> memento); // Float player interface. bool wheelEventFromFloatPlayer(QEvent *e) override; QRect rectForFloatPlayer() const override; bool cmd_search() override; // HistoryView::ListDelegate interface. HistoryView::Context listContext() override; void listScrollTo(int top) override; void listCancelRequest() override; void listDeleteRequest() override; rpl::producer<Data::MessagesSlice> listSource( Data::MessagePosition aroundId, int limitBefore, int limitAfter) override; bool listAllowsMultiSelect() override; bool listIsLessInOrder( not_null<HistoryItem*> first, not_null<HistoryItem*> second) override; void listSelectionChanged( HistoryView::SelectedItems &&items) override; void listVisibleItemsChanged(HistoryItemsList &&items) override; base::optional<int> listUnreadBarView( const std::vector<not_null<Element*>> &elements) override; void listContentRefreshed() override; ClickHandlerPtr listDateLink(not_null<Element*> view) override; protected: void resizeEvent(QResizeEvent *e) override; void paintEvent(QPaintEvent *e) override; void showAnimatedHook( const Window::SectionSlideParams ¶ms) override; void showFinishedHook() override; void doSetInnerFocus() override; private: void checkForSingleChannelFeed(); void onScroll(); void updateInnerVisibleArea(); void updateControlsGeometry(); void updateAdaptiveLayout(); void saveState(not_null<Memento*> memento); void restoreState(not_null<Memento*> memento); void showAtPosition(Data::MessagePosition position); bool showAtPositionNow(Data::MessagePosition position); void validateEmptyTextItem(); void setupScrollDownButton(); void scrollDownClicked(); void scrollDownAnimationFinish(); void updateScrollDownVisibility(); void updateScrollDownPosition(); void forwardSelected(); void confirmDeleteSelected(); void clearSelected(); not_null<Data::Feed*> _feed; object_ptr<Ui::ScrollArea> _scroll; QPointer<HistoryView::ListWidget> _inner; object_ptr<HistoryView::TopBarWidget> _topBar; object_ptr<Ui::PlainShadow> _topBarShadow; object_ptr<Ui::FlatButton> _showNext; bool _skipScrollEvent = false; bool _undefinedAroundPosition = false; std::unique_ptr<HistoryItem, HistoryItem::Destroyer> _emptyTextItem; std::unique_ptr<HistoryView::Element> _emptyTextView; FullMsgId _currentMessageId; FullMsgId _highlightMessageId; base::optional<Data::MessagePosition> _nextAnimatedScrollPosition; int _nextAnimatedScrollDelta = 0; Animation _scrollDownShown; bool _scrollDownIsShown = false; object_ptr<Ui::HistoryDownButton> _scrollDown; std::shared_ptr<Window::DateClickHandler> _dateLink; }; class Memento : public Window::SectionMemento { public: explicit Memento( not_null<Data::Feed*> feed, Data::MessagePosition position = Data::UnreadMessagePosition); ~Memento(); object_ptr<Window::SectionWidget> createWidget( QWidget *parent, not_null<Window::Controller*> controller, Window::Column column, const QRect &geometry) override; not_null<Data::Feed*> feed() const { return _feed; } Data::MessagePosition position() const { return _position; } not_null<HistoryView::ListMemento*> list() const { return _list.get(); } private: not_null<Data::Feed*> _feed; Data::MessagePosition _position; std::unique_ptr<HistoryView::ListMemento> _list; }; } // namespace HistoryFeed ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/history.cpp��������������������������������������������0000664�0000000�0000000�00000221341�13262451251�0023334�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/history.h" #include "history/view/history_view_element.h" #include "history/admin_log/history_admin_log_section.h" #include "history/history_message.h" #include "history/history_media_types.h" #include "history/history_service.h" #include "history/history_item_components.h" #include "history/history_inner_widget.h" #include "dialogs/dialogs_indexed_list.h" #include "styles/style_dialogs.h" #include "data/data_drafts.h" #include "data/data_session.h" #include "lang/lang_keys.h" #include "apiwrap.h" #include "mainwidget.h" #include "mainwindow.h" #include "storage/localstorage.h" #include "observer_peer.h" #include "auth_session.h" #include "window/notifications_manager.h" #include "calls/calls_instance.h" #include "storage/storage_facade.h" #include "storage/storage_shared_media.h" #include "storage/storage_feed_messages.h" #include "data/data_channel_admins.h" #include "data/data_feed.h" #include "ui/text_options.h" #include "core/crash_reports.h" namespace { constexpr auto kStatusShowClientsideTyping = 6000; constexpr auto kStatusShowClientsideRecordVideo = 6000; constexpr auto kStatusShowClientsideUploadVideo = 6000; constexpr auto kStatusShowClientsideRecordVoice = 6000; constexpr auto kStatusShowClientsideUploadVoice = 6000; constexpr auto kStatusShowClientsideRecordRound = 6000; constexpr auto kStatusShowClientsideUploadRound = 6000; constexpr auto kStatusShowClientsideUploadPhoto = 6000; constexpr auto kStatusShowClientsideUploadFile = 6000; constexpr auto kStatusShowClientsideChooseLocation = 6000; constexpr auto kStatusShowClientsideChooseContact = 6000; constexpr auto kStatusShowClientsidePlayGame = 10000; constexpr auto kSetMyActionForMs = 10000; constexpr auto kNewBlockEachMessage = 50; void checkForSwitchInlineButton(HistoryItem *item) { if (item->out() || !item->hasSwitchInlineButton()) { return; } if (const auto user = item->history()->peer->asUser()) { if (!user->botInfo || !user->botInfo->inlineReturnPeerId) { return; } if (const auto markup = item->Get<HistoryMessageReplyMarkup>()) { for_const (auto &row, markup->rows) { for_const (auto &button, row) { if (button.type == HistoryMessageMarkupButton::Type::SwitchInline) { Notify::switchInlineBotButtonReceived(QString::fromUtf8(button.data)); return; } } } } } } } // namespace Histories::Histories() : _a_typings(animation(this, &Histories::step_typings)) , _selfDestructTimer([this] { checkSelfDestructItems(); }) { } History *Histories::find(PeerId peerId) const { if (const auto i = _map.find(peerId); i != _map.end()) { return i->second.get(); } return nullptr; } not_null<History*> Histories::findOrInsert(PeerId peerId) { if (const auto result = find(peerId)) { return result; } const auto [i, ok] = _map.emplace( peerId, std::make_unique<History>(peerId)); return i->second.get(); } void Histories::clear() { App::historyClearMsgs(); _map.clear(); _unreadFull = _unreadMuted = 0; Notify::unreadCounterUpdated(); App::historyClearItems(); typing.clear(); } void Histories::registerSendAction( not_null<History*> history, not_null<UserData*> user, const MTPSendMessageAction &action, TimeId when) { if (history->updateSendActionNeedsAnimating(user, action)) { user->madeAction(when); auto i = typing.find(history); if (i == typing.cend()) { typing.insert(history, getms()); _a_typings.start(); } } } void Histories::step_typings(TimeMs ms, bool timer) { for (auto i = typing.begin(), e = typing.end(); i != e;) { if (i.key()->updateSendActionNeedsAnimating(ms)) { ++i; } else { i = typing.erase(i); } } if (typing.isEmpty()) { _a_typings.stop(); } } void Histories::remove(const PeerId &peer) { const auto i = _map.find(peer); if (i != _map.cend()) { typing.remove(i->second.get()); _map.erase(i); } } HistoryItem *Histories::addNewMessage( const MTPMessage &msg, NewMessageType type) { auto peer = peerFromMessage(msg); if (!peer) return nullptr; auto result = App::history(peer)->addNewMessage(msg, type); if (result && type == NewMessageUnread) { checkForSwitchInlineButton(result); } return result; } int Histories::unreadBadge() const { return _unreadFull - (Global::IncludeMuted() ? 0 : _unreadMuted); } int Histories::unreadMutedCount() const { return _unreadMuted; } void Histories::unreadIncrement(int count, bool muted) { _unreadFull += count; if (muted) { _unreadMuted += count; } if (!muted || Global::IncludeMuted()) { Notify::unreadCounterUpdated(); } } void Histories::unreadMuteChanged(int count, bool muted) { if (muted) { _unreadMuted += count; } else { _unreadMuted -= count; } Notify::unreadCounterUpdated(); } bool Histories::unreadOnlyMuted() const { return Global::IncludeMuted() ? (_unreadMuted >= _unreadFull) : false; } void Histories::selfDestructIn(not_null<HistoryItem*> item, TimeMs delay) { _selfDestructItems.push_back(item->fullId()); if (!_selfDestructTimer.isActive() || _selfDestructTimer.remainingTime() > delay) { _selfDestructTimer.callOnce(delay); } } void Histories::checkSelfDestructItems() { auto now = getms(true); auto nextDestructIn = TimeMs(0); for (auto i = _selfDestructItems.begin(); i != _selfDestructItems.cend();) { if (auto item = App::histItemById(*i)) { if (auto destructIn = item->getSelfDestructIn(now)) { if (nextDestructIn > 0) { accumulate_min(nextDestructIn, destructIn); } else { nextDestructIn = destructIn; } ++i; } else { i = _selfDestructItems.erase(i); } } else { i = _selfDestructItems.erase(i); } } if (nextDestructIn > 0) { _selfDestructTimer.callOnce(nextDestructIn); } } History::History(const PeerId &peerId) : Entry(this) , peer(App::peer(peerId)) , cloudDraftTextCache(st::dialogsTextWidthMin) , _mute(peer->isMuted()) , _sendActionText(st::dialogsTextWidthMin) { if (const auto user = peer->asUser()) { if (user->botInfo) { _outboxReadBefore = std::numeric_limits<MsgId>::max(); } } } void History::clearLastKeyboard() { if (lastKeyboardId) { if (lastKeyboardId == lastKeyboardHiddenId) { lastKeyboardHiddenId = 0; } lastKeyboardId = 0; if (auto main = App::main()) { main->updateBotKeyboard(this); } } lastKeyboardInited = true; lastKeyboardFrom = 0; } int History::height() const { return _height; } void History::removeNotification(not_null<HistoryItem*> item) { if (!notifies.isEmpty()) { for (auto i = notifies.begin(), e = notifies.end(); i != e; ++i) { if ((*i) == item) { notifies.erase(i); break; } } } } HistoryItem *History::currentNotification() { return notifies.isEmpty() ? 0 : notifies.front(); } bool History::hasNotification() const { return !notifies.isEmpty(); } void History::skipNotification() { if (!notifies.isEmpty()) { notifies.pop_front(); } } void History::popNotification(HistoryItem *item) { if (!notifies.isEmpty() && notifies.back() == item) notifies.pop_back(); } bool History::hasPendingResizedItems() const { return _flags & Flag::f_has_pending_resized_items; } void History::setHasPendingResizedItems() { _flags |= Flag::f_has_pending_resized_items; } void History::itemRemoved(not_null<HistoryItem*> item) { item->removeMainView(); if (lastMessage() == item) { _lastMessage = base::none; if (loadedAtBottom()) { if (const auto last = lastAvailableMessage()) { setLastMessage(last); } } if (const auto channel = peer->asChannel()) { if (const auto feed = channel->feed()) { // Must be after history->lastMessage() is updated. // Otherwise feed last message will be this value again. feed->messageRemoved(item); } } } itemVanished(item); } void History::itemVanished(not_null<HistoryItem*> item) { removeNotification(item); if (lastKeyboardId == item->id) { clearLastKeyboard(); } if ((!item->out() || item->isPost()) && item->unread() && unreadCount() > 0) { changeUnreadCount(-1); } if (const auto channel = peer->asChannel()) { if (channel->pinnedMessageId() == item->id) { channel->clearPinnedMessage(); } } } void History::setLocalDraft(std::unique_ptr<Data::Draft> &&draft) { _localDraft = std::move(draft); } void History::takeLocalDraft(History *from) { if (auto &draft = from->_localDraft) { if (!draft->textWithTags.text.isEmpty() && !_localDraft) { _localDraft = std::move(draft); // Edit and reply to drafts can't migrate. // Cloud drafts do not migrate automatically. _localDraft->msgId = 0; } from->clearLocalDraft(); Auth().api().saveDraftToCloudDelayed(from); } } void History::createLocalDraftFromCloud() { auto draft = cloudDraft(); if (Data::draftIsNull(draft) || !draft->date) { return; } auto existing = localDraft(); if (Data::draftIsNull(existing) || !existing->date || draft->date >= existing->date) { if (!existing) { setLocalDraft(std::make_unique<Data::Draft>( draft->textWithTags, draft->msgId, draft->cursor, draft->previewCancelled)); existing = localDraft(); } else if (existing != draft) { existing->textWithTags = draft->textWithTags; existing->msgId = draft->msgId; existing->cursor = draft->cursor; existing->previewCancelled = draft->previewCancelled; } existing->date = draft->date; } } void History::setCloudDraft(std::unique_ptr<Data::Draft> &&draft) { _cloudDraft = std::move(draft); cloudDraftTextCache.clear(); } Data::Draft *History::createCloudDraft(Data::Draft *fromDraft) { if (Data::draftIsNull(fromDraft)) { setCloudDraft(std::make_unique<Data::Draft>( TextWithTags(), 0, MessageCursor(), false)); cloudDraft()->date = TimeId(0); } else { auto existing = cloudDraft(); if (!existing) { setCloudDraft(std::make_unique<Data::Draft>( fromDraft->textWithTags, fromDraft->msgId, fromDraft->cursor, fromDraft->previewCancelled)); existing = cloudDraft(); } else if (existing != fromDraft) { existing->textWithTags = fromDraft->textWithTags; existing->msgId = fromDraft->msgId; existing->cursor = fromDraft->cursor; existing->previewCancelled = fromDraft->previewCancelled; } existing->date = unixtime(); } cloudDraftTextCache.clear(); updateChatListSortPosition(); return cloudDraft(); } void History::setEditDraft(std::unique_ptr<Data::Draft> &&draft) { _editDraft = std::move(draft); } void History::clearLocalDraft() { _localDraft = nullptr; } void History::clearCloudDraft() { if (_cloudDraft) { _cloudDraft = nullptr; cloudDraftTextCache.clear(); updateChatListSortPosition(); } } void History::clearEditDraft() { _editDraft = nullptr; } void History::draftSavedToCloud() { updateChatListEntry(); if (App::main()) App::main()->writeDrafts(this); } HistoryItemsList History::validateForwardDraft() { auto result = Auth().data().idsToItems(_forwardDraft); if (result.size() != _forwardDraft.size()) { setForwardDraft(Auth().data().itemsToIds(result)); } return result; } void History::setForwardDraft(MessageIdsList &&items) { _forwardDraft = std::move(items); } bool History::updateSendActionNeedsAnimating( not_null<UserData*> user, const MTPSendMessageAction &action) { if (peer->isSelf()) { return false; } using Type = SendAction::Type; if (action.type() == mtpc_sendMessageCancelAction) { clearSendAction(user); return false; } auto ms = getms(); switch (action.type()) { case mtpc_sendMessageTypingAction: _typing.insert(user, ms + kStatusShowClientsideTyping); break; case mtpc_sendMessageRecordVideoAction: _sendActions.insert(user, { Type::RecordVideo, ms + kStatusShowClientsideRecordVideo }); break; case mtpc_sendMessageUploadVideoAction: _sendActions.insert(user, { Type::UploadVideo, ms + kStatusShowClientsideUploadVideo, action.c_sendMessageUploadVideoAction().vprogress.v }); break; case mtpc_sendMessageRecordAudioAction: _sendActions.insert(user, { Type::RecordVoice, ms + kStatusShowClientsideRecordVoice }); break; case mtpc_sendMessageUploadAudioAction: _sendActions.insert(user, { Type::UploadVoice, ms + kStatusShowClientsideUploadVoice, action.c_sendMessageUploadAudioAction().vprogress.v }); break; case mtpc_sendMessageRecordRoundAction: _sendActions.insert(user, { Type::RecordRound, ms + kStatusShowClientsideRecordRound }); break; case mtpc_sendMessageUploadRoundAction: _sendActions.insert(user, { Type::UploadRound, ms + kStatusShowClientsideUploadRound }); break; case mtpc_sendMessageUploadPhotoAction: _sendActions.insert(user, { Type::UploadPhoto, ms + kStatusShowClientsideUploadPhoto, action.c_sendMessageUploadPhotoAction().vprogress.v }); break; case mtpc_sendMessageUploadDocumentAction: _sendActions.insert(user, { Type::UploadFile, ms + kStatusShowClientsideUploadFile, action.c_sendMessageUploadDocumentAction().vprogress.v }); break; case mtpc_sendMessageGeoLocationAction: _sendActions.insert(user, { Type::ChooseLocation, ms + kStatusShowClientsideChooseLocation }); break; case mtpc_sendMessageChooseContactAction: _sendActions.insert(user, { Type::ChooseContact, ms + kStatusShowClientsideChooseContact }); break; case mtpc_sendMessageGamePlayAction: { auto it = _sendActions.find(user); if (it == _sendActions.end() || it->type == Type::PlayGame || it->until <= ms) { _sendActions.insert(user, { Type::PlayGame, ms + kStatusShowClientsidePlayGame }); } } break; default: return false; } return updateSendActionNeedsAnimating(ms, true); } bool History::mySendActionUpdated(SendAction::Type type, bool doing) { auto ms = getms(true); auto i = _mySendActions.find(type); if (doing) { if (i == _mySendActions.cend()) { _mySendActions.insert(type, ms + kSetMyActionForMs); } else if (i.value() > ms + (kSetMyActionForMs / 2)) { return false; } else { i.value() = ms + kSetMyActionForMs; } } else { if (i == _mySendActions.cend()) { return false; } else if (i.value() <= ms) { return false; } else { _mySendActions.erase(i); } } return true; } bool History::paintSendAction( Painter &p, int x, int y, int availableWidth, int outerWidth, style::color color, TimeMs ms) { if (_sendActionAnimation) { _sendActionAnimation.paint( p, color, x, y + st::normalFont->ascent, outerWidth, ms); auto animationWidth = _sendActionAnimation.width(); x += animationWidth; availableWidth -= animationWidth; p.setPen(color); _sendActionText.drawElided(p, x, y, availableWidth); return true; } return false; } bool History::updateSendActionNeedsAnimating(TimeMs ms, bool force) { auto changed = force; for (auto i = _typing.begin(), e = _typing.end(); i != e;) { if (ms >= i.value()) { i = _typing.erase(i); changed = true; } else { ++i; } } for (auto i = _sendActions.begin(); i != _sendActions.cend();) { if (ms >= i.value().until) { i = _sendActions.erase(i); changed = true; } else { ++i; } } if (changed) { QString newTypingString; auto typingCount = _typing.size(); if (typingCount > 2) { newTypingString = lng_many_typing(lt_count, typingCount); } else if (typingCount > 1) { newTypingString = lng_users_typing(lt_user, _typing.begin().key()->firstName, lt_second_user, (_typing.end() - 1).key()->firstName); } else if (typingCount) { newTypingString = peer->isUser() ? lang(lng_typing) : lng_user_typing(lt_user, _typing.begin().key()->firstName); } else if (!_sendActions.isEmpty()) { // Handles all actions except game playing. using Type = SendAction::Type; auto sendActionString = [](Type type, const QString &name) -> QString { switch (type) { case Type::RecordVideo: return name.isEmpty() ? lang(lng_send_action_record_video) : lng_user_action_record_video(lt_user, name); case Type::UploadVideo: return name.isEmpty() ? lang(lng_send_action_upload_video) : lng_user_action_upload_video(lt_user, name); case Type::RecordVoice: return name.isEmpty() ? lang(lng_send_action_record_audio) : lng_user_action_record_audio(lt_user, name); case Type::UploadVoice: return name.isEmpty() ? lang(lng_send_action_upload_audio) : lng_user_action_upload_audio(lt_user, name); case Type::RecordRound: return name.isEmpty() ? lang(lng_send_action_record_round) : lng_user_action_record_round(lt_user, name); case Type::UploadRound: return name.isEmpty() ? lang(lng_send_action_upload_round) : lng_user_action_upload_round(lt_user, name); case Type::UploadPhoto: return name.isEmpty() ? lang(lng_send_action_upload_photo) : lng_user_action_upload_photo(lt_user, name); case Type::UploadFile: return name.isEmpty() ? lang(lng_send_action_upload_file) : lng_user_action_upload_file(lt_user, name); case Type::ChooseLocation: case Type::ChooseContact: return name.isEmpty() ? lang(lng_typing) : lng_user_typing(lt_user, name); default: break; }; return QString(); }; for (auto i = _sendActions.cbegin(), e = _sendActions.cend(); i != e; ++i) { newTypingString = sendActionString(i->type, peer->isUser() ? QString() : i.key()->firstName); if (!newTypingString.isEmpty()) { _sendActionAnimation.start(i->type); break; } } // Everyone in sendActions are playing a game. if (newTypingString.isEmpty()) { int playingCount = _sendActions.size(); if (playingCount > 2) { newTypingString = lng_many_playing_game(lt_count, playingCount); } else if (playingCount > 1) { newTypingString = lng_users_playing_game(lt_user, _sendActions.begin().key()->firstName, lt_second_user, (_sendActions.end() - 1).key()->firstName); } else { newTypingString = peer->isUser() ? lang(lng_playing_game) : lng_user_playing_game(lt_user, _sendActions.begin().key()->firstName); } _sendActionAnimation.start(Type::PlayGame); } } if (typingCount > 0) { _sendActionAnimation.start(SendAction::Type::Typing); } else if (newTypingString.isEmpty()) { _sendActionAnimation.stop(); } if (_sendActionString != newTypingString) { _sendActionString = newTypingString; _sendActionText.setText( st::dialogsTextStyle, _sendActionString, Ui::NameTextOptions()); } } auto result = (!_typing.isEmpty() || !_sendActions.isEmpty()); if (changed || result) { App::histories().sendActionAnimationUpdated().notify({ this, _sendActionAnimation.width(), st::normalFont->height, changed }); } return result; } HistoryItem *History::createItem( const MTPMessage &message, bool detachExistingItem) { const auto messageId = idFromMessage(message); if (!messageId) { return nullptr; } if (const auto result = App::histItemById(channelId(), messageId)) { if (detachExistingItem) { result->removeMainView(); } if (message.type() == mtpc_message) { const auto media = message.c_message().has_media() ? &message.c_message().vmedia : nullptr; result->updateSentMedia(media); } return result; } return HistoryItem::Create(this, message); } std::vector<not_null<HistoryItem*>> History::createItems( const QVector<MTPMessage> &data) { auto result = std::vector<not_null<HistoryItem*>>(); result.reserve(data.size()); for (auto i = data.cend(), e = data.cbegin(); i != e;) { const auto detachExistingItem = true; if (const auto item = createItem(*--i, detachExistingItem)) { result.push_back(item); } } return result; } not_null<HistoryItem*> History::addNewService( MsgId msgId, TimeId date, const QString &text, MTPDmessage::Flags flags, bool unread) { auto message = HistoryService::PreparedText { text }; return addNewItem( new HistoryService(this, msgId, date, message, flags), unread); } HistoryItem *History::addNewMessage( const MTPMessage &msg, NewMessageType type) { if (type == NewMessageExisting) { return addToHistory(msg); } if (!loadedAtBottom() || peer->migrateTo()) { if (const auto item = addToHistory(msg)) { setLastMessage(item); if (type == NewMessageUnread) { newItemAdded(item); } return item; } return nullptr; } return addNewToLastBlock(msg, type); } HistoryItem *History::addNewToLastBlock( const MTPMessage &msg, NewMessageType type) { Expects(type != NewMessageExisting); const auto detachExistingItem = (type != NewMessageLast); const auto item = createItem(msg, detachExistingItem); if (!item || item->mainView()) { return item; } const auto newUnreadMessage = (type == NewMessageUnread); if (newUnreadMessage) { applyMessageChanges(item, msg); } const auto result = addNewItem(item, newUnreadMessage); if (type == NewMessageLast) { // When we add just one last item, like we do while loading dialogs, // we want to remove a single added grouped media, otherwise it will // jump once we open the message history (first we show only that // media, then we load the rest of the group and show the group). // // That way when we open the message history we show nothing until a // whole history part is loaded, it certainly will contain the group. removeOrphanMediaGroupPart(); } return result; } HistoryItem *History::addToHistory(const MTPMessage &msg) { const auto detachExistingItem = false; return createItem(msg, detachExistingItem); } not_null<HistoryItem*> History::addNewForwarded( MsgId id, MTPDmessage::Flags flags, TimeId date, UserId from, const QString &postAuthor, not_null<HistoryMessage*> original) { return addNewItem( new HistoryMessage( this, id, flags, date, from, postAuthor, original), true); } not_null<HistoryItem*> History::addNewDocument( MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, TimeId date, UserId from, const QString &postAuthor, not_null<DocumentData*> document, const TextWithEntities &caption, const MTPReplyMarkup &markup) { return addNewItem( new HistoryMessage( this, id, flags, replyTo, viaBotId, date, from, postAuthor, document, caption, markup), true); } not_null<HistoryItem*> History::addNewPhoto( MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, TimeId date, UserId from, const QString &postAuthor, not_null<PhotoData*> photo, const TextWithEntities &caption, const MTPReplyMarkup &markup) { return addNewItem( new HistoryMessage( this, id, flags, replyTo, viaBotId, date, from, postAuthor, photo, caption, markup), true); } not_null<HistoryItem*> History::addNewGame( MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, TimeId date, UserId from, const QString &postAuthor, not_null<GameData*> game, const MTPReplyMarkup &markup) { return addNewItem( new HistoryMessage( this, id, flags, replyTo, viaBotId, date, from, postAuthor, game, markup), true); } void History::setUnreadMentionsCount(int count) { if (_unreadMentions.size() > count) { LOG(("API Warning: real mentions count is greater than received mentions count")); count = _unreadMentions.size(); } _unreadMentionsCount = count; } bool History::addToUnreadMentions( MsgId msgId, UnreadMentionType type) { if (peer->isChannel() && !peer->isMegagroup()) { return false; } auto allLoaded = _unreadMentionsCount ? (_unreadMentions.size() >= *_unreadMentionsCount) : false; if (allLoaded) { if (type == UnreadMentionType::New) { ++*_unreadMentionsCount; _unreadMentions.insert(msgId); return true; } } else if (!_unreadMentions.empty() && type != UnreadMentionType::New) { _unreadMentions.insert(msgId); return true; } return false; } void History::eraseFromUnreadMentions(MsgId msgId) { _unreadMentions.remove(msgId); if (_unreadMentionsCount) { if (*_unreadMentionsCount > 0) { --*_unreadMentionsCount; } } Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::UnreadMentionsChanged); } void History::addUnreadMentionsSlice(const MTPmessages_Messages &result) { auto count = 0; auto messages = (const QVector<MTPMessage>*)nullptr; auto getMessages = [](auto &list) { App::feedUsers(list.vusers); App::feedChats(list.vchats); return &list.vmessages.v; }; switch (result.type()) { case mtpc_messages_messages: { auto &d = result.c_messages_messages(); messages = getMessages(d); count = messages->size(); } break; case mtpc_messages_messagesSlice: { auto &d = result.c_messages_messagesSlice(); messages = getMessages(d); count = d.vcount.v; } break; case mtpc_messages_channelMessages: { LOG(("API Error: unexpected messages.channelMessages! (History::addUnreadMentionsSlice)")); auto &d = result.c_messages_channelMessages(); messages = getMessages(d); count = d.vcount.v; } break; case mtpc_messages_messagesNotModified: { LOG(("API Error: received messages.messagesNotModified! (History::addUnreadMentionsSlice)")); } break; default: Unexpected("type in History::addUnreadMentionsSlice"); } auto added = false; if (messages) { for (auto &message : *messages) { if (auto item = addToHistory(message)) { if (item->mentionsMe() && item->isMediaUnread()) { _unreadMentions.insert(item->id); added = true; } } } } if (!added) { count = _unreadMentions.size(); } setUnreadMentionsCount(count); Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::UnreadMentionsChanged); } not_null<HistoryItem*> History::addNewItem( not_null<HistoryItem*> item, bool unread) { Expects(!isBuildingFrontBlock()); addItemToBlock(item); if (!unread && IsServerMsgId(item->id)) { if (const auto sharedMediaTypes = item->sharedMediaTypes()) { auto from = loadedAtTop() ? 0 : minMsgId(); auto till = loadedAtBottom() ? ServerMaxMsgId : maxMsgId(); Auth().storage().add(Storage::SharedMediaAddExisting( peer->id, sharedMediaTypes, item->id, { from, till })); } } if (item->from()->id) { if (auto user = item->from()->asUser()) { auto getLastAuthors = [this]() -> std::deque<not_null<UserData*>>* { if (auto chat = peer->asChat()) { return &chat->lastAuthors; } else if (auto channel = peer->asMegagroup()) { return &channel->mgInfo->lastParticipants; } return nullptr; }; if (auto megagroup = peer->asMegagroup()) { if (user->botInfo) { auto mgInfo = megagroup->mgInfo.get(); Assert(mgInfo != nullptr); mgInfo->bots.insert(user); if (mgInfo->botStatus != 0 && mgInfo->botStatus < 2) { mgInfo->botStatus = 2; } } } if (auto lastAuthors = getLastAuthors()) { auto prev = ranges::find( *lastAuthors, user, [](not_null<UserData*> user) { return user.get(); }); auto index = (prev != lastAuthors->end()) ? (lastAuthors->end() - prev) : -1; if (index > 0) { lastAuthors->erase(prev); } else if (index < 0 && peer->isMegagroup()) { // nothing is outdated if just reordering // admins information outdated } if (index) { lastAuthors->push_front(user); } if (auto megagroup = peer->asMegagroup()) { Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::MembersChanged); Auth().data().addNewMegagroupParticipant(megagroup, user); } } } if (item->definesReplyKeyboard()) { auto markupFlags = item->replyKeyboardFlags(); if (!(markupFlags & MTPDreplyKeyboardMarkup::Flag::f_selective) || item->mentionsMe()) { auto getMarkupSenders = [this]() -> base::flat_set<not_null<PeerData*>>* { if (auto chat = peer->asChat()) { return &chat->markupSenders; } else if (auto channel = peer->asMegagroup()) { return &channel->mgInfo->markupSenders; } return nullptr; }; if (auto markupSenders = getMarkupSenders()) { markupSenders->insert(item->from()); } if (markupFlags & MTPDreplyKeyboardMarkup_ClientFlag::f_zero) { // zero markup means replyKeyboardHide if (lastKeyboardFrom == item->from()->id || (!lastKeyboardInited && !peer->isChat() && !peer->isMegagroup() && !item->out())) { clearLastKeyboard(); } } else { bool botNotInChat = false; if (peer->isChat()) { botNotInChat = item->from()->isUser() && (!peer->asChat()->participants.empty() || !peer->canWrite()) && !peer->asChat()->participants.contains( item->from()->asUser()); } else if (peer->isMegagroup()) { botNotInChat = item->from()->isUser() && (peer->asChannel()->mgInfo->botStatus != 0 || !peer->canWrite()) && !peer->asChannel()->mgInfo->bots.contains( item->from()->asUser()); } if (botNotInChat) { clearLastKeyboard(); } else { lastKeyboardInited = true; lastKeyboardId = item->id; lastKeyboardFrom = item->from()->id; lastKeyboardUsed = false; } } } } } setLastMessage(item); if (unread) { newItemAdded(item); } Auth().data().notifyHistoryChangeDelayed(this); return item; } void History::applyMessageChanges( not_null<HistoryItem*> item, const MTPMessage &data) { if (data.type() == mtpc_messageService) { applyServiceChanges(item, data.c_messageService()); } App::checkSavedGif(item); } void History::applyServiceChanges( not_null<HistoryItem*> item, const MTPDmessageService &data) { auto &action = data.vaction; switch (action.type()) { case mtpc_messageActionChatAddUser: { auto &d = action.c_messageActionChatAddUser(); if (auto megagroup = peer->asMegagroup()) { auto mgInfo = megagroup->mgInfo.get(); Assert(mgInfo != nullptr); auto &v = d.vusers.v; for (auto i = 0, l = v.size(); i != l; ++i) { if (auto user = App::userLoaded(peerFromUser(v[i]))) { if (!base::contains(mgInfo->lastParticipants, user)) { mgInfo->lastParticipants.push_front(user); Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::MembersChanged); Auth().data().addNewMegagroupParticipant(megagroup, user); } if (user->botInfo) { peer->asChannel()->mgInfo->bots.insert(user); if (peer->asChannel()->mgInfo->botStatus != 0 && peer->asChannel()->mgInfo->botStatus < 2) { peer->asChannel()->mgInfo->botStatus = 2; } } } } } } break; case mtpc_messageActionChatJoinedByLink: { auto &d = action.c_messageActionChatJoinedByLink(); if (auto megagroup = peer->asMegagroup()) { auto mgInfo = megagroup->mgInfo.get(); Assert(mgInfo != nullptr); if (auto user = item->from()->asUser()) { if (!base::contains(mgInfo->lastParticipants, user)) { mgInfo->lastParticipants.push_front(user); Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::MembersChanged); Auth().data().addNewMegagroupParticipant(megagroup, user); } if (user->botInfo) { mgInfo->bots.insert(user); if (mgInfo->botStatus != 0 && mgInfo->botStatus < 2) { mgInfo->botStatus = 2; } } } } } break; case mtpc_messageActionChatDeletePhoto: { if (const auto chat = peer->asChat()) { chat->setPhoto(MTP_chatPhotoEmpty()); } } break; case mtpc_messageActionChatDeleteUser: { auto &d = action.c_messageActionChatDeleteUser(); auto uid = peerFromUser(d.vuser_id); if (lastKeyboardFrom == uid) { clearLastKeyboard(); } if (auto megagroup = peer->asMegagroup()) { if (auto user = App::userLoaded(uid)) { auto mgInfo = megagroup->mgInfo.get(); Assert(mgInfo != nullptr); auto i = ranges::find( mgInfo->lastParticipants, user, [](not_null<UserData*> user) { return user.get(); }); if (i != mgInfo->lastParticipants.end()) { mgInfo->lastParticipants.erase(i); Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::MembersChanged); } Auth().data().removeMegagroupParticipant(megagroup, user); if (megagroup->membersCount() > 1) { megagroup->setMembersCount(megagroup->membersCount() - 1); } else { mgInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsCountOutdated; mgInfo->lastParticipantsCount = 0; } if (mgInfo->lastAdmins.contains(user)) { mgInfo->lastAdmins.remove(user); if (megagroup->adminsCount() > 1) { megagroup->setAdminsCount(megagroup->adminsCount() - 1); } Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::AdminsChanged); } mgInfo->bots.remove(user); if (mgInfo->bots.empty() && mgInfo->botStatus > 0) { mgInfo->botStatus = -1; } } Data::ChannelAdminChanges(megagroup).feed(uid, false); } } break; case mtpc_messageActionChatEditPhoto: { auto &d = action.c_messageActionChatEditPhoto(); if (d.vphoto.type() == mtpc_photo) { auto &sizes = d.vphoto.c_photo().vsizes.v; if (!sizes.isEmpty()) { auto photo = Auth().data().photo(d.vphoto.c_photo()); if (photo) photo->peer = peer; auto &smallSize = sizes.front(); auto &bigSize = sizes.back(); const MTPFileLocation *smallLoc = 0, *bigLoc = 0; switch (smallSize.type()) { case mtpc_photoSize: smallLoc = &smallSize.c_photoSize().vlocation; break; case mtpc_photoCachedSize: smallLoc = &smallSize.c_photoCachedSize().vlocation; break; } switch (bigSize.type()) { case mtpc_photoSize: bigLoc = &bigSize.c_photoSize().vlocation; break; case mtpc_photoCachedSize: bigLoc = &bigSize.c_photoCachedSize().vlocation; break; } if (smallLoc && bigLoc) { const auto newPhotoId = photo ? photo->id : 0; if (const auto chat = peer->asChat()) { chat->setPhoto(newPhotoId, MTP_chatPhoto(*smallLoc, *bigLoc)); } else if (const auto channel = peer->asChannel()) { channel->setPhoto(newPhotoId, MTP_chatPhoto(*smallLoc, *bigLoc)); } peer->loadUserpic(); } } } } break; case mtpc_messageActionChatEditTitle: { auto &d = action.c_messageActionChatEditTitle(); if (auto chat = peer->asChat()) { chat->setName(qs(d.vtitle)); } } break; case mtpc_messageActionChatMigrateTo: { if (auto chat = peer->asChat()) { chat->addFlags(MTPDchat::Flag::f_deactivated); } //auto &d = action.c_messageActionChatMigrateTo(); //auto channel = App::channelLoaded(d.vchannel_id.v); } break; case mtpc_messageActionChannelMigrateFrom: { //auto &d = action.c_messageActionChannelMigrateFrom(); //auto chat = App::chatLoaded(d.vchat_id.v); } break; case mtpc_messageActionPinMessage: { if (data.has_reply_to_msg_id() && item) { if (auto channel = item->history()->peer->asChannel()) { channel->setPinnedMessageId(data.vreply_to_msg_id.v); } } } break; case mtpc_messageActionPhoneCall: { Calls::Current().newServiceMessage().notify(item->fullId()); } break; } } void History::clearSendAction(not_null<UserData*> from) { auto updateAtMs = TimeMs(0); auto i = _typing.find(from); if (i != _typing.cend()) { updateAtMs = getms(); i.value() = updateAtMs; } auto j = _sendActions.find(from); if (j != _sendActions.cend()) { if (!updateAtMs) updateAtMs = getms(); j.value().until = updateAtMs; } if (updateAtMs) { updateSendActionNeedsAnimating(updateAtMs, true); } } void History::mainViewRemoved( not_null<HistoryBlock*> block, not_null<HistoryView::Element*> view) { if (_joinedMessage == view->data()) { _joinedMessage = nullptr; } if (_firstUnreadView == view) { getNextFirstUnreadMessage(); } if (_unreadBarView == view) { _unreadBarView = nullptr; } if (scrollTopItem == view) { getNextScrollTopItem(block, view->indexInBlock()); } } void History::newItemAdded(not_null<HistoryItem*> item) { App::checkImageCacheSize(); item->indexAsNewItem(); if (const auto from = item->from() ? item->from()->asUser() : nullptr) { if (from == item->author()) { clearSendAction(from); } from->madeAction(item->date()); } if (item->out()) { destroyUnreadBar(); if (!item->unread()) { outboxRead(item); } } else if (item->unread()) { if (!isChannel() || peer->asChannel()->amIn()) { notifies.push_back(item); App::main()->newUnreadMsg(this, item); } } else if (!item->isGroupMigrate() || !peer->isMegagroup()) { inboxRead(item); } } HistoryBlock *History::prepareBlockForAddingItem() { if (isBuildingFrontBlock()) { if (_buildingFrontBlock->block) { return _buildingFrontBlock->block; } blocks.push_front(std::make_unique<HistoryBlock>(this)); for (auto i = 0, l = int(blocks.size()); i != l; ++i) { blocks[i]->setIndexInHistory(i); } _buildingFrontBlock->block = blocks.front().get(); if (_buildingFrontBlock->expectedItemsCount > 0) { _buildingFrontBlock->block->messages.reserve( _buildingFrontBlock->expectedItemsCount + 1); } return _buildingFrontBlock->block; } const auto addNewBlock = blocks.empty() || (blocks.back()->messages.size() >= kNewBlockEachMessage); if (addNewBlock) { blocks.push_back(std::make_unique<HistoryBlock>(this)); blocks.back()->setIndexInHistory(blocks.size() - 1); blocks.back()->messages.reserve(kNewBlockEachMessage); } return blocks.back().get(); } void History::viewReplaced(not_null<const Element*> was, Element *now) { if (scrollTopItem == was) scrollTopItem= now; if (_firstUnreadView == was) _firstUnreadView= now; if (_unreadBarView == was) _unreadBarView = now; } void History::addItemToBlock(not_null<HistoryItem*> item) { Expects(!item->mainView()); auto block = prepareBlockForAddingItem(); block->messages.push_back(item->createView( HistoryInner::ElementDelegate())); const auto view = block->messages.back().get(); view->attachToBlock(block, block->messages.size() - 1); if (isBuildingFrontBlock() && _buildingFrontBlock->expectedItemsCount > 0) { --_buildingFrontBlock->expectedItemsCount; } } void History::addEdgesToSharedMedia() { auto from = loadedAtTop() ? 0 : minMsgId(); auto till = loadedAtBottom() ? ServerMaxMsgId : maxMsgId(); for (auto i = 0; i != Storage::kSharedMediaTypeCount; ++i) { const auto type = static_cast<Storage::SharedMediaType>(i); Auth().storage().add(Storage::SharedMediaAddSlice( peer->id, type, {}, { from, till })); } } void History::addOlderSlice(const QVector<MTPMessage> &slice) { if (slice.isEmpty()) { _loadedAtTop = true; checkJoinedMessage(); return; } if (const auto added = createItems(slice); !added.empty()) { startBuildingFrontBlock(added.size()); for (const auto item : added) { addItemToBlock(item); } finishBuildingFrontBlock(); if (loadedAtBottom()) { // Add photos to overview and authors to lastAuthors. addItemsToLists(added); } addToSharedMedia(added); } else { // If no items were added it means we've loaded everything old. _loadedAtTop = true; addEdgesToSharedMedia(); } checkJoinedMessage(); checkLastMessage(); } void History::addNewerSlice(const QVector<MTPMessage> &slice) { bool wasEmpty = isEmpty(), wasLoadedAtBottom = loadedAtBottom(); if (slice.isEmpty()) { _loadedAtBottom = true; if (!lastMessage()) { setLastMessage(lastAvailableMessage()); } } if (const auto added = createItems(slice); !added.empty()) { Assert(!isBuildingFrontBlock()); for (const auto item : added) { addItemToBlock(item); } addToSharedMedia(added); } else { _loadedAtBottom = true; setLastMessage(lastAvailableMessage()); addEdgesToSharedMedia(); } if (!wasLoadedAtBottom) { checkAddAllToUnreadMentions(); } checkJoinedMessage(); checkLastMessage(); } void History::checkLastMessage() { if (const auto last = lastMessage()) { if (!_loadedAtBottom && last->mainView()) { _loadedAtBottom = true; checkAddAllToUnreadMentions(); } } else if (_loadedAtBottom) { setLastMessage(lastAvailableMessage()); } } void History::addItemsToLists( const std::vector<not_null<HistoryItem*>> &items) { std::deque<not_null<UserData*>> *lastAuthors = nullptr; base::flat_set<not_null<PeerData*>> *markupSenders = nullptr; if (peer->isChat()) { lastAuthors = &peer->asChat()->lastAuthors; markupSenders = &peer->asChat()->markupSenders; } else if (peer->isMegagroup()) { // We don't add users to mgInfo->lastParticipants here. // We're scrolling back and we see messages from users that // could be gone from the megagroup already. It is fine for // chat->lastAuthors, because they're used only for field // autocomplete, but this is bad for megagroups, because its // lastParticipants are displayed in Profile as members list. markupSenders = &peer->asChannel()->mgInfo->markupSenders; } for (const auto item : base::reversed(items)) { item->addToUnreadMentions(UnreadMentionType::Existing); if (item->from()->id) { if (lastAuthors) { // chats if (auto user = item->from()->asUser()) { if (!base::contains(*lastAuthors, user)) { lastAuthors->push_back(user); } } } } if (item->author()->id) { if (markupSenders) { // chats with bots if (!lastKeyboardInited && item->definesReplyKeyboard() && !item->out()) { auto markupFlags = item->replyKeyboardFlags(); if (!(markupFlags & MTPDreplyKeyboardMarkup::Flag::f_selective) || item->mentionsMe()) { bool wasKeyboardHide = markupSenders->contains(item->author()); if (!wasKeyboardHide) { markupSenders->insert(item->author()); } if (!(markupFlags & MTPDreplyKeyboardMarkup_ClientFlag::f_zero)) { if (!lastKeyboardInited) { bool botNotInChat = false; if (peer->isChat()) { botNotInChat = (!peer->canWrite() || !peer->asChat()->participants.empty()) && item->author()->isUser() && !peer->asChat()->participants.contains(item->author()->asUser()); } else if (peer->isMegagroup()) { botNotInChat = (!peer->canWrite() || peer->asChannel()->mgInfo->botStatus != 0) && item->author()->isUser() && !peer->asChannel()->mgInfo->bots.contains(item->author()->asUser()); } if (wasKeyboardHide || botNotInChat) { clearLastKeyboard(); } else { lastKeyboardInited = true; lastKeyboardId = item->id; lastKeyboardFrom = item->author()->id; lastKeyboardUsed = false; } } } } } } else if (!lastKeyboardInited && item->definesReplyKeyboard() && !item->out()) { // conversations with bots MTPDreplyKeyboardMarkup::Flags markupFlags = item->replyKeyboardFlags(); if (!(markupFlags & MTPDreplyKeyboardMarkup::Flag::f_selective) || item->mentionsMe()) { if (markupFlags & MTPDreplyKeyboardMarkup_ClientFlag::f_zero) { clearLastKeyboard(); } else { lastKeyboardInited = true; lastKeyboardId = item->id; lastKeyboardFrom = item->author()->id; lastKeyboardUsed = false; } } } } } } void History::checkAddAllToUnreadMentions() { if (!loadedAtBottom()) { return; } for (const auto &block : blocks) { for (const auto &message : block->messages) { const auto item = message->data(); item->addToUnreadMentions(UnreadMentionType::Existing); } } } void History::addToSharedMedia( const std::vector<not_null<HistoryItem*>> &items) { std::vector<MsgId> medias[Storage::kSharedMediaTypeCount]; for (const auto item : items) { if (const auto types = item->sharedMediaTypes()) { for (auto i = 0; i != Storage::kSharedMediaTypeCount; ++i) { const auto type = static_cast<Storage::SharedMediaType>(i); if (types.test(type)) { if (medias[i].empty()) { medias[i].reserve(items.size()); } medias[i].push_back(item->id); } } } } const auto from = loadedAtTop() ? 0 : minMsgId(); const auto till = loadedAtBottom() ? ServerMaxMsgId : maxMsgId(); for (auto i = 0; i != Storage::kSharedMediaTypeCount; ++i) { if (!medias[i].empty()) { const auto type = static_cast<Storage::SharedMediaType>(i); Auth().storage().add(Storage::SharedMediaAddSlice( peer->id, type, std::move(medias[i]), { from, till })); } } } int History::countUnread(MsgId upTo) { int result = 0; for (auto i = blocks.cend(), e = blocks.cbegin(); i != e;) { --i; const auto &messages = (*i)->messages; for (auto j = messages.cend(), en = messages.cbegin(); j != en;) { --j; const auto item = (*j)->data(); if (item->id > 0 && item->id <= upTo) { break; } else if (!item->out() && item->unread() && item->id > upTo) { ++result; } } } return result; } void History::calculateFirstUnreadMessage() { if (_firstUnreadView || !_inboxReadBefore) { return; } for (auto i = blocks.cend(); i != blocks.cbegin();) { --i; const auto &messages = (*i)->messages; for (auto j = messages.cend(); j != messages.cbegin();) { --j; const auto view = j->get(); const auto item = view->data(); if (!IsServerMsgId(item->id)) { continue; } else if (!item->out() || !_firstUnreadView) { if (item->id >= *_inboxReadBefore) { _firstUnreadView = view; } else { return; } } } } } MsgId History::readInbox() { const auto upTo = msgIdForRead(); changeUnreadCount(-unreadCount()); if (upTo) { inboxRead(upTo); } return upTo; } void History::inboxRead(MsgId upTo) { if (const auto nowUnreadCount = unreadCount()) { if (loadedAtBottom()) { App::main()->historyToDown(this); } changeUnreadCount(countUnread(upTo) - nowUnreadCount); } setInboxReadTill(upTo); updateChatListEntry(); if (peer->migrateTo()) { if (auto migrateTo = App::historyLoaded(peer->migrateTo()->id)) { migrateTo->updateChatListEntry(); } } _firstUnreadView = nullptr; Auth().notifications().clearFromHistory(this); } void History::inboxRead(not_null<const HistoryItem*> wasRead) { if (IsServerMsgId(wasRead->id)) { inboxRead(wasRead->id); } } void History::outboxRead(MsgId upTo) { setOutboxReadTill(upTo); if (const auto last = lastMessage()) { if (last->out() && IsServerMsgId(last->id) && last->id <= upTo) { if (const auto main = App::main()) { main->repaintDialogRow(this, last->id); } } } updateChatListEntry(); } void History::outboxRead(not_null<const HistoryItem*> wasRead) { if (IsServerMsgId(wasRead->id)) { outboxRead(wasRead->id); } } MsgId History::loadAroundId() const { if (_unreadCount && *_unreadCount > 0 && _inboxReadBefore) { return *_inboxReadBefore; } return MsgId(0); } HistoryItem *History::lastAvailableMessage() const { return isEmpty() ? nullptr : blocks.back()->messages.back()->data().get(); } int History::unreadCount() const { return _unreadCount ? *_unreadCount : 0; } bool History::unreadCountKnown() const { return !!_unreadCount; } void History::setUnreadCount(int newUnreadCount) { if (!_unreadCount || *_unreadCount != newUnreadCount) { const auto unreadCountDelta = _unreadCount | [&](int count) { return newUnreadCount - count; }; if (newUnreadCount == 1) { if (loadedAtBottom()) { _firstUnreadView = !isEmpty() ? blocks.back()->messages.back().get() : nullptr; } if (const auto last = msgIdForRead()) { setInboxReadTill(last - 1); } } else if (!newUnreadCount) { _firstUnreadView = nullptr; if (const auto last = msgIdForRead()) { setInboxReadTill(last); } } else { if (!_firstUnreadView && !_unreadBarView && loadedAtBottom()) { calculateFirstUnreadMessage(); } } _unreadCount = newUnreadCount; if (_unreadBarView) { const auto count = chatListUnreadCount(); if (count > 0) { _unreadBarView->setUnreadBarCount(count); } else { _unreadBarView->setUnreadBarFreezed(); } } if (inChatList(Dialogs::Mode::All)) { App::histories().unreadIncrement( unreadCountDelta ? *unreadCountDelta : newUnreadCount, mute()); } if (const auto main = App::main()) { main->unreadCountChanged(this); } } } void History::changeUnreadCount(int delta) { if (_unreadCount) { setUnreadCount(std::max(*_unreadCount + delta, 0)); } if (const auto channel = peer->asChannel()) { if (const auto feed = channel->feed()) { const auto mutedCountDelta = mute() ? delta : 0; feed->unreadCountChanged(delta, mutedCountDelta); } } } bool History::mute() const { return _mute; } bool History::changeMute(bool newMute) { if (_mute == newMute) { return false; } _mute = newMute; const auto feed = peer->isChannel() ? peer->asChannel()->feed() : nullptr; if (feed) { if (_unreadCount) { if (*_unreadCount) { const auto unreadCountDelta = 0; const auto mutedCountDelta = _mute ? *_unreadCount : -*_unreadCount; feed->unreadCountChanged(unreadCountDelta, mutedCountDelta); } } else { Auth().api().requestDialogEntry(this); Auth().api().requestDialogEntry(feed); } } if (inChatList(Dialogs::Mode::All)) { if (_unreadCount && *_unreadCount) { App::histories().unreadMuteChanged(*_unreadCount, _mute); Notify::unreadCounterUpdated(); } Notify::historyMuteUpdated(this); } updateChatListEntry(); Notify::peerUpdatedDelayed( peer, Notify::PeerUpdate::Flag::NotificationsEnabled); return true; } void History::getNextFirstUnreadMessage() { Expects(_firstUnreadView != nullptr); const auto block = _firstUnreadView->block(); const auto index = _firstUnreadView->indexInBlock(); const auto setFromMessage = [&](const auto &view) { if (IsServerMsgId(view->data()->id)) { _firstUnreadView = view.get(); return true; } return false; }; if (index >= 0) { const auto count = int(block->messages.size()); for (auto i = index + 1; i != count; ++i) { const auto &message = block->messages[i]; if (setFromMessage(message)) { return; } } } const auto count = int(blocks.size()); for (auto j = block->indexInHistory() + 1; j != count; ++j) { for (const auto &message : blocks[j]->messages) { if (setFromMessage(message)) { return; } } } _firstUnreadView = nullptr; } std::shared_ptr<AdminLog::LocalIdManager> History::adminLogIdManager() { if (const auto strong = _adminLogIdManager.lock()) { return strong; } auto result = std::make_shared<AdminLog::LocalIdManager>(); _adminLogIdManager = result; return result; } QDateTime History::adjustChatListDate() const { const auto result = chatsListDate(); if (const auto draft = cloudDraft()) { if (!Data::draftIsNull(draft)) { const auto draftResult = ParseDateTime(draft->date); if (draftResult > result) { return draftResult; } } } return result; } void History::countScrollState(int top) { countScrollTopItem(top); if (scrollTopItem) { scrollTopOffset = (top - scrollTopItem->block()->y() - scrollTopItem->y()); } } void History::countScrollTopItem(int top) { if (isEmpty()) { forgetScrollState(); return; } auto itemIndex = 0; auto blockIndex = 0; auto itemTop = 0; if (scrollTopItem) { itemIndex = scrollTopItem->indexInBlock(); blockIndex = scrollTopItem->block()->indexInHistory(); itemTop = blocks[blockIndex]->y() + scrollTopItem->y(); } if (itemTop > top) { // go backward through history while we don't find an item that starts above do { const auto &block = blocks[blockIndex]; for (--itemIndex; itemIndex >= 0; --itemIndex) { const auto view = block->messages[itemIndex].get(); itemTop = block->y() + view->y(); if (itemTop <= top) { scrollTopItem = view; return; } } if (--blockIndex >= 0) { itemIndex = blocks[blockIndex]->messages.size(); } else { break; } } while (true); scrollTopItem = blocks.front()->messages.front().get(); } else { // go forward through history while we don't find the last item that starts above for (auto blocksCount = int(blocks.size()); blockIndex < blocksCount; ++blockIndex) { const auto &block = blocks[blockIndex]; for (auto itemsCount = int(block->messages.size()); itemIndex < itemsCount; ++itemIndex) { itemTop = block->y() + block->messages[itemIndex]->y(); if (itemTop > top) { Assert(itemIndex > 0 || blockIndex > 0); if (itemIndex > 0) { scrollTopItem = block->messages[itemIndex - 1].get(); } else { scrollTopItem = blocks[blockIndex - 1]->messages.back().get(); } return; } } itemIndex = 0; } scrollTopItem = blocks.back()->messages.back().get(); } } void History::getNextScrollTopItem(HistoryBlock *block, int32 i) { ++i; if (i > 0 && i < block->messages.size()) { scrollTopItem = block->messages[i].get(); return; } int j = block->indexInHistory() + 1; if (j > 0 && j < blocks.size()) { scrollTopItem = blocks[j]->messages.front().get(); return; } scrollTopItem = nullptr; } void History::addUnreadBar() { if (_unreadBarView || !_firstUnreadView || !unreadCount()) { return; } if (const auto count = chatListUnreadCount()) { _unreadBarView = _firstUnreadView; _unreadBarView->setUnreadBarCount(count); } } void History::destroyUnreadBar() { if (const auto view = base::take(_unreadBarView)) { view->destroyUnreadBar(); } } bool History::hasNotFreezedUnreadBar() const { if (_firstUnreadView) { if (const auto view = _unreadBarView) { if (const auto bar = view->Get<HistoryView::UnreadBar>()) { return !bar->freezed; } } } return false; } void History::unsetFirstUnreadMessage() { _firstUnreadView = nullptr; } HistoryView::Element *History::unreadBar() const { return _unreadBarView; } HistoryView::Element *History::firstUnreadMessage() const { return _firstUnreadView; } not_null<HistoryItem*> History::addNewInTheMiddle( not_null<HistoryItem*> item, int blockIndex, int itemIndex) { Expects(blockIndex >= 0); Expects(blockIndex < blocks.size()); Expects(itemIndex >= 0); Expects(itemIndex <= blocks[blockIndex]->messages.size()); const auto &block = blocks[blockIndex]; const auto it = block->messages.insert( block->messages.begin() + itemIndex, item->createView( HistoryInner::ElementDelegate())); (*it)->attachToBlock(block.get(), itemIndex); if (itemIndex + 1 < block->messages.size()) { for (auto i = itemIndex + 1, l = int(block->messages.size()); i != l; ++i) { block->messages[i]->setIndexInBlock(i); } block->messages[itemIndex + 1]->previousInBlocksChanged(); } else if (blockIndex + 1 < blocks.size() && !blocks[blockIndex + 1]->messages.empty()) { blocks[blockIndex + 1]->messages.front()->previousInBlocksChanged(); } else { (*it)->nextInBlocksRemoved(); } return item; } int History::chatListUnreadCount() const { const auto result = unreadCount(); const auto addFromId = [&] { if (const auto from = peer->migrateFrom()) { return from->id; } else if (const auto to = peer->migrateTo()) { return to->id; } return PeerId(0); }(); if (const auto migrated = App::historyLoaded(addFromId)) { return result + migrated->unreadCount(); } return result; } bool History::chatListMutedBadge() const { return mute(); } HistoryItem *History::chatsListItem() const { return lastMessage(); } const QString &History::chatsListName() const { return peer->name; } const base::flat_set<QString> &History::chatsListNameWords() const { return peer->nameWords(); } const base::flat_set<QChar> &History::chatsListFirstLetters() const { return peer->nameFirstLetters(); } void History::loadUserpic() { peer->loadUserpic(); } void History::paintUserpic( Painter &p, int x, int y, int size) const { peer->paintUserpic(p, x, y, size); } void History::startBuildingFrontBlock(int expectedItemsCount) { Assert(!isBuildingFrontBlock()); Assert(expectedItemsCount > 0); _buildingFrontBlock.reset(new BuildingBlock()); _buildingFrontBlock->expectedItemsCount = expectedItemsCount; } void History::finishBuildingFrontBlock() { Expects(isBuildingFrontBlock()); // Some checks if there was some message history already if (const auto block = base::take(_buildingFrontBlock)->block) { if (blocks.size() > 1) { // ... item, item, item, last ], [ first, item, item ... const auto last = block->messages.back().get(); const auto first = blocks[1]->messages.front().get(); // we've added a new front block, so previous item for // the old first item of a first block was changed first->previousInBlocksChanged(); } else { block->messages.back()->nextInBlocksRemoved(); } } } void History::clearNotifications() { notifies.clear(); } bool History::loadedAtBottom() const { return _loadedAtBottom; } bool History::loadedAtTop() const { return _loadedAtTop; } bool History::isReadyFor(MsgId msgId) { if (msgId < 0 && -msgId < ServerMaxMsgId && peer->migrateFrom()) { // Old group history. return App::history(peer->migrateFrom()->id)->isReadyFor(-msgId); } if (msgId == ShowAtTheEndMsgId) { return loadedAtBottom(); } if (msgId == ShowAtUnreadMsgId) { if (const auto migratePeer = peer->migrateFrom()) { if (const auto migrated = App::historyLoaded(migratePeer)) { if (migrated->unreadCount()) { return migrated->isReadyFor(msgId); } } } if (unreadCount() && _inboxReadBefore) { if (!isEmpty()) { return (loadedAtTop() || minMsgId() <= *_inboxReadBefore) && (loadedAtBottom() || maxMsgId() >= *_inboxReadBefore); } return false; } return loadedAtBottom(); } HistoryItem *item = App::histItemById(channelId(), msgId); return item && (item->history() == this) && item->mainView(); } void History::getReadyFor(MsgId msgId) { if (msgId < 0 && -msgId < ServerMaxMsgId && peer->migrateFrom()) { const auto migrated = App::history(peer->migrateFrom()->id); migrated->getReadyFor(-msgId); if (migrated->isEmpty()) { unloadBlocks(); } return; } if (msgId == ShowAtUnreadMsgId) { if (const auto migratePeer = peer->migrateFrom()) { if (const auto migrated = App::historyLoaded(migratePeer)) { if (migrated->unreadCount()) { unloadBlocks(); migrated->getReadyFor(msgId); return; } } } } if (!isReadyFor(msgId)) { unloadBlocks(); if (msgId == ShowAtTheEndMsgId) { _loadedAtBottom = true; } } } void History::setNotLoadedAtBottom() { _loadedAtBottom = false; Auth().storage().invalidate( Storage::SharedMediaInvalidateBottom(peer->id)); if (const auto channel = peer->asChannel()) { if (const auto feed = channel->feed()) { Auth().storage().invalidate( Storage::FeedMessagesInvalidateBottom( feed->id())); } } } void History::markFullyLoaded() { _loadedAtTop = _loadedAtBottom = true; if (isEmpty()) { Auth().storage().remove(Storage::SharedMediaRemoveAll(peer->id)); if (const auto channel = peer->asChannel()) { if (const auto feed = channel->feed()) { Auth().storage().remove(Storage::FeedMessagesRemoveAll( feed->id(), channel->bareId())); } } } } void History::setLastMessage(HistoryItem *item) { if (item) { if (_lastMessage && !*_lastMessage) { Local::removeSavedPeer(peer); } _lastMessage = item; if (const auto feed = peer->feed()) { feed->updateLastMessage(item); } setChatsListDate(ItemDateTime(item)); } else if (!_lastMessage || *_lastMessage) { _lastMessage = nullptr; updateChatListEntry(); } } HistoryItem *History::lastMessage() const { return _lastMessage ? (*_lastMessage) : nullptr; } bool History::lastMessageKnown() const { return !!_lastMessage; } void History::updateChatListExistence() { Entry::updateChatListExistence(); if (!lastMessageKnown() || !unreadCountKnown()) { if (const auto channel = peer->asChannel()) { if (!channel->feed()) { // After ungrouping from a feed we need to load dialog. Auth().api().requestDialogEntry(this); } } } } bool History::shouldBeInChatList() const { if (peer->migrateTo()) { return false; } else if (isPinnedDialog()) { return true; } else if (const auto channel = peer->asChannel()) { if (!channel->amIn()) { return false; } else if (const auto feed = channel->feed()) { return !feed->needUpdateInChatList(); } } return true; } void History::unknownMessageDeleted(MsgId messageId) { if (_inboxReadBefore && messageId >= *_inboxReadBefore) { changeUnreadCount(-1); } } bool History::isServerSideUnread(not_null<const HistoryItem*> item) const { Expects(IsServerMsgId(item->id)); return item->out() ? (!_outboxReadBefore || (item->id >= *_outboxReadBefore)) : (!_inboxReadBefore || (item->id >= *_inboxReadBefore)); } void History::applyDialog(const MTPDdialog &data) { applyDialogFields( data.vunread_count.v, data.vread_inbox_max_id.v, data.vread_outbox_max_id.v); applyDialogTopMessage(data.vtop_message.v); setUnreadMentionsCount(data.vunread_mentions_count.v); if (const auto channel = peer->asChannel()) { if (data.has_pts()) { channel->ptsReceived(data.vpts.v); } if (!channel->amCreator()) { const auto topMessageId = FullMsgId( peerToChannel(channel->id), data.vtop_message.v); if (const auto item = App::histItemById(topMessageId)) { if (item->date() <= channel->date) { Auth().api().requestSelfParticipant(channel); } } } } App::main()->applyNotifySetting( MTP_notifyPeer(data.vpeer), data.vnotify_settings, this); if (data.has_draft() && data.vdraft.type() == mtpc_draftMessage) { Data::applyPeerCloudDraft(peer->id, data.vdraft.c_draftMessage()); } } void History::applyDialogFields( int unreadCount, MsgId maxInboxRead, MsgId maxOutboxRead) { setUnreadCount(unreadCount); setInboxReadTill(maxInboxRead); setOutboxReadTill(maxOutboxRead); } void History::applyDialogTopMessage(MsgId topMessageId) { if (topMessageId) { const auto itemId = FullMsgId( channelId(), topMessageId); if (const auto item = App::histItemById(itemId)) { setLastMessage(item); } else { setLastMessage(nullptr); } } else { setLastMessage(nullptr); } } void History::setInboxReadTill(MsgId upTo) { if (_inboxReadBefore) { accumulate_max(*_inboxReadBefore, upTo + 1); } else { _inboxReadBefore = upTo + 1; } } void History::setOutboxReadTill(MsgId upTo) { if (_outboxReadBefore) { accumulate_max(*_outboxReadBefore, upTo + 1); } else { _outboxReadBefore = upTo + 1; } } MsgId History::minMsgId() const { for (const auto &block : blocks) { for (const auto &message : block->messages) { const auto item = message->data(); if (IsServerMsgId(item->id)) { return item->id; } } } return 0; } MsgId History::maxMsgId() const { for (const auto &block : base::reversed(blocks)) { for (const auto &message : base::reversed(block->messages)) { const auto item = message->data(); if (IsServerMsgId(item->id)) { return item->id; } } } return 0; } MsgId History::msgIdForRead() const { const auto last = lastMessage(); const auto result = (last && IsServerMsgId(last->id)) ? last->id : MsgId(0); return loadedAtBottom() ? std::max(result, maxMsgId()) : result; } HistoryItem *History::lastSentMessage() const { if (!loadedAtBottom()) { return nullptr; } for (const auto &block : base::reversed(blocks)) { for (const auto &message : base::reversed(block->messages)) { const auto item = message->data(); if (IsServerMsgId(item->id) && item->out()) { return item; } } } return nullptr; } void History::resizeToWidth(int newWidth) { const auto resizeAllItems = (_width != newWidth); if (!resizeAllItems && !hasPendingResizedItems()) { return; } _flags &= ~(Flag::f_has_pending_resized_items); _width = newWidth; int y = 0; for (const auto &block : blocks) { block->setY(y); y += block->resizeGetHeight(newWidth, resizeAllItems); } _height = y; } ChannelId History::channelId() const { return peerToChannel(peer->id); } bool History::isChannel() const { return peerIsChannel(peer->id); } bool History::isMegagroup() const { return peer->isMegagroup(); } not_null<History*> History::migrateToOrMe() const { if (auto to = peer->migrateTo()) { return App::history(to); } // We could get it by App::history(peer), but we optimize. return const_cast<History*>(this); } History *History::migrateFrom() const { if (auto from = peer->migrateFrom()) { return App::history(from); } return nullptr; } MsgRange History::rangeForDifferenceRequest() const { auto fromId = MsgId(0); auto toId = MsgId(0); for (auto blockIndex = 0, blocksCount = int(blocks.size()); blockIndex < blocksCount; ++blockIndex) { const auto &block = blocks[blockIndex]; for (auto itemIndex = 0, itemsCount = int(block->messages.size()); itemIndex < itemsCount; ++itemIndex) { const auto id = block->messages[itemIndex]->data()->id; if (id > 0) { fromId = id; break; } } if (fromId) break; } if (fromId) { for (auto blockIndex = blocks.size(); blockIndex > 0;) { const auto &block = blocks[--blockIndex]; for (auto itemIndex = block->messages.size(); itemIndex > 0;) { const auto id = block->messages[--itemIndex]->data()->id; if (id > 0) { toId = id; break; } } if (toId) break; } return { fromId, toId + 1 }; } return MsgRange(); } HistoryService *History::insertJoinedMessage(bool unread) { if (!isChannel() || _joinedMessage || !peer->asChannel()->amIn() || (peer->isMegagroup() && peer->asChannel()->mgInfo->joinedMessageFound)) { return _joinedMessage; } const auto inviter = (peer->asChannel()->inviter > 0) ? App::userLoaded(peer->asChannel()->inviter) : nullptr; if (!inviter) { return nullptr; } MTPDmessage::Flags flags = 0; if (inviter->id == Auth().userPeerId()) { unread = false; //} else if (unread) { // flags |= MTPDmessage::Flag::f_unread; } const auto inviteDate = peer->asChannel()->inviteDate; if (isEmpty()) { _joinedMessage = GenerateJoinedMessage( this, inviteDate, inviter, flags); addNewItem(_joinedMessage, unread); return _joinedMessage; } for (auto blockIndex = blocks.size(); blockIndex > 0;) { const auto &block = blocks[--blockIndex]; for (auto itemIndex = block->messages.size(); itemIndex > 0;) { const auto item = block->messages[--itemIndex]->data(); // Due to a server bug sometimes inviteDate is less (before) than the // first message in the megagroup (message about migration), let us // ignore that and think, that the inviteDate is always greater-or-equal. if (item->isGroupMigrate() && peer->isMegagroup() && peer->migrateFrom()) { peer->asChannel()->mgInfo->joinedMessageFound = true; return nullptr; } if (item->date() <= inviteDate) { ++itemIndex; _joinedMessage = GenerateJoinedMessage( this, inviteDate, inviter, flags); addNewInTheMiddle(_joinedMessage, blockIndex, itemIndex); const auto lastDate = chatsListDate(); if (lastDate.isNull() || ParseDateTime(inviteDate) >= lastDate) { setLastMessage(_joinedMessage); if (unread) { newItemAdded(_joinedMessage); } } return _joinedMessage; } } } startBuildingFrontBlock(); _joinedMessage = GenerateJoinedMessage( this, inviteDate, inviter, flags); addItemToBlock(_joinedMessage); finishBuildingFrontBlock(); return _joinedMessage; } void History::checkJoinedMessage(bool createUnread) { if (!isChannel() || _joinedMessage || peer->asChannel()->inviter <= 0) { return; } if (isEmpty()) { if (loadedAtTop() && loadedAtBottom()) { if (insertJoinedMessage(createUnread)) { if (_joinedMessage->mainView()) { setLastMessage(_joinedMessage); } } return; } } const auto inviteDate = peer->asChannel()->inviteDate; auto firstDate = TimeId(0); auto lastDate = TimeId(0); if (!blocks.empty()) { firstDate = blocks.front()->messages.front()->data()->date(); lastDate = blocks.back()->messages.back()->data()->date(); } if (firstDate && lastDate && (firstDate <= inviteDate || loadedAtTop()) && (lastDate > inviteDate || loadedAtBottom())) { const auto willBeLastMsg = (inviteDate >= lastDate); if (insertJoinedMessage(createUnread && willBeLastMsg) && willBeLastMsg) { if (_joinedMessage->mainView()) { setLastMessage(_joinedMessage); } } } } bool History::isEmpty() const { return blocks.empty(); } bool History::isDisplayedEmpty() const { return isEmpty() || ((blocks.size() == 1) && blocks.front()->messages.size() == 1 && blocks.front()->messages.front()->data()->isEmpty()); } bool History::hasOrphanMediaGroupPart() const { if (loadedAtTop() || !loadedAtBottom()) { return false; } else if (blocks.size() != 1) { return false; } else if (blocks.front()->messages.size() != 1) { return false; } const auto last = blocks.front()->messages.front()->data(); return last->groupId() != MessageGroupId(); } bool History::removeOrphanMediaGroupPart() { if (hasOrphanMediaGroupPart()) { unloadBlocks(); return true; } return false; } QVector<MsgId> History::collectMessagesFromUserToDelete( not_null<UserData*> user) const { auto result = QVector<MsgId>(); for (const auto &block : blocks) { for (const auto &message : block->messages) { const auto item = message->data(); if (item->from() == user && item->canDelete()) { result.push_back(item->id); } } } return result; } void History::clear() { clearBlocks(false); } void History::unloadBlocks() { clearBlocks(true); } void History::clearBlocks(bool leaveItems) { _unreadBarView = nullptr; _firstUnreadView = nullptr; _joinedMessage = nullptr; if (scrollTopItem) { forgetScrollState(); } if (leaveItems) { Auth().data().notifyHistoryUnloaded(this); } else { setLastMessage(nullptr); notifies.clear(); Auth().data().notifyHistoryCleared(this); } blocks.clear(); if (leaveItems) { lastKeyboardInited = false; } else { changeUnreadCount(-unreadCount()); if (auto channel = peer->asChannel()) { channel->clearPinnedMessage(); if (const auto feed = channel->feed()) { // Should be after setLastMessage(nullptr); feed->historyCleared(this); } } clearLastKeyboard(); } Auth().data().notifyHistoryChangeDelayed(this); _loadedAtTop = false; _loadedAtBottom = !leaveItems; forgetScrollState(); if (const auto chat = peer->asChat()) { chat->lastAuthors.clear(); chat->markupSenders.clear(); } else if (const auto channel = peer->asMegagroup()) { channel->mgInfo->markupSenders.clear(); } } void History::clearUpTill(MsgId availableMinId) { auto minId = minMsgId(); if (!minId || minId > availableMinId) { return; } do { const auto item = blocks.front()->messages.front()->data(); const auto itemId = item->id; if (IsServerMsgId(itemId) && itemId >= availableMinId) { if (itemId == availableMinId) { auto fromId = 0; auto replyToId = 0; item->applyEdition(MTP_messageService( MTP_flags(0), MTP_int(itemId), MTP_int(fromId), peerToMTP(peer->id), MTP_int(replyToId), MTP_int(item->date()), MTP_messageActionHistoryClear() ).c_messageService()); } break; } item->destroy(); } while (!isEmpty()); if (!lastMessageKnown()) { Auth().api().requestDialogEntry(this); } Auth().data().sendHistoryChangeNotifications(); } void History::applyGroupAdminChanges( const base::flat_map<UserId, bool> &changes) { for (const auto &block : blocks) { for (const auto &message : block->messages) { message->data()->applyGroupAdminChanges(changes); } } } void History::changedInChatListHook(Dialogs::Mode list, bool added) { if (list == Dialogs::Mode::All && unreadCount()) { const auto delta = added ? unreadCount() : -unreadCount(); App::histories().unreadIncrement(delta, mute()); } } void History::changedChatListPinHook() { Notify::peerUpdatedDelayed( peer, Notify::PeerUpdate::Flag::PinnedChanged); } void History::removeBlock(not_null<HistoryBlock*> block) { Expects(block->messages.empty()); if (_buildingFrontBlock && block == _buildingFrontBlock->block) { _buildingFrontBlock->block = nullptr; } int index = block->indexInHistory(); blocks.erase(blocks.begin() + index); if (index < blocks.size()) { for (int i = index, l = blocks.size(); i < l; ++i) { blocks[i]->setIndexInHistory(i); } blocks[index]->messages.front()->previousInBlocksChanged(); } else if (!blocks.empty() && !blocks.back()->messages.empty()) { blocks.back()->messages.back()->nextInBlocksRemoved(); } } History::~History() = default; HistoryBlock::HistoryBlock(not_null<History*> history) : _history(history) { } int HistoryBlock::resizeGetHeight(int newWidth, bool resizeAllItems) { auto y = 0; for (const auto &message : messages) { message->setY(y); if (resizeAllItems || message->pendingResize()) { y += message->resizeGetHeight(newWidth); } else { y += message->height(); } } _height = y; return _height; } void HistoryBlock::remove(not_null<Element*> view) { Expects(view->block() == this); _history->mainViewRemoved(this, view); const auto blockIndex = indexInHistory(); const auto itemIndex = view->indexInBlock(); const auto item = view->data(); item->clearMainView(); messages.erase(messages.begin() + itemIndex); for (auto i = itemIndex, l = int(messages.size()); i < l; ++i) { messages[i]->setIndexInBlock(i); } if (messages.empty()) { // Deletes this. _history->removeBlock(this); } else if (itemIndex < messages.size()) { messages[itemIndex]->previousInBlocksChanged(); } else if (blockIndex + 1 < _history->blocks.size()) { _history->blocks[blockIndex + 1]->messages.front()->previousInBlocksChanged(); } else if (!_history->blocks.empty() && !_history->blocks.back()->messages.empty()) { _history->blocks.back()->messages.back()->nextInBlocksRemoved(); } } void HistoryBlock::refreshView(not_null<Element*> view) { Expects(view->block() == this); const auto item = view->data(); auto refreshed = item->createView(HistoryInner::ElementDelegate()); auto blockIndex = indexInHistory(); auto itemIndex = view->indexInBlock(); _history->viewReplaced(view, refreshed.get()); messages[itemIndex] = std::move(refreshed); messages[itemIndex]->attachToBlock(this, itemIndex); if (itemIndex + 1 < messages.size()) { messages[itemIndex + 1]->previousInBlocksChanged(); } else if (blockIndex + 1 < _history->blocks.size()) { _history->blocks[blockIndex + 1]->messages.front()->previousInBlocksChanged(); } else if (!_history->blocks.empty() && !_history->blocks.back()->messages.empty()) { _history->blocks.back()->messages.back()->nextInBlocksRemoved(); } } HistoryBlock::~HistoryBlock() = default; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/history.h����������������������������������������������0000664�0000000�0000000�00000037727�13262451251�0023016�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "data/data_types.h" #include "data/data_peer.h" #include "dialogs/dialogs_entry.h" #include "ui/effects/send_action_animations.h" #include "base/observer.h" #include "base/timer.h" #include "base/variant.h" #include "base/flat_set.h" #include "base/flags.h" class History; class HistoryBlock; class HistoryItem; class HistoryMessage; class HistoryService; class HistoryMedia; namespace Data { struct Draft; } // namespace Data namespace Dialogs { class Row; class IndexedList; } // namespace Dialogs namespace HistoryView { class Element; } // namespace HistoryView namespace AdminLog { class LocalIdManager; } // namespace AdminLog enum NewMessageType : char { NewMessageUnread, NewMessageLast, NewMessageExisting, }; class Histories { public: Histories(); void registerSendAction( not_null<History*> history, not_null<UserData*> user, const MTPSendMessageAction &action, TimeId when); void step_typings(TimeMs ms, bool timer); History *find(PeerId peerId) const; not_null<History*> findOrInsert(PeerId peerId); void clear(); void remove(const PeerId &peer); HistoryItem *addNewMessage(const MTPMessage &msg, NewMessageType type); // When typing in this history started. typedef QMap<History*, TimeMs> TypingHistories; TypingHistories typing; BasicAnimation _a_typings; int unreadBadge() const; int unreadMutedCount() const; bool unreadOnlyMuted() const; void unreadIncrement(int count, bool muted); void unreadMuteChanged(int count, bool muted); struct SendActionAnimationUpdate { History *history; int width; int height; bool textUpdated; }; base::Observable<SendActionAnimationUpdate> &sendActionAnimationUpdated() { return _sendActionAnimationUpdated; } void selfDestructIn(not_null<HistoryItem*> item, TimeMs delay); private: void checkSelfDestructItems(); std::unordered_map<PeerId, std::unique_ptr<History>> _map; int _unreadFull = 0; int _unreadMuted = 0; base::Observable<SendActionAnimationUpdate> _sendActionAnimationUpdated; base::Timer _selfDestructTimer; std::vector<FullMsgId> _selfDestructItems; }; enum class UnreadMentionType { New, // when new message is added to history Existing, // when some messages slice was received }; class History : public Dialogs::Entry { public: using Element = HistoryView::Element; History(const PeerId &peerId); History(const History &) = delete; History &operator=(const History &) = delete; ChannelId channelId() const; bool isChannel() const; bool isMegagroup() const; not_null<History*> migrateToOrMe() const; History *migrateFrom() const; MsgRange rangeForDifferenceRequest() const; HistoryService *insertJoinedMessage(bool unread); void checkJoinedMessage(bool createUnread = false); bool isEmpty() const; bool isDisplayedEmpty() const; bool hasOrphanMediaGroupPart() const; bool removeOrphanMediaGroupPart(); QVector<MsgId> collectMessagesFromUserToDelete( not_null<UserData*> user) const; void clear(); void markFullyLoaded(); void unloadBlocks(); void clearUpTill(MsgId availableMinId); void applyGroupAdminChanges( const base::flat_map<UserId, bool> &changes); HistoryItem *addNewMessage(const MTPMessage &msg, NewMessageType type); HistoryItem *addToHistory(const MTPMessage &msg); not_null<HistoryItem*> addNewService( MsgId msgId, TimeId date, const QString &text, MTPDmessage::Flags flags = 0, bool newMsg = true); not_null<HistoryItem*> addNewForwarded( MsgId id, MTPDmessage::Flags flags, TimeId date, UserId from, const QString &postAuthor, not_null<HistoryMessage*> original); not_null<HistoryItem*> addNewDocument( MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, TimeId date, UserId from, const QString &postAuthor, not_null<DocumentData*> document, const TextWithEntities &caption, const MTPReplyMarkup &markup); not_null<HistoryItem*> addNewPhoto( MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, TimeId date, UserId from, const QString &postAuthor, not_null<PhotoData*> photo, const TextWithEntities &caption, const MTPReplyMarkup &markup); not_null<HistoryItem*> addNewGame( MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, TimeId date, UserId from, const QString &postAuthor, not_null<GameData*> game, const MTPReplyMarkup &markup); // Used only internally and for channel admin log. HistoryItem *createItem( const MTPMessage &message, bool detachExistingItem); std::vector<not_null<HistoryItem*>> createItems( const QVector<MTPMessage> &data); void addOlderSlice(const QVector<MTPMessage> &slice); void addNewerSlice(const QVector<MTPMessage> &slice); void newItemAdded(not_null<HistoryItem*> item); int countUnread(MsgId upTo); MsgId readInbox(); void inboxRead(MsgId upTo); void inboxRead(not_null<const HistoryItem*> wasRead); void outboxRead(MsgId upTo); void outboxRead(not_null<const HistoryItem*> wasRead); bool isServerSideUnread(not_null<const HistoryItem*> item) const; MsgId loadAroundId() const; int unreadCount() const; bool unreadCountKnown() const; void setUnreadCount(int newUnreadCount); void changeUnreadCount(int delta); bool mute() const; bool changeMute(bool newMute); void addUnreadBar(); void destroyUnreadBar(); bool hasNotFreezedUnreadBar() const; Element *unreadBar() const; void calculateFirstUnreadMessage(); void unsetFirstUnreadMessage(); Element *firstUnreadMessage() const; void clearNotifications(); bool loadedAtBottom() const; // last message is in the list void setNotLoadedAtBottom(); bool loadedAtTop() const; // nothing was added after loading history back bool isReadyFor(MsgId msgId); // has messages for showing history at msgId void getReadyFor(MsgId msgId); HistoryItem *lastMessage() const; bool lastMessageKnown() const; void unknownMessageDeleted(MsgId messageId); void applyDialogTopMessage(MsgId topMessageId); void applyDialog(const MTPDdialog &data); void applyDialogFields( int unreadCount, MsgId maxInboxRead, MsgId maxOutboxRead); MsgId minMsgId() const; MsgId maxMsgId() const; MsgId msgIdForRead() const; HistoryItem *lastSentMessage() const; void resizeToWidth(int newWidth); int height() const; void itemRemoved(not_null<HistoryItem*> item); void itemVanished(not_null<HistoryItem*> item); HistoryItem *currentNotification(); bool hasNotification() const; void skipNotification(); void popNotification(HistoryItem *item); bool hasPendingResizedItems() const; void setHasPendingResizedItems(); bool mySendActionUpdated(SendAction::Type type, bool doing); bool paintSendAction(Painter &p, int x, int y, int availableWidth, int outerWidth, style::color color, TimeMs ms); // Interface for Histories bool updateSendActionNeedsAnimating(TimeMs ms, bool force = false); bool updateSendActionNeedsAnimating( not_null<UserData*> user, const MTPSendMessageAction &action); void clearLastKeyboard(); int getUnreadMentionsLoadedCount() const { return _unreadMentions.size(); } MsgId getMinLoadedUnreadMention() const { return _unreadMentions.empty() ? 0 : _unreadMentions.front(); } MsgId getMaxLoadedUnreadMention() const { return _unreadMentions.empty() ? 0 : _unreadMentions.back(); } int getUnreadMentionsCount(int notLoadedValue = -1) const { return _unreadMentionsCount ? *_unreadMentionsCount : notLoadedValue; } bool hasUnreadMentions() const { return (getUnreadMentionsCount() > 0); } void setUnreadMentionsCount(int count); bool addToUnreadMentions(MsgId msgId, UnreadMentionType type); void eraseFromUnreadMentions(MsgId msgId); void addUnreadMentionsSlice(const MTPmessages_Messages &result); Data::Draft *localDraft() const { return _localDraft.get(); } Data::Draft *cloudDraft() const { return _cloudDraft.get(); } Data::Draft *editDraft() const { return _editDraft.get(); } void setLocalDraft(std::unique_ptr<Data::Draft> &&draft); void takeLocalDraft(History *from); void createLocalDraftFromCloud(); void setCloudDraft(std::unique_ptr<Data::Draft> &&draft); Data::Draft *createCloudDraft(Data::Draft *fromDraft); void setEditDraft(std::unique_ptr<Data::Draft> &&draft); void clearLocalDraft(); void clearCloudDraft(); void clearEditDraft(); void draftSavedToCloud(); Data::Draft *draft() { return _editDraft ? editDraft() : localDraft(); } const MessageIdsList &forwardDraft() const { return _forwardDraft; } HistoryItemsList validateForwardDraft(); void setForwardDraft(MessageIdsList &&items); void updateChatListExistence() override; bool shouldBeInChatList() const override; bool toImportant() const override { return !mute(); } int chatListUnreadCount() const override; bool chatListMutedBadge() const override; HistoryItem *chatsListItem() const override; const QString &chatsListName() const override; const base::flat_set<QString> &chatsListNameWords() const override; const base::flat_set<QChar> &chatsListFirstLetters() const override; void loadUserpic() override; void paintUserpic( Painter &p, int x, int y, int size) const override; void forgetScrollState() { scrollTopItem = nullptr; } // find the correct scrollTopItem and scrollTopOffset using given top // of the displayed window relative to the history start coordinate void countScrollState(int top); std::shared_ptr<AdminLog::LocalIdManager> adminLogIdManager(); virtual ~History(); // Still public data. std::deque<std::unique_ptr<HistoryBlock>> blocks; not_null<PeerData*> peer; typedef QList<HistoryItem*> NotifyQueue; NotifyQueue notifies; // we save the last showAtMsgId to restore the state when switching // between different conversation histories MsgId showAtMsgId = ShowAtUnreadMsgId; // we save a pointer of the history item at the top of the displayed window // together with an offset from the window top to the top of this message // resulting scrollTop = top(scrollTopItem) + scrollTopOffset Element *scrollTopItem = nullptr; int scrollTopOffset = 0; bool lastKeyboardInited = false; bool lastKeyboardUsed = false; MsgId lastKeyboardId = 0; MsgId lastKeyboardHiddenId = 0; PeerId lastKeyboardFrom = 0; mtpRequestId sendRequestId = 0; Text cloudDraftTextCache; private: friend class HistoryBlock; enum class Flag { f_has_pending_resized_items = (1 << 0), }; using Flags = base::flags<Flag>; friend inline constexpr auto is_flag_type(Flag) { return true; }; // when this item is destroyed scrollTopItem just points to the next one // and scrollTopOffset remains the same // if we are at the bottom of the window scrollTopItem == nullptr and // scrollTopOffset is undefined void getNextScrollTopItem(HistoryBlock *block, int32 i); // helper method for countScrollState(int top) void countScrollTopItem(int top); HistoryItem *addNewToLastBlock(const MTPMessage &msg, NewMessageType type); // this method just removes a block from the blocks list // when the last item from this block was detached and // calls the required previousItemChanged() void removeBlock(not_null<HistoryBlock*> block); void clearBlocks(bool leaveItems); not_null<HistoryItem*> addNewItem( not_null<HistoryItem*> item, bool unread); not_null<HistoryItem*> addNewInTheMiddle( not_null<HistoryItem*> item, int blockIndex, int itemIndex); // All this methods add a new item to the first or last block // depending on if we are in isBuildingFronBlock() state. // The last block is created on the go if it is needed. // Adds the item to the back or front block, depending on // isBuildingFrontBlock(), creating the block if necessary. void addItemToBlock(not_null<HistoryItem*> item); // Usually all new items are added to the last block. // Only when we scroll up and add a new slice to the // front we want to create a new front block. void startBuildingFrontBlock(int expectedItemsCount = 1); void finishBuildingFrontBlock(); bool isBuildingFrontBlock() const { return _buildingFrontBlock != nullptr; } void mainViewRemoved( not_null<HistoryBlock*> block, not_null<Element*> view); void removeNotification(not_null<HistoryItem*> item); QDateTime adjustChatListDate() const override; void changedInChatListHook(Dialogs::Mode list, bool added) override; void changedChatListPinHook() override; void setInboxReadTill(MsgId upTo); void setOutboxReadTill(MsgId upTo); void applyMessageChanges( not_null<HistoryItem*> item, const MTPMessage &original); void applyServiceChanges( not_null<HistoryItem*> item, const MTPDmessageService &data); // After adding a new history slice check lastMessage / loadedAtBottom. void checkLastMessage(); void setLastMessage(HistoryItem *item); // Add all items to the unread mentions if we were not loaded at bottom and now are. void checkAddAllToUnreadMentions(); void addToSharedMedia(const std::vector<not_null<HistoryItem*>> &items); void addEdgesToSharedMedia(); void addItemsToLists(const std::vector<not_null<HistoryItem*>> &items); void clearSendAction(not_null<UserData*> from); HistoryItem *lastAvailableMessage() const; void getNextFirstUnreadMessage(); // Creates if necessary a new block for adding item. // Depending on isBuildingFrontBlock() gets front or back block. HistoryBlock *prepareBlockForAddingItem(); void viewReplaced(not_null<const Element*> was, Element *now); Flags _flags = 0; bool _mute = false; int _width = 0; int _height = 0; Element *_unreadBarView = nullptr; Element *_firstUnreadView = nullptr; HistoryService *_joinedMessage = nullptr; bool _loadedAtTop = false; bool _loadedAtBottom = true; base::optional<MsgId> _inboxReadBefore; base::optional<MsgId> _outboxReadBefore; base::optional<int> _unreadCount; base::optional<int> _unreadMentionsCount; base::flat_set<MsgId> _unreadMentions; base::optional<HistoryItem*> _lastMessage; // A pointer to the block that is currently being built. // We hold this pointer so we can destroy it while building // and then create a new one if it is necessary. struct BuildingBlock { int expectedItemsCount = 0; // optimization for block->items.reserve() call HistoryBlock *block = nullptr; }; std::unique_ptr<BuildingBlock> _buildingFrontBlock; std::unique_ptr<Data::Draft> _localDraft, _cloudDraft; std::unique_ptr<Data::Draft> _editDraft; MessageIdsList _forwardDraft; using TypingUsers = QMap<UserData*, TimeMs>; TypingUsers _typing; using SendActionUsers = QMap<UserData*, SendAction>; SendActionUsers _sendActions; QString _sendActionString; Text _sendActionText; Ui::SendActionAnimation _sendActionAnimation; QMap<SendAction::Type, TimeMs> _mySendActions; std::weak_ptr<AdminLog::LocalIdManager> _adminLogIdManager; }; class HistoryBlock { public: using Element = HistoryView::Element; HistoryBlock(not_null<History*> history); HistoryBlock(const HistoryBlock &) = delete; HistoryBlock &operator=(const HistoryBlock &) = delete; ~HistoryBlock(); std::vector<std::unique_ptr<Element>> messages; void remove(not_null<Element*> view); void refreshView(not_null<Element*> view); int resizeGetHeight(int newWidth, bool resizeAllItems); int y() const { return _y; } void setY(int y) { _y = y; } int height() const { return _height; } not_null<History*> history() const { return _history; } HistoryBlock *previousBlock() const { Expects(_indexInHistory >= 0); return (_indexInHistory > 0) ? _history->blocks[_indexInHistory - 1].get() : nullptr; } HistoryBlock *nextBlock() const { Expects(_indexInHistory >= 0); return (_indexInHistory + 1 < _history->blocks.size()) ? _history->blocks[_indexInHistory + 1].get() : nullptr; } void setIndexInHistory(int index) { _indexInHistory = index; } int indexInHistory() const { Expects(_indexInHistory >= 0); Expects(_indexInHistory < _history->blocks.size()); Expects(_history->blocks[_indexInHistory].get() == this); return _indexInHistory; } protected: const not_null<History*> _history; int _y = 0; int _height = 0; int _indexInHistory = -1; }; �����������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/history.style������������������������������������������0000664�0000000�0000000�00000037343�13262451251�0023721�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ using "basic.style"; using "dialogs/dialogs.style"; using "ui/widgets/widgets.style"; historyMinimalWidth: 380px; historyScroll: ScrollArea(defaultScrollArea) { bg: historyScrollBg; bgOver: historyScrollBgOver; barBg: historyScrollBarBg; barBgOver: historyScrollBarBgOver; round: 3px; width: 12px; deltax: 3px; deltat: 3px; deltab: 3px; topsh: 0px; bottomsh: -1px; } historyResizeWidth: 6px; historyPaddingBottom: 8px; historyToDownPosition: point(12px, 10px); historyToDownAbove: icon {{ "history_down_arrow", historyToDownFg, point(17px, 23px) }}; historyToDownAboveOver: icon {{ "history_down_arrow", historyToDownFgOver, point(17px, 23px) }}; historyToDownPaddingTop: 10px; historyToDown: TwoIconButton { width: 52px; height: 62px; iconBelow: historyToDownBelow; iconBelowOver: historyToDownBelowOver; iconAbove: historyToDownAbove; iconAboveOver: historyToDownAboveOver; iconPosition: point(0px, historyToDownPaddingTop); rippleAreaPosition: point(5px, 15px); rippleAreaSize: 42px; ripple: RippleAnimation(defaultRippleAnimation) { color: historyToDownBgRipple; } } historyToDownBadgeFont: semiboldFont; historyToDownBadgeSize: 22px; historyToDownShownAfter: 480px; historyToDownDuration: 150; historyUnreadMentions: TwoIconButton(historyToDown) { iconAbove: icon {{ "history_unread_mention", historyToDownFg, point(16px, 16px) }}; iconAboveOver: icon {{ "history_unread_mention", historyToDownFgOver, point(16px, 16px) }}; } historyUnreadMentionsSkip: 4px; membersInnerWidth: 310px; membersInnerHeightMax: 360px; membersInnerDropdown: InnerDropdown(defaultInnerDropdown) { scroll: ScrollArea(defaultSolidScroll) { deltat: 0px; deltab: 0px; round: 1px; width: 8px; deltax: 3px; } scrollMargin: margins(0px, 5px, 0px, 5px); scrollPadding: margins(0px, 3px, 0px, 3px); } membersInnerItem: defaultPeerListItem; historyFileOutImage: icon {{ "history_file_image", historyFileOutIconFg }}; historyFileOutImageSelected: icon {{ "history_file_image", historyFileOutIconFgSelected }}; historyFileInImage: icon {{ "history_file_image", historyFileInIconFg }}; historyFileInImageSelected: icon {{ "history_file_image", historyFileInIconFgSelected }}; historyFileOutDocument: icon {{ "history_file_document", historyFileOutIconFg }}; historyFileOutDocumentSelected: icon {{ "history_file_document", historyFileOutIconFgSelected }}; historyFileInDocument: icon {{ "history_file_document", historyFileInIconFg }}; historyFileInDocumentSelected: icon {{ "history_file_document", historyFileInIconFgSelected }}; historyFileOutDownload: icon {{ "history_file_download", historyFileOutIconFg }}; historyFileOutDownloadSelected: icon {{ "history_file_download", historyFileOutIconFgSelected }}; historyFileInDownload: icon {{ "history_file_download", historyFileInIconFg }}; historyFileInDownloadSelected: icon {{ "history_file_download", historyFileInIconFgSelected }}; historyFileOutCancel: icon {{ "history_file_cancel", historyFileOutIconFg }}; historyFileOutCancelSelected: icon {{ "history_file_cancel", historyFileOutIconFgSelected }}; historyFileInCancel: icon {{ "history_file_cancel", historyFileInIconFg }}; historyFileInCancelSelected: icon {{ "history_file_cancel", historyFileInIconFgSelected }}; historyFileOutPause: icon {{ "history_file_pause", historyFileOutIconFg }}; historyFileOutPauseSelected: icon {{ "history_file_pause", historyFileOutIconFgSelected }}; historyFileInPause: icon {{ "history_file_pause", historyFileInIconFg }}; historyFileInPauseSelected: icon {{ "history_file_pause", historyFileInIconFgSelected }}; historyFileOutPlay: icon {{ "history_file_play", historyFileOutIconFg }}; historyFileOutPlaySelected: icon {{ "history_file_play", historyFileOutIconFgSelected }}; historyFileInPlay: icon {{ "history_file_play", historyFileInIconFg }}; historyFileInPlaySelected: icon {{ "history_file_play", historyFileInIconFgSelected }}; historyFileThumbDownload: icon {{ "history_file_download", historyFileThumbIconFg }}; historyFileThumbDownloadSelected: icon {{ "history_file_download", historyFileThumbIconFgSelected }}; historyFileThumbCancel: icon {{ "history_file_cancel", historyFileThumbIconFg }}; historyFileThumbCancelSelected: icon {{ "history_file_cancel", historyFileThumbIconFgSelected }}; historyFileThumbPlay: icon {{ "history_file_play", historyFileThumbIconFg }}; historyFileThumbPlaySelected: icon {{ "history_file_play", historyFileThumbIconFgSelected }}; historyFileThumbWaiting: icon {{ "mediaview_save_check", historyFileThumbIconFg }}; historyFileThumbWaitingSelected: icon {{ "mediaview_save_check", historyFileThumbIconFgSelected }}; historySendStateSpace: 24px; historySendStatePosition: point(-17px, -19px); historySentIcon: icon {{ "history_sent", historyOutIconFg, point(2px, 4px) }}; historySentSelectedIcon: icon {{ "history_sent", historyOutIconFgSelected, point(2px, 4px) }}; historySentInvertedIcon: icon {{ "history_sent", historyIconFgInverted, point(2px, 4px) }}; historyReceivedIcon: icon {{ "history_received", historyOutIconFg, point(2px, 4px) }}; historyReceivedSelectedIcon: icon {{ "history_received", historyOutIconFgSelected, point(2px, 4px) }}; historyReceivedInvertedIcon: icon {{ "history_received", historyIconFgInverted, point(2px, 4px) }}; historyViewsSpace: 11px; historyViewsWidth: 20px; historyViewsTop: -15px; historyViewsInIcon: icon {{ "history_views", msgInDateFg }}; historyViewsInSelectedIcon: icon {{ "history_views", msgInDateFgSelected }}; historyViewsOutIcon: icon {{ "history_views", historyOutIconFg }}; historyViewsOutSelectedIcon: icon {{ "history_views", historyOutIconFgSelected }}; historyViewsInvertedIcon: icon {{ "history_views", historySendingInvertedIconFg }}; historyComposeField: FlatTextarea { textColor: historyComposeAreaFg; bgColor: historyComposeAreaBg; align: align(left); textMrg: margins(5px, 5px, 5px, 5px); font: msgFont; phColor: placeholderFg; phFocusColor: placeholderFgActive; phAlign: align(topleft); phPos: point(2px, 0px); phShift: 50px; phDuration: 100; } historyComposeFieldMaxHeight: 224px; // historyMinHeight: 56px; historySendPadding: 9px; historySendRight: 2px; historyComposeButton: FlatButton { color: windowActiveTextFg; overColor: windowActiveTextFg; bgColor: historyComposeButtonBg; overBgColor: historyComposeButtonBgOver; width: -32px; height: 46px; textTop: 14px; font: semiboldFont; overFont: semiboldFont; ripple: RippleAnimation(defaultRippleAnimation) { color: historyComposeButtonBgRipple; } } historyUnblock: FlatButton(historyComposeButton) { color: attentionButtonFg; overColor: attentionButtonFgOver; } historySendIcon: icon {{ "send_control_send", historySendIconFg }}; historySendIconOver: icon {{ "send_control_send", historySendIconFgOver }}; historySendIconPosition: point(11px, 11px); historySendSize: size(46px, 46px); historyEditSaveIcon: icon {{ "send_control_save", historySendIconFg, point(3px, 7px) }}; historyEditSaveIconOver: icon {{ "send_control_save", historySendIconFgOver, point(3px, 7px) }}; historyAttach: IconButton { width: 46px; height: 46px; icon: icon {{ "send_control_attach", historyComposeIconFg }}; iconOver: icon {{ "send_control_attach", historyComposeIconFgOver }}; iconPosition: point(11px, 11px); rippleAreaPosition: point(3px, 3px); rippleAreaSize: 40px; ripple: RippleAnimation(defaultRippleAnimation) { color: windowBgOver; } } historyAttachEmoji: IconButton(historyAttach) { icon: icon {{ "send_control_emoji", historyComposeIconFg }}; iconOver: icon {{ "send_control_emoji", historyComposeIconFgOver }}; iconPosition: point(15px, 15px); } historyAttachEmojiFgActive: windowActiveTextFg; historyAttachEmojiActive: icon {{ "send_control_emoji", historyAttachEmojiFgActive }}; historyAttachEmojiTooltipDelta: 4px; historyEmojiCircle: size(20px, 20px); historyEmojiCirclePeriod: 1500; historyEmojiCircleDuration: 500; historyEmojiCircleTop: 13px; historyEmojiCircleLine: 2px; historyEmojiCircleFg: historyComposeIconFg; historyEmojiCircleFgOver: historyComposeIconFgOver; historyEmojiCirclePart: 3.5; historyBotKeyboardShow: IconButton(historyAttach) { icon: icon {{ "send_control_bot_keyboard", historyComposeIconFg }}; iconOver: icon {{ "send_control_bot_keyboard", historyComposeIconFgOver }}; } historyBotKeyboardHide: IconButton(historyAttach) { icon: icon {{ "send_control_bot_keyboard_hide", historyComposeIconFg }}; iconOver: icon {{ "send_control_bot_keyboard_hide", historyComposeIconFgOver }}; iconPosition: point(11px, 16px); } historyBotCommandStart: IconButton(historyAttach) { icon: icon {{ "send_control_bot_command", historyComposeIconFg }}; iconOver: icon {{ "send_control_bot_command", historyComposeIconFgOver }}; } historyRecordVoiceFg: historyComposeIconFg; historyRecordVoiceFgOver: historyComposeIconFgOver; historyRecordVoiceFgActive: windowBgActive; historyRecordVoiceDuration: 120; historyRecordVoice: icon {{ "send_control_record", historyRecordVoiceFg }}; historyRecordVoiceOver: icon {{ "send_control_record", historyRecordVoiceFgOver }}; historyRecordVoiceActive: icon {{ "send_control_record", historyRecordVoiceFgActive }}; historyRecordVoiceRippleBgActive: lightButtonBgOver; historyRecordSignalColor: attentionButtonFg; historyRecordSignalMin: 5px; historyRecordSignalMax: 12px; historyRecordCancel: windowSubTextFg; historyRecordCancelActive: windowActiveTextFg; historyRecordFont: font(13px); historyRecordDurationFg: historyComposeAreaFg; historyRecordTextTop: 14px; historySilentToggle: IconButton(historyBotKeyboardShow) { icon: icon {{ "send_control_silent_off", historyComposeIconFg }}; iconOver: icon {{ "send_control_silent_off", historyComposeIconFgOver }}; } historySilentToggleOn: icon {{ "send_control_silent_on", historyComposeIconFg }}; historySilentToggleOnOver: icon {{ "send_control_silent_on", historyComposeIconFgOver }}; historyReplySkip: 51px; historyReplyNameFg: windowActiveTextFg; historyReplyHeight: 49px; historyReplyTop: 8px; historyReplyBottom: 6px; historyReplyIconPosition: point(13px, 13px); historyReplyIcon: icon {{ "history_action_reply", historyReplyIconFg }}; historyForwardIcon: icon {{ "history_action_forward", historyReplyIconFg }}; historyEditIcon: icon {{ "history_action_edit", historyReplyIconFg }}; historyReplyCancel: IconButton { width: 49px; height: 49px; icon: historyReplyCancelIcon; iconOver: historyReplyCancelIconOver; iconPosition: point(-1px, -1px); rippleAreaPosition: point(4px, 4px); rippleAreaSize: 40px; ripple: RippleAnimation(defaultRippleAnimation) { color: windowBgOver; } } reportSpamHide: FlatButton { color: windowActiveTextFg; overColor: windowActiveTextFg; bgColor: transparent; overBgColor: transparent; width: -40px; height: 46px; textTop: 15px; font: font(fsize); overFont: font(fsize underline); } reportSpamSeparator: 30px; msgBotKbDuration: 200; msgBotKbFont: semiboldFont; msgBotKbIconPadding: 2px; msgBotKbUrlIcon: icon {{ "inline_button_url", msgBotKbIconFg }}; msgBotKbSwitchPmIcon: icon {{ "inline_button_switch", msgBotKbIconFg }}; msgBotKbButton: BotKeyboardButton { margin: 5px; padding: 10px; height: 36px; textTop: 8px; ripple: RippleAnimation(defaultRippleAnimation) { color: msgBotKbRippleBg; } } botKbDuration: 200; botKbStyle: TextStyle(defaultTextStyle) { font: font(15px semibold); linkFont: font(15px semibold); linkFontOver: font(15px semibold); } botKbButton: BotKeyboardButton { margin: 10px; padding: 10px; height: 38px; textTop: 9px; ripple: RippleAnimation(defaultRippleAnimation) { color: botKbDownBg; } } botKbTinyButton: BotKeyboardButton { margin: 4px; padding: 3px; height: 25px; textTop: 2px; ripple: defaultRippleAnimation; } botKbScroll: defaultSolidScroll; historyDateFadeDuration: 200; historyPhotoLeft: 14px; historyMessageRadius: 6px; historyBubbleTailInLeft: icon {{ "bubble_tail", msgInBg }}; historyBubbleTailInLeftSelected: icon {{ "bubble_tail", msgInBgSelected }}; historyBubbleTailOutLeft: icon {{ "bubble_tail", msgOutBg }}; historyBubbleTailOutLeftSelected: icon {{ "bubble_tail", msgOutBgSelected }}; historyBubbleTailInRight: icon {{ "bubble_tail-flip_horizontal", msgInBg }}; historyBubbleTailInRightSelected: icon {{ "bubble_tail-flip_horizontal", msgInBgSelected }}; historyBubbleTailOutRight: icon {{ "bubble_tail-flip_horizontal", msgOutBg }}; historyBubbleTailOutRightSelected: icon {{ "bubble_tail-flip_horizontal", msgOutBgSelected }}; historyPeerUserpicFont: semiboldFont; historyStatusFg: windowSubTextFg; historyStatusFgActive: windowActiveTextFg; historyStatusFgTyping: historyStatusFgActive; historyUnreadBarHeight: 32px; historyUnreadBarMargin: 8px; historyUnreadBarFont: semiboldFont; historyForwardChooseMargins: margins(30px, 10px, 30px, 10px); historyForwardChooseFont: font(16px); historyCallArrowIn: icon {{ "call_arrow_in", historyCallArrowInFg }}; historyCallArrowInSelected: icon {{ "call_arrow_in", historyCallArrowInFgSelected }}; historyCallArrowMissedIn: icon {{ "call_arrow_in", historyCallArrowMissedInFg }}; historyCallArrowMissedInSelected: icon {{ "call_arrow_in", historyCallArrowMissedInFgSelected }}; historyCallArrowOut: icon {{ "call_arrow_out", historyCallArrowOutFg }}; historyCallArrowOutSelected: icon {{ "call_arrow_out", historyCallArrowOutFgSelected }}; historyCallWidth: 240px; historyCallHeight: 56px; historyCallInIcon: icon {{ "menu_calls", msgFileInBg }}; historyCallInIconSelected: icon {{ "menu_calls", msgFileInBgSelected }}; historyCallOutIcon: icon {{ "menu_calls", msgFileOutBg }}; historyCallOutIconSelected: icon {{ "menu_calls", msgFileOutBgSelected }}; historyCallIconPosition: point(17px, 18px); historyCallLeft: 16px; historyCallTop: 9px; historyCallStatusTop: 29px; historyCallStatusSkip: 4px; historyCallArrowPosition: point(-1px, 1px); msgFileMenuSize: size(36px, 36px); msgFileSize: 44px; msgFilePadding: margins(14px, 12px, 11px, 12px); msgFileThumbSize: 72px; msgFileThumbPadding: margins(10px, 10px, 14px, 10px); msgFileThumbNameTop: 12px; msgFileThumbStatusTop: 32px; msgFileThumbLinkTop: 60px; msgFileNameTop: 16px; msgFileStatusTop: 37px; msgFileMinWidth: 268px; msgFileTopMinus: 6px; msgFileOverDuration: 200; msgFileRadialLine: 3px; msgVideoSize: size(320px, 240px); msgWaveformBar: 2px; msgWaveformSkip: 1px; msgWaveformMin: 2px; msgWaveformMax: 20px; historyVideoMessageMute: icon {{ "volume_mute", historyFileThumbIconFg }}; historyVideoMessageMuteSelected: icon {{ "volume_mute", historyFileThumbIconFgSelected }}; historyVideoMessageMuteSize: 25px; historyVideoMessageProgressOpacity: 0.72; historyAdminLogEmptyWidth: 260px; historyAdminLogEmptyPadding: margins(10px, 12px, 10px, 12px); historyAdminLogSearchField: FlatInput(defaultFlatInput) { textColor: windowFg; bgColor: topBarBg; bgActive: topBarBg; font: font(fsize); borderWidth: 0px; borderColor: topBarBg; borderActive: topBarBg; width: 100px; height: 32px; textMrg: margins(0px, 0px, 0px, 0px); } historyAdminLogCancelSearch: CrossButton { width: 40px; height: 54px; cross: CrossAnimation { size: 32px; skip: 10px; stroke: 2px; minScale: 0.3; } crossFg: menuIconFg; crossFgOver: menuIconFgOver; crossPosition: point(6px, 11px); duration: 150; loadingPeriod: 1000; ripple: RippleAnimation(defaultRippleAnimation) { color: windowBgOver; } } historyAdminLogSearchTop: 11px; historyAdminLogSearchSlideDuration: 150; historyFastShareSize: 31px; historyFastShareLeft: 13px; historyFastShareBottom: 5px; historyFastShareIcon: icon {{ "fast_share", msgServiceFg, point(4px, 3px)}}; historyGoToOriginalIcon: icon {{ "title_back-flip_horizontal", msgServiceFg, point(8px, 7px) }}; historySavedFont: font(semibold 14px); historyGroupWidthMax: maxMediaSize; historyGroupWidthMin: minPhotoSize; historyGroupSkip: 4px; historyGroupRadialSize: 44px; historyGroupRadialLine: 3px; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/history_drag_area.cpp����������������������������������0000664�0000000�0000000�00000011003�13262451251�0025311�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/history_drag_area.h" #include "styles/style_chat_helpers.h" #include "styles/style_boxes.h" #include "boxes/confirm_box.h" #include "boxes/sticker_set_box.h" #include "inline_bots/inline_bot_result.h" #include "inline_bots/inline_bot_layout_item.h" #include "dialogs/dialogs_layout.h" #include "history/history_widget.h" #include "storage/localstorage.h" #include "lang/lang_keys.h" #include "mainwindow.h" #include "apiwrap.h" #include "mainwidget.h" #include "ui/widgets/shadow.h" DragArea::DragArea(QWidget *parent) : TWidget(parent) { setMouseTracking(true); setAcceptDrops(true); } bool DragArea::overlaps(const QRect &globalRect) { if (isHidden() || _a_opacity.animating()) { return false; } auto inner = innerRect(); auto testRect = QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size()); return inner.marginsRemoved(QMargins(st::boxRadius, 0, st::boxRadius, 0)).contains(testRect) || inner.marginsRemoved(QMargins(0, st::boxRadius, 0, st::boxRadius)).contains(testRect); } void DragArea::mouseMoveEvent(QMouseEvent *e) { if (_hiding) return; auto in = QRect(st::dragPadding.left(), st::dragPadding.top(), width() - st::dragPadding.left() - st::dragPadding.right(), height() - st::dragPadding.top() - st::dragPadding.bottom()).contains(e->pos()); setIn(in); } void DragArea::dragMoveEvent(QDragMoveEvent *e) { QRect r(st::dragPadding.left(), st::dragPadding.top(), width() - st::dragPadding.left() - st::dragPadding.right(), height() - st::dragPadding.top() - st::dragPadding.bottom()); setIn(r.contains(e->pos())); e->setDropAction(_in ? Qt::CopyAction : Qt::IgnoreAction); e->accept(); } void DragArea::setIn(bool in) { if (_in != in) { _in = in; _a_in.start([this] { update(); }, _in ? 0. : 1., _in ? 1. : 0., st::boxDuration); } } void DragArea::setText(const QString &text, const QString &subtext) { _text = text; _subtext = subtext; update(); } void DragArea::paintEvent(QPaintEvent *e) { Painter p(this); auto ms = getms(); auto opacity = _a_opacity.current(ms, _hiding ? 0. : 1.); if (!_a_opacity.animating() && _hiding) { return; } p.setOpacity(opacity); auto inner = innerRect(); if (!_cache.isNull()) { p.drawPixmapLeft(inner.x() - st::boxRoundShadow.extend.left(), inner.y() - st::boxRoundShadow.extend.top(), width(), _cache); return; } Ui::Shadow::paint(p, inner, width(), st::boxRoundShadow); App::roundRect(p, inner, st::boxBg, BoxCorners); p.setPen(anim::pen(st::dragColor, st::dragDropColor, _a_in.current(ms, _in ? 1. : 0.))); p.setFont(st::dragFont); p.drawText(QRect(0, (height() - st::dragHeight) / 2, width(), st::dragFont->height), _text, QTextOption(style::al_top)); p.setFont(st::dragSubfont); p.drawText(QRect(0, (height() + st::dragHeight) / 2 - st::dragSubfont->height, width(), st::dragSubfont->height * 2), _subtext, QTextOption(style::al_top)); } void DragArea::dragEnterEvent(QDragEnterEvent *e) { static_cast<HistoryWidget*>(parentWidget())->dragEnterEvent(e); e->setDropAction(Qt::IgnoreAction); e->accept(); } void DragArea::dragLeaveEvent(QDragLeaveEvent *e) { static_cast<HistoryWidget*>(parentWidget())->dragLeaveEvent(e); setIn(false); } void DragArea::dropEvent(QDropEvent *e) { static_cast<HistoryWidget*>(parentWidget())->dropEvent(e); if (e->isAccepted() && _droppedCallback) { _droppedCallback(e->mimeData()); } } void DragArea::otherEnter() { showStart(); } void DragArea::otherLeave() { hideStart(); } void DragArea::hideFast() { _a_opacity.finish(); hide(); } void DragArea::hideStart() { if (_hiding || isHidden()) { return; } if (_cache.isNull()) { _cache = Ui::GrabWidget( this, innerRect().marginsAdded(st::boxRoundShadow.extend)); } _hiding = true; setIn(false); _a_opacity.start([this] { opacityAnimationCallback(); }, 1., 0., st::boxDuration); } void DragArea::hideFinish() { hide(); _in = false; _a_in.finish(); } void DragArea::showStart() { if (!_hiding && !isHidden()) { return; } _hiding = false; if (_cache.isNull()) { _cache = Ui::GrabWidget( this, innerRect().marginsAdded(st::boxRoundShadow.extend)); } show(); _a_opacity.start([this] { opacityAnimationCallback(); }, 0., 1., st::boxDuration); } void DragArea::opacityAnimationCallback() { update(); if (!_a_opacity.animating()) { _cache = QPixmap(); if (_hiding) { hideFinish(); } } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/history_drag_area.h������������������������������������0000664�0000000�0000000�00000002742�13262451251�0024770�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/twidget.h" class DragArea : public TWidget { Q_OBJECT public: DragArea(QWidget *parent); void setText(const QString &text, const QString &subtext); void otherEnter(); void otherLeave(); bool overlaps(const QRect &globalRect); void hideFast(); void setDroppedCallback(base::lambda<void(const QMimeData *data)> callback) { _droppedCallback = std::move(callback); } protected: void paintEvent(QPaintEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void dragEnterEvent(QDragEnterEvent *e) override; void dragLeaveEvent(QDragLeaveEvent *e) override; void dropEvent(QDropEvent *e) override; void dragMoveEvent(QDragMoveEvent *e) override; public slots: void hideStart(); void hideFinish(); void showStart(); private: void setIn(bool in); void opacityAnimationCallback(); QRect innerRect() const { return QRect( st::dragPadding.left(), st::dragPadding.top(), width() - st::dragPadding.left() - st::dragPadding.right(), height() - st::dragPadding.top() - st::dragPadding.bottom() ); } bool _hiding = false; bool _in = false; QPixmap _cache; base::lambda<void(const QMimeData *data)> _droppedCallback; Animation _a_opacity; Animation _a_in; QString _text, _subtext; }; ������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/history_inner_widget.cpp�������������������������������0000664�0000000�0000000�00000277730�13262451251�0026107�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/history_inner_widget.h" #include <rpl/merge.h> #include "styles/style_history.h" #include "core/file_utilities.h" #include "core/crash_reports.h" #include "history/history.h" #include "history/history_message.h" #include "history/history_media_types.h" #include "history/history_item_components.h" #include "history/history_item_text.h" #include "history/view/history_view_message.h" #include "history/view/history_view_service_message.h" #include "history/view/history_view_cursor_state.h" #include "ui/text_options.h" #include "ui/widgets/popup_menu.h" #include "window/window_controller.h" #include "window/window_peer_menu.h" #include "boxes/confirm_box.h" #include "chat_helpers/message_field.h" #include "chat_helpers/stickers.h" #include "history/history_widget.h" #include "mainwindow.h" #include "mainwidget.h" #include "layout.h" #include "auth_session.h" #include "messenger.h" #include "apiwrap.h" #include "lang/lang_keys.h" #include "data/data_session.h" #include "data/data_media_types.h" namespace { constexpr auto kScrollDateHideTimeout = 1000; // Helper binary search for an item in a list that is not completely // above the given top of the visible area or below the given bottom of the visible area // is applied once for blocks list in a history and once for items list in the found block. template <bool TopToBottom, typename T> int BinarySearchBlocksOrItems(const T &list, int edge) { // static_cast to work around GCC bug #78693 auto start = 0, end = static_cast<int>(list.size()); while (end - start > 1) { auto middle = (start + end) / 2; auto top = list[middle]->y(); auto chooseLeft = (TopToBottom ? (top <= edge) : (top < edge)); if (chooseLeft) { start = middle; } else { end = middle; } } return start; } } // namespace // flick scroll taken from http://qt-project.org/doc/qt-4.8/demos-embedded-anomaly-src-flickcharm-cpp.html class HistoryInner::BotAbout : public ClickHandlerHost { public: BotAbout(not_null<HistoryInner*> parent, not_null<BotInfo*> info); // ClickHandlerHost interface void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override; not_null<BotInfo*> info; int width = 0; int height = 0; QRect rect; private: not_null<HistoryInner*> _parent; }; HistoryInner::BotAbout::BotAbout( not_null<HistoryInner*> parent, not_null<BotInfo*> info) : info(info) , _parent(parent) { } void HistoryInner::BotAbout::clickHandlerActiveChanged( const ClickHandlerPtr &p, bool active) { _parent->update(rect); } void HistoryInner::BotAbout::clickHandlerPressedChanged( const ClickHandlerPtr &p, bool pressed) { _parent->update(rect); } HistoryInner::HistoryInner( not_null<HistoryWidget*> historyWidget, not_null<Window::Controller*> controller, Ui::ScrollArea *scroll, not_null<History*> history) : RpWidget(nullptr) , _controller(controller) , _peer(history->peer) , _history(history) , _migrated(history->migrateFrom()) , _widget(historyWidget) , _scroll(scroll) , _scrollDateCheck([this] { scrollDateCheck(); }) , _scrollDateHideTimer([this] { scrollDateHideByTimer(); }) { _touchSelectTimer.setSingleShot(true); connect(&_touchSelectTimer, SIGNAL(timeout()), this, SLOT(onTouchSelect())); setAttribute(Qt::WA_AcceptTouchEvents); connect(&_touchScrollTimer, SIGNAL(timeout()), this, SLOT(onTouchScrollTimer())); _trippleClickTimer.setSingleShot(true); notifyIsBotChanged(); setMouseTracking(true); subscribe(_controller->gifPauseLevelChanged(), [this] { if (!_controller->isGifPausedAtLeastFor(Window::GifPauseReason::Any)) { update(); } }); subscribe(_controller->window()->dragFinished(), [this] { mouseActionUpdate(QCursor::pos()); }); Auth().data().itemRemoved( ) | rpl::start_with_next( [this](auto item) { itemRemoved(item); }, lifetime()); Auth().data().viewRemoved( ) | rpl::start_with_next( [this](auto view) { viewRemoved(view); }, lifetime()); Auth().data().itemViewRefreshRequest( ) | rpl::start_with_next( [this](auto item) { refreshView(item); }, lifetime()); rpl::merge( Auth().data().historyUnloaded(), Auth().data().historyCleared() ) | rpl::filter([this](not_null<const History*> history) { return (_history == history); }) | rpl::start_with_next([this] { mouseActionCancel(); }, lifetime()); Auth().data().viewRepaintRequest( ) | rpl::start_with_next([this](not_null<const Element*> view) { repaintItem(view); }, lifetime()); Auth().data().viewLayoutChanged( ) | rpl::filter([](not_null<const Element*> view) { return (view == view->data()->mainView()) && view->isUnderCursor(); }) | rpl::start_with_next([this](not_null<const Element*> view) { mouseActionUpdate(); }, lifetime()); } void HistoryInner::messagesReceived( PeerData *peer, const QVector<MTPMessage> &messages) { if (_history && _history->peer == peer) { _history->addOlderSlice(messages); } else if (_migrated && _migrated->peer == peer) { const auto newLoaded = _migrated && _migrated->isEmpty() && !_history->isEmpty(); _migrated->addOlderSlice(messages); if (newLoaded) { _migrated->addNewerSlice(QVector<MTPMessage>()); } } } void HistoryInner::messagesReceivedDown(PeerData *peer, const QVector<MTPMessage> &messages) { if (_history && _history->peer == peer) { const auto oldLoaded = _migrated && _history->isEmpty() && !_migrated->isEmpty(); _history->addNewerSlice(messages); if (oldLoaded) { _history->addOlderSlice(QVector<MTPMessage>()); } } else if (_migrated && _migrated->peer == peer) { _migrated->addNewerSlice(messages); } } void HistoryInner::repaintItem(const HistoryItem *item) { if (!item) { return; } repaintItem(item->mainView()); } void HistoryInner::repaintItem(const Element *view) { if (_widget->skipItemRepaint()) { return; } const auto top = itemTop(view); if (top >= 0) { update(0, top, width(), view->height()); } } template <bool TopToBottom, typename Method> void HistoryInner::enumerateItemsInHistory(History *history, int historytop, Method method) { // No displayed messages in this history. if (historytop < 0 || history->isEmpty()) { return; } if (_visibleAreaBottom <= historytop || historytop + history->height() <= _visibleAreaTop) { return; } auto searchEdge = TopToBottom ? _visibleAreaTop : _visibleAreaBottom; // Binary search for blockIndex of the first block that is not completely below the visible area. auto blockIndex = BinarySearchBlocksOrItems<TopToBottom>(history->blocks, searchEdge - historytop); // Binary search for itemIndex of the first item that is not completely below the visible area. auto block = history->blocks[blockIndex].get(); auto blocktop = historytop + block->y(); auto blockbottom = blocktop + block->height(); auto itemIndex = BinarySearchBlocksOrItems<TopToBottom>(block->messages, searchEdge - blocktop); while (true) { while (true) { auto view = block->messages[itemIndex].get(); auto itemtop = blocktop + view->y(); auto itembottom = itemtop + view->height(); // Binary search should've skipped all the items that are above / below the visible area. if (TopToBottom) { if (itembottom <= _visibleAreaTop) { QStringList debug; for (const auto &logBlock : history->blocks) { QStringList debugItems; for (const auto &logItem : logBlock->messages) { debugItems.push_back(QString("%1,%2" ).arg(logItem->y() ).arg(logItem->height() )); } debug.push_back(QString("b(%1,%2:%3)" ).arg(logBlock->y() ).arg(logBlock->height() ).arg(debugItems.join(';') )); } CrashReports::SetAnnotation( "geometry", QString("height:%1 " ).arg(history->height() ) + debug.join(';')); CrashReports::SetAnnotation( "info", QString("block:%1(%2,%3), " "item:%4(%5,%6), " "limits:%7,%8, " "has:%9" ).arg(blockIndex ).arg(block->y() ).arg(block->height() ).arg(itemIndex ).arg(view->y() ).arg(view->height() ).arg(_visibleAreaTop ).arg(_visibleAreaBottom ).arg(Logs::b(history->hasPendingResizedItems()) )); Unexpected("itembottom > _visibleAreaTop"); } Assert(itembottom > _visibleAreaTop); } else { Assert(itemtop < _visibleAreaBottom); } if (!method(view, itemtop, itembottom)) { return; } // Skip all the items that are below / above the visible area. if (TopToBottom) { if (itembottom >= _visibleAreaBottom) { return; } } else { if (itemtop <= _visibleAreaTop) { return; } } if (TopToBottom) { if (++itemIndex >= block->messages.size()) { break; } } else { if (--itemIndex < 0) { break; } } } // Skip all the rest blocks that are below / above the visible area. if (TopToBottom) { if (blockbottom >= _visibleAreaBottom) { return; } } else { if (blocktop <= _visibleAreaTop) { return; } } if (TopToBottom) { if (++blockIndex >= history->blocks.size()) { return; } } else { if (--blockIndex < 0) { return; } } block = history->blocks[blockIndex].get(); blocktop = historytop + block->y(); blockbottom = blocktop + block->height(); if (TopToBottom) { itemIndex = 0; } else { itemIndex = block->messages.size() - 1; } } } bool HistoryInner::canHaveFromUserpics() const { if (_peer->isUser() && !_peer->isSelf() && !Adaptive::ChatWide()) { return false; } else if (_peer->isChannel() && !_peer->isMegagroup()) { return false; } return true; } template <typename Method> void HistoryInner::enumerateUserpics(Method method) { if (!canHaveFromUserpics()) { return; } // Find and remember the top of an attached messages pack // -1 means we didn't find an attached to next message yet. int lowestAttachedItemTop = -1; auto userpicCallback = [&](not_null<Element*> view, int itemtop, int itembottom) { // Skip all service messages. const auto item = view->data(); const auto message = item->toHistoryMessage(); if (!message) return true; if (lowestAttachedItemTop < 0 && view->isAttachedToNext()) { lowestAttachedItemTop = itemtop + view->marginTop(); } // Call method on a userpic for all messages that have it and for those who are not showing it // because of their attachment to the next message if they are bottom-most visible. if (view->displayFromPhoto() || (view->hasFromPhoto() && itembottom >= _visibleAreaBottom)) { if (lowestAttachedItemTop < 0) { lowestAttachedItemTop = itemtop + view->marginTop(); } // Attach userpic to the bottom of the visible area with the same margin as the last message. auto userpicMinBottomSkip = st::historyPaddingBottom + st::msgMargin.bottom(); auto userpicBottom = qMin(itembottom - view->marginBottom(), _visibleAreaBottom - userpicMinBottomSkip); // Do not let the userpic go above the attached messages pack top line. userpicBottom = qMax(userpicBottom, lowestAttachedItemTop + st::msgPhotoSize); // Call the template callback function that was passed // and return if it finished everything it needed. if (!method(view, userpicBottom - st::msgPhotoSize)) { return false; } } // Forget the found top of the pack, search for the next one from scratch. if (!view->isAttachedToNext()) { lowestAttachedItemTop = -1; } return true; }; enumerateItems<EnumItemsDirection::TopToBottom>(userpicCallback); } template <typename Method> void HistoryInner::enumerateDates(Method method) { auto drawtop = historyDrawTop(); // Find and remember the bottom of an single-day messages pack // -1 means we didn't find a same-day with previous message yet. auto lowestInOneDayItemBottom = -1; auto dateCallback = [&](not_null<Element*> view, int itemtop, int itembottom) { const auto item = view->data(); if (lowestInOneDayItemBottom < 0 && view->isInOneDayWithPrevious()) { lowestInOneDayItemBottom = itembottom - view->marginBottom(); } // Call method on a date for all messages that have it and for those who are not showing it // because they are in a one day together with the previous message if they are top-most visible. if (view->displayDate() || (!item->isEmpty() && itemtop <= _visibleAreaTop)) { // skip the date of history migrate item if it will be in migrated if (itemtop < drawtop && item->history() == _history) { if (itemtop > _visibleAreaTop) { // Previous item (from the _migrated history) is drawing date now. return false; } else if (item == _history->blocks.front()->messages.front()->data() && item->isGroupMigrate() && _migrated->blocks.back()->messages.back()->data()->isGroupMigrate()) { // This item is completely invisible and should be completely ignored. return false; } } if (lowestInOneDayItemBottom < 0) { lowestInOneDayItemBottom = itembottom - view->marginBottom(); } // Attach date to the top of the visible area with the same margin as it has in service message. int dateTop = qMax(itemtop, _visibleAreaTop) + st::msgServiceMargin.top(); // Do not let the date go below the single-day messages pack bottom line. int dateHeight = st::msgServicePadding.bottom() + st::msgServiceFont->height + st::msgServicePadding.top(); dateTop = qMin(dateTop, lowestInOneDayItemBottom - dateHeight); // Call the template callback function that was passed // and return if it finished everything it needed. if (!method(view, itemtop, dateTop)) { return false; } } // Forget the found bottom of the pack, search for the next one from scratch. if (!view->isInOneDayWithPrevious()) { lowestInOneDayItemBottom = -1; } return true; }; enumerateItems<EnumItemsDirection::BottomToTop>(dateCallback); } TextSelection HistoryInner::computeRenderSelection( not_null<const SelectedItems*> selected, not_null<Element*> view) const { if (view->isHiddenByGroup()) { return TextSelection(); } const auto item = view->data(); const auto itemSelection = [&](not_null<HistoryItem*> item) { auto i = selected->find(item); if (i != selected->end()) { return i->second; } return TextSelection(); }; const auto result = itemSelection(item); if (result != TextSelection() && result != FullSelection) { return result; } if (const auto group = Auth().data().groups().find(item)) { auto parts = TextSelection(); auto allFullSelected = true; const auto count = int(group->items.size()); for (auto i = 0; i != count; ++i) { const auto part = group->items[i]; const auto selection = itemSelection(part); if (part == item && selection != FullSelection && selection != TextSelection()) { return selection; } else if (selection == FullSelection) { parts = AddGroupItemSelection(parts, i); } else { allFullSelected = false; } } return allFullSelected ? FullSelection : parts; } return itemSelection(item); } TextSelection HistoryInner::itemRenderSelection( not_null<Element*> view, int selfromy, int seltoy) const { const auto item = view->data(); const auto y = view->block()->y() + view->y(); if (y >= selfromy && y < seltoy) { if (_dragSelecting && !item->serviceMsg() && item->id > 0) { return FullSelection; } } else if (!_selected.empty()) { return computeRenderSelection(&_selected, view); } return TextSelection(); } void HistoryInner::paintEvent(QPaintEvent *e) { if (Ui::skipPaintEvent(this, e)) { return; } if (hasPendingResizedItems()) { return; } Painter p(this); auto clip = e->rect(); auto ms = getms(); bool historyDisplayedEmpty = (_history->isDisplayedEmpty() && (!_migrated || _migrated->isDisplayedEmpty())); bool noHistoryDisplayed = _firstLoading || historyDisplayedEmpty; if (!_firstLoading && _botAbout && !_botAbout->info->text.isEmpty() && _botAbout->height > 0) { if (clip.y() < _botAbout->rect.y() + _botAbout->rect.height() && clip.y() + clip.height() > _botAbout->rect.y()) { p.setTextPalette(st::inTextPalette); App::roundRect(p, _botAbout->rect, st::msgInBg, MessageInCorners, &st::msgInShadow); p.setFont(st::msgNameFont); p.setPen(st::dialogsNameFg); p.drawText(_botAbout->rect.left() + st::msgPadding.left(), _botAbout->rect.top() + st::msgPadding.top() + st::msgNameFont->ascent, lang(lng_bot_description)); p.setPen(st::historyTextInFg); _botAbout->info->text.draw(p, _botAbout->rect.left() + st::msgPadding.left(), _botAbout->rect.top() + st::msgPadding.top() + st::msgNameFont->height + st::botDescSkip, _botAbout->width); p.restoreTextPalette(); } } else if (noHistoryDisplayed) { HistoryView::paintEmpty(p, width(), height()); } if (!noHistoryDisplayed) { auto readMentions = base::flat_set<not_null<HistoryItem*>>(); adjustCurrent(clip.top()); auto drawToY = clip.y() + clip.height(); auto selfromy = itemTop(_dragSelFrom); auto seltoy = itemTop(_dragSelTo); if (selfromy < 0 || seltoy < 0) { selfromy = seltoy = -1; } else { seltoy += _dragSelTo->height(); } auto mtop = migratedTop(); auto htop = historyTop(); auto hdrawtop = historyDrawTop(); if (mtop >= 0) { auto iBlock = (_curHistory == _migrated ? _curBlock : (_migrated->blocks.size() - 1)); auto block = _migrated->blocks[iBlock].get(); auto iItem = (_curHistory == _migrated ? _curItem : (block->messages.size() - 1)); auto view = block->messages[iItem].get(); auto item = view->data(); auto y = mtop + block->y() + view->y(); p.save(); p.translate(0, y); if (clip.y() < y + view->height()) while (y < drawToY) { const auto selection = itemRenderSelection( view, selfromy - mtop, seltoy - mtop); view->draw(p, clip.translated(0, -y), selection, ms); if (item->hasViews()) { App::main()->scheduleViewIncrement(item); } if (item->mentionsMe() && item->isMediaUnread()) { readMentions.insert(item); _widget->enqueueMessageHighlight(view); } int32 h = view->height(); p.translate(0, h); y += h; ++iItem; if (iItem == block->messages.size()) { iItem = 0; ++iBlock; if (iBlock == _migrated->blocks.size()) { break; } block = _migrated->blocks[iBlock].get(); } view = block->messages[iItem].get(); item = view->data(); } p.restore(); } if (htop >= 0) { auto iBlock = (_curHistory == _history ? _curBlock : 0); auto block = _history->blocks[iBlock].get(); auto iItem = (_curHistory == _history ? _curItem : 0); auto view = block->messages[iItem].get(); auto item = view->data(); auto hclip = clip.intersected(QRect(0, hdrawtop, width(), clip.top() + clip.height())); auto y = htop + block->y() + view->y(); p.save(); p.translate(0, y); while (y < drawToY) { auto h = view->height(); if (hclip.y() < y + h && hdrawtop < y + h) { const auto selection = itemRenderSelection( view, selfromy - htop, seltoy - htop); view->draw(p, hclip.translated(0, -y), selection, ms); if (item->hasViews()) { App::main()->scheduleViewIncrement(item); } if (item->mentionsMe() && item->isMediaUnread()) { readMentions.insert(item); _widget->enqueueMessageHighlight(view); } } p.translate(0, h); y += h; ++iItem; if (iItem == block->messages.size()) { iItem = 0; ++iBlock; if (iBlock == _history->blocks.size()) { break; } block = _history->blocks[iBlock].get(); } view = block->messages[iItem].get(); item = view->data(); } p.restore(); } if (!readMentions.empty() && App::wnd()->doWeReadMentions()) { Auth().api().markMediaRead(readMentions); } if (mtop >= 0 || htop >= 0) { enumerateUserpics([&](not_null<Element*> view, int userpicTop) { // stop the enumeration if the userpic is below the painted rect if (userpicTop >= clip.top() + clip.height()) { return false; } // paint the userpic if it intersects the painted rect if (userpicTop + st::msgPhotoSize > clip.top()) { const auto message = view->data()->toHistoryMessage(); message->displayFrom()->paintUserpicLeft( p, st::historyPhotoLeft, userpicTop, width(), st::msgPhotoSize); } return true; }); int dateHeight = st::msgServicePadding.bottom() + st::msgServiceFont->height + st::msgServicePadding.top(); //QDate lastDate; //if (!_history->isEmpty()) { // lastDate = _history->blocks.back()->messages.back()->data()->date.date(); //} //// if item top is before this value always show date as a floating date //int showFloatingBefore = height() - 2 * (_visibleAreaBottom - _visibleAreaTop) - dateHeight; auto scrollDateOpacity = _scrollDateOpacity.current(ms, _scrollDateShown ? 1. : 0.); enumerateDates([&](not_null<Element*> view, int itemtop, int dateTop) { // stop the enumeration if the date is above the painted rect if (dateTop + dateHeight <= clip.top()) { return false; } const auto displayDate = view->displayDate(); auto dateInPlace = displayDate; if (dateInPlace) { const auto correctDateTop = itemtop + st::msgServiceMargin.top(); dateInPlace = (dateTop < correctDateTop + dateHeight); } //bool noFloatingDate = (item->date.date() == lastDate && displayDate); //if (noFloatingDate) { // if (itemtop < showFloatingBefore) { // noFloatingDate = false; // } //} // paint the date if it intersects the painted rect if (dateTop < clip.top() + clip.height()) { auto opacity = (dateInPlace/* || noFloatingDate*/) ? 1. : scrollDateOpacity; if (opacity > 0.) { p.setOpacity(opacity); const auto dateY = false // noFloatingDate ? itemtop : (dateTop - st::msgServiceMargin.top()); if (const auto date = view->Get<HistoryView::DateBadge>()) { date->paint(p, dateY, _contentWidth); } else { HistoryView::ServiceMessagePainter::paintDate( p, view->dateTime(), dateY, _contentWidth); } } } return true; }); } } } bool HistoryInner::eventHook(QEvent *e) { if (e->type() == QEvent::TouchBegin || e->type() == QEvent::TouchUpdate || e->type() == QEvent::TouchEnd || e->type() == QEvent::TouchCancel) { QTouchEvent *ev = static_cast<QTouchEvent*>(e); if (ev->device()->type() == QTouchDevice::TouchScreen) { touchEvent(ev); return true; } } return RpWidget::eventHook(e); } void HistoryInner::onTouchScrollTimer() { auto nowTime = getms(); if (_touchScrollState == Ui::TouchScrollState::Acceleration && _touchWaitingAcceleration && (nowTime - _touchAccelerationTime) > 40) { _touchScrollState = Ui::TouchScrollState::Manual; touchResetSpeed(); } else if (_touchScrollState == Ui::TouchScrollState::Auto || _touchScrollState == Ui::TouchScrollState::Acceleration) { int32 elapsed = int32(nowTime - _touchTime); QPoint delta = _touchSpeed * elapsed / 1000; bool hasScrolled = _widget->touchScroll(delta); if (_touchSpeed.isNull() || !hasScrolled) { _touchScrollState = Ui::TouchScrollState::Manual; _touchScroll = false; _touchScrollTimer.stop(); } else { _touchTime = nowTime; } touchDeaccelerate(elapsed); } } void HistoryInner::touchUpdateSpeed() { const auto nowTime = getms(); if (_touchPrevPosValid) { const int elapsed = nowTime - _touchSpeedTime; if (elapsed) { const QPoint newPixelDiff = (_touchPos - _touchPrevPos); const QPoint pixelsPerSecond = newPixelDiff * (1000 / elapsed); // fingers are inacurates, we ignore small changes to avoid stopping the autoscroll because // of a small horizontal offset when scrolling vertically const int newSpeedY = (qAbs(pixelsPerSecond.y()) > FingerAccuracyThreshold) ? pixelsPerSecond.y() : 0; const int newSpeedX = (qAbs(pixelsPerSecond.x()) > FingerAccuracyThreshold) ? pixelsPerSecond.x() : 0; if (_touchScrollState == Ui::TouchScrollState::Auto) { const int oldSpeedY = _touchSpeed.y(); const int oldSpeedX = _touchSpeed.x(); if ((oldSpeedY <= 0 && newSpeedY <= 0) || ((oldSpeedY >= 0 && newSpeedY >= 0) && (oldSpeedX <= 0 && newSpeedX <= 0)) || (oldSpeedX >= 0 && newSpeedX >= 0)) { _touchSpeed.setY(snap((oldSpeedY + (newSpeedY / 4)), -MaxScrollAccelerated, +MaxScrollAccelerated)); _touchSpeed.setX(snap((oldSpeedX + (newSpeedX / 4)), -MaxScrollAccelerated, +MaxScrollAccelerated)); } else { _touchSpeed = QPoint(); } } else { // we average the speed to avoid strange effects with the last delta if (!_touchSpeed.isNull()) { _touchSpeed.setX(snap((_touchSpeed.x() / 4) + (newSpeedX * 3 / 4), -MaxScrollFlick, +MaxScrollFlick)); _touchSpeed.setY(snap((_touchSpeed.y() / 4) + (newSpeedY * 3 / 4), -MaxScrollFlick, +MaxScrollFlick)); } else { _touchSpeed = QPoint(newSpeedX, newSpeedY); } } } } else { _touchPrevPosValid = true; } _touchSpeedTime = nowTime; _touchPrevPos = _touchPos; } void HistoryInner::touchResetSpeed() { _touchSpeed = QPoint(); _touchPrevPosValid = false; } void HistoryInner::touchDeaccelerate(int32 elapsed) { int32 x = _touchSpeed.x(); int32 y = _touchSpeed.y(); _touchSpeed.setX((x == 0) ? x : (x > 0) ? qMax(0, x - elapsed) : qMin(0, x + elapsed)); _touchSpeed.setY((y == 0) ? y : (y > 0) ? qMax(0, y - elapsed) : qMin(0, y + elapsed)); } void HistoryInner::touchEvent(QTouchEvent *e) { const Qt::TouchPointStates &states(e->touchPointStates()); if (e->type() == QEvent::TouchCancel) { // cancel if (!_touchInProgress) return; _touchInProgress = false; _touchSelectTimer.stop(); _touchScroll = _touchSelect = false; _touchScrollState = Ui::TouchScrollState::Manual; mouseActionCancel(); return; } if (!e->touchPoints().isEmpty()) { _touchPrevPos = _touchPos; _touchPos = e->touchPoints().cbegin()->screenPos().toPoint(); } switch (e->type()) { case QEvent::TouchBegin: { if (_menu) { e->accept(); return; // ignore mouse press, that was hiding context menu } if (_touchInProgress) return; if (e->touchPoints().isEmpty()) return; _touchInProgress = true; if (_touchScrollState == Ui::TouchScrollState::Auto) { _touchScrollState = Ui::TouchScrollState::Acceleration; _touchWaitingAcceleration = true; _touchAccelerationTime = getms(); touchUpdateSpeed(); _touchStart = _touchPos; } else { _touchScroll = false; _touchSelectTimer.start(QApplication::startDragTime()); } _touchSelect = false; _touchStart = _touchPrevPos = _touchPos; } break; case QEvent::TouchUpdate: { if (!_touchInProgress) return; if (_touchSelect) { mouseActionUpdate(_touchPos); } else if (!_touchScroll && (_touchPos - _touchStart).manhattanLength() >= QApplication::startDragDistance()) { _touchSelectTimer.stop(); _touchScroll = true; touchUpdateSpeed(); } if (_touchScroll) { if (_touchScrollState == Ui::TouchScrollState::Manual) { touchScrollUpdated(_touchPos); } else if (_touchScrollState == Ui::TouchScrollState::Acceleration) { touchUpdateSpeed(); _touchAccelerationTime = getms(); if (_touchSpeed.isNull()) { _touchScrollState = Ui::TouchScrollState::Manual; } } } } break; case QEvent::TouchEnd: { if (!_touchInProgress) return; _touchInProgress = false; auto weak = make_weak(this); if (_touchSelect) { mouseActionFinish(_touchPos, Qt::RightButton); QContextMenuEvent contextMenu(QContextMenuEvent::Mouse, mapFromGlobal(_touchPos), _touchPos); showContextMenu(&contextMenu, true); _touchScroll = false; } else if (_touchScroll) { if (_touchScrollState == Ui::TouchScrollState::Manual) { _touchScrollState = Ui::TouchScrollState::Auto; _touchPrevPosValid = false; _touchScrollTimer.start(15); _touchTime = getms(); } else if (_touchScrollState == Ui::TouchScrollState::Auto) { _touchScrollState = Ui::TouchScrollState::Manual; _touchScroll = false; touchResetSpeed(); } else if (_touchScrollState == Ui::TouchScrollState::Acceleration) { _touchScrollState = Ui::TouchScrollState::Auto; _touchWaitingAcceleration = false; _touchPrevPosValid = false; } } else { // One short tap is like left mouse click. mouseActionStart(_touchPos, Qt::LeftButton); mouseActionFinish(_touchPos, Qt::LeftButton); } if (weak) { _touchSelectTimer.stop(); _touchSelect = false; } } break; } } void HistoryInner::mouseMoveEvent(QMouseEvent *e) { static auto lastGlobalPosition = e->globalPos(); auto reallyMoved = (lastGlobalPosition != e->globalPos()); auto buttonsPressed = (e->buttons() & (Qt::LeftButton | Qt::MiddleButton)); if (!buttonsPressed && _mouseAction != MouseAction::None) { mouseReleaseEvent(e); } if (reallyMoved) { lastGlobalPosition = e->globalPos(); if (!buttonsPressed || (_scrollDateLink && ClickHandler::getPressed() == _scrollDateLink)) { keepScrollDateForNow(); } } mouseActionUpdate(e->globalPos()); } void HistoryInner::mouseActionUpdate(const QPoint &screenPos) { _mousePosition = screenPos; mouseActionUpdate(); } void HistoryInner::touchScrollUpdated(const QPoint &screenPos) { _touchPos = screenPos; _widget->touchScroll(_touchPos - _touchPrevPos); touchUpdateSpeed(); } QPoint HistoryInner::mapPointToItem(QPoint p, const Element *view) { if (view) { const auto top = itemTop(view); p.setY(p.y() - top); return p; } return QPoint(); } QPoint HistoryInner::mapPointToItem(QPoint p, const HistoryItem *item) { return item ? mapPointToItem(p, item->mainView()) : QPoint(); } void HistoryInner::mousePressEvent(QMouseEvent *e) { if (_menu) { e->accept(); return; // ignore mouse press, that was hiding context menu } mouseActionStart(e->globalPos(), e->button()); } void HistoryInner::mouseActionStart(const QPoint &screenPos, Qt::MouseButton button) { mouseActionUpdate(screenPos); if (button != Qt::LeftButton) return; ClickHandler::pressed(); if (App::pressedItem() != App::hoveredItem()) { repaintItem(App::pressedItem()); App::pressedItem(App::hoveredItem()); repaintItem(App::pressedItem()); } const auto mouseActionView = App::mousedItem(); _mouseAction = MouseAction::None; _mouseActionItem = mouseActionView ? mouseActionView->data().get() : nullptr; _dragStartPosition = mapPointToItem(mapFromGlobal(screenPos), mouseActionView); _pressWasInactive = _controller->window()->wasInactivePress(); if (_pressWasInactive) _controller->window()->setInactivePress(false); if (ClickHandler::getPressed()) { _mouseAction = MouseAction::PrepareDrag; } else if (!_selected.empty()) { if (_selected.cbegin()->second == FullSelection) { if (_dragStateItem && _selected.find(_dragStateItem) != _selected.cend() && App::hoveredItem()) { _mouseAction = MouseAction::PrepareDrag; // start items drag } else if (!_pressWasInactive) { _mouseAction = MouseAction::PrepareSelect; // start items select } } } if (_mouseAction == MouseAction::None && mouseActionView) { TextState dragState; if (_trippleClickTimer.isActive() && (screenPos - _trippleClickPoint).manhattanLength() < QApplication::startDragDistance()) { StateRequest request; request.flags = Text::StateRequest::Flag::LookupSymbol; dragState = mouseActionView->textState(_dragStartPosition, request); if (dragState.cursor == CursorState::Text) { TextSelection selStatus = { dragState.symbol, dragState.symbol }; if (selStatus != FullSelection && (_selected.empty() || _selected.cbegin()->second != FullSelection)) { if (!_selected.empty()) { repaintItem(_selected.cbegin()->first); _selected.clear(); } _selected.emplace(_mouseActionItem, selStatus); _mouseTextSymbol = dragState.symbol; _mouseAction = MouseAction::Selecting; _mouseSelectType = TextSelectType::Paragraphs; mouseActionUpdate(_mousePosition); _trippleClickTimer.start(QApplication::doubleClickInterval()); } } } else if (App::pressedItem()) { StateRequest request; request.flags = Text::StateRequest::Flag::LookupSymbol; dragState = mouseActionView->textState(_dragStartPosition, request); } if (_mouseSelectType != TextSelectType::Paragraphs) { if (App::pressedItem()) { _mouseTextSymbol = dragState.symbol; bool uponSelected = (dragState.cursor == CursorState::Text); if (uponSelected) { if (_selected.empty() || _selected.cbegin()->second == FullSelection || _selected.cbegin()->first != _mouseActionItem) { uponSelected = false; } else { uint16 selFrom = _selected.cbegin()->second.from, selTo = _selected.cbegin()->second.to; if (_mouseTextSymbol < selFrom || _mouseTextSymbol >= selTo) { uponSelected = false; } } } if (uponSelected) { _mouseAction = MouseAction::PrepareDrag; // start text drag } else if (!_pressWasInactive) { if (dynamic_cast<HistorySticker*>(App::pressedItem()->media()) || _mouseCursorState == CursorState::Date) { _mouseAction = MouseAction::PrepareDrag; // start sticker drag or by-date drag } else { if (dragState.afterSymbol) ++_mouseTextSymbol; TextSelection selStatus = { _mouseTextSymbol, _mouseTextSymbol }; if (selStatus != FullSelection && (_selected.empty() || _selected.cbegin()->second != FullSelection)) { if (!_selected.empty()) { repaintItem(_selected.cbegin()->first); _selected.clear(); } _selected.emplace(_mouseActionItem, selStatus); _mouseAction = MouseAction::Selecting; repaintItem(_mouseActionItem); } else { _mouseAction = MouseAction::PrepareSelect; } } } } else if (!_pressWasInactive) { _mouseAction = MouseAction::PrepareSelect; // start items select } } } if (!_mouseActionItem) { _mouseAction = MouseAction::None; } else if (_mouseAction == MouseAction::None) { _mouseActionItem = nullptr; } } void HistoryInner::mouseActionCancel() { _mouseActionItem = nullptr; _dragStateItem = nullptr; _mouseAction = MouseAction::None; _dragStartPosition = QPoint(0, 0); _dragSelFrom = _dragSelTo = nullptr; _wasSelectedText = false; _widget->noSelectingScroll(); } std::unique_ptr<QMimeData> HistoryInner::prepareDrag() { if (_mouseAction != MouseAction::Dragging) { return nullptr; } const auto pressedHandler = ClickHandler::getPressed(); if (dynamic_cast<VoiceSeekClickHandler*>(pressedHandler.get())) { return nullptr; } const auto mouseActionView = _mouseActionItem ? _mouseActionItem->mainView() : nullptr; bool uponSelected = false; if (mouseActionView) { if (!_selected.empty() && _selected.cbegin()->second == FullSelection) { uponSelected = _dragStateItem && (_selected.find(_dragStateItem) != _selected.cend()); } else { StateRequest request; request.flags |= Text::StateRequest::Flag::LookupSymbol; auto dragState = mouseActionView->textState(_dragStartPosition, request); uponSelected = (dragState.cursor == CursorState::Text); if (uponSelected) { if (_selected.empty() || _selected.cbegin()->second == FullSelection || _selected.cbegin()->first != _mouseActionItem) { uponSelected = false; } else { uint16 selFrom = _selected.cbegin()->second.from, selTo = _selected.cbegin()->second.to; if (dragState.symbol < selFrom || dragState.symbol >= selTo) { uponSelected = false; } } } } } TextWithEntities sel; QList<QUrl> urls; if (uponSelected) { sel = getSelectedText(); } else if (pressedHandler) { sel = { pressedHandler->dragText(), EntitiesInText() }; //if (!sel.isEmpty() && sel.at(0) != '/' && sel.at(0) != '@' && sel.at(0) != '#') { // urls.push_back(QUrl::fromEncoded(sel.toUtf8())); // Google Chrome crashes in Mac OS X O_o //} } if (auto mimeData = MimeDataFromTextWithEntities(sel)) { updateDragSelection(0, 0, false); _widget->noSelectingScroll(); if (!urls.isEmpty()) mimeData->setUrls(urls); if (uponSelected && !Adaptive::OneColumn()) { auto selectedState = getSelectionState(); if (selectedState.count > 0 && selectedState.count == selectedState.canForwardCount) { Auth().data().setMimeForwardIds(getSelectedItems()); mimeData->setData(qsl("application/x-td-forward"), "1"); } } return mimeData; } else if (_dragStateItem) { const auto view = _dragStateItem->mainView(); if (!view) { return nullptr; } auto forwardIds = MessageIdsList(); if (_mouseCursorState == CursorState::Date) { forwardIds = Auth().data().itemOrItsGroup(_dragStateItem); } else if (view->isHiddenByGroup() && pressedHandler) { forwardIds = MessageIdsList(1, _dragStateItem->fullId()); } else if (const auto media = view->media()) { if (media->dragItemByHandler(pressedHandler) || media->dragItem()) { forwardIds = MessageIdsList(1, _dragStateItem->fullId()); } } if (forwardIds.empty()) { return nullptr; } Auth().data().setMimeForwardIds(std::move(forwardIds)); auto result = std::make_unique<QMimeData>(); result->setData(qsl("application/x-td-forward"), "1"); if (const auto media = view->media()) { if (const auto document = media->getDocument()) { const auto filepath = document->filepath( DocumentData::FilePathResolveChecked); if (!filepath.isEmpty()) { QList<QUrl> urls; urls.push_back(QUrl::fromLocalFile(filepath)); result->setUrls(urls); } } } return result; } return nullptr; } void HistoryInner::performDrag() { if (auto mimeData = prepareDrag()) { // This call enters event loop and can destroy any QObject. _controller->window()->launchDrag(std::move(mimeData)); } } void HistoryInner::itemRemoved(not_null<const HistoryItem*> item) { if (_history != item->history() && _migrated != item->history()) { return; } if (!App::main()) { return; } auto i = _selected.find(item); if (i != _selected.cend()) { _selected.erase(i); _widget->updateTopBarSelection(); } if (_mouseActionItem == item) { mouseActionCancel(); } if (_dragStateItem == item) { _dragStateItem = nullptr; } if ((_dragSelFrom && _dragSelFrom->data() == item) || (_dragSelTo && _dragSelTo->data() == item)) { _dragSelFrom = nullptr; _dragSelTo = nullptr; update(); } if (_scrollDateLastItem && _scrollDateLastItem->data() == item) { _scrollDateLastItem = nullptr; } mouseActionUpdate(); } void HistoryInner::viewRemoved(not_null<const Element*> view) { if (_dragSelFrom == view) { _dragSelFrom = nullptr; } if (_dragSelTo == view) { _dragSelTo = nullptr; } if (_scrollDateLastItem == view) { _scrollDateLastItem = nullptr; } } void HistoryInner::refreshView(not_null<HistoryItem*> item) { const auto dragSelFrom = (_dragSelFrom && _dragSelFrom->data() == item); const auto dragSelTo = (_dragSelTo && _dragSelTo->data() == item); const auto scrollDateLastItem = (_scrollDateLastItem && _scrollDateLastItem->data() == item); item->refreshMainView(); if (dragSelFrom) { _dragSelFrom = item->mainView(); } if (dragSelTo) { _dragSelTo = item->mainView(); } if (scrollDateLastItem) { _scrollDateLastItem = item->mainView(); } } void HistoryInner::mouseActionFinish( const QPoint &screenPos, Qt::MouseButton button) { mouseActionUpdate(screenPos); auto activated = ClickHandler::unpressed(); if (_mouseAction == MouseAction::Dragging) { activated = nullptr; } else if (_mouseActionItem) { // if we are in selecting items mode perhaps we want to // toggle selection instead of activating the pressed link if (_mouseAction == MouseAction::PrepareDrag && !_pressWasInactive && !_selected.empty() && _selected.cbegin()->second == FullSelection && button != Qt::RightButton) { if (const auto view = _mouseActionItem->mainView()) { if (const auto media = view->media()) { if (media->toggleSelectionByHandlerClick(activated)) { activated = nullptr; } } } } } if (App::pressedItem()) { repaintItem(App::pressedItem()); App::pressedItem(nullptr); } _wasSelectedText = false; if (activated) { mouseActionCancel(); App::activateClickHandler(activated, button); return; } if ((_mouseAction == MouseAction::PrepareSelect) && !_pressWasInactive && !_selected.empty() && (_selected.cbegin()->second == FullSelection)) { changeSelectionAsGroup( &_selected, _mouseActionItem, SelectAction::Invert); repaintItem(_mouseActionItem); } else if ((_mouseAction == MouseAction::PrepareDrag) && !_pressWasInactive && _dragStateItem && (button != Qt::RightButton)) { auto i = _selected.find(_dragStateItem); if (i != _selected.cend() && i->second == FullSelection) { _selected.erase(i); repaintItem(_mouseActionItem); } else if ((i == _selected.cend()) && !_dragStateItem->serviceMsg() && (_dragStateItem->id > 0) && !_selected.empty() && _selected.cbegin()->second == FullSelection) { if (_selected.size() < MaxSelectedItems) { _selected.emplace(_dragStateItem, FullSelection); repaintItem(_mouseActionItem); } } else { _selected.clear(); update(); } } else if (_mouseAction == MouseAction::Selecting) { if (_dragSelFrom && _dragSelTo) { applyDragSelection(); _dragSelFrom = _dragSelTo = nullptr; } else if (!_selected.empty() && !_pressWasInactive) { auto sel = _selected.cbegin()->second; if (sel != FullSelection && sel.from == sel.to) { _selected.clear(); App::wnd()->setInnerFocus(); } } } _mouseAction = MouseAction::None; _mouseActionItem = nullptr; _mouseSelectType = TextSelectType::Letters; _widget->noSelectingScroll(); _widget->updateTopBarSelection(); #if defined Q_OS_LINUX32 || defined Q_OS_LINUX64 if (!_selected.empty() && _selected.cbegin()->second != FullSelection) { const auto [item, selection] = *_selected.cbegin(); if (const auto view = item->mainView()) { SetClipboardWithEntities( view->selectedText(selection), QClipboard::Selection); } } #endif // Q_OS_LINUX32 || Q_OS_LINUX64 } void HistoryInner::mouseReleaseEvent(QMouseEvent *e) { mouseActionFinish(e->globalPos(), e->button()); if (!rect().contains(e->pos())) { leaveEvent(e); } } void HistoryInner::mouseDoubleClickEvent(QMouseEvent *e) { if (!_history) return; mouseActionStart(e->globalPos(), e->button()); const auto mouseActionView = _mouseActionItem ? _mouseActionItem->mainView() : nullptr; if (_mouseSelectType == TextSelectType::Letters && mouseActionView && ((_mouseAction == MouseAction::Selecting && !_selected.empty() && _selected.cbegin()->second != FullSelection) || (_mouseAction == MouseAction::None && (_selected.empty() || _selected.cbegin()->second != FullSelection)))) { StateRequest request; request.flags |= Text::StateRequest::Flag::LookupSymbol; auto dragState = mouseActionView->textState(_dragStartPosition, request); if (dragState.cursor == CursorState::Text) { _mouseTextSymbol = dragState.symbol; _mouseSelectType = TextSelectType::Words; if (_mouseAction == MouseAction::None) { _mouseAction = MouseAction::Selecting; TextSelection selStatus = { dragState.symbol, dragState.symbol }; if (!_selected.empty()) { repaintItem(_selected.cbegin()->first); _selected.clear(); } _selected.emplace(_mouseActionItem, selStatus); } mouseMoveEvent(e); _trippleClickPoint = e->globalPos(); _trippleClickTimer.start(QApplication::doubleClickInterval()); } } if (!ClickHandler::getActive() && !ClickHandler::getPressed() && (_mouseCursorState == CursorState::None || _mouseCursorState == CursorState::Date) && !inSelectionMode()) { if (const auto item = _mouseActionItem) { mouseActionCancel(); _widget->replyToMessage(item); } } } void HistoryInner::contextMenuEvent(QContextMenuEvent *e) { showContextMenu(e); } void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { if (e->reason() == QContextMenuEvent::Mouse) { mouseActionUpdate(e->globalPos()); } auto selectedState = getSelectionState(); auto canSendMessages = _peer->canWrite(); // -2 - has full selected items, but not over, -1 - has selection, but no over, 0 - no selection, 1 - over text, 2 - over full selected items auto isUponSelected = 0; auto hasSelected = 0; if (!_selected.empty()) { isUponSelected = -1; if (_selected.cbegin()->second == FullSelection) { hasSelected = 2; if (_dragStateItem && _selected.find(_dragStateItem) != _selected.cend()) { isUponSelected = 2; } else { isUponSelected = -2; } } else { uint16 selFrom = _selected.cbegin()->second.from, selTo = _selected.cbegin()->second.to; hasSelected = (selTo > selFrom) ? 1 : 0; if (App::mousedItem() && App::mousedItem() == App::hoveredItem()) { auto mousePos = mapPointToItem(mapFromGlobal(_mousePosition), App::mousedItem()); StateRequest request; request.flags |= Text::StateRequest::Flag::LookupSymbol; auto dragState = App::mousedItem()->textState(mousePos, request); if (dragState.cursor == CursorState::Text && dragState.symbol >= selFrom && dragState.symbol < selTo) { isUponSelected = 1; } } } } if (showFromTouch && hasSelected && isUponSelected < hasSelected) { isUponSelected = hasSelected; } _menu = base::make_unique_q<Ui::PopupMenu>(nullptr); const auto addItemActions = [&](HistoryItem *item) { if (!item || !IsServerMsgId(item->id) || isUponSelected == 2 || isUponSelected == -2) { return; } const auto itemId = item->fullId(); if (canSendMessages) { _menu->addAction(lang(lng_context_reply_msg), [=] { _widget->replyToMessage(itemId); }); } if (item->allowsEdit(unixtime())) { _menu->addAction(lang(lng_context_edit_msg), [=] { _widget->editMessage(itemId); }); } if (item->canPin()) { const auto isPinned = item->isPinned(); _menu->addAction(lang(isPinned ? lng_context_unpin_msg : lng_context_pin_msg), [=] { if (isPinned) { _widget->unpinMessage(itemId); } else { _widget->pinMessage(itemId); } }); } }; const auto link = ClickHandler::getActive(); auto lnkPhoto = dynamic_cast<PhotoClickHandler*>(link.get()); auto lnkDocument = dynamic_cast<DocumentClickHandler*>(link.get()); auto lnkIsVideo = lnkDocument ? lnkDocument->document()->isVideoFile() : false; auto lnkIsVoice = lnkDocument ? lnkDocument->document()->isVoiceMessage() : false; auto lnkIsAudio = lnkDocument ? lnkDocument->document()->isAudioFile() : false; if (lnkPhoto || lnkDocument) { const auto item = _dragStateItem; const auto itemId = item ? item->fullId() : FullMsgId(); if (isUponSelected > 0) { _menu->addAction( lang((isUponSelected > 1) ? lng_context_copy_selected_items : lng_context_copy_selected), [=] { copySelectedText(); }); } addItemActions(item); if (lnkPhoto) { const auto photo = lnkPhoto->photo(); _menu->addAction(lang(lng_context_save_image), App::LambdaDelayed(st::defaultDropdownMenu.menu.ripple.hideDuration, this, [=] { savePhotoToFile(photo); })); _menu->addAction(lang(lng_context_copy_image), [=] { copyContextImage(photo); }); } else { auto document = lnkDocument->document(); if (document->loading()) { _menu->addAction(lang(lng_context_cancel_download), [=] { cancelContextDownload(document); }); } else { if (document->loaded() && document->isGifv()) { if (!cAutoPlayGif()) { _menu->addAction(lang(lng_context_open_gif), [=] { openContextGif(itemId); }); } _menu->addAction(lang(lng_context_save_gif), [=] { saveContextGif(itemId); }); } if (!document->filepath(DocumentData::FilePathResolveChecked).isEmpty()) { _menu->addAction(lang((cPlatform() == dbipMac || cPlatform() == dbipMacOld) ? lng_context_show_in_finder : lng_context_show_in_folder), [=] { showContextInFolder(document); }); } _menu->addAction(lang(lnkIsVideo ? lng_context_save_video : (lnkIsVoice ? lng_context_save_audio : (lnkIsAudio ? lng_context_save_audio_file : lng_context_save_file))), App::LambdaDelayed(st::defaultDropdownMenu.menu.ripple.hideDuration, this, [=] { saveDocumentToFile(document); })); } } if (item && item->hasDirectLink() && isUponSelected != 2 && isUponSelected != -2) { _menu->addAction(lang(item->history()->peer->isMegagroup() ? lng_context_copy_link : lng_context_copy_post_link), [=] { _widget->copyPostLink(itemId); }); } if (isUponSelected > 1) { if (selectedState.count > 0 && selectedState.canForwardCount == selectedState.count) { _menu->addAction(lang(lng_context_forward_selected), [=] { _widget->forwardSelected(); }); } if (selectedState.count > 0 && selectedState.canDeleteCount == selectedState.count) { _menu->addAction(lang(lng_context_delete_selected), [=] { _widget->confirmDeleteSelected(); }); } _menu->addAction(lang(lng_context_clear_selection), [=] { _widget->clearSelected(); }); } else if (item) { const auto itemId = item->fullId(); if (isUponSelected != -2) { if (item->allowsForward()) { _menu->addAction(lang(lng_context_forward_msg), [=] { forwardItem(itemId); }); } if (item->canDelete()) { _menu->addAction(lang(lng_context_delete_msg), [=] { deleteItem(itemId); }); } } if (IsServerMsgId(item->id) && !item->serviceMsg()) { _menu->addAction(lang(lng_context_select_msg), [=] { if (const auto item = App::histItemById(itemId)) { if (const auto view = item->mainView()) { changeSelection(&_selected, item, SelectAction::Select); repaintItem(item); _widget->updateTopBarSelection(); } } }); } } } else { // maybe cursor on some text history item? const auto item = [&]() -> HistoryItem* { if (const auto result = App::hoveredItem() ? App::hoveredItem()->data().get() : App::hoveredLinkItem() ? App::hoveredLinkItem()->data().get() : nullptr) { if (const auto group = Auth().data().groups().find(result)) { return group->items.front(); } return result; } return nullptr; }(); const auto itemId = item ? item->fullId() : FullMsgId(); const auto canDelete = item && item->canDelete() && (item->id > 0 || !item->serviceMsg()); const auto canForward = item && item->allowsForward(); const auto view = item ? item->mainView() : nullptr; const auto msg = dynamic_cast<HistoryMessage*>(item); if (isUponSelected > 0) { _menu->addAction( lang((isUponSelected > 1) ? lng_context_copy_selected_items : lng_context_copy_selected), [=] { copySelectedText(); }); addItemActions(item); } else { addItemActions(item); if (item && !isUponSelected) { auto mediaHasTextForCopy = false; if (auto media = (view ? view->media() : nullptr)) { mediaHasTextForCopy = media->hasTextForCopy(); if (media->type() == MediaTypeWebPage && static_cast<HistoryWebPage*>(media)->attach()) { media = static_cast<HistoryWebPage*>(media)->attach(); } if (media->type() == MediaTypeSticker) { if (auto document = media->getDocument()) { if (document->sticker() && document->sticker()->set.type() != mtpc_inputStickerSetEmpty) { _menu->addAction(lang(document->isStickerSetInstalled() ? lng_context_pack_info : lng_context_pack_add), [=] { showStickerPackInfo(document); }); _menu->addAction(lang(Stickers::IsFaved(document) ? lng_faved_stickers_remove : lng_faved_stickers_add), [=] { toggleFavedSticker(document); }); } _menu->addAction(lang(lng_context_save_image), App::LambdaDelayed(st::defaultDropdownMenu.menu.ripple.hideDuration, this, [this, document] { saveDocumentToFile(document); })); } } } if (msg && view && !link && (view->hasVisibleText() || mediaHasTextForCopy)) { _menu->addAction(lang(lng_context_copy_text), [=] { copyContextText(itemId); }); } } } const auto actionText = link ? link->copyToClipboardContextItemText() : QString(); if (!actionText.isEmpty()) { _menu->addAction( actionText, [text = link->copyToClipboardText()] { QApplication::clipboard()->setText(text); }); } else if (item && item->hasDirectLink() && isUponSelected != 2 && isUponSelected != -2) { _menu->addAction(lang(item->history()->peer->isMegagroup() ? lng_context_copy_link : lng_context_copy_post_link), [=] { _widget->copyPostLink(itemId); }); } if (isUponSelected > 1) { if (selectedState.count > 0 && selectedState.count == selectedState.canForwardCount) { _menu->addAction(lang(lng_context_forward_selected), [=] { _widget->forwardSelected(); }); } if (selectedState.count > 0 && selectedState.count == selectedState.canDeleteCount) { _menu->addAction(lang(lng_context_delete_selected), [=] { _widget->confirmDeleteSelected(); }); } _menu->addAction(lang(lng_context_clear_selection), [=] { _widget->clearSelected(); }); } else if (item && ((isUponSelected != -2 && (canForward || canDelete)) || item->id > 0)) { if (isUponSelected != -2) { if (canForward) { _menu->addAction(lang(lng_context_forward_msg), [=] { forwardAsGroup(itemId); }); } if (canDelete) { _menu->addAction(lang((msg && msg->uploading()) ? lng_context_cancel_upload : lng_context_delete_msg), [=] { deleteAsGroup(itemId); }); } } if (item->id > 0 && !item->serviceMsg()) { _menu->addAction(lang(lng_context_select_msg), [=] { if (const auto item = App::histItemById(itemId)) { if (const auto view = item->mainView()) { changeSelectionAsGroup(&_selected, item, SelectAction::Select); repaintItem(view); _widget->updateTopBarSelection(); } } }); } } else { if (App::mousedItem() && IsServerMsgId(App::mousedItem()->data()->id) && !App::mousedItem()->data()->serviceMsg()) { const auto itemId = App::mousedItem()->data()->fullId(); _menu->addAction(lang(lng_context_select_msg), [=] { if (const auto item = App::histItemById(itemId)) { if (const auto view = item->mainView()) { changeSelectionAsGroup(&_selected, item, SelectAction::Select); repaintItem(item); _widget->updateTopBarSelection(); } } }); } } } if (_menu->actions().isEmpty()) { _menu = nullptr; } else { _menu->popup(e->globalPos()); e->accept(); } } void HistoryInner::copySelectedText() { SetClipboardWithEntities(getSelectedText()); } void HistoryInner::savePhotoToFile(not_null<PhotoData*> photo) { if (!photo->date || !photo->loaded()) return; auto filter = qsl("JPEG Image (*.jpg);;") + FileDialog::AllFilesFilter(); FileDialog::GetWritePath( lang(lng_save_photo), filter, filedialogDefaultName( qsl("photo"), qsl(".jpg")), base::lambda_guarded(this, [=](const QString &result) { if (!result.isEmpty()) { photo->full->pix().toImage().save(result, "JPG"); } })); } void HistoryInner::copyContextImage(not_null<PhotoData*> photo) { if (!photo->date || !photo->loaded()) return; QApplication::clipboard()->setPixmap(photo->full->pix()); } void HistoryInner::showStickerPackInfo(not_null<DocumentData*> document) { if (auto sticker = document->sticker()) { if (sticker->set.type() != mtpc_inputStickerSetEmpty) { App::main()->stickersBox(sticker->set); } } } void HistoryInner::toggleFavedSticker(not_null<DocumentData*> document) { auto unfave = Stickers::IsFaved(document); MTP::send(MTPmessages_FaveSticker(document->mtpInput(), MTP_bool(unfave)), rpcDone([document, unfave](const MTPBool &result) { Stickers::SetFaved(document, !unfave); })); } void HistoryInner::cancelContextDownload(not_null<DocumentData*> document) { document->cancel(); } void HistoryInner::showContextInFolder(not_null<DocumentData*> document) { const auto filepath = document->filepath( DocumentData::FilePathResolveChecked); if (!filepath.isEmpty()) { File::ShowInFolder(filepath); } } void HistoryInner::saveDocumentToFile(not_null<DocumentData*> document) { DocumentSaveClickHandler::doSave(document, true); } void HistoryInner::openContextGif(FullMsgId itemId) { if (const auto item = App::histItemById(itemId)) { if (const auto media = item->media()) { if (const auto document = media->document()) { Messenger::Instance().showDocument(document, item); } } } } void HistoryInner::saveContextGif(FullMsgId itemId) { if (const auto item = App::histItemById(itemId)) { if (const auto media = item->media()) { if (const auto document = media->document()) { _widget->saveGif(document); } } } } void HistoryInner::copyContextText(FullMsgId itemId) { if (const auto item = App::histItemById(itemId)) { if (const auto group = Auth().data().groups().find(item)) { SetClipboardWithEntities(HistoryGroupText(group)); } else { SetClipboardWithEntities(HistoryItemText(item)); } } } void HistoryInner::resizeEvent(QResizeEvent *e) { mouseActionUpdate(); } TextWithEntities HistoryInner::getSelectedText() const { auto selected = _selected; if (_mouseAction == MouseAction::Selecting && _dragSelFrom && _dragSelTo) { applyDragSelection(&selected); } if (selected.empty()) { return TextWithEntities(); } if (selected.cbegin()->second != FullSelection) { const auto [item, selection] = *selected.cbegin(); if (const auto view = item->mainView()) { return view->selectedText(selection); } return TextWithEntities(); } const auto timeFormat = qsl(", [dd.MM.yy hh:mm]\n"); auto groups = base::flat_set<not_null<const Data::Group*>>(); auto fullSize = 0; auto texts = base::flat_map<Data::MessagePosition, TextWithEntities>(); const auto wrapItem = [&]( not_null<HistoryItem*> item, TextWithEntities &&unwrapped) { auto time = ItemDateTime(item).toString(timeFormat); auto part = TextWithEntities(); auto size = item->author()->name.size() + time.size() + unwrapped.text.size(); part.text.reserve(size); part.text.append(item->author()->name).append(time); TextUtilities::Append(part, std::move(unwrapped)); texts.emplace(item->position(), part); fullSize += size; }; const auto addItem = [&](not_null<HistoryItem*> item) { wrapItem(item, HistoryItemText(item)); }; const auto addGroup = [&](not_null<const Data::Group*> group) { Expects(!group->items.empty()); wrapItem(group->items.back(), HistoryGroupText(group)); }; for (const auto [item, selection] : selected) { if (const auto group = Auth().data().groups().find(item)) { if (groups.contains(group)) { continue; } if (isSelectedGroup(&selected, group)) { groups.emplace(group); addGroup(group); } else { addItem(item); } } else { addItem(item); } } auto result = TextWithEntities(); auto sep = qsl("\n\n"); result.text.reserve(fullSize + (texts.size() - 1) * sep.size()); for (auto i = texts.begin(), e = texts.end(); i != e;) { TextUtilities::Append(result, std::move(i->second)); if (++i != e) { result.text.append(sep); } } return result; } void HistoryInner::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Escape) { _widget->onListEscapePressed(); } else if (e == QKeySequence::Copy && !_selected.empty()) { copySelectedText(); #ifdef Q_OS_MAC } else if (e->key() == Qt::Key_E && e->modifiers().testFlag(Qt::ControlModifier)) { SetClipboardWithEntities(getSelectedText(), QClipboard::FindBuffer); #endif // Q_OS_MAC } else if (e == QKeySequence::Delete) { auto selectedState = getSelectionState(); if (selectedState.count > 0 && selectedState.canDeleteCount == selectedState.count) { _widget->confirmDeleteSelected(); } } else { e->ignore(); } } void HistoryInner::recountHistoryGeometry() { _contentWidth = _scroll->width(); const auto visibleHeight = _scroll->height(); int oldHistoryPaddingTop = qMax(visibleHeight - historyHeight() - st::historyPaddingBottom, 0); if (_botAbout && !_botAbout->info->text.isEmpty()) { accumulate_max(oldHistoryPaddingTop, st::msgMargin.top() + st::msgMargin.bottom() + st::msgPadding.top() + st::msgPadding.bottom() + st::msgNameFont->height + st::botDescSkip + _botAbout->height); } _history->resizeToWidth(_contentWidth); if (_migrated) { _migrated->resizeToWidth(_contentWidth); } // with migrated history we perhaps do not need to display first _history message // (if last _migrated message and first _history message are both isGroupMigrate) // or at least we don't need to display first _history date (just skip it by height) _historySkipHeight = 0; if (_migrated) { if (!_migrated->isEmpty() && !_history->isEmpty() && _migrated->loadedAtBottom() && _history->loadedAtTop()) { if (_migrated->blocks.back()->messages.back()->dateTime().date() == _history->blocks.front()->messages.front()->dateTime().date()) { if (_migrated->blocks.back()->messages.back()->data()->isGroupMigrate() && _history->blocks.front()->messages.front()->data()->isGroupMigrate()) { _historySkipHeight += _history->blocks.front()->messages.front()->height(); } else { _historySkipHeight += _history->blocks.front()->messages.front()->displayedDateHeight(); } } } } updateBotInfo(false); if (_botAbout && !_botAbout->info->text.isEmpty()) { int32 tw = _scroll->width() - st::msgMargin.left() - st::msgMargin.right(); if (tw > st::msgMaxWidth) tw = st::msgMaxWidth; tw -= st::msgPadding.left() + st::msgPadding.right(); int32 mw = qMax(_botAbout->info->text.maxWidth(), st::msgNameFont->width(lang(lng_bot_description))); if (tw > mw) tw = mw; _botAbout->width = tw; _botAbout->height = _botAbout->info->text.countHeight(_botAbout->width); int32 descH = st::msgMargin.top() + st::msgPadding.top() + st::msgNameFont->height + st::botDescSkip + _botAbout->height + st::msgPadding.bottom() + st::msgMargin.bottom(); int32 descMaxWidth = _scroll->width(); if (Adaptive::ChatWide()) { descMaxWidth = qMin(descMaxWidth, int32(st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left())); } int32 descAtX = (descMaxWidth - _botAbout->width) / 2 - st::msgPadding.left(); int32 descAtY = qMin(_historyPaddingTop - descH, qMax(0, (_scroll->height() - descH) / 2)) + st::msgMargin.top(); _botAbout->rect = QRect(descAtX, descAtY, _botAbout->width + st::msgPadding.left() + st::msgPadding.right(), descH - st::msgMargin.top() - st::msgMargin.bottom()); } else if (_botAbout) { _botAbout->width = _botAbout->height = 0; _botAbout->rect = QRect(); } int newHistoryPaddingTop = qMax(visibleHeight - historyHeight() - st::historyPaddingBottom, 0); if (_botAbout && !_botAbout->info->text.isEmpty()) { accumulate_max(newHistoryPaddingTop, st::msgMargin.top() + st::msgMargin.bottom() + st::msgPadding.top() + st::msgPadding.bottom() + st::msgNameFont->height + st::botDescSkip + _botAbout->height); } auto historyPaddingTopDelta = (newHistoryPaddingTop - oldHistoryPaddingTop); if (historyPaddingTopDelta != 0) { if (_history->scrollTopItem) { _history->scrollTopOffset += historyPaddingTopDelta; } else if (_migrated && _migrated->scrollTopItem) { _migrated->scrollTopOffset += historyPaddingTopDelta; } } } void HistoryInner::updateBotInfo(bool recount) { int newh = 0; if (_botAbout && !_botAbout->info->description.isEmpty()) { if (_botAbout->info->text.isEmpty()) { _botAbout->info->text.setText( st::messageTextStyle, _botAbout->info->description, Ui::ItemTextBotNoMonoOptions()); if (recount) { int32 tw = _scroll->width() - st::msgMargin.left() - st::msgMargin.right(); if (tw > st::msgMaxWidth) tw = st::msgMaxWidth; tw -= st::msgPadding.left() + st::msgPadding.right(); int32 mw = qMax(_botAbout->info->text.maxWidth(), st::msgNameFont->width(lang(lng_bot_description))); if (tw > mw) tw = mw; _botAbout->width = tw; newh = _botAbout->info->text.countHeight(_botAbout->width); } } else if (recount) { newh = _botAbout->height; } } if (recount && _botAbout) { if (_botAbout->height != newh) { _botAbout->height = newh; updateSize(); } if (_botAbout->height > 0) { int32 descH = st::msgMargin.top() + st::msgPadding.top() + st::msgNameFont->height + st::botDescSkip + _botAbout->height + st::msgPadding.bottom() + st::msgMargin.bottom(); int32 descAtX = (_scroll->width() - _botAbout->width) / 2 - st::msgPadding.left(); int32 descAtY = qMin(_historyPaddingTop - descH, (_scroll->height() - descH) / 2) + st::msgMargin.top(); _botAbout->rect = QRect(descAtX, descAtY, _botAbout->width + st::msgPadding.left() + st::msgPadding.right(), descH - st::msgMargin.top() - st::msgMargin.bottom()); } else { _botAbout->width = 0; _botAbout->rect = QRect(); } } } bool HistoryInner::wasSelectedText() const { return _wasSelectedText; } void HistoryInner::setFirstLoading(bool loading) { _firstLoading = loading; update(); } void HistoryInner::visibleAreaUpdated(int top, int bottom) { auto scrolledUp = (top < _visibleAreaTop); _visibleAreaTop = top; _visibleAreaBottom = bottom; // if history has pending resize events we should not update scrollTopItem if (hasPendingResizedItems()) { return; } if (bottom >= _historyPaddingTop + historyHeight() + st::historyPaddingBottom) { _history->forgetScrollState(); if (_migrated) { _migrated->forgetScrollState(); } } else { int htop = historyTop(), mtop = migratedTop(); if ((htop >= 0 && top >= htop) || mtop < 0) { _history->countScrollState(top - htop); if (_migrated) { _migrated->forgetScrollState(); } } else if (mtop >= 0 && top >= mtop) { _history->forgetScrollState(); _migrated->countScrollState(top - mtop); } else { _history->countScrollState(top - htop); if (_migrated) { _migrated->forgetScrollState(); } } } if (scrolledUp) { _scrollDateCheck.call(); } else { scrollDateHideByTimer(); } } bool HistoryInner::displayScrollDate() const { return (_visibleAreaTop <= height() - 2 * (_visibleAreaBottom - _visibleAreaTop)); } void HistoryInner::scrollDateCheck() { if (!_history) return; auto newScrollDateItem = _history->scrollTopItem ? _history->scrollTopItem : (_migrated ? _migrated->scrollTopItem : nullptr); auto newScrollDateItemTop = _history->scrollTopItem ? _history->scrollTopOffset : (_migrated ? _migrated->scrollTopOffset : 0); //if (newScrollDateItem && !displayScrollDate()) { // if (!_history->isEmpty() && newScrollDateItem->date.date() == _history->blocks.back()->messages.back()->data()->date.date()) { // newScrollDateItem = nullptr; // } //} if (!newScrollDateItem) { _scrollDateLastItem = nullptr; _scrollDateLastItemTop = 0; scrollDateHide(); } else if (newScrollDateItem != _scrollDateLastItem || newScrollDateItemTop != _scrollDateLastItemTop) { // Show scroll date only if it is not the initial onScroll() event (with empty _scrollDateLastItem). if (_scrollDateLastItem && !_scrollDateShown) { toggleScrollDateShown(); } _scrollDateLastItem = newScrollDateItem; _scrollDateLastItemTop = newScrollDateItemTop; _scrollDateHideTimer.callOnce(kScrollDateHideTimeout); } } void HistoryInner::scrollDateHideByTimer() { _scrollDateHideTimer.cancel(); if (!_scrollDateLink || ClickHandler::getPressed() != _scrollDateLink) { scrollDateHide(); } } void HistoryInner::scrollDateHide() { if (_scrollDateShown) { toggleScrollDateShown(); } } void HistoryInner::keepScrollDateForNow() { if (!_scrollDateShown && _scrollDateLastItem && _scrollDateOpacity.animating()) { toggleScrollDateShown(); } _scrollDateHideTimer.callOnce(kScrollDateHideTimeout); } void HistoryInner::toggleScrollDateShown() { _scrollDateShown = !_scrollDateShown; auto from = _scrollDateShown ? 0. : 1.; auto to = _scrollDateShown ? 1. : 0.; _scrollDateOpacity.start([this] { repaintScrollDateCallback(); }, from, to, st::historyDateFadeDuration); } void HistoryInner::repaintScrollDateCallback() { int updateTop = _visibleAreaTop; int updateHeight = st::msgServiceMargin.top() + st::msgServicePadding.top() + st::msgServiceFont->height + st::msgServicePadding.bottom(); update(0, updateTop, width(), updateHeight); } void HistoryInner::updateSize() { int visibleHeight = _scroll->height(); int newHistoryPaddingTop = qMax(visibleHeight - historyHeight() - st::historyPaddingBottom, 0); if (_botAbout && !_botAbout->info->text.isEmpty()) { accumulate_max(newHistoryPaddingTop, st::msgMargin.top() + st::msgMargin.bottom() + st::msgPadding.top() + st::msgPadding.bottom() + st::msgNameFont->height + st::botDescSkip + _botAbout->height); } if (_botAbout && _botAbout->height > 0) { int32 descH = st::msgMargin.top() + st::msgPadding.top() + st::msgNameFont->height + st::botDescSkip + _botAbout->height + st::msgPadding.bottom() + st::msgMargin.bottom(); int32 descMaxWidth = _scroll->width(); if (Adaptive::ChatWide()) { descMaxWidth = qMin(descMaxWidth, int32(st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left())); } int32 descAtX = (descMaxWidth - _botAbout->width) / 2 - st::msgPadding.left(); int32 descAtY = qMin(newHistoryPaddingTop - descH, qMax(0, (_scroll->height() - descH) / 2)) + st::msgMargin.top(); _botAbout->rect = QRect(descAtX, descAtY, _botAbout->width + st::msgPadding.left() + st::msgPadding.right(), descH - st::msgMargin.top() - st::msgMargin.bottom()); } _historyPaddingTop = newHistoryPaddingTop; int newHeight = _historyPaddingTop + historyHeight() + st::historyPaddingBottom; if (width() != _scroll->width() || height() != newHeight) { resize(_scroll->width(), newHeight); mouseActionUpdate(QCursor::pos()); } else { update(); } } void HistoryInner::enterEventHook(QEvent *e) { mouseActionUpdate(QCursor::pos()); return TWidget::enterEventHook(e); } void HistoryInner::leaveEventHook(QEvent *e) { if (auto item = App::hoveredItem()) { repaintItem(item); App::hoveredItem(nullptr); } ClickHandler::clearActive(); Ui::Tooltip::Hide(); if (!ClickHandler::getPressed() && _cursor != style::cur_default) { _cursor = style::cur_default; setCursor(_cursor); } return TWidget::leaveEventHook(e); } HistoryInner::~HistoryInner() { delete _menu; _mouseAction = MouseAction::None; } bool HistoryInner::focusNextPrevChild(bool next) { if (_selected.empty()) { return TWidget::focusNextPrevChild(next); } else { clearSelected(); return true; } } void HistoryInner::adjustCurrent(int32 y) const { int32 htop = historyTop(), hdrawtop = historyDrawTop(), mtop = migratedTop(); _curHistory = 0; if (mtop >= 0) { adjustCurrent(y - mtop, _migrated); } if (htop >= 0 && hdrawtop >= 0 && (mtop < 0 || y >= hdrawtop)) { adjustCurrent(y - htop, _history); } } void HistoryInner::adjustCurrent(int32 y, History *history) const { Assert(!history->isEmpty()); _curHistory = history; if (_curBlock >= history->blocks.size()) { _curBlock = history->blocks.size() - 1; _curItem = 0; } while (history->blocks[_curBlock]->y() > y && _curBlock > 0) { --_curBlock; _curItem = 0; } while (history->blocks[_curBlock]->y() + history->blocks[_curBlock]->height() <= y && _curBlock + 1 < history->blocks.size()) { ++_curBlock; _curItem = 0; } auto block = history->blocks[_curBlock].get(); if (_curItem >= block->messages.size()) { _curItem = block->messages.size() - 1; } auto by = block->y(); while (block->messages[_curItem]->y() + by > y && _curItem > 0) { --_curItem; } while (block->messages[_curItem]->y() + block->messages[_curItem]->height() + by <= y && _curItem + 1 < block->messages.size()) { ++_curItem; } } auto HistoryInner::prevItem(Element *view) -> Element* { if (!view) { return nullptr; } else if (const auto result = view->previousInBlocks()) { return result; } else if (view->data()->history() == _history && _migrated && _history->loadedAtTop() && !_migrated->isEmpty() && _migrated->loadedAtBottom()) { return _migrated->blocks.back()->messages.back().get(); } return nullptr; } auto HistoryInner::nextItem(Element *view) -> Element* { if (!view) { return nullptr; } else if (const auto result = view->nextInBlocks()) { return result; } else if (view->data()->history() == _migrated && _history && _migrated->loadedAtBottom() && _history->loadedAtTop() && !_history->isEmpty()) { return _history->blocks.front()->messages.front().get(); } return nullptr; } bool HistoryInner::canCopySelected() const { return !_selected.empty(); } bool HistoryInner::canDeleteSelected() const { auto selectedState = getSelectionState(); return (selectedState.count > 0) && (selectedState.count == selectedState.canDeleteCount); } bool HistoryInner::inSelectionMode() const { if (!_selected.empty() && (_selected.begin()->second == FullSelection)) { return true; } else if (_mouseAction == MouseAction::Selecting && _dragSelFrom && _dragSelTo) { return true; } return false; } auto HistoryInner::getSelectionState() const -> HistoryView::TopBarWidget::SelectedState { auto result = HistoryView::TopBarWidget::SelectedState {}; for (auto &selected : _selected) { if (selected.second == FullSelection) { ++result.count; if (selected.first->canDelete()) { ++result.canDeleteCount; } if (selected.first->allowsForward()) { ++result.canForwardCount; } } else if (selected.second.from != selected.second.to) { result.textSelected = true; } } return result; } void HistoryInner::clearSelected(bool onlyTextSelection) { if (!_selected.empty() && (!onlyTextSelection || _selected.cbegin()->second != FullSelection)) { _selected.clear(); _widget->updateTopBarSelection(); _widget->update(); } } MessageIdsList HistoryInner::getSelectedItems() const { using namespace ranges; if (_selected.empty() || _selected.cbegin()->second != FullSelection) { return {}; } auto result = make_iterator_range( _selected.begin(), _selected.end() ) | view::filter([](const auto &selected) { const auto item = selected.first; return item && item->toHistoryMessage() && (item->id > 0); }) | view::transform([](const auto &selected) { return selected.first->fullId(); }) | to_vector; result |= action::sort(ordered_less{}, [](const FullMsgId &msgId) { return msgId.channel ? msgId.msg : (msgId.msg - ServerMaxMsgId); }); return result; } void HistoryInner::selectItem(not_null<HistoryItem*> item) { if (!_selected.empty() && _selected.cbegin()->second != FullSelection) { _selected.clear(); } else if (_selected.size() == MaxSelectedItems && _selected.find(item) == _selected.cend()) { return; } _selected.emplace(item, FullSelection); _widget->updateTopBarSelection(); _widget->update(); } void HistoryInner::onTouchSelect() { _touchSelect = true; mouseActionStart(_touchPos, Qt::LeftButton); } void HistoryInner::mouseActionUpdate() { if (!_history || hasPendingResizedItems()) { return; } auto mousePos = mapFromGlobal(_mousePosition); auto point = _widget->clampMousePosition(mousePos); auto block = (HistoryBlock*)nullptr; auto item = (HistoryItem*)nullptr; auto view = (Element*)nullptr; QPoint m; adjustCurrent(point.y()); if (_curHistory && !_curHistory->isEmpty()) { block = _curHistory->blocks[_curBlock].get(); view = block->messages[_curItem].get(); item = view->data(); App::mousedItem(view); m = mapPointToItem(point, view); if (view->pointState(m) != PointState::Outside) { if (App::hoveredItem() != view) { repaintItem(App::hoveredItem()); App::hoveredItem(view); repaintItem(App::hoveredItem()); } } else if (App::hoveredItem()) { repaintItem(App::hoveredItem()); App::hoveredItem(nullptr); } } if (_mouseActionItem && !_mouseActionItem->mainView()) { mouseActionCancel(); } TextState dragState; ClickHandlerHost *lnkhost = nullptr; auto selectingText = (item == _mouseActionItem) && (view == App::hoveredItem()) && !_selected.empty() && (_selected.cbegin()->second != FullSelection); if (point.y() < _historyPaddingTop) { if (_botAbout && !_botAbout->info->text.isEmpty() && _botAbout->height > 0) { dragState = TextState(nullptr, _botAbout->info->text.getState( point - _botAbout->rect.topLeft() - QPoint(st::msgPadding.left(), st::msgPadding.top() + st::botDescSkip + st::msgNameFont->height), _botAbout->width)); _dragStateItem = App::histItemById(dragState.itemId); lnkhost = _botAbout.get(); } } else if (item) { if (item != _mouseActionItem || (m - _dragStartPosition).manhattanLength() >= QApplication::startDragDistance()) { if (_mouseAction == MouseAction::PrepareDrag) { _mouseAction = MouseAction::Dragging; crl::on_main(this, [=] { performDrag(); }); } else if (_mouseAction == MouseAction::PrepareSelect) { _mouseAction = MouseAction::Selecting; } } auto dateHeight = st::msgServicePadding.bottom() + st::msgServiceFont->height + st::msgServicePadding.top(); auto scrollDateOpacity = _scrollDateOpacity.current(_scrollDateShown ? 1. : 0.); enumerateDates([&](not_null<Element*> view, int itemtop, int dateTop) { // stop enumeration if the date is above our point if (dateTop + dateHeight <= point.y()) { return false; } const auto displayDate = view->displayDate(); auto dateInPlace = displayDate; if (dateInPlace) { const auto correctDateTop = itemtop + st::msgServiceMargin.top(); dateInPlace = (dateTop < correctDateTop + dateHeight); } // stop enumeration if we've found a date under the cursor if (dateTop <= point.y()) { auto opacity = (dateInPlace/* || noFloatingDate*/) ? 1. : scrollDateOpacity; if (opacity > 0.) { const auto item = view->data(); auto dateWidth = 0; if (const auto date = view->Get<HistoryView::DateBadge>()) { dateWidth = date->width; } else { dateWidth = st::msgServiceFont->width(langDayOfMonthFull(view->dateTime().date())); } dateWidth += st::msgServicePadding.left() + st::msgServicePadding.right(); auto dateLeft = st::msgServiceMargin.left(); auto maxwidth = _contentWidth; if (Adaptive::ChatWide()) { maxwidth = qMin(maxwidth, int32(st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left())); } auto widthForDate = maxwidth - st::msgServiceMargin.left() - st::msgServiceMargin.left(); dateLeft += (widthForDate - dateWidth) / 2; if (point.x() >= dateLeft && point.x() < dateLeft + dateWidth) { if (!_scrollDateLink) { _scrollDateLink = std::make_shared<Window::DateClickHandler>(item->history(), view->dateTime().date()); } else { static_cast<Window::DateClickHandler*>(_scrollDateLink.get())->setDate(view->dateTime().date()); } dragState = TextState( nullptr, _scrollDateLink); _dragStateItem = App::histItemById(dragState.itemId); lnkhost = view; } } return false; } return true; }); if (!dragState.link) { StateRequest request; if (_mouseAction == MouseAction::Selecting) { request.flags |= Text::StateRequest::Flag::LookupSymbol; } else { selectingText = false; } dragState = view->textState(m, request); _dragStateItem = App::histItemById(dragState.itemId); lnkhost = view; if (!dragState.link && m.x() >= st::historyPhotoLeft && m.x() < st::historyPhotoLeft + st::msgPhotoSize) { if (auto msg = item->toHistoryMessage()) { if (view->hasFromPhoto()) { enumerateUserpics([&](not_null<Element*> view, int userpicTop) -> bool { // stop enumeration if the userpic is below our point if (userpicTop > point.y()) { return false; } // stop enumeration if we've found a userpic under the cursor if (point.y() >= userpicTop && point.y() < userpicTop + st::msgPhotoSize) { const auto message = view->data()->toHistoryMessage(); Assert(message != nullptr); dragState = TextState( nullptr, message->displayFrom()->openLink()); _dragStateItem = App::histItemById(dragState.itemId); lnkhost = view; return false; } return true; }); } } } } } auto lnkChanged = ClickHandler::setActive(dragState.link, lnkhost); if (lnkChanged || dragState.cursor != _mouseCursorState) { Ui::Tooltip::Hide(); } if (dragState.link || dragState.cursor == CursorState::Date || dragState.cursor == CursorState::Forwarded) { Ui::Tooltip::Show(1000, this); } Qt::CursorShape cur = style::cur_default; if (_mouseAction == MouseAction::None) { _mouseCursorState = dragState.cursor; if (dragState.link) { cur = style::cur_pointer; } else if (_mouseCursorState == CursorState::Text && (_selected.empty() || _selected.cbegin()->second != FullSelection)) { cur = style::cur_text; } else if (_mouseCursorState == CursorState::Date) { //cur = style::cur_cross; } } else if (item) { if (_mouseAction == MouseAction::Selecting) { auto canSelectMany = (_history != nullptr); if (selectingText) { uint16 second = dragState.symbol; if (dragState.afterSymbol && _mouseSelectType == TextSelectType::Letters) { ++second; } auto selState = TextSelection { qMin(second, _mouseTextSymbol), qMax(second, _mouseTextSymbol) }; if (_mouseSelectType != TextSelectType::Letters) { if (const auto view = _mouseActionItem->mainView()) { selState = view->adjustSelection(selState, _mouseSelectType); } } if (_selected[_mouseActionItem] != selState) { _selected[_mouseActionItem] = selState; repaintItem(_mouseActionItem); } if (!_wasSelectedText && (selState == FullSelection || selState.from != selState.to)) { _wasSelectedText = true; setFocus(); } updateDragSelection(0, 0, false); } else if (canSelectMany) { auto selectingDown = (itemTop(_mouseActionItem) < itemTop(item)) || (_mouseActionItem == item && _dragStartPosition.y() < m.y()); auto dragSelFrom = _mouseActionItem->mainView(); auto dragSelTo = view; // Maybe exclude dragSelFrom. if (dragSelFrom->pointState(_dragStartPosition) == PointState::Outside) { if (selectingDown) { if (_dragStartPosition.y() >= dragSelFrom->height() - dragSelFrom->marginBottom() || ((view == dragSelFrom) && (m.y() < _dragStartPosition.y() + QApplication::startDragDistance() || m.y() < dragSelFrom->marginTop()))) { dragSelFrom = (dragSelFrom != dragSelTo) ? nextItem(dragSelFrom) : nullptr; } } else { if (_dragStartPosition.y() < dragSelFrom->marginTop() || ((view == dragSelFrom) && (m.y() >= _dragStartPosition.y() - QApplication::startDragDistance() || m.y() >= dragSelFrom->height() - dragSelFrom->marginBottom()))) { dragSelFrom = (dragSelFrom != dragSelTo) ? prevItem(dragSelFrom) : nullptr; } } } if (_mouseActionItem != item) { // maybe exclude dragSelTo if (selectingDown) { if (m.y() < dragSelTo->marginTop()) { dragSelTo = (dragSelFrom != dragSelTo) ? prevItem(dragSelTo) : nullptr; } } else { if (m.y() >= dragSelTo->height() - dragSelTo->marginBottom()) { dragSelTo = (dragSelFrom != dragSelTo) ? nextItem(dragSelTo) : nullptr; } } } auto dragSelecting = false; auto dragFirstAffected = dragSelFrom; while (dragFirstAffected && (dragFirstAffected->data()->id < 0 || dragFirstAffected->data()->serviceMsg())) { dragFirstAffected = (dragFirstAffected != dragSelTo) ? (selectingDown ? nextItem(dragFirstAffected) : prevItem(dragFirstAffected)) : nullptr; } if (dragFirstAffected) { auto i = _selected.find(dragFirstAffected->data()); dragSelecting = (i == _selected.cend() || i->second != FullSelection); } updateDragSelection(dragSelFrom, dragSelTo, dragSelecting); } } else if (_mouseAction == MouseAction::Dragging) { } if (ClickHandler::getPressed()) { cur = style::cur_pointer; } else if ((_mouseAction == MouseAction::Selecting) && !_selected.empty() && (_selected.cbegin()->second != FullSelection)) { if (!_dragSelFrom || !_dragSelTo) { cur = style::cur_text; } } } // Voice message seek support. if (const auto pressedItem = _dragStateItem) { if (const auto pressedView = pressedItem->mainView()) { if (pressedItem->history() == _history || pressedItem->history() == _migrated) { auto adjustedPoint = mapPointToItem(point, pressedView); pressedView->updatePressed(adjustedPoint); } } } if (_mouseAction == MouseAction::Selecting) { _widget->checkSelectingScroll(mousePos); } else { updateDragSelection(0, 0, false); _widget->noSelectingScroll(); } if (_mouseAction == MouseAction::None && (lnkChanged || cur != _cursor)) { setCursor(_cursor = cur); } } void HistoryInner::updateDragSelection(Element *dragSelFrom, Element *dragSelTo, bool dragSelecting) { if (_dragSelFrom == dragSelFrom && _dragSelTo == dragSelTo && _dragSelecting == dragSelecting) { return; } _dragSelFrom = dragSelFrom; _dragSelTo = dragSelTo; int32 fromy = itemTop(_dragSelFrom), toy = itemTop(_dragSelTo); if (fromy >= 0 && toy >= 0 && fromy > toy) { std::swap(_dragSelFrom, _dragSelTo); } _dragSelecting = dragSelecting; if (!_wasSelectedText && _dragSelFrom && _dragSelTo && _dragSelecting) { _wasSelectedText = true; setFocus(); } update(); } int HistoryInner::historyHeight() const { int result = 0; if (!_history || _history->isEmpty()) { result += _migrated ? _migrated->height() : 0; } else { result += _history->height() - _historySkipHeight + (_migrated ? _migrated->height() : 0); } return result; } int HistoryInner::historyScrollTop() const { auto htop = historyTop(); auto mtop = migratedTop(); if (htop >= 0 && _history->scrollTopItem) { return htop + _history->scrollTopItem->block()->y() + _history->scrollTopItem->y() + _history->scrollTopOffset; } if (mtop >= 0 && _migrated->scrollTopItem) { return mtop + _migrated->scrollTopItem->block()->y() + _migrated->scrollTopItem->y() + _migrated->scrollTopOffset; } return ScrollMax; } int HistoryInner::migratedTop() const { return (_migrated && !_migrated->isEmpty()) ? _historyPaddingTop : -1; } int HistoryInner::historyTop() const { int mig = migratedTop(); return (_history && !_history->isEmpty()) ? (mig >= 0 ? (mig + _migrated->height() - _historySkipHeight) : _historyPaddingTop) : -1; } int HistoryInner::historyDrawTop() const { auto top = historyTop(); return (top >= 0) ? (top + _historySkipHeight) : -1; } // -1 if should not be visible, -2 if bad history() int HistoryInner::itemTop(const HistoryItem *item) const { if (!item) { return -2; } return itemTop(item->mainView()); } int HistoryInner::itemTop(const Element *view) const { if (!view || view->data()->mainView() != view) { return -1; } auto top = (view->data()->history() == _history) ? historyTop() : (view->data()->history() == _migrated ? migratedTop() : -2); return (top < 0) ? top : (top + view->y() + view->block()->y()); } void HistoryInner::notifyIsBotChanged() { const auto newinfo = (_history && _history->peer->isUser()) ? _history->peer->asUser()->botInfo.get() : nullptr; if ((!newinfo && !_botAbout) || (newinfo && _botAbout && _botAbout->info == newinfo)) { return; } if (newinfo) { _botAbout = std::make_unique<BotAbout>(this, newinfo); if (newinfo && !newinfo->inited) { Auth().api().requestFullPeer(_peer); } } else { _botAbout = nullptr; } } void HistoryInner::notifyMigrateUpdated() { _migrated = _history->migrateFrom(); } int HistoryInner::moveScrollFollowingInlineKeyboard( const HistoryItem *item, int oldKeyboardTop, int newKeyboardTop) { if (const auto view = item ? item->mainView() : nullptr) { if (view->isUnderCursor()) { const auto top = itemTop(item); if (top >= oldKeyboardTop) { return newKeyboardTop - oldKeyboardTop; } } } return 0; } void HistoryInner::applyDragSelection() { applyDragSelection(&_selected); } bool HistoryInner::isSelected( not_null<SelectedItems*> toItems, not_null<HistoryItem*> item) const { const auto i = toItems->find(item); return (i != toItems->cend()) && (i->second == FullSelection); } bool HistoryInner::isSelectedGroup( not_null<SelectedItems*> toItems, not_null<const Data::Group*> group) const { for (const auto other : group->items) { if (!isSelected(toItems, other)) { return false; } } return true; } bool HistoryInner::isSelectedAsGroup( not_null<SelectedItems*> toItems, not_null<HistoryItem*> item) const { if (const auto group = Auth().data().groups().find(item)) { return isSelectedGroup(toItems, group); } return isSelected(toItems, item); } bool HistoryInner::goodForSelection( not_null<SelectedItems*> toItems, not_null<HistoryItem*> item, int &totalCount) const { if (item->id <= 0 || item->serviceMsg()) { return false; } if (toItems->find(item) == toItems->end()) { ++totalCount; } return true; } void HistoryInner::addToSelection( not_null<SelectedItems*> toItems, not_null<HistoryItem*> item) const { const auto i = toItems->find(item); if (i == toItems->cend()) { toItems->emplace(item, FullSelection); } else if (i->second != FullSelection) { i->second = FullSelection; } } void HistoryInner::removeFromSelection( not_null<SelectedItems*> toItems, not_null<HistoryItem*> item) const { const auto i = toItems->find(item); if (i != toItems->cend()) { toItems->erase(i); } } void HistoryInner::changeSelection( not_null<SelectedItems*> toItems, not_null<HistoryItem*> item, SelectAction action) const { if (action == SelectAction::Invert) { action = isSelected(toItems, item) ? SelectAction::Deselect : SelectAction::Select; } auto total = int(toItems->size()); const auto add = (action == SelectAction::Select); if (add && goodForSelection(toItems, item, total) && total <= MaxSelectedItems) { addToSelection(toItems, item); } else { removeFromSelection(toItems, item); } } void HistoryInner::changeSelectionAsGroup( not_null<SelectedItems*> toItems, not_null<HistoryItem*> item, SelectAction action) const { const auto group = Auth().data().groups().find(item); if (!group) { return changeSelection(toItems, item, action); } if (action == SelectAction::Invert) { action = isSelectedAsGroup(toItems, item) ? SelectAction::Deselect : SelectAction::Select; } auto total = int(toItems->size()); const auto canSelect = [&] { for (const auto other : group->items) { if (!goodForSelection(toItems, other, total)) { return false; } } return (total <= MaxSelectedItems); }(); if (action == SelectAction::Select && canSelect) { for (const auto other : group->items) { addToSelection(toItems, other); } } else { for (const auto other : group->items) { removeFromSelection(toItems, other); } } } void HistoryInner::forwardItem(FullMsgId itemId) { Window::ShowForwardMessagesBox({ 1, itemId }); } void HistoryInner::forwardAsGroup(FullMsgId itemId) { if (const auto item = App::histItemById(itemId)) { Window::ShowForwardMessagesBox(Auth().data().itemOrItsGroup(item)); } } void HistoryInner::deleteItem(FullMsgId itemId) { if (const auto item = App::histItemById(itemId)) { deleteItem(item); } } void HistoryInner::deleteItem(not_null<HistoryItem*> item) { if (auto message = item->toHistoryMessage()) { if (message->uploading()) { App::main()->cancelUploadLayer(item); return; } } const auto suggestModerateActions = true; Ui::show(Box<DeleteMessagesBox>(item, suggestModerateActions)); } bool HistoryInner::hasPendingResizedItems() const { return (_history && _history->hasPendingResizedItems()) || (_migrated && _migrated->hasPendingResizedItems()); } void HistoryInner::deleteAsGroup(FullMsgId itemId) { if (const auto item = App::histItemById(itemId)) { const auto group = Auth().data().groups().find(item); if (!group) { return deleteItem(item); } Ui::show(Box<DeleteMessagesBox>( Auth().data().itemsToIds(group->items))); } } void HistoryInner::addSelectionRange( not_null<SelectedItems*> toItems, not_null<History*> history, int fromblock, int fromitem, int toblock, int toitem) const { if (fromblock >= 0 && fromitem >= 0 && toblock >= 0 && toitem >= 0) { for (; fromblock <= toblock; ++fromblock) { auto block = history->blocks[fromblock].get(); for (int cnt = (fromblock < toblock) ? block->messages.size() : (toitem + 1); fromitem < cnt; ++fromitem) { auto item = block->messages[fromitem]->data(); changeSelectionAsGroup(toItems, item, SelectAction::Select); } if (toItems->size() >= MaxSelectedItems) break; fromitem = 0; } } } void HistoryInner::applyDragSelection( not_null<SelectedItems*> toItems) const { const auto selfromy = itemTop(_dragSelFrom); const auto seltoy = [&] { auto result = itemTop(_dragSelTo); return (result < 0) ? result : (result + _dragSelTo->height()); }(); if (selfromy < 0 || seltoy < 0) { return; } if (!toItems->empty() && toItems->cbegin()->second != FullSelection) { toItems->clear(); } if (_dragSelecting) { auto fromblock = _dragSelFrom->block()->indexInHistory(); auto fromitem = _dragSelFrom->indexInBlock(); auto toblock = _dragSelTo->block()->indexInHistory(); auto toitem = _dragSelTo->indexInBlock(); if (_migrated) { if (_dragSelFrom->data()->history() == _migrated) { if (_dragSelTo->data()->history() == _migrated) { addSelectionRange(toItems, _migrated, fromblock, fromitem, toblock, toitem); toblock = -1; toitem = -1; } else { addSelectionRange(toItems, _migrated, fromblock, fromitem, _migrated->blocks.size() - 1, _migrated->blocks.back()->messages.size() - 1); } fromblock = 0; fromitem = 0; } else if (_dragSelTo->data()->history() == _migrated) { // wtf toblock = -1; toitem = -1; } } addSelectionRange(toItems, _history, fromblock, fromitem, toblock, toitem); } else { auto toRemove = std::vector<not_null<HistoryItem*>>(); for (auto i = toItems->begin(); i != toItems->cend(); ++i) { auto iy = itemTop(i->first); if (iy < -1) { toRemove.push_back(i->first); } else if (iy >= 0 && iy >= selfromy && iy < seltoy) { toRemove.push_back(i->first); } } for (const auto item : toRemove) { changeSelectionAsGroup(toItems, item, SelectAction::Deselect); } } } QString HistoryInner::tooltipText() const { if (_mouseCursorState == CursorState::Date && _mouseAction == MouseAction::None) { if (const auto view = App::hoveredItem()) { auto dateText = view->dateTime().toString( QLocale::system().dateTimeFormat(QLocale::LongFormat)); if (const auto editedDate = view->displayedEditDate()) { dateText += '\n' + lng_edited_date( lt_date, ParseDateTime(editedDate).toString( QLocale::system().dateTimeFormat( QLocale::LongFormat))); } if (const auto forwarded = view->data()->Get<HistoryMessageForwarded>()) { dateText += '\n' + lng_forwarded_date( lt_date, ParseDateTime(forwarded->originalDate).toString( QLocale::system().dateTimeFormat( QLocale::LongFormat))); } return dateText; } } else if (_mouseCursorState == CursorState::Forwarded && _mouseAction == MouseAction::None) { if (const auto view = App::hoveredItem()) { if (const auto forwarded = view->data()->Get<HistoryMessageForwarded>()) { return forwarded->text.originalText(AllTextSelection, ExpandLinksNone); } } } else if (auto lnk = ClickHandler::getActive()) { return lnk->tooltip(); } return QString(); } QPoint HistoryInner::tooltipPos() const { return _mousePosition; } void HistoryInner::onParentGeometryChanged() { auto mousePos = QCursor::pos(); auto mouseOver = _widget->rect().contains(_widget->mapFromGlobal(mousePos)); auto needToUpdate = (_mouseAction != MouseAction::None || _touchScroll || mouseOver); if (needToUpdate) { mouseActionUpdate(mousePos); } } not_null<HistoryView::ElementDelegate*> HistoryInner::ElementDelegate() { class Result : public HistoryView::ElementDelegate { public: HistoryView::Context elementContext() override { return HistoryView::Context::History; } std::unique_ptr<HistoryView::Element> elementCreate( not_null<HistoryMessage*> message) override { return std::make_unique<HistoryView::Message>(this, message); } std::unique_ptr<HistoryView::Element> elementCreate( not_null<HistoryService*> message) override { return std::make_unique<HistoryView::Service>(this, message); } bool elementUnderCursor( not_null<const HistoryView::Element*> view) override { return (App::hoveredItem() == view); } void elementAnimationAutoplayAsync( not_null<const HistoryView::Element*> view) override { crl::on_main(&Auth(), [msgId = view->data()->fullId()] { if (const auto item = App::histItemById(msgId)) { if (const auto view = item->mainView()) { if (const auto media = view->media()) { media->autoplayAnimation(); } } } }); } TimeMs elementHighlightTime( not_null<const HistoryView::Element*> view) override { const auto fullAnimMs = App::main()->highlightStartTime( view->data()); if (fullAnimMs > 0) { const auto now = getms(); if (fullAnimMs < now) { return now - fullAnimMs; } } return TimeMs(0); } bool elementInSelectionMode() override { return App::main()->historyInSelectionMode(); } }; static Result result; return &result; } ����������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/history_inner_widget.h���������������������������������0000664�0000000�0000000�00000026457�13262451251�0025552�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "base/timer.h" #include "ui/rp_widget.h" #include "ui/widgets/tooltip.h" #include "ui/widgets/scroll_area.h" #include "history/view/history_view_top_bar_widget.h" namespace Data { struct Group; } // namespace Data namespace HistoryView { class ElementDelegate; struct TextState; struct StateRequest; enum class CursorState : char; enum class PointState : char; } // namespace HistoryView namespace Window { class Controller; } // namespace Window namespace Ui { class PopupMenu; } // namespace Ui class HistoryWidget; class HistoryInner : public Ui::RpWidget , public Ui::AbstractTooltipShower , private base::Subscriber { Q_OBJECT public: using Element = HistoryView::Element; HistoryInner( not_null<HistoryWidget*> historyWidget, not_null<Window::Controller*> controller, Ui::ScrollArea *scroll, not_null<History*> history); void messagesReceived(PeerData *peer, const QVector<MTPMessage> &messages); void messagesReceivedDown(PeerData *peer, const QVector<MTPMessage> &messages); TextWithEntities getSelectedText() const; void touchScrollUpdated(const QPoint &screenPos); void recountHistoryGeometry(); void updateSize(); void repaintItem(const HistoryItem *item); void repaintItem(const Element *view); bool canCopySelected() const; bool canDeleteSelected() const; HistoryView::TopBarWidget::SelectedState getSelectionState() const; void clearSelected(bool onlyTextSelection = false); MessageIdsList getSelectedItems() const; void selectItem(not_null<HistoryItem*> item); bool inSelectionMode() const; void updateBotInfo(bool recount = true); bool wasSelectedText() const; void setFirstLoading(bool loading); // updates history->scrollTopItem/scrollTopOffset void visibleAreaUpdated(int top, int bottom); int historyHeight() const; int historyScrollTop() const; int migratedTop() const; int historyTop() const; int historyDrawTop() const; // -1 if should not be visible, -2 if bad history() int itemTop(const HistoryItem *item) const; int itemTop(const Element *view) const; void notifyIsBotChanged(); void notifyMigrateUpdated(); // When inline keyboard has moved because of the edition of its item we want // to move scroll position so that mouse points to the same button row. int moveScrollFollowingInlineKeyboard(const HistoryItem *item, int oldKeyboardTop, int newKeyboardTop); // Ui::AbstractTooltipShower interface. QString tooltipText() const override; QPoint tooltipPos() const override; // HistoryView::ElementDelegate interface. static not_null<HistoryView::ElementDelegate*> ElementDelegate(); ~HistoryInner(); protected: bool focusNextPrevChild(bool next) override; bool eventHook(QEvent *e) override; // calls touchEvent when necessary void touchEvent(QTouchEvent *e); void paintEvent(QPaintEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void mouseDoubleClickEvent(QMouseEvent *e) override; void enterEventHook(QEvent *e) override; void leaveEventHook(QEvent *e) override; void resizeEvent(QResizeEvent *e) override; void keyPressEvent(QKeyEvent *e) override; void contextMenuEvent(QContextMenuEvent *e) override; public slots: void onParentGeometryChanged(); void onTouchSelect(); void onTouchScrollTimer(); private: class BotAbout; using SelectedItems = std::map<HistoryItem*, TextSelection, std::less<>>; enum class MouseAction { None, PrepareDrag, Dragging, PrepareSelect, Selecting, }; enum class SelectAction { Select, Deselect, Invert, }; enum class EnumItemsDirection { TopToBottom, BottomToTop, }; using CursorState = HistoryView::CursorState; using PointState = HistoryView::PointState; using TextState = HistoryView::TextState; using StateRequest = HistoryView::StateRequest; // This function finds all history items that are displayed and calls template method // for each found message (in given direction) in the passed history with passed top offset. // // Method has "bool (*Method)(not_null<Element*> view, int itemtop, int itembottom)" signature // if it returns false the enumeration stops immidiately. template <bool TopToBottom, typename Method> void enumerateItemsInHistory(History *history, int historytop, Method method); template <EnumItemsDirection direction, typename Method> void enumerateItems(Method method) { constexpr auto TopToBottom = (direction == EnumItemsDirection::TopToBottom); if (TopToBottom && _migrated) { enumerateItemsInHistory<TopToBottom>(_migrated, migratedTop(), method); } enumerateItemsInHistory<TopToBottom>(_history, historyTop(), method); if (!TopToBottom && _migrated) { enumerateItemsInHistory<TopToBottom>(_migrated, migratedTop(), method); } } // This function finds all userpics on the left that are displayed and calls template method // for each found userpic (from the top to the bottom) using enumerateItems() method. // // Method has "bool (*Method)(not_null<Element*> view, int userpicTop)" signature // if it returns false the enumeration stops immidiately. template <typename Method> void enumerateUserpics(Method method); // This function finds all date elements that are displayed and calls template method // for each found date element (from the bottom to the top) using enumerateItems() method. // // Method has "bool (*Method)(not_null<Element*> view, int itemtop, int dateTop)" signature // if it returns false the enumeration stops immidiately. template <typename Method> void enumerateDates(Method method); void scrollDateCheck(); void scrollDateHideByTimer(); bool canHaveFromUserpics() const; void mouseActionStart(const QPoint &screenPos, Qt::MouseButton button); void mouseActionUpdate(); void mouseActionUpdate(const QPoint &screenPos); void mouseActionFinish(const QPoint &screenPos, Qt::MouseButton button); void mouseActionCancel(); std::unique_ptr<QMimeData> prepareDrag(); void performDrag(); QPoint mapPointToItem(QPoint p, const Element *view); QPoint mapPointToItem(QPoint p, const HistoryItem *item); void showContextMenu(QContextMenuEvent *e, bool showFromTouch = false); void cancelContextDownload(not_null<DocumentData*> document); void openContextGif(FullMsgId itemId); void saveContextGif(FullMsgId itemId); void copyContextText(FullMsgId itemId); void showContextInFolder(not_null<DocumentData*> document); void savePhotoToFile(not_null<PhotoData*> photo); void saveDocumentToFile(not_null<DocumentData*> document); void copyContextImage(not_null<PhotoData*> photo); void showStickerPackInfo(not_null<DocumentData*> document); void toggleFavedSticker(not_null<DocumentData*> document); void itemRemoved(not_null<const HistoryItem*> item); void viewRemoved(not_null<const Element*> view); void refreshView(not_null<HistoryItem*> item); void touchResetSpeed(); void touchUpdateSpeed(); void touchDeaccelerate(int32 elapsed); void adjustCurrent(int32 y) const; void adjustCurrent(int32 y, History *history) const; Element *prevItem(Element *item); Element *nextItem(Element *item); void updateDragSelection(Element *dragSelFrom, Element *dragSelTo, bool dragSelecting); TextSelection itemRenderSelection( not_null<Element*> view, int selfromy, int seltoy) const; TextSelection computeRenderSelection( not_null<const SelectedItems*> selected, not_null<Element*> view) const; void toggleScrollDateShown(); void repaintScrollDateCallback(); bool displayScrollDate() const; void scrollDateHide(); void keepScrollDateForNow(); void applyDragSelection(); void applyDragSelection(not_null<SelectedItems*> toItems) const; void addSelectionRange( not_null<SelectedItems*> toItems, not_null<History*> history, int fromblock, int fromitem, int toblock, int toitem) const; bool isSelected( not_null<SelectedItems*> toItems, not_null<HistoryItem*> item) const; bool isSelectedGroup( not_null<SelectedItems*> toItems, not_null<const Data::Group*> group) const; bool isSelectedAsGroup( not_null<SelectedItems*> toItems, not_null<HistoryItem*> item) const; bool goodForSelection( not_null<SelectedItems*> toItems, not_null<HistoryItem*> item, int &totalCount) const; void addToSelection( not_null<SelectedItems*> toItems, not_null<HistoryItem*> item) const; void removeFromSelection( not_null<SelectedItems*> toItems, not_null<HistoryItem*> item) const; void changeSelection( not_null<SelectedItems*> toItems, not_null<HistoryItem*> item, SelectAction action) const; void changeSelectionAsGroup( not_null<SelectedItems*> toItems, not_null<HistoryItem*> item, SelectAction action) const; void forwardItem(FullMsgId itemId); void forwardAsGroup(FullMsgId itemId); void deleteItem(not_null<HistoryItem*> item); void deleteItem(FullMsgId itemId); void deleteAsGroup(FullMsgId itemId); void copySelectedText(); // Does any of the shown histories has this flag set. bool hasPendingResizedItems() const; not_null<Window::Controller*> _controller; not_null<PeerData*> _peer; not_null<History*> _history; History *_migrated = nullptr; int _contentWidth = 0; int _historyPaddingTop = 0; // with migrated history we perhaps do not need to display first _history message // (if last _migrated message and first _history message are both isGroupMigrate) // or at least we don't need to display first _history date (just skip it by height) int _historySkipHeight = 0; std::unique_ptr<BotAbout> _botAbout; HistoryWidget *_widget = nullptr; Ui::ScrollArea *_scroll = nullptr; mutable History *_curHistory = nullptr; mutable int _curBlock = 0; mutable int _curItem = 0; bool _firstLoading = false; style::cursor _cursor = style::cur_default; SelectedItems _selected; MouseAction _mouseAction = MouseAction::None; TextSelectType _mouseSelectType = TextSelectType::Letters; QPoint _dragStartPosition; QPoint _mousePosition; HistoryItem *_mouseActionItem = nullptr; HistoryItem *_dragStateItem = nullptr; CursorState _mouseCursorState = CursorState(); uint16 _mouseTextSymbol = 0; bool _pressWasInactive = false; QPoint _trippleClickPoint; QTimer _trippleClickTimer; Element *_dragSelFrom = nullptr; Element *_dragSelTo = nullptr; bool _dragSelecting = false; bool _wasSelectedText = false; // was some text selected in current drag action // scroll by touch support (at least Windows Surface tablets) bool _touchScroll = false; bool _touchSelect = false; bool _touchInProgress = false; QPoint _touchStart, _touchPrevPos, _touchPos; QTimer _touchSelectTimer; Ui::TouchScrollState _touchScrollState = Ui::TouchScrollState::Manual; bool _touchPrevPosValid = false; bool _touchWaitingAcceleration = false; QPoint _touchSpeed; TimeMs _touchSpeedTime = 0; TimeMs _touchAccelerationTime = 0; TimeMs _touchTime = 0; QTimer _touchScrollTimer; base::unique_qptr<Ui::PopupMenu> _menu; // save visible area coords for painting / pressing userpics int _visibleAreaTop = 0; int _visibleAreaBottom = 0; bool _scrollDateShown = false; Animation _scrollDateOpacity; SingleQueuedInvokation _scrollDateCheck; base::Timer _scrollDateHideTimer; Element *_scrollDateLastItem = nullptr; int _scrollDateLastItemTop = 0; ClickHandlerPtr _scrollDateLink; }; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/history_item.cpp���������������������������������������0000664�0000000�0000000�00000050750�13262451251�0024356�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/history_item.h" #include "lang/lang_keys.h" #include "mainwidget.h" #include "layout.h" #include "history/view/history_view_element.h" #include "history/view/history_view_service_message.h" #include "history/history_item_components.h" #include "history/history_media_types.h" #include "history/history_media_grouped.h" #include "history/history_service.h" #include "history/history_message.h" #include "history/history.h" #include "media/media_clip_reader.h" #include "styles/style_dialogs.h" #include "styles/style_history.h" #include "ui/effects/ripple_animation.h" #include "ui/text_options.h" #include "storage/file_upload.h" #include "storage/storage_facade.h" #include "storage/storage_shared_media.h" #include "storage/storage_feed_messages.h" #include "auth_session.h" #include "apiwrap.h" #include "media/media_audio.h" #include "messenger.h" #include "mainwindow.h" #include "window/window_controller.h" #include "core/crash_reports.h" #include "data/data_session.h" #include "data/data_messages.h" #include "data/data_media_types.h" #include "data/data_feed.h" namespace { not_null<HistoryItem*> CreateUnsupportedMessage( not_null<History*> history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, UserId viaBotId, TimeId date, UserId from) { const auto siteLink = qsl("https://desktop.telegram.org"); auto text = TextWithEntities{ lng_message_unsupported(lt_link, siteLink) }; TextUtilities::ParseEntities(text, Ui::ItemTextNoMonoOptions().flags); text.entities.push_front( EntityInText(EntityInTextItalic, 0, text.text.size())); flags &= ~MTPDmessage::Flag::f_post_author; return new HistoryMessage( history, msgId, flags, replyTo, viaBotId, date, from, QString(), text); } } // namespace void HistoryItem::HistoryItem::Destroyer::operator()(HistoryItem *value) { if (value) { value->destroy(); } } HistoryItem::HistoryItem( not_null<History*> history, MsgId id, MTPDmessage::Flags flags, TimeId date, UserId from) : id(id) , _history(history) , _from(from ? App::user(from) : history->peer) , _flags(flags) , _date(date) { App::historyRegItem(this); } TimeId HistoryItem::date() const { return _date; } void HistoryItem::finishEdition(int oldKeyboardTop) { Auth().data().requestItemViewRefresh(this); invalidateChatsListEntry(); if (const auto group = Auth().data().groups().find(this)) { const auto leader = group->items.back(); if (leader != this) { Auth().data().requestItemViewRefresh(leader); leader->invalidateChatsListEntry(); } } //if (oldKeyboardTop >= 0) { // #TODO edit bot message // if (auto keyboard = Get<HistoryMessageReplyMarkup>()) { // keyboard->oldTop = oldKeyboardTop; // } //} App::historyUpdateDependent(this); } void HistoryItem::setGroupId(MessageGroupId groupId) { Expects(!_groupId); _groupId = groupId; Auth().data().groups().registerMessage(this); } HistoryMessageReplyMarkup *HistoryItem::inlineReplyMarkup() { if (const auto markup = Get<HistoryMessageReplyMarkup>()) { if (markup->flags & MTPDreplyKeyboardMarkup_ClientFlag::f_inline) { return markup; } } return nullptr; } ReplyKeyboard *HistoryItem::inlineReplyKeyboard() { if (const auto markup = inlineReplyMarkup()) { return markup->inlineKeyboard.get(); } return nullptr; } void HistoryItem::invalidateChatsListEntry() { if (const auto main = App::main()) { // #TODO feeds search results main->repaintDialogRow(history(), id); } // invalidate cache for drawInDialog if (history()->textCachedFor == this) { history()->textCachedFor = nullptr; } if (const auto feed = history()->peer->feed()) { if (feed->textCachedFor == this) { feed->textCachedFor = nullptr; feed->updateChatListEntry(); } } } void HistoryItem::finishEditionToEmpty() { finishEdition(-1); _history->itemVanished(this); } bool HistoryItem::isMediaUnread() const { if (!mentionsMe() && _history->peer->isChannel()) { auto passed = unixtime() - date(); if (passed >= Global::ChannelsReadMediaPeriod()) { return false; } } return _flags & MTPDmessage::Flag::f_media_unread; } void HistoryItem::markMediaRead() { _flags &= ~MTPDmessage::Flag::f_media_unread; if (mentionsMe()) { history()->updateChatListEntry(); history()->eraseFromUnreadMentions(id); } } bool HistoryItem::definesReplyKeyboard() const { if (const auto markup = Get<HistoryMessageReplyMarkup>()) { if (markup->flags & MTPDreplyKeyboardMarkup_ClientFlag::f_inline) { return false; } return true; } // optimization: don't create markup component for the case // MTPDreplyKeyboardHide with flags = 0, assume it has f_zero flag return (_flags & MTPDmessage::Flag::f_reply_markup); } MTPDreplyKeyboardMarkup::Flags HistoryItem::replyKeyboardFlags() const { Expects(definesReplyKeyboard()); if (const auto markup = Get<HistoryMessageReplyMarkup>()) { return markup->flags; } // optimization: don't create markup component for the case // MTPDreplyKeyboardHide with flags = 0, assume it has f_zero flag return MTPDreplyKeyboardMarkup_ClientFlag::f_zero | 0; } void HistoryItem::addLogEntryOriginal( WebPageId localId, const QString &label, const TextWithEntities &content) { Expects(isLogEntry()); AddComponents(HistoryMessageLogEntryOriginal::Bit()); Get<HistoryMessageLogEntryOriginal>()->page = Auth().data().webpage( localId, label, content); } UserData *HistoryItem::viaBot() const { if (const auto via = Get<HistoryMessageVia>()) { return via->bot; } return nullptr; } UserData *HistoryItem::getMessageBot() const { if (const auto bot = viaBot()) { return bot; } auto bot = from()->asUser(); if (!bot) { bot = history()->peer->asUser(); } return (bot && bot->botInfo) ? bot : nullptr; }; void HistoryItem::destroy() { const auto history = this->history(); if (isLogEntry()) { Assert(!mainView()); } else { // All this must be done for all items manually in History::clear()! eraseFromUnreadMentions(); if (IsServerMsgId(id)) { if (const auto types = sharedMediaTypes()) { Auth().storage().remove(Storage::SharedMediaRemoveOne( history->peer->id, types, id)); } } else { Auth().api().cancelLocalItem(this); } _history->itemRemoved(this); } delete this; } void HistoryItem::refreshMainView() { if (const auto view = mainView()) { Auth().data().notifyHistoryChangeDelayed(_history); view->refreshInBlock(); } } void HistoryItem::removeMainView() { if (const auto view = mainView()) { Auth().data().notifyHistoryChangeDelayed(_history); view->removeFromBlock(); } } void HistoryItem::clearMainView() { _mainView = nullptr; } void HistoryItem::addToUnreadMentions(UnreadMentionType type) { } void HistoryItem::indexAsNewItem() { if (IsServerMsgId(id)) { CrashReports::SetAnnotation("addToUnreadMentions", QString::number(id)); addToUnreadMentions(UnreadMentionType::New); CrashReports::ClearAnnotation("addToUnreadMentions"); if (const auto types = sharedMediaTypes()) { Auth().storage().add(Storage::SharedMediaAddNew( history()->peer->id, types, id)); } if (const auto channel = history()->peer->asChannel()) { if (const auto feed = channel->feed()) { Auth().storage().add(Storage::FeedMessagesAddNew( feed->id(), position())); } } } } void HistoryItem::setRealId(MsgId newId) { Expects(!IsServerMsgId(id)); App::historyUnregItem(this); const auto oldId = std::exchange(id, newId); App::historyRegItem(this); // We don't need to call Notify::replyMarkupUpdated(this) and update keyboard // in history widget, because it can't exist for an outgoing message. // Only inline keyboards can be in outgoing messages. if (const auto markup = inlineReplyMarkup()) { if (markup->inlineKeyboard) { markup->inlineKeyboard->updateMessageId(); } } Auth().data().notifyItemIdChange({ this, oldId }); Auth().data().requestItemRepaint(this); } bool HistoryItem::isPinned() const { if (auto channel = _history->peer->asChannel()) { return (channel->pinnedMessageId() == id); } return false; } bool HistoryItem::canPin() const { if (id < 0 || !toHistoryMessage()) { return false; } if (auto channel = _history->peer->asChannel()) { return channel->canPinMessages(); } return false; } bool HistoryItem::allowsForward() const { return false; } bool HistoryItem::allowsEdit(TimeId now) const { return false; } bool HistoryItem::canDelete() const { if (isLogEntry() || (!IsServerMsgId(id) && serviceMsg())) { return false; } auto channel = _history->peer->asChannel(); if (!channel) { return !(_flags & MTPDmessage_ClientFlag::f_is_group_migrate); } if (id == 1) { return false; } if (channel->canDeleteMessages()) { return true; } if (out() && toHistoryMessage()) { return isPost() ? channel->canPublish() : true; } return false; } bool HistoryItem::canDeleteForEveryone(TimeId now) const { const auto peer = history()->peer; const auto messageToMyself = peer->isSelf(); const auto messageTooOld = messageToMyself ? false : peer->isUser() ? (now - date() >= Global::RevokePrivateTimeLimit()) : (now - date() >= Global::RevokeTimeLimit()); if (id < 0 || messageToMyself || messageTooOld || isPost()) { return false; } if (peer->isChannel()) { return false; } else if (const auto user = peer->asUser()) { // Bots receive all messages and there is no sense in revoking them. // See https://github.com/telegramdesktop/tdesktop/issues/3818 if (user->botInfo) { return false; } } if (!toHistoryMessage()) { return false; } else if (const auto media = this->media()) { if (!media->allowsRevoke()) { return false; } } if (!out()) { if (const auto chat = peer->asChat()) { if (!chat->amCreator() && (!chat->amAdmin() || !chat->adminsEnabled())) { return false; } } else if (peer->isUser()) { return Global::RevokePrivateInbox(); } else { return false; } } return true; } bool HistoryItem::suggestBanReport() const { auto channel = history()->peer->asChannel(); auto fromUser = from()->asUser(); if (!channel || !fromUser || !channel->canRestrictUser(fromUser)) { return false; } return !isPost() && !out() && toHistoryMessage(); } bool HistoryItem::suggestDeleteAllReport() const { auto channel = history()->peer->asChannel(); if (!channel || !channel->canDeleteMessages()) { return false; } return !isPost() && !out() && from()->isUser() && toHistoryMessage(); } bool HistoryItem::hasDirectLink() const { if (!IsServerMsgId(id)) { return false; } if (auto channel = _history->peer->asChannel()) { return channel->isPublic(); } return false; } QString HistoryItem::directLink() const { if (hasDirectLink()) { auto channel = _history->peer->asChannel(); Assert(channel != nullptr); auto query = channel->username + '/' + QString::number(id); if (!channel->isMegagroup()) { if (const auto media = this->media()) { if (const auto document = media->document()) { if (document->isVideoMessage()) { return qsl("https://telesco.pe/") + query; } } } } return Messenger::Instance().createInternalLinkFull(query); } return QString(); } ChannelId HistoryItem::channelId() const { return _history->channelId(); } Data::MessagePosition HistoryItem::position() const { return Data::MessagePosition(date(), fullId()); } MsgId HistoryItem::replyToId() const { if (auto reply = Get<HistoryMessageReply>()) { return reply->replyToId(); } return 0; } not_null<PeerData*> HistoryItem::author() const { return isPost() ? history()->peer : from(); } TimeId HistoryItem::dateOriginal() const { if (const auto forwarded = Get<HistoryMessageForwarded>()) { return forwarded->originalDate; } return date(); } not_null<PeerData*> HistoryItem::senderOriginal() const { if (const auto forwarded = Get<HistoryMessageForwarded>()) { return forwarded->originalSender; } const auto peer = history()->peer; return (peer->isChannel() && !peer->isMegagroup()) ? peer : from(); } not_null<PeerData*> HistoryItem::fromOriginal() const { if (const auto forwarded = Get<HistoryMessageForwarded>()) { if (const auto user = forwarded->originalSender->asUser()) { return user; } } return from(); } QString HistoryItem::authorOriginal() const { if (const auto forwarded = Get<HistoryMessageForwarded>()) { return forwarded->originalAuthor; } else if (const auto msgsigned = Get<HistoryMessageSigned>()) { return msgsigned->author; } return QString(); } MsgId HistoryItem::idOriginal() const { if (const auto forwarded = Get<HistoryMessageForwarded>()) { return forwarded->originalId; } return id; } bool HistoryItem::needCheck() const { return out() || (id < 0 && history()->peer->isSelf()); } bool HistoryItem::unread() const { // Messages from myself are always read. if (history()->peer->isSelf()) return false; if (out()) { // Outgoing messages in converted chats are always read. if (history()->peer->migrateTo()) { return false; } if (IsServerMsgId(id)) { if (!history()->isServerSideUnread(this)) { return false; } if (const auto user = history()->peer->asUser()) { if (user->botInfo) { return false; } } else if (auto channel = history()->peer->asChannel()) { if (!channel->isMegagroup()) { return false; } } } return true; } if (IsServerMsgId(id)) { if (!history()->isServerSideUnread(this)) { return false; } return true; } return (_flags & MTPDmessage_ClientFlag::f_clientside_unread); } MessageGroupId HistoryItem::groupId() const { return _groupId; } bool HistoryItem::isEmpty() const { return _text.isEmpty() && !_media && !Has<HistoryMessageLogEntryOriginal>(); } QString HistoryItem::notificationText() const { auto getText = [this]() { if (_media) { return _media->notificationText(); } else if (!emptyText()) { return _text.originalText(); } return QString(); }; auto result = getText(); if (result.size() > 0xFF) { result = result.mid(0, 0xFF) + qsl("..."); } return result; } QString HistoryItem::inDialogsText(DrawInDialog way) const { auto getText = [this]() { if (_media) { return _media->chatsListText(); } else if (!emptyText()) { return TextUtilities::Clean(_text.originalText()); } return QString(); }; const auto plainText = getText(); const auto sender = [&]() -> PeerData* { if (isPost() || isEmpty() || (way == DrawInDialog::WithoutSender)) { return nullptr; } else if (!_history->peer->isUser() || out()) { return author(); } else if (_history->peer->isSelf() && !Has<HistoryMessageForwarded>()) { return senderOriginal(); } return nullptr; }(); if (sender) { auto fromText = sender->isSelf() ? lang(lng_from_you) : sender->shortName(); auto fromWrapped = textcmdLink(1, lng_dialogs_text_from_wrapped(lt_from, TextUtilities::Clean(fromText))); return lng_dialogs_text_with_from(lt_from_part, fromWrapped, lt_message, plainText); } return plainText; } void HistoryItem::drawInDialog( Painter &p, const QRect &r, bool active, bool selected, DrawInDialog way, const HistoryItem *&cacheFor, Text &cache) const { if (cacheFor != this) { cacheFor = this; cache.setText(st::dialogsTextStyle, inDialogsText(way), Ui::DialogTextOptions()); } if (r.width()) { p.setTextPalette(active ? st::dialogsTextPaletteActive : (selected ? st::dialogsTextPaletteOver : st::dialogsTextPalette)); p.setFont(st::dialogsTextFont); p.setPen(active ? st::dialogsTextFgActive : (selected ? st::dialogsTextFgOver : st::dialogsTextFg)); cache.drawElided(p, r.left(), r.top(), r.width(), r.height() / st::dialogsTextFont->height); p.restoreTextPalette(); } } HistoryItem::~HistoryItem() { Auth().data().notifyItemRemoved(this); App::historyUnregItem(this); if (id < 0 && !App::quitting()) { Auth().uploader().cancel(fullId()); } } QDateTime ItemDateTime(not_null<const HistoryItem*> item) { return ParseDateTime(item->date()); } ClickHandlerPtr goToMessageClickHandler( not_null<HistoryItem*> item, FullMsgId returnToId) { return goToMessageClickHandler( item->history()->peer, item->id, returnToId); } ClickHandlerPtr goToMessageClickHandler( not_null<PeerData*> peer, MsgId msgId, FullMsgId returnToId) { return std::make_shared<LambdaClickHandler>([=] { if (const auto main = App::main()) { if (const auto returnTo = App::histItemById(returnToId)) { if (returnTo->history()->peer == peer) { main->pushReplyReturn(returnTo); } } App::wnd()->controller()->showPeerHistory( peer, Window::SectionShow::Way::Forward, msgId); } }); } not_null<HistoryItem*> HistoryItem::Create( not_null<History*> history, const MTPMessage &message) { switch (message.type()) { case mtpc_messageEmpty: { const auto &data = message.c_messageEmpty(); const auto text = HistoryService::PreparedText { lang(lng_message_empty) }; return new HistoryService(history, data.vid.v, TimeId(0), text); } break; case mtpc_message: { const auto &data = message.c_message(); enum class MediaCheckResult { Good, Unsupported, Empty, HasTimeToLive, }; auto badMedia = MediaCheckResult::Good; const auto &media = data.vmedia; if (data.has_media()) switch (media.type()) { case mtpc_messageMediaEmpty: case mtpc_messageMediaContact: break; case mtpc_messageMediaGeo: switch (media.c_messageMediaGeo().vgeo.type()) { case mtpc_geoPoint: break; case mtpc_geoPointEmpty: badMedia = MediaCheckResult::Empty; break; default: badMedia = MediaCheckResult::Unsupported; break; } break; case mtpc_messageMediaVenue: switch (media.c_messageMediaVenue().vgeo.type()) { case mtpc_geoPoint: break; case mtpc_geoPointEmpty: badMedia = MediaCheckResult::Empty; break; default: badMedia = MediaCheckResult::Unsupported; break; } break; case mtpc_messageMediaGeoLive: switch (media.c_messageMediaGeoLive().vgeo.type()) { case mtpc_geoPoint: break; case mtpc_geoPointEmpty: badMedia = MediaCheckResult::Empty; break; default: badMedia = MediaCheckResult::Unsupported; break; } break; case mtpc_messageMediaPhoto: { auto &photo = media.c_messageMediaPhoto(); if (photo.has_ttl_seconds()) { badMedia = MediaCheckResult::HasTimeToLive; } else if (!photo.has_photo()) { badMedia = MediaCheckResult::Empty; } else { switch (photo.vphoto.type()) { case mtpc_photo: break; case mtpc_photoEmpty: badMedia = MediaCheckResult::Empty; break; default: badMedia = MediaCheckResult::Unsupported; break; } } } break; case mtpc_messageMediaDocument: { auto &document = media.c_messageMediaDocument(); if (document.has_ttl_seconds()) { badMedia = MediaCheckResult::HasTimeToLive; } else if (!document.has_document()) { badMedia = MediaCheckResult::Empty; } else { switch (document.vdocument.type()) { case mtpc_document: break; case mtpc_documentEmpty: badMedia = MediaCheckResult::Empty; break; default: badMedia = MediaCheckResult::Unsupported; break; } } } break; case mtpc_messageMediaWebPage: switch (media.c_messageMediaWebPage().vwebpage.type()) { case mtpc_webPage: case mtpc_webPageEmpty: case mtpc_webPagePending: break; case mtpc_webPageNotModified: default: badMedia = MediaCheckResult::Unsupported; break; } break; case mtpc_messageMediaGame: switch (media.c_messageMediaGame().vgame.type()) { case mtpc_game: break; default: badMedia = MediaCheckResult::Unsupported; break; } break; case mtpc_messageMediaInvoice: break; case mtpc_messageMediaUnsupported: default: badMedia = MediaCheckResult::Unsupported; break; } if (badMedia == MediaCheckResult::Unsupported) { return CreateUnsupportedMessage( history, data.vid.v, data.vflags.v, data.vreply_to_msg_id.v, data.vvia_bot_id.v, data.vdate.v, data.vfrom_id.v); } else if (badMedia == MediaCheckResult::Empty) { const auto text = HistoryService::PreparedText { lang(lng_message_empty) }; return new HistoryService( history, data.vid.v, data.vdate.v, text, data.vflags.v, data.has_from_id() ? data.vfrom_id.v : UserId(0)); } else if (badMedia == MediaCheckResult::HasTimeToLive) { return new HistoryService(history, data); } return new HistoryMessage(history, data); } break; case mtpc_messageService: { auto &data = message.c_messageService(); if (data.vaction.type() == mtpc_messageActionPhoneCall) { return new HistoryMessage(history, data); } return new HistoryService(history, data); } break; } Unexpected("Type in HistoryItem::Create()."); } ������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/history_item.h�����������������������������������������0000664�0000000�0000000�00000016560�13262451251�0024024�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "base/runtime_composer.h" #include "base/flags.h" #include "base/value_ordering.h" enum class UnreadMentionType; struct HistoryMessageReplyMarkup; class ReplyKeyboard; class HistoryMessage; class HistoryMedia; namespace base { template <typename Enum> class enum_mask; } // namespace base namespace Storage { enum class SharedMediaType : signed char; using SharedMediaTypesMask = base::enum_mask<SharedMediaType>; } // namespace Storage namespace Ui { class RippleAnimation; } // namespace Ui namespace style { struct BotKeyboardButton; struct RippleAnimation; } // namespace style namespace Data { struct MessagePosition; class Media; } // namespace Data namespace Window { class Controller; } // namespace Window namespace HistoryView { struct TextState; struct StateRequest; enum class CursorState : char; enum class PointState : char; enum class Context : char; class ElementDelegate; } // namespace HistoryView class HistoryItem : public RuntimeComposer<HistoryItem> { public: static not_null<HistoryItem*> Create( not_null<History*> history, const MTPMessage &message); struct Destroyer { void operator()(HistoryItem *value); }; virtual void dependencyItemRemoved(HistoryItem *dependency) { } virtual bool updateDependencyItem() { return true; } virtual MsgId dependencyMsgId() const { return 0; } virtual bool notificationReady() const { return true; } virtual void applyGroupAdminChanges( const base::flat_map<UserId, bool> &changes) { } UserData *viaBot() const; UserData *getMessageBot() const; bool isLogEntry() const { return (id > ServerMaxMsgId); } void addLogEntryOriginal( WebPageId localId, const QString &label, const TextWithEntities &content); not_null<History*> history() const { return _history; } not_null<PeerData*> from() const { return _from; } HistoryView::Element *mainView() const { return _mainView; } void setMainView(not_null<HistoryView::Element*> view) { _mainView = view; } void refreshMainView(); void clearMainView(); void removeMainView(); void destroy(); bool out() const { return _flags & MTPDmessage::Flag::f_out; } bool unread() const; bool mentionsMe() const { return _flags & MTPDmessage::Flag::f_mentioned; } bool isMediaUnread() const; void markMediaRead(); // Zero result means this message is not self-destructing right now. virtual TimeMs getSelfDestructIn(TimeMs now) { return 0; } bool definesReplyKeyboard() const; MTPDreplyKeyboardMarkup::Flags replyKeyboardFlags() const; bool hasSwitchInlineButton() const { return _flags & MTPDmessage_ClientFlag::f_has_switch_inline_button; } bool hasTextLinks() const { return _flags & MTPDmessage_ClientFlag::f_has_text_links; } bool isGroupMigrate() const { return _flags & MTPDmessage_ClientFlag::f_is_group_migrate; } bool hasViews() const { return _flags & MTPDmessage::Flag::f_views; } bool isPost() const { return _flags & MTPDmessage::Flag::f_post; } bool isSilent() const { return _flags & MTPDmessage::Flag::f_silent; } virtual int viewsCount() const { return hasViews() ? 1 : -1; } virtual bool needCheck() const; virtual bool serviceMsg() const { return false; } virtual void applyEdition(const MTPDmessage &message) { } virtual void applyEdition(const MTPDmessageService &message) { } virtual void updateSentMedia(const MTPMessageMedia *media) { } virtual void updateReplyMarkup(const MTPReplyMarkup *markup) { } virtual void addToUnreadMentions(UnreadMentionType type); virtual void eraseFromUnreadMentions() { } virtual Storage::SharedMediaTypesMask sharedMediaTypes() const = 0; void indexAsNewItem(); virtual QString notificationHeader() const { return QString(); } virtual QString notificationText() const; enum class DrawInDialog { Normal, WithoutSender, }; // Returns text with link-start and link-end commands for service-color highlighting. // Example: "[link1-start]You:[link1-end] [link1-start]Photo,[link1-end] caption text" virtual QString inDialogsText(DrawInDialog way) const; virtual QString inReplyText() const { return notificationText(); } virtual TextWithEntities originalText() const { return { QString(), EntitiesInText() }; } virtual TextWithEntities clipboardText() const { return { QString(), EntitiesInText() }; } virtual void setViewsCount(int32 count) { } virtual void setRealId(MsgId newId); void drawInDialog( Painter &p, const QRect &r, bool active, bool selected, DrawInDialog way, const HistoryItem *&cacheFor, Text &cache) const; bool emptyText() const { return _text.isEmpty(); } bool isPinned() const; bool canPin() const; virtual bool allowsForward() const; virtual bool allowsEdit(TimeId now) const; bool canDelete() const; bool canDeleteForEveryone(TimeId now) const; bool suggestBanReport() const; bool suggestDeleteAllReport() const; bool hasDirectLink() const; QString directLink() const; MsgId id; ChannelId channelId() const; FullMsgId fullId() const { return FullMsgId(channelId(), id); } Data::MessagePosition position() const; TimeId date() const; Data::Media *media() const { return _media.get(); } virtual void setText(const TextWithEntities &textWithEntities) { } virtual bool textHasLinks() const { return false; } virtual HistoryMessage *toHistoryMessage() { // dynamic_cast optimize return nullptr; } virtual const HistoryMessage *toHistoryMessage() const { // dynamic_cast optimize return nullptr; } MsgId replyToId() const; not_null<PeerData*> author() const; TimeId dateOriginal() const; not_null<PeerData*> senderOriginal() const; not_null<PeerData*> fromOriginal() const; QString authorOriginal() const; MsgId idOriginal() const; bool isEmpty() const; MessageGroupId groupId() const; virtual std::unique_ptr<HistoryView::Element> createView( not_null<HistoryView::ElementDelegate*> delegate) = 0; virtual ~HistoryItem(); protected: HistoryItem( not_null<History*> history, MsgId id, MTPDmessage::Flags flags, TimeId date, UserId from); virtual void markMediaAsReadHook() { } void finishEdition(int oldKeyboardTop); void finishEditionToEmpty(); const not_null<History*> _history; not_null<PeerData*> _from; MTPDmessage::Flags _flags = 0; const HistoryMessageReplyMarkup *inlineReplyMarkup() const { return const_cast<HistoryItem*>(this)->inlineReplyMarkup(); } const ReplyKeyboard *inlineReplyKeyboard() const { return const_cast<HistoryItem*>(this)->inlineReplyKeyboard(); } HistoryMessageReplyMarkup *inlineReplyMarkup(); ReplyKeyboard *inlineReplyKeyboard(); void invalidateChatsListEntry(); void setGroupId(MessageGroupId groupId); Text _text = { int(st::msgMinWidth) }; int _textWidth = -1; int _textHeight = 0; std::unique_ptr<Data::Media> _media; private: TimeId _date = 0; HistoryView::Element *_mainView = nullptr; friend class HistoryView::Element; MessageGroupId _groupId = MessageGroupId::None; }; QDateTime ItemDateTime(not_null<const HistoryItem*> item); ClickHandlerPtr goToMessageClickHandler( not_null<PeerData*> peer, MsgId msgId, FullMsgId returnToId = FullMsgId()); ClickHandlerPtr goToMessageClickHandler( not_null<HistoryItem*> item, FullMsgId returnToId = FullMsgId()); ������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/history_item_components.cpp����������������������������0000664�0000000�0000000�00000063624�13262451251�0026627�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/history_item_components.h" #include "lang/lang_keys.h" #include "ui/effects/ripple_animation.h" #include "ui/text_options.h" #include "history/history_message.h" #include "history/history_media.h" #include "history/history_media_types.h" #include "history/view/history_view_service_message.h" #include "media/media_audio.h" #include "media/player/media_player_instance.h" #include "auth_session.h" #include "data/data_media_types.h" #include "data/data_session.h" #include "styles/style_widgets.h" #include "styles/style_history.h" void HistoryMessageVia::create(UserId userId) { bot = App::user(peerFromUser(userId)); maxWidth = st::msgServiceNameFont->width( lng_inline_bot_via(lt_inline_bot, '@' + bot->username)); link = std::make_shared<LambdaClickHandler>([bot = this->bot] { App::insertBotCommand('@' + bot->username); }); } void HistoryMessageVia::resize(int32 availw) const { if (availw < 0) { text = QString(); width = 0; } else { text = lng_inline_bot_via(lt_inline_bot, '@' + bot->username); if (availw < maxWidth) { text = st::msgServiceNameFont->elided(text, availw); width = st::msgServiceNameFont->width(text); } else if (width < maxWidth) { width = maxWidth; } } } void HistoryMessageSigned::refresh(const QString &date) { auto time = qsl(", ") + date; auto name = author; auto timew = st::msgDateFont->width(time); auto namew = st::msgDateFont->width(name); if (timew + namew > st::maxSignatureSize) { name = st::msgDateFont->elided(author, st::maxSignatureSize - timew); } signature.setText( st::msgDateTextStyle, name + time, Ui::NameTextOptions()); } int HistoryMessageSigned::maxWidth() const { return signature.maxWidth(); } void HistoryMessageEdited::refresh(const QString &date, bool displayed) { const auto prefix = displayed ? (lang(lng_edited) + ' ') : QString(); text.setText(st::msgDateTextStyle, prefix + date, Ui::NameTextOptions()); } int HistoryMessageEdited::maxWidth() const { return text.maxWidth(); } void HistoryMessageForwarded::create(const HistoryMessageVia *via) const { auto phrase = QString(); auto fromChannel = (originalSender->isChannel() && !originalSender->isMegagroup()); if (!originalAuthor.isEmpty()) { phrase = lng_forwarded_signed( lt_channel, App::peerName(originalSender), lt_user, originalAuthor); } else { phrase = App::peerName(originalSender); } if (via) { if (fromChannel) { phrase = lng_forwarded_channel_via( lt_channel, textcmdLink(1, phrase), lt_inline_bot, textcmdLink(2, '@' + via->bot->username)); } else { phrase = lng_forwarded_via( lt_user, textcmdLink(1, phrase), lt_inline_bot, textcmdLink(2, '@' + via->bot->username)); } } else { if (fromChannel) { phrase = lng_forwarded_channel( lt_channel, textcmdLink(1, phrase)); } else { phrase = lng_forwarded( lt_user, textcmdLink(1, phrase)); } } TextParseOptions opts = { TextParseRichText, 0, 0, Qt::LayoutDirectionAuto }; text.setText(st::fwdTextStyle, phrase, opts); text.setLink(1, fromChannel ? goToMessageClickHandler(originalSender, originalId) : originalSender->openLink()); if (via) { text.setLink(2, via->link); } } bool HistoryMessageReply::updateData( not_null<HistoryMessage*> holder, bool force) { if (!force) { if (replyToMsg || !replyToMsgId) { return true; } } if (!replyToMsg) { replyToMsg = App::histItemById(holder->channelId(), replyToMsgId); if (replyToMsg) { if (replyToMsg->isEmpty()) { // Really it is deleted. replyToMsg = nullptr; force = true; } else { App::historyRegDependency(holder, replyToMsg); } } } if (replyToMsg) { replyToText.setText( st::messageTextStyle, TextUtilities::Clean(replyToMsg->inReplyText()), Ui::DialogTextOptions()); updateName(); replyToLnk = goToMessageClickHandler(replyToMsg, holder->fullId()); if (!replyToMsg->Has<HistoryMessageForwarded>()) { if (auto bot = replyToMsg->viaBot()) { replyToVia = std::make_unique<HistoryMessageVia>(); replyToVia->create(peerToUser(bot->id)); } } } else if (force) { replyToMsgId = 0; } if (force) { Auth().data().requestItemResize(holder); } return (replyToMsg || !replyToMsgId); } void HistoryMessageReply::clearData(not_null<HistoryMessage*> holder) { replyToVia = nullptr; if (replyToMsg) { App::historyUnregDependency(holder, replyToMsg); replyToMsg = nullptr; } replyToMsgId = 0; } bool HistoryMessageReply::isNameUpdated() const { if (replyToMsg && replyToMsg->author()->nameVersion > replyToVersion) { updateName(); return true; } return false; } void HistoryMessageReply::updateName() const { if (replyToMsg) { QString name = (replyToVia && replyToMsg->author()->isUser()) ? replyToMsg->author()->asUser()->firstName : App::peerName(replyToMsg->author()); replyToName.setText(st::fwdTextStyle, name, Ui::NameTextOptions()); replyToVersion = replyToMsg->author()->nameVersion; bool hasPreview = replyToMsg->media() ? replyToMsg->media()->hasReplyPreview() : false; int32 previewSkip = hasPreview ? (st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x()) : 0; int32 w = replyToName.maxWidth(); if (replyToVia) { w += st::msgServiceFont->spacew + replyToVia->maxWidth; } maxReplyWidth = previewSkip + qMax(w, qMin(replyToText.maxWidth(), int32(st::maxSignatureSize))); } else { maxReplyWidth = st::msgDateFont->width(lang(replyToMsgId ? lng_profile_loading : lng_deleted_message)); } maxReplyWidth = st::msgReplyPadding.left() + st::msgReplyBarSkip + maxReplyWidth + st::msgReplyPadding.right(); } void HistoryMessageReply::resize(int width) const { if (replyToVia) { bool hasPreview = replyToMsg->media() ? replyToMsg->media()->hasReplyPreview() : false; int previewSkip = hasPreview ? (st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x()) : 0; replyToVia->resize(width - st::msgReplyBarSkip - previewSkip - replyToName.maxWidth() - st::msgServiceFont->spacew); } } void HistoryMessageReply::itemRemoved( HistoryMessage *holder, HistoryItem *removed) { if (replyToMsg == removed) { clearData(holder); Auth().data().requestItemResize(holder); } } void HistoryMessageReply::paint( Painter &p, not_null<const HistoryView::Element*> holder, int x, int y, int w, PaintFlags flags) const { bool selected = (flags & PaintFlag::Selected), outbg = holder->hasOutLayout(); style::color bar = st::msgImgReplyBarColor; if (flags & PaintFlag::InBubble) { bar = (flags & PaintFlag::Selected) ? (outbg ? st::msgOutReplyBarSelColor : st::msgInReplyBarSelColor) : (outbg ? st::msgOutReplyBarColor : st::msgInReplyBarColor); } QRect rbar(rtlrect(x + st::msgReplyBarPos.x(), y + st::msgReplyPadding.top() + st::msgReplyBarPos.y(), st::msgReplyBarSize.width(), st::msgReplyBarSize.height(), w + 2 * x)); p.fillRect(rbar, bar); if (w > st::msgReplyBarSkip) { if (replyToMsg) { auto hasPreview = replyToMsg->media() ? replyToMsg->media()->hasReplyPreview() : false; if (hasPreview && w < st::msgReplyBarSkip + st::msgReplyBarSize.height()) { hasPreview = false; } auto previewSkip = hasPreview ? (st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x()) : 0; if (hasPreview) { const auto replyPreview = replyToMsg->media()->replyPreview(); if (!replyPreview->isNull()) { auto to = rtlrect(x + st::msgReplyBarSkip, y + st::msgReplyPadding.top() + st::msgReplyBarPos.y(), st::msgReplyBarSize.height(), st::msgReplyBarSize.height(), w + 2 * x); auto previewWidth = replyPreview->width() / cIntRetinaFactor(); auto previewHeight = replyPreview->height() / cIntRetinaFactor(); auto preview = replyPreview->pixSingle(previewWidth, previewHeight, to.width(), to.height(), ImageRoundRadius::Small, RectPart::AllCorners, selected ? &st::msgStickerOverlay : nullptr); p.drawPixmap(to.x(), to.y(), preview); } } if (w > st::msgReplyBarSkip + previewSkip) { if (flags & PaintFlag::InBubble) { p.setPen(selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg)); } else { p.setPen(st::msgImgReplyBarColor); } replyToName.drawLeftElided(p, x + st::msgReplyBarSkip + previewSkip, y + st::msgReplyPadding.top(), w - st::msgReplyBarSkip - previewSkip, w + 2 * x); if (replyToVia && w > st::msgReplyBarSkip + previewSkip + replyToName.maxWidth() + st::msgServiceFont->spacew) { p.setFont(st::msgServiceFont); p.drawText(x + st::msgReplyBarSkip + previewSkip + replyToName.maxWidth() + st::msgServiceFont->spacew, y + st::msgReplyPadding.top() + st::msgServiceFont->ascent, replyToVia->text); } auto replyToAsMsg = replyToMsg->toHistoryMessage(); if (!(flags & PaintFlag::InBubble)) { } else if (!replyToAsMsg) { p.setPen(outbg ? (selected ? st::msgOutDateFgSelected : st::msgOutDateFg) : (selected ? st::msgInDateFgSelected : st::msgInDateFg)); } else { p.setPen(outbg ? (selected ? st::historyTextOutFgSelected : st::historyTextOutFg) : (selected ? st::historyTextInFgSelected : st::historyTextInFg)); } replyToText.drawLeftElided(p, x + st::msgReplyBarSkip + previewSkip, y + st::msgReplyPadding.top() + st::msgServiceNameFont->height, w - st::msgReplyBarSkip - previewSkip, w + 2 * x); } } else { p.setFont(st::msgDateFont); auto &date = outbg ? (selected ? st::msgOutDateFgSelected : st::msgOutDateFg) : (selected ? st::msgInDateFgSelected : st::msgInDateFg); p.setPen((flags & PaintFlag::InBubble) ? date : st::msgDateImgFg); p.drawTextLeft(x + st::msgReplyBarSkip, y + st::msgReplyPadding.top() + (st::msgReplyBarSize.height() - st::msgDateFont->height) / 2, w + 2 * x, st::msgDateFont->elided(lang(replyToMsgId ? lng_profile_loading : lng_deleted_message), w - st::msgReplyBarSkip)); } } } ReplyMarkupClickHandler::ReplyMarkupClickHandler( int row, int column, FullMsgId context) : _itemId(context) , _row(row) , _column(column) { } // Copy to clipboard support. QString ReplyMarkupClickHandler::copyToClipboardText() const { if (auto button = getButton()) { if (button->type == HistoryMessageMarkupButton::Type::Url) { return QString::fromUtf8(button->data); } } return QString(); } QString ReplyMarkupClickHandler::copyToClipboardContextItemText() const { if (auto button = getButton()) { if (button->type == HistoryMessageMarkupButton::Type::Url) { return lang(lng_context_copy_link); } } return QString(); } // Finds the corresponding button in the items markup struct. // If the button is not found it returns nullptr. // Note: it is possible that we will point to the different button // than the one was used when constructing the handler, but not a big deal. const HistoryMessageMarkupButton *ReplyMarkupClickHandler::getButton() const { if (auto item = App::histItemById(_itemId)) { if (auto markup = item->Get<HistoryMessageReplyMarkup>()) { if (_row < markup->rows.size()) { auto &row = markup->rows[_row]; if (_column < row.size()) { return &row[_column]; } } } } return nullptr; } void ReplyMarkupClickHandler::onClickImpl() const { if (const auto item = App::histItemById(_itemId)) { App::activateBotCommand(item, _row, _column); } } // Returns the full text of the corresponding button. QString ReplyMarkupClickHandler::buttonText() const { if (const auto button = getButton()) { return button->text; } return QString(); } ReplyKeyboard::Button::Button() = default; ReplyKeyboard::Button::Button(Button &&other) = default; ReplyKeyboard::Button &ReplyKeyboard::Button::operator=( Button &&other) = default; ReplyKeyboard::Button::~Button() = default; ReplyKeyboard::ReplyKeyboard( not_null<const HistoryItem*> item, std::unique_ptr<Style> &&s) : _item(item) , _a_selected(animation(this, &ReplyKeyboard::step_selected)) , _st(std::move(s)) { if (const auto markup = _item->Get<HistoryMessageReplyMarkup>()) { const auto context = _item->fullId(); const auto rowCount = int(markup->rows.size()); _rows.reserve(rowCount); for (auto i = 0; i != rowCount; ++i) { const auto &row = markup->rows.at(i); const auto rowSize = int(row.size()); auto newRow = std::vector<Button>(); newRow.reserve(rowSize); for (auto j = 0; j != rowSize; ++j) { auto button = Button(); const auto text = row[j].text; button.type = row.at(j).type; button.link = std::make_shared<ReplyMarkupClickHandler>( i, j, context); button.text.setText( _st->textStyle(), TextUtilities::SingleLine(text), _textPlainOptions); button.characters = text.isEmpty() ? 1 : text.size(); newRow.push_back(std::move(button)); } _rows.push_back(std::move(newRow)); } } } void ReplyKeyboard::updateMessageId() { const auto msgId = _item->fullId(); for (const auto &row : _rows) { for (const auto &button : row) { button.link->setMessageId(msgId); } } } void ReplyKeyboard::resize(int width, int height) { _width = width; auto markup = _item->Get<HistoryMessageReplyMarkup>(); auto y = 0.; auto buttonHeight = _rows.empty() ? float64(_st->buttonHeight()) : (float64(height + _st->buttonSkip()) / _rows.size()); for (auto &row : _rows) { int s = row.size(); int widthForButtons = _width - ((s - 1) * _st->buttonSkip()); int widthForText = widthForButtons; int widthOfText = 0; int maxMinButtonWidth = 0; for_const (auto &button, row) { widthOfText += qMax(button.text.maxWidth(), 1); int minButtonWidth = _st->minButtonWidth(button.type); widthForText -= minButtonWidth; accumulate_max(maxMinButtonWidth, minButtonWidth); } bool exact = (widthForText == widthOfText); bool enough = (widthForButtons - s * maxMinButtonWidth) >= widthOfText; float64 x = 0; for (Button &button : row) { int buttonw = qMax(button.text.maxWidth(), 1); float64 textw = buttonw, minw = _st->minButtonWidth(button.type); float64 w = textw; if (exact) { w += minw; } else if (enough) { w = (widthForButtons / float64(s)); textw = w - minw; } else { textw = (widthForText / float64(s)); w = minw + textw; accumulate_max(w, 2 * float64(_st->buttonPadding())); } int rectx = static_cast<int>(std::floor(x)); int rectw = static_cast<int>(std::floor(x + w)) - rectx; button.rect = QRect(rectx, qRound(y), rectw, qRound(buttonHeight - _st->buttonSkip())); if (rtl()) button.rect.setX(_width - button.rect.x() - button.rect.width()); x += w + _st->buttonSkip(); button.link->setFullDisplayed(textw >= buttonw); } y += buttonHeight; } } bool ReplyKeyboard::isEnoughSpace(int width, const style::BotKeyboardButton &st) const { for_const (auto &row, _rows) { int s = row.size(); int widthLeft = width - ((s - 1) * st.margin + s * 2 * st.padding); for_const (auto &button, row) { widthLeft -= qMax(button.text.maxWidth(), 1); if (widthLeft < 0) { if (row.size() > 3) { return false; } else { break; } } } } return true; } void ReplyKeyboard::setStyle(std::unique_ptr<Style> &&st) { _st = std::move(st); } int ReplyKeyboard::naturalWidth() const { auto result = 0; for (const auto &row : _rows) { auto maxMinButtonWidth = 0; for (const auto &button : row) { accumulate_max( maxMinButtonWidth, _st->minButtonWidth(button.type)); } auto rowMaxButtonWidth = 0; for (const auto &button : row) { accumulate_max( rowMaxButtonWidth, qMax(button.text.maxWidth(), 1) + maxMinButtonWidth); } const auto rowSize = int(row.size()); accumulate_max( result, rowSize * rowMaxButtonWidth + (rowSize - 1) * _st->buttonSkip()); } return result; } int ReplyKeyboard::naturalHeight() const { return (_rows.size() - 1) * _st->buttonSkip() + _rows.size() * _st->buttonHeight(); } void ReplyKeyboard::paint(Painter &p, int outerWidth, const QRect &clip, TimeMs ms) const { Assert(_st != nullptr); Assert(_width > 0); _st->startPaint(p); for_const (auto &row, _rows) { for_const (auto &button, row) { QRect rect(button.rect); if (rect.y() >= clip.y() + clip.height()) return; if (rect.y() + rect.height() < clip.y()) continue; // just ignore the buttons that didn't layout well if (rect.x() + rect.width() > _width) break; _st->paintButton(p, outerWidth, button, ms); } } } ClickHandlerPtr ReplyKeyboard::getLink(QPoint point) const { Assert(_width > 0); for_const (auto &row, _rows) { for_const (auto &button, row) { QRect rect(button.rect); // just ignore the buttons that didn't layout well if (rect.x() + rect.width() > _width) break; if (rect.contains(point)) { _savedCoords = point; return button.link; } } } return ClickHandlerPtr(); } void ReplyKeyboard::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { if (!p) return; _savedActive = active ? p : ClickHandlerPtr(); auto coords = findButtonCoordsByClickHandler(p); if (coords.i >= 0 && _savedPressed != p) { startAnimation(coords.i, coords.j, active ? 1 : -1); } } ReplyKeyboard::ButtonCoords ReplyKeyboard::findButtonCoordsByClickHandler(const ClickHandlerPtr &p) { for (int i = 0, rows = _rows.size(); i != rows; ++i) { auto &row = _rows[i]; for (int j = 0, cols = row.size(); j != cols; ++j) { if (row[j].link == p) { return { i, j }; } } } return { -1, -1 }; } void ReplyKeyboard::clickHandlerPressedChanged( const ClickHandlerPtr &handler, bool pressed) { if (!handler) return; _savedPressed = pressed ? handler : ClickHandlerPtr(); auto coords = findButtonCoordsByClickHandler(handler); if (coords.i >= 0) { auto &button = _rows[coords.i][coords.j]; if (pressed) { if (!button.ripple) { auto mask = Ui::RippleAnimation::roundRectMask( button.rect.size(), _st->buttonRadius()); button.ripple = std::make_unique<Ui::RippleAnimation>( _st->_st->ripple, std::move(mask), [this] { _st->repaint(_item); }); } button.ripple->add(_savedCoords - button.rect.topLeft()); } else { if (button.ripple) { button.ripple->lastStop(); } if (_savedActive != handler) { startAnimation(coords.i, coords.j, -1); } } } } void ReplyKeyboard::startAnimation(int i, int j, int direction) { auto notStarted = _animations.empty(); int indexForAnimation = (i * MatrixRowShift + j + 1) * direction; _animations.remove(-indexForAnimation); if (!_animations.contains(indexForAnimation)) { _animations.emplace(indexForAnimation, getms()); } if (notStarted && !_a_selected.animating()) { _a_selected.start(); } } void ReplyKeyboard::step_selected(TimeMs ms, bool timer) { for (auto i = _animations.begin(); i != _animations.end();) { const auto index = std::abs(i->first) - 1; const auto row = (index / MatrixRowShift); const auto col = index % MatrixRowShift; const auto dt = float64(ms - i->second) / st::botKbDuration; if (dt >= 1) { _rows[row][col].howMuchOver = (i->first > 0) ? 1 : 0; i = _animations.erase(i); } else { _rows[row][col].howMuchOver = (i->first > 0) ? dt : (1 - dt); ++i; } } if (timer) _st->repaint(_item); if (_animations.empty()) { _a_selected.stop(); } } void ReplyKeyboard::clearSelection() { for (const auto [relativeIndex, time] : _animations) { const auto index = std::abs(relativeIndex) - 1; const auto row = (index / MatrixRowShift); const auto col = index % MatrixRowShift; _rows[row][col].howMuchOver = 0; } _animations.clear(); _a_selected.stop(); } int ReplyKeyboard::Style::buttonSkip() const { return _st->margin; } int ReplyKeyboard::Style::buttonPadding() const { return _st->padding; } int ReplyKeyboard::Style::buttonHeight() const { return _st->height; } void ReplyKeyboard::Style::paintButton( Painter &p, int outerWidth, const ReplyKeyboard::Button &button, TimeMs ms) const { const QRect &rect = button.rect; paintButtonBg(p, rect, button.howMuchOver); if (button.ripple) { button.ripple->paint(p, rect.x(), rect.y(), outerWidth, ms); if (button.ripple->empty()) { button.ripple.reset(); } } paintButtonIcon(p, rect, outerWidth, button.type); if (button.type == HistoryMessageMarkupButton::Type::Callback || button.type == HistoryMessageMarkupButton::Type::Game) { if (auto data = button.link->getButton()) { if (data->requestId) { paintButtonLoading(p, rect); } } } int tx = rect.x(), tw = rect.width(); if (tw >= st::botKbStyle.font->elidew + _st->padding * 2) { tx += _st->padding; tw -= _st->padding * 2; } else if (tw > st::botKbStyle.font->elidew) { tx += (tw - st::botKbStyle.font->elidew) / 2; tw = st::botKbStyle.font->elidew; } button.text.drawElided(p, tx, rect.y() + _st->textTop + ((rect.height() - _st->height) / 2), tw, 1, style::al_top); } void HistoryMessageReplyMarkup::createFromButtonRows(const QVector<MTPKeyboardButtonRow> &v) { if (v.isEmpty()) { rows.clear(); return; } rows.reserve(v.size()); for_const (auto &row, v) { switch (row.type()) { case mtpc_keyboardButtonRow: { auto &r = row.c_keyboardButtonRow(); auto &b = r.vbuttons.v; if (!b.isEmpty()) { auto buttonRow = std::vector<Button>(); buttonRow.reserve(b.size()); for_const (auto &button, b) { switch (button.type()) { case mtpc_keyboardButton: { buttonRow.push_back({ Button::Type::Default, qs(button.c_keyboardButton().vtext), QByteArray(), 0 }); } break; case mtpc_keyboardButtonCallback: { auto &buttonData = button.c_keyboardButtonCallback(); buttonRow.push_back({ Button::Type::Callback, qs(buttonData.vtext), qba(buttonData.vdata), 0 }); } break; case mtpc_keyboardButtonRequestGeoLocation: { buttonRow.push_back({ Button::Type::RequestLocation, qs(button.c_keyboardButtonRequestGeoLocation().vtext), QByteArray(), 0 }); } break; case mtpc_keyboardButtonRequestPhone: { buttonRow.push_back({ Button::Type::RequestPhone, qs(button.c_keyboardButtonRequestPhone().vtext), QByteArray(), 0 }); } break; case mtpc_keyboardButtonUrl: { auto &buttonData = button.c_keyboardButtonUrl(); buttonRow.push_back({ Button::Type::Url, qs(buttonData.vtext), qba(buttonData.vurl), 0 }); } break; case mtpc_keyboardButtonSwitchInline: { auto &buttonData = button.c_keyboardButtonSwitchInline(); auto buttonType = buttonData.is_same_peer() ? Button::Type::SwitchInlineSame : Button::Type::SwitchInline; buttonRow.push_back({ buttonType, qs(buttonData.vtext), qba(buttonData.vquery), 0 }); if (buttonType == Button::Type::SwitchInline) { // Optimization flag. // Fast check on all new messages if there is a switch button to auto-click it. flags |= MTPDreplyKeyboardMarkup_ClientFlag::f_has_switch_inline_button; } } break; case mtpc_keyboardButtonGame: { auto &buttonData = button.c_keyboardButtonGame(); buttonRow.push_back({ Button::Type::Game, qs(buttonData.vtext), QByteArray(), 0 }); } break; case mtpc_keyboardButtonBuy: { auto &buttonData = button.c_keyboardButtonBuy(); buttonRow.push_back({ Button::Type::Buy, qs(buttonData.vtext), QByteArray(), 0 }); } } } if (!buttonRow.empty()) { rows.push_back(std::move(buttonRow)); } } } break; } } } void HistoryMessageReplyMarkup::create(const MTPReplyMarkup &markup) { flags = 0; rows.clear(); inlineKeyboard = nullptr; switch (markup.type()) { case mtpc_replyKeyboardMarkup: { auto &d = markup.c_replyKeyboardMarkup(); flags = d.vflags.v; createFromButtonRows(d.vrows.v); } break; case mtpc_replyInlineMarkup: { auto &d = markup.c_replyInlineMarkup(); flags = MTPDreplyKeyboardMarkup::Flags(0) | MTPDreplyKeyboardMarkup_ClientFlag::f_inline; createFromButtonRows(d.vrows.v); } break; case mtpc_replyKeyboardHide: { auto &d = markup.c_replyKeyboardHide(); flags = mtpCastFlags(d.vflags) | MTPDreplyKeyboardMarkup_ClientFlag::f_zero; } break; case mtpc_replyKeyboardForceReply: { auto &d = markup.c_replyKeyboardForceReply(); flags = mtpCastFlags(d.vflags) | MTPDreplyKeyboardMarkup_ClientFlag::f_force_reply; } break; } } void HistoryMessageReplyMarkup::create( const HistoryMessageReplyMarkup &markup) { flags = markup.flags; inlineKeyboard = nullptr; rows.clear(); for (const auto &row : markup.rows) { auto buttonRow = std::vector<Button>(); buttonRow.reserve(row.size()); for (const auto &button : row) { buttonRow.push_back({ button.type, button.text, button.data, 0 }); } if (!buttonRow.empty()) { rows.push_back(std::move(buttonRow)); } } } HistoryMessageLogEntryOriginal::HistoryMessageLogEntryOriginal() = default; HistoryMessageLogEntryOriginal::HistoryMessageLogEntryOriginal( HistoryMessageLogEntryOriginal &&other) : page(std::move(other.page)) { } HistoryMessageLogEntryOriginal &HistoryMessageLogEntryOriginal::operator=( HistoryMessageLogEntryOriginal &&other) { page = std::move(other.page); return *this; } HistoryMessageLogEntryOriginal::~HistoryMessageLogEntryOriginal() = default; HistoryDocumentCaptioned::HistoryDocumentCaptioned() : _caption(st::msgFileMinWidth - st::msgPadding.left() - st::msgPadding.right()) { } HistoryDocumentVoicePlayback::HistoryDocumentVoicePlayback(const HistoryDocument *that) : a_progress(0., 0.) , _a_progress(animation(const_cast<HistoryDocument*>(that), &HistoryDocument::step_voiceProgress)) { } void HistoryDocumentVoice::ensurePlayback(const HistoryDocument *that) const { if (!_playback) { _playback = std::make_unique<HistoryDocumentVoicePlayback>(that); } } void HistoryDocumentVoice::checkPlaybackFinished() const { if (_playback && !_playback->_a_progress.animating()) { _playback.reset(); } } void HistoryDocumentVoice::startSeeking() { _seeking = true; _seekingCurrent = _seekingStart; Media::Player::instance()->startSeeking(AudioMsgId::Type::Voice); } void HistoryDocumentVoice::stopSeeking() { _seeking = false; Media::Player::instance()->stopSeeking(AudioMsgId::Type::Voice); } ������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/history_item_components.h������������������������������0000664�0000000�0000000�00000024645�13262451251�0026274�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "history/history_item.h" class HistoryDocument; struct WebPageData; namespace HistoryView { class Element; } // namespace HistoryView struct HistoryMessageVia : public RuntimeComponent<HistoryMessageVia, HistoryItem> { void create(UserId userId); void resize(int32 availw) const; UserData *bot = nullptr; mutable QString text; mutable int width = 0; mutable int maxWidth = 0; ClickHandlerPtr link; }; struct HistoryMessageViews : public RuntimeComponent<HistoryMessageViews, HistoryItem> { QString _viewsText; int _views = 0; int _viewsWidth = 0; }; struct HistoryMessageSigned : public RuntimeComponent<HistoryMessageSigned, HistoryItem> { void refresh(const QString &date); int maxWidth() const; QString author; Text signature; }; struct HistoryMessageEdited : public RuntimeComponent<HistoryMessageEdited, HistoryItem> { void refresh(const QString &date, bool displayed); int maxWidth() const; TimeId date = 0; Text text; }; struct HistoryMessageForwarded : public RuntimeComponent<HistoryMessageForwarded, HistoryItem> { void create(const HistoryMessageVia *via) const; TimeId originalDate = 0; PeerData *originalSender = nullptr; QString originalAuthor; MsgId originalId = 0; mutable Text text = { 1 }; PeerData *savedFromPeer = nullptr; MsgId savedFromMsgId = 0; }; struct HistoryMessageReply : public RuntimeComponent<HistoryMessageReply, HistoryItem> { HistoryMessageReply() = default; HistoryMessageReply(const HistoryMessageReply &other) = delete; HistoryMessageReply(HistoryMessageReply &&other) = delete; HistoryMessageReply &operator=(const HistoryMessageReply &other) = delete; HistoryMessageReply &operator=(HistoryMessageReply &&other) { replyToMsgId = other.replyToMsgId; std::swap(replyToMsg, other.replyToMsg); replyToLnk = std::move(other.replyToLnk); replyToName = std::move(other.replyToName); replyToText = std::move(other.replyToText); replyToVersion = other.replyToVersion; maxReplyWidth = other.maxReplyWidth; replyToVia = std::move(other.replyToVia); return *this; } ~HistoryMessageReply() { // clearData() should be called by holder. Expects(replyToMsg == nullptr); Expects(replyToVia == nullptr); } bool updateData(not_null<HistoryMessage*> holder, bool force = false); // Must be called before destructor. void clearData(not_null<HistoryMessage*> holder); bool isNameUpdated() const; void updateName() const; void resize(int width) const; void itemRemoved(HistoryMessage *holder, HistoryItem *removed); enum class PaintFlag { InBubble = (1 << 0), Selected = (1 << 1), }; using PaintFlags = base::flags<PaintFlag>; friend inline constexpr auto is_flag_type(PaintFlag) { return true; }; void paint( Painter &p, not_null<const HistoryView::Element*> holder, int x, int y, int w, PaintFlags flags) const; MsgId replyToId() const { return replyToMsgId; } int replyToWidth() const { return maxReplyWidth; } ClickHandlerPtr replyToLink() const { return replyToLnk; } MsgId replyToMsgId = 0; HistoryItem *replyToMsg = nullptr; ClickHandlerPtr replyToLnk; mutable Text replyToName, replyToText; mutable int replyToVersion = 0; mutable int maxReplyWidth = 0; std::unique_ptr<HistoryMessageVia> replyToVia; int toWidth = 0; }; struct HistoryMessageMarkupButton { enum class Type { Default, Url, Callback, RequestPhone, RequestLocation, SwitchInline, SwitchInlineSame, Game, Buy, }; Type type; QString text; QByteArray data; mutable mtpRequestId requestId; }; struct HistoryMessageReplyMarkup : public RuntimeComponent<HistoryMessageReplyMarkup, HistoryItem> { using Button = HistoryMessageMarkupButton; HistoryMessageReplyMarkup() = default; HistoryMessageReplyMarkup(MTPDreplyKeyboardMarkup::Flags f) : flags(f) { } void create(const MTPReplyMarkup &markup); void create(const HistoryMessageReplyMarkup &markup); std::vector<std::vector<Button>> rows; MTPDreplyKeyboardMarkup::Flags flags = 0; std::unique_ptr<ReplyKeyboard> inlineKeyboard; // If >= 0 it holds the y coord of the inlineKeyboard before the last edition. int oldTop = -1; private: void createFromButtonRows(const QVector<MTPKeyboardButtonRow> &v); }; class ReplyMarkupClickHandler : public LeftButtonClickHandler { public: ReplyMarkupClickHandler(int row, int column, FullMsgId context); QString tooltip() const override { return _fullDisplayed ? QString() : buttonText(); } void setFullDisplayed(bool full) { _fullDisplayed = full; } // Copy to clipboard support. QString copyToClipboardText() const override; QString copyToClipboardContextItemText() const override; // Finds the corresponding button in the items markup struct. // If the button is not found it returns nullptr. // Note: it is possible that we will point to the different button // than the one was used when constructing the handler, but not a big deal. const HistoryMessageMarkupButton *getButton() const; // We hold only FullMsgId, not HistoryItem*, because all click handlers // are activated async and the item may be already destroyed. void setMessageId(const FullMsgId &msgId) { _itemId = msgId; } protected: void onClickImpl() const override; private: FullMsgId _itemId; int _row = 0; int _column = 0; bool _fullDisplayed = true; // Returns the full text of the corresponding button. QString buttonText() const; }; class ReplyKeyboard { private: struct Button; public: class Style { public: Style(const style::BotKeyboardButton &st) : _st(&st) { } virtual void startPaint(Painter &p) const = 0; virtual const style::TextStyle &textStyle() const = 0; int buttonSkip() const; int buttonPadding() const; int buttonHeight() const; virtual int buttonRadius() const = 0; virtual void repaint(not_null<const HistoryItem*> item) const = 0; virtual ~Style() { } protected: virtual void paintButtonBg( Painter &p, const QRect &rect, float64 howMuchOver) const = 0; virtual void paintButtonIcon( Painter &p, const QRect &rect, int outerWidth, HistoryMessageMarkupButton::Type type) const = 0; virtual void paintButtonLoading( Painter &p, const QRect &rect) const = 0; virtual int minButtonWidth( HistoryMessageMarkupButton::Type type) const = 0; private: const style::BotKeyboardButton *_st; void paintButton(Painter &p, int outerWidth, const ReplyKeyboard::Button &button, TimeMs ms) const; friend class ReplyKeyboard; }; ReplyKeyboard( not_null<const HistoryItem*> item, std::unique_ptr<Style> &&s); ReplyKeyboard(const ReplyKeyboard &other) = delete; ReplyKeyboard &operator=(const ReplyKeyboard &other) = delete; bool isEnoughSpace(int width, const style::BotKeyboardButton &st) const; void setStyle(std::unique_ptr<Style> &&s); void resize(int width, int height); // what width and height will best fit this keyboard int naturalWidth() const; int naturalHeight() const; void paint(Painter &p, int outerWidth, const QRect &clip, TimeMs ms) const; ClickHandlerPtr getLink(QPoint point) const; void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active); void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed); void clearSelection(); void updateMessageId(); private: friend class Style; struct Button { Button(); Button(Button &&other); Button &operator=(Button &&other); ~Button(); Text text = { 1 }; QRect rect; int characters = 0; float64 howMuchOver = 0.; HistoryMessageMarkupButton::Type type; std::shared_ptr<ReplyMarkupClickHandler> link; mutable std::unique_ptr<Ui::RippleAnimation> ripple; }; struct ButtonCoords { int i, j; }; void startAnimation(int i, int j, int direction); ButtonCoords findButtonCoordsByClickHandler(const ClickHandlerPtr &p); void step_selected(TimeMs ms, bool timer); const not_null<const HistoryItem*> _item; int _width = 0; std::vector<std::vector<Button>> _rows; base::flat_map<int, TimeMs> _animations; BasicAnimation _a_selected; std::unique_ptr<Style> _st; ClickHandlerPtr _savedPressed; ClickHandlerPtr _savedActive; mutable QPoint _savedCoords; }; // Special type of Component for the channel actions log. struct HistoryMessageLogEntryOriginal : public RuntimeComponent<HistoryMessageLogEntryOriginal, HistoryItem> { HistoryMessageLogEntryOriginal(); HistoryMessageLogEntryOriginal(HistoryMessageLogEntryOriginal &&other); HistoryMessageLogEntryOriginal &operator=(HistoryMessageLogEntryOriginal &&other); ~HistoryMessageLogEntryOriginal(); WebPageData *page = nullptr; }; class FileClickHandler; struct HistoryDocumentThumbed : public RuntimeComponent<HistoryDocumentThumbed, HistoryDocument> { std::shared_ptr<FileClickHandler> _linksavel, _linkcancell; int _thumbw = 0; mutable int _linkw = 0; mutable QString _link; }; struct HistoryDocumentCaptioned : public RuntimeComponent<HistoryDocumentCaptioned, HistoryDocument> { HistoryDocumentCaptioned(); Text _caption; }; struct HistoryDocumentNamed : public RuntimeComponent<HistoryDocumentNamed, HistoryDocument> { QString _name; int _namew = 0; }; struct HistoryDocumentVoicePlayback { HistoryDocumentVoicePlayback(const HistoryDocument *that); int32 _position = 0; anim::value a_progress; BasicAnimation _a_progress; }; class HistoryDocumentVoice : public RuntimeComponent<HistoryDocumentVoice, HistoryDocument> { // We don't use float64 because components should align to pointer even on 32bit systems. static constexpr float64 kFloatToIntMultiplier = 65536.; public: void ensurePlayback(const HistoryDocument *interfaces) const; void checkPlaybackFinished() const; mutable std::unique_ptr<HistoryDocumentVoicePlayback> _playback; std::shared_ptr<VoiceSeekClickHandler> _seekl; mutable int _lastDurationMs = 0; bool seeking() const { return _seeking; } void startSeeking(); void stopSeeking(); float64 seekingStart() const { return _seekingStart / kFloatToIntMultiplier; } void setSeekingStart(float64 seekingStart) const { _seekingStart = qRound(seekingStart * kFloatToIntMultiplier); } float64 seekingCurrent() const { return _seekingCurrent / kFloatToIntMultiplier; } void setSeekingCurrent(float64 seekingCurrent) { _seekingCurrent = qRound(seekingCurrent * kFloatToIntMultiplier); } private: bool _seeking = false; mutable int _seekingStart = 0; mutable int _seekingCurrent = 0; }; �������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/history_item_text.cpp����������������������������������0000664�0000000�0000000�00000007571�13262451251�0025425�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/history_item_text.h" #include "history/history_item.h" #include "history/history_item_components.h" #include "data/data_media_types.h" #include "data/data_web_page.h" #include "data/data_groups.h" #include "lang/lang_keys.h" #include "ui/text_options.h" TextWithEntities WrapAsReply( TextWithEntities &&text, not_null<HistoryItem*> to) { const auto name = to->author()->name; auto result = TextWithEntities(); result.text.reserve( lang(lng_in_reply_to).size() + name.size() + 4 + text.text.size()); result.text.append('[' ).append(lang(lng_in_reply_to) ).append(' ' ).append(name ).append(qsl("]\n") ); TextUtilities::Append(result, std::move(text)); return result; } TextWithEntities WrapAsForwarded( TextWithEntities &&text, not_null<HistoryMessageForwarded*> forwarded) { auto info = forwarded->text.originalTextWithEntities( AllTextSelection, ExpandLinksAll); auto result = TextWithEntities(); result.text.reserve( info.text.size() + 4 + text.text.size()); result.entities.reserve( info.entities.size() + text.entities.size()); result.text.append('['); TextUtilities::Append(result, std::move(info)); result.text.append(qsl("]\n")); TextUtilities::Append(result, std::move(text)); return result; } TextWithEntities WrapAsItem( not_null<HistoryItem*> item, TextWithEntities &&result) { if (const auto reply = item->Get<HistoryMessageReply>()) { if (const auto message = reply->replyToMsg) { result = WrapAsReply(std::move(result), message); } } if (const auto forwarded = item->Get<HistoryMessageForwarded>()) { result = WrapAsForwarded(std::move(result), forwarded); } return result; } TextWithEntities HistoryItemText(not_null<HistoryItem*> item) { const auto media = item->media(); auto mediaResult = media ? media->clipboardText() : TextWithEntities(); auto textResult = mediaResult.text.isEmpty() ? item->clipboardText() : TextWithEntities(); auto logEntryOriginalResult = [&] { const auto entry = item->Get<HistoryMessageLogEntryOriginal>(); if (!entry) { return TextWithEntities(); } const auto title = TextUtilities::SingleLine(entry->page->title.isEmpty() ? entry->page->author : entry->page->title); auto titleResult = TextUtilities::ParseEntities( title, Ui::WebpageTextTitleOptions().flags); auto descriptionResult = entry->page->description; if (titleResult.text.isEmpty()) { return descriptionResult; } else if (descriptionResult.text.isEmpty()) { return titleResult; } titleResult.text += '\n'; TextUtilities::Append(titleResult, std::move(descriptionResult)); return titleResult; }(); auto result = textResult; if (result.text.isEmpty()) { result = std::move(mediaResult); } else if (!mediaResult.text.isEmpty()) { result.text += qstr("\n\n"); TextUtilities::Append(result, std::move(mediaResult)); } if (result.text.isEmpty()) { result = std::move(logEntryOriginalResult); } else if (!logEntryOriginalResult.text.isEmpty()) { result.text += qstr("\n\n"); TextUtilities::Append(result, std::move(logEntryOriginalResult)); } return WrapAsItem(item, std::move(result)); } TextWithEntities HistoryGroupText(not_null<const Data::Group*> group) { Expects(!group->items.empty()); auto caption = [&] { const auto first = begin(group->items); const auto result = (*first)->clipboardText(); if (result.text.isEmpty()) { return result; } for (auto i = first + 1; i != end(group->items); ++i) { if (!(*i)->clipboardText().text.isEmpty()) { return TextWithEntities(); } } return result; }(); return WrapAsItem(group->items.back(), Data::WithCaptionClipboardText( lang(lng_in_dlg_album), std::move(caption))); } ���������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/history_item_text.h������������������������������������0000664�0000000�0000000�00000000714�13262451251�0025062�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once class HistoryItem; namespace Data { struct Group; } // namespace Data TextWithEntities HistoryItemText(not_null<HistoryItem*> item); TextWithEntities HistoryGroupText(not_null<const Data::Group*> group); ����������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/history_location_manager.cpp���������������������������0000664�0000000�0000000�00000016254�13262451251�0026723�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/history_location_manager.h" #include "mainwidget.h" #include "lang/lang_keys.h" #include "platform/platform_specific.h" namespace { constexpr auto kCoordPrecision = 8; constexpr auto kMaxHttpRedirects = 5; } // namespace QString LocationClickHandler::copyToClipboardText() const { return _text; } QString LocationClickHandler::copyToClipboardContextItemText() const { return lang(lng_context_copy_link); } void LocationClickHandler::onClick(Qt::MouseButton button) const { if (!psLaunchMaps(_coords)) { QDesktopServices::openUrl(_text); } } void LocationClickHandler::setup() { auto latlon = _coords.latAsString() + ',' + _coords.lonAsString(); _text = qsl("https://maps.google.com/maps?q=") + latlon + qsl("&ll=") + latlon + qsl("&z=16"); } namespace { LocationManager *locationManager = nullptr; } // namespace void initLocationManager() { if (!locationManager) { locationManager = new LocationManager(); locationManager->init(); } } void reinitLocationManager() { if (locationManager) { locationManager->reinit(); } } void deinitLocationManager() { if (locationManager) { locationManager->deinit(); delete locationManager; locationManager = nullptr; } } void LocationManager::init() { if (manager) delete manager; manager = new QNetworkAccessManager(); App::setProxySettings(*manager); connect(manager, SIGNAL(authenticationRequired(QNetworkReply*, QAuthenticator*)), this, SLOT(onFailed(QNetworkReply*))); #ifndef OS_MAC_OLD connect(manager, SIGNAL(sslErrors(QNetworkReply*, const QList<QSslError>&)), this, SLOT(onFailed(QNetworkReply*))); #endif // OS_MAC_OLD connect(manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(onFinished(QNetworkReply*))); if (notLoadedPlaceholder) { delete notLoadedPlaceholder->v(); delete notLoadedPlaceholder; } auto data = QImage(cIntRetinaFactor(), cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); data.fill(st::imageBgTransparent->c); data.setDevicePixelRatio(cRetinaFactor()); notLoadedPlaceholder = new ImagePtr(App::pixmapFromImageInPlace(std::move(data)), "GIF"); } void LocationManager::reinit() { if (manager) App::setProxySettings(*manager); } void LocationManager::deinit() { if (manager) { delete manager; manager = nullptr; } if (notLoadedPlaceholder) { delete notLoadedPlaceholder->v(); delete notLoadedPlaceholder; notLoadedPlaceholder = nullptr; } dataLoadings.clear(); imageLoadings.clear(); } void LocationManager::getData(LocationData *data) { if (!manager) { DEBUG_LOG(("App Error: getting image link data without manager init!")); return failed(data); } int32 w = st::locationSize.width(), h = st::locationSize.height(); int32 zoom = 13, scale = 1; if (cScale() == dbisTwo || cRetina()) { scale = 2; } else { w = convertScale(w); h = convertScale(h); } auto coords = data->coords.latAsString() + ',' + data->coords.lonAsString(); QString url = qsl("https://maps.googleapis.com/maps/api/staticmap?center=") + coords + qsl("&zoom=%1&size=%2x%3&maptype=roadmap&scale=%4&markers=color:red|size:big|").arg(zoom).arg(w).arg(h).arg(scale) + coords + qsl("&sensor=false"); QNetworkReply *reply = manager->get(QNetworkRequest(QUrl(url))); imageLoadings[reply] = data; } void LocationManager::onFinished(QNetworkReply *reply) { if (!manager) return; if (reply->error() != QNetworkReply::NoError) return onFailed(reply); QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); if (statusCode.isValid()) { int status = statusCode.toInt(); if (status == 301 || status == 302) { QString loc = reply->header(QNetworkRequest::LocationHeader).toString(); if (!loc.isEmpty()) { QMap<QNetworkReply*, LocationData*>::iterator i = dataLoadings.find(reply); if (i != dataLoadings.cend()) { LocationData *d = i.value(); if (serverRedirects.constFind(d) == serverRedirects.cend()) { serverRedirects.insert(d, 1); } else if (++serverRedirects[d] > kMaxHttpRedirects) { DEBUG_LOG(("Network Error: Too many HTTP redirects in onFinished() for image link: %1").arg(loc)); return onFailed(reply); } dataLoadings.erase(i); dataLoadings.insert(manager->get(QNetworkRequest(loc)), d); return; } else if ((i = imageLoadings.find(reply)) != imageLoadings.cend()) { LocationData *d = i.value(); if (serverRedirects.constFind(d) == serverRedirects.cend()) { serverRedirects.insert(d, 1); } else if (++serverRedirects[d] > kMaxHttpRedirects) { DEBUG_LOG(("Network Error: Too many HTTP redirects in onFinished() for image link: %1").arg(loc)); return onFailed(reply); } imageLoadings.erase(i); imageLoadings.insert(manager->get(QNetworkRequest(loc)), d); return; } } } if (status != 200) { DEBUG_LOG(("Network Error: Bad HTTP status received in onFinished() for image link: %1").arg(status)); return onFailed(reply); } } LocationData *d = 0; QMap<QNetworkReply*, LocationData*>::iterator i = dataLoadings.find(reply); if (i != dataLoadings.cend()) { d = i.value(); dataLoadings.erase(i); QJsonParseError e; QJsonDocument doc = QJsonDocument::fromJson(reply->readAll(), &e); if (e.error != QJsonParseError::NoError) { DEBUG_LOG(("JSON Error: Bad json received in onFinished() for image link")); return onFailed(reply); } failed(d); if (App::main()) App::main()->update(); } else { i = imageLoadings.find(reply); if (i != imageLoadings.cend()) { d = i.value(); imageLoadings.erase(i); QPixmap thumb; QByteArray format; QByteArray data(reply->readAll()); { QBuffer buffer(&data); QImageReader reader(&buffer); #ifndef OS_MAC_OLD reader.setAutoTransform(true); #endif // OS_MAC_OLD thumb = QPixmap::fromImageReader(&reader, Qt::ColorOnly); format = reader.format(); thumb.setDevicePixelRatio(cRetinaFactor()); if (format.isEmpty()) format = QByteArray("JPG"); } d->loading = false; d->thumb = thumb.isNull() ? (*notLoadedPlaceholder) : ImagePtr(thumb, format); serverRedirects.remove(d); if (App::main()) App::main()->update(); } } } void LocationManager::onFailed(QNetworkReply *reply) { if (!manager) return; LocationData *d = 0; QMap<QNetworkReply*, LocationData*>::iterator i = dataLoadings.find(reply); if (i != dataLoadings.cend()) { d = i.value(); dataLoadings.erase(i); } else { i = imageLoadings.find(reply); if (i != imageLoadings.cend()) { d = i.value(); imageLoadings.erase(i); } } DEBUG_LOG(("Network Error: failed to get data for image link %1,%2 error %3").arg(d ? d->coords.latAsString() : QString()).arg(d ? d->coords.lonAsString() : QString()).arg(reply->errorString())); if (d) { failed(d); } } void LocationManager::failed(LocationData *data) { data->loading = false; data->thumb = *notLoadedPlaceholder; serverRedirects.remove(data); } void LocationData::load() { if (!thumb->isNull()) return thumb->load(false, false); if (loading) return; loading = true; if (locationManager) { locationManager->getData(this); } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/history_location_manager.h�����������������������������0000664�0000000�0000000�00000005336�13262451251�0026367�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once void initLocationManager(); void reinitLocationManager(); void deinitLocationManager(); class LocationCoords { public: LocationCoords() = default; LocationCoords(float64 lat, float64 lon) : _lat(lat), _lon(lon) { } LocationCoords(const MTPDgeoPoint &point) : _lat(point.vlat.v), _lon(point.vlong.v) { } QString latAsString() const { return asString(_lat); } QString lonAsString() const { return asString(_lon); } MTPGeoPoint toMTP() const { return MTP_geoPoint(MTP_double(_lon), MTP_double(_lat)); } private: static QString asString(float64 value) { static constexpr auto kPrecision = 6; return QString::number(value, 'f', kPrecision); } friend inline bool operator==(const LocationCoords &a, const LocationCoords &b) { return (a._lat == b._lat) && (a._lon == b._lon); } friend inline bool operator<(const LocationCoords &a, const LocationCoords &b) { return (a._lat < b._lat) || ((a._lat == b._lat) && (a._lon < b._lon)); } friend inline uint qHash(const LocationCoords &t, uint seed = 0) { #ifndef OS_MAC_OLD return qHash(QtPrivate::QHashCombine().operator()(qHash(t._lat), t._lon), seed); #else // OS_MAC_OLD uint h1 = qHash(t._lat, seed); uint h2 = qHash(t._lon, seed); return ((h1 << 16) | (h1 >> 16)) ^ h2 ^ seed; #endif // OS_MAC_OLD } float64 _lat = 0; float64 _lon = 0; }; struct LocationData { LocationData(const LocationCoords &coords) : coords(coords), loading(false) { } LocationCoords coords; ImagePtr thumb; bool loading; void load(); }; class LocationClickHandler : public ClickHandler { public: LocationClickHandler(const LocationCoords &coords) : _coords(coords) { setup(); } void onClick(Qt::MouseButton button) const override; QString tooltip() const override { return QString(); } QString dragText() const override { return _text; } QString copyToClipboardText() const override; QString copyToClipboardContextItemText() const override; private: void setup(); LocationCoords _coords; QString _text; }; class LocationManager : public QObject { Q_OBJECT public: void init(); void reinit(); void deinit(); void getData(LocationData *data); ~LocationManager() { deinit(); } public slots: void onFinished(QNetworkReply *reply); void onFailed(QNetworkReply *reply); private: void failed(LocationData *data); QNetworkAccessManager *manager = nullptr; QMap<QNetworkReply*, LocationData*> dataLoadings, imageLoadings; QMap<LocationData*, int32> serverRedirects; ImagePtr *notLoadedPlaceholder = nullptr; }; ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/history_media.cpp��������������������������������������0000664�0000000�0000000�00000003675�13262451251�0024503�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/history_media.h" #include "history/history_item.h" #include "history/view/history_view_element.h" #include "history/view/history_view_cursor_state.h" #include "storage/storage_shared_media.h" #include "ui/text_options.h" namespace { using PointState = HistoryView::PointState; using TextState = HistoryView::TextState; } // namespace Storage::SharedMediaTypesMask HistoryMedia::sharedMediaTypes() const { return {}; } bool HistoryMedia::isDisplayed() const { return true; } QSize HistoryMedia::countCurrentSize(int newWidth) { return QSize(qMin(newWidth, maxWidth()), minHeight()); } Text HistoryMedia::createCaption(not_null<HistoryItem*> item) const { if (item->emptyText()) { return Text(); } const auto minResizeWidth = st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right(); auto result = Text(minResizeWidth); result.setMarkedText( st::messageTextStyle, item->originalText(), Ui::ItemTextOptions(item)); if (const auto width = _parent->skipBlockWidth()) { result.updateSkipBlock(width, _parent->skipBlockHeight()); } return result; } TextSelection HistoryMedia::skipSelection(TextSelection selection) const { return HistoryView::UnshiftItemSelection( selection, fullSelectionLength()); } TextSelection HistoryMedia::unskipSelection(TextSelection selection) const { return HistoryView::ShiftItemSelection( selection, fullSelectionLength()); } PointState HistoryMedia::pointState(QPoint point) const { return QRect(0, 0, width(), height()).contains(point) ? PointState::Inside : PointState::Outside; } TextState HistoryMedia::getStateGrouped( const QRect &geometry, QPoint point, StateRequest request) const { Unexpected("Grouping method call."); } �������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/history_media.h����������������������������������������0000664�0000000�0000000�00000014126�13262451251�0024141�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "history/view/history_view_object.h" struct HistoryMessageEdited; struct TextSelection; namespace base { template <typename Enum> class enum_mask; } // namespace base namespace Storage { enum class SharedMediaType : signed char; using SharedMediaTypesMask = base::enum_mask<SharedMediaType>; } // namespace Storage namespace HistoryView { enum class PointState : char; enum class CursorState : char; enum class InfoDisplayType : char; struct TextState; struct StateRequest; } // namespace HistoryView enum class MediaInBubbleState { None, Top, Middle, Bottom, }; enum HistoryMediaType : char { MediaTypePhoto, MediaTypeVideo, MediaTypeContact, MediaTypeCall, MediaTypeFile, MediaTypeGif, MediaTypeSticker, MediaTypeLocation, MediaTypeWebPage, MediaTypeMusicFile, MediaTypeVoiceFile, MediaTypeGame, MediaTypeInvoice, MediaTypeGrouped, MediaTypeCount }; class HistoryMedia : public HistoryView::Object { public: using Element = HistoryView::Element; using PointState = HistoryView::PointState; using TextState = HistoryView::TextState; using StateRequest = HistoryView::StateRequest; HistoryMedia(not_null<Element*> parent) : _parent(parent) { } virtual HistoryMediaType type() const = 0; virtual TextWithEntities selectedText(TextSelection selection) const { return TextWithEntities(); } virtual bool isDisplayed() const; virtual void updateNeedBubbleState() { } virtual bool hasTextForCopy() const { return false; } virtual bool hideMessageText() const { return true; } virtual bool allowsFastShare() const { return false; } virtual void refreshParentId(not_null<HistoryItem*> realParent) { } virtual void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const = 0; virtual PointState pointState(QPoint point) const; virtual TextState textState(QPoint point, StateRequest request) const = 0; virtual void updatePressed(QPoint point) { } virtual Storage::SharedMediaTypesMask sharedMediaTypes() const; // if we are in selecting items mode perhaps we want to // toggle selection instead of activating the pressed link virtual bool toggleSelectionByHandlerClick( const ClickHandlerPtr &p) const = 0; // if we press and drag on this media should we drag the item [[nodiscard]] virtual bool dragItem() const { return false; } [[nodiscard]] virtual TextSelection adjustSelection( TextSelection selection, TextSelectType type) const { return selection; } [[nodiscard]] virtual uint16 fullSelectionLength() const { return 0; } [[nodiscard]] TextSelection skipSelection( TextSelection selection) const; [[nodiscard]] TextSelection unskipSelection( TextSelection selection) const; // if we press and drag this link should we drag the item virtual bool dragItemByHandler(const ClickHandlerPtr &p) const = 0; virtual void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { } virtual void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) { } virtual bool uploading() const { return false; } virtual PhotoData *getPhoto() const { return nullptr; } virtual DocumentData *getDocument() const { return nullptr; } void playAnimation() { playAnimation(false); } void autoplayAnimation() { playAnimation(true); } virtual void stopAnimation() { } virtual QSize sizeForGrouping() const { Unexpected("Grouping method call."); } virtual void drawGrouped( Painter &p, const QRect &clip, TextSelection selection, TimeMs ms, const QRect &geometry, RectParts corners, not_null<uint64*> cacheKey, not_null<QPixmap*> cache) const { Unexpected("Grouping method call."); } virtual TextState getStateGrouped( const QRect &geometry, QPoint point, StateRequest request) const; virtual bool animating() const { return false; } virtual TextWithEntities getCaption() const { return TextWithEntities(); } virtual bool needsBubble() const = 0; virtual bool customInfoLayout() const = 0; virtual QMargins bubbleMargins() const { return QMargins(); } virtual bool hideForwardedFrom() const { return false; } virtual bool overrideEditedDate() const { return false; } virtual HistoryMessageEdited *displayedEditBadge() const { Unexpected("displayedEditBadge() on non-grouped media."); } // An attach media in a web page can provide an // additional text to be displayed below the attach. // For example duration / progress for video messages. virtual QString additionalInfoString() const { return QString(); } void setInBubbleState(MediaInBubbleState state) { _inBubbleState = state; } MediaInBubbleState inBubbleState() const { return _inBubbleState; } bool isBubbleTop() const { return (_inBubbleState == MediaInBubbleState::Top) || (_inBubbleState == MediaInBubbleState::None); } bool isBubbleBottom() const { return (_inBubbleState == MediaInBubbleState::Bottom) || (_inBubbleState == MediaInBubbleState::None); } virtual bool skipBubbleTail() const { return false; } // Sometimes click on media in message is overloaded by the message: // (for example it can open a link or a game instead of opening media) // But the overloading click handler should be used only when media // is already loaded (not a photo or GIF waiting for load with auto // load being disabled - in such case media should handle the click). virtual bool isReadyForOpen() const { return true; } // Should be called only by Data::Session. virtual void updateSharedContactUserId(UserId userId) { } virtual void parentTextUpdated() { } virtual ~HistoryMedia() = default; protected: using CursorState = HistoryView::CursorState; using InfoDisplayType = HistoryView::InfoDisplayType; QSize countCurrentSize(int newWidth) override; Text createCaption(not_null<HistoryItem*> item) const; virtual void playAnimation(bool autoplay) { } not_null<Element*> _parent; MediaInBubbleState _inBubbleState = MediaInBubbleState::None; }; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/history_media_grouped.cpp������������������������������0000664�0000000�0000000�00000027405�13262451251�0026225�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/history_media_grouped.h" #include "history/history_item_components.h" #include "history/history_media_types.h" #include "history/history_message.h" #include "history/view/history_view_element.h" #include "history/view/history_view_cursor_state.h" #include "data/data_media_types.h" #include "data/data_session.h" #include "storage/storage_shared_media.h" #include "lang/lang_keys.h" #include "ui/grouped_layout.h" #include "ui/text_options.h" #include "auth_session.h" #include "layout.h" #include "styles/style_history.h" namespace { using TextState = HistoryView::TextState; using PointState = HistoryView::PointState; constexpr auto kMaxDisplayedGroupSize = 10; } // namespace HistoryGroupedMedia::Part::Part(not_null<HistoryItem*> item) : item(item) { } HistoryGroupedMedia::HistoryGroupedMedia( not_null<Element*> parent, const std::vector<not_null<HistoryItem*>> &items) : HistoryMedia(parent) , _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) { const auto result = (items.size() <= kMaxDisplayedGroupSize) ? applyGroup(items) : applyGroup(std::vector<not_null<HistoryItem*>>( begin(items), begin(items) + kMaxDisplayedGroupSize)); Ensures(result); } QSize HistoryGroupedMedia::countOptimalSize() { if (_caption.hasSkipBlock()) { _caption.updateSkipBlock( _parent->skipBlockWidth(), _parent->skipBlockHeight()); } std::vector<QSize> sizes; sizes.reserve(_parts.size()); for (const auto &part : _parts) { const auto &media = part.content; media->initDimensions(); sizes.push_back(media->sizeForGrouping()); } const auto layout = Ui::LayoutMediaGroup( sizes, st::historyGroupWidthMax, st::historyGroupWidthMin, st::historyGroupSkip); Assert(layout.size() == _parts.size()); auto maxWidth = 0; auto minHeight = 0; for (auto i = 0, count = int(layout.size()); i != count; ++i) { const auto &item = layout[i]; accumulate_max(maxWidth, item.geometry.x() + item.geometry.width()); accumulate_max(minHeight, item.geometry.y() + item.geometry.height()); _parts[i].initialGeometry = item.geometry; _parts[i].sides = item.sides; } if (!_caption.isEmpty()) { auto captionw = maxWidth - st::msgPadding.left() - st::msgPadding.right(); minHeight += st::mediaCaptionSkip + _caption.countHeight(captionw); if (isBubbleBottom()) { minHeight += st::msgPadding.bottom(); } } return { maxWidth, minHeight }; } QSize HistoryGroupedMedia::countCurrentSize(int newWidth) { accumulate_min(newWidth, maxWidth()); auto newHeight = 0; if (newWidth < st::historyGroupWidthMin) { return { newWidth, newHeight }; } const auto initialSpacing = st::historyGroupSkip; const auto factor = newWidth / float64(maxWidth()); const auto scale = [&](int value) { return int(std::round(value * factor)); }; const auto spacing = scale(initialSpacing); for (auto &part : _parts) { const auto sides = part.sides; const auto initialGeometry = part.initialGeometry; const auto needRightSkip = !(sides & RectPart::Right); const auto needBottomSkip = !(sides & RectPart::Bottom); const auto initialLeft = initialGeometry.x(); const auto initialTop = initialGeometry.y(); const auto initialRight = initialLeft + initialGeometry.width() + (needRightSkip ? initialSpacing : 0); const auto initialBottom = initialTop + initialGeometry.height() + (needBottomSkip ? initialSpacing : 0); const auto left = scale(initialLeft); const auto top = scale(initialTop); const auto width = scale(initialRight) - left - (needRightSkip ? spacing : 0); const auto height = scale(initialBottom) - top - (needBottomSkip ? spacing : 0); part.geometry = QRect(left, top, width, height); accumulate_max(newHeight, top + height); } if (!_caption.isEmpty()) { const auto captionw = newWidth - st::msgPadding.left() - st::msgPadding.right(); newHeight += st::mediaCaptionSkip + _caption.countHeight(captionw); if (isBubbleBottom()) { newHeight += st::msgPadding.bottom(); } } return { newWidth, newHeight }; } void HistoryGroupedMedia::refreshParentId( not_null<HistoryItem*> realParent) { for (const auto &part : _parts) { part.content->refreshParentId(part.item); } } void HistoryGroupedMedia::draw( Painter &p, const QRect &clip, TextSelection selection, TimeMs ms) const { for (auto i = 0, count = int(_parts.size()); i != count; ++i) { const auto &part = _parts[i]; const auto partSelection = (selection == FullSelection) ? FullSelection : IsGroupItemSelection(selection, i) ? FullSelection : TextSelection(); auto corners = Ui::GetCornersFromSides(part.sides); if (!isBubbleTop()) { corners &= ~(RectPart::TopLeft | RectPart::TopRight); } if (!isBubbleBottom() || !_caption.isEmpty()) { corners &= ~(RectPart::BottomLeft | RectPart::BottomRight); } part.content->drawGrouped( p, clip, partSelection, ms, part.geometry, corners, &part.cacheKey, &part.cache); } // date const auto selected = (selection == FullSelection); if (!_caption.isEmpty()) { const auto captionw = width() - st::msgPadding.left() - st::msgPadding.right(); const auto outbg = _parent->hasOutLayout(); const auto captiony = height() - (isBubbleBottom() ? st::msgPadding.bottom() : 0) - _caption.countHeight(captionw); p.setPen(outbg ? (selected ? st::historyTextOutFgSelected : st::historyTextOutFg) : (selected ? st::historyTextInFgSelected : st::historyTextInFg)); _caption.draw(p, st::msgPadding.left(), captiony, captionw, style::al_left, 0, -1, selection); } else if (_parent->media() == this) { auto fullRight = width(); auto fullBottom = height(); if (needInfoDisplay()) { _parent->drawInfo(p, fullRight, fullBottom, width(), selected, InfoDisplayType::Image); } if (!_parent->hasBubble() && _parent->displayRightAction()) { auto fastShareLeft = (fullRight + st::historyFastShareLeft); auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize); _parent->drawRightAction(p, fastShareLeft, fastShareTop, width()); } } } TextState HistoryGroupedMedia::getPartState( QPoint point, StateRequest request) const { for (const auto &part : _parts) { if (part.geometry.contains(point)) { auto result = part.content->getStateGrouped( part.geometry, point, request); result.itemId = part.item->fullId(); return result; } } return TextState(_parent->data()); } PointState HistoryGroupedMedia::pointState(QPoint point) const { if (!QRect(0, 0, width(), height()).contains(point)) { return PointState::Outside; } for (const auto &part : _parts) { if (part.geometry.contains(point)) { return PointState::GroupPart; } } return PointState::Inside; } HistoryView::TextState HistoryGroupedMedia::textState( QPoint point, StateRequest request) const { auto result = getPartState(point, request); if (!result.link && !_caption.isEmpty()) { const auto captionw = width() - st::msgPadding.left() - st::msgPadding.right(); const auto captiony = height() - (isBubbleBottom() ? st::msgPadding.bottom() : 0) - _caption.countHeight(captionw); if (QRect(st::msgPadding.left(), captiony, captionw, height() - captiony).contains(point)) { return TextState(_parent->data(), _caption.getState( point - QPoint(st::msgPadding.left(), captiony), captionw, request.forText())); } } else if (_parent->media() == this) { auto fullRight = width(); auto fullBottom = height(); if (_parent->pointInTime(fullRight, fullBottom, point, InfoDisplayType::Image)) { result.cursor = CursorState::Date; } if (!_parent->hasBubble() && _parent->displayRightAction()) { auto fastShareLeft = (fullRight + st::historyFastShareLeft); auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize); if (QRect(fastShareLeft, fastShareTop, st::historyFastShareSize, st::historyFastShareSize).contains(point)) { result.link = _parent->rightActionLink(); } } } return result; } bool HistoryGroupedMedia::toggleSelectionByHandlerClick( const ClickHandlerPtr &p) const { for (const auto &part : _parts) { if (part.content->toggleSelectionByHandlerClick(p)) { return true; } } return false; } bool HistoryGroupedMedia::dragItemByHandler(const ClickHandlerPtr &p) const { for (const auto &part : _parts) { if (part.content->dragItemByHandler(p)) { return true; } } return false; } TextSelection HistoryGroupedMedia::adjustSelection( TextSelection selection, TextSelectType type) const { return _caption.adjustSelection(selection, type); } TextWithEntities HistoryGroupedMedia::selectedText( TextSelection selection) const { return _caption.originalTextWithEntities(selection, ExpandLinksAll); } void HistoryGroupedMedia::clickHandlerActiveChanged( const ClickHandlerPtr &p, bool active) { for (const auto &part : _parts) { part.content->clickHandlerActiveChanged(p, active); } } void HistoryGroupedMedia::clickHandlerPressedChanged( const ClickHandlerPtr &p, bool pressed) { for (const auto &part : _parts) { part.content->clickHandlerPressedChanged(p, pressed); if (pressed && part.content->dragItemByHandler(p)) { // #TODO drag by item from album // App::pressedLinkItem(part.view); } } } bool HistoryGroupedMedia::applyGroup( const std::vector<not_null<HistoryItem*>> &items) { Expects(items.size() <= kMaxDisplayedGroupSize); if (items.empty()) { return false; } if (validateGroupParts(items)) { return true; } for (const auto item : items) { const auto media = item->media(); Assert(media != nullptr && media->canBeGrouped()); _parts.push_back(Part(item)); _parts.back().content = media->createView(_parent, item); }; return true; } bool HistoryGroupedMedia::validateGroupParts( const std::vector<not_null<HistoryItem*>> &items) const { if (_parts.size() != items.size()) { return false; } for (auto i = 0, count = int(items.size()); i != count; ++i) { if (_parts[i].item != items[i]) { return false; } } return true; } not_null<HistoryMedia*> HistoryGroupedMedia::main() const { Expects(!_parts.empty()); return _parts.back().content.get(); } TextWithEntities HistoryGroupedMedia::getCaption() const { return main()->getCaption(); } Storage::SharedMediaTypesMask HistoryGroupedMedia::sharedMediaTypes() const { return main()->sharedMediaTypes(); } PhotoData *HistoryGroupedMedia::getPhoto() const { return main()->getPhoto(); } DocumentData *HistoryGroupedMedia::getDocument() const { return main()->getDocument(); } HistoryMessageEdited *HistoryGroupedMedia::displayedEditBadge() const { if (!_caption.isEmpty()) { return _parts.front().item->Get<HistoryMessageEdited>(); } return nullptr; } void HistoryGroupedMedia::updateNeedBubbleState() { const auto hasCaption = [&] { if (_parts.front().item->emptyText()) { return false; } for (auto i = 1, count = int(_parts.size()); i != count; ++i) { if (!_parts[i].item->emptyText()) { return false; } } return true; }(); if (hasCaption) { _caption = createCaption(_parts.front().item); } _needBubble = computeNeedBubble(); } void HistoryGroupedMedia::parentTextUpdated() { Auth().data().requestViewResize(_parent); } bool HistoryGroupedMedia::needsBubble() const { return _needBubble; } bool HistoryGroupedMedia::computeNeedBubble() const { if (!_caption.isEmpty()) { return true; } if (const auto item = _parent->data()) { if (item->viaBot() || item->Has<HistoryMessageReply>() || _parent->displayForwardedFrom() || _parent->displayFromName() ) { return true; } } return false; } bool HistoryGroupedMedia::needInfoDisplay() const { return (_parent->data()->id < 0 || _parent->isUnderCursor()); } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/history_media_grouped.h��������������������������������0000664�0000000�0000000�00000005637�13262451251�0025675�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "history/history_media.h" #include "data/data_document.h" #include "data/data_photo.h" class HistoryGroupedMedia : public HistoryMedia { public: HistoryGroupedMedia( not_null<Element*> parent, const std::vector<not_null<HistoryItem*>> &items); HistoryMediaType type() const override { return MediaTypeGrouped; } void refreshParentId(not_null<HistoryItem*> realParent) override; void draw( Painter &p, const QRect &clip, TextSelection selection, TimeMs ms) const override; PointState pointState(QPoint point) const override; TextState textState( QPoint point, StateRequest request) const override; bool toggleSelectionByHandlerClick( const ClickHandlerPtr &p) const override; bool dragItemByHandler(const ClickHandlerPtr &p) const override; [[nodiscard]] TextSelection adjustSelection( TextSelection selection, TextSelectType type) const override; uint16 fullSelectionLength() const override { return _caption.length(); } bool hasTextForCopy() const override { return !_caption.isEmpty(); } PhotoData *getPhoto() const override; DocumentData *getDocument() const override; TextWithEntities selectedText(TextSelection selection) const override; void clickHandlerActiveChanged( const ClickHandlerPtr &p, bool active) override; void clickHandlerPressedChanged( const ClickHandlerPtr &p, bool pressed) override; TextWithEntities getCaption() const override; Storage::SharedMediaTypesMask sharedMediaTypes() const override; bool overrideEditedDate() const override { return true; } HistoryMessageEdited *displayedEditBadge() const override; bool skipBubbleTail() const override { return isBubbleBottom() && _caption.isEmpty(); } void updateNeedBubbleState() override; bool needsBubble() const override; bool customInfoLayout() const override { return _caption.isEmpty(); } bool allowsFastShare() const override { return true; } void parentTextUpdated() override; private: struct Part { Part(not_null<HistoryItem*> item); not_null<HistoryItem*> item; std::unique_ptr<HistoryMedia> content; RectParts sides = RectPart::None; QRect initialGeometry; QRect geometry; mutable uint64 cacheKey = 0; mutable QPixmap cache; }; bool applyGroup(const std::vector<not_null<HistoryItem*>> &items); QSize countOptimalSize() override; QSize countCurrentSize(int newWidth) override; bool needInfoDisplay() const; bool computeNeedBubble() const; not_null<HistoryMedia*> main() const; bool validateGroupParts( const std::vector<not_null<HistoryItem*>> &items) const; TextState getPartState( QPoint point, StateRequest request) const; Text _caption; std::vector<Part> _parts; bool _needBubble = false; }; �������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/history_media_types.cpp��������������������������������0000664�0000000�0000000�00000521324�13262451251�0025723�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/history_media_types.h" #include "lang/lang_keys.h" #include "mainwidget.h" #include "layout.h" #include "mainwindow.h" #include "storage/localstorage.h" #include "storage/storage_shared_media.h" #include "media/media_audio.h" #include "media/media_clip_reader.h" #include "media/player/media_player_instance.h" #include "media/player/media_player_round_controller.h" #include "media/view/media_clip_playback.h" #include "boxes/confirm_box.h" #include "boxes/add_contact_box.h" #include "core/click_handler_types.h" #include "history/history.h" #include "history/history_item_components.h" #include "history/history_location_manager.h" #include "history/history_message.h" #include "history/view/history_view_element.h" #include "history/view/history_view_cursor_state.h" #include "window/main_window.h" #include "window/window_controller.h" #include "styles/style_history.h" #include "calls/calls_instance.h" #include "ui/empty_userpic.h" #include "ui/grouped_layout.h" #include "ui/text_options.h" #include "data/data_session.h" #include "data/data_media_types.h" namespace { constexpr auto kMaxGifForwardedBarLines = 4; constexpr auto kMaxOriginalEntryLines = 8192; using TextState = HistoryView::TextState; int documentMaxStatusWidth(DocumentData *document) { auto result = st::normalFont->width(formatDownloadText(document->size, document->size)); if (const auto song = document->song()) { accumulate_max(result, st::normalFont->width(formatPlayedText(song->duration, song->duration))); accumulate_max(result, st::normalFont->width(formatDurationAndSizeText(song->duration, document->size))); } else if (const auto voice = document->voice()) { accumulate_max(result, st::normalFont->width(formatPlayedText(voice->duration, voice->duration))); accumulate_max(result, st::normalFont->width(formatDurationAndSizeText(voice->duration, document->size))); } else if (document->isVideoFile()) { accumulate_max(result, st::normalFont->width(formatDurationAndSizeText(document->duration(), document->size))); } else { accumulate_max(result, st::normalFont->width(formatSizeText(document->size))); } return result; } int gifMaxStatusWidth(DocumentData *document) { auto result = st::normalFont->width(formatDownloadText(document->size, document->size)); accumulate_max(result, st::normalFont->width(formatGifAndSizeText(document->size))); return result; } std::unique_ptr<HistoryMedia> CreateAttach( not_null<HistoryView::Element*> parent, DocumentData *document, PhotoData *photo) { if (document) { if (document->sticker()) { return std::make_unique<HistorySticker>(parent, document); } else if (document->isAnimation()) { return std::make_unique<HistoryGif>( parent, document); } else if (document->isVideoFile()) { return std::make_unique<HistoryVideo>( parent, parent->data(), document); } return std::make_unique<HistoryDocument>( parent, document); } else if (photo) { return std::make_unique<HistoryPhoto>( parent, parent->data(), photo); } return nullptr; } } // namespace void HistoryFileMedia::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { if (p == _savel || p == _cancell) { if (active && !dataLoaded()) { ensureAnimation(); _animation->a_thumbOver.start([this] { thumbAnimationCallback(); }, 0., 1., st::msgFileOverDuration); } else if (!active && _animation && !dataLoaded()) { _animation->a_thumbOver.start([this] { thumbAnimationCallback(); }, 1., 0., st::msgFileOverDuration); } } } void HistoryFileMedia::thumbAnimationCallback() { Auth().data().requestViewRepaint(_parent); } void HistoryFileMedia::clickHandlerPressedChanged( const ClickHandlerPtr &handler, bool pressed) { Auth().data().requestViewRepaint(_parent); } void HistoryFileMedia::setLinks( FileClickHandlerPtr &&openl, FileClickHandlerPtr &&savel, FileClickHandlerPtr &&cancell) { _openl = std::move(openl); _savel = std::move(savel); _cancell = std::move(cancell); } void HistoryFileMedia::refreshParentId(not_null<HistoryItem*> realParent) { const auto contextId = realParent->fullId(); _openl->setMessageId(contextId); _savel->setMessageId(contextId); _cancell->setMessageId(contextId); } void HistoryFileMedia::setStatusSize(int newSize, int fullSize, int duration, qint64 realDuration) const { _statusSize = newSize; if (_statusSize == FileStatusSizeReady) { _statusText = (duration >= 0) ? formatDurationAndSizeText(duration, fullSize) : (duration < -1 ? formatGifAndSizeText(fullSize) : formatSizeText(fullSize)); } else if (_statusSize == FileStatusSizeLoaded) { _statusText = (duration >= 0) ? formatDurationText(duration) : (duration < -1 ? qsl("GIF") : formatSizeText(fullSize)); } else if (_statusSize == FileStatusSizeFailed) { _statusText = lang(lng_attach_failed); } else if (_statusSize >= 0) { _statusText = formatDownloadText(_statusSize, fullSize); } else { _statusText = formatPlayedText(-_statusSize - 1, realDuration); } } void HistoryFileMedia::step_radial(TimeMs ms, bool timer) { if (timer) { Auth().data().requestViewRepaint(_parent); } else { _animation->radial.update(dataProgress(), dataFinished(), ms); if (!_animation->radial.animating()) { checkAnimationFinished(); } } } void HistoryFileMedia::ensureAnimation() const { if (!_animation) { _animation = std::make_unique<AnimationData>(animation(const_cast<HistoryFileMedia*>(this), &HistoryFileMedia::step_radial)); } } void HistoryFileMedia::checkAnimationFinished() const { if (_animation && !_animation->a_thumbOver.animating() && !_animation->radial.animating()) { if (dataLoaded()) { _animation.reset(); } } } void HistoryFileMedia::setDocumentLinks( not_null<DocumentData*> document, not_null<HistoryItem*> realParent, bool inlinegif) { FileClickHandlerPtr open, save; const auto context = realParent->fullId(); if (inlinegif) { open = std::make_shared<GifOpenClickHandler>(document, context); } else { open = std::make_shared<DocumentOpenClickHandler>(document, context); } if (inlinegif) { save = std::make_shared<GifOpenClickHandler>(document, context); } else if (document->isVoiceMessage()) { save = std::make_shared<DocumentOpenClickHandler>(document, context); } else { save = std::make_shared<DocumentSaveClickHandler>(document, context); } setLinks( std::move(open), std::move(save), std::make_shared<DocumentCancelClickHandler>(document, context)); } HistoryFileMedia::~HistoryFileMedia() = default; HistoryPhoto::HistoryPhoto( not_null<Element*> parent, not_null<HistoryItem*> realParent, not_null<PhotoData*> photo) : HistoryFileMedia(parent) , _data(photo) , _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) { const auto fullId = realParent->fullId(); setLinks( std::make_shared<PhotoOpenClickHandler>(_data, fullId), std::make_shared<PhotoSaveClickHandler>(_data, fullId), std::make_shared<PhotoCancelClickHandler>(_data, fullId)); _caption = createCaption(realParent); create(realParent->fullId()); } HistoryPhoto::HistoryPhoto( not_null<Element*> parent, not_null<PeerData*> chat, not_null<PhotoData*> photo, int width) : HistoryFileMedia(parent) , _data(photo) , _serviceWidth(width) { create(parent->data()->fullId(), chat); } void HistoryPhoto::create(FullMsgId contextId, PeerData *chat) { setLinks( std::make_shared<PhotoOpenClickHandler>(_data, contextId, chat), std::make_shared<PhotoSaveClickHandler>(_data, contextId, chat), std::make_shared<PhotoCancelClickHandler>(_data, contextId, chat)); _data->thumb->load(); } QSize HistoryPhoto::countOptimalSize() { if (_parent->media() != this) { _caption = Text(); } else if (_caption.hasSkipBlock()) { _caption.updateSkipBlock( _parent->skipBlockWidth(), _parent->skipBlockHeight()); } auto maxWidth = 0; auto minHeight = 0; auto tw = convertScale(_data->full->width()); auto th = convertScale(_data->full->height()); if (!tw || !th) { tw = th = 1; } if (tw > st::maxMediaSize) { th = (st::maxMediaSize * th) / tw; tw = st::maxMediaSize; } if (th > st::maxMediaSize) { tw = (st::maxMediaSize * tw) / th; th = st::maxMediaSize; } if (_serviceWidth > 0) { return { _serviceWidth, _serviceWidth }; } const auto minWidth = qMax(st::minPhotoSize, _parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); const auto maxActualWidth = qMax(tw, minWidth); maxWidth = qMax(maxActualWidth, th); minHeight = qMax(th, st::minPhotoSize); if (_parent->hasBubble() && !_caption.isEmpty()) { auto captionw = maxActualWidth - st::msgPadding.left() - st::msgPadding.right(); minHeight += st::mediaCaptionSkip + _caption.countHeight(captionw); if (isBubbleBottom()) { minHeight += st::msgPadding.bottom(); } } return { maxWidth, minHeight }; } QSize HistoryPhoto::countCurrentSize(int newWidth) { int tw = convertScale(_data->full->width()), th = convertScale(_data->full->height()); if (tw > st::maxMediaSize) { th = (st::maxMediaSize * th) / tw; tw = st::maxMediaSize; } if (th > st::maxMediaSize) { tw = (st::maxMediaSize * tw) / th; th = st::maxMediaSize; } _pixw = qMin(newWidth, maxWidth()); _pixh = th; if (tw > _pixw) { _pixh = (_pixw * _pixh / tw); } else { _pixw = tw; } if (_pixh > newWidth) { _pixw = (_pixw * newWidth) / _pixh; _pixh = newWidth; } if (_pixw < 1) _pixw = 1; if (_pixh < 1) _pixh = 1; auto minWidth = qMax(st::minPhotoSize, _parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); newWidth = qMax(_pixw, minWidth); auto newHeight = qMax(_pixh, st::minPhotoSize); if (_parent->hasBubble() && !_caption.isEmpty()) { const auto captionw = newWidth - st::msgPadding.left() - st::msgPadding.right(); newHeight += st::mediaCaptionSkip + _caption.countHeight(captionw); if (isBubbleBottom()) { newHeight += st::msgPadding.bottom(); } } return { newWidth, newHeight }; } void HistoryPhoto::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const { if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; _data->automaticLoad(_parent->data()); auto selected = (selection == FullSelection); auto loaded = _data->loaded(); auto displayLoading = _data->displayLoading(); auto inWebPage = (_parent->media() != this); auto paintx = 0, painty = 0, paintw = width(), painth = height(); auto bubble = _parent->hasBubble(); auto captionw = paintw - st::msgPadding.left() - st::msgPadding.right(); if (displayLoading) { ensureAnimation(); if (!_animation->radial.animating()) { _animation->radial.start(_data->progress()); } } bool radial = isRadialAnimation(ms); auto rthumb = rtlrect(paintx, painty, paintw, painth, width()); if (_serviceWidth > 0) { const auto pix = loaded ? _data->full->pixCircled(_pixw, _pixh) : _data->thumb->pixBlurredCircled(_pixw, _pixh); p.drawPixmap(rthumb.topLeft(), pix); } else { if (bubble) { if (!_caption.isEmpty()) { painth -= st::mediaCaptionSkip + _caption.countHeight(captionw); if (isBubbleBottom()) { painth -= st::msgPadding.bottom(); } rthumb = rtlrect(paintx, painty, paintw, painth, width()); } } else { App::roundShadow(p, 0, 0, paintw, painth, selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners); } auto inWebPage = (_parent->media() != this); auto roundRadius = inWebPage ? ImageRoundRadius::Small : ImageRoundRadius::Large; auto roundCorners = inWebPage ? RectPart::AllCorners : ((isBubbleTop() ? (RectPart::TopLeft | RectPart::TopRight) : RectPart::None) | ((isBubbleBottom() && _caption.isEmpty()) ? (RectPart::BottomLeft | RectPart::BottomRight) : RectPart::None)); const auto pix = loaded ? _data->full->pixSingle(_pixw, _pixh, paintw, painth, roundRadius, roundCorners) : _data->thumb->pixBlurredSingle(_pixw, _pixh, paintw, painth, roundRadius, roundCorners); p.drawPixmap(rthumb.topLeft(), pix); if (selected) { App::complexOverlayRect(p, rthumb, roundRadius, roundCorners); } } if (radial || (!loaded && !_data->loading())) { const auto radialOpacity = (radial && loaded && !_data->uploading()) ? _animation->radial.opacity() : 1.; QRect inner(rthumb.x() + (rthumb.width() - st::msgFileSize) / 2, rthumb.y() + (rthumb.height() - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize); p.setPen(Qt::NoPen); if (selected) { p.setBrush(st::msgDateImgBgSelected); } else if (isThumbAnimation(ms)) { auto over = _animation->a_thumbOver.current(); p.setBrush(anim::brush(st::msgDateImgBg, st::msgDateImgBgOver, over)); } else { auto over = ClickHandler::showAsActive(_data->loading() ? _cancell : _savel); p.setBrush(over ? st::msgDateImgBgOver : st::msgDateImgBg); } p.setOpacity(radialOpacity * p.opacity()); { PainterHighQualityEnabler hq(p); p.drawEllipse(inner); } p.setOpacity(radialOpacity); auto icon = ([radial, this, selected]() -> const style::icon* { if (radial || _data->loading()) { auto delayed = _data->full->toDelayedStorageImage(); if (!delayed || !delayed->location().isNull()) { return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel); } return nullptr; } return &(selected ? st::historyFileThumbDownloadSelected : st::historyFileThumbDownload); })(); if (icon) { icon->paintInCenter(p, inner); } p.setOpacity(1); if (radial) { QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine))); _animation->radial.draw(p, rinner, st::msgFileRadialLine, selected ? st::historyFileThumbRadialFgSelected : st::historyFileThumbRadialFg); } } // date if (!_caption.isEmpty()) { auto outbg = _parent->hasOutLayout(); p.setPen(outbg ? (selected ? st::historyTextOutFgSelected : st::historyTextOutFg) : (selected ? st::historyTextInFgSelected : st::historyTextInFg)); _caption.draw(p, st::msgPadding.left(), painty + painth + st::mediaCaptionSkip, captionw, style::al_left, 0, -1, selection); } else if (!inWebPage) { auto fullRight = paintx + paintw; auto fullBottom = painty + painth; if (needInfoDisplay()) { _parent->drawInfo(p, fullRight, fullBottom, 2 * paintx + paintw, selected, InfoDisplayType::Image); } if (!bubble && _parent->displayRightAction()) { auto fastShareLeft = (fullRight + st::historyFastShareLeft); auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize); _parent->drawRightAction(p, fastShareLeft, fastShareTop, 2 * paintx + paintw); } } } TextState HistoryPhoto::textState(QPoint point, StateRequest request) const { auto result = TextState(_parent); if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) { return result; } auto paintx = 0, painty = 0, paintw = width(), painth = height(); auto bubble = _parent->hasBubble(); if (bubble && !_caption.isEmpty()) { const auto captionw = paintw - st::msgPadding.left() - st::msgPadding.right(); painth -= _caption.countHeight(captionw); if (isBubbleBottom()) { painth -= st::msgPadding.bottom(); } if (QRect(st::msgPadding.left(), painth, captionw, height() - painth).contains(point)) { result = TextState(_parent, _caption.getState( point - QPoint(st::msgPadding.left(), painth), captionw, request.forText())); return result; } painth -= st::mediaCaptionSkip; } if (QRect(paintx, painty, paintw, painth).contains(point)) { if (_data->uploading()) { result.link = _cancell; } else if (_data->loaded()) { result.link = _openl; } else if (_data->loading()) { auto delayed = _data->full->toDelayedStorageImage(); if (!delayed || !delayed->location().isNull()) { result.link = _cancell; } } else { result.link = _savel; } } if (_caption.isEmpty() && _parent->media() == this) { auto fullRight = paintx + paintw; auto fullBottom = painty + painth; if (_parent->pointInTime(fullRight, fullBottom, point, InfoDisplayType::Image)) { result.cursor = CursorState::Date; } if (!bubble && _parent->displayRightAction()) { auto fastShareLeft = (fullRight + st::historyFastShareLeft); auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize); if (QRect(fastShareLeft, fastShareTop, st::historyFastShareSize, st::historyFastShareSize).contains(point)) { result.link = _parent->rightActionLink(); } } } return result; } QSize HistoryPhoto::sizeForGrouping() const { const auto width = convertScale(_data->full->width()); const auto height = convertScale(_data->full->height()); return { std::max(width, 1), std::max(height, 1) }; } void HistoryPhoto::drawGrouped( Painter &p, const QRect &clip, TextSelection selection, TimeMs ms, const QRect &geometry, RectParts corners, not_null<uint64*> cacheKey, not_null<QPixmap*> cache) const { _data->automaticLoad(_parent->data()); validateGroupedCache(geometry, corners, cacheKey, cache); const auto selected = (selection == FullSelection); const auto loaded = _data->loaded(); const auto displayLoading = _data->displayLoading(); const auto bubble = _parent->hasBubble(); if (displayLoading) { ensureAnimation(); if (!_animation->radial.animating()) { _animation->radial.start(_data->progress()); } } const auto radial = isRadialAnimation(ms); if (!bubble) { // App::roundShadow(p, 0, 0, paintw, painth, selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners); } p.drawPixmap(geometry.topLeft(), *cache); if (selected) { const auto roundRadius = ImageRoundRadius::Large; App::complexOverlayRect(p, geometry, roundRadius, corners); } const auto displayState = radial || (!loaded && !_data->loading()) || _data->waitingForAlbum(); if (displayState) { const auto radialOpacity = (radial && loaded && !_data->uploading()) ? _animation->radial.opacity() : 1.; const auto radialSize = st::historyGroupRadialSize; const auto inner = QRect( geometry.x() + (geometry.width() - radialSize) / 2, geometry.y() + (geometry.height() - radialSize) / 2, radialSize, radialSize); p.setPen(Qt::NoPen); if (selected) { p.setBrush(st::msgDateImgBgSelected); } else if (isThumbAnimation(ms)) { auto over = _animation->a_thumbOver.current(); p.setBrush(anim::brush(st::msgDateImgBg, st::msgDateImgBgOver, over)); } else { auto over = ClickHandler::showAsActive(_data->loading() ? _cancell : _savel); p.setBrush(over ? st::msgDateImgBgOver : st::msgDateImgBg); } p.setOpacity(radialOpacity * p.opacity()); { PainterHighQualityEnabler hq(p); p.drawEllipse(inner); } p.setOpacity(radialOpacity); auto icon = [&]() -> const style::icon* { if (_data->waitingForAlbum()) { return &(selected ? st::historyFileThumbWaitingSelected : st::historyFileThumbWaiting); } else if (radial || _data->loading()) { auto delayed = _data->full->toDelayedStorageImage(); if (!delayed || !delayed->location().isNull()) { return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel); } return nullptr; } return &(selected ? st::historyFileThumbDownloadSelected : st::historyFileThumbDownload); }(); if (icon) { icon->paintInCenter(p, inner); } p.setOpacity(1); if (radial) { const auto line = st::historyGroupRadialLine; const auto rinner = inner.marginsRemoved({ line, line, line, line }); const auto color = selected ? st::historyFileThumbRadialFgSelected : st::historyFileThumbRadialFg; _animation->radial.draw(p, rinner, line, color); } } } TextState HistoryPhoto::getStateGrouped( const QRect &geometry, QPoint point, StateRequest request) const { if (!geometry.contains(point)) { return {}; } const auto delayed = _data->full->toDelayedStorageImage(); return TextState(_parent, _data->uploading() ? _cancell : _data->loaded() ? _openl : _data->loading() ? ((!delayed || !delayed->location().isNull()) ? _cancell : ClickHandlerPtr()) : _savel); } float64 HistoryPhoto::dataProgress() const { return _data->progress(); } bool HistoryPhoto::dataFinished() const { return !_data->loading() && (!_data->uploading() || _data->waitingForAlbum()); } bool HistoryPhoto::dataLoaded() const { return _data->loaded(); } bool HistoryPhoto::needInfoDisplay() const { return (_data->uploading() || _parent->isUnderCursor()); } void HistoryPhoto::validateGroupedCache( const QRect &geometry, RectParts corners, not_null<uint64*> cacheKey, not_null<QPixmap*> cache) const { using Option = Images::Option; const auto loaded = _data->loaded(); const auto loadLevel = loaded ? 2 : _data->thumb->loaded() ? 1 : 0; const auto width = geometry.width(); const auto height = geometry.height(); const auto options = Option::Smooth | Option::RoundedLarge | (loaded ? Option::None : Option::Blurred) | ((corners & RectPart::TopLeft) ? Option::RoundedTopLeft : Option::None) | ((corners & RectPart::TopRight) ? Option::RoundedTopRight : Option::None) | ((corners & RectPart::BottomLeft) ? Option::RoundedBottomLeft : Option::None) | ((corners & RectPart::BottomRight) ? Option::RoundedBottomRight : Option::None); const auto key = (uint64(width) << 48) | (uint64(height) << 32) | (uint64(options) << 16) | (uint64(loadLevel)); if (*cacheKey == key) { return; } const auto originalWidth = convertScale(_data->full->width()); const auto originalHeight = convertScale(_data->full->height()); const auto pixSize = Ui::GetImageScaleSizeForGeometry( { originalWidth, originalHeight }, { width, height }); const auto pixWidth = pixSize.width() * cIntRetinaFactor(); const auto pixHeight = pixSize.height() * cIntRetinaFactor(); const auto &image = loaded ? _data->full : _data->thumb; *cacheKey = key; *cache = image->pixNoCache(pixWidth, pixHeight, options, width, height); } TextWithEntities HistoryPhoto::selectedText(TextSelection selection) const { return _caption.originalTextWithEntities(selection, ExpandLinksAll); } bool HistoryPhoto::needsBubble() const { if (!_caption.isEmpty()) { return true; } const auto item = _parent->data(); if (item->toHistoryMessage()) { return item->viaBot() || item->Has<HistoryMessageReply>() || _parent->displayForwardedFrom() || _parent->displayFromName(); } return false; } void HistoryPhoto::parentTextUpdated() { _caption = (_parent->media() == this) ? createCaption(_parent->data()) : Text(); Auth().data().requestViewResize(_parent); } HistoryVideo::HistoryVideo( not_null<Element*> parent, not_null<HistoryItem*> realParent, not_null<DocumentData*> document) : HistoryFileMedia(parent) , _data(document) , _thumbw(1) , _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) { _caption = createCaption(realParent); setDocumentLinks(_data, realParent); setStatusSize(FileStatusSizeReady); _data->thumb->load(); } QSize HistoryVideo::countOptimalSize() { if (_parent->media() != this) { _caption = Text(); } else if (_caption.hasSkipBlock()) { _caption.updateSkipBlock( _parent->skipBlockWidth(), _parent->skipBlockHeight()); } auto tw = convertScale(_data->thumb->width()); auto th = convertScale(_data->thumb->height()); if (!tw || !th) { tw = th = 1; } if (tw * st::msgVideoSize.height() > th * st::msgVideoSize.width()) { th = qRound((st::msgVideoSize.width() / float64(tw)) * th); tw = st::msgVideoSize.width(); } else { tw = qRound((st::msgVideoSize.height() / float64(th)) * tw); th = st::msgVideoSize.height(); } _thumbw = qMax(tw, 1); auto minWidth = qMax(st::minPhotoSize, _parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); minWidth = qMax(minWidth, documentMaxStatusWidth(_data) + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); auto maxWidth = qMax(_thumbw, minWidth); auto minHeight = qMax(th, st::minPhotoSize); if (_parent->hasBubble() && !_caption.isEmpty()) { const auto captionw = maxWidth - st::msgPadding.left() - st::msgPadding.right(); minHeight += st::mediaCaptionSkip + _caption.countHeight(captionw); if (isBubbleBottom()) { minHeight += st::msgPadding.bottom(); } } return { maxWidth, minHeight }; } QSize HistoryVideo::countCurrentSize(int newWidth) { int tw = convertScale(_data->thumb->width()), th = convertScale(_data->thumb->height()); if (!tw || !th) { tw = th = 1; } if (tw * st::msgVideoSize.height() > th * st::msgVideoSize.width()) { th = qRound((st::msgVideoSize.width() / float64(tw)) * th); tw = st::msgVideoSize.width(); } else { tw = qRound((st::msgVideoSize.height() / float64(th)) * tw); th = st::msgVideoSize.height(); } if (newWidth < tw) { th = qRound((newWidth / float64(tw)) * th); tw = newWidth; } _thumbw = qMax(tw, 1); auto minWidth = qMax(st::minPhotoSize, _parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); minWidth = qMax(minWidth, documentMaxStatusWidth(_data) + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); newWidth = qMax(_thumbw, minWidth); auto newHeight = qMax(th, st::minPhotoSize); if (_parent->hasBubble() && !_caption.isEmpty()) { const auto captionw = newWidth - st::msgPadding.left() - st::msgPadding.right(); newHeight += st::mediaCaptionSkip + _caption.countHeight(captionw); if (isBubbleBottom()) { newHeight += st::msgPadding.bottom(); } } return { newWidth, newHeight }; } void HistoryVideo::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const { if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; _data->automaticLoad(_parent->data()); bool loaded = _data->loaded(), displayLoading = _data->displayLoading(); bool selected = (selection == FullSelection); auto paintx = 0, painty = 0, paintw = width(), painth = height(); bool bubble = _parent->hasBubble(); int captionw = paintw - st::msgPadding.left() - st::msgPadding.right(); if (displayLoading) { ensureAnimation(); if (!_animation->radial.animating()) { _animation->radial.start(_data->progress()); } } updateStatusText(); bool radial = isRadialAnimation(ms); if (bubble) { if (!_caption.isEmpty()) { painth -= st::mediaCaptionSkip + _caption.countHeight(captionw); if (isBubbleBottom()) { painth -= st::msgPadding.bottom(); } } } else { App::roundShadow(p, 0, 0, paintw, painth, selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners); } auto inWebPage = (_parent->media() != this); auto roundRadius = inWebPage ? ImageRoundRadius::Small : ImageRoundRadius::Large; auto roundCorners = inWebPage ? RectPart::AllCorners : ((isBubbleTop() ? (RectPart::TopLeft | RectPart::TopRight) : RectPart::None) | ((isBubbleBottom() && _caption.isEmpty()) ? (RectPart::BottomLeft | RectPart::BottomRight) : RectPart::None)); QRect rthumb(rtlrect(paintx, painty, paintw, painth, width())); p.drawPixmap(rthumb.topLeft(), _data->thumb->pixBlurredSingle(_thumbw, 0, paintw, painth, roundRadius, roundCorners)); if (selected) { App::complexOverlayRect(p, rthumb, roundRadius, roundCorners); } QRect inner(rthumb.x() + (rthumb.width() - st::msgFileSize) / 2, rthumb.y() + (rthumb.height() - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize); p.setPen(Qt::NoPen); if (selected) { p.setBrush(st::msgDateImgBgSelected); } else if (isThumbAnimation(ms)) { auto over = _animation->a_thumbOver.current(); p.setBrush(anim::brush(st::msgDateImgBg, st::msgDateImgBgOver, over)); } else { bool over = ClickHandler::showAsActive(_data->loading() ? _cancell : _savel); p.setBrush(over ? st::msgDateImgBgOver : st::msgDateImgBg); } { PainterHighQualityEnabler hq(p); p.drawEllipse(inner); } if (!selected && _animation) { p.setOpacity(1); } auto icon = ([this, radial, selected, loaded]() -> const style::icon * { if (loaded && !radial) { return &(selected ? st::historyFileThumbPlaySelected : st::historyFileThumbPlay); } else if (radial || _data->loading()) { if (_parent->data()->id > 0 || _data->uploading()) { return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel); } return nullptr; } return &(selected ? st::historyFileThumbDownloadSelected : st::historyFileThumbDownload); })(); if (icon) { icon->paintInCenter(p, inner); } if (radial) { QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine))); _animation->radial.draw(p, rinner, st::msgFileRadialLine, selected ? st::historyFileThumbRadialFgSelected : st::historyFileThumbRadialFg); } auto statusX = paintx + st::msgDateImgDelta + st::msgDateImgPadding.x(), statusY = painty + st::msgDateImgDelta + st::msgDateImgPadding.y(); auto statusW = st::normalFont->width(_statusText) + 2 * st::msgDateImgPadding.x(); auto statusH = st::normalFont->height + 2 * st::msgDateImgPadding.y(); App::roundRect(p, rtlrect(statusX - st::msgDateImgPadding.x(), statusY - st::msgDateImgPadding.y(), statusW, statusH, width()), selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? DateSelectedCorners : DateCorners); p.setFont(st::normalFont); p.setPen(st::msgDateImgFg); p.drawTextLeft(statusX, statusY, width(), _statusText, statusW - 2 * st::msgDateImgPadding.x()); // date if (!_caption.isEmpty()) { auto outbg = _parent->hasOutLayout(); p.setPen(outbg ? (selected ? st::historyTextOutFgSelected : st::historyTextOutFg) : (selected ? st::historyTextInFgSelected : st::historyTextInFg)); _caption.draw(p, st::msgPadding.left(), painty + painth + st::mediaCaptionSkip, captionw, style::al_left, 0, -1, selection); } else if (_parent->media() == this) { auto fullRight = paintx + paintw, fullBottom = painty + painth; _parent->drawInfo(p, fullRight, fullBottom, 2 * paintx + paintw, selected, InfoDisplayType::Image); if (!bubble && _parent->displayRightAction()) { auto fastShareLeft = (fullRight + st::historyFastShareLeft); auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize); _parent->drawRightAction(p, fastShareLeft, fastShareTop, 2 * paintx + paintw); } } } TextState HistoryVideo::textState(QPoint point, StateRequest request) const { if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) { return {}; } auto result = TextState(_parent); bool loaded = _data->loaded(); auto paintx = 0, painty = 0, paintw = width(), painth = height(); bool bubble = _parent->hasBubble(); if (bubble && !_caption.isEmpty()) { const auto captionw = paintw - st::msgPadding.left() - st::msgPadding.right(); painth -= _caption.countHeight(captionw); if (isBubbleBottom()) { painth -= st::msgPadding.bottom(); } if (QRect(st::msgPadding.left(), painth, captionw, height() - painth).contains(point)) { result = TextState(_parent, _caption.getState( point - QPoint(st::msgPadding.left(), painth), captionw, request.forText())); } painth -= st::mediaCaptionSkip; } if (QRect(paintx, painty, paintw, painth).contains(point)) { if (_data->uploading()) { result.link = _cancell; } else { result.link = loaded ? _openl : (_data->loading() ? _cancell : _savel); } } if (_caption.isEmpty() && _parent->media() == this) { auto fullRight = paintx + paintw; auto fullBottom = painty + painth; if (_parent->pointInTime(fullRight, fullBottom, point, InfoDisplayType::Image)) { result.cursor = CursorState::Date; } if (!bubble && _parent->displayRightAction()) { auto fastShareLeft = (fullRight + st::historyFastShareLeft); auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize); if (QRect(fastShareLeft, fastShareTop, st::historyFastShareSize, st::historyFastShareSize).contains(point)) { result.link = _parent->rightActionLink(); } } } return result; } QSize HistoryVideo::sizeForGrouping() const { const auto width = convertScale(_data->thumb->width()); const auto height = convertScale(_data->thumb->height()); return { std::max(width, 1), std::max(height, 1) }; } void HistoryVideo::drawGrouped( Painter &p, const QRect &clip, TextSelection selection, TimeMs ms, const QRect &geometry, RectParts corners, not_null<uint64*> cacheKey, not_null<QPixmap*> cache) const { _data->automaticLoad(_parent->data()); validateGroupedCache(geometry, corners, cacheKey, cache); const auto selected = (selection == FullSelection); const auto loaded = _data->loaded(); const auto displayLoading = _data->displayLoading(); const auto bubble = _parent->hasBubble(); if (displayLoading) { ensureAnimation(); if (!_animation->radial.animating()) { _animation->radial.start(_data->progress()); } } const auto radial = isRadialAnimation(ms); if (!bubble) { // App::roundShadow(p, 0, 0, paintw, painth, selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners); } p.drawPixmap(geometry.topLeft(), *cache); if (selected) { const auto roundRadius = ImageRoundRadius::Large; App::complexOverlayRect(p, geometry, roundRadius, corners); } const auto radialOpacity = (radial && loaded && !_data->uploading()) ? _animation->radial.opacity() : 1.; const auto radialSize = st::historyGroupRadialSize; const auto inner = QRect( geometry.x() + (geometry.width() - radialSize) / 2, geometry.y() + (geometry.height() - radialSize) / 2, radialSize, radialSize); p.setPen(Qt::NoPen); if (selected) { p.setBrush(st::msgDateImgBgSelected); } else if (isThumbAnimation(ms)) { auto over = _animation->a_thumbOver.current(); p.setBrush(anim::brush(st::msgDateImgBg, st::msgDateImgBgOver, over)); } else { auto over = ClickHandler::showAsActive(_data->loading() ? _cancell : _savel); p.setBrush(over ? st::msgDateImgBgOver : st::msgDateImgBg); } p.setOpacity(radialOpacity * p.opacity()); { PainterHighQualityEnabler hq(p); p.drawEllipse(inner); } p.setOpacity(radialOpacity); auto icon = [&]() -> const style::icon * { if (_data->waitingForAlbum()) { return &(selected ? st::historyFileThumbWaitingSelected : st::historyFileThumbWaiting); } else if (loaded && !radial) { return &(selected ? st::historyFileThumbPlaySelected : st::historyFileThumbPlay); } else if (radial || _data->loading()) { if (_parent->data()->id > 0 || _data->uploading()) { return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel); } return nullptr; } return &(selected ? st::historyFileThumbDownloadSelected : st::historyFileThumbDownload); }(); if (icon) { icon->paintInCenter(p, inner); } p.setOpacity(1); if (radial) { const auto line = st::historyGroupRadialLine; const auto rinner = inner.marginsRemoved({ line, line, line, line }); const auto color = selected ? st::historyFileThumbRadialFgSelected : st::historyFileThumbRadialFg; _animation->radial.draw(p, rinner, line, color); } } TextState HistoryVideo::getStateGrouped( const QRect &geometry, QPoint point, StateRequest request) const { if (!geometry.contains(point)) { return {}; } return TextState(_parent, _data->uploading() ? _cancell : _data->loaded() ? _openl : _data->loading() ? _cancell : _savel); } float64 HistoryVideo::dataProgress() const { return _data->progress(); } bool HistoryVideo::dataFinished() const { return !_data->loading() && (!_data->uploading() || _data->waitingForAlbum()); } bool HistoryVideo::dataLoaded() const { return _data->loaded(); } void HistoryVideo::validateGroupedCache( const QRect &geometry, RectParts corners, not_null<uint64*> cacheKey, not_null<QPixmap*> cache) const { using Option = Images::Option; const auto loaded = _data->thumb->loaded(); const auto loadLevel = loaded ? 1 : 0; const auto width = geometry.width(); const auto height = geometry.height(); const auto options = Option::Smooth | Option::RoundedLarge | Option::Blurred | ((corners & RectPart::TopLeft) ? Option::RoundedTopLeft : Option::None) | ((corners & RectPart::TopRight) ? Option::RoundedTopRight : Option::None) | ((corners & RectPart::BottomLeft) ? Option::RoundedBottomLeft : Option::None) | ((corners & RectPart::BottomRight) ? Option::RoundedBottomRight : Option::None); const auto key = (uint64(width) << 48) | (uint64(height) << 32) | (uint64(options) << 16) | (uint64(loadLevel)); if (*cacheKey == key) { return; } const auto originalWidth = convertScale(_data->thumb->width()); const auto originalHeight = convertScale(_data->thumb->height()); const auto pixSize = Ui::GetImageScaleSizeForGeometry( { originalWidth, originalHeight }, { width, height }); const auto pixWidth = pixSize.width() * cIntRetinaFactor(); const auto pixHeight = pixSize.height() * cIntRetinaFactor(); const auto &image = _data->thumb; *cacheKey = key; *cache = image->pixNoCache(pixWidth, pixHeight, options, width, height); } void HistoryVideo::setStatusSize(int newSize) const { HistoryFileMedia::setStatusSize(newSize, _data->size, _data->duration(), 0); } TextWithEntities HistoryVideo::selectedText(TextSelection selection) const { return _caption.originalTextWithEntities(selection, ExpandLinksAll); } bool HistoryVideo::needsBubble() const { if (!_caption.isEmpty()) { return true; } const auto item = _parent->data(); return item->viaBot() || item->Has<HistoryMessageReply>() || _parent->displayForwardedFrom() || _parent->displayFromName(); return false; } void HistoryVideo::parentTextUpdated() { _caption = (_parent->media() == this) ? createCaption(_parent->data()) : Text(); Auth().data().requestViewResize(_parent); } void HistoryVideo::updateStatusText() const { auto showPause = false; auto statusSize = 0; auto realDuration = 0; if (_data->status == FileDownloadFailed || _data->status == FileUploadFailed) { statusSize = FileStatusSizeFailed; } else if (_data->uploading()) { statusSize = _data->uploadingData->offset; } else if (_data->loading()) { statusSize = _data->loadOffset(); } else if (_data->loaded()) { statusSize = FileStatusSizeLoaded; } else { statusSize = FileStatusSizeReady; } if (statusSize != _statusSize) { setStatusSize(statusSize); } } HistoryDocument::HistoryDocument( not_null<Element*> parent, not_null<DocumentData*> document) : HistoryFileMedia(parent) , _data(document) { const auto item = parent->data(); auto caption = createCaption(item); createComponents(!caption.isEmpty()); if (auto named = Get<HistoryDocumentNamed>()) { fillNamedFromData(named); } setDocumentLinks(_data, item); setStatusSize(FileStatusSizeReady); if (auto captioned = Get<HistoryDocumentCaptioned>()) { captioned->_caption = std::move(caption); } } float64 HistoryDocument::dataProgress() const { return _data->progress(); } bool HistoryDocument::dataFinished() const { return !_data->loading() && !_data->uploading(); } bool HistoryDocument::dataLoaded() const { return _data->loaded(); } void HistoryDocument::createComponents(bool caption) { uint64 mask = 0; if (_data->isVoiceMessage()) { mask |= HistoryDocumentVoice::Bit(); } else { mask |= HistoryDocumentNamed::Bit(); if (!_data->isSong() && !documentIsExecutableName(_data->filename()) && !_data->thumb->isNull() && _data->thumb->width() && _data->thumb->height()) { mask |= HistoryDocumentThumbed::Bit(); } } if (caption) { mask |= HistoryDocumentCaptioned::Bit(); } UpdateComponents(mask); if (auto thumbed = Get<HistoryDocumentThumbed>()) { thumbed->_linksavel = std::make_shared<DocumentSaveClickHandler>( _data, _parent->data()->fullId()); thumbed->_linkcancell = std::make_shared<DocumentCancelClickHandler>( _data, _parent->data()->fullId()); } if (auto voice = Get<HistoryDocumentVoice>()) { voice->_seekl = std::make_shared<VoiceSeekClickHandler>( _data, _parent->data()->fullId()); } } void HistoryDocument::fillNamedFromData(HistoryDocumentNamed *named) { auto nameString = named->_name = _data->composeNameString(); named->_namew = st::semiboldFont->width(nameString); } QSize HistoryDocument::countOptimalSize() { const auto item = _parent->data(); auto captioned = Get<HistoryDocumentCaptioned>(); if (_parent->media() != this) { if (captioned) { RemoveComponents(HistoryDocumentCaptioned::Bit()); captioned = nullptr; } } else if (captioned && captioned->_caption.hasSkipBlock()) { captioned->_caption.updateSkipBlock( _parent->skipBlockWidth(), _parent->skipBlockHeight()); } auto thumbed = Get<HistoryDocumentThumbed>(); if (thumbed) { _data->thumb->load(); auto tw = convertScale(_data->thumb->width()); auto th = convertScale(_data->thumb->height()); if (tw > th) { thumbed->_thumbw = (tw * st::msgFileThumbSize) / th; } else { thumbed->_thumbw = st::msgFileThumbSize; } } auto maxWidth = st::msgFileMinWidth; auto tleft = 0; auto tright = 0; if (thumbed) { tleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right(); tright = st::msgFileThumbPadding.left(); accumulate_max(maxWidth, tleft + documentMaxStatusWidth(_data) + tright); } else { tleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right(); tright = st::msgFileThumbPadding.left(); auto unread = _data->isVoiceMessage() ? (st::mediaUnreadSkip + st::mediaUnreadSize) : 0; accumulate_max(maxWidth, tleft + documentMaxStatusWidth(_data) + unread + _parent->skipBlockWidth() + st::msgPadding.right()); } if (auto named = Get<HistoryDocumentNamed>()) { accumulate_max(maxWidth, tleft + named->_namew + tright); accumulate_min(maxWidth, st::msgMaxWidth); } auto minHeight = 0; if (thumbed) { minHeight = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom(); } else { minHeight = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom(); } if (!captioned && (item->Has<HistoryMessageSigned>() || item->Has<HistoryMessageViews>() || _parent->displayEditedBadge())) { minHeight += st::msgDateFont->height - st::msgDateDelta.y(); } if (!isBubbleTop()) { minHeight -= st::msgFileTopMinus; } if (captioned) { auto captionw = maxWidth - st::msgPadding.left() - st::msgPadding.right(); minHeight += captioned->_caption.countHeight(captionw); if (isBubbleBottom()) { minHeight += st::msgPadding.bottom(); } } return { maxWidth, minHeight }; } QSize HistoryDocument::countCurrentSize(int newWidth) { auto captioned = Get<HistoryDocumentCaptioned>(); if (!captioned) { return HistoryFileMedia::countCurrentSize(newWidth); } accumulate_min(newWidth, maxWidth()); auto newHeight = 0; if (Get<HistoryDocumentThumbed>()) { newHeight = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom(); } else { newHeight = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom(); } if (!isBubbleTop()) { newHeight -= st::msgFileTopMinus; } auto captionw = newWidth - st::msgPadding.left() - st::msgPadding.right(); newHeight += captioned->_caption.countHeight(captionw); if (isBubbleBottom()) { newHeight += st::msgPadding.bottom(); } return { newWidth, newHeight }; } void HistoryDocument::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const { if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; _data->automaticLoad(_parent->data()); bool loaded = _data->loaded(), displayLoading = _data->displayLoading(); bool selected = (selection == FullSelection); int captionw = width() - st::msgPadding.left() - st::msgPadding.right(); auto outbg = _parent->hasOutLayout(); if (displayLoading) { ensureAnimation(); if (!_animation->radial.animating()) { _animation->radial.start(_data->progress()); } } bool showPause = updateStatusText(); bool radial = isRadialAnimation(ms); auto topMinus = isBubbleTop() ? 0 : st::msgFileTopMinus; int nameleft = 0, nametop = 0, nameright = 0, statustop = 0, linktop = 0, bottom = 0; if (auto thumbed = Get<HistoryDocumentThumbed>()) { nameleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right(); nametop = st::msgFileThumbNameTop - topMinus; nameright = st::msgFileThumbPadding.left(); statustop = st::msgFileThumbStatusTop - topMinus; linktop = st::msgFileThumbLinkTop - topMinus; bottom = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom() - topMinus; auto inWebPage = (_parent->media() != this); auto roundRadius = inWebPage ? ImageRoundRadius::Small : ImageRoundRadius::Large; QRect rthumb(rtlrect(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top() - topMinus, st::msgFileThumbSize, st::msgFileThumbSize, width())); QPixmap thumb; if (loaded) { thumb = _data->thumb->pixSingle(thumbed->_thumbw, 0, st::msgFileThumbSize, st::msgFileThumbSize, roundRadius); } else { thumb = _data->thumb->pixBlurredSingle(thumbed->_thumbw, 0, st::msgFileThumbSize, st::msgFileThumbSize, roundRadius); } p.drawPixmap(rthumb.topLeft(), thumb); if (selected) { auto overlayCorners = inWebPage ? SelectedOverlaySmallCorners : SelectedOverlayLargeCorners; App::roundRect(p, rthumb, p.textPalette().selectOverlay, overlayCorners); } if (radial || (!loaded && !_data->loading())) { float64 radialOpacity = (radial && loaded && !_data->uploading()) ? _animation->radial.opacity() : 1; QRect inner(rthumb.x() + (rthumb.width() - st::msgFileSize) / 2, rthumb.y() + (rthumb.height() - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize); p.setPen(Qt::NoPen); if (selected) { p.setBrush(st::msgDateImgBgSelected); } else if (isThumbAnimation(ms)) { auto over = _animation->a_thumbOver.current(); p.setBrush(anim::brush(st::msgDateImgBg, st::msgDateImgBgOver, over)); } else { auto over = ClickHandler::showAsActive(_data->loading() ? _cancell : _savel); p.setBrush(over ? st::msgDateImgBgOver : st::msgDateImgBg); } p.setOpacity(radialOpacity * p.opacity()); { PainterHighQualityEnabler hq(p); p.drawEllipse(inner); } p.setOpacity(radialOpacity); auto icon = ([radial, this, selected] { if (radial || _data->loading()) { return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel); } return &(selected ? st::historyFileThumbDownloadSelected : st::historyFileThumbDownload); })(); p.setOpacity((radial && loaded) ? _animation->radial.opacity() : 1); icon->paintInCenter(p, inner); if (radial) { p.setOpacity(1); QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine))); _animation->radial.draw(p, rinner, st::msgFileRadialLine, selected ? st::historyFileThumbRadialFgSelected : st::historyFileThumbRadialFg); } } if (_data->status != FileUploadFailed) { const auto &lnk = (_data->loading() || _data->uploading()) ? thumbed->_linkcancell : thumbed->_linksavel; bool over = ClickHandler::showAsActive(lnk); p.setFont(over ? st::semiboldFont->underline() : st::semiboldFont); p.setPen(outbg ? (selected ? st::msgFileThumbLinkOutFgSelected : st::msgFileThumbLinkOutFg) : (selected ? st::msgFileThumbLinkInFgSelected : st::msgFileThumbLinkInFg)); p.drawTextLeft(nameleft, linktop, width(), thumbed->_link, thumbed->_linkw); } } else { nameleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right(); nametop = st::msgFileNameTop - topMinus; nameright = st::msgFilePadding.left(); statustop = st::msgFileStatusTop - topMinus; bottom = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom() - topMinus; QRect inner(rtlrect(st::msgFilePadding.left(), st::msgFilePadding.top() - topMinus, st::msgFileSize, st::msgFileSize, width())); p.setPen(Qt::NoPen); if (selected) { p.setBrush(outbg ? st::msgFileOutBgSelected : st::msgFileInBgSelected); } else if (isThumbAnimation(ms)) { auto over = _animation->a_thumbOver.current(); p.setBrush(anim::brush(outbg ? st::msgFileOutBg : st::msgFileInBg, outbg ? st::msgFileOutBgOver : st::msgFileInBgOver, over)); } else { auto over = ClickHandler::showAsActive(_data->loading() ? _cancell : _savel); p.setBrush(outbg ? (over ? st::msgFileOutBgOver : st::msgFileOutBg) : (over ? st::msgFileInBgOver : st::msgFileInBg)); } { PainterHighQualityEnabler hq(p); p.drawEllipse(inner); } if (radial) { QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine))); auto fg = outbg ? (selected ? st::historyFileOutRadialFgSelected : st::historyFileOutRadialFg) : (selected ? st::historyFileInRadialFgSelected : st::historyFileInRadialFg); _animation->radial.draw(p, rinner, st::msgFileRadialLine, fg); } auto icon = ([showPause, radial, this, loaded, outbg, selected] { if (showPause) { return &(outbg ? (selected ? st::historyFileOutPauseSelected : st::historyFileOutPause) : (selected ? st::historyFileInPauseSelected : st::historyFileInPause)); } else if (radial || _data->loading()) { return &(outbg ? (selected ? st::historyFileOutCancelSelected : st::historyFileOutCancel) : (selected ? st::historyFileInCancelSelected : st::historyFileInCancel)); } else if (loaded) { if (_data->isAudioFile() || _data->isVoiceMessage()) { return &(outbg ? (selected ? st::historyFileOutPlaySelected : st::historyFileOutPlay) : (selected ? st::historyFileInPlaySelected : st::historyFileInPlay)); } else if (_data->isImage()) { return &(outbg ? (selected ? st::historyFileOutImageSelected : st::historyFileOutImage) : (selected ? st::historyFileInImageSelected : st::historyFileInImage)); } return &(outbg ? (selected ? st::historyFileOutDocumentSelected : st::historyFileOutDocument) : (selected ? st::historyFileInDocumentSelected : st::historyFileInDocument)); } return &(outbg ? (selected ? st::historyFileOutDownloadSelected : st::historyFileOutDownload) : (selected ? st::historyFileInDownloadSelected : st::historyFileInDownload)); })(); icon->paintInCenter(p, inner); } auto namewidth = width() - nameleft - nameright; auto statuswidth = namewidth; auto voiceStatusOverride = QString(); if (auto voice = Get<HistoryDocumentVoice>()) { const VoiceWaveform *wf = nullptr; uchar norm_value = 0; if (const auto voiceData = _data->voice()) { wf = &voiceData->waveform; if (wf->isEmpty()) { wf = nullptr; if (loaded) { Local::countVoiceWaveform(_data); } } else if (wf->at(0) < 0) { wf = nullptr; } else { norm_value = voiceData->wavemax; } } auto progress = ([voice] { if (voice->seeking()) { return voice->seekingCurrent(); } else if (voice->_playback) { return voice->_playback->a_progress.current(); } return 0.; })(); if (voice->seeking()) { voiceStatusOverride = formatPlayedText(qRound(progress * voice->_lastDurationMs) / 1000, voice->_lastDurationMs / 1000); } // rescale waveform by going in waveform.size * bar_count 1D grid auto active = outbg ? (selected ? st::msgWaveformOutActiveSelected : st::msgWaveformOutActive) : (selected ? st::msgWaveformInActiveSelected : st::msgWaveformInActive); auto inactive = outbg ? (selected ? st::msgWaveformOutInactiveSelected : st::msgWaveformOutInactive) : (selected ? st::msgWaveformInInactiveSelected : st::msgWaveformInInactive); auto wf_size = wf ? wf->size() : Media::Player::kWaveformSamplesCount; auto availw = namewidth + st::msgWaveformSkip; auto activew = qRound(availw * progress); if (!outbg && !voice->_playback && _parent->data()->isMediaUnread()) { activew = availw; } auto bar_count = qMin(availw / (st::msgWaveformBar + st::msgWaveformSkip), wf_size); auto max_value = 0; auto max_delta = st::msgWaveformMax - st::msgWaveformMin; auto bottom = st::msgFilePadding.top() - topMinus + st::msgWaveformMax; p.setPen(Qt::NoPen); for (auto i = 0, bar_x = 0, sum_i = 0; i < wf_size; ++i) { auto value = wf ? wf->at(i) : 0; if (sum_i + bar_count >= wf_size) { // draw bar sum_i = sum_i + bar_count - wf_size; if (sum_i < (bar_count + 1) / 2) { if (max_value < value) max_value = value; } auto bar_value = ((max_value * max_delta) + ((norm_value + 1) / 2)) / (norm_value + 1); if (bar_x >= activew) { p.fillRect(nameleft + bar_x, bottom - bar_value, st::msgWaveformBar, st::msgWaveformMin + bar_value, inactive); } else if (bar_x + st::msgWaveformBar <= activew) { p.fillRect(nameleft + bar_x, bottom - bar_value, st::msgWaveformBar, st::msgWaveformMin + bar_value, active); } else { p.fillRect(nameleft + bar_x, bottom - bar_value, activew - bar_x, st::msgWaveformMin + bar_value, active); p.fillRect(nameleft + activew, bottom - bar_value, st::msgWaveformBar - (activew - bar_x), st::msgWaveformMin + bar_value, inactive); } bar_x += st::msgWaveformBar + st::msgWaveformSkip; if (sum_i < (bar_count + 1) / 2) { max_value = 0; } else { max_value = value; } } else { if (max_value < value) max_value = value; sum_i += bar_count; } } } else if (auto named = Get<HistoryDocumentNamed>()) { p.setFont(st::semiboldFont); p.setPen(outbg ? (selected ? st::historyFileNameOutFgSelected : st::historyFileNameOutFg) : (selected ? st::historyFileNameInFgSelected : st::historyFileNameInFg)); if (namewidth < named->_namew) { p.drawTextLeft(nameleft, nametop, width(), st::semiboldFont->elided(named->_name, namewidth, Qt::ElideMiddle)); } else { p.drawTextLeft(nameleft, nametop, width(), named->_name, named->_namew); } } auto statusText = voiceStatusOverride.isEmpty() ? _statusText : voiceStatusOverride; auto status = outbg ? (selected ? st::mediaOutFgSelected : st::mediaOutFg) : (selected ? st::mediaInFgSelected : st::mediaInFg); p.setFont(st::normalFont); p.setPen(status); p.drawTextLeft(nameleft, statustop, width(), statusText); if (_parent->data()->isMediaUnread()) { auto w = st::normalFont->width(statusText); if (w + st::mediaUnreadSkip + st::mediaUnreadSize <= statuswidth) { p.setPen(Qt::NoPen); p.setBrush(outbg ? (selected ? st::msgFileOutBgSelected : st::msgFileOutBg) : (selected ? st::msgFileInBgSelected : st::msgFileInBg)); { PainterHighQualityEnabler hq(p); p.drawEllipse(rtlrect(nameleft + w + st::mediaUnreadSkip, statustop + st::mediaUnreadTop, st::mediaUnreadSize, st::mediaUnreadSize, width())); } } } if (auto captioned = Get<HistoryDocumentCaptioned>()) { p.setPen(outbg ? (selected ? st::historyTextOutFgSelected : st::historyTextOutFg) : (selected ? st::historyTextInFgSelected : st::historyTextInFg)); captioned->_caption.draw(p, st::msgPadding.left(), bottom, captionw, style::al_left, 0, -1, selection); } } TextState HistoryDocument::textState(QPoint point, StateRequest request) const { auto result = TextState(_parent); if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) { return result; } bool loaded = _data->loaded(); bool showPause = updateStatusText(); auto nameleft = 0, nametop = 0, nameright = 0, statustop = 0, linktop = 0, bottom = 0; auto topMinus = isBubbleTop() ? 0 : st::msgFileTopMinus; if (auto thumbed = Get<HistoryDocumentThumbed>()) { nameleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right(); nameright = st::msgFileThumbPadding.left(); nametop = st::msgFileThumbNameTop - topMinus; linktop = st::msgFileThumbLinkTop - topMinus; bottom = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom() - topMinus; QRect rthumb(rtlrect(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top() - topMinus, st::msgFileThumbSize, st::msgFileThumbSize, width())); if ((_data->loading() || _data->uploading() || !loaded) && rthumb.contains(point)) { result.link = (_data->loading() || _data->uploading()) ? _cancell : _savel; return result; } if (_data->status != FileUploadFailed) { if (rtlrect(nameleft, linktop, thumbed->_linkw, st::semiboldFont->height, width()).contains(point)) { result.link = (_data->loading() || _data->uploading()) ? thumbed->_linkcancell : thumbed->_linksavel; return result; } } } else { nameleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right(); nameright = st::msgFilePadding.left(); nametop = st::msgFileNameTop - topMinus; bottom = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom() - topMinus; QRect inner(rtlrect(st::msgFilePadding.left(), st::msgFilePadding.top() - topMinus, st::msgFileSize, st::msgFileSize, width())); if ((_data->loading() || _data->uploading() || !loaded) && inner.contains(point)) { result.link = (_data->loading() || _data->uploading()) ? _cancell : _savel; return result; } } if (auto voice = Get<HistoryDocumentVoice>()) { auto namewidth = width() - nameleft - nameright; auto waveformbottom = st::msgFilePadding.top() - topMinus + st::msgWaveformMax + st::msgWaveformMin; if (QRect(nameleft, nametop, namewidth, waveformbottom - nametop).contains(point)) { auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice); if (state.id == AudioMsgId(_data, _parent->data()->fullId()) && !Media::Player::IsStoppedOrStopping(state.state)) { if (!voice->seeking()) { voice->setSeekingStart((point.x() - nameleft) / float64(namewidth)); } result.link = voice->_seekl; return result; } } } auto painth = height(); if (auto captioned = Get<HistoryDocumentCaptioned>()) { if (point.y() >= bottom) { result = TextState(_parent, captioned->_caption.getState( point - QPoint(st::msgPadding.left(), bottom), width() - st::msgPadding.left() - st::msgPadding.right(), request.forText())); return result; } auto captionw = width() - st::msgPadding.left() - st::msgPadding.right(); painth -= captioned->_caption.countHeight(captionw); if (isBubbleBottom()) { painth -= st::msgPadding.bottom(); } } if (QRect(0, 0, width(), painth).contains(point) && !_data->loading() && !_data->uploading() && _data->isValid()) { result.link = _openl; return result; } return result; } void HistoryDocument::updatePressed(QPoint point) { if (auto voice = Get<HistoryDocumentVoice>()) { if (voice->seeking()) { auto nameleft = 0, nameright = 0; if (auto thumbed = Get<HistoryDocumentThumbed>()) { nameleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right(); nameright = st::msgFileThumbPadding.left(); } else { nameleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right(); nameright = st::msgFilePadding.left(); } voice->setSeekingCurrent(snap((point.x() - nameleft) / float64(width() - nameleft - nameright), 0., 1.)); Auth().data().requestViewRepaint(_parent); } } } TextSelection HistoryDocument::adjustSelection( TextSelection selection, TextSelectType type) const { if (const auto captioned = Get<HistoryDocumentCaptioned>()) { return captioned->_caption.adjustSelection(selection, type); } return selection; } uint16 HistoryDocument::fullSelectionLength() const { if (const auto captioned = Get<HistoryDocumentCaptioned>()) { return captioned->_caption.length(); } return 0; } bool HistoryDocument::hasTextForCopy() const { return Has<HistoryDocumentCaptioned>(); } TextWithEntities HistoryDocument::selectedText(TextSelection selection) const { if (const auto captioned = Get<HistoryDocumentCaptioned>()) { const auto &caption = captioned->_caption; return caption.originalTextWithEntities(selection, ExpandLinksAll); } return TextWithEntities(); } void HistoryDocument::setStatusSize(int newSize, qint64 realDuration) const { auto duration = _data->isSong() ? _data->song()->duration : (_data->isVoiceMessage() ? _data->voice()->duration : -1); HistoryFileMedia::setStatusSize(newSize, _data->size, duration, realDuration); if (auto thumbed = Get<HistoryDocumentThumbed>()) { if (_statusSize == FileStatusSizeReady) { thumbed->_link = lang(lng_media_download).toUpper(); } else if (_statusSize == FileStatusSizeLoaded) { thumbed->_link = lang(lng_media_open_with).toUpper(); } else if (_statusSize == FileStatusSizeFailed) { thumbed->_link = lang(lng_media_download).toUpper(); } else if (_statusSize >= 0) { thumbed->_link = lang(lng_media_cancel).toUpper(); } else { thumbed->_link = lang(lng_media_open_with).toUpper(); } thumbed->_linkw = st::semiboldFont->width(thumbed->_link); } } bool HistoryDocument::updateStatusText() const { auto showPause = false; auto statusSize = 0; auto realDuration = 0; if (_data->status == FileDownloadFailed || _data->status == FileUploadFailed) { statusSize = FileStatusSizeFailed; } else if (_data->uploading()) { statusSize = _data->uploadingData->offset; } else if (_data->loading()) { statusSize = _data->loadOffset(); } else if (_data->loaded()) { using State = Media::Player::State; statusSize = FileStatusSizeLoaded; if (_data->isVoiceMessage()) { auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice); if (state.id == AudioMsgId(_data, _parent->data()->fullId()) && !Media::Player::IsStoppedOrStopping(state.state)) { if (auto voice = Get<HistoryDocumentVoice>()) { bool was = (voice->_playback != nullptr); voice->ensurePlayback(this); if (!was || state.position != voice->_playback->_position) { auto prg = state.length ? snap(float64(state.position) / state.length, 0., 1.) : 0.; if (voice->_playback->_position < state.position) { voice->_playback->a_progress.start(prg); } else { voice->_playback->a_progress = anim::value(0., prg); } voice->_playback->_position = state.position; voice->_playback->_a_progress.start(); } voice->_lastDurationMs = static_cast<int>((state.length * 1000LL) / state.frequency); // Bad :( } statusSize = -1 - (state.position / state.frequency); realDuration = (state.length / state.frequency); showPause = (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting); } else { if (auto voice = Get<HistoryDocumentVoice>()) { voice->checkPlaybackFinished(); } } if (!showPause && (state.id == AudioMsgId(_data, _parent->data()->fullId()))) { showPause = Media::Player::instance()->isSeeking(AudioMsgId::Type::Voice); } } else if (_data->isAudioFile()) { auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Song); if (state.id == AudioMsgId(_data, _parent->data()->fullId()) && !Media::Player::IsStoppedOrStopping(state.state)) { statusSize = -1 - (state.position / state.frequency); realDuration = (state.length / state.frequency); showPause = (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting); } else { } if (!showPause && (state.id == AudioMsgId(_data, _parent->data()->fullId()))) { showPause = Media::Player::instance()->isSeeking(AudioMsgId::Type::Song); } } } else { statusSize = FileStatusSizeReady; } if (statusSize != _statusSize) { setStatusSize(statusSize, realDuration); } return showPause; } QMargins HistoryDocument::bubbleMargins() const { return Get<HistoryDocumentThumbed>() ? QMargins(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top(), st::msgFileThumbPadding.left(), st::msgFileThumbPadding.bottom()) : st::msgPadding; } void HistoryDocument::step_voiceProgress(float64 ms, bool timer) { if (auto voice = Get<HistoryDocumentVoice>()) { if (voice->_playback) { float64 dt = ms / (2 * AudioVoiceMsgUpdateView); if (dt >= 1) { voice->_playback->_a_progress.stop(); voice->_playback->a_progress.finish(); } else { voice->_playback->a_progress.update(qMin(dt, 1.), anim::linear); } if (timer) { Auth().data().requestViewRepaint(_parent); } } } } void HistoryDocument::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) { if (auto voice = Get<HistoryDocumentVoice>()) { if (pressed && p == voice->_seekl && !voice->seeking()) { voice->startSeeking(); } else if (!pressed && voice->seeking()) { auto type = AudioMsgId::Type::Voice; auto state = Media::Player::mixer()->currentState(type); if (state.id == AudioMsgId(_data, _parent->data()->fullId()) && state.length) { auto currentProgress = voice->seekingCurrent(); auto currentPosition = state.frequency ? qRound(currentProgress * state.length * 1000. / state.frequency) : 0; Media::Player::mixer()->seek(type, currentPosition); voice->ensurePlayback(this); voice->_playback->_position = 0; voice->_playback->a_progress = anim::value(currentProgress, currentProgress); } voice->stopSeeking(); } } HistoryFileMedia::clickHandlerPressedChanged(p, pressed); } void HistoryDocument::refreshParentId(not_null<HistoryItem*> realParent) { HistoryFileMedia::refreshParentId(realParent); const auto fullId = realParent->fullId(); if (auto thumbed = Get<HistoryDocumentThumbed>()) { if (thumbed->_linksavel) { thumbed->_linksavel->setMessageId(fullId); thumbed->_linkcancell->setMessageId(fullId); } } if (auto voice = Get<HistoryDocumentVoice>()) { if (voice->_seekl) { voice->_seekl->setMessageId(fullId); } } } void HistoryDocument::parentTextUpdated() { auto caption = (_parent->media() == this) ? createCaption(_parent->data()) : Text(); if (!caption.isEmpty()) { AddComponents(HistoryDocumentCaptioned::Bit()); auto captioned = Get<HistoryDocumentCaptioned>(); captioned->_caption = std::move(caption); } else { RemoveComponents(HistoryDocumentCaptioned::Bit()); } Auth().data().requestViewResize(_parent); } TextWithEntities HistoryDocument::getCaption() const { if (const auto captioned = Get<HistoryDocumentCaptioned>()) { return captioned->_caption.originalTextWithEntities(); } return TextWithEntities(); } HistoryGif::HistoryGif( not_null<Element*> parent, not_null<DocumentData*> document) : HistoryFileMedia(parent) , _data(document) , _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) { const auto item = parent->data(); setDocumentLinks(_data, item, true); setStatusSize(FileStatusSizeReady); _caption = createCaption(item); _data->thumb->load(); } QSize HistoryGif::countOptimalSize() { if (_parent->media() != this) { _caption = Text(); } else if (_caption.hasSkipBlock()) { _caption.updateSkipBlock( _parent->skipBlockWidth(), _parent->skipBlockHeight()); } if (!_openInMediaviewLink) { _openInMediaviewLink = std::make_shared<DocumentOpenClickHandler>( _data, _parent->data()->fullId()); } auto tw = 0; auto th = 0; if (_gif && _gif->state() == Media::Clip::State::Error) { if (!_gif->autoplay()) { Ui::show(Box<InformBox>(lang(lng_gif_error))); } setClipReader(Media::Clip::ReaderPointer::Bad()); } const auto reader = currentReader(); if (reader) { tw = convertScale(reader->width()); th = convertScale(reader->height()); } else { tw = convertScale(_data->dimensions.width()), th = convertScale(_data->dimensions.height()); if (!tw || !th) { tw = convertScale(_data->thumb->width()); th = convertScale(_data->thumb->height()); } } if (tw > st::maxGifSize) { th = (st::maxGifSize * th) / tw; tw = st::maxGifSize; } if (th > st::maxGifSize) { tw = (st::maxGifSize * tw) / th; th = st::maxGifSize; } if (!tw || !th) { tw = th = 1; } _thumbw = tw; _thumbh = th; auto maxWidth = qMax(tw, st::minPhotoSize); auto minHeight = qMax(th, st::minPhotoSize); accumulate_max(maxWidth, _parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); if (!reader) { accumulate_max(maxWidth, gifMaxStatusWidth(_data) + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); } if (_parent->hasBubble()) { if (!_caption.isEmpty()) { auto captionw = maxWidth - st::msgPadding.left() - st::msgPadding.right(); minHeight += st::mediaCaptionSkip + _caption.countHeight(captionw); if (isBubbleBottom()) { minHeight += st::msgPadding.bottom(); } } } else if (isSeparateRoundVideo()) { const auto item = _parent->data(); auto via = item->Get<HistoryMessageVia>(); auto reply = item->Get<HistoryMessageReply>(); auto forwarded = item->Get<HistoryMessageForwarded>(); if (forwarded) { forwarded->create(via); } maxWidth += additionalWidth(via, reply, forwarded); } return { maxWidth, minHeight }; } QSize HistoryGif::countCurrentSize(int newWidth) { auto availableWidth = newWidth; int tw = 0, th = 0; const auto reader = currentReader(); if (reader) { tw = convertScale(reader->width()); th = convertScale(reader->height()); } else { tw = convertScale(_data->dimensions.width()), th = convertScale(_data->dimensions.height()); if (!tw || !th) { tw = convertScale(_data->thumb->width()); th = convertScale(_data->thumb->height()); } } if (tw > st::maxGifSize) { th = (st::maxGifSize * th) / tw; tw = st::maxGifSize; } if (th > st::maxGifSize) { tw = (st::maxGifSize * tw) / th; th = st::maxGifSize; } if (!tw || !th) { tw = th = 1; } if (newWidth < tw) { th = qRound((newWidth / float64(tw)) * th); tw = newWidth; } _thumbw = tw; _thumbh = th; newWidth = qMax(tw, st::minPhotoSize); auto newHeight = qMax(th, st::minPhotoSize); accumulate_max(newWidth, _parent->infoWidth() + 2 * st::msgDateImgDelta + st::msgDateImgPadding.x()); if (reader) { const auto own = (reader->mode() == Media::Clip::Reader::Mode::Gif); if (own && !reader->started()) { auto isRound = _data->isVideoMessage(); auto inWebPage = (_parent->media() != this); auto roundRadius = isRound ? ImageRoundRadius::Ellipse : inWebPage ? ImageRoundRadius::Small : ImageRoundRadius::Large; auto roundCorners = (isRound || inWebPage) ? RectPart::AllCorners : ((isBubbleTop() ? (RectPart::TopLeft | RectPart::TopRight) : RectPart::None) | ((isBubbleBottom() && _caption.isEmpty()) ? (RectPart::BottomLeft | RectPart::BottomRight) : RectPart::None)); reader->start( _thumbw, _thumbh, newWidth, newHeight, roundRadius, roundCorners); } } else { accumulate_max(newWidth, gifMaxStatusWidth(_data) + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); } if (_parent->hasBubble()) { if (!_caption.isEmpty()) { auto captionw = newWidth - st::msgPadding.left() - st::msgPadding.right(); newHeight += st::mediaCaptionSkip + _caption.countHeight(captionw); if (isBubbleBottom()) { newHeight += st::msgPadding.bottom(); } } } else if (isSeparateRoundVideo()) { const auto item = _parent->data(); auto via = item->Get<HistoryMessageVia>(); auto reply = item->Get<HistoryMessageReply>(); auto forwarded = item->Get<HistoryMessageForwarded>(); if (via || reply || forwarded) { auto additional = additionalWidth(via, reply, forwarded); newWidth += additional; accumulate_min(newWidth, availableWidth); auto usew = maxWidth() - additional; auto availw = newWidth - usew - st::msgReplyPadding.left() - st::msgReplyPadding.left() - st::msgReplyPadding.left(); if (!forwarded && via) { via->resize(availw); } if (reply) { reply->resize(availw); } } } return { newWidth, newHeight }; } void HistoryGif::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const { if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; const auto item = _parent->data(); _data->automaticLoad(item); auto loaded = _data->loaded(); auto displayLoading = (item->id < 0) || _data->displayLoading(); auto selected = (selection == FullSelection); if (loaded && cAutoPlayGif() && !_gif && !_gif.isBad() && !activeRoundVideo()) { _parent->delegate()->elementAnimationAutoplayAsync(_parent); } auto paintx = 0, painty = 0, paintw = width(), painth = height(); bool bubble = _parent->hasBubble(); auto outbg = _parent->hasOutLayout(); auto inWebPage = (_parent->media() != this); auto captionw = paintw - st::msgPadding.left() - st::msgPadding.right(); auto isRound = _data->isVideoMessage(); auto displayMute = false; const auto reader = currentReader(); const auto playingVideo = reader ? (reader->mode() == Media::Clip::Reader::Mode::Video) : false; const auto animating = reader && reader->started(); if (!animating || item->id < 0) { if (displayLoading) { ensureAnimation(); if (!_animation->radial.animating()) { _animation->radial.start(dataProgress()); } } updateStatusText(); } else if (playingVideo) { updateStatusText(); } auto radial = isRadialAnimation(ms); if (bubble) { if (!_caption.isEmpty()) { painth -= st::mediaCaptionSkip + _caption.countHeight(captionw); if (isBubbleBottom()) { painth -= st::msgPadding.bottom(); } } } else if (!isRound) { App::roundShadow(p, 0, 0, paintw, height(), selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners); } auto usex = 0, usew = paintw; auto separateRoundVideo = isSeparateRoundVideo(); auto via = separateRoundVideo ? item->Get<HistoryMessageVia>() : nullptr; auto reply = separateRoundVideo ? item->Get<HistoryMessageReply>() : nullptr; auto forwarded = separateRoundVideo ? item->Get<HistoryMessageForwarded>() : nullptr; if (via || reply || forwarded) { usew = maxWidth() - additionalWidth(via, reply, forwarded); if (outbg) { usex = width() - usew; } } if (rtl()) usex = width() - usex - usew; QRect rthumb(rtlrect(usex + paintx, painty, usew, painth, width())); auto roundRadius = isRound ? ImageRoundRadius::Ellipse : inWebPage ? ImageRoundRadius::Small : ImageRoundRadius::Large; auto roundCorners = (isRound || inWebPage) ? RectPart::AllCorners : ((isBubbleTop() ? (RectPart::TopLeft | RectPart::TopRight) : RectPart::None) | ((isBubbleBottom() && _caption.isEmpty()) ? (RectPart::BottomLeft | RectPart::BottomRight) : RectPart::None)); if (animating) { auto paused = App::wnd()->controller()->isGifPausedAtLeastFor(Window::GifPauseReason::Any); if (isRound) { if (playingVideo) { paused = false; } else { displayMute = true; } } p.drawPixmap(rthumb.topLeft(), reader->current(_thumbw, _thumbh, usew, painth, roundRadius, roundCorners, paused ? 0 : ms)); if (const auto playback = videoPlayback()) { const auto value = playback->value(ms); if (value > 0.) { auto pen = st::historyVideoMessageProgressFg->p; auto was = p.pen(); pen.setWidth(st::radialLine); pen.setCapStyle(Qt::RoundCap); p.setPen(pen); p.setOpacity(st::historyVideoMessageProgressOpacity); auto from = QuarterArcLength; auto len = -qRound(FullArcLength * value); auto stepInside = st::radialLine / 2; { PainterHighQualityEnabler hq(p); p.drawArc(rthumb.marginsRemoved(QMargins(stepInside, stepInside, stepInside, stepInside)), from, len); } p.setPen(was); p.setOpacity(1.); } } } else { p.drawPixmap(rthumb.topLeft(), _data->thumb->pixBlurredSingle(_thumbw, _thumbh, usew, painth, roundRadius, roundCorners)); } if (selected) { App::complexOverlayRect(p, rthumb, roundRadius, roundCorners); } if (radial || (!reader && (_gif.isBad() || (!loaded && !_data->loading()) || !cAutoPlayGif()))) { auto radialOpacity = (radial && loaded && item->id > 0) ? _animation->radial.opacity() : 1.; auto inner = QRect(rthumb.x() + (rthumb.width() - st::msgFileSize) / 2, rthumb.y() + (rthumb.height() - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize); p.setPen(Qt::NoPen); if (selected) { p.setBrush(st::msgDateImgBgSelected); } else if (isThumbAnimation(ms)) { auto over = _animation->a_thumbOver.current(); p.setBrush(anim::brush(st::msgDateImgBg, st::msgDateImgBgOver, over)); } else { auto over = ClickHandler::showAsActive(_data->loading() ? _cancell : _savel); p.setBrush(over ? st::msgDateImgBgOver : st::msgDateImgBg); } p.setOpacity(radialOpacity * p.opacity()); { PainterHighQualityEnabler hq(p); p.drawEllipse(inner); } p.setOpacity(radialOpacity); auto icon = [&]() -> const style::icon * { if (_data->loaded() && !radial) { return &(selected ? st::historyFileThumbPlaySelected : st::historyFileThumbPlay); } else if (radial || _data->loading()) { if (item->id > 0 || _data->uploading()) { return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel); } return nullptr; } return &(selected ? st::historyFileThumbDownloadSelected : st::historyFileThumbDownload); }(); if (icon) { icon->paintInCenter(p, inner); } if (radial) { p.setOpacity(1); QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine))); _animation->radial.draw(p, rinner, st::msgFileRadialLine, selected ? st::historyFileThumbRadialFgSelected : st::historyFileThumbRadialFg); } if (!isRound && (!animating || item->id < 0)) { auto statusX = paintx + st::msgDateImgDelta + st::msgDateImgPadding.x(); auto statusY = painty + st::msgDateImgDelta + st::msgDateImgPadding.y(); auto statusW = st::normalFont->width(_statusText) + 2 * st::msgDateImgPadding.x(); auto statusH = st::normalFont->height + 2 * st::msgDateImgPadding.y(); App::roundRect(p, rtlrect(statusX - st::msgDateImgPadding.x(), statusY - st::msgDateImgPadding.y(), statusW, statusH, width()), selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? DateSelectedCorners : DateCorners); p.setFont(st::normalFont); p.setPen(st::msgDateImgFg); p.drawTextLeft(statusX, statusY, width(), _statusText, statusW - 2 * st::msgDateImgPadding.x()); } } if (displayMute) { auto muteRect = rtlrect(rthumb.x() + (rthumb.width() - st::historyVideoMessageMuteSize) / 2, rthumb.y() + st::msgDateImgDelta, st::historyVideoMessageMuteSize, st::historyVideoMessageMuteSize, width()); p.setPen(Qt::NoPen); p.setBrush(selected ? st::msgDateImgBgSelected : st::msgDateImgBg); PainterHighQualityEnabler hq(p); p.drawEllipse(muteRect); (selected ? st::historyVideoMessageMuteSelected : st::historyVideoMessageMute).paintInCenter(p, muteRect); } if (!inWebPage && isRound) { auto mediaUnread = item->isMediaUnread(); auto statusW = st::normalFont->width(_statusText) + 2 * st::msgDateImgPadding.x(); auto statusH = st::normalFont->height + 2 * st::msgDateImgPadding.y(); auto statusX = usex + paintx + st::msgDateImgDelta + st::msgDateImgPadding.x(); auto statusY = painty + painth - st::msgDateImgDelta - statusH + st::msgDateImgPadding.y(); if (item->isMediaUnread()) { statusW += st::mediaUnreadSkip + st::mediaUnreadSize; } App::roundRect(p, rtlrect(statusX - st::msgDateImgPadding.x(), statusY - st::msgDateImgPadding.y(), statusW, statusH, width()), selected ? st::msgServiceBgSelected : st::msgServiceBg, selected ? StickerSelectedCorners : StickerCorners); p.setFont(st::normalFont); p.setPen(st::msgServiceFg); p.drawTextLeft(statusX, statusY, width(), _statusText, statusW - 2 * st::msgDateImgPadding.x()); if (mediaUnread) { p.setPen(Qt::NoPen); p.setBrush(st::msgServiceFg); { PainterHighQualityEnabler hq(p); p.drawEllipse(rtlrect(statusX - st::msgDateImgPadding.x() + statusW - st::msgDateImgPadding.x() - st::mediaUnreadSize, statusY + st::mediaUnreadTop, st::mediaUnreadSize, st::mediaUnreadSize, width())); } } if (via || reply || forwarded) { auto rectw = width() - usew - st::msgReplyPadding.left(); auto innerw = rectw - (st::msgReplyPadding.left() + st::msgReplyPadding.right()); auto recth = st::msgReplyPadding.top() + st::msgReplyPadding.bottom(); auto forwardedHeightReal = forwarded ? forwarded->text.countHeight(innerw) : 0; auto forwardedHeight = qMin(forwardedHeightReal, kMaxGifForwardedBarLines * st::msgServiceNameFont->height); if (forwarded) { recth += forwardedHeight; } else if (via) { recth += st::msgServiceNameFont->height + (reply ? st::msgReplyPadding.top() : 0); } if (reply) { recth += st::msgReplyBarSize.height(); } int rectx = outbg ? 0 : (usew + st::msgReplyPadding.left()); int recty = painty; if (rtl()) rectx = width() - rectx - rectw; App::roundRect(p, rectx, recty, rectw, recth, selected ? st::msgServiceBgSelected : st::msgServiceBg, selected ? StickerSelectedCorners : StickerCorners); p.setPen(st::msgServiceFg); rectx += st::msgReplyPadding.left(); rectw = innerw; if (forwarded) { p.setTextPalette(st::serviceTextPalette); auto breakEverywhere = (forwardedHeightReal > forwardedHeight); forwarded->text.drawElided(p, rectx, recty + st::msgReplyPadding.top(), rectw, kMaxGifForwardedBarLines, style::al_left, 0, -1, 0, breakEverywhere); p.restoreTextPalette(); } else if (via) { p.setFont(st::msgDateFont); p.drawTextLeft(rectx, recty + st::msgReplyPadding.top(), 2 * rectx + rectw, via->text); int skip = st::msgServiceNameFont->height + (reply ? st::msgReplyPadding.top() : 0); recty += skip; } if (reply) { HistoryMessageReply::PaintFlags flags = 0; if (selected) { flags |= HistoryMessageReply::PaintFlag::Selected; } reply->paint(p, _parent, rectx, recty, rectw, flags); } } } if (!isRound && !_caption.isEmpty()) { p.setPen(outbg ? (selected ? st::historyTextOutFgSelected : st::historyTextOutFg) : (selected ? st::historyTextInFgSelected : st::historyTextInFg)); _caption.draw(p, st::msgPadding.left(), painty + painth + st::mediaCaptionSkip, captionw, style::al_left, 0, -1, selection); } else if (!inWebPage) { auto fullRight = paintx + usex + usew; auto fullBottom = painty + painth; auto maxRight = _parent->width() - st::msgMargin.left(); if (_parent->hasFromPhoto()) { maxRight -= st::msgMargin.right(); } else { maxRight -= st::msgMargin.left(); } if (isRound && !outbg) { auto infoWidth = _parent->infoWidth(); // This is just some arbitrary point, // the main idea is to make info left aligned here. fullRight += infoWidth - st::normalFont->height; if (fullRight > maxRight) { fullRight = maxRight; } } if (isRound || needInfoDisplay()) { _parent->drawInfo(p, fullRight, fullBottom, 2 * paintx + paintw, selected, isRound ? InfoDisplayType::Background : InfoDisplayType::Image); } if (!bubble && _parent->displayRightAction()) { auto fastShareLeft = (fullRight + st::historyFastShareLeft); auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize); if (fastShareLeft + st::historyFastShareSize > maxRight) { fastShareLeft = (fullRight - st::historyFastShareSize - st::msgDateImgDelta); fastShareTop -= (st::msgDateImgDelta + st::msgDateImgPadding.y() + st::msgDateFont->height + st::msgDateImgPadding.y()); } _parent->drawRightAction(p, fastShareLeft, fastShareTop, 2 * paintx + paintw); } } } TextState HistoryGif::textState(QPoint point, StateRequest request) const { auto result = TextState(_parent); if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) { return result; } auto paintx = 0, painty = 0, paintw = width(), painth = height(); auto bubble = _parent->hasBubble(); if (bubble && !_caption.isEmpty()) { auto captionw = paintw - st::msgPadding.left() - st::msgPadding.right(); painth -= _caption.countHeight(captionw); if (isBubbleBottom()) { painth -= st::msgPadding.bottom(); } if (QRect(st::msgPadding.left(), painth, captionw, height() - painth).contains(point)) { result = TextState(_parent, _caption.getState( point - QPoint(st::msgPadding.left(), painth), captionw, request.forText())); return result; } painth -= st::mediaCaptionSkip; } auto outbg = _parent->hasOutLayout(); auto inWebPage = (_parent->media() != this); auto isRound = _data->isVideoMessage(); auto usew = paintw, usex = 0; auto separateRoundVideo = isSeparateRoundVideo(); const auto item = _parent->data(); auto via = separateRoundVideo ? item->Get<HistoryMessageVia>() : nullptr; auto reply = separateRoundVideo ? item->Get<HistoryMessageReply>() : nullptr; auto forwarded = separateRoundVideo ? item->Get<HistoryMessageForwarded>() : nullptr; if (via || reply || forwarded) { usew = maxWidth() - additionalWidth(via, reply, forwarded); if (outbg) { usex = width() - usew; } } if (rtl()) usex = width() - usex - usew; if (via || reply || forwarded) { auto rectw = paintw - usew - st::msgReplyPadding.left(); auto innerw = rectw - (st::msgReplyPadding.left() + st::msgReplyPadding.right()); auto recth = st::msgReplyPadding.top() + st::msgReplyPadding.bottom(); auto forwardedHeightReal = forwarded ? forwarded->text.countHeight(innerw) : 0; auto forwardedHeight = qMin(forwardedHeightReal, kMaxGifForwardedBarLines * st::msgServiceNameFont->height); if (forwarded) { recth += forwardedHeight; } else if (via) { recth += st::msgServiceNameFont->height + (reply ? st::msgReplyPadding.top() : 0); } if (reply) { recth += st::msgReplyBarSize.height(); } auto rectx = outbg ? 0 : (usew + st::msgReplyPadding.left()); auto recty = painty; if (rtl()) rectx = width() - rectx - rectw; if (forwarded) { if (QRect(rectx, recty, rectw, st::msgReplyPadding.top() + forwardedHeight).contains(point)) { auto breakEverywhere = (forwardedHeightReal > forwardedHeight); auto textRequest = request.forText(); if (breakEverywhere) { textRequest.flags |= Text::StateRequest::Flag::BreakEverywhere; } result = TextState(_parent, forwarded->text.getState( point - QPoint(rectx + st::msgReplyPadding.left(), recty + st::msgReplyPadding.top()), innerw, textRequest)); result.symbol = 0; result.afterSymbol = false; if (breakEverywhere) { result.cursor = CursorState::Forwarded; } else { result.cursor = CursorState::None; } return result; } recty += forwardedHeight; recth -= forwardedHeight; } else if (via) { auto viah = st::msgReplyPadding.top() + st::msgServiceNameFont->height + (reply ? 0 : st::msgReplyPadding.bottom()); if (QRect(rectx, recty, rectw, viah).contains(point)) { result.link = via->link; return result; } auto skip = st::msgServiceNameFont->height + (reply ? 2 * st::msgReplyPadding.top() : 0); recty += skip; recth -= skip; } if (reply) { if (QRect(rectx, recty, rectw, recth).contains(point)) { result.link = reply->replyToLink(); return result; } } } if (QRect(usex + paintx, painty, usew, painth).contains(point)) { if (_data->uploading()) { result.link = _cancell; } else if (!_gif || !cAutoPlayGif() || _data->isVideoMessage()) { result.link = _data->loaded() ? _openl : (_data->loading() ? _cancell : _savel); } else { result.link = _openInMediaviewLink; } } if (isRound || _caption.isEmpty()) { auto fullRight = usex + paintx + usew; auto fullBottom = painty + painth; auto maxRight = _parent->width() - st::msgMargin.left(); if (_parent->hasFromPhoto()) { maxRight -= st::msgMargin.right(); } else { maxRight -= st::msgMargin.left(); } if (isRound && !outbg) { auto infoWidth = _parent->infoWidth(); // This is just some arbitrary point, // the main idea is to make info left aligned here. fullRight += infoWidth - st::normalFont->height; if (fullRight > maxRight) { fullRight = maxRight; } } if (!inWebPage) { if (_parent->pointInTime(fullRight, fullBottom, point, isRound ? InfoDisplayType::Background : InfoDisplayType::Image)) { result.cursor = CursorState::Date; } } if (!bubble && _parent->displayRightAction()) { auto fastShareLeft = (fullRight + st::historyFastShareLeft); auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize); if (fastShareLeft + st::historyFastShareSize > maxRight) { fastShareLeft = (fullRight - st::historyFastShareSize - st::msgDateImgDelta); fastShareTop -= st::msgDateImgDelta + st::msgDateImgPadding.y() + st::msgDateFont->height + st::msgDateImgPadding.y(); } if (QRect(fastShareLeft, fastShareTop, st::historyFastShareSize, st::historyFastShareSize).contains(point)) { result.link = _parent->rightActionLink(); } } } return result; } TextWithEntities HistoryGif::selectedText(TextSelection selection) const { return _caption.originalTextWithEntities(selection, ExpandLinksAll); } bool HistoryGif::needsBubble() const { if (_data->isVideoMessage()) { return false; } if (!_caption.isEmpty()) { return true; } const auto item = _parent->data(); return item->viaBot() || item->Has<HistoryMessageReply>() || _parent->displayForwardedFrom() || _parent->displayFromName(); return false; } int HistoryGif::additionalWidth() const { const auto item = _parent->data(); return additionalWidth( item->Get<HistoryMessageVia>(), item->Get<HistoryMessageReply>(), item->Get<HistoryMessageForwarded>()); } QString HistoryGif::mediaTypeString() const { return _data->isVideoMessage() ? lang(lng_in_dlg_video_message) : qsl("GIF"); } bool HistoryGif::isSeparateRoundVideo() const { return _data->isVideoMessage() && (_parent->media() == this) && !_parent->hasBubble(); } void HistoryGif::setStatusSize(int newSize) const { if (_data->isVideoMessage()) { _statusSize = newSize; if (newSize < 0) { _statusText = formatDurationText(-newSize - 1); } else { _statusText = formatDurationText(_data->duration()); } } else { HistoryFileMedia::setStatusSize(newSize, _data->size, -2, 0); } } void HistoryGif::updateStatusText() const { auto showPause = false; auto statusSize = 0; auto realDuration = 0; if (_data->status == FileDownloadFailed || _data->status == FileUploadFailed) { statusSize = FileStatusSizeFailed; } else if (_data->uploading()) { statusSize = _data->uploadingData->offset; } else if (_data->loading()) { statusSize = _data->loadOffset(); } else if (_data->loaded()) { statusSize = FileStatusSizeLoaded; if (const auto video = activeRoundPlayer()) { statusSize = -1 - _data->duration(); const auto state = Media::Player::mixer()->currentState( AudioMsgId::Type::Voice); if (state.id == video->audioMsgId() && state.length) { auto position = int64(0); if (Media::Player::IsStoppedAtEnd(state.state)) { position = state.length; } else if (!Media::Player::IsStoppedOrStopping(state.state)) { position = state.position; } accumulate_max(statusSize, -1 - int((state.length - position) / state.frequency + 1)); } } } else { statusSize = FileStatusSizeReady; } if (statusSize != _statusSize) { setStatusSize(statusSize); } } void HistoryGif::refreshParentId(not_null<HistoryItem*> realParent) { HistoryFileMedia::refreshParentId(realParent); const auto fullId = realParent->fullId(); if (_openInMediaviewLink) { _openInMediaviewLink->setMessageId(fullId); } } QString HistoryGif::additionalInfoString() const { if (_data->isVideoMessage()) { updateStatusText(); return _statusText; } return QString(); } void HistoryGif::parentTextUpdated() { _caption = (_parent->media() == this) ? createCaption(_parent->data()) : Text(); Auth().data().requestViewResize(_parent); } int HistoryGif::additionalWidth(const HistoryMessageVia *via, const HistoryMessageReply *reply, const HistoryMessageForwarded *forwarded) const { int result = 0; if (forwarded) { accumulate_max(result, st::msgReplyPadding.left() + st::msgReplyPadding.left() + forwarded->text.maxWidth() + st::msgReplyPadding.right()); } else if (via) { accumulate_max(result, st::msgReplyPadding.left() + st::msgReplyPadding.left() + via->maxWidth + st::msgReplyPadding.left()); } if (reply) { accumulate_max(result, st::msgReplyPadding.left() + reply->replyToWidth()); } return result; } Media::Player::RoundController *HistoryGif::activeRoundVideo() const { return App::wnd()->controller()->roundVideo(_parent->data()); } Media::Clip::Reader *HistoryGif::activeRoundPlayer() const { if (const auto video = activeRoundVideo()) { if (const auto result = video->reader()) { if (result->ready()) { return result; } } } return nullptr; } Media::Clip::Reader *HistoryGif::currentReader() const { if (const auto result = activeRoundPlayer()) { return result; } return (_gif && _gif->ready()) ? _gif.get() : nullptr; } Media::Clip::Playback *HistoryGif::videoPlayback() const { if (const auto video = activeRoundVideo()) { return video->playback(); } return nullptr; } void HistoryGif::clipCallback(Media::Clip::Notification notification) { using namespace Media::Clip; const auto reader = _gif.get(); if (!reader) { return; } switch (notification) { case NotificationReinit: { auto stopped = false; if (reader->autoPausedGif()) { auto amVisible = false; Auth().data().queryItemVisibility().notify( { _parent->data(), &amVisible }, true); if (!amVisible) { // Stop animation if it is not visible. stopAnimation(); stopped = true; } } if (!stopped) { Auth().data().requestViewResize(_parent); } } break; case NotificationRepaint: { if (!reader->currentDisplayed()) { Auth().data().requestViewRepaint(_parent); } } break; } } void HistoryGif::playAnimation(bool autoplay) { if (_data->isVideoMessage() && !autoplay) { return; } else if (_gif && autoplay) { return; } using Mode = Media::Clip::Reader::Mode; if (_gif) { stopAnimation(); } else if (_data->loaded(DocumentData::FilePathResolveChecked)) { if (!cAutoPlayGif()) { Auth().data().stopAutoplayAnimations(); } setClipReader(Media::Clip::MakeReader( _data, _parent->data()->fullId(), [=](auto notification) { clipCallback(notification); }, Mode::Gif)); if (_gif && autoplay) { _gif->setAutoplay(); } } } void HistoryGif::stopAnimation() { if (_gif) { clearClipReader(); Auth().data().requestViewResize(_parent); _data->forget(); } } void HistoryGif::setClipReader(Media::Clip::ReaderPointer gif) { if (_gif) { Auth().data().unregisterAutoplayAnimation(_gif.get()); } _gif = std::move(gif); if (_gif) { Auth().data().registerAutoplayAnimation(_gif.get(), _parent); } } HistoryGif::~HistoryGif() { clearClipReader(); } float64 HistoryGif::dataProgress() const { return (_data->uploading() || _parent->data()->id > 0) ? _data->progress() : 0; } bool HistoryGif::dataFinished() const { return (_parent->data()->id > 0) ? (!_data->loading() && !_data->uploading()) : false; } bool HistoryGif::dataLoaded() const { return (_parent->data()->id > 0) ? _data->loaded() : false; } bool HistoryGif::needInfoDisplay() const { return (_data->uploading() || _parent->isUnderCursor()); } HistorySticker::HistorySticker( not_null<Element*> parent, not_null<DocumentData*> document) : HistoryMedia(parent) , _data(document) , _emoji(_data->sticker()->alt) { _data->thumb->load(); if (auto emoji = Ui::Emoji::Find(_emoji)) { _emoji = emoji->text(); } } QSize HistorySticker::countOptimalSize() { auto sticker = _data->sticker(); if (!_packLink && sticker && sticker->set.type() != mtpc_inputStickerSetEmpty) { _packLink = std::make_shared<LambdaClickHandler>([document = _data] { if (auto sticker = document->sticker()) { if (sticker->set.type() != mtpc_inputStickerSetEmpty && App::main()) { App::main()->stickersBox(sticker->set); } } }); } _pixw = _data->dimensions.width(); _pixh = _data->dimensions.height(); if (_pixw > st::maxStickerSize) { _pixh = (st::maxStickerSize * _pixh) / _pixw; _pixw = st::maxStickerSize; } if (_pixh > st::maxStickerSize) { _pixw = (st::maxStickerSize * _pixw) / _pixh; _pixh = st::maxStickerSize; } if (_pixw < 1) _pixw = 1; if (_pixh < 1) _pixh = 1; auto maxWidth = qMax(_pixw, st::minPhotoSize); auto minHeight = qMax(_pixh, st::minPhotoSize); if (_parent->media() == this) { maxWidth += additionalWidth(); } return { maxWidth, minHeight }; } QSize HistorySticker::countCurrentSize(int newWidth) { const auto item = _parent->data(); accumulate_min(newWidth, maxWidth()); if (_parent->media() == this) { auto via = item->Get<HistoryMessageVia>(); auto reply = item->Get<HistoryMessageReply>(); if (via || reply) { int usew = maxWidth() - additionalWidth(via, reply); int availw = newWidth - usew - st::msgReplyPadding.left() - st::msgReplyPadding.left() - st::msgReplyPadding.left(); if (via) { via->resize(availw); } if (reply) { reply->resize(availw); } } } return { newWidth, minHeight() }; } void HistorySticker::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const { auto sticker = _data->sticker(); if (!sticker) return; if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; _data->checkSticker(); bool loaded = _data->loaded(); bool selected = (selection == FullSelection); auto outbg = _parent->hasOutLayout(); auto inWebPage = (_parent->media() != this); const auto item = _parent->data(); int usew = maxWidth(), usex = 0; auto via = inWebPage ? nullptr : item->Get<HistoryMessageVia>(); auto reply = inWebPage ? nullptr : item->Get<HistoryMessageReply>(); if (via || reply) { usew -= additionalWidth(via, reply); if (outbg) { usex = width() - usew; } } if (rtl()) usex = width() - usex - usew; if (selected) { if (sticker->img->isNull()) { p.drawPixmap(QPoint(usex + (usew - _pixw) / 2, (minHeight() - _pixh) / 2), _data->thumb->pixBlurredColored(st::msgStickerOverlay, _pixw, _pixh)); } else { p.drawPixmap(QPoint(usex + (usew - _pixw) / 2, (minHeight() - _pixh) / 2), sticker->img->pixColored(st::msgStickerOverlay, _pixw, _pixh)); } } else { if (sticker->img->isNull()) { p.drawPixmap(QPoint(usex + (usew - _pixw) / 2, (minHeight() - _pixh) / 2), _data->thumb->pixBlurred(_pixw, _pixh)); } else { p.drawPixmap(QPoint(usex + (usew - _pixw) / 2, (minHeight() - _pixh) / 2), sticker->img->pix(_pixw, _pixh)); } } if (!inWebPage) { auto fullRight = usex + usew; auto fullBottom = height(); _parent->drawInfo(p, fullRight, fullBottom, usex * 2 + usew, selected, InfoDisplayType::Background); if (via || reply) { int rectw = width() - usew - st::msgReplyPadding.left(); int recth = st::msgReplyPadding.top() + st::msgReplyPadding.bottom(); if (via) { recth += st::msgServiceNameFont->height + (reply ? st::msgReplyPadding.top() : 0); } if (reply) { recth += st::msgReplyBarSize.height(); } int rectx = outbg ? 0 : (usew + st::msgReplyPadding.left()); int recty = st::msgDateImgDelta; if (rtl()) rectx = width() - rectx - rectw; App::roundRect(p, rectx, recty, rectw, recth, selected ? st::msgServiceBgSelected : st::msgServiceBg, selected ? StickerSelectedCorners : StickerCorners); p.setPen(st::msgServiceFg); rectx += st::msgReplyPadding.left(); rectw -= st::msgReplyPadding.left() + st::msgReplyPadding.right(); if (via) { p.setFont(st::msgDateFont); p.drawTextLeft(rectx, recty + st::msgReplyPadding.top(), 2 * rectx + rectw, via->text); int skip = st::msgServiceNameFont->height + (reply ? st::msgReplyPadding.top() : 0); recty += skip; } if (reply) { HistoryMessageReply::PaintFlags flags = 0; if (selected) { flags |= HistoryMessageReply::PaintFlag::Selected; } reply->paint(p, _parent, rectx, recty, rectw, flags); } } if (_parent->displayRightAction()) { auto fastShareLeft = (fullRight + st::historyFastShareLeft); auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize); _parent->drawRightAction(p, fastShareLeft, fastShareTop, 2 * usex + usew); } } } TextState HistorySticker::textState(QPoint point, StateRequest request) const { auto result = TextState(_parent); if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) { return result; } auto outbg = _parent->hasOutLayout(); auto inWebPage = (_parent->media() != this); const auto item = _parent->data(); int usew = maxWidth(), usex = 0; auto via = inWebPage ? nullptr : item->Get<HistoryMessageVia>(); auto reply = inWebPage ? nullptr : item->Get<HistoryMessageReply>(); if (via || reply) { usew -= additionalWidth(via, reply); if (outbg) { usex = width() - usew; } } if (rtl()) usex = width() - usex - usew; if (via || reply) { int rectw = width() - usew - st::msgReplyPadding.left(); int recth = st::msgReplyPadding.top() + st::msgReplyPadding.bottom(); if (via) { recth += st::msgServiceNameFont->height + (reply ? st::msgReplyPadding.top() : 0); } if (reply) { recth += st::msgReplyBarSize.height(); } int rectx = outbg ? 0 : (usew + st::msgReplyPadding.left()); int recty = st::msgDateImgDelta; if (rtl()) rectx = width() - rectx - rectw; if (via) { int viah = st::msgReplyPadding.top() + st::msgServiceNameFont->height + (reply ? 0 : st::msgReplyPadding.bottom()); if (QRect(rectx, recty, rectw, viah).contains(point)) { result.link = via->link; return result; } int skip = st::msgServiceNameFont->height + (reply ? 2 * st::msgReplyPadding.top() : 0); recty += skip; recth -= skip; } if (reply) { if (QRect(rectx, recty, rectw, recth).contains(point)) { result.link = reply->replyToLink(); return result; } } } if (_parent->media() == this) { auto fullRight = usex + usew; auto fullBottom = height(); if (_parent->pointInTime(fullRight, fullBottom, point, InfoDisplayType::Image)) { result.cursor = CursorState::Date; } if (_parent->displayRightAction()) { auto fastShareLeft = (fullRight + st::historyFastShareLeft); auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize); if (QRect(fastShareLeft, fastShareTop, st::historyFastShareSize, st::historyFastShareSize).contains(point)) { result.link = _parent->rightActionLink(); } } } auto pixLeft = usex + (usew - _pixw) / 2; auto pixTop = (minHeight() - _pixh) / 2; if (QRect(pixLeft, pixTop, _pixw, _pixh).contains(point)) { result.link = _packLink; return result; } return result; } int HistorySticker::additionalWidth(const HistoryMessageVia *via, const HistoryMessageReply *reply) const { int result = 0; if (via) { accumulate_max(result, st::msgReplyPadding.left() + st::msgReplyPadding.left() + via->maxWidth + st::msgReplyPadding.left()); } if (reply) { accumulate_max(result, st::msgReplyPadding.left() + reply->replyToWidth()); } return result; } int HistorySticker::additionalWidth() const { const auto item = _parent->data(); return additionalWidth( item->Get<HistoryMessageVia>(), item->Get<HistoryMessageReply>()); } namespace { ClickHandlerPtr sendMessageClickHandler(PeerData *peer) { return std::make_shared<LambdaClickHandler>([peer] { App::wnd()->controller()->showPeerHistory( peer->id, Window::SectionShow::Way::Forward); }); } ClickHandlerPtr addContactClickHandler(not_null<HistoryItem*> item) { return std::make_shared<LambdaClickHandler>([fullId = item->fullId()] { if (const auto item = App::histItemById(fullId)) { if (const auto media = item->media()) { if (const auto contact = media->sharedContact()) { Ui::show(Box<AddContactBox>( contact->firstName, contact->lastName, contact->phoneNumber)); } } } }); } } // namespace HistoryContact::HistoryContact( not_null<Element*> parent, UserId userId, const QString &first, const QString &last, const QString &phone) : HistoryMedia(parent) , _userId(userId) , _fname(first) , _lname(last) , _phone(App::formatPhone(phone)) { Auth().data().registerContactView(userId, parent); _name.setText( st::semiboldTextStyle, lng_full_name(lt_first_name, first, lt_last_name, last).trimmed(), Ui::NameTextOptions()); _phonew = st::normalFont->width(_phone); } HistoryContact::~HistoryContact() { Auth().data().unregisterContactView(_userId, _parent); } void HistoryContact::updateSharedContactUserId(UserId userId) { if (_userId != userId) { Auth().data().unregisterContactView(_userId, _parent); _userId = userId; Auth().data().registerContactView(_userId, _parent); } } QSize HistoryContact::countOptimalSize() { const auto item = _parent->data(); auto maxWidth = st::msgFileMinWidth; _contact = _userId ? App::userLoaded(_userId) : nullptr; if (_contact) { _contact->loadUserpic(); } else { _photoEmpty = std::make_unique<Ui::EmptyUserpic>( Data::PeerUserpicColor(_userId ? _userId : _parent->data()->id), _name.originalText()); } if (_contact && _contact->contactStatus() == UserData::ContactStatus::Contact) { _linkl = sendMessageClickHandler(_contact); _link = lang(lng_profile_send_message).toUpper(); } else if (_userId) { _linkl = addContactClickHandler(_parent->data()); _link = lang(lng_profile_add_contact).toUpper(); } _linkw = _link.isEmpty() ? 0 : st::semiboldFont->width(_link); auto tleft = 0; auto tright = 0; if (_userId) { tleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right(); tright = st::msgFileThumbPadding.left(); accumulate_max(maxWidth, tleft + _phonew + tright); } else { tleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right(); tright = st::msgFileThumbPadding.left(); accumulate_max(maxWidth, tleft + _phonew + _parent->skipBlockWidth() + st::msgPadding.right()); } accumulate_max(maxWidth, tleft + _name.maxWidth() + tright); accumulate_min(maxWidth, st::msgMaxWidth); auto minHeight = 0; if (_userId) { minHeight = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom(); if (item->Has<HistoryMessageSigned>() || item->Has<HistoryMessageViews>()) { minHeight += st::msgDateFont->height - st::msgDateDelta.y(); } } else { minHeight = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom(); } if (!isBubbleTop()) { minHeight -= st::msgFileTopMinus; } return { maxWidth, minHeight }; } void HistoryContact::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const { if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; auto paintx = 0, painty = 0, paintw = width(), painth = height(); auto outbg = _parent->hasOutLayout(); bool selected = (selection == FullSelection); accumulate_min(paintw, maxWidth()); auto nameleft = 0, nametop = 0, nameright = 0, statustop = 0, linktop = 0; auto topMinus = isBubbleTop() ? 0 : st::msgFileTopMinus; if (_userId) { nameleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right(); nametop = st::msgFileThumbNameTop - topMinus; nameright = st::msgFileThumbPadding.left(); statustop = st::msgFileThumbStatusTop - topMinus; linktop = st::msgFileThumbLinkTop - topMinus; QRect rthumb(rtlrect(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top() - topMinus, st::msgFileThumbSize, st::msgFileThumbSize, paintw)); if (_contact) { _contact->paintUserpic(p, rthumb.x(), rthumb.y(), st::msgFileThumbSize); } else { _photoEmpty->paint(p, st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top() - topMinus, paintw, st::msgFileThumbSize); } if (selected) { PainterHighQualityEnabler hq(p); p.setBrush(p.textPalette().selectOverlay); p.setPen(Qt::NoPen); p.drawEllipse(rthumb); } bool over = ClickHandler::showAsActive(_linkl); p.setFont(over ? st::semiboldFont->underline() : st::semiboldFont); p.setPen(outbg ? (selected ? st::msgFileThumbLinkOutFgSelected : st::msgFileThumbLinkOutFg) : (selected ? st::msgFileThumbLinkInFgSelected : st::msgFileThumbLinkInFg)); p.drawTextLeft(nameleft, linktop, paintw, _link, _linkw); } else { nameleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right(); nametop = st::msgFileNameTop - topMinus; nameright = st::msgFilePadding.left(); statustop = st::msgFileStatusTop - topMinus; _photoEmpty->paint(p, st::msgFilePadding.left(), st::msgFilePadding.top() - topMinus, paintw, st::msgFileSize); } auto namewidth = paintw - nameleft - nameright; p.setFont(st::semiboldFont); p.setPen(outbg ? (selected ? st::historyFileNameOutFgSelected : st::historyFileNameOutFg) : (selected ? st::historyFileNameInFgSelected : st::historyFileNameInFg)); _name.drawLeftElided(p, nameleft, nametop, namewidth, paintw); auto &status = outbg ? (selected ? st::mediaOutFgSelected : st::mediaOutFg) : (selected ? st::mediaInFgSelected : st::mediaInFg); p.setFont(st::normalFont); p.setPen(status); p.drawTextLeft(nameleft, statustop, paintw, _phone); } TextState HistoryContact::textState(QPoint point, StateRequest request) const { auto result = TextState(_parent); auto nameleft = 0, nametop = 0, nameright = 0, statustop = 0, linktop = 0; auto topMinus = isBubbleTop() ? 0 : st::msgFileTopMinus; if (_userId) { nameleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right(); linktop = st::msgFileThumbLinkTop - topMinus; if (rtlrect(nameleft, linktop, _linkw, st::semiboldFont->height, width()).contains(point)) { result.link = _linkl; return result; } } if (QRect(0, 0, width(), height()).contains(point) && _contact) { result.link = _contact->openLink(); return result; } return result; } HistoryCall::HistoryCall( not_null<Element*> parent, not_null<Data::Call*> call) : HistoryMedia(parent) { _duration = call->duration; _reason = call->finishReason; const auto item = parent->data(); _text = Data::MediaCall::Text(item, _reason); _status = parent->dateTime().time().toString(cTimeFormat()); if (_duration) { if (_reason != FinishReason::Missed && _reason != FinishReason::Busy) { _status = lng_call_duration_info( lt_time, _status, lt_duration, formatDurationWords(_duration)); } else { _duration = 0; } } } QSize HistoryCall::countOptimalSize() { const auto user = _parent->data()->history()->peer->asUser(); _link = std::make_shared<LambdaClickHandler>([=] { if (user) { Calls::Current().startOutgoingCall(user); } }); auto maxWidth = st::historyCallWidth; auto minHeight = st::historyCallHeight; if (!isBubbleTop()) { minHeight -= st::msgFileTopMinus; } return { maxWidth, minHeight }; } void HistoryCall::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const { if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; auto paintx = 0, painty = 0, paintw = width(), painth = height(); auto outbg = _parent->hasOutLayout(); auto selected = (selection == FullSelection); accumulate_min(paintw, maxWidth()); auto nameleft = 0, nametop = 0, nameright = 0, statustop = 0; auto topMinus = isBubbleTop() ? 0 : st::msgFileTopMinus; nameleft = st::historyCallLeft; nametop = st::historyCallTop - topMinus; nameright = st::msgFilePadding.left(); statustop = st::historyCallStatusTop - topMinus; auto namewidth = paintw - nameleft - nameright; p.setFont(st::semiboldFont); p.setPen(outbg ? (selected ? st::historyFileNameOutFgSelected : st::historyFileNameOutFg) : (selected ? st::historyFileNameInFgSelected : st::historyFileNameInFg)); p.drawTextLeft(nameleft, nametop, paintw, _text); auto statusleft = nameleft; auto missed = (_reason == FinishReason::Missed || _reason == FinishReason::Busy); auto &arrow = outbg ? (selected ? st::historyCallArrowOutSelected : st::historyCallArrowOut) : missed ? (selected ? st::historyCallArrowMissedInSelected : st::historyCallArrowMissedIn) : (selected ? st::historyCallArrowInSelected : st::historyCallArrowIn); arrow.paint(p, statusleft + st::historyCallArrowPosition.x(), statustop + st::historyCallArrowPosition.y(), paintw); statusleft += arrow.width() + st::historyCallStatusSkip; auto &statusFg = outbg ? (selected ? st::mediaOutFgSelected : st::mediaOutFg) : (selected ? st::mediaInFgSelected : st::mediaInFg); p.setFont(st::normalFont); p.setPen(statusFg); p.drawTextLeft(statusleft, statustop, paintw, _status); auto &icon = outbg ? (selected ? st::historyCallOutIconSelected : st::historyCallOutIcon) : (selected ? st::historyCallInIconSelected : st::historyCallInIcon); icon.paint(p, paintw - st::historyCallIconPosition.x() - icon.width(), st::historyCallIconPosition.y() - topMinus, paintw); } TextState HistoryCall::textState(QPoint point, StateRequest request) const { auto result = TextState(_parent); if (QRect(0, 0, width(), height()).contains(point)) { result.link = _link; return result; } return result; } namespace { int articleThumbWidth(PhotoData *thumb, int height) { auto w = thumb->medium->width(); auto h = thumb->medium->height(); return qMax(qMin(height * w / h, height), 1); } int articleThumbHeight(PhotoData *thumb, int width) { return qMax(thumb->medium->height() * width / thumb->medium->width(), 1); } int unitedLineHeight() { return qMax(st::webPageTitleFont->height, st::webPageDescriptionFont->height); } } // namespace HistoryWebPage::HistoryWebPage( not_null<Element*> parent, not_null<WebPageData*> data) : HistoryMedia(parent) , _data(data) , _title(st::msgMinWidth - st::webPageLeft) , _description(st::msgMinWidth - st::webPageLeft) { Auth().data().registerWebPageView(_data, _parent); } QSize HistoryWebPage::countOptimalSize() { if (_data->pendingTill) { return { 0, 0 }; } const auto versionChanged = (_dataVersion != _data->version); if (versionChanged) { _dataVersion = _data->version; _openl = nullptr; _attach = nullptr; _title = Text(st::msgMinWidth - st::webPageLeft); _description = Text(st::msgMinWidth - st::webPageLeft); _siteNameWidth = 0; } auto lineHeight = unitedLineHeight(); if (!_openl && !_data->url.isEmpty()) { _openl = std::make_shared<UrlClickHandler>(_data->url, true); } // init layout auto title = TextUtilities::SingleLine(_data->title.isEmpty() ? _data->author : _data->title); if (!_data->document && _data->photo && _data->type != WebPagePhoto && _data->type != WebPageVideo) { if (_data->type == WebPageProfile) { _asArticle = true; } else if (_data->siteName == qstr("Twitter") || _data->siteName == qstr("Facebook")) { _asArticle = false; } else { _asArticle = true; } if (_asArticle && _data->description.text.isEmpty() && title.isEmpty() && _data->siteName.isEmpty()) { _asArticle = false; } } else { _asArticle = false; } // init attach if (!_attach && !_asArticle) { _attach = CreateAttach(_parent, _data->document, _data->photo); } auto textFloatsAroundInfo = !_asArticle && !_attach && isBubbleBottom(); // init strings if (_description.isEmpty() && !_data->description.text.isEmpty()) { auto text = _data->description; if (textFloatsAroundInfo) { text.text += _parent->skipBlock(); } if (isLogEntryOriginal()) { // Fix layout for small bubbles (narrow media caption edit log entries). _description = Text(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right() - st::webPageLeft); } _description.setMarkedText( st::webPageDescriptionStyle, text, Ui::WebpageTextDescriptionOptions(_data->siteName)); } if (_title.isEmpty() && !title.isEmpty()) { if (textFloatsAroundInfo && _description.isEmpty()) { title += _parent->skipBlock(); } _title.setText( st::webPageTitleStyle, title, Ui::WebpageTextTitleOptions()); } if (!_siteNameWidth && !_data->siteName.isEmpty()) { _siteNameWidth = st::webPageTitleFont->width(_data->siteName); } // init dimensions auto l = st::msgPadding.left() + st::webPageLeft, r = st::msgPadding.right(); auto skipBlockWidth = _parent->skipBlockWidth(); auto maxWidth = skipBlockWidth; auto minHeight = 0; auto siteNameHeight = _data->siteName.isEmpty() ? 0 : lineHeight; auto titleMinHeight = _title.isEmpty() ? 0 : lineHeight; auto descMaxLines = isLogEntryOriginal() ? kMaxOriginalEntryLines : (3 + (siteNameHeight ? 0 : 1) + (titleMinHeight ? 0 : 1)); auto descriptionMinHeight = _description.isEmpty() ? 0 : qMin(_description.minHeight(), descMaxLines * lineHeight); auto articleMinHeight = siteNameHeight + titleMinHeight + descriptionMinHeight; auto articlePhotoMaxWidth = 0; if (_asArticle) { articlePhotoMaxWidth = st::webPagePhotoDelta + qMax(articleThumbWidth(_data->photo, articleMinHeight), lineHeight); } if (_siteNameWidth) { if (_title.isEmpty() && _description.isEmpty()) { accumulate_max(maxWidth, _siteNameWidth + _parent->skipBlockWidth()); } else { accumulate_max(maxWidth, _siteNameWidth + articlePhotoMaxWidth); } minHeight += lineHeight; } if (!_title.isEmpty()) { accumulate_max(maxWidth, _title.maxWidth() + articlePhotoMaxWidth); minHeight += titleMinHeight; } if (!_description.isEmpty()) { accumulate_max(maxWidth, _description.maxWidth() + articlePhotoMaxWidth); minHeight += descriptionMinHeight; } if (_attach) { auto attachAtTop = !_siteNameWidth && _title.isEmpty() && _description.isEmpty(); if (!attachAtTop) minHeight += st::mediaInBubbleSkip; _attach->initDimensions(); auto bubble = _attach->bubbleMargins(); auto maxMediaWidth = _attach->maxWidth() - bubble.left() - bubble.right(); if (isBubbleBottom() && _attach->customInfoLayout()) { maxMediaWidth += skipBlockWidth; } accumulate_max(maxWidth, maxMediaWidth); minHeight += _attach->minHeight() - bubble.top() - bubble.bottom(); if (!_attach->additionalInfoString().isEmpty()) { minHeight += bottomInfoPadding(); } } if (_data->type == WebPageVideo && _data->duration) { _duration = formatDurationText(_data->duration); _durationWidth = st::msgDateFont->width(_duration); } maxWidth += st::msgPadding.left() + st::webPageLeft + st::msgPadding.right(); auto padding = inBubblePadding(); minHeight += padding.top() + padding.bottom(); if (_asArticle) { minHeight = resizeGetHeight(maxWidth); } return { maxWidth, minHeight }; } QSize HistoryWebPage::countCurrentSize(int newWidth) { if (_data->pendingTill) { return { newWidth, minHeight() }; } auto innerWidth = newWidth - st::msgPadding.left() - st::webPageLeft - st::msgPadding.right(); auto newHeight = 0; auto lineHeight = unitedLineHeight(); auto linesMax = isLogEntryOriginal() ? kMaxOriginalEntryLines : 5; auto siteNameLines = _siteNameWidth ? 1 : 0; auto siteNameHeight = _siteNameWidth ? lineHeight : 0; if (_asArticle) { _pixh = linesMax * lineHeight; do { _pixw = articleThumbWidth(_data->photo, _pixh); auto wleft = innerWidth - st::webPagePhotoDelta - qMax(_pixw, lineHeight); newHeight = siteNameHeight; if (_title.isEmpty()) { _titleLines = 0; } else { if (_title.countHeight(wleft) < 2 * st::webPageTitleFont->height) { _titleLines = 1; } else { _titleLines = 2; } newHeight += _titleLines * lineHeight; } auto descriptionHeight = _description.countHeight(wleft); if (descriptionHeight < (linesMax - siteNameLines - _titleLines) * st::webPageDescriptionFont->height) { // We have height for all the lines. _descriptionLines = -1; newHeight += descriptionHeight; } else { _descriptionLines = (linesMax - siteNameLines - _titleLines); newHeight += _descriptionLines * lineHeight; } if (newHeight >= _pixh) { break; } _pixh -= lineHeight; } while (_pixh > lineHeight); newHeight += bottomInfoPadding(); } else { newHeight = siteNameHeight; if (_title.isEmpty()) { _titleLines = 0; } else { if (_title.countHeight(innerWidth) < 2 * st::webPageTitleFont->height) { _titleLines = 1; } else { _titleLines = 2; } newHeight += _titleLines * lineHeight; } if (_description.isEmpty()) { _descriptionLines = 0; } else { auto descriptionHeight = _description.countHeight(innerWidth); if (descriptionHeight < (linesMax - siteNameLines - _titleLines) * st::webPageDescriptionFont->height) { // We have height for all the lines. _descriptionLines = -1; newHeight += descriptionHeight; } else { _descriptionLines = (linesMax - siteNameLines - _titleLines); newHeight += _descriptionLines * lineHeight; } } if (_attach) { auto attachAtTop = !_siteNameWidth && !_titleLines && !_descriptionLines; if (!attachAtTop) newHeight += st::mediaInBubbleSkip; auto bubble = _attach->bubbleMargins(); _attach->resizeGetHeight(innerWidth + bubble.left() + bubble.right()); newHeight += _attach->height() - bubble.top() - bubble.bottom(); if (!_attach->additionalInfoString().isEmpty()) { newHeight += bottomInfoPadding(); } else if (isBubbleBottom() && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > innerWidth + bubble.left() + bubble.right()) { newHeight += bottomInfoPadding(); } } } auto padding = inBubblePadding(); newHeight += padding.top() + padding.bottom(); return { newWidth, newHeight }; } TextSelection HistoryWebPage::toDescriptionSelection( TextSelection selection) const { return HistoryView::UnshiftItemSelection(selection, _title); } TextSelection HistoryWebPage::fromDescriptionSelection( TextSelection selection) const { return HistoryView::ShiftItemSelection(selection, _title); } void HistoryWebPage::refreshParentId(not_null<HistoryItem*> realParent) { if (_attach) { _attach->refreshParentId(realParent); } } void HistoryWebPage::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const { if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; auto paintx = 0, painty = 0, paintw = width(), painth = height(); auto outbg = _parent->hasOutLayout(); bool selected = (selection == FullSelection); auto &barfg = selected ? (outbg ? st::msgOutReplyBarSelColor : st::msgInReplyBarSelColor) : (outbg ? st::msgOutReplyBarColor : st::msgInReplyBarColor); auto &semibold = selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg); auto ®ular = selected ? (outbg ? st::msgOutDateFgSelected : st::msgInDateFgSelected) : (outbg ? st::msgOutDateFg : st::msgInDateFg); QMargins bubble(_attach ? _attach->bubbleMargins() : QMargins()); auto padding = inBubblePadding(); auto tshift = padding.top(); auto bshift = padding.bottom(); paintw -= padding.left() + padding.right(); auto attachAdditionalInfoText = _attach ? _attach->additionalInfoString() : QString(); if (_asArticle) { bshift += bottomInfoPadding(); } else if (!attachAdditionalInfoText.isEmpty()) { bshift += bottomInfoPadding(); } else if (isBubbleBottom() && _attach && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > paintw + bubble.left() + bubble.right()) { bshift += bottomInfoPadding(); } QRect bar(rtlrect(st::msgPadding.left(), tshift, st::webPageBar, height() - tshift - bshift, width())); p.fillRect(bar, barfg); auto lineHeight = unitedLineHeight(); if (_asArticle) { _data->photo->medium->load(false, false); bool full = _data->photo->medium->loaded(); QPixmap pix; auto pw = qMax(_pixw, lineHeight); auto ph = _pixh; auto pixw = _pixw, pixh = articleThumbHeight(_data->photo, _pixw); auto maxw = convertScale(_data->photo->medium->width()), maxh = convertScale(_data->photo->medium->height()); if (pixw * ph != pixh * pw) { float64 coef = (pixw * ph > pixh * pw) ? qMin(ph / float64(pixh), maxh / float64(pixh)) : qMin(pw / float64(pixw), maxw / float64(pixw)); pixh = qRound(pixh * coef); pixw = qRound(pixw * coef); } if (full) { pix = _data->photo->medium->pixSingle(pixw, pixh, pw, ph, ImageRoundRadius::Small); } else { pix = _data->photo->thumb->pixBlurredSingle(pixw, pixh, pw, ph, ImageRoundRadius::Small); } p.drawPixmapLeft(padding.left() + paintw - pw, tshift, width(), pix); if (selected) { App::roundRect(p, rtlrect(padding.left() + paintw - pw, tshift, pw, _pixh, width()), p.textPalette().selectOverlay, SelectedOverlaySmallCorners); } paintw -= pw + st::webPagePhotoDelta; } if (_siteNameWidth) { p.setFont(st::webPageTitleFont); p.setPen(semibold); p.drawTextLeft(padding.left(), tshift, width(), (paintw >= _siteNameWidth) ? _data->siteName : st::webPageTitleFont->elided(_data->siteName, paintw)); tshift += lineHeight; } if (_titleLines) { p.setPen(outbg ? st::webPageTitleOutFg : st::webPageTitleInFg); auto endskip = 0; if (_title.hasSkipBlock()) { endskip = _parent->skipBlockWidth(); } _title.drawLeftElided(p, padding.left(), tshift, paintw, width(), _titleLines, style::al_left, 0, -1, endskip, false, selection); tshift += _titleLines * lineHeight; } if (_descriptionLines) { p.setPen(outbg ? st::webPageDescriptionOutFg : st::webPageDescriptionInFg); auto endskip = 0; if (_description.hasSkipBlock()) { endskip = _parent->skipBlockWidth(); } if (_descriptionLines > 0) { _description.drawLeftElided(p, padding.left(), tshift, paintw, width(), _descriptionLines, style::al_left, 0, -1, endskip, false, toDescriptionSelection(selection)); tshift += _descriptionLines * lineHeight; } else { _description.drawLeft(p, padding.left(), tshift, paintw, width(), style::al_left, 0, -1, toDescriptionSelection(selection)); tshift += _description.countHeight(paintw); } } if (_attach) { auto attachAtTop = !_siteNameWidth && !_titleLines && !_descriptionLines; if (!attachAtTop) tshift += st::mediaInBubbleSkip; auto attachLeft = padding.left() - bubble.left(); auto attachTop = tshift - bubble.top(); if (rtl()) attachLeft = width() - attachLeft - _attach->width(); p.translate(attachLeft, attachTop); auto attachSelection = selected ? FullSelection : TextSelection { 0, 0 }; _attach->draw(p, r.translated(-attachLeft, -attachTop), attachSelection, ms); auto pixwidth = _attach->width(); auto pixheight = _attach->height(); if (_data->type == WebPageVideo && _attach->type() == MediaTypePhoto) { if (_attach->isReadyForOpen()) { if (_data->siteName == qstr("YouTube")) { st::youtubeIcon.paint(p, (pixwidth - st::youtubeIcon.width()) / 2, (pixheight - st::youtubeIcon.height()) / 2, width()); } else { st::videoIcon.paint(p, (pixwidth - st::videoIcon.width()) / 2, (pixheight - st::videoIcon.height()) / 2, width()); } } if (_durationWidth) { auto dateX = pixwidth - _durationWidth - st::msgDateImgDelta - 2 * st::msgDateImgPadding.x(); auto dateY = pixheight - st::msgDateFont->height - 2 * st::msgDateImgPadding.y() - st::msgDateImgDelta; auto dateW = pixwidth - dateX - st::msgDateImgDelta; auto dateH = pixheight - dateY - st::msgDateImgDelta; App::roundRect(p, dateX, dateY, dateW, dateH, selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? DateSelectedCorners : DateCorners); p.setFont(st::msgDateFont); p.setPen(st::msgDateImgFg); p.drawTextLeft(dateX + st::msgDateImgPadding.x(), dateY + st::msgDateImgPadding.y(), pixwidth, _duration); } } p.translate(-attachLeft, -attachTop); if (!attachAdditionalInfoText.isEmpty()) { p.setFont(st::msgDateFont); p.setPen(selected ? (outbg ? st::msgOutDateFgSelected : st::msgInDateFgSelected) : (outbg ? st::msgOutDateFg : st::msgInDateFg)); p.drawTextLeft(st::msgPadding.left(), bar.y() + bar.height() + st::mediaInBubbleSkip, width(), attachAdditionalInfoText); } } } TextState HistoryWebPage::textState(QPoint point, StateRequest request) const { auto result = TextState(_parent); if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) { return result; } auto paintx = 0, painty = 0, paintw = width(), painth = height(); QMargins bubble(_attach ? _attach->bubbleMargins() : QMargins()); auto padding = inBubblePadding(); auto tshift = padding.top(); auto bshift = padding.bottom(); if (_asArticle || (isBubbleBottom() && _attach && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > paintw + bubble.left() + bubble.right())) { bshift += bottomInfoPadding(); } paintw -= padding.left() + padding.right(); auto lineHeight = unitedLineHeight(); auto inThumb = false; if (_asArticle) { auto pw = qMax(_pixw, lineHeight); if (rtlrect(padding.left() + paintw - pw, 0, pw, _pixh, width()).contains(point)) { inThumb = true; } paintw -= pw + st::webPagePhotoDelta; } int symbolAdd = 0; if (_siteNameWidth) { tshift += lineHeight; } if (_titleLines) { if (point.y() >= tshift && point.y() < tshift + _titleLines * lineHeight) { Text::StateRequestElided titleRequest = request.forText(); titleRequest.lines = _titleLines; result = TextState(_parent, _title.getStateElidedLeft( point - QPoint(padding.left(), tshift), paintw, width(), titleRequest)); } else if (point.y() >= tshift + _titleLines * lineHeight) { symbolAdd += _title.length(); } tshift += _titleLines * lineHeight; } if (_descriptionLines) { auto descriptionHeight = (_descriptionLines > 0) ? _descriptionLines * lineHeight : _description.countHeight(paintw); if (point.y() >= tshift && point.y() < tshift + descriptionHeight) { if (_descriptionLines > 0) { Text::StateRequestElided descriptionRequest = request.forText(); descriptionRequest.lines = _descriptionLines; result = TextState(_parent, _description.getStateElidedLeft( point - QPoint(padding.left(), tshift), paintw, width(), descriptionRequest)); } else { result = TextState(_parent, _description.getStateLeft( point - QPoint(padding.left(), tshift), paintw, width(), request.forText())); } } else if (point.y() >= tshift + descriptionHeight) { symbolAdd += _description.length(); } tshift += descriptionHeight; } if (inThumb) { result.link = _openl; } else if (_attach) { auto attachAtTop = !_siteNameWidth && !_titleLines && !_descriptionLines; if (!attachAtTop) tshift += st::mediaInBubbleSkip; if (QRect(padding.left(), tshift, paintw, height() - tshift - bshift).contains(point)) { auto attachLeft = padding.left() - bubble.left(); auto attachTop = tshift - bubble.top(); if (rtl()) attachLeft = width() - attachLeft - _attach->width(); result = _attach->textState(point - QPoint(attachLeft, attachTop), request); if (result.link && !_data->document && _data->photo && _attach->isReadyForOpen()) { if (_data->type == WebPageProfile || _data->type == WebPageVideo) { result.link = _openl; } else if (_data->type == WebPagePhoto || _data->siteName == qstr("Twitter") || _data->siteName == qstr("Facebook")) { // leave photo link } else { result.link = _openl; } } } } result.symbol += symbolAdd; return result; } TextSelection HistoryWebPage::adjustSelection(TextSelection selection, TextSelectType type) const { if (!_descriptionLines || selection.to <= _title.length()) { return _title.adjustSelection(selection, type); } auto descriptionSelection = _description.adjustSelection(toDescriptionSelection(selection), type); if (selection.from >= _title.length()) { return fromDescriptionSelection(descriptionSelection); } auto titleSelection = _title.adjustSelection(selection, type); return { titleSelection.from, fromDescriptionSelection(descriptionSelection).to }; } void HistoryWebPage::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { if (_attach) { _attach->clickHandlerActiveChanged(p, active); } } void HistoryWebPage::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) { if (_attach) { _attach->clickHandlerPressedChanged(p, pressed); } } void HistoryWebPage::playAnimation(bool autoplay) { if (_attach) { if (autoplay) { _attach->autoplayAnimation(); } else { _attach->playAnimation(); } } } bool HistoryWebPage::isDisplayed() const { const auto item = _parent->data(); return !_data->pendingTill && !item->Has<HistoryMessageLogEntryOriginal>(); } TextWithEntities HistoryWebPage::selectedText(TextSelection selection) const { auto titleResult = _title.originalTextWithEntities( selection, ExpandLinksAll); auto descriptionResult = _description.originalTextWithEntities( toDescriptionSelection(selection), ExpandLinksAll); if (titleResult.text.isEmpty()) { return descriptionResult; } else if (descriptionResult.text.isEmpty()) { return titleResult; } titleResult.text += '\n'; TextUtilities::Append(titleResult, std::move(descriptionResult)); return titleResult; } QMargins HistoryWebPage::inBubblePadding() const { auto lshift = st::msgPadding.left() + st::webPageLeft; auto rshift = st::msgPadding.right(); auto bshift = isBubbleBottom() ? st::msgPadding.left() : st::mediaInBubbleSkip; auto tshift = isBubbleTop() ? st::msgPadding.left() : st::mediaInBubbleSkip; return QMargins(lshift, tshift, rshift, bshift); } bool HistoryWebPage::isLogEntryOriginal() const { return _parent->data()->isLogEntry() && _parent->media() != this; } int HistoryWebPage::bottomInfoPadding() const { if (!isBubbleBottom()) return 0; auto result = st::msgDateFont->height; // We use padding greater than st::msgPadding.bottom() in the // bottom of the bubble so that the left line looks pretty. // but if we have bottom skip because of the info display // we don't need that additional padding so we replace it // back with st::msgPadding.bottom() instead of left(). result += st::msgPadding.bottom() - st::msgPadding.left(); return result; } HistoryWebPage::~HistoryWebPage() { Auth().data().unregisterWebPageView(_data, _parent); } HistoryGame::HistoryGame( not_null<Element*> parent, not_null<GameData*> data, const TextWithEntities &consumed) : HistoryMedia(parent) , _data(data) , _title(st::msgMinWidth - st::webPageLeft) , _description(st::msgMinWidth - st::webPageLeft) { if (!consumed.text.isEmpty()) { _description.setMarkedText( st::webPageDescriptionStyle, consumed, Ui::ItemTextOptions(parent->data())); } Auth().data().registerGameView(_data, _parent); } QSize HistoryGame::countOptimalSize() { auto lineHeight = unitedLineHeight(); const auto item = _parent->data(); if (!_openl && IsServerMsgId(item->id)) { const auto row = 0; const auto column = 0; _openl = std::make_shared<ReplyMarkupClickHandler>( row, column, item->fullId()); } auto title = TextUtilities::SingleLine(_data->title); // init attach if (!_attach) { _attach = CreateAttach(_parent, _data->document, _data->photo); } // init strings if (_description.isEmpty() && !_data->description.isEmpty()) { auto text = _data->description; if (!text.isEmpty()) { if (!_attach) { text += _parent->skipBlock(); } auto marked = TextWithEntities { text }; auto parseFlags = TextParseLinks | TextParseMultiline | TextParseRichText; TextUtilities::ParseEntities(marked, parseFlags); _description.setMarkedText( st::webPageDescriptionStyle, marked, Ui::WebpageTextDescriptionOptions()); } } if (_title.isEmpty() && !title.isEmpty()) { _title.setText( st::webPageTitleStyle, title, Ui::WebpageTextTitleOptions()); } // init dimensions auto l = st::msgPadding.left() + st::webPageLeft, r = st::msgPadding.right(); auto skipBlockWidth = _parent->skipBlockWidth(); auto maxWidth = skipBlockWidth; auto minHeight = 0; auto titleMinHeight = _title.isEmpty() ? 0 : lineHeight; // enable any count of lines in game description / message auto descMaxLines = 4096; auto descriptionMinHeight = _description.isEmpty() ? 0 : qMin(_description.minHeight(), descMaxLines * lineHeight); if (!_title.isEmpty()) { accumulate_max(maxWidth, _title.maxWidth()); minHeight += titleMinHeight; } if (!_description.isEmpty()) { accumulate_max(maxWidth, _description.maxWidth()); minHeight += descriptionMinHeight; } if (_attach) { auto attachAtTop = !_titleLines && !_descriptionLines; if (!attachAtTop) minHeight += st::mediaInBubbleSkip; _attach->initDimensions(); QMargins bubble(_attach->bubbleMargins()); auto maxMediaWidth = _attach->maxWidth() - bubble.left() - bubble.right(); if (isBubbleBottom() && _attach->customInfoLayout()) { maxMediaWidth += skipBlockWidth; } accumulate_max(maxWidth, maxMediaWidth); minHeight += _attach->minHeight() - bubble.top() - bubble.bottom(); } maxWidth += st::msgPadding.left() + st::webPageLeft + st::msgPadding.right(); auto padding = inBubblePadding(); minHeight += padding.top() + padding.bottom(); if (!_gameTagWidth) { _gameTagWidth = st::msgDateFont->width(lang(lng_game_tag).toUpper()); } return { maxWidth, minHeight }; } void HistoryGame::refreshParentId(not_null<HistoryItem*> realParent) { if (_openl) { _openl->setMessageId(realParent->fullId()); } if (_attach) { _attach->refreshParentId(realParent); } } QSize HistoryGame::countCurrentSize(int newWidth) { accumulate_min(newWidth, maxWidth()); auto innerWidth = newWidth - st::msgPadding.left() - st::webPageLeft - st::msgPadding.right(); // enable any count of lines in game description / message auto linesMax = 4096; auto lineHeight = unitedLineHeight(); auto newHeight = 0; if (_title.isEmpty()) { _titleLines = 0; } else { if (_title.countHeight(innerWidth) < 2 * st::webPageTitleFont->height) { _titleLines = 1; } else { _titleLines = 2; } newHeight += _titleLines * lineHeight; } if (_description.isEmpty()) { _descriptionLines = 0; } else { auto descriptionHeight = _description.countHeight(innerWidth); if (descriptionHeight < (linesMax - _titleLines) * st::webPageDescriptionFont->height) { _descriptionLines = (descriptionHeight / st::webPageDescriptionFont->height); } else { _descriptionLines = (linesMax - _titleLines); } newHeight += _descriptionLines * lineHeight; } if (_attach) { auto attachAtTop = !_titleLines && !_descriptionLines; if (!attachAtTop) newHeight += st::mediaInBubbleSkip; QMargins bubble(_attach->bubbleMargins()); _attach->resizeGetHeight(innerWidth + bubble.left() + bubble.right()); newHeight += _attach->height() - bubble.top() - bubble.bottom(); if (isBubbleBottom() && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > innerWidth + bubble.left() + bubble.right()) { newHeight += bottomInfoPadding(); } } auto padding = inBubblePadding(); newHeight += padding.top() + padding.bottom(); return { newWidth, newHeight }; } TextSelection HistoryGame::toDescriptionSelection( TextSelection selection) const { return HistoryView::UnshiftItemSelection(selection, _title); } TextSelection HistoryGame::fromDescriptionSelection( TextSelection selection) const { return HistoryView::ShiftItemSelection(selection, _title); } void HistoryGame::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const { if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; auto paintw = width(), painth = height(); auto outbg = _parent->hasOutLayout(); bool selected = (selection == FullSelection); auto &barfg = selected ? (outbg ? st::msgOutReplyBarSelColor : st::msgInReplyBarSelColor) : (outbg ? st::msgOutReplyBarColor : st::msgInReplyBarColor); auto &semibold = selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg); auto ®ular = selected ? (outbg ? st::msgOutDateFgSelected : st::msgInDateFgSelected) : (outbg ? st::msgOutDateFg : st::msgInDateFg); QMargins bubble(_attach ? _attach->bubbleMargins() : QMargins()); auto padding = inBubblePadding(); auto tshift = padding.top(); auto bshift = padding.bottom(); paintw -= padding.left() + padding.right(); if (isBubbleBottom() && _attach && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > paintw + bubble.left() + bubble.right()) { bshift += bottomInfoPadding(); } QRect bar(rtlrect(st::msgPadding.left(), tshift, st::webPageBar, height() - tshift - bshift, width())); p.fillRect(bar, barfg); auto lineHeight = unitedLineHeight(); if (_titleLines) { p.setPen(semibold); auto endskip = 0; if (_title.hasSkipBlock()) { endskip = _parent->skipBlockWidth(); } _title.drawLeftElided(p, padding.left(), tshift, paintw, width(), _titleLines, style::al_left, 0, -1, endskip, false, selection); tshift += _titleLines * lineHeight; } if (_descriptionLines) { p.setPen(outbg ? st::webPageDescriptionOutFg : st::webPageDescriptionInFg); auto endskip = 0; if (_description.hasSkipBlock()) { endskip = _parent->skipBlockWidth(); } _description.drawLeftElided(p, padding.left(), tshift, paintw, width(), _descriptionLines, style::al_left, 0, -1, endskip, false, toDescriptionSelection(selection)); tshift += _descriptionLines * lineHeight; } if (_attach) { auto attachAtTop = !_titleLines && !_descriptionLines; if (!attachAtTop) tshift += st::mediaInBubbleSkip; auto attachLeft = padding.left() - bubble.left(); auto attachTop = tshift - bubble.top(); if (rtl()) attachLeft = width() - attachLeft - _attach->width(); auto attachSelection = selected ? FullSelection : TextSelection { 0, 0 }; p.translate(attachLeft, attachTop); _attach->draw(p, r.translated(-attachLeft, -attachTop), attachSelection, ms); auto pixwidth = _attach->width(); auto pixheight = _attach->height(); auto gameW = _gameTagWidth + 2 * st::msgDateImgPadding.x(); auto gameH = st::msgDateFont->height + 2 * st::msgDateImgPadding.y(); auto gameX = pixwidth - st::msgDateImgDelta - gameW; auto gameY = pixheight - st::msgDateImgDelta - gameH; App::roundRect(p, rtlrect(gameX, gameY, gameW, gameH, pixwidth), selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? DateSelectedCorners : DateCorners); p.setFont(st::msgDateFont); p.setPen(st::msgDateImgFg); p.drawTextLeft(gameX + st::msgDateImgPadding.x(), gameY + st::msgDateImgPadding.y(), pixwidth, lang(lng_game_tag).toUpper()); p.translate(-attachLeft, -attachTop); } } TextState HistoryGame::textState(QPoint point, StateRequest request) const { auto result = TextState(_parent); if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) { return result; } auto paintw = width(), painth = height(); QMargins bubble(_attach ? _attach->bubbleMargins() : QMargins()); auto padding = inBubblePadding(); auto tshift = padding.top(); auto bshift = padding.bottom(); if (isBubbleBottom() && _attach && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > paintw + bubble.left() + bubble.right()) { bshift += bottomInfoPadding(); } paintw -= padding.left() + padding.right(); auto inThumb = false; auto symbolAdd = 0; auto lineHeight = unitedLineHeight(); if (_titleLines) { if (point.y() >= tshift && point.y() < tshift + _titleLines * lineHeight) { Text::StateRequestElided titleRequest = request.forText(); titleRequest.lines = _titleLines; result = TextState(_parent, _title.getStateElidedLeft( point - QPoint(padding.left(), tshift), paintw, width(), titleRequest)); } else if (point.y() >= tshift + _titleLines * lineHeight) { symbolAdd += _title.length(); } tshift += _titleLines * lineHeight; } if (_descriptionLines) { if (point.y() >= tshift && point.y() < tshift + _descriptionLines * lineHeight) { Text::StateRequestElided descriptionRequest = request.forText(); descriptionRequest.lines = _descriptionLines; result = TextState(_parent, _description.getStateElidedLeft( point - QPoint(padding.left(), tshift), paintw, width(), descriptionRequest)); } else if (point.y() >= tshift + _descriptionLines * lineHeight) { symbolAdd += _description.length(); } tshift += _descriptionLines * lineHeight; } if (inThumb) { if (!_parent->data()->isLogEntry()) { result.link = _openl; } } else if (_attach) { auto attachAtTop = !_titleLines && !_descriptionLines; if (!attachAtTop) tshift += st::mediaInBubbleSkip; auto attachLeft = padding.left() - bubble.left(); auto attachTop = tshift - bubble.top(); if (rtl()) attachLeft = width() - attachLeft - _attach->width(); if (QRect(attachLeft, tshift, _attach->width(), height() - tshift - bshift).contains(point)) { if (_attach->isReadyForOpen()) { if (!_parent->data()->isLogEntry()) { result.link = _openl; } } else { result = _attach->textState(point - QPoint(attachLeft, attachTop), request); } } } result.symbol += symbolAdd; return result; } TextSelection HistoryGame::adjustSelection(TextSelection selection, TextSelectType type) const { if (!_descriptionLines || selection.to <= _title.length()) { return _title.adjustSelection(selection, type); } auto descriptionSelection = _description.adjustSelection(toDescriptionSelection(selection), type); if (selection.from >= _title.length()) { return fromDescriptionSelection(descriptionSelection); } auto titleSelection = _title.adjustSelection(selection, type); return { titleSelection.from, fromDescriptionSelection(descriptionSelection).to }; } void HistoryGame::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { if (_attach) { _attach->clickHandlerActiveChanged(p, active); } } void HistoryGame::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) { if (_attach) { _attach->clickHandlerPressedChanged(p, pressed); } } TextWithEntities HistoryGame::selectedText(TextSelection selection) const { auto titleResult = _title.originalTextWithEntities( selection, ExpandLinksAll); auto descriptionResult = _description.originalTextWithEntities( toDescriptionSelection(selection), ExpandLinksAll); if (titleResult.text.isEmpty()) { return descriptionResult; } else if (descriptionResult.text.isEmpty()) { return titleResult; } titleResult.text += '\n'; TextUtilities::Append(titleResult, std::move(descriptionResult)); return titleResult; } void HistoryGame::playAnimation(bool autoplay) { if (_attach) { if (autoplay) { _attach->autoplayAnimation(); } else { _attach->playAnimation(); } } } QMargins HistoryGame::inBubblePadding() const { auto lshift = st::msgPadding.left() + st::webPageLeft; auto rshift = st::msgPadding.right(); auto bshift = isBubbleBottom() ? st::msgPadding.left() : st::mediaInBubbleSkip; auto tshift = isBubbleTop() ? st::msgPadding.left() : st::mediaInBubbleSkip; return QMargins(lshift, tshift, rshift, bshift); } int HistoryGame::bottomInfoPadding() const { if (!isBubbleBottom()) return 0; auto result = st::msgDateFont->height; // we use padding greater than st::msgPadding.bottom() in the // bottom of the bubble so that the left line looks pretty. // but if we have bottom skip because of the info display // we don't need that additional padding so we replace it // back with st::msgPadding.bottom() instead of left(). result += st::msgPadding.bottom() - st::msgPadding.left(); return result; } void HistoryGame::parentTextUpdated() { if (const auto media = _parent->data()->media()) { const auto consumed = media->consumedMessageText(); if (!consumed.text.isEmpty()) { _description.setMarkedText( st::webPageDescriptionStyle, consumed, Ui::ItemTextOptions(_parent->data())); } else { _description = Text(st::msgMinWidth - st::webPageLeft); } Auth().data().requestViewResize(_parent); } } HistoryGame::~HistoryGame() { Auth().data().unregisterGameView(_data, _parent); } HistoryInvoice::HistoryInvoice( not_null<Element*> parent, not_null<Data::Invoice*> invoice) : HistoryMedia(parent) , _title(st::msgMinWidth) , _description(st::msgMinWidth) , _status(st::msgMinWidth) { fillFromData(invoice); } QString HistoryInvoice::fillAmountAndCurrency( uint64 amount, const QString ¤cy) { static const auto ShortCurrencyNames = QMap<QString, QString> { { qsl("USD"), QString::fromUtf8("\x24") }, { qsl("GBP"), QString::fromUtf8("\xC2\xA3") }, { qsl("EUR"), QString::fromUtf8("\xE2\x82\xAC") }, { qsl("JPY"), QString::fromUtf8("\xC2\xA5") }, }; static const auto Denominators = QMap<QString, int> { { qsl("CLF"), 10000 }, { qsl("BHD"), 1000 }, { qsl("IQD"), 1000 }, { qsl("JOD"), 1000 }, { qsl("KWD"), 1000 }, { qsl("LYD"), 1000 }, { qsl("OMR"), 1000 }, { qsl("TND"), 1000 }, { qsl("BIF"), 1 }, { qsl("BYR"), 1 }, { qsl("CLP"), 1 }, { qsl("CVE"), 1 }, { qsl("DJF"), 1 }, { qsl("GNF"), 1 }, { qsl("ISK"), 1 }, { qsl("JPY"), 1 }, { qsl("KMF"), 1 }, { qsl("KRW"), 1 }, { qsl("MGA"), 1 }, { qsl("PYG"), 1 }, { qsl("RWF"), 1 }, { qsl("UGX"), 1 }, { qsl("UYI"), 1 }, { qsl("VND"), 1 }, { qsl("VUV"), 1 }, { qsl("XAF"), 1 }, { qsl("XOF"), 1 }, { qsl("XPF"), 1 }, { qsl("MRO"), 10 }, }; const auto currencyText = ShortCurrencyNames.value(currency, currency); const auto denominator = Denominators.value(currency, 100); const auto currencyValue = amount / float64(denominator); const auto digits = [&] { auto result = 0; for (auto test = 1; test < denominator; test *= 10) { ++result; } return result; }(); return QLocale::system().toCurrencyString(currencyValue, currencyText); //auto amountBucks = amount / 100; //auto amountCents = amount % 100; //auto amountText = qsl("%1,%2").arg(amountBucks).arg(amountCents, 2, 10, QChar('0')); //return currencyText + amountText; } void HistoryInvoice::fillFromData(not_null<Data::Invoice*> invoice) { // init attach auto labelText = [&] { if (invoice->receiptMsgId) { if (invoice->isTest) { return lang(lng_payments_receipt_label_test); } return lang(lng_payments_receipt_label); } else if (invoice->isTest) { return lang(lng_payments_invoice_label_test); } return lang(lng_payments_invoice_label); }; auto statusText = TextWithEntities { fillAmountAndCurrency(invoice->amount, invoice->currency), EntitiesInText() }; statusText.entities.push_back(EntityInText(EntityInTextBold, 0, statusText.text.size())); statusText.text += ' ' + labelText().toUpper(); _status.setMarkedText( st::defaultTextStyle, statusText, Ui::ItemTextOptions(_parent->data())); _receiptMsgId = invoice->receiptMsgId; // init strings if (!invoice->description.isEmpty()) { auto marked = TextWithEntities { invoice->description }; auto parseFlags = TextParseLinks | TextParseMultiline | TextParseRichText; TextUtilities::ParseEntities(marked, parseFlags); _description.setMarkedText( st::webPageDescriptionStyle, marked, Ui::WebpageTextDescriptionOptions()); } if (!invoice->title.isEmpty()) { _title.setText( st::webPageTitleStyle, invoice->title, Ui::WebpageTextTitleOptions()); } } QSize HistoryInvoice::countOptimalSize() { auto lineHeight = unitedLineHeight(); if (_attach) { if (_status.hasSkipBlock()) { _status.removeSkipBlock(); } } else { _status.updateSkipBlock( _parent->skipBlockWidth(), _parent->skipBlockHeight()); } // init dimensions auto l = st::msgPadding.left(), r = st::msgPadding.right(); auto skipBlockWidth = _parent->skipBlockWidth(); auto maxWidth = skipBlockWidth; auto minHeight = 0; auto titleMinHeight = _title.isEmpty() ? 0 : lineHeight; // enable any count of lines in game description / message auto descMaxLines = 4096; auto descriptionMinHeight = _description.isEmpty() ? 0 : qMin(_description.minHeight(), descMaxLines * lineHeight); if (!_title.isEmpty()) { accumulate_max(maxWidth, _title.maxWidth()); minHeight += titleMinHeight; } if (!_description.isEmpty()) { accumulate_max(maxWidth, _description.maxWidth()); minHeight += descriptionMinHeight; } if (_attach) { auto attachAtTop = _title.isEmpty() && _description.isEmpty(); if (!attachAtTop) minHeight += st::mediaInBubbleSkip; _attach->initDimensions(); auto bubble = _attach->bubbleMargins(); auto maxMediaWidth = _attach->maxWidth() - bubble.left() - bubble.right(); if (isBubbleBottom() && _attach->customInfoLayout()) { maxMediaWidth += skipBlockWidth; } accumulate_max(maxWidth, maxMediaWidth); minHeight += _attach->minHeight() - bubble.top() - bubble.bottom(); } else { accumulate_max(maxWidth, _status.maxWidth()); minHeight += st::mediaInBubbleSkip + _status.minHeight(); } auto padding = inBubblePadding(); maxWidth += padding.left() + padding.right(); minHeight += padding.top() + padding.bottom(); return { maxWidth, minHeight }; } QSize HistoryInvoice::countCurrentSize(int newWidth) { accumulate_min(newWidth, maxWidth()); auto innerWidth = newWidth - st::msgPadding.left() - st::msgPadding.right(); auto lineHeight = unitedLineHeight(); auto newHeight = 0; if (_title.isEmpty()) { _titleHeight = 0; } else { if (_title.countHeight(innerWidth) < 2 * st::webPageTitleFont->height) { _titleHeight = lineHeight; } else { _titleHeight = 2 * lineHeight; } newHeight += _titleHeight; } if (_description.isEmpty()) { _descriptionHeight = 0; } else { _descriptionHeight = _description.countHeight(innerWidth); newHeight += _descriptionHeight; } if (_attach) { auto attachAtTop = !_title.isEmpty() && _description.isEmpty(); if (!attachAtTop) newHeight += st::mediaInBubbleSkip; QMargins bubble(_attach->bubbleMargins()); _attach->resizeGetHeight(innerWidth + bubble.left() + bubble.right()); newHeight += _attach->height() - bubble.top() - bubble.bottom(); if (isBubbleBottom() && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > innerWidth + bubble.left() + bubble.right()) { newHeight += bottomInfoPadding(); } } else { newHeight += st::mediaInBubbleSkip + _status.countHeight(innerWidth); } auto padding = inBubblePadding(); newHeight += padding.top() + padding.bottom(); return { newWidth, newHeight }; } TextSelection HistoryInvoice::toDescriptionSelection( TextSelection selection) const { return HistoryView::UnshiftItemSelection(selection, _title); } TextSelection HistoryInvoice::fromDescriptionSelection( TextSelection selection) const { return HistoryView::ShiftItemSelection(selection, _title); } void HistoryInvoice::refreshParentId(not_null<HistoryItem*> realParent) { if (_attach) { _attach->refreshParentId(realParent); } } void HistoryInvoice::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const { if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; auto paintw = width(), painth = height(); auto outbg = _parent->hasOutLayout(); bool selected = (selection == FullSelection); auto &barfg = selected ? (outbg ? st::msgOutReplyBarSelColor : st::msgInReplyBarSelColor) : (outbg ? st::msgOutReplyBarColor : st::msgInReplyBarColor); auto &semibold = selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg); auto ®ular = selected ? (outbg ? st::msgOutDateFgSelected : st::msgInDateFgSelected) : (outbg ? st::msgOutDateFg : st::msgInDateFg); QMargins bubble(_attach ? _attach->bubbleMargins() : QMargins()); auto padding = inBubblePadding(); auto tshift = padding.top(); auto bshift = padding.bottom(); paintw -= padding.left() + padding.right(); if (isBubbleBottom() && _attach && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > paintw + bubble.left() + bubble.right()) { bshift += bottomInfoPadding(); } auto lineHeight = unitedLineHeight(); if (_titleHeight) { p.setPen(semibold); p.setTextPalette(selected ? (outbg ? st::outTextPaletteSelected : st::inTextPaletteSelected) : (outbg ? st::outSemiboldPalette : st::inSemiboldPalette)); auto endskip = 0; if (_title.hasSkipBlock()) { endskip = _parent->skipBlockWidth(); } _title.drawLeftElided(p, padding.left(), tshift, paintw, width(), _titleHeight / lineHeight, style::al_left, 0, -1, endskip, false, selection); tshift += _titleHeight; p.setTextPalette(selected ? (outbg ? st::outTextPaletteSelected : st::inTextPaletteSelected) : (outbg ? st::outTextPalette : st::inTextPalette)); } if (_descriptionHeight) { p.setPen(outbg ? st::webPageDescriptionOutFg : st::webPageDescriptionInFg); _description.drawLeft(p, padding.left(), tshift, paintw, width(), style::al_left, 0, -1, toDescriptionSelection(selection)); tshift += _descriptionHeight; } if (_attach) { auto attachAtTop = !_titleHeight && !_descriptionHeight; if (!attachAtTop) tshift += st::mediaInBubbleSkip; auto attachLeft = padding.left() - bubble.left(); auto attachTop = tshift - bubble.top(); if (rtl()) attachLeft = width() - attachLeft - _attach->width(); auto attachSelection = selected ? FullSelection : TextSelection { 0, 0 }; p.translate(attachLeft, attachTop); _attach->draw(p, r.translated(-attachLeft, -attachTop), attachSelection, ms); auto pixwidth = _attach->width(); auto pixheight = _attach->height(); auto available = _status.maxWidth(); auto statusW = available + 2 * st::msgDateImgPadding.x(); auto statusH = st::msgDateFont->height + 2 * st::msgDateImgPadding.y(); auto statusX = st::msgDateImgDelta; auto statusY = st::msgDateImgDelta; App::roundRect(p, rtlrect(statusX, statusY, statusW, statusH, pixwidth), selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? DateSelectedCorners : DateCorners); p.setFont(st::msgDateFont); p.setPen(st::msgDateImgFg); _status.drawLeftElided(p, statusX + st::msgDateImgPadding.x(), statusY + st::msgDateImgPadding.y(), available, pixwidth); p.translate(-attachLeft, -attachTop); } else { p.setPen(outbg ? st::webPageDescriptionOutFg : st::webPageDescriptionInFg); _status.drawLeft(p, padding.left(), tshift + st::mediaInBubbleSkip, paintw, width()); } } TextState HistoryInvoice::textState(QPoint point, StateRequest request) const { auto result = TextState(_parent); if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) { return result; } auto paintw = width(), painth = height(); QMargins bubble(_attach ? _attach->bubbleMargins() : QMargins()); auto padding = inBubblePadding(); auto tshift = padding.top(); auto bshift = padding.bottom(); if (isBubbleBottom() && _attach && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > paintw + bubble.left() + bubble.right()) { bshift += bottomInfoPadding(); } paintw -= padding.left() + padding.right(); auto lineHeight = unitedLineHeight(); auto symbolAdd = 0; if (_titleHeight) { if (point.y() >= tshift && point.y() < tshift + _titleHeight) { Text::StateRequestElided titleRequest = request.forText(); titleRequest.lines = _titleHeight / lineHeight; result = TextState(_parent, _title.getStateElidedLeft( point - QPoint(padding.left(), tshift), paintw, width(), titleRequest)); } else if (point.y() >= tshift + _titleHeight) { symbolAdd += _title.length(); } tshift += _titleHeight; } if (_descriptionHeight) { if (point.y() >= tshift && point.y() < tshift + _descriptionHeight) { result = TextState(_parent, _description.getStateLeft( point - QPoint(padding.left(), tshift), paintw, width(), request.forText())); } else if (point.y() >= tshift + _descriptionHeight) { symbolAdd += _description.length(); } tshift += _descriptionHeight; } if (_attach) { auto attachAtTop = !_titleHeight && !_descriptionHeight; if (!attachAtTop) tshift += st::mediaInBubbleSkip; auto attachLeft = padding.left() - bubble.left(); auto attachTop = tshift - bubble.top(); if (rtl()) attachLeft = width() - attachLeft - _attach->width(); if (QRect(attachLeft, tshift, _attach->width(), height() - tshift - bshift).contains(point)) { result = _attach->textState(point - QPoint(attachLeft, attachTop), request); } } result.symbol += symbolAdd; return result; } TextSelection HistoryInvoice::adjustSelection(TextSelection selection, TextSelectType type) const { if (!_descriptionHeight || selection.to <= _title.length()) { return _title.adjustSelection(selection, type); } auto descriptionSelection = _description.adjustSelection(toDescriptionSelection(selection), type); if (selection.from >= _title.length()) { return fromDescriptionSelection(descriptionSelection); } auto titleSelection = _title.adjustSelection(selection, type); return { titleSelection.from, fromDescriptionSelection(descriptionSelection).to }; } void HistoryInvoice::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { if (_attach) { _attach->clickHandlerActiveChanged(p, active); } } void HistoryInvoice::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) { if (_attach) { _attach->clickHandlerPressedChanged(p, pressed); } } TextWithEntities HistoryInvoice::selectedText(TextSelection selection) const { auto titleResult = _title.originalTextWithEntities( selection, ExpandLinksAll); auto descriptionResult = _description.originalTextWithEntities( toDescriptionSelection(selection), ExpandLinksAll); if (titleResult.text.isEmpty()) { return descriptionResult; } else if (descriptionResult.text.isEmpty()) { return titleResult; } titleResult.text += '\n'; TextUtilities::Append(titleResult, std::move(descriptionResult)); return titleResult; } QMargins HistoryInvoice::inBubblePadding() const { auto lshift = st::msgPadding.left(); auto rshift = st::msgPadding.right(); auto bshift = isBubbleBottom() ? st::msgPadding.top() : st::mediaInBubbleSkip; auto tshift = isBubbleTop() ? st::msgPadding.bottom() : st::mediaInBubbleSkip; return QMargins(lshift, tshift, rshift, bshift); } int HistoryInvoice::bottomInfoPadding() const { if (!isBubbleBottom()) return 0; auto result = st::msgDateFont->height; return result; } HistoryLocation::HistoryLocation( not_null<Element*> parent, not_null<LocationData*> location, const QString &title, const QString &description) : HistoryMedia(parent) , _data(location) , _title(st::msgMinWidth) , _description(st::msgMinWidth) , _link(std::make_shared<LocationClickHandler>(_data->coords)) { if (!title.isEmpty()) { _title.setText( st::webPageTitleStyle, TextUtilities::Clean(title), Ui::WebpageTextTitleOptions()); } if (!description.isEmpty()) { _description.setMarkedText( st::webPageDescriptionStyle, TextUtilities::ParseEntities( TextUtilities::Clean(description), TextParseLinks | TextParseMultiline | TextParseRichText), Ui::WebpageTextDescriptionOptions()); } } QSize HistoryLocation::countOptimalSize() { auto tw = fullWidth(); auto th = fullHeight(); if (tw > st::maxMediaSize) { th = (st::maxMediaSize * th) / tw; tw = st::maxMediaSize; } auto minWidth = qMax(st::minPhotoSize, _parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); auto maxWidth = qMax(tw, minWidth); auto minHeight = qMax(th, st::minPhotoSize); if (_parent->hasBubble()) { if (!_title.isEmpty()) { minHeight += qMin(_title.countHeight(maxWidth - st::msgPadding.left() - st::msgPadding.right()), 2 * st::webPageTitleFont->height); } if (!_description.isEmpty()) { minHeight += qMin(_description.countHeight(maxWidth - st::msgPadding.left() - st::msgPadding.right()), 3 * st::webPageDescriptionFont->height); } if (!_title.isEmpty() || !_description.isEmpty()) { minHeight += st::mediaInBubbleSkip; if (isBubbleTop()) { minHeight += st::msgPadding.top(); } } } return { maxWidth, minHeight }; } QSize HistoryLocation::countCurrentSize(int newWidth) { accumulate_min(newWidth, maxWidth()); auto tw = fullWidth(); auto th = fullHeight(); if (tw > st::maxMediaSize) { th = (st::maxMediaSize * th) / tw; tw = st::maxMediaSize; } auto newHeight = th; if (tw > newWidth) { newHeight = (newWidth * newHeight / tw); } else { newWidth = tw; } auto minWidth = qMax(st::minPhotoSize, _parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); accumulate_max(newWidth, minWidth); accumulate_max(newHeight, st::minPhotoSize); if (_parent->hasBubble()) { if (!_title.isEmpty()) { newHeight += qMin(_title.countHeight(newWidth - st::msgPadding.left() - st::msgPadding.right()), st::webPageTitleFont->height * 2); } if (!_description.isEmpty()) { newHeight += qMin(_description.countHeight(newWidth - st::msgPadding.left() - st::msgPadding.right()), st::webPageDescriptionFont->height * 3); } if (!_title.isEmpty() || !_description.isEmpty()) { newHeight += st::mediaInBubbleSkip; if (isBubbleTop()) { newHeight += st::msgPadding.top(); } } } return { newWidth, newHeight }; } TextSelection HistoryLocation::toDescriptionSelection( TextSelection selection) const { return HistoryView::UnshiftItemSelection(selection, _title); } TextSelection HistoryLocation::fromDescriptionSelection( TextSelection selection) const { return HistoryView::ShiftItemSelection(selection, _title); } void HistoryLocation::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const { if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; auto paintx = 0, painty = 0, paintw = width(), painth = height(); bool bubble = _parent->hasBubble(); auto outbg = _parent->hasOutLayout(); bool selected = (selection == FullSelection); if (bubble) { if (!_title.isEmpty() || !_description.isEmpty()) { if (isBubbleTop()) { painty += st::msgPadding.top(); } } auto textw = width() - st::msgPadding.left() - st::msgPadding.right(); if (!_title.isEmpty()) { p.setPen(outbg ? st::webPageTitleOutFg : st::webPageTitleInFg); _title.drawLeftElided(p, paintx + st::msgPadding.left(), painty, textw, width(), 2, style::al_left, 0, -1, 0, false, selection); painty += qMin(_title.countHeight(textw), 2 * st::webPageTitleFont->height); } if (!_description.isEmpty()) { p.setPen(outbg ? st::webPageDescriptionOutFg : st::webPageDescriptionInFg); _description.drawLeftElided(p, paintx + st::msgPadding.left(), painty, textw, width(), 3, style::al_left, 0, -1, 0, false, toDescriptionSelection(selection)); painty += qMin(_description.countHeight(textw), 3 * st::webPageDescriptionFont->height); } if (!_title.isEmpty() || !_description.isEmpty()) { painty += st::mediaInBubbleSkip; } painth -= painty; } else { App::roundShadow(p, 0, 0, paintw, painth, selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners); } _data->load(); auto roundRadius = ImageRoundRadius::Large; auto roundCorners = ((isBubbleTop() && _title.isEmpty() && _description.isEmpty()) ? (RectPart::TopLeft | RectPart::TopRight) : RectPart::None) | (isBubbleBottom() ? (RectPart::BottomLeft | RectPart::BottomRight) : RectPart::None); auto rthumb = QRect(paintx, painty, paintw, painth); if (_data && !_data->thumb->isNull()) { auto w = _data->thumb->width(), h = _data->thumb->height(); QPixmap pix; if (paintw * h == painth * w || (w == fullWidth() && h == fullHeight())) { pix = _data->thumb->pixSingle(paintw, painth, paintw, painth, roundRadius, roundCorners); } else if (paintw * h > painth * w) { auto nw = painth * w / h; pix = _data->thumb->pixSingle(nw, painth, paintw, painth, roundRadius, roundCorners); } else { auto nh = paintw * h / w; pix = _data->thumb->pixSingle(paintw, nh, paintw, painth, roundRadius, roundCorners); } p.drawPixmap(rthumb.topLeft(), pix); } else { App::complexLocationRect(p, rthumb, roundRadius, roundCorners); } if (selected) { App::complexOverlayRect(p, rthumb, roundRadius, roundCorners); } if (_parent->media() == this) { auto fullRight = paintx + paintw; auto fullBottom = height(); _parent->drawInfo(p, fullRight, fullBottom, paintx * 2 + paintw, selected, InfoDisplayType::Image); if (!bubble && _parent->displayRightAction()) { auto fastShareLeft = (fullRight + st::historyFastShareLeft); auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize); _parent->drawRightAction(p, fastShareLeft, fastShareTop, 2 * paintx + paintw); } } } TextState HistoryLocation::textState(QPoint point, StateRequest request) const { auto result = TextState(_parent); auto symbolAdd = 0; if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) { return result; } auto paintx = 0, painty = 0, paintw = width(), painth = height(); bool bubble = _parent->hasBubble(); if (bubble) { if (!_title.isEmpty() || !_description.isEmpty()) { if (isBubbleTop()) { painty += st::msgPadding.top(); } } auto textw = width() - st::msgPadding.left() - st::msgPadding.right(); if (!_title.isEmpty()) { auto titleh = qMin(_title.countHeight(textw), 2 * st::webPageTitleFont->height); if (point.y() >= painty && point.y() < painty + titleh) { result = TextState(_parent, _title.getStateLeft( point - QPoint(paintx + st::msgPadding.left(), painty), textw, width(), request.forText())); return result; } else if (point.y() >= painty + titleh) { symbolAdd += _title.length(); } painty += titleh; } if (!_description.isEmpty()) { auto descriptionh = qMin(_description.countHeight(textw), 3 * st::webPageDescriptionFont->height); if (point.y() >= painty && point.y() < painty + descriptionh) { result = TextState(_parent, _description.getStateLeft( point - QPoint(paintx + st::msgPadding.left(), painty), textw, width(), request.forText())); } else if (point.y() >= painty + descriptionh) { symbolAdd += _description.length(); } painty += descriptionh; } if (!_title.isEmpty() || !_description.isEmpty()) { painty += st::mediaInBubbleSkip; } painth -= painty; } if (QRect(paintx, painty, paintw, painth).contains(point) && _data) { result.link = _link; } if (_parent->media() == this) { auto fullRight = paintx + paintw; auto fullBottom = height(); if (_parent->pointInTime(fullRight, fullBottom, point, InfoDisplayType::Image)) { result.cursor = CursorState::Date; } if (!bubble && _parent->displayRightAction()) { auto fastShareLeft = (fullRight + st::historyFastShareLeft); auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize); if (QRect(fastShareLeft, fastShareTop, st::historyFastShareSize, st::historyFastShareSize).contains(point)) { result.link = _parent->rightActionLink(); } } } result.symbol += symbolAdd; return result; } TextSelection HistoryLocation::adjustSelection(TextSelection selection, TextSelectType type) const { if (_description.isEmpty() || selection.to <= _title.length()) { return _title.adjustSelection(selection, type); } auto descriptionSelection = _description.adjustSelection(toDescriptionSelection(selection), type); if (selection.from >= _title.length()) { return fromDescriptionSelection(descriptionSelection); } auto titleSelection = _title.adjustSelection(selection, type); return { titleSelection.from, fromDescriptionSelection(descriptionSelection).to }; } TextWithEntities HistoryLocation::selectedText(TextSelection selection) const { auto titleResult = _title.originalTextWithEntities(selection); auto descriptionResult = _description.originalTextWithEntities(toDescriptionSelection(selection)); if (titleResult.text.isEmpty()) { return descriptionResult; } else if (descriptionResult.text.isEmpty()) { return titleResult; } titleResult.text += '\n'; TextUtilities::Append(titleResult, std::move(descriptionResult)); return titleResult; } bool HistoryLocation::needsBubble() const { if (!_title.isEmpty() || !_description.isEmpty()) { return true; } const auto item = _parent->data(); return item->viaBot() || item->Has<HistoryMessageReply>() || _parent->displayForwardedFrom() || _parent->displayFromName(); return false; } int HistoryLocation::fullWidth() const { return st::locationSize.width(); } int HistoryLocation::fullHeight() const { return st::locationSize.height(); } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/history_media_types.h����������������������������������0000664�0000000�0000000�00000060433�13262451251�0025367�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "base/runtime_composer.h" #include "history/history_media.h" #include "ui/effects/radial_animation.h" #include "data/data_document.h" #include "data/data_photo.h" #include "data/data_web_page.h" #include "data/data_game.h" class ReplyMarkupClickHandler; struct HistoryDocumentNamed; struct HistoryMessageVia; struct HistoryMessageReply; struct HistoryMessageForwarded; namespace Data { enum class CallFinishReason : char; struct Invoice; struct Call; } // namespace Data namespace Media { namespace Clip { class Playback; } // namespace Clip namespace Player { class RoundController; } // namespace Player } // namespace Media namespace Ui { class EmptyUserpic; } // namespace Ui class HistoryFileMedia : public HistoryMedia { public: using HistoryMedia::HistoryMedia; bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override { return p == _openl || p == _savel || p == _cancell; } bool dragItemByHandler(const ClickHandlerPtr &p) const override { return p == _openl || p == _savel || p == _cancell; } void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override; void refreshParentId(not_null<HistoryItem*> realParent) override; bool allowsFastShare() const override { return true; } ~HistoryFileMedia(); protected: using FileClickHandlerPtr = std::shared_ptr<FileClickHandler>; FileClickHandlerPtr _openl, _savel, _cancell; void setLinks( FileClickHandlerPtr &&openl, FileClickHandlerPtr &&savel, FileClickHandlerPtr &&cancell); void setDocumentLinks( not_null<DocumentData*> document, not_null<HistoryItem*> realParent, bool inlinegif = false); // >= 0 will contain download / upload string, _statusSize = loaded bytes // < 0 will contain played string, _statusSize = -(seconds + 1) played // 0x7FFFFFF0 will contain status for not yet downloaded file // 0x7FFFFFF1 will contain status for already downloaded file // 0x7FFFFFF2 will contain status for failed to download / upload file mutable int _statusSize; mutable QString _statusText; // duration = -1 - no duration, duration = -2 - "GIF" duration void setStatusSize(int newSize, int fullSize, int duration, qint64 realDuration) const; void step_radial(TimeMs ms, bool timer); void thumbAnimationCallback(); void ensureAnimation() const; void checkAnimationFinished() const; bool isRadialAnimation(TimeMs ms) const { if (!_animation || !_animation->radial.animating()) return false; _animation->radial.step(ms); return _animation && _animation->radial.animating(); } bool isThumbAnimation(TimeMs ms) const { if (_animation) { if (_animation->a_thumbOver.animating(ms)) { return true; } checkAnimationFinished(); } return false; } virtual float64 dataProgress() const = 0; virtual bool dataFinished() const = 0; virtual bool dataLoaded() const = 0; struct AnimationData { AnimationData(AnimationCallbacks &&radialCallbacks) : radial(std::move(radialCallbacks)) { } Animation a_thumbOver; Ui::RadialAnimation radial; }; mutable std::unique_ptr<AnimationData> _animation; }; class HistoryPhoto : public HistoryFileMedia { public: HistoryPhoto( not_null<Element*> parent, not_null<HistoryItem*> realParent, not_null<PhotoData*> photo); HistoryPhoto( not_null<Element*> parent, not_null<PeerData*> chat, not_null<PhotoData*> photo, int width); HistoryMediaType type() const override { return MediaTypePhoto; } void draw(Painter &p, const QRect &clip, TextSelection selection, TimeMs ms) const override; TextState textState(QPoint point, StateRequest request) const override; [[nodiscard]] TextSelection adjustSelection( TextSelection selection, TextSelectType type) const override { return _caption.adjustSelection(selection, type); } uint16 fullSelectionLength() const override { return _caption.length(); } bool hasTextForCopy() const override { return !_caption.isEmpty(); } TextWithEntities selectedText(TextSelection selection) const override; PhotoData *getPhoto() const override { return _data; } QSize sizeForGrouping() const override; void drawGrouped( Painter &p, const QRect &clip, TextSelection selection, TimeMs ms, const QRect &geometry, RectParts corners, not_null<uint64*> cacheKey, not_null<QPixmap*> cache) const override; TextState getStateGrouped( const QRect &geometry, QPoint point, StateRequest request) const override; TextWithEntities getCaption() const override { return _caption.originalTextWithEntities(); } bool needsBubble() const override; bool customInfoLayout() const override { return _caption.isEmpty(); } bool skipBubbleTail() const override { return isBubbleBottom() && _caption.isEmpty(); } bool isReadyForOpen() const override { return _data->loaded(); } void parentTextUpdated() override; protected: float64 dataProgress() const override; bool dataFinished() const override; bool dataLoaded() const override; private: void create(FullMsgId contextId, PeerData *chat = nullptr); QSize countOptimalSize() override; QSize countCurrentSize(int newWidth) override; bool needInfoDisplay() const; void validateGroupedCache( const QRect &geometry, RectParts corners, not_null<uint64*> cacheKey, not_null<QPixmap*> cache) const; not_null<PhotoData*> _data; int _serviceWidth = 0; int _pixw = 1; int _pixh = 1; Text _caption; }; class HistoryVideo : public HistoryFileMedia { public: HistoryVideo( not_null<Element*> parent, not_null<HistoryItem*> realParent, not_null<DocumentData*> document); HistoryMediaType type() const override { return MediaTypeVideo; } void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override; TextState textState(QPoint point, StateRequest request) const override; [[nodiscard]] TextSelection adjustSelection( TextSelection selection, TextSelectType type) const override { return _caption.adjustSelection(selection, type); } uint16 fullSelectionLength() const override { return _caption.length(); } bool hasTextForCopy() const override { return !_caption.isEmpty(); } TextWithEntities selectedText(TextSelection selection) const override; DocumentData *getDocument() const override { return _data; } QSize sizeForGrouping() const override; void drawGrouped( Painter &p, const QRect &clip, TextSelection selection, TimeMs ms, const QRect &geometry, RectParts corners, not_null<uint64*> cacheKey, not_null<QPixmap*> cache) const override; TextState getStateGrouped( const QRect &geometry, QPoint point, StateRequest request) const override; bool uploading() const override { return _data->uploading(); } TextWithEntities getCaption() const override { return _caption.originalTextWithEntities(); } bool needsBubble() const override; bool customInfoLayout() const override { return _caption.isEmpty(); } bool skipBubbleTail() const override { return isBubbleBottom() && _caption.isEmpty(); } void parentTextUpdated() override; protected: float64 dataProgress() const override; bool dataFinished() const override; bool dataLoaded() const override; private: QSize countOptimalSize() override; QSize countCurrentSize(int newWidth) override; void validateGroupedCache( const QRect &geometry, RectParts corners, not_null<uint64*> cacheKey, not_null<QPixmap*> cache) const; void setStatusSize(int newSize) const; void updateStatusText() const; not_null<DocumentData*> _data; int _thumbw; Text _caption; }; class HistoryDocument : public HistoryFileMedia , public RuntimeComposer<HistoryDocument> { public: HistoryDocument( not_null<Element*> parent, not_null<DocumentData*> document); HistoryMediaType type() const override { return _data->isVoiceMessage() ? MediaTypeVoiceFile : (_data->isSong() ? MediaTypeMusicFile : MediaTypeFile); } void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override; TextState textState(QPoint point, StateRequest request) const override; void updatePressed(QPoint point) override; [[nodiscard]] TextSelection adjustSelection( TextSelection selection, TextSelectType type) const override; uint16 fullSelectionLength() const override; bool hasTextForCopy() const override; TextWithEntities selectedText(TextSelection selection) const override; bool uploading() const override { return _data->uploading(); } DocumentData *getDocument() const override { return _data; } TextWithEntities getCaption() const override; bool needsBubble() const override { return true; } bool customInfoLayout() const override { return false; } QMargins bubbleMargins() const override; bool hideForwardedFrom() const override { return _data->isSong(); } void step_voiceProgress(float64 ms, bool timer); void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override; void refreshParentId(not_null<HistoryItem*> realParent) override; void parentTextUpdated() override; protected: float64 dataProgress() const override; bool dataFinished() const override; bool dataLoaded() const override; private: QSize countOptimalSize() override; QSize countCurrentSize(int newWidth) override; void createComponents(bool caption); void fillNamedFromData(HistoryDocumentNamed *named); void setStatusSize(int newSize, qint64 realDuration = 0) const; bool updateStatusText() const; // returns showPause not_null<DocumentData*> _data; }; class HistoryGif : public HistoryFileMedia { public: HistoryGif( not_null<Element*> parent, not_null<DocumentData*> document); HistoryMediaType type() const override { return MediaTypeGif; } void refreshParentId(not_null<HistoryItem*> realParent) override; void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override; TextState textState(QPoint point, StateRequest request) const override; [[nodiscard]] TextSelection adjustSelection( TextSelection selection, TextSelectType type) const override { return _caption.adjustSelection(selection, type); } uint16 fullSelectionLength() const override { return _caption.length(); } bool hasTextForCopy() const override { return !_caption.isEmpty(); } TextWithEntities selectedText(TextSelection selection) const override; bool uploading() const override { return _data->uploading(); } DocumentData *getDocument() const override { return _data; } void stopAnimation() override; TextWithEntities getCaption() const override { return _caption.originalTextWithEntities(); } bool needsBubble() const override; bool customInfoLayout() const override { return _caption.isEmpty(); } QString additionalInfoString() const override; bool skipBubbleTail() const override { return isBubbleBottom() && _caption.isEmpty(); } bool isReadyForOpen() const override { return _data->loaded(); } void parentTextUpdated() override; ~HistoryGif(); protected: float64 dataProgress() const override; bool dataFinished() const override; bool dataLoaded() const override; void setClipReader(Media::Clip::ReaderPointer gif); void clearClipReader() { setClipReader(Media::Clip::ReaderPointer()); } private: void playAnimation(bool autoplay) override; QSize countOptimalSize() override; QSize countCurrentSize(int newWidth) override; Media::Player::RoundController *activeRoundVideo() const; Media::Clip::Reader *activeRoundPlayer() const; Media::Clip::Reader *currentReader() const; Media::Clip::Playback *videoPlayback() const; void clipCallback(Media::Clip::Notification notification); bool needInfoDisplay() const; int additionalWidth( const HistoryMessageVia *via, const HistoryMessageReply *reply, const HistoryMessageForwarded *forwarded) const; int additionalWidth() const; QString mediaTypeString() const; bool isSeparateRoundVideo() const; not_null<DocumentData*> _data; FileClickHandlerPtr _openInMediaviewLink; int _thumbw = 1; int _thumbh = 1; Text _caption; Media::Clip::ReaderPointer _gif; void setStatusSize(int newSize) const; void updateStatusText() const; }; class HistorySticker : public HistoryMedia { public: HistorySticker( not_null<Element*> parent, not_null<DocumentData*> document); HistoryMediaType type() const override { return MediaTypeSticker; } void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override; TextState textState(QPoint point, StateRequest request) const override; bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override { return true; } bool dragItem() const override { return true; } bool dragItemByHandler(const ClickHandlerPtr &p) const override { return true; } DocumentData *getDocument() const override { return _data; } bool needsBubble() const override { return false; } bool customInfoLayout() const override { return true; } QString emoji() const { return _emoji; } private: QSize countOptimalSize() override; QSize countCurrentSize(int newWidth) override; int additionalWidth(const HistoryMessageVia *via, const HistoryMessageReply *reply) const; int additionalWidth() const; int _pixw = 1; int _pixh = 1; ClickHandlerPtr _packLink; not_null<DocumentData*> _data; QString _emoji; }; class HistoryContact : public HistoryMedia { public: HistoryContact( not_null<Element*> parent, UserId userId, const QString &first, const QString &last, const QString &phone); ~HistoryContact(); HistoryMediaType type() const override { return MediaTypeContact; } void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override; TextState textState(QPoint point, StateRequest request) const override; bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override { return true; } bool dragItemByHandler(const ClickHandlerPtr &p) const override { return true; } bool needsBubble() const override { return true; } bool customInfoLayout() const override { return false; } const QString &fname() const { return _fname; } const QString &lname() const { return _lname; } const QString &phone() const { return _phone; } // Should be called only by Data::Session. void updateSharedContactUserId(UserId userId) override; private: QSize countOptimalSize() override; UserId _userId = 0; UserData *_contact = nullptr; int _phonew = 0; QString _fname, _lname, _phone; Text _name; std::unique_ptr<Ui::EmptyUserpic> _photoEmpty; ClickHandlerPtr _linkl; int _linkw = 0; QString _link; }; class HistoryCall : public HistoryMedia { public: HistoryCall( not_null<Element*> parent, not_null<Data::Call*> call); HistoryMediaType type() const override { return MediaTypeCall; } void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override; TextState textState(QPoint point, StateRequest request) const override; bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override { return true; } bool dragItemByHandler(const ClickHandlerPtr &p) const override { return false; } bool needsBubble() const override { return true; } bool customInfoLayout() const override { return true; } Data::CallFinishReason reason() const; private: using FinishReason = Data::CallFinishReason; QSize countOptimalSize() override; FinishReason _reason; int _duration = 0; QString _text; QString _status; ClickHandlerPtr _link; }; class HistoryWebPage : public HistoryMedia { public: HistoryWebPage( not_null<Element*> parent, not_null<WebPageData*> data); HistoryMediaType type() const override { return MediaTypeWebPage; } void refreshParentId(not_null<HistoryItem*> realParent) override; void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override; TextState textState(QPoint point, StateRequest request) const override; bool hideMessageText() const override { return false; } [[nodiscard]] TextSelection adjustSelection( TextSelection selection, TextSelectType type) const override; uint16 fullSelectionLength() const override { return _title.length() + _description.length(); } bool hasTextForCopy() const override { return false; // we do not add _title and _description in FullSelection text copy. } bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override { return _attach && _attach->toggleSelectionByHandlerClick(p); } bool dragItemByHandler(const ClickHandlerPtr &p) const override { return _attach && _attach->dragItemByHandler(p); } TextWithEntities selectedText(TextSelection selection) const override; void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override; bool isDisplayed() const override; PhotoData *getPhoto() const override { return _attach ? _attach->getPhoto() : nullptr; } DocumentData *getDocument() const override { return _attach ? _attach->getDocument() : nullptr; } void stopAnimation() override { if (_attach) _attach->stopAnimation(); } not_null<WebPageData*> webpage() { return _data; } bool needsBubble() const override { return true; } bool customInfoLayout() const override { return false; } bool allowsFastShare() const override { return true; } HistoryMedia *attach() const { return _attach.get(); } ~HistoryWebPage(); private: void playAnimation(bool autoplay) override; QSize countOptimalSize() override; QSize countCurrentSize(int newWidth) override; TextSelection toDescriptionSelection(TextSelection selection) const; TextSelection fromDescriptionSelection(TextSelection selection) const; QMargins inBubblePadding() const; int bottomInfoPadding() const; bool isLogEntryOriginal() const; not_null<WebPageData*> _data; ClickHandlerPtr _openl; std::unique_ptr<HistoryMedia> _attach; bool _asArticle = false; int _dataVersion = -1; int _titleLines = 0; int _descriptionLines = 0; Text _title, _description; int _siteNameWidth = 0; QString _duration; int _durationWidth = 0; int _pixw = 0; int _pixh = 0; }; class HistoryGame : public HistoryMedia { public: HistoryGame( not_null<Element*> parent, not_null<GameData*> data, const TextWithEntities &consumed); HistoryMediaType type() const override { return MediaTypeGame; } void refreshParentId(not_null<HistoryItem*> realParent) override; void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override; TextState textState(QPoint point, StateRequest request) const override; [[nodiscard]] TextSelection adjustSelection( TextSelection selection, TextSelectType type) const override; uint16 fullSelectionLength() const override { return _title.length() + _description.length(); } bool hasTextForCopy() const override { return false; // we do not add _title and _description in FullSelection text copy. } bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override { return _attach && _attach->toggleSelectionByHandlerClick(p); } bool dragItemByHandler(const ClickHandlerPtr &p) const override { return _attach && _attach->dragItemByHandler(p); } TextWithEntities selectedText(TextSelection selection) const override; void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override; PhotoData *getPhoto() const override { return _attach ? _attach->getPhoto() : nullptr; } DocumentData *getDocument() const override { return _attach ? _attach->getDocument() : nullptr; } void stopAnimation() override { if (_attach) _attach->stopAnimation(); } not_null<GameData*> game() { return _data; } bool needsBubble() const override { return true; } bool customInfoLayout() const override { return false; } bool allowsFastShare() const override { return true; } HistoryMedia *attach() const { return _attach.get(); } void parentTextUpdated() override; ~HistoryGame(); private: void playAnimation(bool autoplay) override; QSize countOptimalSize() override; QSize countCurrentSize(int newWidth) override; TextSelection toDescriptionSelection(TextSelection selection) const; TextSelection fromDescriptionSelection(TextSelection selection) const; QMargins inBubblePadding() const; int bottomInfoPadding() const; not_null<GameData*> _data; std::shared_ptr<ReplyMarkupClickHandler> _openl; std::unique_ptr<HistoryMedia> _attach; int _titleLines, _descriptionLines; Text _title, _description; int _gameTagWidth = 0; }; class HistoryInvoice : public HistoryMedia { public: HistoryInvoice( not_null<Element*> parent, not_null<Data::Invoice*> invoice); HistoryMediaType type() const override { return MediaTypeInvoice; } void refreshParentId(not_null<HistoryItem*> realParent) override; MsgId getReceiptMsgId() const { return _receiptMsgId; } QString getTitle() const { return _title.originalText(); } static QString fillAmountAndCurrency(uint64 amount, const QString ¤cy); bool hideMessageText() const override { return false; } void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override; TextState textState(QPoint point, StateRequest request) const override; [[nodiscard]] TextSelection adjustSelection( TextSelection selection, TextSelectType type) const override; uint16 fullSelectionLength() const override { return _title.length() + _description.length(); } bool hasTextForCopy() const override { return false; // we do not add _title and _description in FullSelection text copy. } bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override { return _attach && _attach->toggleSelectionByHandlerClick(p); } bool dragItemByHandler(const ClickHandlerPtr &p) const override { return _attach && _attach->dragItemByHandler(p); } TextWithEntities selectedText(TextSelection selection) const override; void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override; bool needsBubble() const override { return true; } bool customInfoLayout() const override { return false; } HistoryMedia *attach() const { return _attach.get(); } private: QSize countOptimalSize() override; QSize countCurrentSize(int newWidth) override; void fillFromData(not_null<Data::Invoice*> invoice); TextSelection toDescriptionSelection(TextSelection selection) const; TextSelection fromDescriptionSelection(TextSelection selection) const; QMargins inBubblePadding() const; int bottomInfoPadding() const; std::unique_ptr<HistoryMedia> _attach; int _titleHeight = 0; int _descriptionHeight = 0; Text _title; Text _description; Text _status; MsgId _receiptMsgId = 0; }; class LocationCoords; struct LocationData; class HistoryLocation : public HistoryMedia { public: HistoryLocation( not_null<Element*> parent, not_null<LocationData*> location, const QString &title = QString(), const QString &description = QString()); HistoryMediaType type() const override { return MediaTypeLocation; } void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override; TextState textState(QPoint point, StateRequest request) const override; [[nodiscard]] TextSelection adjustSelection( TextSelection selection, TextSelectType type) const override; uint16 fullSelectionLength() const override { return _title.length() + _description.length(); } bool hasTextForCopy() const override { return !_title.isEmpty() || !_description.isEmpty(); } bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override { return p == _link; } bool dragItemByHandler(const ClickHandlerPtr &p) const override { return p == _link; } TextWithEntities selectedText(TextSelection selection) const override; bool needsBubble() const override; bool customInfoLayout() const override { return true; } bool skipBubbleTail() const override { return isBubbleBottom(); } private: QSize countOptimalSize() override; QSize countCurrentSize(int newWidth) override; TextSelection toDescriptionSelection(TextSelection selection) const; TextSelection fromDescriptionSelection(TextSelection selection) const; LocationData *_data; Text _title, _description; ClickHandlerPtr _link; int fullWidth() const; int fullHeight() const; }; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/history_message.cpp������������������������������������0000664�0000000�0000000�00000075046�13262451251�0025051�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/history_message.h" #include "lang/lang_keys.h" #include "mainwidget.h" #include "mainwindow.h" #include "apiwrap.h" #include "history/history.h" #include "history/history_item_components.h" #include "history/history_location_manager.h" #include "history/history_media_types.h" #include "history/history_service.h" #include "history/view/history_view_service_message.h" #include "auth_session.h" #include "boxes/share_box.h" #include "boxes/confirm_box.h" #include "ui/toast/toast.h" #include "ui/text_options.h" #include "messenger.h" #include "layout.h" #include "window/notifications_manager.h" #include "window/window_controller.h" #include "observer_peer.h" #include "storage/storage_shared_media.h" #include "data/data_session.h" #include "data/data_media_types.h" #include "styles/style_dialogs.h" #include "styles/style_widgets.h" #include "styles/style_history.h" #include "styles/style_window.h" namespace { constexpr auto kPinnedMessageTextLimit = 16; MTPDmessage::Flags NewForwardedFlags( not_null<PeerData*> peer, UserId from, not_null<HistoryMessage*> fwd) { auto result = NewMessageFlags(peer) | MTPDmessage::Flag::f_fwd_from; if (from) { result |= MTPDmessage::Flag::f_from_id; } if (fwd->Has<HistoryMessageVia>()) { result |= MTPDmessage::Flag::f_via_bot_id; } if (const auto channel = peer->asChannel()) { if (dynamic_cast<Data::MediaWebPage*>(fwd->media())) { // Drop web page if we're not allowed to send it. if (channel->restricted( ChannelRestriction::f_embed_links)) { result &= ~MTPDmessage::Flag::f_media; } } } else if (const auto media = fwd->media()) { if (media->forwardedBecomesUnread()) { result |= MTPDmessage::Flag::f_media_unread; } } if (fwd->hasViews()) { result |= MTPDmessage::Flag::f_views; } return result; } bool HasInlineItems(const HistoryItemsList &items) { for (const auto item : items) { if (item->viaBot()) { return true; } } return false; } } // namespace void FastShareMessage(not_null<HistoryItem*> item) { struct ShareData { ShareData(not_null<PeerData*> peer, MessageIdsList &&ids) : peer(peer) , msgIds(std::move(ids)) { } not_null<PeerData*> peer; MessageIdsList msgIds; base::flat_set<mtpRequestId> requests; }; const auto data = std::make_shared<ShareData>( item->history()->peer, Auth().data().itemOrItsGroup(item)); const auto isGroup = (Auth().data().groups().find(item) != nullptr); const auto isGame = item->getMessageBot() && item->media() && (item->media()->game() != nullptr); const auto canCopyLink = item->hasDirectLink() || isGame; auto copyCallback = [data]() { if (auto main = App::main()) { if (auto item = App::histItemById(data->msgIds[0])) { if (item->hasDirectLink()) { QApplication::clipboard()->setText(item->directLink()); Ui::Toast::Show(lang(lng_channel_public_link_copied)); } else if (const auto bot = item->getMessageBot()) { if (const auto media = item->media()) { if (const auto game = media->game()) { const auto link = Messenger::Instance().createInternalLinkFull( bot->username + qsl("?game=") + game->shortName); QApplication::clipboard()->setText(link); Ui::Toast::Show(lang(lng_share_game_link_copied)); } } } } } }; auto submitCallback = [data, isGroup](const QVector<PeerData*> &result) { if (!data->requests.empty()) { return; // Share clicked already. } auto items = Auth().data().idsToItems(data->msgIds); if (items.empty() || result.empty()) { return; } auto restrictedSomewhere = false; auto restrictedEverywhere = true; auto firstError = QString(); for (const auto peer : result) { const auto error = GetErrorTextForForward(peer, items); if (!error.isEmpty()) { if (firstError.isEmpty()) { firstError = error; } restrictedSomewhere = true; continue; } restrictedEverywhere = false; } if (restrictedEverywhere) { Ui::show( Box<InformBox>(firstError), LayerOption::KeepOther); return; } auto doneCallback = [data](const MTPUpdates &updates, mtpRequestId requestId) { if (auto main = App::main()) { main->sentUpdatesReceived(updates); } data->requests.remove(requestId); if (data->requests.empty()) { Ui::Toast::Show(lang(lng_share_done)); Ui::hideLayer(); } }; const auto sendFlags = MTPmessages_ForwardMessages::Flag(0) | MTPmessages_ForwardMessages::Flag::f_with_my_score | (isGroup ? MTPmessages_ForwardMessages::Flag::f_grouped : MTPmessages_ForwardMessages::Flag(0)); auto msgIds = QVector<MTPint>(); msgIds.reserve(data->msgIds.size()); for (const auto fullId : data->msgIds) { msgIds.push_back(MTP_int(fullId.msg)); } auto generateRandom = [&] { auto result = QVector<MTPlong>(data->msgIds.size()); for (auto &value : result) { value = rand_value<MTPlong>(); } return result; }; if (auto main = App::main()) { for (const auto peer : result) { if (!GetErrorTextForForward(peer, items).isEmpty()) { continue; } auto request = MTPmessages_ForwardMessages( MTP_flags(sendFlags), data->peer->input, MTP_vector<MTPint>(msgIds), MTP_vector<MTPlong>(generateRandom()), peer->input); auto callback = doneCallback; auto requestId = MTP::send(request, rpcDone(std::move(callback))); data->requests.insert(requestId); } } }; auto filterCallback = [isGame](PeerData *peer) { if (peer->canWrite()) { if (auto channel = peer->asChannel()) { return isGame ? (!channel->isBroadcast()) : true; } return true; } return false; }; auto copyLinkCallback = canCopyLink ? base::lambda<void()>(std::move(copyCallback)) : base::lambda<void()>(); Ui::show(Box<ShareBox>( std::move(copyLinkCallback), std::move(submitCallback), std::move(filterCallback))); } base::lambda<void(ChannelData*, MsgId)> HistoryDependentItemCallback( const FullMsgId &msgId) { return [dependent = msgId](ChannelData *channel, MsgId msgId) { if (auto item = App::histItemById(dependent)) { item->updateDependencyItem(); } }; } MTPDmessage::Flags NewMessageFlags(not_null<PeerData*> peer) { MTPDmessage::Flags result = 0; if (!peer->isSelf()) { result |= MTPDmessage::Flag::f_out; //if (p->isChat() || (p->isUser() && !p->asUser()->botInfo)) { // result |= MTPDmessage::Flag::f_unread; //} } return result; } QString GetErrorTextForForward( not_null<PeerData*> peer, const HistoryItemsList &items) { if (!peer->canWrite()) { return lang(lng_forward_cant); } if (auto megagroup = peer->asMegagroup()) { for (const auto item : items) { if (const auto media = item->media()) { const auto error = media->errorTextForForward(megagroup); if (!error.isEmpty() && error != qstr("skip")) { return error; } } } if (megagroup->restricted(ChannelRestriction::f_send_inline) && HasInlineItems(items)) { return lang(lng_restricted_send_inline); } } return QString(); } struct HistoryMessage::CreateConfig { MsgId replyTo = 0; UserId viaBotId = 0; int viewsCount = -1; QString author; PeerId senderOriginal = 0; MsgId originalId = 0; PeerId savedFromPeer = 0; MsgId savedFromMsgId = 0; QString authorOriginal; TimeId originalDate = 0; TimeId editDate = 0; // For messages created from MTP structs. const MTPReplyMarkup *mtpMarkup = nullptr; // For messages created from existing messages (forwarded). const HistoryMessageReplyMarkup *inlineMarkup = nullptr; }; HistoryMessage::HistoryMessage( not_null<History*> history, const MTPDmessage &data) : HistoryItem( history, data.vid.v, data.vflags.v, data.vdate.v, data.has_from_id() ? data.vfrom_id.v : UserId(0)) { CreateConfig config; if (data.has_fwd_from() && data.vfwd_from.type() == mtpc_messageFwdHeader) { auto &f = data.vfwd_from.c_messageFwdHeader(); config.originalDate = f.vdate.v; if (f.has_from_id() || f.has_channel_id()) { config.senderOriginal = f.has_channel_id() ? peerFromChannel(f.vchannel_id) : peerFromUser(f.vfrom_id); if (f.has_channel_post()) config.originalId = f.vchannel_post.v; if (f.has_post_author()) config.authorOriginal = qs(f.vpost_author); if (f.has_saved_from_peer() && f.has_saved_from_msg_id()) { config.savedFromPeer = peerFromMTP(f.vsaved_from_peer); config.savedFromMsgId = f.vsaved_from_msg_id.v; } } } if (data.has_reply_to_msg_id()) config.replyTo = data.vreply_to_msg_id.v; if (data.has_via_bot_id()) config.viaBotId = data.vvia_bot_id.v; if (data.has_views()) config.viewsCount = data.vviews.v; if (data.has_reply_markup()) config.mtpMarkup = &data.vreply_markup; if (data.has_edit_date()) config.editDate = data.vedit_date.v; if (data.has_post_author()) config.author = qs(data.vpost_author); createComponents(config); if (data.has_media()) { setMedia(data.vmedia); } auto text = TextUtilities::Clean(qs(data.vmessage)); auto entities = data.has_entities() ? TextUtilities::EntitiesFromMTP(data.ventities.v) : EntitiesInText(); setText({ text, entities }); if (data.has_grouped_id()) { setGroupId(MessageGroupId::FromRaw(data.vgrouped_id.v)); } } HistoryMessage::HistoryMessage( not_null<History*> history, const MTPDmessageService &data) : HistoryItem( history, data.vid.v, mtpCastFlags(data.vflags.v), data.vdate.v, data.has_from_id() ? data.vfrom_id.v : UserId(0)) { CreateConfig config; if (data.has_reply_to_msg_id()) config.replyTo = data.vreply_to_msg_id.v; createComponents(config); switch (data.vaction.type()) { case mtpc_messageActionPhoneCall: { _media = std::make_unique<Data::MediaCall>( this, data.vaction.c_messageActionPhoneCall()); } break; default: Unexpected("Service message action type in HistoryMessage."); } setText(TextWithEntities {}); } HistoryMessage::HistoryMessage( not_null<History*> history, MsgId id, MTPDmessage::Flags flags, TimeId date, UserId from, const QString &postAuthor, not_null<HistoryMessage*> original) : HistoryItem( history, id, NewForwardedFlags(history->peer, from, original) | flags, date, from) { CreateConfig config; if (original->Has<HistoryMessageForwarded>() || !original->history()->peer->isSelf()) { // Server doesn't add "fwd_from" to non-forwarded messages from chat with yourself. config.originalDate = original->dateOriginal(); auto senderOriginal = original->senderOriginal(); config.senderOriginal = senderOriginal->id; config.authorOriginal = original->authorOriginal(); if (senderOriginal->isChannel()) { config.originalId = original->idOriginal(); } } if (history->peer->isSelf()) { // // iOS app sends you to the original post if we forward a forward from channel. // But server returns not the original post but the forward in saved_from_... // //if (config.originalId) { // config.savedFromPeer = config.senderOriginal; // config.savedFromMsgId = config.originalId; //} else { config.savedFromPeer = original->history()->peer->id; config.savedFromMsgId = original->id; //} } if (flags & MTPDmessage::Flag::f_post_author) { config.author = postAuthor; } auto fwdViaBot = original->viaBot(); if (fwdViaBot) config.viaBotId = peerToUser(fwdViaBot->id); int fwdViewsCount = original->viewsCount(); if (fwdViewsCount > 0) { config.viewsCount = fwdViewsCount; } else if (isPost()) { config.viewsCount = 1; } // Copy inline keyboard when forwarding messages with a game. auto mediaOriginal = original->media(); if (mediaOriginal && mediaOriginal->game()) { config.inlineMarkup = original->inlineReplyMarkup(); } createComponents(config); auto ignoreMedia = [&] { if (mediaOriginal && mediaOriginal->webpage()) { if (const auto channel = history->peer->asChannel()) { if (channel->restricted(ChannelRestriction::f_embed_links)) { return true; } } } return false; }; if (mediaOriginal && !ignoreMedia()) { _media = mediaOriginal->clone(this); } setText(original->originalText()); } HistoryMessage::HistoryMessage( not_null<History*> history, MsgId id, MTPDmessage::Flags flags, MsgId replyTo, UserId viaBotId, TimeId date, UserId from, const QString &postAuthor, const TextWithEntities &textWithEntities) : HistoryItem(history, id, flags, date, (flags & MTPDmessage::Flag::f_from_id) ? from : 0) { createComponentsHelper(flags, replyTo, viaBotId, postAuthor, MTPnullMarkup); setText(textWithEntities); } HistoryMessage::HistoryMessage( not_null<History*> history, MsgId id, MTPDmessage::Flags flags, MsgId replyTo, UserId viaBotId, TimeId date, UserId from, const QString &postAuthor, not_null<DocumentData*> document, const TextWithEntities &caption, const MTPReplyMarkup &markup) : HistoryItem(history, id, flags, date, (flags & MTPDmessage::Flag::f_from_id) ? from : 0) { createComponentsHelper(flags, replyTo, viaBotId, postAuthor, markup); _media = std::make_unique<Data::MediaFile>(this, document); setText(caption); } HistoryMessage::HistoryMessage( not_null<History*> history, MsgId id, MTPDmessage::Flags flags, MsgId replyTo, UserId viaBotId, TimeId date, UserId from, const QString &postAuthor, not_null<PhotoData*> photo, const TextWithEntities &caption, const MTPReplyMarkup &markup) : HistoryItem(history, id, flags, date, (flags & MTPDmessage::Flag::f_from_id) ? from : 0) { createComponentsHelper(flags, replyTo, viaBotId, postAuthor, markup); _media = std::make_unique<Data::MediaPhoto>(this, photo); setText(caption); } HistoryMessage::HistoryMessage( not_null<History*> history, MsgId id, MTPDmessage::Flags flags, MsgId replyTo, UserId viaBotId, TimeId date, UserId from, const QString &postAuthor, not_null<GameData*> game, const MTPReplyMarkup &markup) : HistoryItem(history, id, flags, date, (flags & MTPDmessage::Flag::f_from_id) ? from : 0) { createComponentsHelper(flags, replyTo, viaBotId, postAuthor, markup); _media = std::make_unique<Data::MediaGame>(this, game); setText(TextWithEntities()); } void HistoryMessage::createComponentsHelper( MTPDmessage::Flags flags, MsgId replyTo, UserId viaBotId, const QString &postAuthor, const MTPReplyMarkup &markup) { CreateConfig config; if (flags & MTPDmessage::Flag::f_via_bot_id) config.viaBotId = viaBotId; if (flags & MTPDmessage::Flag::f_reply_to_msg_id) config.replyTo = replyTo; if (flags & MTPDmessage::Flag::f_reply_markup) config.mtpMarkup = &markup; if (flags & MTPDmessage::Flag::f_post_author) config.author = postAuthor; if (isPost()) config.viewsCount = 1; createComponents(config); } int HistoryMessage::viewsCount() const { if (const auto views = Get<HistoryMessageViews>()) { return views->_views; } return HistoryItem::viewsCount(); } not_null<PeerData*> HistoryMessage::displayFrom() const { return history()->peer->isSelf() ? senderOriginal() : author(); } bool HistoryMessage::updateDependencyItem() { if (const auto reply = Get<HistoryMessageReply>()) { return reply->updateData(this, true); } return true; } void HistoryMessage::updateAdminBadgeState() { auto hasAdminBadge = [&] { if (auto channel = history()->peer->asChannel()) { if (auto user = author()->asUser()) { return channel->isGroupAdmin(user); } } return false; }(); if (hasAdminBadge) { _flags |= MTPDmessage_ClientFlag::f_has_admin_badge; } else { _flags &= ~MTPDmessage_ClientFlag::f_has_admin_badge; } } void HistoryMessage::applyGroupAdminChanges( const base::flat_map<UserId, bool> &changes) { auto i = changes.find(peerToUser(author()->id)); if (i != changes.end()) { if (i->second) { _flags |= MTPDmessage_ClientFlag::f_has_admin_badge; } else { _flags &= ~MTPDmessage_ClientFlag::f_has_admin_badge; } Auth().data().requestItemResize(this); } } bool HistoryMessage::allowsForward() const { if (id < 0 || isLogEntry()) { return false; } return !_media || _media->allowsForward(); } bool HistoryMessage::allowsEdit(TimeId now) const { const auto peer = _history->peer; const auto messageToMyself = peer->isSelf(); const auto canPinInMegagroup = [&] { if (const auto megagroup = peer->asMegagroup()) { return megagroup->canPinMessages(); } return false; }(); const auto messageTooOld = (messageToMyself || canPinInMegagroup) ? false : (now - date() >= Global::EditTimeLimit()); if (id < 0 || messageTooOld) { return false; } if (Has<HistoryMessageVia>() || Has<HistoryMessageForwarded>()) { return false; } if (_media && !_media->allowsEdit()) { return false; } if (messageToMyself) { return true; } if (const auto channel = _history->peer->asChannel()) { if (isPost() && channel->canEditMessages()) { return true; } if (out()) { return !isPost() || channel->canPublish(); } } return out(); } bool HistoryMessage::uploading() const { return _media && _media->uploading(); } void HistoryMessage::createComponents(const CreateConfig &config) { uint64 mask = 0; if (config.replyTo) { mask |= HistoryMessageReply::Bit(); } if (config.viaBotId) { mask |= HistoryMessageVia::Bit(); } if (config.viewsCount >= 0) { mask |= HistoryMessageViews::Bit(); } if (!config.author.isEmpty()) { mask |= HistoryMessageSigned::Bit(); } auto hasViaBot = (config.viaBotId != 0); auto hasInlineMarkup = [&config] { if (config.mtpMarkup) { return (config.mtpMarkup->type() == mtpc_replyInlineMarkup); } return (config.inlineMarkup != nullptr); }; if (config.editDate != TimeId(0)) { mask |= HistoryMessageEdited::Bit(); } if (config.senderOriginal) { mask |= HistoryMessageForwarded::Bit(); } if (config.mtpMarkup) { // optimization: don't create markup component for the case // MTPDreplyKeyboardHide with flags = 0, assume it has f_zero flag if (config.mtpMarkup->type() != mtpc_replyKeyboardHide || config.mtpMarkup->c_replyKeyboardHide().vflags.v != 0) { mask |= HistoryMessageReplyMarkup::Bit(); } } else if (config.inlineMarkup) { mask |= HistoryMessageReplyMarkup::Bit(); } UpdateComponents(mask); if (const auto reply = Get<HistoryMessageReply>()) { reply->replyToMsgId = config.replyTo; if (!reply->updateData(this)) { Auth().api().requestMessageData( history()->peer->asChannel(), reply->replyToMsgId, HistoryDependentItemCallback(fullId())); } } if (const auto via = Get<HistoryMessageVia>()) { via->create(config.viaBotId); } if (const auto views = Get<HistoryMessageViews>()) { views->_views = config.viewsCount; } if (const auto edited = Get<HistoryMessageEdited>()) { edited->date = config.editDate; } if (const auto msgsigned = Get<HistoryMessageSigned>()) { msgsigned->author = config.author; } if (const auto forwarded = Get<HistoryMessageForwarded>()) { forwarded->originalDate = config.originalDate; forwarded->originalSender = App::peer(config.senderOriginal); forwarded->originalId = config.originalId; forwarded->originalAuthor = config.authorOriginal; forwarded->savedFromPeer = App::peerLoaded(config.savedFromPeer); forwarded->savedFromMsgId = config.savedFromMsgId; } if (const auto markup = Get<HistoryMessageReplyMarkup>()) { if (config.mtpMarkup) { markup->create(*config.mtpMarkup); } else if (config.inlineMarkup) { markup->create(*config.inlineMarkup); } if (markup->flags & MTPDreplyKeyboardMarkup_ClientFlag::f_has_switch_inline_button) { _flags |= MTPDmessage_ClientFlag::f_has_switch_inline_button; } } _fromNameVersion = displayFrom()->nameVersion; } QString FormatViewsCount(int views) { if (views > 999999) { views /= 100000; if (views % 10) { return QString::number(views / 10) + '.' + QString::number(views % 10) + 'M'; } return QString::number(views / 10) + 'M'; } else if (views > 9999) { views /= 100; if (views % 10) { return QString::number(views / 10) + '.' + QString::number(views % 10) + 'K'; } return QString::number(views / 10) + 'K'; } else if (views > 0) { return QString::number(views); } return qsl("1"); } void HistoryMessage::refreshMedia(const MTPMessageMedia *media) { _media = nullptr; if (media) { setMedia(*media); } } void HistoryMessage::refreshSentMedia(const MTPMessageMedia *media) { const auto wasGrouped = Auth().data().groups().isGrouped(this); refreshMedia(media); if (wasGrouped) { Auth().data().groups().refreshMessage(this); } else { Auth().data().requestItemViewRefresh(this); } } void HistoryMessage::setMedia(const MTPMessageMedia &media) { _media = CreateMedia(this, media); if (const auto invoice = _media ? _media->invoice() : nullptr) { if (invoice->receiptMsgId) { replaceBuyWithReceiptInMarkup(); } } } std::unique_ptr<Data::Media> HistoryMessage::CreateMedia( not_null<HistoryMessage*> item, const MTPMessageMedia &media) { switch (media.type()) { case mtpc_messageMediaContact: { const auto &data = media.c_messageMediaContact(); return std::make_unique<Data::MediaContact>( item, data.vuser_id.v, qs(data.vfirst_name), qs(data.vlast_name), qs(data.vphone_number)); } break; case mtpc_messageMediaGeo: { const auto &data = media.c_messageMediaGeo().vgeo; if (data.type() == mtpc_geoPoint) { return std::make_unique<Data::MediaLocation>( item, LocationCoords(data.c_geoPoint())); } } break; case mtpc_messageMediaGeoLive: { const auto &data = media.c_messageMediaGeoLive().vgeo; if (data.type() == mtpc_geoPoint) { return std::make_unique<Data::MediaLocation>( item, LocationCoords(data.c_geoPoint())); } } break; case mtpc_messageMediaVenue: { const auto &data = media.c_messageMediaVenue(); if (data.vgeo.type() == mtpc_geoPoint) { return std::make_unique<Data::MediaLocation>( item, LocationCoords(data.vgeo.c_geoPoint()), qs(data.vtitle), qs(data.vaddress)); } } break; case mtpc_messageMediaPhoto: { const auto &data = media.c_messageMediaPhoto(); if (data.has_ttl_seconds()) { LOG(("App Error: " "Unexpected MTPMessageMediaPhoto " "with ttl_seconds in HistoryMessage.")); } else if (data.has_photo() && data.vphoto.type() == mtpc_photo) { return std::make_unique<Data::MediaPhoto>( item, Auth().data().photo(data.vphoto.c_photo())); } else { LOG(("API Error: " "Got MTPMessageMediaPhoto " "without photo and without ttl_seconds.")); } } break; case mtpc_messageMediaDocument: { const auto &data = media.c_messageMediaDocument(); if (data.has_ttl_seconds()) { LOG(("App Error: " "Unexpected MTPMessageMediaDocument " "with ttl_seconds in HistoryMessage.")); } else if (data.has_document() && data.vdocument.type() == mtpc_document) { return std::make_unique<Data::MediaFile>( item, Auth().data().document(data.vdocument.c_document())); } else { LOG(("API Error: " "Got MTPMessageMediaDocument " "without document and without ttl_seconds.")); } } break; case mtpc_messageMediaWebPage: { const auto &data = media.c_messageMediaWebPage().vwebpage; switch (data.type()) { case mtpc_webPageEmpty: break; case mtpc_webPagePending: return std::make_unique<Data::MediaWebPage>( item, Auth().data().webpage(data.c_webPagePending())); break; case mtpc_webPage: return std::make_unique<Data::MediaWebPage>( item, Auth().data().webpage(data.c_webPage())); break; case mtpc_webPageNotModified: LOG(("API Error: " "webPageNotModified is unexpected in message media.")); break; } } break; case mtpc_messageMediaGame: { const auto &data = media.c_messageMediaGame().vgame; if (data.type() == mtpc_game) { return std::make_unique<Data::MediaGame>( item, Auth().data().game(data.c_game())); } } break; case mtpc_messageMediaInvoice: { return std::make_unique<Data::MediaInvoice>( item, media.c_messageMediaInvoice()); } break; }; return nullptr; } void HistoryMessage::replaceBuyWithReceiptInMarkup() { if (auto markup = inlineReplyMarkup()) { for (auto &row : markup->rows) { for (auto &button : row) { if (button.type == HistoryMessageMarkupButton::Type::Buy) { button.text = lang(lng_payments_receipt_button); } } } } } void HistoryMessage::applyEdition(const MTPDmessage &message) { int keyboardTop = -1; //if (!pendingResize()) {// #TODO edit bot message // if (auto keyboard = inlineReplyKeyboard()) { // int h = st::msgBotKbButton.margin + keyboard->naturalHeight(); // keyboardTop = _height - h + st::msgBotKbButton.margin - marginBottom(); // } //} if (message.has_edit_date()) { _flags |= MTPDmessage::Flag::f_edit_date; if (!Has<HistoryMessageEdited>()) { AddComponents(HistoryMessageEdited::Bit()); } auto edited = Get<HistoryMessageEdited>(); edited->date = message.vedit_date.v; } TextWithEntities textWithEntities = { qs(message.vmessage), EntitiesInText() }; if (message.has_entities()) { textWithEntities.entities = TextUtilities::EntitiesFromMTP(message.ventities.v); } setReplyMarkup(message.has_reply_markup() ? (&message.vreply_markup) : nullptr); refreshMedia(message.has_media() ? (&message.vmedia) : nullptr); setViewsCount(message.has_views() ? message.vviews.v : -1); setText(textWithEntities); finishEdition(keyboardTop); } void HistoryMessage::applyEdition(const MTPDmessageService &message) { if (message.vaction.type() == mtpc_messageActionHistoryClear) { applyEditionToEmpty(); } } void HistoryMessage::applyEditionToEmpty() { setReplyMarkup(nullptr); refreshMedia(nullptr); setEmptyText(); setViewsCount(-1); finishEditionToEmpty(); } void HistoryMessage::updateSentMedia(const MTPMessageMedia *media) { if (_flags & MTPDmessage_ClientFlag::f_from_inline_bot) { if (!media || !_media || !_media->updateInlineResultMedia(*media)) { refreshSentMedia(media); } _flags &= ~MTPDmessage_ClientFlag::f_from_inline_bot; } else { if (!media || !_media || !_media->updateSentMedia(*media)) { refreshSentMedia(media); } } Auth().data().requestItemResize(this); } void HistoryMessage::addToUnreadMentions(UnreadMentionType type) { if (IsServerMsgId(id) && mentionsMe() && isMediaUnread()) { if (history()->addToUnreadMentions(id, type)) { Notify::peerUpdatedDelayed( history()->peer, Notify::PeerUpdate::Flag::UnreadMentionsChanged); } } } void HistoryMessage::eraseFromUnreadMentions() { if (mentionsMe() && isMediaUnread()) { history()->eraseFromUnreadMentions(id); } } Storage::SharedMediaTypesMask HistoryMessage::sharedMediaTypes() const { auto result = Storage::SharedMediaTypesMask {}; if (const auto media = this->media()) { result.set(media->sharedMediaTypes()); } if (hasTextLinks()) { result.set(Storage::SharedMediaType::Link); } return result; } void HistoryMessage::setText(const TextWithEntities &textWithEntities) { for_const (auto &entity, textWithEntities.entities) { auto type = entity.type(); if (type == EntityInTextUrl || type == EntityInTextCustomUrl || type == EntityInTextEmail) { _flags |= MTPDmessage_ClientFlag::f_has_text_links; break; } } if (_media && _media->consumeMessageText(textWithEntities)) { setEmptyText(); } else { _text.setMarkedText( st::messageTextStyle, textWithEntities, Ui::ItemTextOptions(this)); if (!textWithEntities.text.isEmpty() && _text.isEmpty()) { // If server has allowed some text that we've trim-ed entirely, // just replace it with something so that UI won't look buggy. _text.setMarkedText( st::messageTextStyle, { QString::fromUtf8("\xF0\x9F\x98\x94"), EntitiesInText() }, Ui::ItemTextOptions(this)); } _textWidth = -1; _textHeight = 0; } } void HistoryMessage::setEmptyText() { _text.setMarkedText( st::messageTextStyle, { QString(), EntitiesInText() }, Ui::ItemTextOptions(this)); _textWidth = -1; _textHeight = 0; } void HistoryMessage::setReplyMarkup(const MTPReplyMarkup *markup) { if (!markup) { if (_flags & MTPDmessage::Flag::f_reply_markup) { _flags &= ~MTPDmessage::Flag::f_reply_markup; if (Has<HistoryMessageReplyMarkup>()) { RemoveComponents(HistoryMessageReplyMarkup::Bit()); } Auth().data().requestItemResize(this); Notify::replyMarkupUpdated(this); } return; } // optimization: don't create markup component for the case // MTPDreplyKeyboardHide with flags = 0, assume it has f_zero flag if (markup->type() == mtpc_replyKeyboardHide && markup->c_replyKeyboardHide().vflags.v == 0) { bool changed = false; if (Has<HistoryMessageReplyMarkup>()) { RemoveComponents(HistoryMessageReplyMarkup::Bit()); changed = true; } if (!(_flags & MTPDmessage::Flag::f_reply_markup)) { _flags |= MTPDmessage::Flag::f_reply_markup; changed = true; } if (changed) { Auth().data().requestItemResize(this); Notify::replyMarkupUpdated(this); } } else { if (!(_flags & MTPDmessage::Flag::f_reply_markup)) { _flags |= MTPDmessage::Flag::f_reply_markup; } if (!Has<HistoryMessageReplyMarkup>()) { AddComponents(HistoryMessageReplyMarkup::Bit()); } Get<HistoryMessageReplyMarkup>()->create(*markup); Auth().data().requestItemResize(this); Notify::replyMarkupUpdated(this); } } TextWithEntities HistoryMessage::originalText() const { if (emptyText()) { return { QString(), EntitiesInText() }; } return _text.originalTextWithEntities(); } TextWithEntities HistoryMessage::clipboardText() const { if (emptyText()) { return { QString(), EntitiesInText() }; } return _text.originalTextWithEntities(AllTextSelection, ExpandLinksAll); } bool HistoryMessage::textHasLinks() const { return emptyText() ? false : _text.hasLinks(); } void HistoryMessage::setViewsCount(int32 count) { const auto views = Get<HistoryMessageViews>(); if (!views || views->_views == count || (count >= 0 && views->_views > count)) { return; } const auto was = views->_viewsWidth; views->_views = count; views->_viewsText = (views->_views >= 0) ? FormatViewsCount(views->_views) : QString(); views->_viewsWidth = views->_viewsText.isEmpty() ? 0 : st::msgDateFont->width(views->_viewsText); if (was == views->_viewsWidth) { Auth().data().requestItemRepaint(this); } else { Auth().data().requestItemResize(this); } } void HistoryMessage::setRealId(MsgId newId) { HistoryItem::setRealId(newId); Auth().data().groups().refreshMessage(this); Auth().data().requestItemResize(this); } void HistoryMessage::dependencyItemRemoved(HistoryItem *dependency) { if (auto reply = Get<HistoryMessageReply>()) { reply->itemRemoved(this, dependency); } } QString HistoryMessage::notificationHeader() const { return (!_history->peer->isUser() && !isPost()) ? from()->name : QString(); } std::unique_ptr<HistoryView::Element> HistoryMessage::createView( not_null<HistoryView::ElementDelegate*> delegate) { return delegate->elementCreate(this); } HistoryMessage::~HistoryMessage() { _media.reset(); if (auto reply = Get<HistoryMessageReply>()) { reply->clearData(this); } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/history_message.h��������������������������������������0000664�0000000�0000000�00000011466�13262451251�0024512�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "history/history_item.h" namespace HistoryView { class Message; } // namespace HistoryView struct HistoryMessageEdited; base::lambda<void(ChannelData*, MsgId)> HistoryDependentItemCallback( const FullMsgId &msgId); MTPDmessage::Flags NewMessageFlags(not_null<PeerData*> peer); QString GetErrorTextForForward( not_null<PeerData*> peer, const HistoryItemsList &items); void FastShareMessage(not_null<HistoryItem*> item); QString FormatViewsCount(int views); class HistoryMessage : public HistoryItem { public: HistoryMessage( not_null<History*> history, const MTPDmessage &data); HistoryMessage( not_null<History*> history, const MTPDmessageService &data); HistoryMessage( not_null<History*> history, MsgId id, MTPDmessage::Flags flags, TimeId date, UserId from, const QString &postAuthor, not_null<HistoryMessage*> original); // local forwarded HistoryMessage( not_null<History*> history, MsgId id, MTPDmessage::Flags flags, MsgId replyTo, UserId viaBotId, TimeId date, UserId from, const QString &postAuthor, const TextWithEntities &textWithEntities); // local message HistoryMessage( not_null<History*> history, MsgId id, MTPDmessage::Flags flags, MsgId replyTo, UserId viaBotId, TimeId date, UserId from, const QString &postAuthor, not_null<DocumentData*> document, const TextWithEntities &caption, const MTPReplyMarkup &markup); // local document HistoryMessage( not_null<History*> history, MsgId id, MTPDmessage::Flags flags, MsgId replyTo, UserId viaBotId, TimeId date, UserId from, const QString &postAuthor, not_null<PhotoData*> photo, const TextWithEntities &caption, const MTPReplyMarkup &markup); // local photo HistoryMessage( not_null<History*> history, MsgId id, MTPDmessage::Flags flags, MsgId replyTo, UserId viaBotId, TimeId date, UserId from, const QString &postAuthor, not_null<GameData*> game, const MTPReplyMarkup &markup); // local game void refreshMedia(const MTPMessageMedia *media); void refreshSentMedia(const MTPMessageMedia *media); void setMedia(const MTPMessageMedia &media); static std::unique_ptr<Data::Media> CreateMedia( not_null<HistoryMessage*> item, const MTPMessageMedia &media); bool allowsForward() const override; bool allowsEdit(TimeId now) const override; bool uploading() const; void applyGroupAdminChanges( const base::flat_map<UserId, bool> &changes) override; void setViewsCount(int32 count) override; void setRealId(MsgId newId) override; void dependencyItemRemoved(HistoryItem *dependency) override; QString notificationHeader() const override; void applyEdition(const MTPDmessage &message) override; void applyEdition(const MTPDmessageService &message) override; void updateSentMedia(const MTPMessageMedia *media) override; void updateReplyMarkup(const MTPReplyMarkup *markup) override { setReplyMarkup(markup); } void addToUnreadMentions(UnreadMentionType type) override; void eraseFromUnreadMentions() override; Storage::SharedMediaTypesMask sharedMediaTypes() const override; void setText(const TextWithEntities &textWithEntities) override; TextWithEntities originalText() const override; TextWithEntities clipboardText() const override; bool textHasLinks() const override; int viewsCount() const override; not_null<PeerData*> displayFrom() const; bool updateDependencyItem() override; MsgId dependencyMsgId() const override { return replyToId(); } HistoryMessage *toHistoryMessage() override { // dynamic_cast optimize return this; } const HistoryMessage *toHistoryMessage() const override { // dynamic_cast optimize return this; } std::unique_ptr<HistoryView::Element> createView( not_null<HistoryView::ElementDelegate*> delegate) override; ~HistoryMessage(); private: void setEmptyText(); bool hasAdminBadge() const { return _flags & MTPDmessage_ClientFlag::f_has_admin_badge; } // For an invoice button we replace the button text with a "Receipt" key. // It should show the receipt for the payed invoice. Still let mobile apps do that. void replaceBuyWithReceiptInMarkup(); void applyEditionToEmpty(); void setReplyMarkup(const MTPReplyMarkup *markup); struct CreateConfig; void createComponentsHelper(MTPDmessage::Flags flags, MsgId replyTo, UserId viaBotId, const QString &postAuthor, const MTPReplyMarkup &markup); void createComponents(const CreateConfig &config); void updateAdminBadgeState(); ClickHandlerPtr fastReplyLink() const; QString _timeText; int _timeWidth = 0; mutable int32 _fromNameVersion = 0; friend class HistoryView::Element; friend class HistoryView::Message; }; ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/history_service.cpp������������������������������������0000664�0000000�0000000�00000052404�13262451251�0025056�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/history_service.h" #include "lang/lang_keys.h" #include "mainwidget.h" #include "auth_session.h" #include "apiwrap.h" #include "layout.h" #include "history/history.h" #include "history/history_media_types.h" #include "history/history_message.h" #include "history/history_item_components.h" #include "history/view/history_view_service_message.h" #include "data/data_feed.h" #include "data/data_session.h" #include "data/data_media_types.h" #include "window/notifications_manager.h" #include "window/window_controller.h" #include "storage/storage_shared_media.h" #include "ui/text_options.h" namespace { constexpr auto kPinnedMessageTextLimit = 16; } // namespace void HistoryService::setMessageByAction(const MTPmessageAction &action) { auto prepareChatAddUserText = [this](const MTPDmessageActionChatAddUser &action) { auto result = PreparedText {}; auto &users = action.vusers.v; if (users.size() == 1) { auto u = App::user(peerFromUser(users[0])); if (u == _from) { result.links.push_back(fromLink()); result.text = lng_action_user_joined(lt_from, fromLinkText()); } else { result.links.push_back(fromLink()); result.links.push_back(u->createOpenLink()); result.text = lng_action_add_user(lt_from, fromLinkText(), lt_user, textcmdLink(2, u->name)); } } else if (users.isEmpty()) { result.links.push_back(fromLink()); result.text = lng_action_add_user(lt_from, fromLinkText(), lt_user, "somebody"); } else { result.links.push_back(fromLink()); for (auto i = 0, l = users.size(); i != l; ++i) { auto user = App::user(peerFromUser(users[i])); result.links.push_back(user->createOpenLink()); auto linkText = textcmdLink(i + 2, user->name); if (i == 0) { result.text = linkText; } else if (i + 1 == l) { result.text = lng_action_add_users_and_last(lt_accumulated, result.text, lt_user, linkText); } else { result.text = lng_action_add_users_and_one(lt_accumulated, result.text, lt_user, linkText); } } result.text = lng_action_add_users_many(lt_from, fromLinkText(), lt_users, result.text); } return result; }; auto prepareChatJoinedByLink = [this](const MTPDmessageActionChatJoinedByLink &action) { auto result = PreparedText {}; result.links.push_back(fromLink()); result.text = lng_action_user_joined_by_link(lt_from, fromLinkText()); return result; }; auto prepareChatCreate = [this](const MTPDmessageActionChatCreate &action) { auto result = PreparedText {}; result.links.push_back(fromLink()); result.text = lng_action_created_chat(lt_from, fromLinkText(), lt_title, TextUtilities::Clean(qs(action.vtitle))); return result; }; auto prepareChannelCreate = [this](const MTPDmessageActionChannelCreate &action) { auto result = PreparedText {}; if (isPost()) { result.text = lang(lng_action_created_channel); } else { result.links.push_back(fromLink()); result.text = lng_action_created_chat(lt_from, fromLinkText(), lt_title, TextUtilities::Clean(qs(action.vtitle))); } return result; }; auto prepareChatDeletePhoto = [this] { auto result = PreparedText {}; if (isPost()) { result.text = lang(lng_action_removed_photo_channel); } else { result.links.push_back(fromLink()); result.text = lng_action_removed_photo(lt_from, fromLinkText()); } return result; }; auto prepareChatDeleteUser = [this](const MTPDmessageActionChatDeleteUser &action) { auto result = PreparedText {}; if (peerFromUser(action.vuser_id) == _from->id) { result.links.push_back(fromLink()); result.text = lng_action_user_left(lt_from, fromLinkText()); } else { auto user = App::user(peerFromUser(action.vuser_id)); result.links.push_back(fromLink()); result.links.push_back(user->createOpenLink()); result.text = lng_action_kick_user(lt_from, fromLinkText(), lt_user, textcmdLink(2, user->name)); } return result; }; auto prepareChatEditPhoto = [this](const MTPDmessageActionChatEditPhoto &action) { auto result = PreparedText {}; if (isPost()) { result.text = lang(lng_action_changed_photo_channel); } else { result.links.push_back(fromLink()); result.text = lng_action_changed_photo(lt_from, fromLinkText()); } return result; }; auto prepareChatEditTitle = [this](const MTPDmessageActionChatEditTitle &action) { auto result = PreparedText {}; if (isPost()) { result.text = lng_action_changed_title_channel(lt_title, TextUtilities::Clean(qs(action.vtitle))); } else { result.links.push_back(fromLink()); result.text = lng_action_changed_title(lt_from, fromLinkText(), lt_title, TextUtilities::Clean(qs(action.vtitle))); } return result; }; auto prepareScreenshotTaken = [this] { auto result = PreparedText {}; if (out()) { result.text = lang(lng_action_you_took_screenshot); } else { result.links.push_back(fromLink()); result.text = lng_action_took_screenshot(lt_from, fromLinkText()); } return result; }; auto prepareCustomAction = [&](const MTPDmessageActionCustomAction &action) { auto result = PreparedText {}; result.text = qs(action.vmessage); return result; }; auto prepareBotAllowed = [&](const MTPDmessageActionBotAllowed &action) { auto result = PreparedText{}; const auto domain = qs(action.vdomain); result.text = lng_action_bot_allowed_from_domain( lt_domain, textcmdLink(qstr("http://") + domain, domain)); return result; }; auto messageText = PreparedText {}; switch (action.type()) { case mtpc_messageActionChatAddUser: messageText = prepareChatAddUserText(action.c_messageActionChatAddUser()); break; case mtpc_messageActionChatJoinedByLink: messageText = prepareChatJoinedByLink(action.c_messageActionChatJoinedByLink()); break; case mtpc_messageActionChatCreate: messageText = prepareChatCreate(action.c_messageActionChatCreate()); break; case mtpc_messageActionChannelCreate: messageText = prepareChannelCreate(action.c_messageActionChannelCreate()); break; case mtpc_messageActionHistoryClear: break; // Leave empty text. case mtpc_messageActionChatDeletePhoto: messageText = prepareChatDeletePhoto(); break; case mtpc_messageActionChatDeleteUser: messageText = prepareChatDeleteUser(action.c_messageActionChatDeleteUser()); break; case mtpc_messageActionChatEditPhoto: messageText = prepareChatEditPhoto(action.c_messageActionChatEditPhoto()); break; case mtpc_messageActionChatEditTitle: messageText = prepareChatEditTitle(action.c_messageActionChatEditTitle()); break; case mtpc_messageActionChatMigrateTo: messageText.text = lang(lng_action_group_migrate); break; case mtpc_messageActionChannelMigrateFrom: messageText.text = lang(lng_action_group_migrate); break; case mtpc_messageActionPinMessage: messageText = preparePinnedText(); break; case mtpc_messageActionGameScore: messageText = prepareGameScoreText(); break; case mtpc_messageActionPhoneCall: Unexpected("PhoneCall type in HistoryService."); case mtpc_messageActionPaymentSent: messageText = preparePaymentSentText(); break; case mtpc_messageActionScreenshotTaken: messageText = prepareScreenshotTaken(); break; case mtpc_messageActionCustomAction: messageText = prepareCustomAction(action.c_messageActionCustomAction()); break; case mtpc_messageActionBotAllowed: messageText = prepareBotAllowed(action.c_messageActionBotAllowed()); break; default: messageText.text = lang(lng_message_empty); break; } setServiceText(messageText); // Additional information. switch (action.type()) { case mtpc_messageActionChatAddUser: { if (auto channel = history()->peer->asMegagroup()) { auto &users = action.c_messageActionChatAddUser().vusers; for_const (auto &item, users.v) { if (item.v == Auth().userId()) { channel->mgInfo->joinedMessageFound = true; break; } } } } break; case mtpc_messageActionChatJoinedByLink: { if (_from->isSelf() && history()->peer->isMegagroup()) { history()->peer->asChannel()->mgInfo->joinedMessageFound = true; } } break; case mtpc_messageActionChatEditPhoto: { auto &photo = action.c_messageActionChatEditPhoto().vphoto; if (photo.type() == mtpc_photo) { _media = std::make_unique<Data::MediaPhoto>( this, history()->peer, Auth().data().photo(photo.c_photo())); } } break; case mtpc_messageActionChatMigrateTo: case mtpc_messageActionChannelMigrateFrom: { _flags |= MTPDmessage_ClientFlag::f_is_group_migrate; } break; } } void HistoryService::setSelfDestruct(HistoryServiceSelfDestruct::Type type, int ttlSeconds) { UpdateComponents(HistoryServiceSelfDestruct::Bit()); auto selfdestruct = Get<HistoryServiceSelfDestruct>(); selfdestruct->timeToLive = ttlSeconds * 1000LL; selfdestruct->type = type; } bool HistoryService::updateDependent(bool force) { auto dependent = GetDependentData(); Assert(dependent != nullptr); if (!force) { if (!dependent->msgId || dependent->msg) { return true; } } if (!dependent->lnk) { dependent->lnk = goToMessageClickHandler(history()->peer, dependent->msgId); } auto gotDependencyItem = false; if (!dependent->msg) { dependent->msg = App::histItemById(channelId(), dependent->msgId); if (dependent->msg) { if (dependent->msg->isEmpty()) { // Really it is deleted. dependent->msg = nullptr; force = true; } else { App::historyRegDependency(this, dependent->msg); gotDependencyItem = true; } } } if (dependent->msg) { updateDependentText(); } else if (force) { if (dependent->msgId > 0) { dependent->msgId = 0; gotDependencyItem = true; } updateDependentText(); } if (force && gotDependencyItem) { Auth().notifications().checkDelayed(); } return (dependent->msg || !dependent->msgId); } HistoryService::PreparedText HistoryService::preparePinnedText() { auto result = PreparedText {}; auto pinned = Get<HistoryServicePinned>(); if (pinned && pinned->msg) { const auto mediaText = [&] { if (const auto media = pinned->msg->media()) { return media->pinnedTextSubstring(); } return QString(); }(); result.links.push_back(fromLink()); result.links.push_back(pinned->lnk); if (mediaText.isEmpty()) { auto original = pinned->msg->originalText().text; auto cutAt = 0; auto limit = kPinnedMessageTextLimit; auto size = original.size(); for (; limit != 0;) { --limit; if (cutAt >= size) break; if (original.at(cutAt).isLowSurrogate() && cutAt + 1 < size && original.at(cutAt + 1).isHighSurrogate()) { cutAt += 2; } else { ++cutAt; } } if (!limit && cutAt + 5 < size) { original = original.mid(0, cutAt) + qstr("..."); } result.text = lng_action_pinned_message(lt_from, fromLinkText(), lt_text, textcmdLink(2, original)); } else { result.text = lng_action_pinned_media(lt_from, fromLinkText(), lt_media, textcmdLink(2, mediaText)); } } else if (pinned && pinned->msgId) { result.links.push_back(fromLink()); result.links.push_back(pinned->lnk); result.text = lng_action_pinned_media(lt_from, fromLinkText(), lt_media, textcmdLink(2, lang(lng_contacts_loading))); } else { result.links.push_back(fromLink()); result.text = lng_action_pinned_media(lt_from, fromLinkText(), lt_media, lang(lng_deleted_message)); } return result; } HistoryService::PreparedText HistoryService::prepareGameScoreText() { auto result = PreparedText {}; auto gamescore = Get<HistoryServiceGameScore>(); auto computeGameTitle = [gamescore, &result]() -> QString { if (gamescore && gamescore->msg) { if (const auto media = gamescore->msg->media()) { if (const auto game = media->game()) { const auto row = 0; const auto column = 0; result.links.push_back( std::make_shared<ReplyMarkupClickHandler>( row, column, gamescore->msg->fullId())); auto titleText = game->title; return textcmdLink(result.links.size(), titleText); } } return lang(lng_deleted_message); } else if (gamescore && gamescore->msgId) { return lang(lng_contacts_loading); } return QString(); }; const auto scoreNumber = gamescore ? gamescore->score : 0; if (_from->isSelf()) { auto gameTitle = computeGameTitle(); if (gameTitle.isEmpty()) { result.text = lng_action_game_you_scored_no_game( lt_count, scoreNumber); } else { result.text = lng_action_game_you_scored( lt_count, scoreNumber, lt_game, gameTitle); } } else { result.links.push_back(fromLink()); auto gameTitle = computeGameTitle(); if (gameTitle.isEmpty()) { result.text = lng_action_game_score_no_game( lt_count, scoreNumber, lt_from, fromLinkText()); } else { result.text = lng_action_game_score( lt_count, scoreNumber, lt_from, fromLinkText(), lt_game, gameTitle); } } return result; } HistoryService::PreparedText HistoryService::preparePaymentSentText() { auto result = PreparedText {}; auto payment = Get<HistoryServicePayment>(); auto invoiceTitle = [&] { if (payment && payment->msg) { if (const auto media = payment->msg->media()) { if (const auto invoice = media->invoice()) { return invoice->title; } } return lang(lng_deleted_message); } else if (payment && payment->msgId) { return lang(lng_contacts_loading); } return QString(); }(); if (invoiceTitle.isEmpty()) { result.text = lng_action_payment_done(lt_amount, payment->amount, lt_user, history()->peer->name); } else { result.text = lng_action_payment_done_for(lt_amount, payment->amount, lt_user, history()->peer->name, lt_invoice, invoiceTitle); } return result; } HistoryService::HistoryService( not_null<History*> history, const MTPDmessage &data) : HistoryItem( history, data.vid.v, data.vflags.v, data.vdate.v, data.has_from_id() ? data.vfrom_id.v : UserId(0)) { createFromMtp(data); } HistoryService::HistoryService( not_null<History*> history, const MTPDmessageService &data) : HistoryItem( history, data.vid.v, mtpCastFlags(data.vflags.v), data.vdate.v, data.has_from_id() ? data.vfrom_id.v : UserId(0)) { createFromMtp(data); } HistoryService::HistoryService( not_null<History*> history, MsgId id, TimeId date, const PreparedText &message, MTPDmessage::Flags flags, UserId from, PhotoData *photo) : HistoryItem(history, id, flags, date, from) { setServiceText(message); if (photo) { _media = std::make_unique<Data::MediaPhoto>( this, history->peer, photo); } } bool HistoryService::updateDependencyItem() { if (GetDependentData()) { return updateDependent(true); } return HistoryItem::updateDependencyItem(); } QString HistoryService::inDialogsText(DrawInDialog way) const { return textcmdLink(1, TextUtilities::Clean(notificationText())); } QString HistoryService::inReplyText() const { QString result = HistoryService::notificationText(); return result.trimmed().startsWith(author()->name) ? result.trimmed().mid(author()->name.size()).trimmed() : result; } std::unique_ptr<HistoryView::Element> HistoryService::createView( not_null<HistoryView::ElementDelegate*> delegate) { return delegate->elementCreate(this); } void HistoryService::setServiceText(const PreparedText &prepared) { _text.setText( st::serviceTextStyle, prepared.text, Ui::ItemTextServiceOptions()); auto linkIndex = 0; for_const (auto &link, prepared.links) { // Link indices start with 1. _text.setLink(++linkIndex, link); } _textWidth = -1; _textHeight = 0; } void HistoryService::markMediaAsReadHook() { if (auto selfdestruct = Get<HistoryServiceSelfDestruct>()) { if (!selfdestruct->destructAt) { selfdestruct->destructAt = getms(true) + selfdestruct->timeToLive; App::histories().selfDestructIn(this, selfdestruct->timeToLive); } } } TimeMs HistoryService::getSelfDestructIn(TimeMs now) { if (auto selfdestruct = Get<HistoryServiceSelfDestruct>()) { if (selfdestruct->destructAt > 0) { if (selfdestruct->destructAt <= now) { auto text = [selfdestruct] { switch (selfdestruct->type) { case HistoryServiceSelfDestruct::Type::Photo: return lang(lng_ttl_photo_expired); case HistoryServiceSelfDestruct::Type::Video: return lang(lng_ttl_video_expired); } Unexpected("Type in HistoryServiceSelfDestruct::Type"); }; setServiceText({ text() }); return 0; } return selfdestruct->destructAt - now; } } return 0; } void HistoryService::createFromMtp(const MTPDmessage &message) { auto mediaType = message.vmedia.type(); switch (mediaType) { case mtpc_messageMediaPhoto: { if (message.is_media_unread()) { auto &photo = message.vmedia.c_messageMediaPhoto(); Assert(photo.has_ttl_seconds()); setSelfDestruct(HistoryServiceSelfDestruct::Type::Photo, photo.vttl_seconds.v); if (out()) { setServiceText({ lang(lng_ttl_photo_sent) }); } else { auto result = PreparedText(); result.links.push_back(fromLink()); result.text = lng_ttl_photo_received(lt_from, fromLinkText()); setServiceText(std::move(result)); } } else { setServiceText({ lang(lng_ttl_photo_expired) }); } } break; case mtpc_messageMediaDocument: { if (message.is_media_unread()) { auto &document = message.vmedia.c_messageMediaDocument(); Assert(document.has_ttl_seconds()); setSelfDestruct(HistoryServiceSelfDestruct::Type::Video, document.vttl_seconds.v); if (out()) { setServiceText({ lang(lng_ttl_video_sent) }); } else { auto result = PreparedText(); result.links.push_back(fromLink()); result.text = lng_ttl_video_received(lt_from, fromLinkText()); setServiceText(std::move(result)); } } else { setServiceText({ lang(lng_ttl_video_expired) }); } } break; default: Unexpected("Media type in HistoryService::createFromMtp()"); } } void HistoryService::createFromMtp(const MTPDmessageService &message) { if (message.vaction.type() == mtpc_messageActionGameScore) { UpdateComponents(HistoryServiceGameScore::Bit()); Get<HistoryServiceGameScore>()->score = message.vaction.c_messageActionGameScore().vscore.v; } else if (message.vaction.type() == mtpc_messageActionPaymentSent) { UpdateComponents(HistoryServicePayment::Bit()); auto amount = message.vaction.c_messageActionPaymentSent().vtotal_amount.v; auto currency = qs(message.vaction.c_messageActionPaymentSent().vcurrency); Get<HistoryServicePayment>()->amount = HistoryInvoice::fillAmountAndCurrency(amount, currency); } if (message.has_reply_to_msg_id()) { if (message.vaction.type() == mtpc_messageActionPinMessage) { UpdateComponents(HistoryServicePinned::Bit()); } if (auto dependent = GetDependentData()) { dependent->msgId = message.vreply_to_msg_id.v; if (!updateDependent()) { Auth().api().requestMessageData( history()->peer->asChannel(), dependent->msgId, HistoryDependentItemCallback(fullId())); } } } setMessageByAction(message.vaction); } void HistoryService::applyEdition(const MTPDmessageService &message) { clearDependency(); UpdateComponents(0); createFromMtp(message); if (message.vaction.type() == mtpc_messageActionHistoryClear) { removeMedia(); finishEditionToEmpty(); } else { finishEdition(-1); } } void HistoryService::removeMedia() { if (!_media) return; _media.reset(); _textWidth = -1; _textHeight = 0; Auth().data().requestItemResize(this); } Storage::SharedMediaTypesMask HistoryService::sharedMediaTypes() const { if (auto media = this->media()) { return media->sharedMediaTypes(); } return {}; } void HistoryService::updateDependentText() { auto text = PreparedText {}; if (Has<HistoryServicePinned>()) { text = preparePinnedText(); } else if (Has<HistoryServiceGameScore>()) { text = prepareGameScoreText(); } else if (Has<HistoryServicePayment>()) { text = preparePaymentSentText(); } else { return; } setServiceText(text); Auth().data().requestItemResize(this); if (history()->textCachedFor == this) { history()->textCachedFor = nullptr; } if (const auto feed = history()->peer->feed()) { if (feed->textCachedFor == this) { feed->textCachedFor = nullptr; feed->updateChatListEntry(); } } if (const auto main = App::main()) { // #TODO feeds search results main->repaintDialogRow(history(), id); } App::historyUpdateDependent(this); } void HistoryService::clearDependency() { if (auto dependent = GetDependentData()) { if (dependent->msg) { App::historyUnregDependency(this, dependent->msg); } } } HistoryService::~HistoryService() { clearDependency(); _media.reset(); } HistoryService::PreparedText GenerateJoinedText( not_null<History*> history, not_null<UserData*> inviter) { if (inviter->id != Auth().userPeerId()) { auto result = HistoryService::PreparedText{}; result.links.push_back(inviter->createOpenLink()); result.text = (history->isMegagroup() ? lng_action_add_you_group : lng_action_add_you)(lt_from, textcmdLink(1, inviter->name)); return result; } else if (history->isMegagroup()) { auto self = App::user(Auth().userPeerId()); auto result = HistoryService::PreparedText{}; result.links.push_back(self->createOpenLink()); result.text = lng_action_user_joined( lt_from, textcmdLink(1, self->name)); return result; } return { lang(lng_action_you_joined) }; } HistoryService *GenerateJoinedMessage( not_null<History*> history, TimeId inviteDate, not_null<UserData*> inviter, MTPDmessage::Flags flags) { return new HistoryService( history, clientMsgId(), inviteDate, GenerateJoinedText(history, inviter), flags); } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/history_service.h��������������������������������������0000664�0000000�0000000�00000007554�13262451251�0024531�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "history/history_item.h" namespace HistoryView { class Service; } // namespace HistoryView struct HistoryServiceDependentData { MsgId msgId = 0; HistoryItem *msg = nullptr; ClickHandlerPtr lnk; }; struct HistoryServicePinned : public RuntimeComponent<HistoryServicePinned, HistoryItem> , public HistoryServiceDependentData { }; struct HistoryServiceGameScore : public RuntimeComponent<HistoryServiceGameScore, HistoryItem> , public HistoryServiceDependentData { int score = 0; }; struct HistoryServicePayment : public RuntimeComponent<HistoryServicePayment, HistoryItem> , public HistoryServiceDependentData { QString amount; }; struct HistoryServiceSelfDestruct : public RuntimeComponent<HistoryServiceSelfDestruct, HistoryItem> { enum class Type { Photo, Video, }; Type type = Type::Photo; TimeMs timeToLive = 0; TimeMs destructAt = 0; }; namespace HistoryView { class ServiceMessagePainter; } // namespace HistoryView class HistoryService : public HistoryItem { public: struct PreparedText { QString text; QList<ClickHandlerPtr> links; }; HistoryService(not_null<History*> history, const MTPDmessage &data); HistoryService( not_null<History*> history, const MTPDmessageService &data); HistoryService( not_null<History*> history, MsgId id, TimeId date, const PreparedText &message, MTPDmessage::Flags flags = 0, UserId from = 0, PhotoData *photo = nullptr); bool updateDependencyItem() override; MsgId dependencyMsgId() const override { if (auto dependent = GetDependentData()) { return dependent->msgId; } return 0; } bool notificationReady() const override { if (auto dependent = GetDependentData()) { return (dependent->msg || !dependent->msgId); } return true; } void applyEdition(const MTPDmessageService &message) override; TimeMs getSelfDestructIn(TimeMs now) override; Storage::SharedMediaTypesMask sharedMediaTypes() const override; bool needCheck() const override { return false; } bool serviceMsg() const override { return true; } QString inDialogsText(DrawInDialog way) const override; QString inReplyText() const override; std::unique_ptr<HistoryView::Element> createView( not_null<HistoryView::ElementDelegate*> delegate) override; ~HistoryService(); protected: friend class HistoryView::ServiceMessagePainter; void markMediaAsReadHook() override; void setServiceText(const PreparedText &prepared); QString fromLinkText() const { return textcmdLink(1, _from->name); }; ClickHandlerPtr fromLink() const { return _from->createOpenLink(); }; void removeMedia(); private: HistoryServiceDependentData *GetDependentData() { if (auto pinned = Get<HistoryServicePinned>()) { return pinned; } else if (auto gamescore = Get<HistoryServiceGameScore>()) { return gamescore; } else if (auto payment = Get<HistoryServicePayment>()) { return payment; } return nullptr; } const HistoryServiceDependentData *GetDependentData() const { return const_cast<HistoryService*>(this)->GetDependentData(); } bool updateDependent(bool force = false); void updateDependentText(); void clearDependency(); void createFromMtp(const MTPDmessage &message); void createFromMtp(const MTPDmessageService &message); void setMessageByAction(const MTPmessageAction &action); void setSelfDestruct(HistoryServiceSelfDestruct::Type type, int ttlSeconds); PreparedText preparePinnedText(); PreparedText prepareGameScoreText(); PreparedText preparePaymentSentText(); friend class HistoryView::Service; }; HistoryService *GenerateJoinedMessage( not_null<History*> history, TimeId inviteDate, not_null<UserData*> inviter, MTPDmessage::Flags flags); ����������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/history_widget.cpp�������������������������������������0000664�0000000�0000000�00000624240�13262451251�0024704�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/history_widget.h" #include "boxes/confirm_box.h" #include "boxes/send_files_box.h" #include "boxes/share_box.h" #include "boxes/edit_caption_box.h" #include "core/file_utilities.h" #include "ui/toast/toast.h" #include "ui/special_buttons.h" #include "ui/widgets/buttons.h" #include "ui/widgets/inner_dropdown.h" #include "ui/widgets/dropdown_menu.h" #include "ui/widgets/labels.h" #include "ui/widgets/shadow.h" #include "ui/effects/ripple_animation.h" #include "inline_bots/inline_bot_result.h" #include "data/data_drafts.h" #include "data/data_session.h" #include "data/data_media_types.h" #include "history/history.h" #include "history/history_item.h" #include "history/history_message.h" #include "history/history_media_types.h" #include "history/history_drag_area.h" #include "history/history_inner_widget.h" #include "history/history_item_components.h" #include "history/feed/history_feed_section.h" #include "history/view/history_view_service_message.h" #include "history/view/history_view_element.h" #include "profile/profile_block_group_members.h" #include "info/info_memento.h" #include "core/click_handler_types.h" #include "chat_helpers/tabbed_panel.h" #include "chat_helpers/tabbed_selector.h" #include "chat_helpers/tabbed_section.h" #include "chat_helpers/bot_keyboard.h" #include "chat_helpers/message_field.h" #include "lang/lang_keys.h" #include "application.h" #include "mainwidget.h" #include "mainwindow.h" #include "passcodewidget.h" #include "mainwindow.h" #include "storage/localimageloader.h" #include "storage/localstorage.h" #include "storage/file_upload.h" #include "storage/storage_media_prepare.h" #include "media/media_audio.h" #include "media/media_audio_capture.h" #include "media/player/media_player_instance.h" #include "apiwrap.h" #include "history/view/history_view_top_bar_widget.h" #include "observer_peer.h" #include "base/qthelp_regex.h" #include "ui/widgets/popup_menu.h" #include "ui/text_options.h" #include "auth_session.h" #include "window/themes/window_theme.h" #include "window/notifications_manager.h" #include "window/window_controller.h" #include "window/window_slide_animation.h" #include "window/window_peer_menu.h" #include "inline_bots/inline_results_widget.h" #include "chat_helpers/emoji_suggestions_widget.h" #include "core/crash_reports.h" #include "dialogs/dialogs_key.h" #include "styles/style_history.h" #include "styles/style_dialogs.h" #include "styles/style_window.h" #include "styles/style_boxes.h" #include "styles/style_profile.h" #include "styles/style_chat_helpers.h" #include "styles/style_info.h" namespace { constexpr auto kMessagesPerPageFirst = 30; constexpr auto kMessagesPerPage = 50; constexpr auto kPreloadHeightsCount = 3; // when 3 screens to scroll left make a preload request constexpr auto kTabbedSelectorToggleTooltipTimeoutMs = 3000; constexpr auto kTabbedSelectorToggleTooltipCount = 3; constexpr auto kScrollToVoiceAfterScrolledMs = 1000; constexpr auto kSkipRepaintWhileScrollMs = 100; constexpr auto kShowMembersDropdownTimeoutMs = 300; constexpr auto kDisplayEditTimeWarningMs = 300 * 1000; constexpr auto kFullDayInMs = 86400 * 1000; constexpr auto kCancelTypingActionTimeout = TimeMs(5000); ApiWrap::RequestMessageDataCallback replyEditMessageDataCallback() { return [](ChannelData *channel, MsgId msgId) { if (App::main()) { App::main()->messageDataReceived(channel, msgId); } }; } void ActivateWindowDelayed(not_null<Window::Controller*> controller) { const auto window = controller->window(); const auto weak = make_weak(window.get()); window->activateWindow(); crl::on_main(window, [=] { window->activateWindow(); }); } } // namespace ReportSpamPanel::ReportSpamPanel(QWidget *parent) : TWidget(parent), _report(this, lang(lng_report_spam), st::reportSpamHide), _hide(this, lang(lng_report_spam_hide), st::reportSpamHide), _clear(this, lang(lng_profile_delete_conversation)) { resize(parent->width(), _hide->height() + st::lineWidth); connect(_report, SIGNAL(clicked()), this, SIGNAL(reportClicked())); connect(_hide, SIGNAL(clicked()), this, SIGNAL(hideClicked())); connect(_clear, SIGNAL(clicked()), this, SIGNAL(clearClicked())); _clear->hide(); } void ReportSpamPanel::resizeEvent(QResizeEvent *e) { _report->resize(width() - (_hide->width() + st::reportSpamSeparator) * 2, _report->height()); _report->moveToLeft(_hide->width() + st::reportSpamSeparator, 0); _hide->moveToRight(0, 0); _clear->move((width() - _clear->width()) / 2, height() - _clear->height() - ((height() - st::msgFont->height - _clear->height()) / 2)); } void ReportSpamPanel::paintEvent(QPaintEvent *e) { Painter p(this); p.fillRect(QRect(0, 0, width(), height() - st::lineWidth), st::reportSpamBg); p.fillRect(Adaptive::OneColumn() ? 0 : st::lineWidth, height() - st::lineWidth, width() - (Adaptive::OneColumn() ? 0 : st::lineWidth), st::lineWidth, st::shadowFg); if (!_clear->isHidden()) { p.setPen(st::reportSpamFg); p.setFont(st::msgFont); p.drawText(QRect(_report->x(), (_clear->y() - st::msgFont->height) / 2, _report->width(), st::msgFont->height), lang(lng_report_spam_thanks), style::al_top); } } void ReportSpamPanel::setReported(bool reported, PeerData *onPeer) { if (reported) { _report->hide(); _clear->setText(lang(onPeer->isChannel() ? (onPeer->isMegagroup() ? lng_profile_leave_group : lng_profile_leave_channel) : lng_profile_delete_conversation)); _clear->show(); } else { _report->show(); _clear->hide(); } update(); } HistoryHider::HistoryHider( MainWidget *parent, MessageIdsList &&items) : RpWidget(parent) , _forwardItems(std::move(items)) , _send(this, langFactory(lng_forward_send), st::defaultBoxButton) , _cancel(this, langFactory(lng_cancel), st::defaultBoxButton) { init(); } HistoryHider::HistoryHider(MainWidget *parent) : RpWidget(parent) , _sendPath(true) , _send(this, langFactory(lng_forward_send), st::defaultBoxButton) , _cancel(this, langFactory(lng_cancel), st::defaultBoxButton) { init(); } HistoryHider::HistoryHider(MainWidget *parent, const QString &botAndQuery) : RpWidget(parent) , _botAndQuery(botAndQuery) , _send(this, langFactory(lng_forward_send), st::defaultBoxButton) , _cancel(this, langFactory(lng_cancel), st::defaultBoxButton) { init(); } HistoryHider::HistoryHider(MainWidget *parent, const QString &url, const QString &text) : RpWidget(parent) , _shareUrl(url) , _shareText(text) , _send(this, langFactory(lng_forward_send), st::defaultBoxButton) , _cancel(this, langFactory(lng_cancel), st::defaultBoxButton) { init(); } void HistoryHider::init() { subscribe(Lang::Current().updated(), [this] { refreshLang(); }); connect(_send, SIGNAL(clicked()), this, SLOT(forward())); connect(_cancel, SIGNAL(clicked()), this, SLOT(startHide())); subscribe(Global::RefPeerChooseCancel(), [this] { startHide(); }); _chooseWidth = st::historyForwardChooseFont->width(lang(_botAndQuery.isEmpty() ? lng_forward_choose : lng_inline_switch_choose)); resizeEvent(0); _a_opacity.start([this] { update(); }, 0., 1., st::boxDuration); } void HistoryHider::refreshLang() { InvokeQueued(this, [this] { updateControlsGeometry(); }); } bool HistoryHider::withConfirm() const { return _sendPath; } void HistoryHider::paintEvent(QPaintEvent *e) { Painter p(this); auto opacity = _a_opacity.current(getms(), _hiding ? 0. : 1.); if (opacity == 0.) { if (_hiding) { QTimer::singleShot(0, this, SLOT(deleteLater())); } return; } p.setOpacity(opacity); if (!_hiding || !_cacheForAnim.isNull() || !_offered) { p.fillRect(rect(), st::layerBg); } if (_cacheForAnim.isNull() || !_offered) { p.setFont(st::historyForwardChooseFont); if (_offered) { Ui::Shadow::paint(p, _box, width(), st::boxRoundShadow); App::roundRect(p, _box, st::boxBg, BoxCorners); p.setPen(st::boxTextFg); _toText.drawLeftElided(p, _box.left() + st::boxPadding.left(), _box.y() + st::boxTopMargin + st::boxPadding.top(), _toTextWidth + 2, width(), 1, style::al_left); } else { auto w = st::historyForwardChooseMargins.left() + _chooseWidth + st::historyForwardChooseMargins.right(); auto h = st::historyForwardChooseMargins.top() + st::historyForwardChooseFont->height + st::historyForwardChooseMargins.bottom(); App::roundRect(p, (width() - w) / 2, (height() - h) / 2, w, h, st::historyForwardChooseBg, ForwardCorners); p.setPen(st::historyForwardChooseFg); p.drawText(_box, lang(_botAndQuery.isEmpty() ? lng_forward_choose : lng_inline_switch_choose), QTextOption(style::al_center)); } } else { p.drawPixmap(_box.left(), _box.top(), _cacheForAnim); } } void HistoryHider::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Escape) { if (_offered) { _offered = nullptr; resizeEvent(nullptr); update(); App::main()->dialogsActivate(); } else { startHide(); } } else if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) { if (_offered) { forward(); } } } void HistoryHider::mousePressEvent(QMouseEvent *e) { if (e->button() == Qt::LeftButton) { if (!_box.contains(e->pos())) { startHide(); } } } void HistoryHider::startHide() { if (_hiding) return; _hiding = true; if (Adaptive::OneColumn()) { QTimer::singleShot(0, this, SLOT(deleteLater())); } else { if (_offered) _cacheForAnim = Ui::GrabWidget(this, _box); if (_forwardRequest) MTP::cancel(_forwardRequest); _send->hide(); _cancel->hide(); _a_opacity.start([this] { animationCallback(); }, 1., 0., st::boxDuration); } } void HistoryHider::animationCallback() { update(); if (!_a_opacity.animating() && _hiding) { QTimer::singleShot(0, this, SLOT(deleteLater())); } } void HistoryHider::forward() { if (!_hiding && _offered) { if (_sendPath) { parent()->onSendPaths(_offered->id); } else if (!_shareUrl.isEmpty()) { parent()->shareUrl(_offered, _shareUrl, _shareText); } else if (!_botAndQuery.isEmpty()) { parent()->onInlineSwitchChosen(_offered->id, _botAndQuery); } else { parent()->setForwardDraft(_offered->id, std::move(_forwardItems)); } } emit forwarded(); } void HistoryHider::forwardDone() { _forwardRequest = 0; startHide(); } MainWidget *HistoryHider::parent() { return static_cast<MainWidget*>(parentWidget()); } void HistoryHider::resizeEvent(QResizeEvent *e) { updateControlsGeometry(); } void HistoryHider::updateControlsGeometry() { auto w = st::boxWidth; auto h = st::boxPadding.top() + st::boxPadding.bottom(); if (_offered) { if (!_hiding) { _send->show(); _cancel->show(); } h += st::boxTopMargin + qMax(st::boxTextFont->height, st::boxLabelStyle.lineHeight) + st::boxButtonPadding.top() + _send->height() + st::boxButtonPadding.bottom(); } else { h += st::historyForwardChooseFont->height; _send->hide(); _cancel->hide(); } _box = QRect((width() - w) / 2, (height() - h) / 2, w, h); _send->moveToRight(width() - (_box.x() + _box.width()) + st::boxButtonPadding.right(), _box.y() + _box.height() - st::boxButtonPadding.bottom() - _send->height()); _cancel->moveToRight(width() - (_box.x() + _box.width()) + st::boxButtonPadding.right() + _send->width() + st::boxButtonPadding.left(), _send->y()); } bool HistoryHider::offerPeer(PeerId peer) { if (!peer) { _offered = nullptr; _toText.setText(st::boxLabelStyle, QString()); _toTextWidth = 0; resizeEvent(nullptr); return false; } _offered = App::peer(peer); auto phrase = QString(); auto recipient = _offered->isUser() ? _offered->name : '\xAB' + _offered->name + '\xBB'; if (_sendPath) { auto toId = _offered->id; _offered = nullptr; if (parent()->onSendPaths(toId)) { startHide(); } return false; } else if (!_shareUrl.isEmpty()) { auto offered = base::take(_offered); if (parent()->shareUrl(offered, _shareUrl, _shareText)) { startHide(); } return false; } else if (!_botAndQuery.isEmpty()) { auto toId = _offered->id; _offered = nullptr; if (parent()->onInlineSwitchChosen(toId, _botAndQuery)) { startHide(); } return false; } else { auto toId = _offered->id; _offered = nullptr; if (parent()->setForwardDraft(toId, std::move(_forwardItems))) { startHide(); } return false; } _toText.setText(st::boxLabelStyle, phrase, Ui::NameTextOptions()); _toTextWidth = _toText.maxWidth(); if (_toTextWidth > _box.width() - st::boxPadding.left() - st::boxLayerButtonPadding.right()) { _toTextWidth = _box.width() - st::boxPadding.left() - st::boxLayerButtonPadding.right(); } resizeEvent(nullptr); update(); setFocus(); return true; } QString HistoryHider::offeredText() const { return _toText.originalText(); } bool HistoryHider::wasOffered() const { return _offered != nullptr; } HistoryHider::~HistoryHider() { if (_sendPath) cSetSendPaths(QStringList()); parent()->noHider(this); } HistoryWidget::HistoryWidget( QWidget *parent, not_null<Window::Controller*> controller) : Window::AbstractSectionWidget(parent, controller) , _fieldBarCancel(this, st::historyReplyCancel) , _topBar(this, controller) , _scroll(this, st::historyScroll, false) , _historyDown(_scroll, st::historyToDown) , _unreadMentions(_scroll, st::historyUnreadMentions) , _fieldAutocomplete(this) , _send(this) , _unblock(this, lang(lng_unblock_button).toUpper(), st::historyUnblock) , _botStart(this, lang(lng_bot_start).toUpper(), st::historyComposeButton) , _joinChannel(this, lang(lng_profile_join_channel).toUpper(), st::historyComposeButton) , _muteUnmute(this, lang(lng_channel_mute).toUpper(), st::historyComposeButton) , _attachToggle(this, st::historyAttach) , _tabbedSelectorToggle(this, st::historyAttachEmoji) , _botKeyboardShow(this, st::historyBotKeyboardShow) , _botKeyboardHide(this, st::historyBotKeyboardHide) , _botCommandStart(this, st::historyBotCommandStart) , _field(this, controller, st::historyComposeField, langFactory(lng_message_ph)) , _recordCancelWidth(st::historyRecordFont->width(lang(lng_record_cancel))) , _a_recording(animation(this, &HistoryWidget::step_recording)) , _kbScroll(this, st::botKbScroll) , _tabbedPanel(this, controller) , _tabbedSelector(_tabbedPanel->getSelector()) , _attachDragState(DragState::None) , _attachDragDocument(this) , _attachDragPhoto(this) , _sendActionStopTimer([this] { cancelTypingAction(); }) , _topShadow(this) { setAcceptDrops(true); subscribe(Auth().downloaderTaskFinished(), [this] { update(); }); connect(_scroll, SIGNAL(scrolled()), this, SLOT(onScroll())); _historyDown->setClickedCallback([this] { historyDownClicked(); }); _unreadMentions->setClickedCallback([this] { showNextUnreadMention(); }); connect(_fieldBarCancel, SIGNAL(clicked()), this, SLOT(onFieldBarCancel())); _send->setClickedCallback([this] { sendButtonClicked(); }); connect(_unblock, SIGNAL(clicked()), this, SLOT(onUnblock())); connect(_botStart, SIGNAL(clicked()), this, SLOT(onBotStart())); connect(_joinChannel, SIGNAL(clicked()), this, SLOT(onJoinChannel())); connect(_muteUnmute, SIGNAL(clicked()), this, SLOT(onMuteUnmute())); connect(_field, SIGNAL(submitted(bool)), this, SLOT(onSend(bool))); connect(_field, SIGNAL(cancelled()), this, SLOT(onCancel())); connect(_field, SIGNAL(tabbed()), this, SLOT(onFieldTabbed())); connect(_field, SIGNAL(resized()), this, SLOT(onFieldResize())); connect(_field, SIGNAL(focused()), this, SLOT(onFieldFocused())); connect(_field, SIGNAL(changed()), this, SLOT(onTextChange())); connect(_field, SIGNAL(spacedReturnedPasted()), this, SLOT(onPreviewParse())); connect(_field, SIGNAL(linksChanged()), this, SLOT(onPreviewCheck())); connect(App::wnd()->windowHandle(), SIGNAL(visibleChanged(bool)), this, SLOT(onWindowVisibleChanged())); connect(&_scrollTimer, SIGNAL(timeout()), this, SLOT(onScrollTimer())); connect(_tabbedSelector, SIGNAL(emojiSelected(EmojiPtr)), _field, SLOT(onEmojiInsert(EmojiPtr))); connect(_tabbedSelector, SIGNAL(stickerSelected(DocumentData*)), this, SLOT(onStickerSend(DocumentData*))); connect(_tabbedSelector, SIGNAL(photoSelected(PhotoData*)), this, SLOT(onPhotoSend(PhotoData*))); connect(_tabbedSelector, SIGNAL(inlineResultSelected(InlineBots::Result*,UserData*)), this, SLOT(onInlineResultSend(InlineBots::Result*,UserData*))); connect(&_previewTimer, SIGNAL(timeout()), this, SLOT(onPreviewTimeout())); connect(Media::Capture::instance(), SIGNAL(error()), this, SLOT(onRecordError())); connect(Media::Capture::instance(), SIGNAL(updated(quint16,qint32)), this, SLOT(onRecordUpdate(quint16,qint32))); connect(Media::Capture::instance(), SIGNAL(done(QByteArray,VoiceWaveform,qint32)), this, SLOT(onRecordDone(QByteArray,VoiceWaveform,qint32))); _attachToggle->setClickedCallback(App::LambdaDelayed(st::historyAttach.ripple.hideDuration, this, [this] { chooseAttach(); })); _updateHistoryItems.setSingleShot(true); connect(&_updateHistoryItems, SIGNAL(timeout()), this, SLOT(onUpdateHistoryItems())); _scrollTimer.setSingleShot(false); _highlightTimer.setCallback([this] { updateHighlightedMessage(); }); _membersDropdownShowTimer.setSingleShot(true); connect(&_membersDropdownShowTimer, SIGNAL(timeout()), this, SLOT(onMembersDropdownShow())); _saveDraftTimer.setSingleShot(true); connect(&_saveDraftTimer, SIGNAL(timeout()), this, SLOT(onDraftSave())); _saveCloudDraftTimer.setSingleShot(true); connect(&_saveCloudDraftTimer, SIGNAL(timeout()), this, SLOT(onCloudDraftSave())); connect(_field->verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(onDraftSaveDelayed())); connect(_field, SIGNAL(cursorPositionChanged()), this, SLOT(onDraftSaveDelayed())); connect(_field, SIGNAL(cursorPositionChanged()), this, SLOT(onCheckFieldAutocomplete()), Qt::QueuedConnection); _fieldBarCancel->hide(); _topBar->hide(); _scroll->hide(); _keyboard = _kbScroll->setOwnedWidget(object_ptr<BotKeyboard>(this)); _kbScroll->hide(); updateScrollColors(); _historyDown->installEventFilter(this); _unreadMentions->installEventFilter(this); _fieldAutocomplete->hide(); connect(_fieldAutocomplete, SIGNAL(mentionChosen(UserData*,FieldAutocomplete::ChooseMethod)), this, SLOT(onMentionInsert(UserData*))); connect(_fieldAutocomplete, SIGNAL(hashtagChosen(QString,FieldAutocomplete::ChooseMethod)), this, SLOT(onHashtagOrBotCommandInsert(QString,FieldAutocomplete::ChooseMethod))); connect(_fieldAutocomplete, SIGNAL(botCommandChosen(QString,FieldAutocomplete::ChooseMethod)), this, SLOT(onHashtagOrBotCommandInsert(QString,FieldAutocomplete::ChooseMethod))); connect(_fieldAutocomplete, SIGNAL(stickerChosen(DocumentData*,FieldAutocomplete::ChooseMethod)), this, SLOT(onStickerSend(DocumentData*))); connect(_fieldAutocomplete, SIGNAL(moderateKeyActivate(int,bool*)), this, SLOT(onModerateKeyActivate(int,bool*))); _field->installEventFilter(_fieldAutocomplete); _field->setInsertFromMimeDataHook([this](const QMimeData *data) { return confirmSendingFiles( data, CompressConfirm::Auto, data->text()); }); _emojiSuggestions.create(this, _field.data()); updateFieldSubmitSettings(); _field->hide(); _send->hide(); _unblock->hide(); _botStart->hide(); _joinChannel->hide(); _muteUnmute->hide(); _send->setRecordStartCallback([this] { recordStartCallback(); }); _send->setRecordStopCallback([this](bool active) { recordStopCallback(active); }); _send->setRecordUpdateCallback([this](QPoint globalPos) { recordUpdateCallback(globalPos); }); _send->setRecordAnimationCallback([this] { updateField(); }); _attachToggle->hide(); _tabbedSelectorToggle->hide(); _botKeyboardShow->hide(); _botKeyboardHide->hide(); _botCommandStart->hide(); _tabbedSelectorToggle->installEventFilter(_tabbedPanel); _tabbedSelectorToggle->setClickedCallback([this] { toggleTabbedSelectorMode(); }); connect(_botKeyboardShow, SIGNAL(clicked()), this, SLOT(onKbToggle())); connect(_botKeyboardHide, SIGNAL(clicked()), this, SLOT(onKbToggle())); connect(_botCommandStart, SIGNAL(clicked()), this, SLOT(onCmdStart())); _tabbedPanel->hide(); _attachDragDocument->hide(); _attachDragPhoto->hide(); _topShadow->hide(); _attachDragDocument->setDroppedCallback([this](const QMimeData *data) { confirmSendingFiles(data, CompressConfirm::No); ActivateWindowDelayed(this->controller()); }); _attachDragPhoto->setDroppedCallback([this](const QMimeData *data) { confirmSendingFiles(data, CompressConfirm::Yes); ActivateWindowDelayed(this->controller()); }); connect(&_updateEditTimeLeftDisplay, SIGNAL(timeout()), this, SLOT(updateField())); subscribe(Adaptive::Changed(), [this] { update(); }); Auth().data().itemRemoved( ) | rpl::start_with_next( [this](auto item) { itemRemoved(item); }, lifetime()); Auth().data().historyChanged( ) | rpl::start_with_next( [=](auto history) { handleHistoryChange(history); }, lifetime()); Auth().data().viewResizeRequest( ) | rpl::start_with_next([this](auto view) { if (view->data()->mainView() == view) { updateHistoryGeometry(); } }, lifetime()); Auth().data().itemViewRefreshRequest( ) | rpl::start_with_next([this](auto item) { // While HistoryInner doesn't own item views we must refresh them // even if the list is not yet created / was destroyed. if (!_list) { item->refreshMainView(); } }, lifetime()); Auth().data().animationPlayInlineRequest( ) | rpl::start_with_next([=](auto item) { if (const auto view = item->mainView()) { if (const auto media = view->media()) { media->playAnimation(); } } }, lifetime()); subscribe(Auth().data().contactsLoaded(), [this](bool) { if (_peer) { updateReportSpamStatus(); updateControlsVisibility(); } }); subscribe(Media::Player::instance()->switchToNextNotifier(), [this](const Media::Player::Instance::Switch &pair) { if (pair.from.type() == AudioMsgId::Type::Voice) { scrollToCurrentVoiceMessage(pair.from.contextId(), pair.to); } }); using UpdateFlag = Notify::PeerUpdate::Flag; auto changes = UpdateFlag::ChannelRightsChanged | UpdateFlag::UnreadMentionsChanged | UpdateFlag::MigrationChanged | UpdateFlag::RestrictionReasonChanged | UpdateFlag::ChannelPinnedChanged | UpdateFlag::UserIsBlocked | UpdateFlag::AdminsChanged | UpdateFlag::MembersChanged | UpdateFlag::UserOnlineChanged | UpdateFlag::NotificationsEnabled | UpdateFlag::ChannelAmIn; subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(changes, [this](const Notify::PeerUpdate &update) { if (update.peer == _peer) { if (update.flags & UpdateFlag::ChannelRightsChanged) { onPreviewCheck(); } if (update.flags & UpdateFlag::UnreadMentionsChanged) { updateUnreadMentionsVisibility(); } if (update.flags & UpdateFlag::MigrationChanged) { if (auto channel = _peer->migrateTo()) { Ui::showPeerHistory(channel, ShowAtUnreadMsgId); Auth().api().requestParticipantsCountDelayed(channel); return; } } if (update.flags & UpdateFlag::NotificationsEnabled) { updateNotifySettings(); } if (update.flags & UpdateFlag::RestrictionReasonChanged) { auto restriction = _peer->restrictionReason(); if (!restriction.isEmpty()) { this->controller()->showBackFromStack(); Ui::show(Box<InformBox>(restriction)); return; } } if (update.flags & UpdateFlag::ChannelPinnedChanged) { if (pinnedMsgVisibilityUpdated()) { updateHistoryGeometry(); updateControlsVisibility(); updateControlsGeometry(); this->update(); } } if (update.flags & (UpdateFlag::UserIsBlocked | UpdateFlag::AdminsChanged | UpdateFlag::MembersChanged | UpdateFlag::UserOnlineChanged | UpdateFlag::ChannelAmIn)) { handlePeerUpdate(); } } })); subscribe(Auth().data().queryItemVisibility(), [this](const Data::Session::ItemVisibilityQuery &query) { if (_a_show.animating() || _history != query.item->history() || !query.item->mainView() || !isVisible()) { return; } if (const auto view = query.item->mainView()) { auto top = _list->itemTop(view); if (top >= 0) { auto scrollTop = _scroll->scrollTop(); if (top + view->height() > scrollTop && top < scrollTop + _scroll->height()) { *query.isVisible = true; } } } }); _topBar->membersShowAreaActive( ) | rpl::start_with_next([=](bool active) { setMembersShowAreaActive(active); }, _topBar->lifetime()); _topBar->forwardSelectionRequest( ) | rpl::start_with_next([=] { forwardSelected(); }, _topBar->lifetime()); _topBar->deleteSelectionRequest( ) | rpl::start_with_next([=] { confirmDeleteSelected(); }, _topBar->lifetime()); _topBar->clearSelectionRequest( ) | rpl::start_with_next([=] { clearSelected(); }, _topBar->lifetime()); Auth().api().sendActions( ) | rpl::start_with_next([this](const ApiWrap::SendOptions &options) { fastShowAtEnd(options.history); const auto lastKeyboardUsed = lastForceReplyReplied(FullMsgId( options.history->channelId(), options.replyTo)); if (cancelReply(lastKeyboardUsed) && !options.clearDraft) { onCloudDraftSave(); } }, lifetime()); orderWidgets(); } void HistoryWidget::scrollToCurrentVoiceMessage(FullMsgId fromId, FullMsgId toId) { if (getms() <= _lastUserScrolled + kScrollToVoiceAfterScrolledMs) { return; } if (!_list) { return; } auto from = App::histItemById(fromId); auto to = App::histItemById(toId); if (!from || !to) { return; } // If history has pending resize items, the scrollTopItem won't be updated. // And the scrollTop will be reset back to scrollTopItem + scrollTopOffset. handlePendingHistoryUpdate(); if (const auto toView = to->mainView()) { auto toTop = _list->itemTop(toView); if (toTop >= 0 && !isItemCompletelyHidden(from)) { auto scrollTop = _scroll->scrollTop(); auto scrollBottom = scrollTop + _scroll->height(); auto toBottom = toTop + toView->height(); if ((toTop < scrollTop && toBottom < scrollBottom) || (toTop > scrollTop && toBottom > scrollBottom)) { animatedScrollToItem(to->id); } } } } void HistoryWidget::animatedScrollToItem(MsgId msgId) { Expects(_history != nullptr); if (hasPendingResizedItems()) { updateListSize(); } auto to = App::histItemById(_channel, msgId); if (_list->itemTop(to) < 0) { return; } auto scrollTo = snap( itemTopForHighlight(to->mainView()), 0, _scroll->scrollTopMax()); animatedScrollToY(scrollTo, to); } void HistoryWidget::animatedScrollToY(int scrollTo, HistoryItem *attachTo) { Expects(_history != nullptr); if (hasPendingResizedItems()) { updateListSize(); } // Attach our scroll animation to some item. auto itemTop = _list->itemTop(attachTo); auto scrollTop = _scroll->scrollTop(); if (itemTop < 0 && !_history->isEmpty()) { attachTo = _history->blocks.back()->messages.back()->data(); itemTop = _list->itemTop(attachTo); } if (itemTop < 0 || (scrollTop == scrollTo)) { synteticScrollToY(scrollTo); return; } _scrollToAnimation.finish(); auto maxAnimatedDelta = _scroll->height(); auto transition = anim::sineInOut; if (scrollTo > scrollTop + maxAnimatedDelta) { scrollTop = scrollTo - maxAnimatedDelta; synteticScrollToY(scrollTop); transition = anim::easeOutCubic; } else if (scrollTo + maxAnimatedDelta < scrollTop) { scrollTop = scrollTo + maxAnimatedDelta; synteticScrollToY(scrollTop); transition = anim::easeOutCubic; } else { // In local showHistory() we forget current scroll state, // so we need to restore it synchronously, otherwise we may // jump to the bottom of history in some updateHistoryGeometry() call. synteticScrollToY(scrollTop); } _scrollToAnimation.start([this, itemId = attachTo->fullId()] { scrollToAnimationCallback(itemId); }, scrollTop - itemTop, scrollTo - itemTop, st::slideDuration, anim::sineInOut); } void HistoryWidget::scrollToAnimationCallback(FullMsgId attachToId) { auto itemTop = _list->itemTop(App::histItemById(attachToId)); if (itemTop < 0) { _scrollToAnimation.finish(); } else { synteticScrollToY(qRound(_scrollToAnimation.current()) + itemTop); } if (!_scrollToAnimation.animating()) { preloadHistoryByScroll(); checkReplyReturns(); } } void HistoryWidget::enqueueMessageHighlight( not_null<HistoryView::Element*> view) { if (const auto group = Auth().data().groups().find(view->data())) { if (const auto leader = group->items.back()->mainView()) { view = leader; } } auto enqueueMessageId = [this](MsgId universalId) { if (_highlightQueue.empty() && !_highlightTimer.isActive()) { highlightMessage(universalId); } else if (_highlightedMessageId != universalId && !base::contains(_highlightQueue, universalId)) { _highlightQueue.push_back(universalId); checkNextHighlight(); } }; const auto item = view->data(); if (item->history() == _history) { enqueueMessageId(item->id); } else if (item->history() == _migrated) { enqueueMessageId(-item->id); } } void HistoryWidget::highlightMessage(MsgId universalMessageId) { _highlightStart = getms(); _highlightedMessageId = universalMessageId; _highlightTimer.callEach(AnimationTimerDelta); adjustHighlightedMessageToMigrated(); } void HistoryWidget::adjustHighlightedMessageToMigrated() { if (_history && _highlightTimer.isActive() && _highlightedMessageId > 0 && _migrated && !_migrated->isEmpty() && _migrated->loadedAtBottom() && _migrated->blocks.back()->messages.back()->data()->isGroupMigrate() && _list->historyTop() != _list->historyDrawTop()) { auto highlighted = App::histItemById( _history->channelId(), _highlightedMessageId); if (highlighted && highlighted->isGroupMigrate()) { _highlightedMessageId = -_migrated->blocks.back()->messages.back()->data()->id; } } } void HistoryWidget::checkNextHighlight() { if (_highlightTimer.isActive()) { return; } auto nextHighlight = [this] { while (!_highlightQueue.empty()) { auto msgId = _highlightQueue.front(); _highlightQueue.pop_front(); auto item = getItemFromHistoryOrMigrated(msgId); if (item && item->mainView()) { return msgId; } } return 0; }(); if (!nextHighlight) { return; } highlightMessage(nextHighlight); } void HistoryWidget::updateHighlightedMessage() { const auto item = getItemFromHistoryOrMigrated(_highlightedMessageId); const auto view = item ? item->mainView() : nullptr; if (!view) { return stopMessageHighlight(); } auto duration = st::activeFadeInDuration + st::activeFadeOutDuration; if (getms() - _highlightStart > duration) { return stopMessageHighlight(); } Auth().data().requestViewRepaint(view); } TimeMs HistoryWidget::highlightStartTime(not_null<const HistoryItem*> item) const { auto isHighlighted = [this](not_null<const HistoryItem*> item) { if (item->id == _highlightedMessageId) { return (item->history() == _history); } else if (item->id == -_highlightedMessageId) { return (item->history() == _migrated); } return false; }; return (isHighlighted(item) && _highlightTimer.isActive()) ? _highlightStart : 0; } void HistoryWidget::stopMessageHighlight() { _highlightTimer.cancel(); _highlightedMessageId = 0; checkNextHighlight(); } void HistoryWidget::clearHighlightMessages() { _highlightQueue.clear(); stopMessageHighlight(); } int HistoryWidget::itemTopForHighlight( not_null<HistoryView::Element*> view) const { if (const auto group = Auth().data().groups().find(view->data())) { if (const auto leader = group->items.back()->mainView()) { view = leader; } } auto itemTop = _list->itemTop(view); Assert(itemTop >= 0); auto heightLeft = (_scroll->height() - view->height()); if (heightLeft <= 0) { return itemTop; } return qMax(itemTop - (heightLeft / 2), 0); } bool HistoryWidget::inSelectionMode() const { return _list ? _list->inSelectionMode() : false; } void HistoryWidget::start() { Auth().data().stickersUpdated( ) | rpl::start_with_next([this] { _tabbedSelector->refreshStickers(); updateStickersByEmoji(); }, lifetime()); updateRecentStickers(); Auth().data().notifySavedGifsUpdated(); subscribe(Auth().api().fullPeerUpdated(), [this](PeerData *peer) { fullPeerUpdated(peer); }); } void HistoryWidget::onMentionInsert(UserData *user) { QString replacement, entityTag; if (user->username.isEmpty()) { replacement = user->firstName; if (replacement.isEmpty()) { replacement = App::peerName(user); } entityTag = qsl("mention://user.") + QString::number(user->bareId()) + '.' + QString::number(user->accessHash()); } else { replacement = '@' + user->username; } _field->insertTag(replacement, entityTag); } void HistoryWidget::onHashtagOrBotCommandInsert( QString str, FieldAutocomplete::ChooseMethod method) { if (!_peer) { return; } // Send bot command at once, if it was not inserted by pressing Tab. if (str.at(0) == '/' && method != FieldAutocomplete::ChooseMethod::ByTab) { App::sendBotCommand(_peer, nullptr, str, replyToId()); App::main()->finishForwarding(_history); setFieldText(_field->getTextWithTagsPart(_field->textCursor().position())); } else { _field->insertTag(str); } } void HistoryWidget::updateInlineBotQuery() { UserData *bot = nullptr; QString inlineBotUsername; QString query = _field->getInlineBotQuery(&bot, &inlineBotUsername); if (inlineBotUsername != _inlineBotUsername) { _inlineBotUsername = inlineBotUsername; if (_inlineBotResolveRequestId) { // Notify::inlineBotRequesting(false); MTP::cancel(_inlineBotResolveRequestId); _inlineBotResolveRequestId = 0; } if (bot == Ui::LookingUpInlineBot) { _inlineBot = Ui::LookingUpInlineBot; // Notify::inlineBotRequesting(true); _inlineBotResolveRequestId = MTP::send(MTPcontacts_ResolveUsername(MTP_string(_inlineBotUsername)), rpcDone(&HistoryWidget::inlineBotResolveDone), rpcFail(&HistoryWidget::inlineBotResolveFail, _inlineBotUsername)); return; } } else if (bot == Ui::LookingUpInlineBot) { if (_inlineBot == Ui::LookingUpInlineBot) { return; } bot = _inlineBot; } applyInlineBotQuery(bot, query); } void HistoryWidget::applyInlineBotQuery(UserData *bot, const QString &query) { if (bot) { if (_inlineBot != bot) { _inlineBot = bot; inlineBotChanged(); } if (!_inlineResults) { _inlineResults.create(this, controller()); _inlineResults->setResultSelectedCallback([this](InlineBots::Result *result, UserData *bot) { onInlineResultSend(result, bot); }); updateControlsGeometry(); orderWidgets(); } _inlineResults->queryInlineBot(_inlineBot, _peer, query); if (!_fieldAutocomplete->isHidden()) { _fieldAutocomplete->hideAnimated(); } } else { clearInlineBot(); } } void HistoryWidget::orderWidgets() { if (_reportSpamPanel) { _reportSpamPanel->raise(); } _topShadow->raise(); if (_membersDropdown) { _membersDropdown->raise(); } if (_inlineResults) { _inlineResults->raise(); } if (_tabbedPanel) { _tabbedPanel->raise(); } _emojiSuggestions->raise(); if (_tabbedSelectorToggleTooltip) { _tabbedSelectorToggleTooltip->raise(); } _attachDragDocument->raise(); _attachDragPhoto->raise(); } void HistoryWidget::setReportSpamStatus(DBIPeerReportSpamStatus status) { if (_reportSpamStatus == status) { return; } _reportSpamStatus = status; if (_reportSpamStatus == dbiprsShowButton || _reportSpamStatus == dbiprsReportSent) { Assert(_peer != nullptr); _reportSpamPanel.create(this); connect(_reportSpamPanel, SIGNAL(reportClicked()), this, SLOT(onReportSpamClicked())); connect(_reportSpamPanel, SIGNAL(hideClicked()), this, SLOT(onReportSpamHide())); connect(_reportSpamPanel, SIGNAL(clearClicked()), this, SLOT(onReportSpamClear())); _reportSpamPanel->setReported(_reportSpamStatus == dbiprsReportSent, _peer); _reportSpamPanel->show(); orderWidgets(); updateControlsGeometry(); } else { _reportSpamPanel.destroy(); } } void HistoryWidget::updateStickersByEmoji() { int len = 0; if (!_editMsgId) { auto &text = _field->getTextWithTags().text; if (auto emoji = Ui::Emoji::Find(text, &len)) { if (text.size() > len) { len = 0; } else { _fieldAutocomplete->showStickers(emoji); } } } if (!len) { _fieldAutocomplete->showStickers(nullptr); } } void HistoryWidget::onTextChange() { updateInlineBotQuery(); updateStickersByEmoji(); if (_history) { if (!_inlineBot && !_editMsgId && (_textUpdateEvents & TextUpdateEvent::SendTyping)) { updateSendAction(_history, SendAction::Type::Typing); } } updateSendButtonType(); if (showRecordButton()) { _previewCancelled = false; } if (updateCmdStartShown()) { updateControlsVisibility(); updateControlsGeometry(); } _saveCloudDraftTimer.stop(); if (!_peer || !(_textUpdateEvents & TextUpdateEvent::SaveDraft)) return; _saveDraftText = true; onDraftSave(true); } void HistoryWidget::onDraftSaveDelayed() { if (!_peer || !(_textUpdateEvents & TextUpdateEvent::SaveDraft)) return; if (!_field->textCursor().anchor() && !_field->textCursor().position() && !_field->verticalScrollBar()->value()) { if (!Local::hasDraftCursors(_peer->id)) { return; } } onDraftSave(true); } void HistoryWidget::onDraftSave(bool delayed) { if (!_peer) return; if (delayed) { auto ms = getms(); if (!_saveDraftStart) { _saveDraftStart = ms; return _saveDraftTimer.start(SaveDraftTimeout); } else if (ms - _saveDraftStart < SaveDraftAnywayTimeout) { return _saveDraftTimer.start(SaveDraftTimeout); } } writeDrafts(nullptr, nullptr); } void HistoryWidget::saveFieldToHistoryLocalDraft() { if (!_history) return; if (_editMsgId) { _history->setEditDraft(std::make_unique<Data::Draft>(_field, _editMsgId, _previewCancelled, _saveEditMsgRequestId)); } else { if (_replyToId || !_field->isEmpty()) { _history->setLocalDraft(std::make_unique<Data::Draft>(_field, _replyToId, _previewCancelled)); } else { _history->clearLocalDraft(); } _history->clearEditDraft(); } } void HistoryWidget::onCloudDraftSave() { if (App::main()) { App::main()->saveDraftToCloud(); } } void HistoryWidget::writeDrafts(Data::Draft **localDraft, Data::Draft **editDraft) { Data::Draft *historyLocalDraft = _history ? _history->localDraft() : nullptr; if (!localDraft && _editMsgId) localDraft = &historyLocalDraft; bool save = _peer && (_saveDraftStart > 0); _saveDraftStart = 0; _saveDraftTimer.stop(); if (_saveDraftText) { if (save) { Local::MessageDraft storedLocalDraft, storedEditDraft; if (localDraft) { if (*localDraft) { storedLocalDraft = Local::MessageDraft((*localDraft)->msgId, (*localDraft)->textWithTags, (*localDraft)->previewCancelled); } } else { storedLocalDraft = Local::MessageDraft(_replyToId, _field->getTextWithTags(), _previewCancelled); } if (editDraft) { if (*editDraft) { storedEditDraft = Local::MessageDraft((*editDraft)->msgId, (*editDraft)->textWithTags, (*editDraft)->previewCancelled); } } else if (_editMsgId) { storedEditDraft = Local::MessageDraft(_editMsgId, _field->getTextWithTags(), _previewCancelled); } Local::writeDrafts(_peer->id, storedLocalDraft, storedEditDraft); if (_migrated) { Local::writeDrafts(_migrated->peer->id, Local::MessageDraft(), Local::MessageDraft()); } } _saveDraftText = false; } if (save) { MessageCursor localCursor, editCursor; if (localDraft) { if (*localDraft) { localCursor = (*localDraft)->cursor; } } else { localCursor = MessageCursor(_field); } if (editDraft) { if (*editDraft) { editCursor = (*editDraft)->cursor; } } else if (_editMsgId) { editCursor = MessageCursor(_field); } Local::writeDraftCursors(_peer->id, localCursor, editCursor); if (_migrated) { Local::writeDraftCursors(_migrated->peer->id, MessageCursor(), MessageCursor()); } } if (!_editMsgId && !_inlineBot) { _saveCloudDraftTimer.start(SaveCloudDraftIdleTimeout); } } void HistoryWidget::cancelSendAction( not_null<History*> history, SendAction::Type type) { auto i = _sendActionRequests.find(qMakePair(history, type)); if (i != _sendActionRequests.cend()) { MTP::cancel(i.value()); _sendActionRequests.erase(i); } } void HistoryWidget::cancelTypingAction() { if (_history) { cancelSendAction(_history, SendAction::Type::Typing); } _sendActionStopTimer.cancel(); } void HistoryWidget::updateSendAction( not_null<History*> history, SendAction::Type type, int32 progress) { const auto peer = history->peer; if (peer->isSelf() || (peer->isChannel() && !peer->isMegagroup())) { return; } const auto doing = (progress >= 0); if (history->mySendActionUpdated(type, doing)) { cancelSendAction(history, type); if (doing) { using Type = SendAction::Type; MTPsendMessageAction action; switch (type) { case Type::Typing: action = MTP_sendMessageTypingAction(); break; case Type::RecordVideo: action = MTP_sendMessageRecordVideoAction(); break; case Type::UploadVideo: action = MTP_sendMessageUploadVideoAction(MTP_int(progress)); break; case Type::RecordVoice: action = MTP_sendMessageRecordAudioAction(); break; case Type::UploadVoice: action = MTP_sendMessageUploadAudioAction(MTP_int(progress)); break; case Type::RecordRound: action = MTP_sendMessageRecordRoundAction(); break; case Type::UploadRound: action = MTP_sendMessageUploadRoundAction(MTP_int(progress)); break; case Type::UploadPhoto: action = MTP_sendMessageUploadPhotoAction(MTP_int(progress)); break; case Type::UploadFile: action = MTP_sendMessageUploadDocumentAction(MTP_int(progress)); break; case Type::ChooseLocation: action = MTP_sendMessageGeoLocationAction(); break; case Type::ChooseContact: action = MTP_sendMessageChooseContactAction(); break; case Type::PlayGame: action = MTP_sendMessageGamePlayAction(); break; } const auto key = qMakePair(history, type); const auto requestId = MTP::send( MTPmessages_SetTyping( peer->input, action), rpcDone(&HistoryWidget::sendActionDone)); _sendActionRequests.insert(key, requestId); if (type == Type::Typing) { _sendActionStopTimer.callOnce(kCancelTypingActionTimeout); } } } } void HistoryWidget::updateRecentStickers() { _tabbedSelector->refreshStickers(); } void HistoryWidget::sendActionDone(const MTPBool &result, mtpRequestId req) { for (auto i = _sendActionRequests.begin(), e = _sendActionRequests.end(); i != e; ++i) { if (i.value() == req) { _sendActionRequests.erase(i); break; } } } void HistoryWidget::activate() { if (_history) { if (!_historyInited) { updateHistoryGeometry(true); } else if (hasPendingResizedItems()) { updateHistoryGeometry(); } } if (App::wnd()) App::wnd()->setInnerFocus(); } void HistoryWidget::setInnerFocus() { if (_scroll->isHidden()) { setFocus(); } else if (_list) { if (_nonEmptySelection || (_list && _list->wasSelectedText()) || _recording || isBotStart() || isBlocked() || !_canSendMessages) { _list->setFocus(); } else { _field->setFocus(); } } } void HistoryWidget::onRecordError() { stopRecording(false); } void HistoryWidget::onRecordDone( QByteArray result, VoiceWaveform waveform, qint32 samples) { if (!canWriteMessage() || result.isEmpty()) return; ActivateWindowDelayed(controller()); const auto duration = samples / Media::Player::kDefaultFrequency; auto options = ApiWrap::SendOptions(_history); options.replyTo = replyToId(); Auth().api().sendVoiceMessage(result, waveform, duration, options); } void HistoryWidget::onRecordUpdate(quint16 level, qint32 samples) { if (!_recording) { return; } a_recordingLevel.start(level); _a_recording.start(); _recordingSamples = samples; if (samples < 0 || samples >= Media::Player::kDefaultFrequency * AudioVoiceMsgMaxLength) { stopRecording(_peer && samples > 0 && _inField); } updateField(); if (_history) { updateSendAction(_history, SendAction::Type::RecordVoice); } } void HistoryWidget::notify_botCommandsChanged(UserData *user) { if (_peer && (_peer == user || !_peer->isUser())) { if (_fieldAutocomplete->clearFilteredBotCommands()) { onCheckFieldAutocomplete(); } } } void HistoryWidget::notify_inlineBotRequesting(bool requesting) { _tabbedSelectorToggle->setLoading(requesting); } void HistoryWidget::notify_replyMarkupUpdated(const HistoryItem *item) { if (_keyboard->forMsgId() == item->fullId()) { updateBotKeyboard(item->history(), true); } } void HistoryWidget::notify_inlineKeyboardMoved(const HistoryItem *item, int oldKeyboardTop, int newKeyboardTop) { if (_history == item->history() || _migrated == item->history()) { if (int move = _list->moveScrollFollowingInlineKeyboard(item, oldKeyboardTop, newKeyboardTop)) { _addToScroll = move; } } } bool HistoryWidget::notify_switchInlineBotButtonReceived(const QString &query, UserData *samePeerBot, MsgId samePeerReplyTo) { if (samePeerBot) { if (_history) { TextWithTags textWithTags = { '@' + samePeerBot->username + ' ' + query, TextWithTags::Tags() }; MessageCursor cursor = { textWithTags.text.size(), textWithTags.text.size(), QFIXED_MAX }; auto replyTo = _history->peer->isUser() ? 0 : samePeerReplyTo; _history->setLocalDraft(std::make_unique<Data::Draft>(textWithTags, replyTo, cursor, false)); applyDraft(); return true; } } else if (auto bot = _peer ? _peer->asUser() : nullptr) { const auto toPeerId = bot->botInfo ? bot->botInfo->inlineReturnPeerId : PeerId(0); if (!toPeerId) { return false; } bot->botInfo->inlineReturnPeerId = 0; History *h = App::history(toPeerId); TextWithTags textWithTags = { '@' + bot->username + ' ' + query, TextWithTags::Tags() }; MessageCursor cursor = { textWithTags.text.size(), textWithTags.text.size(), QFIXED_MAX }; h->setLocalDraft(std::make_unique<Data::Draft>(textWithTags, 0, cursor, false)); if (h == _history) { applyDraft(); } else { Ui::showPeerHistory(toPeerId, ShowAtUnreadMsgId); } return true; } return false; } void HistoryWidget::notify_userIsBotChanged(UserData *user) { if (_peer && _peer == user) { _list->notifyIsBotChanged(); _list->updateBotInfo(); updateControlsVisibility(); updateControlsGeometry(); } } void HistoryWidget::notify_migrateUpdated(PeerData *peer) { if (_peer) { if (_peer == peer) { if (peer->migrateTo()) { showHistory(peer->migrateTo()->id, (_showAtMsgId > 0) ? (-_showAtMsgId) : _showAtMsgId, true); } else if ((_migrated ? _migrated->peer.get() : nullptr) != peer->migrateFrom()) { auto migrated = _history->migrateFrom(); if (_migrated || (migrated && migrated->unreadCount() > 0)) { showHistory(peer->id, peer->migrateFrom() ? _showAtMsgId : ((_showAtMsgId < 0 && -_showAtMsgId < ServerMaxMsgId) ? ShowAtUnreadMsgId : _showAtMsgId), true); } else { _migrated = migrated; _list->notifyMigrateUpdated(); updateHistoryGeometry(); } } } else if (_migrated && _migrated->peer == peer && peer->migrateTo() != _peer) { showHistory(_peer->id, _showAtMsgId, true); } } } bool HistoryWidget::cmd_search() { if (!inFocusChain() || !_history) return false; App::main()->searchInChat(_history); return true; } bool HistoryWidget::cmd_next_chat() { if (!_history) { return false; } const auto next = App::main()->chatListEntryAfter( Dialogs::RowDescriptor( _history, FullMsgId(_history->channelId(), std::max(_showAtMsgId, 0)))); if (const auto history = next.key.history()) { Ui::showPeerHistory(history, next.fullId.msg); return true; } else if (const auto feed = next.key.feed()) { if (const auto item = App::histItemById(next.fullId)) { controller()->showSection(HistoryFeed::Memento(feed, item->position())); } else { controller()->showSection(HistoryFeed::Memento(feed)); } } return false; } bool HistoryWidget::cmd_previous_chat() { if (!_history) { return false; } const auto next = App::main()->chatListEntryBefore( Dialogs::RowDescriptor( _history, FullMsgId(_history->channelId(), std::max(_showAtMsgId, 0)))); if (const auto history = next.key.history()) { Ui::showPeerHistory(history, next.fullId.msg); return true; } else if (const auto feed = next.key.feed()) { if (const auto item = App::histItemById(next.fullId)) { controller()->showSection(HistoryFeed::Memento(feed, item->position())); } else { controller()->showSection(HistoryFeed::Memento(feed)); } } return false; } void HistoryWidget::saveGif(DocumentData *doc) { if (doc->isGifv() && Auth().data().savedGifs().indexOf(doc) != 0) { MTPInputDocument mtpInput = doc->mtpInput(); if (mtpInput.type() != mtpc_inputDocumentEmpty) { MTP::send(MTPmessages_SaveGif(mtpInput, MTP_bool(false)), rpcDone(&HistoryWidget::saveGifDone, doc)); } } } void HistoryWidget::saveGifDone(DocumentData *doc, const MTPBool &result) { if (mtpIsTrue(result)) { App::addSavedGif(doc); } } void HistoryWidget::clearReplyReturns() { _replyReturns.clear(); _replyReturn = 0; } void HistoryWidget::pushReplyReturn(not_null<HistoryItem*> item) { if (item->history() == _history) { _replyReturns.push_back(item->id); } else if (item->history() == _migrated) { _replyReturns.push_back(-item->id); } else { return; } _replyReturn = item; updateControlsVisibility(); } QList<MsgId> HistoryWidget::replyReturns() { return _replyReturns; } void HistoryWidget::setReplyReturns(PeerId peer, const QList<MsgId> &replyReturns) { if (!_peer || _peer->id != peer) return; _replyReturns = replyReturns; if (_replyReturns.isEmpty()) { _replyReturn = 0; } else if (_replyReturns.back() < 0 && -_replyReturns.back() < ServerMaxMsgId) { _replyReturn = App::histItemById(0, -_replyReturns.back()); } else { _replyReturn = App::histItemById(_channel, _replyReturns.back()); } while (!_replyReturns.isEmpty() && !_replyReturn) { _replyReturns.pop_back(); if (_replyReturns.isEmpty()) { _replyReturn = 0; } else if (_replyReturns.back() < 0 && -_replyReturns.back() < ServerMaxMsgId) { _replyReturn = App::histItemById(0, -_replyReturns.back()); } else { _replyReturn = App::histItemById(_channel, _replyReturns.back()); } } } void HistoryWidget::calcNextReplyReturn() { _replyReturn = 0; while (!_replyReturns.isEmpty() && !_replyReturn) { _replyReturns.pop_back(); if (_replyReturns.isEmpty()) { _replyReturn = 0; } else if (_replyReturns.back() < 0 && -_replyReturns.back() < ServerMaxMsgId) { _replyReturn = App::histItemById(0, -_replyReturns.back()); } else { _replyReturn = App::histItemById(_channel, _replyReturns.back()); } } if (!_replyReturn) updateControlsVisibility(); } void HistoryWidget::fastShowAtEnd(not_null<History*> history) { if (_history != history) { return; } clearAllLoadRequests(); setMsgId(ShowAtUnreadMsgId); _historyInited = false; if (_history->isReadyFor(_showAtMsgId)) { historyLoaded(); } else { firstLoadMessages(); doneShow(); } } void HistoryWidget::applyDraft(bool parseLinks, Ui::FlatTextarea::UndoHistoryAction undoHistoryAction) { auto draft = _history ? _history->draft() : nullptr; auto fieldAvailable = canWriteMessage(); if (!draft || (!_history->editDraft() && !fieldAvailable)) { auto fieldWillBeHiddenAfterEdit = (!fieldAvailable && _editMsgId != 0); clearFieldText(0, undoHistoryAction); _field->setFocus(); _replyEditMsg = nullptr; _editMsgId = _replyToId = 0; if (fieldWillBeHiddenAfterEdit) { updateControlsVisibility(); updateControlsGeometry(); } return; } _textUpdateEvents = 0; setFieldText(draft->textWithTags, 0, undoHistoryAction); _field->setFocus(); draft->cursor.applyTo(_field); _textUpdateEvents = TextUpdateEvent::SaveDraft | TextUpdateEvent::SendTyping; _previewCancelled = draft->previewCancelled; _replyEditMsg = nullptr; if (auto editDraft = _history->editDraft()) { _editMsgId = editDraft->msgId; _replyToId = 0; } else { _editMsgId = 0; _replyToId = readyToForward() ? 0 : _history->localDraft()->msgId; } updateControlsVisibility(); updateControlsGeometry(); if (parseLinks) { onPreviewParse(); } if (_editMsgId || _replyToId) { updateReplyEditTexts(); if (!_replyEditMsg) { Auth().api().requestMessageData( _peer->asChannel(), _editMsgId ? _editMsgId : _replyToId, replyEditMessageDataCallback()); } } } void HistoryWidget::applyCloudDraft(History *history) { if (_history == history && !_editMsgId) { applyDraft(true, Ui::FlatTextarea::AddToUndoHistory); updateControlsVisibility(); updateControlsGeometry(); } } void HistoryWidget::showHistory(const PeerId &peerId, MsgId showAtMsgId, bool reload) { MsgId wasMsgId = _showAtMsgId; History *wasHistory = _history; bool startBot = (showAtMsgId == ShowAndStartBotMsgId); if (startBot) { showAtMsgId = ShowAtTheEndMsgId; } clearHighlightMessages(); if (_history) { if (_peer->id == peerId && !reload) { updateForwarding(); bool canShowNow = _history->isReadyFor(showAtMsgId); if (!canShowNow) { delayedShowAt(showAtMsgId); } else { _history->forgetScrollState(); if (_migrated) { _migrated->forgetScrollState(); } clearDelayedShowAt(); if (_replyReturn) { if (_replyReturn->history() == _history && _replyReturn->id == showAtMsgId) { calcNextReplyReturn(); } else if (_replyReturn->history() == _migrated && -_replyReturn->id == showAtMsgId) { calcNextReplyReturn(); } } setMsgId(showAtMsgId); if (_historyInited) { countHistoryShowFrom(); destroyUnreadBar(); auto item = getItemFromHistoryOrMigrated(_showAtMsgId); animatedScrollToY(countInitialScrollTop(), item); } else { historyLoaded(); } } _topBar->update(); update(); if (const auto user = _peer->asUser()) { if (const auto &info = user->botInfo) { if (startBot) { if (wasHistory) { info->inlineReturnPeerId = wasHistory->peer->id; } onBotStart(); _history->clearLocalDraft(); applyDraft(); _send->finishAnimating(); } } } return; } updateSendAction(_history, SendAction::Type::Typing, -1); cancelTypingAction(); } if (!cAutoPlayGif()) { Auth().data().stopAutoplayAnimations(); } clearReplyReturns(); clearAllLoadRequests(); if (_history) { if (Ui::InFocusChain(_list)) { // Removing focus from list clears selected and updates top bar. setFocus(); } if (App::main()) { App::main()->saveDraftToCloud(); } if (_migrated) { _migrated->clearLocalDraft(); // use migrated draft only once _migrated->clearEditDraft(); } _history->showAtMsgId = _showAtMsgId; destroyUnreadBar(); destroyPinnedBar(); _membersDropdown.destroy(); _scrollToAnimation.finish(); _history = _migrated = nullptr; _peer = nullptr; _channel = NoChannel; _canSendMessages = false; _silent.destroy(); updateBotKeyboard(); } App::clearMousedItems(); _addToScroll = 0; _saveEditMsgRequestId = 0; _replyEditMsg = nullptr; _editMsgId = _replyToId = 0; _previewData = nullptr; _previewCache.clear(); _fieldBarCancel->hide(); _membersDropdownShowTimer.stop(); _scroll->takeWidget<HistoryInner>().destroy(); _list = nullptr; clearInlineBot(); _showAtMsgId = showAtMsgId; _historyInited = false; if (peerId) { _peer = App::peer(peerId); _channel = peerToChannel(_peer->id); _canSendMessages = _peer->canWrite(); _tabbedSelector->setCurrentPeer(_peer); } if (_peer && _peer->isChannel()) { _peer->asChannel()->updateFull(); _joinChannel->setText(lang(_peer->isMegagroup() ? lng_profile_join_group : lng_profile_join_channel).toUpper()); } _unblockRequest = _reportSpamRequest = 0; if (_reportSpamSettingRequestId > 0) { MTP::cancel(_reportSpamSettingRequestId); } _reportSpamSettingRequestId = ReportSpamRequestNeeded; noSelectingScroll(); _nonEmptySelection = false; if (_peer) { App::forgetMedia(); Auth().data().forgetMedia(); _serviceImageCacheSize = imageCacheSize(); Auth().downloader().clearPriorities(); _history = App::history(_peer); _migrated = _history->migrateFrom(); _topBar->setActiveChat(_history); updateTopBarSelection(); if (_channel) { updateNotifySettings(); if (_peer->notifySettingsUnknown()) { Auth().api().requestNotifySetting(_peer); } refreshSilentToggle(); } if (_showAtMsgId == ShowAtUnreadMsgId) { if (_history->scrollTopItem) { _showAtMsgId = _history->showAtMsgId; } } else { _history->forgetScrollState(); if (_migrated) { _migrated->forgetScrollState(); } } _scroll->hide(); _list = _scroll->setOwnedWidget(object_ptr<HistoryInner>(this, controller(), _scroll, _history)); _list->show(); _updateHistoryItems.stop(); pinnedMsgVisibilityUpdated(); if (_history->scrollTopItem || (_migrated && _migrated->scrollTopItem) || _history->isReadyFor(_showAtMsgId)) { historyLoaded(); } else { firstLoadMessages(); doneShow(); } handlePeerUpdate(); Local::readDraftsWithCursors(_history); if (_migrated) { Local::readDraftsWithCursors(_migrated); _migrated->clearEditDraft(); _history->takeLocalDraft(_migrated); } applyDraft(false); _send->finishAnimating(); _tabbedSelector->showMegagroupSet(_peer->asMegagroup()); updateControlsGeometry(); if (!_previewCancelled) { onPreviewParse(); } connect(_scroll, SIGNAL(geometryChanged()), _list, SLOT(onParentGeometryChanged())); if (const auto user = _peer->asUser()) { if (const auto &info = user->botInfo) { if (startBot) { if (wasHistory) { info->inlineReturnPeerId = wasHistory->peer->id; } onBotStart(); } } } unreadCountChanged(_history); // set _historyDown badge. } else { _topBar->setActiveChat(Dialogs::Key()); updateTopBarSelection(); clearFieldText(); _tabbedSelector->showMegagroupSet(nullptr); doneShow(); } updateForwarding(); updateOverStates(mapFromGlobal(QCursor::pos())); if (_history) { controller()->setActiveChatEntry({ _history, FullMsgId(_history->channelId(), _showAtMsgId) }); } update(); crl::on_main(App::wnd(), [] { App::wnd()->setInnerFocus(); }); } void HistoryWidget::clearDelayedShowAt() { _delayedShowAtMsgId = -1; if (_delayedShowAtRequest) { MTP::cancel(_delayedShowAtRequest); _delayedShowAtRequest = 0; } } void HistoryWidget::clearAllLoadRequests() { clearDelayedShowAt(); if (_firstLoadRequest) MTP::cancel(_firstLoadRequest); if (_preloadRequest) MTP::cancel(_preloadRequest); if (_preloadDownRequest) MTP::cancel(_preloadDownRequest); _preloadRequest = _preloadDownRequest = _firstLoadRequest = 0; } void HistoryWidget::updateFieldSubmitSettings() { auto settings = Ui::FlatTextarea::SubmitSettings::Enter; if (_isInlineBot) { settings = Ui::FlatTextarea::SubmitSettings::None; } else if (cCtrlEnter()) { settings = Ui::FlatTextarea::SubmitSettings::CtrlEnter; } _field->setSubmitSettings(settings); } void HistoryWidget::updateNotifySettings() { if (!_peer || !_peer->isChannel()) return; _muteUnmute->setText(lang(_history->mute() ? lng_channel_unmute : lng_channel_mute).toUpper()); if (!_peer->notifySettingsUnknown()) { if (_silent) { _silent->setChecked(_peer->notifySilentPosts()); } else if (hasSilentToggle()) { refreshSilentToggle(); updateControlsGeometry(); updateControlsVisibility(); } } } void HistoryWidget::refreshSilentToggle() { if (!_silent && hasSilentToggle()) { _silent.create(this, _peer->asChannel()); } else if (_silent && !hasSilentToggle()) { _silent.destroy(); } } bool HistoryWidget::contentOverlapped(const QRect &globalRect) { return (_attachDragDocument->overlaps(globalRect) || _attachDragPhoto->overlaps(globalRect) || _fieldAutocomplete->overlaps(globalRect) || (_tabbedPanel && _tabbedPanel->overlaps(globalRect)) || (_inlineResults && _inlineResults->overlaps(globalRect))); } void HistoryWidget::updateReportSpamStatus() { if (!_peer || (_peer->isUser() && (_peer->id == Auth().userPeerId() || isNotificationsUser(_peer->id) || isServiceUser(_peer->id) || _peer->asUser()->botInfo))) { setReportSpamStatus(dbiprsHidden); return; } else if (!_firstLoadRequest && _history->isEmpty()) { setReportSpamStatus(dbiprsNoButton); if (cReportSpamStatuses().contains(_peer->id)) { cRefReportSpamStatuses().remove(_peer->id); Local::writeReportSpamStatuses(); } return; } else { auto i = cReportSpamStatuses().constFind(_peer->id); if (i != cReportSpamStatuses().cend()) { if (i.value() == dbiprsNoButton) { setReportSpamStatus(dbiprsHidden); if (!_peer->isUser() || _peer->asUser()->contactStatus() != UserData::ContactStatus::Contact) { MTP::send(MTPmessages_HideReportSpam(_peer->input)); } cRefReportSpamStatuses().insert(_peer->id, _reportSpamStatus); Local::writeReportSpamStatuses(); } else { setReportSpamStatus(i.value()); if (_reportSpamStatus == dbiprsShowButton) { requestReportSpamSetting(); } } return; } else if (_peer->migrateFrom()) { // migrate report status i = cReportSpamStatuses().constFind(_peer->migrateFrom()->id); if (i != cReportSpamStatuses().cend()) { if (i.value() == dbiprsNoButton) { setReportSpamStatus(dbiprsHidden); if (!_peer->isUser() || _peer->asUser()->contactStatus() != UserData::ContactStatus::Contact) { MTP::send(MTPmessages_HideReportSpam(_peer->input)); } } else { setReportSpamStatus(i.value()); if (_reportSpamStatus == dbiprsShowButton) { requestReportSpamSetting(); } } cRefReportSpamStatuses().insert(_peer->id, _reportSpamStatus); Local::writeReportSpamStatuses(); return; } } } auto status = dbiprsRequesting; if (!Auth().data().contactsLoaded().value() || _firstLoadRequest) { status = dbiprsUnknown; } else if (_peer->isUser() && _peer->asUser()->contactStatus() == UserData::ContactStatus::Contact) { status = dbiprsHidden; } else { requestReportSpamSetting(); } setReportSpamStatus(status); if (_reportSpamStatus == dbiprsHidden) { cRefReportSpamStatuses().insert(_peer->id, _reportSpamStatus); Local::writeReportSpamStatuses(); } } void HistoryWidget::requestReportSpamSetting() { if (_reportSpamSettingRequestId >= 0 || !_peer) return; _reportSpamSettingRequestId = MTP::send(MTPmessages_GetPeerSettings(_peer->input), rpcDone(&HistoryWidget::reportSpamSettingDone), rpcFail(&HistoryWidget::reportSpamSettingFail)); } void HistoryWidget::reportSpamSettingDone(const MTPPeerSettings &result, mtpRequestId req) { if (req != _reportSpamSettingRequestId) return; _reportSpamSettingRequestId = 0; if (result.type() == mtpc_peerSettings) { auto &d = result.c_peerSettings(); auto status = d.is_report_spam() ? dbiprsShowButton : dbiprsHidden; if (status != _reportSpamStatus) { setReportSpamStatus(status); if (_reportSpamPanel) { _reportSpamPanel->setReported(false, _peer); } cRefReportSpamStatuses().insert(_peer->id, _reportSpamStatus); Local::writeReportSpamStatuses(); updateControlsVisibility(); } } } bool HistoryWidget::reportSpamSettingFail(const RPCError &error, mtpRequestId req) { if (MTP::isDefaultHandledError(error)) return false; if (req == _reportSpamSettingRequestId) { req = 0; } return true; } bool HistoryWidget::canWriteMessage() const { if (!_history || !_canSendMessages) return false; if (isBlocked() || isJoinChannel() || isMuteUnmute() || isBotStart()) return false; return true; } bool HistoryWidget::isRestrictedWrite() const { if (auto megagroup = _peer ? _peer->asMegagroup() : nullptr) { return megagroup->restricted( ChannelRestriction::f_send_messages); } return false; } void HistoryWidget::updateControlsVisibility() { if (!_a_show.animating()) { _topShadow->setVisible(_peer != nullptr); _topBar->setVisible(_peer != nullptr); } updateHistoryDownVisibility(); updateUnreadMentionsVisibility(); if (!_history || _a_show.animating()) { hideChildren(); return; } if (_pinnedBar) { _pinnedBar->cancel->show(); _pinnedBar->shadow->show(); } if (_firstLoadRequest && !_scroll->isHidden()) { _scroll->hide(); } else if (!_firstLoadRequest && _scroll->isHidden()) { _scroll->show(); } if (_reportSpamPanel) { _reportSpamPanel->show(); } if (!editingMessage() && (isBlocked() || isJoinChannel() || isMuteUnmute() || isBotStart())) { if (isBlocked()) { _joinChannel->hide(); _muteUnmute->hide(); _botStart->hide(); if (_unblock->isHidden()) { _unblock->clearState(); _unblock->show(); } } else if (isJoinChannel()) { _unblock->hide(); _muteUnmute->hide(); _botStart->hide(); if (_joinChannel->isHidden()) { _joinChannel->clearState(); _joinChannel->show(); } } else if (isMuteUnmute()) { _unblock->hide(); _joinChannel->hide(); _botStart->hide(); if (_muteUnmute->isHidden()) { _muteUnmute->clearState(); _muteUnmute->show(); } } else if (isBotStart()) { _unblock->hide(); _joinChannel->hide(); _muteUnmute->hide(); if (_botStart->isHidden()) { _botStart->clearState(); _botStart->show(); } } _kbShown = false; _fieldAutocomplete->hide(); _send->hide(); if (_silent) { _silent->hide(); } _kbScroll->hide(); _fieldBarCancel->hide(); _attachToggle->hide(); _tabbedSelectorToggle->hide(); _botKeyboardShow->hide(); _botKeyboardHide->hide(); _botCommandStart->hide(); if (_tabbedPanel) { _tabbedPanel->hide(); } if (_inlineResults) { _inlineResults->hide(); } if (!_field->isHidden()) { _field->hide(); updateControlsGeometry(); update(); } } else if (editingMessage() || _canSendMessages) { onCheckFieldAutocomplete(); _unblock->hide(); _botStart->hide(); _joinChannel->hide(); _muteUnmute->hide(); _send->show(); updateSendButtonType(); if (_recording) { _field->hide(); _tabbedSelectorToggle->hide(); _botKeyboardShow->hide(); _botKeyboardHide->hide(); _botCommandStart->hide(); _attachToggle->hide(); if (_silent) { _silent->hide(); } if (_kbShown) { _kbScroll->show(); } else { _kbScroll->hide(); } } else { _field->show(); if (_kbShown) { _kbScroll->show(); _tabbedSelectorToggle->hide(); _botKeyboardHide->show(); _botKeyboardShow->hide(); _botCommandStart->hide(); } else if (_kbReplyTo) { _kbScroll->hide(); _tabbedSelectorToggle->show(); _botKeyboardHide->hide(); _botKeyboardShow->hide(); _botCommandStart->hide(); } else { _kbScroll->hide(); _tabbedSelectorToggle->show(); _botKeyboardHide->hide(); if (_keyboard->hasMarkup()) { _botKeyboardShow->show(); _botCommandStart->hide(); } else { _botKeyboardShow->hide(); if (_cmdStartShown) { _botCommandStart->show(); } else { _botCommandStart->hide(); } } } _attachToggle->show(); if (_silent) { _silent->show(); } updateFieldPlaceholder(); } if (_editMsgId || _replyToId || readyToForward() || (_previewData && _previewData->pendingTill >= 0) || _kbReplyTo) { if (_fieldBarCancel->isHidden()) { _fieldBarCancel->show(); updateControlsGeometry(); update(); } } else { _fieldBarCancel->hide(); } } else { _fieldAutocomplete->hide(); _send->hide(); _unblock->hide(); _botStart->hide(); _joinChannel->hide(); _muteUnmute->hide(); _attachToggle->hide(); if (_silent) { _silent->hide(); } _kbScroll->hide(); _fieldBarCancel->hide(); _attachToggle->hide(); _tabbedSelectorToggle->hide(); _botKeyboardShow->hide(); _botKeyboardHide->hide(); _botCommandStart->hide(); if (_tabbedPanel) { _tabbedPanel->hide(); } if (_inlineResults) { _inlineResults->hide(); } _kbScroll->hide(); if (!_field->isHidden()) { _field->hide(); updateControlsGeometry(); update(); } } //checkTabbedSelectorToggleTooltip(); updateMouseTracking(); } void HistoryWidget::updateMouseTracking() { bool trackMouse = !_fieldBarCancel->isHidden() || _pinnedBar; setMouseTracking(trackMouse); } void HistoryWidget::destroyUnreadBar() { if (_history) _history->destroyUnreadBar(); if (_migrated) _migrated->destroyUnreadBar(); } void HistoryWidget::newUnreadMsg( not_null<History*> history, not_null<HistoryItem*> item) { if (_history == history) { // If we get here in non-resized state we can't rely on results of // doWeReadServerHistory() and mark chat as read. // If we receive N messages being not at bottom: // - on first message we set unreadcount += 1, firstUnreadMessage. // - on second we get wrong doWeReadServerHistory() and read both. Auth().data().sendHistoryChangeNotifications(); if (_scroll->scrollTop() + 1 > _scroll->scrollTopMax()) { destroyUnreadBar(); } if (App::wnd()->doWeReadServerHistory()) { if (item->mentionsMe() && item->isMediaUnread()) { Auth().api().markMediaRead(item); } Auth().api().readServerHistoryForce(history); return; } } Auth().notifications().schedule(history, item); if (history->unreadCountKnown()) { history->changeUnreadCount(1); } else { Auth().api().requestDialogEntry(history); } } void HistoryWidget::historyToDown(History *history) { history->forgetScrollState(); if (auto migrated = App::historyLoaded(history->peer->migrateFrom())) { migrated->forgetScrollState(); } if (history == _history) { synteticScrollToY(_scroll->scrollTopMax()); } } void HistoryWidget::unreadCountChanged(not_null<History*> history) { if (history == _history || history == _migrated) { updateHistoryDownVisibility(); _historyDown->setUnreadCount(_history->chatListUnreadCount()); } } bool HistoryWidget::messagesFailed(const RPCError &error, mtpRequestId requestId) { if (MTP::isDefaultHandledError(error)) return false; if (error.type() == qstr("CHANNEL_PRIVATE") || error.type() == qstr("CHANNEL_PUBLIC_GROUP_NA") || error.type() == qstr("USER_BANNED_IN_CHANNEL")) { auto was = _peer; controller()->showBackFromStack(); Ui::show(Box<InformBox>(lang((was && was->isMegagroup()) ? lng_group_not_accessible : lng_channel_not_accessible))); return true; } LOG(("RPC Error: %1 %2: %3").arg(error.code()).arg(error.type()).arg(error.description())); if (_preloadRequest == requestId) { _preloadRequest = 0; } else if (_preloadDownRequest == requestId) { _preloadDownRequest = 0; } else if (_firstLoadRequest == requestId) { _firstLoadRequest = 0; controller()->showBackFromStack(); } else if (_delayedShowAtRequest == requestId) { _delayedShowAtRequest = 0; } return true; } void HistoryWidget::messagesReceived(PeerData *peer, const MTPmessages_Messages &messages, mtpRequestId requestId) { if (!_history) { _preloadRequest = _preloadDownRequest = _firstLoadRequest = _delayedShowAtRequest = 0; return; } bool toMigrated = (peer == _peer->migrateFrom()); if (peer != _peer && !toMigrated) { _preloadRequest = _preloadDownRequest = _firstLoadRequest = _delayedShowAtRequest = 0; return; } auto count = 0; const QVector<MTPMessage> emptyList, *histList = &emptyList; switch (messages.type()) { case mtpc_messages_messages: { auto &d(messages.c_messages_messages()); App::feedUsers(d.vusers); App::feedChats(d.vchats); histList = &d.vmessages.v; count = histList->size(); } break; case mtpc_messages_messagesSlice: { auto &d(messages.c_messages_messagesSlice()); App::feedUsers(d.vusers); App::feedChats(d.vchats); histList = &d.vmessages.v; count = d.vcount.v; } break; case mtpc_messages_channelMessages: { auto &d(messages.c_messages_channelMessages()); if (peer && peer->isChannel()) { peer->asChannel()->ptsReceived(d.vpts.v); } else { LOG(("API Error: received messages.channelMessages when no channel was passed! (HistoryWidget::messagesReceived)")); } App::feedUsers(d.vusers); App::feedChats(d.vchats); histList = &d.vmessages.v; count = d.vcount.v; } break; case mtpc_messages_messagesNotModified: { LOG(("API Error: received messages.messagesNotModified! (HistoryWidget::messagesReceived)")); } break; } const auto ExtractFirstId = [&] { return histList->empty() ? -1 : idFromMessage(histList->front()); }; const auto ExtractLastId = [&] { return histList->empty() ? -1 : idFromMessage(histList->back()); }; const auto PeerString = [](PeerId peerId) { if (peerIsUser(peerId)) { return QString("User-%1").arg(peerToUser(peerId)); } else if (peerIsChat(peerId)) { return QString("Chat-%1").arg(peerToChat(peerId)); } else if (peerIsChannel(peerId)) { return QString("Channel-%1").arg(peerToChannel(peerId)); } return QString("Bad-%1").arg(peerId); }; if (_preloadRequest == requestId) { auto to = toMigrated ? _migrated : _history; addMessagesToFront(peer, *histList); _preloadRequest = 0; preloadHistoryIfNeeded(); if (_reportSpamStatus == dbiprsUnknown) { updateReportSpamStatus(); if (_reportSpamStatus != dbiprsUnknown) updateControlsVisibility(); } } else if (_preloadDownRequest == requestId) { auto to = toMigrated ? _migrated : _history; addMessagesToBack(peer, *histList); _preloadDownRequest = 0; preloadHistoryIfNeeded(); if (_history->loadedAtBottom() && App::wnd()) App::wnd()->checkHistoryActivation(); } else if (_firstLoadRequest == requestId) { if (toMigrated) { _history->unloadBlocks(); } else if (_migrated) { _migrated->unloadBlocks(); } addMessagesToFront(peer, *histList); _firstLoadRequest = 0; if (_history->loadedAtTop() && _history->isEmpty() && count > 0) { firstLoadMessages(); return; } historyLoaded(); } else if (_delayedShowAtRequest == requestId) { if (toMigrated) { _history->unloadBlocks(); } else if (_migrated) { _migrated->unloadBlocks(); } _delayedShowAtRequest = 0; _history->getReadyFor(_delayedShowAtMsgId); if (_history->isEmpty()) { if (_preloadRequest) MTP::cancel(_preloadRequest); if (_preloadDownRequest) MTP::cancel(_preloadDownRequest); if (_firstLoadRequest) MTP::cancel(_firstLoadRequest); _preloadRequest = _preloadDownRequest = 0; _firstLoadRequest = -1; // hack - don't updateListSize yet addMessagesToFront(peer, *histList); _firstLoadRequest = 0; if (_history->loadedAtTop() && _history->isEmpty() && count > 0) { firstLoadMessages(); return; } } if (_replyReturn) { if (_replyReturn->history() == _history && _replyReturn->id == _delayedShowAtMsgId) { calcNextReplyReturn(); } else if (_replyReturn->history() == _migrated && -_replyReturn->id == _delayedShowAtMsgId) { calcNextReplyReturn(); } } setMsgId(_delayedShowAtMsgId); _historyInited = false; historyLoaded(); } } void HistoryWidget::historyLoaded() { countHistoryShowFrom(); destroyUnreadBar(); doneShow(); } void HistoryWidget::windowShown() { updateControlsGeometry(); } bool HistoryWidget::doWeReadServerHistory() const { if (!_history || !_list) return true; if (_firstLoadRequest || _a_show.animating()) return false; if (_history->loadedAtBottom()) { int scrollTop = _scroll->scrollTop(); if (scrollTop + 1 > _scroll->scrollTopMax()) return true; if (const auto unread = firstUnreadMessage()) { const auto scrollBottom = scrollTop + _scroll->height(); if (scrollBottom > _list->itemTop(unread)) { return true; } } } if (_history->hasNotFreezedUnreadBar() || (_migrated && _migrated->hasNotFreezedUnreadBar())) { return true; } return false; } bool HistoryWidget::doWeReadMentions() const { if (!_history || !_list) return true; if (_firstLoadRequest || _a_show.animating()) return false; return true; } void HistoryWidget::firstLoadMessages() { if (!_history || _firstLoadRequest) return; auto from = _peer; auto offsetId = 0; auto offset = 0; auto loadCount = kMessagesPerPage; if (_showAtMsgId == ShowAtUnreadMsgId) { if (const auto around = _migrated ? _migrated->loadAroundId() : 0) { _history->getReadyFor(_showAtMsgId); from = _migrated->peer; offset = -loadCount / 2; offsetId = around; } else if (const auto around = _history->loadAroundId()) { _history->getReadyFor(_showAtMsgId); offset = -loadCount / 2; offsetId = around; } else { _history->getReadyFor(ShowAtTheEndMsgId); } } else if (_showAtMsgId == ShowAtTheEndMsgId) { _history->getReadyFor(_showAtMsgId); loadCount = kMessagesPerPageFirst; } else if (_showAtMsgId > 0) { _history->getReadyFor(_showAtMsgId); offset = -loadCount / 2; offsetId = _showAtMsgId; } else if (_showAtMsgId < 0 && _history->isChannel()) { if (_showAtMsgId < 0 && -_showAtMsgId < ServerMaxMsgId && _migrated) { _history->getReadyFor(_showAtMsgId); from = _migrated->peer; offset = -loadCount / 2; offsetId = -_showAtMsgId; } else if (_showAtMsgId == SwitchAtTopMsgId) { _history->getReadyFor(_showAtMsgId); } } auto offsetDate = 0; auto maxId = 0; auto minId = 0; auto historyHash = 0; _firstLoadRequest = MTP::send( MTPmessages_GetHistory( from->input, MTP_int(offsetId), MTP_int(offsetDate), MTP_int(offset), MTP_int(loadCount), MTP_int(maxId), MTP_int(minId), MTP_int(historyHash)), rpcDone(&HistoryWidget::messagesReceived, from), rpcFail(&HistoryWidget::messagesFailed)); } void HistoryWidget::loadMessages() { if (!_history || _preloadRequest) return; if (_history->isEmpty() && _migrated && _migrated->isEmpty()) { return firstLoadMessages(); } auto loadMigrated = _migrated && (_history->isEmpty() || _history->loadedAtTop() || (!_migrated->isEmpty() && !_migrated->loadedAtBottom())); auto from = loadMigrated ? _migrated : _history; if (from->loadedAtTop()) { return; } auto offsetId = from->minMsgId(); auto addOffset = 0; auto loadCount = offsetId ? kMessagesPerPage : kMessagesPerPageFirst; auto offsetDate = 0; auto maxId = 0; auto minId = 0; auto historyHash = 0; _preloadRequest = MTP::send( MTPmessages_GetHistory( from->peer->input, MTP_int(offsetId), MTP_int(offsetDate), MTP_int(addOffset), MTP_int(loadCount), MTP_int(maxId), MTP_int(minId), MTP_int(historyHash)), rpcDone(&HistoryWidget::messagesReceived, from->peer.get()), rpcFail(&HistoryWidget::messagesFailed)); } void HistoryWidget::loadMessagesDown() { if (!_history || _preloadDownRequest) return; if (_history->isEmpty() && _migrated && _migrated->isEmpty()) { return firstLoadMessages(); } auto loadMigrated = _migrated && !(_migrated->isEmpty() || _migrated->loadedAtBottom() || (!_history->isEmpty() && !_history->loadedAtTop())); auto from = loadMigrated ? _migrated : _history; if (from->loadedAtBottom()) { return; } auto loadCount = kMessagesPerPage; auto addOffset = -loadCount; auto offsetId = from->maxMsgId(); if (!offsetId) { if (loadMigrated || !_migrated) return; ++offsetId; ++addOffset; } auto offsetDate = 0; auto maxId = 0; auto minId = 0; auto historyHash = 0; _preloadDownRequest = MTP::send( MTPmessages_GetHistory( from->peer->input, MTP_int(offsetId + 1), MTP_int(offsetDate), MTP_int(addOffset), MTP_int(loadCount), MTP_int(maxId), MTP_int(minId), MTP_int(historyHash)), rpcDone(&HistoryWidget::messagesReceived, from->peer.get()), rpcFail(&HistoryWidget::messagesFailed)); } void HistoryWidget::delayedShowAt(MsgId showAtMsgId) { if (!_history || (_delayedShowAtRequest && _delayedShowAtMsgId == showAtMsgId)) return; clearDelayedShowAt(); _delayedShowAtMsgId = showAtMsgId; auto from = _peer; auto offsetId = 0; auto offset = 0; auto loadCount = kMessagesPerPage; if (_delayedShowAtMsgId == ShowAtUnreadMsgId) { if (const auto around = _migrated ? _migrated->loadAroundId() : 0) { from = _migrated->peer; offset = -loadCount / 2; offsetId = around; } else if (const auto around = _history->loadAroundId()) { offset = -loadCount / 2; offsetId = around; } else { loadCount = kMessagesPerPageFirst; } } else if (_delayedShowAtMsgId == ShowAtTheEndMsgId) { loadCount = kMessagesPerPageFirst; } else if (_delayedShowAtMsgId > 0) { offset = -loadCount / 2; offsetId = _delayedShowAtMsgId; } else if (_delayedShowAtMsgId < 0 && _history->isChannel()) { if (_delayedShowAtMsgId < 0 && -_delayedShowAtMsgId < ServerMaxMsgId && _migrated) { from = _migrated->peer; offset = -loadCount / 2; offsetId = -_delayedShowAtMsgId; } } auto offsetDate = 0; auto maxId = 0; auto minId = 0; auto historyHash = 0; _delayedShowAtRequest = MTP::send( MTPmessages_GetHistory( from->input, MTP_int(offsetId), MTP_int(offsetDate), MTP_int(offset), MTP_int(loadCount), MTP_int(maxId), MTP_int(minId), MTP_int(historyHash)), rpcDone(&HistoryWidget::messagesReceived, from), rpcFail(&HistoryWidget::messagesFailed)); } void HistoryWidget::onScroll() { App::checkImageCacheSize(); preloadHistoryIfNeeded(); visibleAreaUpdated(); if (!_synteticScrollEvent) { _lastUserScrolled = getms(); } } bool HistoryWidget::isItemCompletelyHidden(HistoryItem *item) const { const auto view = item ? item->mainView() : nullptr; if (!view) { return true; } auto top = _list ? _list->itemTop(item) : -2; if (top < 0) { return true; } auto bottom = top + view->height(); auto scrollTop = _scroll->scrollTop(); auto scrollBottom = scrollTop + _scroll->height(); return (top >= scrollBottom || bottom <= scrollTop); } void HistoryWidget::visibleAreaUpdated() { if (_list && !_scroll->isHidden()) { auto scrollTop = _scroll->scrollTop(); auto scrollBottom = scrollTop + _scroll->height(); _list->visibleAreaUpdated(scrollTop, scrollBottom); if (_history->loadedAtBottom() && (_history->unreadCount() > 0 || (_migrated && _migrated->unreadCount() > 0))) { const auto unread = firstUnreadMessage(); const auto unreadVisible = unread && (scrollBottom > _list->itemTop(unread)); const auto atBottom = (scrollTop >= _scroll->scrollTopMax()); if ((unreadVisible || atBottom) && App::wnd()->doWeReadServerHistory()) { Auth().api().readServerHistory(_history); } } controller()->floatPlayerAreaUpdated().notify(true); } } void HistoryWidget::preloadHistoryIfNeeded() { if (_firstLoadRequest || _scroll->isHidden() || !_peer) { return; } updateHistoryDownVisibility(); if (!_scrollToAnimation.animating()) { preloadHistoryByScroll(); checkReplyReturns(); } auto scrollTop = _scroll->scrollTop(); if (scrollTop != _lastScrollTop) { _lastScrolled = getms(); _lastScrollTop = scrollTop; } } void HistoryWidget::preloadHistoryByScroll() { if (_firstLoadRequest || _scroll->isHidden() || !_peer) { return; } auto scrollTop = _scroll->scrollTop(); auto scrollTopMax = _scroll->scrollTopMax(); auto scrollHeight = _scroll->height(); if (scrollTop + kPreloadHeightsCount * scrollHeight >= scrollTopMax) { loadMessagesDown(); } if (scrollTop <= kPreloadHeightsCount * scrollHeight) { loadMessages(); } } void HistoryWidget::checkReplyReturns() { if (_firstLoadRequest || _scroll->isHidden() || !_peer) { return; } auto scrollTop = _scroll->scrollTop(); auto scrollTopMax = _scroll->scrollTopMax(); auto scrollHeight = _scroll->height(); while (_replyReturn) { auto below = (!_replyReturn->mainView() && _replyReturn->history() == _history && !_history->isEmpty() && _replyReturn->id < _history->blocks.back()->messages.back()->data()->id); if (!below) { below = (!_replyReturn->mainView() && _replyReturn->history() == _migrated && !_history->isEmpty()); } if (!below) { below = (!_replyReturn->mainView() && _migrated && _replyReturn->history() == _migrated && !_migrated->isEmpty() && _replyReturn->id < _migrated->blocks.back()->messages.back()->data()->id); } if (!below && _replyReturn->mainView()) { below = (scrollTop >= scrollTopMax) || (_list->itemTop(_replyReturn) < scrollTop + scrollHeight / 2); } if (below) { calcNextReplyReturn(); } else { break; } } } void HistoryWidget::onInlineBotCancel() { auto &textWithTags = _field->getTextWithTags(); if (textWithTags.text.size() > _inlineBotUsername.size() + 2) { setFieldText({ '@' + _inlineBotUsername + ' ', TextWithTags::Tags() }, TextUpdateEvent::SaveDraft, Ui::FlatTextarea::AddToUndoHistory); } else { clearFieldText(TextUpdateEvent::SaveDraft, Ui::FlatTextarea::AddToUndoHistory); } } void HistoryWidget::onWindowVisibleChanged() { QTimer::singleShot(0, this, SLOT(preloadHistoryIfNeeded())); } void HistoryWidget::historyDownClicked() { if (_replyReturn && _replyReturn->history() == _history) { showHistory(_peer->id, _replyReturn->id); } else if (_replyReturn && _replyReturn->history() == _migrated) { showHistory(_peer->id, -_replyReturn->id); } else if (_peer) { showHistory(_peer->id, ShowAtUnreadMsgId); } } void HistoryWidget::showNextUnreadMention() { showHistory(_peer->id, _history->getMinLoadedUnreadMention()); } void HistoryWidget::saveEditMsg() { if (_saveEditMsgRequestId) return; WebPageId webPageId = _previewCancelled ? CancelledWebPageId : ((_previewData && _previewData->pendingTill >= 0) ? _previewData->id : 0); auto &textWithTags = _field->getTextWithTags(); auto prepareFlags = Ui::ItemTextOptions(_history, App::self()).flags; auto sending = TextWithEntities(); auto left = TextWithEntities { textWithTags.text, ConvertTextTagsToEntities(textWithTags.tags) }; TextUtilities::PrepareForSending(left, prepareFlags); if (!TextUtilities::CutPart(sending, left, MaxMessageSize)) { _field->selectAll(); _field->setFocus(); return; } else if (!left.text.isEmpty()) { Ui::show(Box<InformBox>(lang(lng_edit_too_long))); return; } auto sendFlags = MTPmessages_EditMessage::Flag::f_message | 0; if (webPageId == CancelledWebPageId) { sendFlags |= MTPmessages_EditMessage::Flag::f_no_webpage; } auto localEntities = TextUtilities::EntitiesToMTP(sending.entities); auto sentEntities = TextUtilities::EntitiesToMTP(sending.entities, TextUtilities::ConvertOption::SkipLocal); if (!sentEntities.v.isEmpty()) { sendFlags |= MTPmessages_EditMessage::Flag::f_entities; } _saveEditMsgRequestId = MTP::send( MTPmessages_EditMessage( MTP_flags(sendFlags), _history->peer->input, MTP_int(_editMsgId), MTP_string(sending.text), MTPnullMarkup, sentEntities, MTP_inputGeoPointEmpty()), rpcDone(&HistoryWidget::saveEditMsgDone, _history), rpcFail(&HistoryWidget::saveEditMsgFail, _history)); } void HistoryWidget::saveEditMsgDone(History *history, const MTPUpdates &updates, mtpRequestId req) { if (App::main()) { App::main()->sentUpdatesReceived(updates); } if (req == _saveEditMsgRequestId) { _saveEditMsgRequestId = 0; cancelEdit(); } if (auto editDraft = history->editDraft()) { if (editDraft->saveRequestId == req) { history->clearEditDraft(); if (App::main()) App::main()->writeDrafts(history); } } } bool HistoryWidget::saveEditMsgFail(History *history, const RPCError &error, mtpRequestId req) { if (MTP::isDefaultHandledError(error)) return false; if (req == _saveEditMsgRequestId) { _saveEditMsgRequestId = 0; } if (auto editDraft = history->editDraft()) { if (editDraft->saveRequestId == req) { editDraft->saveRequestId = 0; } } QString err = error.type(); if (err == qstr("MESSAGE_ID_INVALID") || err == qstr("CHAT_ADMIN_REQUIRED") || err == qstr("MESSAGE_EDIT_TIME_EXPIRED")) { Ui::show(Box<InformBox>(lang(lng_edit_error))); } else if (err == qstr("MESSAGE_NOT_MODIFIED")) { cancelEdit(); } else if (err == qstr("MESSAGE_EMPTY")) { _field->selectAll(); _field->setFocus(); } else { Ui::show(Box<InformBox>(lang(lng_edit_error))); } update(); return true; } void HistoryWidget::hideSelectorControlsAnimated() { _fieldAutocomplete->hideAnimated(); if (_tabbedPanel) { _tabbedPanel->hideAnimated(); } if (_inlineResults) { _inlineResults->hideAnimated(); } } void HistoryWidget::onSend(bool ctrlShiftEnter) { if (!_history) return; if (_editMsgId) { saveEditMsg(); return; } WebPageId webPageId = _previewCancelled ? CancelledWebPageId : ((_previewData && _previewData->pendingTill >= 0) ? _previewData->id : 0); auto message = MainWidget::MessageToSend(_history); message.textWithTags = _field->getTextWithTags(); message.replyTo = replyToId(); message.webPageId = webPageId; App::main()->sendMessage(message); clearFieldText(); _saveDraftText = true; _saveDraftStart = getms(); onDraftSave(); hideSelectorControlsAnimated(); if (_previewData && _previewData->pendingTill) previewCancel(); _field->setFocus(); if (!_keyboard->hasMarkup() && _keyboard->forceReply() && !_kbReplyTo) { onKbToggle(); } } void HistoryWidget::onUnblock() { if (_unblockRequest) return; if (!_peer || !_peer->isUser() || !_peer->asUser()->isBlocked()) { updateControlsVisibility(); return; } _unblockRequest = MTP::send(MTPcontacts_Unblock(_peer->asUser()->inputUser), rpcDone(&HistoryWidget::unblockDone, _peer), rpcFail(&HistoryWidget::unblockFail)); } void HistoryWidget::unblockDone(PeerData *peer, const MTPBool &result, mtpRequestId req) { if (!peer->isUser()) return; if (_unblockRequest == req) _unblockRequest = 0; peer->asUser()->setBlockStatus(UserData::BlockStatus::NotBlocked); } bool HistoryWidget::unblockFail(const RPCError &error, mtpRequestId req) { if (MTP::isDefaultHandledError(error)) return false; if (_unblockRequest == req) _unblockRequest = 0; return false; } void HistoryWidget::blockDone(PeerData *peer, const MTPBool &result) { if (!peer->isUser()) return; peer->asUser()->setBlockStatus(UserData::BlockStatus::Blocked); } void HistoryWidget::onBotStart() { if (!_peer || !_peer->isUser() || !_peer->asUser()->botInfo || !_canSendMessages) { updateControlsVisibility(); return; } QString token = _peer->asUser()->botInfo->startToken; if (token.isEmpty()) { sendBotCommand(_peer, _peer->asUser(), qsl("/start"), 0); } else { uint64 randomId = rand_value<uint64>(); MTP::send(MTPmessages_StartBot(_peer->asUser()->inputUser, MTP_inputPeerEmpty(), MTP_long(randomId), MTP_string(token)), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::addParticipantFail, { _peer->asUser(), (PeerData*)nullptr })); _peer->asUser()->botInfo->startToken = QString(); if (_keyboard->hasMarkup()) { if (_keyboard->singleUse() && _keyboard->forMsgId() == FullMsgId(_channel, _history->lastKeyboardId) && _history->lastKeyboardUsed) { _history->lastKeyboardHiddenId = _history->lastKeyboardId; } if (!kbWasHidden()) _kbShown = _keyboard->hasMarkup(); } } updateControlsVisibility(); updateControlsGeometry(); } void HistoryWidget::onJoinChannel() { if (!_peer || !_peer->isChannel() || !isJoinChannel()) { updateControlsVisibility(); return; } Auth().api().joinChannel(_peer->asChannel()); } void HistoryWidget::onMuteUnmute() { const auto muteState = _history->mute() ? Data::NotifySettings::MuteChange::Unmute : Data::NotifySettings::MuteChange::Mute; App::main()->updateNotifySettings(_peer, muteState); } void HistoryWidget::onBroadcastSilentChange() { updateFieldPlaceholder(); } History *HistoryWidget::history() const { return _history; } PeerData *HistoryWidget::peer() const { return _peer; } // Sometimes _showAtMsgId is set directly. void HistoryWidget::setMsgId(MsgId showAtMsgId) { if (_showAtMsgId != showAtMsgId) { auto wasMsgId = _showAtMsgId; _showAtMsgId = showAtMsgId; if (_history) { controller()->setActiveChatEntry({ _history, FullMsgId(_history->channelId(), _showAtMsgId) }); } } } MsgId HistoryWidget::msgId() const { return _showAtMsgId; } void HistoryWidget::showAnimated( Window::SlideDirection direction, const Window::SectionSlideParams ¶ms) { _showDirection = direction; _a_show.finish(); _cacheUnder = params.oldContentCache; show(); _topBar->finishAnimating(); historyDownAnimationFinish(); unreadMentionsAnimationFinish(); _topShadow->setVisible(params.withTopBarShadow ? false : true); _cacheOver = App::main()->grabForShowAnimation(params); hideChildren(); if (params.withTopBarShadow) _topShadow->show(); if (_showDirection == Window::SlideDirection::FromLeft) { std::swap(_cacheUnder, _cacheOver); } _a_show.start([this] { animationCallback(); }, 0., 1., st::slideDuration, Window::SlideAnimation::transition()); if (_history) { _topBar->show(); _topBar->setAnimatingMode(true); } activate(); } void HistoryWidget::animationCallback() { update(); if (!_a_show.animating()) { historyDownAnimationFinish(); unreadMentionsAnimationFinish(); _cacheUnder = _cacheOver = QPixmap(); doneShow(); } } void HistoryWidget::doneShow() { _topBar->setAnimatingMode(false); updateReportSpamStatus(); updateBotKeyboard(); updateControlsVisibility(); if (!_historyInited) { updateHistoryGeometry(true); } else if (hasPendingResizedItems()) { updateHistoryGeometry(); } preloadHistoryIfNeeded(); if (App::wnd()) { App::wnd()->checkHistoryActivation(); App::wnd()->setInnerFocus(); } } void HistoryWidget::finishAnimating() { if (!_a_show.animating()) return; _a_show.finish(); _topShadow->setVisible(_peer != nullptr); _topBar->setVisible(_peer != nullptr); historyDownAnimationFinish(); unreadMentionsAnimationFinish(); } void HistoryWidget::historyDownAnimationFinish() { _historyDownShown.finish(); updateHistoryDownPosition(); } void HistoryWidget::unreadMentionsAnimationFinish() { _unreadMentionsShown.finish(); updateUnreadMentionsPosition(); } void HistoryWidget::step_recording(float64 ms, bool timer) { float64 dt = ms / AudioVoiceMsgUpdateView; if (dt >= 1) { _a_recording.stop(); a_recordingLevel.finish(); } else { a_recordingLevel.update(dt, anim::linear); } if (timer) update(_attachToggle->geometry()); } void HistoryWidget::chooseAttach() { if (!_peer || !_peer->canWrite()) return; if (auto megagroup = _peer->asMegagroup()) { if (megagroup->restricted(ChannelRestriction::f_send_media)) { Ui::show(Box<InformBox>(lang(lng_restricted_send_media))); return; } } auto filter = FileDialog::AllFilesFilter() + qsl(";;Image files (*") + cImgExtensions().join(qsl(" *")) + qsl(")"); FileDialog::GetOpenPaths(lang(lng_choose_files), filter, base::lambda_guarded(this, [this](FileDialog::OpenResult &&result) { if (result.paths.isEmpty() && result.remoteContent.isEmpty()) { return; } if (!result.remoteContent.isEmpty()) { auto animated = false; auto image = App::readImage( result.remoteContent, nullptr, false, &animated); if (!image.isNull() && !animated) { confirmSendingFiles( std::move(image), std::move(result.remoteContent), CompressConfirm::Auto); } else { uploadFile(result.remoteContent, SendMediaType::File); } } else { auto list = Storage::PrepareMediaList( result.paths, st::sendMediaPreviewSize); if (list.allFilesForCompress || list.albumIsPossible) { confirmSendingFiles(std::move(list), CompressConfirm::Auto); } else if (!showSendingFilesError(list)) { uploadFiles(std::move(list), SendMediaType::File); } } })); } void HistoryWidget::sendButtonClicked() { auto type = _send->type(); if (type == Ui::SendButton::Type::Cancel) { onInlineBotCancel(); } else if (type != Ui::SendButton::Type::Record) { onSend(); } } void HistoryWidget::dragEnterEvent(QDragEnterEvent *e) { if (!_history || !_canSendMessages) return; _attachDragState = Storage::ComputeMimeDataState(e->mimeData()); updateDragAreas(); if (_attachDragState != DragState::None) { e->setDropAction(Qt::IgnoreAction); e->accept(); } } void HistoryWidget::dragLeaveEvent(QDragLeaveEvent *e) { if (_attachDragState != DragState::None || !_attachDragPhoto->isHidden() || !_attachDragDocument->isHidden()) { _attachDragState = DragState::None; updateDragAreas(); } } void HistoryWidget::leaveEventHook(QEvent *e) { if (_attachDragState != DragState::None || !_attachDragPhoto->isHidden() || !_attachDragDocument->isHidden()) { _attachDragState = DragState::None; updateDragAreas(); } if (hasMouseTracking()) mouseMoveEvent(0); } void HistoryWidget::mouseMoveEvent(QMouseEvent *e) { auto pos = e ? e->pos() : mapFromGlobal(QCursor::pos()); updateOverStates(pos); } void HistoryWidget::updateOverStates(QPoint pos) { auto inField = pos.y() >= (_scroll->y() + _scroll->height()) && pos.y() < height() && pos.x() >= 0 && pos.x() < width(); auto inReplyEditForward = QRect(st::historyReplySkip, _field->y() - st::historySendPadding - st::historyReplyHeight, width() - st::historyReplySkip - _fieldBarCancel->width(), st::historyReplyHeight).contains(pos) && (_editMsgId || replyToId() || readyToForward()); auto inPinnedMsg = QRect(0, _topBar->bottomNoMargins(), width(), st::historyReplyHeight).contains(pos) && _pinnedBar; auto inClickable = inReplyEditForward || inPinnedMsg; if (inField != _inField && _recording) { _inField = inField; _send->setRecordActive(_inField); } _inReplyEditForward = inReplyEditForward; _inPinnedMsg = inPinnedMsg; if (inClickable != _inClickable) { _inClickable = inClickable; setCursor(_inClickable ? style::cur_pointer : style::cur_default); } } void HistoryWidget::leaveToChildEvent(QEvent *e, QWidget *child) { // e -- from enterEvent() of child TWidget if (hasMouseTracking()) { updateOverStates(mapFromGlobal(QCursor::pos())); } } void HistoryWidget::recordStartCallback() { if (!Media::Capture::instance()->available()) { return; } if (auto megagroup = _peer ? _peer->asMegagroup() : nullptr) { if (megagroup->restricted(ChannelRestriction::f_send_media)) { Ui::show(Box<InformBox>(lang(lng_restricted_send_media))); return; } } emit Media::Capture::instance()->start(); _recording = _inField = true; updateControlsVisibility(); activate(); updateField(); _send->setRecordActive(true); } void HistoryWidget::recordStopCallback(bool active) { stopRecording(_peer && active); } void HistoryWidget::recordUpdateCallback(QPoint globalPos) { updateOverStates(mapFromGlobal(globalPos)); } void HistoryWidget::mouseReleaseEvent(QMouseEvent *e) { if (_replyForwardPressed) { _replyForwardPressed = false; update(0, _field->y() - st::historySendPadding - st::historyReplyHeight, width(), st::historyReplyHeight); } if (_attachDragState != DragState::None || !_attachDragPhoto->isHidden() || !_attachDragDocument->isHidden()) { _attachDragState = DragState::None; updateDragAreas(); } if (_recording) { stopRecording(_peer && _inField); } } void HistoryWidget::stopRecording(bool send) { emit Media::Capture::instance()->stop(send); a_recordingLevel = anim::value(); _a_recording.stop(); _recording = false; _recordingSamples = 0; if (_history) { updateSendAction(_history, SendAction::Type::RecordVoice, -1); } updateControlsVisibility(); activate(); updateField(); _send->setRecordActive(false); } void HistoryWidget::sendBotCommand(PeerData *peer, UserData *bot, const QString &cmd, MsgId replyTo) { // replyTo != 0 from ReplyKeyboardMarkup, == 0 from cmd links if (!_peer || _peer != peer) return; bool lastKeyboardUsed = (_keyboard->forMsgId() == FullMsgId(_channel, _history->lastKeyboardId)) && (_keyboard->forMsgId() == FullMsgId(_channel, replyTo)); QString toSend = cmd; if (bot && (!bot->isUser() || !bot->asUser()->botInfo)) { bot = nullptr; } QString username = bot ? bot->asUser()->username : QString(); int32 botStatus = _peer->isChat() ? _peer->asChat()->botStatus : (_peer->isMegagroup() ? _peer->asChannel()->mgInfo->botStatus : -1); if (!replyTo && toSend.indexOf('@') < 2 && !username.isEmpty() && (botStatus == 0 || botStatus == 2)) { toSend += '@' + username; } auto message = MainWidget::MessageToSend(_history); message.textWithTags = { toSend, TextWithTags::Tags() }; message.replyTo = replyTo ? ((!_peer->isUser()/* && (botStatus == 0 || botStatus == 2)*/) ? replyTo : replyToId()) : 0; App::main()->sendMessage(message); if (replyTo) { if (_replyToId == replyTo) { cancelReply(); onCloudDraftSave(); } if (_keyboard->singleUse() && _keyboard->hasMarkup() && lastKeyboardUsed) { if (_kbShown) onKbToggle(false); _history->lastKeyboardUsed = true; } } _field->setFocus(); } void HistoryWidget::hideSingleUseKeyboard(PeerData *peer, MsgId replyTo) { if (!_peer || _peer != peer) return; bool lastKeyboardUsed = (_keyboard->forMsgId() == FullMsgId(_channel, _history->lastKeyboardId)) && (_keyboard->forMsgId() == FullMsgId(_channel, replyTo)); if (replyTo) { if (_replyToId == replyTo) { cancelReply(); onCloudDraftSave(); } if (_keyboard->singleUse() && _keyboard->hasMarkup() && lastKeyboardUsed) { if (_kbShown) onKbToggle(false); _history->lastKeyboardUsed = true; } } } void HistoryWidget::app_sendBotCallback( not_null<const HistoryMessageMarkupButton*> button, not_null<const HistoryItem*> msg, int row, int column) { if (msg->id < 0 || _peer != msg->history()->peer) { return; } bool lastKeyboardUsed = (_keyboard->forMsgId() == FullMsgId(_channel, _history->lastKeyboardId)) && (_keyboard->forMsgId() == FullMsgId(_channel, msg->id)); auto bot = msg->getMessageBot(); using ButtonType = HistoryMessageMarkupButton::Type; BotCallbackInfo info = { bot, msg->fullId(), row, column, (button->type == ButtonType::Game) }; auto flags = MTPmessages_GetBotCallbackAnswer::Flags(0); QByteArray sendData; if (info.game) { flags |= MTPmessages_GetBotCallbackAnswer::Flag::f_game; } else if (button->type == ButtonType::Callback) { flags |= MTPmessages_GetBotCallbackAnswer::Flag::f_data; sendData = button->data; } button->requestId = MTP::send( MTPmessages_GetBotCallbackAnswer( MTP_flags(flags), _peer->input, MTP_int(msg->id), MTP_bytes(sendData)), rpcDone(&HistoryWidget::botCallbackDone, info), rpcFail(&HistoryWidget::botCallbackFail, info)); Auth().data().requestItemRepaint(msg); if (_replyToId == msg->id) { cancelReply(); } if (_keyboard->singleUse() && _keyboard->hasMarkup() && lastKeyboardUsed) { if (_kbShown) onKbToggle(false); _history->lastKeyboardUsed = true; } } void HistoryWidget::botCallbackDone( BotCallbackInfo info, const MTPmessages_BotCallbackAnswer &answer, mtpRequestId req) { auto item = App::histItemById(info.msgId); if (item) { if (const auto markup = item->Get<HistoryMessageReplyMarkup>()) { if (info.row < markup->rows.size() && info.col < markup->rows[info.row].size()) { if (markup->rows[info.row][info.col].requestId == req) { markup->rows[info.row][info.col].requestId = 0; Auth().data().requestItemRepaint(item); } } } } if (answer.type() == mtpc_messages_botCallbackAnswer) { auto &answerData = answer.c_messages_botCallbackAnswer(); if (answerData.has_message()) { if (answerData.is_alert()) { Ui::show(Box<InformBox>(qs(answerData.vmessage))); } else { Ui::Toast::Show(qs(answerData.vmessage)); } } else if (answerData.has_url()) { auto url = qs(answerData.vurl); if (info.game) { url = AppendShareGameScoreUrl(url, info.msgId); BotGameUrlClickHandler(info.bot, url).onClick(Qt::LeftButton); if (item) { updateSendAction(item->history(), SendAction::Type::PlayGame); } } else { UrlClickHandler(url).onClick(Qt::LeftButton); } } } } bool HistoryWidget::botCallbackFail( BotCallbackInfo info, const RPCError &error, mtpRequestId req) { // show error? if (const auto item = App::histItemById(info.msgId)) { if (const auto markup = item->Get<HistoryMessageReplyMarkup>()) { if (info.row < markup->rows.size() && info.col < markup->rows[info.row].size()) { if (markup->rows[info.row][info.col].requestId == req) { markup->rows[info.row][info.col].requestId = 0; Auth().data().requestItemRepaint(item); } } } } return true; } bool HistoryWidget::insertBotCommand(const QString &cmd) { if (!canWriteMessage()) return false; auto insertingInlineBot = !cmd.isEmpty() && (cmd.at(0) == '@'); auto toInsert = cmd; if (!toInsert.isEmpty() && !insertingInlineBot) { auto bot = _peer->isUser() ? _peer : (App::hoveredLinkItem() ? App::hoveredLinkItem()->data()->fromOriginal().get() : nullptr); if (bot && (!bot->isUser() || !bot->asUser()->botInfo)) { bot = nullptr; } auto username = bot ? bot->asUser()->username : QString(); auto botStatus = _peer->isChat() ? _peer->asChat()->botStatus : (_peer->isMegagroup() ? _peer->asChannel()->mgInfo->botStatus : -1); if (toInsert.indexOf('@') < 0 && !username.isEmpty() && (botStatus == 0 || botStatus == 2)) { toInsert += '@' + username; } } toInsert += ' '; if (!insertingInlineBot) { auto &textWithTags = _field->getTextWithTags(); TextWithTags textWithTagsToSet; QRegularExpressionMatch m = QRegularExpression(qsl("^/[A-Za-z_0-9]{0,64}(@[A-Za-z_0-9]{0,32})?(\\s|$)")).match(textWithTags.text); if (m.hasMatch()) { textWithTagsToSet = _field->getTextWithTagsPart(m.capturedLength()); } else { textWithTagsToSet = textWithTags; } textWithTagsToSet.text = toInsert + textWithTagsToSet.text; for (auto &tag : textWithTagsToSet.tags) { tag.offset += toInsert.size(); } _field->setTextWithTags(textWithTagsToSet); QTextCursor cur(_field->textCursor()); cur.movePosition(QTextCursor::End); _field->setTextCursor(cur); } else { setFieldText({ toInsert, TextWithTags::Tags() }, TextUpdateEvent::SaveDraft, Ui::FlatTextarea::AddToUndoHistory); _field->setFocus(); return true; } return false; } bool HistoryWidget::eventFilter(QObject *obj, QEvent *e) { if ((obj == _historyDown || obj == _unreadMentions) && e->type() == QEvent::Wheel) { return _scroll->viewportEvent(e); } return TWidget::eventFilter(obj, e); } bool HistoryWidget::wheelEventFromFloatPlayer(QEvent *e) { return _scroll->viewportEvent(e); } QRect HistoryWidget::rectForFloatPlayer() const { return mapToGlobal(_scroll->geometry()); } void HistoryWidget::updateDragAreas() { _field->setAcceptDrops(_attachDragState == DragState::None); updateControlsGeometry(); switch (_attachDragState) { case DragState::None: _attachDragDocument->otherLeave(); _attachDragPhoto->otherLeave(); break; case DragState::Files: _attachDragDocument->setText(lang(lng_drag_files_here), lang(lng_drag_to_send_files)); _attachDragDocument->otherEnter(); _attachDragPhoto->hideFast(); break; case DragState::PhotoFiles: _attachDragDocument->setText(lang(lng_drag_images_here), lang(lng_drag_to_send_no_compression)); _attachDragPhoto->setText(lang(lng_drag_photos_here), lang(lng_drag_to_send_quick)); _attachDragDocument->otherEnter(); _attachDragPhoto->otherEnter(); break; case DragState::Image: _attachDragPhoto->setText(lang(lng_drag_images_here), lang(lng_drag_to_send_quick)); _attachDragDocument->hideFast(); _attachDragPhoto->otherEnter(); break; }; } bool HistoryWidget::readyToForward() const { return _canSendMessages && !_toForward.empty(); } bool HistoryWidget::hasSilentToggle() const { return _peer && _peer->isChannel() && !_peer->isMegagroup() && _peer->canWrite() && !_peer->notifySettingsUnknown(); } void HistoryWidget::inlineBotResolveDone(const MTPcontacts_ResolvedPeer &result) { _inlineBotResolveRequestId = 0; // Notify::inlineBotRequesting(false); UserData *resolvedBot = nullptr; if (result.type() == mtpc_contacts_resolvedPeer) { const auto &d(result.c_contacts_resolvedPeer()); resolvedBot = App::feedUsers(d.vusers); if (resolvedBot) { if (!resolvedBot->botInfo || resolvedBot->botInfo->inlinePlaceholder.isEmpty()) { resolvedBot = nullptr; } } App::feedChats(d.vchats); } UserData *bot = nullptr; QString inlineBotUsername; auto query = _field->getInlineBotQuery(&bot, &inlineBotUsername); if (inlineBotUsername == _inlineBotUsername) { if (bot == Ui::LookingUpInlineBot) { bot = resolvedBot; } } else { bot = nullptr; } if (bot) { applyInlineBotQuery(bot, query); } else { clearInlineBot(); } } bool HistoryWidget::inlineBotResolveFail(QString name, const RPCError &error) { if (MTP::isDefaultHandledError(error)) return false; _inlineBotResolveRequestId = 0; // Notify::inlineBotRequesting(false); if (name == _inlineBotUsername) { clearInlineBot(); } return true; } bool HistoryWidget::isBotStart() const { const auto user = _peer ? _peer->asUser() : nullptr; if (!user || !user->botInfo || !_canSendMessages) { return false; } else if (!user->botInfo->startToken.isEmpty()) { return true; } else if (_history->isEmpty() && !_history->lastMessage()) { return true; } return false; } bool HistoryWidget::isBlocked() const { return _peer && _peer->isUser() && _peer->asUser()->isBlocked(); } bool HistoryWidget::isJoinChannel() const { return _peer && _peer->isChannel() && !_peer->asChannel()->amIn(); } bool HistoryWidget::isMuteUnmute() const { return _peer && _peer->isChannel() && _peer->asChannel()->isBroadcast() && !_peer->asChannel()->canPublish(); } bool HistoryWidget::showRecordButton() const { return Media::Capture::instance()->available() && !_field->hasSendText() && !readyToForward() && !_editMsgId; } bool HistoryWidget::showInlineBotCancel() const { return _inlineBot && (_inlineBot != Ui::LookingUpInlineBot); } void HistoryWidget::updateSendButtonType() { auto type = [this] { using Type = Ui::SendButton::Type; if (_editMsgId) { return Type::Save; } else if (_isInlineBot) { return Type::Cancel; } else if (showRecordButton()) { return Type::Record; } return Type::Send; }; _send->setType(type()); } bool HistoryWidget::updateCmdStartShown() { bool cmdStartShown = false; if (_history && _peer && ((_peer->isChat() && _peer->asChat()->botStatus > 0) || (_peer->isMegagroup() && _peer->asChannel()->mgInfo->botStatus > 0) || (_peer->isUser() && _peer->asUser()->botInfo))) { if (!isBotStart() && !isBlocked() && !_keyboard->hasMarkup() && !_keyboard->forceReply()) { if (!_field->hasSendText()) { cmdStartShown = true; } } } if (_cmdStartShown != cmdStartShown) { _cmdStartShown = cmdStartShown; return true; } return false; } bool HistoryWidget::kbWasHidden() const { return _history && (_keyboard->forMsgId() == FullMsgId(_history->channelId(), _history->lastKeyboardHiddenId)); } void HistoryWidget::dropEvent(QDropEvent *e) { _attachDragState = DragState::None; updateDragAreas(); e->acceptProposedAction(); } void HistoryWidget::onKbToggle(bool manual) { auto fieldEnabled = canWriteMessage() && !_a_show.animating(); if (_kbShown || _kbReplyTo) { _botKeyboardHide->hide(); if (_kbShown) { if (fieldEnabled) { _botKeyboardShow->show(); } if (manual && _history) { _history->lastKeyboardHiddenId = _keyboard->forMsgId().msg; } _kbScroll->hide(); _kbShown = false; _field->setMaxHeight(st::historyComposeFieldMaxHeight); _kbReplyTo = 0; if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !_editMsgId && !_replyToId) { _fieldBarCancel->hide(); updateMouseTracking(); } } else { if (_history) { _history->clearLastKeyboard(); } else { updateBotKeyboard(); } } } else if (!_keyboard->hasMarkup() && _keyboard->forceReply()) { _botKeyboardHide->hide(); _botKeyboardShow->hide(); if (fieldEnabled) { _botCommandStart->show(); } _kbScroll->hide(); _kbShown = false; _field->setMaxHeight(st::historyComposeFieldMaxHeight); _kbReplyTo = (_peer->isChat() || _peer->isChannel() || _keyboard->forceReply()) ? App::histItemById(_keyboard->forMsgId()) : 0; if (_kbReplyTo && !_editMsgId && !_replyToId && fieldEnabled) { updateReplyToName(); _replyEditMsgText.setText( st::messageTextStyle, TextUtilities::Clean(_kbReplyTo->inReplyText()), Ui::DialogTextOptions()); _fieldBarCancel->show(); updateMouseTracking(); } if (manual && _history) { _history->lastKeyboardHiddenId = 0; } } else if (fieldEnabled) { _botKeyboardHide->show(); _botKeyboardShow->hide(); _kbScroll->show(); _kbShown = true; int32 maxh = qMin(_keyboard->height(), st::historyComposeFieldMaxHeight - (st::historyComposeFieldMaxHeight / 2)); _field->setMaxHeight(st::historyComposeFieldMaxHeight - maxh); _kbReplyTo = (_peer->isChat() || _peer->isChannel() || _keyboard->forceReply()) ? App::histItemById(_keyboard->forMsgId()) : 0; if (_kbReplyTo && !_editMsgId && !_replyToId) { updateReplyToName(); _replyEditMsgText.setText( st::messageTextStyle, TextUtilities::Clean(_kbReplyTo->inReplyText()), Ui::DialogTextOptions()); _fieldBarCancel->show(); updateMouseTracking(); } if (manual && _history) { _history->lastKeyboardHiddenId = 0; } } updateControlsGeometry(); if (_botKeyboardHide->isHidden() && canWriteMessage() && !_a_show.animating()) { _tabbedSelectorToggle->show(); } else { _tabbedSelectorToggle->hide(); } updateField(); } void HistoryWidget::onCmdStart() { setFieldText({ qsl("/"), TextWithTags::Tags() }, 0, Ui::FlatTextarea::AddToUndoHistory); } void HistoryWidget::setMembersShowAreaActive(bool active) { if (!active) { _membersDropdownShowTimer.stop(); } if (active && _peer && (_peer->isChat() || _peer->isMegagroup())) { if (_membersDropdown) { _membersDropdown->otherEnter(); } else if (!_membersDropdownShowTimer.isActive()) { _membersDropdownShowTimer.start(kShowMembersDropdownTimeoutMs); } } else if (_membersDropdown) { _membersDropdown->otherLeave(); } } void HistoryWidget::onMembersDropdownShow() { if (!_membersDropdown) { _membersDropdown.create(this, st::membersInnerDropdown); _membersDropdown->setOwnedWidget(object_ptr<Profile::GroupMembersWidget>(this, _peer, Profile::GroupMembersWidget::TitleVisibility::Hidden, st::membersInnerItem)); _membersDropdown->resizeToWidth(st::membersInnerWidth); _membersDropdown->setMaxHeight(countMembersDropdownHeightMax()); _membersDropdown->moveToLeft(0, _topBar->height()); _membersDropdown->setHiddenCallback([this] { _membersDropdown.destroyDelayed(); }); } _membersDropdown->otherEnter(); } void HistoryWidget::onModerateKeyActivate(int index, bool *outHandled) { *outHandled = _keyboard->isHidden() ? false : _keyboard->moderateKeyActivate(index); } void HistoryWidget::pushTabbedSelectorToThirdSection( const Window::SectionShow ¶ms) { if (!_history || !_tabbedPanel) { return; } else if (!_canSendMessages) { Auth().settings().setTabbedReplacedWithInfo(true); controller()->showPeerInfo(_peer, params.withThirdColumn()); return; } Auth().settings().setTabbedReplacedWithInfo(false); _tabbedSelectorToggle->setColorOverrides( &st::historyAttachEmojiActive, &st::historyRecordVoiceFgActive, &st::historyRecordVoiceRippleBgActive); auto destroyingPanel = std::move(_tabbedPanel); auto memento = ChatHelpers::TabbedMemento( destroyingPanel->takeSelector(), base::lambda_guarded(this, [this]( object_ptr<TabbedSelector> selector) { returnTabbedSelector(std::move(selector)); })); controller()->resizeForThirdSection(); controller()->showSection(std::move(memento), params.withThirdColumn()); destroyingPanel.destroy(); } void HistoryWidget::toggleTabbedSelectorMode() { if (_tabbedPanel) { if (controller()->canShowThirdSection() && !Adaptive::OneColumn()) { Auth().settings().setTabbedSelectorSectionEnabled(true); Auth().saveSettingsDelayed(); pushTabbedSelectorToThirdSection( Window::SectionShow::Way::ClearStack); } else { _tabbedPanel->toggleAnimated(); } } else { controller()->closeThirdSection(); } } void HistoryWidget::returnTabbedSelector( object_ptr<TabbedSelector> selector) { _tabbedPanel.create( this, controller(), std::move(selector)); _tabbedPanel->hide(); _tabbedSelectorToggle->installEventFilter(_tabbedPanel); _tabbedSelectorToggle->setColorOverrides(nullptr, nullptr, nullptr); _tabbedSelectorToggleTooltipShown = false; moveFieldControls(); } void HistoryWidget::recountChatWidth() { auto layout = (width() < st::adaptiveChatWideWidth) ? Adaptive::ChatLayout::Normal : Adaptive::ChatLayout::Wide; if (layout != Global::AdaptiveChatLayout()) { Global::SetAdaptiveChatLayout(layout); Adaptive::Changed().notify(true); } } void HistoryWidget::moveFieldControls() { auto keyboardHeight = 0; auto bottom = height(); auto maxKeyboardHeight = st::historyComposeFieldMaxHeight - _field->height(); _keyboard->resizeToWidth(width(), maxKeyboardHeight); if (_kbShown) { keyboardHeight = qMin(_keyboard->height(), maxKeyboardHeight); bottom -= keyboardHeight; _kbScroll->setGeometryToLeft(0, bottom, width(), keyboardHeight); } // _attachToggle --------- _inlineResults -------------------------------------- _tabbedPanel --------- _fieldBarCancel // (_attachDocument|_attachPhoto) _field (_silent|_cmdStart|_kbShow) (_kbHide|_tabbedSelectorToggle) [_broadcast] _send // (_botStart|_unblock|_joinChannel|_muteUnmute) auto buttonsBottom = bottom - _attachToggle->height(); auto left = 0; _attachToggle->moveToLeft(left, buttonsBottom); left += _attachToggle->width(); _field->moveToLeft(left, bottom - _field->height() - st::historySendPadding); auto right = st::historySendRight; _send->moveToRight(right, buttonsBottom); right += _send->width(); _tabbedSelectorToggle->moveToRight(right, buttonsBottom); updateTabbedSelectorToggleTooltipGeometry(); _botKeyboardHide->moveToRight(right, buttonsBottom); right += _botKeyboardHide->width(); _botKeyboardShow->moveToRight(right, buttonsBottom); _botCommandStart->moveToRight(right, buttonsBottom); if (_silent) { _silent->moveToRight(right, buttonsBottom); } _fieldBarCancel->moveToRight(0, _field->y() - st::historySendPadding - _fieldBarCancel->height()); if (_inlineResults) { _inlineResults->moveBottom(_field->y() - st::historySendPadding); } if (_tabbedPanel) { _tabbedPanel->moveBottom(buttonsBottom); } auto fullWidthButtonRect = myrtlrect( 0, bottom - _botStart->height(), width(), _botStart->height()); _botStart->setGeometry(fullWidthButtonRect); _unblock->setGeometry(fullWidthButtonRect); _joinChannel->setGeometry(fullWidthButtonRect); _muteUnmute->setGeometry(fullWidthButtonRect); } void HistoryWidget::updateTabbedSelectorToggleTooltipGeometry() { if (_tabbedSelectorToggleTooltip) { auto toggle = _tabbedSelectorToggle->geometry(); auto margin = st::historyAttachEmojiTooltipDelta; auto margins = QMargins(margin, margin, margin, margin); _tabbedSelectorToggleTooltip->pointAt(toggle.marginsRemoved(margins)); } } void HistoryWidget::updateFieldSize() { auto kbShowShown = _history && !_kbShown && _keyboard->hasMarkup(); auto fieldWidth = width() - _attachToggle->width() - st::historySendRight; fieldWidth -= _send->width(); fieldWidth -= _tabbedSelectorToggle->width(); if (kbShowShown) fieldWidth -= _botKeyboardShow->width(); if (_cmdStartShown) fieldWidth -= _botCommandStart->width(); if (_silent) fieldWidth -= _silent->width(); if (_field->width() != fieldWidth) { _field->resize(fieldWidth, _field->height()); } else { moveFieldControls(); } } void HistoryWidget::clearInlineBot() { if (_inlineBot) { _inlineBot = nullptr; inlineBotChanged(); _field->finishPlaceholder(); } if (_inlineResults) { _inlineResults->clearInlineBot(); } onCheckFieldAutocomplete(); } void HistoryWidget::inlineBotChanged() { bool isInlineBot = showInlineBotCancel(); if (_isInlineBot != isInlineBot) { _isInlineBot = isInlineBot; updateFieldPlaceholder(); updateFieldSubmitSettings(); updateControlsVisibility(); } } void HistoryWidget::onFieldResize() { moveFieldControls(); updateHistoryGeometry(); updateField(); } void HistoryWidget::onFieldFocused() { if (_list) { _list->clearSelected(true); } } void HistoryWidget::onCheckFieldAutocomplete() { if (!_history || _a_show.animating()) return; auto start = false; auto isInlineBot = _inlineBot && (_inlineBot != Ui::LookingUpInlineBot); auto query = isInlineBot ? QString() : _field->getMentionHashtagBotCommandPart(start); if (!query.isEmpty()) { if (query.at(0) == '#' && cRecentWriteHashtags().isEmpty() && cRecentSearchHashtags().isEmpty()) Local::readRecentHashtagsAndBots(); if (query.at(0) == '@' && cRecentInlineBots().isEmpty()) Local::readRecentHashtagsAndBots(); if (query.at(0) == '/' && _peer->isUser() && !_peer->asUser()->botInfo) return; } _fieldAutocomplete->showFiltered(_peer, query, start); } void HistoryWidget::updateFieldPlaceholder() { if (_editMsgId) { _field->setPlaceholder(langFactory(lng_edit_message_text)); } else { if (_inlineBot && _inlineBot != Ui::LookingUpInlineBot) { auto text = _inlineBot->botInfo->inlinePlaceholder.mid(1); _field->setPlaceholder([text] { return text; }, _inlineBot->username.size() + 2); } else { const auto peer = _history ? _history->peer.get() : nullptr; _field->setPlaceholder(langFactory( (peer && peer->isChannel() && !peer->isMegagroup()) ? (peer->notifySilentPosts() ? lng_broadcast_silent_ph : lng_broadcast_ph) : lng_message_ph)); } } updateSendButtonType(); } bool HistoryWidget::showSendingFilesError( const Storage::PreparedList &list) const { const auto text = [&] { if (const auto megagroup = _peer ? _peer->asMegagroup() : nullptr) { if (megagroup->restricted(ChannelRestriction::f_send_media)) { return lang(lng_restricted_send_media); } } if (!canWriteMessage()) { return lang(lng_forward_send_files_cant); } using Error = Storage::PreparedList::Error; switch (list.error) { case Error::None: return QString(); case Error::EmptyFile: case Error::Directory: case Error::NonLocalUrl: return lng_send_image_empty( lt_name, list.errorData); case Error::TooLargeFile: return lng_send_image_too_large( lt_name, list.errorData); } return lang(lng_forward_send_files_cant); }(); if (text.isEmpty()) { return false; } Ui::show(Box<InformBox>(text)); return true; } bool HistoryWidget::confirmSendingFiles(const QStringList &files) { return confirmSendingFiles(files, CompressConfirm::Auto); } bool HistoryWidget::confirmSendingFiles(const QMimeData *data) { return confirmSendingFiles(data, CompressConfirm::Auto); } bool HistoryWidget::confirmSendingFiles( const QStringList &files, CompressConfirm compressed, const QString &insertTextOnCancel) { return confirmSendingFiles( Storage::PrepareMediaList(files, st::sendMediaPreviewSize), compressed, insertTextOnCancel); } bool HistoryWidget::confirmSendingFiles( Storage::PreparedList &&list, CompressConfirm compressed, const QString &insertTextOnCancel) { if (showSendingFilesError(list)) { return false; } const auto noCompressOption = (list.files.size() > 1) && !list.allFilesForCompress && !list.albumIsPossible; const auto boxCompressConfirm = noCompressOption ? CompressConfirm::None : compressed; auto box = Box<SendFilesBox>(std::move(list), boxCompressConfirm); box->setConfirmedCallback(base::lambda_guarded(this, [=]( Storage::PreparedList &&list, SendFilesWay way, const QString &caption, bool ctrlShiftEnter) { if (showSendingFilesError(list)) { return; } const auto type = (way == SendFilesWay::Files) ? SendMediaType::File : SendMediaType::Photo; const auto album = (way == SendFilesWay::Album) ? std::make_shared<SendingAlbum>() : nullptr; uploadFilesAfterConfirmation( std::move(list), type, caption, replyToId(), album); })); if (!insertTextOnCancel.isEmpty()) { box->setCancelledCallback(base::lambda_guarded(this, [=] { _field->textCursor().insertText(insertTextOnCancel); })); } ActivateWindowDelayed(controller()); Ui::show(std::move(box)); return true; } bool HistoryWidget::confirmSendingFiles( QImage &&image, QByteArray &&content, CompressConfirm compressed, const QString &insertTextOnCancel) { if (image.isNull()) { return false; } auto list = Storage::PrepareMediaFromImage( std::move(image), std::move(content), st::sendMediaPreviewSize); return confirmSendingFiles( std::move(list), compressed, insertTextOnCancel); } bool HistoryWidget::confirmSendingFiles( const QMimeData *data, CompressConfirm compressed, const QString &insertTextOnCancel) { if (!canWriteMessage()) { return false; } const auto hasImage = data->hasImage(); if (const auto urls = data->urls(); !urls.empty()) { auto list = Storage::PrepareMediaList( urls, st::sendMediaPreviewSize); if (list.error != Storage::PreparedList::Error::NonLocalUrl) { if (list.error == Storage::PreparedList::Error::None || !hasImage) { const auto emptyTextOnCancel = QString(); confirmSendingFiles( std::move(list), compressed, emptyTextOnCancel); return true; } } } if (hasImage) { auto image = qvariant_cast<QImage>(data->imageData()); if (!image.isNull()) { confirmSendingFiles( std::move(image), QByteArray(), compressed, insertTextOnCancel); return true; } } return false; } void HistoryWidget::uploadFiles( Storage::PreparedList &&list, SendMediaType type) { ActivateWindowDelayed(controller()); auto caption = QString(); uploadFilesAfterConfirmation( std::move(list), type, caption, replyToId()); } void HistoryWidget::uploadFilesAfterConfirmation( Storage::PreparedList &&list, SendMediaType type, QString caption, MsgId replyTo, std::shared_ptr<SendingAlbum> album) { Assert(canWriteMessage()); auto options = ApiWrap::SendOptions(_history); options.replyTo = replyTo; Auth().api().sendFiles( std::move(list), type, caption, album, options); } void HistoryWidget::uploadFile( const QByteArray &fileContent, SendMediaType type) { if (!canWriteMessage()) return; auto options = ApiWrap::SendOptions(_history); options.replyTo = replyToId(); Auth().api().sendFile(fileContent, type, options); } void HistoryWidget::sendFileConfirmed( const std::shared_ptr<FileLoadResult> &file) { const auto channelId = peerToChannel(file->to.peer); const auto lastKeyboardUsed = lastForceReplyReplied(FullMsgId( channelId, file->to.replyTo)); const auto newId = FullMsgId(channelId, clientMsgId()); const auto groupId = file->album ? file->album->groupId : uint64(0); if (file->album) { const auto proj = [](const SendingAlbum::Item &item) { return item.taskId; }; const auto it = ranges::find(file->album->items, file->taskId, proj); Assert(it != file->album->items.end()); it->msgId = newId; } connect(&Auth().uploader(), SIGNAL(photoReady(const FullMsgId&,bool,const MTPInputFile&)), this, SLOT(onPhotoUploaded(const FullMsgId&,bool,const MTPInputFile&)), Qt::UniqueConnection); connect(&Auth().uploader(), SIGNAL(documentReady(const FullMsgId&,bool,const MTPInputFile&)), this, SLOT(onDocumentUploaded(const FullMsgId&,bool,const MTPInputFile&)), Qt::UniqueConnection); connect(&Auth().uploader(), SIGNAL(thumbDocumentReady(const FullMsgId&,bool,const MTPInputFile&,const MTPInputFile&)), this, SLOT(onThumbDocumentUploaded(const FullMsgId&,bool,const MTPInputFile&, const MTPInputFile&)), Qt::UniqueConnection); connect(&Auth().uploader(), SIGNAL(photoProgress(const FullMsgId&)), this, SLOT(onPhotoProgress(const FullMsgId&)), Qt::UniqueConnection); connect(&Auth().uploader(), SIGNAL(documentProgress(const FullMsgId&)), this, SLOT(onDocumentProgress(const FullMsgId&)), Qt::UniqueConnection); connect(&Auth().uploader(), SIGNAL(photoFailed(const FullMsgId&)), this, SLOT(onPhotoFailed(const FullMsgId&)), Qt::UniqueConnection); connect(&Auth().uploader(), SIGNAL(documentFailed(const FullMsgId&)), this, SLOT(onDocumentFailed(const FullMsgId&)), Qt::UniqueConnection); Auth().uploader().upload(newId, file); const auto history = App::history(file->to.peer); const auto peer = history->peer; auto options = ApiWrap::SendOptions(history); options.clearDraft = false; options.replyTo = file->to.replyTo; options.generateLocal = true; Auth().api().sendAction(options); auto flags = NewMessageFlags(peer) | MTPDmessage::Flag::f_media; if (file->to.replyTo) flags |= MTPDmessage::Flag::f_reply_to_msg_id; bool channelPost = peer->isChannel() && !peer->isMegagroup(); bool silentPost = channelPost && file->to.silent; if (channelPost) { flags |= MTPDmessage::Flag::f_views; flags |= MTPDmessage::Flag::f_post; } if (!channelPost) { flags |= MTPDmessage::Flag::f_from_id; } else if (peer->asChannel()->addsSignature()) { flags |= MTPDmessage::Flag::f_post_author; } if (silentPost) { flags |= MTPDmessage::Flag::f_silent; } if (groupId) { flags |= MTPDmessage::Flag::f_grouped_id; } auto messageFromId = channelPost ? 0 : Auth().userId(); auto messagePostAuthor = channelPost ? (Auth().user()->firstName + ' ' + Auth().user()->lastName) : QString(); if (file->type == SendMediaType::Photo) { auto photoFlags = MTPDmessageMediaPhoto::Flag::f_photo | 0; auto photo = MTP_messageMediaPhoto( MTP_flags(photoFlags), file->photo, MTPint()); history->addNewMessage( MTP_message( MTP_flags(flags), MTP_int(newId.msg), MTP_int(messageFromId), peerToMTP(file->to.peer), MTPnullFwdHeader, MTPint(), MTP_int(file->to.replyTo), MTP_int(unixtime()), MTP_string(file->caption), photo, MTPnullMarkup, MTPnullEntities, // #TODO caption entities MTP_int(1), MTPint(), MTP_string(messagePostAuthor), MTP_long(groupId)), NewMessageUnread); } else if (file->type == SendMediaType::File) { auto documentFlags = MTPDmessageMediaDocument::Flag::f_document | 0; auto document = MTP_messageMediaDocument( MTP_flags(documentFlags), file->document, MTPint()); history->addNewMessage( MTP_message( MTP_flags(flags), MTP_int(newId.msg), MTP_int(messageFromId), peerToMTP(file->to.peer), MTPnullFwdHeader, MTPint(), MTP_int(file->to.replyTo), MTP_int(unixtime()), MTP_string(file->caption), document, MTPnullMarkup, MTPnullEntities, // #TODO caption entities MTP_int(1), MTPint(), MTP_string(messagePostAuthor), MTP_long(groupId)), NewMessageUnread); } else if (file->type == SendMediaType::Audio) { if (!peer->isChannel()) { flags |= MTPDmessage::Flag::f_media_unread; } auto documentFlags = MTPDmessageMediaDocument::Flag::f_document | 0; auto document = MTP_messageMediaDocument( MTP_flags(documentFlags), file->document, MTPint()); history->addNewMessage( MTP_message( MTP_flags(flags), MTP_int(newId.msg), MTP_int(messageFromId), peerToMTP(file->to.peer), MTPnullFwdHeader, MTPint(), MTP_int(file->to.replyTo), MTP_int(unixtime()), MTP_string(file->caption), document, MTPnullMarkup, MTPnullEntities, // #TODO caption entities MTP_int(1), MTPint(), MTP_string(messagePostAuthor), MTP_long(groupId)), NewMessageUnread); } Auth().data().sendHistoryChangeNotifications(); if (_peer && file->to.peer == _peer->id) { App::main()->historyToDown(_history); } App::main()->dialogsToUp(); } void HistoryWidget::onPhotoUploaded( const FullMsgId &newId, bool silent, const MTPInputFile &file) { Auth().api().sendUploadedPhoto(newId, file, silent); } void HistoryWidget::onDocumentUploaded( const FullMsgId &newId, bool silent, const MTPInputFile &file) { Auth().api().sendUploadedDocument(newId, file, base::none, silent); } void HistoryWidget::onThumbDocumentUploaded( const FullMsgId &newId, bool silent, const MTPInputFile &file, const MTPInputFile &thumb) { Auth().api().sendUploadedDocument(newId, file, thumb, silent); } void HistoryWidget::onPhotoProgress(const FullMsgId &newId) { if (const auto item = App::histItemById(newId)) { const auto photo = item->media() ? item->media()->photo() : nullptr; updateSendAction(item->history(), SendAction::Type::UploadPhoto, 0); Auth().data().requestItemRepaint(item); } } void HistoryWidget::onDocumentProgress(const FullMsgId &newId) { if (const auto item = App::histItemById(newId)) { const auto media = item->media(); const auto document = media ? media->document() : nullptr; const auto sendAction = (document && document->isVoiceMessage()) ? SendAction::Type::UploadVoice : SendAction::Type::UploadFile; const auto progress = (document && document->uploading()) ? document->uploadingData->offset : 0; updateSendAction( item->history(), sendAction, progress); Auth().data().requestItemRepaint(item); } } void HistoryWidget::onPhotoFailed(const FullMsgId &newId) { if (const auto item = App::histItemById(newId)) { updateSendAction( item->history(), SendAction::Type::UploadPhoto, -1); Auth().data().requestItemRepaint(item); } } void HistoryWidget::onDocumentFailed(const FullMsgId &newId) { if (const auto item = App::histItemById(newId)) { const auto media = item->media(); const auto document = media ? media->document() : nullptr; const auto sendAction = (document && document->isVoiceMessage()) ? SendAction::Type::UploadVoice : SendAction::Type::UploadFile; updateSendAction(item->history(), sendAction, -1); Auth().data().requestItemRepaint(item); } } void HistoryWidget::onReportSpamClicked() { auto text = lang(_peer->isUser() ? lng_report_spam_sure : ((_peer->isChat() || _peer->isMegagroup()) ? lng_report_spam_sure_group : lng_report_spam_sure_channel)); Ui::show(Box<ConfirmBox>(text, lang(lng_report_spam_ok), st::attentionBoxButton, base::lambda_guarded(this, [this, peer = _peer] { if (_reportSpamRequest) return; Ui::hideLayer(); if (auto user = peer->asUser()) { MTP::send(MTPcontacts_Block(user->inputUser), rpcDone(&HistoryWidget::blockDone, peer), RPCFailHandlerPtr(), 0, 5); } _reportSpamRequest = MTP::send(MTPmessages_ReportSpam(peer->input), rpcDone(&HistoryWidget::reportSpamDone, peer), rpcFail(&HistoryWidget::reportSpamFail)); }))); } void HistoryWidget::reportSpamDone(PeerData *peer, const MTPBool &result, mtpRequestId req) { Expects(peer != nullptr); if (req == _reportSpamRequest) { _reportSpamRequest = 0; } cRefReportSpamStatuses().insert(peer->id, dbiprsReportSent); Local::writeReportSpamStatuses(); if (_peer == peer) { setReportSpamStatus(dbiprsReportSent); if (_reportSpamPanel) { _reportSpamPanel->setReported(_reportSpamStatus == dbiprsReportSent, peer); } } } bool HistoryWidget::reportSpamFail(const RPCError &error, mtpRequestId req) { if (MTP::isDefaultHandledError(error)) return false; if (req == _reportSpamRequest) { _reportSpamRequest = 0; } return false; } void HistoryWidget::onReportSpamHide() { if (_peer) { cRefReportSpamStatuses().insert(_peer->id, dbiprsHidden); Local::writeReportSpamStatuses(); MTP::send(MTPmessages_HideReportSpam(_peer->input)); } setReportSpamStatus(dbiprsHidden); updateControlsVisibility(); } void HistoryWidget::onReportSpamClear() { Expects(_peer != nullptr); InvokeQueued(App::main(), [peer = _peer] { if (peer->isUser()) { App::main()->deleteConversation(peer); } else if (auto chat = peer->asChat()) { App::main()->deleteAndExit(chat); } else if (auto channel = peer->asChannel()) { if (channel->migrateFrom()) { App::main()->deleteConversation(channel->migrateFrom()); } Auth().api().leaveChannel(channel); } }); // Invalidates _peer. controller()->showBackFromStack(); } void HistoryWidget::handleHistoryChange(not_null<const History*> history) { if (_list && (_history == history || _migrated == history)) { handlePendingHistoryUpdate(); updateBotKeyboard(); if (!_scroll->isHidden()) { const auto unblock = isBlocked(); const auto botStart = isBotStart(); const auto joinChannel = isJoinChannel(); const auto muteUnmute = isMuteUnmute(); const auto update = false || (_unblock->isHidden() == unblock) || (!unblock && _botStart->isHidden() == botStart) || (!unblock && !botStart && _joinChannel->isHidden() == joinChannel) || (!unblock && !botStart && !joinChannel && _muteUnmute->isHidden() == muteUnmute); if (update) { updateControlsVisibility(); updateControlsGeometry(); } } } } void HistoryWidget::grapWithoutTopBarShadow() { grabStart(); _topShadow->hide(); } void HistoryWidget::grabFinish() { _inGrab = false; updateControlsGeometry(); _topShadow->show(); } bool HistoryWidget::skipItemRepaint() { auto ms = getms(); if (_lastScrolled + kSkipRepaintWhileScrollMs <= ms) { return false; } _updateHistoryItems.start( _lastScrolled + kSkipRepaintWhileScrollMs - ms); return true; } void HistoryWidget::onUpdateHistoryItems() { if (!_list) return; auto ms = getms(); if (_lastScrolled + kSkipRepaintWhileScrollMs <= ms) { _list->update(); } else { _updateHistoryItems.start(_lastScrolled + kSkipRepaintWhileScrollMs - ms); } } PeerData *HistoryWidget::ui_getPeerForMouseAction() { return _peer; } void HistoryWidget::handlePendingHistoryUpdate() { if (hasPendingResizedItems() || _updateHistoryGeometryRequired) { updateHistoryGeometry(); _list->update(); } } void HistoryWidget::resizeEvent(QResizeEvent *e) { //updateTabbedSelectorSectionShown(); recountChatWidth(); updateControlsGeometry(); } void HistoryWidget::updateControlsGeometry() { _topBar->resizeToWidth(width()); _topBar->moveToLeft(0, 0); moveFieldControls(); auto scrollAreaTop = _topBar->bottomNoMargins(); if (_pinnedBar) { _pinnedBar->cancel->moveToLeft(width() - _pinnedBar->cancel->width(), scrollAreaTop); scrollAreaTop += st::historyReplyHeight; _pinnedBar->shadow->setGeometryToLeft(0, scrollAreaTop, width(), st::lineWidth); } if (_scroll->y() != scrollAreaTop) { _scroll->moveToLeft(0, scrollAreaTop); _fieldAutocomplete->setBoundings(_scroll->geometry()); } if (_reportSpamPanel) { _reportSpamPanel->setGeometryToLeft(0, _scroll->y(), width(), _reportSpamPanel->height()); } updateHistoryGeometry(false, false, { ScrollChangeAdd, App::main() ? App::main()->contentScrollAddToY() : 0 }); updateFieldSize(); updateHistoryDownPosition(); if (_membersDropdown) { _membersDropdown->setMaxHeight(countMembersDropdownHeightMax()); } switch (_attachDragState) { case DragState::Files: _attachDragDocument->resize(width() - st::dragMargin.left() - st::dragMargin.right(), height() - st::dragMargin.top() - st::dragMargin.bottom()); _attachDragDocument->move(st::dragMargin.left(), st::dragMargin.top()); break; case DragState::PhotoFiles: _attachDragDocument->resize(width() - st::dragMargin.left() - st::dragMargin.right(), (height() - st::dragMargin.top() - st::dragMargin.bottom()) / 2); _attachDragDocument->move(st::dragMargin.left(), st::dragMargin.top()); _attachDragPhoto->resize(_attachDragDocument->width(), _attachDragDocument->height()); _attachDragPhoto->move(st::dragMargin.left(), height() - _attachDragPhoto->height() - st::dragMargin.bottom()); break; case DragState::Image: _attachDragPhoto->resize(width() - st::dragMargin.left() - st::dragMargin.right(), height() - st::dragMargin.top() - st::dragMargin.bottom()); _attachDragPhoto->move(st::dragMargin.left(), st::dragMargin.top()); break; } auto topShadowLeft = (Adaptive::OneColumn() || _inGrab) ? 0 : st::lineWidth; auto topShadowRight = (Adaptive::ThreeColumn() && !_inGrab && _peer) ? st::lineWidth : 0; _topShadow->setGeometryToLeft( topShadowLeft, _topBar->bottomNoMargins(), width() - topShadowLeft - topShadowRight, st::lineWidth); } void HistoryWidget::itemRemoved(not_null<const HistoryItem*> item) { if (item == _replyEditMsg) { if (_editMsgId) { cancelEdit(); } else { cancelReply(); } } if (item == _replyReturn) { calcNextReplyReturn(); } if (_pinnedBar && item->id == _pinnedBar->msgId) { pinnedMsgVisibilityUpdated(); } if (_kbReplyTo && item == _kbReplyTo) { onKbToggle(); _kbReplyTo = 0; } auto found = ranges::find(_toForward, item); if (found != _toForward.end()) { _toForward.erase(found); updateForwardingTexts(); if (_toForward.empty()) { updateControlsVisibility(); updateControlsGeometry(); } } } void HistoryWidget::itemEdited(HistoryItem *item) { if (item == _replyEditMsg) { updateReplyEditTexts(true); } if (_pinnedBar && item->id == _pinnedBar->msgId) { updatePinnedBar(true); } } void HistoryWidget::updateScrollColors() { _scroll->updateBars(); } MsgId HistoryWidget::replyToId() const { return _replyToId ? _replyToId : (_kbReplyTo ? _kbReplyTo->id : 0); } int HistoryWidget::countInitialScrollTop() { auto result = ScrollMax; if (_history->scrollTopItem || (_migrated && _migrated->scrollTopItem)) { result = _list->historyScrollTop(); } else if (_showAtMsgId && (_showAtMsgId > 0 || -_showAtMsgId < ServerMaxMsgId)) { auto item = getItemFromHistoryOrMigrated(_showAtMsgId); auto itemTop = _list->itemTop(item); if (itemTop < 0) { setMsgId(0); return countInitialScrollTop(); } else { const auto view = item->mainView(); Assert(view != nullptr); result = itemTopForHighlight(view); enqueueMessageHighlight(view); } } else if (const auto top = unreadBarTop()) { result = *top; } else { return countAutomaticScrollTop(); } return qMin(result, _scroll->scrollTopMax()); } int HistoryWidget::countAutomaticScrollTop() { auto result = ScrollMax; if (const auto unread = firstUnreadMessage()) { result = _list->itemTop(unread); const auto possibleUnreadBarTop = _scroll->scrollTopMax() + HistoryView::UnreadBar::height() - HistoryView::UnreadBar::marginTop(); if (result < possibleUnreadBarTop) { const auto history = unread->data()->history(); history->addUnreadBar(); if (hasPendingResizedItems()) { updateListSize(); } if (history->unreadBar() != nullptr) { setMsgId(ShowAtUnreadMsgId); result = countInitialScrollTop(); App::wnd()->checkHistoryActivation(); return result; } } } return qMin(result, _scroll->scrollTopMax()); } void HistoryWidget::updateHistoryGeometry(bool initial, bool loadedDown, const ScrollChange &change) { if (!_history || (initial && _historyInited) || (!initial && !_historyInited)) { return; } if (_firstLoadRequest || _a_show.animating()) { return; // scrollTopMax etc are not working after recountHistoryGeometry() } auto newScrollHeight = height() - _topBar->height(); if (!editingMessage() && (isBlocked() || isBotStart() || isJoinChannel() || isMuteUnmute())) { newScrollHeight -= _unblock->height(); } else { if (editingMessage() || _canSendMessages) { newScrollHeight -= (_field->height() + 2 * st::historySendPadding); } else if (isRestrictedWrite()) { newScrollHeight -= _unblock->height(); } if (_editMsgId || replyToId() || readyToForward() || (_previewData && _previewData->pendingTill >= 0)) { newScrollHeight -= st::historyReplyHeight; } if (_kbShown) { newScrollHeight -= _kbScroll->height(); } } if (_pinnedBar) { newScrollHeight -= st::historyReplyHeight; } auto wasScrollTop = _scroll->scrollTop(); auto wasScrollTopMax = _scroll->scrollTopMax(); auto wasAtBottom = wasScrollTop + 1 > wasScrollTopMax; auto needResize = (_scroll->width() != width()) || (_scroll->height() != newScrollHeight); if (needResize) { _scroll->resize(width(), newScrollHeight); // on initial updateListSize we didn't put the _scroll->scrollTop correctly yet // so visibleAreaUpdated() call will erase it with the new (undefined) value if (!initial) { visibleAreaUpdated(); } _fieldAutocomplete->setBoundings(_scroll->geometry()); if (!_historyDownShown.animating()) { // _historyDown is a child widget of _scroll, not me. _historyDown->moveToRight(st::historyToDownPosition.x(), _scroll->height() - _historyDown->height() - st::historyToDownPosition.y()); if (!_unreadMentionsShown.animating()) { // _unreadMentions is a child widget of _scroll, not me. auto additionalSkip = _historyDownIsShown ? (_historyDown->height() + st::historyUnreadMentionsSkip) : 0; _unreadMentions->moveToRight(st::historyToDownPosition.x(), _scroll->height() - additionalSkip - st::historyToDownPosition.y()); } } controller()->floatPlayerAreaUpdated().notify(true); } updateListSize(); _updateHistoryGeometryRequired = false; if ((!initial && !wasAtBottom) || (loadedDown && (!_history->firstUnreadMessage() || _history->unreadBar() || _history->loadedAtBottom()) && (!_migrated || !_migrated->firstUnreadMessage() || _migrated->unreadBar() || _history->loadedAtBottom()))) { const auto historyScrollTop = _list->historyScrollTop(); if (!wasAtBottom && historyScrollTop == ScrollMax) { // History scroll top was not inited yet. // If we're showing locally unread messages, we get here // from destroyUnreadBar() before we have time to scroll // to good initial position, like top of an unread bar. return; } auto toY = qMin(_list->historyScrollTop(), _scroll->scrollTopMax()); if (change.type == ScrollChangeAdd) { toY += change.value; } else if (change.type == ScrollChangeNoJumpToBottom) { toY = wasScrollTop; } else if (_addToScroll) { toY += _addToScroll; _addToScroll = 0; } toY = snap(toY, 0, _scroll->scrollTopMax()); if (_scroll->scrollTop() == toY) { visibleAreaUpdated(); } else { synteticScrollToY(toY); } return; } if (initial) { _historyInited = true; _scrollToAnimation.finish(); } auto newScrollTop = initial ? countInitialScrollTop() : countAutomaticScrollTop(); if (_scroll->scrollTop() == newScrollTop) { visibleAreaUpdated(); } else { synteticScrollToY(newScrollTop); } } void HistoryWidget::updateListSize() { _list->recountHistoryGeometry(); auto washidden = _scroll->isHidden(); if (washidden) { _scroll->show(); } _list->updateSize(); if (washidden) { _scroll->hide(); } _updateHistoryGeometryRequired = true; } bool HistoryWidget::hasPendingResizedItems() const { return (_history && _history->hasPendingResizedItems()) || (_migrated && _migrated->hasPendingResizedItems()); } base::optional<int> HistoryWidget::unreadBarTop() const { auto getUnreadBar = [this]() -> HistoryView::Element* { if (const auto bar = _migrated ? _migrated->unreadBar() : nullptr) { return bar; } else if (const auto bar = _history->unreadBar()) { return bar; } return nullptr; }; if (const auto bar = getUnreadBar()) { const auto result = _list->itemTop(bar) + HistoryView::UnreadBar::marginTop(); if (bar->Has<HistoryView::DateBadge>()) { return result + bar->Get<HistoryView::DateBadge>()->height(); } return result; } return base::none; } HistoryView::Element *HistoryWidget::firstUnreadMessage() const { if (_migrated) { if (const auto result = _migrated->firstUnreadMessage()) { return result; } } return _history ? _history->firstUnreadMessage() : nullptr; } void HistoryWidget::addMessagesToFront(PeerData *peer, const QVector<MTPMessage> &messages) { _list->messagesReceived(peer, messages); if (!_firstLoadRequest) { updateHistoryGeometry(); adjustHighlightedMessageToMigrated(); updateBotKeyboard(); } } void HistoryWidget::addMessagesToBack(PeerData *peer, const QVector<MTPMessage> &messages) { _list->messagesReceivedDown(peer, messages); if (!_firstLoadRequest) { updateHistoryGeometry(false, true, { ScrollChangeNoJumpToBottom, 0 }); } } void HistoryWidget::countHistoryShowFrom() { if (_migrated && _showAtMsgId == ShowAtUnreadMsgId && _migrated->unreadCount()) { _migrated->calculateFirstUnreadMessage(); } if ((_migrated && _migrated->firstUnreadMessage()) || (_showAtMsgId != ShowAtUnreadMsgId) || !_history->unreadCount()) { _history->unsetFirstUnreadMessage(); } else { _history->calculateFirstUnreadMessage(); } } void HistoryWidget::updateBotKeyboard(History *h, bool force) { if (h && h != _history && h != _migrated) { return; } bool changed = false; bool wasVisible = _kbShown || _kbReplyTo; if ((_replyToId && !_replyEditMsg) || _editMsgId || !_history) { changed = _keyboard->updateMarkup(nullptr, force); } else if (_replyToId && _replyEditMsg) { changed = _keyboard->updateMarkup(_replyEditMsg, force); } else { HistoryItem *keyboardItem = _history->lastKeyboardId ? App::histItemById(_channel, _history->lastKeyboardId) : nullptr; changed = _keyboard->updateMarkup(keyboardItem, force); } updateCmdStartShown(); if (!changed) return; bool hasMarkup = _keyboard->hasMarkup(), forceReply = _keyboard->forceReply() && (!_replyToId || !_replyEditMsg); if (hasMarkup || forceReply) { if (_keyboard->singleUse() && _keyboard->hasMarkup() && _keyboard->forMsgId() == FullMsgId(_channel, _history->lastKeyboardId) && _history->lastKeyboardUsed) { _history->lastKeyboardHiddenId = _history->lastKeyboardId; } if (!isBotStart() && !isBlocked() && _canSendMessages && (wasVisible || (_replyToId && _replyEditMsg) || (!_field->hasSendText() && !kbWasHidden()))) { if (!_a_show.animating()) { if (hasMarkup) { _kbScroll->show(); _tabbedSelectorToggle->hide(); _botKeyboardHide->show(); } else { _kbScroll->hide(); _tabbedSelectorToggle->show(); _botKeyboardHide->hide(); } _botKeyboardShow->hide(); _botCommandStart->hide(); } int32 maxh = hasMarkup ? qMin(_keyboard->height(), st::historyComposeFieldMaxHeight - (st::historyComposeFieldMaxHeight / 2)) : 0; _field->setMaxHeight(st::historyComposeFieldMaxHeight - maxh); _kbShown = hasMarkup; _kbReplyTo = (_peer->isChat() || _peer->isChannel() || _keyboard->forceReply()) ? App::histItemById(_keyboard->forMsgId()) : 0; if (_kbReplyTo && !_replyToId) { updateReplyToName(); _replyEditMsgText.setText( st::messageTextStyle, TextUtilities::Clean(_kbReplyTo->inReplyText()), Ui::DialogTextOptions()); _fieldBarCancel->show(); updateMouseTracking(); } } else { if (!_a_show.animating()) { _kbScroll->hide(); _tabbedSelectorToggle->show(); _botKeyboardHide->hide(); _botKeyboardShow->show(); _botCommandStart->hide(); } _field->setMaxHeight(st::historyComposeFieldMaxHeight); _kbShown = false; _kbReplyTo = 0; if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !_replyToId) { _fieldBarCancel->hide(); updateMouseTracking(); } } } else { if (!_scroll->isHidden()) { _kbScroll->hide(); _tabbedSelectorToggle->show(); _botKeyboardHide->hide(); _botKeyboardShow->hide(); _botCommandStart->show(); } _field->setMaxHeight(st::historyComposeFieldMaxHeight); _kbShown = false; _kbReplyTo = 0; if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !_replyToId && !_editMsgId) { _fieldBarCancel->hide(); updateMouseTracking(); } } updateControlsGeometry(); update(); } void HistoryWidget::updateHistoryDownPosition() { // _historyDown is a child widget of _scroll, not me. auto top = anim::interpolate(0, _historyDown->height() + st::historyToDownPosition.y(), _historyDownShown.current(_historyDownIsShown ? 1. : 0.)); _historyDown->moveToRight(st::historyToDownPosition.x(), _scroll->height() - top); auto shouldBeHidden = !_historyDownIsShown && !_historyDownShown.animating(); if (shouldBeHidden != _historyDown->isHidden()) { _historyDown->setVisible(!shouldBeHidden); } updateUnreadMentionsPosition(); } void HistoryWidget::updateHistoryDownVisibility() { if (_a_show.animating()) return; auto haveUnreadBelowBottom = [&](History *history) { if (!_list || !history || history->unreadCount() <= 0) { return false; } const auto unread = history->firstUnreadMessage(); if (!unread) { return false; } const auto top = _list->itemTop(unread); return (top >= _scroll->scrollTop() + _scroll->height()); }; auto historyDownIsVisible = [&] { if (!_list || _firstLoadRequest) { return false; } if (!_history->loadedAtBottom() || _replyReturn) { return true; } const auto top = _scroll->scrollTop() + st::historyToDownShownAfter; if (top < _scroll->scrollTopMax()) { return true; } if (haveUnreadBelowBottom(_history) || haveUnreadBelowBottom(_migrated)) { return true; } return false; }; auto historyDownIsShown = historyDownIsVisible(); if (_historyDownIsShown != historyDownIsShown) { _historyDownIsShown = historyDownIsShown; _historyDownShown.start([this] { updateHistoryDownPosition(); }, _historyDownIsShown ? 0. : 1., _historyDownIsShown ? 1. : 0., st::historyToDownDuration); } } void HistoryWidget::updateUnreadMentionsPosition() { // _unreadMentions is a child widget of _scroll, not me. auto right = anim::interpolate(-_unreadMentions->width(), st::historyToDownPosition.x(), _unreadMentionsShown.current(_unreadMentionsIsShown ? 1. : 0.)); auto shift = anim::interpolate(0, _historyDown->height() + st::historyUnreadMentionsSkip, _historyDownShown.current(_historyDownIsShown ? 1. : 0.)); auto top = _scroll->height() - _unreadMentions->height() - st::historyToDownPosition.y() - shift; _unreadMentions->moveToRight(right, top); auto shouldBeHidden = !_unreadMentionsIsShown && !_unreadMentionsShown.animating(); if (shouldBeHidden != _unreadMentions->isHidden()) { _unreadMentions->setVisible(!shouldBeHidden); } } void HistoryWidget::updateUnreadMentionsVisibility() { if (_a_show.animating()) return; auto showUnreadMentions = _peer && (_peer->isChat() || _peer->isMegagroup()); if (showUnreadMentions) { Auth().api().preloadEnoughUnreadMentions(_history); } auto unreadMentionsIsVisible = [this, showUnreadMentions] { if (!showUnreadMentions || _firstLoadRequest) { return false; } return (_history->getUnreadMentionsLoadedCount() > 0); }; auto unreadMentionsIsShown = unreadMentionsIsVisible(); if (unreadMentionsIsShown) { _unreadMentions->setUnreadCount(_history->getUnreadMentionsCount()); } if (_unreadMentionsIsShown != unreadMentionsIsShown) { _unreadMentionsIsShown = unreadMentionsIsShown; _unreadMentionsShown.start([this] { updateUnreadMentionsPosition(); }, _unreadMentionsIsShown ? 0. : 1., _unreadMentionsIsShown ? 1. : 0., st::historyToDownDuration); } } void HistoryWidget::mousePressEvent(QMouseEvent *e) { _replyForwardPressed = QRect(0, _field->y() - st::historySendPadding - st::historyReplyHeight, st::historyReplySkip, st::historyReplyHeight).contains(e->pos()); if (_replyForwardPressed && !_fieldBarCancel->isHidden()) { updateField(); } else if (_inReplyEditForward) { if (readyToForward()) { const auto items = std::move(_toForward); App::main()->cancelForwarding(_history); Window::ShowForwardMessagesBox(ranges::view::all( items ) | ranges::view::transform([](not_null<HistoryItem*> item) { return item->fullId(); }) | ranges::to_vector); } else { Ui::showPeerHistory(_peer, _editMsgId ? _editMsgId : replyToId()); } } else if (_inPinnedMsg) { Assert(_pinnedBar != nullptr); Ui::showPeerHistory(_peer, _pinnedBar->msgId); } } void HistoryWidget::keyPressEvent(QKeyEvent *e) { if (!_history) return; if (e->key() == Qt::Key_Escape) { e->ignore(); } else if (e->key() == Qt::Key_Back) { controller()->showBackFromStack(); emit cancelled(); } else if (e->key() == Qt::Key_PageDown) { _scroll->keyPressEvent(e); } else if (e->key() == Qt::Key_PageUp) { _scroll->keyPressEvent(e); } else if (e->key() == Qt::Key_Down) { if (!(e->modifiers() & (Qt::ShiftModifier | Qt::MetaModifier | Qt::ControlModifier))) { _scroll->keyPressEvent(e); } else if ((e->modifiers() & (Qt::ShiftModifier | Qt::MetaModifier | Qt::ControlModifier)) == Qt::ControlModifier) { replyToNextMessage(); } } else if (e->key() == Qt::Key_Up) { if (!(e->modifiers() & (Qt::ShiftModifier | Qt::MetaModifier | Qt::ControlModifier))) { const auto item = _history ? _history->lastSentMessage() : nullptr; if (item && item->allowsEdit(unixtime()) && _field->isEmpty() && !_editMsgId && !_replyToId) { editMessage(item); return; } _scroll->keyPressEvent(e); } else if ((e->modifiers() & (Qt::ShiftModifier | Qt::MetaModifier | Qt::ControlModifier)) == Qt::ControlModifier) { replyToPreviousMessage(); } } else if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) { onListEnterPressed(); } else { e->ignore(); } } void HistoryWidget::replyToPreviousMessage() { if (!_history || _editMsgId) { return; } const auto fullId = FullMsgId( _history->channelId(), _replyToId); if (const auto item = App::histItemById(fullId)) { if (const auto view = item->mainView()) { if (const auto previousView = view->previousInBlocks()) { const auto previous = previousView->data(); Ui::showPeerHistoryAtItem(previous); replyToMessage(previous); } } } else if (const auto previous = _history->lastMessage()) { Ui::showPeerHistoryAtItem(previous); replyToMessage(previous); } } void HistoryWidget::replyToNextMessage() { if (!_history || _editMsgId) { return; } const auto fullId = FullMsgId( _history->channelId(), _replyToId); if (const auto item = App::histItemById(fullId)) { if (const auto view = item->mainView()) { if (const auto nextView = view->nextInBlocks()) { const auto next = nextView->data(); Ui::showPeerHistoryAtItem(next); replyToMessage(next); } } } } void HistoryWidget::onFieldTabbed() { if (!_fieldAutocomplete->isHidden()) { _fieldAutocomplete->chooseSelected(FieldAutocomplete::ChooseMethod::ByTab); } } bool HistoryWidget::onStickerSend(DocumentData *sticker) { if (auto megagroup = _peer ? _peer->asMegagroup() : nullptr) { if (megagroup->restricted(ChannelRestriction::f_send_stickers)) { Ui::show( Box<InformBox>(lang(lng_restricted_send_stickers)), LayerOption::KeepOther); return false; } } return sendExistingDocument(sticker, TextWithEntities()); } void HistoryWidget::onPhotoSend(PhotoData *photo) { if (auto megagroup = _peer ? _peer->asMegagroup() : nullptr) { if (megagroup->restricted(ChannelRestriction::f_send_media)) { Ui::show( Box<InformBox>(lang(lng_restricted_send_media)), LayerOption::KeepOther); return; } } sendExistingPhoto(photo, TextWithEntities()); } void HistoryWidget::onInlineResultSend( InlineBots::Result *result, UserData *bot) { if (!_peer || !_peer->canWrite() || !result) { return; } auto errorText = result->getErrorOnSend(_history); if (!errorText.isEmpty()) { Ui::show(Box<InformBox>(errorText)); return; } auto options = ApiWrap::SendOptions(_history); options.clearDraft = true; options.replyTo = replyToId(); options.generateLocal = true; Auth().api().sendAction(options); uint64 randomId = rand_value<uint64>(); FullMsgId newId(_channel, clientMsgId()); auto flags = NewMessageFlags(_peer) | MTPDmessage::Flag::f_media; auto sendFlags = MTPmessages_SendInlineBotResult::Flag::f_clear_draft | 0; if (options.replyTo) { flags |= MTPDmessage::Flag::f_reply_to_msg_id; sendFlags |= MTPmessages_SendInlineBotResult::Flag::f_reply_to_msg_id; } bool channelPost = _peer->isChannel() && !_peer->isMegagroup(); bool silentPost = channelPost && _peer->notifySilentPosts(); if (channelPost) { flags |= MTPDmessage::Flag::f_views; flags |= MTPDmessage::Flag::f_post; } if (!channelPost) { flags |= MTPDmessage::Flag::f_from_id; } else if (_peer->asChannel()->addsSignature()) { flags |= MTPDmessage::Flag::f_post_author; } if (silentPost) { sendFlags |= MTPmessages_SendInlineBotResult::Flag::f_silent; } if (bot) { flags |= MTPDmessage::Flag::f_via_bot_id; } auto messageFromId = channelPost ? 0 : Auth().userId(); auto messagePostAuthor = channelPost ? (Auth().user()->firstName + ' ' + Auth().user()->lastName) : QString(); MTPint messageDate = MTP_int(unixtime()); UserId messageViaBotId = bot ? peerToUser(bot->id) : 0; MsgId messageId = newId.msg; result->addToHistory( _history, flags, messageId, messageFromId, messageDate, messageViaBotId, options.replyTo, messagePostAuthor); _history->sendRequestId = MTP::send( MTPmessages_SendInlineBotResult( MTP_flags(sendFlags), _peer->input, MTP_int(options.replyTo), MTP_long(randomId), MTP_long(result->getQueryId()), MTP_string(result->getId())), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::sendMessageFail), 0, 0, _history->sendRequestId); App::main()->finishForwarding(_history); App::historyRegRandom(randomId, newId); clearFieldText(); _saveDraftText = true; _saveDraftStart = getms(); onDraftSave(); RecentInlineBots &bots(cRefRecentInlineBots()); int32 index = bots.indexOf(bot); if (index) { if (index > 0) { bots.removeAt(index); } else if (bots.size() >= RecentInlineBotsLimit) { bots.resize(RecentInlineBotsLimit - 1); } bots.push_front(bot); Local::writeRecentHashtagsAndBots(); } hideSelectorControlsAnimated(); _field->setFocus(); } HistoryWidget::PinnedBar::PinnedBar(MsgId msgId, HistoryWidget *parent) : msgId(msgId) , cancel(parent, st::historyReplyCancel) , shadow(parent) { } HistoryWidget::PinnedBar::~PinnedBar() { cancel.destroyDelayed(); shadow.destroyDelayed(); } void HistoryWidget::updatePinnedBar(bool force) { update(); if (!_pinnedBar) { return; } if (!force) { if (_pinnedBar->msg) { return; } } Assert(_history != nullptr); if (!_pinnedBar->msg) { _pinnedBar->msg = App::histItemById(_history->channelId(), _pinnedBar->msgId); } if (_pinnedBar->msg) { _pinnedBar->text.setText( st::messageTextStyle, TextUtilities::Clean(_pinnedBar->msg->notificationText()), Ui::DialogTextOptions()); update(); } else if (force) { if (auto channel = _peer ? _peer->asChannel() : nullptr) { channel->clearPinnedMessage(); } destroyPinnedBar(); updateControlsGeometry(); } } bool HistoryWidget::pinnedMsgVisibilityUpdated() { auto result = false; auto pinnedId = [&] { if (auto channel = _peer ? _peer->asChannel() : nullptr) { return channel->pinnedMessageId(); } return 0; }(); if (pinnedId && !_peer->asChannel()->canPinMessages()) { auto it = Global::HiddenPinnedMessages().constFind(_peer->id); if (it != Global::HiddenPinnedMessages().cend()) { if (it.value() == pinnedId) { pinnedId = 0; } else { Global::RefHiddenPinnedMessages().remove(_peer->id); Local::writeUserSettings(); } } } if (pinnedId) { if (!_pinnedBar) { _pinnedBar = std::make_unique<PinnedBar>(pinnedId, this); if (_a_show.animating()) { _pinnedBar->cancel->hide(); _pinnedBar->shadow->hide(); } else { _pinnedBar->cancel->show(); _pinnedBar->shadow->show(); } connect(_pinnedBar->cancel, SIGNAL(clicked()), this, SLOT(onPinnedHide())); orderWidgets(); updatePinnedBar(); result = true; const auto barTop = unreadBarTop(); if (!barTop || _scroll->scrollTop() != *barTop) { synteticScrollToY(_scroll->scrollTop() + st::historyReplyHeight); } } else if (_pinnedBar->msgId != pinnedId) { _pinnedBar->msgId = pinnedId; _pinnedBar->msg = nullptr; _pinnedBar->text.clear(); updatePinnedBar(); } if (!_pinnedBar->msg) { Auth().api().requestMessageData( _peer->asChannel(), _pinnedBar->msgId, replyEditMessageDataCallback()); } } else if (_pinnedBar) { destroyPinnedBar(); result = true; const auto barTop = unreadBarTop(); if (!barTop || _scroll->scrollTop() != *barTop) { synteticScrollToY(_scroll->scrollTop() - st::historyReplyHeight); } updateControlsGeometry(); } return result; } void HistoryWidget::destroyPinnedBar() { _pinnedBar.reset(); _inPinnedMsg = false; } bool HistoryWidget::sendExistingDocument( DocumentData *doc, TextWithEntities caption) { if (!_peer || !_peer->canWrite() || !doc) { return false; } MTPInputDocument mtpInput = doc->mtpInput(); if (mtpInput.type() == mtpc_inputDocumentEmpty) { return false; } auto options = ApiWrap::SendOptions(_history); options.clearDraft = false; options.replyTo = replyToId(); options.generateLocal = true; Auth().api().sendAction(options); uint64 randomId = rand_value<uint64>(); FullMsgId newId(_channel, clientMsgId()); auto flags = NewMessageFlags(_peer) | MTPDmessage::Flag::f_media; auto sendFlags = MTPmessages_SendMedia::Flags(0); if (options.replyTo) { flags |= MTPDmessage::Flag::f_reply_to_msg_id; sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to_msg_id; } bool channelPost = _peer->isChannel() && !_peer->isMegagroup(); bool silentPost = channelPost && _peer->notifySilentPosts(); if (channelPost) { flags |= MTPDmessage::Flag::f_views; flags |= MTPDmessage::Flag::f_post; } if (!channelPost) { flags |= MTPDmessage::Flag::f_from_id; } else if (_peer->asChannel()->addsSignature()) { flags |= MTPDmessage::Flag::f_post_author; } if (silentPost) { sendFlags |= MTPmessages_SendMedia::Flag::f_silent; } auto messageFromId = channelPost ? 0 : Auth().userId(); auto messagePostAuthor = channelPost ? (Auth().user()->firstName + ' ' + Auth().user()->lastName) : QString(); TextUtilities::Trim(caption); auto sentEntities = TextUtilities::EntitiesToMTP( caption.entities, TextUtilities::ConvertOption::SkipLocal); if (!sentEntities.v.isEmpty()) { sendFlags |= MTPmessages_SendMedia::Flag::f_entities; } _history->addNewDocument( newId.msg, flags, 0, options.replyTo, unixtime(), messageFromId, messagePostAuthor, doc, caption, MTPnullMarkup); _history->sendRequestId = MTP::send( MTPmessages_SendMedia( MTP_flags(sendFlags), _peer->input, MTP_int(options.replyTo), MTP_inputMediaDocument( MTP_flags(0), mtpInput, MTPint()), MTP_string(caption.text), MTP_long(randomId), MTPnullMarkup, sentEntities), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::sendMessageFail), 0, 0, _history->sendRequestId); App::main()->finishForwarding(_history); if (doc->sticker()) App::main()->incrementSticker(doc); App::historyRegRandom(randomId, newId); if (_fieldAutocomplete->stickersShown()) { clearFieldText(); //_saveDraftText = true; //_saveDraftStart = getms(); //onDraftSave(); onCloudDraftSave(); // won't be needed if SendInlineBotResult will clear the cloud draft } hideSelectorControlsAnimated(); _field->setFocus(); return true; } void HistoryWidget::sendExistingPhoto( PhotoData *photo, TextWithEntities caption) { if (!_peer || !_peer->canWrite() || !photo) { return; } auto options = ApiWrap::SendOptions(_history); options.clearDraft = false; options.replyTo = replyToId(); options.generateLocal = true; Auth().api().sendAction(options); uint64 randomId = rand_value<uint64>(); FullMsgId newId(_channel, clientMsgId()); auto flags = NewMessageFlags(_peer) | MTPDmessage::Flag::f_media; auto sendFlags = MTPmessages_SendMedia::Flags(0); if (options.replyTo) { flags |= MTPDmessage::Flag::f_reply_to_msg_id; sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to_msg_id; } bool channelPost = _peer->isChannel() && !_peer->isMegagroup(); bool silentPost = channelPost && _peer->notifySilentPosts(); if (channelPost) { flags |= MTPDmessage::Flag::f_views; flags |= MTPDmessage::Flag::f_post; } if (!channelPost) { flags |= MTPDmessage::Flag::f_from_id; } else if (_peer->asChannel()->addsSignature()) { flags |= MTPDmessage::Flag::f_post_author; } if (silentPost) { sendFlags |= MTPmessages_SendMedia::Flag::f_silent; } auto messageFromId = channelPost ? 0 : Auth().userId(); auto messagePostAuthor = channelPost ? (Auth().user()->firstName + ' ' + Auth().user()->lastName) : QString(); TextUtilities::Trim(caption); auto sentEntities = TextUtilities::EntitiesToMTP( caption.entities, TextUtilities::ConvertOption::SkipLocal); if (!sentEntities.v.isEmpty()) { sendFlags |= MTPmessages_SendMedia::Flag::f_entities; } _history->addNewPhoto( newId.msg, flags, 0, options.replyTo, unixtime(), messageFromId, messagePostAuthor, photo, caption, MTPnullMarkup); _history->sendRequestId = MTP::send( MTPmessages_SendMedia( MTP_flags(sendFlags), _peer->input, MTP_int(options.replyTo), MTP_inputMediaPhoto( MTP_flags(0), MTP_inputPhoto(MTP_long(photo->id), MTP_long(photo->access)), MTPint()), MTP_string(caption.text), MTP_long(randomId), MTPnullMarkup, sentEntities), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::sendMessageFail), 0, 0, _history->sendRequestId); App::main()->finishForwarding(_history); App::historyRegRandom(randomId, newId); hideSelectorControlsAnimated(); _field->setFocus(); } void HistoryWidget::setFieldText(const TextWithTags &textWithTags, TextUpdateEvents events, Ui::FlatTextarea::UndoHistoryAction undoHistoryAction) { _textUpdateEvents = events; _field->setTextWithTags(textWithTags, undoHistoryAction); _field->moveCursor(QTextCursor::End); _textUpdateEvents = TextUpdateEvent::SaveDraft | TextUpdateEvent::SendTyping; _previewCancelled = false; _previewData = nullptr; if (_previewRequest) { MTP::cancel(_previewRequest); _previewRequest = 0; } _previewLinks.clear(); } void HistoryWidget::replyToMessage(FullMsgId itemId) { if (const auto item = App::histItemById(itemId)) { replyToMessage(item); } } void HistoryWidget::replyToMessage(not_null<HistoryItem*> item) { if (!IsServerMsgId(item->id) || !_canSendMessages) { return; } if (item->history() == _migrated) { if (item->isGroupMigrate() && !_history->isEmpty() && _history->blocks.front()->messages.front()->data()->isGroupMigrate() && _history != _migrated) { replyToMessage(_history->blocks.front()->messages.front()->data()); } else { if (item->serviceMsg()) { Ui::show(Box<InformBox>(lang(lng_reply_cant))); } else { const auto itemId = item->fullId(); Ui::show(Box<ConfirmBox>(lang(lng_reply_cant_forward), lang(lng_selected_forward), base::lambda_guarded(this, [=] { App::main()->setForwardDraft( _peer->id, { 1, itemId }); }))); } } return; } App::main()->cancelForwarding(_history); if (_editMsgId) { if (auto localDraft = _history->localDraft()) { localDraft->msgId = item->id; } else { _history->setLocalDraft(std::make_unique<Data::Draft>( TextWithTags(), item->id, MessageCursor(), false)); } } else { _replyEditMsg = item; _replyToId = item->id; _replyEditMsgText.setText( st::messageTextStyle, TextUtilities::Clean(_replyEditMsg->inReplyText()), Ui::DialogTextOptions()); updateBotKeyboard(); if (!_field->isHidden()) _fieldBarCancel->show(); updateMouseTracking(); updateReplyToName(); updateControlsGeometry(); updateField(); } _saveDraftText = true; _saveDraftStart = getms(); onDraftSave(); _field->setFocus(); } void HistoryWidget::editMessage(FullMsgId itemId) { if (const auto item = App::histItemById(itemId)) { editMessage(item); } } void HistoryWidget::editMessage(not_null<HistoryItem*> item) { if (const auto media = item->media()) { if (media->allowsEditCaption()) { Ui::show(Box<EditCaptionBox>(item)); return; } } if (_recording) { // Just fix some strange inconsistency. _send->clearState(); } if (!_editMsgId) { if (_replyToId || !_field->isEmpty()) { _history->setLocalDraft(std::make_unique<Data::Draft>(_field, _replyToId, _previewCancelled)); } else { _history->clearLocalDraft(); } } const auto original = item->originalText(); const auto editData = TextWithTags { TextUtilities::ApplyEntities(original), ConvertEntitiesToTextTags(original.entities) }; const auto cursor = MessageCursor { editData.text.size(), editData.text.size(), QFIXED_MAX }; _history->setEditDraft(std::make_unique<Data::Draft>( editData, item->id, cursor, false)); applyDraft(false); _previewData = nullptr; if (const auto media = item->media()) { if (const auto page = media->webpage()) { _previewData = page; updatePreview(); } } if (!_previewData) { onPreviewParse(); } updateBotKeyboard(); if (!_field->isHidden()) _fieldBarCancel->show(); updateFieldPlaceholder(); updateMouseTracking(); updateReplyToName(); updateControlsGeometry(); updateField(); _saveDraftText = true; _saveDraftStart = getms(); onDraftSave(); _field->setFocus(); } void HistoryWidget::pinMessage(FullMsgId itemId) { if (const auto item = App::histItemById(itemId)) { if (item->canPin()) { const auto channel = item->history()->peer->asChannel(); Assert(channel != nullptr); Ui::show(Box<PinMessageBox>(channel, item->id)); } } } void HistoryWidget::unpinMessage(FullMsgId itemId) { const auto channel = _peer ? _peer->asChannel() : nullptr; if (!channel) { return; } Ui::show(Box<ConfirmBox>(lang(lng_pinned_unpin_sure), lang(lng_pinned_unpin), base::lambda_guarded(this, [=] { channel->clearPinnedMessage(); Ui::hideLayer(); MTP::send( MTPchannels_UpdatePinnedMessage( MTP_flags(0), channel->inputChannel, MTP_int(0)), rpcDone(&HistoryWidget::unpinDone)); }))); } void HistoryWidget::unpinDone(const MTPUpdates &updates) { if (App::main()) { App::main()->sentUpdatesReceived(updates); } } void HistoryWidget::onPinnedHide() { const auto channel = _peer ? _peer->asChannel() : nullptr; const auto pinnedId = channel ? channel->pinnedMessageId() : MsgId(0); if (!pinnedId) { if (pinnedMsgVisibilityUpdated()) { updateControlsGeometry(); update(); } return; } if (channel->canPinMessages()) { unpinMessage(FullMsgId(peerToChannel(channel->id), pinnedId)); } else { Global::RefHiddenPinnedMessages().insert(channel->id, pinnedId); Local::writeUserSettings(); if (pinnedMsgVisibilityUpdated()) { updateControlsGeometry(); update(); } } } void HistoryWidget::copyPostLink(FullMsgId itemId) { if (const auto item = App::histItemById(itemId)) { if (item->hasDirectLink()) { QApplication::clipboard()->setText(item->directLink()); } } } bool HistoryWidget::lastForceReplyReplied(const FullMsgId &replyTo) const { if (replyTo.channel != _channel) { return false; } return _keyboard->forceReply() && _keyboard->forMsgId() == FullMsgId(_channel, _history->lastKeyboardId) && _keyboard->forMsgId().msg == replyTo.msg; } bool HistoryWidget::lastForceReplyReplied() const { return _keyboard->forceReply() && _keyboard->forMsgId() == FullMsgId(_channel, _history->lastKeyboardId) && _keyboard->forMsgId().msg == replyToId(); } bool HistoryWidget::cancelReply(bool lastKeyboardUsed) { bool wasReply = false; if (_replyToId) { wasReply = true; _replyEditMsg = nullptr; _replyToId = 0; mouseMoveEvent(0); if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !_kbReplyTo) { _fieldBarCancel->hide(); updateMouseTracking(); } updateBotKeyboard(); updateControlsGeometry(); update(); } else if (auto localDraft = (_history ? _history->localDraft() : nullptr)) { if (localDraft->msgId) { if (localDraft->textWithTags.text.isEmpty()) { _history->clearLocalDraft(); } else { localDraft->msgId = 0; } } } if (wasReply) { _saveDraftText = true; _saveDraftStart = getms(); onDraftSave(); } if (!_editMsgId && _keyboard->singleUse() && _keyboard->forceReply() && lastKeyboardUsed) { if (_kbReplyTo) { onKbToggle(false); } } return wasReply; } void HistoryWidget::cancelReplyAfterMediaSend(bool lastKeyboardUsed) { if (cancelReply(lastKeyboardUsed)) { onCloudDraftSave(); } } int HistoryWidget::countMembersDropdownHeightMax() const { int result = height() - st::membersInnerDropdown.padding.top() - st::membersInnerDropdown.padding.bottom(); result -= _tabbedSelectorToggle->height(); accumulate_min(result, st::membersInnerHeightMax); return result; } void HistoryWidget::cancelEdit() { if (!_editMsgId) return; _replyEditMsg = nullptr; _editMsgId = 0; _history->clearEditDraft(); applyDraft(); if (_saveEditMsgRequestId) { MTP::cancel(_saveEditMsgRequestId); _saveEditMsgRequestId = 0; } _saveDraftText = true; _saveDraftStart = getms(); onDraftSave(); mouseMoveEvent(nullptr); if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !replyToId()) { _fieldBarCancel->hide(); updateMouseTracking(); } auto old = _textUpdateEvents; _textUpdateEvents = 0; onTextChange(); _textUpdateEvents = old; if (!canWriteMessage()) { updateControlsVisibility(); } updateBotKeyboard(); updateFieldPlaceholder(); updateControlsGeometry(); update(); } void HistoryWidget::onFieldBarCancel() { Ui::hideLayer(); _replyForwardPressed = false; if (_previewData && _previewData->pendingTill >= 0) { _previewCancelled = true; previewCancel(); _saveDraftText = true; _saveDraftStart = getms(); onDraftSave(); } else if (_editMsgId) { cancelEdit(); } else if (readyToForward()) { App::main()->cancelForwarding(_history); } else if (_replyToId) { cancelReply(); } else if (_kbReplyTo) { onKbToggle(); } } void HistoryWidget::previewCancel() { MTP::cancel(base::take(_previewRequest)); _previewData = nullptr; _previewLinks.clear(); updatePreview(); if (!_editMsgId && !_replyToId && !readyToForward() && !_kbReplyTo) { _fieldBarCancel->hide(); updateMouseTracking(); } } void HistoryWidget::onPreviewParse() { if (_previewCancelled) return; _field->parseLinks(); } void HistoryWidget::onPreviewCheck() { auto previewRestricted = [this] { if (auto megagroup = _peer ? _peer->asMegagroup() : nullptr) { if (megagroup->restricted(ChannelRestriction::f_embed_links)) { return true; } } return false; }; if (_previewCancelled || previewRestricted()) { MTP::cancel(base::take(_previewRequest)); _previewData = nullptr; _previewLinks.clear(); update(); return; } auto linksList = _field->linksList(); auto newLinks = linksList.join(' '); if (newLinks != _previewLinks) { MTP::cancel(base::take(_previewRequest)); _previewLinks = newLinks; if (_previewLinks.isEmpty()) { if (_previewData && _previewData->pendingTill >= 0) previewCancel(); } else { PreviewCache::const_iterator i = _previewCache.constFind(_previewLinks); if (i == _previewCache.cend()) { _previewRequest = MTP::send( MTPmessages_GetWebPagePreview( MTP_flags(0), MTP_string(_previewLinks), MTPnullEntities), rpcDone(&HistoryWidget::gotPreview, _previewLinks)); } else if (i.value()) { _previewData = Auth().data().webpage(i.value()); updatePreview(); } else { if (_previewData && _previewData->pendingTill >= 0) previewCancel(); } } } } void HistoryWidget::onPreviewTimeout() { if (_previewData && (_previewData->pendingTill > 0) && !_previewLinks.isEmpty()) { _previewRequest = MTP::send( MTPmessages_GetWebPagePreview( MTP_flags(0), MTP_string(_previewLinks), MTPnullEntities), rpcDone(&HistoryWidget::gotPreview, _previewLinks)); } } void HistoryWidget::gotPreview(QString links, const MTPMessageMedia &result, mtpRequestId req) { if (req == _previewRequest) { _previewRequest = 0; } if (result.type() == mtpc_messageMediaWebPage) { const auto &data = result.c_messageMediaWebPage().vwebpage; const auto page = Auth().data().webpage(data); _previewCache.insert(links, page->id); if (page->pendingTill > 0 && page->pendingTill <= unixtime()) { page->pendingTill = -1; } if (links == _previewLinks && !_previewCancelled) { _previewData = (page->id && page->pendingTill >= 0) ? page.get() : nullptr; updatePreview(); } Auth().data().sendWebPageGameNotifications(); } else if (result.type() == mtpc_messageMediaEmpty) { _previewCache.insert(links, 0); if (links == _previewLinks && !_previewCancelled) { _previewData = 0; updatePreview(); } } } void HistoryWidget::updatePreview() { _previewTimer.stop(); if (_previewData && _previewData->pendingTill >= 0) { _fieldBarCancel->show(); updateMouseTracking(); if (_previewData->pendingTill) { _previewTitle.setText( st::msgNameStyle, lang(lng_preview_loading), Ui::NameTextOptions()); #ifndef OS_MAC_OLD auto linkText = _previewLinks.splitRef(' ').at(0).toString(); #else // OS_MAC_OLD auto linkText = _previewLinks.split(' ').at(0); #endif // OS_MAC_OLD _previewDescription.setText( st::messageTextStyle, TextUtilities::Clean(linkText), Ui::DialogTextOptions()); int32 t = (_previewData->pendingTill - unixtime()) * 1000; if (t <= 0) t = 1; _previewTimer.start(t); } else { QString title, desc; if (_previewData->siteName.isEmpty()) { if (_previewData->title.isEmpty()) { if (_previewData->description.text.isEmpty()) { title = _previewData->author; desc = ((_previewData->document && !_previewData->document->filename().isEmpty()) ? _previewData->document->filename() : _previewData->url); } else { title = _previewData->description.text; desc = _previewData->author.isEmpty() ? ((_previewData->document && !_previewData->document->filename().isEmpty()) ? _previewData->document->filename() : _previewData->url) : _previewData->author; } } else { title = _previewData->title; desc = _previewData->description.text.isEmpty() ? (_previewData->author.isEmpty() ? ((_previewData->document && !_previewData->document->filename().isEmpty()) ? _previewData->document->filename() : _previewData->url) : _previewData->author) : _previewData->description.text; } } else { title = _previewData->siteName; desc = _previewData->title.isEmpty() ? (_previewData->description.text.isEmpty() ? (_previewData->author.isEmpty() ? ((_previewData->document && !_previewData->document->filename().isEmpty()) ? _previewData->document->filename() : _previewData->url) : _previewData->author) : _previewData->description.text) : _previewData->title; } if (title.isEmpty()) { if (_previewData->document) { title = lang(lng_attach_file); } else if (_previewData->photo) { title = lang(lng_attach_photo); } } _previewTitle.setText( st::msgNameStyle, title, Ui::NameTextOptions()); _previewDescription.setText( st::messageTextStyle, TextUtilities::Clean(desc), Ui::DialogTextOptions()); } } else if (!readyToForward() && !replyToId() && !_editMsgId) { _fieldBarCancel->hide(); updateMouseTracking(); } updateControlsGeometry(); update(); } void HistoryWidget::onCancel() { if (_isInlineBot) { onInlineBotCancel(); } else if (_editMsgId) { auto original = _replyEditMsg ? _replyEditMsg->originalText() : TextWithEntities(); auto editData = TextWithTags { TextUtilities::ApplyEntities(original), ConvertEntitiesToTextTags(original.entities) }; if (_replyEditMsg && editData != _field->getTextWithTags()) { Ui::show(Box<ConfirmBox>( lang(lng_cancel_edit_post_sure), lang(lng_cancel_edit_post_yes), lang(lng_cancel_edit_post_no), base::lambda_guarded(this, [this] { onFieldBarCancel(); }))); } else { onFieldBarCancel(); } } else if (!_fieldAutocomplete->isHidden()) { _fieldAutocomplete->hideAnimated(); } else if (_replyToId && _field->getTextWithTags().text.isEmpty()) { cancelReply(); } else { controller()->showBackFromStack(); emit cancelled(); } } void HistoryWidget::fullPeerUpdated(PeerData *peer) { auto refresh = false; if (_list && peer == _peer) { auto newCanSendMessages = _peer->canWrite(); if (newCanSendMessages != _canSendMessages) { _canSendMessages = newCanSendMessages; if (!_canSendMessages) { cancelReply(); } refreshSilentToggle(); refresh = true; } onCheckFieldAutocomplete(); updateReportSpamStatus(); _list->updateBotInfo(); handlePeerUpdate(); } if (updateCmdStartShown()) { refresh = true; } else if (!_scroll->isHidden() && _unblock->isHidden() == isBlocked()) { refresh = true; } if (refresh) { updateControlsVisibility(); updateControlsGeometry(); } } void HistoryWidget::handlePeerUpdate() { bool resize = false; updateHistoryGeometry(); if (_peer->isChannel()) updateReportSpamStatus(); if (_peer->isChat() && _peer->asChat()->noParticipantInfo()) { Auth().api().requestFullPeer(_peer); } else if (_peer->isUser() && (_peer->asUser()->blockStatus() == UserData::BlockStatus::Unknown || _peer->asUser()->callsStatus() == UserData::CallsStatus::Unknown)) { Auth().api().requestFullPeer(_peer); } else if (auto channel = _peer->asMegagroup()) { if (!channel->mgInfo->botStatus) { Auth().api().requestBots(channel); } if (channel->mgInfo->admins.empty()) { Auth().api().requestAdmins(channel); } } if (!_a_show.animating()) { if (_unblock->isHidden() == isBlocked() || (!isBlocked() && _joinChannel->isHidden() == isJoinChannel())) { resize = true; } bool newCanSendMessages = _peer->canWrite(); if (newCanSendMessages != _canSendMessages) { _canSendMessages = newCanSendMessages; if (!_canSendMessages) { cancelReply(); } refreshSilentToggle(); resize = true; } updateControlsVisibility(); if (resize) { updateControlsGeometry(); } } } void HistoryWidget::forwardSelected() { if (!_list) { return; } const auto weak = make_weak(this); Window::ShowForwardMessagesBox(getSelectedItems(), [=] { if (const auto strong = weak.data()) { strong->clearSelected(); } }); } void HistoryWidget::confirmDeleteSelected() { if (!_list) return; auto items = _list->getSelectedItems(); if (items.empty()) { return; } const auto weak = make_weak(this); const auto box = Ui::show(Box<DeleteMessagesBox>(std::move(items))); box->setDeleteConfirmedCallback([=] { if (const auto strong = weak.data()) { strong->clearSelected(); } }); } void HistoryWidget::onListEscapePressed() { if (_nonEmptySelection && _list) { clearSelected(); } else { onCancel(); } } void HistoryWidget::onListEnterPressed() { if (!_botStart->isHidden()) { onBotStart(); } } void HistoryWidget::clearSelected() { if (_list) { _list->clearSelected(); } } HistoryItem *HistoryWidget::getItemFromHistoryOrMigrated(MsgId genericMsgId) const { if (genericMsgId < 0 && -genericMsgId < ServerMaxMsgId && _migrated) { return App::histItemById(_migrated->channelId(), -genericMsgId); } return App::histItemById(_channel, genericMsgId); } MessageIdsList HistoryWidget::getSelectedItems() const { return _list ? _list->getSelectedItems() : MessageIdsList(); } void HistoryWidget::updateTopBarSelection() { if (!_list) { _topBar->showSelected(HistoryView::TopBarWidget::SelectedState {}); return; } auto selectedState = _list->getSelectionState(); _nonEmptySelection = (selectedState.count > 0) || selectedState.textSelected; _topBar->showSelected(selectedState); updateControlsVisibility(); updateHistoryGeometry(); if (!Ui::isLayerShown() && !App::passcoded()) { if (_nonEmptySelection || (_list && _list->wasSelectedText()) || _recording || isBotStart() || isBlocked() || !_canSendMessages) { _list->setFocus(); } else { _field->setFocus(); } } _topBar->update(); update(); } void HistoryWidget::messageDataReceived(ChannelData *channel, MsgId msgId) { if (!_peer || _peer->asChannel() != channel || !msgId) return; if (_editMsgId == msgId || _replyToId == msgId) { updateReplyEditTexts(true); } if (_pinnedBar && _pinnedBar->msgId == msgId) { updatePinnedBar(true); } } void HistoryWidget::updateReplyEditTexts(bool force) { if (!force) { if (_replyEditMsg || (!_editMsgId && !_replyToId)) { return; } } if (!_replyEditMsg) { _replyEditMsg = App::histItemById(_channel, _editMsgId ? _editMsgId : _replyToId); } if (_replyEditMsg) { _replyEditMsgText.setText( st::messageTextStyle, TextUtilities::Clean(_replyEditMsg->inReplyText()), Ui::DialogTextOptions()); updateBotKeyboard(); if (!_field->isHidden() || _recording) { _fieldBarCancel->show(); updateMouseTracking(); } updateReplyToName(); updateField(); } else if (force) { if (_editMsgId) { cancelEdit(); } else { cancelReply(); } } } void HistoryWidget::updateForwarding() { if (_history) { _toForward = _history->validateForwardDraft(); updateForwardingTexts(); } else { _toForward.clear(); } updateControlsVisibility(); updateControlsGeometry(); } void HistoryWidget::updateForwardingTexts() { int32 version = 0; QString from, text; if (const auto count = int(_toForward.size())) { QMap<PeerData*, bool> fromUsersMap; QVector<PeerData*> fromUsers; fromUsers.reserve(_toForward.size()); for (const auto item : _toForward) { const auto from = item->senderOriginal(); if (!fromUsersMap.contains(from)) { fromUsersMap.insert(from, true); fromUsers.push_back(from); } version += from->nameVersion; } if (fromUsers.size() > 2) { from = lng_forwarding_from(lt_count, fromUsers.size() - 1, lt_user, fromUsers.at(0)->shortName()); } else if (fromUsers.size() < 2) { from = fromUsers.at(0)->name; } else { from = lng_forwarding_from_two(lt_user, fromUsers.at(0)->shortName(), lt_second_user, fromUsers.at(1)->shortName()); } if (count < 2) { text = _toForward.front()->inReplyText(); } else { text = lng_forward_messages(lt_count, count); } } _toForwardFrom.setText(st::msgNameStyle, from, Ui::NameTextOptions()); _toForwardText.setText( st::messageTextStyle, TextUtilities::Clean(text), Ui::DialogTextOptions()); _toForwardNameVersion = version; } void HistoryWidget::checkForwardingInfo() { if (!_toForward.empty()) { auto version = 0; for (const auto item : _toForward) { version += item->senderOriginal()->nameVersion; } if (version != _toForwardNameVersion) { updateForwardingTexts(); } } } void HistoryWidget::updateReplyToName() { if (_editMsgId) return; if (!_replyEditMsg && (_replyToId || !_kbReplyTo)) return; _replyToName.setText( st::msgNameStyle, App::peerName((_replyEditMsg ? _replyEditMsg : _kbReplyTo)->author()), Ui::NameTextOptions()); _replyToNameVersion = (_replyEditMsg ? _replyEditMsg : _kbReplyTo)->author()->nameVersion; } void HistoryWidget::updateField() { auto fieldAreaTop = _scroll->y() + _scroll->height(); rtlupdate(0, fieldAreaTop, width(), height() - fieldAreaTop); } void HistoryWidget::drawField(Painter &p, const QRect &rect) { auto backy = _field->y() - st::historySendPadding; auto backh = _field->height() + 2 * st::historySendPadding; auto hasForward = readyToForward(); auto drawMsgText = (_editMsgId || _replyToId) ? _replyEditMsg : _kbReplyTo; if (_editMsgId || _replyToId || (!hasForward && _kbReplyTo)) { if (!_editMsgId && drawMsgText && drawMsgText->author()->nameVersion > _replyToNameVersion) { updateReplyToName(); } backy -= st::historyReplyHeight; backh += st::historyReplyHeight; } else if (hasForward) { checkForwardingInfo(); backy -= st::historyReplyHeight; backh += st::historyReplyHeight; } else if (_previewData && _previewData->pendingTill >= 0) { backy -= st::historyReplyHeight; backh += st::historyReplyHeight; } auto drawWebPagePreview = (_previewData && _previewData->pendingTill >= 0) && !_replyForwardPressed; p.fillRect(myrtlrect(0, backy, width(), backh), st::historyReplyBg); if (_editMsgId || _replyToId || (!hasForward && _kbReplyTo)) { auto replyLeft = st::historyReplySkip; (_editMsgId ? st::historyEditIcon : st::historyReplyIcon).paint(p, st::historyReplyIconPosition + QPoint(0, backy), width()); if (!drawWebPagePreview) { if (drawMsgText) { if (drawMsgText->media() && drawMsgText->media()->hasReplyPreview()) { auto replyPreview = drawMsgText->media()->replyPreview(); if (!replyPreview->isNull()) { auto to = QRect(replyLeft, backy + st::msgReplyPadding.top(), st::msgReplyBarSize.height(), st::msgReplyBarSize.height()); p.drawPixmap(to.x(), to.y(), replyPreview->pixSingle(replyPreview->width() / cIntRetinaFactor(), replyPreview->height() / cIntRetinaFactor(), to.width(), to.height(), ImageRoundRadius::Small)); } replyLeft += st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x(); } p.setPen(st::historyReplyNameFg); if (_editMsgId) { paintEditHeader(p, rect, replyLeft, backy); } else { _replyToName.drawElided(p, replyLeft, backy + st::msgReplyPadding.top(), width() - replyLeft - _fieldBarCancel->width() - st::msgReplyPadding.right()); } p.setPen(!drawMsgText->toHistoryMessage() ? st::historyComposeAreaFgService : st::historyComposeAreaFg); _replyEditMsgText.drawElided(p, replyLeft, backy + st::msgReplyPadding.top() + st::msgServiceNameFont->height, width() - replyLeft - _fieldBarCancel->width() - st::msgReplyPadding.right()); } else { p.setFont(st::msgDateFont); p.setPen(st::historyComposeAreaFgService); p.drawText(replyLeft, backy + st::msgReplyPadding.top() + (st::msgReplyBarSize.height() - st::msgDateFont->height) / 2 + st::msgDateFont->ascent, st::msgDateFont->elided(lang(lng_profile_loading), width() - replyLeft - _fieldBarCancel->width() - st::msgReplyPadding.right())); } } } else if (hasForward) { auto forwardLeft = st::historyReplySkip; st::historyForwardIcon.paint(p, st::historyReplyIconPosition + QPoint(0, backy), width()); if (!drawWebPagePreview) { const auto firstItem = _toForward.front(); const auto firstMedia = firstItem->media(); const auto serviceColor = (_toForward.size() > 1) || (firstMedia != nullptr) || firstItem->serviceMsg(); const auto preview = (_toForward.size() < 2 && firstMedia && firstMedia->hasReplyPreview()) ? firstMedia->replyPreview() : ImagePtr(); if (!preview->isNull()) { auto to = QRect(forwardLeft, backy + st::msgReplyPadding.top(), st::msgReplyBarSize.height(), st::msgReplyBarSize.height()); if (preview->width() == preview->height()) { p.drawPixmap(to.x(), to.y(), preview->pix()); } else { auto from = (preview->width() > preview->height()) ? QRect((preview->width() - preview->height()) / 2, 0, preview->height(), preview->height()) : QRect(0, (preview->height() - preview->width()) / 2, preview->width(), preview->width()); p.drawPixmap(to, preview->pix(), from); } forwardLeft += st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x(); } p.setPen(st::historyReplyNameFg); _toForwardFrom.drawElided(p, forwardLeft, backy + st::msgReplyPadding.top(), width() - forwardLeft - _fieldBarCancel->width() - st::msgReplyPadding.right()); p.setPen(serviceColor ? st::historyComposeAreaFgService : st::historyComposeAreaFg); _toForwardText.drawElided(p, forwardLeft, backy + st::msgReplyPadding.top() + st::msgServiceNameFont->height, width() - forwardLeft - _fieldBarCancel->width() - st::msgReplyPadding.right()); } } if (drawWebPagePreview) { auto previewLeft = st::historyReplySkip + st::webPageLeft; p.fillRect(st::historyReplySkip, backy + st::msgReplyPadding.top(), st::webPageBar, st::msgReplyBarSize.height(), st::msgInReplyBarColor); if ((_previewData->photo && !_previewData->photo->thumb->isNull()) || (_previewData->document && !_previewData->document->thumb->isNull())) { auto replyPreview = _previewData->photo ? _previewData->photo->makeReplyPreview() : _previewData->document->makeReplyPreview(); if (!replyPreview->isNull()) { auto to = QRect(previewLeft, backy + st::msgReplyPadding.top(), st::msgReplyBarSize.height(), st::msgReplyBarSize.height()); if (replyPreview->width() == replyPreview->height()) { p.drawPixmap(to.x(), to.y(), replyPreview->pix()); } else { auto from = (replyPreview->width() > replyPreview->height()) ? QRect((replyPreview->width() - replyPreview->height()) / 2, 0, replyPreview->height(), replyPreview->height()) : QRect(0, (replyPreview->height() - replyPreview->width()) / 2, replyPreview->width(), replyPreview->width()); p.drawPixmap(to, replyPreview->pix(), from); } } previewLeft += st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x(); } p.setPen(st::historyReplyNameFg); _previewTitle.drawElided(p, previewLeft, backy + st::msgReplyPadding.top(), width() - previewLeft - _fieldBarCancel->width() - st::msgReplyPadding.right()); p.setPen(st::historyComposeAreaFg); _previewDescription.drawElided(p, previewLeft, backy + st::msgReplyPadding.top() + st::msgServiceNameFont->height, width() - previewLeft - _fieldBarCancel->width() - st::msgReplyPadding.right()); } } void HistoryWidget::drawRestrictedWrite(Painter &p) { auto rect = myrtlrect(0, height() - _unblock->height(), width(), _unblock->height()); p.fillRect(rect, st::historyReplyBg); p.setFont(st::normalFont); p.setPen(st::windowSubTextFg); p.drawText(rect.marginsRemoved(QMargins(st::historySendPadding, 0, st::historySendPadding, 0)), lang(lng_restricted_send_message), style::al_center); } void HistoryWidget::paintEditHeader(Painter &p, const QRect &rect, int left, int top) const { if (!rect.intersects(myrtlrect(left, top, width() - left, st::normalFont->height))) { return; } p.setFont(st::msgServiceNameFont); p.drawTextLeft(left, top + st::msgReplyPadding.top(), width(), lang(lng_edit_message)); if (!_replyEditMsg || _replyEditMsg->history()->peer->isSelf()) return; QString editTimeLeftText; int updateIn = -1; auto timeSinceMessage = ItemDateTime(_replyEditMsg).msecsTo(QDateTime::currentDateTime()); auto editTimeLeft = (Global::EditTimeLimit() * 1000LL) - timeSinceMessage; if (editTimeLeft < 2) { editTimeLeftText = qsl("0:00"); } else if (editTimeLeft > kDisplayEditTimeWarningMs) { updateIn = static_cast<int>(qMin(editTimeLeft - kDisplayEditTimeWarningMs, qint64(kFullDayInMs))); } else { updateIn = static_cast<int>(editTimeLeft % 1000); if (!updateIn) { updateIn = 1000; } ++updateIn; editTimeLeft = (editTimeLeft - 1) / 1000; // seconds editTimeLeftText = qsl("%1:%2").arg(editTimeLeft / 60).arg(editTimeLeft % 60, 2, 10, QChar('0')); } // Restart timer only if we are sure that we've painted the whole timer. if (rect.contains(myrtlrect(left, top, width() - left, st::normalFont->height)) && updateIn > 0) { _updateEditTimeLeftDisplay.start(updateIn); } if (!editTimeLeftText.isEmpty()) { p.setFont(st::normalFont); p.setPen(st::historyComposeAreaFgService); p.drawText(left + st::msgServiceNameFont->width(lang(lng_edit_message)) + st::normalFont->spacew, top + st::msgReplyPadding.top() + st::msgServiceNameFont->ascent, editTimeLeftText); } } void HistoryWidget::drawRecording(Painter &p, float64 recordActive) { p.setPen(Qt::NoPen); p.setBrush(st::historyRecordSignalColor); auto delta = qMin(a_recordingLevel.current() / 0x4000, 1.); auto d = 2 * qRound(st::historyRecordSignalMin + (delta * (st::historyRecordSignalMax - st::historyRecordSignalMin))); { PainterHighQualityEnabler hq(p); p.drawEllipse(_attachToggle->x() + (_tabbedSelectorToggle->width() - d) / 2, _attachToggle->y() + (_attachToggle->height() - d) / 2, d, d); } auto duration = formatDurationText(_recordingSamples / Media::Player::kDefaultFrequency); p.setFont(st::historyRecordFont); p.setPen(st::historyRecordDurationFg); p.drawText(_attachToggle->x() + _tabbedSelectorToggle->width(), _attachToggle->y() + st::historyRecordTextTop + st::historyRecordFont->ascent, duration); int32 left = _attachToggle->x() + _tabbedSelectorToggle->width() + st::historyRecordFont->width(duration) + ((_send->width() - st::historyRecordVoice.width()) / 2); int32 right = width() - _send->width(); p.setPen(anim::pen(st::historyRecordCancel, st::historyRecordCancelActive, 1. - recordActive)); p.drawText(left + (right - left - _recordCancelWidth) / 2, _attachToggle->y() + st::historyRecordTextTop + st::historyRecordFont->ascent, lang(lng_record_cancel)); } void HistoryWidget::drawPinnedBar(Painter &p) { Expects(_pinnedBar != nullptr); auto top = _topBar->bottomNoMargins(); Text *from = 0, *text = 0; bool serviceColor = false, hasForward = readyToForward(); ImagePtr preview; p.fillRect(myrtlrect(0, top, width(), st::historyReplyHeight), st::historyPinnedBg); top += st::msgReplyPadding.top(); QRect rbar(myrtlrect(st::msgReplyBarSkip + st::msgReplyBarPos.x(), top + st::msgReplyBarPos.y(), st::msgReplyBarSize.width(), st::msgReplyBarSize.height())); p.fillRect(rbar, st::msgInReplyBarColor); int32 left = st::msgReplyBarSkip + st::msgReplyBarSkip; if (_pinnedBar->msg) { if (_pinnedBar->msg->media() && _pinnedBar->msg->media()->hasReplyPreview()) { ImagePtr replyPreview = _pinnedBar->msg->media()->replyPreview(); if (!replyPreview->isNull()) { QRect to(left, top, st::msgReplyBarSize.height(), st::msgReplyBarSize.height()); p.drawPixmap(to.x(), to.y(), replyPreview->pixSingle(replyPreview->width() / cIntRetinaFactor(), replyPreview->height() / cIntRetinaFactor(), to.width(), to.height(), ImageRoundRadius::Small)); } left += st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x(); } p.setPen(st::historyReplyNameFg); p.setFont(st::msgServiceNameFont); p.drawText(left, top + st::msgServiceNameFont->ascent, lang(lng_pinned_message)); p.setPen(!_pinnedBar->msg->toHistoryMessage() ? st::historyComposeAreaFgService : st::historyComposeAreaFg); _pinnedBar->text.drawElided(p, left, top + st::msgServiceNameFont->height, width() - left - _pinnedBar->cancel->width() - st::msgReplyPadding.right()); } else { p.setFont(st::msgDateFont); p.setPen(st::historyComposeAreaFgService); p.drawText(left, top + (st::msgReplyBarSize.height() - st::msgDateFont->height) / 2 + st::msgDateFont->ascent, st::msgDateFont->elided(lang(lng_profile_loading), width() - left - _pinnedBar->cancel->width() - st::msgReplyPadding.right())); } } bool HistoryWidget::paintShowAnimationFrame(TimeMs ms) { auto progress = _a_show.current(ms, 1.); if (!_a_show.animating()) { return false; } Painter p(this); auto animationWidth = width(); auto retina = cIntRetinaFactor(); auto fromLeft = (_showDirection == Window::SlideDirection::FromLeft); auto coordUnder = fromLeft ? anim::interpolate(-st::slideShift, 0, progress) : anim::interpolate(0, -st::slideShift, progress); auto coordOver = fromLeft ? anim::interpolate(0, animationWidth, progress) : anim::interpolate(animationWidth, 0, progress); auto shadow = fromLeft ? (1. - progress) : progress; if (coordOver > 0) { p.drawPixmap(QRect(0, 0, coordOver, height()), _cacheUnder, QRect(-coordUnder * retina, 0, coordOver * retina, height() * retina)); p.setOpacity(shadow); p.fillRect(0, 0, coordOver, height(), st::slideFadeOutBg); p.setOpacity(1); } p.drawPixmap(QRect(coordOver, 0, _cacheOver.width() / retina, height()), _cacheOver, QRect(0, 0, _cacheOver.width(), height() * retina)); p.setOpacity(shadow); st::slideShadow.fill(p, QRect(coordOver - st::slideShadow.width(), 0, st::slideShadow.width(), height())); return true; } void HistoryWidget::paintEvent(QPaintEvent *e) { auto ms = getms(); _historyDownShown.step(ms); _unreadMentionsShown.step(ms); if (paintShowAnimationFrame(ms)) { return; } if (Ui::skipPaintEvent(this, e)) { return; } if (hasPendingResizedItems()) { updateListSize(); } Window::SectionWidget::PaintBackground(this, e); Painter p(this); const auto clip = e->rect(); if (_list) { if (!_field->isHidden() || _recording) { drawField(p, clip); if (!_send->isHidden() && _recording) { drawRecording(p, _send->recordActiveRatio()); } } else if (isRestrictedWrite()) { drawRestrictedWrite(p); } if (_pinnedBar && !_pinnedBar->cancel->isHidden()) { drawPinnedBar(p); } if (_scroll->isHidden()) { p.setClipRect(_scroll->geometry()); HistoryView::paintEmpty(p, width(), height() - _field->height() - 2 * st::historySendPadding); } } else { const auto w = st::msgServiceFont->width(lang(lng_willbe_history)) + st::msgPadding.left() + st::msgPadding.right(); const auto h = st::msgServiceFont->height + st::msgServicePadding.top() + st::msgServicePadding.bottom(); const auto tr = QRect( (width() - w) / 2, st::msgServiceMargin.top() + (height() - _field->height() - 2 * st::historySendPadding - h - st::msgServiceMargin.top() - st::msgServiceMargin.bottom()) / 2, w, h); HistoryView::ServiceMessagePainter::paintBubble(p, tr.x(), tr.y(), tr.width(), tr.height()); p.setPen(st::msgServiceFg); p.setFont(st::msgServiceFont->f); p.drawTextLeft(tr.left() + st::msgPadding.left(), tr.top() + st::msgServicePadding.top(), width(), lang(lng_willbe_history)); } } QRect HistoryWidget::historyRect() const { return _scroll->geometry(); } void HistoryWidget::destroyData() { showHistory(0, 0); } QPoint HistoryWidget::clampMousePosition(QPoint point) { if (point.x() < 0) { point.setX(0); } else if (point.x() >= _scroll->width()) { point.setX(_scroll->width() - 1); } if (point.y() < _scroll->scrollTop()) { point.setY(_scroll->scrollTop()); } else if (point.y() >= _scroll->scrollTop() + _scroll->height()) { point.setY(_scroll->scrollTop() + _scroll->height() - 1); } return point; } void HistoryWidget::onScrollTimer() { auto d = (_scrollDelta > 0) ? qMin(_scrollDelta * 3 / 20 + 1, int32(MaxScrollSpeed)) : qMax(_scrollDelta * 3 / 20 - 1, -int32(MaxScrollSpeed)); _scroll->scrollToY(_scroll->scrollTop() + d); } void HistoryWidget::checkSelectingScroll(QPoint point) { if (point.y() < _scroll->scrollTop()) { _scrollDelta = point.y() - _scroll->scrollTop(); } else if (point.y() >= _scroll->scrollTop() + _scroll->height()) { _scrollDelta = point.y() - _scroll->scrollTop() - _scroll->height() + 1; } else { _scrollDelta = 0; } if (_scrollDelta) { _scrollTimer.start(15); } else { _scrollTimer.stop(); } } void HistoryWidget::noSelectingScroll() { _scrollTimer.stop(); } bool HistoryWidget::touchScroll(const QPoint &delta) { int32 scTop = _scroll->scrollTop(), scMax = _scroll->scrollTopMax(), scNew = snap(scTop - delta.y(), 0, scMax); if (scNew == scTop) return false; _scroll->scrollToY(scNew); return true; } void HistoryWidget::synteticScrollToY(int y) { _synteticScrollEvent = true; if (_scroll->scrollTop() == y) { visibleAreaUpdated(); } else { _scroll->scrollToY(y); } _synteticScrollEvent = false; } HistoryWidget::~HistoryWidget() = default; ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/history_widget.h���������������������������������������0000664�0000000�0000000�00000060456�13262451251�0024354�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/widgets/tooltip.h" #include "mainwidget.h" #include "chat_helpers/field_autocomplete.h" #include "window/section_widget.h" #include "core/single_timer.h" #include "ui/widgets/input_fields.h" #include "ui/rp_widget.h" #include "base/flags.h" #include "base/timer.h" struct FileLoadResult; struct FileMediaInformation; struct SendingAlbum; enum class SendMediaType; enum class CompressConfirm; namespace InlineBots { namespace Layout { class ItemBase; class Widget; } // namespace Layout class Result; } // namespace InlineBots namespace Data { struct Draft; } // namespace Data namespace Ui { class AbstractButton; class InnerDropdown; class DropdownMenu; class PlainShadow; class PopupMenu; class IconButton; class HistoryDownButton; class EmojiButton; class SendButton; class SilentToggle; class FlatButton; class LinkButton; class RoundButton; namespace Emoji { class SuggestionsController; } // namespace Emoji } // namespace Ui namespace Window { class Controller; } // namespace Window namespace ChatHelpers { class TabbedPanel; class TabbedSection; class TabbedSelector; } // namespace ChatHelpers namespace Storage { enum class MimeDataState; struct PreparedList; } // namespace Storage namespace HistoryView { class TopBarWidget; } // namespace HistoryView class DragArea; class SendFilesBox; class BotKeyboard; class MessageField; class HistoryInner; struct HistoryMessageMarkupButton; class ReportSpamPanel : public TWidget { Q_OBJECT public: ReportSpamPanel(QWidget *parent); void setReported(bool reported, PeerData *onPeer); signals: void hideClicked(); void reportClicked(); void clearClicked(); protected: void resizeEvent(QResizeEvent *e) override; void paintEvent(QPaintEvent *e) override; private: object_ptr<Ui::FlatButton> _report; object_ptr<Ui::FlatButton> _hide; object_ptr<Ui::LinkButton> _clear; }; class HistoryHider : public Ui::RpWidget, private base::Subscriber { Q_OBJECT public: HistoryHider(MainWidget *parent, MessageIdsList &&items); // forward messages HistoryHider(MainWidget *parent); // send path from command line argument HistoryHider(MainWidget *parent, const QString &url, const QString &text); // share url HistoryHider(MainWidget *parent, const QString &botAndQuery); // inline switch button handler bool withConfirm() const; bool offerPeer(PeerId peer); QString offeredText() const; QString botAndQuery() const { return _botAndQuery; } bool wasOffered() const; void forwardDone(); ~HistoryHider(); protected: void paintEvent(QPaintEvent *e) override; void keyPressEvent(QKeyEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void resizeEvent(QResizeEvent *e) override; public slots: void startHide(); void forward(); signals: void forwarded(); private: void refreshLang(); void updateControlsGeometry(); void animationCallback(); void init(); MainWidget *parent(); MessageIdsList _forwardItems; bool _sendPath = false; QString _shareUrl, _shareText; QString _botAndQuery; object_ptr<Ui::RoundButton> _send; object_ptr<Ui::RoundButton> _cancel; PeerData *_offered = nullptr; Animation _a_opacity; QRect _box; bool _hiding = false; mtpRequestId _forwardRequest = 0; int _chooseWidth = 0; Text _toText; int32 _toTextWidth = 0; QPixmap _cacheForAnim; }; class HistoryWidget final : public Window::AbstractSectionWidget, public RPCSender { Q_OBJECT public: HistoryWidget(QWidget *parent, not_null<Window::Controller*> controller); void start(); void messagesReceived(PeerData *peer, const MTPmessages_Messages &messages, mtpRequestId requestId); void historyLoaded(); void windowShown(); bool doWeReadServerHistory() const; bool doWeReadMentions() const; bool skipItemRepaint(); void leaveToChildEvent(QEvent *e, QWidget *child) override; void dragEnterEvent(QDragEnterEvent *e) override; void dragLeaveEvent(QDragLeaveEvent *e) override; void dropEvent(QDropEvent *e) override; bool isItemCompletelyHidden(HistoryItem *item) const; void updateTopBarSelection(); void loadMessages(); void loadMessagesDown(); void firstLoadMessages(); void delayedShowAt(MsgId showAtMsgId); void newUnreadMsg( not_null<History*> history, not_null<HistoryItem*> item); void historyToDown(History *history); void unreadCountChanged(not_null<History*> history); QRect historyRect() const; void pushTabbedSelectorToThirdSection( const Window::SectionShow ¶ms); void updateRecentStickers(); void destroyData(); void updateFieldPlaceholder(); void updateStickersByEmoji(); bool confirmSendingFiles(const QStringList &files); bool confirmSendingFiles(const QMimeData *data); void sendFileConfirmed(const std::shared_ptr<FileLoadResult> &file); void updateControlsVisibility(); void updateControlsGeometry(); History *history() const; PeerData *peer() const; void setMsgId(MsgId showAtMsgId); MsgId msgId() const; bool hasTopBarShadow() const { return peer() != nullptr; } void showAnimated(Window::SlideDirection direction, const Window::SectionSlideParams ¶ms); void finishAnimating(); void doneShow(); QPoint clampMousePosition(QPoint point); void checkSelectingScroll(QPoint point); void noSelectingScroll(); bool touchScroll(const QPoint &delta); void enqueueMessageHighlight(not_null<HistoryView::Element*> view); TimeMs highlightStartTime(not_null<const HistoryItem*> item) const; bool inSelectionMode() const; MessageIdsList getSelectedItems() const; void itemEdited(HistoryItem *item); void updateScrollColors(); void replyToMessage(FullMsgId itemId); void replyToMessage(not_null<HistoryItem*> item); void editMessage(FullMsgId itemId); void editMessage(not_null<HistoryItem*> item); void pinMessage(FullMsgId itemId); void unpinMessage(FullMsgId itemId); void copyPostLink(FullMsgId itemId); MsgId replyToId() const; void messageDataReceived(ChannelData *channel, MsgId msgId); bool lastForceReplyReplied(const FullMsgId &replyTo) const; bool lastForceReplyReplied() const; bool cancelReply(bool lastKeyboardUsed = false); void cancelEdit(); void updateForwarding(); void updateForwardingTexts(); void clearReplyReturns(); void pushReplyReturn(not_null<HistoryItem*> item); QList<MsgId> replyReturns(); void setReplyReturns(PeerId peer, const QList<MsgId> &replyReturns); void calcNextReplyReturn(); void updatePreview(); void previewCancel(); void step_recording(float64 ms, bool timer); void stopRecording(bool send); void onListEscapePressed(); void onListEnterPressed(); void sendBotCommand(PeerData *peer, UserData *bot, const QString &cmd, MsgId replyTo); void hideSingleUseKeyboard(PeerData *peer, MsgId replyTo); bool insertBotCommand(const QString &cmd); bool eventFilter(QObject *obj, QEvent *e) override; // With force=true the markup is updated even if it is // already shown for the passed history item. void updateBotKeyboard(History *h = nullptr, bool force = false); void fastShowAtEnd(not_null<History*> history); void applyDraft(bool parseLinks = true, Ui::FlatTextarea::UndoHistoryAction undoHistoryAction = Ui::FlatTextarea::ClearUndoHistory); void showHistory(const PeerId &peer, MsgId showAtMsgId, bool reload = false); void clearDelayedShowAt(); void clearAllLoadRequests(); void saveFieldToHistoryLocalDraft(); void applyCloudDraft(History *history); void updateHistoryDownPosition(); void updateHistoryDownVisibility(); void updateUnreadMentionsPosition(); void updateUnreadMentionsVisibility(); void updateFieldSubmitSettings(); void setInnerFocus(); void updateNotifySettings(); void saveGif(DocumentData *doc); bool contentOverlapped(const QRect &globalRect); void grabStart() override { _inGrab = true; updateControlsGeometry(); } void grapWithoutTopBarShadow(); void grabFinish() override; void forwardSelected(); void confirmDeleteSelected(); void clearSelected(); // Float player interface. bool wheelEventFromFloatPlayer(QEvent *e) override; QRect rectForFloatPlayer() const override; void app_sendBotCallback( not_null<const HistoryMessageMarkupButton*> button, not_null<const HistoryItem*> msg, int row, int column); PeerData *ui_getPeerForMouseAction(); void notify_botCommandsChanged(UserData *user); void notify_inlineBotRequesting(bool requesting); void notify_replyMarkupUpdated(const HistoryItem *item); void notify_inlineKeyboardMoved(const HistoryItem *item, int oldKeyboardTop, int newKeyboardTop); bool notify_switchInlineBotButtonReceived(const QString &query, UserData *samePeerBot, MsgId samePeerReplyTo); void notify_userIsBotChanged(UserData *user); void notify_migrateUpdated(PeerData *peer); bool cmd_search(); bool cmd_next_chat(); bool cmd_previous_chat(); ~HistoryWidget(); protected: void resizeEvent(QResizeEvent *e) override; void keyPressEvent(QKeyEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void paintEvent(QPaintEvent *e) override; void leaveEventHook(QEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; signals: void cancelled(); public slots: void onCancel(); void onPinnedHide(); void onFieldBarCancel(); void onPreviewParse(); void onPreviewCheck(); void onPreviewTimeout(); void onPhotoUploaded(const FullMsgId &msgId, bool silent, const MTPInputFile &file); void onDocumentUploaded(const FullMsgId &msgId, bool silent, const MTPInputFile &file); void onThumbDocumentUploaded(const FullMsgId &msgId, bool silent, const MTPInputFile &file, const MTPInputFile &thumb); void onPhotoProgress(const FullMsgId &msgId); void onDocumentProgress(const FullMsgId &msgId); void onPhotoFailed(const FullMsgId &msgId); void onDocumentFailed(const FullMsgId &msgId); void onReportSpamClicked(); void onReportSpamHide(); void onReportSpamClear(); void onScroll(); void onUnblock(); void onBotStart(); void onJoinChannel(); void onMuteUnmute(); void onBroadcastSilentChange(); void onKbToggle(bool manual = true); void onCmdStart(); void activate(); void onTextChange(); void onFieldTabbed(); bool onStickerSend(DocumentData *sticker); void onPhotoSend(PhotoData *photo); void onInlineResultSend(InlineBots::Result *result, UserData *bot); void onWindowVisibleChanged(); void onFieldFocused(); void onFieldResize(); void onCheckFieldAutocomplete(); void onScrollTimer(); void onDraftSaveDelayed(); void onDraftSave(bool delayed = false); void onCloudDraftSave(); void onRecordError(); void onRecordDone(QByteArray result, VoiceWaveform waveform, qint32 samples); void onRecordUpdate(quint16 level, qint32 samples); void onUpdateHistoryItems(); // checks if we are too close to the top or to the bottom // in the scroll area and preloads history if needed void preloadHistoryIfNeeded(); private slots: void onSend(bool ctrlShiftEnter = false); void onHashtagOrBotCommandInsert(QString str, FieldAutocomplete::ChooseMethod method); void onMentionInsert(UserData *user); void onInlineBotCancel(); void onMembersDropdownShow(); void onModerateKeyActivate(int index, bool *outHandled); void updateField(); private: using TabbedPanel = ChatHelpers::TabbedPanel; using TabbedSelector = ChatHelpers::TabbedSelector; using DragState = Storage::MimeDataState; void handlePendingHistoryUpdate(); void fullPeerUpdated(PeerData *peer); void toggleTabbedSelectorMode(); void returnTabbedSelector(object_ptr<TabbedSelector> selector); void recountChatWidth(); void setReportSpamStatus(DBIPeerReportSpamStatus status); void historyDownClicked(); void showNextUnreadMention(); void handlePeerUpdate(); void setMembersShowAreaActive(bool active); void forwardItems(MessageIdsList &&items); void handleHistoryChange(not_null<const History*> history); void highlightMessage(MsgId universalMessageId); void adjustHighlightedMessageToMigrated(); void checkNextHighlight(); void updateHighlightedMessage(); void clearHighlightMessages(); void stopMessageHighlight(); void updateSendAction( not_null<History*> history, SendAction::Type type, int32 progress = 0); void cancelSendAction( not_null<History*> history, SendAction::Type type); void cancelTypingAction(); void sendActionDone(const MTPBool &result, mtpRequestId req); void animationCallback(); void updateOverStates(QPoint pos); void recordStartCallback(); void recordStopCallback(bool active); void recordUpdateCallback(QPoint globalPos); void chooseAttach(); void historyDownAnimationFinish(); void unreadMentionsAnimationFinish(); void sendButtonClicked(); bool confirmSendingFiles( const QStringList &files, CompressConfirm compressed, const QString &insertTextOnCancel = QString()); bool confirmSendingFiles( QImage &&image, QByteArray &&content, CompressConfirm compressed, const QString &insertTextOnCancel = QString()); bool confirmSendingFiles( const QMimeData *data, CompressConfirm compressed, const QString &insertTextOnCancel = QString()); bool confirmSendingFiles( Storage::PreparedList &&list, CompressConfirm compressed, const QString &insertTextOnCancel = QString()); bool showSendingFilesError(const Storage::PreparedList &list) const; void uploadFiles(Storage::PreparedList &&list, SendMediaType type); void uploadFile(const QByteArray &fileContent, SendMediaType type); void uploadFilesAfterConfirmation( Storage::PreparedList &&list, SendMediaType type, QString caption, MsgId replyTo, std::shared_ptr<SendingAlbum> album = nullptr); void itemRemoved(not_null<const HistoryItem*> item); // Updates position of controls around the message field, // like send button, emoji button and others. void moveFieldControls(); void updateFieldSize(); void updateTabbedSelectorToggleTooltipGeometry(); void checkTabbedSelectorToggleTooltip(); bool canWriteMessage() const; bool isRestrictedWrite() const; void orderWidgets(); void clearInlineBot(); void inlineBotChanged(); // Look in the _field for the inline bot and query string. void updateInlineBotQuery(); // Request to show results in the emoji panel. void applyInlineBotQuery(UserData *bot, const QString &query); void cancelReplyAfterMediaSend(bool lastKeyboardUsed); void replyToPreviousMessage(); void replyToNextMessage(); void hideSelectorControlsAnimated(); int countMembersDropdownHeightMax() const; void updateReplyToName(); void checkForwardingInfo(); bool editingMessage() const { return _editMsgId != 0; } MsgId _replyToId = 0; Text _replyToName; int _replyToNameVersion = 0; HistoryItemsList _toForward; Text _toForwardFrom, _toForwardText; int _toForwardNameVersion = 0; MsgId _editMsgId = 0; HistoryItem *_replyEditMsg = nullptr; Text _replyEditMsgText; mutable SingleTimer _updateEditTimeLeftDisplay; object_ptr<Ui::IconButton> _fieldBarCancel; void updateReplyEditTexts(bool force = false); struct PinnedBar { PinnedBar(MsgId msgId, HistoryWidget *parent); ~PinnedBar(); MsgId msgId = 0; HistoryItem *msg = nullptr; Text text; object_ptr<Ui::IconButton> cancel; object_ptr<Ui::PlainShadow> shadow; }; std::unique_ptr<PinnedBar> _pinnedBar; void updatePinnedBar(bool force = false); bool pinnedMsgVisibilityUpdated(); void destroyPinnedBar(); void unpinDone(const MTPUpdates &updates); bool sendExistingDocument(DocumentData *doc, TextWithEntities caption); void sendExistingPhoto(PhotoData *photo, TextWithEntities caption); void drawField(Painter &p, const QRect &rect); void paintEditHeader(Painter &p, const QRect &rect, int left, int top) const; void drawRecording(Painter &p, float64 recordActive); void drawPinnedBar(Painter &p); void drawRestrictedWrite(Painter &p); bool paintShowAnimationFrame(TimeMs ms); void updateMouseTracking(); // destroys _history and _migrated unread bars void destroyUnreadBar(); mtpRequestId _saveEditMsgRequestId = 0; void saveEditMsg(); void saveEditMsgDone(History *history, const MTPUpdates &updates, mtpRequestId req); bool saveEditMsgFail(History *history, const RPCError &error, mtpRequestId req); static const mtpRequestId ReportSpamRequestNeeded = -1; DBIPeerReportSpamStatus _reportSpamStatus = dbiprsUnknown; mtpRequestId _reportSpamSettingRequestId = ReportSpamRequestNeeded; void updateReportSpamStatus(); void requestReportSpamSetting(); void reportSpamSettingDone(const MTPPeerSettings &result, mtpRequestId req); bool reportSpamSettingFail(const RPCError &error, mtpRequestId req); QString _previewLinks; WebPageData *_previewData = nullptr; typedef QMap<QString, WebPageId> PreviewCache; PreviewCache _previewCache; mtpRequestId _previewRequest = 0; Text _previewTitle; Text _previewDescription; SingleTimer _previewTimer; bool _previewCancelled = false; void gotPreview(QString links, const MTPMessageMedia &media, mtpRequestId req); bool _replyForwardPressed = false; HistoryItem *_replyReturn = nullptr; QList<MsgId> _replyReturns; bool messagesFailed(const RPCError &error, mtpRequestId requestId); void addMessagesToFront(PeerData *peer, const QVector<MTPMessage> &messages); void addMessagesToBack(PeerData *peer, const QVector<MTPMessage> &messages); struct BotCallbackInfo { UserData *bot; FullMsgId msgId; int row, col; bool game; }; void botCallbackDone(BotCallbackInfo info, const MTPmessages_BotCallbackAnswer &answer, mtpRequestId req); bool botCallbackFail(BotCallbackInfo info, const RPCError &error, mtpRequestId req); enum ScrollChangeType { ScrollChangeNone, // When we toggle a pinned message. ScrollChangeAdd, // When loading a history part while scrolling down. ScrollChangeNoJumpToBottom, }; struct ScrollChange { ScrollChangeType type; int value; }; void updateHistoryGeometry(bool initial = false, bool loadedDown = false, const ScrollChange &change = { ScrollChangeNone, 0 }); void updateListSize(); // Does any of the shown histories has this flag set. bool hasPendingResizedItems() const; // Counts scrollTop for placing the scroll right at the unread // messages bar, choosing from _history and _migrated unreadBar. base::optional<int> unreadBarTop() const; int itemTopForHighlight(not_null<HistoryView::Element*> view) const; void scrollToCurrentVoiceMessage(FullMsgId fromId, FullMsgId toId); HistoryView::Element *firstUnreadMessage() const; // Scroll to current y without updating the _lastUserScrolled time. // Used to distinguish between user scrolls and syntetic scrolls. // This one is syntetic. void synteticScrollToY(int y); void saveGifDone(DocumentData *doc, const MTPBool &result); void reportSpamDone(PeerData *peer, const MTPBool &result, mtpRequestId request); bool reportSpamFail(const RPCError &error, mtpRequestId request); void unblockDone(PeerData *peer, const MTPBool &result, mtpRequestId req); bool unblockFail(const RPCError &error, mtpRequestId req); void blockDone(PeerData *peer, const MTPBool &result); void countHistoryShowFrom(); enum class TextUpdateEvent { SaveDraft = (1 << 0), SendTyping = (1 << 1), }; using TextUpdateEvents = base::flags<TextUpdateEvent>; friend inline constexpr bool is_flag_type(TextUpdateEvent) { return true; }; void writeDrafts(Data::Draft **localDraft, Data::Draft **editDraft); void writeDrafts(History *history); void setFieldText(const TextWithTags &textWithTags, TextUpdateEvents events = 0, Ui::FlatTextarea::UndoHistoryAction undoHistoryAction = Ui::FlatTextarea::ClearUndoHistory); void clearFieldText(TextUpdateEvents events = 0, Ui::FlatTextarea::UndoHistoryAction undoHistoryAction = Ui::FlatTextarea::ClearUndoHistory) { setFieldText(TextWithTags(), events, undoHistoryAction); } HistoryItem *getItemFromHistoryOrMigrated(MsgId genericMsgId) const; void animatedScrollToItem(MsgId msgId); void animatedScrollToY(int scrollTo, HistoryItem *attachTo = nullptr); void updateDragAreas(); // when scroll position or scroll area size changed this method // updates the boundings of the visible area in HistoryInner void visibleAreaUpdated(); int countInitialScrollTop(); int countAutomaticScrollTop(); void preloadHistoryByScroll(); void checkReplyReturns(); void scrollToAnimationCallback(FullMsgId attachToId); bool readyToForward() const; bool hasSilentToggle() const; PeerData *_peer = nullptr; ChannelId _channel = NoChannel; bool _canSendMessages = false; MsgId _showAtMsgId = ShowAtUnreadMsgId; mtpRequestId _firstLoadRequest = 0; mtpRequestId _preloadRequest = 0; mtpRequestId _preloadDownRequest = 0; MsgId _delayedShowAtMsgId = -1; mtpRequestId _delayedShowAtRequest = 0; object_ptr<HistoryView::TopBarWidget> _topBar; object_ptr<Ui::ScrollArea> _scroll; QPointer<HistoryInner> _list; History *_migrated = nullptr; History *_history = nullptr; // Initial updateHistoryGeometry() was called. bool _historyInited = false; // If updateListSize() was called without updateHistoryGeometry(). bool _updateHistoryGeometryRequired = false; int _addToScroll = 0; int _lastScrollTop = 0; // gifs optimization TimeMs _lastScrolled = 0; QTimer _updateHistoryItems; TimeMs _lastUserScrolled = 0; bool _synteticScrollEvent = false; Animation _scrollToAnimation; Animation _historyDownShown; bool _historyDownIsShown = false; object_ptr<Ui::HistoryDownButton> _historyDown; Animation _unreadMentionsShown; bool _unreadMentionsIsShown = false; object_ptr<Ui::HistoryDownButton> _unreadMentions; object_ptr<FieldAutocomplete> _fieldAutocomplete; UserData *_inlineBot = nullptr; QString _inlineBotUsername; mtpRequestId _inlineBotResolveRequestId = 0; bool _isInlineBot = false; void inlineBotResolveDone(const MTPcontacts_ResolvedPeer &result); bool inlineBotResolveFail(QString name, const RPCError &error); bool isBotStart() const; bool isBlocked() const; bool isJoinChannel() const; bool isMuteUnmute() const; bool updateCmdStartShown(); void updateSendButtonType(); bool showRecordButton() const; bool showInlineBotCancel() const; void refreshSilentToggle(); object_ptr<ReportSpamPanel> _reportSpamPanel = { nullptr }; object_ptr<Ui::SendButton> _send; object_ptr<Ui::FlatButton> _unblock; object_ptr<Ui::FlatButton> _botStart; object_ptr<Ui::FlatButton> _joinChannel; object_ptr<Ui::FlatButton> _muteUnmute; mtpRequestId _unblockRequest = 0; mtpRequestId _reportSpamRequest = 0; object_ptr<Ui::IconButton> _attachToggle; object_ptr<Ui::EmojiButton> _tabbedSelectorToggle; object_ptr<Ui::ImportantTooltip> _tabbedSelectorToggleTooltip = { nullptr }; bool _tabbedSelectorToggleTooltipShown = false; object_ptr<Ui::IconButton> _botKeyboardShow; object_ptr<Ui::IconButton> _botKeyboardHide; object_ptr<Ui::IconButton> _botCommandStart; object_ptr<Ui::SilentToggle> _silent = { nullptr }; bool _cmdStartShown = false; object_ptr<MessageField> _field; bool _recording = false; bool _inField = false; bool _inReplyEditForward = false; bool _inPinnedMsg = false; bool _inClickable = false; int _recordingSamples = 0; int _recordCancelWidth; // This can animate for a very long time (like in music playing), // so it should be a BasicAnimation, not an Animation. BasicAnimation _a_recording; anim::value a_recordingLevel; bool kbWasHidden() const; bool _kbShown = false; HistoryItem *_kbReplyTo = nullptr; object_ptr<Ui::ScrollArea> _kbScroll; QPointer<BotKeyboard> _keyboard; object_ptr<Ui::InnerDropdown> _membersDropdown = { nullptr }; QTimer _membersDropdownShowTimer; object_ptr<InlineBots::Layout::Widget> _inlineResults = { nullptr }; object_ptr<TabbedPanel> _tabbedPanel; QPointer<TabbedSelector> _tabbedSelector; DragState _attachDragState; object_ptr<DragArea> _attachDragDocument, _attachDragPhoto; object_ptr<Ui::Emoji::SuggestionsController> _emojiSuggestions = { nullptr }; bool _nonEmptySelection = false; TextUpdateEvents _textUpdateEvents = (TextUpdateEvents() | TextUpdateEvent::SaveDraft | TextUpdateEvent::SendTyping); int64 _serviceImageCacheSize = 0; QString _confirmSource; Animation _a_show; Window::SlideDirection _showDirection; QPixmap _cacheUnder, _cacheOver; QTimer _scrollTimer; int32 _scrollDelta = 0; MsgId _highlightedMessageId = 0; std::deque<MsgId> _highlightQueue; base::Timer _highlightTimer; TimeMs _highlightStart = 0; QMap<QPair<not_null<History*>, SendAction::Type>, mtpRequestId> _sendActionRequests; base::Timer _sendActionStopTimer; TimeMs _saveDraftStart = 0; bool _saveDraftText = false; QTimer _saveDraftTimer, _saveCloudDraftTimer; object_ptr<Ui::PlainShadow> _topShadow; bool _inGrab = false; }; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/view/��������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0022076�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/view/history_view_context_menu.cpp���������������������0000664�0000000�0000000�00000034563�13262451251�0030140�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/view/history_view_context_menu.h" #include "history/view/history_view_list_widget.h" #include "history/view/history_view_cursor_state.h" #include "history/history.h" #include "history/history_item.h" #include "history/history_message.h" #include "history/history_item_text.h" #include "history/history_media_types.h" #include "ui/widgets/popup_menu.h" #include "chat_helpers/message_field.h" #include "boxes/confirm_box.h" #include "data/data_photo.h" #include "data/data_document.h" #include "data/data_media_types.h" #include "data/data_session.h" #include "data/data_groups.h" #include "core/file_utilities.h" #include "window/window_peer_menu.h" #include "lang/lang_keys.h" #include "messenger.h" #include "mainwidget.h" #include "auth_session.h" namespace HistoryView { namespace { void AddToggleGroupingAction( not_null<Ui::PopupMenu*> menu, not_null<PeerData*> peer) { if (const auto channel = peer->asChannel()) { const auto grouped = (channel->feed() != nullptr); //menu->addAction( // #feed // lang(grouped ? lng_feed_ungroup : lng_feed_group), // [=] { Window::ToggleChannelGrouping(channel, !grouped); }); } } void SavePhotoToFile(not_null<PhotoData*> photo) { if (!photo->date || !photo->loaded()) { return; } FileDialog::GetWritePath( lang(lng_save_photo), qsl("JPEG Image (*.jpg);;") + FileDialog::AllFilesFilter(), filedialogDefaultName(qsl("photo"), qsl(".jpg")), base::lambda_guarded(&Auth(), [=](const QString &result) { if (!result.isEmpty()) { photo->full->pix().toImage().save(result, "JPG"); } })); } void CopyImage(not_null<PhotoData*> photo) { if (!photo->date || !photo->loaded()) { return; } QApplication::clipboard()->setPixmap(photo->full->pix()); } void ShowStickerPackInfo(not_null<DocumentData*> document) { if (const auto sticker = document->sticker()) { if (sticker->set.type() != mtpc_inputStickerSetEmpty) { App::main()->stickersBox(sticker->set); } } } void ToggleFavedSticker(not_null<DocumentData*> document) { const auto unfave = Stickers::IsFaved(document); MTP::send( MTPmessages_FaveSticker( document->mtpInput(), MTP_bool(unfave)), rpcDone([=](const MTPBool &result) { Stickers::SetFaved(document, !unfave); })); } void AddPhotoActions( not_null<Ui::PopupMenu*> menu, not_null<PhotoData*> photo) { menu->addAction( lang(lng_context_save_image), App::LambdaDelayed( st::defaultDropdownMenu.menu.ripple.hideDuration, &Auth(), [=] { SavePhotoToFile(photo); })); menu->addAction(lang(lng_context_copy_image), [=] { CopyImage(photo); }); } void OpenGif(FullMsgId itemId) { if (const auto item = App::histItemById(itemId)) { if (const auto media = item->media()) { if (const auto document = media->document()) { Messenger::Instance().showDocument(document, item); } } } } void ShowInFolder(not_null<DocumentData*> document) { const auto filepath = document->filepath( DocumentData::FilePathResolveChecked); if (!filepath.isEmpty()) { File::ShowInFolder(filepath); } } void AddSaveDocumentAction( not_null<Ui::PopupMenu*> menu, not_null<DocumentData*> document) { menu->addAction( lang(document->isVideoFile() ? lng_context_save_video : (document->isVoiceMessage() ? lng_context_save_audio : (document->isAudioFile() ? lng_context_save_audio_file : (document->sticker() ? lng_context_save_image : lng_context_save_file)))), App::LambdaDelayed( st::defaultDropdownMenu.menu.ripple.hideDuration, &Auth(), [=] { DocumentSaveClickHandler::doSave(document, true); })); } void AddDocumentActions( not_null<Ui::PopupMenu*> menu, not_null<DocumentData*> document, FullMsgId contextId) { if (document->loading()) { menu->addAction(lang(lng_context_cancel_download), [=] { document->cancel(); }); return; } if (document->loaded() && document->isGifv()) { if (!cAutoPlayGif()) { menu->addAction(lang(lng_context_open_gif), [=] { OpenGif(contextId); }); } } if (document->sticker() && document->sticker()->set.type() != mtpc_inputStickerSetEmpty) { menu->addAction( lang(document->isStickerSetInstalled() ? lng_context_pack_info : lng_context_pack_add), [=] { ShowStickerPackInfo(document); }); menu->addAction( lang(Stickers::IsFaved(document) ? lng_faved_stickers_remove : lng_faved_stickers_add), [=] { ToggleFavedSticker(document); }); } if (!document->filepath( DocumentData::FilePathResolveChecked).isEmpty()) { menu->addAction( lang((cPlatform() == dbipMac || cPlatform() == dbipMacOld) ? lng_context_show_in_finder : lng_context_show_in_folder), [=] { ShowInFolder(document); }); } AddSaveDocumentAction(menu, document); } void CopyPostLink(FullMsgId itemId) { if (const auto item = App::histItemById(itemId)) { if (item->hasDirectLink()) { QApplication::clipboard()->setText(item->directLink()); } } } void AddPostLinkAction( not_null<Ui::PopupMenu*> menu, const ContextMenuRequest &request) { const auto item = request.item; if (!item || !item->hasDirectLink() || request.pointState == PointState::Outside) { return; } else if (request.link && !request.link->copyToClipboardContextItemText().isEmpty()) { return; } const auto itemId = item->fullId(); menu->addAction( lang(item->history()->peer->isMegagroup() ? lng_context_copy_link : lng_context_copy_post_link), [=] { CopyPostLink(itemId); }); } MessageIdsList ExtractIdsList(const SelectedItems &items) { return ranges::view::all( items ) | ranges::view::transform([](const auto &item) { return item.msgId; }) | ranges::to_vector; } bool AddForwardSelectedAction( not_null<Ui::PopupMenu*> menu, const ContextMenuRequest &request, not_null<ListWidget*> list) { if (!request.overSelection || request.selectedItems.empty()) { return false; } if (ranges::find_if(request.selectedItems, [](const auto &item) { return !item.canForward; }) != end(request.selectedItems)) { return false; } menu->addAction(lang(lng_context_forward_selected), [=] { const auto weak = make_weak(list); auto items = ExtractIdsList(request.selectedItems); Window::ShowForwardMessagesBox(std::move(items), [=] { if (const auto strong = weak.data()) { strong->cancelSelection(); } }); }); return true; } bool AddForwardMessageAction( not_null<Ui::PopupMenu*> menu, const ContextMenuRequest &request, not_null<ListWidget*> list) { const auto item = request.item; if (!request.selectedItems.empty()) { return false; } else if (!item || !item->allowsForward()) { return false; } const auto asGroup = (request.pointState != PointState::GroupPart); if (asGroup) { if (const auto group = Auth().data().groups().find(item)) { if (ranges::find_if(group->items, [](auto item) { return !item->allowsForward(); }) != end(group->items)) { return false; } } } const auto itemId = item->fullId(); menu->addAction(lang(lng_context_forward_msg), [=] { if (const auto item = App::histItemById(itemId)) { Window::ShowForwardMessagesBox(asGroup ? Auth().data().itemOrItsGroup(item) : MessageIdsList{ 1, itemId }); } }); return true; } void AddForwardAction( not_null<Ui::PopupMenu*> menu, const ContextMenuRequest &request, not_null<ListWidget*> list) { AddForwardSelectedAction(menu, request, list); AddForwardMessageAction(menu, request, list); } bool AddDeleteSelectedAction( not_null<Ui::PopupMenu*> menu, const ContextMenuRequest &request, not_null<ListWidget*> list) { if (!request.overSelection || request.selectedItems.empty()) { return false; } if (ranges::find_if(request.selectedItems, [](const auto &item) { return !item.canDelete; }) != end(request.selectedItems)) { return false; } menu->addAction(lang(lng_context_delete_selected), [=] { const auto weak = make_weak(list); auto items = ExtractIdsList(request.selectedItems); const auto box = Ui::show(Box<DeleteMessagesBox>(std::move(items))); box->setDeleteConfirmedCallback([=] { if (const auto strong = weak.data()) { strong->cancelSelection(); } }); }); return true; } bool AddDeleteMessageAction( not_null<Ui::PopupMenu*> menu, const ContextMenuRequest &request, not_null<ListWidget*> list) { const auto item = request.item; if (!request.selectedItems.empty()) { return false; } else if (!item || !item->canDelete()) { return false; } const auto asGroup = (request.pointState != PointState::GroupPart); if (asGroup) { if (const auto group = Auth().data().groups().find(item)) { if (ranges::find_if(group->items, [](auto item) { return !IsServerMsgId(item->id) || !item->canDelete(); }) != end(group->items)) { return false; } } } const auto itemId = item->fullId(); menu->addAction(lang(lng_context_delete_msg), [=] { if (const auto item = App::histItemById(itemId)) { if (asGroup) { if (const auto group = Auth().data().groups().find(item)) { Ui::show(Box<DeleteMessagesBox>( Auth().data().itemsToIds(group->items))); return; } } if (const auto message = item->toHistoryMessage()) { if (message->uploading()) { App::main()->cancelUploadLayer(item); return; } } const auto suggestModerateActions = true; Ui::show(Box<DeleteMessagesBox>(item, suggestModerateActions)); } }); return true; } void AddDeleteAction( not_null<Ui::PopupMenu*> menu, const ContextMenuRequest &request, not_null<ListWidget*> list) { if (!AddDeleteSelectedAction(menu, request, list)) { AddDeleteMessageAction(menu, request, list); } } bool AddClearSelectionAction( not_null<Ui::PopupMenu*> menu, const ContextMenuRequest &request, not_null<ListWidget*> list) { if (!request.overSelection || request.selectedItems.empty()) { return false; } menu->addAction(lang(lng_context_clear_selection), [=] { list->cancelSelection(); }); return true; } bool AddSelectMessageAction( not_null<Ui::PopupMenu*> menu, const ContextMenuRequest &request, not_null<ListWidget*> list) { const auto item = request.item; if (request.overSelection && !request.selectedItems.empty()) { return false; } else if (!item || !IsServerMsgId(item->id) || item->serviceMsg()) { return false; } const auto itemId = item->fullId(); const auto asGroup = (request.pointState != PointState::GroupPart); menu->addAction(lang(lng_context_select_msg), [=] { if (const auto item = App::histItemById(itemId)) { if (asGroup) { list->selectItemAsGroup(item); } else { list->selectItem(item); } } }); return true; } void AddSelectionAction( not_null<Ui::PopupMenu*> menu, const ContextMenuRequest &request, not_null<ListWidget*> list) { if (!AddClearSelectionAction(menu, request, list)) { AddSelectMessageAction(menu, request, list); } } void AddMessageActions( not_null<Ui::PopupMenu*> menu, const ContextMenuRequest &request, not_null<ListWidget*> list) { AddPostLinkAction(menu, request); AddForwardAction(menu, request, list); AddDeleteAction(menu, request, list); AddSelectionAction(menu, request, list); } void AddCopyLinkAction( not_null<Ui::PopupMenu*> menu, const ClickHandlerPtr &link) { if (!link) { return; } const auto action = link->copyToClipboardContextItemText(); if (action.isEmpty()) { return; } const auto text = link->copyToClipboardText(); menu->addAction( action, [=] { QApplication::clipboard()->setText(text); }); } } // namespace base::unique_qptr<Ui::PopupMenu> FillContextMenu( not_null<ListWidget*> list, const ContextMenuRequest &request) { auto result = base::make_unique_q<Ui::PopupMenu>(nullptr); const auto link = request.link; const auto view = request.view; const auto item = request.item; const auto itemId = item ? item->fullId() : FullMsgId(); const auto rawLink = link.get(); const auto linkPhoto = dynamic_cast<PhotoClickHandler*>(rawLink); const auto linkDocument = dynamic_cast<DocumentClickHandler*>(rawLink); const auto linkPeer = dynamic_cast<PeerClickHandler*>(rawLink); const auto photo = linkPhoto ? linkPhoto->photo().get() : nullptr; const auto document = linkDocument ? linkDocument->document().get() : nullptr; const auto isVideoLink = document ? document->isVideoFile() : false; const auto isVoiceLink = document ? document->isVoiceMessage() : false; const auto isAudioLink = document ? document->isAudioFile() : false; const auto hasSelection = !request.selectedItems.empty() || !request.selectedText.text.isEmpty(); if (request.overSelection) { const auto text = lang(request.selectedItems.empty() ? lng_context_copy_selected : lng_context_copy_selected_items); result->addAction(text, [=] { SetClipboardWithEntities(list->getSelectedText()); }); } if (linkPhoto) { AddPhotoActions(result, photo); } else if (linkDocument) { AddDocumentActions(result, document, itemId); } else if (linkPeer) { const auto peer = linkPeer->peer(); if (peer->isChannel() && peer->asChannel()->feed() != nullptr && (list->delegate()->listContext() == Context::Feed)) { Window::PeerMenuAddMuteAction(peer, [&]( const QString &text, base::lambda<void()> handler) { return result->addAction(text, handler); }); AddToggleGroupingAction(result, linkPeer->peer()); } } else if (!request.overSelection && view && !hasSelection) { auto media = view->media(); const auto mediaHasTextForCopy = media && media->hasTextForCopy(); if (media) { if (media->type() == MediaTypeWebPage && static_cast<HistoryWebPage*>(media)->attach()) { media = static_cast<HistoryWebPage*>(media)->attach(); } if (media->type() == MediaTypeSticker) { if (const auto document = media->getDocument()) { AddDocumentActions(result, document, view->data()->fullId()); } } } if (!link && (view->hasVisibleText() || mediaHasTextForCopy)) { const auto asGroup = (request.pointState != PointState::GroupPart); result->addAction(lang(lng_context_copy_text), [=] { if (const auto item = App::histItemById(itemId)) { if (asGroup) { if (const auto group = Auth().data().groups().find(item)) { SetClipboardWithEntities(HistoryGroupText(group)); return; } } SetClipboardWithEntities(HistoryItemText(item)); } }); } } AddCopyLinkAction(result, link); AddMessageActions(result, request, list); return result; } } // namespace HistoryView ���������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/view/history_view_context_menu.h�����������������������0000664�0000000�0000000�00000001544�13262451251�0027576�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "base/unique_qptr.h" namespace Ui { class PopupMenu; } // namespace Ui namespace HistoryView { enum class PointState : char; class ListWidget; class Element; struct SelectedItem; using SelectedItems = std::vector<SelectedItem>; struct ContextMenuRequest { ClickHandlerPtr link; Element *view = nullptr; HistoryItem *item = nullptr; SelectedItems selectedItems; TextWithEntities selectedText; bool overSelection = false; PointState pointState = PointState(); }; base::unique_qptr<Ui::PopupMenu> FillContextMenu( not_null<ListWidget*> list, const ContextMenuRequest &request); } // namespace ������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/view/history_view_cursor_state.cpp���������������������0000664�0000000�0000000�00000003033�13262451251�0030131�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/view/history_view_cursor_state.h" #include "history/history_item.h" #include "history/view/history_view_element.h" namespace HistoryView { TextState::TextState(not_null<const HistoryItem*> item) : itemId(item->fullId()) { } TextState::TextState( not_null<const HistoryItem*> item, const Text::StateResult &state) : itemId(item->fullId()) , cursor(state.uponSymbol ? CursorState::Text : CursorState::None) , link(state.link) , afterSymbol(state.afterSymbol) , symbol(state.symbol) { } TextState::TextState( not_null<const HistoryItem*> item, ClickHandlerPtr link) : itemId(item->fullId()) , link(link) { } TextState::TextState( not_null<const HistoryView::Element*> view) : TextState(view->data()) { } TextState::TextState( not_null<const HistoryView::Element*> view, const Text::StateResult &state) : TextState(view->data(), state) { } TextState::TextState( not_null<const HistoryView::Element*> view, ClickHandlerPtr link) : TextState(view->data(), link) { } TextState::TextState( std::nullptr_t, const Text::StateResult &state) : cursor(state.uponSymbol ? CursorState::Text : CursorState::None) , link(state.link) , afterSymbol(state.afterSymbol) , symbol(state.symbol) { } TextState::TextState(std::nullptr_t, ClickHandlerPtr link) : link(link) { } } // namespace HistoryView �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/view/history_view_cursor_state.h�����������������������0000664�0000000�0000000�00000002720�13262451251�0027600�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once class HistoryItem; namespace HistoryView { class Element; enum class PointState : char { Outside, Inside, GroupPart, }; enum class CursorState : char { None, Text, Date, Forwarded, }; struct TextState { TextState() = default; TextState(not_null<const HistoryItem*> item); TextState( not_null<const HistoryItem*> item, const Text::StateResult &state); TextState( not_null<const HistoryItem*> item, ClickHandlerPtr link); TextState(not_null<const HistoryView::Element*> view); TextState( not_null<const HistoryView::Element*> view, const Text::StateResult &state); TextState( not_null<const HistoryView::Element*> view, ClickHandlerPtr link); TextState( std::nullptr_t, const Text::StateResult &state); TextState(std::nullptr_t, ClickHandlerPtr link); FullMsgId itemId; CursorState cursor = CursorState::None; ClickHandlerPtr link; bool afterSymbol = false; uint16 symbol = 0; }; struct StateRequest { Text::StateRequest::Flags flags = Text::StateRequest::Flag::LookupLink; Text::StateRequest forText() const { Text::StateRequest result; result.flags = flags; return result; } }; enum class InfoDisplayType : char { Default, Image, Background, }; } // namespace HistoryView ������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/view/history_view_element.cpp��������������������������0000664�0000000�0000000�00000034015�13262451251�0027051�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/view/history_view_element.h" #include "history/view/history_view_service_message.h" #include "history/history_item_components.h" #include "history/history_item.h" #include "history/history_media.h" #include "history/history_media_grouped.h" #include "history/history.h" #include "data/data_session.h" #include "data/data_groups.h" #include "data/data_media_types.h" #include "lang/lang_keys.h" #include "auth_session.h" #include "layout.h" #include "styles/style_history.h" namespace HistoryView { namespace { // A new message from the same sender is attached to previous within 15 minutes. constexpr int kAttachMessageToPreviousSecondsDelta = 900; } // namespace TextSelection UnshiftItemSelection( TextSelection selection, uint16 byLength) { return (selection == FullSelection) ? selection : ::unshiftSelection(selection, byLength); } TextSelection ShiftItemSelection( TextSelection selection, uint16 byLength) { return (selection == FullSelection) ? selection : ::shiftSelection(selection, byLength); } TextSelection UnshiftItemSelection( TextSelection selection, const Text &byText) { return UnshiftItemSelection(selection, byText.length()); } TextSelection ShiftItemSelection( TextSelection selection, const Text &byText) { return ShiftItemSelection(selection, byText.length()); } void UnreadBar::init(int newCount) { if (freezed) { return; } count = newCount; text = (count == kCountUnknown) ? lang(lng_unread_bar_some) : lng_unread_bar(lt_count, count); width = st::semiboldFont->width(text); } int UnreadBar::height() { return st::historyUnreadBarHeight + st::historyUnreadBarMargin; } int UnreadBar::marginTop() { return st::lineWidth + st::historyUnreadBarMargin; } void UnreadBar::paint(Painter &p, int y, int w) const { const auto bottom = y + height(); y += marginTop(); p.fillRect( 0, y, w, height() - marginTop() - st::lineWidth, st::historyUnreadBarBg); p.fillRect( 0, bottom - st::lineWidth, w, st::lineWidth, st::historyUnreadBarBorder); p.setFont(st::historyUnreadBarFont); p.setPen(st::historyUnreadBarFg); int left = st::msgServiceMargin.left(); int maxwidth = w; if (Adaptive::ChatWide()) { maxwidth = qMin( maxwidth, st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left()); } w = maxwidth; const auto skip = st::historyUnreadBarHeight - 2 * st::lineWidth - st::historyUnreadBarFont->height; p.drawText( (w - width) / 2, y + (skip / 2) + st::historyUnreadBarFont->ascent, text); } void DateBadge::init(const QDateTime &date) { text = langDayOfMonthFull(date.date()); width = st::msgServiceFont->width(text); } int DateBadge::height() const { return st::msgServiceMargin.top() + st::msgServicePadding.top() + st::msgServiceFont->height + st::msgServicePadding.bottom() + st::msgServiceMargin.bottom(); } void DateBadge::paint(Painter &p, int y, int w) const { ServiceMessagePainter::paintDate(p, text, width, y, w); } Element::Element( not_null<ElementDelegate*> delegate, not_null<HistoryItem*> data) : _delegate(delegate) , _data(data) , _dateTime(ItemDateTime(data)) , _context(delegate->elementContext()) { Auth().data().registerItemView(this); refreshMedia(); if (_context == Context::History) { _data->_history->setHasPendingResizedItems(); } } not_null<ElementDelegate*> Element::delegate() const { return _delegate; } not_null<HistoryItem*> Element::data() const { return _data; } QDateTime Element::dateTime() const { return _dateTime; } HistoryMedia *Element::media() const { return _media.get(); } Context Element::context() const { return _context; } int Element::y() const { return _y; } void Element::setY(int y) { _y = y; } void Element::paintHighlight( Painter &p, int geometryHeight) const { const auto animms = delegate()->elementHighlightTime(this); if (!animms || animms >= st::activeFadeInDuration + st::activeFadeOutDuration) { return; } const auto top = marginTop(); const auto bottom = marginBottom(); const auto fill = qMin(top, bottom); const auto skiptop = top - fill; const auto fillheight = fill + geometryHeight + fill; const auto dt = (animms > st::activeFadeInDuration) ? (1. - (animms - st::activeFadeInDuration) / float64(st::activeFadeOutDuration)) : (animms / float64(st::activeFadeInDuration)); const auto o = p.opacity(); p.setOpacity(o * dt); p.fillRect( 0, skiptop, width(), fillheight, st::defaultTextPalette.selectOverlay); p.setOpacity(o); } bool Element::isUnderCursor() const { return _delegate->elementUnderCursor(this); } void Element::setPendingResize() { _flags |= Flag::NeedsResize; if (_context == Context::History) { data()->_history->setHasPendingResizedItems(); } } bool Element::pendingResize() const { return _flags & Flag::NeedsResize; } bool Element::isAttachedToPrevious() const { return _flags & Flag::AttachedToPrevious; } bool Element::isAttachedToNext() const { return _flags & Flag::AttachedToNext; } int Element::skipBlockWidth() const { return st::msgDateSpace + infoWidth() - st::msgDateDelta.x(); } int Element::skipBlockHeight() const { return st::msgDateFont->height - st::msgDateDelta.y(); } QString Element::skipBlock() const { return textcmdSkipBlock(skipBlockWidth(), skipBlockHeight()); } int Element::infoWidth() const { return 0; } bool Element::isHiddenByGroup() const { return _flags & Flag::HiddenByGroup; } bool Element::isHidden() const { return isHiddenByGroup(); } void Element::refreshMedia() { _flags &= ~Flag::HiddenByGroup; const auto item = data(); const auto media = item->media(); if (media && media->canBeGrouped()) { if (const auto group = Auth().data().groups().find(item)) { if (group->items.back() != item) { _media = nullptr; _flags |= Flag::HiddenByGroup; } else { _media = std::make_unique<HistoryGroupedMedia>( this, group->items); if (!pendingResize()) { Auth().data().requestViewResize(this); } } return; } } if (_data->media()) { _media = _data->media()->createView(this); } else { _media = nullptr; } } void Element::previousInBlocksChanged() { recountDisplayDateInBlocks(); recountAttachToPreviousInBlocks(); } void Element::nextInBlocksRemoved() { setAttachToNext(false); } void Element::refreshDataId() { if (const auto media = this->media()) { media->refreshParentId(data()); } } bool Element::computeIsAttachToPrevious(not_null<Element*> previous) { const auto item = data(); if (!Has<DateBadge>() && !Has<UnreadBar>()) { const auto prev = previous->data(); const auto possible = !item->serviceMsg() && !prev->serviceMsg() && !item->isEmpty() && !prev->isEmpty() && (std::abs(prev->date() - item->date()) < kAttachMessageToPreviousSecondsDelta) && (_context == Context::Feed || (!item->isPost() && !prev->isPost())); if (possible) { if (item->history()->peer->isSelf()) { return prev->senderOriginal() == item->senderOriginal() && (prev->Has<HistoryMessageForwarded>() == item->Has<HistoryMessageForwarded>()); } else { return prev->from() == item->from(); } } } return false; } void Element::destroyUnreadBar() { if (!Has<UnreadBar>()) { return; } RemoveComponents(UnreadBar::Bit()); Auth().data().requestViewResize(this); if (data()->mainView() == this) { recountAttachToPreviousInBlocks(); } } void Element::setUnreadBarCount(int count) { const auto changed = AddComponents(UnreadBar::Bit()); const auto bar = Get<UnreadBar>(); if (bar->freezed) { return; } bar->init(count); if (changed) { if (data()->mainView() == this) { recountAttachToPreviousInBlocks(); } Auth().data().requestViewResize(this); } else { Auth().data().requestViewRepaint(this); } } void Element::setUnreadBarFreezed() { if (const auto bar = Get<UnreadBar>()) { bar->freezed = true; } } int Element::displayedDateHeight() const { if (auto date = Get<DateBadge>()) { return date->height(); } return 0; } bool Element::displayDate() const { return Has<DateBadge>(); } bool Element::isInOneDayWithPrevious() const { return !data()->isEmpty() && !displayDate(); } void Element::recountAttachToPreviousInBlocks() { auto attachToPrevious = false; if (const auto previous = previousInBlocks()) { attachToPrevious = computeIsAttachToPrevious(previous); previous->setAttachToNext(attachToPrevious); } setAttachToPrevious(attachToPrevious); } void Element::recountDisplayDateInBlocks() { setDisplayDate([&] { const auto item = data(); if (item->isEmpty()) { return false; } if (const auto previous = previousInBlocks()) { const auto prev = previous->data(); return prev->isEmpty() || (previous->dateTime().date() != dateTime().date()); } return true; }()); } QSize Element::countOptimalSize() { return performCountOptimalSize(); } QSize Element::countCurrentSize(int newWidth) { if (_flags & Flag::NeedsResize) { _flags &= ~Flag::NeedsResize; initDimensions(); } return performCountCurrentSize(newWidth); } void Element::setDisplayDate(bool displayDate) { const auto item = data(); if (displayDate && !Has<DateBadge>()) { AddComponents(DateBadge::Bit()); Get<DateBadge>()->init(dateTime()); setPendingResize(); } else if (!displayDate && Has<DateBadge>()) { RemoveComponents(DateBadge::Bit()); setPendingResize(); } } void Element::setAttachToNext(bool attachToNext) { if (attachToNext && !(_flags & Flag::AttachedToNext)) { _flags |= Flag::AttachedToNext; setPendingResize(); } else if (!attachToNext && (_flags & Flag::AttachedToNext)) { _flags &= ~Flag::AttachedToNext; setPendingResize(); } } void Element::setAttachToPrevious(bool attachToPrevious) { if (attachToPrevious && !(_flags & Flag::AttachedToPrevious)) { _flags |= Flag::AttachedToPrevious; setPendingResize(); } else if (!attachToPrevious && (_flags & Flag::AttachedToPrevious)) { _flags &= ~Flag::AttachedToPrevious; setPendingResize(); } } bool Element::displayFromPhoto() const { return false; } bool Element::hasFromPhoto() const { return false; } bool Element::hasFromName() const { return false; } bool Element::displayFromName() const { return false; } bool Element::displayForwardedFrom() const { return false; } bool Element::hasOutLayout() const { return false; } bool Element::drawBubble() const { return false; } bool Element::hasBubble() const { return false; } bool Element::hasFastReply() const { return false; } bool Element::displayFastReply() const { return false; } bool Element::displayRightAction() const { return false; } void Element::drawRightAction( Painter &p, int left, int top, int outerWidth) const { } ClickHandlerPtr Element::rightActionLink() const { return ClickHandlerPtr(); } bool Element::displayEditedBadge() const { return false; } TimeId Element::displayedEditDate() const { return TimeId(0); } bool Element::hasVisibleText() const { return false; } HistoryBlock *Element::block() { return _block; } const HistoryBlock *Element::block() const { return _block; } void Element::attachToBlock(not_null<HistoryBlock*> block, int index) { Expects(!_data->isLogEntry()); Expects(_block == nullptr); Expects(_indexInBlock < 0); Expects(index >= 0); _block = block; _indexInBlock = index; _data->setMainView(this); previousInBlocksChanged(); } void Element::removeFromBlock() { Expects(_block != nullptr); _block->remove(this); } void Element::refreshInBlock() { Expects(_block != nullptr); _block->refreshView(this); } void Element::setIndexInBlock(int index) { Expects(_block != nullptr); Expects(index >= 0); _indexInBlock = index; } int Element::indexInBlock() const { Expects((_indexInBlock >= 0) == (_block != nullptr)); Expects((_block == nullptr) || (_block->messages[_indexInBlock].get() == this)); return _indexInBlock; } Element *Element::previousInBlocks() const { if (_block && _indexInBlock >= 0) { if (_indexInBlock > 0) { return _block->messages[_indexInBlock - 1].get(); } if (auto previous = _block->previousBlock()) { Assert(!previous->messages.empty()); return previous->messages.back().get(); } } return nullptr; } Element *Element::nextInBlocks() const { if (_block && _indexInBlock >= 0) { if (_indexInBlock + 1 < _block->messages.size()) { return _block->messages[_indexInBlock + 1].get(); } if (auto next = _block->nextBlock()) { Assert(!next->messages.empty()); return next->messages.front().get(); } } return nullptr; } void Element::drawInfo( Painter &p, int right, int bottom, int width, bool selected, InfoDisplayType type) const { } bool Element::pointInTime( int right, int bottom, QPoint point, InfoDisplayType type) const { return false; } TextSelection Element::adjustSelection( TextSelection selection, TextSelectType type) const { return selection; } void Element::clickHandlerActiveChanged( const ClickHandlerPtr &handler, bool active) { if (const auto markup = _data->Get<HistoryMessageReplyMarkup>()) { if (const auto keyboard = markup->inlineKeyboard.get()) { keyboard->clickHandlerActiveChanged(handler, active); } } App::hoveredLinkItem(active ? this : nullptr); Auth().data().requestViewRepaint(this); if (const auto media = this->media()) { media->clickHandlerActiveChanged(handler, active); } } void Element::clickHandlerPressedChanged( const ClickHandlerPtr &handler, bool pressed) { if (const auto markup = _data->Get<HistoryMessageReplyMarkup>()) { if (const auto keyboard = markup->inlineKeyboard.get()) { keyboard->clickHandlerPressedChanged(handler, pressed); } } App::pressedLinkItem(pressed ? this : nullptr); Auth().data().requestViewRepaint(this); if (const auto media = this->media()) { media->clickHandlerPressedChanged(handler, pressed); } } Element::~Element() { if (_data->mainView() == this) { _data->clearMainView(); } if (_context == Context::History) { Auth().data().notifyViewRemoved(this); } Auth().data().unregisterItemView(this); } } // namespace HistoryView �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/view/history_view_element.h����������������������������0000664�0000000�0000000�00000017214�13262451251�0026520�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "history/view/history_view_object.h" #include "base/runtime_composer.h" #include "base/flags.h" class HistoryBlock; class HistoryItem; class HistoryMessage; class HistoryService; class HistoryMedia; class HistoryWebPage; namespace HistoryView { enum class PointState : char; enum class InfoDisplayType : char; struct StateRequest; struct TextState; enum class Context : char { History, Feed, AdminLog }; class Element; class ElementDelegate { public: virtual Context elementContext() = 0; virtual std::unique_ptr<Element> elementCreate( not_null<HistoryMessage*> message) = 0; virtual std::unique_ptr<Element> elementCreate( not_null<HistoryService*> message) = 0; virtual bool elementUnderCursor(not_null<const Element*> view) = 0; virtual void elementAnimationAutoplayAsync( not_null<const Element*> element) = 0; virtual TimeMs elementHighlightTime( not_null<const Element*> element) = 0; virtual bool elementInSelectionMode() = 0; }; TextSelection UnshiftItemSelection( TextSelection selection, uint16 byLength); TextSelection ShiftItemSelection( TextSelection selection, uint16 byLength); TextSelection UnshiftItemSelection( TextSelection selection, const Text &byText); TextSelection ShiftItemSelection( TextSelection selection, const Text &byText); // Any HistoryView::Element can have this Component for // displaying the unread messages bar above the message. struct UnreadBar : public RuntimeComponent<UnreadBar, Element> { void init(int newCount); static int height(); static int marginTop(); void paint(Painter &p, int y, int w) const; static constexpr auto kCountUnknown = std::numeric_limits<int>::max(); QString text; int width = 0; int count = 0; // If unread bar is freezed the new messages do not // increment the counter displayed by this bar. // // It happens when we've opened the conversation and // we've seen the bar and new messages are marked as read // as soon as they are added to the chat history. bool freezed = false; }; // Any HistoryView::Element can have this Component for // displaying the day mark above the message. struct DateBadge : public RuntimeComponent<DateBadge, Element> { void init(const QDateTime &date); int height() const; void paint(Painter &p, int y, int w) const; QString text; int width = 0; }; class Element : public Object , public RuntimeComposer<Element> , public ClickHandlerHost { public: Element( not_null<ElementDelegate*> delegate, not_null<HistoryItem*> data); enum class Flag : uchar { NeedsResize = 0x01, AttachedToPrevious = 0x02, AttachedToNext = 0x04, HiddenByGroup = 0x08, }; using Flags = base::flags<Flag>; friend inline constexpr auto is_flag_type(Flag) { return true; } not_null<ElementDelegate*> delegate() const; not_null<HistoryItem*> data() const; HistoryMedia *media() const; Context context() const; void refreshDataId(); QDateTime dateTime() const; int y() const; void setY(int y); virtual int marginTop() const = 0; virtual int marginBottom() const = 0; void setPendingResize(); bool pendingResize() const; bool isUnderCursor() const; bool isAttachedToPrevious() const; bool isAttachedToNext() const; int skipBlockWidth() const; int skipBlockHeight() const; QString skipBlock() const; virtual int infoWidth() const; bool isHiddenByGroup() const; virtual bool isHidden() const; // For blocks context this should be called only from recountAttachToPreviousInBlocks(). void setAttachToPrevious(bool attachToNext); // For blocks context this should be called only from recountAttachToPreviousInBlocks() // of the next item or when the next item is removed through nextInBlocksRemoved() call. void setAttachToNext(bool attachToNext); // For blocks context this should be called only from recountDisplayDate(). void setDisplayDate(bool displayDate); bool computeIsAttachToPrevious(not_null<Element*> previous); void setUnreadBarCount(int count); void destroyUnreadBar(); // marks the unread bar as freezed so that unread // messages count will not change for this bar // when the new messages arrive in this chat history void setUnreadBarFreezed(); int displayedDateHeight() const; bool displayDate() const; bool isInOneDayWithPrevious() const; virtual void draw( Painter &p, QRect clip, TextSelection selection, TimeMs ms) const = 0; [[nodiscard]] virtual PointState pointState(QPoint point) const = 0; [[nodiscard]] virtual TextState textState( QPoint point, StateRequest request) const = 0; virtual void updatePressed(QPoint point) = 0; virtual void drawInfo( Painter &p, int right, int bottom, int width, bool selected, InfoDisplayType type) const; virtual bool pointInTime( int right, int bottom, QPoint point, InfoDisplayType type) const; virtual TextWithEntities selectedText( TextSelection selection) const = 0; [[nodiscard]] virtual TextSelection adjustSelection( TextSelection selection, TextSelectType type) const; // ClickHandlerHost interface. void clickHandlerActiveChanged( const ClickHandlerPtr &handler, bool active) override; void clickHandlerPressedChanged( const ClickHandlerPtr &handler, bool pressed) override; // hasFromPhoto() returns true even if we don't display the photo // but we need to skip a place at the left side for this photo virtual bool hasFromPhoto() const; virtual bool displayFromPhoto() const; virtual bool hasFromName() const; virtual bool displayFromName() const; virtual bool displayForwardedFrom() const; virtual bool hasOutLayout() const; virtual bool drawBubble() const; virtual bool hasBubble() const; virtual bool hasFastReply() const; virtual bool displayFastReply() const; virtual bool displayRightAction() const; virtual void drawRightAction( Painter &p, int left, int top, int outerWidth) const; virtual ClickHandlerPtr rightActionLink() const; virtual bool displayEditedBadge() const; virtual TimeId displayedEditDate() const; virtual bool hasVisibleText() const; // Legacy blocks structure. HistoryBlock *block(); const HistoryBlock *block() const; void attachToBlock(not_null<HistoryBlock*> block, int index); void removeFromBlock(); void refreshInBlock(); void setIndexInBlock(int index); int indexInBlock() const; Element *previousInBlocks() const; Element *nextInBlocks() const; void previousInBlocksChanged(); void nextInBlocksRemoved(); virtual ~Element(); protected: void paintHighlight( Painter &p, int geometryHeight) const; private: // This should be called only from previousInBlocksChanged() // to add required bits to the Composer mask // after that always use Has<DateBadge>(). void recountDisplayDateInBlocks(); // This should be called only from previousInBlocksChanged() or when // DateBadge or UnreadBar bit is changed in the Composer mask // then the result should be cached in a client side flag // MTPDmessage_ClientFlag::f_attach_to_previous. void recountAttachToPreviousInBlocks(); QSize countOptimalSize() final override; QSize countCurrentSize(int newWidth) final override; virtual QSize performCountOptimalSize() = 0; virtual QSize performCountCurrentSize(int newWidth) = 0; void refreshMedia(); const not_null<ElementDelegate*> _delegate; const not_null<HistoryItem*> _data; std::unique_ptr<HistoryMedia> _media; const QDateTime _dateTime; int _y = 0; Context _context = Context(); Flags _flags = Flag::NeedsResize; HistoryBlock *_block = nullptr; int _indexInBlock = -1; }; } // namespace HistoryView ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/view/history_view_list_widget.cpp����������������������0000664�0000000�0000000�00000210101�13262451251�0027726�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/view/history_view_list_widget.h" #include "history/history_media_types.h" #include "history/history_message.h" #include "history/history_item_components.h" #include "history/history_item_text.h" #include "history/view/history_view_context_menu.h" #include "history/view/history_view_element.h" #include "history/view/history_view_message.h" #include "history/view/history_view_service_message.h" #include "history/view/history_view_cursor_state.h" #include "chat_helpers/message_field.h" #include "mainwindow.h" #include "mainwidget.h" #include "messenger.h" #include "apiwrap.h" #include "layout.h" #include "window/window_controller.h" #include "window/window_peer_menu.h" #include "auth_session.h" #include "ui/widgets/popup_menu.h" #include "core/tl_help.h" #include "base/overload.h" #include "lang/lang_keys.h" #include "boxes/edit_participant_box.h" #include "data/data_session.h" #include "data/data_feed.h" #include "data/data_media_types.h" #include "styles/style_history.h" namespace HistoryView { namespace { constexpr auto kScrollDateHideTimeout = 1000; constexpr auto kPreloadedScreensCount = 4; constexpr auto kPreloadIfLessThanScreens = 2; constexpr auto kPreloadedScreensCountFull = kPreloadedScreensCount + 1 + kPreloadedScreensCount; } // namespace ListWidget::MouseState::MouseState() : pointState(PointState::Outside) { } ListWidget::MouseState::MouseState( FullMsgId itemId, int height, QPoint point, PointState pointState) : itemId(itemId) , height(height) , point(point) , pointState(pointState) { } template <ListWidget::EnumItemsDirection direction, typename Method> void ListWidget::enumerateItems(Method method) { constexpr auto TopToBottom = (direction == EnumItemsDirection::TopToBottom); // No displayed messages in this history. if (_items.empty()) { return; } if (_visibleBottom <= _itemsTop || _itemsTop + _itemsHeight <= _visibleTop) { return; } const auto beginning = begin(_items); const auto ending = end(_items); auto from = TopToBottom ? std::lower_bound( beginning, ending, _visibleTop, [this](auto &elem, int top) { return this->itemTop(elem) + elem->height() <= top; }) : std::upper_bound( beginning, ending, _visibleBottom, [this](int bottom, auto &elem) { return this->itemTop(elem) + elem->height() >= bottom; }); auto wasEnd = (from == ending); if (wasEnd) { --from; } if (TopToBottom) { Assert(itemTop(from->get()) + from->get()->height() > _visibleTop); } else { Assert(itemTop(from->get()) < _visibleBottom); } while (true) { auto view = from->get(); auto itemtop = itemTop(view); auto itembottom = itemtop + view->height(); // Binary search should've skipped all the items that are above / below the visible area. if (TopToBottom) { Assert(itembottom > _visibleTop); } else { Assert(itemtop < _visibleBottom); } if (!method(view, itemtop, itembottom)) { return; } // Skip all the items that are below / above the visible area. if (TopToBottom) { if (itembottom >= _visibleBottom) { return; } } else { if (itemtop <= _visibleTop) { return; } } if (TopToBottom) { if (++from == ending) { break; } } else { if (from == beginning) { break; } --from; } } } template <typename Method> void ListWidget::enumerateUserpics(Method method) { // Find and remember the top of an attached messages pack // -1 means we didn't find an attached to next message yet. int lowestAttachedItemTop = -1; auto userpicCallback = [&](not_null<Element*> view, int itemtop, int itembottom) { // Skip all service messages. auto message = view->data()->toHistoryMessage(); if (!message) return true; if (lowestAttachedItemTop < 0 && view->isAttachedToNext()) { lowestAttachedItemTop = itemtop + view->marginTop(); } // Call method on a userpic for all messages that have it and for those who are not showing it // because of their attachment to the next message if they are bottom-most visible. if (view->displayFromPhoto() || (view->hasFromPhoto() && itembottom >= _visibleBottom)) { if (lowestAttachedItemTop < 0) { lowestAttachedItemTop = itemtop + view->marginTop(); } // Attach userpic to the bottom of the visible area with the same margin as the last message. auto userpicMinBottomSkip = st::historyPaddingBottom + st::msgMargin.bottom(); auto userpicBottom = qMin(itembottom - view->marginBottom(), _visibleBottom - userpicMinBottomSkip); // Do not let the userpic go above the attached messages pack top line. userpicBottom = qMax(userpicBottom, lowestAttachedItemTop + st::msgPhotoSize); // Call the template callback function that was passed // and return if it finished everything it needed. if (!method(view, userpicBottom - st::msgPhotoSize)) { return false; } } // Forget the found top of the pack, search for the next one from scratch. if (!view->isAttachedToNext()) { lowestAttachedItemTop = -1; } return true; }; enumerateItems<EnumItemsDirection::TopToBottom>(userpicCallback); } template <typename Method> void ListWidget::enumerateDates(Method method) { // Find and remember the bottom of an single-day messages pack // -1 means we didn't find a same-day with previous message yet. auto lowestInOneDayItemBottom = -1; auto dateCallback = [&](not_null<Element*> view, int itemtop, int itembottom) { const auto item = view->data(); if (lowestInOneDayItemBottom < 0 && view->isInOneDayWithPrevious()) { lowestInOneDayItemBottom = itembottom - view->marginBottom(); } // Call method on a date for all messages that have it and for those who are not showing it // because they are in a one day together with the previous message if they are top-most visible. if (view->displayDate() || (!item->isEmpty() && itemtop <= _visibleTop)) { if (lowestInOneDayItemBottom < 0) { lowestInOneDayItemBottom = itembottom - view->marginBottom(); } // Attach date to the top of the visible area with the same margin as it has in service message. auto dateTop = qMax(itemtop, _visibleTop) + st::msgServiceMargin.top(); // Do not let the date go below the single-day messages pack bottom line. auto dateHeight = st::msgServicePadding.bottom() + st::msgServiceFont->height + st::msgServicePadding.top(); dateTop = qMin(dateTop, lowestInOneDayItemBottom - dateHeight); // Call the template callback function that was passed // and return if it finished everything it needed. if (!method(view, itemtop, dateTop)) { return false; } } // Forget the found bottom of the pack, search for the next one from scratch. if (!view->isInOneDayWithPrevious()) { lowestInOneDayItemBottom = -1; } return true; }; enumerateItems<EnumItemsDirection::BottomToTop>(dateCallback); } ListWidget::ListWidget( QWidget *parent, not_null<Window::Controller*> controller, not_null<ListDelegate*> delegate) : RpWidget(parent) , _delegate(delegate) , _controller(controller) , _context(_delegate->listContext()) , _itemAverageHeight(itemMinimalHeight()) , _scrollDateCheck([this] { scrollDateCheck(); }) , _applyUpdatedScrollState([this] { applyUpdatedScrollState(); }) , _selectEnabled(_delegate->listAllowsMultiSelect()) , _highlightTimer([this] { updateHighlightedMessage(); }) { setMouseTracking(true); _scrollDateHideTimer.setCallback([this] { scrollDateHideByTimer(); }); Auth().data().viewRepaintRequest( ) | rpl::start_with_next([this](auto view) { if (view->delegate() == this) { repaintItem(view); } }, lifetime()); Auth().data().viewResizeRequest( ) | rpl::start_with_next([this](auto view) { if (view->delegate() == this) { resizeItem(view); } }, lifetime()); Auth().data().itemViewRefreshRequest( ) | rpl::start_with_next([this](auto item) { if (const auto view = viewForItem(item)) { refreshItem(view); } }, lifetime()); Auth().data().viewLayoutChanged( ) | rpl::start_with_next([this](auto view) { if (view->delegate() == this) { if (view->isUnderCursor()) { mouseActionUpdate(); } } }, lifetime()); Auth().data().animationPlayInlineRequest( ) | rpl::start_with_next([this](auto item) { if (const auto view = viewForItem(item)) { if (const auto media = view->media()) { media->playAnimation(); } } }, lifetime()); Auth().data().itemRemoved( ) | rpl::start_with_next( [this](auto item) { itemRemoved(item); }, lifetime()); subscribe(Auth().data().queryItemVisibility(), [this](const Data::Session::ItemVisibilityQuery &query) { if (const auto view = viewForItem(query.item)) { const auto top = itemTop(view); if (top >= 0 && top + view->height() > _visibleTop && top < _visibleBottom) { *query.isVisible = true; } } }); } not_null<ListDelegate*> ListWidget::delegate() const { return _delegate; } void ListWidget::refreshViewer() { _viewerLifetime.destroy(); _delegate->listSource( _aroundPosition, _idsLimit, _idsLimit ) | rpl::start_with_next([=](Data::MessagesSlice &&slice) { _slice = std::move(slice); refreshRows(); }, _viewerLifetime); } void ListWidget::refreshRows() { saveScrollState(); _items.clear(); _items.reserve(_slice.ids.size()); for (const auto &fullId : _slice.ids) { if (const auto item = App::histItemById(fullId)) { _items.push_back(enforceViewForItem(item)); } } updateAroundPositionFromRows(); updateItemsGeometry(); checkUnreadBarCreation(); restoreScrollState(); mouseActionUpdate(QCursor::pos()); _delegate->listContentRefreshed(); } base::optional<int> ListWidget::scrollTopForPosition( Data::MessagePosition position) const { if (position == Data::MaxMessagePosition) { if (loadedAtBottom()) { return height(); } return base::none; } else if (_items.empty() || isBelowPosition(position) || isAbovePosition(position)) { return base::none; } const auto index = findNearestItem(position); const auto view = _items[index]; return scrollTopForView(_items[index]); } base::optional<int> ListWidget::scrollTopForView( not_null<Element*> view) const { if (view->isHiddenByGroup()) { if (const auto group = Auth().data().groups().find(view->data())) { if (const auto leader = viewForItem(group->items.back())) { if (!leader->isHiddenByGroup()) { return scrollTopForView(leader); } } } } const auto top = view->y(); const auto height = view->height(); const auto available = _visibleBottom - _visibleTop; return top - std::max((available - height) / 2, 0); } void ListWidget::animatedScrollTo( int scrollTop, Data::MessagePosition attachPosition, int delta, AnimatedScroll type) { _scrollToAnimation.finish(); if (!delta || _items.empty()) { _delegate->listScrollTo(scrollTop); return; } const auto index = findNearestItem(attachPosition); Assert(index >= 0 && index < int(_items.size())); const auto attachTo = _items[index]; const auto attachToId = attachTo->data()->fullId(); const auto transition = (type == AnimatedScroll::Full) ? anim::sineInOut : anim::easeOutCubic; const auto initial = scrollTop - delta; _delegate->listScrollTo(initial); const auto attachToTop = itemTop(attachTo); const auto relativeStart = initial - attachToTop; const auto relativeFinish = scrollTop - attachToTop; _scrollToAnimation.start( [=] { scrollToAnimationCallback(attachToId); }, relativeStart, relativeFinish, st::slideDuration, transition); } void ListWidget::scrollToAnimationCallback(FullMsgId attachToId) { const auto attachTo = App::histItemById(attachToId); const auto attachToView = viewForItem(attachTo); if (!attachToView) { _scrollToAnimation.finish(); } else { const auto current = int(std::round(_scrollToAnimation.current())); _delegate->listScrollTo(itemTop(attachToView) + current); } } bool ListWidget::isAbovePosition(Data::MessagePosition position) const { if (_items.empty() || loadedAtBottom()) { return false; } return _items.back()->data()->position() < position; } bool ListWidget::isBelowPosition(Data::MessagePosition position) const { if (_items.empty() || loadedAtTop()) { return false; } return _items.front()->data()->position() > position; } void ListWidget::highlightMessage(FullMsgId itemId) { if (const auto item = App::histItemById(itemId)) { if (const auto view = viewForItem(item)) { _highlightStart = getms(); _highlightedMessageId = itemId; _highlightTimer.callEach(AnimationTimerDelta); repaintItem(view); } } } void ListWidget::updateHighlightedMessage() { if (const auto item = App::histItemById(_highlightedMessageId)) { if (const auto view = viewForItem(item)) { repaintItem(view); auto duration = st::activeFadeInDuration + st::activeFadeOutDuration; if (getms() - _highlightStart <= duration) { return; } } } _highlightTimer.cancel(); _highlightedMessageId = FullMsgId(); } void ListWidget::checkUnreadBarCreation() { if (!_unreadBarElement) { if (const auto index = _delegate->listUnreadBarView(_items)) { _unreadBarElement = _items[*index].get(); _unreadBarElement->setUnreadBarCount(UnreadBar::kCountUnknown); refreshAttachmentsAtIndex(*index); } } } void ListWidget::saveScrollState() { if (!_scrollTopState.item) { _scrollTopState = countScrollState(); } } void ListWidget::restoreScrollState() { if (_items.empty()) { return; } if (!_scrollTopState.item) { if (!_unreadBarElement) { return; } _scrollTopState.item = _unreadBarElement->data()->position(); _scrollTopState.shift = st::lineWidth + st::historyUnreadBarMargin; } const auto index = findNearestItem(_scrollTopState.item); if (index >= 0) { const auto view = _items[index]; auto newVisibleTop = itemTop(view) + _scrollTopState.shift; if (_visibleTop != newVisibleTop) { _delegate->listScrollTo(newVisibleTop); } } _scrollTopState = ScrollTopState(); } Element *ListWidget::viewForItem(FullMsgId itemId) const { if (const auto item = App::histItemById(itemId)) { return viewForItem(item); } return nullptr; } Element *ListWidget::viewForItem(const HistoryItem *item) const { if (item) { if (const auto i = _views.find(item); i != _views.end()) { return i->second.get(); } } return nullptr; } not_null<Element*> ListWidget::enforceViewForItem( not_null<HistoryItem*> item) { if (const auto view = viewForItem(item)) { return view; } const auto [i, ok] = _views.emplace( item, item->createView(this)); return i->second.get(); } void ListWidget::updateAroundPositionFromRows() { _aroundIndex = findNearestItem(_aroundPosition); if (_aroundIndex >= 0) { const auto newPosition = _items[_aroundIndex]->data()->position(); if (_aroundPosition != newPosition) { _aroundPosition = newPosition; crl::on_main(this, [=] { refreshViewer(); }); } } } int ListWidget::findNearestItem(Data::MessagePosition position) const { if (_items.empty()) { return -1; } const auto after = ranges::find_if( _items, [&](not_null<Element*> view) { return (view->data()->position() >= position); }); return (after == end(_items)) ? int(_items.size() - 1) : int(after - begin(_items)); } HistoryItemsList ListWidget::collectVisibleItems() const { auto result = HistoryItemsList(); const auto from = std::lower_bound( begin(_items), end(_items), _visibleTop, [this](auto &elem, int top) { return this->itemTop(elem) + elem->height() <= top; }); const auto to = std::lower_bound( begin(_items), end(_items), _visibleBottom, [this](auto &elem, int bottom) { return this->itemTop(elem) < bottom; }); result.reserve(to - from); for (auto i = from; i != to; ++i) { result.push_back((*i)->data()); } return result; } void ListWidget::visibleTopBottomUpdated( int visibleTop, int visibleBottom) { if (!(visibleTop < visibleBottom)) { return; } const auto initializing = !(_visibleTop < _visibleBottom); const auto scrolledUp = (visibleTop < _visibleTop); _visibleTop = visibleTop; _visibleBottom = visibleBottom; if (initializing) { checkUnreadBarCreation(); } updateVisibleTopItem(); if (scrolledUp) { _scrollDateCheck.call(); } else { scrollDateHideByTimer(); } _controller->floatPlayerAreaUpdated().notify(true); _applyUpdatedScrollState.call(); } void ListWidget::applyUpdatedScrollState() { checkMoveToOtherViewer(); _delegate->listVisibleItemsChanged(collectVisibleItems()); } void ListWidget::updateVisibleTopItem() { if (_visibleBottom == height()) { _visibleTopItem = nullptr; } else if (_items.empty()) { _visibleTopItem = nullptr; _visibleTopFromItem = _visibleTop; } else { _visibleTopItem = findItemByY(_visibleTop); _visibleTopFromItem = _visibleTop - itemTop(_visibleTopItem); } } bool ListWidget::displayScrollDate() const { return (_visibleTop <= height() - 2 * (_visibleBottom - _visibleTop)); } void ListWidget::scrollDateCheck() { if (!_visibleTopItem) { _scrollDateLastItem = nullptr; _scrollDateLastItemTop = 0; scrollDateHide(); } else if (_visibleTopItem != _scrollDateLastItem || _visibleTopFromItem != _scrollDateLastItemTop) { // Show scroll date only if it is not the initial onScroll() event (with empty _scrollDateLastItem). if (_scrollDateLastItem && !_scrollDateShown) { toggleScrollDateShown(); } _scrollDateLastItem = _visibleTopItem; _scrollDateLastItemTop = _visibleTopFromItem; _scrollDateHideTimer.callOnce(kScrollDateHideTimeout); } } void ListWidget::scrollDateHideByTimer() { _scrollDateHideTimer.cancel(); if (!_scrollDateLink || ClickHandler::getPressed() != _scrollDateLink) { scrollDateHide(); } } void ListWidget::scrollDateHide() { if (_scrollDateShown) { toggleScrollDateShown(); } } void ListWidget::keepScrollDateForNow() { if (!_scrollDateShown && _scrollDateLastItem && _scrollDateOpacity.animating()) { toggleScrollDateShown(); } _scrollDateHideTimer.callOnce(kScrollDateHideTimeout); } void ListWidget::toggleScrollDateShown() { _scrollDateShown = !_scrollDateShown; auto from = _scrollDateShown ? 0. : 1.; auto to = _scrollDateShown ? 1. : 0.; _scrollDateOpacity.start([this] { repaintScrollDateCallback(); }, from, to, st::historyDateFadeDuration); } void ListWidget::repaintScrollDateCallback() { auto updateTop = _visibleTop; auto updateHeight = st::msgServiceMargin.top() + st::msgServicePadding.top() + st::msgServiceFont->height + st::msgServicePadding.bottom(); update(0, updateTop, width(), updateHeight); } auto ListWidget::collectSelectedItems() const -> SelectedItems { auto transformation = [&](const auto &item) { const auto [itemId, selection] = item; auto result = SelectedItem(itemId); result.canDelete = selection.canDelete; result.canForward = selection.canForward; return result; }; auto items = SelectedItems(); if (hasSelectedItems()) { items.reserve(_selected.size()); std::transform( _selected.begin(), _selected.end(), std::back_inserter(items), transformation); } return items; } MessageIdsList ListWidget::collectSelectedIds() const { const auto selected = collectSelectedItems(); return ranges::view::all( selected ) | ranges::view::transform([](const SelectedItem &item) { return item.msgId; }) | ranges::to_vector; } void ListWidget::pushSelectedItems() { _delegate->listSelectionChanged(collectSelectedItems()); } void ListWidget::removeItemSelection( const SelectedMap::const_iterator &i) { Expects(i != _selected.cend()); _selected.erase(i); if (_selected.empty()) { update(); } pushSelectedItems(); } bool ListWidget::hasSelectedText() const { return (_selectedTextItem != nullptr) && !hasSelectedItems(); } bool ListWidget::hasSelectedItems() const { return !_selected.empty(); } bool ListWidget::overSelectedItems() const { if (_overState.pointState == PointState::GroupPart) { return _overItemExact && _selected.contains(_overItemExact->fullId()); } else if (_overState.pointState == PointState::Inside) { return _overElement && isSelectedAsGroup(_selected, _overElement->data()); } return false; } bool ListWidget::isSelectedGroup( const SelectedMap &applyTo, not_null<const Data::Group*> group) const { for (const auto other : group->items) { if (!applyTo.contains(other->fullId())) { return false; } } return true; } bool ListWidget::isSelectedAsGroup( const SelectedMap &applyTo, not_null<HistoryItem*> item) const { if (const auto group = Auth().data().groups().find(item)) { return isSelectedGroup(applyTo, group); } return applyTo.contains(item->fullId()); } bool ListWidget::isGoodForSelection(not_null<HistoryItem*> item) const { return IsServerMsgId(item->id) && !item->serviceMsg(); } bool ListWidget::isGoodForSelection( SelectedMap &applyTo, not_null<HistoryItem*> item, int &totalCount) const { if (!isGoodForSelection(item)) { return false; } else if (!applyTo.contains(item->fullId())) { ++totalCount; } return (totalCount <= MaxSelectedItems); } bool ListWidget::addToSelection( SelectedMap &applyTo, not_null<HistoryItem*> item) const { const auto itemId = item->fullId(); auto [iterator, ok] = applyTo.try_emplace( itemId, SelectionData()); if (!ok) { return false; } iterator->second.canDelete = item->canDelete(); iterator->second.canForward = item->allowsForward(); return true; } bool ListWidget::removeFromSelection( SelectedMap &applyTo, FullMsgId itemId) const { return applyTo.remove(itemId); } void ListWidget::changeSelection( SelectedMap &applyTo, not_null<HistoryItem*> item, SelectAction action) const { const auto itemId = item->fullId(); if (action == SelectAction::Invert) { action = applyTo.contains(itemId) ? SelectAction::Deselect : SelectAction::Select; } if (action == SelectAction::Select) { auto already = int(applyTo.size()); if (isGoodForSelection(applyTo, item, already)) { addToSelection(applyTo, item); } } else { removeFromSelection(applyTo, itemId); } } void ListWidget::changeSelectionAsGroup( SelectedMap &applyTo, not_null<HistoryItem*> item, SelectAction action) const { const auto group = Auth().data().groups().find(item); if (!group) { return changeSelection(applyTo, item, action); } if (action == SelectAction::Invert) { action = isSelectedAsGroup(applyTo, item) ? SelectAction::Deselect : SelectAction::Select; } auto already = int(applyTo.size()); const auto canSelect = [&] { for (const auto other : group->items) { if (!isGoodForSelection(applyTo, other, already)) { return false; } } return true; }(); if (action == SelectAction::Select && canSelect) { for (const auto other : group->items) { addToSelection(applyTo, other); } } else { for (const auto other : group->items) { removeFromSelection(applyTo, other->fullId()); } } } bool ListWidget::isItemUnderPressSelected() const { return itemUnderPressSelection() != _selected.end(); } auto ListWidget::itemUnderPressSelection() -> SelectedMap::iterator { return (_pressState.itemId && _pressState.pointState != PointState::Outside) ? _selected.find(_pressState.itemId) : _selected.end(); } bool ListWidget::isInsideSelection( not_null<const Element*> view, not_null<HistoryItem*> exactItem, const MouseState &state) const { if (!_selected.empty()) { if (state.pointState == PointState::GroupPart) { return _selected.contains(exactItem->fullId()); } else { return isSelectedAsGroup(_selected, view->data()); } } else if (_selectedTextItem && _selectedTextItem == view->data() && state.pointState != PointState::Outside) { StateRequest stateRequest; stateRequest.flags |= Text::StateRequest::Flag::LookupSymbol; const auto dragState = view->textState( state.point, stateRequest); if (dragState.cursor == CursorState::Text && base::in_range( dragState.symbol, _selectedTextRange.from, _selectedTextRange.to)) { return true; } } return false; } auto ListWidget::itemUnderPressSelection() const -> SelectedMap::const_iterator { return (_pressState.itemId && _pressState.pointState != PointState::Outside) ? _selected.find(_pressState.itemId) : _selected.end(); } bool ListWidget::requiredToStartDragging( not_null<Element*> view) const { if (_mouseCursorState == CursorState::Date) { return true; } else if (const auto media = view->media()) { return media->type() == MediaTypeSticker; } return false; } bool ListWidget::isPressInSelectedText(TextState state) const { if (state.cursor != CursorState::Text) { return false; } if (!hasSelectedText() || !_selectedTextItem || _selectedTextItem->fullId() != _pressState.itemId) { return false; } auto from = _selectedTextRange.from; auto to = _selectedTextRange.to; return (state.symbol >= from && state.symbol < to); } void ListWidget::cancelSelection() { clearSelected(); clearTextSelection(); } void ListWidget::selectItem(not_null<HistoryItem*> item) { if (const auto view = viewForItem(item)) { clearTextSelection(); changeSelection( _selected, item, SelectAction::Select); pushSelectedItems(); } } void ListWidget::selectItemAsGroup(not_null<HistoryItem*> item) { if (const auto view = viewForItem(item)) { clearTextSelection(); changeSelectionAsGroup( _selected, item, SelectAction::Select); pushSelectedItems(); } } void ListWidget::clearSelected() { if (_selected.empty()) { return; } if (hasSelectedText()) { repaintItem(_selected.begin()->first); _selected.clear(); } else { _selected.clear(); pushSelectedItems(); update(); } } void ListWidget::clearTextSelection() { if (_selectedTextItem) { if (const auto view = viewForItem(_selectedTextItem)) { repaintItem(view); } _selectedTextItem = nullptr; _selectedTextRange = TextSelection(); _selectedText = TextWithEntities(); } } void ListWidget::setTextSelection( not_null<Element*> view, TextSelection selection) { clearSelected(); const auto item = view->data(); if (_selectedTextItem != item) { clearTextSelection(); _selectedTextItem = view->data(); } _selectedTextRange = selection; _selectedText = (selection.from != selection.to) ? view->selectedText(selection) : TextWithEntities(); repaintItem(view); if (!_wasSelectedText && !_selectedText.text.isEmpty()) { _wasSelectedText = true; setFocus(); } } bool ListWidget::loadedAtTopKnown() const { return !!_slice.skippedBefore; } bool ListWidget::loadedAtTop() const { return _slice.skippedBefore && (*_slice.skippedBefore == 0); } bool ListWidget::loadedAtBottomKnown() const { return !!_slice.skippedAfter; } bool ListWidget::loadedAtBottom() const { return _slice.skippedAfter && (*_slice.skippedAfter == 0); } bool ListWidget::isEmpty() const { return loadedAtTop() && loadedAtBottom() && (_itemsHeight == 0); } int ListWidget::itemMinimalHeight() const { return st::msgMarginTopAttached + st::msgPhotoSize + st::msgMargin.bottom(); } void ListWidget::checkMoveToOtherViewer() { auto visibleHeight = (_visibleBottom - _visibleTop); if (width() <= 0 || visibleHeight <= 0 || _items.empty() || _aroundIndex < 0 || _scrollTopState.item) { return; } auto topItem = findItemByY(_visibleTop); auto bottomItem = findItemByY(_visibleBottom); auto preloadedHeight = kPreloadedScreensCountFull * visibleHeight; auto preloadedCount = preloadedHeight / _itemAverageHeight; auto preloadIdsLimitMin = (preloadedCount / 2) + 1; auto preloadIdsLimit = preloadIdsLimitMin + (visibleHeight / _itemAverageHeight); auto preloadBefore = kPreloadIfLessThanScreens * visibleHeight; auto before = _slice.skippedBefore; auto preloadTop = (_visibleTop < preloadBefore); auto topLoaded = before && (*before == 0); auto after = _slice.skippedAfter; auto preloadBottom = (height() - _visibleBottom < preloadBefore); auto bottomLoaded = after && (*after == 0); auto minScreenDelta = kPreloadedScreensCount - kPreloadIfLessThanScreens; auto minUniversalIdDelta = (minScreenDelta * visibleHeight) / _itemAverageHeight; auto preloadAroundMessage = [&](not_null<Element*> view) { auto preloadRequired = false; auto itemPosition = view->data()->position(); auto itemIndex = ranges::find(_items, view) - begin(_items); Assert(itemIndex < _items.size()); if (!preloadRequired) { preloadRequired = (_idsLimit < preloadIdsLimitMin); } if (!preloadRequired) { Assert(_aroundIndex >= 0); auto delta = std::abs(itemIndex - _aroundIndex); preloadRequired = (delta >= minUniversalIdDelta); } if (preloadRequired) { _idsLimit = preloadIdsLimit; _aroundPosition = itemPosition; _aroundIndex = itemIndex; refreshViewer(); } }; if (preloadTop && !topLoaded) { preloadAroundMessage(topItem); } else if (preloadBottom && !bottomLoaded) { preloadAroundMessage(bottomItem); } } QString ListWidget::tooltipText() const { const auto item = (_overElement && _mouseAction == MouseAction::None) ? _overElement->data().get() : nullptr; if (_mouseCursorState == CursorState::Date && item) { return _overElement->dateTime().toString( QLocale::system().dateTimeFormat(QLocale::LongFormat)); } else if (_mouseCursorState == CursorState::Forwarded && item) { if (const auto forwarded = item->Get<HistoryMessageForwarded>()) { return forwarded->text.originalText( AllTextSelection, ExpandLinksNone); } } else if (const auto link = ClickHandler::getActive()) { return link->tooltip(); } return QString(); } QPoint ListWidget::tooltipPos() const { return _mousePosition; } Context ListWidget::elementContext() { return _delegate->listContext(); } std::unique_ptr<Element> ListWidget::elementCreate( not_null<HistoryMessage*> message) { return std::make_unique<Message>(this, message); } std::unique_ptr<Element> ListWidget::elementCreate( not_null<HistoryService*> message) { return std::make_unique<Service>(this, message); } bool ListWidget::elementUnderCursor( not_null<const HistoryView::Element*> view) { return (_overElement == view); } void ListWidget::elementAnimationAutoplayAsync( not_null<const Element*> view) { crl::on_main(this, [this, msgId = view->data()->fullId()]{ if (const auto view = viewForItem(msgId)) { if (const auto media = view->media()) { media->autoplayAnimation(); } } }); } TimeMs ListWidget::elementHighlightTime( not_null<const HistoryView::Element*> element) { if (element->data()->fullId() == _highlightedMessageId) { if (_highlightTimer.isActive()) { return getms() - _highlightStart; } } return TimeMs(0); } bool ListWidget::elementInSelectionMode() { return hasSelectedItems() || !_dragSelected.empty(); } void ListWidget::saveState(not_null<ListMemento*> memento) { memento->setAroundPosition(_aroundPosition); auto state = countScrollState(); if (state.item) { memento->setIdsLimit(_idsLimit); memento->setScrollTopState(state); } } void ListWidget::restoreState(not_null<ListMemento*> memento) { _aroundPosition = memento->aroundPosition(); _aroundIndex = -1; if (const auto limit = memento->idsLimit()) { _idsLimit = limit; _scrollTopState = memento->scrollTopState(); } refreshViewer(); } void ListWidget::updateItemsGeometry() { const auto count = int(_items.size()); const auto first = [&] { for (auto i = 0; i != count; ++i) { const auto view = _items[i].get(); if (view->isHidden()) { view->setDisplayDate(false); } else { view->setDisplayDate(true); return i; } } return count; }(); refreshAttachmentsFromTill(first, count); } void ListWidget::updateSize() { resizeToWidth(width(), _minHeight); updateVisibleTopItem(); } void ListWidget::resizeToWidth(int newWidth, int minHeight) { _minHeight = minHeight; TWidget::resizeToWidth(newWidth); restoreScrollPosition(); } int ListWidget::resizeGetHeight(int newWidth) { update(); const auto resizeAllItems = (_itemsWidth != newWidth); auto newHeight = 0; for (auto &view : _items) { view->setY(newHeight); if (view->pendingResize() || resizeAllItems) { newHeight += view->resizeGetHeight(newWidth); } else { newHeight += view->height(); } } if (newHeight > 0) { _itemAverageHeight = std::max( itemMinimalHeight(), newHeight / int(_items.size())); } _itemsWidth = newWidth; _itemsHeight = newHeight; _itemsTop = (_minHeight > _itemsHeight + st::historyPaddingBottom) ? (_minHeight - _itemsHeight - st::historyPaddingBottom) : 0; return _itemsTop + _itemsHeight + st::historyPaddingBottom; } void ListWidget::restoreScrollPosition() { auto newVisibleTop = _visibleTopItem ? (itemTop(_visibleTopItem) + _visibleTopFromItem) : ScrollMax; _delegate->listScrollTo(newVisibleTop); } TextSelection ListWidget::computeRenderSelection( not_null<const SelectedMap*> selected, not_null<const Element*> view) const { const auto itemSelection = [&](not_null<HistoryItem*> item) { auto i = selected->find(item->fullId()); if (i != selected->end()) { return FullSelection; } return TextSelection(); }; const auto item = view->data(); if (const auto group = Auth().data().groups().find(item)) { if (group->items.back() != item) { return TextSelection(); } auto result = TextSelection(); auto allFullSelected = true; const auto count = int(group->items.size()); for (auto i = 0; i != count; ++i) { if (itemSelection(group->items[i]) == FullSelection) { result = AddGroupItemSelection(result, i); } else { allFullSelected = false; } } if (allFullSelected) { return FullSelection; } const auto leaderSelection = itemSelection(item); if (leaderSelection != FullSelection && leaderSelection != TextSelection()) { return leaderSelection; } return result; } return itemSelection(item); } TextSelection ListWidget::itemRenderSelection( not_null<const Element*> view) const { if (!_dragSelected.empty()) { const auto i = _dragSelected.find(view->data()->fullId()); if (i != _dragSelected.end()) { return (_dragSelectAction == DragSelectAction::Selecting) ? FullSelection : TextSelection(); } } if (!_selected.empty() || !_dragSelected.empty()) { return computeRenderSelection(&_selected, view); } else if (view->data() == _selectedTextItem) { return _selectedTextRange; } return TextSelection(); } void ListWidget::paintEvent(QPaintEvent *e) { if (Ui::skipPaintEvent(this, e)) { return; } Painter p(this); auto ms = getms(); auto clip = e->rect(); auto from = std::lower_bound(begin(_items), end(_items), clip.top(), [this](auto &elem, int top) { return this->itemTop(elem) + elem->height() <= top; }); auto to = std::lower_bound(begin(_items), end(_items), clip.top() + clip.height(), [this](auto &elem, int bottom) { return this->itemTop(elem) < bottom; }); if (from != end(_items)) { auto top = itemTop(from->get()); p.translate(0, top); for (auto i = from; i != to; ++i) { const auto view = *i; view->draw( p, clip.translated(0, -top), itemRenderSelection(view), ms); const auto height = view->height(); top += height; p.translate(0, height); } p.translate(0, -top); enumerateUserpics([&](not_null<Element*> view, int userpicTop) { // stop the enumeration if the userpic is below the painted rect if (userpicTop >= clip.top() + clip.height()) { return false; } // paint the userpic if it intersects the painted rect if (userpicTop + st::msgPhotoSize > clip.top()) { const auto message = view->data()->toHistoryMessage(); Assert(message != nullptr); message->from()->paintUserpicLeft( p, st::historyPhotoLeft, userpicTop, view->width(), st::msgPhotoSize); } return true; }); auto dateHeight = st::msgServicePadding.bottom() + st::msgServiceFont->height + st::msgServicePadding.top(); auto scrollDateOpacity = _scrollDateOpacity.current(ms, _scrollDateShown ? 1. : 0.); enumerateDates([&](not_null<Element*> view, int itemtop, int dateTop) { // stop the enumeration if the date is above the painted rect if (dateTop + dateHeight <= clip.top()) { return false; } const auto displayDate = view->displayDate(); auto dateInPlace = displayDate; if (dateInPlace) { const auto correctDateTop = itemtop + st::msgServiceMargin.top(); dateInPlace = (dateTop < correctDateTop + dateHeight); } //bool noFloatingDate = (item->date.date() == lastDate && displayDate); //if (noFloatingDate) { // if (itemtop < showFloatingBefore) { // noFloatingDate = false; // } //} // paint the date if it intersects the painted rect if (dateTop < clip.top() + clip.height()) { auto opacity = (dateInPlace/* || noFloatingDate*/) ? 1. : scrollDateOpacity; if (opacity > 0.) { p.setOpacity(opacity); int dateY = /*noFloatingDate ? itemtop :*/ (dateTop - st::msgServiceMargin.top()); int width = view->width(); if (const auto date = view->Get<HistoryView::DateBadge>()) { date->paint(p, dateY, width); } else { ServiceMessagePainter::paintDate( p, view->dateTime(), dateY, width); } } } return true; }); } } void ListWidget::applyDragSelection() { applyDragSelection(_selected); clearDragSelection(); pushSelectedItems(); } void ListWidget::applyDragSelection(SelectedMap &applyTo) const { if (_dragSelectAction == DragSelectAction::Selecting) { for (const auto itemId : _dragSelected) { if (applyTo.size() >= MaxSelectedItems) { break; } else if (!applyTo.contains(itemId)) { if (const auto item = App::histItemById(itemId)) { addToSelection(applyTo, item); } } } } else if (_dragSelectAction == DragSelectAction::Deselecting) { for (const auto itemId : _dragSelected) { removeFromSelection(applyTo, itemId); } } } TextWithEntities ListWidget::getSelectedText() const { auto selected = _selected; if (_mouseAction == MouseAction::Selecting && !_dragSelected.empty()) { applyDragSelection(selected); } if (selected.empty()) { if (const auto view = viewForItem(_selectedTextItem)) { return view->selectedText(_selectedTextRange); } return _selectedText; } const auto timeFormat = qsl(", [dd.MM.yy hh:mm]\n"); auto groups = base::flat_set<not_null<const Data::Group*>>(); auto fullSize = 0; auto texts = std::vector<std::pair< not_null<HistoryItem*>, TextWithEntities>>(); texts.reserve(selected.size()); const auto wrapItem = [&]( not_null<HistoryItem*> item, TextWithEntities &&unwrapped) { auto time = ItemDateTime(item).toString(timeFormat); auto part = TextWithEntities(); auto size = item->author()->name.size() + time.size() + unwrapped.text.size(); part.text.reserve(size); part.text.append(item->author()->name).append(time); TextUtilities::Append(part, std::move(unwrapped)); texts.push_back(std::make_pair(std::move(item), std::move(part))); fullSize += size; }; const auto addItem = [&](not_null<HistoryItem*> item) { wrapItem(item, HistoryItemText(item)); }; const auto addGroup = [&](not_null<const Data::Group*> group) { Expects(!group->items.empty()); wrapItem(group->items.back(), HistoryGroupText(group)); }; for (const auto [itemId, data] : selected) { if (const auto item = App::histItemById(itemId)) { if (const auto group = Auth().data().groups().find(item)) { if (groups.contains(group)) { continue; } if (isSelectedGroup(selected, group)) { groups.emplace(group); addGroup(group); } else { addItem(item); } } else { addItem(item); } } } ranges::sort(texts, [&]( const std::pair<not_null<HistoryItem*>, TextWithEntities> &a, const std::pair<not_null<HistoryItem*>, TextWithEntities> &b) { return _delegate->listIsLessInOrder(a.first, b.first); }); auto result = TextWithEntities(); auto sep = qsl("\n\n"); result.text.reserve(fullSize + (texts.size() - 1) * sep.size()); for (auto i = begin(texts), e = end(texts); i != e;) { TextUtilities::Append(result, std::move(i->second)); if (++i != e) { result.text.append(sep); } } return result; } MessageIdsList ListWidget::getSelectedItems() const { return collectSelectedIds(); } not_null<Element*> ListWidget::findItemByY(int y) const { Expects(!_items.empty()); if (y < _itemsTop) { return _items.front(); } auto i = std::lower_bound( begin(_items), end(_items), y, [this](auto &elem, int top) { return this->itemTop(elem) + elem->height() <= top; }); return (i != end(_items)) ? i->get() : _items.back().get(); } Element *ListWidget::strictFindItemByY(int y) const { if (_items.empty()) { return nullptr; } return (y >= _itemsTop && y < _itemsTop + _itemsHeight) ? findItemByY(y).get() : nullptr; } auto ListWidget::countScrollState() const -> ScrollTopState { if (_items.empty()) { return { Data::MessagePosition(), 0 }; } auto topItem = findItemByY(_visibleTop); return { topItem->data()->position(), _visibleTop - itemTop(topItem) }; } void ListWidget::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Escape || e->key() == Qt::Key_Back) { if (hasSelectedText() || hasSelectedItems()) { cancelSelection(); } else { _delegate->listCancelRequest(); } } else if (e == QKeySequence::Copy && (hasSelectedText() || hasSelectedItems())) { SetClipboardWithEntities(getSelectedText()); #ifdef Q_OS_MAC } else if (e->key() == Qt::Key_E && e->modifiers().testFlag(Qt::ControlModifier)) { SetClipboardWithEntities(getSelectedText(), QClipboard::FindBuffer); #endif // Q_OS_MAC } else if (e == QKeySequence::Delete) { _delegate->listDeleteRequest(); } else { e->ignore(); } } void ListWidget::mouseDoubleClickEvent(QMouseEvent *e) { mouseActionStart(e->globalPos(), e->button()); trySwitchToWordSelection(); } void ListWidget::trySwitchToWordSelection() { auto selectingSome = (_mouseAction == MouseAction::Selecting) && hasSelectedText(); auto willSelectSome = (_mouseAction == MouseAction::None) && !hasSelectedItems(); auto checkSwitchToWordSelection = _overElement && (_mouseSelectType == TextSelectType::Letters) && (selectingSome || willSelectSome); if (checkSwitchToWordSelection) { switchToWordSelection(); } } void ListWidget::switchToWordSelection() { Expects(_overElement != nullptr); StateRequest request; request.flags |= Text::StateRequest::Flag::LookupSymbol; auto dragState = _overElement->textState(_pressState.point, request); if (dragState.cursor != CursorState::Text) { return; } _mouseTextSymbol = dragState.symbol; _mouseSelectType = TextSelectType::Words; if (_mouseAction == MouseAction::None) { _mouseAction = MouseAction::Selecting; setTextSelection(_overElement, TextSelection( dragState.symbol, dragState.symbol )); } mouseActionUpdate(); _trippleClickPoint = _mousePosition; _trippleClickStartTime = getms(); } void ListWidget::validateTrippleClickStartTime() { if (_trippleClickStartTime) { const auto elapsed = (getms() - _trippleClickStartTime); if (elapsed >= QApplication::doubleClickInterval()) { _trippleClickStartTime = 0; } } } void ListWidget::contextMenuEvent(QContextMenuEvent *e) { showContextMenu(e); } void ListWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { if (e->reason() == QContextMenuEvent::Mouse) { mouseActionUpdate(e->globalPos()); } ContextMenuRequest request; request.link = ClickHandler::getActive(); request.view = _overElement; request.item = _overItemExact ? _overItemExact : _overElement ? _overElement->data().get() : nullptr; request.pointState = _overState.pointState; request.selectedText = _selectedText; request.selectedItems = collectSelectedItems(); request.overSelection = showFromTouch || (_overElement && isInsideSelection( _overElement, _overItemExact ? _overItemExact : _overElement->data().get(), _overState)); _menu = FillContextMenu(this, request); if (_menu && !_menu->actions().isEmpty()) { _menu->popup(e->globalPos()); e->accept(); } else if (_menu) { _menu = nullptr; } } void ListWidget::mousePressEvent(QMouseEvent *e) { if (_menu) { e->accept(); return; // ignore mouse press, that was hiding context menu } mouseActionStart(e->globalPos(), e->button()); } void ListWidget::mouseMoveEvent(QMouseEvent *e) { static auto lastGlobalPosition = e->globalPos(); auto reallyMoved = (lastGlobalPosition != e->globalPos()); auto buttonsPressed = (e->buttons() & (Qt::LeftButton | Qt::MiddleButton)); if (!buttonsPressed && _mouseAction != MouseAction::None) { mouseReleaseEvent(e); } if (reallyMoved) { lastGlobalPosition = e->globalPos(); if (!buttonsPressed || (_scrollDateLink && ClickHandler::getPressed() == _scrollDateLink)) { keepScrollDateForNow(); } } mouseActionUpdate(e->globalPos()); } void ListWidget::mouseReleaseEvent(QMouseEvent *e) { mouseActionFinish(e->globalPos(), e->button()); if (!rect().contains(e->pos())) { leaveEvent(e); } } void ListWidget::enterEventHook(QEvent *e) { mouseActionUpdate(QCursor::pos()); return TWidget::enterEventHook(e); } void ListWidget::leaveEventHook(QEvent *e) { if (const auto view = _overElement) { if (_overState.pointState != PointState::Outside) { repaintItem(view); _overState.pointState = PointState::Outside; } } ClickHandler::clearActive(); Ui::Tooltip::Hide(); if (!ClickHandler::getPressed() && _cursor != style::cur_default) { _cursor = style::cur_default; setCursor(_cursor); } return TWidget::leaveEventHook(e); } void ListWidget::updateDragSelection() { if (!_overState.itemId || !_pressState.itemId) { clearDragSelection(); return; } else if (_items.empty() || !_overElement || !_selectEnabled) { return; } const auto pressItem = App::histItemById(_pressState.itemId); if (!pressItem) { return; } const auto overView = _overElement; const auto pressView = viewForItem(pressItem); const auto selectingUp = _delegate->listIsLessInOrder( overView->data(), pressItem); if (selectingUp != _dragSelectDirectionUp) { _dragSelectDirectionUp = selectingUp; _dragSelectAction = DragSelectAction::None; } const auto fromView = selectingUp ? overView : pressView; const auto tillView = selectingUp ? pressView : overView; updateDragSelection( selectingUp ? overView : pressView, selectingUp ? _overState : _pressState, selectingUp ? pressView : overView, selectingUp ? _pressState : _overState); } void ListWidget::updateDragSelection( const Element *fromView, const MouseState &fromState, const Element *tillView, const MouseState &tillState) { Expects(fromView != nullptr || tillView != nullptr); const auto delta = QApplication::startDragDistance(); const auto includeFrom = [&] ( not_null<const Element*> view, const MouseState &state) { const auto bottom = view->height() - view->marginBottom(); return (state.point.y() < bottom - delta); }; const auto includeTill = [&] ( not_null<const Element*> view, const MouseState &state) { const auto top = view->marginTop(); return (state.point.y() >= top + delta); }; const auto includeSingleItem = [&] ( not_null<const Element*> view, const MouseState &state1, const MouseState &state2) { const auto top = view->marginTop(); const auto bottom = view->height() - view->marginBottom(); const auto y1 = std::min(state1.point.y(), state2.point.y()); const auto y2 = std::max(state1.point.y(), state2.point.y()); return (y1 < bottom - delta && y2 >= top + delta) ? (y2 - y1 >= delta) : false; }; const auto from = [&] { const auto result = fromView ? ranges::find( _items, fromView, [](auto view) { return view.get(); }) : end(_items); return (result == end(_items)) ? begin(_items) : (fromView == tillView || includeFrom(fromView, fromState)) ? result : (result + 1); }(); const auto till = [&] { if (fromView == tillView) { return (from == end(_items)) ? from : includeSingleItem(fromView, fromState, tillState) ? (from + 1) : from; } const auto result = tillView ? ranges::find( _items, tillView, [](auto view) { return view.get(); }) : end(_items); return (result == end(_items)) ? end(_items) : includeTill(tillView, tillState) ? (result + 1) : result; }(); if (from < till) { updateDragSelection(from, till); } else { clearDragSelection(); } } void ListWidget::updateDragSelection( std::vector<not_null<Element*>>::const_iterator from, std::vector<not_null<Element*>>::const_iterator till) { Expects(from < till); const auto &groups = Auth().data().groups(); const auto changeItem = [&](not_null<HistoryItem*> item, bool add) { const auto itemId = item->fullId(); if (add) { _dragSelected.emplace(itemId); } else { _dragSelected.remove(itemId); } }; const auto changeGroup = [&](not_null<HistoryItem*> item, bool add) { if (const auto group = groups.find(item)) { for (const auto item : group->items) { if (!isGoodForSelection(item)) { return; } } for (const auto item : group->items) { changeItem(item, add); } } else if (isGoodForSelection(item)) { changeItem(item, add); } }; const auto changeView = [&](not_null<Element*> view, bool add) { if (!view->isHidden()) { changeGroup(view->data(), add); } }; for (auto i = begin(_items); i != from; ++i) { changeView(*i, false); } for (auto i = from; i != till; ++i) { changeView(*i, true); } for (auto i = till; i != end(_items); ++i) { changeView(*i, false); } ensureDragSelectAction(from, till); update(); } void ListWidget::ensureDragSelectAction( std::vector<not_null<Element*>>::const_iterator from, std::vector<not_null<Element*>>::const_iterator till) { if (_dragSelectAction != DragSelectAction::None) { return; } const auto start = _dragSelectDirectionUp ? (till - 1) : from; const auto startId = (*start)->data()->fullId(); _dragSelectAction = _selected.contains(startId) ? DragSelectAction::Deselecting : DragSelectAction::Selecting; if (!_wasSelectedText && !_dragSelected.empty() && _dragSelectAction == DragSelectAction::Selecting) { _wasSelectedText = true; setFocus(); } } void ListWidget::clearDragSelection() { _dragSelectAction = DragSelectAction::None; if (!_dragSelected.empty()) { _dragSelected.clear(); update(); } } void ListWidget::mouseActionStart( const QPoint &globalPosition, Qt::MouseButton button) { mouseActionUpdate(globalPosition); if (button != Qt::LeftButton) { return; } ClickHandler::pressed(); if (_pressState != _overState) { if (_pressState.itemId != _overState.itemId) { repaintItem(_pressState.itemId); } _pressState = _overState; repaintItem(_overState.itemId); } _pressItemExact = _overItemExact; const auto pressElement = _overElement; _mouseAction = MouseAction::None; _pressWasInactive = _controller->window()->wasInactivePress(); if (_pressWasInactive) _controller->window()->setInactivePress(false); if (ClickHandler::getPressed()) { _mouseAction = MouseAction::PrepareDrag; } else if (hasSelectedItems()) { if (overSelectedItems()) { _mouseAction = MouseAction::PrepareDrag; } else if (!_pressWasInactive) { _mouseAction = MouseAction::PrepareSelect; } } if (_mouseAction == MouseAction::None && pressElement) { validateTrippleClickStartTime(); TextState dragState; auto startDistance = (globalPosition - _trippleClickPoint).manhattanLength(); auto validStartPoint = startDistance < QApplication::startDragDistance(); if (_trippleClickStartTime != 0 && validStartPoint) { StateRequest request; request.flags = Text::StateRequest::Flag::LookupSymbol; dragState = pressElement->textState(_pressState.point, request); if (dragState.cursor == CursorState::Text) { setTextSelection(pressElement, TextSelection( dragState.symbol, dragState.symbol )); _mouseTextSymbol = dragState.symbol; _mouseAction = MouseAction::Selecting; _mouseSelectType = TextSelectType::Paragraphs; mouseActionUpdate(); _trippleClickStartTime = getms(); } } else if (pressElement) { StateRequest request; request.flags = Text::StateRequest::Flag::LookupSymbol; dragState = pressElement->textState(_pressState.point, request); } if (_mouseSelectType != TextSelectType::Paragraphs) { _mouseTextSymbol = dragState.symbol; if (isPressInSelectedText(dragState)) { _mouseAction = MouseAction::PrepareDrag; // start text drag } else if (!_pressWasInactive) { if (requiredToStartDragging(pressElement)) { _mouseAction = MouseAction::PrepareDrag; } else { if (dragState.afterSymbol) ++_mouseTextSymbol; if (!hasSelectedItems() && _overState.pointState != PointState::Outside) { setTextSelection(pressElement, TextSelection( _mouseTextSymbol, _mouseTextSymbol)); _mouseAction = MouseAction::Selecting; } else { _mouseAction = MouseAction::PrepareSelect; } } } } } if (!pressElement) { _mouseAction = MouseAction::None; } else if (_mouseAction == MouseAction::None) { mouseActionCancel(); } } void ListWidget::mouseActionUpdate(const QPoint &globalPosition) { _mousePosition = globalPosition; mouseActionUpdate(); } void ListWidget::mouseActionCancel() { _pressState = MouseState(); _pressItemExact = nullptr; _mouseAction = MouseAction::None; clearDragSelection(); _wasSelectedText = false; //_widget->noSelectingScroll(); // #TODO select scroll } void ListWidget::mouseActionFinish( const QPoint &globalPosition, Qt::MouseButton button) { mouseActionUpdate(globalPosition); auto pressState = base::take(_pressState); base::take(_pressItemExact); repaintItem(pressState.itemId); const auto toggleByHandler = [&](const ClickHandlerPtr &handler) { if (_overElement) { // If we are in selecting items mode perhaps we want to // toggle selection instead of activating the pressed link. if (const auto media = _overElement->media()) { if (media->toggleSelectionByHandlerClick(handler)) { return true; } } } return false; }; auto activated = ClickHandler::unpressed(); auto simpleSelectionChange = pressState.itemId && !_pressWasInactive && (button != Qt::RightButton) && (_mouseAction == MouseAction::PrepareSelect || _mouseAction == MouseAction::PrepareDrag); auto needItemSelectionToggle = simpleSelectionChange && (!activated || toggleByHandler(activated)) && hasSelectedItems(); auto needTextSelectionClear = simpleSelectionChange && hasSelectedText(); _wasSelectedText = false; if (_mouseAction == MouseAction::Dragging || _mouseAction == MouseAction::Selecting || needItemSelectionToggle) { activated = nullptr; } else if (activated) { mouseActionCancel(); App::activateClickHandler(activated, button); return; } if (needItemSelectionToggle) { if (const auto item = App::histItemById(pressState.itemId)) { clearTextSelection(); if (pressState.pointState == PointState::GroupPart) { changeSelection( _selected, _overItemExact ? _overItemExact : item, SelectAction::Invert); } else { changeSelectionAsGroup( _selected, item, SelectAction::Invert); } pushSelectedItems(); } } else if (needTextSelectionClear) { clearTextSelection(); } else if (_mouseAction == MouseAction::Selecting) { if (!_dragSelected.empty()) { applyDragSelection(); } else if (_selectedTextItem && !_pressWasInactive) { if (_selectedTextRange.from == _selectedTextRange.to) { clearTextSelection(); App::wnd()->setInnerFocus(); } } } _mouseAction = MouseAction::None; _mouseSelectType = TextSelectType::Letters; //_widget->noSelectingScroll(); // #TODO select scroll #if defined Q_OS_LINUX32 || defined Q_OS_LINUX64 if (_selectedTextItem && _selectedTextRange.from != _selectedTextRange.to) { if (const auto view = viewForItem(_selectedTextItem)) { SetClipboardWithEntities( view->selectedText(_selectedTextRange), QClipboard::Selection); } } #endif // Q_OS_LINUX32 || Q_OS_LINUX64 } void ListWidget::mouseActionUpdate() { auto mousePosition = mapFromGlobal(_mousePosition); auto point = QPoint(snap(mousePosition.x(), 0, width()), snap(mousePosition.y(), _visibleTop, _visibleBottom)); const auto view = strictFindItemByY(point.y()); const auto item = view ? view->data().get() : nullptr; const auto itemPoint = mapPointToItem(point, view); _overState = MouseState( item ? item->fullId() : FullMsgId(), view ? view->height() : 0, itemPoint, view ? view->pointState(itemPoint) : PointState::Outside); if (_overElement != view) { repaintItem(_overElement); _overElement = view; repaintItem(_overElement); } TextState dragState; ClickHandlerHost *lnkhost = nullptr; auto inTextSelection = (_overState.pointState != PointState::Outside) && (_overState.itemId == _pressState.itemId) && hasSelectedText(); if (view) { auto cursorDeltaLength = [&] { auto cursorDelta = (_overState.point - _pressState.point); return cursorDelta.manhattanLength(); }; auto dragStartLength = [] { return QApplication::startDragDistance(); }; if (_overState.itemId != _pressState.itemId || cursorDeltaLength() >= dragStartLength()) { if (_mouseAction == MouseAction::PrepareDrag) { _mouseAction = MouseAction::Dragging; InvokeQueued(this, [this] { performDrag(); }); } else if (_mouseAction == MouseAction::PrepareSelect) { _mouseAction = MouseAction::Selecting; } } StateRequest request; if (_mouseAction == MouseAction::Selecting) { request.flags |= Text::StateRequest::Flag::LookupSymbol; } else { inTextSelection = false; } const auto dateHeight = st::msgServicePadding.bottom() + st::msgServiceFont->height + st::msgServicePadding.top(); const auto scrollDateOpacity = _scrollDateOpacity.current(_scrollDateShown ? 1. : 0.); enumerateDates([&](not_null<Element*> view, int itemtop, int dateTop) { // stop enumeration if the date is above our point if (dateTop + dateHeight <= point.y()) { return false; } const auto displayDate = view->displayDate(); auto dateInPlace = displayDate; if (dateInPlace) { const auto correctDateTop = itemtop + st::msgServiceMargin.top(); dateInPlace = (dateTop < correctDateTop + dateHeight); } // stop enumeration if we've found a date under the cursor if (dateTop <= point.y()) { auto opacity = (dateInPlace/* || noFloatingDate*/) ? 1. : scrollDateOpacity; if (opacity > 0.) { const auto item = view->data(); auto dateWidth = 0; if (const auto date = view->Get<HistoryView::DateBadge>()) { dateWidth = date->width; } else { dateWidth = st::msgServiceFont->width(langDayOfMonthFull(view->dateTime().date())); } dateWidth += st::msgServicePadding.left() + st::msgServicePadding.right(); auto dateLeft = st::msgServiceMargin.left(); auto maxwidth = view->width(); if (Adaptive::ChatWide()) { maxwidth = qMin(maxwidth, int32(st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left())); } auto widthForDate = maxwidth - st::msgServiceMargin.left() - st::msgServiceMargin.left(); dateLeft += (widthForDate - dateWidth) / 2; if (point.x() >= dateLeft && point.x() < dateLeft + dateWidth) { _scrollDateLink = _delegate->listDateLink(view); dragState = TextState( nullptr, _scrollDateLink); _overItemExact = App::histItemById(dragState.itemId); lnkhost = view; } } return false; } return true; }); if (!dragState.link) { dragState = view->textState(itemPoint, request); _overItemExact = App::histItemById(dragState.itemId); lnkhost = view; if (!dragState.link && itemPoint.x() >= st::historyPhotoLeft && itemPoint.x() < st::historyPhotoLeft + st::msgPhotoSize) { if (view->hasFromPhoto()) { enumerateUserpics([&](not_null<Element*> view, int userpicTop) { // stop enumeration if the userpic is below our point if (userpicTop > point.y()) { return false; } // stop enumeration if we've found a userpic under the cursor if (point.y() >= userpicTop && point.y() < userpicTop + st::msgPhotoSize) { const auto message = view->data()->toHistoryMessage(); Assert(message != nullptr); dragState = TextState( nullptr, message->displayFrom()->openLink()); _overItemExact = App::histItemById(dragState.itemId); lnkhost = view; return false; } return true; }); } } } } auto lnkChanged = ClickHandler::setActive(dragState.link, lnkhost); if (lnkChanged || dragState.cursor != _mouseCursorState) { Ui::Tooltip::Hide(); } if (dragState.link || dragState.cursor == CursorState::Date || dragState.cursor == CursorState::Forwarded) { Ui::Tooltip::Show(1000, this); } auto cursor = style::cur_default; if (_mouseAction == MouseAction::None) { _mouseCursorState = dragState.cursor; auto cursor = computeMouseCursor(); if (_cursor != cursor) { setCursor((_cursor = cursor)); } } else if (view) { if (_mouseAction == MouseAction::Selecting) { if (inTextSelection) { auto second = dragState.symbol; if (dragState.afterSymbol && _mouseSelectType == TextSelectType::Letters) { ++second; } auto selection = TextSelection( qMin(second, _mouseTextSymbol), qMax(second, _mouseTextSymbol) ); if (_mouseSelectType != TextSelectType::Letters) { selection = view->adjustSelection( selection, _mouseSelectType); } setTextSelection(view, selection); clearDragSelection(); } else if (_pressState.itemId) { updateDragSelection(); } } else if (_mouseAction == MouseAction::Dragging) { } } // Voice message seek support. if (_pressState.pointState != PointState::Outside && ClickHandler::getPressed()) { if (const auto item = App::histItemById(_pressState.itemId)) { if (const auto view = viewForItem(item)) { auto adjustedPoint = mapPointToItem(point, view); view->updatePressed(adjustedPoint); } } } //if (_mouseAction == MouseAction::Selecting) { // _widget->checkSelectingScroll(mousePos); //} else { // _widget->noSelectingScroll(); //} // #TODO select scroll } style::cursor ListWidget::computeMouseCursor() const { if (ClickHandler::getPressed() || ClickHandler::getActive()) { return style::cur_pointer; } else if (!hasSelectedItems() && (_mouseCursorState == CursorState::Text)) { return style::cur_text; } return style::cur_default; } std::unique_ptr<QMimeData> ListWidget::prepareDrag() { if (_mouseAction != MouseAction::Dragging) { return nullptr; } auto pressedHandler = ClickHandler::getPressed(); if (dynamic_cast<VoiceSeekClickHandler*>(pressedHandler.get())) { return nullptr; } const auto pressedItem = App::histItemById(_pressState.itemId); const auto pressedView = viewForItem(pressedItem); const auto uponSelected = pressedView && isInsideSelection( pressedView, _pressItemExact ? _pressItemExact : pressedItem, _pressState); QList<QUrl> urls; auto text = [&] { if (uponSelected) { return getSelectedText(); } else if (pressedHandler) { return TextWithEntities{ pressedHandler->dragText(), EntitiesInText() }; } return TextWithEntities(); //if (!sel.isEmpty() && sel.at(0) != '/' && sel.at(0) != '@' && sel.at(0) != '#') { // urls.push_back(QUrl::fromEncoded(sel.toUtf8())); // Google Chrome crashes in Mac OS X O_o //} }(); if (auto mimeData = MimeDataFromTextWithEntities(text)) { clearDragSelection(); // _widget->noSelectingScroll(); #TODO scroll if (!urls.isEmpty()) { mimeData->setUrls(urls); } if (uponSelected && !Adaptive::OneColumn()) { const auto canForwardAll = [&] { for (const auto &[itemId, data] : _selected) { if (!data.canForward) { return false; } } return true; }(); auto items = canForwardAll ? getSelectedItems() : MessageIdsList(); if (!items.empty()) { Auth().data().setMimeForwardIds(std::move(items)); mimeData->setData(qsl("application/x-td-forward"), "1"); } } return mimeData; } else if (pressedView) { auto forwardIds = MessageIdsList(); const auto exactItem = _pressItemExact ? _pressItemExact : pressedItem; if (_mouseCursorState == CursorState::Date) { forwardIds = Auth().data().itemOrItsGroup(_overElement->data()); } else if (_pressState.pointState == PointState::GroupPart) { forwardIds = MessageIdsList(1, exactItem->fullId()); } else if (const auto media = pressedView->media()) { if (media->dragItemByHandler(pressedHandler) || media->dragItem()) { forwardIds = MessageIdsList(1, exactItem->fullId()); } } if (forwardIds.empty()) { return nullptr; } Auth().data().setMimeForwardIds(std::move(forwardIds)); auto result = std::make_unique<QMimeData>(); result->setData(qsl("application/x-td-forward"), "1"); if (const auto media = pressedView->media()) { if (const auto document = media->getDocument()) { const auto filepath = document->filepath( DocumentData::FilePathResolveChecked); if (!filepath.isEmpty()) { QList<QUrl> urls; urls.push_back(QUrl::fromLocalFile(filepath)); result->setUrls(urls); } } } return result; } return nullptr; } void ListWidget::performDrag() { if (auto mimeData = prepareDrag()) { // This call enters event loop and can destroy any QObject. _controller->window()->launchDrag(std::move(mimeData)); } } int ListWidget::itemTop(not_null<const Element*> view) const { return _itemsTop + view->y(); } void ListWidget::repaintItem(const Element *view) { if (!view) { return; } update(0, itemTop(view), width(), view->height()); } void ListWidget::repaintItem(FullMsgId itemId) { if (const auto view = viewForItem(itemId)) { repaintItem(view); } } void ListWidget::resizeItem(not_null<Element*> view) { const auto index = ranges::find(_items, view) - begin(_items); if (index < int(_items.size())) { refreshAttachmentsAtIndex(index); } } void ListWidget::refreshAttachmentsAtIndex(int index) { Expects(index >= 0 && index < _items.size()); const auto from = [&] { if (index > 0) { for (auto i = index - 1; i != 0; --i) { if (!_items[i]->isHidden()) { return i; } } } return index; }(); const auto till = [&] { const auto count = int(_items.size()); for (auto i = index + 1; i != count; ++i) { if (!_items[i]->isHidden()) { return i + 1; } } return index + 1; }(); refreshAttachmentsFromTill(from, till); } void ListWidget::refreshAttachmentsFromTill(int from, int till) { Expects(from >= 0 && from <= till && till <= int(_items.size())); if (from == till) { updateSize(); return; } auto view = _items[from].get(); for (auto i = from + 1; i != till; ++i) { const auto next = _items[i].get(); if (next->isHidden()) { next->setDisplayDate(false); } else { const auto viewDate = view->dateTime(); const auto nextDate = next->dateTime(); next->setDisplayDate(nextDate.date() != viewDate.date()); auto attached = next->computeIsAttachToPrevious(view); next->setAttachToPrevious(attached); view->setAttachToNext(attached); view = next; } } updateSize(); } void ListWidget::refreshItem(not_null<const Element*> view) { const auto i = ranges::find(_items, view); const auto index = i - begin(_items); if (index < int(_items.size())) { const auto item = view->data(); const auto was = [&]() -> std::unique_ptr<Element> { if (const auto i = _views.find(item); i != end(_views)) { auto result = std::move(i->second); _views.erase(i); return result; } return nullptr; }(); const auto [i, ok] = _views.emplace( item, item->createView(this)); const auto now = i->second.get(); _items[index] = now; viewReplaced(view, i->second.get()); refreshAttachmentsAtIndex(index); } } void ListWidget::viewReplaced(not_null<const Element*> was, Element *now) { if (_visibleTopItem == was) _visibleTopItem = now; if (_scrollDateLastItem == was) _scrollDateLastItem = now; if (_overElement == was) _overElement = now; if (_unreadBarElement == was) { const auto bar = _unreadBarElement->Get<UnreadBar>(); const auto count = bar ? bar->count : 0; const auto freezed = bar ? bar->freezed : false; _unreadBarElement = now; if (now && count) { _unreadBarElement->setUnreadBarCount(count); if (freezed) { _unreadBarElement->setUnreadBarFreezed(); } } } } void ListWidget::itemRemoved(not_null<const HistoryItem*> item) { if (_selectedTextItem == item) { clearTextSelection(); } if (_overItemExact == item) { _overItemExact = nullptr; } if (_pressItemExact == item) { _pressItemExact = nullptr; } const auto i = _views.find(item); if (i == end(_views)) { return; } const auto view = i->second.get(); _items.erase( ranges::remove(_items, view, [](auto view) { return view.get(); }), end(_items)); viewReplaced(view, nullptr); _views.erase(i); updateItemsGeometry(); } QPoint ListWidget::mapPointToItem( QPoint point, const Element *view) const { if (!view) { return QPoint(); } return point - QPoint(0, itemTop(view)); } ListWidget::~ListWidget() = default; } // namespace HistoryView ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/view/history_view_list_widget.h������������������������0000664�0000000�0000000�00000033432�13262451251�0027405�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/widgets/tooltip.h" #include "ui/rp_widget.h" #include "mtproto/sender.h" #include "base/timer.h" #include "data/data_messages.h" #include "history/view/history_view_element.h" namespace Ui { class PopupMenu; } // namespace Ui namespace Window { class Controller; } // namespace Window namespace Data { struct Group; } // namespace Data namespace HistoryView { struct TextState; struct StateRequest; enum class CursorState : char; enum class PointState : char; enum class Context : char; struct SelectedItem { explicit SelectedItem(FullMsgId msgId) : msgId(msgId) { } FullMsgId msgId; bool canDelete = false; bool canForward = false; }; using SelectedItems = std::vector<SelectedItem>; class ListDelegate { public: virtual Context listContext() = 0; virtual void listScrollTo(int top) = 0; virtual void listCancelRequest() = 0; virtual void listDeleteRequest() = 0; virtual rpl::producer<Data::MessagesSlice> listSource( Data::MessagePosition aroundId, int limitBefore, int limitAfter) = 0; virtual bool listAllowsMultiSelect() = 0; virtual bool listIsLessInOrder( not_null<HistoryItem*> first, not_null<HistoryItem*> second) = 0; virtual void listSelectionChanged(SelectedItems &&items) = 0; virtual void listVisibleItemsChanged(HistoryItemsList &&items) = 0; virtual base::optional<int> listUnreadBarView( const std::vector<not_null<Element*>> &elements) = 0; virtual void listContentRefreshed() = 0; virtual ClickHandlerPtr listDateLink(not_null<Element*> view) = 0; }; struct SelectionData { bool canDelete = false; bool canForward = false; }; using SelectedMap = base::flat_map< FullMsgId, SelectionData, std::less<>>; class ListMemento { public: struct ScrollTopState { Data::MessagePosition item; int shift = 0; }; explicit ListMemento(Data::MessagePosition position) : _aroundPosition(position) { } void setAroundPosition(Data::MessagePosition position) { _aroundPosition = position; } Data::MessagePosition aroundPosition() const { return _aroundPosition; } void setIdsLimit(int limit) { _idsLimit = limit; } int idsLimit() const { return _idsLimit; } void setScrollTopState(ScrollTopState state) { _scrollTopState = state; } ScrollTopState scrollTopState() const { return _scrollTopState; } private: Data::MessagePosition _aroundPosition; ScrollTopState _scrollTopState; int _idsLimit = 0; }; class ListWidget final : public Ui::RpWidget , public ElementDelegate , public Ui::AbstractTooltipShower , private base::Subscriber { public: ListWidget( QWidget *parent, not_null<Window::Controller*> controller, not_null<ListDelegate*> delegate); not_null<ListDelegate*> delegate() const; // Set the correct scroll position after being resized. void restoreScrollPosition(); void resizeToWidth(int newWidth, int minHeight); void saveState(not_null<ListMemento*> memento); void restoreState(not_null<ListMemento*> memento); base::optional<int> scrollTopForPosition( Data::MessagePosition position) const; base::optional<int> scrollTopForView(not_null<Element*> view) const; enum class AnimatedScroll { Full, Part, }; void animatedScrollTo( int scrollTop, Data::MessagePosition attachPosition, int delta, AnimatedScroll type); bool isAbovePosition(Data::MessagePosition position) const; bool isBelowPosition(Data::MessagePosition position) const; void highlightMessage(FullMsgId itemId); TextWithEntities getSelectedText() const; MessageIdsList getSelectedItems() const; void cancelSelection(); void selectItem(not_null<HistoryItem*> item); void selectItemAsGroup(not_null<HistoryItem*> item); bool loadedAtTopKnown() const; bool loadedAtTop() const; bool loadedAtBottomKnown() const; bool loadedAtBottom() const; bool isEmpty() const; // AbstractTooltipShower interface QString tooltipText() const override; QPoint tooltipPos() const override; // ElementDelegate interface. Context elementContext() override; std::unique_ptr<Element> elementCreate( not_null<HistoryMessage*> message) override; std::unique_ptr<Element> elementCreate( not_null<HistoryService*> message) override; bool elementUnderCursor(not_null<const Element*> view) override; void elementAnimationAutoplayAsync( not_null<const Element*> view) override; TimeMs elementHighlightTime(not_null<const Element*> element) override; bool elementInSelectionMode() override; ~ListWidget(); protected: void visibleTopBottomUpdated( int visibleTop, int visibleBottom) override; void paintEvent(QPaintEvent *e) override; void keyPressEvent(QKeyEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void mouseDoubleClickEvent(QMouseEvent *e) override; void enterEventHook(QEvent *e) override; void leaveEventHook(QEvent *e) override; void contextMenuEvent(QContextMenuEvent *e) override; // Resize content and count natural widget height for the desired width. int resizeGetHeight(int newWidth) override; private: struct MouseState { MouseState(); MouseState( FullMsgId itemId, int height, QPoint point, PointState pointState); FullMsgId itemId; int height = 0; QPoint point; PointState pointState; inline bool operator==(const MouseState &other) const { return (itemId == other.itemId) && (point == other.point); } inline bool operator!=(const MouseState &other) const { return !(*this == other); } }; enum class Direction { Up, Down, }; enum class MouseAction { None, PrepareDrag, Dragging, PrepareSelect, Selecting, }; enum class SelectAction { Select, Deselect, Invert, }; enum class EnumItemsDirection { TopToBottom, BottomToTop, }; enum class DragSelectAction { None, Selecting, Deselecting, }; using ScrollTopState = ListMemento::ScrollTopState; using PointState = HistoryView::PointState; using CursorState = HistoryView::CursorState; void refreshViewer(); void updateAroundPositionFromRows(); void refreshRows(); ScrollTopState countScrollState() const; void saveScrollState(); void restoreScrollState(); Element *viewForItem(FullMsgId itemId) const; Element *viewForItem(const HistoryItem *item) const; not_null<Element*> enforceViewForItem(not_null<HistoryItem*> item); void mouseActionStart( const QPoint &globalPosition, Qt::MouseButton button); void mouseActionUpdate(const QPoint &globalPosition); void mouseActionUpdate(); void mouseActionFinish( const QPoint &globalPosition, Qt::MouseButton button); void mouseActionCancel(); std::unique_ptr<QMimeData> prepareDrag(); void performDrag(); style::cursor computeMouseCursor() const; int itemTop(not_null<const Element*> view) const; void repaintItem(FullMsgId itemId); void repaintItem(const Element *view); void resizeItem(not_null<Element*> view); void refreshItem(not_null<const Element*> view); void itemRemoved(not_null<const HistoryItem*> item); QPoint mapPointToItem(QPoint point, const Element *view) const; void showContextMenu(QContextMenuEvent *e, bool showFromTouch = false); not_null<Element*> findItemByY(int y) const; Element *strictFindItemByY(int y) const; int findNearestItem(Data::MessagePosition position) const; void viewReplaced(not_null<const Element*> was, Element *now); HistoryItemsList collectVisibleItems() const; void checkMoveToOtherViewer(); void updateVisibleTopItem(); void updateItemsGeometry(); void updateSize(); void refreshAttachmentsFromTill(int from, int till); void refreshAttachmentsAtIndex(int index); void toggleScrollDateShown(); void repaintScrollDateCallback(); bool displayScrollDate() const; void scrollDateHide(); void scrollDateCheck(); void scrollDateHideByTimer(); void keepScrollDateForNow(); void trySwitchToWordSelection(); void switchToWordSelection(); void validateTrippleClickStartTime(); SelectedItems collectSelectedItems() const; MessageIdsList collectSelectedIds() const; void pushSelectedItems(); void removeItemSelection( const SelectedMap::const_iterator &i); bool hasSelectedText() const; bool hasSelectedItems() const; bool overSelectedItems() const; void clearTextSelection(); void clearSelected(); void setTextSelection( not_null<Element*> view, TextSelection selection); int itemMinimalHeight() const; bool isGoodForSelection(not_null<HistoryItem*> item) const; bool isGoodForSelection( SelectedMap &applyTo, not_null<HistoryItem*> item, int &totalCount) const; bool addToSelection( SelectedMap &applyTo, not_null<HistoryItem*> item) const; bool removeFromSelection( SelectedMap &applyTo, FullMsgId itemId) const; void changeSelection( SelectedMap &applyTo, not_null<HistoryItem*> item, SelectAction action) const; bool isSelectedGroup( const SelectedMap &applyTo, not_null<const Data::Group*> group) const; bool isSelectedAsGroup( const SelectedMap &applyTo, not_null<HistoryItem*> item) const; void changeSelectionAsGroup( SelectedMap &applyTo, not_null<HistoryItem*> item, SelectAction action) const; SelectedMap::iterator itemUnderPressSelection(); SelectedMap::const_iterator itemUnderPressSelection() const; bool isItemUnderPressSelected() const; bool isInsideSelection( not_null<const Element*> view, not_null<HistoryItem*> exactItem, const MouseState &state) const; bool requiredToStartDragging(not_null<Element*> view) const; bool isPressInSelectedText(TextState state) const; void updateDragSelection(); void updateDragSelection( const Element *fromView, const MouseState &fromState, const Element *tillView, const MouseState &tillState); void updateDragSelection( std::vector<not_null<Element*>>::const_iterator from, std::vector<not_null<Element*>>::const_iterator till); void ensureDragSelectAction( std::vector<not_null<Element*>>::const_iterator from, std::vector<not_null<Element*>>::const_iterator till); void clearDragSelection(); void applyDragSelection(); void applyDragSelection(SelectedMap &applyTo) const; TextSelection itemRenderSelection( not_null<const Element*> view) const; TextSelection computeRenderSelection( not_null<const SelectedMap*> selected, not_null<const Element*> view) const; void checkUnreadBarCreation(); void applyUpdatedScrollState(); void scrollToAnimationCallback(FullMsgId attachToId); void updateHighlightedMessage(); // This function finds all history items that are displayed and calls template method // for each found message (in given direction) in the passed history with passed top offset. // // Method has "bool (*Method)(not_null<Element*> view, int itemtop, int itembottom)" signature // if it returns false the enumeration stops immediately. template <EnumItemsDirection direction, typename Method> void enumerateItems(Method method); // This function finds all userpics on the left that are displayed and calls template method // for each found userpic (from the top to the bottom) using enumerateItems() method. // // Method has "bool (*Method)(not_null<Element*> view, int userpicTop)" signature // if it returns false the enumeration stops immediately. template <typename Method> void enumerateUserpics(Method method); // This function finds all date elements that are displayed and calls template method // for each found date element (from the bottom to the top) using enumerateItems() method. // // Method has "bool (*Method)(not_null<HistoryItem*> item, int itemtop, int dateTop)" signature // if it returns false the enumeration stops immediately. template <typename Method> void enumerateDates(Method method); static constexpr auto kMinimalIdsLimit = 24; not_null<ListDelegate*> _delegate; not_null<Window::Controller*> _controller; Data::MessagePosition _aroundPosition; Data::MessagePosition _shownAtPosition; Context _context; int _aroundIndex = -1; int _idsLimit = kMinimalIdsLimit; Data::MessagesSlice _slice; std::vector<not_null<Element*>> _items; std::map< not_null<HistoryItem*>, std::unique_ptr<Element>, std::less<>> _views; int _itemsTop = 0; int _itemsWidth = 0; int _itemsHeight = 0; int _itemAverageHeight = 0; int _minHeight = 0; int _visibleTop = 0; int _visibleBottom = 0; Element *_visibleTopItem = nullptr; int _visibleTopFromItem = 0; ScrollTopState _scrollTopState; Animation _scrollToAnimation; bool _scrollDateShown = false; Animation _scrollDateOpacity; SingleQueuedInvokation _scrollDateCheck; base::Timer _scrollDateHideTimer; Element *_scrollDateLastItem = nullptr; int _scrollDateLastItemTop = 0; ClickHandlerPtr _scrollDateLink; SingleQueuedInvokation _applyUpdatedScrollState; Element *_unreadBarElement = nullptr; MouseAction _mouseAction = MouseAction::None; TextSelectType _mouseSelectType = TextSelectType::Letters; QPoint _mousePosition; MouseState _overState; MouseState _pressState; Element *_overElement = nullptr; HistoryItem *_overItemExact = nullptr; HistoryItem *_pressItemExact = nullptr; CursorState _mouseCursorState = CursorState(); uint16 _mouseTextSymbol = 0; bool _pressWasInactive = false; bool _selectEnabled = false; HistoryItem *_selectedTextItem = nullptr; TextSelection _selectedTextRange; TextWithEntities _selectedText; SelectedMap _selected; base::flat_set<FullMsgId> _dragSelected; DragSelectAction _dragSelectAction = DragSelectAction::None; bool _dragSelectDirectionUp = false; // Was some text selected in current drag action. bool _wasSelectedText = false; Qt::CursorShape _cursor = style::cur_default; base::unique_qptr<Ui::PopupMenu> _menu; QPoint _trippleClickPoint; TimeMs _trippleClickStartTime = 0; TimeMs _highlightStart = 0; FullMsgId _highlightedMessageId; base::Timer _highlightTimer; rpl::lifetime _viewerLifetime; }; } // namespace HistoryView ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/view/history_view_message.cpp��������������������������0000664�0000000�0000000�00000157276�13262451251�0027063�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/view/history_view_message.h" #include "history/view/history_view_cursor_state.h" #include "history/history_item_components.h" #include "history/history_message.h" #include "history/history_media_types.h" #include "history/history_media.h" #include "history/history.h" #include "data/data_session.h" #include "lang/lang_keys.h" #include "mainwidget.h" #include "mainwindow.h" #include "window/window_controller.h" #include "auth_session.h" #include "layout.h" #include "styles/style_widgets.h" #include "styles/style_history.h" #include "styles/style_dialogs.h" namespace HistoryView { namespace { class KeyboardStyle : public ReplyKeyboard::Style { public: using ReplyKeyboard::Style::Style; int buttonRadius() const override; void startPaint(Painter &p) const override; const style::TextStyle &textStyle() const override; void repaint(not_null<const HistoryItem*> item) const override; protected: void paintButtonBg( Painter &p, const QRect &rect, float64 howMuchOver) const override; void paintButtonIcon(Painter &p, const QRect &rect, int outerWidth, HistoryMessageMarkupButton::Type type) const override; void paintButtonLoading(Painter &p, const QRect &rect) const override; int minButtonWidth(HistoryMessageMarkupButton::Type type) const override; }; void KeyboardStyle::startPaint(Painter &p) const { p.setPen(st::msgServiceFg); } const style::TextStyle &KeyboardStyle::textStyle() const { return st::serviceTextStyle; } void KeyboardStyle::repaint(not_null<const HistoryItem*> item) const { Auth().data().requestItemRepaint(item); } int KeyboardStyle::buttonRadius() const { return st::dateRadius; } void KeyboardStyle::paintButtonBg( Painter &p, const QRect &rect, float64 howMuchOver) const { App::roundRect(p, rect, st::msgServiceBg, StickerCorners); if (howMuchOver > 0) { auto o = p.opacity(); p.setOpacity(o * howMuchOver); App::roundRect(p, rect, st::msgBotKbOverBgAdd, BotKbOverCorners); p.setOpacity(o); } } void KeyboardStyle::paintButtonIcon( Painter &p, const QRect &rect, int outerWidth, HistoryMessageMarkupButton::Type type) const { using Button = HistoryMessageMarkupButton; auto getIcon = [](Button::Type type) -> const style::icon* { switch (type) { case Button::Type::Url: return &st::msgBotKbUrlIcon; case Button::Type::SwitchInlineSame: case Button::Type::SwitchInline: return &st::msgBotKbSwitchPmIcon; } return nullptr; }; if (auto icon = getIcon(type)) { icon->paint(p, rect.x() + rect.width() - icon->width() - st::msgBotKbIconPadding, rect.y() + st::msgBotKbIconPadding, outerWidth); } } void KeyboardStyle::paintButtonLoading(Painter &p, const QRect &rect) const { auto icon = &st::historySendingInvertedIcon; icon->paint(p, rect.x() + rect.width() - icon->width() - st::msgBotKbIconPadding, rect.y() + rect.height() - icon->height() - st::msgBotKbIconPadding, rect.x() * 2 + rect.width()); } int KeyboardStyle::minButtonWidth( HistoryMessageMarkupButton::Type type) const { using Button = HistoryMessageMarkupButton; int result = 2 * buttonPadding(), iconWidth = 0; switch (type) { case Button::Type::Url: iconWidth = st::msgBotKbUrlIcon.width(); break; case Button::Type::SwitchInlineSame: case Button::Type::SwitchInline: iconWidth = st::msgBotKbSwitchPmIcon.width(); break; case Button::Type::Callback: case Button::Type::Game: iconWidth = st::historySendingInvertedIcon.width(); break; } if (iconWidth > 0) { result = std::max(result, 2 * iconWidth + 4 * int(st::msgBotKbIconPadding)); } return result; } QString AdminBadgeText() { return lang(lng_admin_badge); } QString FastReplyText() { return lang(lng_fast_reply); } void PaintBubble(Painter &p, QRect rect, int outerWidth, bool selected, bool outbg, RectPart tailSide) { auto &bg = selected ? (outbg ? st::msgOutBgSelected : st::msgInBgSelected) : (outbg ? st::msgOutBg : st::msgInBg); auto &sh = selected ? (outbg ? st::msgOutShadowSelected : st::msgInShadowSelected) : (outbg ? st::msgOutShadow : st::msgInShadow); auto cors = selected ? (outbg ? MessageOutSelectedCorners : MessageInSelectedCorners) : (outbg ? MessageOutCorners : MessageInCorners); auto parts = RectPart::FullTop | RectPart::NoTopBottom | RectPart::Bottom; if (tailSide == RectPart::Right) { parts |= RectPart::BottomLeft; p.fillRect(rect.x() + rect.width() - st::historyMessageRadius, rect.y() + rect.height() - st::historyMessageRadius, st::historyMessageRadius, st::historyMessageRadius, bg); auto &tail = selected ? st::historyBubbleTailOutRightSelected : st::historyBubbleTailOutRight; tail.paint(p, rect.x() + rect.width(), rect.y() + rect.height() - tail.height(), outerWidth); p.fillRect(rect.x() + rect.width() - st::historyMessageRadius, rect.y() + rect.height(), st::historyMessageRadius + tail.width(), st::msgShadow, sh); } else if (tailSide == RectPart::Left) { parts |= RectPart::BottomRight; p.fillRect(rect.x(), rect.y() + rect.height() - st::historyMessageRadius, st::historyMessageRadius, st::historyMessageRadius, bg); auto &tail = selected ? (outbg ? st::historyBubbleTailOutLeftSelected : st::historyBubbleTailInLeftSelected) : (outbg ? st::historyBubbleTailOutLeft : st::historyBubbleTailInLeft); tail.paint(p, rect.x() - tail.width(), rect.y() + rect.height() - tail.height(), outerWidth); p.fillRect(rect.x() - tail.width(), rect.y() + rect.height(), st::historyMessageRadius + tail.width(), st::msgShadow, sh); } else { parts |= RectPart::FullBottom; } App::roundRect(p, rect, bg, cors, &sh, parts); } style::color FromNameFg(not_null<PeerData*> peer, bool selected) { if (selected) { const style::color colors[] = { st::historyPeer1NameFgSelected, st::historyPeer2NameFgSelected, st::historyPeer3NameFgSelected, st::historyPeer4NameFgSelected, st::historyPeer5NameFgSelected, st::historyPeer6NameFgSelected, st::historyPeer7NameFgSelected, st::historyPeer8NameFgSelected, }; return colors[Data::PeerColorIndex(peer->id)]; } else { const style::color colors[] = { st::historyPeer1NameFg, st::historyPeer2NameFg, st::historyPeer3NameFg, st::historyPeer4NameFg, st::historyPeer5NameFg, st::historyPeer6NameFg, st::historyPeer7NameFg, st::historyPeer8NameFg, }; return colors[Data::PeerColorIndex(peer->id)]; } } } // namespace LogEntryOriginal::LogEntryOriginal() = default; LogEntryOriginal::LogEntryOriginal(LogEntryOriginal &&other) : page(std::move(other.page)) { } LogEntryOriginal &LogEntryOriginal::operator=(LogEntryOriginal &&other) { page = std::move(other.page); return *this; } LogEntryOriginal::~LogEntryOriginal() = default; Message::Message( not_null<ElementDelegate*> delegate, not_null<HistoryMessage*> data) : Element(delegate, data) { initLogEntryOriginal(); } not_null<HistoryMessage*> Message::message() const { return static_cast<HistoryMessage*>(data().get()); } QSize Message::performCountOptimalSize() { const auto item = message(); const auto media = this->media(); auto maxWidth = 0; auto minHeight = 0; updateMediaInBubbleState(); refreshEditedBadge(); auto mediaOnBottom = (logEntryOriginal() != nullptr) || (media && media->isDisplayed() && media->isBubbleBottom()); if (mediaOnBottom) { // remove skip } else { // add skip } if (drawBubble()) { auto forwarded = item->Get<HistoryMessageForwarded>(); auto reply = item->Get<HistoryMessageReply>(); auto via = item->Get<HistoryMessageVia>(); auto entry = logEntryOriginal(); if (forwarded) { forwarded->create(via); } if (reply) { reply->updateName(); } if (displayFromName()) { item->updateAdminBadgeState(); } auto mediaDisplayed = false; if (media) { mediaDisplayed = media->isDisplayed(); media->initDimensions(); } if (entry) { entry->initDimensions(); } // Entry page is always a bubble bottom. auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/); auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop()); if (mediaOnBottom) { if (item->_text.removeSkipBlock()) { item->_textWidth = -1; item->_textHeight = 0; } } else if (item->_text.updateSkipBlock(skipBlockWidth(), skipBlockHeight())) { item->_textWidth = -1; item->_textHeight = 0; } maxWidth = plainMaxWidth(); minHeight = hasVisibleText() ? item->_text.minHeight() : 0; if (!mediaOnBottom) { minHeight += st::msgPadding.bottom(); if (mediaDisplayed) minHeight += st::mediaInBubbleSkip; } if (!mediaOnTop) { minHeight += st::msgPadding.top(); if (mediaDisplayed) minHeight += st::mediaInBubbleSkip; if (entry) minHeight += st::mediaInBubbleSkip; } if (mediaDisplayed) { // Parts don't participate in maxWidth() in case of media message. accumulate_max(maxWidth, media->maxWidth()); minHeight += media->minHeight(); } else { // Count parts in maxWidth(), don't count them in minHeight(). // They will be added in resizeGetHeight() anyway. if (displayFromName()) { auto namew = st::msgPadding.left() + item->displayFrom()->nameText.maxWidth() + st::msgPadding.right(); if (via && !displayForwardedFrom()) { namew += st::msgServiceFont->spacew + via->maxWidth; } const auto replyWidth = hasFastReply() ? st::msgFont->width(FastReplyText()) : 0; if (item->hasAdminBadge()) { const auto badgeWidth = st::msgFont->width( AdminBadgeText()); namew += st::msgPadding.right() + std::max(badgeWidth, replyWidth); } else if (replyWidth) { namew += st::msgPadding.right() + replyWidth; } accumulate_max(maxWidth, namew); } else if (via && !displayForwardedFrom()) { accumulate_max(maxWidth, st::msgPadding.left() + via->maxWidth + st::msgPadding.right()); } if (displayForwardedFrom()) { auto namew = st::msgPadding.left() + forwarded->text.maxWidth() + st::msgPadding.right(); if (via) { namew += st::msgServiceFont->spacew + via->maxWidth; } accumulate_max(maxWidth, namew); } if (reply) { auto replyw = st::msgPadding.left() + reply->maxReplyWidth - st::msgReplyPadding.left() - st::msgReplyPadding.right() + st::msgPadding.right(); if (reply->replyToVia) { replyw += st::msgServiceFont->spacew + reply->replyToVia->maxWidth; } accumulate_max(maxWidth, replyw); } if (entry) { accumulate_max(maxWidth, entry->maxWidth()); minHeight += entry->minHeight(); } } } else if (media) { media->initDimensions(); maxWidth = media->maxWidth(); minHeight = media->isDisplayed() ? media->minHeight() : 0; } else { maxWidth = st::msgMinWidth; minHeight = 0; } if (const auto markup = item->inlineReplyMarkup()) { if (!markup->inlineKeyboard) { markup->inlineKeyboard = std::make_unique<ReplyKeyboard>( item, std::make_unique<KeyboardStyle>(st::msgBotKbButton)); } // if we have a text bubble we can resize it to fit the keyboard // but if we have only media we don't do that if (hasVisibleText()) { accumulate_max(maxWidth, markup->inlineKeyboard->naturalWidth()); } } return QSize(maxWidth, minHeight); } int Message::marginTop() const { auto result = 0; if (!isHidden()) { if (isAttachedToPrevious()) { result += st::msgMarginTopAttached; } else { result += st::msgMargin.top(); } } result += displayedDateHeight(); if (const auto bar = Get<UnreadBar>()) { result += bar->height(); } return result; } int Message::marginBottom() const { return isHidden() ? 0 : st::msgMargin.bottom(); } void Message::draw( Painter &p, QRect clip, TextSelection selection, TimeMs ms) const { auto g = countGeometry(); if (g.width() < 1) { return; } const auto item = message(); const auto media = this->media(); const auto outbg = hasOutLayout(); const auto bubble = drawBubble(); const auto selected = (selection == FullSelection); auto dateh = 0; if (const auto date = Get<DateBadge>()) { dateh = date->height(); } if (const auto bar = Get<UnreadBar>()) { auto unreadbarh = bar->height(); if (clip.intersects(QRect(0, dateh, width(), unreadbarh))) { p.translate(0, dateh); bar->paint(p, 0, width()); p.translate(0, -dateh); } } if (isHidden()) { return; } paintHighlight(p, g.height()); p.setTextPalette(selected ? (outbg ? st::outTextPaletteSelected : st::inTextPaletteSelected) : (outbg ? st::outTextPalette : st::inTextPalette)); auto keyboard = item->inlineReplyKeyboard(); if (keyboard) { auto keyboardHeight = st::msgBotKbButton.margin + keyboard->naturalHeight(); g.setHeight(g.height() - keyboardHeight); auto keyboardPosition = QPoint(g.left(), g.top() + g.height() + st::msgBotKbButton.margin); p.translate(keyboardPosition); keyboard->paint(p, g.width(), clip.translated(-keyboardPosition), ms); p.translate(-keyboardPosition); } if (bubble) { if (displayFromName() && item->displayFrom()->nameVersion > item->_fromNameVersion) { fromNameUpdated(g.width()); } auto entry = logEntryOriginal(); auto mediaDisplayed = media && media->isDisplayed(); auto skipTail = isAttachedToNext() || (media && media->skipBubbleTail()) || (keyboard != nullptr); auto displayTail = skipTail ? RectPart::None : (outbg && !Adaptive::ChatWide()) ? RectPart::Right : RectPart::Left; PaintBubble(p, g, width(), selected, outbg, displayTail); // Entry page is always a bubble bottom. auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/); auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop()); auto trect = g.marginsRemoved(st::msgPadding); if (mediaOnBottom) { trect.setHeight(trect.height() + st::msgPadding.bottom()); } if (mediaOnTop) { trect.setY(trect.y() - st::msgPadding.top()); } else { paintFromName(p, trect, selected); paintForwardedInfo(p, trect, selected); paintReplyInfo(p, trect, selected); paintViaBotIdInfo(p, trect, selected); } if (entry) { trect.setHeight(trect.height() - entry->height()); } paintText(p, trect, selection); if (mediaDisplayed) { auto mediaHeight = media->height(); auto mediaLeft = g.left(); auto mediaTop = (trect.y() + trect.height() - mediaHeight); p.translate(mediaLeft, mediaTop); media->draw(p, clip.translated(-mediaLeft, -mediaTop), skipTextSelection(selection), ms); p.translate(-mediaLeft, -mediaTop); } if (entry) { auto entryLeft = g.left(); auto entryTop = trect.y() + trect.height(); p.translate(entryLeft, entryTop); auto entrySelection = skipTextSelection(selection); if (mediaDisplayed) { entrySelection = media->skipSelection(entrySelection); } entry->draw(p, clip.translated(-entryLeft, -entryTop), entrySelection, ms); p.translate(-entryLeft, -entryTop); } const auto needDrawInfo = entry ? !entry->customInfoLayout() : (mediaDisplayed ? !media->customInfoLayout() : true); if (needDrawInfo) { drawInfo(p, g.left() + g.width(), g.top() + g.height(), 2 * g.left() + g.width(), selected, InfoDisplayType::Default); } if (displayRightAction()) { const auto fastShareSkip = snap( (g.height() - st::historyFastShareSize) / 2, 0, st::historyFastShareBottom); const auto fastShareLeft = g.left() + g.width() + st::historyFastShareLeft; const auto fastShareTop = g.top() + g.height() - fastShareSkip - st::historyFastShareSize; drawRightAction(p, fastShareLeft, fastShareTop, width()); } } else if (media && media->isDisplayed()) { p.translate(g.topLeft()); media->draw(p, clip.translated(-g.topLeft()), skipTextSelection(selection), ms); p.translate(-g.topLeft()); } p.restoreTextPalette(); const auto reply = item->Get<HistoryMessageReply>(); if (reply && reply->isNameUpdated()) { const_cast<Message*>(this)->setPendingResize(); } } void Message::paintFromName( Painter &p, QRect &trect, bool selected) const { const auto item = message(); if (displayFromName()) { const auto badgeWidth = [&] { if (item->hasAdminBadge()) { return st::msgFont->width(AdminBadgeText()); } return 0; }(); const auto replyWidth = [&] { if (isUnderCursor() && displayFastReply()) { return st::msgFont->width(FastReplyText()); } return 0; }(); const auto rightWidth = replyWidth ? replyWidth : badgeWidth; auto availableLeft = trect.left(); auto availableWidth = trect.width(); if (rightWidth) { availableWidth -= st::msgPadding.right() + rightWidth; } p.setFont(st::msgNameFont); if (item->isPost()) { p.setPen(selected ? st::msgInServiceFgSelected : st::msgInServiceFg); } else { p.setPen(FromNameFg(item->author(), selected)); } item->displayFrom()->nameText.drawElided(p, availableLeft, trect.top(), availableWidth); auto skipWidth = item->author()->nameText.maxWidth() + st::msgServiceFont->spacew; availableLeft += skipWidth; availableWidth -= skipWidth; auto via = item->Get<HistoryMessageVia>(); if (via && !displayForwardedFrom() && availableWidth > 0) { const auto outbg = hasOutLayout(); p.setPen(selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg)); p.drawText(availableLeft, trect.top() + st::msgServiceFont->ascent, via->text); auto skipWidth = via->width + st::msgServiceFont->spacew; availableLeft += skipWidth; availableWidth -= skipWidth; } if (rightWidth) { p.setPen(selected ? st::msgInDateFgSelected : st::msgInDateFg); p.setFont(ClickHandler::showAsActive(_fastReplyLink) ? st::msgFont->underline() : st::msgFont); p.drawText( trect.left() + trect.width() - rightWidth, trect.top() + st::msgFont->ascent, replyWidth ? FastReplyText() : AdminBadgeText()); } trect.setY(trect.y() + st::msgNameFont->height); } } void Message::paintForwardedInfo(Painter &p, QRect &trect, bool selected) const { if (displayForwardedFrom()) { style::font serviceFont(st::msgServiceFont), serviceName(st::msgServiceNameFont); const auto item = message(); const auto outbg = hasOutLayout(); p.setPen(selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg)); p.setFont(serviceFont); auto forwarded = item->Get<HistoryMessageForwarded>(); auto breakEverywhere = (forwarded->text.countHeight(trect.width()) > 2 * serviceFont->height); p.setTextPalette(selected ? (outbg ? st::outFwdTextPaletteSelected : st::inFwdTextPaletteSelected) : (outbg ? st::outFwdTextPalette : st::inFwdTextPalette)); forwarded->text.drawElided(p, trect.x(), trect.y(), trect.width(), 2, style::al_left, 0, -1, 0, breakEverywhere); p.setTextPalette(selected ? (outbg ? st::outTextPaletteSelected : st::inTextPaletteSelected) : (outbg ? st::outTextPalette : st::inTextPalette)); trect.setY(trect.y() + (((forwarded->text.maxWidth() > trect.width()) ? 2 : 1) * serviceFont->height)); } } void Message::paintReplyInfo(Painter &p, QRect &trect, bool selected) const { const auto item = message(); if (auto reply = item->Get<HistoryMessageReply>()) { int32 h = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); auto flags = HistoryMessageReply::PaintFlag::InBubble | 0; if (selected) { flags |= HistoryMessageReply::PaintFlag::Selected; } reply->paint(p, this, trect.x(), trect.y(), trect.width(), flags); trect.setY(trect.y() + h); } } void Message::paintViaBotIdInfo(Painter &p, QRect &trect, bool selected) const { const auto item = message(); if (!displayFromName() && !displayForwardedFrom()) { if (auto via = item->Get<HistoryMessageVia>()) { const auto outbg = hasOutLayout(); p.setFont(st::msgServiceNameFont); p.setPen(selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg)); p.drawTextLeft(trect.left(), trect.top(), width(), via->text); trect.setY(trect.y() + st::msgServiceNameFont->height); } } } void Message::paintText(Painter &p, QRect &trect, TextSelection selection) const { if (!hasVisibleText()) { return; } const auto item = message(); const auto outbg = hasOutLayout(); auto selected = (selection == FullSelection); p.setPen(outbg ? (selected ? st::historyTextOutFgSelected : st::historyTextOutFg) : (selected ? st::historyTextInFgSelected : st::historyTextInFg)); p.setFont(st::msgFont); item->_text.draw(p, trect.x(), trect.y(), trect.width(), style::al_left, 0, -1, selection); } PointState Message::pointState(QPoint point) const { const auto g = countGeometry(); if (g.width() < 1 || isHidden()) { return PointState::Outside; } const auto media = this->media(); const auto item = message(); if (drawBubble()) { if (!g.contains(point)) { return PointState::Outside; } if (const auto mediaDisplayed = media && media->isDisplayed()) { // Hack for grouped media point state. auto entry = logEntryOriginal(); // Entry page is always a bubble bottom. auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/); auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop()); auto trect = g.marginsRemoved(st::msgPadding); if (mediaOnBottom) { trect.setHeight(trect.height() + st::msgPadding.bottom()); } //if (mediaOnTop) { // trect.setY(trect.y() - st::msgPadding.top()); //} else { // if (getStateFromName(point, trect, &result)) return result; // if (getStateForwardedInfo(point, trect, &result, request)) return result; // if (getStateReplyInfo(point, trect, &result)) return result; // if (getStateViaBotIdInfo(point, trect, &result)) return result; //} if (entry) { auto entryHeight = entry->height(); trect.setHeight(trect.height() - entryHeight); } auto mediaHeight = media->height(); auto mediaLeft = trect.x() - st::msgPadding.left(); auto mediaTop = (trect.y() + trect.height() - mediaHeight); if (point.y() >= mediaTop && point.y() < mediaTop + mediaHeight) { return media->pointState(point - QPoint(mediaLeft, mediaTop)); } } return PointState::Inside; } else if (media) { return media->pointState(point - g.topLeft()); } return PointState::Outside; } bool Message::displayFromPhoto() const { return hasFromPhoto() && !isAttachedToNext(); } bool Message::hasFromPhoto() const { if (isHidden()) { return false; } switch (context()) { case Context::AdminLog: case Context::Feed: return true; case Context::History: { const auto item = message(); if (item->isPost() || item->isEmpty()) { return false; } else if (Adaptive::ChatWide()) { return true; } else if (item->history()->peer->isSelf()) { return item->Has<HistoryMessageForwarded>(); } return !item->out() && !item->history()->peer->isUser(); } break; } Unexpected("Context in Message::hasFromPhoto."); } TextState Message::textState( QPoint point, StateRequest request) const { const auto item = message(); const auto media = this->media(); auto result = TextState(item); auto g = countGeometry(); if (g.width() < 1 || isHidden()) { return result; } auto keyboard = item->inlineReplyKeyboard(); auto keyboardHeight = 0; if (keyboard) { keyboardHeight = keyboard->naturalHeight(); g.setHeight(g.height() - st::msgBotKbButton.margin - keyboardHeight); } if (drawBubble()) { const auto inBubble = g.contains(point); auto entry = logEntryOriginal(); auto mediaDisplayed = media && media->isDisplayed(); // Entry page is always a bubble bottom. auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/); auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop()); auto trect = g.marginsRemoved(st::msgPadding); if (mediaOnBottom) { trect.setHeight(trect.height() + st::msgPadding.bottom()); } if (mediaOnTop) { trect.setY(trect.y() - st::msgPadding.top()); } else if (inBubble) { if (getStateFromName(point, trect, &result)) { return result; } if (getStateForwardedInfo(point, trect, &result, request)) { return result; } if (getStateReplyInfo(point, trect, &result)) { return result; } if (getStateViaBotIdInfo(point, trect, &result)) { return result; } } if (entry) { auto entryHeight = entry->height(); trect.setHeight(trect.height() - entryHeight); auto entryLeft = g.left(); auto entryTop = trect.y() + trect.height(); if (point.y() >= entryTop && point.y() < entryTop + entryHeight) { result = entry->textState( point - QPoint(entryLeft, entryTop), request); result.symbol += item->_text.length() + (mediaDisplayed ? media->fullSelectionLength() : 0); } } auto checkForPointInTime = [&] { if (mediaOnBottom && (entry || media->customInfoLayout())) { return; } const auto inDate = pointInTime( g.left() + g.width(), g.top() + g.height(), point, InfoDisplayType::Default); if (inDate) { result.cursor = CursorState::Date; } }; if (inBubble) { if (mediaDisplayed) { auto mediaHeight = media->height(); auto mediaLeft = trect.x() - st::msgPadding.left(); auto mediaTop = (trect.y() + trect.height() - mediaHeight); if (point.y() >= mediaTop && point.y() < mediaTop + mediaHeight) { result = media->textState(point - QPoint(mediaLeft, mediaTop), request); result.symbol += item->_text.length(); } else if (getStateText(point, trect, &result, request)) { checkForPointInTime(); return result; } else if (point.y() >= trect.y() + trect.height()) { result.symbol = item->_text.length(); } } else if (getStateText(point, trect, &result, request)) { checkForPointInTime(); return result; } else if (point.y() >= trect.y() + trect.height()) { result.symbol = item->_text.length(); } } checkForPointInTime(); if (displayRightAction()) { const auto fastShareSkip = snap( (g.height() - st::historyFastShareSize) / 2, 0, st::historyFastShareBottom); const auto fastShareLeft = g.left() + g.width() + st::historyFastShareLeft; const auto fastShareTop = g.top() + g.height() - fastShareSkip - st::historyFastShareSize; if (QRect( fastShareLeft, fastShareTop, st::historyFastShareSize, st::historyFastShareSize ).contains(point)) { result.link = rightActionLink(); } } } else if (media && media->isDisplayed()) { result = media->textState(point - g.topLeft(), request); result.symbol += item->_text.length(); } if (keyboard && !item->isLogEntry()) { auto keyboardTop = g.top() + g.height() + st::msgBotKbButton.margin; if (QRect(g.left(), keyboardTop, g.width(), keyboardHeight).contains(point)) { result.link = keyboard->getLink(point - QPoint(g.left(), keyboardTop)); return result; } } return result; } bool Message::getStateFromName( QPoint point, QRect &trect, not_null<TextState*> outResult) const { const auto item = message(); if (displayFromName()) { const auto replyWidth = [&] { if (isUnderCursor() && displayFastReply()) { return st::msgFont->width(FastReplyText()); } return 0; }(); if (replyWidth && point.x() >= trect.left() + trect.width() - replyWidth && point.x() < trect.left() + trect.width() + st::msgPadding.right() && point.y() >= trect.top() - st::msgPadding.top() && point.y() < trect.top() + st::msgServiceFont->height) { outResult->link = fastReplyLink(); return true; } if (point.y() >= trect.top() && point.y() < trect.top() + st::msgNameFont->height) { auto availableLeft = trect.left(); auto availableWidth = trect.width(); if (replyWidth) { availableWidth -= st::msgPadding.right() + replyWidth; } auto user = item->displayFrom(); if (point.x() >= availableLeft && point.x() < availableLeft + availableWidth && point.x() < availableLeft + user->nameText.maxWidth()) { outResult->link = user->openLink(); return true; } auto via = item->Get<HistoryMessageVia>(); if (via && !displayForwardedFrom() && point.x() >= availableLeft + item->author()->nameText.maxWidth() + st::msgServiceFont->spacew && point.x() < availableLeft + availableWidth && point.x() < availableLeft + user->nameText.maxWidth() + st::msgServiceFont->spacew + via->width) { outResult->link = via->link; return true; } } trect.setTop(trect.top() + st::msgNameFont->height); } return false; } bool Message::getStateForwardedInfo( QPoint point, QRect &trect, not_null<TextState*> outResult, StateRequest request) const { if (displayForwardedFrom()) { const auto item = message(); auto forwarded = item->Get<HistoryMessageForwarded>(); auto fwdheight = ((forwarded->text.maxWidth() > trect.width()) ? 2 : 1) * st::semiboldFont->height; if (point.y() >= trect.top() && point.y() < trect.top() + fwdheight) { auto breakEverywhere = (forwarded->text.countHeight(trect.width()) > 2 * st::semiboldFont->height); auto textRequest = request.forText(); if (breakEverywhere) { textRequest.flags |= Text::StateRequest::Flag::BreakEverywhere; } *outResult = TextState(item, forwarded->text.getState( point - trect.topLeft(), trect.width(), textRequest)); outResult->symbol = 0; outResult->afterSymbol = false; if (breakEverywhere) { outResult->cursor = CursorState::Forwarded; } else { outResult->cursor = CursorState::None; } return true; } trect.setTop(trect.top() + fwdheight); } return false; } bool Message::getStateReplyInfo( QPoint point, QRect &trect, not_null<TextState*> outResult) const { const auto item = message(); if (auto reply = item->Get<HistoryMessageReply>()) { int32 h = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); if (point.y() >= trect.top() && point.y() < trect.top() + h) { if (reply->replyToMsg && QRect(trect.x(), trect.y() + st::msgReplyPadding.top(), trect.width(), st::msgReplyBarSize.height()).contains(point)) { outResult->link = reply->replyToLink(); } return true; } trect.setTop(trect.top() + h); } return false; } bool Message::getStateViaBotIdInfo( QPoint point, QRect &trect, not_null<TextState*> outResult) const { const auto item = message(); if (const auto via = item->Get<HistoryMessageVia>()) { if (!displayFromName() && !displayForwardedFrom()) { if (QRect(trect.x(), trect.y(), via->width, st::msgNameFont->height).contains(point)) { outResult->link = via->link; return true; } trect.setTop(trect.top() + st::msgNameFont->height); } } return false; } bool Message::getStateText( QPoint point, QRect &trect, not_null<TextState*> outResult, StateRequest request) const { if (!hasVisibleText()) { return false; } const auto item = message(); if (base::in_range(point.y(), trect.y(), trect.y() + trect.height())) { *outResult = TextState(item, item->_text.getState( point - trect.topLeft(), trect.width(), request.forText())); return true; } return false; } // Forward to media. void Message::updatePressed(QPoint point) { const auto item = message(); const auto media = this->media(); if (!media) return; auto g = countGeometry(); auto keyboard = item->inlineReplyKeyboard(); if (keyboard) { auto keyboardHeight = st::msgBotKbButton.margin + keyboard->naturalHeight(); g.setHeight(g.height() - keyboardHeight); } if (drawBubble()) { auto mediaDisplayed = media && media->isDisplayed(); auto top = marginTop(); auto trect = g.marginsAdded(-st::msgPadding); if (mediaDisplayed && media->isBubbleTop()) { trect.setY(trect.y() - st::msgPadding.top()); } else { if (displayFromName()) { trect.setTop(trect.top() + st::msgNameFont->height); } if (displayForwardedFrom()) { auto forwarded = item->Get<HistoryMessageForwarded>(); auto fwdheight = ((forwarded->text.maxWidth() > trect.width()) ? 2 : 1) * st::semiboldFont->height; trect.setTop(trect.top() + fwdheight); } if (item->Get<HistoryMessageReply>()) { auto h = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); trect.setTop(trect.top() + h); } if (const auto via = item->Get<HistoryMessageVia>()) { if (!displayFromName() && !displayForwardedFrom()) { trect.setTop(trect.top() + st::msgNameFont->height); } } } if (mediaDisplayed && media->isBubbleBottom()) { trect.setHeight(trect.height() + st::msgPadding.bottom()); } auto needDateCheck = true; if (mediaDisplayed) { auto mediaHeight = media->height(); auto mediaLeft = trect.x() - st::msgPadding.left(); auto mediaTop = (trect.y() + trect.height() - mediaHeight); media->updatePressed(point - QPoint(mediaLeft, mediaTop)); } } else { media->updatePressed(point - g.topLeft()); } } TextWithEntities Message::selectedText(TextSelection selection) const { const auto item = message(); const auto media = this->media(); TextWithEntities logEntryOriginalResult; auto textResult = item->_text.originalTextWithEntities( selection, ExpandLinksAll); auto skipped = skipTextSelection(selection); auto mediaDisplayed = (media && media->isDisplayed()); auto mediaResult = (mediaDisplayed || isHiddenByGroup()) ? media->selectedText(skipped) : TextWithEntities(); if (auto entry = logEntryOriginal()) { const auto originalSelection = mediaDisplayed ? media->skipSelection(skipped) : skipped; logEntryOriginalResult = entry->selectedText(originalSelection); } auto result = textResult; if (result.text.isEmpty()) { result = std::move(mediaResult); } else if (!mediaResult.text.isEmpty()) { result.text += qstr("\n\n"); TextUtilities::Append(result, std::move(mediaResult)); } if (result.text.isEmpty()) { result = std::move(logEntryOriginalResult); } else if (!logEntryOriginalResult.text.isEmpty()) { result.text += qstr("\n\n"); TextUtilities::Append(result, std::move(logEntryOriginalResult)); } return result; } TextSelection Message::adjustSelection( TextSelection selection, TextSelectType type) const { const auto item = message(); const auto media = this->media(); auto result = item->_text.adjustSelection(selection, type); auto beforeMediaLength = item->_text.length(); if (selection.to <= beforeMediaLength) { return result; } auto mediaDisplayed = media && media->isDisplayed(); if (mediaDisplayed) { auto mediaSelection = unskipTextSelection( media->adjustSelection(skipTextSelection(selection), type)); if (selection.from >= beforeMediaLength) { result = mediaSelection; } else { result.to = mediaSelection.to; } } auto beforeEntryLength = beforeMediaLength + (mediaDisplayed ? media->fullSelectionLength() : 0); if (selection.to <= beforeEntryLength) { return result; } if (const auto entry = logEntryOriginal()) { auto entrySelection = mediaDisplayed ? media->skipSelection(skipTextSelection(selection)) : skipTextSelection(selection); auto logEntryOriginalSelection = entry->adjustSelection(entrySelection, type); if (mediaDisplayed) { logEntryOriginalSelection = media->unskipSelection(logEntryOriginalSelection); } logEntryOriginalSelection = unskipTextSelection(logEntryOriginalSelection); if (selection.from >= beforeEntryLength) { result = logEntryOriginalSelection; } else { result.to = logEntryOriginalSelection.to; } } return result; } void Message::drawInfo( Painter &p, int right, int bottom, int width, bool selected, InfoDisplayType type) const { p.setFont(st::msgDateFont); bool outbg = hasOutLayout(); bool invertedsprites = (type == InfoDisplayType::Image) || (type == InfoDisplayType::Background); int32 infoRight = right, infoBottom = bottom; switch (type) { case InfoDisplayType::Default: infoRight -= st::msgPadding.right() - st::msgDateDelta.x(); infoBottom -= st::msgPadding.bottom() - st::msgDateDelta.y(); p.setPen(selected ? (outbg ? st::msgOutDateFgSelected : st::msgInDateFgSelected) : (outbg ? st::msgOutDateFg : st::msgInDateFg)); break; case InfoDisplayType::Image: infoRight -= st::msgDateImgDelta + st::msgDateImgPadding.x(); infoBottom -= st::msgDateImgDelta + st::msgDateImgPadding.y(); p.setPen(st::msgDateImgFg); break; case InfoDisplayType::Background: infoRight -= st::msgDateImgDelta + st::msgDateImgPadding.x(); infoBottom -= st::msgDateImgDelta + st::msgDateImgPadding.y(); p.setPen(st::msgServiceFg); break; } const auto item = message(); auto infoW = infoWidth(); if (rtl()) infoRight = width - infoRight + infoW; auto dateX = infoRight - infoW; auto dateY = infoBottom - st::msgDateFont->height; if (type == InfoDisplayType::Image) { auto dateW = infoW + 2 * st::msgDateImgPadding.x(), dateH = st::msgDateFont->height + 2 * st::msgDateImgPadding.y(); App::roundRect(p, dateX - st::msgDateImgPadding.x(), dateY - st::msgDateImgPadding.y(), dateW, dateH, selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? DateSelectedCorners : DateCorners); } else if (type == InfoDisplayType::Background) { auto dateW = infoW + 2 * st::msgDateImgPadding.x(), dateH = st::msgDateFont->height + 2 * st::msgDateImgPadding.y(); App::roundRect(p, dateX - st::msgDateImgPadding.x(), dateY - st::msgDateImgPadding.y(), dateW, dateH, selected ? st::msgServiceBgSelected : st::msgServiceBg, selected ? StickerSelectedCorners : StickerCorners); } dateX += timeLeft(); if (const auto msgsigned = item->Get<HistoryMessageSigned>()) { msgsigned->signature.drawElided(p, dateX, dateY, item->_timeWidth); } else if (const auto edited = displayedEditBadge()) { edited->text.drawElided(p, dateX, dateY, item->_timeWidth); } else { p.drawText(dateX, dateY + st::msgDateFont->ascent, item->_timeText); } if (auto views = item->Get<HistoryMessageViews>()) { auto icon = [&] { if (item->id > 0) { if (outbg) { return &(invertedsprites ? st::historyViewsInvertedIcon : (selected ? st::historyViewsOutSelectedIcon : st::historyViewsOutIcon)); } return &(invertedsprites ? st::historyViewsInvertedIcon : (selected ? st::historyViewsInSelectedIcon : st::historyViewsInIcon)); } return &(invertedsprites ? st::historyViewsSendingInvertedIcon : st::historyViewsSendingIcon); }(); if (item->id > 0) { icon->paint(p, infoRight - infoW, infoBottom + st::historyViewsTop, width); p.drawText(infoRight - infoW + st::historyViewsWidth, infoBottom - st::msgDateFont->descent, views->_viewsText); } else if (!outbg) { // sending outbg icon will be painted below auto iconSkip = st::historyViewsSpace + views->_viewsWidth; icon->paint(p, infoRight - infoW + iconSkip, infoBottom + st::historyViewsTop, width); } } else if (item->id < 0 && item->history()->peer->isSelf() && !outbg) { auto icon = &(invertedsprites ? st::historyViewsSendingInvertedIcon : st::historyViewsSendingIcon); icon->paint(p, infoRight - infoW, infoBottom + st::historyViewsTop, width); } if (outbg) { auto icon = [&] { if (item->id > 0) { if (item->unread()) { return &(invertedsprites ? st::historySentInvertedIcon : (selected ? st::historySentSelectedIcon : st::historySentIcon)); } return &(invertedsprites ? st::historyReceivedInvertedIcon : (selected ? st::historyReceivedSelectedIcon : st::historyReceivedIcon)); } return &(invertedsprites ? st::historySendingInvertedIcon : st::historySendingIcon); }(); icon->paint(p, QPoint(infoRight, infoBottom) + st::historySendStatePosition, width); } } bool Message::pointInTime( int right, int bottom, QPoint point, InfoDisplayType type) const { auto infoRight = right; auto infoBottom = bottom; switch (type) { case InfoDisplayType::Default: infoRight -= st::msgPadding.right() - st::msgDateDelta.x(); infoBottom -= st::msgPadding.bottom() - st::msgDateDelta.y(); break; case InfoDisplayType::Image: infoRight -= st::msgDateImgDelta + st::msgDateImgPadding.x(); infoBottom -= st::msgDateImgDelta + st::msgDateImgPadding.y(); break; } const auto item = message(); auto dateX = infoRight - infoWidth() + timeLeft(); auto dateY = infoBottom - st::msgDateFont->height; return QRect( dateX, dateY, item->_timeWidth, st::msgDateFont->height).contains(point); } int Message::infoWidth() const { const auto item = message(); auto result = item->_timeWidth; if (auto views = item->Get<HistoryMessageViews>()) { result += st::historyViewsSpace + views->_viewsWidth + st::historyViewsWidth; } else if (item->id < 0 && item->history()->peer->isSelf()) { if (!hasOutLayout()) { result += st::historySendStateSpace; } } if (hasOutLayout()) { result += st::historySendStateSpace; } return result; } int Message::timeLeft() const { const auto item = message(); auto result = 0; if (auto views = item->Get<HistoryMessageViews>()) { result += st::historyViewsSpace + views->_viewsWidth + st::historyViewsWidth; } else if (item->id < 0 && item->history()->peer->isSelf()) { if (!hasOutLayout()) { result += st::historySendStateSpace; } } return result; } int Message::plainMaxWidth() const { return st::msgPadding.left() + (hasVisibleText() ? message()->_text.maxWidth() : 0) + st::msgPadding.right(); } void Message::initLogEntryOriginal() { if (const auto log = message()->Get<HistoryMessageLogEntryOriginal>()) { AddComponents(LogEntryOriginal::Bit()); const auto entry = Get<LogEntryOriginal>(); entry->page = std::make_unique<HistoryWebPage>(this, log->page); } } HistoryWebPage *Message::logEntryOriginal() const { if (const auto entry = Get<LogEntryOriginal>()) { return entry->page.get(); } return nullptr; } bool Message::hasFromName() const { switch (context()) { case Context::AdminLog: case Context::Feed: return true; case Context::History: { const auto item = message(); return !hasOutLayout() && (!item->history()->peer->isUser() || item->history()->peer->isSelf()); } break; } Unexpected("Context in Message::hasFromPhoto."); } bool Message::displayFromName() const { if (!hasFromName()) return false; if (isAttachedToPrevious()) return false; return true; } bool Message::displayForwardedFrom() const { const auto item = message(); if (const auto forwarded = item->Get<HistoryMessageForwarded>()) { if (item->history()->peer->isSelf()) { return false; } const auto media = this->media(); return item->Has<HistoryMessageVia>() || !media || !media->isDisplayed() || !media->hideForwardedFrom() || forwarded->originalSender->isChannel(); } return false; } bool Message::hasOutLayout() const { const auto item = message(); if (item->history()->peer->isSelf()) { return !item->Has<HistoryMessageForwarded>(); } return item->out() && !item->isPost(); } bool Message::drawBubble() const { const auto item = message(); if (isHidden()) { return false; } else if (logEntryOriginal()) { return true; } const auto media = this->media(); return media ? (hasVisibleText() || media->needsBubble()) : !item->isEmpty(); } bool Message::hasBubble() const { return drawBubble(); } bool Message::hasFastReply() const { if (context() != Context::History) { return false; } const auto peer = data()->history()->peer; return !hasOutLayout() && (peer->isChat() || peer->isMegagroup()); } bool Message::displayFastReply() const { return hasFastReply() && data()->history()->peer->canWrite() && !delegate()->elementInSelectionMode(); } bool Message::displayRightAction() const { return displayFastShare() || displayGoToOriginal(); } bool Message::displayFastShare() const { const auto item = message(); const auto peer = item->history()->peer; if (peer->isChannel()) { return !peer->isMegagroup(); } else if (const auto user = peer->asUser()) { if (user->botInfo && !item->out()) { if (const auto media = this->media()) { return media->allowsFastShare(); } } } return false; } bool Message::displayGoToOriginal() const { const auto item = message(); const auto peer = item->history()->peer; if (peer->isSelf()) { if (const auto forwarded = item->Get<HistoryMessageForwarded>()) { return forwarded->savedFromPeer && forwarded->savedFromMsgId; } } return false; } void Message::drawRightAction( Painter &p, int left, int top, int outerWidth) const { p.setPen(Qt::NoPen); p.setBrush(st::msgServiceBg); { PainterHighQualityEnabler hq(p); p.drawEllipse(rtlrect( left, top, st::historyFastShareSize, st::historyFastShareSize, outerWidth)); } if (displayFastShare()) { st::historyFastShareIcon.paint(p, left, top, outerWidth); } else { st::historyGoToOriginalIcon.paint(p, left, top, outerWidth); } } ClickHandlerPtr Message::rightActionLink() const { if (!_rightActionLink) { const auto itemId = data()->fullId(); const auto forwarded = data()->Get<HistoryMessageForwarded>(); const auto savedFromPeer = forwarded ? forwarded->savedFromPeer : nullptr; const auto savedFromMsgId = forwarded ? forwarded->savedFromMsgId : 0; _rightActionLink = std::make_shared<LambdaClickHandler>([=] { if (const auto item = App::histItemById(itemId)) { if (savedFromPeer && savedFromMsgId) { App::wnd()->controller()->showPeerHistory( savedFromPeer, Window::SectionShow::Way::Forward, savedFromMsgId); } else { FastShareMessage(item); } } }); } return _rightActionLink; } ClickHandlerPtr Message::fastReplyLink() const { if (!_fastReplyLink) { const auto itemId = data()->fullId(); _fastReplyLink = std::make_shared<LambdaClickHandler>([=] { if (const auto item = App::histItemById(itemId)) { if (const auto main = App::main()) { main->replyToItem(item); } } }); } return _fastReplyLink; } void Message::updateMediaInBubbleState() { const auto item = message(); const auto media = this->media(); auto mediaHasSomethingBelow = false; auto mediaHasSomethingAbove = false; auto getMediaHasSomethingAbove = [&] { return displayFromName() || displayForwardedFrom() || item->Has<HistoryMessageReply>() || item->Has<HistoryMessageVia>(); }; auto entry = logEntryOriginal(); if (entry) { mediaHasSomethingBelow = true; mediaHasSomethingAbove = getMediaHasSomethingAbove(); auto entryState = (mediaHasSomethingAbove || hasVisibleText() || (media && media->isDisplayed())) ? MediaInBubbleState::Bottom : MediaInBubbleState::None; entry->setInBubbleState(entryState); } if (!media) { return; } media->updateNeedBubbleState(); if (!drawBubble()) { media->setInBubbleState(MediaInBubbleState::None); return; } if (!entry) { mediaHasSomethingAbove = getMediaHasSomethingAbove(); } if (hasVisibleText()) { mediaHasSomethingAbove = true; } const auto state = [&] { if (mediaHasSomethingAbove) { if (mediaHasSomethingBelow) { return MediaInBubbleState::Middle; } return MediaInBubbleState::Bottom; } else if (mediaHasSomethingBelow) { return MediaInBubbleState::Top; } return MediaInBubbleState::None; }(); media->setInBubbleState(state); } void Message::fromNameUpdated(int width) const { const auto item = message(); const auto replyWidth = hasFastReply() ? st::msgFont->width(FastReplyText()) : 0; if (item->hasAdminBadge()) { const auto badgeWidth = st::msgFont->width(AdminBadgeText()); width -= st::msgPadding.right() + std::max(badgeWidth, replyWidth); } else if (replyWidth) { width -= st::msgPadding.right() + replyWidth; } item->_fromNameVersion = item->displayFrom()->nameVersion; if (const auto via = item->Get<HistoryMessageVia>()) { if (!displayForwardedFrom()) { via->resize(width - st::msgPadding.left() - st::msgPadding.right() - item->author()->nameText.maxWidth() - st::msgServiceFont->spacew); } } } TextSelection Message::skipTextSelection(TextSelection selection) const { return HistoryView::UnshiftItemSelection(selection, message()->_text); } TextSelection Message::unskipTextSelection(TextSelection selection) const { return HistoryView::ShiftItemSelection(selection, message()->_text); } QRect Message::countGeometry() const { const auto item = message(); const auto media = this->media(); const auto mediaWidth = (media && media->isDisplayed()) ? media->width() : width(); const auto outbg = hasOutLayout(); const auto availableWidth = width() - st::msgMargin.left() - st::msgMargin.right(); auto contentLeft = (outbg && !Adaptive::ChatWide()) ? st::msgMargin.right() : st::msgMargin.left(); auto contentWidth = availableWidth; if (hasFromPhoto()) { contentLeft += st::msgPhotoSkip; if (displayRightAction()) { contentWidth -= st::msgPhotoSkip; } //} else if (!Adaptive::Wide() && !out() && !fromChannel() && st::msgPhotoSkip - (hmaxwidth - hwidth) > 0) { // contentLeft += st::msgPhotoSkip - (hmaxwidth - hwidth); } accumulate_min(contentWidth, maxWidth()); accumulate_min(contentWidth, st::msgMaxWidth); if (mediaWidth < contentWidth) { const auto textualWidth = plainMaxWidth(); if (mediaWidth < textualWidth) { accumulate_min(contentWidth, textualWidth); } else { contentWidth = mediaWidth; } } if (contentWidth < availableWidth && outbg && !Adaptive::ChatWide()) { contentLeft += availableWidth - contentWidth; } const auto contentTop = marginTop(); return QRect( contentLeft, contentTop, contentWidth, height() - contentTop - marginBottom()); } int Message::resizeContentGetHeight(int newWidth) { if (isHidden()) { return marginTop() + marginBottom(); } else if (newWidth < st::msgMinWidth) { return height(); } auto newHeight = minHeight(); const auto item = message(); const auto media = this->media(); const auto mediaDisplayed = media ? media->isDisplayed() : false; const auto bubble = drawBubble(); // This code duplicates countGeometry() but also resizes media. auto contentWidth = newWidth - (st::msgMargin.left() + st::msgMargin.right()); if (hasFromPhoto() && displayRightAction()) { contentWidth -= st::msgPhotoSkip; } accumulate_min(contentWidth, maxWidth()); accumulate_min(contentWidth, st::msgMaxWidth); if (mediaDisplayed) { media->resizeGetHeight(contentWidth); if (media->width() < contentWidth) { const auto textualWidth = plainMaxWidth(); if (media->width() < textualWidth) { accumulate_min(contentWidth, textualWidth); } else { contentWidth = media->width(); } } } if (bubble) { auto reply = item->Get<HistoryMessageReply>(); auto via = item->Get<HistoryMessageVia>(); auto entry = logEntryOriginal(); // Entry page is always a bubble bottom. auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/); auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop()); if (contentWidth == maxWidth()) { if (mediaDisplayed) { if (entry) { newHeight += entry->resizeGetHeight(contentWidth); } } else if (entry) { // In case of text-only message it is counted in minHeight already. entry->resizeGetHeight(contentWidth); } } else { if (hasVisibleText()) { auto textWidth = qMax(contentWidth - st::msgPadding.left() - st::msgPadding.right(), 1); if (textWidth != item->_textWidth) { item->_textWidth = textWidth; item->_textHeight = item->_text.countHeight(textWidth); } newHeight = item->_textHeight; } else { newHeight = 0; } if (!mediaOnBottom) { newHeight += st::msgPadding.bottom(); if (mediaDisplayed) newHeight += st::mediaInBubbleSkip; } if (!mediaOnTop) { newHeight += st::msgPadding.top(); if (mediaDisplayed) newHeight += st::mediaInBubbleSkip; if (entry) newHeight += st::mediaInBubbleSkip; } if (mediaDisplayed) { newHeight += media->height(); if (entry) { newHeight += entry->resizeGetHeight(contentWidth); } } else if (entry) { newHeight += entry->resizeGetHeight(contentWidth); } } if (displayFromName()) { fromNameUpdated(contentWidth); newHeight += st::msgNameFont->height; } else if (via && !displayForwardedFrom()) { via->resize(contentWidth - st::msgPadding.left() - st::msgPadding.right()); newHeight += st::msgNameFont->height; } if (displayForwardedFrom()) { auto forwarded = item->Get<HistoryMessageForwarded>(); auto fwdheight = ((forwarded->text.maxWidth() > (contentWidth - st::msgPadding.left() - st::msgPadding.right())) ? 2 : 1) * st::semiboldFont->height; newHeight += fwdheight; } if (reply) { reply->resize(contentWidth - st::msgPadding.left() - st::msgPadding.right()); newHeight += st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); } } else if (mediaDisplayed) { newHeight = media->height(); } else { newHeight = 0; } if (const auto keyboard = item->inlineReplyKeyboard()) { const auto keyboardHeight = st::msgBotKbButton.margin + keyboard->naturalHeight(); newHeight += keyboardHeight; keyboard->resize(contentWidth, keyboardHeight - st::msgBotKbButton.margin); } newHeight += marginTop() + marginBottom(); return newHeight; } bool Message::hasVisibleText() const { if (message()->emptyText()) { return false; } const auto media = this->media(); return !media || !media->hideMessageText(); } QSize Message::performCountCurrentSize(int newWidth) { const auto item = message(); const auto newHeight = resizeContentGetHeight(newWidth); const auto keyboard = item->inlineReplyKeyboard(); if (const auto markup = item->Get<HistoryMessageReplyMarkup>()) { const auto oldTop = markup->oldTop; if (oldTop >= 0) { markup->oldTop = -1; if (keyboard) { const auto height = st::msgBotKbButton.margin + keyboard->naturalHeight(); const auto keyboardTop = newHeight - height + st::msgBotKbButton.margin - marginBottom(); if (keyboardTop != oldTop) { Notify::inlineKeyboardMoved(item, oldTop, keyboardTop); } } } } return { newWidth, newHeight }; } void Message::refreshEditedBadge() { const auto item = message(); const auto edited = displayedEditBadge(); const auto editDate = displayedEditDate(); const auto dateText = dateTime().toString(cTimeFormat()); if (edited) { edited->refresh(dateText, editDate != 0); } if (const auto msgsigned = item->Get<HistoryMessageSigned>()) { const auto text = (!edited || !editDate) ? dateText : edited->text.originalText(); msgsigned->refresh(text); } initTime(); } void Message::initTime() { const auto item = message(); if (const auto msgsigned = item->Get<HistoryMessageSigned>()) { item->_timeWidth = msgsigned->maxWidth(); } else if (const auto edited = displayedEditBadge()) { item->_timeWidth = edited->maxWidth(); } else { item->_timeText = dateTime().toString(cTimeFormat()); item->_timeWidth = st::msgDateFont->width(item->_timeText); } if (const auto views = item->Get<HistoryMessageViews>()) { views->_viewsText = (views->_views >= 0) ? FormatViewsCount(views->_views) : QString(); views->_viewsWidth = views->_viewsText.isEmpty() ? 0 : st::msgDateFont->width(views->_viewsText); } if (item->_text.hasSkipBlock()) { if (item->_text.updateSkipBlock(skipBlockWidth(), skipBlockHeight())) { item->_textWidth = -1; item->_textHeight = 0; } } } bool Message::displayEditedBadge() const { return (displayedEditDate() != TimeId(0)); } TimeId Message::displayedEditDate() const { const auto item = message(); auto hasViaBotId = item->Has<HistoryMessageVia>(); auto hasInlineMarkup = (item->inlineReplyMarkup() != nullptr); return displayedEditDate(hasViaBotId || hasInlineMarkup); } TimeId Message::displayedEditDate( bool hasViaBotOrInlineMarkup) const { if (hasViaBotOrInlineMarkup) { return TimeId(0); } else if (const auto fromUser = message()->from()->asUser()) { if (fromUser->botInfo) { return TimeId(0); } } if (const auto edited = displayedEditBadge()) { return edited->date; } return TimeId(0); } HistoryMessageEdited *Message::displayedEditBadge() { if (const auto media = this->media()) { if (media->overrideEditedDate()) { return media->displayedEditBadge(); } } return message()->Get<HistoryMessageEdited>(); } const HistoryMessageEdited *Message::displayedEditBadge() const { if (const auto media = this->media()) { if (media->overrideEditedDate()) { return media->displayedEditBadge(); } } return message()->Get<HistoryMessageEdited>(); } } // namespace HistoryView ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/view/history_view_message.h����������������������������0000664�0000000�0000000�00000010540�13262451251�0026506�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "history/view/history_view_element.h" class HistoryMessage; struct HistoryMessageEdited; namespace HistoryView { // Special type of Component for the channel actions log. struct LogEntryOriginal : public RuntimeComponent<LogEntryOriginal, Element> { LogEntryOriginal(); LogEntryOriginal(LogEntryOriginal &&other); LogEntryOriginal &operator=(LogEntryOriginal &&other); ~LogEntryOriginal(); std::unique_ptr<HistoryWebPage> page; }; class Message : public Element { public: Message( not_null<ElementDelegate*> delegate, not_null<HistoryMessage*> data); int marginTop() const override; int marginBottom() const override; void draw( Painter &p, QRect clip, TextSelection selection, TimeMs ms) const override; PointState pointState(QPoint point) const override; TextState textState( QPoint point, StateRequest request) const override; void updatePressed(QPoint point) override; void drawInfo( Painter &p, int right, int bottom, int width, bool selected, InfoDisplayType type) const override; bool pointInTime( int right, int bottom, QPoint point, InfoDisplayType type) const override; TextWithEntities selectedText(TextSelection selection) const override; TextSelection adjustSelection( TextSelection selection, TextSelectType type) const override; // hasFromPhoto() returns true even if we don't display the photo // but we need to skip a place at the left side for this photo bool hasFromPhoto() const override; bool displayFromPhoto() const override; bool hasFromName() const override; bool displayFromName() const override; bool displayForwardedFrom() const override; bool hasOutLayout() const override; bool drawBubble() const override; bool hasBubble() const override; bool hasFastReply() const override; bool displayFastReply() const override; bool displayRightAction() const override; void drawRightAction( Painter &p, int left, int top, int outerWidth) const override; ClickHandlerPtr rightActionLink() const override; bool displayEditedBadge() const override; TimeId displayedEditDate() const override; int infoWidth() const override; private: not_null<HistoryMessage*> message() const; void initLogEntryOriginal(); void refreshEditedBadge(); void fromNameUpdated(int width) const; [[nodiscard]] TextSelection skipTextSelection( TextSelection selection) const; [[nodiscard]] TextSelection unskipTextSelection( TextSelection selection) const; void paintFromName(Painter &p, QRect &trect, bool selected) const; void paintForwardedInfo(Painter &p, QRect &trect, bool selected) const; void paintReplyInfo(Painter &p, QRect &trect, bool selected) const; // this method draws "via @bot" if it is not painted in forwarded info or in from name void paintViaBotIdInfo(Painter &p, QRect &trect, bool selected) const; void paintText(Painter &p, QRect &trect, TextSelection selection) const; bool getStateFromName( QPoint point, QRect &trect, not_null<TextState*> outResult) const; bool getStateForwardedInfo( QPoint point, QRect &trect, not_null<TextState*> outResult, StateRequest request) const; bool getStateReplyInfo( QPoint point, QRect &trect, not_null<TextState*> outResult) const; bool getStateViaBotIdInfo( QPoint point, QRect &trect, not_null<TextState*> outResult) const; bool getStateText( QPoint point, QRect &trect, not_null<TextState*> outResult, StateRequest request) const; void updateMediaInBubbleState(); QRect countGeometry() const; int resizeContentGetHeight(int newWidth); QSize performCountOptimalSize() override; QSize performCountCurrentSize(int newWidth) override; bool hasVisibleText() const override; bool displayFastShare() const; bool displayGoToOriginal() const; ClickHandlerPtr fastReplyLink() const; TimeId displayedEditDate(bool hasViaBotOrInlineMarkup) const; const HistoryMessageEdited *displayedEditBadge() const; HistoryMessageEdited *displayedEditBadge(); void initTime(); int timeLeft() const; int plainMaxWidth() const; HistoryWebPage *logEntryOriginal() const; mutable ClickHandlerPtr _rightActionLink; mutable ClickHandlerPtr _fastReplyLink; }; } // namespace HistoryView ����������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/view/history_view_object.h�����������������������������0000664�0000000�0000000�00000002262�13262451251�0026332�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace HistoryView { class Object { public: Object() = default; Object(const Object &other) = delete; Object &operator=(const Object &other) = delete; void initDimensions() { setOptimalSize(countOptimalSize()); } int resizeGetHeight(int newWidth) { setCurrentSize(countCurrentSize(newWidth)); return _height; } int maxWidth() const { return _maxWidth; } int minHeight() const { return _minHeight; } int width() const { return _width; } int height() const { return _height; } virtual ~Object() = default; protected: void setOptimalSize(QSize size) { _maxWidth = size.width(); _minHeight = size.height(); } void setCurrentSize(QSize size) { _width = size.width(); _height = size.height(); } private: virtual QSize countOptimalSize() = 0; virtual QSize countCurrentSize(int newWidth) = 0; int _maxWidth = 0; int _minHeight = 0; int _width = 0; int _height = 0; }; } // namespace HistoryView ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/view/history_view_service_message.cpp������������������0000664�0000000�0000000�00000040603�13262451251�0030564�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/view/history_view_service_message.h" #include "history/history.h" #include "history/history_service.h" #include "history/history_media.h" #include "history/history_item_components.h" #include "history/view/history_view_cursor_state.h" #include "data/data_abstract_structure.h" #include "styles/style_history.h" #include "mainwidget.h" #include "layout.h" #include "lang/lang_keys.h" namespace HistoryView { namespace { enum CircleMask { NormalMask = 0x00, InvertedMask = 0x01, }; enum CircleMaskMultiplier { MaskMultiplier = 0x04, }; enum CornerVerticalSide { CornerTop = 0x00, CornerBottom = 0x02, }; enum CornerHorizontalSide { CornerLeft = 0x00, CornerRight = 0x01, }; class ServiceMessageStyleData : public Data::AbstractStructure { public: // circle[CircleMask value] QImage circle[2]; // corners[(CircleMask value) * MaskMultiplier | (CornerVerticalSide value) | (CornerHorizontalSide value)] QPixmap corners[8]; }; Data::GlobalStructurePointer<ServiceMessageStyleData> serviceMessageStyle; int historyServiceMsgRadius() { static int HistoryServiceMsgRadius = ([]() { auto minMsgHeight = (st::msgServiceFont->height + st::msgServicePadding.top() + st::msgServicePadding.bottom()); return minMsgHeight / 2; })(); return HistoryServiceMsgRadius; } int historyServiceMsgInvertedRadius() { static int HistoryServiceMsgInvertedRadius = ([]() { auto minRowHeight = st::msgServiceFont->height; return minRowHeight - historyServiceMsgRadius(); })(); return HistoryServiceMsgInvertedRadius; } int historyServiceMsgInvertedShrink() { static int HistoryServiceMsgInvertedShrink = ([]() { return (historyServiceMsgInvertedRadius() * 2) / 3; })(); return HistoryServiceMsgInvertedShrink; } void createCircleMasks() { serviceMessageStyle.createIfNull(); if (!serviceMessageStyle->circle[NormalMask].isNull()) return; int size = historyServiceMsgRadius() * 2; serviceMessageStyle->circle[NormalMask] = style::createCircleMask(size); int sizeInverted = historyServiceMsgInvertedRadius() * 2; serviceMessageStyle->circle[InvertedMask] = style::createInvertedCircleMask(sizeInverted); } QPixmap circleCorner(int corner) { if (serviceMessageStyle->corners[corner].isNull()) { int maskType = corner / MaskMultiplier; int radius = (maskType == NormalMask ? historyServiceMsgRadius() : historyServiceMsgInvertedRadius()); int size = radius * cIntRetinaFactor(); int xoffset = 0, yoffset = 0; if (corner & CornerRight) { xoffset = size; } if (corner & CornerBottom) { yoffset = size; } auto part = QRect(xoffset, yoffset, size, size); auto result = style::colorizeImage(serviceMessageStyle->circle[maskType], st::msgServiceBg, part); result.setDevicePixelRatio(cRetinaFactor()); serviceMessageStyle->corners[corner] = App::pixmapFromImageInPlace(std::move(result)); } return serviceMessageStyle->corners[corner]; } enum class SideStyle { Rounded, Plain, Inverted, }; // Returns amount of pixels already painted vertically (so you can skip them in the complex rect shape). int paintBubbleSide(Painter &p, int x, int y, int width, SideStyle style, CornerVerticalSide side) { if (style == SideStyle::Rounded) { auto left = circleCorner((NormalMask * MaskMultiplier) | side | CornerLeft); int leftWidth = left.width() / cIntRetinaFactor(); p.drawPixmap(x, y, left); auto right = circleCorner((NormalMask * MaskMultiplier) | side | CornerRight); int rightWidth = right.width() / cIntRetinaFactor(); p.drawPixmap(x + width - rightWidth, y, right); int cornerHeight = left.height() / cIntRetinaFactor(); p.fillRect(x + leftWidth, y, width - leftWidth - rightWidth, cornerHeight, st::msgServiceBg); return cornerHeight; } else if (style == SideStyle::Inverted) { // CornerLeft and CornerRight are inverted for SideStyle::Inverted sprites. auto left = circleCorner((InvertedMask * MaskMultiplier) | side | CornerRight); int leftWidth = left.width() / cIntRetinaFactor(); p.drawPixmap(x - leftWidth, y, left); auto right = circleCorner((InvertedMask * MaskMultiplier) | side | CornerLeft); p.drawPixmap(x + width, y, right); } return 0; } void paintBubblePart(Painter &p, int x, int y, int width, int height, SideStyle topStyle, SideStyle bottomStyle, bool forceShrink = false) { if (topStyle == SideStyle::Inverted || bottomStyle == SideStyle::Inverted || forceShrink) { width -= historyServiceMsgInvertedShrink() * 2; x += historyServiceMsgInvertedShrink(); } if (int skip = paintBubbleSide(p, x, y, width, topStyle, CornerTop)) { y += skip; height -= skip; } int bottomSize = 0; if (bottomStyle == SideStyle::Rounded) { bottomSize = historyServiceMsgRadius(); } else if (bottomStyle == SideStyle::Inverted) { bottomSize = historyServiceMsgInvertedRadius(); } if (int skip = paintBubbleSide(p, x, y + height - bottomSize, width, bottomStyle, CornerBottom)) { height -= skip; } p.fillRect(x, y, width, height, st::msgServiceBg); } void paintPreparedDate(Painter &p, const QString &dateText, int dateTextWidth, int y, int w) { int left = st::msgServiceMargin.left(); int maxwidth = w; if (Adaptive::ChatWide()) { maxwidth = qMin(maxwidth, WideChatWidth()); } w = maxwidth - st::msgServiceMargin.left() - st::msgServiceMargin.left(); left += (w - dateTextWidth - st::msgServicePadding.left() - st::msgServicePadding.right()) / 2; int height = st::msgServicePadding.top() + st::msgServiceFont->height + st::msgServicePadding.bottom(); ServiceMessagePainter::paintBubble(p, left, y + st::msgServiceMargin.top(), dateTextWidth + st::msgServicePadding.left() + st::msgServicePadding.left(), height); p.setFont(st::msgServiceFont); p.setPen(st::msgServiceFg); p.drawText(left + st::msgServicePadding.left(), y + st::msgServiceMargin.top() + st::msgServicePadding.top() + st::msgServiceFont->ascent, dateText); } } // namepsace int WideChatWidth() { return st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left(); } void ServiceMessagePainter::paintDate(Painter &p, const QDateTime &date, int y, int w) { auto dateText = langDayOfMonthFull(date.date()); auto dateTextWidth = st::msgServiceFont->width(dateText); paintPreparedDate(p, dateText, dateTextWidth, y, w); } void ServiceMessagePainter::paintDate(Painter &p, const QString &dateText, int dateTextWidth, int y, int w) { paintPreparedDate(p, dateText, dateTextWidth, y, w); } void ServiceMessagePainter::paintBubble(Painter &p, int x, int y, int w, int h) { createCircleMasks(); paintBubblePart(p, x, y, w, h, SideStyle::Rounded, SideStyle::Rounded); } void ServiceMessagePainter::paintComplexBubble(Painter &p, int left, int width, const Text &text, const QRect &textRect) { createCircleMasks(); auto lineWidths = countLineWidths(text, textRect); int y = st::msgServiceMargin.top(), previousRichWidth = 0; bool previousShrink = false, forceShrink = false; SideStyle topStyle = SideStyle::Rounded, bottomStyle; for (int i = 0, count = lineWidths.size(); i < count; ++i) { auto lineWidth = lineWidths.at(i); if (i + 1 < count) { auto nextLineWidth = lineWidths.at(i + 1); if (nextLineWidth > lineWidth) { bottomStyle = SideStyle::Inverted; } else if (nextLineWidth < lineWidth) { bottomStyle = SideStyle::Rounded; } else { bottomStyle = SideStyle::Plain; } } else { bottomStyle = SideStyle::Rounded; } auto richWidth = lineWidth + st::msgServicePadding.left() + st::msgServicePadding.right(); auto richHeight = st::msgServiceFont->height; if (topStyle == SideStyle::Rounded) { richHeight += st::msgServicePadding.top(); } else if (topStyle == SideStyle::Inverted) { richHeight -= st::msgServicePadding.bottom(); } if (bottomStyle == SideStyle::Rounded) { richHeight += st::msgServicePadding.bottom(); } else if (bottomStyle == SideStyle::Inverted) { richHeight -= st::msgServicePadding.top(); } forceShrink = previousShrink && (richWidth == previousRichWidth); paintBubblePart(p, left + ((width - richWidth) / 2), y, richWidth, richHeight, topStyle, bottomStyle, forceShrink); y += richHeight; previousShrink = forceShrink || (topStyle == SideStyle::Inverted) || (bottomStyle == SideStyle::Inverted); previousRichWidth = richWidth; if (bottomStyle == SideStyle::Inverted) { topStyle = SideStyle::Rounded; } else if (bottomStyle == SideStyle::Rounded) { topStyle = SideStyle::Inverted; } else { topStyle = SideStyle::Plain; } } } QVector<int> ServiceMessagePainter::countLineWidths(const Text &text, const QRect &textRect) { int linesCount = qMax(textRect.height() / st::msgServiceFont->height, 1); QVector<int> lineWidths; lineWidths.reserve(linesCount); text.countLineWidths(textRect.width(), &lineWidths); int minDelta = 2 * (historyServiceMsgRadius() + historyServiceMsgInvertedRadius() - historyServiceMsgInvertedShrink()); for (int i = 0, count = lineWidths.size(); i < count; ++i) { int width = qMax(lineWidths.at(i), 0); if (i > 0) { int widthBefore = lineWidths.at(i - 1); if (width < widthBefore && width + minDelta > widthBefore) { width = widthBefore; } } if (i + 1 < count) { int widthAfter = lineWidths.at(i + 1); if (width < widthAfter && width + minDelta > widthAfter) { width = widthAfter; } } if (width > lineWidths.at(i)) { lineWidths[i] = width; if (i > 0) { int widthBefore = lineWidths.at(i - 1); if (widthBefore != width && widthBefore < width + minDelta && widthBefore + minDelta > width) { i -= 2; } } } } return lineWidths; } void paintEmpty(Painter &p, int width, int height) { } void serviceColorsUpdated() { if (serviceMessageStyle) { for (auto &corner : serviceMessageStyle->corners) { corner = QPixmap(); } } } Service::Service( not_null<ElementDelegate*> delegate, not_null<HistoryService*> data) : Element(delegate, data) { } not_null<HistoryService*> Service::message() const { return static_cast<HistoryService*>(data().get()); } QRect Service::countGeometry() const { auto result = QRect(0, 0, width(), height()); if (Adaptive::ChatWide()) { result.setWidth(qMin(result.width(), st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left())); } return result.marginsRemoved(st::msgServiceMargin); } QSize Service::performCountCurrentSize(int newWidth) { auto newHeight = displayedDateHeight(); if (const auto bar = Get<UnreadBar>()) { newHeight += bar->height(); } if (isHidden()) { return { newWidth, newHeight }; } const auto item = message(); const auto media = this->media(); if (item->_text.isEmpty()) { item->_textHeight = 0; } else { auto contentWidth = newWidth; if (Adaptive::ChatWide()) { accumulate_min(contentWidth, st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left()); } contentWidth -= st::msgServiceMargin.left() + st::msgServiceMargin.left(); // two small margins if (contentWidth < st::msgServicePadding.left() + st::msgServicePadding.right() + 1) { contentWidth = st::msgServicePadding.left() + st::msgServicePadding.right() + 1; } auto nwidth = qMax(contentWidth - st::msgServicePadding.left() - st::msgServicePadding.right(), 0); if (nwidth != item->_textWidth) { item->_textWidth = nwidth; item->_textHeight = item->_text.countHeight(nwidth); } if (contentWidth >= maxWidth()) { newHeight += minHeight(); } else { newHeight += item->_textHeight; } newHeight += st::msgServicePadding.top() + st::msgServicePadding.bottom() + st::msgServiceMargin.top() + st::msgServiceMargin.bottom(); if (media) { newHeight += st::msgServiceMargin.top() + media->resizeGetHeight(media->maxWidth()); } } return { newWidth, newHeight }; } QSize Service::performCountOptimalSize() { const auto item = message(); const auto media = this->media(); auto maxWidth = item->_text.maxWidth() + st::msgServicePadding.left() + st::msgServicePadding.right(); auto minHeight = item->_text.minHeight(); if (media) { media->initDimensions(); } return { maxWidth, minHeight }; } bool Service::isHidden() const { if (context() == Context::Feed) { return true; } return Element::isHidden(); } int Service::marginTop() const { return st::msgServiceMargin.top(); } int Service::marginBottom() const { return st::msgServiceMargin.bottom(); } void Service::draw( Painter &p, QRect clip, TextSelection selection, TimeMs ms) const { const auto item = message(); auto g = countGeometry(); if (g.width() < 1) { return; } auto height = this->height() - st::msgServiceMargin.top() - st::msgServiceMargin.bottom(); auto dateh = 0; auto unreadbarh = 0; if (auto date = Get<DateBadge>()) { dateh = date->height(); p.translate(0, dateh); clip.translate(0, -dateh); height -= dateh; } if (const auto bar = Get<UnreadBar>()) { unreadbarh = bar->height(); if (clip.intersects(QRect(0, 0, width(), unreadbarh))) { bar->paint(p, 0, width()); } p.translate(0, unreadbarh); clip.translate(0, -unreadbarh); height -= unreadbarh; } if (isHidden()) { if (auto skiph = dateh + unreadbarh) { p.translate(0, -skiph); } return; } paintHighlight(p, height); p.setTextPalette(st::serviceTextPalette); if (auto media = this->media()) { height -= st::msgServiceMargin.top() + media->height(); auto left = st::msgServiceMargin.left() + (g.width() - media->maxWidth()) / 2, top = st::msgServiceMargin.top() + height + st::msgServiceMargin.top(); p.translate(left, top); media->draw(p, clip.translated(-left, -top), TextSelection(), ms); p.translate(-left, -top); } auto trect = QRect(g.left(), st::msgServiceMargin.top(), g.width(), height).marginsAdded(-st::msgServicePadding); ServiceMessagePainter::paintComplexBubble(p, g.left(), g.width(), item->_text, trect); p.setBrush(Qt::NoBrush); p.setPen(st::msgServiceFg); p.setFont(st::msgServiceFont); item->_text.draw(p, trect.x(), trect.y(), trect.width(), Qt::AlignCenter, 0, -1, selection, false); p.restoreTextPalette(); if (auto skiph = dateh + unreadbarh) { p.translate(0, -skiph); } } PointState Service::pointState(QPoint point) const { const auto item = message(); const auto media = this->media(); auto g = countGeometry(); if (g.width() < 1 || isHidden()) { return PointState::Outside; } if (const auto dateh = displayedDateHeight()) { g.setTop(g.top() + dateh); } if (const auto bar = Get<UnreadBar>()) { g.setTop(g.top() + bar->height()); } if (media) { g.setHeight(g.height() - (st::msgServiceMargin.top() + media->height())); } return g.contains(point) ? PointState::Inside : PointState::Outside; } TextState Service::textState(QPoint point, StateRequest request) const { const auto item = message(); const auto media = this->media(); auto result = TextState(item); auto g = countGeometry(); if (g.width() < 1 || isHidden()) { return result; } if (const auto dateh = displayedDateHeight()) { point.setY(point.y() - dateh); g.setHeight(g.height() - dateh); } if (const auto bar = Get<UnreadBar>()) { auto unreadbarh = bar->height(); point.setY(point.y() - unreadbarh); g.setHeight(g.height() - unreadbarh); } if (media) { g.setHeight(g.height() - (st::msgServiceMargin.top() + media->height())); } auto trect = g.marginsAdded(-st::msgServicePadding); if (trect.contains(point)) { auto textRequest = request.forText(); textRequest.align = style::al_center; result = TextState(item, item->_text.getState( point - trect.topLeft(), trect.width(), textRequest)); if (auto gamescore = item->Get<HistoryServiceGameScore>()) { if (!result.link && result.cursor == CursorState::Text && g.contains(point)) { result.link = gamescore->lnk; } } else if (auto payment = item->Get<HistoryServicePayment>()) { if (!result.link && result.cursor == CursorState::Text && g.contains(point)) { result.link = payment->lnk; } } } else if (media) { result = media->textState(point - QPoint(st::msgServiceMargin.left() + (g.width() - media->maxWidth()) / 2, st::msgServiceMargin.top() + g.height() + st::msgServiceMargin.top()), request); } return result; } void Service::updatePressed(QPoint point) { } TextWithEntities Service::selectedText(TextSelection selection) const { return message()->_text.originalTextWithEntities(selection); } TextSelection Service::adjustSelection( TextSelection selection, TextSelectType type) const { return message()->_text.adjustSelection(selection, type); } } // namespace HistoryView �����������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/view/history_view_service_message.h��������������������0000664�0000000�0000000�00000003747�13262451251�0030241�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "history/view/history_view_element.h" class HistoryService; namespace HistoryView { class Service : public Element { public: Service( not_null<ElementDelegate*> delegate, not_null<HistoryService*> data); int marginTop() const override; int marginBottom() const override; bool isHidden() const override; void draw( Painter &p, QRect clip, TextSelection selection, TimeMs ms) const override; PointState pointState(QPoint point) const override; TextState textState( QPoint point, StateRequest request) const override; void updatePressed(QPoint point) override; TextWithEntities selectedText(TextSelection selection) const override; TextSelection adjustSelection( TextSelection selection, TextSelectType type) const override; private: not_null<HistoryService*> message() const; QRect countGeometry() const; QSize performCountOptimalSize() override; QSize performCountCurrentSize(int newWidth) override; }; int WideChatWidth(); struct PaintContext { PaintContext(TimeMs ms, const QRect &clip, TextSelection selection) : ms(ms) , clip(clip) , selection(selection) { } TimeMs ms; const QRect &clip; TextSelection selection; }; class ServiceMessagePainter { public: static void paintDate(Painter &p, const QDateTime &date, int y, int w); static void paintDate(Painter &p, const QString &dateText, int dateTextWidth, int y, int w); static void paintBubble(Painter &p, int x, int y, int w, int h); static void paintComplexBubble(Painter &p, int left, int width, const Text &text, const QRect &textRect); private: static QVector<int> countLineWidths(const Text &text, const QRect &textRect); }; void paintEmpty(Painter &p, int width, int height); void serviceColorsUpdated(); } // namespace HistoryView �������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp�������������������0000664�0000000�0000000�00000054747�13262451251�0030427�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/view/history_view_top_bar_widget.h" #include <rpl/combine.h> #include <rpl/combine_previous.h> #include "history/history.h" #include "boxes/add_contact_box.h" #include "boxes/confirm_box.h" #include "info/info_memento.h" #include "info/info_controller.h" #include "storage/storage_shared_media.h" #include "mainwidget.h" #include "mainwindow.h" #include "shortcuts.h" #include "auth_session.h" #include "lang/lang_keys.h" #include "ui/special_buttons.h" #include "ui/unread_badge.h" #include "ui/widgets/buttons.h" #include "ui/widgets/dropdown_menu.h" #include "window/window_controller.h" #include "window/window_peer_menu.h" #include "calls/calls_instance.h" #include "data/data_peer_values.h" #include "data/data_feed.h" #include "observer_peer.h" #include "apiwrap.h" #include "styles/style_window.h" #include "styles/style_dialogs.h" #include "styles/style_history.h" #include "styles/style_info.h" namespace HistoryView { TopBarWidget::TopBarWidget( QWidget *parent, not_null<Window::Controller*> controller) : RpWidget(parent) , _controller(controller) , _clear(this, langFactory(lng_selected_clear), st::topBarClearButton) , _forward(this, langFactory(lng_selected_forward), st::defaultActiveButton) , _delete(this, langFactory(lng_selected_delete), st::defaultActiveButton) , _back(this, st::historyTopBarBack) , _call(this, st::topBarCall) , _search(this, st::topBarSearch) , _infoToggle(this, st::topBarInfo) , _menuToggle(this, st::topBarMenuToggle) , _onlineUpdater([this] { updateOnlineDisplay(); }) { subscribe(Lang::Current().updated(), [this] { refreshLang(); }); setAttribute(Qt::WA_OpaquePaintEvent); _forward->setClickedCallback([this] { _forwardSelection.fire({}); }); _forward->setWidthChangedCallback([this] { updateControlsGeometry(); }); _delete->setClickedCallback([this] { _deleteSelection.fire({}); }); _delete->setWidthChangedCallback([this] { updateControlsGeometry(); }); _clear->setClickedCallback([this] { _clearSelection.fire({}); }); _call->setClickedCallback([this] { onCall(); }); _search->setClickedCallback([this] { onSearch(); }); _menuToggle->setClickedCallback([this] { showMenu(); }); _infoToggle->setClickedCallback([this] { toggleInfoSection(); }); _back->addClickHandler([this] { backClicked(); }); rpl::combine( _controller->activeChatValue(), _controller->searchInChat.value() ) | rpl::combine_previous( std::make_tuple(Dialogs::Key(), Dialogs::Key()) ) | rpl::map([]( const std::tuple<Dialogs::Key, Dialogs::Key> &previous, const std::tuple<Dialogs::Key, Dialogs::Key> ¤t) { auto active = std::get<0>(current); auto search = std::get<1>(current); auto activeChanged = (active != std::get<0>(previous)); auto searchInChat = search && (active == search); return std::make_tuple(searchInChat, activeChanged); }) | rpl::start_with_next([this]( bool searchInActiveChat, bool activeChanged) { auto animated = activeChanged ? anim::type::instant : anim::type::normal; _search->setForceRippled(searchInActiveChat, animated); }, lifetime()); subscribe(Adaptive::Changed(), [this] { updateAdaptiveLayout(); }); if (Adaptive::OneColumn()) { createUnreadBadge(); } subscribe( App::histories().sendActionAnimationUpdated(), [this](const Histories::SendActionAnimationUpdate &update) { if (update.history == _activeChat.history()) { this->update(); } }); using UpdateFlag = Notify::PeerUpdate::Flag; auto flags = UpdateFlag::UserHasCalls | UpdateFlag::UserOnlineChanged | UpdateFlag::MembersChanged; subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(flags, [this](const Notify::PeerUpdate &update) { if (update.flags & UpdateFlag::UserHasCalls) { if (update.peer->isUser()) { updateControlsVisibility(); } } else { updateOnlineDisplay(); } })); subscribe(Global::RefPhoneCallsEnabledChanged(), [this] { updateControlsVisibility(); }); rpl::combine( Auth().settings().thirdSectionInfoEnabledValue(), Auth().settings().tabbedReplacedWithInfoValue() ) | rpl::start_with_next( [this] { updateInfoToggleActive(); }, lifetime()); setCursor(style::cur_pointer); updateControlsVisibility(); } void TopBarWidget::refreshLang() { InvokeQueued(this, [this] { updateControlsGeometry(); }); } void TopBarWidget::onSearch() { if (_activeChat) { App::main()->searchInChat(_activeChat); } } void TopBarWidget::onCall() { if (const auto peer = _activeChat.peer()) { if (const auto user = peer->asUser()) { Calls::Current().startOutgoingCall(user); } } } void TopBarWidget::showMenu() { if (!_activeChat || _menu) { return; } _menu.create(parentWidget()); _menu->setHiddenCallback([weak = make_weak(this), menu = _menu.data()] { menu->deleteLater(); if (weak && weak->_menu == menu) { weak->_menu = nullptr; weak->_menuToggle->setForceRippled(false); } }); _menu->setShowStartCallback(base::lambda_guarded(this, [this, menu = _menu.data()] { if (_menu == menu) { _menuToggle->setForceRippled(true); } })); _menu->setHideStartCallback(base::lambda_guarded(this, [this, menu = _menu.data()] { if (_menu == menu) { _menuToggle->setForceRippled(false); } })); _menuToggle->installEventFilter(_menu); const auto addAction = [&]( const QString &text, base::lambda<void()> callback) { return _menu->addAction(text, std::move(callback)); }; if (const auto peer = _activeChat.peer()) { Window::FillPeerMenu( _controller, peer, addAction, Window::PeerMenuSource::History); } else if (const auto feed = _activeChat.feed()) { Window::FillFeedMenu( _controller, feed, addAction, Window::PeerMenuSource::History); } else { Unexpected("Empty active chat in TopBarWidget::showMenu."); } _menu->moveToRight((parentWidget()->width() - width()) + st::topBarMenuPosition.x(), st::topBarMenuPosition.y()); _menu->showAnimated(Ui::PanelAnimation::Origin::TopRight); } void TopBarWidget::toggleInfoSection() { if (Adaptive::ThreeColumn() && (Auth().settings().thirdSectionInfoEnabled() || Auth().settings().tabbedReplacedWithInfo())) { _controller->closeThirdSection(); } else if (_activeChat) { if (_controller->canShowThirdSection()) { Auth().settings().setThirdSectionInfoEnabled(true); Auth().saveSettingsDelayed(); if (Adaptive::ThreeColumn()) { _controller->showSection( Info::Memento::Default(_activeChat), Window::SectionShow().withThirdColumn()); } else { _controller->resizeForThirdSection(); _controller->updateColumnLayout(); } } else { infoClicked(); } } else { updateControlsVisibility(); } } bool TopBarWidget::eventFilter(QObject *obj, QEvent *e) { if (obj == _membersShowArea) { switch (e->type()) { case QEvent::MouseButtonPress: mousePressEvent(static_cast<QMouseEvent*>(e)); return true; case QEvent::Enter: _membersShowAreaActive.fire(true); break; case QEvent::Leave: _membersShowAreaActive.fire(false); break; } } return TWidget::eventFilter(obj, e); } int TopBarWidget::resizeGetHeight(int newWidth) { return st::topBarHeight; } void TopBarWidget::paintEvent(QPaintEvent *e) { if (_animatingMode) { return; } Painter p(this); auto ms = getms(); _forward->stepNumbersAnimation(ms); _delete->stepNumbersAnimation(ms); auto hasSelected = (_selectedCount > 0); auto selectedButtonsTop = countSelectedButtonsTop(_selectedShown.current(getms(), hasSelected ? 1. : 0.)); p.fillRect(QRect(0, 0, width(), st::topBarHeight), st::topBarBg); if (selectedButtonsTop < 0) { p.translate(0, selectedButtonsTop + st::topBarHeight); paintTopBar(p, ms); } } void TopBarWidget::paintTopBar(Painter &p, TimeMs ms) { if (!_activeChat) { return; } auto nameleft = _leftTaken; auto nametop = st::topBarArrowPadding.top(); auto statustop = st::topBarHeight - st::topBarArrowPadding.bottom() - st::dialogsTextFont->height; auto namewidth = width() - _rightTaken - nameleft; auto history = _activeChat.history(); p.setPen(st::dialogsNameFg); if (const auto feed = _activeChat.feed()) { auto text = feed->chatsListName(); // TODO feed name emoji auto textWidth = st::historySavedFont->width(text); if (namewidth < textWidth) { text = st::historySavedFont->elided(text, namewidth); } p.setFont(st::historySavedFont); p.drawTextLeft( nameleft, (height() - st::historySavedFont->height) / 2, width(), text); } else if (_activeChat.peer()->isSelf()) { auto text = lang(lng_saved_messages); auto textWidth = st::historySavedFont->width(text); if (namewidth < textWidth) { text = st::historySavedFont->elided(text, namewidth); } p.setFont(st::historySavedFont); p.drawTextLeft( nameleft, (height() - st::historySavedFont->height) / 2, width(), text); } else if (const auto history = _activeChat.history()) { history->peer->dialogName().drawElided(p, nameleft, nametop, namewidth); p.setFont(st::dialogsTextFont); if (!history->paintSendAction(p, nameleft, statustop, namewidth, width(), st::historyStatusFgTyping, ms)) { auto statustext = _titlePeerText; auto statuswidth = _titlePeerTextWidth; if (statuswidth > namewidth) { statustext = st::dialogsTextFont->elided( statustext, namewidth, Qt::ElideLeft); statuswidth = st::dialogsTextFont->width(statustext); } p.setPen(_titlePeerTextOnline ? st::historyStatusFgActive : st::historyStatusFg); p.drawTextLeft(nameleft, statustop, width(), statustext, statuswidth); } } } QRect TopBarWidget::getMembersShowAreaGeometry() const { int membersTextLeft = _leftTaken; int membersTextTop = st::topBarHeight - st::topBarArrowPadding.bottom() - st::dialogsTextFont->height; int membersTextWidth = _titlePeerTextWidth; int membersTextHeight = st::topBarHeight - membersTextTop; return myrtlrect(membersTextLeft, membersTextTop, membersTextWidth, membersTextHeight); } void TopBarWidget::mousePressEvent(QMouseEvent *e) { auto handleClick = (e->button() == Qt::LeftButton) && (e->pos().y() < st::topBarHeight) && (!_selectedCount); if (handleClick) { if (_animatingMode && _back->rect().contains(e->pos())) { backClicked(); } else { infoClicked(); } } } void TopBarWidget::infoClicked() { if (!_activeChat) { return; } else if (const auto feed = _activeChat.feed()) { _controller->showSection(Info::Memento( feed, Info::Section(Info::Section::Type::Profile))); } else if (_activeChat.peer()->isSelf()) { _controller->showSection(Info::Memento( _activeChat.peer()->id, Info::Section(Storage::SharedMediaType::Photo))); } else { _controller->showPeerInfo(_activeChat.peer()); } } void TopBarWidget::backClicked() { _controller->showBackFromStack(); } void TopBarWidget::setActiveChat(Dialogs::Key chat) { if (_activeChat != chat) { _activeChat = chat; _back->clearState(); update(); updateUnreadBadge(); refreshInfoButton(); if (_menu) { _menuToggle->removeEventFilter(_menu); _menu->hideFast(); } updateOnlineDisplay(); updateControlsVisibility(); } } void TopBarWidget::refreshInfoButton() { if (const auto peer = _activeChat.peer()) { auto info = object_ptr<Ui::UserpicButton>( this, _controller, peer, Ui::UserpicButton::Role::Custom, st::topBarInfoButton); info->showSavedMessagesOnSelf(true); _info.destroy(); _info = std::move(info); } else if (const auto feed = _activeChat.feed()) { _info.destroy(); _info = object_ptr<Ui::FeedUserpicButton>( this, _controller, feed, st::topBarFeedInfoButton); } if (_info) { _info->setAttribute(Qt::WA_TransparentForMouseEvents); } } void TopBarWidget::resizeEvent(QResizeEvent *e) { updateControlsGeometry(); } int TopBarWidget::countSelectedButtonsTop(float64 selectedShown) { return (1. - selectedShown) * (-st::topBarHeight); } void TopBarWidget::updateControlsGeometry() { auto hasSelected = (_selectedCount > 0); auto selectedButtonsTop = countSelectedButtonsTop(_selectedShown.current(hasSelected ? 1. : 0.)); auto otherButtonsTop = selectedButtonsTop + st::topBarHeight; auto buttonsLeft = st::topBarActionSkip + (Adaptive::OneColumn() ? 0 : st::lineWidth); auto buttonsWidth = _forward->contentWidth() + _delete->contentWidth() + _clear->width(); buttonsWidth += buttonsLeft + st::topBarActionSkip * 3; auto widthLeft = qMin(width() - buttonsWidth, -2 * st::defaultActiveButton.width); _forward->setFullWidth(-(widthLeft / 2)); _delete->setFullWidth(-(widthLeft / 2)); selectedButtonsTop += (height() - _forward->height()) / 2; _forward->moveToLeft(buttonsLeft, selectedButtonsTop); if (!_forward->isHidden()) { buttonsLeft += _forward->width() + st::topBarActionSkip; } _delete->moveToLeft(buttonsLeft, selectedButtonsTop); _clear->moveToRight(st::topBarActionSkip, selectedButtonsTop); if (_unreadBadge) { _unreadBadge->setGeometryToLeft( 0, otherButtonsTop + st::titleUnreadCounterTop, _back->width(), st::dialogsUnreadHeight); } if (_back->isHidden()) { _leftTaken = st::topBarArrowPadding.right(); } else { _leftTaken = 0; _back->moveToLeft(_leftTaken, otherButtonsTop); _leftTaken += _back->width(); if (_info && !_info->isHidden()) { _info->moveToLeft(_leftTaken, otherButtonsTop); _leftTaken += _info->width(); } } _rightTaken = 0; _menuToggle->moveToRight(_rightTaken, otherButtonsTop); _rightTaken += _menuToggle->width() + st::topBarSkip; _infoToggle->moveToRight(_rightTaken, otherButtonsTop); if (!_infoToggle->isHidden()) { _rightTaken += _infoToggle->width() + st::topBarSkip; } _search->moveToRight(_rightTaken, otherButtonsTop); _rightTaken += _search->width() + st::topBarCallSkip; _call->moveToRight(_rightTaken, otherButtonsTop); _rightTaken += _call->width(); updateMembersShowArea(); } void TopBarWidget::finishAnimating() { _selectedShown.finish(); updateControlsVisibility(); } void TopBarWidget::setAnimatingMode(bool enabled) { if (_animatingMode != enabled) { _animatingMode = enabled; setAttribute(Qt::WA_OpaquePaintEvent, !_animatingMode); finishAnimating(); updateControlsVisibility(); } } void TopBarWidget::updateControlsVisibility() { if (_animatingMode) { hideChildren(); return; } _clear->show(); _delete->setVisible(_canDelete); _forward->setVisible(_canForward); auto backVisible = Adaptive::OneColumn() || (App::main() && !App::main()->stackIsEmpty()); _back->setVisible(backVisible); if (_info) { _info->setVisible(Adaptive::OneColumn()); } if (_unreadBadge) { _unreadBadge->show(); } _search->show(); _menuToggle->show(); _infoToggle->setVisible(!Adaptive::OneColumn() && _controller->canShowThirdSection()); const auto callsEnabled = [&] { if (const auto peer = _activeChat.peer()) { if (const auto user = peer->asUser()) { return Global::PhoneCallsEnabled() && user->hasCalls(); } } return false; }(); _call->setVisible(callsEnabled); if (_membersShowArea) { _membersShowArea->show(); } updateControlsGeometry(); } void TopBarWidget::updateMembersShowArea() { if (!App::main()) { return; } auto membersShowAreaNeeded = [this]() { auto peer = App::main()->peer(); if ((_selectedCount > 0) || !peer) { return false; } if (auto chat = peer->asChat()) { return chat->amIn(); } if (auto megagroup = peer->asMegagroup()) { return megagroup->canViewMembers() && (megagroup->membersCount() < Global::ChatSizeMax()); } return false; }; if (!membersShowAreaNeeded()) { if (_membersShowArea) { _membersShowAreaActive.fire(false); _membersShowArea.destroy(); } return; } else if (!_membersShowArea) { _membersShowArea.create(this); _membersShowArea->show(); _membersShowArea->installEventFilter(this); } _membersShowArea->setGeometry(getMembersShowAreaGeometry()); } void TopBarWidget::showSelected(SelectedState state) { auto canDelete = (state.count > 0 && state.count == state.canDeleteCount); auto canForward = (state.count > 0 && state.count == state.canForwardCount); if (_selectedCount == state.count && _canDelete == canDelete && _canForward == canForward) { return; } if (state.count == 0) { // Don't change the visible buttons if the selection is cancelled. canDelete = _canDelete; canForward = _canForward; } auto wasSelected = (_selectedCount > 0); _selectedCount = state.count; if (_selectedCount > 0) { _forward->setNumbersText(_selectedCount); _delete->setNumbersText(_selectedCount); if (!wasSelected) { _forward->finishNumbersAnimation(); _delete->finishNumbersAnimation(); } } auto hasSelected = (_selectedCount > 0); if (_canDelete != canDelete || _canForward != canForward) { _canDelete = canDelete; _canForward = canForward; updateControlsVisibility(); } if (wasSelected != hasSelected) { setCursor(hasSelected ? style::cur_default : style::cur_pointer); updateMembersShowArea(); _selectedShown.start( [this] { selectedShowCallback(); }, hasSelected ? 0. : 1., hasSelected ? 1. : 0., st::slideWrapDuration, anim::easeOutCirc); } else { updateControlsGeometry(); } } void TopBarWidget::selectedShowCallback() { updateControlsGeometry(); update(); } void TopBarWidget::updateAdaptiveLayout() { updateControlsVisibility(); if (Adaptive::OneColumn()) { createUnreadBadge(); } else if (_unreadBadge) { unsubscribe(base::take(_unreadCounterSubscription)); _unreadBadge.destroy(); } updateInfoToggleActive(); } void TopBarWidget::createUnreadBadge() { if (_unreadBadge) { return; } _unreadBadge.create(this); _unreadBadge->setGeometryToLeft(0, st::titleUnreadCounterTop, _back->width(), st::dialogsUnreadHeight); _unreadBadge->show(); _unreadBadge->setAttribute(Qt::WA_TransparentForMouseEvents); _unreadCounterSubscription = subscribe( Global::RefUnreadCounterUpdate(), [this] { updateUnreadBadge(); }); updateUnreadBadge(); } void TopBarWidget::updateUnreadBadge() { if (!_unreadBadge) return; auto mutedCount = App::histories().unreadMutedCount(); auto fullCounter = App::histories().unreadBadge() + (Global::IncludeMuted() ? 0 : mutedCount); // Do not include currently shown chat in the top bar unread counter. if (const auto history = _activeChat.history()) { auto shownUnreadCount = history->unreadCount(); fullCounter -= shownUnreadCount; if (history->mute()) { mutedCount -= shownUnreadCount; } } auto active = (mutedCount < fullCounter); _unreadBadge->setText([&] { if (auto counter = (fullCounter - (Global::IncludeMuted() ? 0 : mutedCount))) { return (counter > 999) ? qsl("..%1").arg(counter % 100, 2, 10, QChar('0')) : QString::number(counter); } return QString(); }(), active); } void TopBarWidget::updateInfoToggleActive() { auto infoThirdActive = Adaptive::ThreeColumn() && (Auth().settings().thirdSectionInfoEnabled() || Auth().settings().tabbedReplacedWithInfo()); auto iconOverride = infoThirdActive ? &st::topBarInfoActive : nullptr; auto rippleOverride = infoThirdActive ? &st::lightButtonBgOver : nullptr; _infoToggle->setIconOverride(iconOverride, iconOverride); _infoToggle->setRippleColorOverride(rippleOverride); } void TopBarWidget::updateOnlineDisplay() { if (!_activeChat.peer()) return; QString text; const auto now = unixtime(); bool titlePeerTextOnline = false; if (const auto user = _activeChat.peer()->asUser()) { text = Data::OnlineText(user, now); titlePeerTextOnline = Data::OnlineTextActive(user, now); } else if (const auto chat = _activeChat.peer()->asChat()) { if (!chat->amIn()) { text = lang(lng_chat_status_unaccessible); } else if (chat->participants.empty()) { if (!_titlePeerText.isEmpty()) { text = _titlePeerText; } else if (chat->count <= 0) { text = lang(lng_group_status); } else { text = lng_chat_status_members(lt_count, chat->count); } } else { auto online = 0; auto onlyMe = true; for (auto [user, v] : chat->participants) { if (user->onlineTill > now) { ++online; if (onlyMe && user != App::self()) onlyMe = false; } } if (online > 0 && !onlyMe) { auto membersCount = lng_chat_status_members(lt_count, chat->participants.size()); auto onlineCount = lng_chat_status_online(lt_count, online); text = lng_chat_status_members_online(lt_members_count, membersCount, lt_online_count, onlineCount); } else if (chat->participants.size() > 0) { text = lng_chat_status_members(lt_count, chat->participants.size()); } else { text = lang(lng_group_status); } } } else if (const auto channel = _activeChat.peer()->asChannel()) { if (channel->isMegagroup() && channel->membersCount() > 0 && channel->membersCount() <= Global::ChatSizeMax()) { if (channel->mgInfo->lastParticipants.empty() || channel->lastParticipantsCountOutdated()) { Auth().api().requestLastParticipants(channel); } auto online = 0; bool onlyMe = true; for (auto &participant : std::as_const(channel->mgInfo->lastParticipants)) { if (participant->onlineTill > now) { ++online; if (onlyMe && participant != App::self()) { onlyMe = false; } } } if (online && !onlyMe) { auto membersCount = lng_chat_status_members(lt_count, channel->membersCount()); auto onlineCount = lng_chat_status_online(lt_count, online); text = lng_chat_status_members_online(lt_members_count, membersCount, lt_online_count, onlineCount); } else if (channel->membersCount() > 0) { text = lng_chat_status_members(lt_count, channel->membersCount()); } else { text = lang(lng_group_status); } } else if (channel->membersCount() > 0) { text = lng_chat_status_members(lt_count, channel->membersCount()); } else { text = lang(channel->isMegagroup() ? lng_group_status : lng_channel_status); } } if (_titlePeerText != text) { _titlePeerText = text; _titlePeerTextOnline = titlePeerTextOnline; _titlePeerTextWidth = st::dialogsTextFont->width(_titlePeerText); updateMembersShowArea(); update(); } updateOnlineDisplayTimer(); } void TopBarWidget::updateOnlineDisplayTimer() { if (!_activeChat.peer()) return; const auto now = unixtime(); auto minTimeout = TimeMs(86400); const auto handleUser = [&](not_null<UserData*> user) { auto hisTimeout = Data::OnlineChangeTimeout(user, now); accumulate_min(minTimeout, hisTimeout); }; if (const auto user = _activeChat.peer()->asUser()) { handleUser(user); } else if (auto chat = _activeChat.peer()->asChat()) { for (auto [user, v] : chat->participants) { handleUser(user); } } else if (_activeChat.peer()->isChannel()) { } updateOnlineDisplayIn(minTimeout); } void TopBarWidget::updateOnlineDisplayIn(TimeMs timeout) { _onlineUpdater.callOnce(timeout); } } // namespace HistoryView �������������������������tdesktop-1.2.17/Telegram/SourceFiles/history/view/history_view_top_bar_widget.h���������������������0000664�0000000�0000000�00000006455�13262451251�0030065�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/rp_widget.h" #include "base/timer.h" #include "dialogs/dialogs_key.h" namespace Ui { class AbstractButton; class RoundButton; class IconButton; class DropdownMenu; class UnreadBadge; } // namespace Ui namespace Window { class Controller; } // namespace Window namespace HistoryView { class TopBarWidget : public Ui::RpWidget, private base::Subscriber { public: TopBarWidget( QWidget *parent, not_null<Window::Controller*> controller); struct SelectedState { bool textSelected = false; int count = 0; int canDeleteCount = 0; int canForwardCount = 0; }; void updateControlsVisibility(); void finishAnimating(); void showSelected(SelectedState state); rpl::producer<bool> membersShowAreaActive() const { return _membersShowAreaActive.events(); } void setAnimatingMode(bool enabled); void setActiveChat(Dialogs::Key chat); rpl::producer<> forwardSelectionRequest() const { return _forwardSelection.events(); } rpl::producer<> deleteSelectionRequest() const { return _deleteSelection.events(); } rpl::producer<> clearSelectionRequest() const { return _clearSelection.events(); } protected: void paintEvent(QPaintEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void resizeEvent(QResizeEvent *e) override; bool eventFilter(QObject *obj, QEvent *e) override; int resizeGetHeight(int newWidth) override; private: void refreshInfoButton(); void refreshLang(); void updateControlsGeometry(); void selectedShowCallback(); void updateInfoToggleActive(); void onCall(); void onSearch(); void showMenu(); void toggleInfoSection(); void updateAdaptiveLayout(); int countSelectedButtonsTop(float64 selectedShown); void paintTopBar(Painter &p, TimeMs ms); QRect getMembersShowAreaGeometry() const; void updateMembersShowArea(); void updateOnlineDisplay(); void updateOnlineDisplayTimer(); void updateOnlineDisplayIn(TimeMs timeout); void infoClicked(); void backClicked(); void createUnreadBadge(); void updateUnreadBadge(); not_null<Window::Controller*> _controller; Dialogs::Key _activeChat; int _selectedCount = 0; bool _canDelete = false; bool _canForward = false; Animation _selectedShown; object_ptr<Ui::RoundButton> _clear; object_ptr<Ui::RoundButton> _forward, _delete; object_ptr<Ui::IconButton> _back; object_ptr<Ui::UnreadBadge> _unreadBadge = { nullptr }; object_ptr<Ui::AbstractButton> _info = { nullptr }; object_ptr<Ui::IconButton> _call; object_ptr<Ui::IconButton> _search; object_ptr<Ui::IconButton> _infoToggle; object_ptr<Ui::IconButton> _menuToggle; object_ptr<Ui::DropdownMenu> _menu = { nullptr }; object_ptr<TWidget> _membersShowArea = { nullptr }; rpl::event_stream<bool> _membersShowAreaActive; QString _titlePeerText; bool _titlePeerTextOnline = false; int _titlePeerTextWidth = 0; int _leftTaken = 0; int _rightTaken = 0; bool _animatingMode = false; int _unreadCounterSubscription = 0; base::Timer _onlineUpdater; rpl::event_stream<> _forwardSelection; rpl::event_stream<> _deleteSelection; rpl::event_stream<> _clearSelection; }; } // namespace HistoryView �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/����������������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0020356�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/channels/�������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0022151�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/channels/info_channels_widget.cpp�������������������������0000664�0000000�0000000�00000004461�13262451251�0027033�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/channels/info_channels_widget.h" #include "info/feed/info_feed_channels.h" #include "info/info_controller.h" #include "ui/widgets/scroll_area.h" #include "styles/style_info.h" namespace Info { namespace Channels { Memento::Memento(not_null<Controller*> controller) : Memento(controller->feed()) { } Memento::Memento(not_null<Data::Feed*> feed) : ContentMemento(feed) { } Section Memento::section() const { return Section(Section::Type::Channels); } object_ptr<ContentWidget> Memento::createWidget( QWidget *parent, not_null<Controller*> controller, const QRect &geometry) { auto result = object_ptr<Widget>( parent, controller); result->setInternalState(geometry, this); return std::move(result); } void Memento::setState(std::unique_ptr<SavedState> state) { _state = std::move(state); } std::unique_ptr<SavedState> Memento::state() { return std::move(_state); } Memento::~Memento() = default; Widget::Widget( QWidget *parent, not_null<Controller*> controller) : ContentWidget(parent, controller) { _inner = setInnerWidget(object_ptr<FeedProfile::Channels>( this, controller)); } bool Widget::showInternal(not_null<ContentMemento*> memento) { if (!controller()->validateMementoPeer(memento)) { return false; } if (auto membersMemento = dynamic_cast<Memento*>(memento.get())) { restoreState(membersMemento); return true; } return false; } void Widget::setInternalState( const QRect &geometry, not_null<Memento*> memento) { setGeometry(geometry); Ui::SendPendingMoveResizeEvents(this); restoreState(memento); } std::unique_ptr<ContentMemento> Widget::doCreateMemento() { auto result = std::make_unique<Memento>(controller()); saveState(result.get()); return std::move(result); } void Widget::saveState(not_null<Memento*> memento) { memento->setScrollTop(scrollTopSave()); memento->setState(_inner->saveState()); } void Widget::restoreState(not_null<Memento*> memento) { _inner->restoreState(memento->state()); auto scrollTop = memento->scrollTop(); scrollTopRestore(memento->scrollTop()); } } // namespace Channels } // namespace Info ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/channels/info_channels_widget.h���������������������������0000664�0000000�0000000�00000003010�13262451251�0026465�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "info/info_content_widget.h" struct PeerListState; namespace Data { class Feed; } // namespace Data namespace Info { namespace FeedProfile { class Channels; struct ChannelsState; } // namespace FeedProfile namespace Channels { using SavedState = FeedProfile::ChannelsState; class Memento final : public ContentMemento { public: explicit Memento(not_null<Controller*> controller); explicit Memento(not_null<Data::Feed*> feed); object_ptr<ContentWidget> createWidget( QWidget *parent, not_null<Controller*> controller, const QRect &geometry) override; Section section() const override; void setState(std::unique_ptr<SavedState> state); std::unique_ptr<SavedState> state(); ~Memento(); private: std::unique_ptr<SavedState> _state; }; class Widget final : public ContentWidget { public: Widget( QWidget *parent, not_null<Controller*> controller); bool showInternal( not_null<ContentMemento*> memento) override; void setInternalState( const QRect &geometry, not_null<Memento*> memento); private: void saveState(not_null<Memento*> memento); void restoreState(not_null<Memento*> memento); std::unique_ptr<ContentMemento> doCreateMemento() override; FeedProfile::Channels *_inner = nullptr; }; } // namespace Channels } // namespace Info ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/common_groups/��������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0023245�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/common_groups/info_common_groups_inner_widget.cpp���������0000664�0000000�0000000�00000016762�13262451251�0032425�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/common_groups/info_common_groups_inner_widget.h" #include "info/common_groups/info_common_groups_widget.h" #include "info/info_controller.h" #include "lang/lang_keys.h" #include "styles/style_info.h" #include "styles/style_widgets.h" #include "mtproto/sender.h" #include "window/window_controller.h" #include "ui/widgets/scroll_area.h" #include "ui/search_field_controller.h" #include "apiwrap.h" namespace Info { namespace CommonGroups { namespace { constexpr auto kCommonGroupsPerPage = 40; constexpr auto kCommonGroupsSearchAfter = 20; class ListController : public PeerListController , private base::Subscriber , private MTP::Sender { public: ListController( not_null<Controller*> controller, not_null<UserData*> user); void prepare() override; void rowClicked(not_null<PeerListRow*> row) override; void loadMoreRows() override; std::unique_ptr<PeerListRow> createRestoredRow( not_null<PeerData*> peer) override { return createRow(peer); } std::unique_ptr<PeerListState> saveState() const override; void restoreState(std::unique_ptr<PeerListState> state) override; private: std::unique_ptr<PeerListRow> createRow(not_null<PeerData*> peer); struct SavedState : SavedStateBase { int32 preloadGroupId = 0; bool allLoaded = false; bool wasLoading = false; }; const not_null<Controller*> _controller; not_null<UserData*> _user; mtpRequestId _preloadRequestId = 0; bool _allLoaded = false; int32 _preloadGroupId = 0; }; ListController::ListController( not_null<Controller*> controller, not_null<UserData*> user) : PeerListController() , _controller(controller) , _user(user) { _controller->setSearchEnabledByContent(false); } std::unique_ptr<PeerListRow> ListController::createRow( not_null<PeerData*> peer) { auto result = std::make_unique<PeerListRow>(peer); result->setCustomStatus(QString()); return result; } void ListController::prepare() { setSearchNoResultsText(lang(lng_bot_groups_not_found)); delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled); delegate()->peerListSetTitle(langFactory(lng_profile_common_groups_section)); } void ListController::loadMoreRows() { if (_preloadRequestId || _allLoaded) { return; } _preloadRequestId = request(MTPmessages_GetCommonChats( _user->inputUser, MTP_int(_preloadGroupId), MTP_int(kCommonGroupsPerPage) )).done([this](const MTPmessages_Chats &result) { _preloadRequestId = 0; _preloadGroupId = 0; _allLoaded = true; if (auto chats = Api::getChatsFromMessagesChats(result)) { auto &list = chats->v; if (!list.empty()) { for_const (auto &chatData, list) { if (auto chat = App::feedChat(chatData)) { if (!chat->migrateTo()) { delegate()->peerListAppendRow( createRow(chat)); } _preloadGroupId = chat->bareId(); _allLoaded = false; } } delegate()->peerListRefreshRows(); } } auto fullCount = delegate()->peerListFullRowsCount(); if (fullCount > kCommonGroupsSearchAfter) { _controller->setSearchEnabledByContent(true); } }).send(); } std::unique_ptr<PeerListState> ListController::saveState() const { auto result = PeerListController::saveState(); auto my = std::make_unique<SavedState>(); my->preloadGroupId = _preloadGroupId; my->allLoaded = _allLoaded; my->wasLoading = (_preloadRequestId != 0); result->controllerState = std::move(my); return result; } void ListController::restoreState( std::unique_ptr<PeerListState> state) { auto typeErasedState = state ? state->controllerState.get() : nullptr; if (auto my = dynamic_cast<SavedState*>(typeErasedState)) { if (auto requestId = base::take(_preloadRequestId)) { request(requestId).cancel(); } _allLoaded = my->allLoaded; _preloadGroupId = my->preloadGroupId; if (my->wasLoading) { loadMoreRows(); } PeerListController::restoreState(std::move(state)); auto fullCount = delegate()->peerListFullRowsCount(); if (fullCount > kCommonGroupsSearchAfter) { _controller->setSearchEnabledByContent(true); } } } void ListController::rowClicked(not_null<PeerListRow*> row) { _controller->parentController()->showPeerHistory( row->peer(), Window::SectionShow::Way::Forward); } } // namespace InnerWidget::InnerWidget( QWidget *parent, not_null<Controller*> controller, not_null<UserData*> user) : RpWidget(parent) , _controller(controller) , _user(user) , _listController(std::make_unique<ListController>(controller, _user)) , _list(setupList(this, _listController.get())) { setContent(_list.data()); _listController->setDelegate(static_cast<PeerListDelegate*>(this)); _controller->searchFieldController()->queryValue( ) | rpl::start_with_next([this](QString &&query) { peerListScrollToTop(); content()->searchQueryChanged(std::move(query)); }, lifetime()); } void InnerWidget::visibleTopBottomUpdated( int visibleTop, int visibleBottom) { setChildVisibleTopBottom(_list, visibleTop, visibleBottom); } void InnerWidget::saveState(not_null<Memento*> memento) { memento->setListState(_listController->saveState()); } void InnerWidget::restoreState(not_null<Memento*> memento) { _listController->restoreState(memento->listState()); } rpl::producer<Ui::ScrollToRequest> InnerWidget::scrollToRequests() const { return _scrollToRequests.events(); } int InnerWidget::desiredHeight() const { auto desired = 0; auto count = qMax(_user->commonChatsCount(), 1); desired += qMax(count, _list->fullRowsCount()) * st::infoCommonGroupsList.item.height; return qMax(height(), desired); } object_ptr<InnerWidget::ListWidget> InnerWidget::setupList( RpWidget *parent, not_null<PeerListController*> controller) const { auto result = object_ptr<ListWidget>( parent, controller, st::infoCommonGroupsList); result->scrollToRequests( ) | rpl::start_with_next([this](Ui::ScrollToRequest request) { auto addmin = (request.ymin < 0) ? 0 : st::infoCommonGroupsMargin.top(); auto addmax = (request.ymax < 0) ? 0 : st::infoCommonGroupsMargin.top(); _scrollToRequests.fire({ request.ymin + addmin, request.ymax + addmax }); }, result->lifetime()); result->moveToLeft(0, st::infoCommonGroupsMargin.top()); parent->widthValue( ) | rpl::start_with_next([list = result.data()](int newWidth) { list->resizeToWidth(newWidth); }, result->lifetime()); result->heightValue( ) | rpl::start_with_next([parent](int listHeight) { auto newHeight = st::infoCommonGroupsMargin.top() + listHeight + st::infoCommonGroupsMargin.bottom(); parent->resize(parent->width(), newHeight); }, result->lifetime()); return result; } void InnerWidget::peerListSetTitle(base::lambda<QString()> title) { } void InnerWidget::peerListSetAdditionalTitle( base::lambda<QString()> title) { } bool InnerWidget::peerListIsRowSelected(not_null<PeerData*> peer) { return false; } int InnerWidget::peerListSelectedRowsCount() { return 0; } std::vector<not_null<PeerData*>> InnerWidget::peerListCollectSelectedRows() { return {}; } void InnerWidget::peerListScrollToTop() { _scrollToRequests.fire({ -1, -1 }); } void InnerWidget::peerListAddSelectedRowInBunch(not_null<PeerData*> peer) { Unexpected("Item selection in Info::Profile::Members."); } void InnerWidget::peerListFinishSelectedRowsBunch() { } void InnerWidget::peerListSetDescription( object_ptr<Ui::FlatLabel> description) { description.destroy(); } } // namespace CommonGroups } // namespace Info ��������������tdesktop-1.2.17/Telegram/SourceFiles/info/common_groups/info_common_groups_inner_widget.h�����������0000664�0000000�0000000�00000003657�13262451251�0032071�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/producer.h> #include "ui/rp_widget.h" #include "boxes/peer_list_box.h" namespace Info { class Controller; namespace CommonGroups { class Memento; class InnerWidget final : public Ui::RpWidget , private PeerListContentDelegate { public: InnerWidget( QWidget *parent, not_null<Controller*> controller, not_null<UserData*> user); not_null<UserData*> user() const { return _user; } rpl::producer<Ui::ScrollToRequest> scrollToRequests() const; int desiredHeight() const; void saveState(not_null<Memento*> memento); void restoreState(not_null<Memento*> memento); protected: void visibleTopBottomUpdated( int visibleTop, int visibleBottom) override; private: using ListWidget = PeerListContent; // PeerListContentDelegate interface. void peerListSetTitle(base::lambda<QString()> title) override; void peerListSetAdditionalTitle( base::lambda<QString()> title) override; bool peerListIsRowSelected(not_null<PeerData*> peer) override; int peerListSelectedRowsCount() override; std::vector<not_null<PeerData*>> peerListCollectSelectedRows() override; void peerListScrollToTop() override; void peerListAddSelectedRowInBunch( not_null<PeerData*> peer) override; void peerListFinishSelectedRowsBunch() override; void peerListSetDescription( object_ptr<Ui::FlatLabel> description) override; object_ptr<ListWidget> setupList( RpWidget *parent, not_null<PeerListController*> controller) const; not_null<Controller*> _controller; not_null<UserData*> _user; std::unique_ptr<PeerListController> _listController; object_ptr<ListWidget> _list; rpl::event_stream<Ui::ScrollToRequest> _scrollToRequests; }; } // namespace CommonGroups } // namespace Info ���������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/common_groups/info_common_groups_widget.cpp���������������0000664�0000000�0000000�00000004633�13262451251�0031224�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/common_groups/info_common_groups_widget.h" #include "info/common_groups/info_common_groups_inner_widget.h" #include "info/info_controller.h" #include "ui/search_field_controller.h" #include "ui/widgets/scroll_area.h" #include "styles/style_info.h" namespace Info { namespace CommonGroups { Section Memento::section() const { return Section(Section::Type::CommonGroups); } object_ptr<ContentWidget> Memento::createWidget( QWidget *parent, not_null<Controller*> controller, const QRect &geometry) { auto result = object_ptr<Widget>( parent, controller, App::user(userId())); result->setInternalState(geometry, this); return std::move(result); } void Memento::setListState(std::unique_ptr<PeerListState> state) { _listState = std::move(state); } std::unique_ptr<PeerListState> Memento::listState() { return std::move(_listState); } Memento::~Memento() = default; Widget::Widget( QWidget *parent, not_null<Controller*> controller, not_null<UserData*> user) : ContentWidget(parent, controller) { _inner = setInnerWidget(object_ptr<InnerWidget>( this, controller, user)); } not_null<UserData*> Widget::user() const { return _inner->user(); } bool Widget::showInternal(not_null<ContentMemento*> memento) { if (!controller()->validateMementoPeer(memento)) { return false; } if (auto groupsMemento = dynamic_cast<Memento*>(memento.get())) { if (groupsMemento->userId() == user()->bareId()) { restoreState(groupsMemento); return true; } } return false; } void Widget::setInternalState( const QRect &geometry, not_null<Memento*> memento) { setGeometry(geometry); Ui::SendPendingMoveResizeEvents(this); restoreState(memento); } std::unique_ptr<ContentMemento> Widget::doCreateMemento() { auto result = std::make_unique<Memento>(user()->bareId()); saveState(result.get()); return std::move(result); } void Widget::saveState(not_null<Memento*> memento) { memento->setScrollTop(scrollTopSave()); _inner->saveState(memento); } void Widget::restoreState(not_null<Memento*> memento) { _inner->restoreState(memento); auto scrollTop = memento->scrollTop(); scrollTopRestore(memento->scrollTop()); } } // namespace CommonGroups } // namespace Info �����������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/common_groups/info_common_groups_widget.h�����������������0000664�0000000�0000000�00000003055�13262451251�0030666�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/producer.h> #include "info/info_content_widget.h" struct PeerListState; namespace Ui { class SearchFieldController; } // namespace Ui namespace Info { namespace CommonGroups { class InnerWidget; class Memento final : public ContentMemento { public: Memento(UserId userId) : ContentMemento(peerFromUser(userId), 0) { } object_ptr<ContentWidget> createWidget( QWidget *parent, not_null<Controller*> controller, const QRect &geometry) override; Section section() const override; UserId userId() const { return peerToUser(peerId()); } void setListState(std::unique_ptr<PeerListState> state); std::unique_ptr<PeerListState> listState(); ~Memento(); private: std::unique_ptr<PeerListState> _listState; }; class Widget final : public ContentWidget { public: Widget( QWidget *parent, not_null<Controller*> controller, not_null<UserData*> user); not_null<UserData*> user() const; bool showInternal( not_null<ContentMemento*> memento) override; void setInternalState( const QRect &geometry, not_null<Memento*> memento); private: void saveState(not_null<Memento*> memento); void restoreState(not_null<Memento*> memento); std::unique_ptr<ContentMemento> doCreateMemento() override; InnerWidget *_inner = nullptr; }; } // namespace CommonGroups } // namespace Info �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/feed/�����������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0021261�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/feed/info_feed_channels.cpp�������������������������������0000664�0000000�0000000�00000021527�13262451251�0025565�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/feed/info_feed_channels.h" #include "info/feed/info_feed_channels_controllers.h" #include "info/profile/info_profile_icon.h" #include "info/profile/info_profile_button.h" #include "info/profile/info_profile_values.h" #include "info/channels/info_channels_widget.h" #include "info/info_controller.h" #include "info/info_memento.h" #include "ui/widgets/labels.h" #include "ui/widgets/buttons.h" #include "ui/widgets/scroll_area.h" #include "ui/wrap/padding_wrap.h" #include "ui/search_field_controller.h" #include "boxes/peer_list_controllers.h" #include "data/data_feed.h" #include "lang/lang_keys.h" #include "styles/style_boxes.h" #include "styles/style_info.h" namespace Info { namespace FeedProfile { namespace { constexpr auto kEnableSearchChannelsAfterCount = 20; } // namespace Channels::Channels( QWidget *parent, not_null<Controller*> controller) : RpWidget(parent) , _controller(controller) , _feed(_controller->key().feed()) , _listController(std::make_unique<ChannelsController>(_controller)) { setupHeader(); setupList(); setContent(_list.data()); _listController->setDelegate(static_cast<PeerListDelegate*>(this)); _controller->searchFieldController()->queryValue( ) | rpl::start_with_next([this](QString &&query) { peerListScrollToTop(); content()->searchQueryChanged(std::move(query)); }, lifetime()); Profile::FeedChannelsCountValue( _feed ) | rpl::start_with_next([this](int count) { const auto enabled = (count >= kEnableSearchChannelsAfterCount); _controller->setSearchEnabledByContent(enabled); }, lifetime()); } int Channels::desiredHeight() const { auto desired = _header ? _header->height() : 0; desired += st::infoChannelsList.item.height * std::max(int(_feed->channels().size()), _list->fullRowsCount()); return qMax(height(), desired); } rpl::producer<Ui::ScrollToRequest> Channels::scrollToRequests() const { return _scrollToRequests.events(); } std::unique_ptr<ChannelsState> Channels::saveState() { auto result = std::make_unique<ChannelsState>(); result->list = _listController->saveState(); return result; } void Channels::restoreState(std::unique_ptr<ChannelsState> state) { if (!state) { return; } _listController->restoreState(std::move(state->list)); } void Channels::setupHeader() { if (_controller->section().type() == Section::Type::Channels) { return; } _header = object_ptr<Ui::FixedHeightWidget>( this, st::infoMembersHeader); auto parent = _header.data(); _openChannels = Ui::CreateChild<Profile::Button>( parent, rpl::single(QString())); object_ptr<Profile::FloatingIcon>( parent, st::infoIconFeed, st::infoIconPosition); _titleWrap = Ui::CreateChild<Ui::RpWidget>(parent); _title = setupTitle(); _addChannel = Ui::CreateChild<Ui::IconButton>( _openChannels, st::infoChannelsAddChannel); _search = Ui::CreateChild<Ui::IconButton>( _openChannels, st::infoMembersSearch); setupButtons(); widthValue( ) | rpl::start_with_next([this](int width) { _header->resizeToWidth(width); }, _header->lifetime()); } object_ptr<Ui::FlatLabel> Channels::setupTitle() { auto result = object_ptr<Ui::FlatLabel>( _titleWrap, Profile::FeedChannelsCountValue( _feed ) | rpl::map([](int count) { return lng_feed_channels(lt_count, count); }) | Profile::ToUpperValue(), st::infoBlockHeaderLabel); result->setAttribute(Qt::WA_TransparentForMouseEvents); return result; } void Channels::setupButtons() { using namespace rpl::mappers; _openChannels->addClickHandler([this] { showChannelsWithSearch(false); }); _addChannel->addClickHandler([this] { // TODO throttle(ripple duration) this->addChannel(); }); auto searchShown = Profile::FeedChannelsCountValue(_feed) | rpl::map(_1 >= kEnableSearchChannelsAfterCount) | rpl::distinct_until_changed() | rpl::start_spawning(lifetime()); _search->showOn(rpl::duplicate(searchShown)); _search->addClickHandler([this] { // TODO throttle(ripple duration) this->showChannelsWithSearch(true); }); std::move( searchShown ) | rpl::start_with_next([this] { updateHeaderControlsGeometry(width()); }, lifetime()); } void Channels::setupList() { auto topSkip = _header ? _header->height() : 0; _list = object_ptr<ListWidget>( this, _listController.get(), st::infoChannelsList); _list->scrollToRequests( ) | rpl::start_with_next([this](Ui::ScrollToRequest request) { auto addmin = (request.ymin < 0 || !_header) ? 0 : _header->height(); auto addmax = (request.ymax < 0 || !_header) ? 0 : _header->height(); _scrollToRequests.fire({ request.ymin + addmin, request.ymax + addmax }); }, _list->lifetime()); widthValue( ) | rpl::start_with_next([this](int newWidth) { _list->resizeToWidth(newWidth); }, _list->lifetime()); _list->heightValue( ) | rpl::start_with_next([=](int listHeight) { auto newHeight = (listHeight > st::membersMarginBottom) ? (topSkip + listHeight + st::membersMarginBottom) : 0; resize(width(), newHeight); }, _list->lifetime()); _list->moveToLeft(0, topSkip); } int Channels::resizeGetHeight(int newWidth) { if (_header) { updateHeaderControlsGeometry(newWidth); } return heightNoMargins(); } //void Channels::updateSearchEnabledByContent() { // _controller->setSearchEnabledByContent( // peerListFullRowsCount() >= kEnableSearchMembersAfterCount); //} void Channels::updateHeaderControlsGeometry(int newWidth) { _openChannels->setGeometry(0, st::infoProfileSkip, newWidth, st::infoMembersHeader - st::infoProfileSkip - st::infoMembersHeaderPaddingBottom); auto availableWidth = newWidth - st::infoMembersButtonPosition.x(); //auto cancelLeft = availableWidth - _cancelSearch->width(); //_cancelSearch->moveToLeft( // cancelLeft, // st::infoMembersButtonPosition.y()); //auto searchShownLeft = st::infoIconPosition.x() // - st::infoMembersSearch.iconPosition.x(); //auto searchHiddenLeft = availableWidth - _search->width(); //auto searchShown = _searchShownAnimation.current(_searchShown ? 1. : 0.); //auto searchCurrentLeft = anim::interpolate( // searchHiddenLeft, // searchShownLeft, // searchShown); //_search->moveToLeft( // searchCurrentLeft, // st::infoMembersButtonPosition.y()); //if (!_search->isHidden()) { // availableWidth -= st::infoMembersSearch.width; //} _addChannel->moveToLeft( availableWidth - _addChannel->width(), st::infoMembersButtonPosition.y(), newWidth); if (!_addChannel->isHidden()) { availableWidth -= st::infoMembersSearch.width; } _search->moveToLeft( availableWidth - _search->width(), st::infoMembersButtonPosition.y(), newWidth); //auto fieldLeft = anim::interpolate( // cancelLeft, // st::infoBlockHeaderPosition.x(), // searchShown); //_searchField->setGeometryToLeft( // fieldLeft, // st::infoMembersSearchTop, // cancelLeft - fieldLeft, // _searchField->height()); //_titleWrap->resize( // searchCurrentLeft - st::infoBlockHeaderPosition.x(), // _title->height()); _titleWrap->resize( availableWidth - _addChannel->width() - st::infoBlockHeaderPosition.x(), _title->height()); _titleWrap->moveToLeft( st::infoBlockHeaderPosition.x(), st::infoBlockHeaderPosition.y(), newWidth); _titleWrap->setAttribute(Qt::WA_TransparentForMouseEvents); //_title->resizeToWidth(searchHiddenLeft); _title->resizeToWidth(_titleWrap->width()); _title->moveToLeft(0, 0); } void Channels::addChannel() { EditController::Start(_feed); } void Channels::showChannelsWithSearch(bool withSearch) { auto contentMemento = std::make_unique<Info::Channels::Memento>( _controller); contentMemento->setState(saveState()); contentMemento->setSearchStartsFocused(withSearch); auto mementoStack = std::vector<std::unique_ptr<ContentMemento>>(); mementoStack.push_back(std::move(contentMemento)); _controller->showSection( Info::Memento(std::move(mementoStack))); } void Channels::visibleTopBottomUpdated( int visibleTop, int visibleBottom) { setChildVisibleTopBottom(_list, visibleTop, visibleBottom); } void Channels::peerListSetTitle(base::lambda<QString()> title) { } void Channels::peerListSetAdditionalTitle( base::lambda<QString()> title) { } bool Channels::peerListIsRowSelected(not_null<PeerData*> peer) { return false; } int Channels::peerListSelectedRowsCount() { return 0; } std::vector<not_null<PeerData*>> Channels::peerListCollectSelectedRows() { return {}; } void Channels::peerListScrollToTop() { _scrollToRequests.fire({ -1, -1 }); } void Channels::peerListAddSelectedRowInBunch(not_null<PeerData*> peer) { Unexpected("Item selection in Info::Profile::Members."); } void Channels::peerListFinishSelectedRowsBunch() { } void Channels::peerListSetDescription( object_ptr<Ui::FlatLabel> description) { description.destroy(); } } // namespace FeedProfile } // namespace Info �������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/feed/info_feed_channels.h���������������������������������0000664�0000000�0000000�00000005040�13262451251�0025222�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/rp_widget.h" #include "boxes/peer_list_box.h" namespace Ui { class InputField; class CrossButton; class IconButton; class FlatLabel; struct ScrollToRequest; class AbstractButton; } // namespace Ui namespace Info { class Controller; enum class Wrap; namespace Profile { class Button; } // namespace Profile namespace FeedProfile { class Memento; struct ChannelsState { std::unique_ptr<PeerListState> list; base::optional<QString> search; }; class Channels : public Ui::RpWidget , private PeerListContentDelegate { public: Channels( QWidget *parent, not_null<Controller*> controller); rpl::producer<Ui::ScrollToRequest> scrollToRequests() const; std::unique_ptr<ChannelsState> saveState(); void restoreState(std::unique_ptr<ChannelsState> state); int desiredHeight() const; protected: void visibleTopBottomUpdated( int visibleTop, int visibleBottom) override; int resizeGetHeight(int newWidth) override; private: using ListWidget = PeerListContent; // PeerListContentDelegate interface. void peerListSetTitle(base::lambda<QString()> title) override; void peerListSetAdditionalTitle( base::lambda<QString()> title) override; bool peerListIsRowSelected(not_null<PeerData*> peer) override; int peerListSelectedRowsCount() override; std::vector<not_null<PeerData*>> peerListCollectSelectedRows() override; void peerListScrollToTop() override; void peerListAddSelectedRowInBunch( not_null<PeerData*> peer) override; void peerListFinishSelectedRowsBunch() override; void peerListSetDescription( object_ptr<Ui::FlatLabel> description) override; void setupHeader(); object_ptr<Ui::FlatLabel> setupTitle(); void setupList(); void setupButtons(); void addChannel(); void showChannelsWithSearch(bool withSearch); void updateHeaderControlsGeometry(int newWidth); not_null<Controller*> _controller; not_null<Data::Feed*> _feed; std::unique_ptr<PeerListController> _listController; object_ptr<Ui::RpWidget> _header = { nullptr }; object_ptr<ListWidget> _list = { nullptr }; Profile::Button *_openChannels = nullptr; Ui::RpWidget *_titleWrap = nullptr; Ui::FlatLabel *_title = nullptr; Ui::IconButton *_addChannel = nullptr; Ui::IconButton *_search = nullptr; rpl::event_stream<Ui::ScrollToRequest> _scrollToRequests; }; } // namespace FeedProfile } // namespace Info ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/feed/info_feed_channels_controllers.cpp�������������������0000664�0000000�0000000�00000031455�13262451251�0030214�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/feed/info_feed_channels_controllers.h" #include "data/data_feed.h" #include "data/data_session.h" #include "info/info_controller.h" #include "lang/lang_keys.h" #include "history/history.h" #include "window/window_peer_menu.h" #include "ui/widgets/popup_menu.h" #include "ui/toast/toast.h" #include "auth_session.h" #include "mainwidget.h" #include "apiwrap.h" #include "styles/style_widgets.h" #include "styles/style_info.h" #include "styles/style_boxes.h" namespace Info { namespace FeedProfile { namespace { constexpr auto kChannelsInFeedMin = 4; } // namespace class ChannelsController::Row final : public PeerListRow { public: Row(not_null<History*> history); QSize actionSize() const override; QMargins actionMargins() const override; void paintAction( Painter &p, TimeMs ms, int x, int y, int outerWidth, bool selected, bool actionSelected) override; not_null<History*> history() const { return _history; } private: not_null<History*> _history; }; ChannelsController::Row::Row(not_null<History*> history) : PeerListRow(history->peer) , _history(history) { } QSize ChannelsController::Row::actionSize() const { return QRect( QPoint(), st::smallCloseIcon.size()).marginsAdded( st::infoFeedLeaveIconMargins).size(); } QMargins ChannelsController::Row::actionMargins() const { return QMargins( 0, (st::infoCommonGroupsList.item.height - actionSize().height()) / 2, 0, 0); } void ChannelsController::Row::paintAction( Painter &p, TimeMs ms, int x, int y, int outerWidth, bool selected, bool actionSelected) { if (selected) { x += st::infoFeedLeaveIconMargins.left(); y += st::infoFeedLeaveIconMargins.top(); (actionSelected ? st::smallCloseIconOver : st::smallCloseIcon).paint(p, x, y, outerWidth); } } ChannelsController::ChannelsController(not_null<Controller*> controller) : PeerListController() , _controller(controller) , _feed(_controller->key().feed()) { if (!_feed->channelsLoaded()) { // Auth().api().requestFeedChannels(_feed); // #feed } _controller->setSearchEnabledByContent(false); } auto ChannelsController::createRow(not_null<History*> history) -> std::unique_ptr<Row> { auto result = std::make_unique<Row>(history); result->setCustomStatus(QString()); return result; } std::unique_ptr<PeerListRow> ChannelsController::createRestoredRow( not_null<PeerData*> peer) { return createRow(App::history(peer)); } void ChannelsController::prepare() { setSearchNoResultsText(lang(lng_feed_channels_not_found)); delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled); delegate()->peerListSetTitle(langFactory(lng_info_feed_channels)); rebuildRows(); using Flag = Data::FeedUpdateFlag; Auth().data().feedUpdated( ) | rpl::filter([=](const Data::FeedUpdate &update) { return (update.feed == _feed) && (update.flag == Flag::Channels); }) | rpl::filter([=] { return _feed->channelsLoaded(); }) | rpl::start_with_next([=] { rebuildRows(); }, lifetime()); } void ChannelsController::rebuildRows() { if (!_feed->channelsLoaded()) { return; } const auto &channels = _feed->channels(); auto count = delegate()->peerListFullRowsCount(); for (auto i = 0; i != count;) { const auto row = delegate()->peerListRowAt(i); const auto peer = row->peer(); if (ranges::find_if(channels, [=](not_null<History*> history) { return (history->peer == peer); }) != end(channels)) { ++i; } else { delegate()->peerListRemoveRow(row); --count; } } for (const auto history : channels) { if (auto row = createRow(history)) { delegate()->peerListAppendRow(std::move(row)); } } delegate()->peerListRefreshRows(); } std::unique_ptr<PeerListState> ChannelsController::saveState() const { auto result = PeerListController::saveState(); auto my = std::make_unique<SavedState>(); using Flag = Data::FeedUpdateFlag; // Must not capture `this` here, because it dies before my->lifetime. Auth().data().feedUpdated( ) | rpl::filter([feed = _feed](const Data::FeedUpdate &update) { return (update.feed == feed) && (update.flag == Flag::Channels); }) | rpl::start_with_next([state = result.get()] { state->controllerState = nullptr; }, my->lifetime); result->controllerState = std::move(my); return result; } void ChannelsController::restoreState( std::unique_ptr<PeerListState> state) { PeerListController::restoreState(std::move(state)); } void ChannelsController::rowClicked(not_null<PeerListRow*> row) { _controller->parentController()->showPeerHistory( row->peer(), Window::SectionShow::Way::Forward); } void ChannelsController::rowActionClicked(not_null<PeerListRow*> row) { Window::DeleteAndLeaveHandler(row->peer())(); } base::unique_qptr<Ui::PopupMenu> ChannelsController::rowContextMenu( not_null<PeerListRow*> row) { auto my = static_cast<Row*>(row.get()); auto channel = my->history()->peer->asChannel(); auto result = base::make_unique_q<Ui::PopupMenu>(nullptr); Window::PeerMenuAddMuteAction(channel, [&]( const QString &text, base::lambda<void()> handler) { return result->addAction(text, handler); }); //result->addAction( // #feed // lang(lng_feed_ungroup), // [=] { Window::ToggleChannelGrouping(channel, false); }); result->addAction( lang(lng_profile_leave_channel), Window::DeleteAndLeaveHandler(channel)); return result; } void NotificationsController::Start(not_null<Data::Feed*> feed) { const auto initBox = [=](not_null<PeerListBox*> box) { box->addButton(langFactory(lng_settings_save), [=] { const auto main = App::main(); const auto count = box->peerListFullRowsCount(); for (auto i = 0; i != count; ++i) { const auto row = box->peerListRowAt(i); const auto peer = row->peer(); const auto muted = !row->checked(); if (muted != peer->isMuted()) { main->updateNotifySettings( peer, (muted ? Data::NotifySettings::MuteChange::Mute : Data::NotifySettings::MuteChange::Unmute)); } } box->closeBox(); }); box->addButton(langFactory(lng_cancel), [box] { box->closeBox(); }); }; Ui::show(Box<PeerListBox>( std::make_unique<NotificationsController>(feed), initBox)); } NotificationsController::NotificationsController( not_null<Data::Feed*> feed) : _feed(feed) { } void NotificationsController::prepare() { setSearchNoResultsText(lang(lng_feed_channels_not_found)); delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled); delegate()->peerListSetTitle(langFactory(lng_feed_notifications)); loadMoreRows(); } void NotificationsController::loadMoreRows() { if (_preloadRequestId || _allLoaded) { return; } //_preloadRequestId = request(MTPmessages_GetDialogs( // #feed // MTP_flags(MTPmessages_GetDialogs::Flag::f_feed_id), // MTP_int(_feed->id()), // MTP_int(_preloadOffsetDate), // MTP_int(_preloadOffsetId), // _preloadPeer ? _preloadPeer->input : MTP_inputPeerEmpty(), // MTP_int(Data::Feed::kChannelsLimit) //)).done([=](const MTPmessages_Dialogs &result) { // applyFeedDialogs(result); // _preloadRequestId = 0; //}).fail([=](const RPCError &error) { // _preloadRequestId = 0; //}).send(); } void NotificationsController::applyFeedDialogs( const MTPmessages_Dialogs &result) { const auto [dialogsList, messagesList] = [&] { const auto process = [&](const auto &data) { App::feedUsers(data.vusers); App::feedChats(data.vchats); return std::make_tuple(&data.vdialogs.v, &data.vmessages.v); }; switch (result.type()) { case mtpc_messages_dialogs: _allLoaded = true; return process(result.c_messages_dialogs()); case mtpc_messages_dialogsSlice: LOG(("API Error: " "Unexpected dialogsSlice in feed dialogs list.")); return process(result.c_messages_dialogsSlice()); } Unexpected("Type in NotificationsController::applyFeedDialogs"); }(); App::feedMsgs(*messagesList, NewMessageLast); if (dialogsList->empty()) { _allLoaded = true; } auto channels = std::vector<not_null<ChannelData*>>(); channels.reserve(dialogsList->size()); for (const auto &dialog : *dialogsList) { switch (dialog.type()) { case mtpc_dialog: { if (const auto peerId = peerFromMTP(dialog.c_dialog().vpeer)) { if (peerIsChannel(peerId)) { const auto history = App::history(peerId); const auto channel = history->peer->asChannel(); history->applyDialog(dialog.c_dialog()); channels.push_back(channel); } else { LOG(("API Error: " "Unexpected non-channel in feed dialogs list.")); } } } break; //case mtpc_dialogFeed: { // #feed // LOG(("API Error: Unexpected dialogFeed in feed dialogs list.")); //} break; default: Unexpected("Type in DialogsInner::dialogsReceived"); } } if (!channels.empty()) { auto notMutedChannels = ranges::view::all( channels ) | ranges::view::filter([](not_null<ChannelData*> channel) { return !channel->isMuted(); }); delegate()->peerListAddSelectedRows(notMutedChannels); for (const auto channel : channels) { delegate()->peerListAppendRow(createRow(channel)); } } delegate()->peerListRefreshRows(); } void NotificationsController::rowClicked(not_null<PeerListRow*> row) { delegate()->peerListSetRowChecked(row, !row->checked()); } std::unique_ptr<PeerListRow> NotificationsController::createRow( not_null<ChannelData*> channel) { return std::make_unique<PeerListRow>(channel); } void EditController::Start( not_null<Data::Feed*> feed, ChannelData *channel) { const auto initBox = [=](not_null<PeerListBox*> box) { box->addButton(langFactory(lng_settings_save), [=] { auto channels = std::vector<not_null<ChannelData*>>(); const auto main = App::main(); const auto count = box->peerListFullRowsCount(); for (auto i = 0; i != count; ++i) { const auto row = box->peerListRowAt(i); if (row->checked()) { channels.push_back(row->peer()->asChannel()); } } if (channels.size() < kChannelsInFeedMin) { Ui::Toast::Show(lng_feed_select_more_channels( lt_count, kChannelsInFeedMin)); return; } box->closeBox(); //Auth().api().setFeedChannels(feed, channels); // #feed }); box->addButton(langFactory(lng_cancel), [box] { box->closeBox(); }); }; Ui::show(Box<PeerListBox>( std::make_unique<EditController>(feed, channel), initBox)); } EditController::EditController( not_null<Data::Feed*> feed, ChannelData *channel) : _feed(feed) { //, _startWithChannel(channel) { // #feed } void EditController::prepare() { setSearchNoResultsText(lang(lng_feed_channels_not_found)); delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled); delegate()->peerListSetTitle(langFactory( (_feed->channels().size() < kChannelsInFeedMin ? lng_feed_create_new : lng_feed_edit_title))); loadMoreRows(); } void EditController::loadMoreRows() { if (_preloadRequestId || _allLoaded) { return; } //const auto hash = 0; // #feed //_preloadRequestId = request(MTPchannels_GetFeedSources( // MTP_flags(0), // MTP_int(0), // MTP_int(hash) //)).done([=](const MTPchannels_FeedSources &result) { // applyFeedSources(result); // _preloadRequestId = 0; //}).fail([=](const RPCError &error) { // _preloadRequestId = 0; //}).send(); } // #feed //void EditController::applyFeedSources( // const MTPchannels_FeedSources &result) { // auto channels = std::vector<not_null<ChannelData*>>(); // // switch (result.type()) { // case mtpc_channels_feedSourcesNotModified: // LOG(("API Error: Unexpected channels.feedSourcesNotModified.")); // break; // // case mtpc_channels_feedSources: { // const auto &data = result.c_channels_feedSources(); // Auth().api().applyFeedSources(data); // // for (const auto &chat : data.vchats.v) { // if (chat.type() == mtpc_channel) { // channels.push_back(App::channel(chat.c_channel().vid.v)); // } // } // } break; // // default: Unexpected("Type in channels.getFeedSources response."); // } // // _allLoaded = true; // if (channels.size() < kChannelsInFeedMin) { // setDescriptionText(lng_feed_too_few_channels( // lt_count, // kChannelsInFeedMin)); // delegate()->peerListSetSearchMode(PeerListSearchMode::Disabled); // } else { // auto alreadyInFeed = ranges::view::all( // channels // ) | ranges::view::filter([&](not_null<ChannelData*> channel) { // return (channel->feed() == _feed) // || (channel == _startWithChannel); // }); // delegate()->peerListAddSelectedRows(alreadyInFeed); // for (const auto channel : channels) { // delegate()->peerListAppendRow(createRow(channel)); // } // } // delegate()->peerListRefreshRows(); //} void EditController::rowClicked(not_null<PeerListRow*> row) { delegate()->peerListSetRowChecked(row, !row->checked()); } std::unique_ptr<PeerListRow> EditController::createRow( not_null<ChannelData*> channel) { return std::make_unique<PeerListRow>(channel); } } // namespace FeedProfile } // namespace Info �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/feed/info_feed_channels_controllers.h���������������������0000664�0000000�0000000�00000005147�13262451251�0027660�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "boxes/peer_list_box.h" #include "mtproto/sender.h" namespace Data { class Feed; } // namespace Data namespace Info { class Controller; namespace FeedProfile { class ChannelsController : public PeerListController , private base::Subscriber { public: ChannelsController(not_null<Controller*> controller); void prepare() override; void rowClicked(not_null<PeerListRow*> row) override; void rowActionClicked(not_null<PeerListRow*> row) override; std::unique_ptr<PeerListRow> createRestoredRow( not_null<PeerData*> peer) override; base::unique_qptr<Ui::PopupMenu> rowContextMenu( not_null<PeerListRow*> row) override; std::unique_ptr<PeerListState> saveState() const override; void restoreState(std::unique_ptr<PeerListState> state) override; private: class Row; struct SavedState : SavedStateBase { rpl::lifetime lifetime; }; void rebuildRows(); std::unique_ptr<Row> createRow(not_null<History*> history); const not_null<Controller*> _controller; not_null<Data::Feed*> _feed; }; class NotificationsController : public PeerListController , private MTP::Sender { public: static void Start(not_null<Data::Feed*> feed); NotificationsController(not_null<Data::Feed*> feed); void prepare() override; void rowClicked(not_null<PeerListRow*> row) override; void loadMoreRows() override; private: std::unique_ptr<PeerListRow> createRow(not_null<ChannelData*> channel); void applyFeedDialogs(const MTPmessages_Dialogs &result); not_null<Data::Feed*> _feed; mtpRequestId _preloadRequestId = 0; //TimeId _preloadOffsetDate = TimeId(0); // #feed //MsgId _preloadOffsetId = MsgId(0); //PeerData *_preloadPeer = nullptr; bool _allLoaded = false; }; class EditController : public PeerListController , private MTP::Sender { public: static void Start( not_null<Data::Feed*> feed, ChannelData *channel = nullptr); EditController( not_null<Data::Feed*> feed, ChannelData *channel); void prepare() override; void rowClicked(not_null<PeerListRow*> row) override; void loadMoreRows() override; private: std::unique_ptr<PeerListRow> createRow(not_null<ChannelData*> channel); // void applyFeedSources(const MTPchannels_FeedSources &result); // #feed not_null<Data::Feed*> _feed; // ChannelData *_startWithChannel = nullptr; // #feed mtpRequestId _preloadRequestId = 0; bool _allLoaded = false; }; } // namespace FeedProfile } // namespace Info �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/feed/info_feed_channels_widget.cpp������������������������0000664�0000000�0000000�00000000442�13262451251�0027121�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/feed/info_feed_channels_widget.h" ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/feed/info_feed_channels_widget.h��������������������������0000664�0000000�0000000�00000000376�13262451251�0026574�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/feed/info_feed_cover.cpp����������������������������������0000664�0000000�0000000�00000005645�13262451251�0025113�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/feed/info_feed_cover.h" #include "data/data_feed.h" #include "data/data_session.h" #include "info/info_controller.h" #include "info/info_memento.h" #include "lang/lang_keys.h" #include "ui/widgets/labels.h" #include "ui/special_buttons.h" #include "auth_session.h" #include "styles/style_info.h" namespace Info { namespace FeedProfile { Cover::Cover( QWidget *parent, not_null<Controller*> controller) : FixedHeightWidget( parent, st::infoProfilePhotoTop + st::infoProfilePhoto.size.height() + st::infoProfilePhotoBottom) , _controller(controller) , _feed(_controller->key().feed()) , _userpic( this, controller->parentController(), _feed, st::infoFeedProfilePhoto) , _name(this, st::infoProfileNameLabel) , _status( this, st::infoProfileMegagroupStatusLabel) { _userpic->setPointerCursor(false); _name->setSelectable(true); _name->setContextCopyText(lang(lng_profile_copy_fullname)); refreshNameText(); refreshStatusText(); initViewers(); setupChildGeometry(); } void Cover::setupChildGeometry() { widthValue( ) | rpl::start_with_next([=](int newWidth) { _userpic->moveToLeft( st::infoProfilePhotoLeft, st::infoProfilePhotoTop, newWidth); refreshNameGeometry(newWidth); refreshStatusGeometry(newWidth); }, lifetime()); } void Cover::initViewers() { Auth().data().feedUpdated( ) | rpl::filter([](const Data::FeedUpdate &update) { return (update.flag == Data::FeedUpdateFlag::Channels); }) | rpl::start_with_next( [=] { refreshStatusText(); }, lifetime()); } void Cover::refreshNameText() { _name->setText(_feed->chatsListName()); refreshNameGeometry(width()); } void Cover::refreshStatusText() { const auto statusText = [&] { if (!_feed->channelsLoaded() || _feed->channels().empty()) { return QString(); } return lng_feed_channels(lt_count, _feed->channels().size()); }(); _status->setRichText(textcmdLink(1, statusText)); _status->setLink(1, std::make_shared<LambdaClickHandler>([=] { _controller->showSection(Info::Memento( _feed, Section::Type::Channels)); })); refreshStatusGeometry(width()); } void Cover::refreshNameGeometry(int newWidth) { auto nameLeft = st::infoProfileNameLeft; auto nameTop = st::infoProfileNameTop; auto nameWidth = newWidth - nameLeft - st::infoProfileNameRight; _name->resizeToNaturalWidth(nameWidth); _name->moveToLeft(nameLeft, nameTop, newWidth); } void Cover::refreshStatusGeometry(int newWidth) { auto statusWidth = newWidth - st::infoProfileStatusLeft - st::infoProfileStatusRight; _status->resizeToWidth(statusWidth); _status->moveToLeft( st::infoProfileStatusLeft, st::infoProfileStatusTop, newWidth); } Cover::~Cover() = default; } // namespace FeedProfile } // namespace Info �������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/feed/info_feed_cover.h������������������������������������0000664�0000000�0000000�00000002442�13262451251�0024550�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/wrap/padding_wrap.h" #include "ui/widgets/checkbox.h" #include "base/timer.h" namespace style { struct InfoToggle; } // namespace style namespace Data { class Feed; } // namespace Data namespace Ui { class FeedUserpicButton; class FlatLabel; template <typename Widget> class SlideWrap; } // namespace Ui namespace Info { class Controller; } // namespace Info namespace Info { namespace FeedProfile { class Cover : public Ui::FixedHeightWidget { public: Cover( QWidget *parent, not_null<Controller*> controller); ~Cover(); private: void setupChildGeometry(); void initViewers(); void refreshNameText(); void refreshStatusText(); void refreshNameGeometry(int newWidth); void refreshStatusGeometry(int newWidth); not_null<Controller*> _controller; not_null<Data::Feed*> _feed; object_ptr<Ui::FeedUserpicButton> _userpic; object_ptr<Ui::FlatLabel> _name = { nullptr }; object_ptr<Ui::FlatLabel> _status = { nullptr }; //object_ptr<CoverDropArea> _dropArea = { nullptr }; }; } // namespace FeedProfile } // namespace Info ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/feed/info_feed_profile_inner_widget.cpp�������������������0000664�0000000�0000000�00000005761�13262451251�0030172�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/feed/info_feed_profile_inner_widget.h" #include "info/info_controller.h" #include "info/feed/info_feed_profile_widget.h" #include "info/feed/info_feed_cover.h" #include "info/feed/info_feed_channels.h" #include "info/profile/info_profile_actions.h" #include "ui/widgets/scroll_area.h" #include "ui/wrap/vertical_layout.h" namespace Info { namespace FeedProfile { InnerWidget::InnerWidget( QWidget *parent, not_null<Controller*> controller) : RpWidget(parent) , _controller(controller) , _feed(_controller->key().feed()) , _content(setupContent(this)) { _content->heightValue( ) | rpl::start_with_next([this](int height) { if (!_inResize) { resizeToWidth(width()); updateDesiredHeight(); } }, lifetime()); } object_ptr<Ui::RpWidget> InnerWidget::setupContent( not_null<RpWidget*> parent) { auto result = object_ptr<Ui::VerticalLayout>(parent); _cover = result->add(object_ptr<Cover>( result, _controller)); auto details = Profile::SetupFeedDetails(_controller, parent, _feed); result->add(std::move(details)); result->add(object_ptr<BoxContentDivider>(result)); _channels = result->add(object_ptr<Channels>( result, _controller) ); _channels->scrollToRequests( ) | rpl::start_with_next([this](Ui::ScrollToRequest request) { auto min = (request.ymin < 0) ? request.ymin : mapFromGlobal(_channels->mapToGlobal({ 0, request.ymin })).y(); auto max = (request.ymin < 0) ? mapFromGlobal(_channels->mapToGlobal({ 0, 0 })).y() : (request.ymax < 0) ? request.ymax : mapFromGlobal(_channels->mapToGlobal({ 0, request.ymax })).y(); _scrollToRequests.fire({ min, max }); }, _channels->lifetime()); return std::move(result); } int InnerWidget::countDesiredHeight() const { return _content->height() + (_channels ? (_channels->desiredHeight() - _channels->height()) : 0); } void InnerWidget::visibleTopBottomUpdated( int visibleTop, int visibleBottom) { setChildVisibleTopBottom(_content, visibleTop, visibleBottom); } void InnerWidget::saveState(not_null<Memento*> memento) { if (_channels) { memento->setChannelsState(_channels->saveState()); } } void InnerWidget::restoreState(not_null<Memento*> memento) { if (_channels) { _channels->restoreState(memento->channelsState()); } } rpl::producer<Ui::ScrollToRequest> InnerWidget::scrollToRequests() const { return _scrollToRequests.events(); } rpl::producer<int> InnerWidget::desiredHeightValue() const { return _desiredHeight.events_starting_with(countDesiredHeight()); } int InnerWidget::resizeGetHeight(int newWidth) { _inResize = true; auto guard = gsl::finally([&] { _inResize = false; }); _content->resizeToWidth(newWidth); _content->moveToLeft(0, 0); updateDesiredHeight(); return _content->heightNoMargins(); } } // namespace FeedProfile } // namespace Info ���������������tdesktop-1.2.17/Telegram/SourceFiles/info/feed/info_feed_profile_inner_widget.h���������������������0000664�0000000�0000000�00000003361�13262451251�0027631�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/variable.h> #include "ui/rp_widget.h" namespace Window { class Controller; } // namespace Window namespace Ui { class VerticalLayout; template <typename Widget> class SlideWrap; struct ScrollToRequest; class MultiSlideTracker; } // namespace Ui namespace Info { enum class Wrap; class Controller; namespace FeedProfile { class Memento; class Channels; class Cover; class InnerWidget final : public Ui::RpWidget { public: InnerWidget( QWidget *parent, not_null<Controller*> controller); void saveState(not_null<Memento*> memento); void restoreState(not_null<Memento*> memento); void setIsStackBottom(bool isStackBottom) { _isStackBottom = isStackBottom; } rpl::producer<Ui::ScrollToRequest> scrollToRequests() const; rpl::producer<int> desiredHeightValue() const override; protected: int resizeGetHeight(int newWidth) override; void visibleTopBottomUpdated( int visibleTop, int visibleBottom) override; private: object_ptr<RpWidget> setupContent(not_null<RpWidget*> parent); int countDesiredHeight() const; void updateDesiredHeight() { _desiredHeight.fire(countDesiredHeight()); } rpl::variable<bool> _isStackBottom = true; const not_null<Controller*> _controller; const not_null<Data::Feed*> _feed; Channels *_channels = nullptr; Cover *_cover = nullptr; object_ptr<RpWidget> _content; bool _inResize = false; rpl::event_stream<Ui::ScrollToRequest> _scrollToRequests; rpl::event_stream<int> _desiredHeight; }; } // namespace FeedProfile } // namespace Info �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/feed/info_feed_profile_widget.cpp�������������������������0000664�0000000�0000000�00000005363�13262451251�0026775�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/feed/info_feed_profile_widget.h" #include "info/feed/info_feed_profile_inner_widget.h" #include "info/feed/info_feed_channels.h" #include "ui/widgets/scroll_area.h" #include "info/info_controller.h" namespace Info { namespace FeedProfile { Memento::Memento(not_null<Controller*> controller) : Memento(controller->feed()) { } Memento::Memento(not_null<Data::Feed*> feed) : ContentMemento(feed) { } Section Memento::section() const { return Section(Section::Type::Profile); } object_ptr<ContentWidget> Memento::createWidget( QWidget *parent, not_null<Controller*> controller, const QRect &geometry) { auto result = object_ptr<Widget>( parent, controller); result->setInternalState(geometry, this); return std::move(result); } void Memento::setChannelsState(std::unique_ptr<ChannelsState> state) { _channelsState = std::move(state); } std::unique_ptr<ChannelsState> Memento::channelsState() { return std::move(_channelsState); } Memento::~Memento() = default; Widget::Widget( QWidget *parent, not_null<Controller*> controller) : ContentWidget(parent, controller) { controller->setSearchEnabledByContent(false); _inner = setInnerWidget(object_ptr<InnerWidget>( this, controller)); _inner->move(0, 0); _inner->scrollToRequests( ) | rpl::start_with_next([this](Ui::ScrollToRequest request) { if (request.ymin < 0) { scrollTopRestore( qMin(scrollTopSave(), request.ymax)); } else { scrollTo(request); } }, lifetime()); } void Widget::setIsStackBottom(bool isStackBottom) { _inner->setIsStackBottom(isStackBottom); } void Widget::setInnerFocus() { _inner->setFocus(); } bool Widget::showInternal(not_null<ContentMemento*> memento) { if (!controller()->validateMementoPeer(memento)) { return false; } if (auto profileMemento = dynamic_cast<Memento*>(memento.get())) { restoreState(profileMemento); return true; } return false; } void Widget::setInternalState( const QRect &geometry, not_null<Memento*> memento) { setGeometry(geometry); Ui::SendPendingMoveResizeEvents(this); restoreState(memento); } std::unique_ptr<ContentMemento> Widget::doCreateMemento() { auto result = std::make_unique<Memento>(controller()); saveState(result.get()); return std::move(result); } void Widget::saveState(not_null<Memento*> memento) { memento->setScrollTop(scrollTopSave()); _inner->saveState(memento); } void Widget::restoreState(not_null<Memento*> memento) { _inner->restoreState(memento); scrollTopRestore(memento->scrollTop()); } } // namespace FeedProfile } // namespace Info �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/feed/info_feed_profile_widget.h���������������������������0000664�0000000�0000000�00000002733�13262451251�0026440�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/producer.h> #include "info/info_content_widget.h" namespace Info { namespace FeedProfile { class InnerWidget; struct ChannelsState; class Memento final : public ContentMemento { public: Memento(not_null<Controller*> controller); Memento(not_null<Data::Feed*> feed); object_ptr<ContentWidget> createWidget( QWidget *parent, not_null<Controller*> controller, const QRect &geometry) override; Section section() const override; void setChannelsState(std::unique_ptr<ChannelsState> state); std::unique_ptr<ChannelsState> channelsState(); ~Memento(); private: std::unique_ptr<ChannelsState> _channelsState; }; class Widget final : public ContentWidget { public: Widget( QWidget *parent, not_null<Controller*> controller); void setIsStackBottom(bool isStackBottom) override; bool showInternal( not_null<ContentMemento*> memento) override; void setInternalState( const QRect &geometry, not_null<Memento*> memento); void setInnerFocus() override; private: void saveState(not_null<Memento*> memento); void restoreState(not_null<Memento*> memento); std::unique_ptr<ContentMemento> doCreateMemento() override; InnerWidget *_inner = nullptr; }; } // namespace FeedProfile } // namespace Info �������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/info.style������������������������������������������������0000664�0000000�0000000�00000047431�13262451251�0022404�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ using "basic.style"; using "boxes/boxes.style"; using "ui/widgets/widgets.style"; InfoToggle { color: color; duration: int; size: pixels; skip: pixels; stroke: pixels; rippleAreaPadding: pixels; } infoMediaHeaderFg: windowFg; infoToggleCheckbox: Checkbox(defaultCheckbox) { margin: margins(0px, 0px, 0px, 0px); rippleBgActive: windowBgOver; checkPosition: point(16px, 8px); rippleAreaPosition: point(-8px, -8px); } infoToggle: InfoToggle { color: menuIconFg; duration: slideWrapDuration; size: 24px; skip: 5px; stroke: 2px; rippleAreaPadding: 8px; } infoScroll: ScrollArea(defaultScrollArea) { bottomsh: 0px; topsh: 0px; } infoMediaSearch: SearchFieldRow { height: 44px; padding: margins(8px, 6px, 8px, 6px); field: contactsSearchField; fieldIcon: icon {{ "box_search-flip_horizontal", menuIconFg, point(6px, 8px) }}; fieldIconSkip: 36px; fieldCancel: contactsSearchCancel; fieldCancelSkip: 40px; } infoLayerMediaSearch: SearchFieldRow(infoMediaSearch) { height: 46px; fieldIcon: icon {{ "box_search-flip_horizontal", menuIconFg, point(9px, 9px) }}; fieldIconSkip: 34px; fieldCancel: CrossButton(contactsSearchCancel) { width: 50px; cross: CrossAnimation { size: 38px; skip: 12px; stroke: 2px; minScale: 0.3; } crossPosition: point(3px, 4px); } fieldCancelSkip: 46px; } infoTopBarSearchRow: SearchFieldRow(infoLayerMediaSearch) { height: 52px; padding: margins(0px, 12px, 8px, 10px); fieldCancel: CrossButton(contactsSearchCancel) { width: 51px; height: 52px; cross: CrossAnimation { size: 42px; skip: 14px; stroke: 2px; minScale: 0.3; } crossPosition: point(1px, 6px); } } infoSlideDuration: 0; infoTopBarBackIcon: icon {{ "info_back", boxTitleCloseFg }}; infoTopBarBackIconOver: icon {{ "info_back", boxTitleCloseFgOver }}; infoTopBarHeight: 54px; infoTopBarBack: IconButton(defaultIconButton) { width: 70px; height: infoTopBarHeight; icon: infoTopBarBackIcon; iconOver: infoTopBarBackIconOver; iconPosition: point(11px, -1px); rippleAreaPosition: point(6px, 6px); rippleAreaSize: 42px; ripple: RippleAnimation(defaultRippleAnimation) { color: windowBgOver; } } infoTopBarTitle: FlatLabel(defaultFlatLabel) { textFg: windowBoldFg; maxHeight: 20px; style: TextStyle(defaultTextStyle) { font: font(14px semibold); linkFont: font(14px semibold); linkFontOver: font(14px semibold); } } infoTopBarMediaCancel: IconButton(infoTopBarBack) { width: infoTopBarHeight; icon: icon {{ "info_close", boxTitleCloseFg }}; iconOver: icon {{ "info_close", boxTitleCloseFgOver }}; } infoTopBarClose: IconButton(infoTopBarMediaCancel) { width: 48px; iconPosition: point(5px, -1px); rippleAreaPosition: point(0px, 6px); } infoTopBarSearch: IconButton(infoTopBarBack) { width: 56px; icon: icon {{ "top_bar_search", boxTitleCloseFg }}; iconOver: icon {{ "top_bar_search", boxTitleCloseFgOver }}; } infoTopBarMenu: IconButton(infoTopBarBack) { width: 48px; icon: icon {{ "title_menu_dots", boxTitleCloseFg }}; iconOver: icon {{ "title_menu_dots", boxTitleCloseFgOver }}; iconPosition: point(18px, -1px); rippleAreaPosition: point(1px, 6px); } infoTopBarNotifications: IconButton(infoTopBarMenu) { width: 42px; icon: icon {{ "info_notifications", boxTitleCloseFg }}; iconOver: icon {{ "info_notifications", boxTitleCloseFgOver }}; iconPosition: point(5px, 10px); rippleAreaPosition: point(0px, 6px); } infoNotificationsActive: icon {{ "info_notifications", windowBgActive }}; infoTopBarCall: IconButton(infoTopBarNotifications) { icon: icon {{ "top_bar_call", boxTitleCloseFg }}; iconOver: icon {{ "top_bar_call", boxTitleCloseFgOver }}; } infoTopBarForward: IconButton(infoTopBarBack) { width: 46px; icon: icon {{ "info_media_forward", boxTitleCloseFg }}; iconOver: icon {{ "info_media_forward", boxTitleCloseFgOver }}; iconPosition: point(6px, -1px); rippleAreaPosition: point(1px, 6px); } infoTopBarDelete: IconButton(infoTopBarForward) { icon: icon {{ "info_media_delete", boxTitleCloseFg }}; iconOver: icon {{ "info_media_delete", boxTitleCloseFgOver }}; } infoTopBar: InfoTopBar { height: infoTopBarHeight; back: infoTopBarBack; title: infoTopBarTitle; titlePosition: point(23px, 18px); bg: windowBg; mediaCancel: infoTopBarMediaCancel; mediaActionsSkip: 4px; mediaForward: infoTopBarForward; mediaDelete: infoTopBarDelete; search: infoTopBarSearch; searchRow: infoTopBarSearchRow; highlightBg: windowBgOver; highlightDuration: 240; } infoTopBarScale: 0.7; infoTopBarDuration: 150; infoLayerTopMinimal: 20px; infoLayerTopMaximal: 40px; infoLayerTopBarHeight: boxLayerTitleHeight; infoLayerTopBarBackIcon: icon {{ "info_back", boxTitleCloseFg }}; infoLayerTopBarBackIconOver: icon {{ "info_back", boxTitleCloseFgOver }}; infoLayerTopBarBack: IconButton(infoTopBarBack) { width: infoLayerTopBarHeight; height: infoLayerTopBarHeight; iconPosition: point(12px, -1px); icon: infoLayerTopBarBackIcon; iconOver: infoLayerTopBarBackIconOver; rippleAreaSize: 44px; } infoLayerTopBarMediaCancel: IconButton(infoLayerTopBarBack) { icon: icon {{ "info_close", boxTitleCloseFg }}; iconOver: icon {{ "info_close", boxTitleCloseFgOver }}; } infoLayerTopBarClose: IconButton(infoLayerTopBarMediaCancel) { width: 50px; iconPosition: point(6px, -1px); rippleAreaPosition: point(0px, 6px); } infoLayerTopBarMenu: IconButton(infoLayerTopBarClose) { width: 44px; icon: icon {{ "title_menu_dots", boxTitleCloseFg }}; iconOver: icon {{ "title_menu_dots", boxTitleCloseFgOver }}; iconPosition: point(18px, -1px); } infoLayerTopBarNotifications: IconButton(infoLayerTopBarMenu) { icon: icon {{ "info_notifications", boxTitleCloseFg }}; iconOver: icon {{ "info_notifications", boxTitleCloseFgOver }}; iconPosition: point(5px, 11px); } infoLayerTopBarCall: IconButton(infoLayerTopBarNotifications) { icon: icon {{ "top_bar_call", boxTitleCloseFg }}; iconOver: icon {{ "top_bar_call", boxTitleCloseFgOver }}; } infoLayerTopBarForward: IconButton(infoLayerTopBarBack) { width: 45px; icon: icon {{ "info_media_forward", boxTitleCloseFg }}; iconOver: icon {{ "info_media_forward", boxTitleCloseFgOver }}; iconPosition: point(6px, -1px); rippleAreaPosition: point(1px, 6px); } infoLayerTopBarDelete: IconButton(infoLayerTopBarForward) { icon: icon {{ "info_media_delete", boxTitleCloseFg }}; iconOver: icon {{ "info_media_delete", boxTitleCloseFgOver }}; } infoLayerTopBar: InfoTopBar(infoTopBar) { height: infoLayerTopBarHeight; back: infoLayerTopBarBack; title: boxTitle; titlePosition: boxLayerTitlePosition; bg: boxBg; mediaCancel: infoLayerTopBarMediaCancel; mediaActionsSkip: 6px; mediaForward: infoLayerTopBarForward; mediaDelete: infoLayerTopBarDelete; search: infoTopBarSearch; searchRow: infoTopBarSearchRow; } infoTopBarMenuPosition: point(-2px, 35px); infoLayerTopBarMenuPosition: point(40px, 37px); infoMinimalWidth: 324px; infoDesiredWidth: 392px; infoMinimalLayerMargin: 48px; infoTabs: SettingsSlider(defaultTabsSlider) { height: 55px; barTop: 52px; labelTop: 19px; } infoProfilePhotoInnerSize: 72px; infoProfilePhotoSize: size( infoProfilePhotoInnerSize, infoProfilePhotoInnerSize); infoProfilePhoto: UserpicButton(defaultUserpicButton) { size: infoProfilePhotoSize; photoSize: infoProfilePhotoInnerSize; } infoFeedProfilePhoto: FeedUserpicButton(defaultFeedUserpicButton) { size: infoProfilePhotoSize; innerSize: infoProfilePhotoInnerSize; innerPart: UserpicButton(defaultUserpicButton) { size: size(35px, 35px); photoSize: 35px; } } infoProfilePhotoLeft: 19px; infoProfilePhotoTop: 18px; infoProfilePhotoBottom: 18px; infoProfileStatusLeft: 109px; infoProfileStatusRight: 20px; infoProfileStatusTop: 58px; infoProfileStatusLabel: FlatLabel(defaultFlatLabel) { maxHeight: 18px; textFg: windowSubTextFg; style: TextStyle(defaultTextStyle) { font: normalFont; linkFont: normalFont; linkFontOver: normalFont; } } infoProfileMegagroupStatusLabel: FlatLabel(infoProfileStatusLabel) { style: defaultTextStyle; palette: TextPalette(defaultTextPalette) { linkFg: windowSubTextFg; } } infoProfileNameLeft: infoProfileStatusLeft; infoProfileNameRight: infoProfileStatusRight; infoProfileNameTop: 32px; infoProfileNameLabel: FlatLabel(infoProfileStatusLabel) { maxHeight: 24px; textFg: windowBoldFg; style: TextStyle(defaultTextStyle) { font: font(16px semibold); linkFont: font(16px semibold); linkFontOver: font(16px semibold underline); } } infoVerifiedCheckPosition: point(10px, 2px); infoVerifiedCheck: icon { { "profile_verified_star", profileVerifiedCheckBg, point(0px, 0px) }, { "profile_verified_check", profileVerifiedCheckFg, point(4px, 4px) } }; infoProfileSkip: 12px; infoProfileLabeledPadding: margins(79px, 9px, 30px, 7px); infoProfileSeparatorPadding: margins( 77px, infoProfileSkip, 0px, infoProfileSkip); infoIconFg: menuIconFg; infoIconInformation: icon {{ "info_information", infoIconFg }}; infoIconMembers: icon {{ "info_members", infoIconFg }}; infoIconNotifications: icon {{ "info_notifications", infoIconFg }}; infoIconActions: icon {{ "info_actions", infoIconFg }}; infoIconFeed: icon {{ "info_feed", infoIconFg }}; infoIconMediaPhoto: icon {{ "info_media_photo", infoIconFg }}; infoIconMediaVideo: icon {{ "info_media_video", infoIconFg }}; infoIconMediaFile: icon {{ "info_media_file", infoIconFg }}; infoIconMediaAudio: icon {{ "info_media_audio", infoIconFg }}; infoIconMediaLink: icon {{ "info_media_link", infoIconFg }}; infoIconMediaGroup: icon {{ "info_common_groups", infoIconFg }}; infoIconMediaVoice: icon {{ "info_media_voice", infoIconFg }}; infoIconMediaRound: icon {{ "info_media_round", infoIconFg }}; infoIconRecentActions: icon {{ "info_recent_actions", infoIconFg }}; infoIconAdministrators: icon {{ "info_administrators", infoIconFg }}; infoIconBlacklist: icon {{ "info_blacklist", infoIconFg }}; infoIconRestrictedUsers: icon {{ "info_restricted_users", infoIconFg }}; infoInformationIconPosition: point(25px, 12px); infoNotificationsIconPosition: point(20px, 5px); infoSharedMediaIconPosition: point(20px, 24px); infoSharedMediaButtonIconPosition: point(20px, 3px); infoIconPosition: point(20px, 15px); infoChannelMembersIconPosition: point(20px, 24px); infoLabeledOneLine: FlatLabel(defaultFlatLabel) { maxHeight: 20px; style: TextStyle(defaultTextStyle) { lineHeight: 19px; } } infoLabelSkip: 2px; infoLabel: FlatLabel(infoLabeledOneLine) { textFg: windowSubTextFg; } infoLabeled: FlatLabel(infoLabeledOneLine) { minWidth: 180px; maxHeight: 0px; margin: margins(5px, 5px, 5px, 5px); } infoBlockHeaderLabel: FlatLabel(infoProfileStatusLabel) { textFg: windowBoldFg; style: TextStyle(defaultTextStyle) { font: semiboldFont; linkFont: semiboldFont; linkFontOver: semiboldFont; } } infoBlockHeaderPosition: point(79px, 22px); infoProfileToggle: Toggle(defaultToggle) { diameter: 16px; width: 14px; untoggledFg: menuIconFg; } infoProfileToggleOver: Toggle(infoProfileToggle) { untoggledFg: menuIconFgOver; } infoProfileButton: InfoProfileButton { textFg: windowBoldFg; textFgOver: windowBoldFgOver; textBg: windowBg; textBgOver: windowBgOver; font: normalFont; height: 20px; padding: margins(79px, 10px, 8px, 8px); toggle: infoProfileToggle; toggleOver: infoProfileToggleOver; toggleSkip: 23px; ripple: defaultRippleAnimation; } infoNotificationsButton: InfoProfileButton(infoProfileButton) { padding: margins(79px, 13px, 8px, 9px); } infoMainButton: InfoProfileButton(infoProfileButton) { textFg: lightButtonFg; textFgOver: lightButtonFgOver; font: semiboldFont; } infoSharedMediaCoverHeight: 62px; infoSharedMediaButton: infoProfileButton; infoSharedMediaBottomSkip: 12px; infoBlockButton: InfoProfileButton(infoProfileButton) { textFg: attentionButtonFg; textFgOver: attentionButtonFgOver; } infoBlockButtonSkip: 8px; infoMembersHeader: 56px; infoMembersHeaderPaddingBottom: 6px; infoMembersList: PeerList(defaultPeerList) { item: PeerListItem(defaultPeerListItem) { photoPosition: point(18px, 6px); namePosition: point(79px, 11px); statusPosition: point(79px, 31px); } } infoMembersButtonPosition: point(12px, 0px); infoMembersButtonIconPosition: point(3px, 3px); infoMembersButton: IconButton(defaultIconButton) { width: 38px; height: 38px; iconPosition: infoMembersButtonIconPosition; rippleAreaPosition: point(0px, 0px); rippleAreaSize: 38px; ripple: defaultRippleAnimation; } infoMembersAddMember: IconButton(infoMembersButton) { icon: icon {{ "info_add_member", menuIconFg }}; iconOver: icon {{ "info_add_member", menuIconFgOver }}; } infoMembersSearch: IconButton(infoMembersButton) { icon: icon {{ "top_bar_search", menuIconFg, infoMembersButtonIconPosition }}; iconOver: icon {{ "top_bar_search", menuIconFgOver, infoMembersButtonIconPosition }}; iconPosition: point(0px, 0px); } infoMembersSearchActive: icon { { size(44px, 44px), windowBg }, { "top_bar_search", menuIconFgOver, infoMembersButtonIconPosition } }; infoMembersSearchActiveLayer: icon { { size(44px, 44px), boxBg }, { "top_bar_search", menuIconFgOver, infoMembersButtonIconPosition } }; infoMembersSearchField: InputField(contactsSearchField) { } infoMembersCancelSearch: CrossButton { width: 44px; height: 44px; cross: CrossAnimation { size: 44px; skip: 16px; stroke: 2px; minScale: 0.3; } crossFg: menuIconFg; crossFgOver: menuIconFgOver; crossPosition: point(0px, 0px); duration: 150; loadingPeriod: 1000; ripple: RippleAnimation(defaultRippleAnimation) { color: windowBgOver; } } infoMembersSearchTop: 15px; infoChannelsAddChannel: IconButton(infoMembersButton) { icon: icon {{ "settings_edit_name", menuIconFg, point(9px, 9px) }}; iconOver: icon {{ "settings_edit_name", menuIconFgOver, point(9px, 9px) }}; } infoMembersCreatorIcon: icon {{ "profile_admin_star", profileAdminStartFg, point(4px, 3px) }}; infoMembersCreatorIconOver: icon {{ "profile_admin_star", profileAdminStarFgOver, point(4px, 3px) }}; infoMembersAdminIcon: icon {{ "profile_admin_star", profileOtherAdminStarFg, point(4px, 3px) }}; infoMembersAdminIconOver: icon {{ "profile_admin_star", profileOtherAdminStarFgOver, point(4px, 3px) }}; infoMembersRemoveIcon: icon {{ "simple_close", menuIconFg }}; infoMembersRemoveIconOver: icon {{ "simple_close", menuIconFgOver }}; infoMembersAdminIconMarigns: margins(10px, 18px, 10px, 10px); infoMembersRemoveIconMargins: margins(10px, 12px, 12px, 10px); infoMediaHeaderStyle: TextStyle(semiboldTextStyle) { } infoMediaHeaderHeight: 28px; infoMediaHeaderPosition: point(14px, 6px); infoMediaSkip: 5px; infoMediaMargin: margins(0px, 6px, 0px, 2px); infoMediaMinGridSize: 90px; infoCommonGroupsMargin: margins(0px, 13px, 0px, 2px); infoCommonGroupsListItem: PeerListItem(defaultPeerListItem) { height: 52px; photoSize: 40px; photoPosition: point(16px, 6px); namePosition: point(71px, 15px); nameStyle: TextStyle(defaultTextStyle) { font: font(14px semibold); linkFont: font(14px semibold); linkFontOver: font(14px semibold); } statusPosition: point(79px, 31px); } infoCommonGroupsList: PeerList(infoMembersList) { item: infoCommonGroupsListItem; } infoChannelsList: PeerList(infoCommonGroupsList) { item: PeerListItem(infoCommonGroupsListItem) { photoPosition: point(18px, 6px); namePosition: point(79px, 15px); } } managePeerButton: InfoProfileButton(infoProfileButton) { padding: margins(76px, 12px, 76px, 10px); } managePeerButtonIconPosition: point(20px, 5px); managePeerButtonLabel: FlatLabel(defaultFlatLabel) { textFg: windowActiveTextFg; style: semiboldTextStyle; } managePeerButtonLabelPosition: point(25px, 10px); infoEmptyFg: windowSubTextFg; infoEmptyPhoto: icon {{ "info_media_photo_empty", infoEmptyFg }}; infoEmptyVideo: icon {{ "info_media_video_empty", infoEmptyFg }}; infoEmptyAudio: icon {{ "info_media_audio_empty", infoEmptyFg }}; infoEmptyFile: icon {{ "info_media_file_empty", infoEmptyFg }}; infoEmptyVoice: icon {{ "info_media_voice_empty", infoEmptyFg }}; infoEmptyLink: icon {{ "info_media_link_empty", infoEmptyFg }}; infoEmptyIconTop: 120px; infoEmptyLabelTop: 40px; infoEmptyLabelSkip: 20px; infoEmptyLabel: FlatLabel(defaultFlatLabel) { minWidth: 220px; textFg: windowSubTextFg; } editPeerDeleteButtonMargins: margins(23px, 16px, 23px, 16px); editPeerDeleteButton: sessionTerminateAllButton; editPeerPhotoMargins: margins(23px, 16px, 23px, 8px); editPeerTitle: defaultInputField; editPeerTitleMargins: margins(27px, 21px, 23px, 8px); editPeerDescription: newGroupDescription; editPeerDescriptionMargins: margins(23px, 5px, 23px, 16px); editPeerPrivaciesMargins: margins(23px, 10px, 23px, 0px); editPeerPrivacyTopSkip: 10px; editPeerPrivacyBottomSkip: 16px; editPeerPrivacyLabel: FlatLabel(defaultFlatLabel) { minWidth: 263px; textFg: windowSubTextFg; } editPeerPrivacyLabelMargins: margins(34px, 0px, 0px, 0px); editPeerSectionLabel: FlatLabel(boxTitle) { style: TextStyle(defaultTextStyle) { font: font(15px semibold); linkFont: font(15px semibold); linkFontOver: font(15px semibold underline); } } editPeerUsername: setupChannelLink; editPeerUsernameSkip: 8px; editPeerInviteLink: FlatLabel(defaultFlatLabel) { minWidth: 1px; // for break everywhere style: boxTextStyle; } editPeerInviteLinkButton: boxLinkButton; editPeerUsernameMargins: margins(0px, 10px, 0px, 13px); editPeerUsernameGood: FlatLabel(defaultFlatLabel) { textFg: boxTextFgGood; style: boxTextStyle; } editPeerUsernameError: FlatLabel(editPeerUsernameGood) { textFg: boxTextFgError; } editPeerUsernamePosition: point(0px, 10px); editPeerInviteLinkSkip: 10px; editPeerInviteLinkMargins: margins(23px, 10px, 14px, 16px); editPeerSignaturesMargins: margins(23px, 10px, 23px, 16px); editPeerInvitesMargins: margins(23px, 10px, 23px, 16px); editPeerInvitesTopSkip: 10px; editPeerInvitesSkip: 10px; historyTopBarBack: IconButton(infoTopBarBack) { width: 52px; } topBarHeight: 54px; topBarMenuPosition: point(-2px, 35px); topBarDuration: 200; topBarBackward: icon {{ "title_back", menuIconFg }}; topBarForwardAlpha: 0.6; topBarBack: icon {{ "title_back", lightButtonFg }}; topBarArrowPadding: margins(39px, 8px, 17px, 8px); topBarMinPadding: 5px; topBarButton: RoundButton(defaultLightButton) { width: -18px; padding: margins(0px, 10px, 12px, 10px); } topBarClearButton: RoundButton(defaultLightButton) { width: -18px; } topBarSearch: IconButton { width: 40px; height: topBarHeight; icon: icon {{ "top_bar_search", menuIconFg }}; iconOver: icon {{ "top_bar_search", menuIconFgOver }}; iconPosition: point(4px, 11px); rippleAreaPosition: point(0px, 7px); rippleAreaSize: 40px; ripple: RippleAnimation(defaultRippleAnimation) { color: windowBgOver; } } topBarSkip: -2px; topBarCallSkip: -1px; topBarMenuToggle: IconButton(topBarSearch) { width: 44px; icon: icon {{ "title_menu_dots", menuIconFg }}; iconOver: icon {{ "title_menu_dots", menuIconFgOver }}; iconPosition: point(16px, 17px); rippleAreaPosition: point(0px, 7px); } topBarCall: IconButton(topBarSearch) { icon: icon {{ "top_bar_call", menuIconFg }}; iconOver: icon {{ "top_bar_call", menuIconFgOver }}; } topBarInfo: IconButton(topBarSearch) { icon: icon {{ "top_bar_profile", menuIconFg }}; iconOver: icon {{ "top_bar_profile", menuIconFgOver }}; } topBarInfoActive: icon {{ "top_bar_profile", windowActiveTextFg }}; topBarActionSkip: 10px; topBarInfoButtonSize: size(52px, topBarHeight); topBarInfoButtonInnerSize: 42px; topBarInfoButtonInnerPosition: point(2px, -1px); topBarInfoButton: UserpicButton(defaultUserpicButton) { size: topBarInfoButtonSize; photoSize: topBarInfoButtonInnerSize; photoPosition: topBarInfoButtonInnerPosition; } topBarFeedInfoButton: FeedUserpicButton(defaultFeedUserpicButton) { size: topBarInfoButtonSize; innerSize: topBarInfoButtonInnerSize; innerPosition: topBarInfoButtonInnerPosition; innerPart: UserpicButton(defaultUserpicButton) { size: size(20px, 20px); photoSize: 20px; } } infoFeedLeaveIconMargins: margins(10px, 12px, 20px, 10px); ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/info_content_widget.cpp�����������������������������������0000664�0000000�0000000�00000014735�13262451251�0025124�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/info_content_widget.h" #include <rpl/never.h> #include <rpl/combine.h> #include <rpl/range.h> #include "window/window_controller.h" #include "ui/widgets/scroll_area.h" #include "ui/widgets/input_fields.h" #include "ui/wrap/padding_wrap.h" #include "ui/search_field_controller.h" #include "lang/lang_keys.h" #include "info/profile/info_profile_widget.h" #include "info/media/info_media_widget.h" #include "info/common_groups/info_common_groups_widget.h" #include "info/info_layer_widget.h" #include "info/info_section_widget.h" #include "info/info_controller.h" #include "boxes/peer_list_box.h" #include "styles/style_info.h" #include "styles/style_profile.h" namespace Info { ContentWidget::ContentWidget( QWidget *parent, not_null<Controller*> controller) : RpWidget(parent) , _controller(controller) , _scroll(this, st::infoScroll) { using namespace rpl::mappers; setAttribute(Qt::WA_OpaquePaintEvent); _controller->wrapValue( ) | rpl::start_with_next([this](Wrap value) { if (value != Wrap::Layer) { applyAdditionalScroll(0); } _bg = (value == Wrap::Layer) ? st::boxBg : st::profileBg; update(); }, lifetime()); if (_controller->section().type() != Section::Type::Profile) { rpl::combine( _controller->wrapValue(), _controller->searchEnabledByContent(), (_1 == Wrap::Layer) && _2 ) | rpl::distinct_until_changed( ) | rpl::start_with_next([this](bool shown) { refreshSearchField(shown); }, lifetime()); } _scrollTopSkip.changes( ) | rpl::start_with_next([this] { updateControlsGeometry(); }, lifetime()); } void ContentWidget::resizeEvent(QResizeEvent *e) { updateControlsGeometry(); } void ContentWidget::updateControlsGeometry() { if (!_innerWrap) { return; } auto newScrollTop = _scroll->scrollTop() + _topDelta; auto scrollGeometry = rect().marginsRemoved( QMargins(0, _scrollTopSkip.current(), 0, 0)); if (_scroll->geometry() != scrollGeometry) { _scroll->setGeometry(scrollGeometry); _innerWrap->resizeToWidth(_scroll->width()); } if (!_scroll->isHidden()) { if (_topDelta) { _scroll->scrollToY(newScrollTop); } auto scrollTop = _scroll->scrollTop(); _innerWrap->setVisibleTopBottom( scrollTop, scrollTop + _scroll->height()); } } std::unique_ptr<ContentMemento> ContentWidget::createMemento() { auto result = doCreateMemento(); _controller->saveSearchState(result.get()); return result; } void ContentWidget::paintEvent(QPaintEvent *e) { Painter p(this); p.fillRect(e->rect(), _bg); } void ContentWidget::setGeometryWithTopMoved( const QRect &newGeometry, int topDelta) { _topDelta = topDelta; auto willBeResized = (size() != newGeometry.size()); if (geometry() != newGeometry) { setGeometry(newGeometry); } if (!willBeResized) { QResizeEvent fake(size(), size()); QApplication::sendEvent(this, &fake); } _topDelta = 0; } Ui::RpWidget *ContentWidget::doSetInnerWidget( object_ptr<RpWidget> inner) { using namespace rpl::mappers; _innerWrap = _scroll->setOwnedWidget( object_ptr<Ui::PaddingWrap<Ui::RpWidget>>( this, std::move(inner), _innerWrap ? _innerWrap->padding() : style::margins())); _innerWrap->move(0, 0); rpl::combine( _scroll->scrollTopValue(), _scroll->heightValue(), _innerWrap->entity()->desiredHeightValue(), tuple(_1, _1 + _2, _3) ) | rpl::start_with_next([this]( int top, int bottom, int desired) { _innerDesiredHeight = desired; _innerWrap->setVisibleTopBottom(top, bottom); _scrollTillBottomChanges.fire_copy(std::max(desired - bottom, 0)); }, _innerWrap->lifetime()); return _innerWrap->entity(); } int ContentWidget::scrollTillBottom(int forHeight) const { auto scrollHeight = forHeight - _scrollTopSkip.current(); auto scrollBottom = _scroll->scrollTop() + scrollHeight; auto desired = _innerDesiredHeight; return std::max(desired - scrollBottom, 0); } rpl::producer<int> ContentWidget::scrollTillBottomChanges() const { return _scrollTillBottomChanges.events(); } void ContentWidget::setScrollTopSkip(int scrollTopSkip) { _scrollTopSkip = scrollTopSkip; } rpl::producer<Section> ContentWidget::sectionRequest() const { return rpl::never<Section>(); } rpl::producer<int> ContentWidget::scrollHeightValue() const { return _scroll->heightValue(); } void ContentWidget::applyAdditionalScroll(int additionalScroll) { if (_innerWrap) { _innerWrap->setPadding({ 0, 0, 0, additionalScroll }); } } rpl::producer<int> ContentWidget::desiredHeightValue() const { using namespace rpl::mappers; return rpl::combine( _innerWrap->entity()->desiredHeightValue(), _scrollTopSkip.value() ) | rpl::map(_1 + _2); } rpl::producer<bool> ContentWidget::desiredShadowVisibility() const { using namespace rpl::mappers; return rpl::combine( _scroll->scrollTopValue(), _scrollTopSkip.value() ) | rpl::map((_1 > 0) || (_2 > 0)); } bool ContentWidget::hasTopBarShadow() const { return (_scroll->scrollTop() > 0); } void ContentWidget::setInnerFocus() { if (_searchField) { _searchField->setFocus(); } else { _innerWrap->entity()->setFocus(); } } int ContentWidget::scrollTopSave() const { return _scroll->scrollTop(); } void ContentWidget::scrollTopRestore(int scrollTop) { _scroll->scrollToY(scrollTop); } void ContentWidget::scrollTo(const Ui::ScrollToRequest &request) { _scroll->scrollTo(request); } bool ContentWidget::wheelEventFromFloatPlayer(QEvent *e) { return _scroll->viewportEvent(e); } QRect ContentWidget::rectForFloatPlayer() const { return mapToGlobal(_scroll->geometry()); } rpl::producer<SelectedItems> ContentWidget::selectedListValue() const { return rpl::single(SelectedItems(Storage::SharedMediaType::Photo)); } void ContentWidget::refreshSearchField(bool shown) { auto search = _controller->searchFieldController(); if (search && shown) { auto rowView = search->createRowView( this, st::infoLayerMediaSearch); _searchWrap = std::move(rowView.wrap); _searchField = rowView.field; const auto view = _searchWrap.get(); widthValue( ) | rpl::start_with_next([=](int newWidth) { view->resizeToWidth(newWidth); view->moveToLeft(0, 0); }, view->lifetime()); view->show(); _searchField->setFocus(); setScrollTopSkip(view->heightNoMargins() - st::lineWidth); } else { if (Ui::InFocusChain(this)) { setFocus(); } _searchWrap = nullptr; setScrollTopSkip(0); } } } // namespace Info �����������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/info_content_widget.h�������������������������������������0000664�0000000�0000000�00000010546�13262451251�0024565�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/variable.h> #include "ui/rp_widget.h" #include "info/info_wrap_widget.h" namespace Storage { enum class SharedMediaType : signed char; } // namespace Storage namespace Ui { class ScrollArea; class InputField; struct ScrollToRequest; template <typename Widget> class PaddingWrap; } // namespace Ui namespace Data { class Feed; } // namespace Data namespace Info { class ContentMemento; class Controller; class ContentWidget : public Ui::RpWidget { public: ContentWidget( QWidget *parent, not_null<Controller*> controller); virtual bool showInternal( not_null<ContentMemento*> memento) = 0; std::unique_ptr<ContentMemento> createMemento(); virtual rpl::producer<Section> sectionRequest() const; virtual void setIsStackBottom(bool isStackBottom) { } rpl::producer<int> scrollHeightValue() const; rpl::producer<int> desiredHeightValue() const override; rpl::producer<bool> desiredShadowVisibility() const; bool hasTopBarShadow() const; virtual void setInnerFocus(); // When resizing the widget with top edge moved up or down and we // want to add this top movement to the scroll position, so inner // content will not move. void setGeometryWithTopMoved( const QRect &newGeometry, int topDelta); void applyAdditionalScroll(int additionalScroll); int scrollTillBottom(int forHeight) const; rpl::producer<int> scrollTillBottomChanges() const; // Float player interface. bool wheelEventFromFloatPlayer(QEvent *e); QRect rectForFloatPlayer() const; virtual rpl::producer<SelectedItems> selectedListValue() const; virtual void cancelSelection() { } protected: template <typename Widget> Widget *setInnerWidget(object_ptr<Widget> inner) { return static_cast<Widget*>( doSetInnerWidget(std::move(inner))); } not_null<Controller*> controller() const { return _controller; } void resizeEvent(QResizeEvent *e) override; void paintEvent(QPaintEvent *e) override; void setScrollTopSkip(int scrollTopSkip); int scrollTopSave() const; void scrollTopRestore(int scrollTop); void scrollTo(const Ui::ScrollToRequest &request); private: RpWidget *doSetInnerWidget(object_ptr<RpWidget> inner); void updateControlsGeometry(); void refreshSearchField(bool shown); virtual std::unique_ptr<ContentMemento> doCreateMemento() = 0; const not_null<Controller*> _controller; style::color _bg; rpl::variable<int> _scrollTopSkip = -1; rpl::event_stream<int> _scrollTillBottomChanges; object_ptr<Ui::ScrollArea> _scroll; Ui::PaddingWrap<Ui::RpWidget> *_innerWrap = nullptr; base::unique_qptr<Ui::RpWidget> _searchWrap = nullptr; QPointer<Ui::InputField> _searchField; int _innerDesiredHeight = 0; // Saving here topDelta in setGeometryWithTopMoved() to get it passed to resizeEvent(). int _topDelta = 0; }; class ContentMemento { public: ContentMemento(PeerId peerId, PeerId migratedPeerId) : _peerId(peerId) , _migratedPeerId(migratedPeerId) { } explicit ContentMemento(not_null<Data::Feed*> feed) : _feed(feed) { } virtual object_ptr<ContentWidget> createWidget( QWidget *parent, not_null<Controller*> controller, const QRect &geometry) = 0; PeerId peerId() const { return _peerId; } PeerId migratedPeerId() const { return _migratedPeerId; } Data::Feed *feed() const { return _feed; } virtual Section section() const = 0; virtual ~ContentMemento() = default; void setScrollTop(int scrollTop) { _scrollTop = scrollTop; } int scrollTop() const { return _scrollTop; } void setSearchFieldQuery(const QString &query) { _searchFieldQuery = query; } QString searchFieldQuery() const { return _searchFieldQuery; } void setSearchEnabledByContent(bool enabled) { _searchEnabledByContent = enabled; } bool searchEnabledByContent() const { return _searchEnabledByContent; } void setSearchStartsFocused(bool focused) { _searchStartsFocused = focused; } bool searchStartsFocused() const { return _searchStartsFocused; } private: const PeerId _peerId = 0; const PeerId _migratedPeerId = 0; Data::Feed * const _feed = nullptr; int _scrollTop = 0; QString _searchFieldQuery; bool _searchEnabledByContent = false; bool _searchStartsFocused = false; }; } // namespace Info ����������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/info_controller.cpp���������������������������������������0000664�0000000�0000000�00000015531�13262451251�0024265�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/info_controller.h" #include <rpl/range.h> #include <rpl/then.h> #include "ui/search_field_controller.h" #include "data/data_shared_media.h" #include "info/info_content_widget.h" #include "info/info_memento.h" #include "info/media/info_media_widget.h" #include "observer_peer.h" #include "window/window_controller.h" namespace Info { namespace { not_null<PeerData*> CorrectPeer(PeerId peerId) { Expects(peerId != 0); auto result = App::peer(peerId); if (auto to = result->migrateTo()) { return to; } return result; } } // namespace Key::Key(not_null<PeerData*> peer) : _value(peer) { } Key::Key(not_null<Data::Feed*> feed) : _value(feed) { } PeerData *Key::peer() const { if (const auto peer = base::get_if<not_null<PeerData*>>(&_value)) { return *peer; } return nullptr; } Data::Feed *Key::feed() const { if (const auto feed = base::get_if<not_null<Data::Feed*>>(&_value)) { return *feed; } return nullptr; } rpl::producer<SparseIdsMergedSlice> AbstractController::mediaSource( SparseIdsMergedSlice::UniversalMsgId aroundId, int limitBefore, int limitAfter) const { return SharedMediaMergedViewer( SharedMediaMergedKey( SparseIdsMergedSlice::Key( peerId(), migratedPeerId(), aroundId), section().mediaType()), limitBefore, limitAfter); } rpl::producer<QString> AbstractController::mediaSourceQueryValue() const { return rpl::single(QString()); } void AbstractController::showSection( Window::SectionMemento &&memento, const Window::SectionShow ¶ms) { return parentController()->showSection(std::move(memento), params); } void AbstractController::showBackFromStack( const Window::SectionShow ¶ms) { return parentController()->showBackFromStack(params); } Controller::Controller( not_null<WrapWidget*> widget, not_null<Window::Controller*> window, not_null<ContentMemento*> memento) : AbstractController(window) , _widget(widget) , _key(memento->peerId() ? Key(App::peer(memento->peerId())) : Key(memento->feed())) , _migrated(memento->migratedPeerId() ? App::peer(memento->migratedPeerId()) : nullptr) , _section(memento->section()) { updateSearchControllers(memento); setupMigrationViewer(); } void Controller::setupMigrationViewer() { const auto peer = _key.peer(); if (!peer || (!peer->isChat() && !peer->isChannel()) || _migrated) { return; } Notify::PeerUpdateValue( peer, Notify::PeerUpdate::Flag::MigrationChanged ) | rpl::start_with_next([=] { if (peer->migrateTo() || (peer->migrateFrom() != _migrated)) { const auto window = parentController(); const auto section = _section; InvokeQueued(_widget, [=] { window->showSection( Memento(peer->id, section), Window::SectionShow( Window::SectionShow::Way::Backward, anim::type::instant, anim::activation::background)); }); } }, lifetime()); } Wrap Controller::wrap() const { return _widget->wrap(); } rpl::producer<Wrap> Controller::wrapValue() const { return _widget->wrapValue(); } bool Controller::validateMementoPeer( not_null<ContentMemento*> memento) const { return memento->peerId() == peerId() && memento->migratedPeerId() == migratedPeerId() && memento->feed() == feed(); } void Controller::setSection(not_null<ContentMemento*> memento) { _section = memento->section(); updateSearchControllers(memento); } void Controller::updateSearchControllers( not_null<ContentMemento*> memento) { using Type = Section::Type; auto type = _section.type(); auto isMedia = (type == Type::Media); auto mediaType = isMedia ? _section.mediaType() : Section::MediaType::kCount; auto hasMediaSearch = isMedia && SharedMediaAllowSearch(mediaType); auto hasCommonGroupsSearch = (type == Type::CommonGroups); auto hasMembersSearch = (type == Type::Members || type == Type::Profile || type == Type::Channels); auto searchQuery = memento->searchFieldQuery(); if (isMedia) { _searchController = std::make_unique<Api::DelayedSearchController>(); auto mediaMemento = dynamic_cast<Media::Memento*>(memento.get()); Assert(mediaMemento != nullptr); _searchController->restoreState( mediaMemento->searchState()); } else { _searchController = nullptr; } if (hasMediaSearch || hasCommonGroupsSearch || hasMembersSearch) { _searchFieldController = std::make_unique<Ui::SearchFieldController>( searchQuery); if (_searchController) { _searchFieldController->queryValue( ) | rpl::start_with_next([=](QString &&query) { _searchController->setQuery( produceSearchQuery(std::move(query))); }, _searchFieldController->lifetime()); } _seachEnabledByContent = memento->searchEnabledByContent(); _searchStartsFocused = memento->searchStartsFocused(); } else { _searchFieldController = nullptr; } } void Controller::saveSearchState(not_null<ContentMemento*> memento) { if (_searchFieldController) { memento->setSearchFieldQuery( _searchFieldController->query()); memento->setSearchEnabledByContent( _seachEnabledByContent.current()); } if (_searchController) { auto mediaMemento = dynamic_cast<Media::Memento*>( memento.get()); Assert(mediaMemento != nullptr); mediaMemento->setSearchState(_searchController->saveState()); } } void Controller::showSection( Window::SectionMemento &&memento, const Window::SectionShow ¶ms) { if (!_widget->showInternal(&memento, params)) { AbstractController::showSection(std::move(memento), params); } } void Controller::showBackFromStack(const Window::SectionShow ¶ms) { if (!_widget->showBackFromStackInternal(params)) { AbstractController::showBackFromStack(params); } } auto Controller::produceSearchQuery( const QString &query) const -> SearchQuery { Expects(_key.peer() != nullptr); auto result = SearchQuery(); result.type = _section.mediaType(); result.peerId = _key.peer()->id; result.query = query; result.migratedPeerId = _migrated ? _migrated->id : PeerId(0); return result; } rpl::producer<bool> Controller::searchEnabledByContent() const { return _seachEnabledByContent.value(); } rpl::producer<QString> Controller::mediaSourceQueryValue() const { return _searchController->currentQueryValue(); } rpl::producer<SparseIdsMergedSlice> Controller::mediaSource( SparseIdsMergedSlice::UniversalMsgId aroundId, int limitBefore, int limitAfter) const { auto query = _searchController->currentQuery(); if (!query.query.isEmpty()) { return _searchController->idsSlice( aroundId, limitBefore, limitAfter); } return SharedMediaMergedViewer( SharedMediaMergedKey( SparseIdsMergedSlice::Key( query.peerId, query.migratedPeerId, aroundId), query.type), limitBefore, limitAfter); } Controller::~Controller() = default; } // namespace Info �����������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/info_controller.h�����������������������������������������0000664�0000000�0000000�00000010560�13262451251�0023727�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/variable.h> #include "data/data_search_controller.h" #include "window/window_controller.h" namespace Ui { class SearchFieldController; } // namespace Ui namespace Info { class Key { public: Key(not_null<PeerData*> peer); Key(not_null<Data::Feed*> feed); PeerData *peer() const; Data::Feed *feed() const; private: base::variant<not_null<PeerData*>, not_null<Data::Feed*>> _value; }; enum class Wrap; class WrapWidget; class Memento; class ContentMemento; class Section final { public: enum class Type { Profile, Media, CommonGroups, Members, Channels, }; using MediaType = Storage::SharedMediaType; Section(Type type) : _type(type) { Expects(type != Type::Media); } Section(MediaType mediaType) : _type(Type::Media) , _mediaType(mediaType) { } Type type() const { return _type; } MediaType mediaType() const { Expects(_type == Type::Media); return _mediaType; } private: Type _type; Storage::SharedMediaType _mediaType; }; class AbstractController : public Window::Navigation { public: AbstractController(not_null<Window::Controller*> parent) : _parent(parent) { } virtual Key key() const = 0; virtual PeerData *migrated() const = 0; virtual Section section() const = 0; PeerId peerId() const { if (const auto peer = key().peer()) { return peer->id; } return PeerId(0); } PeerId migratedPeerId() const { if (auto peer = migrated()) { return peer->id; } return PeerId(0); } Data::Feed *feed() const { return key().feed(); } virtual void setSearchEnabledByContent(bool enabled) { } virtual rpl::producer<SparseIdsMergedSlice> mediaSource( SparseIdsMergedSlice::UniversalMsgId aroundId, int limitBefore, int limitAfter) const; virtual rpl::producer<QString> mediaSourceQueryValue() const; void showSection( Window::SectionMemento &&memento, const Window::SectionShow ¶ms = Window::SectionShow()) override; void showBackFromStack( const Window::SectionShow ¶ms = Window::SectionShow()) override; not_null<Window::Controller*> parentController() override { return _parent; } private: not_null<Window::Controller*> _parent; }; class Controller : public AbstractController { public: Controller( not_null<WrapWidget*> widget, not_null<Window::Controller*> window, not_null<ContentMemento*> memento); Key key() const override { return _key; } PeerData *migrated() const override { return _migrated; } Section section() const override { return _section; } bool validateMementoPeer( not_null<ContentMemento*> memento) const; Wrap wrap() const; rpl::producer<Wrap> wrapValue() const; void setSection(not_null<ContentMemento*> memento); Ui::SearchFieldController *searchFieldController() const { return _searchFieldController.get(); } void setSearchEnabledByContent(bool enabled) override { _seachEnabledByContent = enabled; } rpl::producer<bool> searchEnabledByContent() const; rpl::producer<SparseIdsMergedSlice> mediaSource( SparseIdsMergedSlice::UniversalMsgId aroundId, int limitBefore, int limitAfter) const override; rpl::producer<QString> mediaSourceQueryValue() const override; bool takeSearchStartsFocused() { return base::take(_searchStartsFocused); } void saveSearchState(not_null<ContentMemento*> memento); void showSection( Window::SectionMemento &&memento, const Window::SectionShow ¶ms = Window::SectionShow()) override; void showBackFromStack( const Window::SectionShow ¶ms = Window::SectionShow()) override; rpl::lifetime &lifetime() { return _lifetime; } ~Controller(); private: using SearchQuery = Api::DelayedSearchController::Query; void updateSearchControllers(not_null<ContentMemento*> memento); SearchQuery produceSearchQuery(const QString &query) const; void setupMigrationViewer(); not_null<WrapWidget*> _widget; Key _key; PeerData *_migrated = nullptr; rpl::variable<Wrap> _wrap; Section _section; std::unique_ptr<Ui::SearchFieldController> _searchFieldController; std::unique_ptr<Api::DelayedSearchController> _searchController; rpl::variable<bool> _seachEnabledByContent = false; bool _searchStartsFocused = false; rpl::lifetime _lifetime; }; } // namespace Info������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/info_layer_widget.cpp�������������������������������������0000664�0000000�0000000�00000014064�13262451251�0024561�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/info_layer_widget.h" #include <rpl/mappers.h> #include "info/info_content_widget.h" #include "info/info_top_bar.h" #include "info/info_memento.h" #include "ui/rp_widget.h" #include "ui/focus_persister.h" #include "ui/widgets/buttons.h" #include "window/section_widget.h" #include "window/window_controller.h" #include "window/main_window.h" #include "auth_session.h" #include "styles/style_info.h" #include "styles/style_settings.h" #include "styles/style_window.h" #include "styles/style_boxes.h" namespace Info { LayerWidget::LayerWidget( not_null<Window::Controller*> controller, not_null<Memento*> memento) : _controller(controller) , _content(this, controller, Wrap::Layer, memento) { setupHeightConsumers(); } LayerWidget::LayerWidget( not_null<Window::Controller*> controller, not_null<MoveMemento*> memento) : _controller(controller) , _content(memento->takeContent(this, Wrap::Layer)) { setupHeightConsumers(); } void LayerWidget::setupHeightConsumers() { _content->scrollTillBottomChanges( ) | rpl::filter([this] { return !_inResize; }) | rpl::start_with_next([this] { resizeToWidth(width()); }, lifetime()); _content->desiredHeightValue( ) | rpl::start_with_next([this](int height) { accumulate_max(_desiredHeight, height); if (_content && !_inResize) { resizeToWidth(width()); } }, lifetime()); } void LayerWidget::showFinished() { } void LayerWidget::parentResized() { auto parentSize = parentWidget()->size(); auto parentWidth = parentSize.width(); if (parentWidth < MinimalSupportedWidth()) { Ui::FocusPersister persister(this); auto localCopy = _controller; auto memento = MoveMemento(std::move(_content)); localCopy->hideSpecialLayer(anim::type::instant); localCopy->showSection( std::move(memento), Window::SectionShow( Window::SectionShow::Way::Forward, anim::type::instant, anim::activation::background)); // // There was a layout logic which caused layer info to become a // third column info if the window size allows, but it was decided // to keep layer info and third column info separated. // //} else if (_controller->canShowThirdSectionWithoutResize()) { // takeToThirdSection(); } else { auto newWidth = qMin( parentWidth - 2 * st::infoMinimalLayerMargin, st::infoDesiredWidth); resizeToWidth(newWidth); } } bool LayerWidget::takeToThirdSection() { return false; // // There was a layout logic which caused layer info to become a // third column info if the window size allows, but it was decided // to keep layer info and third column info separated. // //Ui::FocusPersister persister(this); //auto localCopy = _controller; //auto memento = MoveMemento(std::move(_content)); //localCopy->hideSpecialLayer(anim::type::instant); //// When creating third section in response to the window //// size allowing it to fit without window resize we want //// to save that we didn't extend the window while showing //// the third section, so that when we close it we won't //// shrink the window size. //// //// See https://github.com/telegramdesktop/tdesktop/issues/4091 //Auth().settings().setThirdSectionExtendedBy(0); //Auth().settings().setThirdSectionInfoEnabled(true); //Auth().saveSettingsDelayed(); //localCopy->showSection( // std::move(memento), // Window::SectionShow( // Window::SectionShow::Way::ClearStack, // anim::type::instant, // anim::activation::background)); //return true; } bool LayerWidget::showSectionInternal( not_null<Window::SectionMemento*> memento, const Window::SectionShow ¶ms) { if (_content->showInternal(memento, params)) { if (params.activation != anim::activation::background) { Ui::hideLayer(); } return true; } return false; } int LayerWidget::MinimalSupportedWidth() { auto minimalMargins = 2 * st::infoMinimalLayerMargin; return st::infoMinimalWidth + minimalMargins; } int LayerWidget::resizeGetHeight(int newWidth) { if (!parentWidget() || !_content) { return 0; } _inResize = true; auto guard = gsl::finally([&] { _inResize = false; }); auto parentSize = parentWidget()->size(); auto windowWidth = parentSize.width(); auto windowHeight = parentSize.height(); auto newLeft = (windowWidth - newWidth) / 2; auto newTop = snap( windowHeight / 24, st::infoLayerTopMinimal, st::infoLayerTopMaximal); auto newBottom = newTop; auto desiredHeight = st::boxRadius + _desiredHeight + st::boxRadius; accumulate_min(desiredHeight, windowHeight - newTop - newBottom); // First resize content to new width and get the new desired height. auto contentLeft = 0; auto contentTop = st::boxRadius; auto contentBottom = st::boxRadius; auto contentWidth = newWidth; auto contentHeight = desiredHeight - contentTop - contentBottom; auto scrollTillBottom = _content->scrollTillBottom(contentHeight); auto additionalScroll = std::min(scrollTillBottom, newBottom); desiredHeight += additionalScroll; contentHeight += additionalScroll; _tillBottom = (newTop + desiredHeight >= windowHeight); if (_tillBottom) { contentHeight += contentBottom; additionalScroll += contentBottom; } _content->updateGeometry({ contentLeft, contentTop, contentWidth, contentHeight }, additionalScroll); auto newGeometry = QRect(newLeft, newTop, newWidth, desiredHeight); if (newGeometry != geometry()) { _content->forceContentRepaint(); } if (newGeometry.topLeft() != geometry().topLeft()) { move(newGeometry.topLeft()); } return desiredHeight; } void LayerWidget::paintEvent(QPaintEvent *e) { Painter p(this); auto clip = e->rect(); auto r = st::boxRadius; auto parts = RectPart::None | 0; if (clip.intersects({ 0, 0, width(), r })) { parts |= RectPart::FullTop; } if (!_tillBottom) { if (clip.intersects({ 0, height() - r, width(), r })) { parts |= RectPart::FullBottom; } } if (parts) { App::roundRect( p, rect(), st::boxBg, BoxCorners, nullptr, parts); } } } // namespace Info ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/info_layer_widget.h���������������������������������������0000664�0000000�0000000�00000002346�13262451251�0024226�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "window/layer_widget.h" namespace Window { class Controller; } // namespace Window namespace Info { class Memento; class MoveMemento; class WrapWidget; class TopBar; class LayerWidget : public Window::LayerWidget { public: LayerWidget( not_null<Window::Controller*> controller, not_null<Memento*> memento); LayerWidget( not_null<Window::Controller*> controller, not_null<MoveMemento*> memento); void showFinished() override; void parentResized() override; bool takeToThirdSection() override; bool showSectionInternal( not_null<Window::SectionMemento*> memento, const Window::SectionShow ¶ms) override; static int MinimalSupportedWidth(); protected: int resizeGetHeight(int newWidth) override; void paintEvent(QPaintEvent *e) override; private: void setupHeightConsumers(); not_null<Window::Controller*> _controller; object_ptr<WrapWidget> _content; int _desiredHeight = 0; bool _inResize = false; bool _tillBottom = false; }; } // namespace Info ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/info_memento.cpp������������������������������������������0000664�0000000�0000000�00000011643�13262451251�0023546�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/info_memento.h" #include "info/profile/info_profile_widget.h" #include "info/media/info_media_widget.h" #include "info/members/info_members_widget.h" #include "info/channels/info_channels_widget.h" #include "info/common_groups/info_common_groups_widget.h" #include "info/feed/info_feed_profile_widget.h" #include "info/info_section_widget.h" #include "info/info_layer_widget.h" #include "info/info_controller.h" #include "boxes/peer_list_box.h" namespace Info { Memento::Memento(PeerId peerId) : Memento(peerId, Section::Type::Profile) { } Memento::Memento(PeerId peerId, Section section) : Memento(DefaultStack(peerId, section)) { } Memento::Memento(not_null<Data::Feed*> feed, Section section) : Memento(DefaultStack(feed, section)) { } Memento::Memento(std::vector<std::unique_ptr<ContentMemento>> stack) : _stack(std::move(stack)) { } std::vector<std::unique_ptr<ContentMemento>> Memento::DefaultStack( PeerId peerId, Section section) { auto result = std::vector<std::unique_ptr<ContentMemento>>(); result.push_back(DefaultContent(peerId, section)); return result; } std::vector<std::unique_ptr<ContentMemento>> Memento::DefaultStack( not_null<Data::Feed*> feed, Section section) { auto result = std::vector<std::unique_ptr<ContentMemento>>(); result.push_back(DefaultContent(feed, section)); return result; } Section Memento::DefaultSection(Dialogs::Key key) { if (const auto peer = key.peer()) { if (peer->isSelf()) { return Section(Section::MediaType::Photo); } } return Section(Section::Type::Profile); } Memento Memento::Default(Dialogs::Key key) { if (const auto peer = key.peer()) { return Memento(peer->id, DefaultSection(key)); } return Memento(key.feed(), DefaultSection(key)); } std::unique_ptr<ContentMemento> Memento::DefaultContent( PeerId peerId, Section section) { Expects(peerId != 0); auto peer = App::peer(peerId); if (auto to = peer->migrateTo()) { peer = to; } auto migrated = peer->migrateFrom(); peerId = peer->id; auto migratedPeerId = migrated ? migrated->id : PeerId(0); switch (section.type()) { case Section::Type::Profile: return std::make_unique<Profile::Memento>( peerId, migratedPeerId); case Section::Type::Media: return std::make_unique<Media::Memento>( peerId, migratedPeerId, section.mediaType()); case Section::Type::CommonGroups: Assert(peerIsUser(peerId)); return std::make_unique<CommonGroups::Memento>( peerToUser(peerId)); case Section::Type::Members: return std::make_unique<Members::Memento>( peerId, migratedPeerId); } Unexpected("Wrong section type in Info::Memento::DefaultContent()"); } std::unique_ptr<ContentMemento> Memento::DefaultContent( not_null<Data::Feed*> feed, Section section) { switch (section.type()) { case Section::Type::Profile: return std::make_unique<FeedProfile::Memento>(feed); case Section::Type::Channels: return std::make_unique<Channels::Memento>(feed); } Unexpected("Wrong feed section in Info::Memento::DefaultContent()"); } object_ptr<Window::SectionWidget> Memento::createWidget( QWidget *parent, not_null<Window::Controller*> controller, Window::Column column, const QRect &geometry) { auto wrap = (column == Window::Column::Third) ? Wrap::Side : Wrap::Narrow; auto result = object_ptr<SectionWidget>( parent, controller, wrap, this); result->setGeometry(geometry); return std::move(result); } object_ptr<Window::LayerWidget> Memento::createLayer( not_null<Window::Controller*> controller, const QRect &geometry) { if (geometry.width() >= LayerWidget::MinimalSupportedWidth()) { return object_ptr<LayerWidget>(controller, this); } return nullptr; } std::vector<std::unique_ptr<ContentMemento>> Memento::takeStack() { return std::move(_stack); } Memento::~Memento() = default; MoveMemento::MoveMemento(object_ptr<WrapWidget> content) : _content(std::move(content)) { } object_ptr<Window::SectionWidget> MoveMemento::createWidget( QWidget *parent, not_null<Window::Controller*> controller, Window::Column column, const QRect &geometry) { auto wrap = (column == Window::Column::Third) ? Wrap::Side : Wrap::Narrow; auto result = object_ptr<SectionWidget>( parent, controller, wrap, this); result->setGeometry(geometry); return std::move(result); } object_ptr<Window::LayerWidget> MoveMemento::createLayer( not_null<Window::Controller*> controller, const QRect &geometry) { if (geometry.width() < LayerWidget::MinimalSupportedWidth()) { return nullptr; } return object_ptr<LayerWidget>(controller, this); } object_ptr<WrapWidget> MoveMemento::takeContent( QWidget *parent, Wrap wrap) { Ui::AttachParentChild(parent, _content); _content->setWrap(wrap); return std::move(_content); } } // namespace Info ���������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/info_memento.h��������������������������������������������0000664�0000000�0000000�00000005075�13262451251�0023215�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/rp_widget.h" #include "info/info_wrap_widget.h" #include "dialogs/dialogs_key.h" #include "window/section_memento.h" namespace Storage { enum class SharedMediaType : signed char; } // namespace Storage namespace Data { class Feed; } // namespace Data namespace Ui { class ScrollArea; struct ScrollToRequest; } // namespace Ui namespace Info { class ContentMemento; class WrapWidget; class Memento final : public Window::SectionMemento { public: Memento(PeerId peerId); Memento(PeerId peerId, Section section); Memento(not_null<Data::Feed*> feed, Section section); Memento(std::vector<std::unique_ptr<ContentMemento>> stack); object_ptr<Window::SectionWidget> createWidget( QWidget *parent, not_null<Window::Controller*> controller, Window::Column column, const QRect &geometry) override; object_ptr<Window::LayerWidget> createLayer( not_null<Window::Controller*> controller, const QRect &geometry) override; int stackSize() const { return int(_stack.size()); } std::vector<std::unique_ptr<ContentMemento>> takeStack(); not_null<ContentMemento*> content() { Expects(!_stack.empty()); return _stack.back().get(); } static Section DefaultSection(Dialogs::Key key); static Memento Default(Dialogs::Key key); ~Memento(); private: static std::vector<std::unique_ptr<ContentMemento>> DefaultStack( PeerId peerId, Section section); static std::vector<std::unique_ptr<ContentMemento>> DefaultStack( not_null<Data::Feed*> feed, Section section); static std::unique_ptr<ContentMemento> DefaultContent( not_null<Data::Feed*> feed, Section section); static std::unique_ptr<ContentMemento> DefaultContent( PeerId peerId, Section section); std::vector<std::unique_ptr<ContentMemento>> _stack; }; class MoveMemento final : public Window::SectionMemento { public: MoveMemento(object_ptr<WrapWidget> content); object_ptr<Window::SectionWidget> createWidget( QWidget *parent, not_null<Window::Controller*> controller, Window::Column column, const QRect &geometry) override; object_ptr<Window::LayerWidget> createLayer( not_null<Window::Controller*> controller, const QRect &geometry) override; bool instant() const override { return true; } object_ptr<WrapWidget> takeContent( QWidget *parent, Wrap wrap); private: object_ptr<WrapWidget> _content; }; } // namespace Info �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/info_section_widget.cpp�����������������������������������0000664�0000000�0000000�00000005126�13262451251�0025110�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/info_section_widget.h" #include "info/info_content_widget.h" #include "info/info_wrap_widget.h" #include "info/info_layer_widget.h" #include "info/info_memento.h" #include "info/info_controller.h" namespace Info { SectionWidget::SectionWidget( QWidget *parent, not_null<Window::Controller*> window, Wrap wrap, not_null<Memento*> memento) : Window::SectionWidget(parent, window) , _content(this, window, wrap, memento) { init(); } SectionWidget::SectionWidget( QWidget *parent, not_null<Window::Controller*> window, Wrap wrap, not_null<MoveMemento*> memento) : Window::SectionWidget(parent, window) , _content(memento->takeContent(this, wrap)) { init(); } void SectionWidget::init() { sizeValue( ) | rpl::start_with_next([wrap = _content.data()](QSize size) { auto wrapGeometry = QRect{ { 0, 0 }, size }; auto additionalScroll = 0; wrap->updateGeometry(wrapGeometry, additionalScroll); }, _content->lifetime()); } Dialogs::RowDescriptor SectionWidget::activeChat() const { return _content->activeChat(); } bool SectionWidget::hasTopBarShadow() const { return _content->hasTopBarShadow(); } QPixmap SectionWidget::grabForShowAnimation( const Window::SectionSlideParams ¶ms) { return _content->grabForShowAnimation(params); } void SectionWidget::doSetInnerFocus() { _content->setInnerFocus(); } void SectionWidget::showFinishedHook() { _topBarSurrogate.destroy(); _content->showFast(); } void SectionWidget::showAnimatedHook( const Window::SectionSlideParams ¶ms) { _topBarSurrogate = _content->createTopBarSurrogate(this); } bool SectionWidget::showInternal( not_null<Window::SectionMemento*> memento, const Window::SectionShow ¶ms) { return _content->showInternal(memento, params); } std::unique_ptr<Window::SectionMemento> SectionWidget::createMemento() { return _content->createMemento(); } object_ptr<Window::LayerWidget> SectionWidget::moveContentToLayer( QRect bodyGeometry) { if (_content->controller()->wrap() != Wrap::Narrow || width() < LayerWidget::MinimalSupportedWidth()) { return nullptr; } return MoveMemento( std::move(_content)).createLayer( controller(), bodyGeometry); } bool SectionWidget::wheelEventFromFloatPlayer(QEvent *e) { return _content->wheelEventFromFloatPlayer(e); } QRect SectionWidget::rectForFloatPlayer() const { return _content->rectForFloatPlayer(); } } // namespace Info ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/info_section_widget.h�������������������������������������0000664�0000000�0000000�00000003201�13262451251�0024545�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/event_stream.h> #include "window/section_widget.h" namespace Ui { class SettingsSlider; } // namespace Ui namespace Info { class Memento; class MoveMemento; class Controller; class WrapWidget; enum class Wrap; class SectionWidget final : public Window::SectionWidget { public: SectionWidget( QWidget *parent, not_null<Window::Controller*> window, Wrap wrap, not_null<Memento*> memento); SectionWidget( QWidget *parent, not_null<Window::Controller*> window, Wrap wrap, not_null<MoveMemento*> memento); Dialogs::RowDescriptor activeChat() const override; bool hasTopBarShadow() const override; QPixmap grabForShowAnimation( const Window::SectionSlideParams ¶ms) override; bool showInternal( not_null<Window::SectionMemento*> memento, const Window::SectionShow ¶ms) override; std::unique_ptr<Window::SectionMemento> createMemento() override; object_ptr<Window::LayerWidget> moveContentToLayer( QRect bodyGeometry) override; // Float player interface. bool wheelEventFromFloatPlayer(QEvent *e) override; QRect rectForFloatPlayer() const override; protected: void doSetInnerFocus() override; void showFinishedHook() override; void showAnimatedHook( const Window::SectionSlideParams ¶ms) override; private: void init(); object_ptr<WrapWidget> _content; object_ptr<Ui::RpWidget> _topBarSurrogate = { nullptr }; }; } // namespace Info �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/info_top_bar.cpp������������������������������������������0000664�0000000�0000000�00000037212�13262451251�0023530�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/info_top_bar.h" #include <rpl/never.h> #include <rpl/merge.h> #include "styles/style_info.h" #include "lang/lang_keys.h" #include "info/info_wrap_widget.h" #include "info/info_controller.h" #include "info/profile/info_profile_values.h" #include "storage/storage_shared_media.h" #include "boxes/confirm_box.h" #include "boxes/peer_list_controllers.h" #include "mainwidget.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" #include "ui/widgets/input_fields.h" #include "ui/widgets/shadow.h" #include "ui/wrap/fade_wrap.h" #include "ui/wrap/padding_wrap.h" #include "ui/search_field_controller.h" #include "window/window_peer_menu.h" namespace Info { TopBar::TopBar( QWidget *parent, const style::InfoTopBar &st, SelectedItems &&selectedItems) : RpWidget(parent) , _st(st) , _selectedItems(Section::MediaType::kCount) { setAttribute(Qt::WA_OpaquePaintEvent); setSelectedItems(std::move(selectedItems)); updateControlsVisibility(anim::type::instant); } template <typename Callback> void TopBar::registerUpdateControlCallback( QObject *guard, Callback &&callback) { _updateControlCallbacks[guard] =[ weak = make_weak(guard), callback = std::forward<Callback>(callback) ](anim::type animated) { if (!weak) { return false; } callback(animated); return true; }; } template <typename Widget, typename IsVisible> void TopBar::registerToggleControlCallback( Widget *widget, IsVisible &&callback) { registerUpdateControlCallback(widget, [ widget, isVisible = std::forward<IsVisible>(callback) ](anim::type animated) { widget->toggle(isVisible(), animated); }); } void TopBar::setTitle(rpl::producer<QString> &&title) { if (_title) { delete _title; } _title = Ui::CreateChild<Ui::FadeWrap<Ui::FlatLabel>>( this, object_ptr<Ui::FlatLabel>(this, std::move(title), _st.title), st::infoTopBarScale); _title->setDuration(st::infoTopBarDuration); _title->toggle(!selectionMode(), anim::type::instant); registerToggleControlCallback(_title.data(), [=] { return !selectionMode() && !searchMode(); }); if (_back) { _title->setAttribute(Qt::WA_TransparentForMouseEvents); } updateControlsGeometry(width()); } void TopBar::enableBackButton() { if (_back) { return; } _back = Ui::CreateChild<Ui::FadeWrap<Ui::IconButton>>( this, object_ptr<Ui::IconButton>(this, _st.back), st::infoTopBarScale); _back->setDuration(st::infoTopBarDuration); _back->toggle(!selectionMode(), anim::type::instant); _back->entity()->clicks() | rpl::start_to_stream(_backClicks, _back->lifetime()); registerToggleControlCallback(_back.data(), [=] { return !selectionMode(); }); if (_title) { _title->setAttribute(Qt::WA_TransparentForMouseEvents); } updateControlsGeometry(width()); } void TopBar::createSearchView( not_null<Ui::SearchFieldController*> controller, rpl::producer<bool> &&shown, bool startsFocused) { setSearchField( controller->createField(this, _st.searchRow.field), std::move(shown), startsFocused); } bool TopBar::focusSearchField() { if (_searchField && _searchField->isVisible()) { _searchField->setFocus(); return true; } return false; } Ui::FadeWrap<Ui::RpWidget> *TopBar::pushButton( base::unique_qptr<Ui::RpWidget> button) { auto wrapped = base::make_unique_q<Ui::FadeWrap<Ui::RpWidget>>( this, object_ptr<Ui::RpWidget>::fromRaw(button.release()), st::infoTopBarScale); auto weak = wrapped.get(); _buttons.push_back(std::move(wrapped)); weak->setDuration(st::infoTopBarDuration); registerToggleControlCallback(weak, [=] { return !selectionMode() && !_searchModeEnabled; }); weak->toggle( !selectionMode() && !_searchModeEnabled, anim::type::instant); weak->widthValue( ) | rpl::start_with_next([this] { updateControlsGeometry(width()); }, lifetime()); return weak; } void TopBar::setSearchField( base::unique_qptr<Ui::InputField> field, rpl::producer<bool> &&shown, bool startsFocused) { Expects(field != nullptr); createSearchView(field.release(), std::move(shown), startsFocused); } void TopBar::clearSearchField() { _searchView = nullptr; } void TopBar::createSearchView( not_null<Ui::InputField*> field, rpl::producer<bool> &&shown, bool startsFocused) { _searchView = base::make_unique_q<Ui::FixedHeightWidget>( this, _st.searchRow.height); auto wrap = _searchView.get(); registerUpdateControlCallback(wrap, [=](anim::type) { wrap->setVisible(!selectionMode() && _searchModeAvailable); }); _searchField = field; auto fieldWrap = Ui::CreateChild<Ui::FadeWrap<Ui::InputField>>( wrap, object_ptr<Ui::InputField>::fromRaw(field), st::infoTopBarScale); fieldWrap->setDuration(st::infoTopBarDuration); auto focusLifetime = field->lifetime().make_state<rpl::lifetime>(); registerUpdateControlCallback(fieldWrap, [=](anim::type animated) { auto fieldShown = !selectionMode() && searchMode(); if (!fieldShown && field->hasFocus()) { setFocus(); } fieldWrap->toggle(fieldShown, animated); if (fieldShown) { *focusLifetime = field->shownValue() | rpl::filter([](bool shown) { return shown; }) | rpl::take(1) | rpl::start_with_next([=] { field->setFocus(); }); } else { focusLifetime->destroy(); } }); auto button = base::make_unique_q<Ui::IconButton>(this, _st.search); auto search = button.get(); search->addClickHandler([=] { _searchModeEnabled = true; updateControlsVisibility(anim::type::normal); }); auto searchWrap = pushButton(std::move(button)); registerToggleControlCallback(searchWrap, [=] { return !selectionMode() && _searchModeAvailable && !_searchModeEnabled; }); auto cancel = Ui::CreateChild<Ui::CrossButton>( wrap, _st.searchRow.fieldCancel); registerToggleControlCallback(cancel, [=] { return !selectionMode() && searchMode(); }); auto cancelSearch = [=] { if (!field->getLastText().isEmpty()) { field->setText(QString()); } else { _searchModeEnabled = false; updateControlsVisibility(anim::type::normal); } }; cancel->addClickHandler(cancelSearch); field->connect(field, &Ui::InputField::cancelled, cancelSearch); wrap->widthValue( ) | rpl::start_with_next([=](int newWidth) { auto availableWidth = newWidth - _st.searchRow.fieldCancelSkip; fieldWrap->resizeToWidth(availableWidth); fieldWrap->moveToLeft( _st.searchRow.padding.left(), _st.searchRow.padding.top()); cancel->moveToRight(0, 0); }, wrap->lifetime()); widthValue( ) | rpl::start_with_next([=](int newWidth) { auto left = _back ? _st.back.width : _st.titlePosition.x(); wrap->setGeometryToLeft( left, 0, newWidth - left, wrap->height(), newWidth); }, wrap->lifetime()); field->alive( ) | rpl::start_with_done([=] { field->setParent(nullptr); removeButton(search); clearSearchField(); }, _searchView->lifetime()); _searchModeEnabled = !field->getLastText().isEmpty() || startsFocused; updateControlsVisibility(anim::type::instant); std::move( shown ) | rpl::start_with_next([=](bool visible) { auto alreadyInSearch = !field->getLastText().isEmpty(); _searchModeAvailable = visible || alreadyInSearch; updateControlsVisibility(anim::type::instant); }, wrap->lifetime()); } void TopBar::removeButton(not_null<Ui::RpWidget*> button) { _buttons.erase( std::remove(_buttons.begin(), _buttons.end(), button), _buttons.end()); } int TopBar::resizeGetHeight(int newWidth) { updateControlsGeometry(newWidth); return _st.height; } void TopBar::updateControlsGeometry(int newWidth) { updateDefaultControlsGeometry(newWidth); updateSelectionControlsGeometry(newWidth); } void TopBar::updateDefaultControlsGeometry(int newWidth) { auto right = 0; for (auto &button : _buttons) { if (!button) continue; button->moveToRight(right, 0, newWidth); right += button->width(); } if (_back) { _back->setGeometryToLeft( 0, 0, newWidth - right, _back->height(), newWidth); } if (_title) { _title->moveToLeft( _back ? _st.back.width : _st.titlePosition.x(), _st.titlePosition.y(), newWidth); } } void TopBar::updateSelectionControlsGeometry(int newWidth) { if (!_selectionText) { return; } auto right = _st.mediaActionsSkip; if (_canDelete) { _delete->moveToRight(right, 0, newWidth); right += _delete->width(); } _forward->moveToRight(right, 0, newWidth); right += _forward->width(); auto left = 0; _cancelSelection->moveToLeft(left, 0); left += _cancelSelection->width(); const auto top = 0; const auto availableWidth = newWidth - left - right; _selectionText->resizeToNaturalWidth(availableWidth); _selectionText->moveToLeft( left, top, newWidth); } void TopBar::paintEvent(QPaintEvent *e) { Painter p(this); auto ms = getms(); auto highlight = _a_highlight.current(ms, _highlight ? 1. : 0.); if (_highlight && !_a_highlight.animating()) { _highlight = false; startHighlightAnimation(); } auto brush = anim::brush(_st.bg, _st.highlightBg, highlight); p.fillRect(e->rect(), brush); } void TopBar::highlight() { _highlight = true; startHighlightAnimation(); } void TopBar::startHighlightAnimation() { _a_highlight.start( [this] { update(); }, _highlight ? 0. : 1., _highlight ? 1. : 0., _st.highlightDuration); } void TopBar::updateControlsVisibility(anim::type animated) { for (auto i = _updateControlCallbacks.begin(); i != _updateControlCallbacks.end();) { auto &&[widget, callback] = *i; if (!callback(animated)) { i = _updateControlCallbacks.erase(i); } else { ++i; } } } void TopBar::setSelectedItems(SelectedItems &&items) { auto wasSelectionMode = selectionMode(); _selectedItems = std::move(items); if (selectionMode()) { if (_selectionText) { updateSelectionState(); if (!wasSelectionMode) { _selectionText->entity()->finishAnimating(); } } else { createSelectionControls(); } } updateControlsVisibility(anim::type::normal); } SelectedItems TopBar::takeSelectedItems() { _canDelete = false; return std::move(_selectedItems); } rpl::producer<> TopBar::cancelSelectionRequests() const { return _cancelSelectionClicks.events(); } void TopBar::updateSelectionState() { Expects(_selectionText && _delete); _canDelete = computeCanDelete(); _selectionText->entity()->setValue(generateSelectedText()); _delete->toggle(_canDelete, anim::type::instant); updateSelectionControlsGeometry(width()); } void TopBar::createSelectionControls() { auto wrap = [&](auto created) { registerToggleControlCallback( created, [this] { return selectionMode(); }); created->toggle(false, anim::type::instant); return created; }; _canDelete = computeCanDelete(); _cancelSelection = wrap(Ui::CreateChild<Ui::FadeWrap<Ui::IconButton>>( this, object_ptr<Ui::IconButton>(this, _st.mediaCancel), st::infoTopBarScale)); _cancelSelection->setDuration(st::infoTopBarDuration); _cancelSelection->entity()->clicks( ) | rpl::start_to_stream( _cancelSelectionClicks, _cancelSelection->lifetime()); _selectionText = wrap(Ui::CreateChild<Ui::FadeWrap<Ui::LabelWithNumbers>>( this, object_ptr<Ui::LabelWithNumbers>( this, _st.title, _st.titlePosition.y(), generateSelectedText()), st::infoTopBarScale)); _selectionText->setDuration(st::infoTopBarDuration); _selectionText->entity()->resize(0, _st.height); _forward = wrap(Ui::CreateChild<Ui::FadeWrap<Ui::IconButton>>( this, object_ptr<Ui::IconButton>(this, _st.mediaForward), st::infoTopBarScale)); _forward->setDuration(st::infoTopBarDuration); _forward->entity()->addClickHandler([this] { performForward(); }); _delete = wrap(Ui::CreateChild<Ui::FadeWrap<Ui::IconButton>>( this, object_ptr<Ui::IconButton>(this, _st.mediaDelete), st::infoTopBarScale)); registerToggleControlCallback( _delete.data(), [this] { return selectionMode() && _canDelete; }); _delete->setDuration(st::infoTopBarDuration); _delete->entity()->addClickHandler([this] { performDelete(); }); _delete->entity()->setVisible(_canDelete); updateControlsGeometry(width()); } bool TopBar::computeCanDelete() const { return ranges::find_if( _selectedItems.list, [](const SelectedItem &item) { return !item.canDelete; } ) == _selectedItems.list.end(); } Ui::StringWithNumbers TopBar::generateSelectedText() const { using Data = Ui::StringWithNumbers; using Type = Storage::SharedMediaType; auto phrase = [&] { switch (_selectedItems.type) { case Type::Photo: return lng_media_selected_photo__generic<Data>; case Type::Video: return lng_media_selected_video__generic<Data>; case Type::File: return lng_media_selected_file__generic<Data>; case Type::MusicFile: return lng_media_selected_song__generic<Data>; case Type::Link: return lng_media_selected_link__generic<Data>; case Type::VoiceFile: return lng_media_selected_audio__generic<Data>; // case Type::RoundFile: return lng_media_selected_round__generic<Data>; } Unexpected("Type in TopBar::generateSelectedText()"); }(); return phrase(lt_count, _selectedItems.list.size()); } bool TopBar::selectionMode() const { return !_selectedItems.list.empty(); } bool TopBar::searchMode() const { return _searchModeAvailable && _searchModeEnabled; } MessageIdsList TopBar::collectItems() const { return ranges::view::all( _selectedItems.list ) | ranges::view::transform([](auto &&item) { return item.msgId; }) | ranges::view::filter([](const FullMsgId &msgId) { return App::histItemById(msgId) != nullptr; }) | ranges::to_vector; } void TopBar::performForward() { auto items = collectItems(); if (items.empty()) { _cancelSelectionClicks.fire({}); return; } Window::ShowForwardMessagesBox( std::move(items), [weak = make_weak(this)] { if (weak) { weak->_cancelSelectionClicks.fire({}); } }); } void TopBar::performDelete() { auto items = collectItems(); if (items.empty()) { _cancelSelectionClicks.fire({}); } else { const auto box = Ui::show(Box<DeleteMessagesBox>(std::move(items))); box->setDeleteConfirmedCallback([weak = make_weak(this)] { if (weak) { weak->_cancelSelectionClicks.fire({}); } }); } } rpl::producer<QString> TitleValue( const Section §ion, Key key, bool isStackBottom) { return Lang::Viewer([&] { const auto peer = key.peer(); switch (section.type()) { case Section::Type::Profile: if (const auto feed = key.feed()) { return lng_info_feed_title; } else if (auto user = peer->asUser()) { return user->botInfo ? lng_info_bot_title : lng_info_user_title; } else if (auto channel = peer->asChannel()) { return channel->isMegagroup() ? lng_info_group_title : lng_info_channel_title; } else if (peer->isChat()) { return lng_info_group_title; } Unexpected("Bad peer type in Info::TitleValue()"); case Section::Type::Media: if (peer->isSelf() && isStackBottom) { return lng_profile_shared_media; } switch (section.mediaType()) { case Section::MediaType::Photo: return lng_media_type_photos; case Section::MediaType::Video: return lng_media_type_videos; case Section::MediaType::MusicFile: return lng_media_type_songs; case Section::MediaType::File: return lng_media_type_files; case Section::MediaType::VoiceFile: return lng_media_type_audios; case Section::MediaType::Link: return lng_media_type_links; case Section::MediaType::RoundFile: return lng_media_type_rounds; } Unexpected("Bad media type in Info::TitleValue()"); case Section::Type::CommonGroups: return lng_profile_common_groups_section; case Section::Type::Members: return lng_profile_participants_section; case Section::Type::Channels: return lng_info_feed_channels; } Unexpected("Bad section type in Info::TitleValue()"); }()); } } // namespace Info ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/info_top_bar.h��������������������������������������������0000664�0000000�0000000�00000007257�13262451251�0023203�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/rp_widget.h" #include "ui/wrap/fade_wrap.h" #include "ui/effects/numbers_animation.h" #include "info/info_wrap_widget.h" namespace style { struct InfoTopBar; } // namespace style namespace Ui { class IconButton; class FlatLabel; class InputField; class SearchFieldController; class LabelWithNumbers; } // namespace Ui namespace Info { class Key; class Section; rpl::producer<QString> TitleValue( const Section §ion, Key key, bool isStackBottom); class TopBar : public Ui::RpWidget { public: TopBar( QWidget *parent, const style::InfoTopBar &st, SelectedItems &&items); auto backRequest() const { return _backClicks.events(); } void setTitle(rpl::producer<QString> &&title); void enableBackButton(); void highlight(); template <typename ButtonWidget> ButtonWidget *addButton(base::unique_qptr<ButtonWidget> button) { auto result = button.get(); pushButton(std::move(button)); return result; } void createSearchView( not_null<Ui::SearchFieldController*> controller, rpl::producer<bool> &&shown, bool startsFocused); bool focusSearchField(); void setSelectedItems(SelectedItems &&items); SelectedItems takeSelectedItems(); rpl::producer<> cancelSelectionRequests() const; void finishAnimating() { updateControlsVisibility(anim::type::instant); } protected: int resizeGetHeight(int newWidth) override; void paintEvent(QPaintEvent *e) override; private: void updateControlsGeometry(int newWidth); void updateDefaultControlsGeometry(int newWidth); void updateSelectionControlsGeometry(int newWidth); Ui::FadeWrap<Ui::RpWidget> *pushButton(base::unique_qptr<Ui::RpWidget> button); void removeButton(not_null<Ui::RpWidget*> button); void startHighlightAnimation(); void updateControlsVisibility(anim::type animated); bool selectionMode() const; bool searchMode() const; Ui::StringWithNumbers generateSelectedText() const; [[nodiscard]] bool computeCanDelete() const; void updateSelectionState(); void createSelectionControls(); void clearSelectionControls(); MessageIdsList collectItems() const; void performForward(); void performDelete(); void setSearchField( base::unique_qptr<Ui::InputField> field, rpl::producer<bool> &&shown, bool startsFocused); void clearSearchField(); void createSearchView( not_null<Ui::InputField*> field, rpl::producer<bool> &&shown, bool startsFocused); template <typename Callback> void registerUpdateControlCallback(QObject *guard, Callback &&callback); template <typename Widget, typename IsVisible> void registerToggleControlCallback(Widget *widget, IsVisible &&callback); const style::InfoTopBar &_st; Animation _a_highlight; bool _highlight = false; QPointer<Ui::FadeWrap<Ui::IconButton>> _back; std::vector<base::unique_qptr<Ui::RpWidget>> _buttons; QPointer<Ui::FadeWrap<Ui::FlatLabel>> _title; bool _searchModeEnabled = false; bool _searchModeAvailable = false; base::unique_qptr<Ui::RpWidget> _searchView; QPointer<Ui::InputField> _searchField; rpl::event_stream<> _backClicks; SelectedItems _selectedItems; bool _canDelete = false; QPointer<Ui::FadeWrap<Ui::IconButton>> _cancelSelection; QPointer<Ui::FadeWrap<Ui::LabelWithNumbers>> _selectionText; QPointer<Ui::FadeWrap<Ui::IconButton>> _forward; QPointer<Ui::FadeWrap<Ui::IconButton>> _delete; rpl::event_stream<> _cancelSelectionClicks; using UpdateCallback = base::lambda<bool(anim::type)>; std::map<QObject*, UpdateCallback> _updateControlCallbacks; }; } // namespace Info �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/info_wrap_widget.cpp��������������������������������������0000664�0000000�0000000�00000065106�13262451251�0024421�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/info_wrap_widget.h" #include <rpl/flatten_latest.h> #include <rpl/take.h> #include <rpl/combine.h> #include "info/profile/info_profile_widget.h" #include "info/profile/info_profile_values.h" #include "info/media/info_media_widget.h" #include "info/info_content_widget.h" #include "info/info_controller.h" #include "info/info_memento.h" #include "info/info_top_bar.h" #include "ui/widgets/discrete_sliders.h" #include "ui/widgets/buttons.h" #include "ui/widgets/shadow.h" #include "ui/widgets/dropdown_menu.h" #include "ui/wrap/fade_wrap.h" #include "ui/search_field_controller.h" #include "calls/calls_instance.h" #include "window/window_controller.h" #include "window/window_slide_animation.h" #include "window/window_peer_menu.h" #include "boxes/peer_list_box.h" #include "auth_session.h" #include "mainwidget.h" #include "lang/lang_keys.h" #include "styles/style_info.h" #include "styles/style_profile.h" namespace Info { namespace { const style::InfoTopBar &TopBarStyle(Wrap wrap) { return (wrap == Wrap::Layer) ? st::infoLayerTopBar : st::infoTopBar; } } // namespace struct WrapWidget::StackItem { std::unique_ptr<ContentMemento> section; // std::unique_ptr<ContentMemento> anotherTab; }; WrapWidget::WrapWidget( QWidget *parent, not_null<Window::Controller*> window, Wrap wrap, not_null<Memento*> memento) : SectionWidget(parent, window) , _wrap(wrap) , _controller(createController(window, memento->content())) , _topShadow(this) { _topShadow->toggleOn( topShadowToggledValue( ) | rpl::filter([](bool shown) { return true; })); _wrap.changes( ) | rpl::start_with_next([this] { setupTop(); finishShowContent(); }, lifetime()); selectedListValue( ) | rpl::start_with_next([this](SelectedItems &&items) { InvokeQueued(this, [this, items = std::move(items)]() mutable { if (_topBar) _topBar->setSelectedItems(std::move(items)); }); }, lifetime()); restoreHistoryStack(memento->takeStack()); } void WrapWidget::restoreHistoryStack( std::vector<std::unique_ptr<ContentMemento>> stack) { Expects(!stack.empty()); Expects(!hasStackHistory()); auto content = std::move(stack.back()); stack.pop_back(); if (!stack.empty()) { _historyStack.reserve(stack.size()); for (auto &stackItem : stack) { auto item = StackItem(); item.section = std::move(stackItem); _historyStack.push_back(std::move(item)); } } startInjectingActivePeerProfiles(); showNewContent(content.get()); } void WrapWidget::startInjectingActivePeerProfiles() { using namespace rpl::mappers; rpl::combine( _wrap.value(), _controller->parentController()->activeChatValue() ) | rpl::filter( (_1 == Wrap::Side) && _2 ) | rpl::map( _2 ) | rpl::start_with_next([this](Dialogs::Key key) { injectActiveProfile(key); }, lifetime()); } void WrapWidget::injectActiveProfile(Dialogs::Key key) { if (const auto peer = key.peer()) { injectActivePeerProfile(peer); } else if (const auto feed = key.feed()) { injectActiveFeedProfile(feed); } } void WrapWidget::injectActivePeerProfile(not_null<PeerData*> peer) { const auto firstPeerId = hasStackHistory() ? _historyStack.front().section->peerId() : _controller->peerId(); const auto firstSectionType = hasStackHistory() ? _historyStack.front().section->section().type() : _controller->section().type(); const auto firstSectionMediaType = [&] { if (firstSectionType == Section::Type::Profile) { return Section::MediaType::kCount; } return hasStackHistory() ? _historyStack.front().section->section().mediaType() : _controller->section().mediaType(); }(); const auto expectedType = peer->isSelf() ? Section::Type::Media : Section::Type::Profile; const auto expectedMediaType = peer->isSelf() ? Section::MediaType::Photo : Section::MediaType::kCount; if (firstSectionType != expectedType || firstSectionMediaType != expectedMediaType || firstPeerId != peer->id) { auto section = peer->isSelf() ? Section(Section::MediaType::Photo) : Section(Section::Type::Profile); injectActiveProfileMemento(std::move( Memento(peer->id, section).takeStack().front())); } } void WrapWidget::injectActiveFeedProfile(not_null<Data::Feed*> feed) { const auto firstFeed = hasStackHistory() ? _historyStack.front().section->feed() : _controller->feed(); const auto firstSectionType = hasStackHistory() ? _historyStack.front().section->section().type() : _controller->section().type(); const auto expectedType = Section::Type::Profile; if (firstSectionType != expectedType || firstFeed != feed) { auto section = Section(Section::Type::Profile); injectActiveProfileMemento(std::move( Memento(feed, section).takeStack().front())); } } void WrapWidget::injectActiveProfileMemento( std::unique_ptr<ContentMemento> memento) { auto injected = StackItem(); injected.section = std::move(memento); _historyStack.insert( _historyStack.begin(), std::move(injected)); if (_content) { setupTop(); finishShowContent(); } } std::unique_ptr<Controller> WrapWidget::createController( not_null<Window::Controller*> window, not_null<ContentMemento*> memento) { auto result = std::make_unique<Controller>( this, window, memento); return result; } Key WrapWidget::key() const { return _controller->key(); } Dialogs::RowDescriptor WrapWidget::activeChat() const { if (const auto peer = key().peer()) { return Dialogs::RowDescriptor(App::history(peer), FullMsgId()); } else if (const auto feed = key().feed()) { return Dialogs::RowDescriptor(feed, FullMsgId()); } Unexpected("Owner in WrapWidget::activeChat()."); } // This was done for tabs support. // //void WrapWidget::createTabs() { // _topTabs.create(this, st::infoTabs); // auto sections = QStringList(); // sections.push_back(lang(lng_profile_info_section).toUpper()); // sections.push_back(lang(lng_info_tab_media).toUpper()); // _topTabs->setSections(sections); // _topTabs->setActiveSection(static_cast<int>(_tab)); // _topTabs->finishAnimating(); // // _topTabs->sectionActivated( // ) | rpl::map([](int index) { // return static_cast<Tab>(index); // }) | rpl::start_with_next( // [this](Tab tab) { showTab(tab); }, // lifetime()); // // _topTabs->move(0, 0); // _topTabs->resizeToWidth(width()); // _topTabs->show(); // // _topTabsBackground.create(this, st::profileBg); // _topTabsBackground->setAttribute(Qt::WA_OpaquePaintEvent); // // _topTabsBackground->move(0, 0); // _topTabsBackground->resize( // width(), // _topTabs->height() - st::lineWidth); // _topTabsBackground->show(); //} void WrapWidget::forceContentRepaint() { // WA_OpaquePaintEvent on TopBar creates render glitches when // animating the LayerWidget's height :( Fixing by repainting. // This was done for tabs support. // //if (_topTabs) { // _topTabsBackground->update(); //} if (_topBar) { _topBar->update(); } _content->update(); } // This was done for tabs support. // //void WrapWidget::showTab(Tab tab) { // if (_tab == tab) { // return; // } // Expects(_content != nullptr); // auto direction = (tab > _tab) // ? SlideDirection::FromRight // : SlideDirection::FromLeft; // auto newAnotherMemento = _content->createMemento(); // if (!_anotherTabMemento) { // _anotherTabMemento = createTabMemento(tab); // } // auto newController = createController( // _controller->parentController(), // _anotherTabMemento.get()); // auto newContent = createContent( // _anotherTabMemento.get(), // newController.get()); // auto animationParams = SectionSlideParams(); //// animationParams.withFade = (wrap() == Wrap::Layer); // animationParams.withTabs = true; // animationParams.withTopBarShadow = hasTopBarShadow() // && newContent->hasTopBarShadow(); // animationParams.oldContentCache = grabForShowAnimation( // animationParams); // // _controller = std::move(newController); // showContent(std::move(newContent)); // // showAnimated(direction, animationParams); // // _anotherTabMemento = std::move(newAnotherMemento); // _tab = tab; //} // //void WrapWidget::setupTabbedTop() { // auto section = _controller->section(); // switch (section.type()) { // case Section::Type::Profile: // setupTabs(Tab::Profile); // break; // case Section::Type::Media: // switch (section.mediaType()) { // case Section::MediaType::Photo: // case Section::MediaType::Video: // case Section::MediaType::File: // setupTabs(Tab::Media); // break; // default: // setupTabs(Tab::None); // break; // } // break; // case Section::Type::CommonGroups: // case Section::Type::Members: // setupTabs(Tab::None); // break; // } //} void WrapWidget::setupTop() { // This was done for tabs support. // //if (wrap() == Wrap::Side && !hasStackHistory()) { // setupTabbedTop(); //} else { // setupTabs(Tab::None); //} //if (_topTabs) { // _topBar.destroy(); //} else { // createTopBar(); //} createTopBar(); } void WrapWidget::createTopBar() { const auto wrapValue = wrap(); auto selectedItems = _topBar ? _topBar->takeSelectedItems() : SelectedItems(Section::MediaType::kCount); _topBar.create(this, TopBarStyle(wrapValue), std::move(selectedItems)); _topBar->cancelSelectionRequests( ) | rpl::start_with_next([this] { _content->cancelSelection(); }, _topBar->lifetime()); _topBar->setTitle(TitleValue( _controller->section(), _controller->key(), !hasStackHistory())); if (wrapValue == Wrap::Narrow || hasStackHistory()) { _topBar->enableBackButton(); _topBar->backRequest( ) | rpl::start_with_next([this] { _controller->showBackFromStack(); }, _topBar->lifetime()); } else if (wrapValue == Wrap::Side) { auto close = _topBar->addButton( base::make_unique_q<Ui::IconButton>( _topBar, st::infoTopBarClose)); close->addClickHandler([this] { _controller->parentController()->closeThirdSection(); }); } if (wrapValue == Wrap::Layer) { auto close = _topBar->addButton( base::make_unique_q<Ui::IconButton>( _topBar, st::infoLayerTopBarClose)); close->addClickHandler([this] { _controller->parentController()->hideSpecialLayer(); }); } else if (requireTopBarSearch()) { auto search = _controller->searchFieldController(); Assert(search != nullptr); _topBar->createSearchView( search, _controller->searchEnabledByContent(), _controller->takeSearchStartsFocused()); } if (_controller->section().type() == Section::Type::Profile && (wrapValue != Wrap::Side || hasStackHistory())) { addProfileMenuButton(); addProfileCallsButton(); // addProfileNotificationsButton(); } _topBar->lower(); _topBar->resizeToWidth(width()); _topBar->finishAnimating(); _topBar->show(); } void WrapWidget::addProfileMenuButton() { Expects(_topBar != nullptr); _topBarMenuToggle.reset(_topBar->addButton( base::make_unique_q<Ui::IconButton>( _topBar, (wrap() == Wrap::Layer ? st::infoLayerTopBarMenu : st::infoTopBarMenu)))); _topBarMenuToggle->addClickHandler([this] { showProfileMenu(); }); } void WrapWidget::addProfileCallsButton() { Expects(_topBar != nullptr); const auto peer = key().peer(); const auto user = peer ? peer->asUser() : nullptr; if (!user || user->isSelf() || !Global::PhoneCallsEnabled()) { return; } Notify::PeerUpdateValue( user, Notify::PeerUpdate::Flag::UserHasCalls ) | rpl::filter([=] { return user->hasCalls(); }) | rpl::take( 1 ) | rpl::start_with_next([=] { _topBar->addButton( base::make_unique_q<Ui::IconButton>( _topBar, (wrap() == Wrap::Layer ? st::infoLayerTopBarCall : st::infoTopBarCall)) )->addClickHandler([=] { Calls::Current().startOutgoingCall(user); }); }, _topBar->lifetime()); if (user && user->callsStatus() == UserData::CallsStatus::Unknown) { user->updateFull(); } } void WrapWidget::addProfileNotificationsButton() { Expects(_topBar != nullptr); const auto peer = key().peer(); if (!peer) { return; } auto notifications = _topBar->addButton( base::make_unique_q<Ui::IconButton>( _topBar, (wrap() == Wrap::Layer ? st::infoLayerTopBarNotifications : st::infoTopBarNotifications))); notifications->addClickHandler([peer] { const auto muteState = peer->isMuted() ? Data::NotifySettings::MuteChange::Unmute : Data::NotifySettings::MuteChange::Mute; App::main()->updateNotifySettings(peer, muteState); }); Profile::NotificationsEnabledValue( peer ) | rpl::start_with_next([notifications](bool enabled) { const auto iconOverride = enabled ? &st::infoNotificationsActive : nullptr; const auto rippleOverride = enabled ? &st::lightButtonBgOver : nullptr; notifications->setIconOverride(iconOverride, iconOverride); notifications->setRippleColorOverride(rippleOverride); }, notifications->lifetime()); } void WrapWidget::showProfileMenu() { if (_topBarMenu) { _topBarMenu->hideAnimated( Ui::InnerDropdown::HideOption::IgnoreShow); return; } _topBarMenu = base::make_unique_q<Ui::DropdownMenu>(this); _topBarMenu->setHiddenCallback([this] { InvokeQueued(this, [this] { _topBarMenu = nullptr; }); if (auto toggle = _topBarMenuToggle.get()) { toggle->setForceRippled(false); } }); _topBarMenu->setShowStartCallback([this] { if (auto toggle = _topBarMenuToggle.get()) { toggle->setForceRippled(true); } }); _topBarMenu->setHideStartCallback([this] { if (auto toggle = _topBarMenuToggle.get()) { toggle->setForceRippled(false); } }); _topBarMenuToggle->installEventFilter(_topBarMenu.get()); const auto addAction = [=]( const QString &text, base::lambda<void()> callback) { return _topBarMenu->addAction(text, std::move(callback)); }; if (const auto peer = key().peer()) { Window::FillPeerMenu( _controller->parentController(), peer, addAction, Window::PeerMenuSource::Profile); } else if (const auto feed = key().feed()) { Window::FillFeedMenu( _controller->parentController(), feed, addAction, Window::PeerMenuSource::Profile); } else { _topBarMenu = nullptr; return; } auto position = (wrap() == Wrap::Layer) ? st::infoLayerTopBarMenuPosition : st::infoTopBarMenuPosition; _topBarMenu->moveToRight(position.x(), position.y()); _topBarMenu->showAnimated(Ui::PanelAnimation::Origin::TopRight); } bool WrapWidget::requireTopBarSearch() const { if (!_controller->searchFieldController()) { return false; } else if (_controller->wrap() == Wrap::Layer || _controller->section().type() == Section::Type::Profile) { return false; } else if (hasStackHistory()) { return true; } // This was for top-level tabs support. // //auto section = _controller->section(); //return (section.type() != Section::Type::Media) // || !Media::TypeToTabIndex(section.mediaType()).has_value(); return false; } bool WrapWidget::showBackFromStackInternal( const Window::SectionShow ¶ms) { if (hasStackHistory()) { auto last = std::move(_historyStack.back()); _historyStack.pop_back(); showNewContent( last.section.get(), params.withWay(Window::SectionShow::Way::Backward)); //_anotherTabMemento = std::move(last.anotherTab); return true; } return (wrap() == Wrap::Layer); } not_null<Ui::RpWidget*> WrapWidget::topWidget() const { // This was done for tabs support. // //if (_topTabs) { // return _topTabsBackground; //} return _topBar; } void WrapWidget::showContent(object_ptr<ContentWidget> content) { _content = std::move(content); _content->show(); _additionalScroll = 0; //_anotherTabMemento = nullptr; finishShowContent(); } void WrapWidget::finishShowContent() { _content->setIsStackBottom(!hasStackHistory()); updateContentGeometry(); _desiredHeights.fire(desiredHeightForContent()); _desiredShadowVisibilities.fire(_content->desiredShadowVisibility()); _selectedLists.fire(_content->selectedListValue()); _scrollTillBottomChanges.fire(_content->scrollTillBottomChanges()); _topShadow->raise(); _topShadow->finishAnimating(); // This was done for tabs support. // //if (_topTabs) { // _topTabs->raise(); //} } rpl::producer<bool> WrapWidget::topShadowToggledValue() const { // Allows always showing shadow for specific wrap value. // Was done for top level tabs support. // //using namespace rpl::mappers; //return rpl::combine( // _controller->wrapValue(), // _desiredShadowVisibilities.events() | rpl::flatten_latest(), // (_1 == Wrap::Side) || _2); return _desiredShadowVisibilities.events() | rpl::flatten_latest(); } rpl::producer<int> WrapWidget::desiredHeightForContent() const { using namespace rpl::mappers; return rpl::combine( _content->desiredHeightValue(), topWidget()->heightValue(), _1 + _2); } rpl::producer<SelectedItems> WrapWidget::selectedListValue() const { return _selectedLists.events() | rpl::flatten_latest(); } // Was done for top level tabs support. // //std::unique_ptr<ContentMemento> WrapWidget::createTabMemento( // Tab tab) { // switch (tab) { // case Tab::Profile: return std::make_unique<Profile::Memento>( // _controller->peerId(), // _controller->migratedPeerId()); // case Tab::Media: return std::make_unique<Media::Memento>( // _controller->peerId(), // _controller->migratedPeerId(), // Media::Type::Photo); // } // Unexpected("Tab value in Info::WrapWidget::createInner()"); //} object_ptr<ContentWidget> WrapWidget::createContent( not_null<ContentMemento*> memento, not_null<Controller*> controller) { return memento->createWidget( this, controller, contentGeometry()); } // Was done for top level tabs support. // //void WrapWidget::convertProfileFromStackToTab() { // if (!hasStackHistory()) { // return; // } // auto &entry = _historyStack[0]; // if (entry.section->section().type() != Section::Type::Profile) { // return; // } // auto convertInsideStack = (_historyStack.size() > 1); // auto checkSection = convertInsideStack // ? _historyStack[1].section->section() // : _controller->section(); // auto &anotherMemento = convertInsideStack // ? _historyStack[1].anotherTab // : _anotherTabMemento; // if (checkSection.type() != Section::Type::Media) { // return; // } // if (!Info::Media::TypeToTabIndex(checkSection.mediaType())) { // return; // } // anotherMemento = std::move(entry.section); // _historyStack.erase(_historyStack.begin()); //} rpl::producer<Wrap> WrapWidget::wrapValue() const { return _wrap.value(); } void WrapWidget::setWrap(Wrap wrap) { // Was done for top level tabs support. // //if (_wrap.current() != Wrap::Side && wrap == Wrap::Side) { // convertProfileFromStackToTab(); //} _wrap = wrap; } bool WrapWidget::hasTopBarShadow() const { return _topShadow->toggled(); } QPixmap WrapWidget::grabForShowAnimation( const Window::SectionSlideParams ¶ms) { if (params.withTopBarShadow) { _topShadow->setVisible(false); } else { _topShadow->setVisible(_topShadow->toggled()); } //if (params.withTabs && _topTabs) { // _topTabs->hide(); //} auto result = Ui::GrabWidget(this); if (params.withTopBarShadow) { _topShadow->setVisible(true); } //if (params.withTabs && _topTabs) { // _topTabs->show(); //} return result; } void WrapWidget::showAnimatedHook( const Window::SectionSlideParams ¶ms) { //if (params.withTabs && _topTabs) { // _topTabs->show(); // _topTabsBackground->show(); //} if (params.withTopBarShadow) { _topShadow->setVisible(true); } _topBarSurrogate = createTopBarSurrogate(this); } void WrapWidget::doSetInnerFocus() { if (!_topBar->focusSearchField()) { _content->setInnerFocus(); } } void WrapWidget::showFinishedHook() { // Restore shadow visibility after showChildren() call. _topShadow->toggle(_topShadow->toggled(), anim::type::instant); _topBarSurrogate.destroy(); } bool WrapWidget::showInternal( not_null<Window::SectionMemento*> memento, const Window::SectionShow ¶ms) { if (auto infoMemento = dynamic_cast<Memento*>(memento.get())) { if (!_controller || infoMemento->stackSize() > 1) { return false; } auto content = infoMemento->content(); auto skipInternal = hasStackHistory() && (params.way == Window::SectionShow::Way::ClearStack); if (_controller->validateMementoPeer(content)) { if (!skipInternal && _content->showInternal(content)) { highlightTopBar(); return true; // This was done for tabs support. // //} else if (_topTabs) { // // If we open the profile being in the media tab. // // Just switch back to the profile tab. // auto type = content->section().type(); // if (type == Section::Type::Profile // && _tab != Tab::Profile) { // _anotherTabMemento = std::move(infoMemento->takeStack().back()); // _topTabs->setActiveSection(static_cast<int>(Tab::Profile)); // return true; // } else if (type == Section::Type::Media // && _tab != Tab::Media // && Media::TypeToTabIndex(content->section().mediaType()).has_value()) { // _anotherTabMemento = std::move(infoMemento->takeStack().back()); // _topTabs->setActiveSection(static_cast<int>(Tab::Media)); // return true; // } } } // If we're in a nested section and we're asked to show // a chat profile that is at the bottom of the stack we'll // just go back in the stack all the way instead of pushing. if (returnToFirstStackFrame(content, params)) { return true; } showNewContent( content, params); return true; } return false; } void WrapWidget::highlightTopBar() { if (_topBar) { _topBar->highlight(); } } std::unique_ptr<Window::SectionMemento> WrapWidget::createMemento() { auto stack = std::vector<std::unique_ptr<ContentMemento>>(); stack.reserve(_historyStack.size() + 1); for (auto &stackItem : base::take(_historyStack)) { stack.push_back(std::move(stackItem.section)); } stack.push_back(_content->createMemento()); // We're not in valid state anymore and supposed to be destroyed. _controller = nullptr; return std::make_unique<Memento>(std::move(stack)); } rpl::producer<int> WrapWidget::desiredHeightValue() const { return _desiredHeights.events_starting_with(desiredHeightForContent()) | rpl::flatten_latest(); } QRect WrapWidget::contentGeometry() const { return rect().marginsRemoved({ 0, topWidget()->height(), 0, 0 }); } bool WrapWidget::returnToFirstStackFrame( not_null<ContentMemento*> memento, const Window::SectionShow ¶ms) { if (!hasStackHistory()) { return false; } auto firstPeerId = _historyStack.front().section->peerId(); auto firstSection = _historyStack.front().section->section(); if (firstPeerId == memento->peerId() && firstSection.type() == memento->section().type() && firstSection.type() == Section::Type::Profile) { _historyStack.resize(1); _controller->showBackFromStack(); return true; } return false; } void WrapWidget::showNewContent( not_null<ContentMemento*> memento, const Window::SectionShow ¶ms) { auto saveToStack = (_content != nullptr) && (params.way == Window::SectionShow::Way::Forward); auto needAnimation = (_content != nullptr) && (params.animated != anim::type::instant); auto animationParams = SectionSlideParams(); auto newController = createController( _controller->parentController(), memento); auto newContent = object_ptr<ContentWidget>(nullptr); if (needAnimation) { newContent = createContent(memento, newController.get()); animationParams.withTopBarShadow = hasTopBarShadow() && newContent->hasTopBarShadow(); animationParams.oldContentCache = grabForShowAnimation( animationParams); animationParams.withFade = (wrap() == Wrap::Layer); } if (saveToStack) { auto item = StackItem(); item.section = _content->createMemento(); //if (_anotherTabMemento) { // item.anotherTab = std::move(_anotherTabMemento); //} _historyStack.push_back(std::move(item)); } else if (params.way == Window::SectionShow::Way::ClearStack) { _historyStack.clear(); } _controller = std::move(newController); if (newContent) { setupTop(); showContent(std::move(newContent)); } else { showNewContent(memento); } if (animationParams) { if (Ui::InFocusChain(this)) { setFocus(); } showAnimated( saveToStack ? SlideDirection::FromRight : SlideDirection::FromLeft, animationParams); } } void WrapWidget::showNewContent(not_null<ContentMemento*> memento) { // Validates contentGeometry(). setupTop(); showContent(createContent(memento, _controller.get())); } // This was done for tabs support. // //void WrapWidget::setupTabs(Tab tab) { // _tab = tab; // if (_tab == Tab::None) { // _topTabs.destroy(); // _topTabsBackground.destroy(); // } else if (!_topTabs) { // createTabs(); // } else { // _topTabs->setActiveSection(static_cast<int>(tab)); // } //} void WrapWidget::resizeEvent(QResizeEvent *e) { // This was done for tabs support. // //if (_topTabs) { // _topTabs->resizeToWidth(width()); // _topTabsBackground->resize( // width(), // _topTabs->height() - st::lineWidth); //} if (_topBar) { _topBar->resizeToWidth(width()); } updateContentGeometry(); } void WrapWidget::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Escape) { if (hasStackHistory() || wrap() != Wrap::Layer) { _controller->showBackFromStack(); return; } } SectionWidget::keyPressEvent(e); } void WrapWidget::updateContentGeometry() { if (_content) { _topShadow->resizeToWidth(width()); _topShadow->moveToLeft(0, topWidget()->height()); _content->setGeometry(contentGeometry()); } } bool WrapWidget::wheelEventFromFloatPlayer(QEvent *e) { return _content->wheelEventFromFloatPlayer(e); } QRect WrapWidget::rectForFloatPlayer() const { return _content->rectForFloatPlayer(); } object_ptr<Ui::RpWidget> WrapWidget::createTopBarSurrogate( QWidget *parent) { if (hasStackHistory() || wrap() == Wrap::Narrow) { Assert(_topBar != nullptr); auto result = object_ptr<Ui::AbstractButton>(parent); result->addClickHandler([weak = make_weak(this)]{ if (weak) { weak->_controller->showBackFromStack(); } }); result->setGeometry(_topBar->geometry()); result->show(); return std::move(result); } return nullptr; } void WrapWidget::updateGeometry(QRect newGeometry, int additionalScroll) { auto scrollChanged = (_additionalScroll != additionalScroll); auto geometryChanged = (geometry() != newGeometry); auto shrinkingContent = (additionalScroll < _additionalScroll); _additionalScroll = additionalScroll; if (geometryChanged) { if (shrinkingContent) { setGeometry(newGeometry); } if (scrollChanged) { _content->applyAdditionalScroll(additionalScroll); } if (!shrinkingContent) { setGeometry(newGeometry); } } else if (scrollChanged) { _content->applyAdditionalScroll(additionalScroll); } } int WrapWidget::scrollTillBottom(int forHeight) const { return _content->scrollTillBottom(forHeight - topWidget()->height()); } rpl::producer<int> WrapWidget::scrollTillBottomChanges() const { return _scrollTillBottomChanges.events_starting_with( _content->scrollTillBottomChanges() ) | rpl::flatten_latest(); } WrapWidget::~WrapWidget() = default; } // namespace Info ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/info_wrap_widget.h����������������������������������������0000664�0000000�0000000�00000013244�13262451251�0024062�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/variable.h> #include <rpl/event_stream.h> #include "window/section_widget.h" namespace Storage { enum class SharedMediaType : signed char; } // namespace Storage namespace Data { class Feed; } // namespace Data namespace Ui { class SettingsSlider; class FadeShadow; class PlainShadow; class DropdownMenu; class IconButton; } // namespace Ui namespace Window { enum class SlideDirection; } // namespace Window namespace Info { namespace Profile { class Widget; } // namespace Profile namespace Media { class Widget; } // namespace Media class Key; class Controller; class Section; class Memento; class MoveMemento; class ContentMemento; class ContentWidget; class TopBar; enum class Wrap { Layer, Narrow, Side, }; struct SelectedItem { explicit SelectedItem(FullMsgId msgId) : msgId(msgId) { } FullMsgId msgId; bool canDelete = false; bool canForward = false; }; struct SelectedItems { explicit SelectedItems(Storage::SharedMediaType type) : type(type) { } Storage::SharedMediaType type; std::vector<SelectedItem> list; }; class WrapWidget final : public Window::SectionWidget { public: WrapWidget( QWidget *parent, not_null<Window::Controller*> window, Wrap wrap, not_null<Memento*> memento); Key key() const; Dialogs::RowDescriptor activeChat() const override; Wrap wrap() const { return _wrap.current(); } rpl::producer<Wrap> wrapValue() const; void setWrap(Wrap wrap); not_null<Controller*> controller() { return _controller.get(); } bool hasTopBarShadow() const override; QPixmap grabForShowAnimation( const Window::SectionSlideParams ¶ms) override; void forceContentRepaint(); bool showInternal( not_null<Window::SectionMemento*> memento, const Window::SectionShow ¶ms) override; bool showBackFromStackInternal(const Window::SectionShow ¶ms); std::unique_ptr<Window::SectionMemento> createMemento() override; rpl::producer<int> desiredHeightValue() const override; void updateInternalState(not_null<Memento*> memento); // Float player interface. bool wheelEventFromFloatPlayer(QEvent *e) override; QRect rectForFloatPlayer() const override; object_ptr<Ui::RpWidget> createTopBarSurrogate(QWidget *parent); void updateGeometry(QRect newGeometry, int additionalScroll); int scrollTillBottom(int forHeight) const; rpl::producer<int> scrollTillBottomChanges() const; ~WrapWidget(); protected: void resizeEvent(QResizeEvent *e) override; void keyPressEvent(QKeyEvent *e) override; void doSetInnerFocus() override; void showFinishedHook() override; void showAnimatedHook( const Window::SectionSlideParams ¶ms) override; private: using SlideDirection = Window::SlideDirection; using SectionSlideParams = Window::SectionSlideParams; //enum class Tab { // Profile, // Media, // None, //}; struct StackItem; void startInjectingActivePeerProfiles(); void injectActiveProfile(Dialogs::Key key); void injectActivePeerProfile(not_null<PeerData*> peer); void injectActiveFeedProfile(not_null<Data::Feed*> feed); void injectActiveProfileMemento( std::unique_ptr<ContentMemento> memento); void restoreHistoryStack( std::vector<std::unique_ptr<ContentMemento>> stack); bool hasStackHistory() const { return !_historyStack.empty(); } void showNewContent(not_null<ContentMemento*> memento); void showNewContent( not_null<ContentMemento*> memento, const Window::SectionShow ¶ms); bool returnToFirstStackFrame( not_null<ContentMemento*> memento, const Window::SectionShow ¶ms); void setupTop(); //void setupTabbedTop(); //void setupTabs(Tab tab); //void createTabs(); void createTopBar(); void highlightTopBar(); not_null<RpWidget*> topWidget() const; QRect contentGeometry() const; rpl::producer<int> desiredHeightForContent() const; void finishShowContent(); rpl::producer<bool> topShadowToggledValue() const; void updateContentGeometry(); //void showTab(Tab tab); void showContent(object_ptr<ContentWidget> content); //std::unique_ptr<ContentMemento> createTabMemento(Tab tab); object_ptr<ContentWidget> createContent( not_null<ContentMemento*> memento, not_null<Controller*> controller); std::unique_ptr<Controller> createController( not_null<Window::Controller*> window, not_null<ContentMemento*> memento); //void convertProfileFromStackToTab(); rpl::producer<SelectedItems> selectedListValue() const; bool requireTopBarSearch() const; void addProfileMenuButton(); void addProfileCallsButton(); void addProfileNotificationsButton(); void showProfileMenu(); rpl::variable<Wrap> _wrap; std::unique_ptr<Controller> _controller; object_ptr<ContentWidget> _content = { nullptr }; int _additionalScroll = 0; //object_ptr<Ui::PlainShadow> _topTabsBackground = { nullptr }; //object_ptr<Ui::SettingsSlider> _topTabs = { nullptr }; object_ptr<TopBar> _topBar = { nullptr }; object_ptr<Ui::RpWidget> _topBarSurrogate = { nullptr }; Animation _topBarOverrideAnimation; bool _topBarOverrideShown = false; object_ptr<Ui::FadeShadow> _topShadow; base::unique_qptr<Ui::IconButton> _topBarMenuToggle; base::unique_qptr<Ui::DropdownMenu> _topBarMenu; // Tab _tab = Tab::Profile; // std::unique_ptr<ContentMemento> _anotherTabMemento; std::vector<StackItem> _historyStack; rpl::event_stream<rpl::producer<int>> _desiredHeights; rpl::event_stream<rpl::producer<bool>> _desiredShadowVisibilities; rpl::event_stream<rpl::producer<SelectedItems>> _selectedLists; rpl::event_stream<rpl::producer<int>> _scrollTillBottomChanges; }; } // namespace Info ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/media/����������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0021435�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/media/info_media_buttons.h��������������������������������0000664�0000000�0000000�00000006042�13262451251�0025460�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/mappers.h> #include <rpl/map.h> #include "lang/lang_keys.h" #include "storage/storage_shared_media.h" #include "info/info_memento.h" #include "info/info_controller.h" #include "info/profile/info_profile_button.h" #include "info/profile/info_profile_values.h" #include "ui/wrap/slide_wrap.h" #include "ui/wrap/vertical_layout.h" #include "window/window_controller.h" #include "styles/style_info.h" namespace Info { namespace Media { using Type = Storage::SharedMediaType; inline auto MediaTextPhrase(Type type) { switch (type) { case Type::Photo: return lng_profile_photos; case Type::Video: return lng_profile_videos; case Type::File: return lng_profile_files; case Type::MusicFile: return lng_profile_songs; case Type::Link: return lng_profile_shared_links; case Type::VoiceFile: return lng_profile_audios; // case Type::RoundFile: return lng_profile_rounds; } Unexpected("Type in MediaTextPhrase()"); }; inline auto MediaText(Type type) { return [phrase = MediaTextPhrase(type)](int count) { return phrase(lt_count, count); }; } template <typename Count, typename Text> inline auto AddCountedButton( Ui::VerticalLayout *parent, Count &&count, Text &&textFromCount, Ui::MultiSlideTracker &tracker) { using namespace rpl::mappers; using Button = Profile::Button; auto forked = std::move(count) | start_spawning(parent->lifetime()); auto text = rpl::duplicate( forked ) | rpl::map([textFromCount](int count) { return (count > 0) ? textFromCount(count) : QString(); }); auto button = parent->add(object_ptr<Ui::SlideWrap<Button>>( parent, object_ptr<Button>( parent, std::move(text), st::infoSharedMediaButton)) )->setDuration( st::infoSlideDuration )->toggleOn( rpl::duplicate(forked) | rpl::map(_1 > 0) ); tracker.track(button); return button; }; inline auto AddButton( Ui::VerticalLayout *parent, not_null<Window::Navigation*> navigation, not_null<PeerData*> peer, PeerData *migrated, Type type, Ui::MultiSlideTracker &tracker) { auto result = AddCountedButton( parent, Profile::SharedMediaCountValue(peer, migrated, type), MediaText(type), tracker)->entity(); result->addClickHandler([=] { navigation->showSection( Info::Memento(peer->id, Section(type))); }); return std::move(result); }; inline auto AddCommonGroupsButton( Ui::VerticalLayout *parent, not_null<Window::Navigation*> navigation, not_null<UserData*> user, Ui::MultiSlideTracker &tracker) { auto result = AddCountedButton( parent, Profile::CommonGroupsCountValue(user), [](int count) { return lng_profile_common_groups(lt_count, count); }, tracker)->entity(); result->addClickHandler([=] { navigation->showSection( Info::Memento(user->id, Section::Type::CommonGroups)); }); return std::move(result); }; } // namespace Media } // namespace Info����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/media/info_media_empty_widget.cpp�������������������������0000664�0000000�0000000�00000005224�13262451251�0027017�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/media/info_media_empty_widget.h" #include "ui/widgets/labels.h" #include "styles/style_info.h" #include "lang/lang_keys.h" namespace Info { namespace Media { EmptyWidget::EmptyWidget(QWidget *parent) : RpWidget(parent) , _text(this, st::infoEmptyLabel) { } void EmptyWidget::setFullHeight(rpl::producer<int> fullHeightValue) { std::move( fullHeightValue ) | rpl::start_with_next([this](int fullHeight) { // Make icon center be on 1/3 height. auto iconCenter = fullHeight / 3; auto iconHeight = st::infoEmptyFile.height(); auto iconTop = iconCenter - iconHeight / 2; _height = iconTop + st::infoEmptyIconTop; resizeToWidth(width()); }, lifetime()); } void EmptyWidget::setType(Type type) { _type = type; _icon = [&] { switch (_type) { case Type::Photo: return &st::infoEmptyPhoto; case Type::Video: return &st::infoEmptyVideo; case Type::MusicFile: return &st::infoEmptyAudio; case Type::File: return &st::infoEmptyFile; case Type::Link: return &st::infoEmptyLink; case Type::VoiceFile: return &st::infoEmptyVoice; } Unexpected("Bad type in EmptyWidget::setType()"); }(); update(); } void EmptyWidget::setSearchQuery(const QString &query) { auto key = [&] { switch (_type) { case Type::Photo: return lng_media_photo_empty; case Type::Video: return lng_media_video_empty; case Type::MusicFile: return query.isEmpty() ? lng_media_song_empty : lng_media_song_empty_search; case Type::File: return query.isEmpty() ? lng_media_file_empty : lng_media_file_empty_search; case Type::Link: return query.isEmpty() ? lng_media_link_empty : lng_media_link_empty_search; case Type::VoiceFile: return lng_media_audio_empty; } Unexpected("Bad type in EmptyWidget::setSearchQuery()"); }(); _text->setText(lang(key)); resizeToWidth(width()); } void EmptyWidget::paintEvent(QPaintEvent *e) { if (!_icon) { return; } Painter p(this); auto iconLeft = (width() - _icon->width()) / 2; auto iconTop = height() - st::infoEmptyIconTop; _icon->paint(p, iconLeft, iconTop, width()); } int EmptyWidget::resizeGetHeight(int newWidth) { auto labelTop = _height - st::infoEmptyLabelTop; auto labelWidth = newWidth - 2 * st::infoEmptyLabelSkip; _text->resizeToNaturalWidth(labelWidth); auto labelLeft = (newWidth - _text->width()) / 2; _text->moveToLeft(labelLeft, labelTop, newWidth); update(); return _height; } } // namespace Media } // namespace Info ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/media/info_media_empty_widget.h���������������������������0000664�0000000�0000000�00000001575�13262451251�0026471�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/rp_widget.h" #include "info/media/info_media_widget.h" namespace Ui { class FlatLabel; } // namespace Ui namespace Info { namespace Media { class EmptyWidget : public Ui::RpWidget { public: EmptyWidget(QWidget *parent); void setFullHeight(rpl::producer<int> fullHeightValue); void setType(Type type); void setSearchQuery(const QString &query); protected: void paintEvent(QPaintEvent *e) override; int resizeGetHeight(int newWidth) override; private: object_ptr<Ui::FlatLabel> _text; Type _type = Type::kCount; const style::icon *_icon = nullptr; int _height = 0; }; } // namespace Media } // namespace Info �����������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/media/info_media_inner_widget.cpp�������������������������0000664�0000000�0000000�00000022233�13262451251�0026773�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/media/info_media_inner_widget.h" #include <rpl/flatten_latest.h> #include "boxes/abstract_box.h" #include "info/media/info_media_list_widget.h" #include "info/media/info_media_buttons.h" #include "info/media/info_media_empty_widget.h" #include "info/profile/info_profile_button.h" #include "info/profile/info_profile_icon.h" #include "info/info_controller.h" #include "ui/widgets/discrete_sliders.h" #include "ui/widgets/shadow.h" #include "ui/wrap/vertical_layout.h" #include "ui/search_field_controller.h" #include "styles/style_info.h" #include "lang/lang_keys.h" namespace Info { namespace Media { InnerWidget::InnerWidget( QWidget *parent, not_null<Controller*> controller) : RpWidget(parent) , _controller(controller) , _empty(this) { _empty->heightValue( ) | rpl::start_with_next( [this] { refreshHeight(); }, _empty->lifetime()); _list = setupList(); } // Allows showing additional shared media links and tabs. // Was done for top level tabs support. // Now used for shared media in Saved Messages. void InnerWidget::setupOtherTypes() { if (_controller->key().peer()->isSelf() && _isStackBottom) { createOtherTypes(); } else { _otherTypes.destroy(); refreshHeight(); } //rpl::combine( // _controller->wrapValue(), // _controller->searchEnabledByContent() //) | rpl::start_with_next([this](Wrap wrap, bool enabled) { // _searchEnabled = enabled; // refreshSearchField(); //}, lifetime()); } void InnerWidget::createOtherTypes() { //_otherTabsShadow.create(this); //_otherTabsShadow->show(); //_otherTabs = nullptr; _otherTypes.create(this); _otherTypes->show(); createTypeButtons(); _otherTypes->add(object_ptr<BoxContentDivider>(_otherTypes)); //createTabs(); _otherTypes->resizeToWidth(width()); _otherTypes->heightValue( ) | rpl::start_with_next( [this] { refreshHeight(); }, _otherTypes->lifetime()); } void InnerWidget::createTypeButtons() { auto wrap = _otherTypes->add(object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>( _otherTypes, object_ptr<Ui::VerticalLayout>(_otherTypes))); auto content = wrap->entity(); content->add(object_ptr<Ui::FixedHeightWidget>( content, st::infoProfileSkip)); auto tracker = Ui::MultiSlideTracker(); auto addMediaButton = [&]( Type buttonType, const style::icon &icon) { if (buttonType == type()) { return; } auto result = AddButton( content, _controller, _controller->key().peer(), _controller->migrated(), buttonType, tracker); object_ptr<Profile::FloatingIcon>( result, icon, st::infoSharedMediaButtonIconPosition)->show(); }; //auto addCommonGroupsButton = [&]( // not_null<UserData*> user, // const style::icon &icon) { // auto result = AddCommonGroupsButton( // content, // _controller, // user, // tracker); // object_ptr<Profile::FloatingIcon>( // result, // icon, // st::infoSharedMediaButtonIconPosition)->show(); //}; addMediaButton(Type::Photo, st::infoIconMediaPhoto); addMediaButton(Type::Video, st::infoIconMediaVideo); addMediaButton(Type::File, st::infoIconMediaFile); addMediaButton(Type::MusicFile, st::infoIconMediaAudio); addMediaButton(Type::Link, st::infoIconMediaLink); if (auto user = _controller->key().peer()->asUser()) { // addCommonGroupsButton(user, st::infoIconMediaGroup); } addMediaButton(Type::VoiceFile, st::infoIconMediaVoice); // addMediaButton(Type::RoundFile, st::infoIconMediaRound); content->add(object_ptr<Ui::FixedHeightWidget>( content, st::infoProfileSkip)); wrap->toggleOn(tracker.atLeastOneShownValue()); wrap->finishAnimating(); } //void InnerWidget::createTabs() { // _otherTabs = _otherTypes->add(object_ptr<Ui::SettingsSlider>( // this, // st::infoTabs)); // auto sections = QStringList(); // sections.push_back(lang(lng_media_type_photos).toUpper()); // sections.push_back(lang(lng_media_type_videos).toUpper()); // sections.push_back(lang(lng_media_type_files).toUpper()); // _otherTabs->setSections(sections); // _otherTabs->setActiveSection(*TypeToTabIndex(type())); // _otherTabs->finishAnimating(); // // _otherTabs->sectionActivated( // ) | rpl::map([](int index) { // return TabIndexToType(index); // }) | rpl::start_with_next( // [this](Type newType) { // if (type() != newType) { // switchToTab(Memento( // _controller->peerId(), // _controller->migratedPeerId(), // newType)); // } // }, // _otherTabs->lifetime()); //} Type InnerWidget::type() const { return _controller->section().mediaType(); } void InnerWidget::visibleTopBottomUpdated( int visibleTop, int visibleBottom) { setChildVisibleTopBottom(_list, visibleTop, visibleBottom); } bool InnerWidget::showInternal(not_null<Memento*> memento) { if (!_controller->validateMementoPeer(memento)) { return false; } auto mementoType = memento->section().mediaType(); if (mementoType == type()) { restoreState(memento); return true; // Allows showing additional shared media links and tabs. // Was done for top level tabs support. // //} else if (_otherTypes) { // if (TypeToTabIndex(mementoType)) { // switchToTab(std::move(*memento)); // return true; // } } return false; } // Allows showing additional shared media links and tabs. // Was done for top level tabs support. // //void InnerWidget::switchToTab(Memento &&memento) { // // Save state of the tab before setSection() call. // _controller->setSection(&memento); // _list = setupList(); // restoreState(&memento); // _list->show(); // _list->resizeToWidth(width()); // refreshHeight(); // if (_otherTypes) { // _otherTabsShadow->raise(); // _otherTypes->raise(); // _otherTabs->setActiveSection(*TypeToTabIndex(type())); // } //} // //void InnerWidget::refreshSearchField() { // auto search = _controller->searchFieldController(); // if (search && _otherTabs && _searchEnabled) { // _searchField = search->createRowView( // this, // st::infoMediaSearch); // _searchField->resizeToWidth(width()); // _searchField->show(); // search->queryChanges( // ) | rpl::start_with_next([this] { // scrollToSearchField(); // }, _searchField->lifetime()); // } else { // _searchField = nullptr; // } //} // //void InnerWidget::scrollToSearchField() { // Expects(_searchField != nullptr); // // auto top = _searchField->y(); // auto bottom = top + _searchField->height(); // _scrollToRequests.fire({ top, bottom }); //} object_ptr<ListWidget> InnerWidget::setupList() { auto result = object_ptr<ListWidget>( this, _controller); result->heightValue( ) | rpl::start_with_next( [this] { refreshHeight(); }, result->lifetime()); using namespace rpl::mappers; result->scrollToRequests( ) | rpl::map([widget = result.data()](int to) { return Ui::ScrollToRequest { widget->y() + to, -1 }; }) | rpl::start_to_stream( _scrollToRequests, result->lifetime()); _selectedLists.fire(result->selectedListValue()); _listTops.fire(result->topValue()); _empty->setType(_controller->section().mediaType()); _controller->mediaSourceQueryValue( ) | rpl::start_with_next([this](const QString &query) { _empty->setSearchQuery(query); }, result->lifetime()); return result; } void InnerWidget::saveState(not_null<Memento*> memento) { _list->saveState(memento); } void InnerWidget::restoreState(not_null<Memento*> memento) { _list->restoreState(memento); } rpl::producer<SelectedItems> InnerWidget::selectedListValue() const { return _selectedLists.events_starting_with( _list->selectedListValue() ) | rpl::flatten_latest(); } void InnerWidget::cancelSelection() { _list->cancelSelection(); } InnerWidget::~InnerWidget() = default; int InnerWidget::resizeGetHeight(int newWidth) { _inResize = true; auto guard = gsl::finally([this] { _inResize = false; }); if (_otherTypes) { _otherTypes->resizeToWidth(newWidth); //_otherTabsShadow->resizeToWidth(newWidth); } //if (_searchField) { // _searchField->resizeToWidth(newWidth); //} _list->resizeToWidth(newWidth); _empty->resizeToWidth(newWidth); return recountHeight(); } void InnerWidget::refreshHeight() { if (_inResize) { return; } resize(width(), recountHeight()); } int InnerWidget::recountHeight() { auto top = 0; if (_otherTypes) { _otherTypes->moveToLeft(0, top); top += _otherTypes->heightNoMargins() - st::lineWidth; // _otherTabsShadow->moveToLeft(0, top); } //if (_searchField) { // _searchField->moveToLeft(0, top); // top += _searchField->heightNoMargins() - st::lineWidth; //} auto listHeight = 0; if (_list) { _list->moveToLeft(0, top); listHeight = _list->heightNoMargins(); top += listHeight; } if (listHeight > 0) { _empty->hide(); } else { _empty->show(); _empty->moveToLeft(0, top); top += _empty->heightNoMargins(); } return top; } void InnerWidget::setScrollHeightValue(rpl::producer<int> value) { using namespace rpl::mappers; _empty->setFullHeight(rpl::combine( std::move(value), _listTops.events_starting_with( _list->topValue() ) | rpl::flatten_latest(), _1 - _2)); } rpl::producer<Ui::ScrollToRequest> InnerWidget::scrollToRequests() const { return _scrollToRequests.events(); } } // namespace Media } // namespace Info ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/media/info_media_inner_widget.h���������������������������0000664�0000000�0000000�00000005054�13262451251�0026442�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/rp_widget.h" #include "ui/widgets/scroll_area.h" #include "base/unique_qptr.h" #include "info/media/info_media_widget.h" #include "info/media/info_media_list_widget.h" namespace Ui { class SettingsSlider; class VerticalLayout; class SearchFieldController; } // namespace Ui namespace Info { class Controller; namespace Media { class Memento; class ListWidget; class EmptyWidget; class InnerWidget final : public Ui::RpWidget { public: InnerWidget( QWidget *parent, not_null<Controller*> controller); bool showInternal(not_null<Memento*> memento); void setIsStackBottom(bool isStackBottom) { _isStackBottom = isStackBottom; setupOtherTypes(); } void saveState(not_null<Memento*> memento); void restoreState(not_null<Memento*> memento); void setScrollHeightValue(rpl::producer<int> value); rpl::producer<Ui::ScrollToRequest> scrollToRequests() const; rpl::producer<SelectedItems> selectedListValue() const; void cancelSelection(); ~InnerWidget(); protected: int resizeGetHeight(int newWidth) override; void visibleTopBottomUpdated( int visibleTop, int visibleBottom) override; private: int recountHeight(); void refreshHeight(); // Allows showing additional shared media links and tabs. // Was done for top level tabs support. // Now used for shared media in Saved Messages. void setupOtherTypes(); void createOtherTypes(); void createTypeButtons(); // Allows showing additional shared media links and tabs. // Was done for top level tabs support. // //void createTabs(); //void switchToTab(Memento &&memento); //void refreshSearchField(); //void scrollToSearchField(); Type type() const; object_ptr<ListWidget> setupList(); const not_null<Controller*> _controller; //Ui::SettingsSlider *_otherTabs = nullptr; object_ptr<Ui::VerticalLayout> _otherTypes = { nullptr }; //object_ptr<Ui::PlainShadow> _otherTabsShadow = { nullptr }; //base::unique_qptr<Ui::RpWidget> _searchField = nullptr; object_ptr<ListWidget> _list = { nullptr }; object_ptr<EmptyWidget> _empty; //bool _searchEnabled = false; bool _inResize = false; bool _isStackBottom = false; rpl::event_stream<Ui::ScrollToRequest> _scrollToRequests; rpl::event_stream<rpl::producer<SelectedItems>> _selectedLists; rpl::event_stream<rpl::producer<int>> _listTops; }; } // namespace Media } // namespace Info ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/media/info_media_list_widget.cpp��������������������������0000664�0000000�0000000�00000157021�13262451251�0026637�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/media/info_media_list_widget.h" #include "info/info_controller.h" #include "overview/overview_layout.h" #include "data/data_media_types.h" #include "data/data_photo.h" #include "data/data_document.h" #include "data/data_session.h" #include "history/history_item.h" #include "history/history.h" #include "history/view/history_view_cursor_state.h" #include "window/themes/window_theme.h" #include "window/window_controller.h" #include "window/window_peer_menu.h" #include "storage/file_download.h" #include "ui/widgets/popup_menu.h" #include "lang/lang_keys.h" #include "auth_session.h" #include "mainwidget.h" #include "window/main_window.h" #include "styles/style_overview.h" #include "styles/style_info.h" #include "media/player/media_player_instance.h" #include "boxes/peer_list_controllers.h" #include "boxes/confirm_box.h" #include "core/file_utilities.h" namespace Layout = Overview::Layout; namespace Info { namespace Media { namespace { constexpr auto kPreloadedScreensCount = 4; constexpr auto kPreloadIfLessThanScreens = 2; constexpr auto kPreloadedScreensCountFull = kPreloadedScreensCount + 1 + kPreloadedScreensCount; constexpr auto kMediaCountForSearch = 10; UniversalMsgId GetUniversalId(FullMsgId itemId) { return (itemId.channel != 0) ? UniversalMsgId(itemId.msg) : UniversalMsgId(itemId.msg - ServerMaxMsgId); } UniversalMsgId GetUniversalId(not_null<const HistoryItem*> item) { return GetUniversalId(item->fullId()); } UniversalMsgId GetUniversalId(not_null<const BaseLayout*> layout) { return GetUniversalId(layout->getItem()->fullId()); } } // namespace struct ListWidget::Context { Layout::PaintContext layoutContext; not_null<SelectedMap*> selected; not_null<SelectedMap*> dragSelected; DragSelectAction dragSelectAction; }; class ListWidget::Section { public: Section(Type type) : _type(type) { } bool addItem(not_null<BaseLayout*> item); bool empty() const { return _items.empty(); } UniversalMsgId minId() const { Expects(!empty()); return _items.back().first; } UniversalMsgId maxId() const { Expects(!empty()); return _items.front().first; } void setTop(int top) { _top = top; } int top() const { return _top; } void resizeToWidth(int newWidth); int height() const { return _height; } int bottom() const { return top() + height(); } bool removeItem(UniversalMsgId universalId); FoundItem findItemNearId(UniversalMsgId universalId) const; FoundItem findItemByPoint(QPoint point) const; void paint( Painter &p, const Context &context, QRect clip, int outerWidth) const; static int MinItemHeight(Type type, int width); private: using Items = base::flat_map< UniversalMsgId, not_null<BaseLayout*>, std::greater<>>; int headerHeight() const; void appendItem(not_null<BaseLayout*> item); void setHeader(not_null<BaseLayout*> item); bool belongsHere(not_null<BaseLayout*> item) const; Items::iterator findItemAfterTop(int top); Items::const_iterator findItemAfterTop(int top) const; Items::const_iterator findItemAfterBottom( Items::const_iterator from, int bottom) const; QRect findItemRect(not_null<const BaseLayout*> item) const; FoundItem completeResult( not_null<BaseLayout*> item, bool exact) const; TextSelection itemSelection( not_null<const BaseLayout*> item, const Context &context) const; int recountHeight() const; void refreshHeight(); Type _type = Type::Photo; Text _header; Items _items; int _itemsLeft = 0; int _itemsTop = 0; int _itemWidth = 0; int _itemsInRow = 1; mutable int _rowsCount = 0; int _top = 0; int _height = 0; }; bool ListWidget::IsAfter( const MouseState &a, const MouseState &b) { if (a.itemId != b.itemId) { return (a.itemId < b.itemId); } auto xAfter = a.cursor.x() - b.cursor.x(); auto yAfter = a.cursor.y() - b.cursor.y(); return (xAfter + yAfter >= 0); } bool ListWidget::SkipSelectFromItem(const MouseState &state) { if (state.cursor.y() >= state.size.height() || state.cursor.x() >= state.size.width()) { return true; } return false; } bool ListWidget::SkipSelectTillItem(const MouseState &state) { if (state.cursor.x() < 0 || state.cursor.y() < 0) { return true; } return false; } ListWidget::CachedItem::CachedItem(std::unique_ptr<BaseLayout> item) : item(std::move(item)) { } ListWidget::CachedItem::~CachedItem() = default; bool ListWidget::Section::addItem(not_null<BaseLayout*> item) { if (_items.empty() || belongsHere(item)) { if (_items.empty()) setHeader(item); appendItem(item); return true; } return false; } void ListWidget::Section::setHeader(not_null<BaseLayout*> item) { auto text = [&] { auto date = item->dateTime().date(); switch (_type) { case Type::Photo: case Type::Video: case Type::RoundFile: case Type::VoiceFile: case Type::File: return langMonthFull(date); case Type::Link: return langDayOfMonthFull(date); case Type::MusicFile: return QString(); } Unexpected("Type in ListWidget::Section::setHeader()"); }(); _header.setText(st::infoMediaHeaderStyle, text); } bool ListWidget::Section::belongsHere( not_null<BaseLayout*> item) const { Expects(!_items.empty()); auto date = item->dateTime().date(); auto myDate = _items.back().second->dateTime().date(); switch (_type) { case Type::Photo: case Type::Video: case Type::RoundFile: case Type::VoiceFile: case Type::File: return date.year() == myDate.year() && date.month() == myDate.month(); case Type::Link: return date.year() == myDate.year() && date.month() == myDate.month() && date.day() == myDate.day(); case Type::MusicFile: return true; } Unexpected("Type in ListWidget::Section::belongsHere()"); } void ListWidget::Section::appendItem(not_null<BaseLayout*> item) { _items.emplace(GetUniversalId(item), item); } bool ListWidget::Section::removeItem(UniversalMsgId universalId) { if (auto it = _items.find(universalId); it != _items.end()) { it = _items.erase(it); refreshHeight(); return true; } return false; } QRect ListWidget::Section::findItemRect( not_null<const BaseLayout*> item) const { auto position = item->position(); auto top = position / _itemsInRow; auto indexInRow = position % _itemsInRow; auto left = _itemsLeft + indexInRow * (_itemWidth + st::infoMediaSkip); return QRect(left, top, _itemWidth, item->height()); } auto ListWidget::Section::completeResult( not_null<BaseLayout*> item, bool exact) const -> FoundItem { return { item, findItemRect(item), exact }; } auto ListWidget::Section::findItemByPoint( QPoint point) const -> FoundItem { Expects(!_items.empty()); auto itemIt = findItemAfterTop(point.y()); if (itemIt == _items.end()) { --itemIt; } auto item = itemIt->second; auto rect = findItemRect(item); if (point.y() >= rect.top()) { auto shift = floorclamp( point.x(), (_itemWidth + st::infoMediaSkip), 0, _itemsInRow); while (shift-- && itemIt != _items.end()) { ++itemIt; } if (itemIt == _items.end()) { --itemIt; } item = itemIt->second; rect = findItemRect(item); } return { item, rect, rect.contains(point) }; } auto ListWidget::Section::findItemNearId( UniversalMsgId universalId) const -> FoundItem { Expects(!_items.empty()); auto itemIt = ranges::lower_bound( _items, universalId, std::greater<>(), [](const auto &item) -> UniversalMsgId { return item.first; }); if (itemIt == _items.end()) { --itemIt; } auto item = itemIt->second; auto exact = (GetUniversalId(item) == universalId); return { item, findItemRect(item), exact }; } auto ListWidget::Section::findItemAfterTop( int top) -> Items::iterator { return ranges::lower_bound( _items, top, std::less_equal<>(), [this](const auto &item) { auto itemTop = item.second->position() / _itemsInRow; return itemTop + item.second->height(); }); } auto ListWidget::Section::findItemAfterTop( int top) const -> Items::const_iterator { return ranges::lower_bound( _items, top, std::less_equal<>(), [this](const auto &item) { auto itemTop = item.second->position() / _itemsInRow; return itemTop + item.second->height(); }); } auto ListWidget::Section::findItemAfterBottom( Items::const_iterator from, int bottom) const -> Items::const_iterator { return ranges::lower_bound( from, _items.end(), bottom, std::less<>(), [this](const auto &item) { auto itemTop = item.second->position() / _itemsInRow; return itemTop; }); } void ListWidget::Section::paint( Painter &p, const Context &context, QRect clip, int outerWidth) const { auto baseIndex = 0; auto header = headerHeight(); if (QRect(0, 0, outerWidth, header).intersects(clip)) { p.setPen(st::infoMediaHeaderFg); _header.drawLeftElided( p, st::infoMediaHeaderPosition.x(), st::infoMediaHeaderPosition.y(), outerWidth - 2 * st::infoMediaHeaderPosition.x(), outerWidth); } auto top = header + _itemsTop; auto fromcol = floorclamp( clip.x() - _itemsLeft, _itemWidth, 0, _itemsInRow); auto tillcol = ceilclamp( clip.x() + clip.width() - _itemsLeft, _itemWidth, 0, _itemsInRow); auto localContext = context.layoutContext; localContext.isAfterDate = (header > 0); auto fromIt = findItemAfterTop(clip.y()); auto tillIt = findItemAfterBottom( fromIt, clip.y() + clip.height()); for (auto it = fromIt; it != tillIt; ++it) { auto item = it->second; auto rect = findItemRect(item); localContext.isAfterDate = (header > 0) && (rect.y() <= header + _itemsTop); if (rect.intersects(clip)) { p.translate(rect.topLeft()); item->paint( p, clip.translated(-rect.topLeft()), itemSelection(item, context), &localContext); p.translate(-rect.topLeft()); } } } TextSelection ListWidget::Section::itemSelection( not_null<const BaseLayout*> item, const Context &context) const { auto universalId = GetUniversalId(item); auto dragSelectAction = context.dragSelectAction; if (dragSelectAction != DragSelectAction::None) { auto i = context.dragSelected->find(universalId); if (i != context.dragSelected->end()) { return (dragSelectAction == DragSelectAction::Selecting) ? FullSelection : TextSelection(); } } auto i = context.selected->find(universalId); return (i == context.selected->cend()) ? TextSelection() : i->second.text; } int ListWidget::Section::headerHeight() const { return _header.isEmpty() ? 0 : st::infoMediaHeaderHeight; } void ListWidget::Section::resizeToWidth(int newWidth) { auto minWidth = st::infoMediaMinGridSize + st::infoMediaSkip * 2; if (newWidth < minWidth) { return; } auto resizeOneColumn = [&](int itemsLeft, int itemWidth) { _itemsLeft = itemsLeft; _itemsTop = 0; _itemsInRow = 1; _itemWidth = itemWidth; for (auto &item : _items) { item.second->resizeGetHeight(_itemWidth); } }; switch (_type) { case Type::Photo: case Type::Video: case Type::RoundFile: { _itemsLeft = st::infoMediaSkip; _itemsTop = st::infoMediaSkip; _itemsInRow = (newWidth - _itemsLeft) / (st::infoMediaMinGridSize + st::infoMediaSkip); _itemWidth = ((newWidth - _itemsLeft) / _itemsInRow) - st::infoMediaSkip; for (auto &item : _items) { item.second->resizeGetHeight(_itemWidth); } } break; case Type::VoiceFile: case Type::MusicFile: resizeOneColumn(0, newWidth); break; case Type::File: case Type::Link: { auto itemsLeft = st::infoMediaHeaderPosition.x(); auto itemWidth = newWidth - 2 * itemsLeft; resizeOneColumn(itemsLeft, itemWidth); } break; } refreshHeight(); } int ListWidget::Section::MinItemHeight(Type type, int width) { auto &songSt = st::overviewFileLayout; switch (type) { case Type::Photo: case Type::Video: case Type::RoundFile: { auto itemsLeft = st::infoMediaSkip; auto itemsInRow = (width - itemsLeft) / (st::infoMediaMinGridSize + st::infoMediaSkip); return (st::infoMediaMinGridSize + st::infoMediaSkip) / itemsInRow; } break; case Type::VoiceFile: return songSt.songPadding.top() + songSt.songThumbSize + songSt.songPadding.bottom() + st::lineWidth; case Type::File: return songSt.filePadding.top() + songSt.fileThumbSize + songSt.filePadding.bottom() + st::lineWidth; case Type::MusicFile: return songSt.songPadding.top() + songSt.songThumbSize + songSt.songPadding.bottom(); case Type::Link: return st::linksPhotoSize + st::linksMargin.top() + st::linksMargin.bottom() + st::linksBorder; } Unexpected("Type in ListWidget::Section::MinItemHeight()"); } int ListWidget::Section::recountHeight() const { auto result = headerHeight(); switch (_type) { case Type::Photo: case Type::Video: case Type::RoundFile: { auto itemHeight = _itemWidth + st::infoMediaSkip; auto index = 0; result += _itemsTop; for (auto &item : _items) { item.second->setPosition(_itemsInRow * result + index); if (++index == _itemsInRow) { result += itemHeight; index = 0; } } if (_items.size() % _itemsInRow) { _rowsCount = int(_items.size()) / _itemsInRow + 1; result += itemHeight; } else { _rowsCount = int(_items.size()) / _itemsInRow; } } break; case Type::VoiceFile: case Type::File: case Type::MusicFile: case Type::Link: for (auto &item : _items) { item.second->setPosition(result); result += item.second->height(); } _rowsCount = _items.size(); break; } return result; } void ListWidget::Section::refreshHeight() { _height = recountHeight(); } ListWidget::ListWidget( QWidget *parent, not_null<AbstractController*> controller) : RpWidget(parent) , _controller(controller) , _peer(_controller->key().peer()) , _migrated(_controller->migrated()) , _type(_controller->section().mediaType()) , _slice(sliceKey(_universalAroundId)) { setMouseTracking(true); start(); } void ListWidget::start() { _controller->setSearchEnabledByContent(false); ObservableViewer( *Window::Theme::Background() ) | rpl::start_with_next([this](const auto &update) { if (update.paletteChanged()) { invalidatePaletteCache(); } }, lifetime()); ObservableViewer( Auth().downloader().taskFinished() ) | rpl::start_with_next([this] { update(); }, lifetime()); Auth().data().itemLayoutChanged( ) | rpl::start_with_next([this](auto item) { itemLayoutChanged(item); }, lifetime()); Auth().data().itemRemoved( ) | rpl::start_with_next([this](auto item) { itemRemoved(item); }, lifetime()); Auth().data().itemRepaintRequest( ) | rpl::start_with_next([this](auto item) { repaintItem(item); }, lifetime()); _controller->mediaSourceQueryValue( ) | rpl::start_with_next([this]{ restart(); }, lifetime()); } rpl::producer<int> ListWidget::scrollToRequests() const { return _scrollToRequests.events(); } rpl::producer<SelectedItems> ListWidget::selectedListValue() const { return _selectedListStream.events_starting_with( collectSelectedItems()); } QRect ListWidget::getCurrentSongGeometry() { const auto type = AudioMsgId::Type::Song; const auto current = ::Media::Player::instance()->current(type); const auto fullMsgId = current.contextId(); if (fullMsgId && isPossiblyMyId(fullMsgId)) { if (const auto item = findItemById(GetUniversalId(fullMsgId))) { return item->geometry; } } return QRect(0, 0, width(), 0); } void ListWidget::restart() { mouseActionCancel(); _overLayout = nullptr; _sections.clear(); _layouts.clear(); _universalAroundId = kDefaultAroundId; _idsLimit = kMinimalIdsLimit; _slice = SparseIdsMergedSlice(sliceKey(_universalAroundId)); refreshViewer(); } void ListWidget::itemRemoved(not_null<const HistoryItem*> item) { if (isMyItem(item)) { auto universalId = GetUniversalId(item); auto sectionIt = findSectionByItem(universalId); if (sectionIt != _sections.end()) { if (sectionIt->removeItem(universalId)) { auto top = sectionIt->top(); if (sectionIt->empty()) { _sections.erase(sectionIt); } refreshHeight(); } } if (isItemLayout(item, _overLayout)) { _overLayout = nullptr; } _layouts.erase(universalId); _dragSelected.remove(universalId); auto i = _selected.find(universalId); if (i != _selected.cend()) { removeItemSelection(i); } mouseActionUpdate(_mousePosition); } } FullMsgId ListWidget::computeFullId( UniversalMsgId universalId) const { Expects(universalId != 0); auto peerChannel = [&] { return _peer->isChannel() ? _peer->bareId() : NoChannel; }; return (universalId > 0) ? FullMsgId(peerChannel(), universalId) : FullMsgId(NoChannel, ServerMaxMsgId + universalId); } auto ListWidget::collectSelectedItems() const -> SelectedItems { auto convert = [&]( UniversalMsgId universalId, const SelectionData &selection) { auto result = SelectedItem(computeFullId(universalId)); result.canDelete = selection.canDelete; result.canForward = selection.canForward; return result; }; auto transformation = [&](const auto &item) { return convert(item.first, item.second); }; auto items = SelectedItems(_type); if (hasSelectedItems()) { items.list.reserve(_selected.size()); std::transform( _selected.begin(), _selected.end(), std::back_inserter(items.list), transformation); } return items; } MessageIdsList ListWidget::collectSelectedIds() const { const auto selected = collectSelectedItems(); return ranges::view::all( selected.list ) | ranges::view::transform([](const SelectedItem &item) { return item.msgId; }) | ranges::to_vector; } void ListWidget::pushSelectedItems() { _selectedListStream.fire(collectSelectedItems()); } bool ListWidget::hasSelected() const { return !_selected.empty(); } bool ListWidget::isSelectedItem( const SelectedMap::const_iterator &i) const { return (i != _selected.end()) && (i->second.text == FullSelection); } void ListWidget::removeItemSelection( const SelectedMap::const_iterator &i) { Expects(i != _selected.cend()); _selected.erase(i); if (_selected.empty()) { update(); } pushSelectedItems(); } bool ListWidget::hasSelectedText() const { return hasSelected() && !hasSelectedItems(); } bool ListWidget::hasSelectedItems() const { return isSelectedItem(_selected.cbegin()); } void ListWidget::itemLayoutChanged( not_null<const HistoryItem*> item) { if (isItemLayout(item, _overLayout)) { mouseActionUpdate(); } } void ListWidget::repaintItem(const HistoryItem *item) { if (item && isMyItem(item)) { repaintItem(GetUniversalId(item)); } } void ListWidget::repaintItem(UniversalMsgId universalId) { if (auto item = findItemById(universalId)) { repaintItem(item->geometry); } } void ListWidget::repaintItem(const BaseLayout *item) { if (item) { repaintItem(GetUniversalId(item)); } } void ListWidget::repaintItem(QRect itemGeometry) { rtlupdate(itemGeometry); } bool ListWidget::isMyItem(not_null<const HistoryItem*> item) const { auto peer = item->history()->peer; return (_peer == peer) || (_migrated == peer); } bool ListWidget::isPossiblyMyId(FullMsgId fullId) const { return (fullId.channel != 0) ? (_peer->isChannel() && _peer->bareId() == fullId.channel) : (!_peer->isChannel() || _migrated); } bool ListWidget::isItemLayout( not_null<const HistoryItem*> item, BaseLayout *layout) const { return layout && (layout->getItem() == item); } void ListWidget::invalidatePaletteCache() { for (auto &layout : _layouts) { layout.second.item->invalidateCache(); } } SparseIdsMergedSlice::Key ListWidget::sliceKey( UniversalMsgId universalId) const { using Key = SparseIdsMergedSlice::Key; if (_migrated) { return Key(_peer->id, _migrated->id, universalId); } if (universalId < 0) { // Convert back to plain id for non-migrated histories. universalId += ServerMaxMsgId; } return Key(_peer->id, 0, universalId); } void ListWidget::refreshViewer() { _viewerLifetime.destroy(); auto idForViewer = sliceKey(_universalAroundId).universalId; _controller->mediaSource( idForViewer, _idsLimit, _idsLimit ) | rpl::start_with_next([=](SparseIdsMergedSlice &&slice) { if (!slice.fullCount()) { // Don't display anything while full count is unknown. return; } _slice = std::move(slice); if (auto nearest = _slice.nearest(idForViewer)) { _universalAroundId = GetUniversalId(*nearest); } refreshRows(); }, _viewerLifetime); } BaseLayout *ListWidget::getLayout(UniversalMsgId universalId) { auto it = _layouts.find(universalId); if (it == _layouts.end()) { if (auto layout = createLayout(universalId, _type)) { layout->initDimensions(); it = _layouts.emplace( universalId, std::move(layout)).first; } else { return nullptr; } } it->second.stale = false; return it->second.item.get(); } BaseLayout *ListWidget::getExistingLayout( UniversalMsgId universalId) const { auto it = _layouts.find(universalId); return (it != _layouts.end()) ? it->second.item.get() : nullptr; } std::unique_ptr<BaseLayout> ListWidget::createLayout( UniversalMsgId universalId, Type type) { auto item = App::histItemById(computeFullId(universalId)); if (!item) { return nullptr; } auto getPhoto = [&]() -> PhotoData* { if (const auto media = item->media()) { return media->photo(); } return nullptr; }; auto getFile = [&]() -> DocumentData* { if (auto media = item->media()) { return media->document(); } return nullptr; }; auto &songSt = st::overviewFileLayout; using namespace Layout; switch (type) { case Type::Photo: if (auto photo = getPhoto()) { return std::make_unique<Photo>(item, photo); } return nullptr; case Type::Video: if (auto file = getFile()) { return std::make_unique<Video>(item, file); } return nullptr; case Type::File: if (auto file = getFile()) { return std::make_unique<Document>(item, file, songSt); } return nullptr; case Type::MusicFile: if (auto file = getFile()) { return std::make_unique<Document>(item, file, songSt); } return nullptr; case Type::VoiceFile: if (auto file = getFile()) { return std::make_unique<Voice>(item, file, songSt); } return nullptr; case Type::Link: return std::make_unique<Link>(item, item->media()); case Type::RoundFile: return nullptr; } Unexpected("Type in ListWidget::createLayout()"); } void ListWidget::refreshRows() { saveScrollState(); markLayoutsStale(); _sections.clear(); auto section = Section(_type); auto count = _slice.size(); for (auto i = count; i != 0;) { auto universalId = GetUniversalId(_slice[--i]); if (auto layout = getLayout(universalId)) { if (!section.addItem(layout)) { _sections.push_back(std::move(section)); section = Section(_type); section.addItem(layout); } } } if (!section.empty()) { _sections.push_back(std::move(section)); } if (auto count = _slice.fullCount()) { if (*count > kMediaCountForSearch) { _controller->setSearchEnabledByContent(true); } } clearStaleLayouts(); resizeToWidth(width()); restoreScrollState(); mouseActionUpdate(); } void ListWidget::markLayoutsStale() { for (auto &layout : _layouts) { layout.second.stale = true; } } void ListWidget::saveState(not_null<Memento*> memento) { if (_universalAroundId != kDefaultAroundId) { auto state = countScrollState(); if (state.item) { memento->setAroundId(computeFullId(_universalAroundId)); memento->setIdsLimit(_idsLimit); memento->setScrollTopItem(computeFullId(state.item)); memento->setScrollTopShift(state.shift); } } } void ListWidget::restoreState(not_null<Memento*> memento) { if (auto limit = memento->idsLimit()) { auto wasAroundId = memento->aroundId(); if (isPossiblyMyId(wasAroundId)) { _idsLimit = limit; _universalAroundId = GetUniversalId(wasAroundId); _scrollTopState.item = GetUniversalId(memento->scrollTopItem()); _scrollTopState.shift = memento->scrollTopShift(); refreshViewer(); } } } int ListWidget::resizeGetHeight(int newWidth) { if (newWidth > 0) { for (auto §ion : _sections) { section.resizeToWidth(newWidth); } } return recountHeight(); } auto ListWidget::findItemByPoint(QPoint point) const -> FoundItem { Expects(!_sections.empty()); auto sectionIt = findSectionAfterTop(point.y()); if (sectionIt == _sections.end()) { --sectionIt; } auto shift = QPoint(0, sectionIt->top()); return foundItemInSection( sectionIt->findItemByPoint(point - shift), *sectionIt); } auto ListWidget::findItemById( UniversalMsgId universalId) -> base::optional<FoundItem> { auto sectionIt = findSectionByItem(universalId); if (sectionIt != _sections.end()) { auto item = sectionIt->findItemNearId(universalId); if (item.exact) { return foundItemInSection(item, *sectionIt); } } return base::none; } auto ListWidget::findItemDetails( BaseLayout *item) -> base::optional<FoundItem> { return item ? findItemById(GetUniversalId(item)) : base::none; } auto ListWidget::foundItemInSection( const FoundItem &item, const Section §ion) const -> FoundItem { return { item.layout, item.geometry.translated(0, section.top()), item.exact }; } void ListWidget::visibleTopBottomUpdated( int visibleTop, int visibleBottom) { _visibleTop = visibleTop; _visibleBottom = visibleBottom; checkMoveToOtherViewer(); } void ListWidget::checkMoveToOtherViewer() { auto visibleHeight = (_visibleBottom - _visibleTop); if (width() <= 0 || visibleHeight <= 0 || _sections.empty() || _scrollTopState.item) { return; } auto topItem = findItemByPoint({ 0, _visibleTop }); auto bottomItem = findItemByPoint({ 0, _visibleBottom }); auto preloadedHeight = kPreloadedScreensCountFull * visibleHeight; auto minItemHeight = Section::MinItemHeight(_type, width()); auto preloadedCount = preloadedHeight / minItemHeight; auto preloadIdsLimitMin = (preloadedCount / 2) + 1; auto preloadIdsLimit = preloadIdsLimitMin + (visibleHeight / minItemHeight); auto preloadBefore = kPreloadIfLessThanScreens * visibleHeight; auto after = _slice.skippedAfter(); auto preloadTop = (_visibleTop < preloadBefore); auto topLoaded = after && (*after == 0); auto before = _slice.skippedBefore(); auto preloadBottom = (height() - _visibleBottom < preloadBefore); auto bottomLoaded = before && (*before == 0); auto minScreenDelta = kPreloadedScreensCount - kPreloadIfLessThanScreens; auto minUniversalIdDelta = (minScreenDelta * visibleHeight) / minItemHeight; auto preloadAroundItem = [&](const FoundItem &item) { auto preloadRequired = false; auto universalId = GetUniversalId(item.layout); if (!preloadRequired) { preloadRequired = (_idsLimit < preloadIdsLimitMin); } if (!preloadRequired) { auto delta = _slice.distance( sliceKey(_universalAroundId), sliceKey(universalId)); Assert(delta != base::none); preloadRequired = (qAbs(*delta) >= minUniversalIdDelta); } if (preloadRequired) { _idsLimit = preloadIdsLimit; _universalAroundId = universalId; refreshViewer(); } }; if (preloadTop && !topLoaded) { preloadAroundItem(topItem); } else if (preloadBottom && !bottomLoaded) { preloadAroundItem(bottomItem); } } auto ListWidget::countScrollState() const -> ScrollTopState { if (_sections.empty()) { return { 0, 0 }; } auto topItem = findItemByPoint({ 0, _visibleTop }); return { GetUniversalId(topItem.layout), _visibleTop - topItem.geometry.y() }; } void ListWidget::saveScrollState() { if (!_scrollTopState.item) { _scrollTopState = countScrollState(); } } void ListWidget::restoreScrollState() { if (_sections.empty() || !_scrollTopState.item) { return; } auto sectionIt = findSectionByItem(_scrollTopState.item); if (sectionIt == _sections.end()) { --sectionIt; } auto item = foundItemInSection( sectionIt->findItemNearId(_scrollTopState.item), *sectionIt); auto newVisibleTop = item.geometry.y() + _scrollTopState.shift; if (_visibleTop != newVisibleTop) { _scrollToRequests.fire_copy(newVisibleTop); } _scrollTopState = ScrollTopState(); } QMargins ListWidget::padding() const { return st::infoMediaMargin; } void ListWidget::paintEvent(QPaintEvent *e) { Painter p(this); auto outerWidth = width(); auto clip = e->rect(); auto ms = getms(); auto fromSectionIt = findSectionAfterTop(clip.y()); auto tillSectionIt = findSectionAfterBottom( fromSectionIt, clip.y() + clip.height()); auto context = Context { Layout::PaintContext(ms, hasSelectedItems()), &_selected, &_dragSelected, _dragSelectAction }; for (auto it = fromSectionIt; it != tillSectionIt; ++it) { auto top = it->top(); p.translate(0, top); it->paint(p, context, clip.translated(0, -top), outerWidth); p.translate(0, -top); } } void ListWidget::mousePressEvent(QMouseEvent *e) { if (_contextMenu) { e->accept(); return; // ignore mouse press, that was hiding context menu } mouseActionStart(e->globalPos(), e->button()); } void ListWidget::mouseMoveEvent(QMouseEvent *e) { auto buttonsPressed = (e->buttons() & (Qt::LeftButton | Qt::MiddleButton)); if (!buttonsPressed && _mouseAction != MouseAction::None) { mouseReleaseEvent(e); } mouseActionUpdate(e->globalPos()); } void ListWidget::mouseReleaseEvent(QMouseEvent *e) { mouseActionFinish(e->globalPos(), e->button()); if (!rect().contains(e->pos())) { leaveEvent(e); } } void ListWidget::mouseDoubleClickEvent(QMouseEvent *e) { mouseActionStart(e->globalPos(), e->button()); trySwitchToWordSelection(); } void ListWidget::showContextMenu( QContextMenuEvent *e, ContextMenuSource source) { if (_contextMenu) { _contextMenu->deleteLater(); _contextMenu = nullptr; repaintItem(_contextUniversalId); } if (e->reason() == QContextMenuEvent::Mouse) { mouseActionUpdate(e->globalPos()); } auto item = App::histItemById(computeFullId(_overState.itemId)); if (!item || !_overState.inside) { return; } auto universalId = _contextUniversalId = _overState.itemId; enum class SelectionState { NoSelectedItems, NotOverSelectedItems, OverSelectedItems, NotOverSelectedText, OverSelectedText, }; auto overSelected = SelectionState::NoSelectedItems; if (source == ContextMenuSource::Touch) { if (hasSelectedItems()) { overSelected = SelectionState::OverSelectedItems; } else if (hasSelectedText()) { overSelected = SelectionState::OverSelectedItems; } } else if (hasSelectedText()) { // #TODO text selection } else if (hasSelectedItems()) { auto it = _selected.find(_overState.itemId); if (isSelectedItem(it) && _overState.inside) { overSelected = SelectionState::OverSelectedItems; } else { overSelected = SelectionState::NotOverSelectedItems; } } auto canDeleteAll = [&] { return ranges::find_if(_selected, [](auto &&item) { return !item.second.canDelete; }) == _selected.end(); }; auto canForwardAll = [&] { return ranges::find_if(_selected, [](auto &&item) { return !item.second.canForward; }) == _selected.end(); }; auto link = ClickHandler::getActive(); _contextMenu = new Ui::PopupMenu(nullptr); _contextMenu->addAction( lang(lng_context_to_msg), [itemFullId = item->fullId()] { if (auto item = App::histItemById(itemFullId)) { Ui::showPeerHistoryAtItem(item); } }); auto photoLink = dynamic_cast<PhotoClickHandler*>(link.get()); auto fileLink = dynamic_cast<DocumentClickHandler*>(link.get()); if (photoLink || fileLink) { auto [isVideo, isVoice, isAudio] = [&] { if (fileLink) { auto document = fileLink->document(); return std::make_tuple( document->isVideoFile(), document->isVoiceMessage(), document->isAudioFile() ); } return std::make_tuple(false, false, false); }(); if (photoLink) { } else { if (auto document = fileLink->document()) { if (document->loading()) { _contextMenu->addAction( lang(lng_context_cancel_download), [document] { document->cancel(); }); } else { auto filepath = document->filepath(DocumentData::FilePathResolveChecked); if (!filepath.isEmpty()) { auto handler = App::LambdaDelayed( st::defaultDropdownMenu.menu.ripple.hideDuration, this, [filepath] { File::ShowInFolder(filepath); }); _contextMenu->addAction( lang((cPlatform() == dbipMac || cPlatform() == dbipMacOld) ? lng_context_show_in_finder : lng_context_show_in_folder), std::move(handler)); } auto handler = App::LambdaDelayed( st::defaultDropdownMenu.menu.ripple.hideDuration, this, [document] { DocumentSaveClickHandler::doSave(document, true); }); _contextMenu->addAction( lang(isVideo ? lng_context_save_video : isVoice ? lng_context_save_audio : isAudio ? lng_context_save_audio_file : lng_context_save_file), std::move(handler)); } } } } else if (link) { const auto actionText = link->copyToClipboardContextItemText(); if (!actionText.isEmpty()) { _contextMenu->addAction( actionText, [text = link->copyToClipboardText()] { QApplication::clipboard()->setText(text); }); } } if (overSelected == SelectionState::OverSelectedItems) { if (canForwardAll()) { _contextMenu->addAction( lang(lng_context_forward_selected), base::lambda_guarded(this, [this] { forwardSelected(); })); } if (canDeleteAll()) { _contextMenu->addAction( lang(lng_context_delete_selected), base::lambda_guarded(this, [this] { deleteSelected(); })); } _contextMenu->addAction( lang(lng_context_clear_selection), base::lambda_guarded(this, [this] { clearSelected(); })); } else { if (overSelected != SelectionState::NotOverSelectedItems) { if (item->allowsForward()) { _contextMenu->addAction( lang(lng_context_forward_msg), base::lambda_guarded(this, [this, universalId] { forwardItem(universalId); })); } if (item->canDelete()) { _contextMenu->addAction( lang(lng_context_delete_msg), base::lambda_guarded(this, [this, universalId] { deleteItem(universalId); })); } } _contextMenu->addAction( lang(lng_context_select_msg), base::lambda_guarded(this, [this, universalId] { if (hasSelectedText()) { clearSelected(); } else if (_selected.size() == MaxSelectedItems) { return; } else if (_selected.empty()) { update(); } applyItemSelection(universalId, FullSelection); })); } _contextMenu->setDestroyedCallback(base::lambda_guarded( this, [this, universalId] { _contextMenu = nullptr; mouseActionUpdate(QCursor::pos()); repaintItem(universalId); _checkForHide.fire({}); })); _contextMenu->popup(e->globalPos()); e->accept(); } void ListWidget::contextMenuEvent(QContextMenuEvent *e) { showContextMenu( e, (e->reason() == QContextMenuEvent::Mouse) ? ContextMenuSource::Mouse : ContextMenuSource::Other); } void ListWidget::forwardSelected() { if (auto items = collectSelectedIds(); !items.empty()) { forwardItems(std::move(items)); } } void ListWidget::forwardItem(UniversalMsgId universalId) { if (const auto item = App::histItemById(computeFullId(universalId))) { forwardItems({ 1, item->fullId() }); } } void ListWidget::forwardItems(MessageIdsList &&items) { auto callback = [weak = make_weak(this)] { if (const auto strong = weak.data()) { strong->clearSelected(); } }; setActionBoxWeak(Window::ShowForwardMessagesBox( std::move(items), std::move(callback))); } void ListWidget::deleteSelected() { if (const auto box = deleteItems(collectSelectedIds())) { const auto weak = make_weak(this); box->setDeleteConfirmedCallback([=]{ if (const auto strong = weak.data()) { strong->clearSelected(); } }); } } void ListWidget::deleteItem(UniversalMsgId universalId) { if (const auto item = App::histItemById(computeFullId(universalId))) { deleteItems({ 1, item->fullId() }); } } DeleteMessagesBox *ListWidget::deleteItems(MessageIdsList &&items) { if (!items.empty()) { const auto box = Ui::show( Box<DeleteMessagesBox>(std::move(items))).data(); setActionBoxWeak(box); return box; } return nullptr; } void ListWidget::setActionBoxWeak(QPointer<Ui::RpWidget> box) { if ((_actionBoxWeak = box)) { _actionBoxWeakLifetime = _actionBoxWeak->alive( ) | rpl::start_with_done([weak = make_weak(this)]{ if (weak) { weak->_checkForHide.fire({}); } }); } } void ListWidget::trySwitchToWordSelection() { auto selectingSome = (_mouseAction == MouseAction::Selecting) && hasSelectedText(); auto willSelectSome = (_mouseAction == MouseAction::None) && !hasSelectedItems(); auto checkSwitchToWordSelection = _overLayout && (_mouseSelectType == TextSelectType::Letters) && (selectingSome || willSelectSome); if (checkSwitchToWordSelection) { switchToWordSelection(); } } void ListWidget::switchToWordSelection() { Expects(_overLayout != nullptr); StateRequest request; request.flags |= Text::StateRequest::Flag::LookupSymbol; auto dragState = _overLayout->getState(_pressState.cursor, request); if (dragState.cursor != CursorState::Text) { return; } _mouseTextSymbol = dragState.symbol; _mouseSelectType = TextSelectType::Words; if (_mouseAction == MouseAction::None) { _mouseAction = MouseAction::Selecting; clearSelected(); auto selStatus = TextSelection { dragState.symbol, dragState.symbol }; applyItemSelection(_overState.itemId, selStatus); } mouseActionUpdate(); _trippleClickPoint = _mousePosition; _trippleClickStartTime = getms(); } void ListWidget::applyItemSelection( UniversalMsgId universalId, TextSelection selection) { if (changeItemSelection( _selected, universalId, selection)) { repaintItem(universalId); pushSelectedItems(); } } void ListWidget::toggleItemSelection(UniversalMsgId universalId) { auto it = _selected.find(universalId); if (it == _selected.cend()) { applyItemSelection(universalId, FullSelection); } else { removeItemSelection(it); } } bool ListWidget::changeItemSelection( SelectedMap &selected, UniversalMsgId universalId, TextSelection selection) const { auto changeExisting = [&](auto it) { if (it == selected.cend()) { return false; } else if (it->second.text != selection) { it->second.text = selection; return true; } return false; }; if (selected.size() < MaxSelectedItems) { auto [iterator, ok] = selected.try_emplace( universalId, selection); if (ok) { auto item = App::histItemById(computeFullId(universalId)); if (!item) { selected.erase(iterator); return false; } iterator->second.canDelete = item->canDelete(); iterator->second.canForward = item->allowsForward(); return true; } return changeExisting(iterator); } return changeExisting(selected.find(universalId)); } bool ListWidget::isItemUnderPressSelected() const { return itemUnderPressSelection() != _selected.end(); } auto ListWidget::itemUnderPressSelection() -> SelectedMap::iterator { return (_pressState.itemId && _pressState.inside) ? _selected.find(_pressState.itemId) : _selected.end(); } auto ListWidget::itemUnderPressSelection() const -> SelectedMap::const_iterator { return (_pressState.itemId && _pressState.inside) ? _selected.find(_pressState.itemId) : _selected.end(); } bool ListWidget::requiredToStartDragging( not_null<BaseLayout*> layout) const { if (_mouseCursorState == CursorState::Date) { return true; } // return dynamic_cast<HistorySticker*>(layout->getMedia()); return false; } bool ListWidget::isPressInSelectedText(TextState state) const { if (state.cursor != CursorState::Text) { return false; } if (!hasSelectedText() || !isItemUnderPressSelected()) { return false; } auto pressedSelection = itemUnderPressSelection(); auto from = pressedSelection->second.text.from; auto to = pressedSelection->second.text.to; return (state.symbol >= from && state.symbol < to); } void ListWidget::clearSelected() { if (_selected.empty()) { return; } if (hasSelectedText()) { repaintItem(_selected.begin()->first); _selected.clear(); } else { _selected.clear(); pushSelectedItems(); update(); } } void ListWidget::validateTrippleClickStartTime() { if (_trippleClickStartTime) { auto elapsed = (getms() - _trippleClickStartTime); if (elapsed >= QApplication::doubleClickInterval()) { _trippleClickStartTime = 0; } } } void ListWidget::enterEventHook(QEvent *e) { mouseActionUpdate(QCursor::pos()); return RpWidget::enterEventHook(e); } void ListWidget::leaveEventHook(QEvent *e) { if (const auto item = _overLayout) { if (_overState.inside) { repaintItem(item); _overState.inside = false; } } ClickHandler::clearActive(); if (!ClickHandler::getPressed() && _cursor != style::cur_default) { _cursor = style::cur_default; setCursor(_cursor); } return RpWidget::leaveEventHook(e); } QPoint ListWidget::clampMousePosition(QPoint position) const { return { std::clamp(position.x(), 0, qMax(0, width() - 1)), std::clamp(position.y(), _visibleTop, _visibleBottom - 1) }; } void ListWidget::mouseActionUpdate(const QPoint &globalPosition) { if (_sections.empty() || _visibleBottom <= _visibleTop) { return; } _mousePosition = globalPosition; auto local = mapFromGlobal(_mousePosition); auto point = clampMousePosition(local); auto [layout, geometry, inside] = findItemByPoint(point); auto state = MouseState{ GetUniversalId(layout), geometry.size(), point - geometry.topLeft(), inside }; auto item = layout ? layout->getItem() : nullptr; if (_overLayout != layout) { repaintItem(_overLayout); _overLayout = layout; repaintItem(geometry); } _overState = state; TextState dragState; ClickHandlerHost *lnkhost = nullptr; auto inTextSelection = _overState.inside && (_overState.itemId == _pressState.itemId) && hasSelectedText(); if (_overLayout) { auto cursorDeltaLength = [&] { auto cursorDelta = (_overState.cursor - _pressState.cursor); return cursorDelta.manhattanLength(); }; auto dragStartLength = [] { return QApplication::startDragDistance(); }; if (_overState.itemId != _pressState.itemId || cursorDeltaLength() >= dragStartLength()) { if (_mouseAction == MouseAction::PrepareDrag) { _mouseAction = MouseAction::Dragging; InvokeQueued(this, [this] { performDrag(); }); } else if (_mouseAction == MouseAction::PrepareSelect) { _mouseAction = MouseAction::Selecting; } } StateRequest request; if (_mouseAction == MouseAction::Selecting) { request.flags |= Text::StateRequest::Flag::LookupSymbol; } else { inTextSelection = false; } dragState = _overLayout->getState(_overState.cursor, request); lnkhost = _overLayout; } ClickHandler::setActive(dragState.link, lnkhost); if (_mouseAction == MouseAction::None) { _mouseCursorState = dragState.cursor; auto cursor = computeMouseCursor(); if (_cursor != cursor) { setCursor(_cursor = cursor); } } else if (_mouseAction == MouseAction::Selecting) { if (inTextSelection) { auto second = dragState.symbol; if (dragState.afterSymbol && _mouseSelectType == TextSelectType::Letters) { ++second; } auto selState = TextSelection { qMin(second, _mouseTextSymbol), qMax(second, _mouseTextSymbol) }; if (_mouseSelectType != TextSelectType::Letters) { selState = _overLayout->adjustSelection(selState, _mouseSelectType); } applyItemSelection(_overState.itemId, selState); auto hasSelection = (selState == FullSelection) || (selState.from != selState.to); if (!_wasSelectedText && hasSelection) { _wasSelectedText = true; setFocus(); } clearDragSelection(); } else if (_pressState.itemId) { updateDragSelection(); } } else if (_mouseAction == MouseAction::Dragging) { } // #TODO scroll by drag //if (_mouseAction == MouseAction::Selecting) { // _widget->checkSelectingScroll(mousePos); //} else { // clearDragSelection(); // _widget->noSelectingScroll(); //} } style::cursor ListWidget::computeMouseCursor() const { if (ClickHandler::getPressed() || ClickHandler::getActive()) { return style::cur_pointer; } else if (!hasSelectedItems() && (_mouseCursorState == CursorState::Text)) { return style::cur_text; } return style::cur_default; } void ListWidget::updateDragSelection() { auto fromState = _pressState; auto tillState = _overState; auto swapStates = IsAfter(fromState, tillState); if (swapStates) { std::swap(fromState, tillState); } if (!fromState.itemId || !tillState.itemId) { clearDragSelection(); return; } auto fromId = SkipSelectFromItem(fromState) ? (fromState.itemId - 1) : fromState.itemId; auto tillId = SkipSelectTillItem(tillState) ? tillState.itemId : (tillState.itemId - 1); for (auto i = _dragSelected.begin(); i != _dragSelected.end();) { auto itemId = i->first; if (itemId > fromId || itemId <= tillId) { i = _dragSelected.erase(i); } else { ++i; } } for (auto &layoutItem : _layouts) { auto &&universalId = layoutItem.first; auto &&layout = layoutItem.second; if (universalId <= fromId && universalId > tillId) { changeItemSelection( _dragSelected, universalId, FullSelection); } } _dragSelectAction = [&] { if (_dragSelected.empty()) { return DragSelectAction::None; } auto &[firstDragItem, data] = swapStates ? _dragSelected.front() : _dragSelected.back(); if (isSelectedItem(_selected.find(firstDragItem))) { return DragSelectAction::Deselecting; } else { return DragSelectAction::Selecting; } }(); if (!_wasSelectedText && !_dragSelected.empty() && _dragSelectAction == DragSelectAction::Selecting) { _wasSelectedText = true; setFocus(); } update(); } void ListWidget::clearDragSelection() { _dragSelectAction = DragSelectAction::None; if (!_dragSelected.empty()) { _dragSelected.clear(); update(); } } void ListWidget::mouseActionStart( const QPoint &globalPosition, Qt::MouseButton button) { mouseActionUpdate(globalPosition); if (button != Qt::LeftButton) { return; } ClickHandler::pressed(); if (_pressState != _overState) { if (_pressState.itemId != _overState.itemId) { repaintItem(_pressState.itemId); } _pressState = _overState; repaintItem(_overLayout); } auto pressLayout = _overLayout; _mouseAction = MouseAction::None; _pressWasInactive = _controller->parentController()->window()->wasInactivePress(); if (_pressWasInactive) _controller->parentController()->window()->setInactivePress(false); if (ClickHandler::getPressed() && !hasSelected()) { _mouseAction = MouseAction::PrepareDrag; } else if (hasSelectedItems()) { if (isItemUnderPressSelected() && ClickHandler::getPressed()) { // In shared media overview drag only by click handlers. _mouseAction = MouseAction::PrepareDrag; // start items drag } else if (!_pressWasInactive) { _mouseAction = MouseAction::PrepareSelect; // start items select } } if (_mouseAction == MouseAction::None && pressLayout) { validateTrippleClickStartTime(); TextState dragState; auto startDistance = (globalPosition - _trippleClickPoint).manhattanLength(); auto validStartPoint = startDistance < QApplication::startDragDistance(); if (_trippleClickStartTime != 0 && validStartPoint) { StateRequest request; request.flags = Text::StateRequest::Flag::LookupSymbol; dragState = pressLayout->getState(_pressState.cursor, request); if (dragState.cursor == CursorState::Text) { TextSelection selStatus = { dragState.symbol, dragState.symbol }; if (selStatus != FullSelection && !hasSelectedItems()) { clearSelected(); applyItemSelection(_pressState.itemId, selStatus); _mouseTextSymbol = dragState.symbol; _mouseAction = MouseAction::Selecting; _mouseSelectType = TextSelectType::Paragraphs; mouseActionUpdate(_mousePosition); _trippleClickStartTime = getms(); } } } else { StateRequest request; request.flags = Text::StateRequest::Flag::LookupSymbol; dragState = pressLayout->getState(_pressState.cursor, request); } if (_mouseSelectType != TextSelectType::Paragraphs) { if (_pressState.inside) { _mouseTextSymbol = dragState.symbol; if (isPressInSelectedText(dragState)) { _mouseAction = MouseAction::PrepareDrag; // start text drag } else if (!_pressWasInactive) { if (requiredToStartDragging(pressLayout)) { _mouseAction = MouseAction::PrepareDrag; } else { if (dragState.afterSymbol) ++_mouseTextSymbol; TextSelection selStatus = { _mouseTextSymbol, _mouseTextSymbol }; if (selStatus != FullSelection && !hasSelectedItems()) { clearSelected(); applyItemSelection(_pressState.itemId, selStatus); _mouseAction = MouseAction::Selecting; repaintItem(pressLayout); } else { _mouseAction = MouseAction::PrepareSelect; } } } } else if (!_pressWasInactive) { _mouseAction = MouseAction::PrepareSelect; // start items select } } } if (!pressLayout) { _mouseAction = MouseAction::None; } else if (_mouseAction == MouseAction::None) { mouseActionCancel(); } } void ListWidget::mouseActionCancel() { _pressState = MouseState(); _mouseAction = MouseAction::None; clearDragSelection(); _wasSelectedText = false; // _widget->noSelectingScroll(); // #TODO scroll by drag } void ListWidget::performDrag() { if (_mouseAction != MouseAction::Dragging) return; auto uponSelected = false; if (_pressState.itemId && _pressState.inside) { if (hasSelectedItems()) { uponSelected = isItemUnderPressSelected(); } else if (auto pressLayout = getExistingLayout( _pressState.itemId)) { StateRequest request; request.flags |= Text::StateRequest::Flag::LookupSymbol; auto dragState = pressLayout->getState( _pressState.cursor, request); uponSelected = isPressInSelectedText(dragState); } } auto pressedHandler = ClickHandler::getPressed(); if (dynamic_cast<VoiceSeekClickHandler*>(pressedHandler.get())) { return; } TextWithEntities sel; QList<QUrl> urls; if (uponSelected) { // sel = getSelectedText(); } else if (pressedHandler) { sel = { pressedHandler->dragText(), EntitiesInText() }; //if (!sel.isEmpty() && sel.at(0) != '/' && sel.at(0) != '@' && sel.at(0) != '#') { // urls.push_back(QUrl::fromEncoded(sel.toUtf8())); // Google Chrome crashes in Mac OS X O_o //} } //if (auto mimeData = MimeDataFromTextWithEntities(sel)) { // clearDragSelection(); // _widget->noSelectingScroll(); // if (!urls.isEmpty()) mimeData->setUrls(urls); // if (uponSelected && !Adaptive::OneColumn()) { // auto selectedState = getSelectionState(); // if (selectedState.count > 0 && selectedState.count == selectedState.canForwardCount) { // Auth().data().setMimeForwardIds(collectSelectedIds()); // mimeData->setData(qsl("application/x-td-forward"), "1"); // } // } // _controller->parentController()->window()->launchDrag(std::move(mimeData)); // return; //} else { // auto forwardMimeType = QString(); // auto pressedMedia = static_cast<HistoryMedia*>(nullptr); // if (auto pressedItem = _pressState.layout) { // pressedMedia = pressedItem->getMedia(); // if (_mouseCursorState == CursorState::Date || (pressedMedia && pressedMedia->dragItem())) { // Auth().data().setMimeForwardIds(Auth().data().itemOrItsGroup(pressedItem)); // forwardMimeType = qsl("application/x-td-forward"); // } // } // if (auto pressedLnkItem = App::pressedLinkItem()) { // if ((pressedMedia = pressedLnkItem->getMedia())) { // if (forwardMimeType.isEmpty() && pressedMedia->dragItemByHandler(pressedHandler)) { // Auth().data().setMimeForwardIds({ 1, pressedLnkItem->fullId() }); // forwardMimeType = qsl("application/x-td-forward"); // } // } // } // if (!forwardMimeType.isEmpty()) { // auto mimeData = std::make_unique<QMimeData>(); // mimeData->setData(forwardMimeType, "1"); // if (auto document = (pressedMedia ? pressedMedia->getDocument() : nullptr)) { // auto filepath = document->filepath(DocumentData::FilePathResolveChecked); // if (!filepath.isEmpty()) { // QList<QUrl> urls; // urls.push_back(QUrl::fromLocalFile(filepath)); // mimeData->setUrls(urls); // } // } // // This call enters event loop and can destroy any QObject. // _controller->parentController()->window()->launchDrag(std::move(mimeData)); // return; // } //} } void ListWidget::mouseActionFinish( const QPoint &globalPosition, Qt::MouseButton button) { mouseActionUpdate(globalPosition); auto pressState = base::take(_pressState); repaintItem(pressState.itemId); auto simpleSelectionChange = pressState.itemId && pressState.inside && !_pressWasInactive && (button != Qt::RightButton) && (_mouseAction == MouseAction::PrepareDrag || _mouseAction == MouseAction::PrepareSelect); auto needSelectionToggle = simpleSelectionChange && hasSelectedItems(); auto needSelectionClear = simpleSelectionChange && hasSelectedText(); auto activated = ClickHandler::unpressed(); if (_mouseAction == MouseAction::Dragging || _mouseAction == MouseAction::Selecting) { activated = nullptr; } else if (needSelectionToggle) { activated = nullptr; } _wasSelectedText = false; if (activated) { mouseActionCancel(); App::activateClickHandler(activated, button); return; } if (needSelectionToggle) { toggleItemSelection(pressState.itemId); } else if (needSelectionClear) { clearSelected(); } else if (_mouseAction == MouseAction::Selecting) { if (!_dragSelected.empty()) { applyDragSelection(); } else if (!_selected.empty() && !_pressWasInactive) { auto selection = _selected.cbegin()->second; if (selection.text != FullSelection && selection.text.from == selection.text.to) { clearSelected(); //_controller->parentController()->window()->setInnerFocus(); // #TODO focus } } } _mouseAction = MouseAction::None; _mouseSelectType = TextSelectType::Letters; //_widget->noSelectingScroll(); // #TODO scroll by drag //_widget->updateTopBarSelection(); #if defined Q_OS_LINUX32 || defined Q_OS_LINUX64 //if (hasSelectedText()) { // #TODO linux clipboard // SetClipboardWithEntities(_selected.cbegin()->first->selectedText(_selected.cbegin()->second), QClipboard::Selection); //} #endif // Q_OS_LINUX32 || Q_OS_LINUX64 } void ListWidget::applyDragSelection() { applyDragSelection(_selected); clearDragSelection(); pushSelectedItems(); } void ListWidget::applyDragSelection(SelectedMap &applyTo) const { if (_dragSelectAction == DragSelectAction::Selecting) { for (auto &[universalId,data] : _dragSelected) { changeItemSelection(applyTo, universalId, FullSelection); } } else if (_dragSelectAction == DragSelectAction::Deselecting) { for (auto &[universalId,data] : _dragSelected) { applyTo.remove(universalId); } } } void ListWidget::refreshHeight() { resize(width(), recountHeight()); } int ListWidget::recountHeight() { if (_sections.empty()) { if (auto count = _slice.fullCount()) { if (*count == 0) { return 0; } } } auto cachedPadding = padding(); auto result = cachedPadding.top(); for (auto §ion : _sections) { section.setTop(result); result += section.height(); } return result + cachedPadding.bottom(); } void ListWidget::mouseActionUpdate() { mouseActionUpdate(_mousePosition); } void ListWidget::clearStaleLayouts() { for (auto i = _layouts.begin(); i != _layouts.end();) { if (i->second.stale) { if (i->second.item.get() == _overLayout) { _overLayout = nullptr; } i = _layouts.erase(i); } else { ++i; } } } auto ListWidget::findSectionByItem( UniversalMsgId universalId) -> std::vector<Section>::iterator { return ranges::lower_bound( _sections, universalId, std::greater<>(), [](const Section §ion) { return section.minId(); }); } auto ListWidget::findSectionAfterTop( int top) -> std::vector<Section>::iterator { return ranges::lower_bound( _sections, top, std::less_equal<>(), [](const Section §ion) { return section.bottom(); }); } auto ListWidget::findSectionAfterTop( int top) const -> std::vector<Section>::const_iterator { return ranges::lower_bound( _sections, top, std::less_equal<>(), [](const Section §ion) { return section.bottom(); }); } auto ListWidget::findSectionAfterBottom( std::vector<Section>::const_iterator from, int bottom) const -> std::vector<Section>::const_iterator { return ranges::lower_bound( from, _sections.end(), bottom, std::less<>(), [](const Section §ion) { return section.top(); }); } ListWidget::~ListWidget() = default; } // namespace Media } // namespace Info ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/media/info_media_list_widget.h����������������������������0000664�0000000�0000000�00000021074�13262451251�0026302�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/rp_widget.h" #include "info/media/info_media_widget.h" #include "data/data_shared_media.h" class DeleteMessagesBox; namespace HistoryView { struct TextState; struct StateRequest; enum class CursorState : char; enum class PointState : char; } // namespace HistoryView namespace Ui { class PopupMenu; } // namespace Ui namespace Overview { namespace Layout { class ItemBase; } // namespace Layout } // namespace Overview namespace Window { class Controller; } // namespace Window namespace Info { class AbstractController; namespace Media { using BaseLayout = Overview::Layout::ItemBase; using UniversalMsgId = int32; class ListWidget : public Ui::RpWidget { public: ListWidget( QWidget *parent, not_null<AbstractController*> controller); void restart(); rpl::producer<int> scrollToRequests() const; rpl::producer<SelectedItems> selectedListValue() const; void cancelSelection() { clearSelected(); } QRect getCurrentSongGeometry(); rpl::producer<> checkForHide() const { return _checkForHide.events(); } bool preventAutoHide() const { return (_contextMenu != nullptr) || (_actionBoxWeak != nullptr); } void saveState(not_null<Memento*> memento); void restoreState(not_null<Memento*> memento); ~ListWidget(); protected: int resizeGetHeight(int newWidth) override; void visibleTopBottomUpdated( int visibleTop, int visibleBottom) override; void paintEvent(QPaintEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void mouseDoubleClickEvent(QMouseEvent *e) override; void contextMenuEvent(QContextMenuEvent *e) override; void enterEventHook(QEvent *e) override; void leaveEventHook(QEvent *e) override; private: struct Context; class Section; using CursorState = HistoryView::CursorState; using TextState = HistoryView::TextState; using StateRequest = HistoryView::StateRequest; enum class MouseAction { None, PrepareDrag, Dragging, PrepareSelect, Selecting, }; struct CachedItem { CachedItem(std::unique_ptr<BaseLayout> item); ~CachedItem(); std::unique_ptr<BaseLayout> item; bool stale = false; }; struct FoundItem { not_null<BaseLayout*> layout; QRect geometry; bool exact = false; }; struct SelectionData { explicit SelectionData(TextSelection text) : text(text) { } TextSelection text; bool canDelete = false; bool canForward = false; }; using SelectedMap = base::flat_map< UniversalMsgId, SelectionData, std::less<>>; enum class DragSelectAction { None, Selecting, Deselecting, }; struct MouseState { UniversalMsgId itemId = 0; QSize size; QPoint cursor; bool inside = false; inline bool operator==(const MouseState &other) const { return (itemId == other.itemId) && (cursor == other.cursor); } inline bool operator!=(const MouseState &other) const { return !(*this == other); } }; enum class ContextMenuSource { Mouse, Touch, Other, }; struct ScrollTopState { UniversalMsgId item = 0; int shift = 0; }; void start(); int recountHeight(); void refreshHeight(); QMargins padding() const; bool isMyItem(not_null<const HistoryItem*> item) const; bool isItemLayout( not_null<const HistoryItem*> item, BaseLayout *layout) const; bool isPossiblyMyId(FullMsgId fullId) const; void repaintItem(const HistoryItem *item); void repaintItem(UniversalMsgId msgId); void repaintItem(const BaseLayout *item); void repaintItem(QRect itemGeometry); void itemRemoved(not_null<const HistoryItem*> item); void itemLayoutChanged(not_null<const HistoryItem*> item); void refreshViewer(); void invalidatePaletteCache(); void refreshRows(); SparseIdsMergedSlice::Key sliceKey( UniversalMsgId universalId) const; BaseLayout *getLayout(UniversalMsgId universalId); BaseLayout *getExistingLayout(UniversalMsgId universalId) const; std::unique_ptr<BaseLayout> createLayout( UniversalMsgId universalId, Type type); SelectedItems collectSelectedItems() const; MessageIdsList collectSelectedIds() const; void pushSelectedItems(); FullMsgId computeFullId(UniversalMsgId universalId) const; bool hasSelected() const; bool isSelectedItem( const SelectedMap::const_iterator &i) const; void removeItemSelection( const SelectedMap::const_iterator &i); bool hasSelectedText() const; bool hasSelectedItems() const; void clearSelected(); void forwardSelected(); void forwardItem(UniversalMsgId universalId); void forwardItems(MessageIdsList &&items); void deleteSelected(); void deleteItem(UniversalMsgId universalId); DeleteMessagesBox *deleteItems(MessageIdsList &&items); void applyItemSelection( UniversalMsgId universalId, TextSelection selection); void toggleItemSelection( UniversalMsgId universalId); SelectedMap::iterator itemUnderPressSelection(); SelectedMap::const_iterator itemUnderPressSelection() const; bool isItemUnderPressSelected() const; bool requiredToStartDragging(not_null<BaseLayout*> layout) const; bool isPressInSelectedText(TextState state) const; void applyDragSelection(); void applyDragSelection(SelectedMap &applyTo) const; bool changeItemSelection( SelectedMap &selected, UniversalMsgId universalId, TextSelection selection) const; static bool IsAfter( const MouseState &a, const MouseState &b); static bool SkipSelectFromItem(const MouseState &state); static bool SkipSelectTillItem(const MouseState &state); void markLayoutsStale(); void clearStaleLayouts(); std::vector<Section>::iterator findSectionByItem( UniversalMsgId universalId); std::vector<Section>::iterator findSectionAfterTop(int top); std::vector<Section>::const_iterator findSectionAfterTop( int top) const; std::vector<Section>::const_iterator findSectionAfterBottom( std::vector<Section>::const_iterator from, int bottom) const; FoundItem findItemByPoint(QPoint point) const; base::optional<FoundItem> findItemById(UniversalMsgId universalId); base::optional<FoundItem> findItemDetails(BaseLayout *item); FoundItem foundItemInSection( const FoundItem &item, const Section §ion) const; ScrollTopState countScrollState() const; void saveScrollState(); void restoreScrollState(); QPoint clampMousePosition(QPoint position) const; void mouseActionStart( const QPoint &globalPosition, Qt::MouseButton button); void mouseActionUpdate(const QPoint &globalPosition); void mouseActionUpdate(); void mouseActionFinish( const QPoint &globalPosition, Qt::MouseButton button); void mouseActionCancel(); void performDrag(); style::cursor computeMouseCursor() const; void showContextMenu( QContextMenuEvent *e, ContextMenuSource source); void updateDragSelection(); void clearDragSelection(); void trySwitchToWordSelection(); void switchToWordSelection(); void validateTrippleClickStartTime(); void checkMoveToOtherViewer(); void setActionBoxWeak(QPointer<Ui::RpWidget> box); const not_null<AbstractController*> _controller; const not_null<PeerData*> _peer; PeerData * const _migrated = nullptr; Type _type = Type::Photo; static constexpr auto kMinimalIdsLimit = 16; static constexpr auto kDefaultAroundId = (ServerMaxMsgId - 1); UniversalMsgId _universalAroundId = kDefaultAroundId; int _idsLimit = kMinimalIdsLimit; SparseIdsMergedSlice _slice; std::map<UniversalMsgId, CachedItem> _layouts; std::vector<Section> _sections; int _visibleTop = 0; int _visibleBottom = 0; ScrollTopState _scrollTopState; rpl::event_stream<int> _scrollToRequests; MouseAction _mouseAction = MouseAction::None; TextSelectType _mouseSelectType = TextSelectType::Letters; QPoint _mousePosition; MouseState _overState; MouseState _pressState; BaseLayout *_overLayout = nullptr; UniversalMsgId _contextUniversalId = 0; CursorState _mouseCursorState = CursorState(); uint16 _mouseTextSymbol = 0; bool _pressWasInactive = false; SelectedMap _selected; SelectedMap _dragSelected; rpl::event_stream<SelectedItems> _selectedListStream; style::cursor _cursor = style::cur_default; DragSelectAction _dragSelectAction = DragSelectAction::None; bool _wasSelectedText = false; // was some text selected in current drag action Ui::PopupMenu *_contextMenu = nullptr; rpl::event_stream<> _checkForHide; QPointer<Ui::RpWidget> _actionBoxWeak; rpl::lifetime _actionBoxWeakLifetime; QPoint _trippleClickPoint; TimeMs _trippleClickStartTime = 0; rpl::lifetime _viewerLifetime; }; } // namespace Media } // namespace Info ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/media/info_media_widget.cpp�������������������������������0000664�0000000�0000000�00000006166�13262451251�0025607�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/media/info_media_widget.h" #include "info/media/info_media_inner_widget.h" #include "info/info_controller.h" #include "ui/widgets/scroll_area.h" #include "ui/search_field_controller.h" #include "styles/style_info.h" namespace Info { namespace Media { base::optional<int> TypeToTabIndex(Type type) { switch (type) { case Type::Photo: return 0; case Type::Video: return 1; case Type::File: return 2; } return base::none; } Type TabIndexToType(int index) { switch (index) { case 0: return Type::Photo; case 1: return Type::Video; case 2: return Type::File; } Unexpected("Index in Info::Media::TabIndexToType()"); } Memento::Memento(not_null<Controller*> controller) : Memento( controller->peerId(), controller->migratedPeerId(), controller->section().mediaType()) { } Memento::Memento(PeerId peerId, PeerId migratedPeerId, Type type) : ContentMemento(peerId, migratedPeerId) , _type(type) { _searchState.query.type = type; _searchState.query.peerId = peerId; _searchState.query.migratedPeerId = migratedPeerId; if (migratedPeerId) { _searchState.migratedList = Storage::SparseIdsList(); } } Section Memento::section() const { return Section(_type); } object_ptr<ContentWidget> Memento::createWidget( QWidget *parent, not_null<Controller*> controller, const QRect &geometry) { auto result = object_ptr<Widget>( parent, controller); result->setInternalState(geometry, this); return std::move(result); } Widget::Widget( QWidget *parent, not_null<Controller*> controller) : ContentWidget(parent, controller) { _inner = setInnerWidget(object_ptr<InnerWidget>( this, controller)); _inner->setScrollHeightValue(scrollHeightValue()); _inner->scrollToRequests( ) | rpl::start_with_next([this](Ui::ScrollToRequest request) { scrollTo(request); }, _inner->lifetime()); } rpl::producer<SelectedItems> Widget::selectedListValue() const { return _inner->selectedListValue(); } void Widget::cancelSelection() { _inner->cancelSelection(); } void Widget::setIsStackBottom(bool isStackBottom) { _inner->setIsStackBottom(isStackBottom); } bool Widget::showInternal(not_null<ContentMemento*> memento) { if (!controller()->validateMementoPeer(memento)) { return false; } if (auto mediaMemento = dynamic_cast<Memento*>(memento.get())) { if (_inner->showInternal(mediaMemento)) { return true; } } return false; } void Widget::setInternalState( const QRect &geometry, not_null<Memento*> memento) { setGeometry(geometry); Ui::SendPendingMoveResizeEvents(this); restoreState(memento); } std::unique_ptr<ContentMemento> Widget::doCreateMemento() { auto result = std::make_unique<Memento>(controller()); saveState(result.get()); return std::move(result); } void Widget::saveState(not_null<Memento*> memento) { _inner->saveState(memento); } void Widget::restoreState(not_null<Memento*> memento) { _inner->restoreState(memento); } } // namespace Media } // namespace Info ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/media/info_media_widget.h���������������������������������0000664�0000000�0000000�00000004642�13262451251�0025251�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/producer.h> #include "info/info_content_widget.h" #include "storage/storage_shared_media.h" #include "data/data_search_controller.h" namespace Info { namespace Media { using Type = Storage::SharedMediaType; base::optional<int> TypeToTabIndex(Type type); Type TabIndexToType(int index); class InnerWidget; class Memento final : public ContentMemento { public: Memento(not_null<Controller*> controller); Memento(PeerId peerId, PeerId migratedPeerId, Type type); using SearchState = Api::DelayedSearchController::SavedState; object_ptr<ContentWidget> createWidget( QWidget *parent, not_null<Controller*> controller, const QRect &geometry) override; Section section() const override; Type type() const { return _type; } void setAroundId(FullMsgId aroundId) { _aroundId = aroundId; } FullMsgId aroundId() const { return _aroundId; } void setIdsLimit(int limit) { _idsLimit = limit; } int idsLimit() const { return _idsLimit; } void setScrollTopItem(FullMsgId item) { _scrollTopItem = item; } FullMsgId scrollTopItem() const { return _scrollTopItem; } void setScrollTopShift(int shift) { _scrollTopShift = shift; } int scrollTopShift() const { return _scrollTopShift; } void setSearchState(SearchState &&state) { _searchState = std::move(state); } SearchState searchState() { return std::move(_searchState); } private: Type _type = Type::Photo; FullMsgId _aroundId; int _idsLimit = 0; FullMsgId _scrollTopItem; int _scrollTopShift = 0; SearchState _searchState; }; class Widget final : public ContentWidget { public: Widget( QWidget *parent, not_null<Controller*> controller); void setIsStackBottom(bool isStackBottom) override; bool showInternal( not_null<ContentMemento*> memento) override; void setInternalState( const QRect &geometry, not_null<Memento*> memento); rpl::producer<SelectedItems> selectedListValue() const override; void cancelSelection() override; private: void saveState(not_null<Memento*> memento); void restoreState(not_null<Memento*> memento); std::unique_ptr<ContentMemento> doCreateMemento() override; InnerWidget *_inner = nullptr; }; } // namespace Media } // namespace Info ����������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/members/��������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0022010�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/members/info_members_widget.cpp���������������������������0000664�0000000�0000000�00000004553�13262451251�0026533�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/members/info_members_widget.h" #include "info/profile/info_profile_members.h" #include "info/info_controller.h" #include "ui/widgets/scroll_area.h" #include "styles/style_info.h" namespace Info { namespace Members { Memento::Memento(not_null<Controller*> controller) : Memento( controller->peerId(), controller->migratedPeerId()) { } Memento::Memento(PeerId peerId, PeerId migratedPeerId) : ContentMemento(peerId, migratedPeerId) { } Section Memento::section() const { return Section(Section::Type::Members); } object_ptr<ContentWidget> Memento::createWidget( QWidget *parent, not_null<Controller*> controller, const QRect &geometry) { auto result = object_ptr<Widget>( parent, controller); result->setInternalState(geometry, this); return std::move(result); } void Memento::setState(std::unique_ptr<SavedState> state) { _state = std::move(state); } std::unique_ptr<SavedState> Memento::state() { return std::move(_state); } Memento::~Memento() = default; Widget::Widget( QWidget *parent, not_null<Controller*> controller) : ContentWidget(parent, controller) { _inner = setInnerWidget(object_ptr<Profile::Members>( this, controller)); } bool Widget::showInternal(not_null<ContentMemento*> memento) { if (!controller()->validateMementoPeer(memento)) { return false; } if (auto membersMemento = dynamic_cast<Memento*>(memento.get())) { restoreState(membersMemento); return true; } return false; } void Widget::setInternalState( const QRect &geometry, not_null<Memento*> memento) { setGeometry(geometry); Ui::SendPendingMoveResizeEvents(this); restoreState(memento); } std::unique_ptr<ContentMemento> Widget::doCreateMemento() { auto result = std::make_unique<Memento>(controller()); saveState(result.get()); return std::move(result); } void Widget::saveState(not_null<Memento*> memento) { memento->setScrollTop(scrollTopSave()); memento->setState(_inner->saveState()); } void Widget::restoreState(not_null<Memento*> memento) { _inner->restoreState(memento->state()); auto scrollTop = memento->scrollTop(); scrollTopRestore(memento->scrollTop()); } } // namespace Members } // namespace Info �����������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/members/info_members_widget.h�����������������������������0000664�0000000�0000000�00000002671�13262451251�0026177�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "info/info_content_widget.h" struct PeerListState; namespace Info { namespace Profile { class Members; struct MembersState; } // namespace Profile namespace Members { using SavedState = Profile::MembersState; class Memento final : public ContentMemento { public: Memento(not_null<Controller*> controller); Memento(PeerId peerId, PeerId migratedPeerId); object_ptr<ContentWidget> createWidget( QWidget *parent, not_null<Controller*> controller, const QRect &geometry) override; Section section() const override; void setState(std::unique_ptr<SavedState> state); std::unique_ptr<SavedState> state(); ~Memento(); private: std::unique_ptr<SavedState> _state; }; class Widget final : public ContentWidget { public: Widget( QWidget *parent, not_null<Controller*> controller); bool showInternal( not_null<ContentMemento*> memento) override; void setInternalState( const QRect &geometry, not_null<Memento*> memento); private: void saveState(not_null<Memento*> memento); void restoreState(not_null<Memento*> memento); std::unique_ptr<ContentMemento> doCreateMemento() override; Profile::Members *_inner = nullptr; }; } // namespace Members } // namespace Info �����������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/profile/��������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0022016�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/profile/info_profile_actions.cpp��������������������������0000664�0000000�0000000�00000051616�13262451251�0026726�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/profile/info_profile_actions.h" #include <rpl/flatten_latest.h> #include <rpl/combine.h> #include "data/data_peer_values.h" #include "data/data_session.h" #include "data/data_feed.h" #include "ui/wrap/vertical_layout.h" #include "ui/wrap/padding_wrap.h" #include "ui/wrap/slide_wrap.h" #include "ui/widgets/shadow.h" #include "ui/widgets/labels.h" #include "ui/toast/toast.h" #include "boxes/abstract_box.h" #include "boxes/confirm_box.h" #include "boxes/peer_list_box.h" #include "boxes/peer_list_controllers.h" #include "boxes/add_contact_box.h" #include "boxes/report_box.h" #include "lang/lang_keys.h" #include "info/info_controller.h" #include "info/info_memento.h" #include "info/profile/info_profile_icon.h" #include "info/profile/info_profile_values.h" #include "info/profile/info_profile_button.h" #include "info/profile/info_profile_text.h" #include "window/window_controller.h" #include "window/window_peer_menu.h" #include "mainwidget.h" #include "auth_session.h" #include "messenger.h" #include "apiwrap.h" #include "application.h" #include "styles/style_info.h" #include "styles/style_boxes.h" namespace Info { namespace Profile { namespace { object_ptr<Ui::RpWidget> CreateSkipWidget( not_null<Ui::RpWidget*> parent) { return Ui::CreateSkipWidget(parent, st::infoProfileSkip); } object_ptr<Ui::SlideWrap<>> CreateSlideSkipWidget( not_null<Ui::RpWidget*> parent) { auto result = Ui::CreateSlideSkipWidget( parent, st::infoProfileSkip); result->setDuration(st::infoSlideDuration); return result; } template <typename Text, typename ToggleOn, typename Callback> auto AddActionButton( not_null<Ui::VerticalLayout*> parent, Text &&text, ToggleOn &&toggleOn, Callback &&callback, const style::InfoProfileButton &st = st::infoSharedMediaButton) { auto result = parent->add(object_ptr<Ui::SlideWrap<Button>>( parent, object_ptr<Button>( parent, std::move(text), st)) ); result->setDuration( st::infoSlideDuration )->toggleOn( std::move(toggleOn) )->entity()->addClickHandler(std::move(callback)); result->finishAnimating(); return result; }; template <typename Text, typename ToggleOn, typename Callback> auto AddMainButton( not_null<Ui::VerticalLayout*> parent, Text &&text, ToggleOn &&toggleOn, Callback &&callback, Ui::MultiSlideTracker &tracker) { tracker.track(AddActionButton( parent, std::move(text) | ToUpperValue(), std::move(toggleOn), std::move(callback), st::infoMainButton)); } class DetailsFiller { public: DetailsFiller( not_null<Controller*> controller, not_null<Ui::RpWidget*> parent, not_null<PeerData*> peer); object_ptr<Ui::RpWidget> fill(); private: object_ptr<Ui::RpWidget> setupInfo(); object_ptr<Ui::RpWidget> setupMuteToggle(); void setupMainButtons(); Ui::MultiSlideTracker fillUserButtons( not_null<UserData*> user); Ui::MultiSlideTracker fillChannelButtons( not_null<ChannelData*> channel); template < typename Widget, typename = std::enable_if_t< std::is_base_of_v<Ui::RpWidget, Widget>>> Widget *add( object_ptr<Widget> &&child, const style::margins &margin = style::margins()) { return _wrap->add( std::move(child), margin); } not_null<Controller*> _controller; not_null<Ui::RpWidget*> _parent; not_null<PeerData*> _peer; object_ptr<Ui::VerticalLayout> _wrap; }; class ActionsFiller { public: ActionsFiller( not_null<Controller*> controller, not_null<Ui::RpWidget*> parent, not_null<PeerData*> peer); object_ptr<Ui::RpWidget> fill(); private: void addInviteToGroupAction(not_null<UserData*> user); void addShareContactAction(not_null<UserData*> user); void addEditContactAction(not_null<UserData*> user); void addDeleteContactAction(not_null<UserData*> user); void addClearHistoryAction(not_null<UserData*> user); void addDeleteConversationAction(not_null<UserData*> user); void addBotCommandActions(not_null<UserData*> user); void addReportAction(); void addBlockAction(not_null<UserData*> user); void addLeaveChannelAction(not_null<ChannelData*> channel); void addJoinChannelAction(not_null<ChannelData*> channel); void fillUserActions(not_null<UserData*> user); void fillChannelActions(not_null<ChannelData*> channel); not_null<Controller*> _controller; not_null<Ui::RpWidget*> _parent; not_null<PeerData*> _peer; object_ptr<Ui::VerticalLayout> _wrap = { nullptr }; }; class FeedDetailsFiller { public: FeedDetailsFiller( not_null<Controller*> controller, not_null<Ui::RpWidget*> parent, not_null<Data::Feed*> feed); object_ptr<Ui::RpWidget> fill(); private: object_ptr<Ui::RpWidget> setupDefaultToggle(); template < typename Widget, typename = std::enable_if_t< std::is_base_of_v<Ui::RpWidget, Widget>>> Widget *add( object_ptr<Widget> &&child, const style::margins &margin = style::margins()) { return _wrap->add( std::move(child), margin); } not_null<Controller*> _controller; not_null<Ui::RpWidget*> _parent; not_null<Data::Feed*> _feed; object_ptr<Ui::VerticalLayout> _wrap; }; DetailsFiller::DetailsFiller( not_null<Controller*> controller, not_null<Ui::RpWidget*> parent, not_null<PeerData*> peer) : _controller(controller) , _parent(parent) , _peer(peer) , _wrap(_parent) { } object_ptr<Ui::RpWidget> DetailsFiller::setupInfo() { auto result = object_ptr<Ui::VerticalLayout>(_wrap); auto tracker = Ui::MultiSlideTracker(); auto addInfoLine = [&]( LangKey label, rpl::producer<TextWithEntities> &&text, const style::FlatLabel &textSt = st::infoLabeled) { auto line = CreateTextWithLabel( result, Lang::Viewer(label) | WithEmptyEntities(), std::move(text), textSt, st::infoProfileLabeledPadding); tracker.track(result->add(std::move(line.wrap))); return line.text; }; auto addInfoOneLine = [&]( LangKey label, rpl::producer<TextWithEntities> &&text, const QString &contextCopyText) { auto result = addInfoLine( label, std::move(text), st::infoLabeledOneLine); result->setDoubleClickSelectsParagraph(true); result->setContextCopyText(contextCopyText); return result; }; if (auto user = _peer->asUser()) { addInfoOneLine( lng_info_mobile_label, PhoneValue(user), lang(lng_profile_copy_phone)); if (user->botInfo) { addInfoLine(lng_info_about_label, AboutValue(user)); } else { addInfoLine(lng_info_bio_label, BioValue(user)); } addInfoOneLine( lng_info_username_label, UsernameValue(user), lang(lng_context_copy_mention)); } else { auto linkText = LinkValue( _peer ) | rpl::map([](const QString &link) { auto result = TextWithEntities{ link, {} }; if (!link.isEmpty()) { auto remove = qstr("https://"); if (result.text.startsWith(remove)) { result.text.remove(0, remove.size()); } result.entities.push_back(EntityInText( EntityInTextCustomUrl, 0, result.text.size(), link)); } return result; }); auto link = addInfoOneLine( lng_info_link_label, std::move(linkText), QString()); link->setClickHandlerHook([peer = _peer](auto&&...) { auto link = Messenger::Instance().createInternalLinkFull( peer->userName()); if (!link.isEmpty()) { Application::clipboard()->setText(link); Ui::Toast::Show(lang(lng_username_copied)); } return false; }); addInfoLine(lng_info_about_label, AboutValue(_peer)); } if (!_peer->isSelf()) { // No notifications toggle for Self => no separator. result->add(object_ptr<Ui::SlideWrap<>>( result, object_ptr<Ui::PlainShadow>(result), st::infoProfileSeparatorPadding) )->setDuration( st::infoSlideDuration )->toggleOn( std::move(tracker).atLeastOneShownValue() ); } object_ptr<FloatingIcon>( result, st::infoIconInformation, st::infoInformationIconPosition); return std::move(result); } object_ptr<Ui::RpWidget> DetailsFiller::setupMuteToggle() { const auto peer = _peer; auto result = object_ptr<Button>( _wrap, Lang::Viewer(lng_profile_enable_notifications), st::infoNotificationsButton); result->toggleOn( NotificationsEnabledValue(peer) )->addClickHandler([=] { const auto muteState = peer->isMuted() ? Data::NotifySettings::MuteChange::Unmute : Data::NotifySettings::MuteChange::Mute; App::main()->updateNotifySettings(peer, muteState); }); object_ptr<FloatingIcon>( result, st::infoIconNotifications, st::infoNotificationsIconPosition); return std::move(result); } void DetailsFiller::setupMainButtons() { auto wrapButtons = [=](auto &&callback) { auto topSkip = _wrap->add(CreateSlideSkipWidget(_wrap)); auto tracker = callback(); topSkip->toggleOn(std::move(tracker).atLeastOneShownValue()); }; if (auto user = _peer->asUser()) { wrapButtons([=] { return fillUserButtons(user); }); } else if (auto channel = _peer->asChannel()) { if (!channel->isMegagroup()) { wrapButtons([=] { return fillChannelButtons(channel); }); } } } Ui::MultiSlideTracker DetailsFiller::fillUserButtons( not_null<UserData*> user) { using namespace rpl::mappers; Ui::MultiSlideTracker tracker; auto window = _controller->parentController(); auto addSendMessageButton = [&] { auto activePeerValue = window->activeChatValue( ) | rpl::map([](Dialogs::Key key) { return key.peer(); }); auto sendMessageVisible = rpl::combine( _controller->wrapValue(), std::move(activePeerValue), (_1 != Wrap::Side) || (_2 != user)); auto sendMessage = [window, user] { window->showPeerHistory( user, Window::SectionShow::Way::Forward); }; AddMainButton( _wrap, Lang::Viewer(lng_profile_send_message), std::move(sendMessageVisible), std::move(sendMessage), tracker); }; if (user->isSelf()) { auto separator = _wrap->add(object_ptr<Ui::SlideWrap<>>( _wrap, object_ptr<Ui::PlainShadow>(_wrap), st::infoProfileSeparatorPadding) )->setDuration( st::infoSlideDuration ); addSendMessageButton(); separator->toggleOn( std::move(tracker).atLeastOneShownValue() ); } else { addSendMessageButton(); AddMainButton( _wrap, Lang::Viewer(lng_info_add_as_contact), CanAddContactValue(user), [user] { Window::PeerMenuAddContact(user); }, tracker); } return tracker; } Ui::MultiSlideTracker DetailsFiller::fillChannelButtons( not_null<ChannelData*> channel) { using namespace rpl::mappers; Ui::MultiSlideTracker tracker; auto window = _controller->parentController(); auto activePeerValue = window->activeChatValue( ) | rpl::map([](Dialogs::Key key) { return key.peer(); }); auto viewChannelVisible = rpl::combine( _controller->wrapValue(), std::move(activePeerValue), (_1 != Wrap::Side) || (_2 != channel)); auto viewChannel = [=] { window->showPeerHistory( channel, Window::SectionShow::Way::Forward); }; AddMainButton( _wrap, Lang::Viewer(lng_profile_view_channel), std::move(viewChannelVisible), std::move(viewChannel), tracker); return tracker; } object_ptr<Ui::RpWidget> DetailsFiller::fill() { add(object_ptr<BoxContentDivider>(_wrap)); add(CreateSkipWidget(_wrap)); add(setupInfo()); if (!_peer->isSelf()) { add(setupMuteToggle()); } setupMainButtons(); add(CreateSkipWidget(_wrap)); return std::move(_wrap); } ActionsFiller::ActionsFiller( not_null<Controller*> controller, not_null<Ui::RpWidget*> parent, not_null<PeerData*> peer) : _controller(controller) , _parent(parent) , _peer(peer) { } void ActionsFiller::addInviteToGroupAction( not_null<UserData*> user) { AddActionButton( _wrap, Lang::Viewer(lng_profile_invite_to_group), CanInviteBotToGroupValue(user), [user] { AddBotToGroupBoxController::Start(user); }); } void ActionsFiller::addShareContactAction(not_null<UserData*> user) { AddActionButton( _wrap, Lang::Viewer(lng_info_share_contact), CanShareContactValue(user), [user] { Window::PeerMenuShareContactBox(user); }); } void ActionsFiller::addEditContactAction(not_null<UserData*> user) { AddActionButton( _wrap, Lang::Viewer(lng_info_edit_contact), IsContactValue(user), [user] { Ui::show(Box<AddContactBox>(user)); }); } void ActionsFiller::addDeleteContactAction( not_null<UserData*> user) { AddActionButton( _wrap, Lang::Viewer(lng_info_delete_contact), IsContactValue(user), [user] { Window::PeerMenuDeleteContact(user); }); } void ActionsFiller::addClearHistoryAction(not_null<UserData*> user) { AddActionButton( _wrap, Lang::Viewer(lng_profile_clear_history), rpl::single(true), Window::ClearHistoryHandler(user)); } void ActionsFiller::addDeleteConversationAction( not_null<UserData*> user) { AddActionButton( _wrap, Lang::Viewer(lng_profile_delete_conversation), rpl::single(true), Window::DeleteAndLeaveHandler(user)); } void ActionsFiller::addBotCommandActions(not_null<UserData*> user) { auto findBotCommand = [user](const QString &command) { if (!user->botInfo) { return QString(); } for_const (auto &data, user->botInfo->commands) { auto isSame = data.command.compare( command, Qt::CaseInsensitive) == 0; if (isSame) { return data.command; } } return QString(); }; auto hasBotCommandValue = [=](const QString &command) { return Notify::PeerUpdateValue( user, Notify::PeerUpdate::Flag::BotCommandsChanged ) | rpl::map([=] { return !findBotCommand(command).isEmpty(); }); }; auto sendBotCommand = [=](const QString &command) { auto original = findBotCommand(command); if (!original.isEmpty()) { Ui::showPeerHistory(user, ShowAtTheEndMsgId); App::sendBotCommand(user, user, '/' + original); } }; auto addBotCommand = [=](LangKey key, const QString &command) { AddActionButton( _wrap, Lang::Viewer(key), hasBotCommandValue(command), [=] { sendBotCommand(command); }); }; addBotCommand(lng_profile_bot_help, qsl("help")); addBotCommand(lng_profile_bot_settings, qsl("settings")); } void ActionsFiller::addReportAction() { auto peer = _peer; AddActionButton( _wrap, Lang::Viewer(lng_profile_report), rpl::single(true), [peer] { Ui::show(Box<ReportBox>(peer)); }, st::infoBlockButton); } void ActionsFiller::addBlockAction(not_null<UserData*> user) { auto text = Notify::PeerUpdateValue( user, Notify::PeerUpdate::Flag::UserIsBlocked ) | rpl::map([user] { switch (user->blockStatus()) { case UserData::BlockStatus::Blocked: return Lang::Viewer(user->botInfo ? lng_profile_unblock_bot : lng_profile_unblock_user); case UserData::BlockStatus::NotBlocked: default: return Lang::Viewer(user->botInfo ? lng_profile_block_bot : lng_profile_block_user); } }) | rpl::flatten_latest( ) | rpl::start_spawning(_wrap->lifetime()); auto toggleOn = rpl::duplicate( text ) | rpl::map([](const QString &text) { return !text.isEmpty(); }); auto callback = [user] { if (user->isBlocked()) { Auth().api().unblockUser(user); } else { Auth().api().blockUser(user); } }; AddActionButton( _wrap, rpl::duplicate(text), std::move(toggleOn), std::move(callback), st::infoBlockButton); } void ActionsFiller::addLeaveChannelAction( not_null<ChannelData*> channel) { AddActionButton( _wrap, Lang::Viewer(lng_profile_leave_channel), AmInChannelValue(channel), Window::DeleteAndLeaveHandler(channel)); } void ActionsFiller::addJoinChannelAction( not_null<ChannelData*> channel) { using namespace rpl::mappers; auto joinVisible = AmInChannelValue(channel) | rpl::map(!_1) | rpl::start_spawning(_wrap->lifetime()); AddActionButton( _wrap, Lang::Viewer(lng_profile_join_channel), rpl::duplicate(joinVisible), [channel] { Auth().api().joinChannel(channel); }); _wrap->add(object_ptr<Ui::SlideWrap<Ui::FixedHeightWidget>>( _wrap, CreateSkipWidget( _wrap, st::infoBlockButtonSkip)) )->setDuration( st::infoSlideDuration )->toggleOn( rpl::duplicate(joinVisible) ); } void ActionsFiller::fillUserActions(not_null<UserData*> user) { if (user->botInfo) { addInviteToGroupAction(user); } addShareContactAction(user); if (!user->isSelf()) { addEditContactAction(user); addDeleteContactAction(user); } addClearHistoryAction(user); addDeleteConversationAction(user); if (!user->isSelf()) { if (user->botInfo) { addBotCommandActions(user); } _wrap->add(CreateSkipWidget( _wrap, st::infoBlockButtonSkip)); if (user->botInfo) { addReportAction(); } addBlockAction(user); } } void ActionsFiller::fillChannelActions( not_null<ChannelData*> channel) { using namespace rpl::mappers; addJoinChannelAction(channel); addLeaveChannelAction(channel); if (!channel->amCreator()) { addReportAction(); } } object_ptr<Ui::RpWidget> ActionsFiller::fill() { auto wrapResult = [=](auto &&callback) { _wrap = object_ptr<Ui::VerticalLayout>(_parent); _wrap->add(CreateSkipWidget(_wrap)); callback(); _wrap->add(CreateSkipWidget(_wrap)); object_ptr<FloatingIcon>( _wrap, st::infoIconActions, st::infoIconPosition); return std::move(_wrap); }; if (auto user = _peer->asUser()) { return wrapResult([=] { fillUserActions(user); }); } else if (auto channel = _peer->asChannel()) { if (channel->isMegagroup()) { return { nullptr }; } return wrapResult([=] { fillChannelActions(channel); }); } return { nullptr }; } FeedDetailsFiller::FeedDetailsFiller( not_null<Controller*> controller, not_null<Ui::RpWidget*> parent, not_null<Data::Feed*> feed) : _controller(controller) , _parent(parent) , _feed(feed) , _wrap(_parent) { } object_ptr<Ui::RpWidget> FeedDetailsFiller::fill() { add(object_ptr<BoxContentDivider>(_wrap)); add(CreateSkipWidget(_wrap)); add(setupDefaultToggle()); add(CreateSkipWidget(_wrap)); return std::move(_wrap); } object_ptr<Ui::RpWidget> FeedDetailsFiller::setupDefaultToggle() { using namespace rpl::mappers; const auto feedId = _feed->id(); auto result = object_ptr<Button>( _wrap, Lang::Viewer(lng_info_feed_is_default), st::infoNotificationsButton); result->toggleOn( Auth().data().defaultFeedIdValue( ) | rpl::map(_1 == feedId) )->addClickHandler([=] { const auto makeDefault = (Auth().data().defaultFeedId() != feedId); const auto defaultFeedId = makeDefault ? feedId : 0; Auth().data().setDefaultFeedId(defaultFeedId); // Auth().api().saveDefaultFeedId(feedId, makeDefault); // #feed }); object_ptr<FloatingIcon>( result, st::infoIconNotifications, st::infoNotificationsIconPosition); return std::move(result); } } // namespace object_ptr<Ui::RpWidget> SetupDetails( not_null<Controller*> controller, not_null<Ui::RpWidget*> parent, not_null<PeerData*> peer) { DetailsFiller filler(controller, parent, peer); return filler.fill(); } object_ptr<Ui::RpWidget> SetupActions( not_null<Controller*> controller, not_null<Ui::RpWidget*> parent, not_null<PeerData*> peer) { ActionsFiller filler(controller, parent, peer); return filler.fill(); } void SetupAddChannelMember( not_null<Ui::RpWidget*> parent, not_null<ChannelData*> channel) { auto add = Ui::CreateChild<Ui::IconButton>( parent.get(), st::infoMembersAddMember); add->showOn(CanAddMemberValue(channel)); add->addClickHandler([channel] { Window::PeerMenuAddChannelMembers(channel); }); parent->widthValue( ) | rpl::start_with_next([add](int newWidth) { auto availableWidth = newWidth - st::infoMembersButtonPosition.x(); add->moveToLeft( availableWidth - add->width(), st::infoMembersButtonPosition.y(), newWidth); }, add->lifetime()); } object_ptr<Ui::RpWidget> SetupChannelMembers( not_null<Controller*> controller, not_null<Ui::RpWidget*> parent, not_null<PeerData*> peer) { using namespace rpl::mappers; auto channel = peer->asChannel(); if (!channel || channel->isMegagroup()) { return { nullptr }; } auto membersShown = rpl::combine( MembersCountValue(channel), Data::PeerFullFlagValue( channel, MTPDchannelFull::Flag::f_can_view_participants), (_1 > 0) && _2); auto membersText = MembersCountValue( channel ) | rpl::map([](int count) { return lng_chat_status_members(lt_count, count); }); auto membersCallback = [controller, channel] { controller->showSection(Info::Memento( channel->id, Section::Type::Members)); }; auto result = object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>( parent, object_ptr<Ui::VerticalLayout>(parent)); result->setDuration( st::infoSlideDuration )->toggleOn( std::move(membersShown) ); auto members = result->entity(); members->add(object_ptr<BoxContentDivider>(members)); members->add(CreateSkipWidget(members)); auto button = AddActionButton( members, std::move(membersText), rpl::single(true), std::move(membersCallback))->entity(); SetupAddChannelMember(button, channel); object_ptr<FloatingIcon>( members, st::infoIconMembers, st::infoChannelMembersIconPosition); members->add(CreateSkipWidget(members)); return std::move(result); } object_ptr<Ui::RpWidget> SetupFeedDetails( not_null<Controller*> controller, not_null<Ui::RpWidget*> parent, not_null<Data::Feed*> feed) { FeedDetailsFiller filler(controller, parent, feed); return filler.fill(); } } // namespace Profile } // namespace Info ������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/profile/info_profile_actions.h����������������������������0000664�0000000�0000000�00000001764�13262451251�0026372�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace Data { class Feed; } // namespace Data namespace Ui { class RpWidget; } // namespace Ui namespace Info { class Controller; namespace Profile { object_ptr<Ui::RpWidget> SetupDetails( not_null<Controller*> controller, not_null<Ui::RpWidget*> parent, not_null<PeerData*> peer); object_ptr<Ui::RpWidget> SetupActions( not_null<Controller*> controller, not_null<Ui::RpWidget*> parent, not_null<PeerData*> peer); object_ptr<Ui::RpWidget> SetupChannelMembers( not_null<Controller*> controller, not_null<Ui::RpWidget*> parent, not_null<PeerData*> peer); object_ptr<Ui::RpWidget> SetupFeedDetails( not_null<Controller*> controller, not_null<Ui::RpWidget*> parent, not_null<Data::Feed*> feed); } // namespace Profile } // namespace Info ������������tdesktop-1.2.17/Telegram/SourceFiles/info/profile/info_profile_button.cpp���������������������������0000664�0000000�0000000�00000006122�13262451251�0026571�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/profile/info_profile_button.h" #include <rpl/never.h> #include "ui/widgets/checkbox.h" #include "styles/style_info.h" namespace Info { namespace Profile { Button::Button( QWidget *parent, rpl::producer<QString> &&text) : Button(parent, std::move(text), st::infoProfileButton) { } Button::Button( QWidget *parent, rpl::producer<QString> &&text, const style::InfoProfileButton &st) : RippleButton(parent, st.ripple) , _st(st) { std::move( text ) | rpl::start_with_next([this](QString &&value) { setText(std::move(value)); }, lifetime()); } Button *Button::toggleOn(rpl::producer<bool> &&toggled) { Expects(_toggle == nullptr); _toggle = std::make_unique<Ui::ToggleView>( isOver() ? _st.toggleOver : _st.toggle, false, [this] { rtlupdate(toggleRect()); }); addClickHandler([this] { _toggle->setCheckedAnimated(!_toggle->checked()); }); std::move( toggled ) | rpl::start_with_next([this](bool toggled) { _toggle->setCheckedAnimated(toggled); }, lifetime()); _toggle->finishAnimating(); return this; } rpl::producer<bool> Button::toggledValue() const { if (_toggle) { return _toggle->checkedValue(); } return rpl::never<bool>(); } void Button::paintEvent(QPaintEvent *e) { Painter p(this); auto ms = getms(); auto paintOver = (isOver() || isDown()); p.fillRect(e->rect(), paintOver ? _st.textBgOver : _st.textBg); paintRipple(p, 0, 0, ms); auto outerw = width(); p.setFont(_st.font); p.setPen(paintOver ? _st.textFgOver : _st.textFg); p.drawTextLeft( _st.padding.left(), _st.padding.top(), outerw, _text, _textWidth); if (_toggle) { auto rect = toggleRect(); _toggle->paint(p, rect.left(), rect.top(), outerw, ms); } } QRect Button::toggleRect() const { Expects(_toggle != nullptr); auto size = _toggle->getSize(); auto left = width() - _st.toggleSkip - size.width(); auto top = (height() - size.height()) / 2; return { QPoint(left, top), size }; } int Button::resizeGetHeight(int newWidth) { updateVisibleText(newWidth); return _st.padding.top() + _st.height + _st.padding.bottom(); } void Button::onStateChanged( State was, StateChangeSource source) { RippleButton::onStateChanged(was, source); if (_toggle) { _toggle->setStyle(isOver() ? _st.toggleOver : _st.toggle); } } void Button::setText(QString &&text) { _original = std::move(text); _originalWidth = _st.font->width(_original); updateVisibleText(width()); } void Button::updateVisibleText(int newWidth) { auto availableWidth = newWidth - _st.padding.left() - _st.padding.right(); if (_toggle) { availableWidth -= (width() - toggleRect().x()); } accumulate_max(availableWidth, 0); if (availableWidth < _originalWidth) { _text = _st.font->elided(_original, availableWidth); _textWidth = _st.font->width(_text); } else { _text = _original; _textWidth = _originalWidth; } update(); } } // namespace Profile } // namespace Info ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/profile/info_profile_button.h�����������������������������0000664�0000000�0000000�00000002226�13262451251�0026237�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/widgets/buttons.h" namespace Ui { class ToggleView; } // namespace Ui namespace Info { namespace Profile { class Button : public Ui::RippleButton { public: Button( QWidget *parent, rpl::producer<QString> &&text); Button( QWidget *parent, rpl::producer<QString> &&text, const style::InfoProfileButton &st); Button *toggleOn(rpl::producer<bool> &&toggled); rpl::producer<bool> toggledValue() const; protected: int resizeGetHeight(int newWidth) override; void onStateChanged( State was, StateChangeSource source) override; void paintEvent(QPaintEvent *e) override; private: void setText(QString &&text); QRect toggleRect() const; void updateVisibleText(int newWidth); const style::InfoProfileButton &_st; QString _original; QString _text; int _originalWidth = 0; int _textWidth = 0; std::unique_ptr<Ui::ToggleView> _toggle; }; } // namespace Profile } // namespace Info ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/profile/info_profile_cover.cpp����������������������������0000664�0000000�0000000�00000030741�13262451251�0026400�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/profile/info_profile_cover.h" #include <rpl/never.h> #include <rpl/combine.h> #include "data/data_photo.h" #include "data/data_peer_values.h" #include "info/profile/info_profile_values.h" #include "info/info_controller.h" #include "info/info_memento.h" #include "lang/lang_keys.h" #include "styles/style_info.h" #include "ui/widgets/labels.h" #include "ui/effects/ripple_animation.h" #include "ui/special_buttons.h" #include "window/window_controller.h" #include "observer_peer.h" #include "messenger.h" #include "auth_session.h" #include "apiwrap.h" namespace Info { namespace Profile { namespace { class SectionToggle : public Ui::AbstractCheckView { public: SectionToggle( const style::InfoToggle &st, bool checked, base::lambda<void()> updateCallback); QSize getSize() const override; void paint( Painter &p, int left, int top, int outerWidth, TimeMs ms) override; QImage prepareRippleMask() const override; bool checkRippleStartPosition(QPoint position) const override; private: QSize rippleSize() const; const style::InfoToggle &_st; }; SectionToggle::SectionToggle( const style::InfoToggle &st, bool checked, base::lambda<void()> updateCallback) : AbstractCheckView(st.duration, checked, std::move(updateCallback)) , _st(st) { } QSize SectionToggle::getSize() const { return QSize(_st.size, _st.size); } void SectionToggle::paint( Painter &p, int left, int top, int outerWidth, TimeMs ms) { auto sqrt2 = sqrt(2.); auto vLeft = rtlpoint(left + _st.skip, 0, outerWidth).x() + 0.; auto vTop = top + _st.skip + 0.; auto vWidth = _st.size - 2 * _st.skip; auto vHeight = _st.size - 2 * _st.skip; auto vStroke = _st.stroke / sqrt2; constexpr auto kPointCount = 6; std::array<QPointF, kPointCount> pathV = { { { vLeft, vTop + (vHeight / 4.) + vStroke }, { vLeft + vStroke, vTop + (vHeight / 4.) }, { vLeft + (vWidth / 2.), vTop + (vHeight * 3. / 4.) - vStroke }, { vLeft + vWidth - vStroke, vTop + (vHeight / 4.) }, { vLeft + vWidth, vTop + (vHeight / 4.) + vStroke }, { vLeft + (vWidth / 2.), vTop + (vHeight * 3. / 4.) + vStroke }, } }; auto toggled = currentAnimationValue(ms); auto alpha = (toggled - 1.) * M_PI_2; auto cosalpha = cos(alpha); auto sinalpha = sin(alpha); auto shiftx = vLeft + (vWidth / 2.); auto shifty = vTop + (vHeight / 2.); for (auto &point : pathV) { auto x = point.x() - shiftx; auto y = point.y() - shifty; point.setX(shiftx + x * cosalpha - y * sinalpha); point.setY(shifty + y * cosalpha + x * sinalpha); } QPainterPath path; path.moveTo(pathV[0]); for (int i = 1; i != kPointCount; ++i) { path.lineTo(pathV[i]); } path.lineTo(pathV[0]); PainterHighQualityEnabler hq(p); p.fillPath(path, _st.color); } QImage SectionToggle::prepareRippleMask() const { return Ui::RippleAnimation::ellipseMask(rippleSize()); } QSize SectionToggle::rippleSize() const { return getSize() + 2 * QSize( _st.rippleAreaPadding, _st.rippleAreaPadding); } bool SectionToggle::checkRippleStartPosition(QPoint position) const { return QRect(QPoint(0, 0), rippleSize()).contains(position); } auto MembersStatusText(int count) { return lng_chat_status_members(lt_count, count); }; auto OnlineStatusText(int count) { return lng_chat_status_online(lt_count, count); }; auto ChatStatusText(int fullCount, int onlineCount, bool isGroup) { if (onlineCount > 1 && onlineCount <= fullCount) { return lng_chat_status_members_online( lt_members_count, MembersStatusText(fullCount), lt_online_count, OnlineStatusText(onlineCount)); } else if (fullCount > 0) { return lng_chat_status_members(lt_count, fullCount); } return lang(isGroup ? lng_group_status : lng_channel_status); }; } // namespace SectionWithToggle *SectionWithToggle::setToggleShown( rpl::producer<bool> &&shown) { _toggle.create( this, QString(), st::infoToggleCheckbox, std::make_unique<SectionToggle>( st::infoToggle, false, [this] { _toggle->updateCheck(); })); _toggle->hide(); _toggle->lower(); _toggle->setCheckAlignment(style::al_right); widthValue( ) | rpl::start_with_next([this](int newValue) { _toggle->setGeometry(0, 0, newValue, height()); }, _toggle->lifetime()); std::move( shown ) | rpl::start_with_next([this](bool shown) { if (_toggle->isHidden() == shown) { _toggle->setVisible(shown); _toggleShown.fire_copy(shown); } }, lifetime()); return this; } void SectionWithToggle::toggle(bool toggled, anim::type animated) { if (_toggle) { _toggle->setChecked(toggled); if (animated == anim::type::instant) { _toggle->finishAnimating(); } } } bool SectionWithToggle::toggled() const { return _toggle ? _toggle->checked() : false; } rpl::producer<bool> SectionWithToggle::toggledValue() const { if (_toggle) { return rpl::single( _toggle->checked() ) | rpl::then(base::ObservableViewer(_toggle->checkedChanged)); } return rpl::never<bool>(); } rpl::producer<bool> SectionWithToggle::toggleShownValue() const { return _toggleShown.events_starting_with( _toggle && !_toggle->isHidden()); } int SectionWithToggle::toggleSkip() const { return (!_toggle || _toggle->isHidden()) ? 0 : st::infoToggleCheckbox.checkPosition.x() + _toggle->checkRect().width(); } Cover::Cover( QWidget *parent, not_null<Controller*> controller) : SectionWithToggle( parent, st::infoProfilePhotoTop + st::infoProfilePhoto.size.height() + st::infoProfilePhotoBottom) , _controller(controller) , _peer(_controller->key().peer()) , _userpic( this, controller->parentController(), _peer, Ui::UserpicButton::Role::OpenPhoto, st::infoProfilePhoto) , _name(this, st::infoProfileNameLabel) , _status( this, _peer->isMegagroup() ? st::infoProfileMegagroupStatusLabel : st::infoProfileStatusLabel) , _refreshStatusTimer([this] { refreshStatusText(); }) { _peer->updateFull(); _name->setSelectable(true); _name->setContextCopyText(lang(lng_profile_copy_fullname)); if (!_peer->isMegagroup()) { _status->setAttribute(Qt::WA_TransparentForMouseEvents); } initViewers(); setupChildGeometry(); } void Cover::setupChildGeometry() { using namespace rpl::mappers; // // Visual Studio 2017 15.5.1 internal compiler error here. // See https://developercommunity.visualstudio.com/content/problem/165155/ice-regression-in-1551-after-successfull-build-in.html // //rpl::combine( // toggleShownValue(), // widthValue(), // _2 //) | rpl::map([](bool shown, int width) { rpl::combine( toggleShownValue(), widthValue() ) | rpl::map([](bool shown, int width) { return width; }) | rpl::start_with_next([this](int newWidth) { _userpic->moveToLeft( st::infoProfilePhotoLeft, st::infoProfilePhotoTop, newWidth); refreshNameGeometry(newWidth); refreshStatusGeometry(newWidth); }, lifetime()); } Cover *Cover::setOnlineCount(rpl::producer<int> &&count) { std::move( count ) | rpl::start_with_next([this](int count) { _onlineCount = count; refreshStatusText(); }, lifetime()); return this; } void Cover::initViewers() { using Flag = Notify::PeerUpdate::Flag; Notify::PeerUpdateValue( _peer, Flag::NameChanged ) | rpl::start_with_next( [this] { refreshNameText(); }, lifetime()); Notify::PeerUpdateValue( _peer, Flag::UserOnlineChanged | Flag::MembersChanged ) | rpl::start_with_next( [this] { refreshStatusText(); }, lifetime()); if (!_peer->isUser()) { Notify::PeerUpdateValue( _peer, Flag::ChannelRightsChanged | Flag::ChatCanEdit ) | rpl::start_with_next( [this] { refreshUploadPhotoOverlay(); }, lifetime()); } VerifiedValue( _peer ) | rpl::start_with_next( [this](bool verified) { setVerified(verified); }, lifetime()); } void Cover::refreshUploadPhotoOverlay() { _userpic->switchChangePhotoOverlay([&] { if (auto chat = _peer->asChat()) { return chat->canEdit(); } else if (auto channel = _peer->asChannel()) { return channel->canEditInformation(); } return false; }()); } void Cover::setVerified(bool verified) { if ((_verifiedCheck != nullptr) == verified) { return; } if (verified) { _verifiedCheck.create(this); _verifiedCheck->show(); _verifiedCheck->resize(st::infoVerifiedCheck.size()); _verifiedCheck->paintRequest( ) | rpl::start_with_next([check = _verifiedCheck.data()] { Painter p(check); st::infoVerifiedCheck.paint(p, 0, 0, check->width()); }, _verifiedCheck->lifetime()); } else { _verifiedCheck.destroy(); } refreshNameGeometry(width()); } void Cover::refreshNameText() { _name->setText(App::peerName(_peer)); refreshNameGeometry(width()); } void Cover::refreshStatusText() { auto hasMembersLink = [&] { if (auto megagroup = _peer->asMegagroup()) { return megagroup->canViewMembers(); } return false; }(); auto statusText = [&] { auto currentTime = unixtime(); if (auto user = _peer->asUser()) { const auto result = Data::OnlineTextFull(user, currentTime); const auto showOnline = Data::OnlineTextActive(user, currentTime); const auto updateIn = Data::OnlineChangeTimeout(user, currentTime); if (showOnline) { _refreshStatusTimer.callOnce(updateIn); } return showOnline ? textcmdLink(1, result) : result; } else if (auto chat = _peer->asChat()) { if (!chat->amIn()) { return lang(lng_chat_status_unaccessible); } auto fullCount = std::max( chat->count, int(chat->participants.size())); return ChatStatusText(fullCount, _onlineCount, true); } else if (auto channel = _peer->asChannel()) { auto fullCount = qMax(channel->membersCount(), 1); auto result = ChatStatusText( fullCount, _onlineCount, channel->isMegagroup()); return hasMembersLink ? textcmdLink(1, result) : result; } return lang(lng_chat_status_unaccessible); }(); _status->setRichText(statusText); if (hasMembersLink) { _status->setLink(1, std::make_shared<LambdaClickHandler>([=] { _controller->showSection(Info::Memento( _controller->peerId(), Section::Type::Members)); })); } refreshStatusGeometry(width()); } Cover::~Cover() { } void Cover::refreshNameGeometry(int newWidth) { auto nameLeft = st::infoProfileNameLeft; auto nameTop = st::infoProfileNameTop; auto nameWidth = newWidth - nameLeft - st::infoProfileNameRight - toggleSkip(); if (_verifiedCheck) { nameWidth -= st::infoVerifiedCheckPosition.x() + st::infoVerifiedCheck.width(); } _name->resizeToNaturalWidth(nameWidth); _name->moveToLeft(nameLeft, nameTop, newWidth); if (_verifiedCheck) { auto checkLeft = nameLeft + _name->width() + st::infoVerifiedCheckPosition.x(); auto checkTop = nameTop + st::infoVerifiedCheckPosition.y(); _verifiedCheck->moveToLeft(checkLeft, checkTop, newWidth); } } void Cover::refreshStatusGeometry(int newWidth) { auto statusWidth = newWidth - st::infoProfileStatusLeft - st::infoProfileStatusRight - toggleSkip(); _status->resizeToWidth(statusWidth); _status->moveToLeft( st::infoProfileStatusLeft, st::infoProfileStatusTop, newWidth); } QMargins SharedMediaCover::getMargins() const { return QMargins(0, 0, 0, st::infoSharedMediaBottomSkip); } SharedMediaCover::SharedMediaCover(QWidget *parent) : SectionWithToggle(parent, st::infoSharedMediaCoverHeight) { createLabel(); } SharedMediaCover *SharedMediaCover::setToggleShown(rpl::producer<bool> &&shown) { return static_cast<SharedMediaCover*>( SectionWithToggle::setToggleShown(std::move(shown))); } void SharedMediaCover::createLabel() { using namespace rpl::mappers; auto label = object_ptr<Ui::FlatLabel>( this, Lang::Viewer(lng_profile_shared_media) | ToUpperValue(), st::infoBlockHeaderLabel); label->setAttribute(Qt::WA_TransparentForMouseEvents); // // Visual Studio 2017 15.5.1 internal compiler error here. // See https://developercommunity.visualstudio.com/content/problem/165155/ice-regression-in-1551-after-successfull-build-in.html // //rpl::combine( // toggleShownValue(), // widthValue(), // _2 //) | rpl::map([](bool shown, int width) { rpl::combine( toggleShownValue(), widthValue() ) | rpl::map([](bool shown, int width) { return width; }) | rpl::start_with_next([this, weak = label.data()](int newWidth) { auto availableWidth = newWidth - st::infoBlockHeaderPosition.x() - st::infoSharedMediaButton.padding.right() - toggleSkip(); weak->resizeToWidth(availableWidth); weak->moveToLeft( st::infoBlockHeaderPosition.x(), st::infoBlockHeaderPosition.y(), newWidth); }, label->lifetime()); } } // namespace Profile } // namespace Info �������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/profile/info_profile_cover.h������������������������������0000664�0000000�0000000�00000004441�13262451251�0026043�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/wrap/padding_wrap.h" #include "ui/widgets/checkbox.h" #include "base/timer.h" namespace style { struct InfoToggle; } // namespace style namespace Ui { class UserpicButton; class FlatLabel; template <typename Widget> class SlideWrap; } // namespace Ui namespace Info { class Controller; } // namespace Info namespace Info { namespace Profile { class SectionWithToggle : public Ui::FixedHeightWidget { public: using FixedHeightWidget::FixedHeightWidget; SectionWithToggle *setToggleShown(rpl::producer<bool> &&shown); void toggle(bool toggled, anim::type animated); bool toggled() const; rpl::producer<bool> toggledValue() const; protected: rpl::producer<bool> toggleShownValue() const; int toggleSkip() const; private: object_ptr<Ui::Checkbox> _toggle = { nullptr }; rpl::event_stream<bool> _toggleShown; }; class Cover : public SectionWithToggle { public: Cover( QWidget *parent, not_null<Controller*> controller); Cover *setOnlineCount(rpl::producer<int> &&count); Cover *setToggleShown(rpl::producer<bool> &&shown) { return static_cast<Cover*>( SectionWithToggle::setToggleShown(std::move(shown))); } ~Cover(); private: void setupChildGeometry(); void initViewers(); void refreshNameText(); void refreshStatusText(); void refreshNameGeometry(int newWidth); void refreshStatusGeometry(int newWidth); void refreshUploadPhotoOverlay(); void setVerified(bool verified); not_null<Controller*> _controller; not_null<PeerData*> _peer; int _onlineCount = 0; object_ptr<Ui::UserpicButton> _userpic; object_ptr<Ui::FlatLabel> _name = { nullptr }; object_ptr<Ui::RpWidget> _verifiedCheck = { nullptr }; object_ptr<Ui::FlatLabel> _status = { nullptr }; //object_ptr<CoverDropArea> _dropArea = { nullptr }; base::Timer _refreshStatusTimer; }; class SharedMediaCover : public SectionWithToggle { public: SharedMediaCover(QWidget *parent); SharedMediaCover *setToggleShown(rpl::producer<bool> &&shown); QMargins getMargins() const override; private: void createLabel(); }; } // namespace Profile } // namespace Info �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/profile/info_profile_icon.cpp�����������������������������0000664�0000000�0000000�00000001740�13262451251�0026207�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/profile/info_profile_icon.h" namespace Info { namespace Profile { FloatingIcon::FloatingIcon( RpWidget *parent, const style::icon &icon, QPoint position) : FloatingIcon(parent, icon, position, Tag{}) { } FloatingIcon::FloatingIcon( RpWidget *parent, const style::icon &icon, QPoint position, const Tag &) : RpWidget(parent) , _icon(&icon) , _point(position) { resize( _point.x() + _icon->width(), _point.y() + _icon->height()); setAttribute(Qt::WA_TransparentForMouseEvents); parent->widthValue( ) | rpl::start_with_next( [this] { moveToLeft(0, 0); }, lifetime()); } void FloatingIcon::paintEvent(QPaintEvent *e) { Painter p(this); _icon->paint(p, _point, width()); } } // namespace Profile } // namespace Info ��������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/profile/info_profile_icon.h�������������������������������0000664�0000000�0000000�00000001336�13262451251�0025655�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/rp_widget.h" namespace Info { namespace Profile { class FloatingIcon : public Ui::RpWidget { public: FloatingIcon( RpWidget *parent, const style::icon &icon, QPoint position); protected: void paintEvent(QPaintEvent *e) override; private: struct Tag { }; FloatingIcon( RpWidget *parent, const style::icon &icon, QPoint position, const Tag &); not_null<const style::icon*> _icon; QPoint _point; }; } // namespace Profile } // namespace Info ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp���������������������0000664�0000000�0000000�00000020034�13262451251�0027732�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/profile/info_profile_inner_widget.h" #include <rpl/combine.h> #include <rpl/combine_previous.h> #include <rpl/flatten_latest.h> #include "info/info_memento.h" #include "info/info_controller.h" #include "info/profile/info_profile_button.h" #include "info/profile/info_profile_widget.h" #include "info/profile/info_profile_text.h" #include "info/profile/info_profile_values.h" #include "info/profile/info_profile_cover.h" #include "info/profile/info_profile_icon.h" #include "info/profile/info_profile_members.h" #include "info/profile/info_profile_actions.h" #include "info/media/info_media_buttons.h" #include "boxes/abstract_box.h" #include "boxes/add_contact_box.h" #include "boxes/confirm_box.h" #include "boxes/report_box.h" #include "mainwidget.h" #include "auth_session.h" #include "apiwrap.h" #include "window/main_window.h" #include "window/window_controller.h" #include "storage/storage_shared_media.h" #include "lang/lang_keys.h" #include "styles/style_info.h" #include "styles/style_boxes.h" #include "ui/widgets/buttons.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/scroll_area.h" #include "ui/widgets/shadow.h" #include "ui/wrap/slide_wrap.h" #include "ui/wrap/vertical_layout.h" #include "data/data_shared_media.h" namespace Info { namespace Profile { InnerWidget::InnerWidget( QWidget *parent, not_null<Controller*> controller) : RpWidget(parent) , _controller(controller) , _peer(_controller->key().peer()) , _migrated(_controller->migrated()) , _content(setupContent(this)) { _content->heightValue( ) | rpl::start_with_next([this](int height) { if (!_inResize) { resizeToWidth(width()); updateDesiredHeight(); } }, lifetime()); } bool InnerWidget::canHideDetailsEver() const { return false;// (_peer->isChat() || _peer->isMegagroup()); } rpl::producer<bool> InnerWidget::canHideDetails() const { using namespace rpl::mappers; return MembersCountValue(_peer) | rpl::map(_1 > 0); } object_ptr<Ui::RpWidget> InnerWidget::setupContent( not_null<RpWidget*> parent) { auto result = object_ptr<Ui::VerticalLayout>(parent); _cover = result->add(object_ptr<Cover>( result, _controller)); _cover->setOnlineCount(rpl::single(0)); auto details = SetupDetails(_controller, parent, _peer); if (canHideDetailsEver()) { _cover->setToggleShown(canHideDetails()); _infoWrap = result->add(object_ptr<Ui::SlideWrap<>>( result, std::move(details)) )->setDuration( st::infoSlideDuration )->toggleOn( _cover->toggledValue() ); } else { result->add(std::move(details)); } result->add(setupSharedMedia(result.data())); if (auto members = SetupChannelMembers(_controller, result.data(), _peer)) { result->add(std::move(members)); } result->add(object_ptr<BoxContentDivider>(result)); if (auto actions = SetupActions(_controller, result.data(), _peer)) { result->add(std::move(actions)); } if (_peer->isChat() || _peer->isMegagroup()) { _members = result->add(object_ptr<Members>( result, _controller)); _members->scrollToRequests( ) | rpl::start_with_next([this](Ui::ScrollToRequest request) { auto min = (request.ymin < 0) ? request.ymin : mapFromGlobal(_members->mapToGlobal({ 0, request.ymin })).y(); auto max = (request.ymin < 0) ? mapFromGlobal(_members->mapToGlobal({ 0, 0 })).y() : (request.ymax < 0) ? request.ymax : mapFromGlobal(_members->mapToGlobal({ 0, request.ymax })).y(); _scrollToRequests.fire({ min, max }); }, _members->lifetime()); _cover->setOnlineCount(_members->onlineCountValue()); } return std::move(result); } object_ptr<Ui::RpWidget> InnerWidget::setupSharedMedia( not_null<RpWidget*> parent) { using namespace rpl::mappers; using MediaType = Media::Type; auto content = object_ptr<Ui::VerticalLayout>(parent); auto tracker = Ui::MultiSlideTracker(); auto addMediaButton = [&]( MediaType type, const style::icon &icon) { auto result = Media::AddButton( content, _controller, _peer, _migrated, type, tracker); object_ptr<Profile::FloatingIcon>( result, icon, st::infoSharedMediaButtonIconPosition); }; auto addCommonGroupsButton = [&]( not_null<UserData*> user, const style::icon &icon) { auto result = Media::AddCommonGroupsButton( content, _controller, user, tracker); object_ptr<Profile::FloatingIcon>( result, icon, st::infoSharedMediaButtonIconPosition); }; addMediaButton(MediaType::Photo, st::infoIconMediaPhoto); addMediaButton(MediaType::Video, st::infoIconMediaVideo); addMediaButton(MediaType::File, st::infoIconMediaFile); addMediaButton(MediaType::MusicFile, st::infoIconMediaAudio); addMediaButton(MediaType::Link, st::infoIconMediaLink); if (auto user = _peer->asUser()) { addCommonGroupsButton(user, st::infoIconMediaGroup); } addMediaButton(MediaType::VoiceFile, st::infoIconMediaVoice); // addMediaButton(MediaType::RoundFile, st::infoIconMediaRound); auto result = object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>( parent, object_ptr<Ui::VerticalLayout>(parent) ); // Allows removing shared media links in third column. // Was done for tabs support. // //using ToggledData = std::tuple<bool, Wrap, bool>; //rpl::combine( // tracker.atLeastOneShownValue(), // _controller->wrapValue(), // _isStackBottom.value() //) | rpl::combine_previous( // ToggledData() //) | rpl::start_with_next([wrap = result.data()]( // const ToggledData &was, // const ToggledData &now) { // bool wasOneShown, wasStackBottom, nowOneShown, nowStackBottom; // Wrap wasWrap, nowWrap; // std::tie(wasOneShown, wasWrap, wasStackBottom) = was; // std::tie(nowOneShown, nowWrap, nowStackBottom) = now; // // MSVC Internal Compiler Error // //auto [wasOneShown, wasWrap, wasStackBottom] = was; // //auto [nowOneShown, nowWrap, nowStackBottom] = now; // wrap->toggle( // nowOneShown && (nowWrap != Wrap::Side || !nowStackBottom), // (wasStackBottom == nowStackBottom && wasWrap == nowWrap) // ? anim::type::normal // : anim::type::instant); //}, result->lifetime()); // // Using that instead result->setDuration( st::infoSlideDuration )->toggleOn( tracker.atLeastOneShownValue() ); auto layout = result->entity(); layout->add(object_ptr<BoxContentDivider>(layout)); layout->add(object_ptr<Ui::FixedHeightWidget>( layout, st::infoSharedMediaBottomSkip) )->setAttribute(Qt::WA_TransparentForMouseEvents); layout->add(std::move(content)); layout->add(object_ptr<Ui::FixedHeightWidget>( layout, st::infoSharedMediaBottomSkip) )->setAttribute(Qt::WA_TransparentForMouseEvents); _sharedMediaWrap = result; return std::move(result); } int InnerWidget::countDesiredHeight() const { return _content->height() + (_members ? (_members->desiredHeight() - _members->height()) : 0); } void InnerWidget::visibleTopBottomUpdated( int visibleTop, int visibleBottom) { setChildVisibleTopBottom(_content, visibleTop, visibleBottom); } void InnerWidget::saveState(not_null<Memento*> memento) { memento->setInfoExpanded(_cover->toggled()); if (_members) { memento->setMembersState(_members->saveState()); } } void InnerWidget::restoreState(not_null<Memento*> memento) { _cover->toggle(memento->infoExpanded(), anim::type::instant); if (_members) { _members->restoreState(memento->membersState()); } if (_infoWrap) { _infoWrap->finishAnimating(); } if (_sharedMediaWrap) { _sharedMediaWrap->finishAnimating(); } } rpl::producer<Ui::ScrollToRequest> InnerWidget::scrollToRequests() const { return _scrollToRequests.events(); } rpl::producer<int> InnerWidget::desiredHeightValue() const { return _desiredHeight.events_starting_with(countDesiredHeight()); } int InnerWidget::resizeGetHeight(int newWidth) { _inResize = true; auto guard = gsl::finally([&] { _inResize = false; }); _content->resizeToWidth(newWidth); _content->moveToLeft(0, 0); updateDesiredHeight(); return _content->heightNoMargins(); } } // namespace Profile } // namespace Info ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/profile/info_profile_inner_widget.h�����������������������0000664�0000000�0000000�00000004004�13262451251�0027376�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/variable.h> #include "ui/rp_widget.h" namespace Window { class Controller; } // namespace Window namespace Ui { class VerticalLayout; template <typename Widget> class SlideWrap; struct ScrollToRequest; class MultiSlideTracker; } // namespace Ui namespace Info { enum class Wrap; class Controller; namespace Profile { class Memento; class Members; class Cover; class InnerWidget final : public Ui::RpWidget { public: InnerWidget( QWidget *parent, not_null<Controller*> controller); void saveState(not_null<Memento*> memento); void restoreState(not_null<Memento*> memento); void setIsStackBottom(bool isStackBottom) { _isStackBottom = isStackBottom; } rpl::producer<Ui::ScrollToRequest> scrollToRequests() const; rpl::producer<int> desiredHeightValue() const override; protected: int resizeGetHeight(int newWidth) override; void visibleTopBottomUpdated( int visibleTop, int visibleBottom) override; private: object_ptr<RpWidget> setupContent(not_null<RpWidget*> parent); object_ptr<RpWidget> setupSharedMedia(not_null<RpWidget*> parent); int countDesiredHeight() const; void updateDesiredHeight() { _desiredHeight.fire(countDesiredHeight()); } bool canHideDetailsEver() const; rpl::producer<bool> canHideDetails() const; rpl::variable<bool> _isStackBottom = true; const not_null<Controller*> _controller; const not_null<PeerData*> _peer; PeerData * const _migrated = nullptr; Members *_members = nullptr; Cover *_cover = nullptr; Ui::SlideWrap<RpWidget> *_infoWrap = nullptr; Ui::SlideWrap<RpWidget> *_sharedMediaWrap = nullptr; object_ptr<RpWidget> _content; bool _inResize = false; rpl::event_stream<Ui::ScrollToRequest> _scrollToRequests; rpl::event_stream<int> _desiredHeight; }; } // namespace Profile } // namespace Info ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/profile/info_profile_members.cpp��������������������������0000664�0000000�0000000�00000030104�13262451251�0026705�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/profile/info_profile_members.h" #include <rpl/combine.h> #include "info/profile/info_profile_widget.h" #include "info/profile/info_profile_values.h" #include "info/profile/info_profile_icon.h" #include "info/profile/info_profile_values.h" #include "info/profile/info_profile_button.h" #include "info/profile/info_profile_members_controllers.h" #include "info/members/info_members_widget.h" #include "info/info_content_widget.h" #include "info/info_controller.h" #include "info/info_memento.h" #include "ui/widgets/labels.h" #include "ui/widgets/buttons.h" #include "ui/widgets/input_fields.h" #include "ui/widgets/scroll_area.h" #include "ui/wrap/padding_wrap.h" #include "ui/search_field_controller.h" #include "styles/style_boxes.h" #include "styles/style_info.h" #include "lang/lang_keys.h" #include "boxes/confirm_box.h" #include "boxes/peer_list_controllers.h" #include "window/window_controller.h" namespace Info { namespace Profile { namespace { constexpr auto kEnableSearchMembersAfterCount = 20; } // namespace Members::Members( QWidget *parent, not_null<Controller*> controller) : RpWidget(parent) , _controller(controller) , _peer(_controller->key().peer()) , _listController(CreateMembersController(controller, _peer)) { setupHeader(); setupList(); setContent(_list.data()); _listController->setDelegate(static_cast<PeerListDelegate*>(this)); _controller->searchFieldController()->queryValue( ) | rpl::start_with_next([this](QString &&query) { peerListScrollToTop(); content()->searchQueryChanged(std::move(query)); }, lifetime()); MembersCountValue( _peer ) | rpl::start_with_next([this](int count) { const auto enabled = (count >= kEnableSearchMembersAfterCount); _controller->setSearchEnabledByContent(enabled); }, lifetime()); } int Members::desiredHeight() const { auto desired = _header ? _header->height() : 0; auto count = [this] { if (auto chat = _peer->asChat()) { return chat->count; } else if (auto channel = _peer->asChannel()) { return channel->membersCount(); } return 0; }(); desired += qMax(count, _list->fullRowsCount()) * st::infoMembersList.item.height; return qMax(height(), desired); } rpl::producer<int> Members::onlineCountValue() const { return _listController->onlineCountValue(); } rpl::producer<Ui::ScrollToRequest> Members::scrollToRequests() const { return _scrollToRequests.events(); } std::unique_ptr<MembersState> Members::saveState() { auto result = std::make_unique<MembersState>(); result->list = _listController->saveState(); //if (_searchShown) { // result->search = _searchField->getLastText(); //} return result; } void Members::restoreState(std::unique_ptr<MembersState> state) { if (!state) { return; } _listController->restoreState(std::move(state->list)); //updateSearchEnabledByContent(); //if (!_controller->searchFieldController()->query().isEmpty()) { // if (!_searchShown) { // toggleSearch(anim::type::instant); // } //} else if (_searchShown) { // toggleSearch(anim::type::instant); //} } void Members::setupHeader() { if (_controller->section().type() == Section::Type::Members) { return; } _header = object_ptr<Ui::FixedHeightWidget>( this, st::infoMembersHeader); auto parent = _header.data(); _openMembers = Ui::CreateChild<Button>( parent, rpl::single(QString())); object_ptr<FloatingIcon>( parent, st::infoIconMembers, st::infoIconPosition); _titleWrap = Ui::CreateChild<Ui::RpWidget>(parent); _title = setupTitle(); _addMember = Ui::CreateChild<Ui::IconButton>( _openMembers, st::infoMembersAddMember); //_searchField = _controller->searchFieldController()->createField( // parent, // st::infoMembersSearchField); _search = Ui::CreateChild<Ui::IconButton>( _openMembers, st::infoMembersSearch); //_cancelSearch = Ui::CreateChild<Ui::CrossButton>( // parent, // st::infoMembersCancelSearch); setupButtons(); //_controller->wrapValue( //) | rpl::start_with_next([this](Wrap wrap) { // _wrap = wrap; // updateSearchOverrides(); //}, lifetime()); widthValue( ) | rpl::start_with_next([this](int width) { _header->resizeToWidth(width); }, _header->lifetime()); } object_ptr<Ui::FlatLabel> Members::setupTitle() { auto result = object_ptr<Ui::FlatLabel>( _titleWrap, MembersCountValue( _peer ) | rpl::map([](int count) { return lng_chat_status_members(lt_count, count); }) | ToUpperValue(), st::infoBlockHeaderLabel); result->setAttribute(Qt::WA_TransparentForMouseEvents); return result; } void Members::setupButtons() { using namespace rpl::mappers; _openMembers->addClickHandler([this] { showMembersWithSearch(false); }); //_searchField->hide(); //_cancelSearch->setVisible(false); auto addMemberShown = CanAddMemberValue(_peer) | rpl::start_spawning(lifetime()); _addMember->showOn(rpl::duplicate(addMemberShown)); _addMember->addClickHandler([this] { // TODO throttle(ripple duration) this->addMember(); }); auto searchShown = MembersCountValue(_peer) | rpl::map(_1 >= kEnableSearchMembersAfterCount) | rpl::distinct_until_changed() | rpl::start_spawning(lifetime()); _search->showOn(rpl::duplicate(searchShown)); _search->addClickHandler([this] { // TODO throttle(ripple duration) this->showMembersWithSearch(true); }); //_cancelSearch->addClickHandler([this] { // this->cancelSearch(); //}); rpl::combine( std::move(addMemberShown), std::move(searchShown) ) | rpl::start_with_next([this] { updateHeaderControlsGeometry(width()); }, lifetime()); } void Members::setupList() { auto topSkip = _header ? _header->height() : 0; _list = object_ptr<ListWidget>( this, _listController.get(), st::infoMembersList); _list->scrollToRequests( ) | rpl::start_with_next([this](Ui::ScrollToRequest request) { auto addmin = (request.ymin < 0 || !_header) ? 0 : _header->height(); auto addmax = (request.ymax < 0 || !_header) ? 0 : _header->height(); _scrollToRequests.fire({ request.ymin + addmin, request.ymax + addmax }); }, _list->lifetime()); widthValue( ) | rpl::start_with_next([this](int newWidth) { _list->resizeToWidth(newWidth); }, _list->lifetime()); _list->heightValue( ) | rpl::start_with_next([=](int listHeight) { auto newHeight = (listHeight > st::membersMarginBottom) ? (topSkip + listHeight + st::membersMarginBottom) : 0; resize(width(), newHeight); }, _list->lifetime()); _list->moveToLeft(0, topSkip); } int Members::resizeGetHeight(int newWidth) { if (_header) { updateHeaderControlsGeometry(newWidth); } return heightNoMargins(); } //void Members::updateSearchEnabledByContent() { // _controller->setSearchEnabledByContent( // peerListFullRowsCount() >= kEnableSearchMembersAfterCount); //} void Members::updateHeaderControlsGeometry(int newWidth) { _openMembers->setGeometry(0, st::infoProfileSkip, newWidth, st::infoMembersHeader - st::infoProfileSkip - st::infoMembersHeaderPaddingBottom); auto availableWidth = newWidth - st::infoMembersButtonPosition.x(); //auto cancelLeft = availableWidth - _cancelSearch->width(); //_cancelSearch->moveToLeft( // cancelLeft, // st::infoMembersButtonPosition.y()); //auto searchShownLeft = st::infoIconPosition.x() // - st::infoMembersSearch.iconPosition.x(); //auto searchHiddenLeft = availableWidth - _search->width(); //auto searchShown = _searchShownAnimation.current(_searchShown ? 1. : 0.); //auto searchCurrentLeft = anim::interpolate( // searchHiddenLeft, // searchShownLeft, // searchShown); //_search->moveToLeft( // searchCurrentLeft, // st::infoMembersButtonPosition.y()); //if (!_search->isHidden()) { // availableWidth -= st::infoMembersSearch.width; //} _addMember->moveToLeft( availableWidth - _addMember->width(), st::infoMembersButtonPosition.y(), newWidth); if (!_addMember->isHidden()) { availableWidth -= st::infoMembersSearch.width; } _search->moveToLeft( availableWidth - _search->width(), st::infoMembersButtonPosition.y(), newWidth); //auto fieldLeft = anim::interpolate( // cancelLeft, // st::infoBlockHeaderPosition.x(), // searchShown); //_searchField->setGeometryToLeft( // fieldLeft, // st::infoMembersSearchTop, // cancelLeft - fieldLeft, // _searchField->height()); //_titleWrap->resize( // searchCurrentLeft - st::infoBlockHeaderPosition.x(), // _title->height()); _titleWrap->resize( availableWidth - _addMember->width() - st::infoBlockHeaderPosition.x(), _title->height()); _titleWrap->moveToLeft( st::infoBlockHeaderPosition.x(), st::infoBlockHeaderPosition.y(), newWidth); _titleWrap->setAttribute(Qt::WA_TransparentForMouseEvents); //_title->resizeToWidth(searchHiddenLeft); _title->resizeToWidth(_titleWrap->width()); _title->moveToLeft(0, 0); } void Members::addMember() { if (const auto chat = _peer->asChat()) { if (chat->count >= Global::ChatSizeMax() && chat->amCreator()) { Ui::show(Box<ConvertToSupergroupBox>(chat)); } else { AddParticipantsBoxController::Start(chat); } } else if (const auto channel = _peer->asChannel()) { const auto state = _listController->saveState(); const auto users = ranges::view::all( state->list ) | ranges::view::transform([](not_null<PeerData*> peer) { return peer->asUser(); }) | ranges::to_vector; AddParticipantsBoxController::Start( channel, { users.begin(), users.end() }); } } void Members::showMembersWithSearch(bool withSearch) { //if (!_searchShown) { // toggleSearch(); //} auto contentMemento = std::make_unique<Info::Members::Memento>( _controller); contentMemento->setState(saveState()); contentMemento->setSearchStartsFocused(withSearch); auto mementoStack = std::vector<std::unique_ptr<ContentMemento>>(); mementoStack.push_back(std::move(contentMemento)); _controller->showSection( Info::Memento(std::move(mementoStack))); } //void Members::toggleSearch(anim::type animated) { // _searchShown = !_searchShown; // _cancelSearch->toggle(_searchShown, animated); // if (animated == anim::type::normal) { // _searchShownAnimation.start( // [this] { searchAnimationCallback(); }, // _searchShown ? 0. : 1., // _searchShown ? 1. : 0., // st::slideWrapDuration); // } else { // _searchShownAnimation.finish(); // searchAnimationCallback(); // } // _search->setDisabled(_searchShown); // if (_searchShown) { // _searchField->show(); // _searchField->setFocus(); // } else { // setFocus(); // } //} // //void Members::searchAnimationCallback() { // if (!_searchShownAnimation.animating()) { // _searchField->setVisible(_searchShown); // updateSearchOverrides(); // _search->setPointerCursor(!_searchShown); // } // updateHeaderControlsGeometry(width()); //} // //void Members::updateSearchOverrides() { // auto iconOverride = !_searchShown // ? nullptr // : (_wrap == Wrap::Layer) // ? &st::infoMembersSearchActiveLayer // : &st::infoMembersSearchActive; // _search->setIconOverride(iconOverride, iconOverride); //} // //void Members::cancelSearch() { // if (_searchShown) { // if (!_searchField->getLastText().isEmpty()) { // _searchField->setText(QString()); // _searchField->setFocus(); // } else { // toggleSearch(); // } // } //} void Members::visibleTopBottomUpdated( int visibleTop, int visibleBottom) { setChildVisibleTopBottom(_list, visibleTop, visibleBottom); } void Members::peerListSetTitle(base::lambda<QString()> title) { } void Members::peerListSetAdditionalTitle( base::lambda<QString()> title) { } bool Members::peerListIsRowSelected(not_null<PeerData*> peer) { return false; } int Members::peerListSelectedRowsCount() { return 0; } std::vector<not_null<PeerData*>> Members::peerListCollectSelectedRows() { return {}; } void Members::peerListScrollToTop() { _scrollToRequests.fire({ -1, -1 }); } void Members::peerListAddSelectedRowInBunch(not_null<PeerData*> peer) { Unexpected("Item selection in Info::Profile::Members."); } void Members::peerListFinishSelectedRowsBunch() { } void Members::peerListSetDescription( object_ptr<Ui::FlatLabel> description) { description.destroy(); } } // namespace Profile } // namespace Info ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/profile/info_profile_members.h����������������������������0000664�0000000�0000000�00000006763�13262451251�0026370�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/rp_widget.h" #include "boxes/peer_list_box.h" namespace Ui { class InputField; class CrossButton; class IconButton; class FlatLabel; struct ScrollToRequest; class AbstractButton; } // namespace Ui namespace Profile { class ParticipantsBoxController; } // namespace Profile namespace Info { class Controller; enum class Wrap; namespace Profile { class Button; class Memento; struct MembersState { std::unique_ptr<PeerListState> list; base::optional<QString> search; }; class Members : public Ui::RpWidget , private PeerListContentDelegate { public: Members( QWidget *parent, not_null<Controller*> controller); rpl::producer<Ui::ScrollToRequest> scrollToRequests() const; std::unique_ptr<MembersState> saveState(); void restoreState(std::unique_ptr<MembersState> state); int desiredHeight() const; rpl::producer<int> onlineCountValue() const; protected: void visibleTopBottomUpdated( int visibleTop, int visibleBottom) override; int resizeGetHeight(int newWidth) override; private: using ListWidget = PeerListContent; // PeerListContentDelegate interface. void peerListSetTitle(base::lambda<QString()> title) override; void peerListSetAdditionalTitle( base::lambda<QString()> title) override; bool peerListIsRowSelected(not_null<PeerData*> peer) override; int peerListSelectedRowsCount() override; std::vector<not_null<PeerData*>> peerListCollectSelectedRows() override; void peerListScrollToTop() override; void peerListAddSelectedRowInBunch( not_null<PeerData*> peer) override; void peerListFinishSelectedRowsBunch() override; void peerListSetDescription( object_ptr<Ui::FlatLabel> description) override; //void peerListAppendRow( // std::unique_ptr<PeerListRow> row) override { // PeerListContentDelegate::peerListAppendRow(std::move(row)); // updateSearchEnabledByContent(); //} //void peerListPrependRow( // std::unique_ptr<PeerListRow> row) override { // PeerListContentDelegate::peerListPrependRow(std::move(row)); // updateSearchEnabledByContent(); //} //void peerListRemoveRow(not_null<PeerListRow*> row) override { // PeerListContentDelegate::peerListRemoveRow(row); // updateSearchEnabledByContent(); //} void setupHeader(); object_ptr<Ui::FlatLabel> setupTitle(); void setupList(); void setupButtons(); //void updateSearchOverrides(); void addMember(); void showMembersWithSearch(bool withSearch); //void toggleSearch(anim::type animated = anim::type::normal); //void cancelSearch(); //void searchAnimationCallback(); void updateHeaderControlsGeometry(int newWidth); //void updateSearchEnabledByContent(); //Wrap _wrap; not_null<Controller*> _controller; not_null<PeerData*> _peer; std::unique_ptr<PeerListController> _listController; object_ptr<Ui::RpWidget> _header = { nullptr }; object_ptr<ListWidget> _list = { nullptr }; Button *_openMembers = nullptr; Ui::RpWidget *_titleWrap = nullptr; Ui::FlatLabel *_title = nullptr; Ui::IconButton *_addMember = nullptr; //base::unique_qptr<Ui::InputField> _searchField; Ui::IconButton *_search = nullptr; //Ui::CrossButton *_cancelSearch = nullptr; //Animation _searchShownAnimation; //bool _searchShown = false; //base::Timer _searchTimer; rpl::event_stream<Ui::ScrollToRequest> _scrollToRequests; }; } // namespace Profile } // namespace Info �������������tdesktop-1.2.17/Telegram/SourceFiles/info/profile/info_profile_members_controllers.cpp��������������0000664�0000000�0000000�00000023720�13262451251�0031341�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/profile/info_profile_members_controllers.h" #include <rpl/variable.h> #include "base/weak_ptr.h" #include "profile/profile_channel_controllers.h" #include "ui/widgets/popup_menu.h" #include "lang/lang_keys.h" #include "apiwrap.h" #include "auth_session.h" #include "mainwidget.h" #include "observer_peer.h" #include "boxes/confirm_box.h" #include "window/window_controller.h" #include "styles/style_info.h" #include "data/data_peer_values.h" namespace Info { namespace Profile { namespace { constexpr auto kSortByOnlineDelay = TimeMs(1000); class ChatMembersController : public PeerListController , private base::Subscriber , public base::has_weak_ptr { public: ChatMembersController( not_null<Window::Navigation*> navigation, not_null<ChatData*> chat); void prepare() override; void rowClicked(not_null<PeerListRow*> row) override; void rowActionClicked(not_null<PeerListRow*> row) override; base::unique_qptr<Ui::PopupMenu> rowContextMenu( not_null<PeerListRow*> row) override; rpl::producer<int> onlineCountValue() const override { return _onlineCount.value(); } std::unique_ptr<PeerListRow> createRestoredRow( not_null<PeerData*> peer) override; std::unique_ptr<PeerListState> saveState() const override; void restoreState(std::unique_ptr<PeerListState> state) override; private: using Rights = MemberListRow::Rights; using Type = MemberListRow::Type; struct SavedState : SavedStateBase { rpl::lifetime lifetime; }; void rebuildRows(); void rebuildRowTypes(); void refreshOnlineCount(); std::unique_ptr<PeerListRow> createRow( not_null<UserData*> user); void sortByOnline(); void sortByOnlineDelayed(); void removeMember(not_null<UserData*> user); Type computeType(not_null<UserData*> user); not_null<Window::Navigation*> _navigation; not_null<ChatData*> _chat; base::Timer _sortByOnlineTimer; rpl::variable<int> _onlineCount = 0; }; ChatMembersController::ChatMembersController( not_null<Window::Navigation*> navigation, not_null<ChatData*> chat) : PeerListController() , _navigation(navigation) , _chat(chat) { _sortByOnlineTimer.setCallback([this] { sortByOnline(); }); } void ChatMembersController::prepare() { setSearchNoResultsText(lang(lng_blocked_list_not_found)); delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled); delegate()->peerListSetTitle(langFactory(lng_channel_admins)); rebuildRows(); if (!delegate()->peerListFullRowsCount()) { Auth().api().requestFullPeer(_chat); } using UpdateFlag = Notify::PeerUpdate::Flag; subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler( UpdateFlag::MembersChanged | UpdateFlag::UserOnlineChanged | UpdateFlag::AdminsChanged, [this](const Notify::PeerUpdate &update) { if (update.flags & UpdateFlag::MembersChanged) { if (update.peer == _chat) { rebuildRows(); } } else if (update.flags & UpdateFlag::AdminsChanged) { if (update.peer == _chat) { rebuildRowTypes(); } } else if (update.flags & UpdateFlag::UserOnlineChanged) { if (auto row = delegate()->peerListFindRow( update.peer->id)) { row->refreshStatus(); sortByOnlineDelayed(); } } })); } void ChatMembersController::sortByOnlineDelayed() { if (!_sortByOnlineTimer.isActive()) { _sortByOnlineTimer.callOnce(kSortByOnlineDelay); } } void ChatMembersController::sortByOnline() { auto now = unixtime(); delegate()->peerListSortRows([now]( const PeerListRow &a, const PeerListRow &b) { return Data::SortByOnlineValue(a.peer()->asUser(), now) > Data::SortByOnlineValue(b.peer()->asUser(), now); }); refreshOnlineCount(); } std::unique_ptr<PeerListState> ChatMembersController::saveState() const { auto result = PeerListController::saveState(); auto my = std::make_unique<SavedState>(); using Flag = Notify::PeerUpdate::Flag; Notify::PeerUpdateViewer( _chat, Flag::MembersChanged ) | rpl::start_with_next([state = result.get()](auto update) { state->controllerState = nullptr; }, my->lifetime); result->controllerState = std::move(my); return result; } void ChatMembersController::restoreState( std::unique_ptr<PeerListState> state) { PeerListController::restoreState(std::move(state)); sortByOnline(); } void ChatMembersController::rebuildRows() { if (_chat->participants.empty()) { // We get such updates often // (when participants list was invalidated). //while (delegate()->peerListFullRowsCount() > 0) { // delegate()->peerListRemoveRow( // delegate()->peerListRowAt(0)); //} return; } auto &participants = _chat->participants; for (auto i = 0, count = delegate()->peerListFullRowsCount(); i != count;) { auto row = delegate()->peerListRowAt(i); auto user = row->peer()->asUser(); if (participants.contains(user)) { ++i; } else { delegate()->peerListRemoveRow(row); --count; } } for (const auto [user, v] : participants) { if (auto row = createRow(user)) { delegate()->peerListAppendRow(std::move(row)); } } sortByOnline(); delegate()->peerListRefreshRows(); } void ChatMembersController::rebuildRowTypes() { auto count = delegate()->peerListFullRowsCount(); for (auto i = 0; i != count; ++i) { auto row = static_cast<MemberListRow*>( delegate()->peerListRowAt(i).get()); row->setType(computeType(row->user())); } delegate()->peerListRefreshRows(); } void ChatMembersController::refreshOnlineCount() { auto now = unixtime(); auto left = 0, right = delegate()->peerListFullRowsCount(); while (right > left) { auto middle = (left + right) / 2; auto row = delegate()->peerListRowAt(middle); if (Data::OnlineTextActive(row->peer()->asUser(), now)) { left = middle + 1; } else { right = middle; } } _onlineCount = left; } std::unique_ptr<PeerListRow> ChatMembersController::createRestoredRow( not_null<PeerData*> peer) { if (auto user = peer->asUser()) { return createRow(user); } return nullptr; } std::unique_ptr<PeerListRow> ChatMembersController::createRow( not_null<UserData*> user) { return std::make_unique<MemberListRow>(user, computeType(user)); } auto ChatMembersController::computeType( not_null<UserData*> user) -> Type { auto isCreator = (peerFromUser(_chat->creator) == user->id); auto isAdmin = _chat->adminsEnabled() && _chat->admins.contains(user); auto canRemove = [&] { if (user->isSelf()) { return false; } else if (_chat->amCreator()) { return true; } else if (isAdmin || isCreator) { return false; } else if (_chat->amAdmin()) { return true; } else if (_chat->invitedByMe.contains(user)) { return true; } return false; }(); auto result = Type(); result.rights = isCreator ? Rights::Creator : isAdmin ? Rights::Admin : Rights::Normal; result.canRemove = canRemove; return result; } void ChatMembersController::rowClicked(not_null<PeerListRow*> row) { _navigation->showPeerInfo(row->peer()); } void ChatMembersController::rowActionClicked( not_null<PeerListRow*> row) { removeMember(row->peer()->asUser()); } base::unique_qptr<Ui::PopupMenu> ChatMembersController::rowContextMenu( not_null<PeerListRow*> row) { auto my = static_cast<MemberListRow*>(row.get()); auto user = my->user(); auto canRemoveMember = my->canRemove(); auto result = base::make_unique_q<Ui::PopupMenu>(nullptr); result->addAction( lang(lng_context_view_profile), [weak = base::make_weak(this), user] { if (weak) { weak->_navigation->showPeerInfo(user); } }); if (canRemoveMember) { result->addAction( lang(lng_context_remove_from_group), [weak = base::make_weak(this), user] { if (weak) { weak->removeMember(user); } }); } return result; } void ChatMembersController::removeMember(not_null<UserData*> user) { auto text = lng_profile_sure_kick(lt_user, user->firstName); Ui::show(Box<ConfirmBox>(text, lang(lng_box_remove), [user, chat = _chat] { Ui::hideLayer(); Auth().api().kickParticipant(chat, user); Ui::showPeerHistory(chat->id, ShowAtTheEndMsgId); })); } } // namespace MemberListRow::MemberListRow( not_null<UserData*> user, Type type) : PeerListRow(user) , _type(type) { } void MemberListRow::setType(Type type) { _type = type; } QSize MemberListRow::actionSize() const { return canRemove() ? QRect( QPoint(), st::infoMembersRemoveIcon.size()).marginsAdded( st::infoMembersRemoveIconMargins).size() : QSize(); } void MemberListRow::paintAction( Painter &p, TimeMs ms, int x, int y, int outerWidth, bool selected, bool actionSelected) { if (_type.canRemove && selected) { x += st::infoMembersRemoveIconMargins.left(); y += st::infoMembersRemoveIconMargins.top(); (actionSelected ? st::infoMembersRemoveIconOver : st::infoMembersRemoveIcon).paint(p, x, y, outerWidth); } } int MemberListRow::nameIconWidth() const { return (_type.rights == Rights::Admin) ? st::infoMembersAdminIcon.width() : (_type.rights == Rights::Creator) ? st::infoMembersCreatorIcon.width() : 0; } void MemberListRow::paintNameIcon( Painter &p, int x, int y, int outerWidth, bool selected) { auto icon = [&] { return (_type.rights == Rights::Admin) ? (selected ? &st::infoMembersAdminIconOver : &st::infoMembersAdminIcon) : (selected ? &st::infoMembersCreatorIconOver : &st::infoMembersCreatorIcon); }(); icon->paint(p, x, y, outerWidth); } std::unique_ptr<PeerListController> CreateMembersController( not_null<Window::Navigation*> navigation, not_null<PeerData*> peer) { if (auto chat = peer->asChat()) { return std::make_unique<ChatMembersController>( navigation, chat); } else if (auto channel = peer->asChannel()) { using ChannelMembersController = ::Profile::ParticipantsBoxController; return std::make_unique<ChannelMembersController>( navigation, channel, ChannelMembersController::Role::Profile); } Unexpected("Peer type in CreateMembersController()"); } } // namespace Profile } // namespace Info ������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/profile/info_profile_members_controllers.h����������������0000664�0000000�0000000�00000002366�13262451251�0031011�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "boxes/peer_list_box.h" namespace Window { class Navigation; } // namespace Window namespace Info { namespace Profile { class MemberListRow final : public PeerListRow { public: enum class Rights { Normal, Admin, Creator, }; struct Type { Rights rights; bool canRemove = false; }; MemberListRow(not_null<UserData*> user, Type type); void setType(Type type); QSize actionSize() const override; void paintAction( Painter &p, TimeMs ms, int x, int y, int outerWidth, bool selected, bool actionSelected) override; int nameIconWidth() const override; void paintNameIcon( Painter &p, int x, int y, int outerWidth, bool selected) override; not_null<UserData*> user() const { return peer()->asUser(); } bool canRemove() const { return _type.canRemove; } private: Type _type; }; std::unique_ptr<PeerListController> CreateMembersController( not_null<Window::Navigation*> navigation, not_null<PeerData*> peer); } // namespace Profile } // namespace Info ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/profile/info_profile_text.cpp�����������������������������0000664�0000000�0000000�00000003333�13262451251�0026243�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/profile/info_profile_text.h" #include <rpl/before_next.h> #include <rpl/filter.h> #include <rpl/after_next.h> #include "ui/widgets/labels.h" #include "ui/wrap/slide_wrap.h" #include "ui/wrap/vertical_layout.h" #include "styles/style_info.h" namespace Info { namespace Profile { TextWithLabel CreateTextWithLabel( QWidget *parent, rpl::producer<TextWithEntities> &&label, rpl::producer<TextWithEntities> &&text, const style::FlatLabel &textSt, const style::margins &padding) { auto result = object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>( parent, object_ptr<Ui::VerticalLayout>(parent), padding); result->setDuration( st::infoSlideDuration ); auto layout = result->entity(); auto nonEmptyText = std::move( text ) | rpl::before_next([slide = result.data()]( const TextWithEntities &value) { if (value.text.isEmpty()) { slide->hide(anim::type::normal); } }) | rpl::filter([](const TextWithEntities &value) { return !value.text.isEmpty(); }) | rpl::after_next([slide = result.data()]( const TextWithEntities &value) { slide->show(anim::type::normal); }); auto labeled = layout->add(object_ptr<Ui::FlatLabel>( layout, std::move(nonEmptyText), textSt)); labeled->setSelectable(true); layout->add(Ui::CreateSkipWidget(layout, st::infoLabelSkip)); layout->add(object_ptr<Ui::FlatLabel>( layout, std::move(label), st::infoLabel)); result->finishAnimating(); return { std::move(result), labeled }; } } // namespace Profile } // namespace Info �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/profile/info_profile_text.h�������������������������������0000664�0000000�0000000�00000001524�13262451251�0025710�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/producer.h> namespace style { struct FlatLabel; } // namespace style namespace Ui { class VerticalLayout; class FlatLabel; template <typename Widget> class SlideWrap; } // namespace Ui namespace Info { namespace Profile { struct TextWithLabel { object_ptr<Ui::SlideWrap<Ui::VerticalLayout>> wrap; not_null<Ui::FlatLabel*> text; }; TextWithLabel CreateTextWithLabel( QWidget *parent, rpl::producer<TextWithEntities> &&label, rpl::producer<TextWithEntities> &&text, const style::FlatLabel &textSt, const style::margins &padding); } // namespace Profile } // namespace Info ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/profile/info_profile_values.cpp���������������������������0000664�0000000�0000000�00000017125�13262451251�0026562�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/profile/info_profile_values.h" #include <rpl/filter.h> #include <rpl/range.h> #include <rpl/then.h> #include <rpl/combine.h> #include "observer_peer.h" #include "messenger.h" #include "auth_session.h" #include "ui/wrap/slide_wrap.h" #include "data/data_peer_values.h" #include "data/data_shared_media.h" #include "data/data_feed.h" #include "data/data_session.h" namespace Info { namespace Profile { rpl::producer<TextWithEntities> PhoneValue( not_null<UserData*> user) { return Notify::PeerUpdateValue( user, Notify::PeerUpdate::Flag::UserPhoneChanged ) | rpl::map([user] { return App::formatPhone(user->phone()); }) | WithEmptyEntities(); } auto PlainBioValue( not_null<UserData*> user) { return Notify::PeerUpdateValue( user, Notify::PeerUpdate::Flag::AboutChanged ) | rpl::map([user] { return user->about(); }); } rpl::producer<TextWithEntities> BioValue( not_null<UserData*> user) { return PlainBioValue(user) | ToSingleLine() | WithEmptyEntities(); } auto PlainUsernameValue( not_null<PeerData*> peer) { return Notify::PeerUpdateValue( peer, Notify::PeerUpdate::Flag::UsernameChanged ) | rpl::map([peer] { return peer->userName(); }); } rpl::producer<TextWithEntities> UsernameValue( not_null<UserData*> user) { return PlainUsernameValue( user ) | rpl::map([](QString &&username) { return username.isEmpty() ? QString() : ('@' + username); }) | WithEmptyEntities(); } rpl::producer<QString> PlainAboutValue( not_null<PeerData*> peer) { if (auto channel = peer->asChannel()) { return Notify::PeerUpdateValue( channel, Notify::PeerUpdate::Flag::AboutChanged ) | rpl::map([channel] { return channel->about(); }); } else if (auto user = peer->asUser()) { if (user->botInfo) { return PlainBioValue(user); } } return rpl::single(QString()); } rpl::producer<TextWithEntities> AboutValue( not_null<PeerData*> peer) { auto flags = TextParseLinks | TextParseMentions | TextParseHashtags; if (peer->isUser()) { flags |= TextParseBotCommands; } return PlainAboutValue( peer ) | WithEmptyEntities( ) | rpl::map([=](TextWithEntities &&text) { TextUtilities::ParseEntities(text, flags); return std::move(text); }); } rpl::producer<QString> LinkValue( not_null<PeerData*> peer) { return PlainUsernameValue( peer ) | rpl::map([](QString &&username) { return username.isEmpty() ? QString() : Messenger::Instance().createInternalLinkFull(username); }); } rpl::producer<bool> NotificationsEnabledValue( not_null<PeerData*> peer) { return Notify::PeerUpdateValue( peer, Notify::PeerUpdate::Flag::NotificationsEnabled ) | rpl::map([peer] { return !peer->isMuted(); }) | rpl::distinct_until_changed(); } rpl::producer<bool> IsContactValue( not_null<UserData*> user) { return Notify::PeerUpdateValue( user, Notify::PeerUpdate::Flag::UserIsContact ) | rpl::map([user] { return user->isContact(); }); } rpl::producer<bool> CanInviteBotToGroupValue( not_null<UserData*> user) { if (!user->botInfo) { return rpl::single(false); } return Notify::PeerUpdateValue( user, Notify::PeerUpdate::Flag::BotCanAddToGroups ) | rpl::map([user] { return !user->botInfo->cantJoinGroups; }); } rpl::producer<bool> CanShareContactValue( not_null<UserData*> user) { return Notify::PeerUpdateValue( user, Notify::PeerUpdate::Flag::UserCanShareContact ) | rpl::map([user] { return user->canShareThisContact(); }); } rpl::producer<bool> CanAddContactValue( not_null<UserData*> user) { using namespace rpl::mappers; return rpl::combine( IsContactValue(user), CanShareContactValue(user), !_1 && _2); } rpl::producer<bool> AmInChannelValue( not_null<ChannelData*> channel) { return Notify::PeerUpdateValue( channel, Notify::PeerUpdate::Flag::ChannelAmIn ) | rpl::map([channel] { return channel->amIn(); }); } rpl::producer<int> MembersCountValue( not_null<PeerData*> peer) { if (auto chat = peer->asChat()) { return Notify::PeerUpdateValue( peer, Notify::PeerUpdate::Flag::MembersChanged ) | rpl::map([chat] { return chat->amIn() ? std::max(chat->count, int(chat->participants.size())) : 0; }); } else if (auto channel = peer->asChannel()) { return Notify::PeerUpdateValue( channel, Notify::PeerUpdate::Flag::MembersChanged ) | rpl::map([channel] { return channel->membersCount(); }); } Unexpected("User in MembersCountViewer()."); } rpl::producer<int> AdminsCountValue( not_null<ChannelData*> channel) { using Flag = Notify::PeerUpdate::Flag; return Notify::PeerUpdateValue( channel, Flag::AdminsChanged | Flag::ChannelRightsChanged ) | rpl::map([channel] { return channel->canViewAdmins() ? channel->adminsCount() : 0; }); } rpl::producer<int> RestrictedCountValue( not_null<ChannelData*> channel) { using Flag = Notify::PeerUpdate::Flag; return Notify::PeerUpdateValue( channel, Flag::BannedUsersChanged | Flag::ChannelRightsChanged ) | rpl::map([channel] { return channel->canViewBanned() ? channel->restrictedCount() : 0; }); } rpl::producer<int> KickedCountValue( not_null<ChannelData*> channel) { using Flag = Notify::PeerUpdate::Flag; return Notify::PeerUpdateValue( channel, Flag::BannedUsersChanged | Flag::ChannelRightsChanged ) | rpl::map([channel] { return channel->canViewBanned() ? channel->kickedCount() : 0; }); } rpl::producer<int> SharedMediaCountValue( not_null<PeerData*> peer, PeerData *migrated, Storage::SharedMediaType type) { auto aroundId = 0; auto limit = 0; auto updated = SharedMediaMergedViewer( SharedMediaMergedKey( SparseIdsMergedSlice::Key( peer->id, migrated ? migrated->id : 0, aroundId), type), limit, limit ) | rpl::map([](const SparseIdsMergedSlice &slice) { return slice.fullCount(); }) | rpl::filter_optional(); return rpl::single(0) | rpl::then(std::move(updated)); } rpl::producer<int> CommonGroupsCountValue( not_null<UserData*> user) { return Notify::PeerUpdateValue( user, Notify::PeerUpdate::Flag::UserCommonChatsChanged ) | rpl::map([user] { return user->commonChatsCount(); }); } rpl::producer<bool> CanAddMemberValue( not_null<PeerData*> peer) { if (auto chat = peer->asChat()) { return Notify::PeerUpdateValue( chat, Notify::PeerUpdate::Flag::ChatCanEdit ) | rpl::map([chat] { return chat->canEdit(); }); } else if (auto channel = peer->asChannel()) { return Notify::PeerUpdateValue( channel, Notify::PeerUpdate::Flag::ChannelRightsChanged ) | rpl::map([channel] { return channel->canAddMembers(); }); } return rpl::single(false); } rpl::producer<bool> VerifiedValue( not_null<PeerData*> peer) { if (auto user = peer->asUser()) { return Data::PeerFlagValue(user, MTPDuser::Flag::f_verified); } else if (auto channel = peer->asChannel()) { return Data::PeerFlagValue( channel, MTPDchannel::Flag::f_verified); } return rpl::single(false); } rpl::producer<int> FeedChannelsCountValue( not_null<Data::Feed*> feed) { using Flag = Data::FeedUpdateFlag; return rpl::single( Data::FeedUpdate{ feed, Flag::Channels } ) | rpl::then( Auth().data().feedUpdated() ) | rpl::filter([=](const Data::FeedUpdate &update) { return (update.feed == feed) && (update.flag == Flag::Channels); }) | rpl::filter([=] { return feed->channelsLoaded(); }) | rpl::map([=] { return int(feed->channels().size()); }) | rpl::distinct_until_changed(); } } // namespace Profile } // namespace Info �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/profile/info_profile_values.h�����������������������������0000664�0000000�0000000�00000004551�13262451251�0026226�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/producer.h> #include <rpl/map.h> #include "observer_peer.h" namespace Ui { class RpWidget; template <typename Widget> class SlideWrap; } // namespace Ui namespace Storage { enum class SharedMediaType : signed char; } // namespace Storage namespace Info { namespace Profile { inline auto ToSingleLine() { return rpl::map([](const QString &text) { return TextUtilities::SingleLine(text); }); } inline auto WithEmptyEntities() { return rpl::map([](QString &&text) { return TextWithEntities{ std::move(text), {} }; }); } inline auto ToUpperValue() { return rpl::map([](QString &&text) { return std::move(text).toUpper(); }); } rpl::producer<TextWithEntities> PhoneValue( not_null<UserData*> user); rpl::producer<TextWithEntities> BioValue( not_null<UserData*> user); rpl::producer<TextWithEntities> UsernameValue( not_null<UserData*> user); rpl::producer<TextWithEntities> AboutValue( not_null<PeerData*> peer); rpl::producer<QString> LinkValue( not_null<PeerData*> peer); rpl::producer<bool> NotificationsEnabledValue( not_null<PeerData*> peer); rpl::producer<bool> IsContactValue( not_null<UserData*> user); rpl::producer<bool> CanInviteBotToGroupValue( not_null<UserData*> user); rpl::producer<bool> CanShareContactValue( not_null<UserData*> user); rpl::producer<bool> CanAddContactValue( not_null<UserData*> user); rpl::producer<bool> AmInChannelValue( not_null<ChannelData*> channel); rpl::producer<int> MembersCountValue( not_null<PeerData*> peer); rpl::producer<int> AdminsCountValue( not_null<ChannelData*> channel); rpl::producer<int> RestrictedCountValue( not_null<ChannelData*> channel); rpl::producer<int> KickedCountValue( not_null<ChannelData*> channel); rpl::producer<int> SharedMediaCountValue( not_null<PeerData*> peer, PeerData *migrated, Storage::SharedMediaType type); rpl::producer<int> CommonGroupsCountValue( not_null<UserData*> user); rpl::producer<bool> CanAddMemberValue( not_null<PeerData*> peer); rpl::producer<bool> VerifiedValue( not_null<PeerData*> peer); rpl::producer<int> FeedChannelsCountValue( not_null<Data::Feed*> feed); } // namespace Profile } // namespace Info �������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/profile/info_profile_widget.cpp���������������������������0000664�0000000�0000000�00000005445�13262451251�0026550�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/profile/info_profile_widget.h" #include "info/profile/info_profile_inner_widget.h" #include "info/profile/info_profile_members.h" #include "ui/widgets/scroll_area.h" #include "info/info_controller.h" namespace Info { namespace Profile { Memento::Memento(not_null<Controller*> controller) : Memento( controller->peerId(), controller->migratedPeerId()) { } Memento::Memento(PeerId peerId, PeerId migratedPeerId) : ContentMemento(peerId, migratedPeerId) { } Section Memento::section() const { return Section(Section::Type::Profile); } object_ptr<ContentWidget> Memento::createWidget( QWidget *parent, not_null<Controller*> controller, const QRect &geometry) { auto result = object_ptr<Widget>( parent, controller); result->setInternalState(geometry, this); return std::move(result); } void Memento::setMembersState(std::unique_ptr<MembersState> state) { _membersState = std::move(state); } std::unique_ptr<MembersState> Memento::membersState() { return std::move(_membersState); } Memento::~Memento() = default; Widget::Widget( QWidget *parent, not_null<Controller*> controller) : ContentWidget(parent, controller) { controller->setSearchEnabledByContent(false); _inner = setInnerWidget(object_ptr<InnerWidget>( this, controller)); _inner->move(0, 0); _inner->scrollToRequests( ) | rpl::start_with_next([this](Ui::ScrollToRequest request) { if (request.ymin < 0) { scrollTopRestore( qMin(scrollTopSave(), request.ymax)); } else { scrollTo(request); } }, lifetime()); } void Widget::setIsStackBottom(bool isStackBottom) { _inner->setIsStackBottom(isStackBottom); } void Widget::setInnerFocus() { _inner->setFocus(); } bool Widget::showInternal(not_null<ContentMemento*> memento) { if (!controller()->validateMementoPeer(memento)) { return false; } if (auto profileMemento = dynamic_cast<Memento*>(memento.get())) { restoreState(profileMemento); return true; } return false; } void Widget::setInternalState( const QRect &geometry, not_null<Memento*> memento) { setGeometry(geometry); Ui::SendPendingMoveResizeEvents(this); restoreState(memento); } std::unique_ptr<ContentMemento> Widget::doCreateMemento() { auto result = std::make_unique<Memento>(controller()); saveState(result.get()); return std::move(result); } void Widget::saveState(not_null<Memento*> memento) { memento->setScrollTop(scrollTopSave()); _inner->saveState(memento); } void Widget::restoreState(not_null<Memento*> memento) { _inner->restoreState(memento); scrollTopRestore(memento->scrollTop()); } } // namespace Profile } // namespace Info ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/info/profile/info_profile_widget.h�����������������������������0000664�0000000�0000000�00000003160�13262451251�0026205�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/producer.h> #include "info/info_content_widget.h" namespace Info { namespace Profile { class InnerWidget; struct MembersState; class Memento final : public ContentMemento { public: Memento(not_null<Controller*> controller); Memento(PeerId peerId, PeerId migratedPeerId); object_ptr<ContentWidget> createWidget( QWidget *parent, not_null<Controller*> controller, const QRect &geometry) override; Section section() const override; void setInfoExpanded(bool expanded) { _infoExpanded = expanded; } bool infoExpanded() const { return _infoExpanded; } void setMembersState(std::unique_ptr<MembersState> state); std::unique_ptr<MembersState> membersState(); ~Memento(); private: bool _infoExpanded = true; std::unique_ptr<MembersState> _membersState; }; class Widget final : public ContentWidget { public: Widget( QWidget *parent, not_null<Controller*> controller); void setIsStackBottom(bool isStackBottom) override; bool showInternal( not_null<ContentMemento*> memento) override; void setInternalState( const QRect &geometry, not_null<Memento*> memento); void setInnerFocus() override; private: void saveState(not_null<Memento*> memento); void restoreState(not_null<Memento*> memento); std::unique_ptr<ContentMemento> doCreateMemento() override; InnerWidget *_inner = nullptr; }; } // namespace Profile } // namespace Info ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/inline_bots/���������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0021730�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp���������������������0000664�0000000�0000000�00000134273�13262451251�0030061�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "inline_bots/inline_bot_layout_internal.h" #include "data/data_photo.h" #include "data/data_document.h" #include "data/data_session.h" #include "styles/style_overview.h" #include "styles/style_history.h" #include "styles/style_chat_helpers.h" #include "styles/style_widgets.h" #include "inline_bots/inline_bot_result.h" #include "media/media_audio.h" #include "media/media_clip_reader.h" #include "media/player/media_player_instance.h" #include "history/history_location_manager.h" #include "history/view/history_view_cursor_state.h" #include "storage/localstorage.h" #include "auth_session.h" #include "lang/lang_keys.h" namespace InlineBots { namespace Layout { namespace internal { using TextState = HistoryView::TextState; FileBase::FileBase(not_null<Context*> context, Result *result) : ItemBase(context, result) { } FileBase::FileBase(not_null<Context*> context, DocumentData *document) : ItemBase(context, document) { } DocumentData *FileBase::getShownDocument() const { if (DocumentData *result = getDocument()) { return result; } return getResultDocument(); } int FileBase::content_width() const { DocumentData *document = getShownDocument(); if (document->dimensions.width() > 0) { return document->dimensions.width(); } if (!document->thumb->isNull()) { return convertScale(document->thumb->width()); } return 0; } int FileBase::content_height() const { DocumentData *document = getShownDocument(); if (document->dimensions.height() > 0) { return document->dimensions.height(); } if (!document->thumb->isNull()) { return convertScale(document->thumb->height()); } return 0; } int FileBase::content_duration() const { if (const auto document = getShownDocument()) { if (document->duration() > 0) { return document->duration(); } else if (const auto song = document->song()) { if (song->duration) { return song->duration; } } } return getResultDuration(); } ImagePtr FileBase::content_thumb() const { if (DocumentData *document = getShownDocument()) { if (!document->thumb->isNull()) { return document->thumb; } } return getResultThumb(); } Gif::Gif(not_null<Context*> context, Result *result) : FileBase(context, result) { } Gif::Gif(not_null<Context*> context, DocumentData *document, bool hasDeleteButton) : FileBase(context, document) { if (hasDeleteButton) { _delete = std::make_shared<DeleteSavedGifClickHandler>(document); } } void Gif::initDimensions() { int32 w = content_width(), h = content_height(); if (w <= 0 || h <= 0) { _maxw = 0; } else { w = w * st::inlineMediaHeight / h; _maxw = qMax(w, int32(st::inlineResultsMinWidth)); } _minh = st::inlineMediaHeight + st::inlineResultsSkip; } void Gif::setPosition(int32 position) { ItemBase::setPosition(position); if (_position < 0) { _gif.reset(); } } void DeleteSavedGifClickHandler::onClickImpl() const { auto index = Auth().data().savedGifs().indexOf(_data); if (index >= 0) { Auth().data().savedGifsRef().remove(index); Local::writeSavedGifs(); MTP::send(MTPmessages_SaveGif(_data->mtpInput(), MTP_bool(true))); } Auth().data().notifySavedGifsUpdated(); } int Gif::resizeGetHeight(int width) { _width = width; _height = _minh; return _height; } void Gif::paint(Painter &p, const QRect &clip, const PaintContext *context) const { DocumentData *document = getShownDocument(); document->automaticLoad(nullptr); bool loaded = document->loaded(), loading = document->loading(), displayLoading = document->displayLoading(); if (loaded && !_gif && !_gif.isBad()) { auto that = const_cast<Gif*>(this); that->_gif = Media::Clip::MakeReader(document, FullMsgId(), [that](Media::Clip::Notification notification) { that->clipCallback(notification); }); if (_gif) _gif->setAutoplay(); } bool animating = (_gif && _gif->started()); if (displayLoading) { ensureAnimation(); if (!_animation->radial.animating()) { _animation->radial.start(document->progress()); } } bool radial = isRadialAnimation(context->ms); int32 height = st::inlineMediaHeight; QSize frame = countFrameSize(); QRect r(0, 0, _width, height); if (animating) { if (!_thumb.isNull()) _thumb = QPixmap(); auto pixmap = _gif->current(frame.width(), frame.height(), _width, height, ImageRoundRadius::None, RectPart::None, context->paused ? 0 : context->ms); p.drawPixmap(r.topLeft(), pixmap); } else { prepareThumb(_width, height, frame); if (_thumb.isNull()) { p.fillRect(r, st::overviewPhotoBg); } else { p.drawPixmap(r.topLeft(), _thumb); } } if (radial || _gif.isBad() || (!_gif && !loaded && !loading)) { auto radialOpacity = (radial && loaded) ? _animation->radial.opacity() : 1.; if (_animation && _animation->_a_over.animating(context->ms)) { auto over = _animation->_a_over.current(); p.fillRect(r, anim::brush(st::msgDateImgBg, st::msgDateImgBgOver, over)); } else { auto over = (_state & StateFlag::Over); p.fillRect(r, over ? st::msgDateImgBgOver : st::msgDateImgBg); } p.setOpacity(radialOpacity * p.opacity()); p.setOpacity(radialOpacity); auto icon = ([loaded, radial, loading] { if (loaded && !radial) { return &st::historyFileInPlay; } else if (radial || loading) { return &st::historyFileInCancel; } return &st::historyFileInDownload; })(); QRect inner((_width - st::msgFileSize) / 2, (height - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize); icon->paintInCenter(p, inner); if (radial) { p.setOpacity(1); QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine))); _animation->radial.draw(p, rinner, st::msgFileRadialLine, st::historyFileThumbRadialFg); } } if (_delete && (_state & StateFlag::Over)) { auto deleteSelected = (_state & StateFlag::DeleteOver); auto deletePos = QPoint(_width - st::stickerPanDeleteIconBg.width(), 0); p.setOpacity(deleteSelected ? st::stickerPanDeleteOpacityBgOver : st::stickerPanDeleteOpacityBg); st::stickerPanDeleteIconBg.paint(p, deletePos, width()); p.setOpacity(deleteSelected ? st::stickerPanDeleteOpacityFgOver : st::stickerPanDeleteOpacityFg); st::stickerPanDeleteIconFg.paint(p, deletePos, width()); p.setOpacity(1.); } } TextState Gif::getState( QPoint point, StateRequest request) const { if (QRect(0, 0, _width, st::inlineMediaHeight).contains(point)) { if (_delete && rtlpoint(point, _width).x() >= _width - st::stickerPanDeleteIconBg.width() && point.y() < st::stickerPanDeleteIconBg.height()) { return { nullptr, _delete }; } else { return { nullptr, _send }; } } return {}; } void Gif::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { if (!p) return; if (_delete && p == _delete) { bool wasactive = (_state & StateFlag::DeleteOver); if (active != wasactive) { auto from = active ? 0. : 1., to = active ? 1. : 0.; _a_deleteOver.start([this] { update(); }, from, to, st::stickersRowDuration); if (active) { _state |= StateFlag::DeleteOver; } else { _state &= ~StateFlag::DeleteOver; } } } if (p == _delete || p == _send) { bool wasactive = (_state & StateFlag::Over); if (active != wasactive) { if (!getShownDocument()->loaded()) { ensureAnimation(); auto from = active ? 0. : 1., to = active ? 1. : 0.; _animation->_a_over.start([this] { update(); }, from, to, st::stickersRowDuration); } if (active) { _state |= StateFlag::Over; } else { _state &= ~StateFlag::Over; } } } ItemBase::clickHandlerActiveChanged(p, active); } QSize Gif::countFrameSize() const { bool animating = (_gif && _gif->ready()); int32 framew = animating ? _gif->width() : content_width(), frameh = animating ? _gif->height() : content_height(), height = st::inlineMediaHeight; if (framew * height > frameh * _width) { if (framew < st::maxStickerSize || frameh > height) { if (frameh > height || (framew * height / frameh) <= st::maxStickerSize) { framew = framew * height / frameh; frameh = height; } else { frameh = int32(frameh * st::maxStickerSize) / framew; framew = st::maxStickerSize; } } } else { if (frameh < st::maxStickerSize || framew > _width) { if (framew > _width || (frameh * _width / framew) <= st::maxStickerSize) { frameh = frameh * _width / framew; framew = _width; } else { framew = int32(framew * st::maxStickerSize) / frameh; frameh = st::maxStickerSize; } } } return QSize(framew, frameh); } void Gif::prepareThumb(int32 width, int32 height, const QSize &frame) const { if (DocumentData *document = getShownDocument()) { if (!document->thumb->isNull()) { if (document->thumb->loaded()) { if (_thumb.width() != width * cIntRetinaFactor() || _thumb.height() != height * cIntRetinaFactor()) { _thumb = document->thumb->pixNoCache(frame.width() * cIntRetinaFactor(), frame.height() * cIntRetinaFactor(), Images::Option::Smooth, width, height); } } else { document->thumb->load(); } } } else { ImagePtr thumb = getResultThumb(); if (!thumb->isNull()) { if (thumb->loaded()) { if (_thumb.width() != width * cIntRetinaFactor() || _thumb.height() != height * cIntRetinaFactor()) { _thumb = thumb->pixNoCache(frame.width() * cIntRetinaFactor(), frame.height() * cIntRetinaFactor(), Images::Option::Smooth, width, height); } } else { thumb->load(); } } } } void Gif::ensureAnimation() const { if (!_animation) { _animation = std::make_unique<AnimationData>(animation(const_cast<Gif*>(this), &Gif::step_radial)); } } bool Gif::isRadialAnimation(TimeMs ms) const { if (!_animation || !_animation->radial.animating()) return false; _animation->radial.step(ms); return _animation && _animation->radial.animating(); } void Gif::step_radial(TimeMs ms, bool timer) { if (timer) { update(); } else { DocumentData *document = getShownDocument(); _animation->radial.update(document->progress(), !document->loading() || document->loaded(), ms); if (!_animation->radial.animating() && document->loaded()) { _animation.reset(); } } } void Gif::clipCallback(Media::Clip::Notification notification) { using namespace Media::Clip; switch (notification) { case NotificationReinit: { if (_gif) { if (_gif->state() == State::Error) { _gif.setBad(); getShownDocument()->forget(); } else if (_gif->ready() && !_gif->started()) { auto height = st::inlineMediaHeight; auto frame = countFrameSize(); _gif->start(frame.width(), frame.height(), _width, height, ImageRoundRadius::None, RectPart::None); } else if (_gif->autoPausedGif() && !context()->inlineItemVisible(this)) { _gif.reset(); getShownDocument()->forget(); } } update(); } break; case NotificationRepaint: { if (_gif && !_gif->currentDisplayed()) { update(); } } break; } } Sticker::Sticker(not_null<Context*> context, Result *result) : FileBase(context, result) { } void Sticker::initDimensions() { _maxw = st::stickerPanSize.width(); _minh = st::stickerPanSize.height(); } void Sticker::preload() const { if (DocumentData *document = getShownDocument()) { bool goodThumb = !document->thumb->isNull() && ((document->thumb->width() >= 128) || (document->thumb->height() >= 128)); if (goodThumb) { document->thumb->load(); } else { document->checkSticker(); } } else { ImagePtr thumb = getResultThumb(); if (!thumb->isNull()) { thumb->load(); } } } void Sticker::paint(Painter &p, const QRect &clip, const PaintContext *context) const { bool loaded = getShownDocument()->loaded(); auto over = _a_over.current(context->ms, _active ? 1. : 0.); if (over > 0) { p.setOpacity(over); App::roundRect(p, QRect(QPoint(0, 0), st::stickerPanSize), st::emojiPanHover, StickerHoverCorners); p.setOpacity(1); } prepareThumb(); if (!_thumb.isNull()) { int w = _thumb.width() / cIntRetinaFactor(), h = _thumb.height() / cIntRetinaFactor(); QPoint pos = QPoint((st::stickerPanSize.width() - w) / 2, (st::stickerPanSize.height() - h) / 2); p.drawPixmap(pos, _thumb); } } TextState Sticker::getState( QPoint point, StateRequest request) const { if (QRect(0, 0, _width, st::inlineMediaHeight).contains(point)) { return { nullptr, _send }; } return {}; } void Sticker::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { if (!p) return; if (p == _send) { if (active != _active) { _active = active; auto from = active ? 0. : 1., to = active ? 1. : 0.; _a_over.start([this] { update(); }, from, to, st::stickersRowDuration); } } ItemBase::clickHandlerActiveChanged(p, active); } QSize Sticker::getThumbSize() const { int width = qMax(content_width(), 1), height = qMax(content_height(), 1); float64 coefw = (st::stickerPanSize.width() - st::buttonRadius * 2) / float64(width); float64 coefh = (st::stickerPanSize.height() - st::buttonRadius * 2) / float64(height); float64 coef = qMin(qMin(coefw, coefh), 1.); int w = qRound(coef * content_width()), h = qRound(coef * content_height()); return QSize(qMax(w, 1), qMax(h, 1)); } void Sticker::prepareThumb() const { if (DocumentData *document = getShownDocument()) { bool goodThumb = !document->thumb->isNull() && ((document->thumb->width() >= 128) || (document->thumb->height() >= 128)); if (goodThumb) { document->thumb->load(); } else { document->checkSticker(); } ImagePtr sticker = goodThumb ? document->thumb : document->sticker()->img; if (!_thumbLoaded && sticker->loaded()) { QSize thumbSize = getThumbSize(); _thumb = sticker->pix(thumbSize.width(), thumbSize.height()); _thumbLoaded = true; } } else { ImagePtr thumb = getResultThumb(); if (thumb->loaded()) { if (!_thumbLoaded) { QSize thumbSize = getThumbSize(); _thumb = thumb->pix(thumbSize.width(), thumbSize.height()); _thumbLoaded = true; } } else { thumb->load(); } } } Photo::Photo(not_null<Context*> context, Result *result) : ItemBase(context, result) { } void Photo::initDimensions() { PhotoData *photo = getShownPhoto(); int32 w = photo->full->width(), h = photo->full->height(); if (w <= 0 || h <= 0) { _maxw = 0; } else { w = w * st::inlineMediaHeight / h; _maxw = qMax(w, int32(st::inlineResultsMinWidth)); } _minh = st::inlineMediaHeight + st::inlineResultsSkip; } void Photo::paint(Painter &p, const QRect &clip, const PaintContext *context) const { int32 height = st::inlineMediaHeight; QSize frame = countFrameSize(); QRect r(0, 0, _width, height); prepareThumb(_width, height, frame); if (_thumb.isNull()) { p.fillRect(r, st::overviewPhotoBg); } else { p.drawPixmap(r.topLeft(), _thumb); } } TextState Photo::getState( QPoint point, StateRequest request) const { if (QRect(0, 0, _width, st::inlineMediaHeight).contains(point)) { return { nullptr, _send }; } return {}; } PhotoData *Photo::getShownPhoto() const { if (PhotoData *result = getPhoto()) { return result; } return getResultPhoto(); } QSize Photo::countFrameSize() const { PhotoData *photo = getShownPhoto(); int32 framew = photo->full->width(), frameh = photo->full->height(), height = st::inlineMediaHeight; if (framew * height > frameh * _width) { if (framew < st::maxStickerSize || frameh > height) { if (frameh > height || (framew * height / frameh) <= st::maxStickerSize) { framew = framew * height / frameh; frameh = height; } else { frameh = int32(frameh * st::maxStickerSize) / framew; framew = st::maxStickerSize; } } } else { if (frameh < st::maxStickerSize || framew > _width) { if (framew > _width || (frameh * _width / framew) <= st::maxStickerSize) { frameh = frameh * _width / framew; framew = _width; } else { framew = int32(framew * st::maxStickerSize) / frameh; frameh = st::maxStickerSize; } } } return QSize(framew, frameh); } void Photo::prepareThumb(int32 width, int32 height, const QSize &frame) const { if (PhotoData *photo = getShownPhoto()) { if (photo->medium->loaded()) { if (!_thumbLoaded || _thumb.width() != width * cIntRetinaFactor() || _thumb.height() != height * cIntRetinaFactor()) { _thumb = photo->medium->pixNoCache(frame.width() * cIntRetinaFactor(), frame.height() * cIntRetinaFactor(), Images::Option::Smooth, width, height); } _thumbLoaded = true; } else { if (photo->thumb->loaded()) { if (_thumb.width() != width * cIntRetinaFactor() || _thumb.height() != height * cIntRetinaFactor()) { _thumb = photo->thumb->pixNoCache(frame.width() * cIntRetinaFactor(), frame.height() * cIntRetinaFactor(), Images::Option::Smooth, width, height); } } photo->medium->load(); } } else { ImagePtr thumb = getResultThumb(); if (thumb->loaded()) { if (_thumb.width() != width * cIntRetinaFactor() || _thumb.height() != height * cIntRetinaFactor()) { _thumb = thumb->pixNoCache(frame.width() * cIntRetinaFactor(), frame.height() * cIntRetinaFactor(), Images::Option::Smooth, width, height); } } else { thumb->load(); } } } Video::Video(not_null<Context*> context, Result *result) : FileBase(context, result) , _link(getResultContentUrlHandler()) , _title(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip) , _description(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip) { if (int duration = content_duration()) { _duration = formatDurationText(duration); _durationWidth = st::normalFont->width(_duration); } } void Video::initDimensions() { bool withThumb = !content_thumb()->isNull(); _maxw = st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft; int32 textWidth = _maxw - (withThumb ? (st::inlineThumbSize + st::inlineThumbSkip) : 0); TextParseOptions titleOpts = { 0, _maxw, 2 * st::semiboldFont->height, Qt::LayoutDirectionAuto }; auto title = TextUtilities::SingleLine(_result->getLayoutTitle()); if (title.isEmpty()) { title = lang(lng_media_video); } _title.setText(st::semiboldTextStyle, title, titleOpts); int32 titleHeight = qMin(_title.countHeight(_maxw), 2 * st::semiboldFont->height); int32 descriptionLines = withThumb ? (titleHeight > st::semiboldFont->height ? 1 : 2) : 3; TextParseOptions descriptionOpts = { TextParseMultiline, _maxw, descriptionLines * st::normalFont->height, Qt::LayoutDirectionAuto }; QString description = _result->getLayoutDescription(); if (description.isEmpty()) { description = _duration; } _description.setText(st::defaultTextStyle, description, descriptionOpts); int32 descriptionHeight = qMin(_description.countHeight(_maxw), descriptionLines * st::normalFont->height); _minh = st::inlineThumbSize; _minh += st::inlineRowMargin * 2 + st::inlineRowBorder; } void Video::paint(Painter &p, const QRect &clip, const PaintContext *context) const { int left = st::inlineThumbSize + st::inlineThumbSkip; bool withThumb = !content_thumb()->isNull(); if (withThumb) { prepareThumb(st::inlineThumbSize, st::inlineThumbSize); if (_thumb.isNull()) { p.fillRect(rtlrect(0, st::inlineRowMargin, st::inlineThumbSize, st::inlineThumbSize, _width), st::overviewPhotoBg); } else { p.drawPixmapLeft(0, st::inlineRowMargin, _width, _thumb); } } else { p.fillRect(rtlrect(0, st::inlineRowMargin, st::inlineThumbSize, st::inlineThumbSize, _width), st::overviewVideoBg); } if (!_duration.isEmpty()) { int durationTop = st::inlineRowMargin + st::inlineThumbSize - st::normalFont->height - st::inlineDurationMargin; int durationW = _durationWidth + 2 * st::msgDateImgPadding.x(), durationH = st::normalFont->height + 2 * st::msgDateImgPadding.y(); int durationX = (st::inlineThumbSize - durationW) / 2, durationY = st::inlineRowMargin + st::inlineThumbSize - durationH; App::roundRect(p, durationX, durationY - st::msgDateImgPadding.y(), durationW, durationH, st::msgDateImgBg, DateCorners); p.setPen(st::msgDateImgFg); p.setFont(st::normalFont); p.drawText(durationX + st::msgDateImgPadding.x(), durationTop + st::normalFont->ascent, _duration); } p.setPen(st::inlineTitleFg); _title.drawLeftElided(p, left, st::inlineRowMargin, _width - left, _width, 2); int32 titleHeight = qMin(_title.countHeight(_width - left), st::semiboldFont->height * 2); p.setPen(st::inlineDescriptionFg); int32 descriptionLines = withThumb ? (titleHeight > st::semiboldFont->height ? 1 : 2) : 3; _description.drawLeftElided(p, left, st::inlineRowMargin + titleHeight, _width - left, _width, descriptionLines); if (!context->lastRow) { p.fillRect(rtlrect(left, _height - st::inlineRowBorder, _width - left, st::inlineRowBorder, _width), st::inlineRowBorderFg); } } TextState Video::getState( QPoint point, StateRequest request) const { if (QRect(0, st::inlineRowMargin, st::inlineThumbSize, st::inlineThumbSize).contains(point)) { return { nullptr, _link }; } if (QRect(st::inlineThumbSize + st::inlineThumbSkip, 0, _width - st::inlineThumbSize - st::inlineThumbSkip, _height).contains(point)) { return { nullptr, _send }; } return {}; } void Video::prepareThumb(int32 width, int32 height) const { ImagePtr thumb = content_thumb(); if (thumb->loaded()) { if (_thumb.width() != width * cIntRetinaFactor() || _thumb.height() != height * cIntRetinaFactor()) { int32 w = qMax(convertScale(thumb->width()), 1), h = qMax(convertScale(thumb->height()), 1); if (w * height > h * width) { if (height < h) { w = w * height / h; h = height; } } else { if (width < w) { h = h * width / w; w = width; } } _thumb = thumb->pixNoCache(w * cIntRetinaFactor(), h * cIntRetinaFactor(), Images::Option::Smooth, width, height); } } else { thumb->load(); } } void OpenFileClickHandler::onClickImpl() const { _result->openFile(); } void CancelFileClickHandler::onClickImpl() const { _result->cancelFile(); } File::File(not_null<Context*> context, Result *result) : FileBase(context, result) , _title(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::msgFileSize - st::inlineThumbSkip) , _description(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::msgFileSize - st::inlineThumbSkip) , _open(std::make_shared<OpenFileClickHandler>(result)) , _cancel(std::make_shared<CancelFileClickHandler>(result)) { updateStatusText(); regDocumentItem(getShownDocument(), this); } void File::initDimensions() { _maxw = st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft; int textWidth = _maxw - (st::msgFileSize + st::inlineThumbSkip); TextParseOptions titleOpts = { 0, _maxw, st::semiboldFont->height, Qt::LayoutDirectionAuto }; _title.setText(st::semiboldTextStyle, TextUtilities::SingleLine(_result->getLayoutTitle()), titleOpts); TextParseOptions descriptionOpts = { TextParseMultiline, _maxw, st::normalFont->height, Qt::LayoutDirectionAuto }; _description.setText(st::defaultTextStyle, _result->getLayoutDescription(), descriptionOpts); _minh = st::msgFileSize; _minh += st::inlineRowMargin * 2 + st::inlineRowBorder; } void File::paint(Painter &p, const QRect &clip, const PaintContext *context) const { int32 left = st::msgFileSize + st::inlineThumbSkip; DocumentData *document = getShownDocument(); bool loaded = document->loaded(), displayLoading = document->displayLoading(); if (displayLoading) { ensureAnimation(); if (!_animation->radial.animating()) { _animation->radial.start(document->progress()); } } bool showPause = updateStatusText(); bool radial = isRadialAnimation(context->ms); auto inner = rtlrect(0, st::inlineRowMargin, st::msgFileSize, st::msgFileSize, _width); p.setPen(Qt::NoPen); if (isThumbAnimation(context->ms)) { auto over = _animation->a_thumbOver.current(); p.setBrush(anim::brush(st::msgFileInBg, st::msgFileInBgOver, over)); } else { bool over = ClickHandler::showAsActive(document->loading() ? _cancel : _open); p.setBrush(over ? st::msgFileInBgOver : st::msgFileInBg); } { PainterHighQualityEnabler hq(p); p.drawEllipse(inner); } if (radial) { auto radialCircle = inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine)); _animation->radial.draw(p, radialCircle, st::msgFileRadialLine, st::historyFileInRadialFg); } auto icon = ([showPause, radial, document] { if (showPause) { return &st::historyFileInPause; } else if (radial || document->loading()) { return &st::historyFileInCancel; } else if (true || document->loaded()) { if (document->isImage()) { return &st::historyFileInImage; } else if (document->isVoiceMessage() || document->isAudioFile()) { return &st::historyFileInPlay; } return &st::historyFileInDocument; } return &st::historyFileInDownload; })(); icon->paintInCenter(p, inner); int titleTop = st::inlineRowMargin + st::inlineRowFileNameTop; int descriptionTop = st::inlineRowMargin + st::inlineRowFileDescriptionTop; p.setPen(st::inlineTitleFg); _title.drawLeftElided(p, left, titleTop, _width - left, _width); p.setPen(st::inlineDescriptionFg); bool drawStatusSize = true; if (_statusSize == FileStatusSizeReady || _statusSize == FileStatusSizeLoaded || _statusSize == FileStatusSizeFailed) { if (!_description.isEmpty()) { _description.drawLeftElided(p, left, descriptionTop, _width - left, _width); drawStatusSize = false; } } if (drawStatusSize) { p.setFont(st::normalFont); p.drawTextLeft(left, descriptionTop, _width, _statusText); } if (!context->lastRow) { p.fillRect(rtlrect(left, _height - st::inlineRowBorder, _width - left, st::inlineRowBorder, _width), st::inlineRowBorderFg); } } TextState File::getState( QPoint point, StateRequest request) const { if (QRect(0, st::inlineRowMargin, st::msgFileSize, st::msgFileSize).contains(point)) { return { nullptr, getShownDocument()->loading() ? _cancel : _open }; } else { auto left = st::msgFileSize + st::inlineThumbSkip; if (QRect(left, 0, _width - left, _height).contains(point)) { return { nullptr, _send }; } } return {}; } void File::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { if (p == _open || p == _cancel) { ensureAnimation(); _animation->a_thumbOver.start([this] { thumbAnimationCallback(); }, active ? 0. : 1., active ? 1. : 0., st::msgFileOverDuration); } } File::~File() { unregDocumentItem(getShownDocument(), this); } void File::thumbAnimationCallback() { update(); } void File::step_radial(TimeMs ms, bool timer) { if (timer) { update(); } else { DocumentData *document = getShownDocument(); _animation->radial.update(document->progress(), !document->loading() || document->loaded(), ms); if (!_animation->radial.animating()) { checkAnimationFinished(); } } } void File::ensureAnimation() const { if (!_animation) { _animation = std::make_unique<AnimationData>(animation(const_cast<File*>(this), &File::step_radial)); } } void File::checkAnimationFinished() const { if (_animation && !_animation->a_thumbOver.animating() && !_animation->radial.animating()) { if (getShownDocument()->loaded()) { _animation.reset(); } } } bool File::updateStatusText() const { bool showPause = false; int32 statusSize = 0, realDuration = 0; DocumentData *document = getShownDocument(); if (document->status == FileDownloadFailed || document->status == FileUploadFailed) { statusSize = FileStatusSizeFailed; } else if (document->uploading()) { statusSize = document->uploadingData->offset; } else if (document->loading()) { statusSize = document->loadOffset(); } else if (document->loaded()) { using State = Media::Player::State; if (document->isVoiceMessage()) { statusSize = FileStatusSizeLoaded; auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice); if (state.id == AudioMsgId(document, FullMsgId()) && !Media::Player::IsStoppedOrStopping(state.state)) { statusSize = -1 - (state.position / state.frequency); realDuration = (state.length / state.frequency); showPause = (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting); } } else if (document->isAudioFile()) { statusSize = FileStatusSizeLoaded; auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Song); if (state.id == AudioMsgId(document, FullMsgId()) && !Media::Player::IsStoppedOrStopping(state.state)) { statusSize = -1 - (state.position / state.frequency); realDuration = (state.length / state.frequency); showPause = (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting); } if (!showPause && (state.id == AudioMsgId(document, FullMsgId())) && Media::Player::instance()->isSeeking(AudioMsgId::Type::Song)) { showPause = true; } } else { statusSize = FileStatusSizeLoaded; } } else { statusSize = FileStatusSizeReady; } if (statusSize != _statusSize) { int32 duration = document->isSong() ? document->song()->duration : (document->isVoiceMessage() ? document->voice()->duration : -1); setStatusSize(statusSize, document->size, duration, realDuration); } return showPause; } void File::setStatusSize(int32 newSize, int32 fullSize, int32 duration, qint64 realDuration) const { _statusSize = newSize; if (_statusSize == FileStatusSizeReady) { _statusText = (duration >= 0) ? formatDurationAndSizeText(duration, fullSize) : (duration < -1 ? formatGifAndSizeText(fullSize) : formatSizeText(fullSize)); } else if (_statusSize == FileStatusSizeLoaded) { _statusText = (duration >= 0) ? formatDurationText(duration) : (duration < -1 ? qsl("GIF") : formatSizeText(fullSize)); } else if (_statusSize == FileStatusSizeFailed) { _statusText = lang(lng_attach_failed); } else if (_statusSize >= 0) { _statusText = formatDownloadText(_statusSize, fullSize); } else { _statusText = formatPlayedText(-_statusSize - 1, realDuration); } } Contact::Contact(not_null<Context*> context, Result *result) : ItemBase(context, result) , _title(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip) , _description(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip) { } void Contact::initDimensions() { _maxw = st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft; int32 textWidth = _maxw - (st::inlineThumbSize + st::inlineThumbSkip); TextParseOptions titleOpts = { 0, _maxw, st::semiboldFont->height, Qt::LayoutDirectionAuto }; _title.setText(st::semiboldTextStyle, TextUtilities::SingleLine(_result->getLayoutTitle()), titleOpts); int32 titleHeight = qMin(_title.countHeight(_maxw), st::semiboldFont->height); TextParseOptions descriptionOpts = { TextParseMultiline, _maxw, st::normalFont->height, Qt::LayoutDirectionAuto }; _description.setText(st::defaultTextStyle, _result->getLayoutDescription(), descriptionOpts); int32 descriptionHeight = qMin(_description.countHeight(_maxw), st::normalFont->height); _minh = st::msgFileSize; _minh += st::inlineRowMargin * 2 + st::inlineRowBorder; } void Contact::paint(Painter &p, const QRect &clip, const PaintContext *context) const { int32 left = st::emojiPanHeaderLeft - st::inlineResultsLeft; left = st::msgFileSize + st::inlineThumbSkip; prepareThumb(st::msgFileSize, st::msgFileSize); QRect rthumb(rtlrect(0, st::inlineRowMargin, st::msgFileSize, st::msgFileSize, _width)); p.drawPixmapLeft(rthumb.topLeft(), _width, _thumb); int titleTop = st::inlineRowMargin + st::inlineRowFileNameTop; int descriptionTop = st::inlineRowMargin + st::inlineRowFileDescriptionTop; p.setPen(st::inlineTitleFg); _title.drawLeftElided(p, left, titleTop, _width - left, _width); p.setPen(st::inlineDescriptionFg); _description.drawLeftElided(p, left, descriptionTop, _width - left, _width); if (!context->lastRow) { p.fillRect(rtlrect(left, _height - st::inlineRowBorder, _width - left, st::inlineRowBorder, _width), st::inlineRowBorderFg); } } TextState Contact::getState( QPoint point, StateRequest request) const { if (!QRect(0, st::inlineRowMargin, st::msgFileSize, st::inlineThumbSize).contains(point)) { auto left = (st::msgFileSize + st::inlineThumbSkip); if (QRect(left, 0, _width - left, _height).contains(point)) { return { nullptr, _send }; } } return {}; } void Contact::prepareThumb(int width, int height) const { ImagePtr thumb = getResultThumb(); if (thumb->isNull()) { if (_thumb.width() != width * cIntRetinaFactor() || _thumb.height() != height * cIntRetinaFactor()) { _thumb = getResultContactAvatar(width, height); } return; } if (thumb->loaded()) { if (_thumb.width() != width * cIntRetinaFactor() || _thumb.height() != height * cIntRetinaFactor()) { int w = qMax(convertScale(thumb->width()), 1), h = qMax(convertScale(thumb->height()), 1); if (w * height > h * width) { if (height < h) { w = w * height / h; h = height; } } else { if (width < w) { h = h * width / w; w = width; } } _thumb = thumb->pixNoCache(w * cIntRetinaFactor(), h * cIntRetinaFactor(), Images::Option::Smooth, width, height); } } else { thumb->load(); } } Article::Article(not_null<Context*> context, Result *result, bool withThumb) : ItemBase(context, result) , _url(getResultUrlHandler()) , _link(getResultContentUrlHandler()) , _withThumb(withThumb) , _title(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip) , _description(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip) { LocationCoords location; if (!_link && result->getLocationCoords(&location)) { _link = std::make_shared<LocationClickHandler>(location); } _thumbLetter = getResultThumbLetter(); } void Article::initDimensions() { _maxw = st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft; int32 textWidth = _maxw - (_withThumb ? (st::inlineThumbSize + st::inlineThumbSkip) : 0); TextParseOptions titleOpts = { 0, _maxw, 2 * st::semiboldFont->height, Qt::LayoutDirectionAuto }; _title.setText(st::semiboldTextStyle, TextUtilities::SingleLine(_result->getLayoutTitle()), titleOpts); int32 titleHeight = qMin(_title.countHeight(_maxw), 2 * st::semiboldFont->height); int32 descriptionLines = (_withThumb || _url) ? 2 : 3; QString description = _result->getLayoutDescription(); TextParseOptions descriptionOpts = { TextParseMultiline, _maxw, descriptionLines * st::normalFont->height, Qt::LayoutDirectionAuto }; _description.setText(st::defaultTextStyle, description, descriptionOpts); int32 descriptionHeight = qMin(_description.countHeight(_maxw), descriptionLines * st::normalFont->height); _minh = titleHeight + descriptionHeight; if (_url) _minh += st::normalFont->height; if (_withThumb) _minh = qMax(_minh, int32(st::inlineThumbSize)); _minh += st::inlineRowMargin * 2 + st::inlineRowBorder; } int32 Article::resizeGetHeight(int32 width) { _width = qMin(width, _maxw); if (_url) { _urlText = getResultUrl(); _urlWidth = st::normalFont->width(_urlText); if (_urlWidth > _width - st::inlineThumbSize - st::inlineThumbSkip) { _urlText = st::normalFont->elided(_urlText, _width - st::inlineThumbSize - st::inlineThumbSkip); _urlWidth = st::normalFont->width(_urlText); } } _height = _minh; return _height; } void Article::paint(Painter &p, const QRect &clip, const PaintContext *context) const { int32 left = st::emojiPanHeaderLeft - st::inlineResultsLeft; if (_withThumb) { left = st::inlineThumbSize + st::inlineThumbSkip; prepareThumb(st::inlineThumbSize, st::inlineThumbSize); QRect rthumb(rtlrect(0, st::inlineRowMargin, st::inlineThumbSize, st::inlineThumbSize, _width)); if (_thumb.isNull()) { ImagePtr thumb = getResultThumb(); if (thumb->isNull() && !_thumbLetter.isEmpty()) { int32 index = (_thumbLetter.at(0).unicode() % 4); style::color colors[] = { st::msgFile3Bg, st::msgFile4Bg, st::msgFile2Bg, st::msgFile1Bg }; p.fillRect(rthumb, colors[index]); if (!_thumbLetter.isEmpty()) { p.setFont(st::linksLetterFont); p.setPen(st::linksLetterFg); p.drawText(rthumb, _thumbLetter, style::al_center); } } else { p.fillRect(rthumb, st::overviewPhotoBg); } } else { p.drawPixmapLeft(rthumb.topLeft(), _width, _thumb); } } p.setPen(st::inlineTitleFg); _title.drawLeftElided(p, left, st::inlineRowMargin, _width - left, _width, 2); int32 titleHeight = qMin(_title.countHeight(_width - left), st::semiboldFont->height * 2); p.setPen(st::inlineDescriptionFg); int32 descriptionLines = (_withThumb || _url) ? 2 : 3; _description.drawLeftElided(p, left, st::inlineRowMargin + titleHeight, _width - left, _width, descriptionLines); if (_url) { int32 descriptionHeight = qMin(_description.countHeight(_width - left), st::normalFont->height * descriptionLines); p.drawTextLeft(left, st::inlineRowMargin + titleHeight + descriptionHeight, _width, _urlText, _urlWidth); } if (!context->lastRow) { p.fillRect(rtlrect(left, _height - st::inlineRowBorder, _width - left, st::inlineRowBorder, _width), st::inlineRowBorderFg); } } TextState Article::getState( QPoint point, StateRequest request) const { if (_withThumb && QRect(0, st::inlineRowMargin, st::inlineThumbSize, st::inlineThumbSize).contains(point)) { return { nullptr, _link }; } auto left = _withThumb ? (st::inlineThumbSize + st::inlineThumbSkip) : 0; if (QRect(left, 0, _width - left, _height).contains(point)) { if (_url) { auto left = st::inlineThumbSize + st::inlineThumbSkip; auto titleHeight = qMin(_title.countHeight(_width - left), st::semiboldFont->height * 2); auto descriptionLines = 2; auto descriptionHeight = qMin(_description.countHeight(_width - left), st::normalFont->height * descriptionLines); if (rtlrect(left, st::inlineRowMargin + titleHeight + descriptionHeight, _urlWidth, st::normalFont->height, _width).contains(point)) { return { nullptr, _url }; } } return { nullptr, _send }; } return {}; } void Article::prepareThumb(int width, int height) const { ImagePtr thumb = getResultThumb(); if (thumb->isNull()) { if (_thumb.width() != width * cIntRetinaFactor() || _thumb.height() != height * cIntRetinaFactor()) { _thumb = getResultContactAvatar(width, height); } return; } if (thumb->loaded()) { if (_thumb.width() != width * cIntRetinaFactor() || _thumb.height() != height * cIntRetinaFactor()) { int w = qMax(convertScale(thumb->width()), 1), h = qMax(convertScale(thumb->height()), 1); if (w * height > h * width) { if (height < h) { w = w * height / h; h = height; } } else { if (width < w) { h = h * width / w; w = width; } } _thumb = thumb->pixNoCache(w * cIntRetinaFactor(), h * cIntRetinaFactor(), Images::Option::Smooth, width, height); } } else { thumb->load(); } } Game::Game(not_null<Context*> context, Result *result) : ItemBase(context, result) , _title(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip) , _description(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip) { countFrameSize(); } void Game::countFrameSize() { if (auto document = getResultDocument()) { if (document->isAnimation()) { auto documentSize = document->dimensions; if (documentSize.isEmpty()) { documentSize = QSize(st::inlineThumbSize, st::inlineThumbSize); } auto resizeByHeight1 = (documentSize.width() > documentSize.height()) && (documentSize.height() >= st::inlineThumbSize); auto resizeByHeight2 = (documentSize.height() >= documentSize.width()) && (documentSize.width() < st::inlineThumbSize); if (resizeByHeight1 || resizeByHeight2) { if (documentSize.height() > st::inlineThumbSize) { _frameSize = QSize((documentSize.width() * st::inlineThumbSize) / documentSize.height(), st::inlineThumbSize); } } else { if (documentSize.width() > st::inlineThumbSize) { _frameSize = QSize(st::inlineThumbSize, (documentSize.height() * st::inlineThumbSize) / documentSize.width()); } } if (!_frameSize.width()) { _frameSize.setWidth(1); } if (!_frameSize.height()) { _frameSize.setHeight(1); } } } } void Game::initDimensions() { _maxw = st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft; int32 textWidth = _maxw - (st::inlineThumbSize + st::inlineThumbSkip); TextParseOptions titleOpts = { 0, _maxw, 2 * st::semiboldFont->height, Qt::LayoutDirectionAuto }; _title.setText(st::semiboldTextStyle, TextUtilities::SingleLine(_result->getLayoutTitle()), titleOpts); int32 titleHeight = qMin(_title.countHeight(_maxw), 2 * st::semiboldFont->height); int32 descriptionLines = 2; QString description = _result->getLayoutDescription(); TextParseOptions descriptionOpts = { TextParseMultiline, _maxw, descriptionLines * st::normalFont->height, Qt::LayoutDirectionAuto }; _description.setText(st::defaultTextStyle, description, descriptionOpts); int32 descriptionHeight = qMin(_description.countHeight(_maxw), descriptionLines * st::normalFont->height); _minh = titleHeight + descriptionHeight; accumulate_max(_minh, st::inlineThumbSize); _minh += st::inlineRowMargin * 2 + st::inlineRowBorder; } void Game::setPosition(int32 position) { ItemBase::setPosition(position); if (_position < 0) { _gif.reset(); } } void Game::paint(Painter &p, const QRect &clip, const PaintContext *context) const { int32 left = st::emojiPanHeaderLeft - st::inlineResultsLeft; left = st::inlineThumbSize + st::inlineThumbSkip; auto rthumb = rtlrect(0, st::inlineRowMargin, st::inlineThumbSize, st::inlineThumbSize, _width); // Gif thumb auto thumbDisplayed = false, radial = false; auto document = getResultDocument(); auto animatedThumb = document && document->isAnimation(); if (animatedThumb) { document->automaticLoad(nullptr); bool loaded = document->loaded(), loading = document->loading(), displayLoading = document->displayLoading(); if (loaded && !_gif && !_gif.isBad()) { auto that = const_cast<Game*>(this); that->_gif = Media::Clip::MakeReader(document, FullMsgId(), [that](Media::Clip::Notification notification) { that->clipCallback(notification); }); if (_gif) _gif->setAutoplay(); } bool animating = (_gif && _gif->started()); if (displayLoading) { if (!_radial) { _radial = std::make_unique<Ui::RadialAnimation>(animation(const_cast<Game*>(this), &Game::step_radial)); } if (!_radial->animating()) { _radial->start(document->progress()); } } radial = isRadialAnimation(context->ms); if (animating) { if (!_thumb.isNull()) _thumb = QPixmap(); auto animationThumb = _gif->current(_frameSize.width(), _frameSize.height(), st::inlineThumbSize, st::inlineThumbSize, ImageRoundRadius::None, RectPart::None, context->paused ? 0 : context->ms); p.drawPixmapLeft(rthumb.topLeft(), _width, animationThumb); thumbDisplayed = true; } } if (!thumbDisplayed) { prepareThumb(st::inlineThumbSize, st::inlineThumbSize); if (_thumb.isNull()) { p.fillRect(rthumb, st::overviewPhotoBg); } else { p.drawPixmapLeft(rthumb.topLeft(), _width, _thumb); } } if (radial) { p.fillRect(rthumb, st::msgDateImgBg); QRect inner((st::inlineThumbSize - st::msgFileSize) / 2, (st::inlineThumbSize - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize); if (radial) { p.setOpacity(1); QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine))); _radial->draw(p, rinner, st::msgFileRadialLine, st::historyFileThumbRadialFg); } } p.setPen(st::inlineTitleFg); _title.drawLeftElided(p, left, st::inlineRowMargin, _width - left, _width, 2); int32 titleHeight = qMin(_title.countHeight(_width - left), st::semiboldFont->height * 2); p.setPen(st::inlineDescriptionFg); int32 descriptionLines = 2; _description.drawLeftElided(p, left, st::inlineRowMargin + titleHeight, _width - left, _width, descriptionLines); if (!context->lastRow) { p.fillRect(rtlrect(left, _height - st::inlineRowBorder, _width - left, st::inlineRowBorder, _width), st::inlineRowBorderFg); } } TextState Game::getState( QPoint point, StateRequest request) const { int left = st::inlineThumbSize + st::inlineThumbSkip; if (QRect(0, st::inlineRowMargin, st::inlineThumbSize, st::inlineThumbSize).contains(point)) { return { nullptr, _send }; } if (QRect(left, 0, _width - left, _height).contains(point)) { return { nullptr, _send }; } return {}; } void Game::prepareThumb(int width, int height) const { auto thumb = [this] { if (auto photo = getResultPhoto()) { return photo->medium; } else if (auto document = getResultDocument()) { return document->thumb; } return ImagePtr(); }(); if (thumb->isNull()) { return; } if (thumb->loaded()) { if (_thumb.width() != width * cIntRetinaFactor() || _thumb.height() != height * cIntRetinaFactor()) { int w = qMax(convertScale(thumb->width()), 1), h = qMax(convertScale(thumb->height()), 1); auto resizeByHeight1 = (w * height > h * width) && (h >= height); auto resizeByHeight2 = (h * width >= w * height) && (w < width); if (resizeByHeight1 || resizeByHeight2) { if (h > height) { w = w * height / h; h = height; } } else { if (w > width) { h = h * width / w; w = width; } } _thumb = thumb->pixNoCache(w * cIntRetinaFactor(), h * cIntRetinaFactor(), Images::Option::Smooth, width, height); } } else { thumb->load(); } } bool Game::isRadialAnimation(TimeMs ms) const { if (!_radial || !_radial->animating()) return false; _radial->step(ms); return _radial && _radial->animating(); } void Game::step_radial(TimeMs ms, bool timer) { if (timer) { update(); } else { auto document = getResultDocument(); _radial->update(document->progress(), !document->loading() || document->loaded(), ms); if (!_radial->animating() && document->loaded()) { _radial.reset(); } } } void Game::clipCallback(Media::Clip::Notification notification) { using namespace Media::Clip; switch (notification) { case NotificationReinit: { if (_gif) { if (_gif->state() == State::Error) { _gif.setBad(); getResultDocument()->forget(); } else if (_gif->ready() && !_gif->started()) { _gif->start(_frameSize.width(), _frameSize.height(), st::inlineThumbSize, st::inlineThumbSize, ImageRoundRadius::None, RectPart::None); } else if (_gif->autoPausedGif() && !context()->inlineItemVisible(this)) { _gif.reset(); getResultDocument()->forget(); } } update(); } break; case NotificationRepaint: { if (_gif && !_gif->currentDisplayed()) { update(); } } break; } } } // namespace internal } // namespace Layout } // namespace InlineBots �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h�����������������������0000664�0000000�0000000�00000021010�13262451251�0027506�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "base/flags.h" #include "inline_bots/inline_bot_layout_item.h" #include "ui/effects/radial_animation.h" #include "ui/text/text.h" namespace InlineBots { namespace Layout { namespace internal { class FileBase : public ItemBase { public: FileBase(not_null<Context*> context, Result *result); // for saved gif layouts FileBase(not_null<Context*> context, DocumentData *doc); protected: DocumentData *getShownDocument() const; int content_width() const; int content_height() const; int content_duration() const; ImagePtr content_thumb() const; }; class DeleteSavedGifClickHandler : public LeftButtonClickHandler { public: DeleteSavedGifClickHandler(DocumentData *data) : _data(data) { } protected: void onClickImpl() const override; private: DocumentData *_data; }; class Gif : public FileBase { public: Gif(not_null<Context*> context, Result *result); Gif(not_null<Context*> context, DocumentData *doc, bool hasDeleteButton); void setPosition(int32 position) override; void initDimensions() override; bool isFullLine() const override { return false; } bool hasRightSkip() const override { return true; } void paint(Painter &p, const QRect &clip, const PaintContext *context) const override; TextState getState( QPoint point, StateRequest request) const override; // ClickHandlerHost interface void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; int resizeGetHeight(int width) override; private: QSize countFrameSize() const; enum class StateFlag { Over = (1 << 0), DeleteOver = (1 << 1), }; using StateFlags = base::flags<StateFlag>; friend inline constexpr auto is_flag_type(StateFlag) { return true; }; StateFlags _state; Media::Clip::ReaderPointer _gif; ClickHandlerPtr _delete; mutable QPixmap _thumb; void prepareThumb(int32 width, int32 height, const QSize &frame) const; void ensureAnimation() const; bool isRadialAnimation(TimeMs ms) const; void step_radial(TimeMs ms, bool timer); void clipCallback(Media::Clip::Notification notification); struct AnimationData { AnimationData(AnimationCallbacks &&callbacks) : over(false) , radial(std::move(callbacks)) { } bool over; Animation _a_over; Ui::RadialAnimation radial; }; mutable std::unique_ptr<AnimationData> _animation; mutable Animation _a_deleteOver; }; class Photo : public ItemBase { public: Photo(not_null<Context*> context, Result *result); // Not used anywhere currently. //Photo(not_null<Context*> context, PhotoData *photo); void initDimensions() override; bool isFullLine() const override { return false; } bool hasRightSkip() const override { return true; } void paint(Painter &p, const QRect &clip, const PaintContext *context) const override; TextState getState( QPoint point, StateRequest request) const override; private: PhotoData *getShownPhoto() const; QSize countFrameSize() const; mutable QPixmap _thumb; mutable bool _thumbLoaded = false; void prepareThumb(int32 width, int32 height, const QSize &frame) const; }; class Sticker : public FileBase { public: Sticker(not_null<Context*> context, Result *result); // Not used anywhere currently. //Sticker(not_null<Context*> context, DocumentData *document); void initDimensions() override; bool isFullLine() const override { return false; } bool hasRightSkip() const override { return false; } void preload() const override; void paint(Painter &p, const QRect &clip, const PaintContext *context) const override; TextState getState( QPoint point, StateRequest request) const override; // ClickHandlerHost interface void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; private: QSize getThumbSize() const; mutable Animation _a_over; mutable bool _active = false; mutable QPixmap _thumb; mutable bool _thumbLoaded = false; void prepareThumb() const; }; class Video : public FileBase { public: Video(not_null<Context*> context, Result *result); void initDimensions() override; void paint(Painter &p, const QRect &clip, const PaintContext *context) const override; TextState getState( QPoint point, StateRequest request) const override; private: ClickHandlerPtr _link; mutable QPixmap _thumb; Text _title, _description; QString _duration; int _durationWidth = 0; void prepareThumb(int32 width, int32 height) const; }; class OpenFileClickHandler : public LeftButtonClickHandler { public: OpenFileClickHandler(Result *result) : _result(result) { } protected: void onClickImpl() const override; private: Result *_result; }; class CancelFileClickHandler : public LeftButtonClickHandler { public: CancelFileClickHandler(Result *result) : _result(result) { } protected: void onClickImpl() const override; private: Result *_result; }; class File : public FileBase { public: File(not_null<Context*> context, Result *result); void initDimensions() override; void paint(Painter &p, const QRect &clip, const PaintContext *context) const override; TextState getState( QPoint point, StateRequest request) const override; // ClickHandlerHost interface void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; ~File(); private: void thumbAnimationCallback(); void step_radial(TimeMs ms, bool timer); void ensureAnimation() const; void checkAnimationFinished() const; bool updateStatusText() const; bool isRadialAnimation(TimeMs ms) const { if (!_animation || !_animation->radial.animating()) return false; _animation->radial.step(ms); return _animation && _animation->radial.animating(); } bool isThumbAnimation(TimeMs ms) const { if (_animation) { if (_animation->a_thumbOver.animating(ms)) { return true; } checkAnimationFinished(); } return false; } struct AnimationData { AnimationData(AnimationCallbacks &&radialCallbacks) : radial(std::move(radialCallbacks)) { } Animation a_thumbOver; Ui::RadialAnimation radial; }; mutable std::unique_ptr<AnimationData> _animation; Text _title, _description; ClickHandlerPtr _open, _cancel; // >= 0 will contain download / upload string, _statusSize = loaded bytes // < 0 will contain played string, _statusSize = -(seconds + 1) played // 0x7FFFFFF0 will contain status for not yet downloaded file // 0x7FFFFFF1 will contain status for already downloaded file // 0x7FFFFFF2 will contain status for failed to download / upload file mutable int32 _statusSize; mutable QString _statusText; // duration = -1 - no duration, duration = -2 - "GIF" duration void setStatusSize(int32 newSize, int32 fullSize, int32 duration, qint64 realDuration) const; }; class Contact : public ItemBase { public: Contact(not_null<Context*> context, Result *result); void initDimensions() override; void paint(Painter &p, const QRect &clip, const PaintContext *context) const override; TextState getState( QPoint point, StateRequest request) const override; private: mutable QPixmap _thumb; Text _title, _description; void prepareThumb(int width, int height) const; }; class Article : public ItemBase { public: Article(not_null<Context*> context, Result *result, bool withThumb); void initDimensions() override; int resizeGetHeight(int width) override; void paint(Painter &p, const QRect &clip, const PaintContext *context) const override; TextState getState( QPoint point, StateRequest request) const override; private: ClickHandlerPtr _url, _link; bool _withThumb; mutable QPixmap _thumb; Text _title, _description; QString _thumbLetter, _urlText; int32 _urlWidth; void prepareThumb(int width, int height) const; }; class Game : public ItemBase { public: Game(not_null<Context*> context, Result *result); void setPosition(int32 position) override; void initDimensions() override; void paint(Painter &p, const QRect &clip, const PaintContext *context) const override; TextState getState( QPoint point, StateRequest request) const override; private: void countFrameSize(); void prepareThumb(int32 width, int32 height) const; bool isRadialAnimation(TimeMs ms) const; void step_radial(TimeMs ms, bool timer); void clipCallback(Media::Clip::Notification notification); Media::Clip::ReaderPointer _gif; mutable QPixmap _thumb; mutable std::unique_ptr<Ui::RadialAnimation> _radial; Text _title, _description; QSize _frameSize; }; } // namespace internal } // namespace Layout } // namespace InlineBots ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.cpp�������������������������0000664�0000000�0000000�00000013517�13262451251�0027200�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "inline_bots/inline_bot_layout_item.h" #include "data/data_photo.h" #include "data/data_document.h" #include "core/click_handler_types.h" #include "inline_bots/inline_bot_result.h" #include "inline_bots/inline_bot_layout_internal.h" #include "storage/localstorage.h" #include "mainwidget.h" #include "ui/empty_userpic.h" namespace InlineBots { namespace Layout { void ItemBase::setPosition(int32 position) { _position = position; } int32 ItemBase::position() const { return _position; } Result *ItemBase::getResult() const { return _result; } DocumentData *ItemBase::getDocument() const { return _doc; } PhotoData *ItemBase::getPhoto() const { return _photo; } DocumentData *ItemBase::getPreviewDocument() const { auto previewDocument = [this]() -> DocumentData* { if (_doc) { return _doc; } if (_result) { return _result->_document; } return nullptr; }; if (DocumentData *result = previewDocument()) { if (result->sticker() || result->loaded()) { return result; } } return nullptr; } PhotoData *ItemBase::getPreviewPhoto() const { if (_photo) { return _photo; } if (_result) { return _result->_photo; } return nullptr; } void ItemBase::preload() const { if (_result) { if (_result->_photo) { _result->_photo->thumb->load(); } else if (_result->_document) { _result->_document->thumb->load(); } else if (!_result->_thumb->isNull()) { _result->_thumb->load(); } } else if (_doc) { _doc->thumb->load(); } else if (_photo) { _photo->medium->load(); } } void ItemBase::update() { if (_position >= 0) { context()->inlineItemRepaint(this); } } void ItemBase::layoutChanged() { if (_position >= 0) { context()->inlineItemLayoutChanged(this); } } std::unique_ptr<ItemBase> ItemBase::createLayout(not_null<Context*> context, Result *result, bool forceThumb) { using Type = Result::Type; switch (result->_type) { case Type::Photo: return std::make_unique<internal::Photo>(context, result); break; case Type::Audio: case Type::File: return std::make_unique<internal::File>(context, result); break; case Type::Video: return std::make_unique<internal::Video>(context, result); break; case Type::Sticker: return std::make_unique<internal::Sticker>(context, result); break; case Type::Gif: return std::make_unique<internal::Gif>(context, result); break; case Type::Article: case Type::Geo: case Type::Venue: return std::make_unique<internal::Article>(context, result, forceThumb); break; case Type::Game: return std::make_unique<internal::Game>(context, result); break; case Type::Contact: return std::make_unique<internal::Contact>(context, result); break; } return nullptr; } std::unique_ptr<ItemBase> ItemBase::createLayoutGif(not_null<Context*> context, DocumentData *document) { return std::make_unique<internal::Gif>(context, document, true); } DocumentData *ItemBase::getResultDocument() const { return _result ? _result->_document : nullptr; } PhotoData *ItemBase::getResultPhoto() const { return _result ? _result->_photo : nullptr; } ImagePtr ItemBase::getResultThumb() const { if (_result) { if (_result->_photo && !_result->_photo->thumb->isNull()) { return _result->_photo->thumb; } if (!_result->_thumb->isNull()) { return _result->_thumb; } return _result->_locationThumb; } return ImagePtr(); } QPixmap ItemBase::getResultContactAvatar(int width, int height) const { if (_result->_type == Result::Type::Contact) { auto result = Ui::EmptyUserpic( Data::PeerUserpicColor(qHash(_result->_id)), _result->getLayoutTitle() ).generate(width); if (result.height() != height * cIntRetinaFactor()) { result = result.scaled(QSize(width, height) * cIntRetinaFactor(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } return result; } return QPixmap(); } int ItemBase::getResultDuration() const { return 0; } QString ItemBase::getResultUrl() const { return _result->_url; } ClickHandlerPtr ItemBase::getResultUrlHandler() const { if (!_result->_url.isEmpty()) { return std::make_shared<UrlClickHandler>(_result->_url); } return ClickHandlerPtr(); } ClickHandlerPtr ItemBase::getResultContentUrlHandler() const { if (!_result->_content_url.isEmpty()) { return std::make_shared<UrlClickHandler>(_result->_content_url); } return ClickHandlerPtr(); } QString ItemBase::getResultThumbLetter() const { #ifndef OS_MAC_OLD auto parts = _result->_url.splitRef('/'); #else // OS_MAC_OLD auto parts = _result->_url.split('/'); #endif // OS_MAC_OLD if (!parts.isEmpty()) { auto domain = parts.at(0); if (parts.size() > 2 && domain.endsWith(':') && parts.at(1).isEmpty()) { // http:// and others domain = parts.at(2); } parts = domain.split('@').back().split('.'); if (parts.size() > 1) { return parts.at(parts.size() - 2).at(0).toUpper(); } } if (!_result->_title.isEmpty()) { return _result->_title.at(0).toUpper(); } return QString(); } namespace { NeverFreedPointer<DocumentItems> documentItemsMap; } // namespace const DocumentItems *documentItems() { return documentItemsMap.data(); } namespace internal { void regDocumentItem( not_null<const DocumentData*> document, not_null<ItemBase*> item) { documentItemsMap.createIfNull(); (*documentItemsMap)[document].insert(item); } void unregDocumentItem( not_null<const DocumentData*> document, not_null<ItemBase*> item) { if (documentItemsMap) { auto i = documentItemsMap->find(document); if (i != documentItemsMap->cend()) { if (i->second.remove(item) && i->second.empty()) { documentItemsMap->erase(i); } } if (documentItemsMap->empty()) { documentItemsMap.clear(); } } } } // namespace internal } // namespace Layout } // namespace InlineBots ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.h���������������������������0000664�0000000�0000000�00000006741�13262451251�0026646�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "layout.h" #include "ui/text/text.h" namespace InlineBots { class Result; namespace Layout { class PaintContext : public PaintContextBase { public: PaintContext(TimeMs ms, bool selecting, bool paused, bool lastRow) : PaintContextBase(ms, selecting) , paused(paused) , lastRow(lastRow) { } bool paused, lastRow; }; // this type used as a flag, we dynamic_cast<> to it class SendClickHandler : public ClickHandler { public: void onClick(Qt::MouseButton) const override { } }; class Context { public: virtual void inlineItemLayoutChanged(const ItemBase *layout) = 0; virtual bool inlineItemVisible(const ItemBase *item) = 0; virtual void inlineItemRepaint(const ItemBase *item) = 0; }; class ItemBase : public LayoutItemBase { public: ItemBase(not_null<Context*> context, Result *result) : _result(result), _context(context) { } ItemBase(not_null<Context*> context, DocumentData *doc) : _doc(doc), _context(context) { } // Not used anywhere currently. //ItemBase(not_null<Context*> context, PhotoData *photo) : _photo(photo), _context(context) { //} virtual void paint(Painter &p, const QRect &clip, const PaintContext *context) const = 0; virtual void setPosition(int32 position); int32 position() const; virtual bool isFullLine() const { return true; } virtual bool hasRightSkip() const { return false; } Result *getResult() const; DocumentData *getDocument() const; PhotoData *getPhoto() const; // Get document or photo (possibly from InlineBots::Result) for // showing sticker / GIF / photo preview by long mouse press. DocumentData *getPreviewDocument() const; PhotoData *getPreviewPhoto() const; virtual void preload() const; void update(); void layoutChanged(); // ClickHandlerHost interface void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override { update(); } void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override { update(); } static std::unique_ptr<ItemBase> createLayout(not_null<Context*> context, Result *result, bool forceThumb); static std::unique_ptr<ItemBase> createLayoutGif(not_null<Context*> context, DocumentData *document); protected: DocumentData *getResultDocument() const; PhotoData *getResultPhoto() const; ImagePtr getResultThumb() const; QPixmap getResultContactAvatar(int width, int height) const; int getResultDuration() const; QString getResultUrl() const; ClickHandlerPtr getResultUrlHandler() const; ClickHandlerPtr getResultContentUrlHandler() const; QString getResultThumbLetter() const; not_null<Context*> context() const { return _context; } Result *_result = nullptr; DocumentData *_doc = nullptr; PhotoData *_photo = nullptr; ClickHandlerPtr _send = ClickHandlerPtr{ new SendClickHandler() }; int _position = 0; // < 0 means removed from layout private: not_null<Context*> _context; }; using DocumentItems = std::map< not_null<const DocumentData*>, base::flat_set<not_null<ItemBase*>>>; const DocumentItems *documentItems(); namespace internal { void regDocumentItem( not_null<const DocumentData*> document, not_null<ItemBase*> item); void unregDocumentItem( not_null<const DocumentData*> document, not_null<ItemBase*> item); } // namespace internal } // namespace Layout } // namespace InlineBots �������������������������������tdesktop-1.2.17/Telegram/SourceFiles/inline_bots/inline_bot_result.cpp������������������������������0000664�0000000�0000000�00000032210�13262451251�0026152�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "inline_bots/inline_bot_result.h" #include "data/data_photo.h" #include "data/data_document.h" #include "data/data_session.h" #include "inline_bots/inline_bot_layout_item.h" #include "inline_bots/inline_bot_send_data.h" #include "storage/file_download.h" #include "core/file_utilities.h" #include "mainwidget.h" #include "auth_session.h" namespace InlineBots { namespace { QString GetContentUrl(const MTPWebDocument &document) { switch (document.type()) { case mtpc_webDocument: return qs(document.c_webDocument().vurl); case mtpc_webDocumentNoProxy: return qs(document.c_webDocumentNoProxy().vurl); } Unexpected("Type in GetContentUrl."); } } // namespace Result::Result(const Creator &creator) : _queryId(creator.queryId), _type(creator.type) { } std::unique_ptr<Result> Result::create(uint64 queryId, const MTPBotInlineResult &mtpData) { using StringToTypeMap = QMap<QString, Result::Type>; static StaticNeverFreedPointer<StringToTypeMap> stringToTypeMap{ ([]() -> StringToTypeMap* { auto result = std::make_unique<StringToTypeMap>(); result->insert(qsl("photo"), Result::Type::Photo); result->insert(qsl("video"), Result::Type::Video); result->insert(qsl("audio"), Result::Type::Audio); result->insert(qsl("voice"), Result::Type::Audio); result->insert(qsl("sticker"), Result::Type::Sticker); result->insert(qsl("file"), Result::Type::File); result->insert(qsl("gif"), Result::Type::Gif); result->insert(qsl("article"), Result::Type::Article); result->insert(qsl("contact"), Result::Type::Contact); result->insert(qsl("venue"), Result::Type::Venue); result->insert(qsl("geo"), Result::Type::Geo); result->insert(qsl("game"), Result::Type::Game); return result.release(); })() }; auto getInlineResultType = [](const MTPBotInlineResult &inlineResult) -> Type { QString type; switch (inlineResult.type()) { case mtpc_botInlineResult: type = qs(inlineResult.c_botInlineResult().vtype); break; case mtpc_botInlineMediaResult: type = qs(inlineResult.c_botInlineMediaResult().vtype); break; } return stringToTypeMap->value(type, Type::Unknown); }; Type type = getInlineResultType(mtpData); if (type == Type::Unknown) { return nullptr; } auto result = std::make_unique<Result>(Creator{ queryId, type }); const MTPBotInlineMessage *message = nullptr; switch (mtpData.type()) { case mtpc_botInlineResult: { const auto &r = mtpData.c_botInlineResult(); result->_id = qs(r.vid); if (r.has_title()) result->_title = qs(r.vtitle); if (r.has_description()) result->_description = qs(r.vdescription); if (r.has_url()) result->_url = qs(r.vurl); if (r.has_thumb()) { result->_thumb = ImagePtr(r.vthumb, result->thumbBox()); } if (r.has_content()) { result->_content_url = GetContentUrl(r.vcontent); if (result->_type == Type::Photo) { result->_photo = Auth().data().photoFromWeb( r.vcontent, result->_thumb); } else { result->_document = Auth().data().documentFromWeb( result->adjustAttributes(r.vcontent), result->_thumb); } } message = &r.vsend_message; } break; case mtpc_botInlineMediaResult: { const auto &r = mtpData.c_botInlineMediaResult(); result->_id = qs(r.vid); if (r.has_title()) result->_title = qs(r.vtitle); if (r.has_description()) result->_description = qs(r.vdescription); if (r.has_photo()) { result->_photo = Auth().data().photo(r.vphoto); } if (r.has_document()) { result->_document = Auth().data().document(r.vdocument); } message = &r.vsend_message; } break; } auto badAttachment = (result->_photo && result->_photo->full->isNull()) || (result->_document && !result->_document->isValid()); if (!message) { return nullptr; } // Ensure required media fields for layouts. if (result->_type == Type::Photo) { if (!result->_photo) { return nullptr; } } else if (result->_type == Type::Audio || result->_type == Type::File || result->_type == Type::Video || result->_type == Type::Sticker || result->_type == Type::Gif) { if (!result->_document) { return nullptr; } } switch (message->type()) { case mtpc_botInlineMessageMediaAuto: { auto &r = message->c_botInlineMessageMediaAuto(); auto entities = r.has_entities() ? TextUtilities::EntitiesFromMTP(r.ventities.v) : EntitiesInText(); if (result->_type == Type::Photo) { if (!result->_photo) { return nullptr; } result->sendData = std::make_unique<internal::SendPhoto>(result->_photo, qs(r.vmessage), entities); } else if (result->_type == Type::Game) { result->createGame(); result->sendData = std::make_unique<internal::SendGame>(result->_game); } else { if (!result->_document) { return nullptr; } result->sendData = std::make_unique<internal::SendFile>(result->_document, qs(r.vmessage), entities); } if (r.has_reply_markup()) { result->_mtpKeyboard = std::make_unique<MTPReplyMarkup>(r.vreply_markup); } } break; case mtpc_botInlineMessageText: { auto &r = message->c_botInlineMessageText(); auto entities = r.has_entities() ? TextUtilities::EntitiesFromMTP(r.ventities.v) : EntitiesInText(); result->sendData = std::make_unique<internal::SendText>(qs(r.vmessage), entities, r.is_no_webpage()); if (result->_type == Type::Photo) { if (!result->_photo) { return nullptr; } } else if (result->_type == Type::Audio || result->_type == Type::File || result->_type == Type::Video || result->_type == Type::Sticker || result->_type == Type::Gif) { if (!result->_document) { return nullptr; } } if (r.has_reply_markup()) { result->_mtpKeyboard = std::make_unique<MTPReplyMarkup>(r.vreply_markup); } } break; case mtpc_botInlineMessageMediaGeo: { // #TODO layer 72 save period and send live location?.. auto &r = message->c_botInlineMessageMediaGeo(); if (r.vgeo.type() == mtpc_geoPoint) { result->sendData = std::make_unique<internal::SendGeo>(r.vgeo.c_geoPoint()); } else { badAttachment = true; } if (r.has_reply_markup()) { result->_mtpKeyboard = std::make_unique<MTPReplyMarkup>(r.vreply_markup); } } break; case mtpc_botInlineMessageMediaVenue: { auto &r = message->c_botInlineMessageMediaVenue(); if (r.vgeo.type() == mtpc_geoPoint) { result->sendData = std::make_unique<internal::SendVenue>(r.vgeo.c_geoPoint(), qs(r.vvenue_id), qs(r.vprovider), qs(r.vtitle), qs(r.vaddress)); } else { badAttachment = true; } if (r.has_reply_markup()) { result->_mtpKeyboard = std::make_unique<MTPReplyMarkup>(r.vreply_markup); } } break; case mtpc_botInlineMessageMediaContact: { auto &r = message->c_botInlineMessageMediaContact(); result->sendData = std::make_unique<internal::SendContact>(qs(r.vfirst_name), qs(r.vlast_name), qs(r.vphone_number)); if (r.has_reply_markup()) { result->_mtpKeyboard = std::make_unique<MTPReplyMarkup>(r.vreply_markup); } } break; default: { badAttachment = true; } break; } if (badAttachment || !result->sendData || !result->sendData->isValid()) { return nullptr; } LocationCoords location; if (result->getLocationCoords(&location)) { int32 w = st::inlineThumbSize, h = st::inlineThumbSize; int32 zoom = 13, scale = 1; if (cScale() == dbisTwo || cRetina()) { scale = 2; w /= 2; h /= 2; } auto coords = location.latAsString() + ',' + location.lonAsString(); QString url = qsl("https://maps.googleapis.com/maps/api/staticmap?center=") + coords + qsl("&zoom=%1&size=%2x%3&maptype=roadmap&scale=%4&markers=color:red|size:big|").arg(zoom).arg(w).arg(h).arg(scale) + coords + qsl("&sensor=false"); result->_locationThumb = ImagePtr(url); } return result; } bool Result::onChoose(Layout::ItemBase *layout) { if (_photo && _type == Type::Photo) { if (_photo->medium->loaded() || _photo->thumb->loaded()) { return true; } else if (!_photo->medium->loading()) { _photo->thumb->loadEvenCancelled(); _photo->medium->loadEvenCancelled(); } return false; } if (_document && ( _type == Type::Video || _type == Type::Audio || _type == Type::Sticker || _type == Type::File || _type == Type::Gif)) { if (_type == Type::Gif) { if (_document->loaded()) { return true; } else if (_document->loading()) { _document->cancel(); } else { DocumentOpenClickHandler::doOpen(_document, nullptr, ActionOnLoadNone); } return false; } return true; } return true; } void Result::forget() { _thumb->forget(); if (_document) { _document->forget(); } if (_photo) { _photo->forget(); } } void Result::openFile() { if (_document) { DocumentOpenClickHandler(_document).onClick(Qt::LeftButton); } else if (_photo) { PhotoOpenClickHandler(_photo).onClick(Qt::LeftButton); } } void Result::cancelFile() { if (_document) { DocumentCancelClickHandler(_document).onClick(Qt::LeftButton); } else if (_photo) { PhotoCancelClickHandler(_photo).onClick(Qt::LeftButton); } } bool Result::hasThumbDisplay() const { if (!_thumb->isNull()) { return true; } if (_type == Type::Contact) { return true; } if (sendData->hasLocationCoords()) { return true; } return false; }; void Result::addToHistory(History *history, MTPDmessage::Flags flags, MsgId msgId, UserId fromId, MTPint mtpDate, UserId viaBotId, MsgId replyToId, const QString &postAuthor) const { flags |= MTPDmessage_ClientFlag::f_from_inline_bot; MTPReplyMarkup markup = MTPnullMarkup; if (_mtpKeyboard) { flags |= MTPDmessage::Flag::f_reply_markup; markup = *_mtpKeyboard; } sendData->addToHistory(this, history, flags, msgId, fromId, mtpDate, viaBotId, replyToId, postAuthor, markup); } QString Result::getErrorOnSend(History *history) const { return sendData->getErrorOnSend(this, history); } bool Result::getLocationCoords(LocationCoords *outLocation) const { return sendData->getLocationCoords(outLocation); } QString Result::getLayoutTitle() const { return sendData->getLayoutTitle(this); } QString Result::getLayoutDescription() const { return sendData->getLayoutDescription(this); } // just to make unique_ptr see the destructors. Result::~Result() { } void Result::createGame() { if (_game) return; const auto gameId = rand_value<GameId>(); _game = Auth().data().game( gameId, 0, QString(), _title, _description, _photo, _document); } QSize Result::thumbBox() const { return (_type == Type::Photo) ? QSize(100, 100) : QSize(90, 90); } MTPWebDocument Result::adjustAttributes(const MTPWebDocument &document) { switch (document.type()) { case mtpc_webDocument: { const auto &data = document.c_webDocument(); return MTP_webDocument( data.vurl, data.vaccess_hash, data.vsize, data.vmime_type, adjustAttributes(data.vattributes, data.vmime_type), data.vdc_id); } break; case mtpc_webDocumentNoProxy: { const auto &data = document.c_webDocumentNoProxy(); return MTP_webDocumentNoProxy( data.vurl, data.vsize, data.vmime_type, adjustAttributes(data.vattributes, data.vmime_type)); } break; } Unexpected("Type in InlineBots::Result::adjustAttributes."); } MTPVector<MTPDocumentAttribute> Result::adjustAttributes( const MTPVector<MTPDocumentAttribute> &existing, const MTPstring &mimeType) { auto result = existing.v; const auto find = [&](mtpTypeId attributeType) { return ranges::find( result, attributeType, [](const MTPDocumentAttribute &value) { return value.type(); }); }; const auto exists = [&](mtpTypeId attributeType) { return find(attributeType) != result.cend(); }; const auto mime = qs(mimeType); if (_type == Type::Gif) { if (!exists(mtpc_documentAttributeFilename)) { auto filename = (mime == qstr("video/mp4") ? "animation.gif.mp4" : "animation.gif"); result.push_back(MTP_documentAttributeFilename( MTP_string(filename))); } if (!exists(mtpc_documentAttributeAnimated)) { result.push_back(MTP_documentAttributeAnimated()); } } else if (_type == Type::Audio) { const auto audio = find(mtpc_documentAttributeAudio); if (audio != result.cend()) { using Flag = MTPDdocumentAttributeAudio::Flag; if (mime == qstr("audio/ogg")) { // We always treat audio/ogg as a voice message. // It was that way before we started to get attributes here. const auto &fields = audio->c_documentAttributeAudio(); if (!(fields.vflags.v & Flag::f_voice)) { *audio = MTP_documentAttributeAudio( MTP_flags(fields.vflags.v | Flag::f_voice), fields.vduration, fields.vtitle, fields.vperformer, fields.vwaveform); } } const auto &fields = audio->c_documentAttributeAudio(); if (!exists(mtpc_documentAttributeFilename) && !(fields.vflags.v & Flag::f_voice)) { const auto p = mimeTypeForName(mime).globPatterns(); auto pattern = p.isEmpty() ? QString() : p.front(); const auto extension = pattern.isEmpty() ? qsl(".unknown") : pattern.replace('*', QString()); const auto filename = filedialogDefaultName( qsl("inline"), extension, QString(), true); result.push_back( MTP_documentAttributeFilename(MTP_string(filename))); } } } return MTP_vector<MTPDocumentAttribute>(std::move(result)); } } // namespace InlineBots ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/inline_bots/inline_bot_result.h��������������������������������0000664�0000000�0000000�00000004761�13262451251�0025631�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "core/basic_types.h" class FileLoader; namespace InlineBots { namespace Layout { class ItemBase; } // namespace Layout namespace internal { class SendData; } // namespace internal class Result { private: // See http://stackoverflow.com/a/8147326 struct Creator; public: // Constructor is public only for std::make_unique<>() to work. // You should use create() static method instead. explicit Result(const Creator &creator); static std::unique_ptr<Result> create(uint64 queryId, const MTPBotInlineResult &mtpData); Result(const Result &other) = delete; Result &operator=(const Result &other) = delete; uint64 getQueryId() const { return _queryId; } QString getId() const { return _id; } // This is real SendClickHandler::onClick implementation for the specified // inline bot result. If it returns true you need to send this result. bool onChoose(Layout::ItemBase *layout); void forget(); void openFile(); void cancelFile(); bool hasThumbDisplay() const; void addToHistory(History *history, MTPDmessage::Flags flags, MsgId msgId, UserId fromId, MTPint mtpDate, UserId viaBotId, MsgId replyToId, const QString &postAuthor) const; QString getErrorOnSend(History *history) const; // interface for Layout:: usage bool getLocationCoords(LocationCoords *outLocation) const; QString getLayoutTitle() const; QString getLayoutDescription() const; ~Result(); private: void createGame(); QSize thumbBox() const; MTPWebDocument adjustAttributes(const MTPWebDocument &document); MTPVector<MTPDocumentAttribute> adjustAttributes( const MTPVector<MTPDocumentAttribute> &document, const MTPstring &mimeType); enum class Type { Unknown, Photo, Video, Audio, Sticker, File, Gif, Article, Contact, Geo, Venue, Game, }; friend class internal::SendData; friend class Layout::ItemBase; struct Creator { uint64 queryId; Type type; }; uint64 _queryId = 0; QString _id; Type _type = Type::Unknown; QString _title, _description, _url; QString _content_url; DocumentData *_document = nullptr; PhotoData *_photo = nullptr; GameData *_game = nullptr; std::unique_ptr<MTPReplyMarkup> _mtpKeyboard; ImagePtr _thumb, _locationThumb; std::unique_ptr<internal::SendData> sendData; }; } // namespace InlineBots ���������������tdesktop-1.2.17/Telegram/SourceFiles/inline_bots/inline_bot_send_data.cpp���������������������������0000664�0000000�0000000�00000012546�13262451251�0026570�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "inline_bots/inline_bot_send_data.h" #include "data/data_document.h" #include "inline_bots/inline_bot_result.h" #include "storage/localstorage.h" #include "lang/lang_keys.h" #include "history/history.h" namespace InlineBots { namespace internal { QString SendData::getLayoutTitle(const Result *owner) const { return owner->_title; } QString SendData::getLayoutDescription(const Result *owner) const { return owner->_description; } void SendDataCommon::addToHistory( const Result *owner, not_null<History*> history, MTPDmessage::Flags flags, MsgId msgId, UserId fromId, MTPint mtpDate, UserId viaBotId, MsgId replyToId, const QString &postAuthor, const MTPReplyMarkup &markup) const { auto fields = getSentMessageFields(); if (!fields.entities.v.isEmpty()) { flags |= MTPDmessage::Flag::f_entities; } history->addNewMessage( MTP_message( MTP_flags(flags), MTP_int(msgId), MTP_int(fromId), peerToMTP(history->peer->id), MTPnullFwdHeader, MTP_int(viaBotId), MTP_int(replyToId), mtpDate, fields.text, fields.media, markup, fields.entities, MTP_int(1), MTPint(), MTP_string(postAuthor), MTPlong()), NewMessageUnread); } QString SendDataCommon::getErrorOnSend( const Result *owner, not_null<History*> history) const { if (const auto megagroup = history->peer->asMegagroup()) { if (megagroup->restricted(ChannelRestriction::f_send_messages)) { return lang(lng_restricted_send_message); } } return QString(); } SendDataCommon::SentMTPMessageFields SendText::getSentMessageFields() const { SentMTPMessageFields result; result.text = MTP_string(_message); result.entities = TextUtilities::EntitiesToMTP(_entities); return result; } SendDataCommon::SentMTPMessageFields SendGeo::getSentMessageFields() const { SentMTPMessageFields result; result.media = MTP_messageMediaGeo(_location.toMTP()); return result; } SendDataCommon::SentMTPMessageFields SendVenue::getSentMessageFields() const { SentMTPMessageFields result; auto venueType = QString(); result.media = MTP_messageMediaVenue( _location.toMTP(), MTP_string(_title), MTP_string(_address), MTP_string(_provider), MTP_string(_venueId), MTP_string(venueType)); return result; } SendDataCommon::SentMTPMessageFields SendContact::getSentMessageFields() const { SentMTPMessageFields result; result.media = MTP_messageMediaContact( MTP_string(_phoneNumber), MTP_string(_firstName), MTP_string(_lastName), MTP_int(0)); return result; } QString SendContact::getLayoutDescription(const Result *owner) const { auto result = SendData::getLayoutDescription(owner); if (result.isEmpty()) { return App::formatPhone(_phoneNumber); } return result; } void SendPhoto::addToHistory( const Result *owner, not_null<History*> history, MTPDmessage::Flags flags, MsgId msgId, UserId fromId, MTPint mtpDate, UserId viaBotId, MsgId replyToId, const QString &postAuthor, const MTPReplyMarkup &markup) const { history->addNewPhoto( msgId, flags, viaBotId, replyToId, mtpDate.v, fromId, postAuthor, _photo, { _message, _entities }, markup); } QString SendPhoto::getErrorOnSend( const Result *owner, not_null<History*> history) const { if (const auto megagroup = history->peer->asMegagroup()) { if (megagroup->restricted(ChannelRestriction::f_send_media)) { return lang(lng_restricted_send_media); } } return QString(); } void SendFile::addToHistory( const Result *owner, not_null<History*> history, MTPDmessage::Flags flags, MsgId msgId, UserId fromId, MTPint mtpDate, UserId viaBotId, MsgId replyToId, const QString &postAuthor, const MTPReplyMarkup &markup) const { history->addNewDocument( msgId, flags, viaBotId, replyToId, mtpDate.v, fromId, postAuthor, _document, { _message, _entities }, markup); } QString SendFile::getErrorOnSend( const Result *owner, not_null<History*> history) const { if (const auto megagroup = history->peer->asMegagroup()) { if (megagroup->restricted(ChannelRestriction::f_send_media)) { return lang(lng_restricted_send_media); } else if (megagroup->restricted(ChannelRestriction::f_send_stickers) && (_document->sticker() != nullptr)) { return lang(lng_restricted_send_stickers); } else if (megagroup->restricted(ChannelRestriction::f_send_gifs) && _document->isAnimation() && !_document->isVideoMessage()) { return lang(lng_restricted_send_gifs); } } return QString(); } void SendGame::addToHistory( const Result *owner, not_null<History*> history, MTPDmessage::Flags flags, MsgId msgId, UserId fromId, MTPint mtpDate, UserId viaBotId, MsgId replyToId, const QString &postAuthor, const MTPReplyMarkup &markup) const { history->addNewGame( msgId, flags, viaBotId, replyToId, mtpDate.v, fromId, postAuthor, _game, markup); } QString SendGame::getErrorOnSend( const Result *owner, not_null<History*> history) const { if (auto megagroup = history->peer->asMegagroup()) { if (megagroup->restricted(ChannelRestriction::f_send_games)) { return lang(lng_restricted_send_inline); } } return QString(); } } // namespace internal } // namespace InlineBots ����������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/inline_bots/inline_bot_send_data.h�����������������������������0000664�0000000�0000000�00000014730�13262451251�0026232�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "core/basic_types.h" #include "history/history_location_manager.h" namespace InlineBots { class Result; namespace internal { // Abstract class describing the message that will be // sent if the user chooses this inline bot result. // For each type of message that can be sent there will be a subclass. class SendData { public: SendData() = default; SendData(const SendData &other) = delete; SendData &operator=(const SendData &other) = delete; virtual ~SendData() = default; virtual bool isValid() const = 0; virtual void addToHistory( const Result *owner, not_null<History*> history, MTPDmessage::Flags flags, MsgId msgId, UserId fromId, MTPint mtpDate, UserId viaBotId, MsgId replyToId, const QString &postAuthor, const MTPReplyMarkup &markup) const = 0; virtual QString getErrorOnSend( const Result *owner, not_null<History*> history) const = 0; virtual bool hasLocationCoords() const { return false; } virtual bool getLocationCoords(LocationCoords *outLocation) const { return false; } virtual QString getLayoutTitle(const Result *owner) const; virtual QString getLayoutDescription(const Result *owner) const; }; // This class implements addHistory() for most of the types hiding // the differences in getSentMessageFields() method. // Only SendFile and SendPhoto work by their own. class SendDataCommon : public SendData { public: struct SentMTPMessageFields { MTPString text = MTP_string(""); MTPVector<MTPMessageEntity> entities = MTPnullEntities; MTPMessageMedia media = MTP_messageMediaEmpty(); }; virtual SentMTPMessageFields getSentMessageFields() const = 0; void addToHistory( const Result *owner, not_null<History*> history, MTPDmessage::Flags flags, MsgId msgId, UserId fromId, MTPint mtpDate, UserId viaBotId, MsgId replyToId, const QString &postAuthor, const MTPReplyMarkup &markup) const override; QString getErrorOnSend( const Result *owner, not_null<History*> history) const override; }; // Plain text message. class SendText : public SendDataCommon { public: SendText( const QString &message, const EntitiesInText &entities, bool/* noWebPage*/) : _message(message) , _entities(entities) { } bool isValid() const override { return !_message.isEmpty(); } SentMTPMessageFields getSentMessageFields() const override; private: QString _message; EntitiesInText _entities; }; // Message with geo location point media. class SendGeo : public SendDataCommon { public: explicit SendGeo(const MTPDgeoPoint &point) : _location(point) { } bool isValid() const override { return true; } SentMTPMessageFields getSentMessageFields() const override; bool hasLocationCoords() const override { return true; } bool getLocationCoords(LocationCoords *outLocation) const override { Assert(outLocation != nullptr); *outLocation = _location; return true; } private: LocationCoords _location; }; // Message with venue media. class SendVenue : public SendDataCommon { public: SendVenue(const MTPDgeoPoint &point, const QString &venueId, const QString &provider, const QString &title, const QString &address) : _location(point) , _venueId(venueId) , _provider(provider) , _title(title) , _address(address) { } bool isValid() const override { return true; } SentMTPMessageFields getSentMessageFields() const override; bool hasLocationCoords() const override { return true; } bool getLocationCoords(LocationCoords *outLocation) const override { Assert(outLocation != nullptr); *outLocation = _location; return true; } private: LocationCoords _location; QString _venueId, _provider, _title, _address; }; // Message with shared contact media. class SendContact : public SendDataCommon { public: SendContact(const QString &firstName, const QString &lastName, const QString &phoneNumber) : _firstName(firstName) , _lastName(lastName) , _phoneNumber(phoneNumber) { } bool isValid() const override { return (!_firstName.isEmpty() || !_lastName.isEmpty()) && !_phoneNumber.isEmpty(); } SentMTPMessageFields getSentMessageFields() const override; QString getLayoutDescription(const Result *owner) const override; private: QString _firstName, _lastName, _phoneNumber; }; // Message with photo. class SendPhoto : public SendData { public: SendPhoto( PhotoData *photo, const QString &message, const EntitiesInText &entities) : _photo(photo) , _message(message) , _entities(entities) { } bool isValid() const override { return _photo != nullptr; } void addToHistory( const Result *owner, not_null<History*> history, MTPDmessage::Flags flags, MsgId msgId, UserId fromId, MTPint mtpDate, UserId viaBotId, MsgId replyToId, const QString &postAuthor, const MTPReplyMarkup &markup) const override; QString getErrorOnSend( const Result *owner, not_null<History*> history) const override; private: PhotoData *_photo; QString _message; EntitiesInText _entities; }; // Message with file. class SendFile : public SendData { public: SendFile( DocumentData *document, const QString &message, const EntitiesInText &entities) : _document(document) , _message(message) , _entities(entities) { } bool isValid() const override { return _document != nullptr; } void addToHistory( const Result *owner, not_null<History*> history, MTPDmessage::Flags flags, MsgId msgId, UserId fromId, MTPint mtpDate, UserId viaBotId, MsgId replyToId, const QString &postAuthor, const MTPReplyMarkup &markup) const override; QString getErrorOnSend( const Result *owner, not_null<History*> history) const override; private: DocumentData *_document; QString _message; EntitiesInText _entities; }; // Message with game. class SendGame : public SendData { public: SendGame(GameData *game) : _game(game) { } bool isValid() const override { return _game != nullptr; } void addToHistory( const Result *owner, not_null<History*> history, MTPDmessage::Flags flags, MsgId msgId, UserId fromId, MTPint mtpDate, UserId viaBotId, MsgId replyToId, const QString &postAuthor, const MTPReplyMarkup &markup) const override; QString getErrorOnSend( const Result *owner, not_null<History*> history) const override; private: GameData *_game; }; } // namespace internal } // namespace InlineBots ����������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp��������������������������0000664�0000000�0000000�00000076714�13262451251�0027055�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "inline_bots/inline_results_widget.h" #include "data/data_photo.h" #include "data/data_document.h" #include "styles/style_chat_helpers.h" #include "ui/widgets/buttons.h" #include "ui/widgets/shadow.h" #include "ui/effects/ripple_animation.h" #include "boxes/confirm_box.h" #include "inline_bots/inline_bot_result.h" #include "inline_bots/inline_bot_layout_item.h" #include "dialogs/dialogs_layout.h" #include "storage/localstorage.h" #include "lang/lang_keys.h" #include "mainwindow.h" #include "apiwrap.h" #include "mainwidget.h" #include "auth_session.h" #include "window/window_controller.h" #include "ui/widgets/scroll_area.h" #include "ui/widgets/labels.h" #include "observer_peer.h" #include "history/view/history_view_cursor_state.h" namespace InlineBots { namespace Layout { namespace internal { namespace { constexpr auto kInlineBotRequestDelay = 400; } // namespace Inner::Inner(QWidget *parent, not_null<Window::Controller*> controller) : TWidget(parent) , _controller(controller) { resize(st::emojiPanWidth - st::emojiScroll.width - st::buttonRadius, st::emojiPanMinHeight); setMouseTracking(true); setAttribute(Qt::WA_OpaquePaintEvent); _previewTimer.setSingleShot(true); connect(&_previewTimer, SIGNAL(timeout()), this, SLOT(onPreview())); _updateInlineItems.setSingleShot(true); connect(&_updateInlineItems, SIGNAL(timeout()), this, SLOT(onUpdateInlineItems())); subscribe(Auth().downloaderTaskFinished(), [this] { update(); }); subscribe(controller->gifPauseLevelChanged(), [this] { if (!_controller->isGifPausedAtLeastFor(Window::GifPauseReason::InlineResults)) { update(); } }); subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(Notify::PeerUpdate::Flag::ChannelRightsChanged, [this](const Notify::PeerUpdate &update) { if (update.peer == _inlineQueryPeer) { auto isRestricted = (_restrictedLabel != nullptr); if (isRestricted != isRestrictedView()) { auto h = countHeight(); if (h != height()) resize(width(), h); } } })); } void Inner::visibleTopBottomUpdated( int visibleTop, int visibleBottom) { _visibleBottom = visibleBottom; if (_visibleTop != visibleTop) { _visibleTop = visibleTop; _lastScrolled = getms(); } } void Inner::checkRestrictedPeer() { if (auto megagroup = _inlineQueryPeer ? _inlineQueryPeer->asMegagroup() : nullptr) { if (megagroup->restricted(ChannelRestriction::f_send_inline)) { if (!_restrictedLabel) { _restrictedLabel.create(this, lang(lng_restricted_send_inline), Ui::FlatLabel::InitType::Simple, st::stickersRestrictedLabel); _restrictedLabel->show(); _restrictedLabel->move(st::inlineResultsLeft - st::buttonRadius, st::stickerPanPadding); if (_switchPmButton) { _switchPmButton->hide(); } update(); } return; } } if (_restrictedLabel) { _restrictedLabel.destroy(); if (_switchPmButton) { _switchPmButton->show(); } update(); } } bool Inner::isRestrictedView() { checkRestrictedPeer(); return (_restrictedLabel != nullptr); } int Inner::countHeight() { if (isRestrictedView()) { return st::stickerPanPadding + _restrictedLabel->height() + st::stickerPanPadding; } else if (_rows.isEmpty() && !_switchPmButton) { return st::stickerPanPadding + st::normalFont->height + st::stickerPanPadding; } auto result = st::stickerPanPadding; if (_switchPmButton) { result += _switchPmButton->height() + st::inlineResultsSkip; } for (int i = 0, l = _rows.count(); i < l; ++i) { result += _rows[i].height; } return result + st::stickerPanPadding; } Inner::~Inner() = default; void Inner::paintEvent(QPaintEvent *e) { Painter p(this); QRect r = e ? e->rect() : rect(); if (r != rect()) { p.setClipRect(r); } p.fillRect(r, st::emojiPanBg); paintInlineItems(p, r); } void Inner::paintInlineItems(Painter &p, const QRect &r) { if (_restrictedLabel) { return; } if (_rows.isEmpty() && !_switchPmButton) { p.setFont(st::normalFont); p.setPen(st::noContactsColor); p.drawText(QRect(0, 0, width(), (height() / 3) * 2 + st::normalFont->height), lang(lng_inline_bot_no_results), style::al_center); return; } auto gifPaused = _controller->isGifPausedAtLeastFor(Window::GifPauseReason::InlineResults); InlineBots::Layout::PaintContext context(getms(), false, gifPaused, false); auto top = st::stickerPanPadding; if (_switchPmButton) { top += _switchPmButton->height() + st::inlineResultsSkip; } auto fromx = rtl() ? (width() - r.x() - r.width()) : r.x(); auto tox = rtl() ? (width() - r.x()) : (r.x() + r.width()); for (auto row = 0, rows = _rows.size(); row != rows; ++row) { auto &inlineRow = _rows[row]; if (top >= r.top() + r.height()) break; if (top + inlineRow.height > r.top()) { auto left = st::inlineResultsLeft - st::buttonRadius; if (row == rows - 1) context.lastRow = true; for (int col = 0, cols = inlineRow.items.size(); col < cols; ++col) { if (left >= tox) break; auto item = inlineRow.items.at(col); auto w = item->width(); if (left + w > fromx) { p.translate(left, top); item->paint(p, r.translated(-left, -top), &context); p.translate(-left, -top); } left += w; if (item->hasRightSkip()) { left += st::inlineResultsSkip; } } } top += inlineRow.height; } } void Inner::mousePressEvent(QMouseEvent *e) { if (e->button() != Qt::LeftButton) { return; } _lastMousePos = e->globalPos(); updateSelected(); _pressed = _selected; ClickHandler::pressed(); _previewTimer.start(QApplication::startDragTime()); } void Inner::mouseReleaseEvent(QMouseEvent *e) { _previewTimer.stop(); auto pressed = std::exchange(_pressed, -1); auto activated = ClickHandler::unpressed(); if (_previewShown) { _previewShown = false; return; } _lastMousePos = e->globalPos(); updateSelected(); if (_selected < 0 || _selected != pressed || !activated) { return; } if (dynamic_cast<InlineBots::Layout::SendClickHandler*>(activated.get())) { int row = _selected / MatrixRowShift, column = _selected % MatrixRowShift; selectInlineResult(row, column); } else { App::activateClickHandler(activated, e->button()); } } void Inner::selectInlineResult(int row, int column) { if (row >= _rows.size() || column >= _rows.at(row).items.size()) { return; } auto item = _rows[row].items[column]; if (auto inlineResult = item->getResult()) { if (inlineResult->onChoose(item)) { _resultSelectedCallback(inlineResult, _inlineBot); } } } void Inner::mouseMoveEvent(QMouseEvent *e) { _lastMousePos = e->globalPos(); updateSelected(); } void Inner::leaveEventHook(QEvent *e) { clearSelection(); } void Inner::leaveToChildEvent(QEvent *e, QWidget *child) { clearSelection(); } void Inner::enterFromChildEvent(QEvent *e, QWidget *child) { _lastMousePos = QCursor::pos(); updateSelected(); } void Inner::clearSelection() { if (_selected >= 0) { int srow = _selected / MatrixRowShift, scol = _selected % MatrixRowShift; Assert(srow >= 0 && srow < _rows.size() && scol >= 0 && scol < _rows.at(srow).items.size()); ClickHandler::clearActive(_rows.at(srow).items.at(scol)); setCursor(style::cur_default); } _selected = _pressed = -1; update(); } void Inner::hideFinish(bool completely) { if (completely) { auto itemForget = [](auto &item) { if (auto document = item->getDocument()) { document->forget(); } if (auto photo = item->getPhoto()) { photo->forget(); } if (auto result = item->getResult()) { result->forget(); } }; clearInlineRows(false); for_const (auto &item, _inlineLayouts) { itemForget(item.second); } } } bool Inner::inlineRowsAddItem(Result *result, Row &row, int32 &sumWidth) { auto layout = layoutPrepareInlineResult(result, (_rows.size() * MatrixRowShift) + row.items.size()); if (!layout) return false; layout->preload(); if (inlineRowFinalize(row, sumWidth, layout->isFullLine())) { layout->setPosition(_rows.size() * MatrixRowShift); } sumWidth += layout->maxWidth(); if (!row.items.isEmpty() && row.items.back()->hasRightSkip()) { sumWidth += st::inlineResultsSkip; } row.items.push_back(layout); return true; } bool Inner::inlineRowFinalize(Row &row, int32 &sumWidth, bool force) { if (row.items.isEmpty()) return false; auto full = (row.items.size() >= kInlineItemsMaxPerRow); auto big = (sumWidth >= st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft); if (full || big || force) { _rows.push_back(layoutInlineRow(row, (full || big) ? sumWidth : 0)); row = Row(); row.items.reserve(kInlineItemsMaxPerRow); sumWidth = 0; return true; } return false; } void Inner::inlineBotChanged() { refreshInlineRows(nullptr, nullptr, nullptr, true); } void Inner::clearInlineRows(bool resultsDeleted) { if (resultsDeleted) { _selected = _pressed = -1; } else { clearSelection(); for_const (auto &row, _rows) { for_const (auto &item, row.items) { item->setPosition(-1); } } } _rows.clear(); } ItemBase *Inner::layoutPrepareInlineResult(Result *result, int32 position) { auto it = _inlineLayouts.find(result); if (it == _inlineLayouts.cend()) { if (auto layout = ItemBase::createLayout(this, result, _inlineWithThumb)) { it = _inlineLayouts.emplace(result, std::move(layout)).first; it->second->initDimensions(); } else { return nullptr; } } if (!it->second->maxWidth()) { return nullptr; } it->second->setPosition(position); return it->second.get(); } void Inner::deleteUnusedInlineLayouts() { if (_rows.isEmpty()) { // delete all _inlineLayouts.clear(); } else { for (auto i = _inlineLayouts.begin(); i != _inlineLayouts.cend();) { if (i->second->position() < 0) { i = _inlineLayouts.erase(i); } else { ++i; } } } } Inner::Row &Inner::layoutInlineRow(Row &row, int32 sumWidth) { auto count = int(row.items.size()); Assert(count <= kInlineItemsMaxPerRow); // enumerate items in the order of growing maxWidth() // for that sort item indices by maxWidth() int indices[kInlineItemsMaxPerRow]; for (auto i = 0; i != count; ++i) { indices[i] = i; } std::sort(indices, indices + count, [&row](int a, int b) -> bool { return row.items.at(a)->maxWidth() < row.items.at(b)->maxWidth(); }); row.height = 0; int availw = width() - (st::inlineResultsLeft - st::buttonRadius); for (int i = 0; i < count; ++i) { int index = indices[i]; int w = sumWidth ? (row.items.at(index)->maxWidth() * availw / sumWidth) : row.items.at(index)->maxWidth(); int actualw = qMax(w, int(st::inlineResultsMinWidth)); row.height = qMax(row.height, row.items.at(index)->resizeGetHeight(actualw)); if (sumWidth) { availw -= actualw; sumWidth -= row.items.at(index)->maxWidth(); if (index > 0 && row.items.at(index - 1)->hasRightSkip()) { availw -= st::inlineResultsSkip; sumWidth -= st::inlineResultsSkip; } } } return row; } void Inner::preloadImages() { for (auto row = 0, rows = _rows.size(); row != rows; ++row) { for (auto col = 0, cols = _rows[row].items.size(); col != cols; ++col) { _rows[row].items[col]->preload(); } } } void Inner::hideInlineRowsPanel() { clearInlineRows(false); } void Inner::clearInlineRowsPanel() { clearInlineRows(false); } void Inner::refreshSwitchPmButton(const CacheEntry *entry) { if (!entry || entry->switchPmText.isEmpty()) { _switchPmButton.destroy(); _switchPmStartToken.clear(); } else { if (!_switchPmButton) { _switchPmButton.create(this, base::lambda<QString()>(), st::switchPmButton); _switchPmButton->show(); _switchPmButton->setTextTransform(Ui::RoundButton::TextTransform::NoTransform); connect(_switchPmButton, SIGNAL(clicked()), this, SLOT(onSwitchPm())); } auto text = entry->switchPmText; _switchPmButton->setText([text] { return text; }); // doesn't perform text.toUpper() _switchPmStartToken = entry->switchPmStartToken; auto buttonTop = st::stickerPanPadding; _switchPmButton->move(st::inlineResultsLeft - st::buttonRadius, buttonTop); if (isRestrictedView()) { _switchPmButton->hide(); } } update(); } int Inner::refreshInlineRows(PeerData *queryPeer, UserData *bot, const CacheEntry *entry, bool resultsDeleted) { _inlineBot = bot; _inlineQueryPeer = queryPeer; refreshSwitchPmButton(entry); auto clearResults = [&] { if (!entry) { return true; } if (entry->results.empty() && entry->switchPmText.isEmpty()) { return true; } return false; }; auto clearResultsResult = clearResults(); // Clang workaround. if (clearResultsResult) { if (resultsDeleted) { clearInlineRows(true); deleteUnusedInlineLayouts(); } emit emptyInlineRows(); return 0; } clearSelection(); Assert(_inlineBot != 0); auto count = int(entry->results.size()); auto from = validateExistingInlineRows(entry->results); auto added = 0; if (count) { _rows.reserve(count); auto row = Row(); row.items.reserve(kInlineItemsMaxPerRow); auto sumWidth = 0; for (auto i = from; i != count; ++i) { if (inlineRowsAddItem(entry->results[i].get(), row, sumWidth)) { ++added; } } inlineRowFinalize(row, sumWidth, true); } auto h = countHeight(); if (h != height()) resize(width(), h); update(); _lastMousePos = QCursor::pos(); updateSelected(); return added; } int Inner::validateExistingInlineRows(const Results &results) { int count = results.size(), until = 0, untilrow = 0, untilcol = 0; for (; until < count;) { if (untilrow >= _rows.size() || _rows[untilrow].items[untilcol]->getResult() != results[until].get()) { break; } ++until; if (++untilcol == _rows[untilrow].items.size()) { ++untilrow; untilcol = 0; } } if (until == count) { // all items are layed out if (untilrow == _rows.size()) { // nothing changed return until; } for (int i = untilrow, l = _rows.size(), skip = untilcol; i < l; ++i) { for (int j = 0, s = _rows[i].items.size(); j < s; ++j) { if (skip) { --skip; } else { _rows[i].items[j]->setPosition(-1); } } } if (!untilcol) { // all good rows are filled _rows.resize(untilrow); return until; } _rows.resize(untilrow + 1); _rows[untilrow].items.resize(untilcol); _rows[untilrow] = layoutInlineRow(_rows[untilrow]); return until; } if (untilrow && !untilcol) { // remove last row, maybe it is not full --untilrow; untilcol = _rows[untilrow].items.size(); } until -= untilcol; for (int i = untilrow, l = _rows.size(); i < l; ++i) { for (int j = 0, s = _rows[i].items.size(); j < s; ++j) { _rows[i].items[j]->setPosition(-1); } } _rows.resize(untilrow); if (_rows.isEmpty()) { _inlineWithThumb = false; for (int i = until; i < count; ++i) { if (results.at(i)->hasThumbDisplay()) { _inlineWithThumb = true; break; } } } return until; } void Inner::inlineItemLayoutChanged(const ItemBase *layout) { if (_selected < 0 || !isVisible()) { return; } int row = _selected / MatrixRowShift, col = _selected % MatrixRowShift; if (row < _rows.size() && col < _rows.at(row).items.size()) { if (layout == _rows.at(row).items.at(col)) { updateSelected(); } } } void Inner::inlineItemRepaint(const ItemBase *layout) { auto ms = getms(); if (_lastScrolled + 100 <= ms) { update(); } else { _updateInlineItems.start(_lastScrolled + 100 - ms); } } bool Inner::inlineItemVisible(const ItemBase *layout) { int32 position = layout->position(); if (position < 0 || !isVisible()) { return false; } int row = position / MatrixRowShift, col = position % MatrixRowShift; Assert((row < _rows.size()) && (col < _rows[row].items.size())); auto &inlineItems = _rows[row].items; int top = st::stickerPanPadding; for (int32 i = 0; i < row; ++i) { top += _rows.at(i).height; } return (top < _visibleBottom) && (top + _rows[row].items[col]->height() > _visibleTop); } void Inner::updateSelected() { if (_pressed >= 0 && !_previewShown) { return; } auto newSelected = -1; auto p = mapFromGlobal(_lastMousePos); int sx = (rtl() ? width() - p.x() : p.x()) - (st::inlineResultsLeft - st::buttonRadius); int sy = p.y() - st::stickerPanPadding; if (_switchPmButton) { sy -= _switchPmButton->height() + st::inlineResultsSkip; } int row = -1, col = -1, sel = -1; ClickHandlerPtr lnk; ClickHandlerHost *lnkhost = nullptr; HistoryView::CursorState cursor = HistoryView::CursorState::None; if (sy >= 0) { row = 0; for (int rows = _rows.size(); row < rows; ++row) { if (sy < _rows[row].height) { break; } sy -= _rows[row].height; } } if (sx >= 0 && row >= 0 && row < _rows.size()) { auto &inlineItems = _rows[row].items; col = 0; for (int cols = inlineItems.size(); col < cols; ++col) { int width = inlineItems.at(col)->width(); if (sx < width) { break; } sx -= width; if (inlineItems.at(col)->hasRightSkip()) { sx -= st::inlineResultsSkip; } } if (col < inlineItems.size()) { sel = row * MatrixRowShift + col; auto result = inlineItems[col]->getState( QPoint(sx, sy), HistoryView::StateRequest()); lnk = result.link; cursor = result.cursor; lnkhost = inlineItems[col]; } else { row = col = -1; } } else { row = col = -1; } int srow = (_selected >= 0) ? (_selected / MatrixRowShift) : -1; int scol = (_selected >= 0) ? (_selected % MatrixRowShift) : -1; if (_selected != sel) { if (srow >= 0 && scol >= 0) { Assert(srow >= 0 && srow < _rows.size() && scol >= 0 && scol < _rows.at(srow).items.size()); _rows[srow].items[scol]->update(); } _selected = sel; if (row >= 0 && col >= 0) { Assert(row >= 0 && row < _rows.size() && col >= 0 && col < _rows.at(row).items.size()); _rows[row].items[col]->update(); } if (_previewShown && _selected >= 0 && _pressed != _selected) { _pressed = _selected; if (row >= 0 && col >= 0) { auto layout = _rows.at(row).items.at(col); if (auto previewDocument = layout->getPreviewDocument()) { Ui::showMediaPreview(previewDocument); } else if (auto previewPhoto = layout->getPreviewPhoto()) { Ui::showMediaPreview(previewPhoto); } } } } if (ClickHandler::setActive(lnk, lnkhost)) { setCursor(lnk ? style::cur_pointer : style::cur_default); } } void Inner::onPreview() { if (_pressed < 0) return; int row = _pressed / MatrixRowShift, col = _pressed % MatrixRowShift; if (row < _rows.size() && col < _rows.at(row).items.size()) { auto layout = _rows.at(row).items.at(col); if (auto previewDocument = layout->getPreviewDocument()) { Ui::showMediaPreview(previewDocument); _previewShown = true; } else if (auto previewPhoto = layout->getPreviewPhoto()) { Ui::showMediaPreview(previewPhoto); _previewShown = true; } } } void Inner::onUpdateInlineItems() { auto ms = getms(); if (_lastScrolled + 100 <= ms) { update(); } else { _updateInlineItems.start(_lastScrolled + 100 - ms); } } void Inner::onSwitchPm() { if (_inlineBot && _inlineBot->botInfo) { _inlineBot->botInfo->startToken = _switchPmStartToken; Ui::showPeerHistory(_inlineBot, ShowAndStartBotMsgId); } } } // namespace internal Widget::Widget(QWidget *parent, not_null<Window::Controller*> controller) : TWidget(parent) , _controller(controller) , _contentMaxHeight(st::emojiPanMaxHeight) , _contentHeight(_contentMaxHeight) , _scroll(this, st::inlineBotsScroll) { resize(QRect(0, 0, st::emojiPanWidth, _contentHeight).marginsAdded(innerPadding()).size()); _width = width(); _height = height(); _scroll->resize(st::emojiPanWidth - st::buttonRadius, _contentHeight); _scroll->move(verticalRect().topLeft()); _inner = _scroll->setOwnedWidget(object_ptr<internal::Inner>(this, controller)); _inner->moveToLeft(0, 0, _scroll->width()); connect(_scroll, SIGNAL(scrolled()), this, SLOT(onScroll())); connect(_inner, SIGNAL(emptyInlineRows()), this, SLOT(onEmptyInlineRows())); // inline bots _inlineRequestTimer.setSingleShot(true); connect(&_inlineRequestTimer, SIGNAL(timeout()), this, SLOT(onInlineRequest())); if (cPlatform() == dbipMac || cPlatform() == dbipMacOld) { connect(App::wnd()->windowHandle(), SIGNAL(activeChanged()), this, SLOT(onWndActiveChanged())); } // Inner widget has OpaquePaintEvent attribute so it doesn't repaint on scroll. // But we should force it to repaint so that GIFs will continue to animate without update() calls. // We do that by creating a transparent widget above our _inner. auto forceRepaintOnScroll = object_ptr<TWidget>(this); forceRepaintOnScroll->setGeometry(innerRect().x() + st::buttonRadius, innerRect().y() + st::buttonRadius, st::buttonRadius, st::buttonRadius); forceRepaintOnScroll->setAttribute(Qt::WA_TransparentForMouseEvents); forceRepaintOnScroll->show(); setMouseTracking(true); setAttribute(Qt::WA_OpaquePaintEvent, false); } void Widget::moveBottom(int bottom) { _bottom = bottom; updateContentHeight(); } void Widget::updateContentHeight() { auto addedHeight = innerPadding().top() + innerPadding().bottom(); auto wantedContentHeight = qRound(st::emojiPanHeightRatio * _bottom) - addedHeight; auto contentHeight = snap(wantedContentHeight, st::emojiPanMinHeight, st::emojiPanMaxHeight); accumulate_min(contentHeight, _bottom - addedHeight); accumulate_min(contentHeight, _contentMaxHeight); auto resultTop = _bottom - addedHeight - contentHeight; if (contentHeight == _contentHeight) { move(x(), resultTop); return; } auto was = _contentHeight; _contentHeight = contentHeight; resize(QRect(0, 0, innerRect().width(), _contentHeight).marginsAdded(innerPadding()).size()); _height = height(); moveToLeft(0, resultTop); if (was > _contentHeight) { _scroll->resize(_scroll->width(), _contentHeight); auto scrollTop = _scroll->scrollTop(); _inner->setVisibleTopBottom(scrollTop, scrollTop + _contentHeight); } else { auto scrollTop = _scroll->scrollTop(); _inner->setVisibleTopBottom(scrollTop, scrollTop + _contentHeight); _scroll->resize(_scroll->width(), _contentHeight); } update(); } void Widget::onWndActiveChanged() { if (!App::wnd()->windowHandle()->isActive() && !isHidden()) { leaveEvent(0); } } void Widget::paintEvent(QPaintEvent *e) { Painter p(this); auto ms = getms(); // This call can finish _a_show animation and destroy _showAnimation. auto opacityAnimating = _a_opacity.animating(ms); auto showAnimating = _a_show.animating(ms); if (_showAnimation && !showAnimating) { _showAnimation.reset(); if (!opacityAnimating) { showChildren(); } } if (showAnimating) { Assert(_showAnimation != nullptr); if (auto opacity = _a_opacity.current(_hiding ? 0. : 1.)) { _showAnimation->paintFrame(p, 0, 0, width(), _a_show.current(1.), opacity); } } else if (opacityAnimating) { p.setOpacity(_a_opacity.current(_hiding ? 0. : 1.)); p.drawPixmap(0, 0, _cache); } else if (_hiding || isHidden()) { hideFinished(); } else { if (!_cache.isNull()) _cache = QPixmap(); if (!_inPanelGrab) Ui::Shadow::paint(p, innerRect(), width(), st::emojiPanAnimation.shadow); paintContent(p); } } void Widget::paintContent(Painter &p) { auto inner = innerRect(); App::roundRect(p, inner, st::emojiPanBg, ImageRoundRadius::Small, RectPart::FullTop | RectPart::FullBottom); auto horizontal = horizontalRect(); auto sidesTop = horizontal.y(); auto sidesHeight = horizontal.height(); p.fillRect(myrtlrect(inner.x() + inner.width() - st::emojiScroll.width, sidesTop, st::emojiScroll.width, sidesHeight), st::emojiPanBg); p.fillRect(myrtlrect(inner.x(), sidesTop, st::buttonRadius, sidesHeight), st::emojiPanBg); } void Widget::moveByBottom() { updateContentHeight(); } void Widget::hideFast() { if (isHidden()) return; _hiding = false; _a_opacity.finish(); hideFinished(); } void Widget::opacityAnimationCallback() { update(); if (!_a_opacity.animating()) { if (_hiding) { _hiding = false; hideFinished(); } else if (!_a_show.animating()) { showChildren(); } } } void Widget::prepareCache() { if (_a_opacity.animating()) return; auto showAnimation = base::take(_a_show); auto showAnimationData = base::take(_showAnimation); showChildren(); _cache = Ui::GrabWidget(this); _showAnimation = base::take(showAnimationData); _a_show = base::take(showAnimation); if (_a_show.animating()) { hideChildren(); } } void Widget::startOpacityAnimation(bool hiding) { _hiding = false; prepareCache(); _hiding = hiding; hideChildren(); _a_opacity.start([this] { opacityAnimationCallback(); }, _hiding ? 1. : 0., _hiding ? 0. : 1., st::emojiPanDuration); } void Widget::startShowAnimation() { if (!_a_show.animating()) { auto cache = base::take(_cache); auto opacityAnimation = base::take(_a_opacity); showChildren(); auto image = grabForPanelAnimation(); _a_opacity = base::take(opacityAnimation); _cache = base::take(_cache); _showAnimation = std::make_unique<Ui::PanelAnimation>(st::emojiPanAnimation, Ui::PanelAnimation::Origin::BottomLeft); auto inner = rect().marginsRemoved(st::emojiPanMargins); _showAnimation->setFinalImage(std::move(image), QRect(inner.topLeft() * cIntRetinaFactor(), inner.size() * cIntRetinaFactor())); auto corners = App::cornersMask(ImageRoundRadius::Small); _showAnimation->setCornerMasks(corners[0], corners[1], corners[2], corners[3]); _showAnimation->start(); } hideChildren(); _a_show.start([this] { update(); }, 0., 1., st::emojiPanShowDuration); } QImage Widget::grabForPanelAnimation() { Ui::SendPendingMoveResizeEvents(this); auto result = QImage(size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); result.setDevicePixelRatio(cRetinaFactor()); result.fill(Qt::transparent); _inPanelGrab = true; render(&result); _inPanelGrab = false; return result; } void Widget::hideAnimated() { if (isHidden()) return; if (_hiding) return; startOpacityAnimation(true); } Widget::~Widget() = default; void Widget::hideFinished() { hide(); _controller->disableGifPauseReason(Window::GifPauseReason::InlineResults); _inner->hideFinish(true); _a_show.finish(); _showAnimation.reset(); _cache = QPixmap(); _horizontal = false; _hiding = false; _scroll->scrollToY(0); } void Widget::showAnimated() { showStarted(); } void Widget::showStarted() { if (isHidden()) { recountContentMaxHeight(); _inner->preloadImages(); show(); _controller->enableGifPauseReason(Window::GifPauseReason::InlineResults); startShowAnimation(); } else if (_hiding) { startOpacityAnimation(false); } } void Widget::onScroll() { auto st = _scroll->scrollTop(); if (st + _scroll->height() > _scroll->scrollTopMax()) { onInlineRequest(); } _inner->setVisibleTopBottom(st, st + _scroll->height()); } style::margins Widget::innerPadding() const { return st::emojiPanMargins; } QRect Widget::innerRect() const { return rect().marginsRemoved(innerPadding()); } QRect Widget::horizontalRect() const { return innerRect().marginsRemoved(style::margins(0, st::buttonRadius, 0, st::buttonRadius)); } QRect Widget::verticalRect() const { return innerRect().marginsRemoved(style::margins(st::buttonRadius, 0, st::buttonRadius, 0)); } void Widget::clearInlineBot() { inlineBotChanged(); } bool Widget::overlaps(const QRect &globalRect) const { if (isHidden() || !_cache.isNull()) return false; auto testRect = QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size()); auto inner = rect().marginsRemoved(st::emojiPanMargins); return inner.marginsRemoved(QMargins(st::buttonRadius, 0, st::buttonRadius, 0)).contains(testRect) || inner.marginsRemoved(QMargins(0, st::buttonRadius, 0, st::buttonRadius)).contains(testRect); } void Widget::inlineBotChanged() { if (!_inlineBot) return; if (!isHidden() && !_hiding) { hideAnimated(); } if (_inlineRequestId) MTP::cancel(_inlineRequestId); _inlineRequestId = 0; _inlineQuery = _inlineNextQuery = _inlineNextOffset = QString(); _inlineBot = nullptr; _inlineCache.clear(); _inner->inlineBotChanged(); _inner->hideInlineRowsPanel(); Notify::inlineBotRequesting(false); } void Widget::inlineResultsDone(const MTPmessages_BotResults &result) { _inlineRequestId = 0; Notify::inlineBotRequesting(false); auto it = _inlineCache.find(_inlineQuery); auto adding = (it != _inlineCache.cend()); if (result.type() == mtpc_messages_botResults) { auto &d = result.c_messages_botResults(); App::feedUsers(d.vusers); auto &v = d.vresults.v; auto queryId = d.vquery_id.v; if (it == _inlineCache.cend()) { it = _inlineCache.emplace(_inlineQuery, std::make_unique<internal::CacheEntry>()).first; } auto entry = it->second.get(); entry->nextOffset = qs(d.vnext_offset); if (d.has_switch_pm() && d.vswitch_pm.type() == mtpc_inlineBotSwitchPM) { auto &switchPm = d.vswitch_pm.c_inlineBotSwitchPM(); entry->switchPmText = qs(switchPm.vtext); entry->switchPmStartToken = qs(switchPm.vstart_param); } if (auto count = v.size()) { entry->results.reserve(entry->results.size() + count); } auto added = 0; for_const (const auto &res, v) { if (auto result = InlineBots::Result::create(queryId, res)) { ++added; entry->results.push_back(std::move(result)); } } if (!added) { entry->nextOffset = QString(); } } else if (adding) { it->second->nextOffset = QString(); } if (!showInlineRows(!adding)) { it->second->nextOffset = QString(); } onScroll(); } void Widget::queryInlineBot(UserData *bot, PeerData *peer, QString query) { bool force = false; _inlineQueryPeer = peer; if (bot != _inlineBot) { inlineBotChanged(); _inlineBot = bot; force = true; } if (_inlineQuery != query || force) { if (_inlineRequestId) { MTP::cancel(_inlineRequestId); _inlineRequestId = 0; Notify::inlineBotRequesting(false); } if (_inlineCache.find(query) != _inlineCache.cend()) { _inlineRequestTimer.stop(); _inlineQuery = _inlineNextQuery = query; showInlineRows(true); } else { _inlineNextQuery = query; _inlineRequestTimer.start(internal::kInlineBotRequestDelay); } } } void Widget::onInlineRequest() { if (_inlineRequestId || !_inlineBot || !_inlineQueryPeer) return; _inlineQuery = _inlineNextQuery; QString nextOffset; auto it = _inlineCache.find(_inlineQuery); if (it != _inlineCache.cend()) { nextOffset = it->second->nextOffset; if (nextOffset.isEmpty()) { return; } } Notify::inlineBotRequesting(true); _inlineRequestId = request(MTPmessages_GetInlineBotResults(MTP_flags(0), _inlineBot->inputUser, _inlineQueryPeer->input, MTPInputGeoPoint(), MTP_string(_inlineQuery), MTP_string(nextOffset))).done([this](const MTPmessages_BotResults &result, mtpRequestId requestId) { inlineResultsDone(result); }).fail([this](const RPCError &error) { // show error? Notify::inlineBotRequesting(false); _inlineRequestId = 0; }).handleAllErrors().send(); } void Widget::onEmptyInlineRows() { hideAnimated(); _inner->clearInlineRowsPanel(); } bool Widget::refreshInlineRows(int *added) { auto it = _inlineCache.find(_inlineQuery); const internal::CacheEntry *entry = nullptr; if (it != _inlineCache.cend()) { if (!it->second->results.empty() || !it->second->switchPmText.isEmpty()) { entry = it->second.get(); } _inlineNextOffset = it->second->nextOffset; } if (!entry) prepareCache(); auto result = _inner->refreshInlineRows(_inlineQueryPeer, _inlineBot, entry, false); if (added) *added = result; return (entry != nullptr); } int Widget::showInlineRows(bool newResults) { auto added = 0; auto clear = !refreshInlineRows(&added); if (newResults) { _scroll->scrollToY(0); } auto hidden = isHidden(); if (!hidden && !clear) { recountContentMaxHeight(); } if (clear) { if (!hidden) { hideAnimated(); } else if (!_hiding) { _cache = QPixmap(); // clear after refreshInlineRows() } } else { if (hidden || _hiding) { showAnimated(); } } return added; } void Widget::recountContentMaxHeight() { _contentMaxHeight = _inner->countHeight(); updateContentHeight(); } } // namespace Layout } // namespace InlineBots ����������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/inline_bots/inline_results_widget.h����������������������������0000664�0000000�0000000�00000014165�13262451251�0026512�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/twidget.h" #include "ui/abstract_button.h" #include "ui/effects/panel_animation.h" #include "mtproto/sender.h" #include "inline_bots/inline_bot_layout_item.h" namespace Ui { class ScrollArea; class IconButton; class LinkButton; class RoundButton; class FlatLabel; class RippleAnimation; } // namesapce Ui namespace Window { class Controller; } // namespace Window namespace InlineBots { class Result; namespace Layout { class ItemBase; namespace internal { constexpr int kInlineItemsMaxPerRow = 5; using Results = std::vector<std::unique_ptr<Result>>; struct CacheEntry { QString nextOffset; QString switchPmText, switchPmStartToken; Results results; }; class Inner : public TWidget, public Context, private base::Subscriber { Q_OBJECT public: Inner(QWidget *parent, not_null<Window::Controller*> controller); void hideFinish(bool completely); void clearSelection(); int refreshInlineRows(PeerData *queryPeer, UserData *bot, const CacheEntry *results, bool resultsDeleted); void inlineBotChanged(); void hideInlineRowsPanel(); void clearInlineRowsPanel(); void preloadImages(); void inlineItemLayoutChanged(const ItemBase *layout) override; void inlineItemRepaint(const ItemBase *layout) override; bool inlineItemVisible(const ItemBase *layout) override; int countHeight(); void setResultSelectedCallback(base::lambda<void(Result *result, UserData *bot)> callback) { _resultSelectedCallback = std::move(callback); } ~Inner(); protected: void visibleTopBottomUpdated( int visibleTop, int visibleBottom) override; void mousePressEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void paintEvent(QPaintEvent *e) override; void leaveEventHook(QEvent *e) override; void leaveToChildEvent(QEvent *e, QWidget *child) override; void enterFromChildEvent(QEvent *e, QWidget *child) override; private slots: void onPreview(); void onUpdateInlineItems(); void onSwitchPm(); signals: void emptyInlineRows(); private: static constexpr bool kRefreshIconsScrollAnimation = true; static constexpr bool kRefreshIconsNoAnimation = false; void updateSelected(); void checkRestrictedPeer(); bool isRestrictedView(); void paintInlineItems(Painter &p, const QRect &r); void refreshSwitchPmButton(const CacheEntry *entry); not_null<Window::Controller*> _controller; int _visibleTop = 0; int _visibleBottom = 0; UserData *_inlineBot = nullptr; PeerData *_inlineQueryPeer = nullptr; TimeMs _lastScrolled = 0; QTimer _updateInlineItems; bool _inlineWithThumb = false; object_ptr<Ui::RoundButton> _switchPmButton = { nullptr }; QString _switchPmStartToken; object_ptr<Ui::FlatLabel> _restrictedLabel = { nullptr }; struct Row { int height = 0; QVector<ItemBase*> items; }; QVector<Row> _rows; void clearInlineRows(bool resultsDeleted); std::map<Result*, std::unique_ptr<ItemBase>> _inlineLayouts; ItemBase *layoutPrepareInlineResult(Result *result, int32 position); bool inlineRowsAddItem(Result *result, Row &row, int32 &sumWidth); bool inlineRowFinalize(Row &row, int32 &sumWidth, bool force = false); Row &layoutInlineRow(Row &row, int32 sumWidth = 0); void deleteUnusedInlineLayouts(); int validateExistingInlineRows(const Results &results); void selectInlineResult(int row, int column); int _selected = -1; int _pressed = -1; QPoint _lastMousePos; QTimer _previewTimer; bool _previewShown = false; base::lambda<void(Result *result, UserData *bot)> _resultSelectedCallback; }; } // namespace internal class Widget : public TWidget, private MTP::Sender { Q_OBJECT public: Widget(QWidget *parent, not_null<Window::Controller*> controller); void moveBottom(int bottom); void hideFast(); bool hiding() const { return _hiding; } void queryInlineBot(UserData *bot, PeerData *peer, QString query); void clearInlineBot(); bool overlaps(const QRect &globalRect) const; void showAnimated(); void hideAnimated(); void setResultSelectedCallback(base::lambda<void(Result *result, UserData *bot)> callback) { _inner->setResultSelectedCallback(std::move(callback)); } ~Widget(); protected: void paintEvent(QPaintEvent *e) override; private slots: void onWndActiveChanged(); void onScroll(); void onInlineRequest(); void onEmptyInlineRows(); private: void moveByBottom(); void paintContent(Painter &p); style::margins innerPadding() const; // Rounded rect which has shadow around it. QRect innerRect() const; // Inner rect with removed st::buttonRadius from top and bottom. // This one is allowed to be not rounded. QRect horizontalRect() const; // Inner rect with removed st::buttonRadius from left and right. // This one is allowed to be not rounded. QRect verticalRect() const; QImage grabForPanelAnimation(); void startShowAnimation(); void startOpacityAnimation(bool hiding); void prepareCache(); class Container; void opacityAnimationCallback(); void hideFinished(); void showStarted(); void updateContentHeight(); void inlineBotChanged(); int showInlineRows(bool newResults); void recountContentMaxHeight(); bool refreshInlineRows(int *added = nullptr); void inlineResultsDone(const MTPmessages_BotResults &result); not_null<Window::Controller*> _controller; int _contentMaxHeight = 0; int _contentHeight = 0; bool _horizontal = false; int _width = 0; int _height = 0; int _bottom = 0; std::unique_ptr<Ui::PanelAnimation> _showAnimation; Animation _a_show; bool _hiding = false; QPixmap _cache; Animation _a_opacity; bool _inPanelGrab = false; object_ptr<Ui::ScrollArea> _scroll; QPointer<internal::Inner> _inner; std::map<QString, std::unique_ptr<internal::CacheEntry>> _inlineCache; QTimer _inlineRequestTimer; UserData *_inlineBot = nullptr; PeerData *_inlineQueryPeer = nullptr; QString _inlineQuery, _inlineNextQuery, _inlineNextOffset; mtpRequestId _inlineRequestId = 0; }; } // namespace Layout } // namespace InlineBots �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/intro/���������������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0020556�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/intro/intro.style����������������������������������������������0000664�0000000�0000000�00000007237�13262451251�0023004�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ using "basic.style"; using "ui/widgets/widgets.style"; countryRipple: defaultRippleAnimation; introCoverHeight: 208px; introCoverMaxWidth: 880px; introCoverIconsMinSkip: 120px; introCoverLeft: icon {{ "intro_left", introCoverIconsFg }}; introCoverRight: icon {{ "intro_right", introCoverIconsFg }}; introCoverIcon: icon { { "intro_plane_trace", introCoverPlaneTrace }, { "intro_plane_inner", introCoverPlaneInner }, { "intro_plane_outer", introCoverPlaneOuter }, { "intro_plane_top", introCoverPlaneTop }, }; introCoverIconLeft: 50px; introCoverIconTop: 46px; introSettingsSkip: 10px; introPhotoTop: 10px; introCoverTitle: FlatLabel(defaultFlatLabel) { textFg: introTitleFg; align: align(center); style: TextStyle(defaultTextStyle) { font: font(22px semibold); linkFont: font(22px semibold); linkFontOver: font(22px semibold underline); } } introCoverTitleTop: 136px; introCoverDescription: FlatLabel(defaultFlatLabel) { textFg: introDescriptionFg; align: align(center); style: TextStyle(defaultTextStyle) { font: font(15px); linkFont: font(15px); linkFontOver: font(15px underline); lineHeight: 24px; } } introCoverDescriptionTop: 174px; introTitle: FlatLabel(defaultFlatLabel) { textFg: introTitleFg; style: TextStyle(defaultTextStyle) { font: font(17px semibold); linkFont: font(17px semibold); linkFontOver: font(17px semibold underline); } } introTitleTop: 1px; introDescription: FlatLabel(defaultFlatLabel) { textFg: introDescriptionFg; style: TextStyle(defaultTextStyle) { lineHeight: 20px; } } introDescriptionTop: 34px; introLink: defaultLinkButton; introPlaneWidth: 48px; introPlaneHeight: 38px; introHeight: 406px; introStepTopMin: 76px; introStepWidth: 380px; introStepHeight: 266px; introStepHeightAdd: 30px; introStepHeightFull: 590px; introSlideDuration: 200; introCoverDuration: 200; introNextButton: RoundButton(defaultActiveButton) { width: 300px; height: 56px; textTop: 17px; font: font(17px semibold); } introStepFieldTop: 96px; introPhoneTop: 6px; introLinkTop: 24px; introCountry: InputField(defaultInputField) { textMargins: margins(3px, 27px, 3px, 6px); font: font(16px); width: 300px; heightMin: 61px; } introCountryCode: InputField(introCountry) { width: 64px; textAlign: align(top); } introPhone: InputField(introCountry) { textMargins: margins(12px, 27px, 12px, 6px); width: 225px; } introCode: introCountry; introName: introCountry; introPassword: introCountry; introPasswordTop: 74px; introPasswordHintTop: 151px; introPasswordHint: FlatLabel(introDescription) { textFg: windowFg; } introResetButton: RoundButton(defaultLightButton) { textFg: attentionButtonFg; textFgOver: attentionButtonFgOver; textBgOver: attentionButtonBgOver; ripple: RippleAnimation(defaultRippleAnimation) { color: attentionButtonBgRipple; } } introResetBottom: 20px; introCountryIcon: icon {{ "intro_country_dropdown", menuIconFg }}; introCountryIconPosition: point(8px, 37px); introSelectDelta: 30px; introErrorTop: 235px; introErrorBelowLinkTop: 220px; introErrorDuration: 200; introError: FlatLabel(introDescription) { } introErrorCentered: FlatLabel(introError) { align: align(center); } introBackButton: IconButton(defaultIconButton) { width: 56px; height: 56px; icon: icon {{ "box_button_back", menuIconFg }}; iconOver: icon {{ "box_button_back", menuIconFgOver }}; rippleAreaPosition: point(8px, 8px); rippleAreaSize: 40px; ripple: RippleAnimation(defaultRippleAnimation) { color: windowBgOver; } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/intro/introcode.cpp��������������������������������������������0000664�0000000�0000000�00000024504�13262451251�0023255�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "intro/introcode.h" #include "lang/lang_keys.h" #include "application.h" #include "intro/introsignup.h" #include "intro/intropwdcheck.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" #include "styles/style_intro.h" namespace Intro { CodeInput::CodeInput(QWidget *parent, const style::InputField &st, base::lambda<QString()> placeholderFactory) : Ui::MaskedInputField(parent, st, std::move(placeholderFactory)) { } void CodeInput::setDigitsCountMax(int digitsCount) { _digitsCountMax = digitsCount; } void CodeInput::correctValue(const QString &was, int wasCursor, QString &now, int &nowCursor) { QString newText; int oldPos(nowCursor), newPos(-1), oldLen(now.length()), digitCount = 0; for (int i = 0; i < oldLen; ++i) { if (now[i].isDigit()) { ++digitCount; } } accumulate_min(digitCount, _digitsCountMax); auto strict = (digitCount == _digitsCountMax); newText.reserve(oldLen); for (int i = 0; i < oldLen; ++i) { QChar ch(now[i]); if (ch.isDigit()) { if (!digitCount--) { break; } newText += ch; if (strict && !digitCount) { break; } } if (i == oldPos) { newPos = newText.length(); } } if (newPos < 0 || newPos > newText.size()) { newPos = newText.size(); } if (newText != now) { now = newText; setText(now); startPlaceholderAnimation(); } if (newPos != nowCursor) { nowCursor = newPos; setCursorPosition(nowCursor); } if (strict) emit codeEntered(); } CodeWidget::CodeWidget(QWidget *parent, Widget::Data *data) : Step(parent, data) , _noTelegramCode(this, lang(lng_code_no_telegram), st::introLink) , _code(this, st::introCode, langFactory(lng_code_ph)) , _callTimer(this) , _callStatus(getData()->callStatus) , _callTimeout(getData()->callTimeout) , _callLabel(this, st::introDescription) , _checkRequest(this) { subscribe(Lang::Current().updated(), [this] { refreshLang(); }); connect(_code, SIGNAL(changed()), this, SLOT(onInputChange())); connect(_callTimer, SIGNAL(timeout()), this, SLOT(onSendCall())); connect(_checkRequest, SIGNAL(timeout()), this, SLOT(onCheckRequest())); connect(_noTelegramCode, SIGNAL(clicked()), this, SLOT(onNoTelegramCode())); _code->setDigitsCountMax(getData()->codeLength); setErrorBelowLink(true); setTitleText([text = App::formatPhone(getData()->phone)] { return text; }); updateDescText(); } void CodeWidget::refreshLang() { if (_noTelegramCode) _noTelegramCode->setText(lang(lng_code_no_telegram)); updateDescText(); updateControlsGeometry(); } void CodeWidget::updateDescText() { setDescriptionText(langFactory(getData()->codeByTelegram ? lng_code_telegram : lng_code_desc)); if (getData()->codeByTelegram) { _noTelegramCode->show(); _callTimer->stop(); } else { _noTelegramCode->hide(); _callStatus = getData()->callStatus; _callTimeout = getData()->callTimeout; if (_callStatus == Widget::Data::CallStatus::Waiting && !_callTimer->isActive()) { _callTimer->start(1000); } } updateCallText(); } void CodeWidget::updateCallText() { auto text = ([this]() -> QString { if (getData()->codeByTelegram) { return QString(); } switch (_callStatus) { case Widget::Data::CallStatus::Waiting: { if (_callTimeout >= 3600) { return lng_code_call(lt_minutes, qsl("%1:%2").arg(_callTimeout / 3600).arg((_callTimeout / 60) % 60, 2, 10, QChar('0')), lt_seconds, qsl("%1").arg(_callTimeout % 60, 2, 10, QChar('0'))); } else { return lng_code_call(lt_minutes, QString::number(_callTimeout / 60), lt_seconds, qsl("%1").arg(_callTimeout % 60, 2, 10, QChar('0'))); } } break; case Widget::Data::CallStatus::Calling: return lang(lng_code_calling); case Widget::Data::CallStatus::Called: return lang(lng_code_called); } return QString(); })(); _callLabel->setText(text); _callLabel->setVisible(!text.isEmpty() && !animating()); } void CodeWidget::resizeEvent(QResizeEvent *e) { Step::resizeEvent(e); updateControlsGeometry(); } void CodeWidget::updateControlsGeometry() { _code->moveToLeft(contentLeft(), contentTop() + st::introStepFieldTop); auto linkTop = _code->y() + _code->height() + st::introLinkTop; _noTelegramCode->moveToLeft(contentLeft() + st::buttonRadius, linkTop); _callLabel->moveToLeft(contentLeft() + st::buttonRadius, linkTop); } void CodeWidget::showCodeError(base::lambda<QString()> textFactory) { if (textFactory) _code->showError(); showError(std::move(textFactory)); } void CodeWidget::setInnerFocus() { _code->setFocusFast(); } void CodeWidget::activate() { Step::activate(); _code->show(); if (getData()->codeByTelegram) { _noTelegramCode->show(); } else { _callLabel->show(); } setInnerFocus(); } void CodeWidget::finished() { Step::finished(); _checkRequest->stop(); _callTimer->stop(); rpcInvalidate(); cancelled(); _sentCode.clear(); _code->setText(QString()); } void CodeWidget::cancelled() { MTP::cancel(base::take(_sentRequest)); MTP::cancel(base::take(_callRequestId)); MTP::send(MTPauth_CancelCode(MTP_string(getData()->phone), MTP_bytes(getData()->phoneHash))); } void CodeWidget::stopCheck() { _checkRequest->stop(); } void CodeWidget::onCheckRequest() { auto status = MTP::state(_sentRequest); if (status < 0) { auto leftms = -status; if (leftms >= 1000) { if (_sentRequest) { MTP::cancel(base::take(_sentRequest)); _sentCode.clear(); } } } if (!_sentRequest && status == MTP::RequestSent) { stopCheck(); } } void CodeWidget::codeSubmitDone(const MTPauth_Authorization &result) { stopCheck(); _sentRequest = 0; auto &d = result.c_auth_authorization(); if (d.vuser.type() != mtpc_user || !d.vuser.c_user().is_self()) { // wtf? showCodeError(&Lang::Hard::ServerError); return; } cSetLoggedPhoneNumber(getData()->phone); finish(d.vuser); } bool CodeWidget::codeSubmitFail(const RPCError &error) { if (MTP::isFloodError(error)) { stopCheck(); _sentRequest = 0; showCodeError(langFactory(lng_flood_error)); return true; } if (MTP::isDefaultHandledError(error)) return false; stopCheck(); _sentRequest = 0; auto &err = error.type(); if (err == qstr("PHONE_NUMBER_INVALID") || err == qstr("PHONE_CODE_EXPIRED")) { // show error goBack(); return true; } else if (err == qstr("PHONE_CODE_EMPTY") || err == qstr("PHONE_CODE_INVALID")) { showCodeError(langFactory(lng_bad_code)); return true; } else if (err == qstr("PHONE_NUMBER_UNOCCUPIED")) { // success, need to signUp getData()->code = _sentCode; goReplace(new Intro::SignupWidget(parentWidget(), getData())); return true; } else if (err == qstr("SESSION_PASSWORD_NEEDED")) { getData()->code = _sentCode; _checkRequest->start(1000); _sentRequest = MTP::send(MTPaccount_GetPassword(), rpcDone(&CodeWidget::gotPassword), rpcFail(&CodeWidget::codeSubmitFail)); return true; } if (cDebug()) { // internal server error auto text = err + ": " + error.description(); showCodeError([text] { return text; }); } else { showCodeError(&Lang::Hard::ServerError); } return false; } void CodeWidget::onInputChange() { hideError(); if (_code->getLastText().length() == getData()->codeLength) { submit(); } } void CodeWidget::onSendCall() { if (_callStatus == Widget::Data::CallStatus::Waiting) { if (--_callTimeout <= 0) { _callStatus = Widget::Data::CallStatus::Calling; _callTimer->stop(); _callRequestId = MTP::send(MTPauth_ResendCode(MTP_string(getData()->phone), MTP_bytes(getData()->phoneHash)), rpcDone(&CodeWidget::callDone)); } else { getData()->callStatus = _callStatus; getData()->callTimeout = _callTimeout; } updateCallText(); } } void CodeWidget::callDone(const MTPauth_SentCode &v) { if (v.type() == mtpc_auth_sentCode) { fillSentCodeData(v.c_auth_sentCode().vtype); _code->setDigitsCountMax(getData()->codeLength); } if (_callStatus == Widget::Data::CallStatus::Calling) { _callStatus = Widget::Data::CallStatus::Called; getData()->callStatus = _callStatus; getData()->callTimeout = _callTimeout; updateCallText(); } } void CodeWidget::gotPassword(const MTPaccount_Password &result) { stopCheck(); _sentRequest = 0; switch (result.type()) { case mtpc_account_noPassword: { // should not happen _code->setFocus(); } break; case mtpc_account_password: { auto &d = result.c_account_password(); getData()->pwdSalt = qba(d.vcurrent_salt); getData()->hasRecovery = mtpIsTrue(d.vhas_recovery); getData()->pwdHint = qs(d.vhint); goReplace(new Intro::PwdCheckWidget(parentWidget(), getData())); } break; } } void CodeWidget::submit() { if (_sentRequest) return; hideError(); _checkRequest->start(1000); _sentCode = _code->getLastText(); getData()->pwdSalt = QByteArray(); getData()->hasRecovery = false; getData()->pwdHint = QString(); _sentRequest = MTP::send(MTPauth_SignIn(MTP_string(getData()->phone), MTP_bytes(getData()->phoneHash), MTP_string(_sentCode)), rpcDone(&CodeWidget::codeSubmitDone), rpcFail(&CodeWidget::codeSubmitFail)); } void CodeWidget::onNoTelegramCode() { if (_noTelegramCodeRequestId) return; _noTelegramCodeRequestId = MTP::send(MTPauth_ResendCode(MTP_string(getData()->phone), MTP_bytes(getData()->phoneHash)), rpcDone(&CodeWidget::noTelegramCodeDone), rpcFail(&CodeWidget::noTelegramCodeFail)); } void CodeWidget::noTelegramCodeDone(const MTPauth_SentCode &result) { if (result.type() != mtpc_auth_sentCode) { showCodeError(&Lang::Hard::ServerError); return; } auto &d = result.c_auth_sentCode(); fillSentCodeData(d.vtype); _code->setDigitsCountMax(getData()->codeLength); if (d.has_next_type() && d.vnext_type.type() == mtpc_auth_codeTypeCall) { getData()->callStatus = Widget::Data::CallStatus::Waiting; getData()->callTimeout = d.has_timeout() ? d.vtimeout.v : 60; } else { getData()->callStatus = Widget::Data::CallStatus::Disabled; getData()->callTimeout = 0; } getData()->codeByTelegram = false; updateDescText(); } bool CodeWidget::noTelegramCodeFail(const RPCError &error) { if (MTP::isFloodError(error)) { showCodeError(langFactory(lng_flood_error)); return true; } if (MTP::isDefaultHandledError(error)) return false; if (cDebug()) { // internal server error auto text = error.type() + ": " + error.description(); showCodeError([text] { return text; }); } else { showCodeError(&Lang::Hard::ServerError); } return false; } } // namespace Intro ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/intro/introcode.h����������������������������������������������0000664�0000000�0000000�00000004222�13262451251�0022715�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "intro/introwidget.h" #include "ui/widgets/input_fields.h" namespace Ui { class RoundButton; class LinkButton; class FlatLabel; } // namespace Ui namespace Intro { class CodeInput final : public Ui::MaskedInputField { Q_OBJECT public: CodeInput(QWidget *parent, const style::InputField &st, base::lambda<QString()> placeholderFactory); void setDigitsCountMax(int digitsCount); signals: void codeEntered(); protected: void correctValue(const QString &was, int wasCursor, QString &now, int &nowCursor) override; private: int _digitsCountMax = 5; }; class CodeWidget : public Widget::Step { Q_OBJECT public: CodeWidget(QWidget *parent, Widget::Data *data); bool hasBack() const override { return true; } void setInnerFocus() override; void activate() override; void finished() override; void cancelled() override; void submit() override; void updateDescText(); protected: void resizeEvent(QResizeEvent *e) override; private slots: void onNoTelegramCode(); void onInputChange(); void onSendCall(); void onCheckRequest(); private: void updateCallText(); void refreshLang(); void updateControlsGeometry(); void codeSubmitDone(const MTPauth_Authorization &result); bool codeSubmitFail(const RPCError &error); void showCodeError(base::lambda<QString()> textFactory); void callDone(const MTPauth_SentCode &v); void gotPassword(const MTPaccount_Password &result); void noTelegramCodeDone(const MTPauth_SentCode &result); bool noTelegramCodeFail(const RPCError &result); void stopCheck(); object_ptr<Ui::LinkButton> _noTelegramCode; mtpRequestId _noTelegramCodeRequestId = 0; object_ptr<CodeInput> _code; QString _sentCode; mtpRequestId _sentRequest = 0; object_ptr<QTimer> _callTimer; Widget::Data::CallStatus _callStatus; int _callTimeout; mtpRequestId _callRequestId = 0; object_ptr<Ui::FlatLabel> _callLabel; object_ptr<QTimer> _checkRequest; }; } // namespace Intro ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/intro/introphone.cpp�������������������������������������������0000664�0000000�0000000�00000020544�13262451251�0023454�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "intro/introphone.h" #include "lang/lang_keys.h" #include "application.h" #include "intro/introcode.h" #include "styles/style_intro.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" #include "ui/wrap/fade_wrap.h" #include "core/click_handler_types.h" #include "boxes/confirm_box.h" #include "base/qthelp_url.h" #include "platform/platform_specific.h" #include "messenger.h" namespace Intro { namespace { void SendToBannedHelp(const QString &phone) { const auto version = QString::fromLatin1(AppVersionStr.c_str()) + (cAlphaVersion() ? " alpha" : "") + (cBetaVersion() ? qsl(" beta %1").arg(cBetaVersion()) : QString()); const auto subject = qsl("Banned phone number: ") + phone; const auto body = qsl("\ I'm trying to use my mobile phone number: ") + phone + qsl("\n\ But Telegram says it's banned. Please help.\n\ \n\ App version: ") + version + qsl("\n\ OS version: ") + cPlatformString() + qsl("\n\ Locale: ") + Platform::SystemLanguage(); const auto url = "mailto:?to=" + qthelp::url_encode("login@stel.com") + "&subject=" + qthelp::url_encode(subject) + "&body=" + qthelp::url_encode(body); UrlClickHandler::doOpen(url); } } // namespace PhoneWidget::PhoneWidget(QWidget *parent, Widget::Data *data) : Step(parent, data) , _country(this, st::introCountry) , _code(this, st::introCountryCode) , _phone(this, st::introPhone) , _checkRequest(this) { connect(_phone, SIGNAL(voidBackspace(QKeyEvent*)), _code, SLOT(startErasing(QKeyEvent*))); connect(_country, SIGNAL(codeChanged(const QString &)), _code, SLOT(codeSelected(const QString &))); connect(_code, SIGNAL(codeChanged(const QString &)), _country, SLOT(onChooseCode(const QString &))); connect(_code, SIGNAL(codeChanged(const QString &)), _phone, SLOT(onChooseCode(const QString &))); connect(_country, SIGNAL(codeChanged(const QString &)), _phone, SLOT(onChooseCode(const QString &))); connect(_code, SIGNAL(addedToNumber(const QString &)), _phone, SLOT(addedToNumber(const QString &))); connect(_phone, SIGNAL(changed()), this, SLOT(onInputChange())); connect(_code, SIGNAL(changed()), this, SLOT(onInputChange())); connect(_checkRequest, SIGNAL(timeout()), this, SLOT(onCheckRequest())); setTitleText(langFactory(lng_phone_title)); setDescriptionText(langFactory(lng_phone_desc)); subscribe(getData()->updated, [this] { countryChanged(); }); setErrorCentered(true); if (!_country->onChooseCountry(getData()->country)) { _country->onChooseCountry(qsl("US")); } _changed = false; Messenger::Instance().destroyStaleAuthorizationKeys(); } void PhoneWidget::resizeEvent(QResizeEvent *e) { Step::resizeEvent(e); _country->moveToLeft(contentLeft(), contentTop() + st::introStepFieldTop); auto phoneTop = _country->y() + _country->height() + st::introPhoneTop; _code->moveToLeft(contentLeft(), phoneTop); _phone->moveToLeft(contentLeft() + _country->width() - st::introPhone.width, phoneTop); updateSignupGeometry(); } void PhoneWidget::updateSignupGeometry() { if (_signup) { _signup->moveToLeft(contentLeft() + st::buttonRadius, contentTop() + st::introDescriptionTop); } } void PhoneWidget::showPhoneError(base::lambda<QString()> textFactory) { _phone->showError(); showError(std::move(textFactory)); } void PhoneWidget::hidePhoneError() { hideError(); if (_signup) { _signup->hide(anim::type::instant); showDescription(); } } void PhoneWidget::showSignup() { showPhoneError(langFactory(lng_bad_phone_noreg)); if (!_signup) { auto signupText = lng_phone_notreg(lt_link_start, textcmdStartLink(1), lt_link_end, textcmdStopLink(), lt_signup_start, textcmdStartLink(2), lt_signup_end, textcmdStopLink()); auto inner = object_ptr<Ui::FlatLabel>(this, signupText, Ui::FlatLabel::InitType::Rich, st::introDescription); _signup.create(this, std::move(inner)); _signup->entity()->setLink(1, std::make_shared<UrlClickHandler>(qsl("https://telegram.org"), false)); _signup->entity()->setLink(2, std::make_shared<LambdaClickHandler>([this] { toSignUp(); })); _signup->hide(anim::type::instant); updateSignupGeometry(); } _signup->show(anim::type::normal); hideDescription(); } void PhoneWidget::countryChanged() { if (!_changed) { selectCountry(getData()->country); } } void PhoneWidget::onInputChange() { _changed = true; hidePhoneError(); } void PhoneWidget::submit() { if (_sentRequest || isHidden()) return; if (!App::isValidPhone(fullNumber())) { showPhoneError(langFactory(lng_bad_phone)); _phone->setFocus(); return; } hidePhoneError(); _checkRequest->start(1000); _sentPhone = fullNumber(); _sentRequest = MTP::send(MTPauth_CheckPhone(MTP_string(_sentPhone)), rpcDone(&PhoneWidget::phoneCheckDone), rpcFail(&PhoneWidget::phoneSubmitFail)); } void PhoneWidget::stopCheck() { _checkRequest->stop(); } void PhoneWidget::onCheckRequest() { auto status = MTP::state(_sentRequest); if (status < 0) { auto leftms = -status; if (leftms >= 1000) { MTP::cancel(base::take(_sentRequest)); } } if (!_sentRequest && status == MTP::RequestSent) { stopCheck(); } } void PhoneWidget::phoneCheckDone(const MTPauth_CheckedPhone &result) { stopCheck(); auto &d = result.c_auth_checkedPhone(); if (mtpIsTrue(d.vphone_registered)) { hidePhoneError(); _checkRequest->start(1000); _sentRequest = MTP::send(MTPauth_SendCode(MTP_flags(0), MTP_string(_sentPhone), MTPBool(), MTP_int(ApiId), MTP_string(ApiHash)), rpcDone(&PhoneWidget::phoneSubmitDone), rpcFail(&PhoneWidget::phoneSubmitFail)); } else { showSignup(); _sentRequest = 0; } } void PhoneWidget::phoneSubmitDone(const MTPauth_SentCode &result) { stopCheck(); _sentRequest = 0; if (result.type() != mtpc_auth_sentCode) { showPhoneError(&Lang::Hard::ServerError); return; } auto &d = result.c_auth_sentCode(); fillSentCodeData(d.vtype); getData()->phone = _sentPhone; getData()->phoneHash = qba(d.vphone_code_hash); getData()->phoneIsRegistered = d.is_phone_registered(); if (d.has_next_type() && d.vnext_type.type() == mtpc_auth_codeTypeCall) { getData()->callStatus = Widget::Data::CallStatus::Waiting; getData()->callTimeout = d.has_timeout() ? d.vtimeout.v : 60; } else { getData()->callStatus = Widget::Data::CallStatus::Disabled; getData()->callTimeout = 0; } goNext(new Intro::CodeWidget(parentWidget(), getData())); } void PhoneWidget::toSignUp() { hideError(); // Hide error, but leave the signup label visible. _checkRequest->start(1000); _sentRequest = MTP::send(MTPauth_SendCode(MTP_flags(0), MTP_string(_sentPhone), MTPBool(), MTP_int(ApiId), MTP_string(ApiHash)), rpcDone(&PhoneWidget::phoneSubmitDone), rpcFail(&PhoneWidget::phoneSubmitFail)); } bool PhoneWidget::phoneSubmitFail(const RPCError &error) { if (MTP::isFloodError(error)) { stopCheck(); _sentRequest = 0; showPhoneError(langFactory(lng_flood_error)); return true; } if (MTP::isDefaultHandledError(error)) return false; stopCheck(); _sentRequest = 0; auto &err = error.type(); if (err == qstr("PHONE_NUMBER_FLOOD")) { Ui::show(Box<InformBox>(lang(lng_error_phone_flood))); return true; } else if (err == qstr("PHONE_NUMBER_INVALID")) { // show error showPhoneError(langFactory(lng_bad_phone)); return true; } else if (err == qstr("PHONE_NUMBER_BANNED")) { const auto phone = _sentPhone; Ui::show(Box<ConfirmBox>( lang(lng_signin_banned_text), lang(lng_box_ok), lang(lng_signin_banned_help), [] { Ui::hideLayer(); }, [phone] { SendToBannedHelp(phone); Ui::hideLayer(); })); return true; } if (cDebug()) { // internal server error auto text = err + ": " + error.description(); showPhoneError([text] { return text; }); } else { showPhoneError(&Lang::Hard::ServerError); } return false; } QString PhoneWidget::fullNumber() const { return _code->getLastText() + _phone->getLastText(); } void PhoneWidget::selectCountry(const QString &c) { _country->onChooseCountry(c); } void PhoneWidget::setInnerFocus() { _phone->setFocusFast(); } void PhoneWidget::activate() { Step::activate(); _country->show(); _phone->show(); _code->show(); setInnerFocus(); } void PhoneWidget::finished() { Step::finished(); _checkRequest->stop(); rpcInvalidate(); cancelled(); } void PhoneWidget::cancelled() { MTP::cancel(base::take(_sentRequest)); } } // namespace Intro ������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/intro/introphone.h���������������������������������������������0000664�0000000�0000000�00000003150�13262451251�0023113�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/countryinput.h" #include "intro/introwidget.h" namespace Ui { class PhonePartInput; class CountryCodeInput; class RoundButton; class FlatLabel; } // namespace Ui namespace Intro { class PhoneWidget : public Widget::Step { Q_OBJECT public: PhoneWidget(QWidget *parent, Widget::Data *data); void selectCountry(const QString &country); void setInnerFocus() override; void activate() override; void finished() override; void cancelled() override; void submit() override; bool hasBack() const override { return true; } protected: void resizeEvent(QResizeEvent *e) override; private slots: void onInputChange(); void onCheckRequest(); private: void updateSignupGeometry(); void countryChanged(); void phoneCheckDone(const MTPauth_CheckedPhone &result); void phoneSubmitDone(const MTPauth_SentCode &result); bool phoneSubmitFail(const RPCError &error); void toSignUp(); QString fullNumber() const; void stopCheck(); void showPhoneError(base::lambda<QString()> textFactory); void hidePhoneError(); void showSignup(); bool _changed = false; object_ptr<CountryInput> _country; object_ptr<Ui::CountryCodeInput> _code; object_ptr<Ui::PhonePartInput> _phone; object_ptr<Ui::FadeWrap<Ui::FlatLabel>> _signup = { nullptr }; QString _sentPhone; mtpRequestId _sentRequest = 0; object_ptr<QTimer> _checkRequest; }; } // namespace Intro ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/intro/intropwdcheck.cpp����������������������������������������0000664�0000000�0000000�00000020012�13262451251�0024121�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "intro/intropwdcheck.h" #include "styles/style_intro.h" #include "styles/style_boxes.h" #include "core/file_utilities.h" #include "boxes/confirm_box.h" #include "lang/lang_keys.h" #include "application.h" #include "intro/introsignup.h" #include "ui/widgets/buttons.h" #include "ui/widgets/input_fields.h" #include "ui/widgets/labels.h" namespace Intro { PwdCheckWidget::PwdCheckWidget(QWidget *parent, Widget::Data *data) : Step(parent, data) , _salt(getData()->pwdSalt) , _hasRecovery(getData()->hasRecovery) , _hint(getData()->pwdHint) , _pwdField(this, st::introPassword, langFactory(lng_signin_password)) , _pwdHint(this, st::introPasswordHint) , _codeField(this, st::introPassword, langFactory(lng_signin_code)) , _toRecover(this, lang(lng_signin_recover)) , _toPassword(this, lang(lng_signin_try_password)) , _checkRequest(this) { subscribe(Lang::Current().updated(), [this] { refreshLang(); }); connect(_checkRequest, SIGNAL(timeout()), this, SLOT(onCheckRequest())); connect(_toRecover, SIGNAL(clicked()), this, SLOT(onToRecover())); connect(_toPassword, SIGNAL(clicked()), this, SLOT(onToPassword())); connect(_pwdField, SIGNAL(changed()), this, SLOT(onInputChange())); connect(_codeField, SIGNAL(changed()), this, SLOT(onInputChange())); setTitleText(langFactory(lng_signin_title)); updateDescriptionText(); setErrorBelowLink(true); if (_hint.isEmpty()) { _pwdHint->hide(); } else { _pwdHint->setText(lng_signin_hint(lt_password_hint, _hint)); } _codeField->hide(); _toPassword->hide(); setMouseTracking(true); } void PwdCheckWidget::refreshLang() { if (_toRecover) _toRecover->setText(lang(lng_signin_recover)); if (_toPassword) _toPassword->setText(lang(lng_signin_try_password)); if (!_hint.isEmpty()) { _pwdHint->setText(lng_signin_hint(lt_password_hint, _hint)); } updateControlsGeometry(); } void PwdCheckWidget::resizeEvent(QResizeEvent *e) { Step::resizeEvent(e); updateControlsGeometry(); } void PwdCheckWidget::updateControlsGeometry() { _pwdField->moveToLeft(contentLeft(), contentTop() + st::introPasswordTop); _pwdHint->moveToLeft(contentLeft() + st::buttonRadius, contentTop() + st::introPasswordHintTop); _codeField->moveToLeft(contentLeft(), contentTop() + st::introStepFieldTop); auto linkTop = _codeField->y() + _codeField->height() + st::introLinkTop; _toRecover->moveToLeft(contentLeft() + st::buttonRadius, linkTop); _toPassword->moveToLeft(contentLeft() + st::buttonRadius, linkTop); } void PwdCheckWidget::setInnerFocus() { if (_pwdField->isHidden()) { _codeField->setFocusFast(); } else { _pwdField->setFocusFast(); } } void PwdCheckWidget::activate() { if (_pwdField->isHidden() && _codeField->isHidden()) { Step::activate(); _pwdField->show(); _pwdHint->show(); _toRecover->show(); } setInnerFocus(); } void PwdCheckWidget::cancelled() { MTP::cancel(base::take(_sentRequest)); } void PwdCheckWidget::stopCheck() { _checkRequest->stop(); } void PwdCheckWidget::onCheckRequest() { auto status = MTP::state(_sentRequest); if (status < 0) { auto leftms = -status; if (leftms >= 1000) { MTP::cancel(base::take(_sentRequest)); } } if (!_sentRequest && status == MTP::RequestSent) { stopCheck(); } } void PwdCheckWidget::pwdSubmitDone(bool recover, const MTPauth_Authorization &result) { _sentRequest = 0; stopCheck(); if (recover) { cSetPasswordRecovered(true); } auto &d = result.c_auth_authorization(); if (d.vuser.type() != mtpc_user || !d.vuser.c_user().is_self()) { // wtf? showError(&Lang::Hard::ServerError); return; } finish(d.vuser); } bool PwdCheckWidget::pwdSubmitFail(const RPCError &error) { if (MTP::isFloodError(error)) { _sentRequest = 0; stopCheck(); showError(langFactory(lng_flood_error)); _pwdField->showError(); return true; } if (MTP::isDefaultHandledError(error)) return false; _sentRequest = 0; stopCheck(); auto &err = error.type(); if (err == qstr("PASSWORD_HASH_INVALID")) { showError(langFactory(lng_signin_bad_password)); _pwdField->selectAll(); _pwdField->showError(); return true; } else if (err == qstr("PASSWORD_EMPTY")) { goBack(); } if (cDebug()) { // internal server error auto text = err + ": " + error.description(); showError([text] { return text; }); } else { showError(&Lang::Hard::ServerError); } _pwdField->setFocus(); return false; } bool PwdCheckWidget::codeSubmitFail(const RPCError &error) { if (MTP::isFloodError(error)) { showError(langFactory(lng_flood_error)); _codeField->showError(); return true; } if (MTP::isDefaultHandledError(error)) return false; _sentRequest = 0; stopCheck(); const QString &err = error.type(); if (err == qstr("PASSWORD_EMPTY")) { goBack(); return true; } else if (err == qstr("PASSWORD_RECOVERY_NA")) { recoverStartFail(error); return true; } else if (err == qstr("PASSWORD_RECOVERY_EXPIRED")) { _emailPattern = QString(); onToPassword(); return true; } else if (err == qstr("CODE_INVALID")) { showError(langFactory(lng_signin_wrong_code)); _codeField->selectAll(); _codeField->showError(); return true; } if (cDebug()) { // internal server error auto text = err + ": " + error.description(); showError([text] { return text; }); } else { showError(&Lang::Hard::ServerError); } _codeField->setFocus(); return false; } void PwdCheckWidget::recoverStarted(const MTPauth_PasswordRecovery &result) { _emailPattern = qs(result.c_auth_passwordRecovery().vemail_pattern); updateDescriptionText(); } bool PwdCheckWidget::recoverStartFail(const RPCError &error) { stopCheck(); _pwdField->show(); _pwdHint->show(); _codeField->hide(); _pwdField->setFocus(); updateDescriptionText(); update(); hideError(); return true; } void PwdCheckWidget::onToRecover() { if (_hasRecovery) { if (_sentRequest) { MTP::cancel(base::take(_sentRequest)); } hideError(); _toRecover->hide(); _toPassword->show(); _pwdField->hide(); _pwdHint->hide(); _pwdField->setText(QString()); _codeField->show(); _codeField->setFocus(); updateDescriptionText(); if (_emailPattern.isEmpty()) { MTP::send(MTPauth_RequestPasswordRecovery(), rpcDone(&PwdCheckWidget::recoverStarted), rpcFail(&PwdCheckWidget::recoverStartFail)); } } else { Ui::show(Box<InformBox>(lang(lng_signin_no_email_forgot), [this] { showReset(); })); } } void PwdCheckWidget::onToPassword() { Ui::show(Box<InformBox>(lang(lng_signin_cant_email_forgot), [this] { showReset(); })); } void PwdCheckWidget::showReset() { if (_sentRequest) { MTP::cancel(base::take(_sentRequest)); } _toRecover->show(); _toPassword->hide(); _pwdField->show(); _pwdHint->show(); _codeField->hide(); _codeField->setText(QString()); _pwdField->setFocus(); showResetButton(); updateDescriptionText(); update(); } void PwdCheckWidget::updateDescriptionText() { auto pwdHidden = _pwdField->isHidden(); auto emailPattern = _emailPattern; setDescriptionText([pwdHidden, emailPattern] { return pwdHidden ? lng_signin_recover_desc(lt_email, emailPattern) : lang(lng_signin_desc); }); } void PwdCheckWidget::onInputChange() { hideError(); } void PwdCheckWidget::submit() { if (_sentRequest) return; if (_pwdField->isHidden()) { auto code = _codeField->getLastText().trimmed(); if (code.isEmpty()) { _codeField->showError(); return; } _sentRequest = MTP::send(MTPauth_RecoverPassword(MTP_string(code)), rpcDone(&PwdCheckWidget::pwdSubmitDone, true), rpcFail(&PwdCheckWidget::codeSubmitFail)); } else { hideError(); QByteArray pwdData = _salt + _pwdField->getLastText().toUtf8() + _salt, pwdHash(32, Qt::Uninitialized); hashSha256(pwdData.constData(), pwdData.size(), pwdHash.data()); _sentRequest = MTP::send(MTPauth_CheckPassword(MTP_bytes(pwdHash)), rpcDone(&PwdCheckWidget::pwdSubmitDone, false), rpcFail(&PwdCheckWidget::pwdSubmitFail)); } } QString PwdCheckWidget::nextButtonText() const { return lang(lng_intro_submit); } } // namespace Intro ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/intro/intropwdcheck.h������������������������������������������0000664�0000000�0000000�00000003155�13262451251�0023577�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "intro/introwidget.h" namespace Ui { class InputField; class PasswordInput; class RoundButton; class LinkButton; } // namespace Ui namespace Intro { class PwdCheckWidget : public Widget::Step { Q_OBJECT public: PwdCheckWidget(QWidget *parent, Widget::Data *data); void setInnerFocus() override; void activate() override; void cancelled() override; void submit() override; QString nextButtonText() const override; protected: void resizeEvent(QResizeEvent *e) override; private slots: void onToRecover(); void onToPassword(); void onInputChange(); void onCheckRequest(); private: void showReset(); void refreshLang(); void updateControlsGeometry(); void pwdSubmitDone(bool recover, const MTPauth_Authorization &result); bool pwdSubmitFail(const RPCError &error); bool codeSubmitFail(const RPCError &error); bool recoverStartFail(const RPCError &error); void recoverStarted(const MTPauth_PasswordRecovery &result); void updateDescriptionText(); void stopCheck(); QByteArray _salt; bool _hasRecovery; QString _hint, _emailPattern; object_ptr<Ui::PasswordInput> _pwdField; object_ptr<Ui::FlatLabel> _pwdHint; object_ptr<Ui::InputField> _codeField; object_ptr<Ui::LinkButton> _toRecover; object_ptr<Ui::LinkButton> _toPassword; mtpRequestId _sentRequest = 0; QByteArray _pwdSalt; object_ptr<QTimer> _checkRequest; }; } // namespace Intro �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/intro/introsignup.cpp������������������������������������������0000664�0000000�0000000�00000012715�13262451251�0023651�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "intro/introsignup.h" #include "styles/style_intro.h" #include "styles/style_boxes.h" #include "core/file_utilities.h" #include "boxes/photo_crop_box.h" #include "boxes/confirm_box.h" #include "lang/lang_keys.h" #include "application.h" #include "ui/widgets/buttons.h" #include "ui/widgets/input_fields.h" #include "ui/widgets/labels.h" #include "ui/special_buttons.h" namespace Intro { SignupWidget::SignupWidget(QWidget *parent, Widget::Data *data) : Step(parent, data) , _photo( this, peerFromUser(0), Ui::UserpicButton::Role::ChangePhoto, st::defaultUserpicButton) , _first(this, st::introName, langFactory(lng_signup_firstname)) , _last(this, st::introName, langFactory(lng_signup_lastname)) , _invertOrder(langFirstNameGoesSecond()) , _checkRequest(this) { subscribe(Lang::Current().updated(), [this] { refreshLang(); }); if (_invertOrder) { setTabOrder(_last, _first); } else { setTabOrder(_first, _last); } connect(_checkRequest, SIGNAL(timeout()), this, SLOT(onCheckRequest())); setErrorCentered(true); setTitleText(langFactory(lng_signup_title)); setDescriptionText(langFactory(lng_signup_desc)); setMouseTracking(true); } void SignupWidget::refreshLang() { _invertOrder = langFirstNameGoesSecond(); if (_invertOrder) { setTabOrder(_last, _first); } else { setTabOrder(_first, _last); } updateControlsGeometry(); } void SignupWidget::resizeEvent(QResizeEvent *e) { Step::resizeEvent(e); updateControlsGeometry(); } void SignupWidget::updateControlsGeometry() { auto photoRight = contentLeft() + st::introNextButton.width; auto photoTop = contentTop() + st::introPhotoTop; _photo->moveToLeft(photoRight - _photo->width(), photoTop); auto firstTop = contentTop() + st::introStepFieldTop; auto secondTop = firstTop + st::introName.heightMin + st::introPhoneTop; if (_invertOrder) { _last->moveToLeft(contentLeft(), firstTop); _first->moveToLeft(contentLeft(), secondTop); } else { _first->moveToLeft(contentLeft(), firstTop); _last->moveToLeft(contentLeft(), secondTop); } } void SignupWidget::setInnerFocus() { if (_invertOrder || _last->hasFocus()) { _last->setFocusFast(); } else { _first->setFocusFast(); } } void SignupWidget::activate() { Step::activate(); _first->show(); _last->show(); _photo->show(); setInnerFocus(); } void SignupWidget::cancelled() { MTP::cancel(base::take(_sentRequest)); } void SignupWidget::stopCheck() { _checkRequest->stop(); } void SignupWidget::onCheckRequest() { auto status = MTP::state(_sentRequest); if (status < 0) { auto leftms = -status; if (leftms >= 1000) { MTP::cancel(base::take(_sentRequest)); } } if (!_sentRequest && status == MTP::RequestSent) { stopCheck(); } } void SignupWidget::nameSubmitDone(const MTPauth_Authorization &result) { stopCheck(); auto &d = result.c_auth_authorization(); if (d.vuser.type() != mtpc_user || !d.vuser.c_user().is_self()) { // wtf? showError(&Lang::Hard::ServerError); return; } finish(d.vuser, _photo->takeResultImage()); } bool SignupWidget::nameSubmitFail(const RPCError &error) { if (MTP::isFloodError(error)) { stopCheck(); showError(langFactory(lng_flood_error)); if (_invertOrder) { _first->setFocus(); } else { _last->setFocus(); } return true; } if (MTP::isDefaultHandledError(error)) return false; stopCheck(); auto &err = error.type(); if (err == qstr("PHONE_NUMBER_FLOOD")) { Ui::show(Box<InformBox>(lang(lng_error_phone_flood))); return true; } else if (err == qstr("PHONE_NUMBER_INVALID") || err == qstr("PHONE_CODE_EXPIRED") || err == qstr("PHONE_CODE_EMPTY") || err == qstr("PHONE_CODE_INVALID") || err == qstr("PHONE_NUMBER_OCCUPIED")) { goBack(); return true; } else if (err == "FIRSTNAME_INVALID") { showError(langFactory(lng_bad_name)); _first->setFocus(); return true; } else if (err == "LASTNAME_INVALID") { showError(langFactory(lng_bad_name)); _last->setFocus(); return true; } if (cDebug()) { // internal server error auto text = err + ": " + error.description(); showError([text] { return text; }); } else { showError(&Lang::Hard::ServerError); } if (_invertOrder) { _last->setFocus(); } else { _first->setFocus(); } return false; } void SignupWidget::onInputChange() { hideError(); } void SignupWidget::submit() { if (_sentRequest) return; if (_invertOrder) { if ((_last->hasFocus() || _last->getLastText().trimmed().length()) && !_first->getLastText().trimmed().length()) { _first->setFocus(); return; } else if (!_last->getLastText().trimmed().length()) { _last->setFocus(); return; } } else { if ((_first->hasFocus() || _first->getLastText().trimmed().length()) && !_last->getLastText().trimmed().length()) { _last->setFocus(); return; } else if (!_first->getLastText().trimmed().length()) { _first->setFocus(); return; } } hideError(); _firstName = _first->getLastText().trimmed(); _lastName = _last->getLastText().trimmed(); _sentRequest = MTP::send(MTPauth_SignUp(MTP_string(getData()->phone), MTP_bytes(getData()->phoneHash), MTP_string(getData()->code), MTP_string(_firstName), MTP_string(_lastName)), rpcDone(&SignupWidget::nameSubmitDone), rpcFail(&SignupWidget::nameSubmitFail)); } QString SignupWidget::nextButtonText() const { return lang(lng_intro_finish); } } // namespace Intro ���������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/intro/introsignup.h��������������������������������������������0000664�0000000�0000000�00000002330�13262451251�0023306�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "intro/introwidget.h" namespace Ui { class RoundButton; class InputField; class UserpicButton; } // namespace Ui namespace Intro { class SignupWidget : public Widget::Step { Q_OBJECT public: SignupWidget(QWidget *parent, Widget::Data *data); void setInnerFocus() override; void activate() override; void cancelled() override; void submit() override; QString nextButtonText() const override; protected: void resizeEvent(QResizeEvent *e) override; private slots: void onInputChange(); void onCheckRequest(); private: void refreshLang(); void updateControlsGeometry(); void nameSubmitDone(const MTPauth_Authorization &result); bool nameSubmitFail(const RPCError &error); void stopCheck(); object_ptr<Ui::UserpicButton> _photo; object_ptr<Ui::InputField> _first; object_ptr<Ui::InputField> _last; QString _firstName, _lastName; mtpRequestId _sentRequest = 0; bool _invertOrder = false; object_ptr<QTimer> _checkRequest; }; } // namespace Intro ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/intro/introstart.cpp�������������������������������������������0000664�0000000�0000000�00000001537�13262451251�0023501�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "intro/introstart.h" #include "lang/lang_keys.h" #include "application.h" #include "intro/introphone.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" namespace Intro { StartWidget::StartWidget(QWidget *parent, Widget::Data *data) : Step(parent, data, true) { setMouseTracking(true); setTitleText([] { return qsl("Telegram Desktop"); }); setDescriptionText(langFactory(lng_intro_about)); show(); } void StartWidget::submit() { goNext(new Intro::PhoneWidget(parentWidget(), getData())); } QString StartWidget::nextButtonText() const { return lang(lng_start_msgs); } } // namespace Intro �����������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/intro/introstart.h���������������������������������������������0000664�0000000�0000000�00000001114�13262451251�0023135�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "intro/introwidget.h" namespace Ui { class FlatLabel; class LinkButton; class RoundButton; } // namespace Ui namespace Intro { class StartWidget : public Widget::Step { public: StartWidget(QWidget *parent, Widget::Data *data); void submit() override; QString nextButtonText() const override; }; } // namespace Intro ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/intro/introwidget.cpp������������������������������������������0000664�0000000�0000000�00000064570�13262451251�0023635�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "intro/introwidget.h" #include "lang/lang_keys.h" #include "storage/localstorage.h" #include "lang/lang_file_parser.h" #include "intro/introstart.h" #include "intro/introphone.h" #include "intro/introcode.h" #include "intro/introsignup.h" #include "intro/intropwdcheck.h" #include "mainwidget.h" #include "apiwrap.h" #include "mainwindow.h" #include "messenger.h" #include "application.h" #include "boxes/confirm_box.h" #include "ui/text/text.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" #include "ui/wrap/fade_wrap.h" #include "ui/effects/slide_animation.h" #include "autoupdater.h" #include "window/window_slide_animation.h" #include "styles/style_boxes.h" #include "styles/style_intro.h" #include "styles/style_window.h" #include "window/themes/window_theme.h" #include "lang/lang_cloud_manager.h" #include "auth_session.h" namespace Intro { namespace { constexpr str_const kDefaultCountry = "US"; } // namespace Widget::Widget(QWidget *parent) : TWidget(parent) , _back(this, object_ptr<Ui::IconButton>(this, st::introBackButton)) , _settings( this, object_ptr<Ui::RoundButton>( this, langFactory(lng_menu_settings), st::defaultBoxButton)) , _next(this, base::lambda<QString()>(), st::introNextButton) { auto country = Platform::SystemCountry(); if (country.isEmpty()) { country = str_const_toString(kDefaultCountry); } getData()->country = country; _back->entity()->setClickedCallback([this] { historyMove(Direction::Back); }); _back->hide(anim::type::instant); _next->setClickedCallback([this] { getStep()->submit(); }); _settings->entity()->setClickedCallback([] { App::wnd()->showSettings(); }); getNearestDC(); appendStep(new StartWidget(this, getData())); fixOrder(); subscribe(Lang::CurrentCloudManager().firstLanguageSuggestion(), [this] { createLanguageLink(); }); createLanguageLink(); if (_changeLanguage) _changeLanguage->finishAnimating(); subscribe(Lang::Current().updated(), [this] { refreshLang(); }); show(); showControls(); getStep()->showFast(); cSetPasswordRecovered(false); #ifndef TDESKTOP_DISABLE_AUTOUPDATE Sandbox::connect(SIGNAL(updateLatest()), this, SLOT(onCheckUpdateStatus())); Sandbox::connect(SIGNAL(updateFailed()), this, SLOT(onCheckUpdateStatus())); Sandbox::connect(SIGNAL(updateReady()), this, SLOT(onCheckUpdateStatus())); Sandbox::startUpdateCheck(); onCheckUpdateStatus(); #endif // !TDESKTOP_DISABLE_AUTOUPDATE } void Widget::refreshLang() { _changeLanguage.destroy(); createLanguageLink(); InvokeQueued(this, [this] { updateControlsGeometry(); }); } void Widget::createLanguageLink() { if (_changeLanguage) return; auto createLink = [this](const QString &text, const QString &languageId) { _changeLanguage.create( this, object_ptr<Ui::LinkButton>(this, text)); _changeLanguage->hide(anim::type::instant); _changeLanguage->entity()->setClickedCallback([=] { Lang::CurrentCloudManager().switchToLanguage(languageId); }); _changeLanguage->toggle(!_resetAccount, anim::type::normal); updateControlsGeometry(); }; auto currentId = Lang::Current().id(); auto defaultId = Lang::DefaultLanguageId(); auto suggestedId = Lang::CurrentCloudManager().suggestedLanguage(); if (!currentId.isEmpty() && currentId != defaultId) { createLink(Lang::GetOriginalValue(lng_switch_to_this), defaultId); } else if (!suggestedId.isEmpty() && suggestedId != currentId) { request(MTPlangpack_GetStrings( MTP_string(suggestedId), MTP_vector<MTPstring>(1, MTP_string("lng_switch_to_this")) )).done([=](const MTPVector<MTPLangPackString> &result) { auto strings = Lang::Instance::ParseStrings(result); auto it = strings.find(lng_switch_to_this); if (it != strings.end()) { createLink(it->second, suggestedId); } }).send(); } } #ifndef TDESKTOP_DISABLE_AUTOUPDATE void Widget::onCheckUpdateStatus() { if (Sandbox::updatingState() == Application::UpdatingReady) { if (_update) return; _update.create( this, object_ptr<Ui::RoundButton>( this, langFactory(lng_menu_update), st::defaultBoxButton)); if (!_a_show.animating()) { _update->setVisible(true); } _update->entity()->setClickedCallback([] { checkReadyUpdate(); App::restart(); }); } else { if (!_update) return; _update.destroy(); } updateControlsGeometry(); } #endif // TDESKTOP_DISABLE_AUTOUPDATE void Widget::setInnerFocus() { if (getStep()->animating()) { setFocus(); } else { getStep()->setInnerFocus(); } } void Widget::historyMove(Direction direction) { if (getStep()->animating()) return; Assert(_stepHistory.size() > 1); auto wasStep = getStep((direction == Direction::Back) ? 0 : 1); if (direction == Direction::Back) { _stepHistory.pop_back(); wasStep->cancelled(); } else if (direction == Direction::Replace) { _stepHistory.removeAt(_stepHistory.size() - 2); } getStep()->prepareShowAnimated(wasStep); if (wasStep->hasCover() != getStep()->hasCover()) { _nextTopFrom = wasStep->contentTop() + st::introStepHeight; _controlsTopFrom = wasStep->hasCover() ? st::introCoverHeight : 0; _coverShownAnimation.start([this] { updateControlsGeometry(); }, 0., 1., st::introCoverDuration, wasStep->hasCover() ? anim::linear : anim::easeOutCirc); } if (direction == Direction::Forward || direction == Direction::Replace) { wasStep->finished(); } if (direction == Direction::Back || direction == Direction::Replace) { delete base::take(wasStep); } _back->toggle(getStep()->hasBack(), anim::type::normal); auto stepHasCover = getStep()->hasCover(); _settings->toggle(!stepHasCover, anim::type::normal); if (_update) _update->toggle(!stepHasCover, anim::type::normal); if (_changeLanguage) _changeLanguage->toggle(!_resetAccount, anim::type::normal); _next->setText([this] { return getStep()->nextButtonText(); }); if (_resetAccount) _resetAccount->hide(anim::type::normal); getStep()->showAnimated(direction); fixOrder(); } void Widget::fixOrder() { _next->raise(); if (_update) _update->raise(); _settings->raise(); _back->raise(); } void Widget::moveToStep(Step *step, Direction direction) { appendStep(step); _back->raise(); _settings->raise(); if (_update) { _update->raise(); } historyMove(direction); } void Widget::appendStep(Step *step) { _stepHistory.push_back(step); step->setGeometry(calculateStepRect()); step->setGoCallback([this](Step *step, Direction direction) { if (direction == Direction::Back) { historyMove(direction); } else { moveToStep(step, direction); } }); step->setShowResetCallback([this] { showResetButton(); }); } void Widget::showResetButton() { if (!_resetAccount) { auto entity = object_ptr<Ui::RoundButton>(this, langFactory(lng_signin_reset_account), st::introResetButton); _resetAccount.create( this, std::move(entity)); _resetAccount->hide(anim::type::instant); _resetAccount->entity()->setClickedCallback([this] { resetAccount(); }); updateControlsGeometry(); } _resetAccount->show(anim::type::normal); if (_changeLanguage) _changeLanguage->hide(anim::type::normal); } void Widget::resetAccount() { if (_resetRequest) return; Ui::show(Box<ConfirmBox>(lang(lng_signin_sure_reset), lang(lng_signin_reset), st::attentionBoxButton, base::lambda_guarded(this, [this] { if (_resetRequest) return; _resetRequest = request(MTPaccount_DeleteAccount(MTP_string("Forgot password"))).done([this](const MTPBool &result) { _resetRequest = 0; Ui::hideLayer(); moveToStep(new SignupWidget(this, getData()), Direction::Replace); }).fail([this](const RPCError &error) { _resetRequest = 0; auto type = error.type(); if (type.startsWith(qstr("2FA_CONFIRM_WAIT_"))) { auto seconds = type.mid(qstr("2FA_CONFIRM_WAIT_").size()).toInt(); auto days = (seconds + 59) / 86400; auto hours = ((seconds + 59) % 86400) / 3600; auto minutes = ((seconds + 59) % 3600) / 60; auto when = lng_signin_reset_minutes(lt_count, minutes); if (days > 0) { auto daysCount = lng_signin_reset_days(lt_count, days); auto hoursCount = lng_signin_reset_hours(lt_count, hours); when = lng_signin_reset_in_days(lt_days_count, daysCount, lt_hours_count, hoursCount, lt_minutes_count, when); } else if (hours > 0) { auto hoursCount = lng_signin_reset_hours(lt_count, hours); when = lng_signin_reset_in_hours(lt_hours_count, hoursCount, lt_minutes_count, when); } Ui::show(Box<InformBox>(lng_signin_reset_wait(lt_phone_number, App::formatPhone(getData()->phone), lt_when, when))); } else if (type == qstr("2FA_RECENT_CONFIRM")) { Ui::show(Box<InformBox>(lang(lng_signin_reset_cancelled))); } else { Ui::hideLayer(); getStep()->showError(&Lang::Hard::ServerError); } }).send(); }))); } void Widget::getNearestDC() { request(MTPhelp_GetNearestDc()).done([this](const MTPNearestDc &result) { auto &nearest = result.c_nearestDc(); DEBUG_LOG(("Got nearest dc, country: %1, nearest: %2, this: %3").arg(qs(nearest.vcountry)).arg(nearest.vnearest_dc.v).arg(nearest.vthis_dc.v)); Messenger::Instance().suggestMainDcId(nearest.vnearest_dc.v); auto nearestCountry = qs(nearest.vcountry); if (getData()->country != nearestCountry) { getData()->country = nearestCountry; getData()->updated.notify(); } }).send(); } void Widget::showControls() { getStep()->show(); _next->show(); _next->setText([this] { return getStep()->nextButtonText(); }); auto hasCover = getStep()->hasCover(); _settings->toggle(!hasCover, anim::type::instant); if (_update) _update->toggle(!hasCover, anim::type::instant); if (_changeLanguage) _changeLanguage->toggle(!_resetAccount, anim::type::instant); _back->toggle(getStep()->hasBack(), anim::type::instant); } void Widget::hideControls() { getStep()->hide(); _next->hide(); _settings->hide(anim::type::instant); if (_update) _update->hide(anim::type::instant); if (_changeLanguage) _changeLanguage->hide(anim::type::instant); _back->hide(anim::type::instant); } void Widget::showAnimated(const QPixmap &bgAnimCache, bool back) { _showBack = back; (_showBack ? _cacheOver : _cacheUnder) = bgAnimCache; _a_show.finish(); showControls(); (_showBack ? _cacheUnder : _cacheOver) = Ui::GrabWidget(this); hideControls(); _a_show.start([this] { animationCallback(); }, 0., 1., st::slideDuration, Window::SlideAnimation::transition()); show(); } void Widget::animationCallback() { update(); if (!_a_show.animating()) { _cacheUnder = _cacheOver = QPixmap(); showControls(); getStep()->activate(); } } void Widget::paintEvent(QPaintEvent *e) { bool trivial = (rect() == e->rect()); setMouseTracking(true); if (_coverShownAnimation.animating()) { _coverShownAnimation.step(getms()); } QPainter p(this); if (!trivial) { p.setClipRect(e->rect()); } p.fillRect(e->rect(), st::windowBg); auto progress = _a_show.current(getms(), 1.); if (_a_show.animating()) { auto coordUnder = _showBack ? anim::interpolate(-st::slideShift, 0, progress) : anim::interpolate(0, -st::slideShift, progress); auto coordOver = _showBack ? anim::interpolate(0, width(), progress) : anim::interpolate(width(), 0, progress); auto shadow = _showBack ? (1. - progress) : progress; if (coordOver > 0) { p.drawPixmap(QRect(0, 0, coordOver, height()), _cacheUnder, QRect(-coordUnder * cRetinaFactor(), 0, coordOver * cRetinaFactor(), height() * cRetinaFactor())); p.setOpacity(shadow); p.fillRect(0, 0, coordOver, height(), st::slideFadeOutBg); p.setOpacity(1); } p.drawPixmap(coordOver, 0, _cacheOver); p.setOpacity(shadow); st::slideShadow.fill(p, QRect(coordOver - st::slideShadow.width(), 0, st::slideShadow.width(), height())); } } QRect Widget::calculateStepRect() const { auto stepInnerTop = (height() - st::introHeight) / 2; accumulate_max(stepInnerTop, st::introStepTopMin); auto nextTop = stepInnerTop + st::introStepHeight; auto additionalHeight = st::introStepHeightAdd; auto stepWidth = width(); auto stepHeight = nextTop + additionalHeight; return QRect(0, 0, stepWidth, stepHeight); } void Widget::resizeEvent(QResizeEvent *e) { auto stepRect = calculateStepRect(); for_const (auto step, _stepHistory) { step->setGeometry(stepRect); } updateControlsGeometry(); } void Widget::updateControlsGeometry() { auto shown = _coverShownAnimation.current(1.); auto controlsTopTo = getStep()->hasCover() ? st::introCoverHeight : 0; auto controlsTop = anim::interpolate(_controlsTopFrom, controlsTopTo, shown); _settings->moveToRight(st::introSettingsSkip, controlsTop + st::introSettingsSkip); if (_update) { _update->moveToRight(st::introSettingsSkip + _settings->width() + st::introSettingsSkip, _settings->y()); } _back->moveToLeft(0, controlsTop); auto nextTopTo = getStep()->contentTop() + st::introStepHeight; auto nextTop = anim::interpolate(_nextTopFrom, nextTopTo, shown); _next->moveToLeft((width() - _next->width()) / 2, nextTop); if (_changeLanguage) { _changeLanguage->moveToLeft((width() - _changeLanguage->width()) / 2, _next->y() + _next->height() + _changeLanguage->height()); } if (_resetAccount) { _resetAccount->moveToLeft((width() - _resetAccount->width()) / 2, height() - st::introResetBottom - _resetAccount->height()); } } void Widget::keyPressEvent(QKeyEvent *e) { if (_a_show.animating() || getStep()->animating()) return; if (e->key() == Qt::Key_Escape) { if (getStep()->hasBack()) { historyMove(Direction::Back); } } else if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return || e->key() == Qt::Key_Space) { getStep()->submit(); } } Widget::~Widget() { for (auto step : base::take(_stepHistory)) { delete step; } if (App::wnd()) App::wnd()->noIntro(this); } QString Widget::Step::nextButtonText() const { return lang(lng_intro_next); } void Widget::Step::finish(const MTPUser &user, QImage &&photo) { if (user.type() != mtpc_user || !user.c_user().is_self()) { // No idea what to do here. // We could've reset intro and MTP, but this really should not happen. Ui::show(Box<InformBox>("Internal error: bad user.is_self() after sign in.")); return; } // Save the default language if we've suggested some other and user ignored it. auto currentId = Lang::Current().id(); auto defaultId = Lang::DefaultLanguageId(); auto suggestedId = Lang::CurrentCloudManager().suggestedLanguage(); if (currentId.isEmpty() && !suggestedId.isEmpty() && suggestedId != defaultId) { Lang::Current().switchToId(defaultId); Local::writeLangPack(); } Messenger::Instance().authSessionCreate(user.c_user().vid.v); Local::writeMtpData(); App::wnd()->setupMain(&user); // "this" is already deleted here by creating the main widget. if (auto user = App::self()) { Auth().api().requestFullPeer(user); } if (!photo.isNull()) { Messenger::Instance().uploadProfilePhoto( std::move(photo), Auth().userId()); } } void Widget::Step::paintEvent(QPaintEvent *e) { Painter p(this); paintAnimated(p, e->rect()); } void Widget::Step::resizeEvent(QResizeEvent *e) { updateLabelsPosition(); } void Widget::Step::updateLabelsPosition() { Ui::SendPendingMoveResizeEvents(_description->entity()); if (hasCover()) { _title->moveToLeft((width() - _title->width()) / 2, contentTop() + st::introCoverTitleTop); _description->moveToLeft((width() - _description->width()) / 2, contentTop() + st::introCoverDescriptionTop); } else { _title->moveToLeft(contentLeft() + st::buttonRadius, contentTop() + st::introTitleTop); _description->moveToLeft(contentLeft() + st::buttonRadius, contentTop() + st::introDescriptionTop); } if (_error) { if (_errorCentered) { _error->entity()->resizeToWidth(width()); } Ui::SendPendingMoveResizeEvents(_error->entity()); auto errorLeft = _errorCentered ? 0 : (contentLeft() + st::buttonRadius); auto errorTop = contentTop() + (_errorBelowLink ? st::introErrorBelowLinkTop : st::introErrorTop); _error->moveToLeft(errorLeft, errorTop); } } void Widget::Step::setTitleText(base::lambda<QString()> richTitleTextFactory) { _titleTextFactory = std::move(richTitleTextFactory); refreshTitle(); updateLabelsPosition(); } void Widget::Step::refreshTitle() { _title->setRichText(_titleTextFactory()); } void Widget::Step::setDescriptionText(base::lambda<QString()> richDescriptionTextFactory) { _descriptionTextFactory = std::move(richDescriptionTextFactory); refreshDescription(); updateLabelsPosition(); } void Widget::Step::refreshDescription() { _description->entity()->setRichText(_descriptionTextFactory()); } void Widget::Step::refreshLang() { refreshTitle(); refreshDescription(); refreshError(); updateLabelsPosition(); } void Widget::Step::showFinished() { _a_show.finish(); _coverAnimation = CoverAnimation(); _slideAnimation.reset(); prepareCoverMask(); activate(); } bool Widget::Step::paintAnimated(Painter &p, QRect clip) { if (_slideAnimation) { _slideAnimation->paintFrame(p, (width() - st::introStepWidth) / 2, contentTop(), width(), getms()); if (!_slideAnimation->animating()) { showFinished(); return false; } return true; } auto dt = _a_show.current(getms(), 1.); if (!_a_show.animating()) { if (hasCover()) { paintCover(p, 0); } if (_coverAnimation.title) { showFinished(); } if (!QRect(0, contentTop(), width(), st::introStepHeight).intersects(clip)) { return true; } return false; } auto progress = (hasCover() ? anim::easeOutCirc(1., dt) : anim::linear(1., dt)); auto arrivingAlpha = progress; auto departingAlpha = 1. - progress; auto showCoverMethod = progress; auto hideCoverMethod = progress; auto coverTop = (hasCover() ? anim::interpolate(-st::introCoverHeight, 0, showCoverMethod) : anim::interpolate(0, -st::introCoverHeight, hideCoverMethod)); paintCover(p, coverTop); auto positionReady = hasCover() ? showCoverMethod : hideCoverMethod; _coverAnimation.title->paintFrame(p, positionReady, departingAlpha, arrivingAlpha); _coverAnimation.description->paintFrame(p, positionReady, departingAlpha, arrivingAlpha); paintContentSnapshot(p, _coverAnimation.contentSnapshotWas, departingAlpha, showCoverMethod); paintContentSnapshot(p, _coverAnimation.contentSnapshotNow, arrivingAlpha, 1. - hideCoverMethod); return true; } void Widget::Step::fillSentCodeData(const MTPauth_SentCodeType &type) { switch (type.type()) { case mtpc_auth_sentCodeTypeApp: { getData()->codeByTelegram = true; getData()->codeLength = type.c_auth_sentCodeTypeApp().vlength.v; } break; case mtpc_auth_sentCodeTypeSms: { getData()->codeByTelegram = false; getData()->codeLength = type.c_auth_sentCodeTypeSms().vlength.v; } break; case mtpc_auth_sentCodeTypeCall: { getData()->codeByTelegram = false; getData()->codeLength = type.c_auth_sentCodeTypeCall().vlength.v; } break; case mtpc_auth_sentCodeTypeFlashCall: LOG(("Error: should not be flashcall!")); break; } } void Widget::Step::showDescription() { _description->show(anim::type::normal); } void Widget::Step::hideDescription() { _description->hide(anim::type::normal); } void Widget::Step::paintContentSnapshot(Painter &p, const QPixmap &snapshot, float64 alpha, float64 howMuchHidden) { if (!snapshot.isNull()) { auto contentTop = anim::interpolate(height() - (snapshot.height() / cIntRetinaFactor()), height(), howMuchHidden); if (contentTop < height()) { p.setOpacity(alpha); p.drawPixmap(QPoint(contentLeft(), contentTop), snapshot, QRect(0, 0, snapshot.width(), (height() - contentTop) * cIntRetinaFactor())); } } } void Widget::Step::prepareCoverMask() { if (!_coverMask.isNull()) return; auto maskWidth = cIntRetinaFactor(); auto maskHeight = st::introCoverHeight * cIntRetinaFactor(); auto mask = QImage(maskWidth, maskHeight, QImage::Format_ARGB32_Premultiplied); auto maskInts = reinterpret_cast<uint32*>(mask.bits()); Assert(mask.depth() == (sizeof(uint32) << 3)); auto maskIntsPerLineAdded = (mask.bytesPerLine() >> 2) - maskWidth; Assert(maskIntsPerLineAdded >= 0); auto realHeight = static_cast<float64>(maskHeight - 1); for (auto y = 0; y != maskHeight; ++y) { auto color = anim::color(st::introCoverTopBg, st::introCoverBottomBg, y / realHeight); auto colorInt = anim::getPremultiplied(color); for (auto x = 0; x != maskWidth; ++x) { *maskInts++ = colorInt; } maskInts += maskIntsPerLineAdded; } _coverMask = App::pixmapFromImageInPlace(std::move(mask)); } void Widget::Step::paintCover(Painter &p, int top) { auto coverHeight = top + st::introCoverHeight; if (coverHeight > 0) { p.drawPixmap(QRect(0, 0, width(), coverHeight), _coverMask, QRect(0, -top * cIntRetinaFactor(), _coverMask.width(), coverHeight * cIntRetinaFactor())); } auto left = 0; auto right = 0; if (width() < st::introCoverMaxWidth) { auto iconsMaxSkip = st::introCoverMaxWidth - st::introCoverLeft.width() - st::introCoverRight.width(); auto iconsSkip = st::introCoverIconsMinSkip + (iconsMaxSkip - st::introCoverIconsMinSkip) * (width() - st::introStepWidth) / (st::introCoverMaxWidth - st::introStepWidth); auto outside = iconsSkip + st::introCoverLeft.width() + st::introCoverRight.width() - width(); left = -outside / 2; right = -outside - left; } if (top < 0) { auto shown = float64(coverHeight) / st::introCoverHeight; auto leftShown = qRound(shown * (left + st::introCoverLeft.width())); left = leftShown - st::introCoverLeft.width(); auto rightShown = qRound(shown * (right + st::introCoverRight.width())); right = rightShown - st::introCoverRight.width(); } st::introCoverLeft.paint(p, left, coverHeight - st::introCoverLeft.height(), width()); st::introCoverRight.paint(p, width() - right - st::introCoverRight.width(), coverHeight - st::introCoverRight.height(), width()); auto planeLeft = (width() - st::introCoverIcon.width()) / 2 - st::introCoverIconLeft; auto planeTop = top + st::introCoverIconTop; if (top < 0 && !_hasCover) { auto deltaLeft = -qRound(float64(st::introPlaneWidth / st::introPlaneHeight) * top); // auto deltaTop = top; planeLeft += deltaLeft; // planeTop += top; } st::introCoverIcon.paint(p, planeLeft, planeTop, width()); } int Widget::Step::contentLeft() const { return (width() - st::introNextButton.width) / 2; } int Widget::Step::contentTop() const { auto result = height() - st::introStepHeight - st::introStepHeightAdd; if (_hasCover) { auto added = 1. - snap(float64(height() - st::windowMinHeight) / (st::introStepHeightFull - st::windowMinHeight), 0., 1.); result += qRound(added * st::introStepHeightAdd); } return result; } void Widget::Step::setErrorCentered(bool centered) { _errorCentered = centered; _error.destroy(); } void Widget::Step::setErrorBelowLink(bool below) { _errorBelowLink = below; if (_error) { updateLabelsPosition(); } } void Widget::Step::showError(base::lambda<QString()> textFactory) { _errorTextFactory = std::move(textFactory); refreshError(); updateLabelsPosition(); } void Widget::Step::refreshError() { if (!_errorTextFactory) { if (_error) _error->hide(anim::type::normal); } else { if (!_error) { _error.create( this, object_ptr<Ui::FlatLabel>( this, _errorCentered ? st::introErrorCentered : st::introError)); _error->hide(anim::type::instant); } _error->entity()->setText(_errorTextFactory()); updateLabelsPosition(); _error->show(anim::type::normal); } } Widget::Step::Step(QWidget *parent, Data *data, bool hasCover) : TWidget(parent) , _data(data) , _hasCover(hasCover) , _title(this, _hasCover ? st::introCoverTitle : st::introTitle) , _description( this, object_ptr<Ui::FlatLabel>( this, _hasCover ? st::introCoverDescription : st::introDescription)) { hide(); subscribe(Window::Theme::Background(), [this]( const Window::Theme::BackgroundUpdate &update) { if (update.paletteChanged()) { if (!_coverMask.isNull()) { _coverMask = QPixmap(); prepareCoverMask(); } } }); subscribe(Lang::Current().updated(), [this] { refreshLang(); }); } void Widget::Step::prepareShowAnimated(Step *after) { setInnerFocus(); if (hasCover() || after->hasCover()) { _coverAnimation = prepareCoverAnimation(after); prepareCoverMask(); } else { auto leftSnapshot = after->prepareSlideAnimation(); auto rightSnapshot = prepareSlideAnimation(); _slideAnimation = std::make_unique<Ui::SlideAnimation>(); _slideAnimation->setSnapshots(std::move(leftSnapshot), std::move(rightSnapshot)); _slideAnimation->setOverflowHidden(false); } } Widget::Step::CoverAnimation Widget::Step::prepareCoverAnimation(Step *after) { auto result = CoverAnimation(); result.title = Ui::FlatLabel::CrossFade( after->_title, _title, st::introBg); result.description = Ui::FlatLabel::CrossFade( after->_description->entity(), _description->entity(), st::introBg, after->_description->pos(), _description->pos()); result.contentSnapshotWas = after->prepareContentSnapshot(); result.contentSnapshotNow = prepareContentSnapshot(); return result; } QPixmap Widget::Step::prepareContentSnapshot() { auto otherTop = _description->y() + _description->height(); auto otherRect = myrtlrect(contentLeft(), otherTop, st::introStepWidth, height() - otherTop); return Ui::GrabWidget(this, otherRect); } QPixmap Widget::Step::prepareSlideAnimation() { auto grabLeft = (width() - st::introStepWidth) / 2; auto grabTop = contentTop(); return Ui::GrabWidget( this, QRect(grabLeft, grabTop, st::introStepWidth, st::introStepHeight)); } void Widget::Step::showAnimated(Direction direction) { setFocus(); show(); hideChildren(); if (_slideAnimation) { auto slideLeft = (direction == Direction::Back); _slideAnimation->start(slideLeft, [this] { update(0, contentTop(), width(), st::introStepHeight); }, st::introSlideDuration); } else { _a_show.start([this] { update(); }, 0., 1., st::introCoverDuration); } } void Widget::Step::setGoCallback(base::lambda<void(Step *step, Direction direction)> callback) { _goCallback = std::move(callback); } void Widget::Step::setShowResetCallback(base::lambda<void()> callback) { _showResetCallback = std::move(callback); } void Widget::Step::showFast() { show(); showFinished(); } bool Widget::Step::animating() const { return (_slideAnimation && _slideAnimation->animating()) || _a_show.animating(); } bool Widget::Step::hasCover() const { return _hasCover; } bool Widget::Step::hasBack() const { return false; } void Widget::Step::activate() { _title->show(); _description->show(anim::type::instant); if (_errorTextFactory) { _error->show(anim::type::instant); } } void Widget::Step::cancelled() { } void Widget::Step::finished() { hide(); } Widget::Step::CoverAnimation::~CoverAnimation() = default; Widget::Step::~Step() = default; } // namespace Intro ����������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/intro/introwidget.h��������������������������������������������0000664�0000000�0000000�00000013746�13262451251�0023301�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "mtproto/sender.h" namespace Ui { class IconButton; class RoundButton; class LinkButton; class SlideAnimation; class CrossFadeAnimation; class FlatLabel; template <typename Widget> class FadeWrap; } // namespace Ui namespace Intro { class Widget : public TWidget, private MTP::Sender, private base::Subscriber { Q_OBJECT public: Widget(QWidget *parent); void showAnimated(const QPixmap &bgAnimCache, bool back = false); void setInnerFocus(); ~Widget(); protected: void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; void keyPressEvent(QKeyEvent *e) override; signals: void countryChanged(); #ifndef TDESKTOP_DISABLE_AUTOUPDATE private slots: void onCheckUpdateStatus(); #endif // TDESKTOP_DISABLE_AUTOUPDATE // Internal interface. public: struct Data { QString country; QString phone; QByteArray phoneHash; bool phoneIsRegistered = false; enum class CallStatus { Waiting, Calling, Called, Disabled, }; CallStatus callStatus = CallStatus::Disabled; int callTimeout = 0; QString code; int codeLength = 5; bool codeByTelegram = false; QByteArray pwdSalt; bool hasRecovery = false; QString pwdHint; base::Observable<void> updated; }; enum class Direction { Back, Forward, Replace, }; class Step : public TWidget, public RPCSender, protected base::Subscriber { public: Step(QWidget *parent, Data *data, bool hasCover = false); virtual void setInnerFocus() { setFocus(); } void setGoCallback(base::lambda<void(Step *step, Direction direction)> callback); void setShowResetCallback(base::lambda<void()> callback); void prepareShowAnimated(Step *after); void showAnimated(Direction direction); void showFast(); bool animating() const; bool hasCover() const; virtual bool hasBack() const; virtual void activate(); virtual void cancelled(); virtual void finished(); virtual void submit() = 0; virtual QString nextButtonText() const; int contentLeft() const; int contentTop() const; void setErrorCentered(bool centered); void setErrorBelowLink(bool below); void showError(base::lambda<QString()> textFactory); void hideError() { showError(base::lambda<QString()>()); } ~Step(); protected: void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; void setTitleText(base::lambda<QString()> richTitleTextFactory); void setDescriptionText(base::lambda<QString()> richDescriptionTextFactory); bool paintAnimated(Painter &p, QRect clip); void fillSentCodeData(const MTPauth_SentCodeType &type); void showDescription(); void hideDescription(); Data *getData() const { return _data; } void finish(const MTPUser &user, QImage &&photo = QImage()); void goBack() { if (_goCallback) _goCallback(nullptr, Direction::Back); } void goNext(Step *step) { if (_goCallback) _goCallback(step, Direction::Forward); } void goReplace(Step *step) { if (_goCallback) _goCallback(step, Direction::Replace); } void showResetButton() { if (_showResetCallback) _showResetCallback(); } private: struct CoverAnimation { CoverAnimation() = default; CoverAnimation(CoverAnimation &&other) = default; CoverAnimation &operator=(CoverAnimation &&other) = default; ~CoverAnimation(); std::unique_ptr<Ui::CrossFadeAnimation> title; std::unique_ptr<Ui::CrossFadeAnimation> description; // From content top till the next button top. QPixmap contentSnapshotWas; QPixmap contentSnapshotNow; }; void updateLabelsPosition(); void paintContentSnapshot(Painter &p, const QPixmap &snapshot, float64 alpha, float64 howMuchHidden); void refreshError(); void refreshTitle(); void refreshDescription(); void refreshLang(); CoverAnimation prepareCoverAnimation(Step *step); QPixmap prepareContentSnapshot(); QPixmap prepareSlideAnimation(); void showFinished(); void prepareCoverMask(); void paintCover(Painter &p, int top); Data *_data = nullptr; bool _hasCover = false; base::lambda<void(Step *step, Direction direction)> _goCallback; base::lambda<void()> _showResetCallback; object_ptr<Ui::FlatLabel> _title; base::lambda<QString()> _titleTextFactory; object_ptr<Ui::FadeWrap<Ui::FlatLabel>> _description; base::lambda<QString()> _descriptionTextFactory; bool _errorCentered = false; bool _errorBelowLink = false; base::lambda<QString()> _errorTextFactory; object_ptr<Ui::FadeWrap<Ui::FlatLabel>> _error = { nullptr }; Animation _a_show; CoverAnimation _coverAnimation; std::unique_ptr<Ui::SlideAnimation> _slideAnimation; QPixmap _coverMask; }; private: void refreshLang(); void animationCallback(); void createLanguageLink(); void updateControlsGeometry(); Data *getData() { return &_data; } void fixOrder(); void showControls(); void hideControls(); QRect calculateStepRect() const; void showResetButton(); void resetAccount(); Step *getStep(int skip = 0) { Assert(_stepHistory.size() + skip > 0); return _stepHistory.at(_stepHistory.size() - skip - 1); } void historyMove(Direction direction); void moveToStep(Step *step, Direction direction); void appendStep(Step *step); void getNearestDC(); Animation _a_show; bool _showBack = false; QPixmap _cacheUnder, _cacheOver; QVector<Step*> _stepHistory; Data _data; Animation _coverShownAnimation; int _nextTopFrom = 0; int _controlsTopFrom = 0; object_ptr<Ui::FadeWrap<Ui::IconButton>> _back; object_ptr<Ui::FadeWrap<Ui::RoundButton>> _update = { nullptr }; object_ptr<Ui::FadeWrap<Ui::RoundButton>> _settings; object_ptr<Ui::RoundButton> _next; object_ptr<Ui::FadeWrap<Ui::LinkButton>> _changeLanguage = { nullptr }; object_ptr<Ui::FadeWrap<Ui::RoundButton>> _resetAccount = { nullptr }; mtpRequestId _resetRequest = 0; }; } // namespace Intro ��������������������������tdesktop-1.2.17/Telegram/SourceFiles/lang/����������������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0020344�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/lang/lang_cloud_manager.cpp������������������������������������0000664�0000000�0000000�00000021622�13262451251�0024654�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "lang/lang_cloud_manager.h" #include "lang/lang_instance.h" #include "mtproto/mtp_instance.h" #include "storage/localstorage.h" #include "messenger.h" #include "apiwrap.h" #include "auth_session.h" #include "boxes/confirm_box.h" #include "lang/lang_file_parser.h" #include "core/file_utilities.h" namespace Lang { CloudManager::CloudManager(Instance &langpack, not_null<MTP::Instance*> mtproto) : MTP::Sender() , _langpack(langpack) { requestLangPackDifference(); } void CloudManager::requestLangPackDifference() { request(_langPackRequestId).cancel(); if (_langpack.isCustom()) { return; } auto version = _langpack.version(); if (version > 0) { _langPackRequestId = request(MTPlangpack_GetDifference(MTP_int(version))).done([this](const MTPLangPackDifference &result) { _langPackRequestId = 0; applyLangPackDifference(result); }).fail([this](const RPCError &error) { _langPackRequestId = 0; }).send(); } else { _langPackRequestId = request(MTPlangpack_GetLangPack(MTP_string(_langpack.cloudLangCode()))).done([this](const MTPLangPackDifference &result) { _langPackRequestId = 0; applyLangPackDifference(result); }).fail([this](const RPCError &error) { _langPackRequestId = 0; }).send(); } } void CloudManager::setSuggestedLanguage(const QString &langCode) { if (!langCode.isEmpty() && langCode != Lang::DefaultLanguageId()) { _suggestedLanguage = langCode; } else { _suggestedLanguage = QString(); } if (!_languageWasSuggested) { _languageWasSuggested = true; _firstLanguageSuggestion.notify(); if (AuthSession::Exists() && _langpack.id().isEmpty() && !_suggestedLanguage.isEmpty()) { auto isLegacy = [](const QString &languageId) { for (auto &legacyString : kLegacyLanguages) { auto legacyId = str_const_toString(legacyString); if (ConvertLegacyLanguageId(legacyId) == languageId) { return true; } } return false; }; // The old available languages (de/it/nl/ko/es/pt_BR) won't be // suggested anyway, because everyone saw the suggestion in intro. if (!isLegacy(_suggestedLanguage)) { _offerSwitchToId = _suggestedLanguage; offerSwitchLangPack(); } } } } void CloudManager::applyLangPackDifference(const MTPLangPackDifference &difference) { Expects(difference.type() == mtpc_langPackDifference); if (_langpack.isCustom()) { return; } auto &langpack = difference.c_langPackDifference(); auto langpackId = qs(langpack.vlang_code); if (needToApplyLangPack(langpackId)) { applyLangPackData(langpack); if (_restartAfterSwitch) { App::restart(); } } else { LOG(("Lang Warning: Ignoring update for '%1' because our language is '%2'").arg(langpackId).arg(_langpack.id())); } } void CloudManager::requestLanguageList() { _languagesRequestId = request(MTPlangpack_GetLanguages()).done([this](const MTPVector<MTPLangPackLanguage> &result) { auto languages = Languages(); for_const (auto &langData, result.v) { Assert(langData.type() == mtpc_langPackLanguage); auto &language = langData.c_langPackLanguage(); languages.push_back({ qs(language.vlang_code), qs(language.vname), qs(language.vnative_name) }); } if (_languages != languages) { _languages = languages; _languagesChanged.notify(); } _languagesRequestId = 0; }).fail([this](const RPCError &error) { _languagesRequestId = 0; }).send(); } bool CloudManager::needToApplyLangPack(const QString &id) { auto currentId = _langpack.id(); if (currentId == id) { return true; } else if (currentId.isEmpty() && id == DefaultLanguageId()) { return true; } return false; } void CloudManager::offerSwitchLangPack() { Expects(!_offerSwitchToId.isEmpty()); Expects(_offerSwitchToId != DefaultLanguageId()); if (!showOfferSwitchBox()) { subscribe(languageListChanged(), [this] { showOfferSwitchBox(); }); requestLanguageList(); } } QString CloudManager::findOfferedLanguageName() { for_const (auto &language, _languages) { if (language.id == _offerSwitchToId) { return language.name; } } return QString(); } bool CloudManager::showOfferSwitchBox() { auto name = findOfferedLanguageName(); if (name.isEmpty()) { return false; } Ui::show(Box<ConfirmBox>("Do you want to switch your language to " + name + "? You can always change your language in Settings.", "Change", lang(lng_cancel), [this] { Ui::hideLayer(); if (_offerSwitchToId.isEmpty()) { return; } performSwitchAndRestart(_offerSwitchToId); }, [this] { Ui::hideLayer(); changeIdAndReInitConnection(DefaultLanguageId()); Local::writeLangPack(); }), LayerOption::KeepOther); return true; } void CloudManager::applyLangPackData(const MTPDlangPackDifference &data) { switchLangPackId(qs(data.vlang_code)); if (_langpack.version() < data.vfrom_version.v) { requestLangPackDifference(); } else if (!data.vstrings.v.isEmpty()) { _langpack.applyDifference(data); Local::writeLangPack(); } else { LOG(("Lang Info: Up to date.")); } } bool CloudManager::canApplyWithoutRestart(const QString &id) const { if (id == qstr("TEST_X") || id == qstr("TEST_0")) { return true; } // We don't support instant language switch if the auth session exists :( return !AuthSession::Exists(); } void CloudManager::resetToDefault() { performSwitch(DefaultLanguageId()); } void CloudManager::switchToLanguage(QString id) { if (id.isEmpty()) { id = DefaultLanguageId(); } if (_langpack.id() == id && id != qstr("custom")) { return; } request(_switchingToLanguageRequest).cancel(); if (id == qstr("custom")) { performSwitchToCustom(); } else if (canApplyWithoutRestart(id)) { performSwitch(id); } else { QVector<MTPstring> keys; keys.reserve(3); keys.push_back(MTP_string("lng_sure_save_language")); keys.push_back(MTP_string("lng_box_ok")); keys.push_back(MTP_string("lng_cancel")); _switchingToLanguageRequest = request(MTPlangpack_GetStrings(MTP_string(id), MTP_vector<MTPstring>(std::move(keys)))).done([this, id](const MTPVector<MTPLangPackString> &result) { auto values = Instance::ParseStrings(result); auto getValue = [&values](LangKey key) { auto it = values.find(key); return (it == values.cend()) ? GetOriginalValue(key) : it->second; }; auto text = getValue(lng_sure_save_language); auto save = getValue(lng_box_ok); auto cancel = getValue(lng_cancel); Ui::show(Box<ConfirmBox>(text, save, cancel, [this, id] { performSwitchAndRestart(id); }), LayerOption::KeepOther); }).send(); } } void CloudManager::performSwitchToCustom() { auto filter = qsl("Language files (*.strings)"); auto title = qsl("Choose language .strings file"); FileDialog::GetOpenPath(title, filter, [weak = base::make_weak(this)](const FileDialog::OpenResult &result) { if (!weak || result.paths.isEmpty()) { return; } auto filePath = result.paths.front(); Lang::FileParser loader(filePath, { lng_sure_save_language, lng_box_ok, lng_cancel }); if (loader.errors().isEmpty()) { weak->request(weak->_switchingToLanguageRequest).cancel(); if (weak->canApplyWithoutRestart(qsl("custom"))) { weak->_langpack.switchToCustomFile(filePath); } else { auto values = loader.found(); auto getValue = [&values](LangKey key) { auto it = values.find(key); return (it == values.cend()) ? GetOriginalValue(key) : it.value(); }; auto text = getValue(lng_sure_save_language); auto save = getValue(lng_box_ok); auto cancel = getValue(lng_cancel); Ui::show(Box<ConfirmBox>(text, save, cancel, [weak, filePath] { weak->_langpack.switchToCustomFile(filePath); App::restart(); }), LayerOption::KeepOther); } } else { Ui::show( Box<InformBox>("Custom lang failed :(\n\nError: " + loader.errors()), LayerOption::KeepOther); } }); } void CloudManager::switchToTestLanguage() { auto testLanguageId = (_langpack.id() == qstr("TEST_X")) ? qsl("TEST_0") : qsl("TEST_X"); performSwitch(testLanguageId); } void CloudManager::performSwitch(const QString &id) { _restartAfterSwitch = false; switchLangPackId(id); requestLangPackDifference(); } void CloudManager::performSwitchAndRestart(const QString &id) { performSwitch(id); if (_langPackRequestId) { _restartAfterSwitch = true; } else { App::restart(); } } void CloudManager::switchLangPackId(const QString &id) { auto currentId = _langpack.id(); auto notChanged = (currentId == id) || (currentId.isEmpty() && id == DefaultLanguageId()); if (!notChanged) { changeIdAndReInitConnection(id); } } void CloudManager::changeIdAndReInitConnection(const QString &id) { _langpack.switchToId(id); auto mtproto = requestMTP(); mtproto->reInitConnection(mtproto->mainDcId()); } CloudManager &CurrentCloudManager() { auto result = Messenger::Instance().langCloudManager(); Assert(result != nullptr); return *result; } } // namespace Lang ��������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/lang/lang_cloud_manager.h��������������������������������������0000664�0000000�0000000�00000004631�13262451251�0024322�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "mtproto/sender.h" #include "base/weak_ptr.h" namespace MTP { class Instance; } // namespace MTP namespace Lang { class Instance; class CloudManager : public base::has_weak_ptr, private MTP::Sender, private base::Subscriber { public: CloudManager(Instance &langpack, not_null<MTP::Instance*> mtproto); struct Language { QString id; QString name; QString nativeName; }; using Languages = QVector<Language>; void requestLanguageList(); Languages languageList() const { return _languages; } base::Observable<void> &languageListChanged() { return _languagesChanged; } void requestLangPackDifference(); void applyLangPackDifference(const MTPLangPackDifference &difference); void resetToDefault(); void switchToLanguage(QString id); void switchToTestLanguage(); void setSuggestedLanguage(const QString &langCode); QString suggestedLanguage() const { return _suggestedLanguage; } base::Observable<void> &firstLanguageSuggestion() { return _firstLanguageSuggestion; } private: bool canApplyWithoutRestart(const QString &id) const; void performSwitchToCustom(); void performSwitch(const QString &id); void performSwitchAndRestart(const QString &id); void offerSwitchLangPack(); bool showOfferSwitchBox(); QString findOfferedLanguageName(); bool needToApplyLangPack(const QString &id); void applyLangPackData(const MTPDlangPackDifference &data); void switchLangPackId(const QString &id); void changeIdAndReInitConnection(const QString &id); Instance &_langpack; Languages _languages; base::Observable<void> _languagesChanged; mtpRequestId _langPackRequestId = 0; mtpRequestId _languagesRequestId = 0; QString _offerSwitchToId; bool _restartAfterSwitch = false; QString _suggestedLanguage; bool _languageWasSuggested = false; base::Observable<void> _firstLanguageSuggestion; mtpRequestId _switchingToLanguageRequest = 0; }; inline bool operator==(const CloudManager::Language &a, const CloudManager::Language &b) { return (a.id == b.id) && (a.name == b.name); } inline bool operator!=(const CloudManager::Language &a, const CloudManager::Language &b) { return !(a == b); } CloudManager &CurrentCloudManager(); } // namespace Lang �������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/lang/lang_file_parser.cpp��������������������������������������0000664�0000000�0000000�00000013211�13262451251�0024342�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "lang/lang_file_parser.h" #include "base/parse_helper.h" namespace Lang { namespace { constexpr auto kLangFileLimit = 1024 * 1024; } // namespace FileParser::FileParser(const QString &file, const std::set<LangKey> &request) : _content(base::parse::stripComments(ReadFile(file, file))) , _request(request) { parse(); } FileParser::FileParser(const QByteArray &content, base::lambda<void(QLatin1String key, const QByteArray &value)> callback) : _content(base::parse::stripComments(content)) , _callback(std::move(callback)) { parse(); } void FileParser::parse() { if (_content.isEmpty()) { error(qsl("Got empty lang file content")); return; } auto text = _content.constData(), end = text + _content.size(); while (text != end) { if (!readKeyValue(text, end)) { break; } } } const QString &FileParser::errors() const { if (_errors.isEmpty() && !_errorsList.isEmpty()) { _errors = _errorsList.join('\n'); } return _errors; } const QString &FileParser::warnings() const { if (_warnings.isEmpty() && !_warningsList.isEmpty()) { _warnings = _warningsList.join('\n'); } return _warnings; } bool FileParser::readKeyValue(const char *&from, const char *end) { using base::parse::skipWhitespaces; if (!skipWhitespaces(from, end)) return false; if (*from != '"') { return error("Expected quote before key name!"); } ++from; const char *nameStart = from; while (from < end && ((*from >= 'a' && *from <= 'z') || (*from >= 'A' && *from <= 'Z') || *from == '_' || (*from >= '0' && *from <= '9') || *from == '#')) { ++from; } auto key = QLatin1String(nameStart, from - nameStart); if (from == end || *from != '"') { return error(qsl("Expected quote after key name '%1'!").arg(key)); } ++from; if (!skipWhitespaces(from, end)) { return error(qsl("Unexpected end of file in key '%1'!").arg(key)); } if (*from != '=') { return error(qsl("'=' expected in key '%1'!").arg(key)); } if (!skipWhitespaces(++from, end)) { return error(qsl("Unexpected end of file in key '%1'!").arg(key)); } if (*from != '"') { return error(qsl("Expected string after '=' in key '%1'!").arg(key)); } auto skipping = false; auto keyIndex = kLangKeysCount; if (!_callback) { keyIndex = GetKeyIndex(key); skipping = (_request.find(keyIndex) == _request.end()); } auto value = QByteArray(); auto appendValue = [&value, skipping](auto&&... args) { if (!skipping) { value.append(std::forward<decltype(args)>(args)...); } }; const char *start = ++from; while (from < end && *from != '"') { if (*from == '\n') { return error(qsl("Unexpected end of string in key '%1'!").arg(key)); } if (*from == '\\') { if (from + 1 >= end) { return error(qsl("Unexpected end of file in key '%1'!").arg(key)); } if (*(from + 1) == '"' || *(from + 1) == '\\') { if (from > start) appendValue(start, from - start); start = ++from; } else if (*(from + 1) == 'n') { if (from > start) appendValue(start, from - start); appendValue('\n'); start = (++from) + 1; } } ++from; } if (from >= end) { return error(qsl("Unexpected end of file in key '%1'!").arg(key)); } if (from > start) { appendValue(start, from - start); } if (!skipWhitespaces(++from, end)) { return error(qsl("Unexpected end of file in key '%1'!").arg(key)); } if (*from != ';') { return error(qsl("';' expected after \"value\" in key '%1'!").arg(key)); } skipWhitespaces(++from, end); if (_callback) { _callback(key, value); } else if (!skipping) { _result.insert(keyIndex, QString::fromUtf8(value)); } return true; } QByteArray FileParser::ReadFile(const QString &absolutePath, const QString &relativePath) { QFile file(QFileInfo(relativePath).exists() ? relativePath : absolutePath); if (!file.open(QIODevice::ReadOnly)) { LOG(("Lang Error: Could not open file at '%1' ('%2')").arg(relativePath).arg(absolutePath)); return QByteArray(); } if (file.size() > kLangFileLimit) { LOG(("Lang Error: File is too big: %1").arg(file.size())); return QByteArray(); } constexpr auto kCodecMagicSize = 3; auto codecMagic = file.read(kCodecMagicSize); if (codecMagic.size() < kCodecMagicSize) { LOG(("Lang Error: Found bad file at '%1' ('%2')").arg(relativePath).arg(absolutePath)); return QByteArray(); } file.seek(0); QByteArray data; int skip = 0; auto readUtf16Stream = [relativePath, absolutePath](auto &&stream) { stream.setCodec("UTF-16"); auto string = stream.readAll(); if (stream.status() != QTextStream::Ok) { LOG(("Lang Error: Could not read UTF-16 data from '%1' ('%2')").arg(relativePath).arg(absolutePath)); return QByteArray(); } if (string.isEmpty()) { LOG(("Lang Error: Empty UTF-16 content in '%1' ('%2')").arg(relativePath).arg(absolutePath)); return QByteArray(); } return string.toUtf8(); }; if ((codecMagic.at(0) == '\xFF' && codecMagic.at(1) == '\xFE') || (codecMagic.at(0) == '\xFE' && codecMagic.at(1) == '\xFF') || (codecMagic.at(1) == 0)) { return readUtf16Stream(QTextStream(&file)); } else if (codecMagic.at(0) == 0) { auto utf16WithBOM = "\xFE\xFF" + file.readAll(); return readUtf16Stream(QTextStream(utf16WithBOM)); } data = file.readAll(); if (codecMagic.at(0) == '\xEF' && codecMagic.at(1) == '\xBB' && codecMagic.at(2) == '\xBF') { data = data.mid(3); // skip UTF-8 BOM } if (data.isEmpty()) { LOG(("Lang Error: Empty UTF-8 content in '%1' ('%2')").arg(relativePath).arg(absolutePath)); return QByteArray(); } return data; } } // namespace Lang ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/lang/lang_file_parser.h����������������������������������������0000664�0000000�0000000�00000002362�13262451251�0024014�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "lang/lang_keys.h" namespace Lang { class FileParser { public: using Result = QMap<LangKey, QString>; FileParser(const QString &file, const std::set<LangKey> &request); FileParser(const QByteArray &content, base::lambda<void(QLatin1String key, const QByteArray &value)> callback); static QByteArray ReadFile(const QString &absolutePath, const QString &relativePath); const QString &errors() const; const QString &warnings() const; Result found() const { return _result; } private: void parse(); bool error(const QString &text) { _errorsList.push_back(text); return false; } void warning(const QString &text) { _warningsList.push_back(text); } bool readKeyValue(const char *&from, const char *end); mutable QStringList _errorsList, _warningsList; mutable QString _errors, _warnings; const QByteArray _content; const std::set<LangKey> _request; const base::lambda<void(QLatin1String key, const QByteArray &value)> _callback; Result _result; }; } // namespace Lang ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/lang/lang_hardcoded.h������������������������������������������0000664�0000000�0000000�00000001302�13262451251�0023427�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace Lang { namespace Hard { inline QString FavedSetTitle() { return qsl("Favorite stickers"); } inline QString CallErrorIncompatible() { return qsl("{user}'s app is using an incompatible protocol. They need to update their app before you can call them."); } inline QString ServerError() { return qsl("Internal server error."); } inline QString ClearPathFailed() { return qsl("Clear failed :("); } } // namespace Hard } // namespace Lang ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/lang/lang_instance.cpp�����������������������������������������0000664�0000000�0000000�00000034561�13262451251�0023666�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "lang/lang_instance.h" #include "messenger.h" #include "storage/serialize_common.h" #include "storage/localstorage.h" #include "platform/platform_specific.h" #include "boxes/confirm_box.h" #include "lang/lang_file_parser.h" #include "base/qthelp_regex.h" namespace Lang { namespace { constexpr auto kDefaultLanguage = str_const("en"); constexpr auto kLangValuesLimit = 20000; class ValueParser { public: ValueParser(const QByteArray &key, LangKey keyIndex, const QByteArray &value); QString takeResult() { Expects(!_failed); return std::move(_result); } bool parse(); private: void appendToResult(const char *nextBegin); bool logError(const QString &text); bool readTag(); const QByteArray &_key; LangKey _keyIndex = kLangKeysCount; QLatin1String _currentTag; ushort _currentTagIndex = 0; QString _currentTagReplacer; bool _failed = true; const char *_begin = nullptr; const char *_ch = nullptr; const char *_end = nullptr; QString _result; OrderedSet<ushort> _tagsUsed; }; ValueParser::ValueParser(const QByteArray &key, LangKey keyIndex, const QByteArray &value) : _key(key) , _keyIndex(keyIndex) , _currentTag("") , _begin(value.constData()) , _ch(_begin) , _end(_begin + value.size()) { } void ValueParser::appendToResult(const char *nextBegin) { if (_ch > _begin) _result.append(QString::fromUtf8(_begin, _ch - _begin)); _begin = nextBegin; } bool ValueParser::logError(const QString &text) { _failed = true; auto loggedKey = (_currentTag.size() > 0) ? (_key + QString(':') + _currentTag) : QString(_key); LOG(("Lang Error: %1 (key '%2')").arg(text).arg(loggedKey)); return false; } bool ValueParser::readTag() { auto tagStart = _ch; auto isTagChar = [](QChar ch) { if (ch >= 'a' && ch <= 'z') { return true; } else if (ch >= 'A' && ch <= 'z') { return true; } else if (ch >= '0' && ch <= '9') { return true; } return (ch == '_'); }; while (_ch != _end && isTagChar(*_ch)) { ++_ch; } if (_ch == tagStart) { return logError("Expected tag name"); } _currentTag = QLatin1String(tagStart, _ch - tagStart); if (_ch == _end || *_ch != '}') { return logError("Expected '}' after tag name"); } _currentTagIndex = GetTagIndex(_currentTag); if (_currentTagIndex == kTagsCount) { return logError("Unknown tag"); } if (!IsTagReplaced(_keyIndex, _currentTagIndex)) { return logError("Unexpected tag"); } if (_tagsUsed.contains(_currentTagIndex)) { return logError("Repeated tag"); } _tagsUsed.insert(_currentTagIndex); if (_currentTagReplacer.isEmpty()) { _currentTagReplacer = QString(4, TextCommand); _currentTagReplacer[1] = TextCommandLangTag; } _currentTagReplacer[2] = QChar(0x0020 + _currentTagIndex); return true; } bool ValueParser::parse() { _failed = false; _result.reserve(_end - _begin); for (; _ch != _end; ++_ch) { if (*_ch == '{') { appendToResult(_ch); ++_ch; if (!readTag()) { return false; } _result.append(_currentTagReplacer); _begin = _ch + 1; _currentTag = QLatin1String(""); } } appendToResult(_end); return true; } QString PrepareTestValue(const QString ¤t, QChar filler) { auto size = current.size(); auto result = QString(size + 1, filler); auto inCommand = false; for (auto i = 0; i != size; ++i) { auto ch = current[i]; auto newInCommand = (ch.unicode() == TextCommand) ? (!inCommand) : inCommand; if (inCommand || newInCommand || ch.isSpace()) { result[i + 1] = ch; } inCommand = newInCommand; } return result; } } // namespace QString DefaultLanguageId() { return str_const_toString(kDefaultLanguage); } void Instance::switchToId(const QString &id) { reset(); _id = id; if (_id == qstr("TEST_X") || _id == qstr("TEST_0")) { for (auto &value : _values) { value = PrepareTestValue(value, _id[5]); } _updated.notify(); } updatePluralRules(); } void Instance::switchToCustomFile(const QString &filePath) { reset(); fillFromCustomFile(filePath); Local::writeLangPack(); _updated.notify(); } void Instance::reset() { _values.clear(); _nonDefaultValues.clear(); _nonDefaultSet.clear(); _legacyId = kLegacyLanguageNone; _customFilePathAbsolute = QString(); _customFilePathRelative = QString(); _customFileContent = QByteArray(); _version = 0; fillDefaults(); } void Instance::fillDefaults() { Expects(_values.empty()); _values.reserve(kLangKeysCount); for (auto i = 0; i != kLangKeysCount; ++i) { _values.emplace_back(GetOriginalValue(LangKey(i))); } _nonDefaultSet = std::vector<uchar>(kLangKeysCount, 0); } QString Instance::systemLangCode() const { if (_systemLanguage.isEmpty()) { _systemLanguage = Platform::SystemLanguage(); if (_systemLanguage.isEmpty()) { auto uiLanguages = QLocale::system().uiLanguages(); if (!uiLanguages.isEmpty()) { _systemLanguage = uiLanguages.front(); } if (_systemLanguage.isEmpty()) { _systemLanguage = DefaultLanguageId(); } } } return _systemLanguage; } QString Instance::cloudLangCode() const { if (isCustom() || id().isEmpty()) { return DefaultLanguageId(); } return id(); } QByteArray Instance::serialize() const { auto size = Serialize::stringSize(_id); size += sizeof(qint32); // version size += Serialize::stringSize(_customFilePathAbsolute) + Serialize::stringSize(_customFilePathRelative); size += Serialize::bytearraySize(_customFileContent); size += sizeof(qint32); // _nonDefaultValues.size() for (auto &nonDefault : _nonDefaultValues) { size += Serialize::bytearraySize(nonDefault.first) + Serialize::bytearraySize(nonDefault.second); } auto result = QByteArray(); result.reserve(size); { QDataStream stream(&result, QIODevice::WriteOnly); stream.setVersion(QDataStream::Qt_5_1); stream << _id << qint32(_version); stream << _customFilePathAbsolute << _customFilePathRelative << _customFileContent; stream << qint32(_nonDefaultValues.size()); for (auto &nonDefault : _nonDefaultValues) { stream << nonDefault.first << nonDefault.second; } } return result; } void Instance::fillFromSerialized(const QByteArray &data) { QDataStream stream(data); stream.setVersion(QDataStream::Qt_5_1); QString id; qint32 version = 0; QString customFilePathAbsolute, customFilePathRelative; QByteArray customFileContent; qint32 nonDefaultValuesCount = 0; stream >> id >> version; stream >> customFilePathAbsolute >> customFilePathRelative >> customFileContent; stream >> nonDefaultValuesCount; if (stream.status() != QDataStream::Ok) { LOG(("Lang Error: Could not read data from serialized langpack.")); return; } if (nonDefaultValuesCount > kLangValuesLimit) { LOG(("Lang Error: Values count limit exceeded: %1").arg(nonDefaultValuesCount)); return; } if (!customFilePathAbsolute.isEmpty()) { auto currentCustomFileContent = Lang::FileParser::ReadFile(customFilePathAbsolute, customFilePathRelative); if (!currentCustomFileContent.isEmpty() && currentCustomFileContent != customFileContent) { loadFromCustomContent(customFilePathAbsolute, customFilePathRelative, currentCustomFileContent); Local::writeLangPack(); return; } } std::vector<QByteArray> nonDefaultStrings; nonDefaultStrings.reserve(2 * nonDefaultValuesCount); for (auto i = 0; i != nonDefaultValuesCount; ++i) { QByteArray key, value; stream >> key >> value; if (stream.status() != QDataStream::Ok) { LOG(("Lang Error: Could not read data from serialized langpack.")); return; } nonDefaultStrings.push_back(key); nonDefaultStrings.push_back(value); } _id = id; _version = version; _customFilePathAbsolute = customFilePathAbsolute; _customFilePathRelative = customFilePathRelative; _customFileContent = customFileContent; LOG(("Lang Info: Loaded cached, keys: %1").arg(nonDefaultValuesCount)); for (auto i = 0, count = nonDefaultValuesCount * 2; i != count; i += 2) { applyValue(nonDefaultStrings[i], nonDefaultStrings[i + 1]); } updatePluralRules(); } void Instance::loadFromContent(const QByteArray &content) { Lang::FileParser loader(content, [this](QLatin1String key, const QByteArray &value) { applyValue(QByteArray(key.data(), key.size()), value); }); if (!loader.errors().isEmpty()) { LOG(("Lang load errors: %1").arg(loader.errors())); } else if (!loader.warnings().isEmpty()) { LOG(("Lang load warnings: %1").arg(loader.warnings())); } } void Instance::loadFromCustomContent(const QString &absolutePath, const QString &relativePath, const QByteArray &content) { _id = qsl("custom"); _version = 0; _customFilePathAbsolute = absolutePath; _customFilePathRelative = relativePath; _customFileContent = content; loadFromContent(_customFileContent); } void Instance::fillFromCustomFile(const QString &filePath) { auto absolutePath = QFileInfo(filePath).absoluteFilePath(); auto relativePath = QDir().relativeFilePath(filePath); auto content = Lang::FileParser::ReadFile(absolutePath, relativePath); if (!content.isEmpty()) { loadFromCustomContent(absolutePath, relativePath, content); updatePluralRules(); } } void Instance::fillFromLegacy(int legacyId, const QString &legacyPath) { if (legacyId == kLegacyDefaultLanguage) { _legacyId = legacyId; // We suppose that user didn't switch to the default language, // so we will suggest him to switch to his language if we get it. // // The old available languages (de/it/nl/ko/es/pt_BR) won't be // suggested anyway, because everyone saw the suggestion in intro. _id = QString();// str_const_toString(kLegacyLanguages[legacyId]); } else if (legacyId == kLegacyCustomLanguage) { auto absolutePath = QFileInfo(legacyPath).absoluteFilePath(); auto relativePath = QDir().relativeFilePath(absolutePath); auto content = Lang::FileParser::ReadFile(absolutePath, relativePath); if (!content.isEmpty()) { _legacyId = legacyId; loadFromCustomContent(absolutePath, relativePath, content); } } else if (legacyId > kLegacyDefaultLanguage && legacyId < base::array_size(kLegacyLanguages)) { auto languageId = str_const_toString(kLegacyLanguages[legacyId]); auto resourcePath = qsl(":/langs/lang_") + languageId + qsl(".strings"); auto content = Lang::FileParser::ReadFile(resourcePath, resourcePath); if (!content.isEmpty()) { _legacyId = legacyId; _id = languageId; _version = 0; loadFromContent(content); } } _id = ConvertLegacyLanguageId(_id); updatePluralRules(); } template <typename SetCallback, typename ResetCallback> void Instance::HandleString(const MTPLangPackString &mtpString, SetCallback setCallback, ResetCallback resetCallback) { switch (mtpString.type()) { case mtpc_langPackString: { auto &string = mtpString.c_langPackString(); setCallback(qba(string.vkey), qba(string.vvalue)); } break; case mtpc_langPackStringPluralized: { auto &string = mtpString.c_langPackStringPluralized(); auto key = qba(string.vkey); setCallback(key + "#zero", string.has_zero_value() ? qba(string.vzero_value) : QByteArray()); setCallback(key + "#one", string.has_one_value() ? qba(string.vone_value) : QByteArray()); setCallback(key + "#two", string.has_two_value() ? qba(string.vtwo_value) : QByteArray()); setCallback(key + "#few", string.has_few_value() ? qba(string.vfew_value) : QByteArray()); setCallback(key + "#many", string.has_many_value() ? qba(string.vmany_value) : QByteArray()); setCallback(key + "#other", qba(string.vother_value)); } break; case mtpc_langPackStringDeleted: { auto &string = mtpString.c_langPackStringDeleted(); auto key = qba(string.vkey); resetCallback(key); for (auto plural : { "#zero", "#one", "#two", "#few", "#many", "#other" }) { resetCallback(key + plural); } } break; default: Unexpected("LangPack string type in applyUpdate()."); } } void Instance::applyDifference(const MTPDlangPackDifference &difference) { auto updateLanguageId = qs(difference.vlang_code); auto isValidUpdate = (updateLanguageId == _id) || (_id.isEmpty() && updateLanguageId == DefaultLanguageId()); Expects(isValidUpdate); Expects(difference.vfrom_version.v <= _version); _version = difference.vversion.v; for_const (auto &mtpString, difference.vstrings.v) { HandleString(mtpString, [this](auto &&key, auto &&value) { applyValue(key, value); }, [this](auto &&key) { resetValue(key); }); } _updated.notify(); } std::map<LangKey, QString> Instance::ParseStrings(const MTPVector<MTPLangPackString> &strings) { auto result = std::map<LangKey, QString>(); for (auto &mtpString : strings.v) { HandleString(mtpString, [&result](auto &&key, auto &&value) { ParseKeyValue(key, value, result); }, [&result](auto &&key) { auto keyIndex = GetKeyIndex(QLatin1String(key)); if (keyIndex != kLangKeysCount) { result.erase(keyIndex); } }); } return result; } template <typename Result> LangKey Instance::ParseKeyValue(const QByteArray &key, const QByteArray &value, Result &result) { auto keyIndex = GetKeyIndex(QLatin1String(key)); if (keyIndex == kLangKeysCount) { LOG(("Lang Error: Unknown key '%1'").arg(QString::fromLatin1(key))); return kLangKeysCount; } ValueParser parser(key, keyIndex, value); if (parser.parse()) { result[keyIndex] = parser.takeResult(); return keyIndex; } return kLangKeysCount; } void Instance::applyValue(const QByteArray &key, const QByteArray &value) { _nonDefaultValues[key] = value; auto index = ParseKeyValue(key, value, _values); if (index != kLangKeysCount) { _nonDefaultSet[index] = 1; } } void Instance::updatePluralRules() { auto id = _id; if (isCustom()) { auto path = _customFilePathAbsolute.isEmpty() ? _customFilePathRelative : _customFilePathAbsolute; auto name = QFileInfo(path).fileName(); if (auto match = qthelp::regex_match("_([a-z]{2,3})(_[A-Z]{2,3}|\\-[a-z]{2,3})?\\.", name)) { id = match->captured(1); } } else if (auto match = qthelp::regex_match("^([a-z]{2,3})(_[A-Z]{2,3}|\\-[a-z]{2,3})$", id)) { id = match->captured(1); } UpdatePluralRules(id); } void Instance::resetValue(const QByteArray &key) { _nonDefaultValues.erase(key); auto keyIndex = GetKeyIndex(QLatin1String(key)); if (keyIndex != kLangKeysCount) { _values[keyIndex] = GetOriginalValue(keyIndex); } } Instance &Current() { return Messenger::Instance().langpack(); } rpl::producer<QString> Viewer(LangKey key) { return rpl::single( Current().getValue(key) ) | then(base::ObservableViewer( Current().updated() ) | rpl::map([=] { return Current().getValue(key); })); } } // namespace Lang �����������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/lang/lang_instance.h�������������������������������������������0000664�0000000�0000000�00000006722�13262451251�0023331�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/producer.h> #include "lang_auto.h" #include "base/weak_ptr.h" namespace Lang { constexpr auto kLegacyLanguageNone = -2; constexpr auto kLegacyCustomLanguage = -1; constexpr auto kLegacyDefaultLanguage = 0; constexpr str_const kLegacyLanguages[] = { "en", "it", "es", "de", "nl", "pt_BR", "ko", }; inline QString ConvertLegacyLanguageId(const QString &languageId) { return languageId.toLower().replace('_', '-'); } QString DefaultLanguageId(); class Instance; Instance &Current(); rpl::producer<QString> Viewer(LangKey key); class Instance { public: Instance() { fillDefaults(); } void switchToId(const QString &id); void switchToCustomFile(const QString &filePath); Instance(const Instance &other) = delete; Instance &operator=(const Instance &other) = delete; Instance(Instance &&other) = default; Instance &operator=(Instance &&other) = default; QString systemLangCode() const; QString cloudLangCode() const; QString id() const { return _id; } bool isCustom() const { return (_id == qstr("custom") || _id == qstr("TEST_X") || _id == qstr("TEST_0")); } int version() const { return _version; } QByteArray serialize() const; void fillFromSerialized(const QByteArray &data); void fillFromLegacy(int legacyId, const QString &legacyPath); void applyDifference(const MTPDlangPackDifference &difference); static std::map<LangKey, QString> ParseStrings(const MTPVector<MTPLangPackString> &strings); base::Observable<void> &updated() { return _updated; } QString getValue(LangKey key) const { Expects(key >= 0 && key < kLangKeysCount); Expects(_values.size() == kLangKeysCount); return _values[key]; } bool isNonDefaultPlural(LangKey key) const { Expects(key >= 0 && key < kLangKeysCount); Expects(_nonDefaultSet.size() == kLangKeysCount); return _nonDefaultSet[key] || _nonDefaultSet[key + 1] || _nonDefaultSet[key + 2] || _nonDefaultSet[key + 3] || _nonDefaultSet[key + 4] || _nonDefaultSet[key + 5]; } private: // SetCallback takes two QByteArrays: key, value. // It is called for all key-value pairs in string. // ResetCallback takes one QByteArray: key. template <typename SetCallback, typename ResetCallback> static void HandleString(const MTPLangPackString &mtpString, SetCallback setCallback, ResetCallback resetCallback); // Writes each key-value pair in the result container. template <typename Result> static LangKey ParseKeyValue(const QByteArray &key, const QByteArray &value, Result &result); void applyValue(const QByteArray &key, const QByteArray &value); void resetValue(const QByteArray &key); void reset(); void fillDefaults(); void fillFromCustomFile(const QString &filePath); void loadFromContent(const QByteArray &content); void loadFromCustomContent(const QString &absolutePath, const QString &relativePath, const QByteArray &content); void updatePluralRules(); QString _id; int _legacyId = kLegacyLanguageNone; QString _customFilePathAbsolute; QString _customFilePathRelative; QByteArray _customFileContent; int _version = 0; base::Observable<void> _updated; mutable QString _systemLanguage; std::vector<QString> _values; std::vector<uchar> _nonDefaultSet; std::map<QByteArray, QByteArray> _nonDefaultValues; }; } // namespace Lang ����������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/lang/lang_keys.cpp���������������������������������������������0000664�0000000�0000000�00000001431�13262451251�0023023�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "lang/lang_keys.h" #include "lang/lang_file_parser.h" bool langFirstNameGoesSecond() { auto fullname = lang(lng_full_name__tagged); for (auto begin = fullname.constData(), ch = begin, end = ch + fullname.size(); ch != end; ++ch) { if (*ch == TextCommand) { if (ch + 3 < end && (ch + 1)->unicode() == TextCommandLangTag && *(ch + 3) == TextCommand) { if ((ch + 2)->unicode() == 0x0020 + lt_last_name) { return true; } else if ((ch + 2)->unicode() == 0x0020 + lt_first_name) { break; } } } } return false; } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/lang/lang_keys.h�����������������������������������������������0000664�0000000�0000000�00000007442�13262451251�0022500�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "lang/lang_instance.h" #include "lang/lang_hardcoded.h" inline QString lang(LangKey key) { return Lang::Current().getValue(key); } inline base::lambda<QString()> langFactory(LangKey key) { return [key] { return Lang::Current().getValue(key); }; } template <typename WithYear, typename WithoutYear> inline QString langDateMaybeWithYear(QDate date, WithYear withYear, WithoutYear withoutYear) { auto month = date.month(); if (month <= 0 || month > 12) { return qsl("MONTH_ERR"); }; auto year = date.year(); auto current = QDate::currentDate(); auto currentYear = current.year(); auto currentMonth = current.month(); if (year != currentYear) { auto yearIsMuchGreater = [](int year, int otherYear) { return (year > otherYear + 1); }; auto monthIsMuchGreater = [](int year, int month, int otherYear, int otherMonth) { return (year == otherYear + 1) && (month + 12 > otherMonth + 3); }; if (false || yearIsMuchGreater(year, currentYear) || yearIsMuchGreater(currentYear, year) || monthIsMuchGreater(year, month, currentYear, currentMonth) || monthIsMuchGreater(currentYear, currentMonth, year, month)) { return withYear(month, year); } } return withoutYear(month, year); } inline QString langDayOfMonth(const QDate &date) { auto day = date.day(); return langDateMaybeWithYear(date, [day](int month, int year) { return lng_month_day_year(lt_month, lang(LangKey(lng_month1_small + month - 1)), lt_day, QString::number(day), lt_year, QString::number(year)); }, [day](int month, int year) { return lng_month_day(lt_month, lang(LangKey(lng_month1_small + month - 1)), lt_day, QString::number(day)); }); } inline QString langDayOfMonthFull(const QDate &date) { auto day = date.day(); return langDateMaybeWithYear(date, [day](int month, int year) { return lng_month_day_year(lt_month, lang(LangKey(lng_month1 + month - 1)), lt_day, QString::number(day), lt_year, QString::number(year)); }, [day](int month, int year) { return lng_month_day(lt_month, lang(LangKey(lng_month1 + month - 1)), lt_day, QString::number(day)); }); } inline QString langMonthOfYear(int month, int year) { return (month > 0 && month <= 12) ? lng_month_year(lt_month, lang(LangKey(lng_month1_small + month - 1)), lt_year, QString::number(year)) : qsl("MONTH_ERR"); } inline QString langMonth(const QDate &date) { return langDateMaybeWithYear(date, [](int month, int year) { return langMonthOfYear(month, year); }, [](int month, int year) { return lang(LangKey(lng_month1_small + month - 1)); }); } inline QString langMonthOfYearFull(int month, int year) { return (month > 0 && month <= 12) ? lng_month_year(lt_month, lang(LangKey(lng_month1 + month - 1)), lt_year, QString::number(year)) : qsl("MONTH_ERR"); } inline QString langMonthFull(const QDate &date) { return langDateMaybeWithYear(date, [](int month, int year) { return langMonthOfYearFull(month, year); }, [](int month, int year) { return lang(LangKey(lng_month1 + month - 1)); }); } inline QString langDayOfWeek(int index) { return (index > 0 && index <= 7) ? lang(LangKey(lng_weekday1 + index - 1)) : qsl("DAY_ERR"); } inline QString langDayOfWeek(const QDate &date) { return langDayOfWeek(date.dayOfWeek()); } inline QString langDateTime(const QDateTime &date) { return lng_mediaview_date_time(lt_date, langDayOfMonth(date.date()), lt_time, date.time().toString(cTimeFormat())); } inline QString langDateTimeFull(const QDateTime &date) { return lng_mediaview_date_time(lt_date, langDayOfMonthFull(date.date()), lt_time, date.time().toString(cTimeFormat())); } bool langFirstNameGoesSecond(); ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/lang/lang_tag.cpp����������������������������������������������0000664�0000000�0000000�00000012075�13262451251�0022631�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "lang/lang_tag.h" #include "lang/lang_keys.h" namespace Lang { namespace { // // http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html // constexpr auto kShiftZero = ushort(0); constexpr auto kShiftOne = ushort(1); constexpr auto kShiftTwo = ushort(2); constexpr auto kShiftFew = ushort(3); constexpr auto kShiftMany = ushort(4); constexpr auto kShiftOther = ushort(5); // // n absolute value of the source number (integer and decimals). // i integer digits of n. // v number of visible fraction digits in n, with trailing zeros. // w number of visible fraction digits in n, without trailing zeros. // f visible fractional digits in n, with trailing zeros. // t visible fractional digits in n, without trailing zeros. // // Let n be int, being -1 for non-integer numbers and n == i for integer numbers. // It is fine while in the rules we compare n only to integers. // // -123.450: n = -1, i = 123, v = 3, w = 2, f = 450, t = 45 // using ChoosePluralMethod = ushort (*)(int n, int i, int v, int w, int f, int t); ushort ChoosePluralAr(int n, int i, int v, int w, int f, int t) { if (n == 0) { return kShiftZero; } else if (n == 1) { return kShiftOne; } else if (n == 2) { return kShiftTwo; } else if (n < 0) { return kShiftOther; } auto mod100 = (n % 100); if (mod100 >= 3 && mod100 <= 10) { return kShiftFew; } else if (mod100 >= 11 && mod100 <= 99) { return kShiftMany; } return kShiftOther; } ushort ChoosePluralEn(int n, int i, int v, int w, int f, int t) { if (i == 1 && v == 0) { return kShiftOne; } return kShiftOther; } ushort ChoosePluralPt(int n, int i, int v, int w, int f, int t) { if (i == 0 || i == 1) { return kShiftOne; } return kShiftOther; } ushort ChoosePluralEs(int n, int i, int v, int w, int f, int t) { if (n == 1) { return kShiftOne; } return kShiftOther; } ushort ChoosePluralKo(int n, int i, int v, int w, int f, int t) { return kShiftOther; } ushort ChoosePluralRu(int n, int i, int v, int w, int f, int t) { if (v == 0) { auto mod10 = (i % 10); auto mod100 = (i % 100); if ((mod10 == 1) && (mod100 != 11)) { return kShiftOne; } else if ((mod10 >= 2) && (mod10 <= 4) && (mod100 < 12 || mod100 > 14)) { return kShiftFew; } else { return kShiftMany; } } return kShiftMany;// kShiftOther; } QMap<QString, ChoosePluralMethod> GeneratePluralRulesMap() { auto result = QMap<QString, ChoosePluralMethod>(); result.insert(qsl("ar"), ChoosePluralAr); // result.insert(qsl("de"), ChoosePluralEn); // result.insert(qsl("en"), ChoosePluralEn); // En is default, so we don't fill it inside the map. result.insert(qsl("es"), ChoosePluralEs); // result.insert(qsl("it"), ChoosePluralEn); result.insert(qsl("ko"), ChoosePluralKo); // result.insert(qsl("nl"), ChoosePluralEn); result.insert(qsl("pt"), ChoosePluralPt); result.insert(qsl("ru"), ChoosePluralRu); return result; } ChoosePluralMethod ChoosePlural = ChoosePluralEn; } // namespace int FindTagReplacementPosition(const QString &original, ushort tag) { for (auto s = original.constData(), ch = s, e = ch + original.size(); ch != e;) { if (*ch == TextCommand) { if (ch + kTagReplacementSize <= e && (ch + 1)->unicode() == TextCommandLangTag && *(ch + 3) == TextCommand) { if ((ch + 2)->unicode() == 0x0020 + tag) { return ch - s; } else { ch += kTagReplacementSize; } } else { auto next = textSkipCommand(ch, e); if (next == ch) { ++ch; } else { ch = next; } } } else { ++ch; } } return -1; } PluralResult Plural(ushort keyBase, float64 value) { // Simplified. auto n = qAbs(value); auto i = qFloor(n); auto integer = (qCeil(n) == i); auto v = integer ? 0 : 6; auto w = v; auto f = integer ? 0 : 111111; auto t = integer ? 0 : 111111; auto &langpack = Lang::Current(); auto useNonDefaultPlural = (ChoosePlural != ChoosePluralEn && langpack.isNonDefaultPlural(LangKey(keyBase))); auto shift = (useNonDefaultPlural ? ChoosePlural : ChoosePluralEn)((integer ? i : -1), i, v, w, f, t); auto string = langpack.getValue(LangKey(keyBase + shift)); if (i == qCeil(n)) { return { string, QString::number(qRound(value)) }; } return { string, QString::number(value) }; } void UpdatePluralRules(const QString &languageId) { static auto kMap = GeneratePluralRulesMap(); ChoosePlural = kMap.value(languageId.toLower(), ChoosePluralEn); } QString ReplaceTag<QString>::Replace(QString &&original, const QString &replacement, int replacementPosition) { auto result = QString(); result.reserve(original.size() + replacement.size() - kTagReplacementSize); if (replacementPosition > 0) { result.append(original.midRef(0, replacementPosition)); } result.append(replacement); if (replacementPosition + kTagReplacementSize < original.size()) { result.append(original.midRef(replacementPosition + kTagReplacementSize)); } return result; } } // namespace Lang �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/lang/lang_tag.h������������������������������������������������0000664�0000000�0000000�00000002353�13262451251�0022274�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace Lang { constexpr auto kTagReplacementSize = 4; int FindTagReplacementPosition(const QString &original, ushort tag); struct PluralResult { QString string; QString replacement; }; PluralResult Plural(ushort keyBase, float64 value); void UpdatePluralRules(const QString &languageId); template <typename ResultString> struct StartReplacements; template <> struct StartReplacements<QString> { static inline QString Call(QString &&langString) { return std::move(langString); } }; template <typename ResultString> struct ReplaceTag; template <> struct ReplaceTag<QString> { static inline QString Call(QString &&original, ushort tag, const QString &replacement) { auto replacementPosition = FindTagReplacementPosition(original, tag); if (replacementPosition < 0) { return std::move(original); } return Replace(std::move(original), replacement, replacementPosition); } static QString Replace(QString &&original, const QString &replacement, int start); }; } // namespace Lang �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/lang/lang_translator.cpp���������������������������������������0000664�0000000�0000000�00000004167�13262451251�0024252�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "lang/lang_translator.h" #include "lang/lang_keys.h" namespace Lang { QString Translator::translate(const char *context, const char *sourceText, const char *disambiguation, int n) const { if (qstr("QMenuBar") == context) { if (qstr("Services") == sourceText) return lang(lng_mac_menu_services); if (qstr("Hide %1") == sourceText) return lng_mac_menu_hide_telegram(lt_telegram, qsl("%1")); if (qstr("Hide Others") == sourceText) return lang(lng_mac_menu_hide_others); if (qstr("Show All") == sourceText) return lang(lng_mac_menu_show_all); if (qstr("Preferences...") == sourceText) return lang(lng_mac_menu_preferences); if (qstr("Quit %1") == sourceText) return lng_mac_menu_quit_telegram(lt_telegram, qsl("%1")); if (qstr("About %1") == sourceText) return lng_mac_menu_about_telegram(lt_telegram, qsl("%1")); return QString(); } if (qstr("QWidgetTextControl") == context || qstr("QLineEdit") == context) { if (qstr("&Undo") == sourceText) return lang((cPlatform() == dbipWindows) ? lng_wnd_menu_undo : ((cPlatform() == dbipMac || cPlatform() == dbipMacOld) ? lng_mac_menu_undo : lng_linux_menu_undo)); if (qstr("&Redo") == sourceText) return lang((cPlatform() == dbipWindows) ? lng_wnd_menu_redo : ((cPlatform() == dbipMac || cPlatform() == dbipMacOld) ? lng_mac_menu_redo : lng_linux_menu_redo)); if (qstr("Cu&t") == sourceText) return lang(lng_mac_menu_cut); if (qstr("&Copy") == sourceText) return lang(lng_mac_menu_copy); if (qstr("&Paste") == sourceText) return lang(lng_mac_menu_paste); if (qstr("Delete") == sourceText) return lang(lng_mac_menu_delete); if (qstr("Select All") == sourceText) return lang(lng_mac_menu_select_all); return QString(); } if (qstr("QUnicodeControlCharacterMenu") == context) { if (qstr("Insert Unicode control character") == sourceText) return lang(lng_menu_insert_unicode); return QString(); } return QString(); } } // namespace Lang ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/lang/lang_translator.h�����������������������������������������0000664�0000000�0000000�00000000725�13262451251�0023713�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace Lang { class Translator : public QTranslator { public: QString translate(const char *context, const char *sourceText, const char *disambiguation = 0, int n = -1) const override; }; } // namespace Lang �������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/layout.cpp�����������������������������������������������������0000664�0000000�0000000�00000016766�13262451251�0021464�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "layout.h" #include "data/data_document.h" #include "lang/lang_keys.h" #include "mainwidget.h" #include "application.h" #include "storage/file_upload.h" #include "mainwindow.h" #include "core/file_utilities.h" #include "boxes/add_contact_box.h" #include "boxes/confirm_box.h" #include "media/media_audio.h" #include "storage/localstorage.h" #include "history/view/history_view_cursor_state.h" QString formatSizeText(qint64 size) { if (size >= 1024 * 1024) { // more than 1 mb qint64 sizeTenthMb = (size * 10 / (1024 * 1024)); return QString::number(sizeTenthMb / 10) + '.' + QString::number(sizeTenthMb % 10) + qsl(" MB"); } if (size >= 1024) { qint64 sizeTenthKb = (size * 10 / 1024); return QString::number(sizeTenthKb / 10) + '.' + QString::number(sizeTenthKb % 10) + qsl(" KB"); } return QString::number(size) + qsl(" B"); } QString formatDownloadText(qint64 ready, qint64 total) { QString readyStr, totalStr, mb; if (total >= 1024 * 1024) { // more than 1 mb qint64 readyTenthMb = (ready * 10 / (1024 * 1024)), totalTenthMb = (total * 10 / (1024 * 1024)); readyStr = QString::number(readyTenthMb / 10) + '.' + QString::number(readyTenthMb % 10); totalStr = QString::number(totalTenthMb / 10) + '.' + QString::number(totalTenthMb % 10); mb = qsl("MB"); } else if (total >= 1024) { qint64 readyKb = (ready / 1024), totalKb = (total / 1024); readyStr = QString::number(readyKb); totalStr = QString::number(totalKb); mb = qsl("KB"); } else { readyStr = QString::number(ready); totalStr = QString::number(total); mb = qsl("B"); } return lng_save_downloaded(lt_ready, readyStr, lt_total, totalStr, lt_mb, mb); } QString formatDurationText(qint64 duration) { qint64 hours = (duration / 3600), minutes = (duration % 3600) / 60, seconds = duration % 60; return (hours ? QString::number(hours) + ':' : QString()) + (minutes >= 10 ? QString() : QString('0')) + QString::number(minutes) + ':' + (seconds >= 10 ? QString() : QString('0')) + QString::number(seconds); } QString formatDurationWords(qint64 duration) { if (duration > 59) { auto minutes = (duration / 60); auto minutesCount = lng_duration_minsec_minutes(lt_count, minutes); auto seconds = (duration % 60); auto secondsCount = lng_duration_minsec_seconds(lt_count, seconds); return lng_duration_minutes_seconds(lt_minutes_count, minutesCount, lt_seconds_count, secondsCount); } return lng_duration_seconds(lt_count, duration); } QString formatDurationAndSizeText(qint64 duration, qint64 size) { return lng_duration_and_size(lt_duration, formatDurationText(duration), lt_size, formatSizeText(size)); } QString formatGifAndSizeText(qint64 size) { return lng_duration_and_size(lt_duration, qsl("GIF"), lt_size, formatSizeText(size)); } QString formatPlayedText(qint64 played, qint64 duration) { return lng_duration_played(lt_played, formatDurationText(played), lt_duration, formatDurationText(duration)); } int32 documentColorIndex(DocumentData *document, QString &ext) { auto colorIndex = 0; auto name = document ? (document->filename().isEmpty() ? (document->sticker() ? lang(lng_in_dlg_sticker) : qsl("Unknown File")) : document->filename()) : lang(lng_message_empty); name = name.toLower(); auto lastDot = name.lastIndexOf('.'); auto mime = document ? document->mimeString().toLower() : QString(); if (name.endsWith(qstr(".doc")) || name.endsWith(qstr(".txt")) || name.endsWith(qstr(".psd")) || mime.startsWith(qstr("text/"))) { colorIndex = 0; } else if ( name.endsWith(qstr(".xls")) || name.endsWith(qstr(".csv"))) { colorIndex = 1; } else if ( name.endsWith(qstr(".pdf")) || name.endsWith(qstr(".ppt")) || name.endsWith(qstr(".key"))) { colorIndex = 2; } else if ( name.endsWith(qstr(".zip")) || name.endsWith(qstr(".rar")) || name.endsWith(qstr(".ai")) || name.endsWith(qstr(".mp3")) || name.endsWith(qstr(".mov")) || name.endsWith(qstr(".avi"))) { colorIndex = 3; } else { auto ch = (lastDot >= 0 && lastDot + 1 < name.size()) ? name.at(lastDot + 1) : (name.isEmpty() ? (mime.isEmpty() ? '0' : mime.at(0)) : name.at(0)); colorIndex = (ch.unicode() % 4); } ext = document ? ((lastDot < 0 || lastDot + 2 > name.size()) ? name : name.mid(lastDot + 1)) : QString(); return colorIndex; } style::color documentColor(int32 colorIndex) { const style::color colors[] = { st::msgFile1Bg, st::msgFile2Bg, st::msgFile3Bg, st::msgFile4Bg }; return colors[colorIndex & 3]; } style::color documentDarkColor(int32 colorIndex) { static style::color colors[] = { st::msgFile1BgDark, st::msgFile2BgDark, st::msgFile3BgDark, st::msgFile4BgDark }; return colors[colorIndex & 3]; } style::color documentOverColor(int32 colorIndex) { static style::color colors[] = { st::msgFile1BgOver, st::msgFile2BgOver, st::msgFile3BgOver, st::msgFile4BgOver }; return colors[colorIndex & 3]; } style::color documentSelectedColor(int32 colorIndex) { static style::color colors[] = { st::msgFile1BgSelected, st::msgFile2BgSelected, st::msgFile3BgSelected, st::msgFile4BgSelected }; return colors[colorIndex & 3]; } RoundCorners documentCorners(int32 colorIndex) { return RoundCorners(Doc1Corners + (colorIndex & 3)); } bool documentIsValidMediaFile(const QString &filepath) { static StaticNeverFreedPointer<QList<QString>> validMediaTypes(([] { std::unique_ptr<QList<QString>> result = std::make_unique<QList<QString>>(); *result = qsl("\ webm mkv flv vob ogv ogg drc gif gifv mng avi mov qt wmv yuv rm rmvb asf amv mp4 m4p \ m4v mpg mp2 mpeg mpe mpv m2v svi 3gp 3g2 mxf roq nsv f4v f4p f4a f4b wma divx evo mk3d \ mka mks mcf m2p ps ts m2ts ifo aaf avchd cam dat dsh dvr-ms m1v fla flr sol wrap smi swf \ wtv 8svx 16svx iff aiff aif aifc au bwf cdda raw wav flac la pac m4a ape ofr ofs off rka \ shn tak tta wv brstm dts dtshd dtsma ast amr mp3 spx gsm aac mpc vqf ra ots swa vox voc \ dwd smp aup cust mid mus sib sid ly gym vgm psf nsf mod ptb s3m xm it mt2 minipsf psflib \ 2sf dsf gsf psf2 qsf ssf usf rmj spc niff mxl xml txm ym jam mp1 mscz\ ").split(' '); return result.release(); })()); QFileInfo info(filepath); auto parts = info.fileName().split('.', QString::SkipEmptyParts); return !parts.isEmpty() && (validMediaTypes->indexOf(parts.back().toLower()) >= 0); } bool documentIsExecutableName(const QString &filename) { static StaticNeverFreedPointer<QList<QString>> executableTypes(([] { std::unique_ptr<QList<QString>> result = std::make_unique<QList<QString>>(); #ifdef Q_OS_MAC *result = qsl("\ action app bin command csh osx workflow\ ").split(' '); #elif defined Q_OS_LINUX // Q_OS_MAC *result = qsl("\ bin csh ksh out run\ ").split(' '); #else // Q_OS_MAC || Q_OS_LINUX *result = qsl("\ bat bin cmd com cpl exe gadget inf ins inx isu job jse lnk msc msi \ msp mst paf pif ps1 reg rgs sct shb shs u3p vb vbe vbs vbscript ws wsf\ ").split(' '); #endif // !Q_OS_MAC && !Q_OS_LINUX return result.release(); })()); auto lastDotIndex = filename.lastIndexOf('.'); return (lastDotIndex >= 0) && (executableTypes->indexOf(filename.mid(lastDotIndex + 1).toLower()) >= 0); } [[nodiscard]] HistoryView::TextState LayoutItemBase::getState( QPoint point, StateRequest request) const { return {}; } [[nodiscard]] TextSelection LayoutItemBase::adjustSelection( TextSelection selection, TextSelectType type) const { return selection; } ����������tdesktop-1.2.17/Telegram/SourceFiles/layout.h�������������������������������������������������������0000664�0000000�0000000�00000006627�13262451251�0021124�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "base/runtime_composer.h" namespace HistoryView { struct TextState; struct StateRequest; } // namespace HistoryView constexpr auto FullSelection = TextSelection { 0xFFFF, 0xFFFF }; inline bool IsSubGroupSelection(TextSelection selection) { return (selection.from == 0xFFFF) && (selection.to != 0xFFFF); } inline bool IsGroupItemSelection( TextSelection selection, int index) { Expects(index >= 0 && index < 0x0F); return IsSubGroupSelection(selection) && (selection.to & (1 << index)); } [[nodiscard]] inline TextSelection AddGroupItemSelection( TextSelection selection, int index) { Expects(index >= 0 && index < 0x0F); const auto bit = uint16(1U << index); return TextSelection( 0xFFFF, IsSubGroupSelection(selection) ? (selection.to | bit) : bit); } [[nodiscard]] inline TextSelection RemoveGroupItemSelection( TextSelection selection, int index) { Expects(index >= 0 && index < 0x0F); const auto bit = uint16(1U << index); return IsSubGroupSelection(selection) ? TextSelection(0xFFFF, selection.to & ~bit) : selection; } static const int32 FileStatusSizeReady = 0x7FFFFFF0; static const int32 FileStatusSizeLoaded = 0x7FFFFFF1; static const int32 FileStatusSizeFailed = 0x7FFFFFF2; QString formatSizeText(qint64 size); QString formatDownloadText(qint64 ready, qint64 total); QString formatDurationText(qint64 duration); QString formatDurationWords(qint64 duration); QString formatDurationAndSizeText(qint64 duration, qint64 size); QString formatGifAndSizeText(qint64 size); QString formatPlayedText(qint64 played, qint64 duration); int32 documentColorIndex(DocumentData *document, QString &ext); style::color documentColor(int colorIndex); style::color documentDarkColor(int colorIndex); style::color documentOverColor(int colorIndex); style::color documentSelectedColor(int colorIndex); RoundCorners documentCorners(int colorIndex); bool documentIsValidMediaFile(const QString &filepath); bool documentIsExecutableName(const QString &filename); class PaintContextBase { public: PaintContextBase(TimeMs ms, bool selecting) : ms(ms), selecting(selecting) { } TimeMs ms; bool selecting; }; class LayoutItemBase : public RuntimeComposer<LayoutItemBase> , public ClickHandlerHost { public: using TextState = HistoryView::TextState; using StateRequest = HistoryView::StateRequest; LayoutItemBase() { } LayoutItemBase(const LayoutItemBase &other) = delete; LayoutItemBase &operator=(const LayoutItemBase &other) = delete; int maxWidth() const { return _maxw; } int minHeight() const { return _minh; } virtual void initDimensions() = 0; virtual int resizeGetHeight(int width) { _width = qMin(width, _maxw); _height = _minh; return _height; } [[nodiscard]] virtual TextState getState( QPoint point, StateRequest request) const; [[nodiscard]] virtual TextSelection adjustSelection( TextSelection selection, TextSelectType type) const; int width() const { return _width; } int height() const { return _height; } bool hasPoint(QPoint point) const { return QRect(0, 0, width(), height()).contains(point); } virtual ~LayoutItemBase() { } protected: int _width = 0; int _height = 0; int _maxw = 0; int _minh = 0; }; ���������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/logs.cpp�������������������������������������������������������0000664�0000000�0000000�00000040021�13262451251�0021070�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "logs.h" #include "platform/platform_specific.h" #include "mtproto/connection.h" #include "core/crash_reports.h" #include "core/launcher.h" enum LogDataType { LogDataMain, LogDataDebug, LogDataTcp, LogDataMtp, LogDataCount }; QMutex *_logsMutex(LogDataType type, bool clear = false) { static QMutex *LogsMutexes = 0; if (clear) { delete[] LogsMutexes; LogsMutexes = 0; } else if (!LogsMutexes) { LogsMutexes = new QMutex[LogDataCount]; } return &LogsMutexes[type]; } QString _logsFilePath(LogDataType type, const QString &postfix = QString()) { QString path(cWorkingDir()); switch (type) { case LogDataMain: path += qstr("log") + postfix + qstr(".txt"); break; case LogDataDebug: path += qstr("DebugLogs/log") + postfix + qstr(".txt"); break; case LogDataTcp: path += qstr("DebugLogs/tcp") + postfix + qstr(".txt"); break; case LogDataMtp: path += qstr("DebugLogs/mtp") + postfix + qstr(".txt"); break; } return path; } int32 LogsStartIndexChosen = -1; QString _logsEntryStart() { static int32 index = 0; QDateTime tm(QDateTime::currentDateTime()); auto thread = qobject_cast<MTP::internal::Thread*>(QThread::currentThread()); auto threadId = thread ? thread->getThreadIndex() : 0; return QString("[%1 %2-%3]").arg(tm.toString("hh:mm:ss.zzz")).arg(QString("%1").arg(threadId, 2, 10, QChar('0'))).arg(++index, 7, 10, QChar('0')); } class LogsDataFields { public: LogsDataFields() { for (int32 i = 0; i < LogDataCount; ++i) { files[i].reset(new QFile()); } } bool openMain() { return reopen(LogDataMain, 0, qsl("start")); } void closeMain() { QMutexLocker lock(_logsMutex(LogDataMain)); if (files[LogDataMain]) { streams[LogDataMain].setDevice(0); files[LogDataMain]->close(); } } bool instanceChecked() { return reopen(LogDataMain, 0, QString()); } QString full() { if (!streams[LogDataMain].device()) { return QString(); } QFile out(files[LogDataMain]->fileName()); if (out.open(QIODevice::ReadOnly)) { return QString::fromUtf8(out.readAll()); } return QString(); } void write(LogDataType type, const QString &msg) { QMutexLocker lock(_logsMutex(type)); if (type != LogDataMain) reopenDebug(); if (!streams[type].device()) return; streams[type] << msg; streams[type].flush(); } private: std::unique_ptr<QFile> files[LogDataCount]; QTextStream streams[LogDataCount]; int32 part = -1; bool reopen(LogDataType type, int32 dayIndex, const QString &postfix) { if (streams[type].device()) { if (type == LogDataMain) { if (!postfix.isEmpty()) { return true; } } else { streams[type].setDevice(0); files[type]->close(); } } auto mode = QIODevice::WriteOnly | QIODevice::Text; if (type == LogDataMain) { // we can call LOG() in LogDataMain reopen - mutex not locked if (postfix.isEmpty()) { // instance checked, need to move to log.txt Assert(!files[type]->fileName().isEmpty()); // one of log_startXX.txt should've been opened already auto to = std::make_unique<QFile>(_logsFilePath(type, postfix)); if (to->exists() && !to->remove()) { LOG(("Could not delete '%1' file to start new logging!").arg(to->fileName())); return false; } if (!QFile(files[type]->fileName()).copy(to->fileName())) { // don't close files[type] yet LOG(("Could not copy '%1' to '%2' to start new logging!").arg(files[type]->fileName()).arg(to->fileName())); return false; } if (to->open(mode | QIODevice::Append)) { std::swap(files[type], to); streams[type].setDevice(files[type].get()); streams[type].setCodec("UTF-8"); LOG(("Moved logging from '%1' to '%2'!").arg(to->fileName()).arg(files[type]->fileName())); to->remove(); LogsStartIndexChosen = -1; QDir working(cWorkingDir()); // delete all other log_startXX.txt that we can QStringList oldlogs = working.entryList(QStringList("log_start*.txt"), QDir::Files); for (QStringList::const_iterator i = oldlogs.cbegin(), e = oldlogs.cend(); i != e; ++i) { QString oldlog = cWorkingDir() + *i, oldlogend = i->mid(qstr("log_start").size()); if (oldlogend.size() == 1 + qstr(".txt").size() && oldlogend.at(0).isDigit() && oldlogend.midRef(1) == qstr(".txt")) { bool removed = QFile(*i).remove(); LOG(("Old start log '%1' found, deleted: %2").arg(*i).arg(Logs::b(removed))); } } return true; } LOG(("Could not open '%1' file to start new logging!").arg(to->fileName())); return false; } else { bool found = false; int32 oldest = -1; // find not existing log_startX.txt or pick the oldest one (by lastModified) QDateTime oldestLastModified; for (int32 i = 0; i < 10; ++i) { QString trying = _logsFilePath(type, qsl("_start%1").arg(i)); files[type]->setFileName(trying); if (!files[type]->exists()) { LogsStartIndexChosen = i; found = true; break; } QDateTime lastModified = QFileInfo(trying).lastModified(); if (oldest < 0 || lastModified < oldestLastModified) { oldestLastModified = lastModified; oldest = i; } } if (!found) { files[type]->setFileName(_logsFilePath(type, qsl("_start%1").arg(oldest))); LogsStartIndexChosen = oldest; } } } else { files[type]->setFileName(_logsFilePath(type, postfix)); if (files[type]->exists()) { if (files[type]->open(QIODevice::ReadOnly | QIODevice::Text)) { if (QString::fromUtf8(files[type]->readLine()).toInt() == dayIndex) { mode |= QIODevice::Append; } files[type]->close(); } } else { QDir().mkdir(cWorkingDir() + qstr("DebugLogs")); } } if (files[type]->open(mode)) { streams[type].setDevice(files[type].get()); streams[type].setCodec("UTF-8"); if (type != LogDataMain) { streams[type] << ((mode & QIODevice::Append) ? qsl("\ ----------------------------------------------------------------\n\ NEW LOGGING INSTANCE STARTED!!!\n\ ----------------------------------------------------------------\n") : qsl("%1\n").arg(dayIndex)); streams[type].flush(); } return true; } else if (type != LogDataMain) { LOG(("Could not open debug log '%1'!").arg(files[type]->fileName())); } return false; } void reopenDebug() { time_t t = time(NULL); struct tm tm; mylocaltime(&tm, &t); static const int switchEach = 15; // minutes int32 newPart = (tm.tm_min + tm.tm_hour * 60) / switchEach; if (newPart == part) return; part = newPart; int32 dayIndex = (tm.tm_year + 1900) * 10000 + (tm.tm_mon + 1) * 100 + tm.tm_mday; QString postfix = QString("_%4_%5").arg((part * switchEach) / 60, 2, 10, QChar('0')).arg((part * switchEach) % 60, 2, 10, QChar('0')); reopen(LogDataDebug, dayIndex, postfix); reopen(LogDataTcp, dayIndex, postfix); reopen(LogDataMtp, dayIndex, postfix); } }; LogsDataFields *LogsData = 0; typedef QList<QPair<LogDataType, QString> > LogsInMemoryList; LogsInMemoryList *LogsInMemory = 0; LogsInMemoryList *DeletedLogsInMemory = SharedMemoryLocation<LogsInMemoryList, 0>(); QString LogsBeforeSingleInstanceChecked; // LogsInMemory already dumped in LogsData, but LogsData is about to be deleted void _logsWrite(LogDataType type, const QString &msg) { if (LogsData && (type == LogDataMain || LogsStartIndexChosen < 0)) { if (type == LogDataMain || cDebug()) { LogsData->write(type, msg); } } else if (LogsInMemory != DeletedLogsInMemory) { if (!LogsInMemory) { LogsInMemory = new LogsInMemoryList; } LogsInMemory->push_back(qMakePair(type, msg)); } else if (!LogsBeforeSingleInstanceChecked.isEmpty() && type == LogDataMain) { LogsBeforeSingleInstanceChecked += msg; } } namespace Logs { namespace { void MoveOldDataFiles(const QString &wasDir) { QFile data(wasDir + "data"), dataConfig(wasDir + "data_config"), tdataConfig(wasDir + "tdata/config"); if (data.exists() && dataConfig.exists() && !QFileInfo(cWorkingDir() + "data").exists() && !QFileInfo(cWorkingDir() + "data_config").exists()) { // move to home dir LOG(("Copying data to home dir '%1' from '%2'").arg(cWorkingDir()).arg(wasDir)); if (data.copy(cWorkingDir() + "data")) { LOG(("Copied 'data' to home dir")); if (dataConfig.copy(cWorkingDir() + "data_config")) { LOG(("Copied 'data_config' to home dir")); bool tdataGood = true; if (tdataConfig.exists()) { tdataGood = false; QDir().mkpath(cWorkingDir() + "tdata"); if (tdataConfig.copy(cWorkingDir() + "tdata/config")) { LOG(("Copied 'tdata/config' to home dir")); tdataGood = true; } else { LOG(("Copied 'data' and 'data_config', but could not copy 'tdata/config'!")); } } if (tdataGood) { if (data.remove()) { LOG(("Removed 'data'")); } else { LOG(("Could not remove 'data'")); } if (dataConfig.remove()) { LOG(("Removed 'data_config'")); } else { LOG(("Could not remove 'data_config'")); } if (!tdataConfig.exists() || tdataConfig.remove()) { LOG(("Removed 'tdata/config'")); } else { LOG(("Could not remove 'tdata/config'")); } QDir().rmdir(wasDir + "tdata"); } } else { LOG(("Copied 'data', but could not copy 'data_config'!!")); } } else { LOG(("Could not copy 'data'!")); } } } } // namespace void start(not_null<Core::Launcher*> launcher) { Assert(LogsData == 0); if (!Sandbox::CheckBetaVersionDir()) { return; } auto initialWorkingDir = QDir(cWorkingDir()).absolutePath() + '/'; auto moveOldDataFrom = QString(); auto workingDirChosen = false; if (cBetaVersion()) { cSetDebug(true); workingDirChosen = true; #if defined Q_OS_MAC || defined Q_OS_LINUX } else { #ifdef _DEBUG cForceWorkingDir(cExeDir()); #else // _DEBUG if (!cWorkingDir().isEmpty()) { // This value must come only from the "-workdir" argument. cForceWorkingDir(cWorkingDir()); } else { cForceWorkingDir(psAppDataPath()); } #endif // !_DEBUG workingDirChosen = true; #if defined Q_OS_LINUX && !defined _DEBUG // fix first version moveOldDataFrom = initialWorkingDir; #endif // Q_OS_LINUX && !_DEBUG #elif defined Q_OS_WINRT // Q_OS_MAC || Q_OS_LINUX } else { cForceWorkingDir(psAppDataPath()); workingDirChosen = true; #elif defined OS_WIN_STORE // Q_OS_MAC || Q_OS_LINUX || Q_OS_WINRT #ifdef _DEBUG cForceWorkingDir(cExeDir()); #else // _DEBUG cForceWorkingDir(psAppDataPath()); #endif // !_DEBUG workingDirChosen = true; #elif defined Q_OS_WIN } else { if (!cWorkingDir().isEmpty()) { // This value must come only from the "-workdir" argument. cForceWorkingDir(cWorkingDir()); workingDirChosen = true; } #endif // Q_OS_MAC || Q_OS_LINUX || Q_OS_WINRT || OS_WIN_STORE } LogsData = new LogsDataFields(); if (!workingDirChosen) { cForceWorkingDir(cExeDir()); if (!LogsData->openMain()) { cForceWorkingDir(psAppDataPath()); } } cForceWorkingDir(QDir(cWorkingDir()).absolutePath() + '/'); // WinRT build requires the working dir to stay the same for plugin loading. #ifndef Q_OS_WINRT QDir().setCurrent(cWorkingDir()); #endif // !Q_OS_WINRT QDir().mkpath(cWorkingDir() + qstr("tdata")); Sandbox::WorkingDirReady(); CrashReports::StartCatching(); if (!LogsData->openMain()) { delete LogsData; LogsData = 0; } LOG(("Launched version: %1, alpha: %2, beta: %3, debug mode: %4, test dc: %5").arg(AppVersion).arg(Logs::b(cAlphaVersion())).arg(cBetaVersion()).arg(Logs::b(cDebug())).arg(Logs::b(cTestMode()))); LOG(("Executable dir: %1, name: %2").arg(cExeDir()).arg(cExeName())); LOG(("Initial working dir: %1").arg(initialWorkingDir)); LOG(("Working dir: %1").arg(cWorkingDir())); LOG(("Command line: %1").arg(launcher->argumentsString())); if (!LogsData) { LOG(("FATAL: Could not open '%1' for writing log!").arg(_logsFilePath(LogDataMain, qsl("_startXX")))); return; } #ifdef Q_OS_WIN if (cWorkingDir() == psAppDataPath()) { // fix old "Telegram Win (Unofficial)" version moveOldDataFrom = psAppDataPathOld(); } #endif if (!moveOldDataFrom.isEmpty()) { MoveOldDataFiles(moveOldDataFrom); } if (LogsInMemory) { Assert(LogsInMemory != DeletedLogsInMemory); LogsInMemoryList list = *LogsInMemory; for (LogsInMemoryList::const_iterator i = list.cbegin(), e = list.cend(); i != e; ++i) { if (i->first == LogDataMain) { _logsWrite(i->first, i->second); } } } LOG(("Logs started")); } void finish() { delete LogsData; LogsData = 0; if (LogsInMemory && LogsInMemory != DeletedLogsInMemory) { delete LogsInMemory; } LogsInMemory = DeletedLogsInMemory; _logsMutex(LogDataMain, true); CrashReports::FinishCatching(); } bool started() { return LogsData != 0; } bool instanceChecked() { if (!LogsData) return false; if (!LogsData->instanceChecked()) { LogsBeforeSingleInstanceChecked = Logs::full(); delete LogsData; LogsData = 0; LOG(("FATAL: Could not move logging to '%1'!").arg(_logsFilePath(LogDataMain))); return false; } if (LogsInMemory) { Assert(LogsInMemory != DeletedLogsInMemory); LogsInMemoryList list = *LogsInMemory; for (LogsInMemoryList::const_iterator i = list.cbegin(), e = list.cend(); i != e; ++i) { if (i->first != LogDataMain) { _logsWrite(i->first, i->second); } } } if (LogsInMemory) { Assert(LogsInMemory != DeletedLogsInMemory); delete LogsInMemory; } LogsInMemory = DeletedLogsInMemory; DEBUG_LOG(("Debug logs started.")); LogsBeforeSingleInstanceChecked.clear(); return true; } void multipleInstances() { if (LogsInMemory) { Assert(LogsInMemory != DeletedLogsInMemory); delete LogsInMemory; } LogsInMemory = DeletedLogsInMemory; if (cDebug()) { LOG(("WARNING: debug logs are not written in multiple instances mode!")); } LogsBeforeSingleInstanceChecked.clear(); } void closeMain() { LOG(("Explicitly closing main log and finishing crash handlers.")); if (LogsData) { LogsData->closeMain(); } } void writeMain(const QString &v) { time_t t = time(NULL); struct tm tm; mylocaltime(&tm, &t); QString msg(QString("[%1.%2.%3 %4:%5:%6] %7\n").arg(tm.tm_year + 1900).arg(tm.tm_mon + 1, 2, 10, QChar('0')).arg(tm.tm_mday, 2, 10, QChar('0')).arg(tm.tm_hour, 2, 10, QChar('0')).arg(tm.tm_min, 2, 10, QChar('0')).arg(tm.tm_sec, 2, 10, QChar('0')).arg(v)); _logsWrite(LogDataMain, msg); QString debugmsg(QString("%1 %2\n").arg(_logsEntryStart()).arg(v)); _logsWrite(LogDataDebug, debugmsg); } void writeDebug(const char *file, int32 line, const QString &v) { const char *last = strstr(file, "/"), *found = 0; while (last) { found = last; last = strstr(last + 1, "/"); } last = strstr(file, "\\"); while (last) { found = last; last = strstr(last + 1, "\\"); } if (found) { file = found + 1; } QString msg(QString("%1 %2 (%3 : %4)\n").arg(_logsEntryStart()).arg(v).arg(file).arg(line)); _logsWrite(LogDataDebug, msg); #ifdef Q_OS_WIN //OutputDebugString(reinterpret_cast<const wchar_t *>(msg.utf16())); #elif defined Q_OS_MAC //objc_outputDebugString(msg); #elif defined Q_OS_LINUX && defined _DEBUG //std::cout << msg.toUtf8().constData(); #endif } void writeTcp(const QString &v) { QString msg(QString("%1 %2\n").arg(_logsEntryStart()).arg(v)); _logsWrite(LogDataTcp, msg); } void writeMtp(int32 dc, const QString &v) { QString msg(QString("%1 (dc:%2) %3\n").arg(_logsEntryStart()).arg(dc).arg(v)); _logsWrite(LogDataMtp, msg); } QString full() { if (LogsData) { return LogsData->full(); } if (!LogsInMemory || LogsInMemory == DeletedLogsInMemory) { return LogsBeforeSingleInstanceChecked; } int32 size = LogsBeforeSingleInstanceChecked.size(); for (LogsInMemoryList::const_iterator i = LogsInMemory->cbegin(), e = LogsInMemory->cend(); i != e; ++i) { if (i->first == LogDataMain) { size += i->second.size(); } } QString result; result.reserve(size); if (!LogsBeforeSingleInstanceChecked.isEmpty()) { result.append(LogsBeforeSingleInstanceChecked); } for (LogsInMemoryList::const_iterator i = LogsInMemory->cbegin(), e = LogsInMemory->cend(); i != e; ++i) { if (i->first == LogDataMain) { result += i->second; } } return result; } } // namespace Logs ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/logs.h���������������������������������������������������������0000664�0000000�0000000�00000003421�13262451251�0020540�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace Core { class Launcher; } // namespace Core namespace Logs { void start(not_null<Core::Launcher*> launcher); bool started(); void finish(); bool instanceChecked(); void multipleInstances(); void closeMain(); void writeMain(const QString &v); void writeDebug(const char *file, int32 line, const QString &v); void writeTcp(const QString &v); void writeMtp(int32 dc, const QString &v); QString full(); inline const char *b(bool v) { return v ? "[TRUE]" : "[FALSE]"; } struct MemoryBuffer { MemoryBuffer(const void *ptr, uint32 size) : p(ptr), s(size) { } QString str() const { QString result; const uchar *buf((const uchar*)p); const char *hex = "0123456789ABCDEF"; result.reserve(s * 3); for (uint32 i = 0; i < s; ++i) { result += hex[(buf[i] >> 4)]; result += hex[buf[i] & 0x0F]; result += ' '; } result.chop(1); return result; } const void *p; uint32 s; }; inline MemoryBuffer mb(const void *ptr, uint32 size) { return MemoryBuffer(ptr, size); } } // namespace Logs #define LOG(msg) (Logs::writeMain(QString msg)) //usage LOG(("log: %1 %2").arg(1).arg(2)) #define DEBUG_LOG(msg) { if (cDebug() || !Logs::started()) Logs::writeDebug(__FILE__, __LINE__, QString msg); } //usage DEBUG_LOG(("log: %1 %2").arg(1).arg(2)) #define TCP_LOG(msg) { if (cDebug() || !Logs::started()) Logs::writeTcp(QString msg); } //usage TCP_LOG(("log: %1 %2").arg(1).arg(2)) #define MTP_LOG(dc, msg) { if (cDebug() || !Logs::started()) Logs::writeMtp(dc, QString msg); } //usage MTP_LOG(dc, ("log: %1 %2").arg(1).arg(2)) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/main.cpp�������������������������������������������������������0000664�0000000�0000000�00000000625�13262451251�0021056�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "core/launcher.h" int main(int argc, char *argv[]) { const auto launcher = Core::Launcher::Create(argc, argv); return launcher ? launcher->exec() : 1; } �����������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/mainwidget.cpp�������������������������������������������������0000664�0000000�0000000�00000461421�13262451251�0022267�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "mainwidget.h" #include <rpl/combine.h> #include <rpl/merge.h> #include <rpl/flatten_latest.h> #include "data/data_photo.h" #include "data/data_document.h" #include "data/data_web_page.h" #include "data/data_game.h" #include "data/data_peer_values.h" #include "data/data_drafts.h" #include "data/data_session.h" #include "data/data_media_types.h" #include "data/data_feed.h" #include "ui/special_buttons.h" #include "ui/widgets/buttons.h" #include "ui/widgets/shadow.h" #include "window/section_memento.h" #include "window/section_widget.h" #include "ui/widgets/dropdown_menu.h" #include "ui/focus_persister.h" #include "ui/resize_area.h" #include "ui/text_options.h" #include "ui/toast/toast.h" #include "chat_helpers/message_field.h" #include "chat_helpers/stickers.h" #include "info/info_memento.h" #include "info/info_controller.h" #include "observer_peer.h" #include "apiwrap.h" #include "dialogs/dialogs_widget.h" #include "dialogs/dialogs_key.h" #include "history/history.h" #include "history/history_widget.h" #include "history/history_message.h" #include "history/history_media.h" #include "history/view/history_view_service_message.h" #include "history/view/history_view_element.h" #include "lang/lang_keys.h" #include "lang/lang_cloud_manager.h" #include "boxes/add_contact_box.h" #include "storage/file_upload.h" #include "messenger.h" #include "application.h" #include "mainwindow.h" #include "inline_bots/inline_bot_layout_item.h" #include "boxes/confirm_box.h" #include "boxes/sticker_set_box.h" #include "boxes/mute_settings_box.h" #include "boxes/peer_list_controllers.h" #include "boxes/download_path_box.h" #include "storage/localstorage.h" #include "shortcuts.h" #include "media/media_audio.h" #include "media/player/media_player_panel.h" #include "media/player/media_player_widget.h" #include "media/player/media_player_volume_controller.h" #include "media/player/media_player_instance.h" #include "media/player/media_player_float.h" #include "base/qthelp_regex.h" #include "base/qthelp_url.h" #include "base/flat_set.h" #include "window/player_wrap_widget.h" #include "window/notifications_manager.h" #include "window/window_slide_animation.h" #include "window/window_controller.h" #include "window/themes/window_theme.h" #include "mtproto/dc_options.h" #include "core/file_utilities.h" #include "calls/calls_instance.h" #include "calls/calls_top_bar.h" #include "auth_session.h" #include "storage/storage_facade.h" #include "storage/storage_shared_media.h" #include "storage/storage_user_photos.h" #include "styles/style_dialogs.h" #include "styles/style_history.h" #include "styles/style_boxes.h" enum StackItemType { HistoryStackItem, SectionStackItem, }; class StackItem { public: StackItem(PeerData *peer) : _peer(peer) { } PeerData *peer() const { return _peer; } void setThirdSectionMemento( std::unique_ptr<Window::SectionMemento> &&memento); std::unique_ptr<Window::SectionMemento> takeThirdSectionMemento() { return std::move(_thirdSectionMemento); } void setThirdSectionWeak(QPointer<Window::SectionWidget> section) { _thirdSectionWeak = section; } QPointer<Window::SectionWidget> thirdSectionWeak() const { return _thirdSectionWeak; } virtual StackItemType type() const = 0; virtual ~StackItem() = default; private: PeerData *_peer = nullptr; QPointer<Window::SectionWidget> _thirdSectionWeak; std::unique_ptr<Window::SectionMemento> _thirdSectionMemento; }; class StackItemHistory : public StackItem { public: StackItemHistory( not_null<History*> history, MsgId msgId, QList<MsgId> replyReturns) : StackItem(history->peer) , history(history) , msgId(msgId) , replyReturns(replyReturns) { } StackItemType type() const override { return HistoryStackItem; } not_null<History*> history; MsgId msgId; QList<MsgId> replyReturns; }; class StackItemSection : public StackItem { public: StackItemSection( std::unique_ptr<Window::SectionMemento> &&memento); StackItemType type() const override { return SectionStackItem; } Window::SectionMemento *memento() const { return _memento.get(); } private: std::unique_ptr<Window::SectionMemento> _memento; }; void StackItem::setThirdSectionMemento( std::unique_ptr<Window::SectionMemento> &&memento) { _thirdSectionMemento = std::move(memento); } StackItemSection::StackItemSection( std::unique_ptr<Window::SectionMemento> &&memento) : StackItem(nullptr) , _memento(std::move(memento)) { } template <typename ToggleCallback, typename DraggedCallback> MainWidget::Float::Float( QWidget *parent, not_null<Window::Controller*> controller, not_null<HistoryItem*> item, ToggleCallback toggle, DraggedCallback dragged) : animationSide(RectPart::Right) , column(Window::Column::Second) , corner(RectPart::TopRight) , widget( parent, controller, item, [this, toggle = std::move(toggle)](bool visible) { toggle(this, visible); }, [this, dragged = std::move(dragged)](bool closed) { dragged(this, closed); }) { } MainWidget::MainWidget( QWidget *parent, not_null<Window::Controller*> controller) : RpWidget(parent) , _controller(controller) , _dialogsWidth(st::columnMinimalWidthLeft) , _thirdColumnWidth(st::columnMinimalWidthThird) , _sideShadow(this) , _dialogs(this, _controller) , _history(this, _controller) , _playerPlaylist( this, _controller, Media::Player::Panel::Layout::OnlyPlaylist) , _playerPanel(this, _controller, Media::Player::Panel::Layout::Full) { Messenger::Instance().mtp()->setUpdatesHandler(rpcDone(&MainWidget::updateReceived)); Messenger::Instance().mtp()->setGlobalFailHandler(rpcFail(&MainWidget::updateFail)); _ptsWaiter.setRequesting(true); updateScrollColors(); connect(_dialogs, SIGNAL(cancelled()), this, SLOT(dialogsCancelled())); connect(this, SIGNAL(dialogsUpdated()), _dialogs, SLOT(onListScroll())); connect(_history, SIGNAL(cancelled()), _dialogs, SLOT(activate())); connect(&noUpdatesTimer, SIGNAL(timeout()), this, SLOT(mtpPing())); connect(&_onlineTimer, SIGNAL(timeout()), this, SLOT(updateOnline())); connect(&_idleFinishTimer, SIGNAL(timeout()), this, SLOT(checkIdleFinish())); connect(&_bySeqTimer, SIGNAL(timeout()), this, SLOT(getDifference())); connect(&_byPtsTimer, SIGNAL(timeout()), this, SLOT(onGetDifferenceTimeByPts())); connect(&_byMinChannelTimer, SIGNAL(timeout()), this, SLOT(getDifference())); connect(&_failDifferenceTimer, SIGNAL(timeout()), this, SLOT(onGetDifferenceTimeAfterFail())); connect(&updateNotifySettingTimer, SIGNAL(timeout()), this, SLOT(onUpdateNotifySettings())); subscribe(Media::Player::Updated(), [this](const AudioMsgId &audioId) { if (audioId.type() != AudioMsgId::Type::Video) { handleAudioUpdate(audioId); } }); subscribe(Auth().calls().currentCallChanged(), [this](Calls::Call *call) { setCurrentCall(call); }); subscribe(_controller->dialogsListFocused(), [this](bool) { updateDialogsWidthAnimated(); }); subscribe(_controller->dialogsListDisplayForced(), [this](bool) { updateDialogsWidthAnimated(); }); rpl::merge( Auth().settings().dialogsWidthRatioChanges() | rpl::map([] { return rpl::empty_value(); }), Auth().settings().thirdColumnWidthChanges() | rpl::map([] { return rpl::empty_value(); }) ) | rpl::start_with_next( [this] { updateControlsGeometry(); }, lifetime()); subscribe(_controller->floatPlayerAreaUpdated(), [this] { checkFloatPlayerVisibility(); }); using namespace rpl::mappers; _controller->activeChatValue( ) | rpl::map([](Dialogs::Key key) { const auto peer = key.peer(); auto canWrite = peer ? Data::CanWriteValue(peer) : rpl::single(false); return std::move(canWrite) | rpl::map(tuple(key, _1)); }) | rpl::flatten_latest( ) | rpl::start_with_next([this](Dialogs::Key key, bool canWrite) { updateThirdColumnToCurrentChat(key, canWrite); }, lifetime()); QCoreApplication::instance()->installEventFilter(this); connect(&_updateMutedTimer, SIGNAL(timeout()), this, SLOT(onUpdateMuted())); connect(&_viewsIncrementTimer, SIGNAL(timeout()), this, SLOT(onViewsIncrement())); using Update = Window::Theme::BackgroundUpdate; subscribe(Window::Theme::Background(), [this](const Update &update) { if (update.type == Update::Type::New || update.type == Update::Type::Changed) { clearCachedBackground(); } }); connect(&_cacheBackgroundTimer, SIGNAL(timeout()), this, SLOT(onCacheBackground())); _playerPanel->setPinCallback([this] { switchToFixedPlayer(); }); _playerPanel->setCloseCallback([this] { closeBothPlayers(); }); subscribe(Media::Player::instance()->titleButtonOver(), [this](bool over) { if (over) { _playerPanel->showFromOther(); } else { _playerPanel->hideFromOther(); } }); subscribe(Media::Player::instance()->playerWidgetOver(), [this](bool over) { if (over) { if (_playerPlaylist->isHidden()) { auto position = mapFromGlobal(QCursor::pos()).x(); auto bestPosition = _playerPlaylist->bestPositionFor(position); if (rtl()) bestPosition = position + 2 * (position - bestPosition) - _playerPlaylist->width(); updateMediaPlaylistPosition(bestPosition); } _playerPlaylist->showFromOther(); } else { _playerPlaylist->hideFromOther(); } }); subscribe(Media::Player::instance()->tracksFinishedNotifier(), [this](AudioMsgId::Type type) { if (type == AudioMsgId::Type::Voice) { auto songState = Media::Player::mixer()->currentState(AudioMsgId::Type::Song); if (!songState.id || IsStoppedOrStopping(songState.state)) { closeBothPlayers(); } } }); subscribe(Media::Player::instance()->trackChangedNotifier(), [this](AudioMsgId::Type type) { if (type == AudioMsgId::Type::Voice) { checkCurrentFloatPlayer(); } }); subscribe(Adaptive::Changed(), [this]() { handleAdaptiveLayoutUpdate(); }); _dialogs->show(); if (Adaptive::OneColumn()) { _history->hide(); } else { _history->show(); } orderWidgets(); #ifndef TDESKTOP_DISABLE_AUTOUPDATE Sandbox::startUpdateCheck(); #endif // !TDESKTOP_DISABLE_AUTOUPDATE } void MainWidget::checkCurrentFloatPlayer() { const auto state = Media::Player::instance()->current(AudioMsgId::Type::Voice); const auto fullId = state.contextId(); const auto last = currentFloatPlayer(); if (last && !last->widget->detached() && last->widget->item()->fullId() == fullId) { return; } if (last) { last->widget->detach(); } if (const auto item = App::histItemById(fullId)) { if (const auto media = item->media()) { if (const auto document = media->document()) { if (document->isVideoMessage()) { createFloatPlayer(item); } } } } } void MainWidget::createFloatPlayer(not_null<HistoryItem*> item) { _playerFloats.push_back(std::make_unique<Float>( this, _controller, item, [this](not_null<Float*> instance, bool visible) { instance->hiddenByWidget = !visible; toggleFloatPlayer(instance); }, [this](not_null<Float*> instance, bool closed) { finishFloatPlayerDrag(instance, closed); })); currentFloatPlayer()->column = Auth().settings().floatPlayerColumn(); currentFloatPlayer()->corner = Auth().settings().floatPlayerCorner(); checkFloatPlayerVisibility(); } void MainWidget::toggleFloatPlayer(not_null<Float*> instance) { auto visible = !instance->hiddenByHistory && !instance->hiddenByWidget && instance->widget->isReady(); if (instance->visible != visible) { instance->widget->resetMouseState(); instance->visible = visible; if (!instance->visibleAnimation.animating() && !instance->hiddenByDrag) { auto finalRect = QRect(getFloatPlayerPosition(instance), instance->widget->size()); instance->animationSide = getFloatPlayerSide(finalRect.center()); } instance->visibleAnimation.start([this, instance] { updateFloatPlayerPosition(instance); }, visible ? 0. : 1., visible ? 1. : 0., st::slideDuration, visible ? anim::easeOutCirc : anim::linear); updateFloatPlayerPosition(instance); } } void MainWidget::checkFloatPlayerVisibility() { auto instance = currentFloatPlayer(); if (!instance) { return; } auto amVisible = false; if (auto item = instance->widget->item()) { Auth().data().queryItemVisibility().notify({ item, &amVisible }, true); } instance->hiddenByHistory = amVisible; toggleFloatPlayer(instance); updateFloatPlayerPosition(instance); } void MainWidget::updateFloatPlayerPosition(not_null<Float*> instance) { auto visible = instance->visibleAnimation.current(instance->visible ? 1. : 0.); if (visible == 0. && !instance->visible) { instance->widget->hide(); if (instance->widget->detached()) { InvokeQueued(instance->widget, [this, instance] { removeFloatPlayer(instance); }); } return; } if (!instance->widget->dragged()) { if (instance->widget->isHidden()) { instance->widget->show(); } auto dragged = instance->draggedAnimation.current(1.); auto position = QPoint(); if (instance->hiddenByDrag) { instance->widget->setOpacity(instance->widget->countOpacityByParent()); position = getFloatPlayerHiddenPosition(instance->dragFrom, instance->widget->size(), instance->animationSide); } else { instance->widget->setOpacity(visible * visible); position = getFloatPlayerPosition(instance); if (visible < 1.) { auto hiddenPosition = getFloatPlayerHiddenPosition(position, instance->widget->size(), instance->animationSide); position.setX(anim::interpolate(hiddenPosition.x(), position.x(), visible)); position.setY(anim::interpolate(hiddenPosition.y(), position.y(), visible)); } } if (dragged < 1.) { position.setX(anim::interpolate(instance->dragFrom.x(), position.x(), dragged)); position.setY(anim::interpolate(instance->dragFrom.y(), position.y(), dragged)); } instance->widget->move(position); } } QPoint MainWidget::getFloatPlayerHiddenPosition(QPoint position, QSize size, RectPart side) const { switch (side) { case RectPart::Left: return QPoint(-size.width(), position.y()); case RectPart::Top: return QPoint(position.x(), -size.height()); case RectPart::Right: return QPoint(width(), position.y()); case RectPart::Bottom: return QPoint(position.x(), height()); } Unexpected("Bad side in MainWidget::getFloatPlayerHiddenPosition()."); } QPoint MainWidget::getFloatPlayerPosition(not_null<Float*> instance) const { auto section = getFloatPlayerSection(instance->column); auto rect = section->rectForFloatPlayer(); auto position = rect.topLeft(); if (IsBottomCorner(instance->corner)) { position.setY(position.y() + rect.height() - instance->widget->height()); } if (IsRightCorner(instance->corner)) { position.setX(position.x() + rect.width() - instance->widget->width()); } return mapFromGlobal(position); } RectPart MainWidget::getFloatPlayerSide(QPoint center) const { auto left = qAbs(center.x()); auto right = qAbs(width() - center.x()); auto top = qAbs(center.y()); auto bottom = qAbs(height() - center.y()); if (left < right && left < top && left < bottom) { return RectPart::Left; } else if (right < top && right < bottom) { return RectPart::Right; } else if (top < bottom) { return RectPart::Top; } return RectPart::Bottom; } void MainWidget::removeFloatPlayer(not_null<Float*> instance) { auto widget = std::move(instance->widget); auto i = std::find_if(_playerFloats.begin(), _playerFloats.end(), [instance](auto &item) { return (item.get() == instance); }); Assert(i != _playerFloats.end()); _playerFloats.erase(i); // ~QWidget() can call HistoryInner::enterEvent() which can // lead to repaintHistoryItem() and we'll have an instance // in _playerFloats with destroyed widget. So we destroy the // instance first and only after that destroy the widget. widget.destroy(); } Window::AbstractSectionWidget *MainWidget::getFloatPlayerSection(Window::Column column) const { if (Adaptive::ThreeColumn()) { if (column == Window::Column::First) { return _dialogs; } else if (column == Window::Column::Second || !_thirdSection) { if (_mainSection) { return _mainSection; } return _history; } return _thirdSection; } else if (Adaptive::Normal()) { if (column == Window::Column::First) { return _dialogs; } else if (_mainSection) { return _mainSection; } return _history; } if (Adaptive::OneColumn() && selectingPeer()) { return _dialogs; } else if (_mainSection) { return _mainSection; } else if (!Adaptive::OneColumn() || _history->peer()) { return _history; } return _dialogs; } void MainWidget::updateFloatPlayerColumnCorner(QPoint center) { Expects(!_playerFloats.empty()); auto size = _playerFloats.back()->widget->size(); auto min = INT_MAX; auto column = Auth().settings().floatPlayerColumn(); auto corner = Auth().settings().floatPlayerCorner(); auto checkSection = [this, center, size, &min, &column, &corner]( Window::AbstractSectionWidget *widget, Window::Column widgetColumn) { auto rect = mapFromGlobal(widget->rectForFloatPlayer()); auto left = rect.x() + (size.width() / 2); auto right = rect.x() + rect.width() - (size.width() / 2); auto top = rect.y() + (size.height() / 2); auto bottom = rect.y() + rect.height() - (size.height() / 2); auto checkCorner = [&](QPoint point, RectPart checked) { auto distance = (point - center).manhattanLength(); if (min > distance) { min = distance; column = widgetColumn; corner = checked; } }; checkCorner({ left, top }, RectPart::TopLeft); checkCorner({ right, top }, RectPart::TopRight); checkCorner({ left, bottom }, RectPart::BottomLeft); checkCorner({ right, bottom }, RectPart::BottomRight); }; if (Adaptive::ThreeColumn()) { checkSection(_dialogs, Window::Column::First); if (_mainSection) { checkSection(_mainSection, Window::Column::Second); } else { checkSection(_history, Window::Column::Second); } if (_thirdSection) { checkSection(_thirdSection, Window::Column::Third); } } else if (Adaptive::Normal()) { checkSection(_dialogs, Window::Column::First); if (_mainSection) { checkSection(_mainSection, Window::Column::Second); } else { checkSection(_history, Window::Column::Second); } } else { if (Adaptive::OneColumn() && selectingPeer()) { checkSection(_dialogs, Window::Column::First); } else if (_mainSection) { checkSection(_mainSection, Window::Column::Second); } else if (!Adaptive::OneColumn() || _history->peer()) { checkSection(_history, Window::Column::Second); } else { checkSection(_dialogs, Window::Column::First); } } if (Auth().settings().floatPlayerColumn() != column) { Auth().settings().setFloatPlayerColumn(column); Auth().saveSettingsDelayed(); } if (Auth().settings().floatPlayerCorner() != corner) { Auth().settings().setFloatPlayerCorner(corner); Auth().saveSettingsDelayed(); } } void MainWidget::finishFloatPlayerDrag(not_null<Float*> instance, bool closed) { instance->dragFrom = instance->widget->pos(); auto center = instance->widget->geometry().center(); if (closed) { instance->hiddenByDrag = true; instance->animationSide = getFloatPlayerSide(center); } updateFloatPlayerColumnCorner(center); instance->column = Auth().settings().floatPlayerColumn(); instance->corner = Auth().settings().floatPlayerCorner(); instance->draggedAnimation.finish(); instance->draggedAnimation.start([this, instance] { updateFloatPlayerPosition(instance); }, 0., 1., st::slideDuration, anim::sineInOut); updateFloatPlayerPosition(instance); if (closed) { if (auto item = instance->widget->item()) { auto voiceData = Media::Player::instance()->current(AudioMsgId::Type::Voice); if (_player && voiceData.contextId() == item->fullId()) { _player->entity()->stopAndClose(); } } instance->widget->detach(); } } bool MainWidget::setForwardDraft(PeerId peerId, MessageIdsList &&items) { Expects(peerId != 0); const auto peer = App::peer(peerId); const auto error = GetErrorTextForForward( peer, Auth().data().idsToItems(items)); if (!error.isEmpty()) { Ui::show(Box<InformBox>(error), LayerOption::KeepOther); return false; } App::history(peer)->setForwardDraft(std::move(items)); Ui::showPeerHistory(peer, ShowAtUnreadMsgId); _history->cancelReply(); return true; } bool MainWidget::shareUrl( not_null<PeerData*> peer, const QString &url, const QString &text) { if (!peer->canWrite()) { Ui::show(Box<InformBox>(lang(lng_share_cant))); return false; } TextWithTags textWithTags = { url + '\n' + text, TextWithTags::Tags() }; MessageCursor cursor = { url.size() + 1, url.size() + 1 + text.size(), QFIXED_MAX }; auto history = App::history(peer->id); history->setLocalDraft( std::make_unique<Data::Draft>(textWithTags, 0, cursor, false)); history->clearEditDraft(); if (_history->peer() == peer) { _history->applyDraft(); } else { Ui::showPeerHistory(peer, ShowAtUnreadMsgId); } return true; } void MainWidget::replyToItem(not_null<HistoryItem*> item) { if (_history->peer() == item->history()->peer || _history->peer() == item->history()->peer->migrateTo()) { _history->replyToMessage(item); } } bool MainWidget::onInlineSwitchChosen(const PeerId &peer, const QString &botAndQuery) { PeerData *p = App::peer(peer); if (!peer || !p->canWrite()) { Ui::show(Box<InformBox>(lang(lng_inline_switch_cant))); return false; } History *h = App::history(peer); TextWithTags textWithTags = { botAndQuery, TextWithTags::Tags() }; MessageCursor cursor = { botAndQuery.size(), botAndQuery.size(), QFIXED_MAX }; h->setLocalDraft(std::make_unique<Data::Draft>(textWithTags, 0, cursor, false)); h->clearEditDraft(); bool opened = _history->peer() && (_history->peer()->id == peer); if (opened) { _history->applyDraft(); } else { Ui::showPeerHistory(peer, ShowAtUnreadMsgId); } return true; } void MainWidget::cancelForwarding(not_null<History*> history) { history->setForwardDraft({}); _history->updateForwarding(); } void MainWidget::finishForwarding(not_null<History*> history) { auto toForward = history->validateForwardDraft(); if (!toForward.empty()) { auto options = ApiWrap::SendOptions(history); Auth().api().forwardMessages(std::move(toForward), options); cancelForwarding(history); } Auth().data().sendHistoryChangeNotifications(); historyToDown(history); dialogsToUp(); } void MainWidget::updateMutedIn(TimeMs delay) { accumulate_max(delay, 24 * 3600 * 1000LL); if (!_updateMutedTimer.isActive() || _updateMutedTimer.remainingTime() > delay) { _updateMutedTimer.start(delay); } } void MainWidget::onUpdateMuted() { App::updateMuted(); } bool MainWidget::onSendPaths(const PeerId &peerId) { Expects(peerId != 0); auto peer = App::peer(peerId); if (!peer->canWrite()) { Ui::show(Box<InformBox>(lang(lng_forward_send_files_cant))); return false; } else if (auto megagroup = peer->asMegagroup()) { if (megagroup->restricted(ChannelRestriction::f_send_media)) { Ui::show(Box<InformBox>(lang(lng_restricted_send_media))); return false; } } Ui::showPeerHistory(peer, ShowAtTheEndMsgId); return _history->confirmSendingFiles(cSendPaths()); } void MainWidget::onFilesOrForwardDrop( const PeerId &peerId, const QMimeData *data) { Expects(peerId != 0); if (data->hasFormat(qsl("application/x-td-forward"))) { if (!setForwardDraft(peerId, Auth().data().takeMimeForwardIds())) { // We've already released the mouse button, so the forwarding is cancelled. if (_hider) { _hider->startHide(); noHider(_hider); } } } else { auto peer = App::peer(peerId); if (!peer->canWrite()) { Ui::show(Box<InformBox>(lang(lng_forward_send_files_cant))); return; } Ui::showPeerHistory(peer, ShowAtTheEndMsgId); _history->confirmSendingFiles(data); } } void MainWidget::notify_botCommandsChanged(UserData *bot) { _history->notify_botCommandsChanged(bot); } void MainWidget::notify_inlineBotRequesting(bool requesting) { _history->notify_inlineBotRequesting(requesting); } void MainWidget::notify_replyMarkupUpdated(const HistoryItem *item) { _history->notify_replyMarkupUpdated(item); } void MainWidget::notify_inlineKeyboardMoved(const HistoryItem *item, int oldKeyboardTop, int newKeyboardTop) { _history->notify_inlineKeyboardMoved(item, oldKeyboardTop, newKeyboardTop); } bool MainWidget::notify_switchInlineBotButtonReceived(const QString &query, UserData *samePeerBot, MsgId samePeerReplyTo) { return _history->notify_switchInlineBotButtonReceived(query, samePeerBot, samePeerReplyTo); } void MainWidget::notify_userIsBotChanged(UserData *bot) { _history->notify_userIsBotChanged(bot); } void MainWidget::notify_migrateUpdated(PeerData *peer) { _history->notify_migrateUpdated(peer); } void MainWidget::notify_historyMuteUpdated(History *history) { _dialogs->notify_historyMuteUpdated(history); } bool MainWidget::cmd_search() { if (Ui::isLayerShown() || !isActiveWindow()) return false; if (_mainSection) { return _mainSection->cmd_search(); } return _history->cmd_search(); } bool MainWidget::cmd_next_chat() { if (Ui::isLayerShown() || !isActiveWindow()) return false; return _history->cmd_next_chat(); } bool MainWidget::cmd_previous_chat() { if (Ui::isLayerShown() || !isActiveWindow()) return false; return _history->cmd_previous_chat(); } void MainWidget::noHider(HistoryHider *destroyed) { if (_hider == destroyed) { _hider = nullptr; if (Adaptive::OneColumn()) { if (_forwardConfirm) { _forwardConfirm->closeBox(); _forwardConfirm = nullptr; } if (_mainSection || (_history->peer() && _history->peer()->id)) { auto animationParams = ([this] { if (_mainSection) { return prepareMainSectionAnimation(_mainSection); } return prepareHistoryAnimation(_history->peer() ? _history->peer()->id : 0); })(); _dialogs->hide(); if (_mainSection) { _mainSection->showAnimated(Window::SlideDirection::FromRight, animationParams); } else { _history->showAnimated(Window::SlideDirection::FromRight, animationParams); } checkFloatPlayerVisibility(); } } else { if (_forwardConfirm) { _forwardConfirm->deleteLater(); _forwardConfirm = nullptr; } } } } void MainWidget::hiderLayer(object_ptr<HistoryHider> h) { if (App::passcoded()) { return; } _hider = std::move(h); connect(_hider, SIGNAL(forwarded()), _dialogs, SLOT(onCancelSearch())); if (Adaptive::OneColumn()) { dialogsToUp(); _hider->hide(); auto animationParams = prepareDialogsAnimation(); if (_mainSection) { _mainSection->hide(); } else { _history->hide(); } if (_dialogs->isHidden()) { _dialogs->show(); updateControlsGeometry(); _dialogs->showAnimated(Window::SlideDirection::FromLeft, animationParams); } } else { _hider->show(); updateControlsGeometry(); _dialogs->activate(); } checkFloatPlayerVisibility(); } void MainWidget::showForwardLayer(MessageIdsList &&items) { hiderLayer(object_ptr<HistoryHider>(this, std::move(items))); } void MainWidget::showSendPathsLayer() { hiderLayer(object_ptr<HistoryHider>(this)); } void MainWidget::deleteLayer(FullMsgId itemId) { if (const auto item = App::histItemById(itemId)) { const auto suggestModerateActions = true; Ui::show(Box<DeleteMessagesBox>(item, suggestModerateActions)); } } void MainWidget::cancelUploadLayer(not_null<HistoryItem*> item) { const auto itemId = item->fullId(); Auth().uploader().pause(itemId); const auto stopUpload = [=] { Ui::hideLayer(); if (const auto item = App::histItemById(itemId)) { const auto history = item->history(); item->destroy(); if (!history->lastMessageKnown()) { Auth().api().requestDialogEntry(history); } Auth().data().sendHistoryChangeNotifications(); } Auth().uploader().unpause(); }; const auto continueUpload = [=] { Auth().uploader().unpause(); }; Ui::show(Box<ConfirmBox>( lang(lng_selected_cancel_sure_this), lang(lng_selected_upload_stop), lang(lng_continue), stopUpload, continueUpload)); } void MainWidget::deletePhotoLayer(PhotoData *photo) { if (!photo) return; Ui::show(Box<ConfirmBox>(lang(lng_delete_photo_sure), lang(lng_box_delete), base::lambda_guarded(this, [=] { Ui::hideLayer(); auto me = App::self(); if (!me) return; if (me->userpicPhotoId() == photo->id) { Messenger::Instance().peerClearPhoto(me->id); } else if (photo->peer && !photo->peer->isUser() && photo->peer->userpicPhotoId() == photo->id) { Messenger::Instance().peerClearPhoto(photo->peer->id); } else { MTP::send(MTPphotos_DeletePhotos(MTP_vector<MTPInputPhoto>(1, MTP_inputPhoto(MTP_long(photo->id), MTP_long(photo->access))))); Auth().storage().remove(Storage::UserPhotosRemoveOne(me->bareId(), photo->id)); } }))); } void MainWidget::shareUrlLayer(const QString &url, const QString &text) { // Don't allow to insert an inline bot query by share url link. if (url.trimmed().startsWith('@')) { return; } hiderLayer(object_ptr<HistoryHider>(this, url, text)); } void MainWidget::inlineSwitchLayer(const QString &botAndQuery) { hiderLayer(object_ptr<HistoryHider>(this, botAndQuery)); } bool MainWidget::selectingPeer(bool withConfirm) const { return _hider ? (withConfirm ? _hider->withConfirm() : true) : false; } bool MainWidget::selectingPeerForInlineSwitch() { return selectingPeer() ? !_hider->botAndQuery().isEmpty() : false; } void MainWidget::offerPeer(PeerId peer) { Ui::hideLayer(); if (_hider->offerPeer(peer) && Adaptive::OneColumn()) { _forwardConfirm = Ui::show(Box<ConfirmBox>(_hider->offeredText(), lang(lng_forward_send), base::lambda_guarded(this, [this] { _hider->forward(); if (_forwardConfirm) _forwardConfirm->closeBox(); if (_hider) _hider->offerPeer(0); }), base::lambda_guarded(this, [this] { if (_hider && _forwardConfirm) _hider->offerPeer(0); }))); } } void MainWidget::dialogsActivate() { _dialogs->activate(); } bool MainWidget::leaveChatFailed(PeerData *peer, const RPCError &error) { if (MTP::isDefaultHandledError(error)) return false; if (error.type() == qstr("USER_NOT_PARTICIPANT") || error.type() == qstr("CHAT_ID_INVALID") || error.type() == qstr("PEER_ID_INVALID")) { // left this chat already deleteConversation(peer); return true; } return false; } void MainWidget::deleteHistoryAfterLeave(PeerData *peer, const MTPUpdates &updates) { sentUpdatesReceived(updates); deleteConversation(peer); } void MainWidget::deleteHistoryPart(DeleteHistoryRequest request, const MTPmessages_AffectedHistory &result) { auto peer = request.peer; auto &d = result.c_messages_affectedHistory(); if (peer && peer->isChannel()) { peer->asChannel()->ptsUpdateAndApply(d.vpts.v, d.vpts_count.v); } else { ptsUpdateAndApply(d.vpts.v, d.vpts_count.v); } auto offset = d.voffset.v; if (offset <= 0) { cRefReportSpamStatuses().remove(peer->id); Local::writeReportSpamStatuses(); return; } auto flags = MTPmessages_DeleteHistory::Flags(0); if (request.justClearHistory) { flags |= MTPmessages_DeleteHistory::Flag::f_just_clear; } MTP::send(MTPmessages_DeleteHistory(MTP_flags(flags), peer->input, MTP_int(0)), rpcDone(&MainWidget::deleteHistoryPart, request)); } void MainWidget::deleteMessages( not_null<PeerData*> peer, const QVector<MTPint> &ids, bool forEveryone) { if (const auto channel = peer->asChannel()) { MTP::send( MTPchannels_DeleteMessages( channel->inputChannel, MTP_vector<MTPint>(ids)), rpcDone(&MainWidget::messagesAffected, peer)); } else { auto flags = MTPmessages_DeleteMessages::Flags(0); if (forEveryone) { flags |= MTPmessages_DeleteMessages::Flag::f_revoke; } MTP::send( MTPmessages_DeleteMessages( MTP_flags(flags), MTP_vector<MTPint>(ids)), rpcDone(&MainWidget::messagesAffected, peer)); } } void MainWidget::deletedContact(UserData *user, const MTPcontacts_Link &result) { auto &d(result.c_contacts_link()); App::feedUsers(MTP_vector<MTPUser>(1, d.vuser)); App::feedUserLink(MTP_int(peerToUser(user->id)), d.vmy_link, d.vforeign_link); } void MainWidget::removeDialog(Dialogs::Key key) { _dialogs->removeDialog(key); } void MainWidget::deleteConversation( not_null<PeerData*> peer, bool deleteHistory) { if (_controller->activeChatCurrent().peer() == peer) { Ui::showChatsList(); } if (const auto history = App::historyLoaded(peer->id)) { Auth().data().setPinnedDialog(history, false); removeDialog(history); if (const auto channel = peer->asMegagroup()) { channel->addFlags(MTPDchannel::Flag::f_left); if (const auto from = channel->mgInfo->migrateFromPtr) { if (const auto migrated = App::historyLoaded(from)) { migrated->updateChatListExistence(); } } } history->clear(); if (deleteHistory) { history->markFullyLoaded(); } } if (const auto channel = peer->asChannel()) { channel->ptsWaitingForShortPoll(-1); } if (deleteHistory) { DeleteHistoryRequest request = { peer, false }; MTP::send( MTPmessages_DeleteHistory( MTP_flags(0), peer->input, MTP_int(0)), rpcDone(&MainWidget::deleteHistoryPart, request)); } } void MainWidget::deleteAndExit(ChatData *chat) { PeerData *peer = chat; MTP::send(MTPmessages_DeleteChatUser(chat->inputChat, App::self()->inputUser), rpcDone(&MainWidget::deleteHistoryAfterLeave, peer), rpcFail(&MainWidget::leaveChatFailed, peer)); } void MainWidget::addParticipants( not_null<PeerData*> chatOrChannel, const std::vector<not_null<UserData*>> &users) { if (auto chat = chatOrChannel->asChat()) { for_const (auto user, users) { MTP::send( MTPmessages_AddChatUser( chat->inputChat, user->inputUser, MTP_int(ForwardOnAdd)), rpcDone(&MainWidget::sentUpdatesReceived), rpcFail(&MainWidget::addParticipantFail, { user, chat }), 0, 5); } } else if (auto channel = chatOrChannel->asChannel()) { QVector<MTPInputUser> inputUsers; inputUsers.reserve(qMin(int(users.size()), int(MaxUsersPerInvite))); for (auto i = users.cbegin(), e = users.cend(); i != e; ++i) { inputUsers.push_back((*i)->inputUser); if (inputUsers.size() == MaxUsersPerInvite) { MTP::send( MTPchannels_InviteToChannel( channel->inputChannel, MTP_vector<MTPInputUser>(inputUsers)), rpcDone(&MainWidget::inviteToChannelDone, { channel }), rpcFail(&MainWidget::addParticipantsFail, { channel }), 0, 5); inputUsers.clear(); } } if (!inputUsers.isEmpty()) { MTP::send( MTPchannels_InviteToChannel( channel->inputChannel, MTP_vector<MTPInputUser>(inputUsers)), rpcDone(&MainWidget::inviteToChannelDone, { channel }), rpcFail(&MainWidget::addParticipantsFail, { channel }), 0, 5); } } } bool MainWidget::addParticipantFail(UserAndPeer data, const RPCError &error) { if (MTP::isDefaultHandledError(error)) return false; QString text = lang(lng_failed_add_participant); if (error.type() == qstr("USER_LEFT_CHAT")) { // trying to return a user who has left } else if (error.type() == qstr("USER_KICKED")) { // trying to return a user who was kicked by admin text = lang(lng_cant_invite_banned); } else if (error.type() == qstr("USER_PRIVACY_RESTRICTED")) { text = lang(lng_cant_invite_privacy); } else if (error.type() == qstr("USER_NOT_MUTUAL_CONTACT")) { // trying to return user who does not have me in contacts text = lang(lng_failed_add_not_mutual); } else if (error.type() == qstr("USER_ALREADY_PARTICIPANT") && data.user->botInfo) { text = lang(lng_bot_already_in_group); } else if (error.type() == qstr("PEER_FLOOD")) { text = PeerFloodErrorText((data.peer->isChat() || data.peer->isMegagroup()) ? PeerFloodType::InviteGroup : PeerFloodType::InviteChannel); } Ui::show(Box<InformBox>(text)); return false; } bool MainWidget::addParticipantsFail( not_null<ChannelData*> channel, const RPCError &error) { if (MTP::isDefaultHandledError(error)) return false; QString text = lang(lng_failed_add_participant); if (error.type() == qstr("USER_LEFT_CHAT")) { // trying to return banned user to his group } else if (error.type() == qstr("USER_KICKED")) { // trying to return a user who was kicked by admin text = lang(lng_cant_invite_banned); } else if (error.type() == qstr("USER_PRIVACY_RESTRICTED")) { text = lang(channel->isMegagroup() ? lng_cant_invite_privacy : lng_cant_invite_privacy_channel); } else if (error.type() == qstr("USER_NOT_MUTUAL_CONTACT")) { // trying to return user who does not have me in contacts text = lang(channel->isMegagroup() ? lng_failed_add_not_mutual : lng_failed_add_not_mutual_channel); } else if (error.type() == qstr("PEER_FLOOD")) { text = PeerFloodErrorText(PeerFloodType::InviteGroup); } Ui::show(Box<InformBox>(text)); return false; } bool MainWidget::sendMessageFail(const RPCError &error) { if (MTP::isDefaultHandledError(error)) return false; if (error.type() == qstr("PEER_FLOOD")) { Ui::show(Box<InformBox>(PeerFloodErrorText(PeerFloodType::Send))); return true; } else if (error.type() == qstr("USER_BANNED_IN_CHANNEL")) { const auto link = textcmdLink( Messenger::Instance().createInternalLinkFull(qsl("spambot")), lang(lng_cant_more_info)); const auto text = lng_error_public_groups_denied(lt_more_info, link); Ui::show(Box<InformBox>(text)); return true; } return false; } void MainWidget::onCacheBackground() { if (Window::Theme::Background()->tile()) { auto &bg = Window::Theme::Background()->pixmapForTiled(); auto result = QImage(_willCacheFor.width() * cIntRetinaFactor(), _willCacheFor.height() * cIntRetinaFactor(), QImage::Format_RGB32); result.setDevicePixelRatio(cRetinaFactor()); { QPainter p(&result); auto left = 0; auto top = 0; auto right = _willCacheFor.width(); auto bottom = _willCacheFor.height(); auto w = bg.width() / cRetinaFactor(); auto h = bg.height() / cRetinaFactor(); auto sx = 0; auto sy = 0; auto cx = qCeil(_willCacheFor.width() / w); auto cy = qCeil(_willCacheFor.height() / h); for (int i = sx; i < cx; ++i) { for (int j = sy; j < cy; ++j) { p.drawPixmap(QPointF(i * w, j * h), bg); } } } _cachedX = 0; _cachedY = 0; _cachedBackground = App::pixmapFromImageInPlace(std::move(result)); } else { auto &bg = Window::Theme::Background()->pixmap(); QRect to, from; Window::Theme::ComputeBackgroundRects(_willCacheFor, bg.size(), to, from); _cachedX = to.x(); _cachedY = to.y(); _cachedBackground = App::pixmapFromImageInPlace(bg.toImage().copy(from).scaled(to.width() * cIntRetinaFactor(), to.height() * cIntRetinaFactor(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); _cachedBackground.setDevicePixelRatio(cRetinaFactor()); } _cachedFor = _willCacheFor; } Dialogs::IndexedList *MainWidget::contactsList() { return _dialogs->contactsList(); } Dialogs::IndexedList *MainWidget::dialogsList() { return _dialogs->dialogsList(); } Dialogs::IndexedList *MainWidget::contactsNoDialogsList() { return _dialogs->contactsNoDialogsList(); } void MainWidget::sendMessage(const MessageToSend &message) { const auto history = message.history; const auto peer = history->peer; auto &textWithTags = message.textWithTags; auto options = ApiWrap::SendOptions(history); options.clearDraft = message.clearDraft; options.replyTo = message.replyTo; options.generateLocal = true; options.webPageId = message.webPageId; Auth().api().sendAction(options); if (!peer->canWrite()) { return; } saveRecentHashtags(textWithTags.text); auto sending = TextWithEntities(); auto left = TextWithEntities { textWithTags.text, ConvertTextTagsToEntities(textWithTags.tags) }; auto prepareFlags = Ui::ItemTextOptions(history, App::self()).flags; TextUtilities::PrepareForSending(left, prepareFlags); HistoryItem *lastMessage = nullptr; while (TextUtilities::CutPart(sending, left, MaxMessageSize)) { auto newId = FullMsgId(peerToChannel(peer->id), clientMsgId()); auto randomId = rand_value<uint64>(); TextUtilities::Trim(sending); App::historyRegRandom(randomId, newId); App::historyRegSentData(randomId, peer->id, sending.text); MTPstring msgText(MTP_string(sending.text)); auto flags = NewMessageFlags(peer) | MTPDmessage::Flag::f_entities; auto sendFlags = MTPmessages_SendMessage::Flags(0); if (message.replyTo) { flags |= MTPDmessage::Flag::f_reply_to_msg_id; sendFlags |= MTPmessages_SendMessage::Flag::f_reply_to_msg_id; } MTPMessageMedia media = MTP_messageMediaEmpty(); if (message.webPageId == CancelledWebPageId) { sendFlags |= MTPmessages_SendMessage::Flag::f_no_webpage; } else if (message.webPageId) { auto page = Auth().data().webpage(message.webPageId); media = MTP_messageMediaWebPage( MTP_webPagePending( MTP_long(page->id), MTP_int(page->pendingTill))); flags |= MTPDmessage::Flag::f_media; } bool channelPost = peer->isChannel() && !peer->isMegagroup(); bool silentPost = channelPost && peer->notifySilentPosts(); if (channelPost) { flags |= MTPDmessage::Flag::f_views; flags |= MTPDmessage::Flag::f_post; } if (!channelPost) { flags |= MTPDmessage::Flag::f_from_id; } else if (peer->asChannel()->addsSignature()) { flags |= MTPDmessage::Flag::f_post_author; } if (silentPost) { sendFlags |= MTPmessages_SendMessage::Flag::f_silent; } auto localEntities = TextUtilities::EntitiesToMTP(sending.entities); auto sentEntities = TextUtilities::EntitiesToMTP(sending.entities, TextUtilities::ConvertOption::SkipLocal); if (!sentEntities.v.isEmpty()) { sendFlags |= MTPmessages_SendMessage::Flag::f_entities; } if (message.clearDraft) { sendFlags |= MTPmessages_SendMessage::Flag::f_clear_draft; history->clearCloudDraft(); } auto messageFromId = channelPost ? 0 : Auth().userId(); auto messagePostAuthor = channelPost ? (Auth().user()->firstName + ' ' + Auth().user()->lastName) : QString(); lastMessage = history->addNewMessage( MTP_message( MTP_flags(flags), MTP_int(newId.msg), MTP_int(messageFromId), peerToMTP(peer->id), MTPnullFwdHeader, MTPint(), MTP_int(message.replyTo), MTP_int(unixtime()), msgText, media, MTPnullMarkup, localEntities, MTP_int(1), MTPint(), MTP_string(messagePostAuthor), MTPlong()), NewMessageUnread); history->sendRequestId = MTP::send( MTPmessages_SendMessage( MTP_flags(sendFlags), peer->input, MTP_int(message.replyTo), msgText, MTP_long(randomId), MTPnullMarkup, sentEntities), rpcDone(&MainWidget::sentUpdatesReceived, randomId), rpcFail(&MainWidget::sendMessageFail), 0, 0, history->sendRequestId); } finishForwarding(history); } void MainWidget::saveRecentHashtags(const QString &text) { bool found = false; QRegularExpressionMatch m; RecentHashtagPack recent(cRecentWriteHashtags()); for (int32 i = 0, next = 0; (m = TextUtilities::RegExpHashtag().match(text, i)).hasMatch(); i = next) { i = m.capturedStart(); next = m.capturedEnd(); if (m.hasMatch()) { if (!m.capturedRef(1).isEmpty()) { ++i; } if (!m.capturedRef(2).isEmpty()) { --next; } } const auto tag = text.mid(i + 1, next - i - 1); if (TextUtilities::RegExpHashtagExclude().match(tag).hasMatch()) { continue; } if (!found && cRecentWriteHashtags().isEmpty() && cRecentSearchHashtags().isEmpty()) { Local::readRecentHashtagsAndBots(); recent = cRecentWriteHashtags(); } found = true; Stickers::IncrementRecentHashtag(recent, tag); } if (found) { cSetRecentWriteHashtags(recent); Local::writeRecentHashtagsAndBots(); } } void MainWidget::unreadCountChanged(not_null<History*> history) { _history->unreadCountChanged(history); } TimeMs MainWidget::highlightStartTime(not_null<const HistoryItem*> item) const { return _history->highlightStartTime(item); } bool MainWidget::historyInSelectionMode() const { return _history->inSelectionMode(); } void MainWidget::sendBotCommand(PeerData *peer, UserData *bot, const QString &cmd, MsgId replyTo) { _history->sendBotCommand(peer, bot, cmd, replyTo); } void MainWidget::hideSingleUseKeyboard(PeerData *peer, MsgId replyTo) { _history->hideSingleUseKeyboard(peer, replyTo); } void MainWidget::app_sendBotCallback( not_null<const HistoryMessageMarkupButton*> button, not_null<const HistoryItem*> msg, int row, int column) { _history->app_sendBotCallback(button, msg, row, column); } bool MainWidget::insertBotCommand(const QString &cmd) { return _history->insertBotCommand(cmd); } void MainWidget::searchMessages(const QString &query, Dialogs::Key inChat) { _dialogs->searchMessages(query, inChat); if (Adaptive::OneColumn()) { Ui::showChatsList(); } else { _dialogs->activate(); } } void MainWidget::itemEdited(not_null<HistoryItem*> item) { if (_history->peer() == item->history()->peer || (_history->peer() && _history->peer() == item->history()->peer->migrateTo())) { _history->itemEdited(item); } } void MainWidget::checkLastUpdate(bool afterSleep) { auto n = getms(true); if (_lastUpdateTime && n > _lastUpdateTime + (afterSleep ? NoUpdatesAfterSleepTimeout : NoUpdatesTimeout)) { _lastUpdateTime = n; MTP::ping(); } } void MainWidget::messagesAffected( not_null<PeerData*> peer, const MTPmessages_AffectedMessages &result) { const auto &data = result.c_messages_affectedMessages(); if (const auto channel = peer->asChannel()) { channel->ptsUpdateAndApply(data.vpts.v, data.vpts_count.v); } else { ptsUpdateAndApply(data.vpts.v, data.vpts_count.v); } if (const auto history = App::historyLoaded(peer)) { if (!history->lastMessageKnown()) { Auth().api().requestDialogEntry(history); } } } void MainWidget::handleAudioUpdate(const AudioMsgId &audioId) { using State = Media::Player::State; const auto document = audioId.audio(); auto state = Media::Player::mixer()->currentState(audioId.type()); if (state.id == audioId && state.state == State::StoppedAtStart) { state.state = State::Stopped; Media::Player::mixer()->clearStoppedAtStart(audioId); auto filepath = document->filepath(DocumentData::FilePathResolveSaveFromData); if (!filepath.isEmpty()) { if (documentIsValidMediaFile(filepath)) { File::Launch(filepath); } } } if (state.id == audioId) { if (!Media::Player::IsStoppedOrStopping(state.state)) { createPlayer(); } } if (const auto item = App::histItemById(audioId.contextId())) { Auth().data().requestItemRepaint(item); } if (const auto items = InlineBots::Layout::documentItems()) { if (const auto i = items->find(document); i != items->end()) { for (const auto item : i->second) { item->update(); } } } } void MainWidget::switchToPanelPlayer() { if (_playerUsingPanel) return; _playerUsingPanel = true; _player->hide(anim::type::normal); _playerVolume.destroyDelayed(); _playerPlaylist->hideIgnoringEnterEvents(); Media::Player::instance()->usePanelPlayer().notify(true, true); } void MainWidget::switchToFixedPlayer() { if (!_playerUsingPanel) return; _playerUsingPanel = false; if (!_player) { createPlayer(); } else { _player->show(anim::type::normal); if (!_playerVolume) { _playerVolume.create(this); _player->entity()->volumeWidgetCreated(_playerVolume); updateMediaPlayerPosition(); } } Media::Player::instance()->usePanelPlayer().notify(false, true); _playerPanel->hideIgnoringEnterEvents(); } void MainWidget::closeBothPlayers() { if (_playerUsingPanel) { _playerUsingPanel = false; _player.destroyDelayed(); } else if (_player) { _player->hide(anim::type::normal); } _playerVolume.destroyDelayed(); Media::Player::instance()->usePanelPlayer().notify(false, true); _playerPanel->hideIgnoringEnterEvents(); _playerPlaylist->hideIgnoringEnterEvents(); Media::Player::instance()->stop(AudioMsgId::Type::Voice); Media::Player::instance()->stop(AudioMsgId::Type::Song); Shortcuts::disableMediaShortcuts(); } void MainWidget::createPlayer() { if (_playerUsingPanel) { return; } if (!_player) { _player.create(this); rpl::merge( _player->heightValue() | rpl::map([] { return true; }), _player->shownValue() ) | rpl::start_with_next( [this] { playerHeightUpdated(); }, _player->lifetime()); _player->entity()->setCloseCallback([this] { closeBothPlayers(); }); _playerVolume.create(this); _player->entity()->volumeWidgetCreated(_playerVolume); orderWidgets(); if (_a_show.animating()) { _player->show(anim::type::instant); _player->setVisible(false); Shortcuts::enableMediaShortcuts(); } else { _player->hide(anim::type::instant); } } if (_player && !_player->toggled()) { if (!_a_show.animating()) { _player->show(anim::type::normal); _playerHeight = _contentScrollAddToY = _player->contentHeight(); updateControlsGeometry(); Shortcuts::enableMediaShortcuts(); } } } void MainWidget::playerHeightUpdated() { if (!_player) { // Player could be already "destroyDelayed", but still handle events. return; } auto playerHeight = _player->contentHeight(); if (playerHeight != _playerHeight) { _contentScrollAddToY += playerHeight - _playerHeight; _playerHeight = playerHeight; updateControlsGeometry(); } if (!_playerHeight && _player->isHidden()) { auto state = Media::Player::mixer()->currentState(Media::Player::instance()->getActiveType()); if (!state.id || Media::Player::IsStoppedOrStopping(state.state)) { _playerVolume.destroyDelayed(); _player.destroyDelayed(); } } } void MainWidget::setCurrentCall(Calls::Call *call) { _currentCall = call; if (_currentCall) { subscribe(_currentCall->stateChanged(), [this](Calls::Call::State state) { using State = Calls::Call::State; if (state == State::Established) { createCallTopBar(); } else { destroyCallTopBar(); } }); } else { destroyCallTopBar(); } } void MainWidget::createCallTopBar() { Expects(_currentCall != nullptr); _callTopBar.create(this, object_ptr<Calls::TopBar>(this, _currentCall)); _callTopBar->heightValue( ) | rpl::start_with_next([this](int value) { callTopBarHeightUpdated(value); }, lifetime()); orderWidgets(); if (_a_show.animating()) { _callTopBar->show(anim::type::instant); _callTopBar->setVisible(false); } else { _callTopBar->hide(anim::type::instant); _callTopBar->show(anim::type::normal); _callTopBarHeight = _contentScrollAddToY = _callTopBar->height(); updateControlsGeometry(); } } void MainWidget::destroyCallTopBar() { if (_callTopBar) { _callTopBar->hide(anim::type::normal); } } void MainWidget::callTopBarHeightUpdated(int callTopBarHeight) { if (!callTopBarHeight && !_currentCall) { _callTopBar.destroyDelayed(); } if (callTopBarHeight != _callTopBarHeight) { _contentScrollAddToY += callTopBarHeight - _callTopBarHeight; _callTopBarHeight = callTopBarHeight; updateControlsGeometry(); } } void MainWidget::documentLoadProgress(FileLoader *loader) { if (auto documentId = loader ? loader->objId() : 0) { documentLoadProgress(Auth().data().document(documentId)); } } void MainWidget::documentLoadProgress(DocumentData *document) { if (document->loaded()) { document->performActionOnLoad(); } Auth().data().requestDocumentViewRepaint(document); Auth().documentUpdated.notify(document, true); if (!document->loaded() && document->isAudioFile()) { Media::Player::instance()->documentLoadProgress(document); } } void MainWidget::documentLoadFailed(FileLoader *loader, bool started) { auto documentId = loader ? loader->objId() : 0; if (!documentId) return; auto document = Auth().data().document(documentId); if (started) { auto failedFileName = loader->fileName(); Ui::show(Box<ConfirmBox>(lang(lng_download_finish_failed), base::lambda_guarded(this, [=] { Ui::hideLayer(); if (document) document->save(failedFileName); }))); } else { Ui::show(Box<ConfirmBox>(lang(lng_download_path_failed), lang(lng_download_path_settings), base::lambda_guarded(this, [=] { Global::SetDownloadPath(QString()); Global::SetDownloadPathBookmark(QByteArray()); Ui::show(Box<DownloadPathBox>()); Global::RefDownloadPathChanged().notify(); }))); } if (document) { if (document->loading()) document->cancel(); document->status = FileDownloadFailed; } } void MainWidget::inlineResultLoadProgress(FileLoader *loader) { //InlineBots::Result *result = InlineBots::resultFromLoader(loader); //if (!result) return; //result->loaded(); //Ui::repaintInlineItem(); } void MainWidget::inlineResultLoadFailed(FileLoader *loader, bool started) { //InlineBots::Result *result = InlineBots::resultFromLoader(loader); //if (!result) return; //result->loaded(); //Ui::repaintInlineItem(); } void MainWidget::onSendFileConfirm( const std::shared_ptr<FileLoadResult> &file) { _history->sendFileConfirmed(file); } bool MainWidget::onSendSticker(DocumentData *document) { return _history->onStickerSend(document); } void MainWidget::dialogsCancelled() { if (_hider) { _hider->startHide(); noHider(_hider); } _history->activate(); } void MainWidget::insertCheckedServiceNotification(const TextWithEntities &message, const MTPMessageMedia &media, int32 date) { auto flags = MTPDmessage::Flag::f_entities | MTPDmessage::Flag::f_from_id | MTPDmessage_ClientFlag::f_clientside_unread; auto sending = TextWithEntities(), left = message; HistoryItem *item = nullptr; while (TextUtilities::CutPart(sending, left, MaxMessageSize)) { auto localEntities = TextUtilities::EntitiesToMTP(sending.entities); item = App::histories().addNewMessage( MTP_message( MTP_flags(flags), MTP_int(clientMsgId()), MTP_int(ServiceUserId), MTP_peerUser(MTP_int(Auth().userId())), MTPnullFwdHeader, MTPint(), MTPint(), MTP_int(date), MTP_string(sending.text), media, MTPnullMarkup, localEntities, MTPint(), MTPint(), MTPstring(), MTPlong()), NewMessageUnread); } Auth().data().sendHistoryChangeNotifications(); } void MainWidget::serviceHistoryDone(const MTPmessages_Messages &msgs) { auto handleResult = [&](auto &&result) { App::feedUsers(result.vusers); App::feedChats(result.vchats); App::feedMsgs(result.vmessages, NewMessageLast); }; switch (msgs.type()) { case mtpc_messages_messages: handleResult(msgs.c_messages_messages()); break; case mtpc_messages_messagesSlice: handleResult(msgs.c_messages_messagesSlice()); break; case mtpc_messages_channelMessages: LOG(("API Error: received messages.channelMessages! (MainWidget::serviceHistoryDone)")); handleResult(msgs.c_messages_channelMessages()); break; case mtpc_messages_messagesNotModified: LOG(("API Error: received messages.messagesNotModified! (MainWidget::serviceHistoryDone)")); break; } App::wnd()->showDelayedServiceMsgs(); } bool MainWidget::serviceHistoryFail(const RPCError &error) { if (MTP::isDefaultHandledError(error)) return false; App::wnd()->showDelayedServiceMsgs(); return false; } bool MainWidget::isIdle() const { return _isIdle; } void MainWidget::clearCachedBackground() { _cachedBackground = QPixmap(); _cacheBackgroundTimer.stop(); update(); } QPixmap MainWidget::cachedBackground(const QRect &forRect, int &x, int &y) { if (!_cachedBackground.isNull() && forRect == _cachedFor) { x = _cachedX; y = _cachedY; return _cachedBackground; } if (_willCacheFor != forRect || !_cacheBackgroundTimer.isActive()) { _willCacheFor = forRect; _cacheBackgroundTimer.start(CacheBackgroundTimeout); } return QPixmap(); } void MainWidget::updateScrollColors() { _history->updateScrollColors(); } void MainWidget::setChatBackground(const App::WallPaper &wp) { _background = std::make_unique<App::WallPaper>(wp); _background->full->loadEvenCancelled(); checkChatBackground(); } bool MainWidget::chatBackgroundLoading() { return (_background != nullptr); } float64 MainWidget::chatBackgroundProgress() const { if (_background) { return _background->full->progress(); } return 1.; } void MainWidget::checkChatBackground() { if (_background) { if (_background->full->loaded()) { if (_background->full->isNull()) { Window::Theme::Background()->setImage(Window::Theme::kDefaultBackground); } else if (false || _background->id == Window::Theme::kInitialBackground || _background->id == Window::Theme::kDefaultBackground) { Window::Theme::Background()->setImage(_background->id); } else { Window::Theme::Background()->setImage(_background->id, _background->full->pix().toImage()); } _background = nullptr; QTimer::singleShot(0, this, SLOT(update())); } } } ImagePtr MainWidget::newBackgroundThumb() { return _background ? _background->thumb : ImagePtr(); } void MainWidget::messageDataReceived(ChannelData *channel, MsgId msgId) { _history->messageDataReceived(channel, msgId); } void MainWidget::updateBotKeyboard(History *h) { _history->updateBotKeyboard(h); } void MainWidget::pushReplyReturn(not_null<HistoryItem*> item) { _history->pushReplyReturn(item); } void MainWidget::setInnerFocus() { if (_hider || !_history->peer()) { if (_hider && _hider->wasOffered()) { _hider->setFocus(); } else if (!_hider && _mainSection) { _mainSection->setInnerFocus(); } else if (!_hider && _thirdSection) { _thirdSection->setInnerFocus(); } else { dialogsActivate(); } } else if (_mainSection) { _mainSection->setInnerFocus(); } else if (_history->peer() || !_thirdSection) { _history->setInnerFocus(); } else { _thirdSection->setInnerFocus(); } } void MainWidget::scheduleViewIncrement(HistoryItem *item) { PeerData *peer = item->history()->peer; ViewsIncrement::iterator i = _viewsIncremented.find(peer); if (i != _viewsIncremented.cend()) { if (i.value().contains(item->id)) return; } else { i = _viewsIncremented.insert(peer, ViewsIncrementMap()); } i.value().insert(item->id, true); ViewsIncrement::iterator j = _viewsToIncrement.find(peer); if (j == _viewsToIncrement.cend()) { j = _viewsToIncrement.insert(peer, ViewsIncrementMap()); _viewsIncrementTimer.start(SendViewsTimeout); } j.value().insert(item->id, true); } void MainWidget::onViewsIncrement() { for (ViewsIncrement::iterator i = _viewsToIncrement.begin(); i != _viewsToIncrement.cend();) { if (_viewsIncrementRequests.contains(i.key())) { ++i; continue; } QVector<MTPint> ids; ids.reserve(i.value().size()); for (ViewsIncrementMap::const_iterator j = i.value().cbegin(), end = i.value().cend(); j != end; ++j) { ids.push_back(MTP_int(j.key())); } auto req = MTP::send(MTPmessages_GetMessagesViews(i.key()->input, MTP_vector<MTPint>(ids), MTP_bool(true)), rpcDone(&MainWidget::viewsIncrementDone, ids), rpcFail(&MainWidget::viewsIncrementFail), 0, 5); _viewsIncrementRequests.insert(i.key(), req); i = _viewsToIncrement.erase(i); } } void MainWidget::viewsIncrementDone(QVector<MTPint> ids, const MTPVector<MTPint> &result, mtpRequestId req) { auto &v = result.v; if (ids.size() == v.size()) { for (ViewsIncrementRequests::iterator i = _viewsIncrementRequests.begin(); i != _viewsIncrementRequests.cend(); ++i) { if (i.value() == req) { PeerData *peer = i.key(); ChannelId channel = peerToChannel(peer->id); for (int32 j = 0, l = ids.size(); j < l; ++j) { if (HistoryItem *item = App::histItemById(channel, ids.at(j).v)) { item->setViewsCount(v.at(j).v); } } _viewsIncrementRequests.erase(i); break; } } } if (!_viewsToIncrement.isEmpty() && !_viewsIncrementTimer.isActive()) { _viewsIncrementTimer.start(SendViewsTimeout); } } bool MainWidget::viewsIncrementFail(const RPCError &error, mtpRequestId req) { if (MTP::isDefaultHandledError(error)) return false; for (ViewsIncrementRequests::iterator i = _viewsIncrementRequests.begin(); i != _viewsIncrementRequests.cend(); ++i) { if (i.value() == req) { _viewsIncrementRequests.erase(i); break; } } if (!_viewsToIncrement.isEmpty() && !_viewsIncrementTimer.isActive()) { _viewsIncrementTimer.start(SendViewsTimeout); } return false; } void MainWidget::createDialog(Dialogs::Key key) { _dialogs->createDialog(key); } void MainWidget::choosePeer(PeerId peerId, MsgId showAtMsgId) { if (selectingPeer()) { offerPeer(peerId); } else { Ui::showPeerHistory(peerId, showAtMsgId); } } void MainWidget::clearBotStartToken(PeerData *peer) { if (peer && peer->isUser() && peer->asUser()->botInfo) { peer->asUser()->botInfo->startToken = QString(); } } void MainWidget::ctrlEnterSubmitUpdated() { _history->updateFieldSubmitSettings(); } void MainWidget::ui_showPeerHistory( PeerId peerId, const SectionShow ¶ms, MsgId showAtMsgId) { if (auto peer = App::peerLoaded(peerId)) { if (peer->migrateTo()) { peer = peer->migrateTo(); peerId = peer->id; if (showAtMsgId > 0) showAtMsgId = -showAtMsgId; } auto restriction = peer->restrictionReason(); if (!restriction.isEmpty()) { if (params.activation != anim::activation::background) { Ui::show(Box<InformBox>(restriction)); } return; } } _controller->dialogsListFocused().set(false, true); _a_dialogsWidth.finish(); using Way = SectionShow::Way; auto way = params.way; bool back = (way == Way::Backward || !peerId); bool foundInStack = !peerId; if (foundInStack || (way == Way::ClearStack)) { for_const (auto &item, _stack) { clearBotStartToken(item->peer()); } _stack.clear(); } else { for (auto i = 0, s = int(_stack.size()); i < s; ++i) { if (_stack.at(i)->type() == HistoryStackItem && _stack.at(i)->peer()->id == peerId) { foundInStack = true; while (int(_stack.size()) > i + 1) { clearBotStartToken(_stack.back()->peer()); _stack.pop_back(); } _stack.pop_back(); if (!back) { back = true; } break; } } if (const auto activeChat = _controller->activeChatCurrent()) { if (const auto peer = activeChat.peer()) { if (way == Way::Forward && peer->id == peerId) { way = Way::ClearStack; } } } } const auto wasActivePeer = _controller->activeChatCurrent().peer(); if (params.activation != anim::activation::background) { Ui::hideSettingsAndLayer(); } if (_hider) { _hider->startHide(); _hider = nullptr; } auto animatedShow = [&] { if (_a_show.animating() || App::passcoded() || (params.animated == anim::type::instant)) { return false; } if (!peerId) { if (Adaptive::OneColumn()) { return _dialogs->isHidden(); } else { return false; } } if (_history->isHidden()) { if (!Adaptive::OneColumn() && way == Way::ClearStack) { return false; } return (_mainSection != nullptr) || (Adaptive::OneColumn() && !_dialogs->isHidden()); } if (back || way == Way::Forward) { return true; } return false; }; auto animationParams = animatedShow() ? prepareHistoryAnimation(peerId) : Window::SectionSlideParams(); if (!back && (way != Way::ClearStack)) { // This may modify the current section, for example remove its contents. saveSectionInStack(); } if (_history->peer() && _history->peer()->id != peerId && way != Way::Forward) { clearBotStartToken(_history->peer()); } _history->showHistory(peerId, showAtMsgId); auto noPeer = !_history->peer(); auto onlyDialogs = noPeer && Adaptive::OneColumn(); if (_mainSection) { _mainSection->hide(); _mainSection->deleteLater(); _mainSection = nullptr; } updateControlsGeometry(); if (noPeer) { _controller->setActiveChatEntry(Dialogs::Key()); } if (onlyDialogs) { _history->hide(); if (!_a_show.animating()) { if (animationParams) { auto direction = back ? Window::SlideDirection::FromLeft : Window::SlideDirection::FromRight; _dialogs->showAnimated(direction, animationParams); } else { _dialogs->showFast(); } } } else { const auto nowActivePeer = _controller->activeChatCurrent().peer(); if (nowActivePeer && nowActivePeer != wasActivePeer) { if (const auto channel = nowActivePeer->asChannel()) { channel->ptsWaitingForShortPoll( WaitForChannelGetDifference); } _viewsIncremented.remove(nowActivePeer); } if (Adaptive::OneColumn() && !_dialogs->isHidden()) { _dialogs->hide(); } if (!_a_show.animating()) { if (!animationParams.oldContentCache.isNull()) { _history->showAnimated( back ? Window::SlideDirection::FromLeft : Window::SlideDirection::FromRight, animationParams); } else { _history->show(); if (App::wnd()) { QTimer::singleShot(0, App::wnd(), SLOT(setInnerFocus())); } } } } if (!_dialogs->isHidden()) { if (!back) { if (const auto history = _history->history()) { _dialogs->scrollToPeer(history, showAtMsgId); } } _dialogs->update(); } checkFloatPlayerVisibility(); } PeerData *MainWidget::ui_getPeerForMouseAction() { return _history->ui_getPeerForMouseAction(); } Dialogs::RowDescriptor MainWidget::chatListEntryBefore( const Dialogs::RowDescriptor &which) const { if (selectingPeer()) { return Dialogs::RowDescriptor(); } return _dialogs->chatListEntryBefore(which); } Dialogs::RowDescriptor MainWidget::chatListEntryAfter( const Dialogs::RowDescriptor &which) const { if (selectingPeer()) { return Dialogs::RowDescriptor(); } return _dialogs->chatListEntryAfter(which); } PeerData *MainWidget::peer() { return _history->peer(); } void MainWidget::saveSectionInStack() { if (_mainSection) { if (auto memento = _mainSection->createMemento()) { _stack.push_back(std::make_unique<StackItemSection>( std::move(memento))); _stack.back()->setThirdSectionWeak(_thirdSection.data()); } } else if (const auto history = _history->history()) { _stack.push_back(std::make_unique<StackItemHistory>( history, _history->msgId(), _history->replyReturns())); _stack.back()->setThirdSectionWeak(_thirdSection.data()); } } void MainWidget::showSection( Window::SectionMemento &&memento, const SectionShow ¶ms) { if (_mainSection && _mainSection->showInternal( &memento, params)) { if (const auto entry = _mainSection->activeChat(); entry.key) { _controller->setActiveChatEntry(entry); } return; // // Now third section handles only its own showSection() requests. // General showSection() should show layer or main_section instead. // //} else if (_thirdSection && _thirdSection->showInternal( // &memento, // params)) { // return; } // If the window was not resized, but we've enabled // tabbedSelectorSectionEnabled or thirdSectionInfoEnabled // we need to update adaptive layout to Adaptive::ThirdColumn(). updateColumnLayout(); showNewSection( std::move(memento), params); } void MainWidget::updateColumnLayout() { updateWindowAdaptiveLayout(); } Window::SectionSlideParams MainWidget::prepareThirdSectionAnimation(Window::SectionWidget *section) { Expects(_thirdSection != nullptr); Window::SectionSlideParams result; result.withTopBarShadow = section->hasTopBarShadow(); if (!_thirdSection->hasTopBarShadow()) { result.withTopBarShadow = false; } for (auto &instance : _playerFloats) { instance->widget->hide(); } auto sectionTop = getThirdSectionTop(); result.oldContentCache = _thirdSection->grabForShowAnimation(result); for (auto &instance : _playerFloats) { if (instance->visible) { instance->widget->show(); } } return result; } Window::SectionSlideParams MainWidget::prepareShowAnimation( bool willHaveTopBarShadow) { Window::SectionSlideParams result; result.withTopBarShadow = willHaveTopBarShadow; if (selectingPeer() && Adaptive::OneColumn()) { result.withTopBarShadow = false; } else if (_mainSection) { if (!_mainSection->hasTopBarShadow()) { result.withTopBarShadow = false; } } else if (!_history->peer()) { result.withTopBarShadow = false; } for (auto &instance : _playerFloats) { instance->widget->hide(); } if (_player) { _player->hideShadow(); } auto playerVolumeVisible = _playerVolume && !_playerVolume->isHidden(); if (playerVolumeVisible) { _playerVolume->hide(); } auto playerPanelVisible = !_playerPanel->isHidden(); if (playerPanelVisible) { _playerPanel->hide(); } auto playerPlaylistVisible = !_playerPlaylist->isHidden(); if (playerPlaylistVisible) { _playerPlaylist->hide(); } auto sectionTop = getMainSectionTop(); if (selectingPeer() && Adaptive::OneColumn()) { result.oldContentCache = Ui::GrabWidget(this, QRect( 0, sectionTop, _dialogsWidth, height() - sectionTop)); } else if (_mainSection) { result.oldContentCache = _mainSection->grabForShowAnimation(result); } else { if (result.withTopBarShadow) { _history->grapWithoutTopBarShadow(); } else { _history->grabStart(); } if (Adaptive::OneColumn()) { result.oldContentCache = Ui::GrabWidget(this, QRect( 0, sectionTop, _dialogsWidth, height() - sectionTop)); } else { _sideShadow->hide(); if (_thirdShadow) { _thirdShadow->hide(); } result.oldContentCache = Ui::GrabWidget(this, QRect( _dialogsWidth, sectionTop, width() - _dialogsWidth, height() - sectionTop)); _sideShadow->show(); if (_thirdShadow) { _thirdShadow->show(); } } _history->grabFinish(); } if (playerVolumeVisible) { _playerVolume->show(); } if (playerPanelVisible) { _playerPanel->show(); } if (playerPlaylistVisible) { _playerPlaylist->show(); } if (_player) { _player->showShadow(); } for (auto &instance : _playerFloats) { if (instance->visible) { instance->widget->show(); } } return result; } Window::SectionSlideParams MainWidget::prepareMainSectionAnimation(Window::SectionWidget *section) { return prepareShowAnimation(section->hasTopBarShadow()); } Window::SectionSlideParams MainWidget::prepareHistoryAnimation(PeerId historyPeerId) { return prepareShowAnimation(historyPeerId != 0); } Window::SectionSlideParams MainWidget::prepareDialogsAnimation() { return prepareShowAnimation(false); } void MainWidget::showNewSection( Window::SectionMemento &&memento, const SectionShow ¶ms) { using Column = Window::Column; auto saveInStack = (params.way == SectionShow::Way::Forward); auto thirdSectionTop = getThirdSectionTop(); auto newThirdGeometry = QRect( width() - st::columnMinimalWidthThird, thirdSectionTop, st::columnMinimalWidthThird, height() - thirdSectionTop); auto newThirdSection = (Adaptive::ThreeColumn() && params.thirdColumn) ? memento.createWidget( this, _controller, Column::Third, newThirdGeometry) : nullptr; if (newThirdSection) { saveInStack = false; } else if (auto layer = memento.createLayer(_controller, rect())) { if (params.activation != anim::activation::background) { Ui::hideLayer(anim::type::instant); } _controller->showSpecialLayer(std::move(layer)); return; } if (params.activation != anim::activation::background) { Ui::hideSettingsAndLayer(); } QPixmap animCache; _controller->dialogsListFocused().set(false, true); _a_dialogsWidth.finish(); auto mainSectionTop = getMainSectionTop(); auto newMainGeometry = QRect( _history->x(), mainSectionTop, _history->width(), height() - mainSectionTop); auto newMainSection = newThirdSection ? nullptr : memento.createWidget( this, _controller, Adaptive::OneColumn() ? Column::First : Column::Second, newMainGeometry); Assert(newMainSection || newThirdSection); auto animatedShow = [&] { if (_a_show.animating() || App::passcoded() || (params.animated == anim::type::instant) || memento.instant()) { return false; } if (!Adaptive::OneColumn() && params.way == SectionShow::Way::ClearStack) { return false; } else if (Adaptive::OneColumn() || (newThirdSection && _thirdSection) || (newMainSection && isMainSectionShown())) { return true; } return false; }(); auto animationParams = animatedShow ? (newThirdSection ? prepareThirdSectionAnimation(newThirdSection) : prepareMainSectionAnimation(newMainSection)) : Window::SectionSlideParams(); setFocus(); // otherwise dialogs widget could be focused. if (saveInStack) { // This may modify the current section, for example remove its contents. saveSectionInStack(); } auto &settingSection = newThirdSection ? _thirdSection : _mainSection; if (newThirdSection) { _thirdSection = std::move(newThirdSection); if (!_thirdShadow) { _thirdShadow.create(this); _thirdShadow->show(); orderWidgets(); } updateControlsGeometry(); } else { if (_mainSection) { _mainSection->hide(); _mainSection->deleteLater(); _mainSection = nullptr; } _mainSection = std::move(newMainSection); updateControlsGeometry(); _history->finishAnimating(); _history->showHistory(0, 0); _history->hide(); if (Adaptive::OneColumn()) _dialogs->hide(); } if (animationParams) { auto back = (params.way == SectionShow::Way::Backward); auto direction = (back || settingSection->forceAnimateBack()) ? Window::SlideDirection::FromLeft : Window::SlideDirection::FromRight; settingSection->showAnimated(direction, animationParams); } else { settingSection->showFast(); } if (settingSection.data() == _mainSection.data()) { if (const auto entry = _mainSection->activeChat(); entry.key) { _controller->setActiveChatEntry(entry); } } checkFloatPlayerVisibility(); orderWidgets(); } void MainWidget::checkMainSectionToLayer() { if (!_mainSection) { return; } Ui::FocusPersister persister(this); if (auto layer = _mainSection->moveContentToLayer(rect())) { dropMainSection(_mainSection); _controller->showSpecialLayer( std::move(layer), anim::type::instant); } } void MainWidget::dropMainSection(Window::SectionWidget *widget) { if (_mainSection != widget) { return; } _mainSection.destroy(); _controller->showBackFromStack( SectionShow( anim::type::instant, anim::activation::background)); } bool MainWidget::isMainSectionShown() const { return _mainSection || _history->peer(); } bool MainWidget::isThirdSectionShown() const { return _thirdSection != nullptr; } bool MainWidget::stackIsEmpty() const { return _stack.empty(); } void MainWidget::showBackFromStack( const SectionShow ¶ms) { if (selectingPeer()) return; if (_stack.empty()) { _controller->clearSectionStack(params); if (App::wnd()) { QTimer::singleShot(0, App::wnd(), SLOT(setInnerFocus())); } return; } auto item = std::move(_stack.back()); _stack.pop_back(); if (auto currentHistoryPeer = _history->peer()) { clearBotStartToken(currentHistoryPeer); } _thirdSectionFromStack = item->takeThirdSectionMemento(); if (item->type() == HistoryStackItem) { auto historyItem = static_cast<StackItemHistory*>(item.get()); _controller->showPeerHistory( historyItem->peer()->id, params.withWay(SectionShow::Way::Backward), ShowAtUnreadMsgId); _history->setReplyReturns(historyItem->peer()->id, historyItem->replyReturns); } else if (item->type() == SectionStackItem) { auto sectionItem = static_cast<StackItemSection*>(item.get()); showNewSection( std::move(*sectionItem->memento()), params.withWay(SectionShow::Way::Backward)); } if (_thirdSectionFromStack && _thirdSection) { _controller->showSection( std::move(*base::take(_thirdSectionFromStack)), SectionShow( SectionShow::Way::ClearStack, anim::type::instant, anim::activation::background)); } } void MainWidget::orderWidgets() { _dialogs->raise(); if (_callTopBar) { _callTopBar->raise(); } if (_player) { _player->raise(); } if (_playerVolume) { _playerVolume->raise(); } _sideShadow->raise(); if (_thirdShadow) { _thirdShadow->raise(); } if (_firstColumnResizeArea) { _firstColumnResizeArea->raise(); } if (_thirdColumnResizeArea) { _thirdColumnResizeArea->raise(); } _playerPlaylist->raise(); _playerPanel->raise(); for (auto &instance : _playerFloats) { instance->widget->raise(); } if (_hider) _hider->raise(); } QRect MainWidget::historyRect() const { QRect r(_history->historyRect()); r.moveLeft(r.left() + _history->x()); r.moveTop(r.top() + _history->y()); return r; } QPixmap MainWidget::grabForShowAnimation(const Window::SectionSlideParams ¶ms) { QPixmap result; for (auto &instance : _playerFloats) { instance->widget->hide(); } if (_player) { _player->hideShadow(); } auto playerVolumeVisible = _playerVolume && !_playerVolume->isHidden(); if (playerVolumeVisible) { _playerVolume->hide(); } auto playerPanelVisible = !_playerPanel->isHidden(); if (playerPanelVisible) { _playerPanel->hide(); } auto playerPlaylistVisible = !_playerPlaylist->isHidden(); if (playerPlaylistVisible) { _playerPlaylist->hide(); } auto sectionTop = getMainSectionTop(); if (Adaptive::OneColumn()) { result = Ui::GrabWidget(this, QRect( 0, sectionTop, _dialogsWidth, height() - sectionTop)); } else { _sideShadow->hide(); if (_thirdShadow) { _thirdShadow->hide(); } result = Ui::GrabWidget(this, QRect( _dialogsWidth, sectionTop, width() - _dialogsWidth, height() - sectionTop)); _sideShadow->show(); if (_thirdShadow) { _thirdShadow->show(); } } if (playerVolumeVisible) { _playerVolume->show(); } if (playerPanelVisible) { _playerPanel->show(); } if (playerPlaylistVisible) { _playerPlaylist->show(); } if (_player) { _player->showShadow(); } for (auto &instance : _playerFloats) { if (instance->visible) { instance->widget->show(); } } return result; } void MainWidget::repaintDialogRow( Dialogs::Mode list, not_null<Dialogs::Row*> row) { _dialogs->repaintDialogRow(list, row); } void MainWidget::repaintDialogRow( not_null<History*> history, MsgId messageId) { _dialogs->repaintDialogRow(history, messageId); } void MainWidget::windowShown() { _history->windowShown(); } void MainWidget::sentUpdatesReceived(uint64 randomId, const MTPUpdates &result) { feedUpdates(result, randomId); } bool MainWidget::deleteChannelFailed(const RPCError &error) { if (MTP::isDefaultHandledError(error)) return false; //if (error.type() == qstr("CHANNEL_TOO_LARGE")) { // Ui::show(Box<InformBox>(lang(lng_cant_delete_channel))); //} return true; } void MainWidget::inviteToChannelDone( not_null<ChannelData*> channel, const MTPUpdates &updates) { sentUpdatesReceived(updates); Auth().api().requestParticipantsCountDelayed(channel); } void MainWidget::historyToDown(History *history) { _history->historyToDown(history); } void MainWidget::dialogsToUp() { _dialogs->dialogsToUp(); } void MainWidget::newUnreadMsg( not_null<History*> history, not_null<HistoryItem*> item) { _history->newUnreadMsg(history, item); } void MainWidget::markActiveHistoryAsRead() { if (const auto activeHistory = _history->history()) { Auth().api().readServerHistory(activeHistory); } } void MainWidget::showAnimated(const QPixmap &bgAnimCache, bool back) { _showBack = back; (_showBack ? _cacheOver : _cacheUnder) = bgAnimCache; _a_show.finish(); showAll(); (_showBack ? _cacheUnder : _cacheOver) = Ui::GrabWidget(this); hideAll(); _a_show.start( [this] { animationCallback(); }, 0., 1., st::slideDuration, Window::SlideAnimation::transition()); show(); } void MainWidget::animationCallback() { update(); if (!_a_show.animating()) { _cacheUnder = _cacheOver = QPixmap(); showAll(); activate(); } } void MainWidget::paintEvent(QPaintEvent *e) { if (_background) checkChatBackground(); Painter p(this); auto progress = _a_show.current(getms(), 1.); if (_a_show.animating()) { auto coordUnder = _showBack ? anim::interpolate(-st::slideShift, 0, progress) : anim::interpolate(0, -st::slideShift, progress); auto coordOver = _showBack ? anim::interpolate(0, width(), progress) : anim::interpolate(width(), 0, progress); auto shadow = _showBack ? (1. - progress) : progress; if (coordOver > 0) { p.drawPixmap(QRect(0, 0, coordOver, height()), _cacheUnder, QRect(-coordUnder * cRetinaFactor(), 0, coordOver * cRetinaFactor(), height() * cRetinaFactor())); p.setOpacity(shadow); p.fillRect(0, 0, coordOver, height(), st::slideFadeOutBg); p.setOpacity(1); } p.drawPixmap(coordOver, 0, _cacheOver); p.setOpacity(shadow); st::slideShadow.fill(p, QRect(coordOver - st::slideShadow.width(), 0, st::slideShadow.width(), height())); } } int MainWidget::getMainSectionTop() const { return _callTopBarHeight + _playerHeight; } int MainWidget::getThirdSectionTop() const { return 0; } void MainWidget::hideAll() { _dialogs->hide(); _history->hide(); if (_mainSection) { _mainSection->hide(); } if (_thirdSection) { _thirdSection->hide(); } _sideShadow->hide(); if (_thirdShadow) { _thirdShadow->hide(); } if (_player) { _player->setVisible(false); _playerHeight = 0; } for (auto &instance : _playerFloats) { instance->widget->hide(); } } void MainWidget::showAll() { if (cPasswordRecovered()) { cSetPasswordRecovered(false); Ui::show(Box<InformBox>(lang(lng_signin_password_removed))); } if (Adaptive::OneColumn()) { _sideShadow->hide(); if (_hider) { _hider->hide(); if (!_forwardConfirm && _hider->wasOffered()) { _forwardConfirm = Ui::show(Box<ConfirmBox>(_hider->offeredText(), lang(lng_forward_send), base::lambda_guarded(this, [this] { _hider->forward(); if (_forwardConfirm) _forwardConfirm->closeBox(); if (_hider) _hider->offerPeer(0); }), base::lambda_guarded(this, [this] { if (_hider && _forwardConfirm) _hider->offerPeer(0); })), LayerOption::CloseOther, anim::type::instant); } } if (selectingPeer()) { _dialogs->showFast(); _history->hide(); if (_mainSection) _mainSection->hide(); } else if (_mainSection) { _mainSection->show(); } else if (_history->peer()) { _history->show(); _history->updateControlsGeometry(); } else { _dialogs->showFast(); _history->hide(); } if (!selectingPeer()) { if (_mainSection) { _dialogs->hide(); } else if (isMainSectionShown()) { _dialogs->hide(); } } } else { _sideShadow->show(); if (_hider) { _hider->show(); if (_forwardConfirm) { _forwardConfirm = nullptr; Ui::hideLayer(anim::type::instant); if (_hider->wasOffered()) { _hider->setFocus(); } } } _dialogs->showFast(); if (_mainSection) { _mainSection->show(); } else { _history->show(); _history->updateControlsGeometry(); } if (_thirdSection) { _thirdSection->show(); } if (_thirdShadow) { _thirdShadow->show(); } } if (_player) { _player->setVisible(true); _playerHeight = _player->contentHeight(); } updateControlsGeometry(); if (auto instance = currentFloatPlayer()) { checkFloatPlayerVisibility(); if (instance->visible) { instance->widget->show(); } } App::wnd()->checkHistoryActivation(); } void MainWidget::resizeEvent(QResizeEvent *e) { updateControlsGeometry(); } void MainWidget::updateControlsGeometry() { updateWindowAdaptiveLayout(); if (Auth().settings().dialogsWidthRatio() > 0) { _a_dialogsWidth.finish(); } if (!_a_dialogsWidth.animating()) { _dialogs->stopWidthAnimation(); } if (Adaptive::ThreeColumn()) { if (!_thirdSection && !_controller->takeThirdSectionFromLayer()) { auto params = Window::SectionShow( Window::SectionShow::Way::ClearStack, anim::type::instant, anim::activation::background); if (Auth().settings().tabbedSelectorSectionEnabled()) { _history->pushTabbedSelectorToThirdSection(params); } else if (Auth().settings().thirdSectionInfoEnabled()) { if (const auto key = _controller->activeChatCurrent()) { _controller->showSection( Info::Memento::Default(key), params.withThirdColumn()); } } } } else { _thirdSection.destroy(); _thirdShadow.destroy(); } auto mainSectionTop = getMainSectionTop(); auto dialogsWidth = qRound(_a_dialogsWidth.current(_dialogsWidth)); if (Adaptive::OneColumn()) { if (_callTopBar) { _callTopBar->resizeToWidth(dialogsWidth); _callTopBar->moveToLeft(0, 0); } if (_player) { _player->resizeToWidth(dialogsWidth); _player->moveToLeft(0, _callTopBarHeight); } auto mainSectionGeometry = QRect( 0, mainSectionTop, dialogsWidth, height() - mainSectionTop); _dialogs->setGeometry(mainSectionGeometry); _history->setGeometry(mainSectionGeometry); if (_hider) _hider->setGeometry(0, 0, dialogsWidth, height()); } else { auto thirdSectionWidth = _thirdSection ? _thirdColumnWidth : 0; if (_thirdSection) { auto thirdSectionTop = getThirdSectionTop(); _thirdSection->setGeometry( width() - thirdSectionWidth, thirdSectionTop, thirdSectionWidth, height() - thirdSectionTop); } accumulate_min(dialogsWidth, width() - st::columnMinimalWidthMain); auto mainSectionWidth = width() - dialogsWidth - thirdSectionWidth; _dialogs->setGeometryToLeft(0, 0, dialogsWidth, height()); _sideShadow->setGeometryToLeft(dialogsWidth, 0, st::lineWidth, height()); if (_thirdShadow) { _thirdShadow->setGeometryToLeft( width() - thirdSectionWidth - st::lineWidth, 0, st::lineWidth, height()); } if (_callTopBar) { _callTopBar->resizeToWidth(mainSectionWidth); _callTopBar->moveToLeft(dialogsWidth, 0); } if (_player) { _player->resizeToWidth(mainSectionWidth); _player->moveToLeft(dialogsWidth, _callTopBarHeight); } _history->setGeometryToLeft(dialogsWidth, mainSectionTop, mainSectionWidth, height() - mainSectionTop); if (_hider) { _hider->setGeometryToLeft(dialogsWidth, 0, mainSectionWidth, height()); } } if (_mainSection) { auto mainSectionGeometry = QRect(_history->x(), mainSectionTop, _history->width(), height() - mainSectionTop); _mainSection->setGeometryWithTopMoved(mainSectionGeometry, _contentScrollAddToY); } refreshResizeAreas(); updateMediaPlayerPosition(); updateMediaPlaylistPosition(_playerPlaylist->x()); _contentScrollAddToY = 0; for (auto &instance : _playerFloats) { updateFloatPlayerPosition(instance.get()); } } void MainWidget::refreshResizeAreas() { if (!Adaptive::OneColumn()) { ensureFirstColumnResizeAreaCreated(); _firstColumnResizeArea->setGeometryToLeft( _history->x(), 0, st::historyResizeWidth, height()); } else if (_firstColumnResizeArea) { _firstColumnResizeArea.destroy(); } if (Adaptive::ThreeColumn() && _thirdSection) { ensureThirdColumnResizeAreaCreated(); _thirdColumnResizeArea->setGeometryToLeft( _thirdSection->x(), 0, st::historyResizeWidth, height()); } else if (_thirdColumnResizeArea) { _thirdColumnResizeArea.destroy(); } } template <typename MoveCallback, typename FinishCallback> void MainWidget::createResizeArea( object_ptr<Ui::ResizeArea> &area, MoveCallback &&moveCallback, FinishCallback &&finishCallback) { area.create(this); area->show(); area->addMoveLeftCallback( std::forward<MoveCallback>(moveCallback)); area->addMoveFinishedCallback( std::forward<FinishCallback>(finishCallback)); orderWidgets(); } void MainWidget::ensureFirstColumnResizeAreaCreated() { if (_firstColumnResizeArea) { return; } auto moveLeftCallback = [=](int globalLeft) { auto newWidth = globalLeft - mapToGlobal(QPoint(0, 0)).x(); auto newRatio = (newWidth < st::columnMinimalWidthLeft / 2) ? 0. : float64(newWidth) / width(); Auth().settings().setDialogsWidthRatio(newRatio); }; auto moveFinishedCallback = [=] { if (Adaptive::OneColumn()) { return; } if (Auth().settings().dialogsWidthRatio() > 0) { Auth().settings().setDialogsWidthRatio( float64(_dialogsWidth) / width()); } Local::writeUserSettings(); }; createResizeArea( _firstColumnResizeArea, std::move(moveLeftCallback), std::move(moveFinishedCallback)); } void MainWidget::ensureThirdColumnResizeAreaCreated() { if (_thirdColumnResizeArea) { return; } auto moveLeftCallback = [=](int globalLeft) { auto newWidth = mapToGlobal(QPoint(width(), 0)).x() - globalLeft; Auth().settings().setThirdColumnWidth(newWidth); }; auto moveFinishedCallback = [=] { if (!Adaptive::ThreeColumn() || !_thirdSection) { return; } Auth().settings().setThirdColumnWidth(snap( Auth().settings().thirdColumnWidth(), st::columnMinimalWidthThird, st::columnMaximalWidthThird)); Local::writeUserSettings(); }; createResizeArea( _thirdColumnResizeArea, std::move(moveLeftCallback), std::move(moveFinishedCallback)); } void MainWidget::updateDialogsWidthAnimated() { if (Auth().settings().dialogsWidthRatio() > 0) { return; } auto dialogsWidth = _dialogsWidth; updateWindowAdaptiveLayout(); if (!Auth().settings().dialogsWidthRatio() && (_dialogsWidth != dialogsWidth || _a_dialogsWidth.animating())) { _dialogs->startWidthAnimation(); _a_dialogsWidth.start( [this] { updateControlsGeometry(); }, dialogsWidth, _dialogsWidth, st::dialogsWidthDuration, anim::easeOutCirc); updateControlsGeometry(); } } bool MainWidget::saveThirdSectionToStackBack() const { return !_stack.empty() && _thirdSection != nullptr && _stack.back()->thirdSectionWeak() == _thirdSection.data(); } auto MainWidget::thirdSectionForCurrentMainSection( Dialogs::Key key) -> std::unique_ptr<Window::SectionMemento> { if (_thirdSectionFromStack) { return std::move(_thirdSectionFromStack); } else if (const auto peer = key.peer()) { return std::make_unique<Info::Memento>( peer->id, Info::Memento::DefaultSection(key)); } else if (const auto feed = key.feed()) { return std::make_unique<Info::Memento>( feed, Info::Memento::DefaultSection(key)); } Unexpected("Key in MainWidget::thirdSectionForCurrentMainSection()."); } void MainWidget::updateThirdColumnToCurrentChat( Dialogs::Key key, bool canWrite) { auto saveOldThirdSection = [&] { if (saveThirdSectionToStackBack()) { _stack.back()->setThirdSectionMemento( _thirdSection->createMemento()); _thirdSection.destroy(); } }; auto params = Window::SectionShow( Window::SectionShow::Way::ClearStack, anim::type::instant, anim::activation::background); auto switchInfoFast = [&] { saveOldThirdSection(); // // Like in _controller->showPeerInfo() // if (Adaptive::ThreeColumn() && !Auth().settings().thirdSectionInfoEnabled()) { Auth().settings().setThirdSectionInfoEnabled(true); Auth().saveSettingsDelayed(); } _controller->showSection( std::move(*thirdSectionForCurrentMainSection(key)), params.withThirdColumn()); }; auto switchTabbedFast = [&] { saveOldThirdSection(); _history->pushTabbedSelectorToThirdSection(params); }; if (Adaptive::ThreeColumn() && Auth().settings().tabbedSelectorSectionEnabled() && key) { if (!canWrite) { switchInfoFast(); Auth().settings().setTabbedSelectorSectionEnabled(true); Auth().settings().setTabbedReplacedWithInfo(true); } else if (Auth().settings().tabbedReplacedWithInfo()) { Auth().settings().setTabbedReplacedWithInfo(false); switchTabbedFast(); } } else { Auth().settings().setTabbedReplacedWithInfo(false); if (!key) { if (_thirdSection) { _thirdSection.destroy(); _thirdShadow.destroy(); updateControlsGeometry(); } } else if (Adaptive::ThreeColumn() && Auth().settings().thirdSectionInfoEnabled()) { switchInfoFast(); } } } void MainWidget::updateMediaPlayerPosition() { _playerPanel->moveToRight(0, 0); if (_player && _playerVolume) { auto relativePosition = _player->entity()->getPositionForVolumeWidget(); auto playerMargins = _playerVolume->getMargin(); _playerVolume->moveToLeft(_player->x() + relativePosition.x() - playerMargins.left(), _player->y() + relativePosition.y() - playerMargins.top()); } } void MainWidget::updateMediaPlaylistPosition(int x) { if (_player) { auto playlistLeft = x; auto playlistWidth = _playerPlaylist->width(); auto playlistTop = _player->y() + _player->height(); auto rightEdge = width(); if (playlistLeft + playlistWidth > rightEdge) { playlistLeft = rightEdge - playlistWidth; } else if (playlistLeft < 0) { playlistLeft = 0; } _playerPlaylist->move(playlistLeft, playlistTop); } } int MainWidget::contentScrollAddToY() const { return _contentScrollAddToY; } void MainWidget::keyPressEvent(QKeyEvent *e) { } bool MainWidget::eventFilter(QObject *o, QEvent *e) { if (e->type() == QEvent::FocusIn) { if (auto widget = qobject_cast<QWidget*>(o)) { if (_history == widget || _history->isAncestorOf(widget) || (_mainSection && (_mainSection == widget || _mainSection->isAncestorOf(widget))) || (_thirdSection && (_thirdSection == widget || _thirdSection->isAncestorOf(widget)))) { _controller->dialogsListFocused().set(false); } else if (_dialogs == widget || _dialogs->isAncestorOf(widget)) { _controller->dialogsListFocused().set(true); } } } else if (e->type() == QEvent::MouseButtonPress) { if (static_cast<QMouseEvent*>(e)->button() == Qt::BackButton) { _controller->showBackFromStack(); return true; } } else if (e->type() == QEvent::Wheel && !_playerFloats.empty()) { for (auto &instance : _playerFloats) { if (instance->widget == o) { auto section = getFloatPlayerSection( instance->column); return section->wheelEventFromFloatPlayer(e); } } } return TWidget::eventFilter(o, e); } void MainWidget::handleAdaptiveLayoutUpdate() { showAll(); _sideShadow->setVisible(!Adaptive::OneColumn()); if (_player) { _player->updateAdaptiveLayout(); } } void MainWidget::updateWindowAdaptiveLayout() { auto layout = _controller->computeColumnLayout(); auto dialogsWidthRatio = Auth().settings().dialogsWidthRatio(); // Check if we are in a single-column layout in a wide enough window // for the normal layout. If so, switch to the normal layout. if (layout.windowLayout == Adaptive::WindowLayout::OneColumn) { auto chatWidth = layout.chatWidth; //if (Auth().settings().tabbedSelectorSectionEnabled() // && chatWidth >= _history->minimalWidthForTabbedSelectorSection()) { // chatWidth -= _history->tabbedSelectorSectionWidth(); //} auto minimalNormalWidth = st::columnMinimalWidthLeft + st::columnMinimalWidthMain; if (chatWidth >= minimalNormalWidth) { // Switch layout back to normal in a wide enough window. layout.windowLayout = Adaptive::WindowLayout::Normal; layout.dialogsWidth = st::columnMinimalWidthLeft; layout.chatWidth = layout.bodyWidth - layout.dialogsWidth; dialogsWidthRatio = float64(layout.dialogsWidth) / layout.bodyWidth; } } // Check if we are going to create the third column and shrink the // dialogs widget to provide a wide enough chat history column. // Don't shrink the column on the first call, when window is inited. if (layout.windowLayout == Adaptive::WindowLayout::ThreeColumn && _started && _controller->window()->positionInited()) { //auto chatWidth = layout.chatWidth; //if (_history->willSwitchToTabbedSelectorWithWidth(chatWidth)) { // auto thirdColumnWidth = _history->tabbedSelectorSectionWidth(); // auto twoColumnsWidth = (layout.bodyWidth - thirdColumnWidth); // auto sameRatioChatWidth = twoColumnsWidth - qRound(dialogsWidthRatio * twoColumnsWidth); // auto desiredChatWidth = qMax(sameRatioChatWidth, HistoryView::WideChatWidth()); // chatWidth -= thirdColumnWidth; // auto extendChatBy = desiredChatWidth - chatWidth; // accumulate_min(extendChatBy, layout.dialogsWidth - st::columnMinimalWidthLeft); // if (extendChatBy > 0) { // layout.dialogsWidth -= extendChatBy; // layout.chatWidth += extendChatBy; // dialogsWidthRatio = float64(layout.dialogsWidth) / layout.bodyWidth; // } //} } Auth().settings().setDialogsWidthRatio(dialogsWidthRatio); auto useSmallColumnWidth = !Adaptive::OneColumn() && !dialogsWidthRatio && !_controller->forceWideDialogs(); _dialogsWidth = useSmallColumnWidth ? _controller->dialogsSmallColumnWidth() : layout.dialogsWidth; _thirdColumnWidth = layout.thirdWidth; if (layout.windowLayout != Global::AdaptiveWindowLayout()) { Global::SetAdaptiveWindowLayout(layout.windowLayout); Adaptive::Changed().notify(true); } } int MainWidget::backgroundFromY() const { return -getMainSectionTop(); } void MainWidget::searchInChat(Dialogs::Key chat) { _dialogs->searchInChat(chat); if (Adaptive::OneColumn()) { dialogsToUp(); Ui::showChatsList(); } else { _dialogs->activate(); } } void MainWidget::onUpdateNotifySettings() { if (this != App::main()) return; while (!updateNotifySettingPeers.empty()) { auto peer = *updateNotifySettingPeers.begin(); updateNotifySettingPeers.erase(updateNotifySettingPeers.begin()); MTP::send( MTPaccount_UpdateNotifySettings( MTP_inputNotifyPeer(peer->input), peer->notifySerialize()), RPCResponseHandler(), 0, updateNotifySettingPeers.empty() ? 0 : 10); } } void MainWidget::feedUpdateVector( const MTPVector<MTPUpdate> &updates, bool skipMessageIds) { for_const (auto &update, updates.v) { if (skipMessageIds && update.type() == mtpc_updateMessageID) continue; feedUpdate(update); } Auth().data().sendHistoryChangeNotifications(); } void MainWidget::feedMessageIds(const MTPVector<MTPUpdate> &updates) { for_const (auto &update, updates.v) { if (update.type() == mtpc_updateMessageID) { feedUpdate(update); } } } bool MainWidget::updateFail(const RPCError &e) { App::logOutDelayed(); return true; } void MainWidget::updSetState(int32 pts, int32 date, int32 qts, int32 seq) { if (pts) { _ptsWaiter.init(pts); } if (updDate < date && !_byMinChannelTimer.isActive()) { updDate = date; } if (qts && updQts < qts) { updQts = qts; } if (seq && seq != updSeq) { updSeq = seq; if (_bySeqTimer.isActive()) _bySeqTimer.stop(); for (QMap<int32, MTPUpdates>::iterator i = _bySeqUpdates.begin(); i != _bySeqUpdates.end();) { int32 s = i.key(); if (s <= seq + 1) { MTPUpdates v = i.value(); i = _bySeqUpdates.erase(i); if (s == seq + 1) { return feedUpdates(v); } } else { if (!_bySeqTimer.isActive()) _bySeqTimer.start(WaitForSkippedTimeout); break; } } } } void MainWidget::gotChannelDifference( ChannelData *channel, const MTPupdates_ChannelDifference &diff) { _channelFailDifferenceTimeout.remove(channel); int32 timeout = 0; bool isFinal = true; switch (diff.type()) { case mtpc_updates_channelDifferenceEmpty: { auto &d = diff.c_updates_channelDifferenceEmpty(); if (d.has_timeout()) timeout = d.vtimeout.v; isFinal = d.is_final(); channel->ptsInit(d.vpts.v); } break; case mtpc_updates_channelDifferenceTooLong: { auto &d = diff.c_updates_channelDifferenceTooLong(); App::feedUsers(d.vusers); App::feedChats(d.vchats); auto history = App::historyLoaded(channel->id); if (history) { history->setNotLoadedAtBottom(); } App::feedMsgs(d.vmessages, NewMessageLast); if (history) { history->applyDialogFields( d.vunread_count.v, d.vread_inbox_max_id.v, d.vread_outbox_max_id.v); history->applyDialogTopMessage(d.vtop_message.v); history->setUnreadMentionsCount(d.vunread_mentions_count.v); if (_history->peer() == channel) { _history->updateHistoryDownVisibility(); _history->preloadHistoryIfNeeded(); } Auth().api().requestChannelRangeDifference(history); } if (d.has_timeout()) { timeout = d.vtimeout.v; } isFinal = d.is_final(); channel->ptsInit(d.vpts.v); } break; case mtpc_updates_channelDifference: { auto &d = diff.c_updates_channelDifference(); feedChannelDifference(d); if (d.has_timeout()) timeout = d.vtimeout.v; isFinal = d.is_final(); channel->ptsInit(d.vpts.v); } break; } channel->ptsSetRequesting(false); if (!isFinal) { MTP_LOG(0, ("getChannelDifference { good - after not final channelDifference was received }%1").arg(cTestMode() ? " TESTMODE" : "")); getChannelDifference(channel); } else if (_controller->activeChatCurrent().peer() == channel) { channel->ptsWaitingForShortPoll(timeout ? (timeout * 1000) : WaitForChannelGetDifference); } } void MainWidget::feedChannelDifference( const MTPDupdates_channelDifference &data) { App::feedUsers(data.vusers); App::feedChats(data.vchats); _handlingChannelDifference = true; feedMessageIds(data.vother_updates); App::feedMsgs(data.vnew_messages, NewMessageUnread); feedUpdateVector(data.vother_updates, true); _handlingChannelDifference = false; } bool MainWidget::failChannelDifference(ChannelData *channel, const RPCError &error) { if (MTP::isDefaultHandledError(error)) return false; LOG(("RPC Error in getChannelDifference: %1 %2: %3").arg(error.code()).arg(error.type()).arg(error.description())); failDifferenceStartTimerFor(channel); return true; } void MainWidget::gotState(const MTPupdates_State &state) { auto &d = state.c_updates_state(); updSetState(d.vpts.v, d.vdate.v, d.vqts.v, d.vseq.v); _lastUpdateTime = getms(true); noUpdatesTimer.start(NoUpdatesTimeout); _ptsWaiter.setRequesting(false); _dialogs->loadDialogs(); updateOnline(); } void MainWidget::gotDifference(const MTPupdates_Difference &difference) { _failDifferenceTimeout = 1; switch (difference.type()) { case mtpc_updates_differenceEmpty: { auto &d = difference.c_updates_differenceEmpty(); updSetState(_ptsWaiter.current(), d.vdate.v, updQts, d.vseq.v); _lastUpdateTime = getms(true); noUpdatesTimer.start(NoUpdatesTimeout); _ptsWaiter.setRequesting(false); } break; case mtpc_updates_differenceSlice: { auto &d = difference.c_updates_differenceSlice(); feedDifference(d.vusers, d.vchats, d.vnew_messages, d.vother_updates); auto &s = d.vintermediate_state.c_updates_state(); updSetState(s.vpts.v, s.vdate.v, s.vqts.v, s.vseq.v); _ptsWaiter.setRequesting(false); MTP_LOG(0, ("getDifference { good - after a slice of difference was received }%1").arg(cTestMode() ? " TESTMODE" : "")); getDifference(); } break; case mtpc_updates_difference: { auto &d = difference.c_updates_difference(); feedDifference(d.vusers, d.vchats, d.vnew_messages, d.vother_updates); gotState(d.vstate); } break; case mtpc_updates_differenceTooLong: { auto &d = difference.c_updates_differenceTooLong(); LOG(("API Error: updates.differenceTooLong is not supported by Telegram Desktop!")); } break; }; } bool MainWidget::getDifferenceTimeChanged(ChannelData *channel, int32 ms, ChannelGetDifferenceTime &channelCurTime, TimeMs &curTime) { if (channel) { if (ms <= 0) { ChannelGetDifferenceTime::iterator i = channelCurTime.find(channel); if (i != channelCurTime.cend()) { channelCurTime.erase(i); } else { return false; } } else { auto when = getms(true) + ms; ChannelGetDifferenceTime::iterator i = channelCurTime.find(channel); if (i != channelCurTime.cend()) { if (i.value() > when) { i.value() = when; } else { return false; } } else { channelCurTime.insert(channel, when); } } } else { if (ms <= 0) { if (curTime) { curTime = 0; } else { return false; } } else { auto when = getms(true) + ms; if (!curTime || curTime > when) { curTime = when; } else { return false; } } } return true; } void MainWidget::ptsWaiterStartTimerFor(ChannelData *channel, int32 ms) { if (getDifferenceTimeChanged(channel, ms, _channelGetDifferenceTimeByPts, _getDifferenceTimeByPts)) { onGetDifferenceTimeByPts(); } } void MainWidget::failDifferenceStartTimerFor(ChannelData *channel) { int32 ms = 0; ChannelFailDifferenceTimeout::iterator i; if (channel) { i = _channelFailDifferenceTimeout.find(channel); if (i == _channelFailDifferenceTimeout.cend()) { i = _channelFailDifferenceTimeout.insert(channel, 1); } ms = i.value() * 1000; } else { ms = _failDifferenceTimeout * 1000; } if (getDifferenceTimeChanged(channel, ms, _channelGetDifferenceTimeAfterFail, _getDifferenceTimeAfterFail)) { onGetDifferenceTimeAfterFail(); } if (channel) { if (i.value() < 64) i.value() *= 2; } else { if (_failDifferenceTimeout < 64) _failDifferenceTimeout *= 2; } } bool MainWidget::ptsUpdateAndApply(int32 pts, int32 ptsCount, const MTPUpdates &updates) { return _ptsWaiter.updateAndApply(nullptr, pts, ptsCount, updates); } bool MainWidget::ptsUpdateAndApply(int32 pts, int32 ptsCount, const MTPUpdate &update) { return _ptsWaiter.updateAndApply(nullptr, pts, ptsCount, update); } bool MainWidget::ptsUpdateAndApply(int32 pts, int32 ptsCount) { return _ptsWaiter.updateAndApply(nullptr, pts, ptsCount); } void MainWidget::feedDifference( const MTPVector<MTPUser> &users, const MTPVector<MTPChat> &chats, const MTPVector<MTPMessage> &msgs, const MTPVector<MTPUpdate> &other) { Auth().checkAutoLock(); App::feedUsers(users); App::feedChats(chats); feedMessageIds(other); App::feedMsgs(msgs, NewMessageUnread); feedUpdateVector(other, true); } bool MainWidget::failDifference(const RPCError &error) { if (MTP::isDefaultHandledError(error)) return false; LOG(("RPC Error in getDifference: %1 %2: %3").arg(error.code()).arg(error.type()).arg(error.description())); failDifferenceStartTimerFor(0); return true; } void MainWidget::onGetDifferenceTimeByPts() { auto now = getms(true), wait = 0LL; if (_getDifferenceTimeByPts) { if (_getDifferenceTimeByPts > now) { wait = _getDifferenceTimeByPts - now; } else { getDifference(); } } for (ChannelGetDifferenceTime::iterator i = _channelGetDifferenceTimeByPts.begin(); i != _channelGetDifferenceTimeByPts.cend();) { if (i.value() > now) { wait = wait ? qMin(wait, i.value() - now) : (i.value() - now); ++i; } else { getChannelDifference(i.key(), ChannelDifferenceRequest::PtsGapOrShortPoll); i = _channelGetDifferenceTimeByPts.erase(i); } } if (wait) { _byPtsTimer.start(wait); } else { _byPtsTimer.stop(); } } void MainWidget::onGetDifferenceTimeAfterFail() { auto now = getms(true), wait = 0LL; if (_getDifferenceTimeAfterFail) { if (_getDifferenceTimeAfterFail > now) { wait = _getDifferenceTimeAfterFail - now; } else { _ptsWaiter.setRequesting(false); MTP_LOG(0, ("getDifference { force - after get difference failed }%1").arg(cTestMode() ? " TESTMODE" : "")); getDifference(); } } for (auto i = _channelGetDifferenceTimeAfterFail.begin(); i != _channelGetDifferenceTimeAfterFail.cend();) { if (i.value() > now) { wait = wait ? qMin(wait, i.value() - now) : (i.value() - now); ++i; } else { getChannelDifference(i.key(), ChannelDifferenceRequest::AfterFail); i = _channelGetDifferenceTimeAfterFail.erase(i); } } if (wait) { _failDifferenceTimer.start(wait); } else { _failDifferenceTimer.stop(); } } void MainWidget::getDifference() { if (this != App::main()) return; _getDifferenceTimeByPts = 0; if (requestingDifference()) return; _bySeqUpdates.clear(); _bySeqTimer.stop(); noUpdatesTimer.stop(); _getDifferenceTimeAfterFail = 0; _ptsWaiter.setRequesting(true); MTP::send(MTPupdates_GetDifference(MTP_flags(0), MTP_int(_ptsWaiter.current()), MTPint(), MTP_int(updDate), MTP_int(updQts)), rpcDone(&MainWidget::gotDifference), rpcFail(&MainWidget::failDifference)); } void MainWidget::getChannelDifference(ChannelData *channel, ChannelDifferenceRequest from) { if (this != App::main() || !channel) return; if (from != ChannelDifferenceRequest::PtsGapOrShortPoll) { _channelGetDifferenceTimeByPts.remove(channel); } if (!channel->ptsInited() || channel->ptsRequesting()) return; if (from != ChannelDifferenceRequest::AfterFail) { _channelGetDifferenceTimeAfterFail.remove(channel); } channel->ptsSetRequesting(true); auto filter = MTP_channelMessagesFilterEmpty(); auto flags = MTPupdates_GetChannelDifference::Flag::f_force | 0; if (from != ChannelDifferenceRequest::PtsGapOrShortPoll) { if (!channel->ptsWaitingForSkipped()) { flags = 0; // No force flag when requesting for short poll. } } MTP::send(MTPupdates_GetChannelDifference(MTP_flags(flags), channel->inputChannel, filter, MTP_int(channel->pts()), MTP_int(MTPChannelGetDifferenceLimit)), rpcDone(&MainWidget::gotChannelDifference, channel), rpcFail(&MainWidget::failChannelDifference, channel)); } void MainWidget::mtpPing() { MTP::ping(); } void MainWidget::start(const MTPUser *self) { if (!self) { MTP::send(MTPusers_GetFullUser(MTP_inputUserSelf()), rpcDone(&MainWidget::startWithSelf)); return; } if (!Auth().validateSelf(*self)) { return; } Local::readSavedPeers(); cSetOtherOnline(0); if (auto user = App::feedUsers(MTP_vector<MTPUser>(1, *self))) { user->loadUserpic(); } MTP::send(MTPupdates_GetState(), rpcDone(&MainWidget::gotState)); update(); _started = true; App::wnd()->sendServiceHistoryRequest(); Local::readInstalledStickers(); Local::readFeaturedStickers(); Local::readRecentStickers(); Local::readFavedStickers(); Local::readSavedGifs(); _history->start(); Messenger::Instance().checkStartUrl(); } bool MainWidget::started() { return _started; } void MainWidget::openPeerByName(const QString &username, MsgId msgId, const QString &startToken) { Messenger::Instance().hideMediaView(); PeerData *peer = App::peerByName(username); if (peer) { if (msgId == ShowAtGameShareMsgId) { if (peer->isUser() && peer->asUser()->botInfo && !startToken.isEmpty()) { peer->asUser()->botInfo->shareGameShortName = startToken; AddBotToGroupBoxController::Start(peer->asUser()); } else { InvokeQueued(this, [this, peer] { _controller->showPeerHistory( peer->id, SectionShow::Way::Forward); }); } } else if (msgId == ShowAtProfileMsgId && !peer->isChannel()) { if (peer->isUser() && peer->asUser()->botInfo && !peer->asUser()->botInfo->cantJoinGroups && !startToken.isEmpty()) { peer->asUser()->botInfo->startGroupToken = startToken; AddBotToGroupBoxController::Start(peer->asUser()); } else if (peer->isUser() && peer->asUser()->botInfo) { // Always open bot chats, even from mention links. InvokeQueued(this, [this, peer] { _controller->showPeerHistory( peer->id, SectionShow::Way::Forward); }); } else { _controller->showPeerInfo(peer); } } else { if (msgId == ShowAtProfileMsgId || !peer->isChannel()) { // show specific posts only in channels / supergroups msgId = ShowAtUnreadMsgId; } if (peer->isUser() && peer->asUser()->botInfo) { peer->asUser()->botInfo->startToken = startToken; if (peer == _history->peer()) { _history->updateControlsVisibility(); _history->updateControlsGeometry(); } } InvokeQueued(this, [this, peer, msgId] { _controller->showPeerHistory( peer->id, SectionShow::Way::Forward, msgId); }); } } else { MTP::send(MTPcontacts_ResolveUsername(MTP_string(username)), rpcDone(&MainWidget::usernameResolveDone, qMakePair(msgId, startToken)), rpcFail(&MainWidget::usernameResolveFail, username)); } } void MainWidget::joinGroupByHash(const QString &hash) { Messenger::Instance().hideMediaView(); MTP::send(MTPmessages_CheckChatInvite(MTP_string(hash)), rpcDone(&MainWidget::inviteCheckDone, hash), rpcFail(&MainWidget::inviteCheckFail)); } void MainWidget::stickersBox(const MTPInputStickerSet &set) { Messenger::Instance().hideMediaView(); Ui::show(Box<StickerSetBox>(set)); } void MainWidget::onSelfParticipantUpdated(ChannelData *channel) { auto history = App::historyLoaded(channel->id); if (_updatedChannels.contains(channel)) { _updatedChannels.remove(channel); if (!history) { history = App::history(channel); } if (history->isEmpty()) { Auth().api().requestDialogEntry(history); } else { history->checkJoinedMessage(true); } } else if (history) { history->checkJoinedMessage(); } Auth().data().sendHistoryChangeNotifications(); } bool MainWidget::contentOverlapped(const QRect &globalRect) { return (_history->contentOverlapped(globalRect) || _playerPanel->overlaps(globalRect) || _playerPlaylist->overlaps(globalRect) || (_playerVolume && _playerVolume->overlaps(globalRect))); } void MainWidget::usernameResolveDone(QPair<MsgId, QString> msgIdAndStartToken, const MTPcontacts_ResolvedPeer &result) { Ui::hideLayer(); if (result.type() != mtpc_contacts_resolvedPeer) return; const auto &d(result.c_contacts_resolvedPeer()); App::feedUsers(d.vusers); App::feedChats(d.vchats); PeerId peerId = peerFromMTP(d.vpeer); if (!peerId) return; PeerData *peer = App::peer(peerId); MsgId msgId = msgIdAndStartToken.first; QString startToken = msgIdAndStartToken.second; if (msgId == ShowAtProfileMsgId && !peer->isChannel()) { if (peer->isUser() && peer->asUser()->botInfo && !peer->asUser()->botInfo->cantJoinGroups && !startToken.isEmpty()) { peer->asUser()->botInfo->startGroupToken = startToken; AddBotToGroupBoxController::Start(peer->asUser()); } else if (peer->isUser() && peer->asUser()->botInfo) { // Always open bot chats, even from mention links. InvokeQueued(this, [this, peer] { _controller->showPeerHistory( peer->id, SectionShow::Way::Forward); }); } else { _controller->showPeerInfo(peer); } } else { if (msgId == ShowAtProfileMsgId || !peer->isChannel()) { // show specific posts only in channels / supergroups msgId = ShowAtUnreadMsgId; } if (peer->isUser() && peer->asUser()->botInfo) { peer->asUser()->botInfo->startToken = startToken; if (peer == _history->peer()) { _history->updateControlsVisibility(); _history->updateControlsGeometry(); } } InvokeQueued(this, [this, peer, msgId] { _controller->showPeerHistory( peer->id, SectionShow::Way::Forward, msgId); }); } } bool MainWidget::usernameResolveFail(QString name, const RPCError &error) { if (MTP::isDefaultHandledError(error)) return false; if (error.code() == 400) { Ui::show(Box<InformBox>(lng_username_not_found(lt_user, name))); } return true; } void MainWidget::inviteCheckDone(QString hash, const MTPChatInvite &invite) { switch (invite.type()) { case mtpc_chatInvite: { auto &d = invite.c_chatInvite(); auto participants = QVector<UserData*>(); if (d.has_participants()) { auto &v = d.vparticipants.v; participants.reserve(v.size()); for_const (auto &user, v) { if (auto feededUser = App::feedUser(user)) { participants.push_back(feededUser); } } } _inviteHash = hash; auto box = Box<ConfirmInviteBox>( qs(d.vtitle), d.is_channel() && !d.is_megagroup(), d.vphoto, d.vparticipants_count.v, participants); Ui::show(std::move(box)); } break; case mtpc_chatInviteAlready: { auto &d = invite.c_chatInviteAlready(); if (auto chat = App::feedChat(d.vchat)) { _controller->showPeerHistory( chat, SectionShow::Way::Forward); } } break; } } bool MainWidget::inviteCheckFail(const RPCError &error) { if (MTP::isDefaultHandledError(error)) return false; if (error.code() == 400) { Ui::show(Box<InformBox>(lang(lng_group_invite_bad_link))); } return true; } void MainWidget::onInviteImport() { if (_inviteHash.isEmpty()) return; MTP::send( MTPmessages_ImportChatInvite(MTP_string(_inviteHash)), rpcDone(&MainWidget::inviteImportDone), rpcFail(&MainWidget::inviteImportFail)); } void MainWidget::inviteImportDone(const MTPUpdates &updates) { App::main()->sentUpdatesReceived(updates); Ui::hideLayer(); const QVector<MTPChat> *v = 0; switch (updates.type()) { case mtpc_updates: v = &updates.c_updates().vchats.v; break; case mtpc_updatesCombined: v = &updates.c_updatesCombined().vchats.v; break; default: LOG(("API Error: unexpected update cons %1 (MainWidget::inviteImportDone)").arg(updates.type())); break; } if (v && !v->isEmpty()) { const auto &mtpChat = v->front(); const auto peerId = [&] { if (mtpChat.type() == mtpc_chat) { return peerFromChat(mtpChat.c_chat().vid.v); } else if (mtpChat.type() == mtpc_channel) { return peerFromChannel(mtpChat.c_channel().vid.v); } return PeerId(0); }(); if (const auto peer = App::peerLoaded(peerId)) { _controller->showPeerHistory( peer, SectionShow::Way::Forward); } } } bool MainWidget::inviteImportFail(const RPCError &error) { if (MTP::isDefaultHandledError(error)) return false; if (error.type() == qstr("CHANNELS_TOO_MUCH")) { Ui::show(Box<InformBox>(lang(lng_join_channel_error))); } else if (error.code() == 400) { Ui::show(Box<InformBox>(lang(error.type() == qstr("USERS_TOO_MUCH") ? lng_group_invite_no_room : lng_group_invite_bad_link))); } return true; } void MainWidget::startWithSelf(const MTPUserFull &result) { Expects(result.type() == mtpc_userFull); auto &d = result.c_userFull(); start(&d.vuser); if (auto user = App::self()) { Auth().api().processFullPeer(user, result); } } void MainWidget::applyNotifySetting( const MTPNotifyPeer ¬ifyPeer, const MTPPeerNotifySettings &settings, History *history) { if (notifyPeer.type() != mtpc_notifyPeer) { // Ignore those for now, they were not ever used. return; } const auto &data = notifyPeer.c_notifyPeer(); const auto peer = App::peerLoaded(peerFromMTP(data.vpeer)); if (!peer || !peer->notifyChange(settings)) { return; } updateNotifySettingsLocal(peer, history); } void MainWidget::updateNotifySettings( not_null<PeerData*> peer, Data::NotifySettings::MuteChange mute, Data::NotifySettings::SilentPostsChange silent, int muteForSeconds) { if (peer->notifyChange(mute, silent, muteForSeconds)) { updateNotifySettingsLocal(peer); updateNotifySettingPeers.insert(peer); updateNotifySettingTimer.start(NotifySettingSaveTimeout); } } void MainWidget::updateNotifySettingsLocal( not_null<PeerData*> peer, History *history) { if (!history) { history = App::historyLoaded(peer->id); } const auto muteFinishesIn = peer->notifyMuteFinishesIn(); const auto muted = (muteFinishesIn > 0); if (history && history->changeMute(muted)) { // Notification already sent. } else { Notify::peerUpdatedDelayed( peer, Notify::PeerUpdate::Flag::NotificationsEnabled); } if (muted) { App::regMuted(peer, muteFinishesIn); if (history) { Auth().notifications().clearFromHistory(history); } } else { App::unregMuted(peer); } } void MainWidget::incrementSticker(DocumentData *sticker) { if (!sticker || !sticker->sticker()) return; if (sticker->sticker()->set.type() == mtpc_inputStickerSetEmpty) return; bool writeRecentStickers = false; auto &sets = Auth().data().stickerSetsRef(); auto it = sets.find(Stickers::CloudRecentSetId); if (it == sets.cend()) { if (it == sets.cend()) { it = sets.insert(Stickers::CloudRecentSetId, Stickers::Set( Stickers::CloudRecentSetId, uint64(0), lang(lng_recent_stickers), QString(), 0, // count 0, // hash MTPDstickerSet_ClientFlag::f_special | 0, TimeId(0))); } else { it->title = lang(lng_recent_stickers); } } auto removedFromEmoji = std::vector<not_null<EmojiPtr>>(); auto index = it->stickers.indexOf(sticker); if (index > 0) { if (it->dates.empty()) { Auth().api().requestRecentStickersForce(); } else { Assert(it->dates.size() == it->stickers.size()); it->dates.erase(it->dates.begin() + index); } it->stickers.removeAt(index); for (auto i = it->emoji.begin(); i != it->emoji.end();) { if (const auto index = i->indexOf(sticker); index >= 0) { removedFromEmoji.push_back(i.key()); i->removeAt(index); if (i->isEmpty()) { i = it->emoji.erase(i); continue; } } ++i; } } if (index) { if (it->dates.size() == it->stickers.size()) { it->dates.insert(it->dates.begin(), unixtime()); } it->stickers.push_front(sticker); if (const auto emojiList = Stickers::GetEmojiListFromSet(sticker)) { for (const auto emoji : *emojiList) { it->emoji[emoji].push_front(sticker); } } else if (!removedFromEmoji.empty()) { for (const auto emoji : removedFromEmoji) { it->emoji[emoji].push_front(sticker); } } else { Auth().api().requestRecentStickersForce(); } writeRecentStickers = true; } // Remove that sticker from old recent, now it is in cloud recent stickers. bool writeOldRecent = false; auto &recent = Stickers::GetRecentPack(); for (auto i = recent.begin(), e = recent.end(); i != e; ++i) { if (i->first == sticker) { writeOldRecent = true; recent.erase(i); break; } } while (!recent.isEmpty() && it->stickers.size() + recent.size() > Global::StickersRecentLimit()) { writeOldRecent = true; recent.pop_back(); } if (writeOldRecent) { Local::writeUserSettings(); } // Remove that sticker from custom stickers, now it is in cloud recent stickers. bool writeInstalledStickers = false; auto custom = sets.find(Stickers::CustomSetId); if (custom != sets.cend()) { int removeIndex = custom->stickers.indexOf(sticker); if (removeIndex >= 0) { custom->stickers.removeAt(removeIndex); if (custom->stickers.isEmpty()) { sets.erase(custom); } writeInstalledStickers = true; } } if (writeInstalledStickers) { Local::writeInstalledStickers(); } if (writeRecentStickers) { Local::writeRecentStickers(); } _history->updateRecentStickers(); } void MainWidget::activate() { if (_a_show.animating()) return; if (!_mainSection) { if (_hider) { if (_hider->wasOffered()) { _hider->setFocus(); } else { _dialogs->activate(); } } else if (App::wnd() && !Ui::isLayerShown()) { if (!cSendPaths().isEmpty()) { showSendPathsLayer(); } else if (_history->peer()) { _history->activate(); } else { _dialogs->activate(); } } } App::wnd()->fixOrder(); } void MainWidget::destroyData() { _history->destroyData(); _dialogs->destroyData(); } bool MainWidget::isActive() const { return !_isIdle && isVisible() && !_a_show.animating(); } bool MainWidget::doWeReadServerHistory() const { return isActive() && !_mainSection && _history->doWeReadServerHistory(); } bool MainWidget::doWeReadMentions() const { return isActive() && !_mainSection && _history->doWeReadMentions(); } bool MainWidget::lastWasOnline() const { return _lastWasOnline; } TimeMs MainWidget::lastSetOnline() const { return _lastSetOnline; } int32 MainWidget::dlgsWidth() const { return _dialogs->width(); } MainWidget::~MainWidget() { if (App::main() == this) _history->showHistory(0, 0); if (HistoryHider *hider = _hider) { _hider = nullptr; delete hider; } Messenger::Instance().mtp()->clearGlobalHandlers(); } void MainWidget::updateOnline(bool gotOtherOffline) { if (this != App::main()) return; InvokeQueued(this, [] { Auth().checkAutoLock(); }); bool isOnline = App::wnd()->isActive(); int updateIn = Global::OnlineUpdatePeriod(); if (isOnline) { auto idle = psIdleTime(); if (idle >= Global::OfflineIdleTimeout()) { isOnline = false; if (!_isIdle) { _isIdle = true; _idleFinishTimer.start(900); } } else { updateIn = qMin(updateIn, int(Global::OfflineIdleTimeout() - idle)); } } auto ms = getms(true); if (isOnline != _lastWasOnline || (isOnline && _lastSetOnline + Global::OnlineUpdatePeriod() <= ms) || (isOnline && gotOtherOffline)) { if (_onlineRequest) { MTP::cancel(_onlineRequest); _onlineRequest = 0; } _lastWasOnline = isOnline; _lastSetOnline = ms; _onlineRequest = MTP::send(MTPaccount_UpdateStatus(MTP_bool(!isOnline))); if (App::self()) { App::self()->onlineTill = unixtime() + (isOnline ? (Global::OnlineUpdatePeriod() / 1000) : -1); Notify::peerUpdatedDelayed( App::self(), Notify::PeerUpdate::Flag::UserOnlineChanged); } if (!isOnline) { // Went offline, so we need to save message draft to the cloud. saveDraftToCloud(); } _lastSetOnline = ms; } else if (isOnline) { updateIn = qMin(updateIn, int(_lastSetOnline + Global::OnlineUpdatePeriod() - ms)); } _onlineTimer.start(updateIn); } void MainWidget::saveDraftToCloud() { _history->saveFieldToHistoryLocalDraft(); auto peer = _history->peer(); if (auto history = App::historyLoaded(peer)) { writeDrafts(history); auto localDraft = history->localDraft(); auto cloudDraft = history->cloudDraft(); if (!Data::draftsAreEqual(localDraft, cloudDraft)) { Auth().api().saveDraftToCloudDelayed(history); } } } void MainWidget::applyCloudDraft(History *history) { _history->applyCloudDraft(history); } void MainWidget::writeDrafts(History *history) { Local::MessageDraft storedLocalDraft, storedEditDraft; MessageCursor localCursor, editCursor; if (auto localDraft = history->localDraft()) { if (!Data::draftsAreEqual(localDraft, history->cloudDraft())) { storedLocalDraft = Local::MessageDraft(localDraft->msgId, localDraft->textWithTags, localDraft->previewCancelled); localCursor = localDraft->cursor; } } if (auto editDraft = history->editDraft()) { storedEditDraft = Local::MessageDraft(editDraft->msgId, editDraft->textWithTags, editDraft->previewCancelled); editCursor = editDraft->cursor; } Local::writeDrafts(history->peer->id, storedLocalDraft, storedEditDraft); Local::writeDraftCursors(history->peer->id, localCursor, editCursor); } void MainWidget::checkIdleFinish() { if (this != App::main()) return; if (psIdleTime() < Global::OfflineIdleTimeout()) { _idleFinishTimer.stop(); _isIdle = false; updateOnline(); if (App::wnd()) App::wnd()->checkHistoryActivation(); } else { _idleFinishTimer.start(900); } } void MainWidget::updateReceived(const mtpPrime *from, const mtpPrime *end) { if (end <= from) return; Auth().checkAutoLock(); if (mtpTypeId(*from) == mtpc_new_session_created) { try { MTPNewSession newSession; newSession.read(from, end); } catch (mtpErrorUnexpected &) { } updSeq = 0; MTP_LOG(0, ("getDifference { after new_session_created }%1").arg(cTestMode() ? " TESTMODE" : "")); return getDifference(); } else { try { MTPUpdates updates; updates.read(from, end); _lastUpdateTime = getms(true); noUpdatesTimer.start(NoUpdatesTimeout); if (!requestingDifference()) { feedUpdates(updates); } } catch (mtpErrorUnexpected &) { // just some other type } } update(); } namespace { bool fwdInfoDataLoaded(const MTPMessageFwdHeader &header) { if (header.type() != mtpc_messageFwdHeader) { return true; } auto &info = header.c_messageFwdHeader(); if (info.has_channel_id()) { if (!App::channelLoaded(peerFromChannel(info.vchannel_id))) { return false; } if (info.has_from_id() && !App::user(peerFromUser(info.vfrom_id), PeerData::MinimalLoaded)) { return false; } } else { if (info.has_from_id() && !App::userLoaded(peerFromUser(info.vfrom_id))) { return false; } } return true; } bool mentionUsersLoaded(const MTPVector<MTPMessageEntity> &entities) { for_const (auto &entity, entities.v) { auto type = entity.type(); if (type == mtpc_messageEntityMentionName) { if (!App::userLoaded(peerFromUser(entity.c_messageEntityMentionName().vuser_id))) { return false; } } else if (type == mtpc_inputMessageEntityMentionName) { auto &inputUser = entity.c_inputMessageEntityMentionName().vuser_id; if (inputUser.type() == mtpc_inputUser) { if (!App::userLoaded(peerFromUser(inputUser.c_inputUser().vuser_id))) { return false; } } } } return true; } enum class DataIsLoadedResult { NotLoaded = 0, FromNotLoaded = 1, MentionNotLoaded = 2, Ok = 3, }; DataIsLoadedResult allDataLoadedForMessage(const MTPMessage &msg) { switch (msg.type()) { case mtpc_message: { const MTPDmessage &d(msg.c_message()); if (!d.is_post() && d.has_from_id()) { if (!App::userLoaded(peerFromUser(d.vfrom_id))) { return DataIsLoadedResult::FromNotLoaded; } } if (d.has_via_bot_id()) { if (!App::userLoaded(peerFromUser(d.vvia_bot_id))) { return DataIsLoadedResult::NotLoaded; } } if (d.has_fwd_from() && !fwdInfoDataLoaded(d.vfwd_from)) { return DataIsLoadedResult::NotLoaded; } if (d.has_entities() && !mentionUsersLoaded(d.ventities)) { return DataIsLoadedResult::MentionNotLoaded; } } break; case mtpc_messageService: { const MTPDmessageService &d(msg.c_messageService()); if (!d.is_post() && d.has_from_id()) { if (!App::userLoaded(peerFromUser(d.vfrom_id))) { return DataIsLoadedResult::FromNotLoaded; } } switch (d.vaction.type()) { case mtpc_messageActionChatAddUser: { for_const (const MTPint &userId, d.vaction.c_messageActionChatAddUser().vusers.v) { if (!App::userLoaded(peerFromUser(userId))) { return DataIsLoadedResult::NotLoaded; } } } break; case mtpc_messageActionChatJoinedByLink: { if (!App::userLoaded(peerFromUser(d.vaction.c_messageActionChatJoinedByLink().vinviter_id))) { return DataIsLoadedResult::NotLoaded; } } break; case mtpc_messageActionChatDeleteUser: { if (!App::userLoaded(peerFromUser(d.vaction.c_messageActionChatDeleteUser().vuser_id))) { return DataIsLoadedResult::NotLoaded; } } break; } } break; } return DataIsLoadedResult::Ok; } } // namespace void MainWidget::feedUpdates(const MTPUpdates &updates, uint64 randomId) { switch (updates.type()) { case mtpc_updates: { auto &d = updates.c_updates(); if (d.vseq.v) { if (d.vseq.v <= updSeq) { return; } if (d.vseq.v > updSeq + 1) { _bySeqUpdates.insert(d.vseq.v, updates); return _bySeqTimer.start(WaitForSkippedTimeout); } } App::feedUsers(d.vusers); App::feedChats(d.vchats); feedUpdateVector(d.vupdates); updSetState(0, d.vdate.v, updQts, d.vseq.v); } break; case mtpc_updatesCombined: { auto &d = updates.c_updatesCombined(); if (d.vseq_start.v) { if (d.vseq_start.v <= updSeq) { return; } if (d.vseq_start.v > updSeq + 1) { _bySeqUpdates.insert(d.vseq_start.v, updates); return _bySeqTimer.start(WaitForSkippedTimeout); } } App::feedUsers(d.vusers); App::feedChats(d.vchats); feedUpdateVector(d.vupdates); updSetState(0, d.vdate.v, updQts, d.vseq.v); } break; case mtpc_updateShort: { auto &d = updates.c_updateShort(); feedUpdate(d.vupdate); updSetState(0, d.vdate.v, updQts, updSeq); } break; case mtpc_updateShortMessage: { auto &d = updates.c_updateShortMessage(); if (!App::userLoaded(d.vuser_id.v) || (d.has_via_bot_id() && !App::userLoaded(d.vvia_bot_id.v)) || (d.has_entities() && !mentionUsersLoaded(d.ventities)) || (d.has_fwd_from() && !fwdInfoDataLoaded(d.vfwd_from))) { MTP_LOG(0, ("getDifference { good - getting user for updateShortMessage }%1").arg(cTestMode() ? " TESTMODE" : "")); return getDifference(); } if (ptsUpdateAndApply(d.vpts.v, d.vpts_count.v, updates)) { // Update date as well. updSetState(0, d.vdate.v, updQts, updSeq); } } break; case mtpc_updateShortChatMessage: { auto &d = updates.c_updateShortChatMessage(); bool noFrom = !App::userLoaded(d.vfrom_id.v); if (!App::chatLoaded(d.vchat_id.v) || noFrom || (d.has_via_bot_id() && !App::userLoaded(d.vvia_bot_id.v)) || (d.has_entities() && !mentionUsersLoaded(d.ventities)) || (d.has_fwd_from() && !fwdInfoDataLoaded(d.vfwd_from))) { MTP_LOG(0, ("getDifference { good - getting user for updateShortChatMessage }%1").arg(cTestMode() ? " TESTMODE" : "")); if (noFrom) { Auth().api().requestFullPeer(App::chatLoaded(d.vchat_id.v)); } return getDifference(); } if (ptsUpdateAndApply(d.vpts.v, d.vpts_count.v, updates)) { // Update date as well. updSetState(0, d.vdate.v, updQts, updSeq); } } break; case mtpc_updateShortSentMessage: { auto &d = updates.c_updateShortSentMessage(); if (!IsServerMsgId(d.vid.v)) { LOG(("API Error: Bad msgId got from server: %1").arg(d.vid.v)); } else if (randomId) { PeerId peerId = 0; QString text; App::histSentDataByItem(randomId, peerId, text); const auto wasAlready = (peerId != 0) && (App::histItemById(peerToChannel(peerId), d.vid.v) != nullptr); feedUpdate(MTP_updateMessageID(d.vid, MTP_long(randomId))); // ignore real date if (peerId) { if (auto item = App::histItemById(peerToChannel(peerId), d.vid.v)) { if (d.has_entities() && !mentionUsersLoaded(d.ventities)) { Auth().api().requestMessageData( item->history()->peer->asChannel(), item->id, ApiWrap::RequestMessageDataCallback()); } const auto entities = d.has_entities() ? TextUtilities::EntitiesFromMTP(d.ventities.v) : EntitiesInText(); const auto media = d.has_media() ? &d.vmedia : nullptr; item->setText({ text, entities }); item->updateSentMedia(media); if (!wasAlready) { item->indexAsNewItem(); } } } } if (ptsUpdateAndApply(d.vpts.v, d.vpts_count.v, updates)) { // Update date as well. updSetState(0, d.vdate.v, updQts, updSeq); } } break; case mtpc_updatesTooLong: { MTP_LOG(0, ("getDifference { good - updatesTooLong received }%1").arg(cTestMode() ? " TESTMODE" : "")); return getDifference(); } break; } Auth().data().sendHistoryChangeNotifications(); } void MainWidget::feedUpdate(const MTPUpdate &update) { switch (update.type()) { // New messages. case mtpc_updateNewMessage: { auto &d = update.c_updateNewMessage(); DataIsLoadedResult isDataLoaded = allDataLoadedForMessage(d.vmessage); if (!requestingDifference() && isDataLoaded != DataIsLoadedResult::Ok) { MTP_LOG(0, ("getDifference { good - " "after not all data loaded in updateNewMessage }%1" ).arg(cTestMode() ? " TESTMODE" : "")); // This can be if this update was created by grouping // some short message update into an updates vector. return getDifference(); } ptsUpdateAndApply(d.vpts.v, d.vpts_count.v, update); } break; case mtpc_updateNewChannelMessage: { auto &d = update.c_updateNewChannelMessage(); auto channel = App::channelLoaded(peerToChannel(peerFromMessage(d.vmessage))); auto isDataLoaded = allDataLoadedForMessage(d.vmessage); if (!requestingDifference() && (!channel || isDataLoaded != DataIsLoadedResult::Ok)) { MTP_LOG(0, ("getDifference { good - " "after not all data loaded in updateNewChannelMessage }%1" ).arg(cTestMode() ? " TESTMODE" : "")); // Request last active supergroup participants if the 'from' user was not loaded yet. // This will optimize similar getDifference() calls for almost all next messages. if (isDataLoaded == DataIsLoadedResult::FromNotLoaded && channel && channel->isMegagroup()) { if (channel->mgInfo->lastParticipants.size() < Global::ChatSizeMax() && (channel->mgInfo->lastParticipants.empty() || channel->mgInfo->lastParticipants.size() < channel->membersCount())) { Auth().api().requestLastParticipants(channel); } } if (!_byMinChannelTimer.isActive()) { // getDifference after timeout _byMinChannelTimer.start(WaitForSkippedTimeout); } return; } if (channel && !_handlingChannelDifference) { if (channel->ptsRequesting()) { // skip global updates while getting channel difference return; } channel->ptsUpdateAndApply(d.vpts.v, d.vpts_count.v, update); } else { Auth().api().applyUpdateNoPtsCheck(update); } } break; case mtpc_updateMessageID: { const auto &d = update.c_updateMessageID(); if (const auto fullId = App::histItemByRandom(d.vrandom_id.v)) { const auto channel = fullId.channel; const auto newId = d.vid.v; if (const auto local = App::histItemById(fullId)) { const auto existing = App::histItemById(channel, newId); if (existing && !local->mainView()) { const auto history = local->history(); local->destroy(); if (!history->lastMessageKnown()) { Auth().api().requestDialogEntry(history); } } else { if (existing) { existing->destroy(); } local->setRealId(d.vid.v); } } App::historyUnregRandom(d.vrandom_id.v); } App::historyUnregSentData(d.vrandom_id.v); } break; // Message contents being read. case mtpc_updateReadMessagesContents: { auto &d = update.c_updateReadMessagesContents(); ptsUpdateAndApply(d.vpts.v, d.vpts_count.v, update); } break; case mtpc_updateChannelReadMessagesContents: { auto &d = update.c_updateChannelReadMessagesContents(); auto channel = App::channelLoaded(d.vchannel_id.v); if (!channel) { if (!_byMinChannelTimer.isActive()) { // getDifference after timeout. _byMinChannelTimer.start(WaitForSkippedTimeout); } return; } auto possiblyReadMentions = base::flat_set<MsgId>(); for_const (auto &msgId, d.vmessages.v) { if (auto item = App::histItemById(channel, msgId.v)) { if (item->isMediaUnread()) { item->markMediaRead(); Auth().data().requestItemRepaint(item); } } else { // Perhaps it was an unread mention! possiblyReadMentions.insert(msgId.v); } } Auth().api().checkForUnreadMentions(possiblyReadMentions, channel); } break; // Edited messages. case mtpc_updateEditMessage: { auto &d = update.c_updateEditMessage(); ptsUpdateAndApply(d.vpts.v, d.vpts_count.v, update); } break; case mtpc_updateEditChannelMessage: { auto &d = update.c_updateEditChannelMessage(); auto channel = App::channelLoaded(peerToChannel(peerFromMessage(d.vmessage))); if (channel && !_handlingChannelDifference) { if (channel->ptsRequesting()) { // skip global updates while getting channel difference return; } else { channel->ptsUpdateAndApply(d.vpts.v, d.vpts_count.v, update); } } else { Auth().api().applyUpdateNoPtsCheck(update); } } break; // Messages being read. case mtpc_updateReadHistoryInbox: { auto &d = update.c_updateReadHistoryInbox(); ptsUpdateAndApply(d.vpts.v, d.vpts_count.v, update); } break; case mtpc_updateReadHistoryOutbox: { auto &d = update.c_updateReadHistoryOutbox(); if (ptsUpdateAndApply(d.vpts.v, d.vpts_count.v, update)) { // We could've updated the double checks. // Better would be for history to be subscribed to outbox read events. _history->update(); } } break; case mtpc_updateReadChannelInbox: { auto &d = update.c_updateReadChannelInbox(); App::feedInboxRead(peerFromChannel(d.vchannel_id.v), d.vmax_id.v); } break; case mtpc_updateReadChannelOutbox: { auto &d = update.c_updateReadChannelOutbox(); auto peerId = peerFromChannel(d.vchannel_id.v); auto when = requestingDifference() ? 0 : unixtime(); App::feedOutboxRead(peerId, d.vmax_id.v, when); if (_history->peer() && _history->peer()->id == peerId) { _history->update(); } } break; //case mtpc_updateReadFeed: { // #feed // const auto &d = update.c_updateReadFeed(); // const auto feedId = d.vfeed_id.v; // if (const auto feed = Auth().data().feedLoaded(feedId)) { // feed->setUnreadPosition( // Data::FeedPositionFromMTP(d.vmax_position)); // if (d.has_unread_count() && d.has_unread_muted_count()) { // feed->setUnreadCounts( // d.vunread_count.v, // d.vunread_muted_count.v); // } else { // Auth().api().requestDialogEntry(feed); // } // } //} break; // Deleted messages. case mtpc_updateDeleteMessages: { auto &d = update.c_updateDeleteMessages(); ptsUpdateAndApply(d.vpts.v, d.vpts_count.v, update); } break; case mtpc_updateDeleteChannelMessages: { auto &d = update.c_updateDeleteChannelMessages(); auto channel = App::channelLoaded(d.vchannel_id.v); if (channel && !_handlingChannelDifference) { if (channel->ptsRequesting()) { // skip global updates while getting channel difference return; } channel->ptsUpdateAndApply(d.vpts.v, d.vpts_count.v, update); } else { Auth().api().applyUpdateNoPtsCheck(update); } } break; case mtpc_updateWebPage: { auto &d = update.c_updateWebPage(); // Update web page anyway. Auth().data().webpage(d.vwebpage); _history->updatePreview(); Auth().data().sendWebPageGameNotifications(); ptsUpdateAndApply(d.vpts.v, d.vpts_count.v, update); } break; case mtpc_updateChannelWebPage: { auto &d = update.c_updateChannelWebPage(); // Update web page anyway. Auth().data().webpage(d.vwebpage); _history->updatePreview(); Auth().data().sendWebPageGameNotifications(); auto channel = App::channelLoaded(d.vchannel_id.v); if (channel && !_handlingChannelDifference) { if (channel->ptsRequesting()) { // skip global updates while getting channel difference return; } else { channel->ptsUpdateAndApply(d.vpts.v, d.vpts_count.v, update); } } else { Auth().api().applyUpdateNoPtsCheck(update); } } break; case mtpc_updateUserTyping: { auto &d = update.c_updateUserTyping(); const auto userId = peerFromUser(d.vuser_id); const auto history = App::historyLoaded(userId); const auto user = App::userLoaded(d.vuser_id.v); if (history && user) { const auto when = requestingDifference() ? 0 : unixtime(); App::histories().registerSendAction(history, user, d.vaction, when); } } break; case mtpc_updateChatUserTyping: { auto &d = update.c_updateChatUserTyping(); const auto history = [&]() -> History* { if (auto chat = App::chatLoaded(d.vchat_id.v)) { return App::historyLoaded(chat->id); } else if (auto channel = App::channelLoaded(d.vchat_id.v)) { return App::historyLoaded(channel->id); } return nullptr; }(); const auto user = (d.vuser_id.v == Auth().userId()) ? nullptr : App::userLoaded(d.vuser_id.v); if (history && user) { const auto when = requestingDifference() ? 0 : unixtime(); App::histories().registerSendAction(history, user, d.vaction, when); } } break; case mtpc_updateChatParticipants: { App::feedParticipants(update.c_updateChatParticipants().vparticipants, true); } break; case mtpc_updateChatParticipantAdd: { App::feedParticipantAdd(update.c_updateChatParticipantAdd()); } break; case mtpc_updateChatParticipantDelete: { App::feedParticipantDelete(update.c_updateChatParticipantDelete()); } break; case mtpc_updateChatAdmins: { App::feedChatAdmins(update.c_updateChatAdmins()); } break; case mtpc_updateChatParticipantAdmin: { App::feedParticipantAdmin(update.c_updateChatParticipantAdmin()); } break; case mtpc_updateUserStatus: { auto &d = update.c_updateUserStatus(); if (auto user = App::userLoaded(d.vuser_id.v)) { switch (d.vstatus.type()) { case mtpc_userStatusEmpty: user->onlineTill = 0; break; case mtpc_userStatusRecently: if (user->onlineTill > -10) { // don't modify pseudo-online user->onlineTill = -2; } break; case mtpc_userStatusLastWeek: user->onlineTill = -3; break; case mtpc_userStatusLastMonth: user->onlineTill = -4; break; case mtpc_userStatusOffline: user->onlineTill = d.vstatus.c_userStatusOffline().vwas_online.v; break; case mtpc_userStatusOnline: user->onlineTill = d.vstatus.c_userStatusOnline().vexpires.v; break; } Notify::peerUpdatedDelayed( user, Notify::PeerUpdate::Flag::UserOnlineChanged); } if (d.vuser_id.v == Auth().userId()) { if (d.vstatus.type() == mtpc_userStatusOffline || d.vstatus.type() == mtpc_userStatusEmpty) { updateOnline(true); if (d.vstatus.type() == mtpc_userStatusOffline) { cSetOtherOnline(d.vstatus.c_userStatusOffline().vwas_online.v); } } else if (d.vstatus.type() == mtpc_userStatusOnline) { cSetOtherOnline(d.vstatus.c_userStatusOnline().vexpires.v); } } } break; case mtpc_updateUserName: { auto &d = update.c_updateUserName(); if (auto user = App::userLoaded(d.vuser_id.v)) { if (user->contactStatus() != UserData::ContactStatus::Contact) { user->setName( TextUtilities::SingleLine(qs(d.vfirst_name)), TextUtilities::SingleLine(qs(d.vlast_name)), user->nameOrPhone, TextUtilities::SingleLine(qs(d.vusername))); } else { user->setName( TextUtilities::SingleLine(user->firstName), TextUtilities::SingleLine(user->lastName), user->nameOrPhone, TextUtilities::SingleLine(qs(d.vusername))); } } } break; case mtpc_updateUserPhoto: { auto &d = update.c_updateUserPhoto(); if (auto user = App::userLoaded(d.vuser_id.v)) { user->setPhoto(d.vphoto); user->loadUserpic(); if (mtpIsTrue(d.vprevious) || !user->userpicPhotoId()) { Auth().storage().remove(Storage::UserPhotosRemoveAfter( user->bareId(), user->userpicPhotoId())); } else { Auth().storage().add(Storage::UserPhotosAddNew( user->bareId(), user->userpicPhotoId())); } } } break; case mtpc_updateContactRegistered: { const auto &d = update.c_updateContactRegistered(); if (const auto user = App::userLoaded(d.vuser_id.v)) { if (App::history(user->id)->loadedAtBottom()) { App::history(user->id)->addNewService( clientMsgId(), d.vdate.v, lng_action_user_registered(lt_from, user->name), MTPDmessage::Flags(0)); } } } break; case mtpc_updateContactLink: { const auto &d = update.c_updateContactLink(); App::feedUserLink(d.vuser_id, d.vmy_link, d.vforeign_link); } break; case mtpc_updateNotifySettings: { auto &d = update.c_updateNotifySettings(); applyNotifySetting(d.vpeer, d.vnotify_settings); } break; case mtpc_updateDcOptions: { auto &d = update.c_updateDcOptions(); Messenger::Instance().dcOptions()->addFromList(d.vdc_options); } break; case mtpc_updateConfig: { Messenger::Instance().mtp()->requestConfig(); } break; case mtpc_updateUserPhone: { auto &d = update.c_updateUserPhone(); if (auto user = App::userLoaded(d.vuser_id.v)) { auto newPhone = qs(d.vphone); if (newPhone != user->phone()) { user->setPhone(newPhone); user->setName( user->firstName, user->lastName, ((user->contactStatus() == UserData::ContactStatus::Contact || isServiceUser(user->id) || user->isSelf() || user->phone().isEmpty()) ? QString() : App::formatPhone(user->phone())), user->username); Notify::peerUpdatedDelayed( user, Notify::PeerUpdate::Flag::UserPhoneChanged); } } } break; case mtpc_updateNewEncryptedMessage: { auto &d = update.c_updateNewEncryptedMessage(); } break; case mtpc_updateEncryptedChatTyping: { auto &d = update.c_updateEncryptedChatTyping(); } break; case mtpc_updateEncryption: { auto &d = update.c_updateEncryption(); } break; case mtpc_updateEncryptedMessagesRead: { auto &d = update.c_updateEncryptedMessagesRead(); } break; case mtpc_updatePhoneCall: { Calls::Current().handleUpdate(update.c_updatePhoneCall()); } break; case mtpc_updateUserBlocked: { auto &d = update.c_updateUserBlocked(); if (auto user = App::userLoaded(d.vuser_id.v)) { user->setBlockStatus(mtpIsTrue(d.vblocked) ? UserData::BlockStatus::Blocked : UserData::BlockStatus::NotBlocked); } } break; case mtpc_updateServiceNotification: { auto &d = update.c_updateServiceNotification(); if (d.is_popup()) { Ui::show(Box<InformBox>(qs(d.vmessage))); } else { App::wnd()->serviceNotification({ qs(d.vmessage), TextUtilities::EntitiesFromMTP(d.ventities.v) }, d.vmedia); emit App::wnd()->checkNewAuthorization(); } } break; case mtpc_updatePrivacy: { auto &d = update.c_updatePrivacy(); Auth().api().handlePrivacyChange(d.vkey.type(), d.vrules); } break; case mtpc_updatePinnedDialogs: { const auto &d = update.c_updatePinnedDialogs(); if (d.has_order()) { const auto &order = d.vorder.v; const auto allLoaded = [&] { for (const auto &dialogPeer : order) { switch (dialogPeer.type()) { case mtpc_dialogPeer: { const auto &peer = dialogPeer.c_dialogPeer(); const auto peerId = peerFromMTP(peer.vpeer); if (!App::historyLoaded(peerId)) { DEBUG_LOG(("API Error: " "pinned chat not loaded for peer %1" ).arg(peerId )); return false; } } break; //case mtpc_dialogPeerFeed: { // #feed // const auto &feed = dialogPeer.c_dialogPeerFeed(); // const auto feedId = feed.vfeed_id.v; // if (!Auth().data().feedLoaded(feedId)) { // DEBUG_LOG(("API Error: " // "pinned feed not loaded for feedId %1" // ).arg(feedId // )); // return false; // } //} break; } } return true; }(); if (allLoaded) { Auth().data().applyPinnedDialogs(order); } else { _dialogs->loadPinnedDialogs(); } } else { _dialogs->loadPinnedDialogs(); } } break; case mtpc_updateDialogPinned: { const auto &d = update.c_updateDialogPinned(); switch (d.vpeer.type()) { case mtpc_dialogPeer: { const auto peerId = peerFromMTP(d.vpeer.c_dialogPeer().vpeer); if (const auto history = App::historyLoaded(peerId)) { Auth().data().setPinnedDialog(history, d.is_pinned()); } else { DEBUG_LOG(("API Error: " "pinned chat not loaded for peer %1" ).arg(peerId )); _dialogs->loadPinnedDialogs(); } } break; //case mtpc_dialogPeerFeed: { // #feed // const auto feedId = d.vpeer.c_dialogPeerFeed().vfeed_id.v; // if (const auto feed = Auth().data().feedLoaded(feedId)) { // Auth().data().setPinnedDialog(feed, d.is_pinned()); // } else { // DEBUG_LOG(("API Error: " // "pinned feed not loaded for feedId %1" // ).arg(feedId // )); // _dialogs->loadPinnedDialogs(); // } //} break; } } break; case mtpc_updateChannel: { auto &d = update.c_updateChannel(); if (const auto channel = App::channelLoaded(d.vchannel_id.v)) { channel->inviter = UserId(0); if (!channel->amIn()) { if (const auto history = App::historyLoaded(channel->id)) { history->updateChatListExistence(); } } else if (!channel->amCreator() && App::history(channel->id)) { _updatedChannels.insert(channel, true); Auth().api().requestSelfParticipant(channel); } if (const auto feed = channel->feed()) { if (!feed->lastMessageKnown() || !feed->unreadCountKnown()) { Auth().api().requestDialogEntry(feed); } } else if (channel->amIn()) { const auto history = App::history(channel->id); if (!history->lastMessageKnown() || !history->unreadCountKnown()) { Auth().api().requestDialogEntry(history); } } } } break; case mtpc_updateChannelPinnedMessage: { auto &d = update.c_updateChannelPinnedMessage(); if (auto channel = App::channelLoaded(d.vchannel_id.v)) { channel->setPinnedMessageId(d.vid.v); } } break; case mtpc_updateChannelTooLong: { auto &d = update.c_updateChannelTooLong(); if (auto channel = App::channelLoaded(d.vchannel_id.v)) { if (!d.has_pts() || channel->pts() < d.vpts.v) { getChannelDifference(channel); } } } break; case mtpc_updateChannelMessageViews: { auto &d = update.c_updateChannelMessageViews(); if (auto item = App::histItemById(d.vchannel_id.v, d.vid.v)) { item->setViewsCount(d.vviews.v); } } break; case mtpc_updateChannelAvailableMessages: { auto &d = update.c_updateChannelAvailableMessages(); if (auto channel = App::channelLoaded(d.vchannel_id.v)) { channel->setAvailableMinId(d.vavailable_min_id.v); } } break; ////// Cloud sticker sets case mtpc_updateNewStickerSet: { auto &d = update.c_updateNewStickerSet(); bool writeArchived = false; if (d.vstickerset.type() == mtpc_messages_stickerSet) { auto &set = d.vstickerset.c_messages_stickerSet(); if (set.vset.type() == mtpc_stickerSet) { auto &s = set.vset.c_stickerSet(); if (!s.has_installed_date()) { LOG(("API Error: " "updateNewStickerSet without install_date flag.")); } if (!s.is_masks()) { auto &sets = Auth().data().stickerSetsRef(); auto it = sets.find(s.vid.v); if (it == sets.cend()) { it = sets.insert(s.vid.v, Stickers::Set( s.vid.v, s.vaccess_hash.v, Stickers::GetSetTitle(s), qs(s.vshort_name), s.vcount.v, s.vhash.v, s.vflags.v | MTPDstickerSet::Flag::f_installed_date, s.has_installed_date() ? s.vinstalled_date.v : unixtime())); } else { it->flags |= MTPDstickerSet::Flag::f_installed_date; if (!it->installDate) { it->installDate = unixtime(); } if (it->flags & MTPDstickerSet::Flag::f_archived) { it->flags &= ~MTPDstickerSet::Flag::f_archived; writeArchived = true; } } auto inputSet = MTP_inputStickerSetID(MTP_long(it->id), MTP_long(it->access)); auto &v = set.vdocuments.v; it->stickers.clear(); it->stickers.reserve(v.size()); for (int i = 0, l = v.size(); i < l; ++i) { const auto doc = Auth().data().document(v.at(i)); if (!doc->sticker()) continue; it->stickers.push_back(doc); if (doc->sticker()->set.type() != mtpc_inputStickerSetID) { doc->sticker()->set = inputSet; } } it->emoji.clear(); auto &packs = set.vpacks.v; for (auto i = 0, l = packs.size(); i != l; ++i) { if (packs[i].type() != mtpc_stickerPack) continue; auto &pack = packs.at(i).c_stickerPack(); if (auto emoji = Ui::Emoji::Find(qs(pack.vemoticon))) { emoji = emoji->original(); auto &stickers = pack.vdocuments.v; Stickers::Pack p; p.reserve(stickers.size()); for (auto j = 0, c = stickers.size(); j != c; ++j) { auto doc = Auth().data().document(stickers[j].v); if (!doc->sticker()) continue; p.push_back(doc); } it->emoji.insert(emoji, p); } } auto &order = Auth().data().stickerSetsOrderRef(); int32 insertAtIndex = 0, currentIndex = order.indexOf(s.vid.v); if (currentIndex != insertAtIndex) { if (currentIndex > 0) { order.removeAt(currentIndex); } order.insert(insertAtIndex, s.vid.v); } auto custom = sets.find(Stickers::CustomSetId); if (custom != sets.cend()) { for (int32 i = 0, l = it->stickers.size(); i < l; ++i) { int32 removeIndex = custom->stickers.indexOf(it->stickers.at(i)); if (removeIndex >= 0) custom->stickers.removeAt(removeIndex); } if (custom->stickers.isEmpty()) { sets.erase(custom); } } Local::writeInstalledStickers(); if (writeArchived) Local::writeArchivedStickers(); Auth().data().notifyStickersUpdated(); } } } } break; case mtpc_updateStickerSetsOrder: { auto &d = update.c_updateStickerSetsOrder(); if (!d.is_masks()) { auto &order = d.vorder.v; auto &sets = Auth().data().stickerSets(); Stickers::Order result; for (int i = 0, l = order.size(); i < l; ++i) { if (sets.constFind(order.at(i).v) == sets.cend()) { break; } result.push_back(order.at(i).v); } if (result.size() != Auth().data().stickerSetsOrder().size() || result.size() != order.size()) { Auth().data().setLastStickersUpdate(0); Auth().api().updateStickers(); } else { Auth().data().stickerSetsOrderRef() = std::move(result); Local::writeInstalledStickers(); Auth().data().notifyStickersUpdated(); } } } break; case mtpc_updateStickerSets: { Auth().data().setLastStickersUpdate(0); Auth().api().updateStickers(); } break; case mtpc_updateRecentStickers: { Auth().data().setLastRecentStickersUpdate(0); Auth().api().updateStickers(); } break; case mtpc_updateFavedStickers: { Auth().data().setLastFavedStickersUpdate(0); Auth().api().updateStickers(); } break; case mtpc_updateReadFeaturedStickers: { // We read some of the featured stickers, perhaps not all of them. // Here we don't know what featured sticker sets were read, so we // request all of them once again. Auth().data().setLastFeaturedStickersUpdate(0); Auth().api().updateStickers(); } break; ////// Cloud saved GIFs case mtpc_updateSavedGifs: { Auth().data().setLastSavedGifsUpdate(0); Auth().api().updateStickers(); } break; ////// Cloud drafts case mtpc_updateDraftMessage: { auto &peerDraft = update.c_updateDraftMessage(); auto peerId = peerFromMTP(peerDraft.vpeer); auto &draftMessage = peerDraft.vdraft; if (draftMessage.type() == mtpc_draftMessage) { auto &draft = draftMessage.c_draftMessage(); Data::applyPeerCloudDraft(peerId, draft); } else { Data::clearPeerCloudDraft(peerId); } } break; ////// Cloud langpacks case mtpc_updateLangPack: { auto &langpack = update.c_updateLangPack(); Lang::CurrentCloudManager().applyLangPackDifference(langpack.vdifference); } break; case mtpc_updateLangPackTooLong: { Lang::CurrentCloudManager().requestLangPackDifference(); } break; } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/mainwidget.h���������������������������������������������������0000664�0000000�0000000�00000046542�13262451251�0021737�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "core/single_timer.h" #include "base/weak_ptr.h" #include "ui/rp_widget.h" struct HistoryMessageMarkupButton; class MainWindow; class ConfirmBox; class DialogsWidget; class HistoryWidget; class HistoryHider; class StackItem; struct FileLoadResult; namespace Notify { struct PeerUpdate; } // namespace Notify namespace Dialogs { struct RowDescriptor; class Row; class Key; class IndexedList; } // namespace Dialogs namespace Media { namespace Player { class Widget; class VolumeWidget; class Panel; class Float; } // namespace Player } // namespace Media namespace Ui { class ResizeArea; class PlainShadow; class DropdownMenu; template <typename Widget> class SlideWrap; } // namespace Ui namespace Window { class Controller; class PlayerWrapWidget; class SectionMemento; class SectionWidget; class AbstractSectionWidget; struct SectionSlideParams; struct SectionShow; enum class Column; } // namespace Window namespace Calls { class Call; class TopBar; } // namespace Calls namespace InlineBots { namespace Layout { class ItemBase; } // namespace Layout } // namespace InlineBots class MainWidget : public Ui::RpWidget, public RPCSender, private base::Subscriber { Q_OBJECT public: using SectionShow = Window::SectionShow; MainWidget(QWidget *parent, not_null<Window::Controller*> controller); bool isMainSectionShown() const; bool isThirdSectionShown() const; int contentScrollAddToY() const; void showAnimated(const QPixmap &bgAnimCache, bool back = false); void start(const MTPUser *self = nullptr); void openPeerByName(const QString &name, MsgId msgId = ShowAtUnreadMsgId, const QString &startToken = QString()); void joinGroupByHash(const QString &hash); void stickersBox(const MTPInputStickerSet &set); bool started(); void applyNotifySetting( const MTPNotifyPeer ¬ifyPeer, const MTPPeerNotifySettings &settings, History *history = 0); void updateNotifySettings( not_null<PeerData*> peer, Data::NotifySettings::MuteChange mute, Data::NotifySettings::SilentPostsChange silent = Data::NotifySettings::SilentPostsChange::Ignore, int muteForSeconds = 86400 * 365); void incrementSticker(DocumentData *sticker); void activate(); void createDialog(Dialogs::Key key); void removeDialog(Dialogs::Key key); void repaintDialogRow(Dialogs::Mode list, not_null<Dialogs::Row*> row); void repaintDialogRow(not_null<History*> history, MsgId messageId); void windowShown(); void sentUpdatesReceived(uint64 randomId, const MTPUpdates &updates); void sentUpdatesReceived(const MTPUpdates &updates) { return sentUpdatesReceived(0, updates); } bool deleteChannelFailed(const RPCError &error); void inviteToChannelDone( not_null<ChannelData*> channel, const MTPUpdates &updates); void historyToDown(History *hist); void dialogsToUp(); void newUnreadMsg( not_null<History*> history, not_null<HistoryItem*> item); void markActiveHistoryAsRead(); Dialogs::RowDescriptor chatListEntryBefore( const Dialogs::RowDescriptor &which) const; Dialogs::RowDescriptor chatListEntryAfter( const Dialogs::RowDescriptor &which) const; PeerData *peer(); int backgroundFromY() const; void showSection( Window::SectionMemento &&memento, const SectionShow ¶ms); void updateColumnLayout(); bool stackIsEmpty() const; void showBackFromStack( const SectionShow ¶ms); void orderWidgets(); QRect historyRect() const; QPixmap grabForShowAnimation(const Window::SectionSlideParams ¶ms); void checkMainSectionToLayer(); void onSendFileConfirm(const std::shared_ptr<FileLoadResult> &file); bool onSendSticker(DocumentData *sticker); void destroyData(); void updateOnlineDisplayIn(int32 msecs); bool isActive() const; bool doWeReadServerHistory() const; bool doWeReadMentions() const; bool lastWasOnline() const; TimeMs lastSetOnline() const; void saveDraftToCloud(); void applyCloudDraft(History *history); void writeDrafts(History *history); int32 dlgsWidth() const; void showForwardLayer(MessageIdsList &&items); void showSendPathsLayer(); void deleteLayer(FullMsgId itemId); void cancelUploadLayer(not_null<HistoryItem*> item); void shareUrlLayer(const QString &url, const QString &text); void inlineSwitchLayer(const QString &botAndQuery); void hiderLayer(object_ptr<HistoryHider> h); void noHider(HistoryHider *destroyed); bool setForwardDraft(PeerId peer, MessageIdsList &&items); bool shareUrl( not_null<PeerData*> peer, const QString &url, const QString &text); void replyToItem(not_null<HistoryItem*> item); bool onInlineSwitchChosen(const PeerId &peer, const QString &botAndQuery); bool onSendPaths(const PeerId &peer); void onFilesOrForwardDrop(const PeerId &peer, const QMimeData *data); bool selectingPeer(bool withConfirm = false) const; bool selectingPeerForInlineSwitch(); void offerPeer(PeerId peer); void dialogsActivate(); void deletePhotoLayer(PhotoData *photo); bool leaveChatFailed(PeerData *peer, const RPCError &e); void deleteHistoryAfterLeave(PeerData *peer, const MTPUpdates &updates); void deleteMessages( not_null<PeerData*> peer, const QVector<MTPint> &ids, bool forEveryone); void deletedContact(UserData *user, const MTPcontacts_Link &result); void deleteConversation( not_null<PeerData*> peer, bool deleteHistory = true); void deleteAndExit(ChatData *chat); void deleteAllFromUser(ChannelData *channel, UserData *from); void addParticipants( not_null<PeerData*> chatOrChannel, const std::vector<not_null<UserData*>> &users); struct UserAndPeer { UserData *user; PeerData *peer; }; bool addParticipantFail(UserAndPeer data, const RPCError &e); bool addParticipantsFail( not_null<ChannelData*> channel, const RPCError &e); // for multi invite in channels bool sendMessageFail(const RPCError &error); Dialogs::IndexedList *contactsList(); Dialogs::IndexedList *dialogsList(); Dialogs::IndexedList *contactsNoDialogsList(); struct MessageToSend { MessageToSend(not_null<History*> history) : history(history) { } not_null<History*> history; TextWithTags textWithTags; MsgId replyTo = 0; WebPageId webPageId = 0; bool clearDraft = true; }; void sendMessage(const MessageToSend &message); void saveRecentHashtags(const QString &text); void unreadCountChanged(not_null<History*> history); // While HistoryInner is not HistoryView::ListWidget. TimeMs highlightStartTime(not_null<const HistoryItem*> item) const; bool historyInSelectionMode() const; void sendBotCommand(PeerData *peer, UserData *bot, const QString &cmd, MsgId replyTo); void hideSingleUseKeyboard(PeerData *peer, MsgId replyTo); bool insertBotCommand(const QString &cmd); void searchMessages(const QString &query, Dialogs::Key inChat); void itemEdited(not_null<HistoryItem*> item); void checkLastUpdate(bool afterSleep); void insertCheckedServiceNotification(const TextWithEntities &message, const MTPMessageMedia &media, int32 date); void serviceHistoryDone(const MTPmessages_Messages &msgs); bool serviceHistoryFail(const RPCError &error); bool isIdle() const; QPixmap cachedBackground(const QRect &forRect, int &x, int &y); void updateScrollColors(); void setChatBackground(const App::WallPaper &wp); bool chatBackgroundLoading(); float64 chatBackgroundProgress() const; void checkChatBackground(); ImagePtr newBackgroundThumb(); void messageDataReceived(ChannelData *channel, MsgId msgId); void updateBotKeyboard(History *h); void pushReplyReturn(not_null<HistoryItem*> item); void cancelForwarding(not_null<History*> history); void finishForwarding(not_null<History*> history); void updateMutedIn(TimeMs delay); // Does offerPeer or showPeerHistory. void choosePeer(PeerId peerId, MsgId showAtMsgId); void clearBotStartToken(PeerData *peer); void ptsWaiterStartTimerFor(ChannelData *channel, int32 ms); // ms <= 0 - stop timer void feedUpdates(const MTPUpdates &updates, uint64 randomId = 0); void ctrlEnterSubmitUpdated(); void setInnerFocus(); void scheduleViewIncrement(HistoryItem *item); void onSelfParticipantUpdated(ChannelData *channel); void feedChannelDifference(const MTPDupdates_channelDifference &data); // Mayde public for ApiWrap, while it is still here. // Better would be for this to be moved to ApiWrap. bool requestingDifference() const { return _ptsWaiter.requesting(); } bool contentOverlapped(const QRect &globalRect); bool ptsUpdateAndApply(int32 pts, int32 ptsCount, const MTPUpdates &updates); bool ptsUpdateAndApply(int32 pts, int32 ptsCount, const MTPUpdate &update); bool ptsUpdateAndApply(int32 pts, int32 ptsCount); void documentLoadProgress(DocumentData *document); void searchInChat(Dialogs::Key chat); void app_sendBotCallback( not_null<const HistoryMessageMarkupButton*> button, not_null<const HistoryItem*> msg, int row, int column); void ui_showPeerHistory( PeerId peer, const SectionShow ¶ms, MsgId msgId); PeerData *ui_getPeerForMouseAction(); void notify_botCommandsChanged(UserData *bot); void notify_inlineBotRequesting(bool requesting); void notify_replyMarkupUpdated(const HistoryItem *item); void notify_inlineKeyboardMoved(const HistoryItem *item, int oldKeyboardTop, int newKeyboardTop); bool notify_switchInlineBotButtonReceived(const QString &query, UserData *samePeerBot, MsgId samePeerReplyTo); void notify_userIsBotChanged(UserData *bot); void notify_migrateUpdated(PeerData *peer); void notify_historyMuteUpdated(History *history); bool cmd_search(); bool cmd_next_chat(); bool cmd_previous_chat(); ~MainWidget(); signals: void dialogRowReplaced(Dialogs::Row *oldRow, Dialogs::Row *newRow); void dialogsUpdated(); public slots: void documentLoadProgress(FileLoader *loader); void documentLoadFailed(FileLoader *loader, bool started); void inlineResultLoadProgress(FileLoader *loader); void inlineResultLoadFailed(FileLoader *loader, bool started); void dialogsCancelled(); void getDifference(); void onGetDifferenceTimeByPts(); void onGetDifferenceTimeAfterFail(); void mtpPing(); void updateOnline(bool gotOtherOffline = false); void checkIdleFinish(); void onUpdateNotifySettings(); void onCacheBackground(); void onInviteImport(); void onUpdateMuted(); void onViewsIncrement(); protected: void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; void keyPressEvent(QKeyEvent *e) override; bool eventFilter(QObject *o, QEvent *e) override; private: struct Float { template <typename ToggleCallback, typename DraggedCallback> Float( QWidget *parent, not_null<Window::Controller*> controller, not_null<HistoryItem*> item, ToggleCallback toggle, DraggedCallback dragged); bool hiddenByWidget = false; bool hiddenByHistory = false; bool visible = false; RectPart animationSide; Animation visibleAnimation; Window::Column column; RectPart corner; QPoint dragFrom; Animation draggedAnimation; bool hiddenByDrag = false; object_ptr<Media::Player::Float> widget; }; using ChannelGetDifferenceTime = QMap<ChannelData*, TimeMs>; enum class ChannelDifferenceRequest { Unknown, PtsGapOrShortPoll, AfterFail, }; struct DeleteHistoryRequest { PeerData *peer; bool justClearHistory; }; struct DeleteAllFromUserParams { ChannelData *channel; UserData *from; }; void animationCallback(); void handleAdaptiveLayoutUpdate(); void updateWindowAdaptiveLayout(); void handleAudioUpdate(const AudioMsgId &audioId); void updateMediaPlayerPosition(); void updateMediaPlaylistPosition(int x); void updateControlsGeometry(); void updateDialogsWidthAnimated(); void updateThirdColumnToCurrentChat( Dialogs::Key key, bool canWrite); [[nodiscard]] bool saveThirdSectionToStackBack() const; [[nodiscard]] auto thirdSectionForCurrentMainSection(Dialogs::Key key) -> std::unique_ptr<Window::SectionMemento>; void userIsContactUpdated(not_null<UserData*> user); void createPlayer(); void switchToPanelPlayer(); void switchToFixedPlayer(); void closeBothPlayers(); void playerHeightUpdated(); void setCurrentCall(Calls::Call *call); void createCallTopBar(); void destroyCallTopBar(); void callTopBarHeightUpdated(int callTopBarHeight); void messagesAffected( not_null<PeerData*> peer, const MTPmessages_AffectedMessages &result); Window::SectionSlideParams prepareShowAnimation( bool willHaveTopBarShadow); void showNewSection( Window::SectionMemento &&memento, const SectionShow ¶ms); void dropMainSection(Window::SectionWidget *widget); Window::SectionSlideParams prepareThirdSectionAnimation(Window::SectionWidget *section); // All this methods use the prepareShowAnimation(). Window::SectionSlideParams prepareMainSectionAnimation(Window::SectionWidget *section); Window::SectionSlideParams prepareHistoryAnimation(PeerId historyPeerId); Window::SectionSlideParams prepareDialogsAnimation(); void startWithSelf(const MTPUserFull &user); void saveSectionInStack(); void getChannelDifference(ChannelData *channel, ChannelDifferenceRequest from = ChannelDifferenceRequest::Unknown); void gotDifference(const MTPupdates_Difference &diff); bool failDifference(const RPCError &e); void feedDifference(const MTPVector<MTPUser> &users, const MTPVector<MTPChat> &chats, const MTPVector<MTPMessage> &msgs, const MTPVector<MTPUpdate> &other); void gotState(const MTPupdates_State &state); void updSetState(int32 pts, int32 date, int32 qts, int32 seq); void gotChannelDifference(ChannelData *channel, const MTPupdates_ChannelDifference &diff); bool failChannelDifference(ChannelData *channel, const RPCError &err); void failDifferenceStartTimerFor(ChannelData *channel); void feedUpdateVector( const MTPVector<MTPUpdate> &updates, bool skipMessageIds = false); // Doesn't call sendHistoryChangeNotifications itself. void feedMessageIds(const MTPVector<MTPUpdate> &updates); // Doesn't call sendHistoryChangeNotifications itself. void feedUpdate(const MTPUpdate &update); void deleteHistoryPart(DeleteHistoryRequest request, const MTPmessages_AffectedHistory &result); void updateReceived(const mtpPrime *from, const mtpPrime *end); bool updateFail(const RPCError &e); void usernameResolveDone(QPair<MsgId, QString> msgIdAndStartToken, const MTPcontacts_ResolvedPeer &result); bool usernameResolveFail(QString name, const RPCError &error); void inviteCheckDone(QString hash, const MTPChatInvite &invite); bool inviteCheckFail(const RPCError &error); void inviteImportDone(const MTPUpdates &result); bool inviteImportFail(const RPCError &error); int getMainSectionTop() const; int getThirdSectionTop() const; void hideAll(); void showAll(); void clearCachedBackground(); void checkCurrentFloatPlayer(); void createFloatPlayer(not_null<HistoryItem*> item); void toggleFloatPlayer(not_null<Float*> instance); void checkFloatPlayerVisibility(); void updateFloatPlayerPosition(not_null<Float*> instance); void removeFloatPlayer(not_null<Float*> instance); Float *currentFloatPlayer() const { return _playerFloats.empty() ? nullptr : _playerFloats.back().get(); } Window::AbstractSectionWidget *getFloatPlayerSection( Window::Column column) const; void finishFloatPlayerDrag( not_null<Float*> instance, bool closed); void updateFloatPlayerColumnCorner(QPoint center); QPoint getFloatPlayerPosition(not_null<Float*> instance) const; QPoint getFloatPlayerHiddenPosition( QPoint position, QSize size, RectPart side) const; RectPart getFloatPlayerSide(QPoint center) const; bool getDifferenceTimeChanged(ChannelData *channel, int32 ms, ChannelGetDifferenceTime &channelCurTime, TimeMs &curTime); void viewsIncrementDone(QVector<MTPint> ids, const MTPVector<MTPint> &result, mtpRequestId req); bool viewsIncrementFail(const RPCError &error, mtpRequestId req); void refreshResizeAreas(); template <typename MoveCallback, typename FinishCallback> void createResizeArea( object_ptr<Ui::ResizeArea> &area, MoveCallback &&moveCallback, FinishCallback &&finishCallback); void ensureFirstColumnResizeAreaCreated(); void ensureThirdColumnResizeAreaCreated(); void updateNotifySettingsLocal( not_null<PeerData*> peer, History *history = nullptr); not_null<Window::Controller*> _controller; bool _started = false; SingleTimer _updateMutedTimer; QString _inviteHash; Animation _a_show; bool _showBack = false; QPixmap _cacheUnder, _cacheOver; int _dialogsWidth = 0; int _thirdColumnWidth = 0; Animation _a_dialogsWidth; object_ptr<Ui::PlainShadow> _sideShadow; object_ptr<Ui::PlainShadow> _thirdShadow = { nullptr }; object_ptr<Ui::ResizeArea> _firstColumnResizeArea = { nullptr }; object_ptr<Ui::ResizeArea> _thirdColumnResizeArea = { nullptr }; object_ptr<DialogsWidget> _dialogs; object_ptr<HistoryWidget> _history; object_ptr<Window::SectionWidget> _mainSection = { nullptr }; object_ptr<Window::SectionWidget> _thirdSection = { nullptr }; std::unique_ptr<Window::SectionMemento> _thirdSectionFromStack; base::weak_ptr<Calls::Call> _currentCall; object_ptr<Ui::SlideWrap<Calls::TopBar>> _callTopBar = { nullptr }; object_ptr<Window::PlayerWrapWidget> _player = { nullptr }; object_ptr<Media::Player::VolumeWidget> _playerVolume = { nullptr }; object_ptr<Media::Player::Panel> _playerPlaylist; object_ptr<Media::Player::Panel> _playerPanel; bool _playerUsingPanel = false; std::vector<std::unique_ptr<Float>> _playerFloats; QPointer<ConfirmBox> _forwardConfirm; // for single column layout object_ptr<HistoryHider> _hider = { nullptr }; std::vector<std::unique_ptr<StackItem>> _stack; int _playerHeight = 0; int _callTopBarHeight = 0; int _contentScrollAddToY = 0; int32 updDate = 0; int32 updQts = -1; int32 updSeq = 0; SingleTimer noUpdatesTimer; PtsWaiter _ptsWaiter; ChannelGetDifferenceTime _channelGetDifferenceTimeByPts, _channelGetDifferenceTimeAfterFail; TimeMs _getDifferenceTimeByPts = 0; TimeMs _getDifferenceTimeAfterFail = 0; SingleTimer _byPtsTimer; QMap<int32, MTPUpdates> _bySeqUpdates; SingleTimer _bySeqTimer; SingleTimer _byMinChannelTimer; mtpRequestId _onlineRequest = 0; SingleTimer _onlineTimer, _idleFinishTimer; bool _lastWasOnline = false; TimeMs _lastSetOnline = 0; bool _isIdle = false; base::flat_set<not_null<PeerData*>> updateNotifySettingPeers; SingleTimer updateNotifySettingTimer; int32 _failDifferenceTimeout = 1; // growing timeout for getDifference calls, if it fails typedef QMap<ChannelData*, int32> ChannelFailDifferenceTimeout; ChannelFailDifferenceTimeout _channelFailDifferenceTimeout; // growing timeout for getChannelDifference calls, if it fails SingleTimer _failDifferenceTimer; TimeMs _lastUpdateTime = 0; bool _handlingChannelDifference = false; QPixmap _cachedBackground; QRect _cachedFor, _willCacheFor; int _cachedX = 0; int _cachedY = 0; SingleTimer _cacheBackgroundTimer; typedef QMap<ChannelData*, bool> UpdatedChannels; UpdatedChannels _updatedChannels; PhotoData *_deletingPhoto = nullptr; typedef QMap<MsgId, bool> ViewsIncrementMap; typedef QMap<PeerData*, ViewsIncrementMap> ViewsIncrement; ViewsIncrement _viewsIncremented, _viewsToIncrement; typedef QMap<PeerData*, mtpRequestId> ViewsIncrementRequests; ViewsIncrementRequests _viewsIncrementRequests; typedef QMap<mtpRequestId, PeerData*> ViewsIncrementByRequest; ViewsIncrementByRequest _viewsIncrementByRequest; SingleTimer _viewsIncrementTimer; std::unique_ptr<App::WallPaper> _background; bool _firstColumnResizing = false; int _firstColumnResizingShift = 0; }; ��������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/mainwindow.cpp�������������������������������������������������0000664�0000000�0000000�00000070371�13262451251�0022313�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "mainwindow.h" #include "data/data_document.h" #include "dialogs/dialogs_layout.h" #include "styles/style_dialogs.h" #include "styles/style_window.h" #include "styles/style_boxes.h" #include "history/history.h" #include "ui/widgets/popup_menu.h" #include "ui/widgets/buttons.h" #include "ui/widgets/shadow.h" #include "lang/lang_cloud_manager.h" #include "lang/lang_instance.h" #include "lang/lang_keys.h" #include "shortcuts.h" #include "messenger.h" #include "application.h" #include "passcodewidget.h" #include "intro/introwidget.h" #include "mainwidget.h" #include "boxes/confirm_box.h" #include "boxes/add_contact_box.h" #include "boxes/connection_box.h" #include "observer_peer.h" #include "mediaview.h" #include "storage/localstorage.h" #include "apiwrap.h" #include "settings/settings_widget.h" #include "platform/platform_notifications_manager.h" #include "window/layer_widget.h" #include "window/notifications_manager.h" #include "window/themes/window_theme.h" #include "window/themes/window_theme_warning.h" #include "window/window_main_menu.h" #include "auth_session.h" #include "window/window_controller.h" namespace { // Code for testing languages is F7-F6-F7-F8 void FeedLangTestingKey(int key) { static auto codeState = 0; if ((codeState == 0 && key == Qt::Key_F7) || (codeState == 1 && key == Qt::Key_F6) || (codeState == 2 && key == Qt::Key_F7) || (codeState == 3 && key == Qt::Key_F8)) { ++codeState; } else { codeState = 0; } if (codeState == 4) { codeState = 0; Lang::CurrentCloudManager().switchToTestLanguage(); } } } // namespace ConnectingWidget::ConnectingWidget(QWidget *parent, const QString &text, const QString &reconnect) : TWidget(parent) , _reconnect(this, QString()) { set(text, reconnect); connect(_reconnect, SIGNAL(clicked()), this, SLOT(onReconnect())); } void ConnectingWidget::set(const QString &text, const QString &reconnect) { _text = text; _textWidth = st::linkFont->width(_text) + st::linkFont->spacew; int32 _reconnectWidth = 0; if (reconnect.isEmpty()) { _reconnect->hide(); } else { _reconnect->setText(reconnect); _reconnect->show(); _reconnect->move(st::connectingPadding.left() + _textWidth, st::boxRoundShadow.extend.top() + st::connectingPadding.top()); _reconnectWidth = _reconnect->width(); } resize(st::connectingPadding.left() + _textWidth + _reconnectWidth + st::connectingPadding.right() + st::boxRoundShadow.extend.right(), st::boxRoundShadow.extend.top() + st::connectingPadding.top() + st::normalFont->height + st::connectingPadding.bottom()); update(); } void ConnectingWidget::paintEvent(QPaintEvent *e) { Painter p(this); auto sides = RectPart::Top | RectPart::Right; Ui::Shadow::paint(p, QRect(0, st::boxRoundShadow.extend.top(), width() - st::boxRoundShadow.extend.right(), height() - st::boxRoundShadow.extend.top()), width(), st::boxRoundShadow, sides); auto parts = RectPart::Top | RectPart::TopRight | RectPart::Center | RectPart::Right; App::roundRect(p, QRect(-st::boxRadius, st::boxRoundShadow.extend.top(), width() - st::boxRoundShadow.extend.right() + st::boxRadius, height() - st::boxRoundShadow.extend.top() + st::boxRadius), st::boxBg, BoxCorners, nullptr, parts); p.setFont(st::normalFont); p.setPen(st::windowSubTextFg); p.drawText(st::connectingPadding.left(), st::boxRoundShadow.extend.top() + st::connectingPadding.top() + st::normalFont->ascent, _text); } void ConnectingWidget::onReconnect() { auto throughProxy = (Global::ConnectionType() != dbictAuto); if (throughProxy) { Ui::show(Box<ConnectionBox>()); } else { MTP::restart(); } } MainWindow::MainWindow() { auto logo = Messenger::Instance().logo(); icon16 = logo.scaledToWidth(16, Qt::SmoothTransformation); icon32 = logo.scaledToWidth(32, Qt::SmoothTransformation); icon64 = logo.scaledToWidth(64, Qt::SmoothTransformation); auto logoNoMargin = Messenger::Instance().logoNoMargin(); iconbig16 = logoNoMargin.scaledToWidth(16, Qt::SmoothTransformation); iconbig32 = logoNoMargin.scaledToWidth(32, Qt::SmoothTransformation); iconbig64 = logoNoMargin.scaledToWidth(64, Qt::SmoothTransformation); resize(st::windowDefaultWidth, st::windowDefaultHeight); setLocale(QLocale(QLocale::English, QLocale::UnitedStates)); subscribe(Global::RefSelfChanged(), [this] { updateGlobalMenu(); }); subscribe(Window::Theme::Background(), [this](const Window::Theme::BackgroundUpdate &data) { themeUpdated(data); }); subscribe(Messenger::Instance().passcodedChanged(), [this] { updateGlobalMenu(); }); setAttribute(Qt::WA_NoSystemBackground); setAttribute(Qt::WA_OpaquePaintEvent); } void MainWindow::initHook() { Platform::MainWindow::initHook(); QCoreApplication::instance()->installEventFilter(this); connect(windowHandle(), &QWindow::activeChanged, this, [this] { checkHistoryActivation(); }, Qt::QueuedConnection); } void MainWindow::firstShow() { #ifdef Q_OS_WIN trayIconMenu = new Ui::PopupMenu(nullptr); trayIconMenu->deleteOnHide(false); #else // Q_OS_WIN trayIconMenu = new QMenu(this); #endif // else for Q_OS_WIN auto isLinux = (cPlatform() == dbipLinux32 || cPlatform() == dbipLinux64); auto notificationActionText = lang(Global::DesktopNotify() ? lng_disable_notifications_from_tray : lng_enable_notifications_from_tray); if (isLinux) { trayIconMenu->addAction(lang(lng_open_from_tray), this, SLOT(showFromTray())); trayIconMenu->addAction(lang(lng_minimize_to_tray), this, SLOT(minimizeToTray())); trayIconMenu->addAction(notificationActionText, this, SLOT(toggleDisplayNotifyFromTray())); trayIconMenu->addAction(lang(lng_quit_from_tray), this, SLOT(quitFromTray())); } else { trayIconMenu->addAction(lang(lng_minimize_to_tray), this, SLOT(minimizeToTray())); trayIconMenu->addAction(notificationActionText, this, SLOT(toggleDisplayNotifyFromTray())); trayIconMenu->addAction(lang(lng_quit_from_tray), this, SLOT(quitFromTray())); } Global::RefWorkMode().setForced(Global::WorkMode().value(), true); psFirstShow(); updateTrayMenu(); } void MainWindow::clearWidgetsHook() { auto wasMain = (_main != nullptr); _passcode.destroyDelayed(); _main.destroy(); _intro.destroy(); if (wasMain) { App::clearHistories(); } } QPixmap MainWindow::grabInner() { QPixmap result; if (_intro) { result = Ui::GrabWidget(_intro); } else if (_passcode) { result = Ui::GrabWidget(_passcode); } else if (_main) { result = Ui::GrabWidget(_main); } return result; } void MainWindow::clearPasscode() { if (!_passcode) return; auto bg = grabInner(); _passcode.destroy(); if (_intro) { _intro->showAnimated(bg, true); } else { Assert(_main != nullptr); _main->showAnimated(bg, true); Messenger::Instance().checkStartUrl(); } } void MainWindow::setupPasscode() { auto animated = (_main || _intro); auto bg = animated ? grabInner() : QPixmap(); _passcode.create(bodyWidget()); updateControlsGeometry(); if (_main) _main->hide(); Messenger::Instance().hideMediaView(); Ui::hideSettingsAndLayer(anim::type::instant); if (_intro) _intro->hide(); if (animated) { _passcode->showAnimated(bg); } else { setInnerFocus(); } } void MainWindow::setupIntro() { if (_intro && !_intro->isHidden() && !_main) { return; } Ui::hideSettingsAndLayer(anim::type::instant); auto animated = (_main || _passcode); auto bg = animated ? grabInner() : QPixmap(); clearWidgets(); _intro.create(bodyWidget()); updateControlsGeometry(); if (animated) { _intro->showAnimated(bg); } else { setInnerFocus(); } fixOrder(); updateConnectingStatus(); _delayedServiceMsgs.clear(); if (_serviceHistoryRequest) { MTP::cancel(_serviceHistoryRequest); _serviceHistoryRequest = 0; } } void MainWindow::serviceNotification(const TextWithEntities &message, const MTPMessageMedia &media, int32 date, bool force) { if (date <= 0) date = unixtime(); auto h = (_main && App::userLoaded(ServiceUserId)) ? App::history(ServiceUserId).get() : nullptr; if (!h || (!force && h->isEmpty())) { _delayedServiceMsgs.push_back(DelayedServiceMsg(message, media, date)); return sendServiceHistoryRequest(); } _main->insertCheckedServiceNotification(message, media, date); } void MainWindow::showDelayedServiceMsgs() { for (auto &delayed : base::take(_delayedServiceMsgs)) { serviceNotification(delayed.message, delayed.media, delayed.date, true); } } void MainWindow::sendServiceHistoryRequest() { if (!_main || !_main->started() || _delayedServiceMsgs.isEmpty() || _serviceHistoryRequest) return; auto user = App::userLoaded(ServiceUserId); if (!user) { auto userFlags = MTPDuser::Flag::f_first_name | MTPDuser::Flag::f_phone | MTPDuser::Flag::f_status | MTPDuser::Flag::f_verified; user = App::feedUsers(MTP_vector<MTPUser>(1, MTP_user( MTP_flags(userFlags), MTP_int(ServiceUserId), MTPlong(), MTP_string("Telegram"), MTPstring(), MTPstring(), MTP_string("42777"), MTP_userProfilePhotoEmpty(), MTP_userStatusRecently(), MTPint(), MTPstring(), MTPstring(), MTPstring()))); } auto offsetId = 0; auto offsetDate = 0; auto addOffset = 0; auto limit = 1; auto maxId = 0; auto minId = 0; auto historyHash = 0; _serviceHistoryRequest = MTP::send( MTPmessages_GetHistory( user->input, MTP_int(offsetId), MTP_int(offsetDate), MTP_int(addOffset), MTP_int(limit), MTP_int(maxId), MTP_int(minId), MTP_int(historyHash)), _main->rpcDone(&MainWidget::serviceHistoryDone), _main->rpcFail(&MainWidget::serviceHistoryFail)); } void MainWindow::setupMain(const MTPUser *self) { auto animated = (_intro || _passcode); auto bg = animated ? grabInner() : QPixmap(); clearWidgets(); Assert(AuthSession::Exists()); _main.create(bodyWidget(), controller()); _main->show(); updateControlsGeometry(); if (animated) { _main->showAnimated(bg); } else { _main->activate(); } _main->start(self); fixOrder(); updateConnectingStatus(); } void MainWindow::showSettings() { if (isHidden()) showFromTray(); controller()->showSpecialLayer(Box<Settings::Widget>()); } void MainWindow::showSpecialLayer( object_ptr<Window::LayerWidget> layer, anim::type animated) { if (_passcode) return; if (layer) { ensureLayerCreated(); _layerBg->showSpecialLayer(std::move(layer), animated); } else if (_layerBg) { _layerBg->hideSpecialLayer(animated); } } bool MainWindow::showSectionInExistingLayer( not_null<Window::SectionMemento*> memento, const Window::SectionShow ¶ms) { if (_layerBg) { return _layerBg->showSectionInternal(memento, params); } return false; } void MainWindow::showMainMenu() { if (_passcode) return; if (isHidden()) showFromTray(); ensureLayerCreated(); _layerBg->showMainMenu(anim::type::normal); } void MainWindow::ensureLayerCreated() { if (!_layerBg) { _layerBg.create(bodyWidget(), controller()); if (controller()) { controller()->enableGifPauseReason(Window::GifPauseReason::Layer); } } } void MainWindow::destroyLayerDelayed() { if (_layerBg) { _layerBg.destroyDelayed(); if (controller()) { controller()->disableGifPauseReason(Window::GifPauseReason::Layer); } } } void MainWindow::ui_hideSettingsAndLayer(anim::type animated) { if (_layerBg) { _layerBg->hideAll(animated); if (animated == anim::type::instant) { destroyLayerDelayed(); } } } void MainWindow::mtpStateChanged(int32 dc, int32 state) { if (dc == MTP::maindc()) { updateConnectingStatus(); Global::RefConnectionTypeChanged().notify(); } } void MainWindow::updateConnectingStatus() { auto state = MTP::dcstate(); auto throughProxy = (Global::ConnectionType() != dbictAuto); if (state == MTP::ConnectingState || state == MTP::DisconnectedState || (state < 0 && state > -600)) { if (_main || getms() > 5000 || _connecting) { showConnecting(lang(throughProxy ? lng_connecting_to_proxy : lng_connecting), throughProxy ? lang(lng_connecting_settings) : QString()); } } else if (state < 0) { showConnecting(lng_reconnecting(lt_count, ((-state) / 1000) + 1), lang(throughProxy ? lng_connecting_settings : lng_reconnecting_try_now)); QTimer::singleShot((-state) % 1000, this, SLOT(updateConnectingStatus())); } else { hideConnecting(); } } MainWidget *MainWindow::mainWidget() { return _main; } PasscodeWidget *MainWindow::passcodeWidget() { return _passcode; } void MainWindow::ui_showBox( object_ptr<BoxContent> box, LayerOptions options, anim::type animated) { if (box) { ensureLayerCreated(); if (options & LayerOption::KeepOther) { if (options & LayerOption::ShowAfterOther) { _layerBg->prependBox(std::move(box), animated); } else { _layerBg->appendBox(std::move(box), animated); } } else { _layerBg->showBox(std::move(box), animated); } } else { if (_layerBg) { _layerBg->hideTopLayer(animated); if ((animated == anim::type::instant) && _layerBg && !_layerBg->layerShown()) { destroyLayerDelayed(); } } Messenger::Instance().hideMediaView(); } } bool MainWindow::ui_isLayerShown() { return _layerBg != nullptr; } void MainWindow::ui_showMediaPreview(DocumentData *document) { if (!document || ((!document->isAnimation() || !document->loaded()) && !document->sticker())) { return; } if (!_mediaPreview) { _mediaPreview.create(bodyWidget(), controller()); updateControlsGeometry(); } if (_mediaPreview->isHidden()) { fixOrder(); } _mediaPreview->showPreview(document); } void MainWindow::ui_showMediaPreview(PhotoData *photo) { if (!photo) return; if (!_mediaPreview) { _mediaPreview.create(bodyWidget(), controller()); updateControlsGeometry(); } if (_mediaPreview->isHidden()) { fixOrder(); } _mediaPreview->showPreview(photo); } void MainWindow::ui_hideMediaPreview() { if (!_mediaPreview) return; _mediaPreview->hidePreview(); } void MainWindow::showConnecting(const QString &text, const QString &reconnect) { if (_connecting) { _connecting->set(text, reconnect); _connecting->show(); } else { _connecting.create(bodyWidget(), text, reconnect); _connecting->show(); updateControlsGeometry(); fixOrder(); } } void MainWindow::hideConnecting() { if (_connecting) { _connecting->hide(); } } void MainWindow::themeUpdated(const Window::Theme::BackgroundUpdate &data) { using Type = Window::Theme::BackgroundUpdate::Type; // We delay animating theme warning because we want all other // subscribers to receive paltte changed notification before any // animations (that include pixmap caches with old palette values). if (data.type == Type::TestingTheme) { if (!_testingThemeWarning) { _testingThemeWarning.create(bodyWidget()); _testingThemeWarning->hide(); _testingThemeWarning->setGeometry(rect()); _testingThemeWarning->setHiddenCallback([this] { _testingThemeWarning.destroyDelayed(); }); } crl::on_main(this, [=] { if (_testingThemeWarning) { _testingThemeWarning->showAnimated(); } }); } else if (data.type == Type::RevertingTheme || data.type == Type::ApplyingTheme) { if (_testingThemeWarning) { if (_testingThemeWarning->isHidden()) { _testingThemeWarning.destroy(); } else { crl::on_main(this, [=] { if (_testingThemeWarning) { _testingThemeWarning->hideAnimated(); _testingThemeWarning = nullptr; } setInnerFocus(); }); } } } } bool MainWindow::doWeReadServerHistory() { updateIsActive(0); return isActive() && _main && !Ui::isLayerShown() && _main->doWeReadServerHistory(); } bool MainWindow::doWeReadMentions() { updateIsActive(0); return isActive() && _main && !Ui::isLayerShown() && _main->doWeReadMentions(); } void MainWindow::checkHistoryActivation() { if (_main && doWeReadServerHistory()) { _main->markActiveHistoryAsRead(); } } bool MainWindow::contentOverlapped(const QRect &globalRect) { if (_main && _main->contentOverlapped(globalRect)) return true; if (_layerBg && _layerBg->contentOverlapped(globalRect)) return true; return false; } void MainWindow::setInnerFocus() { if (_testingThemeWarning) { _testingThemeWarning->setFocus(); } else if (_layerBg && _layerBg->canSetFocus()) { _layerBg->setInnerFocus(); } else if (_passcode) { _passcode->setInnerFocus(); } else if (_main) { _main->setInnerFocus(); } else if (_intro) { _intro->setInnerFocus(); } } bool MainWindow::eventFilter(QObject *object, QEvent *e) { switch (e->type()) { case QEvent::KeyPress: { if (cDebug() && e->type() == QEvent::KeyPress && object == windowHandle()) { auto key = static_cast<QKeyEvent*>(e)->key(); FeedLangTestingKey(key); } } break; case QEvent::MouseMove: { if (_main && _main->isIdle()) { psUserActionDone(); _main->checkIdleFinish(); } } break; case QEvent::MouseButtonRelease: { Ui::hideMediaPreview(); } break; case QEvent::ApplicationActivate: { if (object == QCoreApplication::instance()) { InvokeQueued(this, [this] { handleActiveChanged(); }); } } break; case QEvent::WindowStateChange: { if (object == this) { auto state = (windowState() & Qt::WindowMinimized) ? Qt::WindowMinimized : ((windowState() & Qt::WindowMaximized) ? Qt::WindowMaximized : ((windowState() & Qt::WindowFullScreen) ? Qt::WindowFullScreen : Qt::WindowNoState)); handleStateChanged(state); } } break; case QEvent::Move: case QEvent::Resize: { if (object == this) { positionUpdated(); } } break; } return Platform::MainWindow::eventFilter(object, e); } void MainWindow::updateTrayMenu(bool force) { if (!trayIconMenu || (cPlatform() == dbipWindows && !force)) return; auto iconMenu = trayIconMenu; auto actions = iconMenu->actions(); auto isLinux = (cPlatform() == dbipLinux32 || cPlatform() == dbipLinux64); if (isLinux) { auto minimizeAction = actions.at(1); minimizeAction->setDisabled(!isVisible()); } else { updateIsActive(0); auto active = isActive(); auto toggleAction = actions.at(0); disconnect(toggleAction, SIGNAL(triggered(bool)), this, SLOT(minimizeToTray())); disconnect(toggleAction, SIGNAL(triggered(bool)), this, SLOT(showFromTray())); connect(toggleAction, SIGNAL(triggered(bool)), this, active ? SLOT(minimizeToTray()) : SLOT(showFromTray())); toggleAction->setText(lang(active ? lng_minimize_to_tray : lng_open_from_tray)); // On macOS just remove trayIcon menu if the window is not active. // So we will activate the window on click instead of showing the menu. if (!active && (cPlatform() == dbipMac || cPlatform() == dbipMacOld)) { iconMenu = nullptr; } } auto notificationAction = actions.at(isLinux ? 2 : 1); auto notificationActionText = lang(Global::DesktopNotify() ? lng_disable_notifications_from_tray : lng_enable_notifications_from_tray); notificationAction->setText(notificationActionText); #ifndef Q_OS_WIN if (trayIcon && trayIcon->contextMenu() != iconMenu) { trayIcon->setContextMenu(iconMenu); } #endif // !Q_OS_WIN psTrayMenuUpdated(); } void MainWindow::onShowAddContact() { if (isHidden()) showFromTray(); if (App::self()) { Ui::show(Box<AddContactBox>(), LayerOption::KeepOther); } } void MainWindow::onShowNewGroup() { if (isHidden()) showFromTray(); if (App::self()) { Ui::show( Box<GroupInfoBox>(CreatingGroupGroup, false), LayerOption::KeepOther); } } void MainWindow::onShowNewChannel() { if (isHidden()) showFromTray(); if (_main) { Ui::show( Box<GroupInfoBox>(CreatingGroupChannel, false), LayerOption::KeepOther); } } void MainWindow::onLogout() { if (isHidden()) showFromTray(); Ui::show(Box<ConfirmBox>(lang(lng_sure_logout), lang(lng_settings_logout), st::attentionBoxButton, [] { App::logOut(); })); } void MainWindow::quitFromTray() { App::quit(); } void MainWindow::activate() { bool wasHidden = !isVisible(); setWindowState(windowState() & ~Qt::WindowMinimized); setVisible(true); psActivateProcess(); activateWindow(); updateIsActive(Global::OnlineFocusTimeout()); if (wasHidden) { if (_main) { _main->windowShown(); } } } void MainWindow::noIntro(Intro::Widget *was) { if (was == _intro) { _intro = nullptr; } } void MainWindow::noLayerStack(Window::LayerStackWidget *was) { if (was == _layerBg) { _layerBg = nullptr; if (controller()) { controller()->disableGifPauseReason( Window::GifPauseReason::Layer); } } } void MainWindow::layerFinishedHide(Window::LayerStackWidget *was) { if (was == _layerBg) { auto resetFocus = Ui::InFocusChain(was); if (resetFocus) setFocus(); destroyLayerDelayed(); if (resetFocus) setInnerFocus(); InvokeQueued(this, [this] { checkHistoryActivation(); }); } } bool MainWindow::takeThirdSectionFromLayer() { return _layerBg ? _layerBg->takeToThirdSection() : false; } void MainWindow::fixOrder() { if (_layerBg) _layerBg->raise(); if (_mediaPreview) _mediaPreview->raise(); if (_connecting) _connecting->raise(); if (_testingThemeWarning) _testingThemeWarning->raise(); } void MainWindow::showFromTray(QSystemTrayIcon::ActivationReason reason) { if (reason != QSystemTrayIcon::Context) { App::CallDelayed(1, this, [this] { updateTrayMenu(); updateGlobalMenu(); }); activate(); Notify::unreadCounterUpdated(); } } void MainWindow::toggleTray(QSystemTrayIcon::ActivationReason reason) { updateIsActive(0); if ((cPlatform() == dbipMac || cPlatform() == dbipMacOld) && isActive()) return; if (reason == QSystemTrayIcon::Context) { updateTrayMenu(true); QTimer::singleShot(1, this, SLOT(psShowTrayMenu())); } else if (!skipTrayClick()) { if (isActive()) { minimizeToTray(); } else { showFromTray(reason); } _lastTrayClickTime = getms(); } } bool MainWindow::skipTrayClick() const { return (_lastTrayClickTime > 0) && (getms() - _lastTrayClickTime < QApplication::doubleClickInterval()); } void MainWindow::toggleDisplayNotifyFromTray() { if (App::passcoded()) { if (!isActive()) showFromTray(); Ui::show(Box<InformBox>(lang(lng_passcode_need_unblock))); return; } if (!AuthSession::Exists()) { return; } bool soundNotifyChanged = false; Global::SetDesktopNotify(!Global::DesktopNotify()); if (Global::DesktopNotify()) { if (Global::RestoreSoundNotifyFromTray() && !Global::SoundNotify()) { Global::SetSoundNotify(true); Global::SetRestoreSoundNotifyFromTray(false); soundNotifyChanged = true; } } else { if (Global::SoundNotify()) { Global::SetSoundNotify(false); Global::SetRestoreSoundNotifyFromTray(true); soundNotifyChanged = true; } else { Global::SetRestoreSoundNotifyFromTray(false); } } Local::writeUserSettings(); Auth().notifications().settingsChanged().notify(Window::Notifications::ChangeType::DesktopEnabled); if (soundNotifyChanged) { Auth().notifications().settingsChanged().notify(Window::Notifications::ChangeType::SoundEnabled); } } void MainWindow::closeEvent(QCloseEvent *e) { if (Sandbox::isSavingSession()) { e->accept(); App::quit(); } else { e->ignore(); if (!AuthSession::Exists() || !hideNoQuit()) { App::quit(); } } } void MainWindow::updateControlsGeometry() { Platform::MainWindow::updateControlsGeometry(); auto body = bodyWidget()->rect(); if (_passcode) _passcode->setGeometry(body); if (_main) _main->setGeometry(body); if (_intro) _intro->setGeometry(body); if (_layerBg) _layerBg->setGeometry(body); if (_mediaPreview) _mediaPreview->setGeometry(body); if (_connecting) _connecting->moveToLeft(0, body.height() - _connecting->height()); if (_testingThemeWarning) _testingThemeWarning->setGeometry(body); if (_main) _main->checkMainSectionToLayer(); } MainWindow::TempDirState MainWindow::tempDirState() { if (_clearManager && _clearManager->hasTask(Local::ClearManagerDownloads)) { return TempDirRemoving; } return QDir(cTempDir()).exists() ? TempDirExists : TempDirEmpty; } MainWindow::TempDirState MainWindow::localStorageState() { if (_clearManager && _clearManager->hasTask(Local::ClearManagerStorage)) { return TempDirRemoving; } return (Local::hasImages() || Local::hasStickers() || Local::hasWebFiles() || Local::hasAudios()) ? TempDirExists : TempDirEmpty; } void MainWindow::tempDirDelete(int task) { if (_clearManager) { if (_clearManager->addTask(task)) { return; } else { _clearManager->stop(); _clearManager = nullptr; } } _clearManager = new Local::ClearManager(); _clearManager->addTask(task); connect(_clearManager, SIGNAL(succeed(int,void*)), this, SLOT(onClearFinished(int,void*))); connect(_clearManager, SIGNAL(failed(int,void*)), this, SLOT(onClearFailed(int,void*))); _clearManager->start(); } void MainWindow::onClearFinished(int task, void *manager) { if (manager && manager == _clearManager) { _clearManager->stop(); _clearManager = nullptr; } emit tempDirCleared(task); } void MainWindow::onClearFailed(int task, void *manager) { if (manager && manager == _clearManager) { _clearManager->stop(); _clearManager = nullptr; } emit tempDirClearFailed(task); } void MainWindow::placeSmallCounter(QImage &img, int size, int count, style::color bg, const QPoint &shift, style::color color) { QPainter p(&img); QString cnt = (count < 100) ? QString("%1").arg(count) : QString("..%1").arg(count % 10, 1, 10, QChar('0')); int32 cntSize = cnt.size(); p.setBrush(bg->b); p.setPen(Qt::NoPen); p.setRenderHint(QPainter::Antialiasing); int32 fontSize; if (size == 16) { fontSize = 8; } else if (size == 32) { fontSize = (cntSize < 2) ? 12 : 12; } else { fontSize = (cntSize < 2) ? 22 : 22; } style::font f = { fontSize, 0, 0 }; int32 w = f->width(cnt), d, r; if (size == 16) { d = (cntSize < 2) ? 2 : 1; r = (cntSize < 2) ? 4 : 3; } else if (size == 32) { d = (cntSize < 2) ? 5 : 2; r = (cntSize < 2) ? 8 : 7; } else { d = (cntSize < 2) ? 9 : 4; r = (cntSize < 2) ? 16 : 14; } p.drawRoundedRect(QRect(shift.x() + size - w - d * 2, shift.y() + size - f->height, w + d * 2, f->height), r, r); p.setFont(f->f); p.setPen(color->p); p.drawText(shift.x() + size - w - d, shift.y() + size - f->height + f->ascent, cnt); } QImage MainWindow::iconWithCounter(int size, int count, style::color bg, style::color fg, bool smallIcon) { bool layer = false; if (size < 0) { size = -size; layer = true; } if (layer) { if (size != 16 && size != 20 && size != 24) size = 32; QString cnt = (count < 1000) ? QString("%1").arg(count) : QString("..%1").arg(count % 100, 2, 10, QChar('0')); QImage result(size, size, QImage::Format_ARGB32); int32 cntSize = cnt.size(); result.fill(Qt::transparent); { QPainter p(&result); p.setBrush(bg); p.setPen(Qt::NoPen); p.setRenderHint(QPainter::Antialiasing); int32 fontSize; if (size == 16) { fontSize = (cntSize < 2) ? 11 : ((cntSize < 3) ? 11 : 8); } else if (size == 20) { fontSize = (cntSize < 2) ? 14 : ((cntSize < 3) ? 13 : 10); } else if (size == 24) { fontSize = (cntSize < 2) ? 17 : ((cntSize < 3) ? 16 : 12); } else { fontSize = (cntSize < 2) ? 22 : ((cntSize < 3) ? 20 : 16); } style::font f = { fontSize, 0, 0 }; int32 w = f->width(cnt), d, r; if (size == 16) { d = (cntSize < 2) ? 5 : ((cntSize < 3) ? 2 : 1); r = (cntSize < 2) ? 8 : ((cntSize < 3) ? 7 : 3); } else if (size == 20) { d = (cntSize < 2) ? 6 : ((cntSize < 3) ? 2 : 1); r = (cntSize < 2) ? 10 : ((cntSize < 3) ? 9 : 5); } else if (size == 24) { d = (cntSize < 2) ? 7 : ((cntSize < 3) ? 3 : 1); r = (cntSize < 2) ? 12 : ((cntSize < 3) ? 11 : 6); } else { d = (cntSize < 2) ? 9 : ((cntSize < 3) ? 4 : 2); r = (cntSize < 2) ? 16 : ((cntSize < 3) ? 14 : 8); } p.drawRoundedRect(QRect(size - w - d * 2, size - f->height, w + d * 2, f->height), r, r); p.setFont(f); p.setPen(fg); p.drawText(size - w - d, size - f->height + f->ascent, cnt); } return result; } else { if (size != 16 && size != 32) size = 64; } QImage img(smallIcon ? ((size == 16) ? iconbig16 : (size == 32 ? iconbig32 : iconbig64)) : ((size == 16) ? icon16 : (size == 32 ? icon32 : icon64))); if (!count) return img; if (smallIcon) { placeSmallCounter(img, size, count, bg, QPoint(), fg); } else { QPainter p(&img); p.drawPixmap(size / 2, size / 2, App::pixmapFromImageInPlace(iconWithCounter(-size / 2, count, bg, fg, false))); } return img; } void MainWindow::sendPaths() { if (App::passcoded()) return; Messenger::Instance().hideMediaView(); Ui::hideSettingsAndLayer(anim::type::instant); if (_main) { _main->activate(); } } void MainWindow::updateIsActiveHook() { if (_main) _main->updateOnline(); } MainWindow::~MainWindow() { if (_clearManager) { _clearManager->stop(); _clearManager = nullptr; } delete trayIcon; delete trayIconMenu; } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/mainwindow.h���������������������������������������������������0000664�0000000�0000000�00000012717�13262451251�0021760�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "platform/platform_specific.h" #include "platform/platform_main_window.h" #include "core/single_timer.h" class PasscodeWidget; class MainWidget; class BoxContent; namespace Intro { class Widget; } // namespace Intro namespace Local { class ClearManager; } // namespace Local namespace Window { class LayerWidget; class LayerStackWidget; class SectionMemento; struct SectionShow; namespace Theme { struct BackgroundUpdate; class WarningWidget; } // namespace Theme } // namespace Window namespace Ui { class LinkButton; } // namespace Ui class ConnectingWidget : public TWidget { Q_OBJECT public: ConnectingWidget(QWidget *parent, const QString &text, const QString &reconnect); void set(const QString &text, const QString &reconnect); protected: void paintEvent(QPaintEvent *e) override; public slots: void onReconnect(); private: QString _text; int _textWidth = 0; object_ptr<Ui::LinkButton> _reconnect; }; class MediaPreviewWidget; class MainWindow : public Platform::MainWindow { Q_OBJECT public: MainWindow(); ~MainWindow(); void firstShow(); void setupPasscode(); void clearPasscode(); void setupIntro(); void setupMain(const MTPUser *user = nullptr); void serviceNotification(const TextWithEntities &message, const MTPMessageMedia &media = MTP_messageMediaEmpty(), int32 date = 0, bool force = false); void sendServiceHistoryRequest(); void showDelayedServiceMsgs(); void mtpStateChanged(int32 dc, int32 state); MainWidget *chatsWidget() { return mainWidget(); } MainWidget *mainWidget(); PasscodeWidget *passcodeWidget(); bool doWeReadServerHistory(); bool doWeReadMentions(); void activate(); void noIntro(Intro::Widget *was); void noLayerStack(Window::LayerStackWidget *was); void layerFinishedHide(Window::LayerStackWidget *was); bool takeThirdSectionFromLayer(); void checkHistoryActivation(); void fixOrder(); enum TempDirState { TempDirRemoving, TempDirExists, TempDirEmpty, }; TempDirState tempDirState(); TempDirState localStorageState(); void tempDirDelete(int task); void sendPaths(); QImage iconWithCounter(int size, int count, style::color bg, style::color fg, bool smallIcon) override; bool contentOverlapped(const QRect &globalRect); bool contentOverlapped(QWidget *w, QPaintEvent *e) { return contentOverlapped(QRect(w->mapToGlobal(e->rect().topLeft()), e->rect().size())); } bool contentOverlapped(QWidget *w, const QRegion &r) { return contentOverlapped(QRect(w->mapToGlobal(r.boundingRect().topLeft()), r.boundingRect().size())); } void showMainMenu(); void updateTrayMenu(bool force = false) override; void showSpecialLayer( object_ptr<Window::LayerWidget> layer, anim::type animated); bool showSectionInExistingLayer( not_null<Window::SectionMemento*> memento, const Window::SectionShow ¶ms); void ui_showBox( object_ptr<BoxContent> box, LayerOptions options, anim::type animated); void ui_hideSettingsAndLayer(anim::type animated); bool ui_isLayerShown(); void ui_showMediaPreview(DocumentData *document); void ui_showMediaPreview(PhotoData *photo); void ui_hideMediaPreview(); protected: bool eventFilter(QObject *o, QEvent *e) override; void closeEvent(QCloseEvent *e) override; void initHook() override; void updateIsActiveHook() override; void clearWidgetsHook() override; void updateControlsGeometry() override; public slots: void showSettings(); void setInnerFocus(); void updateConnectingStatus(); void quitFromTray(); void showFromTray(QSystemTrayIcon::ActivationReason reason = QSystemTrayIcon::Unknown); void toggleTray(QSystemTrayIcon::ActivationReason reason = QSystemTrayIcon::Unknown); void toggleDisplayNotifyFromTray(); void onClearFinished(int task, void *manager); void onClearFailed(int task, void *manager); void onShowAddContact(); void onShowNewGroup(); void onShowNewChannel(); void onLogout(); signals: void tempDirCleared(int task); void tempDirClearFailed(int task); void checkNewAuthorization(); private: void showConnecting(const QString &text, const QString &reconnect = QString()); void hideConnecting(); [[nodiscard]] bool skipTrayClick() const; void ensureLayerCreated(); void destroyLayerDelayed(); void themeUpdated(const Window::Theme::BackgroundUpdate &data); QPixmap grabInner(); void placeSmallCounter(QImage &img, int size, int count, style::color bg, const QPoint &shift, style::color color) override; QImage icon16, icon32, icon64, iconbig16, iconbig32, iconbig64; struct DelayedServiceMsg { DelayedServiceMsg(const TextWithEntities &message, const MTPMessageMedia &media, int32 date) : message(message), media(media), date(date) { } TextWithEntities message; MTPMessageMedia media; int32 date; }; QList<DelayedServiceMsg> _delayedServiceMsgs; mtpRequestId _serviceHistoryRequest = 0; TimeMs _lastTrayClickTime = 0; object_ptr<PasscodeWidget> _passcode = { nullptr }; object_ptr<Intro::Widget> _intro = { nullptr }; object_ptr<MainWidget> _main = { nullptr }; object_ptr<Window::LayerStackWidget> _layerBg = { nullptr }; object_ptr<MediaPreviewWidget> _mediaPreview = { nullptr }; object_ptr<ConnectingWidget> _connecting = { nullptr }; object_ptr<Window::Theme::WarningWidget> _testingThemeWarning = { nullptr }; Local::ClearManager *_clearManager = nullptr; }; �������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/media/���������������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0020502�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/media/media_audio.cpp������������������������������������������0000664�0000000�0000000�00000125743�13262451251�0023462�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "media/media_audio.h" #include "data/data_document.h" #include "media/media_audio_ffmpeg_loader.h" #include "media/media_child_ffmpeg_loader.h" #include "media/media_audio_loaders.h" #include "media/media_audio_track.h" #include "platform/platform_audio.h" #include "messenger.h" #include <AL/al.h> #include <AL/alc.h> #include <AL/alext.h> #include <numeric> Q_DECLARE_METATYPE(AudioMsgId); Q_DECLARE_METATYPE(VoiceWaveform); namespace { QMutex AudioMutex; ALCdevice *AudioDevice = nullptr; ALCcontext *AudioContext = nullptr; constexpr auto kSuppressRatioAll = 0.2; constexpr auto kSuppressRatioSong = 0.05; auto VolumeMultiplierAll = 1.; auto VolumeMultiplierSong = 1.; } // namespace namespace Media { namespace Audio { namespace { Player::Mixer *MixerInstance = nullptr; // Thread: Any. bool ContextErrorHappened() { ALenum errCode; if ((errCode = alcGetError(AudioDevice)) != ALC_NO_ERROR) { LOG(("Audio Context Error: %1, %2").arg(errCode).arg((const char *)alcGetString(AudioDevice, errCode))); return true; } return false; } // Thread: Any. bool PlaybackErrorHappened() { ALenum errCode; if ((errCode = alGetError()) != AL_NO_ERROR) { LOG(("Audio Playback Error: %1, %2").arg(errCode).arg((const char *)alGetString(errCode))); return true; } return false; } void EnumeratePlaybackDevices() { auto deviceNames = QStringList(); auto devices = alcGetString(nullptr, ALC_DEVICE_SPECIFIER); Assert(devices != nullptr); while (*devices != 0) { auto deviceName8Bit = QByteArray(devices); auto deviceName = QString::fromLocal8Bit(deviceName8Bit); deviceNames.append(deviceName); devices += deviceName8Bit.size() + 1; } LOG(("Audio Playback Devices: %1").arg(deviceNames.join(';'))); if (auto device = alcGetString(nullptr, ALC_DEFAULT_DEVICE_SPECIFIER)) { LOG(("Audio Playback Default Device: %1").arg(QString::fromLocal8Bit(device))); } else { LOG(("Audio Playback Default Device: (null)")); } } void EnumerateCaptureDevices() { auto deviceNames = QStringList(); auto devices = alcGetString(nullptr, ALC_CAPTURE_DEVICE_SPECIFIER); Assert(devices != nullptr); while (*devices != 0) { auto deviceName8Bit = QByteArray(devices); auto deviceName = QString::fromLocal8Bit(deviceName8Bit); deviceNames.append(deviceName); devices += deviceName8Bit.size() + 1; } LOG(("Audio Capture Devices: %1").arg(deviceNames.join(';'))); if (auto device = alcGetString(nullptr, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER)) { LOG(("Audio Capture Default Device: %1").arg(QString::fromLocal8Bit(device))); } else { LOG(("Audio Capture Default Device: (null)")); } } // Thread: Any. Must be locked: AudioMutex. void DestroyPlaybackDevice() { if (AudioContext) { alcMakeContextCurrent(nullptr); alcDestroyContext(AudioContext); AudioContext = nullptr; } if (AudioDevice) { alcCloseDevice(AudioDevice); AudioDevice = nullptr; } } // Thread: Any. Must be locked: AudioMutex. bool CreatePlaybackDevice() { if (AudioDevice) return true; AudioDevice = alcOpenDevice(nullptr); if (!AudioDevice) { LOG(("Audio Error: Could not create default playback device, enumerating..")); EnumeratePlaybackDevices(); return false; } ALCint attributes[] = { ALC_STEREO_SOURCES, 128, ALC_FREQUENCY, Media::Player::kDefaultFrequency, 0 }; AudioContext = alcCreateContext(AudioDevice, attributes); alcMakeContextCurrent(AudioContext); if (ContextErrorHappened()) { DestroyPlaybackDevice(); return false; } ALfloat v[] = { 0.f, 0.f, -1.f, 0.f, 1.f, 0.f }; alListener3f(AL_POSITION, 0.f, 0.f, 0.f); alListener3f(AL_VELOCITY, 0.f, 0.f, 0.f); alListenerfv(AL_ORIENTATION, v); alDistanceModel(AL_NONE); return true; } // Thread: Main. Must be locked: AudioMutex. void ClosePlaybackDevice() { if (!AudioDevice) return; LOG(("Audio Info: Closing audio playback device.")); if (Player::mixer()) { Player::mixer()->detachTracks(); } Current().detachTracks(); DestroyPlaybackDevice(); } } // namespace // Thread: Main. void Start() { Assert(AudioDevice == nullptr); qRegisterMetaType<AudioMsgId>(); qRegisterMetaType<VoiceWaveform>(); auto loglevel = getenv("ALSOFT_LOGLEVEL"); LOG(("OpenAL Logging Level: %1").arg(loglevel ? loglevel : "(not set)")); EnumeratePlaybackDevices(); EnumerateCaptureDevices(); MixerInstance = new Player::Mixer(); Platform::Audio::Init(); } // Thread: Main. void Finish() { Platform::Audio::DeInit(); // MixerInstance variable should be modified under AudioMutex protection. // So it is modified in the ~Mixer() destructor after all tracks are cleared. delete MixerInstance; // No sync required already. ClosePlaybackDevice(); } // Thread: Main. Locks: AudioMutex. bool IsAttachedToDevice() { QMutexLocker lock(&AudioMutex); return (AudioDevice != nullptr); } // Thread: Any. Must be locked: AudioMutex. bool AttachToDevice() { if (AudioDevice) { return true; } LOG(("Audio Info: recreating audio device and reattaching the tracks")); CreatePlaybackDevice(); if (!AudioDevice) { return false; } if (auto m = Player::mixer()) { m->reattachTracks(); emit m->faderOnTimer(); } crl::on_main([] { if (Messenger::InstancePointer()) { Current().reattachTracks(); } }); return true; } void ScheduleDetachFromDeviceSafe() { crl::on_main([] { if (Messenger::InstancePointer()) { Current().scheduleDetachFromDevice(); } }); } void ScheduleDetachIfNotUsedSafe() { crl::on_main([] { if (Messenger::InstancePointer()) { Current().scheduleDetachIfNotUsed(); } }); } void StopDetachIfNotUsedSafe() { crl::on_main([] { if (Messenger::InstancePointer()) { Current().stopDetachIfNotUsed(); } }); } } // namespace Audio namespace Player { namespace { constexpr auto kVolumeRound = 10000; constexpr auto kPreloadSamples = 2LL * kDefaultFrequency; // preload next part if less than 2 seconds remains constexpr auto kFadeDuration = TimeMs(500); constexpr auto kCheckPlaybackPositionTimeout = TimeMs(100); // 100ms per check audio position constexpr auto kCheckPlaybackPositionDelta = 2400LL; // update position called each 2400 samples constexpr auto kCheckFadingTimeout = TimeMs(7); // 7ms base::Observable<AudioMsgId> UpdatedObservable; } // namespace base::Observable<AudioMsgId> &Updated() { return UpdatedObservable; } // Thread: Any. Must be locked: AudioMutex. float64 ComputeVolume(AudioMsgId::Type type) { switch (type) { case AudioMsgId::Type::Voice: return VolumeMultiplierAll; case AudioMsgId::Type::Song: return VolumeMultiplierSong * mixer()->getSongVolume(); case AudioMsgId::Type::Video: return mixer()->getVideoVolume(); } return 1.; } Mixer *mixer() { return Audio::MixerInstance; } void Mixer::Track::createStream() { alGenSources(1, &stream.source); alSourcef(stream.source, AL_PITCH, 1.f); alSource3f(stream.source, AL_POSITION, 0, 0, 0); alSource3f(stream.source, AL_VELOCITY, 0, 0, 0); alSourcei(stream.source, AL_LOOPING, 0); alGenBuffers(3, stream.buffers); } void Mixer::Track::destroyStream() { if (isStreamCreated()) { alDeleteBuffers(3, stream.buffers); alDeleteSources(1, &stream.source); } stream.source = 0; for (auto i = 0; i != 3; ++i) { stream.buffers[i] = 0; } } void Mixer::Track::reattach(AudioMsgId::Type type) { if (isStreamCreated() || !samplesCount[0]) { return; } createStream(); for (auto i = 0; i != kBuffersCount; ++i) { if (!samplesCount[i]) { break; } alBufferData(stream.buffers[i], format, bufferSamples[i].constData(), bufferSamples[i].size(), frequency); alSourceQueueBuffers(stream.source, 1, stream.buffers + i); } alSourcei(stream.source, AL_SAMPLE_OFFSET, qMax(state.position - bufferedPosition, 0LL)); if (!IsStopped(state.state) && state.state != State::PausedAtEnd) { alSourcef(stream.source, AL_GAIN, ComputeVolume(type)); alSourcePlay(stream.source); if (IsPaused(state.state)) { // We must always start the source if we want the AL_SAMPLE_OFFSET to be applied. // Otherwise it won't be read by alGetSource and we'll get a corrupt position. // So in case of a paused source we start it and then immediately pause it. alSourcePause(stream.source); } } } void Mixer::Track::detach() { resetStream(); destroyStream(); } void Mixer::Track::clear() { detach(); state = TrackState(); file = FileLocation(); data = QByteArray(); bufferedPosition = 0; bufferedLength = 0; loading = false; loaded = false; fadeStartPosition = 0; format = 0; frequency = kDefaultFrequency; for (int i = 0; i != kBuffersCount; ++i) { samplesCount[i] = 0; bufferSamples[i] = QByteArray(); } videoData = nullptr; lastUpdateWhen = 0; lastUpdateCorrectedMs = 0; } void Mixer::Track::started() { resetStream(); bufferedPosition = 0; bufferedLength = 0; loaded = false; fadeStartPosition = 0; format = 0; frequency = kDefaultFrequency; for (auto i = 0; i != kBuffersCount; ++i) { samplesCount[i] = 0; bufferSamples[i] = QByteArray(); } } bool Mixer::Track::isStreamCreated() const { return alIsSource(stream.source); } void Mixer::Track::ensureStreamCreated() { if (!isStreamCreated()) { createStream(); } } int Mixer::Track::getNotQueuedBufferIndex() { // See if there are no free buffers right now. while (samplesCount[kBuffersCount - 1] != 0) { // Try to unqueue some buffer. ALint processed = 0; alGetSourcei(stream.source, AL_BUFFERS_PROCESSED, &processed); if (processed < 1) { // No processed buffers, wait. return -1; } // Unqueue some processed buffer. ALuint buffer = 0; alSourceUnqueueBuffers(stream.source, 1, &buffer); // Find it in the list and clear it. bool found = false; for (auto i = 0; i != kBuffersCount; ++i) { if (stream.buffers[i] == buffer) { auto samplesInBuffer = samplesCount[i]; bufferedPosition += samplesInBuffer; bufferedLength -= samplesInBuffer; for (auto j = i + 1; j != kBuffersCount; ++j) { samplesCount[j - 1] = samplesCount[j]; stream.buffers[j - 1] = stream.buffers[j]; bufferSamples[j - 1] = bufferSamples[j]; } samplesCount[kBuffersCount - 1] = 0; stream.buffers[kBuffersCount - 1] = buffer; bufferSamples[kBuffersCount - 1] = QByteArray(); found = true; break; } } if (!found) { LOG(("Audio Error: Could not find the unqueued buffer! Buffer %1 in source %2 with processed count %3").arg(buffer).arg(stream.source).arg(processed)); return -1; } } for (auto i = 0; i != kBuffersCount; ++i) { if (!samplesCount[i]) { return i; } } return -1; } void Mixer::Track::resetStream() { if (isStreamCreated()) { alSourceStop(stream.source); alSourcei(stream.source, AL_BUFFER, AL_NONE); } } Mixer::Track::~Track() = default; Mixer::Mixer() : _volumeVideo(kVolumeRound) , _volumeSong(kVolumeRound) , _fader(new Fader(&_faderThread)) , _loader(new Loaders(&_loaderThread)) { connect(this, SIGNAL(faderOnTimer()), _fader, SLOT(onTimer()), Qt::QueuedConnection); connect(this, SIGNAL(suppressSong()), _fader, SLOT(onSuppressSong())); connect(this, SIGNAL(unsuppressSong()), _fader, SLOT(onUnsuppressSong())); connect(this, SIGNAL(suppressAll(qint64)), _fader, SLOT(onSuppressAll(qint64))); subscribe(Global::RefSongVolumeChanged(), [this] { QMetaObject::invokeMethod(_fader, "onSongVolumeChanged"); }); subscribe(Global::RefVideoVolumeChanged(), [this] { QMetaObject::invokeMethod(_fader, "onVideoVolumeChanged"); }); connect(this, SIGNAL(loaderOnStart(const AudioMsgId&, qint64)), _loader, SLOT(onStart(const AudioMsgId&, qint64))); connect(this, SIGNAL(loaderOnCancel(const AudioMsgId&)), _loader, SLOT(onCancel(const AudioMsgId&))); connect(_loader, SIGNAL(needToCheck()), _fader, SLOT(onTimer())); connect(_loader, SIGNAL(error(const AudioMsgId&)), this, SLOT(onError(const AudioMsgId&))); connect(_fader, SIGNAL(needToPreload(const AudioMsgId&)), _loader, SLOT(onLoad(const AudioMsgId&))); connect(_fader, SIGNAL(playPositionUpdated(const AudioMsgId&)), this, SIGNAL(updated(const AudioMsgId&))); connect(_fader, SIGNAL(audioStopped(const AudioMsgId&)), this, SLOT(onStopped(const AudioMsgId&))); connect(_fader, SIGNAL(error(const AudioMsgId&)), this, SLOT(onError(const AudioMsgId&))); connect(this, SIGNAL(stoppedOnError(const AudioMsgId&)), this, SIGNAL(updated(const AudioMsgId&)), Qt::QueuedConnection); connect(this, SIGNAL(updated(const AudioMsgId&)), this, SLOT(onUpdated(const AudioMsgId&))); _loaderThread.start(); _faderThread.start(); } // Thread: Main. Locks: AudioMutex. Mixer::~Mixer() { { QMutexLocker lock(&AudioMutex); for (auto i = 0; i != kTogetherLimit; ++i) { trackForType(AudioMsgId::Type::Voice, i)->clear(); trackForType(AudioMsgId::Type::Song, i)->clear(); } _videoTrack.clear(); Audio::ClosePlaybackDevice(); Audio::MixerInstance = nullptr; } _faderThread.quit(); _loaderThread.quit(); _faderThread.wait(); _loaderThread.wait(); } void Mixer::onUpdated(const AudioMsgId &audio) { if (audio.playId()) { videoSoundProgress(audio); } Media::Player::Updated().notify(audio); } void Mixer::onError(const AudioMsgId &audio) { emit stoppedOnError(audio); QMutexLocker lock(&AudioMutex); auto type = audio.type(); if (type == AudioMsgId::Type::Voice) { if (auto current = trackForType(type)) { if (current->state.id == audio) { emit unsuppressSong(); } } } } void Mixer::onStopped(const AudioMsgId &audio) { emit updated(audio); QMutexLocker lock(&AudioMutex); auto type = audio.type(); if (type == AudioMsgId::Type::Voice) { if (auto current = trackForType(type)) { if (current->state.id == audio) { emit unsuppressSong(); } } } } Mixer::Track *Mixer::trackForType(AudioMsgId::Type type, int index) { if (index < 0) { if (auto indexPtr = currentIndex(type)) { index = *indexPtr; } else { return nullptr; } } switch (type) { case AudioMsgId::Type::Voice: return &_audioTracks[index]; case AudioMsgId::Type::Song: return &_songTracks[index]; case AudioMsgId::Type::Video: return &_videoTrack; } return nullptr; } const Mixer::Track *Mixer::trackForType(AudioMsgId::Type type, int index) const { return const_cast<Mixer*>(this)->trackForType(type, index); } int *Mixer::currentIndex(AudioMsgId::Type type) { switch (type) { case AudioMsgId::Type::Voice: return &_audioCurrent; case AudioMsgId::Type::Song: return &_songCurrent; case AudioMsgId::Type::Video: { static int videoIndex = 0; return &videoIndex; } } return nullptr; } const int *Mixer::currentIndex(AudioMsgId::Type type) const { return const_cast<Mixer*>(this)->currentIndex(type); } void Mixer::resetFadeStartPosition(AudioMsgId::Type type, int positionInBuffered) { auto track = trackForType(type); if (!track) return; if (positionInBuffered < 0) { Audio::AttachToDevice(); if (track->isStreamCreated()) { ALint currentPosition = 0; alGetSourcei(track->stream.source, AL_SAMPLE_OFFSET, ¤tPosition); if (Audio::PlaybackErrorHappened()) { setStoppedState(track, State::StoppedAtError); onError(track->state.id); return; } if (currentPosition == 0 && !internal::CheckAudioDeviceConnected()) { track->fadeStartPosition = track->state.position; return; } positionInBuffered = currentPosition; } else { positionInBuffered = 0; } } auto fullPosition = track->bufferedPosition + positionInBuffered; track->state.position = fullPosition; track->fadeStartPosition = fullPosition; } bool Mixer::fadedStop(AudioMsgId::Type type, bool *fadedStart) { auto current = trackForType(type); if (!current) return false; switch (current->state.state) { case State::Starting: case State::Resuming: case State::Playing: { current->state.state = State::Stopping; resetFadeStartPosition(type); if (fadedStart) *fadedStart = true; } break; case State::Pausing: { current->state.state = State::Stopping; if (fadedStart) *fadedStart = true; } break; case State::Paused: case State::PausedAtEnd: { setStoppedState(current); } return true; } return false; } void Mixer::play(const AudioMsgId &audio, TimeMs positionMs) { setSongVolume(Global::SongVolume()); play(audio, nullptr, positionMs); } void Mixer::play( const AudioMsgId &audio, std::unique_ptr<VideoSoundData> videoData, TimeMs positionMs) { Expects(!videoData || audio.playId() != 0); auto type = audio.type(); AudioMsgId stopped; auto notLoadedYet = false; { QMutexLocker lock(&AudioMutex); Audio::AttachToDevice(); if (!AudioDevice) return; auto fadedStart = false; auto current = trackForType(type); if (!current) return; if (type == AudioMsgId::Type::Video) { auto pauseType = [this](AudioMsgId::Type type) { auto current = trackForType(type); switch (current->state.state) { case State::Starting: case State::Resuming: case State::Playing: { current->state.state = State::Pausing; resetFadeStartPosition(type); } break; case State::Stopping: { current->state.state = State::Pausing; } break; } }; pauseType(AudioMsgId::Type::Song); pauseType(AudioMsgId::Type::Voice); } if (current->state.id != audio) { if (fadedStop(type, &fadedStart)) { stopped = current->state.id; } if (current->state.id) { emit loaderOnCancel(current->state.id); emit faderOnTimer(); } if (type == AudioMsgId::Type::Video) { current->clear(); } else { auto foundCurrent = currentIndex(type); auto index = 0; for (; index != kTogetherLimit; ++index) { if (trackForType(type, index)->state.id == audio) { *foundCurrent = index; break; } } if (index == kTogetherLimit && ++*foundCurrent >= kTogetherLimit) { *foundCurrent -= kTogetherLimit; } current = trackForType(type); } } current->state.id = audio; current->lastUpdateWhen = 0; current->lastUpdateCorrectedMs = 0; if (videoData) { current->videoData = std::move(videoData); } else { current->file = audio.audio()->location(true); current->data = audio.audio()->data(); notLoadedYet = (current->file.isEmpty() && current->data.isEmpty()); } if (notLoadedYet) { auto newState = (type == AudioMsgId::Type::Song) ? State::Stopped : State::StoppedAtError; setStoppedState(current, newState); } else { current->state.position = (positionMs * current->state.frequency) / 1000LL; current->state.state = current->videoData ? State::Paused : fadedStart ? State::Starting : State::Playing; current->loading = true; emit loaderOnStart(current->state.id, positionMs); if (type == AudioMsgId::Type::Voice) { emit suppressSong(); } } } if (notLoadedYet) { if (type == AudioMsgId::Type::Song || type == AudioMsgId::Type::Video) { DocumentOpenClickHandler::doOpen(audio.audio(), App::histItemById(audio.contextId())); } else { onError(audio); } } if (stopped) { emit updated(stopped); } } void Mixer::feedFromVideo(VideoSoundPart &&part) { _loader->feedFromVideo(std::move(part)); } TimeMs Mixer::getVideoCorrectedTime(const AudioMsgId &audio, TimeMs frameMs, TimeMs systemMs) { auto result = frameMs; QMutexLocker lock(&AudioMutex); auto type = audio.type(); auto track = trackForType(type); if (track && track->state.id == audio && track->lastUpdateWhen > 0) { result = static_cast<TimeMs>(track->lastUpdateCorrectedMs); if (systemMs > track->lastUpdateWhen) { result += (systemMs - track->lastUpdateWhen); } } return result; } void Mixer::videoSoundProgress(const AudioMsgId &audio) { auto type = audio.type(); QMutexLocker lock(&AudioMutex); auto current = trackForType(type); if (current && current->state.length && current->state.frequency) { if (current->state.id == audio && current->state.state == State::Playing) { current->lastUpdateWhen = getms(); current->lastUpdateCorrectedMs = (current->state.position * 1000ULL) / current->state.frequency; } } } bool Mixer::checkCurrentALError(AudioMsgId::Type type) { if (!Audio::PlaybackErrorHappened()) return true; auto data = trackForType(type); if (!data) { setStoppedState(data, State::StoppedAtError); onError(data->state.id); } return false; } void Mixer::pause(const AudioMsgId &audio, bool fast) { AudioMsgId current; { QMutexLocker lock(&AudioMutex); auto type = audio.type(); auto track = trackForType(type); if (!track || track->state.id != audio) { return; } current = track->state.id; switch (track->state.state) { case State::Starting: case State::Resuming: case State::Playing: { track->state.state = fast ? State::Paused : State::Pausing; resetFadeStartPosition(type); if (type == AudioMsgId::Type::Voice) { emit unsuppressSong(); } } break; case State::Pausing: case State::Stopping: { track->state.state = fast ? State::Paused : State::Pausing; } break; } if (fast && track->isStreamCreated()) { ALint state = AL_INITIAL; alGetSourcei(track->stream.source, AL_SOURCE_STATE, &state); if (!checkCurrentALError(type)) return; if (state == AL_PLAYING) { alSourcePause(track->stream.source); if (!checkCurrentALError(type)) return; } } emit faderOnTimer(); track->lastUpdateWhen = 0; track->lastUpdateCorrectedMs = 0; } if (current) emit updated(current); } void Mixer::resume(const AudioMsgId &audio, bool fast) { AudioMsgId current; { QMutexLocker lock(&AudioMutex); auto type = audio.type(); auto track = trackForType(type); if (!track || track->state.id != audio) { return; } current = track->state.id; switch (track->state.state) { case State::Pausing: case State::Paused: case State::PausedAtEnd: { if (track->state.state == State::Paused) { // This calls Audio::AttachToDevice(). resetFadeStartPosition(type); } else { Audio::AttachToDevice(); if (track->state.state == State::PausedAtEnd) { if (track->isStreamCreated()) { alSourcei(track->stream.source, AL_SAMPLE_OFFSET, qMax(track->state.position - track->bufferedPosition, 0LL)); if (!checkCurrentALError(type)) return; } } } track->state.state = fast ? State::Playing : State::Resuming; if (track->isStreamCreated()) { // When starting the video audio is in paused state and // gets resumed before the stream is created with any data. ALint state = AL_INITIAL; alGetSourcei(track->stream.source, AL_SOURCE_STATE, &state); if (!checkCurrentALError(type)) return; if (state != AL_PLAYING) { if (state == AL_STOPPED && !internal::CheckAudioDeviceConnected()) { return; } alSourcef(track->stream.source, AL_GAIN, ComputeVolume(type)); if (!checkCurrentALError(type)) return; alSourcePlay(track->stream.source); if (!checkCurrentALError(type)) return; } if (type == AudioMsgId::Type::Voice) { emit suppressSong(); } } } break; } emit faderOnTimer(); } if (current) emit updated(current); } void Mixer::seek(AudioMsgId::Type type, TimeMs positionMs) { QMutexLocker lock(&AudioMutex); const auto current = trackForType(type); const auto audio = current->state.id; Audio::AttachToDevice(); const auto streamCreated = current->isStreamCreated(); const auto position = (positionMs * current->frequency) / 1000LL; const auto fastSeek = [&] { const auto loadedStart = current->bufferedPosition; const auto loadedLength = current->bufferedLength; const auto skipBack = (current->loaded ? 0 : kDefaultFrequency); const auto availableEnd = loadedStart + loadedLength - skipBack; if (position < loadedStart) { return false; } else if (position >= availableEnd) { return false; } else if (!streamCreated) { return false; } else if (IsStoppedOrStopping(current->state.state)) { return false; } return true; }(); if (fastSeek) { alSourcei(current->stream.source, AL_SAMPLE_OFFSET, position - current->bufferedPosition); if (!checkCurrentALError(type)) return; alSourcef(current->stream.source, AL_GAIN, ComputeVolume(type)); if (!checkCurrentALError(type)) return; resetFadeStartPosition(type, position - current->bufferedPosition); } else { setStoppedState(current); } switch (current->state.state) { case State::Pausing: case State::Paused: case State::PausedAtEnd: { if (current->state.state == State::PausedAtEnd) { current->state.state = State::Paused; } lock.unlock(); return resume(audio, true); } break; case State::Starting: case State::Resuming: case State::Playing: { current->state.state = State::Pausing; resetFadeStartPosition(type); if (type == AudioMsgId::Type::Voice) { emit unsuppressSong(); } } break; case State::Stopping: case State::Stopped: case State::StoppedAtEnd: case State::StoppedAtError: case State::StoppedAtStart: { lock.unlock(); } return play(audio, positionMs); } emit faderOnTimer(); } void Mixer::stop(const AudioMsgId &audio) { AudioMsgId current; { QMutexLocker lock(&AudioMutex); auto type = audio.type(); auto track = trackForType(type); if (!track || track->state.id != audio) { return; } current = track->state.id; fadedStop(type); if (type == AudioMsgId::Type::Voice) { emit unsuppressSong(); } else if (type == AudioMsgId::Type::Video) { track->clear(); } } if (current) emit updated(current); } void Mixer::stop(const AudioMsgId &audio, State state) { Expects(IsStopped(state)); AudioMsgId current; { QMutexLocker lock(&AudioMutex); auto type = audio.type(); auto track = trackForType(type); if (!track || track->state.id != audio || IsStopped(track->state.state)) { return; } current = track->state.id; setStoppedState(track, state); if (type == AudioMsgId::Type::Voice) { emit unsuppressSong(); } else if (type == AudioMsgId::Type::Video) { track->clear(); } } if (current) emit updated(current); } void Mixer::stopAndClear() { Track *current_audio = nullptr, *current_song = nullptr; { QMutexLocker lock(&AudioMutex); if ((current_audio = trackForType(AudioMsgId::Type::Voice))) { setStoppedState(current_audio); } if ((current_song = trackForType(AudioMsgId::Type::Song))) { setStoppedState(current_song); } } if (current_song) { emit updated(current_song->state.id); } if (current_audio) { emit updated(current_audio->state.id); } { QMutexLocker lock(&AudioMutex); auto clearAndCancel = [this](AudioMsgId::Type type, int index) { auto track = trackForType(type, index); if (track->state.id) { emit loaderOnCancel(track->state.id); } track->clear(); }; for (auto index = 0; index != kTogetherLimit; ++index) { clearAndCancel(AudioMsgId::Type::Voice, index); clearAndCancel(AudioMsgId::Type::Song, index); } _videoTrack.clear(); } } TrackState Mixer::currentState(AudioMsgId::Type type) { QMutexLocker lock(&AudioMutex); auto current = trackForType(type); if (!current) { return TrackState(); } return current->state; } void Mixer::setStoppedState(Track *current, State state) { current->state.state = state; current->state.position = 0; if (current->isStreamCreated()) { alSourceStop(current->stream.source); alSourcef(current->stream.source, AL_GAIN, 1); } } void Mixer::clearStoppedAtStart(const AudioMsgId &audio) { QMutexLocker lock(&AudioMutex); auto track = trackForType(audio.type()); if (track && track->state.id == audio && track->state.state == State::StoppedAtStart) { setStoppedState(track); } } // Thread: Main. Must be locked: AudioMutex. void Mixer::detachTracks() { for (auto i = 0; i != kTogetherLimit; ++i) { trackForType(AudioMsgId::Type::Voice, i)->detach(); trackForType(AudioMsgId::Type::Song, i)->detach(); } _videoTrack.detach(); } // Thread: Main. Must be locked: AudioMutex. void Mixer::reattachIfNeeded() { Audio::Current().stopDetachIfNotUsed(); auto reattachNeeded = [this] { auto isPlayingState = [](const Track &track) { auto state = track.state.state; return (state == State::Playing) || IsFading(state); }; for (auto i = 0; i != kTogetherLimit; ++i) { if (isPlayingState(*trackForType(AudioMsgId::Type::Voice, i)) || isPlayingState(*trackForType(AudioMsgId::Type::Song, i))) { return true; } } return isPlayingState(_videoTrack); }; if (reattachNeeded() || Audio::Current().hasActiveTracks()) { Audio::AttachToDevice(); } } // Thread: Any. Must be locked: AudioMutex. void Mixer::reattachTracks() { for (auto i = 0; i != kTogetherLimit; ++i) { trackForType(AudioMsgId::Type::Voice, i)->reattach(AudioMsgId::Type::Voice); trackForType(AudioMsgId::Type::Song, i)->reattach(AudioMsgId::Type::Song); } _videoTrack.reattach(AudioMsgId::Type::Video); } void Mixer::setSongVolume(float64 volume) { _volumeSong.storeRelease(qRound(volume * kVolumeRound)); } float64 Mixer::getSongVolume() const { return float64(_volumeSong.loadAcquire()) / kVolumeRound; } void Mixer::setVideoVolume(float64 volume) { _volumeVideo.storeRelease(qRound(volume * kVolumeRound)); } float64 Mixer::getVideoVolume() const { return float64(_volumeVideo.loadAcquire()) / kVolumeRound; } Fader::Fader(QThread *thread) : QObject() , _timer(this) , _suppressVolumeAll(1., 1.) , _suppressVolumeSong(1., 1.) { moveToThread(thread); _timer.moveToThread(thread); connect(thread, SIGNAL(started()), this, SLOT(onInit())); connect(thread, SIGNAL(finished()), this, SLOT(deleteLater())); _timer.setSingleShot(true); connect(&_timer, SIGNAL(timeout()), this, SLOT(onTimer())); } void Fader::onInit() { } void Fader::onTimer() { QMutexLocker lock(&AudioMutex); if (!mixer()) return; auto volumeChangedAll = false; auto volumeChangedSong = false; if (_suppressAll || _suppressSongAnim) { auto ms = getms(); if (_suppressAll) { if (ms >= _suppressAllEnd || ms < _suppressAllStart) { _suppressAll = _suppressAllAnim = false; _suppressVolumeAll = anim::value(1., 1.); } else if (ms > _suppressAllEnd - kFadeDuration) { if (_suppressVolumeAll.to() != 1.) _suppressVolumeAll.start(1.); _suppressVolumeAll.update(1. - ((_suppressAllEnd - ms) / float64(kFadeDuration)), anim::linear); } else if (ms >= _suppressAllStart + st::mediaPlayerSuppressDuration) { if (_suppressAllAnim) { _suppressVolumeAll.finish(); _suppressAllAnim = false; } } else if (ms > _suppressAllStart) { _suppressVolumeAll.update((ms - _suppressAllStart) / float64(st::mediaPlayerSuppressDuration), anim::linear); } auto wasVolumeMultiplierAll = VolumeMultiplierAll; VolumeMultiplierAll = _suppressVolumeAll.current(); volumeChangedAll = (VolumeMultiplierAll != wasVolumeMultiplierAll); } if (_suppressSongAnim) { if (ms >= _suppressSongStart + kFadeDuration) { _suppressVolumeSong.finish(); _suppressSongAnim = false; } else { _suppressVolumeSong.update((ms - _suppressSongStart) / float64(kFadeDuration), anim::linear); } } auto wasVolumeMultiplierSong = VolumeMultiplierSong; VolumeMultiplierSong = _suppressVolumeSong.current(); accumulate_min(VolumeMultiplierSong, VolumeMultiplierAll); volumeChangedSong = (VolumeMultiplierSong != wasVolumeMultiplierSong); } auto hasFading = (_suppressAll || _suppressSongAnim); auto hasPlaying = false; auto updatePlayback = [this, &hasPlaying, &hasFading](AudioMsgId::Type type, int index, float64 volumeMultiplier, bool suppressGainChanged) { auto track = mixer()->trackForType(type, index); if (IsStopped(track->state.state) || track->state.state == State::Paused || !track->isStreamCreated()) return; auto emitSignals = updateOnePlayback(track, hasPlaying, hasFading, volumeMultiplier, suppressGainChanged); if (emitSignals & EmitError) emit error(track->state.id); if (emitSignals & EmitStopped) emit audioStopped(track->state.id); if (emitSignals & EmitPositionUpdated) emit playPositionUpdated(track->state.id); if (emitSignals & EmitNeedToPreload) emit needToPreload(track->state.id); }; auto suppressGainForMusic = ComputeVolume(AudioMsgId::Type::Song); auto suppressGainForMusicChanged = volumeChangedSong || _volumeChangedSong; for (auto i = 0; i != kTogetherLimit; ++i) { updatePlayback(AudioMsgId::Type::Voice, i, VolumeMultiplierAll, volumeChangedAll); updatePlayback(AudioMsgId::Type::Song, i, suppressGainForMusic, suppressGainForMusicChanged); } auto suppressGainForVideo = ComputeVolume(AudioMsgId::Type::Video); auto suppressGainForVideoChanged = volumeChangedAll || _volumeChangedVideo; updatePlayback(AudioMsgId::Type::Video, 0, suppressGainForVideo, suppressGainForVideoChanged); _volumeChangedSong = _volumeChangedVideo = false; if (hasFading) { _timer.start(kCheckFadingTimeout); Audio::StopDetachIfNotUsedSafe(); } else if (hasPlaying) { _timer.start(kCheckPlaybackPositionTimeout); Audio::StopDetachIfNotUsedSafe(); } else { Audio::ScheduleDetachIfNotUsedSafe(); } } int32 Fader::updateOnePlayback(Mixer::Track *track, bool &hasPlaying, bool &hasFading, float64 volumeMultiplier, bool volumeChanged) { auto playing = false; auto fading = false; auto errorHappened = [this, track] { if (Audio::PlaybackErrorHappened()) { setStoppedState(track, State::StoppedAtError); return true; } return false; }; ALint positionInBuffered = 0; ALint state = AL_INITIAL; alGetSourcei(track->stream.source, AL_SAMPLE_OFFSET, &positionInBuffered); alGetSourcei(track->stream.source, AL_SOURCE_STATE, &state); if (errorHappened()) return EmitError; int32 emitSignals = 0; if (state == AL_STOPPED && positionInBuffered == 0 && !internal::CheckAudioDeviceConnected()) { return emitSignals; } switch (track->state.state) { case State::Stopping: case State::Pausing: case State::Starting: case State::Resuming: { fading = true; } break; case State::Playing: { playing = true; } break; } auto fullPosition = track->bufferedPosition + positionInBuffered; if (state != AL_PLAYING && !track->loading) { if (fading || playing) { fading = false; playing = false; if (track->state.state == State::Pausing) { setStoppedState(track, State::PausedAtEnd); } else if (track->state.state == State::Stopping) { setStoppedState(track, State::Stopped); } else { setStoppedState(track, State::StoppedAtEnd); } if (errorHappened()) return EmitError; emitSignals |= EmitStopped; } } else if (fading && state == AL_PLAYING) { auto fadingForSamplesCount = (fullPosition - track->fadeStartPosition); if (TimeMs(1000) * fadingForSamplesCount >= kFadeDuration * track->state.frequency) { fading = false; alSourcef(track->stream.source, AL_GAIN, 1. * volumeMultiplier); if (errorHappened()) return EmitError; switch (track->state.state) { case State::Stopping: { setStoppedState(track); state = AL_STOPPED; } break; case State::Pausing: { alSourcePause(track->stream.source); if (errorHappened()) return EmitError; track->state.state = State::Paused; } break; case State::Starting: case State::Resuming: { track->state.state = State::Playing; playing = true; } break; } } else { auto newGain = TimeMs(1000) * fadingForSamplesCount / float64(kFadeDuration * track->state.frequency); if (track->state.state == State::Pausing || track->state.state == State::Stopping) { newGain = 1. - newGain; } alSourcef(track->stream.source, AL_GAIN, newGain * volumeMultiplier); if (errorHappened()) return EmitError; } } else if (playing && state == AL_PLAYING) { if (volumeChanged) { alSourcef(track->stream.source, AL_GAIN, 1. * volumeMultiplier); if (errorHappened()) return EmitError; } } if (state == AL_PLAYING && fullPosition >= track->state.position + kCheckPlaybackPositionDelta) { track->state.position = fullPosition; emitSignals |= EmitPositionUpdated; } if (playing || track->state.state == State::Starting || track->state.state == State::Resuming) { if (!track->loaded && !track->loading) { auto needPreload = (track->state.position + kPreloadSamples > track->bufferedPosition + track->bufferedLength); if (needPreload) { track->loading = true; emitSignals |= EmitNeedToPreload; } } } if (playing) hasPlaying = true; if (fading) hasFading = true; return emitSignals; } void Fader::setStoppedState(Mixer::Track *track, State state) { mixer()->setStoppedState(track, state); } void Fader::onSuppressSong() { if (!_suppressSong) { _suppressSong = true; _suppressSongAnim = true; _suppressSongStart = getms(); _suppressVolumeSong.start(kSuppressRatioSong); onTimer(); } } void Fader::onUnsuppressSong() { if (_suppressSong) { _suppressSong = false; _suppressSongAnim = true; _suppressSongStart = getms(); _suppressVolumeSong.start(1.); onTimer(); } } void Fader::onSuppressAll(qint64 duration) { _suppressAll = true; auto now = getms(); if (_suppressAllEnd < now + kFadeDuration) { _suppressAllStart = now; } _suppressAllEnd = now + duration; _suppressVolumeAll.start(kSuppressRatioAll); onTimer(); } void Fader::onSongVolumeChanged() { _volumeChangedSong = true; onTimer(); } void Fader::onVideoVolumeChanged() { _volumeChangedVideo = true; onTimer(); } namespace internal { // Thread: Any. QMutex *audioPlayerMutex() { return &AudioMutex; } // Thread: Any. bool audioCheckError() { return !Audio::PlaybackErrorHappened(); } // Thread: Any. Must be locked: AudioMutex. bool audioDeviceIsConnected() { if (!AudioDevice) { return false; } auto isConnected = ALint(0); alcGetIntegerv(AudioDevice, ALC_CONNECTED, 1, &isConnected); if (Audio::ContextErrorHappened()) { return false; } return (isConnected != 0); } // Thread: Any. Must be locked: AudioMutex. bool CheckAudioDeviceConnected() { if (audioDeviceIsConnected()) { return true; } Audio::ScheduleDetachFromDeviceSafe(); return false; } // Thread: Main. Locks: AudioMutex. void DetachFromDevice() { QMutexLocker lock(&AudioMutex); Audio::ClosePlaybackDevice(); if (mixer()) { mixer()->reattachIfNeeded(); } } } // namespace internal } // namespace Player } // namespace Media class FFMpegAttributesReader : public AbstractFFMpegLoader { public: FFMpegAttributesReader(const FileLocation &file, const QByteArray &data) : AbstractFFMpegLoader(file, data, base::byte_vector()) { } bool open(TimeMs positionMs) override { if (!AbstractFFMpegLoader::open(positionMs)) { return false; } int res = 0; char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; int videoStreamId = av_find_best_stream(fmtContext, AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0); if (videoStreamId >= 0) { DEBUG_LOG(("Audio Read Error: Found video stream in file '%1', data size '%2', error %3, %4").arg(_file.name()).arg(_data.size()).arg(videoStreamId).arg(av_make_error_string(err, sizeof(err), streamId))); return false; } for (int32 i = 0, l = fmtContext->nb_streams; i < l; ++i) { const auto stream = fmtContext->streams[i]; if (stream->disposition & AV_DISPOSITION_ATTACHED_PIC) { const auto &packet = stream->attached_pic; if (packet.size) { const auto coverBytes = QByteArray( (const char*)packet.data, packet.size); auto format = QByteArray(); auto animated = false; _cover = App::readImage( coverBytes, &format, true, &animated); if (!_cover.isNull()) { _coverBytes = coverBytes; _coverFormat = format; break; } } } } extractMetaData(fmtContext->streams[streamId]->metadata); extractMetaData(fmtContext->metadata); return true; } void trySet(QString &to, AVDictionary *dict, const char *key) { if (!to.isEmpty()) return; if (AVDictionaryEntry* tag = av_dict_get(dict, key, 0, 0)) { to = QString::fromUtf8(tag->value); } } void extractMetaData(AVDictionary *dict) { trySet(_title, dict, "title"); trySet(_performer, dict, "artist"); trySet(_performer, dict, "performer"); trySet(_performer, dict, "album_artist"); //for (AVDictionaryEntry *tag = av_dict_get(dict, "", 0, AV_DICT_IGNORE_SUFFIX); tag; tag = av_dict_get(dict, "", tag, AV_DICT_IGNORE_SUFFIX)) { // const char *key = tag->key; // const char *value = tag->value; // QString tmp = QString::fromUtf8(value); //} } int format() override { return 0; } QString title() { return _title; } QString performer() { return _performer; } QImage cover() { return _cover; } QByteArray coverBytes() { return _coverBytes; } QByteArray coverFormat() { return _coverFormat; } ReadResult readMore(QByteArray &result, int64 &samplesAdded) override { DEBUG_LOG(("Audio Read Error: should not call this")); return ReadResult::Error; } ~FFMpegAttributesReader() { } private: QString _title, _performer; QImage _cover; QByteArray _coverBytes, _coverFormat; }; namespace Media { namespace Player { FileMediaInformation::Song PrepareForSending(const QString &fname, const QByteArray &data) { auto result = FileMediaInformation::Song(); FFMpegAttributesReader reader(FileLocation(fname), data); const auto positionMs = TimeMs(0); if (reader.open(positionMs) && reader.samplesCount() > 0) { result.duration = reader.samplesCount() / reader.samplesFrequency(); result.title = reader.title(); result.performer = reader.performer(); result.cover = reader.cover(); } return result; } } // namespace Player } // namespace Media class FFMpegWaveformCounter : public FFMpegLoader { public: FFMpegWaveformCounter(const FileLocation &file, const QByteArray &data) : FFMpegLoader(file, data, base::byte_vector()) { } bool open(TimeMs positionMs) override { if (!FFMpegLoader::open(positionMs)) { return false; } QByteArray buffer; buffer.reserve(AudioVoiceMsgBufferSize); int64 countbytes = sampleSize() * samplesCount(); int64 processed = 0; int64 sumbytes = 0; if (samplesCount() < Media::Player::kWaveformSamplesCount) { return false; } QVector<uint16> peaks; peaks.reserve(Media::Player::kWaveformSamplesCount); auto fmt = format(); auto peak = uint16(0); auto callback = [&](uint16 sample) { accumulate_max(peak, sample); sumbytes += Media::Player::kWaveformSamplesCount; if (sumbytes >= countbytes) { sumbytes -= countbytes; peaks.push_back(peak); peak = 0; } }; while (processed < countbytes) { buffer.resize(0); int64 samples = 0; auto res = readMore(buffer, samples); if (res == ReadResult::Error || res == ReadResult::EndOfFile) { break; } if (buffer.isEmpty()) { continue; } auto sampleBytes = gsl::as_bytes(gsl::make_span(buffer)); if (fmt == AL_FORMAT_MONO8 || fmt == AL_FORMAT_STEREO8) { Media::Audio::IterateSamples<uchar>(sampleBytes, callback); } else if (fmt == AL_FORMAT_MONO16 || fmt == AL_FORMAT_STEREO16) { Media::Audio::IterateSamples<int16>(sampleBytes, callback); } processed += sampleSize() * samples; } if (sumbytes > 0 && peaks.size() < Media::Player::kWaveformSamplesCount) { peaks.push_back(peak); } if (peaks.isEmpty()) { return false; } auto sum = std::accumulate(peaks.cbegin(), peaks.cend(), 0LL); peak = qMax(int32(sum * 1.8 / peaks.size()), 2500); result.resize(peaks.size()); for (int32 i = 0, l = peaks.size(); i != l; ++i) { result[i] = char(qMin(31U, uint32(qMin(peaks.at(i), peak)) * 31 / peak)); } return true; } const VoiceWaveform &waveform() const { return result; } ~FFMpegWaveformCounter() { } private: VoiceWaveform result; }; VoiceWaveform audioCountWaveform(const FileLocation &file, const QByteArray &data) { FFMpegWaveformCounter counter(file, data); const auto positionMs = TimeMs(0); if (counter.open(positionMs)) { return counter.waveform(); } return VoiceWaveform(); } �����������������������������tdesktop-1.2.17/Telegram/SourceFiles/media/media_audio.h��������������������������������������������0000664�0000000�0000000�00000017606�13262451251�0023125�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "storage/localimageloader.h" struct VideoSoundData; struct VideoSoundPart; namespace Media { namespace Audio { // Thread: Main. void Start(); void Finish(); // Thread: Main. Locks: AudioMutex. bool IsAttachedToDevice(); // Thread: Any. Must be locked: AudioMutex. bool AttachToDevice(); // Thread: Any. void ScheduleDetachFromDeviceSafe(); void ScheduleDetachIfNotUsedSafe(); void StopDetachIfNotUsedSafe(); template <typename Callback> void IterateSamples(); } // namespace Audio namespace Player { constexpr auto kDefaultFrequency = 48000; // 48 kHz constexpr auto kTogetherLimit = 4; constexpr auto kWaveformSamplesCount = 100; class Fader; class Loaders; base::Observable<AudioMsgId> &Updated(); float64 ComputeVolume(AudioMsgId::Type type); enum class State { Stopped = 0x01, StoppedAtEnd = 0x02, StoppedAtError = 0x03, StoppedAtStart = 0x04, Starting = 0x08, Playing = 0x10, Stopping = 0x18, Pausing = 0x20, Paused = 0x28, PausedAtEnd = 0x30, Resuming = 0x38, }; inline bool IsStopped(State state) { return (state == State::Stopped) || (state == State::StoppedAtEnd) || (state == State::StoppedAtError) || (state == State::StoppedAtStart); } inline bool IsStoppedOrStopping(State state) { return IsStopped(state) || (state == State::Stopping); } inline bool IsStoppedAtEnd(State state) { return (state == State::StoppedAtEnd); } inline bool IsPaused(State state) { return (state == State::Paused) || (state == State::PausedAtEnd); } inline bool IsFading(State state) { return (state == State::Starting) || (state == State::Stopping) || (state == State::Pausing) || (state == State::Resuming); } inline bool IsActive(State state) { return !IsStopped(state) && !IsPaused(state); } struct TrackState { AudioMsgId id; State state = State::Stopped; int64 position = 0; int64 length = 0; int frequency = kDefaultFrequency; }; class Mixer : public QObject, private base::Subscriber { Q_OBJECT public: Mixer(); void play(const AudioMsgId &audio, TimeMs positionMs = 0); void play(const AudioMsgId &audio, std::unique_ptr<VideoSoundData> videoData, TimeMs positionMs = 0); void pause(const AudioMsgId &audio, bool fast = false); void resume(const AudioMsgId &audio, bool fast = false); void seek(AudioMsgId::Type type, TimeMs positionMs); // type == AudioMsgId::Type::Song void stop(const AudioMsgId &audio); void stop(const AudioMsgId &audio, State state); // Video player audio stream interface. void feedFromVideo(VideoSoundPart &&part); int64 getVideoCorrectedTime(const AudioMsgId &id, TimeMs frameMs, TimeMs systemMs); void stopAndClear(); TrackState currentState(AudioMsgId::Type type); void clearStoppedAtStart(const AudioMsgId &audio); // Thread: Main. Must be locked: AudioMutex. void detachTracks(); // Thread: Main. Must be locked: AudioMutex. void reattachIfNeeded(); // Thread: Any. Must be locked: AudioMutex. void reattachTracks(); // Thread: Any. void setSongVolume(float64 volume); float64 getSongVolume() const; void setVideoVolume(float64 volume); float64 getVideoVolume() const; ~Mixer(); private slots: void onError(const AudioMsgId &audio); void onStopped(const AudioMsgId &audio); void onUpdated(const AudioMsgId &audio); signals: void updated(const AudioMsgId &audio); void stoppedOnError(const AudioMsgId &audio); void loaderOnStart(const AudioMsgId &audio, qint64 positionMs); void loaderOnCancel(const AudioMsgId &audio); void faderOnTimer(); void suppressSong(); void unsuppressSong(); void suppressAll(qint64 duration); private: bool fadedStop(AudioMsgId::Type type, bool *fadedStart = 0); void resetFadeStartPosition(AudioMsgId::Type type, int positionInBuffered = -1); bool checkCurrentALError(AudioMsgId::Type type); void videoSoundProgress(const AudioMsgId &audio); class Track { public: static constexpr int kBuffersCount = 3; // Thread: Any. Must be locked: AudioMutex. void reattach(AudioMsgId::Type type); void detach(); void clear(); void started(); bool isStreamCreated() const; void ensureStreamCreated(); int getNotQueuedBufferIndex(); ~Track(); TrackState state; FileLocation file; QByteArray data; int64 bufferedPosition = 0; int64 bufferedLength = 0; bool loading = false; bool loaded = false; int64 fadeStartPosition = 0; int32 format = 0; int32 frequency = kDefaultFrequency; int samplesCount[kBuffersCount] = { 0 }; QByteArray bufferSamples[kBuffersCount]; struct Stream { uint32 source = 0; uint32 buffers[kBuffersCount] = { 0 }; }; Stream stream; std::unique_ptr<VideoSoundData> videoData; TimeMs lastUpdateWhen = 0; TimeMs lastUpdateCorrectedMs = 0; private: void createStream(); void destroyStream(); void resetStream(); }; // Thread: Any. Must be locked: AudioMutex. void setStoppedState(Track *current, State state = State::Stopped); Track *trackForType(AudioMsgId::Type type, int index = -1); // -1 uses currentIndex(type) const Track *trackForType(AudioMsgId::Type type, int index = -1) const; int *currentIndex(AudioMsgId::Type type); const int *currentIndex(AudioMsgId::Type type) const; int _audioCurrent = 0; Track _audioTracks[kTogetherLimit]; int _songCurrent = 0; Track _songTracks[kTogetherLimit]; Track _videoTrack; QAtomicInt _volumeVideo; QAtomicInt _volumeSong; friend class Fader; friend class Loaders; QThread _faderThread, _loaderThread; Fader *_fader; Loaders *_loader; }; Mixer *mixer(); class Fader : public QObject { Q_OBJECT public: Fader(QThread *thread); signals: void error(const AudioMsgId &audio); void playPositionUpdated(const AudioMsgId &audio); void audioStopped(const AudioMsgId &audio); void needToPreload(const AudioMsgId &audio); public slots: void onInit(); void onTimer(); void onSuppressSong(); void onUnsuppressSong(); void onSuppressAll(qint64 duration); void onSongVolumeChanged(); void onVideoVolumeChanged(); private: enum { EmitError = 0x01, EmitStopped = 0x02, EmitPositionUpdated = 0x04, EmitNeedToPreload = 0x08, }; int32 updateOnePlayback(Mixer::Track *track, bool &hasPlaying, bool &hasFading, float64 volumeMultiplier, bool volumeChanged); void setStoppedState(Mixer::Track *track, State state = State::Stopped); QTimer _timer; bool _volumeChangedSong = false; bool _volumeChangedVideo = false; bool _suppressAll = false; bool _suppressAllAnim = false; bool _suppressSong = false; bool _suppressSongAnim = false; anim::value _suppressVolumeAll; anim::value _suppressVolumeSong; TimeMs _suppressAllStart = 0; TimeMs _suppressAllEnd = 0; TimeMs _suppressSongStart = 0; }; FileMediaInformation::Song PrepareForSending(const QString &fname, const QByteArray &data); namespace internal { // Thread: Any. Must be locked: AudioMutex. bool CheckAudioDeviceConnected(); // Thread: Main. Locks: AudioMutex. void DetachFromDevice(); // Thread: Any. QMutex *audioPlayerMutex(); // Thread: Any. bool audioCheckError(); } // namespace internal } // namespace Player } // namespace Media VoiceWaveform audioCountWaveform(const FileLocation &file, const QByteArray &data); namespace Media { namespace Audio { FORCE_INLINE uint16 ReadOneSample(uchar data) { return qAbs((static_cast<int16>(data) - 0x80) * 0x100); } FORCE_INLINE uint16 ReadOneSample(int16 data) { return qAbs(data); } template <typename SampleType, typename Callback> void IterateSamples(base::const_byte_span bytes, Callback &&callback) { auto samplesPointer = reinterpret_cast<const SampleType*>(bytes.data()); auto samplesCount = bytes.size() / sizeof(SampleType); auto samplesData = gsl::make_span(samplesPointer, samplesCount); for (auto sampleData : samplesData) { callback(ReadOneSample(sampleData)); } } } // namespace Audio } // namespace Media ��������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/media/media_audio_capture.cpp����������������������������������0000664�0000000�0000000�00000052100�13262451251�0025167�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "media/media_audio_capture.h" #include "media/media_audio_ffmpeg_loader.h" #include <AL/al.h> #include <AL/alc.h> #include <AL/alext.h> #include <numeric> namespace Media { namespace Capture { namespace { constexpr auto kCaptureFrequency = Player::kDefaultFrequency; constexpr auto kCaptureSkipDuration = TimeMs(400); constexpr auto kCaptureFadeInDuration = TimeMs(300); Instance *CaptureInstance = nullptr; bool ErrorHappened(ALCdevice *device) { ALenum errCode; if ((errCode = alcGetError(device)) != ALC_NO_ERROR) { LOG(("Audio Capture Error: %1, %2").arg(errCode).arg((const char *)alcGetString(device, errCode))); return true; } return false; } } // namespace void Start() { Assert(CaptureInstance == nullptr); CaptureInstance = new Instance(); instance()->check(); } void Finish() { delete base::take(CaptureInstance); } Instance::Instance() : _inner(new Inner(&_thread)) { CaptureInstance = this; connect(this, SIGNAL(start()), _inner, SLOT(onStart())); connect(this, SIGNAL(stop(bool)), _inner, SLOT(onStop(bool))); connect(_inner, SIGNAL(done(QByteArray, VoiceWaveform, qint32)), this, SIGNAL(done(QByteArray, VoiceWaveform, qint32))); connect(_inner, SIGNAL(updated(quint16, qint32)), this, SIGNAL(updated(quint16, qint32))); connect(_inner, SIGNAL(error()), this, SIGNAL(error())); connect(&_thread, SIGNAL(started()), _inner, SLOT(onInit())); connect(&_thread, SIGNAL(finished()), _inner, SLOT(deleteLater())); _thread.start(); } void Instance::check() { _available = false; if (auto defaultDevice = alcGetString(0, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER)) { if (auto device = alcCaptureOpenDevice(defaultDevice, kCaptureFrequency, AL_FORMAT_MONO16, kCaptureFrequency / 5)) { auto error = ErrorHappened(device); alcCaptureCloseDevice(device); _available = !error; } } } Instance::~Instance() { _inner = nullptr; _thread.quit(); _thread.wait(); } Instance *instance() { return CaptureInstance; } struct Instance::Inner::Private { ALCdevice *device = nullptr; AVOutputFormat *fmt = nullptr; uchar *ioBuffer = nullptr; AVIOContext *ioContext = nullptr; AVFormatContext *fmtContext = nullptr; AVStream *stream = nullptr; AVCodec *codec = nullptr; AVCodecContext *codecContext = nullptr; bool opened = false; int srcSamples = 0; int dstSamples = 0; int maxDstSamples = 0; int dstSamplesSize = 0; int fullSamples = 0; uint8_t **srcSamplesData = nullptr; uint8_t **dstSamplesData = nullptr; SwrContext *swrContext = nullptr; int32 lastUpdate = 0; uint16 levelMax = 0; QByteArray data; int32 dataPos = 0; int64 waveformMod = 0; int64 waveformEach = (kCaptureFrequency / 100); uint16 waveformPeak = 0; QVector<uchar> waveform; static int _read_data(void *opaque, uint8_t *buf, int buf_size) { auto l = reinterpret_cast<Private*>(opaque); int32 nbytes = qMin(l->data.size() - l->dataPos, int32(buf_size)); if (nbytes <= 0) { return 0; } memcpy(buf, l->data.constData() + l->dataPos, nbytes); l->dataPos += nbytes; return nbytes; } static int _write_data(void *opaque, uint8_t *buf, int buf_size) { auto l = reinterpret_cast<Private*>(opaque); if (buf_size <= 0) return 0; if (l->dataPos + buf_size > l->data.size()) l->data.resize(l->dataPos + buf_size); memcpy(l->data.data() + l->dataPos, buf, buf_size); l->dataPos += buf_size; return buf_size; } static int64_t _seek_data(void *opaque, int64_t offset, int whence) { auto l = reinterpret_cast<Private*>(opaque); int32 newPos = -1; switch (whence) { case SEEK_SET: newPos = offset; break; case SEEK_CUR: newPos = l->dataPos + offset; break; case SEEK_END: newPos = l->data.size() + offset; break; case AVSEEK_SIZE: { // Special whence for determining filesize without any seek. return l->data.size(); } break; } if (newPos < 0) { return -1; } l->dataPos = newPos; return l->dataPos; } }; Instance::Inner::Inner(QThread *thread) : d(new Private()) { moveToThread(thread); _timer.moveToThread(thread); connect(&_timer, SIGNAL(timeout()), this, SLOT(onTimeout())); } Instance::Inner::~Inner() { onStop(false); delete d; } void Instance::Inner::onInit() { } void Instance::Inner::onStart() { // Start OpenAL Capture const ALCchar *dName = alcGetString(0, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER); DEBUG_LOG(("Audio Info: Capture device name '%1'").arg(dName)); d->device = alcCaptureOpenDevice(dName, kCaptureFrequency, AL_FORMAT_MONO16, kCaptureFrequency / 5); if (!d->device) { LOG(("Audio Error: capture device not present!")); emit error(); return; } alcCaptureStart(d->device); if (ErrorHappened(d->device)) { alcCaptureCloseDevice(d->device); d->device = nullptr; emit error(); return; } // Create encoding context d->ioBuffer = (uchar*)av_malloc(AVBlockSize); d->ioContext = avio_alloc_context(d->ioBuffer, AVBlockSize, 1, static_cast<void*>(d), &Private::_read_data, &Private::_write_data, &Private::_seek_data); int res = 0; char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; AVOutputFormat *fmt = 0; while ((fmt = av_oformat_next(fmt))) { if (fmt->name == qstr("opus")) { break; } } if (!fmt) { LOG(("Audio Error: Unable to find opus AVOutputFormat for capture")); onStop(false); emit error(); return; } if ((res = avformat_alloc_output_context2(&d->fmtContext, fmt, 0, 0)) < 0) { LOG(("Audio Error: Unable to avformat_alloc_output_context2 for capture, error %1, %2").arg(res).arg(av_make_error_string(err, sizeof(err), res))); onStop(false); emit error(); return; } d->fmtContext->pb = d->ioContext; d->fmtContext->flags |= AVFMT_FLAG_CUSTOM_IO; d->opened = true; // Add audio stream d->codec = avcodec_find_encoder(fmt->audio_codec); if (!d->codec) { LOG(("Audio Error: Unable to avcodec_find_encoder for capture")); onStop(false); emit error(); return; } d->stream = avformat_new_stream(d->fmtContext, d->codec); if (!d->stream) { LOG(("Audio Error: Unable to avformat_new_stream for capture")); onStop(false); emit error(); return; } d->stream->id = d->fmtContext->nb_streams - 1; d->codecContext = avcodec_alloc_context3(d->codec); if (!d->codecContext) { LOG(("Audio Error: Unable to avcodec_alloc_context3 for capture")); onStop(false); emit error(); return; } av_opt_set_int(d->codecContext, "refcounted_frames", 1, 0); d->codecContext->sample_fmt = AV_SAMPLE_FMT_FLTP; d->codecContext->bit_rate = 64000; d->codecContext->channel_layout = AV_CH_LAYOUT_MONO; d->codecContext->sample_rate = kCaptureFrequency; d->codecContext->channels = 1; if (d->fmtContext->oformat->flags & AVFMT_GLOBALHEADER) { d->codecContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; } // Open audio stream if ((res = avcodec_open2(d->codecContext, d->codec, nullptr)) < 0) { LOG(("Audio Error: Unable to avcodec_open2 for capture, error %1, %2").arg(res).arg(av_make_error_string(err, sizeof(err), res))); onStop(false); emit error(); return; } // Alloc source samples d->srcSamples = (d->codecContext->codec->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE) ? 10000 : d->codecContext->frame_size; //if ((res = av_samples_alloc_array_and_samples(&d->srcSamplesData, 0, d->codecContext->channels, d->srcSamples, d->codecContext->sample_fmt, 0)) < 0) { // LOG(("Audio Error: Unable to av_samples_alloc_array_and_samples for capture, error %1, %2").arg(res).arg(av_make_error_string(err, sizeof(err), res))); // onStop(false); // emit error(); // return; //} // Using _captured directly // Prepare resampling d->swrContext = swr_alloc(); if (!d->swrContext) { fprintf(stderr, "Could not allocate resampler context\n"); exit(1); } av_opt_set_int(d->swrContext, "in_channel_count", d->codecContext->channels, 0); av_opt_set_int(d->swrContext, "in_sample_rate", d->codecContext->sample_rate, 0); av_opt_set_sample_fmt(d->swrContext, "in_sample_fmt", AV_SAMPLE_FMT_S16, 0); av_opt_set_int(d->swrContext, "out_channel_count", d->codecContext->channels, 0); av_opt_set_int(d->swrContext, "out_sample_rate", d->codecContext->sample_rate, 0); av_opt_set_sample_fmt(d->swrContext, "out_sample_fmt", d->codecContext->sample_fmt, 0); if ((res = swr_init(d->swrContext)) < 0) { LOG(("Audio Error: Unable to swr_init for capture, error %1, %2").arg(res).arg(av_make_error_string(err, sizeof(err), res))); onStop(false); emit error(); return; } d->maxDstSamples = d->srcSamples; if ((res = av_samples_alloc_array_and_samples(&d->dstSamplesData, 0, d->codecContext->channels, d->maxDstSamples, d->codecContext->sample_fmt, 0)) < 0) { LOG(("Audio Error: Unable to av_samples_alloc_array_and_samples for capture, error %1, %2").arg(res).arg(av_make_error_string(err, sizeof(err), res))); onStop(false); emit error(); return; } d->dstSamplesSize = av_samples_get_buffer_size(0, d->codecContext->channels, d->maxDstSamples, d->codecContext->sample_fmt, 0); if ((res = avcodec_parameters_from_context(d->stream->codecpar, d->codecContext)) < 0) { LOG(("Audio Error: Unable to avcodec_parameters_from_context for capture, error %1, %2").arg(res).arg(av_make_error_string(err, sizeof(err), res))); onStop(false); emit error(); return; } // Write file header if ((res = avformat_write_header(d->fmtContext, 0)) < 0) { LOG(("Audio Error: Unable to avformat_write_header for capture, error %1, %2").arg(res).arg(av_make_error_string(err, sizeof(err), res))); onStop(false); emit error(); return; } _timer.start(50); _captured.clear(); _captured.reserve(AudioVoiceMsgBufferSize); DEBUG_LOG(("Audio Capture: started!")); } void Instance::Inner::onStop(bool needResult) { if (!_timer.isActive()) return; // in onStop() already _timer.stop(); if (d->device) { alcCaptureStop(d->device); onTimeout(); // get last data } // Write what is left if (!_captured.isEmpty()) { auto fadeSamples = kCaptureFadeInDuration * kCaptureFrequency / 1000; auto capturedSamples = static_cast<int>(_captured.size() / sizeof(short)); if ((_captured.size() % sizeof(short)) || (d->fullSamples + capturedSamples < kCaptureFrequency) || (capturedSamples < fadeSamples)) { d->fullSamples = 0; d->dataPos = 0; d->data.clear(); d->waveformMod = 0; d->waveformPeak = 0; d->waveform.clear(); } else { float64 coef = 1. / fadeSamples, fadedFrom = 0; for (short *ptr = ((short*)_captured.data()) + capturedSamples, *end = ptr - fadeSamples; ptr != end; ++fadedFrom) { --ptr; *ptr = qRound(fadedFrom * coef * *ptr); } if (capturedSamples % d->srcSamples) { int32 s = _captured.size(); _captured.resize(s + (d->srcSamples - (capturedSamples % d->srcSamples)) * sizeof(short)); memset(_captured.data() + s, 0, _captured.size() - s); } int32 framesize = d->srcSamples * d->codecContext->channels * sizeof(short), encoded = 0; while (_captured.size() >= encoded + framesize) { processFrame(encoded, framesize); encoded += framesize; } writeFrame(nullptr); // drain the codec if (encoded != _captured.size()) { d->fullSamples = 0; d->dataPos = 0; d->data.clear(); d->waveformMod = 0; d->waveformPeak = 0; d->waveform.clear(); } } } DEBUG_LOG(("Audio Capture: stopping (need result: %1), size: %2, samples: %3").arg(Logs::b(needResult)).arg(d->data.size()).arg(d->fullSamples)); _captured = QByteArray(); // Finish stream if (d->device) { av_write_trailer(d->fmtContext); } QByteArray result = d->fullSamples ? d->data : QByteArray(); VoiceWaveform waveform; qint32 samples = d->fullSamples; if (samples && !d->waveform.isEmpty()) { int64 count = d->waveform.size(), sum = 0; if (count >= Player::kWaveformSamplesCount) { QVector<uint16> peaks; peaks.reserve(Player::kWaveformSamplesCount); uint16 peak = 0; for (int32 i = 0; i < count; ++i) { uint16 sample = uint16(d->waveform.at(i)) * 256; if (peak < sample) { peak = sample; } sum += Player::kWaveformSamplesCount; if (sum >= count) { sum -= count; peaks.push_back(peak); peak = 0; } } auto sum = std::accumulate(peaks.cbegin(), peaks.cend(), 0LL); peak = qMax(int32(sum * 1.8 / peaks.size()), 2500); waveform.resize(peaks.size()); for (int32 i = 0, l = peaks.size(); i != l; ++i) { waveform[i] = char(qMin(31U, uint32(qMin(peaks.at(i), peak)) * 31 / peak)); } } } if (d->device) { alcCaptureStop(d->device); alcCaptureCloseDevice(d->device); d->device = nullptr; if (d->codecContext) { avcodec_free_context(&d->codecContext); d->codecContext = nullptr; } if (d->srcSamplesData) { if (d->srcSamplesData[0]) { av_freep(&d->srcSamplesData[0]); } av_freep(&d->srcSamplesData); } if (d->dstSamplesData) { if (d->dstSamplesData[0]) { av_freep(&d->dstSamplesData[0]); } av_freep(&d->dstSamplesData); } d->fullSamples = 0; if (d->swrContext) { swr_free(&d->swrContext); d->swrContext = nullptr; } if (d->opened) { avformat_close_input(&d->fmtContext); d->opened = false; } if (d->ioContext) { av_freep(&d->ioContext->buffer); av_freep(&d->ioContext); d->ioBuffer = nullptr; } else if (d->ioBuffer) { av_freep(&d->ioBuffer); } if (d->fmtContext) { avformat_free_context(d->fmtContext); d->fmtContext = nullptr; } d->fmt = nullptr; d->stream = nullptr; d->codec = nullptr; d->lastUpdate = 0; d->levelMax = 0; d->dataPos = 0; d->data.clear(); d->waveformMod = 0; d->waveformPeak = 0; d->waveform.clear(); } if (needResult) emit done(result, waveform, samples); } void Instance::Inner::onTimeout() { if (!d->device) { _timer.stop(); return; } ALint samples; alcGetIntegerv(d->device, ALC_CAPTURE_SAMPLES, sizeof(samples), &samples); if (ErrorHappened(d->device)) { onStop(false); emit error(); return; } if (samples > 0) { // Get samples from OpenAL auto s = _captured.size(); auto news = s + static_cast<int>(samples * sizeof(short)); if (news / AudioVoiceMsgBufferSize > s / AudioVoiceMsgBufferSize) { _captured.reserve(((news / AudioVoiceMsgBufferSize) + 1) * AudioVoiceMsgBufferSize); } _captured.resize(news); alcCaptureSamples(d->device, (ALCvoid *)(_captured.data() + s), samples); if (ErrorHappened(d->device)) { onStop(false); emit error(); return; } // Count new recording level and update view auto skipSamples = kCaptureSkipDuration * kCaptureFrequency / 1000; auto fadeSamples = kCaptureFadeInDuration * kCaptureFrequency / 1000; auto levelindex = d->fullSamples + static_cast<int>(s / sizeof(short)); for (auto ptr = (const short*)(_captured.constData() + s), end = (const short*)(_captured.constData() + news); ptr < end; ++ptr, ++levelindex) { if (levelindex > skipSamples) { uint16 value = qAbs(*ptr); if (levelindex < skipSamples + fadeSamples) { value = qRound(value * float64(levelindex - skipSamples) / fadeSamples); } if (d->levelMax < value) { d->levelMax = value; } } } qint32 samplesFull = d->fullSamples + _captured.size() / sizeof(short), samplesSinceUpdate = samplesFull - d->lastUpdate; if (samplesSinceUpdate > AudioVoiceMsgUpdateView * kCaptureFrequency / 1000) { emit updated(d->levelMax, samplesFull); d->lastUpdate = samplesFull; d->levelMax = 0; } // Write frames int32 framesize = d->srcSamples * d->codecContext->channels * sizeof(short), encoded = 0; while (uint32(_captured.size()) >= encoded + framesize + fadeSamples * sizeof(short)) { processFrame(encoded, framesize); encoded += framesize; } // Collapse the buffer if (encoded > 0) { int32 goodSize = _captured.size() - encoded; memmove(_captured.data(), _captured.constData() + encoded, goodSize); _captured.resize(goodSize); } } else { DEBUG_LOG(("Audio Capture: no samples to capture.")); } } void Instance::Inner::processFrame(int32 offset, int32 framesize) { // Prepare audio frame if (framesize % sizeof(short)) { // in the middle of a sample LOG(("Audio Error: Bad framesize in writeFrame() for capture, framesize %1, %2").arg(framesize)); onStop(false); emit error(); return; } auto samplesCnt = static_cast<int>(framesize / sizeof(short)); int res = 0; char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; auto srcSamplesDataChannel = (short*)(_captured.data() + offset); auto srcSamplesData = &srcSamplesDataChannel; // memcpy(d->srcSamplesData[0], _captured.constData() + offset, framesize); auto skipSamples = static_cast<int>(kCaptureSkipDuration * kCaptureFrequency / 1000); auto fadeSamples = static_cast<int>(kCaptureFadeInDuration * kCaptureFrequency / 1000); if (d->fullSamples < skipSamples + fadeSamples) { int32 fadedCnt = qMin(samplesCnt, skipSamples + fadeSamples - d->fullSamples); float64 coef = 1. / fadeSamples, fadedFrom = d->fullSamples - skipSamples; short *ptr = srcSamplesDataChannel, *zeroEnd = ptr + qMin(samplesCnt, qMax(0, skipSamples - d->fullSamples)), *end = ptr + fadedCnt; for (; ptr != zeroEnd; ++ptr, ++fadedFrom) { *ptr = 0; } for (; ptr != end; ++ptr, ++fadedFrom) { *ptr = qRound(fadedFrom * coef * *ptr); } } d->waveform.reserve(d->waveform.size() + (samplesCnt / d->waveformEach) + 1); for (short *ptr = srcSamplesDataChannel, *end = ptr + samplesCnt; ptr != end; ++ptr) { uint16 value = qAbs(*ptr); if (d->waveformPeak < value) { d->waveformPeak = value; } if (++d->waveformMod == d->waveformEach) { d->waveformMod -= d->waveformEach; d->waveform.push_back(uchar(d->waveformPeak / 256)); d->waveformPeak = 0; } } // Convert to final format d->dstSamples = av_rescale_rnd(swr_get_delay(d->swrContext, d->codecContext->sample_rate) + d->srcSamples, d->codecContext->sample_rate, d->codecContext->sample_rate, AV_ROUND_UP); if (d->dstSamples > d->maxDstSamples) { d->maxDstSamples = d->dstSamples; av_freep(&d->dstSamplesData[0]); if ((res = av_samples_alloc(d->dstSamplesData, 0, d->codecContext->channels, d->dstSamples, d->codecContext->sample_fmt, 1)) < 0) { LOG(("Audio Error: Unable to av_samples_alloc for capture, error %1, %2").arg(res).arg(av_make_error_string(err, sizeof(err), res))); onStop(false); emit error(); return; } d->dstSamplesSize = av_samples_get_buffer_size(0, d->codecContext->channels, d->maxDstSamples, d->codecContext->sample_fmt, 0); } if ((res = swr_convert(d->swrContext, d->dstSamplesData, d->dstSamples, (const uint8_t **)srcSamplesData, d->srcSamples)) < 0) { LOG(("Audio Error: Unable to swr_convert for capture, error %1, %2").arg(res).arg(av_make_error_string(err, sizeof(err), res))); onStop(false); emit error(); return; } // Write audio frame AVFrame *frame = av_frame_alloc(); frame->nb_samples = d->dstSamples; frame->pts = av_rescale_q(d->fullSamples, AVRational { 1, d->codecContext->sample_rate }, d->codecContext->time_base); avcodec_fill_audio_frame(frame, d->codecContext->channels, d->codecContext->sample_fmt, d->dstSamplesData[0], d->dstSamplesSize, 0); writeFrame(frame); d->fullSamples += samplesCnt; av_frame_free(&frame); } void Instance::Inner::writeFrame(AVFrame *frame) { int res = 0; char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; res = avcodec_send_frame(d->codecContext, frame); if (res == AVERROR(EAGAIN)) { int packetsWritten = writePackets(); if (packetsWritten < 0) { if (frame && packetsWritten == AVERROR_EOF) { LOG(("Audio Error: EOF in packets received when EAGAIN was got in avcodec_send_frame()")); onStop(false); emit error(); } return; } else if (!packetsWritten) { LOG(("Audio Error: No packets received when EAGAIN was got in avcodec_send_frame()")); onStop(false); emit error(); return; } res = avcodec_send_frame(d->codecContext, frame); } if (res < 0) { LOG(("Audio Error: Unable to avcodec_send_frame for capture, error %1, %2").arg(res).arg(av_make_error_string(err, sizeof(err), res))); onStop(false); emit error(); return; } if (!frame) { // drain if ((res = writePackets()) != AVERROR_EOF) { LOG(("Audio Error: not EOF in packets received when draining the codec, result %1").arg(res)); onStop(false); emit error(); } } } int Instance::Inner::writePackets() { AVPacket pkt; memset(&pkt, 0, sizeof(pkt)); // data and size must be 0; int res = 0; char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; int written = 0; do { av_init_packet(&pkt); if ((res = avcodec_receive_packet(d->codecContext, &pkt)) < 0) { if (res == AVERROR(EAGAIN)) { return written; } else if (res == AVERROR_EOF) { return res; } LOG(("Audio Error: Unable to avcodec_receive_packet for capture, error %1, %2").arg(res).arg(av_make_error_string(err, sizeof(err), res))); onStop(false); emit error(); return res; } av_packet_rescale_ts(&pkt, d->codecContext->time_base, d->stream->time_base); pkt.stream_index = d->stream->index; if ((res = av_interleaved_write_frame(d->fmtContext, &pkt)) < 0) { LOG(("Audio Error: Unable to av_interleaved_write_frame for capture, error %1, %2").arg(res).arg(av_make_error_string(err, sizeof(err), res))); onStop(false); emit error(); return -1; } ++written; av_packet_unref(&pkt); } while (true); return written; } } // namespace Capture } // namespace Media ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/media/media_audio_capture.h������������������������������������0000664�0000000�0000000�00000002733�13262451251�0024643�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once struct AVFrame; namespace Media { namespace Capture { void Start(); void Finish(); class Instance : public QObject { Q_OBJECT public: Instance(); void check(); bool available() const { return _available; } ~Instance(); signals: void start(); void stop(bool needResult); void done(QByteArray data, VoiceWaveform waveform, qint32 samples); void updated(quint16 level, qint32 samples); void error(); private: class Inner; friend class Inner; bool _available = false; QThread _thread; Inner *_inner; }; Instance *instance(); class Instance::Inner : public QObject { Q_OBJECT public: Inner(QThread *thread); ~Inner(); signals: void error(); void updated(quint16 level, qint32 samples); void done(QByteArray data, VoiceWaveform waveform, qint32 samples); public slots: void onInit(); void onStart(); void onStop(bool needResult); void onTimeout(); private: void processFrame(int32 offset, int32 framesize); void writeFrame(AVFrame *frame); // Writes the packets till EAGAIN is got from av_receive_packet() // Returns number of packets written or -1 on error int writePackets(); struct Private; Private *d; QTimer _timer; QByteArray _captured; }; } // namespace Capture } // namespace Media �������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/media/media_audio_ffmpeg_loader.cpp����������������������������0000664�0000000�0000000�00000041270�13262451251�0026324�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "media/media_audio_ffmpeg_loader.h" uint64_t AbstractFFMpegLoader::ComputeChannelLayout( uint64_t channel_layout, int channels) { if (channel_layout) { if (av_get_channel_layout_nb_channels(channel_layout) == channels) { return channel_layout; } } return av_get_default_channel_layout(channels); } int64 AbstractFFMpegLoader::Mul(int64 value, AVRational rational) { return value * rational.num / rational.den; } bool AbstractFFMpegLoader::open(TimeMs positionMs) { if (!AudioPlayerLoader::openFile()) { return false; } int res = 0; char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; ioBuffer = (uchar*)av_malloc(AVBlockSize); if (!_data.isEmpty()) { ioContext = avio_alloc_context(ioBuffer, AVBlockSize, 0, reinterpret_cast<void*>(this), &AbstractFFMpegLoader::_read_data, 0, &AbstractFFMpegLoader::_seek_data); } else if (!_bytes.empty()) { ioContext = avio_alloc_context(ioBuffer, AVBlockSize, 0, reinterpret_cast<void*>(this), &AbstractFFMpegLoader::_read_bytes, 0, &AbstractFFMpegLoader::_seek_bytes); } else { ioContext = avio_alloc_context(ioBuffer, AVBlockSize, 0, reinterpret_cast<void*>(this), &AbstractFFMpegLoader::_read_file, 0, &AbstractFFMpegLoader::_seek_file); } fmtContext = avformat_alloc_context(); if (!fmtContext) { DEBUG_LOG(("Audio Read Error: Unable to avformat_alloc_context for file '%1', data size '%2'").arg(_file.name()).arg(_data.size())); return false; } fmtContext->pb = ioContext; if ((res = avformat_open_input(&fmtContext, 0, 0, 0)) < 0) { ioBuffer = 0; DEBUG_LOG(("Audio Read Error: Unable to avformat_open_input for file '%1', data size '%2', error %3, %4").arg(_file.name()).arg(_data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); return false; } _opened = true; if ((res = avformat_find_stream_info(fmtContext, 0)) < 0) { DEBUG_LOG(("Audio Read Error: Unable to avformat_find_stream_info for file '%1', data size '%2', error %3, %4").arg(_file.name()).arg(_data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); return false; } streamId = av_find_best_stream(fmtContext, AVMEDIA_TYPE_AUDIO, -1, -1, &codec, 0); if (streamId < 0) { LOG(("Audio Error: Unable to av_find_best_stream for file '%1', data size '%2', error %3, %4").arg(_file.name()).arg(_data.size()).arg(streamId).arg(av_make_error_string(err, sizeof(err), streamId))); return false; } const auto stream = fmtContext->streams[streamId]; const auto params = stream->codecpar; _samplesFrequency = params->sample_rate; if (stream->duration != AV_NOPTS_VALUE) { _samplesCount = Mul( stream->duration * _samplesFrequency, stream->time_base); } else { _samplesCount = Mul( fmtContext->duration * _samplesFrequency, { 1, AV_TIME_BASE }); } return true; } AbstractFFMpegLoader::~AbstractFFMpegLoader() { if (_opened) { avformat_close_input(&fmtContext); } if (ioContext) { av_freep(&ioContext->buffer); av_freep(&ioContext); } else if (ioBuffer) { av_freep(&ioBuffer); } if (fmtContext) avformat_free_context(fmtContext); } int AbstractFFMpegLoader::_read_data(void *opaque, uint8_t *buf, int buf_size) { auto l = reinterpret_cast<AbstractFFMpegLoader*>(opaque); auto nbytes = qMin(l->_data.size() - l->_dataPos, int32(buf_size)); if (nbytes <= 0) { return 0; } memcpy(buf, l->_data.constData() + l->_dataPos, nbytes); l->_dataPos += nbytes; return nbytes; } int64_t AbstractFFMpegLoader::_seek_data(void *opaque, int64_t offset, int whence) { auto l = reinterpret_cast<AbstractFFMpegLoader*>(opaque); int32 newPos = -1; switch (whence) { case SEEK_SET: newPos = offset; break; case SEEK_CUR: newPos = l->_dataPos + offset; break; case SEEK_END: newPos = l->_data.size() + offset; break; case AVSEEK_SIZE: { // Special whence for determining filesize without any seek. return l->_data.size(); } break; } if (newPos < 0 || newPos > l->_data.size()) { return -1; } l->_dataPos = newPos; return l->_dataPos; } int AbstractFFMpegLoader::_read_bytes(void *opaque, uint8_t *buf, int buf_size) { auto l = reinterpret_cast<AbstractFFMpegLoader*>(opaque); auto nbytes = qMin(static_cast<int>(l->_bytes.size()) - l->_dataPos, buf_size); if (nbytes <= 0) { return 0; } memcpy(buf, l->_bytes.data() + l->_dataPos, nbytes); l->_dataPos += nbytes; return nbytes; } int64_t AbstractFFMpegLoader::_seek_bytes(void *opaque, int64_t offset, int whence) { auto l = reinterpret_cast<AbstractFFMpegLoader*>(opaque); int32 newPos = -1; switch (whence) { case SEEK_SET: newPos = offset; break; case SEEK_CUR: newPos = l->_dataPos + offset; break; case SEEK_END: newPos = static_cast<int>(l->_bytes.size()) + offset; break; case AVSEEK_SIZE: { // Special whence for determining filesize without any seek. return l->_bytes.size(); } break; } if (newPos < 0 || newPos > l->_bytes.size()) { return -1; } l->_dataPos = newPos; return l->_dataPos; } int AbstractFFMpegLoader::_read_file(void *opaque, uint8_t *buf, int buf_size) { auto l = reinterpret_cast<AbstractFFMpegLoader*>(opaque); return int(l->_f.read((char*)(buf), buf_size)); } int64_t AbstractFFMpegLoader::_seek_file(void *opaque, int64_t offset, int whence) { auto l = reinterpret_cast<AbstractFFMpegLoader*>(opaque); switch (whence) { case SEEK_SET: return l->_f.seek(offset) ? l->_f.pos() : -1; case SEEK_CUR: return l->_f.seek(l->_f.pos() + offset) ? l->_f.pos() : -1; case SEEK_END: return l->_f.seek(l->_f.size() + offset) ? l->_f.pos() : -1; case AVSEEK_SIZE: { // Special whence for determining filesize without any seek. return l->_f.size(); } break; } return -1; } AbstractAudioFFMpegLoader::AbstractAudioFFMpegLoader( const FileLocation &file, const QByteArray &data, base::byte_vector &&bytes) : AbstractFFMpegLoader(file, data, std::move(bytes)) { _frame = av_frame_alloc(); } bool AbstractAudioFFMpegLoader::initUsingContext( not_null<AVCodecContext*> context, int64 initialCount, int initialFrequency) { const auto layout = ComputeChannelLayout( context->channel_layout, context->channels); if (!layout) { LOG(("Audio Error: Unknown channel layout %1 for %2 channels." ).arg(context->channel_layout ).arg(context->channels )); return false; } _swrSrcSampleFormat = context->sample_fmt; switch (layout) { case AV_CH_LAYOUT_MONO: switch (_swrSrcSampleFormat) { case AV_SAMPLE_FMT_U8: case AV_SAMPLE_FMT_U8P: _swrDstSampleFormat = _swrSrcSampleFormat; _swrDstChannelLayout = layout; _outputChannels = 1; _outputSampleSize = 1; _outputFormat = AL_FORMAT_MONO8; break; case AV_SAMPLE_FMT_S16: case AV_SAMPLE_FMT_S16P: _swrDstSampleFormat = _swrSrcSampleFormat; _swrDstChannelLayout = layout; _outputChannels = 1; _outputSampleSize = sizeof(uint16); _outputFormat = AL_FORMAT_MONO16; break; } break; case AV_CH_LAYOUT_STEREO: switch (_swrSrcSampleFormat) { case AV_SAMPLE_FMT_U8: _swrDstSampleFormat = _swrSrcSampleFormat; _swrDstChannelLayout = layout; _outputChannels = 2; _outputSampleSize = 2; _outputFormat = AL_FORMAT_STEREO8; break; case AV_SAMPLE_FMT_S16: _swrDstSampleFormat = _swrSrcSampleFormat; _swrDstChannelLayout = layout; _outputChannels = 2; _outputSampleSize = 2 * sizeof(uint16); _outputFormat = AL_FORMAT_STEREO16; break; } break; } if (_swrDstRate == initialFrequency) { _outputSamplesCount = initialCount; } else { _outputSamplesCount = av_rescale_rnd( initialCount, _swrDstRate, initialFrequency, AV_ROUND_UP); } return true; } auto AbstractAudioFFMpegLoader::readFromReadyContext( not_null<AVCodecContext*> context, QByteArray &result, int64 &samplesAdded) -> ReadResult { av_frame_unref(_frame); const auto res = avcodec_receive_frame(context, _frame); if (res >= 0) { return readFromReadyFrame(result, samplesAdded); } if (res == AVERROR_EOF) { return ReadResult::EndOfFile; } else if (res != AVERROR(EAGAIN)) { char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; LOG(("Audio Error: " "Unable to avcodec_receive_frame() file '%1', data size '%2', " "error %3, %4" ).arg(_file.name() ).arg(_data.size() ).arg(res ).arg(av_make_error_string(err, sizeof(err), res) )); return ReadResult::Error; } return ReadResult::Wait; } bool AbstractAudioFFMpegLoader::frameHasDesiredFormat() const { const auto frameChannelLayout = ComputeChannelLayout( _frame->channel_layout, _frame->channels); return true && (_frame->format == _swrDstSampleFormat) && (frameChannelLayout == _swrDstChannelLayout) && (_frame->sample_rate == _swrDstRate); } bool AbstractAudioFFMpegLoader::initResampleForFrame() { const auto frameChannelLayout = ComputeChannelLayout( _frame->channel_layout, _frame->channels); if (!frameChannelLayout) { LOG(("Audio Error: " "Unable to compute channel layout for frame in file '%1', " "data size '%2', channel_layout %3, channels %4" ).arg(_file.name() ).arg(_data.size() ).arg(_frame->channel_layout ).arg(_frame->channels )); return false; } else if (_frame->format == -1) { LOG(("Audio Error: " "Unknown frame format in file '%1', data size '%2'" ).arg(_file.name() ).arg(_data.size() )); return false; } else if (_swrContext) { if (true && (_frame->format == _swrSrcSampleFormat) && (frameChannelLayout == _swrSrcChannelLayout) && (_frame->sample_rate == _swrSrcRate)) { return true; } swr_close(_swrContext); } _swrSrcSampleFormat = static_cast<AVSampleFormat>(_frame->format); _swrSrcChannelLayout = frameChannelLayout; _swrSrcRate = _frame->sample_rate; return initResampleUsingFormat(); } bool AbstractAudioFFMpegLoader::initResampleUsingFormat() { int res = 0; _swrContext = swr_alloc_set_opts( _swrContext, _swrDstChannelLayout, _swrDstSampleFormat, _swrDstRate, _swrSrcChannelLayout, _swrSrcSampleFormat, _swrSrcRate, 0, nullptr); if (!_swrContext) { LOG(("Audio Error: " "Unable to swr_alloc for file '%1', data size '%2'" ).arg(_file.name() ).arg(_data.size())); return false; } else if ((res = swr_init(_swrContext)) < 0) { char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; LOG(("Audio Error: " "Unable to swr_init for file '%1', data size '%2', " "error %3, %4" ).arg(_file.name() ).arg(_data.size() ).arg(res ).arg(av_make_error_string(err, sizeof(err), res) )); return false; } if (_swrDstData) { av_freep(&_swrDstData[0]); _swrDstDataCapacity = -1; } return true; } bool AbstractAudioFFMpegLoader::ensureResampleSpaceAvailable(int samples) { if (_swrDstData != nullptr && _swrDstDataCapacity >= samples) { return true; } const auto allocate = std::max(samples, int(av_rescale_rnd( AVBlockSize / _outputSampleSize, _swrDstRate, _swrSrcRate, AV_ROUND_UP))); if (_swrDstData) { av_freep(&_swrDstData[0]); } const auto res = _swrDstData ? av_samples_alloc( _swrDstData, nullptr, _outputChannels, allocate, _swrDstSampleFormat, 0) : av_samples_alloc_array_and_samples( &_swrDstData, nullptr, _outputChannels, allocate, _swrDstSampleFormat, 0); if (res < 0) { char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; LOG(("Audio Error: " "Unable to av_samples_alloc for file '%1', data size '%2', " "error %3, %4" ).arg(_file.name() ).arg(_data.size() ).arg(res ).arg(av_make_error_string(err, sizeof(err), res) )); return false; } _swrDstDataCapacity = allocate; return true; } void AbstractAudioFFMpegLoader::appendSamples( QByteArray &result, int64 &samplesAdded, uint8_t **data, int count) const { result.append( reinterpret_cast<const char*>(data[0]), count * _outputSampleSize); samplesAdded += count; } AudioPlayerLoader::ReadResult AbstractAudioFFMpegLoader::readFromReadyFrame( QByteArray &result, int64 &samplesAdded) { if (frameHasDesiredFormat()) { appendSamples( result, samplesAdded, _frame->extended_data, _frame->nb_samples); return ReadResult::Ok; } else if (!initResampleForFrame()) { return ReadResult::Error; } const auto maxSamples = av_rescale_rnd( swr_get_delay(_swrContext, _swrSrcRate) + _frame->nb_samples, _swrDstRate, _swrSrcRate, AV_ROUND_UP); if (!ensureResampleSpaceAvailable(maxSamples)) { return ReadResult::Error; } const auto samples = swr_convert( _swrContext, _swrDstData, maxSamples, (const uint8_t**)_frame->extended_data, _frame->nb_samples); if (samples < 0) { char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; LOG(("Audio Error: " "Unable to swr_convert for file '%1', data size '%2', " "error %3, %4" ).arg(_file.name() ).arg(_data.size() ).arg(samples ).arg(av_make_error_string(err, sizeof(err), samples) )); return ReadResult::Error; } appendSamples( result, samplesAdded, _swrDstData, samples); return ReadResult::Ok; } AbstractAudioFFMpegLoader::~AbstractAudioFFMpegLoader() { if (_swrContext) { swr_free(&_swrContext); } if (_swrDstData) { if (_swrDstData[0]) { av_freep(&_swrDstData[0]); } av_freep(&_swrDstData); } av_frame_free(&_frame); } FFMpegLoader::FFMpegLoader( const FileLocation &file, const QByteArray &data, base::byte_vector &&bytes) : AbstractAudioFFMpegLoader(file, data, std::move(bytes)) { } bool FFMpegLoader::open(TimeMs positionMs) { if (!AbstractFFMpegLoader::open(positionMs)) { return false; } if (!openCodecContext()) { return false; } if (!initUsingContext(_codecContext, _samplesCount, _samplesFrequency)) { return false; } return seekTo(positionMs); } bool FFMpegLoader::openCodecContext() { int res = 0; char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; _codecContext = avcodec_alloc_context3(nullptr); if (!_codecContext) { LOG(("Audio Error: " "Unable to avcodec_alloc_context3 for file '%1', data size '%2'" ).arg(_file.name() ).arg(_data.size() )); return false; } const auto stream = fmtContext->streams[streamId]; if ((res = avcodec_parameters_to_context( _codecContext, stream->codecpar)) < 0) { LOG(("Audio Error: " "Unable to avcodec_parameters_to_context for file '%1', " "data size '%2', error %3, %4" ).arg(_file.name() ).arg(_data.size() ).arg(res ).arg(av_make_error_string(err, sizeof(err), res) )); return false; } av_codec_set_pkt_timebase(_codecContext, stream->time_base); av_opt_set_int(_codecContext, "refcounted_frames", 1, 0); if ((res = avcodec_open2(_codecContext, codec, 0)) < 0) { LOG(("Audio Error: " "Unable to avcodec_open2 for file '%1', data size '%2', " "error %3, %4" ).arg(_file.name() ).arg(_data.size() ).arg(res ).arg(av_make_error_string(err, sizeof(err), res) )); return false; } return true; } bool FFMpegLoader::seekTo(TimeMs positionMs) { if (positionMs) { const auto stream = fmtContext->streams[streamId]; const auto timeBase = stream->time_base; const auto timeStamp = (positionMs * timeBase.den) / (1000LL * timeBase.num); const auto flags1 = AVSEEK_FLAG_ANY; if (av_seek_frame(fmtContext, streamId, timeStamp, flags1) < 0) { const auto flags2 = 0; if (av_seek_frame(fmtContext, streamId, timeStamp, flags2) < 0) { } } } return true; } AudioPlayerLoader::ReadResult FFMpegLoader::readMore( QByteArray &result, int64 &samplesAdded) { const auto readResult = readFromReadyContext( _codecContext, result, samplesAdded); if (readResult != ReadResult::Wait) { return readResult; } auto res = 0; if ((res = av_read_frame(fmtContext, &_packet)) < 0) { if (res != AVERROR_EOF) { char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; LOG(("Audio Error: " "Unable to av_read_frame() file '%1', data size '%2', " "error %3, %4" ).arg(_file.name() ).arg(_data.size() ).arg(res ).arg(av_make_error_string(err, sizeof(err), res) )); return ReadResult::Error; } avcodec_send_packet(_codecContext, nullptr); // drain return ReadResult::Ok; } if (_packet.stream_index == streamId) { res = avcodec_send_packet(_codecContext, &_packet); if (res < 0) { av_packet_unref(&_packet); char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; LOG(("Audio Error: " "Unable to avcodec_send_packet() file '%1', data size '%2', " "error %3, %4" ).arg(_file.name() ).arg(_data.size() ).arg(res ).arg(av_make_error_string(err, sizeof(err), res) )); // There is a sample voice message where skipping such packet // results in a crash (read_access to nullptr) in swr_convert(). //if (res == AVERROR_INVALIDDATA) { // return ReadResult::NotYet; // try to skip bad packet //} return ReadResult::Error; } } av_packet_unref(&_packet); return ReadResult::Ok; } FFMpegLoader::~FFMpegLoader() { if (_codecContext) { avcodec_free_context(&_codecContext); } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/media/media_audio_ffmpeg_loader.h������������������������������0000664�0000000�0000000�00000007316�13262451251�0025774�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "media/media_audio.h" #include "media/media_audio_loader.h" extern "C" { #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libavutil/opt.h> #include <libswresample/swresample.h> } // extern "C" #include <AL/al.h> class AbstractFFMpegLoader : public AudioPlayerLoader { public: AbstractFFMpegLoader( const FileLocation &file, const QByteArray &data, base::byte_vector &&bytes) : AudioPlayerLoader(file, data, std::move(bytes)) { } bool open(TimeMs positionMs) override; int64 samplesCount() override { return _samplesCount; } int samplesFrequency() override { return _samplesFrequency; } static uint64_t ComputeChannelLayout( uint64_t channel_layout, int channels); ~AbstractFFMpegLoader(); protected: static int64 Mul(int64 value, AVRational rational); int _samplesFrequency = Media::Player::kDefaultFrequency; int64 _samplesCount = 0; uchar *ioBuffer = nullptr; AVIOContext *ioContext = nullptr; AVFormatContext *fmtContext = nullptr; AVCodec *codec = nullptr; int32 streamId = 0; bool _opened = false; private: static int _read_data(void *opaque, uint8_t *buf, int buf_size); static int64_t _seek_data(void *opaque, int64_t offset, int whence); static int _read_bytes(void *opaque, uint8_t *buf, int buf_size); static int64_t _seek_bytes(void *opaque, int64_t offset, int whence); static int _read_file(void *opaque, uint8_t *buf, int buf_size); static int64_t _seek_file(void *opaque, int64_t offset, int whence); }; class AbstractAudioFFMpegLoader : public AbstractFFMpegLoader { public: AbstractAudioFFMpegLoader( const FileLocation &file, const QByteArray &data, base::byte_vector &&bytes); int64 samplesCount() override { return _outputSamplesCount; } int samplesFrequency() override { return _swrDstRate; } int format() override { return _outputFormat; } ~AbstractAudioFFMpegLoader(); protected: bool initUsingContext( not_null<AVCodecContext*> context, int64 initialCount, int initialFrequency); ReadResult readFromReadyContext( not_null<AVCodecContext*> context, QByteArray &result, int64 &samplesAdded); int sampleSize() const { return _outputSampleSize; } private: ReadResult readFromReadyFrame(QByteArray &result, int64 &samplesAdded); bool frameHasDesiredFormat() const; bool initResampleForFrame(); bool initResampleUsingFormat(); bool ensureResampleSpaceAvailable(int samples); void appendSamples( QByteArray &result, int64 &samplesAdded, uint8_t **data, int count) const; AVFrame *_frame = nullptr; int _outputFormat = AL_FORMAT_STEREO16; int _outputChannels = 2; int _outputSampleSize = 2 * sizeof(uint16); int64 _outputSamplesCount = 0; SwrContext *_swrContext = nullptr; int _swrSrcRate = 0; AVSampleFormat _swrSrcSampleFormat = AV_SAMPLE_FMT_NONE; uint64_t _swrSrcChannelLayout = 0; const int _swrDstRate = Media::Player::kDefaultFrequency; AVSampleFormat _swrDstSampleFormat = AV_SAMPLE_FMT_S16; uint64_t _swrDstChannelLayout = AV_CH_LAYOUT_STEREO; uint8_t **_swrDstData = nullptr; int _swrDstDataCapacity = 0; }; class FFMpegLoader : public AbstractAudioFFMpegLoader { public: FFMpegLoader( const FileLocation &file, const QByteArray &data, base::byte_vector &&bytes); bool open(TimeMs positionMs) override; ReadResult readMore(QByteArray &result, int64 &samplesAdded) override; ~FFMpegLoader(); private: bool openCodecContext(); bool seekTo(TimeMs positionMs); AVCodecContext *_codecContext = nullptr; AVPacket _packet; }; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/media/media_audio_loader.cpp�����������������������������������0000664�0000000�0000000�00000003763�13262451251�0025005�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "media/media_audio_loader.h" AudioPlayerLoader::AudioPlayerLoader(const FileLocation &file, const QByteArray &data, base::byte_vector &&bytes) : _file(file) , _data(data) , _bytes(std::move(bytes)) { } AudioPlayerLoader::~AudioPlayerLoader() { if (_access) { _file.accessDisable(); _access = false; } } bool AudioPlayerLoader::check(const FileLocation &file, const QByteArray &data) { return this->_file == file && this->_data.size() == data.size(); } void AudioPlayerLoader::saveDecodedSamples(QByteArray *samples, int64 *samplesCount) { Assert(_savedSamplesCount == 0); Assert(_savedSamples.isEmpty()); Assert(!_holdsSavedSamples); samples->swap(_savedSamples); std::swap(*samplesCount, _savedSamplesCount); _holdsSavedSamples = true; } void AudioPlayerLoader::takeSavedDecodedSamples(QByteArray *samples, int64 *samplesCount) { Assert(*samplesCount == 0); Assert(samples->isEmpty()); Assert(_holdsSavedSamples); samples->swap(_savedSamples); std::swap(*samplesCount, _savedSamplesCount); _holdsSavedSamples = false; } bool AudioPlayerLoader::holdsSavedDecodedSamples() const { return _holdsSavedSamples; } bool AudioPlayerLoader::openFile() { if (_data.isEmpty() && _bytes.empty()) { if (_f.isOpen()) _f.close(); if (!_access) { if (!_file.accessEnable()) { LOG(("Audio Error: could not open file access '%1', data size '%2', error %3, %4").arg(_file.name()).arg(_data.size()).arg(_f.error()).arg(_f.errorString())); return false; } _access = true; } _f.setFileName(_file.name()); if (!_f.open(QIODevice::ReadOnly)) { LOG(("Audio Error: could not open file '%1', data size '%2', error %3, %4").arg(_file.name()).arg(_data.size()).arg(_f.error()).arg(_f.errorString())); return false; } } _dataPos = 0; return true; } �������������tdesktop-1.2.17/Telegram/SourceFiles/media/media_audio_loader.h�������������������������������������0000664�0000000�0000000�00000002630�13262451251�0024442�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace FFMpeg { struct AVPacketDataWrap; } // namespace FFMpeg class AudioPlayerLoader { public: AudioPlayerLoader(const FileLocation &file, const QByteArray &data, base::byte_vector &&bytes); virtual ~AudioPlayerLoader(); virtual bool check(const FileLocation &file, const QByteArray &data); virtual bool open(TimeMs positionMs) = 0; virtual int64 samplesCount() = 0; virtual int samplesFrequency() = 0; virtual int format() = 0; enum class ReadResult { Error, NotYet, Ok, Wait, EndOfFile, }; virtual ReadResult readMore(QByteArray &samples, int64 &samplesCount) = 0; virtual void enqueuePackets(QQueue<FFMpeg::AVPacketDataWrap> &packets) { Unexpected("enqueuePackets() call on not ChildFFMpegLoader."); } void saveDecodedSamples(QByteArray *samples, int64 *samplesCount); void takeSavedDecodedSamples(QByteArray *samples, int64 *samplesCount); bool holdsSavedDecodedSamples() const; protected: FileLocation _file; bool _access = false; QByteArray _data; base::byte_vector _bytes; QFile _f; int _dataPos = 0; bool openFile(); private: QByteArray _savedSamples; int64 _savedSamplesCount = 0; bool _holdsSavedSamples = false; }; ��������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/media/media_audio_loaders.cpp����������������������������������0000664�0000000�0000000�00000024551�13262451251�0025166�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "media/media_audio_loaders.h" #include "media/media_audio.h" #include "media/media_audio_ffmpeg_loader.h" #include "media/media_child_ffmpeg_loader.h" namespace Media { namespace Player { Loaders::Loaders(QThread *thread) : _fromVideoNotify([this] { videoSoundAdded(); }) { moveToThread(thread); _fromVideoNotify.moveToThread(thread); connect(thread, SIGNAL(started()), this, SLOT(onInit())); connect(thread, SIGNAL(finished()), this, SLOT(deleteLater())); } void Loaders::feedFromVideo(VideoSoundPart &&part) { auto invoke = false; { QMutexLocker lock(&_fromVideoMutex); _fromVideoQueues[part.audio].enqueue(FFMpeg::dataWrapFromPacket(*part.packet)); invoke = true; } if (invoke) { _fromVideoNotify.call(); } } void Loaders::videoSoundAdded() { auto waitingAndAdded = false; auto queues = decltype(_fromVideoQueues)(); { QMutexLocker lock(&_fromVideoMutex); queues = base::take(_fromVideoQueues); } auto tryLoader = [this](auto &audio, auto &loader, auto &it) { if (audio == it.key() && loader) { loader->enqueuePackets(it.value()); if (loader->holdsSavedDecodedSamples()) { onLoad(audio); } return true; } return false; }; for (auto i = queues.begin(), e = queues.end(); i != e; ++i) { if (!tryLoader(_audio, _audioLoader, i) && !tryLoader(_song, _songLoader, i) && !tryLoader(_video, _videoLoader, i)) { for (auto &packetData : i.value()) { AVPacket packet; FFMpeg::packetFromDataWrap(packet, packetData); FFMpeg::freePacket(&packet); } } } if (waitingAndAdded) { onLoad(_video); } } Loaders::~Loaders() { QMutexLocker lock(&_fromVideoMutex); clearFromVideoQueue(); } void Loaders::clearFromVideoQueue() { auto queues = base::take(_fromVideoQueues); for (auto &queue : queues) { for (auto &packetData : queue) { AVPacket packet; FFMpeg::packetFromDataWrap(packet, packetData); FFMpeg::freePacket(&packet); } } } void Loaders::onInit() { } void Loaders::onStart(const AudioMsgId &audio, qint64 positionMs) { auto type = audio.type(); clear(type); { QMutexLocker lock(internal::audioPlayerMutex()); if (!mixer()) return; auto track = mixer()->trackForType(type); if (!track) return; track->loading = true; } loadData(audio, positionMs); } AudioMsgId Loaders::clear(AudioMsgId::Type type) { AudioMsgId result; switch (type) { case AudioMsgId::Type::Voice: std::swap(result, _audio); _audioLoader = nullptr; break; case AudioMsgId::Type::Song: std::swap(result, _song); _songLoader = nullptr; break; case AudioMsgId::Type::Video: std::swap(result, _video); _videoLoader = nullptr; break; } return result; } void Loaders::setStoppedState(Mixer::Track *track, State state) { mixer()->setStoppedState(track, state); } void Loaders::emitError(AudioMsgId::Type type) { emit error(clear(type)); } void Loaders::onLoad(const AudioMsgId &audio) { loadData(audio, TimeMs(0)); } void Loaders::loadData(AudioMsgId audio, TimeMs positionMs) { auto err = SetupNoErrorStarted; auto type = audio.type(); auto l = setupLoader(audio, err, positionMs); if (!l) { if (err == SetupErrorAtStart) { emitError(type); } return; } auto started = (err == SetupNoErrorStarted); auto finished = false; auto waiting = false; auto errAtStart = started; QByteArray samples; int64 samplesCount = 0; if (l->holdsSavedDecodedSamples()) { l->takeSavedDecodedSamples(&samples, &samplesCount); } while (samples.size() < AudioVoiceMsgBufferSize) { auto res = l->readMore(samples, samplesCount); using Result = AudioPlayerLoader::ReadResult; if (res == Result::Error) { if (errAtStart) { { QMutexLocker lock(internal::audioPlayerMutex()); if (auto track = checkLoader(type)) { track->state.state = State::StoppedAtStart; } } emitError(type); return; } finished = true; break; } else if (res == Result::EndOfFile) { finished = true; break; } else if (res == Result::Ok) { errAtStart = false; } else if (res == Result::Wait) { waiting = (samples.size() < AudioVoiceMsgBufferSize); if (waiting) { l->saveDecodedSamples(&samples, &samplesCount); } break; } QMutexLocker lock(internal::audioPlayerMutex()); if (!checkLoader(type)) { clear(type); return; } } QMutexLocker lock(internal::audioPlayerMutex()); auto track = checkLoader(type); if (!track) { clear(type); return; } if (started) { Audio::AttachToDevice(); track->started(); if (!internal::audioCheckError()) { setStoppedState(track, State::StoppedAtStart); emitError(type); return; } track->format = l->format(); track->frequency = l->samplesFrequency(); const auto position = (positionMs * track->frequency) / 1000LL; track->bufferedPosition = position; track->state.position = position; track->fadeStartPosition = position; } if (samplesCount) { track->ensureStreamCreated(); auto bufferIndex = track->getNotQueuedBufferIndex(); if (!internal::audioCheckError()) { setStoppedState(track, State::StoppedAtError); emitError(type); return; } if (bufferIndex < 0) { // No free buffers, wait. l->saveDecodedSamples(&samples, &samplesCount); return; } track->bufferSamples[bufferIndex] = samples; track->samplesCount[bufferIndex] = samplesCount; track->bufferedLength += samplesCount; alBufferData(track->stream.buffers[bufferIndex], track->format, samples.constData(), samples.size(), track->frequency); alSourceQueueBuffers(track->stream.source, 1, track->stream.buffers + bufferIndex); if (!internal::audioCheckError()) { setStoppedState(track, State::StoppedAtError); emitError(type); return; } } else { if (waiting) { return; } finished = true; } if (finished) { track->loaded = true; track->state.length = track->bufferedPosition + track->bufferedLength; clear(type); } track->loading = false; if (track->state.state == State::Resuming || track->state.state == State::Playing || track->state.state == State::Starting) { ALint state = AL_INITIAL; alGetSourcei(track->stream.source, AL_SOURCE_STATE, &state); if (internal::audioCheckError()) { if (state != AL_PLAYING) { if (state == AL_STOPPED && !internal::CheckAudioDeviceConnected()) { return; } alSourcef(track->stream.source, AL_GAIN, ComputeVolume(type)); if (!internal::audioCheckError()) { setStoppedState(track, State::StoppedAtError); emitError(type); return; } alSourcePlay(track->stream.source); if (!internal::audioCheckError()) { setStoppedState(track, State::StoppedAtError); emitError(type); return; } emit needToCheck(); } } else { setStoppedState(track, State::StoppedAtError); emitError(type); } } } AudioPlayerLoader *Loaders::setupLoader( const AudioMsgId &audio, SetupError &err, TimeMs positionMs) { err = SetupErrorAtStart; QMutexLocker lock(internal::audioPlayerMutex()); if (!mixer()) return nullptr; auto track = mixer()->trackForType(audio.type()); if (!track || track->state.id != audio || !track->loading) { emit error(audio); LOG(("Audio Error: trying to load part of audio, that is not current at the moment")); err = SetupErrorNotPlaying; return nullptr; } bool isGoodId = false; AudioPlayerLoader *l = nullptr; switch (audio.type()) { case AudioMsgId::Type::Voice: l = _audioLoader.get(); isGoodId = (_audio == audio); break; case AudioMsgId::Type::Song: l = _songLoader.get(); isGoodId = (_song == audio); break; case AudioMsgId::Type::Video: l = _videoLoader.get(); isGoodId = (_video == audio); break; } if (l && (!isGoodId || !l->check(track->file, track->data))) { clear(audio.type()); l = nullptr; } if (!l) { std::unique_ptr<AudioPlayerLoader> *loader = nullptr; switch (audio.type()) { case AudioMsgId::Type::Voice: _audio = audio; loader = &_audioLoader; break; case AudioMsgId::Type::Song: _song = audio; loader = &_songLoader; break; case AudioMsgId::Type::Video: _video = audio; loader = &_videoLoader; break; } if (audio.playId()) { if (!track->videoData) { clear(audio.type()); track->state.state = State::StoppedAtError; emit error(audio); LOG(("Audio Error: video sound data not ready")); return nullptr; } *loader = std::make_unique<ChildFFMpegLoader>(std::move(track->videoData)); } else { *loader = std::make_unique<FFMpegLoader>(track->file, track->data, base::byte_vector()); } l = loader->get(); if (!l->open(positionMs)) { track->state.state = State::StoppedAtStart; return nullptr; } auto length = l->samplesCount(); if (length <= 0) { track->state.state = State::StoppedAtStart; return nullptr; } track->state.length = length; track->state.frequency = l->samplesFrequency(); err = SetupNoErrorStarted; } else if (track->loaded) { err = SetupErrorLoadedFull; LOG(("Audio Error: trying to load part of audio, that is already loaded to the end")); return nullptr; } return l; } Mixer::Track *Loaders::checkLoader(AudioMsgId::Type type) { if (!mixer()) return nullptr; auto track = mixer()->trackForType(type); auto isGoodId = false; AudioPlayerLoader *l = nullptr; switch (type) { case AudioMsgId::Type::Voice: l = _audioLoader.get(); isGoodId = (track->state.id == _audio); break; case AudioMsgId::Type::Song: l = _songLoader.get(); isGoodId = (track->state.id == _song); break; case AudioMsgId::Type::Video: l = _videoLoader.get(); isGoodId = (track->state.id == _video); break; } if (!l || !track) return nullptr; if (!isGoodId || !track->loading || !l->check(track->file, track->data)) { LOG(("Audio Error: playing changed while loading")); return nullptr; } return track; } void Loaders::onCancel(const AudioMsgId &audio) { switch (audio.type()) { case AudioMsgId::Type::Voice: if (_audio == audio) clear(audio.type()); break; case AudioMsgId::Type::Song: if (_song == audio) clear(audio.type()); break; case AudioMsgId::Type::Video: if (_video == audio) clear(audio.type()); break; } QMutexLocker lock(internal::audioPlayerMutex()); if (!mixer()) return; for (auto i = 0; i != kTogetherLimit; ++i) { auto track = mixer()->trackForType(audio.type(), i); if (track->state.id == audio) { track->loading = false; } } } } // namespace Player } // namespace Media �������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/media/media_audio_loaders.h������������������������������������0000664�0000000�0000000�00000003347�13262451251�0024633�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "media/media_child_ffmpeg_loader.h" #include "media/media_audio.h" #include "media/media_child_ffmpeg_loader.h" class AudioPlayerLoader; class ChildFFMpegLoader; namespace Media { namespace Player { class Loaders : public QObject { Q_OBJECT public: Loaders(QThread *thread); void feedFromVideo(VideoSoundPart &&part); ~Loaders(); signals: void error(const AudioMsgId &audio); void needToCheck(); public slots: void onInit(); void onStart(const AudioMsgId &audio, qint64 positionMs); void onLoad(const AudioMsgId &audio); void onCancel(const AudioMsgId &audio); private: void videoSoundAdded(); void clearFromVideoQueue(); AudioMsgId _audio, _song, _video; std::unique_ptr<AudioPlayerLoader> _audioLoader; std::unique_ptr<AudioPlayerLoader> _songLoader; std::unique_ptr<AudioPlayerLoader> _videoLoader; QMutex _fromVideoMutex; QMap<AudioMsgId, QQueue<FFMpeg::AVPacketDataWrap>> _fromVideoQueues; SingleQueuedInvokation _fromVideoNotify; void emitError(AudioMsgId::Type type); AudioMsgId clear(AudioMsgId::Type type); void setStoppedState(Mixer::Track *m, State state = State::Stopped); enum SetupError { SetupErrorAtStart = 0, SetupErrorNotPlaying = 1, SetupErrorLoadedFull = 2, SetupNoErrorStarted = 3, }; void loadData(AudioMsgId audio, TimeMs positionMs); AudioPlayerLoader *setupLoader( const AudioMsgId &audio, SetupError &err, TimeMs positionMs); Mixer::Track *checkLoader(AudioMsgId::Type type); }; } // namespace Player } // namespace Media �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/media/media_audio_track.cpp������������������������������������0000664�0000000�0000000�00000020465�13262451251�0024641�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "media/media_audio_track.h" #include "media/media_audio_ffmpeg_loader.h" #include "media/media_audio.h" #include "messenger.h" #include <AL/al.h> #include <AL/alc.h> #include <AL/alext.h> namespace Media { namespace Audio { namespace { constexpr auto kMaxFileSize = 10 * 1024 * 1024; constexpr auto kDetachDeviceTimeout = TimeMs(500); // destroy the audio device after 500ms of silence constexpr auto kTrackUpdateTimeout = TimeMs(100); ALuint CreateSource() { auto source = ALuint(0); alGenSources(1, &source); alSourcef(source, AL_PITCH, 1.f); alSourcef(source, AL_GAIN, 1.f); alSource3f(source, AL_POSITION, 0, 0, 0); alSource3f(source, AL_VELOCITY, 0, 0, 0); return source; } ALuint CreateBuffer() { auto buffer = ALuint(0); alGenBuffers(1, &buffer); return buffer; } } // namespace Track::Track(not_null<Instance*> instance) : _instance(instance) { _instance->registerTrack(this); } void Track::samplePeakEach(TimeMs peakDuration) { _peakDurationMs = peakDuration; } void Track::fillFromData(base::byte_vector &&data) { FFMpegLoader loader(FileLocation(), QByteArray(), std::move(data)); auto position = qint64(0); if (!loader.open(position)) { _failed = true; return; } auto format = loader.format(); _peakEachPosition = _peakDurationMs ? ((loader.samplesFrequency() * _peakDurationMs) / 1000) : 0; auto peaksCount = _peakEachPosition ? (loader.samplesCount() / _peakEachPosition) : 0; _peaks.reserve(peaksCount); auto peakValue = uint16(0); auto peakSamples = 0; auto peakEachSample = (format == AL_FORMAT_STEREO8 || format == AL_FORMAT_STEREO16) ? (_peakEachPosition * 2) : _peakEachPosition; _peakValueMin = 0x7FFF; _peakValueMax = 0; auto peakCallback = [this, &peakValue, &peakSamples, peakEachSample](uint16 sample) { accumulate_max(peakValue, sample); if (++peakSamples >= peakEachSample) { peakSamples -= peakEachSample; _peaks.push_back(peakValue); accumulate_max(_peakValueMax, peakValue); accumulate_min(_peakValueMin, peakValue); peakValue = 0; } }; do { auto buffer = QByteArray(); auto samplesAdded = int64(0); auto result = loader.readMore(buffer, samplesAdded); if (samplesAdded > 0) { auto sampleBytes = gsl::as_bytes(gsl::make_span(buffer)); _samplesCount += samplesAdded; _samples.insert(_samples.end(), sampleBytes.data(), sampleBytes.data() + sampleBytes.size()); if (peaksCount) { if (format == AL_FORMAT_MONO8 || format == AL_FORMAT_STEREO8) { Media::Audio::IterateSamples<uchar>(sampleBytes, peakCallback); } else if (format == AL_FORMAT_MONO16 || format == AL_FORMAT_STEREO16) { Media::Audio::IterateSamples<int16>(sampleBytes, peakCallback); } } } using Result = AudioPlayerLoader::ReadResult; switch (result) { case Result::Error: case Result::NotYet: case Result::Wait: { _failed = true; } break; } if (result != Result::Ok) { break; } } while (true); _alFormat = loader.format(); _sampleRate = loader.samplesFrequency(); _lengthMs = (loader.samplesCount() * TimeMs(1000)) / _sampleRate; } void Track::fillFromFile(const FileLocation &location) { if (location.accessEnable()) { fillFromFile(location.name()); location.accessDisable(); } else { LOG(("Track Error: Could not enable access to file '%1'.").arg(location.name())); _failed = true; } } void Track::fillFromFile(const QString &filePath) { QFile f(filePath); if (f.open(QIODevice::ReadOnly)) { auto size = f.size(); if (size > 0 && size <= kMaxFileSize) { auto bytes = base::byte_vector(size); if (f.read(reinterpret_cast<char*>(bytes.data()), bytes.size()) == bytes.size()) { fillFromData(std::move(bytes)); } else { LOG(("Track Error: Could not read %1 bytes from file '%2'.").arg(bytes.size()).arg(filePath)); _failed = true; } } else { LOG(("Track Error: Bad file '%1' size: %2.").arg(filePath).arg(size)); _failed = true; } } else { LOG(("Track Error: Could not open file '%1'.").arg(filePath)); _failed = true; } } void Track::playWithLooping(bool looping) { _active = true; if (failed() || _samples.empty()) { finish(); return; } ensureSourceCreated(); alSourceStop(_alSource); _looping = looping; alSourcei(_alSource, AL_LOOPING, _looping ? 1 : 0); alSourcef(_alSource, AL_GAIN, _volume); alSourcePlay(_alSource); _instance->trackStarted(this); } void Track::finish() { if (_active) { _active = false; _instance->trackFinished(this); } _alPosition = 0; } void Track::ensureSourceCreated() { if (alIsSource(_alSource)) { return; } { QMutexLocker lock(Player::internal::audioPlayerMutex()); if (!AttachToDevice()) { _failed = true; return; } } _alSource = CreateSource(); _alBuffer = CreateBuffer(); alBufferData(_alBuffer, _alFormat, _samples.data(), _samples.size(), _sampleRate); alSourcei(_alSource, AL_BUFFER, _alBuffer); } void Track::updateState() { if (!isActive() || !alIsSource(_alSource)) { return; } _stateUpdatedAt = getms(); auto state = ALint(0); alGetSourcei(_alSource, AL_SOURCE_STATE, &state); if (state != AL_PLAYING) { finish(); } else { auto currentPosition = ALint(0); alGetSourcei(_alSource, AL_SAMPLE_OFFSET, ¤tPosition); _alPosition = currentPosition; } } float64 Track::getPeakValue(TimeMs when) const { if (!isActive() || !_samplesCount || _peaks.empty() || _peakValueMin == _peakValueMax) { return 0.; } auto sampleIndex = (_alPosition + ((when - _stateUpdatedAt) * _sampleRate / 1000)); while (sampleIndex < 0) { sampleIndex += _samplesCount; } sampleIndex = sampleIndex % _samplesCount; auto peakIndex = (sampleIndex / _peakEachPosition) % _peaks.size(); return (_peaks[peakIndex] - _peakValueMin) / float64(_peakValueMax - _peakValueMin); } void Track::detachFromDevice() { if (alIsSource(_alSource)) { updateState(); alSourceStop(_alSource); alSourcei(_alSource, AL_BUFFER, AL_NONE); alDeleteBuffers(1, &_alBuffer); alDeleteSources(1, &_alSource); } _alBuffer = 0; _alSource = 0; } void Track::reattachToDevice() { if (!isActive() || alIsSource(_alSource)) { return; } ensureSourceCreated(); alSourcei(_alSource, AL_LOOPING, _looping ? 1 : 0); alSourcei(_alSource, AL_SAMPLE_OFFSET, static_cast<ALint>(_alPosition)); alSourcePlay(_alSource); } Track::~Track() { detachFromDevice(); _instance->unregisterTrack(this); } Instance::Instance() { _updateTimer.setCallback([this] { auto hasActive = false; for (auto track : _tracks) { track->updateState(); if (track->isActive()) { hasActive = true; } } if (hasActive) { Audio::StopDetachIfNotUsedSafe(); } }); _detachFromDeviceTimer.setCallback([this] { _detachFromDeviceForce = false; Player::internal::DetachFromDevice(); }); } std::unique_ptr<Track> Instance::createTrack() { return std::make_unique<Track>(this); } Instance::~Instance() { Expects(_tracks.empty()); } void Instance::registerTrack(Track *track) { _tracks.insert(track); } void Instance::unregisterTrack(Track *track) { _tracks.erase(track); } void Instance::trackStarted(Track *track) { stopDetachIfNotUsed(); if (!_updateTimer.isActive()) { _updateTimer.callEach(kTrackUpdateTimeout); } } void Instance::trackFinished(Track *track) { if (!hasActiveTracks()) { _updateTimer.cancel(); scheduleDetachIfNotUsed(); } if (track->isLooping()) { trackFinished().notify(track, true); } } void Instance::detachTracks() { for (auto track : _tracks) { track->detachFromDevice(); } } void Instance::reattachTracks() { if (!IsAttachedToDevice()) { return; } for (auto track : _tracks) { track->reattachToDevice(); } } bool Instance::hasActiveTracks() const { for (auto track : _tracks) { if (track->isActive()) { return true; } } return false; } void Instance::scheduleDetachFromDevice() { _detachFromDeviceForce = true; scheduleDetachIfNotUsed(); } void Instance::scheduleDetachIfNotUsed() { if (!_detachFromDeviceTimer.isActive()) { _detachFromDeviceTimer.callOnce(kDetachDeviceTimeout); } } void Instance::stopDetachIfNotUsed() { if (!_detachFromDeviceForce) { _detachFromDeviceTimer.cancel(); } } Instance &Current() { return Messenger::Instance().audio(); } } // namespace Audio } // namespace Media �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/media/media_audio_track.h��������������������������������������0000664�0000000�0000000�00000004504�13262451251�0024302�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "base/timer.h" namespace Media { namespace Audio { class Instance; class Track { public: Track(not_null<Instance*> instance); void samplePeakEach(TimeMs peakDuration); void fillFromData(base::byte_vector &&data); void fillFromFile(const FileLocation &location); void fillFromFile(const QString &filePath); void playOnce() { playWithLooping(false); } void playInLoop() { playWithLooping(true); } bool isLooping() const { return _looping; } bool isActive() const { return _active; } bool failed() const { return _failed; } int64 getLengthMs() const { return _lengthMs; } float64 getPeakValue(TimeMs when) const; void detachFromDevice(); void reattachToDevice(); void updateState(); ~Track(); private: void finish(); void ensureSourceCreated(); void playWithLooping(bool looping); not_null<Instance*> _instance; bool _failed = false; bool _active = false; bool _looping = false; float64 _volume = 1.; int64 _samplesCount = 0; int32 _sampleRate = 0; base::byte_vector _samples; TimeMs _peakDurationMs = 0; int _peakEachPosition = 0; std::vector<uint16> _peaks; uint16 _peakValueMin = 0; uint16 _peakValueMax = 0; TimeMs _lengthMs = 0; TimeMs _stateUpdatedAt = 0; int32 _alFormat = 0; int64 _alPosition = 0; uint32 _alSource = 0; uint32 _alBuffer = 0; }; class Instance { public: // Thread: Main. Instance(); std::unique_ptr<Track> createTrack(); base::Observable<Track*> &trackFinished() { return _trackFinished; } void detachTracks(); void reattachTracks(); bool hasActiveTracks() const; void scheduleDetachFromDevice(); void scheduleDetachIfNotUsed(); void stopDetachIfNotUsed(); ~Instance(); private: friend class Track; void registerTrack(Track *track); void unregisterTrack(Track *track); void trackStarted(Track *track); void trackFinished(Track *track); private: std::set<Track*> _tracks; base::Observable<Track*> _trackFinished; base::Timer _updateTimer; base::Timer _detachFromDeviceTimer; bool _detachFromDeviceForce = false; }; Instance &Current(); } // namespace Audio } // namespace Media ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/media/media_child_ffmpeg_loader.cpp����������������������������0000664�0000000�0000000�00000005427�13262451251�0026312�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "media/media_child_ffmpeg_loader.h" #include "core/crash_reports.h" namespace { constexpr AVSampleFormat AudioToFormat = AV_SAMPLE_FMT_S16; constexpr int64_t AudioToChannelLayout = AV_CH_LAYOUT_STEREO; constexpr int32 AudioToChannels = 2; bool IsPlanarFormat(int format) { return (format == AV_SAMPLE_FMT_U8P) || (format == AV_SAMPLE_FMT_S16P) || (format == AV_SAMPLE_FMT_S32P) || (format == AV_SAMPLE_FMT_FLTP) || (format == AV_SAMPLE_FMT_DBLP) || (format == AV_SAMPLE_FMT_S64P); } } // namespace VideoSoundData::~VideoSoundData() { if (context) { avcodec_close(context); avcodec_free_context(&context); context = nullptr; } } ChildFFMpegLoader::ChildFFMpegLoader(std::unique_ptr<VideoSoundData> &&data) : AbstractAudioFFMpegLoader( FileLocation(), QByteArray(), base::byte_vector()) , _parentData(std::move(data)) { } bool ChildFFMpegLoader::open(TimeMs positionMs) { return initUsingContext( _parentData->context, _parentData->length, _parentData->frequency); } AudioPlayerLoader::ReadResult ChildFFMpegLoader::readMore( QByteArray &result, int64 &samplesAdded) { const auto readResult = readFromReadyContext( _parentData->context, result, samplesAdded); if (readResult != ReadResult::Wait) { return readResult; } if (_queue.isEmpty()) { return _eofReached ? ReadResult::EndOfFile : ReadResult::Wait; } AVPacket packet; FFMpeg::packetFromDataWrap(packet, _queue.dequeue()); _eofReached = FFMpeg::isNullPacket(packet); if (_eofReached) { avcodec_send_packet(_parentData->context, nullptr); // drain return ReadResult::Ok; } auto res = avcodec_send_packet(_parentData->context, &packet); if (res < 0) { FFMpeg::freePacket(&packet); char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; LOG(("Audio Error: Unable to avcodec_send_packet() file '%1', data size '%2', error %3, %4").arg(_file.name()).arg(_data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); // There is a sample voice message where skipping such packet // results in a crash (read_access to nullptr) in swr_convert(). if (res == AVERROR_INVALIDDATA) { return ReadResult::NotYet; // try to skip bad packet } return ReadResult::Error; } FFMpeg::freePacket(&packet); return ReadResult::Ok; } void ChildFFMpegLoader::enqueuePackets(QQueue<FFMpeg::AVPacketDataWrap> &packets) { _queue += std::move(packets); packets.clear(); } ChildFFMpegLoader::~ChildFFMpegLoader() { for (auto &packetData : base::take(_queue)) { AVPacket packet; FFMpeg::packetFromDataWrap(packet, packetData); FFMpeg::freePacket(&packet); } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/media/media_child_ffmpeg_loader.h������������������������������0000664�0000000�0000000�00000004065�13262451251�0025754�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "media/media_audio_ffmpeg_loader.h" struct VideoSoundData { AVCodecContext *context = nullptr; int32 frequency = Media::Player::kDefaultFrequency; int64 length = 0; ~VideoSoundData(); }; struct VideoSoundPart { AVPacket *packet = nullptr; AudioMsgId audio; uint32 playId = 0; }; namespace FFMpeg { // AVPacket has a deprecated field, so when you copy an AVPacket // variable (e.g. inside QQueue), a compile warning is emited. // We wrap full AVPacket data in a new AVPacketDataWrap struct. // All other fields are copied from AVPacket without modifications. struct AVPacketDataWrap { char __data[sizeof(AVPacket)]; }; inline void packetFromDataWrap(AVPacket &packet, const AVPacketDataWrap &data) { memcpy(&packet, &data, sizeof(data)); } inline AVPacketDataWrap dataWrapFromPacket(const AVPacket &packet) { AVPacketDataWrap data; memcpy(&data, &packet, sizeof(data)); return data; } inline bool isNullPacket(const AVPacket &packet) { return packet.data == nullptr && packet.size == 0; } inline bool isNullPacket(const AVPacket *packet) { return isNullPacket(*packet); } inline void freePacket(AVPacket *packet) { if (!isNullPacket(packet)) { av_packet_unref(packet); } } } // namespace FFMpeg class ChildFFMpegLoader : public AbstractAudioFFMpegLoader { public: ChildFFMpegLoader(std::unique_ptr<VideoSoundData> &&data); bool open(TimeMs positionMs) override; bool check(const FileLocation &file, const QByteArray &data) override { return true; } ReadResult readMore(QByteArray &result, int64 &samplesAdded) override; void enqueuePackets(QQueue<FFMpeg::AVPacketDataWrap> &packets) override; bool eofReached() const { return _eofReached; } ~ChildFFMpegLoader(); private: std::unique_ptr<VideoSoundData> _parentData; QQueue<FFMpeg::AVPacketDataWrap> _queue; bool _eofReached = false; }; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp������������������������������������0000664�0000000�0000000�00000047434�13262451251�0024634�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "media/media_clip_ffmpeg.h" #include "media/media_audio.h" #include "media/media_child_ffmpeg_loader.h" #include "storage/file_download.h" namespace Media { namespace Clip { namespace internal { namespace { constexpr int kSkipInvalidDataPackets = 10; constexpr int kAlignImageBy = 16; void alignedImageBufferCleanupHandler(void *data) { auto buffer = static_cast<uchar*>(data); delete[] buffer; } // Create a QImage of desired size where all the data is aligned to 16 bytes. QImage createAlignedImage(QSize size) { auto width = size.width(); auto height = size.height(); auto widthalign = kAlignImageBy / 4; auto neededwidth = width + ((width % widthalign) ? (widthalign - (width % widthalign)) : 0); auto bytesperline = neededwidth * 4; auto buffer = new uchar[bytesperline * height + kAlignImageBy]; auto cleanupdata = static_cast<void*>(buffer); auto bufferval = reinterpret_cast<uintptr_t>(buffer); auto alignedbuffer = buffer + ((bufferval % kAlignImageBy) ? (kAlignImageBy - (bufferval % kAlignImageBy)) : 0); return QImage(alignedbuffer, width, height, bytesperline, QImage::Format_ARGB32, alignedImageBufferCleanupHandler, cleanupdata); } bool isAlignedImage(const QImage &image) { return !(reinterpret_cast<uintptr_t>(image.constBits()) % kAlignImageBy) && !(image.bytesPerLine() % kAlignImageBy); } } // namespace FFMpegReaderImplementation::FFMpegReaderImplementation(FileLocation *location, QByteArray *data, const AudioMsgId &audio) : ReaderImplementation(location, data) , _audioMsgId(audio) { _frame = av_frame_alloc(); av_init_packet(&_packetNull); _packetNull.data = nullptr; _packetNull.size = 0; } ReaderImplementation::ReadResult FFMpegReaderImplementation::readNextFrame() { if (_frameRead) { av_frame_unref(_frame); _frameRead = false; } do { int res = avcodec_receive_frame(_codecContext, _frame); if (res >= 0) { processReadFrame(); return ReadResult::Success; } if (res == AVERROR_EOF) { clearPacketQueue(); if (_mode == Mode::Normal) { return ReadResult::EndOfFile; } if (!_hadFrame) { LOG(("Gif Error: Got EOF before a single frame was read!")); return ReadResult::Error; } if ((res = avformat_seek_file(_fmtContext, _streamId, std::numeric_limits<int64_t>::min(), 0, std::numeric_limits<int64_t>::max(), 0)) < 0) { if ((res = av_seek_frame(_fmtContext, _streamId, 0, AVSEEK_FLAG_BYTE)) < 0) { if ((res = av_seek_frame(_fmtContext, _streamId, 0, AVSEEK_FLAG_FRAME)) < 0) { if ((res = av_seek_frame(_fmtContext, _streamId, 0, 0)) < 0) { char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; LOG(("Gif Error: Unable to av_seek_frame() to the start %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); return ReadResult::Error; } } } } avcodec_flush_buffers(_codecContext); _hadFrame = false; _frameMs = 0; _lastReadVideoMs = _lastReadAudioMs = 0; _skippedInvalidDataPackets = 0; continue; } else if (res != AVERROR(EAGAIN)) { char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; LOG(("Gif Error: Unable to avcodec_receive_frame() %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); return ReadResult::Error; } while (_packetQueue.isEmpty()) { auto packetResult = readAndProcessPacket(); if (packetResult == PacketResult::Error) { return ReadResult::Error; } else if (packetResult == PacketResult::EndOfFile) { break; } } if (_packetQueue.isEmpty()) { avcodec_send_packet(_codecContext, nullptr); // drain continue; } startPacket(); AVPacket packet; FFMpeg::packetFromDataWrap(packet, _packetQueue.head()); res = avcodec_send_packet(_codecContext, &packet); if (res < 0) { finishPacket(); char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; LOG(("Gif Error: Unable to avcodec_send_packet() %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); if (res == AVERROR_INVALIDDATA) { if (++_skippedInvalidDataPackets < kSkipInvalidDataPackets) { continue; // try to skip bad packet } } return ReadResult::Error; } finishPacket(); } while (true); return ReadResult::Error; } void FFMpegReaderImplementation::processReadFrame() { int64 duration = _frame->pkt_duration; int64 framePts = _frame->pts; TimeMs frameMs = (framePts * 1000LL * _fmtContext->streams[_streamId]->time_base.num) / _fmtContext->streams[_streamId]->time_base.den; _currentFrameDelay = _nextFrameDelay; if (_frameMs + _currentFrameDelay < frameMs) { _currentFrameDelay = int32(frameMs - _frameMs); } else if (frameMs < _frameMs + _currentFrameDelay) { frameMs = _frameMs + _currentFrameDelay; } if (duration == AV_NOPTS_VALUE) { _nextFrameDelay = 0; } else { _nextFrameDelay = (duration * 1000LL * _fmtContext->streams[_streamId]->time_base.num) / _fmtContext->streams[_streamId]->time_base.den; } _frameMs = frameMs; _hadFrame = _frameRead = true; _frameTime += _currentFrameDelay; } ReaderImplementation::ReadResult FFMpegReaderImplementation::readFramesTill(TimeMs frameMs, TimeMs systemMs) { if (_audioStreamId < 0) { // just keep up if (_frameRead && _frameTime > frameMs) { return ReadResult::Success; } auto readResult = readNextFrame(); if (readResult != ReadResult::Success || _frameTime > frameMs) { return readResult; } readResult = readNextFrame(); if (_frameTime <= frameMs) { _frameTime = frameMs + 5; // keep up } return readResult; } // sync by audio stream auto correctMs = (frameMs >= 0) ? Player::mixer()->getVideoCorrectedTime(_audioMsgId, frameMs, systemMs) : frameMs; if (!_frameRead) { auto readResult = readNextFrame(); if (readResult != ReadResult::Success) { return readResult; } } while (_frameTime <= correctMs) { auto readResult = readNextFrame(); if (readResult != ReadResult::Success) { return readResult; } } if (frameMs >= 0) { _frameTimeCorrection = frameMs - correctMs; } return ReadResult::Success; } TimeMs FFMpegReaderImplementation::frameRealTime() const { return _frameMs; } TimeMs FFMpegReaderImplementation::framePresentationTime() const { return qMax(_frameTime + _frameTimeCorrection, 0LL); } TimeMs FFMpegReaderImplementation::durationMs() const { if (_fmtContext->streams[_streamId]->duration == AV_NOPTS_VALUE) return 0; return (_fmtContext->streams[_streamId]->duration * 1000LL * _fmtContext->streams[_streamId]->time_base.num) / _fmtContext->streams[_streamId]->time_base.den; } bool FFMpegReaderImplementation::renderFrame(QImage &to, bool &hasAlpha, const QSize &size) { Expects(_frameRead); _frameRead = false; if (!_width || !_height) { _width = _frame->width; _height = _frame->height; if (!_width || !_height) { LOG(("Gif Error: Bad frame size %1").arg(logData())); return false; } } QSize toSize(size.isEmpty() ? QSize(_width, _height) : size); if (!size.isEmpty() && rotationSwapWidthHeight()) { toSize.transpose(); } if (to.isNull() || to.size() != toSize || !to.isDetached() || !isAlignedImage(to)) { to = createAlignedImage(toSize); } hasAlpha = (_frame->format == AV_PIX_FMT_BGRA || (_frame->format == -1 && _codecContext->pix_fmt == AV_PIX_FMT_BGRA)); if (_frame->width == toSize.width() && _frame->height == toSize.height() && hasAlpha) { int32 sbpl = _frame->linesize[0], dbpl = to.bytesPerLine(), bpl = qMin(sbpl, dbpl); uchar *s = _frame->data[0], *d = to.bits(); for (int32 i = 0, l = _frame->height; i < l; ++i) { memcpy(d + i * dbpl, s + i * sbpl, bpl); } } else { if ((_swsSize != toSize) || (_frame->format != -1 && _frame->format != _codecContext->pix_fmt) || !_swsContext) { _swsSize = toSize; _swsContext = sws_getCachedContext(_swsContext, _frame->width, _frame->height, AVPixelFormat(_frame->format), toSize.width(), toSize.height(), AV_PIX_FMT_BGRA, 0, 0, 0, 0); } // AV_NUM_DATA_POINTERS defined in AVFrame struct uint8_t *toData[AV_NUM_DATA_POINTERS] = { to.bits(), nullptr }; int toLinesize[AV_NUM_DATA_POINTERS] = { to.bytesPerLine(), 0 }; int res; if ((res = sws_scale(_swsContext, _frame->data, _frame->linesize, 0, _frame->height, toData, toLinesize)) != _swsSize.height()) { LOG(("Gif Error: Unable to sws_scale to good size %1, height %2, should be %3").arg(logData()).arg(res).arg(_swsSize.height())); return false; } } if (_rotation != Rotation::None) { QTransform rotationTransform; switch (_rotation) { case Rotation::Degrees90: rotationTransform.rotate(90); break; case Rotation::Degrees180: rotationTransform.rotate(180); break; case Rotation::Degrees270: rotationTransform.rotate(270); break; } to = to.transformed(rotationTransform); } // Read some future packets for audio stream. if (_audioStreamId >= 0) { while (_frameMs + 5000 > _lastReadAudioMs && _frameMs + 15000 > _lastReadVideoMs) { auto packetResult = readAndProcessPacket(); if (packetResult != PacketResult::Ok) { break; } } } av_frame_unref(_frame); return true; } FFMpegReaderImplementation::Rotation FFMpegReaderImplementation::rotationFromDegrees(int degrees) const { switch (degrees) { case 90: return Rotation::Degrees90; case 180: return Rotation::Degrees180; case 270: return Rotation::Degrees270; } return Rotation::None; } bool FFMpegReaderImplementation::start(Mode mode, TimeMs &positionMs) { _mode = mode; initDevice(); if (!_device->open(QIODevice::ReadOnly)) { LOG(("Gif Error: Unable to open device %1").arg(logData())); return false; } _ioBuffer = (uchar*)av_malloc(AVBlockSize); _ioContext = avio_alloc_context(_ioBuffer, AVBlockSize, 0, static_cast<void*>(this), &FFMpegReaderImplementation::_read, 0, &FFMpegReaderImplementation::_seek); _fmtContext = avformat_alloc_context(); if (!_fmtContext) { LOG(("Gif Error: Unable to avformat_alloc_context %1").arg(logData())); return false; } _fmtContext->pb = _ioContext; int res = 0; char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; if ((res = avformat_open_input(&_fmtContext, 0, 0, 0)) < 0) { _ioBuffer = 0; LOG(("Gif Error: Unable to avformat_open_input %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); return false; } _opened = true; if ((res = avformat_find_stream_info(_fmtContext, 0)) < 0) { LOG(("Gif Error: Unable to avformat_find_stream_info %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); return false; } _streamId = av_find_best_stream(_fmtContext, AVMEDIA_TYPE_VIDEO, -1, -1, 0, 0); if (_streamId < 0) { LOG(("Gif Error: Unable to av_find_best_stream %1, error %2, %3").arg(logData()).arg(_streamId).arg(av_make_error_string(err, sizeof(err), _streamId))); return false; } _packetNull.stream_index = _streamId; auto rotateTag = av_dict_get(_fmtContext->streams[_streamId]->metadata, "rotate", NULL, 0); if (rotateTag && *rotateTag->value) { auto stringRotateTag = QString::fromUtf8(rotateTag->value); auto toIntSucceeded = false; auto rotateDegrees = stringRotateTag.toInt(&toIntSucceeded); if (toIntSucceeded) { _rotation = rotationFromDegrees(rotateDegrees); } } _codecContext = avcodec_alloc_context3(nullptr); if (!_codecContext) { LOG(("Gif Error: Unable to avcodec_alloc_context3 %1").arg(logData())); return false; } if ((res = avcodec_parameters_to_context(_codecContext, _fmtContext->streams[_streamId]->codecpar)) < 0) { LOG(("Gif Error: Unable to avcodec_parameters_to_context %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); return false; } av_codec_set_pkt_timebase(_codecContext, _fmtContext->streams[_streamId]->time_base); av_opt_set_int(_codecContext, "refcounted_frames", 1, 0); _codec = avcodec_find_decoder(_codecContext->codec_id); _audioStreamId = av_find_best_stream(_fmtContext, AVMEDIA_TYPE_AUDIO, -1, -1, 0, 0); if (_mode == Mode::Inspecting) { _hasAudioStream = (_audioStreamId >= 0); _audioStreamId = -1; } else if (_mode == Mode::Silent || !_audioMsgId.playId()) { _audioStreamId = -1; } if ((res = avcodec_open2(_codecContext, _codec, 0)) < 0) { LOG(("Gif Error: Unable to avcodec_open2 %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); return false; } std::unique_ptr<VideoSoundData> soundData; if (_audioStreamId >= 0) { auto audioContext = avcodec_alloc_context3(nullptr); if (!audioContext) { LOG(("Audio Error: Unable to avcodec_alloc_context3 %1").arg(logData())); return false; } if ((res = avcodec_parameters_to_context(audioContext, _fmtContext->streams[_audioStreamId]->codecpar)) < 0) { LOG(("Audio Error: Unable to avcodec_parameters_to_context %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); return false; } av_codec_set_pkt_timebase(audioContext, _fmtContext->streams[_audioStreamId]->time_base); av_opt_set_int(audioContext, "refcounted_frames", 1, 0); auto audioCodec = avcodec_find_decoder(audioContext->codec_id); if ((res = avcodec_open2(audioContext, audioCodec, 0)) < 0) { avcodec_free_context(&audioContext); LOG(("Gif Error: Unable to avcodec_open2 %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); _audioStreamId = -1; } else { soundData = std::make_unique<VideoSoundData>(); soundData->context = audioContext; soundData->frequency = _fmtContext->streams[_audioStreamId]->codecpar->sample_rate; if (_fmtContext->streams[_audioStreamId]->duration == AV_NOPTS_VALUE) { soundData->length = (_fmtContext->duration * soundData->frequency) / AV_TIME_BASE; } else { soundData->length = (_fmtContext->streams[_audioStreamId]->duration * soundData->frequency * _fmtContext->streams[_audioStreamId]->time_base.num) / _fmtContext->streams[_audioStreamId]->time_base.den; } } } if (positionMs > 0) { const auto timeBase = _fmtContext->streams[_streamId]->time_base; const auto timeStamp = (positionMs * timeBase.den) / (1000LL * timeBase.num); if (av_seek_frame(_fmtContext, _streamId, timeStamp, 0) < 0) { if (av_seek_frame(_fmtContext, _streamId, timeStamp, AVSEEK_FLAG_BACKWARD) < 0) { return false; } } } AVPacket packet; auto readResult = readPacket(&packet); if (readResult == PacketResult::Ok && positionMs > 0) { positionMs = countPacketMs(&packet); } if (hasAudio()) { Player::mixer()->play(_audioMsgId, std::move(soundData), positionMs); } if (readResult == PacketResult::Ok) { processPacket(&packet); } return true; } bool FFMpegReaderImplementation::inspectAt(TimeMs &positionMs) { if (positionMs > 0) { const auto timeBase = _fmtContext->streams[_streamId]->time_base; const auto timeStamp = (positionMs * timeBase.den) / (1000LL * timeBase.num); if (av_seek_frame(_fmtContext, _streamId, timeStamp, 0) < 0) { if (av_seek_frame(_fmtContext, _streamId, timeStamp, AVSEEK_FLAG_BACKWARD) < 0) { return false; } } } _packetQueue.clear(); AVPacket packet; auto readResult = readPacket(&packet); if (readResult == PacketResult::Ok && positionMs > 0) { positionMs = countPacketMs(&packet); } if (readResult == PacketResult::Ok) { processPacket(&packet); } return true; } bool FFMpegReaderImplementation::isGifv() const { if (_hasAudioStream) { return false; } if (dataSize() > Storage::kMaxAnimationInMemory) { return false; } if (_codecContext->codec_id != AV_CODEC_ID_H264) { return false; } return true; } QString FFMpegReaderImplementation::logData() const { return qsl("for file '%1', data size '%2'").arg(_location ? _location->name() : QString()).arg(_data->size()); } FFMpegReaderImplementation::~FFMpegReaderImplementation() { clearPacketQueue(); if (_frameRead) { av_frame_unref(_frame); _frameRead = false; } if (_codecContext) avcodec_free_context(&_codecContext); if (_swsContext) sws_freeContext(_swsContext); if (_opened) { avformat_close_input(&_fmtContext); } if (_ioContext) { av_freep(&_ioContext->buffer); av_freep(&_ioContext); } else if (_ioBuffer) { av_freep(&_ioBuffer); } if (_fmtContext) avformat_free_context(_fmtContext); av_frame_free(&_frame); } FFMpegReaderImplementation::PacketResult FFMpegReaderImplementation::readPacket(AVPacket *packet) { av_init_packet(packet); packet->data = nullptr; packet->size = 0; int res = 0; if ((res = av_read_frame(_fmtContext, packet)) < 0) { if (res == AVERROR_EOF) { if (_audioStreamId >= 0) { // queue terminating packet to audio player VideoSoundPart part; part.packet = &_packetNull; part.audio = _audioMsgId; Player::mixer()->feedFromVideo(std::move(part)); } return PacketResult::EndOfFile; } char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; LOG(("Gif Error: Unable to av_read_frame() %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); return PacketResult::Error; } return PacketResult::Ok; } void FFMpegReaderImplementation::processPacket(AVPacket *packet) { auto videoPacket = (packet->stream_index == _streamId); auto audioPacket = (_audioStreamId >= 0 && packet->stream_index == _audioStreamId); if (audioPacket || videoPacket) { if (videoPacket) { _lastReadVideoMs = countPacketMs(packet); _packetQueue.enqueue(FFMpeg::dataWrapFromPacket(*packet)); } else if (audioPacket) { _lastReadAudioMs = countPacketMs(packet); // queue packet to audio player VideoSoundPart part; part.packet = packet; part.audio = _audioMsgId; Player::mixer()->feedFromVideo(std::move(part)); } } else { av_packet_unref(packet); } } TimeMs FFMpegReaderImplementation::countPacketMs(AVPacket *packet) const { int64 packetPts = (packet->pts == AV_NOPTS_VALUE) ? packet->dts : packet->pts; TimeMs packetMs = (packetPts * 1000LL * _fmtContext->streams[packet->stream_index]->time_base.num) / _fmtContext->streams[packet->stream_index]->time_base.den; return packetMs; } FFMpegReaderImplementation::PacketResult FFMpegReaderImplementation::readAndProcessPacket() { AVPacket packet; auto result = readPacket(&packet); if (result == PacketResult::Ok) { processPacket(&packet); } return result; } void FFMpegReaderImplementation::startPacket() { if (!_packetStarted && !_packetQueue.isEmpty()) { AVPacket packet; FFMpeg::packetFromDataWrap(packet, _packetQueue.head()); _packetStartedSize = packet.size; _packetStartedData = packet.data; _packetStarted = true; } } void FFMpegReaderImplementation::finishPacket() { if (_packetStarted) { AVPacket packet; FFMpeg::packetFromDataWrap(packet, _packetQueue.head()); packet.size = _packetStartedSize; packet.data = _packetStartedData; _packetStarted = false; av_packet_unref(&packet); _packetQueue.dequeue(); } } void FFMpegReaderImplementation::clearPacketQueue() { finishPacket(); auto packets = base::take(_packetQueue); for (auto &packetData : packets) { AVPacket packet; FFMpeg::packetFromDataWrap(packet, packetData); av_packet_unref(&packet); } } int FFMpegReaderImplementation::_read(void *opaque, uint8_t *buf, int buf_size) { FFMpegReaderImplementation *l = reinterpret_cast<FFMpegReaderImplementation*>(opaque); return int(l->_device->read((char*)(buf), buf_size)); } int64_t FFMpegReaderImplementation::_seek(void *opaque, int64_t offset, int whence) { FFMpegReaderImplementation *l = reinterpret_cast<FFMpegReaderImplementation*>(opaque); switch (whence) { case SEEK_SET: return l->_device->seek(offset) ? l->_device->pos() : -1; case SEEK_CUR: return l->_device->seek(l->_device->pos() + offset) ? l->_device->pos() : -1; case SEEK_END: return l->_device->seek(l->_device->size() + offset) ? l->_device->pos() : -1; case AVSEEK_SIZE: { // Special whence for determining filesize without any seek. return l->_dataSize; } break; } return -1; } } // namespace internal } // namespace Clip } // namespace Media ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/media/media_clip_ffmpeg.h��������������������������������������0000664�0000000�0000000�00000005564�13262451251�0024277�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once extern "C" { #include <libswscale/swscale.h> } // extern "C" #include "media/media_clip_implementation.h" #include "media/media_child_ffmpeg_loader.h" namespace Media { namespace Clip { namespace internal { class FFMpegReaderImplementation : public ReaderImplementation { public: FFMpegReaderImplementation(FileLocation *location, QByteArray *data, const AudioMsgId &audio); ReadResult readFramesTill(TimeMs frameMs, TimeMs systemMs) override; TimeMs frameRealTime() const override; TimeMs framePresentationTime() const override; bool renderFrame(QImage &to, bool &hasAlpha, const QSize &size) override; TimeMs durationMs() const override; bool hasAudio() const override { return (_audioStreamId >= 0); } bool start(Mode mode, TimeMs &positionMs) override; bool inspectAt(TimeMs &positionMs); QString logData() const; bool isGifv() const; ~FFMpegReaderImplementation(); private: ReadResult readNextFrame(); void processReadFrame(); enum class PacketResult { Ok, EndOfFile, Error, }; PacketResult readPacket(AVPacket *packet); void processPacket(AVPacket *packet); TimeMs countPacketMs(AVPacket *packet) const; PacketResult readAndProcessPacket(); enum class Rotation { None, Degrees90, Degrees180, Degrees270, }; Rotation rotationFromDegrees(int degrees) const; bool rotationSwapWidthHeight() const { return (_rotation == Rotation::Degrees90) || (_rotation == Rotation::Degrees270); } void startPacket(); void finishPacket(); void clearPacketQueue(); static int _read(void *opaque, uint8_t *buf, int buf_size); static int64_t _seek(void *opaque, int64_t offset, int whence); Mode _mode = Mode::Normal; Rotation _rotation = Rotation::None; uchar *_ioBuffer = nullptr; AVIOContext *_ioContext = nullptr; AVFormatContext *_fmtContext = nullptr; AVCodec *_codec = nullptr; AVCodecContext *_codecContext = nullptr; int _streamId = 0; AVFrame *_frame = nullptr; bool _opened = false; bool _hadFrame = false; bool _frameRead = false; int _skippedInvalidDataPackets = 0; bool _hasAudioStream = false; int _audioStreamId = -1; AudioMsgId _audioMsgId; TimeMs _lastReadVideoMs = 0; TimeMs _lastReadAudioMs = 0; QQueue<FFMpeg::AVPacketDataWrap> _packetQueue; AVPacket _packetNull; // for final decoding int _packetStartedSize = 0; uint8_t *_packetStartedData = nullptr; bool _packetStarted = false; int _width = 0; int _height = 0; SwsContext *_swsContext = nullptr; QSize _swsSize; TimeMs _frameMs = 0; int _nextFrameDelay = 0; int _currentFrameDelay = 0; TimeMs _frameTime = 0; TimeMs _frameTimeCorrection = 0; }; } // namespace internal } // namespace Clip } // namespace Media ��������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/media/media_clip_implementation.cpp����������������������������0000664�0000000�0000000�00000001431�13262451251�0026400�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "media/media_clip_implementation.h" namespace Media { namespace Clip { namespace internal { void ReaderImplementation::initDevice() { if (_data->isEmpty()) { if (_file.isOpen()) _file.close(); _file.setFileName(_location->name()); _dataSize = _file.size(); } else { if (_buffer.isOpen()) _buffer.close(); _buffer.setBuffer(_data); _dataSize = _data->size(); } _device = _data->isEmpty() ? static_cast<QIODevice*>(&_file) : static_cast<QIODevice*>(&_buffer); } } // namespace internal } // namespace Clip } // namespace Media ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/media/media_clip_implementation.h������������������������������0000664�0000000�0000000�00000003006�13262451251�0026045�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once class FileLocation; namespace Media { namespace Clip { namespace internal { class ReaderImplementation { public: ReaderImplementation(FileLocation *location, QByteArray *data) : _location(location) , _data(data) { } enum class Mode { Silent, Normal, Inspecting, // Not playing video, but reading data. }; enum class ReadResult { Success, Error, EndOfFile, }; // Read frames till current frame will have presentation time > frameMs, systemMs = getms(). virtual ReadResult readFramesTill(TimeMs frameMs, TimeMs systemMs) = 0; // Get current frame real and presentation time. virtual TimeMs frameRealTime() const = 0; virtual TimeMs framePresentationTime() const = 0; // Render current frame to an image with specific size. virtual bool renderFrame(QImage &to, bool &hasAlpha, const QSize &size) = 0; virtual TimeMs durationMs() const = 0; virtual bool hasAudio() const = 0; virtual bool start(Mode mode, TimeMs &positionMs) = 0; virtual ~ReaderImplementation() { } int64 dataSize() const { return _dataSize; } protected: FileLocation *_location; QByteArray *_data; QFile _file; QBuffer _buffer; QIODevice *_device = nullptr; int64 _dataSize = 0; void initDevice(); }; } // namespace internal } // namespace Clip } // namespace Media ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/media/media_clip_qtgif.cpp�������������������������������������0000664�0000000�0000000�00000006377�13262451251�0024503�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "media/media_clip_qtgif.h" namespace Media { namespace Clip { namespace internal { QtGifReaderImplementation::QtGifReaderImplementation(FileLocation *location, QByteArray *data) : ReaderImplementation(location, data) { } ReaderImplementation::ReadResult QtGifReaderImplementation::readFramesTill(TimeMs frameMs, TimeMs systemMs) { if (!_frame.isNull() && _frameTime > frameMs) { return ReadResult::Success; } auto readResult = readNextFrame(); if (readResult != ReadResult::Success || _frameTime > frameMs) { return readResult; } readResult = readNextFrame(); if (_frameTime <= frameMs) { _frameTime = frameMs + 5; // keep up } return readResult; } TimeMs QtGifReaderImplementation::frameRealTime() const { return _frameRealTime; } TimeMs QtGifReaderImplementation::framePresentationTime() const { return qMax(_frameTime, 0LL); } ReaderImplementation::ReadResult QtGifReaderImplementation::readNextFrame() { if (_reader) _frameDelay = _reader->nextImageDelay(); if (_framesLeft < 1) { if (_mode == Mode::Normal) { return ReadResult::EndOfFile; } else if (!jumpToStart()) { return ReadResult::Error; } } _frame = QImage(); // QGifHandler always reads first to internal QImage and returns it if (!_reader->read(&_frame) || _frame.isNull()) { return ReadResult::Error; } --_framesLeft; _frameTime += _frameDelay; _frameRealTime += _frameDelay; return ReadResult::Success; } bool QtGifReaderImplementation::renderFrame(QImage &to, bool &hasAlpha, const QSize &size) { Assert(!_frame.isNull()); if (size.isEmpty() || size == _frame.size()) { int32 w = _frame.width(), h = _frame.height(); if (to.width() == w && to.height() == h && to.format() == _frame.format()) { if (to.byteCount() != _frame.byteCount()) { int bpl = qMin(to.bytesPerLine(), _frame.bytesPerLine()); for (int i = 0; i < h; ++i) { memcpy(to.scanLine(i), _frame.constScanLine(i), bpl); } } else { memcpy(to.bits(), _frame.constBits(), _frame.byteCount()); } } else { to = _frame.copy(); } } else { to = _frame.scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } hasAlpha = _frame.hasAlphaChannel(); _frame = QImage(); return true; } TimeMs QtGifReaderImplementation::durationMs() const { return 0; // not supported } bool QtGifReaderImplementation::start(Mode mode, TimeMs &positionMs) { if (mode == Mode::Inspecting) { return false; } _mode = mode; return jumpToStart(); } QtGifReaderImplementation::~QtGifReaderImplementation() = default; bool QtGifReaderImplementation::jumpToStart() { if (_reader && _reader->jumpToImage(0)) { _framesLeft = _reader->imageCount(); return true; } _reader = nullptr; initDevice(); _reader = std::make_unique<QImageReader>(_device); #ifndef OS_MAC_OLD _reader->setAutoTransform(true); #endif // OS_MAC_OLD if (!_reader->canRead() || !_reader->supportsAnimation()) { return false; } _framesLeft = _reader->imageCount(); if (_framesLeft < 1) { return false; } return true; } } // namespace internal } // namespace Clip } // namespace Media �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/media/media_clip_qtgif.h���������������������������������������0000664�0000000�0000000�00000002311�13262451251�0024130�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "media/media_clip_implementation.h" namespace Media { namespace Clip { namespace internal { class QtGifReaderImplementation : public ReaderImplementation { public: QtGifReaderImplementation(FileLocation *location, QByteArray *data); ReadResult readFramesTill(TimeMs frameMs, TimeMs systemMs) override; TimeMs frameRealTime() const override; TimeMs framePresentationTime() const override; bool renderFrame(QImage &to, bool &hasAlpha, const QSize &size) override; TimeMs durationMs() const override; bool hasAudio() const override { return false; } bool start(Mode mode, TimeMs &positionMs) override; ~QtGifReaderImplementation(); private: bool jumpToStart(); ReadResult readNextFrame(); Mode _mode = Mode::Normal; std::unique_ptr<QImageReader> _reader; int _framesLeft = 0; TimeMs _frameRealTime = 0; TimeMs _frameTime = 0; int _frameDelay = 0; QImage _frame; }; } // namespace internal } // namespace Clip } // namespace Media �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/media/media_clip_reader.cpp������������������������������������0000664�0000000�0000000�00000065174�13262451251�0024633�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "media/media_clip_reader.h" #include "data/data_document.h" #include "storage/file_download.h" #include "media/media_clip_ffmpeg.h" #include "media/media_clip_qtgif.h" #include "mainwidget.h" #include "mainwindow.h" extern "C" { #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libavutil/opt.h> #include <libswscale/swscale.h> } namespace Media { namespace Clip { namespace { QVector<QThread*> threads; QVector<Manager*> managers; QImage PrepareFrameImage(const FrameRequest &request, const QImage &original, bool hasAlpha, QImage &cache) { auto needResize = (original.width() != request.framew) || (original.height() != request.frameh); auto needOuterFill = (request.outerw != request.framew) || (request.outerh != request.frameh); auto needRounding = (request.radius != ImageRoundRadius::None); if (!needResize && !needOuterFill && !hasAlpha && !needRounding) { return original; } auto factor = request.factor; auto needNewCache = (cache.width() != request.outerw || cache.height() != request.outerh); if (needNewCache) { cache = QImage(request.outerw, request.outerh, QImage::Format_ARGB32_Premultiplied); cache.setDevicePixelRatio(factor); } { Painter p(&cache); if (needNewCache) { if (request.framew < request.outerw) { p.fillRect(0, 0, (request.outerw - request.framew) / (2 * factor), cache.height() / factor, st::imageBg); p.fillRect((request.outerw - request.framew) / (2 * factor) + (request.framew / factor), 0, (cache.width() / factor) - ((request.outerw - request.framew) / (2 * factor) + (request.framew / factor)), cache.height() / factor, st::imageBg); } if (request.frameh < request.outerh) { p.fillRect(qMax(0, (request.outerw - request.framew) / (2 * factor)), 0, qMin(cache.width(), request.framew) / factor, (request.outerh - request.frameh) / (2 * factor), st::imageBg); p.fillRect(qMax(0, (request.outerw - request.framew) / (2 * factor)), (request.outerh - request.frameh) / (2 * factor) + (request.frameh / factor), qMin(cache.width(), request.framew) / factor, (cache.height() / factor) - ((request.outerh - request.frameh) / (2 * factor) + (request.frameh / factor)), st::imageBg); } } if (hasAlpha) { p.fillRect(qMax(0, (request.outerw - request.framew) / (2 * factor)), qMax(0, (request.outerh - request.frameh) / (2 * factor)), qMin(cache.width(), request.framew) / factor, qMin(cache.height(), request.frameh) / factor, st::imageBgTransparent); } auto position = QPoint((request.outerw - request.framew) / (2 * factor), (request.outerh - request.frameh) / (2 * factor)); if (needResize) { PainterHighQualityEnabler hq(p); auto dst = QRect(position, QSize(request.framew / factor, request.frameh / factor)); auto src = QRect(0, 0, original.width(), original.height()); p.drawImage(dst, original, src, Qt::ColorOnly); } else { p.drawImage(position, original); } } if (needRounding) { Images::prepareRound(cache, request.radius, request.corners); } return cache; } QPixmap PrepareFrame(const FrameRequest &request, const QImage &original, bool hasAlpha, QImage &cache) { return QPixmap::fromImage(PrepareFrameImage(request, original, hasAlpha, cache), Qt::ColorOnly); } } // namespace Reader::Reader(const QString &filepath, Callback &&callback, Mode mode, int64 seekMs) : _callback(std::move(callback)) , _mode(mode) , _seekPositionMs(seekMs) { init(FileLocation(filepath), QByteArray()); } Reader::Reader(not_null<DocumentData*> document, FullMsgId msgId, Callback &&callback, Mode mode, int64 seekMs) : _callback(std::move(callback)) , _mode(mode) , _audioMsgId(document, msgId, (mode == Mode::Video) ? rand_value<uint32>() : 0) , _seekPositionMs(seekMs) { init(document->location(), document->data()); } void Reader::init(const FileLocation &location, const QByteArray &data) { if (threads.size() < ClipThreadsCount) { _threadIndex = threads.size(); threads.push_back(new QThread()); managers.push_back(new Manager(threads.back())); threads.back()->start(); } else { _threadIndex = int32(rand_value<uint32>() % threads.size()); int32 loadLevel = 0x7FFFFFFF; for (int32 i = 0, l = threads.size(); i < l; ++i) { int32 level = managers.at(i)->loadLevel(); if (level < loadLevel) { _threadIndex = i; loadLevel = level; } } } managers.at(_threadIndex)->append(this, location, data); } Reader::Frame *Reader::frameToShow(int32 *index) const { // 0 means not ready int step = _step.loadAcquire(), i; if (step == WaitingForDimensionsStep) { if (index) *index = 0; return nullptr; } else if (step == WaitingForRequestStep) { i = 0; } else if (step == WaitingForFirstFrameStep) { i = 0; } else { i = (step / 2) % 3; } if (index) *index = i; return _frames + i; } Reader::Frame *Reader::frameToWrite(int32 *index) const { // 0 means not ready int32 step = _step.loadAcquire(), i; if (step == WaitingForDimensionsStep) { i = 0; } else if (step == WaitingForRequestStep) { if (index) *index = 0; return nullptr; } else if (step == WaitingForFirstFrameStep) { i = 0; } else { i = ((step + 2) / 2) % 3; } if (index) *index = i; return _frames + i; } Reader::Frame *Reader::frameToWriteNext(bool checkNotWriting, int32 *index) const { int32 step = _step.loadAcquire(), i; if (step == WaitingForDimensionsStep || step == WaitingForRequestStep || (checkNotWriting && (step % 2))) { if (index) *index = 0; return nullptr; } i = ((step + 4) / 2) % 3; if (index) *index = i; return _frames + i; } void Reader::moveToNextShow() const { int32 step = _step.loadAcquire(); if (step == WaitingForDimensionsStep) { } else if (step == WaitingForRequestStep) { _step.storeRelease(WaitingForFirstFrameStep); } else if (step == WaitingForFirstFrameStep) { } else if (!(step % 2)) { _step.storeRelease(step + 1); } } void Reader::moveToNextWrite() const { int32 step = _step.loadAcquire(); if (step == WaitingForDimensionsStep) { _step.storeRelease(WaitingForRequestStep); } else if (step == WaitingForRequestStep) { } else if (step == WaitingForFirstFrameStep) { _step.storeRelease(0); // Force paint the first frame so moveToNextShow() is called. _frames[0].displayed.storeRelease(0); } else if (step % 2) { _step.storeRelease((step + 1) % 6); } } void Reader::callback(Reader *reader, int32 threadIndex, Notification notification) { // check if reader is not deleted already if (managers.size() > threadIndex && managers.at(threadIndex)->carries(reader) && reader->_callback) { reader->_callback(notification); } } void Reader::start(int32 framew, int32 frameh, int32 outerw, int32 outerh, ImageRoundRadius radius, RectParts corners) { if (managers.size() <= _threadIndex) error(); if (_state == State::Error) return; if (_step.loadAcquire() == WaitingForRequestStep) { int factor = cIntRetinaFactor(); FrameRequest request; request.factor = factor; request.framew = framew * factor; request.frameh = frameh * factor; request.outerw = outerw * factor; request.outerh = outerh * factor; request.radius = radius; request.corners = corners; _frames[0].request = _frames[1].request = _frames[2].request = request; moveToNextShow(); managers.at(_threadIndex)->start(this); } } QPixmap Reader::current(int32 framew, int32 frameh, int32 outerw, int32 outerh, ImageRoundRadius radius, RectParts corners, TimeMs ms) { Expects(outerw > 0); Expects(outerh > 0); auto frame = frameToShow(); Assert(frame != nullptr); auto shouldBePaused = !ms; if (!shouldBePaused) { frame->displayed.storeRelease(1); if (_autoPausedGif.loadAcquire()) { _autoPausedGif.storeRelease(0); if (managers.size() <= _threadIndex) error(); if (_state != State::Error) { managers.at(_threadIndex)->update(this); } } } else { frame->displayed.storeRelease(-1); } auto factor = cIntRetinaFactor(); if (frame->pix.width() == outerw * factor && frame->pix.height() == outerh * factor && frame->request.radius == radius && frame->request.corners == corners) { moveToNextShow(); return frame->pix; } frame->request.framew = framew * factor; frame->request.frameh = frameh * factor; frame->request.outerw = outerw * factor; frame->request.outerh = outerh * factor; QImage cacheForResize; frame->original.setDevicePixelRatio(factor); frame->pix = QPixmap(); frame->pix = PrepareFrame(frame->request, frame->original, true, cacheForResize); auto other = frameToWriteNext(true); if (other) other->request = frame->request; moveToNextShow(); if (managers.size() <= _threadIndex) error(); if (_state != State::Error) { managers.at(_threadIndex)->update(this); } return frame->pix; } QPixmap Reader::current() { Expects(_mode == Mode::Video); auto frame = frameToShow(); Assert(frame != nullptr); frame->displayed.storeRelease(1); moveToNextShow(); return frame->pix; } bool Reader::ready() const { if (_width && _height) return true; auto frame = frameToShow(); if (frame) { _width = frame->original.width(); _height = frame->original.height(); return true; } return false; } bool Reader::hasAudio() const { return ready() ? _hasAudio : false; } TimeMs Reader::getPositionMs() const { if (auto frame = frameToShow()) { return frame->positionMs; } return _seekPositionMs; } TimeMs Reader::getDurationMs() const { return ready() ? _durationMs : 0; } void Reader::pauseResumeVideo() { if (managers.size() <= _threadIndex) error(); if (_state == State::Error) return; _videoPauseRequest.storeRelease(1 - _videoPauseRequest.loadAcquire()); managers.at(_threadIndex)->start(this); } bool Reader::videoPaused() const { return _videoPauseRequest.loadAcquire() != 0; } int32 Reader::width() const { return _width; } int32 Reader::height() const { return _height; } State Reader::state() const { return _state; } void Reader::stop() { if (managers.size() <= _threadIndex) error(); if (_state != State::Error) { managers.at(_threadIndex)->stop(this); _width = _height = 0; } } void Reader::error() { _state = State::Error; _private = nullptr; } void Reader::finished() { _state = State::Finished; _private = nullptr; } Reader::~Reader() { stop(); } class ReaderPrivate { public: ReaderPrivate(Reader *reader, const FileLocation &location, const QByteArray &data) : _interface(reader) , _mode(reader->mode()) , _audioMsgId(reader->audioMsgId()) , _seekPositionMs(reader->seekPositionMs()) , _data(data) { if (_data.isEmpty()) { _location = std::make_unique<FileLocation>(location); if (!_location->accessEnable()) { error(); return; } } _accessed = true; } ProcessResult start(TimeMs ms) { if (!_implementation && !init()) { return error(); } if (frame() && frame()->original.isNull()) { auto readResult = _implementation->readFramesTill(-1, ms); if (readResult == internal::ReaderImplementation::ReadResult::EndOfFile && _seekPositionMs > 0) { // If seek was done to the end: try to read the first frame, // get the frame size and return a black frame with that size. auto firstFramePositionMs = TimeMs(0); auto reader = std::make_unique<internal::FFMpegReaderImplementation>(_location.get(), &_data, AudioMsgId()); if (reader->start(internal::ReaderImplementation::Mode::Normal, firstFramePositionMs)) { auto firstFrameReadResult = reader->readFramesTill(-1, ms); if (firstFrameReadResult == internal::ReaderImplementation::ReadResult::Success) { if (reader->renderFrame(frame()->original, frame()->alpha, QSize())) { frame()->original.fill(QColor(0, 0, 0)); frame()->positionMs = _seekPositionMs; _width = frame()->original.width(); _height = frame()->original.height(); _durationMs = _implementation->durationMs(); _hasAudio = _implementation->hasAudio(); return ProcessResult::Started; } } } return error(); } else if (readResult != internal::ReaderImplementation::ReadResult::Success) { // Read the first frame. return error(); } if (!_implementation->renderFrame(frame()->original, frame()->alpha, QSize())) { return error(); } frame()->positionMs = _implementation->frameRealTime(); _width = frame()->original.width(); _height = frame()->original.height(); _durationMs = _implementation->durationMs(); _hasAudio = _implementation->hasAudio(); return ProcessResult::Started; } return ProcessResult::Wait; } ProcessResult process(TimeMs ms) { // -1 - do nothing, 0 - update, 1 - reinit if (_state == State::Error) { return ProcessResult::Error; } else if (_state == State::Finished) { return ProcessResult::Finished; } if (!_request.valid()) { return start(ms); } if (!_started) { _started = true; if (!_videoPausedAtMs && _hasAudio) { Player::mixer()->resume(_audioMsgId, true); } } if (!_autoPausedGif && !_videoPausedAtMs && ms >= _nextFrameWhen) { return ProcessResult::Repaint; } return ProcessResult::Wait; } ProcessResult finishProcess(TimeMs ms) { auto frameMs = _seekPositionMs + ms - _animationStarted; auto readResult = _implementation->readFramesTill(frameMs, ms); if (readResult == internal::ReaderImplementation::ReadResult::EndOfFile) { stop(Player::State::StoppedAtEnd); _state = State::Finished; return ProcessResult::Finished; } else if (readResult == internal::ReaderImplementation::ReadResult::Error) { return error(); } _nextFramePositionMs = _implementation->frameRealTime(); _nextFrameWhen = _animationStarted + _implementation->framePresentationTime(); if (_nextFrameWhen > _seekPositionMs) { _nextFrameWhen -= _seekPositionMs; } else { _nextFrameWhen = 1; } if (!renderFrame()) { return error(); } return ProcessResult::CopyFrame; } bool renderFrame() { Assert(frame() != 0 && _request.valid()); if (!_implementation->renderFrame(frame()->original, frame()->alpha, QSize(_request.framew, _request.frameh))) { return false; } frame()->original.setDevicePixelRatio(_request.factor); frame()->pix = QPixmap(); frame()->pix = PrepareFrame(_request, frame()->original, frame()->alpha, frame()->cache); frame()->when = _nextFrameWhen; frame()->positionMs = _nextFramePositionMs; return true; } bool init() { if (_data.isEmpty() && QFileInfo(_location->name()).size() <= Storage::kMaxAnimationInMemory) { QFile f(_location->name()); if (f.open(QIODevice::ReadOnly)) { _data = f.readAll(); if (f.error() != QFile::NoError) { _data = QByteArray(); } } } _implementation = std::make_unique<internal::FFMpegReaderImplementation>(_location.get(), &_data, _audioMsgId); // _implementation = new QtGifReaderImplementation(_location, &_data); auto implementationMode = [this]() { using ImplementationMode = internal::ReaderImplementation::Mode; if (_mode == Reader::Mode::Gif) { return ImplementationMode::Silent; } return ImplementationMode::Normal; }; return _implementation->start(implementationMode(), _seekPositionMs); } void startedAt(TimeMs ms) { _animationStarted = _nextFrameWhen = ms; } void pauseVideo(TimeMs ms) { if (_videoPausedAtMs) return; // Paused already. _videoPausedAtMs = ms; if (_hasAudio) { Player::mixer()->pause(_audioMsgId, true); } } void resumeVideo(TimeMs ms) { if (!_videoPausedAtMs) return; // Not paused. auto delta = ms - _videoPausedAtMs; _animationStarted += delta; _nextFrameWhen += delta; _videoPausedAtMs = 0; if (_hasAudio) { Player::mixer()->resume(_audioMsgId, true); } } ProcessResult error() { stop(Player::State::StoppedAtError); _state = State::Error; return ProcessResult::Error; } void stop(Player::State audioState) { _implementation = nullptr; if (_hasAudio) { Player::mixer()->stop(_audioMsgId, audioState); } if (_location) { if (_accessed) { _location->accessDisable(); } _location = nullptr; } _accessed = false; } ~ReaderPrivate() { stop(Player::State::Stopped); _data.clear(); } private: Reader *_interface; State _state = State::Reading; Reader::Mode _mode; AudioMsgId _audioMsgId; TimeMs _seekPositionMs = 0; QByteArray _data; std::unique_ptr<FileLocation> _location; bool _accessed = false; QBuffer _buffer; std::unique_ptr<internal::ReaderImplementation> _implementation; FrameRequest _request; struct Frame { QPixmap pix; QImage original, cache; bool alpha = true; TimeMs when = 0; // Counted from the end, so that positionMs <= durationMs despite keep up delays. TimeMs positionMs = 0; }; Frame _frames[3]; int _frame = 0; Frame *frame() { return _frames + _frame; } int _width = 0; int _height = 0; bool _hasAudio = false; TimeMs _durationMs = 0; TimeMs _animationStarted = 0; TimeMs _nextFrameWhen = 0; TimeMs _nextFramePositionMs = 0; bool _autoPausedGif = false; bool _started = false; TimeMs _videoPausedAtMs = 0; friend class Manager; }; Manager::Manager(QThread *thread) : _processingInThread(0), _needReProcess(false) { moveToThread(thread); connect(thread, SIGNAL(started()), this, SLOT(process())); connect(thread, SIGNAL(finished()), this, SLOT(finish())); connect(this, SIGNAL(processDelayed()), this, SLOT(process()), Qt::QueuedConnection); _timer.setSingleShot(true); _timer.moveToThread(thread); connect(&_timer, SIGNAL(timeout()), this, SLOT(process())); anim::registerClipManager(this); } void Manager::append(Reader *reader, const FileLocation &location, const QByteArray &data) { reader->_private = new ReaderPrivate(reader, location, data); _loadLevel.fetchAndAddRelaxed(AverageGifSize); update(reader); } void Manager::start(Reader *reader) { update(reader); } void Manager::update(Reader *reader) { QMutexLocker lock(&_readerPointersMutex); auto i = _readerPointers.find(reader); if (i == _readerPointers.cend()) { _readerPointers.insert(reader, QAtomicInt(1)); } else { i->storeRelease(1); } emit processDelayed(); } void Manager::stop(Reader *reader) { if (!carries(reader)) return; QMutexLocker lock(&_readerPointersMutex); _readerPointers.remove(reader); emit processDelayed(); } bool Manager::carries(Reader *reader) const { QMutexLocker lock(&_readerPointersMutex); return _readerPointers.contains(reader); } Manager::ReaderPointers::iterator Manager::unsafeFindReaderPointer(ReaderPrivate *reader) { ReaderPointers::iterator it = _readerPointers.find(reader->_interface); // could be a new reader which was realloced in the same address return (it == _readerPointers.cend() || it.key()->_private == reader) ? it : _readerPointers.end(); } Manager::ReaderPointers::const_iterator Manager::constUnsafeFindReaderPointer(ReaderPrivate *reader) const { ReaderPointers::const_iterator it = _readerPointers.constFind(reader->_interface); // could be a new reader which was realloced in the same address return (it == _readerPointers.cend() || it.key()->_private == reader) ? it : _readerPointers.cend(); } bool Manager::handleProcessResult(ReaderPrivate *reader, ProcessResult result, TimeMs ms) { QMutexLocker lock(&_readerPointersMutex); auto it = unsafeFindReaderPointer(reader); if (result == ProcessResult::Error) { if (it != _readerPointers.cend()) { it.key()->error(); emit callback(it.key(), it.key()->threadIndex(), NotificationReinit); _readerPointers.erase(it); } return false; } else if (result == ProcessResult::Finished) { if (it != _readerPointers.cend()) { it.key()->finished(); emit callback(it.key(), it.key()->threadIndex(), NotificationReinit); } return false; } if (it == _readerPointers.cend()) { return false; } if (result == ProcessResult::Started) { _loadLevel.fetchAndAddRelaxed(reader->_width * reader->_height - AverageGifSize); it.key()->_durationMs = reader->_durationMs; it.key()->_hasAudio = reader->_hasAudio; } // See if we need to pause GIF because it is not displayed right now. if (!reader->_autoPausedGif && reader->_mode == Reader::Mode::Gif && result == ProcessResult::Repaint) { int32 ishowing, iprevious; auto showing = it.key()->frameToShow(&ishowing), previous = it.key()->frameToWriteNext(false, &iprevious); Assert(previous != nullptr && showing != nullptr && ishowing >= 0 && iprevious >= 0); if (reader->_frames[ishowing].when > 0 && showing->displayed.loadAcquire() <= 0) { // current frame was not shown if (reader->_frames[ishowing].when + WaitBeforeGifPause < ms || (reader->_frames[iprevious].when && previous->displayed.loadAcquire() <= 0)) { reader->_autoPausedGif = true; it.key()->_autoPausedGif.storeRelease(1); result = ProcessResult::Paused; } } } if (result == ProcessResult::Started || result == ProcessResult::CopyFrame) { Assert(reader->_frame >= 0); auto frame = it.key()->_frames + reader->_frame; frame->clear(); frame->pix = reader->frame()->pix; frame->original = reader->frame()->original; frame->displayed.storeRelease(0); frame->positionMs = reader->frame()->positionMs; if (result == ProcessResult::Started) { reader->startedAt(ms); it.key()->moveToNextWrite(); emit callback(it.key(), it.key()->threadIndex(), NotificationReinit); } } else if (result == ProcessResult::Paused) { it.key()->moveToNextWrite(); emit callback(it.key(), it.key()->threadIndex(), NotificationReinit); } else if (result == ProcessResult::Repaint) { it.key()->moveToNextWrite(); emit callback(it.key(), it.key()->threadIndex(), NotificationRepaint); } return true; } Manager::ResultHandleState Manager::handleResult(ReaderPrivate *reader, ProcessResult result, TimeMs ms) { if (!handleProcessResult(reader, result, ms)) { _loadLevel.fetchAndAddRelaxed(-1 * (reader->_width > 0 ? reader->_width * reader->_height : AverageGifSize)); delete reader; return ResultHandleRemove; } _processingInThread->eventDispatcher()->processEvents(QEventLoop::AllEvents); if (_processingInThread->isInterruptionRequested()) { return ResultHandleStop; } if (result == ProcessResult::Repaint) { { QMutexLocker lock(&_readerPointersMutex); auto it = constUnsafeFindReaderPointer(reader); if (it != _readerPointers.cend()) { int32 index = 0; Reader *r = it.key(); Reader::Frame *frame = it.key()->frameToWrite(&index); if (frame) { frame->clear(); } else { Assert(!reader->_request.valid()); } reader->_frame = index; } } return handleResult(reader, reader->finishProcess(ms), ms); } return ResultHandleContinue; } void Manager::process() { if (_processingInThread) { _needReProcess = true; return; } _timer.stop(); _processingInThread = thread(); bool checkAllReaders = false; auto ms = getms(), minms = ms + 86400 * 1000LL; { QMutexLocker lock(&_readerPointersMutex); for (auto it = _readerPointers.begin(), e = _readerPointers.end(); it != e; ++it) { if (it->loadAcquire() && it.key()->_private != nullptr) { auto i = _readers.find(it.key()->_private); if (i == _readers.cend()) { _readers.insert(it.key()->_private, 0); } else { i.value() = ms; if (i.key()->_autoPausedGif && !it.key()->_autoPausedGif.loadAcquire()) { i.key()->_autoPausedGif = false; } if (it.key()->_videoPauseRequest.loadAcquire()) { i.key()->pauseVideo(ms); } else { i.key()->resumeVideo(ms); } } auto frame = it.key()->frameToWrite(); if (frame) it.key()->_private->_request = frame->request; it->storeRelease(0); } } checkAllReaders = (_readers.size() > _readerPointers.size()); } for (auto i = _readers.begin(), e = _readers.end(); i != e;) { ReaderPrivate *reader = i.key(); if (i.value() <= ms) { ResultHandleState state = handleResult(reader, reader->process(ms), ms); if (state == ResultHandleRemove) { i = _readers.erase(i); continue; } else if (state == ResultHandleStop) { _processingInThread = 0; return; } ms = getms(); if (reader->_videoPausedAtMs) { i.value() = ms + 86400 * 1000ULL; } else if (reader->_nextFrameWhen && reader->_started) { i.value() = reader->_nextFrameWhen; } else { i.value() = (ms + 86400 * 1000ULL); } } else if (checkAllReaders) { QMutexLocker lock(&_readerPointersMutex); auto it = constUnsafeFindReaderPointer(reader); if (it == _readerPointers.cend()) { _loadLevel.fetchAndAddRelaxed(-1 * (reader->_width > 0 ? reader->_width * reader->_height : AverageGifSize)); delete reader; i = _readers.erase(i); continue; } } if (!reader->_autoPausedGif && i.value() < minms) { minms = i.value(); } ++i; } ms = getms(); if (_needReProcess || minms <= ms) { _needReProcess = false; _timer.start(1); } else { _timer.start(minms - ms); } _processingInThread = 0; } void Manager::finish() { _timer.stop(); clear(); } void Manager::clear() { { QMutexLocker lock(&_readerPointersMutex); for (auto it = _readerPointers.begin(), e = _readerPointers.end(); it != e; ++it) { it.key()->_private = nullptr; } _readerPointers.clear(); } for (Readers::iterator i = _readers.begin(), e = _readers.end(); i != e; ++i) { delete i.key(); } _readers.clear(); } Manager::~Manager() { clear(); } FileMediaInformation::Video PrepareForSending(const QString &fname, const QByteArray &data) { auto result = FileMediaInformation::Video(); auto localLocation = FileLocation(fname); auto localData = QByteArray(data); auto seekPositionMs = 0LL; auto reader = std::make_unique<internal::FFMpegReaderImplementation>(&localLocation, &localData, AudioMsgId()); if (reader->start(internal::ReaderImplementation::Mode::Inspecting, seekPositionMs)) { auto durationMs = reader->durationMs(); if (durationMs > 0) { result.isGifv = reader->isGifv(); if (!result.isGifv) { auto middleMs = durationMs / 2; if (!reader->inspectAt(middleMs)) { return result; } } auto hasAlpha = false; auto readResult = reader->readFramesTill(-1, getms()); auto readFrame = (readResult == internal::ReaderImplementation::ReadResult::Success); if (readFrame && reader->renderFrame(result.thumbnail, hasAlpha, QSize())) { if (hasAlpha) { auto cacheForResize = QImage(); auto request = FrameRequest(); request.framew = request.outerw = result.thumbnail.width(); request.frameh = request.outerh = result.thumbnail.height(); request.factor = 1; result.thumbnail = PrepareFrameImage(request, result.thumbnail, hasAlpha, cacheForResize); } result.duration = static_cast<int>(durationMs / 1000); } } } return result; } void Finish() { if (!threads.isEmpty()) { for (int32 i = 0, l = threads.size(); i < l; ++i) { threads.at(i)->quit(); DEBUG_LOG(("Waiting for clipThread to finish: %1").arg(i)); threads.at(i)->wait(); delete managers.at(i); delete threads.at(i); } threads.clear(); managers.clear(); } } } // namespace Clip } // namespace Media ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/media/media_clip_reader.h��������������������������������������0000664�0000000�0000000�00000013102�13262451251�0024260�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "storage/localimageloader.h" class FileLocation; namespace Media { namespace Clip { enum class State { Reading, Error, Finished, }; struct FrameRequest { bool valid() const { return factor > 0; } int factor = 0; int framew = 0; int frameh = 0; int outerw = 0; int outerh = 0; ImageRoundRadius radius = ImageRoundRadius::None; RectParts corners = RectPart::AllCorners; }; enum ReaderSteps { WaitingForDimensionsStep = -3, // before ReaderPrivate read the first image and got the original frame size WaitingForRequestStep = -2, // before Reader got the original frame size and prepared the frame request WaitingForFirstFrameStep = -1, // before ReaderPrivate got the frame request and started waiting for the 1-2 delay }; class ReaderPrivate; class Reader { public: using Callback = base::lambda<void(Notification)>; enum class Mode { Gif, Video, }; Reader(const QString &filepath, Callback &&callback, Mode mode = Mode::Gif, TimeMs seekMs = 0); Reader(not_null<DocumentData*> document, FullMsgId msgId, Callback &&callback, Mode mode = Mode::Gif, TimeMs seekMs = 0); static void callback(Reader *reader, int threadIndex, Notification notification); // reader can be deleted void setAutoplay() { _autoplay = true; } bool autoplay() const { return _autoplay; } AudioMsgId audioMsgId() const { return _audioMsgId; } TimeMs seekPositionMs() const { return _seekPositionMs; } void start(int framew, int frameh, int outerw, int outerh, ImageRoundRadius radius, RectParts corners); QPixmap current(int framew, int frameh, int outerw, int outerh, ImageRoundRadius radius, RectParts corners, TimeMs ms); QPixmap current(); QPixmap frameOriginal() const { if (auto frame = frameToShow()) { auto result = QPixmap::fromImage(frame->original); result.detach(); return result; } return QPixmap(); } bool currentDisplayed() const { auto frame = frameToShow(); return frame ? (frame->displayed.loadAcquire() != 0) : true; } bool autoPausedGif() const { return _autoPausedGif.loadAcquire(); } bool videoPaused() const; int threadIndex() const { return _threadIndex; } int width() const; int height() const; State state() const; bool started() const { auto step = _step.loadAcquire(); return (step == WaitingForFirstFrameStep) || (step >= 0); } bool ready() const; bool hasAudio() const; TimeMs getPositionMs() const; TimeMs getDurationMs() const; void pauseResumeVideo(); void stop(); void error(); void finished(); Mode mode() const { return _mode; } ~Reader(); private: void init(const FileLocation &location, const QByteArray &data); Callback _callback; Mode _mode; State _state = State::Reading; AudioMsgId _audioMsgId; bool _hasAudio = false; TimeMs _durationMs = 0; TimeMs _seekPositionMs = 0; mutable int _width = 0; mutable int _height = 0; // -2, -1 - init, 0-5 - work, show ((state + 1) / 2) % 3 state, write ((state + 3) / 2) % 3 mutable QAtomicInt _step = WaitingForDimensionsStep; struct Frame { void clear() { pix = QPixmap(); original = QImage(); } QPixmap pix; QImage original; FrameRequest request; QAtomicInt displayed = 0; // Should be counted from the end, // so that positionMs <= _durationMs. TimeMs positionMs = 0; }; mutable Frame _frames[3]; Frame *frameToShow(int *index = nullptr) const; // 0 means not ready Frame *frameToWrite(int *index = nullptr) const; // 0 means not ready Frame *frameToWriteNext(bool check, int *index = nullptr) const; void moveToNextShow() const; void moveToNextWrite() const; QAtomicInt _autoPausedGif = 0; QAtomicInt _videoPauseRequest = 0; int32 _threadIndex; bool _autoplay = false; friend class Manager; ReaderPrivate *_private = nullptr; }; template <typename ...Args> inline ReaderPointer MakeReader(Args&&... args) { return ReaderPointer(new Reader(std::forward<Args>(args)...)); } enum class ProcessResult { Error, Started, Finished, Paused, Repaint, CopyFrame, Wait, }; class Manager : public QObject { Q_OBJECT public: Manager(QThread *thread); int32 loadLevel() const { return _loadLevel.load(); } void append(Reader *reader, const FileLocation &location, const QByteArray &data); void start(Reader *reader); void update(Reader *reader); void stop(Reader *reader); bool carries(Reader *reader) const; ~Manager(); signals: void processDelayed(); void callback(Media::Clip::Reader *reader, qint32 threadIndex, qint32 notification); public slots: void process(); void finish(); private: void clear(); QAtomicInt _loadLevel; using ReaderPointers = QMap<Reader*, QAtomicInt>; ReaderPointers _readerPointers; mutable QMutex _readerPointersMutex; ReaderPointers::const_iterator constUnsafeFindReaderPointer(ReaderPrivate *reader) const; ReaderPointers::iterator unsafeFindReaderPointer(ReaderPrivate *reader); bool handleProcessResult(ReaderPrivate *reader, ProcessResult result, TimeMs ms); enum ResultHandleState { ResultHandleRemove, ResultHandleStop, ResultHandleContinue, }; ResultHandleState handleResult(ReaderPrivate *reader, ProcessResult result, TimeMs ms); typedef QMap<ReaderPrivate*, TimeMs> Readers; Readers _readers; QTimer _timer; QThread *_processingInThread; bool _needReProcess; }; FileMediaInformation::Video PrepareForSending(const QString &fname, const QByteArray &data); void Finish(); } // namespace Clip } // namespace Media ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/media/player/��������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0021776�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/media/player/media_player.style��������������������������������0000664�0000000�0000000�00000014475�13262451251�0025526�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ using "basic.style"; using "ui/widgets/widgets.style"; using "overview/overview.style"; mediaPlayerButton: MediaPlayerButton { playPosition: point(2px, 0px); playOuter: size(17px, 15px); pausePosition: point(1px, 1px); pauseOuter: size(15px, 15px); pauseStroke: 5px; cancelPosition: point(1px, 1px); cancelOuter: size(15px, 15px); cancelStroke: 3px; rippleAreaPosition: point(0px, 5px); rippleAreaSize: 25px; ripple: RippleAnimation(defaultRippleAnimation) { color: lightButtonBgOver; } } mediaPlayerButtonSize: size(25px, 30px); mediaPlayerButtonPosition: point(5px, 10px); mediaPlayerSkipIconPosition: point(5px, 12px); mediaPlayerHeight: 35px; mediaPlayerPadding: 8px; mediaPlayerNameTop: 22px; mediaPlayerPlayLeft: 7px; mediaPlayerPlaySkip: 1px; mediaPlayerPlayTop: 0px; mediaPlayerCloseRight: 0px; mediaPlayerName: FlatLabel(defaultFlatLabel) { maxHeight: 20px; } mediaPlayerTime: LabelSimple(defaultLabelSimple) { textFg: windowSubTextFg; } mediaPlayerRepeatButton: IconButton { width: 31px; height: 30px; icon: icon { { "player_repeat", mediaPlayerActiveFg, point(9px, 11px) } }; iconPosition: point(0px, 0px); rippleAreaPosition: point(3px, 5px); rippleAreaSize: 25px; ripple: RippleAnimation(defaultRippleAnimation) { color: lightButtonBgOver; } } mediaPlayerRepeatDisabledIcon: icon { { "player_repeat", menuIconFg, point(9px, 11px)} }; mediaPlayerRepeatDisabledIconOver: icon { { "player_repeat", menuIconFgOver, point(9px, 11px)} }; mediaPlayerRepeatDisabledRippleBg: windowBgOver; mediaPlayerRepeatInactiveIcon: icon { { "player_repeat", mediaPlayerInactiveFg, point(9px, 11px)} }; mediaPlayerVolumeIcon0: icon { { "player_volume0", mediaPlayerActiveFg }, }; mediaPlayerVolumeIcon1: icon { { "player_volume1", mediaPlayerActiveFg }, }; mediaPlayerVolumeIcon2: icon { { "player_volume2", mediaPlayerActiveFg }, }; mediaPlayerVolumeIcon3: icon { { "player_volume3", mediaPlayerActiveFg }, }; mediaPlayerVolumeToggle: IconButton { width: 31px; height: 30px; icon: mediaPlayerVolumeIcon0; iconPosition: point(8px, 11px); rippleAreaPosition: point(3px, 5px); rippleAreaSize: 25px; ripple: RippleAnimation(defaultRippleAnimation) { color: lightButtonBgOver; } } mediaPlayerVolumeMargin: 10px; mediaPlayerVolumeSize: size(27px, 100px); mediaPlayerPanelPinButton: IconButton(mediaPlayerRepeatButton) { icon: icon { { "player_panel_pin", mediaPlayerActiveFg, point(9px, 11px) } }; } mediaPlayerNextButton: IconButton(mediaPlayerRepeatButton) { width: 25px; icon: icon { { "player_next", mediaPlayerActiveFg, mediaPlayerSkipIconPosition }, }; rippleAreaPosition: point(0px, 5px); } mediaPlayerNextDisabledIcon: icon { { "player_next", mediaPlayerInactiveFg, mediaPlayerSkipIconPosition }, }; mediaPlayerPreviousButton: IconButton(mediaPlayerNextButton) { icon: icon { { "player_next-flip_horizontal", mediaPlayerActiveFg, mediaPlayerSkipIconPosition }, }; rippleAreaPosition: point(1px, 5px); } mediaPlayerPreviousDisabledIcon: icon { { "player_next-flip_horizontal", mediaPlayerInactiveFg, mediaPlayerSkipIconPosition }, }; mediaPlayerClose: IconButton(mediaPlayerRepeatButton) { width: 37px; icon: icon {{ "player_close", menuIconFg, point(10px, 12px) }}; iconOver: icon {{ "player_close", menuIconFgOver, point(10px, 12px) }}; rippleAreaPosition: point(3px, 5px); ripple: RippleAnimation(defaultRippleAnimation) { color: windowBgOver; } } mediaPlayerPlayback: FilledSlider { fullWidth: 6px; lineWidth: 2px; activeFg: mediaPlayerActiveFg; inactiveFg: mediaPlayerInactiveFg; disabledFg: mediaPlayerDisabledFg; duration: 150; } mediaPlayerButtonTransformDuration: 200; mediaPlayerPanelButton: MediaPlayerButton { playPosition: point(3px, 0px); playOuter: size(22px, 18px); pausePosition: point(1px, 1px); pauseOuter: size(16px, 18px); pauseStroke: 5px; cancelPosition: point(0px, 1px); cancelOuter: size(16px, 18px); cancelStroke: 3px; } mediaPlayerPanelButtonSize: size(32px, 32px); mediaPlayerPanelButtonPosition: point(8px, 7px); mediaPlayerPanelMarginLeft: 10px; mediaPlayerPanelMarginBottom: 10px; mediaPlayerPanelWidth: 344px; mediaPlayerCoverHeight: 102px; mediaPlayerPanelClose: IconButton(mediaPlayerClose) { width: 43px; height: 28px; icon: icon {{ "player_close", menuIconFg, point(16px, 14px) }}; iconOver: icon {{ "player_close", menuIconFgOver, point(16px, 14px) }}; } mediaPlayerPanelNextButton: IconButton(mediaPlayerRepeatButton) { width: 37px; icon: icon {{ "player_panel_next", mediaPlayerActiveFg, point(10px, 10px) }}; } mediaPlayerPanelNextDisabledIcon: icon { { "player_panel_next", mediaPlayerInactiveFg, point(10px, 10px) }, }; mediaPlayerPanelPreviousButton: IconButton(mediaPlayerPanelNextButton) { icon: icon {{ "player_panel_next-flip_horizontal", mediaPlayerActiveFg, point(10px, 10px) }}; } mediaPlayerPanelPreviousDisabledIcon: icon { { "player_panel_next-flip_horizontal", mediaPlayerInactiveFg, point(10px, 10px) }, }; mediaPlayerPanelPadding: 16px; mediaPlayerPanelNameTop: 24px; mediaPlayerPanelPlayLeft: 9px; mediaPlayerPanelPlaySkip: 7px; mediaPlayerPanelPlayTop: 58px; mediaPlayerPanelPlaybackTop: 32px; mediaPlayerPanelPlaybackPadding: 8px; mediaPlayerPanelPlayback: MediaSlider { width: 3px; activeFg: mediaPlayerActiveFg; inactiveFg: mediaPlayerInactiveFg; activeFgOver: mediaPlayerActiveFg; inactiveFgOver: mediaPlayerInactiveFg; activeFgDisabled: mediaPlayerInactiveFg; inactiveFgDisabled: windowBg; seekSize: size(9px, 9px); duration: 150; } mediaPlayerPanelVolumeTop: 65px; mediaPlayerPanelVolumeSkip: 3px; mediaPlayerPanelVolumeWidth: 64px; mediaPlayerPanelVolumeToggleSkip: 0px; mediaPlayerPanelVolumeToggleTop: 57px; mediaPlayerScroll: ScrollArea(defaultSolidScroll) { deltat: 10px; deltab: 10px; } mediaPlayerListHeightMax: 280px; mediaPlayerListMarginBottom: 10px; mediaPlayerScrollShadow: Shadow { bottom: icon {{ "playlist_shadow", windowShadowFg }}; extend: margins(2px, 2px, 2px, 2px); } mediaPlayerListMarginTop: 8px; mediaPlayerFileLayout: OverviewFileLayout(overviewFileLayout) { maxWidth: 344px; songIconBg: mediaPlayerActiveFg; songOverBg: mediaPlayerActiveFg; } mediaPlayerFloatSize: 128px; mediaPlayerFloatMargin: 12px; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/media/player/media_player_button.cpp���������������������������0000664�0000000�0000000�00000021247�13262451251�0026536�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "media/player/media_player_button.h" #include "styles/style_widgets.h" namespace Media { namespace Player { PlayButtonLayout::PlayButtonLayout(const style::MediaPlayerButton &st, base::lambda<void()> callback) : _st(st) , _callback(std::move(callback)) { } void PlayButtonLayout::setState(State state) { if (_nextState == state) return; _nextState = state; if (!_transformProgress.animating(getms())) { _oldState = _state; _state = _nextState; _transformBackward = false; if (_state != _oldState) { startTransform(0., 1.); if (_callback) _callback(); } } else if (_oldState == _nextState) { qSwap(_oldState, _state); startTransform(_transformBackward ? 0. : 1., _transformBackward ? 1. : 0.); _transformBackward = !_transformBackward; } } void PlayButtonLayout::finishTransform() { _transformProgress.finish(); _transformBackward = false; if (_callback) _callback(); } void PlayButtonLayout::paint(Painter &p, const QBrush &brush) { if (_transformProgress.animating(getms())) { auto from = _oldState, to = _state; auto backward = _transformBackward; auto progress = _transformProgress.current(1.); if (from == State::Cancel || (from == State::Pause && to == State::Play)) { qSwap(from, to); backward = !backward; } if (backward) progress = 1. - progress; Assert(from != to); if (from == State::Play) { if (to == State::Pause) { paintPlayToPause(p, brush, progress); } else { Assert(to == State::Cancel); paintPlayToCancel(p, brush, progress); } } else { Assert(from == State::Pause && to == State::Cancel); paintPauseToCancel(p, brush, progress); } } else { switch (_state) { case State::Play: paintPlay(p, brush); break; case State::Pause: paintPlayToPause(p, brush, 1.); break; case State::Cancel: paintPlayToCancel(p, brush, 1.); break; } } } void PlayButtonLayout::paintPlay(Painter &p, const QBrush &brush) { auto playLeft = 0. + _st.playPosition.x(); auto playTop = 0. + _st.playPosition.y(); auto playWidth = _st.playOuter.width() - 2 * playLeft; auto playHeight = _st.playOuter.height() - 2 * playTop; PainterHighQualityEnabler hq(p); p.setPen(Qt::NoPen); QPainterPath pathPlay; pathPlay.moveTo(playLeft, playTop); pathPlay.lineTo(playLeft + playWidth, playTop + (playHeight / 2.)); pathPlay.lineTo(playLeft, playTop + playHeight); pathPlay.lineTo(playLeft, playTop); p.fillPath(pathPlay, brush); } void PlayButtonLayout::paintPlayToPause(Painter &p, const QBrush &brush, float64 progress) { auto playLeft = 0. + _st.playPosition.x(); auto playTop = 0. + _st.playPosition.y(); auto playWidth = _st.playOuter.width() - 2 * playLeft; auto playHeight = _st.playOuter.height() - 2 * playTop; auto pauseLeft = 0. + _st.pausePosition.x(); auto pauseTop = 0. + _st.pausePosition.y(); auto pauseWidth = _st.pauseOuter.width() - 2 * pauseLeft; auto pauseHeight = _st.pauseOuter.height() - 2 * pauseTop; auto pauseStroke = 0. + _st.pauseStroke; p.setPen(Qt::NoPen); PainterHighQualityEnabler hq(p); QPointF pathLeftPause[] = { { pauseLeft, pauseTop }, { pauseLeft + pauseStroke, pauseTop }, { pauseLeft + pauseStroke, pauseTop + pauseHeight }, { pauseLeft, pauseTop + pauseHeight }, }; QPointF pathLeftPlay[] = { { playLeft, playTop }, { playLeft + (playWidth / 2.), playTop + (playHeight / 4.) }, { playLeft + (playWidth / 2.), playTop + (3 * playHeight / 4.) }, { playLeft, playTop + playHeight }, }; p.fillPath(anim::interpolate(pathLeftPlay, pathLeftPause, progress), brush); QPointF pathRightPause[] = { { pauseLeft + pauseWidth - pauseStroke, pauseTop }, { pauseLeft + pauseWidth, pauseTop }, { pauseLeft + pauseWidth, pauseTop + pauseHeight }, { pauseLeft + pauseWidth - pauseStroke, pauseTop + pauseHeight }, }; QPointF pathRightPlay[] = { { playLeft + (playWidth / 2.), playTop + (playHeight / 4.) }, { playLeft + playWidth, playTop + (playHeight / 2.) }, { playLeft + playWidth, playTop + (playHeight / 2.) }, { playLeft + (playWidth / 2.), playTop + (3 * playHeight / 4.) }, }; p.fillPath(anim::interpolate(pathRightPlay, pathRightPause, progress), brush); } void PlayButtonLayout::paintPlayToCancel(Painter &p, const QBrush &brush, float64 progress) { static const auto sqrt2 = sqrt(2.); auto playLeft = 0. + _st.playPosition.x(); auto playTop = 0. + _st.playPosition.y(); auto playWidth = _st.playOuter.width() - 2 * playLeft; auto playHeight = _st.playOuter.height() - 2 * playTop; auto cancelLeft = 0. + _st.cancelPosition.x(); auto cancelTop = 0. + _st.cancelPosition.y(); auto cancelWidth = _st.cancelOuter.width() - 2 * cancelLeft; auto cancelHeight = _st.cancelOuter.height() - 2 * cancelTop; auto cancelStroke = (0. + _st.cancelStroke) / sqrt2; p.setPen(Qt::NoPen); PainterHighQualityEnabler hq(p); QPointF pathPlay[] = { { playLeft, playTop }, { playLeft, playTop }, { playLeft + (playWidth / 2.), playTop + (playHeight / 4.) }, { playLeft + playWidth, playTop + (playHeight / 2.) }, { playLeft + playWidth, playTop + (playHeight / 2.) }, { playLeft + playWidth, playTop + (playHeight / 2.) }, { playLeft + playWidth, playTop + (playHeight / 2.) }, { playLeft + playWidth, playTop + (playHeight / 2.) }, { playLeft + (playWidth / 2.), playTop + (3 * playHeight / 4.) }, { playLeft, playTop + playHeight }, { playLeft, playTop + playHeight }, { playLeft, playTop + (playHeight / 2.) }, }; QPointF pathCancel[] = { { cancelLeft, cancelTop + cancelStroke }, { cancelLeft + cancelStroke, cancelTop }, { cancelLeft + (cancelWidth / 2.), cancelTop + (cancelHeight / 2.) - cancelStroke }, { cancelLeft + cancelWidth - cancelStroke, cancelTop }, { cancelLeft + cancelWidth, cancelTop + cancelStroke }, { cancelLeft + (cancelWidth / 2.) + cancelStroke, cancelTop + (cancelHeight / 2.) }, { cancelLeft + cancelWidth, cancelTop + cancelHeight - cancelStroke }, { cancelLeft + cancelWidth - cancelStroke, cancelTop + cancelHeight }, { cancelLeft + (cancelWidth / 2.), cancelTop + (cancelHeight / 2.) + cancelStroke }, { cancelLeft + cancelStroke, cancelTop + cancelHeight }, { cancelLeft, cancelTop + cancelHeight - cancelStroke }, { cancelLeft + (cancelWidth / 2.) - cancelStroke, cancelTop + (cancelHeight / 2.) }, }; p.fillPath(anim::interpolate(pathPlay, pathCancel, progress), brush); } void PlayButtonLayout::paintPauseToCancel(Painter &p, const QBrush &brush, float64 progress) { static const auto sqrt2 = sqrt(2.); auto pauseLeft = 0. + _st.pausePosition.x(); auto pauseTop = 0. + _st.pausePosition.y(); auto pauseWidth = _st.pauseOuter.width() - 2 * pauseLeft; auto pauseHeight = _st.pauseOuter.height() - 2 * pauseTop; auto pauseStroke = 0. + _st.pauseStroke; auto cancelLeft = 0. + _st.cancelPosition.x(); auto cancelTop = 0. + _st.cancelPosition.y(); auto cancelWidth = _st.cancelOuter.width() - 2 * cancelLeft; auto cancelHeight = _st.cancelOuter.height() - 2 * cancelTop; auto cancelStroke = (0. + _st.cancelStroke) / sqrt2; p.setPen(Qt::NoPen); PainterHighQualityEnabler hq(p); QPointF pathLeftPause[] = { { pauseLeft, pauseTop }, { pauseLeft + pauseStroke, pauseTop }, { pauseLeft + pauseStroke, pauseTop + pauseHeight }, { pauseLeft, pauseTop + pauseHeight }, }; QPointF pathLeftCancel[] = { { cancelLeft, cancelTop + cancelStroke }, { cancelLeft + cancelStroke, cancelTop }, { cancelLeft + cancelWidth, cancelTop + cancelHeight - cancelStroke }, { cancelLeft + cancelWidth - cancelStroke, cancelTop + cancelHeight }, }; p.fillPath(anim::interpolate(pathLeftPause, pathLeftCancel, progress), brush); QPointF pathRightPause[] = { { pauseLeft + pauseWidth - pauseStroke, pauseTop }, { pauseLeft + pauseWidth, pauseTop }, { pauseLeft + pauseWidth, pauseTop + pauseHeight }, { pauseLeft + pauseWidth - pauseStroke, pauseTop + pauseHeight }, }; QPointF pathRightCancel[] = { { cancelLeft + cancelWidth - cancelStroke, cancelTop }, { cancelLeft + cancelWidth, cancelTop + cancelStroke }, { cancelLeft + cancelStroke, cancelTop + cancelHeight }, { cancelLeft, cancelTop + cancelHeight - cancelStroke }, }; p.fillPath(anim::interpolate(pathRightPause, pathRightCancel, progress), brush); } void PlayButtonLayout::animationCallback() { if (!_transformProgress.animating()) { auto finalState = _nextState; _nextState = _state; setState(finalState); } _callback(); } void PlayButtonLayout::startTransform(float64 from, float64 to) { _transformProgress.start([this] { animationCallback(); }, from, to, st::mediaPlayerButtonTransformDuration); } } // namespace Player } // namespace Media ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/media/player/media_player_button.h�����������������������������0000664�0000000�0000000�00000002376�13262451251�0026205�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/abstract_button.h" #include "styles/style_media_player.h" namespace Media { namespace Player { class PlayButtonLayout { public: enum class State { Play, Pause, Cancel, }; PlayButtonLayout(const style::MediaPlayerButton &st, base::lambda<void()> callback); void setState(State state); void finishTransform(); void paint(Painter &p, const QBrush &brush); private: void animationCallback(); void startTransform(float64 from, float64 to); void paintPlay(Painter &p, const QBrush &brush); void paintPlayToPause(Painter &p, const QBrush &brush, float64 progress); void paintPlayToCancel(Painter &p, const QBrush &brush, float64 progress); void paintPauseToCancel(Painter &p, const QBrush &brush, float64 progress); const style::MediaPlayerButton &_st; State _state = State::Play; State _oldState = State::Play; State _nextState = State::Play; Animation _transformProgress; bool _transformBackward = false; base::lambda<void()> _callback; }; } // namespace Clip } // namespace Media ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/media/player/media_player_cover.cpp����������������������������0000664�0000000�0000000�00000030572�13262451251�0026342�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "media/player/media_player_cover.h" #include "data/data_document.h" #include "ui/widgets/labels.h" #include "ui/widgets/continuous_sliders.h" #include "ui/widgets/buttons.h" #include "media/media_audio.h" #include "media/view/media_clip_playback.h" #include "media/player/media_player_button.h" #include "media/player/media_player_instance.h" #include "media/player/media_player_volume_controller.h" #include "styles/style_media_player.h" #include "styles/style_mediaview.h" #include "layout.h" namespace Media { namespace Player { using ButtonState = PlayButtonLayout::State; class CoverWidget::PlayButton : public Ui::AbstractButton { public: PlayButton(QWidget *parent); void setState(ButtonState state) { _layout.setState(state); } void finishTransform() { _layout.finishTransform(); } protected: void paintEvent(QPaintEvent *e) override; private: PlayButtonLayout _layout; }; CoverWidget::PlayButton::PlayButton(QWidget *parent) : Ui::AbstractButton(parent) , _layout(st::mediaPlayerPanelButton, [this] { update(); }) { resize(st::mediaPlayerPanelButtonSize); setCursor(style::cur_pointer); } void CoverWidget::PlayButton::paintEvent(QPaintEvent *e) { Painter p(this); p.translate(st::mediaPlayerPanelButtonPosition.x(), st::mediaPlayerPanelButtonPosition.y()); _layout.paint(p, st::mediaPlayerActiveFg); } CoverWidget::CoverWidget(QWidget *parent) : RpWidget(parent) , _nameLabel(this, st::mediaPlayerName) , _timeLabel(this, st::mediaPlayerTime) , _close(this, st::mediaPlayerPanelClose) , _playbackSlider(this, st::mediaPlayerPanelPlayback) , _playback(std::make_unique<Clip::Playback>()) , _playPause(this) , _volumeToggle(this, st::mediaPlayerVolumeToggle) , _volumeController(this) , _pinPlayer(this, st::mediaPlayerPanelPinButton) , _repeatTrack(this, st::mediaPlayerRepeatButton) { setAttribute(Qt::WA_OpaquePaintEvent); resize(width(), st::mediaPlayerCoverHeight); _close->hide(); _nameLabel->setAttribute(Qt::WA_TransparentForMouseEvents); _timeLabel->setAttribute(Qt::WA_TransparentForMouseEvents); setMouseTracking(true); _playback->setInLoadingStateChangedCallback([=](bool loading) { _playbackSlider->setDisabled(loading); }); _playback->setValueChangedCallback([=](float64 value) { _playbackSlider->setValue(value); }); _playbackSlider->setChangeProgressCallback([=](float64 value) { _playback->setValue(value, false); handleSeekProgress(value); }); _playbackSlider->setChangeFinishedCallback([=](float64 value) { _playback->setValue(value, false); handleSeekFinished(value); }); _playPause->setClickedCallback([=] { instance()->playPauseCancelClicked(AudioMsgId::Type::Song); }); updateRepeatTrackIcon(); _repeatTrack->setClickedCallback([=] { instance()->toggleRepeat(AudioMsgId::Type::Song); }); updateVolumeToggleIcon(); _volumeToggle->setClickedCallback([=]() { Global::SetSongVolume((Global::SongVolume() > 0) ? 0. : Global::RememberedSongVolume()); mixer()->setSongVolume(Global::SongVolume()); Global::RefSongVolumeChanged().notify(); }); subscribe(Global::RefSongVolumeChanged(), [=] { updateVolumeToggleIcon(); }); subscribe(instance()->repeatChangedNotifier(), [=](AudioMsgId::Type type) { if (type == AudioMsgId::Type::Song) { updateRepeatTrackIcon(); } }); subscribe(instance()->updatedNotifier(), [=](const TrackState &state) { if (state.id.type() == AudioMsgId::Type::Song) { handleSongUpdate(state); } }); subscribe(instance()->trackChangedNotifier(), [=](AudioMsgId::Type type) { if (type == AudioMsgId::Type::Song) { handleSongChange(); } }); instance()->playlistChanges( AudioMsgId::Type::Song ) | rpl::start_with_next([=] { handlePlaylistUpdate(); }, lifetime()); handleSongChange(); handleSongUpdate(mixer()->currentState(AudioMsgId::Type::Song)); _playPause->finishTransform(); } void CoverWidget::setPinCallback(ButtonCallback &&callback) { _pinPlayer->setClickedCallback(std::move(callback)); } void CoverWidget::setCloseCallback(ButtonCallback &&callback) { _close->setClickedCallback(std::move(callback)); } void CoverWidget::handleSeekProgress(float64 progress) { if (!_lastDurationMs) return; auto positionMs = snap(static_cast<TimeMs>(progress * _lastDurationMs), 0LL, _lastDurationMs); if (_seekPositionMs != positionMs) { _seekPositionMs = positionMs; updateTimeLabel(); instance()->startSeeking(AudioMsgId::Type::Song); } } void CoverWidget::handleSeekFinished(float64 progress) { if (!_lastDurationMs) return; auto positionMs = snap(static_cast<TimeMs>(progress * _lastDurationMs), 0LL, _lastDurationMs); _seekPositionMs = -1; auto type = AudioMsgId::Type::Song; auto state = Media::Player::mixer()->currentState(type); if (state.id && state.length && state.frequency) { Media::Player::mixer()->seek(type, qRound(progress * state.length * 1000. / state.frequency)); } instance()->stopSeeking(type); } void CoverWidget::resizeEvent(QResizeEvent *e) { auto widthForName = width() - 2 * (st::mediaPlayerPanelPadding); widthForName -= _timeLabel->width() + 2 * st::normalFont->spacew; _nameLabel->resizeToWidth(widthForName); updateLabelPositions(); _close->moveToRight(0, 0); int skip = (st::mediaPlayerPanelPlayback.seekSize.width() / 2); int length = (width() - 2 * st::mediaPlayerPanelPadding + st::mediaPlayerPanelPlayback.seekSize.width()); _playbackSlider->setGeometry(st::mediaPlayerPanelPadding - skip, st::mediaPlayerPanelPlaybackTop, length, 2 * st::mediaPlayerPanelPlaybackPadding + st::mediaPlayerPanelPlayback.width); auto top = st::mediaPlayerPanelVolumeToggleTop; auto right = st::mediaPlayerPanelPlayLeft; _repeatTrack->moveToRight(right, top); right += _repeatTrack->width(); _pinPlayer->moveToRight(right, top); right += _pinPlayer->width() + st::mediaPlayerPanelVolumeSkip; _volumeController->moveToRight(right, st::mediaPlayerPanelVolumeTop); right += _volumeController->width() + st::mediaPlayerPanelVolumeToggleSkip; _volumeToggle->moveToRight(right, top); updatePlayPrevNextPositions(); } void CoverWidget::paintEvent(QPaintEvent *e) { Painter p(this); p.fillRect(e->rect(), st::windowBg); } void CoverWidget::mouseMoveEvent(QMouseEvent *e) { auto closeAreaLeft = st::mediaPlayerPanelPadding + _nameLabel->width(); auto closeAreaHeight = _nameLabel->y() + _nameLabel->height(); auto closeArea = myrtlrect(closeAreaLeft, 0, width() - closeAreaLeft, closeAreaHeight); auto closeVisible = closeArea.contains(e->pos()); setCloseVisible(closeVisible); } void CoverWidget::leaveEventHook(QEvent *e) { setCloseVisible(false); } void CoverWidget::setCloseVisible(bool visible) { if (visible == _close->isHidden()) { _close->setVisible(visible); _timeLabel->setVisible(!visible); } } void CoverWidget::updatePlayPrevNextPositions() { auto left = st::mediaPlayerPanelPlayLeft; auto top = st::mediaPlayerPanelPlayTop; if (_previousTrack) { _previousTrack->moveToLeft(left, top); left += _previousTrack->width() + st::mediaPlayerPanelPlaySkip; _playPause->moveToLeft(left, top); left += _playPause->width() + st::mediaPlayerPanelPlaySkip; _nextTrack->moveToLeft(left, top); } else { _playPause->moveToLeft(left, top); } } void CoverWidget::updateLabelPositions() { _nameLabel->moveToLeft(st::mediaPlayerPanelPadding, st::mediaPlayerPanelNameTop - st::mediaPlayerName.style.font->ascent); _timeLabel->moveToRight(st::mediaPlayerPanelPadding, st::mediaPlayerPanelNameTop - st::mediaPlayerTime.font->ascent); } void CoverWidget::updateRepeatTrackIcon() { _repeatTrack->setIconOverride(instance()->repeatEnabled(AudioMsgId::Type::Song) ? nullptr : &st::mediaPlayerRepeatInactiveIcon); } void CoverWidget::handleSongUpdate(const TrackState &state) { if (!state.id.audio() || !state.id.audio()->isAudioFile()) { return; } if (state.id.audio()->loading()) { _playback->updateLoadingState(state.id.audio()->progress()); } else { _playback->updateState(state); } auto stopped = IsStoppedOrStopping(state.state); auto showPause = !stopped && (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting); if (instance()->isSeeking(AudioMsgId::Type::Song)) { showPause = true; } auto buttonState = [audio = state.id.audio(), showPause] { if (audio->loading()) { return ButtonState::Cancel; } else if (showPause) { return ButtonState::Pause; } return ButtonState::Play; }; _playPause->setState(buttonState()); updateTimeText(state); } void CoverWidget::updateTimeText(const TrackState &state) { QString time; qint64 position = 0, length = 0, display = 0; auto frequency = state.frequency; if (!IsStoppedOrStopping(state.state)) { display = position = state.position; length = state.length; } else if (const auto songData = state.id.audio()->song()) { length = state.length ? state.length : (songData->duration * frequency); } _lastDurationMs = (state.length * 1000LL) / frequency; if (state.id.audio()->loading()) { _time = QString::number(qRound(state.id.audio()->progress() * 100)) + '%'; _playbackSlider->setDisabled(true); } else { display = display / frequency; _time = formatDurationText(display); _playbackSlider->setDisabled(false); } if (_seekPositionMs < 0) { updateTimeLabel(); } } void CoverWidget::updateTimeLabel() { auto timeLabelWidth = _timeLabel->width(); if (_seekPositionMs >= 0) { auto playAlready = _seekPositionMs / 1000LL; _timeLabel->setText(formatDurationText(playAlready)); } else { _timeLabel->setText(_time); } if (timeLabelWidth != _timeLabel->width()) { _nameLabel->resizeToWidth(width() - 2 * (st::mediaPlayerPanelPadding) - _timeLabel->width() - st::normalFont->spacew); updateLabelPositions(); } } void CoverWidget::handleSongChange() { const auto current = instance()->current(AudioMsgId::Type::Song); const auto document = current.audio(); if (!current || !document) { return; } TextWithEntities textWithEntities; const auto song = document ? document->song() : nullptr; if (!song) { textWithEntities.text = document->filename().isEmpty() ? qsl("Unknown Track") : document->filename(); } else if (song->performer.isEmpty()) { textWithEntities.text = song->title.isEmpty() ? (document->filename().isEmpty() ? qsl("Unknown Track") : document->filename()) : song->title; } else { auto title = song->title.isEmpty() ? qsl("Unknown Track") : TextUtilities::Clean(song->title); textWithEntities.text = song->performer + QString::fromUtf8(" \xe2\x80\x93 ") + title; textWithEntities.entities.append({ EntityInTextBold, 0, song->performer.size(), QString() }); } _nameLabel->setMarkedText(textWithEntities); handlePlaylistUpdate(); } void CoverWidget::handlePlaylistUpdate() { const auto type = AudioMsgId::Type::Song; const auto previousEnabled = instance()->previousAvailable(type); const auto nextEnabled = instance()->nextAvailable(type); if (!previousEnabled && !nextEnabled) { destroyPrevNextButtons(); } else { createPrevNextButtons(); _previousTrack->setIconOverride(previousEnabled ? nullptr : &st::mediaPlayerPanelPreviousDisabledIcon); _previousTrack->setCursor(previousEnabled ? style::cur_pointer : style::cur_default); _nextTrack->setIconOverride(nextEnabled ? nullptr : &st::mediaPlayerPanelNextDisabledIcon); _nextTrack->setCursor(nextEnabled ? style::cur_pointer : style::cur_default); } } void CoverWidget::createPrevNextButtons() { if (!_previousTrack) { _previousTrack.create(this, st::mediaPlayerPanelPreviousButton); _previousTrack->show(); _previousTrack->setClickedCallback([=]() { instance()->previous(); }); _nextTrack.create(this, st::mediaPlayerPanelNextButton); _nextTrack->show(); _nextTrack->setClickedCallback([=]() { instance()->next(); }); updatePlayPrevNextPositions(); } } void CoverWidget::destroyPrevNextButtons() { if (_previousTrack) { _previousTrack.destroy(); _nextTrack.destroy(); updatePlayPrevNextPositions(); } } void CoverWidget::updateVolumeToggleIcon() { auto icon = []() -> const style::icon * { auto volume = Global::SongVolume(); if (volume > 0) { if (volume < 1 / 3.) { return &st::mediaPlayerVolumeIcon1; } else if (volume < 2 / 3.) { return &st::mediaPlayerVolumeIcon2; } return &st::mediaPlayerVolumeIcon3; } return nullptr; }; _volumeToggle->setIconOverride(icon()); } } // namespace Player } // namespace Media ��������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/media/player/media_player_cover.h������������������������������0000664�0000000�0000000�00000004165�13262451251�0026006�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/rp_widget.h" class AudioMsgId; namespace Ui { class FlatLabel; class LabelSimple; class IconButton; class MediaSlider; } // namespace Ui namespace Media { namespace Clip { class Playback; } // namespace Clip namespace Player { class VolumeController; struct TrackState; class CoverWidget : public Ui::RpWidget, private base::Subscriber { public: CoverWidget(QWidget *parent); using ButtonCallback = base::lambda<void()>; void setPinCallback(ButtonCallback &&callback); void setCloseCallback(ButtonCallback &&callback); protected: void resizeEvent(QResizeEvent *e) override; void paintEvent(QPaintEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void leaveEventHook(QEvent *e) override; private: void setCloseVisible(bool visible); void handleSeekProgress(float64 progress); void handleSeekFinished(float64 progress); void updatePlayPrevNextPositions(); void updateLabelPositions(); void updateRepeatTrackIcon(); void createPrevNextButtons(); void destroyPrevNextButtons(); void updateVolumeToggleIcon(); void handleSongUpdate(const TrackState &state); void handleSongChange(); void handlePlaylistUpdate(); void updateTimeText(const TrackState &state); void updateTimeLabel(); TimeMs _seekPositionMs = -1; TimeMs _lastDurationMs = 0; QString _time; class PlayButton; object_ptr<Ui::FlatLabel> _nameLabel; object_ptr<Ui::LabelSimple> _timeLabel; object_ptr<Ui::IconButton> _close; object_ptr<Ui::MediaSlider> _playbackSlider; std::unique_ptr<Clip::Playback> _playback; object_ptr<Ui::IconButton> _previousTrack = { nullptr }; object_ptr<PlayButton> _playPause; object_ptr<Ui::IconButton> _nextTrack = { nullptr }; object_ptr<Ui::IconButton> _volumeToggle; object_ptr<VolumeController> _volumeController; object_ptr<Ui::IconButton> _pinPlayer; object_ptr<Ui::IconButton> _repeatTrack; }; } // namespace Clip } // namespace Media �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/media/player/media_player_float.cpp����������������������������0000664�0000000�0000000�00000015054�13262451251�0026327�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "media/player/media_player_float.h" #include <rpl/merge.h> #include "data/data_document.h" #include "data/data_session.h" #include "data/data_media_types.h" #include "history/history_media.h" #include "history/history_item.h" #include "history/view/history_view_element.h" #include "media/media_clip_reader.h" #include "media/media_audio.h" #include "media/view/media_clip_playback.h" #include "media/player/media_player_round_controller.h" #include "window/window_controller.h" #include "auth_session.h" #include "styles/style_media_player.h" #include "styles/style_history.h" namespace Media { namespace Player { Float::Float( QWidget *parent, not_null<Window::Controller*> controller, not_null<HistoryItem*> item, base::lambda<void(bool visible)> toggleCallback, base::lambda<void(bool closed)> draggedCallback) : RpWidget(parent) , _controller(controller) , _item(item) , _toggleCallback(std::move(toggleCallback)) , _draggedCallback(std::move(draggedCallback)) { auto media = _item->media(); Assert(media != nullptr); auto document = media->document(); Assert(document != nullptr); Assert(document->isVideoMessage()); auto margin = st::mediaPlayerFloatMargin; auto size = 2 * margin + st::mediaPlayerFloatSize; resize(size, size); prepareShadow(); Auth().data().itemRepaintRequest( ) | rpl::start_with_next([this](auto item) { if (_item == item) { repaintItem(); } }, lifetime()); Auth().data().itemRemoved( ) | rpl::start_with_next([this](auto item) { if (_item == item) { detach(); } }, lifetime()); setCursor(style::cur_pointer); } void Float::mousePressEvent(QMouseEvent *e) { _down = true; _downPoint = e->pos(); } void Float::mouseMoveEvent(QMouseEvent *e) { if (_down && (e->pos() - _downPoint).manhattanLength() > QApplication::startDragDistance()) { _down = false; _drag = true; _dragLocalPoint = e->pos(); } else if (_drag) { auto delta = (e->pos() - _dragLocalPoint); move(pos() + delta); setOpacity(outRatio()); } } float64 Float::outRatio() const { auto parent = parentWidget()->rect(); auto min = 1.; if (x() < parent.x()) { accumulate_min(min, 1. - (parent.x() - x()) / float64(width())); } if (y() < parent.y()) { accumulate_min(min, 1. - (parent.y() - y()) / float64(height())); } if (x() + width() > parent.x() + parent.width()) { accumulate_min(min, 1. - (x() + width() - parent.x() - parent.width()) / float64(width())); } if (y() + height() > parent.y() + parent.height()) { accumulate_min(min, 1. - (y() + height() - parent.y() - parent.height()) / float64(height())); } return snap(min, 0., 1.); } void Float::mouseReleaseEvent(QMouseEvent *e) { if (base::take(_down) && _item) { if (const auto controller = _controller->roundVideo(_item)) { controller->pauseResume(); } } if (_drag) { finishDrag(outRatio() < 0.5); } } void Float::finishDrag(bool closed) { _drag = false; if (_draggedCallback) { _draggedCallback(closed); } } void Float::mouseDoubleClickEvent(QMouseEvent *e) { if (_item) { // Handle second click. if (const auto controller = _controller->roundVideo(_item)) { controller->pauseResume(); } Ui::showPeerHistoryAtItem(_item); } } void Float::detach() { if (_item) { _item = nullptr; if (_toggleCallback) { _toggleCallback(false); } } } void Float::prepareShadow() { auto shadow = QImage(size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); shadow.fill(Qt::transparent); shadow.setDevicePixelRatio(cRetinaFactor()); { Painter p(&shadow); PainterHighQualityEnabler hq(p); p.setPen(Qt::NoPen); p.setBrush(st::shadowFg); auto extend = 2 * st::lineWidth; p.drawEllipse(getInnerRect().marginsAdded(QMargins(extend, extend, extend, extend))); } _shadow = App::pixmapFromImageInPlace(Images::prepareBlur(std::move(shadow))); } QRect Float::getInnerRect() const { auto margin = st::mediaPlayerFloatMargin; return rect().marginsRemoved(QMargins(margin, margin, margin, margin)); } void Float::paintEvent(QPaintEvent *e) { Painter p(this); p.setOpacity(_opacity); p.drawPixmap(0, 0, _shadow); if (!fillFrame() && _toggleCallback) { _toggleCallback(false); } auto inner = getInnerRect(); p.drawImage(inner.topLeft(), _frame); const auto playback = getPlayback(); const auto progress = playback ? playback->value(getms()) : 1.; if (progress > 0.) { auto pen = st::historyVideoMessageProgressFg->p; auto was = p.pen(); pen.setWidth(st::radialLine); pen.setCapStyle(Qt::RoundCap); p.setPen(pen); p.setOpacity(_opacity * st::historyVideoMessageProgressOpacity); auto from = QuarterArcLength; auto len = -qRound(FullArcLength * progress); auto stepInside = st::radialLine / 2; { PainterHighQualityEnabler hq(p); p.drawArc(inner.marginsRemoved(QMargins(stepInside, stepInside, stepInside, stepInside)), from, len); } //p.setPen(was); //p.setOpacity(_opacity); } } Clip::Reader *Float::getReader() const { if (detached()) { return nullptr; } if (const auto controller = _controller->roundVideo(_item)) { if (const auto reader = controller->reader()) { if (reader->started()) { return reader; } } } return nullptr; } Clip::Playback *Float::getPlayback() const { if (detached()) { return nullptr; } if (const auto controller = _controller->roundVideo(_item)) { return controller->playback(); } return nullptr; } bool Float::hasFrame() const { if (const auto reader = getReader()) { return !reader->current().isNull(); } return false; } bool Float::fillFrame() { auto creating = _frame.isNull(); if (creating) { _frame = QImage( getInnerRect().size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); _frame.setDevicePixelRatio(cRetinaFactor()); } auto frameInner = [&] { return QRect(QPoint(), _frame.size() / cIntRetinaFactor()); }; if (const auto reader = getReader()) { auto frame = reader->current(); if (!frame.isNull()) { _frame.fill(Qt::transparent); Painter p(&_frame); PainterHighQualityEnabler hq(p); p.drawPixmap(frameInner(), frame); return true; } } if (creating) { _frame.fill(Qt::transparent); Painter p(&_frame); PainterHighQualityEnabler hq(p); p.setPen(Qt::NoPen); p.setBrush(st::imageBg); p.drawEllipse(frameInner()); } return false; } void Float::repaintItem() { update(); if (hasFrame() && _toggleCallback) { _toggleCallback(true); } } } // namespace Player } // namespace Media ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/media/player/media_player_float.h������������������������������0000664�0000000�0000000�00000004022�13262451251�0025765�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/rp_widget.h" namespace Window { class Controller; } // namespace Window namespace Media { namespace Clip { class Playback; } // namespace Clip namespace Player { class Float : public Ui::RpWidget, private base::Subscriber { public: Float( QWidget *parent, not_null<Window::Controller*> controller, not_null<HistoryItem*> item, base::lambda<void(bool visible)> toggleCallback, base::lambda<void(bool closed)> draggedCallback); HistoryItem *item() const { return _item; } void setOpacity(float64 opacity) { if (_opacity != opacity) { _opacity = opacity; update(); } } float64 countOpacityByParent() const { return outRatio(); } bool isReady() const { return (getReader() != nullptr); } void detach(); bool detached() const { return !_item; } bool dragged() const { return _drag; } void resetMouseState() { _down = false; if (_drag) { finishDrag(false); } } protected: void paintEvent(QPaintEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void mouseDoubleClickEvent(QMouseEvent *e) override; private: float64 outRatio() const; Clip::Reader *getReader() const; Clip::Playback *getPlayback() const; void repaintItem(); void prepareShadow(); bool hasFrame() const; bool fillFrame(); QRect getInnerRect() const; void finishDrag(bool closed); not_null<Window::Controller*> _controller; HistoryItem *_item = nullptr; base::lambda<void(bool visible)> _toggleCallback; float64 _opacity = 1.; QPixmap _shadow; QImage _frame; bool _down = false; QPoint _downPoint; bool _drag = false; QPoint _dragLocalPoint; base::lambda<void(bool closed)> _draggedCallback; }; } // namespace Player } // namespace Media ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/media/player/media_player_instance.cpp�������������������������0000664�0000000�0000000�00000027252�13262451251�0027031�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "media/player/media_player_instance.h" #include "data/data_document.h" #include "data/data_session.h" #include "media/media_audio.h" #include "media/media_audio_capture.h" #include "calls/calls_instance.h" #include "history/history.h" #include "history/history_item.h" #include "data/data_media_types.h" #include "window/window_controller.h" #include "messenger.h" #include "mainwindow.h" #include "auth_session.h" namespace Media { namespace Player { namespace { Instance *SingleInstance = nullptr; // Preload X message ids before and after current. constexpr auto kIdsLimit = 32; // Preload next messages if we went further from current than that. constexpr auto kIdsPreloadAfter = 28; } // namespace void start() { Audio::Start(); Capture::Start(); SingleInstance = new Instance(); } void finish() { delete base::take(SingleInstance); Capture::Finish(); Audio::Finish(); } Instance::Instance() : _songData(AudioMsgId::Type::Song, SharedMediaType::MusicFile) , _voiceData(AudioMsgId::Type::Voice, SharedMediaType::RoundVoiceFile) { subscribe(Media::Player::Updated(), [this](const AudioMsgId &audioId) { handleSongUpdate(audioId); }); subscribe(Global::RefSelfChanged(), [this] { if (!App::self()) { handleLogout(); } }); // While we have one Media::Player::Instance for all authsessions we have to do this. auto handleAuthSessionChange = [this] { if (AuthSession::Exists()) { subscribe(Auth().calls().currentCallChanged(), [this](Calls::Call *call) { if (call) { pause(AudioMsgId::Type::Voice); pause(AudioMsgId::Type::Song); } }); } }; subscribe(Messenger::Instance().authSessionChanged(), [handleAuthSessionChange] { handleAuthSessionChange(); }); handleAuthSessionChange(); } AudioMsgId::Type Instance::getActiveType() const { auto voiceData = getData(AudioMsgId::Type::Voice); if (voiceData->current) { auto state = mixer()->currentState(voiceData->type); if (voiceData->current == state.id && !IsStoppedOrStopping(state.state)) { return voiceData->type; } } return AudioMsgId::Type::Song; } void Instance::handleSongUpdate(const AudioMsgId &audioId) { emitUpdate(audioId.type(), [&audioId](const AudioMsgId &playing) { return (audioId == playing); }); } void Instance::setCurrent(const AudioMsgId &audioId) { if (auto data = getData(audioId.type())) { if (data->current != audioId) { data->current = audioId; data->isPlaying = false; auto history = data->history; auto migrated = data->migrated; auto item = data->current ? App::histItemById(data->current.contextId()) : nullptr; if (item) { data->history = item->history()->migrateToOrMe(); data->migrated = data->history->migrateFrom(); } else { data->history = nullptr; data->migrated = nullptr; } _trackChangedNotifier.notify(data->type, true); refreshPlaylist(data); } } } void Instance::refreshPlaylist(not_null<Data*> data) { if (!validPlaylist(data)) { validatePlaylist(data); } playlistUpdated(data); } void Instance::playlistUpdated(not_null<Data*> data) { if (data->playlistSlice) { const auto fullId = data->current.contextId(); data->playlistIndex = data->playlistSlice->indexOf(fullId); } else { data->playlistIndex = base::none; } data->playlistChanges.fire({}); } bool Instance::validPlaylist(not_null<Data*> data) { if (const auto key = playlistKey(data)) { if (!data->playlistSlice) { return false; } using Key = SliceKey; const auto inSameDomain = [](const Key &a, const Key &b) { return (a.peerId == b.peerId) && (a.migratedPeerId == b.migratedPeerId); }; const auto countDistanceInData = [&](const Key &a, const Key &b) { return [&](const SparseIdsMergedSlice &data) { return inSameDomain(a, b) ? data.distance(a, b) : base::optional<int>(); }; }; if (key == data->playlistRequestedKey) { return true; } else if (!data->playlistSliceKey || !data->playlistRequestedKey || *data->playlistRequestedKey != *data->playlistSliceKey) { return false; } auto distance = data->playlistSlice | countDistanceInData(*key, *data->playlistRequestedKey) | func::abs; if (distance) { return (*distance < kIdsPreloadAfter); } } return !data->playlistSlice; } void Instance::validatePlaylist(not_null<Data*> data) { if (const auto key = playlistKey(data)) { data->playlistRequestedKey = key; SharedMediaMergedViewer( SharedMediaMergedKey(*key, data->overview), kIdsLimit, kIdsLimit ) | rpl::start_with_next([=](SparseIdsMergedSlice &&update) { data->playlistSlice = std::move(update); data->playlistSliceKey = key; playlistUpdated(data); }, data->playlistLifetime); } else { data->playlistSlice = base::none; data->playlistSliceKey = data->playlistRequestedKey = base::none; playlistUpdated(data); } } auto Instance::playlistKey(not_null<Data*> data) const -> base::optional<SliceKey> { const auto contextId = data->current.contextId(); const auto history = data->history; if (!contextId || !history || !IsServerMsgId(contextId.msg)) { return {}; } const auto universalId = (contextId.channel == history->channelId()) ? contextId.msg : (contextId.msg - ServerMaxMsgId); return SliceKey( data->history->peer->id, data->migrated ? data->migrated->peer->id : 0, universalId); } HistoryItem *Instance::itemByIndex(not_null<Data*> data, int index) { if (!data->playlistSlice || index < 0 || index >= data->playlistSlice->size()) { return nullptr; } const auto fullId = (*data->playlistSlice)[index]; return App::histItemById(fullId); } bool Instance::moveInPlaylist( not_null<Data*> data, int delta, bool autonext) { if (!data->playlistIndex) { return false; } const auto newIndex = *data->playlistIndex + delta; if (const auto item = itemByIndex(data, newIndex)) { if (const auto media = item->media()) { if (const auto document = media->document()) { if (autonext) { _switchToNextNotifier.notify({ data->current, item->fullId() }); } if (document->isAudioFile() || document->isVoiceMessage() || document->isVideoMessage()) { play(AudioMsgId(document, item->fullId())); } else { //DocumentOpenClickHandler::doOpen( // document, // item, // ActionOnLoadPlayInline); } return true; } } } return false; } bool Instance::previousAvailable(AudioMsgId::Type type) const { const auto data = getData(type); Assert(data != nullptr); return data->playlistIndex && data->playlistSlice && (*data->playlistIndex > 0); } bool Instance::nextAvailable(AudioMsgId::Type type) const { const auto data = getData(type); Assert(data != nullptr); return data->playlistIndex && data->playlistSlice && (*data->playlistIndex + 1 < data->playlistSlice->size()); } rpl::producer<> Media::Player::Instance::playlistChanges( AudioMsgId::Type type) const { const auto data = getData(type); Assert(data != nullptr); return data->playlistChanges.events(); } Instance *instance() { Expects(SingleInstance != nullptr); return SingleInstance; } void Instance::play(AudioMsgId::Type type) { auto state = mixer()->currentState(type); if (state.id) { if (IsStopped(state.state)) { play(state.id); } else { mixer()->resume(state.id); } } else if (auto data = getData(type)) { if (data->current) { play(data->current); } } } void Instance::play(const AudioMsgId &audioId) { const auto document = audioId.audio(); if (!audioId || !document) { return; } if (document->isAudioFile() || document->isVoiceMessage()) { mixer()->play(audioId); setCurrent(audioId); if (document->loading()) { documentLoadProgress(document); } } else if (document->isVideoMessage()) { if (const auto item = App::histItemById(audioId.contextId())) { App::wnd()->controller()->startRoundVideo(item); } } if (document->isVoiceMessage() || document->isVideoMessage()) { document->session()->data().markMediaRead(document); } } void Instance::pause(AudioMsgId::Type type) { auto state = mixer()->currentState(type); if (state.id) { mixer()->pause(state.id); } } void Instance::stop(AudioMsgId::Type type) { auto state = mixer()->currentState(type); if (state.id) { mixer()->stop(state.id); } } void Instance::playPause(AudioMsgId::Type type) { auto state = mixer()->currentState(type); if (state.id) { if (IsStopped(state.state)) { play(state.id); } else if (IsPaused(state.state) || state.state == State::Pausing) { mixer()->resume(state.id); } else { mixer()->pause(state.id); } } else if (auto data = getData(type)) { if (data->current) { play(data->current); } } } bool Instance::next(AudioMsgId::Type type) { if (auto data = getData(type)) { return moveInPlaylist(data, 1, false); } return false; } bool Instance::previous(AudioMsgId::Type type) { if (auto data = getData(type)) { return moveInPlaylist(data, -1, false); } return false; } void Instance::playPauseCancelClicked(AudioMsgId::Type type) { if (isSeeking(type)) { return; } auto state = mixer()->currentState(type); auto stopped = IsStoppedOrStopping(state.state); auto showPause = !stopped && (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting); auto audio = state.id.audio(); if (audio && audio->loading()) { audio->cancel(); } else if (showPause) { pause(type); } else { play(type); } } void Instance::startSeeking(AudioMsgId::Type type) { if (auto data = getData(type)) { data->seeking = data->current; } pause(type); emitUpdate(type, [](const AudioMsgId &playing) { return true; }); } void Instance::stopSeeking(AudioMsgId::Type type) { if (auto data = getData(type)) { data->seeking = AudioMsgId(); } emitUpdate(type, [](const AudioMsgId &playing) { return true; }); } void Instance::documentLoadProgress(DocumentData *document) { const auto type = document->isAudioFile() ? AudioMsgId::Type::Song : AudioMsgId::Type::Voice; emitUpdate(type, [document](const AudioMsgId &audioId) { return (audioId.audio() == document); }); } template <typename CheckCallback> void Instance::emitUpdate(AudioMsgId::Type type, CheckCallback check) { auto state = mixer()->currentState(type); if (!state.id || !check(state.id)) { return; } setCurrent(state.id); _updatedNotifier.notify(state, true); if (auto data = getData(type)) { if (data->isPlaying && state.state == State::StoppedAtEnd) { if (data->repeatEnabled) { play(data->current); } else if (!moveInPlaylist(data, 1, true)) { _tracksFinishedNotifier.notify(type); } } auto isPlaying = !IsStopped(state.state); if (data->isPlaying != isPlaying) { data->isPlaying = isPlaying; if (data->isPlaying) { preloadNext(data); } } } } void Instance::preloadNext(not_null<Data*> data) { if (!data->current || !data->playlistSlice || !data->playlistIndex) { return; } const auto nextIndex = *data->playlistIndex + 1; if (const auto item = itemByIndex(data, nextIndex)) { if (const auto media = item->media()) { if (const auto document = media->document()) { const auto isLoaded = document->loaded( DocumentData::FilePathResolveSaveFromDataSilent); if (!isLoaded) { DocumentOpenClickHandler::doOpen( document, nullptr, ActionOnLoadNone); } } } } } void Instance::handleLogout() { const auto reset = [&](AudioMsgId::Type type) { const auto data = getData(type); *data = Data(type, data->overview); }; reset(AudioMsgId::Type::Voice); reset(AudioMsgId::Type::Song); _usePanelPlayer.notify(false, true); } } // namespace Player } // namespace Media ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/media/player/media_player_instance.h���������������������������0000664�0000000�0000000�00000011774�13262451251�0026500�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "data/data_shared_media.h" class AudioMsgId; namespace Media { namespace Player { void start(); void finish(); class Instance; Instance *instance(); struct TrackState; class Instance : private base::Subscriber { public: void play(AudioMsgId::Type type); void pause(AudioMsgId::Type type); void stop(AudioMsgId::Type type); void playPause(AudioMsgId::Type type); bool next(AudioMsgId::Type type); bool previous(AudioMsgId::Type type); AudioMsgId::Type getActiveType() const; void play() { play(getActiveType()); } void pause() { pause(getActiveType()); } void stop() { stop(getActiveType()); } void playPause() { playPause(getActiveType()); } bool next() { return next(getActiveType()); } bool previous() { return previous(getActiveType()); } void playPauseCancelClicked(AudioMsgId::Type type); void play(const AudioMsgId &audioId); AudioMsgId current(AudioMsgId::Type type) const { if (auto data = getData(type)) { return data->current; } return AudioMsgId(); } bool repeatEnabled(AudioMsgId::Type type) const { if (auto data = getData(type)) { return data->repeatEnabled; } return false; } void toggleRepeat(AudioMsgId::Type type) { if (auto data = getData(type)) { data->repeatEnabled = !data->repeatEnabled; _repeatChangedNotifier.notify(type); } } bool isSeeking(AudioMsgId::Type type) const { if (auto data = getData(type)) { return (data->seeking == data->current); } return false; } void startSeeking(AudioMsgId::Type type); void stopSeeking(AudioMsgId::Type type); bool nextAvailable(AudioMsgId::Type type) const; bool previousAvailable(AudioMsgId::Type type) const; struct Switch { AudioMsgId from; FullMsgId to; }; base::Observable<Switch> &switchToNextNotifier() { return _switchToNextNotifier; } base::Observable<bool> &usePanelPlayer() { return _usePanelPlayer; } base::Observable<bool> &titleButtonOver() { return _titleButtonOver; } base::Observable<bool> &playerWidgetOver() { return _playerWidgetOver; } base::Observable<TrackState> &updatedNotifier() { return _updatedNotifier; } base::Observable<AudioMsgId::Type> &tracksFinishedNotifier() { return _tracksFinishedNotifier; } base::Observable<AudioMsgId::Type> &trackChangedNotifier() { return _trackChangedNotifier; } base::Observable<AudioMsgId::Type> &repeatChangedNotifier() { return _repeatChangedNotifier; } rpl::producer<> playlistChanges(AudioMsgId::Type type) const; void documentLoadProgress(DocumentData *document); void clear(); private: Instance(); friend void start(); using SharedMediaType = Storage::SharedMediaType; using SliceKey = SparseIdsMergedSlice::Key; struct Data { Data(AudioMsgId::Type type, SharedMediaType overview) : type(type) , overview(overview) { } AudioMsgId::Type type; Storage::SharedMediaType overview; AudioMsgId current; AudioMsgId seeking; base::optional<SparseIdsMergedSlice> playlistSlice; base::optional<SliceKey> playlistSliceKey; base::optional<SliceKey> playlistRequestedKey; base::optional<int> playlistIndex; rpl::lifetime playlistLifetime; rpl::event_stream<> playlistChanges; History *history = nullptr; History *migrated = nullptr; bool repeatEnabled = false; bool isPlaying = false; }; // Observed notifications. void handleSongUpdate(const AudioMsgId &audioId); void setCurrent(const AudioMsgId &audioId); void refreshPlaylist(not_null<Data*> data); base::optional<SliceKey> playlistKey(not_null<Data*> data) const; bool validPlaylist(not_null<Data*> data); void validatePlaylist(not_null<Data*> data); void playlistUpdated(not_null<Data*> data); bool moveInPlaylist(not_null<Data*> data, int delta, bool autonext); void preloadNext(not_null<Data*> data); HistoryItem *itemByIndex(not_null<Data*> data, int index); void handleLogout(); template <typename CheckCallback> void emitUpdate(AudioMsgId::Type type, CheckCallback check); Data *getData(AudioMsgId::Type type) { if (type == AudioMsgId::Type::Song) { return &_songData; } else if (type == AudioMsgId::Type::Voice) { return &_voiceData; } return nullptr; } const Data *getData(AudioMsgId::Type type) const { if (type == AudioMsgId::Type::Song) { return &_songData; } else if (type == AudioMsgId::Type::Voice) { return &_voiceData; } return nullptr; } Data _songData; Data _voiceData; base::Observable<Switch> _switchToNextNotifier; base::Observable<bool> _usePanelPlayer; base::Observable<bool> _titleButtonOver; base::Observable<bool> _playerWidgetOver; base::Observable<TrackState> _updatedNotifier; base::Observable<AudioMsgId::Type> _tracksFinishedNotifier; base::Observable<AudioMsgId::Type> _trackChangedNotifier; base::Observable<AudioMsgId::Type> _repeatChangedNotifier; }; } // namespace Clip } // namespace Media ����tdesktop-1.2.17/Telegram/SourceFiles/media/player/media_player_panel.cpp����������������������������0000664�0000000�0000000�00000026366�13262451251�0026331�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "media/player/media_player_panel.h" #include "media/player/media_player_cover.h" #include "media/player/media_player_instance.h" #include "info/media/info_media_list_widget.h" #include "history/history.h" #include "history/history_item.h" #include "data/data_document.h" #include "data/data_media_types.h" #include "ui/widgets/shadow.h" #include "ui/widgets/scroll_area.h" #include "mainwindow.h" #include "styles/style_overview.h" #include "styles/style_widgets.h" #include "styles/style_media_player.h" #include "styles/style_info.h" namespace Media { namespace Player { namespace { using ListWidget = Info::Media::ListWidget; constexpr auto kPlaylistIdsLimit = 32; constexpr auto kDelayedHideTimeout = TimeMs(3000); } // namespace Panel::Panel( QWidget *parent, not_null<Window::Controller*> window, Layout layout) : RpWidget(parent) , AbstractController(window) , _layout(layout) , _showTimer([this] { startShow(); }) , _hideTimer([this] { startHideChecked(); }) , _scroll(this, st::mediaPlayerScroll) { hide(); updateSize(); } bool Panel::overlaps(const QRect &globalRect) { if (isHidden() || _a_appearance.animating()) return false; auto marginLeft = rtl() ? contentRight() : contentLeft(); auto marginRight = rtl() ? contentLeft() : contentRight(); return rect().marginsRemoved(QMargins(marginLeft, contentTop(), marginRight, contentBottom())).contains(QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size())); } void Panel::windowActiveChanged() { if (!App::wnd()->windowHandle()->isActive() && !isHidden()) { leaveEvent(nullptr); } } void Panel::resizeEvent(QResizeEvent *e) { updateControlsGeometry(); } void Panel::listHeightUpdated(int newHeight) { if (newHeight > emptyInnerHeight() || _cover) { updateSize(); } else { _hideTimer.callOnce(0); } } bool Panel::contentTooSmall() const { const auto innerHeight = _scroll->widget() ? _scroll->widget()->height() : emptyInnerHeight(); return (innerHeight <= emptyInnerHeight() && !_cover); } int Panel::emptyInnerHeight() const { return st::infoMediaMargin.top() + st::overviewFileLayout.songPadding.top() + st::overviewFileLayout.songThumbSize + st::overviewFileLayout.songPadding.bottom() + st::infoMediaMargin.bottom(); } bool Panel::preventAutoHide() const { if (const auto list = static_cast<ListWidget*>(_scroll->widget())) { return list->preventAutoHide(); } return false; } void Panel::updateControlsGeometry() { auto scrollTop = contentTop(); auto width = contentWidth(); if (_cover) { _cover->resizeToWidth(width); _cover->moveToRight(contentRight(), scrollTop); scrollTop += _cover->height(); if (_scrollShadow) { _scrollShadow->resize(width, st::mediaPlayerScrollShadow.extend.bottom()); _scrollShadow->moveToRight(contentRight(), scrollTop); } } auto scrollHeight = qMax(height() - scrollTop - contentBottom() - scrollMarginBottom(), 0); if (scrollHeight > 0) { _scroll->setGeometryToRight(contentRight(), scrollTop, width, scrollHeight); } if (auto widget = static_cast<TWidget*>(_scroll->widget())) { widget->resizeToWidth(width); } } int Panel::bestPositionFor(int left) const { left -= contentLeft(); left -= st::mediaPlayerFileLayout.songPadding.left(); left -= st::mediaPlayerFileLayout.songThumbSize / 2; return left; } void Panel::scrollPlaylistToCurrentTrack() { if (const auto list = static_cast<ListWidget*>(_scroll->widget())) { const auto rect = list->getCurrentSongGeometry(); _scroll->scrollToY(rect.y() - st::infoMediaMargin.top()); } } void Panel::updateSize() { auto width = contentLeft() + st::mediaPlayerPanelWidth + contentRight(); auto height = contentTop(); if (_cover) { height += _cover->height(); } auto listHeight = 0; if (auto widget = _scroll->widget()) { listHeight = widget->height(); } auto scrollVisible = (listHeight > 0); auto scrollHeight = scrollVisible ? (qMin(listHeight, st::mediaPlayerListHeightMax) + st::mediaPlayerListMarginBottom) : 0; height += scrollHeight + contentBottom(); resize(width, height); _scroll->setVisible(scrollVisible); if (_scrollShadow) { _scrollShadow->setVisible(scrollVisible); } } void Panel::paintEvent(QPaintEvent *e) { Painter p(this); if (!_cache.isNull()) { bool animating = _a_appearance.animating(getms()); if (animating) { p.setOpacity(_a_appearance.current(_hiding ? 0. : 1.)); } else if (_hiding || isHidden()) { hideFinished(); return; } p.drawPixmap(0, 0, _cache); if (!animating) { showChildren(); _cache = QPixmap(); } return; } // draw shadow auto shadowedRect = myrtlrect(contentLeft(), contentTop(), contentWidth(), contentHeight()); auto shadowedSides = (rtl() ? RectPart::Right : RectPart::Left) | RectPart::Bottom; if (_layout != Layout::Full) { shadowedSides |= (rtl() ? RectPart::Left : RectPart::Right) | RectPart::Top; } Ui::Shadow::paint(p, shadowedRect, width(), st::defaultRoundShadow, shadowedSides); auto parts = RectPart::Full; App::roundRect(p, shadowedRect, st::menuBg, MenuCorners, nullptr, parts); } void Panel::enterEventHook(QEvent *e) { if (_ignoringEnterEvents || contentTooSmall()) return; _hideTimer.cancel(); if (_a_appearance.animating(getms())) { startShow(); } else { _showTimer.callOnce(0); } return TWidget::enterEventHook(e); } void Panel::leaveEventHook(QEvent *e) { if (preventAutoHide()) { return; } _showTimer.cancel(); if (_a_appearance.animating(getms())) { startHide(); } else { _hideTimer.callOnce(300); } return TWidget::leaveEventHook(e); } void Panel::showFromOther() { _hideTimer.cancel(); if (_a_appearance.animating(getms())) { startShow(); } else { _showTimer.callOnce(300); } } void Panel::hideFromOther() { _showTimer.cancel(); if (_a_appearance.animating(getms())) { startHide(); } else { _hideTimer.callOnce(0); } } void Panel::ensureCreated() { if (_scroll->widget()) return; if (_layout == Layout::Full) { _cover.create(this); setPinCallback(std::move(_pinCallback)); setCloseCallback(std::move(_closeCallback)); _scrollShadow.create(this, st::mediaPlayerScrollShadow, RectPart::Bottom); } _refreshListLifetime = instance()->playlistChanges( AudioMsgId::Type::Song ) | rpl::start_with_next([this] { refreshList(); }); refreshList(); if (cPlatform() == dbipMac || cPlatform() == dbipMacOld) { if (const auto window = App::wnd()) { connect( window->windowHandle(), &QWindow::activeChanged, this, &Panel::windowActiveChanged); } } _ignoringEnterEvents = false; } void Panel::refreshList() { const auto current = instance()->current(AudioMsgId::Type::Song); const auto contextId = current.contextId(); const auto peer = [&]() -> PeerData* { const auto item = contextId ? App::histItemById(contextId) : nullptr; const auto media = item ? item->media() : nullptr; const auto document = media ? media->document() : nullptr; if (!document || !document->isSharedMediaMusic()) { return nullptr; } const auto result = item->history()->peer; if (const auto migrated = result->migrateTo()) { return migrated; } return result; }(); const auto migrated = peer ? peer->migrateFrom() : nullptr; if (_listPeer != peer || _listMigratedPeer != migrated) { _scroll->takeWidget<QWidget>().destroy(); _listPeer = _listMigratedPeer = nullptr; } if (peer && !_listPeer) { _listPeer = peer; _listMigratedPeer = migrated; auto list = object_ptr<ListWidget>(this, infoController()); const auto weak = _scroll->setOwnedWidget(std::move(list)); updateSize(); updateControlsGeometry(); weak->checkForHide( ) | rpl::start_with_next([this] { if (!rect().contains(mapFromGlobal(QCursor::pos()))) { _hideTimer.callOnce(kDelayedHideTimeout); } }, weak->lifetime()); weak->heightValue( ) | rpl::start_with_next([this](int newHeight) { listHeightUpdated(newHeight); }, weak->lifetime()); weak->scrollToRequests( ) | rpl::start_with_next([this](int newScrollTop) { _scroll->scrollToY(newScrollTop); }, weak->lifetime()); using namespace rpl::mappers; rpl::combine( _scroll->scrollTopValue(), _scroll->heightValue(), tuple(_1, _1 + _2) ) | rpl::start_with_next([=](int top, int bottom) { weak->setVisibleTopBottom(top, bottom); }, weak->lifetime()); auto memento = Info::Media::Memento( peerId(), migratedPeerId(), section().mediaType()); memento.setAroundId(contextId); memento.setIdsLimit(kPlaylistIdsLimit); memento.setScrollTopItem(contextId); memento.setScrollTopShift(-st::infoMediaMargin.top()); weak->restoreState(&memento); } } void Panel::performDestroy() { if (!_scroll->widget()) return; _cover.destroy(); _scroll->takeWidget<QWidget>().destroy(); _listPeer = _listMigratedPeer = nullptr; _refreshListLifetime.destroy(); if (cPlatform() == dbipMac || cPlatform() == dbipMacOld) { if (const auto window = App::wnd()) { disconnect( window->windowHandle(), &QWindow::activeChanged, this, &Panel::windowActiveChanged); } } } void Panel::setPinCallback(ButtonCallback &&callback) { _pinCallback = std::move(callback); if (_cover) { _cover->setPinCallback(ButtonCallback(_pinCallback)); } } void Panel::setCloseCallback(ButtonCallback &&callback) { _closeCallback = std::move(callback); if (_cover) { _cover->setCloseCallback(ButtonCallback(_closeCallback)); } } Info::Key Panel::key() const { return Info::Key(_listPeer); } PeerData *Panel::migrated() const { return _listMigratedPeer; } Info::Section Panel::section() const { return Info::Section(Info::Section::MediaType::MusicFile); } void Panel::startShow() { ensureCreated(); if (contentTooSmall()) { return; } if (isHidden()) { scrollPlaylistToCurrentTrack(); show(); } else if (!_hiding) { return; } _hiding = false; startAnimation(); } void Panel::hideIgnoringEnterEvents() { _ignoringEnterEvents = true; if (isHidden()) { hideFinished(); } else { startHide(); } } void Panel::startHideChecked() { if (!contentTooSmall() && preventAutoHide()) { return; } if (isHidden()) { hideFinished(); } else { startHide(); } } void Panel::startHide() { if (_hiding || isHidden()) return; _hiding = true; startAnimation(); } void Panel::startAnimation() { auto from = _hiding ? 1. : 0.; auto to = _hiding ? 0. : 1.; if (_cache.isNull()) { showChildren(); _cache = Ui::GrabWidget(this); } hideChildren(); _a_appearance.start([this] { appearanceCallback(); }, from, to, st::defaultInnerDropdown.duration); } void Panel::appearanceCallback() { if (!_a_appearance.animating() && _hiding) { _hiding = false; hideFinished(); } else { update(); } } void Panel::hideFinished() { hide(); _cache = QPixmap(); performDestroy(); } int Panel::contentLeft() const { return st::mediaPlayerPanelMarginLeft; } int Panel::contentTop() const { return (_layout == Layout::Full) ? 0 : st::mediaPlayerPanelMarginLeft; } int Panel::contentRight() const { return (_layout == Layout::Full) ? 0 : st::mediaPlayerPanelMarginLeft; } int Panel::contentBottom() const { return st::mediaPlayerPanelMarginBottom; } int Panel::scrollMarginBottom() const { return 0;// st::mediaPlayerPanelMarginBottom; } } // namespace Player } // namespace Media ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/media/player/media_player_panel.h������������������������������0000664�0000000�0000000�00000005352�13262451251�0025766�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "base/timer.h" #include "ui/rp_widget.h" #include "info/info_controller.h" namespace Window { class Controller; } // namespace Window namespace Ui { class ScrollArea; class Shadow; } // namespace Ui namespace Media { namespace Player { class CoverWidget; class Panel : public Ui::RpWidget, private Info::AbstractController { public: enum class Layout { Full, OnlyPlaylist, }; Panel( QWidget *parent, not_null<Window::Controller*> controller, Layout layout); bool overlaps(const QRect &globalRect); void hideIgnoringEnterEvents(); void showFromOther(); void hideFromOther(); using ButtonCallback = base::lambda<void()>; void setPinCallback(ButtonCallback &&callback); void setCloseCallback(ButtonCallback &&callback); int bestPositionFor(int left) const; protected: void resizeEvent(QResizeEvent *e) override; void paintEvent(QPaintEvent *e) override; void enterEventHook(QEvent *e) override; void leaveEventHook(QEvent *e) override; private: // Info::AbstractController implementation. Info::Key key() const override; PeerData *migrated() const override; Info::Section section() const override; void startShow(); void startHide(); void startHideChecked(); bool preventAutoHide() const; void listHeightUpdated(int newHeight); int emptyInnerHeight() const; bool contentTooSmall() const; void windowActiveChanged(); void ensureCreated(); void performDestroy(); void updateControlsGeometry(); void refreshList(); void updateSize(); void appearanceCallback(); void hideFinished(); int contentLeft() const; int contentTop() const; int contentRight() const; int contentBottom() const; int scrollMarginBottom() const; int contentWidth() const { return width() - contentLeft() - contentRight(); } int contentHeight() const { return height() - contentTop() - contentBottom();; } void startAnimation(); void scrollPlaylistToCurrentTrack(); not_null<Info::AbstractController*> infoController() { return static_cast<Info::AbstractController*>(this); } Layout _layout; bool _hiding = false; QPixmap _cache; Animation _a_appearance; bool _ignoringEnterEvents = false; base::Timer _showTimer; base::Timer _hideTimer; ButtonCallback _pinCallback, _closeCallback; object_ptr<CoverWidget> _cover = { nullptr }; object_ptr<Ui::ScrollArea> _scroll; object_ptr<Ui::Shadow> _scrollShadow = { nullptr }; rpl::lifetime _refreshListLifetime; PeerData *_listPeer = nullptr; PeerData *_listMigratedPeer = nullptr; }; } // namespace Clip } // namespace Media ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/media/player/media_player_round_controller.cpp�����������������0000664�0000000�0000000�00000010563�13262451251�0030614�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "media/player/media_player_round_controller.h" #include "media/media_clip_reader.h" #include "media/media_audio.h" #include "media/player/media_player_instance.h" #include "media/view/media_clip_playback.h" #include "history/history_item.h" #include "window/window_controller.h" #include "data/data_media_types.h" #include "data/data_document.h" #include "data/data_session.h" #include "auth_session.h" namespace Media { namespace Player { struct RoundController::CreateTag { }; std::unique_ptr<RoundController> RoundController::TryStart( not_null<Window::Controller*> parent, not_null<HistoryItem*> item) { const auto media = item->media(); if (!media) { return nullptr; } const auto document = media->document(); if (!document || !document->isVideoMessage()) { return nullptr; } return std::make_unique<RoundController>(CreateTag(), parent, item); } RoundController::RoundController( CreateTag&&, not_null<Window::Controller*> parent, not_null<HistoryItem*> item) : _parent(parent) , _data(item->media()->document()) , _context(item) { Expects(_data->isVideoMessage()); subscribe(instance()->updatedNotifier(), [this](const TrackState &state) { handleAudioUpdate(state); }); _reader = Clip::MakeReader( _data, _context->fullId(), [=](Clip::Notification notification) { callback(notification); }, Clip::Reader::Mode::Video); _playback = std::make_unique<Clip::Playback>(); _playback->setValueChangedCallback([=](float64 value) { Auth().data().requestItemRepaint(_context); }); Auth().data().markMediaRead(_data); Auth().data().itemRemoved( ) | rpl::start_with_next([=](not_null<const HistoryItem*> item) { if (item == _context) { stop(State::Stopped); } }, lifetime()); Auth().data().itemRepaintRequest( ) | rpl::start_with_next([=](not_null<const HistoryItem*> item) { if (item == _context) { checkReaderState(); } }, lifetime()); } rpl::lifetime &RoundController::lifetime() { return _lifetime; } FullMsgId RoundController::contextId() const { return _context->fullId(); } void RoundController::pauseResume() { if (checkReaderState()) { _reader->pauseResumeVideo(); } } Clip::Reader *RoundController::reader() const { return _reader ? _reader.get() : nullptr; } Clip::Playback *RoundController::playback() const { return _playback.get(); } void RoundController::handleAudioUpdate(const TrackState &state) { if (state.id.type() != AudioMsgId::Type::Voice) { return; } const auto audio = _reader->audioMsgId(); const auto another = (state.id != _reader->audioMsgId()); const auto stopped = IsStoppedOrStopping(state.state); if ((another && !stopped) || (!another && stopped)) { stop(State::Stopped); return; } else if (another) { return; } if (_playback) { _playback->updateState(state); } if (IsPaused(state.state) || state.state == State::Pausing) { if (!_reader->videoPaused()) { _reader->pauseResumeVideo(); } } else { if (_reader->videoPaused()) { _reader->pauseResumeVideo(); } } } void RoundController::callback(Clip::Notification notification) { if (!_reader) { return; } switch (notification) { case Clip::NotificationReinit: { if (checkReaderState()) { Auth().data().requestItemResize(_context); } } break; case Clip::NotificationRepaint: { Auth().data().requestItemRepaint(_context); } break; } } bool RoundController::checkReaderState() { if (!_reader) { return false; } const auto state = _reader->state(); if (state == Media::Clip::State::Error) { stop(State::StoppedAtError); return false; } else if (state == Media::Clip::State::Finished) { stop(State::StoppedAtEnd); return false; } else if (_reader->ready() && !_reader->started()) { const auto size = QSize(_reader->width(), _reader->height()) / cIntRetinaFactor(); _reader->start( size.width(), size.height(), size.width(), size.height(), ImageRoundRadius::Ellipse, RectPart::AllCorners); } return true; } void RoundController::stop(State state) { if (const auto audioId = _reader->audioMsgId()) { mixer()->stop(audioId, state); } _parent->roundVideoFinished(this); } RoundController::~RoundController() = default; } // namespace Player } // namespace Media ���������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/media/player/media_player_round_controller.h�������������������0000664�0000000�0000000�00000002607�13262451251�0030261�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once class HistoryItem; class AudioMsgId; namespace Window { class Controller; } // namespace Window namespace Media { namespace Clip { class Playback; } // namespace Clip } // namespace Media namespace Media { namespace Player { struct TrackState; enum class State; class RoundController : private base::Subscriber { struct CreateTag; public: static std::unique_ptr<RoundController> TryStart( not_null<Window::Controller*> parent, not_null<HistoryItem*> item); FullMsgId contextId() const; void pauseResume(); Clip::Reader *reader() const; Clip::Playback *playback() const; rpl::lifetime &lifetime(); RoundController( CreateTag&&, not_null<Window::Controller*> parent, not_null<HistoryItem*> item); ~RoundController(); private: void stop(State state); bool checkReaderState(); void callback(Clip::Notification notification); void handleAudioUpdate(const TrackState &audioId); not_null<Window::Controller*> _parent; not_null<DocumentData*> _data; not_null<HistoryItem*> _context; Clip::ReaderPointer _reader; std::unique_ptr<Clip::Playback> _playback; rpl::lifetime _lifetime; }; } // namespace Player } // namespace Media �������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/media/player/media_player_volume_controller.cpp����������������0000664�0000000�0000000�00000013740�13262451251�0030774�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "media/player/media_player_volume_controller.h" #include "media/media_audio.h" #include "ui/widgets/buttons.h" #include "ui/widgets/shadow.h" #include "ui/widgets/continuous_sliders.h" #include "styles/style_media_player.h" #include "styles/style_widgets.h" #include "mainwindow.h" namespace Media { namespace Player { VolumeController::VolumeController(QWidget *parent) : TWidget(parent) , _slider(this, st::mediaPlayerPanelPlayback) { _slider->setMoveByWheel(true); _slider->setChangeProgressCallback([this](float64 volume) { applyVolumeChange(volume); }); _slider->setChangeFinishedCallback([this](float64 volume) { if (volume > 0) { Global::SetRememberedSongVolume(volume); } applyVolumeChange(volume); }); subscribe(Global::RefSongVolumeChanged(), [this] { if (!_slider->isChanging()) { _slider->setValue(Global::SongVolume()); } }); setVolume(Global::SongVolume()); resize(st::mediaPlayerPanelVolumeWidth, 2 * st::mediaPlayerPanelPlaybackPadding + st::mediaPlayerPanelPlayback.width); } void VolumeController::setIsVertical(bool vertical) { using Direction = Ui::MediaSlider::Direction; _slider->setDirection(vertical ? Direction::Vertical : Direction::Horizontal); _slider->setAlwaysDisplayMarker(vertical); } void VolumeController::resizeEvent(QResizeEvent *e) { _slider->setGeometry(rect()); } void VolumeController::setVolume(float64 volume) { _slider->setValue(volume); if (volume > 0) { Global::SetRememberedSongVolume(volume); } applyVolumeChange(volume); } void VolumeController::applyVolumeChange(float64 volume) { if (volume != Global::SongVolume()) { Global::SetSongVolume(volume); mixer()->setSongVolume(Global::SongVolume()); Global::RefSongVolumeChanged().notify(); } } VolumeWidget::VolumeWidget(QWidget *parent) : TWidget(parent) , _controller(this) { hide(); _controller->setIsVertical(true); _hideTimer.setSingleShot(true); connect(&_hideTimer, SIGNAL(timeout()), this, SLOT(onHideStart())); _showTimer.setSingleShot(true); connect(&_showTimer, SIGNAL(timeout()), this, SLOT(onShowStart())); if (cPlatform() == dbipMac || cPlatform() == dbipMacOld) { connect(App::wnd()->windowHandle(), SIGNAL(activeChanged()), this, SLOT(onWindowActiveChanged())); } hide(); auto margin = getMargin(); resize(margin.left() + st::mediaPlayerVolumeSize.width() + margin.right(), margin.top() + st::mediaPlayerVolumeSize.height() + margin.bottom()); } QMargins VolumeWidget::getMargin() const { return QMargins(st::mediaPlayerVolumeMargin, st::mediaPlayerPlayback.fullWidth, st::mediaPlayerVolumeMargin, st::mediaPlayerVolumeMargin); } bool VolumeWidget::overlaps(const QRect &globalRect) { if (isHidden() || _a_appearance.animating()) return false; return rect().marginsRemoved(getMargin()).contains(QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size())); } void VolumeWidget::onWindowActiveChanged() { if (!App::wnd()->windowHandle()->isActive() && !isHidden()) { leaveEvent(nullptr); } } void VolumeWidget::resizeEvent(QResizeEvent *e) { auto inner = rect().marginsRemoved(getMargin()); _controller->setGeometry(inner.x(), inner.y() - st::lineWidth, inner.width(), inner.height() + st::lineWidth - ((st::mediaPlayerVolumeSize.width() - st::mediaPlayerPanelPlayback.width) / 2)); } void VolumeWidget::paintEvent(QPaintEvent *e) { Painter p(this); if (!_cache.isNull()) { bool animating = _a_appearance.animating(getms()); if (animating) { p.setOpacity(_a_appearance.current(_hiding)); } else if (_hiding || isHidden()) { hidingFinished(); return; } p.drawPixmap(0, 0, _cache); if (!animating) { showChildren(); _cache = QPixmap(); } return; } // draw shadow auto shadowedRect = rect().marginsRemoved(getMargin()); auto shadowedSides = RectPart::Left | RectPart::Right | RectPart::Bottom; Ui::Shadow::paint(p, shadowedRect, width(), st::defaultRoundShadow, shadowedSides); auto parts = RectPart::NoTopBottom | RectPart::FullBottom; App::roundRect(p, QRect(shadowedRect.x(), -st::buttonRadius, shadowedRect.width(), shadowedRect.y() + shadowedRect.height() + st::buttonRadius), st::menuBg, MenuCorners, nullptr, parts); } void VolumeWidget::enterEventHook(QEvent *e) { _hideTimer.stop(); if (_a_appearance.animating(getms())) { onShowStart(); } else { _showTimer.start(0); } return TWidget::enterEventHook(e); } void VolumeWidget::leaveEventHook(QEvent *e) { _showTimer.stop(); if (_a_appearance.animating(getms())) { onHideStart(); } else { _hideTimer.start(300); } return TWidget::leaveEventHook(e); } void VolumeWidget::otherEnter() { _hideTimer.stop(); if (_a_appearance.animating(getms())) { onShowStart(); } else { _showTimer.start(0); } } void VolumeWidget::otherLeave() { _showTimer.stop(); if (_a_appearance.animating(getms())) { onHideStart(); } else { _hideTimer.start(0); } } void VolumeWidget::onShowStart() { if (isHidden()) { show(); } else if (!_hiding) { return; } _hiding = false; startAnimation(); } void VolumeWidget::onHideStart() { if (_hiding) return; _hiding = true; startAnimation(); } void VolumeWidget::startAnimation() { auto from = _hiding ? 1. : 0.; auto to = _hiding ? 0. : 1.; if (_cache.isNull()) { showChildren(); _cache = Ui::GrabWidget(this); } hideChildren(); _a_appearance.start([this] { appearanceCallback(); }, from, to, st::defaultInnerDropdown.duration); } void VolumeWidget::appearanceCallback() { if (!_a_appearance.animating() && _hiding) { _hiding = false; hidingFinished(); } else { update(); } } void VolumeWidget::hidingFinished() { hide(); _cache = QPixmap(); } bool VolumeWidget::eventFilter(QObject *obj, QEvent *e) { if (e->type() == QEvent::Enter) { otherEnter(); } else if (e->type() == QEvent::Leave) { otherLeave(); } return false; } } // namespace Player } // namespace Media ��������������������������������tdesktop-1.2.17/Telegram/SourceFiles/media/player/media_player_volume_controller.h������������������0000664�0000000�0000000�00000002737�13262451251�0030445�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace Ui { class IconButton; class MediaSlider; } // namespace Ui namespace Media { namespace Player { class VolumeController : public TWidget, private base::Subscriber { public: VolumeController(QWidget *parent); void setIsVertical(bool vertical); protected: void resizeEvent(QResizeEvent *e) override; private: void setVolume(float64 volume); void applyVolumeChange(float64 volume); object_ptr<Ui::MediaSlider> _slider; }; class VolumeWidget : public TWidget { Q_OBJECT public: VolumeWidget(QWidget *parent); bool overlaps(const QRect &globalRect); QMargins getMargin() const; protected: void resizeEvent(QResizeEvent *e) override; void paintEvent(QPaintEvent *e) override; void enterEventHook(QEvent *e) override; void leaveEventHook(QEvent *e) override; bool eventFilter(QObject *obj, QEvent *e) override; private slots: void onShowStart(); void onHideStart(); void onWindowActiveChanged(); private: void otherEnter(); void otherLeave(); void appearanceCallback(); void hidingFinished(); void startAnimation(); bool _hiding = false; QPixmap _cache; Animation _a_appearance; QTimer _hideTimer, _showTimer; object_ptr<VolumeController> _controller; }; } // namespace Clip } // namespace Media ���������������������������������tdesktop-1.2.17/Telegram/SourceFiles/media/player/media_player_widget.cpp���������������������������0000664�0000000�0000000�00000042041�13262451251�0026501�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "media/player/media_player_widget.h" #include "data/data_document.h" #include "ui/widgets/labels.h" #include "ui/widgets/continuous_sliders.h" #include "ui/widgets/shadow.h" #include "ui/widgets/buttons.h" #include "ui/effects/ripple_animation.h" #include "lang/lang_keys.h" #include "media/media_audio.h" #include "media/view/media_clip_playback.h" #include "media/player/media_player_button.h" #include "media/player/media_player_instance.h" #include "media/player/media_player_volume_controller.h" #include "styles/style_media_player.h" #include "styles/style_mediaview.h" #include "history/history_item.h" #include "layout.h" namespace Media { namespace Player { using ButtonState = PlayButtonLayout::State; class Widget::PlayButton : public Ui::RippleButton { public: PlayButton(QWidget *parent); void setState(PlayButtonLayout::State state) { _layout.setState(state); } void finishTransform() { _layout.finishTransform(); } protected: void paintEvent(QPaintEvent *e) override; QImage prepareRippleMask() const override; QPoint prepareRippleStartPosition() const override; private: PlayButtonLayout _layout; }; Widget::PlayButton::PlayButton(QWidget *parent) : Ui::RippleButton(parent, st::mediaPlayerButton.ripple) , _layout(st::mediaPlayerButton, [this] { update(); }) { resize(st::mediaPlayerButtonSize); setCursor(style::cur_pointer); } void Widget::PlayButton::paintEvent(QPaintEvent *e) { Painter p(this); paintRipple(p, st::mediaPlayerButton.rippleAreaPosition.x(), st::mediaPlayerButton.rippleAreaPosition.y(), getms()); p.translate(st::mediaPlayerButtonPosition.x(), st::mediaPlayerButtonPosition.y()); _layout.paint(p, st::mediaPlayerActiveFg); } QImage Widget::PlayButton::prepareRippleMask() const { auto size = QSize(st::mediaPlayerButton.rippleAreaSize, st::mediaPlayerButton.rippleAreaSize); return Ui::RippleAnimation::ellipseMask(size); } QPoint Widget::PlayButton::prepareRippleStartPosition() const { return QPoint(mapFromGlobal(QCursor::pos()) - st::mediaPlayerButton.rippleAreaPosition); } Widget::Widget(QWidget *parent) : RpWidget(parent) , _nameLabel(this, st::mediaPlayerName) , _timeLabel(this, st::mediaPlayerTime) , _playPause(this) , _volumeToggle(this, st::mediaPlayerVolumeToggle) , _repeatTrack(this, st::mediaPlayerRepeatButton) , _close(this, st::mediaPlayerClose) , _shadow(this) , _playbackSlider(this, st::mediaPlayerPlayback) , _playback(std::make_unique<Clip::Playback>()) { setAttribute(Qt::WA_OpaquePaintEvent); setMouseTracking(true); resize(width(), st::mediaPlayerHeight + st::lineWidth); _nameLabel->setAttribute(Qt::WA_TransparentForMouseEvents); _timeLabel->setAttribute(Qt::WA_TransparentForMouseEvents); _playback->setInLoadingStateChangedCallback([this](bool loading) { _playbackSlider->setDisabled(loading); }); _playback->setValueChangedCallback([this](float64 value) { _playbackSlider->setValue(value); }); _playbackSlider->setChangeProgressCallback([this](float64 value) { if (_type != AudioMsgId::Type::Song) { return; // Round video seek is not supported for now :( } _playback->setValue(value, false); handleSeekProgress(value); }); _playbackSlider->setChangeFinishedCallback([this](float64 value) { if (_type != AudioMsgId::Type::Song) { return; // Round video seek is not supported for now :( } _playback->setValue(value, false); handleSeekFinished(value); }); _playPause->setClickedCallback([this] { instance()->playPauseCancelClicked(_type); }); updateVolumeToggleIcon(); _volumeToggle->setClickedCallback([=] { Global::SetSongVolume((Global::SongVolume() > 0) ? 0. : Global::RememberedSongVolume()); mixer()->setSongVolume(Global::SongVolume()); Global::RefSongVolumeChanged().notify(); }); subscribe(Global::RefSongVolumeChanged(), [this] { updateVolumeToggleIcon(); }); updateRepeatTrackIcon(); _repeatTrack->setClickedCallback([=] { instance()->toggleRepeat(AudioMsgId::Type::Song); }); subscribe(instance()->repeatChangedNotifier(), [this](AudioMsgId::Type type) { if (type == _type) { updateRepeatTrackIcon(); } }); subscribe(instance()->updatedNotifier(), [this](const TrackState &state) { handleSongUpdate(state); }); subscribe(instance()->trackChangedNotifier(), [this](AudioMsgId::Type type) { if (type == _type) { handleSongChange(); } }); subscribe(instance()->tracksFinishedNotifier(), [this](AudioMsgId::Type type) { if (type == AudioMsgId::Type::Voice) { _voiceIsActive = false; auto currentSong = instance()->current(AudioMsgId::Type::Song); auto songState = mixer()->currentState(AudioMsgId::Type::Song); if (currentSong == songState.id && !IsStoppedOrStopping(songState.state)) { setType(AudioMsgId::Type::Song); } } }); setType(AudioMsgId::Type::Song); _playPause->finishTransform(); } void Widget::updateVolumeToggleIcon() { auto icon = []() -> const style::icon * { auto volume = Global::SongVolume(); if (volume > 0) { if (volume < 1 / 3.) { return &st::mediaPlayerVolumeIcon1; } else if (volume < 2 / 3.) { return &st::mediaPlayerVolumeIcon2; } return &st::mediaPlayerVolumeIcon3; } return nullptr; }; _volumeToggle->setIconOverride(icon()); } void Widget::setCloseCallback(base::lambda<void()> callback) { _closeCallback = std::move(callback); _close->setClickedCallback([this] { stopAndClose(); }); } void Widget::stopAndClose() { _voiceIsActive = false; if (_type == AudioMsgId::Type::Voice) { auto songData = instance()->current(AudioMsgId::Type::Song); auto songState = mixer()->currentState(AudioMsgId::Type::Song); if (songData == songState.id && !IsStoppedOrStopping(songState.state)) { instance()->stop(AudioMsgId::Type::Voice); return; } } if (_closeCallback) { _closeCallback(); } } void Widget::setShadowGeometryToLeft(int x, int y, int w, int h) { _shadow->setGeometryToLeft(x, y, w, h); } void Widget::showShadow() { _shadow->show(); _playbackSlider->setVisible(_type == AudioMsgId::Type::Song); } void Widget::hideShadow() { _shadow->hide(); _playbackSlider->hide(); } QPoint Widget::getPositionForVolumeWidget() const { auto x = _volumeToggle->x(); x += (_volumeToggle->width() - st::mediaPlayerVolumeSize.width()) / 2; if (rtl()) x = width() - x - st::mediaPlayerVolumeSize.width(); return QPoint(x, height()); } void Widget::volumeWidgetCreated(VolumeWidget *widget) { _volumeToggle->installEventFilter(widget); } Widget::~Widget() = default; void Widget::handleSeekProgress(float64 progress) { if (!_lastDurationMs) return; auto positionMs = snap(static_cast<TimeMs>(progress * _lastDurationMs), 0LL, _lastDurationMs); if (_seekPositionMs != positionMs) { _seekPositionMs = positionMs; updateTimeLabel(); instance()->startSeeking(_type); } } void Widget::handleSeekFinished(float64 progress) { if (!_lastDurationMs) return; auto positionMs = snap(static_cast<TimeMs>(progress * _lastDurationMs), 0LL, _lastDurationMs); _seekPositionMs = -1; auto state = mixer()->currentState(_type); if (state.id && state.length && state.frequency) { mixer()->seek(_type, qRound(progress * state.length * 1000. / state.frequency)); } instance()->stopSeeking(_type); } void Widget::resizeEvent(QResizeEvent *e) { auto right = st::mediaPlayerCloseRight; _close->moveToRight(right, st::mediaPlayerPlayTop); right += _close->width(); _repeatTrack->moveToRight(right, st::mediaPlayerPlayTop); right += _repeatTrack->width(); _volumeToggle->moveToRight(right, st::mediaPlayerPlayTop); right += _volumeToggle->width(); updatePlayPrevNextPositions(); _playbackSlider->setGeometry(0, height() - st::mediaPlayerPlayback.fullWidth, width(), st::mediaPlayerPlayback.fullWidth); } void Widget::paintEvent(QPaintEvent *e) { Painter p(this); auto fill = e->rect().intersected(QRect(0, 0, width(), st::mediaPlayerHeight)); if (!fill.isEmpty()) { p.fillRect(fill, st::mediaPlayerBg); } } void Widget::leaveEventHook(QEvent *e) { updateOverLabelsState(false); } void Widget::mouseMoveEvent(QMouseEvent *e) { updateOverLabelsState(e->pos()); } void Widget::mousePressEvent(QMouseEvent *e) { _labelsDown = _labelsOver; } void Widget::mouseReleaseEvent(QMouseEvent *e) { if (auto downLabels = base::take(_labelsDown)) { if (_labelsOver == downLabels) { if (_type == AudioMsgId::Type::Voice) { auto current = instance()->current(_type); if (auto item = App::histItemById(current.contextId())) { Ui::showPeerHistoryAtItem(item); } } } } } void Widget::updateOverLabelsState(QPoint pos) { auto left = getLabelsLeft(); auto right = getLabelsRight(); auto labels = myrtlrect(left, 0, width() - right - left, height() - st::mediaPlayerPlayback.fullWidth); auto over = labels.contains(pos); updateOverLabelsState(over); } void Widget::updateOverLabelsState(bool over) { _labelsOver = over; auto pressShowsItem = _labelsOver && (_type == AudioMsgId::Type::Voice); setCursor(pressShowsItem ? style::cur_pointer : style::cur_default); auto showPlaylist = over && (_type == AudioMsgId::Type::Song); instance()->playerWidgetOver().notify(showPlaylist, true); } void Widget::updatePlayPrevNextPositions() { auto left = st::mediaPlayerPlayLeft; auto top = st::mediaPlayerPlayTop; if (_previousTrack) { _previousTrack->moveToLeft(left, top); left += _previousTrack->width() + st::mediaPlayerPlaySkip; _playPause->moveToLeft(left, top); left += _playPause->width() + st::mediaPlayerPlaySkip; _nextTrack->moveToLeft(left, top); } else { _playPause->moveToLeft(left, top); } updateLabelsGeometry(); } int Widget::getLabelsLeft() const { auto result = st::mediaPlayerPlayLeft + _playPause->width(); if (_previousTrack) { result += _previousTrack->width() + st::mediaPlayerPlaySkip + _nextTrack->width() + st::mediaPlayerPlaySkip; } result += st::mediaPlayerPadding; return result; } int Widget::getLabelsRight() const { auto result = st::mediaPlayerCloseRight + _close->width(); if (_type == AudioMsgId::Type::Song) { result += _repeatTrack->width() + _volumeToggle->width(); } result += st::mediaPlayerPadding; return result; } void Widget::updateLabelsGeometry() { auto left = getLabelsLeft(); auto right = getLabelsRight(); auto widthForName = width() - left - right; widthForName -= _timeLabel->width() + 2 * st::normalFont->spacew; _nameLabel->resizeToWidth(widthForName); _nameLabel->moveToLeft(left, st::mediaPlayerNameTop - st::mediaPlayerName.style.font->ascent); _timeLabel->moveToRight(right, st::mediaPlayerNameTop - st::mediaPlayerTime.font->ascent); } void Widget::updateRepeatTrackIcon() { auto repeating = instance()->repeatEnabled(AudioMsgId::Type::Song); _repeatTrack->setIconOverride(repeating ? nullptr : &st::mediaPlayerRepeatDisabledIcon, repeating ? nullptr : &st::mediaPlayerRepeatDisabledIconOver); _repeatTrack->setRippleColorOverride(repeating ? nullptr : &st::mediaPlayerRepeatDisabledRippleBg); } void Widget::checkForTypeChange() { auto hasActiveType = [](AudioMsgId::Type type) { auto current = instance()->current(type); auto state = mixer()->currentState(type); return (current == state.id && !IsStoppedOrStopping(state.state)); }; if (hasActiveType(AudioMsgId::Type::Voice)) { _voiceIsActive = true; setType(AudioMsgId::Type::Voice); } else if (!_voiceIsActive && hasActiveType(AudioMsgId::Type::Song)) { setType(AudioMsgId::Type::Song); } } void Widget::setType(AudioMsgId::Type type) { if (_type != type) { _type = type; _repeatTrack->setVisible(_type == AudioMsgId::Type::Song); _volumeToggle->setVisible(_type == AudioMsgId::Type::Song); if (!_shadow->isHidden()) { _playbackSlider->setVisible(_type == AudioMsgId::Type::Song); } updateLabelsGeometry(); handleSongChange(); handleSongUpdate(mixer()->currentState(_type)); updateOverLabelsState(_labelsOver); _playlistChangesLifetime = instance()->playlistChanges( _type ) | rpl::start_with_next([=] { handlePlaylistUpdate(); }); } } void Widget::handleSongUpdate(const TrackState &state) { checkForTypeChange(); if (state.id.type() != _type || !state.id.audio()) { return; } if (state.id.audio()->loading()) { _playback->updateLoadingState(state.id.audio()->progress()); } else { _playback->updateState(state); } auto stopped = IsStoppedOrStopping(state.state); auto showPause = !stopped && (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting); if (instance()->isSeeking(_type)) { showPause = true; } auto buttonState = [audio = state.id.audio(), showPause] { if (audio->loading()) { return ButtonState::Cancel; } else if (showPause) { return ButtonState::Pause; } return ButtonState::Play; }; _playPause->setState(buttonState()); updateTimeText(state); } void Widget::updateTimeText(const TrackState &state) { QString time; qint64 position = 0, length = 0, display = 0; const auto frequency = state.frequency; const auto document = state.id.audio(); if (!IsStoppedOrStopping(state.state)) { display = position = state.position; length = state.length; } else if (state.length) { display = state.length; } else if (const auto song = document->song()) { display = (song->duration * frequency); } _lastDurationMs = (state.length * 1000LL) / frequency; if (document->loading()) { _time = QString::number(qRound(document->progress() * 100)) + '%'; _playbackSlider->setDisabled(true); } else { display = display / frequency; _time = formatDurationText(display); _playbackSlider->setDisabled(false); } if (_seekPositionMs < 0) { updateTimeLabel(); } } void Widget::updateTimeLabel() { auto timeLabelWidth = _timeLabel->width(); if (_seekPositionMs >= 0) { auto playAlready = _seekPositionMs / 1000LL; _timeLabel->setText(formatDurationText(playAlready)); } else { _timeLabel->setText(_time); } if (timeLabelWidth != _timeLabel->width()) { updateLabelsGeometry(); } } void Widget::handleSongChange() { const auto current = instance()->current(_type); const auto document = current.audio(); if (!current || !document) { return; } TextWithEntities textWithEntities; if (document->isVoiceMessage() || document->isVideoMessage()) { if (const auto item = App::histItemById(current.contextId())) { const auto name = App::peerName(item->fromOriginal()); const auto date = [item] { const auto parsed = ItemDateTime(item); const auto date = parsed.date(); const auto time = parsed.time().toString(cTimeFormat()); const auto today = QDateTime::currentDateTime().date(); if (date == today) { return lng_player_message_today(lt_time, time); } else if (date.addDays(1) == today) { return lng_player_message_yesterday(lt_time, time); } return lng_player_message_date( lt_date, langDayOfMonthFull(date), lt_time, time); }; textWithEntities.text = name + ' ' + date(); textWithEntities.entities.append(EntityInText( EntityInTextBold, 0, name.size(), QString())); } else { textWithEntities.text = lang(lng_media_audio); } } else { const auto song = document->song(); if (!song || song->performer.isEmpty()) { textWithEntities.text = (!song || song->title.isEmpty()) ? (document->filename().isEmpty() ? qsl("Unknown Track") : document->filename()) : song->title; } else { auto title = song->title.isEmpty() ? qsl("Unknown Track") : TextUtilities::Clean(song->title); auto dash = QString::fromUtf8(" \xe2\x80\x93 "); textWithEntities.text = song->performer + dash + title; textWithEntities.entities.append({ EntityInTextBold, 0, song->performer.size(), QString() }); } } _nameLabel->setMarkedText(textWithEntities); handlePlaylistUpdate(); } void Widget::handlePlaylistUpdate() { const auto previousEnabled = instance()->previousAvailable(_type); const auto nextEnabled = instance()->nextAvailable(_type); if (!previousEnabled && !nextEnabled) { destroyPrevNextButtons(); } else { createPrevNextButtons(); _previousTrack->setIconOverride(previousEnabled ? nullptr : &st::mediaPlayerPreviousDisabledIcon); _previousTrack->setRippleColorOverride(previousEnabled ? nullptr : &st::mediaPlayerBg); _previousTrack->setCursor(previousEnabled ? style::cur_pointer : style::cur_default); _nextTrack->setIconOverride(nextEnabled ? nullptr : &st::mediaPlayerNextDisabledIcon); _nextTrack->setRippleColorOverride(nextEnabled ? nullptr : &st::mediaPlayerBg); _nextTrack->setCursor(nextEnabled ? style::cur_pointer : style::cur_default); } } void Widget::createPrevNextButtons() { if (!_previousTrack) { _previousTrack.create(this, st::mediaPlayerPreviousButton); _previousTrack->show(); _previousTrack->setClickedCallback([=]() { instance()->previous(); }); _nextTrack.create(this, st::mediaPlayerNextButton); _nextTrack->show(); _nextTrack->setClickedCallback([=]() { instance()->next(); }); updatePlayPrevNextPositions(); } } void Widget::destroyPrevNextButtons() { if (_previousTrack) { _previousTrack.destroy(); _nextTrack.destroy(); updatePlayPrevNextPositions(); } } } // namespace Player } // namespace Media �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/media/player/media_player_widget.h�����������������������������0000664�0000000�0000000�00000005730�13262451251�0026152�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/rp_widget.h" class AudioMsgId; namespace Ui { class FlatLabel; class LabelSimple; class IconButton; class PlainShadow; class FilledSlider; } // namespace Ui namespace Media { namespace Clip { class Playback; } // namespace Clip namespace Player { class PlayButton; class VolumeWidget; struct TrackState; class Widget : public Ui::RpWidget, private base::Subscriber { public: Widget(QWidget *parent); void setCloseCallback(base::lambda<void()> callback); void stopAndClose(); void setShadowGeometryToLeft(int x, int y, int w, int h); void showShadow(); void hideShadow(); QPoint getPositionForVolumeWidget() const; void volumeWidgetCreated(VolumeWidget *widget); ~Widget(); protected: void resizeEvent(QResizeEvent *e) override; void paintEvent(QPaintEvent *e) override; void leaveEventHook(QEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; private: void handleSeekProgress(float64 progress); void handleSeekFinished(float64 progress); int getLabelsLeft() const; int getLabelsRight() const; void updateOverLabelsState(QPoint pos); void updateOverLabelsState(bool over); void updatePlayPrevNextPositions(); void updateLabelsGeometry(); void updateRepeatTrackIcon(); void createPrevNextButtons(); void destroyPrevNextButtons(); void updateVolumeToggleIcon(); void checkForTypeChange(); void setType(AudioMsgId::Type type); void handleSongUpdate(const TrackState &state); void handleSongChange(); void handlePlaylistUpdate(); void updateTimeText(const TrackState &state); void updateTimeLabel(); TimeMs _seekPositionMs = -1; TimeMs _lastDurationMs = 0; QString _time; // We display all the controls according to _type. // We switch to Type::Voice if a voice/video message is played. // We switch to Type::Song only if _voiceIsActive == false. // We change _voiceIsActive to false only manually or from tracksFinished(). AudioMsgId::Type _type = AudioMsgId::Type::Unknown; bool _voiceIsActive = false; base::lambda<void()> _closeCallback; bool _labelsOver = false; bool _labelsDown = false; class PlayButton; object_ptr<Ui::FlatLabel> _nameLabel; object_ptr<Ui::LabelSimple> _timeLabel; object_ptr<Ui::IconButton> _previousTrack = { nullptr }; object_ptr<PlayButton> _playPause; object_ptr<Ui::IconButton> _nextTrack = { nullptr }; object_ptr<Ui::IconButton> _volumeToggle; object_ptr<Ui::IconButton> _repeatTrack; object_ptr<Ui::IconButton> _close; object_ptr<Ui::PlainShadow> _shadow = { nullptr }; object_ptr<Ui::FilledSlider> _playbackSlider; std::unique_ptr<Clip::Playback> _playback; rpl::lifetime _playlistChangesLifetime; }; } // namespace Clip } // namespace Media ����������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/media/view/����������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0021454�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/media/view/media_clip_controller.cpp���������������������������0000664�0000000�0000000�00000016727�13262451251�0026526�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "media/view/media_clip_controller.h" #include "media/view/media_clip_playback.h" #include "media/view/media_clip_volume_controller.h" #include "styles/style_mediaview.h" #include "ui/widgets/labels.h" #include "ui/widgets/continuous_sliders.h" #include "ui/effects/fade_animation.h" #include "ui/widgets/buttons.h" #include "media/media_audio.h" #include "layout.h" namespace Media { namespace Clip { Controller::Controller(QWidget *parent) : TWidget(parent) , _playPauseResume(this, st::mediaviewPlayButton) , _playbackSlider(this, st::mediaviewPlayback) , _playback(std::make_unique<Playback>()) , _volumeController(this) , _fullScreenToggle(this, st::mediaviewFullScreenButton) , _playedAlready(this, st::mediaviewPlayProgressLabel) , _toPlayLeft(this, st::mediaviewPlayProgressLabel) , _fadeAnimation(std::make_unique<Ui::FadeAnimation>(this)) { _fadeAnimation->show(); _fadeAnimation->setFinishedCallback([this] { fadeFinished(); }); _fadeAnimation->setUpdatedCallback([this](float64 opacity) { fadeUpdated(opacity); }); _volumeController->setVolume(Global::VideoVolume()); connect(_playPauseResume, SIGNAL(clicked()), this, SIGNAL(playPressed())); connect(_fullScreenToggle, SIGNAL(clicked()), this, SIGNAL(toFullScreenPressed())); connect(_volumeController, SIGNAL(volumeChanged(float64)), this, SIGNAL(volumeChanged(float64))); _playback->setInLoadingStateChangedCallback([this](bool loading) { _playbackSlider->setDisabled(loading); }); _playback->setValueChangedCallback([this](float64 value) { _playbackSlider->setValue(value); }); _playbackSlider->setChangeProgressCallback([this](float64 value) { _playback->setValue(value, false); handleSeekProgress(value); // This may destroy Controller. }); _playbackSlider->setChangeFinishedCallback([this](float64 value) { _playback->setValue(value, false); handleSeekFinished(value); }); } void Controller::handleSeekProgress(float64 progress) { if (!_lastDurationMs) return; auto positionMs = snap(static_cast<TimeMs>(progress * _lastDurationMs), 0LL, _lastDurationMs); if (_seekPositionMs != positionMs) { _seekPositionMs = positionMs; refreshTimeTexts(); emit seekProgress(positionMs); // This may destroy Controller. } } void Controller::handleSeekFinished(float64 progress) { if (!_lastDurationMs) return; auto positionMs = snap(static_cast<TimeMs>(progress * _lastDurationMs), 0LL, _lastDurationMs); _seekPositionMs = -1; emit seekFinished(positionMs); refreshTimeTexts(); } void Controller::showAnimated() { startFading([this]() { _fadeAnimation->fadeIn(st::mediaviewShowDuration); }); } void Controller::hideAnimated() { startFading([this]() { _fadeAnimation->fadeOut(st::mediaviewHideDuration); }); } template <typename Callback> void Controller::startFading(Callback start) { start(); _playbackSlider->show(); } void Controller::fadeFinished() { fadeUpdated(1.); } void Controller::fadeUpdated(float64 opacity) { _playbackSlider->setFadeOpacity(opacity); } void Controller::updatePlayback(const Player::TrackState &state) { updatePlayPauseResumeState(state); _playback->updateState(state); updateTimeTexts(state); } void Controller::updatePlayPauseResumeState(const Player::TrackState &state) { auto showPause = (state.state == Player::State::Playing || state.state == Player::State::Resuming || _seekPositionMs >= 0); if (showPause != _showPause) { disconnect(_playPauseResume, SIGNAL(clicked()), this, _showPause ? SIGNAL(pausePressed()) : SIGNAL(playPressed())); _showPause = showPause; connect(_playPauseResume, SIGNAL(clicked()), this, _showPause ? SIGNAL(pausePressed()) : SIGNAL(playPressed())); _playPauseResume->setIconOverride(_showPause ? &st::mediaviewPauseIcon : nullptr, _showPause ? &st::mediaviewPauseIconOver : nullptr); } } void Controller::updateTimeTexts(const Player::TrackState &state) { qint64 position = 0, length = state.length; if (Player::IsStoppedAtEnd(state.state)) { position = state.length; } else if (!Player::IsStoppedOrStopping(state.state)) { position = state.position; } else { position = 0; } auto playFrequency = state.frequency; auto playAlready = position / playFrequency; auto playLeft = (state.length / playFrequency) - playAlready; _lastDurationMs = (state.length * 1000LL) / playFrequency; _timeAlready = formatDurationText(playAlready); auto minus = QChar(8722); _timeLeft = minus + formatDurationText(playLeft); if (_seekPositionMs < 0) { refreshTimeTexts(); } } void Controller::refreshTimeTexts() { auto alreadyChanged = false, leftChanged = false; auto timeAlready = _timeAlready; auto timeLeft = _timeLeft; if (_seekPositionMs >= 0) { auto playAlready = _seekPositionMs / 1000LL; auto playLeft = (_lastDurationMs / 1000LL) - playAlready; timeAlready = formatDurationText(playAlready); auto minus = QChar(8722); timeLeft = minus + formatDurationText(playLeft); } _playedAlready->setText(timeAlready, &alreadyChanged); _toPlayLeft->setText(timeLeft, &leftChanged); if (alreadyChanged || leftChanged) { resizeEvent(nullptr); _fadeAnimation->refreshCache(); } } void Controller::setInFullScreen(bool inFullScreen) { _fullScreenToggle->setIconOverride(inFullScreen ? &st::mediaviewFullScreenOutIcon : nullptr, inFullScreen ? &st::mediaviewFullScreenOutIconOver : nullptr); disconnect(_fullScreenToggle, SIGNAL(clicked()), this, SIGNAL(toFullScreenPressed())); disconnect(_fullScreenToggle, SIGNAL(clicked()), this, SIGNAL(fromFullScreenPressed())); auto handler = inFullScreen ? SIGNAL(fromFullScreenPressed()) : SIGNAL(toFullScreenPressed()); connect(_fullScreenToggle, SIGNAL(clicked()), this, handler); } void Controller::grabStart() { showChildren(); _playbackSlider->hide(); } void Controller::grabFinish() { hideChildren(); _playbackSlider->show(); } void Controller::resizeEvent(QResizeEvent *e) { int playTop = (height() - _playPauseResume->height()) / 2; _playPauseResume->moveToLeft(st::mediaviewPlayPauseLeft, playTop); int fullScreenTop = (height() - _fullScreenToggle->height()) / 2; _fullScreenToggle->moveToRight(st::mediaviewFullScreenLeft, fullScreenTop); _volumeController->moveToRight(st::mediaviewFullScreenLeft + _fullScreenToggle->width() + st::mediaviewVolumeLeft, (height() - _volumeController->height()) / 2); auto playbackWidth = width() - st::mediaviewPlayPauseLeft - _playPauseResume->width() - playTop - fullScreenTop - _volumeController->width() - st::mediaviewVolumeLeft - _fullScreenToggle->width() - st::mediaviewFullScreenLeft; _playbackSlider->resize(playbackWidth, st::mediaviewPlayback.seekSize.height()); _playbackSlider->moveToLeft(st::mediaviewPlayPauseLeft + _playPauseResume->width() + playTop, st::mediaviewPlaybackTop); _playedAlready->moveToLeft(st::mediaviewPlayPauseLeft + _playPauseResume->width() + playTop, st::mediaviewPlayProgressTop); _toPlayLeft->moveToRight(width() - (st::mediaviewPlayPauseLeft + _playPauseResume->width() + playTop) - playbackWidth, st::mediaviewPlayProgressTop); } void Controller::paintEvent(QPaintEvent *e) { Painter p(this); if (_fadeAnimation->paint(p)) { return; } App::roundRect(p, rect(), st::mediaviewSaveMsgBg, MediaviewSaveCorners); } void Controller::mousePressEvent(QMouseEvent *e) { e->accept(); // Don't pass event to the MediaView. } Controller::~Controller() = default; } // namespace Clip } // namespace Media �����������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/media/view/media_clip_controller.h�����������������������������0000664�0000000�0000000�00000004047�13262451251�0026163�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace Ui { class LabelSimple; class FadeAnimation; class IconButton; class MediaSlider; } // namespace Ui namespace Media { namespace Player { struct TrackState; } // namespace Player namespace Clip { class Playback; class VolumeController; class Controller : public TWidget { Q_OBJECT public: Controller(QWidget *parent); void showAnimated(); void hideAnimated(); void updatePlayback(const Player::TrackState &state); void setInFullScreen(bool inFullScreen); void grabStart() override; void grabFinish() override; ~Controller(); signals: void playPressed(); void pausePressed(); void seekProgress(TimeMs positionMs); void seekFinished(TimeMs positionMs); void volumeChanged(float64 volume); void toFullScreenPressed(); void fromFullScreenPressed(); protected: void resizeEvent(QResizeEvent *e) override; void paintEvent(QPaintEvent *e) override; void mousePressEvent(QMouseEvent *e) override; private: void handleSeekProgress(float64 progress); void handleSeekFinished(float64 progress); template <typename Callback> void startFading(Callback start); void fadeFinished(); void fadeUpdated(float64 opacity); void updatePlayPauseResumeState(const Player::TrackState &state); void updateTimeTexts(const Player::TrackState &state); void refreshTimeTexts(); bool _showPause = false; QString _timeAlready, _timeLeft; TimeMs _seekPositionMs = -1; TimeMs _lastDurationMs = 0; object_ptr<Ui::IconButton> _playPauseResume; object_ptr<Ui::MediaSlider> _playbackSlider; std::unique_ptr<Playback> _playback; object_ptr<VolumeController> _volumeController; object_ptr<Ui::IconButton> _fullScreenToggle; object_ptr<Ui::LabelSimple> _playedAlready; object_ptr<Ui::LabelSimple> _toPlayLeft; std::unique_ptr<Ui::FadeAnimation> _fadeAnimation; }; } // namespace Clip } // namespace Media �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/media/view/media_clip_playback.cpp�����������������������������0000664�0000000�0000000�00000005122�13262451251�0026114�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "media/view/media_clip_playback.h" #include "styles/style_mediaview.h" #include "media/media_audio.h" namespace Media { namespace Clip { namespace { constexpr auto kPlaybackAnimationDurationMs = TimeMs(200); } // namespace Playback::Playback() : _a_value(animation(this, &Playback::step_value)) { } void Playback::updateState(const Player::TrackState &state) { qint64 position = 0, length = state.length; auto wasInLoadingState = _inLoadingState; if (wasInLoadingState) { _inLoadingState = false; if (_inLoadingStateChanged) { _inLoadingStateChanged(false); } } _playing = !Player::IsStopped(state.state); if (Player::IsStoppedAtEnd(state.state)) { position = state.length; } else if (!Player::IsStoppedOrStopping(state.state)) { position = state.position; } else { position = 0; } auto progress = 0.; if (position > length) { progress = 1.; } else if (length) { progress = snap(float64(position) / length, 0., 1.); } auto animatedPosition = position + (state.frequency * kPlaybackAnimationDurationMs / 1000); auto animatedProgress = length ? qMax(float64(animatedPosition) / length, 0.) : 0.; if (length != _length || position != _position || wasInLoadingState) { if (auto animated = (length && _length && animatedProgress > value())) { setValue(animatedProgress, animated); } else { setValue(progress, animated); } _position = position; _length = length; } } void Playback::updateLoadingState(float64 progress) { if (!_inLoadingState) { _inLoadingState = true; if (_inLoadingStateChanged) { _inLoadingStateChanged(true); } } auto animated = (progress > value()); setValue(progress, animated); } float64 Playback::value() const { return qMin(a_value.current(), 1.); } float64 Playback::value(TimeMs ms) { _a_value.step(ms); return value(); } void Playback::setValue(float64 value, bool animated) { if (animated) { a_value.start(value); _a_value.start(); } else { a_value = anim::value(value, value); _a_value.stop(); } if (_valueChanged) { _valueChanged(a_value.current()); } } void Playback::step_value(float64 ms, bool timer) { auto dt = ms / kPlaybackAnimationDurationMs; if (dt >= 1.) { _a_value.stop(); a_value.finish(); } else { a_value.update(dt, anim::linear); } if (timer && _valueChanged) { _valueChanged(a_value.current()); } } } // namespace Clip } // namespace Media ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/media/view/media_clip_playback.h�������������������������������0000664�0000000�0000000�00000002463�13262451251�0025566�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/widgets/continuous_sliders.h" namespace Media { namespace Player { struct TrackState; } // namespace Player namespace Clip { class Playback { public: Playback(); void setValueChangedCallback(base::lambda<void(float64)> callback) { _valueChanged = std::move(callback); } void setInLoadingStateChangedCallback(base::lambda<void(bool)> callback) { _inLoadingStateChanged = std::move(callback); } void setValue(float64 value, bool animated); float64 value() const; float64 value(TimeMs ms); void updateState(const Player::TrackState &state); void updateLoadingState(float64 progress); private: void step_value(float64 ms, bool timer); // This can animate for a very long time (like in music playing), // so it should be a BasicAnimation, not an Animation. anim::value a_value; BasicAnimation _a_value; base::lambda<void(float64)> _valueChanged; bool _inLoadingState = false; base::lambda<void(bool)> _inLoadingStateChanged; int64 _position = 0; int64 _length = 0; bool _playing = false; }; } // namespace Clip } // namespace Media �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/media/view/media_clip_volume_controller.cpp��������������������0000664�0000000�0000000�00000005447�13262451251�0030112�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "media/view/media_clip_volume_controller.h" #include "styles/style_mediaview.h" namespace Media { namespace Clip { VolumeController::VolumeController(QWidget *parent) : TWidget(parent) { resize(st::mediaviewVolumeSize); setCursor(style::cur_pointer); setMouseTracking(true); } void VolumeController::setVolume(float64 volume) { _volume = volume; update(); } void VolumeController::paintEvent(QPaintEvent *e) { Painter p(this); int32 top = st::mediaviewVolumeIconTop; int32 left = (width() - st::mediaviewVolumeIcon.width()) / 2; int32 mid = left + qRound(st::mediaviewVolumeIcon.width() * _volume); int32 right = left + st::mediaviewVolumeIcon.width(); if (mid > left) { p.setClipRect(rtlrect(left, top, mid - left, st::mediaviewVolumeIcon.height(), width())); auto over = _a_over.current(getms(), _over ? 1. : 0.); if (over < 1.) { st::mediaviewVolumeOnIcon.paint(p, QPoint(left, top), width()); } if (over > 0.) { p.setOpacity(over); st::mediaviewVolumeOnIconOver.paint(p, QPoint(left, top), width()); p.setOpacity(1.); } } if (right > mid) { p.setClipRect(rtlrect(mid, top, right - mid, st::mediaviewVolumeIcon.height(), width())); st::mediaviewVolumeIcon.paint(p, QPoint(left, top), width()); } } void VolumeController::mouseMoveEvent(QMouseEvent *e) { if (_downCoord < 0) { return; } int delta = e->pos().x() - _downCoord; int left = (width() - st::mediaviewVolumeIcon.width()) / 2; float64 startFrom = snap((_downCoord - left) / float64(st::mediaviewVolumeIcon.width()), 0., 1.); float64 add = delta / float64(4 * st::mediaviewVolumeIcon.width()); auto newVolume = snap(startFrom + add, 0., 1.); changeVolume(newVolume); } void VolumeController::mousePressEvent(QMouseEvent *e) { _downCoord = snap(e->pos().x(), 0, width()); int left = (width() - st::mediaviewVolumeIcon.width()) / 2; auto newVolume = snap((_downCoord - left) / float64(st::mediaviewVolumeIcon.width()), 0., 1.); changeVolume(newVolume); } void VolumeController::changeVolume(float64 newVolume) { if (newVolume != _volume) { setVolume(newVolume); emit volumeChanged(_volume); } } void VolumeController::mouseReleaseEvent(QMouseEvent *e) { _downCoord = -1; } void VolumeController::enterEventHook(QEvent *e) { setOver(true); } void VolumeController::leaveEventHook(QEvent *e) { setOver(false); } void VolumeController::setOver(bool over) { if (_over == over) return; _over = over; auto from = _over ? 0. : 1., to = _over ? 1. : 0.; _a_over.start([this] { update(); }, from, to, st::mediaviewOverDuration); } } // namespace Clip } // namespace Media �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/media/view/media_clip_volume_controller.h����������������������0000664�0000000�0000000�00000001743�13262451251�0027552�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace Media { namespace Clip { class VolumeController : public TWidget { Q_OBJECT public: VolumeController(QWidget *parent); void setVolume(float64 volume); signals: void volumeChanged(float64 volume); protected: void paintEvent(QPaintEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void enterEventHook(QEvent *e) override; void leaveEventHook(QEvent *e) override; private: void setOver(bool over); void changeVolume(float64 newVolume); float64 _volume = 0.; int _downCoord = -1; // < 0 means mouse is not pressed bool _over = false; Animation _a_over; }; } // namespace Clip } // namespace Media �����������������������������tdesktop-1.2.17/Telegram/SourceFiles/media/view/media_view_group_thumbs.cpp�������������������������0000664�0000000�0000000�00000040236�13262451251�0027074�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "media/view/media_view_group_thumbs.h" #include "data/data_shared_media.h" #include "data/data_user_photos.h" #include "data/data_photo.h" #include "data/data_document.h" #include "data/data_media_types.h" #include "data/data_session.h" #include "history/history.h" #include "history/history_media.h" #include "auth_session.h" #include "styles/style_mediaview.h" namespace Media { namespace View { namespace { constexpr auto kThumbDuration = TimeMs(150); int Round(float64 value) { return int(std::round(value)); } using Context = GroupThumbs::Context; using Key = GroupThumbs::Key; Context ComputeContext(const SharedMediaWithLastSlice &slice, int index) { Expects(index >= 0 && index < slice.size()); const auto value = slice[index]; if (const auto photo = base::get_if<not_null<PhotoData*>>(&value)) { if (const auto peer = (*photo)->peer) { return peer->id; } return base::none; } else if (const auto msgId = base::get_if<FullMsgId>(&value)) { if (const auto item = App::histItemById(*msgId)) { if (!item->toHistoryMessage()) { return item->history()->peer->id; } else if (const auto groupId = item->groupId()) { return groupId; } } return base::none; } Unexpected("Variant in ComputeContext(SharedMediaWithLastSlice::Value)"); } Context ComputeContext(const UserPhotosSlice &slice, int index) { return peerFromUser(slice.key().userId); } Key ComputeKey(const SharedMediaWithLastSlice &slice, int index) { Expects(index >= 0 && index < slice.size()); const auto value = slice[index]; if (const auto photo = base::get_if<not_null<PhotoData*>>(&value)) { return (*photo)->id; } else if (const auto msgId = base::get_if<FullMsgId>(&value)) { return *msgId; } Unexpected("Variant in ComputeContext(SharedMediaWithLastSlice::Value)"); } Key ComputeKey(const UserPhotosSlice &slice, int index) { return slice[index]; } int ComputeThumbsLimit(int availableWidth) { const auto singleWidth = st::mediaviewGroupWidth + 2 * st::mediaviewGroupSkip; const auto currentWidth = st::mediaviewGroupWidthMax + 2 * st::mediaviewGroupSkipCurrent; const auto skipForAnimation = 2 * singleWidth; const auto leftWidth = availableWidth - currentWidth - skipForAnimation; return std::max(leftWidth / (2 * singleWidth), 1); } } // namespace class GroupThumbs::Thumb { public: enum class State { Unknown, Current, Alive, Dying, }; Thumb(Key key, ImagePtr image, base::lambda<void()> handler); int leftToUpdate() const; int rightToUpdate() const; void animateToLeft(not_null<Thumb*> next); void animateToRight(not_null<Thumb*> prev); void setState(State state); State state() const; bool removed() const; void paint(Painter &p, int x, int y, int outerWidth, float64 progress); ClickHandlerPtr getState(QPoint point) const; private: QSize wantedPixSize() const; void validateImage(); int currentLeft() const; int currentWidth() const; int finalLeft() const; int finalWidth() const; void animateTo(int left, int width); ClickHandlerPtr _link; const Key _key; ImagePtr _image; State _state = State::Alive; QPixmap _full; int _fullWidth = 0; bool _hiding = false; anim::value _left = { 0. }; anim::value _width = { 0. }; anim::value _opacity = { 0., 1. }; }; GroupThumbs::Thumb::Thumb( Key key, ImagePtr image, base::lambda<void()> handler) : _key(key) , _image(image) { _link = std::make_shared<LambdaClickHandler>(std::move(handler)); _fullWidth = std::min( wantedPixSize().width(), st::mediaviewGroupWidthMax); validateImage(); } QSize GroupThumbs::Thumb::wantedPixSize() const { const auto originalWidth = std::max(_image->width(), 1); const auto originalHeight = std::max(_image->height(), 1); const auto pixHeight = st::mediaviewGroupHeight; const auto pixWidth = originalWidth * pixHeight / originalHeight; return { pixWidth, pixHeight }; } void GroupThumbs::Thumb::validateImage() { if (!_full.isNull()) { return; } _image->load(); if (!_image->loaded()) { return; } const auto pixSize = wantedPixSize(); if (pixSize.width() > st::mediaviewGroupWidthMax) { const auto originalWidth = _image->width(); const auto originalHeight = _image->height(); const auto takeWidth = originalWidth * st::mediaviewGroupWidthMax / pixSize.width(); const auto original = _image->pixNoCache().toImage(); _full = App::pixmapFromImageInPlace(original.copy( (originalWidth - takeWidth) / 2, 0, takeWidth, originalHeight ).scaled( st::mediaviewGroupWidthMax * cIntRetinaFactor(), pixSize.height() * cIntRetinaFactor(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); } else { _full = _image->pixNoCache( pixSize.width() * cIntRetinaFactor(), pixSize.height() * cIntRetinaFactor(), Images::Option::Smooth); } } int GroupThumbs::Thumb::leftToUpdate() const { return Round(std::min(_left.from(), _left.to())); } int GroupThumbs::Thumb::rightToUpdate() const { return Round(std::max( _left.from() + _width.from(), _left.to() + _width.to())); } int GroupThumbs::Thumb::currentLeft() const { return Round(_left.current()); } int GroupThumbs::Thumb::currentWidth() const { return Round(_width.current()); } int GroupThumbs::Thumb::finalLeft() const { return Round(_left.to()); } int GroupThumbs::Thumb::finalWidth() const { return Round(_width.to()); } void GroupThumbs::Thumb::setState(State state) { const auto isNewThumb = (_state == State::Alive); _state = state; if (_state == State::Current) { if (isNewThumb) { _opacity = anim::value(1.); _left = anim::value(-_fullWidth / 2); _width = anim::value(_fullWidth); } else { _opacity.start(1.); } _hiding = false; animateTo(-_fullWidth / 2, _fullWidth); } else if (_state == State::Alive) { _opacity.start(0.7); _hiding = false; } else if (_state == State::Dying) { _opacity.start(0.); _hiding = true; _left.restart(); _width.restart(); } } void GroupThumbs::Thumb::animateTo(int left, int width) { _left.start(left); _width.start(width); } void GroupThumbs::Thumb::animateToLeft(not_null<Thumb*> next) { const auto width = st::mediaviewGroupWidth; if (_state == State::Alive) { // New item animation, start exactly from the next, move only. _left = anim::value(next->currentLeft() - width); _width = anim::value(width); } else if (_state == State::Unknown) { // Existing item animation. setState(State::Alive); } const auto skip1 = st::mediaviewGroupSkip; const auto skip2 = (next->state() == State::Current) ? st::mediaviewGroupSkipCurrent : st::mediaviewGroupSkip; animateTo(next->finalLeft() - width - skip1 - skip2, width); } void GroupThumbs::Thumb::animateToRight(not_null<Thumb*> prev) { const auto width = st::mediaviewGroupWidth; if (_state == State::Alive) { // New item animation, start exactly from the next, move only. _left = anim::value(prev->currentLeft() + prev->currentWidth()); _width = anim::value(width); } else if (_state == State::Unknown) { // Existing item animation. setState(State::Alive); } const auto skip1 = st::mediaviewGroupSkip; const auto skip2 = (prev->state() == State::Current) ? st::mediaviewGroupSkipCurrent : st::mediaviewGroupSkip; animateTo(prev->finalLeft() + prev->finalWidth() + skip1 + skip2, width); } auto GroupThumbs::Thumb::state() const -> State { return _state; } bool GroupThumbs::Thumb::removed() const { return (_state == State::Dying) && _hiding && !_opacity.current(); } void GroupThumbs::Thumb::paint( Painter &p, int x, int y, int outerWidth, float64 progress) { validateImage(); _opacity.update(progress, anim::linear); _left.update(progress, anim::linear); _width.update(progress, anim::linear); const auto left = x + currentLeft(); const auto width = currentWidth(); const auto opacity = p.opacity(); p.setOpacity(_opacity.current() * opacity); if (width == _fullWidth) { p.drawPixmap(left, y, _full); } else { const auto takeWidth = width * cIntRetinaFactor(); const auto from = QRect( (_full.width() - takeWidth) / 2, 0, takeWidth, _full.height()); const auto to = QRect(left, y, width, st::mediaviewGroupHeight); p.drawPixmap(to, _full, from); } p.setOpacity(opacity); } ClickHandlerPtr GroupThumbs::Thumb::getState(QPoint point) const { if (_state != State::Alive) { return nullptr; } const auto left = finalLeft(); const auto width = finalWidth(); return QRect(left, 0, width, st::mediaviewGroupHeight).contains(point) ? _link : nullptr; } GroupThumbs::GroupThumbs(Context context) : _context(context) { } void GroupThumbs::updateContext(Context context) { if (_context != context) { clear(); _context = context; } } template <typename Slice> void GroupThumbs::RefreshFromSlice( std::unique_ptr<GroupThumbs> &instance, const Slice &slice, int index, int availableWidth) { const auto context = ComputeContext(slice, index); if (instance) { instance->updateContext(context); } if (!context) { if (instance) { instance->resizeToWidth(availableWidth); } return; } const auto limit = ComputeThumbsLimit(availableWidth); const auto from = [&] { const auto edge = std::max(index - limit, 0); for (auto result = index; result != edge; --result) { if (ComputeContext(slice, result - 1) != context) { return result; } } return edge; }(); const auto till = [&] { const auto edge = std::min(index + limit + 1, slice.size()); for (auto result = index + 1; result != edge; ++result) { if (ComputeContext(slice, result) != context) { return result; } } return edge; }(); if (from + 1 < till) { if (!instance) { instance = std::make_unique<GroupThumbs>(context); } instance->fillItems(slice, from, index, till); instance->resizeToWidth(availableWidth); } else if (instance) { instance->clear(); instance->resizeToWidth(availableWidth); } } template <typename Slice> void GroupThumbs::fillItems( const Slice &slice, int from, int index, int till) { Expects(from <= index); Expects(index < till); Expects(from + 1 < till); const auto current = (index - from); const auto old = base::take(_items); markCacheStale(); _items.reserve(till - from); for (auto i = from; i != till; ++i) { _items.push_back(validateCacheEntry(ComputeKey(slice, i))); } animateAliveItems(current); fillDyingItems(old); startDelayedAnimation(); } void GroupThumbs::animateAliveItems(int current) { Expects(current >= 0 && current < _items.size()); _items[current]->setState(Thumb::State::Current); for (auto i = current; i != 0;) { const auto prev = _items[i]; const auto item = _items[--i]; item->animateToLeft(prev); } for (auto i = current + 1; i != _items.size(); ++i) { const auto prev = _items[i - 1]; const auto item = _items[i]; item->animateToRight(prev); } } void GroupThumbs::fillDyingItems(const std::vector<not_null<Thumb*>> &old) { _dying.reserve(_cache.size() - _items.size()); animatePreviouslyAlive(old); markRestAsDying(); } void GroupThumbs::markRestAsDying() { _dying.reserve(_cache.size() - _items.size()); for (const auto &cacheItem : _cache) { const auto &thumb = cacheItem.second; const auto state = thumb->state(); if (state == Thumb::State::Unknown) { markAsDying(thumb.get()); } } } void GroupThumbs::markAsDying(not_null<Thumb*> thumb) { thumb->setState(Thumb::State::Dying); _dying.push_back(thumb.get()); } void GroupThumbs::animatePreviouslyAlive( const std::vector<not_null<Thumb*>> &old) { auto toRight = false; for (auto i = 0; i != old.size(); ++i) { const auto item = old[i]; if (item->state() == Thumb::State::Unknown) { if (toRight) { markAsDying(item); item->animateToRight(old[i - 1]); } } else if (!toRight) { for (auto j = i; j != 0;) { const auto next = old[j]; const auto prev = old[--j]; markAsDying(prev); prev->animateToLeft(next); } toRight = true; } } } auto GroupThumbs::createThumb(Key key) -> std::unique_ptr<Thumb> { if (const auto photoId = base::get_if<PhotoId>(&key)) { const auto photo = Auth().data().photo(*photoId); return createThumb(key, photo->date ? photo->thumb : ImagePtr()); } else if (const auto msgId = base::get_if<FullMsgId>(&key)) { if (const auto item = App::histItemById(*msgId)) { if (const auto media = item->media()) { if (const auto photo = media->photo()) { return createThumb(key, photo->thumb); } else if (const auto document = media->document()) { return createThumb(key, document->thumb); } } } return createThumb(key, ImagePtr()); } Unexpected("Value of Key in GroupThumbs::createThumb()"); } auto GroupThumbs::createThumb(Key key, ImagePtr image) -> std::unique_ptr<Thumb> { const auto weak = base::make_weak(this); return std::make_unique<Thumb>(key, image, [=] { if (const auto strong = weak.get()) { strong->_activateStream.fire_copy(key); } }); } auto GroupThumbs::validateCacheEntry(Key key) -> not_null<Thumb*> { const auto i = _cache.find(key); return (i != _cache.end()) ? i->second.get() : _cache.emplace(key, createThumb(key)).first->second.get(); } void GroupThumbs::markCacheStale() { _dying.clear(); for (const auto &cacheItem : _cache) { const auto &thumb = cacheItem.second; thumb->setState(Thumb::State::Unknown); } } void GroupThumbs::Refresh( std::unique_ptr<GroupThumbs> &instance, const SharedMediaWithLastSlice &slice, int index, int availableWidth) { RefreshFromSlice(instance, slice, index, availableWidth); } void GroupThumbs::Refresh( std::unique_ptr<GroupThumbs> &instance, const UserPhotosSlice &slice, int index, int availableWidth) { RefreshFromSlice(instance, slice, index, availableWidth); } void GroupThumbs::clear() { if (_items.empty()) { return; } base::take(_items); markCacheStale(); markRestAsDying(); startDelayedAnimation(); } void GroupThumbs::startDelayedAnimation() { _animation.finish(); _waitingForAnimationStart = true; countUpdatedRect(); } void GroupThumbs::resizeToWidth(int newWidth) { _width = newWidth; } int GroupThumbs::height() const { return st::mediaviewGroupPadding.top() + st::mediaviewGroupHeight + st::mediaviewGroupPadding.bottom(); } bool GroupThumbs::hiding() const { return _items.empty(); } bool GroupThumbs::hidden() const { return hiding() && !_waitingForAnimationStart && !_animation.animating(); } void GroupThumbs::checkForAnimationStart() { if (_waitingForAnimationStart) { _waitingForAnimationStart = false; _animation.start([this] { update(); }, 0., 1., kThumbDuration); } } void GroupThumbs::update() { if (_cache.empty()) { return; } _updateRequests.fire_copy(_updatedRect); } void GroupThumbs::paint( Painter &p, int x, int y, int outerWidth, TimeMs ms) { const auto progress = _waitingForAnimationStart ? 0. : _animation.current(ms, 1.); x += (_width / 2); y += st::mediaviewGroupPadding.top(); for (auto i = _cache.begin(); i != _cache.end();) { const auto &thumb = i->second; thumb->paint(p, x, y, outerWidth, progress); if (thumb->removed()) { _dying.erase( ranges::remove( _dying, thumb.get(), [](not_null<Thumb*> thumb) { return thumb.get(); }), _dying.end()); i = _cache.erase(i); } else { ++i; } } } ClickHandlerPtr GroupThumbs::getState(QPoint point) const { point -= QPoint((_width / 2), st::mediaviewGroupPadding.top()); for (const auto &cacheItem : _cache) { const auto &thumb = cacheItem.second; if (auto link = thumb->getState(point)) { return link; } } return nullptr; } void GroupThumbs::countUpdatedRect() { if (_cache.empty()) { return; } auto min = _width; auto max = 0; const auto left = [](const auto &cacheItem) { const auto &[key, thumb] = cacheItem; return thumb->leftToUpdate(); }; const auto right = [](const auto &cacheItem) { const auto &[key, thumb] = cacheItem; return thumb->rightToUpdate(); }; accumulate_min(min, left(*ranges::max_element( _cache, std::greater<>(), left))); accumulate_max(max, right(*ranges::max_element( _cache, std::less<>(), right))); _updatedRect = QRect( min, st::mediaviewGroupPadding.top(), max - min, st::mediaviewGroupHeight); } GroupThumbs::~GroupThumbs() = default; } // namespace View } // namespace Media ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/media/view/media_view_group_thumbs.h���������������������������0000664�0000000�0000000�00000005073�13262451251�0026541�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "history/history_item_components.h" #include "base/weak_ptr.h" class SharedMediaWithLastSlice; class UserPhotosSlice; namespace Media { namespace View { class GroupThumbs : public base::has_weak_ptr { public: using Key = base::variant<PhotoId, FullMsgId>; static void Refresh( std::unique_ptr<GroupThumbs> &instance, const SharedMediaWithLastSlice &slice, int index, int availableWidth); static void Refresh( std::unique_ptr<GroupThumbs> &instance, const UserPhotosSlice &slice, int index, int availableWidth); void clear(); void resizeToWidth(int newWidth); int height() const; bool hiding() const; bool hidden() const; void checkForAnimationStart(); void paint(Painter &p, int x, int y, int outerWidth, TimeMs ms); ClickHandlerPtr getState(QPoint point) const; rpl::producer<QRect> updateRequests() const { return _updateRequests.events(); } rpl::producer<Key> activateRequests() const { return _activateStream.events(); } rpl::lifetime &lifetime() { return _lifetime; } using Context = base::optional_variant<PeerId, MessageGroupId>; GroupThumbs(Context context); ~GroupThumbs(); private: class Thumb; template <typename Slice> static void RefreshFromSlice( std::unique_ptr<GroupThumbs> &instance, const Slice &slice, int index, int availableWidth); template <typename Slice> void fillItems(const Slice &slice, int from, int index, int till); void updateContext(Context context); void markCacheStale(); not_null<Thumb*> validateCacheEntry(Key key); std::unique_ptr<Thumb> createThumb(Key key); std::unique_ptr<Thumb> createThumb(Key key, ImagePtr image); void update(); void countUpdatedRect(); void animateAliveItems(int current); void fillDyingItems(const std::vector<not_null<Thumb*>> &old); void markAsDying(not_null<Thumb*> thumb); void markRestAsDying(); void animatePreviouslyAlive(const std::vector<not_null<Thumb*>> &old); void startDelayedAnimation(); Context _context; bool _waitingForAnimationStart = true; Animation _animation; std::vector<not_null<Thumb*>> _items; std::vector<not_null<Thumb*>> _dying; base::flat_map<Key, std::unique_ptr<Thumb>> _cache; int _width = 0; QRect _updatedRect; rpl::event_stream<QRect> _updateRequests; rpl::event_stream<Key> _activateStream; rpl::lifetime _lifetime; }; } // namespace View } // namespace Media ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/media/view/mediaview.style�������������������������������������0000664�0000000�0000000�00000015066�13262451251�0024520�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ using "basic.style"; using "ui/widgets/widgets.style"; mediaviewOverDuration: 150; mediaviewPlayback: MediaSlider { width: 3px; activeFg: mediaviewPlaybackActive; inactiveFg: mediaviewPlaybackInactive; activeFgOver: mediaviewPlaybackActiveOver; inactiveFgOver: mediaviewPlaybackInactiveOver; activeFgDisabled: mediaviewPlaybackActive; inactiveFgDisabled: mediaviewPlaybackInactive; seekSize: size(11px, 11px); duration: mediaviewOverDuration; } mediaviewControllerSize: size(600px, 50px); mediaviewPlayProgressLabel: LabelSimple(defaultLabelSimple) { font: semiboldFont; textFg: mediaviewPlaybackProgressFg; } mediaviewPlayProgressTop: 11px; mediaviewPlayButton: IconButton { width: 25px; height: 24px; icon: icon {{ "media_play", mediaviewPlaybackIconFg, point(3px, 0px) }}; iconOver: icon {{ "media_play", mediaviewPlaybackIconFgOver, point(3px, 0px) }}; iconPosition: point(3px, 1px); duration: mediaviewOverDuration; } mediaviewPauseIcon: icon {{ "media_pause", mediaviewPlaybackIconFg, point(1px, 1px) }}; mediaviewPauseIconOver: icon {{ "media_pause", mediaviewPlaybackIconFgOver, point(1px, 1px) }}; mediaviewPlayPauseLeft: 17px; mediaviewFullScreenLeft: 17px; mediaviewVolumeLeft: 15px; mediaviewFullScreenButton: IconButton(mediaviewPlayButton) { icon: icon {{ "media_fullscreen_to", mediaviewPlaybackIconFg, point(0px, 0px) }}; iconOver: icon {{ "media_fullscreen_to", mediaviewPlaybackIconFgOver, point(0px, 0px) }}; iconPosition: point(0px, 1px); } mediaviewFullScreenOutIcon: icon {{ "media_fullscreen_from", mediaviewPlaybackIconFg, point(0px, 0px) }}; mediaviewFullScreenOutIconOver: icon {{ "media_fullscreen_from", mediaviewPlaybackIconFgOver, point(0px, 0px) }}; mediaviewPlaybackTop: 28px; mediaviewVolumeSize: size(44px, 20px); mediaviewVolumeIcon: icon {{ "media_volume", mediaviewPlaybackInactiveOver, point(0px, 0px) }}; mediaviewVolumeOnIcon: icon {{ "media_volume", mediaviewPlaybackActive, point(0px, 0px) }}; mediaviewVolumeOnIconOver: icon {{ "media_volume", mediaviewPlaybackActiveOver, point(0px, 0px) }}; mediaviewVolumeIconTop: 8px; mediaviewControllerRadius: 25px; mediaviewLeft: icon {{ "mediaview_next-flip_horizontal", mediaviewControlFg }}; mediaviewRight: icon {{ "mediaview_next", mediaviewControlFg }}; mediaviewClose: icon {{ "mediaview_close", mediaviewControlFg }}; mediaviewSave: icon {{ "mediaview_download", mediaviewControlFg }}; mediaviewMore: icon {{ "mediaview_more", mediaviewControlFg }}; mediaviewFileRed: icon { { size(25px, 25px), mediaviewFileBg }, { "mediaview_file_corner", mediaviewFileRedCornerFg }, }; mediaviewFileYellow: icon { { size(25px, 25px), mediaviewFileBg }, { "mediaview_file_corner", mediaviewFileYellowCornerFg }, }; mediaviewFileGreen: icon { { size(25px, 25px), mediaviewFileBg }, { "mediaview_file_corner", mediaviewFileGreenCornerFg }, }; mediaviewFileBlue: icon { { size(25px, 25px), mediaviewFileBg }, { "mediaview_file_corner", mediaviewFileBlueCornerFg }, }; mediaviewFilePadding: 18px; mediaviewFileSize: size(340px, 116px); mediaviewFileNameTop: 4px; mediaviewFileNameFont: font(semibold 14px); mediaviewFileSizeTop: 29px; mediaviewFileExtTop: 35px; mediaviewFileExtFont: font(semibold 18px); mediaviewFileExtPadding: 10px; mediaviewFileLinksTop: 57px; mediaviewFileIconSize: 80px; mediaviewFileLink: defaultLinkButton; mediaviewMenu: Menu(defaultMenu) { itemBg: mediaviewMenuBg; itemBgOver: mediaviewMenuBgOver; itemFg: mediaviewMenuFg; itemFgOver: mediaviewMenuFg; itemFgDisabled: mediaviewMenuFg; itemFgShortcut: mediaviewMenuFg; itemFgShortcutOver: mediaviewMenuFg; itemFgShortcutDisabled: mediaviewMenuFg; separatorFg: mediaviewMenuFg; ripple: RippleAnimation(defaultRippleAnimation) { color: mediaviewMenuBgRipple; } } mediaviewMenuShadow: Shadow(defaultEmptyShadow) { fallback: mediaviewMenuBg; } mediaviewPanelAnimation: PanelAnimation(defaultPanelAnimation) { fadeBg: mediaviewMenuBg; shadow: mediaviewMenuShadow; } mediaviewPopupMenu: PopupMenu(defaultPopupMenu) { shadow: mediaviewMenuShadow; menu: mediaviewMenu; animation: mediaviewPanelAnimation; } mediaviewDropdownMenu: DropdownMenu(defaultDropdownMenu) { menu: mediaviewMenu; wrap: InnerDropdown(defaultInnerDropdown) { bg: mediaviewMenuBg; animation: mediaviewPanelAnimation; scrollPadding: margins(0px, 8px, 0px, 8px); shadow: mediaviewMenuShadow; } } mediaviewSaveMsgCheck: icon {{ "mediaview_save_check", mediaviewSaveMsgFg }}; mediaviewSaveMsgPadding: margins(55px, 19px, 29px, 20px); mediaviewSaveMsgCheckPos: point(23px, 21px); mediaviewSaveMsgShowing: 200; mediaviewSaveMsgShown: 2000; mediaviewSaveMsgHiding: 2500; mediaviewSaveMsgStyle: TextStyle(defaultTextStyle) { font: font(16px); linkFont: font(16px); linkFontOver: font(16px underline); } mediaviewTextPalette: TextPalette(defaultTextPalette) { linkFg: mediaviewTextLinkFg; } mediaviewCaptionStyle: defaultTextStyle; mediaviewThickFont: semiboldFont; mediaviewFont: normalFont; mediaviewTextStyle: defaultTextStyle; mediaviewTextLeft: 16px; mediaviewTextSkip: 10px; mediaviewHeaderTop: 48px; mediaviewTextTop: 24px; mediaviewTextOpacity: 0.5; mediaviewTextOverOpacity: 1; mediaviewIconOpacity: 0.45; mediaviewIconOverOpacity: 1; mediaviewControlBgOpacity: 0.3; mediaviewControlMargin: 0px; mediaviewControlSize: 90px; mediaviewIconSize: size(60px, 56px); mediaviewWaitHide: 2000; mediaviewHideDuration: 1000; mediaviewShowDuration: 200; mediaviewFadeDuration: 150; mediaviewDeltaFromLastAction: 5px; mediaviewSwipeDistance: 80px; mediaviewCaptionPadding: margins(18px, 10px, 18px, 10px); mediaviewCaptionMargin: size(11px, 11px); mediaviewCaptionRadius: 2px; mediaviewGroupPadding: margins(0px, 14px, 0px, 14px); mediaviewGroupHeight: 80px; mediaviewGroupWidth: 56px; mediaviewGroupWidthMax: 160px; mediaviewGroupSkip: 3px; mediaviewGroupSkipCurrent: 12px; themePreviewSize: size(903px, 584px); themePreviewBg: windowBg; themePreviewOverlayOpacity: 0.8; themePreviewMargin: margins(36px, 52px, 36px, 88px); themePreviewTitleTop: 14px; themePreviewTitleFg: windowBoldFg; themePreviewTitleFont: font(17px semibold); themePreviewLoadingFont: font(16px); themePreviewLoadingFg: windowSubTextFg; themePreviewApplyButton: RoundButton(defaultActiveButton) { height: 38px; font: font(15px semibold); } themePreviewCancelButton: RoundButton(defaultLightButton) { height: 38px; font: font(15px semibold); } themePreviewButtonsSkip: 20px; themePreviewDialogsWidth: 312px; ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/mediaview.cpp��������������������������������������������������0000664�0000000�0000000�00000271374�13262451251�0022117�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "mediaview.h" #include "lang/lang_keys.h" #include "mainwidget.h" #include "mainwindow.h" #include "application.h" #include "core/file_utilities.h" #include "ui/widgets/popup_menu.h" #include "ui/widgets/buttons.h" #include "ui/text_options.h" #include "media/media_clip_reader.h" #include "media/view/media_clip_controller.h" #include "media/view/media_view_group_thumbs.h" #include "media/media_audio.h" #include "history/history.h" #include "history/history_message.h" #include "history/history_media_types.h" #include "data/data_media_types.h" #include "data/data_session.h" #include "window/themes/window_theme_preview.h" #include "window/window_peer_menu.h" #include "observer_peer.h" #include "auth_session.h" #include "messenger.h" #include "layout.h" #include "storage/file_download.h" #include "calls/calls_instance.h" #include "styles/style_mediaview.h" #include "styles/style_history.h" namespace { constexpr auto kPreloadCount = 4; // Preload X message ids before and after current. constexpr auto kIdsLimit = 48; // Preload next messages if we went further from current than that. constexpr auto kIdsPreloadAfter = 28; } // namespace struct MediaView::SharedMedia { SharedMedia(SharedMediaWithLastSlice::Key key) : key(key) { } SharedMediaWithLastSlice::Key key; rpl::lifetime lifetime; }; struct MediaView::UserPhotos { UserPhotos(UserPhotosSlice::Key key) : key(key) { } UserPhotosSlice::Key key; rpl::lifetime lifetime; }; MediaView::MediaView() : TWidget(nullptr) , _transparentBrush(style::transparentPlaceholderBrush()) , _animStarted(getms()) , _docDownload(this, lang(lng_media_download), st::mediaviewFileLink) , _docSaveAs(this, lang(lng_mediaview_save_as), st::mediaviewFileLink) , _docCancel(this, lang(lng_cancel), st::mediaviewFileLink) , _radial(animation(this, &MediaView::step_radial)) , _lastAction(-st::mediaviewDeltaFromLastAction, -st::mediaviewDeltaFromLastAction) , _a_state(animation(this, &MediaView::step_state)) , _dropdown(this, st::mediaviewDropdownMenu) , _dropdownShowTimer(this) { subscribe(Lang::Current().updated(), [this] { refreshLang(); }); TextCustomTagsMap custom; custom.insert(QChar('c'), qMakePair(textcmdStartLink(1), textcmdStopLink())); _saveMsgText.setRichText(st::mediaviewSaveMsgStyle, lang(lng_mediaview_saved), Ui::DialogTextOptions(), custom); _saveMsg = QRect(0, 0, _saveMsgText.maxWidth() + st::mediaviewSaveMsgPadding.left() + st::mediaviewSaveMsgPadding.right(), st::mediaviewSaveMsgStyle.font->height + st::mediaviewSaveMsgPadding.top() + st::mediaviewSaveMsgPadding.bottom()); _saveMsgText.setLink(1, std::make_shared<LambdaClickHandler>([this] { showSaveMsgFile(); })); connect(QApplication::desktop(), SIGNAL(resized(int)), this, SLOT(onScreenResized(int))); // While we have one mediaview for all authsessions we have to do this. auto handleAuthSessionChange = [this] { if (AuthSession::Exists()) { subscribe(Auth().downloaderTaskFinished(), [this] { if (!isHidden()) { updateControls(); } }); subscribe(Auth().calls().currentCallChanged(), [this](Calls::Call *call) { if (call && _clipController && !_videoPaused) { onVideoPauseResume(); } }); subscribe(Auth().documentUpdated, [this](DocumentData *document) { if (!isHidden()) { documentUpdated(document); } }); subscribe(Auth().messageIdChanging, [this](std::pair<not_null<HistoryItem*>, MsgId> update) { changingMsgId(update.first, update.second); }); } else { _sharedMedia = nullptr; _userPhotos = nullptr; } }; subscribe(Messenger::Instance().authSessionChanged(), [handleAuthSessionChange] { handleAuthSessionChange(); }); handleAuthSessionChange(); setWindowFlags(Qt::FramelessWindowHint | Qt::BypassWindowManagerHint | Qt::Tool | Qt::NoDropShadowWindowHint); moveToScreen(); setAttribute(Qt::WA_NoSystemBackground, true); setAttribute(Qt::WA_TranslucentBackground, true); setMouseTracking(true); hide(); createWinId(); if (cPlatform() == dbipWindows) { setWindowState(Qt::WindowFullScreen); } _saveMsgUpdater.setSingleShot(true); connect(&_saveMsgUpdater, SIGNAL(timeout()), this, SLOT(updateImage())); setAttribute(Qt::WA_AcceptTouchEvents); _touchTimer.setSingleShot(true); connect(&_touchTimer, SIGNAL(timeout()), this, SLOT(onTouchTimer())); _controlsHideTimer.setSingleShot(true); connect(&_controlsHideTimer, SIGNAL(timeout()), this, SLOT(onHideControls())); connect(_docDownload, SIGNAL(clicked()), this, SLOT(onDownload())); connect(_docSaveAs, SIGNAL(clicked()), this, SLOT(onSaveAs())); connect(_docCancel, SIGNAL(clicked()), this, SLOT(onSaveCancel())); _dropdown->setHiddenCallback([this] { dropdownHidden(); }); _dropdownShowTimer->setSingleShot(true); connect(_dropdownShowTimer, SIGNAL(timeout()), this, SLOT(onDropdown())); } void MediaView::refreshLang() { InvokeQueued(this, [this] { updateThemePreviewGeometry(); }); } void MediaView::moveToScreen() { auto widgetScreen = [&](auto &&widget) -> QScreen* { if (auto handle = widget ? widget->windowHandle() : nullptr) { return handle->screen(); } return nullptr; }; auto activeWindow = Messenger::Instance().getActiveWindow(); auto activeWindowScreen = widgetScreen(activeWindow); auto myScreen = widgetScreen(this); if (activeWindowScreen && myScreen && myScreen != activeWindowScreen) { windowHandle()->setScreen(activeWindowScreen); } auto available = activeWindow ? Sandbox::screenGeometry(activeWindow->geometry().center()) : QApplication::desktop()->screenGeometry(); if (geometry() != available) { setGeometry(available); } auto navSkip = 2 * st::mediaviewControlMargin + st::mediaviewControlSize; _closeNav = myrtlrect(width() - st::mediaviewControlMargin - st::mediaviewControlSize, st::mediaviewControlMargin, st::mediaviewControlSize, st::mediaviewControlSize); _closeNavIcon = centerrect(_closeNav, st::mediaviewClose); _leftNav = myrtlrect(st::mediaviewControlMargin, navSkip, st::mediaviewControlSize, height() - 2 * navSkip); _leftNavIcon = centerrect(_leftNav, st::mediaviewLeft); _rightNav = myrtlrect(width() - st::mediaviewControlMargin - st::mediaviewControlSize, navSkip, st::mediaviewControlSize, height() - 2 * navSkip); _rightNavIcon = centerrect(_rightNav, st::mediaviewRight); _saveMsg.moveTo((width() - _saveMsg.width()) / 2, (height() - _saveMsg.height()) / 2); } bool MediaView::fileShown() const { return !_current.isNull() || gifShown(); } bool MediaView::fileBubbleShown() const { return (!_photo && !_doc) || (_doc && !fileShown() && !_themePreviewShown); } bool MediaView::gifShown() const { if (_gif && _gif->ready()) { if (!_gif->started()) { const auto streamVideo = _doc && (_doc->isVideoFile() || _doc->isVideoMessage()); const auto pauseOnStart = (_autoplayVideoDocument != _doc); if (streamVideo && pauseOnStart && !_gif->videoPaused()) { const_cast<MediaView*>(this)->toggleVideoPaused(); } const auto rounding = (_doc && _doc->isVideoMessage()) ? ImageRoundRadius::Ellipse : ImageRoundRadius::None; _gif->start( _gif->width() / cIntRetinaFactor(), _gif->height() / cIntRetinaFactor(), _gif->width() / cIntRetinaFactor(), _gif->height() / cIntRetinaFactor(), rounding, RectPart::AllCorners); const_cast<MediaView*>(this)->_current = QPixmap(); updateMixerVideoVolume(); Global::RefVideoVolumeChanged().notify(); } return true;// _gif->state() != Media::Clip::State::Error; } return false; } void MediaView::stopGif() { _gif = nullptr; _videoPaused = _videoStopped = _videoIsSilent = false; _fullScreenVideo = false; _clipController.destroy(); disconnect(Media::Player::mixer(), SIGNAL(updated(const AudioMsgId&)), this, SLOT(onVideoPlayProgress(const AudioMsgId&))); } void MediaView::documentUpdated(DocumentData *doc) { if (fileBubbleShown() && _doc && _doc == doc) { if ((_doc->loading() && _docCancel->isHidden()) || (!_doc->loading() && !_docCancel->isHidden())) { updateControls(); } else if (_doc->loading()) { updateDocSize(); update(_docRect); } } } void MediaView::changingMsgId(not_null<HistoryItem*> row, MsgId newId) { if (row->fullId() == _msgid) { _msgid = FullMsgId(_msgid.channel, newId); refreshMediaViewer(); } } void MediaView::updateDocSize() { if (!_doc || !fileBubbleShown()) return; if (_doc->loading()) { quint64 ready = _doc->loadOffset(), total = _doc->size; QString readyStr, totalStr, mb; if (total >= 1024 * 1024) { // more than 1 mb qint64 readyTenthMb = (ready * 10 / (1024 * 1024)), totalTenthMb = (total * 10 / (1024 * 1024)); readyStr = QString::number(readyTenthMb / 10) + '.' + QString::number(readyTenthMb % 10); totalStr = QString::number(totalTenthMb / 10) + '.' + QString::number(totalTenthMb % 10); mb = qsl("MB"); } else if (total >= 1024) { qint64 readyKb = (ready / 1024), totalKb = (total / 1024); readyStr = QString::number(readyKb); totalStr = QString::number(totalKb); mb = qsl("KB"); } else { readyStr = QString::number(ready); totalStr = QString::number(total); mb = qsl("B"); } _docSize = lng_media_save_progress(lt_ready, readyStr, lt_total, totalStr, lt_mb, mb); } else { _docSize = formatSizeText(_doc->size); } _docSizeWidth = st::mediaviewFont->width(_docSize); int32 maxw = st::mediaviewFileSize.width() - st::mediaviewFileIconSize - st::mediaviewFilePadding * 3; if (_docSizeWidth > maxw) { _docSize = st::mediaviewFont->elided(_docSize, maxw); _docSizeWidth = st::mediaviewFont->width(_docSize); } } void MediaView::refreshNavVisibility() { if (_sharedMediaData) { _leftNavVisible = _index && (*_index > 0); _rightNavVisible = _index && (*_index + 1 < _sharedMediaData->size()); } else if (_userPhotosData) { _leftNavVisible = _index && (*_index > 0); _rightNavVisible = _index && (*_index + 1 < _userPhotosData->size()); } else { _leftNavVisible = false; _rightNavVisible = false; } } void MediaView::updateControls() { if (_doc && fileBubbleShown()) { if (_doc->loading()) { _docDownload->hide(); _docSaveAs->hide(); _docCancel->moveToLeft(_docRect.x() + 2 * st::mediaviewFilePadding + st::mediaviewFileIconSize, _docRect.y() + st::mediaviewFilePadding + st::mediaviewFileLinksTop); _docCancel->show(); } else { if (_doc->loaded(DocumentData::FilePathResolveChecked)) { _docDownload->hide(); _docSaveAs->moveToLeft(_docRect.x() + 2 * st::mediaviewFilePadding + st::mediaviewFileIconSize, _docRect.y() + st::mediaviewFilePadding + st::mediaviewFileLinksTop); _docSaveAs->show(); _docCancel->hide(); } else { _docDownload->moveToLeft(_docRect.x() + 2 * st::mediaviewFilePadding + st::mediaviewFileIconSize, _docRect.y() + st::mediaviewFilePadding + st::mediaviewFileLinksTop); _docDownload->show(); _docSaveAs->moveToLeft(_docRect.x() + 2.5 * st::mediaviewFilePadding + st::mediaviewFileIconSize + _docDownload->width(), _docRect.y() + st::mediaviewFilePadding + st::mediaviewFileLinksTop); _docSaveAs->show(); _docCancel->hide(); } } updateDocSize(); } else { _docDownload->hide(); _docSaveAs->hide(); _docCancel->hide(); } radialStart(); updateThemePreviewGeometry(); _saveVisible = ((_photo && _photo->loaded()) || (_doc && (_doc->loaded(DocumentData::FilePathResolveChecked) || (!fileShown() && (_photo || _doc))))); _saveNav = myrtlrect(width() - st::mediaviewIconSize.width() * 2, height() - st::mediaviewIconSize.height(), st::mediaviewIconSize.width(), st::mediaviewIconSize.height()); _saveNavIcon = centerrect(_saveNav, st::mediaviewSave); _moreNav = myrtlrect(width() - st::mediaviewIconSize.width(), height() - st::mediaviewIconSize.height(), st::mediaviewIconSize.width(), st::mediaviewIconSize.height()); _moreNavIcon = centerrect(_moreNav, st::mediaviewMore); const auto dNow = QDateTime::currentDateTime(); const auto d = [&] { if (_photo) { return ParseDateTime(_photo->date); } else if (_doc) { return ParseDateTime(_doc->date); } else if (const auto item = App::histItemById(_msgid)) { return ItemDateTime(item); } return dNow; }(); if (d.date() == dNow.date()) { _dateText = lng_mediaview_today(lt_time, d.time().toString(cTimeFormat())); } else if (d.date().addDays(1) == dNow.date()) { _dateText = lng_mediaview_yesterday(lt_time, d.time().toString(cTimeFormat())); } else { _dateText = lng_mediaview_date_time(lt_date, d.date().toString(qsl("dd.MM.yy")), lt_time, d.time().toString(cTimeFormat())); } if (_from) { _fromName.setText(st::mediaviewTextStyle, (_from->migrateTo() ? _from->migrateTo() : _from)->name, Ui::NameTextOptions()); _nameNav = myrtlrect(st::mediaviewTextLeft, height() - st::mediaviewTextTop, qMin(_fromName.maxWidth(), width() / 3), st::mediaviewFont->height); _dateNav = myrtlrect(st::mediaviewTextLeft + _nameNav.width() + st::mediaviewTextSkip, height() - st::mediaviewTextTop, st::mediaviewFont->width(_dateText), st::mediaviewFont->height); } else { _nameNav = QRect(); _dateNav = myrtlrect(st::mediaviewTextLeft, height() - st::mediaviewTextTop, st::mediaviewFont->width(_dateText), st::mediaviewFont->height); } updateHeader(); refreshNavVisibility(); resizeCenteredControls(); updateOver(mapFromGlobal(QCursor::pos())); update(); } void MediaView::resizeCenteredControls() { const auto bottomSkip = std::max( _dateNav.left() + _dateNav.width(), _headerNav.left() + _headerNav.width()) + st::mediaviewCaptionMargin.width(); _groupThumbsAvailableWidth = std::max( width() - 2 * bottomSkip, st::msgMinWidth + st::mediaviewCaptionPadding.left() + st::mediaviewCaptionPadding.right()); _groupThumbsLeft = (width() - _groupThumbsAvailableWidth) / 2; refreshGroupThumbs(); _groupThumbsTop = _groupThumbs ? (height() - _groupThumbs->height()) : 0; refreshClipControllerGeometry(); refreshCaptionGeometry(); } void MediaView::refreshCaptionGeometry() { if (_caption.isEmpty()) { _captionRect = QRect(); return; } if (_groupThumbs && _groupThumbs->hiding()) { _groupThumbs = nullptr; _groupThumbsRect = QRect(); } const auto captionBottom = _clipController ? (_clipController->y() - st::mediaviewCaptionMargin.height()) : _groupThumbs ? _groupThumbsTop : height() - st::mediaviewCaptionMargin.height(); const auto captionWidth = std::min( _groupThumbsAvailableWidth - st::mediaviewCaptionPadding.left() - st::mediaviewCaptionPadding.right(), _caption.maxWidth()); const auto captionHeight = std::min( _caption.countHeight(captionWidth), height() / 4 - st::mediaviewCaptionPadding.top() - st::mediaviewCaptionPadding.bottom() - 2 * st::mediaviewCaptionMargin.height()); _captionRect = QRect( (width() - captionWidth) / 2, captionBottom - captionHeight - st::mediaviewCaptionPadding.bottom(), captionWidth, captionHeight); } void MediaView::updateActions() { _actions.clear(); if (_doc && _doc->loading()) { _actions.push_back({ lang(lng_cancel), SLOT(onSaveCancel()) }); } if (IsServerMsgId(_msgid.msg)) { _actions.push_back({ lang(lng_context_to_msg), SLOT(onToMessage()) }); } if (_doc && !_doc->filepath(DocumentData::FilePathResolveChecked).isEmpty()) { _actions.push_back({ lang((cPlatform() == dbipMac || cPlatform() == dbipMacOld) ? lng_context_show_in_finder : lng_context_show_in_folder), SLOT(onShowInFolder()) }); } if ((_doc && fileShown()) || (_photo && _photo->loaded())) { _actions.push_back({ lang(lng_mediaview_copy), SLOT(onCopy()) }); } if (_canForwardItem) { _actions.push_back({ lang(lng_mediaview_forward), SLOT(onForward()) }); } auto canDelete = [&] { if (_canDeleteItem) { return true; } else if (!_msgid && _photo && App::self() && _user == App::self()) { return _userPhotosData && _fullIndex && _fullCount; } else if (_photo && _photo->peer && _photo->peer->userpicPhotoId() == _photo->id) { if (auto chat = _photo->peer->asChat()) { return chat->canEdit(); } else if (auto channel = _photo->peer->asChannel()) { return channel->canEditInformation(); } } return false; }(); if (canDelete) { _actions.push_back({ lang(lng_mediaview_delete), SLOT(onDelete()) }); } _actions.push_back({ lang(lng_mediaview_save_as), SLOT(onSaveAs()) }); if (const auto overviewType = computeOverviewType()) { _actions.push_back({ lang(_doc ? lng_mediaview_files_all : lng_mediaview_photos_all), SLOT(onOverview()) }); } } auto MediaView::computeOverviewType() const -> base::optional<SharedMediaType> { if (const auto mediaType = sharedMediaType()) { if (const auto overviewType = SharedMediaOverviewType(*mediaType)) { return overviewType; } else if (mediaType == SharedMediaType::PhotoVideo) { if (_photo) { return SharedMediaOverviewType(SharedMediaType::Photo); } else if (_doc) { return SharedMediaOverviewType(SharedMediaType::Video); } } } return base::none; } void MediaView::step_state(TimeMs ms, bool timer) { bool result = false; for (Showing::iterator i = _animations.begin(); i != _animations.end();) { TimeMs start = i.value(); switch (i.key()) { case OverLeftNav: update(_leftNav); break; case OverRightNav: update(_rightNav); break; case OverName: update(_nameNav); break; case OverDate: update(_dateNav); break; case OverHeader: update(_headerNav); break; case OverClose: update(_closeNav); break; case OverSave: update(_saveNav); break; case OverIcon: update(_docIconRect); break; case OverMore: update(_moreNav); break; default: break; } float64 dt = float64(ms - start) / st::mediaviewFadeDuration; if (dt >= 1) { _animOpacities.remove(i.key()); i = _animations.erase(i); } else { _animOpacities[i.key()].update(dt, anim::linear); ++i; } } if (_controlsState == ControlsShowing || _controlsState == ControlsHiding) { float64 dt = float64(ms - _controlsAnimStarted) / (_controlsState == ControlsShowing ? st::mediaviewShowDuration : st::mediaviewHideDuration); if (dt >= 1) { a_cOpacity.finish(); _controlsState = (_controlsState == ControlsShowing ? ControlsShown : ControlsHidden); updateCursor(); } else { a_cOpacity.update(dt, anim::linear); } const auto toUpdate = QRegion() + (_over == OverLeftNav ? _leftNav : _leftNavIcon) + (_over == OverRightNav ? _rightNav : _rightNavIcon) + (_over == OverClose ? _closeNav : _closeNavIcon) + _saveNavIcon + _moreNavIcon + _headerNav + _nameNav + _dateNav + _captionRect.marginsAdded(st::mediaviewCaptionPadding) + _groupThumbsRect; update(toUpdate); if (dt < 1) result = true; } if (!result && _animations.isEmpty()) { _a_state.stop(); } } void MediaView::updateCursor() { setCursor(_controlsState == ControlsHidden ? Qt::BlankCursor : (_over == OverNone ? style::cur_default : style::cur_pointer)); } float64 MediaView::radialProgress() const { if (_doc) { return _doc->progress(); } else if (_photo) { return _photo->full->progress(); } return 1.; } bool MediaView::radialLoading() const { if (_doc) { return _doc->loading(); } else if (_photo) { return _photo->full->loading(); } return false; } QRect MediaView::radialRect() const { if (_doc) { return _docIconRect; } else if (_photo) { return _photoRadialRect; } return QRect(); } void MediaView::radialStart() { if (radialLoading() && !_radial.animating()) { _radial.start(radialProgress()); if (auto shift = radialTimeShift()) { _radial.update(radialProgress(), !radialLoading(), getms() + shift); } } } TimeMs MediaView::radialTimeShift() const { return _photo ? st::radialDuration : 0; } void MediaView::step_radial(TimeMs ms, bool timer) { if (!_doc && !_photo) { _radial.stop(); return; } const auto wasAnimating = _radial.animating(); _radial.update(radialProgress(), !radialLoading(), ms + radialTimeShift()); if (timer && (wasAnimating || _radial.animating())) { update(radialRect()); } const auto ready = _doc && _doc->loaded(); const auto streamVideo = ready && (_doc->isAnimation() || _doc->isVideoFile()); const auto tryOpenImage = ready && (_doc->size < App::kImageSizeLimit); if (ready && ((tryOpenImage && !_radial.animating()) || streamVideo)) { if (_doc->isVideoFile() || _doc->isVideoMessage()) { _autoplayVideoDocument = _doc; } if (!_doc->data().isEmpty() && streamVideo) { displayDocument(_doc, App::histItemById(_msgid)); } else { auto &location = _doc->location(true); if (location.accessEnable()) { if (streamVideo || _doc->isTheme() || QImageReader(location.name()).canRead()) { displayDocument(_doc, App::histItemById(_msgid)); } location.accessDisable(); } } } } void MediaView::zoomIn() { int32 newZoom = _zoom; if (newZoom == ZoomToScreenLevel) { if (qCeil(_zoomToScreen) <= MaxZoomLevel) { newZoom = qCeil(_zoomToScreen); } } else { if (newZoom < _zoomToScreen && (newZoom + 1 > _zoomToScreen || (_zoomToScreen > MaxZoomLevel && newZoom == MaxZoomLevel))) { newZoom = ZoomToScreenLevel; } else if (newZoom < MaxZoomLevel) { ++newZoom; } } zoomUpdate(newZoom); } void MediaView::zoomOut() { int32 newZoom = _zoom; if (newZoom == ZoomToScreenLevel) { if (qFloor(_zoomToScreen) >= -MaxZoomLevel) { newZoom = qFloor(_zoomToScreen); } } else { if (newZoom > _zoomToScreen && (newZoom - 1 < _zoomToScreen || (_zoomToScreen < -MaxZoomLevel && newZoom == -MaxZoomLevel))) { newZoom = ZoomToScreenLevel; } else if (newZoom > -MaxZoomLevel) { --newZoom; } } zoomUpdate(newZoom); } void MediaView::zoomReset() { int32 newZoom = _zoom; if (_zoom == 0) { if (qFloor(_zoomToScreen) == qCeil(_zoomToScreen) && qRound(_zoomToScreen) >= -MaxZoomLevel && qRound(_zoomToScreen) <= MaxZoomLevel) { newZoom = qRound(_zoomToScreen); } else { newZoom = ZoomToScreenLevel; } } else { newZoom = 0; } _x = -_width / 2; _y = -((gifShown() ? _gif->height() : (_current.height() / cIntRetinaFactor())) / 2); float64 z = (_zoom == ZoomToScreenLevel) ? _zoomToScreen : _zoom; if (z >= 0) { _x = qRound(_x * (z + 1)); _y = qRound(_y * (z + 1)); } else { _x = qRound(_x / (-z + 1)); _y = qRound(_y / (-z + 1)); } _x += width() / 2; _y += height() / 2; update(); zoomUpdate(newZoom); } void MediaView::zoomUpdate(int32 &newZoom) { if (newZoom != ZoomToScreenLevel) { while ((newZoom < 0 && (-newZoom + 1) > _w) || (-newZoom + 1) > _h) { ++newZoom; } } setZoomLevel(newZoom); } void MediaView::clearData() { if (!isHidden()) { hide(); } if (!_animations.isEmpty()) { _animations.clear(); _a_state.stop(); } if (!_animOpacities.isEmpty()) _animOpacities.clear(); stopGif(); delete _menu; _menu = nullptr; setContext(base::none); _from = nullptr; _photo = nullptr; _doc = nullptr; _fullScreenVideo = false; _caption.clear(); } MediaView::~MediaView() { delete base::take(_menu); } void MediaView::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { setCursor((active || ClickHandler::getPressed()) ? style::cur_pointer : style::cur_default); update(QRegion(_saveMsg) + _captionRect); } void MediaView::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) { setCursor((pressed || ClickHandler::getActive()) ? style::cur_pointer : style::cur_default); update(QRegion(_saveMsg) + _captionRect); } void MediaView::showSaveMsgFile() { File::ShowInFolder(_saveMsgFilename); } void MediaView::updateMixerVideoVolume() const { if (_doc && (_doc->isVideoFile() || _doc->isVideoMessage())) { Media::Player::mixer()->setVideoVolume(Global::VideoVolume()); } } void MediaView::close() { Messenger::Instance().hideMediaView(); } void MediaView::activateControls() { if (!_menu && !_mousePressed) { _controlsHideTimer.start(int(st::mediaviewWaitHide)); } if (_fullScreenVideo) { if (_clipController) { _clipController->showAnimated(); } } if (_controlsState == ControlsHiding || _controlsState == ControlsHidden) { _controlsState = ControlsShowing; _controlsAnimStarted = getms(); a_cOpacity.start(1); if (!_a_state.animating()) _a_state.start(); } } void MediaView::onHideControls(bool force) { if (!force) { if (!_dropdown->isHidden() || _menu || _mousePressed || (_fullScreenVideo && _clipController && _clipController->geometry().contains(_lastMouseMovePos))) { return; } } if (_fullScreenVideo) { if (_clipController) { _clipController->hideAnimated(); } } if (_controlsState == ControlsHiding || _controlsState == ControlsHidden) return; _lastMouseMovePos = mapFromGlobal(QCursor::pos()); _controlsState = ControlsHiding; _controlsAnimStarted = getms(); a_cOpacity.start(0); if (!_a_state.animating()) _a_state.start(); } void MediaView::dropdownHidden() { setFocus(); _ignoringDropdown = true; _lastMouseMovePos = mapFromGlobal(QCursor::pos()); updateOver(_lastMouseMovePos); _ignoringDropdown = false; if (!_controlsHideTimer.isActive()) { onHideControls(true); } } void MediaView::onScreenResized(int screen) { if (isHidden()) return; bool ignore = false; auto screens = QApplication::screens(); if (screen >= 0 && screen < screens.size()) { if (auto screenHandle = windowHandle()->screen()) { if (screens.at(screen) != screenHandle) { ignore = true; } } } if (!ignore) { moveToScreen(); auto item = (_msgid ? App::histItemById(_msgid) : nullptr); if (_photo) { displayPhoto(_photo, item); } else if (_doc) { displayDocument(_doc, item); } } } void MediaView::onToMessage() { if (auto item = _msgid ? App::histItemById(_msgid) : 0) { close(); Ui::showPeerHistoryAtItem(item); } } void MediaView::onSaveAs() { QString file; if (_doc) { const FileLocation &location(_doc->location(true)); if (!_doc->data().isEmpty() || location.accessEnable()) { QFileInfo alreadyInfo(location.name()); QDir alreadyDir(alreadyInfo.dir()); QString name = alreadyInfo.fileName(), filter; MimeType mimeType = mimeTypeForName(_doc->mimeString()); QStringList p = mimeType.globPatterns(); QString pattern = p.isEmpty() ? QString() : p.front(); if (name.isEmpty()) { name = pattern.isEmpty() ? qsl(".unknown") : pattern.replace('*', QString()); } if (pattern.isEmpty()) { filter = QString(); } else { filter = mimeType.filterString() + qsl(";;") + FileDialog::AllFilesFilter(); } psBringToBack(this); file = FileNameForSave(lang(lng_save_file), filter, qsl("doc"), name, true, alreadyDir); psShowOverAll(this); if (!file.isEmpty() && file != location.name()) { if (_doc->data().isEmpty()) { QFile(file).remove(); QFile(location.name()).copy(file); } else { QFile f(file); f.open(QIODevice::WriteOnly); f.write(_doc->data()); } } if (_doc->data().isEmpty()) location.accessDisable(); } else { if (!fileShown()) { DocumentSaveClickHandler::doSave(_doc, true); updateControls(); } else { _saveVisible = false; update(_saveNav); } updateOver(_lastMouseMovePos); } } else { if (!_photo || !_photo->loaded()) return; psBringToBack(this); auto filter = qsl("JPEG Image (*.jpg);;") + FileDialog::AllFilesFilter(); FileDialog::GetWritePath( lang(lng_save_photo), filter, filedialogDefaultName( qsl("photo"), qsl(".jpg"), QString(), false, _photo->date), base::lambda_guarded(this, [this, photo = _photo](const QString &result) { if (!result.isEmpty() && _photo == photo && photo->loaded()) { photo->full->pix().toImage().save(result, "JPG"); } psShowOverAll(this); }), base::lambda_guarded(this, [this] { psShowOverAll(this); })); } activateWindow(); Sandbox::setActiveWindow(this); setFocus(); } void MediaView::onDocClick() { if (_doc->loading()) { onSaveCancel(); } else { DocumentOpenClickHandler::doOpen(_doc, nullptr, ActionOnLoadNone); if (_doc->loading() && !_radial.animating()) { _radial.start(_doc->progress()); } } } void MediaView::clipCallback(Media::Clip::Notification notification) { using namespace Media::Clip; if (!_gif) return; switch (notification) { case NotificationReinit: { if (auto item = App::histItemById(_msgid)) { if (_gif->state() == State::Error) { stopGif(); updateControls(); update(); break; } else if (_gif->state() == State::Finished) { _videoPositionMs = _videoDurationMs; _videoStopped = true; updateSilentVideoPlaybackState(); } else { _videoIsSilent = _doc && (_doc->isVideoFile() || _doc->isVideoMessage()) && !_gif->hasAudio(); _videoDurationMs = _gif->getDurationMs(); _videoPositionMs = _gif->getPositionMs(); if (_videoIsSilent) { updateSilentVideoPlaybackState(); } } displayDocument(_doc, item); } else { stopGif(); updateControls(); update(); } } break; case NotificationRepaint: { if (!_gif->currentDisplayed()) { _videoPositionMs = _gif->getPositionMs(); if (_videoIsSilent) { updateSilentVideoPlaybackState(); } update(_x, _y, _w, _h); } } break; } } PeerData *MediaView::ui_getPeerForMouseAction() { return _history ? _history->peer.get() : nullptr; } void MediaView::onDownload() { if (Global::AskDownloadPath()) { return onSaveAs(); } QString path; if (Global::DownloadPath().isEmpty()) { path = psDownloadPath(); } else if (Global::DownloadPath() == qsl("tmp")) { path = cTempDir(); } else { path = Global::DownloadPath(); } QString toName; if (_doc) { const FileLocation &location(_doc->location(true)); if (location.accessEnable()) { if (!QDir().exists(path)) QDir().mkpath(path); toName = filedialogNextFilename( _doc->filename(), location.name(), path); if (!toName.isEmpty() && toName != location.name()) { QFile(toName).remove(); if (!QFile(location.name()).copy(toName)) { toName = QString(); } } location.accessDisable(); } else { if (!fileShown()) { DocumentSaveClickHandler::doSave(_doc); updateControls(); } else { _saveVisible = false; update(_saveNav); } updateOver(_lastMouseMovePos); } } else { if (!_photo || !_photo->loaded()) { _saveVisible = false; update(_saveNav); } else { if (!QDir().exists(path)) QDir().mkpath(path); toName = filedialogDefaultName(qsl("photo"), qsl(".jpg"), path); if (!_photo->full->pix().toImage().save(toName, "JPG")) { toName = QString(); } } } if (!toName.isEmpty()) { _saveMsgFilename = toName; _saveMsgStarted = getms(); _saveMsgOpacity.start(1); updateImage(); } } void MediaView::onSaveCancel() { if (_doc && _doc->loading()) { _doc->cancel(); } } void MediaView::onShowInFolder() { if (!_doc) return; auto filepath = _doc->filepath(DocumentData::FilePathResolveChecked); if (!filepath.isEmpty()) { File::ShowInFolder(filepath); } } void MediaView::onForward() { auto item = App::histItemById(_msgid); if (!item || !IsServerMsgId(item->id) || item->serviceMsg()) { return; } close(); Window::ShowForwardMessagesBox({ 1, item->fullId() }); } void MediaView::onDelete() { close(); const auto deletingPeerPhoto = [this] { if (!_msgid) { return true; } if (_photo && _history) { if (_history->peer->userpicPhotoId() == _photo->id) { return _firstOpenedPeerPhoto; } } return false; }; if (deletingPeerPhoto()) { App::main()->deletePhotoLayer(_photo); } else { App::main()->deleteLayer(_msgid); } } void MediaView::onOverview() { if (_menu) _menu->hideMenu(true); update(); if (const auto overviewType = computeOverviewType()) { close(); SharedMediaShowOverview(*overviewType, _history); } } void MediaView::onCopy() { _dropdown->hideAnimated(Ui::DropdownMenu::HideOption::IgnoreShow); if (_doc) { if (!_current.isNull()) { QApplication::clipboard()->setPixmap(_current); } else if (gifShown()) { QApplication::clipboard()->setPixmap(_gif->frameOriginal()); } } else { if (!_photo || !_photo->loaded()) return; QApplication::clipboard()->setPixmap(_photo->full->pix()); } } base::optional<MediaView::SharedMediaType> MediaView::sharedMediaType() const { using Type = SharedMediaType; if (auto item = App::histItemById(_msgid)) { if (_photo) { if (item->toHistoryMessage()) { return Type::PhotoVideo; } return Type::ChatPhoto; } else if (_doc) { if (_doc->isGifv()) { return Type::GIF; } else if (_doc->isVideoFile()) { return Type::PhotoVideo; } return Type::File; } } return base::none; } base::optional<MediaView::SharedMediaKey> MediaView::sharedMediaKey() const { if (!_msgid && _peer && !_user && _photo && _peer->userpicPhotoId() == _photo->id) { return SharedMediaKey { _history->peer->id, _migrated ? _migrated->peer->id : 0, SharedMediaType::ChatPhoto, _peer->userpicPhotoId() }; } if (!IsServerMsgId(_msgid.msg)) { return base::none; } auto keyForType = [this](SharedMediaType type) -> SharedMediaKey { return { _history->peer->id, _migrated ? _migrated->peer->id : 0, type, (_msgid.channel == _history->channelId()) ? _msgid.msg : (_msgid.msg - ServerMaxMsgId) }; }; return sharedMediaType() | keyForType; } bool MediaView::validSharedMedia() const { if (auto key = sharedMediaKey()) { if (!_sharedMedia) { return false; } using Key = SharedMediaWithLastSlice::Key; auto inSameDomain = [](const Key &a, const Key &b) { return (a.type == b.type) && (a.peerId == b.peerId) && (a.migratedPeerId == b.migratedPeerId); }; auto countDistanceInData = [&](const Key &a, const Key &b) { return [&](const SharedMediaWithLastSlice &data) { return inSameDomain(a, b) ? data.distance(a, b) : base::optional<int>(); }; }; if (key == _sharedMedia->key) { return true; } else if (!_sharedMediaDataKey || _sharedMedia->key != *_sharedMediaDataKey) { return false; } auto distance = _sharedMediaData | countDistanceInData(*key, _sharedMedia->key) | func::abs; if (distance) { return (*distance < kIdsPreloadAfter); } } return (_sharedMedia == nullptr); } void MediaView::validateSharedMedia() { if (auto key = sharedMediaKey()) { _sharedMedia = std::make_unique<SharedMedia>(*key); auto viewer = (key->type == SharedMediaType::ChatPhoto) ? SharedMediaWithLastReversedViewer : SharedMediaWithLastViewer; viewer( *key, kIdsLimit, kIdsLimit ) | rpl::start_with_next([this]( SharedMediaWithLastSlice &&update) { handleSharedMediaUpdate(std::move(update)); }, _sharedMedia->lifetime); } else { _sharedMedia = nullptr; _sharedMediaData = base::none; _sharedMediaDataKey = base::none; } } void MediaView::handleSharedMediaUpdate(SharedMediaWithLastSlice &&update) { if ((!_photo && !_doc) || !_sharedMedia) { _sharedMediaData = base::none; _sharedMediaDataKey = base::none; } else { _sharedMediaData = std::move(update); _sharedMediaDataKey = _sharedMedia->key; } findCurrent(); updateControls(); preloadData(0); } base::optional<MediaView::UserPhotosKey> MediaView::userPhotosKey() const { if (!_msgid && _user && _photo) { return UserPhotosKey { _user->bareId(), _photo->id }; } return base::none; } bool MediaView::validUserPhotos() const { if (auto key = userPhotosKey()) { if (!_userPhotos) { return false; } auto countDistanceInData = [](const auto &a, const auto &b) { return [&](const UserPhotosSlice &data) { return data.distance(a, b); }; }; auto distance = (key == _userPhotos->key) ? 0 : _userPhotosData | countDistanceInData(*key, _userPhotos->key) | func::abs; if (distance) { return (*distance < kIdsPreloadAfter); } } return (_userPhotos == nullptr); } void MediaView::validateUserPhotos() { if (auto key = userPhotosKey()) { _userPhotos = std::make_unique<UserPhotos>(*key); UserPhotosReversedViewer( *key, kIdsLimit, kIdsLimit ) | rpl::start_with_next([this]( UserPhotosSlice &&update) { handleUserPhotosUpdate(std::move(update)); }, _userPhotos->lifetime); } else { _userPhotos = nullptr; _userPhotosData = base::none; } } void MediaView::handleUserPhotosUpdate(UserPhotosSlice &&update) { if (!_photo || !_userPhotos) { _userPhotosData = base::none; } else { _userPhotosData = std::move(update); } findCurrent(); updateControls(); preloadData(0); } void MediaView::refreshMediaViewer() { if (!validSharedMedia()) { validateSharedMedia(); } if (!validUserPhotos()) { validateUserPhotos(); } findCurrent(); updateControls(); preloadData(0); } void MediaView::refreshCaption(HistoryItem *item) { _caption = Text(); if (!item) { return; } const auto caption = item->originalText(); if (caption.text.isEmpty()) { return; } const auto asBot = [&] { if (const auto author = item->author()->asUser()) { return author->botInfo != nullptr; } return false; }(); _caption = Text(st::msgMinWidth); _caption.setMarkedText( st::mediaviewCaptionStyle, caption, Ui::ItemTextOptions(item)); } void MediaView::refreshGroupThumbs() { const auto existed = (_groupThumbs != nullptr); if (_index && _sharedMediaData) { Media::View::GroupThumbs::Refresh( _groupThumbs, *_sharedMediaData, *_index, _groupThumbsAvailableWidth); } else if (_index && _userPhotosData) { Media::View::GroupThumbs::Refresh( _groupThumbs, *_userPhotosData, *_index, _groupThumbsAvailableWidth); } else if (_groupThumbs) { _groupThumbs->clear(); _groupThumbs->resizeToWidth(_groupThumbsAvailableWidth); } if (_groupThumbs && !existed) { initGroupThumbs(); } } void MediaView::initGroupThumbs() { Expects(_groupThumbs != nullptr); _groupThumbs->updateRequests( ) | rpl::start_with_next([this](QRect rect) { const auto shift = (width() / 2); _groupThumbsRect = QRect( shift + rect.x(), _groupThumbsTop, rect.width(), _groupThumbs->height()); update(_groupThumbsRect); }, _groupThumbs->lifetime()); _groupThumbs->activateRequests( ) | rpl::start_with_next([this](Media::View::GroupThumbs::Key key) { if (const auto photoId = base::get_if<PhotoId>(&key)) { const auto photo = Auth().data().photo(*photoId); moveToEntity({ photo, nullptr }); } else if (const auto itemId = base::get_if<FullMsgId>(&key)) { moveToEntity(entityForItemId(*itemId)); } }, _groupThumbs->lifetime()); _groupThumbsRect = QRect( _groupThumbsLeft, _groupThumbsTop, width() - 2 * _groupThumbsLeft, height() - _groupThumbsTop); } void MediaView::showPhoto(not_null<PhotoData*> photo, HistoryItem *context) { if (context) { setContext(context); } else { setContext(base::none); } _firstOpenedPeerPhoto = false; _saveMsgStarted = 0; _loadRequest = 0; _over = OverNone; _pressed = false; _dragging = 0; setCursor(style::cur_default); if (!_animations.isEmpty()) { _animations.clear(); _a_state.stop(); } if (!_animOpacities.isEmpty()) _animOpacities.clear(); _photo = photo; refreshMediaViewer(); displayPhoto(photo, context); preloadData(0); activateControls(); } void MediaView::showPhoto(not_null<PhotoData*> photo, not_null<PeerData*> context) { setContext(context); _firstOpenedPeerPhoto = true; _saveMsgStarted = 0; _loadRequest = 0; _over = OverNone; setCursor(style::cur_default); if (!_animations.isEmpty()) { _animations.clear(); _a_state.stop(); } if (!_animOpacities.isEmpty()) _animOpacities.clear(); _photo = photo; refreshMediaViewer(); displayPhoto(photo, 0); preloadData(0); activateControls(); } void MediaView::showDocument(not_null<DocumentData*> document, HistoryItem *context) { if (context) { setContext(context); } else { setContext(base::none); } _photo = nullptr; _saveMsgStarted = 0; _loadRequest = 0; _down = OverNone; _pressed = false; _dragging = 0; setCursor(style::cur_default); if (!_animations.isEmpty()) { _animations.clear(); _a_state.stop(); } if (!_animOpacities.isEmpty()) _animOpacities.clear(); if (document->isVideoFile() || document->isVideoMessage()) { _autoplayVideoDocument = document; } displayDocument(document, context); preloadData(0); activateControls(); } void MediaView::displayPhoto(not_null<PhotoData*> photo, HistoryItem *item) { stopGif(); destroyThemePreview(); _doc = _autoplayVideoDocument = nullptr; _fullScreenVideo = false; _photo = photo; _radial.stop(); refreshMediaViewer(); _photoRadialRect = QRect(QPoint((width() - st::radialSize.width()) / 2, (height() - st::radialSize.height()) / 2), st::radialSize); _zoom = 0; refreshCaption(item); _zoomToScreen = 0; Auth().downloader().clearPriorities(); _full = -1; _current = QPixmap(); _down = OverNone; _w = convertScale(photo->full->width()); _h = convertScale(photo->full->height()); if (isHidden()) { moveToScreen(); } if (_w > width()) { _h = qRound(_h * width() / float64(_w)); _w = width(); } if (_h > height()) { _w = qRound(_w * height() / float64(_h)); _h = height(); } _x = (width() - _w) / 2; _y = (height() - _h) / 2; _width = _w; if (_msgid && item) { _from = item->senderOriginal(); } else { _from = _user; } _photo->download(); displayFinished(); } void MediaView::destroyThemePreview() { _themePreviewId = 0; _themePreviewShown = false; _themePreview.reset(); _themeApply.destroy(); _themeCancel.destroy(); } void MediaView::displayDocument(DocumentData *doc, HistoryItem *item) { // empty messages shown as docs: doc can be NULL auto documentChanged = (!doc || doc != _doc || (item && item->fullId() != _msgid)); if (documentChanged || (!doc->isAnimation() && !doc->isVideoFile())) { _fullScreenVideo = false; _current = QPixmap(); stopGif(); } else if (gifShown()) { _current = QPixmap(); } if (documentChanged || !doc->isTheme()) { destroyThemePreview(); } _doc = doc; _photo = nullptr; _radial.stop(); refreshMediaViewer(); if (_autoplayVideoDocument && _doc != _autoplayVideoDocument) { _autoplayVideoDocument = nullptr; } if (documentChanged) { refreshCaption(item); } if (_doc) { if (_doc->sticker()) { _doc->checkSticker(); if (!_doc->sticker()->img->isNull()) { _current = _doc->sticker()->img->pix(); } else { _current = _doc->thumb->pixBlurred(_doc->dimensions.width(), _doc->dimensions.height()); } } else { _doc->automaticLoad(item); if (_doc->isAnimation() || _doc->isVideoFile()) { initAnimation(); } else if (_doc->isTheme()) { initThemePreview(); } else { auto &location = _doc->location(true); if (location.accessEnable()) { if (QImageReader(location.name()).canRead()) { _current = App::pixmapFromImageInPlace(App::readImage(location.name(), 0, false)); } } location.accessDisable(); } } } _docIconRect = QRect((width() - st::mediaviewFileIconSize) / 2, (height() - st::mediaviewFileIconSize) / 2, st::mediaviewFileIconSize, st::mediaviewFileIconSize); if (fileBubbleShown()) { if (!_doc || _doc->thumb->isNull()) { int32 colorIndex = documentColorIndex(_doc, _docExt); _docIconColor = documentColor(colorIndex); const style::icon *(thumbs[]) = { &st::mediaviewFileBlue, &st::mediaviewFileGreen, &st::mediaviewFileRed, &st::mediaviewFileYellow }; _docIcon = thumbs[colorIndex]; int32 extmaxw = (st::mediaviewFileIconSize - st::mediaviewFileExtPadding * 2); _docExtWidth = st::mediaviewFileExtFont->width(_docExt); if (_docExtWidth > extmaxw) { _docExt = st::mediaviewFileNameFont->elided(_docExt, extmaxw, Qt::ElideMiddle); _docExtWidth = st::mediaviewFileNameFont->width(_docExt); } } else { _doc->thumb->load(); int32 tw = _doc->thumb->width(), th = _doc->thumb->height(); if (!tw || !th) { _docThumbx = _docThumby = _docThumbw = 0; } else if (tw > th) { _docThumbw = (tw * st::mediaviewFileIconSize) / th; _docThumbx = (_docThumbw - st::mediaviewFileIconSize) / 2; _docThumby = 0; } else { _docThumbw = st::mediaviewFileIconSize; _docThumbx = 0; _docThumby = ((th * _docThumbw) / tw - st::mediaviewFileIconSize) / 2; } } int32 maxw = st::mediaviewFileSize.width() - st::mediaviewFileIconSize - st::mediaviewFilePadding * 3; if (_doc) { _docName = (_doc->type == StickerDocument) ? lang(lng_in_dlg_sticker) : (_doc->type == AnimatedDocument ? qsl("GIF") : (_doc->filename().isEmpty() ? lang(lng_mediaview_doc_image) : _doc->filename())); } else { _docName = lang(lng_message_empty); } _docNameWidth = st::mediaviewFileNameFont->width(_docName); if (_docNameWidth > maxw) { _docName = st::mediaviewFileNameFont->elided(_docName, maxw, Qt::ElideMiddle); _docNameWidth = st::mediaviewFileNameFont->width(_docName); } // _docSize is updated in updateControls() _docRect = QRect((width() - st::mediaviewFileSize.width()) / 2, (height() - st::mediaviewFileSize.height()) / 2, st::mediaviewFileSize.width(), st::mediaviewFileSize.height()); _docIconRect = myrtlrect(_docRect.x() + st::mediaviewFilePadding, _docRect.y() + st::mediaviewFilePadding, st::mediaviewFileIconSize, st::mediaviewFileIconSize); } else if (_themePreviewShown) { updateThemePreviewGeometry(); } else if (!_current.isNull()) { _current.setDevicePixelRatio(cRetinaFactor()); _w = convertScale(_current.width()); _h = convertScale(_current.height()); } else { _w = convertScale(_gif->width()); _h = convertScale(_gif->height()); } if (isHidden()) { moveToScreen(); } _width = _w; if (_w > 0 && _h > 0) { _zoomToScreen = float64(width()) / _w; if (_h * _zoomToScreen > height()) { _zoomToScreen = float64(height()) / _h; } if (_zoomToScreen >= 1.) { _zoomToScreen -= 1.; } else { _zoomToScreen = 1. - (1. / _zoomToScreen); } } else { _zoomToScreen = 0; } if ((_w > width()) || (_h > height()) || _fullScreenVideo) { _zoom = ZoomToScreenLevel; if (_zoomToScreen >= 0) { _w = qRound(_w * (_zoomToScreen + 1)); _h = qRound(_h * (_zoomToScreen + 1)); } else { _w = qRound(_w / (-_zoomToScreen + 1)); _h = qRound(_h / (-_zoomToScreen + 1)); } snapXY(); } else { _zoom = 0; } _x = (width() - _w) / 2; _y = (height() - _h) / 2; if (_msgid && item) { _from = item->senderOriginal(); } else { _from = _user; } _full = 1; displayFinished(); } void MediaView::updateThemePreviewGeometry() { if (_themePreviewShown) { auto previewRect = QRect((width() - st::themePreviewSize.width()) / 2, (height() - st::themePreviewSize.height()) / 2, st::themePreviewSize.width(), st::themePreviewSize.height()); _themePreviewRect = previewRect.marginsAdded(st::themePreviewMargin); if (_themeApply) { auto right = qMax(width() - _themePreviewRect.x() - _themePreviewRect.width(), 0) + st::themePreviewMargin.right(); auto bottom = qMin(height(), _themePreviewRect.y() + _themePreviewRect.height()); _themeApply->moveToRight(right, bottom - st::themePreviewMargin.bottom() + (st::themePreviewMargin.bottom() - _themeApply->height()) / 2); right += _themeApply->width() + st::themePreviewButtonsSkip; _themeCancel->moveToRight(right, _themeApply->y()); } // For context menu event. _x = _themePreviewRect.x(); _y = _themePreviewRect.y(); _w = _themePreviewRect.width(); _h = _themePreviewRect.height(); } } void MediaView::displayFinished() { updateControls(); if (isHidden()) { psUpdateOverlayed(this); show(); psShowOverAll(this); activateWindow(); Sandbox::setActiveWindow(this); setFocus(); } } Images::Options MediaView::videoThumbOptions() const { auto options = Images::Option::Smooth | Images::Option::Blurred; if (_doc && _doc->isVideoMessage()) { options |= Images::Option::Circled; } return options; } void MediaView::initAnimation() { Expects(_doc != nullptr); Expects(_doc->isAnimation() || _doc->isVideoFile()); auto &location = _doc->location(true); if (!_doc->data().isEmpty()) { createClipReader(); } else if (location.accessEnable()) { createClipReader(); location.accessDisable(); } else if (_doc->dimensions.width() && _doc->dimensions.height()) { auto w = _doc->dimensions.width(); auto h = _doc->dimensions.height(); _current = _doc->thumb->pixNoCache(w, h, videoThumbOptions(), w / cIntRetinaFactor(), h / cIntRetinaFactor()); if (cRetina()) _current.setDevicePixelRatio(cRetinaFactor()); } else { _current = _doc->thumb->pixNoCache(_doc->thumb->width(), _doc->thumb->height(), videoThumbOptions(), st::mediaviewFileIconSize, st::mediaviewFileIconSize); } } void MediaView::createClipReader() { if (_gif) return; Expects(_doc != nullptr); Expects(_doc->isAnimation() || _doc->isVideoFile()); if (_doc->dimensions.width() && _doc->dimensions.height()) { int w = _doc->dimensions.width(); int h = _doc->dimensions.height(); _current = _doc->thumb->pixNoCache(w, h, videoThumbOptions(), w / cIntRetinaFactor(), h / cIntRetinaFactor()); if (cRetina()) _current.setDevicePixelRatio(cRetinaFactor()); } else { _current = _doc->thumb->pixNoCache(_doc->thumb->width(), _doc->thumb->height(), videoThumbOptions(), st::mediaviewFileIconSize, st::mediaviewFileIconSize); } auto mode = (_doc->isVideoFile() || _doc->isVideoMessage()) ? Media::Clip::Reader::Mode::Video : Media::Clip::Reader::Mode::Gif; _gif = Media::Clip::MakeReader(_doc, _msgid, [this](Media::Clip::Notification notification) { clipCallback(notification); }, mode); // Correct values will be set when gif gets inited. _videoPaused = _videoIsSilent = _videoStopped = false; _videoPositionMs = 0ULL; _videoDurationMs = _doc->duration() * 1000ULL; createClipController(); } void MediaView::initThemePreview() { Assert(_doc && _doc->isTheme()); auto &location = _doc->location(); if (!location.isEmpty() && location.accessEnable()) { _themePreviewShown = true; Window::Theme::CurrentData current; current.backgroundId = Window::Theme::Background()->id(); current.backgroundImage = Window::Theme::Background()->pixmap().toImage(); current.backgroundTiled = Window::Theme::Background()->tile(); const auto path = _doc->location().name(); const auto id = _themePreviewId = rand_value<uint64>(); const auto weak = make_weak(this); crl::async([=, data = std::move(current)]() mutable { auto preview = Window::Theme::GeneratePreview( path, std::move(data)); crl::on_main(weak, [=, result = std::move(preview)]() mutable { if (id != _themePreviewId) { return; } _themePreviewId = 0; _themePreview = std::move(result); if (_themePreview) { _themeApply.create( this, langFactory(lng_theme_preview_apply), st::themePreviewApplyButton); _themeApply->show(); _themeApply->setClickedCallback([this] { auto preview = std::move(_themePreview); close(); Window::Theme::Apply(std::move(preview)); }); _themeCancel.create( this, langFactory(lng_cancel), st::themePreviewCancelButton); _themeCancel->show(); _themeCancel->setClickedCallback([this] { close(); }); updateControls(); } update(); }); }); location.accessDisable(); } } void MediaView::createClipController() { Expects(_doc != nullptr); if (!_doc->isVideoFile() && !_doc->isVideoMessage()) return; _clipController.create(this); refreshClipControllerGeometry(); _clipController->show(); connect(_clipController, SIGNAL(playPressed()), this, SLOT(onVideoPauseResume())); connect(_clipController, SIGNAL(pausePressed()), this, SLOT(onVideoPauseResume())); connect(_clipController, SIGNAL(seekProgress(TimeMs)), this, SLOT(onVideoSeekProgress(TimeMs))); connect(_clipController, SIGNAL(seekFinished(TimeMs)), this, SLOT(onVideoSeekFinished(TimeMs))); connect(_clipController, SIGNAL(volumeChanged(float64)), this, SLOT(onVideoVolumeChanged(float64))); connect(_clipController, SIGNAL(toFullScreenPressed()), this, SLOT(onVideoToggleFullScreen())); connect(_clipController, SIGNAL(fromFullScreenPressed()), this, SLOT(onVideoToggleFullScreen())); connect(Media::Player::mixer(), SIGNAL(updated(const AudioMsgId&)), this, SLOT(onVideoPlayProgress(const AudioMsgId&))); } void MediaView::refreshClipControllerGeometry() { if (!_clipController) { return; } if (_groupThumbs && _groupThumbs->hiding()) { _groupThumbs = nullptr; _groupThumbsRect = QRect(); } const auto controllerBottom = _groupThumbs ? _groupThumbsTop : height(); _clipController->setGeometry( (width() - _clipController->width()) / 2, controllerBottom - _clipController->height() - st::mediaviewCaptionPadding.bottom() - st::mediaviewCaptionMargin.height(), st::mediaviewControllerSize.width(), st::mediaviewControllerSize.height()); Ui::SendPendingMoveResizeEvents(_clipController); } void MediaView::onVideoPauseResume() { if (!_gif) return; if (auto item = App::histItemById(_msgid)) { if (_gif->state() == Media::Clip::State::Error) { displayDocument(_doc, item); } else if (_gif->state() == Media::Clip::State::Finished) { restartVideoAtSeekPosition(0); } else { toggleVideoPaused(); } } else { stopGif(); updateControls(); update(); } } void MediaView::toggleVideoPaused() { _gif->pauseResumeVideo(); _videoPaused = _gif->videoPaused(); if (_videoIsSilent) { updateSilentVideoPlaybackState(); } } void MediaView::restartVideoAtSeekPosition(TimeMs positionMs) { _autoplayVideoDocument = _doc; if (_current.isNull()) { auto rounding = (_doc && _doc->isVideoMessage()) ? ImageRoundRadius::Ellipse : ImageRoundRadius::None; _current = _gif->current(_gif->width() / cIntRetinaFactor(), _gif->height() / cIntRetinaFactor(), _gif->width() / cIntRetinaFactor(), _gif->height() / cIntRetinaFactor(), rounding, RectPart::AllCorners, getms()); } _gif = Media::Clip::MakeReader(_doc, _msgid, [this](Media::Clip::Notification notification) { clipCallback(notification); }, Media::Clip::Reader::Mode::Video, positionMs); // Correct values will be set when gif gets inited. _videoPaused = _videoIsSilent = _videoStopped = false; _videoPositionMs = positionMs; Media::Player::TrackState state; state.state = Media::Player::State::Playing; state.position = _videoPositionMs; state.length = _videoDurationMs; state.frequency = _videoFrequencyMs; updateVideoPlaybackState(state); } void MediaView::onVideoSeekProgress(TimeMs positionMs) { if (!_videoPaused && !_videoStopped) { onVideoPauseResume(); } } void MediaView::onVideoSeekFinished(TimeMs positionMs) { restartVideoAtSeekPosition(positionMs); } void MediaView::onVideoVolumeChanged(float64 volume) { Global::SetVideoVolume(volume); updateMixerVideoVolume(); Global::RefVideoVolumeChanged().notify(); } void MediaView::onVideoToggleFullScreen() { if (!_clipController) return; _fullScreenVideo = !_fullScreenVideo; if (_fullScreenVideo) { _fullScreenZoomCache = _zoom; setZoomLevel(ZoomToScreenLevel); } else { setZoomLevel(_fullScreenZoomCache); _clipController->showAnimated(); } _clipController->setInFullScreen(_fullScreenVideo); updateControls(); update(); } void MediaView::onVideoPlayProgress(const AudioMsgId &audioId) { if (!_gif || _gif->audioMsgId() != audioId) { return; } auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Video); if (state.id == _gif->audioMsgId()) { if (state.length) { updateVideoPlaybackState(state); } Auth().settings().setLastTimeVideoPlayedAt(getms(true)); } } void MediaView::updateVideoPlaybackState(const Media::Player::TrackState &state) { if (state.frequency) { if (Media::Player::IsStopped(state.state)) { _videoStopped = true; } _clipController->updatePlayback(state); } else { // Audio has stopped already. _videoIsSilent = true; updateSilentVideoPlaybackState(); } } void MediaView::updateSilentVideoPlaybackState() { Media::Player::TrackState state; if (_videoPaused) { state.state = Media::Player::State::Paused; } else if (_videoPositionMs == _videoDurationMs) { state.state = Media::Player::State::StoppedAtEnd; } else { state.state = Media::Player::State::Playing; } state.position = _videoPositionMs; state.length = _videoDurationMs; state.frequency = _videoFrequencyMs; updateVideoPlaybackState(state); } void MediaView::paintEvent(QPaintEvent *e) { QRect r(e->rect()); QRegion region(e->region()); QVector<QRect> rs(region.rects()); auto ms = getms(); Painter p(this); bool name = false; p.setClipRegion(region); // main bg QPainter::CompositionMode m = p.compositionMode(); p.setCompositionMode(QPainter::CompositionMode_Source); if (_fullScreenVideo) { for (int i = 0, l = region.rectCount(); i < l; ++i) { p.fillRect(rs.at(i), st::mediaviewVideoBg); } if (_doc && _doc->isVideoMessage()) { p.setCompositionMode(m); } } else { for (int i = 0, l = region.rectCount(); i < l; ++i) { p.fillRect(rs.at(i), st::mediaviewBg); } p.setCompositionMode(m); } // photo if (_photo) { int32 w = _width * cIntRetinaFactor(); if (_full <= 0 && _photo->loaded()) { int32 h = int((_photo->full->height() * (qreal(w) / qreal(_photo->full->width()))) + 0.9999); _current = _photo->full->pixNoCache(w, h, Images::Option::Smooth); if (cRetina()) _current.setDevicePixelRatio(cRetinaFactor()); _full = 1; } else if (_full < 0 && _photo->medium->loaded()) { int32 h = int((_photo->full->height() * (qreal(w) / qreal(_photo->full->width()))) + 0.9999); _current = _photo->medium->pixNoCache(w, h, Images::Option::Smooth | Images::Option::Blurred); if (cRetina()) _current.setDevicePixelRatio(cRetinaFactor()); _full = 0; } else if (_current.isNull() && _photo->thumb->loaded()) { int32 h = int((_photo->full->height() * (qreal(w) / qreal(_photo->full->width()))) + 0.9999); _current = _photo->thumb->pixNoCache(w, h, Images::Option::Smooth | Images::Option::Blurred); if (cRetina()) _current.setDevicePixelRatio(cRetinaFactor()); } else if (_current.isNull()) { _current = _photo->thumb->pix(); } } p.setOpacity(1); if (_photo || fileShown()) { QRect imgRect(_x, _y, _w, _h); if (imgRect.intersects(r)) { auto rounding = (_doc && _doc->isVideoMessage()) ? ImageRoundRadius::Ellipse : ImageRoundRadius::None; auto toDraw = _current.isNull() ? _gif->current(_gif->width() / cIntRetinaFactor(), _gif->height() / cIntRetinaFactor(), _gif->width() / cIntRetinaFactor(), _gif->height() / cIntRetinaFactor(), rounding, RectPart::AllCorners, ms) : _current; if (!_gif && (!_doc || !_doc->sticker() || _doc->sticker()->img->isNull()) && toDraw.hasAlpha()) { p.fillRect(imgRect, _transparentBrush); } if (toDraw.width() != _w * cIntRetinaFactor()) { PainterHighQualityEnabler hq(p); p.drawPixmap(QRect(_x, _y, _w, _h), toDraw); } else { p.drawPixmap(_x, _y, toDraw); } bool radial = false; float64 radialOpacity = 0; if (_radial.animating()) { _radial.step(ms); radial = _radial.animating(); radialOpacity = _radial.opacity(); } if (_photo) { if (radial) { auto inner = radialRect(); p.setPen(Qt::NoPen); p.setOpacity(radialOpacity); p.setBrush(st::radialBg); { PainterHighQualityEnabler hq(p); p.drawEllipse(inner); } p.setOpacity(1); QRect arc(inner.marginsRemoved(QMargins(st::radialLine, st::radialLine, st::radialLine, st::radialLine))); _radial.draw(p, arc, st::radialLine, st::radialFg); } } else if (_doc) { paintDocRadialLoading(p, radial, radialOpacity); } if (_saveMsgStarted) { auto ms = getms(); float64 dt = float64(ms) - _saveMsgStarted, hidingDt = dt - st::mediaviewSaveMsgShowing - st::mediaviewSaveMsgShown; if (dt < st::mediaviewSaveMsgShowing + st::mediaviewSaveMsgShown + st::mediaviewSaveMsgHiding) { if (hidingDt >= 0 && _saveMsgOpacity.to() > 0.5) { _saveMsgOpacity.start(0); } float64 progress = (hidingDt >= 0) ? (hidingDt / st::mediaviewSaveMsgHiding) : (dt / st::mediaviewSaveMsgShowing); _saveMsgOpacity.update(qMin(progress, 1.), anim::linear); if (_saveMsgOpacity.current() > 0) { p.setOpacity(_saveMsgOpacity.current()); App::roundRect(p, _saveMsg, st::mediaviewSaveMsgBg, MediaviewSaveCorners); st::mediaviewSaveMsgCheck.paint(p, _saveMsg.topLeft() + st::mediaviewSaveMsgCheckPos, width()); p.setPen(st::mediaviewSaveMsgFg); p.setTextPalette(st::mediaviewTextPalette); _saveMsgText.draw(p, _saveMsg.x() + st::mediaviewSaveMsgPadding.left(), _saveMsg.y() + st::mediaviewSaveMsgPadding.top(), _saveMsg.width() - st::mediaviewSaveMsgPadding.left() - st::mediaviewSaveMsgPadding.right()); p.restoreTextPalette(); p.setOpacity(1); } if (_full >= 1) { auto nextFrame = (dt < st::mediaviewSaveMsgShowing || hidingDt >= 0) ? int(AnimationTimerDelta) : (st::mediaviewSaveMsgShowing + st::mediaviewSaveMsgShown + 1 - dt); _saveMsgUpdater.start(nextFrame); } } else { _saveMsgStarted = 0; } } } } else if (_themePreviewShown) { paintThemePreview(p, r); } else { if (_docRect.intersects(r)) { p.fillRect(_docRect, st::mediaviewFileBg); if (_docIconRect.intersects(r)) { bool radial = false; float64 radialOpacity = 0; if (_radial.animating()) { _radial.step(ms); radial = _radial.animating(); radialOpacity = _radial.opacity(); } if (!_doc || _doc->thumb->isNull()) { p.fillRect(_docIconRect, _docIconColor); if ((!_doc || _doc->loaded()) && (!radial || radialOpacity < 1) && _docIcon) { _docIcon->paint(p, _docIconRect.x() + (_docIconRect.width() - _docIcon->width()), _docIconRect.y(), width()); p.setPen(st::mediaviewFileExtFg); p.setFont(st::mediaviewFileExtFont); if (!_docExt.isEmpty()) { p.drawText(_docIconRect.x() + (_docIconRect.width() - _docExtWidth) / 2, _docIconRect.y() + st::mediaviewFileExtTop + st::mediaviewFileExtFont->ascent, _docExt); } } } else { int32 rf(cIntRetinaFactor()); p.drawPixmap(_docIconRect.topLeft(), _doc->thumb->pix(_docThumbw), QRect(_docThumbx * rf, _docThumby * rf, st::mediaviewFileIconSize * rf, st::mediaviewFileIconSize * rf)); } paintDocRadialLoading(p, radial, radialOpacity); } if (!_docIconRect.contains(r)) { name = true; p.setPen(st::mediaviewFileNameFg); p.setFont(st::mediaviewFileNameFont); p.drawTextLeft(_docRect.x() + 2 * st::mediaviewFilePadding + st::mediaviewFileIconSize, _docRect.y() + st::mediaviewFilePadding + st::mediaviewFileNameTop, width(), _docName, _docNameWidth); p.setPen(st::mediaviewFileSizeFg); p.setFont(st::mediaviewFont); p.drawTextLeft(_docRect.x() + 2 * st::mediaviewFilePadding + st::mediaviewFileIconSize, _docRect.y() + st::mediaviewFilePadding + st::mediaviewFileSizeTop, width(), _docSize, _docSizeWidth); } } } float64 co = _fullScreenVideo ? 0. : a_cOpacity.current(); if (co > 0) { // left nav bar if (_leftNav.intersects(r) && _leftNavVisible) { auto o = overLevel(OverLeftNav); if (o > 0) { p.setOpacity(o * co); for (int i = 0, l = region.rectCount(); i < l; ++i) { auto fill = _leftNav.intersected(rs.at(i)); if (!fill.isEmpty()) p.fillRect(fill, st::mediaviewControlBg); } } if (_leftNavIcon.intersects(r)) { p.setOpacity((o * st::mediaviewIconOverOpacity + (1 - o) * st::mediaviewIconOpacity) * co); st::mediaviewLeft.paintInCenter(p, _leftNavIcon); } } // right nav bar if (_rightNav.intersects(r) && _rightNavVisible) { auto o = overLevel(OverRightNav); if (o > 0) { p.setOpacity(o * co); for (int i = 0, l = region.rectCount(); i < l; ++i) { auto fill = _rightNav.intersected(rs.at(i)); if (!fill.isEmpty()) p.fillRect(fill, st::mediaviewControlBg); } } if (_rightNavIcon.intersects(r)) { p.setOpacity((o * st::mediaviewIconOverOpacity + (1 - o) * st::mediaviewIconOpacity) * co); st::mediaviewRight.paintInCenter(p, _rightNavIcon); } } // close button if (_closeNav.intersects(r)) { auto o = overLevel(OverClose); if (o > 0) { p.setOpacity(o * co); for (int i = 0, l = region.rectCount(); i < l; ++i) { auto fill = _closeNav.intersected(rs.at(i)); if (!fill.isEmpty()) p.fillRect(fill, st::mediaviewControlBg); } } if (_closeNavIcon.intersects(r)) { p.setOpacity((o * st::mediaviewIconOverOpacity + (1 - o) * st::mediaviewIconOpacity) * co); st::mediaviewClose.paintInCenter(p, _closeNavIcon); } } // save button if (_saveVisible && _saveNavIcon.intersects(r)) { auto o = overLevel(OverSave); p.setOpacity((o * st::mediaviewIconOverOpacity + (1 - o) * st::mediaviewIconOpacity) * co); st::mediaviewSave.paintInCenter(p, _saveNavIcon); } // more area if (_moreNavIcon.intersects(r)) { auto o = overLevel(OverMore); p.setOpacity((o * st::mediaviewIconOverOpacity + (1 - o) * st::mediaviewIconOpacity) * co); st::mediaviewMore.paintInCenter(p, _moreNavIcon); } p.setPen(st::mediaviewControlFg); p.setFont(st::mediaviewThickFont); // header if (_headerNav.intersects(r)) { auto o = _headerHasLink ? overLevel(OverHeader) : 0; p.setOpacity((o * st::mediaviewIconOverOpacity + (1 - o) * st::mediaviewIconOpacity) * co); p.drawText(_headerNav.left(), _headerNav.top() + st::mediaviewThickFont->ascent, _headerText); if (o > 0) { p.setOpacity(o * co); p.drawLine(_headerNav.left(), _headerNav.top() + st::mediaviewThickFont->ascent + 1, _headerNav.right(), _headerNav.top() + st::mediaviewThickFont->ascent + 1); } } p.setFont(st::mediaviewFont); // name if (_from && _nameNav.intersects(r)) { float64 o = overLevel(OverName); p.setOpacity((o * st::mediaviewIconOverOpacity + (1 - o) * st::mediaviewIconOpacity) * co); _fromName.drawElided(p, _nameNav.left(), _nameNav.top(), _nameNav.width()); if (o > 0) { p.setOpacity(o * co); p.drawLine(_nameNav.left(), _nameNav.top() + st::mediaviewFont->ascent + 1, _nameNav.right(), _nameNav.top() + st::mediaviewFont->ascent + 1); } } // date if (_dateNav.intersects(r)) { float64 o = overLevel(OverDate); p.setOpacity((o * st::mediaviewIconOverOpacity + (1 - o) * st::mediaviewIconOpacity) * co); p.drawText(_dateNav.left(), _dateNav.top() + st::mediaviewFont->ascent, _dateText); if (o > 0) { p.setOpacity(o * co); p.drawLine(_dateNav.left(), _dateNav.top() + st::mediaviewFont->ascent + 1, _dateNav.right(), _dateNav.top() + st::mediaviewFont->ascent + 1); } } // caption if (!_caption.isEmpty()) { QRect outer(_captionRect.marginsAdded(st::mediaviewCaptionPadding)); if (outer.intersects(r)) { p.setOpacity(co); p.setBrush(st::mediaviewCaptionBg); p.setPen(Qt::NoPen); p.drawRoundedRect(outer, st::mediaviewCaptionRadius, st::mediaviewCaptionRadius); if (_captionRect.intersects(r)) { p.setTextPalette(st::mediaviewTextPalette); p.setPen(st::mediaviewCaptionFg); _caption.drawElided(p, _captionRect.x(), _captionRect.y(), _captionRect.width(), _captionRect.height() / st::mediaviewCaptionStyle.font->height); p.restoreTextPalette(); } } } if (_groupThumbs && _groupThumbsRect.intersects(r)) { p.setOpacity(co); _groupThumbs->paint( p, _groupThumbsLeft, _groupThumbsTop, width(), ms); if (_groupThumbs->hidden()) { _groupThumbs = nullptr; _groupThumbsRect = QRect(); } } } checkGroupThumbsAnimation(); } void MediaView::checkGroupThumbsAnimation() { if (_groupThumbs && (!_gif || _gif->started())) { _groupThumbs->checkForAnimationStart(); } } void MediaView::paintDocRadialLoading(Painter &p, bool radial, float64 radialOpacity) { float64 o = overLevel(OverIcon); if (radial || (_doc && !_doc->loaded())) { QRect inner(QPoint(_docIconRect.x() + ((_docIconRect.width() - st::radialSize.width()) / 2), _docIconRect.y() + ((_docIconRect.height() - st::radialSize.height()) / 2)), st::radialSize); p.setPen(Qt::NoPen); p.setOpacity(_doc->loaded() ? radialOpacity : 1.); p.setBrush(anim::brush(st::msgDateImgBg, st::msgDateImgBgOver, o)); { PainterHighQualityEnabler hq(p); p.drawEllipse(inner); } p.setOpacity(1.); auto icon = ([radial, this]() -> const style::icon* { if (radial || _doc->loading()) { return &st::historyFileThumbCancel; } return &st::historyFileThumbDownload; })(); if (icon) { icon->paintInCenter(p, inner); } if (radial) { p.setOpacity(1); QRect arc(inner.marginsRemoved(QMargins(st::radialLine, st::radialLine, st::radialLine, st::radialLine))); _radial.draw(p, arc, st::radialLine, st::radialFg); } } } void MediaView::paintThemePreview(Painter &p, QRect clip) { auto fill = _themePreviewRect.intersected(clip); if (!fill.isEmpty()) { if (_themePreview) { p.drawImage( myrtlrect(_themePreviewRect).topLeft(), _themePreview->preview); } else { p.fillRect(fill, st::themePreviewBg); p.setFont(st::themePreviewLoadingFont); p.setPen(st::themePreviewLoadingFg); p.drawText( _themePreviewRect, lang(_themePreviewId ? lng_theme_preview_generating : lng_theme_preview_invalid), QTextOption(style::al_center)); } } auto fillOverlay = [&](QRect fill) { auto clipped = fill.intersected(clip); if (!clipped.isEmpty()) { p.setOpacity(st::themePreviewOverlayOpacity); p.fillRect(clipped, st::themePreviewBg); p.setOpacity(1.); } }; auto titleRect = QRect(_themePreviewRect.x(), _themePreviewRect.y(), _themePreviewRect.width(), st::themePreviewMargin.top()); if (titleRect.x() < 0) { titleRect = QRect(0, _themePreviewRect.y(), width(), st::themePreviewMargin.top()); } if (auto fillTitleRect = (titleRect.y() < 0)) { titleRect.moveTop(0); fillOverlay(titleRect); } titleRect = titleRect.marginsRemoved(QMargins(st::themePreviewMargin.left(), st::themePreviewTitleTop, st::themePreviewMargin.right(), titleRect.height() - st::themePreviewTitleTop - st::themePreviewTitleFont->height)); if (titleRect.intersects(clip)) { p.setFont(st::themePreviewTitleFont); p.setPen(st::themePreviewTitleFg); p.drawTextLeft(titleRect.x(), titleRect.y(), width(), lang(lng_theme_preview_title)); } auto buttonsRect = QRect(_themePreviewRect.x(), _themePreviewRect.y() + _themePreviewRect.height() - st::themePreviewMargin.bottom(), _themePreviewRect.width(), st::themePreviewMargin.bottom()); if (auto fillButtonsRect = (buttonsRect.y() + buttonsRect.height() > height())) { buttonsRect.moveTop(height() - buttonsRect.height()); fillOverlay(buttonsRect); } } void MediaView::keyPressEvent(QKeyEvent *e) { if (_clipController) { auto toggle1 = (e->key() == Qt::Key_F && e->modifiers().testFlag(Qt::ControlModifier)); auto toggle2 = (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) && (e->modifiers().testFlag(Qt::AltModifier) || e->modifiers().testFlag(Qt::ControlModifier)); if (toggle1 || toggle2) { onVideoToggleFullScreen(); return; } if (_fullScreenVideo) { if (e->key() == Qt::Key_Escape) { onVideoToggleFullScreen(); } else if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return || e->key() == Qt::Key_Space) { onVideoPauseResume(); } return; } } if (!_menu && e->key() == Qt::Key_Escape) { if (_doc && _doc->loading()) { onDocClick(); } else { close(); } } else if (e == QKeySequence::Save || e == QKeySequence::SaveAs) { onSaveAs(); } else if (e->key() == Qt::Key_Copy || (e->key() == Qt::Key_C && e->modifiers().testFlag(Qt::ControlModifier))) { onCopy(); } else if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return || e->key() == Qt::Key_Space) { if (_doc && !_doc->loading() && (fileBubbleShown() || !_doc->loaded())) { onDocClick(); } else if (_doc && (_doc->isVideoFile() || _doc->isVideoMessage())) { onVideoPauseResume(); } } else if (e->key() == Qt::Key_Left) { if (_controlsHideTimer.isActive()) { activateControls(); } moveToNext(-1); } else if (e->key() == Qt::Key_Right) { if (_controlsHideTimer.isActive()) { activateControls(); } moveToNext(1); } else if (e->modifiers().testFlag(Qt::ControlModifier) && (e->key() == Qt::Key_Plus || e->key() == Qt::Key_Equal || e->key() == ']' || e->key() == Qt::Key_Asterisk || e->key() == Qt::Key_Minus || e->key() == Qt::Key_Underscore || e->key() == Qt::Key_0)) { if (e->key() == Qt::Key_Plus || e->key() == Qt::Key_Equal || e->key() == Qt::Key_Asterisk || e->key() == ']') { zoomIn(); } else if (e->key() == Qt::Key_Minus || e->key() == Qt::Key_Underscore) { zoomOut(); } else { zoomReset(); } } } void MediaView::wheelEvent(QWheelEvent *e) { #ifdef OS_MAC_OLD constexpr auto step = 120; #else // OS_MAC_OLD constexpr auto step = static_cast<int>(QWheelEvent::DefaultDeltasPerStep); #endif // OS_MAC_OLD _verticalWheelDelta += e->angleDelta().y(); while (qAbs(_verticalWheelDelta) >= step) { if (_verticalWheelDelta < 0) { _verticalWheelDelta += step; if (e->modifiers().testFlag(Qt::ControlModifier)) { zoomOut(); } else { #ifndef OS_MAC_OLD if (e->source() == Qt::MouseEventNotSynthesized) { moveToNext(1); } #endif // OS_MAC_OLD } } else { _verticalWheelDelta -= step; if (e->modifiers().testFlag(Qt::ControlModifier)) { zoomIn(); } else { #ifndef OS_MAC_OLD if (e->source() == Qt::MouseEventNotSynthesized) { moveToNext(-1); } #endif // OS_MAC_OLD } } } } void MediaView::setZoomLevel(int newZoom) { if (_zoom == newZoom) return; float64 nx, ny, z = (_zoom == ZoomToScreenLevel) ? _zoomToScreen : _zoom; _w = gifShown() ? convertScale(_gif->width()) : (convertScale(_current.width()) / cIntRetinaFactor()); _h = gifShown() ? convertScale(_gif->height()) : (convertScale(_current.height()) / cIntRetinaFactor()); if (z >= 0) { nx = (_x - width() / 2.) / (z + 1); ny = (_y - height() / 2.) / (z + 1); } else { nx = (_x - width() / 2.) * (-z + 1); ny = (_y - height() / 2.) * (-z + 1); } _zoom = newZoom; z = (_zoom == ZoomToScreenLevel) ? _zoomToScreen : _zoom; if (z > 0) { _w = qRound(_w * (z + 1)); _h = qRound(_h * (z + 1)); _x = qRound(nx * (z + 1) + width() / 2.); _y = qRound(ny * (z + 1) + height() / 2.); } else { _w = qRound(_w / (-z + 1)); _h = qRound(_h / (-z + 1)); _x = qRound(nx / (-z + 1) + width() / 2.); _y = qRound(ny / (-z + 1) + height() / 2.); } snapXY(); update(); } MediaView::Entity MediaView::entityForUserPhotos(int index) const { Expects(!!_userPhotosData); if (index < 0 || index >= _userPhotosData->size()) { return { base::none, nullptr }; } if (auto photo = Auth().data().photo((*_userPhotosData)[index])) { return { photo, nullptr }; } return { base::none, nullptr }; } MediaView::Entity MediaView::entityForSharedMedia(int index) const { Expects(!!_sharedMediaData); if (index < 0 || index >= _sharedMediaData->size()) { return { base::none, nullptr }; } auto value = (*_sharedMediaData)[index]; if (const auto photo = base::get_if<not_null<PhotoData*>>(&value)) { // Last peer photo. return { *photo, nullptr }; } else if (const auto itemId = base::get_if<FullMsgId>(&value)) { return entityForItemId(*itemId); } return { base::none, nullptr }; } MediaView::Entity MediaView::entityForItemId(const FullMsgId &itemId) const { if (const auto item = App::histItemById(itemId)) { if (const auto media = item->media()) { if (const auto photo = media->photo()) { return { photo, item }; } else if (const auto document = media->document()) { return { document, item }; } } return { base::none, item }; } return { base::none, nullptr }; } MediaView::Entity MediaView::entityByIndex(int index) const { if (_sharedMediaData) { return entityForSharedMedia(index); } else if (_userPhotosData) { return entityForUserPhotos(index); } return { base::none, nullptr }; } void MediaView::setContext(base::optional_variant< not_null<HistoryItem*>, not_null<PeerData*>> context) { if (auto item = base::get_if<not_null<HistoryItem*>>(&context)) { _msgid = (*item)->fullId(); _canForwardItem = (*item)->allowsForward(); _canDeleteItem = (*item)->canDelete(); _history = (*item)->history(); _peer = _history->peer; } else if (auto peer = base::get_if<not_null<PeerData*>>(&context)) { _msgid = FullMsgId(); _canForwardItem = _canDeleteItem = false; _history = App::history(*peer); _peer = *peer; } else { _msgid = FullMsgId(); _canForwardItem = _canDeleteItem = false; _history = nullptr; _peer = nullptr; } _migrated = nullptr; if (_history) { if (_history->peer->migrateFrom()) { _migrated = App::history(_history->peer->migrateFrom()->id); } else if (_history->peer->migrateTo()) { _migrated = _history; _history = App::history(_history->peer->migrateTo()->id); } } _user = _peer ? _peer->asUser() : nullptr; } bool MediaView::moveToNext(int delta) { if (!_index) { return false; } auto newIndex = *_index + delta; return moveToEntity(entityByIndex(newIndex)); } bool MediaView::moveToEntity(const Entity &entity, int preloadDelta) { if (!entity.data && !entity.item) { return false; } if (const auto item = entity.item) { setContext(item); } else if (_peer) { setContext(_peer); } else { setContext(base::none); } stopGif(); if (auto photo = base::get_if<not_null<PhotoData*>>(&entity.data)) { displayPhoto(*photo, entity.item); } else if (auto document = base::get_if<not_null<DocumentData*>>(&entity.data)) { displayDocument(*document, entity.item); } else { displayDocument(nullptr, entity.item); } preloadData(preloadDelta); return true; } void MediaView::preloadData(int delta) { if (!_index) { return; } auto from = *_index + (delta ? delta : -1); auto till = *_index + (delta ? delta * kPreloadCount : 1); if (from > till) std::swap(from, till); if (delta != 0) { auto forgetIndex = *_index - delta * 2; auto entity = entityByIndex(forgetIndex); if (auto photo = base::get_if<not_null<PhotoData*>>(&entity.data)) { (*photo)->forget(); } else if (auto document = base::get_if<not_null<DocumentData*>>(&entity.data)) { (*document)->forget(); } } for (auto index = from; index != till; ++index) { auto entity = entityByIndex(index); if (auto photo = base::get_if<not_null<PhotoData*>>(&entity.data)) { (*photo)->download(); } else if (auto document = base::get_if<not_null<DocumentData*>>(&entity.data)) { if (auto sticker = (*document)->sticker()) { sticker->img->load(); } else { (*document)->thumb->load(); (*document)->automaticLoad(entity.item); } } } } void MediaView::mousePressEvent(QMouseEvent *e) { updateOver(e->pos()); if (_menu || !_receiveMouse) return; ClickHandler::pressed(); if (e->button() == Qt::LeftButton) { _down = OverNone; if (!ClickHandler::getPressed()) { if (_over == OverLeftNav && moveToNext(-1)) { _lastAction = e->pos(); } else if (_over == OverRightNav && moveToNext(1)) { _lastAction = e->pos(); } else if (_over == OverName) { _down = OverName; } else if (_over == OverDate) { _down = OverDate; } else if (_over == OverHeader) { _down = OverHeader; } else if (_over == OverSave) { _down = OverSave; } else if (_over == OverIcon) { _down = OverIcon; } else if (_over == OverMore) { _down = OverMore; } else if (_over == OverClose) { _down = OverClose; } else if (_over == OverVideo) { _down = OverVideo; } else if (!_saveMsg.contains(e->pos()) || !_saveMsgStarted) { _pressed = true; _dragging = 0; updateCursor(); _mStart = e->pos(); _xStart = _x; _yStart = _y; } } } else if (e->button() == Qt::MiddleButton) { zoomReset(); } activateControls(); } void MediaView::mouseDoubleClickEvent(QMouseEvent *e) { updateOver(e->pos()); if (_over == OverVideo) { onVideoToggleFullScreen(); onVideoPauseResume(); } else { e->ignore(); return TWidget::mouseDoubleClickEvent(e); } } void MediaView::snapXY() { int32 xmin = width() - _w, xmax = 0; int32 ymin = height() - _h, ymax = 0; if (xmin > (width() - _w) / 2) xmin = (width() - _w) / 2; if (xmax < (width() - _w) / 2) xmax = (width() - _w) / 2; if (ymin > (height() - _h) / 2) ymin = (height() - _h) / 2; if (ymax < (height() - _h) / 2) ymax = (height() - _h) / 2; if (_x < xmin) _x = xmin; if (_x > xmax) _x = xmax; if (_y < ymin) _y = ymin; if (_y > ymax) _y = ymax; } void MediaView::mouseMoveEvent(QMouseEvent *e) { updateOver(e->pos()); if (_lastAction.x() >= 0 && (e->pos() - _lastAction).manhattanLength() >= st::mediaviewDeltaFromLastAction) { _lastAction = QPoint(-st::mediaviewDeltaFromLastAction, -st::mediaviewDeltaFromLastAction); } if (_pressed) { if (!_dragging && (e->pos() - _mStart).manhattanLength() >= QApplication::startDragDistance()) { _dragging = QRect(_x, _y, _w, _h).contains(_mStart) ? 1 : -1; if (_dragging > 0) { if (_w > width() || _h > height()) { setCursor(style::cur_sizeall); } else { setCursor(style::cur_default); } } } if (_dragging > 0) { _x = _xStart + (e->pos() - _mStart).x(); _y = _yStart + (e->pos() - _mStart).y(); snapXY(); update(); } } } void MediaView::updateOverRect(OverState state) { switch (state) { case OverLeftNav: update(_leftNav); break; case OverRightNav: update(_rightNav); break; case OverName: update(_nameNav); break; case OverDate: update(_dateNav); break; case OverSave: update(_saveNavIcon); break; case OverIcon: update(_docIconRect); break; case OverHeader: update(_headerNav); break; case OverClose: update(_closeNav); break; case OverMore: update(_moreNavIcon); break; } } bool MediaView::updateOverState(OverState newState) { bool result = true; if (_over != newState) { if (newState == OverMore && !_ignoringDropdown) { _dropdownShowTimer->start(0); } else { _dropdownShowTimer->stop(); } updateOverRect(_over); updateOverRect(newState); if (_over != OverNone) { _animations[_over] = getms(); ShowingOpacities::iterator i = _animOpacities.find(_over); if (i != _animOpacities.end()) { i->start(0); } else { _animOpacities.insert(_over, anim::value(1, 0)); } if (!_a_state.animating()) _a_state.start(); } else { result = false; } _over = newState; if (newState != OverNone) { _animations[_over] = getms(); ShowingOpacities::iterator i = _animOpacities.find(_over); if (i != _animOpacities.end()) { i->start(1); } else { _animOpacities.insert(_over, anim::value(0, 1)); } if (!_a_state.animating()) _a_state.start(); } updateCursor(); } return result; } void MediaView::updateOver(QPoint pos) { ClickHandlerPtr lnk; ClickHandlerHost *lnkhost = nullptr; if (_saveMsgStarted && _saveMsg.contains(pos)) { auto textState = _saveMsgText.getState(pos - _saveMsg.topLeft() - QPoint(st::mediaviewSaveMsgPadding.left(), st::mediaviewSaveMsgPadding.top()), _saveMsg.width() - st::mediaviewSaveMsgPadding.left() - st::mediaviewSaveMsgPadding.right()); lnk = textState.link; lnkhost = this; } else if (_captionRect.contains(pos)) { auto textState = _caption.getState(pos - _captionRect.topLeft(), _captionRect.width()); lnk = textState.link; lnkhost = this; } else if (_groupThumbs && _groupThumbsRect.contains(pos)) { const auto point = pos - QPoint(_groupThumbsLeft, _groupThumbsTop); lnk = _groupThumbs->getState(point); lnkhost = this; } // retina if (pos.x() == width()) { pos.setX(pos.x() - 1); } if (pos.y() == height()) { pos.setY(pos.y() - 1); } ClickHandler::setActive(lnk, lnkhost); if (_pressed || _dragging) return; if (_fullScreenVideo) { updateOverState(OverVideo); } else if (_leftNavVisible && _leftNav.contains(pos)) { updateOverState(OverLeftNav); } else if (_rightNavVisible && _rightNav.contains(pos)) { updateOverState(OverRightNav); } else if (_nameNav.contains(pos)) { updateOverState(OverName); } else if (IsServerMsgId(_msgid.msg) && _dateNav.contains(pos)) { updateOverState(OverDate); } else if (_headerHasLink && _headerNav.contains(pos)) { updateOverState(OverHeader); } else if (_saveVisible && _saveNav.contains(pos)) { updateOverState(OverSave); } else if (_doc && fileBubbleShown() && _docIconRect.contains(pos)) { updateOverState(OverIcon); } else if (_moreNav.contains(pos)) { updateOverState(OverMore); } else if (_closeNav.contains(pos)) { updateOverState(OverClose); } else if (_doc && fileShown() && QRect(_x, _y, _w, _h).contains(pos)) { if ((_doc->isVideoFile() || _doc->isVideoMessage()) && _gif) { updateOverState(OverVideo); } else if (!_doc->loaded()) { updateOverState(OverIcon); } else if (_over != OverNone) { updateOverState(OverNone); } } else if (_over != OverNone) { updateOverState(OverNone); } } void MediaView::mouseReleaseEvent(QMouseEvent *e) { updateOver(e->pos()); if (ClickHandlerPtr activated = ClickHandler::unpressed()) { App::activateClickHandler(activated, e->button()); return; } if (_over == OverName && _down == OverName) { if (_from) { close(); Ui::showPeerProfile(_from); } } else if (_over == OverDate && _down == OverDate) { onToMessage(); } else if (_over == OverHeader && _down == OverHeader) { onOverview(); } else if (_over == OverSave && _down == OverSave) { onDownload(); } else if (_over == OverIcon && _down == OverIcon) { onDocClick(); } else if (_over == OverMore && _down == OverMore) { QTimer::singleShot(0, this, SLOT(onDropdown())); } else if (_over == OverClose && _down == OverClose) { close(); } else if (_over == OverVideo && _down == OverVideo) { onVideoPauseResume(); } else if (_pressed) { if (_dragging) { if (_dragging > 0) { _x = _xStart + (e->pos() - _mStart).x(); _y = _yStart + (e->pos() - _mStart).y(); snapXY(); update(); } _dragging = 0; setCursor(style::cur_default); } else if ((e->pos() - _lastAction).manhattanLength() >= st::mediaviewDeltaFromLastAction) { if (_themePreviewShown) { if (!_themePreviewRect.contains(e->pos())) { close(); } } else if (!_doc || fileShown() || !_docRect.contains(e->pos())) { close(); } } _pressed = false; } _down = OverNone; if (!isHidden()) { activateControls(); } } void MediaView::contextMenuEvent(QContextMenuEvent *e) { if (e->reason() != QContextMenuEvent::Mouse || QRect(_x, _y, _w, _h).contains(e->pos())) { if (_menu) { _menu->deleteLater(); _menu = 0; } _menu = new Ui::PopupMenu(nullptr, st::mediaviewPopupMenu); updateActions(); for_const (auto &action, _actions) { _menu->addAction(action.text, this, action.member); } connect(_menu, SIGNAL(destroyed(QObject*)), this, SLOT(onMenuDestroy(QObject*))); _menu->popup(e->globalPos()); e->accept(); activateControls(); } } void MediaView::touchEvent(QTouchEvent *e) { switch (e->type()) { case QEvent::TouchBegin: { if (_touchPress || e->touchPoints().isEmpty()) return; _touchTimer.start(QApplication::startDragTime()); _touchPress = true; _touchMove = _touchRightButton = false; _touchStart = e->touchPoints().cbegin()->screenPos().toPoint(); } break; case QEvent::TouchUpdate: { if (!_touchPress || e->touchPoints().isEmpty()) return; if (!_touchMove && (e->touchPoints().cbegin()->screenPos().toPoint() - _touchStart).manhattanLength() >= QApplication::startDragDistance()) { _touchMove = true; } } break; case QEvent::TouchEnd: { if (!_touchPress) return; auto weak = make_weak(this); if (!_touchMove) { Qt::MouseButton btn(_touchRightButton ? Qt::RightButton : Qt::LeftButton); auto mapped = mapFromGlobal(_touchStart); QMouseEvent pressEvent(QEvent::MouseButtonPress, mapped, mapped, _touchStart, btn, Qt::MouseButtons(btn), Qt::KeyboardModifiers()); pressEvent.accept(); if (weak) mousePressEvent(&pressEvent); QMouseEvent releaseEvent(QEvent::MouseButtonRelease, mapped, mapped, _touchStart, btn, Qt::MouseButtons(btn), Qt::KeyboardModifiers()); if (weak) mouseReleaseEvent(&releaseEvent); if (weak && _touchRightButton) { QContextMenuEvent contextEvent(QContextMenuEvent::Mouse, mapped, _touchStart); contextMenuEvent(&contextEvent); } } else if (_touchMove) { if ((!_leftNavVisible || !_leftNav.contains(mapFromGlobal(_touchStart))) && (!_rightNavVisible || !_rightNav.contains(mapFromGlobal(_touchStart)))) { QPoint d = (e->touchPoints().cbegin()->screenPos().toPoint() - _touchStart); if (d.x() * d.x() > d.y() * d.y() && (d.x() > st::mediaviewSwipeDistance || d.x() < -st::mediaviewSwipeDistance)) { moveToNext(d.x() > 0 ? -1 : 1); } } } if (weak) { _touchTimer.stop(); _touchPress = _touchMove = _touchRightButton = false; } } break; case QEvent::TouchCancel: { _touchPress = false; _touchTimer.stop(); } break; } } bool MediaView::event(QEvent *e) { if (e->type() == QEvent::TouchBegin || e->type() == QEvent::TouchUpdate || e->type() == QEvent::TouchEnd || e->type() == QEvent::TouchCancel) { QTouchEvent *ev = static_cast<QTouchEvent*>(e); if (ev->device()->type() == QTouchDevice::TouchScreen) { if (ev->type() != QEvent::TouchBegin || ev->touchPoints().isEmpty() || !childAt(mapFromGlobal(ev->touchPoints().cbegin()->screenPos().toPoint()))) { touchEvent(ev); return true; } } } else if (e->type() == QEvent::Wheel) { QWheelEvent *ev = static_cast<QWheelEvent*>(e); if (ev->phase() == Qt::ScrollBegin) { _accumScroll = ev->angleDelta(); } else { _accumScroll += ev->angleDelta(); if (ev->phase() == Qt::ScrollEnd) { if (ev->orientation() == Qt::Horizontal) { if (_accumScroll.x() * _accumScroll.x() > _accumScroll.y() * _accumScroll.y() && _accumScroll.x() != 0) { moveToNext(_accumScroll.x() > 0 ? -1 : 1); } _accumScroll = QPoint(); } } } } return TWidget::event(e); } bool MediaView::eventFilter(QObject *obj, QEvent *e) { auto type = e->type(); if ((type == QEvent::MouseMove || type == QEvent::MouseButtonPress || type == QEvent::MouseButtonRelease) && obj->isWidgetType()) { if (isAncestorOf(static_cast<QWidget*>(obj))) { const auto mouseEvent = static_cast<QMouseEvent*>(e); const auto mousePosition = mapFromGlobal(mouseEvent->globalPos()); const auto delta = (mousePosition - _lastMouseMovePos); auto activate = delta.manhattanLength() >= st::mediaviewDeltaFromLastAction; if (activate) { _lastMouseMovePos = mousePosition; } if (type == QEvent::MouseButtonPress) { _mousePressed = true; activate = true; } else if (type == QEvent::MouseButtonRelease) { _mousePressed = false; activate = true; } if (activate) { if (_controlsState == ControlsHiding || _controlsState == ControlsHidden) { int a = 0; } activateControls(); } } } return TWidget::eventFilter(obj, e); } void MediaView::setVisible(bool visible) { if (!visible) { _sharedMedia = nullptr; _sharedMediaData = base::none; _sharedMediaDataKey = base::none; _userPhotos = nullptr; _userPhotosData = base::none; if (_menu) _menu->hideMenu(true); _controlsHideTimer.stop(); _controlsState = ControlsShown; a_cOpacity = anim::value(1, 1); _groupThumbs = nullptr; _groupThumbsRect = QRect(); } TWidget::setVisible(visible); if (visible) { QCoreApplication::instance()->installEventFilter(this); } else { QCoreApplication::instance()->removeEventFilter(this); stopGif(); destroyThemePreview(); _radial.stop(); } } void MediaView::onMenuDestroy(QObject *obj) { if (_menu == obj) { _menu = 0; activateControls(); } _receiveMouse = false; QTimer::singleShot(0, this, SLOT(receiveMouse())); } void MediaView::receiveMouse() { _receiveMouse = true; } void MediaView::onDropdown() { updateActions(); _dropdown->clearActions(); for_const (auto &action, _actions) { _dropdown->addAction(action.text, this, action.member); } _dropdown->moveToRight(0, height() - _dropdown->height()); _dropdown->showAnimated(Ui::PanelAnimation::Origin::BottomRight); _dropdown->setFocus(); } void MediaView::onTouchTimer() { _touchRightButton = true; } void MediaView::updateImage() { update(_saveMsg); } void MediaView::findCurrent() { using namespace rpl::mappers; if (_sharedMediaData) { _index = _msgid ? _sharedMediaData->indexOf(_msgid) : _photo ? _sharedMediaData->indexOf(_photo) : base::none; _fullIndex = _sharedMediaData->skippedBefore() ? (_index | func::add(*_sharedMediaData->skippedBefore())) : base::none; _fullCount = _sharedMediaData->fullCount(); } else if (_userPhotosData) { _index = _photo ? _userPhotosData->indexOf(_photo->id) : base::none; _fullIndex = _userPhotosData->skippedBefore() ? (_index | func::add(*_userPhotosData->skippedBefore())) : base::none; _fullCount = _userPhotosData->fullCount(); } else { _index = _fullIndex = _fullCount = base::none; } } void MediaView::updateHeader() { auto index = _fullIndex ? *_fullIndex : -1; auto count = _fullCount ? *_fullCount : -1; if (index >= 0 && index < count && count > 1) { if (_doc) { _headerText = lng_mediaview_file_n_of_count(lt_file, _doc->filename().isEmpty() ? lang(lng_mediaview_doc_image) : _doc->filename(), lt_n, QString::number(index + 1), lt_count, QString::number(count)); } else { _headerText = lng_mediaview_n_of_count(lt_n, QString::number(index + 1), lt_count, QString::number(count)); } } else { if (_doc) { _headerText = _doc->filename().isEmpty() ? lang(lng_mediaview_doc_image) : _doc->filename(); } else if (_msgid) { _headerText = lang(lng_mediaview_single_photo); } else if (_user) { _headerText = lang(lng_mediaview_profile_photo); } else if ((_history && _history->channelId() && !_history->isMegagroup()) || (_peer && _peer->isChannel() && !_peer->isMegagroup())) { _headerText = lang(lng_mediaview_channel_photo); } else if (_peer) { _headerText = lang(lng_mediaview_group_photo); } else { _headerText = lang(lng_mediaview_single_photo); } } _headerHasLink = computeOverviewType() != base::none; auto hwidth = st::mediaviewThickFont->width(_headerText); if (hwidth > width() / 3) { hwidth = width() / 3; _headerText = st::mediaviewThickFont->elided(_headerText, hwidth, Qt::ElideMiddle); } _headerNav = myrtlrect(st::mediaviewTextLeft, height() - st::mediaviewHeaderTop, hwidth, st::mediaviewThickFont->height); } float64 MediaView::overLevel(OverState control) const { auto i = _animOpacities.constFind(control); return (i == _animOpacities.cend()) ? (_over == control ? 1 : 0) : i->current(); } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/mediaview.h����������������������������������������������������0000664�0000000�0000000�00000025222�13262451251�0021551�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/widgets/dropdown_menu.h" #include "ui/effects/radial_animation.h" #include "data/data_shared_media.h" #include "data/data_user_photos.h" namespace Media { namespace Player { struct TrackState; } // namespace Player namespace Clip { class Controller; } // namespace Clip namespace View { class GroupThumbs; } // namespace View } // namespace Media namespace Ui { class PopupMenu; class LinkButton; class RoundButton; } // namespace Ui namespace Window { namespace Theme { struct Preview; } // namespace Theme } // namespace Window namespace Notify { struct PeerUpdate; } // namespace Notify class MediaView : public TWidget, private base::Subscriber, public ClickHandlerHost { Q_OBJECT public: MediaView(); void setVisible(bool visible) override; void showPhoto(not_null<PhotoData*> photo, HistoryItem *context); void showPhoto(not_null<PhotoData*> photo, not_null<PeerData*> context); void showDocument(not_null<DocumentData*> document, HistoryItem *context); void leaveToChildEvent(QEvent *e, QWidget *child) override { // e -- from enterEvent() of child TWidget updateOverState(OverNone); } void enterFromChildEvent(QEvent *e, QWidget *child) override { // e -- from leaveEvent() of child TWidget updateOver(mapFromGlobal(QCursor::pos())); } void close(); void activateControls(); void onDocClick(); void clipCallback(Media::Clip::Notification notification); PeerData *ui_getPeerForMouseAction(); void clearData(); ~MediaView(); // ClickHandlerHost interface void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override; protected: void paintEvent(QPaintEvent *e) override; void keyPressEvent(QKeyEvent *e) override; void wheelEvent(QWheelEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseDoubleClickEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void contextMenuEvent(QContextMenuEvent *e) override; void touchEvent(QTouchEvent *e); bool event(QEvent *e) override; bool eventFilter(QObject *obj, QEvent *e) override; private slots: void onHideControls(bool force = false); void onScreenResized(int screen); void onToMessage(); void onSaveAs(); void onDownload(); void onSaveCancel(); void onShowInFolder(); void onForward(); void onDelete(); void onOverview(); void onCopy(); void onMenuDestroy(QObject *obj); void receiveMouse(); void onDropdown(); void onTouchTimer(); void updateImage(); void onVideoPauseResume(); void onVideoSeekProgress(TimeMs positionMs); void onVideoSeekFinished(TimeMs positionMs); void onVideoVolumeChanged(float64 volume); void onVideoToggleFullScreen(); void onVideoPlayProgress(const AudioMsgId &audioId); private: enum OverState { OverNone, OverLeftNav, OverRightNav, OverClose, OverHeader, OverName, OverDate, OverSave, OverMore, OverIcon, OverVideo, }; void updateOver(QPoint mpos); void moveToScreen(); bool moveToNext(int delta); void preloadData(int delta); struct Entity { base::optional_variant< not_null<PhotoData*>, not_null<DocumentData*>> data; HistoryItem *item; }; Entity entityForUserPhotos(int index) const; Entity entityForSharedMedia(int index) const; Entity entityByIndex(int index) const; Entity entityForItemId(const FullMsgId &itemId) const; bool moveToEntity(const Entity &entity, int preloadDelta = 0); void setContext(base::optional_variant< not_null<HistoryItem*>, not_null<PeerData*>> context); void refreshLang(); void showSaveMsgFile(); void updateMixerVideoVolume() const; struct SharedMedia; using SharedMediaType = SharedMediaWithLastSlice::Type; using SharedMediaKey = SharedMediaWithLastSlice::Key; base::optional<SharedMediaType> sharedMediaType() const; base::optional<SharedMediaKey> sharedMediaKey() const; base::optional<SharedMediaType> computeOverviewType() const; bool validSharedMedia() const; void validateSharedMedia(); void handleSharedMediaUpdate(SharedMediaWithLastSlice &&update); struct UserPhotos; using UserPhotosKey = UserPhotosSlice::Key; base::optional<UserPhotosKey> userPhotosKey() const; bool validUserPhotos() const; void validateUserPhotos(); void handleUserPhotosUpdate(UserPhotosSlice &&update); void refreshCaption(HistoryItem *item); void refreshMediaViewer(); void refreshNavVisibility(); void refreshGroupThumbs(); void dropdownHidden(); void updateDocSize(); void updateControls(); void updateActions(); void resizeCenteredControls(); void displayPhoto(not_null<PhotoData*> photo, HistoryItem *item); void displayDocument(DocumentData *document, HistoryItem *item); void displayFinished(); void findCurrent(); void updateCursor(); void setZoomLevel(int newZoom); void updateVideoPlaybackState(const Media::Player::TrackState &state); void updateSilentVideoPlaybackState(); void restartVideoAtSeekPosition(TimeMs positionMs); void toggleVideoPaused(); void createClipController(); void refreshClipControllerGeometry(); void refreshCaptionGeometry(); void initAnimation(); void createClipReader(); Images::Options videoThumbOptions() const; void initThemePreview(); void destroyThemePreview(); void updateThemePreviewGeometry(); void documentUpdated(DocumentData *doc); void changingMsgId(not_null<HistoryItem*> row, MsgId newId); // Radial animation interface. float64 radialProgress() const; bool radialLoading() const; QRect radialRect() const; void radialStart(); TimeMs radialTimeShift() const; void updateHeader(); void snapXY(); void step_state(TimeMs ms, bool timer); void step_radial(TimeMs ms, bool timer); void zoomIn(); void zoomOut(); void zoomReset(); void zoomUpdate(int32 &newZoom); void paintDocRadialLoading(Painter &p, bool radial, float64 radialOpacity); void paintThemePreview(Painter &p, QRect clip); void updateOverRect(OverState state); bool updateOverState(OverState newState); float64 overLevel(OverState control) const; void checkGroupThumbsAnimation(); void initGroupThumbs(); QBrush _transparentBrush; PhotoData *_photo = nullptr; DocumentData *_doc = nullptr; std::unique_ptr<SharedMedia> _sharedMedia; base::optional<SharedMediaWithLastSlice> _sharedMediaData; base::optional<SharedMediaWithLastSlice::Key> _sharedMediaDataKey; std::unique_ptr<UserPhotos> _userPhotos; base::optional<UserPhotosSlice> _userPhotosData; QRect _closeNav, _closeNavIcon; QRect _leftNav, _leftNavIcon, _rightNav, _rightNavIcon; QRect _headerNav, _nameNav, _dateNav; QRect _saveNav, _saveNavIcon, _moreNav, _moreNavIcon; bool _leftNavVisible = false; bool _rightNavVisible = false; bool _saveVisible = false; bool _headerHasLink = false; QString _dateText; QString _headerText; object_ptr<Media::Clip::Controller> _clipController = { nullptr }; DocumentData *_autoplayVideoDocument = nullptr; bool _fullScreenVideo = false; int _fullScreenZoomCache = 0; std::unique_ptr<Media::View::GroupThumbs> _groupThumbs; QRect _groupThumbsRect; int _groupThumbsAvailableWidth = 0; int _groupThumbsLeft = 0; int _groupThumbsTop = 0; Text _caption; QRect _captionRect; TimeMs _animStarted; int _width = 0; int _x = 0, _y = 0, _w = 0, _h = 0; int _xStart = 0, _yStart = 0; int _zoom = 0; // < 0 - out, 0 - none, > 0 - in float64 _zoomToScreen = 0.; // for documents QPoint _mStart; bool _pressed = false; int32 _dragging = 0; QPixmap _current; Media::Clip::ReaderPointer _gif; int32 _full = -1; // -1 - thumb, 0 - medium, 1 - full // Video without audio stream playback information. bool _videoIsSilent = false; bool _videoPaused = false; bool _videoStopped = false; TimeMs _videoPositionMs = 0; TimeMs _videoDurationMs = 0; int32 _videoFrequencyMs = 1000; // 1000 ms per second. bool fileShown() const; bool gifShown() const; bool fileBubbleShown() const; void stopGif(); const style::icon *_docIcon = nullptr; style::color _docIconColor; QString _docName, _docSize, _docExt; int _docNameWidth = 0, _docSizeWidth = 0, _docExtWidth = 0; QRect _docRect, _docIconRect; int _docThumbx = 0, _docThumby = 0, _docThumbw = 0; object_ptr<Ui::LinkButton> _docDownload; object_ptr<Ui::LinkButton> _docSaveAs; object_ptr<Ui::LinkButton> _docCancel; QRect _photoRadialRect; Ui::RadialAnimation _radial; History *_migrated = nullptr; History *_history = nullptr; // if conversation photos or files overview PeerData *_peer = nullptr; UserData *_user = nullptr; // if user profile photos overview // We save the information about the reason of the current mediaview show: // did we open a peer profile photo or a photo from some message. // We use it when trying to delete a photo: if we've opened a peer photo, // then we'll delete group photo instead of the corresponding message. bool _firstOpenedPeerPhoto = false; PeerData *_from = nullptr; Text _fromName; base::optional<int> _index; // Index in current _sharedMedia data. base::optional<int> _fullIndex; // Index in full shared media. base::optional<int> _fullCount; FullMsgId _msgid; bool _canForwardItem = false; bool _canDeleteItem = false; mtpRequestId _loadRequest = 0; OverState _over = OverNone; OverState _down = OverNone; QPoint _lastAction, _lastMouseMovePos; bool _ignoringDropdown = false; BasicAnimation _a_state; enum ControlsState { ControlsShowing, ControlsShown, ControlsHiding, ControlsHidden, }; ControlsState _controlsState = ControlsShown; TimeMs _controlsAnimStarted = 0; QTimer _controlsHideTimer; anim::value a_cOpacity; bool _mousePressed = false; Ui::PopupMenu *_menu = nullptr; object_ptr<Ui::DropdownMenu> _dropdown; object_ptr<QTimer> _dropdownShowTimer; struct ActionData { QString text; const char *member; }; QList<ActionData> _actions; bool _receiveMouse = true; bool _touchPress = false, _touchMove = false, _touchRightButton = false; QTimer _touchTimer; QPoint _touchStart; QPoint _accumScroll; QString _saveMsgFilename; TimeMs _saveMsgStarted = 0; anim::value _saveMsgOpacity; QRect _saveMsg; QTimer _saveMsgUpdater; Text _saveMsgText; typedef QMap<OverState, TimeMs> Showing; Showing _animations; typedef QMap<OverState, anim::value> ShowingOpacities; ShowingOpacities _animOpacities; int _verticalWheelDelta = 0; bool _themePreviewShown = false; uint64 _themePreviewId = 0; QRect _themePreviewRect; std::unique_ptr<Window::Theme::Preview> _themePreview; object_ptr<Ui::RoundButton> _themeApply = { nullptr }; object_ptr<Ui::RoundButton> _themeCancel = { nullptr }; }; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/messenger.cpp��������������������������������������������������0000664�0000000�0000000�00000075341�13262451251�0022131�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "messenger.h" #include <rpl/complete.h> #include "data/data_photo.h" #include "data/data_document.h" #include "data/data_session.h" #include "base/timer.h" #include "storage/localstorage.h" #include "platform/platform_specific.h" #include "mainwindow.h" #include "dialogs/dialogs_entry.h" #include "history/history.h" #include "application.h" #include "shortcuts.h" #include "auth_session.h" #include "apiwrap.h" #include "calls/calls_instance.h" #include "lang/lang_file_parser.h" #include "lang/lang_translator.h" #include "lang/lang_cloud_manager.h" #include "observer_peer.h" #include "storage/file_upload.h" #include "mainwidget.h" #include "mediaview.h" #include "mtproto/dc_options.h" #include "mtproto/mtp_instance.h" #include "media/player/media_player_instance.h" #include "media/media_audio_track.h" #include "window/notifications_manager.h" #include "window/themes/window_theme.h" #include "history/history_location_manager.h" #include "ui/widgets/tooltip.h" #include "ui/text_options.h" #include "storage/serialize_common.h" #include "window/window_controller.h" #include "base/qthelp_regex.h" #include "base/qthelp_url.h" #include "boxes/connection_box.h" #include "boxes/confirm_phone_box.h" #include "boxes/share_box.h" namespace { constexpr auto kQuitPreventTimeoutMs = 1500; Messenger *SingleInstance = nullptr; } // namespace Messenger *Messenger::InstancePointer() { return SingleInstance; } struct Messenger::Private { UserId authSessionUserId = 0; std::unique_ptr<AuthSessionSettings> storedAuthSession; MTP::Instance::Config mtpConfig; MTP::AuthKeysList mtpKeysToDestroy; base::Timer quitTimer; }; Messenger::Messenger(not_null<Core::Launcher*> launcher) : QObject() , _launcher(launcher) , _private(std::make_unique<Private>()) , _langpack(std::make_unique<Lang::Instance>()) , _audio(std::make_unique<Media::Audio::Instance>()) , _logo(Window::LoadLogo()) , _logoNoMargin(Window::LoadLogoNoMargin()) { Expects(!_logo.isNull()); Expects(!_logoNoMargin.isNull()); Expects(SingleInstance == nullptr); SingleInstance = this; Fonts::Start(); ThirdParty::start(); Global::start(); startLocalStorage(); if (Local::oldSettingsVersion() < AppVersion) { psNewVersion(); } if (cLaunchMode() == LaunchModeAutoStart && !cAutoStart()) { psAutoStart(false, true); App::quit(); return; } if (cRetina()) { cSetConfigScale(dbisOne); cSetRealScale(dbisOne); } _translator = std::make_unique<Lang::Translator>(); QCoreApplication::instance()->installTranslator(_translator.get()); style::startManager(); anim::startManager(); Ui::InitTextOptions(); Media::Player::start(); DEBUG_LOG(("Application Info: inited...")); QCoreApplication::instance()->installNativeEventFilter(psNativeEventFilter()); cChangeTimeFormat(QLocale::system().timeFormat(QLocale::ShortFormat)); connect(&killDownloadSessionsTimer, SIGNAL(timeout()), this, SLOT(killDownloadSessions())); DEBUG_LOG(("Application Info: starting app...")); // Create mime database, so it won't be slow later. QMimeDatabase().mimeTypeForName(qsl("text/plain")); _window = std::make_unique<MainWindow>(); _window->init(); auto currentGeometry = _window->geometry(); _mediaView = std::make_unique<MediaView>(); _window->setGeometry(currentGeometry); QCoreApplication::instance()->installEventFilter(this); Sandbox::connect(SIGNAL(applicationStateChanged(Qt::ApplicationState)), this, SLOT(onAppStateChanged(Qt::ApplicationState))); DEBUG_LOG(("Application Info: window created...")); Shortcuts::start(); initLocationManager(); App::initMedia(); Local::ReadMapState state = Local::readMap(QByteArray()); if (state == Local::ReadMapPassNeeded) { Global::SetLocalPasscode(true); Global::RefLocalPasscodeChanged().notify(); DEBUG_LOG(("Application Info: passcode needed...")); } else { DEBUG_LOG(("Application Info: local map read...")); startMtp(); } DEBUG_LOG(("Application Info: MTP started...")); DEBUG_LOG(("Application Info: showing.")); if (state == Local::ReadMapPassNeeded) { setupPasscode(); } else { if (AuthSession::Exists()) { _window->setupMain(); } else { _window->setupIntro(); } } _window->firstShow(); if (cStartToSettings()) { _window->showSettings(); } #ifndef TDESKTOP_DISABLE_NETWORK_PROXY QNetworkProxyFactory::setUseSystemConfiguration(true); #endif // !TDESKTOP_DISABLE_NETWORK_PROXY _window->updateIsActive(Global::OnlineFocusTimeout()); if (!Shortcuts::errors().isEmpty()) { const QStringList &errors(Shortcuts::errors()); for (QStringList::const_iterator i = errors.cbegin(), e = errors.cend(); i != e; ++i) { LOG(("Shortcuts Error: %1").arg(*i)); } } } bool Messenger::hideMediaView() { if (_mediaView && !_mediaView->isHidden()) { _mediaView->hide(); if (auto activeWindow = getActiveWindow()) { activeWindow->reActivateWindow(); } return true; } return false; } void Messenger::showPhoto(not_null<const PhotoOpenClickHandler*> link) { const auto item = App::histItemById(link->context()); const auto peer = link->peer(); return (!item && peer) ? showPhoto(link->photo(), peer) : showPhoto(link->photo(), item); } void Messenger::showPhoto(not_null<PhotoData*> photo, HistoryItem *item) { if (_mediaView->isHidden()) Ui::hideLayer(anim::type::instant); _mediaView->showPhoto(photo, item); _mediaView->activateWindow(); _mediaView->setFocus(); } void Messenger::showPhoto( not_null<PhotoData*> photo, not_null<PeerData*> peer) { if (_mediaView->isHidden()) Ui::hideLayer(anim::type::instant); _mediaView->showPhoto(photo, peer); _mediaView->activateWindow(); _mediaView->setFocus(); } void Messenger::showDocument(not_null<DocumentData*> document, HistoryItem *item) { if (cUseExternalVideoPlayer() && document->isVideoFile()) { QDesktopServices::openUrl(QUrl("file:///" + document->location(false).fname)); } else { if (_mediaView->isHidden()) { Ui::hideLayer(anim::type::instant); } _mediaView->showDocument(document, item); _mediaView->activateWindow(); _mediaView->setFocus(); } } PeerData *Messenger::ui_getPeerForMouseAction() { if (_mediaView && !_mediaView->isHidden()) { return _mediaView->ui_getPeerForMouseAction(); } else if (auto main = App::main()) { return main->ui_getPeerForMouseAction(); } return nullptr; } bool Messenger::eventFilter(QObject *object, QEvent *e) { switch (e->type()) { case QEvent::KeyPress: case QEvent::MouseButtonPress: case QEvent::TouchBegin: case QEvent::Wheel: { psUserActionDone(); } break; case QEvent::ShortcutOverride: { // handle shortcuts ourselves return true; } break; case QEvent::Shortcut: { DEBUG_LOG(("Shortcut event caught: %1").arg(static_cast<QShortcutEvent*>(e)->key().toString())); if (Shortcuts::launch(static_cast<QShortcutEvent*>(e)->shortcutId())) { return true; } } break; case QEvent::ApplicationActivate: { if (object == QCoreApplication::instance()) { psUserActionDone(); } } break; case QEvent::FileOpen: { if (object == QCoreApplication::instance()) { auto url = QString::fromUtf8(static_cast<QFileOpenEvent*>(e)->url().toEncoded().trimmed()); if (url.startsWith(qstr("tg://"), Qt::CaseInsensitive)) { cSetStartUrl(url.mid(0, 8192)); checkStartUrl(); } _window->activate(); } } break; } return QObject::eventFilter(object, e); } void Messenger::setMtpMainDcId(MTP::DcId mainDcId) { Expects(!_mtproto); _private->mtpConfig.mainDcId = mainDcId; } void Messenger::setMtpKey(MTP::DcId dcId, const MTP::AuthKey::Data &keyData) { Expects(!_mtproto); _private->mtpConfig.keys.push_back(std::make_shared<MTP::AuthKey>(MTP::AuthKey::Type::ReadFromFile, dcId, keyData)); } QByteArray Messenger::serializeMtpAuthorization() const { auto serialize = [this](auto mainDcId, auto &keys, auto &keysToDestroy) { auto keysSize = [](auto &list) { return sizeof(qint32) + list.size() * (sizeof(qint32) + MTP::AuthKey::Data().size()); }; auto writeKeys = [](QDataStream &stream, auto &keys) { stream << qint32(keys.size()); for (auto &key : keys) { stream << qint32(key->dcId()); key->write(stream); } }; auto result = QByteArray(); auto size = sizeof(qint32) + sizeof(qint32); // userId + mainDcId size += keysSize(keys) + keysSize(keysToDestroy); result.reserve(size); { QDataStream stream(&result, QIODevice::WriteOnly); stream.setVersion(QDataStream::Qt_5_1); auto currentUserId = _authSession ? _authSession->userId() : 0; stream << qint32(currentUserId) << qint32(mainDcId); writeKeys(stream, keys); writeKeys(stream, keysToDestroy); DEBUG_LOG(("MTP Info: Keys written, userId: %1, dcId: %2").arg(currentUserId).arg(mainDcId)); } return result; }; if (_mtproto) { auto keys = _mtproto->getKeysForWrite(); auto keysToDestroy = _mtprotoForKeysDestroy ? _mtprotoForKeysDestroy->getKeysForWrite() : MTP::AuthKeysList(); return serialize(_mtproto->mainDcId(), keys, keysToDestroy); } auto &keys = _private->mtpConfig.keys; auto &keysToDestroy = _private->mtpKeysToDestroy; return serialize(_private->mtpConfig.mainDcId, keys, keysToDestroy); } void Messenger::setAuthSessionUserId(UserId userId) { Expects(!authSession()); _private->authSessionUserId = userId; } void Messenger::setAuthSessionFromStorage(std::unique_ptr<AuthSessionSettings> data) { Expects(!authSession()); _private->storedAuthSession = std::move(data); } AuthSessionSettings *Messenger::getAuthSessionSettings() { if (_private->authSessionUserId) { return _private->storedAuthSession ? _private->storedAuthSession.get() : nullptr; } else if (_authSession) { return &_authSession->settings(); } return nullptr; } void Messenger::setMtpAuthorization(const QByteArray &serialized) { Expects(!_mtproto); QDataStream stream(serialized); stream.setVersion(QDataStream::Qt_5_1); auto userId = Serialize::read<qint32>(stream); auto mainDcId = Serialize::read<qint32>(stream); if (stream.status() != QDataStream::Ok) { LOG(("MTP Error: could not read main fields from serialized mtp authorization.")); return; } setAuthSessionUserId(userId); _private->mtpConfig.mainDcId = mainDcId; auto readKeys = [&stream](auto &keys) { auto count = Serialize::read<qint32>(stream); if (stream.status() != QDataStream::Ok) { LOG(("MTP Error: could not read keys count from serialized mtp authorization.")); return; } keys.reserve(count); for (auto i = 0; i != count; ++i) { auto dcId = Serialize::read<qint32>(stream); auto keyData = Serialize::read<MTP::AuthKey::Data>(stream); if (stream.status() != QDataStream::Ok) { LOG(("MTP Error: could not read key from serialized mtp authorization.")); return; } keys.push_back(std::make_shared<MTP::AuthKey>(MTP::AuthKey::Type::ReadFromFile, dcId, keyData)); } }; readKeys(_private->mtpConfig.keys); readKeys(_private->mtpKeysToDestroy); LOG(("MTP Info: read keys, current: %1, to destroy: %2").arg(_private->mtpConfig.keys.size()).arg(_private->mtpKeysToDestroy.size())); } void Messenger::startMtp() { Expects(!_mtproto); _mtproto = std::make_unique<MTP::Instance>(_dcOptions.get(), MTP::Instance::Mode::Normal, base::take(_private->mtpConfig)); _private->mtpConfig.mainDcId = _mtproto->mainDcId(); _mtproto->setStateChangedHandler([](MTP::ShiftedDcId shiftedDcId, int32 state) { if (App::wnd()) { App::wnd()->mtpStateChanged(shiftedDcId, state); } }); _mtproto->setSessionResetHandler([](MTP::ShiftedDcId shiftedDcId) { if (App::main() && shiftedDcId == MTP::maindc()) { App::main()->getDifference(); } }); if (!_private->mtpKeysToDestroy.empty()) { destroyMtpKeys(base::take(_private->mtpKeysToDestroy)); } if (_private->authSessionUserId) { authSessionCreate(base::take(_private->authSessionUserId)); } if (_private->storedAuthSession) { if (_authSession) { _authSession->settings().moveFrom( std::move(*_private->storedAuthSession)); } _private->storedAuthSession.reset(); } _langCloudManager = std::make_unique<Lang::CloudManager>(langpack(), mtp()); } void Messenger::destroyMtpKeys(MTP::AuthKeysList &&keys) { if (keys.empty()) { return; } if (_mtprotoForKeysDestroy) { _mtprotoForKeysDestroy->addKeysForDestroy(std::move(keys)); Local::writeMtpData(); return; } auto destroyConfig = MTP::Instance::Config(); destroyConfig.mainDcId = MTP::Instance::Config::kNoneMainDc; destroyConfig.keys = std::move(keys); _mtprotoForKeysDestroy = std::make_unique<MTP::Instance>(_dcOptions.get(), MTP::Instance::Mode::KeysDestroyer, std::move(destroyConfig)); connect(_mtprotoForKeysDestroy.get(), SIGNAL(allKeysDestroyed()), this, SLOT(onAllKeysDestroyed())); } void Messenger::onAllKeysDestroyed() { LOG(("MTP Info: all keys scheduled for destroy are destroyed.")); _mtprotoForKeysDestroy.reset(); Local::writeMtpData(); } void Messenger::suggestMainDcId(MTP::DcId mainDcId) { Assert(_mtproto != nullptr); _mtproto->suggestMainDcId(mainDcId); if (_private->mtpConfig.mainDcId != MTP::Instance::Config::kNotSetMainDc) { _private->mtpConfig.mainDcId = mainDcId; } } void Messenger::destroyStaleAuthorizationKeys() { Assert(_mtproto != nullptr); auto keys = _mtproto->getKeysForWrite(); for (auto &key : keys) { // Disable this for now. if (key->type() == MTP::AuthKey::Type::ReadFromFile) { _private->mtpKeysToDestroy = _mtproto->getKeysForWrite(); _mtproto.reset(); LOG(("MTP Info: destroying stale keys, count: %1").arg(_private->mtpKeysToDestroy.size())); startMtp(); Local::writeMtpData(); return; } } } void Messenger::startLocalStorage() { _dcOptions = std::make_unique<MTP::DcOptions>(); _dcOptions->constructFromBuiltIn(); Local::start(); subscribe(_dcOptions->changed(), [this](const MTP::DcOptions::Ids &ids) { Local::writeSettings(); if (auto instance = mtp()) { for (auto id : ids) { instance->restart(id); } } }); subscribe(authSessionChanged(), [this] { InvokeQueued(this, [this] { if (_mtproto) { _mtproto->requestConfig(); } }); }); } void Messenger::regPhotoUpdate(const PeerId &peer, const FullMsgId &msgId) { photoUpdates.insert(msgId, peer); } bool Messenger::isPhotoUpdating(const PeerId &peer) { for (QMap<FullMsgId, PeerId>::iterator i = photoUpdates.begin(), e = photoUpdates.end(); i != e; ++i) { if (i.value() == peer) { return true; } } return false; } void Messenger::cancelPhotoUpdate(const PeerId &peer) { for (QMap<FullMsgId, PeerId>::iterator i = photoUpdates.begin(), e = photoUpdates.end(); i != e;) { if (i.value() == peer) { i = photoUpdates.erase(i); } else { ++i; } } } void Messenger::selfPhotoCleared(const MTPUserProfilePhoto &result) { if (!App::self()) return; App::self()->setPhoto(result); emit peerPhotoDone(App::self()->id); } void Messenger::chatPhotoCleared(PeerId peer, const MTPUpdates &updates) { if (App::main()) { App::main()->sentUpdatesReceived(updates); } cancelPhotoUpdate(peer); emit peerPhotoDone(peer); } void Messenger::selfPhotoDone(const MTPphotos_Photo &result) { if (!App::self()) return; const auto &photo = result.c_photos_photo(); Auth().data().photo(photo.vphoto); App::feedUsers(photo.vusers); cancelPhotoUpdate(App::self()->id); emit peerPhotoDone(App::self()->id); } void Messenger::chatPhotoDone(PeerId peer, const MTPUpdates &updates) { if (App::main()) { App::main()->sentUpdatesReceived(updates); } cancelPhotoUpdate(peer); emit peerPhotoDone(peer); } bool Messenger::peerPhotoFailed(PeerId peer, const RPCError &error) { if (MTP::isDefaultHandledError(error)) return false; LOG(("Application Error: update photo failed %1: %2").arg(error.type()).arg(error.description())); cancelPhotoUpdate(peer); emit peerPhotoFail(peer); return true; } void Messenger::peerClearPhoto(PeerId id) { if (!AuthSession::Exists()) return; if (id == Auth().userPeerId()) { MTP::send(MTPphotos_UpdateProfilePhoto(MTP_inputPhotoEmpty()), rpcDone(&Messenger::selfPhotoCleared), rpcFail(&Messenger::peerPhotoFailed, id)); } else if (peerIsChat(id)) { MTP::send(MTPmessages_EditChatPhoto(peerToBareMTPInt(id), MTP_inputChatPhotoEmpty()), rpcDone(&Messenger::chatPhotoCleared, id), rpcFail(&Messenger::peerPhotoFailed, id)); } else if (peerIsChannel(id)) { if (auto channel = App::channelLoaded(id)) { MTP::send(MTPchannels_EditPhoto(channel->inputChannel, MTP_inputChatPhotoEmpty()), rpcDone(&Messenger::chatPhotoCleared, id), rpcFail(&Messenger::peerPhotoFailed, id)); } } } void Messenger::killDownloadSessionsStart(MTP::DcId dcId) { if (killDownloadSessionTimes.constFind(dcId) == killDownloadSessionTimes.cend()) { killDownloadSessionTimes.insert(dcId, getms() + MTPAckSendWaiting + MTPKillFileSessionTimeout); } if (!killDownloadSessionsTimer.isActive()) { killDownloadSessionsTimer.start(MTPAckSendWaiting + MTPKillFileSessionTimeout + 5); } } void Messenger::killDownloadSessionsStop(MTP::DcId dcId) { killDownloadSessionTimes.remove(dcId); if (killDownloadSessionTimes.isEmpty() && killDownloadSessionsTimer.isActive()) { killDownloadSessionsTimer.stop(); } } void Messenger::checkLocalTime() { if (App::main()) App::main()->checkLastUpdate(checkms()); } void Messenger::onAppStateChanged(Qt::ApplicationState state) { if (state == Qt::ApplicationActive) { handleAppActivated(); } else { handleAppDeactivated(); } } void Messenger::handleAppActivated() { checkLocalTime(); if (_window) { _window->updateIsActive(Global::OnlineFocusTimeout()); } } void Messenger::handleAppDeactivated() { if (_window) { _window->updateIsActive(Global::OfflineBlurTimeout()); } Ui::Tooltip::Hide(); } void Messenger::call_handleUnreadCounterUpdate() { Global::RefUnreadCounterUpdate().notify(true); } void Messenger::call_handleDelayedPeerUpdates() { Notify::peerUpdatedSendDelayed(); } void Messenger::call_handleObservables() { base::HandleObservables(); } void Messenger::killDownloadSessions() { auto ms = getms(), left = static_cast<TimeMs>(MTPAckSendWaiting) + MTPKillFileSessionTimeout; for (auto i = killDownloadSessionTimes.begin(); i != killDownloadSessionTimes.end(); ) { if (i.value() <= ms) { for (int j = 0; j < MTP::kDownloadSessionsCount; ++j) { MTP::stopSession(MTP::downloadDcId(i.key(), j)); } i = killDownloadSessionTimes.erase(i); } else { if (i.value() - ms < left) { left = i.value() - ms; } ++i; } } if (!killDownloadSessionTimes.isEmpty()) { killDownloadSessionsTimer.start(left); } } void Messenger::photoUpdated(const FullMsgId &msgId, bool silent, const MTPInputFile &file) { if (!AuthSession::Exists()) return; auto i = photoUpdates.find(msgId); if (i != photoUpdates.end()) { auto id = i.value(); if (id == Auth().userPeerId()) { MTP::send(MTPphotos_UploadProfilePhoto(file), rpcDone(&Messenger::selfPhotoDone), rpcFail(&Messenger::peerPhotoFailed, id)); } else if (peerIsChat(id)) { auto history = App::history(id); history->sendRequestId = MTP::send(MTPmessages_EditChatPhoto(history->peer->asChat()->inputChat, MTP_inputChatUploadedPhoto(file)), rpcDone(&Messenger::chatPhotoDone, id), rpcFail(&Messenger::peerPhotoFailed, id), 0, 0, history->sendRequestId); } else if (peerIsChannel(id)) { auto history = App::history(id); history->sendRequestId = MTP::send(MTPchannels_EditPhoto(history->peer->asChannel()->inputChannel, MTP_inputChatUploadedPhoto(file)), rpcDone(&Messenger::chatPhotoDone, id), rpcFail(&Messenger::peerPhotoFailed, id), 0, 0, history->sendRequestId); } } } void Messenger::onSwitchDebugMode() { if (cDebug()) { QFile(cWorkingDir() + qsl("tdata/withdebug")).remove(); cSetDebug(false); App::restart(); } else { cSetDebug(true); DEBUG_LOG(("Debug logs started.")); QFile f(cWorkingDir() + qsl("tdata/withdebug")); if (f.open(QIODevice::WriteOnly)) { f.write("1"); f.close(); } Ui::hideLayer(); } } void Messenger::onSwitchWorkMode() { Global::SetDialogsModeEnabled(!Global::DialogsModeEnabled()); Global::SetDialogsMode(Dialogs::Mode::All); Local::writeUserSettings(); App::restart(); } void Messenger::onSwitchTestMode() { if (cTestMode()) { QFile(cWorkingDir() + qsl("tdata/withtestmode")).remove(); cSetTestMode(false); } else { QFile f(cWorkingDir() + qsl("tdata/withtestmode")); if (f.open(QIODevice::WriteOnly)) { f.write("1"); f.close(); } cSetTestMode(true); } App::restart(); } void Messenger::authSessionCreate(UserId userId) { Expects(_mtproto != nullptr); _authSession = std::make_unique<AuthSession>(userId); authSessionChanged().notify(true); } void Messenger::authSessionDestroy() { _authSession.reset(); _private->storedAuthSession.reset(); _private->authSessionUserId = 0; authSessionChanged().notify(true); loggedOut(); } void Messenger::setInternalLinkDomain(const QString &domain) const { // This domain should start with 'http[s]://' and end with '/', like 'https://t.me/'. auto validate = [](auto &domain) { auto prefixes = { qstr("https://"), qstr("http://"), }; for (auto &prefix : prefixes) { if (domain.startsWith(prefix, Qt::CaseInsensitive)) { return domain.endsWith('/'); } } return false; }; if (validate(domain) && domain != Global::InternalLinksDomain()) { Global::SetInternalLinksDomain(domain); } } QString Messenger::createInternalLink(const QString &query) const { auto result = createInternalLinkFull(query); auto prefixes = { qstr("https://"), qstr("http://"), }; for (auto &prefix : prefixes) { if (result.startsWith(prefix, Qt::CaseInsensitive)) { return result.mid(prefix.size()); } } LOG(("Warning: bad internal url '%1'").arg(result)); return result; } QString Messenger::createInternalLinkFull(const QString &query) const { return Global::InternalLinksDomain() + query; } void Messenger::checkStartUrl() { if (!cStartUrl().isEmpty() && !App::passcoded()) { auto url = cStartUrl(); cSetStartUrl(QString()); if (!openLocalUrl(url)) { cSetStartUrl(url); } } } bool Messenger::openLocalUrl(const QString &url) { auto urlTrimmed = url.trimmed(); if (urlTrimmed.size() > 8192) urlTrimmed = urlTrimmed.mid(0, 8192); if (!urlTrimmed.startsWith(qstr("tg://"), Qt::CaseInsensitive) || App::passcoded()) { return false; } auto command = urlTrimmed.midRef(qstr("tg://").size()); using namespace qthelp; auto matchOptions = RegExOption::CaseInsensitive; if (auto joinChatMatch = regex_match(qsl("^join/?\\?invite=([a-zA-Z0-9\\.\\_\\-]+)(&|$)"), command, matchOptions)) { if (auto main = App::main()) { main->joinGroupByHash(joinChatMatch->captured(1)); return true; } } else if (auto stickerSetMatch = regex_match(qsl("^addstickers/?\\?set=([a-zA-Z0-9\\.\\_]+)(&|$)"), command, matchOptions)) { if (auto main = App::main()) { main->stickersBox(MTP_inputStickerSetShortName(MTP_string(stickerSetMatch->captured(1)))); return true; } } else if (auto shareUrlMatch = regex_match(qsl("^msg_url/?\\?(.+)(#|$)"), command, matchOptions)) { if (auto main = App::main()) { auto params = url_parse_params(shareUrlMatch->captured(1), UrlParamNameTransform::ToLower); auto url = params.value(qsl("url")); if (!url.isEmpty()) { main->shareUrlLayer(url, params.value("text")); return true; } } } else if (auto confirmPhoneMatch = regex_match(qsl("^confirmphone/?\\?(.+)(#|$)"), command, matchOptions)) { if (auto main = App::main()) { auto params = url_parse_params(confirmPhoneMatch->captured(1), UrlParamNameTransform::ToLower); auto phone = params.value(qsl("phone")); auto hash = params.value(qsl("hash")); if (!phone.isEmpty() && !hash.isEmpty()) { ConfirmPhoneBox::start(phone, hash); return true; } } } else if (auto usernameMatch = regex_match(qsl("^resolve/?\\?(.+)(#|$)"), command, matchOptions)) { if (auto main = App::main()) { auto params = url_parse_params(usernameMatch->captured(1), UrlParamNameTransform::ToLower); auto domain = params.value(qsl("domain")); if (regex_match(qsl("^[a-zA-Z0-9\\.\\_]+$"), domain, matchOptions)) { auto start = qsl("start"); auto startToken = params.value(start); if (startToken.isEmpty()) { start = qsl("startgroup"); startToken = params.value(start); if (startToken.isEmpty()) { start = QString(); } } auto post = (start == qsl("startgroup")) ? ShowAtProfileMsgId : ShowAtUnreadMsgId; auto postParam = params.value(qsl("post")); if (auto postId = postParam.toInt()) { post = postId; } auto gameParam = params.value(qsl("game")); if (!gameParam.isEmpty() && regex_match(qsl("^[a-zA-Z0-9\\.\\_]+$"), gameParam, matchOptions)) { startToken = gameParam; post = ShowAtGameShareMsgId; } main->openPeerByName(domain, post, startToken); return true; } } } else if (auto shareGameScoreMatch = regex_match(qsl("^share_game_score/?\\?(.+)(#|$)"), command, matchOptions)) { if (auto main = App::main()) { auto params = url_parse_params(shareGameScoreMatch->captured(1), UrlParamNameTransform::ToLower); ShareGameScoreByHash(params.value(qsl("hash"))); return true; } } else if (auto socksMatch = regex_match(qsl("^socks/?\\?(.+)(#|$)"), command, matchOptions)) { auto params = url_parse_params(socksMatch->captured(1), UrlParamNameTransform::ToLower); ConnectionBox::ShowApplyProxyConfirmation(params); return true; } return false; } void Messenger::uploadProfilePhoto(QImage &&tosend, const PeerId &peerId) { PreparedPhotoThumbs photoThumbs; QVector<MTPPhotoSize> photoSizes; auto thumb = App::pixmapFromImageInPlace(tosend.scaled(160, 160, Qt::KeepAspectRatio, Qt::SmoothTransformation)); photoThumbs.insert('a', thumb); photoSizes.push_back(MTP_photoSize(MTP_string("a"), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(thumb.width()), MTP_int(thumb.height()), MTP_int(0))); auto medium = App::pixmapFromImageInPlace(tosend.scaled(320, 320, Qt::KeepAspectRatio, Qt::SmoothTransformation)); photoThumbs.insert('b', medium); photoSizes.push_back(MTP_photoSize(MTP_string("b"), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(medium.width()), MTP_int(medium.height()), MTP_int(0))); auto full = QPixmap::fromImage(tosend, Qt::ColorOnly); photoThumbs.insert('c', full); photoSizes.push_back(MTP_photoSize(MTP_string("c"), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(full.width()), MTP_int(full.height()), MTP_int(0))); QByteArray jpeg; QBuffer jpegBuffer(&jpeg); full.save(&jpegBuffer, "JPG", 87); PhotoId id = rand_value<PhotoId>(); auto photo = MTP_photo(MTP_flags(0), MTP_long(id), MTP_long(0), MTP_int(unixtime()), MTP_vector<MTPPhotoSize>(photoSizes)); QString file, filename; int32 filesize = 0; QByteArray data; SendMediaReady ready(SendMediaType::Photo, file, filename, filesize, data, id, id, qsl("jpg"), peerId, photo, photoThumbs, MTP_documentEmpty(MTP_long(0)), jpeg, 0); connect(&Auth().uploader(), SIGNAL(photoReady(const FullMsgId&, bool, const MTPInputFile&)), this, SLOT(photoUpdated(const FullMsgId&, bool, const MTPInputFile&)), Qt::UniqueConnection); FullMsgId newId(peerToChannel(peerId), clientMsgId()); regPhotoUpdate(peerId, newId); Auth().uploader().uploadMedia(newId, ready); } void Messenger::setupPasscode() { _window->setupPasscode(); _passcodedChanged.notify(); } void Messenger::clearPasscode() { cSetPasscodeBadTries(0); _window->clearPasscode(); _passcodedChanged.notify(); } Messenger::~Messenger() { Expects(SingleInstance == this); _window.reset(); _mediaView.reset(); // Some MTP requests can be cancelled from data clearing. App::clearHistories(); authSessionDestroy(); // The langpack manager should be destroyed before MTProto instance, // because it is MTP::Sender and it may have pending requests. _langCloudManager.reset(); _mtproto.reset(); _mtprotoForKeysDestroy.reset(); Shortcuts::finish(); anim::stopManager(); stopWebLoadManager(); App::deinitMedia(); deinitLocationManager(); Window::Theme::Unload(); Media::Player::finish(); style::stopManager(); Local::finish(); Global::finish(); ThirdParty::finish(); SingleInstance = nullptr; } MainWindow *Messenger::getActiveWindow() const { return _window.get(); } bool Messenger::closeActiveWindow() { if (hideMediaView()) { return true; } if (auto activeWindow = getActiveWindow()) { if (!activeWindow->hideNoQuit()) { activeWindow->close(); } return true; } return false; } bool Messenger::minimizeActiveWindow() { hideMediaView(); if (auto activeWindow = getActiveWindow()) { if (Global::WorkMode().value() == dbiwmTrayOnly) { activeWindow->minimizeToTray(); } else { activeWindow->setWindowState(Qt::WindowMinimized); } return true; } return false; } QWidget *Messenger::getFileDialogParent() { return (_mediaView && _mediaView->isVisible()) ? (QWidget*)_mediaView.get() : (QWidget*)getActiveWindow(); } void Messenger::checkMediaViewActivation() { if (_mediaView && !_mediaView->isHidden()) { _mediaView->activateWindow(); Sandbox::setActiveWindow(_mediaView.get()); _mediaView->setFocus(); } } void Messenger::loggedOut() { if (_mediaView) { hideMediaView(); _mediaView->clearData(); } } QPoint Messenger::getPointForCallPanelCenter() const { if (auto activeWindow = getActiveWindow()) { Assert(activeWindow->windowHandle() != nullptr); if (activeWindow->isActive()) { return activeWindow->geometry().center(); } return activeWindow->windowHandle()->screen()->geometry().center(); } return QApplication::desktop()->screenGeometry().center(); } // macOS Qt bug workaround, sometimes no leaveEvent() gets to the nested widgets. void Messenger::registerLeaveSubscription(QWidget *widget) { #ifdef Q_OS_MAC if (auto topLevel = widget->window()) { if (topLevel == _window.get()) { auto weak = make_weak(widget); auto subscription = _window->leaveEvents( ) | rpl::start_with_next([weak] { if (const auto window = weak.data()) { QEvent ev(QEvent::Leave); QGuiApplication::sendEvent(window, &ev); } }); _leaveSubscriptions.emplace_back(weak, std::move(subscription)); } } #endif // Q_OS_MAC } void Messenger::unregisterLeaveSubscription(QWidget *widget) { #ifdef Q_OS_MAC _leaveSubscriptions = std::move( _leaveSubscriptions ) | ranges::action::remove_if([&](const LeaveSubscription &subscription) { auto pointer = subscription.pointer.data(); return !pointer || (pointer == widget); }); #endif // Q_OS_MAC } void Messenger::QuitAttempt() { auto prevents = false; if (!Sandbox::isSavingSession() && AuthSession::Exists()) { if (Auth().api().isQuitPrevent()) { prevents = true; } if (Auth().calls().isQuitPrevent()) { prevents = true; } } if (prevents) { Instance().quitDelayed(); } else { QCoreApplication::quit(); } } void Messenger::quitPreventFinished() { if (App::quitting()) { QuitAttempt(); } } void Messenger::quitDelayed() { if (!_private->quitTimer.isActive()) { _private->quitTimer.setCallback([] { QCoreApplication::quit(); }); _private->quitTimer.callOnce(kQuitPreventTimeoutMs); } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/messenger.h����������������������������������������������������0000664�0000000�0000000�00000015057�13262451251�0021574�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "base/observer.h" #include "mtproto/auth_key.h" #include "base/timer.h" class AuthSession; class AuthSessionSettings; class MainWidget; class FileUploader; class Translator; class MediaView; namespace Core { class Launcher; } // namespace Core namespace App { void quit(); } // namespace App namespace MTP { class DcOptions; class Instance; class AuthKey; using AuthKeyPtr = std::shared_ptr<AuthKey>; using AuthKeysList = std::vector<AuthKeyPtr>; } // namespace MTP namespace Media { namespace Audio { class Instance; } // namespace Audio } // namespace Media namespace Lang { class Instance; class Translator; class CloudManager; } // namespace Lang class Messenger final : public QObject, public RPCSender, private base::Subscriber { Q_OBJECT public: Messenger(not_null<Core::Launcher*> launcher); Messenger(const Messenger &other) = delete; Messenger &operator=(const Messenger &other) = delete; ~Messenger(); not_null<Core::Launcher*> launcher() const { return _launcher; } // Windows interface. MainWindow *getActiveWindow() const; bool closeActiveWindow(); bool minimizeActiveWindow(); QWidget *getFileDialogParent(); QWidget *getGlobalShortcutParent() { return &_globalShortcutParent; } // MediaView interface. void checkMediaViewActivation(); bool hideMediaView(); void showPhoto(not_null<const PhotoOpenClickHandler*> link); void showPhoto(not_null<PhotoData*> photo, HistoryItem *item); void showPhoto(not_null<PhotoData*> photo, not_null<PeerData*> item); void showDocument(not_null<DocumentData*> document, HistoryItem *item); PeerData *ui_getPeerForMouseAction(); QPoint getPointForCallPanelCenter() const; QImage logo() const { return _logo; } QImage logoNoMargin() const { return _logoNoMargin; } static Messenger *InstancePointer(); static Messenger &Instance() { auto result = InstancePointer(); Assert(result != nullptr); return *result; } // MTProto components. MTP::DcOptions *dcOptions() { return _dcOptions.get(); } // Set from legacy storage. void setMtpMainDcId(MTP::DcId mainDcId); void setMtpKey(MTP::DcId dcId, const MTP::AuthKey::Data &keyData); void setAuthSessionUserId(UserId userId); void setAuthSessionFromStorage( std::unique_ptr<AuthSessionSettings> data); AuthSessionSettings *getAuthSessionSettings(); // Serialization. QByteArray serializeMtpAuthorization() const; void setMtpAuthorization(const QByteArray &serialized); void startMtp(); MTP::Instance *mtp() { return _mtproto.get(); } void suggestMainDcId(MTP::DcId mainDcId); void destroyStaleAuthorizationKeys(); // AuthSession component. AuthSession *authSession() { return _authSession.get(); } Lang::Instance &langpack() { return *_langpack; } Lang::CloudManager *langCloudManager() { return _langCloudManager.get(); } void authSessionCreate(UserId userId); void authSessionDestroy(); base::Observable<void> &authSessionChanged() { return _authSessionChanged; } // Media component. Media::Audio::Instance &audio() { return *_audio; } // Internal links. void setInternalLinkDomain(const QString &domain) const; QString createInternalLink(const QString &query) const; QString createInternalLinkFull(const QString &query) const; void checkStartUrl(); bool openLocalUrl(const QString &url); void uploadProfilePhoto(QImage &&tosend, const PeerId &peerId); void regPhotoUpdate(const PeerId &peer, const FullMsgId &msgId); bool isPhotoUpdating(const PeerId &peer); void cancelPhotoUpdate(const PeerId &peer); void selfPhotoCleared(const MTPUserProfilePhoto &result); void chatPhotoCleared(PeerId peer, const MTPUpdates &updates); void selfPhotoDone(const MTPphotos_Photo &result); void chatPhotoDone(PeerId peerId, const MTPUpdates &updates); bool peerPhotoFailed(PeerId peerId, const RPCError &e); void peerClearPhoto(PeerId peer); void writeUserConfigIn(TimeMs ms); void killDownloadSessionsStart(MTP::DcId dcId); void killDownloadSessionsStop(MTP::DcId dcId); void checkLocalTime(); void setupPasscode(); void clearPasscode(); base::Observable<void> &passcodedChanged() { return _passcodedChanged; } void registerLeaveSubscription(QWidget *widget); void unregisterLeaveSubscription(QWidget *widget); void quitPreventFinished(); void handleAppActivated(); void handleAppDeactivated(); void call_handleUnreadCounterUpdate(); void call_handleDelayedPeerUpdates(); void call_handleObservables(); void callDelayed(int duration, base::lambda_once<void()> &&lambda) { _callDelayedTimer.call(duration, std::move(lambda)); } protected: bool eventFilter(QObject *object, QEvent *event) override; signals: void peerPhotoDone(PeerId peer); void peerPhotoFail(PeerId peer); public slots: void onAllKeysDestroyed(); void photoUpdated(const FullMsgId &msgId, bool silent, const MTPInputFile &file); void onSwitchDebugMode(); void onSwitchWorkMode(); void onSwitchTestMode(); void killDownloadSessions(); void onAppStateChanged(Qt::ApplicationState state); private: void destroyMtpKeys(MTP::AuthKeysList &&keys); void startLocalStorage(); friend void App::quit(); static void QuitAttempt(); void quitDelayed(); void loggedOut(); not_null<Core::Launcher*> _launcher; QMap<FullMsgId, PeerId> photoUpdates; QMap<MTP::DcId, TimeMs> killDownloadSessionTimes; SingleTimer killDownloadSessionsTimer; // Some fields are just moved from the declaration. struct Private; const std::unique_ptr<Private> _private; QWidget _globalShortcutParent; std::unique_ptr<MainWindow> _window; std::unique_ptr<MediaView> _mediaView; std::unique_ptr<Lang::Instance> _langpack; std::unique_ptr<Lang::CloudManager> _langCloudManager; std::unique_ptr<Lang::Translator> _translator; std::unique_ptr<MTP::DcOptions> _dcOptions; std::unique_ptr<MTP::Instance> _mtproto; std::unique_ptr<MTP::Instance> _mtprotoForKeysDestroy; std::unique_ptr<AuthSession> _authSession; base::Observable<void> _authSessionChanged; base::Observable<void> _passcodedChanged; std::unique_ptr<Media::Audio::Instance> _audio; QImage _logo; QImage _logoNoMargin; base::DelayedCallTimer _callDelayedTimer; struct LeaveSubscription { LeaveSubscription( QPointer<QWidget> pointer, rpl::lifetime &&subscription) : pointer(pointer), subscription(std::move(subscription)) { } QPointer<QWidget> pointer; rpl::lifetime subscription; }; std::vector<LeaveSubscription> _leaveSubscriptions; }; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/mtproto/�������������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0021127�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/mtproto/auth_key.cpp�������������������������������������������0000664�0000000�0000000�00000006545�13262451251�0023456�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "mtproto/auth_key.h" #include <openssl/aes.h> extern "C" { #include <openssl/modes.h> } namespace MTP { void AuthKey::prepareAES_oldmtp(const MTPint128 &msgKey, MTPint256 &aesKey, MTPint256 &aesIV, bool send) const { uint32 x = send ? 0 : 8; uchar data_a[16 + 32], sha1_a[20]; memcpy(data_a, &msgKey, 16); memcpy(data_a + 16, _key.data() + x, 32); hashSha1(data_a, 16 + 32, sha1_a); uchar data_b[16 + 16 + 16], sha1_b[20]; memcpy(data_b, _key.data() + 32 + x, 16); memcpy(data_b + 16, &msgKey, 16); memcpy(data_b + 32, _key.data() + 48 + x, 16); hashSha1(data_b, 16 + 16 + 16, sha1_b); uchar data_c[32 + 16], sha1_c[20]; memcpy(data_c, _key.data() + 64 + x, 32); memcpy(data_c + 32, &msgKey, 16); hashSha1(data_c, 32 + 16, sha1_c); uchar data_d[16 + 32], sha1_d[20]; memcpy(data_d, &msgKey, 16); memcpy(data_d + 16, _key.data() + 96 + x, 32); hashSha1(data_d, 16 + 32, sha1_d); auto key = reinterpret_cast<uchar*>(&aesKey); auto iv = reinterpret_cast<uchar*>(&aesIV); memcpy(key, sha1_a, 8); memcpy(key + 8, sha1_b + 8, 12); memcpy(key + 8 + 12, sha1_c + 4, 12); memcpy(iv, sha1_a + 8, 12); memcpy(iv + 12, sha1_b, 8); memcpy(iv + 12 + 8, sha1_c + 16, 4); memcpy(iv + 12 + 8 + 4, sha1_d, 8); } void AuthKey::prepareAES(const MTPint128 &msgKey, MTPint256 &aesKey, MTPint256 &aesIV, bool send) const { uint32 x = send ? 0 : 8; uchar data_a[16 + 36], sha256_a[32]; memcpy(data_a, &msgKey, 16); memcpy(data_a + 16, _key.data() + x, 36); hashSha256(data_a, 16 + 36, sha256_a); uchar data_b[36 + 16], sha256_b[32]; memcpy(data_b, _key.data() + 40 + x, 36); memcpy(data_b + 36, &msgKey, 16); hashSha256(data_b, 36 + 16, sha256_b); auto key = reinterpret_cast<uchar*>(&aesKey); auto iv = reinterpret_cast<uchar*>(&aesIV); memcpy(key, sha256_a, 8); memcpy(key + 8, sha256_b + 8, 16); memcpy(key + 8 + 16, sha256_a + 24, 8); memcpy(iv, sha256_b, 8); memcpy(iv + 8, sha256_a + 8, 16); memcpy(iv + 8 + 16, sha256_b + 24, 8); } void aesIgeEncryptRaw(const void *src, void *dst, uint32 len, const void *key, const void *iv) { uchar aes_key[32], aes_iv[32]; memcpy(aes_key, key, 32); memcpy(aes_iv, iv, 32); AES_KEY aes; AES_set_encrypt_key(aes_key, 256, &aes); AES_ige_encrypt(static_cast<const uchar*>(src), static_cast<uchar*>(dst), len, &aes, aes_iv, AES_ENCRYPT); } void aesIgeDecryptRaw(const void *src, void *dst, uint32 len, const void *key, const void *iv) { uchar aes_key[32], aes_iv[32]; memcpy(aes_key, key, 32); memcpy(aes_iv, iv, 32); AES_KEY aes; AES_set_decrypt_key(aes_key, 256, &aes); AES_ige_encrypt(static_cast<const uchar*>(src), static_cast<uchar*>(dst), len, &aes, aes_iv, AES_DECRYPT); } void aesCtrEncrypt(void *data, uint32 len, const void *key, CTRState *state) { AES_KEY aes; AES_set_encrypt_key(static_cast<const uchar*>(key), 256, &aes); static_assert(CTRState::IvecSize == AES_BLOCK_SIZE, "Wrong size of ctr ivec!"); static_assert(CTRState::EcountSize == AES_BLOCK_SIZE, "Wrong size of ctr ecount!"); CRYPTO_ctr128_encrypt(static_cast<const uchar*>(data), static_cast<uchar*>(data), len, &aes, state->ivec, state->ecount, &state->num, (block128_f) AES_encrypt); } } // namespace MTP �����������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/mtproto/auth_key.h���������������������������������������������0000664�0000000�0000000�00000011372�13262451251�0023115�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <array> #include <memory> namespace MTP { class AuthKey { public: static constexpr auto kSize = 256; // 2048 bits. using Data = std::array<gsl::byte, kSize>; using KeyId = uint64; enum class Type { Generated, ReadFromFile, Local, }; AuthKey(Type type, DcId dcId, const Data &data) : _type(type), _dcId(dcId), _key(data) { countKeyId(); } AuthKey(const Data &data) : _type(Type::Local), _key(data) { countKeyId(); } AuthKey(const AuthKey &other) = delete; AuthKey &operator=(const AuthKey &other) = delete; Type type() const { return _type; } int dcId() const { return _dcId; } KeyId keyId() const { return _keyId; } void prepareAES_oldmtp(const MTPint128 &msgKey, MTPint256 &aesKey, MTPint256 &aesIV, bool send) const; void prepareAES(const MTPint128 &msgKey, MTPint256 &aesKey, MTPint256 &aesIV, bool send) const; const void *partForMsgKey(bool send) const { return _key.data() + 88 + (send ? 0 : 8); } void write(QDataStream &to) const { to.writeRawData(reinterpret_cast<const char*>(_key.data()), _key.size()); } bool equals(const std::shared_ptr<AuthKey> &other) const { return other ? (_key == other->_key) : false; } static void FillData(Data &authKey, base::const_byte_span computedAuthKey) { auto computedAuthKeySize = computedAuthKey.size(); Assert(computedAuthKeySize <= kSize); auto authKeyBytes = gsl::make_span(authKey); if (computedAuthKeySize < kSize) { base::set_bytes(authKeyBytes.subspan(0, kSize - computedAuthKeySize), gsl::byte()); base::copy_bytes(authKeyBytes.subspan(kSize - computedAuthKeySize), computedAuthKey); } else { base::copy_bytes(authKeyBytes, computedAuthKey); } } private: void countKeyId() { auto sha1 = hashSha1(_key.data(), _key.size()); // Lower 64 bits = 8 bytes of 20 byte SHA1 hash. _keyId = *reinterpret_cast<KeyId*>(sha1.data() + 12); } Type _type = Type::Generated; DcId _dcId = 0; Data _key = { { gsl::byte{} } }; KeyId _keyId = 0; }; using AuthKeyPtr = std::shared_ptr<AuthKey>; using AuthKeysList = std::vector<AuthKeyPtr>; void aesIgeEncryptRaw(const void *src, void *dst, uint32 len, const void *key, const void *iv); void aesIgeDecryptRaw(const void *src, void *dst, uint32 len, const void *key, const void *iv); inline void aesIgeEncrypt_oldmtp(const void *src, void *dst, uint32 len, const AuthKeyPtr &authKey, const MTPint128 &msgKey) { MTPint256 aesKey, aesIV; authKey->prepareAES_oldmtp(msgKey, aesKey, aesIV, true); return aesIgeEncryptRaw(src, dst, len, static_cast<const void*>(&aesKey), static_cast<const void*>(&aesIV)); } inline void aesIgeEncrypt(const void *src, void *dst, uint32 len, const AuthKeyPtr &authKey, const MTPint128 &msgKey) { MTPint256 aesKey, aesIV; authKey->prepareAES(msgKey, aesKey, aesIV, true); return aesIgeEncryptRaw(src, dst, len, static_cast<const void*>(&aesKey), static_cast<const void*>(&aesIV)); } inline void aesEncryptLocal(const void *src, void *dst, uint32 len, const AuthKeyPtr &authKey, const void *key128) { MTPint256 aesKey, aesIV; authKey->prepareAES_oldmtp(*(const MTPint128*)key128, aesKey, aesIV, false); return aesIgeEncryptRaw(src, dst, len, static_cast<const void*>(&aesKey), static_cast<const void*>(&aesIV)); } inline void aesIgeDecrypt_oldmtp(const void *src, void *dst, uint32 len, const AuthKeyPtr &authKey, const MTPint128 &msgKey) { MTPint256 aesKey, aesIV; authKey->prepareAES_oldmtp(msgKey, aesKey, aesIV, false); return aesIgeDecryptRaw(src, dst, len, static_cast<const void*>(&aesKey), static_cast<const void*>(&aesIV)); } inline void aesIgeDecrypt(const void *src, void *dst, uint32 len, const AuthKeyPtr &authKey, const MTPint128 &msgKey) { MTPint256 aesKey, aesIV; authKey->prepareAES(msgKey, aesKey, aesIV, false); return aesIgeDecryptRaw(src, dst, len, static_cast<const void*>(&aesKey), static_cast<const void*>(&aesIV)); } inline void aesDecryptLocal(const void *src, void *dst, uint32 len, const AuthKeyPtr &authKey, const void *key128) { MTPint256 aesKey, aesIV; authKey->prepareAES_oldmtp(*(const MTPint128*)key128, aesKey, aesIV, false); return aesIgeDecryptRaw(src, dst, len, static_cast<const void*>(&aesKey), static_cast<const void*>(&aesIV)); } // ctr used inplace, encrypt the data and leave it at the same place struct CTRState { static constexpr int KeySize = 32; static constexpr int IvecSize = 16; static constexpr int EcountSize = 16; uchar ivec[IvecSize] = { 0 }; uint32 num = 0; uchar ecount[EcountSize] = { 0 }; }; void aesCtrEncrypt(void *data, uint32 len, const void *key, CTRState *state); } // namespace MTP ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/mtproto/config_loader.cpp��������������������������������������0000664�0000000�0000000�00000011453�13262451251�0024432�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "mtproto/config_loader.h" #include "mtproto/dc_options.h" #include "mtproto/mtp_instance.h" #include "mtproto/special_config_request.h" namespace MTP { namespace internal { namespace { constexpr auto kEnumerateDcTimeout = 8000; // 8 seconds timeout for help_getConfig to work (then move to other dc) constexpr auto kSpecialRequestTimeoutMs = 6000; // 4 seconds timeout for it to work in a specially requested dc. } // namespace ConfigLoader::ConfigLoader( not_null<Instance*> instance, RPCDoneHandlerPtr onDone, RPCFailHandlerPtr onFail) : _instance(instance) , _doneHandler(onDone) , _failHandler(onFail) { _enumDCTimer.setCallback([this] { enumerate(); }); _specialEnumTimer.setCallback([this] { sendSpecialRequest(); }); } void ConfigLoader::load() { if (!_instance->isKeysDestroyer()) { sendRequest(_instance->mainDcId()); _enumDCTimer.callOnce(kEnumerateDcTimeout); } else { auto ids = _instance->dcOptions()->configEnumDcIds(); Assert(!ids.empty()); _enumCurrent = ids.front(); enumerate(); } } mtpRequestId ConfigLoader::sendRequest(ShiftedDcId shiftedDcId) { return _instance->send( MTPhelp_GetConfig(), base::duplicate(_doneHandler), base::duplicate(_failHandler), shiftedDcId); } DcId ConfigLoader::specialToRealDcId(DcId specialDcId) { return Instance::Config::kTemporaryMainDc + specialDcId; } void ConfigLoader::terminateRequest() { if (_enumRequest) { _instance->cancel(base::take(_enumRequest)); } if (_enumCurrent) { _instance->killSession(MTP::configDcId(_enumCurrent)); } } void ConfigLoader::terminateSpecialRequest() { if (_specialEnumRequest) { _instance->cancel(base::take(_specialEnumRequest)); } if (_specialEnumCurrent) { _instance->killSession(_specialEnumCurrent); } } ConfigLoader::~ConfigLoader() { terminateRequest(); terminateSpecialRequest(); } void ConfigLoader::enumerate() { terminateRequest(); if (!_enumCurrent) { _enumCurrent = _instance->mainDcId(); } auto ids = _instance->dcOptions()->configEnumDcIds(); Assert(!ids.empty()); auto i = std::find(ids.cbegin(), ids.cend(), _enumCurrent); if (i == ids.cend() || (++i) == ids.cend()) { _enumCurrent = ids.front(); } else { _enumCurrent = *i; } _enumRequest = sendRequest(MTP::configDcId(_enumCurrent)); _enumDCTimer.callOnce(kEnumerateDcTimeout); createSpecialLoader(); } void ConfigLoader::createSpecialLoader() { if (Global::ConnectionType() != dbictAuto) { _specialLoader.reset(); return; } if (!_specialLoader || (!_specialEnumRequest && _specialEndpoints.empty())) { _specialLoader = std::make_unique<SpecialConfigRequest>([this](DcId dcId, const std::string &ip, int port) { addSpecialEndpoint(dcId, ip, port); }); _triedSpecialEndpoints.clear(); } } void ConfigLoader::addSpecialEndpoint(DcId dcId, const std::string &ip, int port) { auto endpoint = SpecialEndpoint { dcId, ip, port }; if (base::contains(_specialEndpoints, endpoint) || base::contains(_triedSpecialEndpoints, endpoint)) { return; } DEBUG_LOG(("MTP Info: Special endpoint received, '%1:%2'").arg(ip.c_str()).arg(port)); _specialEndpoints.push_back(endpoint); if (!_specialEnumTimer.isActive()) { _specialEnumTimer.callOnce(1); } } void ConfigLoader::sendSpecialRequest() { terminateSpecialRequest(); if (Global::ConnectionType() != dbictAuto) { _specialLoader.reset(); return; } if (_specialEndpoints.empty()) { createSpecialLoader(); return; } const auto weak = base::make_weak(this); const auto index = rand_value<uint32>() % _specialEndpoints.size(); const auto endpoint = _specialEndpoints.begin() + index; _specialEnumCurrent = specialToRealDcId(endpoint->dcId); _instance->dcOptions()->constructAddOne( _specialEnumCurrent, MTPDdcOption::Flag::f_tcpo_only, endpoint->ip, endpoint->port); _specialEnumRequest = _instance->send( MTPhelp_GetConfig(), rpcDone([weak](const MTPConfig &result) { if (const auto strong = weak.get()) { strong->specialConfigLoaded(result); } }), base::duplicate(_failHandler), _specialEnumCurrent); _triedSpecialEndpoints.push_back(*endpoint); _specialEndpoints.erase(endpoint); _specialEnumTimer.callOnce(kSpecialRequestTimeoutMs); } void ConfigLoader::specialConfigLoaded(const MTPConfig &result) { Expects(result.type() == mtpc_config); auto &data = result.c_config(); if (data.vdc_options.v.empty()) { LOG(("MTP Error: config with empty dc_options received!")); return; } // We use special config only for dc options. // For everything else we wait for normal config from main dc. _instance->dcOptions()->setFromList(data.vdc_options); } } // namespace internal } // namespace MTP ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/mtproto/config_loader.h����������������������������������������0000664�0000000�0000000�00000003375�13262451251�0024103�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "base/timer.h" #include "base/weak_ptr.h" #include "mtproto/rpc_sender.h" namespace MTP { class SpecialConfigRequest; class Instance; namespace internal { class ConfigLoader : public base::has_weak_ptr { public: ConfigLoader(not_null<Instance*> instance, RPCDoneHandlerPtr onDone, RPCFailHandlerPtr onFail); ~ConfigLoader(); void load(); private: mtpRequestId sendRequest(ShiftedDcId shiftedDcId); void addSpecialEndpoint(DcId dcId, const std::string &ip, int port); void sendSpecialRequest(); void enumerate(); void createSpecialLoader(); DcId specialToRealDcId(DcId specialDcId); void specialConfigLoaded(const MTPConfig &result); void terminateRequest(); void terminateSpecialRequest(); not_null<Instance*> _instance; base::Timer _enumDCTimer; DcId _enumCurrent = 0; mtpRequestId _enumRequest = 0; struct SpecialEndpoint { DcId dcId; std::string ip; int port; }; friend bool operator==(const SpecialEndpoint &a, const SpecialEndpoint &b); std::unique_ptr<SpecialConfigRequest> _specialLoader; std::vector<SpecialEndpoint> _specialEndpoints; std::vector<SpecialEndpoint> _triedSpecialEndpoints; base::Timer _specialEnumTimer; DcId _specialEnumCurrent = 0; mtpRequestId _specialEnumRequest = 0; RPCDoneHandlerPtr _doneHandler; RPCFailHandlerPtr _failHandler; }; inline bool operator==(const ConfigLoader::SpecialEndpoint &a, const ConfigLoader::SpecialEndpoint &b) { return (a.dcId == b.dcId) && (a.ip == b.ip) && (a.port == b.port); } } // namespace internal } // namespace MTP �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/mtproto/connection.cpp�����������������������������������������0000664�0000000�0000000�00000331313�13262451251�0023776�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "mtproto/connection.h" #include "mtproto/session.h" #include "mtproto/rsa_public_key.h" #include "mtproto/rpc_sender.h" #include "mtproto/dc_options.h" #include "mtproto/connection_abstract.h" #include "zlib.h" #include "lang/lang_keys.h" #include "base/openssl_help.h" #include <openssl/bn.h> #include <openssl/err.h> #include <openssl/aes.h> #include <openssl/sha.h> #include <openssl/md5.h> #include <openssl/rand.h> namespace MTP { namespace internal { namespace { constexpr auto kRecreateKeyId = AuthKey::KeyId(0xFFFFFFFFFFFFFFFFULL); constexpr auto kIntSize = static_cast<int>(sizeof(mtpPrime)); constexpr auto kMaxModExpSize = 256; // Don't try to handle messages larger than this size. constexpr auto kMaxMessageLength = 16 * 1024 * 1024; QString LogIdsVector(const QVector<MTPlong> &ids) { if (!ids.size()) return "[]"; auto idsStr = QString("[%1").arg(ids.cbegin()->v); for (const auto &id : ids) { idsStr += QString(", %2").arg(id.v); } return idsStr + "]"; } bool IsGoodModExpFirst(const openssl::BigNum &modexp, const openssl::BigNum &prime) { auto diff = prime - modexp; if (modexp.failed() || prime.failed() || diff.failed()) { return false; } constexpr auto kMinDiffBitsCount = 2048 - 64; if (diff.isNegative() || diff.bitsSize() < kMinDiffBitsCount || modexp.bitsSize() < kMinDiffBitsCount) { return false; } Assert(modexp.bytesSize() <= kMaxModExpSize); return true; } bool IsPrimeAndGoodCheck(const openssl::BigNum &prime, int g) { constexpr auto kGoodPrimeBitsCount = 2048; if (prime.failed() || prime.isNegative() || prime.bitsSize() != kGoodPrimeBitsCount) { LOG(("MTP Error: Bad prime bits count %1, expected %2.").arg(prime.bitsSize()).arg(kGoodPrimeBitsCount)); return false; } openssl::Context context; if (!prime.isPrime(context)) { LOG(("MTP Error: Bad prime.")); return false; } switch (g) { case 2: { auto mod8 = prime.modWord(8); if (mod8 != 7) { LOG(("BigNum PT Error: bad g value: %1, mod8: %2").arg(g).arg(mod8)); return false; } } break; case 3: { auto mod3 = prime.modWord(3); if (mod3 != 2) { LOG(("BigNum PT Error: bad g value: %1, mod3: %2").arg(g).arg(mod3)); return false; } } break; case 4: break; case 5: { auto mod5 = prime.modWord(5); if (mod5 != 1 && mod5 != 4) { LOG(("BigNum PT Error: bad g value: %1, mod5: %2").arg(g).arg(mod5)); return false; } } break; case 6: { auto mod24 = prime.modWord(24); if (mod24 != 19 && mod24 != 23) { LOG(("BigNum PT Error: bad g value: %1, mod24: %2").arg(g).arg(mod24)); return false; } } break; case 7: { auto mod7 = prime.modWord(7); if (mod7 != 3 && mod7 != 5 && mod7 != 6) { LOG(("BigNum PT Error: bad g value: %1, mod7: %2").arg(g).arg(mod7)); return false; } } break; default: { LOG(("BigNum PT Error: bad g value: %1").arg(g)); return false; } break; } auto primeSubOneDivTwo = prime; primeSubOneDivTwo.setSubWord(1); primeSubOneDivTwo.setDivWord(2); if (!primeSubOneDivTwo.isPrime(context)) { LOG(("MTP Error: Bad (prime - 1) / 2.")); return false; } return true; } bool IsPrimeAndGood(base::const_byte_span primeBytes, int g) { static constexpr unsigned char GoodPrime[] = { 0xC7, 0x1C, 0xAE, 0xB9, 0xC6, 0xB1, 0xC9, 0x04, 0x8E, 0x6C, 0x52, 0x2F, 0x70, 0xF1, 0x3F, 0x73, 0x98, 0x0D, 0x40, 0x23, 0x8E, 0x3E, 0x21, 0xC1, 0x49, 0x34, 0xD0, 0x37, 0x56, 0x3D, 0x93, 0x0F, 0x48, 0x19, 0x8A, 0x0A, 0xA7, 0xC1, 0x40, 0x58, 0x22, 0x94, 0x93, 0xD2, 0x25, 0x30, 0xF4, 0xDB, 0xFA, 0x33, 0x6F, 0x6E, 0x0A, 0xC9, 0x25, 0x13, 0x95, 0x43, 0xAE, 0xD4, 0x4C, 0xCE, 0x7C, 0x37, 0x20, 0xFD, 0x51, 0xF6, 0x94, 0x58, 0x70, 0x5A, 0xC6, 0x8C, 0xD4, 0xFE, 0x6B, 0x6B, 0x13, 0xAB, 0xDC, 0x97, 0x46, 0x51, 0x29, 0x69, 0x32, 0x84, 0x54, 0xF1, 0x8F, 0xAF, 0x8C, 0x59, 0x5F, 0x64, 0x24, 0x77, 0xFE, 0x96, 0xBB, 0x2A, 0x94, 0x1D, 0x5B, 0xCD, 0x1D, 0x4A, 0xC8, 0xCC, 0x49, 0x88, 0x07, 0x08, 0xFA, 0x9B, 0x37, 0x8E, 0x3C, 0x4F, 0x3A, 0x90, 0x60, 0xBE, 0xE6, 0x7C, 0xF9, 0xA4, 0xA4, 0xA6, 0x95, 0x81, 0x10, 0x51, 0x90, 0x7E, 0x16, 0x27, 0x53, 0xB5, 0x6B, 0x0F, 0x6B, 0x41, 0x0D, 0xBA, 0x74, 0xD8, 0xA8, 0x4B, 0x2A, 0x14, 0xB3, 0x14, 0x4E, 0x0E, 0xF1, 0x28, 0x47, 0x54, 0xFD, 0x17, 0xED, 0x95, 0x0D, 0x59, 0x65, 0xB4, 0xB9, 0xDD, 0x46, 0x58, 0x2D, 0xB1, 0x17, 0x8D, 0x16, 0x9C, 0x6B, 0xC4, 0x65, 0xB0, 0xD6, 0xFF, 0x9C, 0xA3, 0x92, 0x8F, 0xEF, 0x5B, 0x9A, 0xE4, 0xE4, 0x18, 0xFC, 0x15, 0xE8, 0x3E, 0xBE, 0xA0, 0xF8, 0x7F, 0xA9, 0xFF, 0x5E, 0xED, 0x70, 0x05, 0x0D, 0xED, 0x28, 0x49, 0xF4, 0x7B, 0xF9, 0x59, 0xD9, 0x56, 0x85, 0x0C, 0xE9, 0x29, 0x85, 0x1F, 0x0D, 0x81, 0x15, 0xF6, 0x35, 0xB1, 0x05, 0xEE, 0x2E, 0x4E, 0x15, 0xD0, 0x4B, 0x24, 0x54, 0xBF, 0x6F, 0x4F, 0xAD, 0xF0, 0x34, 0xB1, 0x04, 0x03, 0x11, 0x9C, 0xD8, 0xE3, 0xB9, 0x2F, 0xCC, 0x5B }; if (!base::compare_bytes(gsl::as_bytes(gsl::make_span(GoodPrime)), primeBytes)) { if (g == 3 || g == 4 || g == 5 || g == 7) { return true; } } return IsPrimeAndGoodCheck(openssl::BigNum(primeBytes), g); } std::vector<gsl::byte> CreateAuthKey( base::const_byte_span firstBytes, base::const_byte_span randomBytes, base::const_byte_span primeBytes) { using openssl::BigNum; BigNum first(firstBytes); BigNum prime(primeBytes); if (!IsGoodModExpFirst(first, prime)) { LOG(("AuthKey Error: Bad first prime in CreateAuthKey().")); return std::vector<gsl::byte>(); } return BigNum::ModExp(first, BigNum(randomBytes), prime).getBytes(); } ModExpFirst CreateModExp( int g, base::const_byte_span primeBytes, base::const_byte_span randomSeed) { Expects(randomSeed.size() == ModExpFirst::kRandomPowerSize); using namespace openssl; BigNum prime(primeBytes); ModExpFirst result; constexpr auto kMaxModExpFirstTries = 5; for (auto tries = 0; tries != kMaxModExpFirstTries; ++tries) { FillRandom(result.randomPower); for (auto i = 0; i != ModExpFirst::kRandomPowerSize; ++i) { result.randomPower[i] ^= randomSeed[i]; } auto modexp = BigNum::ModExp(BigNum(g), BigNum(result.randomPower), prime); if (IsGoodModExpFirst(modexp, prime)) { result.modexp = modexp.getBytes(); break; } } return result; } void wrapInvokeAfter(mtpRequest &to, const mtpRequest &from, const mtpRequestMap &haveSent, int32 skipBeforeRequest = 0) { mtpMsgId afterId(*(mtpMsgId*)(from->after->data() + 4)); mtpRequestMap::const_iterator i = afterId ? haveSent.constFind(afterId) : haveSent.cend(); int32 size = to->size(), lenInInts = (from.innerLength() >> 2), headlen = 4, fulllen = headlen + lenInInts; if (i == haveSent.constEnd()) { // no invoke after or such msg was not sent or was completed recently to->resize(size + fulllen + skipBeforeRequest); if (skipBeforeRequest) { memcpy(to->data() + size, from->constData() + 4, headlen * sizeof(mtpPrime)); memcpy(to->data() + size + headlen + skipBeforeRequest, from->constData() + 4 + headlen, lenInInts * sizeof(mtpPrime)); } else { memcpy(to->data() + size, from->constData() + 4, fulllen * sizeof(mtpPrime)); } } else { to->resize(size + fulllen + skipBeforeRequest + 3); memcpy(to->data() + size, from->constData() + 4, headlen * sizeof(mtpPrime)); (*to)[size + 3] += 3 * sizeof(mtpPrime); *((mtpTypeId*)&((*to)[size + headlen + skipBeforeRequest])) = mtpc_invokeAfterMsg; memcpy(to->data() + size + headlen + skipBeforeRequest + 1, &afterId, 2 * sizeof(mtpPrime)); memcpy(to->data() + size + headlen + skipBeforeRequest + 3, from->constData() + 4 + headlen, lenInInts * sizeof(mtpPrime)); if (size + 3 != 7) (*to)[7] += 3 * sizeof(mtpPrime); } } bool parsePQ(const QByteArray &pqStr, QByteArray &pStr, QByteArray &qStr) { if (pqStr.length() > 8) return false; // more than 64 bit pq uint64 pq = 0, p, q; const uchar *pqChars = (const uchar*)pqStr.constData(); for (uint32 i = 0, l = pqStr.length(); i < l; ++i) { pq <<= 8; pq |= (uint64)pqChars[i]; } uint64 pqSqrt = (uint64)sqrtl((long double)pq), ySqr, y; while (pqSqrt * pqSqrt > pq) --pqSqrt; while (pqSqrt * pqSqrt < pq) ++pqSqrt; for (ySqr = pqSqrt * pqSqrt - pq; ; ++pqSqrt, ySqr = pqSqrt * pqSqrt - pq) { y = (uint64)sqrtl((long double)ySqr); while (y * y > ySqr) --y; while (y * y < ySqr) ++y; if (!ySqr || y + pqSqrt >= pq) return false; if (y * y == ySqr) { p = pqSqrt + y; q = (pqSqrt > y) ? (pqSqrt - y) : (y - pqSqrt); break; } } if (p > q) std::swap(p, q); pStr.resize(4); uchar *pChars = (uchar*)pStr.data(); for (uint32 i = 0; i < 4; ++i) { *(pChars + 3 - i) = (uchar)(p & 0xFF); p >>= 8; } qStr.resize(4); uchar *qChars = (uchar*)qStr.data(); for (uint32 i = 0; i < 4; ++i) { *(qChars + 3 - i) = (uchar)(q & 0xFF); q >>= 8; } return true; } } // namespace Connection::Connection(Instance *instance) : _instance(instance) { } void Connection::start(SessionData *sessionData, ShiftedDcId shiftedDcId) { Expects(thread == nullptr && data == nullptr); thread = std::make_unique<Thread>(); auto newData = std::make_unique<ConnectionPrivate>(_instance, thread.get(), this, sessionData, shiftedDcId); // will be deleted in the thread::finished signal data = newData.release(); thread->start(); } void Connection::kill() { Expects(data != nullptr && thread != nullptr); data->stop(); data = nullptr; thread->quit(); } void Connection::waitTillFinish() { Expects(data == nullptr && thread != nullptr); DEBUG_LOG(("Waiting for connectionThread to finish")); thread->wait(); thread.reset(); } int32 Connection::state() const { Expects(data != nullptr && thread != nullptr); return data->getState(); } QString Connection::transport() const { Expects(data != nullptr && thread != nullptr); return data->transport(); } Connection::~Connection() { Expects(data == nullptr); if (thread) { waitTillFinish(); } } void ConnectionPrivate::createConn(bool createIPv4, bool createIPv6) { destroyConn(); if (createIPv4) { QWriteLocker lock(&stateConnMutex); _conn4 = AbstractConnection::create(_dcType, thread()); connect(_conn4, SIGNAL(error(qint32)), this, SLOT(onError4(qint32))); connect(_conn4, SIGNAL(receivedSome()), this, SLOT(onReceivedSome())); } if (createIPv6) { QWriteLocker lock(&stateConnMutex); _conn6 = AbstractConnection::create(_dcType, thread()); connect(_conn6, SIGNAL(error(qint32)), this, SLOT(onError6(qint32))); connect(_conn6, SIGNAL(receivedSome()), this, SLOT(onReceivedSome())); } firstSentAt = 0; if (oldConnection) { oldConnection = false; DEBUG_LOG(("This connection marked as not old!")); } oldConnectionTimer.start(MTPConnectionOldTimeout); } void ConnectionPrivate::destroyConn(AbstractConnection **conn) { if (conn) { AbstractConnection *toDisconnect = nullptr; { QWriteLocker lock(&stateConnMutex); if (*conn) { toDisconnect = *conn; disconnect(*conn, SIGNAL(connected()), nullptr, nullptr); disconnect(*conn, SIGNAL(disconnected()), nullptr, nullptr); disconnect(*conn, SIGNAL(error(qint32)), nullptr, nullptr); disconnect(*conn, SIGNAL(receivedData()), nullptr, nullptr); disconnect(*conn, SIGNAL(receivedSome()), nullptr, nullptr); *conn = nullptr; } } if (toDisconnect) { toDisconnect->disconnectFromServer(); toDisconnect->deleteLater(); } } else { destroyConn(&_conn4); destroyConn(&_conn6); _conn = nullptr; } } ConnectionPrivate::ConnectionPrivate(Instance *instance, QThread *thread, Connection *owner, SessionData *data, ShiftedDcId shiftedDcId) : QObject() , _instance(instance) , _state(DisconnectedState) , _shiftedDcId(shiftedDcId) , _owner(owner) , _waitForReceived(MTPMinReceiveDelay) , _waitForConnected(MTPMinConnectDelay) //, sessionDataMutex(QReadWriteLock::Recursive) , sessionData(data) { oldConnectionTimer.moveToThread(thread); _waitForConnectedTimer.moveToThread(thread); _waitForReceivedTimer.moveToThread(thread); _waitForIPv4Timer.moveToThread(thread); _pingSender.moveToThread(thread); retryTimer.moveToThread(thread); moveToThread(thread); Expects(_shiftedDcId != 0); connect(thread, &QThread::started, this, [this] { connectToServer(); }); connect(thread, &QThread::finished, this, [this] { finishAndDestroy(); }); connect(this, SIGNAL(finished(internal::Connection*)), _instance, SLOT(connectionFinished(internal::Connection*)), Qt::QueuedConnection); connect(&retryTimer, SIGNAL(timeout()), this, SLOT(retryByTimer())); connect(&_waitForConnectedTimer, SIGNAL(timeout()), this, SLOT(onWaitConnectedFailed())); connect(&_waitForReceivedTimer, SIGNAL(timeout()), this, SLOT(onWaitReceivedFailed())); connect(&_waitForIPv4Timer, SIGNAL(timeout()), this, SLOT(onWaitIPv4Failed())); connect(&oldConnectionTimer, SIGNAL(timeout()), this, SLOT(onOldConnection())); connect(&_pingSender, SIGNAL(timeout()), this, SLOT(onPingSender())); connect(sessionData->owner(), SIGNAL(authKeyCreated()), this, SLOT(updateAuthKey()), Qt::QueuedConnection); connect(sessionData->owner(), SIGNAL(needToRestart()), this, SLOT(restartNow()), Qt::QueuedConnection); connect(this, SIGNAL(needToReceive()), sessionData->owner(), SLOT(tryToReceive()), Qt::QueuedConnection); connect(this, SIGNAL(stateChanged(qint32)), sessionData->owner(), SLOT(onConnectionStateChange(qint32)), Qt::QueuedConnection); connect(sessionData->owner(), SIGNAL(needToSend()), this, SLOT(tryToSend()), Qt::QueuedConnection); connect(sessionData->owner(), SIGNAL(needToPing()), this, SLOT(onPingSendForce()), Qt::QueuedConnection); connect(this, SIGNAL(sessionResetDone()), sessionData->owner(), SLOT(onResetDone()), Qt::QueuedConnection); static bool _registered = false; if (!_registered) { _registered = true; qRegisterMetaType<QVector<quint64> >("QVector<quint64>"); } connect(this, SIGNAL(needToSendAsync()), sessionData->owner(), SLOT(needToResumeAndSend()), Qt::QueuedConnection); connect(this, SIGNAL(sendAnythingAsync(qint64)), sessionData->owner(), SLOT(sendAnything(qint64)), Qt::QueuedConnection); connect(this, SIGNAL(sendHttpWaitAsync()), sessionData->owner(), SLOT(sendAnything()), Qt::QueuedConnection); connect(this, SIGNAL(sendPongAsync(quint64,quint64)), sessionData->owner(), SLOT(sendPong(quint64,quint64)), Qt::QueuedConnection); connect(this, SIGNAL(sendMsgsStateInfoAsync(quint64, QByteArray)), sessionData->owner(), SLOT(sendMsgsStateInfo(quint64,QByteArray)), Qt::QueuedConnection); connect(this, SIGNAL(resendAsync(quint64,qint64,bool,bool)), sessionData->owner(), SLOT(resend(quint64,qint64,bool,bool)), Qt::QueuedConnection); connect(this, SIGNAL(resendManyAsync(QVector<quint64>,qint64,bool,bool)), sessionData->owner(), SLOT(resendMany(QVector<quint64>,qint64,bool,bool)), Qt::QueuedConnection); connect(this, SIGNAL(resendAllAsync()), sessionData->owner(), SLOT(resendAll())); } void ConnectionPrivate::onConfigLoaded() { connectToServer(true); } void ConnectionPrivate::onCDNConfigLoaded() { restart(); } int32 ConnectionPrivate::getShiftedDcId() const { return _shiftedDcId; } int32 ConnectionPrivate::getState() const { QReadLocker lock(&stateConnMutex); int32 result = _state; if (_state < 0) { if (retryTimer.isActive()) { result = int32(getms(true) - retryWillFinish); if (result >= 0) { result = -1; } } } return result; } QString ConnectionPrivate::transport() const { QReadLocker lock(&stateConnMutex); if ((!_conn4 && !_conn6) || (_conn4 && _conn6) || (_state < 0)) { return QString(); } QString result = (_conn4 ? _conn4 : _conn6)->transport(); if (!result.isEmpty() && Global::TryIPv6()) result += (_conn4 ? "/IPv4" : "/IPv6"); return result; } bool ConnectionPrivate::setState(int32 state, int32 ifState) { if (ifState != Connection::UpdateAlways) { QReadLocker lock(&stateConnMutex); if (_state != ifState) return false; } QWriteLocker lock(&stateConnMutex); if (_state == state) return false; _state = state; if (state < 0) { retryTimeout = -state; retryTimer.start(retryTimeout); retryWillFinish = getms(true) + retryTimeout; } emit stateChanged(state); return true; } void ConnectionPrivate::resetSession() { // recreate all msg_id and msg_seqno _needSessionReset = false; QWriteLocker locker1(sessionData->haveSentMutex()); QWriteLocker locker2(sessionData->toResendMutex()); QWriteLocker locker3(sessionData->toSendMutex()); QWriteLocker locker4(sessionData->wereAckedMutex()); mtpRequestMap &haveSent(sessionData->haveSentMap()); mtpRequestIdsMap &toResend(sessionData->toResendMap()); mtpPreRequestMap &toSend(sessionData->toSendMap()); mtpRequestIdsMap &wereAcked(sessionData->wereAckedMap()); mtpMsgId newId = msgid(); mtpRequestMap setSeqNumbers; typedef QMap<mtpMsgId, mtpMsgId> Replaces; Replaces replaces; for (mtpRequestMap::const_iterator i = haveSent.cbegin(), e = haveSent.cend(); i != e; ++i) { if (!mtpRequestData::isSentContainer(i.value())) { if (!*(mtpMsgId*)(i.value()->constData() + 4)) continue; mtpMsgId id = i.key(); if (id > newId) { while (true) { if (toResend.constFind(newId) == toResend.cend() && wereAcked.constFind(newId) == wereAcked.cend() && haveSent.constFind(newId) == haveSent.cend()) { break; } mtpMsgId m = msgid(); if (m <= newId) break; // wtf newId = m; } MTP_LOG(_shiftedDcId, ("Replacing msgId %1 to %2!").arg(id).arg(newId)); replaces.insert(id, newId); id = newId; *(mtpMsgId*)(i.value()->data() + 4) = id; } setSeqNumbers.insert(id, i.value()); } } for (mtpRequestIdsMap::const_iterator i = toResend.cbegin(), e = toResend.cend(); i != e; ++i) { // collect all non-container requests mtpPreRequestMap::const_iterator j = toSend.constFind(i.value()); if (j == toSend.cend()) continue; if (!mtpRequestData::isSentContainer(j.value())) { if (!*(mtpMsgId*)(j.value()->constData() + 4)) continue; mtpMsgId id = i.key(); if (id > newId) { while (true) { if (toResend.constFind(newId) == toResend.cend() && wereAcked.constFind(newId) == wereAcked.cend() && haveSent.constFind(newId) == haveSent.cend()) { break; } mtpMsgId m = msgid(); if (m <= newId) break; // wtf newId = m; } MTP_LOG(_shiftedDcId, ("Replacing msgId %1 to %2!").arg(id).arg(newId)); replaces.insert(id, newId); id = newId; *(mtpMsgId*)(j.value()->data() + 4) = id; } setSeqNumbers.insert(id, j.value()); } } uint64 session = rand_value<uint64>(); DEBUG_LOG(("MTP Info: creating new session after bad_msg_notification, setting random server_session %1").arg(session)); sessionData->setSession(session); for (mtpRequestMap::const_iterator i = setSeqNumbers.cbegin(), e = setSeqNumbers.cend(); i != e; ++i) { // generate new seq_numbers bool wasNeedAck = (*(i.value()->data() + 6) & 1); *(i.value()->data() + 6) = sessionData->nextRequestSeqNumber(wasNeedAck); } if (!replaces.isEmpty()) { for (Replaces::const_iterator i = replaces.cbegin(), e = replaces.cend(); i != e; ++i) { // replace msgIds keys in all data structs mtpRequestMap::iterator j = haveSent.find(i.key()); if (j != haveSent.cend()) { mtpRequest req = j.value(); haveSent.erase(j); haveSent.insert(i.value(), req); } mtpRequestIdsMap::iterator k = toResend.find(i.key()); if (k != toResend.cend()) { mtpRequestId req = k.value(); toResend.erase(k); toResend.insert(i.value(), req); } k = wereAcked.find(i.key()); if (k != wereAcked.cend()) { mtpRequestId req = k.value(); wereAcked.erase(k); wereAcked.insert(i.value(), req); } } for (mtpRequestMap::const_iterator i = haveSent.cbegin(), e = haveSent.cend(); i != e; ++i) { // replace msgIds in saved containers if (mtpRequestData::isSentContainer(i.value())) { mtpMsgId *ids = (mtpMsgId *)(i.value()->data() + 8); for (uint32 j = 0, l = (i.value()->size() - 8) >> 1; j < l; ++j) { Replaces::const_iterator k = replaces.constFind(ids[j]); if (k != replaces.cend()) { ids[j] = k.value(); } } } } } ackRequestData.clear(); resendRequestData.clear(); { QWriteLocker locker5(sessionData->stateRequestMutex()); sessionData->stateRequestMap().clear(); } emit sessionResetDone(); } mtpMsgId ConnectionPrivate::prepareToSend(mtpRequest &request, mtpMsgId currentLastId) { if (request->size() < 9) return 0; mtpMsgId msgId = *(mtpMsgId*)(request->constData() + 4); if (msgId) { // resending this request QWriteLocker locker(sessionData->toResendMutex()); mtpRequestIdsMap &toResend(sessionData->toResendMap()); mtpRequestIdsMap::iterator i = toResend.find(msgId); if (i != toResend.cend()) { toResend.erase(i); } } else { msgId = *(mtpMsgId*)(request->data() + 4) = currentLastId; *(request->data() + 6) = sessionData->nextRequestSeqNumber(mtpRequestData::needAck(request)); } return msgId; } mtpMsgId ConnectionPrivate::replaceMsgId(mtpRequest &request, mtpMsgId newId) { if (request->size() < 9) return 0; mtpMsgId oldMsgId = *(mtpMsgId*)(request->constData() + 4); if (oldMsgId != newId) { if (oldMsgId) { QWriteLocker locker(sessionData->toResendMutex()); // haveSentMutex() and wereAckedMutex() were locked in tryToSend() mtpRequestIdsMap &toResend(sessionData->toResendMap()); mtpRequestIdsMap &wereAcked(sessionData->wereAckedMap()); mtpRequestMap &haveSent(sessionData->haveSentMap()); while (true) { if (toResend.constFind(newId) == toResend.cend() && wereAcked.constFind(newId) == wereAcked.cend() && haveSent.constFind(newId) == haveSent.cend()) { break; } mtpMsgId m = msgid(); if (m <= newId) break; // wtf newId = m; } mtpRequestIdsMap::iterator i = toResend.find(oldMsgId); if (i != toResend.cend()) { mtpRequestId req = i.value(); toResend.erase(i); toResend.insert(newId, req); } mtpRequestIdsMap::iterator j = wereAcked.find(oldMsgId); if (j != wereAcked.cend()) { mtpRequestId req = j.value(); wereAcked.erase(j); wereAcked.insert(newId, req); } mtpRequestMap::iterator k = haveSent.find(oldMsgId); if (k != haveSent.cend()) { mtpRequest req = k.value(); haveSent.erase(k); haveSent.insert(newId, req); } for (k = haveSent.begin(); k != haveSent.cend(); ++k) { mtpRequest req(k.value()); if (mtpRequestData::isSentContainer(req)) { mtpMsgId *ids = (mtpMsgId *)(req->data() + 8); for (uint32 i = 0, l = (req->size() - 8) >> 1; i < l; ++i) { if (ids[i] == oldMsgId) { ids[i] = newId; } } } } } else { *(request->data() + 6) = sessionData->nextRequestSeqNumber(mtpRequestData::needAck(request)); } *(mtpMsgId*)(request->data() + 4) = newId; } return newId; } mtpMsgId ConnectionPrivate::placeToContainer(mtpRequest &toSendRequest, mtpMsgId &bigMsgId, mtpMsgId *&haveSentArr, mtpRequest &req) { mtpMsgId msgId = prepareToSend(req, bigMsgId); if (msgId > bigMsgId) msgId = replaceMsgId(req, bigMsgId); if (msgId >= bigMsgId) bigMsgId = msgid(); *(haveSentArr++) = msgId; uint32 from = toSendRequest->size(), len = mtpRequestData::messageSize(req); toSendRequest->resize(from + len); memcpy(toSendRequest->data() + from, req->constData() + 4, len * sizeof(mtpPrime)); return msgId; } void ConnectionPrivate::tryToSend() { QReadLocker lockFinished(&sessionDataMutex); if (!sessionData || !_conn) { return; } bool needsLayer = !sessionData->layerWasInited(); int32 state = getState(); bool prependOnly = (state != ConnectedState); mtpRequest pingRequest; if (_shiftedDcId == bareDcId(_shiftedDcId)) { // main session if (!prependOnly && !_pingIdToSend && !_pingId && _pingSendAt <= getms(true)) { _pingIdToSend = rand_value<mtpPingId>(); } } if (_pingIdToSend) { if (prependOnly || _shiftedDcId != bareDcId(_shiftedDcId)) { MTPPing ping(MTPping(MTP_long(_pingIdToSend))); uint32 pingSize = ping.innerLength() >> 2; // copy from Session::send pingRequest = mtpRequestData::prepare(pingSize); ping.write(*pingRequest); DEBUG_LOG(("MTP Info: sending ping, ping_id: %1").arg(_pingIdToSend)); } else { MTPPing_delay_disconnect ping(MTP_long(_pingIdToSend), MTP_int(MTPPingDelayDisconnect)); uint32 pingSize = ping.innerLength() >> 2; // copy from Session::send pingRequest = mtpRequestData::prepare(pingSize); ping.write(*pingRequest); DEBUG_LOG(("MTP Info: sending ping_delay_disconnect, ping_id: %1").arg(_pingIdToSend)); } pingRequest->msDate = getms(true); // > 0 - can send without container _pingSendAt = pingRequest->msDate + (MTPPingSendAfterAuto * 1000LL); pingRequest->requestId = 0; // dont add to haveSent / wereAcked maps if (_shiftedDcId == bareDcId(_shiftedDcId) && !prependOnly) { // main session _pingSender.start(MTPPingSendAfter * 1000); } _pingId = _pingIdToSend; _pingIdToSend = 0; } else { if (prependOnly) { DEBUG_LOG(("MTP Info: dc %1 not sending, waiting for Connected state, state: %2").arg(_shiftedDcId).arg(state)); return; // just do nothing, if is not connected yet } else { DEBUG_LOG(("MTP Info: dc %1 trying to send after ping, state: %2").arg(_shiftedDcId).arg(state)); } } mtpRequest ackRequest, resendRequest, stateRequest, httpWaitRequest; if (!prependOnly && !ackRequestData.isEmpty()) { MTPMsgsAck ack(MTP_msgs_ack(MTP_vector<MTPlong>(ackRequestData))); ackRequest = mtpRequestData::prepare(ack.innerLength() >> 2); ack.write(*ackRequest); ackRequest->msDate = getms(true); // > 0 - can send without container ackRequest->requestId = 0; // dont add to haveSent / wereAcked maps ackRequestData.clear(); } if (!prependOnly && !resendRequestData.isEmpty()) { MTPMsgResendReq resend(MTP_msg_resend_req(MTP_vector<MTPlong>(resendRequestData))); resendRequest = mtpRequestData::prepare(resend.innerLength() >> 2); resend.write(*resendRequest); resendRequest->msDate = getms(true); // > 0 - can send without container resendRequest->requestId = 0; // dont add to haveSent / wereAcked maps resendRequestData.clear(); } if (!prependOnly) { QVector<MTPlong> stateReq; { QWriteLocker locker(sessionData->stateRequestMutex()); mtpMsgIdsSet &ids(sessionData->stateRequestMap()); if (!ids.isEmpty()) { stateReq.reserve(ids.size()); for (mtpMsgIdsSet::const_iterator i = ids.cbegin(), e = ids.cend(); i != e; ++i) { stateReq.push_back(MTP_long(i.key())); } } ids.clear(); } if (!stateReq.isEmpty()) { MTPMsgsStateReq req(MTP_msgs_state_req(MTP_vector<MTPlong>(stateReq))); stateRequest = mtpRequestData::prepare(req.innerLength() >> 2); req.write(*stateRequest); stateRequest->msDate = getms(true); // > 0 - can send without container stateRequest->requestId = reqid();// add to haveSent / wereAcked maps, but don't add to requestMap } if (_conn->usingHttpWait()) { MTPHttpWait req(MTP_http_wait(MTP_int(100), MTP_int(30), MTP_int(25000))); httpWaitRequest = mtpRequestData::prepare(req.innerLength() >> 2); req.write(*httpWaitRequest); httpWaitRequest->msDate = getms(true); // > 0 - can send without container httpWaitRequest->requestId = 0; // dont add to haveSent / wereAcked maps } } MTPInitConnection<mtpRequest> initWrapper; int32 initSize = 0, initSizeInInts = 0; if (needsLayer) { auto systemLangCode = sessionData->systemLangCode(); auto cloudLangCode = sessionData->cloudLangCode(); auto langPack = "tdesktop"; auto deviceModel = (_dcType == DcType::Cdn) ? "n/a" : cApiDeviceModel(); auto systemVersion = (_dcType == DcType::Cdn) ? "n/a" : cApiSystemVersion(); initWrapper = MTPInitConnection<mtpRequest>(MTP_int(ApiId), MTP_string(deviceModel), MTP_string(systemVersion), MTP_string(str_const_toString(AppVersionStr)), MTP_string(systemLangCode), MTP_string(langPack), MTP_string(cloudLangCode), mtpRequest()); initSizeInInts = (initWrapper.innerLength() >> 2) + 2; initSize = initSizeInInts * sizeof(mtpPrime); } bool needAnyResponse = false; mtpRequest toSendRequest; { QWriteLocker locker1(sessionData->toSendMutex()); mtpPreRequestMap toSendDummy, &toSend(prependOnly ? toSendDummy : sessionData->toSendMap()); if (prependOnly) locker1.unlock(); uint32 toSendCount = toSend.size(); if (pingRequest) ++toSendCount; if (ackRequest) ++toSendCount; if (resendRequest) ++toSendCount; if (stateRequest) ++toSendCount; if (httpWaitRequest) ++toSendCount; if (!toSendCount) return; // nothing to send mtpRequest first = pingRequest ? pingRequest : (ackRequest ? ackRequest : (resendRequest ? resendRequest : (stateRequest ? stateRequest : (httpWaitRequest ? httpWaitRequest : toSend.cbegin().value())))); if (toSendCount == 1 && first->msDate > 0) { // if can send without container toSendRequest = first; if (!prependOnly) { toSend.clear(); locker1.unlock(); } mtpMsgId msgId = prepareToSend(toSendRequest, msgid()); if (pingRequest) { _pingMsgId = msgId; needAnyResponse = true; } else if (resendRequest || stateRequest) { needAnyResponse = true; } if (toSendRequest->requestId) { if (mtpRequestData::needAck(toSendRequest)) { toSendRequest->msDate = mtpRequestData::isStateRequest(toSendRequest) ? 0 : getms(true); QWriteLocker locker2(sessionData->haveSentMutex()); mtpRequestMap &haveSent(sessionData->haveSentMap()); haveSent.insert(msgId, toSendRequest); if (needsLayer && !toSendRequest->needsLayer) needsLayer = false; if (toSendRequest->after) { int32 toSendSize = toSendRequest.innerLength() >> 2; mtpRequest wrappedRequest(mtpRequestData::prepare(toSendSize, toSendSize + 3)); // cons + msg_id wrappedRequest->resize(4); memcpy(wrappedRequest->data(), toSendRequest->constData(), 4 * sizeof(mtpPrime)); wrapInvokeAfter(wrappedRequest, toSendRequest, haveSent); toSendRequest = wrappedRequest; } if (needsLayer) { int32 noWrapSize = (toSendRequest.innerLength() >> 2), toSendSize = noWrapSize + initSizeInInts; mtpRequest wrappedRequest(mtpRequestData::prepare(toSendSize)); memcpy(wrappedRequest->data(), toSendRequest->constData(), 7 * sizeof(mtpPrime)); // all except length wrappedRequest->push_back(mtpc_invokeWithLayer); wrappedRequest->push_back(MTP::internal::CurrentLayer); initWrapper.write(*wrappedRequest); wrappedRequest->resize(wrappedRequest->size() + noWrapSize); memcpy(wrappedRequest->data() + wrappedRequest->size() - noWrapSize, toSendRequest->constData() + 8, noWrapSize * sizeof(mtpPrime)); toSendRequest = wrappedRequest; } needAnyResponse = true; } else { QWriteLocker locker3(sessionData->wereAckedMutex()); sessionData->wereAckedMap().insert(msgId, toSendRequest->requestId); } } } else { // send in container bool willNeedInit = false; uint32 containerSize = 1 + 1, idsWrapSize = (toSendCount << 1); // cons + vector size, idsWrapSize - size of "request-like" wrap for msgId vector if (pingRequest) containerSize += mtpRequestData::messageSize(pingRequest); if (ackRequest) containerSize += mtpRequestData::messageSize(ackRequest); if (resendRequest) containerSize += mtpRequestData::messageSize(resendRequest); if (stateRequest) containerSize += mtpRequestData::messageSize(stateRequest); if (httpWaitRequest) containerSize += mtpRequestData::messageSize(httpWaitRequest); for (mtpPreRequestMap::iterator i = toSend.begin(), e = toSend.end(); i != e; ++i) { containerSize += mtpRequestData::messageSize(i.value()); if (needsLayer && i.value()->needsLayer) { containerSize += initSizeInInts; willNeedInit = true; } } mtpBuffer initSerialized; if (willNeedInit) { initSerialized.reserve(initSizeInInts); initSerialized.push_back(mtpc_invokeWithLayer); initSerialized.push_back(MTP::internal::CurrentLayer); initWrapper.write(initSerialized); } toSendRequest = mtpRequestData::prepare(containerSize, containerSize + 3 * toSend.size()); // prepare container + each in invoke after toSendRequest->push_back(mtpc_msg_container); toSendRequest->push_back(toSendCount); mtpMsgId bigMsgId = msgid(); // check for a valid container QWriteLocker locker2(sessionData->haveSentMutex()); // the fact of this lock is used in replaceMsgId() mtpRequestMap &haveSent(sessionData->haveSentMap()); QWriteLocker locker3(sessionData->wereAckedMutex()); // the fact of this lock is used in replaceMsgId() mtpRequestIdsMap &wereAcked(sessionData->wereAckedMap()); mtpRequest haveSentIdsWrap(mtpRequestData::prepare(idsWrapSize)); // prepare "request-like" wrap for msgId vector haveSentIdsWrap->requestId = 0; haveSentIdsWrap->resize(haveSentIdsWrap->size() + idsWrapSize); mtpMsgId *haveSentArr = (mtpMsgId*)(haveSentIdsWrap->data() + 8); if (pingRequest) { _pingMsgId = placeToContainer(toSendRequest, bigMsgId, haveSentArr, pingRequest); needAnyResponse = true; } else if (resendRequest || stateRequest) { needAnyResponse = true; } for (mtpPreRequestMap::iterator i = toSend.begin(), e = toSend.end(); i != e; ++i) { mtpRequest &req(i.value()); mtpMsgId msgId = prepareToSend(req, bigMsgId); if (msgId > bigMsgId) msgId = replaceMsgId(req, bigMsgId); if (msgId >= bigMsgId) bigMsgId = msgid(); *(haveSentArr++) = msgId; bool added = false; if (req->requestId) { if (mtpRequestData::needAck(req)) { req->msDate = mtpRequestData::isStateRequest(req) ? 0 : getms(true); int32 reqNeedsLayer = (needsLayer && req->needsLayer) ? toSendRequest->size() : 0; if (req->after) { wrapInvokeAfter(toSendRequest, req, haveSent, reqNeedsLayer ? initSizeInInts : 0); if (reqNeedsLayer) { memcpy(toSendRequest->data() + reqNeedsLayer + 4, initSerialized.constData(), initSize); *(toSendRequest->data() + reqNeedsLayer + 3) += initSize; } added = true; } else if (reqNeedsLayer) { toSendRequest->resize(reqNeedsLayer + initSizeInInts + mtpRequestData::messageSize(req)); memcpy(toSendRequest->data() + reqNeedsLayer, req->constData() + 4, 4 * sizeof(mtpPrime)); memcpy(toSendRequest->data() + reqNeedsLayer + 4, initSerialized.constData(), initSize); memcpy(toSendRequest->data() + reqNeedsLayer + 4 + initSizeInInts, req->constData() + 8, req.innerLength()); *(toSendRequest->data() + reqNeedsLayer + 3) += initSize; added = true; } haveSent.insert(msgId, req); needAnyResponse = true; } else { wereAcked.insert(msgId, req->requestId); } } if (!added) { uint32 from = toSendRequest->size(), len = mtpRequestData::messageSize(req); toSendRequest->resize(from + len); memcpy(toSendRequest->data() + from, req->constData() + 4, len * sizeof(mtpPrime)); } } if (stateRequest) { mtpMsgId msgId = placeToContainer(toSendRequest, bigMsgId, haveSentArr, stateRequest); stateRequest->msDate = 0; // 0 for state request, do not request state of it haveSent.insert(msgId, stateRequest); } if (resendRequest) placeToContainer(toSendRequest, bigMsgId, haveSentArr, resendRequest); if (ackRequest) placeToContainer(toSendRequest, bigMsgId, haveSentArr, ackRequest); if (httpWaitRequest) placeToContainer(toSendRequest, bigMsgId, haveSentArr, httpWaitRequest); mtpMsgId contMsgId = prepareToSend(toSendRequest, bigMsgId); *(mtpMsgId*)(haveSentIdsWrap->data() + 4) = contMsgId; (*haveSentIdsWrap)[6] = 0; // for container, msDate = 0, seqNo = 0 haveSent.insert(contMsgId, haveSentIdsWrap); toSend.clear(); } } mtpRequestData::padding(toSendRequest); sendRequest(toSendRequest, needAnyResponse, lockFinished); } void ConnectionPrivate::retryByTimer() { QReadLocker lockFinished(&sessionDataMutex); if (!sessionData) return; if (retryTimeout < 3) { ++retryTimeout; } else if (retryTimeout == 3) { retryTimeout = 1000; } else if (retryTimeout < 64000) { retryTimeout *= 2; } if (keyId == kRecreateKeyId) { if (sessionData->getKey()) { unlockKey(); QWriteLocker lock(sessionData->keyMutex()); sessionData->owner()->destroyKey(); } keyId = 0; } connectToServer(); } void ConnectionPrivate::restartNow() { retryTimeout = 1; retryTimer.stop(); restart(); } void ConnectionPrivate::connectToServer(bool afterConfig) { if (_finished) { DEBUG_LOG(("MTP Error: connectToServer() called for finished connection!")); return; } auto bareDc = bareDcId(_shiftedDcId); _dcType = _instance->dcOptions()->dcType(_shiftedDcId); if (_dcType == DcType::MediaDownload) { // using media_only addresses only if key for this dc is already created QReadLocker lockFinished(&sessionDataMutex); if (!sessionData || !sessionData->getKey()) { _dcType = DcType::Regular; } } else if (_dcType == DcType::Cdn && !_instance->isKeysDestroyer()) { if (!_instance->dcOptions()->hasCDNKeysForDc(bareDc)) { requestCDNConfig(); return; } } using Variants = DcOptions::Variants; auto kIPv4 = Variants::IPv4; auto kIPv6 = Variants::IPv6; auto kTcp = Variants::Tcp; auto kHttp = Variants::Http; auto variants = _instance->dcOptions()->lookup(bareDc, _dcType); auto noIPv4 = (_dcType == DcType::Temporary) ? (variants.data[kIPv4][kTcp].port == 0) : (variants.data[kIPv4][kHttp].port == 0); auto noIPv6 = (_dcType == DcType::Temporary) ? true : (!Global::TryIPv6() || (variants.data[kIPv6][kHttp].port == 0)); if (noIPv4 && noIPv6) { if (_instance->isKeysDestroyer()) { LOG(("MTP Error: DC %1 options for IPv4 over HTTP not found for auth key destruction!").arg(_shiftedDcId)); if (Global::TryIPv6() && noIPv6) LOG(("MTP Error: DC %1 options for IPv6 over HTTP not found for auth key destruction!").arg(_shiftedDcId)); emit _instance->keyDestroyed(_shiftedDcId); return; } else if (afterConfig) { LOG(("MTP Error: DC %1 options for IPv4 over HTTP not found right after config load!").arg(_shiftedDcId)); if (Global::TryIPv6() && noIPv6) LOG(("MTP Error: DC %1 options for IPv6 over HTTP not found right after config load!").arg(_shiftedDcId)); return restart(); } DEBUG_LOG(("MTP Info: DC %1 options for IPv4 over HTTP not found, waiting for config").arg(_shiftedDcId)); if (Global::TryIPv6() && noIPv6) DEBUG_LOG(("MTP Info: DC %1 options for IPv6 over HTTP not found, waiting for config").arg(_shiftedDcId)); connect(_instance, SIGNAL(configLoaded()), this, SLOT(onConfigLoaded()), Qt::UniqueConnection); InvokeQueued(_instance, [instance = _instance] { instance->requestConfig(); }); return; } if (afterConfig && (_conn4 || _conn6)) return; createConn(!noIPv4, !noIPv6); retryTimer.stop(); _waitForConnectedTimer.stop(); setState(ConnectingState); _pingId = _pingMsgId = _pingIdToSend = _pingSendAt = 0; _pingSender.stop(); if (!noIPv4) DEBUG_LOG(("MTP Info: creating IPv4 connection to %1:%2 (tcp) and %3:%4 (http)...").arg(variants.data[kIPv4][kTcp].ip.c_str()).arg(variants.data[kIPv4][kTcp].port).arg(variants.data[kIPv4][kHttp].ip.c_str()).arg(variants.data[kIPv4][kHttp].port)); if (!noIPv6) DEBUG_LOG(("MTP Info: creating IPv6 connection to [%1]:%2 (tcp) and [%3]:%4 (http)...").arg(variants.data[kIPv6][kTcp].ip.c_str()).arg(variants.data[kIPv6][kTcp].port).arg(variants.data[kIPv4][kHttp].ip.c_str()).arg(variants.data[kIPv4][kHttp].port)); _waitForConnectedTimer.start(_waitForConnected); if (auto conn = _conn4) { connect(conn, SIGNAL(connected()), this, SLOT(onConnected4())); connect(conn, SIGNAL(disconnected()), this, SLOT(onDisconnected4())); conn->connectTcp(variants.data[kIPv4][kTcp]); conn->connectHttp(variants.data[kIPv4][kHttp]); } if (auto conn = _conn6) { connect(conn, SIGNAL(connected()), this, SLOT(onConnected6())); connect(conn, SIGNAL(disconnected()), this, SLOT(onDisconnected6())); conn->connectTcp(variants.data[kIPv6][kTcp]); conn->connectHttp(variants.data[kIPv6][kHttp]); } } void ConnectionPrivate::restart() { QReadLocker lockFinished(&sessionDataMutex); if (!sessionData) return; DEBUG_LOG(("MTP Info: restarting Connection")); _waitForReceivedTimer.stop(); _waitForConnectedTimer.stop(); auto key = sessionData->getKey(); if (key) { if (!sessionData->isCheckedKey()) { // No destroying in case of an error. // //if (mayBeBadKey) { // clearMessages(); // keyId = kRecreateKeyId; // retryTimeout = 1; // no ddos please // LOG(("MTP Info: key may be bad and was not checked - but won't be destroyed, no log outs because of bad server right now...")); //} } else { sessionData->setCheckedKey(false); } } lockFinished.unlock(); doDisconnect(); lockFinished.relock(); if (sessionData && _needSessionReset) { resetSession(); } restarted = true; if (retryTimer.isActive()) return; DEBUG_LOG(("MTP Info: restart timeout: %1ms").arg(retryTimeout)); setState(-retryTimeout); } void ConnectionPrivate::onSentSome(uint64 size) { if (!_waitForReceivedTimer.isActive()) { auto remain = static_cast<uint64>(_waitForReceived); if (!oldConnection) { auto remainBySize = size * _waitForReceived / 8192; // 8kb / sec, so 512 kb give 64 sec remain = snap(remainBySize, remain, uint64(MTPMaxReceiveDelay)); if (remain != _waitForReceived) { DEBUG_LOG(("Checking connect for request with size %1 bytes, delay will be %2").arg(size).arg(remain)); } } if (isUploadDcId(_shiftedDcId)) { remain *= kUploadSessionsCount; } else if (isDownloadDcId(_shiftedDcId)) { remain *= kDownloadSessionsCount; } _waitForReceivedTimer.start(remain); } if (!firstSentAt) firstSentAt = getms(true); } void ConnectionPrivate::onReceivedSome() { if (oldConnection) { oldConnection = false; DEBUG_LOG(("This connection marked as not old!")); } oldConnectionTimer.start(MTPConnectionOldTimeout); _waitForReceivedTimer.stop(); if (firstSentAt > 0) { int32 ms = getms(true) - firstSentAt; DEBUG_LOG(("MTP Info: response in %1ms, _waitForReceived: %2ms").arg(ms).arg(_waitForReceived)); if (ms > 0 && ms * 2 < int32(_waitForReceived)) _waitForReceived = qMax(ms * 2, int32(MTPMinReceiveDelay)); firstSentAt = -1; } } void ConnectionPrivate::onOldConnection() { oldConnection = true; _waitForReceived = MTPMinReceiveDelay; DEBUG_LOG(("This connection marked as old! _waitForReceived now %1ms").arg(_waitForReceived)); } void ConnectionPrivate::onPingSender() { if (_pingId) { if (_pingSendAt + (MTPPingSendAfter - MTPPingSendAfterAuto - 1) * 1000LL < getms(true)) { LOG(("Could not send ping for MTPPingSendAfter seconds, restarting...")); return restart(); } else { _pingSender.start(_pingSendAt + (MTPPingSendAfter - MTPPingSendAfterAuto) * 1000LL - getms(true)); } } else { emit needToSendAsync(); } } void ConnectionPrivate::onPingSendForce() { if (!_pingId) { _pingSendAt = 0; DEBUG_LOG(("Will send ping!")); tryToSend(); } } void ConnectionPrivate::onWaitReceivedFailed() { if (Global::ConnectionType() != dbictAuto && Global::ConnectionType() != dbictTcpProxy) { return; } DEBUG_LOG(("MTP Info: bad connection, _waitForReceived: %1ms").arg(_waitForReceived)); if (_waitForReceived < MTPMaxReceiveDelay) { _waitForReceived *= 2; } doDisconnect(); restarted = true; if (retryTimer.isActive()) return; DEBUG_LOG(("MTP Info: immediate restart!")); InvokeQueued(this, [this] { connectToServer(); }); } void ConnectionPrivate::onWaitConnectedFailed() { DEBUG_LOG(("MTP Info: can't connect in %1ms").arg(_waitForConnected)); if (_waitForConnected < MTPMaxConnectDelay) _waitForConnected *= 2; doDisconnect(); restarted = true; DEBUG_LOG(("MTP Info: immediate restart!")); InvokeQueued(this, [this] { connectToServer(); }); } void ConnectionPrivate::onWaitIPv4Failed() { _conn = _conn6; destroyConn(&_conn4); if (_conn) { DEBUG_LOG(("MTP Info: can't connect through IPv4, using IPv6 connection.")); updateAuthKey(); } else { restart(); } } void ConnectionPrivate::doDisconnect() { destroyConn(); { QReadLocker lockFinished(&sessionDataMutex); if (sessionData) { unlockKey(); } } clearAuthKeyData(); setState(DisconnectedState); restarted = false; } void ConnectionPrivate::finishAndDestroy() { doDisconnect(); _finished = true; emit finished(_owner); deleteLater(); } void ConnectionPrivate::requestCDNConfig() { connect(_instance, SIGNAL(cdnConfigLoaded()), this, SLOT(onCDNConfigLoaded()), Qt::UniqueConnection); InvokeQueued(_instance, [instance = _instance] { instance->requestCDNConfig(); }); } void ConnectionPrivate::handleReceived() { QReadLocker lockFinished(&sessionDataMutex); if (!sessionData) return; onReceivedSome(); auto restartOnError = [this, &lockFinished] { lockFinished.unlock(); restart(); }; ReadLockerAttempt lock(sessionData->keyMutex()); if (!lock) { DEBUG_LOG(("MTP Error: auth_key for dc %1 busy, cant lock").arg(_shiftedDcId)); clearMessages(); keyId = 0; return restartOnError(); } auto key = sessionData->getKey(); if (!key || key->keyId() != keyId) { DEBUG_LOG(("MTP Error: auth_key id for dc %1 changed").arg(_shiftedDcId)); return restartOnError(); } while (!_conn->received().empty()) { auto intsBuffer = std::move(_conn->received().front()); _conn->received().pop_front(); constexpr auto kExternalHeaderIntsCount = 6U; // 2 auth_key_id, 4 msg_key constexpr auto kEncryptedHeaderIntsCount = 8U; // 2 salt, 2 session, 2 msg_id, 1 seq_no, 1 length constexpr auto kMinimalEncryptedIntsCount = kEncryptedHeaderIntsCount + 4U; // + 1 data + 3 padding constexpr auto kMinimalIntsCount = kExternalHeaderIntsCount + kMinimalEncryptedIntsCount; auto intsCount = uint32(intsBuffer.size()); auto ints = intsBuffer.constData(); if ((intsCount < kMinimalIntsCount) || (intsCount > kMaxMessageLength / kIntSize)) { LOG(("TCP Error: bad message received, len %1").arg(intsCount * kIntSize)); TCP_LOG(("TCP Error: bad message %1").arg(Logs::mb(ints, intsCount * kIntSize).str())); return restartOnError(); } if (keyId != *(uint64*)ints) { LOG(("TCP Error: bad auth_key_id %1 instead of %2 received").arg(keyId).arg(*(uint64*)ints)); TCP_LOG(("TCP Error: bad message %1").arg(Logs::mb(ints, intsCount * kIntSize).str())); return restartOnError(); } auto encryptedInts = ints + kExternalHeaderIntsCount; auto encryptedIntsCount = (intsCount - kExternalHeaderIntsCount); auto encryptedBytesCount = encryptedIntsCount * kIntSize; auto decryptedBuffer = QByteArray(encryptedBytesCount, Qt::Uninitialized); auto msgKey = *(MTPint128*)(ints + 2); #ifdef TDESKTOP_MTPROTO_OLD aesIgeDecrypt_oldmtp(encryptedInts, decryptedBuffer.data(), encryptedBytesCount, key, msgKey); #else // TDESKTOP_MTPROTO_OLD aesIgeDecrypt(encryptedInts, decryptedBuffer.data(), encryptedBytesCount, key, msgKey); #endif // TDESKTOP_MTPROTO_OLD auto decryptedInts = reinterpret_cast<const mtpPrime*>(decryptedBuffer.constData()); auto serverSalt = *(uint64*)&decryptedInts[0]; auto session = *(uint64*)&decryptedInts[2]; auto msgId = *(uint64*)&decryptedInts[4]; auto seqNo = *(uint32*)&decryptedInts[6]; auto needAck = ((seqNo & 0x01) != 0); auto messageLength = *(uint32*)&decryptedInts[7]; if (messageLength > kMaxMessageLength) { LOG(("TCP Error: bad messageLength %1").arg(messageLength)); TCP_LOG(("TCP Error: bad message %1").arg(Logs::mb(ints, intsCount * kIntSize).str())); return restartOnError(); } auto fullDataLength = kEncryptedHeaderIntsCount * kIntSize + messageLength; // Without padding. // Can underflow, but it is an unsigned type, so we just check the range later. auto paddingSize = static_cast<uint32>(encryptedBytesCount) - static_cast<uint32>(fullDataLength); #ifdef TDESKTOP_MTPROTO_OLD constexpr auto kMinPaddingSize_oldmtp = 0U; constexpr auto kMaxPaddingSize_oldmtp = 15U; auto badMessageLength = (/*paddingSize < kMinPaddingSize_oldmtp || */paddingSize > kMaxPaddingSize_oldmtp); auto hashedDataLength = badMessageLength ? encryptedBytesCount : fullDataLength; auto sha1ForMsgKeyCheck = hashSha1(decryptedInts, hashedDataLength); constexpr auto kMsgKeyShift_oldmtp = 4U; if (memcmp(&msgKey, sha1ForMsgKeyCheck.data() + kMsgKeyShift_oldmtp, sizeof(msgKey)) != 0) { LOG(("TCP Error: bad SHA1 hash after aesDecrypt in message.")); TCP_LOG(("TCP Error: bad message %1").arg(Logs::mb(encryptedInts, encryptedBytesCount).str())); return restartOnError(); } #else // TDESKTOP_MTPROTO_OLD constexpr auto kMinPaddingSize = 12U; constexpr auto kMaxPaddingSize = 1024U; auto badMessageLength = (paddingSize < kMinPaddingSize || paddingSize > kMaxPaddingSize); std::array<uchar, 32> sha256Buffer = { { 0 } }; SHA256_CTX msgKeyLargeContext; SHA256_Init(&msgKeyLargeContext); SHA256_Update(&msgKeyLargeContext, key->partForMsgKey(false), 32); SHA256_Update(&msgKeyLargeContext, decryptedInts, encryptedBytesCount); SHA256_Final(sha256Buffer.data(), &msgKeyLargeContext); constexpr auto kMsgKeyShift = 8U; if (memcmp(&msgKey, sha256Buffer.data() + kMsgKeyShift, sizeof(msgKey)) != 0) { LOG(("TCP Error: bad SHA256 hash after aesDecrypt in message")); TCP_LOG(("TCP Error: bad message %1").arg(Logs::mb(encryptedInts, encryptedBytesCount).str())); return restartOnError(); } #endif // TDESKTOP_MTPROTO_OLD if (badMessageLength || (messageLength & 0x03)) { LOG(("TCP Error: bad msg_len received %1, data size: %2").arg(messageLength).arg(encryptedBytesCount)); TCP_LOG(("TCP Error: bad message %1").arg(Logs::mb(encryptedInts, encryptedBytesCount).str())); return restartOnError(); } TCP_LOG(("TCP Info: decrypted message %1,%2,%3 is %4 len").arg(msgId).arg(seqNo).arg(Logs::b(needAck)).arg(fullDataLength)); uint64 serverSession = sessionData->getSession(); if (session != serverSession) { LOG(("MTP Error: bad server session received")); TCP_LOG(("MTP Error: bad server session %1 instead of %2 in message received").arg(session).arg(serverSession)); return restartOnError(); } int32 serverTime((int32)(msgId >> 32)), clientTime(unixtime()); bool isReply = ((msgId & 0x03) == 1); if (!isReply && ((msgId & 0x03) != 3)) { LOG(("MTP Error: bad msg_id %1 in message received").arg(msgId)); return restartOnError(); } bool badTime = false; uint64 mySalt = sessionData->getSalt(); if (serverTime > clientTime + 60 || serverTime + 300 < clientTime) { DEBUG_LOG(("MTP Info: bad server time from msg_id: %1, my time: %2").arg(serverTime).arg(clientTime)); badTime = true; } bool wasConnected = (getState() == ConnectedState); if (serverSalt != mySalt) { if (!badTime) { DEBUG_LOG(("MTP Info: other salt received... received: %1, my salt: %2, updating...").arg(serverSalt).arg(mySalt)); sessionData->setSalt(serverSalt); if (setState(ConnectedState, ConnectingState)) { // only connected if (restarted) { emit resendAllAsync(); restarted = false; } } } else { DEBUG_LOG(("MTP Info: other salt received... received: %1, my salt: %2").arg(serverSalt).arg(mySalt)); } } else { serverSalt = 0; // dont pass to handle method, so not to lock in setSalt() } if (needAck) ackRequestData.push_back(MTP_long(msgId)); auto res = HandleResult::Success; // if no need to handle, then succeed auto from = decryptedInts + kEncryptedHeaderIntsCount; auto end = from + (messageLength / kIntSize); auto sfrom = decryptedInts + 4U; // msg_id + seq_no + length + message MTP_LOG(_shiftedDcId, ("Recv: ") + mtpTextSerialize(sfrom, end)); bool needToHandle = false; { QWriteLocker lock(sessionData->receivedIdsMutex()); needToHandle = sessionData->receivedIdsSet().registerMsgId(msgId, needAck); } if (needToHandle) { res = handleOneReceived(from, end, msgId, serverTime, serverSalt, badTime); } { QWriteLocker lock(sessionData->receivedIdsMutex()); sessionData->receivedIdsSet().shrink(); } // send acks uint32 toAckSize = ackRequestData.size(); if (toAckSize) { DEBUG_LOG(("MTP Info: will send %1 acks, ids: %2").arg(toAckSize).arg(LogIdsVector(ackRequestData))); emit sendAnythingAsync(MTPAckSendWaiting); } bool emitSignal = false; { QReadLocker locker(sessionData->haveReceivedMutex()); emitSignal = !sessionData->haveReceivedResponses().isEmpty() || !sessionData->haveReceivedUpdates().isEmpty(); if (emitSignal) { DEBUG_LOG(("MTP Info: emitting needToReceive() - need to parse in another thread, %1 responses, %2 updates.").arg(sessionData->haveReceivedResponses().size()).arg(sessionData->haveReceivedUpdates().size())); } } if (emitSignal) { emit needToReceive(); } if (res != HandleResult::Success && res != HandleResult::Ignored) { _needSessionReset = (res == HandleResult::ResetSession); return restartOnError(); } retryTimeout = 1; // reset restart() timer if (!sessionData->isCheckedKey()) { DEBUG_LOG(("MTP Info: marked auth key as checked")); sessionData->setCheckedKey(true); } if (!wasConnected) { if (getState() == ConnectedState) { emit needToSendAsync(); } } } if (_conn->needHttpWait()) { emit sendHttpWaitAsync(); } } ConnectionPrivate::HandleResult ConnectionPrivate::handleOneReceived(const mtpPrime *from, const mtpPrime *end, uint64 msgId, int32 serverTime, uint64 serverSalt, bool badTime) { mtpTypeId cons = *from; try { switch (cons) { case mtpc_gzip_packed: { DEBUG_LOG(("Message Info: gzip container")); mtpBuffer response = ungzip(++from, end); if (!response.size()) { return HandleResult::RestartConnection; } return handleOneReceived(response.data(), response.data() + response.size(), msgId, serverTime, serverSalt, badTime); } case mtpc_msg_container: { if (++from >= end) throw mtpErrorInsufficient(); const mtpPrime *otherEnd; uint32 msgsCount = (uint32)*(from++); DEBUG_LOG(("Message Info: container received, count: %1").arg(msgsCount)); for (uint32 i = 0; i < msgsCount; ++i) { if (from + 4 >= end) throw mtpErrorInsufficient(); otherEnd = from + 4; MTPlong inMsgId; inMsgId.read(from, otherEnd); bool isReply = ((inMsgId.v & 0x03) == 1); if (!isReply && ((inMsgId.v & 0x03) != 3)) { LOG(("Message Error: bad msg_id %1 in contained message received").arg(inMsgId.v)); return HandleResult::RestartConnection; } MTPint inSeqNo; inSeqNo.read(from, otherEnd); MTPint bytes; bytes.read(from, otherEnd); if ((bytes.v & 0x03) || bytes.v < 4) { LOG(("Message Error: bad length %1 of contained message received").arg(bytes.v)); return HandleResult::RestartConnection; } bool needAck = (inSeqNo.v & 0x01); if (needAck) ackRequestData.push_back(inMsgId); DEBUG_LOG(("Message Info: message from container, msg_id: %1, needAck: %2").arg(inMsgId.v).arg(Logs::b(needAck))); otherEnd = from + (bytes.v >> 2); if (otherEnd > end) throw mtpErrorInsufficient(); bool needToHandle = false; { QWriteLocker lock(sessionData->receivedIdsMutex()); needToHandle = sessionData->receivedIdsSet().registerMsgId(inMsgId.v, needAck); } auto res = HandleResult::Success; // if no need to handle, then succeed if (needToHandle) { res = handleOneReceived(from, otherEnd, inMsgId.v, serverTime, serverSalt, badTime); badTime = false; } if (res != HandleResult::Success) { return res; } from = otherEnd; } } return HandleResult::Success; case mtpc_msgs_ack: { MTPMsgsAck msg; msg.read(from, end); auto &ids = msg.c_msgs_ack().vmsg_ids.v; uint32 idsCount = ids.size(); DEBUG_LOG(("Message Info: acks received, ids: %1").arg(LogIdsVector(ids))); if (!idsCount) return (badTime ? HandleResult::Ignored : HandleResult::Success); if (badTime) { if (requestsFixTimeSalt(ids, serverTime, serverSalt)) { badTime = false; } else { return HandleResult::Ignored; } } requestsAcked(ids); } return HandleResult::Success; case mtpc_bad_msg_notification: { MTPBadMsgNotification msg; msg.read(from, end); const auto &data(msg.c_bad_msg_notification()); LOG(("Message Info: bad message notification received (error_code %3) for msg_id = %1, seq_no = %2").arg(data.vbad_msg_id.v).arg(data.vbad_msg_seqno.v).arg(data.verror_code.v)); mtpMsgId resendId = data.vbad_msg_id.v; if (resendId == _pingMsgId) { _pingId = 0; } int32 errorCode = data.verror_code.v; if (false || errorCode == 16 || errorCode == 17 || errorCode == 32 || errorCode == 33 || errorCode == 64) { // can handle const auto needResend = false || (errorCode == 16) // bad msg_id || (errorCode == 17) // bad msg_id || (errorCode == 64); // bad container if (errorCode == 64) { // bad container! if (cDebug()) { mtpRequest request; { QWriteLocker locker(sessionData->haveSentMutex()); mtpRequestMap &haveSent(sessionData->haveSentMap()); mtpRequestMap::const_iterator i = haveSent.constFind(resendId); if (i == haveSent.cend()) { LOG(("Message Error: Container not found!")); } else { request = i.value(); } } if (request) { if (mtpRequestData::isSentContainer(request)) { QStringList lst; const auto ids = (const mtpMsgId *)(request->constData() + 8); for (uint32 i = 0, l = (request->size() - 8) >> 1; i < l; ++i) { lst.push_back(QString::number(ids[i])); } LOG(("Message Info: bad container received! messages: %1").arg(lst.join(','))); } else { LOG(("Message Error: bad container received, but request is not a container!")); } } } } if (!wasSent(resendId)) { DEBUG_LOG(("Message Error: " "such message was not sent recently %1").arg(resendId)); return badTime ? HandleResult::Ignored : HandleResult::Success; } if (needResend) { // bad msg_id or bad container if (serverSalt) sessionData->setSalt(serverSalt); unixtimeSet(serverTime, true); DEBUG_LOG(("Message Info: unixtime updated, now %1, resending in container...").arg(serverTime)); resend(resendId, 0, true); } else { // must create new session, because msg_id and msg_seqno are inconsistent if (badTime) { if (serverSalt) sessionData->setSalt(serverSalt); unixtimeSet(serverTime, true); badTime = false; } LOG(("Message Info: bad message notification received, msgId %1, error_code %2").arg(data.vbad_msg_id.v).arg(errorCode)); return HandleResult::ResetSession; } } else { // fatal (except 48, but it must not get here) const auto badMsgId = mtpMsgId(data.vbad_msg_id.v); const auto requestId = wasSent(resendId); if (requestId) { LOG(("Message Error: " "bad message notification received, " "msgId %1, error_code %2, fatal: clearing callbacks" ).arg(badMsgId ).arg(errorCode )); _instance->clearCallbacksDelayed({ 1, RPCCallbackClear( requestId, -errorCode) }); } else { DEBUG_LOG(("Message Error: " "such message was not sent recently %1").arg(badMsgId)); } return badTime ? HandleResult::Ignored : HandleResult::Success; } } return HandleResult::Success; case mtpc_bad_server_salt: { MTPBadMsgNotification msg; msg.read(from, end); const auto &data(msg.c_bad_server_salt()); DEBUG_LOG(("Message Info: bad server salt received (error_code %4) for msg_id = %1, seq_no = %2, new salt: %3").arg(data.vbad_msg_id.v).arg(data.vbad_msg_seqno.v).arg(data.vnew_server_salt.v).arg(data.verror_code.v)); mtpMsgId resendId = data.vbad_msg_id.v; if (resendId == _pingMsgId) { _pingId = 0; } else if (!wasSent(resendId)) { DEBUG_LOG(("Message Error: such message was not sent recently %1").arg(resendId)); return (badTime ? HandleResult::Ignored : HandleResult::Success); } uint64 serverSalt = data.vnew_server_salt.v; sessionData->setSalt(serverSalt); unixtimeSet(serverTime); if (setState(ConnectedState, ConnectingState)) { // maybe only connected if (restarted) { emit resendAllAsync(); restarted = false; } } badTime = false; DEBUG_LOG(("Message Info: unixtime updated, now %1, server_salt updated, now %2, resending...").arg(serverTime).arg(serverSalt)); resend(resendId); } return HandleResult::Success; case mtpc_msgs_state_req: { if (badTime) { DEBUG_LOG(("Message Info: skipping with bad time...")); return HandleResult::Ignored; } MTPMsgsStateReq msg; msg.read(from, end); auto &ids = msg.c_msgs_state_req().vmsg_ids.v; auto idsCount = ids.size(); DEBUG_LOG(("Message Info: msgs_state_req received, ids: %1").arg(LogIdsVector(ids))); if (!idsCount) return HandleResult::Success; QByteArray info(idsCount, Qt::Uninitialized); { QReadLocker lock(sessionData->receivedIdsMutex()); auto &receivedIds = sessionData->receivedIdsSet(); auto minRecv = receivedIds.min(); auto maxRecv = receivedIds.max(); QReadLocker locker(sessionData->wereAckedMutex()); const mtpRequestIdsMap &wereAcked(sessionData->wereAckedMap()); mtpRequestIdsMap::const_iterator wereAckedEnd(wereAcked.cend()); for (uint32 i = 0, l = idsCount; i < l; ++i) { char state = 0; uint64 reqMsgId = ids[i].v; if (reqMsgId < minRecv) { state |= 0x01; } else if (reqMsgId > maxRecv) { state |= 0x03; } else { auto msgIdState = receivedIds.lookup(reqMsgId); if (msgIdState == ReceivedMsgIds::State::NotFound) { state |= 0x02; } else { state |= 0x04; if (wereAcked.constFind(reqMsgId) != wereAckedEnd) { state |= 0x80; // we know, that server knows, that we received request } if (msgIdState == ReceivedMsgIds::State::NeedsAck) { // need ack, so we sent ack state |= 0x08; } else { state |= 0x10; } } } info[i] = state; } } emit sendMsgsStateInfoAsync(msgId, info); } return HandleResult::Success; case mtpc_msgs_state_info: { MTPMsgsStateInfo msg; msg.read(from, end); auto &data = msg.c_msgs_state_info(); auto reqMsgId = data.vreq_msg_id.v; auto &states = data.vinfo.v; DEBUG_LOG(("Message Info: msg state received, msgId %1, reqMsgId: %2, HEX states %3").arg(msgId).arg(reqMsgId).arg(Logs::mb(states.data(), states.length()).str())); mtpRequest requestBuffer; { // find this request in session-shared sent requests map QReadLocker locker(sessionData->haveSentMutex()); const mtpRequestMap &haveSent(sessionData->haveSentMap()); mtpRequestMap::const_iterator replyTo = haveSent.constFind(reqMsgId); if (replyTo == haveSent.cend()) { // do not look in toResend, because we do not resend msgs_state_req requests DEBUG_LOG(("Message Error: such message was not sent recently %1").arg(reqMsgId)); return (badTime ? HandleResult::Ignored : HandleResult::Success); } if (badTime) { if (serverSalt) sessionData->setSalt(serverSalt); // requestsFixTimeSalt with no lookup unixtimeSet(serverTime, true); DEBUG_LOG(("Message Info: unixtime updated from mtpc_msgs_state_info, now %1").arg(serverTime)); badTime = false; } requestBuffer = replyTo.value(); } QVector<MTPlong> toAckReq(1, MTP_long(reqMsgId)), toAck; requestsAcked(toAck, true); if (requestBuffer->size() < 9) { LOG(("Message Error: bad request %1 found in requestMap, size: %2").arg(reqMsgId).arg(requestBuffer->size())); return HandleResult::RestartConnection; } try { const mtpPrime *rFrom = requestBuffer->constData() + 8, *rEnd = requestBuffer->constData() + requestBuffer->size(); if (mtpTypeId(*rFrom) == mtpc_msgs_state_req) { MTPMsgsStateReq request; request.read(rFrom, rEnd); handleMsgsStates(request.c_msgs_state_req().vmsg_ids.v, states, toAck); } else { MTPMsgResendReq request; request.read(rFrom, rEnd); handleMsgsStates(request.c_msg_resend_req().vmsg_ids.v, states, toAck); } } catch(Exception &) { LOG(("Message Error: could not parse sent msgs_state_req")); throw; } requestsAcked(toAck); } return HandleResult::Success; case mtpc_msgs_all_info: { if (badTime) { DEBUG_LOG(("Message Info: skipping with bad time...")); return HandleResult::Ignored; } MTPMsgsAllInfo msg; msg.read(from, end); auto &data = msg.c_msgs_all_info(); auto &ids = data.vmsg_ids.v; auto &states = data.vinfo.v; QVector<MTPlong> toAck; DEBUG_LOG(("Message Info: msgs all info received, msgId %1, reqMsgIds: %2, states %3").arg(msgId).arg(LogIdsVector(ids)).arg(Logs::mb(states.data(), states.length()).str())); handleMsgsStates(ids, states, toAck); requestsAcked(toAck); } return HandleResult::Success; case mtpc_msg_detailed_info: { MTPMsgDetailedInfo msg; msg.read(from, end); const auto &data(msg.c_msg_detailed_info()); DEBUG_LOG(("Message Info: msg detailed info, sent msgId %1, answerId %2, status %3, bytes %4").arg(data.vmsg_id.v).arg(data.vanswer_msg_id.v).arg(data.vstatus.v).arg(data.vbytes.v)); QVector<MTPlong> ids(1, data.vmsg_id); if (badTime) { if (requestsFixTimeSalt(ids, serverTime, serverSalt)) { badTime = false; } else { DEBUG_LOG(("Message Info: error, such message was not sent recently %1").arg(data.vmsg_id.v)); return HandleResult::Ignored; } } requestsAcked(ids); bool received = false; MTPlong resMsgId = data.vanswer_msg_id; { QReadLocker lock(sessionData->receivedIdsMutex()); received = (sessionData->receivedIdsSet().lookup(resMsgId.v) != ReceivedMsgIds::State::NotFound); } if (received) { ackRequestData.push_back(resMsgId); } else { DEBUG_LOG(("Message Info: answer message %1 was not received, requesting...").arg(resMsgId.v)); resendRequestData.push_back(resMsgId); } } return HandleResult::Success; case mtpc_msg_new_detailed_info: { if (badTime) { DEBUG_LOG(("Message Info: skipping msg_new_detailed_info with bad time...")); return HandleResult::Ignored; } MTPMsgDetailedInfo msg; msg.read(from, end); const auto &data(msg.c_msg_new_detailed_info()); DEBUG_LOG(("Message Info: msg new detailed info, answerId %2, status %3, bytes %4").arg(data.vanswer_msg_id.v).arg(data.vstatus.v).arg(data.vbytes.v)); bool received = false; MTPlong resMsgId = data.vanswer_msg_id; { QReadLocker lock(sessionData->receivedIdsMutex()); received = (sessionData->receivedIdsSet().lookup(resMsgId.v) != ReceivedMsgIds::State::NotFound); } if (received) { ackRequestData.push_back(resMsgId); } else { DEBUG_LOG(("Message Info: answer message %1 was not received, requesting...").arg(resMsgId.v)); resendRequestData.push_back(resMsgId); } } return HandleResult::Success; case mtpc_msg_resend_req: { MTPMsgResendReq msg; msg.read(from, end); auto &ids = msg.c_msg_resend_req().vmsg_ids.v; auto idsCount = ids.size(); DEBUG_LOG(("Message Info: resend of msgs requested, ids: %1").arg(LogIdsVector(ids))); if (!idsCount) return (badTime ? HandleResult::Ignored : HandleResult::Success); QVector<quint64> toResend(ids.size()); for (int32 i = 0, l = ids.size(); i < l; ++i) { toResend[i] = ids.at(i).v; } resendMany(toResend, 0, false, true); } return HandleResult::Success; case mtpc_rpc_result: { if (from + 3 > end) throw mtpErrorInsufficient(); auto response = SerializedMessage(); MTPlong reqMsgId; reqMsgId.read(++from, end); mtpTypeId typeId = from[0]; DEBUG_LOG(("RPC Info: response received for %1, queueing...").arg(reqMsgId.v)); QVector<MTPlong> ids(1, reqMsgId); if (badTime) { if (requestsFixTimeSalt(ids, serverTime, serverSalt)) { badTime = false; } else { DEBUG_LOG(("Message Info: error, such message was not sent recently %1").arg(reqMsgId.v)); return HandleResult::Ignored; } } requestsAcked(ids, true); if (typeId == mtpc_gzip_packed) { DEBUG_LOG(("RPC Info: gzip container")); response = ungzip(++from, end); if (!response.size()) { return HandleResult::RestartConnection; } typeId = response[0]; } else { response.resize(end - from); memcpy(response.data(), from, (end - from) * sizeof(mtpPrime)); } if (typeId != mtpc_rpc_error) { // An error could be some RPC_CALL_FAIL or other error inside // the initConnection, so we're not sure yet that it was inited. // Wait till a good response is received. if (!sessionData->layerWasInited()) { sessionData->setLayerWasInited(true); sessionData->owner()->notifyLayerInited(true); } } auto requestId = wasSent(reqMsgId.v); if (requestId && requestId != mtpRequestId(0xFFFFFFFF)) { // Save rpc_result for processing in the main thread. QWriteLocker locker(sessionData->haveReceivedMutex()); sessionData->haveReceivedResponses().insert(requestId, response); } else { DEBUG_LOG(("RPC Info: requestId not found for msgId %1").arg(reqMsgId.v)); } } return HandleResult::Success; case mtpc_new_session_created: { const mtpPrime *start = from; MTPNewSession msg; msg.read(from, end); const auto &data(msg.c_new_session_created()); if (badTime) { if (requestsFixTimeSalt(QVector<MTPlong>(1, data.vfirst_msg_id), serverTime, serverSalt)) { badTime = false; } else { DEBUG_LOG(("Message Info: error, such message was not sent recently %1").arg(data.vfirst_msg_id.v)); return HandleResult::Ignored; } } DEBUG_LOG(("Message Info: new server session created, unique_id %1, first_msg_id %2, server_salt %3").arg(data.vunique_id.v).arg(data.vfirst_msg_id.v).arg(data.vserver_salt.v)); sessionData->setSalt(data.vserver_salt.v); mtpMsgId firstMsgId = data.vfirst_msg_id.v; QVector<quint64> toResend; { QReadLocker locker(sessionData->haveSentMutex()); const mtpRequestMap &haveSent(sessionData->haveSentMap()); toResend.reserve(haveSent.size()); for (mtpRequestMap::const_iterator i = haveSent.cbegin(), e = haveSent.cend(); i != e; ++i) { if (i.key() >= firstMsgId) break; if (i.value()->requestId) toResend.push_back(i.key()); } } resendMany(toResend, 10, true); mtpBuffer update(from - start); if (from > start) memcpy(update.data(), start, (from - start) * sizeof(mtpPrime)); // Notify main process about new session - need to get difference. QWriteLocker locker(sessionData->haveReceivedMutex()); sessionData->haveReceivedUpdates().push_back(SerializedMessage(update)); } return HandleResult::Success; case mtpc_ping: { if (badTime) return HandleResult::Ignored; MTPPing msg; msg.read(from, end); DEBUG_LOG(("Message Info: ping received, ping_id: %1, sending pong...").arg(msg.vping_id.v)); emit sendPongAsync(msgId, msg.vping_id.v); } return HandleResult::Success; case mtpc_pong: { MTPPong msg; msg.read(from, end); const auto &data(msg.c_pong()); DEBUG_LOG(("Message Info: pong received, msg_id: %1, ping_id: %2").arg(data.vmsg_id.v).arg(data.vping_id.v)); if (!wasSent(data.vmsg_id.v)) { DEBUG_LOG(("Message Error: such msg_id %1 ping_id %2 was not sent recently").arg(data.vmsg_id.v).arg(data.vping_id.v)); return HandleResult::Ignored; } if (data.vping_id.v == _pingId) { _pingId = 0; } else { DEBUG_LOG(("Message Info: just pong...")); } QVector<MTPlong> ids(1, data.vmsg_id); if (badTime) { if (requestsFixTimeSalt(ids, serverTime, serverSalt)) { badTime = false; } else { return HandleResult::Ignored; } } requestsAcked(ids, true); } return HandleResult::Success; } } catch (Exception &) { return HandleResult::RestartConnection; } if (badTime) { DEBUG_LOG(("Message Error: bad time in updates cons, must create new session")); return HandleResult::ResetSession; } if (_dcType == DcType::Regular) { mtpBuffer update(end - from); if (end > from) memcpy(update.data(), from, (end - from) * sizeof(mtpPrime)); // Notify main process about the new updates. QWriteLocker locker(sessionData->haveReceivedMutex()); sessionData->haveReceivedUpdates().push_back(SerializedMessage(update)); if (cons != mtpc_updatesTooLong && cons != mtpc_updateShortMessage && cons != mtpc_updateShortChatMessage && cons != mtpc_updateShortSentMessage && cons != mtpc_updateShort && cons != mtpc_updatesCombined && cons != mtpc_updates) { // Maybe some new unknown update? LOG(("Message Error: unknown constructor %1").arg(cons)); } } else { LOG(("Message Error: unexpected updates in dcType: %1").arg(static_cast<int>(_dcType))); } return HandleResult::Success; } mtpBuffer ConnectionPrivate::ungzip(const mtpPrime *from, const mtpPrime *end) const { MTPstring packed; packed.read(from, end); // read packed string as serialized mtp string type uint32 packedLen = packed.v.size(), unpackedChunk = packedLen, unpackedLen = 0; mtpBuffer result; // * 4 because of mtpPrime type result.resize(0); z_stream stream; stream.zalloc = 0; stream.zfree = 0; stream.opaque = 0; stream.avail_in = 0; stream.next_in = 0; int res = inflateInit2(&stream, 16 + MAX_WBITS); if (res != Z_OK) { LOG(("RPC Error: could not init zlib stream, code: %1").arg(res)); return result; } stream.avail_in = packedLen; stream.next_in = reinterpret_cast<Bytef*>(packed.v.data()); stream.avail_out = 0; while (!stream.avail_out) { result.resize(result.size() + unpackedChunk); stream.avail_out = unpackedChunk * sizeof(mtpPrime); stream.next_out = (Bytef*)&result[result.size() - unpackedChunk]; int res = inflate(&stream, Z_NO_FLUSH); if (res != Z_OK && res != Z_STREAM_END) { inflateEnd(&stream); LOG(("RPC Error: could not unpack gziped data, code: %1").arg(res)); DEBUG_LOG(("RPC Error: bad gzip: %1").arg(Logs::mb(packed.v.constData(), packedLen).str())); return mtpBuffer(); } } if (stream.avail_out & 0x03) { uint32 badSize = result.size() * sizeof(mtpPrime) - stream.avail_out; LOG(("RPC Error: bad length of unpacked data %1").arg(badSize)); DEBUG_LOG(("RPC Error: bad unpacked data %1").arg(Logs::mb(result.data(), badSize).str())); return mtpBuffer(); } result.resize(result.size() - (stream.avail_out >> 2)); inflateEnd(&stream); if (!result.size()) { LOG(("RPC Error: bad length of unpacked data 0")); } return result; } bool ConnectionPrivate::requestsFixTimeSalt(const QVector<MTPlong> &ids, int32 serverTime, uint64 serverSalt) { uint32 idsCount = ids.size(); for (uint32 i = 0; i < idsCount; ++i) { if (wasSent(ids[i].v)) {// found such msg_id in recent acked requests or in recent sent requests if (serverSalt) sessionData->setSalt(serverSalt); unixtimeSet(serverTime, true); return true; } } return false; } void ConnectionPrivate::requestsAcked(const QVector<MTPlong> &ids, bool byResponse) { uint32 idsCount = ids.size(); DEBUG_LOG(("Message Info: requests acked, ids %1").arg(LogIdsVector(ids))); auto clearedBecauseTooOld = std::vector<RPCCallbackClear>(); QVector<MTPlong> toAckMore; { QWriteLocker locker1(sessionData->wereAckedMutex()); mtpRequestIdsMap &wereAcked(sessionData->wereAckedMap()); { QWriteLocker locker2(sessionData->haveSentMutex()); mtpRequestMap &haveSent(sessionData->haveSentMap()); for (uint32 i = 0; i < idsCount; ++i) { mtpMsgId msgId = ids[i].v; mtpRequestMap::iterator req = haveSent.find(msgId); if (req != haveSent.cend()) { if (!req.value()->msDate) { DEBUG_LOG(("Message Info: container ack received, msgId %1").arg(ids[i].v)); uint32 inContCount = ((*req)->size() - 8) / 2; const mtpMsgId *inContId = (const mtpMsgId *)(req.value()->constData() + 8); toAckMore.reserve(toAckMore.size() + inContCount); for (uint32 j = 0; j < inContCount; ++j) { toAckMore.push_back(MTP_long(*(inContId++))); } haveSent.erase(req); } else { mtpRequestId reqId = req.value()->requestId; bool moveToAcked = byResponse; if (!moveToAcked) { // ignore ACK, if we need a response (if we have a handler) moveToAcked = !_instance->hasCallbacks(reqId); } if (moveToAcked) { wereAcked.insert(msgId, reqId); haveSent.erase(req); } else { DEBUG_LOG(("Message Info: ignoring ACK for msgId %1 because request %2 requires a response").arg(msgId).arg(reqId)); } } } else { DEBUG_LOG(("Message Info: msgId %1 was not found in recent sent, while acking requests, searching in resend...").arg(msgId)); QWriteLocker locker3(sessionData->toResendMutex()); mtpRequestIdsMap &toResend(sessionData->toResendMap()); mtpRequestIdsMap::iterator reqIt = toResend.find(msgId); if (reqIt != toResend.cend()) { mtpRequestId reqId = reqIt.value(); bool moveToAcked = byResponse; if (!moveToAcked) { // ignore ACK, if we need a response (if we have a handler) moveToAcked = !_instance->hasCallbacks(reqId); } if (moveToAcked) { QWriteLocker locker4(sessionData->toSendMutex()); mtpPreRequestMap &toSend(sessionData->toSendMap()); mtpPreRequestMap::iterator req = toSend.find(reqId); if (req != toSend.cend()) { wereAcked.insert(msgId, req.value()->requestId); if (req.value()->requestId != reqId) { DEBUG_LOG(("Message Error: for msgId %1 found resent request, requestId %2, contains requestId %3").arg(msgId).arg(reqId).arg(req.value()->requestId)); } else { DEBUG_LOG(("Message Info: acked msgId %1 that was prepared to resend, requestId %2").arg(msgId).arg(reqId)); } toSend.erase(req); } else { DEBUG_LOG(("Message Info: msgId %1 was found in recent resent, requestId %2 was not found in prepared to send").arg(msgId)); } toResend.erase(reqIt); } else { DEBUG_LOG(("Message Info: ignoring ACK for msgId %1 because request %2 requires a response").arg(msgId).arg(reqId)); } } else { DEBUG_LOG(("Message Info: msgId %1 was not found in recent resent either").arg(msgId)); } } } } uint32 ackedCount = wereAcked.size(); if (ackedCount > MTPIdsBufferSize) { DEBUG_LOG(("Message Info: removing some old acked sent msgIds %1").arg(ackedCount - MTPIdsBufferSize)); clearedBecauseTooOld.reserve(ackedCount - MTPIdsBufferSize); while (ackedCount-- > MTPIdsBufferSize) { auto i = wereAcked.begin(); clearedBecauseTooOld.push_back(RPCCallbackClear( i.key(), RPCError::TimeoutError)); wereAcked.erase(i); } } } if (!clearedBecauseTooOld.empty()) { _instance->clearCallbacksDelayed(std::move(clearedBecauseTooOld)); } if (toAckMore.size()) { requestsAcked(toAckMore); } } void ConnectionPrivate::handleMsgsStates(const QVector<MTPlong> &ids, const QByteArray &states, QVector<MTPlong> &acked) { uint32 idsCount = ids.size(); if (!idsCount) { DEBUG_LOG(("Message Info: void ids vector in handleMsgsStates()")); return; } if (states.size() < idsCount) { LOG(("Message Error: got less states than required ids count.")); return; } acked.reserve(acked.size() + idsCount); for (uint32 i = 0, count = idsCount; i < count; ++i) { char state = states[i]; uint64 requestMsgId = ids[i].v; { QReadLocker locker(sessionData->haveSentMutex()); const mtpRequestMap &haveSent(sessionData->haveSentMap()); mtpRequestMap::const_iterator haveSentEnd = haveSent.cend(); if (haveSent.find(requestMsgId) == haveSentEnd) { DEBUG_LOG(("Message Info: state was received for msgId %1, but request is not found, looking in resent requests...").arg(requestMsgId)); QWriteLocker locker2(sessionData->toResendMutex()); mtpRequestIdsMap &toResend(sessionData->toResendMap()); mtpRequestIdsMap::iterator reqIt = toResend.find(requestMsgId); if (reqIt != toResend.cend()) { if ((state & 0x07) != 0x04) { // was received DEBUG_LOG(("Message Info: state was received for msgId %1, state %2, already resending in container").arg(requestMsgId).arg((int32)state)); } else { DEBUG_LOG(("Message Info: state was received for msgId %1, state %2, ack, cancelling resend").arg(requestMsgId).arg((int32)state)); acked.push_back(MTP_long(requestMsgId)); // will remove from resend in requestsAcked } } else { DEBUG_LOG(("Message Info: msgId %1 was not found in recent resent either").arg(requestMsgId)); } continue; } } if ((state & 0x07) != 0x04) { // was received DEBUG_LOG(("Message Info: state was received for msgId %1, state %2, resending in container").arg(requestMsgId).arg((int32)state)); resend(requestMsgId, 10, true); } else { DEBUG_LOG(("Message Info: state was received for msgId %1, state %2, ack").arg(requestMsgId).arg((int32)state)); acked.push_back(MTP_long(requestMsgId)); } } } void ConnectionPrivate::resend(quint64 msgId, qint64 msCanWait, bool forceContainer, bool sendMsgStateInfo) { if (msgId == _pingMsgId) return; emit resendAsync(msgId, msCanWait, forceContainer, sendMsgStateInfo); } void ConnectionPrivate::resendMany(QVector<quint64> msgIds, qint64 msCanWait, bool forceContainer, bool sendMsgStateInfo) { for (int32 i = 0, l = msgIds.size(); i < l; ++i) { if (msgIds.at(i) == _pingMsgId) { msgIds.remove(i); --l; } } emit resendManyAsync(msgIds, msCanWait, forceContainer, sendMsgStateInfo); } void ConnectionPrivate::onConnected4() { _waitForConnected = MTPMinConnectDelay; _waitForConnectedTimer.stop(); _waitForIPv4Timer.stop(); QReadLocker lockFinished(&sessionDataMutex); if (!sessionData) return; disconnect(_conn4, SIGNAL(connected()), this, SLOT(onConnected4())); if (!_conn4->isConnected()) { LOG(("Connection Error: not connected in onConnected4(), state: %1").arg(_conn4->debugState())); lockFinished.unlock(); return restart(); } _conn = _conn4; destroyConn(&_conn6); DEBUG_LOG(("MTP Info: connection through IPv4 succeed.")); lockFinished.unlock(); updateAuthKey(); } void ConnectionPrivate::onConnected6() { _waitForConnected = MTPMinConnectDelay; _waitForConnectedTimer.stop(); QReadLocker lockFinished(&sessionDataMutex); if (!sessionData) return; disconnect(_conn6, SIGNAL(connected()), this, SLOT(onConnected6())); if (!_conn6->isConnected()) { LOG(("Connection Error: not connected in onConnected(), state: %1").arg(_conn6->debugState())); lockFinished.unlock(); return restart(); } DEBUG_LOG(("MTP Info: connection through IPv6 succeed, waiting IPv4 for %1ms.").arg(MTPIPv4ConnectionWaitTimeout)); _waitForIPv4Timer.start(MTPIPv4ConnectionWaitTimeout); } void ConnectionPrivate::onDisconnected4() { if (_conn && _conn == _conn6) return; // disconnected the unused if (_conn || !_conn6) { destroyConn(); restart(); } else { destroyConn(&_conn4); } } void ConnectionPrivate::onDisconnected6() { if (_conn && _conn == _conn4) return; // disconnected the unused if (_conn || !_conn4) { destroyConn(); restart(); } else { destroyConn(&_conn6); } } void ConnectionPrivate::updateAuthKey() { QReadLocker lockFinished(&sessionDataMutex); if (!sessionData || !_conn) return; DEBUG_LOG(("AuthKey Info: Connection updating key from Session, dc %1").arg(_shiftedDcId)); uint64 newKeyId = 0; { ReadLockerAttempt lock(sessionData->keyMutex()); if (!lock) { DEBUG_LOG(("MTP Info: could not lock auth_key for read, waiting signal emit")); clearMessages(); keyId = newKeyId; return; // some other connection is getting key } auto key = sessionData->getKey(); newKeyId = key ? key->keyId() : 0; } if (keyId != newKeyId) { clearMessages(); keyId = newKeyId; } DEBUG_LOG(("AuthKey Info: Connection update key from Session, dc %1 result: %2").arg(_shiftedDcId).arg(Logs::mb(&keyId, sizeof(keyId)).str())); if (keyId) { return authKeyCreated(); } DEBUG_LOG(("AuthKey Info: No key in updateAuthKey(), will be creating auth_key")); lockKey(); auto &key = sessionData->getKey(); if (key) { if (keyId != key->keyId()) clearMessages(); keyId = key->keyId(); unlockKey(); return authKeyCreated(); } else if (_instance->isKeysDestroyer()) { // We are here to destroy an old key, so we're done. LOG(("MTP Error: No key %1 in updateAuthKey() for destroying.").arg(_shiftedDcId)); emit _instance->keyDestroyed(_shiftedDcId); return; } _authKeyData = std::make_unique<ConnectionPrivate::AuthKeyCreateData>(); _authKeyStrings = std::make_unique<ConnectionPrivate::AuthKeyCreateStrings>(); _authKeyData->req_num = 0; _authKeyData->nonce = rand_value<MTPint128>(); MTPReq_pq_multi req_pq; req_pq.vnonce = _authKeyData->nonce; connect(_conn, SIGNAL(receivedData()), this, SLOT(pqAnswered())); DEBUG_LOG(("AuthKey Info: sending Req_pq...")); lockFinished.unlock(); sendRequestNotSecure(req_pq); } void ConnectionPrivate::clearMessages() { if (keyId && keyId != kRecreateKeyId && _conn) { _conn->received().clear(); } } void ConnectionPrivate::pqAnswered() { disconnect(_conn, SIGNAL(receivedData()), this, SLOT(pqAnswered())); DEBUG_LOG(("AuthKey Info: receiving Req_pq answer...")); MTPReq_pq::ResponseType res_pq; if (!readResponseNotSecure(res_pq)) { return restart(); } auto &res_pq_data = res_pq.c_resPQ(); if (res_pq_data.vnonce != _authKeyData->nonce) { LOG(("AuthKey Error: received nonce <> sent nonce (in res_pq)!")); DEBUG_LOG(("AuthKey Error: received nonce: %1, sent nonce: %2").arg(Logs::mb(&res_pq_data.vnonce, 16).str()).arg(Logs::mb(&_authKeyData->nonce, 16).str())); return restart(); } auto rsaKey = internal::RSAPublicKey(); if (!_instance->dcOptions()->getDcRSAKey(bareDcId(_shiftedDcId), res_pq.c_resPQ().vserver_public_key_fingerprints.v, &rsaKey)) { if (_dcType == DcType::Cdn) { LOG(("Warning: CDN public RSA key not found")); requestCDNConfig(); return; } LOG(("AuthKey Error: could not choose public RSA key")); return restart(); } Assert(rsaKey.isValid()); _authKeyData->server_nonce = res_pq_data.vserver_nonce; _authKeyData->new_nonce = rand_value<MTPint256>(); auto &pq = res_pq_data.vpq.v; auto p = QByteArray(); auto q = QByteArray(); if (!MTP::internal::parsePQ(pq, p, q)) { LOG(("AuthKey Error: could not factor pq!")); DEBUG_LOG(("AuthKey Error: problematic pq: %1").arg(Logs::mb(pq.constData(), pq.length()).str())); return restart(); } auto p_q_inner = MTP_p_q_inner_data(res_pq_data.vpq, MTP_bytes(std::move(p)), MTP_bytes(std::move(q)), _authKeyData->nonce, _authKeyData->server_nonce, _authKeyData->new_nonce); auto dhEncString = encryptPQInnerRSA(p_q_inner, rsaKey); if (dhEncString.empty()) { return restart(); } connect(_conn, SIGNAL(receivedData()), this, SLOT(dhParamsAnswered())); DEBUG_LOG(("AuthKey Info: sending Req_DH_params...")); MTPReq_DH_params req_DH_params; req_DH_params.vnonce = _authKeyData->nonce; req_DH_params.vserver_nonce = _authKeyData->server_nonce; req_DH_params.vpublic_key_fingerprint = MTP_long(rsaKey.getFingerPrint()); req_DH_params.vp = p_q_inner.c_p_q_inner_data().vp; req_DH_params.vq = p_q_inner.c_p_q_inner_data().vq; req_DH_params.vencrypted_data = MTP_bytes(dhEncString); sendRequestNotSecure(req_DH_params); } base::byte_vector ConnectionPrivate::encryptPQInnerRSA(const MTPP_Q_inner_data &data, const MTP::internal::RSAPublicKey &key) { auto p_q_inner_size = data.innerLength(); auto encSize = (p_q_inner_size >> 2) + 6; if (encSize >= 65) { auto tmp = mtpBuffer(); tmp.reserve(encSize); data.write(tmp); LOG(("AuthKey Error: too large data for RSA encrypt, size %1").arg(encSize * sizeof(mtpPrime))); DEBUG_LOG(("AuthKey Error: bad data for RSA encrypt %1").arg(Logs::mb(&tmp[0], tmp.size() * 4).str())); return base::byte_vector(); // can't be 255-byte string } auto encBuffer = mtpBuffer(); encBuffer.reserve(65); // 260 bytes encBuffer.resize(6); encBuffer[0] = 0; data.write(encBuffer); hashSha1(&encBuffer[6], p_q_inner_size, &encBuffer[1]); if (encSize < 65) { encBuffer.resize(65); memset_rand(&encBuffer[encSize], (65 - encSize) * sizeof(mtpPrime)); } auto bytes = gsl::as_bytes(gsl::make_span(encBuffer)); auto bytesToEncrypt = bytes.subspan(3, 256); return key.encrypt(bytesToEncrypt); } void ConnectionPrivate::dhParamsAnswered() { disconnect(_conn, SIGNAL(receivedData()), this, SLOT(dhParamsAnswered())); DEBUG_LOG(("AuthKey Info: receiving Req_DH_params answer...")); MTPReq_DH_params::ResponseType res_DH_params; if (!readResponseNotSecure(res_DH_params)) { return restart(); } switch (res_DH_params.type()) { case mtpc_server_DH_params_ok: { const auto &encDH(res_DH_params.c_server_DH_params_ok()); if (encDH.vnonce != _authKeyData->nonce) { LOG(("AuthKey Error: received nonce <> sent nonce (in server_DH_params_ok)!")); DEBUG_LOG(("AuthKey Error: received nonce: %1, sent nonce: %2").arg(Logs::mb(&encDH.vnonce, 16).str()).arg(Logs::mb(&_authKeyData->nonce, 16).str())); return restart(); } if (encDH.vserver_nonce != _authKeyData->server_nonce) { LOG(("AuthKey Error: received server_nonce <> sent server_nonce (in server_DH_params_ok)!")); DEBUG_LOG(("AuthKey Error: received server_nonce: %1, sent server_nonce: %2").arg(Logs::mb(&encDH.vserver_nonce, 16).str()).arg(Logs::mb(&_authKeyData->server_nonce, 16).str())); return restart(); } auto &encDHStr = encDH.vencrypted_answer.v; uint32 encDHLen = encDHStr.length(), encDHBufLen = encDHLen >> 2; if ((encDHLen & 0x03) || encDHBufLen < 6) { LOG(("AuthKey Error: bad encrypted data length %1 (in server_DH_params_ok)!").arg(encDHLen)); DEBUG_LOG(("AuthKey Error: received encrypted data %1").arg(Logs::mb(encDHStr.constData(), encDHLen).str())); return restart(); } uint32 nlen = _authKeyData->new_nonce.innerLength(), slen = _authKeyData->server_nonce.innerLength(); uchar tmp_aes[1024], sha1ns[20], sha1sn[20], sha1nn[20]; memcpy(tmp_aes, &_authKeyData->new_nonce, nlen); memcpy(tmp_aes + nlen, &_authKeyData->server_nonce, slen); memcpy(tmp_aes + nlen + slen, &_authKeyData->new_nonce, nlen); memcpy(tmp_aes + nlen + slen + nlen, &_authKeyData->new_nonce, nlen); hashSha1(tmp_aes, nlen + slen, sha1ns); hashSha1(tmp_aes + nlen, nlen + slen, sha1sn); hashSha1(tmp_aes + nlen + slen, nlen + nlen, sha1nn); mtpBuffer decBuffer; decBuffer.resize(encDHBufLen); memcpy(_authKeyData->aesKey, sha1ns, 20); memcpy(_authKeyData->aesKey + 20, sha1sn, 12); memcpy(_authKeyData->aesIV, sha1sn + 12, 8); memcpy(_authKeyData->aesIV + 8, sha1nn, 20); memcpy(_authKeyData->aesIV + 28, &_authKeyData->new_nonce, 4); aesIgeDecryptRaw(encDHStr.constData(), &decBuffer[0], encDHLen, _authKeyData->aesKey, _authKeyData->aesIV); const mtpPrime *from(&decBuffer[5]), *to(from), *end(from + (encDHBufLen - 5)); MTPServer_DH_inner_data dh_inner; dh_inner.read(to, end); const auto &dh_inner_data(dh_inner.c_server_DH_inner_data()); if (dh_inner_data.vnonce != _authKeyData->nonce) { LOG(("AuthKey Error: received nonce <> sent nonce (in server_DH_inner_data)!")); DEBUG_LOG(("AuthKey Error: received nonce: %1, sent nonce: %2").arg(Logs::mb(&dh_inner_data.vnonce, 16).str()).arg(Logs::mb(&_authKeyData->nonce, 16).str())); return restart(); } if (dh_inner_data.vserver_nonce != _authKeyData->server_nonce) { LOG(("AuthKey Error: received server_nonce <> sent server_nonce (in server_DH_inner_data)!")); DEBUG_LOG(("AuthKey Error: received server_nonce: %1, sent server_nonce: %2").arg(Logs::mb(&dh_inner_data.vserver_nonce, 16).str()).arg(Logs::mb(&_authKeyData->server_nonce, 16).str())); return restart(); } uchar sha1Buffer[20]; if (memcmp(&decBuffer[0], hashSha1(&decBuffer[5], (to - from) * sizeof(mtpPrime), sha1Buffer), 20)) { LOG(("AuthKey Error: sha1 hash of encrypted part did not match!")); DEBUG_LOG(("AuthKey Error: sha1 did not match, server_nonce: %1, new_nonce %2, encrypted data %3").arg(Logs::mb(&_authKeyData->server_nonce, 16).str()).arg(Logs::mb(&_authKeyData->new_nonce, 16).str()).arg(Logs::mb(encDHStr.constData(), encDHLen).str())); return restart(); } unixtimeSet(dh_inner_data.vserver_time.v); // check that dhPrime and (dhPrime - 1) / 2 are really prime if (!IsPrimeAndGood(bytesFromMTP(dh_inner_data.vdh_prime), dh_inner_data.vg.v)) { LOG(("AuthKey Error: bad dh_prime primality!")); return restart(); } _authKeyStrings->dh_prime = byteVectorFromMTP(dh_inner_data.vdh_prime); _authKeyData->g = dh_inner_data.vg.v; _authKeyStrings->g_a = byteVectorFromMTP(dh_inner_data.vg_a); _authKeyData->retry_id = MTP_long(0); _authKeyData->retries = 0; } return dhClientParamsSend(); case mtpc_server_DH_params_fail: { const auto &encDH(res_DH_params.c_server_DH_params_fail()); if (encDH.vnonce != _authKeyData->nonce) { LOG(("AuthKey Error: received nonce <> sent nonce (in server_DH_params_fail)!")); DEBUG_LOG(("AuthKey Error: received nonce: %1, sent nonce: %2").arg(Logs::mb(&encDH.vnonce, 16).str()).arg(Logs::mb(&_authKeyData->nonce, 16).str())); return restart(); } if (encDH.vserver_nonce != _authKeyData->server_nonce) { LOG(("AuthKey Error: received server_nonce <> sent server_nonce (in server_DH_params_fail)!")); DEBUG_LOG(("AuthKey Error: received server_nonce: %1, sent server_nonce: %2").arg(Logs::mb(&encDH.vserver_nonce, 16).str()).arg(Logs::mb(&_authKeyData->server_nonce, 16).str())); return restart(); } uchar sha1Buffer[20]; if (encDH.vnew_nonce_hash != *(MTPint128*)(hashSha1(&_authKeyData->new_nonce, 32, sha1Buffer) + 1)) { LOG(("AuthKey Error: received new_nonce_hash did not match!")); DEBUG_LOG(("AuthKey Error: received new_nonce_hash: %1, new_nonce: %2").arg(Logs::mb(&encDH.vnew_nonce_hash, 16).str()).arg(Logs::mb(&_authKeyData->new_nonce, 32).str())); return restart(); } LOG(("AuthKey Error: server_DH_params_fail received!")); } return restart(); } LOG(("AuthKey Error: unknown server_DH_params received, typeId = %1").arg(res_DH_params.type())); return restart(); } void ConnectionPrivate::dhClientParamsSend() { if (++_authKeyData->retries > 5) { LOG(("AuthKey Error: could not create auth_key for %1 retries").arg(_authKeyData->retries - 1)); return restart(); } // gen rand 'b' auto randomSeed = std::array<gsl::byte, ModExpFirst::kRandomPowerSize>(); openssl::FillRandom(randomSeed); auto g_b_data = CreateModExp(_authKeyData->g, _authKeyStrings->dh_prime, randomSeed); if (g_b_data.modexp.empty()) { LOG(("AuthKey Error: could not generate good g_b.")); return restart(); } auto computedAuthKey = CreateAuthKey(_authKeyStrings->g_a, g_b_data.randomPower, _authKeyStrings->dh_prime); if (computedAuthKey.empty()) { LOG(("AuthKey Error: could not generate auth_key.")); return restart(); } AuthKey::FillData(_authKeyStrings->auth_key, computedAuthKey); // count auth_key hashes - parts of sha1(auth_key) auto auth_key_sha = hashSha1(_authKeyStrings->auth_key.data(), _authKeyStrings->auth_key.size()); memcpy(&_authKeyData->auth_key_aux_hash, auth_key_sha.data(), 8); memcpy(&_authKeyData->auth_key_hash, auth_key_sha.data() + 12, 8); auto client_dh_inner = MTP_client_DH_inner_data(_authKeyData->nonce, _authKeyData->server_nonce, _authKeyData->retry_id, MTP_bytes(g_b_data.modexp)); auto sdhEncString = encryptClientDHInner(client_dh_inner); connect(_conn, SIGNAL(receivedData()), this, SLOT(dhClientParamsAnswered())); MTPSet_client_DH_params req_client_DH_params; req_client_DH_params.vnonce = _authKeyData->nonce; req_client_DH_params.vserver_nonce = _authKeyData->server_nonce; req_client_DH_params.vencrypted_data = MTP_string(std::move(sdhEncString)); DEBUG_LOG(("AuthKey Info: sending Req_client_DH_params...")); sendRequestNotSecure(req_client_DH_params); } std::string ConnectionPrivate::encryptClientDHInner(const MTPClient_DH_Inner_Data &data) { auto client_dh_inner_size = data.innerLength(); auto encSize = (client_dh_inner_size >> 2) + 5; auto encFullSize = encSize; if (encSize & 0x03) { encFullSize += 4 - (encSize & 0x03); } auto encBuffer = mtpBuffer(); encBuffer.reserve(encFullSize); encBuffer.resize(5); data.write(encBuffer); hashSha1(&encBuffer[5], client_dh_inner_size, &encBuffer[0]); if (encSize < encFullSize) { encBuffer.resize(encFullSize); memset_rand(&encBuffer[encSize], (encFullSize - encSize) * sizeof(mtpPrime)); } auto sdhEncString = std::string(encFullSize * 4, ' '); aesIgeEncryptRaw(&encBuffer[0], &sdhEncString[0], encFullSize * sizeof(mtpPrime), _authKeyData->aesKey, _authKeyData->aesIV); return sdhEncString; } void ConnectionPrivate::dhClientParamsAnswered() { QReadLocker lockFinished(&sessionDataMutex); if (!sessionData) return; disconnect(_conn, SIGNAL(receivedData()), this, SLOT(dhClientParamsAnswered())); DEBUG_LOG(("AuthKey Info: receiving Req_client_DH_params answer...")); MTPSet_client_DH_params::ResponseType res_client_DH_params; if (!readResponseNotSecure(res_client_DH_params)) { lockFinished.unlock(); return restart(); } switch (res_client_DH_params.type()) { case mtpc_dh_gen_ok: { const auto &resDH(res_client_DH_params.c_dh_gen_ok()); if (resDH.vnonce != _authKeyData->nonce) { LOG(("AuthKey Error: received nonce <> sent nonce (in dh_gen_ok)!")); DEBUG_LOG(("AuthKey Error: received nonce: %1, sent nonce: %2").arg(Logs::mb(&resDH.vnonce, 16).str()).arg(Logs::mb(&_authKeyData->nonce, 16).str())); lockFinished.unlock(); return restart(); } if (resDH.vserver_nonce != _authKeyData->server_nonce) { LOG(("AuthKey Error: received server_nonce <> sent server_nonce (in dh_gen_ok)!")); DEBUG_LOG(("AuthKey Error: received server_nonce: %1, sent server_nonce: %2").arg(Logs::mb(&resDH.vserver_nonce, 16).str()).arg(Logs::mb(&_authKeyData->server_nonce, 16).str())); lockFinished.unlock(); return restart(); } _authKeyData->new_nonce_buf[32] = 1; uchar sha1Buffer[20]; if (resDH.vnew_nonce_hash1 != *(MTPint128*)(hashSha1(_authKeyData->new_nonce_buf, 41, sha1Buffer) + 1)) { LOG(("AuthKey Error: received new_nonce_hash1 did not match!")); DEBUG_LOG(("AuthKey Error: received new_nonce_hash1: %1, new_nonce_buf: %2").arg(Logs::mb(&resDH.vnew_nonce_hash1, 16).str()).arg(Logs::mb(_authKeyData->new_nonce_buf, 41).str())); lockFinished.unlock(); return restart(); } uint64 salt1 = _authKeyData->new_nonce.l.l, salt2 = _authKeyData->server_nonce.l, serverSalt = salt1 ^ salt2; sessionData->setSalt(serverSalt); auto authKey = std::make_shared<AuthKey>(AuthKey::Type::Generated, bareDcId(_shiftedDcId), _authKeyStrings->auth_key); DEBUG_LOG(("AuthKey Info: auth key gen succeed, id: %1, server salt: %2").arg(authKey->keyId()).arg(serverSalt)); sessionData->owner()->notifyKeyCreated(std::move(authKey)); // slot will call authKeyCreated() sessionData->clear(_instance); unlockKey(); } return; case mtpc_dh_gen_retry: { const auto &resDH(res_client_DH_params.c_dh_gen_retry()); if (resDH.vnonce != _authKeyData->nonce) { LOG(("AuthKey Error: received nonce <> sent nonce (in dh_gen_retry)!")); DEBUG_LOG(("AuthKey Error: received nonce: %1, sent nonce: %2").arg(Logs::mb(&resDH.vnonce, 16).str()).arg(Logs::mb(&_authKeyData->nonce, 16).str())); lockFinished.unlock(); return restart(); } if (resDH.vserver_nonce != _authKeyData->server_nonce) { LOG(("AuthKey Error: received server_nonce <> sent server_nonce (in dh_gen_retry)!")); DEBUG_LOG(("AuthKey Error: received server_nonce: %1, sent server_nonce: %2").arg(Logs::mb(&resDH.vserver_nonce, 16).str()).arg(Logs::mb(&_authKeyData->server_nonce, 16).str())); lockFinished.unlock(); return restart(); } _authKeyData->new_nonce_buf[32] = 2; uchar sha1Buffer[20]; if (resDH.vnew_nonce_hash2 != *(MTPint128*)(hashSha1(_authKeyData->new_nonce_buf, 41, sha1Buffer) + 1)) { LOG(("AuthKey Error: received new_nonce_hash2 did not match!")); DEBUG_LOG(("AuthKey Error: received new_nonce_hash2: %1, new_nonce_buf: %2").arg(Logs::mb(&resDH.vnew_nonce_hash2, 16).str()).arg(Logs::mb(_authKeyData->new_nonce_buf, 41).str())); lockFinished.unlock(); return restart(); } _authKeyData->retry_id = _authKeyData->auth_key_aux_hash; } return dhClientParamsSend(); case mtpc_dh_gen_fail: { const auto &resDH(res_client_DH_params.c_dh_gen_fail()); if (resDH.vnonce != _authKeyData->nonce) { LOG(("AuthKey Error: received nonce <> sent nonce (in dh_gen_fail)!")); DEBUG_LOG(("AuthKey Error: received nonce: %1, sent nonce: %2").arg(Logs::mb(&resDH.vnonce, 16).str()).arg(Logs::mb(&_authKeyData->nonce, 16).str())); lockFinished.unlock(); return restart(); } if (resDH.vserver_nonce != _authKeyData->server_nonce) { LOG(("AuthKey Error: received server_nonce <> sent server_nonce (in dh_gen_fail)!")); DEBUG_LOG(("AuthKey Error: received server_nonce: %1, sent server_nonce: %2").arg(Logs::mb(&resDH.vserver_nonce, 16).str()).arg(Logs::mb(&_authKeyData->server_nonce, 16).str())); lockFinished.unlock(); return restart(); } _authKeyData->new_nonce_buf[32] = 3; uchar sha1Buffer[20]; if (resDH.vnew_nonce_hash3 != *(MTPint128*)(hashSha1(_authKeyData->new_nonce_buf, 41, sha1Buffer) + 1)) { LOG(("AuthKey Error: received new_nonce_hash3 did not match!")); DEBUG_LOG(("AuthKey Error: received new_nonce_hash3: %1, new_nonce_buf: %2").arg(Logs::mb(&resDH.vnew_nonce_hash3, 16).str()).arg(Logs::mb(_authKeyData->new_nonce_buf, 41).str())); lockFinished.unlock(); return restart(); } LOG(("AuthKey Error: dh_gen_fail received!")); } lockFinished.unlock(); return restart(); } LOG(("AuthKey Error: unknown set_client_DH_params_answer received, typeId = %1").arg(res_client_DH_params.type())); lockFinished.unlock(); return restart(); } void ConnectionPrivate::authKeyCreated() { clearAuthKeyData(); connect(_conn, SIGNAL(receivedData()), this, SLOT(handleReceived())); if (sessionData->getSalt()) { // else receive salt in bad_server_salt first, then try to send all the requests setState(ConnectedState); if (restarted) { emit resendAllAsync(); restarted = false; } } _pingIdToSend = rand_value<uint64>(); // get server_salt emit needToSendAsync(); } void ConnectionPrivate::clearAuthKeyData() { auto zeroMemory = [](base::byte_span bytes) { #ifdef Q_OS_WIN2 SecureZeroMemory(bytes.data(), bytes.size()); #else // Q_OS_WIN auto end = reinterpret_cast<char*>(bytes.data()) + bytes.size(); for (volatile auto p = reinterpret_cast<volatile char*>(bytes.data()); p != end; ++p) { *p = 0; } #endif // Q_OS_WIN }; if (_authKeyData) { zeroMemory(gsl::make_span(reinterpret_cast<gsl::byte*>(_authKeyData.get()), sizeof(AuthKeyCreateData))); _authKeyData.reset(); } if (_authKeyStrings) { if (!_authKeyStrings->dh_prime.empty()) { zeroMemory(_authKeyStrings->dh_prime); } if (!_authKeyStrings->g_a.empty()) { zeroMemory(_authKeyStrings->g_a); } zeroMemory(_authKeyStrings->auth_key); _authKeyStrings.reset(); } } void ConnectionPrivate::onError4(qint32 errorCode) { if (_conn && _conn == _conn6) return; // error in the unused if (errorCode == -429) { LOG(("Protocol Error: -429 flood code returned!")); } if (_conn || !_conn6) { handleError(errorCode); } else { destroyConn(&_conn4); } } void ConnectionPrivate::onError6(qint32 errorCode) { if (_conn && _conn == _conn4) return; // error in the unused if (errorCode == -429) { LOG(("Protocol Error: -429 flood code returned!")); } if (_conn || !_conn4) { handleError(errorCode); } else { destroyConn(&_conn6); } } void ConnectionPrivate::handleError(int errorCode) { destroyConn(); _waitForConnectedTimer.stop(); if (errorCode == -404) { if (_instance->isKeysDestroyer()) { LOG(("MTP Info: -404 error received on destroying key %1, assuming it is destroyed.").arg(_shiftedDcId)); emit _instance->keyDestroyed(_shiftedDcId); return; } else if (_dcType == DcType::Cdn) { LOG(("MTP Info: -404 error received in CDN dc %1, assuming it was destroyed, recreating.").arg(_shiftedDcId)); clearMessages(); keyId = kRecreateKeyId; return restart(); } } MTP_LOG(_shiftedDcId, ("Restarting after error in connection, error code: %1...").arg(errorCode)); return restart(); } void ConnectionPrivate::onReadyData() { } template <typename TRequest> void ConnectionPrivate::sendRequestNotSecure(const TRequest &request) { try { mtpBuffer buffer; uint32 requestSize = request.innerLength() >> 2; buffer.resize(0); buffer.reserve(8 + requestSize); buffer.push_back(0); // tcp packet len buffer.push_back(0); // tcp packet num buffer.push_back(0); buffer.push_back(0); buffer.push_back(_authKeyData->req_num); buffer.push_back(unixtime()); buffer.push_back(requestSize * 4); request.write(buffer); buffer.push_back(0); // tcp crc32 hash ++_authKeyData->msgs_sent; DEBUG_LOG(("AuthKey Info: sending request, size: %1, num: %2, time: %3").arg(requestSize).arg(_authKeyData->req_num).arg(buffer[5])); _conn->sendData(buffer); onSentSome(buffer.size() * sizeof(mtpPrime)); } catch (Exception &) { return restart(); } } template <typename TResponse> bool ConnectionPrivate::readResponseNotSecure(TResponse &response) { onReceivedSome(); try { if (_conn->received().empty()) { LOG(("AuthKey Error: trying to read response from empty received list")); return false; } auto buffer = std::move(_conn->received().front()); _conn->received().pop_front(); auto answer = buffer.constData(); auto len = buffer.size(); if (len < 5) { LOG(("AuthKey Error: bad request answer, len = %1").arg(len * sizeof(mtpPrime))); DEBUG_LOG(("AuthKey Error: answer bytes %1").arg(Logs::mb(answer, len * sizeof(mtpPrime)).str())); return false; } if (answer[0] != 0 || answer[1] != 0 || (((uint32)answer[2]) & 0x03) != 1/* || (unixtime() - answer[3] > 300) || (answer[3] - unixtime() > 60)*/) { // didnt sync time yet LOG(("AuthKey Error: bad request answer start (%1 %2 %3)").arg(answer[0]).arg(answer[1]).arg(answer[2])); DEBUG_LOG(("AuthKey Error: answer bytes %1").arg(Logs::mb(answer, len * sizeof(mtpPrime)).str())); return false; } uint32 answerLen = (uint32)answer[4]; if (answerLen != (len - 5) * sizeof(mtpPrime)) { LOG(("AuthKey Error: bad request answer %1 <> %2").arg(answerLen).arg((len - 5) * sizeof(mtpPrime))); DEBUG_LOG(("AuthKey Error: answer bytes %1").arg(Logs::mb(answer, len * sizeof(mtpPrime)).str())); return false; } const mtpPrime *from(answer + 5), *end(from + len - 5); response.read(from, end); } catch (Exception &) { return false; } return true; } bool ConnectionPrivate::sendRequest(mtpRequest &request, bool needAnyResponse, QReadLocker &lockFinished) { uint32 fullSize = request->size(); if (fullSize < 9) return false; auto messageSize = mtpRequestData::messageSize(request); if (messageSize < 5 || fullSize < messageSize + 4) return false; auto lock = ReadLockerAttempt(sessionData->keyMutex()); if (!lock) { DEBUG_LOG(("MTP Info: could not lock key for read in sendBuffer(), dc %1, restarting...").arg(_shiftedDcId)); lockFinished.unlock(); restart(); return false; } auto key = sessionData->getKey(); if (!key || key->keyId() != keyId) { DEBUG_LOG(("MTP Error: auth_key id for dc %1 changed").arg(_shiftedDcId)); lockFinished.unlock(); restart(); return false; } auto session = sessionData->getSession(); auto salt = sessionData->getSalt(); memcpy(request->data() + 0, &salt, 2 * sizeof(mtpPrime)); memcpy(request->data() + 2, &session, 2 * sizeof(mtpPrime)); auto from = request->constData() + 4; MTP_LOG(_shiftedDcId, ("Send: ") + mtpTextSerialize(from, from + messageSize)); #ifdef TDESKTOP_MTPROTO_OLD uint32 padding = fullSize - 4 - messageSize; uchar encryptedSHA[20]; MTPint128 &msgKey(*(MTPint128*)(encryptedSHA + 4)); hashSha1(request->constData(), (fullSize - padding) * sizeof(mtpPrime), encryptedSHA); mtpBuffer result; result.resize(9 + fullSize); *((uint64*)&result[2]) = keyId; *((MTPint128*)&result[4]) = msgKey; aesIgeEncrypt_oldmtp(request->constData(), &result[8], fullSize * sizeof(mtpPrime), key, msgKey); #else // TDESKTOP_MTPROTO_OLD uchar encryptedSHA256[32]; MTPint128 &msgKey(*(MTPint128*)(encryptedSHA256 + 8)); SHA256_CTX msgKeyLargeContext; SHA256_Init(&msgKeyLargeContext); SHA256_Update(&msgKeyLargeContext, key->partForMsgKey(true), 32); SHA256_Update(&msgKeyLargeContext, request->constData(), fullSize * sizeof(mtpPrime)); SHA256_Final(encryptedSHA256, &msgKeyLargeContext); mtpBuffer result; result.resize(9 + fullSize); *((uint64*)&result[2]) = keyId; *((MTPint128*)&result[4]) = msgKey; aesIgeEncrypt(request->constData(), &result[8], fullSize * sizeof(mtpPrime), key, msgKey); #endif // TDESKTOP_MTPROTO_OLD DEBUG_LOG(("MTP Info: sending request, size: %1, num: %2, time: %3").arg(fullSize + 6).arg((*request)[4]).arg((*request)[5])); _conn->setSentEncrypted(); _conn->sendData(result); if (needAnyResponse) { onSentSome(result.size() * sizeof(mtpPrime)); } return true; } mtpRequestId ConnectionPrivate::wasSent(mtpMsgId msgId) const { if (msgId == _pingMsgId) return mtpRequestId(0xFFFFFFFF); { QReadLocker locker(sessionData->haveSentMutex()); const mtpRequestMap &haveSent(sessionData->haveSentMap()); mtpRequestMap::const_iterator i = haveSent.constFind(msgId); if (i != haveSent.cend()) return i.value()->requestId ? i.value()->requestId : mtpRequestId(0xFFFFFFFF); } { QReadLocker locker(sessionData->toResendMutex()); const mtpRequestIdsMap &toResend(sessionData->toResendMap()); mtpRequestIdsMap::const_iterator i = toResend.constFind(msgId); if (i != toResend.cend()) return i.value(); } { QReadLocker locker(sessionData->wereAckedMutex()); const mtpRequestIdsMap &wereAcked(sessionData->wereAckedMap()); mtpRequestIdsMap::const_iterator i = wereAcked.constFind(msgId); if (i != wereAcked.cend()) return i.value(); } return 0; } void ConnectionPrivate::lockKey() { unlockKey(); sessionData->keyMutex()->lockForWrite(); myKeyLock = true; } void ConnectionPrivate::unlockKey() { if (myKeyLock) { myKeyLock = false; sessionData->keyMutex()->unlock(); } } ConnectionPrivate::~ConnectionPrivate() { clearAuthKeyData(); Assert(_finished && _conn == nullptr && _conn4 == nullptr && _conn6 == nullptr); } void ConnectionPrivate::stop() { QWriteLocker lockFinished(&sessionDataMutex); if (sessionData) { if (myKeyLock) { sessionData->owner()->notifyKeyCreated(AuthKeyPtr()); // release key lock, let someone else create it sessionData->keyMutex()->unlock(); myKeyLock = false; } sessionData = nullptr; } } } // namespace internal bool IsPrimeAndGood(base::const_byte_span primeBytes, int g) { return internal::IsPrimeAndGood(primeBytes, g); } ModExpFirst CreateModExp(int g, base::const_byte_span primeBytes, base::const_byte_span randomSeed) { return internal::CreateModExp(g, primeBytes, randomSeed); } std::vector<gsl::byte> CreateAuthKey(base::const_byte_span firstBytes, base::const_byte_span randomBytes, base::const_byte_span primeBytes) { return internal::CreateAuthKey(firstBytes, randomBytes, primeBytes); } } // namespace MTP ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/mtproto/connection.h�������������������������������������������0000664�0000000�0000000�00000016704�13262451251�0023447�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "mtproto/auth_key.h" #include "mtproto/dc_options.h" #include "core/single_timer.h" namespace MTP { class Instance; bool IsPrimeAndGood(base::const_byte_span primeBytes, int g); struct ModExpFirst { static constexpr auto kRandomPowerSize = 256; std::vector<gsl::byte> modexp; std::array<gsl::byte, kRandomPowerSize> randomPower; }; ModExpFirst CreateModExp(int g, base::const_byte_span primeBytes, base::const_byte_span randomSeed); std::vector<gsl::byte> CreateAuthKey(base::const_byte_span firstBytes, base::const_byte_span randomBytes, base::const_byte_span primeBytes); namespace internal { class AbstractConnection; class ConnectionPrivate; class SessionData; class RSAPublicKey; class Thread : public QThread { Q_OBJECT public: Thread() { static int ThreadCounter = 0; _threadIndex = ++ThreadCounter; } int getThreadIndex() const { return _threadIndex; } private: int _threadIndex = 0; }; class Connection { public: enum ConnectionType { TcpConnection, HttpConnection }; Connection(Instance *instance); void start(SessionData *data, ShiftedDcId shiftedDcId); void kill(); void waitTillFinish(); ~Connection(); static const int UpdateAlways = 666; int32 state() const; QString transport() const; private: Instance *_instance = nullptr; std::unique_ptr<QThread> thread; ConnectionPrivate *data = nullptr; }; class ConnectionPrivate : public QObject { Q_OBJECT public: ConnectionPrivate(Instance *instance, QThread *thread, Connection *owner, SessionData *data, ShiftedDcId shiftedDcId); ~ConnectionPrivate(); void stop(); int32 getShiftedDcId() const; int32 getState() const; QString transport() const; signals: void needToReceive(); void needToRestart(); void stateChanged(qint32 newState); void sessionResetDone(); void needToSendAsync(); void sendAnythingAsync(qint64 msWait); void sendHttpWaitAsync(); void sendPongAsync(quint64 msgId, quint64 pingId); void sendMsgsStateInfoAsync(quint64 msgId, QByteArray data); void resendAsync(quint64 msgId, qint64 msCanWait, bool forceContainer, bool sendMsgStateInfo); void resendManyAsync(QVector<quint64> msgIds, qint64 msCanWait, bool forceContainer, bool sendMsgStateInfo); void resendAllAsync(); void finished(internal::Connection *connection); public slots: void retryByTimer(); void restartNow(); void onPingSender(); void onPingSendForce(); void onWaitConnectedFailed(); void onWaitReceivedFailed(); void onWaitIPv4Failed(); void onOldConnection(); void onSentSome(uint64 size); void onReceivedSome(); void onReadyData(); void onConnected4(); void onConnected6(); void onDisconnected4(); void onDisconnected6(); void onError4(qint32 errorCode); void onError6(qint32 errorCode); // Auth key creation packet receive slots void pqAnswered(); void dhParamsAnswered(); void dhClientParamsAnswered(); // General packet receive slot, connected to conn->receivedData signal void handleReceived(); // Sessions signals, when we need to send something void tryToSend(); void updateAuthKey(); void onConfigLoaded(); void onCDNConfigLoaded(); private: void connectToServer(bool afterConfig = false); void doDisconnect(); void restart(); void finishAndDestroy(); void requestCDNConfig(); void handleError(int errorCode); void createConn(bool createIPv4, bool createIPv6); void destroyConn(AbstractConnection **conn = 0); // 0 - destory all mtpMsgId placeToContainer(mtpRequest &toSendRequest, mtpMsgId &bigMsgId, mtpMsgId *&haveSentArr, mtpRequest &req); mtpMsgId prepareToSend(mtpRequest &request, mtpMsgId currentLastId); mtpMsgId replaceMsgId(mtpRequest &request, mtpMsgId newId); bool sendRequest(mtpRequest &request, bool needAnyResponse, QReadLocker &lockFinished); mtpRequestId wasSent(mtpMsgId msgId) const; enum class HandleResult { Success, Ignored, RestartConnection, ResetSession, }; HandleResult handleOneReceived(const mtpPrime *from, const mtpPrime *end, uint64 msgId, int32 serverTime, uint64 serverSalt, bool badTime); mtpBuffer ungzip(const mtpPrime *from, const mtpPrime *end) const; void handleMsgsStates(const QVector<MTPlong> &ids, const QByteArray &states, QVector<MTPlong> &acked); void clearMessages(); bool setState(int32 state, int32 ifState = Connection::UpdateAlways); base::byte_vector encryptPQInnerRSA(const MTPP_Q_inner_data &data, const MTP::internal::RSAPublicKey &key); std::string encryptClientDHInner(const MTPClient_DH_Inner_Data &data); Instance *_instance = nullptr; DcType _dcType = DcType::Regular; mutable QReadWriteLock stateConnMutex; int32 _state = DisconnectedState; bool _needSessionReset = false; void resetSession(); ShiftedDcId _shiftedDcId = 0; Connection *_owner = nullptr; AbstractConnection *_conn = nullptr; AbstractConnection *_conn4 = nullptr; AbstractConnection *_conn6 = nullptr;; SingleTimer retryTimer; // exp retry timer int retryTimeout = 1; qint64 retryWillFinish; SingleTimer oldConnectionTimer; bool oldConnection = true; SingleTimer _waitForConnectedTimer, _waitForReceivedTimer, _waitForIPv4Timer; uint32 _waitForReceived, _waitForConnected; TimeMs firstSentAt = -1; QVector<MTPlong> ackRequestData, resendRequestData; // if badTime received - search for ids in sessionData->haveSent and sessionData->wereAcked and sync time/salt, return true if found bool requestsFixTimeSalt(const QVector<MTPlong> &ids, int32 serverTime, uint64 serverSalt); // remove msgs with such ids from sessionData->haveSent, add to sessionData->wereAcked void requestsAcked(const QVector<MTPlong> &ids, bool byResponse = false); mtpPingId _pingId = 0; mtpPingId _pingIdToSend = 0; TimeMs _pingSendAt = 0; mtpMsgId _pingMsgId = 0; SingleTimer _pingSender; void resend(quint64 msgId, qint64 msCanWait = 0, bool forceContainer = false, bool sendMsgStateInfo = false); void resendMany(QVector<quint64> msgIds, qint64 msCanWait = 0, bool forceContainer = false, bool sendMsgStateInfo = false); template <typename TRequest> void sendRequestNotSecure(const TRequest &request); template <typename TResponse> bool readResponseNotSecure(TResponse &response); bool restarted = false; bool _finished = false; uint64 keyId = 0; QReadWriteLock sessionDataMutex; SessionData *sessionData = nullptr; bool myKeyLock = false; void lockKey(); void unlockKey(); // Auth key creation fields and methods struct AuthKeyCreateData { AuthKeyCreateData() : new_nonce(*(MTPint256*)((uchar*)new_nonce_buf)) , auth_key_aux_hash(*(MTPlong*)((uchar*)new_nonce_buf + 33)) { } MTPint128 nonce, server_nonce; uchar new_nonce_buf[41] = { 0 }; // 32 bytes new_nonce + 1 check byte + 8 bytes of auth_key_aux_hash MTPint256 &new_nonce; MTPlong &auth_key_aux_hash; uint32 retries = 0; MTPlong retry_id; int32 g = 0; uchar aesKey[32] = { 0 }; uchar aesIV[32] = { 0 }; MTPlong auth_key_hash; uint32 req_num = 0; // sent not encrypted request number uint32 msgs_sent = 0; }; struct AuthKeyCreateStrings { std::vector<gsl::byte> dh_prime; std::vector<gsl::byte> g_a; AuthKey::Data auth_key = { { gsl::byte{} } }; }; std::unique_ptr<AuthKeyCreateData> _authKeyData; std::unique_ptr<AuthKeyCreateStrings> _authKeyStrings; void dhClientParamsSend(); void authKeyCreated(); void clearAuthKeyData(); }; } // namespace internal } // namespace MTP ������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/mtproto/connection_abstract.cpp��������������������������������0000664�0000000�0000000�00000005107�13262451251�0025660�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "mtproto/connection_abstract.h" #include "mtproto/connection_tcp.h" #include "mtproto/connection_http.h" #include "mtproto/connection_auto.h" namespace MTP { namespace internal { AbstractConnection::~AbstractConnection() { } mtpBuffer AbstractConnection::preparePQFake(const MTPint128 &nonce) { MTPReq_pq req_pq(nonce); mtpBuffer buffer; uint32 requestSize = req_pq.innerLength() >> 2; buffer.resize(0); buffer.reserve(8 + requestSize); buffer.push_back(0); // tcp packet len buffer.push_back(0); // tcp packet num buffer.push_back(0); buffer.push_back(0); buffer.push_back(0); buffer.push_back(unixtime()); buffer.push_back(requestSize * 4); req_pq.write(buffer); buffer.push_back(0); // tcp crc32 hash return buffer; } MTPResPQ AbstractConnection::readPQFakeReply(const mtpBuffer &buffer) { const mtpPrime *answer(buffer.constData()); uint32 len = buffer.size(); if (len < 5) { LOG(("Fake PQ Error: bad request answer, len = %1").arg(len * sizeof(mtpPrime))); DEBUG_LOG(("Fake PQ Error: answer bytes %1").arg(Logs::mb(answer, len * sizeof(mtpPrime)).str())); throw Exception("bad pq reply"); } if (answer[0] != 0 || answer[1] != 0 || (((uint32)answer[2]) & 0x03) != 1/* || (unixtime() - answer[3] > 300) || (answer[3] - unixtime() > 60)*/) { // didnt sync time yet LOG(("Fake PQ Error: bad request answer start (%1 %2 %3)").arg(answer[0]).arg(answer[1]).arg(answer[2])); DEBUG_LOG(("Fake PQ Error: answer bytes %1").arg(Logs::mb(answer, len * sizeof(mtpPrime)).str())); throw Exception("bad pq reply"); } uint32 answerLen = (uint32)answer[4]; if (answerLen != (len - 5) * sizeof(mtpPrime)) { LOG(("Fake PQ Error: bad request answer %1 <> %2").arg(answerLen).arg((len - 5) * sizeof(mtpPrime))); DEBUG_LOG(("Fake PQ Error: answer bytes %1").arg(Logs::mb(answer, len * sizeof(mtpPrime)).str())); throw Exception("bad pq reply"); } const mtpPrime *from(answer + 5), *end(from + len - 5); MTPResPQ response; response.read(from, end); return response; } AbstractConnection *AbstractConnection::create(DcType type, QThread *thread) { if ((type == DcType::Temporary) || (Global::ConnectionType() == dbictTcpProxy)) { return new TCPConnection(thread); } else if (Global::ConnectionType() == dbictHttpProxy) { return new HTTPConnection(thread); } return new AutoConnection(thread); } } // namespace internal } // namespace MTP ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/mtproto/connection_abstract.h����������������������������������0000664�0000000�0000000�00000004117�13262451251�0025325�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "mtproto/dc_options.h" namespace MTP { namespace internal { class AbstractConnection : public QObject { Q_OBJECT public: AbstractConnection(QThread *thread) : _sentEncrypted(false) { moveToThread(thread); } AbstractConnection(const AbstractConnection &other) = delete; AbstractConnection &operator=(const AbstractConnection &other) = delete; virtual ~AbstractConnection() = 0; // virtual constructor static AbstractConnection *create(DcType type, QThread *thread); void setSentEncrypted() { _sentEncrypted = true; } virtual void sendData(mtpBuffer &buffer) = 0; // has size + 3, buffer[0] = len, buffer[1] = packetnum, buffer[last] = crc32 virtual void disconnectFromServer() = 0; virtual void connectTcp(const DcOptions::Endpoint &endpoint) = 0; virtual void connectHttp(const DcOptions::Endpoint &endpoint) = 0; virtual bool isConnected() const = 0; virtual bool usingHttpWait() { return false; } virtual bool needHttpWait() { return false; } virtual int32 debugState() const = 0; virtual QString transport() const = 0; using BuffersQueue = std::deque<mtpBuffer>; BuffersQueue &received() { return _receivedQueue; } // Used to emit error(...) with no real code from the server. static constexpr auto kErrorCodeOther = -499; signals: void receivedData(); void receivedSome(); // to stop restart timer void error(qint32 errorCodebool); void connected(); void disconnected(); protected: BuffersQueue _receivedQueue; // list of received packets, not processed yet bool _sentEncrypted; // first we always send fake MTPReq_pq to see if connection works at all // we send them simultaneously through TCP/HTTP/IPv4/IPv6 to choose the working one static mtpBuffer preparePQFake(const MTPint128 &nonce); static MTPResPQ readPQFakeReply(const mtpBuffer &buffer); }; } // namespace internal } // namespace MTP �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/mtproto/connection_auto.cpp������������������������������������0000664�0000000�0000000�00000024770�13262451251�0025034�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "mtproto/connection_auto.h" #include "mtproto/connection_http.h" namespace MTP { namespace internal { AutoConnection::AutoConnection(QThread *thread) : AbstractTCPConnection(thread) , status(WaitingBoth) , tcpNonce(rand_value<MTPint128>()) , httpNonce(rand_value<MTPint128>()) , _flagsTcp(0) , _flagsHttp(0) , _tcpTimeout(MTPMinReceiveDelay) { manager.moveToThread(thread); #ifndef TDESKTOP_DISABLE_NETWORK_PROXY manager.setProxy(QNetworkProxy(QNetworkProxy::DefaultProxy)); #endif // !TDESKTOP_DISABLE_NETWORK_PROXY httpStartTimer.moveToThread(thread); httpStartTimer.setSingleShot(true); connect(&httpStartTimer, SIGNAL(timeout()), this, SLOT(onHttpStart())); tcpTimeoutTimer.moveToThread(thread); tcpTimeoutTimer.setSingleShot(true); connect(&tcpTimeoutTimer, SIGNAL(timeout()), this, SLOT(onTcpTimeoutTimer())); sock.moveToThread(thread); #ifndef TDESKTOP_DISABLE_NETWORK_PROXY sock.setProxy(QNetworkProxy(QNetworkProxy::NoProxy)); #endif // !TDESKTOP_DISABLE_NETWORK_PROXY connect(&sock, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(socketError(QAbstractSocket::SocketError))); connect(&sock, SIGNAL(connected()), this, SLOT(onSocketConnected())); connect(&sock, SIGNAL(disconnected()), this, SLOT(onSocketDisconnected())); } void AutoConnection::onHttpStart() { if (status == HttpReady) { DEBUG_LOG(("Connection Info: HTTP/%1-transport chosen by timer").arg((_flagsHttp & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4")); status = UsingHttp; sock.disconnectFromHost(); emit connected(); } } void AutoConnection::onSocketConnected() { if (status == HttpReady || status == WaitingBoth || status == WaitingTcp) { mtpBuffer buffer(preparePQFake(tcpNonce)); DEBUG_LOG(("Connection Info: sending fake req_pq through TCP/%1 transport").arg((_flagsTcp & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4")); if (_tcpTimeout < 0) _tcpTimeout = -_tcpTimeout; tcpTimeoutTimer.start(_tcpTimeout); tcpSend(buffer); } else if (status == WaitingHttp || status == UsingHttp) { sock.disconnectFromHost(); } } void AutoConnection::onTcpTimeoutTimer() { if (status == HttpReady || status == WaitingBoth || status == WaitingTcp) { if (_tcpTimeout < MTPMaxReceiveDelay) _tcpTimeout *= 2; _tcpTimeout = -_tcpTimeout; QAbstractSocket::SocketState state = sock.state(); if (state == QAbstractSocket::ConnectedState || state == QAbstractSocket::ConnectingState || state == QAbstractSocket::HostLookupState) { sock.disconnectFromHost(); } else if (state != QAbstractSocket::ClosingState) { sock.connectToHost(QHostAddress(_addrTcp), _portTcp); } } } void AutoConnection::onSocketDisconnected() { if (_tcpTimeout < 0) { _tcpTimeout = -_tcpTimeout; if (status == HttpReady || status == WaitingBoth || status == WaitingTcp) { sock.connectToHost(QHostAddress(_addrTcp), _portTcp); return; } } if (status == WaitingBoth) { status = WaitingHttp; } else if (status == WaitingTcp || status == UsingTcp) { emit disconnected(); } else if (status == HttpReady) { DEBUG_LOG(("Connection Info: HTTP/%1-transport chosen by socket disconnect").arg((_flagsHttp & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4")); status = UsingHttp; emit connected(); } } void AutoConnection::sendData(mtpBuffer &buffer) { if (status == FinishedWork) return; if (buffer.size() < 3) { LOG(("TCP Error: writing bad packet, len = %1").arg(buffer.size() * sizeof(mtpPrime))); TCP_LOG(("TCP Error: bad packet %1").arg(Logs::mb(&buffer[0], buffer.size() * sizeof(mtpPrime)).str())); emit error(kErrorCodeOther); return; } if (status == UsingTcp) { tcpSend(buffer); } else { httpSend(buffer); } } void AutoConnection::httpSend(mtpBuffer &buffer) { int32 requestSize = (buffer.size() - 3) * sizeof(mtpPrime); QNetworkRequest request(address); request.setHeader(QNetworkRequest::ContentLengthHeader, QVariant(requestSize)); request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant(qsl("application/x-www-form-urlencoded"))); TCP_LOG(("HTTP Info: sending %1 len request").arg(requestSize)); requests.insert(manager.post(request, QByteArray((const char*)(&buffer[2]), requestSize))); } void AutoConnection::disconnectFromServer() { if (status == FinishedWork) return; status = FinishedWork; Requests copy = requests; requests.clear(); for (Requests::const_iterator i = copy.cbegin(), e = copy.cend(); i != e; ++i) { (*i)->abort(); (*i)->deleteLater(); } disconnect(&manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(requestFinished(QNetworkReply*))); address = QUrl(); disconnect(&sock, SIGNAL(readyRead()), 0, 0); sock.close(); httpStartTimer.stop(); } void AutoConnection::connectTcp(const DcOptions::Endpoint &endpoint) { _addrTcp = QString::fromStdString(endpoint.ip); _portTcp = endpoint.port; _flagsTcp = endpoint.flags; connect(&sock, SIGNAL(readyRead()), this, SLOT(socketRead())); sock.connectToHost(QHostAddress(_addrTcp), _portTcp); } void AutoConnection::connectHttp(const DcOptions::Endpoint &endpoint) { _addrHttp = QString::fromStdString(endpoint.ip); _portHttp = endpoint.port; _flagsHttp = endpoint.flags; // not endpoint.port - always 80 port for http transport address = QUrl(((_flagsHttp & MTPDdcOption::Flag::f_ipv6) ? qsl("http://[%1]:%2/api") : qsl("http://%1:%2/api")).arg(_addrHttp).arg(80)); TCP_LOG(("HTTP Info: address is %1").arg(address.toDisplayString())); connect(&manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(requestFinished(QNetworkReply*))); mtpBuffer buffer(preparePQFake(httpNonce)); DEBUG_LOG(("Connection Info: sending fake req_pq through HTTP/%1 transport").arg((_flagsHttp & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4")); httpSend(buffer); } bool AutoConnection::isConnected() const { return (status == UsingTcp) || (status == UsingHttp); } void AutoConnection::requestFinished(QNetworkReply *reply) { if (status == FinishedWork) return; reply->deleteLater(); if (reply->error() == QNetworkReply::NoError) { requests.remove(reply); mtpBuffer data = HTTPConnection::handleResponse(reply); if (data.size() == 1) { if (status == WaitingBoth) { status = WaitingTcp; } else { emit error(data[0]); } } else if (!data.isEmpty()) { if (status == UsingHttp) { _receivedQueue.push_back(data); emit receivedData(); } else if (status == WaitingBoth || status == WaitingHttp) { try { auto res_pq = readPQFakeReply(data); const auto &res_pq_data(res_pq.c_resPQ()); if (res_pq_data.vnonce == httpNonce) { if (status == WaitingBoth) { status = HttpReady; httpStartTimer.start(MTPTcpConnectionWaitTimeout); } else { DEBUG_LOG(("Connection Info: HTTP/%1-transport chosen by pq-response, awaited").arg((_flagsHttp & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4")); status = UsingHttp; sock.disconnectFromHost(); emit connected(); } } } catch (Exception &e) { DEBUG_LOG(("Connection Error: exception in parsing HTTP fake pq-responce, %1").arg(e.what())); if (status == WaitingBoth) { status = WaitingTcp; } else { emit error(kErrorCodeOther); } } } else if (status == UsingTcp) { DEBUG_LOG(("Connection Info: already using tcp, ignoring http response")); } } } else { if (!requests.remove(reply)) { return; } if (status == WaitingBoth) { status = WaitingTcp; } else if (status == WaitingHttp || status == UsingHttp) { emit error(HTTPConnection::handleError(reply)); } else { LOG(("Strange Http Error: status %1").arg(status)); } } } void AutoConnection::socketPacket(const char *packet, uint32 length) { if (status == FinishedWork) return; mtpBuffer data = AbstractTCPConnection::handleResponse(packet, length); if (data.size() == 1) { if (status == WaitingBoth) { status = WaitingHttp; sock.disconnectFromHost(); } else if (status == HttpReady) { DEBUG_LOG(("Connection Info: HTTP/%1-transport chosen by bad tcp response, ready").arg((_flagsHttp & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4")); status = UsingHttp; sock.disconnectFromHost(); emit connected(); } else if (status == WaitingTcp || status == UsingTcp) { emit error(data[0]); } else { LOG(("Strange Tcp Error; status %1").arg(status)); } } else if (status == UsingTcp) { _receivedQueue.push_back(data); emit receivedData(); } else if (status == WaitingBoth || status == WaitingTcp || status == HttpReady) { tcpTimeoutTimer.stop(); try { auto res_pq = readPQFakeReply(data); const auto &res_pq_data(res_pq.c_resPQ()); if (res_pq_data.vnonce == tcpNonce) { DEBUG_LOG(("Connection Info: TCP/%1-transport chosen by pq-response").arg((_flagsTcp & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4")); status = UsingTcp; emit connected(); } } catch (Exception &e) { DEBUG_LOG(("Connection Error: exception in parsing TCP fake pq-responce, %1").arg(e.what())); if (status == WaitingBoth) { status = WaitingHttp; sock.disconnectFromHost(); } else if (status == HttpReady) { DEBUG_LOG(("Connection Info: HTTP/%1-transport chosen by bad tcp response, awaited").arg((_flagsHttp & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4")); status = UsingHttp; sock.disconnectFromHost(); emit connected(); } else { emit error(kErrorCodeOther); } } } } bool AutoConnection::usingHttpWait() { return (status == UsingHttp); } bool AutoConnection::needHttpWait() { return (status == UsingHttp) ? requests.isEmpty() : false; } int32 AutoConnection::debugState() const { return (status == UsingHttp) ? -1 : ((status == UsingTcp) ? sock.state() : -777); } QString AutoConnection::transport() const { if (status == UsingTcp) { return qsl("TCP"); } else if (status == UsingHttp) { return qsl("HTTP"); } else { return QString(); } } void AutoConnection::socketError(QAbstractSocket::SocketError e) { if (status == FinishedWork) return; AbstractTCPConnection::handleError(e, sock); if (status == WaitingBoth) { status = WaitingHttp; } else if (status == HttpReady) { DEBUG_LOG(("Connection Info: HTTP/%1-transport chosen by tcp error, ready").arg((_flagsHttp & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4")); status = UsingHttp; emit connected(); } else if (status == WaitingTcp || status == UsingTcp) { emit error(kErrorCodeOther); } else { LOG(("Strange Tcp Error: status %1").arg(status)); } } } // namespace internal } // namespace MTP ��������tdesktop-1.2.17/Telegram/SourceFiles/mtproto/connection_auto.h��������������������������������������0000664�0000000�0000000�00000003177�13262451251�0024477�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "mtproto/connection_tcp.h" namespace MTP { namespace internal { class AutoConnection : public AbstractTCPConnection { Q_OBJECT public: AutoConnection(QThread *thread); void sendData(mtpBuffer &buffer) override; void disconnectFromServer() override; void connectTcp(const DcOptions::Endpoint &endpoint) override; void connectHttp(const DcOptions::Endpoint &endpoint) override; bool isConnected() const override; bool usingHttpWait() override; bool needHttpWait() override; int32 debugState() const override; QString transport() const override; public slots: void socketError(QAbstractSocket::SocketError e); void requestFinished(QNetworkReply *reply); void onSocketConnected(); void onSocketDisconnected(); void onHttpStart(); void onTcpTimeoutTimer(); protected: void socketPacket(const char *packet, uint32 length) override; private: void httpSend(mtpBuffer &buffer); enum Status { WaitingBoth = 0, WaitingHttp, WaitingTcp, HttpReady, UsingHttp, UsingTcp, FinishedWork }; Status status; MTPint128 tcpNonce, httpNonce; QTimer httpStartTimer; QNetworkAccessManager manager; QUrl address; typedef QSet<QNetworkReply*> Requests; Requests requests; QString _addrTcp, _addrHttp; int32 _portTcp, _portHttp; MTPDdcOption::Flags _flagsTcp, _flagsHttp; int32 _tcpTimeout; QTimer tcpTimeoutTimer; }; } // namespace internal } // namespace MTP �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/mtproto/connection_http.cpp������������������������������������0000664�0000000�0000000�00000015444�13262451251�0025041�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "mtproto/connection_http.h" namespace MTP { namespace internal { mtpBuffer HTTPConnection::handleResponse(QNetworkReply *reply) { QByteArray response = reply->readAll(); TCP_LOG(("HTTP Info: read %1 bytes").arg(response.size())); if (response.isEmpty()) return mtpBuffer(); if (response.size() & 0x03 || response.size() < 8) { LOG(("HTTP Error: bad response size %1").arg(response.size())); return mtpBuffer(1, -500); } mtpBuffer data(response.size() >> 2); memcpy(data.data(), response.constData(), response.size()); return data; } qint32 HTTPConnection::handleError(QNetworkReply *reply) { // returnes "maybe bad key" auto result = qint32(kErrorCodeOther); QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); if (statusCode.isValid()) { int status = statusCode.toInt(); result = -status; } switch (reply->error()) { case QNetworkReply::ConnectionRefusedError: LOG(("HTTP Error: connection refused - %1").arg(reply->errorString())); break; case QNetworkReply::RemoteHostClosedError: LOG(("HTTP Error: remote host closed - %1").arg(reply->errorString())); break; case QNetworkReply::HostNotFoundError: LOG(("HTTP Error: host not found - %2").arg(reply->error()).arg(reply->errorString())); break; case QNetworkReply::TimeoutError: LOG(("HTTP Error: timeout - %2").arg(reply->error()).arg(reply->errorString())); break; case QNetworkReply::OperationCanceledError: LOG(("HTTP Error: cancelled - %2").arg(reply->error()).arg(reply->errorString())); break; case QNetworkReply::SslHandshakeFailedError: case QNetworkReply::TemporaryNetworkFailureError: case QNetworkReply::NetworkSessionFailedError: case QNetworkReply::BackgroundRequestNotAllowedError: case QNetworkReply::UnknownNetworkError: LOG(("HTTP Error: network error %1 - %2").arg(reply->error()).arg(reply->errorString())); break; // proxy errors (101-199): case QNetworkReply::ProxyConnectionRefusedError: case QNetworkReply::ProxyConnectionClosedError: case QNetworkReply::ProxyNotFoundError: case QNetworkReply::ProxyTimeoutError: case QNetworkReply::ProxyAuthenticationRequiredError: case QNetworkReply::UnknownProxyError:LOG(("HTTP Error: proxy error %1 - %2").arg(reply->error()).arg(reply->errorString())); break; // content errors (201-299): case QNetworkReply::ContentAccessDenied: case QNetworkReply::ContentOperationNotPermittedError: case QNetworkReply::ContentNotFoundError: case QNetworkReply::AuthenticationRequiredError: case QNetworkReply::ContentReSendError: case QNetworkReply::UnknownContentError: LOG(("HTTP Error: content error %1 - %2").arg(reply->error()).arg(reply->errorString())); break; // protocol errors case QNetworkReply::ProtocolUnknownError: case QNetworkReply::ProtocolInvalidOperationError: case QNetworkReply::ProtocolFailure: LOG(("HTTP Error: protocol error %1 - %2").arg(reply->error()).arg(reply->errorString())); break; }; TCP_LOG(("HTTP Error %1, restarting! - %2").arg(reply->error()).arg(reply->errorString())); return result; } HTTPConnection::HTTPConnection(QThread *thread) : AbstractConnection(thread) , status(WaitingHttp) , httpNonce(rand_value<MTPint128>()) , _flags(0) { manager.moveToThread(thread); App::setProxySettings(manager); } void HTTPConnection::sendData(mtpBuffer &buffer) { if (status == FinishedWork) return; if (buffer.size() < 3) { LOG(("TCP Error: writing bad packet, len = %1").arg(buffer.size() * sizeof(mtpPrime))); TCP_LOG(("TCP Error: bad packet %1").arg(Logs::mb(&buffer[0], buffer.size() * sizeof(mtpPrime)).str())); emit error(kErrorCodeOther); return; } int32 requestSize = (buffer.size() - 3) * sizeof(mtpPrime); QNetworkRequest request(address); request.setHeader(QNetworkRequest::ContentLengthHeader, QVariant(requestSize)); request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant(qsl("application/x-www-form-urlencoded"))); TCP_LOG(("HTTP Info: sending %1 len request %2").arg(requestSize).arg(Logs::mb(&buffer[2], requestSize).str())); requests.insert(manager.post(request, QByteArray((const char*)(&buffer[2]), requestSize))); } void HTTPConnection::disconnectFromServer() { if (status == FinishedWork) return; status = FinishedWork; Requests copy = requests; requests.clear(); for (Requests::const_iterator i = copy.cbegin(), e = copy.cend(); i != e; ++i) { (*i)->abort(); (*i)->deleteLater(); } disconnect(&manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(requestFinished(QNetworkReply*))); address = QUrl(); } void HTTPConnection::connectHttp(const DcOptions::Endpoint &endpoint) { _flags = endpoint.flags; auto addr = QString::fromStdString(endpoint.ip); // not endpoint.port - always 80 port for http transport address = QUrl(((_flags & MTPDdcOption::Flag::f_ipv6) ? qsl("http://[%1]:%2/api") : qsl("http://%1:%2/api")).arg(addr).arg(80)); TCP_LOG(("HTTP Info: address is %1").arg(address.toDisplayString())); connect(&manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(requestFinished(QNetworkReply*))); mtpBuffer buffer(preparePQFake(httpNonce)); DEBUG_LOG(("Connection Info: sending fake req_pq through HTTP/%1 transport").arg((_flags & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4")); sendData(buffer); } bool HTTPConnection::isConnected() const { return (status == UsingHttp); } void HTTPConnection::requestFinished(QNetworkReply *reply) { if (status == FinishedWork) return; reply->deleteLater(); if (reply->error() == QNetworkReply::NoError) { requests.remove(reply); mtpBuffer data = handleResponse(reply); if (data.size() == 1) { emit error(data[0]); } else if (!data.isEmpty()) { if (status == UsingHttp) { _receivedQueue.push_back(data); emit receivedData(); } else { try { auto res_pq = readPQFakeReply(data); const auto &res_pq_data(res_pq.c_resPQ()); if (res_pq_data.vnonce == httpNonce) { DEBUG_LOG(("Connection Info: HTTP/%1-transport connected by pq-response").arg((_flags & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4")); status = UsingHttp; emit connected(); } } catch (Exception &e) { DEBUG_LOG(("Connection Error: exception in parsing HTTP fake pq-responce, %1").arg(e.what())); emit error(kErrorCodeOther); } } } } else { if (!requests.remove(reply)) { return; } emit error(handleError(reply)); } } bool HTTPConnection::usingHttpWait() { return true; } bool HTTPConnection::needHttpWait() { return requests.isEmpty(); } int32 HTTPConnection::debugState() const { return -1; } QString HTTPConnection::transport() const { if (status == UsingHttp) { return qsl("HTTP"); } else { return QString(); } } } // namespace internal } // namespace MTP ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/mtproto/connection_http.h��������������������������������������0000664�0000000�0000000�00000002470�13262451251�0024501�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "mtproto/connection_abstract.h" namespace MTP { namespace internal { class HTTPConnection : public AbstractConnection { Q_OBJECT public: HTTPConnection(QThread *thread); void sendData(mtpBuffer &buffer) override; void disconnectFromServer() override; void connectTcp(const DcOptions::Endpoint &endpoint) override { // not supported } void connectHttp(const DcOptions::Endpoint &endpoint) override; bool isConnected() const override; bool usingHttpWait() override; bool needHttpWait() override; int32 debugState() const override; QString transport() const override; static mtpBuffer handleResponse(QNetworkReply *reply); static qint32 handleError(QNetworkReply *reply); // returnes error code public slots: void requestFinished(QNetworkReply *reply); private: enum Status { WaitingHttp = 0, UsingHttp, FinishedWork }; Status status; MTPint128 httpNonce; MTPDdcOption::Flags _flags; QNetworkAccessManager manager; QUrl address; typedef QSet<QNetworkReply*> Requests; Requests requests; }; } // namespace internal } // namespace MTP ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/mtproto/connection_tcp.cpp�������������������������������������0000664�0000000�0000000�00000027535�13262451251�0024654�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "mtproto/connection_tcp.h" #include <openssl/aes.h> namespace MTP { namespace internal { namespace { uint32 tcpPacketSize(const char *packet) { // must have at least 4 bytes readable uint32 result = (packet[0] > 0) ? packet[0] : 0; if (result == 0x7f) { const uchar *bytes = reinterpret_cast<const uchar*>(packet); result = (((uint32(bytes[3]) << 8) | uint32(bytes[2])) << 8) | uint32(bytes[1]); return (result << 2) + 4; } return (result << 2) + 1; } } // namespace AbstractTCPConnection::AbstractTCPConnection(QThread *thread) : AbstractConnection(thread) , packetNum(0) , packetRead(0) , packetLeft(0) , readingToShort(true) , currentPos((char*)shortBuffer) { } AbstractTCPConnection::~AbstractTCPConnection() { } void AbstractTCPConnection::socketRead() { if (sock.state() != QAbstractSocket::ConnectedState) { LOG(("MTP error: socket not connected in socketRead(), state: %1").arg(sock.state())); emit error(kErrorCodeOther); return; } do { uint32 toRead = packetLeft ? packetLeft : (readingToShort ? (MTPShortBufferSize * sizeof(mtpPrime) - packetRead) : 4); if (readingToShort) { if (currentPos + toRead > ((char*)shortBuffer) + MTPShortBufferSize * sizeof(mtpPrime)) { longBuffer.resize(((packetRead + toRead) >> 2) + 1); memcpy(&longBuffer[0], shortBuffer, packetRead); currentPos = ((char*)&longBuffer[0]) + packetRead; readingToShort = false; } } else { if (longBuffer.size() * sizeof(mtpPrime) < packetRead + toRead) { longBuffer.resize(((packetRead + toRead) >> 2) + 1); currentPos = ((char*)&longBuffer[0]) + packetRead; } } int32 bytes = (int32)sock.read(currentPos, toRead); if (bytes > 0) { aesCtrEncrypt(currentPos, bytes, _receiveKey, &_receiveState); TCP_LOG(("TCP Info: read %1 bytes").arg(bytes)); packetRead += bytes; currentPos += bytes; if (packetLeft) { packetLeft -= bytes; if (!packetLeft) { socketPacket(currentPos - packetRead, packetRead); currentPos = (char*)shortBuffer; packetRead = packetLeft = 0; readingToShort = true; longBuffer.clear(); } else { TCP_LOG(("TCP Info: not enough %1 for packet! read %2").arg(packetLeft).arg(packetRead)); emit receivedSome(); } } else { bool move = false; while (packetRead >= 4) { uint32 packetSize = tcpPacketSize(currentPos - packetRead); if (packetSize < 5 || packetSize > MTPPacketSizeMax) { LOG(("TCP Error: packet size = %1").arg(packetSize)); emit error(kErrorCodeOther); return; } if (packetRead >= packetSize) { socketPacket(currentPos - packetRead, packetSize); packetRead -= packetSize; packetLeft = 0; move = true; } else { packetLeft = packetSize - packetRead; TCP_LOG(("TCP Info: not enough %1 for packet! size %2 read %3").arg(packetLeft).arg(packetSize).arg(packetRead)); emit receivedSome(); break; } } if (move) { if (!packetRead) { currentPos = (char*)shortBuffer; readingToShort = true; longBuffer.clear(); } else if (!readingToShort && packetRead < MTPShortBufferSize * sizeof(mtpPrime)) { memcpy(shortBuffer, currentPos - packetRead, packetRead); currentPos = (char*)shortBuffer + packetRead; readingToShort = true; longBuffer.clear(); } } } } else if (bytes < 0) { LOG(("TCP Error: socket read return -1")); emit error(kErrorCodeOther); return; } else { TCP_LOG(("TCP Info: no bytes read, but bytes available was true...")); break; } } while (sock.state() == QAbstractSocket::ConnectedState && sock.bytesAvailable()); } mtpBuffer AbstractTCPConnection::handleResponse(const char *packet, uint32 length) { if (length < 5 || length > MTPPacketSizeMax) { LOG(("TCP Error: bad packet size %1").arg(length)); return mtpBuffer(1, -500); } int32 size = packet[0], len = length - 1; if (size == 0x7f) { const uchar *bytes = reinterpret_cast<const uchar*>(packet); size = (((uint32(bytes[3]) << 8) | uint32(bytes[2])) << 8) | uint32(bytes[1]); len -= 3; } if (size * int32(sizeof(mtpPrime)) != len) { LOG(("TCP Error: bad packet header")); TCP_LOG(("TCP Error: bad packet header, packet: %1").arg(Logs::mb(packet, length).str())); return mtpBuffer(1, -500); } const mtpPrime *packetdata = reinterpret_cast<const mtpPrime*>(packet + (length - len)); TCP_LOG(("TCP Info: packet received, size = %1").arg(size * sizeof(mtpPrime))); if (size == 1) { LOG(("TCP Error: error packet received, code = %1").arg(*packetdata)); return mtpBuffer(1, *packetdata); } mtpBuffer data(size); memcpy(data.data(), packetdata, size * sizeof(mtpPrime)); return data; } void AbstractTCPConnection::handleError(QAbstractSocket::SocketError e, QTcpSocket &sock) { switch (e) { case QAbstractSocket::ConnectionRefusedError: LOG(("TCP Error: socket connection refused - %1").arg(sock.errorString())); break; case QAbstractSocket::RemoteHostClosedError: TCP_LOG(("TCP Info: remote host closed socket connection - %1").arg(sock.errorString())); break; case QAbstractSocket::HostNotFoundError: LOG(("TCP Error: host not found - %1").arg(sock.errorString())); break; case QAbstractSocket::SocketTimeoutError: LOG(("TCP Error: socket timeout - %1").arg(sock.errorString())); break; case QAbstractSocket::NetworkError: LOG(("TCP Error: network - %1").arg(sock.errorString())); break; case QAbstractSocket::ProxyAuthenticationRequiredError: case QAbstractSocket::ProxyConnectionRefusedError: case QAbstractSocket::ProxyConnectionClosedError: case QAbstractSocket::ProxyConnectionTimeoutError: case QAbstractSocket::ProxyNotFoundError: case QAbstractSocket::ProxyProtocolError: LOG(("TCP Error: proxy (%1) - %2").arg(e).arg(sock.errorString())); break; default: LOG(("TCP Error: other (%1) - %2").arg(e).arg(sock.errorString())); break; } TCP_LOG(("TCP Error %1, restarting! - %2").arg(e).arg(sock.errorString())); } TCPConnection::TCPConnection(QThread *thread) : AbstractTCPConnection(thread) , status(WaitingTcp) , tcpNonce(rand_value<MTPint128>()) , _tcpTimeout(MTPMinReceiveDelay) , _flags(0) { tcpTimeoutTimer.moveToThread(thread); tcpTimeoutTimer.setSingleShot(true); connect(&tcpTimeoutTimer, SIGNAL(timeout()), this, SLOT(onTcpTimeoutTimer())); sock.moveToThread(thread); App::setProxySettings(sock); connect(&sock, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(socketError(QAbstractSocket::SocketError))); connect(&sock, SIGNAL(connected()), this, SLOT(onSocketConnected())); connect(&sock, SIGNAL(disconnected()), this, SLOT(onSocketDisconnected())); } void TCPConnection::onSocketConnected() { if (status == WaitingTcp) { mtpBuffer buffer(preparePQFake(tcpNonce)); DEBUG_LOG(("Connection Info: sending fake req_pq through TCP/%1 transport").arg((_flags & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4")); if (_tcpTimeout < 0) _tcpTimeout = -_tcpTimeout; tcpTimeoutTimer.start(_tcpTimeout); sendData(buffer); } } void TCPConnection::onTcpTimeoutTimer() { if (status == WaitingTcp) { if (_tcpTimeout < MTPMaxReceiveDelay) _tcpTimeout *= 2; _tcpTimeout = -_tcpTimeout; QAbstractSocket::SocketState state = sock.state(); if (state == QAbstractSocket::ConnectedState || state == QAbstractSocket::ConnectingState || state == QAbstractSocket::HostLookupState) { sock.disconnectFromHost(); } else if (state != QAbstractSocket::ClosingState) { sock.connectToHost(QHostAddress(_addr), _port); } } } void TCPConnection::onSocketDisconnected() { if (_tcpTimeout < 0) { _tcpTimeout = -_tcpTimeout; if (status == WaitingTcp) { sock.connectToHost(QHostAddress(_addr), _port); return; } } if (status == WaitingTcp || status == UsingTcp) { emit disconnected(); } } void TCPConnection::sendData(mtpBuffer &buffer) { if (status == FinishedWork) return; if (buffer.size() < 3) { LOG(("TCP Error: writing bad packet, len = %1").arg(buffer.size() * sizeof(mtpPrime))); TCP_LOG(("TCP Error: bad packet %1").arg(Logs::mb(&buffer[0], buffer.size() * sizeof(mtpPrime)).str())); emit error(kErrorCodeOther); return; } tcpSend(buffer); } void AbstractTCPConnection::tcpSend(mtpBuffer &buffer) { if (!packetNum) { // prepare random part char nonce[64]; uint32 *first = reinterpret_cast<uint32*>(nonce), *second = first + 1; uint32 first1 = 0x44414548U, first2 = 0x54534f50U, first3 = 0x20544547U, first4 = 0x20544547U, first5 = 0xeeeeeeeeU; uint32 second1 = 0; do { memset_rand(nonce, sizeof(nonce)); } while (*first == first1 || *first == first2 || *first == first3 || *first == first4 || *first == first5 || *second == second1 || *reinterpret_cast<uchar*>(nonce) == 0xef); //sock.write(nonce, 64); // prepare encryption key/iv memcpy(_sendKey, nonce + 8, CTRState::KeySize); memcpy(_sendState.ivec, nonce + 8 + CTRState::KeySize, CTRState::IvecSize); // prepare decryption key/iv char reversed[48]; memcpy(reversed, nonce + 8, sizeof(reversed)); std::reverse(reversed, reversed + base::array_size(reversed)); memcpy(_receiveKey, reversed, CTRState::KeySize); memcpy(_receiveState.ivec, reversed + CTRState::KeySize, CTRState::IvecSize); // write protocol identifier *reinterpret_cast<uint32*>(nonce + 56) = 0xefefefefU; sock.write(nonce, 56); aesCtrEncrypt(nonce, 64, _sendKey, &_sendState); sock.write(nonce + 56, 8); } ++packetNum; uint32 size = buffer.size() - 3, len = size * 4; char *data = reinterpret_cast<char*>(&buffer[0]); if (size < 0x7f) { data[7] = char(size); TCP_LOG(("TCP Info: write %1 packet %2").arg(packetNum).arg(len + 1)); aesCtrEncrypt(data + 7, len + 1, _sendKey, &_sendState); sock.write(data + 7, len + 1); } else { data[4] = 0x7f; reinterpret_cast<uchar*>(data)[5] = uchar(size & 0xFF); reinterpret_cast<uchar*>(data)[6] = uchar((size >> 8) & 0xFF); reinterpret_cast<uchar*>(data)[7] = uchar((size >> 16) & 0xFF); TCP_LOG(("TCP Info: write %1 packet %2").arg(packetNum).arg(len + 4)); aesCtrEncrypt(data + 4, len + 4, _sendKey, &_sendState); sock.write(data + 4, len + 4); } } void TCPConnection::disconnectFromServer() { if (status == FinishedWork) return; status = FinishedWork; disconnect(&sock, SIGNAL(readyRead()), 0, 0); sock.close(); } void TCPConnection::connectTcp(const DcOptions::Endpoint &endpoint) { _addr = QString::fromStdString(endpoint.ip); _port = endpoint.port; _flags = endpoint.flags; connect(&sock, SIGNAL(readyRead()), this, SLOT(socketRead())); sock.connectToHost(QHostAddress(_addr), _port); } void TCPConnection::socketPacket(const char *packet, uint32 length) { if (status == FinishedWork) return; mtpBuffer data = handleResponse(packet, length); if (data.size() == 1) { emit error(data[0]); } else if (status == UsingTcp) { _receivedQueue.push_back(data); emit receivedData(); } else if (status == WaitingTcp) { tcpTimeoutTimer.stop(); try { auto res_pq = readPQFakeReply(data); const auto &res_pq_data(res_pq.c_resPQ()); if (res_pq_data.vnonce == tcpNonce) { DEBUG_LOG(("Connection Info: TCP/%1-transport chosen by pq-response").arg((_flags & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4")); status = UsingTcp; emit connected(); } } catch (Exception &e) { DEBUG_LOG(("Connection Error: exception in parsing TCP fake pq-responce, %1").arg(e.what())); emit error(kErrorCodeOther); } } } bool TCPConnection::isConnected() const { return (status == UsingTcp); } int32 TCPConnection::debugState() const { return sock.state(); } QString TCPConnection::transport() const { return isConnected() ? qsl("TCP") : QString(); } void TCPConnection::socketError(QAbstractSocket::SocketError e) { if (status == FinishedWork) return; handleError(e, sock); emit error(kErrorCodeOther); } } // namespace internal } // namespace MTP �������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/mtproto/connection_tcp.h���������������������������������������0000664�0000000�0000000�00000004327�13262451251�0024313�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "mtproto/auth_key.h" #include "mtproto/connection_abstract.h" namespace MTP { namespace internal { class AbstractTCPConnection : public AbstractConnection { Q_OBJECT public: AbstractTCPConnection(QThread *thread); virtual ~AbstractTCPConnection() = 0; public slots: void socketRead(); protected: QTcpSocket sock; uint32 packetNum; // sent packet number uint32 packetRead, packetLeft; // reading from socket bool readingToShort; char *currentPos; mtpBuffer longBuffer; mtpPrime shortBuffer[MTPShortBufferSize]; virtual void socketPacket(const char *packet, uint32 length) = 0; static mtpBuffer handleResponse(const char *packet, uint32 length); static void handleError(QAbstractSocket::SocketError e, QTcpSocket &sock); static uint32 fourCharsToUInt(char ch1, char ch2, char ch3, char ch4) { char ch[4] = { ch1, ch2, ch3, ch4 }; return *reinterpret_cast<uint32*>(ch); } void tcpSend(mtpBuffer &buffer); uchar _sendKey[CTRState::KeySize]; CTRState _sendState; uchar _receiveKey[CTRState::KeySize]; CTRState _receiveState; }; class TCPConnection : public AbstractTCPConnection { Q_OBJECT public: TCPConnection(QThread *thread); void sendData(mtpBuffer &buffer) override; void disconnectFromServer() override; void connectTcp(const DcOptions::Endpoint &endpoint) override; void connectHttp(const DcOptions::Endpoint &endpoint) override { // not supported } bool isConnected() const override; int32 debugState() const override; QString transport() const override; public slots: void socketError(QAbstractSocket::SocketError e); void onSocketConnected(); void onSocketDisconnected(); void onTcpTimeoutTimer(); protected: void socketPacket(const char *packet, uint32 length) override; private: enum Status { WaitingTcp = 0, UsingTcp, FinishedWork }; Status status; MTPint128 tcpNonce; QString _addr; int32 _port, _tcpTimeout; MTPDdcOption::Flags _flags; QTimer tcpTimeoutTimer; }; } // namespace internal } // namespace MTP ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/mtproto/core_types.cpp�����������������������������������������0000664�0000000�0000000�00000017675�13262451251�0024027�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "mtproto/core_types.h" #include "zlib.h" uint32 MTPstring::innerLength() const { uint32 l = v.length(); if (l < 254) { l += 1; } else { l += 4; } uint32 d = l & 0x03; if (d) l += (4 - d); return l; } void MTPstring::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) { if (from + 1 > end) throw mtpErrorInsufficient(); if (cons != mtpc_string) throw mtpErrorUnexpected(cons, "MTPstring"); uint32 l; const uchar *buf = (const uchar*)from; if (buf[0] == 254) { l = (uint32)buf[1] + ((uint32)buf[2] << 8) + ((uint32)buf[3] << 16); buf += 4; from += ((l + 4) >> 2) + (((l + 4) & 0x03) ? 1 : 0); } else { l = (uint32)buf[0]; ++buf; from += ((l + 1) >> 2) + (((l + 1) & 0x03) ? 1 : 0); } if (from > end) throw mtpErrorInsufficient(); v = QByteArray(reinterpret_cast<const char*>(buf), l); } void MTPstring::write(mtpBuffer &to) const { uint32 l = v.length(), s = l + ((l < 254) ? 1 : 4), was = to.size(); if (s & 0x03) { s += 4; } s >>= 2; to.resize(was + s); char *buf = (char*)&to[was]; if (l < 254) { uchar sl = (uchar)l; *(buf++) = *(char*)(&sl); } else { *(buf++) = (char)254; *(buf++) = (char)(l & 0xFF); *(buf++) = (char)((l >> 8) & 0xFF); *(buf++) = (char)((l >> 16) & 0xFF); } memcpy(buf, v.constData(), l); } uint32 mtpRequest::innerLength() const { // for template MTP requests and MTPBoxed instanciation const auto value = get(); if (!value || value->size() < 9) { return 0; } return value->at(7); } void mtpRequest::write(mtpBuffer &to) const { const auto value = get(); if (!value || value->size() < 9) { return; } uint32 was = to.size(), s = innerLength() / sizeof(mtpPrime); to.resize(was + s); memcpy(to.data() + was, value->constData() + 8, s * sizeof(mtpPrime)); } bool mtpRequestData::isSentContainer(const mtpRequest &request) { // "request-like" wrap for msgIds vector if (request->size() < 9) return false; return (!request->msDate && !(*request)[6]); // msDate = 0, seqNo = 0 } bool mtpRequestData::isStateRequest(const mtpRequest &request) { if (request->size() < 9) return false; return (mtpTypeId((*request)[8]) == mtpc_msgs_state_req); } bool mtpRequestData::needAckByType(mtpTypeId type) { switch (type) { case mtpc_msg_container: case mtpc_msgs_ack: case mtpc_http_wait: case mtpc_bad_msg_notification: case mtpc_msgs_all_info: case mtpc_msgs_state_info: case mtpc_msg_detailed_info: case mtpc_msg_new_detailed_info: return false; } return true; } mtpRequest mtpRequestData::prepare(uint32 requestSize, uint32 maxSize) { if (!maxSize) maxSize = requestSize; mtpRequest result(new mtpRequestData(true)); result->reserve(8 + maxSize + _padding(maxSize)); // 2: salt, 2: session_id, 2: msg_id, 1: seq_no, 1: message_length result->resize(7); result->push_back(requestSize << 2); return result; } void mtpRequestData::padding(mtpRequest &request) { if (request->size() < 9) return; uint32 requestSize = (request.innerLength() >> 2), padding = _padding(requestSize), fullSize = 8 + requestSize + padding; // 2: salt, 2: session_id, 2: msg_id, 1: seq_no, 1: message_length if (uint32(request->size()) != fullSize) { request->resize(fullSize); if (padding) { memset_rand(request->data() + (fullSize - padding), padding * sizeof(mtpPrime)); } } } uint32 mtpRequestData::_padding(uint32 requestSize) { #ifdef TDESKTOP_MTPROTO_OLD return ((8 + requestSize) & 0x03) ? (4 - ((8 + requestSize) & 0x03)) : 0; #else // TDESKTOP_MTPROTO_OLD auto result = ((8 + requestSize) & 0x03) ? (4 - ((8 + requestSize) & 0x03)) : 0; // At least 12 bytes of random padding. if (result < 3) { result += 4; } return result; #endif // TDESKTOP_MTPROTO_OLD } void mtpTextSerializeCore(MTPStringLogger &to, const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons, uint32 level, mtpPrime vcons) { switch (mtpTypeId(cons)) { case mtpc_int: { MTPint value; value.read(from, end, cons); to.add(QString::number(value.v)).add(" [INT]"); } break; case mtpc_long: { MTPlong value; value.read(from, end, cons); to.add(QString::number(value.v)).add(" [LONG]"); } break; case mtpc_int128: { MTPint128 value; value.read(from, end, cons); to.add(QString::number(value.h)).add(" * 2^64 + ").add(QString::number(value.l)).add(" [INT128]"); } break; case mtpc_int256: { MTPint256 value; value.read(from, end, cons); to.add(QString::number(value.h.h)).add(" * 2^192 + ").add(QString::number(value.h.l)).add(" * 2^128 + ").add(QString::number(value.l.h)).add(" * 2 ^ 64 + ").add(QString::number(value.l.l)).add(" [INT256]"); } break; case mtpc_double: { MTPdouble value; value.read(from, end, cons); to.add(QString::number(value.v)).add(" [DOUBLE]"); } break; case mtpc_string: { MTPstring value; value.read(from, end, cons); auto strUtf8 = value.v; auto str = QString::fromUtf8(strUtf8); if (str.toUtf8() == strUtf8) { to.add("\"").add(str.replace('\\', "\\\\").replace('"', "\\\"").replace('\n', "\\n")).add("\" [STRING]"); } else if (strUtf8.size() < 64) { to.add(Logs::mb(strUtf8.constData(), strUtf8.size()).str()).add(" [").add(QString::number(strUtf8.size())).add(" BYTES]"); } else { to.add(Logs::mb(strUtf8.constData(), 16).str()).add("... [").add(QString::number(strUtf8.size())).add(" BYTES]"); } } break; case mtpc_vector: { if (from >= end) { throw Exception("from >= end in vector"); } int32 cnt = *(from++); to.add("[ vector<0x").add(QString::number(vcons, 16)).add(">"); if (cnt) { to.add("\n").addSpaces(level); for (int32 i = 0; i < cnt; ++i) { to.add(" "); mtpTextSerializeType(to, from, end, vcons, level + 1); to.add(",\n").addSpaces(level); } } else { to.add(" "); } to.add("]"); } break; case mtpc_gzip_packed: { MTPstring packed; packed.read(from, end); // read packed string as serialized mtp string type uint32 packedLen = packed.v.size(), unpackedChunk = packedLen; mtpBuffer result; // * 4 because of mtpPrime type result.resize(0); z_stream stream; stream.zalloc = 0; stream.zfree = 0; stream.opaque = 0; stream.avail_in = 0; stream.next_in = 0; int res = inflateInit2(&stream, 16 + MAX_WBITS); if (res != Z_OK) { throw Exception(QString("ungzip init, code: %1").arg(res)); } stream.avail_in = packedLen; stream.next_in = reinterpret_cast<Bytef*>(packed.v.data()); stream.avail_out = 0; while (!stream.avail_out) { result.resize(result.size() + unpackedChunk); stream.avail_out = unpackedChunk * sizeof(mtpPrime); stream.next_out = (Bytef*)&result[result.size() - unpackedChunk]; int res = inflate(&stream, Z_NO_FLUSH); if (res != Z_OK && res != Z_STREAM_END) { inflateEnd(&stream); throw Exception(QString("ungzip unpack, code: %1").arg(res)); } } if (stream.avail_out & 0x03) { uint32 badSize = result.size() * sizeof(mtpPrime) - stream.avail_out; throw Exception(QString("ungzip bad length, size: %1").arg(badSize)); } result.resize(result.size() - (stream.avail_out >> 2)); inflateEnd(&stream); if (!result.size()) { throw Exception("ungzip void data"); } const mtpPrime *newFrom = result.constData(), *newEnd = result.constData() + result.size(); to.add("[GZIPPED] "); mtpTextSerializeType(to, newFrom, newEnd, 0, level); } break; default: { for (uint32 i = 1; i < mtpLayerMaxSingle; ++i) { if (cons == mtpLayers[i]) { to.add("[LAYER").add(QString::number(i + 1)).add("] "); mtpTextSerializeType(to, from, end, 0, level); return; } } if (cons == mtpc_invokeWithLayer) { if (from >= end) { throw Exception("from >= end in invokeWithLayer"); } int32 layer = *(from++); to.add("[LAYER").add(QString::number(layer)).add("] "); mtpTextSerializeType(to, from, end, 0, level); return; } throw Exception(QString("unknown cons 0x%1").arg(cons, 0, 16)); } break; } } �������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/mtproto/core_types.h�������������������������������������������0000664�0000000�0000000�00000047417�13262451251�0023471�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "core/basic_types.h" #include "base/flags.h" namespace MTP { // type DcId represents actual data center id, while in most cases // we use some shifted ids, like DcId() + X * DCShift using DcId = int32; using ShiftedDcId = int32; } // namespace MTP using mtpPrime = int32; using mtpRequestId = int32; using mtpMsgId = uint64; using mtpPingId = uint64; using mtpBuffer = QVector<mtpPrime>; using mtpTypeId = uint32; class mtpRequestData; class mtpRequest : public std::shared_ptr<mtpRequestData> { public: mtpRequest() = default; explicit mtpRequest(mtpRequestData *ptr) : std::shared_ptr<mtpRequestData>(ptr) { } uint32 innerLength() const; void write(mtpBuffer &to) const; using ResponseType = void; // don't know real response type =( }; class mtpRequestData : public mtpBuffer { public: // in toSend: = 0 - must send in container, > 0 - can send without container // in haveSent: = 0 - container with msgIds, > 0 - when was sent TimeMs msDate = 0; mtpRequestId requestId = 0; mtpRequest after; bool needsLayer = false; mtpRequestData(bool/* sure*/) { } static mtpRequest prepare(uint32 requestSize, uint32 maxSize = 0); static void padding(mtpRequest &request); template <typename TRequest> static mtpRequest serialize(const TRequest &request) { const auto requestSize = request.innerLength() >> 2; auto serialized = prepare(requestSize); request.write(*serialized); return serialized; } static uint32 messageSize(const mtpRequest &request) { if (request->size() < 9) return 0; return 4 + (request.innerLength() >> 2); // 2: msg_id, 1: seq_no, q: message_length } static bool isSentContainer(const mtpRequest &request); // "request-like" wrap for msgIds vector static bool isStateRequest(const mtpRequest &request); static bool needAck(const mtpRequest &request) { if (request->size() < 9) return false; return mtpRequestData::needAckByType((*request)[8]); } static bool needAckByType(mtpTypeId type); private: static uint32 _padding(uint32 requestSize); }; using mtpPreRequestMap = QMap<mtpRequestId, mtpRequest>; using mtpRequestMap = QMap<mtpMsgId, mtpRequest>; using mtpMsgIdsSet = QMap<mtpMsgId, bool>; class mtpRequestIdsMap : public QMap<mtpMsgId, mtpRequestId> { public: using ParentType = QMap<mtpMsgId, mtpRequestId>; mtpMsgId min() const { return size() ? cbegin().key() : 0; } mtpMsgId max() const { ParentType::const_iterator e(cend()); return size() ? (--e).key() : 0; } }; class mtpErrorUnexpected : public Exception { public: mtpErrorUnexpected(mtpTypeId typeId, const QString &type) : Exception(QString("MTP Unexpected type id #%1 read in %2").arg(uint32(typeId), 0, 16).arg(type), false) { // maybe api changed?.. } }; class mtpErrorInsufficient : public Exception { public: mtpErrorInsufficient() : Exception("MTP Insufficient bytes in input buffer") { } }; class mtpErrorBadTypeId : public Exception { public: mtpErrorBadTypeId(mtpTypeId typeId, const QString &type) : Exception(QString("MTP Bad type id %1 passed to constructor of %2").arg(typeId).arg(type)) { } }; namespace MTP { namespace internal { class TypeData { public: TypeData() = default; TypeData(const TypeData &other) = delete; TypeData(TypeData &&other) = delete; TypeData &operator=(const TypeData &other) = delete; TypeData &operator=(TypeData &&other) = delete; virtual ~TypeData() { } private: void incrementCounter() const { _counter.ref(); } bool decrementCounter() const { return _counter.deref(); } friend class TypeDataOwner; mutable QAtomicInt _counter = { 1 }; }; class TypeDataOwner { public: TypeDataOwner(TypeDataOwner &&other) : _data(base::take(other._data)) { } TypeDataOwner(const TypeDataOwner &other) : _data(other._data) { incrementCounter(); } TypeDataOwner &operator=(TypeDataOwner &&other) { if (other._data != _data) { decrementCounter(); _data = base::take(other._data); } return *this; } TypeDataOwner &operator=(const TypeDataOwner &other) { if (other._data != _data) { setData(other._data); incrementCounter(); } return *this; } ~TypeDataOwner() { decrementCounter(); } protected: TypeDataOwner() = default; TypeDataOwner(const TypeData *data) : _data(data) { } void setData(const TypeData *data) { decrementCounter(); _data = data; } // Unsafe cast, type should be checked by the caller. template <typename DataType> const DataType &queryData() const { Expects(_data != nullptr); return static_cast<const DataType &>(*_data); } private: void incrementCounter() { if (_data) { _data->incrementCounter(); } } void decrementCounter() { if (_data && !_data->decrementCounter()) { delete base::take(_data); } } const TypeData * _data = nullptr; }; } // namespace internal } // namespace MTP enum { // core types mtpc_int = 0xa8509bda, mtpc_long = 0x22076cba, mtpc_int128 = 0x4bb5362b, mtpc_int256 = 0x929c32f, mtpc_double = 0x2210c154, mtpc_string = 0xb5286e24, mtpc_vector = 0x1cb5c415, // layers mtpc_invokeWithLayer1 = 0x53835315, mtpc_invokeWithLayer2 = 0x289dd1f6, mtpc_invokeWithLayer3 = 0xb7475268, mtpc_invokeWithLayer4 = 0xdea0d430, mtpc_invokeWithLayer5 = 0x417a57ae, mtpc_invokeWithLayer6 = 0x3a64d54d, mtpc_invokeWithLayer7 = 0xa5be56d3, mtpc_invokeWithLayer8 = 0xe9abd9fd, mtpc_invokeWithLayer9 = 0x76715a63, mtpc_invokeWithLayer10 = 0x39620c41, mtpc_invokeWithLayer11 = 0xa6b88fdf, mtpc_invokeWithLayer12 = 0xdda60d3c, mtpc_invokeWithLayer13 = 0x427c8ea2, mtpc_invokeWithLayer14 = 0x2b9b08fa, mtpc_invokeWithLayer15 = 0xb4418b64, mtpc_invokeWithLayer16 = 0xcf5f0987, mtpc_invokeWithLayer17 = 0x50858a19, mtpc_invokeWithLayer18 = 0x1c900537, // manually parsed mtpc_rpc_result = 0xf35c6d01, mtpc_msg_container = 0x73f1f8dc, // mtpc_msg_copy = 0xe06046b2, mtpc_gzip_packed = 0x3072cfa1 }; static const mtpTypeId mtpc_bytes = mtpc_string; static const mtpTypeId mtpc_flags = mtpc_int; static const mtpTypeId mtpc_core_message = -1; // undefined type, but is used static const mtpTypeId mtpLayers[] = { mtpTypeId(mtpc_invokeWithLayer1), mtpTypeId(mtpc_invokeWithLayer2), mtpTypeId(mtpc_invokeWithLayer3), mtpTypeId(mtpc_invokeWithLayer4), mtpTypeId(mtpc_invokeWithLayer5), mtpTypeId(mtpc_invokeWithLayer6), mtpTypeId(mtpc_invokeWithLayer7), mtpTypeId(mtpc_invokeWithLayer8), mtpTypeId(mtpc_invokeWithLayer9), mtpTypeId(mtpc_invokeWithLayer10), mtpTypeId(mtpc_invokeWithLayer11), mtpTypeId(mtpc_invokeWithLayer12), mtpTypeId(mtpc_invokeWithLayer13), mtpTypeId(mtpc_invokeWithLayer14), mtpTypeId(mtpc_invokeWithLayer15), mtpTypeId(mtpc_invokeWithLayer16), mtpTypeId(mtpc_invokeWithLayer17), mtpTypeId(mtpc_invokeWithLayer18), }; static const uint32 mtpLayerMaxSingle = sizeof(mtpLayers) / sizeof(mtpLayers[0]); template <typename bareT> class MTPBoxed : public bareT { public: using bareT::bareT; MTPBoxed() = default; MTPBoxed(const MTPBoxed<bareT> &v) = default; MTPBoxed<bareT> &operator=(const MTPBoxed<bareT> &v) = default; MTPBoxed(const bareT &v) : bareT(v) { } MTPBoxed<bareT> &operator=(const bareT &v) { *((bareT*)this) = v; return *this; } uint32 innerLength() const { return sizeof(mtpTypeId) + bareT::innerLength(); } void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) { if (from + 1 > end) throw mtpErrorInsufficient(); cons = (mtpTypeId)*(from++); bareT::read(from, end, cons); } void write(mtpBuffer &to) const { to.push_back(bareT::type()); bareT::write(to); } using Unboxed = bareT; }; template <typename T> class MTPBoxed<MTPBoxed<T> > { typename T::CantMakeBoxedBoxedType v; }; class MTPint { public: int32 v = 0; MTPint() = default; uint32 innerLength() const { return sizeof(int32); } mtpTypeId type() const { return mtpc_int; } void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_int) { if (from + 1 > end) throw mtpErrorInsufficient(); if (cons != mtpc_int) throw mtpErrorUnexpected(cons, "MTPint"); v = (int32)*(from++); } void write(mtpBuffer &to) const { to.push_back((mtpPrime)v); } private: explicit MTPint(int32 val) : v(val) { } friend MTPint MTP_int(int32 v); }; inline MTPint MTP_int(int32 v) { return MTPint(v); } using MTPInt = MTPBoxed<MTPint>; namespace internal { struct ZeroFlagsHelper { }; } // namespace internal template <typename Flags> class MTPflags { public: Flags v = 0; static_assert(sizeof(Flags) == sizeof(int32), "MTPflags are allowed only wrapping int32 flag types!"); MTPflags() = default; MTPflags(internal::ZeroFlagsHelper helper) { } uint32 innerLength() const { return sizeof(Flags); } mtpTypeId type() const { return mtpc_flags; } void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_flags) { if (from + 1 > end) throw mtpErrorInsufficient(); if (cons != mtpc_flags) throw mtpErrorUnexpected(cons, "MTPflags"); v = Flags::from_raw(static_cast<typename Flags::Type>(*(from++))); } void write(mtpBuffer &to) const { to.push_back(static_cast<mtpPrime>(v.value())); } private: explicit MTPflags(Flags val) : v(val) { } template <typename T> friend MTPflags<base::flags<T>> MTP_flags(base::flags<T> v); template <typename T, typename> friend MTPflags<base::flags<T>> MTP_flags(T v); }; template <typename T> inline MTPflags<base::flags<T>> MTP_flags(base::flags<T> v) { return MTPflags<base::flags<T>>(v); } template <typename T, typename = std::enable_if_t<!std::is_same<T, int>::value>> inline MTPflags<base::flags<T>> MTP_flags(T v) { return MTPflags<base::flags<T>>(v); } inline internal::ZeroFlagsHelper MTP_flags(void(internal::ZeroFlagsHelper::*)()) { return internal::ZeroFlagsHelper(); } template <typename Flags> using MTPFlags = MTPBoxed<MTPflags<Flags>>; inline bool operator==(const MTPint &a, const MTPint &b) { return a.v == b.v; } inline bool operator!=(const MTPint &a, const MTPint &b) { return a.v != b.v; } class MTPlong { public: uint64 v = 0; MTPlong() = default; uint32 innerLength() const { return sizeof(uint64); } mtpTypeId type() const { return mtpc_long; } void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_long) { if (from + 2 > end) throw mtpErrorInsufficient(); if (cons != mtpc_long) throw mtpErrorUnexpected(cons, "MTPlong"); v = (uint64)(((uint32*)from)[0]) | ((uint64)(((uint32*)from)[1]) << 32); from += 2; } void write(mtpBuffer &to) const { to.push_back((mtpPrime)(v & 0xFFFFFFFFL)); to.push_back((mtpPrime)(v >> 32)); } private: explicit MTPlong(uint64 val) : v(val) { } friend MTPlong MTP_long(uint64 v); }; inline MTPlong MTP_long(uint64 v) { return MTPlong(v); } using MTPLong = MTPBoxed<MTPlong>; inline bool operator==(const MTPlong &a, const MTPlong &b) { return a.v == b.v; } inline bool operator!=(const MTPlong &a, const MTPlong &b) { return a.v != b.v; } class MTPint128 { public: uint64 l = 0; uint64 h = 0; MTPint128() = default; uint32 innerLength() const { return sizeof(uint64) + sizeof(uint64); } mtpTypeId type() const { return mtpc_int128; } void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_int128) { if (from + 4 > end) throw mtpErrorInsufficient(); if (cons != mtpc_int128) throw mtpErrorUnexpected(cons, "MTPint128"); l = (uint64)(((uint32*)from)[0]) | ((uint64)(((uint32*)from)[1]) << 32); h = (uint64)(((uint32*)from)[2]) | ((uint64)(((uint32*)from)[3]) << 32); from += 4; } void write(mtpBuffer &to) const { to.push_back((mtpPrime)(l & 0xFFFFFFFFL)); to.push_back((mtpPrime)(l >> 32)); to.push_back((mtpPrime)(h & 0xFFFFFFFFL)); to.push_back((mtpPrime)(h >> 32)); } private: explicit MTPint128(uint64 low, uint64 high) : l(low), h(high) { } friend MTPint128 MTP_int128(uint64 l, uint64 h); }; inline MTPint128 MTP_int128(uint64 l, uint64 h) { return MTPint128(l, h); } using MTPInt128 = MTPBoxed<MTPint128>; inline bool operator==(const MTPint128 &a, const MTPint128 &b) { return a.l == b.l && a.h == b.h; } inline bool operator!=(const MTPint128 &a, const MTPint128 &b) { return a.l != b.l || a.h != b.h; } class MTPint256 { public: MTPint128 l; MTPint128 h; MTPint256() = default; uint32 innerLength() const { return l.innerLength() + h.innerLength(); } mtpTypeId type() const { return mtpc_int256; } void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_int256) { if (cons != mtpc_int256) throw mtpErrorUnexpected(cons, "MTPint256"); l.read(from, end); h.read(from, end); } void write(mtpBuffer &to) const { l.write(to); h.write(to); } private: explicit MTPint256(MTPint128 low, MTPint128 high) : l(low), h(high) { } friend MTPint256 MTP_int256(const MTPint128 &l, const MTPint128 &h); }; inline MTPint256 MTP_int256(const MTPint128 &l, const MTPint128 &h) { return MTPint256(l, h); } using MTPInt256 = MTPBoxed<MTPint256>; inline bool operator==(const MTPint256 &a, const MTPint256 &b) { return a.l == b.l && a.h == b.h; } inline bool operator!=(const MTPint256 &a, const MTPint256 &b) { return a.l != b.l || a.h != b.h; } class MTPdouble { public: float64 v = 0.; MTPdouble() = default; uint32 innerLength() const { return sizeof(float64); } mtpTypeId type() const { return mtpc_double; } void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_double) { if (from + 2 > end) throw mtpErrorInsufficient(); if (cons != mtpc_double) throw mtpErrorUnexpected(cons, "MTPdouble"); *(uint64*)(&v) = (uint64)(((uint32*)from)[0]) | ((uint64)(((uint32*)from)[1]) << 32); from += 2; } void write(mtpBuffer &to) const { uint64 iv = *(uint64*)(&v); to.push_back((mtpPrime)(iv & 0xFFFFFFFFL)); to.push_back((mtpPrime)(iv >> 32)); } private: explicit MTPdouble(float64 val) : v(val) { } friend MTPdouble MTP_double(float64 v); }; inline MTPdouble MTP_double(float64 v) { return MTPdouble(v); } using MTPDouble = MTPBoxed<MTPdouble>; inline bool operator==(const MTPdouble &a, const MTPdouble &b) { return a.v == b.v; } inline bool operator!=(const MTPdouble &a, const MTPdouble &b) { return a.v != b.v; } class MTPstring; using MTPbytes = MTPstring; class MTPstring { public: MTPstring() = default; uint32 innerLength() const; mtpTypeId type() const { return mtpc_string; } void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_string); void write(mtpBuffer &to) const; QByteArray v; private: explicit MTPstring(QByteArray &&data) : v(std::move(data)) { } friend MTPstring MTP_string(const std::string &v); friend MTPstring MTP_string(const QString &v); friend MTPstring MTP_string(const char *v); friend MTPbytes MTP_bytes(const QByteArray &v); friend MTPbytes MTP_bytes(QByteArray &&v); }; using MTPString = MTPBoxed<MTPstring>; using MTPBytes = MTPBoxed<MTPbytes>; inline MTPstring MTP_string(const std::string &v) { return MTPstring(QByteArray(v.data(), v.size())); } inline MTPstring MTP_string(const QString &v) { return MTPstring(v.toUtf8()); } inline MTPstring MTP_string(const char *v) { return MTPstring(QByteArray(v, strlen(v))); } MTPstring MTP_string(const QByteArray &v) = delete; inline MTPbytes MTP_bytes(const QByteArray &v) { return MTPbytes(QByteArray(v)); } inline MTPbytes MTP_bytes(QByteArray &&v) { return MTPbytes(std::move(v)); } inline MTPbytes MTP_bytes(base::const_byte_span bytes) { return MTP_bytes(QByteArray(reinterpret_cast<const char*>(bytes.data()), bytes.size())); } inline MTPbytes MTP_bytes(const std::vector<gsl::byte> &bytes) { return MTP_bytes(gsl::make_span(bytes)); } template <size_t N> inline MTPbytes MTP_bytes(const std::array<gsl::byte, N> &bytes) { return MTP_bytes(gsl::make_span(bytes)); } inline bool operator==(const MTPstring &a, const MTPstring &b) { return a.v == b.v; } inline bool operator!=(const MTPstring &a, const MTPstring &b) { return a.v != b.v; } inline QString qs(const MTPstring &v) { return QString::fromUtf8(v.v); } inline QByteArray qba(const MTPstring &v) { return v.v; } inline base::const_byte_span bytesFromMTP(const MTPbytes &v) { return gsl::as_bytes(gsl::make_span(v.v)); } inline std::vector<gsl::byte> byteVectorFromMTP(const MTPbytes &v) { auto bytes = bytesFromMTP(v); return std::vector<gsl::byte>(bytes.cbegin(), bytes.cend()); } template <typename T> class MTPvector { public: MTPvector() = default; uint32 innerLength() const { uint32 result(sizeof(uint32)); for_const (auto &item, v) { result += item.innerLength(); } return result; } mtpTypeId type() const { return mtpc_vector; } void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_vector) { if (from + 1 > end) throw mtpErrorInsufficient(); if (cons != mtpc_vector) throw mtpErrorUnexpected(cons, "MTPvector"); auto count = static_cast<uint32>(*(from++)); auto vector = QVector<T>(count, T()); for (auto &item : vector) { item.read(from, end); } v = std::move(vector); } void write(mtpBuffer &to) const { to.push_back(v.size()); for_const (auto &item, v) { item.write(to); } } QVector<T> v; private: explicit MTPvector(QVector<T> &&data) : v(std::move(data)) { } template <typename U> friend MTPvector<U> MTP_vector(uint32 count); template <typename U> friend MTPvector<U> MTP_vector(uint32 count, const U &value); template <typename U> friend MTPvector<U> MTP_vector(const QVector<U> &v); template <typename U> friend MTPvector<U> MTP_vector(QVector<U> &&v); }; template <typename T> inline MTPvector<T> MTP_vector(uint32 count) { return MTPvector<T>(QVector<T>(count)); } template <typename T> inline MTPvector<T> MTP_vector(uint32 count, const T &value) { return MTPvector<T>(QVector<T>(count, value)); } template <typename T> inline MTPvector<T> MTP_vector(const QVector<T> &v) { return MTPvector<T>(QVector<T>(v)); } template <typename T> inline MTPvector<T> MTP_vector(QVector<T> &&v) { return MTPvector<T>(std::move(v)); } template <typename T> using MTPVector = MTPBoxed<MTPvector<T>>; template <typename T> inline bool operator==(const MTPvector<T> &a, const MTPvector<T> &b) { return a.c_vector().v == b.c_vector().v; } template <typename T> inline bool operator!=(const MTPvector<T> &a, const MTPvector<T> &b) { return a.c_vector().v != b.c_vector().v; } // Human-readable text serialization struct MTPStringLogger { MTPStringLogger() : p(new char[MTPDebugBufferSize]), size(0), alloced(MTPDebugBufferSize) { } ~MTPStringLogger() { delete[] p; } MTPStringLogger &add(const QString &data) { auto d = data.toUtf8(); return add(d.constData(), d.size()); } MTPStringLogger &add(const char *data, int32 len = -1) { if (len < 0) len = strlen(data); if (!len) return (*this); ensureLength(len); memcpy(p + size, data, len); size += len; return (*this); } MTPStringLogger &addSpaces(int32 level) { int32 len = level * 2; if (!len) return (*this); ensureLength(len); for (char *ptr = p + size, *end = ptr + len; ptr != end; ++ptr) { *ptr = ' '; } size += len; return (*this); } void ensureLength(int32 add) { if (size + add <= alloced) return; int32 newsize = size + add; if (newsize % MTPDebugBufferSize) newsize += MTPDebugBufferSize - (newsize % MTPDebugBufferSize); char *b = new char[newsize]; memcpy(b, p, size); alloced = newsize; delete[] p; p = b; } char *p; int32 size, alloced; }; void mtpTextSerializeType(MTPStringLogger &to, const mtpPrime *&from, const mtpPrime *end, mtpPrime cons = 0, uint32 level = 0, mtpPrime vcons = 0); void mtpTextSerializeCore(MTPStringLogger &to, const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons, uint32 level, mtpPrime vcons = 0); inline QString mtpTextSerialize(const mtpPrime *&from, const mtpPrime *end) { MTPStringLogger to; try { mtpTextSerializeType(to, from, end, mtpc_core_message); } catch (Exception &e) { to.add("[ERROR] (").add(e.what()).add(")"); } return QString::fromUtf8(to.p, to.size); } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/mtproto/dc_options.cpp�����������������������������������������0000664�0000000�0000000�00000054753�13262451251�0024012�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "mtproto/dc_options.h" #include "storage/serialize_common.h" namespace MTP { namespace { const char *(PublicRSAKeys[]) = { "\ -----BEGIN RSA PUBLIC KEY-----\n\ MIIBCgKCAQEAwVACPi9w23mF3tBkdZz+zwrzKOaaQdr01vAbU4E1pvkfj4sqDsm6\n\ lyDONS789sVoD/xCS9Y0hkkC3gtL1tSfTlgCMOOul9lcixlEKzwKENj1Yz/s7daS\n\ an9tqw3bfUV/nqgbhGX81v/+7RFAEd+RwFnK7a+XYl9sluzHRyVVaTTveB2GazTw\n\ Efzk2DWgkBluml8OREmvfraX3bkHZJTKX4EQSjBbbdJ2ZXIsRrYOXfaA+xayEGB+\n\ 8hdlLmAjbCVfaigxX0CDqWeR1yFL9kwd9P0NsZRPsmoqVwMbMu7mStFai6aIhc3n\n\ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB\n\ -----END RSA PUBLIC KEY-----", "\ -----BEGIN PUBLIC KEY-----\n\ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAruw2yP/BCcsJliRoW5eB\n\ VBVle9dtjJw+OYED160Wybum9SXtBBLXriwt4rROd9csv0t0OHCaTmRqBcQ0J8fx\n\ hN6/cpR1GWgOZRUAiQxoMnlt0R93LCX/j1dnVa/gVbCjdSxpbrfY2g2L4frzjJvd\n\ l84Kd9ORYjDEAyFnEA7dD556OptgLQQ2e2iVNq8NZLYTzLp5YpOdO1doK+ttrltg\n\ gTCy5SrKeLoCPPbOgGsdxJxyz5KKcZnSLj16yE5HvJQn0CNpRdENvRUXe6tBP78O\n\ 39oJ8BTHp9oIjd6XWXAsp2CvK45Ol8wFXGF710w9lwCGNbmNxNYhtIkdqfsEcwR5\n\ JwIDAQAB\n\ -----END PUBLIC KEY-----", "\ -----BEGIN PUBLIC KEY-----\n\ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvfLHfYH2r9R70w8prHbl\n\ Wt/nDkh+XkgpflqQVcnAfSuTtO05lNPspQmL8Y2XjVT4t8cT6xAkdgfmmvnvRPOO\n\ KPi0OfJXoRVylFzAQG/j83u5K3kRLbae7fLccVhKZhY46lvsueI1hQdLgNV9n1cQ\n\ 3TDS2pQOCtovG4eDl9wacrXOJTG2990VjgnIKNA0UMoP+KF03qzryqIt3oTvZq03\n\ DyWdGK+AZjgBLaDKSnC6qD2cFY81UryRWOab8zKkWAnhw2kFpcqhI0jdV5QaSCEx\n\ vnsjVaX0Y1N0870931/5Jb9ICe4nweZ9kSDF/gip3kWLG0o8XQpChDfyvsqB9OLV\n\ /wIDAQAB\n\ -----END PUBLIC KEY-----", "\ -----BEGIN PUBLIC KEY-----\n\ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs/ditzm+mPND6xkhzwFI\n\ z6J/968CtkcSE/7Z2qAJiXbmZ3UDJPGrzqTDHkO30R8VeRM/Kz2f4nR05GIFiITl\n\ 4bEjvpy7xqRDspJcCFIOcyXm8abVDhF+th6knSU0yLtNKuQVP6voMrnt9MV1X92L\n\ GZQLgdHZbPQz0Z5qIpaKhdyA8DEvWWvSUwwc+yi1/gGaybwlzZwqXYoPOhwMebzK\n\ Uk0xW14htcJrRrq+PXXQbRzTMynseCoPIoke0dtCodbA3qQxQovE16q9zz4Otv2k\n\ 4j63cz53J+mhkVWAeWxVGI0lltJmWtEYK6er8VqqWot3nqmWMXogrgRLggv/Nbbo\n\ oQIDAQAB\n\ -----END PUBLIC KEY-----", "\ -----BEGIN PUBLIC KEY-----\n\ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvmpxVY7ld/8DAjz6F6q0\n\ 5shjg8/4p6047bn6/m8yPy1RBsvIyvuDuGnP/RzPEhzXQ9UJ5Ynmh2XJZgHoE9xb\n\ nfxL5BXHplJhMtADXKM9bWB11PU1Eioc3+AXBB8QiNFBn2XI5UkO5hPhbb9mJpjA\n\ 9Uhw8EdfqJP8QetVsI/xrCEbwEXe0xvifRLJbY08/Gp66KpQvy7g8w7VB8wlgePe\n\ xW3pT13Ap6vuC+mQuJPyiHvSxjEKHgqePji9NP3tJUFQjcECqcm0yV7/2d0t/pbC\n\ m+ZH1sadZspQCEPPrtbkQBlvHb4OLiIWPGHKSMeRFvp3IWcmdJqXahxLCUS1Eh6M\n\ AQIDAQAB\n\ -----END PUBLIC KEY-----" }; } // namespace class DcOptions::WriteLocker { public: WriteLocker(DcOptions *that) : _that(that), _lock(&_that->_useThroughLockers) { } ~WriteLocker() { _that->computeCdnDcIds(); } private: not_null<DcOptions*> _that; QWriteLocker _lock; }; class DcOptions::ReadLocker { public: ReadLocker(const DcOptions *that) : _lock(&that->_useThroughLockers) { } private: QReadLocker _lock; }; void DcOptions::readBuiltInPublicKeys() { for (const auto key : PublicRSAKeys) { const auto keyBytes = gsl::make_span(key, key + strlen(key)); auto parsed = internal::RSAPublicKey(gsl::as_bytes(keyBytes)); if (parsed.isValid()) { _publicKeys.emplace(parsed.getFingerPrint(), std::move(parsed)); } else { LOG(("MTP Error: could not read this public RSA key:")); LOG((key)); } } } void DcOptions::constructFromBuiltIn() { WriteLocker lock(this); _data.clear(); readBuiltInPublicKeys(); auto bdcs = builtInDcs(); for (auto i = 0, l = builtInDcsCount(); i != l; ++i) { auto flags = MTPDdcOption::Flags(0); auto idWithShift = MTP::shiftDcId(bdcs[i].id, flags); _data.emplace(idWithShift, Option(bdcs[i].id, flags, bdcs[i].ip, bdcs[i].port)); DEBUG_LOG(("MTP Info: adding built in DC %1 connect option: %2:%3").arg(bdcs[i].id).arg(bdcs[i].ip).arg(bdcs[i].port)); } auto bdcsipv6 = builtInDcsIPv6(); for (auto i = 0, l = builtInDcsCountIPv6(); i != l; ++i) { auto flags = MTPDdcOption::Flags(MTPDdcOption::Flag::f_ipv6); auto idWithShift = MTP::shiftDcId(bdcsipv6[i].id, flags); _data.emplace(idWithShift, Option(bdcsipv6[i].id, flags, bdcsipv6[i].ip, bdcsipv6[i].port)); DEBUG_LOG(("MTP Info: adding built in DC %1 IPv6 connect option: %2:%3").arg(bdcsipv6[i].id).arg(bdcsipv6[i].ip).arg(bdcsipv6[i].port)); } } void DcOptions::processFromList(const QVector<MTPDcOption> &options, bool overwrite) { if (options.empty() || _immutable) { return; } auto idsChanged = std::vector<DcId>(); idsChanged.reserve(options.size()); auto shiftedIdsProcessed = std::vector<ShiftedDcId>(); shiftedIdsProcessed.reserve(options.size()); { WriteLocker lock(this); if (overwrite) { idsChanged.reserve(_data.size()); } for (auto &mtpOption : options) { if (mtpOption.type() != mtpc_dcOption) { LOG(("Wrong type in DcOptions: %1").arg(mtpOption.type())); continue; } auto &option = mtpOption.c_dcOption(); auto dcId = option.vid.v; auto flags = option.vflags.v; auto dcIdWithShift = MTP::shiftDcId(dcId, flags); if (base::contains(shiftedIdsProcessed, dcIdWithShift)) { continue; } shiftedIdsProcessed.push_back(dcIdWithShift); auto ip = std::string(option.vip_address.v.constData(), option.vip_address.v.size()); auto port = option.vport.v; if (applyOneGuarded(dcId, flags, ip, port)) { if (!base::contains(idsChanged, dcId)) { idsChanged.push_back(dcId); } } } if (overwrite && shiftedIdsProcessed.size() < _data.size()) { for (auto i = _data.begin(); i != _data.end();) { if (base::contains(shiftedIdsProcessed, i->first)) { ++i; } else { if (!base::contains(idsChanged, i->second.id)) { idsChanged.push_back(i->second.id); } i = _data.erase(i); } } } } if (!idsChanged.empty()) { _changed.notify(std::move(idsChanged)); } } void DcOptions::setFromList(const MTPVector<MTPDcOption> &options) { processFromList(options.v, true); } void DcOptions::addFromList(const MTPVector<MTPDcOption> &options) { processFromList(options.v, false); } void DcOptions::addFromOther(DcOptions &&options) { if (this == &options || _immutable) { return; } auto idsChanged = std::vector<DcId>(); { ReadLocker lock(&options); if (options._data.empty()) { return; } idsChanged.reserve(options._data.size()); { WriteLocker lock(this); for (auto &item : base::take(options._data)) { auto dcId = item.second.id; auto flags = item.second.flags; auto &ip = item.second.ip; auto port = item.second.port; if (applyOneGuarded(dcId, flags, ip, port)) { if (!base::contains(idsChanged, dcId)) { idsChanged.push_back(dcId); } } } for (auto &keysForDc : options._cdnPublicKeys) { for (auto &entry : keysForDc.second) { _cdnPublicKeys[keysForDc.first].insert(std::move(entry)); } } } } if (!idsChanged.empty()) { _changed.notify(std::move(idsChanged)); } } void DcOptions::constructAddOne(int id, MTPDdcOption::Flags flags, const std::string &ip, int port) { WriteLocker lock(this); applyOneGuarded(bareDcId(id), flags, ip, port); } bool DcOptions::applyOneGuarded(DcId dcId, MTPDdcOption::Flags flags, const std::string &ip, int port) { auto dcIdWithShift = MTP::shiftDcId(dcId, flags); auto i = _data.find(dcIdWithShift); if (i != _data.cend()) { if (i->second.ip == ip && i->second.port == port) { return false; } i->second.ip = ip; i->second.port = port; } else { _data.emplace(dcIdWithShift, Option(dcId, flags, ip, port)); } return true; } QByteArray DcOptions::serialize() const { if (_immutable) { // Don't write the overriden options to our settings. return DcOptions().serialize(); } ReadLocker lock(this); auto size = sizeof(qint32); for (auto &item : _data) { if (isTemporaryDcId(item.first)) { continue; } size += sizeof(qint32) + sizeof(qint32) + sizeof(qint32); // id + flags + port size += sizeof(qint32) + item.second.ip.size(); } auto count = 0; for (auto &keysInDc : _cdnPublicKeys) { count += keysInDc.second.size(); } struct SerializedPublicKey { DcId dcId; base::byte_vector n; base::byte_vector e; }; std::vector<SerializedPublicKey> publicKeys; publicKeys.reserve(count); for (auto &keysInDc : _cdnPublicKeys) { for (auto &entry : keysInDc.second) { publicKeys.push_back({ keysInDc.first, entry.second.getN(), entry.second.getE() }); size += sizeof(qint32) + Serialize::bytesSize(publicKeys.back().n) + Serialize::bytesSize(publicKeys.back().e); } } auto result = QByteArray(); result.reserve(size); { QDataStream stream(&result, QIODevice::WriteOnly); stream.setVersion(QDataStream::Qt_5_1); stream << qint32(_data.size()); for (auto &item : _data) { if (isTemporaryDcId(item.first)) { continue; } stream << qint32(item.second.id) << qint32(item.second.flags) << qint32(item.second.port); stream << qint32(item.second.ip.size()); stream.writeRawData(item.second.ip.data(), item.second.ip.size()); } stream << qint32(publicKeys.size()); for (auto &key : publicKeys) { stream << qint32(key.dcId) << Serialize::bytes(key.n) << Serialize::bytes(key.e); } } return result; } void DcOptions::constructFromSerialized(const QByteArray &serialized) { QDataStream stream(serialized); stream.setVersion(QDataStream::Qt_5_1); auto count = qint32(0); stream >> count; if (stream.status() != QDataStream::Ok) { LOG(("MTP Error: Bad data for DcOptions::constructFromSerialized()")); return; } WriteLocker lock(this); _data.clear(); for (auto i = 0; i != count; ++i) { qint32 id = 0, flags = 0, port = 0, ipSize = 0; stream >> id >> flags >> port >> ipSize; // https://stackoverflow.com/questions/1076714/max-length-for-client-ip-address constexpr auto kMaxIpSize = 45; if (ipSize <= 0 || ipSize > kMaxIpSize) { LOG(("MTP Error: Bad data inside DcOptions::constructFromSerialized()")); return; } std::string ip(ipSize, ' '); stream.readRawData(&ip[0], ipSize); if (stream.status() != QDataStream::Ok) { LOG(("MTP Error: Bad data inside DcOptions::constructFromSerialized()")); return; } applyOneGuarded(DcId(id), MTPDdcOption::Flags::from_raw(flags), ip, port); } // Read CDN config if (!stream.atEnd()) { auto count = qint32(0); stream >> count; if (stream.status() != QDataStream::Ok) { LOG(("MTP Error: Bad data for CDN config in DcOptions::constructFromSerialized()")); return; } for (auto i = 0; i != count; ++i) { qint32 dcId = 0; base::byte_vector n, e; stream >> dcId >> Serialize::bytes(n) >> Serialize::bytes(e); if (stream.status() != QDataStream::Ok) { LOG(("MTP Error: Bad data for CDN config inside DcOptions::constructFromSerialized()")); return; } auto key = internal::RSAPublicKey(n, e); if (key.isValid()) { _cdnPublicKeys[dcId].emplace(key.getFingerPrint(), std::move(key)); } else { LOG(("MTP Error: Could not read valid CDN public key.")); } } } } DcOptions::Ids DcOptions::configEnumDcIds() const { auto result = Ids(); { ReadLocker lock(this); result.reserve(_data.size()); for (auto &item : _data) { if (!isCdnDc(item.second.flags) && !isTemporaryDcId(item.first) && !base::contains(result, item.second.id)) { result.push_back(item.second.id); } } } ranges::sort(result); return result; } DcType DcOptions::dcType(ShiftedDcId shiftedDcId) const { if (isTemporaryDcId(shiftedDcId)) { return DcType::Temporary; } ReadLocker lock(this); if (_cdnDcIds.find(bareDcId(shiftedDcId)) != _cdnDcIds.cend()) { return DcType::Cdn; } if (isDownloadDcId(shiftedDcId)) { return DcType::MediaDownload; } return DcType::Regular; } void DcOptions::setCDNConfig(const MTPDcdnConfig &config) { WriteLocker lock(this); _cdnPublicKeys.clear(); for_const (auto &publicKey, config.vpublic_keys.v) { Expects(publicKey.type() == mtpc_cdnPublicKey); const auto &keyData = publicKey.c_cdnPublicKey(); const auto keyBytes = gsl::make_span(keyData.vpublic_key.v); auto key = internal::RSAPublicKey(gsl::as_bytes(keyBytes)); if (key.isValid()) { _cdnPublicKeys[keyData.vdc_id.v].emplace( key.getFingerPrint(), std::move(key)); } else { LOG(("MTP Error: could not read this public RSA key:")); LOG((qs(keyData.vpublic_key))); } } } bool DcOptions::hasCDNKeysForDc(DcId dcId) const { ReadLocker lock(this); return _cdnPublicKeys.find(dcId) != _cdnPublicKeys.cend(); } bool DcOptions::getDcRSAKey(DcId dcId, const QVector<MTPlong> &fingerprints, internal::RSAPublicKey *result) const { auto findKey = [&fingerprints, &result](const std::map<uint64, internal::RSAPublicKey> &keys) { for_const (auto &fingerprint, fingerprints) { auto it = keys.find(static_cast<uint64>(fingerprint.v)); if (it != keys.cend()) { *result = it->second; return true; } } return false; }; { ReadLocker lock(this); auto it = _cdnPublicKeys.find(dcId); if (it != _cdnPublicKeys.cend()) { return findKey(it->second); } } return findKey(_publicKeys); } DcOptions::Variants DcOptions::lookup(DcId dcId, DcType type) const { auto lookupDesiredFlags = [type](int address, int protocol) -> std::vector<MTPDdcOption::Flags> { auto throughProxy = (Global::ConnectionType() != dbictAuto); switch (type) { case DcType::Regular: case DcType::Temporary: { switch (address) { case Variants::IPv4: { switch (protocol) { case Variants::Tcp: return { // Regular TCP IPv4 throughProxy ? (MTPDdcOption::Flag::f_tcpo_only | MTPDdcOption::Flag::f_static) : (MTPDdcOption::Flag::f_tcpo_only | 0), throughProxy ? (MTPDdcOption::Flag::f_static | 0) : MTPDdcOption::Flags(0), (MTPDdcOption::Flag::f_tcpo_only | 0), 0 }; case Variants::Http: return { // Regular HTTP IPv4 throughProxy ? (MTPDdcOption::Flag::f_static | 0) : MTPDdcOption::Flags(0), 0, }; } } break; case Variants::IPv6: { switch (protocol) { case Variants::Tcp: return { // Regular TCP IPv6 throughProxy ? (MTPDdcOption::Flag::f_tcpo_only | MTPDdcOption::Flag::f_ipv6 | MTPDdcOption::Flag::f_static) : (MTPDdcOption::Flag::f_tcpo_only | MTPDdcOption::Flag::f_ipv6), throughProxy ? (MTPDdcOption::Flag::f_ipv6 | MTPDdcOption::Flag::f_static) : (MTPDdcOption::Flag::f_ipv6 | 0), (MTPDdcOption::Flag::f_tcpo_only | MTPDdcOption::Flag::f_ipv6), (MTPDdcOption::Flag::f_ipv6 | 0), }; case Variants::Http: return { // Regular HTTP IPv6 throughProxy ? (MTPDdcOption::Flag::f_ipv6 | MTPDdcOption::Flag::f_static) : (MTPDdcOption::Flag::f_ipv6 | 0), (MTPDdcOption::Flag::f_ipv6 | 0), }; } } break; } } break; case DcType::MediaDownload: { switch (address) { case Variants::IPv4: { switch (protocol) { case Variants::Tcp: return { // Media download TCP IPv4 throughProxy ? (MTPDdcOption::Flag::f_media_only | MTPDdcOption::Flag::f_tcpo_only | MTPDdcOption::Flag::f_static) : (MTPDdcOption::Flag::f_media_only | MTPDdcOption::Flag::f_tcpo_only), throughProxy ? (MTPDdcOption::Flag::f_media_only | MTPDdcOption::Flag::f_static) : (MTPDdcOption::Flag::f_tcpo_only | 0), (MTPDdcOption::Flag::f_media_only | 0), throughProxy ? (MTPDdcOption::Flag::f_static | 0) : MTPDdcOption::Flags(0), (MTPDdcOption::Flag::f_media_only | MTPDdcOption::Flag::f_tcpo_only), throughProxy ? (MTPDdcOption::Flag::f_tcpo_only | MTPDdcOption::Flag::f_static) : (MTPDdcOption::Flag::f_tcpo_only | 0), throughProxy ? (MTPDdcOption::Flag::f_tcpo_only | 0) : (MTPDdcOption::Flag::f_media_only | 0), 0, }; case Variants::Http: return { // Media download HTTP IPv4 throughProxy ? (MTPDdcOption::Flag::f_media_only | MTPDdcOption::Flag::f_static) : (MTPDdcOption::Flag::f_media_only | 0), (MTPDdcOption::Flag::f_media_only | 0), throughProxy ? (MTPDdcOption::Flag::f_static | 0) : MTPDdcOption::Flags(0), 0, }; } } break; case Variants::IPv6: { switch (protocol) { case Variants::Tcp: return { // Media download TCP IPv6 throughProxy ? (MTPDdcOption::Flag::f_media_only | MTPDdcOption::Flag::f_tcpo_only | MTPDdcOption::Flag::f_ipv6 | MTPDdcOption::Flag::f_static) : (MTPDdcOption::Flag::f_media_only | MTPDdcOption::Flag::f_tcpo_only | MTPDdcOption::Flag::f_ipv6), throughProxy ? (MTPDdcOption::Flag::f_media_only | MTPDdcOption::Flag::f_ipv6 | MTPDdcOption::Flag::f_static) : (MTPDdcOption::Flag::f_tcpo_only | MTPDdcOption::Flag::f_ipv6), (MTPDdcOption::Flag::f_media_only | MTPDdcOption::Flag::f_ipv6), throughProxy ? (MTPDdcOption::Flag::f_ipv6 | MTPDdcOption::Flag::f_static) : (MTPDdcOption::Flag::f_ipv6 | 0), (MTPDdcOption::Flag::f_media_only | MTPDdcOption::Flag::f_tcpo_only | MTPDdcOption::Flag::f_ipv6), throughProxy ? (MTPDdcOption::Flag::f_tcpo_only | MTPDdcOption::Flag::f_ipv6 | MTPDdcOption::Flag::f_static) : (MTPDdcOption::Flag::f_tcpo_only | MTPDdcOption::Flag::f_ipv6), throughProxy ? (MTPDdcOption::Flag::f_tcpo_only | MTPDdcOption::Flag::f_ipv6) : (MTPDdcOption::Flag::f_media_only | MTPDdcOption::Flag::f_ipv6), (MTPDdcOption::Flag::f_ipv6 | 0) }; case Variants::Http: return { // Media download HTTP IPv6 throughProxy ? (MTPDdcOption::Flag::f_media_only | MTPDdcOption::Flag::f_ipv6 | MTPDdcOption::Flag::f_static) : (MTPDdcOption::Flag::f_media_only | MTPDdcOption::Flag::f_ipv6), (MTPDdcOption::Flag::f_media_only | MTPDdcOption::Flag::f_ipv6), throughProxy ? (MTPDdcOption::Flag::f_ipv6 | MTPDdcOption::Flag::f_static) : (MTPDdcOption::Flag::f_ipv6 | 0), (MTPDdcOption::Flag::f_ipv6 | 0), }; } } break; } } break; case DcType::Cdn: { switch (address) { case Variants::IPv4: { switch (protocol) { case Variants::Tcp: return { // CDN TCP IPv4 throughProxy ? (MTPDdcOption::Flag::f_cdn | MTPDdcOption::Flag::f_tcpo_only | MTPDdcOption::Flag::f_static) : (MTPDdcOption::Flag::f_cdn | MTPDdcOption::Flag::f_tcpo_only), throughProxy ? (MTPDdcOption::Flag::f_cdn | MTPDdcOption::Flag::f_static) : (MTPDdcOption::Flag::f_cdn | 0), (MTPDdcOption::Flag::f_cdn | MTPDdcOption::Flag::f_tcpo_only), (MTPDdcOption::Flag::f_cdn | 0), }; case Variants::Http: return { // CDN HTTP IPv4 throughProxy ? (MTPDdcOption::Flag::f_cdn | MTPDdcOption::Flag::f_static) : (MTPDdcOption::Flag::f_cdn | 0), (MTPDdcOption::Flag::f_cdn | 0), }; } } break; case Variants::IPv6: { switch (protocol) { case Variants::Tcp: return { // CDN TCP IPv6 throughProxy ? (MTPDdcOption::Flag::f_cdn | MTPDdcOption::Flag::f_ipv6 | MTPDdcOption::Flag::f_tcpo_only | MTPDdcOption::Flag::f_static) : (MTPDdcOption::Flag::f_cdn | MTPDdcOption::Flag::f_ipv6 | MTPDdcOption::Flag::f_tcpo_only), throughProxy ? (MTPDdcOption::Flag::f_cdn | MTPDdcOption::Flag::f_ipv6 | MTPDdcOption::Flag::f_static) : (MTPDdcOption::Flag::f_cdn | MTPDdcOption::Flag::f_ipv6), (MTPDdcOption::Flag::f_cdn | MTPDdcOption::Flag::f_tcpo_only | MTPDdcOption::Flag::f_ipv6), (MTPDdcOption::Flag::f_cdn | MTPDdcOption::Flag::f_ipv6), }; case Variants::Http: return { // CDN HTTP IPv6 throughProxy ? (MTPDdcOption::Flag::f_cdn | MTPDdcOption::Flag::f_ipv6 | MTPDdcOption::Flag::f_static) : (MTPDdcOption::Flag::f_cdn | MTPDdcOption::Flag::f_ipv6), (MTPDdcOption::Flag::f_cdn | MTPDdcOption::Flag::f_ipv6), }; } } break; } } break; } Unexpected("Bad type / address / protocol"); }; auto result = Variants(); { ReadLocker lock(this); for (auto address = 0; address != Variants::AddressTypeCount; ++address) { for (auto protocol = 0; protocol != Variants::ProtocolCount; ++protocol) { auto desiredFlags = lookupDesiredFlags(address, protocol); for (auto flags : desiredFlags) { auto shift = static_cast<int>(flags); if (shift < 0) continue; auto it = _data.find(shiftDcId(dcId, shift)); if (it != _data.cend()) { result.data[address][protocol].ip = it->second.ip; result.data[address][protocol].flags = it->second.flags; result.data[address][protocol].port = it->second.port; break; } } } } } return result; } void DcOptions::computeCdnDcIds() { _cdnDcIds.clear(); for (auto &item : _data) { if (item.second.flags & MTPDdcOption::Flag::f_cdn) { _cdnDcIds.insert(item.second.id); } } } bool DcOptions::loadFromFile(const QString &path) { QVector<MTPDcOption> options; QFile f(path); if (!f.open(QIODevice::ReadOnly)) { LOG(("MTP Error: could not read '%1'").arg(path)); return false; } QTextStream stream(&f); stream.setCodec("UTF-8"); while (!stream.atEnd()) { auto line = stream.readLine(); auto components = line.split(QRegularExpression(R"(\s)"), QString::SkipEmptyParts); if (components.isEmpty() || components[0].startsWith('#')) { continue; } auto error = [line] { LOG(("MTP Error: in .tdesktop-endpoints expected 'dcId host port [tcpo_only] [media_only]', got '%1'").arg(line)); return false; }; if (components.size() < 3) { return error(); } auto dcId = components[0].toInt(); auto ip = components[1]; auto port = components[2].toInt(); auto host = QHostAddress(); if (dcId <= 0 || dcId >= internal::kDcShift || !host.setAddress(ip) || port <= 0) { return error(); } auto flags = MTPDdcOption::Flags(0); if (host.protocol() == QAbstractSocket::IPv6Protocol) { flags |= MTPDdcOption::Flag::f_ipv6; } for (auto &option : components.mid(3)) { if (option.startsWith('#')) { break; } else if (option == qstr("tcpo_only")) { flags |= MTPDdcOption::Flag::f_tcpo_only; } else if (option == qstr("media_only")) { flags |= MTPDdcOption::Flag::f_media_only; } else { return error(); } } options.push_back(MTP_dcOption(MTP_flags(flags), MTP_int(dcId), MTP_string(ip), MTP_int(port))); } if (options.isEmpty()) { LOG(("MTP Error: in .tdesktop-endpoints expected at least one endpoint being provided.")); return false; } _immutable = false; setFromList(MTP_vector<MTPDcOption>(options)); _immutable = true; return true; } bool DcOptions::writeToFile(const QString &path) const { QFile f(path); if (!f.open(QIODevice::WriteOnly)) { return false; } QTextStream stream(&f); stream.setCodec("UTF-8"); ReadLocker lock(this); for (auto &item : _data) { auto &endpoint = item.second; stream << endpoint.id << ' ' << QString::fromStdString(endpoint.ip) << ' ' << endpoint.port; if (endpoint.flags & MTPDdcOption::Flag::f_tcpo_only) { stream << " tcpo_only"; } if (endpoint.flags & MTPDdcOption::Flag::f_media_only) { stream << " media_only"; } stream << '\n'; } return true; } } // namespace MTP ���������������������tdesktop-1.2.17/Telegram/SourceFiles/mtproto/dc_options.h�������������������������������������������0000664�0000000�0000000�00000005177�13262451251�0023453�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "base/observer.h" #include "mtproto/rsa_public_key.h" #include <string> #include <vector> #include <map> namespace MTP { enum class DcType { Regular, Temporary, MediaDownload, Cdn, }; class DcOptions { public: // construct methods don't notify "changed" subscribers. void constructFromSerialized(const QByteArray &serialized); void constructFromBuiltIn(); void constructAddOne(int id, MTPDdcOption::Flags flags, const std::string &ip, int port); QByteArray serialize() const; using Ids = std::vector<DcId>; base::Observable<Ids> &changed() const { return _changed; } void setFromList(const MTPVector<MTPDcOption> &options); void addFromList(const MTPVector<MTPDcOption> &options); void addFromOther(DcOptions &&options); Ids configEnumDcIds() const; struct Endpoint { std::string ip; int port = 0; MTPDdcOption::Flags flags = 0; }; struct Variants { enum { IPv4 = 0, IPv6 = 1, AddressTypeCount = 2, }; enum { Tcp = 0, Http = 1, ProtocolCount = 2, }; Endpoint data[AddressTypeCount][ProtocolCount]; }; Variants lookup(DcId dcId, DcType type) const; DcType dcType(ShiftedDcId shiftedDcId) const; void setCDNConfig(const MTPDcdnConfig &config); bool hasCDNKeysForDc(DcId dcId) const; bool getDcRSAKey(DcId dcId, const QVector<MTPlong> &fingerprints, internal::RSAPublicKey *result) const; // Debug feature for now. bool loadFromFile(const QString &path); bool writeToFile(const QString &path) const; private: struct Option { Option(DcId id, MTPDdcOption::Flags flags, const std::string &ip, int port) : id(id), flags(flags), ip(ip), port(port) { } DcId id; MTPDdcOption::Flags flags; std::string ip; int port; }; bool applyOneGuarded(DcId dcId, MTPDdcOption::Flags flags, const std::string &ip, int port); void processFromList(const QVector<MTPDcOption> &options, bool overwrite); void computeCdnDcIds(); void readBuiltInPublicKeys(); class WriteLocker; friend class WriteLocker; class ReadLocker; friend class ReadLocker; std::map<ShiftedDcId, Option> _data; std::set<DcId> _cdnDcIds; std::map<uint64, internal::RSAPublicKey> _publicKeys; std::map<DcId, std::map<uint64, internal::RSAPublicKey>> _cdnPublicKeys; mutable QReadWriteLock _useThroughLockers; mutable base::Observable<Ids> _changed; // True when we have overriden options from a .tdesktop-endpoints file. bool _immutable = false; }; } // namespace MTP �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/mtproto/dcenter.cpp��������������������������������������������0000664�0000000�0000000�00000003170�13262451251�0023260�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "mtproto/dcenter.h" #include "mtproto/facade.h" #include "mtproto/auth_key.h" #include "mtproto/dc_options.h" #include "mtproto/mtp_instance.h" #include "mtproto/special_config_request.h" #include "storage/localstorage.h" namespace MTP { namespace internal { namespace { constexpr auto kEnumerateDcTimeout = 8000; // 8 seconds timeout for help_getConfig to work (then move to other dc) constexpr auto kSpecialRequestTimeoutMs = 6000; // 4 seconds timeout for it to work in a specially requested dc. } // namespace Dcenter::Dcenter(not_null<Instance*> instance, DcId dcId, AuthKeyPtr &&key) : _instance(instance) , _id(dcId) , _key(std::move(key)) { connect(this, SIGNAL(authKeyCreated()), this, SLOT(authKeyWrite()), Qt::QueuedConnection); } void Dcenter::authKeyWrite() { DEBUG_LOG(("AuthKey Info: MTProtoDC::authKeyWrite() slot, dc %1").arg(_id)); if (_key) { Local::writeMtpData(); } } void Dcenter::setKey(AuthKeyPtr &&key) { DEBUG_LOG(("AuthKey Info: MTProtoDC::setKey(%1), emitting authKeyCreated, dc %2").arg(key ? key->keyId() : 0).arg(_id)); _key = std::move(key); _connectionInited = false; _instance->setKeyForWrite(_id, _key); emit authKeyCreated(); } QReadWriteLock *Dcenter::keyMutex() const { return &keyLock; } const AuthKeyPtr &Dcenter::getKey() const { return _key; } void Dcenter::destroyKey() { setKey(AuthKeyPtr()); } } // namespace internal } // namespace MTP ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/mtproto/dcenter.h����������������������������������������������0000664�0000000�0000000�00000002235�13262451251�0022726�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace MTP { class Instance; class AuthKey; using AuthKeyPtr = std::shared_ptr<AuthKey>; namespace internal { class Dcenter : public QObject { Q_OBJECT public: Dcenter(not_null<Instance*> instance, DcId dcId, AuthKeyPtr &&key); QReadWriteLock *keyMutex() const; const AuthKeyPtr &getKey() const; void setKey(AuthKeyPtr &&key); void destroyKey(); bool connectionInited() const { QMutexLocker lock(&initLock); bool res = _connectionInited; return res; } void setConnectionInited(bool connectionInited = true) { QMutexLocker lock(&initLock); _connectionInited = connectionInited; } signals: void authKeyCreated(); void layerWasInited(bool was); private slots: void authKeyWrite(); private: mutable QReadWriteLock keyLock; mutable QMutex initLock; not_null<Instance*> _instance; DcId _id = 0; AuthKeyPtr _key; bool _connectionInited = false; }; } // namespace internal } // namespace MTP �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/mtproto/facade.cpp���������������������������������������������0000664�0000000�0000000�00000001330�13262451251�0023033�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "mtproto/facade.h" #include "storage/localstorage.h" #include "messenger.h" namespace MTP { namespace internal { namespace { int PauseLevel = 0; } // namespace bool paused() { return PauseLevel > 0; } void pause() { ++PauseLevel; } void unpause() { --PauseLevel; if (!PauseLevel) { if (auto instance = MainInstance()) { instance->unpaused(); } } } } // namespace internal Instance *MainInstance() { return Messenger::Instance().mtp(); } } // namespace MTP ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/mtproto/facade.h�����������������������������������������������0000664�0000000�0000000�00000014013�13262451251�0022502�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "core/single_timer.h" #include "mtproto/type_utils.h" #include "mtproto/mtp_instance.h" namespace MTP { namespace internal { bool paused(); void pause(); void unpause(); constexpr auto kDcShift = ShiftedDcId(10000); constexpr auto kConfigDcShift = 0x01; constexpr auto kLogoutDcShift = 0x02; constexpr auto kMaxMediaDcCount = 0x10; constexpr auto kBaseDownloadDcShift = 0x10; constexpr auto kBaseUploadDcShift = 0x20; constexpr auto kDestroyKeyStartDcShift = 0x100; } // namespace internal class PauseHolder { public: PauseHolder() { restart(); } void restart() { if (!std::exchange(_paused, true)) { internal::pause(); } } void release() { if (std::exchange(_paused, false)) { internal::unpause(); } } ~PauseHolder() { release(); } private: bool _paused = false; }; constexpr DcId bareDcId(ShiftedDcId shiftedDcId) { return (shiftedDcId % internal::kDcShift); } constexpr ShiftedDcId shiftDcId(DcId dcId, int value) { return dcId + internal::kDcShift * value; } constexpr int getDcIdShift(ShiftedDcId shiftedDcId) { return shiftedDcId / internal::kDcShift; } // send(MTPhelp_GetConfig(), MTP::configDcId(dc)) - for dc enumeration constexpr ShiftedDcId configDcId(DcId dcId) { return shiftDcId(dcId, internal::kConfigDcShift); } // send(MTPauth_LogOut(), MTP::logoutDcId(dc)) - for logout of guest dcs enumeration constexpr ShiftedDcId logoutDcId(DcId dcId) { return shiftDcId(dcId, internal::kLogoutDcShift); } constexpr auto kDownloadSessionsCount = 2; constexpr auto kUploadSessionsCount = 2; namespace internal { constexpr ShiftedDcId downloadDcId(DcId dcId, int index) { static_assert(kDownloadSessionsCount < internal::kMaxMediaDcCount, "Too large MTPDownloadSessionsCount!"); return shiftDcId(dcId, internal::kBaseDownloadDcShift + index); }; } // namespace internal // send(req, callbacks, MTP::downloadDcId(dc, index)) - for download shifted dc id inline ShiftedDcId downloadDcId(DcId dcId, int index) { Expects(index >= 0 && index < kDownloadSessionsCount); return internal::downloadDcId(dcId, index); } inline constexpr bool isDownloadDcId(ShiftedDcId shiftedDcId) { return (shiftedDcId >= internal::downloadDcId(0, 0)) && (shiftedDcId < internal::downloadDcId(0, kDownloadSessionsCount - 1) + internal::kDcShift); } inline bool isCdnDc(MTPDdcOption::Flags flags) { return (flags & MTPDdcOption::Flag::f_cdn); } inline bool isTemporaryDcId(ShiftedDcId shiftedDcId) { auto dcId = bareDcId(shiftedDcId); return (dcId >= Instance::Config::kTemporaryMainDc); } inline DcId getRealIdFromTemporaryDcId(ShiftedDcId shiftedDcId) { auto dcId = bareDcId(shiftedDcId); return (dcId >= Instance::Config::kTemporaryMainDc) ? (dcId - Instance::Config::kTemporaryMainDc) : 0; } inline DcId getTemporaryIdFromRealDcId(ShiftedDcId shiftedDcId) { auto dcId = bareDcId(shiftedDcId); return (dcId < Instance::Config::kTemporaryMainDc) ? (dcId + Instance::Config::kTemporaryMainDc) : 0; } namespace internal { constexpr ShiftedDcId uploadDcId(DcId dcId, int index) { static_assert(kUploadSessionsCount < internal::kMaxMediaDcCount, "Too large MTPUploadSessionsCount!"); return shiftDcId(dcId, internal::kBaseUploadDcShift + index); }; } // namespace internal // send(req, callbacks, MTP::uploadDcId(index)) - for upload shifted dc id // uploading always to the main dc so bareDcId == 0 inline ShiftedDcId uploadDcId(int index) { Expects(index >= 0 && index < kUploadSessionsCount); return internal::uploadDcId(0, index); }; constexpr bool isUploadDcId(ShiftedDcId shiftedDcId) { return (shiftedDcId >= internal::uploadDcId(0, 0)) && (shiftedDcId < internal::uploadDcId(0, kUploadSessionsCount - 1) + internal::kDcShift); } inline ShiftedDcId destroyKeyNextDcId(ShiftedDcId shiftedDcId) { auto shift = getDcIdShift(shiftedDcId); return shiftDcId(bareDcId(shiftedDcId), shift ? (shift + 1) : internal::kDestroyKeyStartDcShift); } enum { DisconnectedState = 0, ConnectingState = 1, ConnectedState = 2, }; enum { RequestSent = 0, RequestConnecting = 1, RequestSending = 2 }; Instance *MainInstance(); inline void restart() { return MainInstance()->restart(); } inline void restart(ShiftedDcId shiftedDcId) { return MainInstance()->restart(shiftedDcId); } inline DcId maindc() { return MainInstance()->mainDcId(); } inline int32 dcstate(ShiftedDcId shiftedDcId = 0) { if (auto instance = MainInstance()) { return instance->dcstate(shiftedDcId); } return DisconnectedState; } inline QString dctransport(ShiftedDcId shiftedDcId = 0) { if (auto instance = MainInstance()) { return instance->dctransport(shiftedDcId); } return QString(); } template <typename TRequest> inline mtpRequestId send( const TRequest &request, RPCResponseHandler &&callbacks = {}, ShiftedDcId dcId = 0, TimeMs msCanWait = 0, mtpRequestId after = 0) { return MainInstance()->send(request, std::move(callbacks), dcId, msCanWait, after); } template <typename TRequest> inline mtpRequestId send( const TRequest &request, RPCDoneHandlerPtr &&onDone, RPCFailHandlerPtr &&onFail = nullptr, ShiftedDcId dcId = 0, TimeMs msCanWait = 0, mtpRequestId after = 0) { return MainInstance()->send(request, std::move(onDone), std::move(onFail), dcId, msCanWait, after); } inline void sendAnything(ShiftedDcId shiftedDcId = 0, TimeMs msCanWait = 0) { return MainInstance()->sendAnything(shiftedDcId, msCanWait); } inline void cancel(mtpRequestId requestId) { return MainInstance()->cancel(requestId); } inline void ping() { return MainInstance()->ping(); } inline void killSession(ShiftedDcId shiftedDcId) { return MainInstance()->killSession(shiftedDcId); } inline void stopSession(ShiftedDcId shiftedDcId) { return MainInstance()->stopSession(shiftedDcId); } inline int32 state(mtpRequestId requestId) { // < 0 means waiting for such count of ms return MainInstance()->state(requestId); } } // namespace MTP ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/mtproto/mtp_instance.cpp���������������������������������������0000664�0000000�0000000�00000131315�13262451251�0024323�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "mtproto/mtp_instance.h" #include "mtproto/session.h" #include "mtproto/dc_options.h" #include "mtproto/dcenter.h" #include "mtproto/config_loader.h" #include "mtproto/connection.h" #include "mtproto/sender.h" #include "mtproto/rsa_public_key.h" #include "storage/localstorage.h" #include "auth_session.h" #include "apiwrap.h" #include "messenger.h" #include "lang/lang_instance.h" #include "lang/lang_cloud_manager.h" #include "base/timer.h" namespace MTP { class Instance::Private : private Sender { public: Private(not_null<Instance*> instance, not_null<DcOptions*> options, Instance::Mode mode); void start(Config &&config); void suggestMainDcId(DcId mainDcId); void setMainDcId(DcId mainDcId); DcId mainDcId() const; void setKeyForWrite(DcId dcId, const AuthKeyPtr &key); AuthKeysList getKeysForWrite() const; void addKeysForDestroy(AuthKeysList &&keys); not_null<DcOptions*> dcOptions(); void requestConfig(); void requestCDNConfig(); void restart(); void restart(ShiftedDcId shiftedDcId); int32 dcstate(ShiftedDcId shiftedDcId = 0); QString dctransport(ShiftedDcId shiftedDcId = 0); void ping(); void cancel(mtpRequestId requestId); int32 state(mtpRequestId requestId); // < 0 means waiting for such count of ms void killSession(ShiftedDcId shiftedDcId); void killSession(std::unique_ptr<internal::Session> session); void stopSession(ShiftedDcId shiftedDcId); void reInitConnection(DcId dcId); void logout(RPCDoneHandlerPtr onDone, RPCFailHandlerPtr onFail); std::shared_ptr<internal::Dcenter> getDcById(ShiftedDcId shiftedDcId); void unpaused(); void queueQuittingConnection( std::unique_ptr<internal::Connection> &&connection); void connectionFinished(internal::Connection *connection); void registerRequest(mtpRequestId requestId, ShiftedDcId dcWithShift); void unregisterRequest(mtpRequestId requestId); mtpRequestId storeRequest( mtpRequest &request, RPCResponseHandler &&callbacks); mtpRequest getRequest(mtpRequestId requestId); void clearCallbacksDelayed(std::vector<RPCCallbackClear> &&ids); void execCallback(mtpRequestId requestId, const mtpPrime *from, const mtpPrime *end); bool hasCallbacks(mtpRequestId requestId); void globalCallback(const mtpPrime *from, const mtpPrime *end); void onStateChange(ShiftedDcId dcWithShift, int32 state); void onSessionReset(ShiftedDcId dcWithShift); // return true if need to clean request data bool rpcErrorOccured(mtpRequestId requestId, const RPCFailHandlerPtr &onFail, const RPCError &err); inline bool rpcErrorOccured(mtpRequestId requestId, const RPCResponseHandler &handler, const RPCError &err) { return rpcErrorOccured(requestId, handler.onFail, err); } void setUpdatesHandler(RPCDoneHandlerPtr onDone); void setGlobalFailHandler(RPCFailHandlerPtr onFail); void setStateChangedHandler(base::lambda<void(ShiftedDcId shiftedDcId, int32 state)> handler); void setSessionResetHandler(base::lambda<void(ShiftedDcId shiftedDcId)> handler); void clearGlobalHandlers(); internal::Session *getSession(ShiftedDcId shiftedDcId); bool isNormal() const { return (_mode == Instance::Mode::Normal); } bool isKeysDestroyer() const { return (_mode == Instance::Mode::KeysDestroyer); } bool isSpecialConfigRequester() const { return (_mode == Instance::Mode::SpecialConfigRequester); } void scheduleKeyDestroy(ShiftedDcId shiftedDcId); void performKeyDestroy(ShiftedDcId shiftedDcId); void completedKeyDestroy(ShiftedDcId shiftedDcId); void clearKilledSessions(); void prepareToDestroy(); private: bool hasAuthorization(); void importDone(const MTPauth_Authorization &result, mtpRequestId requestId); bool importFail(const RPCError &error, mtpRequestId requestId); void exportDone(const MTPauth_ExportedAuthorization &result, mtpRequestId requestId); bool exportFail(const RPCError &error, mtpRequestId requestId); bool onErrorDefault(mtpRequestId requestId, const RPCError &error); void logoutGuestDcs(); bool logoutGuestDone(mtpRequestId requestId); void configLoadDone(const MTPConfig &result); bool configLoadFail(const RPCError &error); void cdnConfigLoadDone(const MTPCdnConfig &result); bool cdnConfigLoadFail(const RPCError &error); // RPCError::NoError means do not toggle onError callback. void clearCallbacks( mtpRequestId requestId, int32 errorCode = RPCError::NoError); void clearCallbacks(const std::vector<RPCCallbackClear> &ids); void checkDelayedRequests(); not_null<Instance*> _instance; not_null<DcOptions*> _dcOptions; Instance::Mode _mode = Instance::Mode::Normal; DcId _mainDcId = Config::kDefaultMainDc; bool _mainDcIdForced = false; std::map<DcId, std::shared_ptr<internal::Dcenter>> _dcenters; internal::Session *_mainSession = nullptr; std::map<ShiftedDcId, std::unique_ptr<internal::Session>> _sessions; std::vector<std::unique_ptr<internal::Session>> _killedSessions; // delayed delete base::set_of_unique_ptr<internal::Connection> _quittingConnections; std::unique_ptr<internal::ConfigLoader> _configLoader; mtpRequestId _cdnConfigLoadRequestId = 0; std::map<DcId, AuthKeyPtr> _keysForWrite; mutable QReadWriteLock _keysForWriteLock; std::map<ShiftedDcId, mtpRequestId> _logoutGuestRequestIds; // holds dcWithShift for request to this dc or -dc for request to main dc std::map<mtpRequestId, ShiftedDcId> _requestsByDc; QMutex _requestByDcLock; // holds target dcWithShift for auth export request std::map<mtpRequestId, ShiftedDcId> _authExportRequests; std::map<mtpRequestId, RPCResponseHandler> _parserMap; QMutex _parserMapLock; std::map<mtpRequestId, mtpRequest> _requestMap; QReadWriteLock _requestMapLock; std::deque<std::pair<mtpRequestId, TimeMs>> _delayedRequests; std::map<mtpRequestId, int> _requestsDelays; std::set<mtpRequestId> _badGuestDcRequests; std::map<DcId, std::vector<mtpRequestId>> _authWaiters; RPCResponseHandler _globalHandler; base::lambda<void(ShiftedDcId shiftedDcId, int32 state)> _stateChangedHandler; base::lambda<void(ShiftedDcId shiftedDcId)> _sessionResetHandler; base::Timer _checkDelayedTimer; // Debug flag to find out how we end up crashing. bool MustNotCreateSessions = false; }; Instance::Private::Private(not_null<Instance*> instance, not_null<DcOptions*> options, Instance::Mode mode) : Sender() , _instance(instance) , _dcOptions(options) , _mode(mode) { } void Instance::Private::start(Config &&config) { if (isKeysDestroyer()) { _instance->connect(_instance, SIGNAL(keyDestroyed(qint32)), _instance, SLOT(onKeyDestroyed(qint32)), Qt::QueuedConnection); } else if (isNormal()) { unixtimeInit(); } for (auto &key : config.keys) { auto dcId = key->dcId(); auto shiftedDcId = dcId; if (isKeysDestroyer()) { shiftedDcId = MTP::destroyKeyNextDcId(shiftedDcId); // There could be several keys for one dc if we're destroying them. // Place them all in separate shiftedDcId so that they won't conflict. while (_keysForWrite.find(shiftedDcId) != _keysForWrite.cend()) { shiftedDcId = MTP::destroyKeyNextDcId(shiftedDcId); } } _keysForWrite[shiftedDcId] = key; auto dc = std::make_shared<internal::Dcenter>(_instance, dcId, std::move(key)); _dcenters.emplace(shiftedDcId, std::move(dc)); } if (config.mainDcId != Config::kNotSetMainDc) { _mainDcId = config.mainDcId; _mainDcIdForced = true; } if (isKeysDestroyer()) { for (auto &dc : _dcenters) { Assert(!MustNotCreateSessions); auto shiftedDcId = dc.first; auto session = std::make_unique<internal::Session>(_instance, shiftedDcId); auto it = _sessions.emplace(shiftedDcId, std::move(session)).first; it->second->start(); } } else if (_mainDcId != Config::kNoneMainDc) { Assert(!MustNotCreateSessions); auto main = std::make_unique<internal::Session>(_instance, _mainDcId); _mainSession = main.get(); _sessions.emplace(_mainDcId, std::move(main)); _mainSession->start(); } _checkDelayedTimer.setCallback([this] { checkDelayedRequests(); }); Assert((_mainDcId == Config::kNoneMainDc) == isKeysDestroyer()); if (!isKeysDestroyer()) { requestConfig(); } } void Instance::Private::suggestMainDcId(DcId mainDcId) { if (_mainDcIdForced) return; setMainDcId(mainDcId); } void Instance::Private::setMainDcId(DcId mainDcId) { if (!_mainSession) { LOG(("MTP Error: attempting to change mainDcId in an MTP instance without main session.")); return; } _mainDcIdForced = true; auto oldMainDcId = _mainSession->getDcWithShift(); _mainDcId = mainDcId; if (oldMainDcId != _mainDcId) { killSession(oldMainDcId); } Local::writeMtpData(); } DcId Instance::Private::mainDcId() const { Expects(_mainDcId != Config::kNoneMainDc); return _mainDcId; } void Instance::Private::requestConfig() { if (_configLoader) { return; } _configLoader = std::make_unique<internal::ConfigLoader>(_instance, rpcDone([this](const MTPConfig &result) { configLoadDone(result); }), rpcFail([this](const RPCError &error) { return configLoadFail(error); })); _configLoader->load(); } void Instance::Private::requestCDNConfig() { if (_cdnConfigLoadRequestId || _mainDcId == Config::kNoneMainDc) { return; } _cdnConfigLoadRequestId = request(MTPhelp_GetCdnConfig()).done([this](const MTPCdnConfig &result) { _cdnConfigLoadRequestId = 0; Expects(result.type() == mtpc_cdnConfig); dcOptions()->setCDNConfig(result.c_cdnConfig()); Local::writeSettings(); emit _instance->cdnConfigLoaded(); }).send(); } void Instance::Private::restart() { for (auto &session : _sessions) { session.second->restart(); } } void Instance::Private::restart(ShiftedDcId shiftedDcId) { auto dcId = bareDcId(shiftedDcId); for (auto &session : _sessions) { if (bareDcId(session.second->getDcWithShift()) == dcId) { session.second->restart(); } } } int32 Instance::Private::dcstate(ShiftedDcId shiftedDcId) { if (!shiftedDcId) { Assert(_mainSession != nullptr); return _mainSession->getState(); } if (!bareDcId(shiftedDcId)) { Assert(_mainSession != nullptr); shiftedDcId += bareDcId(_mainSession->getDcWithShift()); } auto it = _sessions.find(shiftedDcId); if (it != _sessions.cend()) { return it->second->getState(); } return DisconnectedState; } QString Instance::Private::dctransport(ShiftedDcId shiftedDcId) { if (!shiftedDcId) { Assert(_mainSession != nullptr); return _mainSession->transport(); } if (!bareDcId(shiftedDcId)) { Assert(_mainSession != nullptr); shiftedDcId += bareDcId(_mainSession->getDcWithShift()); } auto it = _sessions.find(shiftedDcId); if (it != _sessions.cend()) { return it->second->transport(); } return QString(); } void Instance::Private::ping() { if (auto session = getSession(0)) { session->ping(); } } void Instance::Private::cancel(mtpRequestId requestId) { if (!requestId) return; mtpMsgId msgId = 0; _requestsDelays.erase(requestId); { QWriteLocker locker(&_requestMapLock); auto it = _requestMap.find(requestId); if (it != _requestMap.end()) { msgId = *(mtpMsgId*)(it->second->constData() + 4); _requestMap.erase(it); } } { QMutexLocker locker(&_requestByDcLock); auto it = _requestsByDc.find(requestId); if (it != _requestsByDc.end()) { if (auto session = getSession(qAbs(it->second))) { session->cancel(requestId, msgId); } _requestsByDc.erase(it); } } clearCallbacks(requestId); } int32 Instance::Private::state(mtpRequestId requestId) { // < 0 means waiting for such count of ms if (requestId > 0) { QMutexLocker locker(&_requestByDcLock); auto i = _requestsByDc.find(requestId); if (i != _requestsByDc.end()) { if (auto session = getSession(qAbs(i->second))) { return session->requestState(requestId); } return MTP::RequestConnecting; } return MTP::RequestSent; } if (auto session = getSession(-requestId)) { return session->requestState(0); } return MTP::RequestConnecting; } void Instance::Private::killSession(ShiftedDcId shiftedDcId) { auto checkIfMainAndKill = [this](ShiftedDcId shiftedDcId) { auto it = _sessions.find(shiftedDcId); if (it != _sessions.cend()) { _killedSessions.push_back(std::move(it->second)); _sessions.erase(it); _killedSessions.back()->kill(); return (_killedSessions.back().get() == _mainSession); } return false; }; if (checkIfMainAndKill(shiftedDcId)) { checkIfMainAndKill(_mainDcId); Assert(!MustNotCreateSessions); auto main = std::make_unique<internal::Session>(_instance, _mainDcId); _mainSession = main.get(); _sessions.emplace(_mainDcId, std::move(main)); _mainSession->start(); } InvokeQueued(_instance, [this] { clearKilledSessions(); }); } void Instance::Private::clearKilledSessions() { _killedSessions.clear(); } void Instance::Private::stopSession(ShiftedDcId shiftedDcId) { auto it = _sessions.find(shiftedDcId); if (it != _sessions.end()) { if (it->second.get() != _mainSession) { // don't stop main session it->second->stop(); } } } void Instance::Private::reInitConnection(DcId dcId) { killSession(dcId); getSession(dcId)->notifyLayerInited(false); } void Instance::Private::logout( RPCDoneHandlerPtr onDone, RPCFailHandlerPtr onFail) { _instance->send(MTPauth_LogOut(), std::move(onDone), std::move(onFail)); logoutGuestDcs(); } void Instance::Private::logoutGuestDcs() { auto dcIds = std::vector<DcId>(); { QReadLocker lock(&_keysForWriteLock); dcIds.reserve(_keysForWrite.size()); for (auto &key : _keysForWrite) { dcIds.push_back(key.first); } } for (auto dcId : dcIds) { if (dcId != mainDcId() && dcOptions()->dcType(dcId) != DcType::Cdn) { auto shiftedDcId = MTP::logoutDcId(dcId); auto requestId = _instance->send(MTPauth_LogOut(), rpcDone([this](mtpRequestId requestId) { logoutGuestDone(requestId); }), rpcFail([this](mtpRequestId requestId) { return logoutGuestDone(requestId); }), shiftedDcId); _logoutGuestRequestIds.emplace(shiftedDcId, requestId); } } } bool Instance::Private::logoutGuestDone(mtpRequestId requestId) { for (auto i = _logoutGuestRequestIds.begin(), e = _logoutGuestRequestIds.end(); i != e; ++i) { if (i->second == requestId) { killSession(i->first); _logoutGuestRequestIds.erase(i); return true; } } return false; } std::shared_ptr<internal::Dcenter> Instance::Private::getDcById(ShiftedDcId shiftedDcId) { auto it = _dcenters.find(shiftedDcId); if (it == _dcenters.cend()) { auto dcId = bareDcId(shiftedDcId); if (isTemporaryDcId(dcId)) { if (auto realDcId = getRealIdFromTemporaryDcId(dcId)) { dcId = realDcId; } } it = _dcenters.find(dcId); if (it == _dcenters.cend()) { auto result = std::make_shared<internal::Dcenter>(_instance, dcId, AuthKeyPtr()); return _dcenters.emplace(dcId, std::move(result)).first->second; } } return it->second; } void Instance::Private::setKeyForWrite(DcId dcId, const AuthKeyPtr &key) { if (isTemporaryDcId(dcId)) { return; } QWriteLocker lock(&_keysForWriteLock); if (key) { _keysForWrite[dcId] = key; } else { _keysForWrite.erase(dcId); } } AuthKeysList Instance::Private::getKeysForWrite() const { auto result = AuthKeysList(); QReadLocker lock(&_keysForWriteLock); result.reserve(_keysForWrite.size()); for (auto &key : _keysForWrite) { result.push_back(key.second); } return result; } void Instance::Private::addKeysForDestroy(AuthKeysList &&keys) { Expects(isKeysDestroyer()); for (auto &key : keys) { auto dcId = key->dcId(); auto shiftedDcId = MTP::destroyKeyNextDcId(dcId); { QWriteLocker lock(&_keysForWriteLock); // There could be several keys for one dc if we're destroying them. // Place them all in separate shiftedDcId so that they won't conflict. while (_keysForWrite.find(shiftedDcId) != _keysForWrite.cend()) { shiftedDcId = MTP::destroyKeyNextDcId(shiftedDcId); } _keysForWrite[shiftedDcId] = key; } auto dc = std::make_shared<internal::Dcenter>(_instance, dcId, std::move(key)); _dcenters.emplace(shiftedDcId, std::move(dc)); Assert(!MustNotCreateSessions); auto session = std::make_unique<internal::Session>(_instance, shiftedDcId); auto it = _sessions.emplace(shiftedDcId, std::move(session)).first; it->second->start(); } } not_null<DcOptions*> Instance::Private::dcOptions() { return _dcOptions; } void Instance::Private::unpaused() { for (auto &session : _sessions) { session.second->unpaused(); } } void Instance::Private::queueQuittingConnection( std::unique_ptr<internal::Connection> &&connection) { _quittingConnections.insert(std::move(connection)); } void Instance::Private::connectionFinished(internal::Connection *connection) { auto it = _quittingConnections.find(connection); if (it != _quittingConnections.end()) { _quittingConnections.erase(it); } } void Instance::Private::configLoadDone(const MTPConfig &result) { Expects(result.type() == mtpc_config); _configLoader.reset(); auto &data = result.c_config(); DEBUG_LOG(("MTP Info: got config, chat_size_max: %1, date: %2, test_mode: %3, this_dc: %4, dc_options.length: %5").arg(data.vchat_size_max.v).arg(data.vdate.v).arg(mtpIsTrue(data.vtest_mode)).arg(data.vthis_dc.v).arg(data.vdc_options.v.size())); if (data.vdc_options.v.empty()) { LOG(("MTP Error: config with empty dc_options received!")); } else { _dcOptions->setFromList(data.vdc_options); } Global::SetChatSizeMax(data.vchat_size_max.v); Global::SetMegagroupSizeMax(data.vmegagroup_size_max.v); Global::SetForwardedCountMax(data.vforwarded_count_max.v); Global::SetOnlineUpdatePeriod(data.vonline_update_period_ms.v); Global::SetOfflineBlurTimeout(data.voffline_blur_timeout_ms.v); Global::SetOfflineIdleTimeout(data.voffline_idle_timeout_ms.v); Global::SetOnlineCloudTimeout(data.vonline_cloud_timeout_ms.v); Global::SetNotifyCloudDelay(data.vnotify_cloud_delay_ms.v); Global::SetNotifyDefaultDelay(data.vnotify_default_delay_ms.v); Global::SetPushChatPeriod(data.vpush_chat_period_ms.v); Global::SetPushChatLimit(data.vpush_chat_limit.v); Global::SetSavedGifsLimit(data.vsaved_gifs_limit.v); Global::SetEditTimeLimit(data.vedit_time_limit.v); Global::SetRevokeTimeLimit(data.vrevoke_time_limit.v); Global::SetRevokePrivateTimeLimit(data.vrevoke_pm_time_limit.v); Global::SetRevokePrivateInbox(data.is_revoke_pm_inbox()); Global::SetStickersRecentLimit(data.vstickers_recent_limit.v); Global::SetStickersFavedLimit(data.vstickers_faved_limit.v); Global::SetPinnedDialogsCountMax(data.vpinned_dialogs_count_max.v); Messenger::Instance().setInternalLinkDomain(qs(data.vme_url_prefix)); Global::SetChannelsReadMediaPeriod(data.vchannels_read_media_period.v); Global::SetCallReceiveTimeoutMs(data.vcall_receive_timeout_ms.v); Global::SetCallRingTimeoutMs(data.vcall_ring_timeout_ms.v); Global::SetCallConnectTimeoutMs(data.vcall_connect_timeout_ms.v); Global::SetCallPacketTimeoutMs(data.vcall_packet_timeout_ms.v); if (Global::PhoneCallsEnabled() != data.is_phonecalls_enabled()) { Global::SetPhoneCallsEnabled(data.is_phonecalls_enabled()); Global::RefPhoneCallsEnabledChanged().notify(); } Lang::CurrentCloudManager().setSuggestedLanguage(data.has_suggested_lang_code() ? qs(data.vsuggested_lang_code) : QString()); Local::writeSettings(); emit _instance->configLoaded(); } bool Instance::Private::configLoadFail(const RPCError &error) { if (isDefaultHandledError(error)) return false; // loadingConfig = false; LOG(("MTP Error: failed to get config!")); return false; } void Instance::Private::checkDelayedRequests() { auto now = getms(true); while (!_delayedRequests.empty() && now >= _delayedRequests.front().second) { auto requestId = _delayedRequests.front().first; _delayedRequests.pop_front(); auto dcWithShift = ShiftedDcId(0); { QMutexLocker locker(&_requestByDcLock); auto it = _requestsByDc.find(requestId); if (it != _requestsByDc.cend()) { dcWithShift = it->second; } else { LOG(("MTP Error: could not find request dc for delayed resend, requestId %1").arg(requestId)); continue; } } auto request = mtpRequest(); { QReadLocker locker(&_requestMapLock); auto it = _requestMap.find(requestId); if (it == _requestMap.cend()) { DEBUG_LOG(("MTP Error: could not find request %1").arg(requestId)); continue; } request = it->second; } if (auto session = getSession(qAbs(dcWithShift))) { session->sendPrepared(request); } } if (!_delayedRequests.empty()) { _checkDelayedTimer.callOnce(_delayedRequests.front().second - now); } } void Instance::Private::registerRequest( mtpRequestId requestId, ShiftedDcId dcWithShift) { QMutexLocker locker(&_requestByDcLock); _requestsByDc.emplace(requestId, dcWithShift); } void Instance::Private::unregisterRequest(mtpRequestId requestId) { _requestsDelays.erase(requestId); { QWriteLocker locker(&_requestMapLock); _requestMap.erase(requestId); } QMutexLocker locker(&_requestByDcLock); _requestsByDc.erase(requestId); } mtpRequestId Instance::Private::storeRequest( mtpRequest &request, RPCResponseHandler &&callbacks) { const auto requestId = reqid(); request->requestId = requestId; if (callbacks.onDone || callbacks.onFail) { QMutexLocker locker(&_parserMapLock); _parserMap.emplace(requestId, std::move(callbacks)); } { QWriteLocker locker(&_requestMapLock); _requestMap.emplace(requestId, request); } return requestId; } mtpRequest Instance::Private::getRequest(mtpRequestId requestId) { auto result = mtpRequest(); { QReadLocker locker(&_requestMapLock); auto it = _requestMap.find(requestId); if (it != _requestMap.cend()) { result = it->second; } } return result; } void Instance::Private::clearCallbacks(mtpRequestId requestId, int32 errorCode) { RPCResponseHandler h; bool found = false; { QMutexLocker locker(&_parserMapLock); auto it = _parserMap.find(requestId); if (it != _parserMap.end()) { h = it->second; found = true; _parserMap.erase(it); } } if (errorCode && found) { rpcErrorOccured(requestId, h, internal::rpcClientError("CLEAR_CALLBACK", QString("did not handle request %1, error code %2").arg(requestId).arg(errorCode))); } } void Instance::Private::clearCallbacksDelayed( std::vector<RPCCallbackClear> &&ids) { if (ids.empty()) { return; } if (cDebug()) { auto idsString = QStringList(); idsString.reserve(ids.size()); for (auto &value : ids) { idsString.push_back(QString::number(value.requestId)); } DEBUG_LOG(("RPC Info: clear callbacks delayed, msgIds: %1" ).arg(idsString.join(", "))); } crl::on_main(_instance, [this, list = std::move(ids)] { clearCallbacks(list); }); } void Instance::Private::clearCallbacks( const std::vector<RPCCallbackClear> &ids) { Expects(!ids.empty()); for (const auto &clearRequest : ids) { if (cDebug()) { QMutexLocker locker(&_parserMapLock); if (_parserMap.find(clearRequest.requestId) != _parserMap.end()) { DEBUG_LOG(("RPC Info: " "clearing delayed callback %1, error code %2" ).arg(clearRequest.requestId ).arg(clearRequest.errorCode)); } } clearCallbacks(clearRequest.requestId, clearRequest.errorCode); unregisterRequest(clearRequest.requestId); } } void Instance::Private::execCallback( mtpRequestId requestId, const mtpPrime *from, const mtpPrime *end) { RPCResponseHandler h; { QMutexLocker locker(&_parserMapLock); auto it = _parserMap.find(requestId); if (it != _parserMap.cend()) { h = it->second; _parserMap.erase(it); DEBUG_LOG(("RPC Info: found parser for request %1, trying to parse response...").arg(requestId)); } } if (h.onDone || h.onFail) { try { if (from >= end) throw mtpErrorInsufficient(); if (*from == mtpc_rpc_error) { auto mtpError = MTPRpcError(); mtpError.read(from, end); auto error = RPCError(mtpError); DEBUG_LOG(("RPC Info: error received, code %1, type %2, description: %3").arg(error.code()).arg(error.type()).arg(error.description())); if (!rpcErrorOccured(requestId, h, error)) { QMutexLocker locker(&_parserMapLock); _parserMap.emplace(requestId, h); return; } } else { if (h.onDone) { (*h.onDone)(requestId, from, end); } } } catch (Exception &e) { if (!rpcErrorOccured(requestId, h, internal::rpcClientError("RESPONSE_PARSE_FAILED", QString("exception text: ") + e.what()))) { QMutexLocker locker(&_parserMapLock); _parserMap.emplace(requestId, h); return; } } } else { DEBUG_LOG(("RPC Info: parser not found for %1").arg(requestId)); } unregisterRequest(requestId); } bool Instance::Private::hasCallbacks(mtpRequestId requestId) { QMutexLocker locker(&_parserMapLock); auto it = _parserMap.find(requestId); return (it != _parserMap.cend()); } void Instance::Private::globalCallback(const mtpPrime *from, const mtpPrime *end) { if (_globalHandler.onDone) { (*_globalHandler.onDone)(0, from, end); // some updates were received } } void Instance::Private::onStateChange(int32 dcWithShift, int32 state) { if (_stateChangedHandler) { _stateChangedHandler(dcWithShift, state); } } void Instance::Private::onSessionReset(int32 dcWithShift) { if (_sessionResetHandler) { _sessionResetHandler(dcWithShift); } } bool Instance::Private::rpcErrorOccured(mtpRequestId requestId, const RPCFailHandlerPtr &onFail, const RPCError &err) { // return true if need to clean request data if (isDefaultHandledError(err)) { if (onFail && (*onFail)(requestId, err)) return true; } if (onErrorDefault(requestId, err)) { return false; } LOG(("RPC Error: request %1 got fail with code %2, error %3%4").arg(requestId).arg(err.code()).arg(err.type()).arg(err.description().isEmpty() ? QString() : QString(": %1").arg(err.description()))); onFail && (*onFail)(requestId, err); return true; } bool Instance::Private::hasAuthorization() { return AuthSession::Exists(); } void Instance::Private::importDone(const MTPauth_Authorization &result, mtpRequestId requestId) { QMutexLocker locker1(&_requestByDcLock); auto it = _requestsByDc.find(requestId); if (it == _requestsByDc.end()) { LOG(("MTP Error: auth import request not found in requestsByDC, requestId: %1").arg(requestId)); RPCError error(internal::rpcClientError("AUTH_IMPORT_FAIL", QString("did not find import request in requestsByDC, request %1").arg(requestId))); if (_globalHandler.onFail && hasAuthorization()) { (*_globalHandler.onFail)(requestId, error); // auth failed in main dc } return; } auto newdc = bareDcId(it->second); DEBUG_LOG(("MTP Info: auth import to dc %1 succeeded").arg(newdc)); auto &waiters = _authWaiters[newdc]; if (waiters.size()) { QReadLocker locker(&_requestMapLock); for (auto waitedRequestId : waiters) { auto it = _requestMap.find(waitedRequestId); if (it == _requestMap.cend()) { LOG(("MTP Error: could not find request %1 for resending").arg(waitedRequestId)); continue; } auto dcWithShift = ShiftedDcId(newdc); { auto k = _requestsByDc.find(waitedRequestId); if (k == _requestsByDc.cend()) { LOG(("MTP Error: could not find request %1 by dc for resending").arg(waitedRequestId)); continue; } if (k->second < 0) { _instance->setMainDcId(newdc); k->second = -newdc; } else { dcWithShift = shiftDcId(newdc, getDcIdShift(k->second)); k->second = dcWithShift; } DEBUG_LOG(("MTP Info: resending request %1 to dc %2 after import auth").arg(waitedRequestId).arg(k->second)); } if (auto session = getSession(dcWithShift)) { session->sendPrepared(it->second); } } waiters.clear(); } } bool Instance::Private::importFail(const RPCError &error, mtpRequestId requestId) { if (isDefaultHandledError(error)) return false; if (_globalHandler.onFail && hasAuthorization()) { (*_globalHandler.onFail)(requestId, error); // auth import failed } return true; } void Instance::Private::exportDone(const MTPauth_ExportedAuthorization &result, mtpRequestId requestId) { auto it = _authExportRequests.find(requestId); if (it == _authExportRequests.cend()) { LOG(("MTP Error: auth export request target dcWithShift not found, requestId: %1").arg(requestId)); RPCError error(internal::rpcClientError("AUTH_IMPORT_FAIL", QString("did not find target dcWithShift, request %1").arg(requestId))); if (_globalHandler.onFail && hasAuthorization()) { (*_globalHandler.onFail)(requestId, error); // auth failed in main dc } return; } auto &data = result.c_auth_exportedAuthorization(); _instance->send(MTPauth_ImportAuthorization(data.vid, data.vbytes), rpcDone([this](const MTPauth_Authorization &result, mtpRequestId requestId) { importDone(result, requestId); }), rpcFail([this](const RPCError &error, mtpRequestId requestId) { return importFail(error, requestId); }), it->second); _authExportRequests.erase(requestId); } bool Instance::Private::exportFail(const RPCError &error, mtpRequestId requestId) { if (isDefaultHandledError(error)) return false; auto it = _authExportRequests.find(requestId); if (it != _authExportRequests.cend()) { _authWaiters[bareDcId(it->second)].clear(); } if (_globalHandler.onFail && hasAuthorization()) { (*_globalHandler.onFail)(requestId, error); // auth failed in main dc } return true; } bool Instance::Private::onErrorDefault(mtpRequestId requestId, const RPCError &error) { auto &err(error.type()); auto code = error.code(); if (!isFloodError(error) && err != qstr("AUTH_KEY_UNREGISTERED")) { int breakpoint = 0; } auto badGuestDc = (code == 400) && (err == qsl("FILE_ID_INVALID")); QRegularExpressionMatch m; if ((m = QRegularExpression("^(FILE|PHONE|NETWORK|USER)_MIGRATE_(\\d+)$").match(err)).hasMatch()) { if (!requestId) return false; ShiftedDcId dcWithShift = 0, newdcWithShift = m.captured(2).toInt(); { QMutexLocker locker(&_requestByDcLock); auto it = _requestsByDc.find(requestId); if (it == _requestsByDc.end()) { LOG(("MTP Error: could not find request %1 for migrating to %2").arg(requestId).arg(newdcWithShift)); } else { dcWithShift = it->second; } } if (!dcWithShift || !newdcWithShift) return false; DEBUG_LOG(("MTP Info: changing request %1 from dcWithShift%2 to dc%3").arg(requestId).arg(dcWithShift).arg(newdcWithShift)); if (dcWithShift < 0) { // newdc shift = 0 if (false && hasAuthorization() && _authExportRequests.find(requestId) == _authExportRequests.cend()) { // // migrate not supported at this moment // this was not tested even once // //DEBUG_LOG(("MTP Info: importing auth to dc %1").arg(newdcWithShift)); //auto &waiters(_authWaiters[newdcWithShift]); //if (waiters.empty()) { // auto exportRequestId = _instance->send(MTPauth_ExportAuthorization(MTP_int(newdcWithShift)), rpcDone([this](const MTPauth_ExportedAuthorization &result, mtpRequestId requestId) { // exportDone(result, requestId); // }), rpcFail([this](const RPCError &error, mtpRequestId requestId) { // return exportFail(error, requestId); // })); // _authExportRequests.emplace(exportRequestId, newdcWithShift); //} //waiters.push_back(requestId); //return true; } else { _instance->setMainDcId(newdcWithShift); } } else { newdcWithShift = shiftDcId(newdcWithShift, getDcIdShift(dcWithShift)); } auto request = mtpRequest(); { QReadLocker locker(&_requestMapLock); auto it = _requestMap.find(requestId); if (it == _requestMap.cend()) { LOG(("MTP Error: could not find request %1").arg(requestId)); return false; } request = it->second; } if (auto session = getSession(newdcWithShift)) { registerRequest( requestId, (dcWithShift < 0) ? -newdcWithShift : newdcWithShift); session->sendPrepared(request); } return true; } else if (code < 0 || code >= 500 || (m = QRegularExpression("^FLOOD_WAIT_(\\d+)$").match(err)).hasMatch()) { if (!requestId) return false; int32 secs = 1; if (code < 0 || code >= 500) { auto it = _requestsDelays.find(requestId); if (it != _requestsDelays.cend()) { secs = (it->second > 60) ? it->second : (it->second *= 2); } else { _requestsDelays.emplace(requestId, secs); } } else { secs = m.captured(1).toInt(); // if (secs >= 60) return false; } auto sendAt = getms(true) + secs * 1000 + 10; auto it = _delayedRequests.begin(), e = _delayedRequests.end(); for (; it != e; ++it) { if (it->first == requestId) return true; if (it->second > sendAt) break; } _delayedRequests.insert(it, std::make_pair(requestId, sendAt)); checkDelayedRequests(); return true; } else if (code == 401 || (badGuestDc && _badGuestDcRequests.find(requestId) == _badGuestDcRequests.cend())) { auto dcWithShift = ShiftedDcId(0); { QMutexLocker locker(&_requestByDcLock); auto it = _requestsByDc.find(requestId); if (it != _requestsByDc.end()) { dcWithShift = it->second; } else { LOG(("MTP Error: unauthorized request without dc info, requestId %1").arg(requestId)); } } auto newdc = bareDcId(qAbs(dcWithShift)); if (!newdc || newdc == mainDcId() || !hasAuthorization()) { if (!badGuestDc && _globalHandler.onFail) { (*_globalHandler.onFail)(requestId, error); // auth failed in main dc } return false; } DEBUG_LOG(("MTP Info: importing auth to dcWithShift %1").arg(dcWithShift)); auto &waiters(_authWaiters[newdc]); if (!waiters.size()) { auto exportRequestId = _instance->send(MTPauth_ExportAuthorization(MTP_int(newdc)), rpcDone([this](const MTPauth_ExportedAuthorization &result, mtpRequestId requestId) { exportDone(result, requestId); }), rpcFail([this](const RPCError &error, mtpRequestId requestId) { return exportFail(error, requestId); })); _authExportRequests.emplace(exportRequestId, abs(dcWithShift)); } waiters.push_back(requestId); if (badGuestDc) _badGuestDcRequests.insert(requestId); return true; } else if (err == qstr("CONNECTION_NOT_INITED") || err == qstr("CONNECTION_LAYER_INVALID")) { mtpRequest request; { QReadLocker locker(&_requestMapLock); auto it = _requestMap.find(requestId); if (it == _requestMap.cend()) { LOG(("MTP Error: could not find request %1").arg(requestId)); return false; } request = it->second; } auto dcWithShift = ShiftedDcId(0); { QMutexLocker locker(&_requestByDcLock); auto it = _requestsByDc.find(requestId); if (it == _requestsByDc.end()) { LOG(("MTP Error: could not find request %1 for resending with init connection").arg(requestId)); } else { dcWithShift = it->second; } } if (!dcWithShift) return false; if (auto session = getSession(qAbs(dcWithShift))) { request->needsLayer = true; session->sendPrepared(request); } return true; } else if (err == qstr("CONNECTION_LANG_CODE_INVALID")) { Lang::CurrentCloudManager().resetToDefault(); } else if (err == qstr("MSG_WAIT_FAILED")) { mtpRequest request; { QReadLocker locker(&_requestMapLock); auto it = _requestMap.find(requestId); if (it == _requestMap.cend()) { LOG(("MTP Error: could not find request %1").arg(requestId)); return false; } request = it->second; } if (!request->after) { LOG(("MTP Error: wait failed for not dependent request %1").arg(requestId)); return false; } auto dcWithShift = ShiftedDcId(0); { QMutexLocker locker(&_requestByDcLock); auto it = _requestsByDc.find(requestId); auto afterIt = _requestsByDc.find(request->after->requestId); if (it == _requestsByDc.end()) { LOG(("MTP Error: could not find request %1 by dc").arg(requestId)); } else if (afterIt == _requestsByDc.end()) { LOG(("MTP Error: could not find dependent request %1 by dc").arg(request->after->requestId)); } else { dcWithShift = it->second; if (it->second != afterIt->second) { request->after = mtpRequest(); } } } if (!dcWithShift) return false; if (!request->after) { if (auto session = getSession(qAbs(dcWithShift))) { request->needsLayer = true; session->sendPrepared(request); } } else { auto newdc = bareDcId(qAbs(dcWithShift)); auto &waiters(_authWaiters[newdc]); if (base::contains(waiters, request->after->requestId)) { if (!base::contains(waiters, requestId)) { waiters.push_back(requestId); } if (_badGuestDcRequests.find(request->after->requestId) != _badGuestDcRequests.cend()) { if (_badGuestDcRequests.find(requestId) == _badGuestDcRequests.cend()) { _badGuestDcRequests.insert(requestId); } } } else { auto i = _delayedRequests.begin(), e = _delayedRequests.end(); for (; i != e; ++i) { if (i->first == requestId) return true; if (i->first == request->after->requestId) break; } if (i != e) { _delayedRequests.insert(i, std::make_pair(requestId, i->second)); } checkDelayedRequests(); } } return true; } if (badGuestDc) _badGuestDcRequests.erase(requestId); return false; } internal::Session *Instance::Private::getSession(ShiftedDcId shiftedDcId) { if (!shiftedDcId) { Assert(_mainSession != nullptr); return _mainSession; } if (!bareDcId(shiftedDcId)) { Assert(_mainSession != nullptr); shiftedDcId += bareDcId(_mainSession->getDcWithShift()); } auto it = _sessions.find(shiftedDcId); if (it == _sessions.cend()) { Assert(!MustNotCreateSessions); it = _sessions.emplace(shiftedDcId, std::make_unique<internal::Session>(_instance, shiftedDcId)).first; it->second->start(); } return it->second.get(); } void Instance::Private::scheduleKeyDestroy(ShiftedDcId shiftedDcId) { Expects(isKeysDestroyer()); _instance->send(MTPauth_LogOut(), rpcDone([this, shiftedDcId](const MTPBool &result) { performKeyDestroy(shiftedDcId); }), rpcFail([this, shiftedDcId](const RPCError &error) { if (isDefaultHandledError(error)) return false; performKeyDestroy(shiftedDcId); return true; }), shiftedDcId); } void Instance::Private::performKeyDestroy(ShiftedDcId shiftedDcId) { Expects(isKeysDestroyer()); _instance->send(MTPDestroy_auth_key(), rpcDone([this, shiftedDcId](const MTPDestroyAuthKeyRes &result) { switch (result.type()) { case mtpc_destroy_auth_key_ok: LOG(("MTP Info: key %1 destroyed.").arg(shiftedDcId)); break; case mtpc_destroy_auth_key_fail: { LOG(("MTP Error: key %1 destruction fail, leave it for now.").arg(shiftedDcId)); killSession(shiftedDcId); } break; case mtpc_destroy_auth_key_none: LOG(("MTP Info: key %1 already destroyed.").arg(shiftedDcId)); break; } emit _instance->keyDestroyed(shiftedDcId); }), rpcFail([this, shiftedDcId](const RPCError &error) { LOG(("MTP Error: key %1 destruction resulted in error: %2").arg(shiftedDcId).arg(error.type())); emit _instance->keyDestroyed(shiftedDcId); return true; }), shiftedDcId); } void Instance::Private::completedKeyDestroy(ShiftedDcId shiftedDcId) { Expects(isKeysDestroyer()); _dcenters.erase(shiftedDcId); { QWriteLocker lock(&_keysForWriteLock); _keysForWrite.erase(shiftedDcId); } killSession(shiftedDcId); if (_dcenters.empty()) { emit _instance->allKeysDestroyed(); } } void Instance::Private::setUpdatesHandler(RPCDoneHandlerPtr onDone) { _globalHandler.onDone = onDone; } void Instance::Private::setGlobalFailHandler(RPCFailHandlerPtr onFail) { _globalHandler.onFail = onFail; } void Instance::Private::setStateChangedHandler(base::lambda<void(ShiftedDcId shiftedDcId, int32 state)> handler) { _stateChangedHandler = std::move(handler); } void Instance::Private::setSessionResetHandler(base::lambda<void(ShiftedDcId shiftedDcId)> handler) { _sessionResetHandler = std::move(handler); } void Instance::Private::clearGlobalHandlers() { setUpdatesHandler(RPCDoneHandlerPtr()); setGlobalFailHandler(RPCFailHandlerPtr()); setStateChangedHandler(base::lambda<void(ShiftedDcId,int32)>()); setSessionResetHandler(base::lambda<void(ShiftedDcId)>()); } void Instance::Private::prepareToDestroy() { // It accesses Instance in destructor, so it should be destroyed first. _configLoader.reset(); requestCancellingDiscard(); for (auto &session : base::take(_sessions)) { session.second->kill(); } _mainSession = nullptr; MustNotCreateSessions = true; } Instance::Instance(not_null<DcOptions*> options, Mode mode, Config &&config) : QObject() , _private(std::make_unique<Private>(this, options, mode)) { _private->start(std::move(config)); } void Instance::suggestMainDcId(DcId mainDcId) { _private->suggestMainDcId(mainDcId); } void Instance::setMainDcId(DcId mainDcId) { _private->setMainDcId(mainDcId); } DcId Instance::mainDcId() const { return _private->mainDcId(); } QString Instance::systemLangCode() const { return Lang::Current().systemLangCode(); } QString Instance::cloudLangCode() const { return Lang::Current().cloudLangCode(); } void Instance::requestConfig() { _private->requestConfig(); } void Instance::requestCDNConfig() { _private->requestCDNConfig(); } void Instance::connectionFinished(internal::Connection *connection) { _private->connectionFinished(connection); } void Instance::restart() { _private->restart(); } void Instance::restart(ShiftedDcId shiftedDcId) { _private->restart(shiftedDcId); } int32 Instance::dcstate(ShiftedDcId shiftedDcId) { return _private->dcstate(shiftedDcId); } QString Instance::dctransport(ShiftedDcId shiftedDcId) { return _private->dctransport(shiftedDcId); } void Instance::ping() { _private->ping(); } void Instance::cancel(mtpRequestId requestId) { _private->cancel(requestId); } int32 Instance::state(mtpRequestId requestId) { // < 0 means waiting for such count of ms return _private->state(requestId); } void Instance::killSession(ShiftedDcId shiftedDcId) { _private->killSession(shiftedDcId); } void Instance::stopSession(ShiftedDcId shiftedDcId) { _private->stopSession(shiftedDcId); } void Instance::reInitConnection(DcId dcId) { _private->reInitConnection(dcId); } void Instance::logout(RPCDoneHandlerPtr onDone, RPCFailHandlerPtr onFail) { _private->logout(onDone, onFail); } std::shared_ptr<internal::Dcenter> Instance::getDcById(ShiftedDcId shiftedDcId) { return _private->getDcById(shiftedDcId); } void Instance::setKeyForWrite(DcId dcId, const AuthKeyPtr &key) { _private->setKeyForWrite(dcId, key); } AuthKeysList Instance::getKeysForWrite() const { return _private->getKeysForWrite(); } void Instance::addKeysForDestroy(AuthKeysList &&keys) { _private->addKeysForDestroy(std::move(keys)); } not_null<DcOptions*> Instance::dcOptions() { return _private->dcOptions(); } void Instance::unpaused() { _private->unpaused(); } void Instance::queueQuittingConnection( std::unique_ptr<internal::Connection> &&connection) { _private->queueQuittingConnection(std::move(connection)); } void Instance::setUpdatesHandler(RPCDoneHandlerPtr onDone) { _private->setUpdatesHandler(onDone); } void Instance::setGlobalFailHandler(RPCFailHandlerPtr onFail) { _private->setGlobalFailHandler(onFail); } void Instance::setStateChangedHandler(base::lambda<void(ShiftedDcId shiftedDcId, int32 state)> handler) { _private->setStateChangedHandler(std::move(handler)); } void Instance::setSessionResetHandler(base::lambda<void(ShiftedDcId shiftedDcId)> handler) { _private->setSessionResetHandler(std::move(handler)); } void Instance::clearGlobalHandlers() { _private->clearGlobalHandlers(); } void Instance::onStateChange(ShiftedDcId dcWithShift, int32 state) { _private->onStateChange(dcWithShift, state); } void Instance::onSessionReset(ShiftedDcId dcWithShift) { _private->onSessionReset(dcWithShift); } void Instance::registerRequest( mtpRequestId requestId, ShiftedDcId dcWithShift) { _private->registerRequest(requestId, dcWithShift); } mtpRequestId Instance::storeRequest( mtpRequest &request, RPCResponseHandler &&callbacks) { return _private->storeRequest(request, std::move(callbacks)); } mtpRequest Instance::getRequest(mtpRequestId requestId) { return _private->getRequest(requestId); } void Instance::clearCallbacksDelayed(std::vector<RPCCallbackClear> &&ids) { _private->clearCallbacksDelayed(std::move(ids)); } void Instance::execCallback(mtpRequestId requestId, const mtpPrime *from, const mtpPrime *end) { _private->execCallback(requestId, from, end); } bool Instance::hasCallbacks(mtpRequestId requestId) { return _private->hasCallbacks(requestId); } void Instance::globalCallback(const mtpPrime *from, const mtpPrime *end) { _private->globalCallback(from, end); } bool Instance::rpcErrorOccured(mtpRequestId requestId, const RPCFailHandlerPtr &onFail, const RPCError &err) { return _private->rpcErrorOccured(requestId, onFail, err); } bool Instance::isKeysDestroyer() const { return _private->isKeysDestroyer(); } void Instance::scheduleKeyDestroy(ShiftedDcId shiftedDcId) { _private->scheduleKeyDestroy(shiftedDcId); } void Instance::onKeyDestroyed(qint32 shiftedDcId) { _private->completedKeyDestroy(shiftedDcId); } mtpRequestId Instance::send( mtpRequest &&request, RPCResponseHandler &&callbacks, ShiftedDcId dcId, TimeMs msCanWait, mtpRequestId after) { if (const auto session = _private->getSession(dcId)) { return session->send( mtpRequestData::serialize(request), std::move(callbacks), msCanWait, true, !dcId, after); } return 0; } void Instance::sendAnything(ShiftedDcId dcId, TimeMs msCanWait) { if (const auto session = _private->getSession(dcId)) { session->sendAnything(msCanWait); } } Instance::~Instance() { _private->prepareToDestroy(); } } // namespace MTP �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/mtproto/mtp_instance.h�����������������������������������������0000664�0000000�0000000�00000010622�13262451251�0023765�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <map> #include <set> #include "mtproto/rpc_sender.h" namespace MTP { namespace internal { class Dcenter; class Session; class Connection; } // namespace internal class DcOptions; class AuthKey; using AuthKeyPtr = std::shared_ptr<AuthKey>; using AuthKeysList = std::vector<AuthKeyPtr>; class Instance : public QObject { Q_OBJECT public: struct Config { static constexpr auto kNoneMainDc = -1; static constexpr auto kNotSetMainDc = 0; static constexpr auto kDefaultMainDc = 2; static constexpr auto kTemporaryMainDc = 1000; DcId mainDcId = kNotSetMainDc; AuthKeysList keys; }; enum class Mode { Normal, SpecialConfigRequester, KeysDestroyer, }; Instance(not_null<DcOptions*> options, Mode mode, Config &&config); Instance(const Instance &other) = delete; Instance &operator=(const Instance &other) = delete; void suggestMainDcId(DcId mainDcId); void setMainDcId(DcId mainDcId); DcId mainDcId() const; QString systemLangCode() const; QString cloudLangCode() const; void setKeyForWrite(DcId dcId, const AuthKeyPtr &key); AuthKeysList getKeysForWrite() const; void addKeysForDestroy(AuthKeysList &&keys); not_null<DcOptions*> dcOptions(); template <typename TRequest> mtpRequestId send( const TRequest &request, RPCResponseHandler &&callbacks = {}, ShiftedDcId dcId = 0, TimeMs msCanWait = 0, mtpRequestId after = 0) { return send( mtpRequestData::serialize(request), std::move(callbacks), dcId, msCanWait, after); } template <typename TRequest> mtpRequestId send( const TRequest &request, RPCDoneHandlerPtr &&onDone, RPCFailHandlerPtr &&onFail = nullptr, ShiftedDcId dc = 0, TimeMs msCanWait = 0, mtpRequestId after = 0) { return send( request, RPCResponseHandler(std::move(onDone), std::move(onFail)), dc, msCanWait, after); } void sendAnything(ShiftedDcId dcId = 0, TimeMs msCanWait = 0); void restart(); void restart(ShiftedDcId shiftedDcId); int32 dcstate(ShiftedDcId shiftedDcId = 0); QString dctransport(ShiftedDcId shiftedDcId = 0); void ping(); void cancel(mtpRequestId requestId); int32 state(mtpRequestId requestId); // < 0 means waiting for such count of ms void killSession(ShiftedDcId shiftedDcId); void stopSession(ShiftedDcId shiftedDcId); void reInitConnection(DcId dcId); void logout(RPCDoneHandlerPtr onDone, RPCFailHandlerPtr onFail); std::shared_ptr<internal::Dcenter> getDcById(ShiftedDcId shiftedDcId); void unpaused(); void queueQuittingConnection(std::unique_ptr<internal::Connection> &&connection); void setUpdatesHandler(RPCDoneHandlerPtr onDone); void setGlobalFailHandler(RPCFailHandlerPtr onFail); void setStateChangedHandler(base::lambda<void(ShiftedDcId shiftedDcId, int32 state)> handler); void setSessionResetHandler(base::lambda<void(ShiftedDcId shiftedDcId)> handler); void clearGlobalHandlers(); void onStateChange(ShiftedDcId dcWithShift, int32 state); void onSessionReset(ShiftedDcId dcWithShift); void registerRequest(mtpRequestId requestId, ShiftedDcId dcWithShift); mtpRequestId storeRequest( mtpRequest &request, RPCResponseHandler &&callbacks); mtpRequest getRequest(mtpRequestId requestId); void clearCallbacksDelayed(std::vector<RPCCallbackClear> &&ids); void execCallback(mtpRequestId requestId, const mtpPrime *from, const mtpPrime *end); bool hasCallbacks(mtpRequestId requestId); void globalCallback(const mtpPrime *from, const mtpPrime *end); // return true if need to clean request data bool rpcErrorOccured(mtpRequestId requestId, const RPCFailHandlerPtr &onFail, const RPCError &err); bool isKeysDestroyer() const; void scheduleKeyDestroy(ShiftedDcId shiftedDcId); void requestConfig(); void requestCDNConfig(); ~Instance(); public slots: void connectionFinished(internal::Connection *connection); signals: void configLoaded(); void cdnConfigLoaded(); void keyDestroyed(qint32 shiftedDcId); void allKeysDestroyed(); private slots: void onKeyDestroyed(qint32 shiftedDcId); private: mtpRequestId send( mtpRequest &&request, RPCResponseHandler &&callbacks, ShiftedDcId dcId, TimeMs msCanWait, mtpRequestId after); class Private; const std::unique_ptr<Private> _private; }; } // namespace MTP ��������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/mtproto/rpc_sender.cpp�����������������������������������������0000664�0000000�0000000�00000001252�13262451251�0023757�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "mtproto/rpc_sender.h" RPCOwnedDoneHandler::RPCOwnedDoneHandler(RPCSender *owner) : _owner(owner) { _owner->_rpcRegHandler(this); } RPCOwnedDoneHandler::~RPCOwnedDoneHandler() { if (_owner) _owner->_rpcUnregHandler(this); } RPCOwnedFailHandler::RPCOwnedFailHandler(RPCSender *owner) : _owner(owner) { _owner->_rpcRegHandler(this); } RPCOwnedFailHandler::~RPCOwnedFailHandler() { if (_owner) _owner->_rpcUnregHandler(this); } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/mtproto/rpc_sender.h�������������������������������������������0000664�0000000�0000000�00000105242�13262451251�0023430�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/details/callable.h> class RPCError { public: RPCError(const MTPrpcError &error) : _code(error.c_rpc_error().verror_code.v) { QString text = qs(error.c_rpc_error().verror_message); if (_code < 0 || _code >= 500) { _type = qsl("INTERNAL_SERVER_ERROR"); _description = text; } else { auto m = QRegularExpression("^([A-Z0-9_]+)(: .*)?$", reMultiline).match(text); if (m.hasMatch()) { _type = m.captured(1); _description = m.captured(2).mid(2); } else { _type = qsl("CLIENT_BAD_RPC_ERROR"); _description = qsl("Bad rpc error received, text = '") + text + '\''; } } } int32 code() const { return _code; } const QString &type() const { return _type; } const QString &description() const { return _description; } enum { NoError, TimeoutError }; private: int32 _code; QString _type, _description; }; namespace MTP { inline bool isFloodError(const RPCError &error) { return error.type().startsWith(qstr("FLOOD_WAIT_")); } inline bool isTemporaryError(const RPCError &error) { return error.code() < 0 || error.code() >= 500 || isFloodError(error); } inline bool isDefaultHandledError(const RPCError &error) { return isTemporaryError(error); } } // namespace MTP class RPCAbstractDoneHandler { // abstract done public: virtual void operator()(mtpRequestId requestId, const mtpPrime *from, const mtpPrime *end) = 0; virtual ~RPCAbstractDoneHandler() { } }; using RPCDoneHandlerPtr = std::shared_ptr<RPCAbstractDoneHandler>; class RPCAbstractFailHandler { // abstract fail public: virtual bool operator()(mtpRequestId requestId, const RPCError &e) = 0; virtual ~RPCAbstractFailHandler() { } }; using RPCFailHandlerPtr = std::shared_ptr<RPCAbstractFailHandler>; struct RPCResponseHandler { RPCResponseHandler() = default; RPCResponseHandler(RPCDoneHandlerPtr &&done, RPCFailHandlerPtr &&fail) : onDone(std::move(done)) , onFail(std::move(fail)) { } RPCDoneHandlerPtr onDone; RPCFailHandlerPtr onFail; }; template <typename TReturn> class RPCDoneHandlerBare : public RPCAbstractDoneHandler { // done(from, end) using CallbackType = TReturn (*)(const mtpPrime *, const mtpPrime *); public: RPCDoneHandlerBare(CallbackType onDone) : _onDone(onDone) { } void operator()(mtpRequestId requestId, const mtpPrime *from, const mtpPrime *end) override { (*_onDone)(from, end); } private: CallbackType _onDone; }; template <typename TReturn> class RPCDoneHandlerBareReq : public RPCAbstractDoneHandler { // done(from, end, req_id) using CallbackType = TReturn (*)(const mtpPrime *, const mtpPrime *, mtpRequestId); public: RPCDoneHandlerBareReq(CallbackType onDone) : _onDone(onDone) { } void operator()(mtpRequestId requestId, const mtpPrime *from, const mtpPrime *end) override { (*_onDone)(from, end, requestId); } private: CallbackType _onDone; }; template <typename TReturn, typename TResponse> class RPCDoneHandlerPlain : public RPCAbstractDoneHandler { // done(result) using CallbackType = TReturn (*)(const TResponse &); public: RPCDoneHandlerPlain(CallbackType onDone) : _onDone(onDone) { } void operator()(mtpRequestId requestId, const mtpPrime *from, const mtpPrime *end) override { auto response = TResponse(); response.read(from, end); (*_onDone)(std::move(response)); } private: CallbackType _onDone; }; template <typename TReturn, typename TResponse> class RPCDoneHandlerReq : public RPCAbstractDoneHandler { // done(result, req_id) using CallbackType = TReturn (*)(const TResponse &, mtpRequestId); public: RPCDoneHandlerReq(CallbackType onDone) : _onDone(onDone) { } void operator()(mtpRequestId requestId, const mtpPrime *from, const mtpPrime *end) override { auto response = TResponse(); response.read(from, end); (*_onDone)(std::move(response), requestId); } private: CallbackType _onDone; }; template <typename TReturn> class RPCDoneHandlerNo : public RPCAbstractDoneHandler { // done() using CallbackType = TReturn (*)(); public: RPCDoneHandlerNo(CallbackType onDone) : _onDone(onDone) { } void operator()(mtpRequestId requestId, const mtpPrime *from, const mtpPrime *end) override { (*_onDone)(); } private: CallbackType _onDone; }; template <typename TReturn> class RPCDoneHandlerNoReq : public RPCAbstractDoneHandler { // done(req_id) using CallbackType = TReturn (*)(mtpRequestId); public: RPCDoneHandlerNoReq(CallbackType onDone) : _onDone(onDone) { } void operator()(mtpRequestId requestId, const mtpPrime *from, const mtpPrime *end) override { (*_onDone)(requestId); } private: CallbackType _onDone; }; class RPCFailHandlerPlain : public RPCAbstractFailHandler { // fail(error) using CallbackType = bool (*)(const RPCError &); public: RPCFailHandlerPlain(CallbackType onFail) : _onFail(onFail) { } bool operator()(mtpRequestId requestId, const RPCError &e) override { return (*_onFail)(e); } private: CallbackType _onFail; }; class RPCFailHandlerReq : public RPCAbstractFailHandler { // fail(error, req_id) using CallbackType = bool (*)(const RPCError &, mtpRequestId); public: RPCFailHandlerReq(CallbackType onFail) : _onFail(onFail) { } bool operator()(mtpRequestId requestId, const RPCError &e) override { return (*_onFail)(e, requestId); } private: CallbackType _onFail; }; class RPCFailHandlerNo : public RPCAbstractFailHandler { // fail() using CallbackType = bool (*)(); public: RPCFailHandlerNo(CallbackType onFail) : _onFail(onFail) { } bool operator()(mtpRequestId requestId, const RPCError &e) override { return (*_onFail)(); } private: CallbackType _onFail; }; class RPCFailHandlerNoReq : public RPCAbstractFailHandler { // fail(req_id) using CallbackType = bool (*)(mtpRequestId); public: RPCFailHandlerNoReq(CallbackType onFail) : _onFail(onFail) { } bool operator()(mtpRequestId requestId, const RPCError &e) override { return (*_onFail)(requestId); } private: CallbackType _onFail; }; struct RPCCallbackClear { RPCCallbackClear(mtpRequestId id , int32 code = RPCError::NoError) : requestId(id) , errorCode(code) { } mtpRequestId requestId; int32 errorCode; }; template <typename TReturn> inline RPCDoneHandlerPtr rpcDone(TReturn (*onDone)(const mtpPrime *, const mtpPrime *)) { // done(from, end) return RPCDoneHandlerPtr(new RPCDoneHandlerBare<TReturn>(onDone)); } template <typename TReturn> inline RPCDoneHandlerPtr rpcDone(TReturn (*onDone)(const mtpPrime *, const mtpPrime *, mtpRequestId)) { // done(from, end, req_id) return RPCDoneHandlerPtr(new RPCDoneHandlerBareReq<TReturn>(onDone)); } template <typename TReturn, typename TResponse> inline RPCDoneHandlerPtr rpcDone(TReturn (*onDone)(const TResponse &)) { // done(result) return RPCDoneHandlerPtr(new RPCDoneHandlerPlain<TReturn, TResponse>(onDone)); } template <typename TReturn, typename TResponse> inline RPCDoneHandlerPtr rpcDone(TReturn (*onDone)(const TResponse &, mtpRequestId)) { // done(result, req_id) return RPCDoneHandlerPtr(new RPCDoneHandlerReq<TReturn, TResponse>(onDone)); } template <typename TReturn> inline RPCDoneHandlerPtr rpcDone(TReturn (*onDone)()) { // done() return RPCDoneHandlerPtr(new RPCDoneHandlerNo<TReturn>(onDone)); } template <typename TReturn> inline RPCDoneHandlerPtr rpcDone(TReturn (*onDone)(mtpRequestId)) { // done(req_id) return RPCDoneHandlerPtr(new RPCDoneHandlerNoReq<TReturn>(onDone)); } inline RPCFailHandlerPtr rpcFail(bool (*onFail)(const RPCError &)) { // fail(error) return RPCFailHandlerPtr(new RPCFailHandlerPlain(onFail)); } inline RPCFailHandlerPtr rpcFail(bool (*onFail)(const RPCError &, mtpRequestId)) { // fail(error, req_id) return RPCFailHandlerPtr(new RPCFailHandlerReq(onFail)); } inline RPCFailHandlerPtr rpcFail(bool (*onFail)()) { // fail() return RPCFailHandlerPtr(new RPCFailHandlerNo(onFail)); } inline RPCFailHandlerPtr rpcFail(bool (*onFail)(mtpRequestId)) { // fail(req_id) return RPCFailHandlerPtr(new RPCFailHandlerNoReq(onFail)); } class RPCSender; class RPCOwnedDoneHandler : public RPCAbstractDoneHandler { // abstract done public: RPCOwnedDoneHandler(RPCSender *owner); void invalidate() { _owner = nullptr; } ~RPCOwnedDoneHandler(); protected: RPCSender *_owner = nullptr; }; class RPCOwnedFailHandler : public RPCAbstractFailHandler { // abstract fail public: RPCOwnedFailHandler(RPCSender *owner); void invalidate() { _owner = nullptr; } ~RPCOwnedFailHandler(); protected: RPCSender *_owner = nullptr; }; template <typename TReturn, typename TReceiver> class RPCDoneHandlerBareOwned : public RPCOwnedDoneHandler { // done(from, end) using CallbackType = TReturn (TReceiver::*)(const mtpPrime *, const mtpPrime *); public: RPCDoneHandlerBareOwned(TReceiver *receiver, CallbackType onDone) : RPCOwnedDoneHandler(receiver), _onDone(onDone) { } void operator()(mtpRequestId requestId, const mtpPrime *from, const mtpPrime *end) override { if (_owner) (static_cast<TReceiver*>(_owner)->*_onDone)(from, end); } private: CallbackType _onDone; }; template <typename TReturn, typename TReceiver> class RPCDoneHandlerBareOwnedReq : public RPCOwnedDoneHandler { // done(from, end, req_id) using CallbackType = TReturn (TReceiver::*)(const mtpPrime *, const mtpPrime *, mtpRequestId); public: RPCDoneHandlerBareOwnedReq(TReceiver *receiver, CallbackType onDone) : RPCOwnedDoneHandler(receiver), _onDone(onDone) { } void operator()(mtpRequestId requestId, const mtpPrime *from, const mtpPrime *end) override { if (_owner) (static_cast<TReceiver*>(_owner)->*_onDone)(from, end, requestId); } private: CallbackType _onDone; }; template <typename TReturn, typename TReceiver, typename TResponse> class RPCDoneHandlerOwned : public RPCOwnedDoneHandler { // done(result) using CallbackType = TReturn (TReceiver::*)(const TResponse &); public: RPCDoneHandlerOwned(TReceiver *receiver, CallbackType onDone) : RPCOwnedDoneHandler(receiver), _onDone(onDone) { } void operator()(mtpRequestId requestId, const mtpPrime *from, const mtpPrime *end) override { if (_owner) { auto response = TResponse(); response.read(from, end); (static_cast<TReceiver*>(_owner)->*_onDone)(std::move(response)); } } private: CallbackType _onDone; }; template <typename TReturn, typename TReceiver, typename TResponse> class RPCDoneHandlerOwnedReq : public RPCOwnedDoneHandler { // done(result, req_id) using CallbackType = TReturn (TReceiver::*)(const TResponse &, mtpRequestId); public: RPCDoneHandlerOwnedReq(TReceiver *receiver, CallbackType onDone) : RPCOwnedDoneHandler(receiver), _onDone(onDone) { } void operator()(mtpRequestId requestId, const mtpPrime *from, const mtpPrime *end) override { if (_owner) { auto response = TResponse(); response.read(from, end); (static_cast<TReceiver*>(_owner)->*_onDone)(std::move(response), requestId); } } private: CallbackType _onDone; }; template <typename TReturn, typename TReceiver> class RPCDoneHandlerOwnedNo : public RPCOwnedDoneHandler { // done() using CallbackType = TReturn (TReceiver::*)(); public: RPCDoneHandlerOwnedNo(TReceiver *receiver, CallbackType onDone) : RPCOwnedDoneHandler(receiver), _onDone(onDone) { } void operator()(mtpRequestId requestId, const mtpPrime *from, const mtpPrime *end) override { if (_owner) (static_cast<TReceiver*>(_owner)->*_onDone)(); } private: CallbackType _onDone; }; template <typename TReturn, typename TReceiver> class RPCDoneHandlerOwnedNoReq : public RPCOwnedDoneHandler { // done(req_id) using CallbackType = TReturn (TReceiver::*)(mtpRequestId); public: RPCDoneHandlerOwnedNoReq(TReceiver *receiver, CallbackType onDone) : RPCOwnedDoneHandler(receiver), _onDone(onDone) { } void operator()(mtpRequestId requestId, const mtpPrime *from, const mtpPrime *end) override { if (_owner) (static_cast<TReceiver*>(_owner)->*_onDone)(requestId); } private: CallbackType _onDone; }; template <typename T, typename TReturn, typename TReceiver> class RPCBindedDoneHandlerBareOwned : public RPCOwnedDoneHandler { // done(b, from, end) using CallbackType = TReturn (TReceiver::*)(T, const mtpPrime *, const mtpPrime *); public: RPCBindedDoneHandlerBareOwned(T b, TReceiver *receiver, CallbackType onDone) : RPCOwnedDoneHandler(receiver), _b(b), _onDone(onDone) { } void operator()(mtpRequestId requestId, const mtpPrime *from, const mtpPrime *end) override { if (_owner) (static_cast<TReceiver*>(_owner)->*_onDone)(_b, from, end); } private: CallbackType _onDone; T _b; }; template <typename T, typename TReturn, typename TReceiver> class RPCBindedDoneHandlerBareOwnedReq : public RPCOwnedDoneHandler { // done(b, from, end, req_id) using CallbackType = TReturn (TReceiver::*)(T, const mtpPrime *, const mtpPrime *, mtpRequestId); public: RPCBindedDoneHandlerBareOwnedReq(T b, TReceiver *receiver, CallbackType onDone) : RPCOwnedDoneHandler(receiver), _b(b), _onDone(onDone) { } void operator()(mtpRequestId requestId, const mtpPrime *from, const mtpPrime *end) override { if (_owner) (static_cast<TReceiver*>(_owner)->*_onDone)(_b, from, end, requestId); } private: CallbackType _onDone; T _b; }; template <typename T, typename TReturn, typename TReceiver, typename TResponse> class RPCBindedDoneHandlerOwned : public RPCOwnedDoneHandler { // done(b, result) using CallbackType = TReturn (TReceiver::*)(T, const TResponse &); public: RPCBindedDoneHandlerOwned(T b, TReceiver *receiver, CallbackType onDone) : RPCOwnedDoneHandler(receiver), _onDone(onDone), _b(b) { } void operator()(mtpRequestId requestId, const mtpPrime *from, const mtpPrime *end) override { if (_owner) { auto response = TResponse(); response.read(from, end); (static_cast<TReceiver*>(_owner)->*_onDone)(_b, std::move(response)); } } private: CallbackType _onDone; T _b; }; template <typename T, typename TReturn, typename TReceiver, typename TResponse> class RPCBindedDoneHandlerOwnedReq : public RPCOwnedDoneHandler { // done(b, result, req_id) using CallbackType = TReturn (TReceiver::*)(T, const TResponse &, mtpRequestId); public: RPCBindedDoneHandlerOwnedReq(T b, TReceiver *receiver, CallbackType onDone) : RPCOwnedDoneHandler(receiver), _onDone(onDone), _b(b) { } void operator()(mtpRequestId requestId, const mtpPrime *from, const mtpPrime *end) override { if (_owner) { auto response = TResponse(); response.read(from, end); (static_cast<TReceiver*>(_owner)->*_onDone)(_b, std::move(response), requestId); } } private: CallbackType _onDone; T _b; }; template <typename T, typename TReturn, typename TReceiver> class RPCBindedDoneHandlerOwnedNo : public RPCOwnedDoneHandler { // done(b) using CallbackType = TReturn (TReceiver::*)(T); public: RPCBindedDoneHandlerOwnedNo(T b, TReceiver *receiver, CallbackType onDone) : RPCOwnedDoneHandler(receiver), _b(b), _onDone(onDone) { } void operator()(mtpRequestId requestId, const mtpPrime *from, const mtpPrime *end) override { if (_owner) (static_cast<TReceiver*>(_owner)->*_onDone)(_b); } private: CallbackType _onDone; T _b; }; template <typename T, typename TReturn, typename TReceiver> class RPCBindedDoneHandlerOwnedNoReq : public RPCOwnedDoneHandler { // done(b, req_id) using CallbackType = TReturn (TReceiver::*)(T, mtpRequestId); public: RPCBindedDoneHandlerOwnedNoReq(T b, TReceiver *receiver, CallbackType onDone) : RPCOwnedDoneHandler(receiver), _b(b), _onDone(onDone) { } void operator()(mtpRequestId requestId, const mtpPrime *from, const mtpPrime *end) override { if (_owner) (static_cast<TReceiver*>(_owner)->*_onDone)(_b, requestId); } private: CallbackType _onDone; T _b; }; template <typename TReceiver> class RPCFailHandlerOwned : public RPCOwnedFailHandler { // fail(error) using CallbackType = bool (TReceiver::*)(const RPCError &); public: RPCFailHandlerOwned(TReceiver *receiver, CallbackType onFail) : RPCOwnedFailHandler(receiver), _onFail(onFail) { } bool operator()(mtpRequestId requestId, const RPCError &e) override { return _owner ? (static_cast<TReceiver*>(_owner)->*_onFail)(e) : true; } private: CallbackType _onFail; }; template <typename TReceiver> class RPCFailHandlerOwnedReq : public RPCOwnedFailHandler { // fail(error, req_id) using CallbackType = bool (TReceiver::*)(const RPCError &, mtpRequestId); public: RPCFailHandlerOwnedReq(TReceiver *receiver, CallbackType onFail) : RPCOwnedFailHandler(receiver), _onFail(onFail) { } bool operator()(mtpRequestId requestId, const RPCError &e) override { return _owner ? (static_cast<TReceiver*>(_owner)->*_onFail)(e, requestId) : true; } private: CallbackType _onFail; }; template <typename TReceiver> class RPCFailHandlerOwnedNo : public RPCOwnedFailHandler { // fail() using CallbackType = bool (TReceiver::*)(); public: RPCFailHandlerOwnedNo(TReceiver *receiver, CallbackType onFail) : RPCOwnedFailHandler(receiver), _onFail(onFail) { } bool operator()(mtpRequestId requestId, const RPCError &e) override { return _owner ? (static_cast<TReceiver*>(_owner)->*_onFail)() : true; } private: CallbackType _onFail; }; template <typename TReceiver> class RPCFailHandlerOwnedNoReq : public RPCOwnedFailHandler { // fail(req_id) using CallbackType = bool (TReceiver::*)(mtpRequestId); public: RPCFailHandlerOwnedNoReq(TReceiver *receiver, CallbackType onFail) : RPCOwnedFailHandler(receiver), _onFail(onFail) { } bool operator()(mtpRequestId requestId, const RPCError &e) override { return _owner ? (static_cast<TReceiver*>(_owner)->*_onFail)(requestId) : true; } private: CallbackType _onFail; }; template <typename T, typename TReceiver> class RPCBindedFailHandlerOwned : public RPCOwnedFailHandler { // fail(b, error) using CallbackType = bool (TReceiver::*)(T, const RPCError &); public: RPCBindedFailHandlerOwned(T b, TReceiver *receiver, CallbackType onFail) : RPCOwnedFailHandler(receiver), _onFail(onFail), _b(b) { } bool operator()(mtpRequestId requestId, const RPCError &e) override { return _owner ? (static_cast<TReceiver*>(_owner)->*_onFail)(_b, e) : true; } private: CallbackType _onFail; T _b; }; template <typename T, typename TReceiver> class RPCBindedFailHandlerOwnedReq : public RPCOwnedFailHandler { // fail(b, error, req_id) using CallbackType = bool (TReceiver::*)(T, const RPCError &, mtpRequestId); public: RPCBindedFailHandlerOwnedReq(T b, TReceiver *receiver, CallbackType onFail) : RPCOwnedFailHandler(receiver), _onFail(onFail), _b(b) { } bool operator()(mtpRequestId requestId, const RPCError &e) override { return _owner ? (static_cast<TReceiver*>(_owner)->*_onFail)(_b, e, requestId) : true; } private: CallbackType _onFail; T _b; }; template <typename T, typename TReceiver> class RPCBindedFailHandlerOwnedNo : public RPCOwnedFailHandler { // fail(b) using CallbackType = bool (TReceiver::*)(T); public: RPCBindedFailHandlerOwnedNo(T b, TReceiver *receiver, CallbackType onFail) : RPCOwnedFailHandler(receiver), _onFail(onFail), _b(b) { } bool operator()(mtpRequestId requestId, const RPCError &e) override { return _owner ? (static_cast<TReceiver*>(_owner)->*_onFail)(_b) : true; } private: CallbackType _onFail; T _b; }; template <typename T, typename TReceiver> class RPCBindedFailHandlerOwnedNoReq : public RPCOwnedFailHandler { // fail(b, req_id) using CallbackType = bool (TReceiver::*)(T, mtpRequestId); public: RPCBindedFailHandlerOwnedNoReq(T b, TReceiver *receiver, CallbackType onFail) : RPCOwnedFailHandler(receiver), _onFail(onFail), _b(b) { } bool operator()(mtpRequestId requestId, const RPCError &e) override { return _owner ? (static_cast<TReceiver*>(_owner)->*_onFail)(_b, requestId) : true; } private: CallbackType _onFail; T _b; }; class RPCSender { using DoneHandlers = QSet<RPCOwnedDoneHandler*>; DoneHandlers _rpcDoneHandlers; using FailHandlers = QSet<RPCOwnedFailHandler*>; FailHandlers _rpcFailHandlers; void _rpcRegHandler(RPCOwnedDoneHandler *handler) { _rpcDoneHandlers.insert(handler); } void _rpcUnregHandler(RPCOwnedDoneHandler *handler) { _rpcDoneHandlers.remove(handler); } void _rpcRegHandler(RPCOwnedFailHandler *handler) { _rpcFailHandlers.insert(handler); } void _rpcUnregHandler(RPCOwnedFailHandler *handler) { _rpcFailHandlers.remove(handler); } friend class RPCOwnedDoneHandler; friend class RPCOwnedFailHandler; public: template <typename TReturn, typename TReceiver> // done(from, end) RPCDoneHandlerPtr rpcDone(TReturn (TReceiver::*onDone)(const mtpPrime *, const mtpPrime *)) { return RPCDoneHandlerPtr(new RPCDoneHandlerBareOwned<TReturn, TReceiver>(static_cast<TReceiver*>(this), onDone)); } template <typename TReturn, typename TReceiver> // done(from, end, req_id) RPCDoneHandlerPtr rpcDone(TReturn (TReceiver::*onDone)(const mtpPrime *, const mtpPrime *, mtpRequestId)) { return RPCDoneHandlerPtr(new RPCDoneHandlerBareOwnedReq<TReturn, TReceiver>(static_cast<TReceiver*>(this), onDone)); } template <typename TReturn, typename TReceiver, typename TResponse> // done(result) RPCDoneHandlerPtr rpcDone(TReturn (TReceiver::*onDone)(const TResponse &)) { return RPCDoneHandlerPtr(new RPCDoneHandlerOwned<TReturn, TReceiver, TResponse>(static_cast<TReceiver*>(this), onDone)); } template <typename TReturn, typename TReceiver, typename TResponse> // done(result, req_id) RPCDoneHandlerPtr rpcDone(TReturn (TReceiver::*onDone)(const TResponse &, mtpRequestId)) { return RPCDoneHandlerPtr(new RPCDoneHandlerOwnedReq<TReturn, TReceiver, TResponse>(static_cast<TReceiver*>(this), onDone)); } template <typename TReturn, typename TReceiver> // done() RPCDoneHandlerPtr rpcDone(TReturn (TReceiver::*onDone)()) { return RPCDoneHandlerPtr(new RPCDoneHandlerOwnedNo<TReturn, TReceiver>(static_cast<TReceiver*>(this), onDone)); } template <typename TReturn, typename TReceiver> // done(req_id) RPCDoneHandlerPtr rpcDone(TReturn (TReceiver::*onDone)(mtpRequestId)) { return RPCDoneHandlerPtr(new RPCDoneHandlerOwnedNoReq<TReturn, TReceiver>(static_cast<TReceiver*>(this), onDone)); } template <typename TReceiver> // fail(error) RPCFailHandlerPtr rpcFail(bool (TReceiver::*onFail)(const RPCError &)) { return RPCFailHandlerPtr(new RPCFailHandlerOwned<TReceiver>(static_cast<TReceiver*>(this), onFail)); } template <typename TReceiver> // fail(error, req_id) RPCFailHandlerPtr rpcFail(bool (TReceiver::*onFail)(const RPCError &, mtpRequestId)) { return RPCFailHandlerPtr(new RPCFailHandlerOwnedReq<TReceiver>(static_cast<TReceiver*>(this), onFail)); } template <typename TReceiver> // fail() RPCFailHandlerPtr rpcFail(bool (TReceiver::*onFail)()) { return RPCFailHandlerPtr(new RPCFailHandlerOwnedNo<TReceiver>(static_cast<TReceiver*>(this), onFail)); } template <typename TReceiver> // fail(req_id) RPCFailHandlerPtr rpcFail(bool (TReceiver::*onFail)(mtpRequestId)) { return RPCFailHandlerPtr(new RPCFailHandlerOwnedNo<TReceiver>(static_cast<TReceiver*>(this), onFail)); } template <typename T, typename TReturn, typename TReceiver> // done(b, from, end) RPCDoneHandlerPtr rpcDone(TReturn (TReceiver::*onDone)(T, const mtpPrime *, const mtpPrime *), T b) { return RPCDoneHandlerPtr(new RPCBindedDoneHandlerBareOwned<T, TReturn, TReceiver>(b, static_cast<TReceiver*>(this), onDone)); } template <typename T, typename TReturn, typename TReceiver> // done(b, from, end, req_id) RPCDoneHandlerPtr rpcDone(TReturn (TReceiver::*onDone)(T, const mtpPrime *, const mtpPrime *, mtpRequestId), T b) { return RPCDoneHandlerPtr(new RPCBindedDoneHandlerBareOwnedReq<T, TReturn, TReceiver>(b, static_cast<TReceiver*>(this), onDone)); } template <typename T, typename TReturn, typename TReceiver, typename TResponse> // done(b, result) RPCDoneHandlerPtr rpcDone(TReturn (TReceiver::*onDone)(T, const TResponse &), T b) { return RPCDoneHandlerPtr(new RPCBindedDoneHandlerOwned<T, TReturn, TReceiver, TResponse>(b, static_cast<TReceiver*>(this), onDone)); } template <typename T, typename TReturn, typename TReceiver, typename TResponse> // done(b, result, req_id) RPCDoneHandlerPtr rpcDone(TReturn (TReceiver::*onDone)(T, const TResponse &, mtpRequestId), T b) { return RPCDoneHandlerPtr(new RPCBindedDoneHandlerOwnedReq<T, TReturn, TReceiver, TResponse>(b, static_cast<TReceiver*>(this), onDone)); } template <typename T, typename TReturn, typename TReceiver> // done(b) RPCDoneHandlerPtr rpcDone(TReturn (TReceiver::*onDone)(T), T b) { return RPCDoneHandlerPtr(new RPCBindedDoneHandlerOwnedNo<T, TReturn, TReceiver>(b, static_cast<TReceiver*>(this), onDone)); } template <typename T, typename TReturn, typename TReceiver> // done(b, req_id) RPCDoneHandlerPtr rpcDone(TReturn (TReceiver::*onDone)(T, mtpRequestId), T b) { return RPCDoneHandlerPtr(new RPCBindedDoneHandlerOwnedNoReq<T, TReturn, TReceiver>(b, static_cast<TReceiver*>(this), onDone)); } template <typename T, typename TReceiver> // fail(b, error) RPCFailHandlerPtr rpcFail(bool (TReceiver::*onFail)(T, const RPCError &), T b) { return RPCFailHandlerPtr(new RPCBindedFailHandlerOwned<T, TReceiver>(b, static_cast<TReceiver*>(this), onFail)); } template <typename T, typename TReceiver> // fail(b, error, req_id) RPCFailHandlerPtr rpcFail(bool (TReceiver::*onFail)(T, const RPCError &, mtpRequestId), T b) { return RPCFailHandlerPtr(new RPCBindedFailHandlerOwnedReq<T, TReceiver>(b, static_cast<TReceiver*>(this), onFail)); } template <typename T, typename TReceiver> // fail(b) RPCFailHandlerPtr rpcFail(bool (TReceiver::*onFail)(T), T b) { return RPCFailHandlerPtr(new RPCBindedFailHandlerOwnedNo<T, TReceiver>(b, static_cast<TReceiver*>(this), onFail)); } template <typename T, typename TReceiver> // fail(b, req_id) RPCFailHandlerPtr rpcFail(bool (TReceiver::*onFail)(T, mtpRequestId), T b) { return RPCFailHandlerPtr(new RPCBindedFailHandlerOwnedNo<T, TReceiver>(b, static_cast<TReceiver*>(this), onFail)); } virtual void rpcClear() { rpcInvalidate(); } virtual ~RPCSender() { rpcInvalidate(); } protected: void rpcInvalidate() { for (auto handler : base::take(_rpcDoneHandlers)) { handler->invalidate(); } for (auto handler : base::take(_rpcFailHandlers)) { handler->invalidate(); } } }; using MTPStateChangedHandler = void (*)(int32 dcId, int32 state); using MTPSessionResetHandler = void (*)(int32 dcId); template <typename Base, typename FunctionType> class RPCHandlerImplementation : public Base { protected: using Lambda = base::lambda_once<FunctionType>; using Parent = RPCHandlerImplementation<Base, FunctionType>; public: RPCHandlerImplementation(Lambda handler) : _handler(std::move(handler)) { } protected: Lambda _handler; }; template <typename FunctionType> using RPCDoneHandlerImplementation = RPCHandlerImplementation<RPCAbstractDoneHandler, FunctionType>; template <typename R> class RPCDoneHandlerImplementationBare : public RPCDoneHandlerImplementation<R(const mtpPrime*, const mtpPrime*)> { // done(from, end) public: using RPCDoneHandlerImplementation<R(const mtpPrime*, const mtpPrime*)>::Parent::Parent; void operator()(mtpRequestId requestId, const mtpPrime *from, const mtpPrime *end) override { return this->_handler ? this->_handler(from, end) : void(0); } }; template <typename R> class RPCDoneHandlerImplementationBareReq : public RPCDoneHandlerImplementation<R(const mtpPrime*, const mtpPrime*, mtpRequestId)> { // done(from, end, req_id) public: using RPCDoneHandlerImplementation<R(const mtpPrime*, const mtpPrime*, mtpRequestId)>::Parent::Parent; void operator()(mtpRequestId requestId, const mtpPrime *from, const mtpPrime *end) override { return this->_handler ? this->_handler(from, end, requestId) : void(0); } }; template <typename R, typename TResponse> class RPCDoneHandlerImplementationPlain : public RPCDoneHandlerImplementation<R(const TResponse&)> { // done(result) public: using RPCDoneHandlerImplementation<R(const TResponse&)>::Parent::Parent; void operator()(mtpRequestId requestId, const mtpPrime *from, const mtpPrime *end) override { if (this->_handler) { auto response = TResponse(); response.read(from, end); this->_handler(std::move(response)); } } }; template <typename R, typename TResponse> class RPCDoneHandlerImplementationReq : public RPCDoneHandlerImplementation<R(const TResponse&, mtpRequestId)> { // done(result, req_id) public: using RPCDoneHandlerImplementation<R(const TResponse&, mtpRequestId)>::Parent::Parent; void operator()(mtpRequestId requestId, const mtpPrime *from, const mtpPrime *end) override { if (this->_handler) { auto response = TResponse(); response.read(from, end); this->_handler(std::move(response), requestId); } } }; template <typename R> class RPCDoneHandlerImplementationNo : public RPCDoneHandlerImplementation<R()> { // done() public: using RPCDoneHandlerImplementation<R()>::Parent::Parent; void operator()(mtpRequestId requestId, const mtpPrime *from, const mtpPrime *end) override { if (this->_handler) { this->_handler(); } } }; template <typename R> class RPCDoneHandlerImplementationNoReq : public RPCDoneHandlerImplementation<R(mtpRequestId)> { // done(req_id) public: using RPCDoneHandlerImplementation<R(mtpRequestId)>::Parent::Parent; void operator()(mtpRequestId requestId, const mtpPrime *from, const mtpPrime *end) override { if (this->_handler) { this->_handler(requestId); } } }; template <typename Lambda> constexpr bool rpcDone_canCallBare_v = rpl::details::is_callable_plain_v< Lambda, const mtpPrime*, const mtpPrime*>; template <typename Lambda> constexpr bool rpcDone_canCallBareReq_v = rpl::details::is_callable_plain_v< Lambda, const mtpPrime*, const mtpPrime*, mtpRequestId>; template <typename Lambda> constexpr bool rpcDone_canCallNo_v = rpl::details::is_callable_plain_v< Lambda>; template <typename Lambda> constexpr bool rpcDone_canCallNoReq_v = rpl::details::is_callable_plain_v< Lambda, mtpRequestId>; template <typename Function> struct rpcDone_canCallPlain : std::false_type { }; template <typename Lambda, typename Return, typename T> struct rpcDone_canCallPlain<Return(Lambda::*)(const T&)> : std::true_type { using Arg = T; }; template <typename Lambda, typename Return, typename T> struct rpcDone_canCallPlain<Return(Lambda::*)(const T&)const> : rpcDone_canCallPlain<Return(Lambda::*)(const T&)> { }; template <typename Function> constexpr bool rpcDone_canCallPlain_v = rpcDone_canCallPlain<Function>::value; template <typename Function> struct rpcDone_canCallReq : std::false_type { }; template <typename Lambda, typename Return, typename T> struct rpcDone_canCallReq<Return(Lambda::*)(const T&, mtpRequestId)> : std::true_type { using Arg = T; }; template <typename Lambda, typename Return, typename T> struct rpcDone_canCallReq<Return(Lambda::*)(const T&, mtpRequestId)const> : rpcDone_canCallReq<Return(Lambda::*)(const T&, mtpRequestId)> { }; template <typename Function> constexpr bool rpcDone_canCallReq_v = rpcDone_canCallReq<Function>::value; template <typename Function> struct rpcDone_returnType; template <typename Lambda, typename Return, typename ...Args> struct rpcDone_returnType<Return(Lambda::*)(Args...)> { using type = Return; }; template <typename Lambda, typename Return, typename ...Args> struct rpcDone_returnType<Return(Lambda::*)(Args...)const> { using type = Return; }; template <typename Function> using rpcDone_returnType_t = typename rpcDone_returnType<Function>::type; template < typename Lambda, typename Function = base::lambda_call_type_t<Lambda>> RPCDoneHandlerPtr rpcDone(Lambda lambda) { using R = rpcDone_returnType_t<Function>; if constexpr (rpcDone_canCallBare_v<Lambda>) { return RPCDoneHandlerPtr(new RPCDoneHandlerImplementationBare<R>(std::move(lambda))); } else if constexpr (rpcDone_canCallBareReq_v<Lambda>) { return RPCDoneHandlerPtr(new RPCDoneHandlerImplementationBareReq<R>(std::move(lambda))); } else if constexpr (rpcDone_canCallNo_v<Lambda>) { return RPCDoneHandlerPtr(new RPCDoneHandlerImplementationNo<R>(std::move(lambda))); } else if constexpr (rpcDone_canCallNoReq_v<Lambda>) { return RPCDoneHandlerPtr(new RPCDoneHandlerImplementationNoReq<R>(std::move(lambda))); } else if constexpr (rpcDone_canCallPlain_v<Function>) { using T = typename rpcDone_canCallPlain<Function>::Arg; return RPCDoneHandlerPtr(new RPCDoneHandlerImplementationPlain<R, T>(std::move(lambda))); } else if constexpr (rpcDone_canCallReq_v<Function>) { using T = typename rpcDone_canCallReq<Function>::Arg; return RPCDoneHandlerPtr(new RPCDoneHandlerImplementationReq<R, T>(std::move(lambda))); } else { static_assert(false_t(lambda), "Unknown method."); } } template <typename FunctionType> using RPCFailHandlerImplementation = RPCHandlerImplementation<RPCAbstractFailHandler, FunctionType>; class RPCFailHandlerImplementationPlain : public RPCFailHandlerImplementation<bool(const RPCError&)> { // fail(error) public: using Parent::Parent; bool operator()(mtpRequestId requestId, const RPCError &error) override { return _handler ? _handler(error) : true; } }; class RPCFailHandlerImplementationReq : public RPCFailHandlerImplementation<bool(const RPCError&, mtpRequestId)> { // fail(error, req_id) public: using Parent::Parent; bool operator()(mtpRequestId requestId, const RPCError &error) override { return this->_handler ? this->_handler(error, requestId) : true; } }; class RPCFailHandlerImplementationNo : public RPCFailHandlerImplementation<bool()> { // fail() public: using Parent::Parent; bool operator()(mtpRequestId requestId, const RPCError &error) override { return this->_handler ? this->_handler() : true; } }; class RPCFailHandlerImplementationNoReq : public RPCFailHandlerImplementation<bool(mtpRequestId)> { // fail(req_id) public: using Parent::Parent; bool operator()(mtpRequestId requestId, const RPCError &error) override { return this->_handler ? this->_handler(requestId) : true; } }; template <typename Lambda> constexpr bool rpcFail_canCallNo_v = rpl::details::is_callable_plain_v< Lambda>; template <typename Lambda> constexpr bool rpcFail_canCallNoReq_v = rpl::details::is_callable_plain_v< Lambda, mtpRequestId>; template <typename Lambda> constexpr bool rpcFail_canCallPlain_v = rpl::details::is_callable_plain_v< Lambda, const RPCError&>; template <typename Lambda> constexpr bool rpcFail_canCallReq_v = rpl::details::is_callable_plain_v< Lambda, const RPCError&, mtpRequestId>; template < typename Lambda, typename Function = base::lambda_call_type_t<Lambda>> RPCFailHandlerPtr rpcFail(Lambda lambda) { if constexpr (rpcFail_canCallNo_v<Lambda>) { return RPCFailHandlerPtr(new RPCFailHandlerImplementationNo(std::move(lambda))); } else if constexpr (rpcFail_canCallNoReq_v<Lambda>) { return RPCFailHandlerPtr(new RPCFailHandlerImplementationNoReq(std::move(lambda))); } else if constexpr (rpcFail_canCallPlain_v<Lambda>) { return RPCFailHandlerPtr(new RPCFailHandlerImplementationPlain(std::move(lambda))); } else if constexpr (rpcFail_canCallReq_v<Lambda>) { return RPCFailHandlerPtr(new RPCFailHandlerImplementationReq(std::move(lambda))); } else { static_assert(false_t(lambda), "Unknown method."); } } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/mtproto/rsa_public_key.cpp�������������������������������������0000664�0000000�0000000�00000014610�13262451251�0024630�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "mtproto/rsa_public_key.h" #include "base/openssl_help.h" #include <openssl/rsa.h> #include <openssl/pem.h> #include <openssl/bio.h> #include <openssl/err.h> namespace MTP { namespace internal { namespace { #if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) // This is a key setter for compatibility with OpenSSL 1.0 int RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d) { if ((r->n == nullptr && n == nullptr) || (r->e == nullptr && e == nullptr)) { return 0; } if (n != nullptr) { BN_free(r->n); r->n = n; } if (e != nullptr) { BN_free(r->e); r->e = e; } if (d != nullptr) { BN_free(r->d); r->d = d; } return 1; } // This is a key getter for compatibility with OpenSSL 1.0 void RSA_get0_key(const RSA *r, const BIGNUM **n, const BIGNUM **e, const BIGNUM **d) { if (n != nullptr) { *n = r->n; } if (e != nullptr) { *e = r->e; } if (d != nullptr) { *d = r->d; } } #endif enum class Format { RSAPublicKey, RSA_PUBKEY, Unknown, }; Format GuessFormat(base::const_byte_span key) { const auto array = QByteArray::fromRawData( reinterpret_cast<const char*>(key.data()), key.size()); if (array.indexOf("BEGIN RSA PUBLIC KEY") >= 0) { return Format::RSAPublicKey; } else if (array.indexOf("BEGIN PUBLIC KEY") >= 0) { return Format::RSA_PUBKEY; } return Format::Unknown; } RSA *CreateRaw(base::const_byte_span key) { const auto format = GuessFormat(key); const auto bio = BIO_new_mem_buf( const_cast<gsl::byte*>(key.data()), key.size()); switch (format) { case Format::RSAPublicKey: return PEM_read_bio_RSAPublicKey(bio, nullptr, nullptr, nullptr); case Format::RSA_PUBKEY: return PEM_read_bio_RSA_PUBKEY(bio, nullptr, nullptr, nullptr); } Unexpected("format in RSAPublicKey::Private::Create."); } } // namespace class RSAPublicKey::Private { public: Private(base::const_byte_span key) : _rsa(CreateRaw(key)) { if (_rsa) { computeFingerprint(); } } Private(base::const_byte_span nBytes, base::const_byte_span eBytes) : _rsa(RSA_new()) { if (_rsa) { auto n = openssl::BigNum(nBytes).takeRaw(); auto e = openssl::BigNum(eBytes).takeRaw(); auto valid = (n != nullptr) && (e != nullptr); // We still pass both values to RSA_set0_key() so that even // if only one of them is valid RSA would take ownership of it. if (!RSA_set0_key(_rsa, n, e, nullptr) || !valid) { RSA_free(base::take(_rsa)); } else { computeFingerprint(); } } } base::byte_vector getN() const { Expects(isValid()); const BIGNUM *n; RSA_get0_key(_rsa, &n, nullptr, nullptr); return toBytes(n); } base::byte_vector getE() const { Expects(isValid()); const BIGNUM *e; RSA_get0_key(_rsa, nullptr, &e, nullptr); return toBytes(e); } uint64 getFingerPrint() const { return _fingerprint; } bool isValid() const { return _rsa != nullptr; } base::byte_vector encrypt(base::const_byte_span data) const { Expects(isValid()); constexpr auto kEncryptSize = 256; auto result = base::byte_vector(kEncryptSize, gsl::byte {}); auto res = RSA_public_encrypt(kEncryptSize, reinterpret_cast<const unsigned char*>(data.data()), reinterpret_cast<unsigned char*>(result.data()), _rsa, RSA_NO_PADDING); if (res < 0 || res > kEncryptSize) { ERR_load_crypto_strings(); LOG(("RSA Error: RSA_public_encrypt failed, key fp: %1, result: %2, error: %3").arg(getFingerPrint()).arg(res).arg(ERR_error_string(ERR_get_error(), 0))); return base::byte_vector(); } else if (auto zeroBytes = kEncryptSize - res) { auto resultBytes = gsl::make_span(result); base::move_bytes(resultBytes.subspan(zeroBytes, res), resultBytes.subspan(0, res)); base::set_bytes(resultBytes.subspan(0, zeroBytes), gsl::byte {}); } return result; } base::byte_vector decrypt(base::const_byte_span data) const { Expects(isValid()); constexpr auto kDecryptSize = 256; auto result = base::byte_vector(kDecryptSize, gsl::byte {}); auto res = RSA_public_decrypt(kDecryptSize, reinterpret_cast<const unsigned char*>(data.data()), reinterpret_cast<unsigned char*>(result.data()), _rsa, RSA_NO_PADDING); if (res < 0 || res > kDecryptSize) { ERR_load_crypto_strings(); LOG(("RSA Error: RSA_public_encrypt failed, key fp: %1, result: %2, error: %3").arg(getFingerPrint()).arg(res).arg(ERR_error_string(ERR_get_error(), 0))); return base::byte_vector(); } else if (auto zeroBytes = kDecryptSize - res) { auto resultBytes = gsl::make_span(result); base::move_bytes(resultBytes.subspan(zeroBytes - res, res), resultBytes.subspan(0, res)); base::set_bytes(resultBytes.subspan(0, zeroBytes - res), gsl::byte {}); } return result; } ~Private() { RSA_free(_rsa); } private: void computeFingerprint() { Expects(isValid()); const BIGNUM *n, *e; mtpBuffer string; RSA_get0_key(_rsa, &n, &e, nullptr); MTP_bytes(toBytes(n)).write(string); MTP_bytes(toBytes(e)).write(string); uchar sha1Buffer[20]; _fingerprint = *(uint64*)(hashSha1(&string[0], string.size() * sizeof(mtpPrime), sha1Buffer) + 3); } static base::byte_vector toBytes(const BIGNUM *number) { auto size = BN_num_bytes(number); auto result = base::byte_vector(size, gsl::byte {}); BN_bn2bin(number, reinterpret_cast<unsigned char*>(result.data())); return result; } RSA *_rsa = nullptr; uint64 _fingerprint = 0; }; RSAPublicKey::RSAPublicKey(base::const_byte_span key) : _private(std::make_shared<Private>(key)) { } RSAPublicKey::RSAPublicKey( base::const_byte_span nBytes, base::const_byte_span eBytes) : _private(std::make_shared<Private>(nBytes, eBytes)) { } bool RSAPublicKey::isValid() const { return _private && _private->isValid(); } uint64 RSAPublicKey::getFingerPrint() const { Expects(isValid()); return _private->getFingerPrint(); } base::byte_vector RSAPublicKey::getN() const { Expects(isValid()); return _private->getN(); } base::byte_vector RSAPublicKey::getE() const { Expects(isValid()); return _private->getE(); } base::byte_vector RSAPublicKey::encrypt(base::const_byte_span data) const { Expects(isValid()); return _private->encrypt(data); } base::byte_vector RSAPublicKey::decrypt(base::const_byte_span data) const { Expects(isValid()); return _private->decrypt(data); } } // namespace internal } // namespace MTP ������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/mtproto/rsa_public_key.h���������������������������������������0000664�0000000�0000000�00000002474�13262451251�0024302�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace MTP { namespace internal { // this class holds an RSA public key and can encrypt fixed-size messages with it class RSAPublicKey final { public: RSAPublicKey() = default; RSAPublicKey(base::const_byte_span nBytes, base::const_byte_span eBytes); RSAPublicKey(RSAPublicKey &&other) = default; RSAPublicKey(const RSAPublicKey &other) = default; RSAPublicKey &operator=(RSAPublicKey &&other) = default; RSAPublicKey &operator=(const RSAPublicKey &other) = default; // key in "-----BEGIN RSA PUBLIC KEY----- ..." format // or in "-----BEGIN PUBLIC KEY----- ..." format explicit RSAPublicKey(base::const_byte_span key); bool isValid() const; uint64 getFingerPrint() const; base::byte_vector getN() const; base::byte_vector getE() const; // data has exactly 256 chars to be encrypted base::byte_vector encrypt(base::const_byte_span data) const; // data has exactly 256 chars to be decrypted base::byte_vector decrypt(base::const_byte_span data) const; private: class Private; std::shared_ptr<Private> _private; }; } // namespace internal } // namespace MTP ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/mtproto/sender.h�����������������������������������������������0000664�0000000�0000000�00000025427�13262451251�0022572�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "base/variant.h" namespace MTP { class Instance; Instance *MainInstance(); class Sender { class RequestBuilder { public: RequestBuilder(const RequestBuilder &other) = delete; RequestBuilder &operator=(const RequestBuilder &other) = delete; RequestBuilder &operator=(RequestBuilder &&other) = delete; protected: using FailPlainHandler = base::lambda_once<void(const RPCError &error)>; using FailRequestIdHandler = base::lambda_once<void(const RPCError &error, mtpRequestId requestId)>; enum class FailSkipPolicy { Simple, HandleFlood, HandleAll, }; template <typename Response> struct DonePlainPolicy { using Callback = base::lambda_once<void(const Response &result)>; static void handle(Callback &&handler, mtpRequestId requestId, Response &&result) { handler(result); } }; template <typename Response> struct DoneRequestIdPolicy { using Callback = base::lambda_once<void(const Response &result, mtpRequestId requestId)>; static void handle(Callback &&handler, mtpRequestId requestId, Response &&result) { handler(result, requestId); } }; template <typename Response, template <typename> typename PolicyTemplate> class DoneHandler : public RPCAbstractDoneHandler { using Policy = PolicyTemplate<Response>; using Callback = typename Policy::Callback; public: DoneHandler(not_null<Sender*> sender, Callback handler) : _sender(sender), _handler(std::move(handler)) { } void operator()(mtpRequestId requestId, const mtpPrime *from, const mtpPrime *end) override { auto handler = std::move(_handler); _sender->senderRequestHandled(requestId); if (handler) { auto result = Response(); result.read(from, end); Policy::handle(std::move(handler), requestId, std::move(result)); } } private: not_null<Sender*> _sender; Callback _handler; }; struct FailPlainPolicy { using Callback = base::lambda_once<void(const RPCError &error)>; static void handle(Callback &&handler, mtpRequestId requestId, const RPCError &error) { handler(error); } }; struct FailRequestIdPolicy { using Callback = base::lambda_once<void(const RPCError &error, mtpRequestId requestId)>; static void handle(Callback &&handler, mtpRequestId requestId, const RPCError &error) { handler(error, requestId); } }; template <typename Policy> class FailHandler : public RPCAbstractFailHandler { using Callback = typename Policy::Callback; public: FailHandler(not_null<Sender*> sender, Callback handler, FailSkipPolicy skipPolicy) : _sender(sender) , _handler(std::move(handler)) , _skipPolicy(skipPolicy) { } bool operator()(mtpRequestId requestId, const RPCError &error) override { if (_skipPolicy == FailSkipPolicy::Simple) { if (MTP::isDefaultHandledError(error)) { return false; } } else if (_skipPolicy == FailSkipPolicy::HandleFlood) { if (MTP::isDefaultHandledError(error) && !MTP::isFloodError(error)) { return false; } } auto handler = std::move(_handler); _sender->senderRequestHandled(requestId); if (handler) { Policy::handle(std::move(handler), requestId, error); } return true; } private: not_null<Sender*> _sender; Callback _handler; FailSkipPolicy _skipPolicy = FailSkipPolicy::Simple; }; explicit RequestBuilder(not_null<Sender*> sender) noexcept : _sender(sender) { } RequestBuilder(RequestBuilder &&other) = default; void setToDC(ShiftedDcId dcId) noexcept { _dcId = dcId; } void setCanWait(TimeMs ms) noexcept { _canWait = ms; } void setDoneHandler(RPCDoneHandlerPtr &&handler) noexcept { _done = std::move(handler); } void setFailHandler(FailPlainHandler &&handler) noexcept { _fail = std::move(handler); } void setFailHandler(FailRequestIdHandler &&handler) noexcept { _fail = std::move(handler); } void setFailSkipPolicy(FailSkipPolicy policy) noexcept { _failSkipPolicy = policy; } void setAfter(mtpRequestId requestId) noexcept { _afterRequestId = requestId; } ShiftedDcId takeDcId() const noexcept { return _dcId; } TimeMs takeCanWait() const noexcept { return _canWait; } RPCDoneHandlerPtr takeOnDone() noexcept { return std::move(_done); } RPCFailHandlerPtr takeOnFail() { if (auto handler = base::get_if<FailPlainHandler>(&_fail)) { return std::make_shared<FailHandler<FailPlainPolicy>>(_sender, std::move(*handler), _failSkipPolicy); } else if (auto handler = base::get_if<FailRequestIdHandler>(&_fail)) { return std::make_shared<FailHandler<FailRequestIdPolicy>>(_sender, std::move(*handler), _failSkipPolicy); } return RPCFailHandlerPtr(); } mtpRequestId takeAfter() const noexcept { return _afterRequestId; } not_null<Sender*> sender() const noexcept { return _sender; } void registerRequest(mtpRequestId requestId) { _sender->senderRequestRegister(requestId); } private: not_null<Sender*> _sender; ShiftedDcId _dcId = 0; TimeMs _canWait = 0; RPCDoneHandlerPtr _done; base::variant<FailPlainHandler, FailRequestIdHandler> _fail; FailSkipPolicy _failSkipPolicy = FailSkipPolicy::Simple; mtpRequestId _afterRequestId = 0; }; public: Sender() noexcept { } template <typename Request> class SpecificRequestBuilder : public RequestBuilder { private: friend class Sender; SpecificRequestBuilder(not_null<Sender*> sender, Request &&request) noexcept : RequestBuilder(sender), _request(std::move(request)) { } SpecificRequestBuilder(SpecificRequestBuilder &&other) = default; public: [[nodiscard]] SpecificRequestBuilder &toDC(ShiftedDcId dcId) noexcept { setToDC(dcId); return *this; } [[nodiscard]] SpecificRequestBuilder &afterDelay(TimeMs ms) noexcept { setCanWait(ms); return *this; } [[nodiscard]] SpecificRequestBuilder &done(base::lambda_once<void(const typename Request::ResponseType &result)> callback) { setDoneHandler(std::make_shared<DoneHandler<typename Request::ResponseType, DonePlainPolicy>>(sender(), std::move(callback))); return *this; } [[nodiscard]] SpecificRequestBuilder &done(base::lambda_once<void(const typename Request::ResponseType &result, mtpRequestId requestId)> callback) { setDoneHandler(std::make_shared<DoneHandler<typename Request::ResponseType, DoneRequestIdPolicy>>(sender(), std::move(callback))); return *this; } [[nodiscard]] SpecificRequestBuilder &fail(base::lambda_once<void(const RPCError &error)> callback) noexcept { setFailHandler(std::move(callback)); return *this; } [[nodiscard]] SpecificRequestBuilder &fail(base::lambda_once<void(const RPCError &error, mtpRequestId requestId)> callback) noexcept { setFailHandler(std::move(callback)); return *this; } [[nodiscard]] SpecificRequestBuilder &handleFloodErrors() noexcept { setFailSkipPolicy(FailSkipPolicy::HandleFlood); return *this; } [[nodiscard]] SpecificRequestBuilder &handleAllErrors() noexcept { setFailSkipPolicy(FailSkipPolicy::HandleAll); return *this; } [[nodiscard]] SpecificRequestBuilder &afterRequest(mtpRequestId requestId) noexcept { setAfter(requestId); return *this; } mtpRequestId send() { const auto id = MainInstance()->send( _request, takeOnDone(), takeOnFail(), takeDcId(), takeCanWait(), takeAfter()); registerRequest(id); return id; } private: Request _request; }; class SentRequestWrap { private: friend class Sender; SentRequestWrap(not_null<Sender*> sender, mtpRequestId requestId) : _sender(sender), _requestId(requestId) { } public: void cancel() { _sender->senderRequestCancel(_requestId); } private: not_null<Sender*> _sender; mtpRequestId _requestId = 0; }; template < typename Request, typename = std::enable_if_t<!std::is_reference_v<Request>>, typename = typename Request::Unboxed> [[nodiscard]] SpecificRequestBuilder<Request> request(Request &&request) noexcept; [[nodiscard]] SentRequestWrap request(mtpRequestId requestId) noexcept; [[nodiscard]] auto requestCanceller() noexcept { return [this](mtpRequestId requestId) { request(requestId).cancel(); }; } void requestSendDelayed() { MainInstance()->sendAnything(); } void requestCancellingDiscard() { for (auto &request : _requests) { request.handled(); } } not_null<Instance*> requestMTP() const { return MainInstance(); } private: class RequestWrap { public: RequestWrap( Instance *instance, mtpRequestId requestId) noexcept : _id(requestId) { } RequestWrap(const RequestWrap &other) = delete; RequestWrap &operator=(const RequestWrap &other) = delete; RequestWrap(RequestWrap &&other) : _id(base::take(other._id)) { } RequestWrap &operator=(RequestWrap &&other) { if (_id != other._id) { cancelRequest(); _id = base::take(other._id); } return *this; } mtpRequestId id() const noexcept { return _id; } void handled() const noexcept { } ~RequestWrap() { cancelRequest(); } private: void cancelRequest() { if (_id) { if (auto instance = MainInstance()) { instance->cancel(_id); } } } mtpRequestId _id = 0; }; struct RequestWrapComparator { using is_transparent = std::true_type; struct helper { mtpRequestId requestId = 0; helper() = default; helper(const helper &other) = default; helper(mtpRequestId requestId) noexcept : requestId(requestId) { } helper(const RequestWrap &request) noexcept : requestId(request.id()) { } bool operator<(helper other) const { return requestId < other.requestId; } }; bool operator()(const helper &&lhs, const helper &&rhs) const { return lhs < rhs; } }; template <typename Request> friend class SpecialRequestBuilder; friend class RequestBuilder; friend class RequestWrap; friend class SentRequestWrap; void senderRequestRegister(mtpRequestId requestId) { _requests.emplace(MainInstance(), requestId); } void senderRequestHandled(mtpRequestId requestId) { auto it = _requests.find(requestId); if (it != _requests.cend()) { it->handled(); _requests.erase(it); } } void senderRequestCancel(mtpRequestId requestId) { auto it = _requests.find(requestId); if (it != _requests.cend()) { _requests.erase(it); } } base::flat_set<RequestWrap, RequestWrapComparator> _requests; }; template <typename Request, typename, typename> Sender::SpecificRequestBuilder<Request> Sender::request(Request &&request) noexcept { return SpecificRequestBuilder<Request>(this, std::move(request)); } inline Sender::SentRequestWrap Sender::request(mtpRequestId requestId) noexcept { return SentRequestWrap(this, requestId); } } // namespace MTP �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/mtproto/session.cpp��������������������������������������������0000664�0000000�0000000�00000040630�13262451251�0023321�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "mtproto/session.h" #include "mtproto/connection.h" #include "mtproto/dcenter.h" #include "mtproto/auth_key.h" #include "core/crash_reports.h" namespace MTP { namespace internal { namespace { QString LogIds(const QVector<uint64> &ids) { if (!ids.size()) return "[]"; auto idsStr = QString("[%1").arg(*ids.cbegin()); for (const auto id : ids) { idsStr += QString(", %2").arg(id); } return idsStr + "]"; } } // namespace void SessionData::setKey(const AuthKeyPtr &key) { if (_authKey != key) { uint64 session = rand_value<uint64>(); _authKey = key; DEBUG_LOG(("MTP Info: new auth key set in SessionData, id %1, setting random server_session %2").arg(key ? key->keyId() : 0).arg(session)); QWriteLocker locker(&_lock); if (_session != session) { _session = session; _messagesSent = 0; } _layerInited = false; } } void SessionData::clear(Instance *instance) { auto clearCallbacks = std::vector<RPCCallbackClear>(); { QReadLocker locker1(haveSentMutex()), locker2(toResendMutex()), locker3(haveReceivedMutex()), locker4(wereAckedMutex()); auto receivedResponsesEnd = _receivedResponses.cend(); clearCallbacks.reserve(_haveSent.size() + _wereAcked.size()); for (auto i = _haveSent.cbegin(), e = _haveSent.cend(); i != e; ++i) { auto requestId = i.value()->requestId; if (!_receivedResponses.contains(requestId)) { clearCallbacks.push_back(requestId); } } for (auto i = _toResend.cbegin(), e = _toResend.cend(); i != e; ++i) { auto requestId = i.value(); if (!_receivedResponses.contains(requestId)) { clearCallbacks.push_back(requestId); } } for (auto i = _wereAcked.cbegin(), e = _wereAcked.cend(); i != e; ++i) { auto requestId = i.value(); if (!_receivedResponses.contains(requestId)) { clearCallbacks.push_back(requestId); } } } { QWriteLocker locker(haveSentMutex()); _haveSent.clear(); } { QWriteLocker locker(toResendMutex()); _toResend.clear(); } { QWriteLocker locker(wereAckedMutex()); _wereAcked.clear(); } { QWriteLocker locker(receivedIdsMutex()); _receivedIds.clear(); } instance->clearCallbacksDelayed(std::move(clearCallbacks)); } Session::Session(not_null<Instance*> instance, ShiftedDcId shiftedDcId) : QObject() , _instance(instance) , data(this) , dcWithShift(shiftedDcId) { connect(&timeouter, SIGNAL(timeout()), this, SLOT(checkRequestsByTimer())); timeouter.start(1000); data.setSystemLangCode(instance->systemLangCode()); data.setCloudLangCode(instance->cloudLangCode()); connect(&sender, SIGNAL(timeout()), this, SLOT(needToResumeAndSend())); } void Session::start() { createDcData(); _connection = std::make_unique<Connection>(_instance); _connection->start(&data, dcWithShift); if (_instance->isKeysDestroyer()) { _instance->scheduleKeyDestroy(dcWithShift); } } void Session::createDcData() { if (dc) { return; } dc = _instance->getDcById(dcWithShift); if (auto lock = ReadLockerAttempt(keyMutex())) { data.setKey(dc->getKey()); if (dc->connectionInited()) { data.setLayerWasInited(true); } } connect(dc.get(), SIGNAL(authKeyCreated()), this, SLOT(authKeyCreatedForDC()), Qt::QueuedConnection); connect(dc.get(), SIGNAL(layerWasInited(bool)), this, SLOT(layerWasInitedForDC(bool)), Qt::QueuedConnection); } void Session::registerRequest(mtpRequestId requestId, ShiftedDcId dcWithShift) { return _instance->registerRequest(requestId, dcWithShift); } mtpRequestId Session::storeRequest( mtpRequest &request, RPCResponseHandler &&callbacks) { return _instance->storeRequest(request, std::move(callbacks)); } mtpRequest Session::getRequest(mtpRequestId requestId) { return _instance->getRequest(requestId); } bool Session::rpcErrorOccured(mtpRequestId requestId, const RPCFailHandlerPtr &onFail, const RPCError &err) { // return true if need to clean request data return _instance->rpcErrorOccured(requestId, onFail, err); } void Session::restart() { if (_killed) { DEBUG_LOG(("Session Error: can't restart a killed session")); return; } data.setSystemLangCode(_instance->systemLangCode()); data.setCloudLangCode(_instance->cloudLangCode()); emit needToRestart(); } void Session::stop() { if (_killed) { DEBUG_LOG(("Session Error: can't kill a killed session")); return; } DEBUG_LOG(("Session Info: stopping session dcWithShift %1").arg(dcWithShift)); if (_connection) { _connection->kill(); _instance->queueQuittingConnection(std::move(_connection)); } } void Session::kill() { stop(); _killed = true; DEBUG_LOG(("Session Info: marked session dcWithShift %1 as killed").arg(dcWithShift)); } void Session::unpaused() { if (_needToReceive) { _needToReceive = false; QTimer::singleShot(0, this, SLOT(tryToReceive())); } } void Session::sendAnything(qint64 msCanWait) { if (_killed) { DEBUG_LOG(("Session Error: can't send anything in a killed session")); return; } auto ms = getms(true); if (msSendCall) { if (ms > msSendCall + msWait) { msWait = 0; } else { msWait = (msSendCall + msWait) - ms; if (msWait > msCanWait) { msWait = msCanWait; } } } else { msWait = msCanWait; } if (msWait) { DEBUG_LOG(("MTP Info: dcWithShift %1 can wait for %2ms from current %3").arg(dcWithShift).arg(msWait).arg(msSendCall)); msSendCall = ms; sender.start(msWait); } else { DEBUG_LOG(("MTP Info: dcWithShift %1 stopped send timer, can wait for %2ms from current %3").arg(dcWithShift).arg(msWait).arg(msSendCall)); sender.stop(); msSendCall = 0; needToResumeAndSend(); } } void Session::needToResumeAndSend() { if (_killed) { DEBUG_LOG(("Session Info: can't resume a killed session")); return; } if (!_connection) { DEBUG_LOG(("Session Info: resuming session dcWithShift %1").arg(dcWithShift)); createDcData(); _connection = std::make_unique<Connection>(_instance); _connection->start(&data, dcWithShift); } if (_ping) { _ping = false; emit needToPing(); } else { emit needToSend(); } } void Session::sendPong(quint64 msgId, quint64 pingId) { send(mtpRequestData::serialize(MTPPong(MTP_pong( MTP_long(msgId), MTP_long(pingId))))); } void Session::sendMsgsStateInfo(quint64 msgId, QByteArray data) { auto info = std::string(); if (!data.isEmpty()) { info.resize(data.size()); auto src = gsl::as_bytes(gsl::make_span(data)); // auto dst = gsl::as_writeable_bytes(gsl::make_span(info)); auto dst = gsl::as_writeable_bytes(gsl::make_span(&info[0], info.size())); base::copy_bytes(dst, src); } send(mtpRequestData::serialize(MTPMsgsStateInfo( MTP_msgs_state_info(MTP_long(msgId), MTP_string(std::move(info)))))); } void Session::checkRequestsByTimer() { QVector<mtpMsgId> resendingIds; QVector<mtpMsgId> removingIds; // remove very old (10 minutes) containers and resend requests QVector<mtpMsgId> stateRequestIds; { QReadLocker locker(data.haveSentMutex()); mtpRequestMap &haveSent(data.haveSentMap()); uint32 haveSentCount(haveSent.size()); auto ms = getms(true); for (mtpRequestMap::iterator i = haveSent.begin(), e = haveSent.end(); i != e; ++i) { mtpRequest &req(i.value()); if (req->msDate > 0) { if (req->msDate + MTPCheckResendTimeout < ms) { // need to resend or check state if (mtpRequestData::messageSize(req) < MTPResendThreshold) { // resend resendingIds.reserve(haveSentCount); resendingIds.push_back(i.key()); } else { req->msDate = ms; stateRequestIds.reserve(haveSentCount); stateRequestIds.push_back(i.key()); } } } else if (unixtime() > (int32)(i.key() >> 32) + MTPContainerLives) { removingIds.reserve(haveSentCount); removingIds.push_back(i.key()); } } } if (stateRequestIds.size()) { DEBUG_LOG(("MTP Info: requesting state of msgs: %1").arg(LogIds(stateRequestIds))); { QWriteLocker locker(data.stateRequestMutex()); for (uint32 i = 0, l = stateRequestIds.size(); i < l; ++i) { data.stateRequestMap().insert(stateRequestIds[i], true); } } sendAnything(MTPCheckResendWaiting); } if (!resendingIds.isEmpty()) { for (uint32 i = 0, l = resendingIds.size(); i < l; ++i) { DEBUG_LOG(("MTP Info: resending request %1").arg(resendingIds[i])); resend(resendingIds[i], MTPCheckResendWaiting); } } if (!removingIds.isEmpty()) { auto clearCallbacks = std::vector<RPCCallbackClear>(); { QWriteLocker locker(data.haveSentMutex()); auto &haveSent = data.haveSentMap(); for (uint32 i = 0, l = removingIds.size(); i < l; ++i) { auto j = haveSent.find(removingIds[i]); if (j != haveSent.cend()) { if (j.value()->requestId) { clearCallbacks.push_back(j.value()->requestId); } haveSent.erase(j); } } } _instance->clearCallbacksDelayed(std::move(clearCallbacks)); } } void Session::onConnectionStateChange(qint32 newState) { _instance->onStateChange(dcWithShift, newState); } void Session::onResetDone() { _instance->onSessionReset(dcWithShift); } void Session::cancel(mtpRequestId requestId, mtpMsgId msgId) { if (requestId) { QWriteLocker locker(data.toSendMutex()); data.toSendMap().remove(requestId); } if (msgId) { QWriteLocker locker(data.haveSentMutex()); data.haveSentMap().remove(msgId); } } void Session::ping() { _ping = true; sendAnything(0); } int32 Session::requestState(mtpRequestId requestId) const { int32 result = MTP::RequestSent; bool connected = false; if (_connection) { int32 s = _connection->state(); if (s == ConnectedState) { connected = true; } else if (s == ConnectingState || s == DisconnectedState) { if (result < 0 || result == MTP::RequestSent) { result = MTP::RequestConnecting; } } else if (s < 0) { if ((result < 0 && s > result) || result == MTP::RequestSent) { result = s; } } } if (!connected) { return result; } if (!requestId) return MTP::RequestSent; QWriteLocker locker(data.toSendMutex()); const mtpPreRequestMap &toSend(data.toSendMap()); mtpPreRequestMap::const_iterator i = toSend.constFind(requestId); if (i != toSend.cend()) { return MTP::RequestSending; } else { return MTP::RequestSent; } } int32 Session::getState() const { int32 result = -86400000; if (_connection) { int32 s = _connection->state(); if (s == ConnectedState) { return s; } else if (s == ConnectingState || s == DisconnectedState) { if (result < 0) { return s; } } else if (s < 0) { if (result < 0 && s > result) { result = s; } } } if (result == -86400000) { result = DisconnectedState; } return result; } QString Session::transport() const { return _connection ? _connection->transport() : QString(); } mtpRequestId Session::resend(quint64 msgId, qint64 msCanWait, bool forceContainer, bool sendMsgStateInfo) { mtpRequest request; { QWriteLocker locker(data.haveSentMutex()); mtpRequestMap &haveSent(data.haveSentMap()); auto i = haveSent.find(msgId); if (i == haveSent.end()) { if (sendMsgStateInfo) { char cantResend[2] = {1, 0}; DEBUG_LOG(("Message Info: cant resend %1, request not found").arg(msgId)); auto info = std::string(cantResend, cantResend + 1); return send(mtpRequestData::serialize(MTPMsgsStateInfo( MTP_msgs_state_info( MTP_long(msgId), MTP_string(std::move(info)))))); } return 0; } request = i.value(); haveSent.erase(i); } if (mtpRequestData::isSentContainer(request)) { // for container just resend all messages we can DEBUG_LOG(("Message Info: resending container from haveSent, msgId %1").arg(msgId)); const mtpMsgId *ids = (const mtpMsgId *)(request->constData() + 8); for (uint32 i = 0, l = (request->size() - 8) >> 1; i < l; ++i) { resend(ids[i], 10, true); } return 0xFFFFFFFF; } else if (!mtpRequestData::isStateRequest(request)) { request->msDate = forceContainer ? 0 : getms(true); sendPrepared(request, msCanWait, false); { QWriteLocker locker(data.toResendMutex()); data.toResendMap().insert(msgId, request->requestId); } return request->requestId; } else { return 0; } } void Session::resendMany(QVector<quint64> msgIds, qint64 msCanWait, bool forceContainer, bool sendMsgStateInfo) { for (int32 i = 0, l = msgIds.size(); i < l; ++i) { resend(msgIds.at(i), msCanWait, forceContainer, sendMsgStateInfo); } } void Session::resendAll() { QVector<mtpMsgId> toResend; { QReadLocker locker(data.haveSentMutex()); const mtpRequestMap &haveSent(data.haveSentMap()); toResend.reserve(haveSent.size()); for (mtpRequestMap::const_iterator i = haveSent.cbegin(), e = haveSent.cend(); i != e; ++i) { if (i.value()->requestId) toResend.push_back(i.key()); } } for (uint32 i = 0, l = toResend.size(); i < l; ++i) { resend(toResend[i], 10, true); } } mtpRequestId Session::send( mtpRequest &&request, RPCResponseHandler &&callbacks, TimeMs msCanWait, bool needsLayer, bool toMainDC, mtpRequestId after) { DEBUG_LOG(("MTP Info: adding request to toSendMap, msCanWait %1").arg(msCanWait)); request->msDate = getms(true); // > 0 - can send without container request->needsLayer = needsLayer; if (after) { request->after = getRequest(after); } const auto requestId = storeRequest(request, std::move(callbacks)); Assert(requestId != 0); const auto signedDcId = toMainDC ? -getDcWithShift() : getDcWithShift(); sendPrepared(request, msCanWait); registerRequest(requestId, signedDcId); return requestId; } void Session::sendPrepared(const mtpRequest &request, TimeMs msCanWait, bool newRequest) { // returns true, if emit of needToSend() is needed { QWriteLocker locker(data.toSendMutex()); data.toSendMap().insert(request->requestId, request); if (newRequest) { *(mtpMsgId*)(request->data() + 4) = 0; *(request->data() + 6) = 0; } } DEBUG_LOG(("MTP Info: added, requestId %1").arg(request->requestId)); sendAnything(msCanWait); } QReadWriteLock *Session::keyMutex() const { return dc->keyMutex(); } void Session::authKeyCreatedForDC() { DEBUG_LOG(("AuthKey Info: Session::authKeyCreatedForDC slot, emitting authKeyCreated(), dcWithShift %1").arg(dcWithShift)); data.setKey(dc->getKey()); emit authKeyCreated(); } void Session::notifyKeyCreated(AuthKeyPtr &&key) { DEBUG_LOG(("AuthKey Info: Session::keyCreated(), setting, dcWithShift %1").arg(dcWithShift)); dc->setKey(std::move(key)); } void Session::layerWasInitedForDC(bool wasInited) { DEBUG_LOG(("MTP Info: Session::layerWasInitedForDC slot, dcWithShift %1").arg(dcWithShift)); data.setLayerWasInited(wasInited); } void Session::notifyLayerInited(bool wasInited) { DEBUG_LOG(("MTP Info: emitting MTProtoDC::layerWasInited(%1), dcWithShift %2").arg(Logs::b(wasInited)).arg(dcWithShift)); dc->setConnectionInited(wasInited); emit dc->layerWasInited(wasInited); } void Session::destroyKey() { if (!dc) return; if (data.getKey()) { DEBUG_LOG(("MTP Info: destroying auth_key for dcWithShift %1").arg(dcWithShift)); if (data.getKey() == dc->getKey()) { dc->destroyKey(); } data.setKey(AuthKeyPtr()); } } int32 Session::getDcWithShift() const { return dcWithShift; } void Session::tryToReceive() { if (_killed) { DEBUG_LOG(("Session Error: can't receive in a killed session")); return; } if (paused()) { _needToReceive = true; return; } while (true) { auto requestId = mtpRequestId(0); auto isUpdate = false; auto message = SerializedMessage(); { QWriteLocker locker(data.haveReceivedMutex()); auto &responses = data.haveReceivedResponses(); auto response = responses.begin(); if (response == responses.cend()) { auto &updates = data.haveReceivedUpdates(); auto update = updates.begin(); if (update == updates.cend()) { return; } else { message = std::move(*update); isUpdate = true; updates.pop_front(); } } else { requestId = response.key(); message = std::move(response.value()); responses.erase(response); } } if (isUpdate) { if (dcWithShift == bareDcId(dcWithShift)) { // call globalCallback only in main session _instance->globalCallback(message.constData(), message.constData() + message.size()); } } else { _instance->execCallback(requestId, message.constData(), message.constData() + message.size()); } } } Session::~Session() { Assert(_connection == nullptr); } MTPrpcError rpcClientError(const QString &type, const QString &description) { return MTP_rpc_error(MTP_int(0), MTP_string(("CLIENT_" + type + (description.length() ? (": " + description) : "")).toUtf8().constData())); } } // namespace internal } // namespace MTP ��������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/mtproto/session.h����������������������������������������������0000664�0000000�0000000�00000022475�13262451251�0022775�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "core/single_timer.h" #include "mtproto/rpc_sender.h" namespace MTP { class Instance; class AuthKey; using AuthKeyPtr = std::shared_ptr<AuthKey>; namespace internal { class Dcenter; class Connection; class ReceivedMsgIds { public: bool registerMsgId(mtpMsgId msgId, bool needAck) { auto i = _idsNeedAck.constFind(msgId); if (i == _idsNeedAck.cend()) { if (_idsNeedAck.size() < MTPIdsBufferSize || msgId > min()) { _idsNeedAck.insert(msgId, needAck); return true; } MTP_LOG(-1, ("No need to handle - %1 < min = %2").arg(msgId).arg(min())); } else { MTP_LOG(-1, ("No need to handle - %1 already is in map").arg(msgId)); } return false; } mtpMsgId min() const { return _idsNeedAck.isEmpty() ? 0 : _idsNeedAck.cbegin().key(); } mtpMsgId max() const { auto end = _idsNeedAck.cend(); return _idsNeedAck.isEmpty() ? 0 : (--end).key(); } void shrink() { auto size = _idsNeedAck.size(); while (size-- > MTPIdsBufferSize) { _idsNeedAck.erase(_idsNeedAck.begin()); } } enum class State { NotFound, NeedsAck, NoAckNeeded, }; State lookup(mtpMsgId msgId) const { auto i = _idsNeedAck.constFind(msgId); if (i == _idsNeedAck.cend()) { return State::NotFound; } return i.value() ? State::NeedsAck : State::NoAckNeeded; } void clear() { _idsNeedAck.clear(); } private: QMap<mtpMsgId, bool> _idsNeedAck; }; using SerializedMessage = mtpBuffer; inline bool ResponseNeedsAck(const SerializedMessage &response) { if (response.size() < 8) { return false; } auto seqNo = *(uint32*)(response.constData() + 6); return (seqNo & 0x01) ? true : false; } class Session; class SessionData { public: SessionData(not_null<Session*> creator) : _owner(creator) { } void setSession(uint64 session) { DEBUG_LOG(("MTP Info: setting server_session: %1").arg(session)); QWriteLocker locker(&_lock); if (_session != session) { _session = session; _messagesSent = 0; } } uint64 getSession() const { QReadLocker locker(&_lock); return _session; } bool layerWasInited() const { QReadLocker locker(&_lock); return _layerInited; } void setLayerWasInited(bool was) { QWriteLocker locker(&_lock); _layerInited = was; } QString systemLangCode() const { QReadLocker locker(&_lock); return _systemLangCode; } void setSystemLangCode(const QString &code) { QWriteLocker locker(&_lock); _systemLangCode = code; } QString cloudLangCode() const { QReadLocker locker(&_lock); return _cloudLangCode; } void setCloudLangCode(const QString &code) { QWriteLocker locker(&_lock); _cloudLangCode = code; } void setSalt(uint64 salt) { QWriteLocker locker(&_lock); _salt = salt; } uint64 getSalt() const { QReadLocker locker(&_lock); return _salt; } const AuthKeyPtr &getKey() const { return _authKey; } void setKey(const AuthKeyPtr &key); bool isCheckedKey() const { QReadLocker locker(&_lock); return _keyChecked; } void setCheckedKey(bool checked) { QWriteLocker locker(&_lock); _keyChecked = checked; } not_null<QReadWriteLock*> keyMutex() const; not_null<QReadWriteLock*> toSendMutex() const { return &_toSendLock; } not_null<QReadWriteLock*> haveSentMutex() const { return &_haveSentLock; } not_null<QReadWriteLock*> toResendMutex() const { return &_toResendLock; } not_null<QReadWriteLock*> wereAckedMutex() const { return &_wereAckedLock; } not_null<QReadWriteLock*> receivedIdsMutex() const { return &_receivedIdsLock; } not_null<QReadWriteLock*> haveReceivedMutex() const { return &_haveReceivedLock; } not_null<QReadWriteLock*> stateRequestMutex() const { return &_stateRequestLock; } mtpPreRequestMap &toSendMap() { return _toSend; } const mtpPreRequestMap &toSendMap() const { return _toSend; } mtpRequestMap &haveSentMap() { return _haveSent; } const mtpRequestMap &haveSentMap() const { return _haveSent; } mtpRequestIdsMap &toResendMap() { // msgId -> requestId, on which toSend: requestId -> request for resended requests return _toResend; } const mtpRequestIdsMap &toResendMap() const { return _toResend; } ReceivedMsgIds &receivedIdsSet() { return _receivedIds; } const ReceivedMsgIds &receivedIdsSet() const { return _receivedIds; } mtpRequestIdsMap &wereAckedMap() { return _wereAcked; } const mtpRequestIdsMap &wereAckedMap() const { return _wereAcked; } QMap<mtpRequestId, SerializedMessage> &haveReceivedResponses() { return _receivedResponses; } const QMap<mtpRequestId, SerializedMessage> &haveReceivedResponses() const { return _receivedResponses; } QList<SerializedMessage> &haveReceivedUpdates() { return _receivedUpdates; } const QList<SerializedMessage> &haveReceivedUpdates() const { return _receivedUpdates; } mtpMsgIdsSet &stateRequestMap() { return _stateRequest; } const mtpMsgIdsSet &stateRequestMap() const { return _stateRequest; } not_null<Session*> owner() { return _owner; } not_null<const Session*> owner() const { return _owner; } uint32 nextRequestSeqNumber(bool needAck = true) { QWriteLocker locker(&_lock); auto result = _messagesSent; _messagesSent += (needAck ? 1 : 0); return result * 2 + (needAck ? 1 : 0); } void clear(Instance *instance); private: uint64 _session = 0; uint64 _salt = 0; uint32 _messagesSent = 0; not_null<Session*> _owner; AuthKeyPtr _authKey; bool _keyChecked = false; bool _layerInited = false; QString _systemLangCode; QString _cloudLangCode; mtpPreRequestMap _toSend; // map of request_id -> request, that is waiting to be sent mtpRequestMap _haveSent; // map of msg_id -> request, that was sent, msDate = 0 for msgs_state_req (no resend / state req), msDate = 0, seqNo = 0 for containers mtpRequestIdsMap _toResend; // map of msg_id -> request_id, that request_id -> request lies in toSend and is waiting to be resent ReceivedMsgIds _receivedIds; // set of received msg_id's, for checking new msg_ids mtpRequestIdsMap _wereAcked; // map of msg_id -> request_id, this msg_ids already were acked or do not need ack mtpMsgIdsSet _stateRequest; // set of msg_id's, whose state should be requested QMap<mtpRequestId, SerializedMessage> _receivedResponses; // map of request_id -> response that should be processed in the main thread QList<SerializedMessage> _receivedUpdates; // list of updates that should be processed in the main thread // mutexes mutable QReadWriteLock _lock; mutable QReadWriteLock _toSendLock; mutable QReadWriteLock _haveSentLock; mutable QReadWriteLock _toResendLock; mutable QReadWriteLock _receivedIdsLock; mutable QReadWriteLock _wereAckedLock; mutable QReadWriteLock _haveReceivedLock; mutable QReadWriteLock _stateRequestLock; }; class Session : public QObject { Q_OBJECT public: Session(not_null<Instance*> instance, ShiftedDcId shiftedDcId); void start(); void restart(); void stop(); void kill(); void unpaused(); ShiftedDcId getDcWithShift() const; QReadWriteLock *keyMutex() const; void notifyKeyCreated(AuthKeyPtr &&key); void destroyKey(); void notifyLayerInited(bool wasInited); void ping(); void cancel(mtpRequestId requestId, mtpMsgId msgId); int32 requestState(mtpRequestId requestId) const; int32 getState() const; QString transport() const; mtpRequestId send( mtpRequest &&request, RPCResponseHandler &&callbacks = {}, TimeMs msCanWait = 0, bool needsLayer = false, bool toMainDC = false, mtpRequestId after = 0); // Nulls msgId and seqNo in request, if newRequest = true. void sendPrepared( const mtpRequest &request, TimeMs msCanWait = 0, bool newRequest = true); ~Session(); signals: void authKeyCreated(); void needToSend(); void needToPing(); void needToRestart(); public slots: void needToResumeAndSend(); mtpRequestId resend(quint64 msgId, qint64 msCanWait = 0, bool forceContainer = false, bool sendMsgStateInfo = false); void resendMany(QVector<quint64> msgIds, qint64 msCanWait, bool forceContainer, bool sendMsgStateInfo); void resendAll(); // after connection restart void authKeyCreatedForDC(); void layerWasInitedForDC(bool wasInited); void tryToReceive(); void checkRequestsByTimer(); void onConnectionStateChange(qint32 newState); void onResetDone(); void sendAnything(qint64 msCanWait = 0); void sendPong(quint64 msgId, quint64 pingId); void sendMsgsStateInfo(quint64 msgId, QByteArray data); private: void createDcData(); void registerRequest(mtpRequestId requestId, ShiftedDcId dcWithShift); mtpRequestId storeRequest( mtpRequest &request, RPCResponseHandler &&callbacks); mtpRequest getRequest(mtpRequestId requestId); bool rpcErrorOccured(mtpRequestId requestId, const RPCFailHandlerPtr &onFail, const RPCError &err); not_null<Instance*> _instance; std::unique_ptr<Connection> _connection; bool _killed = false; bool _needToReceive = false; SessionData data; ShiftedDcId dcWithShift = 0; std::shared_ptr<Dcenter> dc; TimeMs msSendCall = 0; TimeMs msWait = 0; bool _ping = false; QTimer timeouter; SingleTimer sender; }; inline not_null<QReadWriteLock*> SessionData::keyMutex() const { return _owner->keyMutex(); } MTPrpcError rpcClientError(const QString &type, const QString &description = QString()); } // namespace internal } // namespace MTP ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/mtproto/special_config_request.cpp�����������������������������0000664�0000000�0000000�00000022013�13262451251�0026346�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "mtproto/special_config_request.h" #include "mtproto/rsa_public_key.h" #include "mtproto/dc_options.h" #include "mtproto/auth_key.h" #include "base/openssl_help.h" #include <openssl/aes.h> namespace MTP { namespace { constexpr auto kPublicKey = str_const("\ -----BEGIN RSA PUBLIC KEY-----\n\ MIIBCgKCAQEAyr+18Rex2ohtVy8sroGPBwXD3DOoKCSpjDqYoXgCqB7ioln4eDCF\n\ fOBUlfXUEvM/fnKCpF46VkAftlb4VuPDeQSS/ZxZYEGqHaywlroVnXHIjgqoxiAd\n\ 192xRGreuXIaUKmkwlM9JID9WS2jUsTpzQ91L8MEPLJ/4zrBwZua8W5fECwCCh2c\n\ 9G5IzzBm+otMS/YKwmR1olzRCyEkyAEjXWqBI9Ftv5eG8m0VkBzOG655WIYdyV0H\n\ fDK/NWcvGqa0w/nriMD6mDjKOryamw0OP9QuYgMN0C9xMW9y8SmP4h92OAWodTYg\n\ Y1hZCxdv6cs5UnW9+PWvS+WIbkh+GaWYxwIDAQAB\n\ -----END RSA PUBLIC KEY-----\ "); } // namespace SpecialConfigRequest::SpecialConfigRequest( base::lambda<void( DcId dcId, const std::string &ip, int port)> callback) : _callback(std::move(callback)) { App::setProxySettings(_manager); performApp1Request(); performApp2Request(); performDnsRequest(); } void SpecialConfigRequest::performApp1Request() { auto appUrl = QUrl(); appUrl.setScheme(qsl("https")); appUrl.setHost(qsl("google.com")); if (cTestMode()) { appUrl.setPath(qsl("/test/")); } auto appRequest = QNetworkRequest(appUrl); appRequest.setRawHeader("Host", "dns-telegram.appspot.com"); _app1Reply.reset(_manager.get(appRequest)); connect(_app1Reply.get(), &QNetworkReply::finished, this, [=] { app1Finished(); }); } void SpecialConfigRequest::performApp2Request() { auto appUrl = QUrl(); appUrl.setScheme(qsl("https")); appUrl.setHost(qsl("software-download.microsoft.com")); appUrl.setPath(cTestMode() ? qsl("/test/config.txt") : qsl("/prod/config.txt")); auto appRequest = QNetworkRequest(appUrl); appRequest.setRawHeader("Host", "tcdnb.azureedge.net"); _app2Reply.reset(_manager.get(appRequest)); connect(_app2Reply.get(), &QNetworkReply::finished, this, [=] { app2Finished(); }); } void SpecialConfigRequest::performDnsRequest() { auto dnsUrl = QUrl(); dnsUrl.setScheme(qsl("https")); dnsUrl.setHost(qsl("google.com")); dnsUrl.setPath(qsl("/resolve")); dnsUrl.setQuery( qsl("name=%1.stel.com&type=16").arg( cTestMode() ? qsl("tap") : qsl("ap"))); auto dnsRequest = QNetworkRequest(QUrl(dnsUrl)); dnsRequest.setRawHeader("Host", "dns.google.com"); _dnsReply.reset(_manager.get(dnsRequest)); connect(_dnsReply.get(), &QNetworkReply::finished, this, [this] { dnsFinished(); }); } void SpecialConfigRequest::app1Finished() { if (!_app1Reply) { return; } auto result = _app1Reply->readAll(); _app1Reply.release()->deleteLater(); handleResponse(result); } void SpecialConfigRequest::app2Finished() { if (!_app2Reply) { return; } auto result = _app2Reply->readAll(); _app2Reply.release()->deleteLater(); handleResponse(result); } void SpecialConfigRequest::dnsFinished() { if (!_dnsReply) { return; } if (_dnsReply->error() != QNetworkReply::NoError) { LOG(("Config Error: Failed to get dns response JSON, error: %1 (%2)").arg(_dnsReply->errorString()).arg(_dnsReply->error())); } auto result = _dnsReply->readAll(); _dnsReply.release()->deleteLater(); // Read and store to "entries" map all the data bytes from this response: // { .., "Answer": [ { .., "data": "bytes1", .. }, { .., "data": "bytes2", .. } ], .. } auto entries = QMap<int, QString>(); auto error = QJsonParseError { 0, QJsonParseError::NoError }; auto document = QJsonDocument::fromJson(result, &error); if (error.error != QJsonParseError::NoError) { LOG(("Config Error: Failed to parse dns response JSON, error: %1").arg(error.errorString())); } else if (!document.isObject()) { LOG(("Config Error: Not an object received in dns response JSON.")); } else { auto response = document.object(); auto answerIt = response.find(qsl("Answer")); if (answerIt == response.constEnd()) { LOG(("Config Error: Could not find Answer in dns response JSON.")); } else if (!(*answerIt).isArray()) { LOG(("Config Error: Not an array received in Answer in dns response JSON.")); } else { for (auto elem : (*answerIt).toArray()) { if (!elem.isObject()) { LOG(("Config Error: Not an object found in Answer array in dns response JSON.")); } else { auto object = elem.toObject(); auto dataIt = object.find(qsl("data")); if (dataIt == object.constEnd()) { LOG(("Config Error: Could not find data in Answer array entry in dns response JSON.")); } else if (!(*dataIt).isString()) { LOG(("Config Error: Not a string data found in Answer array entry in dns response JSON.")); } else { auto data = (*dataIt).toString(); entries.insertMulti(INT_MAX - data.size(), data); } } } } } auto text = QStringList(entries.values()).join(QString()); handleResponse(text.toLatin1()); } bool SpecialConfigRequest::decryptSimpleConfig(const QByteArray &bytes) { auto cleanBytes = bytes; auto removeFrom = std::remove_if(cleanBytes.begin(), cleanBytes.end(), [](char ch) { auto isGoodBase64 = (ch == '+') || (ch == '=') || (ch == '/') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9'); return !isGoodBase64; }); if (removeFrom != cleanBytes.end()) { cleanBytes.remove(removeFrom - cleanBytes.begin(), cleanBytes.end() - removeFrom); } constexpr auto kGoodSizeBase64 = 344; if (cleanBytes.size() != kGoodSizeBase64) { LOG(("Config Error: Bad data size %1 required %2").arg(cleanBytes.size()).arg(kGoodSizeBase64)); return false; } constexpr auto kGoodSizeData = 256; auto decodedBytes = QByteArray::fromBase64(cleanBytes, QByteArray::Base64Encoding); if (decodedBytes.size() != kGoodSizeData) { LOG(("Config Error: Bad data size %1 required %2").arg(decodedBytes.size()).arg(kGoodSizeData)); return false; } auto publicKey = internal::RSAPublicKey(gsl::as_bytes(gsl::make_span( kPublicKey.c_str(), kPublicKey.size()))); auto decrypted = publicKey.decrypt(gsl::as_bytes(gsl::make_span(decodedBytes))); auto decryptedBytes = gsl::make_span(decrypted); constexpr auto kAesKeySize = CTRState::KeySize; constexpr auto kAesIvecSize = CTRState::IvecSize; auto aesEncryptedBytes = decryptedBytes.subspan(kAesKeySize); base::byte_array<kAesIvecSize> aesivec; base::copy_bytes(aesivec, decryptedBytes.subspan(CTRState::KeySize - CTRState::IvecSize, CTRState::IvecSize)); AES_KEY aeskey; AES_set_decrypt_key(reinterpret_cast<const unsigned char*>(decryptedBytes.data()), kAesKeySize * CHAR_BIT, &aeskey); AES_cbc_encrypt(reinterpret_cast<const unsigned char*>(aesEncryptedBytes.data()), reinterpret_cast<unsigned char*>(aesEncryptedBytes.data()), aesEncryptedBytes.size(), &aeskey, reinterpret_cast<unsigned char*>(aesivec.data()), AES_DECRYPT); constexpr auto kDigestSize = 16; auto dataSize = aesEncryptedBytes.size() - kDigestSize; auto data = aesEncryptedBytes.subspan(0, dataSize); auto hash = openssl::Sha256(data); if (base::compare_bytes(gsl::make_span(hash).subspan(0, kDigestSize), aesEncryptedBytes.subspan(dataSize)) != 0) { LOG(("Config Error: Bad digest.")); return false; } mtpBuffer buffer; buffer.resize(data.size() / sizeof(mtpPrime)); base::copy_bytes(gsl::as_writeable_bytes(gsl::make_span(buffer)), data); auto from = &*buffer.cbegin(); auto end = from + buffer.size(); auto realLength = *from++; if (realLength <= 0 || realLength > dataSize || (realLength & 0x03)) { LOG(("Config Error: Bad length %1.").arg(realLength)); return false; } try { _simpleConfig.read(from, end); } catch (...) { LOG(("Config Error: Could not read configSimple.")); return false; } if ((end - from) * sizeof(mtpPrime) != (dataSize - realLength)) { LOG(("Config Error: Bad read length %1, should be %2.").arg((end - from) * sizeof(mtpPrime)).arg(dataSize - realLength)); return false; } return true; } void SpecialConfigRequest::handleResponse(const QByteArray &bytes) { if (!decryptSimpleConfig(bytes)) { return; } Assert(_simpleConfig.type() == mtpc_help_configSimple); auto &config = _simpleConfig.c_help_configSimple(); auto now = unixtime(); if (now < config.vdate.v || now > config.vexpires.v) { LOG(("Config Error: Bad date frame for simple config: %1-%2, our time is %3.").arg(config.vdate.v).arg(config.vexpires.v).arg(now)); return; } if (config.vip_port_list.v.empty()) { LOG(("Config Error: Empty simple config received.")); return; } for (auto &entry : config.vip_port_list.v) { Assert(entry.type() == mtpc_ipPort); auto &ipPort = entry.c_ipPort(); auto ip = *reinterpret_cast<const uint32*>(&ipPort.vipv4.v); auto ipString = qsl("%1.%2.%3.%4").arg((ip >> 24) & 0xFF).arg((ip >> 16) & 0xFF).arg((ip >> 8) & 0xFF).arg(ip & 0xFF); _callback(config.vdc_id.v, ipString.toStdString(), ipPort.vport.v); } } SpecialConfigRequest::~SpecialConfigRequest() { if (_app1Reply) { _app1Reply->abort(); } if (_app2Reply) { _app2Reply->abort(); } if (_dnsReply) { _dnsReply->abort(); } } } // namespace MTP ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/mtproto/special_config_request.h�������������������������������0000664�0000000�0000000�00000002132�13262451251�0026013�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace MTP { class SpecialConfigRequest : public QObject { public: SpecialConfigRequest( base::lambda<void( DcId dcId, const std::string &ip, int port)> callback); ~SpecialConfigRequest(); private: void performApp1Request(); void performApp2Request(); void performDnsRequest(); void app1Finished(); void app2Finished(); void dnsFinished(); void handleResponse(const QByteArray &bytes); bool decryptSimpleConfig(const QByteArray &bytes); base::lambda<void( DcId dcId, const std::string &ip, int port)> _callback; MTPhelp_ConfigSimple _simpleConfig; QNetworkAccessManager _manager; std::unique_ptr<QNetworkReply> _app1Reply; std::unique_ptr<QNetworkReply> _app2Reply; std::unique_ptr<QNetworkReply> _dnsReply; std::unique_ptr<DcOptions> _localOptions; std::unique_ptr<Instance> _localInstance; }; } // namespace MTP ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/mtproto/type_utils.cpp�����������������������������������������0000664�0000000�0000000�00000001176�13262451251�0024041�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "mtproto/type_utils.h" const MTPReplyMarkup MTPnullMarkup = MTP_replyKeyboardMarkup( MTP_flags(0), MTP_vector<MTPKeyboardButtonRow>(0)); const MTPVector<MTPMessageEntity> MTPnullEntities = MTP_vector<MTPMessageEntity>(0); const MTPMessageFwdHeader MTPnullFwdHeader = MTP_messageFwdHeader( MTP_flags(0), MTPint(), MTPint(), MTPint(), MTPint(), MTPstring(), MTPPeer(), MTPint()); ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/mtproto/type_utils.h�������������������������������������������0000664�0000000�0000000�00000010150�13262451251�0023476�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "scheme.h" #include "base/flags.h" inline MTPbool MTP_bool(bool v) { return v ? MTP_boolTrue() : MTP_boolFalse(); } inline bool mtpIsTrue(const MTPBool &v) { return v.type() == mtpc_boolTrue; } inline bool mtpIsFalse(const MTPBool &v) { return !mtpIsTrue(v); } // we must validate that MTProto scheme flags don't intersect with client side flags // and define common bit operators which allow use Type_ClientFlag together with Type::Flag #define DEFINE_MTP_CLIENT_FLAGS(Type) \ static_assert(Type::Flags(Type::Flag::MAX_FIELD) < static_cast<Type::Flag>(Type##_ClientFlag::MIN_FIELD), \ "MTProto flags conflict with client side flags!"); \ namespace base {\ template<>\ struct extended_flags<Type##_ClientFlag> {\ using type = Type::Flag;\ };\ } // we use the same flags field for some additional client side flags enum class MTPDmessage_ClientFlag : uint32 { // message has links for "shared links" indexing f_has_text_links = (1U << 30), // message is a group migrate (group -> supergroup) service message f_is_group_migrate = (1U << 29), //// message needs initDimensions() + resize() + paint() //f_pending_init_dimensions = (1U << 28), //// message needs resize() + paint() //f_pending_resize = (1U << 27), //// message needs paint() //f_pending_paint = (1U << 26), //// message is attached to previous one when displaying the history //f_attach_to_previous = (1U << 25), //// message is attached to next one when displaying the history //f_attach_to_next = (1U << 24), // message was sent from inline bot, need to re-set media when sent f_from_inline_bot = (1U << 23), // message has a switch inline keyboard button, need to return to inline f_has_switch_inline_button = (1U << 22), // message is generated on the client side and should be unread f_clientside_unread = (1U << 21), // message has an admin badge in supergroup f_has_admin_badge = (1U << 20), //// message is not displayed because it is part of a group //f_hidden_by_group = (1U << 19), // update this when adding new client side flags MIN_FIELD = (1U << 19), }; DEFINE_MTP_CLIENT_FLAGS(MTPDmessage) enum class MTPDreplyKeyboardMarkup_ClientFlag : uint32 { // none (zero) markup f_zero = (1U << 30), // markup just wants a text reply f_force_reply = (1U << 29), // markup keyboard is inline f_inline = (1U << 28), // markup has a switch inline keyboard button f_has_switch_inline_button = (1U << 27), // update this when adding new client side flags MIN_FIELD = (1U << 27), }; DEFINE_MTP_CLIENT_FLAGS(MTPDreplyKeyboardMarkup) enum class MTPDstickerSet_ClientFlag : uint32 { // old value for sticker set is not yet loaded flag f_not_loaded__old = (1U << 31), // sticker set is not yet loaded f_not_loaded = (1U << 30), // sticker set is one of featured (should be saved locally) f_featured = (1U << 29), // sticker set is an unread featured set f_unread = (1U << 28), // special set like recent or custom stickers f_special = (1U << 27), // update this when adding new client side flags MIN_FIELD = (1U << 27), }; DEFINE_MTP_CLIENT_FLAGS(MTPDstickerSet) //enum class MTPDuser_ClientFlag : uint32 { // // forbidden constructor received // f_inaccessible = (1U << 31), // // // update this when adding new client side flags // MIN_FIELD = (1U << 31), //}; //DEFINE_MTP_CLIENT_FLAGS(MTPDuser) enum class MTPDchat_ClientFlag : uint32 { // forbidden constructor received f_forbidden = (1U << 31), // update this when adding new client side flags MIN_FIELD = (1U << 31), }; DEFINE_MTP_CLIENT_FLAGS(MTPDchat) enum class MTPDchannel_ClientFlag : uint32 { // forbidden constructor received f_forbidden = (1U << 31), // update this when adding new client side flags MIN_FIELD = (1U << 31), }; DEFINE_MTP_CLIENT_FLAGS(MTPDchannel) extern const MTPReplyMarkup MTPnullMarkup; extern const MTPVector<MTPMessageEntity> MTPnullEntities; extern const MTPMessageFwdHeader MTPnullFwdHeader; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/observer_peer.cpp����������������������������������������������0000664�0000000�0000000�00000006211�13262451251�0022771�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "observer_peer.h" #include "base/observer.h" namespace Notify { namespace { using SmallUpdatesList = QVector<PeerUpdate>; NeverFreedPointer<SmallUpdatesList> SmallUpdates; using AllUpdatesList = QMap<PeerData*, PeerUpdate>; NeverFreedPointer<AllUpdatesList> AllUpdates; void StartCallback() { SmallUpdates.createIfNull(); AllUpdates.createIfNull(); } void FinishCallback() { SmallUpdates.clear(); AllUpdates.clear(); } base::Observable<PeerUpdate, PeerUpdatedHandler> PeerUpdatedObservable; } // namespace void mergePeerUpdate(PeerUpdate &mergeTo, const PeerUpdate &mergeFrom) { if (!(mergeTo.flags & PeerUpdate::Flag::NameChanged)) { if (mergeFrom.flags & PeerUpdate::Flag::NameChanged) { mergeTo.oldNameFirstLetters = mergeFrom.oldNameFirstLetters; } } mergeTo.flags |= mergeFrom.flags; } void peerUpdatedDelayed(const PeerUpdate &update) { SmallUpdates.createIfNull(); AllUpdates.createIfNull(); Global::RefHandleDelayedPeerUpdates().call(); int existingUpdatesCount = SmallUpdates->size(); for (int i = 0; i < existingUpdatesCount; ++i) { auto &existingUpdate = (*SmallUpdates)[i]; if (existingUpdate.peer == update.peer) { mergePeerUpdate(existingUpdate, update); return; } } if (AllUpdates->isEmpty()) { if (existingUpdatesCount < 5) { SmallUpdates->push_back(update); } else { AllUpdates->insert(update.peer, update); } } else { auto it = AllUpdates->find(update.peer); if (it != AllUpdates->cend()) { mergePeerUpdate(it.value(), update); return; } AllUpdates->insert(update.peer, update); } } void peerUpdatedSendDelayed() { if (!SmallUpdates || !AllUpdates || SmallUpdates->empty()) return; auto smallList = base::take(*SmallUpdates); auto allList = base::take(*AllUpdates); for (auto &update : smallList) { PeerUpdated().notify(std::move(update), true); } for (auto &update : allList) { PeerUpdated().notify(std::move(update), true); } if (SmallUpdates->isEmpty()) { std::swap(smallList, *SmallUpdates); SmallUpdates->resize(0); } } base::Observable<PeerUpdate, PeerUpdatedHandler> &PeerUpdated() { return PeerUpdatedObservable; } rpl::producer<PeerUpdate> PeerUpdateViewer( PeerUpdate::Flags flags) { return [=](const auto &consumer) { auto lifetime = rpl::lifetime(); lifetime.make_state<base::Subscription>( PeerUpdated().add_subscription({ flags, [=]( const PeerUpdate &update) { consumer.put_next_copy(update); }})); return lifetime; }; } rpl::producer<PeerUpdate> PeerUpdateViewer( not_null<PeerData*> peer, PeerUpdate::Flags flags) { return PeerUpdateViewer( flags ) | rpl::filter([=](const PeerUpdate &update) { return (update.peer == peer); }); } rpl::producer<PeerUpdate> PeerUpdateValue( not_null<PeerData*> peer, PeerUpdate::Flags flags) { auto initial = PeerUpdate(peer); initial.flags = flags; return rpl::single( initial ) | rpl::then(PeerUpdateViewer(peer, flags)); } } // namespace Notify ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/observer_peer.h������������������������������������������������0000664�0000000�0000000�00000006212�13262451251�0022437�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/producer.h> #include <rpl/filter.h> #include <rpl/then.h> #include <rpl/range.h> #include "base/observer.h" #include "base/flags.h" namespace Notify { // Generic notifications about updates of some PeerData. // You can subscribe to them by Notify::registerPeerObserver(). // 0x0000FFFFU for general peer updates (valid for any peer). // 0xFFFF0000U for specific peer updates (valid for user / chat / channel). struct PeerUpdate { PeerUpdate(PeerData *updated = nullptr) : peer(updated) { } PeerData *peer; enum class Flag : uint32 { None = 0, // Common flags NameChanged = (1 << 0), UsernameChanged = (1 << 1), PhotoChanged = (1 << 2), AboutChanged = (1 << 3), NotificationsEnabled = (1 << 4), MigrationChanged = (1 << 6), PinnedChanged = (1 << 7), RestrictionReasonChanged = (1 << 8), // For chats and channels InviteLinkChanged = (1 << 9), MembersChanged = (1 << 10), AdminsChanged = (1 << 11), BannedUsersChanged = (1 << 12), UnreadMentionsChanged = (1 << 13), // For users UserCanShareContact = (1 << 16), UserIsContact = (1 << 17), UserPhoneChanged = (1 << 18), UserIsBlocked = (1 << 19), BotCommandsChanged = (1 << 20), UserOnlineChanged = (1 << 21), BotCanAddToGroups = (1 << 22), UserCommonChatsChanged = (1 << 23), UserHasCalls = (1 << 24), // For chats ChatCanEdit = (1 << 16), // For channels ChannelAmIn = (1 << 16), ChannelRightsChanged = (1 << 17), ChannelStickersChanged = (1 << 18), ChannelPinnedChanged = (1 << 19), }; using Flags = base::flags<Flag>; friend inline constexpr auto is_flag_type(Flag) { return true; } Flags flags = 0; // NameChanged data base::flat_set<QChar> oldNameFirstLetters; }; void peerUpdatedDelayed(const PeerUpdate &update); inline void peerUpdatedDelayed(PeerData *peer, PeerUpdate::Flags events) { PeerUpdate update(peer); update.flags = events; peerUpdatedDelayed(update); } void peerUpdatedSendDelayed(); class PeerUpdatedHandler { public: template <typename Lambda> PeerUpdatedHandler(PeerUpdate::Flags events, Lambda &&handler) : _events(events), _handler(std::move(handler)) { } void operator()(const PeerUpdate &update) const { if (update.flags & _events) { _handler(update); } } private: PeerUpdate::Flags _events; base::lambda<void(const PeerUpdate&)> _handler; }; base::Observable<PeerUpdate, PeerUpdatedHandler> &PeerUpdated(); rpl::producer<PeerUpdate> PeerUpdateViewer( PeerUpdate::Flags flags); rpl::producer<PeerUpdate> PeerUpdateViewer( not_null<PeerData*> peer, PeerUpdate::Flags flags); rpl::producer<PeerUpdate> PeerUpdateValue( not_null<PeerData*> peer, PeerUpdate::Flags flags); } // namespace Notify ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/overview/������������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0021271�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/overview/overview.style����������������������������������������0000664�0000000�0000000�00000007153�13262451251�0024227�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ using "basic.style"; using "history/history.style"; using "ui/widgets/widgets.style"; using "media/view/mediaview.style"; using "boxes/boxes.style"; OverviewFileLayout { maxWidth: pixels; songPadding: margins; songThumbSize: pixels; songNameTop: pixels; songStatusTop: pixels; songIconBg: color; songOverBg: color; songPause: icon; songPauseSelected: icon; songPlay: icon; songPlaySelected: icon; songCancel: icon; songCancelSelected: icon; songDownload: icon; songDownloadSelected: icon; filePadding: margins; fileThumbSize: pixels; fileNameTop: pixels; fileStatusTop: pixels; fileDateTop: pixels; } overviewLeftMin: 28px; overviewLeftMax: 42px; overviewCheckPressedSize: 0.8; overviewCheck: RoundCheckbox(defaultRoundCheckbox) { bgInactive: overviewCheckBg; bgActive: overviewCheckBgActive; border: overviewCheckBorder; size: 29px; sizeSmall: 0.3; check: icon {{ "overview_photo_check", overviewCheckFgActive, point(4px, 8px) }}; } overviewSmallCheck: RoundCheckbox(contactsPhotoCheck) { bgInactive: overviewCheckBg; bgActive: overviewCheckBgActive; border: overviewCheckBorder; } overviewCheckSkip: 5px; overviewPhotoBg: windowBgOver; overviewPhotoMinSize: 90px; overviewVideoBg: imageBg; overviewFileThumbBg: imageBg; overviewFileChecked: overviewCheckBgActive; overviewFileCheck: overviewCheckBg; overviewFileExtPadding: 5px; overviewFileExtTop: 24px; overviewFileExtFg: windowFgActive; overviewFileExtFont: font(18px semibold); overviewSongPause: icon {{ "playlist_pause", historyFileInIconFg }}; overviewSongPauseSelected: icon {{ "playlist_pause", historyFileInIconFgSelected }}; overviewSongPlay: icon {{ "playlist_play", historyFileInIconFg }}; overviewSongPlaySelected: icon {{ "playlist_play", historyFileInIconFgSelected }}; overviewSongCancel: icon {{ "playlist_cancel", historyFileInIconFg }}; overviewSongCancelSelected: icon {{ "playlist_cancel", historyFileInIconFgSelected }}; overviewSongDownload: icon {{ "playlist_download", historyFileInIconFg }}; overviewSongDownloadSelected: icon {{ "playlist_download", historyFileInIconFgSelected }}; overviewFileLayout: OverviewFileLayout { maxWidth: 520px; songPadding: margins(17px, 7px, 10px, 6px); songThumbSize: 36px; songNameTop: 7px; songStatusTop: 25px; songIconBg: msgFileInBg; songOverBg: msgFileInBgOver; songPause: overviewSongPause; songPauseSelected: overviewSongPauseSelected; songPlay: overviewSongPlay; songPlaySelected: overviewSongPlaySelected; songCancel: overviewSongCancel; songCancelSelected: overviewSongCancelSelected; songDownload: overviewSongDownload; songDownloadSelected: overviewSongDownloadSelected; filePadding: margins(0px, 3px, 16px, 3px); fileThumbSize: 70px; fileNameTop: 7px; fileStatusTop: 24px; fileDateTop: 49px; } overviewLoader: size(34px, 14px); overviewLoaderPoint: size(4px, 4px); overviewLoaderSkip: 4px; playlistHoverBg: windowBgOver; playlistPadding: 10px; linksSearchTop: 30px; linksMaxWidth: 520px; linksLetterFg: windowFgActive; linksLetterFont: font(24px); linksMargin: margins(0px, 7px, 0px, 5px); linksTextFg: windowFg; linksTextTop: 6px; linksBorder: 1px; linksBorderFg: shadowFg; linksDateColor: windowSubTextFg; linksDateMargin: margins(0px, 15px, 0px, 2px); linksPhotoSize: 46px; linksPhotoPadding: 12px; overviewFilter: FlatInput(defaultFlatInput) { font: font(fsize); icon: fieldSearchIcon; width: 240px; height: 32px; textMrg: margins(32px, 3px, 32px, 3px); } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/overview/overview_layout.cpp�����������������������������������0000664�0000000�0000000�00000135736�13262451251�0025257�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "overview/overview_layout.h" #include "data/data_document.h" #include "data/data_session.h" #include "data/data_web_page.h" #include "data/data_media_types.h" #include "styles/style_overview.h" #include "styles/style_history.h" #include "core/file_utilities.h" #include "boxes/add_contact_box.h" #include "boxes/confirm_box.h" #include "lang/lang_keys.h" #include "mainwidget.h" #include "application.h" #include "storage/file_upload.h" #include "mainwindow.h" #include "media/media_audio.h" #include "media/player/media_player_instance.h" #include "storage/localstorage.h" #include "history/history_item.h" #include "history/history_item_components.h" #include "history/view/history_view_cursor_state.h" #include "ui/effects/round_checkbox.h" #include "ui/text_options.h" namespace Overview { namespace Layout { namespace { using TextState = HistoryView::TextState; TextParseOptions _documentNameOptions = { TextParseMultiline | TextParseRichText | TextParseLinks | TextParseMarkdown, // flags 0, // maxw 0, // maxh Qt::LayoutDirectionAuto, // dir }; TextWithEntities ComposeNameWithEntities(DocumentData *document) { TextWithEntities result; const auto song = document->song(); if (!song || (song->title.isEmpty() && song->performer.isEmpty())) { result.text = document->filename().isEmpty() ? qsl("Unknown File") : document->filename(); result.entities.push_back({ EntityInTextBold, 0, result.text.size() }); } else if (song->performer.isEmpty()) { result.text = song->title; result.entities.push_back({ EntityInTextBold, 0, result.text.size() }); } else { result.text = song->performer + QString::fromUtf8(" \xe2\x80\x93 ") + (song->title.isEmpty() ? qsl("Unknown Track") : song->title); result.entities.push_back({ EntityInTextBold, 0, song->performer.size() }); } return result; } } // namespace class Checkbox { public: template <typename UpdateCallback> Checkbox(UpdateCallback callback, const style::RoundCheckbox &st) : _updateCallback(callback) , _check(st, _updateCallback) { } void paint(Painter &p, TimeMs ms, QPoint position, int outerWidth, bool selected, bool selecting); void setActive(bool active); void setPressed(bool pressed); void invalidateCache() { _check.invalidateCache(); } private: void startAnimation(); base::lambda<void()> _updateCallback; Ui::RoundCheckbox _check; Animation _pression; bool _active = false; bool _pressed = false; }; void Checkbox::paint(Painter &p, TimeMs ms, QPoint position, int outerWidth, bool selected, bool selecting) { _check.setDisplayInactive(selecting); _check.setChecked(selected); const auto pression = _pression.current(ms, (_active && _pressed) ? 1. : 0.); const auto masterScale = 1. - (1. - st::overviewCheckPressedSize) * pression; _check.paint(p, ms, position.x(), position.y(), outerWidth, masterScale); } void Checkbox::setActive(bool active) { _active = active; if (_pressed) { startAnimation(); } } void Checkbox::setPressed(bool pressed) { _pressed = pressed; if (_active) { startAnimation(); } } void Checkbox::startAnimation() { auto showPressed = (_pressed && _active); _pression.start(_updateCallback, showPressed ? 0. : 1., showPressed ? 1. : 0., st::overviewCheck.duration); } MsgId AbstractItem::msgId() const { auto item = getItem(); return item ? item->id : 0; } ItemBase::ItemBase(not_null<HistoryItem*> parent) : _parent(parent) , _dateTime(ItemDateTime(parent)) { } QDateTime ItemBase::dateTime() const { return _dateTime; } void ItemBase::clickHandlerActiveChanged( const ClickHandlerPtr &action, bool active) { Auth().data().requestItemRepaint(_parent); if (_check) { _check->setActive(active); } } void ItemBase::clickHandlerPressedChanged( const ClickHandlerPtr &action, bool pressed) { Auth().data().requestItemRepaint(_parent); if (_check) { _check->setPressed(pressed); } } void ItemBase::invalidateCache() { if (_check) { _check->invalidateCache(); } } void ItemBase::paintCheckbox( Painter &p, QPoint position, bool selected, const PaintContext *context) { if (selected || context->selecting) { ensureCheckboxCreated(); } if (_check) { _check->paint(p, context->ms, position, _width, selected, context->selecting); } } const style::RoundCheckbox &ItemBase::checkboxStyle() const { return st::overviewCheck; } void ItemBase::ensureCheckboxCreated() { if (!_check) { _check = std::make_unique<Checkbox>( [=] { Auth().data().requestItemRepaint(_parent); }, checkboxStyle()); } } ItemBase::~ItemBase() = default; void RadialProgressItem::setDocumentLinks( not_null<DocumentData*> document) { const auto context = parent()->fullId(); const auto createSaveHandler = [&]() -> ClickHandlerPtr { if (document->isVoiceMessage()) { return std::make_shared<DocumentOpenClickHandler>( document, context); } return std::make_shared<DocumentSaveClickHandler>( document, context); }; setLinks( std::make_shared<DocumentOpenClickHandler>( document, context), createSaveHandler(), std::make_shared<DocumentCancelClickHandler>( document, context)); } void RadialProgressItem::clickHandlerActiveChanged(const ClickHandlerPtr &action, bool active) { ItemBase::clickHandlerActiveChanged(action, active); if (action == _openl || action == _savel || action == _cancell) { if (iconAnimated()) { _a_iconOver.start( [=] { Auth().data().requestItemRepaint(parent()); }, active ? 0. : 1., active ? 1. : 0., st::msgFileOverDuration); } } } void RadialProgressItem::setLinks(ClickHandlerPtr &&openl, ClickHandlerPtr &&savel, ClickHandlerPtr &&cancell) { _openl = std::move(openl); _savel = std::move(savel); _cancell = std::move(cancell); } void RadialProgressItem::step_radial(TimeMs ms, bool timer) { if (timer) { Auth().data().requestItemRepaint(parent()); } else { _radial->update(dataProgress(), dataFinished(), ms); if (!_radial->animating()) { checkRadialFinished(); } } } void RadialProgressItem::ensureRadial() { if (!_radial) { _radial = std::make_unique<Ui::RadialAnimation>(animation(const_cast<RadialProgressItem*>(this), &RadialProgressItem::step_radial)); } } void RadialProgressItem::checkRadialFinished() { if (_radial && !_radial->animating() && dataLoaded()) { _radial.reset(); } } RadialProgressItem::~RadialProgressItem() = default; void StatusText::update(int newSize, int fullSize, int duration, TimeMs realDuration) { setSize(newSize); if (_size == FileStatusSizeReady) { _text = (duration >= 0) ? formatDurationAndSizeText(duration, fullSize) : (duration < -1 ? formatGifAndSizeText(fullSize) : formatSizeText(fullSize)); } else if (_size == FileStatusSizeLoaded) { _text = (duration >= 0) ? formatDurationText(duration) : (duration < -1 ? qsl("GIF") : formatSizeText(fullSize)); } else if (_size == FileStatusSizeFailed) { _text = lang(lng_attach_failed); } else if (_size >= 0) { _text = formatDownloadText(_size, fullSize); } else { _text = formatPlayedText(-_size - 1, realDuration); } } void StatusText::setSize(int newSize) { _size = newSize; } Date::Date(const QDate &date, bool month) : _date(date) , _text(month ? langMonthFull(date) : langDayOfMonthFull(date)) { AddComponents(Info::Bit()); } void Date::initDimensions() { _maxw = st::normalFont->width(_text); _minh = st::linksDateMargin.top() + st::normalFont->height + st::linksDateMargin.bottom() + st::linksBorder; } void Date::paint(Painter &p, const QRect &clip, TextSelection selection, const PaintContext *context) { if (clip.intersects(QRect(0, st::linksDateMargin.top(), _width, st::normalFont->height))) { p.setPen(st::linksDateColor); p.setFont(st::semiboldFont); p.drawTextLeft(0, st::linksDateMargin.top(), _width, _text); } } Photo::Photo( not_null<HistoryItem*> parent, not_null<PhotoData*> photo) : ItemBase(parent) , _data(photo) , _link(std::make_shared<PhotoOpenClickHandler>(photo, parent->fullId())) { } void Photo::initDimensions() { _maxw = 2 * st::overviewPhotoMinSize; _minh = _maxw; } int32 Photo::resizeGetHeight(int32 width) { width = qMin(width, _maxw); if (width != _width || width != _height) { _width = qMin(width, _maxw); _height = _width; } return _height; } void Photo::paint(Painter &p, const QRect &clip, TextSelection selection, const PaintContext *context) { bool good = _data->loaded(), selected = (selection == FullSelection); if (!good) { _data->medium->automaticLoad(parent()); good = _data->medium->loaded(); } if ((good && !_goodLoaded) || _pix.width() != _width * cIntRetinaFactor()) { _goodLoaded = good; int32 size = _width * cIntRetinaFactor(); if (_goodLoaded || _data->thumb->loaded()) { auto img = (_data->loaded() ? _data->full : (_data->medium->loaded() ? _data->medium : _data->thumb))->pix().toImage(); if (!_goodLoaded) { img = Images::prepareBlur(std::move(img)); } if (img.width() == img.height()) { if (img.width() != size) { img = img.scaled(size, size, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); } } else if (img.width() > img.height()) { img = img.copy((img.width() - img.height()) / 2, 0, img.height(), img.height()).scaled(size, size, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); } else { img = img.copy(0, (img.height() - img.width()) / 2, img.width(), img.width()).scaled(size, size, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); } img.setDevicePixelRatio(cRetinaFactor()); _data->forget(); _pix = App::pixmapFromImageInPlace(std::move(img)); } else if (!_pix.isNull()) { _pix = QPixmap(); } } if (_pix.isNull()) { p.fillRect(0, 0, _width, _height, st::overviewPhotoBg); } else { p.drawPixmap(0, 0, _pix); } if (selected) { p.fillRect(0, 0, _width, _height, st::overviewPhotoSelectOverlay); } const auto checkDelta = st::overviewCheckSkip + st::overviewCheck.size; const auto checkLeft = _width - checkDelta; const auto checkTop = _height - checkDelta; paintCheckbox(p, { checkLeft, checkTop }, selected, context); } TextState Photo::getState( QPoint point, StateRequest request) const { if (hasPoint(point)) { return { parent(), _link }; } return {}; } Video::Video( not_null<HistoryItem*> parent, not_null<DocumentData*> video) : RadialProgressItem(parent) , _data(video) , _duration(formatDurationText(_data->duration())) , _thumbLoaded(false) { setDocumentLinks(_data); _data->thumb->load(); } void Video::initDimensions() { _maxw = 2 * st::minPhotoSize; _minh = _maxw; } int32 Video::resizeGetHeight(int32 width) { _width = qMin(width, _maxw); _height = _width; return _height; } void Video::paint(Painter &p, const QRect &clip, TextSelection selection, const PaintContext *context) { bool selected = (selection == FullSelection), thumbLoaded = _data->thumb->loaded(); _data->automaticLoad(parent()); bool loaded = _data->loaded(), displayLoading = _data->displayLoading(); if (displayLoading) { ensureRadial(); if (!_radial->animating()) { _radial->start(_data->progress()); } } updateStatusText(); bool radial = isRadialAnimation(context->ms); if ((thumbLoaded && !_thumbLoaded) || (_pix.width() != _width * cIntRetinaFactor())) { _thumbLoaded = thumbLoaded; if (_thumbLoaded && !_data->thumb->isNull()) { auto size = _width * cIntRetinaFactor(); auto img = Images::prepareBlur(_data->thumb->pix().toImage()); if (img.width() == img.height()) { if (img.width() != size) { img = img.scaled(size, size, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); } } else if (img.width() > img.height()) { img = img.copy((img.width() - img.height()) / 2, 0, img.height(), img.height()).scaled(size, size, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); } else { img = img.copy(0, (img.height() - img.width()) / 2, img.width(), img.width()).scaled(size, size, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); } img.setDevicePixelRatio(cRetinaFactor()); _data->forget(); _pix = App::pixmapFromImageInPlace(std::move(img)); } else if (!_pix.isNull()) { _pix = QPixmap(); } } if (_pix.isNull()) { p.fillRect(0, 0, _width, _height, st::overviewPhotoBg); } else { p.drawPixmap(0, 0, _pix); } if (selected) { p.fillRect(QRect(0, 0, _width, _height), st::overviewPhotoSelectOverlay); } if (!selected && !context->selecting && !loaded) { if (clip.intersects(QRect(0, _height - st::normalFont->height, _width, st::normalFont->height))) { int32 statusX = st::msgDateImgPadding.x(), statusY = _height - st::normalFont->height - st::msgDateImgPadding.y(); int32 statusW = st::normalFont->width(_status.text()) + 2 * st::msgDateImgPadding.x(); int32 statusH = st::normalFont->height + 2 * st::msgDateImgPadding.y(); statusX = _width - statusW + statusX; p.fillRect(rtlrect(statusX - st::msgDateImgPadding.x(), statusY - st::msgDateImgPadding.y(), statusW, statusH, _width), selected ? st::msgDateImgBgSelected : st::msgDateImgBg); p.setFont(st::normalFont); p.setPen(st::msgDateImgFg); p.drawTextLeft(statusX, statusY, _width, _status.text(), statusW - 2 * st::msgDateImgPadding.x()); } } if (clip.intersects(QRect(0, 0, _width, st::normalFont->height))) { int32 statusX = st::msgDateImgPadding.x(), statusY = st::msgDateImgPadding.y(); int32 statusW = st::normalFont->width(_duration) + 2 * st::msgDateImgPadding.x(); int32 statusH = st::normalFont->height + 2 * st::msgDateImgPadding.y(); p.fillRect(rtlrect(statusX - st::msgDateImgPadding.x(), statusY - st::msgDateImgPadding.y(), statusW, statusH, _width), selected ? st::msgDateImgBgSelected : st::msgDateImgBg); p.setFont(st::normalFont); p.setPen(st::msgDateImgFg); p.drawTextLeft(statusX, statusY, _width, _duration, statusW - 2 * st::msgDateImgPadding.x()); } QRect inner((_width - st::msgFileSize) / 2, (_height - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize); if (clip.intersects(inner)) { p.setPen(Qt::NoPen); if (selected) { p.setBrush(st::msgDateImgBgSelected); } else { auto over = ClickHandler::showAsActive(loaded ? _openl : (_data->loading() ? _cancell : _savel)); p.setBrush(anim::brush(st::msgDateImgBg, st::msgDateImgBgOver, _a_iconOver.current(context->ms, over ? 1. : 0.))); } { PainterHighQualityEnabler hq(p); p.drawEllipse(inner); } p.setOpacity((radial && loaded) ? _radial->opacity() : 1); auto icon = ([radial, loaded, selected] { if (radial) { return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel); } else if (loaded) { return &(selected ? st::historyFileThumbPlaySelected : st::historyFileThumbPlay); } return &(selected ? st::historyFileThumbDownloadSelected : st::historyFileThumbDownload); })(); icon->paintInCenter(p, inner); if (radial) { p.setOpacity(1); QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine))); _radial->draw(p, rinner, st::msgFileRadialLine, selected ? st::historyFileThumbRadialFgSelected : st::historyFileThumbRadialFg); } } const auto checkDelta = st::overviewCheckSkip + st::overviewCheck.size; const auto checkLeft = _width - checkDelta; const auto checkTop = _height - checkDelta; paintCheckbox(p, { checkLeft, checkTop }, selected, context); } float64 Video::dataProgress() const { return _data->progress(); } bool Video::dataFinished() const { return !_data->loading(); } bool Video::dataLoaded() const { return _data->loaded(); } bool Video::iconAnimated() const { return true; } TextState Video::getState( QPoint point, StateRequest request) const { bool loaded = _data->loaded(); if (hasPoint(point)) { const auto link = loaded ? _openl : _data->loading() ? _cancell : _savel; return { parent(), link }; } return {}; } void Video::updateStatusText() { bool showPause = false; int statusSize = 0; if (_data->status == FileDownloadFailed || _data->status == FileUploadFailed) { statusSize = FileStatusSizeFailed; } else if (_data->uploading()) { statusSize = _data->uploadingData->offset; } else if (_data->loading()) { statusSize = _data->loadOffset(); } else if (_data->loaded()) { statusSize = FileStatusSizeLoaded; } else { statusSize = FileStatusSizeReady; } if (statusSize != _status.size()) { int status = statusSize, size = _data->size; if (statusSize >= 0 && statusSize < 0x7F000000) { size = status; status = FileStatusSizeReady; } _status.update(status, size, -1, 0); _status.setSize(statusSize); } } Voice::Voice( not_null<HistoryItem*> parent, not_null<DocumentData*> voice, const style::OverviewFileLayout &st) : RadialProgressItem(parent) , _data(voice) , _namel(std::make_shared<DocumentOpenClickHandler>(_data, parent->fullId())) , _st(st) { AddComponents(Info::Bit()); Assert(_data->isVoiceMessage()); setDocumentLinks(_data); updateName(); const auto dateText = textcmdLink( 1, TextUtilities::EscapeForRichParsing( langDateTime(ParseDateTime(_data->date)))); TextParseOptions opts = { TextParseRichText, 0, 0, Qt::LayoutDirectionAuto }; _details.setText( st::defaultTextStyle, lng_date_and_duration( lt_date, dateText, lt_duration, formatDurationText(_data->voice()->duration)), opts); _details.setLink(1, goToMessageClickHandler(parent)); } void Voice::initDimensions() { _maxw = _st.maxWidth; _minh = _st.songPadding.top() + _st.songThumbSize + _st.songPadding.bottom() + st::lineWidth; } void Voice::paint(Painter &p, const QRect &clip, TextSelection selection, const PaintContext *context) { bool selected = (selection == FullSelection); _data->automaticLoad(parent()); bool loaded = _data->loaded(), displayLoading = _data->displayLoading(); if (displayLoading) { ensureRadial(); if (!_radial->animating()) { _radial->start(_data->progress()); } } bool showPause = updateStatusText(); int32 nameVersion = parent()->fromOriginal()->nameVersion; if (nameVersion > _nameVersion) { updateName(); } bool radial = isRadialAnimation(context->ms); const auto nameleft = _st.songPadding.left() + _st.songThumbSize + _st.songPadding.right(); const auto nameright = _st.songPadding.left(); const auto nametop = _st.songNameTop; const auto statustop = _st.songStatusTop; const auto namewidth = _width - nameleft - nameright; const auto inner = rtlrect( _st.songPadding.left(), _st.songPadding.top(), _st.songThumbSize, _st.songThumbSize, _width); if (clip.intersects(inner)) { p.setPen(Qt::NoPen); if (selected) { p.setBrush(st::msgFileInBgSelected); } else { auto over = ClickHandler::showAsActive(loaded ? _openl : (_data->loading() ? _cancell : _openl)); p.setBrush(anim::brush(st::msgFileInBg, st::msgFileInBgOver, _a_iconOver.current(context->ms, over ? 1. : 0.))); } { PainterHighQualityEnabler hq(p); p.drawEllipse(inner); } if (radial) { QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine))); auto &bg = selected ? st::historyFileInRadialFgSelected : st::historyFileInRadialFg; _radial->draw(p, rinner, st::msgFileRadialLine, bg); } auto icon = ([showPause, this, selected] { if (showPause) { return &(selected ? _st.songPauseSelected : _st.songPause); } else if (_status.size() < 0 || _status.size() == FileStatusSizeLoaded) { return &(selected ? _st.songPlaySelected : _st.songPlay); } else if (_data->loading()) { return &(selected ? _st.songCancelSelected : _st.songCancel); } return &(selected ? _st.songDownloadSelected : _st.songDownload); })(); icon->paintInCenter(p, inner); } if (clip.intersects(rtlrect(nameleft, nametop, namewidth, st::semiboldFont->height, _width))) { p.setPen(st::historyFileNameInFg); _name.drawLeftElided(p, nameleft, nametop, namewidth, _width); } if (clip.intersects(rtlrect(nameleft, statustop, namewidth, st::normalFont->height, _width))) { p.setFont(st::normalFont); p.setPen(selected ? st::mediaInFgSelected : st::mediaInFg); int32 unreadx = nameleft; if (_status.size() == FileStatusSizeLoaded || _status.size() == FileStatusSizeReady) { p.setTextPalette(selected ? st::mediaInPaletteSelected : st::mediaInPalette); _details.drawLeftElided(p, nameleft, statustop, namewidth, _width); p.restoreTextPalette(); unreadx += _details.maxWidth(); } else { int32 statusw = st::normalFont->width(_status.text()); p.drawTextLeft(nameleft, statustop, _width, _status.text(), statusw); unreadx += statusw; } if (parent()->isMediaUnread() && unreadx + st::mediaUnreadSkip + st::mediaUnreadSize <= _width) { p.setPen(Qt::NoPen); p.setBrush(selected ? st::msgFileInBgSelected : st::msgFileInBg); { PainterHighQualityEnabler hq(p); p.drawEllipse(rtlrect(unreadx + st::mediaUnreadSkip, statustop + st::mediaUnreadTop, st::mediaUnreadSize, st::mediaUnreadSize, _width)); } } } const auto checkDelta = _st.songThumbSize + st::overviewCheckSkip - st::overviewSmallCheck.size; const auto checkLeft = _st.songPadding.left() + checkDelta; const auto checkTop = _st.songPadding.top() + checkDelta; paintCheckbox(p, { checkLeft, checkTop }, selected, context); } TextState Voice::getState( QPoint point, StateRequest request) const { const auto loaded = _data->loaded(); const auto nameleft = _st.songPadding.left() + _st.songThumbSize + _st.songPadding.right(); const auto nameright = _st.songPadding.left(); const auto nametop = _st.songNameTop; const auto statustop = _st.songStatusTop; const auto inner = rtlrect( _st.songPadding.left(), _st.songPadding.top(), _st.songThumbSize, _st.songThumbSize, _width); if (inner.contains(point)) { const auto link = loaded ? _openl : (_data->loading() || _data->uploading()) ? _cancell : _openl; return { parent(), link }; } auto result = TextState(parent()); const auto statusmaxwidth = _width - nameleft - nameright; const auto statusrect = rtlrect( nameleft, statustop, statusmaxwidth, st::normalFont->height, _width); if (statusrect.contains(point)) { if (_status.size() == FileStatusSizeLoaded || _status.size() == FileStatusSizeReady) { auto textState = _details.getStateLeft(point - QPoint(nameleft, statustop), _width, _width); result.link = textState.link; result.cursor = textState.uponSymbol ? HistoryView::CursorState::Text : HistoryView::CursorState::None; } } const auto namewidth = std::min( _width - nameleft - nameright, _name.maxWidth()); const auto namerect = rtlrect( nameleft, nametop, namewidth, st::normalFont->height, _width); if (namerect.contains(point) && !result.link && !_data->loading()) { return { parent(), _namel }; } return result; } float64 Voice::dataProgress() const { return _data->progress(); } bool Voice::dataFinished() const { return !_data->loading(); } bool Voice::dataLoaded() const { return _data->loaded(); } bool Voice::iconAnimated() const { return true; } const style::RoundCheckbox &Voice::checkboxStyle() const { return st::overviewSmallCheck; } void Voice::updateName() { auto version = 0; if (const auto forwarded = parent()->Get<HistoryMessageForwarded>()) { if (parent()->fromOriginal()->isChannel()) { _name.setText(st::semiboldTextStyle, lng_forwarded_channel(lt_channel, App::peerName(parent()->fromOriginal())), Ui::NameTextOptions()); } else { _name.setText(st::semiboldTextStyle, lng_forwarded(lt_user, App::peerName(parent()->fromOriginal())), Ui::NameTextOptions()); } } else { _name.setText(st::semiboldTextStyle, App::peerName(parent()->from()), Ui::NameTextOptions()); } version = parent()->fromOriginal()->nameVersion; _nameVersion = version; } bool Voice::updateStatusText() { bool showPause = false; int32 statusSize = 0, realDuration = 0; if (_data->status == FileDownloadFailed || _data->status == FileUploadFailed) { statusSize = FileStatusSizeFailed; } else if (_data->loaded()) { statusSize = FileStatusSizeLoaded; using State = Media::Player::State; auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice); if (state.id == AudioMsgId(_data, parent()->fullId()) && !Media::Player::IsStoppedOrStopping(state.state)) { statusSize = -1 - (state.position / state.frequency); realDuration = (state.length / state.frequency); showPause = (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting); } } else { statusSize = FileStatusSizeReady; } if (statusSize != _status.size()) { _status.update(statusSize, _data->size, _data->voice()->duration, realDuration); } return showPause; } Document::Document( not_null<HistoryItem*> parent, not_null<DocumentData*> document, const style::OverviewFileLayout &st) : RadialProgressItem(parent) , _data(document) , _msgl(goToMessageClickHandler(parent)) , _namel(std::make_shared<DocumentOpenClickHandler>(_data, parent->fullId())) , _st(st) , _date(langDateTime(ParseDateTime(_data->date))) , _datew(st::normalFont->width(_date)) , _colorIndex(documentColorIndex(_data, _ext)) { _name.setMarkedText(st::defaultTextStyle, ComposeNameWithEntities(_data), _documentNameOptions); AddComponents(Info::Bit()); setDocumentLinks(_data); _status.update(FileStatusSizeReady, _data->size, _data->isSong() ? _data->song()->duration : -1, 0); if (withThumb()) { _data->thumb->load(); int32 tw = convertScale(_data->thumb->width()), th = convertScale(_data->thumb->height()); if (tw > th) { _thumbw = (tw * _st.fileThumbSize) / th; } else { _thumbw = _st.fileThumbSize; } } else { _thumbw = 0; } _extw = st::overviewFileExtFont->width(_ext); if (_extw > _st.fileThumbSize - st::overviewFileExtPadding * 2) { _ext = st::overviewFileExtFont->elided(_ext, _st.fileThumbSize - st::overviewFileExtPadding * 2, Qt::ElideMiddle); _extw = st::overviewFileExtFont->width(_ext); } } void Document::initDimensions() { _maxw = _st.maxWidth; if (_data->isAudioFile()) { _minh = _st.songPadding.top() + _st.songThumbSize + _st.songPadding.bottom(); } else { _minh = _st.filePadding.top() + _st.fileThumbSize + _st.filePadding.bottom() + st::lineWidth; } } void Document::paint(Painter &p, const QRect &clip, TextSelection selection, const PaintContext *context) { bool selected = (selection == FullSelection); _data->automaticLoad(parent()); bool loaded = _data->loaded() || Local::willStickerImageLoad(_data->mediaKey()), displayLoading = _data->displayLoading(); if (displayLoading) { ensureRadial(); if (!_radial->animating()) { _radial->start(_data->progress()); } } bool showPause = updateStatusText(); bool radial = isRadialAnimation(context->ms); int32 nameleft = 0, nametop = 0, nameright = 0, statustop = 0, datetop = -1; bool wthumb = withThumb(); auto isSong = _data->isAudioFile(); if (isSong) { nameleft = _st.songPadding.left() + _st.songThumbSize + _st.songPadding.right(); nameright = _st.songPadding.left(); nametop = _st.songNameTop; statustop = _st.songStatusTop; auto inner = rtlrect(_st.songPadding.left(), _st.songPadding.top(), _st.songThumbSize, _st.songThumbSize, _width); if (clip.intersects(inner)) { p.setPen(Qt::NoPen); if (selected) { p.setBrush(st::msgFileInBgSelected); } else { auto over = ClickHandler::showAsActive(loaded ? _openl : (_data->loading() ? _cancell : _openl)); p.setBrush(anim::brush(_st.songIconBg, _st.songOverBg, _a_iconOver.current(context->ms, over ? 1. : 0.))); } { PainterHighQualityEnabler hq(p); p.drawEllipse(inner); } if (radial) { auto rinner = inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine)); auto &bg = selected ? st::historyFileInRadialFgSelected : st::historyFileInRadialFg; _radial->draw(p, rinner, st::msgFileRadialLine, bg); } auto icon = ([showPause, loaded, this, selected] { if (showPause) { return &(selected ? _st.songPauseSelected : _st.songPause); } else if (loaded) { return &(selected ? _st.songPlaySelected : _st.songPlay); } else if (_data->loading()) { return &(selected ? _st.songCancelSelected : _st.songCancel); } return &(selected ? _st.songDownloadSelected : _st.songDownload); })(); icon->paintInCenter(p, inner); } } else { nameleft = _st.fileThumbSize + _st.filePadding.right(); nametop = st::linksBorder + _st.fileNameTop; statustop = st::linksBorder + _st.fileStatusTop; datetop = st::linksBorder + _st.fileDateTop; QRect border(rtlrect(nameleft, 0, _width - nameleft, st::linksBorder, _width)); if (!context->isAfterDate && clip.intersects(border)) { p.fillRect(clip.intersected(border), st::linksBorderFg); } QRect rthumb(rtlrect(0, st::linksBorder + _st.filePadding.top(), _st.fileThumbSize, _st.fileThumbSize, _width)); if (clip.intersects(rthumb)) { if (wthumb) { if (_data->thumb->loaded()) { if (_thumb.isNull() || loaded != _thumbForLoaded) { _thumbForLoaded = loaded; auto options = Images::Option::Smooth | Images::Option::None; if (!_thumbForLoaded) options |= Images::Option::Blurred; _thumb = _data->thumb->pixNoCache(_thumbw * cIntRetinaFactor(), 0, options, _st.fileThumbSize, _st.fileThumbSize); } p.drawPixmap(rthumb.topLeft(), _thumb); } else { p.fillRect(rthumb, st::overviewFileThumbBg); } } else { p.fillRect(rthumb, documentColor(_colorIndex)); if (!radial && loaded && !_ext.isEmpty()) { p.setFont(st::overviewFileExtFont); p.setPen(st::overviewFileExtFg); p.drawText(rthumb.left() + (rthumb.width() - _extw) / 2, rthumb.top() + st::overviewFileExtTop + st::overviewFileExtFont->ascent, _ext); } } if (selected) { p.fillRect(rthumb, st::defaultTextPalette.selectOverlay); } if (radial || (!loaded && !_data->loading())) { QRect inner(rthumb.x() + (rthumb.width() - _st.songThumbSize) / 2, rthumb.y() + (rthumb.height() - _st.songThumbSize) / 2, _st.songThumbSize, _st.songThumbSize); if (clip.intersects(inner)) { auto radialOpacity = (radial && loaded && !_data->uploading()) ? _radial->opacity() : 1; p.setPen(Qt::NoPen); if (selected) { p.setBrush(wthumb ? st::msgDateImgBgSelected : documentSelectedColor(_colorIndex)); } else { auto over = ClickHandler::showAsActive(_data->loading() ? _cancell : _savel); p.setBrush(anim::brush(wthumb ? st::msgDateImgBg : documentDarkColor(_colorIndex), wthumb ? st::msgDateImgBgOver : documentOverColor(_colorIndex), _a_iconOver.current(context->ms, over ? 1. : 0.))); } p.setOpacity(radialOpacity * p.opacity()); { PainterHighQualityEnabler hq(p); p.drawEllipse(inner); } p.setOpacity(radialOpacity); auto icon = ([loaded, this, selected] { if (loaded || _data->loading()) { return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel); } return &(selected ? st::historyFileThumbDownloadSelected : st::historyFileThumbDownload); })(); icon->paintInCenter(p, inner); if (radial) { p.setOpacity(1); QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine))); _radial->draw(p, rinner, st::msgFileRadialLine, selected ? st::historyFileThumbRadialFgSelected : st::historyFileThumbRadialFg); } } } } } const auto availwidth = _width - nameleft - nameright; const auto namewidth = std::min(availwidth, _name.maxWidth()); if (clip.intersects(rtlrect(nameleft, nametop, namewidth, st::semiboldFont->height, _width))) { p.setPen(st::historyFileNameInFg); _name.drawLeftElided(p, nameleft, nametop, namewidth, _width); } if (clip.intersects(rtlrect(nameleft, statustop, availwidth, st::normalFont->height, _width))) { p.setFont(st::normalFont); p.setPen((isSong && selected) ? st::mediaInFgSelected : st::mediaInFg); p.drawTextLeft(nameleft, statustop, _width, _status.text()); } if (datetop >= 0 && clip.intersects(rtlrect(nameleft, datetop, _datew, st::normalFont->height, _width))) { p.setFont(ClickHandler::showAsActive(_msgl) ? st::normalFont->underline() : st::normalFont); p.setPen(st::mediaInFg); p.drawTextLeft(nameleft, datetop, _width, _date, _datew); } const auto checkDelta = (isSong ? _st.songThumbSize : _st.fileThumbSize) + (isSong ? st::overviewCheckSkip : -st::overviewCheckSkip) - st::overviewSmallCheck.size; const auto checkLeft = (isSong ? _st.songPadding.left() : 0) + checkDelta; const auto checkTop = (isSong ? _st.songPadding.top() : (st::linksBorder + _st.filePadding.top())) + checkDelta; paintCheckbox(p, { checkLeft, checkTop }, selected, context); } TextState Document::getState( QPoint point, StateRequest request) const { const auto loaded = _data->loaded() || Local::willStickerImageLoad(_data->mediaKey()); const auto wthumb = withThumb(); if (_data->isAudioFile()) { const auto nameleft = _st.songPadding.left() + _st.songThumbSize + _st.songPadding.right(); const auto nameright = _st.songPadding.left(); const auto namewidth = std::min( _width - nameleft - nameright, _name.maxWidth()); const auto nametop = _st.songNameTop; const auto statustop = _st.songStatusTop; const auto inner = rtlrect( _st.songPadding.left(), _st.songPadding.top(), _st.songThumbSize, _st.songThumbSize, _width); if (inner.contains(point)) { const auto link = loaded ? _openl : (_data->loading() || _data->uploading()) ? _cancell : _openl; return { parent(), link }; } const auto namerect = rtlrect( nameleft, nametop, namewidth, st::semiboldFont->height, _width); if (namerect.contains(point) && !_data->loading()) { return { parent(), _namel }; } } else { const auto nameleft = _st.fileThumbSize + _st.filePadding.right(); const auto nameright = 0; const auto nametop = st::linksBorder + _st.fileNameTop; const auto namewidth = std::min( _width - nameleft - nameright, _name.maxWidth()); const auto statustop = st::linksBorder + _st.fileStatusTop; const auto datetop = st::linksBorder + _st.fileDateTop; const auto rthumb = rtlrect( 0, st::linksBorder + _st.filePadding.top(), _st.fileThumbSize, _st.fileThumbSize, _width); if (rthumb.contains(point)) { const auto link = loaded ? _openl : (_data->loading() || _data->uploading()) ? _cancell : _savel; return { parent(), link }; } if (_data->status != FileUploadFailed) { auto daterect = rtlrect( nameleft, datetop, _datew, st::normalFont->height, _width); if (daterect.contains(point)) { return { parent(), _msgl }; } } if (!_data->loading() && _data->isValid()) { auto leftofnamerect = rtlrect( 0, st::linksBorder, nameleft, _height - st::linksBorder, _width); if (loaded && leftofnamerect.contains(point)) { return { parent(), _namel }; } const auto namerect = rtlrect( nameleft, nametop, namewidth, st::semiboldFont->height, _width); if (namerect.contains(point)) { return { parent(), _namel }; } } } return {}; } const style::RoundCheckbox &Document::checkboxStyle() const { return st::overviewSmallCheck; } float64 Document::dataProgress() const { return _data->progress(); } bool Document::dataFinished() const { return !_data->loading(); } bool Document::dataLoaded() const { return _data->loaded(); } bool Document::iconAnimated() const { return _data->isAudioFile() || !_data->loaded() || (_radial && _radial->animating()); } bool Document::withThumb() const { return !_data->isAudioFile() && !_data->thumb->isNull() && _data->thumb->width() && _data->thumb->height() && !documentIsExecutableName(_data->filename()); } bool Document::updateStatusText() { bool showPause = false; int32 statusSize = 0, realDuration = 0; if (_data->status == FileDownloadFailed || _data->status == FileUploadFailed) { statusSize = FileStatusSizeFailed; } else if (_data->uploading()) { statusSize = _data->uploadingData->offset; } else if (_data->loading()) { statusSize = _data->loadOffset(); } else if (_data->loaded()) { if (_data->isAudioFile()) { statusSize = FileStatusSizeLoaded; using State = Media::Player::State; auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Song); if (state.id == AudioMsgId(_data, parent()->fullId()) && !Media::Player::IsStoppedOrStopping(state.state)) { statusSize = -1 - (state.position / state.frequency); realDuration = (state.length / state.frequency); showPause = (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting); } if (!showPause && (state.id == AudioMsgId(_data, parent()->fullId())) && Media::Player::instance()->isSeeking(AudioMsgId::Type::Song)) { showPause = true; } } else { statusSize = FileStatusSizeLoaded; } } else { statusSize = FileStatusSizeReady; } if (statusSize != _status.size()) { _status.update(statusSize, _data->size, _data->isSong() ? _data->song()->duration : -1, realDuration); } return showPause; } Link::Link( not_null<HistoryItem*> parent, Data::Media *media) : ItemBase(parent) { AddComponents(Info::Bit()); auto textWithEntities = parent->originalText(); QString mainUrl; auto text = textWithEntities.text; const auto &entities = textWithEntities.entities; int32 from = 0, till = text.size(), lnk = entities.size(); for (const auto &entity : entities) { auto type = entity.type(); if (type != EntityInTextUrl && type != EntityInTextCustomUrl && type != EntityInTextEmail) { continue; } const auto customUrl = entity.data(); const auto entityText = text.mid(entity.offset(), entity.length()); const auto url = customUrl.isEmpty() ? entityText : customUrl; if (_links.isEmpty()) { mainUrl = url; } _links.push_back(LinkEntry(url, entityText)); } while (lnk > 0 && till > from) { --lnk; auto &entity = entities.at(lnk); auto type = entity.type(); if (type != EntityInTextUrl && type != EntityInTextCustomUrl && type != EntityInTextEmail) { ++lnk; break; } int32 afterLinkStart = entity.offset() + entity.length(); if (till > afterLinkStart) { if (!QRegularExpression(qsl("^[,.\\s_=+\\-;:`'\"\\(\\)\\[\\]\\{\\}<>*&^%\\$#@!\\\\/]+$")).match(text.mid(afterLinkStart, till - afterLinkStart)).hasMatch()) { ++lnk; break; } } till = entity.offset(); } if (!lnk) { if (QRegularExpression(qsl("^[,.\\s\\-;:`'\"\\(\\)\\[\\]\\{\\}<>*&^%\\$#@!\\\\/]+$")).match(text.mid(from, till - from)).hasMatch()) { till = from; } } _page = media ? media->webpage() : nullptr; if (_page) { mainUrl = _page->url; if (_page->document) { _photol = std::make_shared<DocumentOpenClickHandler>( _page->document, parent->fullId()); } else if (_page->photo) { if (_page->type == WebPageProfile || _page->type == WebPageVideo) { _photol = std::make_shared<UrlClickHandler>(_page->url); } else if (_page->type == WebPagePhoto || _page->siteName == qstr("Twitter") || _page->siteName == qstr("Facebook")) { _photol = std::make_shared<PhotoOpenClickHandler>( _page->photo, parent->fullId()); } else { _photol = std::make_shared<UrlClickHandler>(_page->url); } } else { _photol = std::make_shared<UrlClickHandler>(_page->url); } } else if (!mainUrl.isEmpty()) { _photol = std::make_shared<UrlClickHandler>(mainUrl); } if (from >= till && _page) { text = _page->description.text; from = 0; till = text.size(); } if (till > from) { TextParseOptions opts = { TextParseMultiline, int32(st::linksMaxWidth), 3 * st::normalFont->height, Qt::LayoutDirectionAuto }; _text.setText(st::defaultTextStyle, text.mid(from, till - from), opts); } int32 tw = 0, th = 0; if (_page && _page->photo) { if (!_page->photo->loaded()) _page->photo->thumb->load(false, false); tw = convertScale(_page->photo->thumb->width()); th = convertScale(_page->photo->thumb->height()); } else if (_page && _page->document) { if (!_page->document->thumb->loaded()) _page->document->thumb->load(false, false); tw = convertScale(_page->document->thumb->width()); th = convertScale(_page->document->thumb->height()); } if (tw > st::linksPhotoSize) { if (th > tw) { th = th * st::linksPhotoSize / tw; tw = st::linksPhotoSize; } else if (th > st::linksPhotoSize) { tw = tw * st::linksPhotoSize / th; th = st::linksPhotoSize; } } _pixw = qMax(tw, 1); _pixh = qMax(th, 1); if (_page) { _title = _page->title; } #ifndef OS_MAC_OLD auto parts = mainUrl.splitRef('/'); #else // OS_MAC_OLD auto parts = mainUrl.split('/'); #endif // OS_MAC_OLD if (!parts.isEmpty()) { auto domain = parts.at(0); if (parts.size() > 2 && domain.endsWith(':') && parts.at(1).isEmpty()) { // http:// and others domain = parts.at(2); } parts = domain.split('@').back().split('.'); if (parts.size() > 1) { _letter = parts.at(parts.size() - 2).at(0).toUpper(); if (_title.isEmpty()) { _title.reserve(parts.at(parts.size() - 2).size()); _title.append(_letter).append(parts.at(parts.size() - 2).mid(1)); } } } _titlew = st::semiboldFont->width(_title); } void Link::initDimensions() { _maxw = st::linksMaxWidth; _minh = 0; if (!_title.isEmpty()) { _minh += st::semiboldFont->height; } if (!_text.isEmpty()) { _minh += qMin(3 * st::normalFont->height, _text.countHeight(_maxw - st::linksPhotoSize - st::linksPhotoPadding)); } _minh += _links.size() * st::normalFont->height; _minh = qMax(_minh, int32(st::linksPhotoSize)) + st::linksMargin.top() + st::linksMargin.bottom() + st::linksBorder; } int32 Link::resizeGetHeight(int32 width) { _width = qMin(width, _maxw); int32 w = _width - st::linksPhotoSize - st::linksPhotoPadding; for (int32 i = 0, l = _links.size(); i < l; ++i) { _links.at(i).lnk->setFullDisplayed(w >= _links.at(i).width); } _height = 0; if (!_title.isEmpty()) { _height += st::semiboldFont->height; } if (!_text.isEmpty()) { _height += qMin(3 * st::normalFont->height, _text.countHeight(_width - st::linksPhotoSize - st::linksPhotoPadding)); } _height += _links.size() * st::normalFont->height; _height = qMax(_height, int32(st::linksPhotoSize)) + st::linksMargin.top() + st::linksMargin.bottom() + st::linksBorder; return _height; } void Link::paint(Painter &p, const QRect &clip, TextSelection selection, const PaintContext *context) { auto selected = (selection == FullSelection); const auto pixLeft = 0; const auto pixTop = st::linksMargin.top() + st::linksBorder; if (clip.intersects(rtlrect(0, pixTop, st::linksPhotoSize, st::linksPhotoSize, _width))) { if (_page && _page->photo) { QPixmap pix; if (_page->photo->medium->loaded()) { pix = _page->photo->medium->pixSingle(_pixw, _pixh, st::linksPhotoSize, st::linksPhotoSize, ImageRoundRadius::Small); } else if (_page->photo->loaded()) { pix = _page->photo->full->pixSingle(_pixw, _pixh, st::linksPhotoSize, st::linksPhotoSize, ImageRoundRadius::Small); } else { pix = _page->photo->thumb->pixSingle(_pixw, _pixh, st::linksPhotoSize, st::linksPhotoSize, ImageRoundRadius::Small); } p.drawPixmapLeft(pixLeft, pixTop, _width, pix); } else if (_page && _page->document && !_page->document->thumb->isNull()) { auto roundRadius = _page->document->isVideoMessage() ? ImageRoundRadius::Ellipse : ImageRoundRadius::Small; p.drawPixmapLeft(pixLeft, pixTop, _width, _page->document->thumb->pixSingle(_pixw, _pixh, st::linksPhotoSize, st::linksPhotoSize, roundRadius)); } else { const auto index = _letter.isEmpty() ? 0 : (_letter[0].unicode() % 4); const auto fill = [&](style::color color, RoundCorners corners) { auto pixRect = rtlrect( pixLeft, pixTop, st::linksPhotoSize, st::linksPhotoSize, _width); App::roundRect(p, pixRect, color, corners); }; switch (index) { case 0: fill(st::msgFile1Bg, Doc1Corners); break; case 1: fill(st::msgFile2Bg, Doc2Corners); break; case 2: fill(st::msgFile3Bg, Doc3Corners); break; case 3: fill(st::msgFile4Bg, Doc4Corners); break; } if (!_letter.isEmpty()) { p.setFont(st::linksLetterFont); p.setPen(st::linksLetterFg); p.drawText(rtlrect(pixLeft, pixTop, st::linksPhotoSize, st::linksPhotoSize, _width), _letter, style::al_center); } } } const auto left = st::linksPhotoSize + st::linksPhotoPadding; const auto w = _width - left; auto top = [&] { if (!_title.isEmpty() && _text.isEmpty() && _links.size() == 1) { return pixTop + (st::linksPhotoSize - st::semiboldFont->height - st::normalFont->height) / 2; } return st::linksTextTop; }(); p.setPen(st::linksTextFg); p.setFont(st::semiboldFont); if (!_title.isEmpty()) { if (clip.intersects(rtlrect(left, top, qMin(w, _titlew), st::semiboldFont->height, _width))) { p.drawTextLeft(left, top, _width, (w < _titlew) ? st::semiboldFont->elided(_title, w) : _title); } top += st::semiboldFont->height; } p.setFont(st::msgFont); if (!_text.isEmpty()) { int32 h = qMin(st::normalFont->height * 3, _text.countHeight(w)); if (clip.intersects(rtlrect(left, top, w, h, _width))) { _text.drawLeftElided(p, left, top, w, _width, 3); } top += h; } p.setPen(st::windowActiveTextFg); for (int32 i = 0, l = _links.size(); i < l; ++i) { if (clip.intersects(rtlrect(left, top, qMin(w, _links.at(i).width), st::normalFont->height, _width))) { p.setFont(ClickHandler::showAsActive(_links.at(i).lnk) ? st::normalFont->underline() : st::normalFont); p.drawTextLeft(left, top, _width, (w < _links.at(i).width) ? st::normalFont->elided(_links.at(i).text, w) : _links.at(i).text); } top += st::normalFont->height; } QRect border(rtlrect(left, 0, w, st::linksBorder, _width)); if (!context->isAfterDate && clip.intersects(border)) { p.fillRect(clip.intersected(border), st::linksBorderFg); } const auto checkDelta = st::linksPhotoSize + st::overviewCheckSkip - st::overviewSmallCheck.size; const auto checkLeft = pixLeft + checkDelta; const auto checkTop = pixTop + checkDelta; paintCheckbox(p, { checkLeft, checkTop }, selected, context); } TextState Link::getState( QPoint point, StateRequest request) const { int32 left = st::linksPhotoSize + st::linksPhotoPadding, top = st::linksMargin.top() + st::linksBorder, w = _width - left; if (rtlrect(0, top, st::linksPhotoSize, st::linksPhotoSize, _width).contains(point)) { return { parent(), _photol }; } if (!_title.isEmpty() && _text.isEmpty() && _links.size() == 1) { top += (st::linksPhotoSize - st::semiboldFont->height - st::normalFont->height) / 2; } if (!_title.isEmpty()) { if (rtlrect(left, top, qMin(w, _titlew), st::semiboldFont->height, _width).contains(point)) { return { parent(), _photol }; } top += st::webPageTitleFont->height; } if (!_text.isEmpty()) { top += qMin(st::normalFont->height * 3, _text.countHeight(w)); } for (int32 i = 0, l = _links.size(); i < l; ++i) { if (rtlrect(left, top, qMin(w, _links.at(i).width), st::normalFont->height, _width).contains(point)) { return { parent(), ClickHandlerPtr(_links[i].lnk) }; } top += st::normalFont->height; } return {}; } const style::RoundCheckbox &Link::checkboxStyle() const { return st::overviewSmallCheck; } Link::LinkEntry::LinkEntry(const QString &url, const QString &text) : text(text) , width(st::normalFont->width(text)) , lnk(std::make_shared<UrlClickHandler>(url)) { } } // namespace Layout } // namespace Overview ����������������������������������tdesktop-1.2.17/Telegram/SourceFiles/overview/overview_layout.h�������������������������������������0000664�0000000�0000000�00000020124�13262451251�0024704�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "layout.h" #include "core/click_handler_types.h" #include "ui/effects/radial_animation.h" #include "styles/style_overview.h" class HistoryMedia; namespace style { struct RoundCheckbox; } // namespace style namespace Data { class Media; } // namespace Data namespace Overview { namespace Layout { class Checkbox; class PaintContext : public PaintContextBase { public: PaintContext(TimeMs ms, bool selecting) : PaintContextBase(ms, selecting), isAfterDate(false) { } bool isAfterDate; }; class ItemBase; class AbstractItem : public LayoutItemBase { public: virtual void paint(Painter &p, const QRect &clip, TextSelection selection, const PaintContext *context) = 0; virtual ItemBase *toMediaItem() { return nullptr; } virtual const ItemBase *toMediaItem() const { return nullptr; } virtual HistoryItem *getItem() const { return nullptr; } virtual DocumentData *getDocument() const { return nullptr; } MsgId msgId() const; virtual void invalidateCache() { } }; class ItemBase : public AbstractItem { public: ItemBase(not_null<HistoryItem*> parent); QDateTime dateTime() const; void setPosition(int position) { _position = position; } int position() const { return _position; } ItemBase *toMediaItem() final override { return this; } const ItemBase *toMediaItem() const final override { return this; } HistoryItem *getItem() const final override { return _parent; } void clickHandlerActiveChanged(const ClickHandlerPtr &action, bool active) override; void clickHandlerPressedChanged(const ClickHandlerPtr &action, bool pressed) override; void invalidateCache() override; ~ItemBase(); protected: not_null<HistoryItem*> parent() const { return _parent; } void paintCheckbox( Painter &p, QPoint position, bool selected, const PaintContext *context); virtual const style::RoundCheckbox &checkboxStyle() const; private: void ensureCheckboxCreated(); int _position = 0; const not_null<HistoryItem*> _parent; const QDateTime _dateTime; std::unique_ptr<Checkbox> _check; }; class RadialProgressItem : public ItemBase { public: RadialProgressItem(not_null<HistoryItem*> parent) : ItemBase(parent) { } RadialProgressItem(const RadialProgressItem &other) = delete; void clickHandlerActiveChanged(const ClickHandlerPtr &action, bool active) override; ~RadialProgressItem(); protected: ClickHandlerPtr _openl, _savel, _cancell; void setLinks( ClickHandlerPtr &&openl, ClickHandlerPtr &&savel, ClickHandlerPtr &&cancell); void setDocumentLinks(not_null<DocumentData*> document); void step_radial(TimeMs ms, bool timer); void ensureRadial(); void checkRadialFinished(); bool isRadialAnimation(TimeMs ms) const { if (!_radial || !_radial->animating()) return false; _radial->step(ms); return _radial && _radial->animating(); } virtual float64 dataProgress() const = 0; virtual bool dataFinished() const = 0; virtual bool dataLoaded() const = 0; virtual bool iconAnimated() const { return false; } std::unique_ptr<Ui::RadialAnimation> _radial; Animation _a_iconOver; }; class StatusText { public: // duration = -1 - no duration, duration = -2 - "GIF" duration void update(int newSize, int fullSize, int duration, TimeMs realDuration); void setSize(int newSize); int size() const { return _size; } QString text() const { return _text; } private: // >= 0 will contain download / upload string, _size = loaded bytes // < 0 will contain played string, _size = -(seconds + 1) played // 0x7FFFFFF0 will contain status for not yet downloaded file // 0x7FFFFFF1 will contain status for already downloaded file // 0x7FFFFFF2 will contain status for failed to download / upload file int _size = 0; QString _text; }; struct Info : public RuntimeComponent<Info, LayoutItemBase> { int top = 0; }; class Date : public AbstractItem { public: Date(const QDate &date, bool month); void initDimensions() override; void paint(Painter &p, const QRect &clip, TextSelection selection, const PaintContext *context) override; private: QDate _date; QString _text; }; class Photo : public ItemBase { public: Photo( not_null<HistoryItem*> parent, not_null<PhotoData*> photo); void initDimensions() override; int32 resizeGetHeight(int32 width) override; void paint(Painter &p, const QRect &clip, TextSelection selection, const PaintContext *context) override; TextState getState( QPoint point, StateRequest request) const override; private: not_null<PhotoData*> _data; ClickHandlerPtr _link; QPixmap _pix; bool _goodLoaded = false; }; class Video : public RadialProgressItem { public: Video( not_null<HistoryItem*> parent, not_null<DocumentData*> video); void initDimensions() override; int32 resizeGetHeight(int32 width) override; void paint(Painter &p, const QRect &clip, TextSelection selection, const PaintContext *context) override; TextState getState( QPoint point, StateRequest request) const override; protected: float64 dataProgress() const override; bool dataFinished() const override; bool dataLoaded() const override; bool iconAnimated() const override; private: not_null<DocumentData*> _data; StatusText _status; QString _duration; QPixmap _pix; bool _thumbLoaded = false; void updateStatusText(); }; class Voice : public RadialProgressItem { public: Voice( not_null<HistoryItem*> parent, not_null<DocumentData*> voice, const style::OverviewFileLayout &st); void initDimensions() override; void paint(Painter &p, const QRect &clip, TextSelection selection, const PaintContext *context) override; TextState getState( QPoint point, StateRequest request) const override; protected: float64 dataProgress() const override; bool dataFinished() const override; bool dataLoaded() const override; bool iconAnimated() const override; const style::RoundCheckbox &checkboxStyle() const override; private: not_null<DocumentData*> _data; StatusText _status; ClickHandlerPtr _namel; const style::OverviewFileLayout &_st; Text _name, _details; int _nameVersion; void updateName(); bool updateStatusText(); }; class Document : public RadialProgressItem { public: Document( not_null<HistoryItem*> parent, not_null<DocumentData*> document, const style::OverviewFileLayout &st); void initDimensions() override; void paint(Painter &p, const QRect &clip, TextSelection selection, const PaintContext *context) override; TextState getState( QPoint point, StateRequest request) const override; virtual DocumentData *getDocument() const override { return _data; } protected: float64 dataProgress() const override; bool dataFinished() const override; bool dataLoaded() const override; bool iconAnimated() const override; const style::RoundCheckbox &checkboxStyle() const override; private: not_null<DocumentData*> _data; StatusText _status; ClickHandlerPtr _msgl, _namel; const style::OverviewFileLayout &_st; bool _thumbForLoaded = false; QPixmap _thumb; Text _name; QString _date, _ext; int32 _datew, _extw; int32 _thumbw, _colorIndex; bool withThumb() const; bool updateStatusText(); }; class Link : public ItemBase { public: Link( not_null<HistoryItem*> parent, Data::Media *media); void initDimensions() override; int32 resizeGetHeight(int32 width) override; void paint(Painter &p, const QRect &clip, TextSelection selection, const PaintContext *context) override; TextState getState( QPoint point, StateRequest request) const override; protected: const style::RoundCheckbox &checkboxStyle() const override; private: ClickHandlerPtr _photol; QString _title, _letter; int _titlew = 0; WebPageData *_page = nullptr; int _pixw = 0; int _pixh = 0; Text _text = { int(st::msgMinWidth) }; struct LinkEntry { LinkEntry() : width(0) { } LinkEntry(const QString &url, const QString &text); QString text; int32 width; std::shared_ptr<TextClickHandler> lnk; }; QVector<LinkEntry> _links; }; } // namespace Layout } // namespace Overview ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/passcodewidget.cpp���������������������������������������������0000664�0000000�0000000�00000012030�13262451251�0023130�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "passcodewidget.h" #include "lang/lang_keys.h" #include "storage/localstorage.h" #include "mainwindow.h" #include "messenger.h" #include "ui/text/text.h" #include "ui/widgets/buttons.h" #include "ui/widgets/input_fields.h" #include "styles/style_boxes.h" #include "window/window_slide_animation.h" #include "window/window_controller.h" #include "auth_session.h" PasscodeWidget::PasscodeWidget(QWidget *parent) : TWidget(parent) , _passcode(this, st::passcodeInput, langFactory(lng_passcode_ph)) , _submit(this, langFactory(lng_passcode_submit), st::passcodeSubmit) , _logout(this, lang(lng_passcode_logout)) { connect(_passcode, SIGNAL(changed()), this, SLOT(onChanged())); connect(_passcode, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); _submit->setClickedCallback([this] { onSubmit(); }); _logout->setClickedCallback([] { App::wnd()->onLogout(); }); show(); } void PasscodeWidget::onSubmit() { if (_passcode->text().isEmpty()) { _passcode->showError(); return; } if (!passcodeCanTry()) { _error = lang(lng_flood_error); _passcode->showError(); update(); return; } if (App::main()) { if (Local::checkPasscode(_passcode->text().toUtf8())) { Messenger::Instance().clearPasscode(); // Destroys this widget. return; } else { cSetPasscodeBadTries(cPasscodeBadTries() + 1); cSetPasscodeLastTry(getms(true)); onError(); return; } } else { if (Local::readMap(_passcode->text().toUtf8()) != Local::ReadMapPassNeeded) { cSetPasscodeBadTries(0); Messenger::Instance().startMtp(); if (AuthSession::Exists()) { App::wnd()->setupMain(); } else { App::wnd()->setupIntro(); } } else { cSetPasscodeBadTries(cPasscodeBadTries() + 1); cSetPasscodeLastTry(getms(true)); onError(); return; } } } void PasscodeWidget::onError() { _error = lang(lng_passcode_wrong); _passcode->selectAll(); _passcode->showError(); update(); } void PasscodeWidget::onChanged() { if (!_error.isEmpty()) { _error = QString(); update(); } } void PasscodeWidget::showAnimated(const QPixmap &bgAnimCache, bool back) { _showBack = back; (_showBack ? _cacheOver : _cacheUnder) = bgAnimCache; _a_show.finish(); showAll(); setInnerFocus(); _passcode->finishAnimating(); (_showBack ? _cacheUnder : _cacheOver) = Ui::GrabWidget(this); hideAll(); _a_show.start( [this] { animationCallback(); }, 0., 1., st::slideDuration, Window::SlideAnimation::transition()); show(); } void PasscodeWidget::animationCallback() { update(); if (!_a_show.animating()) { showAll(); if (App::wnd()) App::wnd()->setInnerFocus(); Ui::showChatsList(); _cacheUnder = _cacheOver = QPixmap(); } } void PasscodeWidget::showAll() { _passcode->show(); _submit->show(); _logout->show(); } void PasscodeWidget::hideAll() { _passcode->hide(); _submit->hide(); _logout->hide(); } void PasscodeWidget::paintEvent(QPaintEvent *e) { bool trivial = (rect() == e->rect()); setMouseTracking(true); Painter p(this); if (!trivial) { p.setClipRect(e->rect()); } auto progress = _a_show.current(getms(), 1.); if (_a_show.animating()) { auto coordUnder = _showBack ? anim::interpolate(-st::slideShift, 0, progress) : anim::interpolate(0, -st::slideShift, progress); auto coordOver = _showBack ? anim::interpolate(0, width(), progress) : anim::interpolate(width(), 0, progress); auto shadow = _showBack ? (1. - progress) : progress; if (coordOver > 0) { p.drawPixmap(QRect(0, 0, coordOver, height()), _cacheUnder, QRect(-coordUnder * cRetinaFactor(), 0, coordOver * cRetinaFactor(), height() * cRetinaFactor())); p.setOpacity(shadow); p.fillRect(0, 0, coordOver, height(), st::slideFadeOutBg); p.setOpacity(1); } p.drawPixmap(coordOver, 0, _cacheOver); p.setOpacity(shadow); st::slideShadow.fill(p, QRect(coordOver - st::slideShadow.width(), 0, st::slideShadow.width(), height())); } else { p.fillRect(rect(), st::windowBg); p.setFont(st::passcodeHeaderFont); p.setPen(st::windowFg); p.drawText(QRect(0, _passcode->y() - st::passcodeHeaderHeight, width(), st::passcodeHeaderHeight), lang(lng_passcode_enter), style::al_center); if (!_error.isEmpty()) { p.setFont(st::boxTextFont); p.setPen(st::boxTextFgError); p.drawText(QRect(0, _passcode->y() + _passcode->height(), width(), st::passcodeSubmitSkip), _error, style::al_center); } } } void PasscodeWidget::resizeEvent(QResizeEvent *e) { _passcode->move((width() - _passcode->width()) / 2, (height() / 3)); _submit->move(_passcode->x(), _passcode->y() + _passcode->height() + st::passcodeSubmitSkip); _logout->move(_passcode->x() + (_passcode->width() - _logout->width()) / 2, _submit->y() + _submit->height() + st::linkFont->ascent); } void PasscodeWidget::setInnerFocus() { if (auto controller = App::wnd()->controller()) { controller->dialogsListFocused().set(false, true); } _passcode->setFocusFast(); } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/passcodewidget.h�����������������������������������������������0000664�0000000�0000000�00000001737�13262451251�0022611�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace Ui { class PasswordInput; class LinkButton; class RoundButton; } // namespace Ui class PasscodeWidget : public TWidget { Q_OBJECT public: PasscodeWidget(QWidget *parent); void setInnerFocus(); void showAnimated(const QPixmap &bgAnimCache, bool back = false); protected: void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; public slots: void onError(); void onChanged(); void onSubmit(); private: void animationCallback(); void showAll(); void hideAll(); Animation _a_show; bool _showBack = false; QPixmap _cacheUnder, _cacheOver; object_ptr<Ui::PasswordInput> _passcode; object_ptr<Ui::RoundButton> _submit; object_ptr<Ui::LinkButton> _logout; QString _error; }; ���������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/������������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0021247�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/linux/������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0022406�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp������������������������0000664�0000000�0000000�00000051572�13262451251�0027355�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "platform/linux/file_utilities_linux.h" #include <private/qguiapplication_p.h> #include "platform/linux/linux_libs.h" #include "platform/linux/linux_gdk_helper.h" #include "messenger.h" #include "mainwindow.h" #include "storage/localstorage.h" QStringList qt_make_filter_list(const QString &filter); namespace Platform { namespace File { namespace internal { QByteArray EscapeShell(const QByteArray &content) { auto result = QByteArray(); auto b = content.constData(), e = content.constEnd(); for (auto ch = b; ch != e; ++ch) { if (*ch == ' ' || *ch == '"' || *ch == '\'' || *ch == '\\') { if (result.isEmpty()) { result.reserve(content.size() * 2); } if (ch > b) { result.append(b, ch - b); } result.append('\\'); b = ch; } } if (result.isEmpty()) { return content; } if (e > b) { result.append(b, e - b); } return result; } } // namespace internal void UnsafeShowInFolder(const QString &filepath) { Ui::hideLayer(anim::type::instant); // Hide mediaview to make other apps visible. auto absolutePath = QFileInfo(filepath).absoluteFilePath(); QProcess process; process.start("xdg-mime", QStringList() << "query" << "default" << "inode/directory"); process.waitForFinished(); auto output = QString::fromLatin1(process.readLine().simplified()); auto command = qsl("xdg-open"); auto arguments = QStringList(); if (output == qstr("dolphin.desktop") || output == qstr("org.kde.dolphin.desktop")) { command = qsl("dolphin"); arguments << "--select" << absolutePath; } else if (output == qstr("nautilus.desktop") || output == qstr("org.gnome.Nautilus.desktop") || output == qstr("nautilus-folder-handler.desktop")) { command = qsl("nautilus"); arguments << "--no-desktop" << absolutePath; } else if (output == qstr("nemo.desktop")) { command = qsl("nemo"); arguments << "--no-desktop" << absolutePath; } else if (output == qstr("konqueror.desktop") || output == qstr("kfmclient_dir.desktop")) { command = qsl("konqueror"); arguments << "--select" << absolutePath; } else { arguments << QFileInfo(filepath).absoluteDir().absolutePath(); } if (!process.startDetached(command, arguments)) { LOG(("Failed to launch '%1 %2'").arg(command).arg(arguments.join(' '))); } } } // namespace File namespace FileDialog { namespace { #ifndef TDESKTOP_DISABLE_GTK_INTEGRATION // GTK file chooser image preview: thanks to Chromium // The size of the preview we display for selected image files. We set height // larger than width because generally there is more free space vertically // than horiztonally (setting the preview image will alway expand the width of // the dialog, but usually not the height). The image's aspect ratio will always // be preserved. constexpr auto kPreviewWidth = 256; constexpr auto kPreviewHeight = 512; #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION using Type = ::FileDialog::internal::Type; #ifndef TDESKTOP_DISABLE_GTK_INTEGRATION bool NativeSupported() { return Platform::internal::GdkHelperLoaded() && (Libs::gtk_widget_hide_on_delete != nullptr) && (Libs::gtk_clipboard_store != nullptr) && (Libs::gtk_clipboard_get != nullptr) && (Libs::gtk_widget_destroy != nullptr) && (Libs::gtk_dialog_get_type != nullptr) && (Libs::gtk_dialog_run != nullptr) && (Libs::gtk_widget_realize != nullptr) && (Libs::gdk_window_set_modal_hint != nullptr) && (Libs::gtk_widget_show != nullptr) && (Libs::gdk_window_focus != nullptr) && (Libs::gtk_widget_hide != nullptr) && (Libs::gtk_widget_hide_on_delete != nullptr) && (Libs::gtk_file_chooser_dialog_new != nullptr) && (Libs::gtk_file_chooser_get_type != nullptr) && (Libs::gtk_file_chooser_set_current_folder != nullptr) && (Libs::gtk_file_chooser_get_current_folder != nullptr) && (Libs::gtk_file_chooser_set_current_name != nullptr) && (Libs::gtk_file_chooser_select_filename != nullptr) && (Libs::gtk_file_chooser_get_filenames != nullptr) && (Libs::gtk_file_chooser_set_filter != nullptr) && (Libs::gtk_file_chooser_get_filter != nullptr) && (Libs::gtk_window_get_type != nullptr) && (Libs::gtk_window_set_title != nullptr) && (Libs::gtk_file_chooser_set_local_only != nullptr) && (Libs::gtk_file_chooser_set_action != nullptr) && (Libs::gtk_file_chooser_set_select_multiple != nullptr) && (Libs::gtk_file_chooser_set_do_overwrite_confirmation != nullptr) && (Libs::gtk_file_chooser_remove_filter != nullptr) && (Libs::gtk_file_filter_set_name != nullptr) && (Libs::gtk_file_filter_add_pattern != nullptr) && (Libs::gtk_file_chooser_add_filter != nullptr) && (Libs::gtk_file_filter_new != nullptr); } bool PreviewSupported() { return NativeSupported() && (Libs::gdk_pixbuf_new_from_file_at_size != nullptr); } bool GetNative(QStringList &files, QByteArray &remoteContent, const QString &caption, const QString &filter, Type type, QString startFile) { auto parent = Messenger::Instance().getFileDialogParent(); internal::GtkFileDialog dialog(parent, caption, QString(), filter); dialog.setModal(true); if (type == Type::ReadFile || type == Type::ReadFiles) { dialog.setFileMode((type == Type::ReadFiles) ? QFileDialog::ExistingFiles : QFileDialog::ExistingFile); dialog.setAcceptMode(QFileDialog::AcceptOpen); } else if (type == Type::ReadFolder) { dialog.setAcceptMode(QFileDialog::AcceptOpen); dialog.setFileMode(QFileDialog::Directory); dialog.setOption(QFileDialog::ShowDirsOnly); } else { dialog.setFileMode(QFileDialog::AnyFile); dialog.setAcceptMode(QFileDialog::AcceptSave); } if (startFile.isEmpty() || startFile.at(0) != '/') { startFile = cDialogLastPath() + '/' + startFile; } dialog.selectFile(startFile); int res = dialog.exec(); QString path = dialog.directory().absolutePath(); if (path != cDialogLastPath()) { cSetDialogLastPath(path); Local::writeUserSettings(); } if (res == QDialog::Accepted) { if (type == Type::ReadFiles) { files = dialog.selectedFiles(); } else { files = dialog.selectedFiles().mid(0, 1); } return true; } files = QStringList(); remoteContent = QByteArray(); return false; } #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION } // namespace bool Get(QStringList &files, QByteArray &remoteContent, const QString &caption, const QString &filter, Type type, QString startFile) { #ifndef TDESKTOP_DISABLE_GTK_INTEGRATION if (NativeSupported()) { return GetNative(files, remoteContent, caption, filter, type, startFile); } #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION return ::FileDialog::internal::GetDefault(files, remoteContent, caption, filter, type, startFile); } #ifndef TDESKTOP_DISABLE_GTK_INTEGRATION namespace internal { QGtkDialog::QGtkDialog(GtkWidget *gtkWidget) : gtkWidget(gtkWidget) { Libs::g_signal_connect_swapped_helper(Libs::g_object_cast(gtkWidget), "response", GCallback(onResponse), this); Libs::g_signal_connect_helper(Libs::g_object_cast(gtkWidget), "delete-event", GCallback(Libs::gtk_widget_hide_on_delete), nullptr); if (PreviewSupported()) { _preview = Libs::gtk_image_new(); Libs::g_signal_connect_swapped_helper(Libs::g_object_cast(gtkWidget), "update-preview", GCallback(onUpdatePreview), this); Libs::gtk_file_chooser_set_preview_widget(Libs::gtk_file_chooser_cast(gtkWidget), _preview); } } QGtkDialog::~QGtkDialog() { Libs::gtk_clipboard_store(Libs::gtk_clipboard_get(GDK_SELECTION_CLIPBOARD)); Libs::gtk_widget_destroy(gtkWidget); } GtkDialog *QGtkDialog::gtkDialog() const { return Libs::gtk_dialog_cast(gtkWidget); } void QGtkDialog::exec() { if (auto w = App::wnd()) { w->reActivateWindow(); } if (modality() == Qt::ApplicationModal) { // block input to the whole app, including other GTK dialogs Libs::gtk_dialog_run(gtkDialog()); } else { // block input to the window, allow input to other GTK dialogs QEventLoop loop; connect(this, SIGNAL(accept()), &loop, SLOT(quit())); connect(this, SIGNAL(reject()), &loop, SLOT(quit())); loop.exec(); } } void QGtkDialog::show(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent) { connect(parent, &QWindow::destroyed, this, &QGtkDialog::onParentWindowDestroyed, Qt::UniqueConnection); setParent(parent); setFlags(flags); setModality(modality); Libs::gtk_widget_realize(gtkWidget); // creates X window if (parent) { Platform::internal::XSetTransientForHint(Libs::gtk_widget_get_window(gtkWidget), parent->winId()); } if (modality != Qt::NonModal) { Libs::gdk_window_set_modal_hint(Libs::gtk_widget_get_window(gtkWidget), true); QGuiApplicationPrivate::showModalWindow(this); } Libs::gtk_widget_show(gtkWidget); Libs::gdk_window_focus(Libs::gtk_widget_get_window(gtkWidget), 0); } void QGtkDialog::hide() { QGuiApplicationPrivate::hideModalWindow(this); Libs::gtk_widget_hide(gtkWidget); } void QGtkDialog::onResponse(QGtkDialog *dialog, int response) { if (response == GTK_RESPONSE_OK) emit dialog->accept(); else emit dialog->reject(); } void QGtkDialog::onUpdatePreview(QGtkDialog* dialog) { auto filename = Libs::gtk_file_chooser_get_preview_filename(Libs::gtk_file_chooser_cast(dialog->gtkWidget)); if (!filename) { Libs::gtk_file_chooser_set_preview_widget_active(Libs::gtk_file_chooser_cast(dialog->gtkWidget), false); return; } // Don't attempt to open anything which isn't a regular file. If a named pipe, // this may hang. See https://crbug.com/534754. struct stat stat_buf; if (stat(filename, &stat_buf) != 0 || !S_ISREG(stat_buf.st_mode)) { Libs::g_free(filename); Libs::gtk_file_chooser_set_preview_widget_active(Libs::gtk_file_chooser_cast(dialog->gtkWidget), false); return; } // This will preserve the image's aspect ratio. auto pixbuf = Libs::gdk_pixbuf_new_from_file_at_size(filename, kPreviewWidth, kPreviewHeight, nullptr); Libs::g_free(filename); if (pixbuf) { Libs::gtk_image_set_from_pixbuf(Libs::gtk_image_cast(dialog->_preview), pixbuf); Libs::g_object_unref(pixbuf); } Libs::gtk_file_chooser_set_preview_widget_active(Libs::gtk_file_chooser_cast(dialog->gtkWidget), pixbuf ? true : false); } void QGtkDialog::onParentWindowDestroyed() { // The Gtk*DialogHelper classes own this object. Make sure the parent doesn't delete it. setParent(nullptr); } #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION namespace { const char *filterRegExp = "^(.*)\\(([a-zA-Z0-9_.,*? +;#\\-\\[\\]@\\{\\}/!<>\\$%&=^~:\\|]*)\\)$"; // Makes a list of filters from a normal filter string "Image Files (*.png *.jpg)" QStringList cleanFilterList(const QString &filter) { QRegExp regexp(QString::fromLatin1(filterRegExp)); Q_ASSERT(regexp.isValid()); QString f = filter; int i = regexp.indexIn(f); if (i >= 0) f = regexp.cap(2); return f.split(QLatin1Char(' '), QString::SkipEmptyParts); } } // namespace #ifndef TDESKTOP_DISABLE_GTK_INTEGRATION GtkFileDialog::GtkFileDialog(QWidget *parent, const QString &caption, const QString &directory, const QString &filter) : QDialog(parent) , _windowTitle(caption) , _initialDirectory(directory) { auto filters = qt_make_filter_list(filter); const int numFilters = filters.count(); _nameFilters.reserve(numFilters); for (int i = 0; i < numFilters; ++i) { _nameFilters << filters[i].simplified(); } d.reset(new QGtkDialog(Libs::gtk_file_chooser_dialog_new("", nullptr, GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_OK, NULL))); connect(d.data(), SIGNAL(accept()), this, SLOT(onAccepted())); connect(d.data(), SIGNAL(reject()), this, SLOT(onRejected())); Libs::g_signal_connect_helper(Libs::gtk_file_chooser_cast(d->gtkDialog()), "selection-changed", G_CALLBACK(onSelectionChanged), this); Libs::g_signal_connect_swapped_helper(Libs::gtk_file_chooser_cast(d->gtkDialog()), "current-folder-changed", G_CALLBACK(onCurrentFolderChanged), this); } GtkFileDialog::~GtkFileDialog() { } void GtkFileDialog::showHelper(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent) { _dir.clear(); _selection.clear(); applyOptions(); return d->show(flags, modality, parent); } void GtkFileDialog::setVisible(bool visible) { if (visible) { if (testAttribute(Qt::WA_WState_ExplicitShowHide) && !testAttribute(Qt::WA_WState_Hidden)) { return; } } else if (testAttribute(Qt::WA_WState_ExplicitShowHide) && testAttribute(Qt::WA_WState_Hidden)) { return; } if (visible) { showHelper(windowFlags(), windowModality(), parentWidget() ? parentWidget()->windowHandle() : nullptr); } else { hideHelper(); } // Set WA_DontShowOnScreen so that QDialog::setVisible(visible) below // updates the state correctly, but skips showing the non-native version: setAttribute(Qt::WA_DontShowOnScreen); QDialog::setVisible(visible); } int GtkFileDialog::exec() { d->setModality(windowModality()); bool deleteOnClose = testAttribute(Qt::WA_DeleteOnClose); setAttribute(Qt::WA_DeleteOnClose, false); bool wasShowModal = testAttribute(Qt::WA_ShowModal); setAttribute(Qt::WA_ShowModal, true); setResult(0); show(); QPointer<QDialog> guard = this; d->exec(); if (guard.isNull()) return QDialog::Rejected; setAttribute(Qt::WA_ShowModal, wasShowModal); return result(); } void GtkFileDialog::hideHelper() { // After GtkFileChooserDialog has been hidden, gtk_file_chooser_get_current_folder() // & gtk_file_chooser_get_filenames() will return bogus values -> cache the actual // values before hiding the dialog _dir = directory().absolutePath(); _selection = selectedFiles(); d->hide(); } bool GtkFileDialog::defaultNameFilterDisables() const { return false; } void GtkFileDialog::setDirectory(const QString &directory) { GtkDialog *gtkDialog = d->gtkDialog(); Libs::gtk_file_chooser_set_current_folder(Libs::gtk_file_chooser_cast(gtkDialog), directory.toUtf8()); } QDir GtkFileDialog::directory() const { // While GtkFileChooserDialog is hidden, gtk_file_chooser_get_current_folder() // returns a bogus value -> return the cached value before hiding if (!_dir.isEmpty()) return _dir; QString ret; GtkDialog *gtkDialog = d->gtkDialog(); gchar *folder = Libs::gtk_file_chooser_get_current_folder(Libs::gtk_file_chooser_cast(gtkDialog)); if (folder) { ret = QString::fromUtf8(folder); Libs::g_free(folder); } return QDir(ret); } void GtkFileDialog::selectFile(const QString &filename) { _initialFiles.clear(); _initialFiles.append(filename); } QStringList GtkFileDialog::selectedFiles() const { // While GtkFileChooserDialog is hidden, gtk_file_chooser_get_filenames() // returns a bogus value -> return the cached value before hiding if (!_selection.isEmpty()) return _selection; QStringList selection; GtkDialog *gtkDialog = d->gtkDialog(); GSList *filenames = Libs::gtk_file_chooser_get_filenames(Libs::gtk_file_chooser_cast(gtkDialog)); for (GSList *it = filenames; it; it = it->next) selection += QString::fromUtf8((const char*)it->data); Libs::g_slist_free(filenames); return selection; } void GtkFileDialog::setFilter() { applyOptions(); } void GtkFileDialog::selectNameFilter(const QString &filter) { GtkFileFilter *gtkFilter = _filters.value(filter); if (gtkFilter) { GtkDialog *gtkDialog = d->gtkDialog(); Libs::gtk_file_chooser_set_filter(Libs::gtk_file_chooser_cast(gtkDialog), gtkFilter); } } QString GtkFileDialog::selectedNameFilter() const { GtkDialog *gtkDialog = d->gtkDialog(); GtkFileFilter *gtkFilter = Libs::gtk_file_chooser_get_filter(Libs::gtk_file_chooser_cast(gtkDialog)); return _filterNames.value(gtkFilter); } void GtkFileDialog::onAccepted() { emit accept(); // QString filter = selectedNameFilter(); // if (filter.isEmpty()) // emit filterSelected(filter); // QList<QUrl> files = selectedFiles(); // emit filesSelected(files); // if (files.count() == 1) // emit fileSelected(files.first()); } void GtkFileDialog::onRejected() { emit reject(); // } void GtkFileDialog::onSelectionChanged(GtkDialog *gtkDialog, GtkFileDialog *helper) { // QString selection; // gchar *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(gtkDialog)); // if (filename) { // selection = QString::fromUtf8(filename); // g_free(filename); // } // emit helper->currentChanged(QUrl::fromLocalFile(selection)); } void GtkFileDialog::onCurrentFolderChanged(GtkFileDialog *dialog) { // emit dialog->directoryEntered(dialog->directory()); } GtkFileChooserAction gtkFileChooserAction(QFileDialog::FileMode fileMode, QFileDialog::AcceptMode acceptMode) { switch (fileMode) { case QFileDialog::AnyFile: case QFileDialog::ExistingFile: case QFileDialog::ExistingFiles: if (acceptMode == QFileDialog::AcceptOpen) return GTK_FILE_CHOOSER_ACTION_OPEN; else return GTK_FILE_CHOOSER_ACTION_SAVE; case QFileDialog::Directory: case QFileDialog::DirectoryOnly: default: if (acceptMode == QFileDialog::AcceptOpen) return GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER; else return GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER; } } bool CustomButtonsSupported() { return (Libs::gtk_dialog_get_widget_for_response != nullptr) && (Libs::gtk_button_set_label != nullptr) && (Libs::gtk_button_get_type != nullptr); } void GtkFileDialog::applyOptions() { GtkDialog *gtkDialog = d->gtkDialog(); Libs::gtk_window_set_title(Libs::gtk_window_cast(gtkDialog), _windowTitle.toUtf8()); Libs::gtk_file_chooser_set_local_only(Libs::gtk_file_chooser_cast(gtkDialog), true); const GtkFileChooserAction action = gtkFileChooserAction(_fileMode, _acceptMode); Libs::gtk_file_chooser_set_action(Libs::gtk_file_chooser_cast(gtkDialog), action); const bool selectMultiple = (_fileMode == QFileDialog::ExistingFiles); Libs::gtk_file_chooser_set_select_multiple(Libs::gtk_file_chooser_cast(gtkDialog), selectMultiple); const bool confirmOverwrite = !_options.testFlag(QFileDialog::DontConfirmOverwrite); Libs::gtk_file_chooser_set_do_overwrite_confirmation(Libs::gtk_file_chooser_cast(gtkDialog), confirmOverwrite); if (!_nameFilters.isEmpty()) setNameFilters(_nameFilters); if (!_initialDirectory.isEmpty()) setDirectory(_initialDirectory); for_const (const auto &filename, _initialFiles) { if (_acceptMode == QFileDialog::AcceptSave) { QFileInfo fi(filename); Libs::gtk_file_chooser_set_current_folder(Libs::gtk_file_chooser_cast(gtkDialog), fi.path().toUtf8()); Libs::gtk_file_chooser_set_current_name(Libs::gtk_file_chooser_cast(gtkDialog), fi.fileName().toUtf8()); } else if (filename.endsWith('/')) { Libs::gtk_file_chooser_set_current_folder(Libs::gtk_file_chooser_cast(gtkDialog), filename.toUtf8()); } else { Libs::gtk_file_chooser_select_filename(Libs::gtk_file_chooser_cast(gtkDialog), filename.toUtf8()); } } const QString initialNameFilter = _nameFilters.isEmpty() ? QString() : _nameFilters.front(); if (!initialNameFilter.isEmpty()) selectNameFilter(initialNameFilter); if (CustomButtonsSupported()) { GtkWidget *acceptButton = Libs::gtk_dialog_get_widget_for_response(gtkDialog, GTK_RESPONSE_OK); if (acceptButton) { /*if (opts->isLabelExplicitlySet(QFileDialogOptions::Accept)) Libs::gtk_button_set_label(Libs::gtk_button_cast(acceptButton), opts->labelText(QFileDialogOptions::Accept).toUtf8()); else*/ if (_acceptMode == QFileDialog::AcceptOpen) Libs::gtk_button_set_label(Libs::gtk_button_cast(acceptButton), GTK_STOCK_OPEN); else Libs::gtk_button_set_label(Libs::gtk_button_cast(acceptButton), GTK_STOCK_SAVE); } GtkWidget *rejectButton = Libs::gtk_dialog_get_widget_for_response(gtkDialog, GTK_RESPONSE_CANCEL); if (rejectButton) { /*if (opts->isLabelExplicitlySet(QFileDialogOptions::Reject)) Libs::gtk_button_set_label(Libs::gtk_button_cast(rejectButton), opts->labelText(QFileDialogOptions::Reject).toUtf8()); else*/ Libs::gtk_button_set_label(Libs::gtk_button_cast(rejectButton), GTK_STOCK_CANCEL); } } } void GtkFileDialog::setNameFilters(const QStringList &filters) { GtkDialog *gtkDialog = d->gtkDialog(); foreach (GtkFileFilter *filter, _filters) Libs::gtk_file_chooser_remove_filter(Libs::gtk_file_chooser_cast(gtkDialog), filter); _filters.clear(); _filterNames.clear(); for_const (auto &filter, filters) { GtkFileFilter *gtkFilter = Libs::gtk_file_filter_new(); auto name = filter;//.left(filter.indexOf(QLatin1Char('('))); auto extensions = cleanFilterList(filter); Libs::gtk_file_filter_set_name(gtkFilter, name.isEmpty() ? extensions.join(QStringLiteral(", ")).toUtf8() : name.toUtf8()); for_const (auto &ext, extensions) { auto caseInsensitiveExt = QString(); caseInsensitiveExt.reserve(4 * ext.size()); for_const (auto ch, ext) { auto chLower = ch.toLower(); auto chUpper = ch.toUpper(); if (chLower != chUpper) { caseInsensitiveExt.append('[').append(chLower).append(chUpper).append(']'); } else { caseInsensitiveExt.append(ch); } } Libs::gtk_file_filter_add_pattern(gtkFilter, caseInsensitiveExt.toUtf8()); } Libs::gtk_file_chooser_add_filter(Libs::gtk_file_chooser_cast(gtkDialog), gtkFilter); _filters.insert(filter, gtkFilter); _filterNames.insert(gtkFilter, filter); } } } // namespace internal #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION } // namespace FileDialog } // namespace Platform ��������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/linux/file_utilities_linux.h��������������������������0000664�0000000�0000000�00000010056�13262451251�0027012�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "platform/platform_file_utilities.h" extern "C" { #undef signals #ifndef TDESKTOP_DISABLE_GTK_INTEGRATION #include <gtk/gtk.h> #include <gdk/gdk.h> #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION #define signals public } // extern "C" namespace Platform { namespace File { namespace internal { QByteArray EscapeShell(const QByteArray &content); } // namespace internal inline QString UrlToLocal(const QUrl &url) { return ::File::internal::UrlToLocalDefault(url); } inline void UnsafeOpenEmailLink(const QString &email) { return ::File::internal::UnsafeOpenEmailLinkDefault(email); } inline bool UnsafeShowOpenWithDropdown(const QString &filepath, QPoint menuPosition) { return false; } inline bool UnsafeShowOpenWith(const QString &filepath) { return false; } inline void UnsafeLaunch(const QString &filepath) { return ::File::internal::UnsafeLaunchDefault(filepath); } inline void PostprocessDownloaded(const QString &filepath) { } } // namespace File namespace FileDialog { inline void InitLastPath() { ::FileDialog::internal::InitLastPathDefault(); } namespace internal { #ifndef TDESKTOP_DISABLE_GTK_INTEGRATION // This is a patched copy of qgtk2 theme plugin. // We need to use our own gtk file dialog instead of // styling Qt file dialog, because Qt only works with gtk2. // We need to be able to work with gtk2 and gtk3, because // we use gtk3 to work with appindicator3. class QGtkDialog : public QWindow { Q_OBJECT public: QGtkDialog(GtkWidget *gtkWidget); ~QGtkDialog(); GtkDialog *gtkDialog() const; void exec(); void show(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent); void hide(); signals: void accept(); void reject(); protected: static void onResponse(QGtkDialog *dialog, int response); static void onUpdatePreview(QGtkDialog *dialog); private slots: void onParentWindowDestroyed(); private: GtkWidget *gtkWidget; GtkWidget *_preview = nullptr; }; class GtkFileDialog : public QDialog { Q_OBJECT public: GtkFileDialog(QWidget *parent = Q_NULLPTR, const QString &caption = QString(), const QString &directory = QString(), const QString &filter = QString()); ~GtkFileDialog(); void setVisible(bool visible) override; void setWindowTitle(const QString &windowTitle) { _windowTitle = windowTitle; } void setAcceptMode(QFileDialog::AcceptMode acceptMode) { _acceptMode = acceptMode; } void setFileMode(QFileDialog::FileMode fileMode) { _fileMode = fileMode; } void setOption(QFileDialog::Option option, bool on = true) { if (on) { _options |= option; } else { _options &= ~option; } } int exec() override; bool defaultNameFilterDisables() const; void setDirectory(const QString &directory); QDir directory() const; void selectFile(const QString &filename); QStringList selectedFiles() const; void setFilter(); void selectNameFilter(const QString &filter); QString selectedNameFilter() const; private slots: void onAccepted(); void onRejected(); private: static void onSelectionChanged(GtkDialog *dialog, GtkFileDialog *helper); static void onCurrentFolderChanged(GtkFileDialog *helper); void applyOptions(); void setNameFilters(const QStringList &filters); void showHelper(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent); void hideHelper(); // Options QFileDialog::Options _options = { 0 }; QString _windowTitle = "Choose file"; QString _initialDirectory; QStringList _initialFiles; QStringList _nameFilters; QFileDialog::AcceptMode _acceptMode = QFileDialog::AcceptOpen; QFileDialog::FileMode _fileMode = QFileDialog::ExistingFile; QString _dir; QStringList _selection; QHash<QString, GtkFileFilter*> _filters; QHash<GtkFileFilter*, QString> _filterNames; QScopedPointer<QGtkDialog> d; }; #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION } // namespace internal } // namespace FileDialog } // namespace Platform ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/linux/launcher_linux.cpp������������������������������0000664�0000000�0000000�00000004477�13262451251�0026146�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "platform/linux/launcher_linux.h" #include "core/crash_reports.h" #include <sys/stat.h> #include <sys/types.h> #include <cstdlib> #include <unistd.h> #include <dirent.h> #include <pwd.h> namespace Platform { namespace { class Arguments { public: void push(QByteArray argument) { argument.append(char(0)); _argumentValues.push_back(argument); _arguments.push_back(_argumentValues.back().data()); } char **result() { _arguments.push_back(nullptr); return _arguments.data(); } private: std::vector<QByteArray> _argumentValues; std::vector<char*> _arguments; }; } // namespace bool Launcher::launchUpdater(UpdaterLaunch action) { if (cExeName().isEmpty()) { return false; } const auto binaryName = (action == UpdaterLaunch::JustRelaunch) ? cExeName() : QStringLiteral("Updater"); auto argumentsList = Arguments(); argumentsList.push(QFile::encodeName(cExeDir() + binaryName)); if (cLaunchMode() == LaunchModeAutoStart) { argumentsList.push("-autostart"); } if (cDebug()) { argumentsList.push("-debug"); } if (cStartInTray()) { argumentsList.push("-startintray"); } if (cTestMode()) { argumentsList.push("-testmode"); } if (cDataFile() != qsl("data")) { argumentsList.push("-key"); argumentsList.push(QFile::encodeName(cDataFile())); } if (action == UpdaterLaunch::JustRelaunch) { argumentsList.push("-noupdate"); argumentsList.push("-tosettings"); if (customWorkingDir()) { argumentsList.push("-workdir"); argumentsList.push(QFile::encodeName(cWorkingDir())); } } else { argumentsList.push("-workpath"); argumentsList.push(QFile::encodeName(cWorkingDir())); argumentsList.push("-exename"); argumentsList.push(QFile::encodeName(cExeName())); argumentsList.push("-exepath"); argumentsList.push(QFile::encodeName(cExeDir())); if (customWorkingDir()) { argumentsList.push("-workdir_custom"); } } Logs::closeMain(); CrashReports::Finish(); const auto args = argumentsList.result(); pid_t pid = fork(); switch (pid) { case -1: return false; case 0: execv(args[0], args); return false; } return true; } } // namespace �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/linux/launcher_linux.h��������������������������������0000664�0000000�0000000�00000000735�13262451251�0025604�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "core/launcher.h" namespace Platform { class Launcher : public Core::Launcher { public: using Core::Launcher::Launcher; private: bool launchUpdater(UpdaterLaunch action) override; }; } // namespace Platform �����������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/linux/linux_desktop_environment.cpp�������������������0000664�0000000�0000000�00000006764�13262451251�0030443�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "platform/linux/linux_desktop_environment.h" #include <QDBusInterface> namespace Platform { namespace DesktopEnvironment { namespace { QString GetEnv(const char *name) { auto result = getenv(name); auto value = result ? QString::fromLatin1(result) : QString(); LOG(("Getting DE, %1: '%2'").arg(name).arg(value)); return value; } Type Compute() { auto xdgCurrentDesktop = GetEnv("XDG_CURRENT_DESKTOP").toLower(); auto list = xdgCurrentDesktop.split(':', QString::SkipEmptyParts); auto desktopSession = GetEnv("DESKTOP_SESSION").toLower(); auto kdeSession = GetEnv("KDE_SESSION_VERSION"); if (!list.isEmpty()) { if (list.contains("unity")) { // gnome-fallback sessions set XDG_CURRENT_DESKTOP to Unity // DESKTOP_SESSION can be gnome-fallback or gnome-fallback-compiz if (desktopSession.indexOf(qstr("gnome-fallback")) >= 0) { return Type::Gnome; } return Type::Unity; } else if (list.contains("xfce")) { return Type::XFCE; } else if (list.contains("pantheon")) { return Type::Pantheon; } else if (list.contains("gnome")) { if (list.contains("ubuntu")) return Type::Ubuntu; return Type::Gnome; } else if (list.contains("kde")) { if (kdeSession == qstr("5")) { return Type::KDE5; } return Type::KDE4; } } if (!desktopSession.isEmpty()) { if (desktopSession == qstr("gnome") || desktopSession == qstr("mate")) { return Type::Gnome; } else if (desktopSession == qstr("kde4") || desktopSession == qstr("kde-plasma")) { return Type::KDE4; } else if (desktopSession == qstr("kde")) { // This may mean KDE4 on newer systems, so we have to check. if (!kdeSession.isEmpty()) { return Type::KDE4; } return Type::KDE3; } else if (desktopSession.indexOf(qstr("xfce")) >= 0 || desktopSession == qstr("xubuntu")) { return Type::XFCE; } else if (desktopSession == qstr("awesome")) { return Type::Awesome; } } // Fall back on some older environment variables. // Useful particularly in the DESKTOP_SESSION=default case. if (!GetEnv("GNOME_DESKTOP_SESSION_ID").isEmpty()) { return Type::Gnome; } else if (!GetEnv("KDE_FULL_SESSION").isEmpty()) { if (!kdeSession.isEmpty()) { return Type::KDE4; } return Type::KDE3; } return Type::Other; } Type ComputeAndLog() { auto result = Compute(); auto name = [result]() -> QString { switch (result) { case Type::Other: return "Other"; case Type::Gnome: return "Gnome"; case Type::KDE3: return "KDE3"; case Type::KDE4: return "KDE4"; case Type::KDE5: return "KDE5"; case Type::Ubuntu: return "Ubuntu"; case Type::Unity: return "Unity"; case Type::XFCE: return "XFCE"; case Type::Pantheon: return "Pantheon"; case Type::Awesome: return "Awesome"; } return QString::number(static_cast<int>(result)); }; LOG(("DE: %1").arg(name())); return result; } } // namespace // Thanks Chromium. Type Get() { static const auto result = ComputeAndLog(); return result; } bool TryQtTrayIcon() { return !IsPantheon() && !IsAwesome(); } bool PreferAppIndicatorTrayIcon() { return IsXFCE() || IsUnity() || IsUbuntu() || (IsGnome() && QDBusInterface("org.kde.StatusNotifierWatcher", "/").isValid()); } bool TryUnityCounter() { return IsUnity() || IsPantheon() || IsUbuntu(); } } // namespace DesktopEnvironment } // namespace Platform ������������tdesktop-1.2.17/Telegram/SourceFiles/platform/linux/linux_desktop_environment.h���������������������0000664�0000000�0000000�00000002174�13262451251�0030077�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace Platform { namespace DesktopEnvironment { enum class Type { Other, Gnome, KDE3, KDE4, KDE5, Ubuntu, Unity, XFCE, Pantheon, Awesome, }; Type Get(); inline bool IsGnome() { return Get() == Type::Gnome; } inline bool IsKDE3() { return Get() == Type::KDE3; } inline bool IsKDE4() { return Get() == Type::KDE4; } inline bool IsKDE5() { return Get() == Type::KDE5; } inline bool IsKDE() { return IsKDE3() || IsKDE4() || IsKDE5(); } inline bool IsUbuntu() { return Get() == Type::Ubuntu; } inline bool IsUnity() { return Get() == Type::Unity; } inline bool IsXFCE() { return Get() == Type::XFCE; } inline bool IsPantheon() { return Get() == Type::Pantheon; } inline bool IsAwesome() { return Get() == Type::Awesome; } bool TryQtTrayIcon(); bool PreferAppIndicatorTrayIcon(); bool TryUnityCounter(); } // namespace DesktopEnvironment } // namespace Platform ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/linux/linux_gdk_helper.cpp����������������������������0000664�0000000�0000000�00000006236�13262451251�0026444�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #ifndef TDESKTOP_DISABLE_GTK_INTEGRATION #include "platform/linux/linux_gdk_helper.h" #include "platform/linux/linux_libs.h" extern "C" { #undef signals #include <gdk/gdkx.h> #define signals public } // extern "C" namespace Platform { namespace internal { enum class GtkLoaded { GtkNone, Gtk2, Gtk3, }; GtkLoaded gdk_helper_loaded = GtkLoaded::GtkNone; // To be able to compile with gtk-3.0 headers as well #define GdkDrawable GdkWindow // Gtk 2 using f_gdk_x11_drawable_get_xdisplay = Display*(*)(GdkDrawable*); f_gdk_x11_drawable_get_xdisplay gdk_x11_drawable_get_xdisplay = nullptr; using f_gdk_x11_drawable_get_xid = XID(*)(GdkDrawable*); f_gdk_x11_drawable_get_xid gdk_x11_drawable_get_xid = nullptr; // Gtk 3 using f_gdk_x11_window_get_type = GType (*)(void); f_gdk_x11_window_get_type gdk_x11_window_get_type = nullptr; // To be able to compile with gtk-2.0 headers as well template <typename Object> inline bool gdk_is_x11_window_check(Object *obj) { return Libs::g_type_cit_helper(obj, gdk_x11_window_get_type()); } using f_gdk_window_get_display = GdkDisplay*(*)(GdkWindow *window); f_gdk_window_get_display gdk_window_get_display = nullptr; using f_gdk_x11_display_get_xdisplay = Display*(*)(GdkDisplay *display); f_gdk_x11_display_get_xdisplay gdk_x11_display_get_xdisplay = nullptr; using f_gdk_x11_window_get_xid = Window(*)(GdkWindow *window); f_gdk_x11_window_get_xid gdk_x11_window_get_xid = nullptr; bool GdkHelperLoadGtk2(QLibrary &lib) { if (!Libs::load(lib, "gdk_x11_drawable_get_xdisplay", gdk_x11_drawable_get_xdisplay)) return false; if (!Libs::load(lib, "gdk_x11_drawable_get_xid", gdk_x11_drawable_get_xid)) return false; return true; } bool GdkHelperLoadGtk3(QLibrary &lib) { if (!Libs::load(lib, "gdk_x11_window_get_type", gdk_x11_window_get_type)) return false; if (!Libs::load(lib, "gdk_window_get_display", gdk_window_get_display)) return false; if (!Libs::load(lib, "gdk_x11_display_get_xdisplay", gdk_x11_display_get_xdisplay)) return false; if (!Libs::load(lib, "gdk_x11_window_get_xid", gdk_x11_window_get_xid)) return false; return true; } void GdkHelperLoad(QLibrary &lib) { gdk_helper_loaded = GtkLoaded::GtkNone; if (GdkHelperLoadGtk2(lib)) { gdk_helper_loaded = GtkLoaded::Gtk2; } else if (GdkHelperLoadGtk3(lib)) { gdk_helper_loaded = GtkLoaded::Gtk3; } } bool GdkHelperLoaded() { return gdk_helper_loaded != GtkLoaded::GtkNone; } void XSetTransientForHint(GdkWindow *window, quintptr winId) { if (gdk_helper_loaded == GtkLoaded::Gtk2) { ::XSetTransientForHint(gdk_x11_drawable_get_xdisplay(window), gdk_x11_drawable_get_xid(window), winId); } else if (gdk_helper_loaded == GtkLoaded::Gtk3) { if (gdk_is_x11_window_check(window)) { ::XSetTransientForHint(gdk_x11_display_get_xdisplay(gdk_window_get_display(window)), gdk_x11_window_get_xid(window), winId); } } } } // namespace internal } // namespace Platform #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/linux/linux_gdk_helper.h������������������������������0000664�0000000�0000000�00000001261�13262451251�0026102�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <QtCore/QObject> #ifndef TDESKTOP_DISABLE_GTK_INTEGRATION extern "C" { #undef signals #include <gtk/gtk.h> #include <gdk/gdk.h> #define signals public } // extern "C" namespace Platform { namespace internal { void GdkHelperLoad(QLibrary &lib); bool GdkHelperLoaded(); void XSetTransientForHint(GdkWindow *window, quintptr winId); } // namespace internal } // namespace Platform #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/linux/linux_glibc_wraps.c�����������������������������0000664�0000000�0000000�00000001360�13262451251�0026265�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include <stdlib.h> #include <unistd.h> #include <sys/types.h> void *__wrap_aligned_alloc(size_t alignment, size_t size) { void *result = NULL; return (posix_memalign(&result, alignment, size) == 0) ? result : NULL; } int enable_secure_inited = 0; int enable_secure = 1; char *__wrap_secure_getenv(const char *name) { if (enable_secure_inited == 0) { enable_secure_inited = 1; enable_secure = (geteuid() != getuid()) || (getegid() != getgid()); } return enable_secure ? NULL : getenv(name); } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/linux/linux_glibc_wraps_32.c��������������������������0000664�0000000�0000000�00000002225�13262451251�0026572�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include <time.h> #include <stdint.h> #if defined(_M_IX86) || defined(__i386__) #define GETTIME_GLIBC_VERSION "2.2" #elif defined(_M_ARM) || defined(__arm__) #define GETTIME_GLIBC_VERSION "2.4" #else #error Please add glibc wraps for your architecture #endif int __clock_gettime_glibc_old(clockid_t clk_id, struct timespec *tp); __asm__(".symver __clock_gettime_glibc_old,clock_gettime@GLIBC_" GETTIME_GLIBC_VERSION); int __wrap_clock_gettime(clockid_t clk_id, struct timespec *tp) { return __clock_gettime_glibc_old(clk_id, tp); } uint64_t __udivmoddi4(uint64_t num, uint64_t den, uint64_t *rem_p); int64_t __wrap___divmoddi4(int64_t num, int64_t den, int64_t *rem_p) { int minus = 0; int64_t v; if (num < 0) { num = -num; minus = 1; } if (den < 0) { den = -den; minus ^= 1; } v = __udivmoddi4(num, den, (uint64_t *)rem_p); if (minus) { v = -v; if (rem_p) *rem_p = -(*rem_p); } return v; } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/linux/linux_glibc_wraps_64.c��������������������������0000664�0000000�0000000�00000001362�13262451251�0026600�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include <time.h> #if defined(_M_X64) || defined(__x86_64__) #define GETTIME_GLIBC_VERSION "2.2.5" #elif defined(__aarch64__) #define GETTIME_GLIBC_VERSION "2.17" #else #error Please add glibc wraps for your architecture #endif int __clock_gettime_glibc_old(clockid_t clk_id, struct timespec *tp); __asm__(".symver __clock_gettime_glibc_old,clock_gettime@GLIBC_" GETTIME_GLIBC_VERSION); int __wrap_clock_gettime(clockid_t clk_id, struct timespec *tp) { return __clock_gettime_glibc_old(clk_id, tp); } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/linux/linux_libnotify.cpp�����������������������������0000664�0000000�0000000�00000013251�13262451251�0026332�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "platform/linux/linux_libnotify.h" #include "platform/linux/linux_libs.h" namespace Platform { namespace Libs { namespace { bool loadLibrary(QLibrary &lib, const char *name, int version) { DEBUG_LOG(("Loading '%1' with version %2...").arg(QLatin1String(name)).arg(version)); lib.setFileNameAndVersion(QLatin1String(name), version); if (lib.load()) { DEBUG_LOG(("Loaded '%1' with version %2!").arg(QLatin1String(name)).arg(version)); return true; } lib.setFileNameAndVersion(QLatin1String(name), QString()); if (lib.load()) { DEBUG_LOG(("Loaded '%1' without version!").arg(QLatin1String(name))); return true; } LOG(("Could not load '%1' with version %2 :(").arg(QLatin1String(name)).arg(version)); return false; } } // namespace #ifndef TDESKTOP_DISABLE_GTK_INTEGRATION f_notify_init notify_init = nullptr; f_notify_uninit notify_uninit = nullptr; f_notify_is_initted notify_is_initted = nullptr; //f_notify_get_app_name notify_get_app_name = nullptr; //f_notify_set_app_name notify_set_app_name = nullptr; f_notify_get_server_caps notify_get_server_caps = nullptr; f_notify_get_server_info notify_get_server_info = nullptr; f_notify_notification_new notify_notification_new = nullptr; //f_notify_notification_update notify_notification_update = nullptr; f_notify_notification_show notify_notification_show = nullptr; //f_notify_notification_set_app_name notify_notification_set_app_name = nullptr; f_notify_notification_set_timeout notify_notification_set_timeout = nullptr; //f_notify_notification_set_category notify_notification_set_category = nullptr; //f_notify_notification_set_urgency notify_notification_set_urgency = nullptr; //f_notify_notification_set_icon_from_pixbuf notify_notification_set_icon_from_pixbuf = nullptr; f_notify_notification_set_image_from_pixbuf notify_notification_set_image_from_pixbuf = nullptr; //f_notify_notification_set_hint notify_notification_set_hint = nullptr; //f_notify_notification_set_hint_int32 notify_notification_set_hint_int32 = nullptr; //f_notify_notification_set_hint_uint32 notify_notification_set_hint_uint32 = nullptr; //f_notify_notification_set_hint_double notify_notification_set_hint_double = nullptr; f_notify_notification_set_hint_string notify_notification_set_hint_string = nullptr; //f_notify_notification_set_hint_byte notify_notification_set_hint_byte = nullptr; //f_notify_notification_set_hint_byte_array notify_notification_set_hint_byte_array = nullptr; //f_notify_notification_clear_hints notify_notification_clear_hints = nullptr; f_notify_notification_add_action notify_notification_add_action = nullptr; f_notify_notification_clear_actions notify_notification_clear_actions = nullptr; f_notify_notification_close notify_notification_close = nullptr; f_notify_notification_get_closed_reason notify_notification_get_closed_reason = nullptr; void startLibNotify() { DEBUG_LOG(("Loading libnotify")); QLibrary lib_notify; if (!loadLibrary(lib_notify, "notify", 4)) { if (!loadLibrary(lib_notify, "notify", 5)) { if (!loadLibrary(lib_notify, "notify", 1)) { return; } } } load(lib_notify, "notify_init", notify_init); load(lib_notify, "notify_uninit", notify_uninit); load(lib_notify, "notify_is_initted", notify_is_initted); // load(lib_notify, "notify_get_app_name", notify_get_app_name); // load(lib_notify, "notify_set_app_name", notify_set_app_name); load(lib_notify, "notify_get_server_caps", notify_get_server_caps); load(lib_notify, "notify_get_server_info", notify_get_server_info); load(lib_notify, "notify_notification_new", notify_notification_new); // load(lib_notify, "notify_notification_update", notify_notification_update); load(lib_notify, "notify_notification_show", notify_notification_show); // load(lib_notify, "notify_notification_set_app_name", notify_notification_set_app_name); load(lib_notify, "notify_notification_set_timeout", notify_notification_set_timeout); // load(lib_notify, "notify_notification_set_category", notify_notification_set_category); // load(lib_notify, "notify_notification_set_urgency", notify_notification_set_urgency); // load(lib_notify, "notify_notification_set_icon_from_pixbuf", notify_notification_set_icon_from_pixbuf); load(lib_notify, "notify_notification_set_image_from_pixbuf", notify_notification_set_image_from_pixbuf); // load(lib_notify, "notify_notification_set_hint", notify_notification_set_hint); // load(lib_notify, "notify_notification_set_hint_int32", notify_notification_set_hint_int32); // load(lib_notify, "notify_notification_set_hint_uint32", notify_notification_set_hint_uint32); // load(lib_notify, "notify_notification_set_hint_double", notify_notification_set_hint_double); load(lib_notify, "notify_notification_set_hint_string", notify_notification_set_hint_string); // load(lib_notify, "notify_notification_set_hint_byte", notify_notification_set_hint_byte); // load(lib_notify, "notify_notification_set_hint_byte_array", notify_notification_set_hint_byte_array); // load(lib_notify, "notify_notification_clear_hints", notify_notification_clear_hints); load(lib_notify, "notify_notification_add_action", notify_notification_add_action); load(lib_notify, "notify_notification_clear_actions", notify_notification_clear_actions); load(lib_notify, "notify_notification_close", notify_notification_close); load(lib_notify, "notify_notification_get_closed_reason", notify_notification_get_closed_reason); } #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION } // namespace Libs } // namespace Platform �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/linux/linux_libnotify.h�������������������������������0000664�0000000�0000000�00000013225�13262451251�0026000�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #ifndef TDESKTOP_DISABLE_GTK_INTEGRATION extern "C" { #undef signals #include <gtk/gtk.h> #define signals public } // extern "C" namespace Platform { namespace Libs { void startLibNotify(); constexpr gint NOTIFY_EXPIRES_DEFAULT = -1; constexpr gint NOTIFY_EXPIRES_NEVER = 0; struct NotifyNotification; typedef enum { NOTIFY_URGENCY_LOW, NOTIFY_URGENCY_NORMAL, NOTIFY_URGENCY_CRITICAL, } NotifyUrgency; using NotifyActionCallback = void (*)(NotifyNotification *notification, char *action, gpointer user_data); using f_notify_init = gboolean (*)(const char *app_name); extern f_notify_init notify_init; using f_notify_uninit = void (*)(void); extern f_notify_uninit notify_uninit; using f_notify_is_initted = gboolean (*)(void); extern f_notify_is_initted notify_is_initted; //using f_notify_get_app_name = const char* (*)(void); //extern f_notify_get_app_name notify_get_app_name; //using f_notify_set_app_name = void (*)(const char *app_name); //extern f_notify_set_app_name notify_set_app_name; using f_notify_get_server_caps = GList* (*)(void); extern f_notify_get_server_caps notify_get_server_caps; using f_notify_get_server_info = gboolean (*)(char **ret_name, char **ret_vendor, char **ret_version, char **ret_spec_version); extern f_notify_get_server_info notify_get_server_info; using f_notify_notification_new = NotifyNotification* (*)(const char *summary, const char *body, const char *icon); extern f_notify_notification_new notify_notification_new; //using f_notify_notification_update = gboolean (*)(NotifyNotification *notification, const char *summary, const char *body, const char *icon); //extern f_notify_notification_update notify_notification_update; using f_notify_notification_show = gboolean (*)(NotifyNotification *notification, GError **error); extern f_notify_notification_show notify_notification_show; //using f_notify_notification_set_app_name = void (*)(NotifyNotification *notification, const char *app_name); //extern f_notify_notification_set_app_name notify_notification_set_app_name; using f_notify_notification_set_timeout = void (*)(NotifyNotification *notification, gint timeout); extern f_notify_notification_set_timeout notify_notification_set_timeout; //using f_notify_notification_set_category = void (*)(NotifyNotification *notification, const char *category); //extern f_notify_notification_set_category notify_notification_set_category; //using f_notify_notification_set_urgency = void (*)(NotifyNotification *notification, NotifyUrgency urgency); //extern f_notify_notification_set_urgency notify_notification_set_urgency; //using f_notify_notification_set_icon_from_pixbuf = void (*)(NotifyNotification *notification, GdkPixbuf *icon); //extern f_notify_notification_set_icon_from_pixbuf notify_notification_set_icon_from_pixbuf; using f_notify_notification_set_image_from_pixbuf = void (*)(NotifyNotification *notification, GdkPixbuf *pixbuf); extern f_notify_notification_set_image_from_pixbuf notify_notification_set_image_from_pixbuf; //using f_notify_notification_set_hint = void (*)(NotifyNotification *notification, const char *key, GVariant *value); //extern f_notify_notification_set_hint notify_notification_set_hint; //using f_notify_notification_set_hint_int32 = void (*)(NotifyNotification *notification, const char *key, gint value); //extern f_notify_notification_set_hint_int32 notify_notification_set_hint_int32; //using f_notify_notification_set_hint_uint32 = void (*)(NotifyNotification *notification, const char *key, guint value); //extern f_notify_notification_set_hint_uint32 notify_notification_set_hint_uint32; //using f_notify_notification_set_hint_double = void (*)(NotifyNotification *notification, const char *key, gdouble value); //extern f_notify_notification_set_hint_double notify_notification_set_hint_double; using f_notify_notification_set_hint_string = void (*)(NotifyNotification *notification, const char *key, const char *value); extern f_notify_notification_set_hint_string notify_notification_set_hint_string; //using f_notify_notification_set_hint_byte = void (*)(NotifyNotification *notification, const char *key, guchar value); //extern f_notify_notification_set_hint_byte notify_notification_set_hint_byte; //using f_notify_notification_set_hint_byte_array = void (*)(NotifyNotification *notification, const char *key, const guchar *value, gsize len); //extern f_notify_notification_set_hint_byte_array notify_notification_set_hint_byte_array; //using f_notify_notification_clear_hints = void (*)(NotifyNotification *notification); //extern f_notify_notification_clear_hints notify_notification_clear_hints; using f_notify_notification_add_action = void (*)(NotifyNotification *notification, const char *action, const char *label, NotifyActionCallback callback, gpointer user_data, GFreeFunc free_func); extern f_notify_notification_add_action notify_notification_add_action; using f_notify_notification_clear_actions = void (*)(NotifyNotification *notification); extern f_notify_notification_clear_actions notify_notification_clear_actions; using f_notify_notification_close = gboolean (*)(NotifyNotification *notification, GError **error); extern f_notify_notification_close notify_notification_close; using f_notify_notification_get_closed_reason = gint (*)(const NotifyNotification *notification); extern f_notify_notification_get_closed_reason notify_notification_get_closed_reason; } // namespace Libs } // namespace Platform #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/linux/linux_libs.cpp����������������������������������0000664�0000000�0000000�00000040261�13262451251�0025265�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "platform/linux/linux_libs.h" #include "platform/linux/linux_gdk_helper.h" #include "platform/linux/linux_libnotify.h" #include "platform/linux/linux_desktop_environment.h" namespace Platform { namespace Libs { namespace { bool loadLibrary(QLibrary &lib, const char *name, int version) { DEBUG_LOG(("Loading '%1' with version %2...").arg(QLatin1String(name)).arg(version)); lib.setFileNameAndVersion(QLatin1String(name), version); if (lib.load()) { DEBUG_LOG(("Loaded '%1' with version %2!").arg(QLatin1String(name)).arg(version)); return true; } lib.setFileNameAndVersion(QLatin1String(name), QString()); if (lib.load()) { DEBUG_LOG(("Loaded '%1' without version!").arg(QLatin1String(name))); return true; } LOG(("Could not load '%1' with version %2 :(").arg(QLatin1String(name)).arg(version)); return false; } #ifndef TDESKTOP_DISABLE_GTK_INTEGRATION bool setupGtkBase(QLibrary &lib_gtk) { if (!load(lib_gtk, "gtk_init_check", gtk_init_check)) return false; if (!load(lib_gtk, "gtk_menu_new", gtk_menu_new)) return false; if (!load(lib_gtk, "gtk_menu_get_type", gtk_menu_get_type)) return false; if (!load(lib_gtk, "gtk_menu_item_new_with_label", gtk_menu_item_new_with_label)) return false; if (!load(lib_gtk, "gtk_menu_item_set_label", gtk_menu_item_set_label)) return false; if (!load(lib_gtk, "gtk_menu_shell_append", gtk_menu_shell_append)) return false; if (!load(lib_gtk, "gtk_menu_shell_get_type", gtk_menu_shell_get_type)) return false; if (!load(lib_gtk, "gtk_widget_show", gtk_widget_show)) return false; if (!load(lib_gtk, "gtk_widget_hide", gtk_widget_hide)) return false; if (!load(lib_gtk, "gtk_widget_get_toplevel", gtk_widget_get_toplevel)) return false; if (!load(lib_gtk, "gtk_widget_get_visible", gtk_widget_get_visible)) return false; if (!load(lib_gtk, "gtk_widget_get_window", gtk_widget_get_window)) return false; if (!load(lib_gtk, "gtk_widget_set_sensitive", gtk_widget_set_sensitive)) return false; if (!load(lib_gtk, "gtk_widget_realize", gtk_widget_realize)) return false; if (!load(lib_gtk, "gtk_widget_hide_on_delete", gtk_widget_hide_on_delete)) return false; if (!load(lib_gtk, "gtk_widget_destroy", gtk_widget_destroy)) return false; if (!load(lib_gtk, "gtk_clipboard_get", gtk_clipboard_get)) return false; if (!load(lib_gtk, "gtk_clipboard_store", gtk_clipboard_store)) return false; if (!load(lib_gtk, "gtk_file_chooser_dialog_new", gtk_file_chooser_dialog_new)) return false; if (!load(lib_gtk, "gtk_file_chooser_get_type", gtk_file_chooser_get_type)) return false; if (!load(lib_gtk, "gtk_image_get_type", gtk_image_get_type)) return false; if (!load(lib_gtk, "gtk_file_chooser_set_current_folder", gtk_file_chooser_set_current_folder)) return false; if (!load(lib_gtk, "gtk_file_chooser_get_current_folder", gtk_file_chooser_get_current_folder)) return false; if (!load(lib_gtk, "gtk_file_chooser_set_current_name", gtk_file_chooser_set_current_name)) return false; if (!load(lib_gtk, "gtk_file_chooser_select_filename", gtk_file_chooser_select_filename)) return false; if (!load(lib_gtk, "gtk_file_chooser_get_filenames", gtk_file_chooser_get_filenames)) return false; if (!load(lib_gtk, "gtk_file_chooser_set_filter", gtk_file_chooser_set_filter)) return false; if (!load(lib_gtk, "gtk_file_chooser_get_filter", gtk_file_chooser_get_filter)) return false; if (!load(lib_gtk, "gtk_window_get_type", gtk_window_get_type)) return false; if (!load(lib_gtk, "gtk_window_set_title", gtk_window_set_title)) return false; if (!load(lib_gtk, "gtk_file_chooser_set_local_only", gtk_file_chooser_set_local_only)) return false; if (!load(lib_gtk, "gtk_file_chooser_set_action", gtk_file_chooser_set_action)) return false; if (!load(lib_gtk, "gtk_file_chooser_set_select_multiple", gtk_file_chooser_set_select_multiple)) return false; if (!load(lib_gtk, "gtk_file_chooser_set_do_overwrite_confirmation", gtk_file_chooser_set_do_overwrite_confirmation)) return false; if (!load(lib_gtk, "gtk_file_chooser_remove_filter", gtk_file_chooser_remove_filter)) return false; if (!load(lib_gtk, "gtk_file_filter_set_name", gtk_file_filter_set_name)) return false; if (!load(lib_gtk, "gtk_file_filter_add_pattern", gtk_file_filter_add_pattern)) return false; if (!load(lib_gtk, "gtk_file_chooser_add_filter", gtk_file_chooser_add_filter)) return false; if (!load(lib_gtk, "gtk_file_chooser_set_preview_widget", gtk_file_chooser_set_preview_widget)) return false; if (!load(lib_gtk, "gtk_file_chooser_get_preview_filename", gtk_file_chooser_get_preview_filename)) return false; if (!load(lib_gtk, "gtk_file_chooser_set_preview_widget_active", gtk_file_chooser_set_preview_widget_active)) return false; if (!load(lib_gtk, "gtk_file_filter_new", gtk_file_filter_new)) return false; if (!load(lib_gtk, "gtk_image_new", gtk_image_new)) return false; if (!load(lib_gtk, "gtk_image_set_from_pixbuf", gtk_image_set_from_pixbuf)) return false; if (!load(lib_gtk, "gdk_window_set_modal_hint", gdk_window_set_modal_hint)) return false; if (!load(lib_gtk, "gdk_window_focus", gdk_window_focus)) return false; if (!load(lib_gtk, "gtk_dialog_get_type", gtk_dialog_get_type)) return false; if (!load(lib_gtk, "gtk_dialog_run", gtk_dialog_run)) return false; if (!load(lib_gtk, "g_type_check_instance_cast", g_type_check_instance_cast)) return false; if (!load(lib_gtk, "g_type_check_instance_is_a", g_type_check_instance_is_a)) return false; if (!load(lib_gtk, "g_signal_connect_data", g_signal_connect_data)) return false; if (!load(lib_gtk, "g_signal_handler_disconnect", g_signal_handler_disconnect)) return false; if (!load(lib_gtk, "g_object_ref_sink", g_object_ref_sink)) return false; if (!load(lib_gtk, "g_object_unref", g_object_unref)) return false; if (!load(lib_gtk, "g_free", g_free)) return false; if (!load(lib_gtk, "g_list_foreach", g_list_foreach)) return false; if (!load(lib_gtk, "g_list_free", g_list_free)) return false; if (!load(lib_gtk, "g_list_free_full", g_list_free_full)) return false; if (!load(lib_gtk, "g_error_free", g_error_free)) return false; if (!load(lib_gtk, "g_slist_free", g_slist_free)) return false; DEBUG_LOG(("Library gtk functions loaded!")); if (load(lib_gtk, "gdk_set_allowed_backends", gdk_set_allowed_backends)) { // We work only with X11 GDK backend. // Otherwise we get segfault in Ubuntu 17.04 in gtk_init_check() call. // See https://github.com/telegramdesktop/tdesktop/issues/3176 // See https://github.com/telegramdesktop/tdesktop/issues/3162 DEBUG_LOG(("Limit allowed GDK backends to x11")); gdk_set_allowed_backends("x11"); } DEBUG_LOG(("Library gtk functions loaded!")); if (!gtk_init_check(0, 0)) { gtk_init_check = nullptr; DEBUG_LOG(("Failed to gtk_init_check(0, 0)!")); return false; } DEBUG_LOG(("Checked gtk with gtk_init_check!")); return true; } bool setupAppIndicator(QLibrary &lib_indicator) { if (!load(lib_indicator, "app_indicator_new", app_indicator_new)) return false; if (!load(lib_indicator, "app_indicator_set_status", app_indicator_set_status)) return false; if (!load(lib_indicator, "app_indicator_set_menu", app_indicator_set_menu)) return false; if (!load(lib_indicator, "app_indicator_set_icon_full", app_indicator_set_icon_full)) return false; DEBUG_LOG(("Library appindicator functions loaded!")); return true; } #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION } // namespace #ifndef TDESKTOP_DISABLE_GTK_INTEGRATION f_gtk_init_check gtk_init_check = nullptr; f_gtk_menu_new gtk_menu_new = nullptr; f_gtk_menu_get_type gtk_menu_get_type = nullptr; f_gtk_menu_item_new_with_label gtk_menu_item_new_with_label = nullptr; f_gtk_menu_item_set_label gtk_menu_item_set_label = nullptr; f_gtk_menu_shell_append gtk_menu_shell_append = nullptr; f_gtk_menu_shell_get_type gtk_menu_shell_get_type = nullptr; f_gtk_widget_show gtk_widget_show = nullptr; f_gtk_widget_hide gtk_widget_hide = nullptr; f_gtk_widget_get_toplevel gtk_widget_get_toplevel = nullptr; f_gtk_widget_get_visible gtk_widget_get_visible = nullptr; f_gtk_widget_get_window gtk_widget_get_window = nullptr; f_gtk_widget_set_sensitive gtk_widget_set_sensitive = nullptr; f_gtk_widget_realize gtk_widget_realize = nullptr; f_gtk_widget_hide_on_delete gtk_widget_hide_on_delete = nullptr; f_gtk_widget_destroy gtk_widget_destroy = nullptr; f_gtk_clipboard_get gtk_clipboard_get = nullptr; f_gtk_clipboard_store gtk_clipboard_store = nullptr; f_gtk_file_chooser_dialog_new gtk_file_chooser_dialog_new = nullptr; f_gtk_file_chooser_get_type gtk_file_chooser_get_type = nullptr; f_gtk_image_get_type gtk_image_get_type = nullptr; f_gtk_file_chooser_set_current_folder gtk_file_chooser_set_current_folder = nullptr; f_gtk_file_chooser_get_current_folder gtk_file_chooser_get_current_folder = nullptr; f_gtk_file_chooser_set_current_name gtk_file_chooser_set_current_name = nullptr; f_gtk_file_chooser_select_filename gtk_file_chooser_select_filename = nullptr; f_gtk_file_chooser_get_filenames gtk_file_chooser_get_filenames = nullptr; f_gtk_file_chooser_set_filter gtk_file_chooser_set_filter = nullptr; f_gtk_file_chooser_get_filter gtk_file_chooser_get_filter = nullptr; f_gtk_window_get_type gtk_window_get_type = nullptr; f_gtk_window_set_title gtk_window_set_title = nullptr; f_gtk_file_chooser_set_local_only gtk_file_chooser_set_local_only = nullptr; f_gtk_file_chooser_set_action gtk_file_chooser_set_action = nullptr; f_gtk_file_chooser_set_select_multiple gtk_file_chooser_set_select_multiple = nullptr; f_gtk_file_chooser_set_do_overwrite_confirmation gtk_file_chooser_set_do_overwrite_confirmation = nullptr; f_gtk_file_chooser_remove_filter gtk_file_chooser_remove_filter = nullptr; f_gtk_file_filter_set_name gtk_file_filter_set_name = nullptr; f_gtk_file_filter_add_pattern gtk_file_filter_add_pattern = nullptr; f_gtk_file_chooser_add_filter gtk_file_chooser_add_filter = nullptr; f_gtk_file_chooser_set_preview_widget gtk_file_chooser_set_preview_widget = nullptr; f_gtk_file_chooser_get_preview_filename gtk_file_chooser_get_preview_filename = nullptr; f_gtk_file_chooser_set_preview_widget_active gtk_file_chooser_set_preview_widget_active = nullptr; f_gtk_file_filter_new gtk_file_filter_new = nullptr; f_gtk_image_new gtk_image_new = nullptr; f_gtk_image_set_from_pixbuf gtk_image_set_from_pixbuf = nullptr; f_gtk_dialog_get_widget_for_response gtk_dialog_get_widget_for_response = nullptr; f_gtk_button_set_label gtk_button_set_label = nullptr; f_gtk_button_get_type gtk_button_get_type = nullptr; f_gdk_set_allowed_backends gdk_set_allowed_backends = nullptr; f_gdk_window_set_modal_hint gdk_window_set_modal_hint = nullptr; f_gdk_window_focus gdk_window_focus = nullptr; f_gtk_dialog_get_type gtk_dialog_get_type = nullptr; f_gtk_dialog_run gtk_dialog_run = nullptr; f_g_type_check_instance_cast g_type_check_instance_cast = nullptr; f_g_type_check_instance_is_a g_type_check_instance_is_a = nullptr; f_g_signal_connect_data g_signal_connect_data = nullptr; f_g_signal_handler_disconnect g_signal_handler_disconnect = nullptr; f_app_indicator_new app_indicator_new = nullptr; f_app_indicator_set_status app_indicator_set_status = nullptr; f_app_indicator_set_menu app_indicator_set_menu = nullptr; f_app_indicator_set_icon_full app_indicator_set_icon_full = nullptr; f_gdk_init_check gdk_init_check = nullptr; f_gdk_pixbuf_new_from_data gdk_pixbuf_new_from_data = nullptr; f_gdk_pixbuf_new_from_file gdk_pixbuf_new_from_file = nullptr; f_gdk_pixbuf_new_from_file_at_size gdk_pixbuf_new_from_file_at_size = nullptr; f_gtk_status_icon_new_from_pixbuf gtk_status_icon_new_from_pixbuf = nullptr; f_gtk_status_icon_set_from_pixbuf gtk_status_icon_set_from_pixbuf = nullptr; f_gtk_status_icon_new_from_file gtk_status_icon_new_from_file = nullptr; f_gtk_status_icon_set_from_file gtk_status_icon_set_from_file = nullptr; f_gtk_status_icon_set_title gtk_status_icon_set_title = nullptr; f_gtk_status_icon_set_tooltip_text gtk_status_icon_set_tooltip_text = nullptr; f_gtk_status_icon_set_visible gtk_status_icon_set_visible = nullptr; f_gtk_status_icon_is_embedded gtk_status_icon_is_embedded = nullptr; f_gtk_status_icon_get_geometry gtk_status_icon_get_geometry = nullptr; f_gtk_status_icon_position_menu gtk_status_icon_position_menu = nullptr; f_gtk_menu_popup gtk_menu_popup = nullptr; f_gtk_get_current_event_time gtk_get_current_event_time = nullptr; f_g_object_ref_sink g_object_ref_sink = nullptr; f_g_object_unref g_object_unref = nullptr; f_g_idle_add g_idle_add = nullptr; f_g_free g_free = nullptr; f_g_list_foreach g_list_foreach = nullptr; f_g_list_free g_list_free = nullptr; f_g_list_free_full g_list_free_full = nullptr; f_g_error_free g_error_free = nullptr; f_g_slist_free g_slist_free = nullptr; #ifndef TDESKTOP_DISABLE_UNITY_INTEGRATION f_unity_launcher_entry_set_count unity_launcher_entry_set_count = nullptr; f_unity_launcher_entry_set_count_visible unity_launcher_entry_set_count_visible = nullptr; f_unity_launcher_entry_get_for_desktop_id unity_launcher_entry_get_for_desktop_id = nullptr; #endif // !TDESKTOP_DISABLE_UNITY_INTEGRATION #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION void start() { DEBUG_LOG(("Loading libraries")); #ifndef TDESKTOP_DISABLE_GTK_INTEGRATION bool gtkLoaded = false; bool indicatorLoaded = false; QLibrary lib_gtk, lib_indicator; if (loadLibrary(lib_indicator, "appindicator3", 1)) { if (loadLibrary(lib_gtk, "gtk-3", 0)) { gtkLoaded = setupGtkBase(lib_gtk); indicatorLoaded = setupAppIndicator(lib_indicator); } } if (!gtkLoaded || !indicatorLoaded) { if (loadLibrary(lib_indicator, "appindicator", 1)) { if (loadLibrary(lib_gtk, "gtk-x11-2.0", 0)) { gtkLoaded = indicatorLoaded = false; gtkLoaded = setupGtkBase(lib_gtk); indicatorLoaded = setupAppIndicator(lib_indicator); } } } // If no appindicator, try at least load gtk. if (!gtkLoaded && !indicatorLoaded) { if (loadLibrary(lib_gtk, "gtk-3", 0)) { gtkLoaded = setupGtkBase(lib_gtk); } if (!gtkLoaded && loadLibrary(lib_gtk, "gtk-x11-2.0", 0)) { gtkLoaded = setupGtkBase(lib_gtk); } } if (gtkLoaded) { load(lib_gtk, "gdk_init_check", gdk_init_check); load(lib_gtk, "gdk_pixbuf_new_from_data", gdk_pixbuf_new_from_data); load(lib_gtk, "gdk_pixbuf_new_from_file", gdk_pixbuf_new_from_file); load(lib_gtk, "gdk_pixbuf_new_from_file_at_size", gdk_pixbuf_new_from_file_at_size); load(lib_gtk, "gtk_status_icon_new_from_pixbuf", gtk_status_icon_new_from_pixbuf); load(lib_gtk, "gtk_status_icon_set_from_pixbuf", gtk_status_icon_set_from_pixbuf); load(lib_gtk, "gtk_status_icon_new_from_file", gtk_status_icon_new_from_file); load(lib_gtk, "gtk_status_icon_set_from_file", gtk_status_icon_set_from_file); load(lib_gtk, "gtk_status_icon_set_title", gtk_status_icon_set_title); load(lib_gtk, "gtk_status_icon_set_tooltip_text", gtk_status_icon_set_tooltip_text); load(lib_gtk, "gtk_status_icon_set_visible", gtk_status_icon_set_visible); load(lib_gtk, "gtk_status_icon_is_embedded", gtk_status_icon_is_embedded); load(lib_gtk, "gtk_status_icon_get_geometry", gtk_status_icon_get_geometry); load(lib_gtk, "gtk_status_icon_position_menu", gtk_status_icon_position_menu); load(lib_gtk, "gtk_menu_popup", gtk_menu_popup); load(lib_gtk, "gtk_get_current_event_time", gtk_get_current_event_time); load(lib_gtk, "g_idle_add", g_idle_add); internal::GdkHelperLoad(lib_gtk); load(lib_gtk, "gtk_dialog_get_widget_for_response", gtk_dialog_get_widget_for_response); load(lib_gtk, "gtk_button_set_label", gtk_button_set_label); load(lib_gtk, "gtk_button_get_type", gtk_button_get_type); } else { LOG(("Could not load gtk-x11-2.0!")); } #ifndef TDESKTOP_DISABLE_UNITY_INTEGRATION if (DesktopEnvironment::TryUnityCounter()) { QLibrary lib_unity(qstr("unity"), 9, 0); loadLibrary(lib_unity, "unity", 9); load(lib_unity, "unity_launcher_entry_get_for_desktop_id", unity_launcher_entry_get_for_desktop_id); load(lib_unity, "unity_launcher_entry_set_count", unity_launcher_entry_set_count); load(lib_unity, "unity_launcher_entry_set_count_visible", unity_launcher_entry_set_count_visible); } #endif // !TDESKTOP_DISABLE_UNITY_INTEGRATION if (gtkLoaded) { startLibNotify(); } #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION } } // namespace Libs } // namespace Platform �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/linux/linux_libs.h������������������������������������0000664�0000000�0000000�00000040055�13262451251�0024733�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #ifndef TDESKTOP_DISABLE_GTK_INTEGRATION extern "C" { #undef signals #include <libappindicator/app-indicator.h> #include <gtk/gtk.h> #include <gdk/gdk.h> #define signals public } // extern "C" #ifndef TDESKTOP_DISABLE_UNITY_INTEGRATION #include <unity/unity/unity.h> #endif // !TDESKTOP_DISABLE_UNITY_INTEGRATION #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION namespace Platform { namespace Libs { void start(); template <typename Function> bool load(QLibrary &lib, const char *name, Function &func) { func = nullptr; if (!lib.isLoaded()) { return false; } func = reinterpret_cast<Function>(lib.resolve(name)); if (func) { return true; } LOG(("Error: failed to load '%1' function!").arg(name)); return false; } #ifndef TDESKTOP_DISABLE_GTK_INTEGRATION typedef gboolean (*f_gtk_init_check)(int *argc, char ***argv); extern f_gtk_init_check gtk_init_check; typedef GtkWidget* (*f_gtk_menu_new)(void); extern f_gtk_menu_new gtk_menu_new; typedef GType (*f_gtk_menu_get_type)(void) G_GNUC_CONST; extern f_gtk_menu_get_type gtk_menu_get_type; typedef GtkWidget* (*f_gtk_menu_item_new_with_label)(const gchar *label); extern f_gtk_menu_item_new_with_label gtk_menu_item_new_with_label; typedef void (*f_gtk_menu_item_set_label)(GtkMenuItem *menu_item, const gchar *label); extern f_gtk_menu_item_set_label gtk_menu_item_set_label; typedef void (*f_gtk_menu_shell_append)(GtkMenuShell *menu_shell, GtkWidget *child); extern f_gtk_menu_shell_append gtk_menu_shell_append; typedef GType (*f_gtk_menu_shell_get_type)(void) G_GNUC_CONST; extern f_gtk_menu_shell_get_type gtk_menu_shell_get_type; typedef void (*f_gtk_widget_show)(GtkWidget *widget); extern f_gtk_widget_show gtk_widget_show; typedef void (*f_gtk_widget_hide)(GtkWidget *widget); extern f_gtk_widget_hide gtk_widget_hide; typedef GtkWidget* (*f_gtk_widget_get_toplevel)(GtkWidget *widget); extern f_gtk_widget_get_toplevel gtk_widget_get_toplevel; typedef gboolean (*f_gtk_widget_get_visible)(GtkWidget *widget); extern f_gtk_widget_get_visible gtk_widget_get_visible; typedef GdkWindow* (*f_gtk_widget_get_window)(GtkWidget *widget); extern f_gtk_widget_get_window gtk_widget_get_window; typedef void (*f_gtk_widget_set_sensitive)(GtkWidget *widget, gboolean sensitive); extern f_gtk_widget_set_sensitive gtk_widget_set_sensitive; typedef void (*f_gtk_widget_realize)(GtkWidget *widget); extern f_gtk_widget_realize gtk_widget_realize; typedef gboolean (*f_gtk_widget_hide_on_delete)(GtkWidget *widget); extern f_gtk_widget_hide_on_delete gtk_widget_hide_on_delete; typedef void (*f_gtk_widget_destroy)(GtkWidget *widget); extern f_gtk_widget_destroy gtk_widget_destroy; typedef GtkClipboard* (*f_gtk_clipboard_get)(GdkAtom selection); extern f_gtk_clipboard_get gtk_clipboard_get; typedef void (*f_gtk_clipboard_store)(GtkClipboard *clipboard); extern f_gtk_clipboard_store gtk_clipboard_store; typedef GtkWidget* (*f_gtk_file_chooser_dialog_new)(const gchar *title, GtkWindow *parent, GtkFileChooserAction action, const gchar *first_button_text, ...) G_GNUC_NULL_TERMINATED; extern f_gtk_file_chooser_dialog_new gtk_file_chooser_dialog_new; typedef gboolean (*f_gtk_file_chooser_set_current_folder)(GtkFileChooser *chooser, const gchar *filename); extern f_gtk_file_chooser_set_current_folder gtk_file_chooser_set_current_folder; typedef gchar* (*f_gtk_file_chooser_get_current_folder)(GtkFileChooser *chooser); extern f_gtk_file_chooser_get_current_folder gtk_file_chooser_get_current_folder; typedef void (*f_gtk_file_chooser_set_current_name)(GtkFileChooser *chooser, const gchar *name); extern f_gtk_file_chooser_set_current_name gtk_file_chooser_set_current_name; typedef gboolean (*f_gtk_file_chooser_select_filename)(GtkFileChooser *chooser, const gchar *filename); extern f_gtk_file_chooser_select_filename gtk_file_chooser_select_filename; typedef GSList* (*f_gtk_file_chooser_get_filenames)(GtkFileChooser *chooser); extern f_gtk_file_chooser_get_filenames gtk_file_chooser_get_filenames; typedef void (*f_gtk_file_chooser_set_filter)(GtkFileChooser *chooser, GtkFileFilter *filter); extern f_gtk_file_chooser_set_filter gtk_file_chooser_set_filter; typedef GtkFileFilter* (*f_gtk_file_chooser_get_filter)(GtkFileChooser *chooser); extern f_gtk_file_chooser_get_filter gtk_file_chooser_get_filter; typedef void (*f_gtk_window_set_title)(GtkWindow *window, const gchar *title); extern f_gtk_window_set_title gtk_window_set_title; typedef void (*f_gtk_file_chooser_set_local_only)(GtkFileChooser *chooser, gboolean local_only); extern f_gtk_file_chooser_set_local_only gtk_file_chooser_set_local_only; typedef void (*f_gtk_file_chooser_set_action)(GtkFileChooser *chooser, GtkFileChooserAction action); extern f_gtk_file_chooser_set_action gtk_file_chooser_set_action; typedef void (*f_gtk_file_chooser_set_select_multiple)(GtkFileChooser *chooser, gboolean select_multiple); extern f_gtk_file_chooser_set_select_multiple gtk_file_chooser_set_select_multiple; typedef void (*f_gtk_file_chooser_set_do_overwrite_confirmation)(GtkFileChooser *chooser, gboolean do_overwrite_confirmation); extern f_gtk_file_chooser_set_do_overwrite_confirmation gtk_file_chooser_set_do_overwrite_confirmation; typedef GtkWidget* (*f_gtk_dialog_get_widget_for_response)(GtkDialog *dialog, gint response_id); extern f_gtk_dialog_get_widget_for_response gtk_dialog_get_widget_for_response; typedef void (*f_gtk_button_set_label)(GtkButton *button, const gchar *label); extern f_gtk_button_set_label gtk_button_set_label; typedef void (*f_gtk_file_chooser_remove_filter)(GtkFileChooser *chooser, GtkFileFilter *filter); extern f_gtk_file_chooser_remove_filter gtk_file_chooser_remove_filter; typedef void (*f_gtk_file_filter_set_name)(GtkFileFilter *filter, const gchar *name); extern f_gtk_file_filter_set_name gtk_file_filter_set_name; typedef void (*f_gtk_file_filter_add_pattern)(GtkFileFilter *filter, const gchar *pattern); extern f_gtk_file_filter_add_pattern gtk_file_filter_add_pattern; typedef void (*f_gtk_file_chooser_add_filter)(GtkFileChooser *chooser, GtkFileFilter *filter); extern f_gtk_file_chooser_add_filter gtk_file_chooser_add_filter; typedef void (*f_gtk_file_chooser_set_preview_widget)(GtkFileChooser *chooser, GtkWidget *preview_widget); extern f_gtk_file_chooser_set_preview_widget gtk_file_chooser_set_preview_widget; typedef gchar* (*f_gtk_file_chooser_get_preview_filename)(GtkFileChooser *chooser); extern f_gtk_file_chooser_get_preview_filename gtk_file_chooser_get_preview_filename; typedef void (*f_gtk_file_chooser_set_preview_widget_active)(GtkFileChooser *chooser, gboolean active); extern f_gtk_file_chooser_set_preview_widget_active gtk_file_chooser_set_preview_widget_active; typedef GtkFileFilter* (*f_gtk_file_filter_new)(void); extern f_gtk_file_filter_new gtk_file_filter_new; typedef GtkWidget* (*f_gtk_image_new)(void); extern f_gtk_image_new gtk_image_new; typedef void (*f_gtk_image_set_from_pixbuf)(GtkImage *image, GdkPixbuf *pixbuf); extern f_gtk_image_set_from_pixbuf gtk_image_set_from_pixbuf; typedef void (*f_gdk_set_allowed_backends)(const gchar *backends); extern f_gdk_set_allowed_backends gdk_set_allowed_backends; typedef void (*f_gdk_window_set_modal_hint)(GdkWindow *window, gboolean modal); extern f_gdk_window_set_modal_hint gdk_window_set_modal_hint; typedef void (*f_gdk_window_focus)(GdkWindow *window, guint32 timestamp); extern f_gdk_window_focus gdk_window_focus; typedef GTypeInstance* (*f_g_type_check_instance_cast)(GTypeInstance *instance, GType iface_type); extern f_g_type_check_instance_cast g_type_check_instance_cast; template <typename Result, typename Object> inline Result *g_type_cic_helper(Object *instance, GType iface_type) { return reinterpret_cast<Result*>(g_type_check_instance_cast(reinterpret_cast<GTypeInstance*>(instance), iface_type)); } template <typename Object> inline GtkMenu *gtk_menu_cast(Object *obj) { return g_type_cic_helper<GtkMenu, Object>(obj, gtk_menu_get_type()); } template <typename Object> inline GtkMenuShell *gtk_menu_shell_cast(Object *obj) { return g_type_cic_helper<GtkMenuShell, Object>(obj, gtk_menu_get_type()); } typedef GType (*f_gtk_dialog_get_type)(void) G_GNUC_CONST; extern f_gtk_dialog_get_type gtk_dialog_get_type; template <typename Object> inline GtkDialog *gtk_dialog_cast(Object *obj) { return g_type_cic_helper<GtkDialog, Object>(obj, gtk_dialog_get_type()); } template <typename Object> inline GObject *g_object_cast(Object *obj) { return g_type_cic_helper<GObject, Object>(obj, G_TYPE_OBJECT); } typedef GType (*f_gtk_file_chooser_get_type)(void) G_GNUC_CONST; extern f_gtk_file_chooser_get_type gtk_file_chooser_get_type; template <typename Object> inline GtkFileChooser *gtk_file_chooser_cast(Object *obj) { return g_type_cic_helper<GtkFileChooser, Object>(obj, gtk_file_chooser_get_type()); } typedef GType (*f_gtk_image_get_type)(void) G_GNUC_CONST; extern f_gtk_image_get_type gtk_image_get_type; template <typename Object> inline GtkImage *gtk_image_cast(Object *obj) { return g_type_cic_helper<GtkImage, Object>(obj, gtk_image_get_type()); } typedef GType (*f_gtk_button_get_type)(void) G_GNUC_CONST; extern f_gtk_button_get_type gtk_button_get_type; template <typename Object> inline GtkButton *gtk_button_cast(Object *obj) { return g_type_cic_helper<GtkButton, Object>(obj, gtk_button_get_type()); } typedef GType (*f_gtk_window_get_type)(void) G_GNUC_CONST; extern f_gtk_window_get_type gtk_window_get_type; template <typename Object> inline GtkWindow *gtk_window_cast(Object *obj) { return g_type_cic_helper<GtkWindow, Object>(obj, gtk_window_get_type()); } typedef gboolean (*f_g_type_check_instance_is_a)(GTypeInstance *instance, GType iface_type); extern f_g_type_check_instance_is_a g_type_check_instance_is_a; template <typename Object> inline bool g_type_cit_helper(Object *instance, GType iface_type) { if (!instance) return false; auto ginstance = reinterpret_cast<GTypeInstance*>(instance); if (ginstance->g_class && ginstance->g_class->g_type == iface_type) { return true; } return g_type_check_instance_is_a(ginstance, iface_type); } typedef gint (*f_gtk_dialog_run)(GtkDialog *dialog); extern f_gtk_dialog_run gtk_dialog_run; typedef gulong (*f_g_signal_connect_data)(gpointer instance, const gchar *detailed_signal, GCallback c_handler, gpointer data, GClosureNotify destroy_data, GConnectFlags connect_flags); extern f_g_signal_connect_data g_signal_connect_data; inline gulong g_signal_connect_helper(gpointer instance, const gchar *detailed_signal, GCallback c_handler, gpointer data, GClosureNotify destroy_data = nullptr) { return g_signal_connect_data(instance, detailed_signal, c_handler, data, destroy_data, (GConnectFlags)0); } inline gulong g_signal_connect_swapped_helper(gpointer instance, const gchar *detailed_signal, GCallback c_handler, gpointer data, GClosureNotify destroy_data = nullptr) { return g_signal_connect_data(instance, detailed_signal, c_handler, data, destroy_data, G_CONNECT_SWAPPED); } typedef void (*f_g_signal_handler_disconnect)(gpointer instance, gulong handler_id); extern f_g_signal_handler_disconnect g_signal_handler_disconnect; typedef AppIndicator* (*f_app_indicator_new)(const gchar *id, const gchar *icon_name, AppIndicatorCategory category); extern f_app_indicator_new app_indicator_new; typedef void (*f_app_indicator_set_status)(AppIndicator *self, AppIndicatorStatus status); extern f_app_indicator_set_status app_indicator_set_status; typedef void (*f_app_indicator_set_menu)(AppIndicator *self, GtkMenu *menu); extern f_app_indicator_set_menu app_indicator_set_menu; typedef void (*f_app_indicator_set_icon_full)(AppIndicator *self, const gchar *icon_name, const gchar *icon_desc); extern f_app_indicator_set_icon_full app_indicator_set_icon_full; typedef gboolean (*f_gdk_init_check)(gint *argc, gchar ***argv); extern f_gdk_init_check gdk_init_check; typedef GdkPixbuf* (*f_gdk_pixbuf_new_from_data)(const guchar *data, GdkColorspace colorspace, gboolean has_alpha, int bits_per_sample, int width, int height, int rowstride, GdkPixbufDestroyNotify destroy_fn, gpointer destroy_fn_data); extern f_gdk_pixbuf_new_from_data gdk_pixbuf_new_from_data; typedef GdkPixbuf* (*f_gdk_pixbuf_new_from_file)(const gchar *filename, GError **error); extern f_gdk_pixbuf_new_from_file gdk_pixbuf_new_from_file; typedef GdkPixbuf* (*f_gdk_pixbuf_new_from_file_at_size)(const gchar *filename, int width, int height, GError **error); extern f_gdk_pixbuf_new_from_file_at_size gdk_pixbuf_new_from_file_at_size; typedef GtkStatusIcon* (*f_gtk_status_icon_new_from_pixbuf)(GdkPixbuf *pixbuf); extern f_gtk_status_icon_new_from_pixbuf gtk_status_icon_new_from_pixbuf; typedef void (*f_gtk_status_icon_set_from_pixbuf)(GtkStatusIcon *status_icon, GdkPixbuf *pixbuf); extern f_gtk_status_icon_set_from_pixbuf gtk_status_icon_set_from_pixbuf; typedef GtkStatusIcon* (*f_gtk_status_icon_new_from_file)(const gchar *filename); extern f_gtk_status_icon_new_from_file gtk_status_icon_new_from_file; typedef void (*f_gtk_status_icon_set_from_file)(GtkStatusIcon *status_icon, const gchar *filename); extern f_gtk_status_icon_set_from_file gtk_status_icon_set_from_file; typedef void (*f_gtk_status_icon_set_title)(GtkStatusIcon *status_icon, const gchar *title); extern f_gtk_status_icon_set_title gtk_status_icon_set_title; typedef void (*f_gtk_status_icon_set_tooltip_text)(GtkStatusIcon *status_icon, const gchar *title); extern f_gtk_status_icon_set_tooltip_text gtk_status_icon_set_tooltip_text; typedef void (*f_gtk_status_icon_set_visible)(GtkStatusIcon *status_icon, gboolean visible); extern f_gtk_status_icon_set_visible gtk_status_icon_set_visible; typedef gboolean (*f_gtk_status_icon_is_embedded)(GtkStatusIcon *status_icon); extern f_gtk_status_icon_is_embedded gtk_status_icon_is_embedded; typedef gboolean (*f_gtk_status_icon_get_geometry)(GtkStatusIcon *status_icon, GdkScreen **screen, GdkRectangle *area, GtkOrientation *orientation); extern f_gtk_status_icon_get_geometry gtk_status_icon_get_geometry; typedef void (*f_gtk_status_icon_position_menu)(GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer user_data); extern f_gtk_status_icon_position_menu gtk_status_icon_position_menu; typedef void (*f_gtk_menu_popup)(GtkMenu *menu, GtkWidget *parent_menu_shell, GtkWidget *parent_menu_item, GtkMenuPositionFunc func, gpointer data, guint button, guint32 activate_time); extern f_gtk_menu_popup gtk_menu_popup; typedef guint32 (*f_gtk_get_current_event_time)(void); extern f_gtk_get_current_event_time gtk_get_current_event_time; typedef gpointer (*f_g_object_ref_sink)(gpointer object); extern f_g_object_ref_sink g_object_ref_sink; typedef void (*f_g_object_unref)(gpointer object); extern f_g_object_unref g_object_unref; typedef guint (*f_g_idle_add)(GSourceFunc function, gpointer data); extern f_g_idle_add g_idle_add; typedef void (*f_g_free)(gpointer mem); extern f_g_free g_free; typedef void (*f_g_list_foreach)(GList *list, GFunc func, gpointer user_data); extern f_g_list_foreach g_list_foreach; typedef void (*f_g_list_free)(GList *list); extern f_g_list_free g_list_free; typedef void (*f_g_list_free_full)(GList *list, GDestroyNotify free_func); extern f_g_list_free_full g_list_free_full; typedef void (*f_g_error_free)(GError *error); extern f_g_error_free g_error_free; typedef void (*f_g_slist_free)(GSList *list); extern f_g_slist_free g_slist_free; #ifndef TDESKTOP_DISABLE_UNITY_INTEGRATION typedef void (*f_unity_launcher_entry_set_count)(UnityLauncherEntry* self, gint64 value); extern f_unity_launcher_entry_set_count unity_launcher_entry_set_count; typedef void (*f_unity_launcher_entry_set_count_visible)(UnityLauncherEntry* self, gboolean value); extern f_unity_launcher_entry_set_count_visible unity_launcher_entry_set_count_visible; typedef UnityLauncherEntry* (*f_unity_launcher_entry_get_for_desktop_id)(const gchar* desktop_id); extern f_unity_launcher_entry_get_for_desktop_id unity_launcher_entry_get_for_desktop_id; #endif // !TDESKTOP_DISABLE_UNITY_INTEGRATION #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION } // namespace Libs } // namespace Platform �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/linux/main_window_linux.cpp���������������������������0000664�0000000�0000000�00000051127�13262451251�0026652�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "platform/linux/main_window_linux.h" #include "styles/style_window.h" #include "platform/linux/linux_libs.h" #include "platform/linux/linux_desktop_environment.h" #include "platform/platform_notifications_manager.h" #include "history/history.h" #include "mainwindow.h" #include "messenger.h" #include "application.h" #include "lang/lang_keys.h" #include "storage/localstorage.h" namespace Platform { namespace { bool noQtTrayIcon = false, tryAppIndicator = false; bool useGtkBase = false, useAppIndicator = false, useStatusIcon = false, trayIconChecked = false, useUnityCount = false; #ifndef TDESKTOP_DISABLE_GTK_INTEGRATION AppIndicator *_trayIndicator = 0; GtkStatusIcon *_trayIcon = 0; GtkWidget *_trayMenu = 0; GdkPixbuf *_trayPixbuf = 0; QByteArray _trayPixbufData; QList<QPair<GtkWidget*, QObject*> > _trayItems; #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION int32 _trayIconSize = 22; bool _trayIconMuted = true; int32 _trayIconCount = 0; QImage _trayIconImageBack, _trayIconImage; #ifndef TDESKTOP_DISABLE_GTK_INTEGRATION void _trayIconPopup(GtkStatusIcon *status_icon, guint button, guint32 activate_time, gpointer popup_menu) { Libs::gtk_menu_popup(Libs::gtk_menu_cast(popup_menu), NULL, NULL, Libs::gtk_status_icon_position_menu, status_icon, button, activate_time); } void _trayIconActivate(GtkStatusIcon *status_icon, gpointer popup_menu) { if (App::wnd()->isActiveWindow() && App::wnd()->isVisible()) { Libs::gtk_menu_popup(Libs::gtk_menu_cast(popup_menu), NULL, NULL, Libs::gtk_status_icon_position_menu, status_icon, 0, Libs::gtk_get_current_event_time()); } else { App::wnd()->showFromTray(); } } gboolean _trayIconResized(GtkStatusIcon *status_icon, gint size, gpointer popup_menu) { _trayIconSize = size; if (Global::started()) Notify::unreadCounterUpdated(); return FALSE; } #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION #define QT_RED 0 #define QT_GREEN 1 #define QT_BLUE 2 #define QT_ALPHA 3 #ifndef TDESKTOP_DISABLE_GTK_INTEGRATION #define GTK_RED 2 #define GTK_GREEN 1 #define GTK_BLUE 0 #define GTK_ALPHA 3 #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION QImage _trayIconImageGen() { int32 counter = App::histories().unreadBadge(), counterSlice = (counter >= 1000) ? (1000 + (counter % 100)) : counter; bool muted = App::histories().unreadOnlyMuted(); if (_trayIconImage.isNull() || _trayIconImage.width() != _trayIconSize || muted != _trayIconMuted || counterSlice != _trayIconCount) { if (_trayIconImageBack.isNull() || _trayIconImageBack.width() != _trayIconSize) { _trayIconImageBack = Messenger::Instance().logo().scaled(_trayIconSize, _trayIconSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); _trayIconImageBack = _trayIconImageBack.convertToFormat(QImage::Format_ARGB32); int w = _trayIconImageBack.width(), h = _trayIconImageBack.height(), perline = _trayIconImageBack.bytesPerLine(); uchar *bytes = _trayIconImageBack.bits(); for (int32 y = 0; y < h; ++y) { for (int32 x = 0; x < w; ++x) { int32 srcoff = y * perline + x * 4; bytes[srcoff + QT_RED ] = qMax(bytes[srcoff + QT_RED ], uchar(224)); bytes[srcoff + QT_GREEN] = qMax(bytes[srcoff + QT_GREEN], uchar(165)); bytes[srcoff + QT_BLUE ] = qMax(bytes[srcoff + QT_BLUE ], uchar(44)); } } } _trayIconImage = _trayIconImageBack; if (counter > 0) { QPainter p(&_trayIconImage); int32 layerSize = -16; if (_trayIconSize >= 48) { layerSize = -32; } else if (_trayIconSize >= 36) { layerSize = -24; } else if (_trayIconSize >= 32) { layerSize = -20; } auto &bg = (muted ? st::trayCounterBgMute : st::trayCounterBg); auto &fg = st::trayCounterFg; auto layer = App::wnd()->iconWithCounter(layerSize, counter, bg, fg, false); p.drawImage(_trayIconImage.width() - layer.width() - 1, _trayIconImage.height() - layer.height() - 1, layer); } } return _trayIconImage; } QString _trayIconImageFile() { int32 counter = App::histories().unreadBadge(), counterSlice = (counter >= 1000) ? (1000 + (counter % 100)) : counter; bool muted = App::histories().unreadOnlyMuted(); QString name = cWorkingDir() + qsl("tdata/ticons/ico%1_%2_%3.png").arg(muted ? "mute" : "").arg(_trayIconSize).arg(counterSlice); QFileInfo info(name); if (info.exists()) return name; QImage img = _trayIconImageGen(); if (img.save(name, "PNG")) return name; QDir dir(info.absoluteDir()); if (!dir.exists()) { dir.mkpath(dir.absolutePath()); if (img.save(name, "PNG")) return name; } return QString(); } #ifndef TDESKTOP_DISABLE_GTK_INTEGRATION void loadPixbuf(QImage image) { int w = image.width(), h = image.height(), perline = image.bytesPerLine(), s = image.byteCount(); _trayPixbufData.resize(w * h * 4); uchar *result = (uchar*)_trayPixbufData.data(), *bytes = image.bits(); for (int32 y = 0; y < h; ++y) { for (int32 x = 0; x < w; ++x) { int32 offset = (y * w + x) * 4, srcoff = y * perline + x * 4; result[offset + GTK_RED ] = bytes[srcoff + QT_RED ]; result[offset + GTK_GREEN] = bytes[srcoff + QT_GREEN]; result[offset + GTK_BLUE ] = bytes[srcoff + QT_BLUE ]; result[offset + GTK_ALPHA] = bytes[srcoff + QT_ALPHA]; } } if (_trayPixbuf) Libs::g_object_unref(_trayPixbuf); _trayPixbuf = Libs::gdk_pixbuf_new_from_data(result, GDK_COLORSPACE_RGB, true, 8, w, h, w * 4, 0, 0); } void _trayMenuCallback(GtkMenu *menu, gpointer data) { for (int32 i = 0, l = _trayItems.size(); i < l; ++i) { if ((void*)_trayItems.at(i).first == (void*)menu) { QMetaObject::invokeMethod(_trayItems.at(i).second, "triggered"); } } } static gboolean _trayIconCheck(gpointer/* pIn*/) { if (useStatusIcon && !trayIconChecked) { if (Libs::gtk_status_icon_is_embedded(_trayIcon)) { trayIconChecked = true; cSetSupportTray(true); if (Global::started()) { Global::RefWorkMode().setForced(Global::WorkMode().value(), true); } if (App::wnd()) { Notify::unreadCounterUpdated(); App::wnd()->updateTrayMenu(); } } } return FALSE; } #ifndef TDESKTOP_DISABLE_UNITY_INTEGRATION UnityLauncherEntry *_psUnityLauncherEntry = nullptr; #endif // !TDESKTOP_DISABLE_UNITY_INTEGRATION #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION } // namespace MainWindow::MainWindow() { connect(&_psCheckStatusIconTimer, SIGNAL(timeout()), this, SLOT(psStatusIconCheck())); _psCheckStatusIconTimer.setSingleShot(false); connect(&_psUpdateIndicatorTimer, SIGNAL(timeout()), this, SLOT(psUpdateIndicator())); _psUpdateIndicatorTimer.setSingleShot(true); } bool MainWindow::hasTrayIcon() const { #ifndef TDESKTOP_DISABLE_GTK_INTEGRATION return trayIcon || ((useAppIndicator || (useStatusIcon && trayIconChecked)) && (Global::WorkMode().value() != dbiwmWindowOnly)); #else return trayIcon; #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION } void MainWindow::psStatusIconCheck() { #ifndef TDESKTOP_DISABLE_GTK_INTEGRATION _trayIconCheck(0); #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION if (cSupportTray() || !--_psCheckStatusIconLeft) { _psCheckStatusIconTimer.stop(); return; } } void MainWindow::psShowTrayMenu() { } void MainWindow::psTrayMenuUpdated() { #ifndef TDESKTOP_DISABLE_GTK_INTEGRATION if (noQtTrayIcon && (useAppIndicator || useStatusIcon)) { const QList<QAction*> &actions = trayIconMenu->actions(); if (_trayItems.isEmpty()) { DEBUG_LOG(("Creating tray menu!")); for (int32 i = 0, l = actions.size(); i != l; ++i) { GtkWidget *item = Libs::gtk_menu_item_new_with_label(actions.at(i)->text().toUtf8()); Libs::gtk_menu_shell_append(Libs::gtk_menu_shell_cast(_trayMenu), item); Libs::g_signal_connect_helper(item, "activate", G_CALLBACK(_trayMenuCallback), this); Libs::gtk_widget_show(item); Libs::gtk_widget_set_sensitive(item, actions.at(i)->isEnabled()); _trayItems.push_back(qMakePair(item, actions.at(i))); } } else { DEBUG_LOG(("Updating tray menu!")); for (int32 i = 0, l = actions.size(); i != l; ++i) { if (i < _trayItems.size()) { Libs::gtk_menu_item_set_label(reinterpret_cast<GtkMenuItem*>(_trayItems.at(i).first), actions.at(i)->text().toUtf8()); Libs::gtk_widget_set_sensitive(_trayItems.at(i).first, actions.at(i)->isEnabled()); } } } } #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION } void MainWindow::psSetupTrayIcon() { if (noQtTrayIcon) { if (!cSupportTray()) return; updateIconCounters(); } else { LOG(("Using Qt tray icon.")); if (!trayIcon) { trayIcon = new QSystemTrayIcon(this); QIcon icon; QFileInfo iconFile(_trayIconImageFile()); if (iconFile.exists()) { QByteArray path = QFile::encodeName(iconFile.absoluteFilePath()); icon = QIcon(path.constData()); } else { icon = Window::CreateIcon(); } trayIcon->setIcon(icon); trayIcon->setToolTip(str_const_toString(AppName)); connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(toggleTray(QSystemTrayIcon::ActivationReason)), Qt::UniqueConnection); // This is very important for native notifications via libnotify! // Some notification servers compose several notifications with a "Reply" // action into one and after that a click on "Reply" button does not call // the specified callback from any of the sent notification - libnotify // just ignores ibus messages, but Qt tray icon at least emits this signal. connect(trayIcon, SIGNAL(messageClicked()), this, SLOT(showFromTray())); App::wnd()->updateTrayMenu(); } updateIconCounters(); trayIcon->show(); } } void MainWindow::workmodeUpdated(DBIWorkMode mode) { if (!cSupportTray()) return; if (mode == dbiwmWindowOnly) { if (noQtTrayIcon) { #ifndef TDESKTOP_DISABLE_GTK_INTEGRATION if (useAppIndicator) { Libs::app_indicator_set_status(_trayIndicator, APP_INDICATOR_STATUS_PASSIVE); } else if (useStatusIcon) { Libs::gtk_status_icon_set_visible(_trayIcon, false); } #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION } else { if (trayIcon) { trayIcon->setContextMenu(0); trayIcon->deleteLater(); } trayIcon = 0; } } else { if (noQtTrayIcon) { #ifndef TDESKTOP_DISABLE_GTK_INTEGRATION if (useAppIndicator) { Libs::app_indicator_set_status(_trayIndicator, APP_INDICATOR_STATUS_ACTIVE); } else if (useStatusIcon) { Libs::gtk_status_icon_set_visible(_trayIcon, true); } #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION } else { psSetupTrayIcon(); } } } void MainWindow::psUpdateIndicator() { #ifndef TDESKTOP_DISABLE_GTK_INTEGRATION _psUpdateIndicatorTimer.stop(); _psLastIndicatorUpdate = getms(); QFileInfo iconFile(_trayIconImageFile()); if (iconFile.exists()) { QByteArray path = QFile::encodeName(iconFile.absoluteFilePath()), name = QFile::encodeName(iconFile.fileName()); name = name.mid(0, name.size() - 4); Libs::app_indicator_set_icon_full(_trayIndicator, path.constData(), name); } else { useAppIndicator = false; } #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION } void MainWindow::unreadCounterChangedHook() { setWindowTitle(titleText()); updateIconCounters(); } void MainWindow::updateIconCounters() { updateWindowIcon(); auto counter = App::histories().unreadBadge(); #if !defined(TDESKTOP_DISABLE_GTK_INTEGRATION) && !defined(TDESKTOP_DISABLE_UNITY_INTEGRATION) if (_psUnityLauncherEntry) { if (counter > 0) { Libs::unity_launcher_entry_set_count(_psUnityLauncherEntry, (counter > 9999) ? 9999 : counter); Libs::unity_launcher_entry_set_count_visible(_psUnityLauncherEntry, TRUE); } else { Libs::unity_launcher_entry_set_count_visible(_psUnityLauncherEntry, FALSE); } } #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION && !TDESKTOP_DISABLE_UNITY_INTEGRATION if (noQtTrayIcon) { #ifndef TDESKTOP_DISABLE_GTK_INTEGRATION if (useAppIndicator) { if (getms() > _psLastIndicatorUpdate + 1000) { psUpdateIndicator(); } else if (!_psUpdateIndicatorTimer.isActive()) { _psUpdateIndicatorTimer.start(100); } } else if (useStatusIcon && trayIconChecked) { QFileInfo iconFile(_trayIconImageFile()); if (iconFile.exists()) { QByteArray path = QFile::encodeName(iconFile.absoluteFilePath()); Libs::gtk_status_icon_set_from_file(_trayIcon, path.constData()); } else { loadPixbuf(_trayIconImageGen()); Libs::gtk_status_icon_set_from_pixbuf(_trayIcon, _trayPixbuf); } } #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION } else if (trayIcon) { QIcon icon; QFileInfo iconFile(_trayIconImageFile()); if (iconFile.exists()) { QByteArray path = QFile::encodeName(iconFile.absoluteFilePath()); icon = QIcon(path.constData()); } else { int32 counter = App::histories().unreadBadge(); bool muted = App::histories().unreadOnlyMuted(); auto &bg = (muted ? st::trayCounterBgMute : st::trayCounterBg); auto &fg = st::trayCounterFg; icon.addPixmap(App::pixmapFromImageInPlace(iconWithCounter(16, counter, bg, fg, true))); icon.addPixmap(App::pixmapFromImageInPlace(iconWithCounter(32, counter, bg, fg, true))); } trayIcon->setIcon(icon); } } void MainWindow::LibsLoaded() { noQtTrayIcon = !DesktopEnvironment::TryQtTrayIcon(); #ifndef TDESKTOP_DISABLE_GTK_INTEGRATION tryAppIndicator = DesktopEnvironment::PreferAppIndicatorTrayIcon(); #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION LOG(("Tray Icon: Try Qt = %1, Prefer appindicator = %2").arg(Logs::b(!noQtTrayIcon)).arg(Logs::b(tryAppIndicator))); if (noQtTrayIcon) cSetSupportTray(false); #ifndef TDESKTOP_DISABLE_GTK_INTEGRATION useGtkBase = (Libs::gtk_init_check != nullptr) && (Libs::gtk_menu_new != nullptr) && (Libs::gtk_menu_get_type != nullptr) && (Libs::gtk_menu_item_new_with_label != nullptr) && (Libs::gtk_menu_item_set_label != nullptr) && (Libs::gtk_menu_shell_append != nullptr) && (Libs::gtk_menu_shell_get_type != nullptr) && (Libs::gtk_widget_show != nullptr) && (Libs::gtk_widget_get_toplevel != nullptr) && (Libs::gtk_widget_get_visible != nullptr) && (Libs::gtk_widget_set_sensitive != nullptr) && (Libs::g_type_check_instance_cast != nullptr) && (Libs::g_signal_connect_data != nullptr) && (Libs::g_object_ref_sink != nullptr) && (Libs::g_object_unref != nullptr); useAppIndicator = useGtkBase && (Libs::app_indicator_new != nullptr) && (Libs::app_indicator_set_status != nullptr) && (Libs::app_indicator_set_menu != nullptr) && (Libs::app_indicator_set_icon_full != nullptr); if (tryAppIndicator && useGtkBase && useAppIndicator) { noQtTrayIcon = true; cSetSupportTray(false); } useStatusIcon = (Libs::gdk_init_check != nullptr) && (Libs::gdk_pixbuf_new_from_data != nullptr) && (Libs::gtk_status_icon_new_from_pixbuf != nullptr) && (Libs::gtk_status_icon_set_from_pixbuf != nullptr) && (Libs::gtk_status_icon_new_from_file != nullptr) && (Libs::gtk_status_icon_set_from_file != nullptr) && (Libs::gtk_status_icon_set_title != nullptr) && (Libs::gtk_status_icon_set_tooltip_text != nullptr) && (Libs::gtk_status_icon_set_visible != nullptr) && (Libs::gtk_status_icon_is_embedded != nullptr) && (Libs::gtk_status_icon_get_geometry != nullptr) && (Libs::gtk_status_icon_position_menu != nullptr) && (Libs::gtk_menu_popup != nullptr) && (Libs::gtk_get_current_event_time != nullptr) && (Libs::g_idle_add != nullptr); if (useStatusIcon) { DEBUG_LOG(("Status icon api loaded!")); } #ifndef TDESKTOP_DISABLE_UNITY_INTEGRATION useUnityCount = (Libs::unity_launcher_entry_get_for_desktop_id != nullptr) && (Libs::unity_launcher_entry_set_count != nullptr) && (Libs::unity_launcher_entry_set_count_visible != nullptr); if (useUnityCount) { DEBUG_LOG(("Unity count api loaded!")); } #endif // !TDESKTOP_DISABLE_UNITY_INTEGRATION #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION } void MainWindow::psCreateTrayIcon() { if (!noQtTrayIcon) { LOG(("Tray Icon: Using Qt tray icon, available: %1").arg(Logs::b(QSystemTrayIcon::isSystemTrayAvailable()))); cSetSupportTray(QSystemTrayIcon::isSystemTrayAvailable()); return; } #ifndef TDESKTOP_DISABLE_GTK_INTEGRATION if (useAppIndicator) { DEBUG_LOG(("Trying to create AppIndicator")); _trayMenu = Libs::gtk_menu_new(); if (_trayMenu) { DEBUG_LOG(("Created gtk menu for appindicator!")); QFileInfo iconFile(_trayIconImageFile()); if (iconFile.exists()) { QByteArray path = QFile::encodeName(iconFile.absoluteFilePath()); _trayIndicator = Libs::app_indicator_new("Telegram Desktop", path.constData(), APP_INDICATOR_CATEGORY_APPLICATION_STATUS); if (_trayIndicator) { LOG(("Tray Icon: Using appindicator tray icon.")); } else { DEBUG_LOG(("Failed to app_indicator_new()!")); } } else { useAppIndicator = false; DEBUG_LOG(("Failed to create image file!")); } } else { DEBUG_LOG(("Failed to gtk_menu_new()!")); } if (_trayMenu && _trayIndicator) { Libs::app_indicator_set_status(_trayIndicator, APP_INDICATOR_STATUS_ACTIVE); Libs::app_indicator_set_menu(_trayIndicator, Libs::gtk_menu_cast(_trayMenu)); useStatusIcon = false; } else { DEBUG_LOG(("AppIndicator failed!")); useAppIndicator = false; } } if (useStatusIcon) { if (Libs::gdk_init_check(0, 0)) { if (!_trayMenu) _trayMenu = Libs::gtk_menu_new(); if (_trayMenu) { QFileInfo iconFile(_trayIconImageFile()); if (iconFile.exists()) { QByteArray path = QFile::encodeName(iconFile.absoluteFilePath()); _trayIcon = Libs::gtk_status_icon_new_from_file(path.constData()); } else { loadPixbuf(_trayIconImageGen()); _trayIcon = Libs::gtk_status_icon_new_from_pixbuf(_trayPixbuf); } if (_trayIcon) { LOG(("Tray Icon: Using GTK status tray icon.")); Libs::g_signal_connect_helper(_trayIcon, "popup-menu", GCallback(_trayIconPopup), _trayMenu); Libs::g_signal_connect_helper(_trayIcon, "activate", GCallback(_trayIconActivate), _trayMenu); Libs::g_signal_connect_helper(_trayIcon, "size-changed", GCallback(_trayIconResized), _trayMenu); Libs::gtk_status_icon_set_title(_trayIcon, "Telegram Desktop"); Libs::gtk_status_icon_set_tooltip_text(_trayIcon, "Telegram Desktop"); Libs::gtk_status_icon_set_visible(_trayIcon, true); } else { useStatusIcon = false; } } else { useStatusIcon = false; } } else { useStatusIcon = false; } } if (!useStatusIcon && !useAppIndicator) { LOG(("Tray Icon: Not able to use any tray icon :(")); if (_trayMenu) { Libs::g_object_ref_sink(_trayMenu); Libs::g_object_unref(_trayMenu); _trayMenu = nullptr; } } cSetSupportTray(useAppIndicator); if (useStatusIcon) { Libs::g_idle_add((GSourceFunc)_trayIconCheck, 0); _psCheckStatusIconTimer.start(100); } else { workmodeUpdated(Global::WorkMode().value()); } #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION } void MainWindow::psFirstShow() { psCreateTrayIcon(); #if !defined(TDESKTOP_DISABLE_GTK_INTEGRATION) && !defined(TDESKTOP_DISABLE_UNITY_INTEGRATION) if (useUnityCount) { _psUnityLauncherEntry = Libs::unity_launcher_entry_get_for_desktop_id("telegramdesktop.desktop"); if (_psUnityLauncherEntry) { LOG(("Found Unity Launcher entry telegramdesktop.desktop!")); } else { _psUnityLauncherEntry = Libs::unity_launcher_entry_get_for_desktop_id("Telegram.desktop"); if (_psUnityLauncherEntry) { LOG(("Found Unity Launcher entry Telegram.desktop!")); } else { LOG(("Could not get Unity Launcher entry!")); } } } else { LOG(("Not using Unity Launcher count.")); } #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION && !TDESKTOP_DISABLE_GTK_INTEGRATION psUpdateMargins(); bool showShadows = true; show(); //_private.enableShadow(winId()); if (cWindowPos().maximized) { DEBUG_LOG(("Window Pos: First show, setting maximized.")); setWindowState(Qt::WindowMaximized); } if ((cLaunchMode() == LaunchModeAutoStart && cStartMinimized()) || cStartInTray()) { setWindowState(Qt::WindowMinimized); if (Global::WorkMode().value() == dbiwmTrayOnly || Global::WorkMode().value() == dbiwmWindowAndTray) { hide(); } else { show(); } showShadows = false; } else { show(); } setPositionInited(); } void MainWindow::psInitSysMenu() { } void MainWindow::psUpdateMargins() { } MainWindow::~MainWindow() { #ifndef TDESKTOP_DISABLE_GTK_INTEGRATION if (_trayIcon) { Libs::g_object_unref(_trayIcon); _trayIcon = nullptr; } if (_trayPixbuf) { Libs::g_object_unref(_trayPixbuf); _trayPixbuf = nullptr; } if (_trayMenu) { Libs::g_object_ref_sink(_trayMenu); Libs::g_object_unref(_trayMenu); _trayMenu = nullptr; } if (_trayIndicator) { Libs::g_object_unref(_trayIndicator); _trayIndicator = nullptr; } #ifndef TDESKTOP_DISABLE_UNITY_INTEGRATION if (_psUnityLauncherEntry) { Libs::g_object_unref(_psUnityLauncherEntry); _psUnityLauncherEntry = nullptr; } #endif // ! TDESKTOP_DISABLE_UNITY_INTEGRATION #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION } } // namespace Platform �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/linux/main_window_linux.h�����������������������������0000664�0000000�0000000�00000002547�13262451251�0026321�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "platform/platform_main_window.h" namespace Platform { class MainWindow : public Window::MainWindow { Q_OBJECT public: MainWindow(); void psFirstShow(); void psInitSysMenu(); void psUpdateMargins(); void psRefreshTaskbarIcon() { } virtual QImage iconWithCounter(int size, int count, style::color bg, style::color fg, bool smallIcon) = 0; static void LibsLoaded(); ~MainWindow(); public slots: void psShowTrayMenu(); void psStatusIconCheck(); void psUpdateIndicator(); protected: void unreadCounterChangedHook() override; bool hasTrayIcon() const override; void workmodeUpdated(DBIWorkMode mode) override; QSystemTrayIcon *trayIcon = nullptr; QMenu *trayIconMenu = nullptr; void psTrayMenuUpdated(); void psSetupTrayIcon(); virtual void placeSmallCounter(QImage &img, int size, int count, style::color bg, const QPoint &shift, style::color color) = 0; private: void updateIconCounters(); void psCreateTrayIcon(); QTimer _psCheckStatusIconTimer; int _psCheckStatusIconLeft = 100; QTimer _psUpdateIndicatorTimer; TimeMs _psLastIndicatorUpdate = 0; }; } // namespace Platform ���������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp�����������������0000664�0000000�0000000�00000040753�13262451251�0030705�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "platform/linux/notifications_manager_linux.h" #include "window/notifications_utilities.h" #include "platform/linux/linux_libnotify.h" #include "platform/linux/linux_libs.h" #include "history/history.h" #include "lang/lang_keys.h" namespace Platform { namespace Notifications { #ifndef TDESKTOP_DISABLE_GTK_INTEGRATION namespace { bool LibNotifyLoaded() { return (Libs::notify_init != nullptr) && (Libs::notify_uninit != nullptr) && (Libs::notify_is_initted != nullptr) // && (Libs::notify_get_app_name != nullptr) // && (Libs::notify_set_app_name != nullptr) && (Libs::notify_get_server_caps != nullptr) && (Libs::notify_get_server_info != nullptr) && (Libs::notify_notification_new != nullptr) // && (Libs::notify_notification_update != nullptr) && (Libs::notify_notification_show != nullptr) // && (Libs::notify_notification_set_app_name != nullptr) && (Libs::notify_notification_set_timeout != nullptr) // && (Libs::notify_notification_set_category != nullptr) // && (Libs::notify_notification_set_urgency != nullptr) // && (Libs::notify_notification_set_icon_from_pixbuf != nullptr) && (Libs::notify_notification_set_image_from_pixbuf != nullptr) // && (Libs::notify_notification_set_hint != nullptr) // && (Libs::notify_notification_set_hint_int32 != nullptr) // && (Libs::notify_notification_set_hint_uint32 != nullptr) // && (Libs::notify_notification_set_hint_double != nullptr) && (Libs::notify_notification_set_hint_string != nullptr) // && (Libs::notify_notification_set_hint_byte != nullptr) // && (Libs::notify_notification_set_hint_byte_array != nullptr) // && (Libs::notify_notification_clear_hints != nullptr) && (Libs::notify_notification_add_action != nullptr) && (Libs::notify_notification_clear_actions != nullptr) && (Libs::notify_notification_close != nullptr) && (Libs::notify_notification_get_closed_reason != nullptr) && (Libs::g_object_ref_sink != nullptr) && (Libs::g_object_unref != nullptr) && (Libs::g_list_free_full != nullptr) && (Libs::g_error_free != nullptr) && (Libs::g_signal_connect_data != nullptr) && (Libs::g_signal_handler_disconnect != nullptr) // && (Libs::gdk_pixbuf_new_from_data != nullptr) && (Libs::gdk_pixbuf_new_from_file != nullptr); } QString escapeHtml(const QString &text) { auto result = QString(); auto copyFrom = 0, textSize = text.size(); auto data = text.constData(); for (auto i = 0; i != textSize; ++i) { auto ch = data[i]; if (ch == '<' || ch == '>' || ch == '&') { if (!copyFrom) { result.reserve(textSize * 5); } if (i > copyFrom) { result.append(data + copyFrom, i - copyFrom); } switch (ch.unicode()) { case '<': result.append(qstr("<")); break; case '>': result.append(qstr(">")); break; case '&': result.append(qstr("&")); break; } copyFrom = i + 1; } } if (copyFrom > 0) { result.append(data + copyFrom, textSize - copyFrom); return result; } return text; } class NotificationData { public: NotificationData(const std::shared_ptr<Manager*> &guarded, const QString &title, const QString &body, const QStringList &capabilities, PeerId peerId, MsgId msgId) : _data(Libs::notify_notification_new(title.toUtf8().constData(), body.toUtf8().constData(), nullptr)) { if (valid()) { init(guarded, capabilities, peerId, msgId); } } bool valid() const { return (_data != nullptr); } NotificationData(const NotificationData &other) = delete; NotificationData &operator=(const NotificationData &other) = delete; NotificationData(NotificationData &&other) = delete; NotificationData &operator=(NotificationData &&other) = delete; void setImage(const QString &imagePath) { auto imagePathNative = QFile::encodeName(imagePath); if (auto pixbuf = Libs::gdk_pixbuf_new_from_file(imagePathNative.constData(), nullptr)) { Libs::notify_notification_set_image_from_pixbuf(_data, pixbuf); Libs::g_object_unref(Libs::g_object_cast(pixbuf)); } } bool show() { if (valid()) { GError *error = nullptr; Libs::notify_notification_show(_data, &error); if (!error) { return true; } logError(error); } return false; } bool close() { if (valid()) { GError *error = nullptr; Libs::notify_notification_close(_data, &error); if (!error) { return true; } logError(error); } return false; } ~NotificationData() { if (valid()) { // if (_handlerId > 0) { // Libs::g_signal_handler_disconnect(Libs::g_object_cast(_data), _handlerId); // } // Libs::notify_notification_clear_actions(_data); Libs::g_object_unref(Libs::g_object_cast(_data)); } } private: void init(const std::shared_ptr<Manager*> &guarded, const QStringList &capabilities, PeerId peerId, MsgId msgId) { if (capabilities.contains(qsl("append"))) { Libs::notify_notification_set_hint_string(_data, "append", "true"); } else if (capabilities.contains(qsl("x-canonical-append"))) { Libs::notify_notification_set_hint_string(_data, "x-canonical-append", "true"); } auto signalReceiver = Libs::g_object_cast(_data); auto signalHandler = G_CALLBACK(NotificationData::notificationClosed); auto signalName = "closed"; auto signalDataFreeMethod = &NotificationData::notificationDataFreeClosure; auto signalData = new NotificationDataStruct(guarded, peerId, msgId); _handlerId = Libs::g_signal_connect_helper(signalReceiver, signalName, signalHandler, signalData, signalDataFreeMethod); Libs::notify_notification_set_timeout(_data, Libs::NOTIFY_EXPIRES_DEFAULT); if ((*guarded)->hasActionsSupport()) { auto label = lang(lng_notification_reply).toUtf8(); auto actionReceiver = _data; auto actionHandler = &NotificationData::notificationClicked; auto actionLabel = label.constData(); auto actionName = "default"; auto actionDataFreeMethod = &NotificationData::notificationDataFree; auto actionData = new NotificationDataStruct(guarded, peerId, msgId); Libs::notify_notification_add_action(actionReceiver, actionName, actionLabel, actionHandler, actionData, actionDataFreeMethod); } } void logError(GError *error) { LOG(("LibNotify Error: domain %1, code %2, message '%3'").arg(error->domain).arg(error->code).arg(QString::fromUtf8(error->message))); Libs::g_error_free(error); } struct NotificationDataStruct { NotificationDataStruct(const std::shared_ptr<Manager*> &guarded, PeerId peerId, MsgId msgId) : weak(guarded) , peerId(peerId) , msgId(msgId) { } std::weak_ptr<Manager*> weak; PeerId peerId = 0; MsgId msgId = 0; }; static void performOnMainQueue(NotificationDataStruct *data, base::lambda_once<void(Manager *manager)> task) { const auto weak = data->weak; crl::on_main(weak, [=, task = std::move(task)]() mutable { task(*weak.lock()); }); } static void notificationDataFree(gpointer data) { auto notificationData = static_cast<NotificationDataStruct*>(data); delete notificationData; } static void notificationDataFreeClosure(gpointer data, GClosure *closure) { auto notificationData = static_cast<NotificationDataStruct*>(data); delete notificationData; } static void notificationClosed(Libs::NotifyNotification *notification, gpointer data) { auto closedReason = Libs::notify_notification_get_closed_reason(notification); auto notificationData = static_cast<NotificationDataStruct*>(data); performOnMainQueue(notificationData, [peerId = notificationData->peerId, msgId = notificationData->msgId](Manager *manager) { manager->clearNotification(peerId, msgId); }); } static void notificationClicked(Libs::NotifyNotification *notification, char *action, gpointer data) { auto notificationData = static_cast<NotificationDataStruct*>(data); performOnMainQueue(notificationData, [peerId = notificationData->peerId, msgId = notificationData->msgId](Manager *manager) { manager->notificationActivated(peerId, msgId); }); } Libs::NotifyNotification *_data = nullptr; gulong _handlerId = 0; }; using Notification = std::shared_ptr<NotificationData>; QString GetServerName() { if (!LibNotifyLoaded()) { return QString(); } if (!Libs::notify_is_initted() && !Libs::notify_init("Telegram Desktop")) { LOG(("LibNotify Error: failed to init!")); return QString(); } gchar *name = nullptr; auto guard = gsl::finally([&name] { if (name) Libs::g_free(name); }); if (!Libs::notify_get_server_info(&name, nullptr, nullptr, nullptr)) { LOG(("LibNotify Error: could not get server name!")); return QString(); } if (!name) { LOG(("LibNotify Error: successfully got empty server name!")); return QString(); } auto result = QString::fromUtf8(static_cast<const char*>(name)); LOG(("Notifications Server: %1").arg(result)); return result; } auto LibNotifyServerName = QString(); } // namespace #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION bool Supported() { #ifndef TDESKTOP_DISABLE_GTK_INTEGRATION static auto Checked = false; if (!Checked) { Checked = true; LibNotifyServerName = GetServerName(); } return !LibNotifyServerName.isEmpty(); #else return false; #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION } std::unique_ptr<Window::Notifications::Manager> Create(Window::Notifications::System *system) { #ifndef TDESKTOP_DISABLE_GTK_INTEGRATION if (Global::NativeNotifications() && Supported()) { return std::make_unique<Manager>(system); } #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION return nullptr; } void Finish() { #ifndef TDESKTOP_DISABLE_GTK_INTEGRATION if (Libs::notify_is_initted && Libs::notify_uninit) { if (Libs::notify_is_initted()) { Libs::notify_uninit(); } } #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION } #ifndef TDESKTOP_DISABLE_GTK_INTEGRATION class Manager::Private { public: using Type = Window::Notifications::CachedUserpics::Type; explicit Private(Type type) : _cachedUserpics(type) { } void init(Manager *manager); void showNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, const QString &msg, bool hideNameAndPhoto, bool hideReplyButton); void clearAll(); void clearFromHistory(History *history); void clearNotification(PeerId peerId, MsgId msgId); bool hasPoorSupport() const { return _poorSupported; } bool hasActionsSupport() const { return _actionsSupported; } ~Private(); private: QString escapeNotificationText(const QString &text) const; void showNextNotification(); struct QueuedNotification { PeerData *peer = nullptr; MsgId msgId = 0; QString title; QString body; bool hideNameAndPhoto = false; }; QString _serverName; QStringList _capabilities; using QueuedNotifications = QList<QueuedNotification>; QueuedNotifications _queuedNotifications; using Notifications = QMap<PeerId, QMap<MsgId, Notification>>; Notifications _notifications; Window::Notifications::CachedUserpics _cachedUserpics; bool _actionsSupported = false; bool _markupSupported = false; bool _poorSupported = false; std::shared_ptr<Manager*> _guarded; }; #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION #ifndef TDESKTOP_DISABLE_GTK_INTEGRATION void Manager::Private::init(Manager *manager) { _guarded = std::make_shared<Manager*>(manager); if (auto capabilities = Libs::notify_get_server_caps()) { for (auto capability = capabilities; capability; capability = capability->next) { auto capabilityText = QString::fromUtf8(static_cast<const char*>(capability->data)); _capabilities.push_back(capabilityText); } Libs::g_list_free_full(capabilities, g_free); LOG(("LibNotify capabilities: %1").arg(_capabilities.join(qstr(", ")))); if (_capabilities.contains(qsl("actions"))) { _actionsSupported = true; } else if (_capabilities.contains(qsl("body-markup"))) { _markupSupported = true; } } else { LOG(("LibNotify Error: could not get capabilities!")); } // Unity and other Notify OSD users handle desktop notifications // extremely poor, even without the ability to close() them. _serverName = LibNotifyServerName; Assert(!_serverName.isEmpty()); if (_serverName == qstr("notify-osd")) { // _poorSupported = true; _actionsSupported = false; } } QString Manager::Private::escapeNotificationText(const QString &text) const { return _markupSupported ? escapeHtml(text) : text; } void Manager::Private::showNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, const QString &msg, bool hideNameAndPhoto, bool hideReplyButton) { auto titleText = escapeNotificationText(title); auto subtitleText = escapeNotificationText(subtitle); auto msgText = escapeNotificationText(msg); if (_markupSupported && !subtitleText.isEmpty()) { subtitleText = qstr("<b>") + subtitleText + qstr("</b>"); } auto bodyText = subtitleText.isEmpty() ? msgText : (subtitleText + '\n' + msgText); QueuedNotification notification; notification.peer = peer; notification.msgId = msgId; notification.title = titleText; notification.body = bodyText; notification.hideNameAndPhoto = hideNameAndPhoto; _queuedNotifications.push_back(notification); showNextNotification(); } void Manager::Private::showNextNotification() { // Show only one notification at a time in Unity / Notify OSD. if (_poorSupported) { for (auto b = _notifications.begin(); !_notifications.isEmpty() && b->isEmpty();) { _notifications.erase(b); } if (!_notifications.isEmpty()) { return; } } QueuedNotification data; while (!_queuedNotifications.isEmpty()) { data = _queuedNotifications.front(); _queuedNotifications.pop_front(); if (data.peer) { break; } } if (!data.peer) { return; } auto peerId = data.peer->id; auto msgId = data.msgId; auto notification = std::make_shared<NotificationData>( _guarded, data.title, data.body, _capabilities, peerId, msgId); if (!notification->valid()) { return; } StorageKey key; if (data.hideNameAndPhoto) { key = StorageKey(0, 0); } else { key = data.peer->userpicUniqueKey(); } notification->setImage(_cachedUserpics.get(key, data.peer)); auto i = _notifications.find(peerId); if (i != _notifications.cend()) { auto j = i->find(msgId); if (j != i->cend()) { auto oldNotification = j.value(); i->erase(j); oldNotification->close(); i = _notifications.find(peerId); } } if (i == _notifications.cend()) { i = _notifications.insert(peerId, QMap<MsgId, Notification>()); } _notifications[peerId].insert(msgId, notification); if (!notification->show()) { i = _notifications.find(peerId); if (i != _notifications.cend()) { i->remove(msgId); if (i->isEmpty()) _notifications.erase(i); } showNextNotification(); } } void Manager::Private::clearAll() { _queuedNotifications.clear(); auto temp = base::take(_notifications); for_const (auto ¬ifications, temp) { for_const (auto notification, notifications) { notification->close(); } } } void Manager::Private::clearFromHistory(History *history) { for (auto i = _queuedNotifications.begin(); i != _queuedNotifications.end();) { if (i->peer == history->peer) { i = _queuedNotifications.erase(i); } else { ++i; } } auto i = _notifications.find(history->peer->id); if (i != _notifications.cend()) { auto temp = base::take(i.value()); _notifications.erase(i); for_const (auto notification, temp) { notification->close(); } } showNextNotification(); } void Manager::Private::clearNotification(PeerId peerId, MsgId msgId) { auto i = _notifications.find(peerId); if (i != _notifications.cend()) { i.value().remove(msgId); if (i.value().isEmpty()) { _notifications.erase(i); } } showNextNotification(); } Manager::Private::~Private() { clearAll(); } Manager::Manager(Window::Notifications::System *system) : NativeManager(system) , _private(std::make_unique<Private>(Private::Type::Rounded)) { _private->init(this); } void Manager::clearNotification(PeerId peerId, MsgId msgId) { _private->clearNotification(peerId, msgId); } bool Manager::hasPoorSupport() const { return _private->hasPoorSupport(); } bool Manager::hasActionsSupport() const { return _private->hasActionsSupport(); } Manager::~Manager() = default; void Manager::doShowNativeNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, const QString &msg, bool hideNameAndPhoto, bool hideReplyButton) { _private->showNotification(peer, msgId, title, subtitle, msg, hideNameAndPhoto, hideReplyButton); } void Manager::doClearAllFast() { _private->clearAll(); } void Manager::doClearFromHistory(History *history) { _private->clearFromHistory(history); } #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION } // namespace Notifications } // namespace Platform ���������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/linux/notifications_manager_linux.h�������������������0000664�0000000�0000000�00000002173�13262451251�0030344�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "platform/platform_notifications_manager.h" namespace Platform { namespace Notifications { inline bool SkipAudio() { return false; } inline bool SkipToast() { return false; } inline void FlashBounce() { } void Finish(); class Manager : public Window::Notifications::NativeManager { public: Manager(Window::Notifications::System *system); void clearNotification(PeerId peerId, MsgId msgId); bool hasPoorSupport() const; bool hasActionsSupport() const; ~Manager(); protected: void doShowNativeNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, const QString &msg, bool hideNameAndPhoto, bool hideReplyButton) override; void doClearAllFast() override; void doClearFromHistory(History *history) override; private: class Private; const std::unique_ptr<Private> _private; }; } // namespace Notifications } // namespace Platform �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/linux/specific_linux.cpp������������������������������0000664�0000000�0000000�00000037102�13262451251�0026121�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "platform/linux/specific_linux.h" #include "platform/linux/linux_libs.h" #include "lang/lang_keys.h" #include "application.h" #include "mainwidget.h" #include "mainwindow.h" #include "platform/linux/file_utilities_linux.h" #include "platform/platform_notifications_manager.h" #include "storage/localstorage.h" #include "core/crash_reports.h" #include <sys/stat.h> #include <sys/types.h> #include <cstdlib> #include <unistd.h> #include <dirent.h> #include <pwd.h> #include <iostream> #include <qpa/qplatformnativeinterface.h> using namespace Platform; using Platform::File::internal::EscapeShell; namespace Platform { QString CurrentExecutablePath(int argc, char *argv[]) { constexpr auto kMaxPath = 1024; char result[kMaxPath] = { 0 }; auto count = readlink("/proc/self/exe", result, kMaxPath); if (count > 0) { auto filename = QFile::decodeName(result); auto deletedPostfix = qstr(" (deleted)"); if (filename.endsWith(deletedPostfix) && !QFileInfo(filename).exists()) { filename.chop(deletedPostfix.size()); } return filename; } // Fallback to the first command line argument. return argc ? QFile::decodeName(argv[0]) : QString(); } } // namespace Platform namespace { class _PsEventFilter : public QAbstractNativeEventFilter { public: bool nativeEventFilter(const QByteArray &eventType, void *message, long *result) { //auto wnd = App::wnd(); //if (!wnd) return false; return false; } }; _PsEventFilter *_psEventFilter = nullptr; QRect _monitorRect; auto _monitorLastGot = 0LL; } // namespace QRect psDesktopRect() { auto tnow = getms(); if (tnow > _monitorLastGot + 1000LL || tnow < _monitorLastGot) { _monitorLastGot = tnow; _monitorRect = QApplication::desktop()->availableGeometry(App::wnd()); } return _monitorRect; } void psShowOverAll(QWidget *w, bool canFocus) { w->show(); } void psBringToBack(QWidget *w) { w->hide(); } QAbstractNativeEventFilter *psNativeEventFilter() { delete _psEventFilter; _psEventFilter = new _PsEventFilter(); return _psEventFilter; } void psWriteDump() { } QString demanglestr(const QString &mangled) { if (mangled.isEmpty()) return mangled; QByteArray cmd = ("c++filt -n " + mangled).toUtf8(); FILE *f = popen(cmd.constData(), "r"); if (!f) return "BAD_SYMBOL_" + mangled; QString result; char buffer[4096] = { 0 }; while (!feof(f)) { if (fgets(buffer, 4096, f) != NULL) { result += buffer; } } pclose(f); return result.trimmed(); } QStringList addr2linestr(uint64 *addresses, int count) { QStringList result; if (!count || cExeName().isEmpty()) return result; result.reserve(count); QByteArray cmd = "addr2line -e " + EscapeShell(QFile::encodeName(cExeDir() + cExeName())); for (int i = 0; i < count; ++i) { if (addresses[i]) { cmd += qsl(" 0x%1").arg(addresses[i], 0, 16).toUtf8(); } } FILE *f = popen(cmd.constData(), "r"); QStringList addr2lineResult; if (f) { char buffer[4096] = {0}; while (!feof(f)) { if (fgets(buffer, 4096, f) != NULL) { addr2lineResult.push_back(QString::fromUtf8(buffer)); } } pclose(f); } for (int i = 0, j = 0; i < count; ++i) { if (addresses[i]) { if (j < addr2lineResult.size() && !addr2lineResult.at(j).isEmpty() && !addr2lineResult.at(j).startsWith(qstr("0x"))) { QString res = addr2lineResult.at(j).trimmed(); if (int index = res.indexOf(qstr("/Telegram/"))) { if (index > 0) { res = res.mid(index + qstr("/Telegram/").size()); } } result.push_back(res); } else { result.push_back(QString()); } ++j; } else { result.push_back(QString()); } } return result; } QString psPrepareCrashDump(const QByteArray &crashdump, QString dumpfile) { QString initial = QString::fromUtf8(crashdump), result; QStringList lines = initial.split('\n'); result.reserve(initial.size()); int32 i = 0, l = lines.size(); while (i < l) { uint64 addresses[1024] = { 0 }; for (; i < l; ++i) { result.append(lines.at(i)).append('\n'); QString line = lines.at(i).trimmed(); if (line == qstr("Backtrace:")) { ++i; break; } } int32 start = i; for (; i < l; ++i) { QString line = lines.at(i).trimmed(); if (line.isEmpty()) break; QRegularExpressionMatch m1 = QRegularExpression(qsl("^(.+)\\(([^+]+)\\+([^\\)]+)\\)\\[(.+)\\]$")).match(line); QRegularExpressionMatch m2 = QRegularExpression(qsl("^(.+)\\[(.+)\\]$")).match(line); QString addrstr = m1.hasMatch() ? m1.captured(4) : (m2.hasMatch() ? m2.captured(2) : QString()); if (!addrstr.isEmpty()) { uint64 addr = addrstr.startsWith(qstr("0x")) ? addrstr.mid(2).toULongLong(0, 16) : addrstr.toULongLong(); if (addr > 1) { addresses[i - start] = addr; } } } QStringList addr2line = addr2linestr(addresses, i - start); for (i = start; i < l; ++i) { QString line = lines.at(i).trimmed(); if (line.isEmpty()) break; result.append(qsl("\n%1. ").arg(i - start)); if (line.startsWith(qstr("ERROR: "))) { result.append(line).append('\n'); continue; } if (line == qstr("[0x1]")) { result.append(qsl("(0x1 separator)\n")); continue; } QRegularExpressionMatch m1 = QRegularExpression(qsl("^(.+)\\(([^+]*)\\+([^\\)]+)\\)(.+)$")).match(line); QRegularExpressionMatch m2 = QRegularExpression(qsl("^(.+)\\[(.+)\\]$")).match(line); if (!m1.hasMatch() && !m2.hasMatch()) { result.append(qstr("BAD LINE: ")).append(line).append('\n'); continue; } if (m1.hasMatch()) { result.append(demanglestr(m1.captured(2))).append(qsl(" + ")).append(m1.captured(3)).append(qsl(" [")).append(m1.captured(1)).append(qsl("] ")); if (!addr2line.at(i - start).isEmpty() && addr2line.at(i - start) != qsl("??:0")) { result.append(qsl(" (")).append(addr2line.at(i - start)).append(qsl(")\n")); } else { result.append(m1.captured(4)).append(qsl(" (demangled)")).append('\n'); } } else { result.append('[').append(m2.captured(1)).append(']'); if (!addr2line.at(i - start).isEmpty() && addr2line.at(i - start) != qsl("??:0")) { result.append(qsl(" (")).append(addr2line.at(i - start)).append(qsl(")\n")); } else { result.append(' ').append(m2.captured(2)).append('\n'); } } } } return result; } bool _removeDirectory(const QString &path) { // from http://stackoverflow.com/questions/2256945/removing-a-non-empty-directory-programmatically-in-c-or-c QByteArray pathRaw = QFile::encodeName(path); DIR *d = opendir(pathRaw.constData()); if (!d) return false; while (struct dirent *p = readdir(d)) { /* Skip the names "." and ".." as we don't want to recurse on them. */ if (!strcmp(p->d_name, ".") || !strcmp(p->d_name, "..")) continue; QString fname = path + '/' + p->d_name; QByteArray fnameRaw = QFile::encodeName(fname); struct stat statbuf; if (!stat(fnameRaw.constData(), &statbuf)) { if (S_ISDIR(statbuf.st_mode)) { if (!_removeDirectory(fname)) { closedir(d); return false; } } else { if (unlink(fnameRaw.constData())) { closedir(d); return false; } } } } closedir(d); return !rmdir(pathRaw.constData()); } void psDeleteDir(const QString &dir) { _removeDirectory(dir); } namespace { auto _lastUserAction = 0LL; } // namespace void psUserActionDone() { _lastUserAction = getms(true); } bool psIdleSupported() { return false; } TimeMs psIdleTime() { return getms(true) - _lastUserAction; } void psActivateProcess(uint64 pid) { // objc_activateProgram(); } namespace { QString getHomeDir() { auto home = QDir::homePath(); if (home != QDir::rootPath()) return home + '/'; struct passwd *pw = getpwuid(getuid()); return (pw && pw->pw_dir && strlen(pw->pw_dir)) ? (QFile::decodeName(pw->pw_dir) + '/') : QString(); } } // namespace QString psAppDataPath() { // Previously we used ~/.TelegramDesktop, so look there first. // If we find data there, we should still use it. auto home = getHomeDir(); if (!home.isEmpty()) { auto oldPath = home + qsl(".TelegramDesktop/"); auto oldSettingsBase = oldPath + qsl("tdata/settings"); if (QFile(oldSettingsBase + '0').exists() || QFile(oldSettingsBase + '1').exists()) { return oldPath; } } return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + '/'; } QString psDownloadPath() { return QStandardPaths::writableLocation(QStandardPaths::DownloadLocation) + '/' + str_const_toString(AppName) + '/'; } void psDoCleanup() { try { psAutoStart(false, true); psSendToMenu(false, true); } catch (...) { } } int psCleanup() { psDoCleanup(); return 0; } void psDoFixPrevious() { } int psFixPrevious() { psDoFixPrevious(); return 0; } namespace Platform { void start() { } void finish() { Notifications::Finish(); delete _psEventFilter; _psEventFilter = nullptr; } bool TranslucentWindowsSupported(QPoint globalPosition) { if (auto app = static_cast<QGuiApplication*>(QCoreApplication::instance())) { if (auto native = app->platformNativeInterface()) { if (auto desktop = QApplication::desktop()) { auto index = desktop->screenNumber(globalPosition); auto screens = QGuiApplication::screens(); if (auto screen = (index >= 0 && index < screens.size()) ? screens[index] : QGuiApplication::primaryScreen()) { if (native->nativeResourceForScreen(QByteArray("compositingEnabled"), screen)) { return true; } static OrderedSet<int> WarnedAbout; if (!WarnedAbout.contains(index)) { WarnedAbout.insert(index); LOG(("WARNING: Compositing is disabled for screen index %1 (for position %2,%3)").arg(index).arg(globalPosition.x()).arg(globalPosition.y())); } } else { LOG(("WARNING: Could not get screen for index %1 (for position %2,%3)").arg(index).arg(globalPosition.x()).arg(globalPosition.y())); } } } } return false; } QString SystemCountry() { return QString(); } QString SystemLanguage() { return QString(); } namespace ThirdParty { void start() { Libs::start(); MainWindow::LibsLoaded(); } void finish() { } } // namespace ThirdParty } // namespace Platform namespace { bool _psRunCommand(const QByteArray &command) { auto result = system(command.constData()); if (result) { DEBUG_LOG(("App Error: command failed, code: %1, command (in utf8): %2").arg(result).arg(command.constData())); return false; } DEBUG_LOG(("App Info: command succeeded, command (in utf8): %1").arg(command.constData())); return true; } } // namespace void psRegisterCustomScheme() { #ifndef TDESKTOP_DISABLE_REGISTER_CUSTOM_SCHEME auto home = getHomeDir(); if (home.isEmpty() || cBetaVersion() || cExeName().isEmpty()) return; // don't update desktop file for beta version #ifndef TDESKTOP_DISABLE_DESKTOP_FILE_GENERATION DEBUG_LOG(("App Info: placing .desktop file")); if (QDir(home + qsl(".local/")).exists()) { QString apps = home + qsl(".local/share/applications/"); QString icons = home + qsl(".local/share/icons/"); if (!QDir(apps).exists()) QDir().mkpath(apps); if (!QDir(icons).exists()) QDir().mkpath(icons); QString path = cWorkingDir() + qsl("tdata/"), file = path + qsl("telegramdesktop.desktop"); QDir().mkpath(path); QFile f(file); if (f.open(QIODevice::WriteOnly)) { QString icon = icons + qsl("telegram.png"); auto iconExists = QFile(icon).exists(); if (Local::oldSettingsVersion() < 10021 && iconExists) { // Icon was changed. if (QFile(icon).remove()) { iconExists = false; } } if (!iconExists) { if (QFile(qsl(":/gui/art/logo_256.png")).copy(icon)) { DEBUG_LOG(("App Info: Icon copied to 'tdata'")); } } QTextStream s(&f); s.setCodec("UTF-8"); s << "[Desktop Entry]\n"; s << "Version=1.0\n"; s << "Name=Telegram Desktop\n"; s << "Comment=Official desktop application for the Telegram messaging service\n"; s << "TryExec=" << EscapeShell(QFile::encodeName(cExeDir() + cExeName())) << "\n"; s << "Exec=" << EscapeShell(QFile::encodeName(cExeDir() + cExeName())) << " -- %u\n"; s << "Icon=telegram\n"; s << "Terminal=false\n"; s << "StartupWMClass=TelegramDesktop\n"; s << "Type=Application\n"; s << "Categories=Network;InstantMessaging;Qt;\n"; s << "MimeType=x-scheme-handler/tg;\n"; f.close(); if (_psRunCommand("desktop-file-install --dir=" + EscapeShell(QFile::encodeName(home + qsl(".local/share/applications"))) + " --delete-original " + EscapeShell(QFile::encodeName(file)))) { DEBUG_LOG(("App Info: removing old .desktop file")); QFile(qsl("%1.local/share/applications/telegram.desktop").arg(home)).remove(); _psRunCommand("update-desktop-database " + EscapeShell(QFile::encodeName(home + qsl(".local/share/applications")))); _psRunCommand("xdg-mime default telegramdesktop.desktop x-scheme-handler/tg"); } } else { LOG(("App Error: Could not open '%1' for write").arg(file)); } } #endif // !TDESKTOP_DISABLE_DESKTOP_FILE_GENERATION DEBUG_LOG(("App Info: registerting for Gnome")); if (_psRunCommand("gconftool-2 -t string -s /desktop/gnome/url-handlers/tg/command " + EscapeShell(EscapeShell(QFile::encodeName(cExeDir() + cExeName())) + " -- %s"))) { _psRunCommand("gconftool-2 -t bool -s /desktop/gnome/url-handlers/tg/needs_terminal false"); _psRunCommand("gconftool-2 -t bool -s /desktop/gnome/url-handlers/tg/enabled true"); } DEBUG_LOG(("App Info: placing .protocol file")); QString services; if (QDir(home + qsl(".kde4/")).exists()) { services = home + qsl(".kde4/share/kde4/services/"); } else if (QDir(home + qsl(".kde/")).exists()) { services = home + qsl(".kde/share/kde4/services/"); } if (!services.isEmpty()) { if (!QDir(services).exists()) QDir().mkpath(services); QString path = services, file = path + qsl("tg.protocol"); QFile f(file); if (f.open(QIODevice::WriteOnly)) { QTextStream s(&f); s.setCodec("UTF-8"); s << "[Protocol]\n"; s << "exec=" << QFile::decodeName(EscapeShell(QFile::encodeName(cExeDir() + cExeName()))) << " -- %u\n"; s << "protocol=tg\n"; s << "input=none\n"; s << "output=none\n"; s << "helper=true\n"; s << "listing=false\n"; s << "reading=false\n"; s << "writing=false\n"; s << "makedir=false\n"; s << "deleting=false\n"; f.close(); } else { LOG(("App Error: Could not open '%1' for write").arg(file)); } } #endif // !TDESKTOP_DISABLE_REGISTER_CUSTOM_SCHEME } void psNewVersion() { psRegisterCustomScheme(); } bool psShowOpenWithMenu(int x, int y, const QString &file) { return false; } void psAutoStart(bool start, bool silent) { } void psSendToMenu(bool send, bool silent) { } void psUpdateOverlayed(QWidget *widget) { } bool linuxMoveFile(const char *from, const char *to) { FILE *ffrom = fopen(from, "rb"), *fto = fopen(to, "wb"); if (!ffrom) { if (fto) fclose(fto); return false; } if (!fto) { fclose(ffrom); return false; } static const int BufSize = 65536; char buf[BufSize]; while (size_t size = fread(buf, 1, BufSize, ffrom)) { fwrite(buf, 1, size, fto); } struct stat fst; // from http://stackoverflow.com/questions/5486774/keeping-fileowner-and-permissions-after-copying-file-in-c //let's say this wont fail since you already worked OK on that fp if (fstat(fileno(ffrom), &fst) != 0) { fclose(ffrom); fclose(fto); return false; } //update to the same uid/gid if (fchown(fileno(fto), fst.st_uid, fst.st_gid) != 0) { fclose(ffrom); fclose(fto); return false; } //update the permissions if (fchmod(fileno(fto), fst.st_mode) != 0) { fclose(ffrom); fclose(fto); return false; } fclose(ffrom); fclose(fto); if (unlink(from)) { return false; } return true; } bool psLaunchMaps(const LocationCoords &coords) { return false; } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/linux/specific_linux.h��������������������������������0000664�0000000�0000000�00000004326�13262451251�0025570�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <execinfo.h> #include <signal.h> namespace Platform { inline void SetWatchingMediaKeys(bool watching) { } inline void StartTranslucentPaint(QPainter &p, QPaintEvent *e) { } inline void InitOnTopPanel(QWidget *panel) { } inline void DeInitOnTopPanel(QWidget *panel) { } inline void ReInitOnTopPanel(QWidget *panel) { } QString CurrentExecutablePath(int argc, char *argv[]); } // namespace Platform inline QString psServerPrefix() { return qsl("/tmp/"); } inline void psCheckLocalSocket(const QString &serverName) { QFile address(serverName); if (address.exists()) { address.remove(); } } void psWriteDump(); QString psPrepareCrashDump(const QByteArray &crashdump, QString dumpfile); void psDeleteDir(const QString &dir); void psUserActionDone(); bool psIdleSupported(); TimeMs psIdleTime(); QStringList psInitLogs(); void psClearInitLogs(); void psActivateProcess(uint64 pid = 0); QString psLocalServerPrefix(); QString psAppDataPath(); QString psDownloadPath(); void psAutoStart(bool start, bool silent = false); void psSendToMenu(bool send, bool silent = false); QRect psDesktopRect(); void psShowOverAll(QWidget *w, bool canFocus = true); void psBringToBack(QWidget *w); int psCleanup(); int psFixPrevious(); QAbstractNativeEventFilter *psNativeEventFilter(); void psNewVersion(); void psUpdateOverlayed(QWidget *widget); inline QByteArray psDownloadPathBookmark(const QString &path) { return QByteArray(); } inline QByteArray psPathBookmark(const QString &path) { return QByteArray(); } inline void psDownloadPathEnableAccess() { } class PsFileBookmark { public: PsFileBookmark(const QByteArray &bookmark) { } bool check() const { return true; } bool enable() const { return true; } void disable() const { } const QString &name(const QString &original) const { return original; } QByteArray bookmark() const { return QByteArray(); } }; bool linuxMoveFile(const char *from, const char *to); bool psLaunchMaps(const LocationCoords &coords); ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/mac/��������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0022007�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/mac/file_utilities_mac.h������������������������������0000664�0000000�0000000�00000001710�13262451251�0026011�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "platform/platform_file_utilities.h" namespace Platform { namespace File { inline void UnsafeOpenEmailLink(const QString &email) { return ::File::internal::UnsafeOpenEmailLinkDefault(email); } inline void PostprocessDownloaded(const QString &filepath) { } } // namespace File namespace FileDialog { inline void InitLastPath() { ::FileDialog::internal::InitLastPathDefault(); } inline bool Get(QStringList &files, QByteArray &remoteContent, const QString &caption, const QString &filter, ::FileDialog::internal::Type type, QString startFile) { return ::FileDialog::internal::GetDefault(files, remoteContent, caption, filter, type, startFile); } } // namespace FileDialog } // namespace Platform ��������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/mac/file_utilities_mac.mm�����������������������������0000664�0000000�0000000�00000045442�13262451251�0026205�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "platform/mac/file_utilities_mac.h" #include "platform/mac/mac_utilities.h" #include "styles/style_window.h" #include <Cocoa/Cocoa.h> #include <CoreFoundation/CFURL.h> namespace { using namespace Platform; QString strNeedToReload() { const uint32 letters[] = { 0xAD92C02B, 0xA2217C97, 0x5E55F4F5, 0x2207DAAC, 0xD18BA536, 0x03E41869, 0xB96D2BFD, 0x810C7284, 0xE412099E, 0x5AAD0837, 0xE6637AEE, 0x8E5E2FF5, 0xE3BDA123, 0x94A5CE38, 0x4A42F7D1, 0xCE4677DC, 0x40A81701, 0x9C5B38CD, 0x61801E1A, 0x6FF16179 }; return MakeFromLetters(letters); } QString strNeedToRefresh1() { const uint32 letters[] = { 0xEDDFCD66, 0x434DF1FB, 0x820B76AB, 0x48CE7965, 0x3609C0BA, 0xFC9A990C, 0x3EDD1C51, 0xE2BDA036, 0x7140CEE9, 0x65DB414D, 0x88592EC3, 0x2CB2613A }; return MakeFromLetters(letters); } QString strNeedToRefresh2() { const uint32 letters[] = { 0x8AE4915D, 0x7159D7EF, 0x79C74167, 0x29B7611C, 0x0E6B9ADD, 0x0D93610F, 0xEBEAFE7A, 0x5BD17540, 0x121EF3B7, 0x61B02E26, 0x2174AAEE, 0x61AD3325 }; return MakeFromLetters(letters); } } // namespace @interface OpenWithApp : NSObject { NSString *fullname; NSURL *app; NSImage *icon; } @property (nonatomic, retain) NSString *fullname; @property (nonatomic, retain) NSURL *app; @property (nonatomic, retain) NSImage *icon; @end // @interface OpenWithApp @implementation OpenWithApp @synthesize fullname, app, icon; - (void) dealloc { [fullname release]; [app release]; [icon release]; [super dealloc]; } @end // @implementation OpenWithApp @interface OpenFileWithInterface : NSObject { } - (id) init:(NSString *)file; - (BOOL) popupAtX:(int)x andY:(int)y; - (void) itemChosen:(id)sender; - (void) dealloc; @end // @interface OpenFileWithInterface @implementation OpenFileWithInterface { NSString *toOpen; NSURL *defUrl; NSString *defBundle, *defName, *defVersion; NSImage *defIcon; NSMutableArray *apps; NSMenu *menu; } - (void) fillAppByUrl:(NSURL*)url bundle:(NSString**)bundle name:(NSString**)name version:(NSString**)version icon:(NSImage**)icon { NSBundle *b = [NSBundle bundleWithURL:url]; if (b) { NSString *path = [url path]; *name = [[NSFileManager defaultManager] displayNameAtPath: path]; if (!*name) *name = (NSString*)[b objectForInfoDictionaryKey:@"CFBundleDisplayName"]; if (!*name) *name = (NSString*)[b objectForInfoDictionaryKey:@"CFBundleName"]; if (*name) { *bundle = [b bundleIdentifier]; if (bundle) { *version = (NSString*)[b objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; *icon = [[NSWorkspace sharedWorkspace] iconForFile: path]; if (*icon && [*icon isValid]) [*icon setSize: CGSizeMake(16., 16.)]; return; } } } *bundle = *name = *version = nil; *icon = nil; } - (id) init:(NSString*)file { toOpen = [file retain]; if (self = [super init]) { NSURL *url = [NSURL fileURLWithPath:file]; defUrl = [[NSWorkspace sharedWorkspace] URLForApplicationToOpenURL:url]; if (defUrl) { [self fillAppByUrl:defUrl bundle:&defBundle name:&defName version:&defVersion icon:&defIcon]; if (!defBundle || !defName) { defUrl = nil; } } NSArray *appsList = (NSArray*)LSCopyApplicationURLsForURL(CFURLRef(url), kLSRolesAll); NSMutableDictionary *data = [NSMutableDictionary dictionaryWithCapacity:16]; int fullcount = 0; for (id app in appsList) { if (fullcount > 15) break; NSString *bundle = nil, *name = nil, *version = nil; NSImage *icon = nil; [self fillAppByUrl:(NSURL*)app bundle:&bundle name:&name version:&version icon:&icon]; if (bundle && name) { if ([bundle isEqualToString:defBundle] && [version isEqualToString:defVersion]) continue; NSString *key = [[NSArray arrayWithObjects:bundle, name, nil] componentsJoinedByString:@"|"]; if (!version) version = @""; NSMutableDictionary *versions = (NSMutableDictionary*)[data objectForKey:key]; if (!versions) { versions = [NSMutableDictionary dictionaryWithCapacity:2]; [data setValue:versions forKey:key]; } if (![versions objectForKey:version]) { [versions setValue:[NSArray arrayWithObjects:name, icon, app, nil] forKey:version]; ++fullcount; } } } if (fullcount || defUrl) { apps = [NSMutableArray arrayWithCapacity:fullcount]; for (id key in data) { NSMutableDictionary *val = (NSMutableDictionary*)[data objectForKey:key]; for (id ver in val) { NSArray *app = (NSArray*)[val objectForKey:ver]; OpenWithApp *a = [[OpenWithApp alloc] init]; NSString *fullname = (NSString*)[app objectAtIndex:0], *version = (NSString*)ver; BOOL showVersion = ([val count] > 1); if (!showVersion) { NSError *error = NULL; NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"^\\d+\\.\\d+\\.\\d+(\\.\\d+)?$" options:NSRegularExpressionCaseInsensitive error:&error]; showVersion = ![regex numberOfMatchesInString:version options:NSMatchingWithoutAnchoringBounds range:{0,[version length]}]; } if (showVersion) fullname = [[NSArray arrayWithObjects:fullname, @" (", version, @")", nil] componentsJoinedByString:@""]; [a setFullname:fullname]; [a setIcon:(NSImage*)[app objectAtIndex:1]]; [a setApp:(NSURL*)[app objectAtIndex:2]]; [apps addObject:a]; [a release]; } } } [apps sortUsingDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"fullname" ascending:YES]]]; [appsList release]; menu = nil; } return self; } - (BOOL) popupAtX:(int)x andY:(int)y { if (![apps count] && !defName) return NO; menu = [[NSMenu alloc] initWithTitle:@"Open With"]; int index = 0; if (defName) { NSMenuItem *item = [menu insertItemWithTitle:[[NSArray arrayWithObjects:defName, @" (default)", nil] componentsJoinedByString:@""] action:@selector(itemChosen:) keyEquivalent:@"" atIndex:index++]; if (defIcon) [item setImage:defIcon]; [item setTarget:self]; [menu insertItem:[NSMenuItem separatorItem] atIndex:index++]; } if ([apps count]) { for (id a in apps) { OpenWithApp *app = (OpenWithApp*)a; NSMenuItem *item = [menu insertItemWithTitle:[a fullname] action:@selector(itemChosen:) keyEquivalent:@"" atIndex:index++]; if ([app icon]) [item setImage:[app icon]]; [item setTarget:self]; } [menu insertItem:[NSMenuItem separatorItem] atIndex:index++]; } NSMenuItem *item = [menu insertItemWithTitle:NSlang(lng_mac_choose_program_menu) action:@selector(itemChosen:) keyEquivalent:@"" atIndex:index++]; [item setTarget:self]; [menu popUpMenuPositioningItem:nil atLocation:CGPointMake(x, y) inView:nil]; return YES; } - (void) itemChosen:(id)sender { NSArray *items = [menu itemArray]; NSURL *url = nil; for (int i = 0, l = [items count]; i < l; ++i) { if ([items objectAtIndex:i] == sender) { if (defName) i -= 2; if (i < 0) { url = defUrl; } else if (i < int([apps count])) { url = [(OpenWithApp*)[apps objectAtIndex:i] app]; } break; } } if (url) { [[NSWorkspace sharedWorkspace] openFile:toOpen withApplication:[url path]]; } else if (!Platform::File::UnsafeShowOpenWith(NS2QString(toOpen))) { Platform::File::UnsafeLaunch(NS2QString(toOpen)); } } - (void) dealloc { [toOpen release]; if (menu) [menu release]; [super dealloc]; } @end // @implementation OpenFileWithInterface @interface NSURL(CompareUrls) - (BOOL) isEquivalent:(NSURL *)aURL; @end // @interface NSURL(CompareUrls) @implementation NSURL(CompareUrls) - (BOOL) isEquivalent:(NSURL *)aURL { if ([self isEqual:aURL]) return YES; if ([[self scheme] caseInsensitiveCompare:[aURL scheme]] != NSOrderedSame) return NO; if ([[self host] caseInsensitiveCompare:[aURL host]] != NSOrderedSame) return NO; if ([[self path] compare:[aURL path]] != NSOrderedSame) return NO; if ([[self port] compare:[aURL port]] != NSOrderedSame) return NO; if ([[self query] compare:[aURL query]] != NSOrderedSame) return NO; return YES; } @end // @implementation NSURL(CompareUrls) @interface ChooseApplicationDelegate : NSObject<NSOpenSavePanelDelegate> { } - (id) init:(NSArray *)recommendedApps withPanel:(NSOpenPanel *)creator withSelector:(NSPopUpButton *)menu withGood:(NSTextField *)goodLabel withBad:(NSTextField *)badLabel withIcon:(NSImageView *)badIcon withAccessory:(NSView *)acc; - (BOOL) panel:(id)sender shouldEnableURL:(NSURL *)url; - (void) panelSelectionDidChange:(id)sender; - (void) menuDidClose; - (void) dealloc; @end // @interface ChooseApplicationDelegate @implementation ChooseApplicationDelegate { BOOL onlyRecommended; NSArray *apps; NSOpenPanel *panel; NSPopUpButton *selector; NSTextField *good, *bad; NSImageView *icon; NSString *recom; NSView *accessory; } - (id) init:(NSArray *)recommendedApps withPanel:(NSOpenPanel *)creator withSelector:(NSPopUpButton *)menu withGood:(NSTextField *)goodLabel withBad:(NSTextField *)badLabel withIcon:(NSImageView *)badIcon withAccessory:(NSView *)acc { if (self = [super init]) { onlyRecommended = YES; recom = [NSlang(lng_mac_recommended_apps) copy]; apps = recommendedApps; panel = creator; selector = menu; good = goodLabel; bad = badLabel; icon = badIcon; accessory = acc; [selector setAction:@selector(menuDidClose)]; } return self; } - (BOOL) isRecommended:(NSURL *)url { if (apps) { for (id app in apps) { if ([(NSURL*)app isEquivalent:url]) { return YES; } } } return NO; } - (BOOL) panel:(id)sender shouldEnableURL:(NSURL *)url { NSNumber *isDirectory; if ([url getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:nil] && isDirectory != nil && [isDirectory boolValue]) { if (onlyRecommended) { CFStringRef ext = CFURLCopyPathExtension((CFURLRef)url); NSNumber *isPackage; if ([url getResourceValue:&isPackage forKey:NSURLIsPackageKey error:nil] && isPackage != nil && [isPackage boolValue]) { return [self isRecommended:url]; } } return YES; } return NO; } - (void) panelSelectionDidChange:(id)sender { NSArray *urls = [panel URLs]; if ([urls count]) { if ([self isRecommended:[urls firstObject]]) { [bad removeFromSuperview]; [icon removeFromSuperview]; [accessory addSubview:good]; } else { [good removeFromSuperview]; [accessory addSubview:bad]; [accessory addSubview:icon]; } } else { [good removeFromSuperview]; [bad removeFromSuperview]; [icon removeFromSuperview]; } } - (void) menuDidClose { onlyRecommended = [[[selector selectedItem] title] isEqualToString:recom]; [self refreshPanelTable]; } - (BOOL) refreshDataInViews: (NSArray*)subviews { for (id view in subviews) { NSString *cls = [view className]; if ([cls isEqualToString:Q2NSString(strNeedToReload())]) { [view reloadData]; } else if ([cls isEqualToString:Q2NSString(strNeedToRefresh1())] || [cls isEqualToString:Q2NSString(strNeedToRefresh2())]) { [view reloadData]; return YES; } else { NSArray *next = [view subviews]; if ([next count] && [self refreshDataInViews:next]) { return YES; } } } return NO; } - (void) refreshPanelTable { @autoreleasepool { [self refreshDataInViews:[[panel contentView] subviews]]; [panel validateVisibleColumns]; } } - (void) dealloc { if (apps) { [apps release]; [recom release]; } [super dealloc]; } @end // @implementation ChooseApplicationDelegate namespace Platform { namespace File { QString UrlToLocal(const QUrl &url) { auto result = url.toLocalFile(); if (result.startsWith(qsl("/.file/id="))) { NSString *nsurl = [[[NSURL URLWithString: [NSString stringWithUTF8String: (qsl("file://") + result).toUtf8().constData()]] filePathURL] path]; if (!nsurl) return QString(); return NS2QString(nsurl); } return result; } bool UnsafeShowOpenWithDropdown(const QString &filepath, QPoint menuPosition) { @autoreleasepool { NSString *file = Q2NSString(filepath); @try { OpenFileWithInterface *menu = [[[OpenFileWithInterface alloc] init:file] autorelease]; auto r = QApplication::desktop()->screenGeometry(menuPosition); auto x = menuPosition.x(); auto y = r.y() + r.height() - menuPosition.y(); return !![menu popupAtX:x andY:y]; } @catch (NSException *exception) { } @finally { } } return false; } bool UnsafeShowOpenWith(const QString &filepath) { @autoreleasepool { NSString *file = Q2NSString(filepath); @try { NSURL *url = [NSURL fileURLWithPath:file]; NSString *ext = [url pathExtension]; NSArray *names = [url pathComponents]; NSString *name = [names count] ? [names lastObject] : @""; NSArray *apps = (NSArray*)LSCopyApplicationURLsForURL(CFURLRef(url), kLSRolesAll); NSOpenPanel *openPanel = [NSOpenPanel openPanel]; NSRect fullRect = { { 0., 0. }, { st::macAccessoryWidth, st::macAccessoryHeight } }; NSView *accessory = [[NSView alloc] initWithFrame:fullRect]; [accessory setAutoresizesSubviews:YES]; NSPopUpButton *selector = [[NSPopUpButton alloc] init]; [accessory addSubview:selector]; [selector addItemWithTitle:NSlang(lng_mac_recommended_apps)]; [selector addItemWithTitle:NSlang(lng_mac_all_apps)]; [selector sizeToFit]; NSTextField *enableLabel = [[NSTextField alloc] init]; [accessory addSubview:enableLabel]; [enableLabel setStringValue:NSlang(lng_mac_enable_filter)]; [enableLabel setFont:[selector font]]; [enableLabel setBezeled:NO]; [enableLabel setDrawsBackground:NO]; [enableLabel setEditable:NO]; [enableLabel setSelectable:NO]; [enableLabel sizeToFit]; NSRect selectorFrame = [selector frame], enableFrame = [enableLabel frame]; enableFrame.size.width += st::macEnableFilterAdd; enableFrame.origin.x = (fullRect.size.width - selectorFrame.size.width - enableFrame.size.width) / 2.; selectorFrame.origin.x = (fullRect.size.width - selectorFrame.size.width + enableFrame.size.width) / 2.; enableFrame.origin.y = fullRect.size.height - selectorFrame.size.height - st::macEnableFilterTop + (selectorFrame.size.height - enableFrame.size.height) / 2.; selectorFrame.origin.y = fullRect.size.height - selectorFrame.size.height - st::macSelectorTop; [enableLabel setFrame:enableFrame]; [enableLabel setAutoresizingMask:NSViewMinXMargin|NSViewMaxXMargin]; [selector setFrame:selectorFrame]; [selector setAutoresizingMask:NSViewMinXMargin|NSViewMaxXMargin]; NSButton *button = [[NSButton alloc] init]; [accessory addSubview:button]; [button setButtonType:NSSwitchButton]; [button setFont:[selector font]]; [button setTitle:NSlang(lng_mac_always_open_with)]; [button sizeToFit]; NSRect alwaysRect = [button frame]; alwaysRect.origin.x = (fullRect.size.width - alwaysRect.size.width) / 2; alwaysRect.origin.y = selectorFrame.origin.y - alwaysRect.size.height - st::macAlwaysThisAppTop; [button setFrame:alwaysRect]; [button setAutoresizingMask:NSViewMinXMargin|NSViewMaxXMargin]; #ifdef OS_MAC_STORE [button setHidden:YES]; #endif // OS_MAC_STORE NSTextField *goodLabel = [[NSTextField alloc] init]; [goodLabel setStringValue:Q2NSString(lng_mac_this_app_can_open(lt_file, NS2QString(name)))]; [goodLabel setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]]; [goodLabel setBezeled:NO]; [goodLabel setDrawsBackground:NO]; [goodLabel setEditable:NO]; [goodLabel setSelectable:NO]; [goodLabel sizeToFit]; NSRect goodFrame = [goodLabel frame]; goodFrame.origin.x = (fullRect.size.width - goodFrame.size.width) / 2.; goodFrame.origin.y = alwaysRect.origin.y - goodFrame.size.height - st::macAppHintTop; [goodLabel setFrame:goodFrame]; NSTextField *badLabel = [[NSTextField alloc] init]; [badLabel setStringValue:Q2NSString(lng_mac_not_known_app(lt_file, NS2QString(name)))]; [badLabel setFont:[goodLabel font]]; [badLabel setBezeled:NO]; [badLabel setDrawsBackground:NO]; [badLabel setEditable:NO]; [badLabel setSelectable:NO]; [badLabel sizeToFit]; NSImageView *badIcon = [[NSImageView alloc] init]; NSImage *badImage = [NSImage imageNamed:NSImageNameCaution]; [badIcon setImage:badImage]; [badIcon setFrame:NSMakeRect(0, 0, st::macCautionIconSize, st::macCautionIconSize)]; NSRect badFrame = [badLabel frame], badIconFrame = [badIcon frame]; badFrame.origin.x = (fullRect.size.width - badFrame.size.width + badIconFrame.size.width) / 2.; badIconFrame.origin.x = (fullRect.size.width - badFrame.size.width - badIconFrame.size.width) / 2.; badFrame.origin.y = alwaysRect.origin.y - badFrame.size.height - st::macAppHintTop; badIconFrame.origin.y = badFrame.origin.y; [badLabel setFrame:badFrame]; [badIcon setFrame:badIconFrame]; [openPanel setAccessoryView:accessory]; ChooseApplicationDelegate *delegate = [[ChooseApplicationDelegate alloc] init:apps withPanel:openPanel withSelector:selector withGood:goodLabel withBad:badLabel withIcon:badIcon withAccessory:accessory]; [openPanel setDelegate:delegate]; [openPanel setCanChooseDirectories:NO]; [openPanel setCanChooseFiles:YES]; [openPanel setAllowsMultipleSelection:NO]; [openPanel setResolvesAliases:YES]; [openPanel setTitle:NSlang(lng_mac_choose_app)]; [openPanel setMessage:Q2NSString(lng_mac_choose_text(lt_file, NS2QString(name)))]; NSArray *appsPaths = [[NSFileManager defaultManager] URLsForDirectory:NSApplicationDirectory inDomains:NSLocalDomainMask]; if ([appsPaths count]) [openPanel setDirectoryURL:[appsPaths firstObject]]; [openPanel beginWithCompletionHandler:^(NSInteger result){ if (result == NSFileHandlingPanelOKButton) { if ([[openPanel URLs] count] > 0) { NSURL *app = [[openPanel URLs] objectAtIndex:0]; NSString *path = [app path]; if ([button state] == NSOnState) { NSArray *UTIs = (NSArray *)UTTypeCreateAllIdentifiersForTag(kUTTagClassFilenameExtension, (CFStringRef)ext, nil); for (NSString *UTI in UTIs) { OSStatus result = LSSetDefaultRoleHandlerForContentType((CFStringRef)UTI, kLSRolesAll, (CFStringRef)[[NSBundle bundleWithPath:path] bundleIdentifier]); DEBUG_LOG(("App Info: set default handler for '%1' UTI result: %2").arg(NS2QString(UTI)).arg(result)); } [UTIs release]; } [[NSWorkspace sharedWorkspace] openFile:file withApplication:[app path]]; } } [selector release]; [button release]; [enableLabel release]; [goodLabel release]; [badLabel release]; [badIcon release]; [accessory release]; [delegate release]; }]; } @catch (NSException *exception) { [[NSWorkspace sharedWorkspace] openFile:file]; } @finally { } } return YES; } void UnsafeLaunch(const QString &filepath) { @autoreleasepool { NSString *file = Q2NSString(filepath); if ([[NSWorkspace sharedWorkspace] openFile:file] == NO) { UnsafeShowOpenWith(filepath); } } } void UnsafeShowInFolder(const QString &filepath) { auto folder = QFileInfo(filepath).absolutePath(); @autoreleasepool { [[NSWorkspace sharedWorkspace] selectFile:Q2NSString(filepath) inFileViewerRootedAtPath:Q2NSString(folder)]; } } } // namespace File } // namespace Platform ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/mac/launcher_mac.h������������������������������������0000664�0000000�0000000�00000000771�13262451251�0024606�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "core/launcher.h" namespace Platform { class Launcher : public Core::Launcher { public: using Core::Launcher::Launcher; private: void initHook() override; bool launchUpdater(UpdaterLaunch action) override; }; } // namespace Platform �������tdesktop-1.2.17/Telegram/SourceFiles/platform/mac/launcher_mac.mm�����������������������������������0000664�0000000�0000000�00000005533�13262451251�0024771�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "platform/mac/launcher_mac.h" #include "core/crash_reports.h" #include "platform/mac/mac_utilities.h" #include <Cocoa/Cocoa.h> #include <CoreFoundation/CFURL.h> namespace Platform { void Launcher::initHook() { #ifndef OS_MAC_OLD // macOS Retina display support is working fine, others are not. QCoreApplication::setAttribute(Qt::AA_DisableHighDpiScaling, false); #endif // OS_MAC_OLD } bool Launcher::launchUpdater(UpdaterLaunch action) { if (cExeName().isEmpty()) { return false; } @autoreleasepool { #ifdef OS_MAC_STORE // In AppStore version we don't have Updater. // We just relaunch our app. if (action == UpdaterLaunch::JustRelaunch) { NSDictionary *conf = [NSDictionary dictionaryWithObject:[NSArray array] forKey:NSWorkspaceLaunchConfigurationArguments]; [[NSWorkspace sharedWorkspace] launchApplicationAtURL:[NSURL fileURLWithPath:Q2NSString(cExeDir() + cExeName())] options:NSWorkspaceLaunchAsync | NSWorkspaceLaunchNewInstance configuration:conf error:0]; return true; } #endif // OS_MAC_STORE NSString *path = @"", *args = @""; @try { path = [[NSBundle mainBundle] bundlePath]; if (!path) { LOG(("Could not get bundle path!!")); return false; } path = [path stringByAppendingString:@"/Contents/Frameworks/Updater"]; NSMutableArray *args = [[NSMutableArray alloc] initWithObjects:@"-workpath", Q2NSString(cWorkingDir()), @"-procid", nil]; [args addObject:[NSString stringWithFormat:@"%d", [[NSProcessInfo processInfo] processIdentifier]]]; if (cRestartingToSettings()) [args addObject:@"-tosettings"]; if (action == UpdaterLaunch::JustRelaunch) [args addObject:@"-noupdate"]; if (cLaunchMode() == LaunchModeAutoStart) [args addObject:@"-autostart"]; if (cDebug()) [args addObject:@"-debug"]; if (cStartInTray()) [args addObject:@"-startintray"]; if (cTestMode()) [args addObject:@"-testmode"]; if (cDataFile() != qsl("data")) { [args addObject:@"-key"]; [args addObject:Q2NSString(cDataFile())]; } if (customWorkingDir()) { [args addObject:@"-workdir_custom"]; } DEBUG_LOG(("Application Info: executing %1 %2").arg(NS2QString(path)).arg(NS2QString([args componentsJoinedByString:@" "]))); Logs::closeMain(); CrashReports::Finish(); if (![NSTask launchedTaskWithLaunchPath:path arguments:args]) { DEBUG_LOG(("Task not launched while executing %1 %2").arg(NS2QString(path)).arg(NS2QString([args componentsJoinedByString:@" "]))); return false; } } @catch (NSException *exception) { LOG(("Exception caught while executing %1 %2").arg(NS2QString(path)).arg(NS2QString(args))); return false; } @finally { } } return true; } } // namespace Platform ���������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/mac/mac_iconv_helper.c��������������������������������0000664�0000000�0000000�00000001363�13262451251�0025453�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include <iconv.h> #ifdef iconv_open #undef iconv_open #endif // iconv_open #ifdef iconv #undef iconv #endif // iconv #ifdef iconv_close #undef iconv_close #endif // iconv_close iconv_t iconv_open(const char* tocode, const char* fromcode) { return libiconv_open(tocode, fromcode); } size_t iconv(iconv_t cd, char** inbuf, size_t *inbytesleft, char** outbuf, size_t *outbytesleft) { return libiconv(cd, inbuf, inbytesleft, outbuf, outbytesleft); } int iconv_close(iconv_t cd) { return libiconv_close(cd); } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/mac/mac_utilities.h�����������������������������������0000664�0000000�0000000�00000002147�13262451251�0025017�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "lang/lang_keys.h" #include <Cocoa/Cocoa.h> namespace Platform { inline NSString *Q2NSString(const QString &str) { return [NSString stringWithUTF8String:str.toUtf8().constData()]; } inline NSString *NSlang(LangKey key) { return Q2NSString(lang(key)); } inline QString NS2QString(NSString *str) { return QString::fromUtf8([str cStringUsingEncoding:NSUTF8StringEncoding]); } template <int Size> inline QString MakeFromLetters(const uint32 (&letters)[Size]) { QString result; result.reserve(Size); for (int32 i = 0; i < Size; ++i) { auto code = letters[i]; auto salt1 = (code >> 8) & 0xFFU; auto salt2 = (code >> 24) & 0xFFU; auto part1 = ((code & 0xFFU) ^ (salt1 ^ salt2)) & 0xFFU; auto part2 = (((code >> 16) & 0xFFU) ^ (salt1 ^ ~salt2)) & 0xFFU; result.push_back(QChar((part2 << 8) | part1)); } return result; } } // namespace Platform �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/mac/mac_utilities.mm����������������������������������0000664�0000000�0000000�00000000430�13262451251�0025172�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "platform/mac/mac_utilities.h" ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/mac/main_window_mac.h���������������������������������0000664�0000000�0000000�00000005106�13262451251�0025315�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "platform/platform_main_window.h" #include "platform/mac/specific_mac_p.h" #include "base/timer.h" namespace Platform { class MainWindow : public Window::MainWindow { Q_OBJECT public: MainWindow(); void psFirstShow(); void psInitSysMenu(); void psUpdateMargins(); void psRefreshTaskbarIcon() { } bool psFilterNativeEvent(void *event); virtual QImage iconWithCounter(int size, int count, style::color bg, style::color fg, bool smallIcon) = 0; int getCustomTitleHeight() const { return _customTitleHeight; } ~MainWindow(); class Private; public slots: void psShowTrayMenu(); void psMacUndo(); void psMacRedo(); void psMacCut(); void psMacCopy(); void psMacPaste(); void psMacDelete(); void psMacSelectAll(); protected: bool eventFilter(QObject *obj, QEvent *evt) override; void handleActiveChangedHook() override; void stateChangedHook(Qt::WindowState state) override; void initHook() override; void updateWindowIcon() override; void titleVisibilityChangedHook() override; void unreadCounterChangedHook() override; QImage psTrayIcon(bool selected = false) const; bool hasTrayIcon() const override { return trayIcon; } void updateGlobalMenuHook() override; void workmodeUpdated(DBIWorkMode mode) override; QSystemTrayIcon *trayIcon = nullptr; QMenu *trayIconMenu = nullptr; QImage trayImg, trayImgSel; void psTrayMenuUpdated(); void psSetupTrayIcon(); virtual void placeSmallCounter(QImage &img, int size, int count, style::color bg, const QPoint &shift, style::color color) = 0; QTimer psUpdatedPositionTimer; void closeWithoutDestroy() override; private: void hideAndDeactivate(); void createGlobalMenu(); void updateTitleCounter(); void updateIconCounters(); friend class Private; std::unique_ptr<Private> _private; mutable bool psIdle; mutable QTimer psIdleTimer; base::Timer _hideAfterFullScreenTimer; QMenuBar psMainMenu; QAction *psLogout = nullptr; QAction *psUndo = nullptr; QAction *psRedo = nullptr; QAction *psCut = nullptr; QAction *psCopy = nullptr; QAction *psPaste = nullptr; QAction *psDelete = nullptr; QAction *psSelectAll = nullptr; QAction *psContacts = nullptr; QAction *psAddContact = nullptr; QAction *psNewGroup = nullptr; QAction *psNewChannel = nullptr; QAction *psShowTelegram = nullptr; int _customTitleHeight = 0; }; } // namespace Platform ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/mac/main_window_mac.mm��������������������������������0000664�0000000�0000000�00000055330�13262451251�0025503�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "platform/mac/main_window_mac.h" #include "styles/style_window.h" #include "mainwindow.h" #include "mainwidget.h" #include "application.h" #include "history/history.h" #include "history/history_widget.h" #include "history/history_inner_widget.h" #include "storage/localstorage.h" #include "window/notifications_manager_default.h" #include "window/themes/window_theme.h" #include "platform/platform_notifications_manager.h" #include "boxes/peer_list_controllers.h" #include "boxes/about_box.h" #include "lang/lang_keys.h" #include "platform/mac/mac_utilities.h" #include <Cocoa/Cocoa.h> #include <CoreFoundation/CFURL.h> #include <IOKit/IOKitLib.h> #include <IOKit/hidsystem/ev_keymap.h> #include <SPMediaKeyTap.h> @interface MainWindowObserver : NSObject { } - (id) init:(MainWindow::Private*)window; - (void) activeSpaceDidChange:(NSNotification *)aNotification; - (void) darkModeChanged:(NSNotification *)aNotification; - (void) screenIsLocked:(NSNotification *)aNotification; - (void) screenIsUnlocked:(NSNotification *)aNotification; - (void) windowWillEnterFullScreen:(NSNotification *)aNotification; - (void) windowWillExitFullScreen:(NSNotification *)aNotification; @end // @interface MainWindowObserver namespace Platform { namespace { // When we close a window that is fullscreen we first leave the fullscreen // mode and after that hide the window. This is a timeout for elaving the // fullscreen mode, after that we'll hide the window no matter what. constexpr auto kHideAfterFullscreenTimeoutMs = 3000; id FindClassInSubviews(NSView *parent, NSString *className) { for (NSView *child in [parent subviews]) { if ([child isKindOfClass:NSClassFromString(className)]) { return child; } else if (id inchild = FindClassInSubviews(child, className)) { return inchild; } } return nil; } #ifndef OS_MAC_OLD class LayerCreationChecker : public QObject { public: LayerCreationChecker(NSView * __weak view, base::lambda<void()> callback) : _weakView(view) , _callback(std::move(callback)) { QCoreApplication::instance()->installEventFilter(this); } protected: bool eventFilter(QObject *object, QEvent *event) override { if (!_weakView || [_weakView layer] != nullptr) { _callback(); } return QObject::eventFilter(object, event); } private: NSView * __weak _weakView = nil; base::lambda<void()> _callback; }; #endif // OS_MAC_OLD } // namespace class MainWindow::Private { public: Private(MainWindow *window); void setNativeWindow(NSWindow *window, NSView *view); void setWindowBadge(const QString &str); void setWindowTitle(const QString &str); void updateNativeTitle(); void enableShadow(WId winId); bool filterNativeEvent(void *event); void willEnterFullScreen(); void willExitFullScreen(); bool clipboardHasText(); ~Private(); private: void initCustomTitle(); void refreshWeakTitleReferences(); MainWindow *_public; friend class MainWindow; #ifdef OS_MAC_OLD NSWindow *_nativeWindow = nil; NSView *_nativeView = nil; #else // OS_MAC_OLD NSWindow * __weak _nativeWindow = nil; NSView * __weak _nativeView = nil; id __weak _nativeTitleWrapWeak = nil; id __weak _nativeTitleWeak = nil; std::unique_ptr<LayerCreationChecker> _layerCreationChecker; #endif // !OS_MAC_OLD bool _useNativeTitle = false; bool _inFullScreen = false; MainWindowObserver *_observer; NSPasteboard *_generalPasteboard = nullptr; int _generalPasteboardChangeCount = -1; bool _generalPasteboardHasText = false; }; } // namespace Platform @implementation MainWindowObserver { MainWindow::Private *_private; } - (id) init:(MainWindow::Private*)window { if (self = [super init]) { _private = window; } return self; } - (void) activeSpaceDidChange:(NSNotification *)aNotification { } - (void) darkModeChanged:(NSNotification *)aNotification { Notify::unreadCounterUpdated(); } - (void) screenIsLocked:(NSNotification *)aNotification { Global::SetScreenIsLocked(true); } - (void) screenIsUnlocked:(NSNotification *)aNotification { Global::SetScreenIsLocked(false); } - (void) windowWillEnterFullScreen:(NSNotification *)aNotification { _private->willEnterFullScreen(); } - (void) windowWillExitFullScreen:(NSNotification *)aNotification { _private->willExitFullScreen(); } @end // @implementation MainWindowObserver namespace Platform { MainWindow::Private::Private(MainWindow *window) : _public(window) , _observer([[MainWindowObserver alloc] init:this]) { _generalPasteboard = [NSPasteboard generalPasteboard]; @autoreleasepool { [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:_observer selector:@selector(activeSpaceDidChange:) name:NSWorkspaceActiveSpaceDidChangeNotification object:nil]; [[NSDistributedNotificationCenter defaultCenter] addObserver:_observer selector:@selector(darkModeChanged:) name:Q2NSString(strNotificationAboutThemeChange()) object:nil]; [[NSDistributedNotificationCenter defaultCenter] addObserver:_observer selector:@selector(screenIsLocked:) name:Q2NSString(strNotificationAboutScreenLocked()) object:nil]; [[NSDistributedNotificationCenter defaultCenter] addObserver:_observer selector:@selector(screenIsUnlocked:) name:Q2NSString(strNotificationAboutScreenUnlocked()) object:nil]; #ifndef OS_MAC_STORE // Register defaults for the whitelist of apps that want to use media keys [[NSUserDefaults standardUserDefaults] registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys:[SPMediaKeyTap defaultMediaKeyUserBundleIdentifiers], kMediaKeyUsingBundleIdentifiersDefaultsKey, nil]]; #endif // !OS_MAC_STORE } } void MainWindow::Private::setWindowBadge(const QString &str) { @autoreleasepool { [[NSApp dockTile] setBadgeLabel:Q2NSString(str)]; } } void MainWindow::Private::setWindowTitle(const QString &str) { _public->setWindowTitle(str); updateNativeTitle(); } void MainWindow::Private::setNativeWindow(NSWindow *window, NSView *view) { _nativeWindow = window; _nativeView = view; initCustomTitle(); } void MainWindow::Private::initCustomTitle() { #ifndef OS_MAC_OLD if (![_nativeWindow respondsToSelector:@selector(contentLayoutRect)] || ![_nativeWindow respondsToSelector:@selector(setTitlebarAppearsTransparent:)]) { return; } [_nativeWindow setTitlebarAppearsTransparent:YES]; [[NSNotificationCenter defaultCenter] addObserver:_observer selector:@selector(windowWillEnterFullScreen:) name:NSWindowWillEnterFullScreenNotification object:_nativeWindow]; [[NSNotificationCenter defaultCenter] addObserver:_observer selector:@selector(windowWillExitFullScreen:) name:NSWindowWillExitFullScreenNotification object:_nativeWindow]; // Qt has bug with layer-backed widgets containing QOpenGLWidgets. // See https://bugreports.qt.io/browse/QTBUG-64494 // Emulate custom title instead (code below). // // Tried to backport a fix, testing. [_nativeWindow setStyleMask:[_nativeWindow styleMask] | NSFullSizeContentViewWindowMask]; auto inner = [_nativeWindow contentLayoutRect]; auto full = [_nativeView frame]; _public->_customTitleHeight = qMax(qRound(full.size.height - inner.size.height), 0); // Qt still has some bug with layer-backed widgets containing QOpenGLWidgets. // See https://github.com/telegramdesktop/tdesktop/issues/4150 // Tried to workaround it by catching the first moment we have CALayer created // and explicitly setting contentsScale to window->backingScaleFactor there. _layerCreationChecker = std::make_unique<LayerCreationChecker>(_nativeView, [=] { if (_nativeView && _nativeWindow) { if (CALayer *layer = [_nativeView layer]) { LOG(("Window Info: Setting layer scale factor to: %1").arg([_nativeWindow backingScaleFactor])); [layer setContentsScale: [_nativeWindow backingScaleFactor]]; _layerCreationChecker = nullptr; } } else { _layerCreationChecker = nullptr; } }); // Disabled for now. //_useNativeTitle = true; //setWindowTitle(qsl("Telegram")); #endif // !OS_MAC_OLD } void MainWindow::Private::refreshWeakTitleReferences() { if (!_nativeWindow) { return; } #ifndef OS_MAC_OLD @autoreleasepool { if (NSView *parent = [[_nativeWindow contentView] superview]) { if (id titleWrap = FindClassInSubviews(parent, Q2NSString(strTitleWrapClass()))) { if ([titleWrap respondsToSelector:@selector(setBackgroundColor:)]) { if (id title = FindClassInSubviews(titleWrap, Q2NSString(strTitleClass()))) { if ([title respondsToSelector:@selector(setAttributedStringValue:)]) { _nativeTitleWrapWeak = titleWrap; _nativeTitleWeak = title; } } } } } } #endif // !OS_MAC_OLD } void MainWindow::Private::updateNativeTitle() { if (!_useNativeTitle) { return; } #ifndef OS_MAC_OLD if (!_nativeTitleWrapWeak || !_nativeTitleWeak) { refreshWeakTitleReferences(); } if (_nativeTitleWrapWeak && _nativeTitleWeak) { @autoreleasepool { auto convertColor = [](QColor color) { return [NSColor colorWithDeviceRed:color.redF() green:color.greenF() blue:color.blueF() alpha:color.alphaF()]; }; auto adjustFg = [](const style::color &st) { // Weird thing with NSTextField taking NSAttributedString with // NSForegroundColorAttributeName set to colorWithDeviceRed:green:blue // with components all equal to 128 - it ignores it and prints black text! auto color = st->c; return (color.red() == 128 && color.green() == 128 && color.blue() == 128) ? QColor(129, 129, 129, color.alpha()) : color; }; auto active = _public->isActiveWindow(); auto bgColor = (active ? st::titleBgActive : st::titleBg)->c; auto fgColor = adjustFg(active ? st::titleFgActive : st::titleFg); auto bgConverted = convertColor(bgColor); auto fgConverted = convertColor(fgColor); [_nativeTitleWrapWeak setBackgroundColor:bgConverted]; auto title = Q2NSString(_public->windowTitle()); NSDictionary *attributes = _inFullScreen ? nil : [NSDictionary dictionaryWithObjectsAndKeys: fgConverted, NSForegroundColorAttributeName, bgConverted, NSBackgroundColorAttributeName, nil]; NSAttributedString *string = [[NSAttributedString alloc] initWithString:title attributes:attributes]; [_nativeTitleWeak setAttributedStringValue:string]; } } #endif // !OS_MAC_OLD } bool MainWindow::Private::clipboardHasText() { auto currentChangeCount = static_cast<int>([_generalPasteboard changeCount]); if (_generalPasteboardChangeCount != currentChangeCount) { _generalPasteboardChangeCount = currentChangeCount; _generalPasteboardHasText = !Application::clipboard()->text().isEmpty(); } return _generalPasteboardHasText; } void MainWindow::Private::willEnterFullScreen() { _inFullScreen = true; _public->setTitleVisible(false); } void MainWindow::Private::willExitFullScreen() { _inFullScreen = false; _public->setTitleVisible(true); } void MainWindow::Private::enableShadow(WId winId) { // [[(NSView*)winId window] setStyleMask:NSBorderlessWindowMask]; // [[(NSView*)winId window] setHasShadow:YES]; } bool MainWindow::Private::filterNativeEvent(void *event) { NSEvent *e = static_cast<NSEvent*>(event); if (e && [e type] == NSSystemDefined && [e subtype] == SPSystemDefinedEventMediaKeys) { #ifndef OS_MAC_STORE // If event tap is not installed, handle events that reach the app instead if (![SPMediaKeyTap usesGlobalMediaKeyTap]) { return objc_handleMediaKeyEvent(e); } #else // !OS_MAC_STORE return objc_handleMediaKeyEvent(e); #endif // else for !OS_MAC_STORE } return false; } MainWindow::Private::~Private() { [_observer release]; } MainWindow::MainWindow() : _private(std::make_unique<Private>(this)) { #ifndef OS_MAC_OLD auto forceOpenGL = std::make_unique<QOpenGLWidget>(this); #endif // !OS_MAC_OLD trayImg = st::macTrayIcon.instance(QColor(0, 0, 0, 180), dbisOne); trayImgSel = st::macTrayIcon.instance(QColor(255, 255, 255), dbisOne); _hideAfterFullScreenTimer.setCallback([this] { hideAndDeactivate(); }); subscribe(Window::Theme::Background(), [this](const Window::Theme::BackgroundUpdate &data) { if (data.paletteChanged()) { _private->updateNativeTitle(); } }); } void MainWindow::closeWithoutDestroy() { NSWindow *nsWindow = [reinterpret_cast<NSView*>(winId()) window]; auto isFullScreen = (([nsWindow styleMask] & NSFullScreenWindowMask) == NSFullScreenWindowMask); if (isFullScreen) { _hideAfterFullScreenTimer.callOnce(kHideAfterFullscreenTimeoutMs); [nsWindow toggleFullScreen:nsWindow]; } else { hideAndDeactivate(); } } void MainWindow::stateChangedHook(Qt::WindowState state) { if (_hideAfterFullScreenTimer.isActive()) { _hideAfterFullScreenTimer.callOnce(0); } } void MainWindow::handleActiveChangedHook() { InvokeQueued(this, [this] { _private->updateNativeTitle(); }); } void MainWindow::initHook() { _customTitleHeight = 0; if (auto view = reinterpret_cast<NSView*>(winId())) { if (auto window = [view window]) { _private->setNativeWindow(window, view); } } } void MainWindow::updateWindowIcon() { } void MainWindow::titleVisibilityChangedHook() { updateTitleCounter(); } void MainWindow::hideAndDeactivate() { hide(); } QImage MainWindow::psTrayIcon(bool selected) const { return selected ? trayImgSel : trayImg; } void MainWindow::psShowTrayMenu() { } void MainWindow::psTrayMenuUpdated() { } void MainWindow::psSetupTrayIcon() { if (!trayIcon) { trayIcon = new QSystemTrayIcon(this); QIcon icon(QPixmap::fromImage(psTrayIcon(), Qt::ColorOnly)); icon.addPixmap(QPixmap::fromImage(psTrayIcon(true), Qt::ColorOnly), QIcon::Selected); trayIcon->setIcon(icon); trayIcon->setToolTip(str_const_toString(AppName)); connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(toggleTray(QSystemTrayIcon::ActivationReason)), Qt::UniqueConnection); App::wnd()->updateTrayMenu(); } updateIconCounters(); trayIcon->show(); } void MainWindow::workmodeUpdated(DBIWorkMode mode) { psSetupTrayIcon(); if (mode == dbiwmWindowOnly) { if (trayIcon) { trayIcon->setContextMenu(0); delete trayIcon; trayIcon = nullptr; } } } void _placeCounter(QImage &img, int size, int count, style::color bg, style::color color) { if (!count) return; auto savedRatio = img.devicePixelRatio(); img.setDevicePixelRatio(1.); { Painter p(&img); PainterHighQualityEnabler hq(p); auto cnt = (count < 100) ? QString("%1").arg(count) : QString("..%1").arg(count % 100, 2, 10, QChar('0')); auto cntSize = cnt.size(); p.setBrush(bg); p.setPen(Qt::NoPen); int32 fontSize, skip; if (size == 22) { skip = 1; fontSize = 8; } else { skip = 2; fontSize = 16; } style::font f(fontSize, 0, 0); int32 w = f->width(cnt), d, r; if (size == 22) { d = (cntSize < 2) ? 3 : 2; r = (cntSize < 2) ? 6 : 5; } else { d = (cntSize < 2) ? 6 : 5; r = (cntSize < 2) ? 9 : 11; } p.drawRoundedRect(QRect(size - w - d * 2 - skip, size - f->height - skip, w + d * 2, f->height), r, r); p.setCompositionMode(QPainter::CompositionMode_Source); p.setFont(f); p.setPen(color); p.drawText(size - w - d - skip, size - f->height + f->ascent - skip, cnt); } img.setDevicePixelRatio(savedRatio); } void MainWindow::updateTitleCounter() { _private->setWindowTitle(titleVisible() ? QString() : titleText()); } void MainWindow::unreadCounterChangedHook() { updateTitleCounter(); updateIconCounters(); } void MainWindow::updateIconCounters() { auto counter = App::histories().unreadBadge(); QString cnt = (counter < 1000) ? QString("%1").arg(counter) : QString("..%1").arg(counter % 100, 2, 10, QChar('0')); _private->setWindowBadge(counter ? cnt : QString()); if (trayIcon) { bool muted = App::histories().unreadOnlyMuted(); bool dm = objc_darkMode(); auto &bg = (muted ? st::trayCounterBgMute : st::trayCounterBg); QIcon icon; QImage img(psTrayIcon(dm)), imgsel(psTrayIcon(true)); img.detach(); imgsel.detach(); int32 size = cRetina() ? 44 : 22; _placeCounter(img, size, counter, bg, (dm && muted) ? st::trayCounterFgMacInvert : st::trayCounterFg); _placeCounter(imgsel, size, counter, st::trayCounterBgMacInvert, st::trayCounterFgMacInvert); icon.addPixmap(App::pixmapFromImageInPlace(std::move(img))); icon.addPixmap(App::pixmapFromImageInPlace(std::move(imgsel)), QIcon::Selected); trayIcon->setIcon(icon); } } void MainWindow::psFirstShow() { psUpdateMargins(); bool showShadows = true; show(); _private->enableShadow(winId()); if (cWindowPos().maximized) { DEBUG_LOG(("Window Pos: First show, setting maximized.")); setWindowState(Qt::WindowMaximized); } if ((cLaunchMode() == LaunchModeAutoStart && cStartMinimized()) || cStartInTray()) { setWindowState(Qt::WindowMinimized); if (Global::WorkMode().value() == dbiwmTrayOnly || Global::WorkMode().value() == dbiwmWindowAndTray) { hide(); } else { show(); } showShadows = false; } else { show(); } setPositionInited(); createGlobalMenu(); } void MainWindow::createGlobalMenu() { auto main = psMainMenu.addMenu(qsl("Telegram")); auto about = main->addAction(lng_mac_menu_about_telegram(lt_telegram, qsl("Telegram"))); connect(about, &QAction::triggered, about, [] { if (App::wnd() && App::wnd()->isHidden()) App::wnd()->showFromTray(); Ui::show(Box<AboutBox>()); }); about->setMenuRole(QAction::AboutQtRole); main->addSeparator(); QAction *prefs = main->addAction(lang(lng_mac_menu_preferences), App::wnd(), SLOT(showSettings()), QKeySequence(Qt::ControlModifier | Qt::Key_Comma)); prefs->setMenuRole(QAction::PreferencesRole); QMenu *file = psMainMenu.addMenu(lang(lng_mac_menu_file)); psLogout = file->addAction(lang(lng_mac_menu_logout), App::wnd(), SLOT(onLogout())); QMenu *edit = psMainMenu.addMenu(lang(lng_mac_menu_edit)); psUndo = edit->addAction(lang(lng_mac_menu_undo), this, SLOT(psMacUndo()), QKeySequence::Undo); psRedo = edit->addAction(lang(lng_mac_menu_redo), this, SLOT(psMacRedo()), QKeySequence::Redo); edit->addSeparator(); psCut = edit->addAction(lang(lng_mac_menu_cut), this, SLOT(psMacCut()), QKeySequence::Cut); psCopy = edit->addAction(lang(lng_mac_menu_copy), this, SLOT(psMacCopy()), QKeySequence::Copy); psPaste = edit->addAction(lang(lng_mac_menu_paste), this, SLOT(psMacPaste()), QKeySequence::Paste); psDelete = edit->addAction(lang(lng_mac_menu_delete), this, SLOT(psMacDelete()), QKeySequence(Qt::ControlModifier | Qt::Key_Backspace)); edit->addSeparator(); psSelectAll = edit->addAction(lang(lng_mac_menu_select_all), this, SLOT(psMacSelectAll()), QKeySequence::SelectAll); QMenu *window = psMainMenu.addMenu(lang(lng_mac_menu_window)); psContacts = window->addAction(lang(lng_mac_menu_contacts)); connect(psContacts, &QAction::triggered, psContacts, [] { if (App::wnd() && App::wnd()->isHidden()) App::wnd()->showFromTray(); if (!App::self()) return; Ui::show(Box<PeerListBox>(std::make_unique<ContactsBoxController>(), [](not_null<PeerListBox*> box) { box->addButton(langFactory(lng_close), [box] { box->closeBox(); }); box->addLeftButton(langFactory(lng_profile_add_contact), [] { App::wnd()->onShowAddContact(); }); })); }); psAddContact = window->addAction(lang(lng_mac_menu_add_contact), App::wnd(), SLOT(onShowAddContact())); window->addSeparator(); psNewGroup = window->addAction(lang(lng_mac_menu_new_group), App::wnd(), SLOT(onShowNewGroup())); psNewChannel = window->addAction(lang(lng_mac_menu_new_channel), App::wnd(), SLOT(onShowNewChannel())); window->addSeparator(); psShowTelegram = window->addAction(lang(lng_mac_menu_show), App::wnd(), SLOT(showFromTray())); updateGlobalMenu(); } namespace { void _sendKeySequence(Qt::Key key, Qt::KeyboardModifiers modifiers = Qt::NoModifier) { QWidget *focused = QApplication::focusWidget(); if (qobject_cast<QLineEdit*>(focused) || qobject_cast<QTextEdit*>(focused) || qobject_cast<HistoryInner*>(focused)) { QApplication::postEvent(focused, new QKeyEvent(QEvent::KeyPress, key, modifiers)); QApplication::postEvent(focused, new QKeyEvent(QEvent::KeyRelease, key, modifiers)); } } void _forceDisabled(QAction *action, bool disabled) { if (action->isEnabled()) { if (disabled) action->setDisabled(true); } else if (!disabled) { action->setDisabled(false); } } } void MainWindow::psMacUndo() { _sendKeySequence(Qt::Key_Z, Qt::ControlModifier); } void MainWindow::psMacRedo() { _sendKeySequence(Qt::Key_Z, Qt::ControlModifier | Qt::ShiftModifier); } void MainWindow::psMacCut() { _sendKeySequence(Qt::Key_X, Qt::ControlModifier); } void MainWindow::psMacCopy() { _sendKeySequence(Qt::Key_C, Qt::ControlModifier); } void MainWindow::psMacPaste() { _sendKeySequence(Qt::Key_V, Qt::ControlModifier); } void MainWindow::psMacDelete() { _sendKeySequence(Qt::Key_Delete); } void MainWindow::psMacSelectAll() { _sendKeySequence(Qt::Key_A, Qt::ControlModifier); } void MainWindow::psInitSysMenu() { } void MainWindow::psUpdateMargins() { } void MainWindow::updateGlobalMenuHook() { if (!App::wnd() || !positionInited()) return; auto focused = QApplication::focusWidget(); bool isLogged = !!App::self(), canUndo = false, canRedo = false, canCut = false, canCopy = false, canPaste = false, canDelete = false, canSelectAll = false; auto clipboardHasText = _private->clipboardHasText(); if (auto edit = qobject_cast<QLineEdit*>(focused)) { canCut = canCopy = canDelete = edit->hasSelectedText(); canSelectAll = !edit->text().isEmpty(); canUndo = edit->isUndoAvailable(); canRedo = edit->isRedoAvailable(); canPaste = clipboardHasText; } else if (auto edit = qobject_cast<QTextEdit*>(focused)) { canCut = canCopy = canDelete = edit->textCursor().hasSelection(); canSelectAll = !edit->document()->isEmpty(); canUndo = edit->document()->isUndoAvailable(); canRedo = edit->document()->isRedoAvailable(); canPaste = clipboardHasText; } else if (auto list = qobject_cast<HistoryInner*>(focused)) { canCopy = list->canCopySelected(); canDelete = list->canDeleteSelected(); } App::wnd()->updateIsActive(0); _forceDisabled(psLogout, !isLogged && !App::passcoded()); _forceDisabled(psUndo, !canUndo); _forceDisabled(psRedo, !canRedo); _forceDisabled(psCut, !canCut); _forceDisabled(psCopy, !canCopy); _forceDisabled(psPaste, !canPaste); _forceDisabled(psDelete, !canDelete); _forceDisabled(psSelectAll, !canSelectAll); _forceDisabled(psContacts, !isLogged || App::passcoded()); _forceDisabled(psAddContact, !isLogged || App::passcoded()); _forceDisabled(psNewGroup, !isLogged || App::passcoded()); _forceDisabled(psNewChannel, !isLogged || App::passcoded()); _forceDisabled(psShowTelegram, App::wnd()->isActive()); } bool MainWindow::psFilterNativeEvent(void *event) { return _private->filterNativeEvent(event); } bool MainWindow::eventFilter(QObject *obj, QEvent *evt) { QEvent::Type t = evt->type(); if (t == QEvent::FocusIn || t == QEvent::FocusOut) { if (qobject_cast<QLineEdit*>(obj) || qobject_cast<QTextEdit*>(obj) || qobject_cast<HistoryInner*>(obj)) { updateGlobalMenu(); } } return Window::MainWindow::eventFilter(obj, evt); } MainWindow::~MainWindow() { } } // namespace ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/mac/notifications_manager_mac.h�����������������������0000664�0000000�0000000�00000001731�13262451251�0027345�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "platform/platform_notifications_manager.h" #include "base/weak_ptr.h" namespace Platform { namespace Notifications { bool SkipAudio(); bool SkipToast(); class Manager : public Window::Notifications::NativeManager, public base::has_weak_ptr { public: Manager(Window::Notifications::System *system); ~Manager(); protected: void doShowNativeNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, const QString &msg, bool hideNameAndPhoto, bool hideReplyButton) override; void doClearAllFast() override; void doClearFromHistory(History *history) override; private: class Private; const std::unique_ptr<Private> _private; }; } // namespace Notifications } // namespace Platform ���������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/mac/notifications_manager_mac.mm����������������������0000664�0000000�0000000�00000025560�13262451251�0027535�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "platform/mac/notifications_manager_mac.h" #include "platform/platform_specific.h" #include "platform/mac/mac_utilities.h" #include "history/history.h" #include "mainwindow.h" #include "styles/style_window.h" #include <thread> #include <Cocoa/Cocoa.h> namespace { static constexpr auto kQuerySettingsEachMs = 1000; auto DoNotDisturbEnabled = false; auto LastSettingsQueryMs = 0; void queryDoNotDisturbState() { auto ms = getms(true); if (LastSettingsQueryMs > 0 && ms <= LastSettingsQueryMs + kQuerySettingsEachMs) { return; } LastSettingsQueryMs = ms; auto userDefaults = [NSUserDefaults alloc]; if ([userDefaults respondsToSelector:@selector(initWithSuiteName:)]) { id userDefaultsValue = [[[NSUserDefaults alloc] initWithSuiteName:@"com.apple.notificationcenterui_test"] objectForKey:@"doNotDisturb"]; DoNotDisturbEnabled = ([userDefaultsValue boolValue] == YES); } else { DoNotDisturbEnabled = false; } } using Manager = Platform::Notifications::Manager; } // namespace NSImage *qt_mac_create_nsimage(const QPixmap &pm); @interface NotificationDelegate : NSObject<NSUserNotificationCenterDelegate> { } - (id) initWithManager:(base::weak_ptr<Manager>)manager managerId:(uint64)managerId; - (void) userNotificationCenter:(NSUserNotificationCenter*)center didActivateNotification:(NSUserNotification*)notification; - (BOOL) userNotificationCenter:(NSUserNotificationCenter*)center shouldPresentNotification:(NSUserNotification*)notification; @end // @interface NotificationDelegate @implementation NotificationDelegate { base::weak_ptr<Manager> _manager; uint64 _managerId; } - (id) initWithManager:(base::weak_ptr<Manager>)manager managerId:(uint64)managerId { if (self = [super init]) { _manager = manager; _managerId = managerId; } return self; } - (void) userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification { NSDictionary *notificationUserInfo = [notification userInfo]; NSNumber *managerIdObject = [notificationUserInfo objectForKey:@"manager"]; auto notificationManagerId = managerIdObject ? [managerIdObject unsignedLongLongValue] : 0ULL; DEBUG_LOG(("Received notification with instance %1, mine: %2").arg(notificationManagerId).arg(_managerId)); if (notificationManagerId != _managerId) { // other app instance notification crl::on_main([] { // Usually we show and activate main window when the application // is activated (receives applicationDidBecomeActive: notification). // // This is used for window show in Cmd+Tab switching to the application. // // But when a notification arrives sometimes macOS still activates the app // and we receive applicationDidBecomeActive: notification even if the // notification was sent by another instance of the application. In that case // we set a flag for a couple of seconds to ignore this app activation. objc_ignoreApplicationActivationRightNow(); }); return; } NSNumber *peerObject = [notificationUserInfo objectForKey:@"peer"]; auto notificationPeerId = peerObject ? [peerObject unsignedLongLongValue] : 0ULL; if (!notificationPeerId) { LOG(("App Error: A notification with unknown peer was received")); return; } NSNumber *msgObject = [notificationUserInfo objectForKey:@"msgid"]; auto notificationMsgId = msgObject ? [msgObject intValue] : 0; if (notification.activationType == NSUserNotificationActivationTypeReplied) { const auto notificationReply = QString::fromUtf8([[[notification response] string] UTF8String]); const auto manager = _manager; crl::on_main(manager, [=] { manager->notificationReplied(notificationPeerId, notificationMsgId, notificationReply); }); } else if (notification.activationType == NSUserNotificationActivationTypeContentsClicked) { const auto manager = _manager; crl::on_main(manager, [=] { manager->notificationActivated(notificationPeerId, notificationMsgId); }); } [center removeDeliveredNotification: notification]; } - (BOOL) userNotificationCenter:(NSUserNotificationCenter *)center shouldPresentNotification:(NSUserNotification *)notification { return YES; } @end // @implementation NotificationDelegate namespace Platform { namespace Notifications { bool SkipAudio() { queryDoNotDisturbState(); return DoNotDisturbEnabled; } bool SkipToast() { if (Supported()) { // Do not skip native notifications because of Do not disturb. // They respect this setting anyway. return false; } queryDoNotDisturbState(); return DoNotDisturbEnabled; } bool Supported() { return (cPlatform() != dbipMacOld); } std::unique_ptr<Window::Notifications::Manager> Create(Window::Notifications::System *system) { if (Supported()) { return std::make_unique<Manager>(system); } return nullptr; } void FlashBounce() { [NSApp requestUserAttention:NSInformationalRequest]; } class Manager::Private : public QObject, private base::Subscriber { public: Private(Manager *manager); void showNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, const QString &msg, bool hideNameAndPhoto, bool hideReplyButton); void clearAll(); void clearFromHistory(History *history); void updateDelegate(); ~Private(); private: template <typename Task> void putClearTask(Task task); void clearingThreadLoop(); const uint64 _managerId = 0; QString _managerIdString; NotificationDelegate *_delegate = nullptr; std::thread _clearingThread; std::mutex _clearingMutex; std::condition_variable _clearingCondition; struct ClearFromHistory { PeerId peerId; }; struct ClearAll { }; struct ClearFinish { }; using ClearTask = base::variant<ClearFromHistory, ClearAll, ClearFinish>; std::vector<ClearTask> _clearingTasks; }; Manager::Private::Private(Manager *manager) : _managerId(rand_value<uint64>()) , _managerIdString(QString::number(_managerId)) , _delegate([[NotificationDelegate alloc] initWithManager:manager managerId:_managerId]) { updateDelegate(); subscribe(Global::RefWorkMode(), [this](DBIWorkMode mode) { // We need to update the delegate _after_ the tray icon change was done in Qt. // Because Qt resets the delegate. crl::on_main(this, [=] { updateDelegate(); }); }); } void Manager::Private::showNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, const QString &msg, bool hideNameAndPhoto, bool hideReplyButton) { @autoreleasepool { NSUserNotification *notification = [[[NSUserNotification alloc] init] autorelease]; if ([notification respondsToSelector:@selector(setIdentifier:)]) { auto identifier = _managerIdString + '_' + QString::number(peer->id) + '_' + QString::number(msgId); auto identifierValue = Q2NSString(identifier); [notification setIdentifier:identifierValue]; } [notification setUserInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithUnsignedLongLong:peer->id],@"peer",[NSNumber numberWithInt:msgId],@"msgid",[NSNumber numberWithUnsignedLongLong:_managerId],@"manager",nil]]; [notification setTitle:Q2NSString(title)]; [notification setSubtitle:Q2NSString(subtitle)]; [notification setInformativeText:Q2NSString(msg)]; if (!hideNameAndPhoto && [notification respondsToSelector:@selector(setContentImage:)]) { auto userpic = peer->genUserpic(st::notifyMacPhotoSize); NSImage *img = [qt_mac_create_nsimage(userpic) autorelease]; [notification setContentImage:img]; } if (!hideReplyButton && [notification respondsToSelector:@selector(setHasReplyButton:)]) { [notification setHasReplyButton:YES]; } [notification setSoundName:nil]; NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter]; [center deliverNotification:notification]; } } void Manager::Private::clearingThreadLoop() { auto finished = false; while (!finished) { auto clearAll = false; auto clearFromPeers = std::set<PeerId>(); // Better to use flatmap. { std::unique_lock<std::mutex> lock(_clearingMutex); while (_clearingTasks.empty()) { _clearingCondition.wait(lock); } for (auto &task : _clearingTasks) { if (base::get_if<ClearFinish>(&task)) { finished = true; clearAll = true; } else if (base::get_if<ClearAll>(&task)) { clearAll = true; } else if (auto fromHistory = base::get_if<ClearFromHistory>(&task)) { clearFromPeers.insert(fromHistory->peerId); } } _clearingTasks.clear(); } auto clearByPeer = [&clearFromPeers](NSDictionary *notificationUserInfo) { if (NSNumber *peerObject = [notificationUserInfo objectForKey:@"peer"]) { auto notificationPeerId = [peerObject unsignedLongLongValue]; if (notificationPeerId) { return (clearFromPeers.find(notificationPeerId) != clearFromPeers.cend()); } } return true; }; NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter]; NSArray *notificationsList = [center deliveredNotifications]; for (id notification in notificationsList) { NSDictionary *notificationUserInfo = [notification userInfo]; NSNumber *managerIdObject = [notificationUserInfo objectForKey:@"manager"]; auto notificationManagerId = managerIdObject ? [managerIdObject unsignedLongLongValue] : 0ULL; if (notificationManagerId == _managerId) { if (clearAll || clearByPeer(notificationUserInfo)) { [center removeDeliveredNotification:notification]; } } } } } template <typename Task> void Manager::Private::putClearTask(Task task) { if (!_clearingThread.joinable()) { _clearingThread = std::thread([this] { clearingThreadLoop(); }); } std::unique_lock<std::mutex> lock(_clearingMutex); _clearingTasks.push_back(task); _clearingCondition.notify_one(); } void Manager::Private::clearAll() { putClearTask(ClearAll()); } void Manager::Private::clearFromHistory(History *history) { putClearTask(ClearFromHistory { history->peer->id }); } void Manager::Private::updateDelegate() { NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter]; [center setDelegate:_delegate]; } Manager::Private::~Private() { if (_clearingThread.joinable()) { putClearTask(ClearFinish()); _clearingThread.join(); } [_delegate release]; } Manager::Manager(Window::Notifications::System *system) : NativeManager(system) , _private(std::make_unique<Private>(this)) { } Manager::~Manager() = default; void Manager::doShowNativeNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, const QString &msg, bool hideNameAndPhoto, bool hideReplyButton) { _private->showNotification(peer, msgId, title, subtitle, msg, hideNameAndPhoto, hideReplyButton); } void Manager::doClearAllFast() { _private->clearAll(); } void Manager::doClearFromHistory(History *history) { _private->clearFromHistory(history); } } // namespace Notifications } // namespace Platform ������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/mac/specific_mac.h������������������������������������0000664�0000000�0000000�00000004761�13262451251�0024575�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "platform/mac/specific_mac_p.h" namespace Platform { inline bool TranslucentWindowsSupported(QPoint globalPosition) { return true; } QString CurrentExecutablePath(int argc, char *argv[]); namespace ThirdParty { inline void start() { } inline void finish() { } } // namespace ThirdParty } // namespace Platform inline QString psServerPrefix() { #ifndef OS_MAC_STORE return qsl("/tmp/"); #else // OS_MAC_STORE return objc_documentsPath(); #endif // OS_MAC_STORE } inline void psCheckLocalSocket(const QString &serverName) { QFile address(serverName); if (address.exists()) { address.remove(); } } void psWriteDump(); QString psPrepareCrashDump(const QByteArray &crashdump, QString dumpfile); void psDeleteDir(const QString &dir); void psUserActionDone(); bool psIdleSupported(); TimeMs psIdleTime(); QStringList psInitLogs(); void psClearInitLogs(); void psActivateProcess(uint64 pid = 0); QString psLocalServerPrefix(); QString psAppDataPath(); QString psDownloadPath(); void psAutoStart(bool start, bool silent = false); void psSendToMenu(bool send, bool silent = false); QRect psDesktopRect(); void psShowOverAll(QWidget *w, bool canFocus = true); void psBringToBack(QWidget *w); int psCleanup(); int psFixPrevious(); bool psShowOpenWithMenu(int x, int y, const QString &file); QAbstractNativeEventFilter *psNativeEventFilter(); void psNewVersion(); void psUpdateOverlayed(QWidget *widget); void psDownloadPathEnableAccess(); QByteArray psDownloadPathBookmark(const QString &path); QByteArray psPathBookmark(const QString &path); class PsFileBookmark { public: PsFileBookmark(const QByteArray &bookmark) : _inner(bookmark) { } bool check() const { return _inner.valid(); } bool enable() const { return _inner.enable(); } void disable() const { return _inner.disable(); } const QString &name(const QString &original) const { return _inner.name(original); } QByteArray bookmark() const { return _inner.bookmark(); } private: objc_FileBookmark _inner; }; QString strNotificationAboutThemeChange(); QString strNotificationAboutScreenLocked(); QString strNotificationAboutScreenUnlocked(); QString strStyleOfInterface(); QString strTitleWrapClass(); QString strTitleClass(); bool psLaunchMaps(const LocationCoords &coords); ���������������tdesktop-1.2.17/Telegram/SourceFiles/platform/mac/specific_mac.mm�����������������������������������0000664�0000000�0000000�00000030030�13262451251�0024743�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "platform/mac/specific_mac.h" #include "lang/lang_keys.h" #include "application.h" #include "mainwidget.h" #include "history/history_widget.h" #include "core/crash_reports.h" #include "storage/localstorage.h" #include "passcodewidget.h" #include "mainwindow.h" #include "history/history_location_manager.h" #include "platform/mac/mac_utilities.h" #include <cstdlib> #include <execinfo.h> #include <Cocoa/Cocoa.h> #include <CoreFoundation/CFURL.h> #include <IOKit/IOKitLib.h> #include <IOKit/hidsystem/ev_keymap.h> #include <SPMediaKeyTap.h> #include <mach-o/dyld.h> namespace { QStringList _initLogs; class _PsEventFilter : public QAbstractNativeEventFilter { public: _PsEventFilter() { } bool nativeEventFilter(const QByteArray &eventType, void *message, long *result) { auto wnd = App::wnd(); if (!wnd) return false; return wnd->psFilterNativeEvent(message); } }; _PsEventFilter *_psEventFilter = nullptr; }; namespace { QRect _monitorRect; TimeMs _monitorLastGot = 0; } // namespace QRect psDesktopRect() { auto tnow = getms(true); if (tnow > _monitorLastGot + 1000 || tnow < _monitorLastGot) { _monitorLastGot = tnow; _monitorRect = QApplication::desktop()->availableGeometry(App::wnd()); } return _monitorRect; } void psShowOverAll(QWidget *w, bool canFocus) { objc_showOverAll(w->winId(), canFocus); } void psBringToBack(QWidget *w) { objc_bringToBack(w->winId()); } QAbstractNativeEventFilter *psNativeEventFilter() { delete _psEventFilter; _psEventFilter = new _PsEventFilter(); return _psEventFilter; } void psWriteDump() { #ifndef TDESKTOP_DISABLE_CRASH_REPORTS double v = objc_appkitVersion(); CrashReports::dump() << "OS-Version: " << v; #endif // TDESKTOP_DISABLE_CRASH_REPORTS } QString demanglestr(const QString &mangled) { QByteArray cmd = ("c++filt -n " + mangled).toUtf8(); FILE *f = popen(cmd.constData(), "r"); if (!f) return "BAD_SYMBOL_" + mangled; QString result; char buffer[4096] = {0}; while (!feof(f)) { if (fgets(buffer, 4096, f) != NULL) { result += buffer; } } pclose(f); return result.trimmed(); } QString escapeShell(const QString &str) { QString result; const QChar *b = str.constData(), *e = str.constEnd(); for (const QChar *ch = b; ch != e; ++ch) { if (*ch == ' ' || *ch == '"' || *ch == '\'' || *ch == '\\') { if (result.isEmpty()) { result.reserve(str.size() * 2); } if (ch > b) { result.append(b, ch - b); } result.append('\\'); b = ch; } } if (result.isEmpty()) return str; if (e > b) { result.append(b, e - b); } return result; } QStringList atosstr(uint64 *addresses, int count, uint64 base) { QStringList result; if (!count || cExeName().isEmpty()) return result; result.reserve(count); QString cmdstr = "atos -o " + escapeShell(cExeDir() + cExeName()) + qsl("/Contents/MacOS/Telegram -l 0x%1").arg(base, 0, 16); for (int i = 0; i < count; ++i) { if (addresses[i]) { cmdstr += qsl(" 0x%1").arg(addresses[i], 0, 16); } } QByteArray cmd = cmdstr.toUtf8(); FILE *f = popen(cmd.constData(), "r"); QStringList atosResult; if (f) { char buffer[4096] = {0}; while (!feof(f)) { if (fgets(buffer, 4096, f) != NULL) { atosResult.push_back(QString::fromUtf8(buffer)); } } pclose(f); } for (int i = 0, j = 0; i < count; ++i) { if (addresses[i]) { if (j < atosResult.size() && !atosResult.at(j).isEmpty() && !atosResult.at(j).startsWith(qstr("0x"))) { result.push_back(atosResult.at(j).trimmed()); } else { result.push_back(QString()); } ++j; } else { result.push_back(QString()); } } return result; } QString psPrepareCrashDump(const QByteArray &crashdump, QString dumpfile) { QString initial = QString::fromUtf8(crashdump), result; QStringList lines = initial.split('\n'); result.reserve(initial.size()); int32 i = 0, l = lines.size(); while (i < l) { uint64 addresses[1024] = { 0 }; for (; i < l; ++i) { result.append(lines.at(i)).append('\n'); QString line = lines.at(i).trimmed(); if (line == qstr("Base image addresses:")) { ++i; break; } } uint64 base = 0; for (int32 start = i; i < l; ++i) { QString line = lines.at(i).trimmed(); if (line.isEmpty()) break; if (!base) { QRegularExpressionMatch m = QRegularExpression(qsl("^\\d+ (\\d+) \\((.+)\\)")).match(line); if (m.hasMatch()) { if (uint64 address = m.captured(1).toULongLong()) { if (m.captured(2).endsWith(qstr("Contents/MacOS/Telegram"))) { base = address; } } } } } if (base) { result.append(qsl("(base address read: 0x%1)\n").arg(base, 0, 16)); } else { result.append(qsl("ERROR: base address not read!\n")); } for (; i < l; ++i) { result.append(lines.at(i)).append('\n'); QString line = lines.at(i).trimmed(); if (line == qstr("Backtrace:")) { ++i; break; } } int32 start = i; for (; i < l; ++i) { QString line = lines.at(i).trimmed(); if (line.isEmpty()) break; if (QRegularExpression(qsl("^\\d+")).match(line).hasMatch()) { QStringList lst = line.split(' ', QString::SkipEmptyParts); if (lst.size() > 2) { uint64 addr = lst.at(2).startsWith(qstr("0x")) ? lst.at(2).mid(2).toULongLong(0, 16) : lst.at(2).toULongLong(); addresses[i - start] = addr; } } } QStringList atos = atosstr(addresses, i - start, base); for (i = start; i < l; ++i) { QString line = lines.at(i).trimmed(); if (line.isEmpty()) break; if (!QRegularExpression(qsl("^\\d+")).match(line).hasMatch()) { if (!lines.at(i).startsWith(qstr("ERROR: "))) { result.append(qstr("BAD LINE: ")); } result.append(line).append('\n'); continue; } QStringList lst = line.split(' ', QString::SkipEmptyParts); result.append('\n').append(lst.at(0)).append(qsl(". ")); if (lst.size() < 3) { result.append(qstr("BAD LINE: ")).append(line).append('\n'); continue; } if (lst.size() > 5 && lst.at(3) == qsl("0x0") && lst.at(4) == qsl("+") && lst.at(5) == qsl("1")) { result.append(qsl("(0x1 separator)\n")); continue; } if (i - start < atos.size()) { if (!atos.at(i - start).isEmpty()) { result.append(atos.at(i - start)).append('\n'); continue; } } for (int j = 1, s = lst.size();;) { if (lst.at(j).startsWith('_')) { result.append(demanglestr(lst.at(j))); if (++j < s) { result.append(' '); for (;;) { result.append(lst.at(j)); if (++j < s) { result.append(' '); } else { break; } } } break; } else if (j > 2) { result.append(lst.at(j)); } if (++j < s) { result.append(' '); } else { break; } } result.append(qsl(" [demangled]")).append('\n'); } } return result; } void psDeleteDir(const QString &dir) { objc_deleteDir(dir); } namespace { auto _lastUserAction = 0LL; } // namespace void psUserActionDone() { _lastUserAction = getms(true); } bool psIdleSupported() { return objc_idleSupported(); } TimeMs psIdleTime() { auto idleTime = 0LL; return objc_idleTime(idleTime) ? idleTime : (getms(true) - _lastUserAction); } QStringList psInitLogs() { return _initLogs; } void psClearInitLogs() { _initLogs = QStringList(); } void psActivateProcess(uint64 pid) { if (!pid) { objc_activateProgram(App::wnd() ? App::wnd()->winId() : 0); } } QString psAppDataPath() { return objc_appDataPath(); } QString psDownloadPath() { return objc_downloadPath(); } void psDoCleanup() { try { psAutoStart(false, true); psSendToMenu(false, true); } catch (...) { } } int psCleanup() { psDoCleanup(); return 0; } void psDoFixPrevious() { } int psFixPrevious() { psDoFixPrevious(); return 0; } namespace Platform { void start() { objc_start(); } void finish() { delete _psEventFilter; _psEventFilter = nullptr; objc_finish(); } void StartTranslucentPaint(QPainter &p, QPaintEvent *e) { #ifdef OS_MAC_OLD p.setCompositionMode(QPainter::CompositionMode_Source); p.fillRect(e->rect(), Qt::transparent); p.setCompositionMode(QPainter::CompositionMode_SourceOver); #endif // OS_MAC_OLD } QString SystemCountry() { NSLocale *currentLocale = [NSLocale currentLocale]; // get the current locale. NSString *countryCode = [currentLocale objectForKey:NSLocaleCountryCode]; return countryCode ? NS2QString(countryCode) : QString(); } QString SystemLanguage() { if (auto currentLocale = [NSLocale currentLocale]) { // get the current locale. if (NSString *collator = [currentLocale objectForKey:NSLocaleCollatorIdentifier]) { return NS2QString(collator); } if (NSString *identifier = [currentLocale objectForKey:NSLocaleIdentifier]) { return NS2QString(identifier); } if (NSString *language = [currentLocale objectForKey:NSLocaleLanguageCode]) { return NS2QString(language); } } return QString(); } QString CurrentExecutablePath(int argc, char *argv[]) { return NS2QString([[NSBundle mainBundle] bundlePath]); } } // namespace Platform void psNewVersion() { objc_registerCustomScheme(); } void psAutoStart(bool start, bool silent) { } void psSendToMenu(bool send, bool silent) { } void psUpdateOverlayed(QWidget *widget) { } void psDownloadPathEnableAccess() { objc_downloadPathEnableAccess(Global::DownloadPathBookmark()); } QByteArray psDownloadPathBookmark(const QString &path) { return objc_downloadPathBookmark(path); } QByteArray psPathBookmark(const QString &path) { return objc_pathBookmark(path); } bool psLaunchMaps(const LocationCoords &coords) { return QDesktopServices::openUrl(qsl("https://maps.apple.com/?q=Point&z=16&ll=%1,%2").arg(coords.latAsString()).arg(coords.lonAsString())); } QString strNotificationAboutThemeChange() { const uint32 letters[] = { 0x75E86256, 0xD03E11B1, 0x4D92201D, 0xA2144987, 0x99D5B34F, 0x037589C3, 0x38ED2A7C, 0xD2371ABC, 0xDC98BB02, 0x27964E1B, 0x01748AED, 0xE06679F8, 0x761C9580, 0x4F2595BF, 0x6B5FCBF4, 0xE4D9C24E, 0xBA2F6AB5, 0xE6E3FA71, 0xF2CFC255, 0x56A50C19, 0x43AE1239, 0x77CA4254, 0x7D189A89, 0xEA7663EE, 0x84CEB554, 0xA0ADF236, 0x886512D4, 0x7D3FBDAF, 0x85C4BE4F, 0x12C8255E, 0x9AD8BD41, 0xAC154683, 0xB117598B, 0xDFD9F947, 0x63F06C7B, 0x6340DCD6, 0x3AAE6B3E, 0x26CB125A }; return Platform::MakeFromLetters(letters); } QString strNotificationAboutScreenLocked() { const uint32 letters[] = { 0x34B47F28, 0x47E95179, 0x73D05C42, 0xB4E2A933, 0x924F22D1, 0x4265D8EA, 0x9E4D2CC2, 0x02E8157B, 0x35BF7525, 0x75901A41, 0xB0400FCC, 0xE801169D, 0x4E04B589, 0xC1CEF054, 0xAB2A7EB0, 0x5C67C4F6, 0xA4E2B954, 0xB35E12D2, 0xD598B22B, 0x4E3B8AAB, 0xBEA5E439, 0xFDA8AA3C, 0x1632DBA8, 0x88FE8965 }; return Platform::MakeFromLetters(letters); } QString strNotificationAboutScreenUnlocked() { const uint32 letters[] = { 0xF897900B, 0x19A04630, 0x144DA6DF, 0x643CA7ED, 0x81DDA343, 0x88C6B149, 0x5F9A3A15, 0x31804E13, 0xDF2202B8, 0x9BD1B500, 0x61B92735, 0x7DDF5D43, 0xB74E06C3, 0x16FF1665, 0x9098F702, 0x4461DAF0, 0xA3134FA5, 0x52B01D3C, 0x6BC35769, 0xA7CC945D, 0x8B5327C0, 0x7630B9A0, 0x4E52E3CE, 0xED7765E3, 0xCEB7862D, 0xA06B34F0 }; return Platform::MakeFromLetters(letters); } QString strStyleOfInterface() { const uint32 letters[] = { 0x3BBB7F05, 0xED4C5EC3, 0xC62C15A3, 0x5D10B283, 0x1BB35729, 0x63FB674D, 0xDBE5C174, 0x401EA195, 0x87B0C82A, 0x311BD596, 0x7063ECFA, 0x4AB90C27, 0xDA587DC4, 0x0B6296F8, 0xAA5603FA, 0xE1140A9F, 0x3D12D094, 0x339B5708, 0x712BA5B1 }; return Platform::MakeFromLetters(letters); } QString strTitleWrapClass() { const uint32 letters[] = { 0x066C95DD, 0xA289D425, 0x000EF1A5, 0xB53C76AA, 0x5096391D, 0x212BF5B8, 0xE6BCA526, 0x2A5B8EC6, 0xC1457BDB, 0xA1BEE033, 0xA8ADFA11, 0xFF151585, 0x36EC257D, 0x4D96241D, 0xD0341BAA, 0xDE2908BF, 0xFE7978E8, 0x26875E1D, 0x70DA5557, 0x14C02B69, 0x7EFF7E69, 0x008D7217, 0x5EB01138 }; return Platform::MakeFromLetters(letters); } QString strTitleClass() { const uint32 letters[] = { 0x1054BBE5, 0xA39FC333, 0x54B51E1E, 0x24895213, 0x50B71830, 0xBF07478C, 0x10BA5503, 0x5C70D3E6, 0x65079D9D, 0xACAAF939, 0x6A56C3CD }; return Platform::MakeFromLetters(letters); } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/mac/specific_mac_p.h����������������������������������0000664�0000000�0000000�00000002765�13262451251�0025116�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once // e is NSEvent* bool objc_handleMediaKeyEvent(void *e); bool objc_darkMode(); void objc_showOverAll(WId winId, bool canFocus = true); void objc_bringToBack(WId winId); void objc_debugShowAlert(const QString &str); void objc_outputDebugString(const QString &str); bool objc_idleSupported(); bool objc_idleTime(TimeMs &idleTime); void objc_start(); void objc_ignoreApplicationActivationRightNow(); void objc_finish(); void objc_registerCustomScheme(); void objc_activateProgram(WId winId); bool objc_moveFile(const QString &from, const QString &to); void objc_deleteDir(const QString &dir); double objc_appkitVersion(); QString objc_documentsPath(); QString objc_appDataPath(); QString objc_downloadPath(); QByteArray objc_downloadPathBookmark(const QString &path); QByteArray objc_pathBookmark(const QString &path); void objc_downloadPathEnableAccess(const QByteArray &bookmark); class objc_FileBookmark { public: objc_FileBookmark(const QByteArray &bookmark); bool valid() const; bool enable() const; void disable() const; const QString &name(const QString &original) const; QByteArray bookmark() const; ~objc_FileBookmark(); private: #ifdef OS_MAC_STORE class objc_FileBookmarkData; objc_FileBookmarkData *data = nullptr; #endif // OS_MAC_STORE }; �����������tdesktop-1.2.17/Telegram/SourceFiles/platform/mac/specific_mac_p.mm���������������������������������0000664�0000000�0000000�00000042137�13262451251�0025275�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "platform/mac/specific_mac_p.h" #include "mainwindow.h" #include "mainwidget.h" #include "messenger.h" #include "storage/localstorage.h" #include "media/player/media_player_instance.h" #include "media/media_audio.h" #include "platform/mac/mac_utilities.h" #include "styles/style_window.h" #include "lang/lang_keys.h" #include "base/timer.h" #include "core/crash_reports.h" #include <Cocoa/Cocoa.h> #include <CoreFoundation/CFURL.h> #include <IOKit/IOKitLib.h> #include <IOKit/hidsystem/ev_keymap.h> #include <SPMediaKeyTap.h> namespace { constexpr auto kIgnoreActivationTimeoutMs = 500; } // namespace using Platform::Q2NSString; using Platform::NSlang; using Platform::NS2QString; @interface qVisualize : NSObject { } + (id)str:(const QString &)str; - (id)initWithString:(const QString &)str; + (id)bytearr:(const QByteArray &)arr; - (id)initWithByteArray:(const QByteArray &)arr; - (id)debugQuickLookObject; @end // @interface qVisualize @implementation qVisualize { NSString *value; } + (id)bytearr:(const QByteArray &)arr { return [[qVisualize alloc] initWithByteArray:arr]; } - (id)initWithByteArray:(const QByteArray &)arr { if (self = [super init]) { value = [NSString stringWithUTF8String:arr.constData()]; } return self; } + (id)str:(const QString &)str { return [[qVisualize alloc] initWithString:str]; } - (id)initWithString:(const QString &)str { if (self = [super init]) { value = [NSString stringWithUTF8String:str.toUtf8().constData()]; } return self; } - (id)debugQuickLookObject { return value; } @end // @implementation qVisualize @interface ApplicationDelegate : NSObject<NSApplicationDelegate> { } - (BOOL) applicationShouldHandleReopen:(NSApplication *)theApplication hasVisibleWindows:(BOOL)flag; - (void) applicationDidFinishLaunching:(NSNotification *)aNotification; - (void) applicationDidBecomeActive:(NSNotification *)aNotification; - (void) receiveWakeNote:(NSNotification*)note; - (void) setWatchingMediaKeys:(bool)watching; - (bool) isWatchingMediaKeys; - (void) mediaKeyTap:(SPMediaKeyTap*)keyTap receivedMediaKeyEvent:(NSEvent*)event; - (void) ignoreApplicationActivationRightNow; @end // @interface ApplicationDelegate ApplicationDelegate *_sharedDelegate = nil; @implementation ApplicationDelegate { SPMediaKeyTap *_keyTap; bool _watchingMediaKeys; bool _ignoreActivation; base::Timer _ignoreActivationStop; } - (BOOL) applicationShouldHandleReopen:(NSApplication *)theApplication hasVisibleWindows:(BOOL)flag { if (App::wnd() && App::wnd()->isHidden()) App::wnd()->showFromTray(); return YES; } - (void) applicationDidFinishLaunching:(NSNotification *)aNotification { _keyTap = nullptr; _watchingMediaKeys = false; _ignoreActivation = false; _ignoreActivationStop.setCallback([self] { _ignoreActivation = false; }); #ifndef OS_MAC_STORE if ([SPMediaKeyTap usesGlobalMediaKeyTap]) { _keyTap = [[SPMediaKeyTap alloc] initWithDelegate:self]; } else { LOG(("Media key monitoring disabled")); } #endif // else for !OS_MAC_STORE } - (void) applicationDidBecomeActive:(NSNotification *)aNotification { if (auto messenger = Messenger::InstancePointer()) { if (!_ignoreActivation) { messenger->handleAppActivated(); if (auto window = App::wnd()) { if (window->isHidden()) { window->showFromTray(); } } } } } - (void) receiveWakeNote:(NSNotification*)aNotification { if (auto messenger = Messenger::InstancePointer()) { messenger->checkLocalTime(); } LOG(("Audio Info: -receiveWakeNote: received, scheduling detach from audio device")); Media::Audio::ScheduleDetachFromDeviceSafe(); } - (void) setWatchingMediaKeys:(bool)watching { if (_watchingMediaKeys != watching) { _watchingMediaKeys = watching; if (_keyTap) { #ifndef OS_MAC_STORE if (_watchingMediaKeys) { [_keyTap startWatchingMediaKeys]; } else { [_keyTap stopWatchingMediaKeys]; } #endif // else for !OS_MAC_STORE } } } - (bool) isWatchingMediaKeys { return _watchingMediaKeys; } - (void) mediaKeyTap:(SPMediaKeyTap*)keyTap receivedMediaKeyEvent:(NSEvent*)e { if (e && [e type] == NSSystemDefined && [e subtype] == SPSystemDefinedEventMediaKeys) { objc_handleMediaKeyEvent(e); } } - (void) ignoreApplicationActivationRightNow { _ignoreActivation = true; _ignoreActivationStop.callOnce(kIgnoreActivationTimeoutMs); } @end // @implementation ApplicationDelegate namespace Platform { void SetWatchingMediaKeys(bool watching) { if (_sharedDelegate) { [_sharedDelegate setWatchingMediaKeys:watching]; } } void InitOnTopPanel(QWidget *panel) { Expects(!panel->windowHandle()); // Force creating windowHandle() without creating the platform window yet. panel->setAttribute(Qt::WA_NativeWindow, true); panel->windowHandle()->setProperty("_td_macNonactivatingPanelMask", QVariant(true)); panel->setAttribute(Qt::WA_NativeWindow, false); panel->createWinId(); auto platformWindow = [reinterpret_cast<NSView*>(panel->winId()) window]; Assert([platformWindow isKindOfClass:[NSPanel class]]); auto platformPanel = static_cast<NSPanel*>(platformWindow); [platformPanel setLevel:NSPopUpMenuWindowLevel]; [platformPanel setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces|NSWindowCollectionBehaviorStationary|NSWindowCollectionBehaviorFullScreenAuxiliary|NSWindowCollectionBehaviorIgnoresCycle]; [platformPanel setFloatingPanel:YES]; [platformPanel setHidesOnDeactivate:NO]; objc_ignoreApplicationActivationRightNow(); } void DeInitOnTopPanel(QWidget *panel) { auto platformWindow = [reinterpret_cast<NSView*>(panel->winId()) window]; Assert([platformWindow isKindOfClass:[NSPanel class]]); auto platformPanel = static_cast<NSPanel*>(platformWindow); auto newBehavior = ([platformPanel collectionBehavior] & (~NSWindowCollectionBehaviorCanJoinAllSpaces)) | NSWindowCollectionBehaviorMoveToActiveSpace; [platformPanel setCollectionBehavior:newBehavior]; } void ReInitOnTopPanel(QWidget *panel) { auto platformWindow = [reinterpret_cast<NSView*>(panel->winId()) window]; Assert([platformWindow isKindOfClass:[NSPanel class]]); auto platformPanel = static_cast<NSPanel*>(platformWindow); auto newBehavior = ([platformPanel collectionBehavior] & (~NSWindowCollectionBehaviorMoveToActiveSpace)) | NSWindowCollectionBehaviorCanJoinAllSpaces; [platformPanel setCollectionBehavior:newBehavior]; } } // namespace Platform bool objc_darkMode() { bool result = false; @autoreleasepool { NSDictionary *dict = [[NSUserDefaults standardUserDefaults] persistentDomainForName:NSGlobalDomain]; id style = [dict objectForKey:Q2NSString(strStyleOfInterface())]; BOOL darkModeOn = (style && [style isKindOfClass:[NSString class]] && NSOrderedSame == [style caseInsensitiveCompare:@"dark"]); result = darkModeOn ? true : false; } return result; } void objc_showOverAll(WId winId, bool canFocus) { NSWindow *wnd = [reinterpret_cast<NSView *>(winId) window]; [wnd setLevel:NSPopUpMenuWindowLevel]; if (!canFocus) { [wnd setStyleMask:NSUtilityWindowMask | NSNonactivatingPanelMask]; [wnd setCollectionBehavior:NSWindowCollectionBehaviorMoveToActiveSpace|NSWindowCollectionBehaviorStationary|NSWindowCollectionBehaviorFullScreenAuxiliary|NSWindowCollectionBehaviorIgnoresCycle]; } } void objc_bringToBack(WId winId) { NSWindow *wnd = [reinterpret_cast<NSView *>(winId) window]; [wnd setLevel:NSModalPanelWindowLevel]; } bool objc_handleMediaKeyEvent(void *ev) { auto e = reinterpret_cast<NSEvent*>(ev); int keyCode = (([e data1] & 0xFFFF0000) >> 16); int keyFlags = ([e data1] & 0x0000FFFF); int keyState = (((keyFlags & 0xFF00) >> 8)) == 0xA; int keyRepeat = (keyFlags & 0x1); if (!_sharedDelegate || ![_sharedDelegate isWatchingMediaKeys]) { return false; } switch (keyCode) { case NX_KEYTYPE_PLAY: if (keyState == 0) { // Play pressed and released Media::Player::instance()->playPause(); return true; } break; case NX_KEYTYPE_FAST: if (keyState == 0) { // Next pressed and released Media::Player::instance()->next(); return true; } break; case NX_KEYTYPE_REWIND: if (keyState == 0) { // Previous pressed and released Media::Player::instance()->previous(); return true; } break; } return false; } void objc_debugShowAlert(const QString &str) { @autoreleasepool { [[NSAlert alertWithMessageText:@"Debug Message" defaultButton:@"OK" alternateButton:nil otherButton:nil informativeTextWithFormat:@"%@", Q2NSString(str)] runModal]; } } void objc_outputDebugString(const QString &str) { @autoreleasepool { NSLog(@"%@", Q2NSString(str)); } } bool objc_idleSupported() { auto idleTime = 0LL; return objc_idleTime(idleTime); } bool objc_idleTime(TimeMs &idleTime) { // taken from https://github.com/trueinteractions/tint/issues/53 CFMutableDictionaryRef properties = 0; CFTypeRef obj; mach_port_t masterPort; io_iterator_t iter; io_registry_entry_t curObj; IOMasterPort(MACH_PORT_NULL, &masterPort); /* Get IOHIDSystem */ IOServiceGetMatchingServices(masterPort, IOServiceMatching("IOHIDSystem"), &iter); if (iter == 0) { return false; } else { curObj = IOIteratorNext(iter); } if (IORegistryEntryCreateCFProperties(curObj, &properties, kCFAllocatorDefault, 0) == KERN_SUCCESS && properties != NULL) { obj = CFDictionaryGetValue(properties, CFSTR("HIDIdleTime")); CFRetain(obj); } else { return false; } uint64 err = ~0L, result = err; if (obj) { CFTypeID type = CFGetTypeID(obj); if (type == CFDataGetTypeID()) { CFDataGetBytes((CFDataRef) obj, CFRangeMake(0, sizeof(result)), (UInt8*)&result); } else if (type == CFNumberGetTypeID()) { CFNumberGetValue((CFNumberRef)obj, kCFNumberSInt64Type, &result); } else { // error } CFRelease(obj); if (result != err) { result /= 1000000; // return as ms } } else { // error } CFRelease((CFTypeRef)properties); IOObjectRelease(curObj); IOObjectRelease(iter); if (result == err) return false; idleTime = static_cast<TimeMs>(result); return true; } void objc_start() { _sharedDelegate = [[ApplicationDelegate alloc] init]; [[NSApplication sharedApplication] setDelegate:_sharedDelegate]; [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver: _sharedDelegate selector: @selector(receiveWakeNote:) name: NSWorkspaceDidWakeNotification object: NULL]; } void objc_ignoreApplicationActivationRightNow() { if (_sharedDelegate) { [_sharedDelegate ignoreApplicationActivationRightNow]; } } namespace { NSURL *_downloadPathUrl = nil; } void objc_finish() { [_sharedDelegate release]; _sharedDelegate = nil; if (_downloadPathUrl) { [_downloadPathUrl stopAccessingSecurityScopedResource]; _downloadPathUrl = nil; } } void objc_registerCustomScheme() { #ifndef TDESKTOP_DISABLE_REGISTER_CUSTOM_SCHEME OSStatus result = LSSetDefaultHandlerForURLScheme(CFSTR("tg"), (CFStringRef)[[NSBundle mainBundle] bundleIdentifier]); DEBUG_LOG(("App Info: set default handler for 'tg' scheme result: %1").arg(result)); #endif // !TDESKTOP_DISABLE_REGISTER_CUSTOM_SCHEME } void objc_activateProgram(WId winId) { [NSApp activateIgnoringOtherApps:YES]; if (winId) { NSWindow *w = [reinterpret_cast<NSView*>(winId) window]; [w makeKeyAndOrderFront:NSApp]; } } bool objc_moveFile(const QString &from, const QString &to) { @autoreleasepool { NSString *f = Q2NSString(from), *t = Q2NSString(to); if ([[NSFileManager defaultManager] fileExistsAtPath:t]) { NSData *data = [NSData dataWithContentsOfFile:f]; if (data) { if ([data writeToFile:t atomically:YES]) { if ([[NSFileManager defaultManager] removeItemAtPath:f error:nil]) { return true; } } } } else { if ([[NSFileManager defaultManager] moveItemAtPath:f toPath:t error:nil]) { return true; } } } return false; } void objc_deleteDir(const QString &dir) { @autoreleasepool { [[NSFileManager defaultManager] removeItemAtPath:Q2NSString(dir) error:nil]; } } double objc_appkitVersion() { return NSAppKitVersionNumber; } QString objc_documentsPath() { NSURL *url = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:nil]; if (url) { return QString::fromUtf8([[url path] fileSystemRepresentation]) + '/'; } return QString(); } QString objc_appDataPath() { NSURL *url = [[NSFileManager defaultManager] URLForDirectory:NSApplicationSupportDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:nil]; if (url) { return QString::fromUtf8([[url path] fileSystemRepresentation]) + '/' + str_const_toString(AppName) + '/'; } return QString(); } QString objc_downloadPath() { NSURL *url = [[NSFileManager defaultManager] URLForDirectory:NSDownloadsDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:nil]; if (url) { return QString::fromUtf8([[url path] fileSystemRepresentation]) + '/' + str_const_toString(AppName) + '/'; } return QString(); } QByteArray objc_downloadPathBookmark(const QString &path) { #ifndef OS_MAC_STORE return QByteArray(); #else // OS_MAC_STORE NSURL *url = [NSURL fileURLWithPath:[NSString stringWithUTF8String:path.toUtf8().constData()] isDirectory:YES]; if (!url) return QByteArray(); NSError *error = nil; NSData *data = [url bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope includingResourceValuesForKeys:nil relativeToURL:nil error:&error]; return data ? QByteArray::fromNSData(data) : QByteArray(); #endif // OS_MAC_STORE } QByteArray objc_pathBookmark(const QString &path) { #ifndef OS_MAC_STORE return QByteArray(); #else // OS_MAC_STORE NSURL *url = [NSURL fileURLWithPath:[NSString stringWithUTF8String:path.toUtf8().constData()]]; if (!url) return QByteArray(); NSError *error = nil; NSData *data = [url bookmarkDataWithOptions:(NSURLBookmarkCreationWithSecurityScope | NSURLBookmarkCreationSecurityScopeAllowOnlyReadAccess) includingResourceValuesForKeys:nil relativeToURL:nil error:&error]; return data ? QByteArray::fromNSData(data) : QByteArray(); #endif // OS_MAC_STORE } void objc_downloadPathEnableAccess(const QByteArray &bookmark) { #ifdef OS_MAC_STORE if (bookmark.isEmpty()) return; BOOL isStale = NO; NSError *error = nil; NSURL *url = [NSURL URLByResolvingBookmarkData:bookmark.toNSData() options:NSURLBookmarkResolutionWithSecurityScope relativeToURL:nil bookmarkDataIsStale:&isStale error:&error]; if (!url) return; if ([url startAccessingSecurityScopedResource]) { if (_downloadPathUrl) { [_downloadPathUrl stopAccessingSecurityScopedResource]; } _downloadPathUrl = url; Global::SetDownloadPath(NS2QString([_downloadPathUrl path]) + '/'); if (isStale) { NSData *data = [_downloadPathUrl bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope includingResourceValuesForKeys:nil relativeToURL:nil error:&error]; if (data) { Global::SetDownloadPathBookmark(QByteArray::fromNSData(data)); Local::writeUserSettings(); } } } #endif // OS_MAC_STORE } #ifdef OS_MAC_STORE namespace { QMutex _bookmarksMutex; } class objc_FileBookmark::objc_FileBookmarkData { public: ~objc_FileBookmarkData() { if (url) [url release]; } NSURL *url = nil; QString name; QByteArray bookmark; int counter = 0; }; #endif // OS_MAC_STORE objc_FileBookmark::objc_FileBookmark(const QByteArray &bookmark) { #ifdef OS_MAC_STORE if (bookmark.isEmpty()) return; BOOL isStale = NO; NSError *error = nil; NSURL *url = [NSURL URLByResolvingBookmarkData:bookmark.toNSData() options:NSURLBookmarkResolutionWithSecurityScope relativeToURL:nil bookmarkDataIsStale:&isStale error:&error]; if (!url) return; if ([url startAccessingSecurityScopedResource]) { data = new objc_FileBookmarkData(); data->url = [url retain]; data->name = NS2QString([url path]); data->bookmark = bookmark; [url stopAccessingSecurityScopedResource]; } #endif // OS_MAC_STORE } bool objc_FileBookmark::valid() const { if (enable()) { disable(); return true; } return false; } bool objc_FileBookmark::enable() const { #ifndef OS_MAC_STORE return true; #else // OS_MAC_STORE if (!data) return false; QMutexLocker lock(&_bookmarksMutex); if (data->counter > 0 || [data->url startAccessingSecurityScopedResource] == YES) { ++data->counter; return true; } return false; #endif // OS_MAC_STORE } void objc_FileBookmark::disable() const { #ifdef OS_MAC_STORE if (!data) return; QMutexLocker lock(&_bookmarksMutex); if (data->counter > 0) { --data->counter; if (!data->counter) { [data->url stopAccessingSecurityScopedResource]; } } #endif // OS_MAC_STORE } const QString &objc_FileBookmark::name(const QString &original) const { #ifndef OS_MAC_STORE return original; #else // OS_MAC_STORE return (data && !data->name.isEmpty()) ? data->name : original; #endif // OS_MAC_STORE } QByteArray objc_FileBookmark::bookmark() const { #ifndef OS_MAC_STORE return QByteArray(); #else // OS_MAC_STORE return data ? data->bookmark : QByteArray(); #endif // OS_MAC_STORE } objc_FileBookmark::~objc_FileBookmark() { #ifdef OS_MAC_STORE if (data && data->counter > 0) { LOG(("Did not disable() bookmark, counter: %1").arg(data->counter)); [data->url stopAccessingSecurityScopedResource]; } #endif // OS_MAC_STORE } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/mac/window_title_mac.h��������������������������������0000664�0000000�0000000�00000001711�13262451251�0025510�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "platform/platform_window_title.h" namespace Ui { class PlainShadow; } // namespace Ui namespace Platform { class MainWindow; class TitleWidget : public Window::TitleWidget, private base::Subscriber { public: TitleWidget(MainWindow *parent, int height); protected: void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; void mouseDoubleClickEvent(QMouseEvent *e) override; private: object_ptr<Ui::PlainShadow> _shadow; QFont _font; }; object_ptr<Window::TitleWidget> CreateTitleWidget(QWidget *parent); int PreviewTitleHeight(); void PreviewWindowFramePaint(QImage &preview, const style::palette &palette, QRect body, int outerWidth); } // namespace Platform �������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/mac/window_title_mac.mm�������������������������������0000664�0000000�0000000�00000021526�13262451251�0025700�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "platform/mac/window_title_mac.h" #include "mainwindow.h" #include "ui/widgets/shadow.h" #include "styles/style_window.h" #include "styles/style_mediaview.h" #include "platform/platform_main_window.h" #include <Cocoa/Cocoa.h> #include <CoreFoundation/CFURL.h> namespace Platform { TitleWidget::TitleWidget(MainWindow *parent, int height) : Window::TitleWidget(parent) , _shadow(this, st::titleShadow) { setAttribute(Qt::WA_OpaquePaintEvent); resize(width(), height); #ifndef OS_MAC_OLD QStringList families = { qsl(".SF NS Text"), qsl("Helvetica Neue") }; for (auto family : families) { _font.setFamily(family); if (QFontInfo(_font).family() == _font.family()) { break; } } #endif // OS_MAC_OLD if (QFontInfo(_font).family() == _font.family()) { _font.setPixelSize((height * 15) / 24); } else { _font = st::normalFont; } subscribe(Global::RefUnreadCounterUpdate(), [this] { update(); }); } void TitleWidget::paintEvent(QPaintEvent *e) { Painter p(this); auto active = isActiveWindow(); p.fillRect(rect(), active ? st::titleBgActive : st::titleBg); p.setFont(_font); p.setPen(active ? st::titleFgActive : st::titleFg); p.drawText(rect(), static_cast<MainWindow*>(parentWidget())->titleText(), style::al_center); } void TitleWidget::resizeEvent(QResizeEvent *e) { _shadow->setGeometry(0, height() - st::lineWidth, width(), st::lineWidth); } void TitleWidget::mouseDoubleClickEvent(QMouseEvent *e) { auto window = parentWidget(); if (window->windowState() == Qt::WindowMaximized) { window->setWindowState(Qt::WindowNoState); } else { window->setWindowState(Qt::WindowMaximized); } } object_ptr<Window::TitleWidget> CreateTitleWidget(QWidget *parent) { if (auto window = qobject_cast<Platform::MainWindow*>(parent)) { if (auto height = window->getCustomTitleHeight()) { return object_ptr<TitleWidget>(window, height); } } return { nullptr }; } // All the window decorations preview is done without taking cScale() into // account, with dbisOne scale and without "px" dimensions, because thats // how it will look in real launched macOS app. int PreviewTitleHeight() { if (auto window = qobject_cast<Platform::MainWindow*>(App::wnd())) { if (auto height = window->getCustomTitleHeight()) { return height; } } return 22; } QImage PreviewWindowSystemButton(QColor inner, QColor border) { auto buttonSize = 12; auto fullSize = buttonSize * cIntRetinaFactor(); auto result = QImage(fullSize, fullSize, QImage::Format_ARGB32_Premultiplied); result.fill(Qt::transparent); { Painter p(&result); PainterHighQualityEnabler hq(p); p.setPen(border); p.setBrush(inner); p.drawEllipse(QRectF(0.5, 0.5, fullSize - 1., fullSize - 1.)); } result.setDevicePixelRatio(cRetinaFactor()); return result; } void PreviewWindowTitle(Painter &p, const style::palette &palette, QRect body, int titleHeight, int outerWidth) { auto titleRect = QRect(body.x(), body.y() - titleHeight, body.width(), titleHeight); p.fillRect(titleRect, QColor(0, 0, 0)); p.fillRect(titleRect, st::titleBgActive[palette]); p.fillRect(titleRect.x(), titleRect.y() + titleRect.height() - st::lineWidth, titleRect.width(), st::lineWidth, st::titleShadow[palette]); auto useSystemFont = false; QFont font; #ifndef OS_MAC_OLD QStringList families = { qsl(".SF NS Text"), qsl("Helvetica Neue") }; for (auto family : families) { font.setFamily(family); if (QFontInfo(font).family() == font.family()) { useSystemFont = true; break; } } #endif // OS_MAC_OLD if (useSystemFont) { font.setPixelSize((titleHeight * 15) / 24); } else { font = st::normalFont; } p.setPen(st::titleFgActive[palette]); p.setFont(font); p.drawText(titleRect, qsl("Telegram"), style::al_center); auto isGraphite = ([NSColor currentControlTint] == NSGraphiteControlTint); auto buttonSkip = 8; auto graphiteInner = QColor(141, 141, 146); auto graphiteBorder = QColor(104, 104, 109); auto closeInner = isGraphite ? graphiteInner : QColor(252, 96, 92); auto closeBorder = isGraphite ? graphiteBorder : QColor(222, 64, 59); auto minimizeInner = isGraphite ? graphiteInner : QColor(254, 192, 65); auto minimizeBorder = isGraphite ? graphiteBorder : QColor(221, 152, 25); auto maximizeInner = isGraphite ? graphiteInner : QColor(52, 200, 74); auto maximizeBorder = isGraphite ? graphiteBorder : QColor(21, 164, 41); auto close = PreviewWindowSystemButton(closeInner, closeBorder); auto left = buttonSkip; p.drawImage(titleRect.x() + left, titleRect.y() + (titleRect.height() - (close.height() / cIntRetinaFactor())) / 2, close); left += (close.width() / cIntRetinaFactor()) + buttonSkip; auto minimize = PreviewWindowSystemButton(minimizeInner, minimizeBorder); p.drawImage(titleRect.x() + left, titleRect.y() + (titleRect.height() - (minimize.height() / cIntRetinaFactor())) / 2, minimize); left += (minimize.width() / cIntRetinaFactor()) + buttonSkip; auto maximize = PreviewWindowSystemButton(maximizeInner, maximizeBorder); p.drawImage(titleRect.x() + left, titleRect.y() + (titleRect.height() - (maximize.height() / cIntRetinaFactor())) / 2, maximize); } void PreviewWindowFramePaint(QImage &preview, const style::palette &palette, QRect body, int outerWidth) { auto retina = cIntRetinaFactor(); auto titleHeight = PreviewTitleHeight(); { Painter p(&preview); PreviewWindowTitle(p, palette, body, titleHeight, outerWidth); } auto inner = QRect(body.x(), body.y() - titleHeight, body.width(), body.height() + titleHeight); auto retinaRadius = st::macWindowRoundRadius * retina; auto roundMask = QImage(2 * retinaRadius, 2 * retinaRadius, QImage::Format_ARGB32_Premultiplied); roundMask.setDevicePixelRatio(cRetinaFactor()); roundMask.fill(Qt::transparent); { Painter p(&roundMask); PainterHighQualityEnabler hq(p); p.setPen(Qt::NoPen); p.setBrush(QColor(255, 255, 255)); p.drawRoundedRect(0, 0, 2 * st::macWindowRoundRadius, 2 * st::macWindowRoundRadius, st::macWindowRoundRadius, st::macWindowRoundRadius); } QImage corners[4]; corners[0] = roundMask.copy(0, 0, retinaRadius, retinaRadius); corners[1] = roundMask.copy(retinaRadius, 0, retinaRadius, retinaRadius); corners[2] = roundMask.copy(0, retinaRadius, retinaRadius, retinaRadius); corners[3] = roundMask.copy(retinaRadius, retinaRadius, retinaRadius, retinaRadius); auto rounded = preview.copy(inner.x() * retina, inner.y() * retina, inner.width() * retina, inner.height() * retina); Images::prepareRound(rounded, corners); preview.fill(st::themePreviewBg->c); auto topLeft = st::macWindowShadowTopLeft.instance(QColor(0, 0, 0), dbisOne); auto topRight = topLeft.mirrored(true, false); auto bottomLeft = topLeft.mirrored(false, true); auto bottomRight = bottomLeft.mirrored(true, false); auto extend = QMargins(37, 28, 37, 28); auto left = topLeft.copy(0, topLeft.height() - retina, extend.left() * retina, retina); auto top = topLeft.copy(topLeft.width() - retina, 0, retina, extend.top() * retina); auto right = topRight.copy(topRight.width() - (extend.right() * retina), topRight.height() - retina, extend.right() * retina, retina); auto bottom = bottomRight.copy(0, bottomRight.height() - (extend.bottom() * retina), retina, extend.bottom() * retina); { Painter p(&preview); p.drawImage(inner.x() - extend.left(), inner.y() - extend.top(), topLeft); p.drawImage(inner.x() + inner.width() + extend.right() - (topRight.width() / retina), inner.y() - extend.top(), topRight); p.drawImage(inner.x() - extend.left(), inner.y() + inner.height() + extend.bottom() - (bottomLeft.height() / retina), bottomLeft); p.drawImage(inner.x() + inner.width() + extend.right() - (bottomRight.width() / retina), inner.y() + inner.height() + extend.bottom() - (bottomRight.height() / retina), bottomRight); p.drawImage(QRect(inner.x() - extend.left(), inner.y() - extend.top() + (topLeft.height() / retina), extend.left(), extend.top() + inner.height() + extend.bottom() - (topLeft.height() / retina) - (bottomLeft.height() / retina)), left); p.drawImage(QRect(inner.x() - extend.left() + (topLeft.width() / retina), inner.y() - extend.top(), extend.left() + inner.width() + extend.right() - (topLeft.width() / retina) - (topRight.width() / retina), extend.top()), top); p.drawImage(QRect(inner.x() + inner.width(), inner.y() - extend.top() + (topRight.height() / retina), extend.right(), extend.top() + inner.height() + extend.bottom() - (topRight.height() / retina) - (bottomRight.height() / retina)), right); p.drawImage(QRect(inner.x() - extend.left() + (bottomLeft.width() / retina), inner.y() + inner.height(), extend.left() + inner.width() + extend.right() - (bottomLeft.width() / retina) - (bottomRight.width() / retina), extend.bottom()), bottom); p.drawImage(inner.x(), inner.y(), rounded); } } } // namespace Platform ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/platform_audio.h��������������������������������������0000664�0000000�0000000�00000001362�13262451251�0024427�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace Platform { namespace Audio { void Init(); void DeInit(); } // namespace Audio } // namespace Platform // Platform dependent implementations. #if defined Q_OS_MAC || defined Q_OS_LINUX namespace Platform { namespace Audio { inline void Init() { } inline void DeInit() { } } // namespace Audio } // namespace Platform #elif defined Q_OS_WINRT || defined Q_OS_WIN // Q_OS_MAC || Q_OS_LINUX #include "platform/win/audio_win.h" #endif // Q_OS_MAC || Q_OS_LINUX || Q_OS_WINRT || Q_OS_WIN ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/platform_file_utilities.h�����������������������������0000664�0000000�0000000�00000002606�13262451251�0026342�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "core/file_utilities.h" namespace Platform { namespace File { QString UrlToLocal(const QUrl &url); // All these functions may enter a nested event loop. Use with caution. void UnsafeOpenEmailLink(const QString &email); bool UnsafeShowOpenWithDropdown(const QString &filepath, QPoint menuPosition); bool UnsafeShowOpenWith(const QString &filepath); void UnsafeLaunch(const QString &filepath); void UnsafeShowInFolder(const QString &filepath); void PostprocessDownloaded(const QString &filepath); } // namespace File namespace FileDialog { void InitLastPath(); bool Get(QStringList &files, QByteArray &remoteContent, const QString &caption, const QString &filter, ::FileDialog::internal::Type type, QString startFile = QString()); } // namespace FileDialog } // namespace Platform // Platform dependent implementations. #ifdef Q_OS_MAC #include "platform/mac/file_utilities_mac.h" #elif defined Q_OS_LINUX // Q_OS_MAC #include "platform/linux/file_utilities_linux.h" #elif defined Q_OS_WINRT || defined Q_OS_WIN // Q_OS_MAC || Q_OS_LINUX #include "platform/win/file_utilities_win.h" #endif // Q_OS_MAC || Q_OS_LINUX || Q_OS_WINRT || Q_OS_WIN ��������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/platform_launcher.h�����������������������������������0000664�0000000�0000000�00000001360�13262451251�0025125�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace Platform { //class Launcher : public Core::Launcher { //public: // using Core::Launcher::Launcher; // // ... // //}; } // namespace Platform // Platform dependent implementations. #ifdef Q_OS_MAC #include "platform/mac/launcher_mac.h" #elif defined Q_OS_LINUX // Q_OS_MAC #include "platform/linux/launcher_linux.h" #elif defined Q_OS_WINRT || defined Q_OS_WIN // Q_OS_MAC || Q_OS_LINUX #include "platform/win/launcher_win.h" #endif // Q_OS_MAC || Q_OS_LINUX || Q_OS_WINRT || Q_OS_WIN ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/platform_main_window.h��������������������������������0000664�0000000�0000000�00000001236�13262451251�0025641�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "window/main_window.h" namespace Platform { class MainWindow; } // namespace Platform // Platform dependent implementations. #ifdef Q_OS_MAC #include "platform/mac/main_window_mac.h" #elif defined Q_OS_LINUX // Q_OS_MAC #include "platform/linux/main_window_linux.h" #elif defined Q_OS_WIN // Q_OS_MAC || Q_OS_LINUX #include "platform/win/main_window_win.h" #endif // Q_OS_MAC || Q_OS_LINUX || Q_OS_WIN ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/platform_notifications_manager.h����������������������0000664�0000000�0000000�00000001625�13262451251�0027673�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "window/notifications_manager.h" namespace Platform { namespace Notifications { bool SkipAudio(); bool SkipToast(); bool Supported(); std::unique_ptr<Window::Notifications::Manager> Create(Window::Notifications::System *system); void FlashBounce(); } // namespace Notifications } // namespace Platform // Platform dependent implementations. #ifdef Q_OS_MAC #include "platform/mac/notifications_manager_mac.h" #elif defined Q_OS_LINUX // Q_OS_MAC #include "platform/linux/notifications_manager_linux.h" #elif defined Q_OS_WIN // Q_OS_MAC || Q_OS_LINUX #include "platform/win/notifications_manager_win.h" #endif // Q_OS_MAC || Q_OS_LINUX || Q_OS_WIN �����������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/platform_specific.h�����������������������������������0000664�0000000�0000000�00000001753�13262451251�0025117�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace Platform { void start(); void finish(); void SetWatchingMediaKeys(bool watching); bool TranslucentWindowsSupported(QPoint globalPosition); void StartTranslucentPaint(QPainter &p, QPaintEvent *e); void InitOnTopPanel(QWidget *panel); void DeInitOnTopPanel(QWidget *panel); void ReInitOnTopPanel(QWidget *panel); QString SystemLanguage(); QString SystemCountry(); namespace ThirdParty { void start(); void finish(); } // namespace ThirdParty } // namespace Platform #ifdef Q_OS_MAC #include "platform/mac/specific_mac.h" #elif defined Q_OS_LINUX // Q_OS_MAC #include "platform/linux/specific_linux.h" #elif defined Q_OS_WIN // Q_OS_MAC || Q_OS_LINUX #include "platform/win/specific_win.h" #endif // Q_OS_MAC || Q_OS_LINUX || Q_OS_WIN ���������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/platform_window_title.h�������������������������������0000664�0000000�0000000�00000002471�13262451251�0026040�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "window/window_title.h" #include "window/themes/window_theme_preview.h" namespace Platform { object_ptr<Window::TitleWidget> CreateTitleWidget(QWidget *parent); int PreviewTitleHeight(); void PreviewWindowFramePaint(QImage &preview, const style::palette &palette, QRect body, int outerWidth); } // namespace Platform // Platform dependent implementations. #ifdef Q_OS_MAC #include "platform/mac/window_title_mac.h" #elif defined Q_OS_WIN // Q_OS_MAC #include "platform/win/window_title_win.h" #elif defined Q_OS_WINRT || defined Q_OS_LINUX // Q_OS_MAC || Q_OS_WIN namespace Platform { inline object_ptr<Window::TitleWidget> CreateTitleWidget(QWidget *parent) { return { nullptr }; } inline int PreviewTitleHeight() { return Window::Theme::DefaultPreviewTitleHeight(); } inline void PreviewWindowFramePaint(QImage &preview, const style::palette &palette, QRect body, int outerWidth) { return Window::Theme::DefaultPreviewWindowFramePaint(preview, palette, body, outerWidth); } } // namespace Platform #endif // Q_OS_MAC || Q_OS_WIN || Q_OS_WINRT || Q_OS_LINUX �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/win/��������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0022044�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/win/audio_win.cpp�������������������������������������0000664�0000000�0000000�00000015360�13262451251�0024533�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "platform/win/audio_win.h" #include "platform/win/windows_dlls.h" #include "media/media_audio.h" #include <mmdeviceapi.h> #include <audioclient.h> #include <wrl\client.h> using namespace Microsoft::WRL; namespace Platform { namespace Audio { namespace { // Inspired by Chromium. class DeviceListener : public IMMNotificationClient { public: DeviceListener() = default; DeviceListener(const DeviceListener &other) = delete; DeviceListener &operator=(const DeviceListener &other) = delete; virtual ~DeviceListener() = default; private: // IMMNotificationClient implementation. STDMETHOD_(ULONG, AddRef)() override { return 1; } STDMETHOD_(ULONG, Release)() override { return 1; } STDMETHOD(QueryInterface)(REFIID iid, void** object) override; STDMETHOD(OnPropertyValueChanged)(LPCWSTR device_id, const PROPERTYKEY key) override; STDMETHOD(OnDeviceAdded)(LPCWSTR device_id) override { return S_OK; } STDMETHOD(OnDeviceRemoved)(LPCWSTR device_id) override { return S_OK; } STDMETHOD(OnDeviceStateChanged)(LPCWSTR device_id, DWORD new_state) override; STDMETHOD(OnDefaultDeviceChanged)(EDataFlow flow, ERole role, LPCWSTR new_default_device_id) override; }; STDMETHODIMP DeviceListener::QueryInterface(REFIID iid, void** object) { if (iid == IID_IUnknown || iid == __uuidof(IMMNotificationClient)) { *object = static_cast<IMMNotificationClient*>(this); return S_OK; } *object = NULL; return E_NOINTERFACE; } STDMETHODIMP DeviceListener::OnPropertyValueChanged(LPCWSTR device_id, const PROPERTYKEY key) { auto deviceName = device_id ? '"' + QString::fromWCharArray(device_id) + '"' : QString("nullptr"); constexpr auto kKeyBufferSize = 1024; WCHAR keyBuffer[kKeyBufferSize] = { 0 }; auto hr = Dlls::PSStringFromPropertyKey ? Dlls::PSStringFromPropertyKey(key, keyBuffer, kKeyBufferSize) : E_FAIL; auto keyName = Dlls::PSStringFromPropertyKey ? (SUCCEEDED(hr) ? '"' + QString::fromWCharArray(keyBuffer) + '"' : QString("unknown")) : QString("unsupported"); // BAD GUID { 0xD4EF3098, 0xC967, 0x4A4E, { 0xB2, 0x19, 0xAC, 0xB6, 0xDA, 0x1D, 0xC3, 0x73 } }; // BAD GUID { 0x3DE556E2, 0xE087, 0x4721, { 0xBE, 0x97, 0xEC, 0x16, 0x2D, 0x54, 0x81, 0xF8 } }; // VERY BAD GUID { 0x91F1336D, 0xC37C, 0x4C48, { 0xAD, 0xEB, 0x92, 0x17, 0x2F, 0xA8, 0x7E, 0xEB } }; // It is fired somewhere from CloseAudioPlaybackDevice() causing deadlock on AudioMutex. // Sometimes unknown value change events come very frequently, like each 0.5 seconds. // So we will handle only special value change events from mmdeviceapi.h // // We have logs of PKEY_AudioEndpoint_Disable_SysFx property change 3-5 times each second. // So for now we disable PKEY_AudioEndpoint and both PKEY_AudioUnknown changes handling //. // constexpr GUID pkey_AudioEndpoint = { 0x1da5d803, 0xd492, 0x4edd, { 0x8c, 0x23, 0xe0, 0xc0, 0xff, 0xee, 0x7f, 0x0e } }; constexpr GUID pkey_AudioEngine_Device = { 0xf19f064d, 0x82c, 0x4e27, { 0xbc, 0x73, 0x68, 0x82, 0xa1, 0xbb, 0x8e, 0x4c } }; constexpr GUID pkey_AudioEngine_OEM = { 0xe4870e26, 0x3cc5, 0x4cd2, { 0xba, 0x46, 0xca, 0xa, 0x9a, 0x70, 0xed, 0x4 } }; // constexpr GUID pkey_AudioUnknown1 = { 0x3d6e1656, 0x2e50, 0x4c4c, { 0x8d, 0x85, 0xd0, 0xac, 0xae, 0x3c, 0x6c, 0x68 } }; // constexpr GUID pkey_AudioUnknown2 = { 0x624f56de, 0xfd24, 0x473e, { 0x81, 0x4a, 0xde, 0x40, 0xaa, 0xca, 0xed, 0x16 } }; if (false // || key.fmtid == pkey_AudioEndpoint || key.fmtid == pkey_AudioEngine_Device || key.fmtid == pkey_AudioEngine_OEM // || key.fmtid == pkey_AudioUnknown1 // || key.fmtid == pkey_AudioUnknown2 || false) { LOG(("Audio Info: OnPropertyValueChanged(%1, %2) scheduling detach from audio device.").arg(deviceName).arg(keyName)); Media::Audio::ScheduleDetachFromDeviceSafe(); } else { DEBUG_LOG(("Audio Info: OnPropertyValueChanged(%1, %2) unknown, skipping.").arg(deviceName).arg(keyName)); } return S_OK; } STDMETHODIMP DeviceListener::OnDeviceStateChanged(LPCWSTR device_id, DWORD new_state) { auto deviceName = device_id ? '"' + QString::fromWCharArray(device_id) + '"' : QString("nullptr"); LOG(("Audio Info: OnDeviceStateChanged(%1, %2) scheduling detach from audio device.").arg(deviceName).arg(new_state)); Media::Audio::ScheduleDetachFromDeviceSafe(); return S_OK; } STDMETHODIMP DeviceListener::OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR new_default_device_id) { // Only listen for console and communication device changes. if ((role != eConsole && role != eCommunications) || (flow != eRender && flow != eCapture)) { LOG(("Audio Info: skipping OnDefaultDeviceChanged() flow %1, role %2, new_default_device_id: %3").arg(flow).arg(role).arg(new_default_device_id ? '"' + QString::fromWCharArray(new_default_device_id) + '"' : QString("nullptr"))); return S_OK; } LOG(("Audio Info: OnDefaultDeviceChanged() scheduling detach from audio device, flow %1, role %2, new_default_device_id: %3").arg(flow).arg(role).arg(new_default_device_id ? '"' + QString::fromWCharArray(new_default_device_id) + '"' : QString("nullptr"))); Media::Audio::ScheduleDetachFromDeviceSafe(); return S_OK; } auto WasCoInitialized = false; ComPtr<IMMDeviceEnumerator> Enumerator; DeviceListener *Listener = nullptr; } // namespace void Init() { auto hr = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&Enumerator)); if (FAILED(hr)) { Enumerator.Reset(); if (hr == CO_E_NOTINITIALIZED) { LOG(("Audio Info: CoCreateInstance fails with CO_E_NOTINITIALIZED")); hr = CoInitialize(nullptr); if (SUCCEEDED(hr)) { WasCoInitialized = true; hr = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&Enumerator)); if (FAILED(hr)) { Enumerator.Reset(); LOG(("Audio Error: could not CoCreateInstance of MMDeviceEnumerator, HRESULT: %1").arg(hr)); return; } } } else { LOG(("Audio Error: could not CoCreateInstance of MMDeviceEnumerator, HRESULT: %1").arg(hr)); return; } } Listener = new DeviceListener(); hr = Enumerator->RegisterEndpointNotificationCallback(Listener); if (FAILED(hr)) { LOG(("Audio Error: RegisterEndpointNotificationCallback failed, HRESULT: %1").arg(hr)); delete base::take(Listener); } } void DeInit() { if (Enumerator) { if (Listener) { auto hr = Enumerator->UnregisterEndpointNotificationCallback(Listener); if (FAILED(hr)) { LOG(("Audio Error: UnregisterEndpointNotificationCallback failed, HRESULT: %1").arg(hr)); } delete base::take(Listener); } Enumerator.Reset(); } if (WasCoInitialized) { CoUninitialize(); } } } // namespace Audio } // namespace Platform ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/win/audio_win.h���������������������������������������0000664�0000000�0000000�00000000561�13262451251�0024175�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace Platform { namespace Audio { void Init(); void DeInit(); } // namespace Audio } // namespace Platform �����������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/win/file_utilities_win.cpp����������������������������0000664�0000000�0000000�00000031050�13262451251�0026436�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "platform/win/file_utilities_win.h" #include "mainwindow.h" #include "storage/localstorage.h" #include "platform/win/windows_dlls.h" #include "lang/lang_keys.h" #include "messenger.h" #include <Shlwapi.h> #include <Windowsx.h> HBITMAP qt_pixmapToWinHBITMAP(const QPixmap &, int hbitmapFormat); namespace Platform { namespace File { namespace { class OpenWithApp { public: OpenWithApp(const QString &name, IAssocHandler *handler, HBITMAP icon = nullptr) : _name(name) , _handler(handler) , _icon(icon) { } OpenWithApp(OpenWithApp &&other) : _name(base::take(other._name)) , _handler(base::take(other._handler)) , _icon(base::take(other._icon)) { } OpenWithApp &operator=(OpenWithApp &&other) { _name = base::take(other._name); _icon = base::take(other._icon); _handler = base::take(other._handler); return (*this); } OpenWithApp(const OpenWithApp &other) = delete; OpenWithApp &operator=(const OpenWithApp &other) = delete; ~OpenWithApp() { if (_icon) { DeleteBitmap(_icon); } if (_handler) { _handler->Release(); } } const QString &name() const { return _name; } HBITMAP icon() const { return _icon; } IAssocHandler *handler() const { return _handler; } private: QString _name; IAssocHandler *_handler = nullptr; HBITMAP _icon = nullptr; }; HBITMAP IconToBitmap(LPWSTR icon, int iconindex) { if (!icon) return 0; WCHAR tmpIcon[4096]; if (icon[0] == L'@' && SUCCEEDED(SHLoadIndirectString(icon, tmpIcon, 4096, 0))) { icon = tmpIcon; } int32 w = GetSystemMetrics(SM_CXSMICON), h = GetSystemMetrics(SM_CYSMICON); HICON ico = ExtractIcon(0, icon, iconindex); if (!ico) { if (!iconindex) { // try to read image QImage img(QString::fromWCharArray(icon)); if (!img.isNull()) { return qt_pixmapToWinHBITMAP(App::pixmapFromImageInPlace(img.scaled(w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)), /* HBitmapAlpha */ 2); } } return 0; } HDC screenDC = GetDC(0), hdc = CreateCompatibleDC(screenDC); HBITMAP result = CreateCompatibleBitmap(screenDC, w, h); HGDIOBJ was = SelectObject(hdc, result); DrawIconEx(hdc, 0, 0, ico, w, h, 0, NULL, DI_NORMAL); SelectObject(hdc, was); DeleteDC(hdc); ReleaseDC(0, screenDC); DestroyIcon(ico); return (HBITMAP)CopyImage(result, IMAGE_BITMAP, 0, 0, LR_DEFAULTSIZE | LR_CREATEDIBSECTION); } } // namespace void UnsafeOpenEmailLink(const QString &email) { auto url = QUrl(qstr("mailto:") + email); if (!QDesktopServices::openUrl(url)) { auto wstringUrl = url.toString(QUrl::FullyEncoded).toStdWString(); if (Dlls::SHOpenWithDialog) { OPENASINFO info; info.oaifInFlags = OAIF_ALLOW_REGISTRATION | OAIF_REGISTER_EXT | OAIF_EXEC | OAIF_FILE_IS_URI | OAIF_URL_PROTOCOL; info.pcszClass = NULL; info.pcszFile = wstringUrl.c_str(); Dlls::SHOpenWithDialog(0, &info); } else if (Dlls::OpenAs_RunDLL) { Dlls::OpenAs_RunDLL(0, 0, wstringUrl.c_str(), SW_SHOWNORMAL); } else { ShellExecute(0, L"open", wstringUrl.c_str(), 0, 0, SW_SHOWNORMAL); } } } bool UnsafeShowOpenWithDropdown(const QString &filepath, QPoint menuPosition) { if (!Dlls::SHAssocEnumHandlers || !Dlls::SHCreateItemFromParsingName) { return false; } auto window = App::wnd(); if (!window) { return false; } auto parentHWND = window->psHwnd(); auto wstringPath = QDir::toNativeSeparators(filepath).toStdWString(); auto result = false; std::vector<OpenWithApp> handlers; IShellItem* pItem = nullptr; if (SUCCEEDED(Dlls::SHCreateItemFromParsingName(wstringPath.c_str(), nullptr, IID_PPV_ARGS(&pItem)))) { IEnumAssocHandlers *assocHandlers = nullptr; if (SUCCEEDED(pItem->BindToHandler(nullptr, BHID_EnumAssocHandlers, IID_PPV_ARGS(&assocHandlers)))) { HRESULT hr = S_FALSE; do { IAssocHandler *handler = nullptr; ULONG ulFetched = 0; hr = assocHandlers->Next(1, &handler, &ulFetched); if (FAILED(hr) || hr == S_FALSE || !ulFetched) break; LPWSTR name = 0; if (SUCCEEDED(handler->GetUIName(&name))) { LPWSTR icon = 0; int iconindex = 0; if (SUCCEEDED(handler->GetIconLocation(&icon, &iconindex)) && icon) { handlers.push_back(OpenWithApp(QString::fromWCharArray(name), handler, IconToBitmap(icon, iconindex))); CoTaskMemFree(icon); } else { handlers.push_back(OpenWithApp(QString::fromWCharArray(name), handler)); } CoTaskMemFree(name); } else { handler->Release(); } } while (hr != S_FALSE); assocHandlers->Release(); } if (!handlers.empty()) { HMENU menu = CreatePopupMenu(); ranges::sort(handlers, [](const OpenWithApp &a, auto &b) { return a.name() < b.name(); }); for (int32 i = 0, l = handlers.size(); i < l; ++i) { MENUITEMINFO menuInfo = { 0 }; menuInfo.cbSize = sizeof(menuInfo); menuInfo.fMask = MIIM_STRING | MIIM_DATA | MIIM_ID; menuInfo.fType = MFT_STRING; menuInfo.wID = i + 1; if (auto icon = handlers[i].icon()) { menuInfo.fMask |= MIIM_BITMAP; menuInfo.hbmpItem = icon; } auto name = handlers[i].name(); if (name.size() > 512) name = name.mid(0, 512); WCHAR nameArr[1024]; name.toWCharArray(nameArr); nameArr[name.size()] = 0; menuInfo.dwTypeData = nameArr; InsertMenuItem(menu, GetMenuItemCount(menu), TRUE, &menuInfo); } MENUITEMINFO sepInfo = { 0 }; sepInfo.cbSize = sizeof(sepInfo); sepInfo.fMask = MIIM_STRING | MIIM_DATA; sepInfo.fType = MFT_SEPARATOR; InsertMenuItem(menu, GetMenuItemCount(menu), true, &sepInfo); MENUITEMINFO menuInfo = { 0 }; menuInfo.cbSize = sizeof(menuInfo); menuInfo.fMask = MIIM_STRING | MIIM_DATA | MIIM_ID; menuInfo.fType = MFT_STRING; menuInfo.wID = handlers.size() + 1; QString name = lang(lng_wnd_choose_program_menu); if (name.size() > 512) name = name.mid(0, 512); WCHAR nameArr[1024]; name.toWCharArray(nameArr); nameArr[name.size()] = 0; menuInfo.dwTypeData = nameArr; InsertMenuItem(menu, GetMenuItemCount(menu), TRUE, &menuInfo); int sel = TrackPopupMenu(menu, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_LEFTBUTTON | TPM_RETURNCMD, menuPosition.x(), menuPosition.y(), 0, parentHWND, 0); DestroyMenu(menu); if (sel > 0) { if (sel <= handlers.size()) { IDataObject *dataObj = 0; if (SUCCEEDED(pItem->BindToHandler(nullptr, BHID_DataObject, IID_PPV_ARGS(&dataObj))) && dataObj) { handlers[sel - 1].handler()->Invoke(dataObj); dataObj->Release(); result = true; } } } else { result = true; } } pItem->Release(); } return result; } bool UnsafeShowOpenWith(const QString &filepath) { auto wstringPath = QDir::toNativeSeparators(filepath).toStdWString(); if (Dlls::SHOpenWithDialog) { OPENASINFO info; info.oaifInFlags = OAIF_ALLOW_REGISTRATION | OAIF_REGISTER_EXT | OAIF_EXEC; info.pcszClass = NULL; info.pcszFile = wstringPath.c_str(); Dlls::SHOpenWithDialog(0, &info); return true; } else if (Dlls::OpenAs_RunDLL) { Dlls::OpenAs_RunDLL(0, 0, wstringPath.c_str(), SW_SHOWNORMAL); return true; } return false; } void UnsafeLaunch(const QString &filepath) { auto wstringPath = QDir::toNativeSeparators(filepath).toStdWString(); ShellExecute(0, L"open", wstringPath.c_str(), 0, 0, SW_SHOWNORMAL); } void UnsafeShowInFolder(const QString &filepath) { auto nativePath = QDir::toNativeSeparators(filepath); auto wstringPath = nativePath.toStdWString(); if (auto pidl = ILCreateFromPathW(wstringPath.c_str())) { SHOpenFolderAndSelectItems(pidl, 0, nullptr, 0); ILFree(pidl); } else { auto pathEscaped = nativePath.replace('"', qsl("\"\"")); auto wstringParam = (qstr("/select,") + pathEscaped).toStdWString(); ShellExecute(0, 0, L"explorer", wstringParam.c_str(), 0, SW_SHOWNORMAL); } } void PostprocessDownloaded(const QString &filepath) { auto wstringZoneFile = QDir::toNativeSeparators(filepath).toStdWString() + L":Zone.Identifier"; auto f = CreateFile(wstringZoneFile.c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); if (f == INVALID_HANDLE_VALUE) { // :( return; } const char data[] = "[ZoneTransfer]\r\nZoneId=3\r\n"; DWORD written = 0; BOOL result = WriteFile(f, data, sizeof(data), &written, NULL); CloseHandle(f); if (!result || written != sizeof(data)) { // :( return; } } } // namespace File namespace FileDialog { namespace { using Type = ::FileDialog::internal::Type; } // namespace void InitLastPath() { // hack to restore previous dir without hurting performance QSettings settings(QSettings::UserScope, qstr("QtProject")); settings.beginGroup(qstr("Qt")); QByteArray sd = settings.value(qstr("filedialog")).toByteArray(); QDataStream stream(&sd, QIODevice::ReadOnly); if (!stream.atEnd()) { int version = 3, _QFileDialogMagic = 190; QByteArray splitterState; QByteArray headerData; QList<QUrl> bookmarks; QStringList history; QString currentDirectory; qint32 marker; qint32 v; qint32 viewMode; stream >> marker; stream >> v; if (marker == _QFileDialogMagic && v == version) { stream >> splitterState >> bookmarks >> history >> currentDirectory >> headerData >> viewMode; cSetDialogLastPath(currentDirectory); } } if (cDialogHelperPath().isEmpty()) { QDir temppath(cWorkingDir() + "tdata/tdummy/"); if (!temppath.exists()) { temppath.mkpath(temppath.absolutePath()); } if (temppath.exists()) { cSetDialogHelperPath(temppath.absolutePath()); } } } bool Get(QStringList &files, QByteArray &remoteContent, const QString &caption, const QString &filter, ::FileDialog::internal::Type type, QString startFile) { if (cDialogLastPath().isEmpty()) { Platform::FileDialog::InitLastPath(); } // A hack for fast dialog create. There was some huge performance problem // if we open a file dialog in some folder with a large amount of files. // Some internal Qt watcher iterated over all of them, querying some information // that forced file icon and maybe other properties being resolved and this was // a blocking operation. auto helperPath = cDialogHelperPathFinal(); auto parent = Messenger::Instance().getFileDialogParent(); QFileDialog dialog(parent, caption, helperPath, filter); dialog.setModal(true); if (type == Type::ReadFile || type == Type::ReadFiles) { dialog.setFileMode((type == Type::ReadFiles) ? QFileDialog::ExistingFiles : QFileDialog::ExistingFile); dialog.setAcceptMode(QFileDialog::AcceptOpen); } else if (type == Type::ReadFolder) { // save dir dialog.setAcceptMode(QFileDialog::AcceptOpen); // We use "obsolete" value ::DirectoryOnly instead of ::Directory + ::ShowDirsOnly // because in Windows XP native dialog this one works, while the "preferred" one // shows a native file choose dialog where you can't choose a directory, only open one. dialog.setFileMode(QFileDialog::DirectoryOnly); dialog.setOption(QFileDialog::ShowDirsOnly); } else { // save file dialog.setFileMode(QFileDialog::AnyFile); dialog.setAcceptMode(QFileDialog::AcceptSave); } dialog.show(); auto realLastPath = ([startFile] { // If we're given some non empty path containing a folder - use it. if (!startFile.isEmpty() && (startFile.indexOf('/') >= 0 || startFile.indexOf('\\') >= 0)) { return QFileInfo(startFile).dir().absolutePath(); } return cDialogLastPath(); })(); if (realLastPath.isEmpty() || realLastPath.endsWith(qstr("/tdummy"))) { realLastPath = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation); } dialog.setDirectory(realLastPath); if (type == Type::WriteFile) { auto toSelect = startFile; auto lastSlash = toSelect.lastIndexOf('/'); if (lastSlash >= 0) { toSelect = toSelect.mid(lastSlash + 1); } auto lastBackSlash = toSelect.lastIndexOf('\\'); if (lastBackSlash >= 0) { toSelect = toSelect.mid(lastBackSlash + 1); } dialog.selectFile(toSelect); } int res = dialog.exec(); if (type != Type::ReadFolder) { // Save last used directory for all queries except directory choosing. auto path = dialog.directory().absolutePath(); if (path != cDialogLastPath()) { cSetDialogLastPath(path); Local::writeUserSettings(); } } if (res == QDialog::Accepted) { if (type == Type::ReadFiles) { files = dialog.selectedFiles(); } else { files = dialog.selectedFiles().mid(0, 1); } if (type == Type::ReadFile || type == Type::ReadFiles) { remoteContent = dialog.selectedRemoteContent(); } return true; } files = QStringList(); remoteContent = QByteArray(); return false; } } // namespace FileDialog } // namespace Platform����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/win/file_utilities_win.h������������������������������0000664�0000000�0000000�00000000713�13262451251�0026105�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "platform/platform_file_utilities.h" namespace Platform { namespace File { inline QString UrlToLocal(const QUrl &url) { return url.toLocalFile(); } } // namespace File } // namespace Platform �����������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/win/launcher_win.cpp����������������������������������0000664�0000000�0000000�00000006576�13262451251�0025244�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "platform/win/launcher_win.h" #include "core/crash_reports.h" #include "platform/platform_specific.h" #include <windows.h> #include <shellapi.h> namespace Platform { base::optional<QStringList> Launcher::readArgumentsHook( int argc, char *argv[]) const { auto count = 0; if (const auto list = CommandLineToArgvW(GetCommandLine(), &count)) { const auto guard = gsl::finally([&] { LocalFree(list); }); if (count > 0) { auto result = QStringList(); result.reserve(count); for (auto i = 0; i != count; ++i) { result.push_back(QString::fromWCharArray(list[i])); } return result; } } return base::none; } bool Launcher::launchUpdater(UpdaterLaunch action) { if (cExeName().isEmpty()) { return false; } const auto operation = (action == UpdaterLaunch::JustRelaunch) ? QString() : (cWriteProtected() ? qsl("runas") : QString()); const auto binaryPath = (action == UpdaterLaunch::JustRelaunch) ? (cExeDir() + cExeName()) : (cWriteProtected() ? (cWorkingDir() + qsl("tupdates/temp/Updater.exe")) : (cExeDir() + qsl("Updater.exe"))); auto argumentsList = QStringList(); const auto pushArgument = [&](const QString &argument) { argumentsList.push_back(argument.trimmed()); }; if (cLaunchMode() == LaunchModeAutoStart) { pushArgument(qsl("-autostart")); } if (cDebug()) { pushArgument(qsl("-debug")); } if (cStartInTray()) { pushArgument(qsl("-startintray")); } if (cTestMode()) { pushArgument(qsl("-testmode")); } if (customWorkingDir()) { pushArgument(qsl("-workdir")); pushArgument('"' + cWorkingDir() + '"'); } if (cDataFile() != qsl("data")) { pushArgument(qsl("-key")); pushArgument('"' + cDataFile() + '"'); } if (action == UpdaterLaunch::JustRelaunch) { pushArgument(qsl("-noupdate")); if (cRestartingToSettings()) { pushArgument(qsl("-tosettings")); } } else { pushArgument(qsl("-update")); pushArgument(qsl("-exename")); pushArgument('"' + cExeName() + '"'); if (cWriteProtected()) { pushArgument(qsl("-writeprotected")); pushArgument('"' + cExeDir() + '"'); } } return launch(operation, binaryPath, argumentsList); } bool Launcher::launch( const QString &operation, const QString &binaryPath, const QStringList &argumentsList) { const auto convertPath = [](const QString &path) { return QDir::toNativeSeparators(path).toStdWString(); }; const auto nativeBinaryPath = convertPath(binaryPath); const auto nativeWorkingDir = convertPath(cWorkingDir()); const auto arguments = argumentsList.join(' '); DEBUG_LOG(("Application Info: executing %1 %2" ).arg(binaryPath ).arg(arguments )); Logs::closeMain(); CrashReports::Finish(); const auto hwnd = HWND(0); const auto result = ShellExecute( hwnd, operation.isEmpty() ? nullptr : operation.toStdWString().c_str(), nativeBinaryPath.c_str(), arguments.toStdWString().c_str(), nativeWorkingDir.empty() ? nullptr : nativeWorkingDir.c_str(), SW_SHOWNORMAL); if (long(result) < 32) { DEBUG_LOG(("Application Error: failed to execute %1, working directory: '%2', result: %3" ).arg(binaryPath ).arg(cWorkingDir() ).arg(long(result) )); return false; } return true; } } // namespace ����������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/win/launcher_win.h������������������������������������0000664�0000000�0000000�00000001247�13262451251�0024677�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "core/launcher.h" namespace Platform { class Launcher : public Core::Launcher { public: using Core::Launcher::Launcher; private: base::optional<QStringList> readArgumentsHook( int argc, char *argv[]) const override; bool launchUpdater(UpdaterLaunch action) override; bool launch( const QString &operation, const QString &binaryPath, const QStringList &argumentsList); }; } // namespace Platform ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/win/main_window_win.cpp�������������������������������0000664�0000000�0000000�00000074153�13262451251�0025752�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "platform/win/main_window_win.h" #include "styles/style_window.h" #include "platform/platform_notifications_manager.h" #include "platform/win/windows_dlls.h" #include "window/notifications_manager.h" #include "mainwindow.h" #include "messenger.h" #include "application.h" #include "lang/lang_keys.h" #include "storage/localstorage.h" #include "ui/widgets/popup_menu.h" #include "window/themes/window_theme.h" #include "history/history.h" #include <qpa/qplatformnativeinterface.h> #include <Shobjidl.h> #include <shellapi.h> #include <WtsApi32.h> #include <roapi.h> #include <wrl\client.h> #include <wrl\implements.h> #include <windows.ui.notifications.h> #include <Windowsx.h> #define min(a, b) ((a) < (b) ? (a) : (b)) #define max(a, b) ((a) < (b) ? (b) : (a)) #include <gdiplus.h> #undef min #undef max HICON qt_pixmapToWinHICON(const QPixmap &); using namespace Microsoft::WRL; namespace Platform { namespace { HICON createHIconFromQIcon(const QIcon &icon, int xSize, int ySize) { if (!icon.isNull()) { const QPixmap pm = icon.pixmap(icon.actualSize(QSize(xSize, ySize))); if (!pm.isNull()) { return qt_pixmapToWinHICON(pm); } } return nullptr; } HWND createTaskbarHider() { HINSTANCE appinst = (HINSTANCE)GetModuleHandle(0); HWND hWnd = 0; QString cn = QString("TelegramTaskbarHider"); LPCWSTR _cn = (LPCWSTR)cn.utf16(); WNDCLASSEX wc; wc.cbSize = sizeof(wc); wc.style = 0; wc.lpfnWndProc = DefWindowProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = appinst; wc.hIcon = 0; wc.hCursor = 0; wc.hbrBackground = 0; wc.lpszMenuName = NULL; wc.lpszClassName = _cn; wc.hIconSm = 0; if (!RegisterClassEx(&wc)) { DEBUG_LOG(("Application Error: could not register taskbar hider window class, error: %1").arg(GetLastError())); return hWnd; } hWnd = CreateWindowEx(WS_EX_TOOLWINDOW, _cn, 0, WS_POPUP, 0, 0, 0, 0, 0, 0, appinst, 0); if (!hWnd) { DEBUG_LOG(("Application Error: could not create taskbar hider window class, error: %1").arg(GetLastError())); return hWnd; } return hWnd; } enum { _PsInitHor = 0x01, _PsInitVer = 0x02, }; int32 _psSize = 0; class _PsShadowWindows { public: using Change = MainWindow::ShadowsChange; using Changes = MainWindow::ShadowsChanges; _PsShadowWindows() : screenDC(0), noKeyColor(RGB(255, 255, 255)) { for (int i = 0; i < 4; ++i) { dcs[i] = 0; bitmaps[i] = 0; hwnds[i] = 0; } } void setColor(QColor c) { r = c.red(); g = c.green(); b = c.blue(); if (!hwnds[0]) return; Gdiplus::SolidBrush brush(Gdiplus::Color(_alphas[0], r, g, b)); for (int i = 0; i < 4; ++i) { Gdiplus::Graphics graphics(dcs[i]); graphics.SetCompositingMode(Gdiplus::CompositingModeSourceCopy); if ((i % 2) && _h || !(i % 2) && _w) { graphics.FillRectangle(&brush, 0, 0, (i % 2) ? _size : _w, (i % 2) ? _h : _size); } } initCorners(); _x = _y = _w = _h = 0; update(Change::Moved | Change::Resized); } bool init(QColor c) { _fullsize = st::windowShadow.width(); _shift = st::windowShadowShift; auto cornersImage = QImage(_fullsize, _fullsize, QImage::Format_ARGB32_Premultiplied); { Painter p(&cornersImage); p.setCompositionMode(QPainter::CompositionMode_Source); st::windowShadow.paint(p, 0, 0, _fullsize, QColor(0, 0, 0)); } if (rtl()) cornersImage = cornersImage.mirrored(true, false); _metaSize = _fullsize + 2 * _shift; _alphas.reserve(_metaSize); _colors.reserve(_metaSize * _metaSize); for (int32 j = 0; j < _metaSize; ++j) { for (int32 i = 0; i < _metaSize; ++i) { _colors.push_back((i < 2 * _shift || j < 2 * _shift) ? 1 : qMax(BYTE(1), BYTE(cornersImage.pixel(QPoint(i - 2 * _shift, j - 2 * _shift)) >> 24))); } } uchar prev = 0; for (int32 i = 0; i < _metaSize; ++i) { uchar a = _colors[(_metaSize - 1) * _metaSize + i]; if (a < prev) break; _alphas.push_back(a); prev = a; } _psSize = _size = _alphas.size() - 2 * _shift; setColor(c); Gdiplus::GdiplusStartupInput gdiplusStartupInput; ULONG_PTR gdiplusToken; Gdiplus::Status gdiRes = Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL); if (gdiRes != Gdiplus::Ok) { LOG(("Application Error: could not init GDI+, error: %1").arg((int)gdiRes)); return false; } blend.AlphaFormat = AC_SRC_ALPHA; blend.SourceConstantAlpha = 255; blend.BlendFlags = 0; blend.BlendOp = AC_SRC_OVER; screenDC = GetDC(0); if (!screenDC) { LOG(("Application Error: could not GetDC(0), error: %2").arg(GetLastError())); return false; } QRect avail(Sandbox::availableGeometry()); max_w = avail.width(); accumulate_max(max_w, st::windowMinWidth); max_h = avail.height(); accumulate_max(max_h, st::titleHeight + st::windowMinHeight); HINSTANCE appinst = (HINSTANCE)GetModuleHandle(0); HWND hwnd = App::wnd() ? App::wnd()->psHwnd() : 0; for (int i = 0; i < 4; ++i) { QString cn = QString("TelegramShadow%1").arg(i); LPCWSTR _cn = (LPCWSTR)cn.utf16(); WNDCLASSEX wc; wc.cbSize = sizeof(wc); wc.style = 0; wc.lpfnWndProc = wndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = appinst; wc.hIcon = 0; wc.hCursor = 0; wc.hbrBackground = 0; wc.lpszMenuName = NULL; wc.lpszClassName = _cn; wc.hIconSm = 0; if (!RegisterClassEx(&wc)) { LOG(("Application Error: could not register shadow window class %1, error: %2").arg(i).arg(GetLastError())); destroy(); return false; } hwnds[i] = CreateWindowEx(WS_EX_LAYERED | WS_EX_TOOLWINDOW, _cn, 0, WS_POPUP, 0, 0, 0, 0, 0, 0, appinst, 0); if (!hwnds[i]) { LOG(("Application Error: could not create shadow window class %1, error: %2").arg(i).arg(GetLastError())); destroy(); return false; } SetWindowLong(hwnds[i], GWL_HWNDPARENT, (LONG)hwnd); dcs[i] = CreateCompatibleDC(screenDC); if (!dcs[i]) { LOG(("Application Error: could not create dc for shadow window class %1, error: %2").arg(i).arg(GetLastError())); destroy(); return false; } bitmaps[i] = CreateCompatibleBitmap(screenDC, (i % 2) ? _size : max_w, (i % 2) ? max_h : _size); if (!bitmaps[i]) { LOG(("Application Error: could not create bitmap for shadow window class %1, error: %2").arg(i).arg(GetLastError())); destroy(); return false; } SelectObject(dcs[i], bitmaps[i]); } QStringList alphasForLog; for_const (auto alpha, _alphas) { alphasForLog.append(QString::number(alpha)); } LOG(("Window Shadow: %1").arg(alphasForLog.join(", "))); initCorners(); return true; } void initCorners(int directions = (_PsInitHor | _PsInitVer)) { bool hor = (directions & _PsInitHor), ver = (directions & _PsInitVer); Gdiplus::Graphics graphics0(dcs[0]), graphics1(dcs[1]), graphics2(dcs[2]), graphics3(dcs[3]); graphics0.SetCompositingMode(Gdiplus::CompositingModeSourceCopy); graphics1.SetCompositingMode(Gdiplus::CompositingModeSourceCopy); graphics2.SetCompositingMode(Gdiplus::CompositingModeSourceCopy); graphics3.SetCompositingMode(Gdiplus::CompositingModeSourceCopy); Gdiplus::SolidBrush brush(Gdiplus::Color(_alphas[0], r, g, b)); if (hor) graphics0.FillRectangle(&brush, 0, 0, _fullsize - (_size - _shift), 2 * _shift); if (ver) { graphics1.FillRectangle(&brush, 0, 0, _size, 2 * _shift); graphics3.FillRectangle(&brush, 0, 0, _size, 2 * _shift); graphics1.FillRectangle(&brush, _size - _shift, 2 * _shift, _shift, _fullsize); graphics3.FillRectangle(&brush, 0, 2 * _shift, _shift, _fullsize); } if (hor) { for (int j = 2 * _shift; j < _size; ++j) { for (int k = 0; k < _fullsize - (_size - _shift); ++k) { brush.SetColor(Gdiplus::Color(_colors[j * _metaSize + k + (_size + _shift)], r, g, b)); graphics0.FillRectangle(&brush, k, j, 1, 1); graphics2.FillRectangle(&brush, k, _size - (j - 2 * _shift) - 1, 1, 1); } } for (int j = _size; j < _size + 2 * _shift; ++j) { for (int k = 0; k < _fullsize - (_size - _shift); ++k) { brush.SetColor(Gdiplus::Color(_colors[j * _metaSize + k + (_size + _shift)], r, g, b)); graphics2.FillRectangle(&brush, k, _size - (j - 2 * _shift) - 1, 1, 1); } } } if (ver) { for (int j = 2 * _shift; j < _fullsize + 2 * _shift; ++j) { for (int k = _shift; k < _size; ++k) { brush.SetColor(Gdiplus::Color(_colors[j * _metaSize + (k + _shift)], r, g, b)); graphics1.FillRectangle(&brush, _size - k - 1, j, 1, 1); graphics3.FillRectangle(&brush, k, j, 1, 1); } } } } void verCorners(int h, Gdiplus::Graphics *pgraphics1, Gdiplus::Graphics *pgraphics3) { Gdiplus::SolidBrush brush(Gdiplus::Color(_alphas[0], r, g, b)); pgraphics1->FillRectangle(&brush, _size - _shift, h - _fullsize, _shift, _fullsize); pgraphics3->FillRectangle(&brush, 0, h - _fullsize, _shift, _fullsize); for (int j = 0; j < _fullsize; ++j) { for (int k = _shift; k < _size; ++k) { brush.SetColor(Gdiplus::Color(_colors[(j + 2 * _shift) * _metaSize + k + _shift], r, g, b)); pgraphics1->FillRectangle(&brush, _size - k - 1, h - j - 1, 1, 1); pgraphics3->FillRectangle(&brush, k, h - j - 1, 1, 1); } } } void horCorners(int w, Gdiplus::Graphics *pgraphics0, Gdiplus::Graphics *pgraphics2) { Gdiplus::SolidBrush brush(Gdiplus::Color(_alphas[0], r, g, b)); pgraphics0->FillRectangle(&brush, w - 2 * _size - (_fullsize - (_size - _shift)), 0, _fullsize - (_size - _shift), 2 * _shift); for (int j = 2 * _shift; j < _size; ++j) { for (int k = 0; k < _fullsize - (_size - _shift); ++k) { brush.SetColor(Gdiplus::Color(_colors[j * _metaSize + k + (_size + _shift)], r, g, b)); pgraphics0->FillRectangle(&brush, w - 2 * _size - k - 1, j, 1, 1); pgraphics2->FillRectangle(&brush, w - 2 * _size - k - 1, _size - (j - 2 * _shift) - 1, 1, 1); } } for (int j = _size; j < _size + 2 * _shift; ++j) { for (int k = 0; k < _fullsize - (_size - _shift); ++k) { brush.SetColor(Gdiplus::Color(_colors[j * _metaSize + k + (_size + _shift)], r, g, b)); pgraphics2->FillRectangle(&brush, w - 2 * _size - k - 1, _size - (j - 2 * _shift) - 1, 1, 1); } } } void update(Changes changes, WINDOWPOS *pos = 0) { HWND hwnd = App::wnd() ? App::wnd()->psHwnd() : 0; if (!hwnd || !hwnds[0]) return; if (changes == Changes(Change::Activate)) { for (int i = 0; i < 4; ++i) { SetWindowPos(hwnds[i], hwnd, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); } return; } if (changes & Change::Hidden) { if (!hidden) { for (int i = 0; i < 4; ++i) { hidden = true; ShowWindow(hwnds[i], SW_HIDE); } } return; } if (!App::wnd()->positionInited()) return; int x = _x, y = _y, w = _w, h = _h; if (pos && (!(pos->flags & SWP_NOMOVE) || !(pos->flags & SWP_NOSIZE) || !(pos->flags & SWP_NOREPOSITION))) { if (!(pos->flags & SWP_NOMOVE)) { x = pos->x - _size; y = pos->y - _size; } else if (pos->flags & SWP_NOSIZE) { for (int i = 0; i < 4; ++i) { SetWindowPos(hwnds[i], hwnd, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); } return; } if (!(pos->flags & SWP_NOSIZE)) { w = pos->cx + 2 * _size; h = pos->cy + 2 * _size; } } else { RECT r; GetWindowRect(hwnd, &r); x = r.left - _size; y = r.top - _size; w = r.right + _size - x; h = r.bottom + _size - y; } if (h < 2 * _fullsize + 2 * _shift) { h = 2 * _fullsize + 2 * _shift; } if (w < 2 * (_fullsize + _shift)) { w = 2 * (_fullsize + _shift); } if (w != _w) { int from = (_w > 2 * (_fullsize + _shift)) ? (_w - _size - _fullsize - _shift) : (_fullsize - (_size - _shift)); int to = w - _size - _fullsize - _shift; if (w > max_w) { from = _fullsize - (_size - _shift); max_w *= 2; for (int i = 0; i < 4; i += 2) { DeleteObject(bitmaps[i]); bitmaps[i] = CreateCompatibleBitmap(screenDC, max_w, _size); SelectObject(dcs[i], bitmaps[i]); } initCorners(_PsInitHor); } Gdiplus::Graphics graphics0(dcs[0]), graphics2(dcs[2]); graphics0.SetCompositingMode(Gdiplus::CompositingModeSourceCopy); graphics2.SetCompositingMode(Gdiplus::CompositingModeSourceCopy); Gdiplus::SolidBrush brush(Gdiplus::Color(_alphas[0], r, g, b)); if (to > from) { graphics0.FillRectangle(&brush, from, 0, to - from, 2 * _shift); for (int i = 2 * _shift; i < _size; ++i) { Gdiplus::Pen pen(Gdiplus::Color(_alphas[i], r, g, b)); graphics0.DrawLine(&pen, from, i, to, i); graphics2.DrawLine(&pen, from, _size - (i - 2 * _shift) - 1, to, _size - (i - 2 * _shift) - 1); } for (int i = _size; i < _size + 2 * _shift; ++i) { Gdiplus::Pen pen(Gdiplus::Color(_alphas[i], r, g, b)); graphics2.DrawLine(&pen, from, _size - (i - 2 * _shift) - 1, to, _size - (i - 2 * _shift) - 1); } } if (_w > w) { graphics0.FillRectangle(&brush, w - _size - _fullsize - _shift, 0, _fullsize - (_size - _shift), _size); graphics2.FillRectangle(&brush, w - _size - _fullsize - _shift, 0, _fullsize - (_size - _shift), _size); } horCorners(w, &graphics0, &graphics2); POINT p0 = { x + _size, y }, p2 = { x + _size, y + h - _size }, f = { 0, 0 }; SIZE s = { w - 2 * _size, _size }; updateWindow(0, &p0, &s); updateWindow(2, &p2, &s); } else if (x != _x || y != _y) { POINT p0 = { x + _size, y }, p2 = { x + _size, y + h - _size }; updateWindow(0, &p0); updateWindow(2, &p2); } else if (h != _h) { POINT p2 = { x + _size, y + h - _size }; updateWindow(2, &p2); } if (h != _h) { int from = (_h > 2 * _fullsize + 2 * _shift) ? (_h - _fullsize) : (_fullsize + 2 * _shift); int to = h - _fullsize; if (h > max_h) { from = (_fullsize + 2 * _shift); max_h *= 2; for (int i = 1; i < 4; i += 2) { DeleteObject(bitmaps[i]); bitmaps[i] = CreateCompatibleBitmap(dcs[i], _size, max_h); SelectObject(dcs[i], bitmaps[i]); } initCorners(_PsInitVer); } Gdiplus::Graphics graphics1(dcs[1]), graphics3(dcs[3]); graphics1.SetCompositingMode(Gdiplus::CompositingModeSourceCopy); graphics3.SetCompositingMode(Gdiplus::CompositingModeSourceCopy); Gdiplus::SolidBrush brush(Gdiplus::Color(_alphas[0], r, g, b)); if (to > from) { graphics1.FillRectangle(&brush, _size - _shift, from, _shift, to - from); graphics3.FillRectangle(&brush, 0, from, _shift, to - from); for (int i = 2 * _shift; i < _size + _shift; ++i) { Gdiplus::Pen pen(Gdiplus::Color(_alphas[i], r, g, b)); graphics1.DrawLine(&pen, _size + _shift - i - 1, from, _size + _shift - i - 1, to); graphics3.DrawLine(&pen, i - _shift, from, i - _shift, to); } } if (_h > h) { graphics1.FillRectangle(&brush, 0, h - _fullsize, _size, _fullsize); graphics3.FillRectangle(&brush, 0, h - _fullsize, _size, _fullsize); } verCorners(h, &graphics1, &graphics3); POINT p1 = { x + w - _size, y }, p3 = { x, y }, f = { 0, 0 }; SIZE s = { _size, h }; updateWindow(1, &p1, &s); updateWindow(3, &p3, &s); } else if (x != _x || y != _y) { POINT p1 = { x + w - _size, y }, p3 = { x, y }; updateWindow(1, &p1); updateWindow(3, &p3); } else if (w != _w) { POINT p1 = { x + w - _size, y }; updateWindow(1, &p1); } _x = x; _y = y; _w = w; _h = h; if (hidden && (changes & Change::Shown)) { for (int i = 0; i < 4; ++i) { SetWindowPos(hwnds[i], hwnd, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOACTIVATE); } hidden = false; } } void updateWindow(int i, POINT *p, SIZE *s = 0) { static POINT f = { 0, 0 }; if (s) { UpdateLayeredWindow(hwnds[i], (s ? screenDC : 0), p, s, (s ? dcs[i] : 0), (s ? (&f) : 0), noKeyColor, &blend, ULW_ALPHA); } else { SetWindowPos(hwnds[i], 0, p->x, p->y, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER); } } void destroy() { for (int i = 0; i < 4; ++i) { if (dcs[i]) DeleteDC(dcs[i]); if (bitmaps[i]) DeleteObject(bitmaps[i]); if (hwnds[i]) DestroyWindow(hwnds[i]); dcs[i] = 0; bitmaps[i] = 0; hwnds[i] = 0; } if (screenDC) ReleaseDC(0, screenDC); } private: int _x = 0, _y = 0, _w = 0, _h = 0; int _metaSize = 0, _fullsize = 0, _size = 0, _shift = 0; QVector<BYTE> _alphas, _colors; bool hidden = true; HWND hwnds[4]; HDC dcs[4], screenDC; HBITMAP bitmaps[4]; int max_w = 0, max_h = 0; BLENDFUNCTION blend; BYTE r = 0, g = 0, b = 0; COLORREF noKeyColor; static LRESULT CALLBACK wndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); }; _PsShadowWindows _psShadowWindows; LRESULT CALLBACK _PsShadowWindows::wndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { auto wnd = App::wnd(); if (!wnd || !wnd->shadowsWorking()) return DefWindowProc(hwnd, msg, wParam, lParam); int i; for (i = 0; i < 4; ++i) { if (_psShadowWindows.hwnds[i] && hwnd == _psShadowWindows.hwnds[i]) { break; } } if (i == 4) return DefWindowProc(hwnd, msg, wParam, lParam); switch (msg) { case WM_CLOSE: App::wnd()->close(); break; case WM_NCHITTEST: { int32 xPos = GET_X_LPARAM(lParam), yPos = GET_Y_LPARAM(lParam); switch (i) { case 0: return HTTOP; case 1: return (yPos < _psShadowWindows._y + _psSize) ? HTTOPRIGHT : ((yPos >= _psShadowWindows._y + _psShadowWindows._h - _psSize) ? HTBOTTOMRIGHT : HTRIGHT); case 2: return HTBOTTOM; case 3: return (yPos < _psShadowWindows._y + _psSize) ? HTTOPLEFT : ((yPos >= _psShadowWindows._y + _psShadowWindows._h - _psSize) ? HTBOTTOMLEFT : HTLEFT); } return HTTRANSPARENT; } break; case WM_NCACTIVATE: return DefWindowProc(hwnd, msg, wParam, lParam); case WM_NCLBUTTONDOWN: case WM_NCLBUTTONUP: case WM_NCLBUTTONDBLCLK: case WM_NCMBUTTONDOWN: case WM_NCMBUTTONUP: case WM_NCMBUTTONDBLCLK: case WM_NCRBUTTONDOWN: case WM_NCRBUTTONUP: case WM_NCRBUTTONDBLCLK: case WM_NCXBUTTONDOWN: case WM_NCXBUTTONUP: case WM_NCXBUTTONDBLCLK: case WM_NCMOUSEHOVER: case WM_NCMOUSELEAVE: case WM_NCMOUSEMOVE: case WM_NCPOINTERUPDATE: case WM_NCPOINTERDOWN: case WM_NCPOINTERUP: if (App::wnd() && App::wnd()->psHwnd()) { if (msg == WM_NCLBUTTONDOWN) { ::SetForegroundWindow(App::wnd()->psHwnd()); } LRESULT res = SendMessage(App::wnd()->psHwnd(), msg, wParam, lParam); return res; } return 0; break; case WM_ACTIVATE: if (App::wnd() && App::wnd()->psHwnd() && wParam == WA_ACTIVE) { if ((HWND)lParam != App::wnd()->psHwnd()) { ::SetForegroundWindow(hwnd); ::SetWindowPos(App::wnd()->psHwnd(), hwnd, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); } } return DefWindowProc(hwnd, msg, wParam, lParam); break; default: return DefWindowProc(hwnd, msg, wParam, lParam); } return 0; } ComPtr<ITaskbarList3> taskbarList; bool handleSessionNotification = false; } // namespace UINT MainWindow::_taskbarCreatedMsgId = 0; MainWindow::MainWindow() : ps_tbHider_hWnd(createTaskbarHider()) { if (!_taskbarCreatedMsgId) { _taskbarCreatedMsgId = RegisterWindowMessage(L"TaskbarButtonCreated"); } subscribe(Window::Theme::Background(), [this](const Window::Theme::BackgroundUpdate &update) { if (update.paletteChanged()) { _psShadowWindows.setColor(st::windowShadowFg->c); } }); } void MainWindow::TaskbarCreated() { HRESULT hr = CoCreateInstance(CLSID_TaskbarList, nullptr, CLSCTX_ALL, IID_PPV_ARGS(&taskbarList)); if (!SUCCEEDED(hr)) { taskbarList.Reset(); } } void MainWindow::shadowsUpdate(ShadowsChanges changes, WINDOWPOS *position) { _psShadowWindows.update(changes, position); } void MainWindow::shadowsActivate() { // _psShadowWindows.setColor(_shActive); shadowsUpdate(ShadowsChange::Activate); } void MainWindow::shadowsDeactivate() { // _psShadowWindows.setColor(_shInactive); } void MainWindow::psShowTrayMenu() { trayIconMenu->popup(QCursor::pos()); } int32 MainWindow::screenNameChecksum(const QString &name) const { constexpr int DeviceNameSize = base::array_size(MONITORINFOEX().szDevice); wchar_t buffer[DeviceNameSize] = { 0 }; if (name.size() < DeviceNameSize) { name.toWCharArray(buffer); } else { memcpy(buffer, name.toStdWString().data(), sizeof(buffer)); } return hashCrc32(buffer, sizeof(buffer)); } void MainWindow::psRefreshTaskbarIcon() { auto refresher = object_ptr<QWidget>(this); auto guard = gsl::finally([&refresher] { refresher.destroy(); }); refresher->setWindowFlags(static_cast<Qt::WindowFlags>(Qt::Tool) | Qt::FramelessWindowHint); refresher->setGeometry(x() + 1, y() + 1, 1, 1); auto palette = refresher->palette(); palette.setColor(QPalette::Background, (isActiveWindow() ? st::titleBgActive : st::titleBg)->c); refresher->setPalette(palette); refresher->show(); refresher->activateWindow(); updateIconCounters(); } void MainWindow::psTrayMenuUpdated() { } void MainWindow::psSetupTrayIcon() { if (!trayIcon) { trayIcon = new QSystemTrayIcon(this); auto icon = QIcon(App::pixmapFromImageInPlace(Messenger::Instance().logoNoMargin())); trayIcon->setIcon(icon); trayIcon->setToolTip(str_const_toString(AppName)); connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(toggleTray(QSystemTrayIcon::ActivationReason)), Qt::UniqueConnection); connect(trayIcon, SIGNAL(messageClicked()), this, SLOT(showFromTray())); App::wnd()->updateTrayMenu(); } updateIconCounters(); trayIcon->show(); } void MainWindow::showTrayTooltip() { if (trayIcon && !cSeenTrayTooltip()) { trayIcon->showMessage(str_const_toString(AppName), lang(lng_tray_icon_text), QSystemTrayIcon::Information, 10000); cSetSeenTrayTooltip(true); Local::writeSettings(); } } void MainWindow::workmodeUpdated(DBIWorkMode mode) { switch (mode) { case dbiwmWindowAndTray: { psSetupTrayIcon(); HWND psOwner = (HWND)GetWindowLong(ps_hWnd, GWL_HWNDPARENT); if (psOwner) { SetWindowLong(ps_hWnd, GWL_HWNDPARENT, 0); psRefreshTaskbarIcon(); } } break; case dbiwmTrayOnly: { psSetupTrayIcon(); HWND psOwner = (HWND)GetWindowLong(ps_hWnd, GWL_HWNDPARENT); if (!psOwner) { SetWindowLong(ps_hWnd, GWL_HWNDPARENT, (LONG)ps_tbHider_hWnd); } } break; case dbiwmWindowOnly: { if (trayIcon) { trayIcon->setContextMenu(0); trayIcon->deleteLater(); } trayIcon = 0; HWND psOwner = (HWND)GetWindowLong(ps_hWnd, GWL_HWNDPARENT); if (psOwner) { SetWindowLong(ps_hWnd, GWL_HWNDPARENT, 0); psRefreshTaskbarIcon(); } } break; } } void MainWindow::unreadCounterChangedHook() { setWindowTitle(titleText()); updateIconCounters(); } void MainWindow::updateIconCounters() { auto counter = App::histories().unreadBadge(); auto muted = App::histories().unreadOnlyMuted(); auto iconSizeSmall = QSize(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON)); auto iconSizeBig = QSize(GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON)); auto &bg = (muted ? st::trayCounterBgMute : st::trayCounterBg); auto &fg = st::trayCounterFg; auto iconSmallPixmap16 = App::pixmapFromImageInPlace(iconWithCounter(16, counter, bg, fg, true)); auto iconSmallPixmap32 = App::pixmapFromImageInPlace(iconWithCounter(32, counter, bg, fg, true)); QIcon iconSmall, iconBig; iconSmall.addPixmap(iconSmallPixmap16); iconSmall.addPixmap(iconSmallPixmap32); iconBig.addPixmap(App::pixmapFromImageInPlace(iconWithCounter(32, taskbarList.Get() ? 0 : counter, bg, fg, false))); iconBig.addPixmap(App::pixmapFromImageInPlace(iconWithCounter(64, taskbarList.Get() ? 0 : counter, bg, fg, false))); if (trayIcon) { // Force Qt to use right icon size, not the larger one. QIcon forTrayIcon; forTrayIcon.addPixmap(iconSizeSmall.width() >= 20 ? iconSmallPixmap32 : iconSmallPixmap16); trayIcon->setIcon(forTrayIcon); } psDestroyIcons(); ps_iconSmall = createHIconFromQIcon(iconSmall, iconSizeSmall.width(), iconSizeSmall.height()); ps_iconBig = createHIconFromQIcon(iconBig, iconSizeBig.width(), iconSizeBig.height()); SendMessage(ps_hWnd, WM_SETICON, 0, (LPARAM)ps_iconSmall); SendMessage(ps_hWnd, WM_SETICON, 1, (LPARAM)(ps_iconBig ? ps_iconBig : ps_iconSmall)); if (taskbarList.Get()) { if (counter > 0) { QIcon iconOverlay; iconOverlay.addPixmap(App::pixmapFromImageInPlace(iconWithCounter(-16, counter, bg, fg, false))); iconOverlay.addPixmap(App::pixmapFromImageInPlace(iconWithCounter(-32, counter, bg, fg, false))); ps_iconOverlay = createHIconFromQIcon(iconOverlay, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON)); } auto description = (counter > 0) ? lng_unread_bar(lt_count, counter) : QString(); taskbarList->SetOverlayIcon(ps_hWnd, ps_iconOverlay, description.toStdWString().c_str()); } SetWindowPos(ps_hWnd, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); } void MainWindow::initHook() { auto platformInterface = QGuiApplication::platformNativeInterface(); ps_hWnd = static_cast<HWND>(platformInterface->nativeResourceForWindow(QByteArrayLiteral("handle"), windowHandle())); if (!ps_hWnd) return; handleSessionNotification = (Dlls::WTSRegisterSessionNotification != nullptr) && (Dlls::WTSUnRegisterSessionNotification != nullptr); if (handleSessionNotification) { Dlls::WTSRegisterSessionNotification(ps_hWnd, NOTIFY_FOR_THIS_SESSION); } psInitSysMenu(); } Q_DECLARE_METATYPE(QMargins); void MainWindow::psFirstShow() { _psShadowWindows.init(st::windowShadowFg->c); _shadowsWorking = true; psUpdateMargins(); shadowsUpdate(ShadowsChange::Hidden); bool showShadows = true; show(); if (cWindowPos().maximized) { DEBUG_LOG(("Window Pos: First show, setting maximized.")); setWindowState(Qt::WindowMaximized); } if ((cLaunchMode() == LaunchModeAutoStart && cStartMinimized() && !App::passcoded()) || cStartInTray()) { DEBUG_LOG(("Window Pos: First show, setting minimized after.")); setWindowState(Qt::WindowMinimized); if (Global::WorkMode().value() == dbiwmTrayOnly || Global::WorkMode().value() == dbiwmWindowAndTray) { hide(); } else { show(); } showShadows = false; } else { show(); } setPositionInited(); if (showShadows) { shadowsUpdate(ShadowsChange::Moved | ShadowsChange::Resized | ShadowsChange::Shown); } } void MainWindow::stateChangedHook(Qt::WindowState state) { updateSystemMenu(state); } void MainWindow::psInitSysMenu() { Qt::WindowStates states = windowState(); ps_menu = GetSystemMenu(ps_hWnd, FALSE); updateSystemMenu(windowHandle()->windowState()); } void MainWindow::updateSystemMenu(Qt::WindowState state) { if (!ps_menu) return; int menuToDisable = SC_RESTORE; if (state == Qt::WindowMaximized) { menuToDisable = SC_MAXIMIZE; } else if (state == Qt::WindowMinimized) { menuToDisable = SC_MINIMIZE; } int itemCount = GetMenuItemCount(ps_menu); for (int i = 0; i < itemCount; ++i) { MENUITEMINFO itemInfo = { 0 }; itemInfo.cbSize = sizeof(itemInfo); itemInfo.fMask = MIIM_TYPE | MIIM_STATE | MIIM_ID; if (GetMenuItemInfo(ps_menu, i, TRUE, &itemInfo)) { if (itemInfo.fType & MFT_SEPARATOR) { continue; } if (itemInfo.wID && !(itemInfo.fState & MFS_DEFAULT)) { UINT fOldState = itemInfo.fState, fState = itemInfo.fState & ~MFS_DISABLED; if (itemInfo.wID == SC_CLOSE) { fState |= MFS_DEFAULT; } else if (itemInfo.wID == menuToDisable || (itemInfo.wID != SC_MINIMIZE && itemInfo.wID != SC_MAXIMIZE && itemInfo.wID != SC_RESTORE)) { fState |= MFS_DISABLED; } itemInfo.fMask = MIIM_STATE; itemInfo.fState = fState; if (!SetMenuItemInfo(ps_menu, i, TRUE, &itemInfo)) { DEBUG_LOG(("PS Error: could not set state %1 to menu item %2, old state %3, error %4").arg(fState).arg(itemInfo.wID).arg(fOldState).arg(GetLastError())); DestroyMenu(ps_menu); ps_menu = 0; break; } } } else { DEBUG_LOG(("PS Error: could not get state, menu item %1 of %2, error %3").arg(i).arg(itemCount).arg(GetLastError())); DestroyMenu(ps_menu); ps_menu = 0; break; } } } void MainWindow::psUpdateMargins() { if (!ps_hWnd || _inUpdateMargins) return; _inUpdateMargins = true; RECT r, a; GetClientRect(ps_hWnd, &r); a = r; LONG style = GetWindowLong(ps_hWnd, GWL_STYLE), styleEx = GetWindowLong(ps_hWnd, GWL_EXSTYLE); AdjustWindowRectEx(&a, style, false, styleEx); QMargins margins = QMargins(a.left - r.left, a.top - r.top, r.right - a.right, r.bottom - a.bottom); if (style & WS_MAXIMIZE) { RECT w, m; GetWindowRect(ps_hWnd, &w); m = w; HMONITOR hMonitor = MonitorFromRect(&w, MONITOR_DEFAULTTONEAREST); if (hMonitor) { MONITORINFO mi; mi.cbSize = sizeof(mi); GetMonitorInfo(hMonitor, &mi); m = mi.rcWork; } _deltaLeft = w.left - m.left; _deltaTop = w.top - m.top; _deltaRight = m.right - w.right; _deltaBottom = m.bottom - w.bottom; margins.setLeft(margins.left() - _deltaLeft); margins.setRight(margins.right() - _deltaRight); margins.setBottom(margins.bottom() - _deltaBottom); margins.setTop(margins.top() - _deltaTop); } else if (_deltaLeft != 0 || _deltaTop != 0 || _deltaRight != 0 || _deltaBottom != 0) { RECT w; GetWindowRect(ps_hWnd, &w); SetWindowPos(ps_hWnd, 0, 0, 0, w.right - w.left - _deltaLeft - _deltaRight, w.bottom - w.top - _deltaBottom - _deltaTop, SWP_NOMOVE | SWP_NOSENDCHANGING | SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREPOSITION); _deltaLeft = _deltaTop = _deltaRight = _deltaBottom = 0; } QPlatformNativeInterface *i = QGuiApplication::platformNativeInterface(); i->setWindowProperty(windowHandle()->handle(), qsl("WindowsCustomMargins"), QVariant::fromValue<QMargins>(margins)); if (!_themeInited) { _themeInited = true; if (QSysInfo::WindowsVersion < QSysInfo::WV_WINDOWS8) { if (Dlls::SetWindowTheme != nullptr) { Dlls::SetWindowTheme(ps_hWnd, L" ", L" "); QApplication::setStyle(QStyleFactory::create(qsl("Windows"))); } } } _inUpdateMargins = false; } HWND MainWindow::psHwnd() const { return ps_hWnd; } HMENU MainWindow::psMenu() const { return ps_menu; } void MainWindow::psDestroyIcons() { if (ps_iconBig) { DestroyIcon(ps_iconBig); ps_iconBig = 0; } if (ps_iconSmall) { DestroyIcon(ps_iconSmall); ps_iconSmall = 0; } if (ps_iconOverlay) { DestroyIcon(ps_iconOverlay); ps_iconOverlay = 0; } } MainWindow::~MainWindow() { if (handleSessionNotification) { QPlatformNativeInterface *i = QGuiApplication::platformNativeInterface(); if (HWND hWnd = static_cast<HWND>(i->nativeResourceForWindow(QByteArrayLiteral("handle"), windowHandle()))) { Dlls::WTSUnRegisterSessionNotification(hWnd); } } if (taskbarList) taskbarList.Reset(); _shadowsWorking = false; if (ps_menu) DestroyMenu(ps_menu); psDestroyIcons(); _psShadowWindows.destroy(); if (ps_tbHider_hWnd) DestroyWindow(ps_tbHider_hWnd); } } // namespace Platform ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/win/main_window_win.h���������������������������������0000664�0000000�0000000�00000005154�13262451251�0025412�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "platform/platform_main_window.h" #include "base/flags.h" #include <windows.h> namespace Ui { class PopupMenu; } // namespace Ui namespace Platform { class MainWindow : public Window::MainWindow { Q_OBJECT public: MainWindow(); HWND psHwnd() const; HMENU psMenu() const; void psFirstShow(); void psInitSysMenu(); void updateSystemMenu(Qt::WindowState state); void psUpdateMargins(); void psRefreshTaskbarIcon(); virtual QImage iconWithCounter(int size, int count, style::color bg, style::color fg, bool smallIcon) = 0; static UINT TaskbarCreatedMsgId() { return _taskbarCreatedMsgId; } static void TaskbarCreated(); // Custom shadows. enum class ShadowsChange { Moved = (1 << 0), Resized = (1 << 1), Shown = (1 << 2), Hidden = (1 << 3), Activate = (1 << 4), }; using ShadowsChanges = base::flags<ShadowsChange>; friend inline constexpr auto is_flag_type(ShadowsChange) { return true; }; bool shadowsWorking() const { return _shadowsWorking; } void shadowsActivate(); void shadowsDeactivate(); void shadowsUpdate(ShadowsChanges changes, WINDOWPOS *position = nullptr); int deltaLeft() const { return _deltaLeft; } int deltaTop() const { return _deltaTop; } ~MainWindow(); public slots: void psShowTrayMenu(); protected: void initHook() override; int32 screenNameChecksum(const QString &name) const override; void unreadCounterChangedHook() override; void stateChangedHook(Qt::WindowState state) override; bool hasTrayIcon() const override { return trayIcon; } QSystemTrayIcon *trayIcon = nullptr; Ui::PopupMenu *trayIconMenu = nullptr; void psTrayMenuUpdated(); void psSetupTrayIcon(); virtual void placeSmallCounter(QImage &img, int size, int count, style::color bg, const QPoint &shift, style::color color) = 0; void showTrayTooltip() override; void workmodeUpdated(DBIWorkMode mode) override; QTimer psUpdatedPositionTimer; private: void updateIconCounters(); void psDestroyIcons(); static UINT _taskbarCreatedMsgId; bool _shadowsWorking = false; bool _themeInited = false; bool _inUpdateMargins = false; HWND ps_hWnd = nullptr; HWND ps_tbHider_hWnd = nullptr; HMENU ps_menu = nullptr; HICON ps_iconBig = nullptr; HICON ps_iconSmall = nullptr; HICON ps_iconOverlay = nullptr; int _deltaLeft = 0; int _deltaTop = 0; int _deltaRight = 0; int _deltaBottom = 0; }; } // namespace Platform ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/win/notifications_manager_win.cpp���������������������0000664�0000000�0000000�00000047560�13262451251�0030004�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "platform/win/notifications_manager_win.h" #include "window/notifications_utilities.h" #include "platform/win/windows_app_user_model_id.h" #include "platform/win/windows_event_filter.h" #include "platform/win/windows_dlls.h" #include "history/history.h" #include "mainwindow.h" #include <Shobjidl.h> #include <shellapi.h> #include <roapi.h> #include <wrl\client.h> #include <wrl\implements.h> #include <windows.ui.notifications.h> #include <strsafe.h> #include <intsafe.h> HICON qt_pixmapToWinHICON(const QPixmap &); using namespace Microsoft::WRL; using namespace ABI::Windows::UI::Notifications; using namespace ABI::Windows::Data::Xml::Dom; using namespace Windows::Foundation; namespace Platform { namespace Notifications { namespace { class StringReferenceWrapper { public: StringReferenceWrapper(_In_reads_(length) PCWSTR stringRef, _In_ UINT32 length) throw() { HRESULT hr = Dlls::WindowsCreateStringReference(stringRef, length, &_header, &_hstring); if (!SUCCEEDED(hr)) { RaiseException(static_cast<DWORD>(STATUS_INVALID_PARAMETER), EXCEPTION_NONCONTINUABLE, 0, nullptr); } } ~StringReferenceWrapper() { Dlls::WindowsDeleteString(_hstring); } template <size_t N> StringReferenceWrapper(_In_reads_(N) wchar_t const (&stringRef)[N]) throw() { UINT32 length = N - 1; HRESULT hr = Dlls::WindowsCreateStringReference(stringRef, length, &_header, &_hstring); if (!SUCCEEDED(hr)) { RaiseException(static_cast<DWORD>(STATUS_INVALID_PARAMETER), EXCEPTION_NONCONTINUABLE, 0, nullptr); } } template <size_t _> StringReferenceWrapper(_In_reads_(_) wchar_t(&stringRef)[_]) throw() { UINT32 length; HRESULT hr = SizeTToUInt32(wcslen(stringRef), &length); if (!SUCCEEDED(hr)) { RaiseException(static_cast<DWORD>(STATUS_INVALID_PARAMETER), EXCEPTION_NONCONTINUABLE, 0, nullptr); } Dlls::WindowsCreateStringReference(stringRef, length, &_header, &_hstring); } HSTRING Get() const throw() { return _hstring; } private: HSTRING _hstring; HSTRING_HEADER _header; }; template<class T> _Check_return_ __inline HRESULT _1_GetActivationFactory(_In_ HSTRING activatableClassId, _COM_Outptr_ T** factory) { return Dlls::RoGetActivationFactory(activatableClassId, IID_INS_ARGS(factory)); } template<typename T> inline HRESULT wrap_GetActivationFactory(_In_ HSTRING activatableClassId, _Inout_ Details::ComPtrRef<T> factory) throw() { return _1_GetActivationFactory(activatableClassId, factory.ReleaseAndGetAddressOf()); } bool init() { if (QSysInfo::windowsVersion() < QSysInfo::WV_WINDOWS8) { return false; } if ((Dlls::SetCurrentProcessExplicitAppUserModelID == nullptr) || (Dlls::PropVariantToString == nullptr) || (Dlls::RoGetActivationFactory == nullptr) || (Dlls::WindowsCreateStringReference == nullptr) || (Dlls::WindowsDeleteString == nullptr)) { return false; } if (!AppUserModelId::validateShortcut()) { return false; } auto appUserModelId = AppUserModelId::getId(); if (!SUCCEEDED(Dlls::SetCurrentProcessExplicitAppUserModelID(appUserModelId))) { return false; } return true; } HRESULT SetNodeValueString(_In_ HSTRING inputString, _In_ IXmlNode *node, _In_ IXmlDocument *xml) { ComPtr<IXmlText> inputText; HRESULT hr = xml->CreateTextNode(inputString, &inputText); if (!SUCCEEDED(hr)) return hr; ComPtr<IXmlNode> inputTextNode; hr = inputText.As(&inputTextNode); if (!SUCCEEDED(hr)) return hr; ComPtr<IXmlNode> pAppendedChild; return node->AppendChild(inputTextNode.Get(), &pAppendedChild); } HRESULT SetAudioSilent(_In_ IXmlDocument *toastXml) { ComPtr<IXmlNodeList> nodeList; HRESULT hr = toastXml->GetElementsByTagName(StringReferenceWrapper(L"audio").Get(), &nodeList); if (!SUCCEEDED(hr)) return hr; ComPtr<IXmlNode> audioNode; hr = nodeList->Item(0, &audioNode); if (!SUCCEEDED(hr)) return hr; if (audioNode) { ComPtr<IXmlElement> audioElement; hr = audioNode.As(&audioElement); if (!SUCCEEDED(hr)) return hr; hr = audioElement->SetAttribute(StringReferenceWrapper(L"silent").Get(), StringReferenceWrapper(L"true").Get()); if (!SUCCEEDED(hr)) return hr; } else { ComPtr<IXmlElement> audioElement; hr = toastXml->CreateElement(StringReferenceWrapper(L"audio").Get(), &audioElement); if (!SUCCEEDED(hr)) return hr; hr = audioElement->SetAttribute(StringReferenceWrapper(L"silent").Get(), StringReferenceWrapper(L"true").Get()); if (!SUCCEEDED(hr)) return hr; ComPtr<IXmlNode> audioNode; hr = audioElement.As(&audioNode); if (!SUCCEEDED(hr)) return hr; ComPtr<IXmlNodeList> nodeList; hr = toastXml->GetElementsByTagName(StringReferenceWrapper(L"toast").Get(), &nodeList); if (!SUCCEEDED(hr)) return hr; ComPtr<IXmlNode> toastNode; hr = nodeList->Item(0, &toastNode); if (!SUCCEEDED(hr)) return hr; ComPtr<IXmlNode> appendedNode; hr = toastNode->AppendChild(audioNode.Get(), &appendedNode); } return hr; } HRESULT SetImageSrc(_In_z_ const wchar_t *imagePath, _In_ IXmlDocument *toastXml) { wchar_t imageSrc[MAX_PATH] = L"file:///"; HRESULT hr = StringCchCat(imageSrc, ARRAYSIZE(imageSrc), imagePath); if (!SUCCEEDED(hr)) return hr; ComPtr<IXmlNodeList> nodeList; hr = toastXml->GetElementsByTagName(StringReferenceWrapper(L"image").Get(), &nodeList); if (!SUCCEEDED(hr)) return hr; ComPtr<IXmlNode> imageNode; hr = nodeList->Item(0, &imageNode); if (!SUCCEEDED(hr)) return hr; ComPtr<IXmlNamedNodeMap> attributes; hr = imageNode->get_Attributes(&attributes); if (!SUCCEEDED(hr)) return hr; ComPtr<IXmlNode> srcAttribute; hr = attributes->GetNamedItem(StringReferenceWrapper(L"src").Get(), &srcAttribute); if (!SUCCEEDED(hr)) return hr; return SetNodeValueString(StringReferenceWrapper(imageSrc).Get(), srcAttribute.Get(), toastXml); } typedef ABI::Windows::Foundation::ITypedEventHandler<ToastNotification*, ::IInspectable *> DesktopToastActivatedEventHandler; typedef ABI::Windows::Foundation::ITypedEventHandler<ToastNotification*, ToastDismissedEventArgs*> DesktopToastDismissedEventHandler; typedef ABI::Windows::Foundation::ITypedEventHandler<ToastNotification*, ToastFailedEventArgs*> DesktopToastFailedEventHandler; class ToastEventHandler : public Implements< DesktopToastActivatedEventHandler, DesktopToastDismissedEventHandler, DesktopToastFailedEventHandler> { public: // We keep a weak pointer to a member field of native notifications manager. ToastEventHandler( const std::shared_ptr<Manager*> &guarded, const PeerId &peer, MsgId msg) : _peerId(peer) , _msgId(msg) , _weak(guarded) { } ~ToastEventHandler() = default; void performOnMainQueue(base::lambda_once<void(Manager *manager)> task) { const auto weak = _weak; crl::on_main(weak, [=, task = std::move(task)]() mutable { task(*weak.lock()); }); } // DesktopToastActivatedEventHandler IFACEMETHODIMP Invoke(_In_ IToastNotification *sender, _In_ IInspectable* args) { performOnMainQueue([peerId = _peerId, msgId = _msgId](Manager *manager) { manager->notificationActivated(peerId, msgId); }); return S_OK; } // DesktopToastDismissedEventHandler IFACEMETHODIMP Invoke(_In_ IToastNotification *sender, _In_ IToastDismissedEventArgs *e) { ToastDismissalReason tdr; if (SUCCEEDED(e->get_Reason(&tdr))) { switch (tdr) { case ToastDismissalReason_ApplicationHidden: break; case ToastDismissalReason_UserCanceled: case ToastDismissalReason_TimedOut: default: performOnMainQueue([peerId = _peerId, msgId = _msgId](Manager *manager) { manager->clearNotification(peerId, msgId); }); break; } } return S_OK; } // DesktopToastFailedEventHandler IFACEMETHODIMP Invoke(_In_ IToastNotification *sender, _In_ IToastFailedEventArgs *e) { performOnMainQueue([peerId = _peerId, msgId = _msgId](Manager *manager) { manager->clearNotification(peerId, msgId); }); return S_OK; } // IUnknown IFACEMETHODIMP_(ULONG) AddRef() { return InterlockedIncrement(&_refCount); } IFACEMETHODIMP_(ULONG) Release() { auto refCount = InterlockedDecrement(&_refCount); if (refCount == 0) { delete this; } return refCount; } IFACEMETHODIMP QueryInterface(_In_ REFIID riid, _COM_Outptr_ void **ppv) { if (IsEqualIID(riid, IID_IUnknown)) *ppv = static_cast<IUnknown*>(static_cast<DesktopToastActivatedEventHandler*>(this)); else if (IsEqualIID(riid, __uuidof(DesktopToastActivatedEventHandler))) *ppv = static_cast<DesktopToastActivatedEventHandler*>(this); else if (IsEqualIID(riid, __uuidof(DesktopToastDismissedEventHandler))) *ppv = static_cast<DesktopToastDismissedEventHandler*>(this); else if (IsEqualIID(riid, __uuidof(DesktopToastFailedEventHandler))) *ppv = static_cast<DesktopToastFailedEventHandler*>(this); else *ppv = nullptr; if (*ppv) { reinterpret_cast<IUnknown*>(*ppv)->AddRef(); return S_OK; } return E_NOINTERFACE; } private: ULONG _refCount = 0; PeerId _peerId = 0; MsgId _msgId = 0; std::weak_ptr<Manager*> _weak; }; auto Checked = false; auto InitSucceeded = false; void Check() { InitSucceeded = init(); } } // namespace bool Supported() { if (!Checked) { Checked = true; Check(); } return InitSucceeded; } std::unique_ptr<Window::Notifications::Manager> Create(Window::Notifications::System *system) { if (Global::NativeNotifications() && Supported()) { auto result = std::make_unique<Manager>(system); if (result->init()) { return std::move(result); } } return nullptr; } void FlashBounce() { auto window = App::wnd(); if (!window || GetForegroundWindow() == window->psHwnd()) { return; } FLASHWINFO info; info.cbSize = sizeof(info); info.hwnd = window->psHwnd(); info.dwFlags = FLASHW_ALL; info.dwTimeout = 0; info.uCount = 1; FlashWindowEx(&info); } class Manager::Private { public: using Type = Window::Notifications::CachedUserpics::Type; explicit Private(Manager *instance, Type type); bool init(); bool showNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, const QString &msg, bool hideNameAndPhoto, bool hideReplyButton); void clearAll(); void clearFromHistory(History *history); void beforeNotificationActivated(PeerId peerId, MsgId msgId); void afterNotificationActivated(PeerId peerId, MsgId msgId); void clearNotification(PeerId peerId, MsgId msgId); ~Private(); private: Window::Notifications::CachedUserpics _cachedUserpics; std::shared_ptr<Manager*> _guarded; ComPtr<IToastNotificationManagerStatics> _notificationManager; ComPtr<IToastNotifier> _notifier; ComPtr<IToastNotificationFactory> _notificationFactory; struct NotificationPtr { NotificationPtr() { } NotificationPtr(const ComPtr<IToastNotification> &ptr) : p(ptr) { } ComPtr<IToastNotification> p; }; QMap<PeerId, QMap<MsgId, NotificationPtr>> _notifications; }; Manager::Private::Private(Manager *instance, Type type) : _guarded(std::make_shared<Manager*>(instance)) , _cachedUserpics(type) { } bool Manager::Private::init() { if (!SUCCEEDED(wrap_GetActivationFactory(StringReferenceWrapper(RuntimeClass_Windows_UI_Notifications_ToastNotificationManager).Get(), &_notificationManager))) { return false; } auto appUserModelId = AppUserModelId::getId(); if (!SUCCEEDED(_notificationManager->CreateToastNotifierWithId(StringReferenceWrapper(appUserModelId, wcslen(appUserModelId)).Get(), &_notifier))) { return false; } if (!SUCCEEDED(wrap_GetActivationFactory(StringReferenceWrapper(RuntimeClass_Windows_UI_Notifications_ToastNotification).Get(), &_notificationFactory))) { return false; } return true; } Manager::Private::~Private() { clearAll(); _notifications.clear(); if (_notificationManager) _notificationManager.Reset(); if (_notifier) _notifier.Reset(); if (_notificationFactory) _notificationFactory.Reset(); } void Manager::Private::clearAll() { if (!_notifier) return; auto temp = base::take(_notifications); for_const (auto ¬ifications, temp) { for_const (auto ¬ification, notifications) { _notifier->Hide(notification.p.Get()); } } } void Manager::Private::clearFromHistory(History *history) { if (!_notifier) return; auto i = _notifications.find(history->peer->id); if (i != _notifications.cend()) { auto temp = base::take(i.value()); _notifications.erase(i); for_const (auto ¬ification, temp) { _notifier->Hide(notification.p.Get()); } } } void Manager::Private::beforeNotificationActivated(PeerId peerId, MsgId msgId) { clearNotification(peerId, msgId); } void Manager::Private::afterNotificationActivated(PeerId peerId, MsgId msgId) { if (auto window = App::wnd()) { SetForegroundWindow(window->psHwnd()); } } void Manager::Private::clearNotification(PeerId peerId, MsgId msgId) { auto i = _notifications.find(peerId); if (i != _notifications.cend()) { i.value().remove(msgId); if (i.value().isEmpty()) { _notifications.erase(i); } } } bool Manager::Private::showNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, const QString &msg, bool hideNameAndPhoto, bool hideReplyButton) { if (!_notificationManager || !_notifier || !_notificationFactory) return false; ComPtr<IXmlDocument> toastXml; bool withSubtitle = !subtitle.isEmpty(); HRESULT hr = _notificationManager->GetTemplateContent(withSubtitle ? ToastTemplateType_ToastImageAndText04 : ToastTemplateType_ToastImageAndText02, &toastXml); if (!SUCCEEDED(hr)) return false; hr = SetAudioSilent(toastXml.Get()); if (!SUCCEEDED(hr)) return false; StorageKey key; if (hideNameAndPhoto) { key = StorageKey(0, 0); } else { key = peer->userpicUniqueKey(); } auto userpicPath = _cachedUserpics.get(key, peer); auto userpicPathWide = QDir::toNativeSeparators(userpicPath).toStdWString(); hr = SetImageSrc(userpicPathWide.c_str(), toastXml.Get()); if (!SUCCEEDED(hr)) return false; ComPtr<IXmlNodeList> nodeList; hr = toastXml->GetElementsByTagName(StringReferenceWrapper(L"text").Get(), &nodeList); if (!SUCCEEDED(hr)) return false; UINT32 nodeListLength; hr = nodeList->get_Length(&nodeListLength); if (!SUCCEEDED(hr)) return false; if (nodeListLength < (withSubtitle ? 3U : 2U)) return false; { ComPtr<IXmlNode> textNode; hr = nodeList->Item(0, &textNode); if (!SUCCEEDED(hr)) return false; std::wstring wtitle = title.toStdWString(); hr = SetNodeValueString(StringReferenceWrapper(wtitle.data(), wtitle.size()).Get(), textNode.Get(), toastXml.Get()); if (!SUCCEEDED(hr)) return false; } if (withSubtitle) { ComPtr<IXmlNode> textNode; hr = nodeList->Item(1, &textNode); if (!SUCCEEDED(hr)) return false; std::wstring wsubtitle = subtitle.toStdWString(); hr = SetNodeValueString(StringReferenceWrapper(wsubtitle.data(), wsubtitle.size()).Get(), textNode.Get(), toastXml.Get()); if (!SUCCEEDED(hr)) return false; } { ComPtr<IXmlNode> textNode; hr = nodeList->Item(withSubtitle ? 2 : 1, &textNode); if (!SUCCEEDED(hr)) return false; std::wstring wmsg = msg.toStdWString(); hr = SetNodeValueString(StringReferenceWrapper(wmsg.data(), wmsg.size()).Get(), textNode.Get(), toastXml.Get()); if (!SUCCEEDED(hr)) return false; } ComPtr<IToastNotification> toast; hr = _notificationFactory->CreateToastNotification(toastXml.Get(), &toast); if (!SUCCEEDED(hr)) return false; EventRegistrationToken activatedToken, dismissedToken, failedToken; ComPtr<ToastEventHandler> eventHandler(new ToastEventHandler(_guarded, peer->id, msgId)); hr = toast->add_Activated(eventHandler.Get(), &activatedToken); if (!SUCCEEDED(hr)) return false; hr = toast->add_Dismissed(eventHandler.Get(), &dismissedToken); if (!SUCCEEDED(hr)) return false; hr = toast->add_Failed(eventHandler.Get(), &failedToken); if (!SUCCEEDED(hr)) return false; auto i = _notifications.find(peer->id); if (i != _notifications.cend()) { auto j = i->find(msgId); if (j != i->cend()) { ComPtr<IToastNotification> notify = j->p; i->erase(j); _notifier->Hide(notify.Get()); i = _notifications.find(peer->id); } } if (i == _notifications.cend()) { i = _notifications.insert(peer->id, QMap<MsgId, NotificationPtr>()); } hr = _notifier->Show(toast.Get()); if (!SUCCEEDED(hr)) { i = _notifications.find(peer->id); if (i != _notifications.cend() && i->isEmpty()) _notifications.erase(i); return false; } _notifications[peer->id].insert(msgId, toast); return true; } Manager::Manager(Window::Notifications::System *system) : NativeManager(system) , _private(std::make_unique<Private>(this, Private::Type::Rounded)) { } bool Manager::init() { return _private->init(); } void Manager::clearNotification(PeerId peerId, MsgId msgId) { _private->clearNotification(peerId, msgId); } Manager::~Manager() = default; void Manager::doShowNativeNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, const QString &msg, bool hideNameAndPhoto, bool hideReplyButton) { _private->showNotification(peer, msgId, title, subtitle, msg, hideNameAndPhoto, hideReplyButton); } void Manager::doClearAllFast() { _private->clearAll(); } void Manager::doClearFromHistory(History *history) { _private->clearFromHistory(history); } void Manager::onBeforeNotificationActivated(PeerId peerId, MsgId msgId) { _private->beforeNotificationActivated(peerId, msgId); } void Manager::onAfterNotificationActivated(PeerId peerId, MsgId msgId) { _private->afterNotificationActivated(peerId, msgId); } namespace { bool QuietHoursEnabled = false; DWORD QuietHoursValue = 0; // Thanks https://stackoverflow.com/questions/35600128/get-windows-quiet-hours-from-win32-or-c-sharp-api void queryQuietHours() { if (QSysInfo::windowsVersion() < QSysInfo::WV_WINDOWS10) { // There are quiet hours in Windows starting from Windows 8.1 // But there were several reports about the notifications being shut // down according to the registry while no quiet hours were enabled. // So we try this method only starting with Windows 10. return; } LPCWSTR lpKeyName = L"Software\\Microsoft\\Windows\\CurrentVersion\\Notifications\\Settings"; LPCWSTR lpValueName = L"NOC_GLOBAL_SETTING_TOASTS_ENABLED"; HKEY key; auto result = RegOpenKeyEx(HKEY_CURRENT_USER, lpKeyName, 0, KEY_READ, &key); if (result != ERROR_SUCCESS) { return; } DWORD value = 0, type = 0, size = sizeof(value); result = RegQueryValueEx(key, lpValueName, 0, &type, (LPBYTE)&value, &size); RegCloseKey(key); auto quietHoursEnabled = (result == ERROR_SUCCESS) && (value == 0); if (QuietHoursEnabled != quietHoursEnabled) { QuietHoursEnabled = quietHoursEnabled; QuietHoursValue = value; LOG(("Quiet hours changed, entry value: %1").arg(value)); } else if (QuietHoursValue != value) { QuietHoursValue = value; LOG(("Quiet hours value changed, was value: %1, entry value: %2").arg(QuietHoursValue).arg(value)); } } QUERY_USER_NOTIFICATION_STATE UserNotificationState = QUNS_ACCEPTS_NOTIFICATIONS; void queryUserNotificationState() { if (Dlls::SHQueryUserNotificationState != nullptr) { QUERY_USER_NOTIFICATION_STATE state; if (SUCCEEDED(Dlls::SHQueryUserNotificationState(&state))) { UserNotificationState = state; } } } static constexpr auto kQuerySettingsEachMs = 1000; TimeMs LastSettingsQueryMs = 0; void querySystemNotificationSettings() { auto ms = getms(true); if (LastSettingsQueryMs > 0 && ms <= LastSettingsQueryMs + kQuerySettingsEachMs) { return; } LastSettingsQueryMs = ms; queryQuietHours(); queryUserNotificationState(); } } // namespace bool SkipAudio() { querySystemNotificationSettings(); if (UserNotificationState == QUNS_NOT_PRESENT || UserNotificationState == QUNS_PRESENTATION_MODE) { return true; } if (QuietHoursEnabled) { return true; } if (EventFilter::getInstance()->sessionLoggedOff()) { return true; } return false; } bool SkipToast() { querySystemNotificationSettings(); if (UserNotificationState == QUNS_PRESENTATION_MODE || UserNotificationState == QUNS_RUNNING_D3D_FULL_SCREEN/* || UserNotificationState == QUNS_BUSY*/) { return true; } if (QuietHoursEnabled) { return true; } return false; } } // namespace Notifications } // namespace Platform ������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/win/notifications_manager_win.h�����������������������0000664�0000000�0000000�00000002127�13262451251�0027437�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "platform/platform_notifications_manager.h" namespace Platform { namespace Notifications { class Manager : public Window::Notifications::NativeManager { public: Manager(Window::Notifications::System *system); bool init(); void clearNotification(PeerId peerId, MsgId msgId); ~Manager(); protected: void doShowNativeNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, const QString &msg, bool hideNameAndPhoto, bool hideReplyButton) override; void doClearAllFast() override; void doClearFromHistory(History *history) override; void onBeforeNotificationActivated(PeerId peerId, MsgId msgId) override; void onAfterNotificationActivated(PeerId peerId, MsgId msgId) override; private: class Private; const std::unique_ptr<Private> _private; }; } // namespace Notifications } // namespace Platform �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/win/specific_win.cpp����������������������������������0000664�0000000�0000000�00000123356�13262451251�0025224�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "platform/win/specific_win.h" #include "platform/win/main_window_win.h" #include "platform/win/notifications_manager_win.h" #include "platform/win/windows_app_user_model_id.h" #include "platform/win/windows_dlls.h" #include "platform/win/windows_event_filter.h" #include "lang/lang_keys.h" #include "mainwindow.h" #include "mainwidget.h" #include "history/history_location_manager.h" #include "storage/localstorage.h" #include "passcodewidget.h" #include "core/crash_reports.h" #include <Shobjidl.h> #include <shellapi.h> #include <roapi.h> #include <wrl\client.h> #include <wrl\implements.h> #include <windows.ui.notifications.h> #pragma warning(push) #pragma warning(disable:4091) #include <dbghelp.h> #include <shlobj.h> #pragma warning(pop) #include <Shlwapi.h> #include <Strsafe.h> #include <Windowsx.h> #include <WtsApi32.h> #include <SDKDDKVer.h> #include <sal.h> #include <Psapi.h> #include <strsafe.h> #include <ObjBase.h> #include <propvarutil.h> #include <functiondiscoverykeys.h> #include <intsafe.h> #include <guiddef.h> #include <qpa/qplatformnativeinterface.h> #ifndef DCX_USESTYLE #define DCX_USESTYLE 0x00010000 #endif #ifndef WM_NCPOINTERUPDATE #define WM_NCPOINTERUPDATE 0x0241 #define WM_NCPOINTERDOWN 0x0242 #define WM_NCPOINTERUP 0x0243 #endif using namespace Microsoft::WRL; using namespace ABI::Windows::UI::Notifications; using namespace ABI::Windows::Data::Xml::Dom; using namespace Windows::Foundation; using namespace Platform; namespace { QStringList _initLogs; bool themeInited = false; bool finished = true; QMargins simpleMargins, margins; HICON bigIcon = 0, smallIcon = 0, overlayIcon = 0; class _PsInitializer { public: _PsInitializer() { Dlls::start(); } }; _PsInitializer _psInitializer; }; QAbstractNativeEventFilter *psNativeEventFilter() { return EventFilter::createInstance(); } void psDeleteDir(const QString &dir) { std::wstring wDir = QDir::toNativeSeparators(dir).toStdWString(); WCHAR path[4096]; memcpy(path, wDir.c_str(), (wDir.size() + 1) * sizeof(WCHAR)); path[wDir.size() + 1] = 0; SHFILEOPSTRUCT file_op = { NULL, FO_DELETE, path, L"", FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT, false, 0, L"" }; int res = SHFileOperation(&file_op); } namespace { BOOL CALLBACK _ActivateProcess(HWND hWnd, LPARAM lParam) { uint64 &processId(*(uint64*)lParam); DWORD dwProcessId; ::GetWindowThreadProcessId(hWnd, &dwProcessId); if ((uint64)dwProcessId == processId) { // found top-level window static const int32 nameBufSize = 1024; WCHAR nameBuf[nameBufSize]; int32 len = GetWindowText(hWnd, nameBuf, nameBufSize); if (len && len < nameBufSize) { if (QRegularExpression(qsl("^Telegram(\\s*\\(\\d+\\))?$")).match(QString::fromStdWString(nameBuf)).hasMatch()) { BOOL res = ::SetForegroundWindow(hWnd); ::SetFocus(hWnd); return FALSE; } } } return TRUE; } } namespace { TimeMs _lastUserAction = 0; } // namespace void psUserActionDone() { _lastUserAction = getms(true); EventFilter::getInstance()->setSessionLoggedOff(false); } bool psIdleSupported() { LASTINPUTINFO lii; lii.cbSize = sizeof(LASTINPUTINFO); return GetLastInputInfo(&lii); } TimeMs psIdleTime() { LASTINPUTINFO lii; lii.cbSize = sizeof(LASTINPUTINFO); return GetLastInputInfo(&lii) ? (GetTickCount() - lii.dwTime) : (getms(true) - _lastUserAction); } QStringList psInitLogs() { return _initLogs; } void psClearInitLogs() { _initLogs = QStringList(); } void psActivateProcess(uint64 pid) { if (pid) { ::EnumWindows((WNDENUMPROC)_ActivateProcess, (LPARAM)&pid); } } QString psAppDataPath() { static const int maxFileLen = MAX_PATH * 10; WCHAR wstrPath[maxFileLen]; if (GetEnvironmentVariable(L"APPDATA", wstrPath, maxFileLen)) { QDir appData(QString::fromStdWString(std::wstring(wstrPath))); #ifdef OS_WIN_STORE return appData.absolutePath() + qsl("/Telegram Desktop UWP/"); #else // OS_WIN_STORE return appData.absolutePath() + '/' + str_const_toString(AppName) + '/'; #endif // OS_WIN_STORE } return QString(); } QString psAppDataPathOld() { static const int maxFileLen = MAX_PATH * 10; WCHAR wstrPath[maxFileLen]; if (GetEnvironmentVariable(L"APPDATA", wstrPath, maxFileLen)) { QDir appData(QString::fromStdWString(std::wstring(wstrPath))); return appData.absolutePath() + '/' + str_const_toString(AppNameOld) + '/'; } return QString(); } QString psDownloadPath() { return QStandardPaths::writableLocation(QStandardPaths::DownloadLocation) + '/' + str_const_toString(AppName) + '/'; } void psDoCleanup() { try { psAutoStart(false, true); psSendToMenu(false, true); AppUserModelId::cleanupShortcut(); } catch (...) { } } namespace { QRect _monitorRect; TimeMs _monitorLastGot = 0; } // namespace QRect psDesktopRect() { auto tnow = getms(); if (tnow > _monitorLastGot + 1000LL || tnow < _monitorLastGot) { _monitorLastGot = tnow; HMONITOR hMonitor = MonitorFromWindow(App::wnd()->psHwnd(), MONITOR_DEFAULTTONEAREST); if (hMonitor) { MONITORINFOEX info; info.cbSize = sizeof(info); GetMonitorInfo(hMonitor, &info); _monitorRect = QRect(info.rcWork.left, info.rcWork.top, info.rcWork.right - info.rcWork.left, info.rcWork.bottom - info.rcWork.top); } else { _monitorRect = QApplication::desktop()->availableGeometry(App::wnd()); } } return _monitorRect; } void psShowOverAll(QWidget *w, bool canFocus) { } void psBringToBack(QWidget *w) { } int psCleanup() { __try { psDoCleanup(); } __except(EXCEPTION_EXECUTE_HANDLER) { return 0; } return 0; } void psDoFixPrevious() { try { static const int bufSize = 4096; DWORD checkType, checkSize = bufSize * 2; WCHAR checkStr[bufSize]; QString appId = str_const_toString(AppId); QString newKeyStr1 = QString("Software\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\%1_is1").arg(appId); QString newKeyStr2 = QString("Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\%1_is1").arg(appId); QString oldKeyStr1 = QString("SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\%1_is1").arg(appId); QString oldKeyStr2 = QString("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\%1_is1").arg(appId); HKEY newKey1, newKey2, oldKey1, oldKey2; LSTATUS newKeyRes1 = RegOpenKeyEx(HKEY_CURRENT_USER, newKeyStr1.toStdWString().c_str(), 0, KEY_READ, &newKey1); LSTATUS newKeyRes2 = RegOpenKeyEx(HKEY_CURRENT_USER, newKeyStr2.toStdWString().c_str(), 0, KEY_READ, &newKey2); LSTATUS oldKeyRes1 = RegOpenKeyEx(HKEY_LOCAL_MACHINE, oldKeyStr1.toStdWString().c_str(), 0, KEY_READ, &oldKey1); LSTATUS oldKeyRes2 = RegOpenKeyEx(HKEY_LOCAL_MACHINE, oldKeyStr2.toStdWString().c_str(), 0, KEY_READ, &oldKey2); bool existNew1 = (newKeyRes1 == ERROR_SUCCESS) && (RegQueryValueEx(newKey1, L"InstallDate", 0, &checkType, (BYTE*)checkStr, &checkSize) == ERROR_SUCCESS); checkSize = bufSize * 2; bool existNew2 = (newKeyRes2 == ERROR_SUCCESS) && (RegQueryValueEx(newKey2, L"InstallDate", 0, &checkType, (BYTE*)checkStr, &checkSize) == ERROR_SUCCESS); checkSize = bufSize * 2; bool existOld1 = (oldKeyRes1 == ERROR_SUCCESS) && (RegQueryValueEx(oldKey1, L"InstallDate", 0, &checkType, (BYTE*)checkStr, &checkSize) == ERROR_SUCCESS); checkSize = bufSize * 2; bool existOld2 = (oldKeyRes2 == ERROR_SUCCESS) && (RegQueryValueEx(oldKey2, L"InstallDate", 0, &checkType, (BYTE*)checkStr, &checkSize) == ERROR_SUCCESS); checkSize = bufSize * 2; if (newKeyRes1 == ERROR_SUCCESS) RegCloseKey(newKey1); if (newKeyRes2 == ERROR_SUCCESS) RegCloseKey(newKey2); if (oldKeyRes1 == ERROR_SUCCESS) RegCloseKey(oldKey1); if (oldKeyRes2 == ERROR_SUCCESS) RegCloseKey(oldKey2); if (existNew1 || existNew2) { oldKeyRes1 = existOld1 ? RegDeleteKey(HKEY_LOCAL_MACHINE, oldKeyStr1.toStdWString().c_str()) : ERROR_SUCCESS; oldKeyRes2 = existOld2 ? RegDeleteKey(HKEY_LOCAL_MACHINE, oldKeyStr2.toStdWString().c_str()) : ERROR_SUCCESS; } QString userDesktopLnk, commonDesktopLnk; WCHAR userDesktopFolder[MAX_PATH], commonDesktopFolder[MAX_PATH]; HRESULT userDesktopRes = SHGetFolderPath(0, CSIDL_DESKTOPDIRECTORY, 0, SHGFP_TYPE_CURRENT, userDesktopFolder); HRESULT commonDesktopRes = SHGetFolderPath(0, CSIDL_COMMON_DESKTOPDIRECTORY, 0, SHGFP_TYPE_CURRENT, commonDesktopFolder); if (SUCCEEDED(userDesktopRes)) { userDesktopLnk = QString::fromWCharArray(userDesktopFolder) + "\\Telegram.lnk"; } if (SUCCEEDED(commonDesktopRes)) { commonDesktopLnk = QString::fromWCharArray(commonDesktopFolder) + "\\Telegram.lnk"; } QFile userDesktopFile(userDesktopLnk), commonDesktopFile(commonDesktopLnk); if (QFile::exists(userDesktopLnk) && QFile::exists(commonDesktopLnk) && userDesktopLnk != commonDesktopLnk) { bool removed = QFile::remove(commonDesktopLnk); } } catch (...) { } } int psFixPrevious() { __try { psDoFixPrevious(); } __except(EXCEPTION_EXECUTE_HANDLER) { return 0; } return 0; } namespace Platform { void start() { Dlls::init(); } void finish() { EventFilter::destroy(); } QString SystemCountry() { int chCount = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SISO3166CTRYNAME, 0, 0); if (chCount && chCount < 128) { WCHAR wstrCountry[128]; int len = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SISO3166CTRYNAME, wstrCountry, chCount); if (len) { return QString::fromStdWString(std::wstring(wstrCountry)); } } return QString(); } QString CurrentExecutablePath(int argc, char *argv[]) { WCHAR result[MAX_PATH + 1] = { 0 }; auto count = GetModuleFileName(nullptr, result, MAX_PATH + 1); if (count < MAX_PATH + 1) { auto info = QFileInfo(QDir::fromNativeSeparators(QString::fromWCharArray(result))); return info.absoluteFilePath(); } // Fallback to the first command line argument. auto argsCount = 0; if (auto args = CommandLineToArgvW(GetCommandLine(), &argsCount)) { auto info = QFileInfo(QDir::fromNativeSeparators(QString::fromWCharArray(args[0]))); LocalFree(args); return info.absoluteFilePath(); } return QString(); } namespace { QString GetLangCodeById(unsigned lngId) { auto primary = (lngId & 0xFFU); switch (primary) { case 0x36: return qsl("af"); case 0x1C: return qsl("sq"); case 0x5E: return qsl("am"); case 0x01: return qsl("ar"); case 0x2B: return qsl("hy"); case 0x4D: return qsl("as"); case 0x2C: return qsl("az"); case 0x45: return qsl("bn"); case 0x6D: return qsl("ba"); case 0x2D: return qsl("eu"); case 0x23: return qsl("be"); case 0x1A: if (lngId == LANG_CROATIAN) { return qsl("hr"); } else if (lngId == LANG_BOSNIAN_NEUTRAL || lngId == LANG_BOSNIAN) { return qsl("bs"); } return qsl("sr"); break; case 0x7E: return qsl("br"); case 0x02: return qsl("bg"); case 0x92: return qsl("ku"); case 0x03: return qsl("ca"); case 0x04: return qsl("zh"); case 0x83: return qsl("co"); case 0x05: return qsl("cs"); case 0x06: return qsl("da"); case 0x65: return qsl("dv"); case 0x13: return qsl("nl"); case 0x09: return qsl("en"); case 0x25: return qsl("et"); case 0x38: return qsl("fo"); case 0x0B: return qsl("fi"); case 0x0c: return qsl("fr"); case 0x62: return qsl("fy"); case 0x56: return qsl("gl"); case 0x37: return qsl("ka"); case 0x07: return qsl("de"); case 0x08: return qsl("el"); case 0x6F: return qsl("kl"); case 0x47: return qsl("gu"); case 0x68: return qsl("ha"); case 0x0D: return qsl("he"); case 0x39: return qsl("hi"); case 0x0E: return qsl("hu"); case 0x0F: return qsl("is"); case 0x70: return qsl("ig"); case 0x21: return qsl("id"); case 0x5D: return qsl("iu"); case 0x3C: return qsl("ga"); case 0x34: return qsl("xh"); case 0x35: return qsl("zu"); case 0x10: return qsl("it"); case 0x11: return qsl("ja"); case 0x4B: return qsl("kn"); case 0x3F: return qsl("kk"); case 0x53: return qsl("kh"); case 0x87: return qsl("rw"); case 0x12: return qsl("ko"); case 0x40: return qsl("ky"); case 0x54: return qsl("lo"); case 0x26: return qsl("lv"); case 0x27: return qsl("lt"); case 0x6E: return qsl("lb"); case 0x2F: return qsl("mk"); case 0x3E: return qsl("ms"); case 0x4C: return qsl("ml"); case 0x3A: return qsl("mt"); case 0x81: return qsl("mi"); case 0x4E: return qsl("mr"); case 0x50: return qsl("mn"); case 0x61: return qsl("ne"); case 0x14: return qsl("no"); case 0x82: return qsl("oc"); case 0x48: return qsl("or"); case 0x63: return qsl("ps"); case 0x29: return qsl("fa"); case 0x15: return qsl("pl"); case 0x16: return qsl("pt"); case 0x67: return qsl("ff"); case 0x46: return qsl("pa"); case 0x18: return qsl("ro"); case 0x17: return qsl("rm"); case 0x19: return qsl("ru"); case 0x3B: return qsl("se"); case 0x4F: return qsl("sa"); case 0x32: return qsl("tn"); case 0x59: return qsl("sd"); case 0x5B: return qsl("si"); case 0x1B: return qsl("sk"); case 0x24: return qsl("sl"); case 0x0A: return qsl("es"); case 0x41: return qsl("sw"); case 0x1D: return qsl("sv"); case 0x28: return qsl("tg"); case 0x49: return qsl("ta"); case 0x44: return qsl("tt"); case 0x4A: return qsl("te"); case 0x1E: return qsl("th"); case 0x51: return qsl("bo"); case 0x73: return qsl("ti"); case 0x1F: return qsl("tr"); case 0x42: return qsl("tk"); case 0x22: return qsl("uk"); case 0x20: return qsl("ur"); case 0x80: return qsl("ug"); case 0x43: return qsl("uz"); case 0x2A: return qsl("vi"); case 0x52: return qsl("cy"); case 0x88: return qsl("wo"); case 0x78: return qsl("ii"); case 0x6A: return qsl("yo"); } return QString(); } } // namespace QString SystemLanguage() { constexpr auto kMaxLanguageLength = 128; auto uiLanguageId = GetUserDefaultUILanguage(); auto uiLanguageLength = GetLocaleInfo(uiLanguageId, LOCALE_SNAME, nullptr, 0); if (uiLanguageLength > 0 && uiLanguageLength < kMaxLanguageLength) { WCHAR uiLanguageWideString[kMaxLanguageLength] = { 0 }; uiLanguageLength = GetLocaleInfo(uiLanguageId, LOCALE_SNAME, uiLanguageWideString, uiLanguageLength); if (uiLanguageLength <= 0) { return QString(); } return QString::fromWCharArray(uiLanguageWideString); } auto uiLanguageCodeLength = GetLocaleInfo(uiLanguageId, LOCALE_ILANGUAGE, nullptr, 0); if (uiLanguageCodeLength > 0 && uiLanguageCodeLength < kMaxLanguageLength) { WCHAR uiLanguageCodeWideString[kMaxLanguageLength] = { 0 }; uiLanguageCodeLength = GetLocaleInfo(uiLanguageId, LOCALE_ILANGUAGE, uiLanguageCodeWideString, uiLanguageCodeLength); if (uiLanguageCodeLength <= 0) { return QString(); } auto languageCode = 0U; for (auto i = 0; i != uiLanguageCodeLength; ++i) { auto ch = uiLanguageCodeWideString[i]; if (!ch) { break; } languageCode *= 0x10U; if (ch >= WCHAR('0') && ch <= WCHAR('9')) { languageCode += static_cast<unsigned>(int(ch) - int(WCHAR('0'))); } else if (ch >= WCHAR('A') && ch <= WCHAR('F')) { languageCode += static_cast<unsigned>(0x0A + int(ch) - int(WCHAR('A'))); } else { return QString(); } } return GetLangCodeById(languageCode); } return QString(); } } // namespace Platform namespace { void _psLogError(const char *str, LSTATUS code) { LPWSTR errorTextFormatted = nullptr; auto formatFlags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS; FormatMessage( formatFlags, NULL, code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&errorTextFormatted, 0, 0); auto errorText = errorTextFormatted ? errorTextFormatted : L"(Unknown error)"; LOG((str).arg(code).arg(QString::fromStdWString(errorText))); LocalFree(errorTextFormatted); } bool _psOpenRegKey(LPCWSTR key, PHKEY rkey) { DEBUG_LOG(("App Info: opening reg key %1...").arg(QString::fromStdWString(key))); LSTATUS status = RegOpenKeyEx(HKEY_CURRENT_USER, key, 0, KEY_QUERY_VALUE | KEY_WRITE, rkey); if (status != ERROR_SUCCESS) { if (status == ERROR_FILE_NOT_FOUND) { status = RegCreateKeyEx(HKEY_CURRENT_USER, key, 0, 0, REG_OPTION_NON_VOLATILE, KEY_QUERY_VALUE | KEY_WRITE, 0, rkey, 0); if (status != ERROR_SUCCESS) { QString msg = qsl("App Error: could not create '%1' registry key, error %2").arg(QString::fromStdWString(key)).arg(qsl("%1: %2")); _psLogError(msg.toUtf8().constData(), status); return false; } } else { QString msg = qsl("App Error: could not open '%1' registry key, error %2").arg(QString::fromStdWString(key)).arg(qsl("%1: %2")); _psLogError(msg.toUtf8().constData(), status); return false; } } return true; } bool _psSetKeyValue(HKEY rkey, LPCWSTR value, QString v) { static const int bufSize = 4096; DWORD defaultType, defaultSize = bufSize * 2; WCHAR defaultStr[bufSize] = { 0 }; if (RegQueryValueEx(rkey, value, 0, &defaultType, (BYTE*)defaultStr, &defaultSize) != ERROR_SUCCESS || defaultType != REG_SZ || defaultSize != (v.size() + 1) * 2 || QString::fromStdWString(defaultStr) != v) { WCHAR tmp[bufSize] = { 0 }; if (!v.isEmpty()) wsprintf(tmp, v.replace(QChar('%'), qsl("%%")).toStdWString().c_str()); LSTATUS status = RegSetValueEx(rkey, value, 0, REG_SZ, (BYTE*)tmp, (wcslen(tmp) + 1) * sizeof(WCHAR)); if (status != ERROR_SUCCESS) { QString msg = qsl("App Error: could not set %1, error %2").arg(value ? ('\'' + QString::fromStdWString(value) + '\'') : qsl("(Default)")).arg("%1: %2"); _psLogError(msg.toUtf8().constData(), status); return false; } } return true; } } void RegisterCustomScheme() { if (cExeName().isEmpty()) { return; } #ifndef TDESKTOP_DISABLE_REGISTER_CUSTOM_SCHEME DEBUG_LOG(("App Info: Checking custom scheme 'tg'...")); HKEY rkey; QString exe = QDir::toNativeSeparators(cExeDir() + cExeName()); // Legacy URI scheme registration if (!_psOpenRegKey(L"Software\\Classes\\tg", &rkey)) return; if (!_psSetKeyValue(rkey, L"URL Protocol", QString())) return; if (!_psSetKeyValue(rkey, 0, qsl("URL:Telegram Link"))) return; if (!_psOpenRegKey(L"Software\\Classes\\tg\\DefaultIcon", &rkey)) return; if (!_psSetKeyValue(rkey, 0, '"' + exe + qsl(",1\""))) return; if (!_psOpenRegKey(L"Software\\Classes\\tg\\shell", &rkey)) return; if (!_psOpenRegKey(L"Software\\Classes\\tg\\shell\\open", &rkey)) return; if (!_psOpenRegKey(L"Software\\Classes\\tg\\shell\\open\\command", &rkey)) return; if (!_psSetKeyValue(rkey, 0, '"' + exe + qsl("\" -workdir \"") + cWorkingDir() + qsl("\" -- \"%1\""))) return; // URI scheme registration as Default Program - Windows Vista and above if (!_psOpenRegKey(L"Software\\Classes\\tdesktop.tg", &rkey)) return; if (!_psOpenRegKey(L"Software\\Classes\\tdesktop.tg\\DefaultIcon", &rkey)) return; if (!_psSetKeyValue(rkey, 0, '"' + exe + qsl(",1\""))) return; if (!_psOpenRegKey(L"Software\\Classes\\tdesktop.tg\\shell", &rkey)) return; if (!_psOpenRegKey(L"Software\\Classes\\tdesktop.tg\\shell\\open", &rkey)) return; if (!_psOpenRegKey(L"Software\\Classes\\tdesktop.tg\\shell\\open\\command", &rkey)) return; if (!_psSetKeyValue(rkey, 0, '"' + exe + qsl("\" -workdir \"") + cWorkingDir() + qsl("\" -- \"%1\""))) return; if (!_psOpenRegKey(L"Software\\TelegramDesktop", &rkey)) return; if (!_psOpenRegKey(L"Software\\TelegramDesktop\\Capabilities", &rkey)) return; if (!_psSetKeyValue(rkey, L"ApplicationName", qsl("Telegram Desktop"))) return; if (!_psSetKeyValue(rkey, L"ApplicationDescription", qsl("Telegram Desktop"))) return; if (!_psOpenRegKey(L"Software\\TelegramDesktop\\Capabilities\\UrlAssociations", &rkey)) return; if (!_psSetKeyValue(rkey, L"tg", qsl("tdesktop.tg"))) return; if (!_psOpenRegKey(L"Software\\RegisteredApplications", &rkey)) return; if (!_psSetKeyValue(rkey, L"Telegram Desktop", qsl("SOFTWARE\\TelegramDesktop\\Capabilities"))) return; #endif // !TDESKTOP_DISABLE_REGISTER_CUSTOM_SCHEME } void psNewVersion() { RegisterCustomScheme(); if (Local::oldSettingsVersion() < 8051) { AppUserModelId::checkPinned(); } if (Local::oldSettingsVersion() > 0 && Local::oldSettingsVersion() < 10021) { // Reset icons cache, because we've changed the application icon. if (Dlls::SHChangeNotify) { Dlls::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr); } } } void _manageAppLnk(bool create, bool silent, int path_csidl, const wchar_t *args, const wchar_t *description) { if (cExeName().isEmpty()) { return; } WCHAR startupFolder[MAX_PATH]; HRESULT hr = SHGetFolderPath(0, path_csidl, 0, SHGFP_TYPE_CURRENT, startupFolder); if (SUCCEEDED(hr)) { QString lnk = QString::fromWCharArray(startupFolder) + '\\' + str_const_toString(AppFile) + qsl(".lnk"); if (create) { ComPtr<IShellLink> shellLink; hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&shellLink)); if (SUCCEEDED(hr)) { ComPtr<IPersistFile> persistFile; QString exe = QDir::toNativeSeparators(cExeDir() + cExeName()), dir = QDir::toNativeSeparators(QDir(cWorkingDir()).absolutePath()); shellLink->SetArguments(args); shellLink->SetPath(exe.toStdWString().c_str()); shellLink->SetWorkingDirectory(dir.toStdWString().c_str()); shellLink->SetDescription(description); ComPtr<IPropertyStore> propertyStore; hr = shellLink.As(&propertyStore); if (SUCCEEDED(hr)) { PROPVARIANT appIdPropVar; hr = InitPropVariantFromString(AppUserModelId::getId(), &appIdPropVar); if (SUCCEEDED(hr)) { hr = propertyStore->SetValue(AppUserModelId::getKey(), appIdPropVar); PropVariantClear(&appIdPropVar); if (SUCCEEDED(hr)) { hr = propertyStore->Commit(); } } } hr = shellLink.As(&persistFile); if (SUCCEEDED(hr)) { hr = persistFile->Save(lnk.toStdWString().c_str(), TRUE); } else { if (!silent) LOG(("App Error: could not create interface IID_IPersistFile %1").arg(hr)); } } else { if (!silent) LOG(("App Error: could not create instance of IID_IShellLink %1").arg(hr)); } } else { QFile::remove(lnk); } } else { if (!silent) LOG(("App Error: could not get CSIDL %1 folder %2").arg(path_csidl).arg(hr)); } } void psAutoStart(bool start, bool silent) { _manageAppLnk(start, silent, CSIDL_STARTUP, L"-autostart", L"Telegram autorun link.\nYou can disable autorun in Telegram settings."); } void psSendToMenu(bool send, bool silent) { _manageAppLnk(send, silent, CSIDL_SENDTO, L"-sendpath", L"Telegram send to link.\nYou can disable send to menu item in Telegram settings."); } void psUpdateOverlayed(TWidget *widget) { bool wm = widget->testAttribute(Qt::WA_Mapped), wv = widget->testAttribute(Qt::WA_WState_Visible); if (!wm) widget->setAttribute(Qt::WA_Mapped, true); if (!wv) widget->setAttribute(Qt::WA_WState_Visible, true); widget->update(); QEvent e(QEvent::UpdateRequest); QGuiApplication::sendEvent(widget, &e); if (!wm) widget->setAttribute(Qt::WA_Mapped, false); if (!wv) widget->setAttribute(Qt::WA_WState_Visible, false); } // Stack walk code is inspired by http://www.codeproject.com/Articles/11132/Walking-the-callstack static const int StackEntryMaxNameLength = MAX_SYM_NAME + 1; typedef BOOL(FAR STDAPICALLTYPE *t_SymCleanup)( _In_ HANDLE hProcess ); t_SymCleanup symCleanup = 0; typedef PVOID (FAR STDAPICALLTYPE *t_SymFunctionTableAccess64)( _In_ HANDLE hProcess, _In_ DWORD64 AddrBase ); t_SymFunctionTableAccess64 symFunctionTableAccess64 = 0; typedef BOOL (FAR STDAPICALLTYPE *t_SymGetLineFromAddr64)( _In_ HANDLE hProcess, _In_ DWORD64 dwAddr, _Out_ PDWORD pdwDisplacement, _Out_ PIMAGEHLP_LINEW64 Line ); t_SymGetLineFromAddr64 symGetLineFromAddr64 = 0; typedef DWORD64 (FAR STDAPICALLTYPE *t_SymGetModuleBase64)( _In_ HANDLE hProcess, _In_ DWORD64 qwAddr ); t_SymGetModuleBase64 symGetModuleBase64 = 0; typedef BOOL (FAR STDAPICALLTYPE *t_SymGetModuleInfo64)( _In_ HANDLE hProcess, _In_ DWORD64 qwAddr, _Out_ PIMAGEHLP_MODULEW64 ModuleInfo ); t_SymGetModuleInfo64 symGetModuleInfo64 = 0; typedef DWORD (FAR STDAPICALLTYPE *t_SymGetOptions)( VOID ); t_SymGetOptions symGetOptions = 0; typedef DWORD (FAR STDAPICALLTYPE *t_SymSetOptions)( _In_ DWORD SymOptions ); t_SymSetOptions symSetOptions = 0; typedef BOOL (FAR STDAPICALLTYPE *t_SymGetSymFromAddr64)( IN HANDLE hProcess, IN DWORD64 dwAddr, OUT PDWORD64 pdwDisplacement, OUT PIMAGEHLP_SYMBOL64 Symbol ); t_SymGetSymFromAddr64 symGetSymFromAddr64 = 0; typedef BOOL (FAR STDAPICALLTYPE *t_SymInitialize)( _In_ HANDLE hProcess, _In_opt_ PCWSTR UserSearchPath, _In_ BOOL fInvadeProcess ); t_SymInitialize symInitialize = 0; typedef DWORD64 (FAR STDAPICALLTYPE *t_SymLoadModule64)( _In_ HANDLE hProcess, _In_opt_ HANDLE hFile, _In_opt_ PCSTR ImageName, _In_opt_ PCSTR ModuleName, _In_ DWORD64 BaseOfDll, _In_ DWORD SizeOfDll ); t_SymLoadModule64 symLoadModule64; typedef BOOL (FAR STDAPICALLTYPE *t_StackWalk64)( _In_ DWORD MachineType, _In_ HANDLE hProcess, _In_ HANDLE hThread, _Inout_ LPSTACKFRAME64 StackFrame, _Inout_ PVOID ContextRecord, _In_opt_ PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine, _In_opt_ PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine, _In_opt_ PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine, _In_opt_ PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress ); t_StackWalk64 stackWalk64 = 0; typedef DWORD (FAR STDAPICALLTYPE *t_UnDecorateSymbolName)( PCSTR DecoratedName, PSTR UnDecoratedName, DWORD UndecoratedLength, DWORD Flags ); t_UnDecorateSymbolName unDecorateSymbolName = 0; typedef BOOL(FAR STDAPICALLTYPE *t_SymGetSearchPath)( _In_ HANDLE hProcess, _Out_writes_(SearchPathLength) PWSTR SearchPath, _In_ DWORD SearchPathLength ); t_SymGetSearchPath symGetSearchPath = 0; BOOL __stdcall ReadProcessMemoryRoutine64( _In_ HANDLE hProcess, _In_ DWORD64 qwBaseAddress, _Out_writes_bytes_(nSize) PVOID lpBuffer, _In_ DWORD nSize, _Out_ LPDWORD lpNumberOfBytesRead ) { SIZE_T st; BOOL bRet = ReadProcessMemory(hProcess, (LPVOID)qwBaseAddress, lpBuffer, nSize, &st); *lpNumberOfBytesRead = (DWORD)st; return bRet; } // **************************************** ToolHelp32 ************************ #define MAX_MODULE_NAME32 255 #define TH32CS_SNAPMODULE 0x00000008 #pragma pack( push, 8 ) typedef struct tagMODULEENTRY32 { DWORD dwSize; DWORD th32ModuleID; // This module DWORD th32ProcessID; // owning process DWORD GlblcntUsage; // Global usage count on the module DWORD ProccntUsage; // Module usage count in th32ProcessID's context BYTE * modBaseAddr; // Base address of module in th32ProcessID's context DWORD modBaseSize; // Size in bytes of module starting at modBaseAddr HMODULE hModule; // The hModule of this module in th32ProcessID's context char szModule[MAX_MODULE_NAME32 + 1]; char szExePath[MAX_PATH]; } MODULEENTRY32; typedef MODULEENTRY32 *PMODULEENTRY32; typedef MODULEENTRY32 *LPMODULEENTRY32; #pragma pack( pop ) typedef HANDLE (FAR STDAPICALLTYPE *t_CreateToolhelp32Snapshot)(DWORD dwFlags, DWORD th32ProcessID); t_CreateToolhelp32Snapshot createToolhelp32Snapshot = 0; typedef BOOL (FAR STDAPICALLTYPE *t_Module32First)(HANDLE hSnapshot, LPMODULEENTRY32 lpme); t_Module32First module32First = 0; typedef BOOL (FAR STDAPICALLTYPE *t_Module32Next)(HANDLE hSnapshot, LPMODULEENTRY32 lpme); t_Module32Next module32Next = 0; bool LoadDbgHelp(bool extended = false) { if (stackWalk64 && (!extended || symInitialize)) return true; HMODULE hDll = 0; WCHAR szTemp[4096]; if (GetModuleFileName(NULL, szTemp, 4096) > 0) { wcscat_s(szTemp, L".local"); if (GetFileAttributes(szTemp) == INVALID_FILE_ATTRIBUTES) { // ".local" file does not exist, so we can try to load the dbghelp.dll from the "Debugging Tools for Windows" if (GetEnvironmentVariable(L"ProgramFiles", szTemp, 4096) > 0) { wcscat_s(szTemp, L"\\Debugging Tools for Windows\\dbghelp.dll"); // now check if the file exists: if (GetFileAttributes(szTemp) != INVALID_FILE_ATTRIBUTES) { hDll = LoadLibrary(szTemp); } } // Still not found? Then try to load the 64-Bit version: if (!hDll && (GetEnvironmentVariable(L"ProgramFiles", szTemp, 4096) > 0)) { wcscat_s(szTemp, L"\\Debugging Tools for Windows 64-Bit\\dbghelp.dll"); if (GetFileAttributes(szTemp) != INVALID_FILE_ATTRIBUTES) { hDll = LoadLibrary(szTemp); } } } } if (!hDll) { hDll = LoadLibrary(L"DBGHELP.DLL"); } if (!hDll) return false; stackWalk64 = (t_StackWalk64)GetProcAddress(hDll, "StackWalk64"); symFunctionTableAccess64 = (t_SymFunctionTableAccess64)GetProcAddress(hDll, "SymFunctionTableAccess64"); symGetModuleBase64 = (t_SymGetModuleBase64)GetProcAddress(hDll, "SymGetModuleBase64"); if (!stackWalk64 || !symFunctionTableAccess64 || !symGetModuleBase64) { stackWalk64 = 0; return false; } if (extended) { HANDLE hProcess = GetCurrentProcess(); DWORD dwProcessId = GetCurrentProcessId(); symGetLineFromAddr64 = (t_SymGetLineFromAddr64)GetProcAddress(hDll, "SymGetLineFromAddrW64"); symGetModuleInfo64 = (t_SymGetModuleInfo64)GetProcAddress(hDll, "SymGetModuleInfoW64"); symGetSymFromAddr64 = (t_SymGetSymFromAddr64)GetProcAddress(hDll, "SymGetSymFromAddr64"); unDecorateSymbolName = (t_UnDecorateSymbolName)GetProcAddress(hDll, "UnDecorateSymbolName"); symInitialize = (t_SymInitialize)GetProcAddress(hDll, "SymInitializeW"); symCleanup = (t_SymCleanup)GetProcAddress(hDll, "SymCleanup"); symGetSearchPath = (t_SymGetSearchPath)GetProcAddress(hDll, "SymGetSearchPathW"); symGetOptions = (t_SymGetOptions)GetProcAddress(hDll, "SymGetOptions"); symSetOptions = (t_SymSetOptions)GetProcAddress(hDll, "SymSetOptions"); symLoadModule64 = (t_SymLoadModule64)GetProcAddress(hDll, "SymLoadModule64"); if (!symGetModuleInfo64 || !symGetLineFromAddr64 || !symGetSymFromAddr64 || !unDecorateSymbolName || !symInitialize || !symCleanup || !symGetOptions || !symSetOptions || !symLoadModule64) { symInitialize = 0; return false; } const size_t nSymPathLen = 10 * MAX_PATH; WCHAR szSymPath[nSymPathLen] = { 0 }; wcscat_s(szSymPath, nSymPathLen, L".;..;"); WCHAR szTemp[MAX_PATH + 1] = { 0 }; if (GetCurrentDirectory(MAX_PATH, szTemp) > 0) { wcscat_s(szSymPath, nSymPathLen, szTemp); wcscat_s(szSymPath, nSymPathLen, L";"); } if (GetModuleFileName(NULL, szTemp, MAX_PATH) > 0) { for (WCHAR *p = (szTemp + wcslen(szTemp) - 1); p >= szTemp; --p) { if ((*p == '\\') || (*p == '/') || (*p == ':')) { *p = 0; break; } } if (wcslen(szTemp) > 0) { wcscat_s(szSymPath, nSymPathLen, szTemp); wcscat_s(szSymPath, nSymPathLen, L";"); } } if (GetEnvironmentVariable(L"_NT_SYMBOL_PATH", szTemp, MAX_PATH) > 0) { wcscat_s(szSymPath, nSymPathLen, szTemp); wcscat_s(szSymPath, nSymPathLen, L";"); } if (GetEnvironmentVariable(L"_NT_ALTERNATE_SYMBOL_PATH", szTemp, MAX_PATH) > 0) { wcscat_s(szSymPath, nSymPathLen, szTemp); wcscat_s(szSymPath, nSymPathLen, L";"); } if (GetEnvironmentVariable(L"SYSTEMROOT", szTemp, MAX_PATH) > 0) { wcscat_s(szSymPath, nSymPathLen, szTemp); wcscat_s(szSymPath, nSymPathLen, L";"); // also add the "system32"-directory: wcscat_s(szTemp, MAX_PATH, L"\\system32"); wcscat_s(szSymPath, nSymPathLen, szTemp); wcscat_s(szSymPath, nSymPathLen, L";"); } if (GetEnvironmentVariable(L"SYSTEMDRIVE", szTemp, MAX_PATH) > 0) { wcscat_s(szSymPath, nSymPathLen, L"SRV*"); wcscat_s(szSymPath, nSymPathLen, szTemp); wcscat_s(szSymPath, nSymPathLen, L"\\websymbols*http://msdl.microsoft.com/download/symbols;"); } else { wcscat_s(szSymPath, nSymPathLen, L"SRV*c:\\websymbols*http://msdl.microsoft.com/download/symbols;"); } if (symInitialize(hProcess, szSymPath, FALSE) == FALSE) { symInitialize = 0; return false; } DWORD symOptions = symGetOptions(); symOptions |= SYMOPT_LOAD_LINES; symOptions |= SYMOPT_FAIL_CRITICAL_ERRORS; symOptions = symSetOptions(symOptions); const WCHAR *dllname[] = { L"kernel32.dll", L"tlhelp32.dll" }; HINSTANCE hToolhelp = NULL; HANDLE hSnap; MODULEENTRY32 me; me.dwSize = sizeof(me); BOOL keepGoing; size_t i; for (i = 0; i < (sizeof(dllname) / sizeof(dllname[0])); i++) { hToolhelp = LoadLibrary(dllname[i]); if (!hToolhelp) continue; createToolhelp32Snapshot = (t_CreateToolhelp32Snapshot)GetProcAddress(hToolhelp, "CreateToolhelp32Snapshot"); module32First = (t_Module32First)GetProcAddress(hToolhelp, "Module32First"); module32Next = (t_Module32Next)GetProcAddress(hToolhelp, "Module32Next"); if (createToolhelp32Snapshot && module32First && module32Next) { break; // found the functions! } FreeLibrary(hToolhelp); hToolhelp = NULL; } if (hToolhelp == NULL) { return false; } hSnap = createToolhelp32Snapshot(TH32CS_SNAPMODULE, dwProcessId); if (hSnap == (HANDLE)-1) return FALSE; keepGoing = !!module32First(hSnap, &me); int cnt = 0; while (keepGoing) { symLoadModule64(hProcess, 0, me.szExePath, me.szModule, (DWORD64)me.modBaseAddr, me.modBaseSize); ++cnt; keepGoing = !!module32Next(hSnap, &me); } CloseHandle(hSnap); FreeLibrary(hToolhelp); return (cnt > 0); } return true; } struct StackEntry { DWORD64 offset; // if 0, we have no valid entry CHAR name[StackEntryMaxNameLength]; CHAR undName[StackEntryMaxNameLength]; CHAR undFullName[StackEntryMaxNameLength]; DWORD64 offsetFromSmybol; DWORD offsetFromLine; DWORD lineNumber; WCHAR lineFileName[StackEntryMaxNameLength]; DWORD symType; LPCSTR symTypeString; WCHAR moduleName[StackEntryMaxNameLength]; DWORD64 baseOfImage; WCHAR loadedImageName[StackEntryMaxNameLength]; }; enum StackEntryType { StackEntryFirst, StackEntryNext, StackEntryLast, }; char GetModuleInfoData[2 * sizeof(IMAGEHLP_MODULEW64)]; BOOL _getModuleInfo(HANDLE hProcess, DWORD64 baseAddr, IMAGEHLP_MODULEW64 *pModuleInfo) { pModuleInfo->SizeOfStruct = sizeof(IMAGEHLP_MODULEW64); memcpy(GetModuleInfoData, pModuleInfo, sizeof(IMAGEHLP_MODULEW64)); if (symGetModuleInfo64(hProcess, baseAddr, (IMAGEHLP_MODULEW64*)GetModuleInfoData) != FALSE) { // only copy as much memory as is reserved... memcpy(pModuleInfo, GetModuleInfoData, sizeof(IMAGEHLP_MODULEW64)); pModuleInfo->SizeOfStruct = sizeof(IMAGEHLP_MODULEW64); return TRUE; } return FALSE; } void psWriteDump() { } char ImageHlpSymbol64[sizeof(IMAGEHLP_SYMBOL64) + StackEntryMaxNameLength]; QString psPrepareCrashDump(const QByteArray &crashdump, QString dumpfile) { if (!LoadDbgHelp(true) || cExeName().isEmpty()) { return qsl("ERROR: could not init dbghelp.dll!"); } HANDLE hProcess = GetCurrentProcess(); QString initial = QString::fromUtf8(crashdump), result; QStringList lines = initial.split('\n'); result.reserve(initial.size()); int32 i = 0, l = lines.size(); QString versionstr; uint64 version = 0, betaversion = 0; for (; i < l; ++i) { result.append(lines.at(i)).append('\n'); QString line = lines.at(i).trimmed(); if (line.startsWith(qstr("Version: "))) { versionstr = line.mid(qstr("Version: ").size()).trimmed(); version = versionstr.toULongLong(); if (versionstr.endsWith(qstr("beta"))) { if (version % 1000) { betaversion = version; } else { version /= 1000; } } ++i; break; } } // maybe need to launch another executable QString tolaunch; if ((betaversion && betaversion != cBetaVersion()) || (!betaversion && version && version != AppVersion)) { QString path = cExeDir(); QRegularExpressionMatch m = QRegularExpression("deploy/\\d+\\.\\d+/\\d+\\.\\d+\\.\\d+(/|\\.dev/|\\.alpha/|_\\d+/)(Telegram/)?$").match(path); if (m.hasMatch()) { QString base = path.mid(0, m.capturedStart()) + qstr("deploy/"); int32 major = version / 1000000, minor = (version % 1000000) / 1000, micro = (version % 1000); base += qsl("%1.%2/%3.%4.%5").arg(major).arg(minor).arg(major).arg(minor).arg(micro); if (betaversion) { base += qsl("_%1").arg(betaversion); } else if (QDir(base + qstr(".dev")).exists()) { base += qstr(".dev"); } else if (QDir(base + qstr(".alpha")).exists()) { base += qstr(".alpha"); } if (QFile(base + qstr("/Telegram/Telegram.exe")).exists()) { base += qstr("/Telegram"); } tolaunch = base + qstr("Telegram.exe"); } } if (!tolaunch.isEmpty()) { result.append(qsl("ERROR: for this crashdump executable '%1' should be used!").arg(tolaunch)); } while (i < l) { for (; i < l; ++i) { result.append(lines.at(i)).append('\n'); QString line = lines.at(i).trimmed(); if (line == qstr("Backtrace:")) { ++i; break; } } IMAGEHLP_SYMBOL64 *pSym = NULL; IMAGEHLP_MODULEW64 Module; IMAGEHLP_LINEW64 Line; pSym = (IMAGEHLP_SYMBOL64*)ImageHlpSymbol64; memset(pSym, 0, sizeof(IMAGEHLP_SYMBOL64) + StackEntryMaxNameLength); pSym->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64); pSym->MaxNameLength = StackEntryMaxNameLength; memset(&Line, 0, sizeof(Line)); Line.SizeOfStruct = sizeof(Line); memset(&Module, 0, sizeof(Module)); Module.SizeOfStruct = sizeof(Module); StackEntry csEntry; for (int32 start = i; i < l; ++i) { QString line = lines.at(i).trimmed(); if (line.isEmpty()) break; result.append(qsl("%1. ").arg(i + 1 - start)); if (!QRegularExpression(qsl("^\\d+$")).match(line).hasMatch()) { if (!lines.at(i).startsWith(qstr("ERROR: "))) { result.append(qstr("BAD LINE: ")); } result.append(line).append('\n'); continue; } DWORD64 address = line.toULongLong(); csEntry.offset = address; csEntry.name[0] = 0; csEntry.undName[0] = 0; csEntry.undFullName[0] = 0; csEntry.offsetFromSmybol = 0; csEntry.offsetFromLine = 0; csEntry.lineFileName[0] = 0; csEntry.lineNumber = 0; csEntry.loadedImageName[0] = 0; csEntry.moduleName[0] = 0; if (symGetSymFromAddr64(hProcess, address, &(csEntry.offsetFromSmybol), pSym) != FALSE) { // TODO: Mache dies sicher...! strcpy_s(csEntry.name, pSym->Name); unDecorateSymbolName(pSym->Name, csEntry.undName, StackEntryMaxNameLength, UNDNAME_NAME_ONLY); unDecorateSymbolName(pSym->Name, csEntry.undFullName, StackEntryMaxNameLength, UNDNAME_COMPLETE); if (symGetLineFromAddr64) { if (symGetLineFromAddr64(hProcess, address, &(csEntry.offsetFromLine), &Line) != FALSE) { csEntry.lineNumber = Line.LineNumber; // TODO: Mache dies sicher...! wcscpy_s(csEntry.lineFileName, Line.FileName); } } } else { result.append("ERROR: could not get Sym from Addr! for ").append(QString::number(address)).append('\n'); continue; } if (_getModuleInfo(hProcess, address, &Module) != FALSE) { // TODO: Mache dies sicher...! wcscpy_s(csEntry.moduleName, Module.ModuleName); } if (csEntry.name[0] == 0) { strcpy_s(csEntry.name, "(function-name not available)"); } if (csEntry.undName[0] != 0) { strcpy_s(csEntry.name, csEntry.undName); } if (csEntry.undFullName[0] != 0) { strcpy_s(csEntry.name, csEntry.undFullName); } if (csEntry.lineFileName[0] == 0) { if (csEntry.moduleName[0] == 0) { wcscpy_s(csEntry.moduleName, L"module-name not available"); } result.append(csEntry.name).append(qsl(" (%1) 0x%3").arg(QString::fromWCharArray(csEntry.moduleName)).arg(address, 0, 16)).append('\n'); } else { QString file = QString::fromWCharArray(csEntry.lineFileName).toLower(); int32 index = file.indexOf(qstr("tbuild\\tdesktop\\telegram\\")); if (index >= 0) { file = file.mid(index + qstr("tbuild\\tdesktop\\telegram\\").size()); if (file.startsWith(qstr("sourcefiles\\"))) { file = file.mid(qstr("sourcefiles\\").size()); } } result.append(csEntry.name).append(qsl(" (%1 - %2) 0x%3").arg(file).arg(csEntry.lineNumber).arg(address, 0, 16)).append('\n'); } } } symCleanup(hProcess); return result; } void psWriteStackTrace() { #ifndef TDESKTOP_DISABLE_CRASH_REPORTS if (!LoadDbgHelp()) { CrashReports::dump() << "ERROR: Could not load dbghelp.dll!\n"; return; } HANDLE hThread = GetCurrentThread(), hProcess = GetCurrentProcess(); const CONTEXT *context = NULL; LPVOID pUserData = NULL; CONTEXT c; int frameNum; memset(&c, 0, sizeof(CONTEXT)); c.ContextFlags = CONTEXT_FULL; RtlCaptureContext(&c); // init STACKFRAME for first call STACKFRAME64 s; // in/out stackframe memset(&s, 0, sizeof(s)); DWORD imageType; #ifdef _M_IX86 // normally, call ImageNtHeader() and use machine info from PE header imageType = IMAGE_FILE_MACHINE_I386; s.AddrPC.Offset = c.Eip; s.AddrPC.Mode = AddrModeFlat; s.AddrFrame.Offset = c.Ebp; s.AddrFrame.Mode = AddrModeFlat; s.AddrStack.Offset = c.Esp; s.AddrStack.Mode = AddrModeFlat; #elif _M_X64 imageType = IMAGE_FILE_MACHINE_AMD64; s.AddrPC.Offset = c.Rip; s.AddrPC.Mode = AddrModeFlat; s.AddrFrame.Offset = c.Rsp; s.AddrFrame.Mode = AddrModeFlat; s.AddrStack.Offset = c.Rsp; s.AddrStack.Mode = AddrModeFlat; #elif _M_IA64 imageType = IMAGE_FILE_MACHINE_IA64; s.AddrPC.Offset = c.StIIP; s.AddrPC.Mode = AddrModeFlat; s.AddrFrame.Offset = c.IntSp; s.AddrFrame.Mode = AddrModeFlat; s.AddrBStore.Offset = c.RsBSP; s.AddrBStore.Mode = AddrModeFlat; s.AddrStack.Offset = c.IntSp; s.AddrStack.Mode = AddrModeFlat; #else #error "Platform not supported!" #endif for (frameNum = 0; frameNum < 1024; ++frameNum) { // get next stack frame (StackWalk64(), SymFunctionTableAccess64(), SymGetModuleBase64()) // if this returns ERROR_INVALID_ADDRESS (487) or ERROR_NOACCESS (998), you can // assume that either you are done, or that the stack is so hosed that the next // deeper frame could not be found. // CONTEXT need not to be suplied if imageTyp is IMAGE_FILE_MACHINE_I386! if (!stackWalk64(imageType, hProcess, hThread, &s, &c, ReadProcessMemoryRoutine64, symFunctionTableAccess64, symGetModuleBase64, NULL)) { CrashReports::dump() << "ERROR: Call to StackWalk64() failed!\n"; return; } if (s.AddrPC.Offset == s.AddrReturn.Offset) { CrashReports::dump() << s.AddrPC.Offset << "\n"; CrashReports::dump() << "ERROR: StackWalk64() endless callstack!"; return; } if (s.AddrPC.Offset != 0) { // we seem to have a valid PC CrashReports::dump() << s.AddrPC.Offset << "\n"; } if (s.AddrReturn.Offset == 0) { break; } } #endif // !TDESKTOP_DISABLE_CRASH_REPORTS } bool psLaunchMaps(const LocationCoords &coords) { return QDesktopServices::openUrl(qsl("bingmaps:?lvl=16&collection=point.%1_%2_Point").arg(coords.latAsString()).arg(coords.lonAsString())); } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/win/specific_win.h������������������������������������0000664�0000000�0000000�00000004436�13262451251�0024666�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <windows.h> namespace Platform { inline void SetWatchingMediaKeys(bool watching) { } inline bool TranslucentWindowsSupported(QPoint globalPosition) { return true; } inline void StartTranslucentPaint(QPainter &p, QPaintEvent *e) { } inline void InitOnTopPanel(QWidget *panel) { } inline void DeInitOnTopPanel(QWidget *panel) { } inline void ReInitOnTopPanel(QWidget *panel) { } QString CurrentExecutablePath(int argc, char *argv[]); namespace ThirdParty { inline void start() { } inline void finish() { } } // namespace ThirdParty } // namespace Platform inline QString psServerPrefix() { return qsl("Global\\"); } inline void psCheckLocalSocket(const QString &) { } void psWriteDump(); void psWriteStackTrace(); QString psPrepareCrashDump(const QByteArray &crashdump, QString dumpfile); void psDeleteDir(const QString &dir); void psUserActionDone(); bool psIdleSupported(); TimeMs psIdleTime(); QStringList psInitLogs(); void psClearInitLogs(); void psActivateProcess(uint64 pid = 0); QString psLocalServerPrefix(); QString psAppDataPath(); QString psAppDataPathOld(); QString psDownloadPath(); void psAutoStart(bool start, bool silent = false); void psSendToMenu(bool send, bool silent = false); QRect psDesktopRect(); void psShowOverAll(QWidget *w, bool canFocus = true); void psBringToBack(QWidget *w); int psCleanup(); int psFixPrevious(); QAbstractNativeEventFilter *psNativeEventFilter(); void psNewVersion(); void psUpdateOverlayed(TWidget *widget); inline QByteArray psDownloadPathBookmark(const QString &path) { return QByteArray(); } inline QByteArray psPathBookmark(const QString &path) { return QByteArray(); } inline void psDownloadPathEnableAccess() { } class PsFileBookmark { public: PsFileBookmark(const QByteArray &bookmark) { } bool check() const { return true; } bool enable() const { return true; } void disable() const { } const QString &name(const QString &original) const { return original; } QByteArray bookmark() const { return QByteArray(); } }; bool psLaunchMaps(const LocationCoords &coords); ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/win/window_title_win.cpp������������������������������0000664�0000000�0000000�00000007167�13262451251�0026150�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "platform/win/window_title_win.h" #include "ui/widgets/buttons.h" #include "ui/widgets/shadow.h" #include "styles/style_window.h" namespace Platform { TitleWidget::TitleWidget(QWidget *parent) : Window::TitleWidget(parent) , _minimize(this, st::titleButtonMinimize) , _maximizeRestore(this, st::titleButtonMaximize) , _close(this, st::titleButtonClose) , _shadow(this, st::titleShadow) , _maximizedState(parent->window()->windowState() & Qt::WindowMaximized) { _minimize->setClickedCallback([this]() { window()->setWindowState(Qt::WindowMinimized); _minimize->clearState(); }); _minimize->setPointerCursor(false); _maximizeRestore->setClickedCallback([this]() { window()->setWindowState(_maximizedState ? Qt::WindowNoState : Qt::WindowMaximized); _maximizeRestore->clearState(); }); _maximizeRestore->setPointerCursor(false); _close->setClickedCallback([this]() { window()->close(); _close->clearState(); }); _close->setPointerCursor(false); setAttribute(Qt::WA_OpaquePaintEvent); resize(width(), st::titleHeight); } void TitleWidget::init() { connect(window()->windowHandle(), SIGNAL(windowStateChanged(Qt::WindowState)), this, SLOT(onWindowStateChanged(Qt::WindowState))); _maximizedState = (window()->windowState() & Qt::WindowMaximized); _activeState = isActiveWindow(); updateButtonsState(); } void TitleWidget::paintEvent(QPaintEvent *e) { auto active = isActiveWindow(); if (_activeState != active) { _activeState = active; updateButtonsState(); } Painter(this).fillRect(rect(), active ? st::titleBgActive : st::titleBg); } void TitleWidget::updateControlsPosition() { auto right = 0; _close->moveToRight(right, 0); right += _close->width(); _maximizeRestore->moveToRight(right, 0); right += _maximizeRestore->width(); _minimize->moveToRight(right, 0); } void TitleWidget::resizeEvent(QResizeEvent *e) { updateControlsPosition(); _shadow->setGeometry(0, height() - st::lineWidth, width(), st::lineWidth); } void TitleWidget::updateControlsVisibility() { updateControlsPosition(); update(); } void TitleWidget::onWindowStateChanged(Qt::WindowState state) { if (state == Qt::WindowMinimized) return; auto maximized = (state == Qt::WindowMaximized); if (_maximizedState != maximized) { _maximizedState = maximized; updateButtonsState(); } } void TitleWidget::updateButtonsState() { _minimize->setIconOverride(_activeState ? &st::titleButtonMinimizeIconActive : nullptr, _activeState ? &st::titleButtonMinimizeIconActiveOver : nullptr); if (_maximizedState) { _maximizeRestore->setIconOverride(_activeState ? &st::titleButtonRestoreIconActive : &st::titleButtonRestoreIcon, _activeState ? &st::titleButtonRestoreIconActiveOver : &st::titleButtonRestoreIconOver); } else { _maximizeRestore->setIconOverride(_activeState ? &st::titleButtonMaximizeIconActive : nullptr, _activeState ? &st::titleButtonMaximizeIconActiveOver : nullptr); } _close->setIconOverride(_activeState ? &st::titleButtonCloseIconActive : nullptr, _activeState ? &st::titleButtonCloseIconActiveOver : nullptr); } Window::HitTestResult TitleWidget::hitTest(const QPoint &p) const { if (false || (_minimize->geometry().contains(p)) || (_maximizeRestore->geometry().contains(p)) || (_close->geometry().contains(p)) ) { return Window::HitTestResult::SysButton; } else if (rect().contains(p)) { return Window::HitTestResult::Caption; } return Window::HitTestResult::None; } } // namespace Platform ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/win/window_title_win.h��������������������������������0000664�0000000�0000000�00000003377�13262451251�0025614�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "platform/platform_window_title.h" namespace Ui { class IconButton; class PlainShadow; } // namespace Ui namespace Window { namespace Theme { int DefaultPreviewTitleHeight(); void DefaultPreviewWindowFramePaint(QImage &preview, const style::palette &palette, QRect body, int outerWidth); } // namespace Theme } // namespace Window namespace Platform { class TitleWidget : public Window::TitleWidget, private base::Subscriber { Q_OBJECT public: TitleWidget(QWidget *parent); void init() override; Window::HitTestResult hitTest(const QPoint &p) const override; public slots: void onWindowStateChanged(Qt::WindowState state = Qt::WindowNoState); void updateControlsVisibility(); protected: void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; private: void updateButtonsState(); void updateControlsPosition(); object_ptr<Ui::IconButton> _minimize; object_ptr<Ui::IconButton> _maximizeRestore; object_ptr<Ui::IconButton> _close; object_ptr<Ui::PlainShadow> _shadow; bool _maximizedState = false; bool _activeState = false; }; inline object_ptr<Window::TitleWidget> CreateTitleWidget(QWidget *parent) { return object_ptr<TitleWidget>(parent); } inline int PreviewTitleHeight() { return Window::Theme::DefaultPreviewTitleHeight(); } inline void PreviewWindowFramePaint(QImage &preview, const style::palette &palette, QRect body, int outerWidth) { return Window::Theme::DefaultPreviewWindowFramePaint(preview, palette, body, outerWidth); } } // namespace Platform �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/win/windows_app_user_model_id.cpp���������������������0000664�0000000�0000000�00000023651�13262451251�0030003�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "platform/win/windows_app_user_model_id.h" #include "platform/win/windows_dlls.h" #include <propvarutil.h> #include <propkey.h> #include <roapi.h> #include <wrl\client.h> #include <wrl\implements.h> #include <windows.ui.notifications.h> using namespace Microsoft::WRL; namespace Platform { namespace AppUserModelId { namespace { const PROPERTYKEY pkey_AppUserModel_ID = { { 0x9F4C2855, 0x9F79, 0x4B39, { 0xA8, 0xD0, 0xE1, 0xD4, 0x2D, 0xE1, 0xD5, 0xF3 } }, 5 }; const PROPERTYKEY pkey_AppUserModel_StartPinOption = { { 0x9F4C2855, 0x9F79, 0x4B39, { 0xA8, 0xD0, 0xE1, 0xD4, 0x2D, 0xE1, 0xD5, 0xF3 } }, 12 }; #ifdef OS_WIN_STORE const WCHAR AppUserModelIdRelease[] = L"Telegram.TelegramDesktop.Store"; #else // OS_WIN_STORE const WCHAR AppUserModelIdRelease[] = L"Telegram.TelegramDesktop"; #endif // OS_WIN_STORE const WCHAR AppUserModelIdBeta[] = L"Telegram.TelegramDesktop.Beta"; } // namespace QString pinnedPath() { static const int maxFileLen = MAX_PATH * 10; WCHAR wstrPath[maxFileLen]; if (GetEnvironmentVariable(L"APPDATA", wstrPath, maxFileLen)) { QDir appData(QString::fromStdWString(std::wstring(wstrPath))); return appData.absolutePath() + qsl("/Microsoft/Internet Explorer/Quick Launch/User Pinned/TaskBar/"); } return QString(); } void checkPinned() { if (!Dlls::PropVariantToString) return; static const int maxFileLen = MAX_PATH * 10; HRESULT hr = CoInitialize(0); if (!SUCCEEDED(hr)) return; QString path = pinnedPath(); std::wstring p = QDir::toNativeSeparators(path).toStdWString(); WCHAR src[MAX_PATH]; GetModuleFileName(GetModuleHandle(0), src, MAX_PATH); BY_HANDLE_FILE_INFORMATION srcinfo = { 0 }; HANDLE srcfile = CreateFile(src, 0x00, 0x00, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (srcfile == INVALID_HANDLE_VALUE) return; BOOL srcres = GetFileInformationByHandle(srcfile, &srcinfo); CloseHandle(srcfile); if (!srcres) return; LOG(("Checking...")); WIN32_FIND_DATA findData; HANDLE findHandle = FindFirstFileEx((p + L"*").c_str(), FindExInfoStandard, &findData, FindExSearchNameMatch, 0, 0); if (findHandle == INVALID_HANDLE_VALUE) { LOG(("Init Error: could not find files in pinned folder")); return; } do { std::wstring fname = p + findData.cFileName; LOG(("Checking %1").arg(QString::fromStdWString(fname))); if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { continue; } else { DWORD attributes = GetFileAttributes(fname.c_str()); if (attributes >= 0xFFFFFFF) continue; // file does not exist ComPtr<IShellLink> shellLink; HRESULT hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&shellLink)); if (!SUCCEEDED(hr)) continue; ComPtr<IPersistFile> persistFile; hr = shellLink.As(&persistFile); if (!SUCCEEDED(hr)) continue; hr = persistFile->Load(fname.c_str(), STGM_READWRITE); if (!SUCCEEDED(hr)) continue; WCHAR dst[MAX_PATH]; hr = shellLink->GetPath(dst, MAX_PATH, 0, 0); if (!SUCCEEDED(hr)) continue; BY_HANDLE_FILE_INFORMATION dstinfo = { 0 }; HANDLE dstfile = CreateFile(dst, 0x00, 0x00, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (dstfile == INVALID_HANDLE_VALUE) continue; BOOL dstres = GetFileInformationByHandle(dstfile, &dstinfo); CloseHandle(dstfile); if (!dstres) continue; if (srcinfo.dwVolumeSerialNumber == dstinfo.dwVolumeSerialNumber && srcinfo.nFileIndexLow == dstinfo.nFileIndexLow && srcinfo.nFileIndexHigh == dstinfo.nFileIndexHigh) { ComPtr<IPropertyStore> propertyStore; hr = shellLink.As(&propertyStore); if (!SUCCEEDED(hr)) return; PROPVARIANT appIdPropVar; hr = propertyStore->GetValue(getKey(), &appIdPropVar); if (!SUCCEEDED(hr)) return; LOG(("Reading...")); WCHAR already[MAX_PATH]; hr = Dlls::PropVariantToString(appIdPropVar, already, MAX_PATH); if (SUCCEEDED(hr)) { if (std::wstring(getId()) == already) { LOG(("Already!")); PropVariantClear(&appIdPropVar); return; } } if (appIdPropVar.vt != VT_EMPTY) { PropVariantClear(&appIdPropVar); return; } PropVariantClear(&appIdPropVar); hr = InitPropVariantFromString(getId(), &appIdPropVar); if (!SUCCEEDED(hr)) return; hr = propertyStore->SetValue(getKey(), appIdPropVar); PropVariantClear(&appIdPropVar); if (!SUCCEEDED(hr)) return; hr = propertyStore->Commit(); if (!SUCCEEDED(hr)) return; if (persistFile->IsDirty() == S_OK) { persistFile->Save(fname.c_str(), TRUE); } return; } } } while (FindNextFile(findHandle, &findData)); DWORD errorCode = GetLastError(); if (errorCode && errorCode != ERROR_NO_MORE_FILES) { // everything is found LOG(("Init Error: could not find some files in pinned folder")); return; } FindClose(findHandle); } QString systemShortcutPath() { static const int maxFileLen = MAX_PATH * 10; WCHAR wstrPath[maxFileLen]; if (GetEnvironmentVariable(L"APPDATA", wstrPath, maxFileLen)) { QDir appData(QString::fromStdWString(std::wstring(wstrPath))); return appData.absolutePath() + qsl("/Microsoft/Windows/Start Menu/Programs/"); } return QString(); } void cleanupShortcut() { static const int maxFileLen = MAX_PATH * 10; QString path = systemShortcutPath() + qsl("Telegram.lnk"); std::wstring p = QDir::toNativeSeparators(path).toStdWString(); DWORD attributes = GetFileAttributes(p.c_str()); if (attributes >= 0xFFFFFFF) return; // file does not exist ComPtr<IShellLink> shellLink; HRESULT hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&shellLink)); if (!SUCCEEDED(hr)) return; ComPtr<IPersistFile> persistFile; hr = shellLink.As(&persistFile); if (!SUCCEEDED(hr)) return; hr = persistFile->Load(p.c_str(), STGM_READWRITE); if (!SUCCEEDED(hr)) return; WCHAR szGotPath[MAX_PATH]; WIN32_FIND_DATA wfd; hr = shellLink->GetPath(szGotPath, MAX_PATH, (WIN32_FIND_DATA*)&wfd, SLGP_SHORTPATH); if (!SUCCEEDED(hr)) return; if (QDir::toNativeSeparators(cExeDir() + cExeName()).toStdWString() == szGotPath) { QFile().remove(path); } } bool validateShortcutAt(const QString &path) { static const int maxFileLen = MAX_PATH * 10; std::wstring p = QDir::toNativeSeparators(path).toStdWString(); DWORD attributes = GetFileAttributes(p.c_str()); if (attributes >= 0xFFFFFFF) return false; // file does not exist ComPtr<IShellLink> shellLink; HRESULT hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&shellLink)); if (!SUCCEEDED(hr)) return false; ComPtr<IPersistFile> persistFile; hr = shellLink.As(&persistFile); if (!SUCCEEDED(hr)) return false; hr = persistFile->Load(p.c_str(), STGM_READWRITE); if (!SUCCEEDED(hr)) return false; ComPtr<IPropertyStore> propertyStore; hr = shellLink.As(&propertyStore); if (!SUCCEEDED(hr)) return false; PROPVARIANT appIdPropVar; hr = propertyStore->GetValue(getKey(), &appIdPropVar); if (!SUCCEEDED(hr)) return false; WCHAR already[MAX_PATH]; hr = Dlls::PropVariantToString(appIdPropVar, already, MAX_PATH); if (SUCCEEDED(hr)) { if (std::wstring(getId()) == already) { PropVariantClear(&appIdPropVar); return true; } } if (appIdPropVar.vt != VT_EMPTY) { PropVariantClear(&appIdPropVar); return false; } PropVariantClear(&appIdPropVar); hr = InitPropVariantFromString(getId(), &appIdPropVar); if (!SUCCEEDED(hr)) return false; hr = propertyStore->SetValue(getKey(), appIdPropVar); PropVariantClear(&appIdPropVar); if (!SUCCEEDED(hr)) return false; hr = propertyStore->Commit(); if (!SUCCEEDED(hr)) return false; if (persistFile->IsDirty() == S_OK) { persistFile->Save(p.c_str(), TRUE); } return true; } bool validateShortcut() { QString path = systemShortcutPath(); if (path.isEmpty() || cExeName().isEmpty()) return false; if (cBetaVersion()) { path += qsl("TelegramBeta.lnk"); if (validateShortcutAt(path)) return true; } else { if (validateShortcutAt(path + qsl("Telegram Desktop/Telegram.lnk"))) return true; if (validateShortcutAt(path + qsl("Telegram Win (Unofficial)/Telegram.lnk"))) return true; path += qsl("Telegram.lnk"); if (validateShortcutAt(path)) return true; } ComPtr<IShellLink> shellLink; HRESULT hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&shellLink)); if (!SUCCEEDED(hr)) return false; hr = shellLink->SetPath(QDir::toNativeSeparators(cExeDir() + cExeName()).toStdWString().c_str()); if (!SUCCEEDED(hr)) return false; hr = shellLink->SetArguments(L""); if (!SUCCEEDED(hr)) return false; hr = shellLink->SetWorkingDirectory(QDir::toNativeSeparators(QDir(cWorkingDir()).absolutePath()).toStdWString().c_str()); if (!SUCCEEDED(hr)) return false; ComPtr<IPropertyStore> propertyStore; hr = shellLink.As(&propertyStore); if (!SUCCEEDED(hr)) return false; PROPVARIANT appIdPropVar; hr = InitPropVariantFromString(getId(), &appIdPropVar); if (!SUCCEEDED(hr)) return false; hr = propertyStore->SetValue(getKey(), appIdPropVar); PropVariantClear(&appIdPropVar); if (!SUCCEEDED(hr)) return false; PROPVARIANT startPinPropVar; hr = InitPropVariantFromUInt32(APPUSERMODEL_STARTPINOPTION_NOPINONINSTALL, &startPinPropVar); if (!SUCCEEDED(hr)) return false; hr = propertyStore->SetValue(pkey_AppUserModel_StartPinOption, startPinPropVar); PropVariantClear(&startPinPropVar); if (!SUCCEEDED(hr)) return false; hr = propertyStore->Commit(); if (!SUCCEEDED(hr)) return false; ComPtr<IPersistFile> persistFile; hr = shellLink.As(&persistFile); if (!SUCCEEDED(hr)) return false; hr = persistFile->Save(QDir::toNativeSeparators(path).toStdWString().c_str(), TRUE); if (!SUCCEEDED(hr)) return false; return true; } const WCHAR *getId() { return cBetaVersion() ? AppUserModelIdBeta : AppUserModelIdRelease; } const PROPERTYKEY &getKey() { return pkey_AppUserModel_ID; } } // namespace AppUserModelId } // namespace Platform ���������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/win/windows_app_user_model_id.h�����������������������0000664�0000000�0000000�00000000766�13262451251�0027452�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <windows.h> namespace Platform { namespace AppUserModelId { void cleanupShortcut(); void checkPinned(); const WCHAR *getId(); bool validateShortcut(); const PROPERTYKEY &getKey(); } // namespace AppUserModelId } // namespace Platform ����������tdesktop-1.2.17/Telegram/SourceFiles/platform/win/windows_dlls.cpp����������������������������������0000664�0000000�0000000�00000006213�13262451251�0025262�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "platform/win/windows_dlls.h" namespace Platform { namespace Dlls { f_SetDllDirectory SetDllDirectory; HINSTANCE LibKernel32; void init() { static bool inited = false; if (inited) return; inited = true; LibKernel32 = LoadLibrary(L"KERNEL32.DLL"); load(LibKernel32, "SetDllDirectoryW", SetDllDirectory); if (SetDllDirectory) { // Remove the current directory from the DLL search order. SetDllDirectory(L""); } } f_SetWindowTheme SetWindowTheme; f_OpenAs_RunDLL OpenAs_RunDLL; f_SHOpenWithDialog SHOpenWithDialog; f_SHAssocEnumHandlers SHAssocEnumHandlers; f_SHCreateItemFromParsingName SHCreateItemFromParsingName; f_WTSRegisterSessionNotification WTSRegisterSessionNotification; f_WTSUnRegisterSessionNotification WTSUnRegisterSessionNotification; f_SHQueryUserNotificationState SHQueryUserNotificationState; f_SHChangeNotify SHChangeNotify; f_SetCurrentProcessExplicitAppUserModelID SetCurrentProcessExplicitAppUserModelID; f_RoGetActivationFactory RoGetActivationFactory; f_WindowsCreateStringReference WindowsCreateStringReference; f_WindowsDeleteString WindowsDeleteString; f_PropVariantToString PropVariantToString; f_PSStringFromPropertyKey PSStringFromPropertyKey; HINSTANCE LibUxTheme; HINSTANCE LibShell32; HINSTANCE LibWtsApi32; HINSTANCE LibPropSys; HINSTANCE LibComBase; void start() { init(); LibShell32 = LoadLibrary(L"SHELL32.DLL"); load(LibShell32, "SHAssocEnumHandlers", SHAssocEnumHandlers); load(LibShell32, "SHCreateItemFromParsingName", SHCreateItemFromParsingName); load(LibShell32, "SHOpenWithDialog", SHOpenWithDialog); load(LibShell32, "OpenAs_RunDLLW", OpenAs_RunDLL); load(LibShell32, "SHQueryUserNotificationState", SHQueryUserNotificationState); load(LibShell32, "SHChangeNotify", SHChangeNotify); load(LibShell32, "SetCurrentProcessExplicitAppUserModelID", SetCurrentProcessExplicitAppUserModelID); if (cBetaVersion() == 10020001 && SHChangeNotify) { // Temp - app icon was changed SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr); } LibUxTheme = LoadLibrary(L"UXTHEME.DLL"); load(LibUxTheme, "SetWindowTheme", SetWindowTheme); auto version = QSysInfo::windowsVersion(); if (version >= QSysInfo::WV_VISTA) { LibWtsApi32 = LoadLibrary(L"WTSAPI32.DLL"); load(LibWtsApi32, "WTSRegisterSessionNotification", WTSRegisterSessionNotification); load(LibWtsApi32, "WTSUnRegisterSessionNotification", WTSUnRegisterSessionNotification); LibPropSys = LoadLibrary(L"PROPSYS.DLL"); load(LibPropSys, "PropVariantToString", PropVariantToString); load(LibPropSys, "PSStringFromPropertyKey", PSStringFromPropertyKey); if (version >= QSysInfo::WV_WINDOWS8) { LibComBase = LoadLibrary(L"COMBASE.DLL"); load(LibComBase, "RoGetActivationFactory", RoGetActivationFactory); load(LibComBase, "WindowsCreateStringReference", WindowsCreateStringReference); load(LibComBase, "WindowsDeleteString", WindowsDeleteString); } } } } // namespace Dlls } // namespace Platform �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/win/windows_dlls.h������������������������������������0000664�0000000�0000000�00000007010�13262451251�0024723�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <windows.h> #include <shlobj.h> #include <roapi.h> namespace Platform { namespace Dlls { void init(); // KERNEL32.DLL typedef BOOL (FAR STDAPICALLTYPE *f_SetDllDirectory)(LPCWSTR lpPathName); extern f_SetDllDirectory SetDllDirectory; void start(); template <typename Function> bool load(HINSTANCE library, LPCSTR name, Function &func) { if (!library) return false; func = reinterpret_cast<Function>(GetProcAddress(library, name)); return (func != nullptr); } // UXTHEME.DLL typedef HRESULT (FAR STDAPICALLTYPE *f_SetWindowTheme)(HWND hWnd, LPCWSTR pszSubAppName, LPCWSTR pszSubIdList); extern f_SetWindowTheme SetWindowTheme; // SHELL32.DLL typedef HRESULT (FAR STDAPICALLTYPE *f_SHAssocEnumHandlers)(PCWSTR pszExtra, ASSOC_FILTER afFilter, IEnumAssocHandlers **ppEnumHandler); extern f_SHAssocEnumHandlers SHAssocEnumHandlers; typedef HRESULT (FAR STDAPICALLTYPE *f_SHCreateItemFromParsingName)(PCWSTR pszPath, IBindCtx *pbc, REFIID riid, void **ppv); extern f_SHCreateItemFromParsingName SHCreateItemFromParsingName; typedef HRESULT (FAR STDAPICALLTYPE *f_SHOpenWithDialog)(HWND hwndParent, const OPENASINFO *poainfo); extern f_SHOpenWithDialog SHOpenWithDialog; typedef HRESULT (FAR STDAPICALLTYPE *f_OpenAs_RunDLL)(HWND hWnd, HINSTANCE hInstance, LPCWSTR lpszCmdLine, int nCmdShow); extern f_OpenAs_RunDLL OpenAs_RunDLL; typedef HRESULT (FAR STDAPICALLTYPE *f_SHQueryUserNotificationState)(QUERY_USER_NOTIFICATION_STATE *pquns); extern f_SHQueryUserNotificationState SHQueryUserNotificationState; typedef void (FAR STDAPICALLTYPE *f_SHChangeNotify)(LONG wEventId, UINT uFlags, __in_opt LPCVOID dwItem1, __in_opt LPCVOID dwItem2); extern f_SHChangeNotify SHChangeNotify; typedef HRESULT (FAR STDAPICALLTYPE *f_SetCurrentProcessExplicitAppUserModelID)(__in PCWSTR AppID); extern f_SetCurrentProcessExplicitAppUserModelID SetCurrentProcessExplicitAppUserModelID; // WTSAPI32.DLL typedef BOOL (FAR STDAPICALLTYPE *f_WTSRegisterSessionNotification)(HWND hWnd, DWORD dwFlags); extern f_WTSRegisterSessionNotification WTSRegisterSessionNotification; typedef BOOL (FAR STDAPICALLTYPE *f_WTSUnRegisterSessionNotification)(HWND hWnd); extern f_WTSUnRegisterSessionNotification WTSUnRegisterSessionNotification; // PROPSYS.DLL typedef HRESULT (FAR STDAPICALLTYPE *f_PropVariantToString)(_In_ REFPROPVARIANT propvar, _Out_writes_(cch) PWSTR psz, _In_ UINT cch); extern f_PropVariantToString PropVariantToString; typedef HRESULT (FAR STDAPICALLTYPE *f_PSStringFromPropertyKey)(_In_ REFPROPERTYKEY pkey, _Out_writes_(cch) LPWSTR psz, _In_ UINT cch); extern f_PSStringFromPropertyKey PSStringFromPropertyKey; // COMBASE.DLL typedef HRESULT (FAR STDAPICALLTYPE *f_RoGetActivationFactory)(_In_ HSTRING activatableClassId, _In_ REFIID iid, _COM_Outptr_ void ** factory); extern f_RoGetActivationFactory RoGetActivationFactory; typedef HRESULT (FAR STDAPICALLTYPE *f_WindowsCreateStringReference)(_In_reads_opt_(length + 1) PCWSTR sourceString, UINT32 length, _Out_ HSTRING_HEADER * hstringHeader, _Outptr_result_maybenull_ _Result_nullonfailure_ HSTRING * string); extern f_WindowsCreateStringReference WindowsCreateStringReference; typedef HRESULT (FAR STDAPICALLTYPE *f_WindowsDeleteString)(_In_opt_ HSTRING string); extern f_WindowsDeleteString WindowsDeleteString; } // namespace Dlls } // namespace Platform ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/win/windows_event_filter.cpp��������������������������0000664�0000000�0000000�00000014762�13262451251�0027022�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "platform/win/windows_event_filter.h" #include "mainwindow.h" #include "auth_session.h" namespace Platform { namespace { EventFilter *instance = nullptr; int menuShown = 0, menuHidden = 0; } // namespace EventFilter *EventFilter::createInstance() { destroy(); instance = new EventFilter(); return getInstance(); } EventFilter *EventFilter::getInstance() { return instance; } void EventFilter::destroy() { delete instance; instance = nullptr; } bool EventFilter::nativeEventFilter(const QByteArray &eventType, void *message, long *result) { auto wnd = App::wnd(); if (!wnd) return false; MSG *msg = (MSG*)message; if (msg->message == WM_ENDSESSION) { App::quit(); return false; } if (msg->hwnd == wnd->psHwnd() || msg->hwnd && !wnd->psHwnd()) { return mainWindowEvent(msg->hwnd, msg->message, msg->wParam, msg->lParam, (LRESULT*)result); } return false; } bool EventFilter::mainWindowEvent(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, LRESULT *result) { using ShadowsChange = MainWindow::ShadowsChange; if (auto tbCreatedMsgId = Platform::MainWindow::TaskbarCreatedMsgId()) { if (msg == tbCreatedMsgId) { Platform::MainWindow::TaskbarCreated(); } } switch (msg) { case WM_TIMECHANGE: { if (AuthSession::Exists()) { Auth().checkAutoLockIn(100); } } return false; case WM_WTSSESSION_CHANGE: { if (wParam == WTS_SESSION_LOGOFF || wParam == WTS_SESSION_LOCK) { setSessionLoggedOff(true); } else if (wParam == WTS_SESSION_LOGON || wParam == WTS_SESSION_UNLOCK) { setSessionLoggedOff(false); } } return false; case WM_DESTROY: { App::quit(); } return false; case WM_ACTIVATE: { if (LOWORD(wParam) == WA_CLICKACTIVE) { App::wnd()->setInactivePress(true); } if (LOWORD(wParam) != WA_INACTIVE) { App::wnd()->shadowsActivate(); } else { App::wnd()->shadowsDeactivate(); } if (Global::started()) { App::wnd()->update(); } } return false; case WM_NCPAINT: { if (QSysInfo::WindowsVersion >= QSysInfo::WV_WINDOWS8) return false; if (result) *result = 0; } return true; case WM_NCCALCSIZE: { WINDOWPLACEMENT wp; wp.length = sizeof(WINDOWPLACEMENT); if (GetWindowPlacement(hWnd, &wp) && wp.showCmd == SW_SHOWMAXIMIZED) { LPNCCALCSIZE_PARAMS params = (LPNCCALCSIZE_PARAMS)lParam; LPRECT r = (wParam == TRUE) ? ¶ms->rgrc[0] : (LPRECT)lParam; HMONITOR hMonitor = MonitorFromPoint({ (r->left + r->right) / 2, (r->top + r->bottom) / 2 }, MONITOR_DEFAULTTONEAREST); if (hMonitor) { MONITORINFO mi; mi.cbSize = sizeof(mi); if (GetMonitorInfo(hMonitor, &mi)) { *r = mi.rcWork; } } } if (result) *result = 0; return true; } case WM_NCACTIVATE: { auto res = DefWindowProc(hWnd, msg, wParam, -1); if (result) *result = res; } return true; case WM_WINDOWPOSCHANGING: case WM_WINDOWPOSCHANGED: { WINDOWPLACEMENT wp; wp.length = sizeof(WINDOWPLACEMENT); if (GetWindowPlacement(hWnd, &wp) && (wp.showCmd == SW_SHOWMAXIMIZED || wp.showCmd == SW_SHOWMINIMIZED)) { App::wnd()->shadowsUpdate(ShadowsChange::Hidden); } else { App::wnd()->shadowsUpdate(ShadowsChange::Moved | ShadowsChange::Resized, (WINDOWPOS*)lParam); } } return false; case WM_SIZE: { if (App::wnd()) { if (wParam == SIZE_MAXIMIZED || wParam == SIZE_RESTORED || wParam == SIZE_MINIMIZED) { if (wParam != SIZE_RESTORED || App::wnd()->windowState() != Qt::WindowNoState) { Qt::WindowState state = Qt::WindowNoState; if (wParam == SIZE_MAXIMIZED) { state = Qt::WindowMaximized; } else if (wParam == SIZE_MINIMIZED) { state = Qt::WindowMinimized; } emit App::wnd()->windowHandle()->windowStateChanged(state); } else { App::wnd()->positionUpdated(); } App::wnd()->psUpdateMargins(); MainWindow::ShadowsChanges changes = (wParam == SIZE_MINIMIZED || wParam == SIZE_MAXIMIZED) ? ShadowsChange::Hidden : (ShadowsChange::Resized | ShadowsChange::Shown); App::wnd()->shadowsUpdate(changes); } } } return false; case WM_SHOWWINDOW: { LONG style = GetWindowLong(hWnd, GWL_STYLE); auto changes = ShadowsChange::Resized | ((wParam && !(style & (WS_MAXIMIZE | WS_MINIMIZE))) ? ShadowsChange::Shown : ShadowsChange::Hidden); App::wnd()->shadowsUpdate(changes); } return false; case WM_MOVE: { App::wnd()->shadowsUpdate(ShadowsChange::Moved); App::wnd()->positionUpdated(); } return false; case WM_NCHITTEST: { if (!result) return false; POINTS p = MAKEPOINTS(lParam); RECT r; GetWindowRect(hWnd, &r); auto res = App::wnd()->hitTest(QPoint(p.x - r.left + App::wnd()->deltaLeft(), p.y - r.top + App::wnd()->deltaTop())); switch (res) { case Window::HitTestResult::Client: case Window::HitTestResult::SysButton: *result = HTCLIENT; break; case Window::HitTestResult::Caption: *result = HTCAPTION; break; case Window::HitTestResult::Top: *result = HTTOP; break; case Window::HitTestResult::TopRight: *result = HTTOPRIGHT; break; case Window::HitTestResult::Right: *result = HTRIGHT; break; case Window::HitTestResult::BottomRight: *result = HTBOTTOMRIGHT; break; case Window::HitTestResult::Bottom: *result = HTBOTTOM; break; case Window::HitTestResult::BottomLeft: *result = HTBOTTOMLEFT; break; case Window::HitTestResult::Left: *result = HTLEFT; break; case Window::HitTestResult::TopLeft: *result = HTTOPLEFT; break; case Window::HitTestResult::None: default: *result = HTTRANSPARENT; break; }; } return true; case WM_NCRBUTTONUP: { SendMessage(hWnd, WM_SYSCOMMAND, SC_MOUSEMENU, lParam); } return true; case WM_SYSCOMMAND: { if (wParam == SC_MOUSEMENU) { POINTS p = MAKEPOINTS(lParam); App::wnd()->updateSystemMenu(App::wnd()->windowHandle()->windowState()); TrackPopupMenu(App::wnd()->psMenu(), TPM_LEFTALIGN | TPM_TOPALIGN | TPM_LEFTBUTTON, p.x, p.y, 0, hWnd, 0); } } return false; case WM_COMMAND: { if (HIWORD(wParam)) return false; int cmd = LOWORD(wParam); switch (cmd) { case SC_CLOSE: App::wnd()->close(); return true; case SC_MINIMIZE: App::wnd()->setWindowState(Qt::WindowMinimized); return true; case SC_MAXIMIZE: App::wnd()->setWindowState(Qt::WindowMaximized); return true; case SC_RESTORE: App::wnd()->setWindowState(Qt::WindowNoState); return true; } } return true; } return false; } } // namespace Platform ��������������tdesktop-1.2.17/Telegram/SourceFiles/platform/win/windows_event_filter.h����������������������������0000664�0000000�0000000�00000001552�13262451251�0026460�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <windows.h> namespace Platform { class EventFilter : public QAbstractNativeEventFilter { public: bool nativeEventFilter(const QByteArray &eventType, void *message, long *result); bool mainWindowEvent(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, LRESULT *result); bool sessionLoggedOff() const { return _sessionLoggedOff; } void setSessionLoggedOff(bool loggedOff) { _sessionLoggedOff = loggedOff; } static EventFilter *createInstance(); static EventFilter *getInstance(); static void destroy(); private: EventFilter() { } bool _sessionLoggedOff = false; }; } // namespace Platform ������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/platform/win/windows_range_v3_helpers.h������������������������0000664�0000000�0000000�00000000646�13262451251�0027223�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace ranges { inline namespace v3 { namespace { constexpr auto &&make_iterator_range = make_range; } // namespace } // inline namespace v3 } // namespace ranges ������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/profile/�������������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0021063�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/profile/profile.style������������������������������������������0000664�0000000�0000000�00000011335�13262451251�0023610�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ using "basic.style"; using "ui/widgets/widgets.style"; using "info/info.style"; profileBg: windowBg; profileTopBarHeight: topBarHeight; profileFixedBarButton: topBarButton; profileMarginTop: 13px; profilePhotoSize: 112px; profilePhotoLeftMin: 18px; profilePhotoLeftMax: 35px; profilePhotoDuration: 500; profileNameLeft: 26px; profileNameTop: 9px; profileNameLabel: FlatLabel(defaultFlatLabel) { margin: margins(10px, 5px, 10px, 5px); minWidth: 160px; maxHeight: 24px; textFg: windowBoldFg; style: TextStyle(defaultTextStyle) { font: font(16px semibold); linkFont: font(16px semibold); linkFontOver: font(16px semibold underline); } } profileStatusLeft: 27px; profileStatusTop: 35px; profileStatusFont: normalFont; profileStatusFg: windowSubTextFg; profileStatusFgActive: windowActiveTextFg; profileMarginBottom: 30px; profileButtonLeft: 27px; profileButtonTop: 88px; profileButtonSkip: 10px; profilePrimaryButton: defaultActiveButton; profileSecondaryButton: defaultLightButton; profileAddMemberButton: RoundButton(profileSecondaryButton) { width: 62px; icon: icon {{ "profile_add_member", lightButtonFg, point(20px, 10px) }}; } profileDropAreaBg: profileBg; profileDropAreaFg: lightButtonFg; profileDropAreaPadding: margins(25px, 3px, 25px, 20px); profileDropAreaTitleFont: font(24px); profileDropAreaTitleTop: 30px; profileDropAreaSubtitleFont: font(16px); profileDropAreaSubtitleTop: 68px; profileDropAreaBorderFg: profileDropAreaFg; profileDropAreaBorderWidth: 3px; profileDropAreaDuration: 200; profileDividerBg: windowBgOver; profileDividerFg: windowShadowFg; profileDividerLeft: icon {{ "profile_divider_left", profileDividerFg }}; profileDividerTop: icon {{ "profile_divider_top", profileDividerFg }}; profileDividerBottom: icon {{ "profile_divider_bottom", profileDividerFg }}; profileBlocksTop: 7px; profileBlocksBottom: 20px; profileBlockLeftMin: 8px; profileBlockLeftMax: 25px; profileBlockNarrowWidthMin: 220px; profileBlockWideWidthMin: 300px; profileBlockWideWidthMax: 340px; profileBlockMarginTop: 14px; profileBlockMarginRight: 10px; profileBlockMarginBottom: 7px; profileBlockTitleHeight: 24px; profileBlockTitleFont: font(14px semibold); profileBlockTitleFg: windowBoldFg; profileBlockTitlePosition: point(24px, 0px); profileBlockLabel: FlatLabel(defaultFlatLabel) { textFg: windowSubTextFg; } profileBlockTextPart: FlatLabel(defaultFlatLabel) { minWidth: 180px; margin: margins(5px, 5px, 5px, 5px); } profileBlockOneLineTextPart: FlatLabel(profileBlockTextPart) { minWidth: 0px; // No need to set minWidth in one-line text. maxHeight: 20px; } profileBioLabel: FlatLabel(profileBlockOneLineTextPart) { minWidth: 120px; maxHeight: 0px; } profileBlockOneLineSkip: 9px; profileBlockOneLineWidthMax: 240px; profileEnableNotificationsTop: 7px; profileSettingsBlockSkip: 8px; profileInviteLinkText: FlatLabel(profileBlockTextPart) { minWidth: 1px; // Required for BreakEverywhere } profileLimitReachedSkip: 6px; profileMemberItem: PeerListItem(defaultPeerListItem) { left: 8px; bottom: profileBlockMarginBottom; button: defaultLeftOutlineButton; maximalWidth: profileBlockWideWidthMax; statusFgOver: profileStatusFgOver; } profileMemberPaddingLeft: 16px; profileMemberNameFg: windowBoldFg; profileMemberCreatorIcon: icon {{ "profile_admin_star", profileAdminStartFg, point(4px, 3px) }}; profileMemberCreatorIconOver: icon {{ "profile_admin_star", profileAdminStarFgOver, point(4px, 3px) }}; profileMemberAdminIcon: icon {{ "profile_admin_star", profileOtherAdminStarFg, point(4px, 3px) }}; profileMemberAdminIconOver: icon {{ "profile_admin_star", profileOtherAdminStarFgOver, point(4px, 3px) }}; profileLimitReachedLabel: FlatLabel(defaultFlatLabel) { minWidth: 180px; margin: margins(profileMemberPaddingLeft, 9px, profileMemberPaddingLeft, 6px); style: TextStyle(defaultTextStyle) { lineHeight: 19px; } } profileReportReasonOther: InputField(defaultInputField) { textMargins: margins(1px, 26px, 1px, 4px); heightMax: 135px; } profileVerifiedCheckShift: -3px; profileVerifiedCheck: icon { { "profile_verified_star", profileVerifiedCheckBg, point(0px, 7px) }, { "profile_verified_check", profileVerifiedCheckFg, point(4px, 11px) } }; profileCommonGroupsSkip: 24px; profileCommonGroupsLeftMin: 24px; profileCommonGroupsLeftMax: 36px; profileCommonGroupsWidthMax: 480px; profileCommonGroupsPadding: margins(7px, 7px, 7px, 7px); profileCommonGroupsPhotoSize: 42px; profileCommonGroupsNameTop: 12px; profileCommonGroupsNameLeft: 16px; profileCommonGroupsBgOver: windowBgOver; profileCommonGroupsRipple: defaultRippleAnimation; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/profile/profile_back_button.cpp��������������������������������0000664�0000000�0000000�00000003510�13262451251�0025601�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "profile/profile_back_button.h" //#include "history/view/history_view_top_bar_widget.h" #include "styles/style_widgets.h" #include "styles/style_window.h" #include "styles/style_profile.h" #include "styles/style_info.h" namespace Profile { BackButton::BackButton(QWidget *parent, const QString &text) : Ui::AbstractButton(parent) , _text(text.toUpper()) { setCursor(style::cur_pointer); subscribe(Adaptive::Changed(), [this] { updateAdaptiveLayout(); }); updateAdaptiveLayout(); } void BackButton::setText(const QString &text) { _text = text.toUpper(); update(); } int BackButton::resizeGetHeight(int newWidth) { return st::profileTopBarHeight; } void BackButton::paintEvent(QPaintEvent *e) { Painter p(this); p.fillRect(e->rect(), st::profileBg); st::topBarBack.paint(p, (st::topBarArrowPadding.left() - st::topBarBack.width()) / 2, (st::topBarHeight - st::topBarBack.height()) / 2, width()); p.setFont(st::topBarButton.font); p.setPen(st::topBarButton.textFg); p.drawTextLeft(st::topBarArrowPadding.left(), st::topBarButton.padding.top() + st::topBarButton.textTop, width(), _text); } void BackButton::onStateChanged(State was, StateChangeSource source) { if (isDown() && !(was & StateFlag::Down)) { emit clicked(); } } void BackButton::updateAdaptiveLayout() { if (!Adaptive::OneColumn()) { unsubscribe(base::take(_unreadCounterSubscription)); } else if (!_unreadCounterSubscription) { _unreadCounterSubscription = subscribe(Global::RefUnreadCounterUpdate(), [this] { rtlupdate(0, 0, st::titleUnreadCounterRight, st::titleUnreadCounterTop); }); } } } // namespace Profile ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/profile/profile_back_button.h����������������������������������0000664�0000000�0000000�00000001406�13262451251�0025250�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/abstract_button.h" namespace Profile { class BackButton final : public Ui::AbstractButton, private base::Subscriber { public: BackButton(QWidget *parent, const QString &text); void setText(const QString &text); protected: void paintEvent(QPaintEvent *e) override; int resizeGetHeight(int newWidth) override; void onStateChanged(State was, StateChangeSource source) override; private: void updateAdaptiveLayout(); int _unreadCounterSubscription = 0; QString _text; }; } // namespace Profile ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/profile/profile_block_group_members.cpp������������������������0000664�0000000�0000000�00000043667�13262451251�0027347�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "profile/profile_block_group_members.h" #include "styles/style_profile.h" #include "ui/widgets/labels.h" #include "boxes/confirm_box.h" #include "boxes/edit_participant_box.h" #include "ui/widgets/popup_menu.h" #include "data/data_peer_values.h" #include "mainwidget.h" #include "apiwrap.h" #include "observer_peer.h" #include "auth_session.h" #include "lang/lang_keys.h" namespace Profile { using UpdateFlag = Notify::PeerUpdate::Flag; GroupMembersWidget::GroupMembersWidget( QWidget *parent, PeerData *peer, TitleVisibility titleVisibility, const style::PeerListItem &st) : PeerListWidget(parent , peer , (titleVisibility == TitleVisibility::Visible) ? lang(lng_profile_participants_section) : QString() , st , lang(lng_profile_kick)) { _updateOnlineTimer.setSingleShot(true); connect(&_updateOnlineTimer, SIGNAL(timeout()), this, SLOT(onUpdateOnlineDisplay())); auto observeEvents = UpdateFlag::AdminsChanged | UpdateFlag::MembersChanged | UpdateFlag::UserOnlineChanged; subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(observeEvents, [this](const Notify::PeerUpdate &update) { notifyPeerUpdated(update); })); setRemovedCallback([=](PeerData *selectedPeer) { removePeer(selectedPeer); }); setSelectedCallback([=](PeerData *selectedPeer) { Ui::showPeerProfile(selectedPeer); }); setUpdateItemCallback([=](Item *item) { updateItemStatusText(item); }); setPreloadMoreCallback([=] { preloadMore(); }); refreshMembers(); } void GroupMembersWidget::editAdmin(not_null<UserData*> user) { auto megagroup = peer()->asMegagroup(); if (!megagroup) { return; // not supported } auto currentRightsIt = megagroup->mgInfo->lastAdmins.find(user); auto hasAdminRights = (currentRightsIt != megagroup->mgInfo->lastAdmins.cend()); auto currentRights = hasAdminRights ? currentRightsIt->second.rights : MTP_channelAdminRights(MTP_flags(0)); auto weak = QPointer<GroupMembersWidget>(this); auto box = Box<EditAdminBox>(megagroup, user, currentRights); box->setSaveCallback([weak, megagroup, user](const MTPChannelAdminRights &oldRights, const MTPChannelAdminRights &newRights) { Ui::hideLayer(); MTP::send(MTPchannels_EditAdmin(megagroup->inputChannel, user->inputUser, newRights), rpcDone([weak, megagroup, user, oldRights, newRights](const MTPUpdates &result) { if (App::main()) App::main()->sentUpdatesReceived(result); megagroup->applyEditAdmin(user, oldRights, newRights); })); }); Ui::show(std::move(box)); } void GroupMembersWidget::restrictUser(not_null<UserData*> user) { auto megagroup = peer()->asMegagroup(); if (!megagroup) { return; // not supported } auto currentRightsIt = megagroup->mgInfo->lastRestricted.find(user); auto currentRights = (currentRightsIt != megagroup->mgInfo->lastRestricted.end()) ? currentRightsIt->second.rights : MTP_channelBannedRights(MTP_flags(0), MTP_int(0)); auto hasAdminRights = megagroup->mgInfo->lastAdmins.find(user) != megagroup->mgInfo->lastAdmins.cend(); auto box = Box<EditRestrictedBox>(megagroup, user, hasAdminRights, currentRights); box->setSaveCallback([megagroup, user](const MTPChannelBannedRights &oldRights, const MTPChannelBannedRights &newRights) { Ui::hideLayer(); MTP::send(MTPchannels_EditBanned(megagroup->inputChannel, user->inputUser, newRights), rpcDone([megagroup, user, oldRights, newRights](const MTPUpdates &result) { if (App::main()) App::main()->sentUpdatesReceived(result); megagroup->applyEditBanned(user, oldRights, newRights); })); }); Ui::show(std::move(box)); } void GroupMembersWidget::removePeer(PeerData *selectedPeer) { auto user = selectedPeer->asUser(); Assert(user != nullptr); auto text = lng_profile_sure_kick(lt_user, user->firstName); auto currentRestrictedRights = [&]() -> MTPChannelBannedRights { if (auto channel = peer()->asMegagroup()) { auto it = channel->mgInfo->lastRestricted.find(user); if (it != channel->mgInfo->lastRestricted.cend()) { return it->second.rights; } } return MTP_channelBannedRights(MTP_flags(0), MTP_int(0)); }(); Ui::show(Box<ConfirmBox>(text, lang(lng_box_remove), [user, currentRestrictedRights, peer = peer()] { Ui::hideLayer(); if (const auto chat = peer->asChat()) { Auth().api().kickParticipant(chat, user); Ui::showPeerHistory(chat->id, ShowAtTheEndMsgId); } else if (const auto channel = peer->asChannel()) { Auth().api().kickParticipant( channel, user, currentRestrictedRights); } })); } void GroupMembersWidget::notifyPeerUpdated(const Notify::PeerUpdate &update) { if (update.peer != peer()) { if (update.flags & UpdateFlag::UserOnlineChanged) { if (auto user = update.peer->asUser()) { refreshUserOnline(user); } } return; } if (update.flags & UpdateFlag::MembersChanged) { refreshMembers(); contentSizeUpdated(); } else if (update.flags & UpdateFlag::AdminsChanged) { if (auto chat = peer()->asChat()) { for_const (auto item, items()) { setItemFlags(getMember(item), chat); } } else if (auto megagroup = peer()->asMegagroup()) { for_const (auto item, items()) { setItemFlags(getMember(item), megagroup); } } } this->update(); } void GroupMembersWidget::refreshUserOnline(UserData *user) { auto it = _membersByUser.find(user); if (it == _membersByUser.cend()) return; _now = unixtime(); auto member = getMember(it.value()); member->statusHasOnlineColor = !user->botInfo && Data::OnlineTextActive(user->onlineTill, _now); member->onlineTill = user->onlineTill; member->onlineForSort = user->isSelf() ? std::numeric_limits<TimeId>::max() : Data::SortByOnlineValue(user, _now); member->statusText = QString(); sortMembers(); update(); } void GroupMembersWidget::preloadMore() { // // This can cause a ddos, because lastParticipants may never reach members count. // //if (auto megagroup = peer()->asMegagroup()) { // auto &megagroupInfo = megagroup->mgInfo; // if (!megagroupInfo->lastParticipants.isEmpty() && megagroupInfo->lastParticipants.size() < megagroup->membersCount()) { // Auth().api().requestLastParticipants(megagroup, false); // } //} } int GroupMembersWidget::resizeGetHeight(int newWidth) { if (_limitReachedInfo) { int limitReachedInfoWidth = newWidth - getListLeft(); accumulate_min(limitReachedInfoWidth, st::profileBlockWideWidthMax); _limitReachedInfo->resizeToWidth(limitReachedInfoWidth); _limitReachedInfo->moveToLeft(getListLeft(), contentTop()); } return PeerListWidget::resizeGetHeight(newWidth); } void GroupMembersWidget::paintContents(Painter &p) { int left = getListLeft(); int top = getListTop(); int memberRowWidth = width() - left; accumulate_min(memberRowWidth, st::profileBlockWideWidthMax); if (_limitReachedInfo) { int infoTop = contentTop(); int infoHeight = top - infoTop - st::profileLimitReachedSkip; paintOutlinedRect(p, left, infoTop, memberRowWidth, infoHeight); } _now = unixtime(); PeerListWidget::paintContents(p); } Ui::PopupMenu *GroupMembersWidget::fillPeerMenu(PeerData *selectedPeer) { Expects(selectedPeer->isUser()); if (emptyTitle()) { return nullptr; } auto user = selectedPeer->asUser(); auto result = new Ui::PopupMenu(nullptr); result->addAction(lang(lng_context_view_profile), [selectedPeer] { Ui::showPeerProfile(selectedPeer); }); auto chat = peer()->asChat(); auto channel = peer()->asMegagroup(); for_const (auto item, items()) { if (item->peer == selectedPeer) { auto canRemoveAdmin = [item, chat, channel] { if ((item->adminState == Item::AdminState::Admin) && !item->peer->isSelf()) { if (chat) { // Adding of admins from context menu of chat participants // is not supported, so the removing is also disabled. return false;//chat->amCreator(); } else if (channel) { return channel->amCreator(); } } return false; }; if (channel) { if (channel->canEditAdmin(user)) { auto label = lang((item->adminState != Item::AdminState::None) ? lng_context_edit_permissions : lng_context_promote_admin); result->addAction(label, base::lambda_guarded(this, [this, user] { editAdmin(user); })); } if (channel->canRestrictUser(user)) { result->addAction(lang(lng_context_restrict_user), base::lambda_guarded(this, [this, user] { restrictUser(user); })); result->addAction(lang(lng_context_remove_from_group), base::lambda_guarded(this, [this, selectedPeer] { removePeer(selectedPeer); })); } } else if (item->hasRemoveLink) { result->addAction(lang(lng_context_remove_from_group), base::lambda_guarded(this, [this, selectedPeer] { removePeer(selectedPeer); })); } } } return result; } void GroupMembersWidget::updateItemStatusText(Item *item) { auto member = getMember(item); auto user = member->user(); if (member->statusText.isEmpty() || (member->onlineTextTill <= _now)) { if (user->botInfo) { auto seesAllMessages = (user->botInfo->readsAllHistory || (member->adminState != Item::AdminState::None)); member->statusText = lang(seesAllMessages ? lng_status_bot_reads_all : lng_status_bot_not_reads_all); member->onlineTextTill = _now + 86400; } else { member->statusHasOnlineColor = Data::OnlineTextActive(member->onlineTill, _now); member->statusText = Data::OnlineText(member->onlineTill, _now); const auto changeInMs = Data::OnlineChangeTimeout( member->onlineTill, _now); member->onlineTextTill = _now + TimeId(changeInMs / 1000); } } if (_updateOnlineAt <= _now || _updateOnlineAt > member->onlineTextTill) { _updateOnlineAt = member->onlineTextTill; _updateOnlineTimer.start((_updateOnlineAt - _now + 1) * 1000); } } int GroupMembersWidget::getListTop() const { int result = contentTop(); if (_limitReachedInfo) { result += _limitReachedInfo->height(); result += st::profileLimitReachedSkip; } return result; } void GroupMembersWidget::refreshMembers() { _now = unixtime(); if (auto chat = peer()->asChat()) { checkSelfAdmin(chat); if (chat->noParticipantInfo()) { Auth().api().requestFullPeer(chat); } fillChatMembers(chat); refreshLimitReached(); } else if (auto megagroup = peer()->asMegagroup()) { auto &megagroupInfo = megagroup->mgInfo; if (megagroupInfo->lastParticipants.empty() || megagroup->lastParticipantsCountOutdated()) { Auth().api().requestLastParticipants(megagroup); } fillMegagroupMembers(megagroup); } sortMembers(); refreshVisibility(); } void GroupMembersWidget::refreshLimitReached() { auto chat = peer()->asChat(); if (!chat) return; bool limitReachedShown = (itemsCount() >= Global::ChatSizeMax()) && chat->amCreator() && !emptyTitle(); if (limitReachedShown && !_limitReachedInfo) { _limitReachedInfo.create(this, st::profileLimitReachedLabel); QString title = TextUtilities::EscapeForRichParsing(lng_profile_migrate_reached(lt_count, Global::ChatSizeMax())); QString body = TextUtilities::EscapeForRichParsing(lang(lng_profile_migrate_body)); QString link = TextUtilities::EscapeForRichParsing(lang(lng_profile_migrate_learn_more)); QString text = qsl("%1%2%3\n%4 [a href=\"https://telegram.org/blog/supergroups5k\"]%5[/a]").arg(textcmdStartSemibold()).arg(title).arg(textcmdStopSemibold()).arg(body).arg(link); _limitReachedInfo->setRichText(text); _limitReachedInfo->setClickHandlerHook([this](const ClickHandlerPtr &handler, Qt::MouseButton button) { Ui::show(Box<ConvertToSupergroupBox>(peer()->asChat())); return false; }); } else if (!limitReachedShown && _limitReachedInfo) { _limitReachedInfo.destroy(); } } void GroupMembersWidget::checkSelfAdmin(ChatData *chat) { if (chat->participants.empty()) return; auto self = App::self(); if (chat->amAdmin() && !chat->admins.contains(self)) { chat->admins.insert(self); } else if (!chat->amAdmin() && chat->admins.contains(self)) { chat->admins.remove(self); } } void GroupMembersWidget::sortMembers() { if (!_sortByOnline || !itemsCount()) return; sortItems([this](Item *a, Item *b) { return getMember(a)->onlineForSort > getMember(b)->onlineForSort; }); updateOnlineCount(); } void GroupMembersWidget::updateOnlineCount() { bool onlyMe = true; int newOnlineCount = 0; for_const (auto item, items()) { auto member = getMember(item); auto user = member->user(); auto isOnline = !user->botInfo && Data::OnlineTextActive(member->onlineTill, _now); if (member->statusHasOnlineColor != isOnline) { member->statusHasOnlineColor = isOnline; member->statusText = QString(); } if (member->statusHasOnlineColor) { ++newOnlineCount; if (!user->isSelf()) { onlyMe = false; } } } if (newOnlineCount == 1 && onlyMe) { newOnlineCount = 0; } if (_onlineCount != newOnlineCount) { _onlineCount = newOnlineCount; emit onlineCountUpdated(_onlineCount); } } GroupMembersWidget::Member *GroupMembersWidget::addUser(ChatData *chat, UserData *user) { auto member = computeMember(user); setItemFlags(member, chat); addItem(member); return member; } void GroupMembersWidget::fillChatMembers(ChatData *chat) { if (chat->participants.empty()) return; clearItems(); if (!chat->amIn()) return; _sortByOnline = true; reserveItemsForSize(chat->participants.size()); addUser(chat, App::self())->onlineForSort = std::numeric_limits<TimeId>::max(); for (auto [user, v] : chat->participants) { if (!user->isSelf()) { addUser(chat, user); } } } void GroupMembersWidget::setItemFlags(Item *item, ChatData *chat) { using AdminState = Item::AdminState; auto user = getMember(item)->user(); auto isCreator = (peerFromUser(chat->creator) == item->peer->id); auto isAdmin = chat->adminsEnabled() && chat->admins.contains(user); auto adminState = isCreator ? AdminState::Creator : isAdmin ? AdminState::Admin : AdminState::None; item->adminState = adminState; if (item->peer->id == Auth().userPeerId()) { item->hasRemoveLink = false; } else if (chat->amCreator() || (chat->amAdmin() && (adminState == AdminState::None))) { item->hasRemoveLink = true; } else if (chat->invitedByMe.contains(user) && (adminState == AdminState::None)) { item->hasRemoveLink = true; } else { item->hasRemoveLink = false; } } GroupMembersWidget::Member *GroupMembersWidget::addUser(ChannelData *megagroup, UserData *user) { auto member = computeMember(user); setItemFlags(member, megagroup); addItem(member); return member; } void GroupMembersWidget::fillMegagroupMembers(ChannelData *megagroup) { Assert(megagroup->mgInfo != nullptr); if (megagroup->mgInfo->lastParticipants.empty()) return; if (!megagroup->canViewMembers()) { clearItems(); return; } _sortByOnline = (megagroup->membersCount() > 0 && megagroup->membersCount() <= Global::ChatSizeMax()); auto &membersList = megagroup->mgInfo->lastParticipants; if (_sortByOnline) { clearItems(); reserveItemsForSize(membersList.size()); if (megagroup->amIn()) { addUser(megagroup, App::self())->onlineForSort = std::numeric_limits<TimeId>::max(); } } else if (membersList.size() >= itemsCount()) { if (addUsersToEnd(megagroup)) { return; } } if (!_sortByOnline) { clearItems(); reserveItemsForSize(membersList.size()); } for_const (auto user, membersList) { if (!_sortByOnline || !user->isSelf()) { addUser(megagroup, user); } } } bool GroupMembersWidget::addUsersToEnd(ChannelData *megagroup) { auto &membersList = megagroup->mgInfo->lastParticipants; auto &itemsList = items(); for (int i = 0, count = itemsList.size(); i < count; ++i) { if (itemsList[i]->peer != membersList.at(i)) { return false; } } reserveItemsForSize(membersList.size()); for (int i = itemsCount(), count = membersList.size(); i < count; ++i) { addUser(megagroup, membersList.at(i)); } return true; } void GroupMembersWidget::setItemFlags(Item *item, ChannelData *megagroup) { using AdminState = Item::AdminState; auto amCreator = item->peer->isSelf() && megagroup->amCreator(); auto amAdmin = item->peer->isSelf() && megagroup->hasAdminRights(); auto adminIt = megagroup->mgInfo->lastAdmins.find(getMember(item)->user()); auto isAdmin = (adminIt != megagroup->mgInfo->lastAdmins.cend()); auto isCreator = megagroup->mgInfo->creator == item->peer; auto adminCanEdit = isAdmin && adminIt->second.canEdit; auto adminState = (amCreator || isCreator) ? AdminState::Creator : (amAdmin || isAdmin) ? AdminState::Admin : AdminState::None; if (item->adminState != adminState) { item->adminState = adminState; auto user = item->peer->asUser(); Assert(user != nullptr); if (user->botInfo) { // Update "has access to messages" status. item->statusText = QString(); updateItemStatusText(item); } } if (item->peer->isSelf()) { item->hasRemoveLink = false; } else if (megagroup->amCreator() || (megagroup->canBanMembers() && ((adminState == AdminState::None) || adminCanEdit))) { item->hasRemoveLink = true; } else { item->hasRemoveLink = false; } } GroupMembersWidget::Member *GroupMembersWidget::computeMember(UserData *user) { auto it = _membersByUser.constFind(user); if (it == _membersByUser.cend()) { auto member = new Member(user); it = _membersByUser.insert(user, member); member->statusHasOnlineColor = !user->botInfo && Data::OnlineTextActive(user->onlineTill, _now); member->onlineTill = user->onlineTill; member->onlineForSort = Data::SortByOnlineValue(user, _now); } return it.value(); } void GroupMembersWidget::onUpdateOnlineDisplay() { if (_sortByOnline) { _now = unixtime(); bool changed = false; for_const (auto item, items()) { if (!item->statusHasOnlineColor) { if (!item->peer->isSelf()) { continue; } else { break; } } auto member = getMember(item); bool isOnline = !member->user()->botInfo && Data::OnlineTextActive(member->onlineTill, _now); if (!isOnline) { changed = true; } } if (changed) { updateOnlineCount(); } } update(); } GroupMembersWidget::~GroupMembersWidget() { auto members = base::take(_membersByUser); for_const (auto member, members) { delete member; } } } // namespace Profile �������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/profile/profile_block_group_members.h��������������������������0000664�0000000�0000000�00000005233�13262451251�0026777�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "profile/profile_block_peer_list.h" namespace Ui { class FlatLabel; class LeftOutlineButton; } // namespace Ui namespace Notify { struct PeerUpdate; } // namespace Notify namespace Profile { class GroupMembersWidget : public PeerListWidget { Q_OBJECT public: enum class TitleVisibility { Visible, Hidden, }; GroupMembersWidget(QWidget *parent, PeerData *peer, TitleVisibility titleVisibility = TitleVisibility::Visible, const style::PeerListItem &st = st::profileMemberItem); int onlineCount() const { return _onlineCount; } ~GroupMembersWidget(); protected: // Resizes content and counts natural widget height for the desired width. int resizeGetHeight(int newWidth) override; void paintContents(Painter &p) override; Ui::PopupMenu *fillPeerMenu(PeerData *peer) override; signals: void onlineCountUpdated(int onlineCount); private slots: void onUpdateOnlineDisplay(); private: // Observed notifications. void notifyPeerUpdated(const Notify::PeerUpdate &update); void editAdmin(not_null<UserData*> user); void restrictUser(not_null<UserData*> user); void removePeer(PeerData *selectedPeer); void refreshMembers(); void fillChatMembers(ChatData *chat); void fillMegagroupMembers(ChannelData *megagroup); void sortMembers(); void updateOnlineCount(); void checkSelfAdmin(ChatData *chat); void refreshLimitReached(); void preloadMore(); bool limitReachedHook(const ClickHandlerPtr &handler, Qt::MouseButton button); void refreshUserOnline(UserData *user); int getListTop() const override; struct Member : public Item { explicit Member(UserData *user) : Item(user) { } UserData *user() const { return static_cast<UserData*>(peer); } TimeId onlineTextTill = 0; TimeId onlineTill = 0; TimeId onlineForSort = 0; }; Member *getMember(Item *item) { return static_cast<Member*>(item); } void updateItemStatusText(Item *item); Member *computeMember(UserData *user); Member *addUser(ChatData *chat, UserData *user); Member *addUser(ChannelData *megagroup, UserData *user); void setItemFlags(Item *item, ChatData *chat); void setItemFlags(Item *item, ChannelData *megagroup); bool addUsersToEnd(ChannelData *megagroup); object_ptr<Ui::FlatLabel> _limitReachedInfo = { nullptr }; QMap<UserData*, Member*> _membersByUser; bool _sortByOnline = false; TimeId _now = 0; int _onlineCount = 0; TimeId _updateOnlineAt = 0; QTimer _updateOnlineTimer; }; } // namespace Profile ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/profile/profile_block_peer_list.cpp����������������������������0000664�0000000�0000000�00000022447�13262451251�0026460�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "profile/profile_block_peer_list.h" #include "ui/effects/ripple_animation.h" #include "ui/widgets/popup_menu.h" #include "ui/text_options.h" #include "styles/style_profile.h" #include "styles/style_widgets.h" #include "auth_session.h" namespace Profile { PeerListWidget::Item::Item(PeerData *peer) : peer(peer) { } PeerListWidget::Item::~Item() = default; PeerListWidget::PeerListWidget(QWidget *parent, PeerData *peer, const QString &title, const style::PeerListItem &st, const QString &removeText) : BlockWidget(parent, peer, title) , _st(st) , _removeText(removeText) , _removeWidth(st::normalFont->width(_removeText)) { setMouseTracking(true); subscribe(Auth().downloaderTaskFinished(), [this] { update(); }); } int PeerListWidget::resizeGetHeight(int newWidth) { auto newHeight = getListTop(); newHeight += _items.size() * _st.height; return newHeight + _st.bottom; } void PeerListWidget::visibleTopBottomUpdated(int visibleTop, int visibleBottom) { _visibleTop = visibleTop; _visibleBottom = visibleBottom; if (_preloadMoreCallback) { if (_visibleTop + PreloadHeightsCount * (_visibleBottom - _visibleTop) > height()) { _preloadMoreCallback(); } } } void PeerListWidget::paintContents(Painter &p) { auto ms = getms(); auto left = getListLeft(); auto top = getListTop(); auto memberRowWidth = rowWidth(); auto from = floorclamp(_visibleTop - top, _st.height, 0, _items.size()); auto to = ceilclamp(_visibleBottom - top, _st.height, 0, _items.size()); for (auto i = from; i < to; ++i) { auto y = top + i * _st.height; auto selected = (_menuRowIndex >= 0) ? (i == _menuRowIndex) : (_pressed >= 0) ? (i == _pressed) : (i == _selected); auto selectedRemove = selected && _selectedRemove; if (_pressed >= 0 && !_pressedRemove) { selectedRemove = false; } paintItem(p, left, y, _items[i], selected, selectedRemove, ms); } } void PeerListWidget::paintItem(Painter &p, int x, int y, Item *item, bool selected, bool selectedKick, TimeMs ms) { if (_updateItemCallback) { _updateItemCallback(item); } auto memberRowWidth = rowWidth(); if (selected) { paintOutlinedRect(p, x, y, memberRowWidth, _st.height); } if (auto &ripple = item->ripple) { ripple->paint(p, x + _st.button.outlineWidth, y, width(), ms); if (ripple->empty()) { ripple.reset(); } } int skip = _st.photoPosition.x(); item->peer->paintUserpicLeft(p, x + _st.photoPosition.x(), y + _st.photoPosition.y(), width(), _st.photoSize); if (item->name.isEmpty()) { item->name.setText( st::msgNameStyle, App::peerName(item->peer), Ui::NameTextOptions()); } int nameLeft = x + _st.namePosition.x(); int nameTop = y + _st.namePosition.y(); int nameWidth = memberRowWidth - _st.namePosition.x() - skip; if (item->hasRemoveLink && selected) { p.setFont(selectedKick ? st::normalFont->underline() : st::normalFont); p.setPen(st::windowActiveTextFg); p.drawTextLeft(nameLeft + nameWidth - _removeWidth, nameTop, width(), _removeText, _removeWidth); nameWidth -= _removeWidth + skip; } if (item->adminState != Item::AdminState::None) { nameWidth -= st::profileMemberAdminIcon.width(); auto iconLeft = nameLeft + qMin(nameWidth, item->name.maxWidth()); auto &icon = (item->adminState == Item::AdminState::Creator) ? (selected ? st::profileMemberCreatorIconOver : st::profileMemberCreatorIcon) : (selected ? st::profileMemberAdminIconOver : st::profileMemberAdminIcon); icon.paint(p, QPoint(iconLeft, nameTop), width()); } p.setPen(st::profileMemberNameFg); item->name.drawLeftElided(p, nameLeft, nameTop, nameWidth, width()); if (item->statusHasOnlineColor) { p.setPen(_st.statusFgActive); } else { p.setPen(selected ? _st.statusFgOver : _st.statusFg); } p.setFont(st::normalFont); p.drawTextLeft(x + _st.statusPosition.x(), y + _st.statusPosition.y(), width(), item->statusText); } void PeerListWidget::paintOutlinedRect(Painter &p, int x, int y, int w, int h) const { auto outlineWidth = _st.button.outlineWidth; if (outlineWidth) { p.fillRect(rtlrect(x, y, outlineWidth, h, width()), _st.button.outlineFgOver); } p.fillRect(rtlrect(x + outlineWidth, y, w - outlineWidth, h, width()), _st.button.textBgOver); } void PeerListWidget::mouseMoveEvent(QMouseEvent *e) { _mousePosition = e->globalPos(); updateSelection(); } void PeerListWidget::mousePressEvent(QMouseEvent *e) { _mousePosition = e->globalPos(); updateSelection(); _pressButton = e->button(); _pressed = _selected; _pressedRemove = _selectedRemove; if (_pressed >= 0 && !_pressedRemove) { auto item = _items[_pressed]; if (!item->ripple) { auto memberRowWidth = rowWidth(); auto mask = Ui::RippleAnimation::rectMask(QSize(memberRowWidth - _st.button.outlineWidth, _st.height)); item->ripple = std::make_unique<Ui::RippleAnimation>(_st.button.ripple, std::move(mask), [this, index = _pressed] { repaintRow(index); }); } auto left = getListLeft() + _st.button.outlineWidth; auto top = getListTop() + _st.height * _pressed; item->ripple->add(e->pos() - QPoint(left, top)); } } void PeerListWidget::mouseReleaseEvent(QMouseEvent *e) { mousePressReleased(e->button()); } void PeerListWidget::mousePressReleased(Qt::MouseButton button) { repaintRow(_pressed); auto pressed = std::exchange(_pressed, -1); auto pressedRemove = base::take(_pressedRemove); if (pressed >= 0 && pressed < _items.size()) { if (auto &ripple = _items[pressed]->ripple) { ripple->lastStop(); } if (pressed == _selected && pressedRemove == _selectedRemove && button == Qt::LeftButton) { InvokeQueued(this, [this, pressedRemove, peer = _items[pressed]->peer] { if (auto &callback = (pressedRemove ? _removedCallback : _selectedCallback)) { callback(peer); } }); } } setCursor(_selectedRemove ? style::cur_pointer : style::cur_default); repaintSelectedRow(); } void PeerListWidget::contextMenuEvent(QContextMenuEvent *e) { if (_menu) { _menu->deleteLater(); _menu = nullptr; } if (_menuRowIndex >= 0) { repaintRow(_menuRowIndex); _menuRowIndex = -1; } if (e->reason() == QContextMenuEvent::Mouse) { _mousePosition = e->globalPos(); updateSelection(); } _menuRowIndex = _selected; if (_pressButton != Qt::LeftButton) { mousePressReleased(_pressButton); } if (_selected < 0 || _selected >= _items.size()) { return; } _menu = fillPeerMenu(_items[_selected]->peer); if (_menu) { _menu->setDestroyedCallback(base::lambda_guarded(this, [this, menu = _menu] { if (_menu == menu) { _menu = nullptr; } repaintRow(_menuRowIndex); _menuRowIndex = -1; _mousePosition = QCursor::pos(); updateSelection(); })); _menu->popup(e->globalPos()); e->accept(); } } void PeerListWidget::enterEventHook(QEvent *e) { _mousePosition = QCursor::pos(); updateSelection(); } void PeerListWidget::leaveEventHook(QEvent *e) { _mousePosition = QPoint(-1, -1); updateSelection(); } void PeerListWidget::updateSelection() { auto selected = -1; auto selectedKick = false; auto mouse = mapFromGlobal(_mousePosition); if (rtl()) mouse.setX(width() - mouse.x()); auto left = getListLeft(); auto top = getListTop(); auto memberRowWidth = rowWidth(); if (mouse.x() >= left && mouse.x() < left + memberRowWidth && mouse.y() >= top) { selected = (mouse.y() - top) / _st.height; if (selected >= _items.size()) { selected = -1; } else if (_items[selected]->hasRemoveLink) { int skip = _st.photoPosition.x(); int nameLeft = left + _st.namePosition.x(); int nameTop = top + _selected * _st.height + _st.namePosition.y(); int nameWidth = memberRowWidth - _st.namePosition.x() - skip; if (mouse.x() >= nameLeft + nameWidth - _removeWidth && mouse.x() < nameLeft + nameWidth) { if (mouse.y() >= nameTop && mouse.y() < nameTop + st::normalFont->height) { selectedKick = true; } } } } setSelected(selected, selectedKick); } void PeerListWidget::setSelected(int selected, bool selectedRemove) { if (_selected == selected && _selectedRemove == selectedRemove) { return; } repaintSelectedRow(); if (_selectedRemove != selectedRemove) { _selectedRemove = selectedRemove; if (_pressed < 0) { setCursor(_selectedRemove ? style::cur_pointer : style::cur_default); } } if (_selected != selected) { _selected = selected; repaintSelectedRow(); } } void PeerListWidget::repaintSelectedRow() { repaintRow(_selected); } void PeerListWidget::repaintRow(int index) { if (index >= 0) { auto left = getListLeft(); rtlupdate(left, getListTop() + index * _st.height, width() - left, _st.height); } } int PeerListWidget::getListLeft() const { return _st.left; } int PeerListWidget::rowWidth() const { return _st.maximalWidth ? qMin(width() - getListLeft(), _st.maximalWidth) : width() - getListLeft(); } void PeerListWidget::preloadPhotos() { int top = getListTop(); int preloadFor = (_visibleBottom - _visibleTop) * PreloadHeightsCount; int from = floorclamp(_visibleTop - top, _st.height, 0, _items.size()); int to = ceilclamp(_visibleBottom + preloadFor - top, _st.height, 0, _items.size()); for (int i = from; i < to; ++i) { _items[i]->peer->loadUserpic(); } } void PeerListWidget::refreshVisibility() { setVisible(!_items.isEmpty()); } } // namespace Profile �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/profile/profile_block_peer_list.h������������������������������0000664�0000000�0000000�00000007356�13262451251�0026127�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "profile/profile_block_widget.h" #include "styles/style_profile.h" namespace Ui { class RippleAnimation; class PopupMenu; } // namespace Ui namespace Notify { struct PeerUpdate; } // namespace Notify namespace Profile { class PeerListWidget : public BlockWidget { public: PeerListWidget(QWidget *parent, PeerData *peer, const QString &title, const style::PeerListItem &st = st::profileMemberItem, const QString &removeText = QString()); struct Item { explicit Item(PeerData *peer); ~Item(); PeerData * const peer; Text name; QString statusText; bool statusHasOnlineColor = false; enum class AdminState { None, Admin, Creator, }; AdminState adminState = AdminState::None; bool hasRemoveLink = false; std::unique_ptr<Ui::RippleAnimation> ripple; }; virtual int getListTop() const { return contentTop(); } int getListLeft() const; const QList<Item*> &items() const { return _items; } int itemsCount() const { return _items.size(); } // Does not take ownership of item. void addItem(Item *item) { if (!item) return; _items.push_back(item); } void clearItems() { _items.clear(); } void reserveItemsForSize(int size) { _items.reserve(size); } template <typename Predicate> void sortItems(Predicate predicate) { qSort(_items.begin(), _items.end(), std::move(predicate)); } void setPreloadMoreCallback(base::lambda<void()> callback) { _preloadMoreCallback = std::move(callback); } void setSelectedCallback(base::lambda<void(PeerData*)> callback) { _selectedCallback = std::move(callback); } void setRemovedCallback(base::lambda<void(PeerData*)> callback) { _removedCallback = std::move(callback); } void setUpdateItemCallback(base::lambda<void(Item*)> callback) { _updateItemCallback = std::move(callback); } protected: int resizeGetHeight(int newWidth) override; void visibleTopBottomUpdated( int visibleTop, int visibleBottom) override; void paintOutlinedRect(Painter &p, int x, int y, int w, int h) const; void refreshVisibility(); void paintContents(Painter &p) override; void mouseMoveEvent(QMouseEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void contextMenuEvent(QContextMenuEvent *e) override; void enterEventHook(QEvent *e) override; void enterFromChildEvent(QEvent *e, QWidget *child) override { enterEventHook(e); } void leaveEventHook(QEvent *e) override; void leaveToChildEvent(QEvent *e, QWidget *child) override { leaveEventHook(e); } virtual Ui::PopupMenu *fillPeerMenu(PeerData *peer) { return nullptr; } private: void mousePressReleased(Qt::MouseButton button); void updateSelection(); void setSelected(int selected, bool selectedRemove); void repaintSelectedRow(); void repaintRow(int index); void preloadPhotos(); int rowWidth() const; void paintItem(Painter &p, int x, int y, Item *item, bool selected, bool selectedRemove, TimeMs ms); const style::PeerListItem &_st; base::lambda<void()> _preloadMoreCallback; base::lambda<void(PeerData*)> _selectedCallback; base::lambda<void(PeerData*)> _removedCallback; base::lambda<void(Item*)> _updateItemCallback; QList<Item*> _items; int _visibleTop = 0; int _visibleBottom = 0; int _selected = -1; int _pressed = -1; Qt::MouseButton _pressButton = Qt::LeftButton; bool _selectedRemove = false; bool _pressedRemove = false; QPoint _mousePosition; QString _removeText; int _removeWidth = 0; Ui::PopupMenu *_menu = nullptr; int _menuRowIndex = -1; }; } // namespace Profile ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/profile/profile_block_widget.cpp�������������������������������0000664�0000000�0000000�00000002311�13262451251�0025741�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "profile/profile_block_widget.h" #include "styles/style_profile.h" #include "styles/style_widgets.h" namespace Profile { BlockWidget::BlockWidget( QWidget *parent, PeerData *peer, const QString &title) : RpWidget(parent) , _peer(peer) , _title(title) { } int BlockWidget::contentTop() const { return emptyTitle() ? 0 : (st::profileBlockMarginTop + st::profileBlockTitleHeight); } void BlockWidget::paintEvent(QPaintEvent *e) { Painter p(this); paintTitle(p); paintContents(p); } void BlockWidget::paintTitle(Painter &p) { if (emptyTitle()) return; p.setFont(st::profileBlockTitleFont); p.setPen(st::profileBlockTitleFg); int titleLeft = st::profileBlockTitlePosition.x(); int titleTop = st::profileBlockMarginTop + st::profileBlockTitlePosition.y(); p.drawTextLeft(titleLeft, titleTop, width(), _title); } int defaultOutlineButtonLeft() { return st::profileBlockTitlePosition.x() - st::defaultLeftOutlineButton.padding.left(); } } // namespace Profile �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/profile/profile_block_widget.h���������������������������������0000664�0000000�0000000�00000002474�13262451251�0025420�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "base/observer.h" #include "ui/rp_widget.h" namespace Profile { class SectionMemento; class BlockWidget : public Ui::RpWidget, protected base::Subscriber { Q_OBJECT public: BlockWidget(QWidget *parent, PeerData *peer, const QString &title); virtual void showFinished() { } virtual void saveState(not_null<SectionMemento*> memento) { } virtual void restoreState(not_null<SectionMemento*> memento) { } protected: void paintEvent(QPaintEvent *e) override; virtual void paintContents(Painter &p) { } // Where does the block content start (after the title). int contentTop() const; // Resizes content and counts natural widget height for the desired width. int resizeGetHeight(int newWidth) override = 0; void contentSizeUpdated() { auto oldHeight = height(); resizeToWidth(width()); emit heightUpdated(); } PeerData *peer() const { return _peer; } bool emptyTitle() const { return _title.isEmpty(); } private: void paintTitle(Painter &p); PeerData *_peer; QString _title; }; int defaultOutlineButtonLeft(); } // namespace Profile ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/profile/profile_channel_controllers.cpp������������������������0000664�0000000�0000000�00000166632�13262451251�0027363�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "profile/profile_channel_controllers.h" #include "boxes/peer_list_controllers.h" #include "boxes/edit_participant_box.h" #include "boxes/confirm_box.h" #include "boxes/add_contact_box.h" #include "core/tl_help.h" #include "base/overload.h" #include "auth_session.h" #include "apiwrap.h" #include "lang/lang_keys.h" #include "mainwidget.h" #include "observer_peer.h" #include "dialogs/dialogs_indexed_list.h" #include "data/data_peer_values.h" #include "data/data_session.h" #include "ui/widgets/popup_menu.h" #include "window/window_controller.h" #include "history/history.h" namespace Profile { namespace { constexpr auto kParticipantsFirstPageCount = 16; constexpr auto kParticipantsPerPage = 200; constexpr auto kSortByOnlineDelay = TimeMs(1000); void RemoveAdmin( not_null<ChannelData*> channel, not_null<UserData*> user, const MTPChannelAdminRights &oldRights, base::lambda<void()> onDone) { const auto newRights = MTP_channelAdminRights(MTP_flags(0)); auto done = [=](const MTPUpdates &result) { Auth().api().applyUpdates(result); channel->applyEditAdmin(user, oldRights, newRights); onDone(); }; MTP::send( MTPchannels_EditAdmin( channel->inputChannel, user->inputUser, newRights), rpcDone(std::move(done))); } } // namespace ParticipantsBoxController::ParticipantsBoxController( not_null<Window::Navigation*> navigation, not_null<ChannelData*> channel, Role role) : PeerListController(CreateSearchController(channel, role, &_additional)) , _navigation(navigation) , _channel(channel) , _role(role) { if (_channel->mgInfo) { _additional.creator = _channel->mgInfo->creator; } if (_role == Role::Profile) { setupSortByOnline(); setupListChangeViewers(); } } void ParticipantsBoxController::setupSortByOnline() { _sortByOnlineTimer.setCallback([this] { sortByOnline(); }); using UpdateFlag = Notify::PeerUpdate::Flag; subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler( UpdateFlag::UserOnlineChanged, [this](const Notify::PeerUpdate &update) { if (auto row = delegate()->peerListFindRow( update.peer->id)) { row->refreshStatus(); sortByOnlineDelayed(); } })); } void ParticipantsBoxController::setupListChangeViewers() { if (!_channel->isMegagroup()) { return; } Auth().data().megagroupParticipantAdded( _channel ) | rpl::start_with_next([this](not_null<UserData*> user) { if (delegate()->peerListFullRowsCount() > 0) { if (delegate()->peerListRowAt(0)->peer() == user) { return; } } if (auto row = delegate()->peerListFindRow(user->id)) { delegate()->peerListPartitionRows([user](const PeerListRow &row) { return (row.peer() == user); }); } else { delegate()->peerListPrependRow(createRow(user)); delegate()->peerListRefreshRows(); sortByOnline(); } }, lifetime()); Auth().data().megagroupParticipantRemoved( _channel ) | rpl::start_with_next([this](not_null<UserData*> user) { if (auto row = delegate()->peerListFindRow(user->id)) { delegate()->peerListRemoveRow(row); } delegate()->peerListRefreshRows(); }, lifetime()); } void ParticipantsBoxController::sortByOnlineDelayed() { if (!_sortByOnlineTimer.isActive()) { _sortByOnlineTimer.callOnce(kSortByOnlineDelay); } } void ParticipantsBoxController::sortByOnline() { if (_role != Role::Profile || !_channel->isMegagroup() || _channel->membersCount() > Global::ChatSizeMax()) { _onlineCount = 0; return; } auto now = unixtime(); delegate()->peerListSortRows([now]( const PeerListRow &a, const PeerListRow &b) { return Data::SortByOnlineValue(a.peer()->asUser(), now) > Data::SortByOnlineValue(b.peer()->asUser(), now); }); refreshOnlineCount(); } void ParticipantsBoxController::refreshOnlineCount() { Expects(_role == Role::Profile); Expects(_channel->membersCount() <= Global::ChatSizeMax()); auto now = unixtime(); auto left = 0, right = delegate()->peerListFullRowsCount(); while (right > left) { auto middle = (left + right) / 2; auto row = delegate()->peerListRowAt(middle); if (Data::OnlineTextActive(row->peer()->asUser(), now)) { left = middle + 1; } else { right = middle; } } _onlineCount = left; } std::unique_ptr<PeerListSearchController> ParticipantsBoxController::CreateSearchController( not_null<ChannelData*> channel, Role role, not_null<Additional*> additional) { // In admins box complex search is used for adding new admins. if (role != Role::Admins || channel->canAddAdmins()) { return std::make_unique<ParticipantsBoxSearchController>( channel, role, additional); } return nullptr; } void ParticipantsBoxController::Start( not_null<Window::Navigation*> navigation, not_null<ChannelData*> channel, Role role) { auto controller = std::make_unique<ParticipantsBoxController>( navigation, channel, role); auto initBox = [role, channel, controller = controller.get()](not_null<PeerListBox*> box) { box->addButton(langFactory(lng_close), [box] { box->closeBox(); }); auto canAddNewItem = [role, channel] { switch (role) { case Role::Members: return !channel->isMegagroup() && channel->canAddMembers() && (channel->membersCount() < Global::ChatSizeMax()); case Role::Admins: return channel->canAddAdmins(); case Role::Restricted: case Role::Kicked: return channel->canBanMembers(); } Unexpected("Role value in ParticipantsBoxController::Start()"); }; auto addNewItemText = [role] { switch (role) { case Role::Members: return langFactory(lng_channel_add_members); case Role::Admins: return langFactory(lng_channel_add_admin); case Role::Restricted: return langFactory(lng_channel_add_restricted); case Role::Kicked: return langFactory(lng_channel_add_banned); } Unexpected("Role value in ParticipantsBoxController::Start()"); }; if (canAddNewItem()) { box->addLeftButton(addNewItemText(), [controller] { controller->addNewItem(); }); } }; Ui::show( Box<PeerListBox>(std::move(controller), std::move(initBox)), LayerOption::KeepOther); } void ParticipantsBoxController::addNewItem() { Expects(_role != Role::Profile); if (_role == Role::Members) { if (_channel->membersCount() >= Global::ChatSizeMax()) { Ui::show( Box<MaxInviteBox>(_channel), LayerOption::KeepOther); } else { auto already = std::vector<not_null<UserData*>>(); already.reserve(delegate()->peerListFullRowsCount()); for (auto i = 0, count = delegate()->peerListFullRowsCount(); i != count; ++i) { already.push_back(delegate()->peerListRowAt(i)->peer()->asUser()); } AddParticipantsBoxController::Start(_channel, { already.begin(), already.end() }); } return; } auto weak = base::make_weak(this); _addBox = Ui::show(Box<PeerListBox>(std::make_unique<AddParticipantBoxController>(_channel, _role, [weak](not_null<UserData*> user, const MTPChannelAdminRights &rights) { if (const auto strong = weak.get()) { strong->editAdminDone(user, rights); } }, [weak](not_null<UserData*> user, const MTPChannelBannedRights &rights) { if (const auto strong = weak.get()) { strong->editRestrictedDone(user, rights); } }), [](not_null<PeerListBox*> box) { box->addButton(langFactory(lng_cancel), [box] { box->closeBox(); }); }), LayerOption::KeepOther); } void ParticipantsBoxController::peerListSearchAddRow(not_null<PeerData*> peer) { PeerListController::peerListSearchAddRow(peer); if (_role == Role::Restricted && delegate()->peerListFullRowsCount() > 0) { setDescriptionText(QString()); } } std::unique_ptr<PeerListRow> ParticipantsBoxController::createSearchRow( not_null<PeerData*> peer) { if (auto user = peer->asUser()) { return createRow(user); } return nullptr; } std::unique_ptr<PeerListRow> ParticipantsBoxController::createRestoredRow( not_null<PeerData*> peer) { if (auto user = peer->asUser()) { return createRow(user); } return nullptr; } std::unique_ptr<PeerListState> ParticipantsBoxController::saveState() const { Expects(_role == Role::Profile); auto result = PeerListController::saveState(); auto my = std::make_unique<SavedState>(); my->additional = _additional; my->offset = _offset; my->allLoaded = _allLoaded; my->wasLoading = (_loadRequestId != 0); if (auto search = searchController()) { my->searchState = search->saveState(); } if (_channel->isMegagroup()) { auto weak = result.get(); Auth().data().megagroupParticipantAdded( _channel ) | rpl::start_with_next([weak](not_null<UserData*> user) { if (!weak->list.empty()) { if (weak->list[0] == user) { return; } } auto pos = ranges::find(weak->list, user); if (pos == weak->list.cend()) { weak->list.push_back(user); } ranges::stable_partition( weak->list, [user](auto peer) { return (peer == user); }); }, my->lifetime); Auth().data().megagroupParticipantRemoved( _channel ) | rpl::start_with_next([weak](not_null<UserData*> user) { weak->list.erase(std::remove( weak->list.begin(), weak->list.end(), user), weak->list.end()); weak->filterResults.erase(std::remove( weak->filterResults.begin(), weak->filterResults.end(), user), weak->filterResults.end()); }, my->lifetime); } result->controllerState = std::move(my); return result; } void ParticipantsBoxController::restoreState( std::unique_ptr<PeerListState> state) { auto typeErasedState = state ? state->controllerState.get() : nullptr; if (auto my = dynamic_cast<SavedState*>(typeErasedState)) { if (auto requestId = base::take(_loadRequestId)) { request(requestId).cancel(); } _additional = std::move(my->additional); _offset = my->offset; _allLoaded = my->allLoaded; if (auto search = searchController()) { search->restoreState(std::move(my->searchState)); } if (my->wasLoading) { loadMoreRows(); } PeerListController::restoreState(std::move(state)); if (delegate()->peerListFullRowsCount() > 0) { setNonEmptyDescription(); } else if (_allLoaded) { setDescriptionText(lang(lng_blocked_list_not_found)); } sortByOnline(); } } rpl::producer<int> ParticipantsBoxController::onlineCountValue() const { return _onlineCount.value(); } template <typename Callback> void ParticipantsBoxController::HandleParticipant(const MTPChannelParticipant &participant, Role role, not_null<Additional*> additional, Callback callback) { if ((role == Role::Profile || role == Role::Members || role == Role::Admins) && participant.type() == mtpc_channelParticipantAdmin) { auto &admin = participant.c_channelParticipantAdmin(); if (auto user = App::userLoaded(admin.vuser_id.v)) { additional->adminRights[user] = admin.vadmin_rights; if (admin.is_can_edit()) { additional->adminCanEdit.emplace(user); } else { additional->adminCanEdit.erase(user); } if (auto promoted = App::userLoaded(admin.vpromoted_by.v)) { auto it = additional->adminPromotedBy.find(user); if (it == additional->adminPromotedBy.end()) { additional->adminPromotedBy.emplace(user, promoted); } else { it->second = promoted; } } else { LOG(("API Error: No user %1 for admin promoted by.").arg(admin.vpromoted_by.v)); } callback(user); } } else if ((role == Role::Profile || role == Role::Members || role == Role::Admins) && participant.type() == mtpc_channelParticipantCreator) { auto &creator = participant.c_channelParticipantCreator(); if (auto user = App::userLoaded(creator.vuser_id.v)) { additional->creator = user; callback(user); } } else if ((role == Role::Profile || role == Role::Members || role == Role::Restricted || role == Role::Kicked) && participant.type() == mtpc_channelParticipantBanned) { auto &banned = participant.c_channelParticipantBanned(); if (auto user = App::userLoaded(banned.vuser_id.v)) { additional->restrictedRights[user] = banned.vbanned_rights; if (auto kickedby = App::userLoaded(banned.vkicked_by.v)) { auto it = additional->restrictedBy.find(user); if (it == additional->restrictedBy.end()) { additional->restrictedBy.emplace(user, kickedby); } else { it->second = kickedby; } } callback(user); } } else if ((role == Role::Profile || role == Role::Members) && participant.type() == mtpc_channelParticipant) { auto &member = participant.c_channelParticipant(); if (auto user = App::userLoaded(member.vuser_id.v)) { callback(user); } } else if ((role == Role::Profile || role == Role::Members) && participant.type() == mtpc_channelParticipantSelf) { auto &member = participant.c_channelParticipantSelf(); if (auto user = App::userLoaded(member.vuser_id.v)) { callback(user); } } else { LOG(("API Error: Bad participant type got while requesting for participants: %1").arg(participant.type())); } } void ParticipantsBoxController::prepare() { auto titleKey = [this] { switch (_role) { case Role::Admins: return lng_channel_admins; case Role::Profile: case Role::Members: return lng_profile_participants_section; case Role::Restricted: return lng_restricted_list_title; case Role::Kicked: return lng_banned_list_title; } Unexpected("Role in ParticipantsBoxController::prepare()"); }; delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled); delegate()->peerListSetTitle(langFactory(titleKey())); setDescriptionText(lang(lng_contacts_loading)); setSearchNoResultsText(lang(lng_blocked_list_not_found)); loadMoreRows(); delegate()->peerListRefreshRows(); } void ParticipantsBoxController::loadMoreRows() { if (searchController() && searchController()->loadMoreRows()) { return; } if (_loadRequestId || _allLoaded) { return; } if (feedMegagroupLastParticipants()) { return; } auto filter = [this] { if (_role == Role::Members || _role == Role::Profile) { return MTP_channelParticipantsRecent(); } else if (_role == Role::Admins) { return MTP_channelParticipantsAdmins(); } else if (_role == Role::Restricted) { return MTP_channelParticipantsBanned(MTP_string(QString())); } return MTP_channelParticipantsKicked(MTP_string(QString())); }(); // First query is small and fast, next loads a lot of rows. auto perPage = (_offset > 0) ? kParticipantsPerPage : kParticipantsFirstPageCount; auto participantsHash = 0; _loadRequestId = request(MTPchannels_GetParticipants( _channel->inputChannel, filter, MTP_int(_offset), MTP_int(perPage), MTP_int(participantsHash) )).done([this](const MTPchannels_ChannelParticipants &result) { auto firstLoad = !_offset; _loadRequestId = 0; auto wasRecentRequest = firstLoad && (_role == Role::Members || _role == Role::Profile); auto parseParticipants = [&](auto &&result, auto &&callback) { if (wasRecentRequest) { Auth().api().parseRecentChannelParticipants( _channel, result, callback); } else { Auth().api().parseChannelParticipants( _channel, result, callback); } }; parseParticipants(result, [&]( int availableCount, const QVector<MTPChannelParticipant> &list) { for (auto &participant : list) { HandleParticipant( participant, _role, &_additional, [&](auto user) { appendRow(user); }); } if (auto size = list.size()) { _offset += size; } else { // To be sure - wait for a whole empty result list. _allLoaded = true; } }); if (delegate()->peerListFullRowsCount() > 0) { sortByOnline(); if (firstLoad) { setNonEmptyDescription(); } } else if (_allLoaded) { setDescriptionText(lang(lng_blocked_list_not_found)); } delegate()->peerListRefreshRows(); }).fail([this](const RPCError &error) { _loadRequestId = 0; }).send(); } void ParticipantsBoxController::setNonEmptyDescription() { setDescriptionText((_role == Role::Kicked) ? lang(_channel->isMegagroup() ? lng_group_blocked_list_about : lng_channel_blocked_list_about) : QString()); } bool ParticipantsBoxController::feedMegagroupLastParticipants() { if ((_role != Role::Members && _role != Role::Profile) || delegate()->peerListFullRowsCount() > 0) { return false; } auto megagroup = _channel->asMegagroup(); if (!megagroup) { return false; } auto info = megagroup->mgInfo.get(); // // channelFull and channels_channelParticipants members count is desynced // so we almost always have LastParticipantsCountOutdated that is set // inside setMembersCount() and so we almost never use lastParticipants. // // => disable this check temporarily. // //if (info->lastParticipantsStatus != MegagroupInfo::LastParticipantsUpToDate) { // _channel->updateFull(); // return false; //} if (info->lastParticipants.empty()) { return false; } if (info->creator) { _additional.creator = info->creator; } for_const (auto user, info->lastParticipants) { auto admin = info->lastAdmins.find(user); if (admin != info->lastAdmins.cend()) { _additional.restrictedRights.erase(user); if (admin->second.canEdit) { _additional.adminCanEdit.emplace(user); } else { _additional.adminCanEdit.erase(user); } _additional.adminRights.emplace(user, admin->second.rights); } else { _additional.adminCanEdit.erase(user); _additional.adminRights.erase(user); auto restricted = info->lastRestricted.find(user); if (restricted != info->lastRestricted.cend()) { _additional.restrictedRights.emplace(user, restricted->second.rights); } else { _additional.restrictedRights.erase(user); } } appendRow(user); // // Don't count lastParticipants in _offset, because we don't know // their exact information (admin / creator / restricted), they // could simply be added from the last messages authors. // //++_offset; } sortByOnline(); return true; } void ParticipantsBoxController::rowClicked(not_null<PeerListRow*> row) { auto user = row->peer()->asUser(); Expects(user != nullptr); if (_role == Role::Admins) { showAdmin(user); } else if (_role == Role::Restricted || _role == Role::Kicked) { showRestricted(user); } else { _navigation->showPeerInfo(row->peer()); } } void ParticipantsBoxController::rowActionClicked(not_null<PeerListRow*> row) { auto user = row->peer()->asUser(); Expects(user != nullptr); if (_role == Role::Members || _role == Role::Profile) { kickMember(user); } else if (_role == Role::Admins) { removeAdmin(user); } else if (_role == Role::Restricted) { showRestricted(user); } else { removeKicked(row, user); } } bool ParticipantsBoxController::canEditAdminByRights( not_null<UserData*> user) const { if (_additional.adminRights.find(user) != _additional.adminRights.cend()) { return (_additional.adminCanEdit.find(user) != _additional.adminCanEdit.cend()); } return (user != _additional.creator); } bool ParticipantsBoxController::canEditAdmin( not_null<UserData*> user) const { if (user->isSelf()) { return false; } else if (_channel->amCreator()) { return true; } else if (!canEditAdminByRights(user)) { return false; } return _channel->adminRights() & ChannelAdminRight::f_add_admins; } bool ParticipantsBoxController::canRestrictUser( not_null<UserData*> user) const { if (user->isSelf()) { return false; } else if (_channel->amCreator()) { return true; } else if (!canEditAdminByRights(user)) { return false; } return _channel->adminRights() & ChannelAdminRight::f_ban_users; } base::unique_qptr<Ui::PopupMenu> ParticipantsBoxController::rowContextMenu( not_null<PeerListRow*> row) { Expects(row->peer()->isUser()); auto user = row->peer()->asUser(); auto result = base::make_unique_q<Ui::PopupMenu>(nullptr); result->addAction( lang(lng_context_view_profile), [weak = base::make_weak(this), user] { if (const auto strong = weak.get()) { strong->_navigation->showPeerInfo(user); } }); if (canEditAdmin(user)) { auto it = _additional.adminRights.find(user); auto isCreator = (user == _additional.creator); auto notAdmin = !isCreator && (it == _additional.adminRights.cend()); auto label = lang(notAdmin ? lng_context_promote_admin : lng_context_edit_permissions); result->addAction( label, [weak = base::make_weak(this), user] { if (const auto strong = weak.get()) { strong->showAdmin(user); } }); } if (canRestrictUser(user)) { auto isGroup = _channel->isMegagroup(); if (isGroup) { result->addAction( lang(lng_context_restrict_user), [weak = base::make_weak(this), user]{ if (const auto strong = weak.get()) { strong->showRestricted(user); } }); } result->addAction( lang(isGroup ? lng_context_remove_from_group : lng_profile_kick), [weak = base::make_weak(this), user] { if (auto strong = weak.get()) { strong->kickMember(user); } }); } return result; } void ParticipantsBoxController::showAdmin(not_null<UserData*> user) { auto it = _additional.adminRights.find(user); auto isCreator = (user == _additional.creator); auto notAdmin = !isCreator && (it == _additional.adminRights.cend()); auto currentRights = isCreator ? MTP_channelAdminRights(MTP_flags(~MTPDchannelAdminRights::Flag::f_add_admins | MTPDchannelAdminRights::Flag::f_add_admins)) : notAdmin ? MTP_channelAdminRights(MTP_flags(0)) : it->second; auto weak = base::make_weak(this); auto box = Box<EditAdminBox>(_channel, user, currentRights); auto canEdit = (_additional.adminCanEdit.find(user) != _additional.adminCanEdit.end()); auto canSave = notAdmin ? _channel->canAddAdmins() : canEdit; if (canSave) { box->setSaveCallback([channel = _channel.get(), user, weak](const MTPChannelAdminRights &oldRights, const MTPChannelAdminRights &newRights) { MTP::send(MTPchannels_EditAdmin(channel->inputChannel, user->inputUser, newRights), rpcDone([channel, user, weak, oldRights, newRights](const MTPUpdates &result) { Auth().api().applyUpdates(result); channel->applyEditAdmin(user, oldRights, newRights); if (weak) { weak->editAdminDone(user, newRights); } })); }); } _editBox = Ui::show(std::move(box), LayerOption::KeepOther); } void ParticipantsBoxController::editAdminDone( not_null<UserData*> user, const MTPChannelAdminRights &rights) { if (_editBox) { _editBox->closeBox(); } if (_addBox) { _addBox->closeBox(); } auto notAdmin = (rights.c_channelAdminRights().vflags.v == 0); if (notAdmin) { _additional.adminRights.erase(user); _additional.adminPromotedBy.erase(user); _additional.adminCanEdit.erase(user); if (_role == Role::Admins) { removeRow(user); } } else { // It won't be replaced if the entry already exists. _additional.adminPromotedBy.emplace(user, App::self()); _additional.adminCanEdit.emplace(user); _additional.adminRights[user] = rights; _additional.kicked.erase(user); _additional.restrictedRights.erase(user); _additional.restrictedBy.erase(user); if (_role == Role::Admins) { prependRow(user); } else if (_role == Role::Kicked || _role == Role::Restricted) { removeRow(user); } } recomputeTypeFor(user); delegate()->peerListRefreshRows(); } void ParticipantsBoxController::showRestricted(not_null<UserData*> user) { auto it = _additional.restrictedRights.find(user); auto restrictedRights = (it == _additional.restrictedRights.cend()) ? MTP_channelBannedRights(MTP_flags(0), MTP_int(0)) : it->second; auto weak = base::make_weak(this); auto hasAdminRights = false; auto box = Box<EditRestrictedBox>(_channel, user, hasAdminRights, restrictedRights); if (_channel->canBanMembers()) { box->setSaveCallback([megagroup = _channel.get(), user, weak](const MTPChannelBannedRights &oldRights, const MTPChannelBannedRights &newRights) { MTP::send(MTPchannels_EditBanned(megagroup->inputChannel, user->inputUser, newRights), rpcDone([megagroup, user, weak, oldRights, newRights](const MTPUpdates &result) { Auth().api().applyUpdates(result); megagroup->applyEditBanned(user, oldRights, newRights); if (weak) { weak->editRestrictedDone(user, newRights); } })); }); } _editBox = Ui::show(std::move(box), LayerOption::KeepOther); } void ParticipantsBoxController::editRestrictedDone(not_null<UserData*> user, const MTPChannelBannedRights &rights) { if (_editBox) { _editBox->closeBox(); } if (_addBox) { _addBox->closeBox(); } auto notBanned = (rights.c_channelBannedRights().vflags.v == 0); auto fullBanned = rights.c_channelBannedRights().is_view_messages(); if (notBanned) { _additional.kicked.erase(user); _additional.restrictedRights.erase(user); _additional.restrictedBy.erase(user); if (_role == Role::Kicked || _role == Role::Restricted) { removeRow(user); } } else { _additional.adminRights.erase(user); _additional.adminCanEdit.erase(user); _additional.adminPromotedBy.erase(user); _additional.restrictedBy.emplace(user, App::self()); if (fullBanned) { _additional.kicked.emplace(user); _additional.restrictedRights.erase(user); if (_role == Role::Kicked) { prependRow(user); } else if (_role == Role::Admins || _role == Role::Restricted || _role == Role::Members) { removeRow(user); } } else { _additional.restrictedRights[user] = rights; _additional.kicked.erase(user); if (_role == Role::Restricted) { prependRow(user); } else if (_role == Role::Kicked || _role == Role::Admins || _role == Role::Members) { removeRow(user); } } } recomputeTypeFor(user); delegate()->peerListRefreshRows(); } void ParticipantsBoxController::kickMember(not_null<UserData*> user) { auto text = (_channel->isMegagroup() ? lng_profile_sure_kick : lng_profile_sure_kick_channel)(lt_user, user->firstName); auto weak = base::make_weak(this); _editBox = Ui::show(Box<ConfirmBox>(text, lang(lng_box_remove), [weak, user] { if (const auto strong = weak.get()) { strong->kickMemberSure(user); } }), LayerOption::KeepOther); } void ParticipantsBoxController::kickMemberSure(not_null<UserData*> user) { if (_editBox) { _editBox->closeBox(); } auto alreadyIt = _additional.restrictedRights.find(user); auto currentRights = (alreadyIt == _additional.restrictedRights.cend()) ? MTP_channelBannedRights(MTP_flags(0), MTP_int(0)) : alreadyIt->second; if (auto row = delegate()->peerListFindRow(user->id)) { delegate()->peerListRemoveRow(row); delegate()->peerListRefreshRows(); } Auth().api().kickParticipant(_channel, user, currentRights); } void ParticipantsBoxController::removeAdmin(not_null<UserData*> user) { const auto text = lng_profile_sure_remove_admin(lt_user, user->firstName); const auto weak = base::make_weak(this); _editBox = Ui::show(Box<ConfirmBox>(text, lang(lng_box_remove), [=] { if (const auto strong = weak.get()) { strong->removeAdminSure(user); } }), LayerOption::KeepOther); } void ParticipantsBoxController::removeAdminSure(not_null<UserData*> user) { if (_editBox) { _editBox->closeBox(); } const auto oldRightsIt = _additional.adminRights.find(user); if (oldRightsIt == _additional.adminRights.cend()) { return; } const auto weak = base::make_weak(this); RemoveAdmin(_channel, user, oldRightsIt->second, [=] { if (const auto strong = weak.get()) { const auto newRights = MTP_channelAdminRights(MTP_flags(0)); strong->editAdminDone(user, newRights); } }); } void ParticipantsBoxController::removeKicked( not_null<PeerListRow*> row, not_null<UserData*> user) { delegate()->peerListRemoveRow(row); delegate()->peerListRefreshRows(); Auth().api().unblockParticipant(_channel, user); } bool ParticipantsBoxController::appendRow(not_null<UserData*> user) { if (delegate()->peerListFindRow(user->id)) { recomputeTypeFor(user); return false; } delegate()->peerListAppendRow(createRow(user)); if (_role != Role::Kicked) { setDescriptionText(QString()); } return true; } bool ParticipantsBoxController::prependRow(not_null<UserData*> user) { if (auto row = delegate()->peerListFindRow(user->id)) { recomputeTypeFor(user); refreshCustomStatus(row); if (_role == Role::Admins) { // Perhaps we've added a new admin from search. delegate()->peerListPrependRowFromSearchResult(row); } return false; } delegate()->peerListPrependRow(createRow(user)); if (_role != Role::Kicked) { setDescriptionText(QString()); } return true; } bool ParticipantsBoxController::removeRow(not_null<UserData*> user) { if (auto row = delegate()->peerListFindRow(user->id)) { if (_role == Role::Admins) { // Perhaps we are removing an admin from search results. row->setCustomStatus(lang(lng_channel_admin_status_not_admin)); delegate()->peerListConvertRowToSearchResult(row); } else { delegate()->peerListRemoveRow(row); } if (_role != Role::Kicked && !delegate()->peerListFullRowsCount()) { setDescriptionText(lang(lng_blocked_list_not_found)); } return true; } return false; } std::unique_ptr<PeerListRow> ParticipantsBoxController::createRow( not_null<UserData*> user) const { if (_role == Role::Profile) { return std::make_unique<Row>(user, computeType(user)); } auto row = std::make_unique<PeerListRowWithLink>(user); refreshCustomStatus(row.get()); if (_role == Role::Admins && canEditAdminByRights(user) && _additional.adminRights.find(user) != _additional.adminRights.cend()) { row->setActionLink(lang(lng_profile_kick)); } else if (_role == Role::Restricted || (_role == Role::Admins && _additional.adminCanEdit.find(user) != _additional.adminCanEdit.cend())) { // row->setActionLink(lang(lng_profile_edit_permissions)); } else if (_role == Role::Kicked) { row->setActionLink(lang(lng_blocked_list_unblock)); } else if (_role == Role::Members) { if (_channel->canBanMembers() && _additional.creator != user && (_additional.adminRights.find(user) == _additional.adminRights.cend() || _additional.adminCanEdit.find(user) != _additional.adminCanEdit.cend())) { row->setActionLink(lang(lng_profile_kick)); } } return std::move(row); } auto ParticipantsBoxController::computeType( not_null<UserData*> user) const -> Type { auto isCreator = (user == _additional.creator); auto isAdmin = (_additional.adminRights.find(user) != _additional.adminRights.cend()); auto result = Type(); result.rights = isCreator ? Rights::Creator : isAdmin ? Rights::Admin : Rights::Normal; result.canRemove = canRestrictUser(user); return result; } void ParticipantsBoxController::recomputeTypeFor( not_null<UserData*> user) { if (_role != Role::Profile) { return; } if (auto row = delegate()->peerListFindRow(user->id)) { static_cast<Row*>(row)->setType(computeType(user)); } } void ParticipantsBoxController::refreshCustomStatus(not_null<PeerListRow*> row) const { auto user = row->peer()->asUser(); if (_role == Role::Admins) { auto promotedBy = _additional.adminPromotedBy.find(user); if (promotedBy == _additional.adminPromotedBy.cend()) { if (user == _additional.creator) { row->setCustomStatus(lang(lng_channel_admin_status_creator)); } else { row->setCustomStatus(lang(lng_channel_admin_status_not_admin)); } } else { row->setCustomStatus(lng_channel_admin_status_promoted_by(lt_user, App::peerName(promotedBy->second))); } } else if (_role == Role::Kicked || _role == Role::Restricted) { auto restrictedBy = _additional.restrictedBy.find(user); if (restrictedBy == _additional.restrictedBy.cend()) { row->setCustomStatus(lng_channel_banned_status_restricted_by(lt_user, "Unknown")); } else { row->setCustomStatus(lng_channel_banned_status_restricted_by(lt_user, App::peerName(restrictedBy->second))); } } } ParticipantsBoxSearchController::ParticipantsBoxSearchController(not_null<ChannelData*> channel, Role role, not_null<Additional*> additional) : _channel(channel) , _role(role) , _additional(additional) { _timer.setCallback([this] { searchOnServer(); }); } void ParticipantsBoxSearchController::searchQuery(const QString &query) { if (_query != query) { _query = query; _offset = 0; _requestId = 0; _allLoaded = false; if (!_query.isEmpty() && !searchInCache()) { _timer.callOnce(AutoSearchTimeout); } else { _timer.cancel(); } } } auto ParticipantsBoxSearchController::saveState() const -> std::unique_ptr<SavedStateBase> { auto result = std::make_unique<SavedState>(); result->query = _query; result->offset = _offset; result->allLoaded = _allLoaded; result->wasLoading = (_requestId != 0); return std::move(result); } void ParticipantsBoxSearchController::restoreState( std::unique_ptr<SavedStateBase> state) { if (auto my = dynamic_cast<SavedState*>(state.get())) { if (auto requestId = base::take(_requestId)) { request(requestId).cancel(); } _cache.clear(); _queries.clear(); _allLoaded = my->allLoaded; _offset = my->offset; _query = my->query; if (my->wasLoading) { searchOnServer(); } } } void ParticipantsBoxSearchController::searchOnServer() { Expects(!_query.isEmpty()); loadMoreRows(); } bool ParticipantsBoxSearchController::isLoading() { return _timer.isActive() || _requestId; } bool ParticipantsBoxSearchController::searchInCache() { auto it = _cache.find(_query); if (it != _cache.cend()) { _requestId = 0; searchDone(_requestId, it->second.result, it->second.requestedCount); return true; } return false; } bool ParticipantsBoxSearchController::loadMoreRows() { if (_query.isEmpty()) { return false; } if (!_allLoaded && !isLoading()) { auto filter = [this] { switch (_role) { case Role::Admins: // Search for members, appoint as admin on found. case Role::Profile: case Role::Members: return MTP_channelParticipantsSearch(MTP_string(_query)); case Role::Restricted: return MTP_channelParticipantsBanned(MTP_string(_query)); case Role::Kicked: return MTP_channelParticipantsKicked(MTP_string(_query)); } Unexpected("Role in ParticipantsBoxSearchController::loadMoreRows()"); }(); // For search we request a lot of rows from the first query. // (because we've waited for search request by timer already, // so we don't expect it to be fast, but we want to fill cache). auto perPage = kParticipantsPerPage; auto participantsHash = 0; _requestId = request(MTPchannels_GetParticipants( _channel->inputChannel, filter, MTP_int(_offset), MTP_int(perPage), MTP_int(participantsHash) )).done([this, perPage](const MTPchannels_ChannelParticipants &result, mtpRequestId requestId) { searchDone(requestId, result, perPage); }).fail([this](const RPCError &error, mtpRequestId requestId) { if (_requestId == requestId) { _requestId = 0; _allLoaded = true; delegate()->peerListSearchRefreshRows(); } }).send(); auto entry = Query(); entry.text = _query; entry.offset = _offset; _queries.emplace(_requestId, entry); } return true; } void ParticipantsBoxSearchController::searchDone( mtpRequestId requestId, const MTPchannels_ChannelParticipants &result, int requestedCount) { auto query = _query; if (requestId) { Auth().api().parseChannelParticipants(_channel, result, [&](auto&&...) { auto it = _queries.find(requestId); if (it != _queries.cend()) { query = it->second.text; if (it->second.offset == 0) { auto &entry = _cache[query]; entry.result = result; entry.requestedCount = requestedCount; } _queries.erase(it); } }); } if (_requestId != requestId) { return; } _requestId = 0; TLHelp::VisitChannelParticipants(result, base::overload([&]( const MTPDchannels_channelParticipants &data) { auto &list = data.vparticipants.v; if (list.size() < requestedCount) { // We want cache to have full information about a query with small // results count (if we don't need the second request). So we don't // wait for an empty results list unlike the non-search peer list. _allLoaded = true; } auto parseRole = (_role == Role::Admins) ? Role::Members : _role; for_const (auto &participant, list) { ParticipantsBoxController::HandleParticipant(participant, parseRole, _additional, [this](not_null<UserData*> user) { delegate()->peerListSearchAddRow(user); }); } _offset += list.size(); }, [&](mtpTypeId type) { _allLoaded = true; })); delegate()->peerListSearchRefreshRows(); } AddParticipantBoxController::AddParticipantBoxController(not_null<ChannelData*> channel, Role role, AdminDoneCallback adminDoneCallback, BannedDoneCallback bannedDoneCallback) : PeerListController(std::make_unique<AddParticipantBoxSearchController>(channel, &_additional)) , _channel(channel) , _role(role) , _adminDoneCallback(std::move(adminDoneCallback)) , _bannedDoneCallback(std::move(bannedDoneCallback)) { if (_channel->mgInfo) { _additional.creator = _channel->mgInfo->creator; } } std::unique_ptr<PeerListRow> AddParticipantBoxController::createSearchRow(not_null<PeerData*> peer) { if (peer->isSelf()) { return nullptr; } if (auto user = peer->asUser()) { return createRow(user); } return nullptr; } void AddParticipantBoxController::prepare() { delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled); auto title = [this] { switch (_role) { case Role::Admins: return langFactory(lng_channel_add_admin); case Role::Restricted: return langFactory(lng_channel_add_restricted); case Role::Kicked: return langFactory(lng_channel_add_banned); } Unexpected("Role in AddParticipantBoxController::prepare()"); }; delegate()->peerListSetTitle(title()); setDescriptionText(lang(lng_contacts_loading)); setSearchNoResultsText(lang(lng_blocked_list_not_found)); delegate()->peerListRefreshRows(); loadMoreRows(); } void AddParticipantBoxController::loadMoreRows() { if (searchController() && searchController()->loadMoreRows()) { return; } if (_loadRequestId || _allLoaded) { return; } // First query is small and fast, next loads a lot of rows. auto perPage = (_offset > 0) ? kParticipantsPerPage : kParticipantsFirstPageCount; auto participantsHash = 0; _loadRequestId = request(MTPchannels_GetParticipants( _channel->inputChannel, MTP_channelParticipantsRecent(), MTP_int(_offset), MTP_int(perPage), MTP_int(participantsHash) )).done([this](const MTPchannels_ChannelParticipants &result) { _loadRequestId = 0; Auth().api().parseChannelParticipants(_channel, result, [&]( int availableCount, const QVector<MTPChannelParticipant> &list) { for (auto &participant : list) { HandleParticipant( participant, &_additional, [this](auto user) { appendRow(user); }); } if (auto size = list.size()) { _offset += size; } else { // To be sure - wait for a whole empty result list. _allLoaded = true; } }); if (delegate()->peerListFullRowsCount() > 0) { setDescriptionText(QString()); } else if (_allLoaded) { setDescriptionText(lang(lng_blocked_list_not_found)); } delegate()->peerListRefreshRows(); }).fail([this](const RPCError &error) { _loadRequestId = 0; }).send(); } void AddParticipantBoxController::rowClicked(not_null<PeerListRow*> row) { auto user = row->peer()->asUser(); switch (_role) { case Role::Admins: return showAdmin(user); case Role::Restricted: return showRestricted(user); case Role::Kicked: return kickUser(user); } Unexpected("Role in AddParticipantBoxController::rowClicked()"); } template <typename Callback> bool AddParticipantBoxController::checkInfoLoaded(not_null<UserData*> user, Callback callback) { if (_additional.infoNotLoaded.find(user) == _additional.infoNotLoaded.end()) { return true; } // We don't know what this user status is in the group. request(MTPchannels_GetParticipant(_channel->inputChannel, user->inputUser)).done([this, user, callback](const MTPchannels_ChannelParticipant &result) { Expects(result.type() == mtpc_channels_channelParticipant); auto &participant = result.c_channels_channelParticipant(); App::feedUsers(participant.vusers); HandleParticipant(participant.vparticipant, &_additional, [](not_null<UserData*>) {}); _additional.infoNotLoaded.erase(user); callback(); }).fail([this, user, callback](const RPCError &error) { _additional.infoNotLoaded.erase(user); _additional.external.emplace(user); callback(); }).send(); return false; } void AddParticipantBoxController::showAdmin(not_null<UserData*> user, bool sure) { if (!checkInfoLoaded(user, [this, user] { showAdmin(user); })) { return; } if (sure && _editBox) { // Close the confirmation box. _editBox->closeBox(); } // Check restrictions. auto weak = base::make_weak(this); auto alreadyIt = _additional.adminRights.find(user); auto currentRights = (_additional.creator == user) ? MTP_channelAdminRights(MTP_flags(~MTPDchannelAdminRights::Flag::f_add_admins | MTPDchannelAdminRights::Flag::f_add_admins)) : MTP_channelAdminRights(MTP_flags(0)); if (alreadyIt != _additional.adminRights.end()) { // The user is already an admin. currentRights = alreadyIt->second; } else if (_additional.kicked.find(user) != _additional.kicked.end()) { // The user is banned. if (_channel->canAddMembers()) { if (_channel->canBanMembers()) { if (!sure) { _editBox = Ui::show(Box<ConfirmBox>(lang(lng_sure_add_admin_unban), [weak, user] { if (weak) { weak->showAdmin(user, true); } }), LayerOption::KeepOther); return; } } else { Ui::show(Box<InformBox>( lang(lng_error_cant_add_admin_unban)), LayerOption::KeepOther); return; } } else { Ui::show(Box<InformBox>( lang(lng_error_cant_add_admin_invite)), LayerOption::KeepOther); return; } } else if (_additional.restrictedRights.find(user) != _additional.restrictedRights.end()) { // The user is restricted. if (_channel->canBanMembers()) { if (!sure) { _editBox = Ui::show(Box<ConfirmBox>(lang(lng_sure_add_admin_unban), [weak, user] { if (weak) { weak->showAdmin(user, true); } }), LayerOption::KeepOther); return; } } else { Ui::show(Box<InformBox>( lang(lng_error_cant_add_admin_unban)), LayerOption::KeepOther); return; } } else if (_additional.external.find(user) != _additional.external.end()) { // The user is not in the group yet. if (_channel->canAddMembers()) { if (!sure) { const auto text = lang(_channel->isMegagroup() ? lng_sure_add_admin_invite : lng_sure_add_admin_invite_channel); _editBox = Ui::show(Box<ConfirmBox>(text, [weak, user] { if (weak) { weak->showAdmin(user, true); } }), LayerOption::KeepOther); return; } } else { Ui::show( Box<InformBox>(lang(lng_error_cant_add_admin_invite)), LayerOption::KeepOther); return; } } // Finally show the admin. auto canNotEdit = (_additional.creator == user) || ((alreadyIt != _additional.adminRights.end()) && (_additional.adminCanEdit.find(user) == _additional.adminCanEdit.end())); auto box = Box<EditAdminBox>(_channel, user, currentRights); if (!canNotEdit) { box->setSaveCallback([channel = _channel.get(), user, weak](const MTPChannelAdminRights &oldRights, const MTPChannelAdminRights &newRights) { MTP::send(MTPchannels_EditAdmin(channel->inputChannel, user->inputUser, newRights), rpcDone([channel, user, weak, oldRights, newRights](const MTPUpdates &result) { Auth().api().applyUpdates(result); channel->applyEditAdmin(user, oldRights, newRights); if (weak) { weak->editAdminDone(user, newRights); } }), rpcFail([channel, weak](const RPCError &error) { if (MTP::isDefaultHandledError(error)) { return false; } if (error.type() == qstr("USER_NOT_MUTUAL_CONTACT")) { Ui::show( Box<InformBox>(PeerFloodErrorText( channel->isMegagroup() ? PeerFloodType::InviteGroup : PeerFloodType::InviteChannel)), LayerOption::KeepOther); } else if (error.type() == qstr("BOT_GROUPS_BLOCKED")) { Ui::show( Box<InformBox>(lang(lng_error_cant_add_bot)), LayerOption::KeepOther); } if (weak && weak->_editBox) { weak->_editBox->closeBox(); } return true; })); }); } _editBox = Ui::show(std::move(box), LayerOption::KeepOther); } void AddParticipantBoxController::editAdminDone( not_null<UserData*> user, const MTPChannelAdminRights &rights) { if (_editBox) _editBox->closeBox(); _additional.restrictedRights.erase(user); _additional.restrictedBy.erase(user); _additional.kicked.erase(user); _additional.external.erase(user); if (rights.c_channelAdminRights().vflags.v == 0) { _additional.adminRights.erase(user); _additional.adminPromotedBy.erase(user); _additional.adminCanEdit.erase(user); } else { _additional.adminRights[user] = rights; _additional.adminCanEdit.emplace(user); auto it = _additional.adminPromotedBy.find(user); if (it == _additional.adminPromotedBy.end()) { _additional.adminPromotedBy.emplace(user, App::self()); } } if (_adminDoneCallback) { _adminDoneCallback(user, rights); } } void AddParticipantBoxController::showRestricted(not_null<UserData*> user, bool sure) { if (!checkInfoLoaded(user, [this, user] { showRestricted(user); })) { return; } if (sure && _editBox) { // Close the confirmation box. _editBox->closeBox(); } // Check restrictions. auto weak = base::make_weak(this); auto alreadyIt = _additional.restrictedRights.find(user); auto currentRights = MTP_channelBannedRights(MTP_flags(0), MTP_int(0)); auto hasAdminRights = false; if (alreadyIt != _additional.restrictedRights.end()) { // The user is already banned or restricted. currentRights = alreadyIt->second; } else if (_additional.adminRights.find(user) != _additional.adminRights.end() || _additional.creator == user) { // The user is an admin or creator. if (_additional.adminCanEdit.find(user) != _additional.adminCanEdit.end()) { hasAdminRights = true; if (!sure) { _editBox = Ui::show(Box<ConfirmBox>(lang(lng_sure_ban_admin), [weak, user] { if (weak) { weak->showRestricted(user, true); } }), LayerOption::KeepOther); return; } } else { Ui::show( Box<InformBox>(lang(lng_error_cant_ban_admin)), LayerOption::KeepOther); return; } } // Finally edit the restricted. auto box = Box<EditRestrictedBox>(_channel, user, hasAdminRights, currentRights); box->setSaveCallback([user, weak](const MTPChannelBannedRights &oldRights, const MTPChannelBannedRights &newRights) { if (weak) { weak->restrictUserSure(user, oldRights, newRights); } }); _editBox = Ui::show(std::move(box), LayerOption::KeepOther); } void AddParticipantBoxController::restrictUserSure(not_null<UserData*> user, const MTPChannelBannedRights &oldRights, const MTPChannelBannedRights &newRights) { auto weak = base::make_weak(this); MTP::send(MTPchannels_EditBanned(_channel->inputChannel, user->inputUser, newRights), rpcDone([megagroup = _channel.get(), user, weak, oldRights, newRights](const MTPUpdates &result) { Auth().api().applyUpdates(result); megagroup->applyEditBanned(user, oldRights, newRights); if (const auto strong = weak.get()) { strong->editRestrictedDone(user, newRights); } })); } void AddParticipantBoxController::editRestrictedDone( not_null<UserData*> user, const MTPChannelBannedRights &rights) { if (_editBox) _editBox->closeBox(); _additional.adminRights.erase(user); _additional.adminCanEdit.erase(user); _additional.adminPromotedBy.erase(user); if (rights.c_channelBannedRights().vflags.v == 0) { _additional.restrictedRights.erase(user); _additional.restrictedBy.erase(user); _additional.kicked.erase(user); } else { _additional.restrictedRights[user] = rights; if (rights.c_channelBannedRights().vflags.v & MTPDchannelBannedRights::Flag::f_view_messages) { _additional.kicked.emplace(user); } else { _additional.kicked.erase(user); } _additional.restrictedBy.emplace(user, App::self()); } if (_bannedDoneCallback) { _bannedDoneCallback(user, rights); } } void AddParticipantBoxController::kickUser(not_null<UserData*> user, bool sure) { if (!checkInfoLoaded(user, [this, user] { kickUser(user); })) { return; } // Check restrictions. auto weak = base::make_weak(this); if (_additional.adminRights.find(user) != _additional.adminRights.end() || _additional.creator == user) { // The user is an admin or creator. if (_additional.adminCanEdit.find(user) != _additional.adminCanEdit.end()) { if (!sure) { _editBox = Ui::show(Box<ConfirmBox>(lang(lng_sure_ban_admin), [weak, user] { if (weak) { weak->kickUser(user, true); } }), LayerOption::KeepOther); return; } } else { Ui::show( Box<InformBox>(lang(lng_error_cant_ban_admin)), LayerOption::KeepOther); return; } } // Finally kick him. if (!sure) { auto text = lng_sure_ban_user_group(lt_user, App::peerName(user)); _editBox = Ui::show(Box<ConfirmBox>(text, [weak, user] { if (weak) { weak->kickUser(user, true); } }), LayerOption::KeepOther); return; } auto currentRights = MTP_channelBannedRights(MTP_flags(0), MTP_int(0)); auto alreadyIt = _additional.restrictedRights.find(user); if (alreadyIt != _additional.restrictedRights.end()) { // The user is already banned or restricted. currentRights = alreadyIt->second; } restrictUserSure(user, currentRights, ChannelData::KickedRestrictedRights()); } bool AddParticipantBoxController::appendRow(not_null<UserData*> user) { if (delegate()->peerListFindRow(user->id) || user->isSelf()) { return false; } delegate()->peerListAppendRow(createRow(user)); return true; } bool AddParticipantBoxController::prependRow(not_null<UserData*> user) { if (delegate()->peerListFindRow(user->id)) { return false; } delegate()->peerListPrependRow(createRow(user)); return true; } std::unique_ptr<PeerListRow> AddParticipantBoxController::createRow(not_null<UserData*> user) const { return std::make_unique<PeerListRow>(user); } template <typename Callback> void AddParticipantBoxController::HandleParticipant( const MTPChannelParticipant &participant, not_null<Additional*> additional, Callback callback) { switch (participant.type()) { case mtpc_channelParticipantAdmin: { auto &admin = participant.c_channelParticipantAdmin(); if (auto user = App::userLoaded(admin.vuser_id.v)) { additional->infoNotLoaded.erase(user); additional->restrictedRights.erase(user); additional->kicked.erase(user); additional->restrictedBy.erase(user); additional->adminRights[user] = admin.vadmin_rights; if (admin.is_can_edit()) { additional->adminCanEdit.emplace(user); } else { additional->adminCanEdit.erase(user); } if (auto promoted = App::userLoaded(admin.vpromoted_by.v)) { auto it = additional->adminPromotedBy.find(user); if (it == additional->adminPromotedBy.end()) { additional->adminPromotedBy.emplace(user, promoted); } else { it->second = promoted; } } else { LOG(("API Error: No user %1 for admin promoted by.").arg(admin.vpromoted_by.v)); } callback(user); } } break; case mtpc_channelParticipantCreator: { auto &creator = participant.c_channelParticipantCreator(); if (auto user = App::userLoaded(creator.vuser_id.v)) { additional->infoNotLoaded.erase(user); additional->creator = user; callback(user); } } break; case mtpc_channelParticipantBanned: { auto &banned = participant.c_channelParticipantBanned(); if (auto user = App::userLoaded(banned.vuser_id.v)) { additional->infoNotLoaded.erase(user); additional->adminRights.erase(user); additional->adminCanEdit.erase(user); additional->adminPromotedBy.erase(user); if (banned.is_left()) { additional->kicked.emplace(user); } else { additional->kicked.erase(user); } additional->restrictedRights[user] = banned.vbanned_rights; if (auto kickedby = App::userLoaded(banned.vkicked_by.v)) { auto it = additional->restrictedBy.find(user); if (it == additional->restrictedBy.end()) { additional->restrictedBy.emplace(user, kickedby); } else { it->second = kickedby; } } callback(user); } } break; case mtpc_channelParticipant: { auto &data = participant.c_channelParticipant(); if (auto user = App::userLoaded(data.vuser_id.v)) { additional->infoNotLoaded.erase(user); additional->adminRights.erase(user); additional->adminCanEdit.erase(user); additional->adminPromotedBy.erase(user); additional->restrictedRights.erase(user); additional->kicked.erase(user); additional->restrictedBy.erase(user); callback(user); } } break; default: Unexpected("Participant type in AddParticipantBoxController::HandleParticipant()"); } } AddParticipantBoxSearchController::AddParticipantBoxSearchController(not_null<ChannelData*> channel, not_null<Additional*> additional) : _channel(channel) , _additional(additional) { _timer.setCallback([this] { searchOnServer(); }); } void AddParticipantBoxSearchController::searchQuery(const QString &query) { if (_query != query) { _query = query; _offset = 0; _requestId = 0; _participantsLoaded = false; _chatsContactsAdded = false; _globalLoaded = false; if (!_query.isEmpty() && !searchParticipantsInCache()) { _timer.callOnce(AutoSearchTimeout); } else { _timer.cancel(); } } } void AddParticipantBoxSearchController::searchOnServer() { Expects(!_query.isEmpty()); loadMoreRows(); } bool AddParticipantBoxSearchController::isLoading() { return _timer.isActive() || _requestId; } bool AddParticipantBoxSearchController::searchParticipantsInCache() { auto it = _participantsCache.find(_query); if (it != _participantsCache.cend()) { _requestId = 0; searchParticipantsDone(_requestId, it->second.result, it->second.requestedCount); return true; } return false; } bool AddParticipantBoxSearchController::searchGlobalInCache() { auto it = _globalCache.find(_query); if (it != _globalCache.cend()) { _requestId = 0; searchGlobalDone(_requestId, it->second); return true; } return false; } bool AddParticipantBoxSearchController::loadMoreRows() { if (_query.isEmpty()) { return false; } if (_globalLoaded) { return true; } if (_participantsLoaded) { if (!_chatsContactsAdded) { addChatsContacts(); } if (!isLoading() && !searchGlobalInCache()) { requestGlobal(); } } else if (!isLoading()) { requestParticipants(); } return true; } void AddParticipantBoxSearchController::requestParticipants() { // For search we request a lot of rows from the first query. // (because we've waited for search request by timer already, // so we don't expect it to be fast, but we want to fill cache). auto perPage = kParticipantsPerPage; auto participantsHash = 0; _requestId = request(MTPchannels_GetParticipants( _channel->inputChannel, MTP_channelParticipantsSearch(MTP_string(_query)), MTP_int(_offset), MTP_int(perPage), MTP_int(participantsHash) )).done([this, perPage](const MTPchannels_ChannelParticipants &result, mtpRequestId requestId) { searchParticipantsDone(requestId, result, perPage); }).fail([this](const RPCError &error, mtpRequestId requestId) { if (_requestId == requestId) { _requestId = 0; _participantsLoaded = true; loadMoreRows(); delegate()->peerListSearchRefreshRows(); } }).send(); auto entry = Query(); entry.text = _query; entry.offset = _offset; _participantsQueries.emplace(_requestId, entry); } void AddParticipantBoxSearchController::searchParticipantsDone(mtpRequestId requestId, const MTPchannels_ChannelParticipants &result, int requestedCount) { auto query = _query; if (requestId) { Auth().api().parseChannelParticipants(_channel, result, [&](auto&&...) { auto it = _participantsQueries.find(requestId); if (it != _participantsQueries.cend()) { query = it->second.text; if (it->second.offset == 0) { auto &entry = _participantsCache[query]; entry.result = result; entry.requestedCount = requestedCount; } _participantsQueries.erase(it); } }); } if (_requestId != requestId) { return; } _requestId = 0; TLHelp::VisitChannelParticipants(result, base::overload([&]( const MTPDchannels_channelParticipants &data) { auto &list = data.vparticipants.v; if (list.size() < requestedCount) { // We want cache to have full information about a query with small // results count (if we don't need the second request). So we don't // wait for an empty results list unlike the non-search peer list. _participantsLoaded = true; if (list.empty() && _offset == 0) { // No results, so we want to request global search immediately. loadMoreRows(); } } auto addUser = [&](auto user) { delegate()->peerListSearchAddRow(user); }; for (auto &participant : list) { AddParticipantBoxController::HandleParticipant( participant, _additional, addUser); } _offset += list.size(); }, [&](mtpTypeId type) { _participantsLoaded = true; })); delegate()->peerListSearchRefreshRows(); } void AddParticipantBoxSearchController::requestGlobal() { if (_query.isEmpty()) { _globalLoaded = true; return; } auto perPage = SearchPeopleLimit; _requestId = request(MTPcontacts_Search( MTP_string(_query), MTP_int(perPage) )).done([=](const MTPcontacts_Found &result, mtpRequestId requestId) { searchGlobalDone(requestId, result); }).fail([=](const RPCError &error, mtpRequestId requestId) { if (_requestId == requestId) { _requestId = 0; _globalLoaded = true; delegate()->peerListSearchRefreshRows(); } }).send(); _globalQueries.emplace(_requestId, _query); } void AddParticipantBoxSearchController::searchGlobalDone(mtpRequestId requestId, const MTPcontacts_Found &result) { Expects(result.type() == mtpc_contacts_found); auto &found = result.c_contacts_found(); auto query = _query; if (requestId) { App::feedUsers(found.vusers); App::feedChats(found.vchats); auto it = _globalQueries.find(requestId); if (it != _globalQueries.cend()) { query = it->second; _globalCache[query] = result; _globalQueries.erase(it); } } const auto feedList = [&](const MTPVector<MTPPeer> &list) { const auto contains = [](const auto &map, const auto &value) { return map.find(value) != map.end(); }; for (const auto &mtpPeer : list.v) { const auto peerId = peerFromMTP(mtpPeer); if (const auto peer = App::peerLoaded(peerId)) { if (const auto user = peer->asUser()) { if (_additional->creator != user && !contains(_additional->adminRights, user) && !contains(_additional->restrictedRights, user) && !contains(_additional->external, user) && !contains(_additional->kicked, user)) { _additional->infoNotLoaded.emplace(user); } delegate()->peerListSearchAddRow(user); } } } }; if (_requestId == requestId) { _requestId = 0; _globalLoaded = true; feedList(found.vmy_results); feedList(found.vresults); delegate()->peerListSearchRefreshRows(); } } void AddParticipantBoxSearchController::addChatsContacts() { _chatsContactsAdded = true; auto wordList = TextUtilities::PrepareSearchWords(_query); if (wordList.empty()) { return; } auto getSmallestIndex = [&](Dialogs::IndexedList *list) -> const Dialogs::List* { if (list->isEmpty()) { return nullptr; } auto result = (const Dialogs::List*)nullptr; for_const (auto &word, wordList) { auto found = list->filtered(word[0]); if (found->isEmpty()) { return nullptr; } if (!result || result->size() > found->size()) { result = found; } } return result; }; auto dialogsIndex = getSmallestIndex(App::main()->dialogsList()); auto contactsIndex = getSmallestIndex(App::main()->contactsNoDialogsList()); auto allWordsAreFound = [&](const base::flat_set<QString> &nameWords) { auto hasNamePartStartingWith = [&](const QString &word) { for (auto &nameWord : nameWords) { if (nameWord.startsWith(word)) { return true; } } return false; }; for_const (auto &word, wordList) { if (!hasNamePartStartingWith(word)) { return false; } } return true; }; auto filterAndAppend = [&](const Dialogs::List *list) { if (!list) { return; } for (const auto row : *list) { if (const auto history = row->history()) { if (const auto user = history->peer->asUser()) { if (allWordsAreFound(user->nameWords())) { delegate()->peerListSearchAddRow(user); } } } } }; filterAndAppend(dialogsIndex); filterAndAppend(contactsIndex); delegate()->peerListSearchRefreshRows(); } } // namespace Profile ������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/profile/profile_channel_controllers.h��������������������������0000664�0000000�0000000�00000022755�13262451251�0027025�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/variable.h> #include "boxes/peer_list_box.h" #include "mtproto/sender.h" #include "base/timer.h" #include "base/weak_ptr.h" #include "info/profile/info_profile_members_controllers.h" namespace Window { class Navigation; } // namespace Window namespace Profile { // Viewing admins, banned or restricted users list with search. class ParticipantsBoxController : public PeerListController , private base::Subscriber , private MTP::Sender , public base::has_weak_ptr { public: enum class Role { Profile, Members, Admins, Restricted, Kicked, }; static void Start( not_null<Window::Navigation*> navigation, not_null<ChannelData*> channel, Role role); struct Additional { std::map<not_null<UserData*>, MTPChannelAdminRights> adminRights; std::set<not_null<UserData*>> adminCanEdit; std::map<not_null<UserData*>, not_null<UserData*>> adminPromotedBy; std::map<not_null<UserData*>, MTPChannelBannedRights> restrictedRights; std::set<not_null<UserData*>> kicked; std::map<not_null<UserData*>, not_null<UserData*>> restrictedBy; std::set<not_null<UserData*>> external; std::set<not_null<UserData*>> infoNotLoaded; UserData *creator = nullptr; }; ParticipantsBoxController( not_null<Window::Navigation*> navigation, not_null<ChannelData*> channel, Role role); void addNewItem(); void prepare() override; void rowClicked(not_null<PeerListRow*> row) override; void rowActionClicked(not_null<PeerListRow*> row) override; base::unique_qptr<Ui::PopupMenu> rowContextMenu( not_null<PeerListRow*> row) override; void loadMoreRows() override; void peerListSearchAddRow(not_null<PeerData*> peer) override; std::unique_ptr<PeerListRow> createSearchRow( not_null<PeerData*> peer) override; std::unique_ptr<PeerListRow> createRestoredRow( not_null<PeerData*> peer) override; std::unique_ptr<PeerListState> saveState() const override; void restoreState(std::unique_ptr<PeerListState> state) override; // Callback(not_null<UserData*>) template <typename Callback> static void HandleParticipant( const MTPChannelParticipant &participant, Role role, not_null<Additional*> additional, Callback callback); rpl::producer<int> onlineCountValue() const override; protected: virtual std::unique_ptr<PeerListRow> createRow(not_null<UserData*> user) const; private: using Row = Info::Profile::MemberListRow; using Type = Row::Type; using Rights = Row::Rights; struct SavedState : SavedStateBase { std::unique_ptr<PeerListSearchController::SavedStateBase> searchState; int offset = 0; bool allLoaded = false; bool wasLoading = false; Additional additional; rpl::lifetime lifetime; }; static std::unique_ptr<PeerListSearchController> CreateSearchController( not_null<ChannelData*> channel, Role role, not_null<Additional*> additional); void setNonEmptyDescription(); void setupSortByOnline(); void setupListChangeViewers(); void sortByOnlineDelayed(); void sortByOnline(); void showAdmin(not_null<UserData*> user); void editAdminDone( not_null<UserData*> user, const MTPChannelAdminRights &rights); void showRestricted(not_null<UserData*> user); void editRestrictedDone( not_null<UserData*> user, const MTPChannelBannedRights &rights); void removeKicked(not_null<PeerListRow*> row, not_null<UserData*> user); void kickMember(not_null<UserData*> user); void kickMemberSure(not_null<UserData*> user); void removeAdmin(not_null<UserData*> user); void removeAdminSure(not_null<UserData*> user); bool appendRow(not_null<UserData*> user); bool prependRow(not_null<UserData*> user); bool removeRow(not_null<UserData*> user); void refreshCustomStatus(not_null<PeerListRow*> row) const; bool feedMegagroupLastParticipants(); void refreshOnlineCount(); Type computeType(not_null<UserData*> user) const; void recomputeTypeFor(not_null<UserData*> user); bool canEditAdmin(not_null<UserData*> user) const; bool canRestrictUser(not_null<UserData*> user) const; bool canEditAdminByRights(not_null<UserData*> user) const; not_null<Window::Navigation*> _navigation; not_null<ChannelData*> _channel; Role _role = Role::Admins; int _offset = 0; mtpRequestId _loadRequestId = 0; bool _allLoaded = false; Additional _additional; QPointer<BoxContent> _editBox; QPointer<PeerListBox> _addBox; base::Timer _sortByOnlineTimer; rpl::variable<int> _onlineCount = 0; }; // Members, banned and restricted users server side search. class ParticipantsBoxSearchController : public PeerListSearchController , private MTP::Sender { public: using Role = ParticipantsBoxController::Role; using Additional = ParticipantsBoxController::Additional; ParticipantsBoxSearchController(not_null<ChannelData*> channel, Role role, not_null<Additional*> additional); void searchQuery(const QString &query) override; bool isLoading() override; bool loadMoreRows() override; std::unique_ptr<SavedStateBase> saveState() const override; void restoreState(std::unique_ptr<SavedStateBase> state) override; private: struct SavedState : SavedStateBase { QString query; int offset = 0; bool allLoaded = false; bool wasLoading = false; }; struct CacheEntry { MTPchannels_ChannelParticipants result; int requestedCount = 0; }; struct Query { QString text; int offset = 0; }; void searchOnServer(); bool searchInCache(); void searchDone( mtpRequestId requestId, const MTPchannels_ChannelParticipants &result, int requestedCount); not_null<ChannelData*> _channel; Role _role = Role::Restricted; not_null<Additional*> _additional; base::Timer _timer; QString _query; mtpRequestId _requestId = 0; int _offset = 0; bool _allLoaded = false; std::map<QString, CacheEntry> _cache; std::map<mtpRequestId, Query> _queries; }; // Adding an admin, banned or restricted user from channel members with search + contacts search + global search. class AddParticipantBoxController : public PeerListController, private base::Subscriber, private MTP::Sender, public base::has_weak_ptr { public: using Role = ParticipantsBoxController::Role; using Additional = ParticipantsBoxController::Additional; using AdminDoneCallback = base::lambda<void(not_null<UserData*> user, const MTPChannelAdminRights &adminRights)>; using BannedDoneCallback = base::lambda<void(not_null<UserData*> user, const MTPChannelBannedRights &bannedRights)>; AddParticipantBoxController(not_null<ChannelData*> channel, Role role, AdminDoneCallback adminDoneCallback, BannedDoneCallback bannedDoneCallback); void prepare() override; void rowClicked(not_null<PeerListRow*> row) override; void loadMoreRows() override; std::unique_ptr<PeerListRow> createSearchRow(not_null<PeerData*> peer) override; // Callback(not_null<UserData*>) template <typename Callback> static void HandleParticipant(const MTPChannelParticipant &participant, not_null<Additional*> additional, Callback callback); private: template <typename Callback> bool checkInfoLoaded(not_null<UserData*> user, Callback callback); void showAdmin(not_null<UserData*> user, bool sure = false); void editAdminDone(not_null<UserData*> user, const MTPChannelAdminRights &rights); void showRestricted(not_null<UserData*> user, bool sure = false); void editRestrictedDone(not_null<UserData*> user, const MTPChannelBannedRights &rights); void kickUser(not_null<UserData*> user, bool sure = false); void restrictUserSure(not_null<UserData*> user, const MTPChannelBannedRights &oldRights, const MTPChannelBannedRights &newRights); bool appendRow(not_null<UserData*> user); bool prependRow(not_null<UserData*> user); std::unique_ptr<PeerListRow> createRow(not_null<UserData*> user) const; not_null<ChannelData*> _channel; Role _role = Role::Admins; int _offset = 0; mtpRequestId _loadRequestId = 0; bool _allLoaded = false; Additional _additional; QPointer<BoxContent> _editBox; AdminDoneCallback _adminDoneCallback; BannedDoneCallback _bannedDoneCallback; }; // Finds channel members, then contacts, then global search results. class AddParticipantBoxSearchController : public PeerListSearchController, private MTP::Sender { public: using Role = ParticipantsBoxController::Role; using Additional = ParticipantsBoxController::Additional; AddParticipantBoxSearchController(not_null<ChannelData*> channel, not_null<Additional*> additional); void searchQuery(const QString &query) override; bool isLoading() override; bool loadMoreRows() override; private: struct CacheEntry { MTPchannels_ChannelParticipants result; int requestedCount = 0; }; struct Query { QString text; int offset = 0; }; void searchOnServer(); bool searchParticipantsInCache(); void searchParticipantsDone(mtpRequestId requestId, const MTPchannels_ChannelParticipants &result, int requestedCount); bool searchGlobalInCache(); void searchGlobalDone(mtpRequestId requestId, const MTPcontacts_Found &result); void requestParticipants(); void addChatsContacts(); void requestGlobal(); not_null<ChannelData*> _channel; not_null<Additional*> _additional; base::Timer _timer; QString _query; mtpRequestId _requestId = 0; int _offset = 0; bool _participantsLoaded = false; bool _chatsContactsAdded = false; bool _globalLoaded = false; std::map<QString, CacheEntry> _participantsCache; std::map<mtpRequestId, Query> _participantsQueries; std::map<QString, MTPcontacts_Found> _globalCache; std::map<mtpRequestId, QString> _globalQueries; }; } // namespace Profile �������������������tdesktop-1.2.17/Telegram/SourceFiles/profile/profile_cover_drop_area.cpp����������������������������0000664�0000000�0000000�00000005431�13262451251�0026444�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "profile/profile_cover_drop_area.h" #include "styles/style_profile.h" namespace Profile { CoverDropArea::CoverDropArea(QWidget *parent, const QString &title, const QString &subtitle) : TWidget(parent) , _title(title) , _subtitle(subtitle) , _titleWidth(st::profileDropAreaTitleFont->width(_title)) , _subtitleWidth(st::profileDropAreaSubtitleFont->width(_subtitle)) { } void CoverDropArea::showAnimated() { show(); _hiding = false; setupAnimation(); } void CoverDropArea::hideAnimated(HideFinishCallback &&callback) { _hideFinishCallback = std::move(callback); _hiding = true; setupAnimation(); } void CoverDropArea::paintEvent(QPaintEvent *e) { Painter p(this); if (_a_appearance.animating(getms())) { p.setOpacity(_a_appearance.current()); p.drawPixmap(0, 0, _cache); return; } if (!_cache.isNull()) { _cache = QPixmap(); if (_hiding) { hide(); if (_hideFinishCallback) { _hideFinishCallback(this); } return; } } p.fillRect(e->rect(), st::profileDropAreaBg); if (width() < st::profileDropAreaPadding.left() + st::profileDropAreaPadding.right()) return; if (height() < st::profileDropAreaPadding.top() + st::profileDropAreaPadding.bottom()) return; auto border = st::profileDropAreaBorderWidth; auto &borderFg = st::profileDropAreaBorderFg; auto inner = rect().marginsRemoved(st::profileDropAreaPadding); p.fillRect(inner.x(), inner.y(), inner.width(), border, borderFg); p.fillRect(inner.x(), inner.y() + inner.height() - border, inner.width(), border, borderFg); p.fillRect(inner.x(), inner.y() + border, border, inner.height() - 2 * border, borderFg); p.fillRect(inner.x() + inner.width() - border, inner.y() + border, border, inner.height() - 2 * border, borderFg); int titleLeft = inner.x() + (inner.width() - _titleWidth) / 2; int titleTop = inner.y() + st::profileDropAreaTitleTop + st::profileDropAreaTitleFont->ascent; p.setFont(st::profileDropAreaTitleFont); p.setPen(st::profileDropAreaFg); p.drawText(titleLeft, titleTop, _title); int subtitleLeft = inner.x() + (inner.width() - _subtitleWidth) / 2; int subtitleTop = inner.y() + st::profileDropAreaSubtitleTop + st::profileDropAreaSubtitleFont->ascent; p.setFont(st::profileDropAreaSubtitleFont); p.setPen(st::profileDropAreaFg); p.drawText(subtitleLeft, subtitleTop, _subtitle); } void CoverDropArea::setupAnimation() { if (_cache.isNull()) { _cache = Ui::GrabWidget(this); } auto from = _hiding ? 1. : 0., to = _hiding ? 0. : 1.; _a_appearance.start( [this] { update(); }, from, to, st::profileDropAreaDuration); } } // namespace Profile ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/profile/profile_cover_drop_area.h������������������������������0000664�0000000�0000000�00000001552�13262451251�0026111�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace Profile { class CoverDropArea : public TWidget { public: CoverDropArea(QWidget *parent, const QString &title, const QString &subtitle); void showAnimated(); using HideFinishCallback = base::lambda<void(CoverDropArea*)>; void hideAnimated(HideFinishCallback &&callback); bool hiding() const { return _hiding; } protected: void paintEvent(QPaintEvent *e) override; private: void setupAnimation(); QString _title, _subtitle; int _titleWidth, _subtitleWidth; QPixmap _cache; Animation _a_appearance; bool _hiding = false; HideFinishCallback _hideFinishCallback; }; } // namespace Profile ������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/qt_static_plugins.cpp������������������������������������������0000664�0000000�0000000�00000001723�13262451251�0023666�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include <QtCore/QtPlugin> #ifdef Q_OS_WIN Q_IMPORT_PLUGIN(QWebpPlugin) Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin) #elif defined Q_OS_MAC // Q_OS_WIN Q_IMPORT_PLUGIN(QWebpPlugin) Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin) Q_IMPORT_PLUGIN(QGenericEnginePlugin) #elif defined Q_OS_LINUX // Q_OS_WIN | Q_OS_MAC Q_IMPORT_PLUGIN(QWebpPlugin) Q_IMPORT_PLUGIN(QXcbIntegrationPlugin) Q_IMPORT_PLUGIN(QConnmanEnginePlugin) Q_IMPORT_PLUGIN(QGenericEnginePlugin) Q_IMPORT_PLUGIN(QNetworkManagerEnginePlugin) Q_IMPORT_PLUGIN(QComposePlatformInputContextPlugin) Q_IMPORT_PLUGIN(QIbusPlatformInputContextPlugin) Q_IMPORT_PLUGIN(QFcitxPlatformInputContextPlugin) Q_IMPORT_PLUGIN(QHimePlatformInputContextPlugin) #endif // Q_OS_WIN | Q_OS_MAC | Q_OS_LINUX ���������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/rpl/�����������������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0020220�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/rpl/after_next.h�����������������������������������������������0000664�0000000�0000000�00000002721�13262451251�0022532�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/producer.h> namespace rpl { namespace details { template <typename SideEffect> class after_next_helper { public: template <typename OtherSideEffect> after_next_helper(OtherSideEffect &&method) : _method(std::forward<OtherSideEffect>(method)) { } template <typename Value, typename Error, typename Generator> auto operator()(producer<Value, Error, Generator> &&initial) { return make_producer<Value, Error>([ initial = std::move(initial), method = std::move(_method) ](const auto &consumer) mutable { return std::move(initial).start( [method = std::move(method), consumer](auto &&value) { auto copy = method; consumer.put_next_copy(value); std::move(copy)( std::forward<decltype(value)>(value)); }, [consumer](auto &&error) { consumer.put_error_forward( std::forward<decltype(error)>(error)); }, [consumer] { consumer.put_done(); }); }); } private: SideEffect _method; }; } // namespace details template <typename SideEffect> inline auto after_next(SideEffect &&method) -> details::after_next_helper<std::decay_t<SideEffect>> { return details::after_next_helper<std::decay_t<SideEffect>>( std::forward<SideEffect>(method)); } } // namespace rpl �����������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/rpl/before_next.h����������������������������������������������0000664�0000000�0000000�00000001037�13262451251�0022672�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/producer.h> #include <rpl/filter.h> namespace rpl { template <typename SideEffect> inline auto before_next(SideEffect &&method) { return filter([method = std::forward<SideEffect>(method)]( const auto &value) { method(value); return true; }); } } // namespace rpl �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/rpl/combine.h��������������������������������������������������0000664�0000000�0000000�00000022654�13262451251�0022016�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "base/optional.h" #include <rpl/map.h> #include <rpl/producer.h> #include <rpl/details/type_list.h> #include <rpl/details/callable.h> #include <rpl/mappers.h> #include <rpl/complete.h> namespace rpl { namespace details { template <typename ...Values> struct combine_state { combine_state() : accumulated(std::tuple<base::optional<Values>...>()) { } base::optional<std::tuple<base::optional<Values>...>> accumulated; base::optional<std::tuple<Values...>> latest; int invalid = sizeof...(Values); int working = sizeof...(Values); }; template <typename ...Values, std::size_t ...I> inline std::tuple<Values...> combine_make_first( std::tuple<base::optional<Values>...> &&accumulated, std::index_sequence<I...>) { return std::make_tuple(std::move(*std::get<I>(accumulated))...); } template <size_t Index, typename consumer_type, typename ...Values> class combine_subscribe_one { public: combine_subscribe_one( const consumer_type &consumer, combine_state<Values...> *state) : _consumer(consumer) , _state(state) { } template <typename Value, typename Error, typename Generator> void subscribe(producer<Value, Error, Generator> &&producer) { _consumer.add_lifetime(std::move(producer).start( [consumer = _consumer, state = _state](Value &&value) { if (!state->accumulated) { std::get<Index>(*state->latest) = std::move(value); consumer.put_next_copy(*state->latest); } else { auto &accumulated = std::get<Index>( *state->accumulated); if (accumulated) { accumulated = std::move(value); } else { accumulated = std::move(value); if (!--state->invalid) { constexpr auto kArity = sizeof...(Values); state->latest = combine_make_first( std::move(*state->accumulated), std::make_index_sequence<kArity>()); state->accumulated = base::none; consumer.put_next_copy(*state->latest); } } } }, [consumer = _consumer](auto &&error) { consumer.put_error_forward( std::forward<decltype(error)>(error)); }, [consumer = _consumer, state = _state] { if (!--state->working) { consumer.put_done(); } })); } private: const consumer_type &_consumer; combine_state<Values...> *_state = nullptr; }; template < typename consumer_type, typename ...Values, typename ...Errors, typename ...Generators, std::size_t ...I> inline void combine_subscribe( const consumer_type &consumer, combine_state<Values...> *state, std::index_sequence<I...>, std::tuple<producer<Values, Errors, Generators>...> &&saved) { auto consume = { ( combine_subscribe_one<I, consumer_type, Values...>( consumer, state ).subscribe(std::get<I>(std::move(saved))), 0)... }; (void)consume; } template <typename ...Producers> class combine_implementation_helper; template <typename ...Producers> combine_implementation_helper<std::decay_t<Producers>...> make_combine_implementation_helper(Producers &&...producers) { return combine_implementation_helper<std::decay_t<Producers>...>( std::forward<Producers>(producers)...); } template < typename ...Values, typename ...Errors, typename ...Generators> class combine_implementation_helper<producer<Values, Errors, Generators>...> { public: using CombinedValue = std::tuple<Values...>; using CombinedError = normalized_variant_t<Errors...>; combine_implementation_helper( producer<Values, Errors, Generators> &&...producers) : _saved(std::make_tuple(std::move(producers)...)) { } template <typename Handlers> lifetime operator()(const consumer<CombinedValue, CombinedError, Handlers> &consumer) { auto state = consumer.template make_state< combine_state<Values...>>(); constexpr auto kArity = sizeof...(Values); combine_subscribe( consumer, state, std::make_index_sequence<kArity>(), std::move(_saved)); return lifetime(); } private: std::tuple<producer<Values, Errors, Generators>...> _saved; }; template < typename ...Values, typename ...Errors, typename ...Generators> inline auto combine_implementation( producer<Values, Errors, Generators> &&...producers) { using CombinedValue = std::tuple<Values...>; using CombinedError = normalized_variant_t<Errors...>; return make_producer<CombinedValue, CombinedError>( make_combine_implementation_helper(std::move(producers)...)); } template <typename ...Args> struct combine_just_producers : std::false_type { }; template <typename ...Args> constexpr bool combine_just_producers_v = combine_just_producers<Args...>::value; template < typename ...Values, typename ...Errors, typename ...Generators> struct combine_just_producers< producer<Values, Errors, Generators>...> : std::true_type { }; template <typename ArgsList> struct combine_just_producers_list : type_list::extract_to_t<ArgsList, combine_just_producers> { }; template <typename ...Args> struct combine_result_type; template <typename ...Args> using combine_result_type_t = typename combine_result_type<Args...>::type; template < typename ...Values, typename ...Errors, typename ...Generators> struct combine_result_type<producer<Values, Errors, Generators>...> { using type = std::tuple<Values...>; }; template <typename ArgsList> struct combine_result_type_list : type_list::extract_to_t<ArgsList, combine_result_type> { }; template <typename ArgsList> using combine_result_type_list_t = typename combine_result_type_list<ArgsList>::type; template <typename ArgsList> using combine_producers_no_mapper_t = type_list::chop_last_t<ArgsList>; template <typename ArgsList> constexpr bool combine_is_good_mapper(std::true_type) { return is_callable_v< type_list::last_t<ArgsList>, combine_result_type_list_t< combine_producers_no_mapper_t<ArgsList> >>; } template <typename ArgsList> constexpr bool combine_is_good_mapper(std::false_type) { return false; } template <typename ArgsList> struct combine_producers_with_mapper_list : std::bool_constant< combine_is_good_mapper<ArgsList>( combine_just_producers_list< combine_producers_no_mapper_t<ArgsList> >())> { }; template <typename ...Args> struct combine_producers_with_mapper : combine_producers_with_mapper_list<type_list::list<Args...>> { }; template <typename ...Args> constexpr bool combine_producers_with_mapper_v = combine_producers_with_mapper<Args...>::value; template <typename ...Producers, std::size_t ...I> inline decltype(auto) combine_call( std::index_sequence<I...>, Producers &&...producers) { return combine_implementation( argument_mapper<I>::call(std::move(producers)...)...); } } // namespace details template < typename ...Args, typename = std::enable_if_t< details::combine_just_producers_v<Args...> || details::combine_producers_with_mapper_v<Args...>>> inline decltype(auto) combine(Args &&...args) { if constexpr (details::combine_just_producers_v<Args...>) { return details::combine_implementation(std::move(args)...); } else if constexpr (details::combine_producers_with_mapper_v<Args...>) { constexpr auto kProducersCount = sizeof...(Args) - 1; return details::combine_call( std::make_index_sequence<kProducersCount>(), std::forward<Args>(args)...) | map(details::argument_mapper<kProducersCount>::call( std::forward<Args>(args)...)); } else { static_assert(false_(args...), "Bad combine() call."); } } namespace details { template <typename Value> struct combine_vector_state { std::vector<base::optional<Value>> accumulated; std::vector<Value> latest; int invalid = 0; int working = 0; }; } // namespace details template <typename Value, typename Error, typename Generator> inline auto combine( std::vector<producer<Value, Error, Generator>> &&producers) { using state_type = details::combine_vector_state<Value>; return make_producer<std::vector<Value>, Error>([ producers = std::move(producers) ](const auto &consumer) mutable { auto count = producers.size(); auto state = consumer.template make_state<state_type>(); state->accumulated.resize(count); state->invalid = count; state->working = count; for (auto index = 0; index != count; ++index) { auto &producer = producers[index]; consumer.add_lifetime(std::move(producer).start( [consumer, state, index](Value &&value) { if (state->accumulated.empty()) { state->latest[index] = std::move(value); consumer.put_next_copy(state->latest); } else if (state->accumulated[index]) { state->accumulated[index] = std::move(value); } else { state->accumulated[index] = std::move(value); if (!--state->invalid) { state->latest.reserve( state->accumulated.size()); for (auto &&value : state->accumulated) { state->latest.push_back( std::move(*value)); } details::take(state->accumulated); consumer.put_next_copy(state->latest); } } }, [consumer](auto &&error) { consumer.put_error_forward( std::forward<decltype(error)>(error)); }, [consumer, state] { if (!--state->working) { consumer.put_done(); } })); } if (!count) { consumer.put_done(); } return lifetime(); }); } template < typename Value, typename Error, typename Generator, typename Mapper> inline auto combine( std::vector<producer<Value, Error, Generator>> &&producers, Mapper &&mapper) { return combine(std::move(producers)) | map(std::forward<Mapper>(mapper)); } } // namespace rpl ������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/rpl/combine_previous.h�����������������������������������������0000664�0000000�0000000�00000005566�13262451251�0023755�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/producer.h> #include "base/optional.h" namespace rpl { namespace details { class combine_previous_helper { public: template <typename Value, typename Error, typename Generator> auto operator()( producer<Value, Error, Generator> &&initial) const { return make_producer<std::tuple<Value, Value>, Error>([ initial = std::move(initial) ](const auto &consumer) mutable { auto previous = consumer.template make_state< base::optional<Value> >(); return std::move(initial).start( [consumer, previous](auto &&value) { if (auto &exists = *previous) { auto &existing = *exists; auto next = std::make_tuple( std::move(existing), value); consumer.put_next(std::move(next)); existing = std::forward<decltype(value)>( value); } else { *previous = std::forward<decltype(value)>( value); } }, [consumer](auto &&error) { consumer.put_error_forward(std::forward<decltype(error)>(error)); }, [consumer] { consumer.put_done(); }); }); } }; template <typename DefaultValue> class combine_previous_with_default_helper { public: template <typename OtherValue> combine_previous_with_default_helper(OtherValue &&value) : _value(std::forward<OtherValue>(value)) { } template <typename Value, typename Error, typename Generator> auto operator()(producer<Value, Error, Generator> &&initial) { return make_producer<std::tuple<Value, Value>, Error>([ initial = std::move(initial), value = Value(std::move(_value)) ](const auto &consumer) mutable { auto previous = consumer.template make_state<Value>( std::move(value)); return std::move(initial).start( [consumer, previous](auto &&value) { auto &existing = *previous; auto next = std::make_tuple( std::move(existing), value); consumer.put_next(std::move(next)); existing = std::forward<decltype(value)>(value); }, [consumer](auto &&error) { consumer.put_error_forward(std::forward<decltype(error)>(error)); }, [consumer] { consumer.put_done(); }); }); } private: DefaultValue _value; }; template <typename DefaultValue> combine_previous_with_default_helper<std::decay_t<DefaultValue>> combine_previous_with_default(DefaultValue &&value) { return { std::forward<DefaultValue>(value) }; } } // namespace details inline auto combine_previous() -> details::combine_previous_helper { return details::combine_previous_helper(); } template <typename DefaultValue> inline auto combine_previous(DefaultValue &&value) { return details::combine_previous_with_default( std::forward<DefaultValue>(value)); } } // namespace rpl ������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/rpl/complete.h�������������������������������������������������0000664�0000000�0000000�00000001014�13262451251�0022175�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/producer.h> namespace rpl { template <typename Value = empty_value, typename Error = no_error> inline auto complete() { return make_producer<Value, Error>([](const auto &consumer) { consumer.put_done(); return lifetime(); }); } } // namespace rpl ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/rpl/consumer.h�������������������������������������������������0000664�0000000�0000000�00000034273�13262451251�0022235�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <gsl/gsl_assert> #include <rpl/lifetime.h> #include <rpl/details/callable.h> // GCC 7.2 can't handle not type-erased consumers. // It eats up 4GB RAM + 16GB swap on the unittest and dies. // Clang and Visual C++ both handle it without such problems. #if defined _DEBUG || defined COMPILER_GCC #define RPL_CONSUMER_TYPE_ERASED_ALWAYS #endif // _DEBUG || COMPILER_GCC namespace rpl { namespace details { template < typename Value, typename Error, typename OnNext, typename OnError, typename OnDone> class consumer_handlers; template <typename Value, typename Error> class type_erased_handlers { public: virtual bool put_next(Value &&value) = 0; virtual bool put_next_copy(const Value &value) = 0; virtual void put_error(Error &&error) = 0; virtual void put_error_copy(const Error &error) = 0; virtual void put_done() = 0; bool add_lifetime(lifetime &&lifetime); template <typename Type, typename... Args> Type *make_state(Args&& ...args); void terminate(); virtual ~type_erased_handlers() = default; protected: lifetime _lifetime; bool _terminated = false; }; template <typename Handlers> struct is_type_erased_handlers : std::false_type { }; template <typename Value, typename Error> struct is_type_erased_handlers<type_erased_handlers<Value, Error>> : std::true_type { }; template <typename Handlers> constexpr bool is_type_erased_handlers_v = is_type_erased_handlers<Handlers>::value; template <typename Value, typename Error, typename OnNext, typename OnError, typename OnDone> class consumer_handlers final : public type_erased_handlers<Value, Error> { public: template < typename OnNextOther, typename OnErrorOther, typename OnDoneOther> consumer_handlers( OnNextOther &&next, OnErrorOther &&error, OnDoneOther &&done) : _next(std::forward<OnNextOther>(next)) , _error(std::forward<OnErrorOther>(error)) , _done(std::forward<OnDoneOther>(done)) { } bool put_next(Value &&value) final override; bool put_next_copy(const Value &value) final override; void put_error(Error &&error) final override; void put_error_copy(const Error &error) final override; void put_done() final override; private: OnNext _next; OnError _error; OnDone _done; }; template <typename Value, typename Error> inline bool type_erased_handlers<Value, Error>::add_lifetime( lifetime &&lifetime) { if (_terminated) { lifetime.destroy(); return false; } _lifetime.add(std::move(lifetime)); return true; } template <typename Value, typename Error> template <typename Type, typename... Args> inline Type *type_erased_handlers<Value, Error>::make_state( Args&& ...args) { if (_terminated) { return nullptr; } return _lifetime.make_state<Type>(std::forward<Args>(args)...); } template <typename Value, typename Error> inline void type_erased_handlers<Value, Error>::terminate() { if (!_terminated) { _terminated = true; _lifetime.destroy(); } } template < typename Value, typename Error, typename OnNext, typename OnError, typename OnDone> bool consumer_handlers< Value, Error, OnNext, OnError, OnDone >::put_next(Value &&value) { if (this->_terminated) { return false; } auto handler = this->_next; details::callable_invoke(std::move(handler), std::move(value)); return true; } template < typename Value, typename Error, typename OnNext, typename OnError, typename OnDone> bool consumer_handlers< Value, Error, OnNext, OnError, OnDone >::put_next_copy(const Value &value) { if (this->_terminated) { return false; } auto handler = this->_next; details::const_ref_call_invoke(std::move(handler), value); return true; } template < typename Value, typename Error, typename OnNext, typename OnError, typename OnDone> void consumer_handlers< Value, Error, OnNext, OnError, OnDone >::put_error(Error &&error) { if (!this->_terminated) { details::callable_invoke( std::move(this->_error), std::move(error)); this->terminate(); } } template < typename Value, typename Error, typename OnNext, typename OnError, typename OnDone> void consumer_handlers< Value, Error, OnNext, OnError, OnDone >::put_error_copy(const Error &error) { if (!this->_terminated) { details::const_ref_call_invoke( std::move(this->_error), error); this->terminate(); } } template < typename Value, typename Error, typename OnNext, typename OnError, typename OnDone> void consumer_handlers< Value, Error, OnNext, OnError, OnDone >::put_done() { if (!this->_terminated) { std::move(this->_done)(); this->terminate(); } } } // namespace details struct no_value { no_value() = delete; }; struct no_error { no_error() = delete; }; struct empty_value { }; struct empty_error { }; template < typename Value = empty_value, typename Error = no_error, typename Handlers = details::type_erased_handlers<Value, Error>> class consumer; namespace details { template <typename Value, typename Error, typename Handlers> class consumer_base { static constexpr bool is_type_erased = is_type_erased_handlers_v<Handlers>; public: template < typename OnNext, typename OnError, typename OnDone> consumer_base( OnNext &&next, OnError &&error, OnDone &&done); bool put_next(Value &&value) const; bool put_next_copy(const Value &value) const; bool put_next_forward(Value &&value) const { return put_next(std::move(value)); } bool put_next_forward(const Value &value) const { return put_next_copy(value); } void put_error(Error &&error) const; void put_error_copy(const Error &error) const; void put_error_forward(Error &&error) const { return put_error(std::move(error)); } void put_error_forward(const Error &error) const { return put_error_copy(error); } void put_done() const; bool add_lifetime(lifetime &&lifetime) const; template <typename Type, typename... Args> Type *make_state(Args&& ...args) const; void terminate() const; auto terminator() const { return [self = *this] { self.terminate(); }; } const details::type_erased_handlers<Value, Error> *comparable() const { return _handlers.get(); } private: template < typename OtherHandlers, typename = std::enable_if_t< std::is_base_of_v<Handlers, OtherHandlers>>> consumer_base(const std::shared_ptr<OtherHandlers> &handlers) : _handlers(handlers) { } template < typename OtherHandlers, typename = std::enable_if_t< std::is_base_of_v<Handlers, OtherHandlers>>> consumer_base(std::shared_ptr<OtherHandlers> &&handlers) : _handlers(std::move(handlers)) { } mutable std::shared_ptr<Handlers> _handlers; bool handlers_put_next(Value &&value) const { if constexpr (is_type_erased) { return _handlers->put_next(std::move(value)); } else { return _handlers->Handlers::put_next(std::move(value)); } } bool handlers_put_next_copy(const Value &value) const { if constexpr (is_type_erased) { return _handlers->put_next_copy(value); } else { return _handlers->Handlers::put_next_copy(value); } } std::shared_ptr<Handlers> take_handlers() const { return std::exchange(_handlers, nullptr); } template < typename OtherValue, typename OtherError, typename OtherHandlers> friend class ::rpl::consumer; }; template <typename Value, typename Error, typename Handlers> template <typename OnNext, typename OnError, typename OnDone> inline consumer_base<Value, Error, Handlers>::consumer_base( OnNext &&next, OnError &&error, OnDone &&done) : _handlers(std::make_shared<consumer_handlers< Value, Error, std::decay_t<OnNext>, std::decay_t<OnError>, std::decay_t<OnDone>>>( std::forward<OnNext>(next), std::forward<OnError>(error), std::forward<OnDone>(done))) { } template <typename Value, typename Error, typename Handlers> inline bool consumer_base<Value, Error, Handlers>::put_next( Value &&value) const { if (_handlers) { if (handlers_put_next(std::move(value))) { return true; } _handlers = nullptr; } return false; } template <typename Value, typename Error, typename Handlers> inline bool consumer_base<Value, Error, Handlers>::put_next_copy( const Value &value) const { if (_handlers) { if (handlers_put_next_copy(value)) { return true; } _handlers = nullptr; } return false; } template <typename Value, typename Error, typename Handlers> inline void consumer_base<Value, Error, Handlers>::put_error( Error &&error) const { if (_handlers) { if constexpr (is_type_erased) { take_handlers()->put_error(std::move(error)); } else { take_handlers()->Handlers::put_error(std::move(error)); } } } template <typename Value, typename Error, typename Handlers> inline void consumer_base<Value, Error, Handlers>::put_error_copy( const Error &error) const { if (_handlers) { if constexpr (is_type_erased) { take_handlers()->put_error_copy(error); } else { take_handlers()->Handlers::put_error_copy(error); } } } template <typename Value, typename Error, typename Handlers> inline void consumer_base<Value, Error, Handlers>::put_done() const { if (_handlers) { if constexpr (is_type_erased) { take_handlers()->put_done(); } else { take_handlers()->Handlers::put_done(); } } } template <typename Value, typename Error, typename Handlers> inline bool consumer_base<Value, Error, Handlers>::add_lifetime( lifetime &&lifetime) const { if (!_handlers) { lifetime.destroy(); return false; } if (_handlers->add_lifetime(std::move(lifetime))) { return true; } _handlers = nullptr; return false; } template <typename Value, typename Error, typename Handlers> template <typename Type, typename... Args> inline Type *consumer_base<Value, Error, Handlers>::make_state( Args&& ...args) const { if (!_handlers) { return nullptr; } if (auto result = _handlers->template make_state<Type>( std::forward<Args>(args)...)) { return result; } _handlers = nullptr; return nullptr; } template <typename Value, typename Error, typename Handlers> inline void consumer_base<Value, Error, Handlers>::terminate() const { if (_handlers) { std::exchange(_handlers, nullptr)->terminate(); } } template <typename Value, typename Error> using consumer_base_type_erased = consumer_base< Value, Error, details::type_erased_handlers<Value, Error>>; template <typename Value, typename Error, typename Handlers> constexpr bool is_specific_handlers_v = !std::is_same_v< details::type_erased_handlers<Value, Error>, Handlers > && std::is_base_of_v< details::type_erased_handlers<Value, Error>, Handlers >; } // namespace details template <typename Value, typename Error, typename Handlers> class consumer final : public details::consumer_base<Value, Error, Handlers> { using parent_type = details::consumer_base< Value, Error, Handlers>; public: using parent_type::parent_type; }; template <typename Value, typename Error> class consumer<Value, Error, details::type_erased_handlers<Value, Error>> final : public details::consumer_base_type_erased<Value, Error> { using parent_type = details::consumer_base_type_erased< Value, Error>; public: using parent_type::parent_type; template < typename Handlers, typename = std::enable_if_t< details::is_specific_handlers_v<Value, Error, Handlers>>> consumer(const details::consumer_base<Value, Error, Handlers> &other) : parent_type(other._handlers) { } template < typename Handlers, typename = std::enable_if_t< details::is_specific_handlers_v<Value, Error, Handlers>>> consumer(details::consumer_base<Value, Error, Handlers> &&other) : parent_type(std::move(other._handlers)) { } template < typename Handlers, typename = std::enable_if_t< details::is_specific_handlers_v<Value, Error, Handlers>>> consumer &operator=( const details::consumer_base<Value, Error, Handlers> &other) { this->_handlers = other._handlers; return *this; } template < typename Handlers, typename = std::enable_if_t< details::is_specific_handlers_v<Value, Error, Handlers>>> consumer &operator=( details::consumer_base<Value, Error, Handlers> &&other) { this->_handlers = std::move(other._handlers); return *this; } }; template < typename Value, typename Error, typename Handlers1, typename Handlers2> inline bool operator==( const consumer<Value, Error, Handlers1> &a, const consumer<Value, Error, Handlers2> &b) { return a.comparable() == b.comparable(); } template < typename Value, typename Error, typename Handlers1, typename Handlers2> inline bool operator<( const consumer<Value, Error, Handlers1> &a, const consumer<Value, Error, Handlers2> &b) { return a.comparable() < b.comparable(); } template < typename Value, typename Error, typename Handlers1, typename Handlers2> inline bool operator!=( const consumer<Value, Error, Handlers1> &a, const consumer<Value, Error, Handlers2> &b) { return !(a == b); } template < typename Value, typename Error, typename Handlers1, typename Handlers2> inline bool operator>( const consumer<Value, Error, Handlers1> &a, const consumer<Value, Error, Handlers2> &b) { return b < a; } template < typename Value, typename Error, typename Handlers1, typename Handlers2> inline bool operator<=( const consumer<Value, Error, Handlers1> &a, const consumer<Value, Error, Handlers2> &b) { return !(b < a); } template < typename Value, typename Error, typename Handlers1, typename Handlers2> inline bool operator>=( const consumer<Value, Error, Handlers1> &a, const consumer<Value, Error, Handlers2> &b) { return !(a < b); } template < typename Value, typename Error, typename OnNext, typename OnError, typename OnDone, typename = std::enable_if_t< details::is_callable_v<OnNext, Value> && details::is_callable_v<OnError, Error> && details::is_callable_v<OnDone>>> #ifdef RPL_CONSUMER_TYPE_ERASED_ALWAYS inline consumer<Value, Error> make_consumer( #else // RPL_CONSUMER_TYPE_ERASED_ALWAYS inline auto make_consumer( #endif // !RPL_CONSUMER_TYPE_ERASED_ALWAYS OnNext &&next, OnError &&error, OnDone &&done) { return consumer<Value, Error, details::consumer_handlers< Value, Error, std::decay_t<OnNext>, std::decay_t<OnError>, std::decay_t<OnDone>>>( std::forward<OnNext>(next), std::forward<OnError>(error), std::forward<OnDone>(done)); } } // namespace rpl �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/rpl/deferred.h�������������������������������������������������0000664�0000000�0000000�00000001321�13262451251�0022146�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/producer.h> namespace rpl { template < typename Creator, typename Value = typename decltype(std::declval<Creator>()())::value_type, typename Error = typename decltype(std::declval<Creator>()())::error_type> inline auto deferred(Creator &&creator) { return make_producer<Value, Error>([ creator = std::forward<Creator>(creator) ](const auto &consumer) mutable { return std::move(creator)().start_existing(consumer); }); } } // namespace rpl ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/rpl/details/���������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0021645�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/rpl/details/callable.h�����������������������������������������0000664�0000000�0000000�00000007461�13262451251�0023565�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "base/build_config.h" #include <tuple> namespace rpl { namespace details { template <typename Arg> const Arg &const_ref_val() noexcept; template <typename Arg> Arg &lvalue_ref_val() noexcept; using false_t = char; struct true_t { false_t data[2]; }; static_assert(sizeof(false_t) != sizeof(true_t), "I can't work :("); template < typename Method, typename ...Args, typename = decltype(std::declval<Method>()( std::declval<Args>()...))> true_t test_callable_plain(Method &&, Args &&...) noexcept; false_t test_callable_plain(...) noexcept; template <typename Method, typename ...Args> struct is_callable_plain : std::bool_constant<( sizeof(test_callable_plain( std::declval<Method>(), std::declval<Args>()... )) == sizeof(true_t))> { }; template <typename Method, typename ...Args> constexpr bool is_callable_plain_v = is_callable_plain<Method, Args...>::value; template < typename Method, typename ...Types, typename = decltype(std::declval<Method>()( std::declval<Types>()...))> true_t test_callable_tuple( Method &&, std::tuple<Types...> &&) noexcept; template < typename Method, typename ...Types, typename = decltype(std::declval<Method>()( const_ref_val<Types>()...))> true_t test_callable_tuple( Method &&, const std::tuple<Types...> &) noexcept; false_t test_callable_tuple(...) noexcept; template <typename Method, typename Arg> constexpr bool is_callable_tuple_v = (sizeof(test_callable_tuple( std::declval<Method>(), std::declval<Arg>())) == sizeof(true_t)); template <typename Method, typename Arg> struct is_callable_tuple : std::bool_constant< is_callable_tuple_v<Method, Arg>> { }; template <typename Method, typename ...Args> struct is_callable; template <typename Method> struct is_callable<Method> : std::bool_constant< is_callable_plain_v<Method>> { }; template <typename Method, typename Arg> struct is_callable<Method, Arg> : std::bool_constant< is_callable_plain_v<Method, Arg> || is_callable_tuple_v<Method, Arg> || is_callable_plain_v<Method>> { }; template <typename Method, typename ...Args> constexpr bool is_callable_v = is_callable<Method, Args...>::value; template <typename Method, typename Arg> inline decltype(auto) callable_invoke(Method &&method, Arg &&arg) { if constexpr (is_callable_plain_v<Method, Arg>) { return std::forward<Method>(method)(std::forward<Arg>(arg)); } else if constexpr (is_callable_tuple_v<Method, Arg>) { return std::apply( std::forward<Method>(method), std::forward<Arg>(arg)); } else if constexpr (is_callable_v<Method>) { return std::forward<Method>(method)(); } else { static_assert(false_(method, arg), "Bad callable_invoke() call."); } } template <typename Method, typename Arg> using callable_result = decltype(callable_invoke( std::declval<Method>(), std::declval<Arg>())); template < typename Method, typename Arg, typename = decltype(std::declval<Method>()( const_ref_val<std::decay_t<Arg>>()))> true_t test_allows_const_ref(Method &&, Arg &&) noexcept; false_t test_allows_const_ref(...) noexcept; template <typename Method, typename Arg> constexpr bool allows_const_ref_v = (sizeof(test_allows_const_ref( std::declval<Method>(), std::declval<Arg>())) == sizeof(true_t)); template <typename Method, typename Arg> inline decltype(auto) const_ref_call_invoke( Method &&method, const Arg &arg) { if constexpr (allows_const_ref_v<Method, Arg>) { return callable_invoke(std::forward<Method>(method), arg); } else { auto copy = arg; return callable_invoke( std::forward<Method>(method), std::move(copy)); } } } // namespace details } // namespace rpl ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/rpl/details/superset_type.h������������������������������������0000664�0000000�0000000�00000001060�13262451251�0024726�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace rpl { template <typename Value1, typename Value2> struct superset_type; template <typename Value1, typename Value2> using superset_type_t = typename superset_type<Value1, Value2>::type; template <typename Value> struct superset_type<Value, Value> { using type = Value; }; } // namespace rpl ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/rpl/details/type_list.h����������������������������������������0000664�0000000�0000000�00000010611�13262451251�0024031�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <type_traits> #include "base/variant.h" namespace rpl { namespace details { namespace type_list { template <typename ...Types> struct list { }; template <typename Type, typename ...Types> struct list<Type, Types...> { using head = Type; using tail = list<Types...>; }; using empty_list = list<>; template <typename TypeList> using head_t = typename TypeList::head; template <typename TypeList> using tail_t = typename TypeList::tail; template <typename Head, typename Tail> struct construct; template <typename Head, typename Tail> using construct_t = typename construct<Head, Tail>::type; template <typename Head, typename ...Types> struct construct<Head, list<Types...>> { using type = list<Head, Types...>; }; template <typename TypeList> struct size; template <typename TypeList> constexpr std::size_t size_v = size<TypeList>::value; template <typename ...Types> struct size<list<Types...>> : std::integral_constant< std::size_t, sizeof...(Types)> { }; template <typename TypeList> constexpr bool empty_v = (size_v<TypeList> == 0); template <typename TypeList> struct empty : std::bool_constant<empty_v<TypeList>> { }; template <std::size_t Index, typename TypeList> struct get; template <std::size_t Index, typename TypeList> using get_t = typename get<Index, TypeList>::type; template <std::size_t Index, typename TypeList> struct get { using type = get_t<Index - 1, tail_t<TypeList>>; }; template <typename TypeList> struct get<0, TypeList> { using type = head_t<TypeList>; }; template <typename TypeList1, typename TypeList2> struct concat; template <typename TypeList1, typename TypeList2> using concat_t = typename concat<TypeList1, TypeList2>::type; template <typename ...Types1, typename ...Types2> struct concat<list<Types1...>, list<Types2...>> { using type = list<Types1..., Types2...>; }; template <typename TypeList, typename Type> struct remove_all; template <typename TypeList, typename Type> using remove_all_t = typename remove_all<TypeList, Type>::type; template <typename TypeList, typename Type> struct remove_all { using head = head_t<TypeList>; using tail = tail_t<TypeList>; using clean_tail = remove_all_t<tail, Type>; using type = std::conditional_t< std::is_same_v<head, Type>, clean_tail, construct_t<head, clean_tail>>; }; template <typename Type> struct remove_all<empty_list, Type> { using type = empty_list; }; template <typename TypeList> struct last; template <typename TypeList> using last_t = typename last<TypeList>::type; template <typename TypeList> struct last { using type = last_t<tail_t<TypeList>>; }; template <typename Type> struct last<list<Type>> { using type = Type; }; template <typename TypeList> struct chop_last; template <typename TypeList> using chop_last_t = typename chop_last<TypeList>::type; template <typename TypeList> struct chop_last { using type = construct_t< head_t<TypeList>, chop_last_t<tail_t<TypeList>>>; }; template <typename Type> struct chop_last<list<Type>> { using type = empty_list; }; template <typename TypeList> struct distinct; template <typename TypeList> using distinct_t = typename distinct<TypeList>::type; template <typename TypeList> struct distinct { using type = construct_t< head_t<TypeList>, distinct_t< remove_all_t<tail_t<TypeList>, head_t<TypeList>>>>; }; template <> struct distinct<empty_list> { using type = empty_list; }; template <typename TypeList, template <typename ...> typename To> struct extract_to; template <typename TypeList, template <typename ...> typename To> using extract_to_t = typename extract_to<TypeList, To>::type; template <typename ...Types, template <typename ...> typename To> struct extract_to<list<Types...>, To> { using type = To<Types...>; }; } // namespace type_list template <typename ...Types> struct normalized_variant { using list = type_list::list<Types...>; using distinct = type_list::distinct_t<list>; using type = std::conditional_t< type_list::size_v<distinct> == 1, type_list::get_t<0, distinct>, type_list::extract_to_t<distinct, base::variant>>; }; template <typename ...Types> using normalized_variant_t = typename normalized_variant<Types...>::type; } // namespace details } // namespace rpl �����������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/rpl/distinct_until_changed.h�����������������������������������0000664�0000000�0000000�00000002422�13262451251�0025076�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/producer.h> #include "base/optional.h" namespace rpl { namespace details { class distinct_until_changed_helper { public: template <typename Value, typename Error, typename Generator> auto operator()( producer<Value, Error, Generator> &&initial) const { return make_producer<Value, Error>([ initial = std::move(initial) ](const auto &consumer) mutable { auto previous = consumer.template make_state< base::optional<Value> >(); return std::move(initial).start( [consumer, previous](auto &&value) { if (!(*previous) || (**previous) != value) { *previous = value; consumer.put_next_forward(std::forward<decltype(value)>(value)); } }, [consumer](auto &&error) { consumer.put_error_forward(std::forward<decltype(error)>(error)); }, [consumer] { consumer.put_done(); }); }); } }; } // namespace details inline auto distinct_until_changed() -> details::distinct_until_changed_helper { return details::distinct_until_changed_helper(); } } // namespace rpl ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/rpl/event_stream.h���������������������������������������������0000664�0000000�0000000�00000011373�13262451251�0023072�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/producer.h> #include <rpl/range.h> #include <rpl/then.h> #include <rpl/range.h> #include <algorithm> #include "base/assertion.h" #include "base/index_based_iterator.h" namespace rpl { // Currently not thread-safe :( template <typename Value = empty_value> class event_stream { public: event_stream(); event_stream(event_stream &&other); event_stream &operator=(event_stream &&other); template <typename OtherValue> void fire_forward(OtherValue &&value) const; void fire(Value &&value) const { return fire_forward(std::move(value)); } void fire_copy(const Value &value) const { return fire_forward(value); } auto events() const { return make_producer<Value>([weak = make_weak()]( const auto &consumer) { if (auto strong = weak.lock()) { auto result = [weak, consumer] { if (auto strong = weak.lock()) { auto it = std::find( strong->consumers.begin(), strong->consumers.end(), consumer); if (it != strong->consumers.end()) { it->terminate(); } } }; strong->consumers.push_back(std::move(consumer)); return lifetime(std::move(result)); } return lifetime(); }); } auto events_starting_with(Value &&value) const { return single(std::move(value)) | then(events()); } auto events_starting_with_copy(const Value &value) const { return single(value) | then(events()); } ~event_stream(); private: struct Data { std::vector<consumer<Value, no_error>> consumers; int depth = 0; }; std::weak_ptr<Data> make_weak() const; mutable std::shared_ptr<Data> _data; }; template <typename Value> inline event_stream<Value>::event_stream() { } template <typename Value> inline event_stream<Value>::event_stream(event_stream &&other) : _data(details::take(other._data)) { } template <typename Value> inline event_stream<Value> &event_stream<Value>::operator=( event_stream &&other) { _data = details::take(other._data); return *this; } template <typename Value> template <typename OtherValue> inline void event_stream<Value>::fire_forward( OtherValue &&value) const { auto copy = _data; if (!copy) { return; } ++copy->depth; auto &consumers = copy->consumers; auto begin = base::index_based_begin(consumers); auto end = base::index_based_end(consumers); if (begin != end) { // Copy value for every consumer except the last. auto prev = end - 1; auto removeFrom = std::remove_if(begin, prev, [&](auto &consumer) { return !consumer.put_next_copy(value); }); // Perhaps move value for the last consumer. if (prev->put_next_forward(std::forward<OtherValue>(value))) { if (removeFrom != prev) { *removeFrom++ = std::move(*prev); } else { ++removeFrom; } } if (removeFrom != end) { // Move new consumers. auto newEnd = base::index_based_end(consumers); if (newEnd != end) { Assert(newEnd > end); while (end != newEnd) { *removeFrom++ = *end++; } } // Erase stale consumers. if (copy->depth == 1) { consumers.erase(removeFrom.base(), consumers.end()); } } } --copy->depth; } template <typename Value> inline auto event_stream<Value>::make_weak() const -> std::weak_ptr<Data> { if (!_data) { _data = std::make_shared<Data>(); } return _data; } template <typename Value> inline event_stream<Value>::~event_stream() { if (auto data = details::take(_data)) { for (auto &consumer : data->consumers) { consumer.put_done(); } } } template <typename Value> inline auto start_to_stream( event_stream<Value> &stream, lifetime &alive_while) { return start_with_next([&stream](auto &&value) { stream.fire_forward(std::forward<decltype(value)>(value)); }, alive_while); } namespace details { class start_spawning_helper { public: start_spawning_helper(lifetime &alive_while) : _lifetime(alive_while) { } template <typename Value, typename Error, typename Generator> auto operator()(producer<Value, Error, Generator> &&initial) { auto stream = _lifetime.make_state<event_stream<Value>>(); auto collected = std::vector<Value>(); { auto collecting = stream->events().start( [&collected](Value &&value) { collected.push_back(std::move(value)); }, [](const Error &error) {}, [] {}); std::move(initial) | start_to_stream(*stream, _lifetime); } return vector(std::move(collected)) | then(stream->events()); } private: lifetime &_lifetime; }; } // namespace details inline auto start_spawning(lifetime &alive_while) -> details::start_spawning_helper { return details::start_spawning_helper(alive_while); } } // namespace rpl ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/rpl/fail.h�����������������������������������������������������0000664�0000000�0000000�00000001112�13262451251�0021277�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/producer.h> namespace rpl { template <typename Value, typename Error> inline auto fail(Error &&error) { return make_producer<Value, std::decay_t<Error>>([ error = std::forward<Error>(error) ](const auto &consumer) mutable { consumer.put_error(std::move(error)); return lifetime(); }); } } // namespace rpl ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/rpl/filter.h���������������������������������������������������0000664�0000000�0000000�00000006554�13262451251�0021670�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/producer.h> #include <rpl/combine.h> #include <rpl/mappers.h> #include "base/optional.h" namespace rpl { namespace details { template <typename Predicate> class filter_helper { public: template <typename OtherPredicate> filter_helper(OtherPredicate &&predicate) : _predicate(std::forward<OtherPredicate>(predicate)) { } template < typename Value, typename Error, typename Generator, typename = std::enable_if_t< details::is_callable_v<Predicate, Value>>> auto operator()(producer<Value, Error, Generator> &&initial) { return make_producer<Value, Error>([ initial = std::move(initial), predicate = std::move(_predicate) ](const auto &consumer) mutable { return std::move(initial).start( [ consumer, predicate = std::move(predicate) ](auto &&value) { const auto &immutable = value; if (details::callable_invoke( predicate, immutable) ) { consumer.put_next_forward( std::forward<decltype(value)>(value)); } }, [consumer](auto &&error) { consumer.put_error_forward( std::forward<decltype(error)>(error)); }, [consumer] { consumer.put_done(); }); }); } private: Predicate _predicate; }; } // namespace details template <typename Predicate> inline auto filter(Predicate &&predicate) -> details::filter_helper<std::decay_t<Predicate>> { return details::filter_helper<std::decay_t<Predicate>>( std::forward<Predicate>(predicate)); } namespace details { template <typename FilterError, typename FilterGenerator> class filter_helper<producer<bool, FilterError, FilterGenerator>> { public: filter_helper( producer<bool, FilterError, FilterGenerator> &&filterer) : _filterer(std::move(filterer)) { } template <typename Value, typename Error, typename Generator> auto operator()(producer<Value, Error, Generator> &&initial) { using namespace mappers; return combine(std::move(initial), std::move(_filterer)) | filter(_2) | map(_1_of_two); } private: producer<bool, FilterError, FilterGenerator> _filterer; }; template <typename Value> inline const Value &deref_optional_helper( const base::optional<Value> &value) { return *value; } template <typename Value> inline Value &&deref_optional_helper( base::optional<Value> &&value) { return std::move(*value); } class filter_optional_helper { public: template <typename Value, typename Error, typename Generator> auto operator()(producer< base::optional<Value>, Error, Generator> &&initial) const { return make_producer<Value, Error>([ initial = std::move(initial) ](const auto &consumer) mutable { return std::move(initial).start( [consumer](auto &&value) { if (value) { consumer.put_next_forward( deref_optional_helper( std::forward<decltype(value)>( value))); } }, [consumer](auto &&error) { consumer.put_error_forward( std::forward<decltype(error)>(error)); }, [consumer] { consumer.put_done(); }); }); } }; } // namespace details inline auto filter_optional() -> details::filter_optional_helper { return details::filter_optional_helper(); } } // namespace rpl ����������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/rpl/flatten_latest.h�������������������������������������������0000664�0000000�0000000�00000003320�13262451251�0023400�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/producer.h> namespace rpl { namespace details { class flatten_latest_helper { public: template < typename Value, typename Error, typename Generator, typename MetaGenerator> auto operator()(producer< producer<Value, Error, Generator>, Error, MetaGenerator> &&initial) const { return make_producer<Value, Error>([ initial = std::move(initial) ](const auto &consumer) mutable { auto state = consumer.template make_state<State>(); return std::move(initial).start( [consumer, state](producer<Value, Error> &&inner) { state->finished = false; state->alive = lifetime(); std::move(inner).start( [consumer](auto &&value) { consumer.put_next_forward(std::forward<decltype(value)>(value)); }, [consumer](auto &&error) { consumer.put_error_forward(std::forward<decltype(error)>(error)); }, [consumer, state] { if (state->finished) { consumer.put_done(); } else { state->finished = true; } }, state->alive); }, [consumer](auto &&error) { consumer.put_error_forward(std::forward<decltype(error)>(error)); }, [consumer, state] { if (state->finished) { consumer.put_done(); } else { state->finished = true; } }); }); } private: struct State { lifetime alive; bool finished = false; }; }; } // namespace details inline auto flatten_latest() -> details::flatten_latest_helper { return details::flatten_latest_helper(); } } // namespace rpl ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/rpl/lifetime.h�������������������������������������������������0000664�0000000�0000000�00000004101�13262451251�0022163�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "base/lambda.h" #include <deque> namespace rpl { namespace details { template <typename Type> inline Type take(Type &value) { return std::exchange(value, Type{}); } } // namespace details class lifetime { public: lifetime() = default; lifetime(lifetime &&other); lifetime &operator=(lifetime &&other); template <typename Destroy, typename = decltype(std::declval<Destroy>()())> lifetime(Destroy &&destroy); explicit operator bool() const { return !_callbacks.empty(); } template <typename Destroy, typename = decltype(std::declval<Destroy>()())> void add(Destroy &&destroy); void add(lifetime &&other); void destroy(); template <typename Type, typename... Args> Type *make_state(Args&& ...args) { auto result = new Type(std::forward<Args>(args)...); add([result] { static_assert(sizeof(Type) > 0, "Can't delete unknown type."); delete result; }); return result; } ~lifetime() { destroy(); } private: std::deque<base::lambda_once<void()>> _callbacks; }; inline lifetime::lifetime(lifetime &&other) : _callbacks(details::take(other._callbacks)) { } inline lifetime &lifetime::operator=(lifetime &&other) { std::swap(_callbacks, other._callbacks); other.destroy(); return *this; } template <typename Destroy, typename> inline lifetime::lifetime(Destroy &&destroy) { _callbacks.emplace_back(std::forward<Destroy>(destroy)); } template <typename Destroy, typename> inline void lifetime::add(Destroy &&destroy) { _callbacks.push_front(destroy); } inline void lifetime::add(lifetime &&other) { auto callbacks = details::take(other._callbacks); _callbacks.insert( _callbacks.begin(), std::make_move_iterator(callbacks.begin()), std::make_move_iterator(callbacks.end())); } inline void lifetime::destroy() { for (auto &callback : details::take(_callbacks)) { callback(); } } } // namespace rpl ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/rpl/map.h������������������������������������������������������0000664�0000000�0000000�00000011761�13262451251�0021154�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/producer.h> namespace rpl { namespace details { template < typename Transform, typename NewValue, typename Error, typename Handlers> class map_transform_helper { public: map_transform_helper( Transform &&transform, const consumer<NewValue, Error, Handlers> &consumer) : _consumer(consumer) , _transform(std::move(transform)) { } template < typename OtherValue, typename = std::enable_if_t< std::is_rvalue_reference_v<OtherValue&&>>> void operator()(OtherValue &&value) const { _consumer.put_next_forward( details::callable_invoke(_transform, std::move(value))); } template < typename OtherValue, typename = decltype( std::declval<Transform>()(const_ref_val<OtherValue>()))> void operator()(const OtherValue &value) const { _consumer.put_next_forward( details::callable_invoke(_transform, value)); } private: consumer<NewValue, Error, Handlers> _consumer; Transform _transform; }; template < typename Transform, typename NewValue, typename Error, typename Handlers, typename = std::enable_if_t< std::is_rvalue_reference_v<Transform&&>>> inline map_transform_helper<Transform, NewValue, Error, Handlers> map_transform( Transform &&transform, const consumer<NewValue, Error, Handlers> &consumer) { return { std::move(transform), consumer }; } template <typename Transform> class map_helper { public: template <typename OtherTransform> map_helper(OtherTransform &&transform) : _transform(std::forward<OtherTransform>(transform)) { } template < typename Value, typename Error, typename Generator, typename NewValue = details::callable_result< Transform, Value>> auto operator()(producer<Value, Error, Generator> &&initial) { return make_producer<NewValue, Error>([ initial = std::move(initial), transform = std::move(_transform) ](const auto &consumer) mutable { return std::move(initial).start( map_transform( std::move(transform), consumer ), [consumer](auto &&error) { consumer.put_error_forward( std::forward<decltype(error)>(error)); }, [consumer] { consumer.put_done(); }); }); } private: Transform _transform; }; } // namespace details template <typename Transform> inline auto map(Transform &&transform) -> details::map_helper<std::decay_t<Transform>> { return details::map_helper<std::decay_t<Transform>>( std::forward<Transform>(transform)); } namespace details { template < typename Transform, typename Value, typename NewError, typename Handlers> class map_error_transform_helper { public: map_error_transform_helper( Transform &&transform, const consumer<Value, NewError, Handlers> &consumer) : _transform(std::move(transform)) , _consumer(consumer) { } template < typename OtherError, typename = std::enable_if_t< std::is_rvalue_reference_v<OtherError&&>>> void operator()(OtherError &&error) const { _consumer.put_error_forward( details::callable_invoke(_transform, std::move(error))); } template < typename OtherError, typename = decltype( std::declval<Transform>()(const_ref_val<OtherError>()))> void operator()(const OtherError &error) const { _consumer.put_error_forward( details::callable_invoke(_transform, error)); } private: consumer<Value, NewError, Handlers> _consumer; Transform _transform; }; template < typename Transform, typename Value, typename NewError, typename Handlers, typename = std::enable_if_t< std::is_rvalue_reference_v<Transform&&>>> inline map_error_transform_helper<Transform, Value, NewError, Handlers> map_error_transform( Transform &&transform, const consumer<Value, NewError, Handlers> &consumer) { return { std::move(transform), consumer }; } template <typename Transform> class map_error_helper { public: template <typename OtherTransform> map_error_helper(OtherTransform &&transform) : _transform(std::forward<OtherTransform>(transform)) { } template < typename Value, typename Error, typename Generator, typename NewError = details::callable_result< Transform, Error>> auto operator()(producer<Value, Error, Generator> &&initial) { return make_producer<Value, NewError>([ initial = std::move(initial), transform = std::move(_transform) ](const auto &consumer) mutable { return std::move(initial).start( [consumer](auto &&value) { consumer.put_next_forward( std::forward<decltype(value)>(value)); }, map_error_transform( std::move(transform), consumer ), [consumer] { consumer.put_done(); }); }); } private: Transform _transform; }; } // namespace details template <typename Transform> inline auto map_error(Transform &&transform) -> details::map_error_helper<std::decay_t<Transform>> { return details::map_error_helper<std::decay_t<Transform>>( std::forward<Transform>(transform)); } } // namespace rpl ���������������tdesktop-1.2.17/Telegram/SourceFiles/rpl/mappers.h��������������������������������������������������0000664�0000000�0000000�00000024412�13262451251�0022043�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace rpl { namespace details { struct base_mapper { }; template <typename Type> constexpr bool is_mapper_v = std::is_base_of_v< base_mapper, std::decay_t<Type>>; template <std::size_t Index> struct argument_mapper : base_mapper { template < typename Arg, typename ...Args, typename = std::enable_if_t<(sizeof...(Args) >= Index)>> static constexpr decltype(auto) call(Arg &&arg, Args &&...args) { return argument_mapper<Index - 1>::call( std::forward<Args>(args)...); } template < typename ...Args, typename = std::enable_if_t<(sizeof...(Args) > Index)>> constexpr auto operator()(Args &&...args) const { return call(std::forward<Args>(args)...); } }; template <> struct argument_mapper<0> : base_mapper { template < typename Arg, typename ...Args> static constexpr decltype(auto) call(Arg &&arg, Args &&...args) { return std::forward<Arg>(arg); } template < typename Arg, typename ...Args> constexpr auto operator()(Arg &&arg, Args &&...args) const { return std::forward<Arg>(arg); } }; template <typename Type> class value_mapper : public base_mapper { public: template <typename OtherType> constexpr value_mapper(OtherType &&value) : _value(std::forward<OtherType>(value)) { } template <typename ...Args> constexpr auto operator()(Args &&...args) const { return _value; } private: Type _value; }; template <typename Type> struct wrap_mapper { using type = std::conditional_t< is_mapper_v<Type>, Type, value_mapper<Type>>; }; template <typename Type> using wrap_mapper_t = typename wrap_mapper<Type>::type; template <typename Type, typename Operator> class unary_operator_mapper : public base_mapper { using TypeWrapper = wrap_mapper_t<std::decay_t<Type>>; public: template <typename OtherType> constexpr unary_operator_mapper(OtherType &&value) : _value(std::forward<OtherType>(value)) { } template < typename ...Args, typename Result = decltype((Operator{})( std::declval<TypeWrapper>()(std::declval<Args>()...)))> constexpr std::decay_t<Result> operator()(Args &&...args) const { return (Operator{})( _value(std::forward<Args>(args)...)); } private: TypeWrapper _value; }; template <typename Left, typename Right, typename Operator> class binary_operator_mapper : public base_mapper { using LeftWrapper = wrap_mapper_t<std::decay_t<Left>>; using RightWrapper = wrap_mapper_t<std::decay_t<Right>>; public: template <typename OtherLeft, typename OtherRight> constexpr binary_operator_mapper(OtherLeft &&left, OtherRight &&right) : _left(std::forward<OtherLeft>(left)) , _right(std::forward<OtherRight>(right)) { } template < typename ...Args, typename Result = decltype((Operator{})( std::declval<LeftWrapper>()(std::declval<Args>()...), std::declval<RightWrapper>()(std::declval<Args>()...)))> constexpr std::decay_t<Result> operator()(Args &&...args) const { return (Operator{})( _left(std::forward<Args>(args)...), _right(std::forward<Args>(args)...)); } private: LeftWrapper _left; RightWrapper _right; }; template < typename Left, typename Right, typename = std::enable_if_t< is_mapper_v<Left> || is_mapper_v<Right> >> inline auto operator+(Left &&left, Right &&right) { return binary_operator_mapper< Left, Right, std::plus<>>( std::forward<Left>(left), std::forward<Right>(right)); } template < typename Left, typename Right, typename = std::enable_if_t< is_mapper_v<Left> || is_mapper_v<Right> >> inline auto operator-(Left &&left, Right &&right) { return binary_operator_mapper< Left, Right, std::minus<>>( std::forward<Left>(left), std::forward<Right>(right)); } template < typename Left, typename Right, typename = std::enable_if_t< is_mapper_v<Left> || is_mapper_v<Right> >> inline auto operator*(Left &&left, Right &&right) { return binary_operator_mapper< Left, Right, std::multiplies<>>( std::forward<Left>(left), std::forward<Right>(right)); } template < typename Left, typename Right, typename = std::enable_if_t< is_mapper_v<Left> || is_mapper_v<Right> >> inline auto operator/(Left &&left, Right &&right) { return binary_operator_mapper< Left, Right, std::divides<>>( std::forward<Left>(left), std::forward<Right>(right)); } template < typename Left, typename Right, typename = std::enable_if_t< is_mapper_v<Left> || is_mapper_v<Right> >> inline auto operator%(Left &&left, Right &&right) { return binary_operator_mapper< Left, Right, std::modulus<>>( std::forward<Left>(left), std::forward<Right>(right)); } template < typename Type, typename = std::enable_if_t< is_mapper_v<Type> >> inline auto operator-(Type &&value) { return unary_operator_mapper< Type, std::negate<>>( std::forward<Type>(value)); } template < typename Left, typename Right, typename = std::enable_if_t< is_mapper_v<Left> || is_mapper_v<Right> >> inline auto operator<(Left &&left, Right &&right) { return binary_operator_mapper< Left, Right, std::less<>>( std::forward<Left>(left), std::forward<Right>(right)); } template < typename Left, typename Right, typename = std::enable_if_t< is_mapper_v<Left> || is_mapper_v<Right> >> inline auto operator<=(Left &&left, Right &&right) { return binary_operator_mapper< Left, Right, std::less_equal<>>( std::forward<Left>(left), std::forward<Right>(right)); } template < typename Left, typename Right, typename = std::enable_if_t< is_mapper_v<Left> || is_mapper_v<Right> >> inline auto operator>(Left &&left, Right &&right) { return binary_operator_mapper< Left, Right, std::greater<>>( std::forward<Left>(left), std::forward<Right>(right)); } template < typename Left, typename Right, typename = std::enable_if_t< is_mapper_v<Left> || is_mapper_v<Right> >> inline auto operator>=(Left &&left, Right &&right) { return binary_operator_mapper< Left, Right, std::greater_equal<>>( std::forward<Left>(left), std::forward<Right>(right)); } template < typename Left, typename Right, typename = std::enable_if_t< is_mapper_v<Left> || is_mapper_v<Right> >> inline auto operator==(Left &&left, Right &&right) { return binary_operator_mapper< Left, Right, std::equal_to<>>( std::forward<Left>(left), std::forward<Right>(right)); } template < typename Left, typename Right, typename = std::enable_if_t< is_mapper_v<Left> || is_mapper_v<Right> >> inline auto operator!=(Left &&left, Right &&right) { return binary_operator_mapper< Left, Right, std::not_equal_to<>>( std::forward<Left>(left), std::forward<Right>(right)); } template < typename Left, typename Right, typename = std::enable_if_t< is_mapper_v<Left> || is_mapper_v<Right> >> inline auto operator&&(Left &&left, Right &&right) { return binary_operator_mapper< Left, Right, std::logical_and<>>( std::forward<Left>(left), std::forward<Right>(right)); } template < typename Left, typename Right, typename = std::enable_if_t< is_mapper_v<Left> || is_mapper_v<Right> >> inline auto operator||(Left &&left, Right &&right) { return binary_operator_mapper< Left, Right, std::logical_or<>>( std::forward<Left>(left), std::forward<Right>(right)); } template < typename Type, typename = std::enable_if_t< is_mapper_v<Type> >> inline auto operator!(Type &&value) { return unary_operator_mapper< Type, std::logical_not<>>( std::forward<Type>(value)); } template < typename Left, typename Right, typename = std::enable_if_t< is_mapper_v<Left> || is_mapper_v<Right> >> inline auto operator&(Left &&left, Right &&right) { return binary_operator_mapper< Left, Right, std::bit_and<>>( std::forward<Left>(left), std::forward<Right>(right)); } template < typename Left, typename Right, typename = std::enable_if_t< is_mapper_v<Left> || is_mapper_v<Right> >> inline auto operator|(Left &&left, Right &&right) { return binary_operator_mapper< Left, Right, std::bit_or<>>( std::forward<Left>(left), std::forward<Right>(right)); } template < typename Left, typename Right, typename = std::enable_if_t< is_mapper_v<Left> || is_mapper_v<Right> >> inline auto operator^(Left &&left, Right &&right) { return binary_operator_mapper< Left, Right, std::bit_xor<>>( std::forward<Left>(left), std::forward<Right>(right)); } template < typename Type, typename = std::enable_if_t< is_mapper_v<Type> >> inline auto operator~(Type &&value) { return unary_operator_mapper< Type, std::bit_not<>>( std::forward<Type>(value)); } template <typename ...Mappers> class tuple_mapper { template <typename ...Args> using tuple_result = std::tuple<decltype( std::declval<wrap_mapper_t<std::decay_t<Mappers>>>()( std::declval<Args>()...))...>; public: template <typename ...OtherMappers> tuple_mapper(OtherMappers &&...mappers) : _mappers( std::forward<OtherMappers>(mappers)...) { } template <typename ...Args> constexpr tuple_result<Args...> operator()( Args &&...args) const { constexpr auto kArity = sizeof...(Mappers); return call_helper( std::make_index_sequence<kArity>(), std::forward<Args>(args)...); } private: template <typename ...Args, std::size_t ...I> inline tuple_result<Args...> call_helper( std::index_sequence<I...>, Args &&...args) const { return std::make_tuple( std::get<I>(_mappers)(std::forward<Args>(args)...)...); } std::tuple<wrap_mapper_t<std::decay_t<Mappers>>...> _mappers; }; template <typename ...Args> tuple_mapper<Args...> tuple(Args &&...args) { return tuple_mapper<Args...>(std::forward<Args>(args)...); } } // namespace details namespace mappers { constexpr const details::argument_mapper<0> _1; constexpr const details::argument_mapper<1> _2; constexpr const details::argument_mapper<2> _3; constexpr const details::argument_mapper<3> _4; constexpr const details::argument_mapper<4> _5; constexpr const details::argument_mapper<5> _6; constexpr const details::argument_mapper<6> _7; constexpr const details::argument_mapper<7> _8; constexpr const details::argument_mapper<8> _9; constexpr const details::argument_mapper<9> _10; constexpr const auto _1_of_two = ((void)_2, _1); } // namespace mappers } // namespace rpl ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/rpl/merge.h����������������������������������������������������0000664�0000000�0000000�00000007025�13262451251�0021474�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/producer.h> namespace rpl { namespace details { struct merge_state { merge_state(int working) : working(working) { } int working = 0; }; template <size_t Index, typename consumer_type> class merge_subscribe_one { public: merge_subscribe_one( const consumer_type &consumer, merge_state *state) : _consumer(consumer) , _state(state) { } template <typename Value, typename Error, typename Generator> void subscribe(producer<Value, Error, Generator> &&producer) { _consumer.add_lifetime(std::move(producer).start( [consumer = _consumer](auto &&value) { consumer.put_next_forward( std::forward<decltype(value)>(value)); }, [consumer = _consumer](auto &&error) { consumer.put_error_forward( std::forward<decltype(error)>(error)); }, [consumer = _consumer, state = _state] { if (!--state->working) { consumer.put_done(); } })); } private: const consumer_type &_consumer; merge_state *_state = nullptr; }; template < typename consumer_type, typename Value, typename Error, typename ...Generators, std::size_t ...I> inline void merge_subscribe( const consumer_type &consumer, merge_state *state, std::index_sequence<I...>, std::tuple<producer<Value, Error, Generators>...> &&saved) { auto consume = { ( details::merge_subscribe_one<I, consumer_type>( consumer, state ).subscribe(std::get<I>(std::move(saved))), 0)... }; (void)consume; } template <typename ...Producers> class merge_implementation_helper; template <typename ...Producers> merge_implementation_helper<std::decay_t<Producers>...> make_merge_implementation_helper(Producers &&...producers) { return merge_implementation_helper<std::decay_t<Producers>...>( std::forward<Producers>(producers)...); } template < typename Value, typename Error, typename ...Generators> class merge_implementation_helper<producer<Value, Error, Generators>...> { public: merge_implementation_helper( producer<Value, Error, Generators> &&...producers) : _saved(std::make_tuple(std::move(producers)...)) { } template <typename Handlers> lifetime operator()(const consumer<Value, Error, Handlers> &consumer) { auto state = consumer.template make_state< details::merge_state>(sizeof...(Generators)); constexpr auto kArity = sizeof...(Generators); details::merge_subscribe( consumer, state, std::make_index_sequence<kArity>(), std::move(_saved)); return lifetime(); } private: std::tuple<producer<Value, Error, Generators>...> _saved; }; template < typename Value, typename Error, typename ...Generators> inline auto merge_implementation( producer<Value, Error, Generators> &&...producers) { return make_producer<Value, Error>( make_merge_implementation_helper(std::move(producers)...)); } template <typename ...Args> struct merge_producers : std::false_type { }; template <typename ...Args> constexpr bool merge_producers_v = merge_producers<Args...>::value; template < typename Value, typename Error, typename ...Generators> struct merge_producers< producer<Value, Error, Generators>...> : std::true_type { }; } // namespace details template < typename ...Args, typename = std::enable_if_t< details::merge_producers_v<Args...>>> inline decltype(auto) merge(Args &&...args) { return details::merge_implementation(std::move(args)...); } } // namespace rpl �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/rpl/never.h����������������������������������������������������0000664�0000000�0000000�00000000762�13262451251�0021515�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/producer.h> namespace rpl { template <typename Value = empty_value, typename Error = no_error> inline auto never() { return make_producer<Value, Error>([](const auto &consumer) { return lifetime(); }); } } // namespace rpl ��������������tdesktop-1.2.17/Telegram/SourceFiles/rpl/operators_tests.cpp����������������������������������������0000664�0000000�0000000�00000025207�13262451251�0024172�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "catch.hpp" #include <rpl/rpl.h> #include <string> using namespace rpl; class OnDestructor { public: OnDestructor(base::lambda_once<void()> callback) : _callback(std::move(callback)) { } ~OnDestructor() { if (_callback) { _callback(); } } private: base::lambda_once<void()> _callback; }; class InvokeCounter { public: InvokeCounter( const std::shared_ptr<int> ©Counter, const std::shared_ptr<int> &moveCounter) : _copyCounter(copyCounter) , _moveCounter(moveCounter) { } InvokeCounter(const InvokeCounter &other) : _copyCounter(other._copyCounter) , _moveCounter(other._moveCounter) { if (_copyCounter) { ++*_copyCounter; } } InvokeCounter(InvokeCounter &&other) : _copyCounter(details::take(other._copyCounter)) , _moveCounter(details::take(other._moveCounter)) { if (_moveCounter) { ++*_moveCounter; } } InvokeCounter &operator=(const InvokeCounter &other) { _copyCounter = other._copyCounter; _moveCounter = other._moveCounter; if (_copyCounter) { ++*_copyCounter; } return *this; } InvokeCounter &operator=(InvokeCounter &&other) { _copyCounter = details::take(other._copyCounter); _moveCounter = details::take(other._moveCounter); if (_moveCounter) { ++*_moveCounter; } return *this; } private: std::shared_ptr<int> _copyCounter; std::shared_ptr<int> _moveCounter; }; TEST_CASE("basic operators tests", "[rpl::operators]") { SECTION("single test") { auto sum = std::make_shared<int>(0); auto doneGenerated = std::make_shared<bool>(false); auto destroyed = std::make_shared<bool>(false); auto copyCount = std::make_shared<int>(0); auto moveCount = std::make_shared<int>(0); { InvokeCounter counter(copyCount, moveCount); auto destroyCalled = std::make_shared<OnDestructor>([=] { *destroyed = true; }); rpl::lifetime lifetime; single(std::move(counter)) | start_with_next_error_done([=](InvokeCounter&&) { (void)destroyCalled; ++*sum; }, [=](no_error) { (void)destroyCalled; }, [=] { (void)destroyCalled; *doneGenerated = true; }, lifetime); } REQUIRE(*sum == 1); REQUIRE(*doneGenerated); REQUIRE(*destroyed); REQUIRE(*copyCount == 0); } SECTION("then test") { auto sum = std::make_shared<int>(0); auto doneGenerated = std::make_shared<bool>(false); auto destroyed = std::make_shared<bool>(false); auto copyCount = std::make_shared<int>(0); auto moveCount = std::make_shared<int>(0); { auto testing = complete<InvokeCounter>() | type_erased(); for (auto i = 0; i != 5; ++i) { InvokeCounter counter(copyCount, moveCount); testing = std::move(testing) | then(single(std::move(counter))); } auto destroyCalled = std::make_shared<OnDestructor>([=] { *destroyed = true; }); rpl::lifetime lifetime; std::move(testing) | then(complete<InvokeCounter>()) | start_with_next_error_done([=](InvokeCounter&&) { (void)destroyCalled; ++*sum; }, [=](no_error) { (void)destroyCalled; }, [=] { (void)destroyCalled; *doneGenerated = true; }, lifetime); } REQUIRE(*sum == 5); REQUIRE(*doneGenerated); REQUIRE(*destroyed); REQUIRE(*copyCount == 0); } SECTION("map test") { auto sum = std::make_shared<std::string>(""); { rpl::lifetime lifetime; single(1) | then(single(2)) | then(single(3)) | then(single(4)) | then(single(5)) | map([](int value) { return std::to_string(value); }) | start_with_next([=](std::string &&value) { *sum += std::move(value) + ' '; }, lifetime); } REQUIRE(*sum == "1 2 3 4 5 "); } SECTION("deferred test") { auto launched = std::make_shared<int>(0); auto checked = std::make_shared<int>(0); { rpl::lifetime lifetime; auto make_next = [=] { return deferred([=] { return single(++*launched); }); }; make_next() | then(make_next()) | then(make_next()) | then(make_next()) | then(make_next()) | start_with_next([=](int value) { REQUIRE(++*checked == *launched); REQUIRE(*checked == value); }, lifetime); REQUIRE(*launched == 5); } } SECTION("filter test") { auto sum = std::make_shared<std::string>(""); { rpl::lifetime lifetime; single(1) | then(single(1)) | then(single(2)) | then(single(2)) | then(single(3)) | filter([](int value) { return value != 2; }) | map([](int value) { return std::to_string(value); }) | start_with_next([=](std::string &&value) { *sum += std::move(value) + ' '; }, lifetime); } REQUIRE(*sum == "1 1 3 "); } SECTION("filter tuple test") { auto sum = std::make_shared<std::string>(""); { auto lifetime = single(std::make_tuple(1, 2)) | then(single(std::make_tuple(1, 2))) | then(single(std::make_tuple(2, 3))) | then(single(std::make_tuple(2, 3))) | then(single(std::make_tuple(3, 4))) | filter([](auto first, auto second) { return first != 2; }) | map([](auto first, auto second) { return std::to_string(second); }) | start_with_next([=](std::string &&value) { *sum += std::move(value) + ' '; }); } REQUIRE(*sum == "2 2 4 "); } SECTION("distinct_until_changed test") { auto sum = std::make_shared<std::string>(""); { rpl::lifetime lifetime; single(1) | then(single(1)) | then(single(2)) | then(single(2)) | then(single(3)) | distinct_until_changed() | map([](int value) { return std::to_string(value); }) | start_with_next([=](std::string &&value) { *sum += std::move(value) + ' '; }, lifetime); } REQUIRE(*sum == "1 2 3 "); } SECTION("flatten_latest test") { auto sum = std::make_shared<std::string>(""); { rpl::lifetime lifetime; { event_stream<int> stream; single(single(1) | then(single(2))) | then(single(single(3) | then(single(4)))) | then(single(single(5) | then(stream.events()))) | flatten_latest() | map([](int value) { return std::to_string(value); }) | start_with_next_done([=](std::string &&value) { *sum += std::move(value) + ' '; }, [=] { *sum += "done "; }, lifetime); stream.fire(6); } single(single(1)) | then(single(single(2) | then(single(3)))) | then(single(single(4) | then(single(5)) | then(single(6)))) | flatten_latest() | map([](int value) { return std::to_string(value); }) | start_with_next([=](std::string &&value) { *sum += std::move(value) + ' '; }, lifetime); } REQUIRE(*sum == "1 2 3 4 5 6 done 1 2 3 4 5 6 "); } SECTION("combine vector test") { auto sum = std::make_shared<std::string>(""); { rpl::lifetime lifetime; event_stream<bool> a; event_stream<bool> b; event_stream<bool> c; std::vector<producer<bool>> v; v.push_back(a.events()); v.push_back(b.events()); v.push_back(c.events()); combine(std::move(v), [](const auto &values) { return values[0] && values[1] && !values[2]; }) | start_with_next([=](bool value) { *sum += std::to_string(value ? 1 : 0); }, lifetime); a.fire(true); b.fire(true); c.fire(false); a.fire(false); b.fire(true); a.fire(true); c.fire(true); } REQUIRE(*sum == "10010"); } SECTION("combine test") { auto sum = std::make_shared<std::string>(""); { rpl::lifetime lifetime; event_stream<int> a; event_stream<short> b; event_stream<char> c; combine( a.events(), b.events(), c.events(), [](long a, long b, long c) { return a; }) | start_with_next([=](int value) { *sum += std::to_string(value); }, lifetime); combine( a.events(), b.events(), c.events(), [](auto &&value) { return std::get<1>(value); }) | start_with_next([=](int value) { *sum += std::to_string(value); }, lifetime); combine(a.events(), b.events(), c.events()) | map([](auto &&value) { return std::make_tuple( std::to_string(std::get<0>(value)), std::to_string(std::get<1>(value)), std::to_string(std::get<2>(value))); }) | start_with_next([=](auto &&value) { *sum += std::get<0>(value) + ' ' + std::get<1>(value) + ' ' + std::get<2>(value) + ' '; }, lifetime); a.fire(1); b.fire(2); c.fire(3); a.fire(4); b.fire(5); c.fire(6); } REQUIRE(*sum == "121 2 3 424 2 3 454 5 3 454 5 6 "); } SECTION("mappers test") { auto sum = std::make_shared<std::string>(""); { rpl::lifetime lifetime; event_stream<int> a; event_stream<short> b; event_stream<char> c; using namespace mappers; combine( a.events(), b.events(), c.events(), _1 + _2 + _3 + 10) | start_with_next([=](int value) { *sum += std::to_string(value); }, lifetime); a.fire(1); b.fire(2); c.fire(3); a.fire(4); b.fire(5); c.fire(6); } REQUIRE(*sum == "16192225"); } SECTION("after_next test") { auto sum = std::make_shared<std::string>(""); { rpl::lifetime lifetime; ints(3) | after_next([=](int value) { *sum += std::to_string(-value-1); }) | start_with_next([=](int value) { *sum += std::to_string(value); }, lifetime); } REQUIRE(*sum == "0-11-22-3"); } SECTION("combine_previous test") { auto sum = std::make_shared<std::string>(""); { rpl::lifetime lifetime; event_stream<int> a; a.events( ) | combine_previous( ) | start_with_next([=](int previous, int next) { *sum += std::to_string(previous) + ' '; *sum += std::to_string(next) + ' '; }, lifetime); a.events( ) | combine_previous( 5 ) | start_with_next([=](int previous, int next) { *sum += std::to_string(10 + previous) + ' '; *sum += std::to_string(next) + ' '; }, lifetime); a.fire(1); a.fire(2); a.fire(3); a.fire(4); } REQUIRE(*sum == "15 1 1 2 11 2 2 3 12 3 3 4 13 4 "); } SECTION("take test") { auto sum = std::make_shared<std::string>(""); { rpl::lifetime lifetime; ints(10) | take(3) | start_with_next_done([=](int value) { *sum += std::to_string(value); }, [=] { *sum += "done"; }, lifetime); } { rpl::lifetime lifetime; ints(3) | take(3) | start_with_next_done([=](int value) { *sum += std::to_string(value); }, [=] { *sum += "done"; }, lifetime); } { rpl::lifetime lifetime; ints(3) | take(10) | start_with_next_done([=](int value) { *sum += std::to_string(value); }, [=] { *sum += "done"; }, lifetime); } REQUIRE(*sum == "012done012done012done"); } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/rpl/producer.h�������������������������������������������������0000664�0000000�0000000�00000056412�13262451251�0022224�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "base/lambda.h" #include <rpl/consumer.h> #include <rpl/lifetime.h> #include <rpl/details/superset_type.h> #include <rpl/details/callable.h> #if defined _DEBUG #define RPL_PRODUCER_TYPE_ERASED_ALWAYS #endif // _DEBUG namespace rpl { namespace details { template <typename Value, typename Error> const consumer<Value, Error> &const_ref_consumer(); template <typename Lambda> class mutable_lambda_wrap { public: mutable_lambda_wrap(Lambda &&lambda) : _lambda(std::move(lambda)) { } mutable_lambda_wrap(const mutable_lambda_wrap &other) = default; mutable_lambda_wrap(mutable_lambda_wrap &&other) = default; mutable_lambda_wrap &operator=( const mutable_lambda_wrap &other) = default; mutable_lambda_wrap &operator=( mutable_lambda_wrap &&other) = default; template <typename... Args> auto operator()(Args&&... args) const { return (const_cast<mutable_lambda_wrap*>(this)->_lambda)( std::forward<Args>(args)...); } private: Lambda _lambda; }; // Type-erased copyable mutable lambda using base::lambda. template <typename Value, typename Error> class type_erased_generator final { public: template <typename Handlers> using consumer_type = consumer<Value, Error, Handlers>; using value_type = Value; using error_type = Error; type_erased_generator( const type_erased_generator &other) = default; type_erased_generator( type_erased_generator &&other) = default; type_erased_generator &operator=( const type_erased_generator &other) = default; type_erased_generator &operator=( type_erased_generator &&other) = default; template < typename Generator, typename = std::enable_if_t< std::is_convertible_v< decltype(std::declval<Generator>()( const_ref_consumer<Value, Error>())), lifetime> && !std::is_same_v< std::decay_t<Generator>, type_erased_generator>>> type_erased_generator(Generator other) : _implementation( mutable_lambda_wrap<Generator>(std::move(other))) { } template < typename Generator, typename = std::enable_if_t< std::is_convertible_v< decltype(std::declval<Generator>()( const_ref_consumer<Value, Error>())), lifetime> && !std::is_same_v< std::decay_t<Generator>, type_erased_generator>>> type_erased_generator &operator=(Generator other) { _implementation = mutable_lambda_wrap<Generator>( std::move(other)); return *this; } template <typename Handlers> lifetime operator()(const consumer_type<Handlers> &consumer) { return _implementation(consumer); } private: base::lambda<lifetime(const consumer_type<type_erased_handlers<Value, Error>> &)> _implementation; }; } // namespace details template < typename Value = empty_value, typename Error = no_error, typename Generator = details::type_erased_generator< Value, Error>> class producer; template < typename Value1, typename Value2, typename Error1, typename Error2, typename Generator> struct superset_type< producer<Value1, Error1, Generator>, producer<Value2, Error2, Generator>> { using type = producer< superset_type_t<Value1, Value2>, superset_type_t<Error1, Error2>, Generator>; }; template < typename Value, typename Error, typename Generator1, typename Generator2> struct superset_type< producer<Value, Error, Generator1>, producer<Value, Error, Generator2>> { using type = producer<Value, Error>; }; template < typename Value, typename Error, typename Generator> struct superset_type< producer<Value, Error, Generator>, producer<Value, Error, Generator>> { using type = producer<Value, Error, Generator>; }; namespace details { template <typename Value, typename Error, typename Generator> class producer_base { public: template <typename Handlers> using consumer_type = consumer<Value, Error, Handlers>; using value_type = Value; using error_type = Error; template < typename OtherGenerator, typename = std::enable_if_t< std::is_constructible_v<Generator, OtherGenerator&&>>> producer_base(OtherGenerator &&generator); producer_base(const producer_base &other) = default; producer_base(producer_base &&other) = default; producer_base &operator=(const producer_base &other) = default; producer_base &operator=(producer_base &&other) = default; template < typename OnNext, typename OnError, typename OnDone, typename = std::enable_if_t< is_callable_v<OnNext, Value> && is_callable_v<OnError, Error> && is_callable_v<OnDone>>> void start( OnNext &&next, OnError &&error, OnDone &&done, lifetime &alive_while) &&; template < typename OnNext, typename OnError, typename OnDone, typename = std::enable_if_t< is_callable_v<OnNext, Value> && is_callable_v<OnError, Error> && is_callable_v<OnDone>>> [[nodiscard]] lifetime start( OnNext &&next, OnError &&error, OnDone &&done) &&; template < typename OnNext, typename OnError, typename OnDone, typename = std::enable_if_t< is_callable_v<OnNext, Value> && is_callable_v<OnError, Error> && is_callable_v<OnDone>>> void start_copy( OnNext &&next, OnError &&error, OnDone &&done, lifetime &alive_while) const &; template < typename OnNext, typename OnError, typename OnDone, typename = std::enable_if_t< is_callable_v<OnNext, Value> && is_callable_v<OnError, Error> && is_callable_v<OnDone>>> [[nodiscard]] lifetime start_copy( OnNext &&next, OnError &&error, OnDone &&done) const &; template <typename Handlers> void start_existing( const consumer_type<Handlers> &consumer, lifetime &alive_while) &&; template <typename Handlers> [[nodiscard]] lifetime start_existing( const consumer_type<Handlers> &consumer) &&; private: Generator _generator; template < typename OtherValue, typename OtherError, typename OtherGenerator> friend class ::rpl::producer; }; template <typename Value, typename Error, typename Generator> template <typename OtherGenerator, typename> inline producer_base<Value, Error, Generator>::producer_base( OtherGenerator &&generator) : _generator(std::forward<OtherGenerator>(generator)) { } template <typename Value, typename Error, typename Generator> template < typename OnNext, typename OnError, typename OnDone, typename> inline void producer_base<Value, Error, Generator>::start( OnNext &&next, OnError &&error, OnDone &&done, lifetime &alive_while) && { return std::move(*this).start_existing( make_consumer<Value, Error>( std::forward<OnNext>(next), std::forward<OnError>(error), std::forward<OnDone>(done)), alive_while); } template <typename Value, typename Error, typename Generator> template < typename OnNext, typename OnError, typename OnDone, typename> [[nodiscard]] inline lifetime producer_base<Value, Error, Generator>::start( OnNext &&next, OnError &&error, OnDone &&done) && { auto result = lifetime(); std::move(*this).start_existing( make_consumer<Value, Error>( std::forward<OnNext>(next), std::forward<OnError>(error), std::forward<OnDone>(done)), result); return result; } template <typename Value, typename Error, typename Generator> template < typename OnNext, typename OnError, typename OnDone, typename> inline void producer_base<Value, Error, Generator>::start_copy( OnNext &&next, OnError &&error, OnDone &&done, lifetime &alive_while) const & { auto copy = *this; return std::move(copy).start_existing( make_consumer<Value, Error>( std::forward<OnNext>(next), std::forward<OnError>(error), std::forward<OnDone>(done)), alive_while); } template <typename Value, typename Error, typename Generator> template < typename OnNext, typename OnError, typename OnDone, typename> [[nodiscard]] inline lifetime producer_base<Value, Error, Generator>::start_copy( OnNext &&next, OnError &&error, OnDone &&done) const & { auto result = lifetime(); auto copy = *this; std::move(copy).start_existing( make_consumer<Value, Error>( std::forward<OnNext>(next), std::forward<OnError>(error), std::forward<OnDone>(done)), result); return result; } template <typename Value, typename Error, typename Generator> template <typename Handlers> inline void producer_base<Value, Error, Generator>::start_existing( const consumer_type<Handlers> &consumer, lifetime &alive_while) && { alive_while.add(consumer.terminator()); consumer.add_lifetime(std::move(_generator)(consumer)); } template <typename Value, typename Error, typename Generator> template <typename Handlers> [[nodiscard]] inline lifetime producer_base<Value, Error, Generator>::start_existing( const consumer_type<Handlers> &consumer) && { auto result = lifetime(); std::move(*this).start_existing(consumer, result); return result; } template <typename Value, typename Error> using producer_base_type_erased = producer_base< Value, Error, type_erased_generator<Value, Error>>; } // namespace details template <typename Value, typename Error, typename Generator> class producer final : public details::producer_base<Value, Error, Generator> { using parent_type = details::producer_base< Value, Error, Generator>; public: using parent_type::parent_type; }; template <typename Value, typename Error> class producer< Value, Error, details::type_erased_generator<Value, Error>> final : public details::producer_base_type_erased<Value, Error> { using parent_type = details::producer_base_type_erased< Value, Error>; public: using parent_type::parent_type;; producer(const producer &other) = default; producer(producer &&other) = default; producer &operator=(const producer &other) = default; producer &operator=(producer &&other) = default; template < typename Generic, typename = std::enable_if_t<!std::is_same_v< Generic, details::type_erased_generator<Value, Error>>>> producer(const details::producer_base<Value, Error, Generic> &other) : parent_type(other._generator) { } template < typename Generic, typename = std::enable_if_t<!std::is_same_v< Generic, details::type_erased_generator<Value, Error>>>> producer(details::producer_base<Value, Error, Generic> &&other) : parent_type(std::move(other._generator)) { } template < typename Generic, typename = std::enable_if_t<!std::is_same_v< Generic, details::type_erased_generator<Value, Error>>>> producer &operator=( const details::producer_base<Value, Error, Generic> &other) { this->_generator = other._generator; return *this; } template < typename Generic, typename = std::enable_if_t<!std::is_same_v< Generic, details::type_erased_generator<Value, Error>>>> producer &operator=( details::producer_base<Value, Error, Generic> &&other) { this->_generator = std::move(other._generator); return *this; } }; template < typename Value = empty_value, typename Error = no_error, typename Generator, typename = std::enable_if_t< std::is_convertible_v< decltype(std::declval<Generator>()( details::const_ref_consumer<Value, Error>())), lifetime>>> inline auto make_producer(Generator &&generator) #ifdef RPL_PRODUCER_TYPE_ERASED_ALWAYS -> producer<Value, Error> { #else // RPL_CONSUMER_TYPE_ERASED_ALWAYS -> producer<Value, Error, std::decay_t<Generator>> { #endif // !RPL_CONSUMER_TYPE_ERASED_ALWAYS return std::forward<Generator>(generator); } template <typename Value, typename Error, typename Generator> inline producer<Value, Error, Generator> duplicate( const producer<Value, Error, Generator> &value) { return value; } template < typename Value, typename Error, typename Generator, typename Method, typename = decltype(std::declval<Method>()( std::declval<producer<Value, Error, Generator>>()))> inline auto operator|( producer<Value, Error, Generator> &&value, Method &&method) { return std::forward<Method>(method)(std::move(value)); } namespace details { struct with_none { }; struct lifetime_with_none { lifetime &alive_while; }; template <typename OnNext> struct with_next { OnNext next; }; template <typename OnNext> struct lifetime_with_next { lifetime &alive_while; OnNext next; }; template <typename OnError> struct with_error { OnError error; }; template <typename OnError> struct lifetime_with_error { lifetime &alive_while; OnError error; }; template <typename OnDone> struct with_done { OnDone done; }; template <typename OnDone> struct lifetime_with_done { lifetime &alive_while; OnDone done; }; template <typename OnNext, typename OnError> struct with_next_error { OnNext next; OnError error; }; template <typename OnNext, typename OnError> struct lifetime_with_next_error { lifetime &alive_while; OnNext next; OnError error; }; template <typename OnError, typename OnDone> struct with_error_done { OnError error; OnDone done; }; template <typename OnError, typename OnDone> struct lifetime_with_error_done { lifetime &alive_while; OnError error; OnDone done; }; template <typename OnNext, typename OnDone> struct with_next_done { OnNext next; OnDone done; }; template <typename OnNext, typename OnDone> struct lifetime_with_next_done { lifetime &alive_while; OnNext next; OnDone done; }; template <typename OnNext, typename OnError, typename OnDone> struct with_next_error_done { OnNext next; OnError error; OnDone done; }; template <typename OnNext, typename OnError, typename OnDone> struct lifetime_with_next_error_done { lifetime &alive_while; OnNext next; OnError error; OnDone done; }; } // namespace details inline auto start() -> details::with_none { return {}; } inline auto start(lifetime &alive_while) -> details::lifetime_with_none { return { alive_while }; } template <typename OnNext> inline auto start_with_next(OnNext &&next) -> details::with_next<std::decay_t<OnNext>> { return { std::forward<OnNext>(next) }; } template <typename OnNext> inline auto start_with_next(OnNext &&next, lifetime &alive_while) -> details::lifetime_with_next<std::decay_t<OnNext>> { return { alive_while, std::forward<OnNext>(next) }; } template <typename OnError> inline auto start_with_error(OnError &&error) -> details::with_error<std::decay_t<OnError>> { return { std::forward<OnError>(error) }; } template <typename OnError> inline auto start_with_error(OnError &&error, lifetime &alive_while) -> details::lifetime_with_error<std::decay_t<OnError>> { return { alive_while, std::forward<OnError>(error) }; } template <typename OnDone> inline auto start_with_done(OnDone &&done) -> details::with_done<std::decay_t<OnDone>> { return { std::forward<OnDone>(done) }; } template <typename OnDone> inline auto start_with_done(OnDone &&done, lifetime &alive_while) -> details::lifetime_with_done<std::decay_t<OnDone>> { return { alive_while, std::forward<OnDone>(done) }; } template <typename OnNext, typename OnError> inline auto start_with_next_error( OnNext &&next, OnError &&error) -> details::with_next_error< std::decay_t<OnNext>, std::decay_t<OnError>> { return { std::forward<OnNext>(next), std::forward<OnError>(error) }; } template <typename OnNext, typename OnError> inline auto start_with_next_error( OnNext &&next, OnError &&error, lifetime &alive_while) -> details::lifetime_with_next_error< std::decay_t<OnNext>, std::decay_t<OnError>> { return { alive_while, std::forward<OnNext>(next), std::forward<OnError>(error) }; } template <typename OnError, typename OnDone> inline auto start_with_error_done( OnError &&error, OnDone &&done) -> details::with_error_done< std::decay_t<OnError>, std::decay_t<OnDone>> { return { std::forward<OnError>(error), std::forward<OnDone>(done) }; } template <typename OnError, typename OnDone> inline auto start_with_error_done( OnError &&error, OnDone &&done, lifetime &alive_while) -> details::lifetime_with_error_done< std::decay_t<OnError>, std::decay_t<OnDone>> { return { alive_while, std::forward<OnError>(error), std::forward<OnDone>(done) }; } template <typename OnNext, typename OnDone> inline auto start_with_next_done( OnNext &&next, OnDone &&done) -> details::with_next_done< std::decay_t<OnNext>, std::decay_t<OnDone>> { return { std::forward<OnNext>(next), std::forward<OnDone>(done) }; } template <typename OnNext, typename OnDone> inline auto start_with_next_done( OnNext &&next, OnDone &&done, lifetime &alive_while) -> details::lifetime_with_next_done< std::decay_t<OnNext>, std::decay_t<OnDone>> { return { alive_while, std::forward<OnNext>(next), std::forward<OnDone>(done) }; } template <typename OnNext, typename OnError, typename OnDone> inline auto start_with_next_error_done( OnNext &&next, OnError &&error, OnDone &&done) -> details::with_next_error_done< std::decay_t<OnNext>, std::decay_t<OnError>, std::decay_t<OnDone>> { return { std::forward<OnNext>(next), std::forward<OnError>(error), std::forward<OnDone>(done) }; } template <typename OnNext, typename OnError, typename OnDone> inline auto start_with_next_error_done( OnNext &&next, OnError &&error, OnDone &&done, lifetime &alive_while) -> details::lifetime_with_next_error_done< std::decay_t<OnNext>, std::decay_t<OnError>, std::decay_t<OnDone>> { return { alive_while, std::forward<OnNext>(next), std::forward<OnError>(error), std::forward<OnDone>(done) }; } namespace details { template <typename Value, typename Error, typename Generator> [[nodiscard]] inline lifetime operator|( producer<Value, Error, Generator> &&value, with_none &&handlers) { return std::move(value).start( [] {}, [] {}, [] {}); } template <typename Value, typename Error, typename Generator> inline void operator|( producer<Value, Error, Generator> &&value, lifetime_with_none &&handlers) { std::move(value).start( [] {}, [] {}, [] {}, handlers.alive_while); } template < typename Value, typename Error, typename Generator, typename OnNext, typename = std::enable_if_t<is_callable_v<OnNext, Value>>> [[nodiscard]] inline lifetime operator|( producer<Value, Error, Generator> &&value, with_next<OnNext> &&handlers) { return std::move(value).start( std::move(handlers.next), [] {}, [] {}); } template < typename Value, typename Error, typename Generator, typename OnNext, typename = std::enable_if_t<is_callable_v<OnNext, Value>>> inline void operator|( producer<Value, Error, Generator> &&value, lifetime_with_next<OnNext> &&handlers) { std::move(value).start( std::move(handlers.next), [] {}, [] {}, handlers.alive_while); } template < typename Value, typename Error, typename Generator, typename OnError, typename = std::enable_if_t<is_callable_v<OnError, Error>>> [[nodiscard]] inline lifetime operator|( producer<Value, Error, Generator> &&value, with_error<OnError> &&handlers) { return std::move(value).start( [] {}, std::move(handlers.error), [] {}); } template < typename Value, typename Error, typename Generator, typename OnError, typename = std::enable_if_t<is_callable_v<OnError, Error>>> inline void operator|( producer<Value, Error, Generator> &&value, lifetime_with_error<OnError> &&handlers) { std::move(value).start( [] {}, std::move(handlers.error), [] {}, handlers.alive_while); } template < typename Value, typename Error, typename Generator, typename OnDone, typename = std::enable_if_t<is_callable_v<OnDone>>> [[nodiscard]] inline lifetime operator|( producer<Value, Error, Generator> &&value, with_done<OnDone> &&handlers) { return std::move(value).start( [] {}, [] {}, std::move(handlers.done)); } template < typename Value, typename Error, typename Generator, typename OnDone, typename = std::enable_if_t<is_callable_v<OnDone>>> inline void operator|( producer<Value, Error, Generator> &&value, lifetime_with_done<OnDone> &&handlers) { std::move(value).start( [] {}, [] {}, std::move(handlers.done), handlers.alive_while); } template < typename Value, typename Error, typename Generator, typename OnNext, typename OnError, typename = std::enable_if_t< is_callable_v<OnNext, Value> && is_callable_v<OnError, Error>>> [[nodiscard]] inline lifetime operator|( producer<Value, Error, Generator> &&value, with_next_error<OnNext, OnError> &&handlers) { return std::move(value).start( std::move(handlers.next), std::move(handlers.error), [] {}); } template < typename Value, typename Error, typename Generator, typename OnNext, typename OnError, typename = std::enable_if_t< is_callable_v<OnNext, Value> && is_callable_v<OnError, Error>>> inline void operator|( producer<Value, Error, Generator> &&value, lifetime_with_next_error<OnNext, OnError> &&handlers) { std::move(value).start( std::move(handlers.next), std::move(handlers.error), [] {}, handlers.alive_while); } template < typename Value, typename Error, typename Generator, typename OnError, typename OnDone, typename = std::enable_if_t< is_callable_v<OnError, Error> && is_callable_v<OnDone>>> [[nodiscard]] inline lifetime operator|( producer<Value, Error, Generator> &&value, with_error_done<OnError, OnDone> &&handlers) { return std::move(value).start( [] {}, std::move(handlers.error), std::move(handlers.done)); } template < typename Value, typename Error, typename Generator, typename OnError, typename OnDone, typename = std::enable_if_t< is_callable_v<OnError, Error> && is_callable_v<OnDone>>> inline void operator|( producer<Value, Error, Generator> &&value, lifetime_with_error_done<OnError, OnDone> &&handlers) { std::move(value).start( [] {}, std::move(handlers.error), std::move(handlers.done), handlers.alive_while); } template < typename Value, typename Error, typename Generator, typename OnNext, typename OnDone, typename = std::enable_if_t< is_callable_v<OnNext, Value> && is_callable_v<OnDone>>> [[nodiscard]] inline lifetime operator|( producer<Value, Error, Generator> &&value, with_next_done<OnNext, OnDone> &&handlers) { return std::move(value).start( std::move(handlers.next), [] {}, std::move(handlers.done)); } template < typename Value, typename Error, typename Generator, typename OnNext, typename OnDone, typename = std::enable_if_t< is_callable_v<OnNext, Value> && is_callable_v<OnDone>>> inline void operator|( producer<Value, Error, Generator> &&value, lifetime_with_next_done<OnNext, OnDone> &&handlers) { std::move(value).start( std::move(handlers.next), [] {}, std::move(handlers.done), handlers.alive_while); } template < typename Value, typename Error, typename Generator, typename OnNext, typename OnError, typename OnDone, typename = std::enable_if_t< is_callable_v<OnNext, Value> && is_callable_v<OnError, Error> && is_callable_v<OnDone>>> [[nodiscard]] inline lifetime operator|( producer<Value, Error, Generator> &&value, with_next_error_done< OnNext, OnError, OnDone> &&handlers) { return std::move(value).start( std::move(handlers.next), std::move(handlers.error), std::move(handlers.done)); } template < typename Value, typename Error, typename Generator, typename OnNext, typename OnError, typename OnDone, typename = std::enable_if_t< is_callable_v<OnNext, Value> && is_callable_v<OnError, Error> && is_callable_v<OnDone>>> inline void operator|( producer<Value, Error, Generator> &&value, lifetime_with_next_error_done< OnNext, OnError, OnDone> &&handlers) { std::move(value).start( std::move(handlers.next), std::move(handlers.error), std::move(handlers.done), handlers.alive_while); } } // namespace details } // namespace rpl ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/rpl/producer_tests.cpp�����������������������������������������0000664�0000000�0000000�00000023143�13262451251�0023774�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "catch.hpp" #include <rpl/producer.h> #include <rpl/event_stream.h> using namespace rpl; class OnDestructor { public: OnDestructor(base::lambda_once<void()> callback) : _callback(std::move(callback)) { } ~OnDestructor() { if (_callback) { _callback(); } } private: base::lambda_once<void()> _callback; }; TEST_CASE("basic producer tests", "[rpl::producer]") { SECTION("producer next, done and lifetime end test") { auto lifetimeEnded = std::make_shared<bool>(false); auto sum = std::make_shared<int>(0); auto doneGenerated = std::make_shared<bool>(false); auto destroyed = std::make_shared<bool>(false); { auto destroyCaller = std::make_shared<OnDestructor>([=] { *destroyed = true; }); { auto alive = make_producer<int>([=](auto &&consumer) { (void)destroyCaller; consumer.put_next(1); consumer.put_next(2); consumer.put_next(3); consumer.put_done(); return [=] { (void)destroyCaller; *lifetimeEnded = true; }; }).start([=](int value) { (void)destroyCaller; *sum += value; }, [=](no_error) { (void)destroyCaller; }, [=]() { (void)destroyCaller; *doneGenerated = true; }); } } REQUIRE(*sum == 1 + 2 + 3); REQUIRE(*doneGenerated); REQUIRE(*lifetimeEnded); REQUIRE(*destroyed); } SECTION("producer error test") { auto errorGenerated = std::make_shared<bool>(false); { auto alive = make_producer<no_value, bool>([=](auto &&consumer) { consumer.put_error(true); return lifetime(); }).start([=](no_value) { }, [=](bool error) { *errorGenerated = error; }, [=]() { }); } REQUIRE(*errorGenerated); } SECTION("nested lifetimes test") { auto lifetimeEndCount = std::make_shared<int>(0); { auto lifetimes = lifetime(); { auto testProducer = make_producer<no_value>([=](auto &&consumer) { return [=] { ++*lifetimeEndCount; }; }); testProducer.start_copy([=](no_value) { }, [=](no_error) { }, [=] { }, lifetimes); std::move(testProducer).start([=](no_value) { }, [=](no_error) { }, [=] { }, lifetimes); } REQUIRE(*lifetimeEndCount == 0); } REQUIRE(*lifetimeEndCount == 2); } SECTION("nested producers test") { auto sum = std::make_shared<int>(0); auto lifetimeEndCount = std::make_shared<int>(0); auto saved = lifetime(); { make_producer<int>([=](auto &&consumer) { auto inner = make_producer<int>([=](auto &&consumer) { consumer.put_next(1); consumer.put_next(2); consumer.put_next(3); return [=] { ++*lifetimeEndCount; }; }); auto result = lifetime([=] { ++*lifetimeEndCount; }); inner.start_copy([=](int value) { consumer.put_next_copy(value); }, [=](no_error) { }, [=] { }, result); std::move(inner).start([=](int value) { consumer.put_next_copy(value); }, [=](no_error) { }, [=] { }, result); return result; }).start([=](int value) { *sum += value; }, [=](no_error) { }, [=] { }, saved); } REQUIRE(*sum == 1 + 2 + 3 + 1 + 2 + 3); REQUIRE(*lifetimeEndCount == 0); saved.destroy(); REQUIRE(*lifetimeEndCount == 3); } SECTION("tuple producer test") { auto result = std::make_shared<int>(0); { auto alive = make_producer<std::tuple<int, double>>([=]( auto &&consumer) { consumer.put_next(std::make_tuple(1, 2.)); return lifetime(); }).start([=](int a, double b) { *result = a + int(b); }, [=](no_error error) { }, [=]() { }); } REQUIRE(*result == 3); } } TEST_CASE("basic event_streams tests", "[rpl::event_stream]") { SECTION("event_stream basic test") { auto sum = std::make_shared<int>(0); event_stream<int> stream; stream.fire(1); stream.fire(2); stream.fire(3); { auto saved = lifetime(); stream.events().start([=](int value) { *sum += value; }, [=](no_error) { }, [=] { }, saved); stream.fire(11); stream.fire(12); stream.fire(13); } stream.fire(21); stream.fire(22); stream.fire(23); REQUIRE(11 + 12 + 13); } SECTION("event_stream add in handler test") { auto sum = std::make_shared<int>(0); event_stream<int> stream; { auto composite = lifetime(); stream.events().start([=, &stream, &composite](int value) { *sum += value; stream.events().start([=](int value) { *sum += value; }, [=](no_error) { }, [=] { }, composite); }, [=](no_error) { }, [=] { }, composite); { auto inner = lifetime(); stream.events().start([=, &stream, &inner](int value) { *sum += value; stream.events().start([=](int value) { *sum += value; }, [=](no_error) { }, [=] { }, inner); }, [=](no_error) { }, [=] { }, inner); stream.fire(1); stream.fire(2); stream.fire(3); } stream.fire(11); stream.fire(12); stream.fire(13); } stream.fire(21); stream.fire(22); stream.fire(23); REQUIRE(*sum == (1 + 1) + ((2 + 2) + (2 + 2)) + ((3 + 3 + 3) + (3 + 3 + 3)) + (11 + 11 + 11 + 11) + (12 + 12 + 12 + 12 + 12) + (13 + 13 + 13 + 13 + 13 + 13)); } SECTION("event_stream add and remove in handler test") { auto sum = std::make_shared<int>(0); event_stream<int> stream; { auto composite = lifetime(); stream.events().start([=, &stream, &composite](int value) { *sum += value; composite.destroy(); stream.events().start([=](int value) { *sum += value; }, [=](no_error) { }, [=] { }, composite); }, [=](no_error) { }, [=] { }, composite); { auto inner = lifetime(); stream.events().start([=, &stream, &inner](int value) { *sum += value; inner.destroy(); stream.events().start([=](int value) { *sum += value; }, [=](no_error) { }, [=] { }, inner); }, [=](no_error) { }, [=] { }, inner); stream.fire(1); stream.fire(2); stream.fire(3); } stream.fire(11); stream.fire(12); stream.fire(13); } stream.fire(21); stream.fire(22); stream.fire(23); REQUIRE(*sum == (1 + 1) + (2 + 2) + (3 + 3) + (11) + (12) + (13)); } SECTION("event_stream ends before handler lifetime") { auto sum = std::make_shared<int>(0); lifetime extended; { event_stream<int> stream; stream.events().start([=](int value) { *sum += value; }, [=](no_error) { }, [=] { }, extended); stream.fire(1); stream.fire(2); stream.fire(3); } REQUIRE(*sum == 1 + 2 + 3); } SECTION("event_stream move test") { auto sum = std::make_shared<int>(0); lifetime extended; { event_stream<int> stream; stream.events() | start_with_next([=](int value) { *sum += value; }, extended); stream.fire(1); stream.fire(2); auto movedStream = std::move(stream); movedStream.fire(3); movedStream.fire(4); } REQUIRE(*sum == 1 + 2 + 3 + 4); } } TEST_CASE("basic piping tests", "[rpl::producer]") { SECTION("start_with_*") { auto sum = std::make_shared<int>(0); auto dones = std::make_shared<int>(0); { auto alive = lifetime(); make_producer<int, int>([=](auto &&consumer) { consumer.put_next(1); consumer.put_done(); return lifetime(); }) | start_with_next([=](int value) { *sum += value; }, alive); make_producer<int, int>([=](auto &&consumer) { consumer.put_next(11); consumer.put_error(111); return lifetime(); }) | start_with_error([=](int value) { *sum += value; }, alive); make_producer<int, int>([=](auto &&consumer) { consumer.put_next(1111); consumer.put_done(); return lifetime(); }) | start_with_done([=]() { *dones += 1; }, alive); make_producer<int, int>([=](auto &&consumer) { consumer.put_next(11111); consumer.put_next(11112); consumer.put_next(11113); consumer.put_error(11114); return lifetime(); }) | start_with_next_error([=](int value) { *sum += value; }, [=](int value) { *sum += value; }, alive); } auto alive = lifetime(); make_producer<int, int>([=](auto &&consumer) { consumer.put_next(111111); consumer.put_next(111112); consumer.put_next(111113); consumer.put_done(); return lifetime(); }) | start_with_next_done([=](int value) { *sum += value; }, [=]() { *dones += 11; }, alive); make_producer<int, int>([=](auto &&consumer) { consumer.put_error(1111111); return lifetime(); }) | start_with_error_done([=](int value) { *sum += value; }, [=]() { *dones = 0; }, alive); make_producer<int, int>([=](auto &&consumer) { consumer.put_next(11111111); consumer.put_next(11111112); consumer.put_next(11111113); consumer.put_error(11111114); return lifetime(); }) | start_with_next_error_done([=](int value) { *sum += value; }, [=](int value) { *sum += value; }, [=]() { *dones = 0; }, alive); REQUIRE(*sum == 1 + 111 + 11111 + 11112 + 11113 + 11114 + 111111 + 111112 + 111113 + 1111111 + 11111111 + 11111112 + 11111113 + 11111114); REQUIRE(*dones == 1 + 11); } SECTION("start_with_next should copy its callback") { auto sum = std::make_shared<int>(0); { auto next = [=](int value) { REQUIRE(sum != nullptr); *sum += value; }; for (int i = 0; i != 3; ++i) { auto alive = lifetime(); make_producer<int, int>([=](auto &&consumer) { consumer.put_next(1); consumer.put_done(); return lifetime(); }) | start_with_next(next, alive); } } REQUIRE(*sum == 3); } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/rpl/range.h����������������������������������������������������0000664�0000000�0000000�00000003464�13262451251�0021474�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/producer.h> #include <vector> namespace rpl { template <typename Value> inline auto single(Value &&value) { return make_producer<std::decay_t<Value>>([ value = std::forward<Value>(value) ](const auto &consumer) mutable { consumer.put_next(std::move(value)); consumer.put_done(); return lifetime(); }); } inline auto single() { return make_producer<>([](const auto &consumer) { consumer.put_next({}); consumer.put_done(); return lifetime(); }); } template <typename Value> inline auto vector(std::vector<Value> &&values) { return make_producer<Value>([ values = std::move(values) ](const auto &consumer) mutable { for (auto &value : values) { consumer.put_next(std::move(value)); } consumer.put_done(); return lifetime(); }); } inline auto vector(std::vector<bool> &&values) { return make_producer<bool>([ values = std::move(values) ](const auto &consumer) { for (auto value : values) { consumer.put_next_copy(value); } consumer.put_done(); return lifetime(); }); } template < typename Range, typename Value = std::decay_t< decltype(*std::begin(std::declval<Range>()))>> inline auto range(Range &&range) { return vector(std::vector<Value>( std::begin(range), std::end(range))); } inline auto ints(int from, int till) { Expects(from <= till); return make_producer<int>([from, till](const auto &consumer) { for (auto i = from; i != till; ++i) { consumer.put_next_copy(i); } consumer.put_done(); return lifetime(); }); } inline auto ints(int count) { return ints(0, count); } } // namespace rpl ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/rpl/rpl.h������������������������������������������������������0000664�0000000�0000000�00000001610�13262451251�0021164�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once // rpl - reactive programming library #include <rpl/lifetime.h> #include <rpl/consumer.h> #include <rpl/producer.h> #include <rpl/event_stream.h> #include <rpl/range.h> #include <rpl/complete.h> #include <rpl/fail.h> #include <rpl/never.h> #include <rpl/take.h> #include <rpl/then.h> #include <rpl/deferred.h> #include <rpl/map.h> #include <rpl/mappers.h> #include <rpl/merge.h> #include <rpl/filter.h> #include <rpl/distinct_until_changed.h> #include <rpl/type_erased.h> #include <rpl/flatten_latest.h> #include <rpl/combine.h> #include <rpl/combine_previous.h> #include <rpl/variable.h> #include <rpl/before_next.h> #include <rpl/after_next.h> ������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/rpl/take.h�����������������������������������������������������0000664�0000000�0000000�00000002645�13262451251�0021324�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace rpl { namespace details { class take_helper { public: take_helper(int count) : _count(count) { } template < typename Value, typename Error, typename Generator> auto operator()(producer<Value, Error, Generator> &&initial) { return make_producer<Value, Error>([ initial = std::move(initial), limit = _count ](const auto &consumer) mutable { auto count = consumer.template make_state<int>(limit); auto initial_consumer = make_consumer<Value, Error>( [consumer, count](auto &&value) { auto left = (*count)--; if (left) { consumer.put_next_forward( std::forward<decltype(value)>(value)); --left; } if (!left) { consumer.put_done(); } }, [consumer](auto &&error) { consumer.put_error_forward( std::forward<decltype(error)>(error)); }, [consumer] { consumer.put_done(); }); consumer.add_lifetime(initial_consumer.terminator()); return std::move(initial).start_existing(initial_consumer); }); } private: int _count = 0; }; } // namespace details inline auto take(int count) -> details::take_helper { Expects(count >= 0); return details::take_helper(count); } } // namespace rpl �������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/rpl/then.h�����������������������������������������������������0000664�0000000�0000000�00000003617�13262451251�0021336�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/producer.h> namespace rpl { namespace details { template <typename Value, typename Error, typename Generator> class then_helper { public: then_helper(producer<Value, Error, Generator> &&following) : _following(std::move(following)) { } template < typename OtherValue, typename OtherError, typename OtherGenerator, typename NewValue = superset_type_t<Value, OtherValue>, typename NewError = superset_type_t<Error, OtherError>> auto operator()( producer<OtherValue, OtherError, OtherGenerator> &&initial ) { return make_producer<NewValue, NewError>([ initial = std::move(initial), following = std::move(_following) ](const auto &consumer) mutable { return std::move(initial).start( [consumer](auto &&value) { consumer.put_next_forward( std::forward<decltype(value)>(value)); }, [consumer](auto &&error) { consumer.put_error_forward( std::forward<decltype(error)>(error)); }, [ consumer, following = std::move(following) ]() mutable { consumer.add_lifetime(std::move(following).start( [consumer](auto &&value) { consumer.put_next_forward( std::forward<decltype(value)>(value)); }, [consumer](auto &&error) { consumer.put_error_forward( std::forward<decltype(error)>(error)); }, [consumer] { consumer.put_done(); })); }); }); } private: producer<Value, Error, Generator> _following; }; } // namespace details template <typename Value, typename Error, typename Generator> inline auto then(producer<Value, Error, Generator> &&following) -> details::then_helper<Value, Error, Generator> { return { std::move(following) }; } } // namespace rpl �����������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/rpl/type_erased.h����������������������������������������������0000664�0000000�0000000�00000001261�13262451251�0022675�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/producer.h> namespace rpl { namespace details { class type_erased_helper { public: template <typename Value, typename Error, typename Generator> producer<Value, Error> operator()( producer<Value, Error, Generator> &&initial) const { return std::move(initial); } }; } // namespace details inline auto type_erased() -> details::type_erased_helper { return details::type_erased_helper(); } } // namespace rpl �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/rpl/variable.h�������������������������������������������������0000664�0000000�0000000�00000005550�13262451251�0022163�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/producer.h> #include <rpl/event_stream.h> namespace rpl { namespace details { template <typename A, typename B> struct supports_equality_compare { template <typename U, typename V> static auto test(const U *u, const V *v) -> decltype(*u == *v, details::true_t()); static details::false_t test(...); static constexpr bool value = (sizeof(test( (std::decay_t<A>*)nullptr, (std::decay_t<B>*)nullptr )) == sizeof(details::true_t)); }; template <typename A, typename B> constexpr bool supports_equality_compare_v = supports_equality_compare<A, B>::value; } // namespace details template <typename Type> class variable final { public: variable() : _data{} { } template < typename OtherType, typename = std::enable_if_t< std::is_constructible_v<Type, OtherType&&>>> variable(OtherType &&data) : _data(std::forward<OtherType>(data)) { } template < typename OtherType, typename = std::enable_if_t< std::is_assignable_v<Type&, OtherType&&>>> variable &operator=(OtherType &&data) { _lifetime.destroy(); return assign(std::forward<OtherType>(data)); } template < typename OtherType, typename Error, typename Generator, typename = std::enable_if_t< std::is_assignable_v<Type&, OtherType>>> variable(producer<OtherType, Error, Generator> &&stream) { std::move(stream) | start_with_next([this](auto &&data) { assign(std::forward<decltype(data)>(data)); }, _lifetime); } template < typename OtherType, typename Error, typename Generator, typename = std::enable_if_t< std::is_assignable_v<Type&, OtherType>>> variable &operator=( producer<OtherType, Error, Generator> &&stream) { _lifetime.destroy(); std::move(stream) | start_with_next([this](auto &&data) { assign(std::forward<decltype(data)>(data)); }, _lifetime); } Type current() const { return _data; } auto value() const { return _changes.events_starting_with_copy(_data); } auto changes() const { return _changes.events(); } private: template <typename OtherType> variable &assign(OtherType &&data) { if constexpr (details::supports_equality_compare_v<Type, OtherType>) { if (!(_data == data)) { _data = std::forward<OtherType>(data); _changes.fire_copy(_data); } } else if constexpr (details::supports_equality_compare_v<Type, Type>) { auto old = std::move(_data); _data = std::forward<OtherType>(data); if (!(_data == old)) { _changes.fire_copy(_data); } } else { _data = std::forward<OtherType>(data); _changes.fire_copy(_data); } return *this; } Type _data; event_stream<Type> _changes; lifetime _lifetime; }; } // namespace rpl ��������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/rpl/variable_tests.cpp�����������������������������������������0000664�0000000�0000000�00000001243�13262451251�0023733�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "catch.hpp" #include <rpl/rpl.h> #include <string> using namespace rpl; TEST_CASE("basic variable tests", "[rpl::variable]") { SECTION("simple test") { auto sum = std::make_shared<int>(0); { auto var = variable<int>(1); auto lifeftime = var.value() | start_with_next([=](int value) { *sum += value; }); var = 1; var = 11; var = 111; var = 111; } REQUIRE(*sum == 1 + 11 + 111); } } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/settings.cpp���������������������������������������������������0000664�0000000�0000000�00000005156�13262451251�0021776�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "settings.h" bool gRtl = false; Qt::LayoutDirection gLangDir = gRtl ? Qt::RightToLeft : Qt::LeftToRight; bool gAlphaVersion = AppAlphaVersion; uint64 gBetaVersion = AppBetaVersion; uint64 gRealBetaVersion = AppBetaVersion; QByteArray gBetaPrivateKey; bool gTestMode = false; bool gDebug = false; bool gManyInstance = false; QString gKeyFile; QString gWorkingDir, gExeDir, gExeName; QStringList gSendPaths; QString gStartUrl; QString gDialogLastPath, gDialogHelperPath; // optimize QFileDialog bool gStartMinimized = false; bool gStartInTray = false; bool gAutoStart = false; bool gSendToMenu = false; bool gUseExternalVideoPlayer = false; bool gAutoUpdate = true; TWindowPos gWindowPos; LaunchMode gLaunchMode = LaunchModeNormal; bool gSupportTray = true; bool gSeenTrayTooltip = false; bool gRestartingUpdate = false, gRestarting = false, gRestartingToSettings = false, gWriteProtected = false; int32 gLastUpdateCheck = 0; bool gNoStartUpdate = false; bool gStartToSettings = false; bool gReplaceEmojis = true; bool gCtrlEnter = false; uint32 gConnectionsInSession = 1; QString gLoggedPhoneNumber; QByteArray gLocalSalt; DBIScale gRealScale = dbisAuto; DBIScale gScreenScale = dbisOne; DBIScale gConfigScale = dbisAuto; QString gTimeFormat = qsl("hh:mm"); RecentEmojiPack gRecentEmoji; RecentEmojiPreload gRecentEmojiPreload; EmojiColorVariants gEmojiVariants; RecentStickerPreload gRecentStickersPreload; RecentStickerPack gRecentStickers; RecentHashtagPack gRecentWriteHashtags, gRecentSearchHashtags; RecentInlineBots gRecentInlineBots; bool gPasswordRecovered = false; int32 gPasscodeBadTries = 0; TimeMs gPasscodeLastTry = 0; bool gRetina = false; float64 gRetinaFactor = 1.; int32 gIntRetinaFactor = 1; #ifdef Q_OS_WIN DBIPlatform gPlatform = dbipWindows; #elif defined OS_MAC_OLD DBIPlatform gPlatform = dbipMacOld; #elif defined Q_OS_MAC DBIPlatform gPlatform = dbipMac; #elif defined Q_OS_LINUX64 DBIPlatform gPlatform = dbipLinux64; #elif defined Q_OS_LINUX32 DBIPlatform gPlatform = dbipLinux32; #else #error Unknown platform #endif QString gPlatformString; QUrl gUpdateURL; bool gIsElCapitan = false; bool gIsSnowLeopard = false; int gOtherOnline = 0; SavedPeers gSavedPeers; SavedPeersByTime gSavedPeersByTime; ReportSpamStatuses gReportSpamStatuses; int32 gAutoDownloadPhoto = 0; // all auto download int32 gAutoDownloadAudio = 0; int32 gAutoDownloadGif = 0; bool gAutoPlayGif = true; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/settings.h�����������������������������������������������������0000664�0000000�0000000�00000014043�13262451251�0021436�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once extern bool gDebug; inline bool cDebug() { #if defined _DEBUG return true; #else return gDebug; #endif } inline void cSetDebug(bool debug) { gDebug = debug; } #define DeclareReadSetting(Type, Name) extern Type g##Name; \ inline const Type &c##Name() { \ return g##Name; \ } #define DeclareSetting(Type, Name) DeclareReadSetting(Type, Name) \ inline void cSet##Name(const Type &Name) { \ g##Name = Name; \ } #define DeclareRefSetting(Type, Name) DeclareSetting(Type, Name) \ inline Type &cRef##Name() { \ return g##Name; \ } DeclareSetting(bool, Rtl); DeclareSetting(Qt::LayoutDirection, LangDir); inline bool rtl() { return cRtl(); } DeclareSetting(bool, AlphaVersion); DeclareSetting(uint64, BetaVersion); DeclareSetting(uint64, RealBetaVersion); DeclareSetting(QByteArray, BetaPrivateKey); DeclareSetting(bool, TestMode); DeclareSetting(QString, LoggedPhoneNumber); DeclareSetting(bool, AutoStart); DeclareSetting(bool, StartMinimized); DeclareSetting(bool, StartInTray); DeclareSetting(bool, SendToMenu); DeclareSetting(bool, UseExternalVideoPlayer); enum LaunchMode { LaunchModeNormal = 0, LaunchModeAutoStart, LaunchModeFixPrevious, LaunchModeCleanup, }; DeclareReadSetting(LaunchMode, LaunchMode); DeclareSetting(QString, WorkingDir); inline void cForceWorkingDir(const QString &newDir) { cSetWorkingDir(newDir); if (!gWorkingDir.isEmpty()) { QDir().mkpath(gWorkingDir); QFile::setPermissions(gWorkingDir, QFileDevice::ReadUser | QFileDevice::WriteUser | QFileDevice::ExeUser); } } DeclareReadSetting(QString, ExeName); DeclareReadSetting(QString, ExeDir); DeclareSetting(QString, DialogLastPath); DeclareSetting(QString, DialogHelperPath); inline const QString &cDialogHelperPathFinal() { return cDialogHelperPath().isEmpty() ? cExeDir() : cDialogHelperPath(); } DeclareSetting(bool, CtrlEnter); DeclareSetting(bool, AutoUpdate); struct TWindowPos { TWindowPos() = default; int32 moncrc = 0; int maximized = 0; int x = 0; int y = 0; int w = 0; int h = 0; }; DeclareSetting(TWindowPos, WindowPos); DeclareSetting(bool, SupportTray); DeclareSetting(bool, SeenTrayTooltip); DeclareSetting(bool, RestartingUpdate); DeclareSetting(bool, Restarting); DeclareSetting(bool, RestartingToSettings); DeclareSetting(bool, WriteProtected); DeclareSetting(int32, LastUpdateCheck); DeclareSetting(bool, NoStartUpdate); DeclareSetting(bool, StartToSettings); DeclareSetting(bool, ReplaceEmojis); DeclareReadSetting(bool, ManyInstance); DeclareSetting(QByteArray, LocalSalt); DeclareSetting(DBIScale, RealScale); DeclareSetting(DBIScale, ScreenScale); DeclareSetting(DBIScale, ConfigScale); DeclareSetting(QString, TimeFormat); inline void cChangeTimeFormat(const QString &newFormat) { if (!newFormat.isEmpty()) cSetTimeFormat(newFormat); } inline DBIScale cEvalScale(DBIScale scale) { return (scale == dbisAuto) ? cScreenScale() : scale; } inline DBIScale cScale() { return cEvalScale(cRealScale()); } template <typename T> T convertScale(T v) { switch (cScale()) { case dbisOneAndQuarter: return qRound(float64(v) * 1.25 - 0.01); case dbisOneAndHalf: return qRound(float64(v) * 1.5 - 0.01); case dbisTwo: return v * 2; } return v; } namespace Ui { namespace Emoji { class One; } // namespace Emoji } // namespace Ui using EmojiPtr = const Ui::Emoji::One*; using EmojiPack = QVector<EmojiPtr>; using RecentEmojiPreloadOldOld = QVector<QPair<uint32, ushort>>; using RecentEmojiPreloadOld = QVector<QPair<uint64, ushort>>; using RecentEmojiPreload = QVector<QPair<QString, ushort>>; using RecentEmojiPack = QVector<QPair<EmojiPtr, ushort>>; using EmojiColorVariantsOld = QMap<uint32, uint64>; using EmojiColorVariants = QMap<QString, int>; DeclareRefSetting(RecentEmojiPack, RecentEmoji); DeclareSetting(RecentEmojiPreload, RecentEmojiPreload); DeclareRefSetting(EmojiColorVariants, EmojiVariants); class DocumentData; typedef QList<QPair<DocumentData*, int16>> RecentStickerPackOld; typedef QVector<QPair<uint64, ushort>> RecentStickerPreload; typedef QVector<QPair<DocumentData*, ushort>> RecentStickerPack; DeclareSetting(RecentStickerPreload, RecentStickersPreload); DeclareRefSetting(RecentStickerPack, RecentStickers); typedef QList<QPair<QString, ushort>> RecentHashtagPack; DeclareRefSetting(RecentHashtagPack, RecentWriteHashtags); DeclareSetting(RecentHashtagPack, RecentSearchHashtags); class UserData; typedef QVector<UserData*> RecentInlineBots; DeclareRefSetting(RecentInlineBots, RecentInlineBots); DeclareSetting(bool, PasswordRecovered); DeclareSetting(int32, PasscodeBadTries); DeclareSetting(TimeMs, PasscodeLastTry); inline bool passcodeCanTry() { if (cPasscodeBadTries() < 3) return true; auto dt = getms(true) - cPasscodeLastTry(); switch (cPasscodeBadTries()) { case 3: return dt >= 5000; case 4: return dt >= 10000; case 5: return dt >= 15000; case 6: return dt >= 20000; case 7: return dt >= 25000; } return dt >= 30000; } DeclareSetting(QStringList, SendPaths); DeclareSetting(QString, StartUrl); DeclareSetting(bool, Retina); DeclareSetting(float64, RetinaFactor); DeclareSetting(int32, IntRetinaFactor); DeclareReadSetting(DBIPlatform, Platform); DeclareReadSetting(QString, PlatformString); DeclareReadSetting(bool, IsElCapitan); DeclareReadSetting(bool, IsSnowLeopard); DeclareReadSetting(QUrl, UpdateURL); DeclareSetting(int, OtherOnline); class PeerData; typedef QMap<PeerData*, QDateTime> SavedPeers; typedef QMultiMap<QDateTime, PeerData*> SavedPeersByTime; DeclareRefSetting(SavedPeers, SavedPeers); DeclareRefSetting(SavedPeersByTime, SavedPeersByTime); typedef QMap<uint64, DBIPeerReportSpamStatus> ReportSpamStatuses; DeclareRefSetting(ReportSpamStatuses, ReportSpamStatuses); enum DBIAutoDownloadFlags { dbiadNoPrivate = 0x01, dbiadNoGroups = 0x02, }; DeclareSetting(int32, AutoDownloadPhoto); DeclareSetting(int32, AutoDownloadAudio); DeclareSetting(int32, AutoDownloadGif); DeclareSetting(bool, AutoPlayGif); ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/settings/������������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0021263�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/settings/settings.style����������������������������������������0000664�0000000�0000000�00000005464�13262451251�0024216�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ using "basic.style"; using "dialogs/dialogs.style"; using "ui/widgets/widgets.style"; using "boxes/boxes.style"; settingsScroll: ScrollArea(defaultScrollArea) { bottomsh: 0px; topsh: 0px; } settingsMaxWidth: 520px; settingsMaxPadding: 48px; settingsMinPadding: 32px; settingsMargin: 48px; settingsFixedBarHeight: boxLayerTitleHeight; settingsFixedBarFont: boxTitleFont; settingsFixedBarTextPosition: boxLayerTitlePosition; settingsFixedBarClose: IconButton(boxTitleClose) { width: settingsFixedBarHeight; height: settingsFixedBarHeight; } settingsMarginTop: 34px; settingsPhoto: UserpicButton(defaultUserpicButton) { size: size(112px, 112px); photoSize: 112px; } settingsPhotoLeft: -8px; settingsPhotoDuration: 500; settingsNameLeft: 26px; settingsNameTop: 9px; settingsNameLabel: FlatLabel(defaultFlatLabel) { margin: margins(10px, 5px, 10px, 5px); minWidth: 160px; maxHeight: 24px; textFg: windowBoldFg; style: TextStyle(defaultTextStyle) { font: font(16px semibold); linkFont: font(16px semibold); linkFontOver: font(16px semibold underline); } } settingsStatusLeft: 27px; settingsStatusTop: 35px; settingsStatusFont: normalFont; settingsStatusFg: windowSubTextFg; settingsStatusFgActive: windowActiveTextFg; settingsMarginBottom: 35px; settingsButtonLeft: 27px; settingsButtonTop: 75px; settingsButtonSkip: 10px; settingsPrimaryButton: defaultActiveButton; settingsSecondaryButton: defaultLightButton; settingsEditButton: IconButton { width: 24px; height: 34px; icon: icon {{ "settings_edit_name", menuIconFg }}; iconPosition: point(3px, 9px); } settingsBlocksTop: 7px; settingsBlocksBottom: 20px; settingsBlockMarginTop: 14px; settingsBlockMarginRight: 10px; settingsBlockMarginBottom: 10px; settingsBlockTitleHeight: 31px; settingsBlockTitleFont: font(15px semibold); settingsBlockTitleFg: windowBoldFg; settingsBlockTitleTop: 0px; settingsPrimaryLabel: FlatLabel(defaultFlatLabel) { style: TextStyle(defaultTextStyle) { font: boxTextFont; linkFont: boxTextFont; linkFontOver: font(boxFontSize underline); } } settingsBlockLabel: FlatLabel(settingsPrimaryLabel) { textFg: windowSubTextFg; } settingsBlockOneLineTextPart: FlatLabel(settingsPrimaryLabel) { minWidth: 0px; // No need to set minWidth in one-line text. margin: margins(5px, 5px, 5px, 5px); maxHeight: 20px; } settingsBioValue: FlatLabel(settingsBlockOneLineTextPart) { minWidth: 120px; maxHeight: 0px; } settingsSubSkip: 4px; settingsSmallSkip: 10px; settingsSkip: 14px; settingsLargeSkip: 23px; settingsActionPadding: margins(0px, 4px, 0px, 5px); settingsBackgroundSize: 120px; settingsUpdateFg: windowSubTextFg; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/settings/settings_advanced_widget.cpp��������������������������0000664�0000000�0000000�00000012553�13262451251�0027025�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "settings/settings_advanced_widget.h" #include "styles/style_settings.h" #include "lang/lang_keys.h" #include "boxes/connection_box.h" #include "boxes/confirm_box.h" #include "boxes/about_box.h" #include "boxes/local_storage_box.h" #include "mainwindow.h" #include "ui/widgets/buttons.h" #include "ui/wrap/slide_wrap.h" #include "storage/localstorage.h" #include "window/themes/window_theme.h" namespace Settings { AdvancedWidget::AdvancedWidget(QWidget *parent, UserData *self) : BlockWidget(parent, self, lang(lng_settings_section_advanced_settings)) { createControls(); #ifndef TDESKTOP_DISABLE_NETWORK_PROXY subscribe(Global::RefConnectionTypeChanged(), [this]() { connectionTypeUpdated(); }); #endif // !TDESKTOP_DISABLE_NETWORK_PROXY if (!self) { subscribe(Window::Theme::Background(), [this](const Window::Theme::BackgroundUpdate &update) { if (update.type == Window::Theme::BackgroundUpdate::Type::ApplyingTheme) { checkNonDefaultTheme(); } }); } } void AdvancedWidget::createControls() { style::margins marginSmall(0, 0, 0, st::settingsSmallSkip); style::margins marginLarge(0, 0, 0, st::settingsLargeSkip); style::margins marginLocalStorage = [&] { #ifndef TDESKTOP_DISABLE_NETWORK_PROXY return marginSmall; #else // !TDESKTOP_DISABLE_NETWORK_PROXY return marginLarge; #endif // TDESKTOP_DISABLE_NETWORK_PROXY }(); if (self()) { createChildRow(_manageLocalStorage, marginLocalStorage, lang(lng_settings_manage_local_storage), SLOT(onManageLocalStorage())); } #ifndef TDESKTOP_DISABLE_NETWORK_PROXY createChildRow(_connectionType, marginLarge, lang(lng_connection_type), lang(lng_connection_auto_connecting), LabeledLink::Type::Primary, SLOT(onConnectionType())); connectionTypeUpdated(); #endif // !TDESKTOP_DISABLE_NETWORK_PROXY if (self()) { createChildRow(_askQuestion, marginSmall, lang(lng_settings_ask_question), SLOT(onAskQuestion())); } else { style::margins slidedPadding(0, marginLarge.bottom() / 2, 0, marginLarge.bottom() - (marginLarge.bottom() / 2)); createChildRow(_useDefaultTheme, marginLarge, slidedPadding, lang(lng_settings_bg_use_default), SLOT(onUseDefaultTheme())); if (!Window::Theme::IsNonDefaultUsed()) { _useDefaultTheme->hide(anim::type::instant); } createChildRow(_toggleNightTheme, marginLarge, slidedPadding, getNightThemeToggleText(), SLOT(onToggleNightTheme())); if (Window::Theme::IsNonDefaultUsed()) { _toggleNightTheme->hide(anim::type::instant); } } createChildRow(_telegramFAQ, marginLarge, lang(lng_settings_faq), SLOT(onTelegramFAQ())); if (self()) { style::margins marginLogout(0, 0, 0, 2 * st::settingsLargeSkip); createChildRow(_logOut, marginLogout, lang(lng_settings_logout), SLOT(onLogOut())); } } void AdvancedWidget::checkNonDefaultTheme() { if (self()) return; _useDefaultTheme->toggle( Window::Theme::IsNonDefaultUsed(), anim::type::normal); _toggleNightTheme->entity()->setText(getNightThemeToggleText()); _toggleNightTheme->toggle( !Window::Theme::IsNonDefaultUsed(), anim::type::normal); } void AdvancedWidget::onManageLocalStorage() { Ui::show(Box<LocalStorageBox>()); } #ifndef TDESKTOP_DISABLE_NETWORK_PROXY void AdvancedWidget::connectionTypeUpdated() { auto connection = [] { switch (Global::ConnectionType()) { case dbictHttpProxy: case dbictTcpProxy: { auto transport = MTP::dctransport(); return transport.isEmpty() ? lang(lng_connection_proxy_connecting) : lng_connection_proxy(lt_transport, transport); } break; case dbictAuto: default: { auto transport = MTP::dctransport(); return transport.isEmpty() ? lang(lng_connection_auto_connecting) : lng_connection_auto(lt_transport, transport); } break; } }; _connectionType->link()->setText(connection()); resizeToWidth(width()); } void AdvancedWidget::onConnectionType() { Ui::show(Box<ConnectionBox>()); } #endif // !TDESKTOP_DISABLE_NETWORK_PROXY void AdvancedWidget::onUseDefaultTheme() { Window::Theme::ApplyDefault(); } void AdvancedWidget::onToggleNightTheme() { Window::Theme::SwitchNightTheme(!Window::Theme::IsNightTheme()); } void AdvancedWidget::onAskQuestion() { auto box = Box<ConfirmBox>(lang(lng_settings_ask_sure), lang(lng_settings_ask_ok), lang(lng_settings_faq_button), base::lambda_guarded(this, [this] { onAskQuestionSure(); }), base::lambda_guarded(this, [this] { onTelegramFAQ(); })); box->setStrictCancel(true); Ui::show(std::move(box)); } void AdvancedWidget::onAskQuestionSure() { if (_supportGetRequest) return; _supportGetRequest = MTP::send(MTPhelp_GetSupport(), rpcDone(&AdvancedWidget::supportGot)); } void AdvancedWidget::supportGot(const MTPhelp_Support &support) { if (!App::main()) return; if (support.type() == mtpc_help_support) { if (auto user = App::feedUsers(MTP_vector<MTPUser>(1, support.c_help_support().vuser))) { Ui::showPeerHistory(user, ShowAtUnreadMsgId); } } } QString AdvancedWidget::getNightThemeToggleText() const { return lang(Window::Theme::IsNightTheme() ? lng_settings_disable_night_theme : lng_settings_enable_night_theme); } void AdvancedWidget::onTelegramFAQ() { QDesktopServices::openUrl(telegramFaqLink()); } void AdvancedWidget::onLogOut() { App::wnd()->onLogout(); } } // namespace Settings �����������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/settings/settings_advanced_widget.h����������������������������0000664�0000000�0000000�00000003015�13262451251�0026463�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "settings/settings_block_widget.h" #include "settings/settings_chat_settings_widget.h" namespace Settings { class AdvancedWidget : public BlockWidget, public RPCSender { Q_OBJECT public: AdvancedWidget(QWidget *parent, UserData *self); private slots: void onManageLocalStorage(); #ifndef TDESKTOP_DISABLE_NETWORK_PROXY void onConnectionType(); #endif // !TDESKTOP_DISABLE_NETWORK_PROXY void onAskQuestion(); void onAskQuestionSure(); void onUseDefaultTheme(); void onToggleNightTheme(); void onTelegramFAQ(); void onLogOut(); private: void createControls(); void checkNonDefaultTheme(); #ifndef TDESKTOP_DISABLE_NETWORK_PROXY void connectionTypeUpdated(); #endif // !TDESKTOP_DISABLE_NETWORK_PROXY void supportGot(const MTPhelp_Support &support); QString getNightThemeToggleText() const; Ui::LinkButton *_manageLocalStorage = nullptr; #ifndef TDESKTOP_DISABLE_NETWORK_PROXY LabeledLink *_connectionType = nullptr; #endif // !TDESKTOP_DISABLE_NETWORK_PROXY Ui::SlideWrap<Ui::LinkButton> *_useDefaultTheme = nullptr; Ui::SlideWrap<Ui::LinkButton> *_toggleNightTheme = nullptr; Ui::LinkButton *_askQuestion = nullptr; Ui::LinkButton *_telegramFAQ = nullptr; Ui::LinkButton *_logOut = nullptr; mtpRequestId _supportGetRequest = 0; }; } // namespace Settings �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/settings/settings_background_widget.cpp������������������������0000664�0000000�0000000�00000023422�13262451251�0027374�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "settings/settings_background_widget.h" #include "styles/style_settings.h" #include "lang/lang_keys.h" #include "mainwidget.h" #include "boxes/background_box.h" #include "ui/wrap/slide_wrap.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/buttons.h" #include "storage/localstorage.h" #include "mainwindow.h" #include "window/themes/window_theme.h" #include "window/themes/window_theme_editor.h" #include "core/file_utilities.h" namespace Settings { BackgroundRow::BackgroundRow(QWidget *parent) : RpWidget(parent) , _chooseFromGallery(this, lang(lng_settings_bg_from_gallery), st::boxLinkButton) , _chooseFromFile(this, lang(lng_settings_bg_from_file), st::boxLinkButton) , _editTheme(this, lang(lng_settings_bg_edit_theme), st::boxLinkButton) , _radial(animation(this, &BackgroundRow::step_radial)) { updateImage(); connect(_chooseFromGallery, SIGNAL(clicked()), this, SIGNAL(chooseFromGallery())); connect(_chooseFromFile, SIGNAL(clicked()), this, SIGNAL(chooseFromFile())); connect(_editTheme, SIGNAL(clicked()), this, SIGNAL(editTheme())); checkNonDefaultTheme(); subscribe(Window::Theme::Background(), [this](const Window::Theme::BackgroundUpdate &update) { if (update.type == Window::Theme::BackgroundUpdate::Type::ApplyingTheme) { checkNonDefaultTheme(); } }); } void BackgroundRow::checkNonDefaultTheme() { if (Window::Theme::IsNonDefaultUsed()) { if (!_useDefaultTheme) { _useDefaultTheme.create(this, lang(lng_settings_bg_use_default), st::boxLinkButton); _useDefaultTheme->show(); connect(_useDefaultTheme, SIGNAL(clicked()), this, SIGNAL(useDefault())); resizeToWidth(width()); } } else if (_useDefaultTheme) { _useDefaultTheme.destroy(); resizeToWidth(width()); } } void BackgroundRow::paintEvent(QPaintEvent *e) { Painter p(this); bool radial = false; float64 radialOpacity = 0; if (_radial.animating()) { _radial.step(getms()); radial = _radial.animating(); radialOpacity = _radial.opacity(); } if (radial) { auto backThumb = App::main() ? App::main()->newBackgroundThumb() : ImagePtr(); if (backThumb->isNull()) { p.drawPixmap(0, 0, _background); } else { const QPixmap &pix = App::main()->newBackgroundThumb()->pixBlurred(st::settingsBackgroundSize); p.drawPixmap(0, 0, st::settingsBackgroundSize, st::settingsBackgroundSize, pix, 0, (pix.height() - st::settingsBackgroundSize * cIntRetinaFactor()) / 2, st::settingsBackgroundSize * cIntRetinaFactor(), st::settingsBackgroundSize * cIntRetinaFactor()); } auto outer = radialRect(); QRect inner(QPoint(outer.x() + (outer.width() - st::radialSize.width()) / 2, outer.y() + (outer.height() - st::radialSize.height()) / 2), st::radialSize); p.setPen(Qt::NoPen); p.setOpacity(radialOpacity); p.setBrush(st::radialBg); { PainterHighQualityEnabler hq(p); p.drawEllipse(inner); } p.setOpacity(1); QRect arc(inner.marginsRemoved(QMargins(st::radialLine, st::radialLine, st::radialLine, st::radialLine))); _radial.draw(p, arc, st::radialLine, st::radialFg); } else { p.drawPixmap(0, 0, _background); } } int BackgroundRow::resizeGetHeight(int newWidth) { auto linkTop = 0; auto linkLeft = st::settingsBackgroundSize + st::settingsSmallSkip; auto linkWidth = newWidth - linkLeft; _chooseFromGallery->resizeToWidth(qMin(linkWidth, _chooseFromGallery->naturalWidth())); _chooseFromFile->resizeToWidth(qMin(linkWidth, _chooseFromFile->naturalWidth())); _editTheme->resizeToWidth(qMin(linkWidth, _editTheme->naturalWidth())); if (_useDefaultTheme) { _useDefaultTheme->resizeToWidth(qMin(linkWidth, _useDefaultTheme->naturalWidth())); _useDefaultTheme->moveToLeft(linkLeft, linkTop, newWidth); linkTop += _useDefaultTheme->height() + st::settingsSmallSkip; } _chooseFromGallery->moveToLeft(linkLeft, linkTop, newWidth); linkTop += _chooseFromGallery->height() + st::settingsSmallSkip; _chooseFromFile->moveToLeft(linkLeft, linkTop, newWidth); linkTop += _chooseFromFile->height() + st::settingsSmallSkip; _editTheme->moveToLeft(linkLeft, linkTop, newWidth); return st::settingsBackgroundSize; } float64 BackgroundRow::radialProgress() const { if (auto m = App::main()) { return m->chatBackgroundProgress(); } return 1.; } bool BackgroundRow::radialLoading() const { if (auto m = App::main()) { if (m->chatBackgroundLoading()) { m->checkChatBackground(); if (m->chatBackgroundLoading()) { return true; } else { const_cast<BackgroundRow*>(this)->updateImage(); } } } return false; } QRect BackgroundRow::radialRect() const { return QRect(0, 0, st::settingsBackgroundSize, st::settingsBackgroundSize); } void BackgroundRow::radialStart() { if (radialLoading() && !_radial.animating()) { _radial.start(radialProgress()); if (auto shift = radialTimeShift()) { _radial.update(radialProgress(), !radialLoading(), getms() + shift); } } } TimeMs BackgroundRow::radialTimeShift() const { return st::radialDuration; } void BackgroundRow::step_radial(TimeMs ms, bool timer) { _radial.update(radialProgress(), !radialLoading(), ms + radialTimeShift()); if (timer && _radial.animating()) { rtlupdate(radialRect()); } } void BackgroundRow::updateImage() { int32 size = st::settingsBackgroundSize * cIntRetinaFactor(); QImage back(size, size, QImage::Format_ARGB32_Premultiplied); back.setDevicePixelRatio(cRetinaFactor()); { Painter p(&back); PainterHighQualityEnabler hq(p); auto &pix = Window::Theme::Background()->pixmap(); int sx = (pix.width() > pix.height()) ? ((pix.width() - pix.height()) / 2) : 0; int sy = (pix.height() > pix.width()) ? ((pix.height() - pix.width()) / 2) : 0; int s = (pix.width() > pix.height()) ? pix.height() : pix.width(); p.drawPixmap(0, 0, st::settingsBackgroundSize, st::settingsBackgroundSize, pix, sx, sy, s, s); } Images::prepareRound(back, ImageRoundRadius::Small); _background = App::pixmapFromImageInPlace(std::move(back)); _background.setDevicePixelRatio(cRetinaFactor()); rtlupdate(radialRect()); if (radialLoading()) { radialStart(); } } BackgroundWidget::BackgroundWidget(QWidget *parent, UserData *self) : BlockWidget(parent, self, lang(lng_settings_section_background)) { createControls(); using Update = Window::Theme::BackgroundUpdate; subscribe(Window::Theme::Background(), [this](const Update &update) { if (update.type == Update::Type::New) { _background->updateImage(); } else if (update.type == Update::Type::Start) { needBackgroundUpdate(update.tiled); } }); subscribe(Adaptive::Changed(), [this]() { _adaptive->toggle( (Global::AdaptiveChatLayout() == Adaptive::ChatLayout::Wide), anim::type::normal); }); } void BackgroundWidget::createControls() { style::margins margin(0, 0, 0, st::settingsSmallSkip); style::margins slidedPadding(0, margin.bottom() / 2, 0, margin.bottom() - (margin.bottom() / 2)); createChildRow(_background, margin); connect(_background, SIGNAL(chooseFromGallery()), this, SLOT(onChooseFromGallery())); connect(_background, SIGNAL(chooseFromFile()), this, SLOT(onChooseFromFile())); connect(_background, SIGNAL(editTheme()), this, SLOT(onEditTheme())); connect(_background, SIGNAL(useDefault()), this, SLOT(onUseDefaultTheme())); createChildRow(_tile, margin, lang(lng_settings_bg_tile), [this](bool) { onTile(); }, Window::Theme::Background()->tile()); createChildRow(_adaptive, margin, slidedPadding, lang(lng_settings_adaptive_wide), [this](bool) { onAdaptive(); }, Global::AdaptiveForWide()); if (Global::AdaptiveChatLayout() != Adaptive::ChatLayout::Wide) { _adaptive->hide(anim::type::instant); } } void BackgroundWidget::onChooseFromGallery() { Ui::show(Box<BackgroundBox>()); } void BackgroundWidget::needBackgroundUpdate(bool tile) { _tile->setChecked(tile); _background->updateImage(); } void BackgroundWidget::onChooseFromFile() { auto imgExtensions = cImgExtensions(); auto filters = QStringList(qsl("Theme files (*.tdesktop-theme *.tdesktop-palette *") + imgExtensions.join(qsl(" *")) + qsl(")")); filters.push_back(FileDialog::AllFilesFilter()); FileDialog::GetOpenPath(lang(lng_choose_image), filters.join(qsl(";;")), base::lambda_guarded(this, [this](const FileDialog::OpenResult &result) { if (result.paths.isEmpty() && result.remoteContent.isEmpty()) { return; } if (!result.paths.isEmpty()) { auto filePath = result.paths.front(); if (filePath.endsWith(qstr(".tdesktop-theme"), Qt::CaseInsensitive) || filePath.endsWith(qstr(".tdesktop-palette"), Qt::CaseInsensitive)) { Window::Theme::Apply(filePath); return; } } QImage img; if (!result.remoteContent.isEmpty()) { img = App::readImage(result.remoteContent); } else { img = App::readImage(result.paths.front()); } if (img.isNull() || img.width() <= 0 || img.height() <= 0) return; if (img.width() > 4096 * img.height()) { img = img.copy((img.width() - 4096 * img.height()) / 2, 0, 4096 * img.height(), img.height()); } else if (img.height() > 4096 * img.width()) { img = img.copy(0, (img.height() - 4096 * img.width()) / 2, img.width(), 4096 * img.width()); } Window::Theme::Background()->setImage(Window::Theme::kCustomBackground, std::move(img)); _tile->setChecked(false); _background->updateImage(); })); } void BackgroundWidget::onEditTheme() { Window::Theme::Editor::Start(); } void BackgroundWidget::onUseDefaultTheme() { Window::Theme::ApplyDefault(); } void BackgroundWidget::onTile() { Window::Theme::Background()->setTile(_tile->checked()); } void BackgroundWidget::onAdaptive() { if (Global::AdaptiveForWide() != _adaptive->entity()->checked()) { Global::SetAdaptiveForWide(_adaptive->entity()->checked()); Adaptive::Changed().notify(); Local::writeUserSettings(); } } } // namespace Settings ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/settings/settings_background_widget.h��������������������������0000664�0000000�0000000�00000003254�13262451251�0027042�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "settings/settings_block_widget.h" #include "ui/effects/radial_animation.h" #include "ui/rp_widget.h" namespace Settings { class BackgroundRow : public Ui::RpWidget, private base::Subscriber { Q_OBJECT public: BackgroundRow(QWidget *parent); void updateImage(); protected: void paintEvent(QPaintEvent *e) override; int resizeGetHeight(int newWidth) override; signals: void chooseFromGallery(); void chooseFromFile(); void editTheme(); void useDefault(); private: void checkNonDefaultTheme(); float64 radialProgress() const; bool radialLoading() const; QRect radialRect() const; void radialStart(); TimeMs radialTimeShift() const; void step_radial(TimeMs ms, bool timer); QPixmap _background; object_ptr<Ui::LinkButton> _useDefaultTheme = { nullptr }; object_ptr<Ui::LinkButton> _chooseFromGallery; object_ptr<Ui::LinkButton> _chooseFromFile; object_ptr<Ui::LinkButton> _editTheme; Ui::RadialAnimation _radial; }; class BackgroundWidget : public BlockWidget { Q_OBJECT public: BackgroundWidget(QWidget *parent, UserData *self); private slots: void onChooseFromGallery(); void onChooseFromFile(); void onEditTheme(); void onUseDefaultTheme(); void onTile(); void onAdaptive(); private: void createControls(); void needBackgroundUpdate(bool tile); BackgroundRow *_background = nullptr; Ui::Checkbox *_tile = nullptr; Ui::SlideWrap<Ui::Checkbox> *_adaptive = nullptr; }; } // namespace Settings ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/settings/settings_block_widget.cpp�����������������������������0000664�0000000�0000000�00000005354�13262451251�0026353�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "settings/settings_block_widget.h" #include "styles/style_settings.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/buttons.h" #include "ui/wrap/vertical_layout.h" namespace Settings { BlockWidget::BlockWidget(QWidget *parent, UserData *self, const QString &title) : RpWidget(parent) , _content(this) , _self(self) , _title(title) { _content->heightValue( ) | rpl::start_with_next([this](int contentHeight) { resize( width(), contentTop() + contentHeight + st::settingsBlockMarginBottom); }, lifetime()); } void BlockWidget::setContentLeft(int contentLeft) { _contentLeft = contentLeft; } int BlockWidget::contentTop() const { return emptyTitle() ? 0 : (st::settingsBlockMarginTop + st::settingsBlockTitleHeight); } int BlockWidget::resizeGetHeight(int newWidth) { int x = contentLeft(), result = contentTop(); int availw = newWidth - x; auto margins = getMargins(); _content->resizeToWidth(availw); _content->moveToLeft(margins.left() + x, margins.top() + result, newWidth); result += _content->heightNoMargins() + st::settingsBlockMarginBottom; return result; } QMargins BlockWidget::getMargins() const { auto result = _content->getMargins(); return QMargins( result.left(), qMax(result.top() - contentTop(), 0), result.right(), qMax(result.bottom() - st::settingsBlockMarginBottom, 0)); } void BlockWidget::paintEvent(QPaintEvent *e) { Painter p(this); paintTitle(p); paintContents(p); } void BlockWidget::paintTitle(Painter &p) { if (emptyTitle()) return; p.setFont(st::settingsBlockTitleFont); p.setPen(st::settingsBlockTitleFg); auto margins = getMargins(); auto titleTop = st::settingsBlockMarginTop + st::settingsBlockTitleTop; p.drawTextLeft( margins.left() + contentLeft(), margins.top() + titleTop, width(), _title); } Ui::RpWidget *BlockWidget::addCreatedRow( object_ptr<RpWidget> row, const style::margins &margin) { return _content->add(std::move(row), margin); } void BlockWidget::createChildWidget( object_ptr<Ui::Checkbox> &child, style::margins &margin, const QString &text, base::lambda<void(bool checked)> callback, bool checked) { child.create(this, text, checked, st::defaultBoxCheckbox); subscribe(child->checkedChanged, std::move(callback)); } void BlockWidget::createChildWidget( object_ptr<Ui::LinkButton> &child, style::margins &margin, const QString &text, const char *slot, const style::LinkButton &st) { child.create(this, text, st); connect(child, SIGNAL(clicked()), this, slot); } } // namespace Settings ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/settings/settings_block_widget.h�������������������������������0000664�0000000�0000000�00000007522�13262451251�0026017�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "base/observer.h" #include "ui/rp_widget.h" #include "styles/style_boxes.h" namespace Ui { class Checkbox; class RadiobuttonGroup; class Radiobutton; class LinkButton; template <typename Enum> class RadioenumGroup; template <typename Enum> class Radioenum; template <typename Widget> class SlideWrap; class VerticalLayout; } // namespace Ui namespace Settings { class BlockWidget : public Ui::RpWidget, protected base::Subscriber { public: BlockWidget(QWidget *parent, UserData *self, const QString &title); void setContentLeft(int contentLeft); QMargins getMargins() const override; protected: void paintEvent(QPaintEvent *e) override; virtual void paintContents(Painter &p) { } // Where does the block content start (after the title). int contentLeft() const { return _contentLeft; } int contentTop() const; // Resizes content and counts natural widget height for the desired width. int resizeGetHeight(int newWidth) override; UserData *self() const { return _self; } bool emptyTitle() const { return _title.isEmpty(); } template <typename Widget, typename ...Args> void createChildRow( Widget *&child, style::margins margin, Args&&... args) { auto row = object_ptr<Widget>{ nullptr }; createChildWidget(row, margin, std::forward<Args>(args)...); child = static_cast<Widget*>(addCreatedRow( std::move(row), margin)); } private: template <typename Widget, typename ...Args> void createChildWidget( object_ptr<Ui::SlideWrap<Widget>> &child, style::margins &margin, const style::margins &padding, Args&&... args) { object_ptr<Widget> entity = { nullptr }; createChildWidget(entity, margin, std::forward<Args>(args)...); child.create(this, std::move(entity), padding); margin.setLeft(margin.left() - padding.left()); margin.setTop(margin.top() - padding.top()); margin.setRight(margin.right() - padding.right()); margin.setBottom(margin.bottom() - padding.bottom()); } void createChildWidget( object_ptr<Ui::Checkbox> &child, style::margins &margin, const QString &text, base::lambda<void(bool checked)> callback, bool checked); void createChildWidget( object_ptr<Ui::LinkButton> &child, style::margins &margin, const QString &text, const char *slot, const style::LinkButton &st = st::boxLinkButton); template <typename Enum> void createChildWidget( object_ptr<Ui::Radioenum<Enum>> &child, style::margins &margin, const std::shared_ptr<Ui::RadioenumGroup<Enum>> &group, Enum value, const QString &text) { child.create( this, group, value, text, st::defaultBoxCheckbox); } RpWidget *addCreatedRow( object_ptr<RpWidget> row, const style::margins &margin); template <typename Widget> struct IsSlideWrap : std::false_type { }; template <typename Widget> struct IsSlideWrap<Ui::SlideWrap<Widget>> : std::true_type { }; template <typename Widget> struct IsRadioenum : std::false_type { }; template <typename Enum> struct IsRadioenum<Ui::Radioenum<Enum>> : std::true_type { }; template <typename Widget> using NotImplementedYet = std::enable_if_t< !IsSlideWrap<Widget>::value && !IsRadioenum<Widget>::value && !std::is_same<Ui::Checkbox, Widget>::value && !std::is_same<Ui::LinkButton, Widget>::value>; template <typename Widget, typename... Args, typename = NotImplementedYet<Widget>> void createChildWidget(object_ptr<Widget> &child, style::margins &margin, Args&&... args) { child.create(this, std::forward<Args>(args)...); } void paintTitle(Painter &p); object_ptr<Ui::VerticalLayout> _content; int _contentLeft = 0; UserData *_self; QString _title; }; } // namespace Settings ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/settings/settings_chat_settings_widget.cpp���������������������0000664�0000000�0000000�00000016305�13262451251�0030116�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "settings/settings_chat_settings_widget.h" #include "styles/style_settings.h" #include "lang/lang_keys.h" #include "ui/wrap/slide_wrap.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" #include "storage/localstorage.h" #include "mainwidget.h" #include "mainwindow.h" #include "boxes/stickers_box.h" #include "boxes/download_path_box.h" #include "boxes/connection_box.h" #include "boxes/confirm_box.h" namespace Settings { LabeledLink::LabeledLink(QWidget *parent, const QString &label, const QString &text, Type type, const char *slot) : RpWidget(parent) , _label(this, label, Ui::FlatLabel::InitType::Simple, (type == Type::Primary) ? st::settingsPrimaryLabel : st::defaultFlatLabel) , _link(this, text, (type == Type::Primary) ? st::boxLinkButton : st::defaultLinkButton) { connect(_link, SIGNAL(clicked()), parent, slot); } Ui::LinkButton *LabeledLink::link() const { return _link; } int LabeledLink::naturalWidth() const { return _label->naturalWidth() + st::normalFont->spacew + _link->naturalWidth(); } int LabeledLink::resizeGetHeight(int newWidth) { _label->moveToLeft(0, 0, newWidth); _link->resizeToWidth(newWidth - st::normalFont->spacew - _label->width()); _link->moveToLeft(_label->width() + st::normalFont->spacew, 0, newWidth); return _label->height(); } #ifndef OS_WIN_STORE DownloadPathState::DownloadPathState(QWidget *parent) : RpWidget(parent) , _path(this, lang(lng_download_path_label), downloadPathText(), LabeledLink::Type::Secondary, SLOT(onDownloadPath())) , _clear(this, lang(lng_download_path_clear)) { connect(_clear, SIGNAL(clicked()), this, SLOT(onClear())); connect(App::wnd(), SIGNAL(tempDirCleared(int)), this, SLOT(onTempDirCleared(int))); connect(App::wnd(), SIGNAL(tempDirClearFailed(int)), this, SLOT(onTempDirClearFailed(int))); subscribe(Global::RefDownloadPathChanged(), [this]() { _path->link()->setText(downloadPathText()); resizeToWidth(width()); }); switch (App::wnd()->tempDirState()) { case MainWindow::TempDirEmpty: _state = State::Empty; break; case MainWindow::TempDirExists: _state = State::Exists; break; case MainWindow::TempDirRemoving: _state = State::Clearing; break; } updateControls(); } int DownloadPathState::resizeGetHeight(int newWidth) { _path->resizeToWidth(qMin(newWidth, _path->naturalWidth())); _path->moveToLeft(0, 0, newWidth); _clear->moveToRight(0, 0, newWidth); return _path->height(); } void DownloadPathState::paintEvent(QPaintEvent *e) { Painter p(this); auto text = ([this]() -> QString { switch (_state) { case State::Clearing: return lang(lng_download_path_clearing); case State::Cleared: return lang(lng_download_path_cleared); case State::ClearFailed: return Lang::Hard::ClearPathFailed(); } return QString(); })(); if (!text.isEmpty()) { p.setFont(st::linkFont); p.setPen(st::windowFg); p.drawTextRight(0, 0, width(), text); } } void DownloadPathState::updateControls() { _clear->setVisible(_state == State::Exists); update(); } QString DownloadPathState::downloadPathText() const { if (Global::DownloadPath().isEmpty()) { return lang(lng_download_path_default); } else if (Global::DownloadPath() == qsl("tmp")) { return lang(lng_download_path_temp); } return QDir::toNativeSeparators(Global::DownloadPath()); }; void DownloadPathState::onDownloadPath() { Ui::show(Box<DownloadPathBox>()); } void DownloadPathState::onClear() { Ui::show(Box<ConfirmBox>(lang(lng_sure_clear_downloads), base::lambda_guarded(this, [this] { Ui::hideLayer(); App::wnd()->tempDirDelete(Local::ClearManagerDownloads); _state = State::Clearing; updateControls(); }))); } void DownloadPathState::onTempDirCleared(int task) { if (task & Local::ClearManagerDownloads) { _state = State::Cleared; } updateControls(); } void DownloadPathState::onTempDirClearFailed(int task) { if (task & Local::ClearManagerDownloads) { _state = State::ClearFailed; } updateControls(); } #endif // OS_WIN_STORE ChatSettingsWidget::ChatSettingsWidget(QWidget *parent, UserData *self) : BlockWidget(parent, self, lang(lng_settings_section_chat_settings)) { createControls(); } void ChatSettingsWidget::createControls() { style::margins marginSmall(0, 0, 0, st::settingsSmallSkip); style::margins marginSkip(0, 0, 0, st::settingsSkip); style::margins marginSub(0, 0, 0, st::settingsSubSkip); style::margins slidedPadding(0, marginSub.bottom() / 2, 0, marginSub.bottom() - (marginSub.bottom() / 2)); createChildRow(_replaceEmoji, marginSmall, lang(lng_settings_replace_emojis), [this](bool) { toggleReplaceEmoji(); }, cReplaceEmojis()); createChildRow(_suggestByEmoji, marginSkip, lang(lng_settings_suggest_by_emoji), [this](bool) { toggleSuggestStickersByEmoji(); }, Global::SuggestStickersByEmoji()); #ifndef OS_WIN_STORE auto pathMargin = marginSub; #else // OS_WIN_STORE auto pathMargin = marginSkip; #endif // OS_WIN_STORE createChildRow(_dontAskDownloadPath, pathMargin, lang(lng_download_path_dont_ask), [this](bool) { onDontAskDownloadPath(); }, !Global::AskDownloadPath()); #ifndef OS_WIN_STORE style::margins marginPath(st::defaultCheck.diameter + st::defaultBoxCheckbox.textPosition.x(), 0, 0, st::settingsSkip); createChildRow(_downloadPath, marginPath, slidedPadding); if (Global::AskDownloadPath()) { _downloadPath->hide(anim::type::instant); } #endif // OS_WIN_STORE auto group = std::make_shared<Ui::RadioenumGroup<SendByType>>(cCtrlEnter() ? SendByType::CtrlEnter : SendByType::Enter); createChildRow(_sendByEnter, marginSmall, group, SendByType::Enter, lang(lng_settings_send_enter)); createChildRow(_sendByCtrlEnter, marginSkip, group, SendByType::CtrlEnter, lang((cPlatform() == dbipMac || cPlatform() == dbipMacOld) ? lng_settings_send_cmdenter : lng_settings_send_ctrlenter)); group->setChangedCallback([this](SendByType value) { sendByChanged(value); }); createChildRow(_automaticMediaDownloadSettings, marginSmall, lang(lng_media_auto_settings), SLOT(onAutomaticMediaDownloadSettings())); createChildRow(_manageStickerSets, marginSmall, lang(lng_stickers_you_have), SLOT(onManageStickerSets())); } void ChatSettingsWidget::toggleReplaceEmoji() { cSetReplaceEmojis(_replaceEmoji->checked()); Local::writeUserSettings(); } void ChatSettingsWidget::toggleSuggestStickersByEmoji() { Global::SetSuggestStickersByEmoji(_suggestByEmoji->checked()); Local::writeUserSettings(); } void ChatSettingsWidget::onDontAskDownloadPath() { Global::SetAskDownloadPath(!_dontAskDownloadPath->checked()); Local::writeUserSettings(); #ifndef OS_WIN_STORE _downloadPath->toggle( _dontAskDownloadPath->checked(), anim::type::normal); #endif // OS_WIN_STORE } void ChatSettingsWidget::sendByChanged(SendByType value) { cSetCtrlEnter(value == SendByType::CtrlEnter); if (App::main()) App::main()->ctrlEnterSubmitUpdated(); Local::writeUserSettings(); } void ChatSettingsWidget::onAutomaticMediaDownloadSettings() { Ui::show(Box<AutoDownloadBox>()); } void ChatSettingsWidget::onManageStickerSets() { Ui::show(Box<StickersBox>(StickersBox::Section::Installed)); } } // namespace Settings ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/settings/settings_chat_settings_widget.h�����������������������0000664�0000000�0000000�00000004512�13262451251�0027560�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "settings/settings_block_widget.h" #include "ui/rp_widget.h" namespace Ui { class FlatLabel; } // namespace Ui namespace Settings { class LabeledLink : public Ui::RpWidget { public: enum class Type { Primary, Secondary, }; LabeledLink(QWidget *parent, const QString &label, const QString &text, Type type, const char *slot); Ui::LinkButton *link() const; int naturalWidth() const override; protected: int resizeGetHeight(int newWidth) override; private: object_ptr<Ui::FlatLabel> _label; object_ptr<Ui::LinkButton> _link; }; #ifndef OS_WIN_STORE class DownloadPathState : public Ui::RpWidget, private base::Subscriber { Q_OBJECT public: DownloadPathState(QWidget *parent); protected: int resizeGetHeight(int newWidth) override; void paintEvent(QPaintEvent *e) override; private slots: void onDownloadPath(); void onClear(); void onTempDirCleared(int task); void onTempDirClearFailed(int task); private: QString downloadPathText() const; void updateControls(); enum class State { Empty, Exists, Clearing, Cleared, ClearFailed, }; State _state = State::Empty; object_ptr<LabeledLink> _path; object_ptr<Ui::LinkButton> _clear; }; #endif // OS_WIN_STORE class ChatSettingsWidget : public BlockWidget { Q_OBJECT public: ChatSettingsWidget(QWidget *parent, UserData *self); private slots: void onDontAskDownloadPath(); void onAutomaticMediaDownloadSettings(); void onManageStickerSets(); private: enum class SendByType { Enter, CtrlEnter, }; void sendByChanged(SendByType value); void createControls(); void toggleReplaceEmoji(); void toggleSuggestStickersByEmoji(); Ui::Checkbox *_replaceEmoji = nullptr; Ui::Checkbox *_suggestByEmoji = nullptr; Ui::Checkbox *_dontAskDownloadPath = nullptr; #ifndef OS_WIN_STORE Ui::SlideWrap<DownloadPathState> *_downloadPath = nullptr; #endif // OS_WIN_STORE Ui::Radioenum<SendByType> *_sendByEnter = nullptr; Ui::Radioenum<SendByType> *_sendByCtrlEnter = nullptr; Ui::LinkButton *_automaticMediaDownloadSettings = nullptr; Ui::LinkButton *_manageStickerSets = nullptr; }; } // namespace Settings ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/settings/settings_cover.cpp������������������������������������0000664�0000000�0000000�00000025707�13262451251�0025040�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "settings/settings_cover.h" #include "data/data_photo.h" #include "data/data_session.h" #include "ui/widgets/labels.h" #include "ui/widgets/buttons.h" #include "ui/special_buttons.h" #include "observer_peer.h" #include "lang/lang_keys.h" #include "messenger.h" #include "mainwindow.h" #include "apiwrap.h" #include "auth_session.h" #include "profile/profile_cover_drop_area.h" #include "boxes/confirm_box.h" #include "boxes/photo_crop_box.h" #include "boxes/add_contact_box.h" #include "styles/style_settings.h" #include "styles/style_profile.h" // for divider #include "platform/platform_file_utilities.h" namespace Settings { CoverWidget::CoverWidget(QWidget *parent, UserData *self) : BlockWidget(parent, self, QString()) , _self(App::self()) , _userpicButton( this, App::wnd()->controller(), _self, Ui::UserpicButton::Role::OpenPhoto, st::settingsPhoto) , _name(this, st::settingsNameLabel) , _editNameInline(this, st::settingsEditButton) , _setPhoto(this, langFactory(lng_settings_upload), st::settingsPrimaryButton) , _editName(this, langFactory(lng_settings_edit), st::settingsSecondaryButton) { if (_self) { _self->updateFull(); } setAcceptDrops(true); _name->setSelectable(true); _name->setContextCopyText(lang(lng_profile_copy_fullname)); _setPhoto->setClickedCallback(App::LambdaDelayed( st::settingsPrimaryButton.ripple.hideDuration, this, [this] { chooseNewPhoto(); })); _editName->addClickHandler([this] { editName(); }); _editNameInline->addClickHandler([this] { editName(); }); auto observeEvents = Notify::PeerUpdate::Flag::NameChanged | Notify::PeerUpdate::Flag::PhotoChanged; subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(observeEvents, [this](const Notify::PeerUpdate &update) { notifyPeerUpdated(update); })); connect( &Messenger::Instance(), &Messenger::peerPhotoDone, this, &CoverWidget::onPhotoUploadStatusChanged); connect( &Messenger::Instance(), &Messenger::peerPhotoFail, this, &CoverWidget::onPhotoUploadStatusChanged); _userpicButton->addClickHandler([this] { showPhoto(); }); validatePhoto(); refreshNameText(); subscribe(Global::RefConnectionTypeChanged(), [this] { refreshStatusText(); }); refreshStatusText(); } PhotoData *CoverWidget::validatePhoto() const { Expects(_self != nullptr); const auto photo = _self->userpicPhotoId() ? Auth().data().photo(_self->userpicPhotoId()).get() : nullptr; _userpicButton->setPointerCursor(photo != nullptr && photo->date != 0); if (_self->userpicPhotoUnknown() || (photo && !photo->date)) { Auth().api().requestFullPeer(_self); return nullptr; } return photo; } void CoverWidget::showPhoto() { if (const auto photo = validatePhoto()) { Messenger::Instance().showPhoto(photo, _self); } } void CoverWidget::cancelPhotoUpload() { Messenger::Instance().cancelPhotoUpdate(_self->id); refreshStatusText(); } int CoverWidget::resizeGetHeight(int newWidth) { int newHeight = 0; newHeight += st::settingsMarginTop; auto margins = getMargins(); _userpicButton->moveToLeft( margins.left() + contentLeft() + st::settingsPhotoLeft, margins.top() + newHeight, newWidth); int infoLeft = _userpicButton->x() + _userpicButton->width(); _statusPosition = QPoint(infoLeft + st::settingsStatusLeft, _userpicButton->y() + st::settingsStatusTop); if (_cancelPhotoUpload) { _cancelPhotoUpload->moveToLeft( margins.left() + _statusPosition.x() + st::settingsStatusFont->width(_statusText) + st::settingsStatusFont->spacew, margins.top() + _statusPosition.y(), newWidth); } refreshButtonsGeometry(newWidth); refreshNameGeometry(newWidth); newHeight += st::settingsPhoto.size.height(); newHeight += st::settingsMarginBottom; _dividerTop = newHeight; newHeight += st::profileDividerLeft.height(); newHeight += st::settingsBlocksTop; resizeDropArea(); return newHeight; } void CoverWidget::refreshButtonsGeometry(int newWidth) { auto margins = getMargins(); auto buttonLeft = margins.left() + _userpicButton->x() + _userpicButton->width() + st::settingsButtonLeft; _setPhoto->moveToLeft( buttonLeft, margins.top() + _userpicButton->y() + st::settingsButtonTop, newWidth); buttonLeft += _setPhoto->width() + st::settingsButtonSkip; _editName->moveToLeft( buttonLeft, margins.top() + _setPhoto->y(), newWidth); _editNameVisible = (buttonLeft + _editName->width() + st::settingsButtonSkip <= newWidth); _editName->setVisible(_editNameVisible); } void CoverWidget::refreshNameGeometry(int newWidth) { auto margins = getMargins(); auto infoLeft = _userpicButton->x() + _userpicButton->width(); auto nameLeft = infoLeft + st::settingsNameLeft; auto nameTop = _userpicButton->y() + st::settingsNameTop; auto nameWidth = newWidth - infoLeft - st::settingsNameLeft; auto editNameInlineVisible = !_editNameVisible; if (editNameInlineVisible) { nameWidth -= _editNameInline->width(); } _name->resizeToNaturalWidth(nameWidth); _name->moveToLeft( margins.left() + nameLeft, margins.top() + nameTop, newWidth); _editNameInline->moveToLeft( margins.left() + nameLeft + _name->widthNoMargins() + st::settingsNameLabel.margin.right(), margins.top() + nameTop - st::settingsNameLabel.margin.top(), newWidth); _editNameInline->setVisible(editNameInlineVisible); } void CoverWidget::paintContents(Painter &p) { p.setFont(st::settingsStatusFont); p.setPen(_statusTextIsOnline ? st::settingsStatusFgActive : st::settingsStatusFg); p.drawTextLeft(_statusPosition.x(), _statusPosition.y(), width(), _statusText); paintDivider(p); } void CoverWidget::resizeDropArea() { if (_dropArea) { _dropArea->setGeometry(0, 0, width(), _dividerTop); } } void CoverWidget::dropAreaHidden(Profile::CoverDropArea *dropArea) { if (_dropArea == dropArea) { _dropArea.destroyDelayed(); } } bool CoverWidget::mimeDataHasImage(const QMimeData *mimeData) const { if (!mimeData) return false; if (mimeData->hasImage()) return true; auto uriListFormat = qsl("text/uri-list"); if (!mimeData->hasFormat(uriListFormat)) return false; const auto &urls = mimeData->urls(); if (urls.size() != 1) return false; auto &url = urls.at(0); if (!url.isLocalFile()) return false; auto file = Platform::File::UrlToLocal(url); QFileInfo info(file); if (info.isDir()) return false; if (info.size() > App::kImageSizeLimit) return false; for (auto &ext : cImgExtensions()) { if (file.endsWith(ext, Qt::CaseInsensitive)) { return true; } } return false; } void CoverWidget::dragEnterEvent(QDragEnterEvent *e) { if (!mimeDataHasImage(e->mimeData())) { e->ignore(); return; } if (!_dropArea) { auto title = lang(lng_profile_drop_area_title); auto subtitle = lang(lng_settings_drop_area_subtitle); _dropArea.create(this, title, subtitle); resizeDropArea(); } _dropArea->showAnimated(); e->setDropAction(Qt::CopyAction); e->accept(); } void CoverWidget::dragLeaveEvent(QDragLeaveEvent *e) { if (_dropArea && !_dropArea->hiding()) { _dropArea->hideAnimated([this](Profile::CoverDropArea *area) { dropAreaHidden(area); }); } } void CoverWidget::dropEvent(QDropEvent *e) { auto mimeData = e->mimeData(); QImage img; if (mimeData->hasImage()) { img = qvariant_cast<QImage>(mimeData->imageData()); } else { auto urls = mimeData->urls(); if (urls.size() == 1) { auto &url = urls.at(0); if (url.isLocalFile()) { img = App::readImage(Platform::File::UrlToLocal(url)); } } } if (!_dropArea->hiding()) { _dropArea->hideAnimated([this](Profile::CoverDropArea *area) { dropAreaHidden(area); }); } e->acceptProposedAction(); showSetPhotoBox(img); App::wnd()->activateWindow(); } void CoverWidget::paintDivider(Painter &p) { auto dividerHeight = st::profileDividerLeft.height(); auto divider = rtlrect(0, _dividerTop, width(), dividerHeight, width()); p.fillRect(divider, st::profileDividerBg); auto dividerFillTop = rtlrect(0, _dividerTop, width(), st::profileDividerTop.height(), width()); st::profileDividerTop.fill(p, dividerFillTop); auto dividerFillBottom = rtlrect(0, _dividerTop + dividerHeight - st::profileDividerBottom.height(), width(), st::profileDividerBottom.height(), width()); st::profileDividerBottom.fill(p, dividerFillBottom); } void CoverWidget::notifyPeerUpdated(const Notify::PeerUpdate &update) { if (update.peer != _self) { return; } if (update.flags & Notify::PeerUpdate::Flag::NameChanged) { refreshNameText(); } if (update.flags & Notify::PeerUpdate::Flag::PhotoChanged) { validatePhoto(); } } void CoverWidget::refreshNameText() { _name->setText(App::peerName(_self)); refreshNameGeometry(width()); } void CoverWidget::refreshStatusText() { if (Messenger::Instance().isPhotoUpdating(_self->id)) { _statusText = lang(lng_settings_uploading_photo); _statusTextIsOnline = false; if (!_cancelPhotoUpload) { auto margins = getMargins(); _cancelPhotoUpload.create(this, lang(lng_cancel), st::defaultLinkButton); _cancelPhotoUpload->addClickHandler([this] { cancelPhotoUpload(); }); _cancelPhotoUpload->show(); _cancelPhotoUpload->moveToLeft( margins.left() + _statusPosition.x() + st::settingsStatusFont->width(_statusText) + st::settingsStatusFont->spacew, margins.top() + _statusPosition.y()); } update(); return; } _cancelPhotoUpload.destroy(); auto state = MTP::dcstate(); if (state == MTP::ConnectingState || state == MTP::DisconnectedState || state < 0) { _statusText = lang(lng_status_connecting); _statusTextIsOnline = false; } else { _statusText = lang(lng_status_online); _statusTextIsOnline = true; } update(); } void CoverWidget::chooseNewPhoto() { auto imageExtensions = cImgExtensions(); auto filter = qsl("Image files (*") + imageExtensions.join(qsl(" *")) + qsl(");;") + FileDialog::AllFilesFilter(); FileDialog::GetOpenPath(lang(lng_choose_image), filter, base::lambda_guarded(this, [this](const FileDialog::OpenResult &result) { if (result.paths.isEmpty() && result.remoteContent.isEmpty()) { return; } QImage img; if (!result.remoteContent.isEmpty()) { img = App::readImage(result.remoteContent); } else { img = App::readImage(result.paths.front()); } showSetPhotoBox(img); })); } void CoverWidget::editName() { Ui::show(Box<EditNameBox>(self())); } void CoverWidget::showSetPhotoBox(const QImage &img) { if (img.isNull() || img.width() > 10 * img.height() || img.height() > 10 * img.width()) { Ui::show(Box<InformBox>(lang(lng_bad_photo))); return; } auto peer = _self; auto box = Ui::show(Box<PhotoCropBox>(img, peer)); box->ready( ) | rpl::start_with_next([=](QImage &&image) { Messenger::Instance().uploadProfilePhoto( std::move(image), peer->id); }, box->lifetime()); subscribe(box->boxClosing, [this] { onPhotoUploadStatusChanged(); }); } void CoverWidget::onPhotoUploadStatusChanged(PeerId peerId) { if (!peerId || peerId == _self->id) { refreshStatusText(); } } } // namespace Settings ���������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/settings/settings_cover.h��������������������������������������0000664�0000000�0000000�00000004137�13262451251�0024477�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "base/observer.h" #include "settings/settings_block_widget.h" namespace Ui { class FlatLabel; class RoundButton; class IconButton; class UserpicButton; } // namespace Ui namespace Notify { struct PeerUpdate; } // namespace Notify namespace Profile { class CoverDropArea; } // namespace Profile namespace Settings { class CoverWidget : public BlockWidget { public: CoverWidget(QWidget *parent, UserData *self); protected: void dragEnterEvent(QDragEnterEvent *e) override; void dragLeaveEvent(QDragLeaveEvent *e) override; void dropEvent(QDropEvent *e) override; void paintContents(Painter &p) override; // Resizes content and counts natural widget height for the desired width. int resizeGetHeight(int newWidth) override; private: // Observed notifications. void notifyPeerUpdated(const Notify::PeerUpdate &update); void showPhoto(); void cancelPhotoUpload(); void chooseNewPhoto(); void editName(); void onPhotoUploadStatusChanged(PeerId peerId = 0); PhotoData *validatePhoto() const; void refreshButtonsGeometry(int newWidth); void refreshNameGeometry(int newWidth); void refreshNameText(); void refreshStatusText(); void paintDivider(Painter &p); void showSetPhotoBox(const QImage &img); void resizeDropArea(); void dropAreaHidden(Profile::CoverDropArea *dropArea); bool mimeDataHasImage(const QMimeData *mimeData) const; UserData *_self; object_ptr<Ui::UserpicButton> _userpicButton; object_ptr<Profile::CoverDropArea> _dropArea = { nullptr }; object_ptr<Ui::FlatLabel> _name; object_ptr<Ui::IconButton> _editNameInline; object_ptr<Ui::LinkButton> _cancelPhotoUpload = { nullptr }; QPoint _statusPosition; QString _statusText; bool _statusTextIsOnline = false; object_ptr<Ui::RoundButton> _setPhoto; object_ptr<Ui::RoundButton> _editName; bool _editNameVisible = true; int _dividerTop = 0; }; } // namespace Settings ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/settings/settings_fixed_bar.cpp��������������������������������0000664�0000000�0000000�00000001731�13262451251�0025634�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "settings/settings_fixed_bar.h" #include "styles/style_settings.h" #include "styles/style_boxes.h" #include "mainwindow.h" namespace Settings { FixedBar::FixedBar(QWidget *parent) : TWidget(parent) { setAttribute(Qt::WA_OpaquePaintEvent); } void FixedBar::setText(const QString &text) { _text = text; update(); } int FixedBar::resizeGetHeight(int newWidth) { return st::settingsFixedBarHeight - st::boxRadius; } void FixedBar::paintEvent(QPaintEvent *e) { Painter p(this); p.fillRect(e->rect(), st::boxBg); p.setFont(st::settingsFixedBarFont); p.setPen(st::windowFg); p.drawTextLeft(st::settingsFixedBarTextPosition.x(), st::settingsFixedBarTextPosition.y() - st::boxRadius, width(), _text); } } // namespace Settings ���������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/settings/settings_fixed_bar.h����������������������������������0000664�0000000�0000000�00000001117�13262451251�0025277�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace Ui { class IconButton; } // namespace Ui namespace Settings { class FixedBar : public TWidget { public: FixedBar(QWidget *parent); void setText(const QString &text); protected: void paintEvent(QPaintEvent *e) override; int resizeGetHeight(int newWidth) override; private: QString _text; }; } // namespace Settings �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/settings/settings_general_widget.cpp���������������������������0000664�0000000�0000000�00000024253�13262451251�0026675�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "settings/settings_general_widget.h" #include "styles/style_settings.h" #include "lang/lang_keys.h" #include "ui/wrap/slide_wrap.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/buttons.h" #include "storage/localstorage.h" #include "platform/platform_specific.h" #include "mainwindow.h" #include "application.h" #include "boxes/language_box.h" #include "boxes/confirm_box.h" #include "boxes/about_box.h" #include "core/file_utilities.h" #include "lang/lang_file_parser.h" #include "lang/lang_cloud_manager.h" #include "messenger.h" #include "autoupdater.h" namespace Settings { #ifndef TDESKTOP_DISABLE_AUTOUPDATE UpdateStateRow::UpdateStateRow(QWidget *parent) : RpWidget(parent) , _check(this, lang(lng_settings_check_now)) , _restart(this, lang(lng_settings_update_now)) { connect(_check, SIGNAL(clicked()), this, SLOT(onCheck())); connect(_restart, SIGNAL(clicked()), this, SIGNAL(restart())); Sandbox::connect(SIGNAL(updateChecking()), this, SLOT(onChecking())); Sandbox::connect(SIGNAL(updateLatest()), this, SLOT(onLatest())); Sandbox::connect(SIGNAL(updateProgress(qint64, qint64)), this, SLOT(onDownloading(qint64, qint64))); Sandbox::connect(SIGNAL(updateFailed()), this, SLOT(onFailed())); Sandbox::connect(SIGNAL(updateReady()), this, SLOT(onReady())); _versionText = lng_settings_current_version_label(lt_version, currentVersionText()); switch (Sandbox::updatingState()) { case Application::UpdatingDownload: setState(State::Download, true); setDownloadProgress(Sandbox::updatingReady(), Sandbox::updatingSize()); break; case Application::UpdatingReady: setState(State::Ready, true); break; default: setState(State::None, true); break; } } int UpdateStateRow::resizeGetHeight(int newWidth) { auto labelWidth = [](const QString &label) { return st::linkFont->width(label) + st::linkFont->spacew; }; auto checkLeft = (_state == State::Latest) ? labelWidth(lang(lng_settings_latest_installed)) : labelWidth(_versionText); auto restartLeft = labelWidth(lang(lng_settings_update_ready)); _check->resizeToWidth(qMin(newWidth, _check->naturalWidth())); _check->moveToLeft(checkLeft, 0, newWidth); _restart->resizeToWidth(qMin(newWidth, _restart->naturalWidth())); _restart->moveToLeft(restartLeft, 0, newWidth); return _check->height(); } void UpdateStateRow::paintEvent(QPaintEvent *e) { Painter p(this); auto text = ([this]() -> QString { switch (_state) { case State::Check: return lang(lng_settings_update_checking); case State::Latest: return lang(lng_settings_latest_installed); case State::Download: return _downloadText; case State::Ready: return lang(lng_settings_update_ready); case State::Fail: return lang(lng_settings_update_fail); default: return _versionText; } })(); p.setFont(st::linkFont); p.setPen((_state == State::None) ? st::windowFg : st::settingsUpdateFg); p.drawTextLeft(0, 0, width(), text); } void UpdateStateRow::onCheck() { if (!cAutoUpdate()) return; setState(State::Check); cSetLastUpdateCheck(0); Sandbox::startUpdateCheck(); } void UpdateStateRow::setState(State state, bool force) { if (_state != state || force) { _state = state; switch (state) { case State::None: _check->show(); _restart->hide(); break; case State::Ready: _check->hide(); _restart->show(); break; case State::Check: case State::Download: case State::Latest: case State::Fail: _check->hide(); _restart->hide(); break; } resizeToWidth(width()); sendSynteticMouseEvent(this, QEvent::MouseMove, Qt::NoButton); update(); } } void UpdateStateRow::setDownloadProgress(qint64 ready, qint64 total) { auto readyTenthMb = (ready * 10 / (1024 * 1024)), totalTenthMb = (total * 10 / (1024 * 1024)); auto readyStr = QString::number(readyTenthMb / 10) + '.' + QString::number(readyTenthMb % 10); auto totalStr = QString::number(totalTenthMb / 10) + '.' + QString::number(totalTenthMb % 10); auto result = lng_settings_downloading(lt_ready, readyStr, lt_total, totalStr); if (_downloadText != result) { _downloadText = result; update(); } } void UpdateStateRow::onChecking() { setState(State::Check); } void UpdateStateRow::onLatest() { setState(State::Latest); } void UpdateStateRow::onDownloading(qint64 ready, qint64 total) { setState(State::Download); setDownloadProgress(ready, total); } void UpdateStateRow::onReady() { setState(State::Ready); } void UpdateStateRow::onFailed() { setState(State::Fail); } #endif // !TDESKTOP_DISABLE_AUTOUPDATE GeneralWidget::GeneralWidget(QWidget *parent, UserData *self) : BlockWidget(parent, self, lang(lng_settings_section_general)) , _changeLanguage(this, lang(lng_settings_change_lang), st::boxLinkButton) { connect(_changeLanguage, SIGNAL(clicked()), this, SLOT(onChangeLanguage())); refreshControls(); } int GeneralWidget::resizeGetHeight(int newWidth) { _changeLanguage->moveToRight(0, st::settingsBlockMarginTop + st::settingsBlockTitleTop + st::settingsBlockTitleFont->ascent - st::defaultLinkButton.font->ascent, newWidth); return BlockWidget::resizeGetHeight(newWidth); } void GeneralWidget::refreshControls() { style::margins marginSub(0, 0, 0, st::settingsSubSkip); style::margins marginLarge(0, 0, 0, st::settingsLargeSkip); style::margins marginSmall(0, 0, 0, st::settingsSmallSkip); style::margins slidedPadding(0, marginSmall.bottom() / 2, 0, marginSmall.bottom() - (marginSmall.bottom() / 2)); #ifndef TDESKTOP_DISABLE_AUTOUPDATE createChildRow(_updateAutomatically, marginSub, lang(lng_settings_update_automatically), [this](bool) { onUpdateAutomatically(); }, cAutoUpdate()); style::margins marginLink(st::defaultCheck.diameter + st::defaultBoxCheckbox.textPosition.x(), 0, 0, st::settingsSkip); createChildRow(_updateRow, marginLink, slidedPadding); connect(_updateRow->entity(), SIGNAL(restart()), this, SLOT(onRestart())); if (!cAutoUpdate()) { _updateRow->hide(anim::type::instant); } #endif // !TDESKTOP_DISABLE_AUTOUPDATE if (cPlatform() == dbipWindows || cSupportTray()) { auto workMode = Global::WorkMode().value(); createChildRow(_enableTrayIcon, marginSmall, lang(lng_settings_workmode_tray), [this](bool) { onEnableTrayIcon(); }, (workMode == dbiwmTrayOnly || workMode == dbiwmWindowAndTray)); if (cPlatform() == dbipWindows) { createChildRow(_enableTaskbarIcon, marginLarge, lang(lng_settings_workmode_window), [this](bool) { onEnableTaskbarIcon(); }, (workMode == dbiwmWindowOnly || workMode == dbiwmWindowAndTray)); #ifndef OS_WIN_STORE createChildRow(_autoStart, marginSmall, lang(lng_settings_auto_start), [this](bool) { onAutoStart(); }, cAutoStart()); createChildRow(_startMinimized, marginLarge, slidedPadding, lang(lng_settings_start_min), [this](bool) { onStartMinimized(); }, (cStartMinimized() && !Global::LocalPasscode())); subscribe(Global::RefLocalPasscodeChanged(), [this] { _startMinimized->entity()->setChecked(cStartMinimized() && !Global::LocalPasscode()); }); if (!cAutoStart()) { _startMinimized->hide(anim::type::instant); } createChildRow(_addInSendTo, marginSmall, lang(lng_settings_add_sendto), [this](bool) { onAddInSendTo(); }, cSendToMenu()); #endif // OS_WIN_STORE } } } void GeneralWidget::onChangeLanguage() { if ((_changeLanguage->clickModifiers() & Qt::ShiftModifier) && (_changeLanguage->clickModifiers() & Qt::AltModifier)) { Lang::CurrentCloudManager().switchToLanguage(qsl("custom")); return; } auto manager = Messenger::Instance().langCloudManager(); if (manager->languageList().isEmpty()) { _languagesLoadedSubscription = subscribe(manager->languageListChanged(), [this] { unsubscribe(base::take(_languagesLoadedSubscription)); Ui::show(Box<LanguageBox>()); }); } else { unsubscribe(base::take(_languagesLoadedSubscription)); Ui::show(Box<LanguageBox>()); } manager->requestLanguageList(); } void GeneralWidget::onRestart() { #ifndef TDESKTOP_DISABLE_AUTOUPDATE checkReadyUpdate(); #endif // !TDESKTOP_DISABLE_AUTOUPDATE App::restart(); } #ifndef TDESKTOP_DISABLE_AUTOUPDATE void GeneralWidget::onUpdateAutomatically() { cSetAutoUpdate(_updateAutomatically->checked()); Local::writeSettings(); _updateRow->toggle( cAutoUpdate(), anim::type::normal); if (cAutoUpdate()) { Sandbox::startUpdateCheck(); } else { Sandbox::stopUpdate(); } } #endif // !TDESKTOP_DISABLE_AUTOUPDATE void GeneralWidget::onEnableTrayIcon() { if ((!_enableTrayIcon->checked() || cPlatform() != dbipWindows) && _enableTaskbarIcon && !_enableTaskbarIcon->checked()) { _enableTaskbarIcon->setChecked(true); } else { updateWorkmode(); } } void GeneralWidget::onEnableTaskbarIcon() { if (!_enableTrayIcon->checked() && !_enableTaskbarIcon->checked()) { _enableTrayIcon->setChecked(true); } else { updateWorkmode(); } } void GeneralWidget::updateWorkmode() { auto newMode = (_enableTrayIcon->checked() && (!_enableTaskbarIcon || _enableTaskbarIcon->checked())) ? dbiwmWindowAndTray : (_enableTrayIcon->checked() ? dbiwmTrayOnly : dbiwmWindowOnly); if (Global::WorkMode().value() != newMode && (newMode == dbiwmWindowAndTray || newMode == dbiwmTrayOnly)) { cSetSeenTrayTooltip(false); } Global::RefWorkMode().set(newMode); Local::writeSettings(); } #if !defined OS_WIN_STORE void GeneralWidget::onAutoStart() { cSetAutoStart(_autoStart->checked()); if (cAutoStart()) { psAutoStart(true); Local::writeSettings(); } else { psAutoStart(false); if (_startMinimized->entity()->checked()) { _startMinimized->entity()->setChecked(false); } else { Local::writeSettings(); } } _startMinimized->toggle(cAutoStart(), anim::type::normal); } void GeneralWidget::onStartMinimized() { auto checked = _startMinimized->entity()->checked(); if (Global::LocalPasscode()) { if (checked) { _startMinimized->entity()->setChecked(false); Ui::show(Box<InformBox>(lang(lng_error_start_minimized_passcoded))); } return; } if (cStartMinimized() != checked) { cSetStartMinimized(checked); Local::writeSettings(); } } void GeneralWidget::onAddInSendTo() { cSetSendToMenu(_addInSendTo->checked()); psSendToMenu(_addInSendTo->checked()); Local::writeSettings(); } #endif // !OS_WIN_STORE } // namespace Settings �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/settings/settings_general_widget.h�����������������������������0000664�0000000�0000000�00000004376�13262451251�0026346�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "settings/settings_block_widget.h" #include "ui/rp_widget.h" namespace Settings { #ifndef TDESKTOP_DISABLE_AUTOUPDATE class UpdateStateRow : public Ui::RpWidget { Q_OBJECT public: UpdateStateRow(QWidget *parent); bool isUpdateReady() const { return (_state == State::Ready); } protected: int resizeGetHeight(int newWidth) override; void paintEvent(QPaintEvent *e) override; signals: void restart(); private slots: void onCheck(); void onChecking(); void onLatest(); void onDownloading(qint64 ready, qint64 total); void onReady(); void onFailed(); private: enum class State { None, Check, Latest, Download, Fail, Ready }; void setState(State state, bool force = false); void setDownloadProgress(qint64 ready, qint64 total); object_ptr<Ui::LinkButton> _check; object_ptr<Ui::LinkButton> _restart; State _state = State::None; QString _downloadText; QString _versionText; }; #endif // !TDESKTOP_DISABLE_AUTOUPDATE class GeneralWidget : public BlockWidget { Q_OBJECT public: GeneralWidget(QWidget *parent, UserData *self); protected: int resizeGetHeight(int newWidth) override; private slots: void onChangeLanguage(); #ifndef TDESKTOP_DISABLE_AUTOUPDATE void onUpdateAutomatically(); #endif // !TDESKTOP_DISABLE_AUTOUPDATE void onEnableTrayIcon(); void onEnableTaskbarIcon(); #ifndef OS_WIN_STORE void onAutoStart(); void onStartMinimized(); void onAddInSendTo(); #endif // !OS_WIN_STORE void onRestart(); private: void refreshControls(); void updateWorkmode(); object_ptr<Ui::LinkButton> _changeLanguage; #ifndef TDESKTOP_DISABLE_AUTOUPDATE Ui::Checkbox *_updateAutomatically = nullptr; Ui::SlideWrap<UpdateStateRow> *_updateRow = nullptr; #endif // !TDESKTOP_DISABLE_AUTOUPDATE Ui::Checkbox *_enableTrayIcon = nullptr; Ui::Checkbox *_enableTaskbarIcon = nullptr; Ui::Checkbox *_autoStart = nullptr; Ui::SlideWrap<Ui::Checkbox> *_startMinimized = nullptr; Ui::Checkbox *_addInSendTo = nullptr; int _languagesLoadedSubscription = 0; }; } // namespace Settings ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/settings/settings_info_widget.cpp������������������������������0000664�0000000�0000000�00000015462�13262451251�0026215�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "settings/settings_info_widget.h" #include "styles/style_settings.h" #include "lang/lang_keys.h" #include "ui/widgets/labels.h" #include "ui/wrap/slide_wrap.h" #include "boxes/username_box.h" #include "boxes/add_contact_box.h" #include "boxes/change_phone_box.h" #include "data/data_session.h" #include "observer_peer.h" #include "messenger.h" #include "auth_session.h" namespace Settings { using UpdateFlag = Notify::PeerUpdate::Flag; InfoWidget::InfoWidget(QWidget *parent, UserData *self) : BlockWidget(parent, self, lang(lng_settings_section_info)) { auto observeEvents = UpdateFlag::UsernameChanged | UpdateFlag::UserPhoneChanged | UpdateFlag::AboutChanged; subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(observeEvents, [this](const Notify::PeerUpdate &update) { notifyPeerUpdated(update); })); createControls(); } void InfoWidget::createControls() { style::margins margin(0, 0, 0, 0); style::margins slidedPadding(0, 0, 0, 0); createChildRow(_mobileNumber, margin, slidedPadding, st::settingsBlockOneLineTextPart); createChildRow(_username, margin, slidedPadding, st::settingsBlockOneLineTextPart); createChildRow(_bio, margin, slidedPadding, st::settingsBioValue); refreshControls(); } void InfoWidget::refreshControls() { refreshMobileNumber(); refreshUsername(); refreshBio(); } void InfoWidget::refreshMobileNumber() { TextWithEntities phoneText; if (const auto user = self()->asUser()) { phoneText.text = Auth().data().findContactPhone(user); } setLabeledText( _mobileNumber, lang(lng_profile_mobile_number), phoneText, TextWithEntities(), lang(lng_profile_copy_phone)); if (auto text = _mobileNumber->entity()->textLabel()) { text->setRichText(textcmdLink(1, phoneText.text)); text->setLink(1, std::make_shared<LambdaClickHandler>([] { Ui::show(Box<ChangePhoneBox>()); })); } } void InfoWidget::refreshUsername() { TextWithEntities usernameText; QString copyText; if (self()->username.isEmpty()) { usernameText.text = lang(lng_settings_choose_username); } else { usernameText.text = '@' + self()->username; copyText = lang(lng_context_copy_mention); } usernameText.entities.push_back(EntityInText( EntityInTextCustomUrl, 0, usernameText.text.size(), Messenger::Instance().createInternalLinkFull( self()->username))); setLabeledText( _username, lang(lng_profile_username), usernameText, TextWithEntities(), copyText); if (auto text = _username->entity()->textLabel()) { text->setClickHandlerHook([](const ClickHandlerPtr &handler, Qt::MouseButton button) { Ui::show(Box<UsernameBox>()); return false; }); } } void InfoWidget::refreshBio() { TextWithEntities bioText; auto aboutText = self()->about(); if (self()->about().isEmpty()) { bioText.text = lang(lng_settings_empty_bio); } else { bioText.text = aboutText; } bioText.entities.push_back(EntityInText( EntityInTextCustomUrl, 0, bioText.text.size(), QString("internal:edit_bio"))); setLabeledText( _bio, lang(lng_profile_bio), bioText, TextWithEntities(), QString()); if (auto text = _bio->entity()->textLabel()) { text->setClickHandlerHook([](const ClickHandlerPtr &handler, Qt::MouseButton button) { Ui::show(Box<EditBioBox>(App::self())); return false; }); } } void InfoWidget::setLabeledText( LabeledWrap *row, const QString &label, const TextWithEntities &textWithEntities, const TextWithEntities &shortTextWithEntities, const QString ©Text) { auto nonEmptyText = !textWithEntities.text.isEmpty(); if (nonEmptyText) { row->entity()->setLabeledText( label, textWithEntities, shortTextWithEntities, copyText, width()); } row->toggle(nonEmptyText, anim::type::normal); } InfoWidget::LabeledWidget::LabeledWidget(QWidget *parent, const style::FlatLabel &valueSt) : RpWidget(parent) , _valueSt(valueSt) { } void InfoWidget::LabeledWidget::setLabeledText( const QString &label, const TextWithEntities &textWithEntities, const TextWithEntities &shortTextWithEntities, const QString ©Text, int availableWidth) { _label.destroy(); _text.destroy(); _shortText.destroy(); if (textWithEntities.text.isEmpty()) return; _label.create(this, label, Ui::FlatLabel::InitType::Simple, st::settingsBlockLabel); _label->show(); setLabelText(_text, textWithEntities, copyText); setLabelText(_shortText, shortTextWithEntities, copyText); resizeToNaturalWidth(availableWidth); } Ui::FlatLabel *InfoWidget::LabeledWidget::textLabel() const { return _text; } Ui::FlatLabel *InfoWidget::LabeledWidget::shortTextLabel() const { return _shortText; } void InfoWidget::LabeledWidget::setLabelText(object_ptr<Ui::FlatLabel> &text, const TextWithEntities &textWithEntities, const QString ©Text) { text.destroy(); if (textWithEntities.text.isEmpty()) return; text.create(this, QString(), Ui::FlatLabel::InitType::Simple, _valueSt); text->show(); text->setMarkedText(textWithEntities); text->setContextCopyText(copyText); text->setSelectable(true); text->setDoubleClickSelectsParagraph(true); } void InfoWidget::notifyPeerUpdated(const Notify::PeerUpdate &update) { if (update.peer != self()) { return; } if (update.flags & UpdateFlag::UsernameChanged) { refreshUsername(); } if (update.flags & (UpdateFlag::UserPhoneChanged)) { refreshMobileNumber(); } if (update.flags & UpdateFlag::AboutChanged) { refreshBio(); } } int InfoWidget::LabeledWidget::naturalWidth() const { if (!_text) return -1; return _label->naturalWidth() + st::normalFont->spacew + _text->naturalWidth(); } int InfoWidget::LabeledWidget::resizeGetHeight(int newWidth) { if (!_label) return 0; _label->moveToLeft( 0, st::settingsBlockOneLineTextPart.margin.top(), newWidth); _label->resizeToNaturalWidth(newWidth); int textLeft = _label->width() + st::normalFont->spacew; int textWidth = _text->naturalWidth(); int availableWidth = newWidth - textLeft; bool doesNotFit = (textWidth > availableWidth); accumulate_min(textWidth, availableWidth); accumulate_min(textWidth, st::msgMaxWidth); if (textWidth < 0) { textWidth = 0; } _text->resizeToWidth(textWidth); _text->moveToLeft( textLeft, st::settingsBlockOneLineTextPart.margin.top(), newWidth); if (_shortText) { _shortText->resizeToWidth(textWidth); _shortText->moveToLeft( textLeft, st::settingsBlockOneLineTextPart.margin.top(), newWidth); if (doesNotFit) { _shortText->show(); _text->hide(); } else { _shortText->hide(); _text->show(); } } return st::settingsBlockOneLineTextPart.margin.top() + qMax(_label->heightNoMargins(), _text->heightNoMargins()) + st::settingsBlockOneLineTextPart.margin.bottom(); } } // namespace Settings ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/settings/settings_info_widget.h��������������������������������0000664�0000000�0000000�00000003671�13262451251�0025661�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "settings/settings_block_widget.h" #include "ui/rp_widget.h" namespace Ui { class FlatLabel; } // namespace Ui namespace Notify { struct PeerUpdate; } // namespace Notify namespace Settings { class InfoWidget : public BlockWidget { public: InfoWidget(QWidget *parent, UserData *self); private: // Observed notifications. void notifyPeerUpdated(const Notify::PeerUpdate &update); void createControls(); void refreshControls(); void refreshMobileNumber(); void refreshUsername(); void refreshBio(); class LabeledWidget : public Ui::RpWidget { public: LabeledWidget(QWidget *parent, const style::FlatLabel &valueSt); void setLabeledText( const QString &label, const TextWithEntities &textWithEntities, const TextWithEntities &shortTextWithEntities, const QString ©Text, int availableWidth); Ui::FlatLabel *textLabel() const; Ui::FlatLabel *shortTextLabel() const; int naturalWidth() const override; protected: int resizeGetHeight(int newWidth) override; private: void setLabelText( object_ptr<Ui::FlatLabel> &text, const TextWithEntities &textWithEntities, const QString ©Text); const style::FlatLabel &_valueSt; object_ptr<Ui::FlatLabel> _label = { nullptr }; object_ptr<Ui::FlatLabel> _text = { nullptr }; object_ptr<Ui::FlatLabel> _shortText = { nullptr }; }; using LabeledWrap = Ui::SlideWrap<LabeledWidget>; void setLabeledText( LabeledWrap *row, const QString &label, const TextWithEntities &textWithEntities, const TextWithEntities &shortTextWithEntities, const QString ©Text); LabeledWrap *_mobileNumber = nullptr; LabeledWrap *_username = nullptr; LabeledWrap *_bio = nullptr; }; } // namespace Settings �����������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/settings/settings_inner_widget.cpp�����������������������������0000664�0000000�0000000�00000005256�13262451251�0026375�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "settings/settings_inner_widget.h" #include "lang/lang_instance.h" #include "styles/style_settings.h" #include "settings/settings_cover.h" #include "settings/settings_block_widget.h" #include "settings/settings_info_widget.h" #include "settings/settings_notifications_widget.h" #include "settings/settings_general_widget.h" #include "settings/settings_chat_settings_widget.h" #include "settings/settings_scale_widget.h" #include "settings/settings_background_widget.h" #include "settings/settings_privacy_widget.h" #include "settings/settings_advanced_widget.h" namespace Settings { InnerWidget::InnerWidget(QWidget *parent) : LayerInner(parent) , _blocks(this) , _self(App::self()) { refreshBlocks(); subscribe(Global::RefSelfChanged(), [this] { fullRebuild(); }); subscribe(Lang::Current().updated(), [this] { fullRebuild(); }); } void InnerWidget::fullRebuild() { _self = App::self(); refreshBlocks(); } void InnerWidget::refreshBlocks() { if (App::quitting()) { _cover.destroy(); _blocks.destroy(); return; } _cover = _self ? object_ptr<CoverWidget>(this, _self) : nullptr; _blocks = object_ptr<Ui::VerticalLayout>(this); resizeToWidth(width(), _contentLeft); if (_self) { _blocks->add(object_ptr<InfoWidget>(this, _self)); _blocks->add(object_ptr<NotificationsWidget>(this, _self)); } _blocks->add(object_ptr<GeneralWidget>(this, _self)); if (!cRetina()) { _blocks->add(object_ptr<ScaleWidget>(this, _self)); } if (_self) { _blocks->add(object_ptr<ChatSettingsWidget>(this, _self)); _blocks->add(object_ptr<BackgroundWidget>(this, _self)); _blocks->add(object_ptr<PrivacyWidget>(this, _self)); } _blocks->add(object_ptr<AdvancedWidget>(this, _self)); if (_cover) { _cover->show(); } _blocks->show(); _blocks->heightValue( ) | rpl::start_with_next([this](int blocksHeight) { resize(width(), _blocks->y() + blocksHeight); }, lifetime()); } int InnerWidget::resizeGetHeight(int newWidth) { if (_cover) { _cover->setContentLeft(_contentLeft); _cover->resizeToWidth(newWidth); } _blocks->resizeToWidth(newWidth - 2 * _contentLeft); _blocks->moveToLeft( _contentLeft, (_cover ? (_cover->y() + _cover->height()) : 0) + st::settingsBlocksTop); return height(); } void InnerWidget::visibleTopBottomUpdated( int visibleTop, int visibleBottom) { setChildVisibleTopBottom( _cover, visibleTop, visibleBottom); setChildVisibleTopBottom( _blocks, visibleTop, visibleBottom); } } // namespace Settings ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/settings/settings_inner_widget.h�������������������������������0000664�0000000�0000000�00000002036�13262451251�0026033�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "settings/settings_layer.h" #include "ui/wrap/vertical_layout.h" namespace Settings { class CoverWidget; class BlockWidget; class InnerWidget : public LayerInner, private base::Subscriber { public: InnerWidget(QWidget *parent); // Count new height for width=newWidth and resize to it. void resizeToWidth(int newWidth, int contentLeft) override { _contentLeft = contentLeft; return TWidget::resizeToWidth(newWidth); } protected: int resizeGetHeight(int newWidth) override; void visibleTopBottomUpdated( int visibleTop, int visibleBottom) override; private: void fullRebuild(); void refreshBlocks(); object_ptr<CoverWidget> _cover = { nullptr }; object_ptr<Ui::VerticalLayout> _blocks; UserData *_self = nullptr; int _contentLeft = 0; }; } // namespace Settings ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/settings/settings_layer.cpp������������������������������������0000664�0000000�0000000�00000007137�13262451251�0025033�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "settings/settings_layer.h" #include <rpl/mappers.h> #include "settings/settings_inner_widget.h" #include "settings/settings_fixed_bar.h" #include "styles/style_settings.h" #include "styles/style_window.h" #include "styles/style_boxes.h" #include "ui/widgets/scroll_area.h" #include "ui/widgets/buttons.h" #include "ui/widgets/shadow.h" #include "ui/wrap/fade_wrap.h" #include "mainwindow.h" #include "mainwidget.h" #include "storage/localstorage.h" #include "boxes/confirm_box.h" #include "application.h" #include "core/file_utilities.h" #include "window/themes/window_theme.h" namespace Settings { Layer::Layer() : _scroll(this, st::settingsScroll) , _fixedBar(this) , _fixedBarClose(this, st::settingsFixedBarClose) , _fixedBarShadow(this) { _fixedBar->moveToLeft(0, st::boxRadius); _fixedBarClose->moveToRight(0, 0); _fixedBarShadow->entity()->resize(width(), st::lineWidth); _fixedBarShadow->moveToLeft(0, _fixedBar->y() + _fixedBar->height()); _fixedBarShadow->hide(anim::type::instant); _scroll->moveToLeft(0, st::settingsFixedBarHeight); using namespace rpl::mappers; _fixedBarShadow->toggleOn(_scroll->scrollTopValue() | rpl::map(_1 > 0)); } void Layer::setCloseClickHandler(base::lambda<void()> callback) { _fixedBarClose->setClickedCallback(std::move(callback)); } void Layer::resizeToWidth(int newWidth, int newContentLeft) { // Widget height depends on InnerWidget height, so we // resize it here, not in the resizeEvent() handler. _inner->resizeToWidth(newWidth, newContentLeft); resizeUsingInnerHeight(newWidth, _inner->height()); } void Layer::doSetInnerWidget(object_ptr<LayerInner> widget) { _inner = _scroll->setOwnedWidget(std::move(widget)); _inner->heightValue( ) | rpl::start_with_next([this](int innerHeight) { resizeUsingInnerHeight(width(), innerHeight); }, lifetime()); } void Layer::paintEvent(QPaintEvent *e) { Painter p(this); auto clip = e->rect(); if (_roundedCorners) { auto paintTopRounded = clip.intersects(QRect(0, 0, width(), st::boxRadius)); auto paintBottomRounded = clip.intersects(QRect(0, height() - st::boxRadius, width(), st::boxRadius)); if (paintTopRounded || paintBottomRounded) { auto parts = RectPart::None | 0; if (paintTopRounded) parts |= RectPart::FullTop; if (paintBottomRounded) parts |= RectPart::FullBottom; App::roundRect(p, rect(), st::boxBg, BoxCorners, nullptr, parts); } auto other = clip.intersected(QRect(0, st::boxRadius, width(), height() - 2 * st::boxRadius)); if (!other.isEmpty()) { p.fillRect(other, st::boxBg); } } else { p.fillRect(e->rect(), st::boxBg); } } void Layer::resizeEvent(QResizeEvent *e) { LayerWidget::resizeEvent(e); if (!width() || !height()) { return; } _fixedBar->resizeToWidth(width()); _fixedBar->moveToLeft(0, st::boxRadius); _fixedBar->update(); _fixedBarClose->moveToRight(0, 0); auto shadowTop = _fixedBar->y() + _fixedBar->height(); _fixedBarShadow->entity()->resize(width(), st::lineWidth); _fixedBarShadow->moveToLeft(0, shadowTop); auto scrollSize = QSize(width(), height() - shadowTop - (_roundedCorners ? st::boxRadius : 0)); if (_scroll->size() != scrollSize) { _scroll->resize(scrollSize); } if (!_scroll->isHidden()) { auto scrollTop = _scroll->scrollTop(); _inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height()); } } void Layer::setTitle(const QString &title) { _fixedBar->setText(title); } } // namespace Settings ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/settings/settings_layer.h��������������������������������������0000664�0000000�0000000�00000003175�13262451251�0024476�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "window/layer_widget.h" #include "ui/rp_widget.h" namespace Ui { class ScrollArea; class IconButton; class FadeShadow; } // namespace Ui namespace Settings { class FixedBar; class LayerInner : public Ui::RpWidget { public: LayerInner(QWidget *parent) : RpWidget(parent) { } virtual void resizeToWidth(int newWidth, int contentLeft) { TWidget::resizeToWidth(newWidth); } }; class Layer : public Window::LayerWidget { public: Layer(); void setCloseClickHandler(base::lambda<void()> callback); void resizeToWidth(int newWidth, int newContentLeft); protected: void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; template <typename Widget> QPointer<Widget> setInnerWidget(object_ptr<Widget> widget) { auto result = QPointer<Widget>(widget); doSetInnerWidget(std::move(widget)); return result; } void setTitle(const QString &title); void setRoundedCorners(bool roundedCorners) { _roundedCorners = roundedCorners; } private: void doSetInnerWidget(object_ptr<LayerInner> widget); virtual void resizeUsingInnerHeight(int newWidth, int innerHeight) { resize(newWidth, height()); } object_ptr<Ui::ScrollArea> _scroll; QPointer<LayerInner> _inner; object_ptr<FixedBar> _fixedBar; object_ptr<Ui::IconButton> _fixedBarClose; object_ptr<Ui::FadeShadow> _fixedBarShadow; bool _roundedCorners = false; }; } // namespace Settings ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/settings/settings_notifications_widget.cpp���������������������0000664�0000000�0000000�00000014021�13262451251�0030121�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "settings/settings_notifications_widget.h" #include "styles/style_settings.h" #include "lang/lang_keys.h" #include "storage/localstorage.h" #include "ui/wrap/slide_wrap.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/buttons.h" #include "mainwindow.h" #include "window/notifications_manager.h" #include "boxes/notifications_box.h" #include "platform/platform_notifications_manager.h" #include "auth_session.h" namespace Settings { namespace { using ChangeType = Window::Notifications::ChangeType; } // namespace NotificationsWidget::NotificationsWidget(QWidget *parent, UserData *self) : BlockWidget(parent, self, lang(lng_settings_section_notify)) { createControls(); subscribe(Auth().notifications().settingsChanged(), [this](ChangeType type) { if (type == ChangeType::DesktopEnabled) { desktopEnabledUpdated(); } else if (type == ChangeType::ViewParams) { viewParamUpdated(); } else if (type == ChangeType::SoundEnabled) { _playSound->setChecked(Global::SoundNotify()); } }); } void NotificationsWidget::createControls() { style::margins margin(0, 0, 0, st::settingsSkip); style::margins slidedPadding(0, margin.bottom() / 2, 0, margin.bottom() - (margin.bottom() / 2)); createChildRow(_desktopNotifications, margin, lang(lng_settings_desktop_notify), [this](bool) { onDesktopNotifications(); }, Global::DesktopNotify()); createChildRow(_showSenderName, margin, slidedPadding, lang(lng_settings_show_name), [this](bool) { onShowSenderName(); }, Global::NotifyView() <= dbinvShowName); createChildRow(_showMessagePreview, margin, slidedPadding, lang(lng_settings_show_preview), [this](bool) { onShowMessagePreview(); }, Global::NotifyView() <= dbinvShowPreview); if (!_showSenderName->entity()->checked()) { _showMessagePreview->hide(anim::type::instant); } if (!_desktopNotifications->checked()) { _showSenderName->hide(anim::type::instant); _showMessagePreview->hide(anim::type::instant); } createChildRow(_playSound, margin, lang(lng_settings_sound_notify), [this](bool) { onPlaySound(); }, Global::SoundNotify()); createChildRow(_includeMuted, margin, lang(lng_settings_include_muted), [this](bool) { onIncludeMuted(); }, Global::IncludeMuted()); if (cPlatform() != dbipMac) { createNotificationsControls(); } } void NotificationsWidget::createNotificationsControls() { style::margins margin(0, 0, 0, st::settingsSkip); style::margins slidedPadding(0, margin.bottom() / 2, 0, margin.bottom() - (margin.bottom() / 2)); auto nativeNotificationsLabel = QString(); if (Platform::Notifications::Supported()) { #ifdef Q_OS_WIN nativeNotificationsLabel = lang(lng_settings_use_windows); #elif defined Q_OS_LINUX64 || defined Q_OS_LINUX32 // Q_OS_WIN nativeNotificationsLabel = lang(lng_settings_use_native_notifications); #endif // Q_OS_WIN || Q_OS_LINUX64 || Q_OS_LINUX32 } if (!nativeNotificationsLabel.isEmpty()) { createChildRow(_nativeNotifications, margin, nativeNotificationsLabel, [this](bool) { onNativeNotifications(); }, Global::NativeNotifications()); } createChildRow(_advanced, margin, slidedPadding, lang(lng_settings_advanced_notifications), SLOT(onAdvanced())); if (!nativeNotificationsLabel.isEmpty() && Global::NativeNotifications()) { _advanced->hide(anim::type::instant); } } void NotificationsWidget::onDesktopNotifications() { if (Global::DesktopNotify() == _desktopNotifications->checked()) { return; } Global::SetDesktopNotify(_desktopNotifications->checked()); Local::writeUserSettings(); Auth().notifications().settingsChanged().notify(ChangeType::DesktopEnabled); } void NotificationsWidget::desktopEnabledUpdated() { _desktopNotifications->setChecked(Global::DesktopNotify()); _showSenderName->toggle( Global::DesktopNotify(), anim::type::normal); _showMessagePreview->toggle( Global::DesktopNotify() && _showSenderName->entity()->checked(), anim::type::normal); } void NotificationsWidget::onShowSenderName() { auto viewParam = ([this]() { if (!_showSenderName->entity()->checked()) { return dbinvShowNothing; } else if (!_showMessagePreview->entity()->checked()) { return dbinvShowName; } return dbinvShowPreview; })(); if (viewParam == Global::NotifyView()) { return; } Global::SetNotifyView(viewParam); Local::writeUserSettings(); Auth().notifications().settingsChanged().notify(ChangeType::ViewParams); } void NotificationsWidget::onShowMessagePreview() { auto viewParam = ([this]() { if (_showMessagePreview->entity()->checked()) { return dbinvShowPreview; } else if (_showSenderName->entity()->checked()) { return dbinvShowName; } return dbinvShowNothing; })(); if (viewParam == Global::NotifyView()) { return; } Global::SetNotifyView(viewParam); Local::writeUserSettings(); Auth().notifications().settingsChanged().notify(ChangeType::ViewParams); } void NotificationsWidget::viewParamUpdated() { _showMessagePreview->toggle( _showSenderName->entity()->checked(), anim::type::normal); } void NotificationsWidget::onNativeNotifications() { if (Global::NativeNotifications() == _nativeNotifications->checked()) { return; } Global::SetNativeNotifications(_nativeNotifications->checked()); Local::writeUserSettings(); Auth().notifications().createManager(); _advanced->toggle( !Global::NativeNotifications(), anim::type::normal); } void NotificationsWidget::onAdvanced() { Ui::show(Box<NotificationsBox>()); } void NotificationsWidget::onPlaySound() { if (_playSound->checked() == Global::SoundNotify()) { return; } Global::SetSoundNotify(_playSound->checked()); Local::writeUserSettings(); Auth().notifications().settingsChanged().notify(ChangeType::SoundEnabled); } void NotificationsWidget::onIncludeMuted() { Global::SetIncludeMuted(_includeMuted->checked()); Local::writeUserSettings(); Auth().notifications().settingsChanged().notify(ChangeType::IncludeMuted); } } // namespace Settings ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/settings/settings_notifications_widget.h�����������������������0000664�0000000�0000000�00000002171�13262451251�0027571�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "settings/settings_block_widget.h" namespace Settings { class NotificationsWidget : public BlockWidget { Q_OBJECT public: NotificationsWidget(QWidget *parent, UserData *self); private slots: void onDesktopNotifications(); void onShowSenderName(); void onShowMessagePreview(); void onNativeNotifications(); void onPlaySound(); void onIncludeMuted(); void onAdvanced(); private: void createControls(); void createNotificationsControls(); void desktopEnabledUpdated(); void viewParamUpdated(); Ui::Checkbox *_desktopNotifications = nullptr; Ui::SlideWrap<Ui::Checkbox> *_showSenderName = nullptr; Ui::SlideWrap<Ui::Checkbox> *_showMessagePreview = nullptr; Ui::Checkbox *_nativeNotifications = nullptr; Ui::Checkbox *_playSound = nullptr; Ui::Checkbox *_includeMuted = nullptr; Ui::SlideWrap<Ui::LinkButton> *_advanced = nullptr; }; } // namespace Settings �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp����������������������0000664�0000000�0000000�00000025073�13262451251�0030021�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "settings/settings_privacy_controllers.h" #include "lang/lang_keys.h" #include "apiwrap.h" #include "observer_peer.h" #include "mainwidget.h" #include "auth_session.h" #include "storage/localstorage.h" #include "history/history.h" #include "boxes/peer_list_controllers.h" #include "boxes/confirm_box.h" namespace Settings { namespace { constexpr auto kBlockedPerPage = 40; class BlockUserBoxController : public ChatsListBoxController { public: void rowClicked(not_null<PeerListRow*> row) override; void setBlockUserCallback(base::lambda<void(not_null<UserData*> user)> callback) { _blockUserCallback = std::move(callback); } protected: void prepareViewHook() override; std::unique_ptr<Row> createRow(not_null<History*> history) override; void updateRowHook(not_null<Row*> row) override { updateIsBlocked(row, row->peer()->asUser()); delegate()->peerListUpdateRow(row); } private: void updateIsBlocked(not_null<PeerListRow*> row, UserData *user) const; base::lambda<void(not_null<UserData*> user)> _blockUserCallback; }; void BlockUserBoxController::prepareViewHook() { delegate()->peerListSetTitle(langFactory(lng_blocked_list_add_title)); subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(Notify::PeerUpdate::Flag::UserIsBlocked, [this](const Notify::PeerUpdate &update) { if (auto user = update.peer->asUser()) { if (auto row = delegate()->peerListFindRow(user->id)) { updateIsBlocked(row, user); delegate()->peerListUpdateRow(row); } } })); } void BlockUserBoxController::updateIsBlocked(not_null<PeerListRow*> row, UserData *user) const { auto blocked = user->isBlocked(); row->setDisabledState(blocked ? PeerListRow::State::DisabledChecked : PeerListRow::State::Active); if (blocked) { row->setCustomStatus(lang(lng_blocked_list_already_blocked)); } else { row->clearCustomStatus(); } } void BlockUserBoxController::rowClicked(not_null<PeerListRow*> row) { _blockUserCallback(row->peer()->asUser()); } std::unique_ptr<BlockUserBoxController::Row> BlockUserBoxController::createRow(not_null<History*> history) { if (history->peer->isSelf()) { return nullptr; } if (auto user = history->peer->asUser()) { auto row = std::make_unique<Row>(history); updateIsBlocked(row.get(), user); return row; } return nullptr; } } // namespace void BlockedBoxController::prepare() { delegate()->peerListSetTitle(langFactory(lng_blocked_list_title)); setDescriptionText(lang(lng_contacts_loading)); delegate()->peerListRefreshRows(); subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(Notify::PeerUpdate::Flag::UserIsBlocked, [this](const Notify::PeerUpdate &update) { if (auto user = update.peer->asUser()) { handleBlockedEvent(user); } })); loadMoreRows(); } void BlockedBoxController::loadMoreRows() { if (_loadRequestId || _allLoaded) { return; } _loadRequestId = request(MTPcontacts_GetBlocked(MTP_int(_offset), MTP_int(kBlockedPerPage))).done([this](const MTPcontacts_Blocked &result) { _loadRequestId = 0; if (!_offset) { setDescriptionText(lang(lng_blocked_list_about)); } auto handleContactsBlocked = [](auto &list) { App::feedUsers(list.vusers); return list.vblocked.v; }; switch (result.type()) { case mtpc_contacts_blockedSlice: { receivedUsers(handleContactsBlocked(result.c_contacts_blockedSlice())); } break; case mtpc_contacts_blocked: { _allLoaded = true; receivedUsers(handleContactsBlocked(result.c_contacts_blocked())); } break; default: Unexpected("Bad type() in MTPcontacts_GetBlocked() result."); } }).fail([this](const RPCError &error) { _loadRequestId = 0; }).send(); } void BlockedBoxController::rowClicked(not_null<PeerListRow*> row) { InvokeQueued(App::main(), [peerId = row->peer()->id] { Ui::showPeerHistory(peerId, ShowAtUnreadMsgId); }); } void BlockedBoxController::rowActionClicked(not_null<PeerListRow*> row) { auto user = row->peer()->asUser(); Expects(user != nullptr); Auth().api().unblockUser(user); } void BlockedBoxController::receivedUsers(const QVector<MTPContactBlocked> &result) { if (result.empty()) { _allLoaded = true; } for_const (auto &item, result) { ++_offset; if (item.type() != mtpc_contactBlocked) { continue; } auto &contactBlocked = item.c_contactBlocked(); auto userId = contactBlocked.vuser_id.v; if (auto user = App::userLoaded(userId)) { appendRow(user); user->setBlockStatus(UserData::BlockStatus::Blocked); } } delegate()->peerListRefreshRows(); } void BlockedBoxController::handleBlockedEvent(UserData *user) { if (user->isBlocked()) { if (prependRow(user)) { delegate()->peerListRefreshRows(); delegate()->peerListScrollToTop(); } } else if (auto row = delegate()->peerListFindRow(user->id)) { delegate()->peerListRemoveRow(row); delegate()->peerListRefreshRows(); } } void BlockedBoxController::BlockNewUser() { auto controller = std::make_unique<BlockUserBoxController>(); auto initBox = [controller = controller.get()](not_null<PeerListBox*> box) { controller->setBlockUserCallback([box](not_null<UserData*> user) { Auth().api().blockUser(user); box->closeBox(); }); box->addButton(langFactory(lng_cancel), [box] { box->closeBox(); }); }; Ui::show( Box<PeerListBox>(std::move(controller), std::move(initBox)), LayerOption::KeepOther); } bool BlockedBoxController::appendRow(UserData *user) { if (delegate()->peerListFindRow(user->id)) { return false; } delegate()->peerListAppendRow(createRow(user)); return true; } bool BlockedBoxController::prependRow(UserData *user) { if (delegate()->peerListFindRow(user->id)) { return false; } delegate()->peerListPrependRow(createRow(user)); return true; } std::unique_ptr<PeerListRow> BlockedBoxController::createRow(UserData *user) const { auto row = std::make_unique<PeerListRowWithLink>(user); row->setActionLink(lang(lng_blocked_list_unblock)); auto status = [user]() -> QString { if (user->botInfo) { return lang(lng_status_bot); } else if (user->phone().isEmpty()) { return lang(lng_blocked_list_unknown_phone); } return App::formatPhone(user->phone()); }; row->setCustomStatus(status()); return std::move(row); } MTPInputPrivacyKey LastSeenPrivacyController::key() { return MTP_inputPrivacyKeyStatusTimestamp(); } QString LastSeenPrivacyController::title() { return lang(lng_edit_privacy_lastseen_title); } QString LastSeenPrivacyController::description() { return lang(lng_edit_privacy_lastseen_description); } QString LastSeenPrivacyController::warning() { return lang(lng_edit_privacy_lastseen_warning); } QString LastSeenPrivacyController::exceptionLinkText(Exception exception, int count) { switch (exception) { case Exception::Always: return (count > 0) ? lng_edit_privacy_lastseen_always(lt_count, count) : lang(lng_edit_privacy_lastseen_always_empty); case Exception::Never: return (count > 0) ? lng_edit_privacy_lastseen_never(lt_count, count) : lang(lng_edit_privacy_lastseen_never_empty); } Unexpected("Invalid exception value."); } QString LastSeenPrivacyController::exceptionBoxTitle(Exception exception) { switch (exception) { case Exception::Always: return lang(lng_edit_privacy_lastseen_always_title); case Exception::Never: return lang(lng_edit_privacy_lastseen_never_title); } Unexpected("Invalid exception value."); } QString LastSeenPrivacyController::exceptionsDescription() { return lang(lng_edit_privacy_lastseen_exceptions); } void LastSeenPrivacyController::confirmSave(bool someAreDisallowed, base::lambda_once<void()> saveCallback) { if (someAreDisallowed && !Auth().settings().lastSeenWarningSeen()) { auto weakBox = std::make_shared<QPointer<ConfirmBox>>(); auto callback = [weakBox, saveCallback = std::move(saveCallback)]() mutable { if (auto box = *weakBox) { box->closeBox(); } saveCallback(); Auth().settings().setLastSeenWarningSeen(true); Local::writeUserSettings(); }; auto box = Box<ConfirmBox>(lang(lng_edit_privacy_lastseen_warning), lang(lng_continue), lang(lng_cancel), std::move(callback)); *weakBox = Ui::show(std::move(box), LayerOption::KeepOther); } else { saveCallback(); } } MTPInputPrivacyKey GroupsInvitePrivacyController::key() { return MTP_inputPrivacyKeyChatInvite(); } QString GroupsInvitePrivacyController::title() { return lang(lng_edit_privacy_groups_title); } bool GroupsInvitePrivacyController::hasOption(Option option) { return (option != Option::Nobody); } QString GroupsInvitePrivacyController::description() { return lang(lng_edit_privacy_groups_description); } QString GroupsInvitePrivacyController::exceptionLinkText(Exception exception, int count) { switch (exception) { case Exception::Always: return (count > 0) ? lng_edit_privacy_groups_always(lt_count, count) : lang(lng_edit_privacy_groups_always_empty); case Exception::Never: return (count > 0) ? lng_edit_privacy_groups_never(lt_count, count) : lang(lng_edit_privacy_groups_never_empty); } Unexpected("Invalid exception value."); } QString GroupsInvitePrivacyController::exceptionBoxTitle(Exception exception) { switch (exception) { case Exception::Always: return lang(lng_edit_privacy_groups_always_title); case Exception::Never: return lang(lng_edit_privacy_groups_never_title); } Unexpected("Invalid exception value."); } QString GroupsInvitePrivacyController::exceptionsDescription() { return lang(lng_edit_privacy_groups_exceptions); } MTPInputPrivacyKey CallsPrivacyController::key() { return MTP_inputPrivacyKeyPhoneCall(); } QString CallsPrivacyController::title() { return lang(lng_edit_privacy_calls_title); } QString CallsPrivacyController::description() { return lang(lng_edit_privacy_calls_description); } QString CallsPrivacyController::exceptionLinkText(Exception exception, int count) { switch (exception) { case Exception::Always: return (count > 0) ? lng_edit_privacy_calls_always(lt_count, count) : lang(lng_edit_privacy_calls_always_empty); case Exception::Never: return (count > 0) ? lng_edit_privacy_calls_never(lt_count, count) : lang(lng_edit_privacy_calls_never_empty); } Unexpected("Invalid exception value."); } QString CallsPrivacyController::exceptionBoxTitle(Exception exception) { switch (exception) { case Exception::Always: return lang(lng_edit_privacy_calls_always_title); case Exception::Never: return lang(lng_edit_privacy_calls_never_title); } Unexpected("Invalid exception value."); } QString CallsPrivacyController::exceptionsDescription() { return lang(lng_edit_privacy_calls_exceptions); } } // namespace Settings ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/settings/settings_privacy_controllers.h������������������������0000664�0000000�0000000�00000005044�13262451251�0027462�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "boxes/peer_list_box.h" #include "boxes/edit_privacy_box.h" #include "mtproto/sender.h" namespace Settings { class BlockedBoxController : public PeerListController, private base::Subscriber, private MTP::Sender { public: void prepare() override; void rowClicked(not_null<PeerListRow*> row) override; void rowActionClicked(not_null<PeerListRow*> row) override; void loadMoreRows() override; static void BlockNewUser(); private: void receivedUsers(const QVector<MTPContactBlocked> &result); void handleBlockedEvent(UserData *user); bool appendRow(UserData *user); bool prependRow(UserData *user); std::unique_ptr<PeerListRow> createRow(UserData *user) const; int _offset = 0; mtpRequestId _loadRequestId = 0; bool _allLoaded = false; }; class LastSeenPrivacyController : public EditPrivacyBox::Controller, private base::Subscriber { public: using Option = EditPrivacyBox::Option; using Exception = EditPrivacyBox::Exception; MTPInputPrivacyKey key() override; QString title() override; QString description() override; QString warning() override; QString exceptionLinkText(Exception exception, int count) override; QString exceptionBoxTitle(Exception exception) override; QString exceptionsDescription() override; void confirmSave(bool someAreDisallowed, base::lambda_once<void()> saveCallback) override; }; class GroupsInvitePrivacyController : public EditPrivacyBox::Controller, private base::Subscriber { public: using Option = EditPrivacyBox::Option; using Exception = EditPrivacyBox::Exception; MTPInputPrivacyKey key() override; QString title() override; bool hasOption(Option option) override; QString description() override; QString exceptionLinkText(Exception exception, int count) override; QString exceptionBoxTitle(Exception exception) override; QString exceptionsDescription() override; }; class CallsPrivacyController : public EditPrivacyBox::Controller, private base::Subscriber { public: using Option = EditPrivacyBox::Option; using Exception = EditPrivacyBox::Exception; MTPInputPrivacyKey key() override; QString title() override; QString description() override; QString exceptionLinkText(Exception exception, int count) override; QString exceptionBoxTitle(Exception exception) override; QString exceptionsDescription() override; }; } // namespace Settings ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/settings/settings_privacy_widget.cpp���������������������������0000664�0000000�0000000�00000020437�13262451251�0026735�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "settings/settings_privacy_widget.h" #include "ui/wrap/slide_wrap.h" #include "ui/widgets/buttons.h" #include "styles/style_settings.h" #include "lang/lang_keys.h" #include "application.h" #include "platform/platform_specific.h" #include "boxes/sessions_box.h" #include "boxes/passcode_box.h" #include "boxes/autolock_box.h" #include "boxes/peer_list_box.h" #include "boxes/edit_privacy_box.h" #include "boxes/self_destruction_box.h" #include "settings/settings_privacy_controllers.h" namespace Settings { LocalPasscodeState::LocalPasscodeState(QWidget *parent) : RpWidget(parent) , _edit(this, GetEditPasscodeText(), st::boxLinkButton) , _turnOff(this, lang(lng_passcode_turn_off), st::boxLinkButton) { updateControls(); connect(_edit, SIGNAL(clicked()), this, SLOT(onEdit())); connect(_turnOff, SIGNAL(clicked()), this, SLOT(onTurnOff())); subscribe(Global::RefLocalPasscodeChanged(), [this]() { updateControls(); }); } int LocalPasscodeState::resizeGetHeight(int newWidth) { _edit->moveToLeft(0, 0, newWidth); _turnOff->moveToRight(0, 0, newWidth); return _edit->height(); } void LocalPasscodeState::onEdit() { Ui::show(Box<PasscodeBox>(false)); } void LocalPasscodeState::onTurnOff() { Ui::show(Box<PasscodeBox>(true)); } void LocalPasscodeState::updateControls() { _edit->setText(GetEditPasscodeText()); _edit->moveToLeft(0, 0); _turnOff->setVisible(Global::LocalPasscode()); } QString LocalPasscodeState::GetEditPasscodeText() { return lang(Global::LocalPasscode() ? lng_passcode_change : lng_passcode_turn_on); } CloudPasswordState::CloudPasswordState(QWidget *parent) : RpWidget(parent) , _edit(this, lang(lng_cloud_password_set), st::boxLinkButton) , _turnOff(this, lang(lng_passcode_turn_off), st::boxLinkButton) { _turnOff->hide(); connect(_edit, SIGNAL(clicked()), this, SLOT(onEdit())); connect(_turnOff, SIGNAL(clicked()), this, SLOT(onTurnOff())); Sandbox::connect(SIGNAL(applicationStateChanged(Qt::ApplicationState)), this, SLOT(onReloadPassword(Qt::ApplicationState))); onReloadPassword(); } int CloudPasswordState::resizeGetHeight(int newWidth) { _edit->moveToLeft(0, 0, newWidth); _turnOff->moveToRight(0, 0, newWidth); return _edit->height(); } void CloudPasswordState::onEdit() { auto box = Ui::show(Box<PasscodeBox>(_newPasswordSalt, _curPasswordSalt, _hasPasswordRecovery, _curPasswordHint)); connect(box, SIGNAL(reloadPassword()), this, SLOT(onReloadPassword())); } void CloudPasswordState::onTurnOff() { if (_curPasswordSalt.isEmpty()) { _turnOff->hide(); auto flags = MTPDaccount_passwordInputSettings::Flag::f_email; MTPaccount_PasswordInputSettings settings(MTP_account_passwordInputSettings(MTP_flags(flags), MTP_bytes(QByteArray()), MTP_bytes(QByteArray()), MTP_string(QString()), MTP_string(QString()))); MTP::send(MTPaccount_UpdatePasswordSettings(MTP_bytes(QByteArray()), settings), rpcDone(&CloudPasswordState::offPasswordDone), rpcFail(&CloudPasswordState::offPasswordFail)); } else { auto box = Ui::show(Box<PasscodeBox>(_newPasswordSalt, _curPasswordSalt, _hasPasswordRecovery, _curPasswordHint, true)); connect(box, SIGNAL(reloadPassword()), this, SLOT(onReloadPassword())); } } void CloudPasswordState::onReloadPassword(Qt::ApplicationState state) { if (state == Qt::ApplicationActive) { MTP::send(MTPaccount_GetPassword(), rpcDone(&CloudPasswordState::getPasswordDone)); } } void CloudPasswordState::getPasswordDone(const MTPaccount_Password &result) { _waitingConfirm = QString(); switch (result.type()) { case mtpc_account_noPassword: { auto &d = result.c_account_noPassword(); _curPasswordSalt = QByteArray(); _hasPasswordRecovery = false; _curPasswordHint = QString(); _newPasswordSalt = qba(d.vnew_salt); auto pattern = qs(d.vemail_unconfirmed_pattern); if (!pattern.isEmpty()) { _waitingConfirm = lng_cloud_password_waiting(lt_email, pattern); } } break; case mtpc_account_password: { auto &d = result.c_account_password(); _curPasswordSalt = qba(d.vcurrent_salt); _hasPasswordRecovery = mtpIsTrue(d.vhas_recovery); _curPasswordHint = qs(d.vhint); _newPasswordSalt = qba(d.vnew_salt); auto pattern = qs(d.vemail_unconfirmed_pattern); if (!pattern.isEmpty()) { _waitingConfirm = lng_cloud_password_waiting(lt_email, pattern); } } break; } _edit->setText(lang(_curPasswordSalt.isEmpty() ? lng_cloud_password_set : lng_cloud_password_edit)); _edit->setVisible(_waitingConfirm.isEmpty()); _turnOff->setVisible(!_waitingConfirm.isEmpty() || !_curPasswordSalt.isEmpty()); update(); _newPasswordSalt.resize(_newPasswordSalt.size() + 8); memset_rand(_newPasswordSalt.data() + _newPasswordSalt.size() - 8, 8); } void CloudPasswordState::paintEvent(QPaintEvent *e) { Painter p(this); auto text = st::boxTextFont->elided(_waitingConfirm, width() - _turnOff->width() - st::boxTextFont->spacew); if (!text.isEmpty()) { p.setPen(st::windowFg); p.setFont(st::boxTextFont); p.drawTextLeft(0, 0, width(), text); } } void CloudPasswordState::offPasswordDone(const MTPBool &result) { onReloadPassword(); } bool CloudPasswordState::offPasswordFail(const RPCError &error) { if (MTP::isDefaultHandledError(error)) return false; onReloadPassword(); return true; } PrivacyWidget::PrivacyWidget(QWidget *parent, UserData *self) : BlockWidget(parent, self, lang(lng_settings_section_privacy)) { createControls(); subscribe(Global::RefLocalPasscodeChanged(), [this]() { autoLockUpdated(); }); } QString PrivacyWidget::GetAutoLockText() { return (Global::AutoLock() % 3600) ? lng_passcode_autolock_minutes(lt_count, Global::AutoLock() / 60) : lng_passcode_autolock_hours(lt_count, Global::AutoLock() / 3600); } void PrivacyWidget::createControls() { style::margins marginSmall(0, 0, 0, st::settingsSmallSkip); style::margins marginSkip(0, 0, 0, st::settingsSkip); style::margins slidedPadding(0, marginSmall.bottom() / 2, 0, marginSmall.bottom() - (marginSmall.bottom() / 2)); createChildRow(_blockedUsers, marginSmall, lang(lng_settings_blocked_users), SLOT(onBlockedUsers())); createChildRow(_lastSeenPrivacy, marginSmall, lang(lng_settings_last_seen_privacy), SLOT(onLastSeenPrivacy())); createChildRow(_callsPrivacy, marginSmall, lang(lng_settings_calls_privacy), SLOT(onCallsPrivacy())); createChildRow(_groupsInvitePrivacy, marginSmall, lang(lng_settings_groups_invite_privacy), SLOT(onGroupsInvitePrivacy())); createChildRow(_localPasscodeState, marginSmall); auto label = lang(psIdleSupported() ? lng_passcode_autolock_away : lng_passcode_autolock_inactive); auto value = GetAutoLockText(); createChildRow(_autoLock, marginSmall, slidedPadding, label, value, LabeledLink::Type::Primary, SLOT(onAutoLock())); if (!Global::LocalPasscode()) { _autoLock->hide(anim::type::instant); } createChildRow(_cloudPasswordState, marginSmall); createChildRow(_showAllSessions, marginSmall, lang(lng_settings_show_sessions), SLOT(onShowSessions())); createChildRow(_selfDestruction, marginSmall, lang(lng_settings_self_destruct), SLOT(onSelfDestruction())); } void PrivacyWidget::autoLockUpdated() { if (Global::LocalPasscode()) { auto value = GetAutoLockText(); _autoLock->entity()->link()->setText(value); resizeToWidth(width()); } _autoLock->toggle( Global::LocalPasscode(), anim::type::normal); } void PrivacyWidget::onBlockedUsers() { Ui::show(Box<PeerListBox>(std::make_unique<BlockedBoxController>(), [](not_null<PeerListBox*> box) { box->addButton(langFactory(lng_close), [box] { box->closeBox(); }); box->addLeftButton(langFactory(lng_blocked_list_add), [=] { BlockedBoxController::BlockNewUser(); }); })); } void PrivacyWidget::onLastSeenPrivacy() { Ui::show(Box<EditPrivacyBox>(std::make_unique<LastSeenPrivacyController>())); } void PrivacyWidget::onCallsPrivacy() { Ui::show(Box<EditPrivacyBox>(std::make_unique<CallsPrivacyController>())); } void PrivacyWidget::onGroupsInvitePrivacy() { Ui::show(Box<EditPrivacyBox>(std::make_unique<GroupsInvitePrivacyController>())); } void PrivacyWidget::onAutoLock() { Ui::show(Box<AutoLockBox>()); } void PrivacyWidget::onShowSessions() { Ui::show(Box<SessionsBox>()); } void PrivacyWidget::onSelfDestruction() { Ui::show(Box<SelfDestructionBox>()); } } // namespace Settings ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/settings/settings_privacy_widget.h�����������������������������0000664�0000000�0000000�00000004552�13262451251�0026402�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "settings/settings_block_widget.h" #include "settings/settings_chat_settings_widget.h" #include "ui/rp_widget.h" namespace Settings { class LocalPasscodeState : public Ui::RpWidget, private base::Subscriber { Q_OBJECT public: LocalPasscodeState(QWidget *parent); protected: int resizeGetHeight(int newWidth) override; private slots: void onEdit(); void onTurnOff(); private: static QString GetEditPasscodeText(); void updateControls(); object_ptr<Ui::LinkButton> _edit; object_ptr<Ui::LinkButton> _turnOff; }; class CloudPasswordState : public Ui::RpWidget, private base::Subscriber, public RPCSender { Q_OBJECT public: CloudPasswordState(QWidget *parent); protected: int resizeGetHeight(int newWidth) override; void paintEvent(QPaintEvent *e) override; private slots: void onEdit(); void onTurnOff(); void onReloadPassword(Qt::ApplicationState state = Qt::ApplicationActive); private: void getPasswordDone(const MTPaccount_Password &result); void offPasswordDone(const MTPBool &result); bool offPasswordFail(const RPCError &error); object_ptr<Ui::LinkButton> _edit; object_ptr<Ui::LinkButton> _turnOff; QString _waitingConfirm; QByteArray _curPasswordSalt; bool _hasPasswordRecovery = false; QString _curPasswordHint; QByteArray _newPasswordSalt; }; class PrivacyWidget : public BlockWidget { Q_OBJECT public: PrivacyWidget(QWidget *parent, UserData *self); private slots: void onBlockedUsers(); void onLastSeenPrivacy(); void onCallsPrivacy(); void onGroupsInvitePrivacy(); void onAutoLock(); void onShowSessions(); void onSelfDestruction(); private: static QString GetAutoLockText(); void createControls(); void autoLockUpdated(); Ui::LinkButton *_blockedUsers = nullptr; Ui::LinkButton *_lastSeenPrivacy = nullptr; Ui::LinkButton *_callsPrivacy = nullptr; Ui::LinkButton *_groupsInvitePrivacy = nullptr; LocalPasscodeState *_localPasscodeState = nullptr; Ui::SlideWrap<LabeledLink> *_autoLock = nullptr; CloudPasswordState *_cloudPasswordState = nullptr; Ui::LinkButton *_showAllSessions = nullptr; Ui::LinkButton *_selfDestruction = nullptr; }; } // namespace Settings ������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/settings/settings_scale_widget.cpp�����������������������������0000664�0000000�0000000�00000006620�13262451251�0026345�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "settings/settings_scale_widget.h" #include "styles/style_boxes.h" #include "styles/style_settings.h" #include "ui/widgets/checkbox.h" #include "lang/lang_keys.h" #include "storage/localstorage.h" #include "mainwindow.h" #include "boxes/confirm_box.h" #include "application.h" #include "ui/widgets/discrete_sliders.h" namespace Settings { namespace { QString scaleLabel(DBIScale scale) { switch (scale) { case dbisOne: return qsl("100%"); case dbisOneAndQuarter: return qsl("125%"); case dbisOneAndHalf: return qsl("150%"); case dbisTwo: return qsl("200%"); } return QString(); } } // namespace ScaleWidget::ScaleWidget(QWidget *parent, UserData *self) : BlockWidget(parent, self, lang(lng_settings_section_scale)) { createControls(); } void ScaleWidget::createControls() { style::margins margin(0, 0, 0, st::settingsSmallSkip); createChildRow(_auto, margin, lng_settings_scale_auto(lt_cur, scaleLabel(cScreenScale())), [this](bool) { onAutoChanged(); }, (cConfigScale() == dbisAuto)); createChildRow(_scale, style::margins(0, 0, 0, 0)); _scale->addSection(scaleLabel(dbisOne)); _scale->addSection(scaleLabel(dbisOneAndQuarter)); _scale->addSection(scaleLabel(dbisOneAndHalf)); _scale->addSection(scaleLabel(dbisTwo)); _scale->setActiveSectionFast(cEvalScale(cConfigScale()) - 1); _scale->sectionActivated( ) | rpl::start_with_next( [this] { scaleChanged(); }, lifetime()); } void ScaleWidget::onAutoChanged() { auto newScale = _auto->checked() ? dbisAuto : cEvalScale(cConfigScale()); if (newScale == cScreenScale()) { if (newScale != cScale()) { newScale = cScale(); } else { switch (newScale) { case dbisOne: newScale = dbisOneAndQuarter; break; case dbisOneAndQuarter: newScale = dbisOne; break; case dbisOneAndHalf: newScale = dbisOneAndQuarter; break; case dbisTwo: newScale = dbisOneAndHalf; break; } } } setScale(newScale); } void ScaleWidget::setScale(DBIScale newScale) { if (_inSetScale) return; _inSetScale = true; auto guard = gsl::finally([this] { _inSetScale = false; }); if (newScale == cScreenScale()) newScale = dbisAuto; if (newScale == dbisAuto && !_auto->checked()) { _auto->setChecked(true); } else if (newScale != dbisAuto && _auto->checked()) { _auto->setChecked(false); } _newScale = newScale; if (newScale == dbisAuto) newScale = cScreenScale(); if (_scale->activeSection() != newScale - 1) { _scale->setActiveSection(newScale - 1); } if (cEvalScale(newScale) != cEvalScale(cRealScale())) { Ui::show(Box<ConfirmBox>(lang(lng_settings_need_restart), lang(lng_settings_restart_now), base::lambda_guarded(this, [this] { cSetConfigScale(_newScale); Local::writeSettings(); App::restart(); }), base::lambda_guarded(this, [this] { App::CallDelayed(st::boxDuration, this, [this] { setScale(cRealScale()); }); }))); } else { cSetConfigScale(newScale); Local::writeSettings(); } } void ScaleWidget::scaleChanged() { auto newScale = dbisAuto; switch (_scale->activeSection()) { case 0: newScale = dbisOne; break; case 1: newScale = dbisOneAndQuarter; break; case 2: newScale = dbisOneAndHalf; break; case 3: newScale = dbisTwo; break; } setScale(newScale); } } // namespace Settings ����������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/settings/settings_scale_widget.h�������������������������������0000664�0000000�0000000�00000001427�13262451251�0026012�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "settings/settings_block_widget.h" namespace Ui { class Checkbox; class SettingsSlider; } // namespace Ui namespace Settings { class ScaleWidget : public BlockWidget { Q_OBJECT public: ScaleWidget(QWidget *parent, UserData *self); private slots: void onAutoChanged(); private: void scaleChanged(); void createControls(); void setScale(DBIScale newScale); Ui::Checkbox *_auto = nullptr; Ui::SettingsSlider *_scale = nullptr; DBIScale _newScale = dbisAuto; bool _inSetScale = false; }; } // namespace Settings �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/settings/settings_widget.cpp�����������������������������������0000664�0000000�0000000�00000017563�13262451251�0025206�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "settings/settings_widget.h" #include "settings/settings_inner_widget.h" #include "settings/settings_fixed_bar.h" #include "styles/style_settings.h" #include "styles/style_window.h" #include "styles/style_boxes.h" #include "ui/widgets/scroll_area.h" #include "ui/widgets/buttons.h" #include "mainwindow.h" #include "mainwidget.h" #include "storage/localstorage.h" #include "boxes/confirm_box.h" #include "lang/lang_keys.h" #include "lang/lang_cloud_manager.h" #include "messenger.h" #include "mtproto/mtp_instance.h" #include "mtproto/dc_options.h" #include "core/file_utilities.h" #include "window/themes/window_theme.h" #include "window/themes/window_theme_editor.h" #include "media/media_audio_track.h" namespace Settings { namespace { QString SecretText; QMap<QString, base::lambda<void()>> Codes; void fillCodes() { Codes.insert(qsl("debugmode"), [] { QString text = cDebug() ? qsl("Do you want to disable DEBUG logs?") : qsl("Do you want to enable DEBUG logs?\n\nAll network events will be logged."); Ui::show(Box<ConfirmBox>(text, [] { Messenger::Instance().onSwitchDebugMode(); })); }); Codes.insert(qsl("testmode"), [] { auto text = cTestMode() ? qsl("Do you want to disable TEST mode?") : qsl("Do you want to enable TEST mode?\n\nYou will be switched to test cloud."); Ui::show(Box<ConfirmBox>(text, [] { Messenger::Instance().onSwitchTestMode(); })); }); Codes.insert(qsl("loadlang"), [] { Lang::CurrentCloudManager().switchToLanguage(qsl("custom")); }); Codes.insert(qsl("debugfiles"), [] { if (!cDebug()) return; if (DebugLogging::FileLoader()) { Global::RefDebugLoggingFlags() &= ~DebugLogging::FileLoaderFlag; } else { Global::RefDebugLoggingFlags() |= DebugLogging::FileLoaderFlag; } Ui::show(Box<InformBox>(DebugLogging::FileLoader() ? qsl("Enabled file download logging") : qsl("Disabled file download logging"))); }); Codes.insert(qsl("crashplease"), [] { Unexpected("Crashed in Settings!"); }); Codes.insert(qsl("workmode"), [] { auto text = Global::DialogsModeEnabled() ? qsl("Disable work mode?") : qsl("Enable work mode?"); Ui::show(Box<ConfirmBox>(text, [] { Messenger::Instance().onSwitchWorkMode(); })); }); Codes.insert(qsl("moderate"), [] { auto text = Global::ModerateModeEnabled() ? qsl("Disable moderate mode?") : qsl("Enable moderate mode?"); Ui::show(Box<ConfirmBox>(text, []() { Global::SetModerateModeEnabled(!Global::ModerateModeEnabled()); Local::writeUserSettings(); Ui::hideLayer(); })); }); Codes.insert(qsl("getdifference"), [] { if (auto main = App::main()) { main->getDifference(); } }); Codes.insert(qsl("loadcolors"), [] { FileDialog::GetOpenPath("Open palette file", "Palette (*.tdesktop-palette)", [](const FileDialog::OpenResult &result) { if (!result.paths.isEmpty()) { Window::Theme::Apply(result.paths.front()); } }); }); Codes.insert(qsl("edittheme"), [] { Window::Theme::Editor::Start(); }); Codes.insert(qsl("videoplayer"), [] { auto text = cUseExternalVideoPlayer() ? qsl("Use internal video player?") : qsl("Use external video player?"); Ui::show(Box<ConfirmBox>(text, [] { cSetUseExternalVideoPlayer(!cUseExternalVideoPlayer()); Local::writeUserSettings(); Ui::hideLayer(); })); }); Codes.insert(qsl("endpoints"), [] { FileDialog::GetOpenPath("Open DC endpoints", "DC Endpoints (*.tdesktop-endpoints)", [](const FileDialog::OpenResult &result) { if (!result.paths.isEmpty()) { if (!Messenger::Instance().mtp()->dcOptions()->loadFromFile(result.paths.front())) { Ui::show(Box<InformBox>("Could not load endpoints :( Errors in 'log.txt'.")); } } }); }); auto audioFilters = qsl("Audio files (*.wav *.mp3);;") + FileDialog::AllFilesFilter(); auto audioKeys = { qsl("msg_incoming"), qsl("call_incoming"), qsl("call_outgoing"), qsl("call_busy"), qsl("call_connect"), qsl("call_end"), }; for (auto &key : audioKeys) { Codes.insert(key, [audioFilters, key] { if (!AuthSession::Exists()) { return; } FileDialog::GetOpenPath("Open audio file", audioFilters, [key](const FileDialog::OpenResult &result) { if (AuthSession::Exists() && !result.paths.isEmpty()) { auto track = Media::Audio::Current().createTrack(); track->fillFromFile(result.paths.front()); if (track->failed()) { Ui::show(Box<InformBox>("Could not audio :( Errors in 'log.txt'.")); } else { Auth().settings().setSoundOverride(key, result.paths.front()); Local::writeUserSettings(); } } }); }); } Codes.insert(qsl("sounds_reset"), [] { if (AuthSession::Exists()) { Auth().settings().clearSoundOverrides(); Local::writeUserSettings(); Ui::show(Box<InformBox>("All sound overrides were reset.")); } }); } void codesFeedString(const QString &text) { if (Codes.isEmpty()) fillCodes(); SecretText += text.toLower(); int size = SecretText.size(), from = 0; while (size > from) { auto piece = SecretText.midRef(from); auto found = false; for (auto i = Codes.cbegin(), e = Codes.cend(); i != e; ++i) { if (piece == i.key()) { (*i)(); from = size; found = true; break; } } if (found) break; for (auto i = Codes.cbegin(), e = Codes.cend(); i != e; ++i) { if (i.key().startsWith(piece)) { found = true; break; } } if (found) break; ++from; } SecretText = (size > from) ? SecretText.mid(from) : QString(); } } // namespace Widget::Widget(QWidget *parent) { refreshLang(); subscribe(Lang::Current().updated(), [this] { refreshLang(); }); _inner = setInnerWidget(object_ptr<InnerWidget>(this)); setCloseClickHandler([]() { Ui::hideSettingsAndLayer(); }); } void Widget::refreshLang() { setTitle(lang(lng_menu_settings)); update(); } void Widget::keyPressEvent(QKeyEvent *e) { codesFeedString(e->text()); return LayerWidget::keyPressEvent(e); } void Widget::parentResized() { auto parentSize = parentWidget()->size(); auto windowWidth = parentSize.width(); auto newWidth = st::settingsMaxWidth; auto newContentLeft = st::settingsMaxPadding; if (windowWidth <= st::settingsMaxWidth) { newWidth = windowWidth; newContentLeft = st::settingsMinPadding; if (windowWidth > st::windowMinWidth) { // Width changes from st::windowMinWidth to st::settingsMaxWidth. // Padding changes from st::settingsMinPadding to st::settingsMaxPadding. newContentLeft += ((newWidth - st::windowMinWidth) * (st::settingsMaxPadding - st::settingsMinPadding)) / (st::settingsMaxWidth - st::windowMinWidth); } } else if (windowWidth < st::settingsMaxWidth + 2 * st::settingsMargin) { newWidth = windowWidth - 2 * st::settingsMargin; newContentLeft = st::settingsMinPadding; if (windowWidth > st::windowMinWidth) { // Width changes from st::windowMinWidth to st::settingsMaxWidth. // Padding changes from st::settingsMinPadding to st::settingsMaxPadding. newContentLeft += ((newWidth - st::windowMinWidth) * (st::settingsMaxPadding - st::settingsMinPadding)) / (st::settingsMaxWidth - st::windowMinWidth); } } resizeToWidth(newWidth, newContentLeft); } void Widget::resizeUsingInnerHeight(int newWidth, int innerHeight) { if (!parentWidget()) return; auto parentSize = parentWidget()->size(); auto windowWidth = parentSize.width(); auto windowHeight = parentSize.height(); auto maxHeight = st::settingsFixedBarHeight + innerHeight; auto newHeight = maxHeight + st::boxRadius; if (newHeight > windowHeight || newWidth >= windowWidth) { newHeight = windowHeight; } auto roundedCorners = newHeight < windowHeight; setRoundedCorners(roundedCorners); setAttribute(Qt::WA_OpaquePaintEvent, !roundedCorners); setGeometry((windowWidth - newWidth) / 2, (windowHeight - newHeight) / 2, newWidth, newHeight); update(); } } // namespace Settings ���������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/settings/settings_widget.h�������������������������������������0000664�0000000�0000000�00000001235�13262451251�0024640�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "settings/settings_layer.h" namespace Settings { class InnerWidget; class Widget : public Layer, private base::Subscriber { public: Widget(QWidget*); void refreshLang(); void parentResized() override; protected: void keyPressEvent(QKeyEvent *e) override; private: void resizeUsingInnerHeight(int newWidth, int innerHeight) override; QPointer<InnerWidget> _inner; }; } // namespace Settings �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/shortcuts.cpp��������������������������������������������������0000664�0000000�0000000�00000030764�13262451251�0022177�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "shortcuts.h" #include "mainwindow.h" #include "passcodewidget.h" #include "mainwidget.h" #include "messenger.h" #include "media/player/media_player_instance.h" #include "platform/platform_specific.h" #include "base/parse_helper.h" namespace ShortcutCommands { using Handler = bool(*)(); bool lock_telegram() { if (!App::passcoded() && Global::LocalPasscode()) { Messenger::Instance().setupPasscode(); return true; } return false; } bool minimize_telegram() { return Messenger::Instance().minimizeActiveWindow(); } bool close_telegram() { return Messenger::Instance().closeActiveWindow(); } bool quit_telegram() { App::quit(); return true; } //void start_stop_recording() { //} //void cancel_recording() { //} bool media_play() { Media::Player::instance()->play(); return true; } bool media_pause() { Media::Player::instance()->pause(AudioMsgId::Type::Song); return true; } bool media_playpause() { Media::Player::instance()->playPause(); return true; } bool media_stop() { Media::Player::instance()->stop(); return true; } bool media_previous() { Media::Player::instance()->previous(); return true; } bool media_next() { Media::Player::instance()->next(); return true; } bool search() { if (auto m = App::main()) { return m->cmd_search(); } return false; } bool previous_chat() { if (auto m = App::main()) { return m->cmd_previous_chat(); } return false; } bool next_chat() { if (auto m = App::main()) { return m->cmd_next_chat(); } return false; } // other commands here } // namespace ShortcutCommands inline bool qMapLessThanKey(const ShortcutCommands::Handler &a, const ShortcutCommands::Handler &b) { return a < b; } namespace Shortcuts { struct DataStruct; DataStruct *DataPtr = nullptr; namespace { void createCommand(const QString &command, ShortcutCommands::Handler handler); QKeySequence setShortcut(const QString &keys, const QString &command); void destroyShortcut(QShortcut *shortcut); } // namespace struct DataStruct { DataStruct() { Assert(DataPtr == nullptr); DataPtr = this; if (autoRepeatCommands.isEmpty()) { autoRepeatCommands.insert(qsl("media_previous")); autoRepeatCommands.insert(qsl("media_next")); autoRepeatCommands.insert(qsl("next_chat")); autoRepeatCommands.insert(qsl("previous_chat")); } if (mediaCommands.isEmpty()) { mediaCommands.insert(qsl("media_play")); mediaCommands.insert(qsl("media_playpause")); mediaCommands.insert(qsl("media_play")); mediaCommands.insert(qsl("media_stop")); mediaCommands.insert(qsl("media_previous")); mediaCommands.insert(qsl("media_next")); } #define DeclareAlias(keys, command) setShortcut(qsl(keys), qsl(#command)) #define DeclareCommand(keys, command) createCommand(qsl(#command), ShortcutCommands::command); DeclareAlias(keys, command) DeclareCommand("ctrl+w", close_telegram); DeclareAlias("ctrl+f4", close_telegram); DeclareCommand("ctrl+l", lock_telegram); DeclareCommand("ctrl+m", minimize_telegram); DeclareCommand("ctrl+q", quit_telegram); //DeclareCommand("ctrl+r", start_stop_recording); //DeclareCommand("ctrl+shift+r", cancel_recording); //DeclareCommand("media record", start_stop_recording); DeclareCommand("media play", media_play); DeclareCommand("media pause", media_pause); DeclareCommand("toggle media play/pause", media_playpause); DeclareCommand("media stop", media_stop); DeclareCommand("media previous", media_previous); DeclareCommand("media next", media_next); DeclareCommand("ctrl+f", search); DeclareAlias("search", search); DeclareAlias("find", search); DeclareCommand("ctrl+pgdown", next_chat); DeclareAlias("alt+down", next_chat); DeclareCommand("ctrl+pgup", previous_chat); DeclareAlias("alt+up", previous_chat); if (cPlatform() == dbipMac || cPlatform() == dbipMacOld) { DeclareAlias("meta+tab", next_chat); DeclareAlias("meta+shift+tab", previous_chat); DeclareAlias("meta+backtab", previous_chat); } else { DeclareAlias("ctrl+tab", next_chat); DeclareAlias("ctrl+shift+tab", previous_chat); DeclareAlias("ctrl+backtab", previous_chat); } // other commands here #undef DeclareCommand #undef DeclareAlias } QStringList errors; QMap<QString, ShortcutCommands::Handler> commands; QMap<ShortcutCommands::Handler, QString> commandnames; QMap<QKeySequence, QShortcut*> sequences; QMap<int, ShortcutCommands::Handler> handlers; QSet<QShortcut*> mediaShortcuts; QSet<QString> autoRepeatCommands; QSet<QString> mediaCommands; }; namespace { void createCommand(const QString &command, ShortcutCommands::Handler handler) { Assert(DataPtr != nullptr); Assert(!command.isEmpty()); DataPtr->commands.insert(command, handler); DataPtr->commandnames.insert(handler, command); } QKeySequence setShortcut(const QString &keys, const QString &command) { Assert(DataPtr != nullptr); Assert(!command.isEmpty()); if (keys.isEmpty()) return QKeySequence(); QKeySequence seq(keys, QKeySequence::PortableText); if (seq.isEmpty()) { DataPtr->errors.push_back(qsl("Could not derive key sequence '%1'!").arg(keys)); } else { auto it = DataPtr->commands.constFind(command); if (it == DataPtr->commands.cend()) { LOG(("Warning: could not find shortcut command handler '%1'").arg(command)); } else { auto shortcut = std::make_unique<QShortcut>(seq, Messenger::Instance().getActiveWindow(), nullptr, nullptr, Qt::ApplicationShortcut); if (!DataPtr->autoRepeatCommands.contains(command)) { shortcut->setAutoRepeat(false); } auto isMediaShortcut = DataPtr->mediaCommands.contains(command); if (isMediaShortcut) { shortcut->setEnabled(false); } int shortcutId = shortcut->id(); if (!shortcutId) { DataPtr->errors.push_back(qsl("Could not create shortcut '%1'!").arg(keys)); } else { auto seqIt = DataPtr->sequences.find(seq); if (seqIt == DataPtr->sequences.cend()) { seqIt = DataPtr->sequences.insert(seq, shortcut.release()); } else { auto oldShortcut = seqIt.value(); seqIt.value() = shortcut.release(); destroyShortcut(oldShortcut); } DataPtr->handlers.insert(shortcutId, it.value()); if (isMediaShortcut) { DataPtr->mediaShortcuts.insert(seqIt.value()); } } } } return seq; } QKeySequence removeShortcut(const QString &keys) { Assert(DataPtr != nullptr); if (keys.isEmpty()) return QKeySequence(); QKeySequence seq(keys, QKeySequence::PortableText); if (seq.isEmpty()) { DataPtr->errors.push_back(qsl("Could not derive key sequence '%1'!").arg(keys)); } else { auto seqIt = DataPtr->sequences.find(seq); if (seqIt != DataPtr->sequences.cend()) { auto shortcut = seqIt.value(); DataPtr->sequences.erase(seqIt); destroyShortcut(shortcut); } } return seq; } void destroyShortcut(QShortcut *shortcut) { Assert(DataPtr != nullptr); DataPtr->handlers.remove(shortcut->id()); DataPtr->mediaShortcuts.remove(shortcut); delete shortcut; } } // namespace void start() { Assert(Global::started()); new DataStruct(); // write default shortcuts to a file if they are not there already bool defaultValid = false; QFile defaultFile(cWorkingDir() + qsl("tdata/shortcuts-default.json")); if (defaultFile.open(QIODevice::ReadOnly)) { QJsonParseError error = { 0, QJsonParseError::NoError }; QJsonDocument doc = QJsonDocument::fromJson(base::parse::stripComments(defaultFile.readAll()), &error); defaultFile.close(); if (error.error == QJsonParseError::NoError && doc.isArray()) { QJsonArray shortcuts(doc.array()); if (!shortcuts.isEmpty() && (*shortcuts.constBegin()).isObject()) { QJsonObject versionObject((*shortcuts.constBegin()).toObject()); QJsonObject::const_iterator version = versionObject.constFind(qsl("version")); if (version != versionObject.constEnd() && (*version).isString() && (*version).toString() == QString::number(AppVersion)) { defaultValid = true; } } } } if (!defaultValid && defaultFile.open(QIODevice::WriteOnly)) { const char *defaultHeader = "\ // This is a list of default shortcuts for Telegram Desktop\n\ // Please don't modify it, its content is not used in any way\n\ // You can place your own shortcuts in the 'shortcuts-custom.json' file\n\n"; defaultFile.write(defaultHeader); QJsonArray shortcuts; QJsonObject version; version.insert(qsl("version"), QString::number(AppVersion)); shortcuts.push_back(version); for (auto i = DataPtr->sequences.cbegin(), e = DataPtr->sequences.cend(); i != e; ++i) { auto h = DataPtr->handlers.constFind(i.value()->id()); if (h != DataPtr->handlers.cend()) { auto n = DataPtr->commandnames.constFind(h.value()); if (n != DataPtr->commandnames.cend()) { QJsonObject entry; entry.insert(qsl("keys"), i.key().toString().toLower()); entry.insert(qsl("command"), n.value()); shortcuts.append(entry); } } } QJsonDocument doc; doc.setArray(shortcuts); defaultFile.write(doc.toJson(QJsonDocument::Indented)); defaultFile.close(); } // read custom shortcuts from file if it exists or write an empty custom shortcuts file QFile customFile(cWorkingDir() + qsl("tdata/shortcuts-custom.json")); if (customFile.exists()) { if (customFile.open(QIODevice::ReadOnly)) { QJsonParseError error = { 0, QJsonParseError::NoError }; QJsonDocument doc = QJsonDocument::fromJson(base::parse::stripComments(customFile.readAll()), &error); customFile.close(); if (error.error != QJsonParseError::NoError) { DataPtr->errors.push_back(qsl("Failed to parse! Error: %2").arg(error.errorString())); } else if (!doc.isArray()) { DataPtr->errors.push_back(qsl("Failed to parse! Error: array expected")); } else { QJsonArray shortcuts = doc.array(); int limit = ShortcutsCountLimit; for (QJsonArray::const_iterator i = shortcuts.constBegin(), e = shortcuts.constEnd(); i != e; ++i) { if (!(*i).isObject()) { DataPtr->errors.push_back(qsl("Bad entry! Error: object expected")); } else { QKeySequence seq; QJsonObject entry((*i).toObject()); QJsonObject::const_iterator keys = entry.constFind(qsl("keys")), command = entry.constFind(qsl("command")); if (keys == entry.constEnd() || command == entry.constEnd() || !(*keys).isString() || (!(*command).isString() && !(*command).isNull())) { DataPtr->errors.push_back(qsl("Bad entry! {\"keys\": \"...\", \"command\": [ \"...\" | null ]} expected")); } else if ((*command).isNull()) { seq = removeShortcut((*keys).toString()); } else { seq = setShortcut((*keys).toString(), (*command).toString()); } if (!--limit) { DataPtr->errors.push_back(qsl("Too many entries! Limit is %1").arg(ShortcutsCountLimit)); break; } } } } } else { DataPtr->errors.push_back(qsl("Could not read the file!")); } if (!DataPtr->errors.isEmpty()) { DataPtr->errors.push_front(qsl("While reading file '%1'...").arg(customFile.fileName())); } } else if (customFile.open(QIODevice::WriteOnly)) { const char *customContent = "\ // This is a list of your own shortcuts for Telegram Desktop\n\ // You can see full list of commands in the 'shortcuts-default.json' file\n\ // Place a null value instead of a command string to switch the shortcut off\n\n\ [\n\ // {\n\ // \"command\": \"close_telegram\",\n\ // \"keys\": \"ctrl+f4\"\n\ // },\n\ // {\n\ // \"command\": \"quit_telegram\",\n\ // \"keys\": \"ctrl+q\"\n\ // }\n\ ]\n"; customFile.write(customContent); customFile.close(); } } const QStringList &errors() { Assert(DataPtr != nullptr); return DataPtr->errors; } bool launch(int shortcutId) { Assert(DataPtr != nullptr); auto it = DataPtr->handlers.constFind(shortcutId); if (it == DataPtr->handlers.cend()) { return false; } return (*it.value())(); } bool launch(const QString &command) { Assert(DataPtr != nullptr); auto it = DataPtr->commands.constFind(command); if (it == DataPtr->commands.cend()) { return false; } return (*it.value())(); } void enableMediaShortcuts() { if (!DataPtr) return; for_const (auto shortcut, DataPtr->mediaShortcuts) { shortcut->setEnabled(true); } Platform::SetWatchingMediaKeys(true); } void disableMediaShortcuts() { if (!DataPtr) return; for_const (auto shortcut, DataPtr->mediaShortcuts) { shortcut->setEnabled(false); } Platform::SetWatchingMediaKeys(false); } void finish() { delete DataPtr; DataPtr = nullptr; } } // namespace Shortcuts ������������tdesktop-1.2.17/Telegram/SourceFiles/shortcuts.h����������������������������������������������������0000664�0000000�0000000�00000001230�13262451251�0021626�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace Shortcuts { void start(); const QStringList &errors(); bool launch(int shortcutId); bool launch(const QString &command); // Media shortcuts are not enabled by default, because other // applications also use them. They are enabled only when // the in-app player is active and disabled back after. void enableMediaShortcuts(); void disableMediaShortcuts(); void finish(); } // namespace Shortcuts ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/stdafx.cpp�����������������������������������������������������0000664�0000000�0000000�00000000443�13262451251�0021421�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "stdafx.h" // Precompiled header helper. �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/stdafx.h�������������������������������������������������������0000664�0000000�0000000�00000004326�13262451251�0021072�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #define NOMINMAX // no min() and max() macro declarations #define __HUGE // Fix Google Breakpad build for Mac App Store version #ifdef Q_OS_MAC #define __STDC_FORMAT_MACROS #endif // Q_OS_MAC #ifdef __cplusplus #include <cmath> // False positive warning in clang for QMap member function value: // const T QMap<Key, T>::value(const Key &akey, const T &adefaultValue) // fires with "Returning address of local temporary object" which is not true. #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wreturn-stack-address" #endif // __clang__ #include <QtCore/QtCore> #ifdef __clang__ #pragma clang diagnostic pop #endif // __clang__ #if QT_VERSION < QT_VERSION_CHECK(5, 5, 0) #define OS_MAC_OLD #define RANGES_CXX_THREAD_LOCAL 0 #endif // QT_VERSION < 5.5.0 #ifdef OS_MAC_STORE #define MAC_USE_BREAKPAD #endif // OS_MAC_STORE #include <QtWidgets/QtWidgets> #include <QtNetwork/QtNetwork> #include <array> #include <vector> #include <set> #include <map> #include <unordered_map> #include <algorithm> #include <memory> #include <range/v3/all.hpp> #ifdef Q_OS_WIN #include "platform/win/windows_range_v3_helpers.h" #endif // Q_OS_WIN // Ensures/Expects. #include <gsl/gsl_assert> // Redefine Ensures/Expects by our own assertions. #include "base/assertion.h" #include <gsl/gsl> #include <rpl/rpl.h> #include <crl/crl.h> #include "base/variant.h" #include "base/optional.h" #include "base/algorithm.h" #include "base/functors.h" namespace func = base::functors; #include "base/flat_set.h" #include "base/flat_map.h" #include "core/basic_types.h" #include "logs.h" #include "core/utils.h" #include "base/lambda.h" #include "base/lambda_guard.h" #include "config.h" #include "mtproto/facade.h" #include "ui/style/style_core.h" #include "styles/palette.h" #include "styles/style_basic.h" #include "ui/animation.h" #include "ui/twidget.h" #include "ui/images.h" #include "ui/text/text.h" #include "data/data_types.h" #include "app.h" #include "facades.h" #endif // __cplusplus ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/storage/�������������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0021067�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/storage/file_download.cpp��������������������������������������0000664�0000000�0000000�00000120031�13262451251�0024376�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "storage/file_download.h" #include "data/data_document.h" #include "mainwidget.h" #include "mainwindow.h" #include "messenger.h" #include "storage/localstorage.h" #include "platform/platform_file_utilities.h" #include "auth_session.h" #include "core/crash_reports.h" namespace Storage { Downloader::Downloader() : _delayedLoadersDestroyer([this] { _delayedDestroyedLoaders.clear(); }) { } void Downloader::delayedDestroyLoader(std::unique_ptr<FileLoader> loader) { _delayedDestroyedLoaders.push_back(std::move(loader)); _delayedLoadersDestroyer.call(); } void Downloader::clearPriorities() { ++_priority; } void Downloader::requestedAmountIncrement(MTP::DcId dcId, int index, int amount) { Expects(index >= 0 && index < MTP::kDownloadSessionsCount); auto it = _requestedBytesAmount.find(dcId); if (it == _requestedBytesAmount.cend()) { it = _requestedBytesAmount.emplace(dcId, RequestedInDc { { 0 } }).first; } it->second[index] += amount; if (it->second[index]) { Messenger::Instance().killDownloadSessionsStop(dcId); } else { Messenger::Instance().killDownloadSessionsStart(dcId); } } int Downloader::chooseDcIndexForRequest(MTP::DcId dcId) const { auto result = 0; auto it = _requestedBytesAmount.find(dcId); if (it != _requestedBytesAmount.cend()) { for (auto i = 1; i != MTP::kDownloadSessionsCount; ++i) { if (it->second[i] < it->second[result]) { result = i; } } } return result; } Downloader::~Downloader() { // The file loaders have pointer to downloader and they cancel // requests in destructor where they use that pointer, so all // of them need to be destroyed before any internal state of Downloader. _delayedDestroyedLoaders.clear(); } } // namespace Storage namespace { constexpr auto kDownloadPhotoPartSize = 64 * 1024; // 64kb for photo constexpr auto kDownloadDocumentPartSize = 128 * 1024; // 128kb for document constexpr auto kMaxFileQueries = 16; // max 16 file parts downloaded at the same time constexpr auto kMaxWebFileQueries = 8; // max 8 http[s] files downloaded at the same time constexpr auto kDownloadCdnPartSize = 128 * 1024; // 128kb for cdn requests } // namespace struct FileLoaderQueue { FileLoaderQueue(int queriesLimit) : queriesLimit(queriesLimit) { } int queriesCount = 0; int queriesLimit = 0; FileLoader *start = nullptr; FileLoader *end = nullptr; }; namespace { using LoaderQueues = QMap<int32, FileLoaderQueue>; LoaderQueues queues; FileLoaderQueue _webQueue(kMaxWebFileQueries); QThread *_webLoadThread = nullptr; WebLoadManager *_webLoadManager = nullptr; WebLoadManager *webLoadManager() { return (_webLoadManager && _webLoadManager != FinishedWebLoadManager) ? _webLoadManager : nullptr; } WebLoadMainManager *_webLoadMainManager = nullptr; } // namespace FileLoader::FileLoader(const QString &toFile, int32 size, LocationType locationType, LoadToCacheSetting toCache, LoadFromCloudSetting fromCloud, bool autoLoading) : _downloader(&Auth().downloader()) , _autoLoading(autoLoading) , _filename(toFile) , _file(_filename) , _toCache(toCache) , _fromCloud(fromCloud) , _size(size) , _locationType(locationType) { Expects(!_filename.isEmpty() || (_size <= Storage::kMaxFileInMemory)); } QByteArray FileLoader::imageFormat(const QSize &shrinkBox) const { if (_imageFormat.isEmpty() && _locationType == UnknownFileLocation) { readImage(shrinkBox); } return _imageFormat; } QPixmap FileLoader::imagePixmap(const QSize &shrinkBox) const { if (_imagePixmap.isNull() && _locationType == UnknownFileLocation) { readImage(shrinkBox); } return _imagePixmap; } void FileLoader::readImage(const QSize &shrinkBox) const { auto format = QByteArray(); auto image = App::readImage(_data, &format, false); if (!image.isNull()) { if (!shrinkBox.isEmpty() && (image.width() > shrinkBox.width() || image.height() > shrinkBox.height())) { _imagePixmap = App::pixmapFromImageInPlace(image.scaled(shrinkBox, Qt::KeepAspectRatio, Qt::SmoothTransformation)); } else { _imagePixmap = App::pixmapFromImageInPlace(std::move(image)); } _imageFormat = format; } } float64 FileLoader::currentProgress() const { if (_finished) return 1.; if (!fullSize()) return 0.; return snap(float64(currentOffset()) / fullSize(), 0., 1.); } int32 FileLoader::fullSize() const { return _size; } bool FileLoader::setFileName(const QString &fileName) { if (_toCache != LoadToCacheAsWell || !_filename.isEmpty()) { return fileName.isEmpty() || (fileName == _filename); } _filename = fileName; _file.setFileName(_filename); return true; } void FileLoader::permitLoadFromCloud() { _fromCloud = LoadFromCloudOrLocal; } void FileLoader::loadNext() { if (_queue->queriesCount >= _queue->queriesLimit) { return; } for (auto i = _queue->start; i;) { if (i->loadPart()) { if (_queue->queriesCount >= _queue->queriesLimit) { return; } } else { i = i->_next; } } } void FileLoader::removeFromQueue() { if (!_inQueue) return; if (_next) { _next->_prev = _prev; } if (_prev) { _prev->_next = _next; } if (_queue->end == this) { _queue->end = _prev; } if (_queue->start == this) { _queue->start = _next; } _next = _prev = 0; _inQueue = false; } void FileLoader::pause() { removeFromQueue(); _paused = true; } FileLoader::~FileLoader() { if (_localTaskId) { Local::cancelTask(_localTaskId); } removeFromQueue(); } void FileLoader::localLoaded(const StorageImageSaved &result, const QByteArray &imageFormat, const QPixmap &imagePixmap) { _localTaskId = 0; if (result.data.isEmpty()) { _localStatus = LocalFailed; start(true); return; } _data = result.data; if (!imagePixmap.isNull()) { _imageFormat = imageFormat; _imagePixmap = imagePixmap; } _localStatus = LocalLoaded; if (!_filename.isEmpty() && _toCache == LoadToCacheAsWell) { if (!_fileIsOpen) _fileIsOpen = _file.open(QIODevice::WriteOnly); if (!_fileIsOpen) { cancel(true); return; } if (_file.write(_data) != qint64(_data.size())) { cancel(true); return; } } _finished = true; if (_fileIsOpen) { _file.close(); _fileIsOpen = false; Platform::File::PostprocessDownloaded(QFileInfo(_file).absoluteFilePath()); } _downloader->taskFinished().notify(); emit progress(this); loadNext(); } void FileLoader::start(bool loadFirst, bool prior) { if (_paused) { _paused = false; } if (_finished || tryLoadLocal()) return; if (_fromCloud == LoadFromLocalOnly) { cancel(); return; } if (!_filename.isEmpty() && _toCache == LoadToFileOnly && !_fileIsOpen) { _fileIsOpen = _file.open(QIODevice::WriteOnly); if (!_fileIsOpen) { return cancel(true); } } auto currentPriority = _downloader->currentPriority(); FileLoader *before = 0, *after = 0; if (prior) { if (_inQueue && _priority == currentPriority) { if (loadFirst) { if (!_prev) return startLoading(loadFirst, prior); before = _queue->start; } else { if (!_next || _next->_priority < currentPriority) return startLoading(loadFirst, prior); after = _next; while (after->_next && after->_next->_priority == currentPriority) { after = after->_next; } } } else { _priority = currentPriority; if (loadFirst) { if (_inQueue && !_prev) return startLoading(loadFirst, prior); before = _queue->start; } else { if (_inQueue) { if (_next && _next->_priority == currentPriority) { after = _next; } else if (_prev && _prev->_priority < currentPriority) { before = _prev; while (before->_prev && before->_prev->_priority < currentPriority) { before = before->_prev; } } else { return startLoading(loadFirst, prior); } } else { if (_queue->start && _queue->start->_priority == currentPriority) { after = _queue->start; } else { before = _queue->start; } } if (after) { while (after->_next && after->_next->_priority == currentPriority) { after = after->_next; } } } } } else { if (loadFirst) { if (_inQueue && (!_prev || _prev->_priority == currentPriority)) return startLoading(loadFirst, prior); before = _prev; while (before->_prev && before->_prev->_priority != currentPriority) { before = before->_prev; } } else { if (_inQueue && !_next) return startLoading(loadFirst, prior); after = _queue->end; } } removeFromQueue(); _inQueue = true; if (!_queue->start) { _queue->start = _queue->end = this; } else if (before) { if (before != _next) { _prev = before->_prev; _next = before; _next->_prev = this; if (_prev) { _prev->_next = this; } if (_queue->start->_prev) _queue->start = _queue->start->_prev; } } else if (after) { if (after != _prev) { _next = after->_next; _prev = after; after->_next = this; if (_next) { _next->_prev = this; } if (_queue->end->_next) _queue->end = _queue->end->_next; } } else { LOG(("Queue Error: _start && !before && !after")); } return startLoading(loadFirst, prior); } void FileLoader::cancel() { cancel(false); } void FileLoader::cancel(bool fail) { bool started = currentOffset(true) > 0; cancelRequests(); _cancelled = true; _finished = true; if (_fileIsOpen) { _file.close(); _fileIsOpen = false; _file.remove(); } _data = QByteArray(); removeFromQueue(); if (fail) { emit failed(this, started); } else { emit progress(this); } _filename = QString(); _file.setFileName(_filename); loadNext(); } void FileLoader::startLoading(bool loadFirst, bool prior) { if ((_queue->queriesCount >= _queue->queriesLimit && (!loadFirst || !prior)) || _finished) { return; } loadPart(); } mtpFileLoader::mtpFileLoader(const StorageImageLocation *location, int32 size, LoadFromCloudSetting fromCloud, bool autoLoading) : FileLoader(QString(), size, UnknownFileLocation, LoadToCacheAsWell, fromCloud, autoLoading) , _dcId(location->dc()) , _location(location) { auto shiftedDcId = MTP::downloadDcId(_dcId, 0); auto i = queues.find(shiftedDcId); if (i == queues.cend()) { i = queues.insert(shiftedDcId, FileLoaderQueue(kMaxFileQueries)); } _queue = &i.value(); } mtpFileLoader::mtpFileLoader(int32 dc, uint64 id, uint64 accessHash, int32 version, LocationType type, const QString &to, int32 size, LoadToCacheSetting toCache, LoadFromCloudSetting fromCloud, bool autoLoading) : FileLoader(to, size, type, toCache, fromCloud, autoLoading) , _dcId(dc) , _id(id) , _accessHash(accessHash) , _version(version) { auto shiftedDcId = MTP::downloadDcId(_dcId, 0); auto i = queues.find(shiftedDcId); if (i == queues.cend()) { i = queues.insert(shiftedDcId, FileLoaderQueue(kMaxFileQueries)); } _queue = &i.value(); } mtpFileLoader::mtpFileLoader(const WebFileLocation *location, int32 size, LoadFromCloudSetting fromCloud, bool autoLoading) : FileLoader(QString(), size, UnknownFileLocation, LoadToCacheAsWell, fromCloud, autoLoading) , _dcId(location->dc()) , _urlLocation(location) { auto shiftedDcId = MTP::downloadDcId(_dcId, 0); auto i = queues.find(shiftedDcId); if (i == queues.cend()) { i = queues.insert(shiftedDcId, FileLoaderQueue(kMaxFileQueries)); } _queue = &i.value(); } int32 mtpFileLoader::currentOffset(bool includeSkipped) const { return (_fileIsOpen ? _file.size() : _data.size()) - (includeSkipped ? 0 : _skippedBytes); } bool mtpFileLoader::loadPart() { if (_finished || _lastComplete || (!_sentRequests.empty() && !_size)) { return false; } else if (_size && _nextRequestOffset >= _size) { return false; } makeRequest(_nextRequestOffset); _nextRequestOffset += partSize(); return true; } int mtpFileLoader::partSize() const { return kDownloadCdnPartSize; // Different part sizes are not supported for now :( // Because we start downloading with some part size // and then we get a cdn-redirect where we support only // fixed part size download for hash checking. // //if (_cdnDcId) { // return kDownloadCdnPartSize; //} else if (_locationType == UnknownFileLocation) { // return kDownloadPhotoPartSize; //} //return kDownloadDocumentPartSize; } mtpFileLoader::RequestData mtpFileLoader::prepareRequest(int offset) const { auto result = RequestData(); result.dcId = _cdnDcId ? _cdnDcId : _dcId; result.dcIndex = _size ? _downloader->chooseDcIndexForRequest(result.dcId) : 0; result.offset = offset; return result; } void mtpFileLoader::makeRequest(int offset) { Expects(!_finished); auto requestData = prepareRequest(offset); auto send = [this, &requestData] { auto offset = requestData.offset; auto limit = partSize(); auto shiftedDcId = MTP::downloadDcId(requestData.dcId, requestData.dcIndex); if (_cdnDcId) { Assert(requestData.dcId == _cdnDcId); return MTP::send(MTPupload_GetCdnFile(MTP_bytes(_cdnToken), MTP_int(offset), MTP_int(limit)), rpcDone(&mtpFileLoader::cdnPartLoaded), rpcFail(&mtpFileLoader::cdnPartFailed), shiftedDcId, 50); } else if (_urlLocation) { Assert(requestData.dcId == _dcId); return MTP::send(MTPupload_GetWebFile(MTP_inputWebFileLocation(MTP_bytes(_urlLocation->url()), MTP_long(_urlLocation->accessHash())), MTP_int(offset), MTP_int(limit)), rpcDone(&mtpFileLoader::webPartLoaded), rpcFail(&mtpFileLoader::partFailed), shiftedDcId, 50); } else { Assert(requestData.dcId == _dcId); return MTP::send(MTPupload_GetFile(computeLocation(), MTP_int(offset), MTP_int(limit)), rpcDone(&mtpFileLoader::normalPartLoaded), rpcFail(&mtpFileLoader::partFailed), shiftedDcId, 50); } }; placeSentRequest(send(), requestData); } MTPInputFileLocation mtpFileLoader::computeLocation() const { if (_location) { return MTP_inputFileLocation(MTP_long(_location->volume()), MTP_int(_location->local()), MTP_long(_location->secret())); } return MTP_inputDocumentFileLocation(MTP_long(_id), MTP_long(_accessHash), MTP_int(_version)); } void mtpFileLoader::requestMoreCdnFileHashes() { Expects(!_finished); if (_cdnHashesRequestId || _cdnUncheckedParts.empty()) { return; } auto offset = _cdnUncheckedParts.cbegin()->first; auto requestData = RequestData(); requestData.dcId = _dcId; requestData.dcIndex = 0; requestData.offset = offset; auto shiftedDcId = MTP::downloadDcId(requestData.dcId, requestData.dcIndex); auto requestId = _cdnHashesRequestId = MTP::send( MTPupload_GetCdnFileHashes( MTP_bytes(_cdnToken), MTP_int(offset)), rpcDone(&mtpFileLoader::getCdnFileHashesDone), rpcFail(&mtpFileLoader::cdnPartFailed), shiftedDcId); placeSentRequest(requestId, requestData); } void mtpFileLoader::normalPartLoaded(const MTPupload_File &result, mtpRequestId requestId) { Expects(!_finished); Expects(result.type() == mtpc_upload_fileCdnRedirect || result.type() == mtpc_upload_file); auto offset = finishSentRequestGetOffset(requestId); if (result.type() == mtpc_upload_fileCdnRedirect) { return switchToCDN(offset, result.c_upload_fileCdnRedirect()); } auto bytes = gsl::as_bytes(gsl::make_span(result.c_upload_file().vbytes.v)); return partLoaded(offset, bytes); } void mtpFileLoader::webPartLoaded(const MTPupload_WebFile &result, mtpRequestId requestId) { Expects(result.type() == mtpc_upload_webFile); auto offset = finishSentRequestGetOffset(requestId); auto &webFile = result.c_upload_webFile(); if (!_size) { _size = webFile.vsize.v; } else if (webFile.vsize.v != _size) { LOG(("MTP Error: Bad size provided by bot for webDocument: %1, real: %2").arg(_size).arg(webFile.vsize.v)); return cancel(true); } auto bytes = gsl::as_bytes(gsl::make_span(webFile.vbytes.v)); return partLoaded(offset, bytes); } void mtpFileLoader::cdnPartLoaded(const MTPupload_CdnFile &result, mtpRequestId requestId) { Expects(!_finished); auto offset = finishSentRequestGetOffset(requestId); if (result.type() == mtpc_upload_cdnFileReuploadNeeded) { auto requestData = RequestData(); requestData.dcId = _dcId; requestData.dcIndex = 0; requestData.offset = offset; auto shiftedDcId = MTP::downloadDcId(requestData.dcId, requestData.dcIndex); auto requestId = MTP::send(MTPupload_ReuploadCdnFile(MTP_bytes(_cdnToken), result.c_upload_cdnFileReuploadNeeded().vrequest_token), rpcDone(&mtpFileLoader::reuploadDone), rpcFail(&mtpFileLoader::cdnPartFailed), shiftedDcId); placeSentRequest(requestId, requestData); return; } Expects(result.type() == mtpc_upload_cdnFile); auto key = gsl::as_bytes(gsl::make_span(_cdnEncryptionKey)); auto iv = gsl::as_bytes(gsl::make_span(_cdnEncryptionIV)); Expects(key.size() == MTP::CTRState::KeySize); Expects(iv.size() == MTP::CTRState::IvecSize); auto state = MTP::CTRState(); auto ivec = gsl::as_writeable_bytes(gsl::make_span(state.ivec)); std::copy(iv.begin(), iv.end(), ivec.begin()); auto counterOffset = static_cast<uint32>(offset) >> 4; state.ivec[15] = static_cast<uchar>(counterOffset & 0xFF); state.ivec[14] = static_cast<uchar>((counterOffset >> 8) & 0xFF); state.ivec[13] = static_cast<uchar>((counterOffset >> 16) & 0xFF); state.ivec[12] = static_cast<uchar>((counterOffset >> 24) & 0xFF); auto decryptInPlace = result.c_upload_cdnFile().vbytes.v; MTP::aesCtrEncrypt(decryptInPlace.data(), decryptInPlace.size(), key.data(), &state); auto bytes = gsl::as_bytes(gsl::make_span(decryptInPlace)); switch (checkCdnFileHash(offset, bytes)) { case CheckCdnHashResult::NoHash: { _cdnUncheckedParts.emplace(offset, decryptInPlace); requestMoreCdnFileHashes(); } return; case CheckCdnHashResult::Invalid: { LOG(("API Error: Wrong cdnFileHash for offset %1.").arg(offset)); cancel(true); } return; case CheckCdnHashResult::Good: return partLoaded(offset, bytes); } Unexpected("Result of checkCdnFileHash()"); } mtpFileLoader::CheckCdnHashResult mtpFileLoader::checkCdnFileHash(int offset, base::const_byte_span bytes) { auto cdnFileHashIt = _cdnFileHashes.find(offset); if (cdnFileHashIt == _cdnFileHashes.cend()) { return CheckCdnHashResult::NoHash; } auto realHash = hashSha256(bytes.data(), bytes.size()); if (base::compare_bytes(gsl::as_bytes(gsl::make_span(realHash)), gsl::as_bytes(gsl::make_span(cdnFileHashIt->second.hash)))) { return CheckCdnHashResult::Invalid; } return CheckCdnHashResult::Good; } void mtpFileLoader::reuploadDone(const MTPVector<MTPFileHash> &result, mtpRequestId requestId) { auto offset = finishSentRequestGetOffset(requestId); addCdnHashes(result.v); makeRequest(offset); } void mtpFileLoader::getCdnFileHashesDone(const MTPVector<MTPFileHash> &result, mtpRequestId requestId) { Expects(!_finished); Expects(_cdnHashesRequestId == requestId); _cdnHashesRequestId = 0; const auto offset = finishSentRequestGetOffset(requestId); addCdnHashes(result.v); auto someMoreChecked = false; for (auto i = _cdnUncheckedParts.begin(); i != _cdnUncheckedParts.cend();) { const auto uncheckedOffset = i->first; const auto uncheckedBytes = gsl::as_bytes(gsl::make_span(i->second)); switch (checkCdnFileHash(uncheckedOffset, uncheckedBytes)) { case CheckCdnHashResult::NoHash: { ++i; } break; case CheckCdnHashResult::Invalid: { LOG(("API Error: Wrong cdnFileHash for offset %1.").arg(offset)); cancel(true); return; } break; case CheckCdnHashResult::Good: { someMoreChecked = true; const auto goodOffset = uncheckedOffset; const auto goodBytes = std::move(i->second); const auto goodBytesSpan = gsl::make_span(goodBytes); const auto weak = QPointer<mtpFileLoader>(this); i = _cdnUncheckedParts.erase(i); if (!feedPart(goodOffset, gsl::as_bytes(goodBytesSpan)) || !weak) { return; } else if (_finished) { emit progress(this); loadNext(); return; } } break; default: Unexpected("Result of checkCdnFileHash()"); } } if (someMoreChecked) { emit progress(this); loadNext(); return requestMoreCdnFileHashes(); } LOG(("API Error: Could not find cdnFileHash for offset %1 after getCdnFileHashes request.").arg(offset)); cancel(true); } void mtpFileLoader::placeSentRequest(mtpRequestId requestId, const RequestData &requestData) { Expects(!_finished); _downloader->requestedAmountIncrement(requestData.dcId, requestData.dcIndex, partSize()); ++_queue->queriesCount; _sentRequests.emplace(requestId, requestData); } int mtpFileLoader::finishSentRequestGetOffset(mtpRequestId requestId) { auto it = _sentRequests.find(requestId); Expects(it != _sentRequests.cend()); auto requestData = it->second; _downloader->requestedAmountIncrement(requestData.dcId, requestData.dcIndex, -partSize()); --_queue->queriesCount; _sentRequests.erase(it); return requestData.offset; } bool mtpFileLoader::feedPart(int offset, base::const_byte_span bytes) { Expects(!_finished); if (bytes.size()) { if (_fileIsOpen) { auto fsize = _file.size(); if (offset < fsize) { _skippedBytes -= bytes.size(); } else if (offset > fsize) { _skippedBytes += offset - fsize; } _file.seek(offset); if (_file.write(reinterpret_cast<const char*>(bytes.data()), bytes.size()) != qint64(bytes.size())) { cancel(true); return false; } } else { if (offset > 100 * 1024 * 1024) { // Debugging weird out of memory crashes. auto info = QString("offset: %1, size: %2, cancelled: %3, finished: %4, filename: '%5', tocache: %6, fromcloud: %7, data: %8, fullsize: %9").arg(offset).arg(bytes.size()).arg(Logs::b(_cancelled)).arg(Logs::b(_finished)).arg(_filename).arg(int(_toCache)).arg(int(_fromCloud)).arg(_data.size()).arg(_size); info += QString(", locationtype: %1, inqueue: %2, localstatus: %3").arg(int(_locationType)).arg(Logs::b(_inQueue)).arg(int(_localStatus)); CrashReports::SetAnnotation("DebugInfo", info); } _data.reserve(offset + bytes.size()); if (offset > 100 * 1024 * 1024) { CrashReports::ClearAnnotation("DebugInfo"); } if (offset > _data.size()) { _skippedBytes += offset - _data.size(); _data.resize(offset); } if (offset == _data.size()) { _data.append(reinterpret_cast<const char*>(bytes.data()), bytes.size()); } else { _skippedBytes -= bytes.size(); if (int64(offset + bytes.size()) > _data.size()) { _data.resize(offset + bytes.size()); } auto src = bytes; auto dst = gsl::make_span(_data).subspan(offset, bytes.size()); base::copy_bytes(gsl::as_writeable_bytes(dst), src); } } } if (!bytes.size() || (bytes.size() % 1024)) { // bad next offset _lastComplete = true; } if (_sentRequests.empty() && _cdnUncheckedParts.empty() && (_lastComplete || (_size && _nextRequestOffset >= _size))) { if (!_filename.isEmpty() && (_toCache == LoadToCacheAsWell)) { if (!_fileIsOpen) { _fileIsOpen = _file.open(QIODevice::WriteOnly); } if (!_fileIsOpen || _file.write(_data) != qint64(_data.size())) { cancel(true); return false; } } _finished = true; if (_fileIsOpen) { _file.close(); _fileIsOpen = false; Platform::File::PostprocessDownloaded(QFileInfo(_file).absoluteFilePath()); } removeFromQueue(); if (_localStatus == LocalNotFound || _localStatus == LocalFailed) { if (_urlLocation) { Local::writeImage(storageKey(*_urlLocation), StorageImageSaved(_data)); } else if (_locationType != UnknownFileLocation) { // audio, video, document auto mkey = mediaKey(_locationType, _dcId, _id, _version); if (!_filename.isEmpty()) { Local::writeFileLocation(mkey, FileLocation(_filename)); } if (_toCache == LoadToCacheAsWell) { if (_locationType == DocumentFileLocation) { Local::writeStickerImage(mkey, _data); } else if (_locationType == AudioFileLocation) { Local::writeAudio(mkey, _data); } } } else { Local::writeImage(storageKey(*_location), StorageImageSaved(_data)); } } } if (_finished) { _downloader->taskFinished().notify(); } return true; } void mtpFileLoader::partLoaded(int offset, base::const_byte_span bytes) { if (feedPart(offset, bytes)) { emit progress(this); loadNext(); } } bool mtpFileLoader::partFailed(const RPCError &error) { if (MTP::isDefaultHandledError(error)) return false; cancel(true); return true; } bool mtpFileLoader::cdnPartFailed(const RPCError &error, mtpRequestId requestId) { if (MTP::isDefaultHandledError(error)) return false; if (requestId == _cdnHashesRequestId) { _cdnHashesRequestId = 0; } if (error.type() == qstr("FILE_TOKEN_INVALID") || error.type() == qstr("REQUEST_TOKEN_INVALID")) { auto offset = finishSentRequestGetOffset(requestId); changeCDNParams(offset, 0, QByteArray(), QByteArray(), QByteArray(), QVector<MTPFileHash>()); return true; } return partFailed(error); } void mtpFileLoader::cancelRequests() { while (!_sentRequests.empty()) { auto requestId = _sentRequests.begin()->first; MTP::cancel(requestId); finishSentRequestGetOffset(requestId); } } void mtpFileLoader::switchToCDN( int offset, const MTPDupload_fileCdnRedirect &redirect) { changeCDNParams( offset, redirect.vdc_id.v, redirect.vfile_token.v, redirect.vencryption_key.v, redirect.vencryption_iv.v, redirect.vfile_hashes.v); } void mtpFileLoader::addCdnHashes(const QVector<MTPFileHash> &hashes) { for_const (auto &hash, hashes) { Assert(hash.type() == mtpc_fileHash); auto &data = hash.c_fileHash(); _cdnFileHashes.emplace( data.voffset.v, CdnFileHash { data.vlimit.v, data.vhash.v }); } } void mtpFileLoader::changeCDNParams(int offset, MTP::DcId dcId, const QByteArray &token, const QByteArray &encryptionKey, const QByteArray &encryptionIV, const QVector<MTPFileHash> &hashes) { if (dcId != 0 && (encryptionKey.size() != MTP::CTRState::KeySize || encryptionIV.size() != MTP::CTRState::IvecSize)) { LOG(("Message Error: Wrong key (%1) / iv (%2) size in CDN params").arg(encryptionKey.size()).arg(encryptionIV.size())); cancel(true); return; } auto resendAllRequests = (_cdnDcId != dcId || _cdnToken != token || _cdnEncryptionKey != encryptionKey || _cdnEncryptionIV != encryptionIV); _cdnDcId = dcId; _cdnToken = token; _cdnEncryptionKey = encryptionKey; _cdnEncryptionIV = encryptionIV; addCdnHashes(hashes); if (resendAllRequests && !_sentRequests.empty()) { auto resendOffsets = std::vector<int>(); resendOffsets.reserve(_sentRequests.size()); while (!_sentRequests.empty()) { auto requestId = _sentRequests.begin()->first; MTP::cancel(requestId); auto resendOffset = finishSentRequestGetOffset(requestId); resendOffsets.push_back(resendOffset); } for (auto resendOffset : resendOffsets) { makeRequest(resendOffset); } } makeRequest(offset); } bool mtpFileLoader::tryLoadLocal() { if (_localStatus == LocalNotFound || _localStatus == LocalLoaded || _localStatus == LocalFailed) { return false; } if (_localStatus == LocalLoading) { return true; } if (_urlLocation) { _localTaskId = Local::startImageLoad(storageKey(*_urlLocation), this); } else if (_location) { _localTaskId = Local::startImageLoad(storageKey(*_location), this); } else { if (_toCache == LoadToCacheAsWell) { MediaKey mkey = mediaKey(_locationType, _dcId, _id, _version); if (_locationType == DocumentFileLocation) { _localTaskId = Local::startStickerImageLoad(mkey, this); } else if (_locationType == AudioFileLocation) { _localTaskId = Local::startAudioLoad(mkey, this); } } } emit progress(this); if (_localStatus != LocalNotTried) { return _finished; } else if (_localTaskId) { _localStatus = LocalLoading; return true; } _localStatus = LocalNotFound; return false; } mtpFileLoader::~mtpFileLoader() { cancelRequests(); } webFileLoader::webFileLoader(const QString &url, const QString &to, LoadFromCloudSetting fromCloud, bool autoLoading) : FileLoader(QString(), 0, UnknownFileLocation, LoadToCacheAsWell, fromCloud, autoLoading) , _url(url) , _requestSent(false) , _already(0) { _queue = &_webQueue; } bool webFileLoader::loadPart() { if (_finished || _requestSent || _webLoadManager == FinishedWebLoadManager) return false; if (!_webLoadManager) { _webLoadMainManager = new WebLoadMainManager(); _webLoadThread = new QThread(); _webLoadManager = new WebLoadManager(_webLoadThread); _webLoadThread->start(); } _requestSent = true; _webLoadManager->append(this, _url); return false; } int32 webFileLoader::currentOffset(bool includeSkipped) const { return _already; } void webFileLoader::onProgress(qint64 already, qint64 size) { _size = size; _already = already; emit progress(this); } void webFileLoader::onFinished(const QByteArray &data) { if (_fileIsOpen) { if (_file.write(data.constData(), data.size()) != qint64(data.size())) { return cancel(true); } } else { _data = data; } if (!_filename.isEmpty() && (_toCache == LoadToCacheAsWell)) { if (!_fileIsOpen) _fileIsOpen = _file.open(QIODevice::WriteOnly); if (!_fileIsOpen) { return cancel(true); } if (_file.write(_data) != qint64(_data.size())) { return cancel(true); } } _finished = true; if (_fileIsOpen) { _file.close(); _fileIsOpen = false; Platform::File::PostprocessDownloaded(QFileInfo(_file).absoluteFilePath()); } removeFromQueue(); if (_localStatus == LocalNotFound || _localStatus == LocalFailed) { Local::writeWebFile(_url, _data); } _downloader->taskFinished().notify(); emit progress(this); loadNext(); } void webFileLoader::onError() { cancel(true); } bool webFileLoader::tryLoadLocal() { if (_localStatus == LocalNotFound || _localStatus == LocalLoaded || _localStatus == LocalFailed) { return false; } if (_localStatus == LocalLoading) { return true; } _localTaskId = Local::startWebFileLoad(_url, this); if (_localStatus != LocalNotTried) { return _finished; } else if (_localTaskId) { _localStatus = LocalLoading; return true; } _localStatus = LocalNotFound; return false; } void webFileLoader::cancelRequests() { if (!webLoadManager()) return; webLoadManager()->stop(this); } webFileLoader::~webFileLoader() { } class webFileLoaderPrivate { public: webFileLoaderPrivate(webFileLoader *loader, const QString &url) : _interface(loader) , _url(url) , _redirectsLeft(kMaxHttpRedirects) { } QNetworkReply *reply() { return _reply; } QNetworkReply *request(QNetworkAccessManager &manager, const QString &redirect) { if (!redirect.isEmpty()) _url = redirect; QNetworkRequest req(_url); QByteArray rangeHeaderValue = "bytes=" + QByteArray::number(_already) + "-"; req.setRawHeader("Range", rangeHeaderValue); _reply = manager.get(req); return _reply; } bool oneMoreRedirect() { if (_redirectsLeft) { --_redirectsLeft; return true; } return false; } void setData(const QByteArray &data) { _data = data; } void addData(const QByteArray &data) { _data.append(data); } const QByteArray &data() { return _data; } void setProgress(qint64 already, qint64 size) { _already = already; _size = qMax(size, 0LL); } qint64 size() const { return _size; } qint64 already() const { return _already; } private: static constexpr auto kMaxHttpRedirects = 5; webFileLoader *_interface = nullptr; QUrl _url; qint64 _already = 0; qint64 _size = 0; QNetworkReply *_reply = nullptr; int32 _redirectsLeft = kMaxHttpRedirects; QByteArray _data; friend class WebLoadManager; }; void reinitWebLoadManager() { #ifndef TDESKTOP_DISABLE_NETWORK_PROXY if (webLoadManager()) { webLoadManager()->setProxySettings(App::getHttpProxySettings()); } #endif // !TDESKTOP_DISABLE_NETWORK_PROXY } void stopWebLoadManager() { if (webLoadManager()) { _webLoadThread->quit(); DEBUG_LOG(("Waiting for webloadThread to finish")); _webLoadThread->wait(); delete _webLoadManager; delete _webLoadMainManager; delete _webLoadThread; _webLoadThread = 0; _webLoadMainManager = 0; _webLoadManager = FinishedWebLoadManager; } } #ifndef TDESKTOP_DISABLE_NETWORK_PROXY void WebLoadManager::setProxySettings(const QNetworkProxy &proxy) { QMutexLocker lock(&_loaderPointersMutex); _proxySettings = proxy; emit proxyApplyDelayed(); } #endif // !TDESKTOP_DISABLE_NETWORK_PROXY WebLoadManager::WebLoadManager(QThread *thread) { moveToThread(thread); _manager.moveToThread(thread); connect(thread, SIGNAL(started()), this, SLOT(process())); connect(thread, SIGNAL(finished()), this, SLOT(finish())); connect(this, SIGNAL(processDelayed()), this, SLOT(process()), Qt::QueuedConnection); connect(this, SIGNAL(proxyApplyDelayed()), this, SLOT(proxyApply()), Qt::QueuedConnection); connect(this, SIGNAL(progress(webFileLoader*,qint64,qint64)), _webLoadMainManager, SLOT(progress(webFileLoader*,qint64,qint64))); connect(this, SIGNAL(finished(webFileLoader*,QByteArray)), _webLoadMainManager, SLOT(finished(webFileLoader*,QByteArray))); connect(this, SIGNAL(error(webFileLoader*)), _webLoadMainManager, SLOT(error(webFileLoader*))); connect(&_manager, SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)), this, SLOT(onFailed(QNetworkReply*))); #ifndef OS_MAC_OLD connect(&_manager, SIGNAL(sslErrors(QNetworkReply*,const QList<QSslError>&)), this, SLOT(onFailed(QNetworkReply*))); #endif // OS_MAC_OLD } void WebLoadManager::append(webFileLoader *loader, const QString &url) { loader->_private = new webFileLoaderPrivate(loader, url); QMutexLocker lock(&_loaderPointersMutex); _loaderPointers.insert(loader, loader->_private); emit processDelayed(); } void WebLoadManager::stop(webFileLoader *loader) { QMutexLocker lock(&_loaderPointersMutex); _loaderPointers.remove(loader); emit processDelayed(); } bool WebLoadManager::carries(webFileLoader *loader) const { QMutexLocker lock(&_loaderPointersMutex); return _loaderPointers.contains(loader); } bool WebLoadManager::handleReplyResult(webFileLoaderPrivate *loader, WebReplyProcessResult result) { QMutexLocker lock(&_loaderPointersMutex); LoaderPointers::iterator it = _loaderPointers.find(loader->_interface); if (it != _loaderPointers.cend() && it.key()->_private != loader) { it = _loaderPointers.end(); // it is a new loader which was realloced in the same address } if (it == _loaderPointers.cend()) { return false; } if (result == WebReplyProcessProgress) { if (loader->size() > Storage::kMaxFileInMemory) { LOG(("API Error: too large file is loaded to cache: %1").arg(loader->size())); result = WebReplyProcessError; } } if (result == WebReplyProcessError) { if (it != _loaderPointers.cend()) { emit error(it.key()); } return false; } if (loader->already() < loader->size() || !loader->size()) { emit progress(it.key(), loader->already(), loader->size()); return true; } emit finished(it.key(), loader->data()); return false; } void WebLoadManager::onFailed(QNetworkReply::NetworkError error) { onFailed(qobject_cast<QNetworkReply*>(QObject::sender())); } void WebLoadManager::onFailed(QNetworkReply *reply) { if (!reply) return; reply->deleteLater(); Replies::iterator j = _replies.find(reply); if (j == _replies.cend()) { // handled already return; } webFileLoaderPrivate *loader = j.value(); _replies.erase(j); LOG(("Network Error: Failed to request '%1', error %2 (%3)").arg(QString::fromLatin1(loader->_url.toEncoded())).arg(int(reply->error())).arg(reply->errorString())); if (!handleReplyResult(loader, WebReplyProcessError)) { _loaders.remove(loader); delete loader; } } void WebLoadManager::onProgress(qint64 already, qint64 size) { QNetworkReply *reply = qobject_cast<QNetworkReply*>(QObject::sender()); if (!reply) return; Replies::iterator j = _replies.find(reply); if (j == _replies.cend()) { // handled already return; } webFileLoaderPrivate *loader = j.value(); WebReplyProcessResult result = WebReplyProcessProgress; QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); int32 status = statusCode.isValid() ? statusCode.toInt() : 200; if (status != 200 && status != 206 && status != 416) { if (status == 301 || status == 302) { QString loc = reply->header(QNetworkRequest::LocationHeader).toString(); if (!loc.isEmpty()) { if (loader->oneMoreRedirect()) { sendRequest(loader, loc); return; } else { LOG(("Network Error: Too many HTTP redirects in onFinished() for web file loader: %1").arg(loc)); result = WebReplyProcessError; } } } else { LOG(("Network Error: Bad HTTP status received in WebLoadManager::onProgress(): %1").arg(statusCode.toInt())); result = WebReplyProcessError; } } else { loader->setProgress(already, size); QByteArray r = reply->readAll(); if (!r.isEmpty()) { loader->addData(r); } if (size == 0) { LOG(("Network Error: Zero size received for HTTP download progress in WebLoadManager::onProgress(): %1 / %2").arg(already).arg(size)); result = WebReplyProcessError; } } if (!handleReplyResult(loader, result)) { _replies.erase(j); _loaders.remove(loader); delete loader; reply->abort(); reply->deleteLater(); } } void WebLoadManager::onMeta() { QNetworkReply *reply = qobject_cast<QNetworkReply*>(QObject::sender()); if (!reply) return; Replies::iterator j = _replies.find(reply); if (j == _replies.cend()) { // handled already return; } webFileLoaderPrivate *loader = j.value(); typedef QList<QNetworkReply::RawHeaderPair> Pairs; Pairs pairs = reply->rawHeaderPairs(); for (Pairs::iterator i = pairs.begin(), e = pairs.end(); i != e; ++i) { if (QString::fromUtf8(i->first).toLower() == "content-range") { QRegularExpressionMatch m = QRegularExpression(qsl("/(\\d+)([^\\d]|$)")).match(QString::fromUtf8(i->second)); if (m.hasMatch()) { loader->setProgress(qMax(qint64(loader->data().size()), loader->already()), m.captured(1).toLongLong()); if (!handleReplyResult(loader, WebReplyProcessProgress)) { _replies.erase(j); _loaders.remove(loader); delete loader; reply->abort(); reply->deleteLater(); } } } } } void WebLoadManager::process() { Loaders newLoaders; { QMutexLocker lock(&_loaderPointersMutex); for (LoaderPointers::iterator i = _loaderPointers.begin(), e = _loaderPointers.end(); i != e; ++i) { Loaders::iterator it = _loaders.find(i.value()); if (i.value()) { if (it == _loaders.cend()) { _loaders.insert(i.value()); newLoaders.insert(i.value()); } i.value() = 0; } } for (auto i = _loaders.begin(), e = _loaders.end(); i != e;) { LoaderPointers::iterator it = _loaderPointers.find((*i)->_interface); if (it != _loaderPointers.cend() && it.key()->_private != (*i)) { it = _loaderPointers.end(); } if (it == _loaderPointers.cend()) { if (QNetworkReply *reply = (*i)->reply()) { _replies.remove(reply); reply->abort(); reply->deleteLater(); } delete (*i); i = _loaders.erase(i); } else { ++i; } } } for_const (webFileLoaderPrivate *loader, newLoaders) { if (_loaders.contains(loader)) { sendRequest(loader); } } } void WebLoadManager::sendRequest(webFileLoaderPrivate *loader, const QString &redirect) { Replies::iterator j = _replies.find(loader->reply()); if (j != _replies.cend()) { QNetworkReply *r = j.key(); _replies.erase(j); r->abort(); r->deleteLater(); } QNetworkReply *r = loader->request(_manager, redirect); connect(r, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(onProgress(qint64, qint64))); connect(r, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onFailed(QNetworkReply::NetworkError))); connect(r, SIGNAL(metaDataChanged()), this, SLOT(onMeta())); _replies.insert(r, loader); } void WebLoadManager::proxyApply() { #ifndef TDESKTOP_DISABLE_NETWORK_PROXY QMutexLocker lock(&_loaderPointersMutex); _manager.setProxy(_proxySettings); #endif // !TDESKTOP_DISABLE_NETWORK_PROXY } void WebLoadManager::finish() { clear(); } void WebLoadManager::clear() { QMutexLocker lock(&_loaderPointersMutex); for (LoaderPointers::iterator i = _loaderPointers.begin(), e = _loaderPointers.end(); i != e; ++i) { if (i.value()) { i.key()->_private = 0; } } _loaderPointers.clear(); for_const (webFileLoaderPrivate *loader, _loaders) { delete loader; } _loaders.clear(); for (Replies::iterator i = _replies.begin(), e = _replies.end(); i != e; ++i) { delete i.key(); } _replies.clear(); } WebLoadManager::~WebLoadManager() { clear(); } void WebLoadMainManager::progress(webFileLoader *loader, qint64 already, qint64 size) { if (webLoadManager() && webLoadManager()->carries(loader)) { loader->onProgress(already, size); } } void WebLoadMainManager::finished(webFileLoader *loader, QByteArray data) { if (webLoadManager() && webLoadManager()->carries(loader)) { loader->onFinished(data); } } void WebLoadMainManager::error(webFileLoader *loader) { if (webLoadManager() && webLoadManager()->carries(loader)) { loader->onError(); } } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/storage/file_download.h����������������������������������������0000664�0000000�0000000�00000024307�13262451251�0024054�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "base/observer.h" #include "storage/localimageloader.h" // for TaskId namespace Storage { constexpr auto kMaxFileInMemory = 10 * 1024 * 1024; // 10 MB max file could be hold in memory constexpr auto kMaxVoiceInMemory = 2 * 1024 * 1024; // 2 MB audio is hold in memory and auto loaded constexpr auto kMaxStickerInMemory = 2 * 1024 * 1024; // 2 MB stickers hold in memory, auto loaded and displayed inline constexpr auto kMaxAnimationInMemory = kMaxFileInMemory; // 10 MB gif and mp4 animations held in memory while playing class Downloader final { public: Downloader(); int currentPriority() const { return _priority; } void clearPriorities(); void delayedDestroyLoader(std::unique_ptr<FileLoader> loader); base::Observable<void> &taskFinished() { return _taskFinishedObservable; } void requestedAmountIncrement(MTP::DcId dcId, int index, int amount); int chooseDcIndexForRequest(MTP::DcId dcId) const; ~Downloader(); private: base::Observable<void> _taskFinishedObservable; int _priority = 1; SingleQueuedInvokation _delayedLoadersDestroyer; std::vector<std::unique_ptr<FileLoader>> _delayedDestroyedLoaders; using RequestedInDc = std::array<int64, MTP::kDownloadSessionsCount>; std::map<MTP::DcId, RequestedInDc> _requestedBytesAmount; }; } // namespace Storage struct StorageImageSaved { StorageImageSaved() = default; explicit StorageImageSaved(const QByteArray &data) : data(data) { } QByteArray data; }; enum LocalLoadStatus { LocalNotTried, LocalNotFound, LocalLoading, LocalLoaded, LocalFailed, }; class mtpFileLoader; class webFileLoader; struct FileLoaderQueue; class FileLoader : public QObject { Q_OBJECT public: FileLoader(const QString &toFile, int32 size, LocationType locationType, LoadToCacheSetting, LoadFromCloudSetting fromCloud, bool autoLoading); bool finished() const { return _finished; } bool cancelled() const { return _cancelled; } const QByteArray &bytes() const { return _data; } virtual uint64 objId() const { return 0; } QByteArray imageFormat(const QSize &shrinkBox = QSize()) const; QPixmap imagePixmap(const QSize &shrinkBox = QSize()) const; QString fileName() const { return _filename; } float64 currentProgress() const; virtual int32 currentOffset(bool includeSkipped = false) const = 0; int32 fullSize() const; bool setFileName(const QString &filename); // set filename for loaders to cache void permitLoadFromCloud(); void pause(); void start(bool loadFirst = false, bool prior = true); void cancel(); bool loading() const { return _inQueue; } bool paused() const { return _paused; } bool started() const { return _inQueue || _paused; } bool loadingLocal() const { return (_localStatus == LocalLoading); } bool autoLoading() const { return _autoLoading; } virtual void stop() { } virtual ~FileLoader(); void localLoaded(const StorageImageSaved &result, const QByteArray &imageFormat = QByteArray(), const QPixmap &imagePixmap = QPixmap()); signals: void progress(FileLoader *loader); void failed(FileLoader *loader, bool started); protected: void readImage(const QSize &shrinkBox) const; not_null<Storage::Downloader*> _downloader; FileLoader *_prev = nullptr; FileLoader *_next = nullptr; int _priority = 0; FileLoaderQueue *_queue = nullptr; bool _paused = false; bool _autoLoading = false; bool _inQueue = false; bool _finished = false; bool _cancelled = false; mutable LocalLoadStatus _localStatus = LocalNotTried; virtual bool tryLoadLocal() = 0; virtual void cancelRequests() = 0; void startLoading(bool loadFirst, bool prior); void removeFromQueue(); void cancel(bool failed); void loadNext(); virtual bool loadPart() = 0; QString _filename; QFile _file; bool _fileIsOpen = false; LoadToCacheSetting _toCache; LoadFromCloudSetting _fromCloud; QByteArray _data; int32 _size; LocationType _locationType; TaskId _localTaskId = 0; mutable QByteArray _imageFormat; mutable QPixmap _imagePixmap; }; class StorageImageLocation; class WebFileLocation; class mtpFileLoader : public FileLoader, public RPCSender { Q_OBJECT public: mtpFileLoader(const StorageImageLocation *location, int32 size, LoadFromCloudSetting fromCloud, bool autoLoading); mtpFileLoader(int32 dc, uint64 id, uint64 accessHash, int32 version, LocationType type, const QString &toFile, int32 size, LoadToCacheSetting toCache, LoadFromCloudSetting fromCloud, bool autoLoading); mtpFileLoader(const WebFileLocation *location, int32 size, LoadFromCloudSetting fromCloud, bool autoLoading); int32 currentOffset(bool includeSkipped = false) const override; uint64 objId() const override { return _id; } void stop() override { rpcInvalidate(); } ~mtpFileLoader(); private: struct RequestData { MTP::DcId dcId = 0; int dcIndex = 0; int offset = 0; }; struct CdnFileHash { CdnFileHash(int limit, QByteArray hash) : limit(limit), hash(hash) { } int limit = 0; QByteArray hash; }; bool tryLoadLocal() override; void cancelRequests() override; int partSize() const; RequestData prepareRequest(int offset) const; void makeRequest(int offset); MTPInputFileLocation computeLocation() const; bool loadPart() override; void normalPartLoaded(const MTPupload_File &result, mtpRequestId requestId); void webPartLoaded(const MTPupload_WebFile &result, mtpRequestId requestId); void cdnPartLoaded(const MTPupload_CdnFile &result, mtpRequestId requestId); void reuploadDone(const MTPVector<MTPFileHash> &result, mtpRequestId requestId); void requestMoreCdnFileHashes(); void getCdnFileHashesDone(const MTPVector<MTPFileHash> &result, mtpRequestId requestId); bool feedPart(int offset, base::const_byte_span bytes); void partLoaded(int offset, base::const_byte_span bytes); bool partFailed(const RPCError &error); bool cdnPartFailed(const RPCError &error, mtpRequestId requestId); void placeSentRequest(mtpRequestId requestId, const RequestData &requestData); int finishSentRequestGetOffset(mtpRequestId requestId); void switchToCDN(int offset, const MTPDupload_fileCdnRedirect &redirect); void addCdnHashes(const QVector<MTPFileHash> &hashes); void changeCDNParams(int offset, MTP::DcId dcId, const QByteArray &token, const QByteArray &encryptionKey, const QByteArray &encryptionIV, const QVector<MTPFileHash> &hashes); enum class CheckCdnHashResult { NoHash, Invalid, Good, }; CheckCdnHashResult checkCdnFileHash(int offset, base::const_byte_span bytes); std::map<mtpRequestId, RequestData> _sentRequests; bool _lastComplete = false; int32 _skippedBytes = 0; int32 _nextRequestOffset = 0; MTP::DcId _dcId = 0; // for photo locations const StorageImageLocation *_location = nullptr; uint64 _id = 0; // for document locations uint64 _accessHash = 0; int32 _version = 0; const WebFileLocation *_urlLocation = nullptr; // for webdocument locations MTP::DcId _cdnDcId = 0; QByteArray _cdnToken; QByteArray _cdnEncryptionKey; QByteArray _cdnEncryptionIV; std::map<int, CdnFileHash> _cdnFileHashes; std::map<int, QByteArray> _cdnUncheckedParts; mtpRequestId _cdnHashesRequestId = 0; }; class webFileLoaderPrivate; class webFileLoader : public FileLoader { Q_OBJECT public: webFileLoader(const QString &url, const QString &to, LoadFromCloudSetting fromCloud, bool autoLoading); virtual int32 currentOffset(bool includeSkipped = false) const; virtual webFileLoader *webLoader() { return this; } virtual const webFileLoader *webLoader() const { return this; } void onProgress(qint64 already, qint64 size); void onFinished(const QByteArray &data); void onError(); virtual void stop() { cancelRequests(); } ~webFileLoader(); protected: virtual void cancelRequests(); virtual bool tryLoadLocal(); virtual bool loadPart(); QString _url; bool _requestSent; int32 _already; friend class WebLoadManager; webFileLoaderPrivate *_private; }; enum WebReplyProcessResult { WebReplyProcessError, WebReplyProcessProgress, WebReplyProcessFinished, }; class WebLoadManager : public QObject { Q_OBJECT public: WebLoadManager(QThread *thread); #ifndef TDESKTOP_DISABLE_NETWORK_PROXY void setProxySettings(const QNetworkProxy &proxy); #endif // !TDESKTOP_DISABLE_NETWORK_PROXY void append(webFileLoader *loader, const QString &url); void stop(webFileLoader *reader); bool carries(webFileLoader *reader) const; ~WebLoadManager(); signals: void processDelayed(); void proxyApplyDelayed(); void progress(webFileLoader *loader, qint64 already, qint64 size); void finished(webFileLoader *loader, QByteArray data); void error(webFileLoader *loader); public slots: void onFailed(QNetworkReply *reply); void onFailed(QNetworkReply::NetworkError error); void onProgress(qint64 already, qint64 size); void onMeta(); void process(); void proxyApply(); void finish(); private: void clear(); void sendRequest(webFileLoaderPrivate *loader, const QString &redirect = QString()); bool handleReplyResult(webFileLoaderPrivate *loader, WebReplyProcessResult result); #ifndef TDESKTOP_DISABLE_NETWORK_PROXY QNetworkProxy _proxySettings; #endif // !TDESKTOP_DISABLE_NETWORK_PROXY QNetworkAccessManager _manager; typedef QMap<webFileLoader*, webFileLoaderPrivate*> LoaderPointers; LoaderPointers _loaderPointers; mutable QMutex _loaderPointersMutex; typedef OrderedSet<webFileLoaderPrivate*> Loaders; Loaders _loaders; typedef QMap<QNetworkReply*, webFileLoaderPrivate*> Replies; Replies _replies; }; class WebLoadMainManager : public QObject { Q_OBJECT public: public slots: void progress(webFileLoader *loader, qint64 already, qint64 size); void finished(webFileLoader *loader, QByteArray data); void error(webFileLoader *loader); }; static FileLoader * const CancelledFileLoader = SharedMemoryLocation<FileLoader, 0>(); static mtpFileLoader * const CancelledMtpFileLoader = static_cast<mtpFileLoader*>(CancelledFileLoader); static webFileLoader * const CancelledWebFileLoader = static_cast<webFileLoader*>(CancelledFileLoader); static WebLoadManager * const FinishedWebLoadManager = SharedMemoryLocation<WebLoadManager, 0>(); void reinitWebLoadManager(); void stopWebLoadManager(); �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/storage/file_upload.cpp����������������������������������������0000664�0000000�0000000�00000034143�13262451251�0024063�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "storage/file_upload.h" #include "storage/localimageloader.h" #include "data/data_document.h" #include "data/data_photo.h" #include "data/data_session.h" #include "auth_session.h" namespace Storage { namespace { constexpr auto kMaxUploadFileParallelSize = MTP::kUploadSessionsCount * 512 * 1024; // max 512kb uploaded at the same time in each session } // namespace struct Uploader::File { File(const SendMediaReady &media); File(const std::shared_ptr<FileLoadResult> &file); void setDocSize(int32 size); bool setPartSize(uint32 partSize); std::shared_ptr<FileLoadResult> file; SendMediaReady media; int32 partsCount; mutable int32 fileSentSize; uint64 id() const; SendMediaType type() const; uint64 thumbId() const; const QString &filename() const; HashMd5 md5Hash; std::unique_ptr<QFile> docFile; int32 docSentParts = 0; int32 docSize = 0; int32 docPartSize = 0; int32 docPartsCount = 0; }; Uploader::File::File(const SendMediaReady &media) : media(media) { partsCount = media.parts.size(); if (type() == SendMediaType::File || type() == SendMediaType::Audio) { setDocSize(media.file.isEmpty() ? media.data.size() : media.filesize); } else { docSize = docPartSize = docPartsCount = 0; } } Uploader::File::File(const std::shared_ptr<FileLoadResult> &file) : file(file) { partsCount = (type() == SendMediaType::Photo) ? file->fileparts.size() : file->thumbparts.size(); if (type() == SendMediaType::File || type() == SendMediaType::Audio) { setDocSize(file->filesize); } else { docSize = docPartSize = docPartsCount = 0; } } void Uploader::File::setDocSize(int32 size) { docSize = size; constexpr auto limit0 = 1024 * 1024; constexpr auto limit1 = 32 * limit0; if (docSize >= limit0 || !setPartSize(DocumentUploadPartSize0)) { if (docSize > limit1 || !setPartSize(DocumentUploadPartSize1)) { if (!setPartSize(DocumentUploadPartSize2)) { if (!setPartSize(DocumentUploadPartSize3)) { if (!setPartSize(DocumentUploadPartSize4)) { LOG(("Upload Error: bad doc size: %1").arg(docSize)); } } } } } } bool Uploader::File::setPartSize(uint32 partSize) { docPartSize = partSize; docPartsCount = (docSize / docPartSize) + ((docSize % docPartSize) ? 1 : 0); return (docPartsCount <= DocumentMaxPartsCount); } uint64 Uploader::File::id() const { return file ? file->id : media.id; } SendMediaType Uploader::File::type() const { return file ? file->type : media.type; } uint64 Uploader::File::thumbId() const { return file ? file->thumbId : media.thumbId; } const QString &Uploader::File::filename() const { return file ? file->filename : media.filename; } Uploader::Uploader() { nextTimer.setSingleShot(true); connect(&nextTimer, SIGNAL(timeout()), this, SLOT(sendNext())); killSessionsTimer.setSingleShot(true); connect(&killSessionsTimer, SIGNAL(timeout()), this, SLOT(killSessions())); } void Uploader::uploadMedia(const FullMsgId &msgId, const SendMediaReady &media) { if (media.type == SendMediaType::Photo) { Auth().data().photo(media.photo, media.photoThumbs); } else if (media.type == SendMediaType::File || media.type == SendMediaType::Audio) { const auto document = media.photoThumbs.isEmpty() ? Auth().data().document(media.document) : Auth().data().document(media.document, media.photoThumbs.begin().value()); if (!media.data.isEmpty()) { document->setData(media.data); } if (!media.file.isEmpty()) { document->setLocation(FileLocation(media.file)); } } queue.emplace(msgId, File(media)); sendNext(); } void Uploader::upload( const FullMsgId &msgId, const std::shared_ptr<FileLoadResult> &file) { if (file->type == SendMediaType::Photo) { auto photo = Auth().data().photo(file->photo, file->photoThumbs); photo->uploadingData = std::make_unique<Data::UploadState>(file->partssize); } else if (file->type == SendMediaType::File || file->type == SendMediaType::Audio) { auto document = file->thumb.isNull() ? Auth().data().document(file->document) : Auth().data().document(file->document, file->thumb); document->uploadingData = std::make_unique<Data::UploadState>(document->size); if (!file->content.isEmpty()) { document->setData(file->content); } if (!file->filepath.isEmpty()) { document->setLocation(FileLocation(file->filepath)); } } queue.emplace(msgId, File(file)); sendNext(); } void Uploader::currentFailed() { auto j = queue.find(uploadingId); if (j != queue.end()) { if (j->second.type() == SendMediaType::Photo) { emit photoFailed(j->first); } else if (j->second.type() == SendMediaType::File) { const auto document = Auth().data().document(j->second.id()); if (document->uploading()) { document->status = FileUploadFailed; } emit documentFailed(j->first); } queue.erase(j); } requestsSent.clear(); docRequestsSent.clear(); dcMap.clear(); uploadingId = FullMsgId(); sentSize = 0; for (int i = 0; i < MTP::kUploadSessionsCount; ++i) { sentSizes[i] = 0; } sendNext(); } void Uploader::killSessions() { for (int i = 0; i < MTP::kUploadSessionsCount; ++i) { MTP::stopSession(MTP::uploadDcId(i)); } } void Uploader::sendNext() { if (sentSize >= kMaxUploadFileParallelSize || _pausedId.msg) return; bool killing = killSessionsTimer.isActive(); if (queue.empty()) { if (!killing) { killSessionsTimer.start(MTPAckSendWaiting + MTPKillFileSessionTimeout); } return; } if (killing) { killSessionsTimer.stop(); } auto i = uploadingId.msg ? queue.find(uploadingId) : queue.begin(); if (!uploadingId.msg) { uploadingId = i->first; } else if (i == queue.end()) { i = queue.begin(); uploadingId = i->first; } auto &uploadingData = i->second; auto todc = 0; for (auto dc = 1; dc != MTP::kUploadSessionsCount; ++dc) { if (sentSizes[dc] < sentSizes[todc]) { todc = dc; } } auto &parts = uploadingData.file ? (uploadingData.type() == SendMediaType::Photo ? uploadingData.file->fileparts : uploadingData.file->thumbparts) : uploadingData.media.parts; const auto partsOfId = uploadingData.file ? (uploadingData.type() == SendMediaType::Photo ? uploadingData.file->id : uploadingData.file->thumbId) : uploadingData.media.thumbId; if (parts.isEmpty()) { if (uploadingData.docSentParts >= uploadingData.docPartsCount) { if (requestsSent.empty() && docRequestsSent.empty()) { const auto silent = uploadingData.file && uploadingData.file->to.silent; if (uploadingData.type() == SendMediaType::Photo) { auto photoFilename = uploadingData.filename(); if (!photoFilename.endsWith(qstr(".jpg"), Qt::CaseInsensitive)) { // Server has some extensions checking for inputMediaUploadedPhoto, // so force the extension to be .jpg anyway. It doesn't matter, // because the filename from inputFile is not used anywhere. photoFilename += qstr(".jpg"); } const auto md5 = uploadingData.file ? uploadingData.file->filemd5 : uploadingData.media.jpeg_md5; const auto file = MTP_inputFile( MTP_long(uploadingData.id()), MTP_int(uploadingData.partsCount), MTP_string(photoFilename), MTP_bytes(md5)); emit photoReady(uploadingId, silent, file); } else if (uploadingData.type() == SendMediaType::File || uploadingData.type() == SendMediaType::Audio) { QByteArray docMd5(32, Qt::Uninitialized); hashMd5Hex(uploadingData.md5Hash.result(), docMd5.data()); const auto file = (uploadingData.docSize > UseBigFilesFrom) ? MTP_inputFileBig( MTP_long(uploadingData.id()), MTP_int(uploadingData.docPartsCount), MTP_string(uploadingData.filename())) : MTP_inputFile( MTP_long(uploadingData.id()), MTP_int(uploadingData.docPartsCount), MTP_string(uploadingData.filename()), MTP_bytes(docMd5)); if (uploadingData.partsCount) { const auto thumbFilename = uploadingData.file ? uploadingData.file->thumbname : (qsl("thumb.") + uploadingData.media.thumbExt); const auto thumbMd5 = uploadingData.file ? uploadingData.file->thumbmd5 : uploadingData.media.jpeg_md5; const auto thumb = MTP_inputFile( MTP_long(uploadingData.thumbId()), MTP_int(uploadingData.partsCount), MTP_string(thumbFilename), MTP_bytes(thumbMd5)); emit thumbDocumentReady( uploadingId, silent, file, thumb); } else { emit documentReady(uploadingId, silent, file); } } queue.erase(uploadingId); uploadingId = FullMsgId(); sendNext(); } return; } auto &content = uploadingData.file ? uploadingData.file->content : uploadingData.media.data; QByteArray toSend; if (content.isEmpty()) { if (!uploadingData.docFile) { const auto filepath = uploadingData.file ? uploadingData.file->filepath : uploadingData.media.file; uploadingData.docFile = std::make_unique<QFile>(filepath); if (!uploadingData.docFile->open(QIODevice::ReadOnly)) { currentFailed(); return; } } toSend = uploadingData.docFile->read(uploadingData.docPartSize); if (uploadingData.docSize <= UseBigFilesFrom) { uploadingData.md5Hash.feed(toSend.constData(), toSend.size()); } } else { const auto offset = uploadingData.docSentParts * uploadingData.docPartSize; toSend = content.mid(offset, uploadingData.docPartSize); if ((uploadingData.type() == SendMediaType::File || uploadingData.type() == SendMediaType::Audio) && uploadingData.docSentParts <= UseBigFilesFrom) { uploadingData.md5Hash.feed(toSend.constData(), toSend.size()); } } if ((toSend.size() > uploadingData.docPartSize) || ((toSend.size() < uploadingData.docPartSize && uploadingData.docSentParts + 1 != uploadingData.docPartsCount))) { currentFailed(); return; } mtpRequestId requestId; if (uploadingData.docSize > UseBigFilesFrom) { requestId = MTP::send( MTPupload_SaveBigFilePart( MTP_long(uploadingData.id()), MTP_int(uploadingData.docSentParts), MTP_int(uploadingData.docPartsCount), MTP_bytes(toSend)), rpcDone(&Uploader::partLoaded), rpcFail(&Uploader::partFailed), MTP::uploadDcId(todc)); } else { requestId = MTP::send( MTPupload_SaveFilePart( MTP_long(uploadingData.id()), MTP_int(uploadingData.docSentParts), MTP_bytes(toSend)), rpcDone(&Uploader::partLoaded), rpcFail(&Uploader::partFailed), MTP::uploadDcId(todc)); } docRequestsSent.emplace(requestId, uploadingData.docSentParts); dcMap.emplace(requestId, todc); sentSize += uploadingData.docPartSize; sentSizes[todc] += uploadingData.docPartSize; uploadingData.docSentParts++; } else { auto part = parts.begin(); const auto requestId = MTP::send( MTPupload_SaveFilePart( MTP_long(partsOfId), MTP_int(part.key()), MTP_bytes(part.value())), rpcDone(&Uploader::partLoaded), rpcFail(&Uploader::partFailed), MTP::uploadDcId(todc)); requestsSent.emplace(requestId, part.value()); dcMap.emplace(requestId, todc); sentSize += part.value().size(); sentSizes[todc] += part.value().size(); parts.erase(part); } nextTimer.start(UploadRequestInterval); } void Uploader::cancel(const FullMsgId &msgId) { uploaded.erase(msgId); if (uploadingId == msgId) { currentFailed(); } else { queue.erase(msgId); } } void Uploader::pause(const FullMsgId &msgId) { _pausedId = msgId; } void Uploader::unpause() { _pausedId = FullMsgId(); sendNext(); } void Uploader::confirm(const FullMsgId &msgId) { } void Uploader::clear() { uploaded.clear(); queue.clear(); for (const auto &requestData : requestsSent) { MTP::cancel(requestData.first); } requestsSent.clear(); for (const auto &requestData : docRequestsSent) { MTP::cancel(requestData.first); } docRequestsSent.clear(); dcMap.clear(); sentSize = 0; for (int i = 0; i < MTP::kUploadSessionsCount; ++i) { MTP::stopSession(MTP::uploadDcId(i)); sentSizes[i] = 0; } killSessionsTimer.stop(); } void Uploader::partLoaded(const MTPBool &result, mtpRequestId requestId) { auto j = docRequestsSent.end(); auto i = requestsSent.find(requestId); if (i == requestsSent.cend()) { j = docRequestsSent.find(requestId); } if (i != requestsSent.cend() || j != docRequestsSent.cend()) { if (mtpIsFalse(result)) { // failed to upload current file currentFailed(); return; } else { auto dcIt = dcMap.find(requestId); if (dcIt == dcMap.cend()) { // must not happen currentFailed(); return; } auto dc = dcIt->second; dcMap.erase(dcIt); int32 sentPartSize = 0; auto k = queue.find(uploadingId); Assert(k != queue.cend()); auto &[fullId, file] = *k; if (i != requestsSent.cend()) { sentPartSize = i->second.size(); requestsSent.erase(i); } else { sentPartSize = file.docPartSize; docRequestsSent.erase(j); } sentSize -= sentPartSize; sentSizes[dc] -= sentPartSize; if (file.type() == SendMediaType::Photo) { file.fileSentSize += sentPartSize; const auto photo = Auth().data().photo(file.id()); if (photo->uploading() && file.file) { photo->uploadingData->size = file.file->partssize; photo->uploadingData->offset = file.fileSentSize; } emit photoProgress(fullId); } else if (file.type() == SendMediaType::File || file.type() == SendMediaType::Audio) { const auto document = Auth().data().document(file.id()); if (document->uploading()) { const auto doneParts = file.docSentParts - int(docRequestsSent.size()); document->uploadingData->offset = std::min( document->uploadingData->size, doneParts * file.docPartSize); } emit documentProgress(fullId); } } } sendNext(); } bool Uploader::partFailed(const RPCError &error, mtpRequestId requestId) { if (MTP::isDefaultHandledError(error)) return false; // failed to upload current file if ((requestsSent.find(requestId) != requestsSent.cend()) || (docRequestsSent.find(requestId) != docRequestsSent.cend())) { currentFailed(); } sendNext(); return true; } Uploader::~Uploader() { clear(); } } // namespace Storage �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/storage/file_upload.h������������������������������������������0000664�0000000�0000000�00000003672�13262451251�0023533�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once struct FileLoadResult; struct SendMediaReady; namespace Storage { class Uploader : public QObject, public RPCSender { Q_OBJECT public: Uploader(); void uploadMedia(const FullMsgId &msgId, const SendMediaReady &image); void upload( const FullMsgId &msgId, const std::shared_ptr<FileLoadResult> &file); int32 currentOffset(const FullMsgId &msgId) const; // -1 means file not found int32 fullSize(const FullMsgId &msgId) const; void cancel(const FullMsgId &msgId); void pause(const FullMsgId &msgId); void confirm(const FullMsgId &msgId); void clear(); ~Uploader(); public slots: void unpause(); void sendNext(); void killSessions(); signals: void photoReady(const FullMsgId &msgId, bool silent, const MTPInputFile &file); void documentReady(const FullMsgId &msgId, bool silent, const MTPInputFile &file); void thumbDocumentReady(const FullMsgId &msgId, bool silent, const MTPInputFile &file, const MTPInputFile &thumb); void photoProgress(const FullMsgId &msgId); void documentProgress(const FullMsgId &msgId); void photoFailed(const FullMsgId &msgId); void documentFailed(const FullMsgId &msgId); private: struct File; void partLoaded(const MTPBool &result, mtpRequestId requestId); bool partFailed(const RPCError &err, mtpRequestId requestId); void currentFailed(); base::flat_map<mtpRequestId, QByteArray> requestsSent; base::flat_map<mtpRequestId, int32> docRequestsSent; base::flat_map<mtpRequestId, int32> dcMap; uint32 sentSize = 0; uint32 sentSizes[MTP::kUploadSessionsCount] = { 0 }; FullMsgId uploadingId; FullMsgId _pausedId; std::map<FullMsgId, File> queue; std::map<FullMsgId, File> uploaded; QTimer nextTimer, killSessionsTimer; }; } // namespace Storage ����������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/storage/localimageloader.cpp�����������������������������������0000664�0000000�0000000�00000046327�13262451251�0025073�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "storage/localimageloader.h" #include "data/data_document.h" #include "core/file_utilities.h" #include "media/media_audio.h" #include "boxes/send_files_box.h" #include "media/media_clip_reader.h" #include "mainwidget.h" #include "mainwindow.h" #include "lang/lang_keys.h" #include "boxes/confirm_box.h" #include "storage/file_download.h" #include "storage/storage_media_prepare.h" using Storage::ValidateThumbDimensions; TaskQueue::TaskQueue(TimeMs stopTimeoutMs) { if (stopTimeoutMs > 0) { _stopTimer = new QTimer(this); connect(_stopTimer, SIGNAL(timeout()), this, SLOT(stop())); _stopTimer->setSingleShot(true); _stopTimer->setInterval(int(stopTimeoutMs)); } } TaskId TaskQueue::addTask(std::unique_ptr<Task> &&task) { const auto result = task->id(); { QMutexLocker lock(&_tasksToProcessMutex); _tasksToProcess.push_back(std::move(task)); } wakeThread(); return result; } void TaskQueue::addTasks(std::vector<std::unique_ptr<Task>> &&tasks) { { QMutexLocker lock(&_tasksToProcessMutex); for (auto &task : tasks) { _tasksToProcess.push_back(std::move(task)); } } wakeThread(); } void TaskQueue::wakeThread() { if (!_thread) { _thread = new QThread(); _worker = new TaskQueueWorker(this); _worker->moveToThread(_thread); connect(this, SIGNAL(taskAdded()), _worker, SLOT(onTaskAdded())); connect(_worker, SIGNAL(taskProcessed()), this, SLOT(onTaskProcessed())); _thread->start(); } if (_stopTimer) _stopTimer->stop(); emit taskAdded(); } void TaskQueue::cancelTask(TaskId id) { const auto removeFrom = [&](std::deque<std::unique_ptr<Task>> &queue) { const auto proj = [](const std::unique_ptr<Task> &task) { return task->id(); }; auto i = ranges::find(queue, id, proj); if (i != queue.end()) { queue.erase(i); } }; { QMutexLocker lock(&_tasksToProcessMutex); removeFrom(_tasksToProcess); if (_taskInProcessId == id) { _taskInProcessId = TaskId(); } } QMutexLocker lock(&_tasksToFinishMutex); removeFrom(_tasksToFinish); } void TaskQueue::onTaskProcessed() { do { auto task = std::unique_ptr<Task>(); { QMutexLocker lock(&_tasksToFinishMutex); if (_tasksToFinish.empty()) break; task = std::move(_tasksToFinish.front()); _tasksToFinish.pop_front(); } task->finish(); } while (true); if (_stopTimer) { QMutexLocker lock(&_tasksToProcessMutex); if (_tasksToProcess.empty() && !_taskInProcessId) { _stopTimer->start(); } } } void TaskQueue::stop() { if (_thread) { _thread->requestInterruption(); _thread->quit(); DEBUG_LOG(("Waiting for taskThread to finish")); _thread->wait(); delete _worker; delete _thread; _worker = 0; _thread = 0; } _tasksToProcess.clear(); _tasksToFinish.clear(); _taskInProcessId = TaskId(); } TaskQueue::~TaskQueue() { stop(); delete _stopTimer; } void TaskQueueWorker::onTaskAdded() { if (_inTaskAdded) return; _inTaskAdded = true; bool someTasksLeft = false; do { auto task = std::unique_ptr<Task>(); { QMutexLocker lock(&_queue->_tasksToProcessMutex); if (!_queue->_tasksToProcess.empty()) { task = std::move(_queue->_tasksToProcess.front()); _queue->_tasksToProcess.pop_front(); _queue->_taskInProcessId = task->id(); } } if (task) { task->process(); bool emitTaskProcessed = false; { QMutexLocker lockToProcess(&_queue->_tasksToProcessMutex); if (_queue->_taskInProcessId == task->id()) { _queue->_taskInProcessId = TaskId(); someTasksLeft = !_queue->_tasksToProcess.empty(); QMutexLocker lockToFinish(&_queue->_tasksToFinishMutex); emitTaskProcessed = _queue->_tasksToFinish.empty(); _queue->_tasksToFinish.push_back(std::move(task)); } } if (emitTaskProcessed) { emit taskProcessed(); } } QCoreApplication::processEvents(); } while (someTasksLeft && !thread()->isInterruptionRequested()); _inTaskAdded = false; } SendingAlbum::SendingAlbum() : groupId(rand_value<uint64>()) { } FileLoadResult::FileLoadResult( TaskId taskId, uint64 id, const FileLoadTo &to, const QString &caption, std::shared_ptr<SendingAlbum> album) : taskId(taskId) , id(id) , to(to) , album(std::move(album)) , caption(caption) { } FileLoadTask::FileLoadTask( const QString &filepath, const QByteArray &content, std::unique_ptr<FileMediaInformation> information, SendMediaType type, const FileLoadTo &to, const QString &caption, std::shared_ptr<SendingAlbum> album) : _id(rand_value<uint64>()) , _to(to) , _album(std::move(album)) , _filepath(filepath) , _content(content) , _information(std::move(information)) , _type(type) , _caption(caption) { } FileLoadTask::FileLoadTask( const QByteArray &voice, int32 duration, const VoiceWaveform &waveform, const FileLoadTo &to, const QString &caption) : _id(rand_value<uint64>()) , _to(to) , _content(voice) , _duration(duration) , _waveform(waveform) , _type(SendMediaType::Audio) , _caption(caption) { } std::unique_ptr<FileMediaInformation> FileLoadTask::ReadMediaInformation( const QString &filepath, const QByteArray &content, const QString &filemime) { auto result = std::make_unique<FileMediaInformation>(); result->filemime = filemime; if (CheckForSong(filepath, content, result)) { return result; } else if (CheckForVideo(filepath, content, result)) { return result; } else if (CheckForImage(filepath, content, result)) { return result; } return result; } template <typename Mimes, typename Extensions> bool FileLoadTask::CheckMimeOrExtensions( const QString &filepath, const QString &filemime, Mimes &mimes, Extensions &extensions) { if (std::find(std::begin(mimes), std::end(mimes), filemime) != std::end(mimes)) { return true; } if (std::find_if(std::begin(extensions), std::end(extensions), [&filepath](auto &extension) { return filepath.endsWith(extension, Qt::CaseInsensitive); }) != std::end(extensions)) { return true; } return false; } bool FileLoadTask::CheckForSong( const QString &filepath, const QByteArray &content, std::unique_ptr<FileMediaInformation> &result) { static const auto mimes = { qstr("audio/mp3"), qstr("audio/m4a"), qstr("audio/aac"), qstr("audio/ogg"), qstr("audio/flac"), }; static const auto extensions = { qstr(".mp3"), qstr(".m4a"), qstr(".aac"), qstr(".ogg"), qstr(".flac"), }; if (!CheckMimeOrExtensions(filepath, result->filemime, mimes, extensions)) { return false; } auto media = Media::Player::PrepareForSending(filepath, content); if (media.duration < 0) { return false; } if (!ValidateThumbDimensions(media.cover.width(), media.cover.height())) { media.cover = QImage(); } result->media = std::move(media); return true; } bool FileLoadTask::CheckForVideo( const QString &filepath, const QByteArray &content, std::unique_ptr<FileMediaInformation> &result) { static const auto mimes = { qstr("video/mp4"), qstr("video/quicktime"), }; static const auto extensions = { qstr(".mp4"), qstr(".mov"), }; if (!CheckMimeOrExtensions(filepath, result->filemime, mimes, extensions)) { return false; } auto media = Media::Clip::PrepareForSending(filepath, content); if (media.duration < 0) { return false; } auto coverWidth = media.thumbnail.width(); auto coverHeight = media.thumbnail.height(); if (!ValidateThumbDimensions(coverWidth, coverHeight)) { return false; } if (filepath.endsWith(qstr(".mp4"), Qt::CaseInsensitive)) { result->filemime = qstr("video/mp4"); } result->media = std::move(media); return true; } bool FileLoadTask::CheckForImage( const QString &filepath, const QByteArray &content, std::unique_ptr<FileMediaInformation> &result) { auto animated = false; auto image = ([&filepath, &content, &animated] { if (!content.isEmpty()) { return App::readImage(content, nullptr, false, &animated); } else if (!filepath.isEmpty()) { return App::readImage(filepath, nullptr, false, &animated); } return QImage(); })(); return FillImageInformation(std::move(image), animated, result); } bool FileLoadTask::FillImageInformation( QImage &&image, bool animated, std::unique_ptr<FileMediaInformation> &result) { Expects(result != nullptr); if (image.isNull()) { return false; } auto media = FileMediaInformation::Image(); media.data = std::move(image); media.animated = animated; result->media = media; return true; } void FileLoadTask::process() { const auto stickerMime = qsl("image/webp"); _result = std::make_shared<FileLoadResult>( id(), _id, _to, _caption, _album); QString filename, filemime; qint64 filesize = 0; QByteArray filedata; uint64 thumbId = 0; auto thumbname = qsl("thumb.jpg"); QByteArray thumbdata; auto isAnimation = false; auto isSong = false; auto isVideo = false; auto isVoice = (_type == SendMediaType::Audio); auto fullimage = QImage(); auto info = _filepath.isEmpty() ? QFileInfo() : QFileInfo(_filepath); if (info.exists()) { if (info.isDir()) { _result->filesize = -1; return; } // Voice sending is supported only from memory for now. // Because for voice we force mime type and don't read MediaInformation. // For a real file we always read mime type and read MediaInformation. Assert(!isVoice); filesize = info.size(); filename = info.fileName(); if (!_information) { _information = readMediaInformation(mimeTypeForFile(info).name()); } filemime = _information->filemime; if (auto image = base::get_if<FileMediaInformation::Image>( &_information->media)) { fullimage = base::take(image->data); if (auto opaque = (filemime != stickerMime)) { fullimage = Images::prepareOpaque(std::move(fullimage)); } isAnimation = image->animated; } } else if (!_content.isEmpty()) { filesize = _content.size(); if (isVoice) { filename = filedialogDefaultName(qsl("audio"), qsl(".ogg"), QString(), true); filemime = "audio/ogg"; } else { if (_information) { if (auto image = base::get_if<FileMediaInformation::Image>( &_information->media)) { fullimage = base::take(image->data); } } auto mimeType = mimeTypeForData(_content); filemime = mimeType.name(); if (filemime != stickerMime) { fullimage = Images::prepareOpaque(std::move(fullimage)); } if (filemime == "image/jpeg") { filename = filedialogDefaultName(qsl("photo"), qsl(".jpg"), QString(), true); } else if (filemime == "image/png") { filename = filedialogDefaultName(qsl("image"), qsl(".png"), QString(), true); } else { QString ext; QStringList patterns = mimeType.globPatterns(); if (!patterns.isEmpty()) { ext = patterns.front().replace('*', QString()); } filename = filedialogDefaultName(qsl("file"), ext, QString(), true); } } } else { if (_information) { if (auto image = base::get_if<FileMediaInformation::Image>( &_information->media)) { fullimage = base::take(image->data); } } if (!fullimage.isNull() && fullimage.width() > 0) { if (_type == SendMediaType::Photo) { if (ValidateThumbDimensions(fullimage.width(), fullimage.height())) { filesize = -1; // Fill later. filemime = mimeTypeForName("image/jpeg").name(); filename = filedialogDefaultName(qsl("image"), qsl(".jpg"), QString(), true); } else { _type = SendMediaType::File; } } if (_type == SendMediaType::File) { filemime = mimeTypeForName("image/png").name(); filename = filedialogDefaultName(qsl("image"), qsl(".png"), QString(), true); { QBuffer buffer(&_content); fullimage.save(&buffer, "PNG"); } filesize = _content.size(); } fullimage = Images::prepareOpaque(std::move(fullimage)); } } _result->filesize = (int32)qMin(filesize, qint64(INT_MAX)); if (!filesize || filesize > App::kFileSizeLimit) { return; } PreparedPhotoThumbs photoThumbs; QVector<MTPPhotoSize> photoSizes; QPixmap thumb; QVector<MTPDocumentAttribute> attributes(1, MTP_documentAttributeFilename(MTP_string(filename))); auto thumbSize = MTP_photoSizeEmpty(MTP_string("")); auto photo = MTP_photoEmpty(MTP_long(0)); auto document = MTP_documentEmpty(MTP_long(0)); if (!isVoice) { if (!_information) { _information = readMediaInformation(filemime); filemime = _information->filemime; } if (auto song = base::get_if<FileMediaInformation::Song>( &_information->media)) { isSong = true; auto flags = MTPDdocumentAttributeAudio::Flag::f_title | MTPDdocumentAttributeAudio::Flag::f_performer; attributes.push_back(MTP_documentAttributeAudio(MTP_flags(flags), MTP_int(song->duration), MTP_string(song->title), MTP_string(song->performer), MTPstring())); if (!song->cover.isNull()) { // cover to thumb auto coverWidth = song->cover.width(); auto coverHeight = song->cover.height(); auto full = (coverWidth > 90 || coverHeight > 90) ? App::pixmapFromImageInPlace(song->cover.scaled(90, 90, Qt::KeepAspectRatio, Qt::SmoothTransformation)) : App::pixmapFromImageInPlace(std::move(song->cover)); { auto thumbFormat = QByteArray("JPG"); auto thumbQuality = 87; QBuffer buffer(&thumbdata); full.save(&buffer, thumbFormat, thumbQuality); } thumb = full; thumbSize = MTP_photoSize(MTP_string(""), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(full.width()), MTP_int(full.height()), MTP_int(0)); thumbId = rand_value<uint64>(); } } else if (auto video = base::get_if<FileMediaInformation::Video>( &_information->media)) { isVideo = true; auto coverWidth = video->thumbnail.width(); auto coverHeight = video->thumbnail.height(); if (video->isGifv && !_album) { attributes.push_back(MTP_documentAttributeAnimated()); } auto flags = MTPDdocumentAttributeVideo::Flags(0); attributes.push_back(MTP_documentAttributeVideo(MTP_flags(flags), MTP_int(video->duration), MTP_int(coverWidth), MTP_int(coverHeight))); auto cover = (coverWidth > 90 || coverHeight > 90) ? video->thumbnail.scaled(90, 90, Qt::KeepAspectRatio, Qt::SmoothTransformation) : std::move(video->thumbnail); { auto thumbFormat = QByteArray("JPG"); auto thumbQuality = 87; QBuffer buffer(&thumbdata); cover.save(&buffer, thumbFormat, thumbQuality); } thumb = App::pixmapFromImageInPlace(std::move(cover)); thumbSize = MTP_photoSize(MTP_string(""), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(thumb.width()), MTP_int(thumb.height()), MTP_int(0)); thumbId = rand_value<uint64>(); } } if (!fullimage.isNull() && fullimage.width() > 0 && !isSong && !isVideo && !isVoice) { auto w = fullimage.width(), h = fullimage.height(); attributes.push_back(MTP_documentAttributeImageSize(MTP_int(w), MTP_int(h))); if (ValidateThumbDimensions(w, h)) { if (isAnimation) { attributes.push_back(MTP_documentAttributeAnimated()); } else if (_type != SendMediaType::File) { auto thumb = (w > 100 || h > 100) ? App::pixmapFromImageInPlace(fullimage.scaled(100, 100, Qt::KeepAspectRatio, Qt::SmoothTransformation)) : QPixmap::fromImage(fullimage); photoThumbs.insert('s', thumb); photoSizes.push_back(MTP_photoSize(MTP_string("s"), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(thumb.width()), MTP_int(thumb.height()), MTP_int(0))); auto medium = (w > 320 || h > 320) ? App::pixmapFromImageInPlace(fullimage.scaled(320, 320, Qt::KeepAspectRatio, Qt::SmoothTransformation)) : QPixmap::fromImage(fullimage); photoThumbs.insert('m', medium); photoSizes.push_back(MTP_photoSize(MTP_string("m"), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(medium.width()), MTP_int(medium.height()), MTP_int(0))); auto full = (w > 1280 || h > 1280) ? App::pixmapFromImageInPlace(fullimage.scaled(1280, 1280, Qt::KeepAspectRatio, Qt::SmoothTransformation)) : QPixmap::fromImage(fullimage); photoThumbs.insert('y', full); photoSizes.push_back(MTP_photoSize(MTP_string("y"), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(full.width()), MTP_int(full.height()), MTP_int(0))); { QBuffer buffer(&filedata); full.save(&buffer, "JPG", 87); } photo = MTP_photo(MTP_flags(0), MTP_long(_id), MTP_long(0), MTP_int(unixtime()), MTP_vector<MTPPhotoSize>(photoSizes)); if (filesize < 0) { filesize = _result->filesize = filedata.size(); } } QByteArray thumbFormat = "JPG"; auto thumbQuality = 87; if (!isAnimation && filemime == stickerMime && w > 0 && h > 0 && w <= StickerMaxSize && h <= StickerMaxSize && filesize < Storage::kMaxStickerInMemory) { attributes.push_back(MTP_documentAttributeSticker(MTP_flags(0), MTP_string(""), MTP_inputStickerSetEmpty(), MTPMaskCoords())); thumbFormat = "webp"; thumbname = qsl("thumb.webp"); } QPixmap full = (w > 90 || h > 90) ? App::pixmapFromImageInPlace(fullimage.scaled(90, 90, Qt::KeepAspectRatio, Qt::SmoothTransformation)) : QPixmap::fromImage(fullimage, Qt::ColorOnly); { QBuffer buffer(&thumbdata); full.save(&buffer, thumbFormat, thumbQuality); } thumb = full; thumbSize = MTP_photoSize(MTP_string(""), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(full.width()), MTP_int(full.height()), MTP_int(0)); thumbId = rand_value<uint64>(); } } if (_type == SendMediaType::Photo && photo.type() == mtpc_photoEmpty) { _type = SendMediaType::File; } if (isVoice) { auto flags = MTPDdocumentAttributeAudio::Flag::f_voice | MTPDdocumentAttributeAudio::Flag::f_waveform; attributes[0] = MTP_documentAttributeAudio(MTP_flags(flags), MTP_int(_duration), MTPstring(), MTPstring(), MTP_bytes(documentWaveformEncode5bit(_waveform))); attributes.resize(1); document = MTP_document(MTP_long(_id), MTP_long(0), MTP_int(unixtime()), MTP_string(filemime), MTP_int(filesize), thumbSize, MTP_int(MTP::maindc()), MTP_int(0), MTP_vector<MTPDocumentAttribute>(attributes)); } else if (_type != SendMediaType::Photo) { document = MTP_document(MTP_long(_id), MTP_long(0), MTP_int(unixtime()), MTP_string(filemime), MTP_int(filesize), thumbSize, MTP_int(MTP::maindc()), MTP_int(0), MTP_vector<MTPDocumentAttribute>(attributes)); _type = SendMediaType::File; } _result->type = _type; _result->filepath = _filepath; _result->content = _content; _result->filename = filename; _result->filemime = filemime; _result->setFileData(filedata); _result->thumbId = thumbId; _result->thumbname = thumbname; _result->setThumbData(thumbdata); _result->thumb = thumb; _result->photo = photo; _result->document = document; _result->photoThumbs = photoThumbs; } void FileLoadTask::finish() { if (!_result || !_result->filesize || _result->filesize < 0) { Ui::show( Box<InformBox>(lng_send_image_empty(lt_name, _filepath)), LayerOption::KeepOther); removeFromAlbum(); } else if (_result->filesize > App::kFileSizeLimit) { Ui::show( Box<InformBox>( lng_send_image_too_large(lt_name, _filepath)), LayerOption::KeepOther); removeFromAlbum(); } else if (App::main()) { App::main()->onSendFileConfirm(_result); } } void FileLoadTask::removeFromAlbum() { if (!_album) { return; } const auto proj = [](const SendingAlbum::Item &item) { return item.taskId; }; const auto it = ranges::find(_album->items, id(), proj); Assert(it != _album->items.end()); _album->items.erase(it); } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/storage/localimageloader.h�������������������������������������0000664�0000000�0000000�00000020022�13262451251�0024520�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "base/variant.h" enum class CompressConfirm { Auto, Yes, No, None, }; enum class SendMediaType { Photo, Audio, File, }; struct SendMediaPrepare { SendMediaPrepare(const QString &file, const PeerId &peer, SendMediaType type, MsgId replyTo) : id(rand_value<PhotoId>()), file(file), peer(peer), type(type), replyTo(replyTo) { } SendMediaPrepare(const QImage &img, const PeerId &peer, SendMediaType type, MsgId replyTo) : id(rand_value<PhotoId>()), img(img), peer(peer), type(type), replyTo(replyTo) { } SendMediaPrepare(const QByteArray &data, const PeerId &peer, SendMediaType type, MsgId replyTo) : id(rand_value<PhotoId>()), data(data), peer(peer), type(type), replyTo(replyTo) { } SendMediaPrepare(const QByteArray &data, int duration, const PeerId &peer, SendMediaType type, MsgId replyTo) : id(rand_value<PhotoId>()), data(data), peer(peer), type(type), duration(duration), replyTo(replyTo) { } PhotoId id; QString file; QImage img; QByteArray data; PeerId peer; SendMediaType type; int duration = 0; MsgId replyTo; }; using SendMediaPrepareList = QList<SendMediaPrepare>; using UploadFileParts = QMap<int, QByteArray>; struct SendMediaReady { SendMediaReady() = default; // temp SendMediaReady(SendMediaType type, const QString &file, const QString &filename, int32 filesize, const QByteArray &data, const uint64 &id, const uint64 &thumbId, const QString &thumbExt, const PeerId &peer, const MTPPhoto &photo, const PreparedPhotoThumbs &photoThumbs, const MTPDocument &document, const QByteArray &jpeg, MsgId replyTo) : replyTo(replyTo) , type(type) , file(file) , filename(filename) , filesize(filesize) , data(data) , thumbExt(thumbExt) , id(id) , thumbId(thumbId) , peer(peer) , photo(photo) , document(document) , photoThumbs(photoThumbs) { if (!jpeg.isEmpty()) { int32 size = jpeg.size(); for (int32 i = 0, part = 0; i < size; i += UploadPartSize, ++part) { parts.insert(part, jpeg.mid(i, UploadPartSize)); } jpeg_md5.resize(32); hashMd5Hex(jpeg.constData(), jpeg.size(), jpeg_md5.data()); } } MsgId replyTo; SendMediaType type; QString file, filename; int32 filesize; QByteArray data; QString thumbExt; uint64 id, thumbId; // id always file-id of media, thumbId is file-id of thumb ( == id for photos) PeerId peer; MTPPhoto photo; MTPDocument document; PreparedPhotoThumbs photoThumbs; UploadFileParts parts; QByteArray jpeg_md5; QString caption; }; using TaskId = void*; // no interface, just id class Task { public: virtual void process() = 0; // is executed in a separate thread virtual void finish() = 0; // is executed in the same as TaskQueue thread virtual ~Task() = default; TaskId id() const { return static_cast<TaskId>(const_cast<Task*>(this)); } }; class TaskQueueWorker; class TaskQueue : public QObject { Q_OBJECT public: explicit TaskQueue(TimeMs stopTimeoutMs = 0); // <= 0 - never stop worker TaskId addTask(std::unique_ptr<Task> &&task); void addTasks(std::vector<std::unique_ptr<Task>> &&tasks); void cancelTask(TaskId id); // this task finish() won't be called ~TaskQueue(); signals: void taskAdded(); public slots: void onTaskProcessed(); void stop(); private: friend class TaskQueueWorker; void wakeThread(); std::deque<std::unique_ptr<Task>> _tasksToProcess; std::deque<std::unique_ptr<Task>> _tasksToFinish; TaskId _taskInProcessId = TaskId(); QMutex _tasksToProcessMutex, _tasksToFinishMutex; QThread *_thread = nullptr; TaskQueueWorker *_worker = nullptr; QTimer *_stopTimer = nullptr; }; class TaskQueueWorker : public QObject { Q_OBJECT public: TaskQueueWorker(TaskQueue *queue) : _queue(queue) { } signals: void taskProcessed(); public slots: void onTaskAdded(); private: TaskQueue *_queue; bool _inTaskAdded = false; }; struct SendingAlbum { struct Item { explicit Item(TaskId taskId) : taskId(taskId) { } TaskId taskId; FullMsgId msgId; base::optional<MTPInputSingleMedia> media; }; SendingAlbum(); uint64 groupId = 0; std::vector<Item> items; bool silent = false; }; struct FileLoadTo { FileLoadTo(const PeerId &peer, bool silent, MsgId replyTo) : peer(peer) , silent(silent) , replyTo(replyTo) { } PeerId peer; bool silent; MsgId replyTo; }; struct FileLoadResult { FileLoadResult( TaskId taskId, uint64 id, const FileLoadTo &to, const QString &caption, std::shared_ptr<SendingAlbum> album); TaskId taskId; uint64 id; FileLoadTo to; std::shared_ptr<SendingAlbum> album; SendMediaType type = SendMediaType::File; QString filepath; QByteArray content; QString filename; QString filemime; int32 filesize = 0; UploadFileParts fileparts; QByteArray filemd5; int32 partssize; uint64 thumbId = 0; // id is always file-id of media, thumbId is file-id of thumb ( == id for photos) QString thumbname; UploadFileParts thumbparts; QByteArray thumbmd5; QPixmap thumb; MTPPhoto photo; MTPDocument document; PreparedPhotoThumbs photoThumbs; QString caption; void setFileData(const QByteArray &filedata) { if (filedata.isEmpty()) { partssize = 0; } else { partssize = filedata.size(); for (int32 i = 0, part = 0; i < partssize; i += UploadPartSize, ++part) { fileparts.insert(part, filedata.mid(i, UploadPartSize)); } filemd5.resize(32); hashMd5Hex(filedata.constData(), filedata.size(), filemd5.data()); } } void setThumbData(const QByteArray &thumbdata) { if (!thumbdata.isEmpty()) { int32 size = thumbdata.size(); for (int32 i = 0, part = 0; i < size; i += UploadPartSize, ++part) { thumbparts.insert(part, thumbdata.mid(i, UploadPartSize)); } thumbmd5.resize(32); hashMd5Hex(thumbdata.constData(), thumbdata.size(), thumbmd5.data()); } } }; struct FileMediaInformation { struct Image { QImage data; bool animated = false; }; struct Song { int duration = -1; QString title; QString performer; QImage cover; }; struct Video { bool isGifv = false; int duration = -1; QImage thumbnail; }; QString filemime; base::variant<Image, Song, Video> media; }; class FileLoadTask final : public Task { public: static std::unique_ptr<FileMediaInformation> ReadMediaInformation( const QString &filepath, const QByteArray &content, const QString &filemime); static bool FillImageInformation( QImage &&image, bool animated, std::unique_ptr<FileMediaInformation> &result); FileLoadTask( const QString &filepath, const QByteArray &content, std::unique_ptr<FileMediaInformation> information, SendMediaType type, const FileLoadTo &to, const QString &caption, std::shared_ptr<SendingAlbum> album = nullptr); FileLoadTask( const QByteArray &voice, int32 duration, const VoiceWaveform &waveform, const FileLoadTo &to, const QString &caption); uint64 fileid() const { return _id; } void process(); void finish(); private: static bool CheckForSong( const QString &filepath, const QByteArray &content, std::unique_ptr<FileMediaInformation> &result); static bool CheckForVideo( const QString &filepath, const QByteArray &content, std::unique_ptr<FileMediaInformation> &result); static bool CheckForImage( const QString &filepath, const QByteArray &content, std::unique_ptr<FileMediaInformation> &result); template <typename Mimes, typename Extensions> static bool CheckMimeOrExtensions(const QString &filepath, const QString &filemime, Mimes &mimes, Extensions &extensions); std::unique_ptr<FileMediaInformation> readMediaInformation(const QString &filemime) const { return ReadMediaInformation(_filepath, _content, filemime); } void removeFromAlbum(); uint64 _id; FileLoadTo _to; const std::shared_ptr<SendingAlbum> _album; QString _filepath; QByteArray _content; std::unique_ptr<FileMediaInformation> _information; int32 _duration = 0; VoiceWaveform _waveform; SendMediaType _type; QString _caption; std::shared_ptr<FileLoadResult> _result; }; ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/storage/localstorage.cpp���������������������������������������0000664�0000000�0000000�00000425433�13262451251�0024265�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "storage/localstorage.h" #include "storage/serialize_document.h" #include "storage/serialize_common.h" #include "chat_helpers/stickers.h" #include "data/data_drafts.h" #include "boxes/send_files_box.h" #include "window/themes/window_theme.h" #include "core/crash_reports.h" #include "observer_peer.h" #include "mainwidget.h" #include "mainwindow.h" #include "lang/lang_keys.h" #include "media/media_audio.h" #include "ui/widgets/input_fields.h" #include "mtproto/dc_options.h" #include "messenger.h" #include "application.h" #include "apiwrap.h" #include "auth_session.h" #include "window/window_controller.h" #include "base/flags.h" #include "data/data_session.h" #include "history/history.h" #include <openssl/evp.h> namespace Local { namespace { constexpr auto kThemeFileSizeLimit = 5 * 1024 * 1024; constexpr auto kFileLoaderQueueStopTimeout = TimeMs(5000); constexpr auto kDefaultStickerInstallDate = TimeId(1); using FileKey = quint64; constexpr char tdfMagic[] = { 'T', 'D', 'F', '$' }; constexpr auto tdfMagicLen = int(sizeof(tdfMagic)); QString toFilePart(FileKey val) { QString result; result.reserve(0x10); for (int32 i = 0; i < 0x10; ++i) { uchar v = (val & 0x0F); result.push_back((v < 0x0A) ? ('0' + v) : ('A' + (v - 0x0A))); val >>= 4; } return result; } QString _basePath, _userBasePath; bool _started = false; internal::Manager *_manager = nullptr; TaskQueue *_localLoader = nullptr; bool _working() { return _manager && !_basePath.isEmpty(); } bool _userWorking() { return _manager && !_basePath.isEmpty() && !_userBasePath.isEmpty(); } enum class FileOption { User = (1 << 0), Safe = (1 << 1), }; using FileOptions = base::flags<FileOption>; inline constexpr auto is_flag_type(FileOption) { return true; }; bool keyAlreadyUsed(QString &name, FileOptions options = FileOption::User | FileOption::Safe) { name += '0'; if (QFileInfo(name).exists()) return true; if (options & (FileOption::Safe)) { name[name.size() - 1] = '1'; return QFileInfo(name).exists(); } return false; } FileKey genKey(FileOptions options = FileOption::User | FileOption::Safe) { if (options & FileOption::User) { if (!_userWorking()) return 0; } else { if (!_working()) return 0; } FileKey result; QString base = (options & FileOption::User) ? _userBasePath : _basePath, path; path.reserve(base.size() + 0x11); path += base; do { result = rand_value<FileKey>(); path.resize(base.size()); path += toFilePart(result); } while (!result || keyAlreadyUsed(path, options)); return result; } void clearKey(const FileKey &key, FileOptions options = FileOption::User | FileOption::Safe) { if (options & FileOption::User) { if (!_userWorking()) return; } else { if (!_working()) return; } QString base = (options & FileOption::User) ? _userBasePath : _basePath, name; name.reserve(base.size() + 0x11); name.append(base).append(toFilePart(key)).append('0'); QFile::remove(name); if (options & FileOption::Safe) { name[name.size() - 1] = '1'; QFile::remove(name); } } bool _checkStreamStatus(QDataStream &stream) { if (stream.status() != QDataStream::Ok) { LOG(("Bad data stream status: %1").arg(stream.status())); return false; } return true; } QByteArray _settingsSalt, _passKeySalt, _passKeyEncrypted; constexpr auto kLocalKeySize = MTP::AuthKey::kSize; auto OldKey = MTP::AuthKeyPtr(); auto SettingsKey = MTP::AuthKeyPtr(); auto PassKey = MTP::AuthKeyPtr(); auto LocalKey = MTP::AuthKeyPtr(); void createLocalKey(const QByteArray &pass, QByteArray *salt, MTP::AuthKeyPtr *result) { auto key = MTP::AuthKey::Data { { gsl::byte{} } }; auto iterCount = pass.size() ? LocalEncryptIterCount : LocalEncryptNoPwdIterCount; // dont slow down for no password auto newSalt = QByteArray(); if (!salt) { newSalt.resize(LocalEncryptSaltSize); memset_rand(newSalt.data(), newSalt.size()); salt = &newSalt; cSetLocalSalt(newSalt); } PKCS5_PBKDF2_HMAC_SHA1(pass.constData(), pass.size(), (uchar*)salt->data(), salt->size(), iterCount, key.size(), (uchar*)key.data()); *result = std::make_shared<MTP::AuthKey>(key); } struct FileReadDescriptor { int32 version = 0; QByteArray data; QBuffer buffer; QDataStream stream; ~FileReadDescriptor() { if (version) { stream.setDevice(0); if (buffer.isOpen()) buffer.close(); buffer.setBuffer(0); } } }; struct EncryptedDescriptor { EncryptedDescriptor() { } EncryptedDescriptor(uint32 size) { uint32 fullSize = sizeof(uint32) + size; if (fullSize & 0x0F) fullSize += 0x10 - (fullSize & 0x0F); data.reserve(fullSize); data.resize(sizeof(uint32)); buffer.setBuffer(&data); buffer.open(QIODevice::WriteOnly); buffer.seek(sizeof(uint32)); stream.setDevice(&buffer); stream.setVersion(QDataStream::Qt_5_1); } QByteArray data; QBuffer buffer; QDataStream stream; void finish() { if (stream.device()) stream.setDevice(0); if (buffer.isOpen()) buffer.close(); buffer.setBuffer(0); } ~EncryptedDescriptor() { finish(); } }; struct FileWriteDescriptor { FileWriteDescriptor(const FileKey &key, FileOptions options = FileOption::User | FileOption::Safe) { init(toFilePart(key), options); } FileWriteDescriptor(const QString &name, FileOptions options = FileOption::User | FileOption::Safe) { init(name, options); } void init(const QString &name, FileOptions options) { if (options & FileOption::User) { if (!_userWorking()) return; } else { if (!_working()) return; } // detect order of read attempts and file version QString toTry[2]; toTry[0] = ((options & FileOption::User) ? _userBasePath : _basePath) + name + '0'; if (options & FileOption::Safe) { toTry[1] = ((options & FileOption::User) ? _userBasePath : _basePath) + name + '1'; QFileInfo toTry0(toTry[0]); QFileInfo toTry1(toTry[1]); if (toTry0.exists()) { if (toTry1.exists()) { QDateTime mod0 = toTry0.lastModified(), mod1 = toTry1.lastModified(); if (mod0 > mod1) { qSwap(toTry[0], toTry[1]); } } else { qSwap(toTry[0], toTry[1]); } toDelete = toTry[1]; } else if (toTry1.exists()) { toDelete = toTry[1]; } } file.setFileName(toTry[0]); if (file.open(QIODevice::WriteOnly)) { file.write(tdfMagic, tdfMagicLen); qint32 version = AppVersion; file.write((const char*)&version, sizeof(version)); stream.setDevice(&file); stream.setVersion(QDataStream::Qt_5_1); } } bool writeData(const QByteArray &data) { if (!file.isOpen()) return false; stream << data; quint32 len = data.isNull() ? 0xffffffff : data.size(); if (QSysInfo::ByteOrder != QSysInfo::BigEndian) { len = qbswap(len); } md5.feed(&len, sizeof(len)); md5.feed(data.constData(), data.size()); dataSize += sizeof(len) + data.size(); return true; } static QByteArray prepareEncrypted(EncryptedDescriptor &data, const MTP::AuthKeyPtr &key = LocalKey) { data.finish(); QByteArray &toEncrypt(data.data); // prepare for encryption uint32 size = toEncrypt.size(), fullSize = size; if (fullSize & 0x0F) { fullSize += 0x10 - (fullSize & 0x0F); toEncrypt.resize(fullSize); memset_rand(toEncrypt.data() + size, fullSize - size); } *(uint32*)toEncrypt.data() = size; QByteArray encrypted(0x10 + fullSize, Qt::Uninitialized); // 128bit of sha1 - key128, sizeof(data), data hashSha1(toEncrypt.constData(), toEncrypt.size(), encrypted.data()); MTP::aesEncryptLocal(toEncrypt.constData(), encrypted.data() + 0x10, fullSize, key, encrypted.constData()); return encrypted; } bool writeEncrypted(EncryptedDescriptor &data, const MTP::AuthKeyPtr &key = LocalKey) { return writeData(prepareEncrypted(data, key)); } void finish() { if (!file.isOpen()) return; stream.setDevice(0); md5.feed(&dataSize, sizeof(dataSize)); qint32 version = AppVersion; md5.feed(&version, sizeof(version)); md5.feed(tdfMagic, tdfMagicLen); file.write((const char*)md5.result(), 0x10); file.close(); if (!toDelete.isEmpty()) { QFile::remove(toDelete); } } QFile file; QDataStream stream; QString toDelete; HashMd5 md5; int32 dataSize = 0; ~FileWriteDescriptor() { finish(); } }; bool readFile(FileReadDescriptor &result, const QString &name, FileOptions options = FileOption::User | FileOption::Safe) { if (options & FileOption::User) { if (!_userWorking()) return false; } else { if (!_working()) return false; } // detect order of read attempts QString toTry[2]; toTry[0] = ((options & FileOption::User) ? _userBasePath : _basePath) + name + '0'; if (options & FileOption::Safe) { QFileInfo toTry0(toTry[0]); if (toTry0.exists()) { toTry[1] = ((options & FileOption::User) ? _userBasePath : _basePath) + name + '1'; QFileInfo toTry1(toTry[1]); if (toTry1.exists()) { QDateTime mod0 = toTry0.lastModified(), mod1 = toTry1.lastModified(); if (mod0 < mod1) { qSwap(toTry[0], toTry[1]); } } else { toTry[1] = QString(); } } else { toTry[0][toTry[0].size() - 1] = '1'; } } for (int32 i = 0; i < 2; ++i) { QString fname(toTry[i]); if (fname.isEmpty()) break; QFile f(fname); if (!f.open(QIODevice::ReadOnly)) { DEBUG_LOG(("App Info: failed to open '%1' for reading").arg(name)); continue; } // check magic char magic[tdfMagicLen]; if (f.read(magic, tdfMagicLen) != tdfMagicLen) { DEBUG_LOG(("App Info: failed to read magic from '%1'").arg(name)); continue; } if (memcmp(magic, tdfMagic, tdfMagicLen)) { DEBUG_LOG(("App Info: bad magic %1 in '%2'").arg(Logs::mb(magic, tdfMagicLen).str()).arg(name)); continue; } // read app version qint32 version; if (f.read((char*)&version, sizeof(version)) != sizeof(version)) { DEBUG_LOG(("App Info: failed to read version from '%1'").arg(name)); continue; } if (version > AppVersion) { DEBUG_LOG(("App Info: version too big %1 for '%2', my version %3").arg(version).arg(name).arg(AppVersion)); continue; } // read data QByteArray bytes = f.read(f.size()); int32 dataSize = bytes.size() - 16; if (dataSize < 0) { DEBUG_LOG(("App Info: bad file '%1', could not read sign part").arg(name)); continue; } // check signature HashMd5 md5; md5.feed(bytes.constData(), dataSize); md5.feed(&dataSize, sizeof(dataSize)); md5.feed(&version, sizeof(version)); md5.feed(magic, tdfMagicLen); if (memcmp(md5.result(), bytes.constData() + dataSize, 16)) { DEBUG_LOG(("App Info: bad file '%1', signature did not match").arg(name)); continue; } bytes.resize(dataSize); result.data = bytes; bytes = QByteArray(); result.version = version; result.buffer.setBuffer(&result.data); result.buffer.open(QIODevice::ReadOnly); result.stream.setDevice(&result.buffer); result.stream.setVersion(QDataStream::Qt_5_1); if ((i == 0 && !toTry[1].isEmpty()) || i == 1) { QFile::remove(toTry[1 - i]); } return true; } return false; } bool decryptLocal(EncryptedDescriptor &result, const QByteArray &encrypted, const MTP::AuthKeyPtr &key = LocalKey) { if (encrypted.size() <= 16 || (encrypted.size() & 0x0F)) { LOG(("App Error: bad encrypted part size: %1").arg(encrypted.size())); return false; } uint32 fullLen = encrypted.size() - 16; QByteArray decrypted; decrypted.resize(fullLen); const char *encryptedKey = encrypted.constData(), *encryptedData = encrypted.constData() + 16; aesDecryptLocal(encryptedData, decrypted.data(), fullLen, key, encryptedKey); uchar sha1Buffer[20]; if (memcmp(hashSha1(decrypted.constData(), decrypted.size(), sha1Buffer), encryptedKey, 16)) { LOG(("App Info: bad decrypt key, data not decrypted - incorrect password?")); return false; } uint32 dataLen = *(const uint32*)decrypted.constData(); if (dataLen > uint32(decrypted.size()) || dataLen <= fullLen - 16 || dataLen < sizeof(uint32)) { LOG(("App Error: bad decrypted part size: %1, fullLen: %2, decrypted size: %3").arg(dataLen).arg(fullLen).arg(decrypted.size())); return false; } decrypted.resize(dataLen); result.data = decrypted; decrypted = QByteArray(); result.buffer.setBuffer(&result.data); result.buffer.open(QIODevice::ReadOnly); result.buffer.seek(sizeof(uint32)); // skip len result.stream.setDevice(&result.buffer); result.stream.setVersion(QDataStream::Qt_5_1); return true; } bool readEncryptedFile(FileReadDescriptor &result, const QString &name, FileOptions options = FileOption::User | FileOption::Safe, const MTP::AuthKeyPtr &key = LocalKey) { if (!readFile(result, name, options)) { return false; } QByteArray encrypted; result.stream >> encrypted; EncryptedDescriptor data; if (!decryptLocal(data, encrypted, key)) { result.stream.setDevice(0); if (result.buffer.isOpen()) result.buffer.close(); result.buffer.setBuffer(0); result.data = QByteArray(); result.version = 0; return false; } result.stream.setDevice(0); if (result.buffer.isOpen()) result.buffer.close(); result.buffer.setBuffer(0); result.data = data.data; result.buffer.setBuffer(&result.data); result.buffer.open(QIODevice::ReadOnly); result.buffer.seek(data.buffer.pos()); result.stream.setDevice(&result.buffer); result.stream.setVersion(QDataStream::Qt_5_1); return true; } bool readEncryptedFile(FileReadDescriptor &result, const FileKey &fkey, FileOptions options = FileOption::User | FileOption::Safe, const MTP::AuthKeyPtr &key = LocalKey) { return readEncryptedFile(result, toFilePart(fkey), options, key); } FileKey _dataNameKey = 0; enum { // Local Storage Keys lskUserMap = 0x00, lskDraft = 0x01, // data: PeerId peer lskDraftPosition = 0x02, // data: PeerId peer lskImages = 0x03, // data: StorageKey location lskLocations = 0x04, // no data lskStickerImages = 0x05, // data: StorageKey location lskAudios = 0x06, // data: StorageKey location lskRecentStickersOld = 0x07, // no data lskBackground = 0x08, // no data lskUserSettings = 0x09, // no data lskRecentHashtagsAndBots = 0x0a, // no data lskStickersOld = 0x0b, // no data lskSavedPeers = 0x0c, // no data lskReportSpamStatuses = 0x0d, // no data lskSavedGifsOld = 0x0e, // no data lskSavedGifs = 0x0f, // no data lskStickersKeys = 0x10, // no data lskTrustedBots = 0x11, // no data lskFavedStickers = 0x12, // no data }; enum { dbiKey = 0x00, dbiUser = 0x01, dbiDcOptionOldOld = 0x02, dbiChatSizeMax = 0x03, dbiMutePeer = 0x04, dbiSendKey = 0x05, dbiAutoStart = 0x06, dbiStartMinimized = 0x07, dbiSoundNotify = 0x08, dbiWorkMode = 0x09, dbiSeenTrayTooltip = 0x0a, dbiDesktopNotify = 0x0b, dbiAutoUpdate = 0x0c, dbiLastUpdateCheck = 0x0d, dbiWindowPosition = 0x0e, dbiConnectionTypeOld = 0x0f, // 0x10 reserved dbiDefaultAttach = 0x11, dbiCatsAndDogs = 0x12, dbiReplaceEmojis = 0x13, dbiAskDownloadPath = 0x14, dbiDownloadPathOld = 0x15, dbiScale = 0x16, dbiEmojiTabOld = 0x17, dbiRecentEmojiOldOld = 0x18, dbiLoggedPhoneNumber = 0x19, dbiMutedPeers = 0x1a, // 0x1b reserved dbiNotifyView = 0x1c, dbiSendToMenu = 0x1d, dbiCompressPastedImage = 0x1e, dbiLangOld = 0x1f, dbiLangFileOld = 0x20, dbiTileBackground = 0x21, dbiAutoLock = 0x22, dbiDialogLastPath = 0x23, dbiRecentEmojiOld = 0x24, dbiEmojiVariantsOld = 0x25, dbiRecentStickers = 0x26, dbiDcOptionOld = 0x27, dbiTryIPv6 = 0x28, dbiSongVolume = 0x29, dbiWindowsNotificationsOld = 0x30, dbiIncludeMuted = 0x31, dbiMegagroupSizeMax = 0x32, dbiDownloadPath = 0x33, dbiAutoDownload = 0x34, dbiSavedGifsLimit = 0x35, dbiShowingSavedGifsOld = 0x36, dbiAutoPlay = 0x37, dbiAdaptiveForWide = 0x38, dbiHiddenPinnedMessages = 0x39, dbiRecentEmoji = 0x3a, dbiEmojiVariants = 0x3b, dbiDialogsMode = 0x40, dbiModerateMode = 0x41, dbiVideoVolume = 0x42, dbiStickersRecentLimit = 0x43, dbiNativeNotifications = 0x44, dbiNotificationsCount = 0x45, dbiNotificationsCorner = 0x46, dbiThemeKey = 0x47, dbiDialogsWidthRatioOld = 0x48, dbiUseExternalVideoPlayer = 0x49, dbiDcOptions = 0x4a, dbiMtpAuthorization = 0x4b, dbiLastSeenWarningSeenOld = 0x4c, dbiAuthSessionSettings = 0x4d, dbiLangPackKey = 0x4e, dbiConnectionType = 0x4f, dbiStickersFavedLimit = 0x50, dbiSuggestStickersByEmoji = 0x51, dbiEncryptedWithSalt = 333, dbiEncrypted = 444, // 500-600 reserved dbiVersion = 666, }; typedef QMap<PeerId, FileKey> DraftsMap; DraftsMap _draftsMap, _draftCursorsMap; typedef QMap<PeerId, bool> DraftsNotReadMap; DraftsNotReadMap _draftsNotReadMap; typedef QPair<FileKey, qint32> FileDesc; // file, size typedef QMultiMap<MediaKey, FileLocation> FileLocations; FileLocations _fileLocations; typedef QPair<MediaKey, FileLocation> FileLocationPair; typedef QMap<QString, FileLocationPair> FileLocationPairs; FileLocationPairs _fileLocationPairs; typedef QMap<MediaKey, MediaKey> FileLocationAliases; FileLocationAliases _fileLocationAliases; typedef QMap<QString, FileDesc> WebFilesMap; WebFilesMap _webFilesMap; uint64 _storageWebFilesSize = 0; FileKey _locationsKey = 0, _reportSpamStatusesKey = 0, _trustedBotsKey = 0; using TrustedBots = OrderedSet<uint64>; TrustedBots _trustedBots; bool _trustedBotsRead = false; FileKey _recentStickersKeyOld = 0; FileKey _installedStickersKey = 0, _featuredStickersKey = 0, _recentStickersKey = 0, _favedStickersKey = 0, _archivedStickersKey = 0; FileKey _savedGifsKey = 0; FileKey _backgroundKey = 0; bool _backgroundWasRead = false; bool _backgroundCanWrite = true; FileKey _themeKey = 0; QString _themeAbsolutePath; QString _themePaletteAbsolutePath; bool _readingUserSettings = false; FileKey _userSettingsKey = 0; FileKey _recentHashtagsAndBotsKey = 0; bool _recentHashtagsAndBotsWereRead = false; FileKey _savedPeersKey = 0; FileKey _langPackKey = 0; typedef QMap<StorageKey, FileDesc> StorageMap; StorageMap _imagesMap, _stickerImagesMap, _audiosMap; qint64 _storageImagesSize = 0, _storageStickersSize = 0, _storageAudiosSize = 0; bool _mapChanged = false; int32 _oldMapVersion = 0, _oldSettingsVersion = 0; enum class WriteMapWhen { Now, Fast, Soon, }; std::unique_ptr<AuthSessionSettings> StoredAuthSessionCache; AuthSessionSettings &GetStoredAuthSessionCache() { if (!StoredAuthSessionCache) { StoredAuthSessionCache = std::make_unique<AuthSessionSettings>(); } return *StoredAuthSessionCache; } void _writeMap(WriteMapWhen when = WriteMapWhen::Soon); void _writeLocations(WriteMapWhen when = WriteMapWhen::Soon) { if (when != WriteMapWhen::Now) { _manager->writeLocations(when == WriteMapWhen::Fast); return; } if (!_working()) return; _manager->writingLocations(); if (_fileLocations.isEmpty() && _webFilesMap.isEmpty()) { if (_locationsKey) { clearKey(_locationsKey); _locationsKey = 0; _mapChanged = true; _writeMap(); } } else { if (!_locationsKey) { _locationsKey = genKey(); _mapChanged = true; _writeMap(WriteMapWhen::Fast); } quint32 size = 0; for (FileLocations::const_iterator i = _fileLocations.cbegin(), e = _fileLocations.cend(); i != e; ++i) { // location + type + namelen + name size += sizeof(quint64) * 2 + sizeof(quint32) + Serialize::stringSize(i.value().name()); if (AppVersion > 9013) { // bookmark size += Serialize::bytearraySize(i.value().bookmark()); } // date + size size += Serialize::dateTimeSize() + sizeof(quint32); } //end mark size += sizeof(quint64) * 2 + sizeof(quint32) + Serialize::stringSize(QString()); if (AppVersion > 9013) { size += Serialize::bytearraySize(QByteArray()); } size += Serialize::dateTimeSize() + sizeof(quint32); size += sizeof(quint32); // aliases count for (FileLocationAliases::const_iterator i = _fileLocationAliases.cbegin(), e = _fileLocationAliases.cend(); i != e; ++i) { // alias + location size += sizeof(quint64) * 2 + sizeof(quint64) * 2; } size += sizeof(quint32); // web files count for (WebFilesMap::const_iterator i = _webFilesMap.cbegin(), e = _webFilesMap.cend(); i != e; ++i) { // url + filekey + size size += Serialize::stringSize(i.key()) + sizeof(quint64) + sizeof(qint32); } EncryptedDescriptor data(size); auto legacyTypeField = 0; for (FileLocations::const_iterator i = _fileLocations.cbegin(); i != _fileLocations.cend(); ++i) { data.stream << quint64(i.key().first) << quint64(i.key().second) << quint32(legacyTypeField) << i.value().name(); if (AppVersion > 9013) { data.stream << i.value().bookmark(); } data.stream << i.value().modified << quint32(i.value().size); } data.stream << quint64(0) << quint64(0) << quint32(0) << QString(); if (AppVersion > 9013) { data.stream << QByteArray(); } data.stream << QDateTime::currentDateTime() << quint32(0); data.stream << quint32(_fileLocationAliases.size()); for (FileLocationAliases::const_iterator i = _fileLocationAliases.cbegin(), e = _fileLocationAliases.cend(); i != e; ++i) { data.stream << quint64(i.key().first) << quint64(i.key().second) << quint64(i.value().first) << quint64(i.value().second); } data.stream << quint32(_webFilesMap.size()); for (WebFilesMap::const_iterator i = _webFilesMap.cbegin(), e = _webFilesMap.cend(); i != e; ++i) { data.stream << i.key() << quint64(i.value().first) << qint32(i.value().second); } FileWriteDescriptor file(_locationsKey); file.writeEncrypted(data); } } void _readLocations() { FileReadDescriptor locations; if (!readEncryptedFile(locations, _locationsKey)) { clearKey(_locationsKey); _locationsKey = 0; _writeMap(); return; } bool endMarkFound = false; while (!locations.stream.atEnd()) { quint64 first, second; QByteArray bookmark; FileLocation loc; quint32 legacyTypeField = 0; locations.stream >> first >> second >> legacyTypeField >> loc.fname; if (locations.version > 9013) { locations.stream >> bookmark; } locations.stream >> loc.modified >> loc.size; loc.setBookmark(bookmark); if (!first && !second && !legacyTypeField && loc.fname.isEmpty() && !loc.size) { // end mark endMarkFound = true; break; } MediaKey key(first, second); _fileLocations.insert(key, loc); _fileLocationPairs.insert(loc.fname, FileLocationPair(key, loc)); } if (endMarkFound) { quint32 cnt; locations.stream >> cnt; for (quint32 i = 0; i < cnt; ++i) { quint64 kfirst, ksecond, vfirst, vsecond; locations.stream >> kfirst >> ksecond >> vfirst >> vsecond; _fileLocationAliases.insert(MediaKey(kfirst, ksecond), MediaKey(vfirst, vsecond)); } if (!locations.stream.atEnd()) { _storageWebFilesSize = 0; _webFilesMap.clear(); quint32 webLocationsCount; locations.stream >> webLocationsCount; for (quint32 i = 0; i < webLocationsCount; ++i) { QString url; quint64 key; qint32 size; locations.stream >> url >> key >> size; _webFilesMap.insert(url, FileDesc(key, size)); _storageWebFilesSize += size; } } } } void _writeReportSpamStatuses() { if (!_working()) return; if (cReportSpamStatuses().isEmpty()) { if (_reportSpamStatusesKey) { clearKey(_reportSpamStatusesKey); _reportSpamStatusesKey = 0; _mapChanged = true; _writeMap(); } } else { if (!_reportSpamStatusesKey) { _reportSpamStatusesKey = genKey(); _mapChanged = true; _writeMap(WriteMapWhen::Fast); } const ReportSpamStatuses &statuses(cReportSpamStatuses()); quint32 size = sizeof(qint32); for (ReportSpamStatuses::const_iterator i = statuses.cbegin(), e = statuses.cend(); i != e; ++i) { // peer + status size += sizeof(quint64) + sizeof(qint32); } EncryptedDescriptor data(size); data.stream << qint32(statuses.size()); for (ReportSpamStatuses::const_iterator i = statuses.cbegin(), e = statuses.cend(); i != e; ++i) { data.stream << quint64(i.key()) << qint32(i.value()); } FileWriteDescriptor file(_reportSpamStatusesKey); file.writeEncrypted(data); } } void _readReportSpamStatuses() { FileReadDescriptor statuses; if (!readEncryptedFile(statuses, _reportSpamStatusesKey)) { clearKey(_reportSpamStatusesKey); _reportSpamStatusesKey = 0; _writeMap(); return; } ReportSpamStatuses &map(cRefReportSpamStatuses()); map.clear(); qint32 size = 0; statuses.stream >> size; for (int32 i = 0; i < size; ++i) { quint64 peer = 0; qint32 status = 0; statuses.stream >> peer >> status; map.insert(peer, DBIPeerReportSpamStatus(status)); } } struct ReadSettingsContext { int legacyLanguageId = Lang::kLegacyLanguageNone; QString legacyLanguageFile; MTP::DcOptions dcOptions; }; void applyReadContext(ReadSettingsContext &&context) { Messenger::Instance().dcOptions()->addFromOther(std::move(context.dcOptions)); if (context.legacyLanguageId != Lang::kLegacyLanguageNone) { Lang::Current().fillFromLegacy(context.legacyLanguageId, context.legacyLanguageFile); writeLangPack(); } } bool _readSetting(quint32 blockId, QDataStream &stream, int version, ReadSettingsContext &context) { switch (blockId) { case dbiDcOptionOldOld: { quint32 dcId, port; QString host, ip; stream >> dcId >> host >> ip >> port; if (!_checkStreamStatus(stream)) return false; context.dcOptions.constructAddOne(dcId, 0, ip.toStdString(), port); } break; case dbiDcOptionOld: { quint32 dcIdWithShift, port; qint32 flags; QString ip; stream >> dcIdWithShift >> flags >> ip >> port; if (!_checkStreamStatus(stream)) return false; context.dcOptions.constructAddOne(dcIdWithShift, MTPDdcOption::Flags::from_raw(flags), ip.toStdString(), port); } break; case dbiDcOptions: { auto serialized = QByteArray(); stream >> serialized; if (!_checkStreamStatus(stream)) return false; context.dcOptions.constructFromSerialized(serialized); } break; case dbiChatSizeMax: { qint32 maxSize; stream >> maxSize; if (!_checkStreamStatus(stream)) return false; Global::SetChatSizeMax(maxSize); } break; case dbiSavedGifsLimit: { qint32 limit; stream >> limit; if (!_checkStreamStatus(stream)) return false; Global::SetSavedGifsLimit(limit); } break; case dbiStickersRecentLimit: { qint32 limit; stream >> limit; if (!_checkStreamStatus(stream)) return false; Global::SetStickersRecentLimit(limit); } break; case dbiStickersFavedLimit: { qint32 limit; stream >> limit; if (!_checkStreamStatus(stream)) return false; Global::SetStickersFavedLimit(limit); } break; case dbiMegagroupSizeMax: { qint32 maxSize; stream >> maxSize; if (!_checkStreamStatus(stream)) return false; Global::SetMegagroupSizeMax(maxSize); } break; case dbiUser: { quint32 dcId; qint32 userId; stream >> userId >> dcId; if (!_checkStreamStatus(stream)) return false; DEBUG_LOG(("MTP Info: user found, dc %1, uid %2").arg(dcId).arg(userId)); Messenger::Instance().setMtpMainDcId(dcId); Messenger::Instance().setAuthSessionUserId(userId); } break; case dbiKey: { qint32 dcId; stream >> dcId; auto key = Serialize::read<MTP::AuthKey::Data>(stream); if (!_checkStreamStatus(stream)) return false; Messenger::Instance().setMtpKey(dcId, key); } break; case dbiMtpAuthorization: { auto serialized = QByteArray(); stream >> serialized; if (!_checkStreamStatus(stream)) return false; Messenger::Instance().setMtpAuthorization(serialized); } break; case dbiAutoStart: { qint32 v; stream >> v; if (!_checkStreamStatus(stream)) return false; cSetAutoStart(v == 1); } break; case dbiStartMinimized: { qint32 v; stream >> v; if (!_checkStreamStatus(stream)) return false; cSetStartMinimized(v == 1); } break; case dbiSendToMenu: { qint32 v; stream >> v; if (!_checkStreamStatus(stream)) return false; cSetSendToMenu(v == 1); } break; case dbiUseExternalVideoPlayer: { qint32 v; stream >> v; if (!_checkStreamStatus(stream)) return false; cSetUseExternalVideoPlayer(v == 1); } break; case dbiSoundNotify: { qint32 v; stream >> v; if (!_checkStreamStatus(stream)) return false; Global::SetSoundNotify(v == 1); } break; case dbiSuggestStickersByEmoji: { qint32 v; stream >> v; if (!_checkStreamStatus(stream)) return false; Global::SetSuggestStickersByEmoji(v == 1); } break; case dbiAutoDownload: { qint32 photo, audio, gif; stream >> photo >> audio >> gif; if (!_checkStreamStatus(stream)) return false; cSetAutoDownloadPhoto(photo); cSetAutoDownloadAudio(audio); cSetAutoDownloadGif(gif); } break; case dbiAutoPlay: { qint32 gif; stream >> gif; if (!_checkStreamStatus(stream)) return false; cSetAutoPlayGif(gif == 1); } break; case dbiDialogsMode: { qint32 enabled, modeInt; stream >> enabled >> modeInt; if (!_checkStreamStatus(stream)) return false; Global::SetDialogsModeEnabled(enabled == 1); auto mode = Dialogs::Mode::All; if (enabled) { mode = static_cast<Dialogs::Mode>(modeInt); if (mode != Dialogs::Mode::All && mode != Dialogs::Mode::Important) { mode = Dialogs::Mode::All; } } Global::SetDialogsMode(mode); } break; case dbiModerateMode: { qint32 enabled; stream >> enabled; if (!_checkStreamStatus(stream)) return false; Global::SetModerateModeEnabled(enabled == 1); } break; case dbiIncludeMuted: { qint32 v; stream >> v; if (!_checkStreamStatus(stream)) return false; Global::SetIncludeMuted(v == 1); } break; case dbiShowingSavedGifsOld: { qint32 v; stream >> v; if (!_checkStreamStatus(stream)) return false; } break; case dbiDesktopNotify: { qint32 v; stream >> v; if (!_checkStreamStatus(stream)) return false; Global::SetDesktopNotify(v == 1); if (App::wnd()) App::wnd()->updateTrayMenu(); } break; case dbiWindowsNotificationsOld: { qint32 v; stream >> v; if (!_checkStreamStatus(stream)) return false; } break; case dbiNativeNotifications: { qint32 v; stream >> v; if (!_checkStreamStatus(stream)) return false; Global::SetNativeNotifications(v == 1); } break; case dbiNotificationsCount: { qint32 v; stream >> v; if (!_checkStreamStatus(stream)) return false; Global::SetNotificationsCount((v > 0 ? v : 3)); } break; case dbiNotificationsCorner: { qint32 v; stream >> v; if (!_checkStreamStatus(stream)) return false; Global::SetNotificationsCorner(static_cast<Notify::ScreenCorner>((v >= 0 && v < 4) ? v : 2)); } break; case dbiDialogsWidthRatioOld: { qint32 v; stream >> v; if (!_checkStreamStatus(stream)) return false; GetStoredAuthSessionCache().setDialogsWidthRatio(v / 1000000.); } break; case dbiLastSeenWarningSeenOld: { qint32 v; stream >> v; if (!_checkStreamStatus(stream)) return false; GetStoredAuthSessionCache().setLastSeenWarningSeen(v == 1); } break; case dbiAuthSessionSettings: { QByteArray v; stream >> v; if (!_checkStreamStatus(stream)) return false; GetStoredAuthSessionCache().constructFromSerialized(v); } break; case dbiWorkMode: { qint32 v; stream >> v; if (!_checkStreamStatus(stream)) return false; auto newMode = [v] { switch (v) { case dbiwmTrayOnly: return dbiwmTrayOnly; case dbiwmWindowOnly: return dbiwmWindowOnly; }; return dbiwmWindowAndTray; }; Global::RefWorkMode().set(newMode()); } break; case dbiConnectionTypeOld: { qint32 v; stream >> v; if (!_checkStreamStatus(stream)) return false; switch (v) { case dbictHttpProxy: case dbictTcpProxy: { ProxyData p; qint32 port; stream >> p.host >> port >> p.user >> p.password; if (!_checkStreamStatus(stream)) return false; p.port = uint32(port); Global::SetConnectionProxy(p); Global::SetConnectionType(DBIConnectionType(v)); } break; case dbictHttpAuto: default: Global::SetConnectionType(dbictAuto); break; }; Global::SetLastProxyType(Global::ConnectionType()); } break; case dbiConnectionType: { ProxyData p; qint32 connectionType, lastProxyType, port; stream >> connectionType >> lastProxyType >> p.host >> port >> p.user >> p.password; if (!_checkStreamStatus(stream)) return false; p.port = port; switch (connectionType) { case dbictHttpProxy: case dbictTcpProxy: { Global::SetConnectionType(DBIConnectionType(lastProxyType)); } break; case dbictHttpAuto: default: Global::SetConnectionType(dbictAuto); break; }; switch (lastProxyType) { case dbictHttpProxy: case dbictTcpProxy: { Global::SetLastProxyType(DBIConnectionType(lastProxyType)); Global::SetConnectionProxy(p); } break; case dbictHttpAuto: default: { Global::SetLastProxyType(dbictAuto); Global::SetConnectionProxy(ProxyData()); } break; } } break; case dbiThemeKey: { quint64 themeKey = 0; stream >> themeKey; if (!_checkStreamStatus(stream)) return false; _themeKey = themeKey; } break; case dbiLangPackKey: { quint64 langPackKey = 0; stream >> langPackKey; if (!_checkStreamStatus(stream)) return false; _langPackKey = langPackKey; } break; case dbiTryIPv6: { qint32 v; stream >> v; if (!_checkStreamStatus(stream)) return false; Global::SetTryIPv6(v == 1); } break; case dbiSeenTrayTooltip: { qint32 v; stream >> v; if (!_checkStreamStatus(stream)) return false; cSetSeenTrayTooltip(v == 1); } break; case dbiAutoUpdate: { qint32 v; stream >> v; if (!_checkStreamStatus(stream)) return false; cSetAutoUpdate(v == 1); #ifndef TDESKTOP_DISABLE_AUTOUPDATE if (!cAutoUpdate()) { Sandbox::stopUpdate(); } #endif // !TDESKTOP_DISABLE_AUTOUPDATE } break; case dbiLastUpdateCheck: { qint32 v; stream >> v; if (!_checkStreamStatus(stream)) return false; cSetLastUpdateCheck(v); } break; case dbiScale: { qint32 v; stream >> v; if (!_checkStreamStatus(stream)) return false; DBIScale s = cRealScale(); switch (v) { case dbisAuto: s = dbisAuto; break; case dbisOne: s = dbisOne; break; case dbisOneAndQuarter: s = dbisOneAndQuarter; break; case dbisOneAndHalf: s = dbisOneAndHalf; break; case dbisTwo: s = dbisTwo; break; } if (cRetina()) s = dbisOne; cSetConfigScale(s); cSetRealScale(s); } break; case dbiLangOld: { qint32 v; stream >> v; if (!_checkStreamStatus(stream)) return false; context.legacyLanguageId = v; } break; case dbiLangFileOld: { QString v; stream >> v; if (!_checkStreamStatus(stream)) return false; context.legacyLanguageFile = v; } break; case dbiWindowPosition: { auto position = TWindowPos(); stream >> position.x >> position.y >> position.w >> position.h; stream >> position.moncrc >> position.maximized; if (!_checkStreamStatus(stream)) return false; DEBUG_LOG(("Window Pos: Read from storage %1, %2, %3, %4 (maximized %5)").arg(position.x).arg(position.y).arg(position.w).arg(position.h).arg(Logs::b(position.maximized))); cSetWindowPos(position); } break; case dbiLoggedPhoneNumber: { QString v; stream >> v; if (!_checkStreamStatus(stream)) return false; cSetLoggedPhoneNumber(v); } break; case dbiMutePeer: { // deprecated quint64 peerId; stream >> peerId; if (!_checkStreamStatus(stream)) return false; } break; case dbiMutedPeers: { // deprecated quint32 count; stream >> count; if (!_checkStreamStatus(stream)) return false; for (uint32 i = 0; i < count; ++i) { quint64 peerId; stream >> peerId; } if (!_checkStreamStatus(stream)) return false; } break; case dbiSendKey: { qint32 v; stream >> v; if (!_checkStreamStatus(stream)) return false; cSetCtrlEnter(v == dbiskCtrlEnter); if (App::main()) App::main()->ctrlEnterSubmitUpdated(); } break; case dbiCatsAndDogs: { // deprecated qint32 v; stream >> v; if (!_checkStreamStatus(stream)) return false; } break; case dbiTileBackground: { qint32 v; stream >> v; if (!_checkStreamStatus(stream)) return false; bool tile = (version < 8005 && !_backgroundKey) ? false : (v == 1); Window::Theme::Background()->setTile(tile); } break; case dbiAdaptiveForWide: { qint32 v; stream >> v; if (!_checkStreamStatus(stream)) return false; Global::SetAdaptiveForWide(v == 1); } break; case dbiAutoLock: { qint32 v; stream >> v; if (!_checkStreamStatus(stream)) return false; Global::SetAutoLock(v); Global::RefLocalPasscodeChanged().notify(); } break; case dbiReplaceEmojis: { qint32 v; stream >> v; if (!_checkStreamStatus(stream)) return false; cSetReplaceEmojis(v == 1); } break; case dbiDefaultAttach: { qint32 v; stream >> v; if (!_checkStreamStatus(stream)) return false; } break; case dbiNotifyView: { qint32 v; stream >> v; if (!_checkStreamStatus(stream)) return false; switch (v) { case dbinvShowNothing: Global::SetNotifyView(dbinvShowNothing); break; case dbinvShowName: Global::SetNotifyView(dbinvShowName); break; default: Global::SetNotifyView(dbinvShowPreview); break; } } break; case dbiAskDownloadPath: { qint32 v; stream >> v; if (!_checkStreamStatus(stream)) return false; Global::SetAskDownloadPath(v == 1); } break; case dbiDownloadPathOld: { QString v; stream >> v; if (!_checkStreamStatus(stream)) return false; #ifndef OS_WIN_STORE if (!v.isEmpty() && v != qstr("tmp") && !v.endsWith('/')) v += '/'; Global::SetDownloadPath(v); Global::SetDownloadPathBookmark(QByteArray()); Global::RefDownloadPathChanged().notify(); #endif // OS_WIN_STORE } break; case dbiDownloadPath: { QString v; QByteArray bookmark; stream >> v >> bookmark; if (!_checkStreamStatus(stream)) return false; #ifndef OS_WIN_STORE if (!v.isEmpty() && v != qstr("tmp") && !v.endsWith('/')) v += '/'; Global::SetDownloadPath(v); Global::SetDownloadPathBookmark(bookmark); psDownloadPathEnableAccess(); Global::RefDownloadPathChanged().notify(); #endif // OS_WIN_STORE } break; case dbiCompressPastedImage: { qint32 v; stream >> v; if (!_checkStreamStatus(stream)) return false; GetStoredAuthSessionCache().setSendFilesWay((v == 1) ? SendFilesWay::Album : SendFilesWay::Files); } break; case dbiEmojiTabOld: { qint32 v; stream >> v; if (!_checkStreamStatus(stream)) return false; // deprecated } break; case dbiRecentEmojiOldOld: { RecentEmojiPreloadOldOld v; stream >> v; if (!_checkStreamStatus(stream)) return false; if (!v.isEmpty()) { RecentEmojiPreload p; p.reserve(v.size()); for (auto &item : v) { auto oldKey = uint64(item.first); switch (oldKey) { case 0xD83CDDEFLLU: oldKey = 0xD83CDDEFD83CDDF5LLU; break; case 0xD83CDDF0LLU: oldKey = 0xD83CDDF0D83CDDF7LLU; break; case 0xD83CDDE9LLU: oldKey = 0xD83CDDE9D83CDDEALLU; break; case 0xD83CDDE8LLU: oldKey = 0xD83CDDE8D83CDDF3LLU; break; case 0xD83CDDFALLU: oldKey = 0xD83CDDFAD83CDDF8LLU; break; case 0xD83CDDEBLLU: oldKey = 0xD83CDDEBD83CDDF7LLU; break; case 0xD83CDDEALLU: oldKey = 0xD83CDDEAD83CDDF8LLU; break; case 0xD83CDDEELLU: oldKey = 0xD83CDDEED83CDDF9LLU; break; case 0xD83CDDF7LLU: oldKey = 0xD83CDDF7D83CDDFALLU; break; case 0xD83CDDECLLU: oldKey = 0xD83CDDECD83CDDE7LLU; break; } auto id = Ui::Emoji::IdFromOldKey(oldKey); if (!id.isEmpty()) { p.push_back(qMakePair(id, item.second)); } } cSetRecentEmojiPreload(p); } } break; case dbiRecentEmojiOld: { RecentEmojiPreloadOld v; stream >> v; if (!_checkStreamStatus(stream)) return false; if (!v.isEmpty()) { RecentEmojiPreload p; p.reserve(v.size()); for (auto &item : v) { auto id = Ui::Emoji::IdFromOldKey(item.first); if (!id.isEmpty()) { p.push_back(qMakePair(id, item.second)); } } cSetRecentEmojiPreload(p); } } break; case dbiRecentEmoji: { RecentEmojiPreload v; stream >> v; if (!_checkStreamStatus(stream)) return false; cSetRecentEmojiPreload(v); } break; case dbiRecentStickers: { RecentStickerPreload v; stream >> v; if (!_checkStreamStatus(stream)) return false; cSetRecentStickersPreload(v); } break; case dbiEmojiVariantsOld: { EmojiColorVariantsOld v; stream >> v; if (!_checkStreamStatus(stream)) return false; EmojiColorVariants variants; for (auto i = v.cbegin(), e = v.cend(); i != e; ++i) { auto id = Ui::Emoji::IdFromOldKey(static_cast<uint64>(i.key())); if (!id.isEmpty()) { auto index = Ui::Emoji::ColorIndexFromOldKey(i.value()); if (index >= 0) { variants.insert(id, index); } } } cSetEmojiVariants(variants); } break; case dbiEmojiVariants: { EmojiColorVariants v; stream >> v; if (!_checkStreamStatus(stream)) return false; cSetEmojiVariants(v); } break; case dbiHiddenPinnedMessages: { Global::HiddenPinnedMessagesMap v; stream >> v; if (!_checkStreamStatus(stream)) return false; Global::SetHiddenPinnedMessages(v); } break; case dbiDialogLastPath: { QString path; stream >> path; if (!_checkStreamStatus(stream)) return false; cSetDialogLastPath(path); } break; case dbiSongVolume: { qint32 v; stream >> v; if (!_checkStreamStatus(stream)) return false; Global::SetSongVolume(snap(v / 1e6, 0., 1.)); } break; case dbiVideoVolume: { qint32 v; stream >> v; if (!_checkStreamStatus(stream)) return false; Global::SetVideoVolume(snap(v / 1e6, 0., 1.)); } break; default: LOG(("App Error: unknown blockId in _readSetting: %1").arg(blockId)); return false; } return true; } bool _readOldSettings(bool remove, ReadSettingsContext &context) { bool result = false; QFile file(cWorkingDir() + qsl("tdata/config")); if (file.open(QIODevice::ReadOnly)) { LOG(("App Info: reading old config...")); QDataStream stream(&file); stream.setVersion(QDataStream::Qt_5_1); qint32 version = 0; while (!stream.atEnd()) { quint32 blockId; stream >> blockId; if (!_checkStreamStatus(stream)) break; if (blockId == dbiVersion) { stream >> version; if (!_checkStreamStatus(stream)) break; if (version > AppVersion) break; } else if (!_readSetting(blockId, stream, version, context)) { break; } } file.close(); result = true; } if (remove) file.remove(); return result; } void _readOldUserSettingsFields(QIODevice *device, qint32 &version, ReadSettingsContext &context) { QDataStream stream(device); stream.setVersion(QDataStream::Qt_5_1); while (!stream.atEnd()) { quint32 blockId; stream >> blockId; if (!_checkStreamStatus(stream)) { break; } if (blockId == dbiVersion) { stream >> version; if (!_checkStreamStatus(stream)) { break; } if (version > AppVersion) return; } else if (blockId == dbiEncryptedWithSalt) { QByteArray salt, data, decrypted; stream >> salt >> data; if (!_checkStreamStatus(stream)) { break; } if (salt.size() != 32) { LOG(("App Error: bad salt in old user config encrypted part, size: %1").arg(salt.size())); continue; } createLocalKey(QByteArray(), &salt, &OldKey); if (data.size() <= 16 || (data.size() & 0x0F)) { LOG(("App Error: bad encrypted part size in old user config: %1").arg(data.size())); continue; } uint32 fullDataLen = data.size() - 16; decrypted.resize(fullDataLen); const char *dataKey = data.constData(), *encrypted = data.constData() + 16; aesDecryptLocal(encrypted, decrypted.data(), fullDataLen, OldKey, dataKey); uchar sha1Buffer[20]; if (memcmp(hashSha1(decrypted.constData(), decrypted.size(), sha1Buffer), dataKey, 16)) { LOG(("App Error: bad decrypt key, data from old user config not decrypted")); continue; } uint32 dataLen = *(const uint32*)decrypted.constData(); if (dataLen > uint32(decrypted.size()) || dataLen <= fullDataLen - 16 || dataLen < 4) { LOG(("App Error: bad decrypted part size in old user config: %1, fullDataLen: %2, decrypted size: %3").arg(dataLen).arg(fullDataLen).arg(decrypted.size())); continue; } decrypted.resize(dataLen); QBuffer decryptedStream(&decrypted); decryptedStream.open(QIODevice::ReadOnly); decryptedStream.seek(4); // skip size LOG(("App Info: reading encrypted old user config...")); _readOldUserSettingsFields(&decryptedStream, version, context); } else if (!_readSetting(blockId, stream, version, context)) { return; } } } bool _readOldUserSettings(bool remove, ReadSettingsContext &context) { bool result = false; QFile file(cWorkingDir() + cDataFile() + (cTestMode() ? qsl("_test") : QString()) + qsl("_config")); if (file.open(QIODevice::ReadOnly)) { LOG(("App Info: reading old user config...")); qint32 version = 0; _readOldUserSettingsFields(&file, version, context); file.close(); result = true; } if (remove) file.remove(); return result; } void _readOldMtpDataFields(QIODevice *device, qint32 &version, ReadSettingsContext &context) { QDataStream stream(device); stream.setVersion(QDataStream::Qt_5_1); while (!stream.atEnd()) { quint32 blockId; stream >> blockId; if (!_checkStreamStatus(stream)) { break; } if (blockId == dbiVersion) { stream >> version; if (!_checkStreamStatus(stream)) { break; } if (version > AppVersion) return; } else if (blockId == dbiEncrypted) { QByteArray data, decrypted; stream >> data; if (!_checkStreamStatus(stream)) { break; } if (!OldKey) { LOG(("MTP Error: reading old encrypted keys without old key!")); continue; } if (data.size() <= 16 || (data.size() & 0x0F)) { LOG(("MTP Error: bad encrypted part size in old keys: %1").arg(data.size())); continue; } uint32 fullDataLen = data.size() - 16; decrypted.resize(fullDataLen); const char *dataKey = data.constData(), *encrypted = data.constData() + 16; aesDecryptLocal(encrypted, decrypted.data(), fullDataLen, OldKey, dataKey); uchar sha1Buffer[20]; if (memcmp(hashSha1(decrypted.constData(), decrypted.size(), sha1Buffer), dataKey, 16)) { LOG(("MTP Error: bad decrypt key, data from old keys not decrypted")); continue; } uint32 dataLen = *(const uint32*)decrypted.constData(); if (dataLen > uint32(decrypted.size()) || dataLen <= fullDataLen - 16 || dataLen < 4) { LOG(("MTP Error: bad decrypted part size in old keys: %1, fullDataLen: %2, decrypted size: %3").arg(dataLen).arg(fullDataLen).arg(decrypted.size())); continue; } decrypted.resize(dataLen); QBuffer decryptedStream(&decrypted); decryptedStream.open(QIODevice::ReadOnly); decryptedStream.seek(4); // skip size LOG(("App Info: reading encrypted old keys...")); _readOldMtpDataFields(&decryptedStream, version, context); } else if (!_readSetting(blockId, stream, version, context)) { return; } } } bool _readOldMtpData(bool remove, ReadSettingsContext &context) { bool result = false; QFile file(cWorkingDir() + cDataFile() + (cTestMode() ? qsl("_test") : QString())); if (file.open(QIODevice::ReadOnly)) { LOG(("App Info: reading old keys...")); qint32 version = 0; _readOldMtpDataFields(&file, version, context); file.close(); result = true; } if (remove) file.remove(); return result; } void _writeUserSettings() { if (_readingUserSettings) { LOG(("App Error: attempt to write settings while reading them!")); return; } LOG(("App Info: writing encrypted user settings...")); if (!_userSettingsKey) { _userSettingsKey = genKey(); _mapChanged = true; _writeMap(WriteMapWhen::Fast); } auto recentEmojiPreloadData = cRecentEmojiPreload(); if (recentEmojiPreloadData.isEmpty()) { recentEmojiPreloadData.reserve(Ui::Emoji::GetRecent().size()); for (auto &item : Ui::Emoji::GetRecent()) { recentEmojiPreloadData.push_back(qMakePair(item.first->id(), item.second)); } } auto userDataInstance = StoredAuthSessionCache ? StoredAuthSessionCache.get() : Messenger::Instance().getAuthSessionSettings(); auto userData = userDataInstance ? userDataInstance->serialize() : QByteArray(); uint32 size = 22 * (sizeof(quint32) + sizeof(qint32)); size += sizeof(quint32) + Serialize::stringSize(Global::AskDownloadPath() ? QString() : Global::DownloadPath()) + Serialize::bytearraySize(Global::AskDownloadPath() ? QByteArray() : Global::DownloadPathBookmark()); size += sizeof(quint32) + sizeof(qint32); for (auto &item : recentEmojiPreloadData) { size += Serialize::stringSize(item.first) + sizeof(item.second); } size += sizeof(quint32) + sizeof(qint32) + cEmojiVariants().size() * (sizeof(uint32) + sizeof(uint64)); size += sizeof(quint32) + sizeof(qint32) + (Stickers::GetRecentPack().isEmpty() ? Stickers::GetRecentPack().size() : cRecentStickersPreload().size()) * (sizeof(uint64) + sizeof(ushort)); size += sizeof(quint32) + Serialize::stringSize(cDialogLastPath()); size += sizeof(quint32) + 3 * sizeof(qint32); size += sizeof(quint32) + 2 * sizeof(qint32); if (!Global::HiddenPinnedMessages().isEmpty()) { size += sizeof(quint32) + sizeof(qint32) + Global::HiddenPinnedMessages().size() * (sizeof(PeerId) + sizeof(MsgId)); } if (!userData.isEmpty()) { size += sizeof(quint32) + Serialize::bytearraySize(userData); } EncryptedDescriptor data(size); data.stream << quint32(dbiSendKey) << qint32(cCtrlEnter() ? dbiskCtrlEnter : dbiskEnter); data.stream << quint32(dbiTileBackground) << qint32(Window::Theme::Background()->tileForSave() ? 1 : 0); data.stream << quint32(dbiAdaptiveForWide) << qint32(Global::AdaptiveForWide() ? 1 : 0); data.stream << quint32(dbiAutoLock) << qint32(Global::AutoLock()); data.stream << quint32(dbiReplaceEmojis) << qint32(cReplaceEmojis() ? 1 : 0); data.stream << quint32(dbiSoundNotify) << qint32(Global::SoundNotify()); data.stream << quint32(dbiIncludeMuted) << qint32(Global::IncludeMuted()); data.stream << quint32(dbiDesktopNotify) << qint32(Global::DesktopNotify()); data.stream << quint32(dbiNotifyView) << qint32(Global::NotifyView()); data.stream << quint32(dbiNativeNotifications) << qint32(Global::NativeNotifications()); data.stream << quint32(dbiNotificationsCount) << qint32(Global::NotificationsCount()); data.stream << quint32(dbiNotificationsCorner) << qint32(Global::NotificationsCorner()); data.stream << quint32(dbiAskDownloadPath) << qint32(Global::AskDownloadPath()); data.stream << quint32(dbiDownloadPath) << (Global::AskDownloadPath() ? QString() : Global::DownloadPath()) << (Global::AskDownloadPath() ? QByteArray() : Global::DownloadPathBookmark()); data.stream << quint32(dbiDialogLastPath) << cDialogLastPath(); data.stream << quint32(dbiSongVolume) << qint32(qRound(Global::SongVolume() * 1e6)); data.stream << quint32(dbiVideoVolume) << qint32(qRound(Global::VideoVolume() * 1e6)); data.stream << quint32(dbiAutoDownload) << qint32(cAutoDownloadPhoto()) << qint32(cAutoDownloadAudio()) << qint32(cAutoDownloadGif()); data.stream << quint32(dbiDialogsMode) << qint32(Global::DialogsModeEnabled() ? 1 : 0) << static_cast<qint32>(Global::DialogsMode()); data.stream << quint32(dbiModerateMode) << qint32(Global::ModerateModeEnabled() ? 1 : 0); data.stream << quint32(dbiAutoPlay) << qint32(cAutoPlayGif() ? 1 : 0); data.stream << quint32(dbiUseExternalVideoPlayer) << qint32(cUseExternalVideoPlayer()); data.stream << quint32(dbiSuggestStickersByEmoji) << qint32(Global::SuggestStickersByEmoji() ? 1 : 0); if (!userData.isEmpty()) { data.stream << quint32(dbiAuthSessionSettings) << userData; } { data.stream << quint32(dbiRecentEmoji) << recentEmojiPreloadData; } data.stream << quint32(dbiEmojiVariants) << cEmojiVariants(); { auto v = cRecentStickersPreload(); if (v.isEmpty()) { v.reserve(Stickers::GetRecentPack().size()); for_const (auto &pair, Stickers::GetRecentPack()) { v.push_back(qMakePair(pair.first->id, pair.second)); } } data.stream << quint32(dbiRecentStickers) << v; } if (!Global::HiddenPinnedMessages().isEmpty()) { data.stream << quint32(dbiHiddenPinnedMessages) << Global::HiddenPinnedMessages(); } FileWriteDescriptor file(_userSettingsKey); file.writeEncrypted(data); } void _readUserSettings() { ReadSettingsContext context; FileReadDescriptor userSettings; if (!readEncryptedFile(userSettings, _userSettingsKey)) { LOG(("App Info: could not read encrypted user settings...")); _readOldUserSettings(true, context); applyReadContext(std::move(context)); return _writeUserSettings(); } LOG(("App Info: reading encrypted user settings...")); _readingUserSettings = true; while (!userSettings.stream.atEnd()) { quint32 blockId; userSettings.stream >> blockId; if (!_checkStreamStatus(userSettings.stream)) { _readingUserSettings = false; return _writeUserSettings(); } if (!_readSetting(blockId, userSettings.stream, userSettings.version, context)) { _readingUserSettings = false; return _writeUserSettings(); } } _readingUserSettings = false; LOG(("App Info: encrypted user settings read.")); applyReadContext(std::move(context)); } void _writeMtpData() { FileWriteDescriptor mtp(toFilePart(_dataNameKey), FileOption::Safe); if (!LocalKey) { LOG(("App Error: localkey not created in _writeMtpData()")); return; } auto mtpAuthorizationSerialized = Messenger::Instance().serializeMtpAuthorization(); quint32 size = sizeof(quint32) + Serialize::bytearraySize(mtpAuthorizationSerialized); EncryptedDescriptor data(size); data.stream << quint32(dbiMtpAuthorization) << mtpAuthorizationSerialized; mtp.writeEncrypted(data); } void _readMtpData() { ReadSettingsContext context; FileReadDescriptor mtp; if (!readEncryptedFile(mtp, toFilePart(_dataNameKey), FileOption::Safe)) { if (LocalKey) { _readOldMtpData(true, context); applyReadContext(std::move(context)); _writeMtpData(); } return; } LOG(("App Info: reading encrypted mtp data...")); while (!mtp.stream.atEnd()) { quint32 blockId; mtp.stream >> blockId; if (!_checkStreamStatus(mtp.stream)) { return _writeMtpData(); } if (!_readSetting(blockId, mtp.stream, mtp.version, context)) { return _writeMtpData(); } } applyReadContext(std::move(context)); } ReadMapState _readMap(const QByteArray &pass) { auto ms = getms(); QByteArray dataNameUtf8 = (cDataFile() + (cTestMode() ? qsl(":/test/") : QString())).toUtf8(); FileKey dataNameHash[2]; hashMd5(dataNameUtf8.constData(), dataNameUtf8.size(), dataNameHash); _dataNameKey = dataNameHash[0]; _userBasePath = _basePath + toFilePart(_dataNameKey) + QChar('/'); FileReadDescriptor mapData; if (!readFile(mapData, qsl("map"))) { return ReadMapFailed; } LOG(("App Info: reading map...")); QByteArray salt, keyEncrypted, mapEncrypted; mapData.stream >> salt >> keyEncrypted >> mapEncrypted; if (!_checkStreamStatus(mapData.stream)) { return ReadMapFailed; } if (salt.size() != LocalEncryptSaltSize) { LOG(("App Error: bad salt in map file, size: %1").arg(salt.size())); return ReadMapFailed; } createLocalKey(pass, &salt, &PassKey); EncryptedDescriptor keyData, map; if (!decryptLocal(keyData, keyEncrypted, PassKey)) { LOG(("App Info: could not decrypt pass-protected key from map file, maybe bad password...")); return ReadMapPassNeeded; } auto key = Serialize::read<MTP::AuthKey::Data>(keyData.stream); if (keyData.stream.status() != QDataStream::Ok || !keyData.stream.atEnd()) { LOG(("App Error: could not read pass-protected key from map file")); return ReadMapFailed; } LocalKey = std::make_shared<MTP::AuthKey>(key); _passKeyEncrypted = keyEncrypted; _passKeySalt = salt; if (!decryptLocal(map, mapEncrypted)) { LOG(("App Error: could not decrypt map.")); return ReadMapFailed; } LOG(("App Info: reading encrypted map...")); DraftsMap draftsMap, draftCursorsMap; DraftsNotReadMap draftsNotReadMap; StorageMap imagesMap, stickerImagesMap, audiosMap; qint64 storageImagesSize = 0, storageStickersSize = 0, storageAudiosSize = 0; quint64 locationsKey = 0, reportSpamStatusesKey = 0, trustedBotsKey = 0; quint64 recentStickersKeyOld = 0; quint64 installedStickersKey = 0, featuredStickersKey = 0, recentStickersKey = 0, favedStickersKey = 0, archivedStickersKey = 0; quint64 savedGifsKey = 0; quint64 backgroundKey = 0, userSettingsKey = 0, recentHashtagsAndBotsKey = 0, savedPeersKey = 0; while (!map.stream.atEnd()) { quint32 keyType; map.stream >> keyType; switch (keyType) { case lskDraft: { quint32 count = 0; map.stream >> count; for (quint32 i = 0; i < count; ++i) { FileKey key; quint64 p; map.stream >> key >> p; draftsMap.insert(p, key); draftsNotReadMap.insert(p, true); } } break; case lskDraftPosition: { quint32 count = 0; map.stream >> count; for (quint32 i = 0; i < count; ++i) { FileKey key; quint64 p; map.stream >> key >> p; draftCursorsMap.insert(p, key); } } break; case lskImages: { quint32 count = 0; map.stream >> count; for (quint32 i = 0; i < count; ++i) { FileKey key; quint64 first, second; qint32 size; map.stream >> key >> first >> second >> size; imagesMap.insert(StorageKey(first, second), FileDesc(key, size)); storageImagesSize += size; } } break; case lskStickerImages: { quint32 count = 0; map.stream >> count; for (quint32 i = 0; i < count; ++i) { FileKey key; quint64 first, second; qint32 size; map.stream >> key >> first >> second >> size; stickerImagesMap.insert(StorageKey(first, second), FileDesc(key, size)); storageStickersSize += size; } } break; case lskAudios: { quint32 count = 0; map.stream >> count; for (quint32 i = 0; i < count; ++i) { FileKey key; quint64 first, second; qint32 size; map.stream >> key >> first >> second >> size; audiosMap.insert(StorageKey(first, second), FileDesc(key, size)); storageAudiosSize += size; } } break; case lskLocations: { map.stream >> locationsKey; } break; case lskReportSpamStatuses: { map.stream >> reportSpamStatusesKey; } break; case lskTrustedBots: { map.stream >> trustedBotsKey; } break; case lskRecentStickersOld: { map.stream >> recentStickersKeyOld; } break; case lskBackground: { map.stream >> backgroundKey; } break; case lskUserSettings: { map.stream >> userSettingsKey; } break; case lskRecentHashtagsAndBots: { map.stream >> recentHashtagsAndBotsKey; } break; case lskStickersOld: { map.stream >> installedStickersKey; } break; case lskStickersKeys: { map.stream >> installedStickersKey >> featuredStickersKey >> recentStickersKey >> archivedStickersKey; } break; case lskFavedStickers: { map.stream >> favedStickersKey; } break; case lskSavedGifsOld: { quint64 key; map.stream >> key; } break; case lskSavedGifs: { map.stream >> savedGifsKey; } break; case lskSavedPeers: { map.stream >> savedPeersKey; } break; default: LOG(("App Error: unknown key type in encrypted map: %1").arg(keyType)); return ReadMapFailed; } if (!_checkStreamStatus(map.stream)) { return ReadMapFailed; } } _draftsMap = draftsMap; _draftCursorsMap = draftCursorsMap; _draftsNotReadMap = draftsNotReadMap; _imagesMap = imagesMap; _storageImagesSize = storageImagesSize; _stickerImagesMap = stickerImagesMap; _storageStickersSize = storageStickersSize; _audiosMap = audiosMap; _storageAudiosSize = storageAudiosSize; _locationsKey = locationsKey; _reportSpamStatusesKey = reportSpamStatusesKey; _trustedBotsKey = trustedBotsKey; _recentStickersKeyOld = recentStickersKeyOld; _installedStickersKey = installedStickersKey; _featuredStickersKey = featuredStickersKey; _recentStickersKey = recentStickersKey; _favedStickersKey = favedStickersKey; _archivedStickersKey = archivedStickersKey; _savedGifsKey = savedGifsKey; _savedPeersKey = savedPeersKey; _backgroundKey = backgroundKey; _userSettingsKey = userSettingsKey; _recentHashtagsAndBotsKey = recentHashtagsAndBotsKey; _oldMapVersion = mapData.version; if (_oldMapVersion < AppVersion) { _mapChanged = true; _writeMap(); } else { _mapChanged = false; } if (_locationsKey) { _readLocations(); } if (_reportSpamStatusesKey) { _readReportSpamStatuses(); } _readUserSettings(); _readMtpData(); Messenger::Instance().setAuthSessionFromStorage(std::move(StoredAuthSessionCache)); LOG(("Map read time: %1").arg(getms() - ms)); if (_oldSettingsVersion < AppVersion) { writeSettings(); } return ReadMapDone; } void _writeMap(WriteMapWhen when) { if (when != WriteMapWhen::Now) { _manager->writeMap(when == WriteMapWhen::Fast); return; } _manager->writingMap(); if (!_mapChanged) return; if (_userBasePath.isEmpty()) { LOG(("App Error: _userBasePath is empty in writeMap()")); return; } if (!QDir().exists(_userBasePath)) QDir().mkpath(_userBasePath); FileWriteDescriptor map(qsl("map")); if (_passKeySalt.isEmpty() || _passKeyEncrypted.isEmpty()) { QByteArray pass(kLocalKeySize, Qt::Uninitialized), salt(LocalEncryptSaltSize, Qt::Uninitialized); memset_rand(pass.data(), pass.size()); memset_rand(salt.data(), salt.size()); createLocalKey(pass, &salt, &LocalKey); _passKeySalt.resize(LocalEncryptSaltSize); memset_rand(_passKeySalt.data(), _passKeySalt.size()); createLocalKey(QByteArray(), &_passKeySalt, &PassKey); EncryptedDescriptor passKeyData(kLocalKeySize); LocalKey->write(passKeyData.stream); _passKeyEncrypted = FileWriteDescriptor::prepareEncrypted(passKeyData, PassKey); } map.writeData(_passKeySalt); map.writeData(_passKeyEncrypted); uint32 mapSize = 0; if (!_draftsMap.isEmpty()) mapSize += sizeof(quint32) * 2 + _draftsMap.size() * sizeof(quint64) * 2; if (!_draftCursorsMap.isEmpty()) mapSize += sizeof(quint32) * 2 + _draftCursorsMap.size() * sizeof(quint64) * 2; if (!_imagesMap.isEmpty()) mapSize += sizeof(quint32) * 2 + _imagesMap.size() * (sizeof(quint64) * 3 + sizeof(qint32)); if (!_stickerImagesMap.isEmpty()) mapSize += sizeof(quint32) * 2 + _stickerImagesMap.size() * (sizeof(quint64) * 3 + sizeof(qint32)); if (!_audiosMap.isEmpty()) mapSize += sizeof(quint32) * 2 + _audiosMap.size() * (sizeof(quint64) * 3 + sizeof(qint32)); if (_locationsKey) mapSize += sizeof(quint32) + sizeof(quint64); if (_reportSpamStatusesKey) mapSize += sizeof(quint32) + sizeof(quint64); if (_trustedBotsKey) mapSize += sizeof(quint32) + sizeof(quint64); if (_recentStickersKeyOld) mapSize += sizeof(quint32) + sizeof(quint64); if (_installedStickersKey || _featuredStickersKey || _recentStickersKey || _archivedStickersKey) { mapSize += sizeof(quint32) + 4 * sizeof(quint64); } if (_favedStickersKey) mapSize += sizeof(quint32) + sizeof(quint64); if (_savedGifsKey) mapSize += sizeof(quint32) + sizeof(quint64); if (_savedPeersKey) mapSize += sizeof(quint32) + sizeof(quint64); if (_backgroundKey) mapSize += sizeof(quint32) + sizeof(quint64); if (_userSettingsKey) mapSize += sizeof(quint32) + sizeof(quint64); if (_recentHashtagsAndBotsKey) mapSize += sizeof(quint32) + sizeof(quint64); if (mapSize > 30 * 1024 * 1024) { CrashReports::SetAnnotation("MapSize", QString("%1,%2,%3,%4,%5" ).arg(_draftsMap.size() ).arg(_draftCursorsMap.size() ).arg(_imagesMap.size() ).arg(_stickerImagesMap.size() ).arg(_audiosMap.size() )); } EncryptedDescriptor mapData(mapSize); if (!_draftsMap.isEmpty()) { mapData.stream << quint32(lskDraft) << quint32(_draftsMap.size()); for (DraftsMap::const_iterator i = _draftsMap.cbegin(), e = _draftsMap.cend(); i != e; ++i) { mapData.stream << quint64(i.value()) << quint64(i.key()); } } if (!_draftCursorsMap.isEmpty()) { mapData.stream << quint32(lskDraftPosition) << quint32(_draftCursorsMap.size()); for (DraftsMap::const_iterator i = _draftCursorsMap.cbegin(), e = _draftCursorsMap.cend(); i != e; ++i) { mapData.stream << quint64(i.value()) << quint64(i.key()); } } if (!_imagesMap.isEmpty()) { mapData.stream << quint32(lskImages) << quint32(_imagesMap.size()); for (StorageMap::const_iterator i = _imagesMap.cbegin(), e = _imagesMap.cend(); i != e; ++i) { mapData.stream << quint64(i.value().first) << quint64(i.key().first) << quint64(i.key().second) << qint32(i.value().second); } } if (!_stickerImagesMap.isEmpty()) { mapData.stream << quint32(lskStickerImages) << quint32(_stickerImagesMap.size()); for (StorageMap::const_iterator i = _stickerImagesMap.cbegin(), e = _stickerImagesMap.cend(); i != e; ++i) { mapData.stream << quint64(i.value().first) << quint64(i.key().first) << quint64(i.key().second) << qint32(i.value().second); } } if (!_audiosMap.isEmpty()) { mapData.stream << quint32(lskAudios) << quint32(_audiosMap.size()); for (StorageMap::const_iterator i = _audiosMap.cbegin(), e = _audiosMap.cend(); i != e; ++i) { mapData.stream << quint64(i.value().first) << quint64(i.key().first) << quint64(i.key().second) << qint32(i.value().second); } } if (_locationsKey) { mapData.stream << quint32(lskLocations) << quint64(_locationsKey); } if (_reportSpamStatusesKey) { mapData.stream << quint32(lskReportSpamStatuses) << quint64(_reportSpamStatusesKey); } if (_trustedBotsKey) { mapData.stream << quint32(lskTrustedBots) << quint64(_trustedBotsKey); } if (_recentStickersKeyOld) { mapData.stream << quint32(lskRecentStickersOld) << quint64(_recentStickersKeyOld); } if (_installedStickersKey || _featuredStickersKey || _recentStickersKey || _archivedStickersKey) { mapData.stream << quint32(lskStickersKeys); mapData.stream << quint64(_installedStickersKey) << quint64(_featuredStickersKey) << quint64(_recentStickersKey) << quint64(_archivedStickersKey); } if (_favedStickersKey) { mapData.stream << quint32(lskFavedStickers) << quint64(_favedStickersKey); } if (_savedGifsKey) { mapData.stream << quint32(lskSavedGifs) << quint64(_savedGifsKey); } if (_savedPeersKey) { mapData.stream << quint32(lskSavedPeers) << quint64(_savedPeersKey); } if (_backgroundKey) { mapData.stream << quint32(lskBackground) << quint64(_backgroundKey); } if (_userSettingsKey) { mapData.stream << quint32(lskUserSettings) << quint64(_userSettingsKey); } if (_recentHashtagsAndBotsKey) { mapData.stream << quint32(lskRecentHashtagsAndBots) << quint64(_recentHashtagsAndBotsKey); } map.writeEncrypted(mapData); _mapChanged = false; if (mapSize > 30 * 1024 * 1024) { CrashReports::ClearAnnotation("MapSize"); } } } // namespace void finish() { if (_manager) { _writeMap(WriteMapWhen::Now); _manager->finish(); _manager->deleteLater(); _manager = 0; delete base::take(_localLoader); } } void readTheme(); void readLangPack(); void start() { Expects(!_manager); _manager = new internal::Manager(); _localLoader = new TaskQueue(kFileLoaderQueueStopTimeout); _basePath = cWorkingDir() + qsl("tdata/"); if (!QDir().exists(_basePath)) QDir().mkpath(_basePath); ReadSettingsContext context; FileReadDescriptor settingsData; if (!readFile(settingsData, cTestMode() ? qsl("settings_test") : qsl("settings"), FileOption::Safe)) { _readOldSettings(true, context); _readOldUserSettings(false, context); // needed further in _readUserSettings _readOldMtpData(false, context); // needed further in _readMtpData applyReadContext(std::move(context)); return writeSettings(); } LOG(("App Info: reading settings...")); QByteArray salt, settingsEncrypted; settingsData.stream >> salt >> settingsEncrypted; if (!_checkStreamStatus(settingsData.stream)) { return writeSettings(); } if (salt.size() != LocalEncryptSaltSize) { LOG(("App Error: bad salt in settings file, size: %1").arg(salt.size())); return writeSettings(); } createLocalKey(QByteArray(), &salt, &SettingsKey); EncryptedDescriptor settings; if (!decryptLocal(settings, settingsEncrypted, SettingsKey)) { LOG(("App Error: could not decrypt settings from settings file, maybe bad passcode...")); return writeSettings(); } LOG(("App Info: reading encrypted settings...")); while (!settings.stream.atEnd()) { quint32 blockId; settings.stream >> blockId; if (!_checkStreamStatus(settings.stream)) { return writeSettings(); } if (!_readSetting(blockId, settings.stream, settingsData.version, context)) { return writeSettings(); } } _oldSettingsVersion = settingsData.version; _settingsSalt = salt; readTheme(); readLangPack(); applyReadContext(std::move(context)); } void writeSettings() { if (_basePath.isEmpty()) { LOG(("App Error: _basePath is empty in writeSettings()")); return; } if (!QDir().exists(_basePath)) QDir().mkpath(_basePath); FileWriteDescriptor settings(cTestMode() ? qsl("settings_test") : qsl("settings"), FileOption::Safe); if (_settingsSalt.isEmpty() || !SettingsKey) { _settingsSalt.resize(LocalEncryptSaltSize); memset_rand(_settingsSalt.data(), _settingsSalt.size()); createLocalKey(QByteArray(), &_settingsSalt, &SettingsKey); } settings.writeData(_settingsSalt); auto dcOptionsSerialized = Messenger::Instance().dcOptions()->serialize(); quint32 size = 12 * (sizeof(quint32) + sizeof(qint32)); size += sizeof(quint32) + Serialize::bytearraySize(dcOptionsSerialized); auto &proxy = Global::ConnectionProxy(); size += sizeof(quint32) + sizeof(qint32) + sizeof(qint32); size += Serialize::stringSize(proxy.host) + sizeof(qint32) + Serialize::stringSize(proxy.user) + Serialize::stringSize(proxy.password); if (_themeKey) { size += sizeof(quint32) + sizeof(quint64); } if (_langPackKey) { size += sizeof(quint32) + sizeof(quint64); } size += sizeof(quint32) + sizeof(qint32) * 8; EncryptedDescriptor data(size); data.stream << quint32(dbiChatSizeMax) << qint32(Global::ChatSizeMax()); data.stream << quint32(dbiMegagroupSizeMax) << qint32(Global::MegagroupSizeMax()); data.stream << quint32(dbiSavedGifsLimit) << qint32(Global::SavedGifsLimit()); data.stream << quint32(dbiStickersRecentLimit) << qint32(Global::StickersRecentLimit()); data.stream << quint32(dbiStickersFavedLimit) << qint32(Global::StickersFavedLimit()); data.stream << quint32(dbiAutoStart) << qint32(cAutoStart()); data.stream << quint32(dbiStartMinimized) << qint32(cStartMinimized()); data.stream << quint32(dbiSendToMenu) << qint32(cSendToMenu()); data.stream << quint32(dbiWorkMode) << qint32(Global::WorkMode().value()); data.stream << quint32(dbiSeenTrayTooltip) << qint32(cSeenTrayTooltip()); data.stream << quint32(dbiAutoUpdate) << qint32(cAutoUpdate()); data.stream << quint32(dbiLastUpdateCheck) << qint32(cLastUpdateCheck()); data.stream << quint32(dbiScale) << qint32(cConfigScale()); data.stream << quint32(dbiDcOptions) << dcOptionsSerialized; data.stream << quint32(dbiConnectionType) << qint32(Global::ConnectionType()) << qint32(Global::LastProxyType()); data.stream << proxy.host << qint32(proxy.port) << proxy.user << proxy.password; data.stream << quint32(dbiTryIPv6) << qint32(Global::TryIPv6()); if (_themeKey) { data.stream << quint32(dbiThemeKey) << quint64(_themeKey); } if (_langPackKey) { data.stream << quint32(dbiLangPackKey) << quint64(_langPackKey); } auto position = cWindowPos(); data.stream << quint32(dbiWindowPosition) << qint32(position.x) << qint32(position.y) << qint32(position.w) << qint32(position.h); data.stream << qint32(position.moncrc) << qint32(position.maximized); DEBUG_LOG(("Window Pos: Writing to storage %1, %2, %3, %4 (maximized %5)").arg(position.x).arg(position.y).arg(position.w).arg(position.h).arg(Logs::b(position.maximized))); settings.writeEncrypted(data, SettingsKey); } void writeUserSettings() { _writeUserSettings(); } void writeMtpData() { _writeMtpData(); } void reset() { if (_localLoader) { _localLoader->stop(); } _passKeySalt.clear(); // reset passcode, local key _draftsMap.clear(); _draftCursorsMap.clear(); _fileLocations.clear(); _fileLocationPairs.clear(); _fileLocationAliases.clear(); _imagesMap.clear(); _draftsNotReadMap.clear(); _stickerImagesMap.clear(); _audiosMap.clear(); _storageImagesSize = _storageStickersSize = _storageAudiosSize = 0; _webFilesMap.clear(); _storageWebFilesSize = 0; _locationsKey = _reportSpamStatusesKey = _trustedBotsKey = 0; _recentStickersKeyOld = 0; _installedStickersKey = _featuredStickersKey = _recentStickersKey = _favedStickersKey = _archivedStickersKey = 0; _savedGifsKey = 0; _backgroundKey = _userSettingsKey = _recentHashtagsAndBotsKey = _savedPeersKey = 0; _oldMapVersion = _oldSettingsVersion = 0; StoredAuthSessionCache.reset(); _mapChanged = true; _writeMap(WriteMapWhen::Now); _writeMtpData(); } bool checkPasscode(const QByteArray &passcode) { auto checkKey = MTP::AuthKeyPtr(); createLocalKey(passcode, &_passKeySalt, &checkKey); return checkKey->equals(PassKey); } void setPasscode(const QByteArray &passcode) { createLocalKey(passcode, &_passKeySalt, &PassKey); EncryptedDescriptor passKeyData(kLocalKeySize); LocalKey->write(passKeyData.stream); _passKeyEncrypted = FileWriteDescriptor::prepareEncrypted(passKeyData, PassKey); _mapChanged = true; _writeMap(WriteMapWhen::Now); Global::SetLocalPasscode(!passcode.isEmpty()); Global::RefLocalPasscodeChanged().notify(); } ReadMapState readMap(const QByteArray &pass) { ReadMapState result = _readMap(pass); if (result == ReadMapFailed) { _mapChanged = true; _writeMap(WriteMapWhen::Now); } return result; } int32 oldMapVersion() { return _oldMapVersion; } int32 oldSettingsVersion() { return _oldSettingsVersion; } void writeDrafts(const PeerId &peer, const MessageDraft &localDraft, const MessageDraft &editDraft) { if (!_working()) return; if (localDraft.msgId <= 0 && localDraft.textWithTags.text.isEmpty() && editDraft.msgId <= 0) { auto i = _draftsMap.find(peer); if (i != _draftsMap.cend()) { clearKey(i.value()); _draftsMap.erase(i); _mapChanged = true; _writeMap(); } _draftsNotReadMap.remove(peer); } else { auto i = _draftsMap.constFind(peer); if (i == _draftsMap.cend()) { i = _draftsMap.insert(peer, genKey()); _mapChanged = true; _writeMap(WriteMapWhen::Fast); } auto msgTags = Ui::FlatTextarea::serializeTagsList(localDraft.textWithTags.tags); auto editTags = Ui::FlatTextarea::serializeTagsList(editDraft.textWithTags.tags); int size = sizeof(quint64); size += Serialize::stringSize(localDraft.textWithTags.text) + Serialize::bytearraySize(msgTags) + 2 * sizeof(qint32); size += Serialize::stringSize(editDraft.textWithTags.text) + Serialize::bytearraySize(editTags) + 2 * sizeof(qint32); EncryptedDescriptor data(size); data.stream << quint64(peer); data.stream << localDraft.textWithTags.text << msgTags; data.stream << qint32(localDraft.msgId) << qint32(localDraft.previewCancelled ? 1 : 0); data.stream << editDraft.textWithTags.text << editTags; data.stream << qint32(editDraft.msgId) << qint32(editDraft.previewCancelled ? 1 : 0); FileWriteDescriptor file(i.value()); file.writeEncrypted(data); _draftsNotReadMap.remove(peer); } } void clearDraftCursors(const PeerId &peer) { DraftsMap::iterator i = _draftCursorsMap.find(peer); if (i != _draftCursorsMap.cend()) { clearKey(i.value()); _draftCursorsMap.erase(i); _mapChanged = true; _writeMap(); } } void _readDraftCursors(const PeerId &peer, MessageCursor &localCursor, MessageCursor &editCursor) { DraftsMap::iterator j = _draftCursorsMap.find(peer); if (j == _draftCursorsMap.cend()) { return; } FileReadDescriptor draft; if (!readEncryptedFile(draft, j.value())) { clearDraftCursors(peer); return; } quint64 draftPeer; qint32 localPosition = 0, localAnchor = 0, localScroll = QFIXED_MAX; qint32 editPosition = 0, editAnchor = 0, editScroll = QFIXED_MAX; draft.stream >> draftPeer >> localPosition >> localAnchor >> localScroll; if (!draft.stream.atEnd()) { draft.stream >> editPosition >> editAnchor >> editScroll; } if (draftPeer != peer) { clearDraftCursors(peer); return; } localCursor = MessageCursor(localPosition, localAnchor, localScroll); editCursor = MessageCursor(editPosition, editAnchor, editScroll); } void readDraftsWithCursors(History *h) { PeerId peer = h->peer->id; if (!_draftsNotReadMap.remove(peer)) { clearDraftCursors(peer); return; } DraftsMap::iterator j = _draftsMap.find(peer); if (j == _draftsMap.cend()) { clearDraftCursors(peer); return; } FileReadDescriptor draft; if (!readEncryptedFile(draft, j.value())) { clearKey(j.value()); _draftsMap.erase(j); clearDraftCursors(peer); return; } quint64 draftPeer = 0; TextWithTags msgData, editData; QByteArray msgTagsSerialized, editTagsSerialized; qint32 msgReplyTo = 0, msgPreviewCancelled = 0, editMsgId = 0, editPreviewCancelled = 0; draft.stream >> draftPeer >> msgData.text; if (draft.version >= 9048) { draft.stream >> msgTagsSerialized; } if (draft.version >= 7021) { draft.stream >> msgReplyTo; if (draft.version >= 8001) { draft.stream >> msgPreviewCancelled; if (!draft.stream.atEnd()) { draft.stream >> editData.text; if (draft.version >= 9048) { draft.stream >> editTagsSerialized; } draft.stream >> editMsgId >> editPreviewCancelled; } } } if (draftPeer != peer) { clearKey(j.value()); _draftsMap.erase(j); clearDraftCursors(peer); return; } msgData.tags = Ui::FlatTextarea::deserializeTagsList(msgTagsSerialized, msgData.text.size()); editData.tags = Ui::FlatTextarea::deserializeTagsList(editTagsSerialized, editData.text.size()); MessageCursor msgCursor, editCursor; _readDraftCursors(peer, msgCursor, editCursor); if (!h->localDraft()) { if (msgData.text.isEmpty() && !msgReplyTo) { h->clearLocalDraft(); } else { h->setLocalDraft(std::make_unique<Data::Draft>(msgData, msgReplyTo, msgCursor, msgPreviewCancelled)); } } if (!editMsgId) { h->clearEditDraft(); } else { h->setEditDraft(std::make_unique<Data::Draft>(editData, editMsgId, editCursor, editPreviewCancelled)); } } void writeDraftCursors(const PeerId &peer, const MessageCursor &msgCursor, const MessageCursor &editCursor) { if (!_working()) return; if (msgCursor == MessageCursor() && editCursor == MessageCursor()) { clearDraftCursors(peer); } else { DraftsMap::const_iterator i = _draftCursorsMap.constFind(peer); if (i == _draftCursorsMap.cend()) { i = _draftCursorsMap.insert(peer, genKey()); _mapChanged = true; _writeMap(WriteMapWhen::Fast); } EncryptedDescriptor data(sizeof(quint64) + sizeof(qint32) * 3); data.stream << quint64(peer) << qint32(msgCursor.position) << qint32(msgCursor.anchor) << qint32(msgCursor.scroll); data.stream << qint32(editCursor.position) << qint32(editCursor.anchor) << qint32(editCursor.scroll); FileWriteDescriptor file(i.value()); file.writeEncrypted(data); } } bool hasDraftCursors(const PeerId &peer) { return _draftCursorsMap.contains(peer); } bool hasDraft(const PeerId &peer) { return _draftsMap.contains(peer); } void writeFileLocation(MediaKey location, const FileLocation &local) { if (local.fname.isEmpty()) return; FileLocationAliases::const_iterator aliasIt = _fileLocationAliases.constFind(location); if (aliasIt != _fileLocationAliases.cend()) { location = aliasIt.value(); } FileLocationPairs::iterator i = _fileLocationPairs.find(local.fname); if (i != _fileLocationPairs.cend()) { if (i.value().second == local) { if (i.value().first != location) { _fileLocationAliases.insert(location, i.value().first); _writeLocations(WriteMapWhen::Fast); } return; } if (i.value().first != location) { for (FileLocations::iterator j = _fileLocations.find(i.value().first), e = _fileLocations.end(); (j != e) && (j.key() == i.value().first);) { if (j.value() == i.value().second) { _fileLocations.erase(j); break; } } _fileLocationPairs.erase(i); } } _fileLocations.insert(location, local); _fileLocationPairs.insert(local.fname, FileLocationPair(location, local)); _writeLocations(WriteMapWhen::Fast); } FileLocation readFileLocation(MediaKey location, bool check) { FileLocationAliases::const_iterator aliasIt = _fileLocationAliases.constFind(location); if (aliasIt != _fileLocationAliases.cend()) { location = aliasIt.value(); } FileLocations::iterator i = _fileLocations.find(location); for (FileLocations::iterator i = _fileLocations.find(location); (i != _fileLocations.end()) && (i.key() == location);) { if (check) { if (!i.value().check()) { _fileLocationPairs.remove(i.value().fname); i = _fileLocations.erase(i); _writeLocations(); continue; } } return i.value(); } return FileLocation(); } qint32 _storageImageSize(qint32 rawlen) { // fulllen + storagekey + type + len + data qint32 result = sizeof(uint32) + sizeof(quint64) * 2 + sizeof(quint32) + sizeof(quint32) + rawlen; if (result & 0x0F) result += 0x10 - (result & 0x0F); result += tdfMagicLen + sizeof(qint32) + sizeof(quint32) + 0x10 + 0x10; // magic + version + len of encrypted + part of sha1 + md5 return result; } qint32 _storageStickerSize(qint32 rawlen) { // fulllen + storagekey + len + data qint32 result = sizeof(uint32) + sizeof(quint64) * 2 + sizeof(quint32) + rawlen; if (result & 0x0F) result += 0x10 - (result & 0x0F); result += tdfMagicLen + sizeof(qint32) + sizeof(quint32) + 0x10 + 0x10; // magic + version + len of encrypted + part of sha1 + md5 return result; } qint32 _storageAudioSize(qint32 rawlen) { // fulllen + storagekey + len + data qint32 result = sizeof(uint32) + sizeof(quint64) * 2 + sizeof(quint32) + rawlen; if (result & 0x0F) result += 0x10 - (result & 0x0F); result += tdfMagicLen + sizeof(qint32) + sizeof(quint32) + 0x10 + 0x10; // magic + version + len of encrypted + part of sha1 + md5 return result; } void writeImage(const StorageKey &location, const ImagePtr &image) { if (image->isNull() || !image->loaded()) return; if (_imagesMap.constFind(location) != _imagesMap.cend()) return; image->forget(); writeImage(location, StorageImageSaved(image->savedData()), false); } void writeImage(const StorageKey &location, const StorageImageSaved &image, bool overwrite) { if (!_working()) return; qint32 size = _storageImageSize(image.data.size()); StorageMap::const_iterator i = _imagesMap.constFind(location); if (i == _imagesMap.cend()) { i = _imagesMap.insert(location, FileDesc(genKey(FileOption::User), size)); _storageImagesSize += size; _mapChanged = true; _writeMap(); } else if (!overwrite) { return; } auto legacyTypeField = 0; EncryptedDescriptor data(sizeof(quint64) * 2 + sizeof(quint32) + sizeof(quint32) + image.data.size()); data.stream << quint64(location.first) << quint64(location.second) << quint32(legacyTypeField) << image.data; FileWriteDescriptor file(i.value().first, FileOption::User); file.writeEncrypted(data); if (i.value().second != size) { _storageImagesSize += size; _storageImagesSize -= i.value().second; _imagesMap[location].second = size; } } class AbstractCachedLoadTask : public Task { public: AbstractCachedLoadTask(const FileKey &key, const StorageKey &location, bool readImageFlag, mtpFileLoader *loader) : _key(key), _location(location), _readImageFlag(readImageFlag), _loader(loader), _result(0) { } void process() { FileReadDescriptor image; if (!readEncryptedFile(image, _key, FileOption::User)) { return; } QByteArray imageData; quint64 locFirst, locSecond; quint32 legacyTypeField = 0; readFromStream(image.stream, locFirst, locSecond, imageData); // we're saving files now before we have actual location //if (locFirst != _location.first || locSecond != _location.second) { // return; //} _result = new Result(imageData, _readImageFlag); } void finish() { if (_result) { _loader->localLoaded(_result->image, _result->format, _result->pixmap); } else { clearInMap(); _loader->localLoaded(StorageImageSaved()); } } virtual void readFromStream(QDataStream &stream, quint64 &first, quint64 &second, QByteArray &data) = 0; virtual void clearInMap() = 0; virtual ~AbstractCachedLoadTask() { delete base::take(_result); } protected: FileKey _key; StorageKey _location; bool _readImageFlag; struct Result { Result(const QByteArray &data, bool readImageFlag) : image(data) { if (readImageFlag) { auto realFormat = QByteArray(); pixmap = App::pixmapFromImageInPlace(App::readImage(data, &realFormat, false)); if (!pixmap.isNull()) { format = realFormat; } } } StorageImageSaved image; QByteArray format; QPixmap pixmap; }; mtpFileLoader *_loader; Result *_result; }; class ImageLoadTask : public AbstractCachedLoadTask { public: ImageLoadTask(const FileKey &key, const StorageKey &location, mtpFileLoader *loader) : AbstractCachedLoadTask(key, location, true, loader) { } void readFromStream(QDataStream &stream, quint64 &first, quint64 &second, QByteArray &data) override { qint32 legacyTypeField = 0; stream >> first >> second >> legacyTypeField >> data; } void clearInMap() override { StorageMap::iterator j = _imagesMap.find(_location); if (j != _imagesMap.cend() && j->first == _key) { clearKey(_key, FileOption::User); _storageImagesSize -= j->second; _imagesMap.erase(j); } } }; TaskId startImageLoad(const StorageKey &location, mtpFileLoader *loader) { StorageMap::const_iterator j = _imagesMap.constFind(location); if (j == _imagesMap.cend() || !_localLoader) { return 0; } return _localLoader->addTask( std::make_unique<ImageLoadTask>(j->first, location, loader)); } bool willImageLoad(const StorageKey &location) { return _imagesMap.constFind(location) != _imagesMap.cend(); } int32 hasImages() { return _imagesMap.size(); } qint64 storageImagesSize() { return _storageImagesSize; } void writeStickerImage(const StorageKey &location, const QByteArray &sticker, bool overwrite) { if (!_working()) return; qint32 size = _storageStickerSize(sticker.size()); StorageMap::const_iterator i = _stickerImagesMap.constFind(location); if (i == _stickerImagesMap.cend()) { i = _stickerImagesMap.insert(location, FileDesc(genKey(FileOption::User), size)); _storageStickersSize += size; _mapChanged = true; _writeMap(); } else if (!overwrite) { return; } EncryptedDescriptor data(sizeof(quint64) * 2 + sizeof(quint32) + sizeof(quint32) + sticker.size()); data.stream << quint64(location.first) << quint64(location.second) << sticker; FileWriteDescriptor file(i.value().first, FileOption::User); file.writeEncrypted(data); if (i.value().second != size) { _storageStickersSize += size; _storageStickersSize -= i.value().second; _stickerImagesMap[location].second = size; } } class StickerImageLoadTask : public AbstractCachedLoadTask { public: StickerImageLoadTask(const FileKey &key, const StorageKey &location, mtpFileLoader *loader) : AbstractCachedLoadTask(key, location, true, loader) { } void readFromStream(QDataStream &stream, quint64 &first, quint64 &second, QByteArray &data) { stream >> first >> second >> data; } void clearInMap() { auto j = _stickerImagesMap.find(_location); if (j != _stickerImagesMap.cend() && j->first == _key) { clearKey(j.value().first, FileOption::User); _storageStickersSize -= j.value().second; _stickerImagesMap.erase(j); } } }; TaskId startStickerImageLoad(const StorageKey &location, mtpFileLoader *loader) { auto j = _stickerImagesMap.constFind(location); if (j == _stickerImagesMap.cend() || !_localLoader) { return 0; } return _localLoader->addTask( std::make_unique<StickerImageLoadTask>(j->first, location, loader)); } bool willStickerImageLoad(const StorageKey &location) { return _stickerImagesMap.constFind(location) != _stickerImagesMap.cend(); } bool copyStickerImage(const StorageKey &oldLocation, const StorageKey &newLocation) { auto i = _stickerImagesMap.constFind(oldLocation); if (i == _stickerImagesMap.cend()) { return false; } _stickerImagesMap.insert(newLocation, i.value()); _mapChanged = true; _writeMap(); return true; } int32 hasStickers() { return _stickerImagesMap.size(); } qint64 storageStickersSize() { return _storageStickersSize; } void writeAudio(const StorageKey &location, const QByteArray &audio, bool overwrite) { if (!_working()) return; qint32 size = _storageAudioSize(audio.size()); StorageMap::const_iterator i = _audiosMap.constFind(location); if (i == _audiosMap.cend()) { i = _audiosMap.insert(location, FileDesc(genKey(FileOption::User), size)); _storageAudiosSize += size; _mapChanged = true; _writeMap(); } else if (!overwrite) { return; } EncryptedDescriptor data(sizeof(quint64) * 2 + sizeof(quint32) + sizeof(quint32) + audio.size()); data.stream << quint64(location.first) << quint64(location.second) << audio; FileWriteDescriptor file(i.value().first, FileOption::User); file.writeEncrypted(data); if (i.value().second != size) { _storageAudiosSize += size; _storageAudiosSize -= i.value().second; _audiosMap[location].second = size; } } class AudioLoadTask : public AbstractCachedLoadTask { public: AudioLoadTask(const FileKey &key, const StorageKey &location, mtpFileLoader *loader) : AbstractCachedLoadTask(key, location, false, loader) { } void readFromStream(QDataStream &stream, quint64 &first, quint64 &second, QByteArray &data) { stream >> first >> second >> data; } void clearInMap() { auto j = _audiosMap.find(_location); if (j != _audiosMap.cend() && j->first == _key) { clearKey(j.value().first, FileOption::User); _storageAudiosSize -= j.value().second; _audiosMap.erase(j); } } }; TaskId startAudioLoad(const StorageKey &location, mtpFileLoader *loader) { auto j = _audiosMap.constFind(location); if (j == _audiosMap.cend() || !_localLoader) { return 0; } return _localLoader->addTask( std::make_unique<AudioLoadTask>(j->first, location, loader)); } bool copyAudio(const StorageKey &oldLocation, const StorageKey &newLocation) { auto i = _audiosMap.constFind(oldLocation); if (i == _audiosMap.cend()) { return false; } _audiosMap.insert(newLocation, i.value()); _mapChanged = true; _writeMap(); return true; } bool willAudioLoad(const StorageKey &location) { return _audiosMap.constFind(location) != _audiosMap.cend(); } int32 hasAudios() { return _audiosMap.size(); } qint64 storageAudiosSize() { return _storageAudiosSize; } qint32 _storageWebFileSize(const QString &url, qint32 rawlen) { // fulllen + url + len + data qint32 result = sizeof(uint32) + Serialize::stringSize(url) + sizeof(quint32) + rawlen; if (result & 0x0F) result += 0x10 - (result & 0x0F); result += tdfMagicLen + sizeof(qint32) + sizeof(quint32) + 0x10 + 0x10; // magic + version + len of encrypted + part of sha1 + md5 return result; } void writeWebFile(const QString &url, const QByteArray &content, bool overwrite) { if (!_working()) return; qint32 size = _storageWebFileSize(url, content.size()); WebFilesMap::const_iterator i = _webFilesMap.constFind(url); if (i == _webFilesMap.cend()) { i = _webFilesMap.insert(url, FileDesc(genKey(FileOption::User), size)); _storageWebFilesSize += size; _writeLocations(); } else if (!overwrite) { return; } EncryptedDescriptor data(Serialize::stringSize(url) + sizeof(quint32) + sizeof(quint32) + content.size()); data.stream << url << content; FileWriteDescriptor file(i.value().first, FileOption::User); file.writeEncrypted(data); if (i.value().second != size) { _storageWebFilesSize += size; _storageWebFilesSize -= i.value().second; _webFilesMap[url].second = size; } } class WebFileLoadTask : public Task { public: WebFileLoadTask(const FileKey &key, const QString &url, webFileLoader *loader) : _key(key) , _url(url) , _loader(loader) , _result(0) { } void process() { FileReadDescriptor image; if (!readEncryptedFile(image, _key, FileOption::User)) { return; } QByteArray imageData; QString url; image.stream >> url >> imageData; _result = new Result(imageData); } void finish() { if (_result) { _loader->localLoaded(_result->image, _result->format, _result->pixmap); } else { WebFilesMap::iterator j = _webFilesMap.find(_url); if (j != _webFilesMap.cend() && j->first == _key) { clearKey(j.value().first, FileOption::User); _storageWebFilesSize -= j.value().second; _webFilesMap.erase(j); } _loader->localLoaded(StorageImageSaved()); } } virtual ~WebFileLoadTask() { delete base::take(_result); } protected: FileKey _key; QString _url; struct Result { explicit Result(const QByteArray &data) : image(data) { QByteArray guessFormat; pixmap = App::pixmapFromImageInPlace(App::readImage(data, &guessFormat, false)); if (!pixmap.isNull()) { format = guessFormat; } } StorageImageSaved image; QByteArray format; QPixmap pixmap; }; webFileLoader *_loader; Result *_result; }; TaskId startWebFileLoad(const QString &url, webFileLoader *loader) { WebFilesMap::const_iterator j = _webFilesMap.constFind(url); if (j == _webFilesMap.cend() || !_localLoader) { return 0; } return _localLoader->addTask( std::make_unique<WebFileLoadTask>(j->first, url, loader)); } bool willWebFileLoad(const QString &url) { return _webFilesMap.constFind(url) != _webFilesMap.cend(); } int32 hasWebFiles() { return _webFilesMap.size(); } qint64 storageWebFilesSize() { return _storageWebFilesSize; } class CountWaveformTask : public Task { public: CountWaveformTask(DocumentData *doc) : _doc(doc) , _loc(doc->location(true)) , _data(doc->data()) , _wavemax(0) { if (_data.isEmpty() && !_loc.accessEnable()) { _doc = 0; } } void process() { if (!_doc) return; _waveform = audioCountWaveform(_loc, _data); uchar wavemax = 0; for (int32 i = 0, l = _waveform.size(); i < l; ++i) { uchar waveat = _waveform.at(i); if (wavemax < waveat) wavemax = waveat; } _wavemax = wavemax; } void finish() { if (const auto voice = _doc ? _doc->voice() : nullptr) { if (!_waveform.isEmpty()) { voice->waveform = _waveform; voice->wavemax = _wavemax; } if (voice->waveform.isEmpty()) { voice->waveform.resize(1); voice->waveform[0] = -2; voice->wavemax = 0; } else if (voice->waveform[0] < 0) { voice->waveform[0] = -2; voice->wavemax = 0; } Auth().data().requestDocumentViewRepaint(_doc); } } virtual ~CountWaveformTask() { if (_data.isEmpty() && _doc) { _loc.accessDisable(); } } protected: DocumentData *_doc; FileLocation _loc; QByteArray _data; VoiceWaveform _waveform; char _wavemax; }; void countVoiceWaveform(DocumentData *document) { if (const auto voice = document->voice()) { if (_localLoader) { voice->waveform.resize(1 + sizeof(TaskId)); voice->waveform[0] = -1; // counting TaskId taskId = _localLoader->addTask( std::make_unique<CountWaveformTask>(document)); memcpy(voice->waveform.data() + 1, &taskId, sizeof(taskId)); } } } void cancelTask(TaskId id) { if (_localLoader) { _localLoader->cancelTask(id); } } void _writeStickerSet(QDataStream &stream, const Stickers::Set &set) { bool notLoaded = (set.flags & MTPDstickerSet_ClientFlag::f_not_loaded); if (notLoaded) { stream << quint64(set.id) << quint64(set.access) << set.title << set.shortName << qint32(-set.count) << qint32(set.hash) << qint32(set.flags); if (AppVersion > 1002008) { stream << qint32(set.installDate); } return; } else { if (set.stickers.isEmpty()) return; } stream << quint64(set.id) << quint64(set.access) << set.title << set.shortName << qint32(set.stickers.size()) << qint32(set.hash) << qint32(set.flags); if (AppVersion > 1002008) { stream << qint32(set.installDate); } for (auto j = set.stickers.cbegin(), e = set.stickers.cend(); j != e; ++j) { Serialize::Document::writeToStream(stream, *j); } if (AppVersion > 1002008) { stream << qint32(set.dates.size()); if (!set.dates.empty()) { Assert(set.dates.size() == set.stickers.size()); for (const auto date : set.dates) { stream << qint32(date); } } } if (AppVersion > 9018) { stream << qint32(set.emoji.size()); for (auto j = set.emoji.cbegin(), e = set.emoji.cend(); j != e; ++j) { stream << j.key()->id() << qint32(j->size()); for (int32 k = 0, l = j->size(); k < l; ++k) { stream << quint64(j->at(k)->id); } } } } // In generic method _writeStickerSets() we look through all the sets and call a // callback on each set to see, if we write it, skip it or abort the whole write. enum class StickerSetCheckResult { Write, Skip, Abort, }; // CheckSet is a functor on Stickers::Set, which returns a StickerSetCheckResult. template <typename CheckSet> void _writeStickerSets(FileKey &stickersKey, CheckSet checkSet, const Stickers::Order &order) { if (!_working()) return; auto &sets = Auth().data().stickerSets(); if (sets.isEmpty()) { if (stickersKey) { clearKey(stickersKey); stickersKey = 0; _mapChanged = true; } _writeMap(); return; } int32 setsCount = 0; QByteArray hashToWrite; quint32 size = sizeof(quint32) + Serialize::bytearraySize(hashToWrite); for_const (auto &set, sets) { auto result = checkSet(set); if (result == StickerSetCheckResult::Abort) { return; } else if (result == StickerSetCheckResult::Skip) { continue; } // id + access + title + shortName + stickersCount + hash + flags + installDate size += sizeof(quint64) * 2 + Serialize::stringSize(set.title) + Serialize::stringSize(set.shortName) + sizeof(quint32) + sizeof(qint32) * 3; for_const (auto &sticker, set.stickers) { size += Serialize::Document::sizeInStream(sticker); } size += sizeof(qint32); // dates count if (!set.dates.empty()) { Assert(set.stickers.size() == set.dates.size()); size += set.dates.size() * sizeof(qint32); } size += sizeof(qint32); // emojiCount for (auto j = set.emoji.cbegin(), e = set.emoji.cend(); j != e; ++j) { size += Serialize::stringSize(j.key()->id()) + sizeof(qint32) + (j->size() * sizeof(quint64)); } ++setsCount; } if (!setsCount && order.isEmpty()) { if (stickersKey) { clearKey(stickersKey); stickersKey = 0; _mapChanged = true; } _writeMap(); return; } size += sizeof(qint32) + (order.size() * sizeof(quint64)); if (!stickersKey) { stickersKey = genKey(); _mapChanged = true; _writeMap(WriteMapWhen::Fast); } EncryptedDescriptor data(size); data.stream << quint32(setsCount) << hashToWrite; for_const (auto &set, sets) { auto result = checkSet(set); if (result == StickerSetCheckResult::Abort) { return; } else if (result == StickerSetCheckResult::Skip) { continue; } _writeStickerSet(data.stream, set); } data.stream << order; FileWriteDescriptor file(stickersKey); file.writeEncrypted(data); } void _readStickerSets(FileKey &stickersKey, Stickers::Order *outOrder = nullptr, MTPDstickerSet::Flags readingFlags = 0) { FileReadDescriptor stickers; if (!readEncryptedFile(stickers, stickersKey)) { clearKey(stickersKey); stickersKey = 0; _writeMap(); return; } bool readingInstalled = (readingFlags == MTPDstickerSet::Flag::f_installed_date); auto &sets = Auth().data().stickerSetsRef(); if (outOrder) outOrder->clear(); quint32 cnt; QByteArray hash; stickers.stream >> cnt >> hash; // ignore hash, it is counted if (readingInstalled && stickers.version < 8019) { // bad data in old caches cnt += 2; // try to read at least something } for (uint32 i = 0; i < cnt; ++i) { quint64 setId = 0, setAccess = 0; QString setTitle, setShortName; qint32 scnt = 0; auto setInstallDate = qint32(0); stickers.stream >> setId >> setAccess >> setTitle >> setShortName >> scnt; qint32 setHash = 0; MTPDstickerSet::Flags setFlags = 0; if (stickers.version > 8033) { qint32 setFlagsValue = 0; stickers.stream >> setHash >> setFlagsValue; setFlags = MTPDstickerSet::Flags::from_raw(setFlagsValue); if (setFlags & MTPDstickerSet_ClientFlag::f_not_loaded__old) { setFlags &= ~MTPDstickerSet_ClientFlag::f_not_loaded__old; setFlags |= MTPDstickerSet_ClientFlag::f_not_loaded; } } if (stickers.version > 1002008) { stickers.stream >> setInstallDate; } if (readingInstalled && stickers.version < 9061) { setFlags |= MTPDstickerSet::Flag::f_installed_date; } if (setId == Stickers::DefaultSetId) { setTitle = lang(lng_stickers_default_set); setFlags |= MTPDstickerSet::Flag::f_official | MTPDstickerSet_ClientFlag::f_special; if (readingInstalled && outOrder && stickers.version < 9061) { outOrder->push_front(setId); } } else if (setId == Stickers::CustomSetId) { setTitle = qsl("Custom stickers"); setFlags |= MTPDstickerSet_ClientFlag::f_special; } else if (setId == Stickers::CloudRecentSetId) { setTitle = lang(lng_recent_stickers); setFlags |= MTPDstickerSet_ClientFlag::f_special; } else if (setId == Stickers::FavedSetId) { setTitle = Lang::Hard::FavedSetTitle(); setFlags |= MTPDstickerSet_ClientFlag::f_special; } else if (setId) { if (readingInstalled && outOrder && stickers.version < 9061) { outOrder->push_back(setId); } } else { continue; } auto it = sets.find(setId); if (it == sets.cend()) { // We will set this flags from order lists when reading those stickers. setFlags &= ~(MTPDstickerSet::Flag::f_installed_date | MTPDstickerSet_ClientFlag::f_featured); it = sets.insert(setId, Stickers::Set( setId, setAccess, setTitle, setShortName, 0, setHash, MTPDstickerSet::Flags(setFlags), setInstallDate)); } auto &set = it.value(); auto inputSet = MTP_inputStickerSetID(MTP_long(set.id), MTP_long(set.access)); if (scnt < 0) { // disabled not loaded set if (!set.count || set.stickers.isEmpty()) { set.count = -scnt; } continue; } bool fillStickers = set.stickers.isEmpty(); if (fillStickers) { set.stickers.reserve(scnt); set.count = 0; } Serialize::Document::StickerSetInfo info(setId, setAccess, setShortName); OrderedSet<DocumentId> read; for (int32 j = 0; j < scnt; ++j) { auto document = Serialize::Document::readStickerFromStream(stickers.version, stickers.stream, info); if (!document || !document->sticker()) continue; if (read.contains(document->id)) continue; read.insert(document->id); if (fillStickers) { set.stickers.push_back(document); if (!(set.flags & MTPDstickerSet_ClientFlag::f_special)) { if (document->sticker()->set.type() != mtpc_inputStickerSetID) { document->sticker()->set = inputSet; } } ++set.count; } } if (stickers.version > 1002008) { auto datesCount = qint32(0); stickers.stream >> datesCount; if (datesCount > 0) { if (datesCount != scnt) { // Bad file. return; } set.dates.reserve(datesCount); for (auto i = 0; i != datesCount; ++i) { auto date = qint32(); stickers.stream >> date; if (set.id == Stickers::CloudRecentSetId) { set.dates.push_back(TimeId(date)); } } } } if (stickers.version > 9018) { qint32 emojiCount; stickers.stream >> emojiCount; for (int32 j = 0; j < emojiCount; ++j) { QString emojiString; qint32 stickersCount; stickers.stream >> emojiString >> stickersCount; Stickers::Pack pack; pack.reserve(stickersCount); for (int32 k = 0; k < stickersCount; ++k) { quint64 id; stickers.stream >> id; const auto doc = Auth().data().document(id); if (!doc->sticker()) continue; pack.push_back(doc); } if (fillStickers) { if (auto emoji = Ui::Emoji::Find(emojiString)) { emoji = emoji->original(); set.emoji.insert(emoji, pack); } } } } } // Read orders of installed and featured stickers. if (outOrder && stickers.version >= 9061) { stickers.stream >> *outOrder; } // Set flags that we dropped above from the order. if (readingFlags && outOrder) { for_const (auto setId, *outOrder) { auto it = sets.find(setId); if (it != sets.cend()) { it->flags |= readingFlags; if (readingInstalled && !it->installDate) { it->installDate = kDefaultStickerInstallDate; } } } } } void writeInstalledStickers() { if (!Global::started()) return; _writeStickerSets(_installedStickersKey, [](const Stickers::Set &set) { if (set.id == Stickers::CloudRecentSetId || set.id == Stickers::FavedSetId) { // separate files for them return StickerSetCheckResult::Skip; } else if (set.flags & MTPDstickerSet_ClientFlag::f_special) { if (set.stickers.isEmpty()) { // all other special are "installed" return StickerSetCheckResult::Skip; } } else if (!(set.flags & MTPDstickerSet::Flag::f_installed_date) || (set.flags & MTPDstickerSet::Flag::f_archived)) { return StickerSetCheckResult::Skip; } else if (set.flags & MTPDstickerSet_ClientFlag::f_not_loaded) { // waiting to receive return StickerSetCheckResult::Abort; } else if (set.stickers.isEmpty()) { return StickerSetCheckResult::Skip; } return StickerSetCheckResult::Write; }, Auth().data().stickerSetsOrder()); } void writeFeaturedStickers() { if (!Global::started()) return; _writeStickerSets(_featuredStickersKey, [](const Stickers::Set &set) { if (set.id == Stickers::CloudRecentSetId || set.id == Stickers::FavedSetId) { // separate files for them return StickerSetCheckResult::Skip; } else if (set.flags & MTPDstickerSet_ClientFlag::f_special) { return StickerSetCheckResult::Skip; } else if (!(set.flags & MTPDstickerSet_ClientFlag::f_featured)) { return StickerSetCheckResult::Skip; } else if (set.flags & MTPDstickerSet_ClientFlag::f_not_loaded) { // waiting to receive return StickerSetCheckResult::Abort; } else if (set.stickers.isEmpty()) { return StickerSetCheckResult::Skip; } return StickerSetCheckResult::Write; }, Auth().data().featuredStickerSetsOrder()); } void writeRecentStickers() { if (!Global::started()) return; _writeStickerSets(_recentStickersKey, [](const Stickers::Set &set) { if (set.id != Stickers::CloudRecentSetId || set.stickers.isEmpty()) { return StickerSetCheckResult::Skip; } return StickerSetCheckResult::Write; }, Stickers::Order()); } void writeFavedStickers() { if (!Global::started()) return; _writeStickerSets(_favedStickersKey, [](const Stickers::Set &set) { if (set.id != Stickers::FavedSetId || set.stickers.isEmpty()) { return StickerSetCheckResult::Skip; } return StickerSetCheckResult::Write; }, Stickers::Order()); } void writeArchivedStickers() { if (!Global::started()) return; _writeStickerSets(_archivedStickersKey, [](const Stickers::Set &set) { if (!(set.flags & MTPDstickerSet::Flag::f_archived) || set.stickers.isEmpty()) { return StickerSetCheckResult::Skip; } return StickerSetCheckResult::Write; }, Auth().data().archivedStickerSetsOrder()); } void importOldRecentStickers() { if (!_recentStickersKeyOld) return; FileReadDescriptor stickers; if (!readEncryptedFile(stickers, _recentStickersKeyOld)) { clearKey(_recentStickersKeyOld); _recentStickersKeyOld = 0; _writeMap(); return; } auto &sets = Auth().data().stickerSetsRef(); sets.clear(); auto &order = Auth().data().stickerSetsOrderRef(); order.clear(); auto &recent = cRefRecentStickers(); recent.clear(); auto &def = sets.insert(Stickers::DefaultSetId, Stickers::Set( Stickers::DefaultSetId, uint64(0), lang(lng_stickers_default_set), QString(), 0, // count 0, // hash (MTPDstickerSet::Flag::f_official | MTPDstickerSet::Flag::f_installed_date | MTPDstickerSet_ClientFlag::f_special), kDefaultStickerInstallDate)).value(); auto &custom = sets.insert(Stickers::CustomSetId, Stickers::Set( Stickers::CustomSetId, uint64(0), qsl("Custom stickers"), QString(), 0, // count 0, // hash (MTPDstickerSet::Flag::f_installed_date | MTPDstickerSet_ClientFlag::f_special), kDefaultStickerInstallDate)).value(); QMap<uint64, bool> read; while (!stickers.stream.atEnd()) { quint64 id, access; QString name, mime, alt; qint32 date, dc, size, width, height, type; qint16 value; stickers.stream >> id >> value >> access >> date >> name >> mime >> dc >> size >> width >> height >> type; if (stickers.version >= 7021) { stickers.stream >> alt; } if (!value || read.contains(id)) continue; read.insert(id, true); QVector<MTPDocumentAttribute> attributes; if (!name.isEmpty()) attributes.push_back(MTP_documentAttributeFilename(MTP_string(name))); if (type == AnimatedDocument) { attributes.push_back(MTP_documentAttributeAnimated()); } else if (type == StickerDocument) { attributes.push_back(MTP_documentAttributeSticker(MTP_flags(0), MTP_string(alt), MTP_inputStickerSetEmpty(), MTPMaskCoords())); } if (width > 0 && height > 0) { attributes.push_back(MTP_documentAttributeImageSize(MTP_int(width), MTP_int(height))); } const auto doc = Auth().data().document( id, access, int32(0), date, attributes, mime, ImagePtr(), dc, size, StorageImageLocation()); if (!doc->sticker()) continue; if (value > 0) { def.stickers.push_back(doc); ++def.count; } else { custom.stickers.push_back(doc); ++custom.count; } if (recent.size() < Global::StickersRecentLimit() && qAbs(value) > 1) { recent.push_back(qMakePair(doc, qAbs(value))); } } if (def.stickers.isEmpty()) { sets.remove(Stickers::DefaultSetId); } else { order.push_front(Stickers::DefaultSetId); } if (custom.stickers.isEmpty()) sets.remove(Stickers::CustomSetId); writeInstalledStickers(); writeUserSettings(); clearKey(_recentStickersKeyOld); _recentStickersKeyOld = 0; _writeMap(); } void readInstalledStickers() { if (!_installedStickersKey) { return importOldRecentStickers(); } Auth().data().stickerSetsRef().clear(); _readStickerSets( _installedStickersKey, &Auth().data().stickerSetsOrderRef(), MTPDstickerSet::Flag::f_installed_date); } void readFeaturedStickers() { _readStickerSets( _featuredStickersKey, &Auth().data().featuredStickerSetsOrderRef(), MTPDstickerSet::Flags() | MTPDstickerSet_ClientFlag::f_featured); auto &sets = Auth().data().stickerSets(); int unreadCount = 0; for_const (auto setId, Auth().data().featuredStickerSetsOrder()) { auto it = sets.constFind(setId); if (it != sets.cend() && (it->flags & MTPDstickerSet_ClientFlag::f_unread)) { ++unreadCount; } } Auth().data().setFeaturedStickerSetsUnreadCount(unreadCount); } void readRecentStickers() { _readStickerSets(_recentStickersKey); } void readFavedStickers() { _readStickerSets(_favedStickersKey); } void readArchivedStickers() { static bool archivedStickersRead = false; if (!archivedStickersRead) { _readStickerSets(_archivedStickersKey, &Auth().data().archivedStickerSetsOrderRef()); archivedStickersRead = true; } } int32 countDocumentVectorHash(const QVector<DocumentData*> vector) { uint32 acc = 0; for_const (auto doc, vector) { auto docId = doc->id; acc = (acc * 20261) + uint32(docId >> 32); acc = (acc * 20261) + uint32(docId & 0xFFFFFFFF); } return int32(acc & 0x7FFFFFFF); } int32 countSpecialStickerSetHash(uint64 setId) { auto &sets = Auth().data().stickerSets(); auto it = sets.constFind(setId); if (it != sets.cend()) { return countDocumentVectorHash(it->stickers); } return 0; } int32 countStickersHash(bool checkOutdatedInfo) { uint32 acc = 0; bool foundOutdated = false; auto &sets = Auth().data().stickerSets(); auto &order = Auth().data().stickerSetsOrder(); for (auto i = order.cbegin(), e = order.cend(); i != e; ++i) { auto j = sets.constFind(*i); if (j != sets.cend()) { if (j->id == Stickers::DefaultSetId) { foundOutdated = true; } else if (!(j->flags & MTPDstickerSet_ClientFlag::f_special) && !(j->flags & MTPDstickerSet::Flag::f_archived)) { acc = (acc * 20261) + j->hash; } } } return (!checkOutdatedInfo || !foundOutdated) ? int32(acc & 0x7FFFFFFF) : 0; } int32 countRecentStickersHash() { return countSpecialStickerSetHash(Stickers::CloudRecentSetId); } int32 countFavedStickersHash() { return countSpecialStickerSetHash(Stickers::FavedSetId); } int32 countFeaturedStickersHash() { uint32 acc = 0; auto &sets = Auth().data().stickerSets(); auto &featured = Auth().data().featuredStickerSetsOrder(); for_const (auto setId, featured) { acc = (acc * 20261) + uint32(setId >> 32); acc = (acc * 20261) + uint32(setId & 0xFFFFFFFF); auto it = sets.constFind(setId); if (it != sets.cend() && (it->flags & MTPDstickerSet_ClientFlag::f_unread)) { acc = (acc * 20261) + 1U; } } return int32(acc & 0x7FFFFFFF); } int32 countSavedGifsHash() { return countDocumentVectorHash(Auth().data().savedGifs()); } void writeSavedGifs() { if (!_working()) return; auto &saved = Auth().data().savedGifs(); if (saved.isEmpty()) { if (_savedGifsKey) { clearKey(_savedGifsKey); _savedGifsKey = 0; _mapChanged = true; } _writeMap(); } else { quint32 size = sizeof(quint32); // count for_const (auto gif, saved) { size += Serialize::Document::sizeInStream(gif); } if (!_savedGifsKey) { _savedGifsKey = genKey(); _mapChanged = true; _writeMap(WriteMapWhen::Fast); } EncryptedDescriptor data(size); data.stream << quint32(saved.size()); for_const (auto gif, saved) { Serialize::Document::writeToStream(data.stream, gif); } FileWriteDescriptor file(_savedGifsKey); file.writeEncrypted(data); } } void readSavedGifs() { if (!_savedGifsKey) return; FileReadDescriptor gifs; if (!readEncryptedFile(gifs, _savedGifsKey)) { clearKey(_savedGifsKey); _savedGifsKey = 0; _writeMap(); return; } auto &saved = Auth().data().savedGifsRef(); saved.clear(); quint32 cnt; gifs.stream >> cnt; saved.reserve(cnt); OrderedSet<DocumentId> read; for (uint32 i = 0; i < cnt; ++i) { auto document = Serialize::Document::readFromStream(gifs.version, gifs.stream); if (!document || !document->isGifv()) continue; if (read.contains(document->id)) continue; read.insert(document->id); saved.push_back(document); } } void writeBackground(int32 id, const QImage &img) { if (!_working() || !_backgroundCanWrite) return; if (!LocalKey) { LOG(("App Error: localkey not created in writeBackground()")); return; } QByteArray bmp; if (!img.isNull()) { QBuffer buf(&bmp); if (!img.save(&buf, "BMP")) return; } if (!_backgroundKey) { _backgroundKey = genKey(); _mapChanged = true; _writeMap(WriteMapWhen::Fast); } quint32 size = sizeof(qint32) + sizeof(quint32) + (bmp.isEmpty() ? 0 : (sizeof(quint32) + bmp.size())); EncryptedDescriptor data(size); data.stream << qint32(id) << bmp; FileWriteDescriptor file(_backgroundKey); file.writeEncrypted(data); } bool readBackground() { if (_backgroundWasRead) { return false; } _backgroundWasRead = true; FileReadDescriptor bg; if (!readEncryptedFile(bg, _backgroundKey)) { clearKey(_backgroundKey); _backgroundKey = 0; _writeMap(); return false; } QByteArray pngData; qint32 id; bg.stream >> id >> pngData; auto oldEmptyImage = (bg.stream.status() != QDataStream::Ok); if (oldEmptyImage || id == Window::Theme::kInitialBackground || id == Window::Theme::kDefaultBackground) { _backgroundCanWrite = false; if (oldEmptyImage || bg.version < 8005) { Window::Theme::Background()->setImage(Window::Theme::kDefaultBackground); Window::Theme::Background()->setTile(false); } else { Window::Theme::Background()->setImage(id); } _backgroundCanWrite = true; return true; } else if (id == Window::Theme::kThemeBackground && pngData.isEmpty()) { _backgroundCanWrite = false; Window::Theme::Background()->setImage(id); _backgroundCanWrite = true; return true; } QImage image; QBuffer buf(&pngData); QImageReader reader(&buf); #ifndef OS_MAC_OLD reader.setAutoTransform(true); #endif // OS_MAC_OLD if (reader.read(&image)) { _backgroundCanWrite = false; Window::Theme::Background()->setImage(id, std::move(image)); _backgroundCanWrite = true; return true; } return false; } bool readThemeUsingKey(FileKey key) { FileReadDescriptor theme; if (!readEncryptedFile(theme, key, FileOption::Safe, SettingsKey)) { return false; } QByteArray themeContent; QString pathRelative, pathAbsolute; Window::Theme::Cached cache; theme.stream >> themeContent; theme.stream >> pathRelative >> pathAbsolute; if (theme.stream.status() != QDataStream::Ok) { return false; } _themeAbsolutePath = pathAbsolute; _themePaletteAbsolutePath = Window::Theme::IsPaletteTestingPath(pathAbsolute) ? pathAbsolute : QString(); QFile file(pathRelative); if (pathRelative.isEmpty() || !file.exists()) { file.setFileName(pathAbsolute); } auto changed = false; if (!file.fileName().isEmpty() && file.exists() && file.open(QIODevice::ReadOnly)) { if (file.size() > kThemeFileSizeLimit) { LOG(("Error: theme file too large: %1 (should be less than 5 MB, got %2)").arg(file.fileName()).arg(file.size())); return false; } auto fileContent = file.readAll(); file.close(); if (themeContent != fileContent) { themeContent = fileContent; changed = true; } } if (!changed) { quint32 backgroundIsTiled = 0; theme.stream >> cache.paletteChecksum >> cache.contentChecksum >> cache.colors >> cache.background >> backgroundIsTiled; cache.tiled = (backgroundIsTiled == 1); if (theme.stream.status() != QDataStream::Ok) { return false; } } return Window::Theme::Load(pathRelative, pathAbsolute, themeContent, cache); } void writeTheme(const QString &pathRelative, const QString &pathAbsolute, const QByteArray &content, const Window::Theme::Cached &cache) { if (content.isEmpty()) { _themeAbsolutePath = _themePaletteAbsolutePath = QString(); if (_themeKey) { clearKey(_themeKey); _themeKey = 0; writeSettings(); } return; } _themeAbsolutePath = pathAbsolute; _themePaletteAbsolutePath = Window::Theme::IsPaletteTestingPath(pathAbsolute) ? pathAbsolute : QString(); if (!_themeKey) { _themeKey = genKey(FileOption::Safe); writeSettings(); } auto backgroundTiled = static_cast<quint32>(cache.tiled ? 1 : 0); quint32 size = Serialize::bytearraySize(content); size += Serialize::stringSize(pathRelative) + Serialize::stringSize(pathAbsolute); size += sizeof(int32) * 2 + Serialize::bytearraySize(cache.colors) + Serialize::bytearraySize(cache.background) + sizeof(quint32); EncryptedDescriptor data(size); data.stream << content; data.stream << pathRelative << pathAbsolute; data.stream << cache.paletteChecksum << cache.contentChecksum << cache.colors << cache.background << backgroundTiled; FileWriteDescriptor file(_themeKey, FileOption::Safe); file.writeEncrypted(data, SettingsKey); } void clearTheme() { writeTheme(QString(), QString(), QByteArray(), Window::Theme::Cached()); } void readTheme() { if (_themeKey && !readThemeUsingKey(_themeKey)) { clearTheme(); } } bool hasTheme() { return (_themeKey != 0); } void readLangPack() { FileReadDescriptor langpack; if (!_langPackKey || !readEncryptedFile(langpack, _langPackKey, FileOption::Safe, SettingsKey)) { return; } auto data = QByteArray(); langpack.stream >> data; if (langpack.stream.status() == QDataStream::Ok) { Lang::Current().fillFromSerialized(data); } } void writeLangPack() { auto langpack = Lang::Current().serialize(); if (!_langPackKey) { _langPackKey = genKey(FileOption::Safe); writeSettings(); } EncryptedDescriptor data(Serialize::bytearraySize(langpack)); data.stream << langpack; FileWriteDescriptor file(_langPackKey, FileOption::Safe); file.writeEncrypted(data, SettingsKey); } QString themePaletteAbsolutePath() { return _themePaletteAbsolutePath; } QString themeAbsolutePath() { return _themeAbsolutePath; } bool copyThemeColorsToPalette(const QString &path) { if (!_themeKey) { return false; } FileReadDescriptor theme; if (!readEncryptedFile(theme, _themeKey, FileOption::Safe, SettingsKey)) { return false; } QByteArray themeContent; theme.stream >> themeContent; if (theme.stream.status() != QDataStream::Ok) { return false; } return Window::Theme::CopyColorsToPalette(path, themeContent); } uint32 _peerSize(PeerData *peer) { uint32 result = sizeof(quint64) + sizeof(quint64) + Serialize::storageImageLocationSize(); if (peer->isUser()) { UserData *user = peer->asUser(); // first + last + phone + username + access result += Serialize::stringSize(user->firstName) + Serialize::stringSize(user->lastName) + Serialize::stringSize(user->phone()) + Serialize::stringSize(user->username) + sizeof(quint64); // flags if (AppVersion >= 9012) { result += sizeof(qint32); } // onlineTill + contact + botInfoVersion result += sizeof(qint32) + sizeof(qint32) + sizeof(qint32); } else if (peer->isChat()) { ChatData *chat = peer->asChat(); // name + count + date + version + admin + old forbidden + left + inviteLink result += Serialize::stringSize(chat->name) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(quint32) + Serialize::stringSize(chat->inviteLink()); } else if (peer->isChannel()) { ChannelData *channel = peer->asChannel(); // name + access + date + version + old forbidden + flags + inviteLink result += Serialize::stringSize(channel->name) + sizeof(quint64) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(quint32) + Serialize::stringSize(channel->inviteLink()); } return result; } void _writePeer(QDataStream &stream, PeerData *peer) { stream << quint64(peer->id) << quint64(peer->userpicPhotoId()); Serialize::writeStorageImageLocation(stream, peer->userpicLocation()); if (const auto user = peer->asUser()) { stream << user->firstName << user->lastName << user->phone() << user->username << quint64(user->accessHash()); if (AppVersion >= 9012) { stream << qint32(user->flags()); } if (AppVersion >= 9016) { const auto botInlinePlaceholder = user->botInfo ? user->botInfo->inlinePlaceholder : QString(); stream << botInlinePlaceholder; } const auto contactSerialized = [&] { switch (user->contactStatus()) { case UserData::ContactStatus::Contact: return 1; case UserData::ContactStatus::CanAdd: return 0; case UserData::ContactStatus::PhoneUnknown: return -1; } Unexpected("contactStatus in _writePeer()"); }(); stream << qint32(user->onlineTill) << qint32(contactSerialized) << qint32(user->botInfo ? user->botInfo->version : -1); } else if (const auto chat = peer->asChat()) { stream << chat->name << qint32(chat->count) << qint32(chat->date) << qint32(chat->version) << qint32(chat->creator) << qint32(0) << quint32(chat->flags()) << chat->inviteLink(); } else if (const auto channel = peer->asChannel()) { stream << channel->name << quint64(channel->access) << qint32(channel->date) << qint32(channel->version) << qint32(0) << quint32(channel->flags()) << channel->inviteLink(); } } PeerData *_readPeer(FileReadDescriptor &from, int32 fileVersion = 0) { quint64 peerId = 0, photoId = 0; from.stream >> peerId >> photoId; auto photoLoc = Serialize::readStorageImageLocation(from.stream); PeerData *result = App::peerLoaded(peerId); bool wasLoaded = (result != nullptr); if (!wasLoaded) { result = App::peer(peerId); result->loadedStatus = PeerData::FullLoaded; } if (const auto user = result->asUser()) { QString first, last, phone, username, inlinePlaceholder; quint64 access; qint32 flags = 0, onlineTill, contact, botInfoVersion; from.stream >> first >> last >> phone >> username >> access; if (from.version >= 9012) { from.stream >> flags; } if (from.version >= 9016 || fileVersion >= 9016) { from.stream >> inlinePlaceholder; } from.stream >> onlineTill >> contact >> botInfoVersion; const auto showPhone = !isServiceUser(user->id) && (user->id != Auth().userPeerId()) && (contact <= 0); const auto pname = (showPhone && !phone.isEmpty()) ? App::formatPhone(phone) : QString(); if (!wasLoaded) { user->setPhone(phone); user->setName(first, last, pname, username); user->setFlags(MTPDuser::Flags::from_raw(flags)); user->setAccessHash(access); user->onlineTill = onlineTill; user->setContactStatus((contact > 0) ? UserData::ContactStatus::Contact : (contact == 0) ? UserData::ContactStatus::CanAdd : UserData::ContactStatus::PhoneUnknown); user->setBotInfoVersion(botInfoVersion); if (!inlinePlaceholder.isEmpty() && user->botInfo) { user->botInfo->inlinePlaceholder = inlinePlaceholder; } if (user->id == Auth().userPeerId()) { user->input = MTP_inputPeerSelf(); user->inputUser = MTP_inputUserSelf(); } else { user->input = MTP_inputPeerUser(MTP_int(peerToUser(user->id)), MTP_long(user->accessHash())); user->inputUser = MTP_inputUser(MTP_int(peerToUser(user->id)), MTP_long(user->accessHash())); } } } else if (const auto chat = result->asChat()) { QString name, inviteLink; qint32 count, date, version, creator, oldForbidden; quint32 flagsData, flags; from.stream >> name >> count >> date >> version >> creator >> oldForbidden >> flagsData >> inviteLink; if (from.version >= 9012) { flags = flagsData; } else { // flagsData was haveLeft flags = (flagsData == 1) ? MTPDchat::Flags(MTPDchat::Flag::f_left) : MTPDchat::Flags(0); } if (oldForbidden) { flags |= quint32(MTPDchat_ClientFlag::f_forbidden); } if (!wasLoaded) { chat->setName(name); chat->count = count; chat->date = date; chat->version = version; chat->creator = creator; chat->setFlags(MTPDchat::Flags::from_raw(flags)); chat->setInviteLink(inviteLink); chat->input = MTP_inputPeerChat(MTP_int(peerToChat(chat->id))); chat->inputChat = MTP_int(peerToChat(chat->id)); } } else if (const auto channel = result->asChannel()) { QString name, inviteLink; quint64 access; qint32 date, version, oldForbidden; quint32 flags; from.stream >> name >> access >> date >> version >> oldForbidden >> flags >> inviteLink; if (oldForbidden) { flags |= quint32(MTPDchannel_ClientFlag::f_forbidden); } if (!wasLoaded) { channel->setName(name, QString()); channel->access = access; channel->date = date; channel->version = version; channel->setFlags(MTPDchannel::Flags::from_raw(flags)); channel->setInviteLink(inviteLink); channel->input = MTP_inputPeerChannel(MTP_int(peerToChannel(channel->id)), MTP_long(access)); channel->inputChannel = MTP_inputChannel(MTP_int(peerToChannel(channel->id)), MTP_long(access)); } } if (!wasLoaded) { result->setUserpic( photoId, photoLoc, photoLoc.isNull() ? ImagePtr() : ImagePtr(photoLoc)); } return result; } void writeRecentHashtagsAndBots() { if (!_working()) return; const RecentHashtagPack &write(cRecentWriteHashtags()), &search(cRecentSearchHashtags()); const RecentInlineBots &bots(cRecentInlineBots()); if (write.isEmpty() && search.isEmpty() && bots.isEmpty()) readRecentHashtagsAndBots(); if (write.isEmpty() && search.isEmpty() && bots.isEmpty()) { if (_recentHashtagsAndBotsKey) { clearKey(_recentHashtagsAndBotsKey); _recentHashtagsAndBotsKey = 0; _mapChanged = true; } _writeMap(); } else { if (!_recentHashtagsAndBotsKey) { _recentHashtagsAndBotsKey = genKey(); _mapChanged = true; _writeMap(WriteMapWhen::Fast); } quint32 size = sizeof(quint32) * 3, writeCnt = 0, searchCnt = 0, botsCnt = cRecentInlineBots().size(); for (RecentHashtagPack::const_iterator i = write.cbegin(), e = write.cend(); i != e; ++i) { if (!i->first.isEmpty()) { size += Serialize::stringSize(i->first) + sizeof(quint16); ++writeCnt; } } for (RecentHashtagPack::const_iterator i = search.cbegin(), e = search.cend(); i != e; ++i) { if (!i->first.isEmpty()) { size += Serialize::stringSize(i->first) + sizeof(quint16); ++searchCnt; } } for (RecentInlineBots::const_iterator i = bots.cbegin(), e = bots.cend(); i != e; ++i) { size += _peerSize(*i); } EncryptedDescriptor data(size); data.stream << quint32(writeCnt) << quint32(searchCnt); for (RecentHashtagPack::const_iterator i = write.cbegin(), e = write.cend(); i != e; ++i) { if (!i->first.isEmpty()) data.stream << i->first << quint16(i->second); } for (RecentHashtagPack::const_iterator i = search.cbegin(), e = search.cend(); i != e; ++i) { if (!i->first.isEmpty()) data.stream << i->first << quint16(i->second); } data.stream << quint32(botsCnt); for (RecentInlineBots::const_iterator i = bots.cbegin(), e = bots.cend(); i != e; ++i) { _writePeer(data.stream, *i); } FileWriteDescriptor file(_recentHashtagsAndBotsKey); file.writeEncrypted(data); } } void readRecentHashtagsAndBots() { if (_recentHashtagsAndBotsWereRead) return; _recentHashtagsAndBotsWereRead = true; if (!_recentHashtagsAndBotsKey) return; FileReadDescriptor hashtags; if (!readEncryptedFile(hashtags, _recentHashtagsAndBotsKey)) { clearKey(_recentHashtagsAndBotsKey); _recentHashtagsAndBotsKey = 0; _writeMap(); return; } quint32 writeCount = 0, searchCount = 0, botsCount = 0; hashtags.stream >> writeCount >> searchCount; QString tag; quint16 count; RecentHashtagPack write, search; RecentInlineBots bots; if (writeCount) { write.reserve(writeCount); for (uint32 i = 0; i < writeCount; ++i) { hashtags.stream >> tag >> count; write.push_back(qMakePair(tag.trimmed(), count)); } } if (searchCount) { search.reserve(searchCount); for (uint32 i = 0; i < searchCount; ++i) { hashtags.stream >> tag >> count; search.push_back(qMakePair(tag.trimmed(), count)); } } cSetRecentWriteHashtags(write); cSetRecentSearchHashtags(search); if (!hashtags.stream.atEnd()) { hashtags.stream >> botsCount; if (botsCount) { bots.reserve(botsCount); for (uint32 i = 0; i < botsCount; ++i) { PeerData *peer = _readPeer(hashtags, 9016); if (peer && peer->isUser() && peer->asUser()->botInfo && !peer->asUser()->botInfo->inlinePlaceholder.isEmpty() && !peer->asUser()->username.isEmpty()) { bots.push_back(peer->asUser()); } } } cSetRecentInlineBots(bots); } } void writeSavedPeers() { if (!_working()) return; const SavedPeers &saved(cSavedPeers()); if (saved.isEmpty()) { if (_savedPeersKey) { clearKey(_savedPeersKey); _savedPeersKey = 0; _mapChanged = true; } _writeMap(); } else { if (!_savedPeersKey) { _savedPeersKey = genKey(); _mapChanged = true; _writeMap(WriteMapWhen::Fast); } quint32 size = sizeof(quint32); for (SavedPeers::const_iterator i = saved.cbegin(); i != saved.cend(); ++i) { size += _peerSize(i.key()) + Serialize::dateTimeSize(); } EncryptedDescriptor data(size); data.stream << quint32(saved.size()); for (SavedPeers::const_iterator i = saved.cbegin(); i != saved.cend(); ++i) { _writePeer(data.stream, i.key()); data.stream << i.value(); } FileWriteDescriptor file(_savedPeersKey); file.writeEncrypted(data); } } void readSavedPeers() { if (!_savedPeersKey) return; FileReadDescriptor saved; if (!readEncryptedFile(saved, _savedPeersKey)) { clearKey(_savedPeersKey); _savedPeersKey = 0; _writeMap(); return; } if (saved.version == 9011) { // broken dev version clearKey(_savedPeersKey); _savedPeersKey = 0; _writeMap(); return; } quint32 count = 0; saved.stream >> count; cRefSavedPeers().clear(); cRefSavedPeersByTime().clear(); QList<PeerData*> peers; peers.reserve(count); for (uint32 i = 0; i < count; ++i) { PeerData *peer = _readPeer(saved); if (!peer) break; QDateTime t; saved.stream >> t; cRefSavedPeers().insert(peer, t); cRefSavedPeersByTime().insert(t, peer); peers.push_back(peer); } Auth().api().requestPeers(peers); } void addSavedPeer(PeerData *peer, const QDateTime &position) { auto &savedPeers = cRefSavedPeers(); auto i = savedPeers.find(peer); if (i == savedPeers.cend()) { savedPeers.insert(peer, position); } else if (i.value() != position) { cRefSavedPeersByTime().remove(i.value(), peer); i.value() = position; cRefSavedPeersByTime().insert(i.value(), peer); } writeSavedPeers(); } void removeSavedPeer(PeerData *peer) { auto &savedPeers = cRefSavedPeers(); if (savedPeers.isEmpty()) return; auto i = savedPeers.find(peer); if (i != savedPeers.cend()) { cRefSavedPeersByTime().remove(i.value(), peer); savedPeers.erase(i); writeSavedPeers(); } } void writeReportSpamStatuses() { _writeReportSpamStatuses(); } void writeTrustedBots() { if (!_working()) return; if (_trustedBots.isEmpty()) { if (_trustedBotsKey) { clearKey(_trustedBotsKey); _trustedBotsKey = 0; _mapChanged = true; _writeMap(); } } else { if (!_trustedBotsKey) { _trustedBotsKey = genKey(); _mapChanged = true; _writeMap(WriteMapWhen::Fast); } quint32 size = sizeof(qint32) + _trustedBots.size() * sizeof(quint64); EncryptedDescriptor data(size); data.stream << qint32(_trustedBots.size()); for_const (auto botId, _trustedBots) { data.stream << quint64(botId); } FileWriteDescriptor file(_trustedBotsKey); file.writeEncrypted(data); } } void readTrustedBots() { if (!_trustedBotsKey) return; FileReadDescriptor trusted; if (!readEncryptedFile(trusted, _trustedBotsKey)) { clearKey(_trustedBotsKey); _trustedBotsKey = 0; _writeMap(); return; } qint32 size = 0; trusted.stream >> size; for (int i = 0; i < size; ++i) { quint64 botId = 0; trusted.stream >> botId; _trustedBots.insert(botId); } } void makeBotTrusted(UserData *bot) { if (!isBotTrusted(bot)) { _trustedBots.insert(bot->id); writeTrustedBots(); } } bool isBotTrusted(UserData *bot) { if (!_trustedBotsRead) { readTrustedBots(); _trustedBotsRead = true; } return _trustedBots.contains(bot->id); } bool encrypt(const void *src, void *dst, uint32 len, const void *key128) { if (!LocalKey) { return false; } MTP::aesEncryptLocal(src, dst, len, LocalKey, key128); return true; } bool decrypt(const void *src, void *dst, uint32 len, const void *key128) { if (!LocalKey) { return false; } MTP::aesDecryptLocal(src, dst, len, LocalKey, key128); return true; } struct ClearManagerData { QThread *thread; StorageMap images, stickers, audios; WebFilesMap webFiles; QMutex mutex; QList<int> tasks; bool working; }; ClearManager::ClearManager() : data(new ClearManagerData()) { data->thread = new QThread(); data->working = true; } bool ClearManager::addTask(int task) { QMutexLocker lock(&data->mutex); if (!data->working) return false; if (!data->tasks.isEmpty() && (data->tasks.at(0) == ClearManagerAll)) return true; if (task == ClearManagerAll) { data->tasks.clear(); if (!_imagesMap.isEmpty()) { _imagesMap.clear(); _storageImagesSize = 0; _mapChanged = true; } if (!_stickerImagesMap.isEmpty()) { _stickerImagesMap.clear(); _storageStickersSize = 0; _mapChanged = true; } if (!_audiosMap.isEmpty()) { _audiosMap.clear(); _storageAudiosSize = 0; _mapChanged = true; } if (!_draftsMap.isEmpty()) { _draftsMap.clear(); _mapChanged = true; } if (!_draftCursorsMap.isEmpty()) { _draftCursorsMap.clear(); _mapChanged = true; } if (_locationsKey) { _locationsKey = 0; _mapChanged = true; } if (_reportSpamStatusesKey) { _reportSpamStatusesKey = 0; _mapChanged = true; } if (_trustedBotsKey) { _trustedBotsKey = 0; _mapChanged = true; } if (_recentStickersKeyOld) { _recentStickersKeyOld = 0; _mapChanged = true; } if (_installedStickersKey || _featuredStickersKey || _recentStickersKey || _archivedStickersKey) { _installedStickersKey = _featuredStickersKey = _recentStickersKey = _archivedStickersKey = 0; _mapChanged = true; } if (_recentHashtagsAndBotsKey) { _recentHashtagsAndBotsKey = 0; _mapChanged = true; } if (_savedPeersKey) { _savedPeersKey = 0; _mapChanged = true; } _writeMap(); } else { if (task & ClearManagerStorage) { if (data->images.isEmpty()) { data->images = _imagesMap; } else { for (StorageMap::const_iterator i = _imagesMap.cbegin(), e = _imagesMap.cend(); i != e; ++i) { StorageKey k = i.key(); while (data->images.constFind(k) != data->images.cend()) { ++k.second; } data->images.insert(k, i.value()); } } if (!_imagesMap.isEmpty()) { _imagesMap.clear(); _storageImagesSize = 0; _mapChanged = true; } if (data->stickers.isEmpty()) { data->stickers = _stickerImagesMap; } else { for (StorageMap::const_iterator i = _stickerImagesMap.cbegin(), e = _stickerImagesMap.cend(); i != e; ++i) { StorageKey k = i.key(); while (data->stickers.constFind(k) != data->stickers.cend()) { ++k.second; } data->stickers.insert(k, i.value()); } } if (!_stickerImagesMap.isEmpty()) { _stickerImagesMap.clear(); _storageStickersSize = 0; _mapChanged = true; } if (data->webFiles.isEmpty()) { data->webFiles = _webFilesMap; } else { for (WebFilesMap::const_iterator i = _webFilesMap.cbegin(), e = _webFilesMap.cend(); i != e; ++i) { QString k = i.key(); while (data->webFiles.constFind(k) != data->webFiles.cend()) { k += '#'; } data->webFiles.insert(k, i.value()); } } if (!_webFilesMap.isEmpty()) { _webFilesMap.clear(); _storageWebFilesSize = 0; _writeLocations(); } if (data->audios.isEmpty()) { data->audios = _audiosMap; } else { for (StorageMap::const_iterator i = _audiosMap.cbegin(), e = _audiosMap.cend(); i != e; ++i) { StorageKey k = i.key(); while (data->audios.constFind(k) != data->audios.cend()) { ++k.second; } data->audios.insert(k, i.value()); } } if (!_audiosMap.isEmpty()) { _audiosMap.clear(); _storageAudiosSize = 0; _mapChanged = true; } _writeMap(); } for (int32 i = 0, l = data->tasks.size(); i < l; ++i) { if (data->tasks.at(i) == task) return true; } } data->tasks.push_back(task); return true; } bool ClearManager::hasTask(ClearManagerTask task) { QMutexLocker lock(&data->mutex); if (data->tasks.isEmpty()) return false; if (data->tasks.at(0) == ClearManagerAll) return true; for (int32 i = 0, l = data->tasks.size(); i < l; ++i) { if (data->tasks.at(i) == task) return true; } return false; } void ClearManager::start() { moveToThread(data->thread); connect(data->thread, SIGNAL(started()), this, SLOT(onStart())); connect(data->thread, SIGNAL(finished()), data->thread, SLOT(deleteLater())); connect(data->thread, SIGNAL(finished()), this, SLOT(deleteLater())); data->thread->start(); } void ClearManager::stop() { { QMutexLocker lock(&data->mutex); data->tasks.clear(); } auto thread = data->thread; thread->quit(); thread->wait(); } ClearManager::~ClearManager() { delete data; } void ClearManager::onStart() { while (true) { int task = 0; bool result = false; StorageMap images, stickers, audios; WebFilesMap webFiles; { QMutexLocker lock(&data->mutex); if (data->tasks.isEmpty()) { data->working = false; break; } task = data->tasks.at(0); images = data->images; stickers = data->stickers; audios = data->audios; webFiles = data->webFiles; } switch (task) { case ClearManagerAll: { result = QDir(cTempDir()).removeRecursively(); QDirIterator di(_userBasePath, QDir::AllEntries | QDir::Hidden | QDir::System | QDir::NoDotAndDotDot); while (di.hasNext()) { di.next(); const QFileInfo& fi = di.fileInfo(); if (fi.isDir() && !fi.isSymLink()) { if (!QDir(di.filePath()).removeRecursively()) result = false; } else { QString path = di.filePath(); if (!path.endsWith(qstr("map0")) && !path.endsWith(qstr("map1"))) { if (!QFile::remove(di.filePath())) result = false; } } } } break; case ClearManagerDownloads: result = QDir(cTempDir()).removeRecursively(); break; case ClearManagerStorage: for (StorageMap::const_iterator i = images.cbegin(), e = images.cend(); i != e; ++i) { clearKey(i.value().first, FileOption::User); } for (StorageMap::const_iterator i = stickers.cbegin(), e = stickers.cend(); i != e; ++i) { clearKey(i.value().first, FileOption::User); } for (StorageMap::const_iterator i = audios.cbegin(), e = audios.cend(); i != e; ++i) { clearKey(i.value().first, FileOption::User); } for (WebFilesMap::const_iterator i = webFiles.cbegin(), e = webFiles.cend(); i != e; ++i) { clearKey(i.value().first, FileOption::User); } result = true; break; } { QMutexLocker lock(&data->mutex); if (!data->tasks.isEmpty() && data->tasks.at(0) == task) { data->tasks.pop_front(); } if (data->tasks.isEmpty()) { data->working = false; } if (result) { emit succeed(task, data->working ? 0 : this); } else { emit failed(task, data->working ? 0 : this); } if (!data->working) break; } } } namespace internal { Manager::Manager() { _mapWriteTimer.setSingleShot(true); connect(&_mapWriteTimer, SIGNAL(timeout()), this, SLOT(mapWriteTimeout())); _locationsWriteTimer.setSingleShot(true); connect(&_locationsWriteTimer, SIGNAL(timeout()), this, SLOT(locationsWriteTimeout())); } void Manager::writeMap(bool fast) { if (!_mapWriteTimer.isActive() || fast) { _mapWriteTimer.start(fast ? 1 : WriteMapTimeout); } else if (_mapWriteTimer.remainingTime() <= 0) { mapWriteTimeout(); } } void Manager::writingMap() { _mapWriteTimer.stop(); } void Manager::writeLocations(bool fast) { if (!_locationsWriteTimer.isActive() || fast) { _locationsWriteTimer.start(fast ? 1 : WriteMapTimeout); } else if (_locationsWriteTimer.remainingTime() <= 0) { locationsWriteTimeout(); } } void Manager::writingLocations() { _locationsWriteTimer.stop(); } void Manager::mapWriteTimeout() { _writeMap(WriteMapWhen::Now); } void Manager::locationsWriteTimeout() { _writeLocations(WriteMapWhen::Now); } void Manager::finish() { if (_mapWriteTimer.isActive()) { mapWriteTimeout(); } if (_locationsWriteTimer.isActive()) { locationsWriteTimeout(); } } } // namespace internal } // namespace Local �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/storage/localstorage.h�����������������������������������������0000664�0000000�0000000�00000012176�13262451251�0023726�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "core/basic_types.h" #include "storage/file_download.h" #include "auth_session.h" namespace Window { namespace Theme { struct Cached; } // namespace Theme } // namespace Window namespace Local { void start(); void finish(); void readSettings(); void writeSettings(); void writeUserSettings(); void writeMtpData(); void reset(); bool checkPasscode(const QByteArray &passcode); void setPasscode(const QByteArray &passcode); enum ClearManagerTask { ClearManagerAll = 0xFFFF, ClearManagerDownloads = 0x01, ClearManagerStorage = 0x02, }; struct ClearManagerData; class ClearManager : public QObject { Q_OBJECT public: ClearManager(); bool addTask(int task); bool hasTask(ClearManagerTask task); void start(); void stop(); signals: void succeed(int task, void *manager); void failed(int task, void *manager); private slots: void onStart(); private: ~ClearManager(); ClearManagerData *data; }; enum ReadMapState { ReadMapFailed = 0, ReadMapDone = 1, ReadMapPassNeeded = 2, }; ReadMapState readMap(const QByteArray &pass); int32 oldMapVersion(); int32 oldSettingsVersion(); struct MessageDraft { MessageDraft(MsgId msgId = 0, TextWithTags textWithTags = TextWithTags(), bool previewCancelled = false) : msgId(msgId) , textWithTags(textWithTags) , previewCancelled(previewCancelled) { } MsgId msgId; TextWithTags textWithTags; bool previewCancelled; }; void writeDrafts(const PeerId &peer, const MessageDraft &localDraft, const MessageDraft &editDraft); void readDraftsWithCursors(History *h); void writeDraftCursors(const PeerId &peer, const MessageCursor &localCursor, const MessageCursor &editCursor); bool hasDraftCursors(const PeerId &peer); bool hasDraft(const PeerId &peer); void writeFileLocation(MediaKey location, const FileLocation &local); FileLocation readFileLocation(MediaKey location, bool check = true); void writeImage(const StorageKey &location, const ImagePtr &img); void writeImage(const StorageKey &location, const StorageImageSaved &jpeg, bool overwrite = true); TaskId startImageLoad(const StorageKey &location, mtpFileLoader *loader); bool willImageLoad(const StorageKey &location); int32 hasImages(); qint64 storageImagesSize(); void writeStickerImage(const StorageKey &location, const QByteArray &data, bool overwrite = true); TaskId startStickerImageLoad(const StorageKey &location, mtpFileLoader *loader); bool willStickerImageLoad(const StorageKey &location); bool copyStickerImage(const StorageKey &oldLocation, const StorageKey &newLocation); int32 hasStickers(); qint64 storageStickersSize(); void writeAudio(const StorageKey &location, const QByteArray &data, bool overwrite = true); TaskId startAudioLoad(const StorageKey &location, mtpFileLoader *loader); bool willAudioLoad(const StorageKey &location); bool copyAudio(const StorageKey &oldLocation, const StorageKey &newLocation); int32 hasAudios(); qint64 storageAudiosSize(); void writeWebFile(const QString &url, const QByteArray &data, bool overwrite = true); TaskId startWebFileLoad(const QString &url, webFileLoader *loader); bool willWebFileLoad(const QString &url); int32 hasWebFiles(); qint64 storageWebFilesSize(); void countVoiceWaveform(DocumentData *document); void cancelTask(TaskId id); void writeInstalledStickers(); void writeFeaturedStickers(); void writeRecentStickers(); void writeFavedStickers(); void writeArchivedStickers(); void readInstalledStickers(); void readFeaturedStickers(); void readRecentStickers(); void readFavedStickers(); void readArchivedStickers(); int32 countStickersHash(bool checkOutdatedInfo = false); int32 countRecentStickersHash(); int32 countFavedStickersHash(); int32 countFeaturedStickersHash(); void writeSavedGifs(); void readSavedGifs(); int32 countSavedGifsHash(); void writeBackground(int32 id, const QImage &img); bool readBackground(); void writeTheme(const QString &pathRelative, const QString &pathAbsolute, const QByteArray &content, const Window::Theme::Cached &cache); void clearTheme(); bool hasTheme(); QString themeAbsolutePath(); QString themePaletteAbsolutePath(); bool copyThemeColorsToPalette(const QString &file); void writeLangPack(); void writeRecentHashtagsAndBots(); void readRecentHashtagsAndBots(); void addSavedPeer(PeerData *peer, const QDateTime &position); void removeSavedPeer(PeerData *peer); void readSavedPeers(); void writeReportSpamStatuses(); void makeBotTrusted(UserData *bot); bool isBotTrusted(UserData *bot); bool encrypt(const void *src, void *dst, uint32 len, const void *key128); bool decrypt(const void *src, void *dst, uint32 len, const void *key128); namespace internal { class Manager : public QObject { Q_OBJECT public: Manager(); void writeMap(bool fast); void writingMap(); void writeLocations(bool fast); void writingLocations(); void finish(); public slots: void mapWriteTimeout(); void locationsWriteTimeout(); private: QTimer _mapWriteTimer; QTimer _locationsWriteTimer; }; } // namespace internal } // namespace Local ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/storage/serialize_common.cpp�����������������������������������0000664�0000000�0000000�00000002016�13262451251�0025131�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "storage/serialize_common.h" namespace Serialize { void writeStorageImageLocation(QDataStream &stream, const StorageImageLocation &loc) { stream << qint32(loc.width()) << qint32(loc.height()); stream << qint32(loc.dc()) << quint64(loc.volume()) << qint32(loc.local()) << quint64(loc.secret()); } StorageImageLocation readStorageImageLocation(QDataStream &stream) { qint32 width, height, dc, local; quint64 volume, secret; stream >> width >> height >> dc >> volume >> local >> secret; return StorageImageLocation(width, height, dc, volume, local, secret); } int storageImageLocationSize() { // width + height + dc + volume + local + secret return sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(quint64) + sizeof(qint32) + sizeof(quint64); } } // namespace Serialize ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/storage/serialize_common.h�������������������������������������0000664�0000000�0000000�00000005312�13262451251�0024600�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/images.h" #include "mtproto/auth_key.h" namespace Serialize { inline int stringSize(const QString &str) { return sizeof(quint32) + str.size() * sizeof(ushort); } inline int bytearraySize(const QByteArray &arr) { return sizeof(quint32) + arr.size(); } inline int bytesSize(base::const_byte_span bytes) { return sizeof(quint32) + bytes.size(); } struct ReadBytesVectorWrap { base::byte_vector &bytes; }; inline ReadBytesVectorWrap bytes(base::byte_vector &bytes) { return ReadBytesVectorWrap { bytes }; } // Compatible with QDataStream &operator>>(QDataStream &, QByteArray &); inline QDataStream &operator>>(QDataStream &stream, ReadBytesVectorWrap data) { auto &bytes = data.bytes; bytes.clear(); quint32 len; stream >> len; if (stream.status() != QDataStream::Ok || len == 0xFFFFFFFF) { return stream; } constexpr auto kStep = quint32(1024 * 1024); for (auto allocated = quint32(0); allocated < len;) { auto blockSize = qMin(kStep, len - allocated); bytes.resize(allocated + blockSize); if (stream.readRawData(reinterpret_cast<char*>(bytes.data()) + allocated, blockSize) != blockSize) { bytes.clear(); stream.setStatus(QDataStream::ReadPastEnd); return stream; } allocated += blockSize; } return stream; } struct WriteBytesWrap { base::const_byte_span bytes; }; inline WriteBytesWrap bytes(base::const_byte_span bytes) { return WriteBytesWrap { bytes }; } inline QDataStream &operator<<(QDataStream &stream, WriteBytesWrap data) { auto bytes = data.bytes; if (bytes.empty()) { stream << quint32(0xFFFFFFFF); } else { auto size = quint32(bytes.size()); stream << size; stream.writeRawData(reinterpret_cast<const char*>(bytes.data()), size); } return stream; } inline QDataStream &operator<<(QDataStream &stream, ReadBytesVectorWrap data) { return stream << WriteBytesWrap { data.bytes }; } inline int dateTimeSize() { return (sizeof(qint64) + sizeof(quint32) + sizeof(qint8)); } void writeStorageImageLocation(QDataStream &stream, const StorageImageLocation &loc); StorageImageLocation readStorageImageLocation(QDataStream &stream); int storageImageLocationSize(); template <typename T> inline T read(QDataStream &stream) { auto result = T(); stream >> result; return result; } template <> inline MTP::AuthKey::Data read<MTP::AuthKey::Data>(QDataStream &stream) { auto result = MTP::AuthKey::Data(); stream.readRawData(reinterpret_cast<char*>(result.data()), result.size()); return result; } } // namespace Serialize ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/storage/serialize_document.cpp���������������������������������0000664�0000000�0000000�00000012507�13262451251�0025465�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "storage/serialize_document.h" #include "storage/serialize_common.h" #include "chat_helpers/stickers.h" #include "data/data_session.h" #include "auth_session.h" namespace { enum StickerSetType { StickerSetTypeEmpty = 0, StickerSetTypeID = 1, StickerSetTypeShortName = 2, }; } // namespace namespace Serialize { void Document::writeToStream(QDataStream &stream, DocumentData *document) { stream << quint64(document->id) << quint64(document->_access) << qint32(document->date); stream << qint32(document->_version); stream << document->filename() << document->mimeString() << qint32(document->_dc) << qint32(document->size); stream << qint32(document->dimensions.width()) << qint32(document->dimensions.height()); stream << qint32(document->type); if (auto sticker = document->sticker()) { stream << document->sticker()->alt; switch (document->sticker()->set.type()) { case mtpc_inputStickerSetID: { stream << qint32(StickerSetTypeID); } break; case mtpc_inputStickerSetShortName: { stream << qint32(StickerSetTypeShortName); } break; case mtpc_inputStickerSetEmpty: default: { stream << qint32(StickerSetTypeEmpty); } break; } writeStorageImageLocation(stream, document->sticker()->loc); } else { stream << qint32(document->duration()); writeStorageImageLocation(stream, document->thumb->location()); } } DocumentData *Document::readFromStreamHelper(int streamAppVersion, QDataStream &stream, const StickerSetInfo *info) { quint64 id, access; QString name, mime; qint32 date, dc, size, width, height, type, version; stream >> id >> access >> date; if (streamAppVersion >= 9061) { stream >> version; } else { version = 0; } stream >> name >> mime >> dc >> size; stream >> width >> height; stream >> type; QVector<MTPDocumentAttribute> attributes; if (!name.isEmpty()) { attributes.push_back(MTP_documentAttributeFilename(MTP_string(name))); } qint32 duration = -1; StorageImageLocation thumb; if (type == StickerDocument) { QString alt; qint32 typeOfSet; stream >> alt >> typeOfSet; thumb = readStorageImageLocation(stream); if (typeOfSet == StickerSetTypeEmpty) { attributes.push_back(MTP_documentAttributeSticker(MTP_flags(0), MTP_string(alt), MTP_inputStickerSetEmpty(), MTPMaskCoords())); } else if (info) { if (info->setId == Stickers::DefaultSetId || info->setId == Stickers::CloudRecentSetId || info->setId == Stickers::FavedSetId || info->setId == Stickers::CustomSetId) { typeOfSet = StickerSetTypeEmpty; } switch (typeOfSet) { case StickerSetTypeID: { attributes.push_back(MTP_documentAttributeSticker(MTP_flags(0), MTP_string(alt), MTP_inputStickerSetID(MTP_long(info->setId), MTP_long(info->accessHash)), MTPMaskCoords())); } break; case StickerSetTypeShortName: { attributes.push_back(MTP_documentAttributeSticker(MTP_flags(0), MTP_string(alt), MTP_inputStickerSetShortName(MTP_string(info->shortName)), MTPMaskCoords())); } break; case StickerSetTypeEmpty: default: { attributes.push_back(MTP_documentAttributeSticker(MTP_flags(0), MTP_string(alt), MTP_inputStickerSetEmpty(), MTPMaskCoords())); } break; } } } else { stream >> duration; if (type == AnimatedDocument) { attributes.push_back(MTP_documentAttributeAnimated()); } thumb = readStorageImageLocation(stream); } if (width > 0 && height > 0) { if (duration >= 0) { auto flags = MTPDdocumentAttributeVideo::Flags(0); if (type == RoundVideoDocument) { flags |= MTPDdocumentAttributeVideo::Flag::f_round_message; } attributes.push_back(MTP_documentAttributeVideo(MTP_flags(flags), MTP_int(duration), MTP_int(width), MTP_int(height))); } else { attributes.push_back(MTP_documentAttributeImageSize(MTP_int(width), MTP_int(height))); } } if (!dc && !access) { return nullptr; } return Auth().data().document( id, access, version, date, attributes, mime, thumb.isNull() ? ImagePtr() : ImagePtr(thumb), dc, size, thumb); } DocumentData *Document::readStickerFromStream(int streamAppVersion, QDataStream &stream, const StickerSetInfo &info) { return readFromStreamHelper(streamAppVersion, stream, &info); } DocumentData *Document::readFromStream(int streamAppVersion, QDataStream &stream) { return readFromStreamHelper(streamAppVersion, stream, nullptr); } int Document::sizeInStream(DocumentData *document) { int result = 0; // id + access + date + version result += sizeof(quint64) + sizeof(quint64) + sizeof(qint32) + sizeof(qint32); // + namelen + name + mimelen + mime + dc + size result += stringSize(document->filename()) + stringSize(document->mimeString()) + sizeof(qint32) + sizeof(qint32); // + width + height result += sizeof(qint32) + sizeof(qint32); // + type result += sizeof(qint32); if (auto sticker = document->sticker()) { // type == StickerDocument // + altlen + alt + type-of-set result += stringSize(sticker->alt) + sizeof(qint32); // + thumb loc result += Serialize::storageImageLocationSize(); } else { // + duration result += sizeof(qint32); // + thumb loc result += Serialize::storageImageLocationSize(); } return result; } } // namespace Serialize �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/storage/serialize_document.h�����������������������������������0000664�0000000�0000000�00000002025�13262451251�0025124�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "data/data_document.h" namespace Serialize { class Document { public: struct StickerSetInfo { StickerSetInfo(uint64 setId, uint64 accessHash, QString shortName) : setId(setId) , accessHash(accessHash) , shortName(shortName) { } uint64 setId; uint64 accessHash; QString shortName; }; static void writeToStream(QDataStream &stream, DocumentData *document); static DocumentData *readStickerFromStream(int streamAppVersion, QDataStream &stream, const StickerSetInfo &info); static DocumentData *readFromStream(int streamAppVersion, QDataStream &stream); static int sizeInStream(DocumentData *document); private: static DocumentData *readFromStreamHelper(int streamAppVersion, QDataStream &stream, const StickerSetInfo *info); }; } // namespace Serialize �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/storage/storage_facade.cpp�������������������������������������0000664�0000000�0000000�00000021147�13262451251�0024527�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "storage/storage_facade.h" #include "storage/storage_shared_media.h" #include "storage/storage_user_photos.h" #include "storage/storage_feed_messages.h" namespace Storage { class Facade::Impl { public: void add(SharedMediaAddNew &&query); void add(SharedMediaAddExisting &&query); void add(SharedMediaAddSlice &&query); void remove(SharedMediaRemoveOne &&query); void remove(SharedMediaRemoveAll &&query); void invalidate(SharedMediaInvalidateBottom &&query); rpl::producer<SharedMediaResult> query(SharedMediaQuery &&query) const; rpl::producer<SharedMediaSliceUpdate> sharedMediaSliceUpdated() const; rpl::producer<SharedMediaRemoveOne> sharedMediaOneRemoved() const; rpl::producer<SharedMediaRemoveAll> sharedMediaAllRemoved() const; rpl::producer<SharedMediaInvalidateBottom> sharedMediaBottomInvalidated() const; void add(UserPhotosAddNew &&query); void add(UserPhotosAddSlice &&query); void remove(UserPhotosRemoveOne &&query); void remove(UserPhotosRemoveAfter &&query); rpl::producer<UserPhotosResult> query(UserPhotosQuery &&query) const; rpl::producer<UserPhotosSliceUpdate> userPhotosSliceUpdated() const; void add(FeedMessagesAddNew &&query); void add(FeedMessagesAddSlice &&query); void remove(FeedMessagesRemoveOne &&query); void remove(FeedMessagesRemoveAll &&query); void invalidate(FeedMessagesInvalidate &&query); void invalidate(FeedMessagesInvalidateBottom &&query); rpl::producer<FeedMessagesResult> query( FeedMessagesQuery &&query) const; rpl::producer<FeedMessagesSliceUpdate> feedMessagesSliceUpdated() const; rpl::producer<FeedMessagesRemoveOne> feedMessagesOneRemoved() const; rpl::producer<FeedMessagesRemoveAll> feedMessagesAllRemoved() const; rpl::producer<FeedMessagesInvalidate> feedMessagesInvalidated() const; rpl::producer<FeedMessagesInvalidateBottom> feedMessagesBottomInvalidated() const; private: SharedMedia _sharedMedia; UserPhotos _userPhotos; FeedMessages _feedMessages; }; void Facade::Impl::add(SharedMediaAddNew &&query) { _sharedMedia.add(std::move(query)); } void Facade::Impl::add(SharedMediaAddExisting &&query) { _sharedMedia.add(std::move(query)); } void Facade::Impl::add(SharedMediaAddSlice &&query) { _sharedMedia.add(std::move(query)); } void Facade::Impl::remove(SharedMediaRemoveOne &&query) { _sharedMedia.remove(std::move(query)); } void Facade::Impl::remove(SharedMediaRemoveAll &&query) { _sharedMedia.remove(std::move(query)); } void Facade::Impl::invalidate(SharedMediaInvalidateBottom &&query) { _sharedMedia.invalidate(std::move(query)); } rpl::producer<SharedMediaResult> Facade::Impl::query(SharedMediaQuery &&query) const { return _sharedMedia.query(std::move(query)); } rpl::producer<SharedMediaSliceUpdate> Facade::Impl::sharedMediaSliceUpdated() const { return _sharedMedia.sliceUpdated(); } rpl::producer<SharedMediaRemoveOne> Facade::Impl::sharedMediaOneRemoved() const { return _sharedMedia.oneRemoved(); } rpl::producer<SharedMediaRemoveAll> Facade::Impl::sharedMediaAllRemoved() const { return _sharedMedia.allRemoved(); } rpl::producer<SharedMediaInvalidateBottom> Facade::Impl::sharedMediaBottomInvalidated() const { return _sharedMedia.bottomInvalidated(); } void Facade::Impl::add(UserPhotosAddNew &&query) { return _userPhotos.add(std::move(query)); } void Facade::Impl::add(UserPhotosAddSlice &&query) { return _userPhotos.add(std::move(query)); } void Facade::Impl::remove(UserPhotosRemoveOne &&query) { return _userPhotos.remove(std::move(query)); } void Facade::Impl::remove(UserPhotosRemoveAfter &&query) { return _userPhotos.remove(std::move(query)); } rpl::producer<UserPhotosResult> Facade::Impl::query(UserPhotosQuery &&query) const { return _userPhotos.query(std::move(query)); } rpl::producer<UserPhotosSliceUpdate> Facade::Impl::userPhotosSliceUpdated() const { return _userPhotos.sliceUpdated(); } void Facade::Impl::add(FeedMessagesAddNew &&query) { return _feedMessages.add(std::move(query)); } void Facade::Impl::add(FeedMessagesAddSlice &&query) { return _feedMessages.add(std::move(query)); } void Facade::Impl::remove(FeedMessagesRemoveOne &&query) { return _feedMessages.remove(std::move(query)); } void Facade::Impl::remove(FeedMessagesRemoveAll &&query) { return _feedMessages.remove(std::move(query)); } void Facade::Impl::invalidate(FeedMessagesInvalidate &&query) { return _feedMessages.invalidate(std::move(query)); } void Facade::Impl::invalidate(FeedMessagesInvalidateBottom &&query) { return _feedMessages.invalidate(std::move(query)); } rpl::producer<FeedMessagesResult> Facade::Impl::query( FeedMessagesQuery &&query) const { return _feedMessages.query(std::move(query)); } rpl::producer<FeedMessagesSliceUpdate> Facade::Impl::feedMessagesSliceUpdated() const { return _feedMessages.sliceUpdated(); } rpl::producer<FeedMessagesRemoveOne> Facade::Impl::feedMessagesOneRemoved() const { return _feedMessages.oneRemoved(); } rpl::producer<FeedMessagesRemoveAll> Facade::Impl::feedMessagesAllRemoved() const { return _feedMessages.allRemoved(); } rpl::producer<FeedMessagesInvalidate> Facade::Impl::feedMessagesInvalidated() const { return _feedMessages.invalidated(); } rpl::producer<FeedMessagesInvalidateBottom> Facade::Impl::feedMessagesBottomInvalidated() const { return _feedMessages.bottomInvalidated(); } Facade::Facade() : _impl(std::make_unique<Impl>()) { } void Facade::add(SharedMediaAddNew &&query) { _impl->add(std::move(query)); } void Facade::add(SharedMediaAddExisting &&query) { _impl->add(std::move(query)); } void Facade::add(SharedMediaAddSlice &&query) { _impl->add(std::move(query)); } void Facade::remove(SharedMediaRemoveOne &&query) { _impl->remove(std::move(query)); } void Facade::remove(SharedMediaRemoveAll &&query) { _impl->remove(std::move(query)); } void Facade::invalidate(SharedMediaInvalidateBottom &&query) { _impl->invalidate(std::move(query)); } rpl::producer<SharedMediaResult> Facade::query(SharedMediaQuery &&query) const { return _impl->query(std::move(query)); } rpl::producer<SharedMediaSliceUpdate> Facade::sharedMediaSliceUpdated() const { return _impl->sharedMediaSliceUpdated(); } rpl::producer<SharedMediaRemoveOne> Facade::sharedMediaOneRemoved() const { return _impl->sharedMediaOneRemoved(); } rpl::producer<SharedMediaRemoveAll> Facade::sharedMediaAllRemoved() const { return _impl->sharedMediaAllRemoved(); } rpl::producer<SharedMediaInvalidateBottom> Facade::sharedMediaBottomInvalidated() const { return _impl->sharedMediaBottomInvalidated(); } void Facade::add(UserPhotosAddNew &&query) { return _impl->add(std::move(query)); } void Facade::add(UserPhotosAddSlice &&query) { return _impl->add(std::move(query)); } void Facade::remove(UserPhotosRemoveOne &&query) { return _impl->remove(std::move(query)); } void Facade::remove(UserPhotosRemoveAfter &&query) { return _impl->remove(std::move(query)); } rpl::producer<UserPhotosResult> Facade::query(UserPhotosQuery &&query) const { return _impl->query(std::move(query)); } rpl::producer<UserPhotosSliceUpdate> Facade::userPhotosSliceUpdated() const { return _impl->userPhotosSliceUpdated(); } void Facade::add(FeedMessagesAddNew &&query) { return _impl->add(std::move(query)); } void Facade::add(FeedMessagesAddSlice &&query) { return _impl->add(std::move(query)); } void Facade::remove(FeedMessagesRemoveOne &&query) { return _impl->remove(std::move(query)); } void Facade::remove(FeedMessagesRemoveAll &&query) { return _impl->remove(std::move(query)); } void Facade::invalidate(FeedMessagesInvalidate &&query) { return _impl->invalidate(std::move(query)); } void Facade::invalidate(FeedMessagesInvalidateBottom &&query) { return _impl->invalidate(std::move(query)); } rpl::producer<FeedMessagesResult> Facade::query( FeedMessagesQuery &&query) const { return _impl->query(std::move(query)); } rpl::producer<FeedMessagesSliceUpdate> Facade::feedMessagesSliceUpdated() const { return _impl->feedMessagesSliceUpdated(); } rpl::producer<FeedMessagesRemoveOne> Facade::feedMessagesOneRemoved() const { return _impl->feedMessagesOneRemoved(); } rpl::producer<FeedMessagesRemoveAll> Facade::feedMessagesAllRemoved() const { return _impl->feedMessagesAllRemoved(); } rpl::producer<FeedMessagesInvalidate> Facade::feedMessagesInvalidated() const { return _impl->feedMessagesInvalidated(); } rpl::producer<FeedMessagesInvalidateBottom> Facade::feedMessagesBottomInvalidated() const { return _impl->feedMessagesBottomInvalidated(); } Facade::~Facade() = default; } // namespace Storage �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/storage/storage_facade.h���������������������������������������0000664�0000000�0000000�00000005645�13262451251�0024201�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/producer.h> #include "base/enum_mask.h" namespace Data { struct MessagesResult; } // namespace Data namespace Storage { struct SparseIdsListResult; struct SharedMediaAddNew; struct SharedMediaAddExisting; struct SharedMediaAddSlice; struct SharedMediaRemoveOne; struct SharedMediaRemoveAll; struct SharedMediaInvalidateBottom; struct SharedMediaQuery; using SharedMediaResult = SparseIdsListResult; struct SharedMediaSliceUpdate; struct UserPhotosAddNew; struct UserPhotosAddSlice; struct UserPhotosRemoveOne; struct UserPhotosRemoveAfter; struct UserPhotosQuery; struct UserPhotosResult; struct UserPhotosSliceUpdate; struct FeedMessagesAddNew; struct FeedMessagesAddSlice; struct FeedMessagesRemoveOne; struct FeedMessagesRemoveAll; struct FeedMessagesInvalidate; struct FeedMessagesInvalidateBottom; struct FeedMessagesQuery; using FeedMessagesResult = Data::MessagesResult; struct FeedMessagesSliceUpdate; class Facade { public: Facade(); void add(SharedMediaAddNew &&query); void add(SharedMediaAddExisting &&query); void add(SharedMediaAddSlice &&query); void remove(SharedMediaRemoveOne &&query); void remove(SharedMediaRemoveAll &&query); void invalidate(SharedMediaInvalidateBottom &&query); rpl::producer<SharedMediaResult> query(SharedMediaQuery &&query) const; rpl::producer<SharedMediaSliceUpdate> sharedMediaSliceUpdated() const; rpl::producer<SharedMediaRemoveOne> sharedMediaOneRemoved() const; rpl::producer<SharedMediaRemoveAll> sharedMediaAllRemoved() const; rpl::producer<SharedMediaInvalidateBottom> sharedMediaBottomInvalidated() const; void add(UserPhotosAddNew &&query); void add(UserPhotosAddSlice &&query); void remove(UserPhotosRemoveOne &&query); void remove(UserPhotosRemoveAfter &&query); rpl::producer<UserPhotosResult> query(UserPhotosQuery &&query) const; rpl::producer<UserPhotosSliceUpdate> userPhotosSliceUpdated() const; void add(FeedMessagesAddNew &&query); void add(FeedMessagesAddSlice &&query); void remove(FeedMessagesRemoveOne &&query); void remove(FeedMessagesRemoveAll &&query); void invalidate(FeedMessagesInvalidate &&query); void invalidate(FeedMessagesInvalidateBottom &&query); rpl::producer<FeedMessagesResult> query( FeedMessagesQuery &&query) const; rpl::producer<FeedMessagesSliceUpdate> feedMessagesSliceUpdated() const; rpl::producer<FeedMessagesRemoveOne> feedMessagesOneRemoved() const; rpl::producer<FeedMessagesRemoveAll> feedMessagesAllRemoved() const; rpl::producer<FeedMessagesInvalidate> feedMessagesInvalidated() const; rpl::producer<FeedMessagesInvalidateBottom> feedMessagesBottomInvalidated() const; ~Facade(); private: class Impl; const std::unique_ptr<Impl> _impl; }; } // namespace Storage �������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/storage/storage_feed_messages.cpp������������������������������0000664�0000000�0000000�00000005652�13262451251�0026121�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "storage/storage_feed_messages.h" namespace Storage { auto FeedMessages::enforceLists(FeedId feedId) -> std::map<FeedId, List>::iterator { auto result = _lists.find(feedId); if (result != _lists.end()) { return result; } result = _lists.emplace(feedId, List {}).first; result->second.sliceUpdated( ) | rpl::map([=](Data::MessagesSliceUpdate &&update) { return FeedMessagesSliceUpdate( feedId, std::move(update)); }) | rpl::start_to_stream(_sliceUpdated, _lifetime); return result; } void FeedMessages::add(FeedMessagesAddNew &&query) { auto feedId = query.feedId; auto feedIt = enforceLists(feedId); feedIt->second.addNew(query.messageId); } void FeedMessages::add(FeedMessagesAddSlice &&query) { auto feedIt = enforceLists(query.feedId); feedIt->second.addSlice( std::move(query.messageIds), query.noSkipRange, base::none); } void FeedMessages::remove(FeedMessagesRemoveOne &&query) { auto feedIt = _lists.find(query.feedId); if (feedIt != _lists.end()) { feedIt->second.removeOne(query.messageId); _oneRemoved.fire(std::move(query)); } } void FeedMessages::remove(FeedMessagesRemoveAll &&query) { auto feedIt = _lists.find(query.feedId); if (feedIt != _lists.end()) { feedIt->second.removeAll(query.channelId); _allRemoved.fire(std::move(query)); } } void FeedMessages::invalidate(FeedMessagesInvalidate &&query) { auto feedIt = _lists.find(query.feedId); if (feedIt != _lists.end()) { feedIt->second.invalidateBottom(); feedIt->second.invalidate(); _invalidated.fire(std::move(query)); } } void FeedMessages::invalidate(FeedMessagesInvalidateBottom &&query) { auto feedIt = _lists.find(query.feedId); if (feedIt != _lists.end()) { feedIt->second.invalidateBottom(); _bottomInvalidated.fire(std::move(query)); } } rpl::producer<FeedMessagesResult> FeedMessages::query( FeedMessagesQuery &&query) const { auto feedIt = _lists.find(query.key.feedId); if (feedIt != _lists.end()) { return feedIt->second.query(Data::MessagesQuery( query.key.position, query.limitBefore, query.limitAfter)); } return [](auto consumer) { consumer.put_done(); return rpl::lifetime(); }; } rpl::producer<FeedMessagesSliceUpdate> FeedMessages::sliceUpdated() const { return _sliceUpdated.events(); } rpl::producer<FeedMessagesRemoveOne> FeedMessages::oneRemoved() const { return _oneRemoved.events(); } rpl::producer<FeedMessagesRemoveAll> FeedMessages::allRemoved() const { return _allRemoved.events(); } rpl::producer<FeedMessagesInvalidate> FeedMessages::invalidated() const { return _invalidated.events(); } rpl::producer<FeedMessagesInvalidateBottom> FeedMessages::bottomInvalidated() const { return _bottomInvalidated.events(); } } // namespace Storage ��������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/storage/storage_feed_messages.h��������������������������������0000664�0000000�0000000�00000007215�13262451251�0025563�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/event_stream.h> #include "storage/storage_facade.h" #include "data/data_feed_messages.h" namespace Storage { struct FeedMessagesAddNew { FeedMessagesAddNew(FeedId feedId, Data::MessagePosition messageId) : feedId(feedId) , messageId(messageId) { } FeedId feedId = 0; Data::MessagePosition messageId; }; struct FeedMessagesAddSlice { FeedMessagesAddSlice( FeedId feedId, std::vector<Data::MessagePosition> &&messageIds, Data::MessagesRange noSkipRange) : feedId(feedId) , messageIds(std::move(messageIds)) , noSkipRange(noSkipRange) { } FeedId feedId = 0; std::vector<Data::MessagePosition> messageIds; Data::MessagesRange noSkipRange; }; struct FeedMessagesRemoveOne { FeedMessagesRemoveOne( FeedId feedId, Data::MessagePosition messageId) : feedId(feedId) , messageId(messageId) { } FeedId feedId = 0; Data::MessagePosition messageId; }; struct FeedMessagesRemoveAll { FeedMessagesRemoveAll(FeedId feedId, ChannelId channelId) : feedId(feedId) , channelId(channelId) { } FeedId feedId = 0; ChannelId channelId = 0; }; struct FeedMessagesInvalidate { explicit FeedMessagesInvalidate(FeedId feedId) : feedId(feedId) { } FeedId feedId = 0; }; struct FeedMessagesInvalidateBottom { explicit FeedMessagesInvalidateBottom(FeedId feedId) : feedId(feedId) { } FeedId feedId = 0; }; struct FeedMessagesKey { FeedMessagesKey( FeedId feedId, Data::MessagePosition position) : feedId(feedId) , position(position) { } bool operator==(const FeedMessagesKey &other) const { return (feedId == other.feedId) && (position == other.position); } bool operator!=(const FeedMessagesKey &other) const { return !(*this == other); } FeedId feedId = 0; Data::MessagePosition position; }; struct FeedMessagesQuery { FeedMessagesQuery( FeedMessagesKey key, int limitBefore, int limitAfter) : key(key) , limitBefore(limitBefore) , limitAfter(limitAfter) { } FeedMessagesKey key; int limitBefore = 0; int limitAfter = 0; }; using FeedMessagesResult = Data::MessagesResult; struct FeedMessagesSliceUpdate { FeedMessagesSliceUpdate( FeedId feedId, Data::MessagesSliceUpdate &&data) : feedId(feedId) , data(std::move(data)) { } FeedId feedId = 0; Data::MessagesSliceUpdate data; }; class FeedMessages { public: void add(FeedMessagesAddNew &&query); void add(FeedMessagesAddSlice &&query); void remove(FeedMessagesRemoveOne &&query); void remove(FeedMessagesRemoveAll &&query); void invalidate(FeedMessagesInvalidate &&query); void invalidate(FeedMessagesInvalidateBottom &&query); rpl::producer<FeedMessagesResult> query( FeedMessagesQuery &&query) const; rpl::producer<FeedMessagesSliceUpdate> sliceUpdated() const; rpl::producer<FeedMessagesRemoveOne> oneRemoved() const; rpl::producer<FeedMessagesRemoveAll> allRemoved() const; rpl::producer<FeedMessagesInvalidate> invalidated() const; rpl::producer<FeedMessagesInvalidateBottom> bottomInvalidated() const; private: using List = Data::MessagesList; std::map<FeedId, List>::iterator enforceLists(FeedId feedId); std::map<FeedId, List> _lists; rpl::event_stream<FeedMessagesSliceUpdate> _sliceUpdated; rpl::event_stream<FeedMessagesRemoveOne> _oneRemoved; rpl::event_stream<FeedMessagesRemoveAll> _allRemoved; rpl::event_stream<FeedMessagesInvalidate> _invalidated; rpl::event_stream<FeedMessagesInvalidateBottom> _bottomInvalidated; rpl::lifetime _lifetime; }; } // namespace Storage �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/storage/storage_media_prepare.cpp������������������������������0000664�0000000�0000000�00000020060�13262451251�0026112�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "storage/storage_media_prepare.h" #include "platform/platform_file_utilities.h" #include "storage/localimageloader.h" namespace Storage { namespace { constexpr auto kMaxAlbumCount = 10; bool HasExtensionFrom(const QString &file, const QStringList &extensions) { for (const auto &extension : extensions) { const auto ext = file.right(extension.size()); if (ext.compare(extension, Qt::CaseInsensitive) == 0) { return true; } } return false; } bool ValidPhotoForAlbum(const FileMediaInformation::Image &image) { if (image.animated) { return false; } const auto width = image.data.width(); const auto height = image.data.height(); return ValidateThumbDimensions(width, height); } bool ValidVideoForAlbum(const FileMediaInformation::Video &video) { const auto width = video.thumbnail.width(); const auto height = video.thumbnail.height(); return ValidateThumbDimensions(width, height); } bool PrepareAlbumMediaIsWaiting( QSemaphore &semaphore, PreparedFile &file, int previewWidth) { // TODO: Use some special thread queue, like a separate QThreadPool. crl::async([=, &semaphore, &file] { const auto guard = gsl::finally([&] { semaphore.release(); }); if (!file.path.isEmpty()) { file.mime = mimeTypeForFile(QFileInfo(file.path)).name(); file.information = FileLoadTask::ReadMediaInformation( file.path, QByteArray(), file.mime); } else if (!file.content.isEmpty()) { file.mime = mimeTypeForData(file.content).name(); file.information = FileLoadTask::ReadMediaInformation( QString(), file.content, file.mime); } else { Assert(file.information != nullptr); } using Image = FileMediaInformation::Image; using Video = FileMediaInformation::Video; if (const auto image = base::get_if<Image>( &file.information->media)) { if (ValidPhotoForAlbum(*image)) { file.preview = Images::prepareOpaque(image->data.scaledToWidth( std::min(previewWidth, convertScale(image->data.width())) * cIntRetinaFactor(), Qt::SmoothTransformation)); file.preview.setDevicePixelRatio(cRetinaFactor()); file.type = PreparedFile::AlbumType::Photo; } } else if (const auto video = base::get_if<Video>( &file.information->media)) { if (ValidVideoForAlbum(*video)) { auto blurred = Images::prepareBlur(Images::prepareOpaque(video->thumbnail)); file.preview = std::move(blurred).scaledToWidth( previewWidth * cIntRetinaFactor(), Qt::SmoothTransformation); file.preview.setDevicePixelRatio(cRetinaFactor()); file.type = PreparedFile::AlbumType::Video; } } }); return true; } void PrepareAlbum(PreparedList &result, int previewWidth) { const auto count = int(result.files.size()); if (count > kMaxAlbumCount) { return; } result.albumIsPossible = (count > 1); auto waiting = 0; QSemaphore semaphore; for (auto &file : result.files) { if (PrepareAlbumMediaIsWaiting(semaphore, file, previewWidth)) { ++waiting; } } if (waiting > 0) { semaphore.acquire(waiting); if (result.albumIsPossible) { const auto badIt = ranges::find( result.files, PreparedFile::AlbumType::None, [](const PreparedFile &file) { return file.type; }); result.albumIsPossible = (badIt == result.files.end()); } } } } // namespace bool ValidateThumbDimensions(int width, int height) { return (width > 0) && (height > 0) && (width < 20 * height) && (height < 20 * width); } PreparedFile::PreparedFile(const QString &path) : path(path) { } PreparedFile::PreparedFile(PreparedFile &&other) = default; PreparedFile &PreparedFile::operator=(PreparedFile &&other) = default; PreparedFile::~PreparedFile() = default; MimeDataState ComputeMimeDataState(const QMimeData *data) { if (!data || data->hasFormat(qsl("application/x-td-forward"))) { return MimeDataState::None; } if (data->hasImage()) { return MimeDataState::Image; } const auto uriListFormat = qsl("text/uri-list"); if (!data->hasFormat(uriListFormat)) { return MimeDataState::None; } const auto &urls = data->urls(); if (urls.isEmpty()) { return MimeDataState::None; } const auto imageExtensions = cImgExtensions(); auto files = QStringList(); auto allAreSmallImages = true; for (const auto &url : urls) { if (!url.isLocalFile()) { return MimeDataState::None; } const auto file = Platform::File::UrlToLocal(url); const auto info = QFileInfo(file); if (info.isDir()) { return MimeDataState::None; } const auto filesize = info.size(); if (filesize > App::kFileSizeLimit) { return MimeDataState::None; } else if (allAreSmallImages) { if (filesize > App::kImageSizeLimit) { allAreSmallImages = false; } else if (!HasExtensionFrom(file, imageExtensions)) { allAreSmallImages = false; } } } return allAreSmallImages ? MimeDataState::PhotoFiles : MimeDataState::Files; } PreparedList PrepareMediaList(const QList<QUrl> &files, int previewWidth) { auto locals = QStringList(); locals.reserve(files.size()); for (const auto &url : files) { if (!url.isLocalFile()) { return { PreparedList::Error::NonLocalUrl, url.toDisplayString() }; } locals.push_back(Platform::File::UrlToLocal(url)); } return PrepareMediaList(locals, previewWidth); } PreparedList PrepareMediaList(const QStringList &files, int previewWidth) { auto result = PreparedList(); result.files.reserve(files.size()); const auto extensionsToCompress = cExtensionsForCompress(); for (const auto &file : files) { const auto fileinfo = QFileInfo(file); const auto filesize = fileinfo.size(); if (fileinfo.isDir()) { return { PreparedList::Error::Directory, file }; } else if (filesize <= 0) { return { PreparedList::Error::EmptyFile, file }; } else if (filesize > App::kFileSizeLimit) { return { PreparedList::Error::TooLargeFile, file }; } const auto toCompress = HasExtensionFrom(file, extensionsToCompress); if (filesize > App::kImageSizeLimit || !toCompress) { result.allFilesForCompress = false; } result.files.emplace_back(file); } PrepareAlbum(result, previewWidth); return result; } PreparedList PrepareMediaFromImage( QImage &&image, QByteArray &&content, int previewWidth) { auto result = Storage::PreparedList(); result.allFilesForCompress = ValidateThumbDimensions( image.width(), image.height()); auto file = PreparedFile(QString()); file.content = content; if (file.content.isEmpty()) { file.information = std::make_unique<FileMediaInformation>(); const auto animated = false; FileLoadTask::FillImageInformation( std::move(image), animated, file.information); } result.files.push_back(std::move(file)); PrepareAlbum(result, previewWidth); return result; } PreparedList PreparedList::Reordered( PreparedList &&list, std::vector<int> order) { Expects(list.error == PreparedList::Error::None); Expects(list.files.size() == order.size()); auto result = PreparedList(list.error, list.errorData); result.albumIsPossible = list.albumIsPossible; result.allFilesForCompress = list.allFilesForCompress; result.files.reserve(list.files.size()); for (auto index : order) { result.files.push_back(std::move(list.files[index])); } return result; } void PreparedList::mergeToEnd(PreparedList &&other) { if (error != Error::None) { return; } if (other.error != Error::None) { error = other.error; errorData = other.errorData; return; } allFilesForCompress = allFilesForCompress && other.allFilesForCompress; files.reserve(files.size() + other.files.size()); for (auto &file : other.files) { files.push_back(std::move(file)); } if (files.size() > 1 && files.size() <= kMaxAlbumCount) { const auto badIt = ranges::find( files, PreparedFile::AlbumType::None, [](const PreparedFile &file) { return file.type; }); albumIsPossible = (badIt == files.end()); } else { albumIsPossible = false; } } int MaxAlbumItems() { return kMaxAlbumCount; } } // namespace Storage ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/storage/storage_media_prepare.h��������������������������������0000664�0000000�0000000�00000003172�13262451251�0025564�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once struct FileMediaInformation; namespace Storage { enum class MimeDataState { None, Files, PhotoFiles, Image, }; MimeDataState ComputeMimeDataState(const QMimeData *data); struct PreparedFile { enum class AlbumType { None, Photo, Video, }; PreparedFile(const QString &path); PreparedFile(PreparedFile &&other); PreparedFile &operator=(PreparedFile &&other); ~PreparedFile(); QString path; QByteArray content; QString mime; std::unique_ptr<FileMediaInformation> information; QImage preview; AlbumType type = AlbumType::None; }; struct PreparedList { enum class Error { None, NonLocalUrl, Directory, EmptyFile, TooLargeFile, }; PreparedList() = default; PreparedList(Error error, QString errorData) : error(error) , errorData(errorData) { } static PreparedList Reordered( PreparedList &&list, std::vector<int> order); void mergeToEnd(PreparedList &&other); Error error = Error::None; QString errorData; std::vector<PreparedFile> files; bool allFilesForCompress = true; bool albumIsPossible = false; }; bool ValidateThumbDimensions(int width, int height); PreparedList PrepareMediaList(const QList<QUrl> &files, int previewWidth); PreparedList PrepareMediaList(const QStringList &files, int previewWidth); PreparedList PrepareMediaFromImage( QImage &&image, QByteArray &&content, int previewWidth); int MaxAlbumItems(); } // namespace Storage ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/storage/storage_shared_media.cpp�������������������������������0000664�0000000�0000000�00000007307�13262451251�0025733�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "storage/storage_shared_media.h" #include <rpl/map.h> namespace Storage { std::map<PeerId, SharedMedia::Lists>::iterator SharedMedia::enforceLists(PeerId peer) { auto result = _lists.find(peer); if (result != _lists.end()) { return result; } result = _lists.emplace(peer, Lists {}).first; for (auto index = 0; index != kSharedMediaTypeCount; ++index) { auto &list = result->second[index]; auto type = static_cast<SharedMediaType>(index); list.sliceUpdated( ) | rpl::map([=](const SparseIdsSliceUpdate &update) { return SharedMediaSliceUpdate( peer, type, update); }) | rpl::start_to_stream(_sliceUpdated, _lifetime); } return result; } void SharedMedia::add(SharedMediaAddNew &&query) { auto peer = query.peerId; auto peerIt = enforceLists(peer); for (auto index = 0; index != kSharedMediaTypeCount; ++index) { auto type = static_cast<SharedMediaType>(index); if (query.types.test(type)) { peerIt->second[index].addNew(query.messageId); } } } void SharedMedia::add(SharedMediaAddExisting &&query) { auto peerIt = enforceLists(query.peerId); for (auto index = 0; index != kSharedMediaTypeCount; ++index) { auto type = static_cast<SharedMediaType>(index); if (query.types.test(type)) { peerIt->second[index].addExisting(query.messageId, query.noSkipRange); } } } void SharedMedia::add(SharedMediaAddSlice &&query) { Expects(IsValidSharedMediaType(query.type)); auto peerIt = enforceLists(query.peerId); auto index = static_cast<int>(query.type); peerIt->second[index].addSlice( std::move(query.messageIds), query.noSkipRange, query.count); } void SharedMedia::remove(SharedMediaRemoveOne &&query) { auto peerIt = _lists.find(query.peerId); if (peerIt != _lists.end()) { for (auto index = 0; index != kSharedMediaTypeCount; ++index) { auto type = static_cast<SharedMediaType>(index); if (query.types.test(type)) { peerIt->second[index].removeOne(query.messageId); } } _oneRemoved.fire(std::move(query)); } } void SharedMedia::remove(SharedMediaRemoveAll &&query) { auto peerIt = _lists.find(query.peerId); if (peerIt != _lists.end()) { for (auto index = 0; index != kSharedMediaTypeCount; ++index) { peerIt->second[index].removeAll(); } _allRemoved.fire(std::move(query)); } } void SharedMedia::invalidate(SharedMediaInvalidateBottom &&query) { auto peerIt = _lists.find(query.peerId); if (peerIt != _lists.end()) { for (auto index = 0; index != kSharedMediaTypeCount; ++index) { peerIt->second[index].invalidateBottom(); } _bottomInvalidated.fire(std::move(query)); } } rpl::producer<SharedMediaResult> SharedMedia::query(SharedMediaQuery &&query) const { Expects(IsValidSharedMediaType(query.key.type)); auto peerIt = _lists.find(query.key.peerId); if (peerIt != _lists.end()) { auto index = static_cast<int>(query.key.type); return peerIt->second[index].query(SparseIdsListQuery( query.key.messageId, query.limitBefore, query.limitAfter)); } return [](auto consumer) { consumer.put_done(); return rpl::lifetime(); }; } rpl::producer<SharedMediaSliceUpdate> SharedMedia::sliceUpdated() const { return _sliceUpdated.events(); } rpl::producer<SharedMediaRemoveOne> SharedMedia::oneRemoved() const { return _oneRemoved.events(); } rpl::producer<SharedMediaRemoveAll> SharedMedia::allRemoved() const { return _allRemoved.events(); } rpl::producer<SharedMediaInvalidateBottom> SharedMedia::bottomInvalidated() const { return _bottomInvalidated.events(); } } // namespace Storage �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/storage/storage_shared_media.h���������������������������������0000664�0000000�0000000�00000011041�13262451251�0025366�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/event_stream.h> #include "storage/storage_facade.h" #include "storage/storage_sparse_ids_list.h" namespace Storage { // Allow forward declarations. enum class SharedMediaType : signed char { Photo, Video, PhotoVideo, MusicFile, File, VoiceFile, Link, ChatPhoto, RoundVoiceFile, GIF, RoundFile, kCount, }; constexpr auto kSharedMediaTypeCount = static_cast<int>(SharedMediaType::kCount); constexpr bool IsValidSharedMediaType(SharedMediaType type) { return (static_cast<int>(type) >= 0) && (static_cast<int>(type) < kSharedMediaTypeCount); } using SharedMediaTypesMask = base::enum_mask<SharedMediaType>; struct SharedMediaAddNew { SharedMediaAddNew(PeerId peerId, SharedMediaTypesMask types, MsgId messageId) : peerId(peerId), messageId(messageId), types(types) { } PeerId peerId = 0; MsgId messageId = 0; SharedMediaTypesMask types; }; struct SharedMediaAddExisting { SharedMediaAddExisting( PeerId peerId, SharedMediaTypesMask types, MsgId messageId, MsgRange noSkipRange) : peerId(peerId) , messageId(messageId) , noSkipRange(noSkipRange) , types(types) { } PeerId peerId = 0; MsgId messageId = 0; MsgRange noSkipRange; SharedMediaTypesMask types; }; struct SharedMediaAddSlice { SharedMediaAddSlice( PeerId peerId, SharedMediaType type, std::vector<MsgId> &&messageIds, MsgRange noSkipRange, base::optional<int> count = base::none) : peerId(peerId) , messageIds(std::move(messageIds)) , noSkipRange(noSkipRange) , type(type) , count(count) { } PeerId peerId = 0; std::vector<MsgId> messageIds; MsgRange noSkipRange; SharedMediaType type = SharedMediaType::kCount; base::optional<int> count; }; struct SharedMediaRemoveOne { SharedMediaRemoveOne( PeerId peerId, SharedMediaTypesMask types, MsgId messageId) : peerId(peerId) , messageId(messageId) , types(types) { } PeerId peerId = 0; MsgId messageId = 0; SharedMediaTypesMask types; }; struct SharedMediaRemoveAll { SharedMediaRemoveAll(PeerId peerId) : peerId(peerId) { } PeerId peerId = 0; }; struct SharedMediaInvalidateBottom { SharedMediaInvalidateBottom(PeerId peerId) : peerId(peerId) { } PeerId peerId = 0; }; struct SharedMediaKey { SharedMediaKey( PeerId peerId, SharedMediaType type, MsgId messageId) : peerId(peerId) , type(type) , messageId(messageId) { } bool operator==(const SharedMediaKey &other) const { return (peerId == other.peerId) && (type == other.type) && (messageId == other.messageId); } bool operator!=(const SharedMediaKey &other) const { return !(*this == other); } PeerId peerId = 0; SharedMediaType type = SharedMediaType::kCount; MsgId messageId = 0; }; struct SharedMediaQuery { SharedMediaQuery( SharedMediaKey key, int limitBefore, int limitAfter) : key(key) , limitBefore(limitBefore) , limitAfter(limitAfter) { } SharedMediaKey key; int limitBefore = 0; int limitAfter = 0; }; using SharedMediaResult = SparseIdsListResult; struct SharedMediaSliceUpdate { SharedMediaSliceUpdate( PeerId peerId, SharedMediaType type, const SparseIdsSliceUpdate &data) : peerId(peerId) , type(type) , data(data) { } PeerId peerId = 0; SharedMediaType type = SharedMediaType::kCount; SparseIdsSliceUpdate data; }; class SharedMedia { public: using Type = SharedMediaType; void add(SharedMediaAddNew &&query); void add(SharedMediaAddExisting &&query); void add(SharedMediaAddSlice &&query); void remove(SharedMediaRemoveOne &&query); void remove(SharedMediaRemoveAll &&query); void invalidate(SharedMediaInvalidateBottom &&query); rpl::producer<SharedMediaResult> query(SharedMediaQuery &&query) const; rpl::producer<SharedMediaSliceUpdate> sliceUpdated() const; rpl::producer<SharedMediaRemoveOne> oneRemoved() const; rpl::producer<SharedMediaRemoveAll> allRemoved() const; rpl::producer<SharedMediaInvalidateBottom> bottomInvalidated() const; private: using Lists = std::array<SparseIdsList, kSharedMediaTypeCount>; std::map<PeerId, Lists>::iterator enforceLists(PeerId peer); std::map<PeerId, Lists> _lists; rpl::event_stream<SharedMediaSliceUpdate> _sliceUpdated; rpl::event_stream<SharedMediaRemoveOne> _oneRemoved; rpl::event_stream<SharedMediaRemoveAll> _allRemoved; rpl::event_stream<SharedMediaInvalidateBottom> _bottomInvalidated; rpl::lifetime _lifetime; }; } // namespace Storage �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/storage/storage_sparse_ids_list.cpp����������������������������0000664�0000000�0000000�00000014531�13262451251�0026512�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "storage/storage_sparse_ids_list.h" namespace Storage { SparseIdsList::Slice::Slice( base::flat_set<MsgId> &&messages, MsgRange range) : messages(std::move(messages)) , range(range) { } template <typename Range> void SparseIdsList::Slice::merge( const Range &moreMessages, MsgRange moreNoSkipRange) { Expects(moreNoSkipRange.from <= range.till); Expects(range.from <= moreNoSkipRange.till); messages.merge(std::begin(moreMessages), std::end(moreMessages)); range = { qMin(range.from, moreNoSkipRange.from), qMax(range.till, moreNoSkipRange.till) }; } template <typename Range> int SparseIdsList::uniteAndAdd( SparseIdsSliceUpdate &update, base::flat_set<Slice>::iterator uniteFrom, base::flat_set<Slice>::iterator uniteTill, const Range &messages, MsgRange noSkipRange) { auto uniteFromIndex = uniteFrom - _slices.begin(); auto was = uniteFrom->messages.size(); _slices.modify(uniteFrom, [&](Slice &slice) { slice.merge(messages, noSkipRange); }); auto firstToErase = uniteFrom + 1; if (firstToErase != uniteTill) { for (auto it = firstToErase; it != uniteTill; ++it) { _slices.modify(uniteFrom, [&](Slice &slice) { slice.merge(it->messages, it->range); }); } _slices.erase(firstToErase, uniteTill); uniteFrom = _slices.begin() + uniteFromIndex; } update.messages = &uniteFrom->messages; update.range = uniteFrom->range; return uniteFrom->messages.size() - was; } template <typename Range> int SparseIdsList::addRangeItemsAndCountNew( SparseIdsSliceUpdate &update, const Range &messages, MsgRange noSkipRange) { Expects(noSkipRange.from <= noSkipRange.till); auto uniteFrom = ranges::lower_bound( _slices, noSkipRange.from, std::less<>(), [](const Slice &slice) { return slice.range.till; }); auto uniteTill = ranges::upper_bound( _slices, noSkipRange.till, std::less<>(), [](const Slice &slice) { return slice.range.from; }); if (uniteFrom < uniteTill) { return uniteAndAdd(update, uniteFrom, uniteTill, messages, noSkipRange); } auto sliceMessages = base::flat_set<MsgId> { std::begin(messages), std::end(messages) }; auto slice = _slices.emplace( std::move(sliceMessages), noSkipRange); update.messages = &slice->messages; update.range = slice->range; return slice->messages.size(); } template <typename Range> void SparseIdsList::addRange( const Range &messages, MsgRange noSkipRange, base::optional<int> count, bool incrementCount) { Expects(!count || !incrementCount); auto wasCount = _count; auto update = SparseIdsSliceUpdate(); auto result = addRangeItemsAndCountNew( update, messages, noSkipRange); if (count) { _count = count; } else if (incrementCount && _count && result > 0) { *_count += result; } if (_slices.size() == 1) { if (_slices.front().range == MsgRange { 0, ServerMaxMsgId }) { _count = _slices.front().messages.size(); } } update.count = _count; _sliceUpdated.fire(std::move(update)); } void SparseIdsList::addNew(MsgId messageId) { auto range = { messageId }; addRange(range, { messageId, ServerMaxMsgId }, base::none, true); } void SparseIdsList::addExisting( MsgId messageId, MsgRange noSkipRange) { auto range = { messageId }; addRange(range, noSkipRange, base::none); } void SparseIdsList::addSlice( std::vector<MsgId> &&messageIds, MsgRange noSkipRange, base::optional<int> count) { addRange(messageIds, noSkipRange, count); } void SparseIdsList::removeOne(MsgId messageId) { auto slice = ranges::lower_bound( _slices, messageId, std::less<>(), [](const Slice &slice) { return slice.range.till; }); if (slice != _slices.end() && slice->range.from <= messageId) { _slices.modify(slice, [messageId](Slice &slice) { return slice.messages.remove(messageId); }); } if (_count) { --*_count; } } void SparseIdsList::removeAll() { _slices.clear(); _slices.emplace(base::flat_set<MsgId>{}, MsgRange { 0, ServerMaxMsgId }); _count = 0; } void SparseIdsList::invalidateBottom() { if (!_slices.empty()) { const auto &last = _slices.back(); if (last.range.till == ServerMaxMsgId) { _slices.modify(_slices.end() - 1, [](Slice &slice) { slice.range.till = slice.messages.empty() ? slice.range.from : slice.messages.back(); }); } } _count = base::none; } rpl::producer<SparseIdsListResult> SparseIdsList::query( SparseIdsListQuery &&query) const { return [this, query = std::move(query)](auto consumer) { auto slice = query.aroundId ? ranges::lower_bound( _slices, query.aroundId, std::less<>(), [](const Slice &slice) { return slice.range.till; }) : _slices.end(); if (slice != _slices.end() && slice->range.from <= query.aroundId) { consumer.put_next(queryFromSlice(query, *slice)); } else if (_count) { auto result = SparseIdsListResult {}; result.count = _count; consumer.put_next(std::move(result)); } consumer.put_done(); return rpl::lifetime(); }; } rpl::producer<SparseIdsSliceUpdate> SparseIdsList::sliceUpdated() const { return _sliceUpdated.events(); } SparseIdsListResult SparseIdsList::queryFromSlice( const SparseIdsListQuery &query, const Slice &slice) const { auto result = SparseIdsListResult {}; auto position = ranges::lower_bound(slice.messages, query.aroundId); auto haveBefore = int(position - slice.messages.begin()); auto haveEqualOrAfter = int(slice.messages.end() - position); auto before = qMin(haveBefore, query.limitBefore); auto equalOrAfter = qMin(haveEqualOrAfter, query.limitAfter + 1); auto ids = std::vector<MsgId>(position - before, position + equalOrAfter); result.messageIds.merge(ids.begin(), ids.end()); if (slice.range.from == 0) { result.skippedBefore = haveBefore - before; } if (slice.range.till == ServerMaxMsgId) { result.skippedAfter = haveEqualOrAfter - equalOrAfter; } if (_count) { result.count = _count; if (!result.skippedBefore && result.skippedAfter) { result.skippedBefore = *result.count - *result.skippedAfter - int(result.messageIds.size()); } else if (!result.skippedAfter && result.skippedBefore) { result.skippedAfter = *result.count - *result.skippedBefore - int(result.messageIds.size()); } } return result; } } // namespace Storage �����������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/storage/storage_sparse_ids_list.h������������������������������0000664�0000000�0000000�00000004472�13262451251�0026162�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace Storage { struct SparseIdsListQuery { SparseIdsListQuery( MsgId aroundId, int limitBefore, int limitAfter) : aroundId(aroundId) , limitBefore(limitBefore) , limitAfter(limitAfter) { } MsgId aroundId = 0; int limitBefore = 0; int limitAfter = 0; }; struct SparseIdsListResult { base::optional<int> count; base::optional<int> skippedBefore; base::optional<int> skippedAfter; base::flat_set<MsgId> messageIds; }; struct SparseIdsSliceUpdate { const base::flat_set<MsgId> *messages = nullptr; MsgRange range; base::optional<int> count; }; class SparseIdsList { public: void addNew(MsgId messageId); void addExisting(MsgId messageId, MsgRange noSkipRange); void addSlice( std::vector<MsgId> &&messageIds, MsgRange noSkipRange, base::optional<int> count); void removeOne(MsgId messageId); void removeAll(); void invalidateBottom(); rpl::producer<SparseIdsListResult> query(SparseIdsListQuery &&query) const; rpl::producer<SparseIdsSliceUpdate> sliceUpdated() const; private: struct Slice { Slice(base::flat_set<MsgId> &&messages, MsgRange range); template <typename Range> void merge(const Range &moreMessages, MsgRange moreNoSkipRange); base::flat_set<MsgId> messages; MsgRange range; inline bool operator<(const Slice &other) const { return range.from < other.range.from; } }; template <typename Range> int uniteAndAdd( SparseIdsSliceUpdate &update, base::flat_set<Slice>::iterator uniteFrom, base::flat_set<Slice>::iterator uniteTill, const Range &messages, MsgRange noSkipRange); template <typename Range> int addRangeItemsAndCountNew( SparseIdsSliceUpdate &update, const Range &messages, MsgRange noSkipRange); template <typename Range> void addRange( const Range &messages, MsgRange noSkipRange, base::optional<int> count, bool incrementCount = false); SparseIdsListResult queryFromSlice( const SparseIdsListQuery &query, const Slice &slice) const; base::optional<int> _count; base::flat_set<Slice> _slices; rpl::event_stream<SparseIdsSliceUpdate> _sliceUpdated; }; } // namespace Storage ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/storage/storage_user_photos.cpp��������������������������������0000664�0000000�0000000�00000010252�13262451251�0025671�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "storage/storage_user_photos.h" namespace Storage { void UserPhotos::List::addNew(PhotoId photoId) { if (!base::contains(_photoIds, photoId)) { _photoIds.push_back(photoId); if (_count) { ++*_count; } sendUpdate(); } } void UserPhotos::List::addSlice( std::vector<PhotoId> &&photoIds, int count) { for (auto photoId : photoIds) { if (!base::contains(_photoIds, photoId)) { _photoIds.push_front(photoId); } } _count = count; if ((_count && *_count < _photoIds.size()) || photoIds.empty()) { _count = _photoIds.size(); } sendUpdate(); } void UserPhotos::List::removeOne(PhotoId photoId) { auto position = ranges::find(_photoIds, photoId); if (position == _photoIds.end()) { _count = base::none; } else { if (_count) { --*_count; } _photoIds.erase(position); } sendUpdate(); } void UserPhotos::List::removeAfter(PhotoId photoId) { auto position = ranges::find(_photoIds, photoId); if (position == _photoIds.end()) { _count = base::none; _photoIds.clear(); } else { if (_count) { *_count -= (_photoIds.end() - position); } _photoIds.erase(position, _photoIds.end()); } sendUpdate(); } void UserPhotos::List::sendUpdate() { auto update = SliceUpdate(); update.photoIds = &_photoIds; update.count = _count; _sliceUpdated.fire(std::move(update)); } rpl::producer<UserPhotosResult> UserPhotos::List::query( UserPhotosQuery &&query) const { return [this, query = std::move(query)](auto consumer) { auto result = UserPhotosResult {}; result.count = _count; auto position = ranges::find(_photoIds, query.key.photoId); if (position != _photoIds.end()) { auto haveBefore = int(position - _photoIds.begin()); auto haveEqualOrAfter = int(_photoIds.end() - position); auto before = qMin(haveBefore, query.limitBefore); auto equalOrAfter = qMin(haveEqualOrAfter, query.limitAfter + 1); result.photoIds = std::deque<PhotoId>( position - before, position + equalOrAfter); auto skippedInIds = (haveBefore - before); result.skippedBefore = _count | func::add(-int(_photoIds.size()) + skippedInIds); result.skippedBefore = haveBefore - before; result.skippedAfter = (haveEqualOrAfter - equalOrAfter); consumer.put_next(std::move(result)); } else if (_count) { consumer.put_next(std::move(result)); } consumer.put_done(); return rpl::lifetime(); }; } auto UserPhotos::List::sliceUpdated() const -> rpl::producer<SliceUpdate> { return _sliceUpdated.events(); } rpl::producer<UserPhotosSliceUpdate> UserPhotos::sliceUpdated() const { return _sliceUpdated.events(); } std::map<UserId, UserPhotos::List>::iterator UserPhotos::enforceLists(UserId user) { auto result = _lists.find(user); if (result != _lists.end()) { return result; } result = _lists.emplace(user, List {}).first; result->second.sliceUpdated( ) | rpl::start_with_next([this, user]( const SliceUpdate &update) { _sliceUpdated.fire(UserPhotosSliceUpdate( user, update.photoIds, update.count)); }, _lifetime); return result; } void UserPhotos::add(UserPhotosAddNew &&query) { auto userIt = enforceLists(query.userId); userIt->second.addNew(query.photoId); } void UserPhotos::add(UserPhotosAddSlice &&query) { auto userIt = enforceLists(query.userId); userIt->second.addSlice( std::move(query.photoIds), query.count); } void UserPhotos::remove(UserPhotosRemoveOne &&query) { auto userIt = _lists.find(query.userId); if (userIt != _lists.end()) { userIt->second.removeOne(query.photoId); } } void UserPhotos::remove(UserPhotosRemoveAfter &&query) { auto userIt = _lists.find(query.userId); if (userIt != _lists.end()) { userIt->second.removeAfter(query.photoId); } } rpl::producer<UserPhotosResult> UserPhotos::query(UserPhotosQuery &&query) const { auto userIt = _lists.find(query.key.userId); if (userIt != _lists.end()) { return userIt->second.query(std::move(query)); } return [](auto consumer) { consumer.put_done(); return rpl::lifetime(); }; } } // namespace Storage ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/storage/storage_user_photos.h����������������������������������0000664�0000000�0000000�00000006447�13262451251�0025351�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/event_stream.h> #include "storage/storage_facade.h" namespace Storage { struct UserPhotosAddNew { UserPhotosAddNew(UserId userId, PhotoId photoId) : userId(userId), photoId(photoId) { } UserId userId = 0; PhotoId photoId = 0; }; struct UserPhotosAddSlice { UserPhotosAddSlice( UserId userId, std::vector<PhotoId> &&photoIds, int count) : userId(userId) , photoIds(std::move(photoIds)) , count(count) { } UserId userId = 0; std::vector<PhotoId> photoIds; int count = 0; }; struct UserPhotosRemoveOne { UserPhotosRemoveOne( UserId userId, PhotoId photoId) : userId(userId) , photoId(photoId) { } UserId userId = 0; PhotoId photoId = 0; }; struct UserPhotosRemoveAfter { UserPhotosRemoveAfter( UserId userId, PhotoId photoId) : userId(userId) , photoId(photoId) { } UserId userId = 0; PhotoId photoId = 0; }; struct UserPhotosKey { UserPhotosKey( UserId userId, PhotoId photoId) : userId(userId) , photoId(photoId) { } bool operator==(const UserPhotosKey &other) const { return (userId == other.userId) && (photoId == other.photoId); } bool operator!=(const UserPhotosKey &other) const { return !(*this == other); } PeerId userId = 0; PhotoId photoId = 0; }; struct UserPhotosQuery { UserPhotosQuery( UserPhotosKey key, int limitBefore, int limitAfter) : key(key) , limitBefore(limitBefore) , limitAfter(limitAfter) { } UserPhotosKey key; int limitBefore = 0; int limitAfter = 0; }; struct UserPhotosResult { base::optional<int> count; base::optional<int> skippedBefore; int skippedAfter = 0; std::deque<PhotoId> photoIds; }; struct UserPhotosSliceUpdate { UserPhotosSliceUpdate( UserId userId, const std::deque<PhotoId> *photoIds, base::optional<int> count) : userId(userId) , photoIds(photoIds) , count(count) { } UserId userId = 0; const std::deque<PhotoId> *photoIds = nullptr; base::optional<int> count; }; class UserPhotos { public: void add(UserPhotosAddNew &&query); void add(UserPhotosAddSlice &&query); void remove(UserPhotosRemoveOne &&query); void remove(UserPhotosRemoveAfter &&query); rpl::producer<UserPhotosResult> query(UserPhotosQuery &&query) const; rpl::producer<UserPhotosSliceUpdate> sliceUpdated() const; private: class List { public: void addNew(PhotoId photoId); void addSlice( std::vector<PhotoId> &&photoIds, int count); void removeOne(PhotoId photoId); void removeAfter(PhotoId photoId); rpl::producer<UserPhotosResult> query(UserPhotosQuery &&query) const; struct SliceUpdate { const std::deque<PhotoId> *photoIds = nullptr; base::optional<int> count; }; rpl::producer<SliceUpdate> sliceUpdated() const; private: void sendUpdate(); base::optional<int> _count; std::deque<PhotoId> _photoIds; rpl::event_stream<SliceUpdate> _sliceUpdated; }; using SliceUpdate = List::SliceUpdate; std::map<UserId, List>::iterator enforceLists(UserId user); std::map<UserId, List> _lists; rpl::event_stream<UserPhotosSliceUpdate> _sliceUpdated; rpl::lifetime _lifetime; }; } // namespace Storage �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/������������������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0020040�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/abstract_button.cpp�����������������������������������������0000664�0000000�0000000�00000006752�13262451251�0023754�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/abstract_button.h" #include <rpl/filter.h> #include <rpl/mappers.h> #include "messenger.h" namespace Ui { AbstractButton::AbstractButton(QWidget *parent) : RpWidget(parent) { setMouseTracking(true); using namespace rpl::mappers; shownValue() | rpl::filter(_1 == false) | rpl::start_with_next([this] { clearState(); }, lifetime()); } void AbstractButton::leaveEventHook(QEvent *e) { if (_state & StateFlag::Down) return; setOver(false, StateChangeSource::ByHover); return TWidget::leaveEventHook(e); } void AbstractButton::enterEventHook(QEvent *e) { checkIfOver(mapFromGlobal(QCursor::pos())); return TWidget::enterEventHook(e); } void AbstractButton::setAcceptBoth(bool acceptBoth) { _acceptBoth = acceptBoth; } void AbstractButton::checkIfOver(QPoint localPos) { auto over = rect().marginsRemoved(getMargins()).contains(localPos); setOver(over, StateChangeSource::ByHover); } void AbstractButton::mousePressEvent(QMouseEvent *e) { checkIfOver(e->pos()); if (_acceptBoth || (e->buttons() & Qt::LeftButton)) { if ((_state & StateFlag::Over) && !(_state & StateFlag::Down)) { auto was = _state; _state |= StateFlag::Down; onStateChanged(was, StateChangeSource::ByPress); e->accept(); } } } void AbstractButton::mouseMoveEvent(QMouseEvent *e) { if (rect().marginsRemoved(getMargins()).contains(e->pos())) { setOver(true, StateChangeSource::ByHover); } else { setOver(false, StateChangeSource::ByHover); } } void AbstractButton::mouseReleaseEvent(QMouseEvent *e) { if (_state & StateFlag::Down) { auto was = _state; _state &= ~State(StateFlag::Down); onStateChanged(was, StateChangeSource::ByPress); if (was & StateFlag::Over) { _modifiers = e->modifiers(); auto weak = make_weak(this); if (_clickedCallback) { _clickedCallback(); } else { emit clicked(); } if (weak) { _clicks.fire({}); } } else { setOver(false, StateChangeSource::ByHover); } } } void AbstractButton::setPointerCursor(bool enablePointerCursor) { if (_enablePointerCursor != enablePointerCursor) { _enablePointerCursor = enablePointerCursor; updateCursor(); } } void AbstractButton::setOver(bool over, StateChangeSource source) { if (over && !(_state & StateFlag::Over)) { auto was = _state; _state |= StateFlag::Over; Messenger::Instance().registerLeaveSubscription(this); onStateChanged(was, source); } else if (!over && (_state & StateFlag::Over)) { auto was = _state; _state &= ~State(StateFlag::Over); Messenger::Instance().unregisterLeaveSubscription(this); onStateChanged(was, source); } updateCursor(); } void AbstractButton::updateCursor() { auto pointerCursor = _enablePointerCursor && (_state & StateFlag::Over); setCursor(pointerCursor ? style::cur_pointer : style::cur_default); } void AbstractButton::setDisabled(bool disabled) { auto was = _state; if (disabled && !(_state & StateFlag::Disabled)) { _state |= StateFlag::Disabled; onStateChanged(was, StateChangeSource::ByUser); } else if (!disabled && (_state & StateFlag::Disabled)) { _state &= ~State(StateFlag::Disabled); onStateChanged(was, StateChangeSource::ByUser); } } void AbstractButton::clearState() { auto was = _state; _state = StateFlag::None; onStateChanged(was, StateChangeSource::ByUser); } } // namespace Ui ����������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/abstract_button.h�������������������������������������������0000664�0000000�0000000�00000004344�13262451251�0023414�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/event_stream.h> #include "ui/rp_widget.h" #include "base/flags.h" namespace Ui { class AbstractButton : public RpWidget { Q_OBJECT public: AbstractButton(QWidget *parent); Qt::KeyboardModifiers clickModifiers() const { return _modifiers; } void setDisabled(bool disabled = true); virtual void clearState(); bool isOver() const { return _state & StateFlag::Over; } bool isDown() const { return _state & StateFlag::Down; } bool isDisabled() const { return _state & StateFlag::Disabled; } void setPointerCursor(bool enablePointerCursor); void setAcceptBoth(bool acceptBoth = true); void setClickedCallback(base::lambda<void()> callback) { _clickedCallback = std::move(callback); } auto clicks() const { return _clicks.events(); } template <typename Handler> void addClickHandler(Handler &&handler) { clicks( ) | rpl::start_with_next( std::forward<Handler>(handler), lifetime()); } protected: void enterEventHook(QEvent *e) override; void leaveEventHook(QEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; signals: void clicked(); protected: enum class StateFlag { None = 0, Over = (1 << 0), Down = (1 << 1), Disabled = (1 << 2), }; friend constexpr bool is_flag_type(StateFlag) { return true; }; using State = base::flags<StateFlag>; State state() const { return _state; } enum class StateChangeSource { ByUser = 0x00, ByPress = 0x01, ByHover = 0x02, }; void setOver(bool over, StateChangeSource source = StateChangeSource::ByUser); virtual void onStateChanged(State was, StateChangeSource source) { } private: void updateCursor(); void checkIfOver(QPoint localPos); State _state = StateFlag::None; bool _acceptBoth = false; Qt::KeyboardModifiers _modifiers; bool _enablePointerCursor = true; base::lambda<void()> _clickedCallback; rpl::event_stream<> _clicks; }; } // namespace Ui ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/animation.cpp�����������������������������������������������0000664�0000000�0000000�00000010132�13262451251�0022520�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "animation.h" #include "media/media_clip_reader.h" namespace Media { namespace Clip { Reader *const ReaderPointer::BadPointer = SharedMemoryLocation<Reader, 0>(); ReaderPointer::~ReaderPointer() { if (valid()) { delete _pointer; } _pointer = nullptr; } } // namespace Clip } // namespace Media namespace { AnimationManager *_manager = nullptr; bool AnimationsDisabled = false; } // namespace namespace anim { transition linear = [](const float64 &delta, const float64 &dt) { return delta * dt; }; transition sineInOut = [](const float64 &delta, const float64 &dt) { return -(delta / 2) * (cos(M_PI * dt) - 1); }; transition halfSine = [](const float64 &delta, const float64 &dt) { return delta * sin(M_PI * dt / 2); }; transition easeOutBack = [](const float64 &delta, const float64 &dt) { static constexpr auto s = 1.70158; const float64 t = dt - 1; return delta * (t * t * ((s + 1) * t + s) + 1); }; transition easeInCirc = [](const float64 &delta, const float64 &dt) { return -delta * (sqrt(1 - dt * dt) - 1); }; transition easeOutCirc = [](const float64 &delta, const float64 &dt) { const float64 t = dt - 1; return delta * sqrt(1 - t * t); }; transition easeInCubic = [](const float64 &delta, const float64 &dt) { return delta * dt * dt * dt; }; transition easeOutCubic = [](const float64 &delta, const float64 &dt) { const float64 t = dt - 1; return delta * (t * t * t + 1); }; transition easeInQuint = [](const float64 &delta, const float64 &dt) { const float64 t2 = dt * dt; return delta * t2 * t2 * dt; }; transition easeOutQuint = [](const float64 &delta, const float64 &dt) { const float64 t = dt - 1, t2 = t * t; return delta * (t2 * t2 * t + 1); }; void startManager() { stopManager(); _manager = new AnimationManager(); } void stopManager() { delete _manager; _manager = nullptr; Media::Clip::Finish(); } void registerClipManager(Media::Clip::Manager *manager) { manager->connect(manager, SIGNAL(callback(Media::Clip::Reader*,qint32,qint32)), _manager, SLOT(clipCallback(Media::Clip::Reader*,qint32,qint32))); } bool Disabled() { return AnimationsDisabled; } void SetDisabled(bool disabled) { AnimationsDisabled = disabled; if (disabled && _manager) { _manager->timeout(); } } } // anim void BasicAnimation::start() { if (!_manager) return; _callbacks.start(); _manager->start(this); _animating = true; } void BasicAnimation::stop() { if (!_manager) return; _animating = false; _manager->stop(this); } AnimationManager::AnimationManager() : _timer(this), _iterating(false) { _timer.setSingleShot(false); connect(&_timer, SIGNAL(timeout()), this, SLOT(timeout())); } void AnimationManager::start(BasicAnimation *obj) { if (_iterating) { _starting.insert(obj); if (!_stopping.isEmpty()) { _stopping.remove(obj); } } else { if (_objects.isEmpty()) { _timer.start(AnimationTimerDelta); } _objects.insert(obj); } } void AnimationManager::stop(BasicAnimation *obj) { if (_iterating) { _stopping.insert(obj); if (!_starting.isEmpty()) { _starting.remove(obj); } } else { auto i = _objects.find(obj); if (i != _objects.cend()) { _objects.erase(i); if (_objects.empty()) { _timer.stop(); } } } } void AnimationManager::timeout() { _iterating = true; auto ms = getms(); for_const (auto object, _objects) { if (!_stopping.contains(object)) { object->step(ms, true); } } _iterating = false; if (!_starting.isEmpty()) { for_const (auto object, _starting) { _objects.insert(object); } _starting.clear(); } if (!_stopping.isEmpty()) { for_const (auto object, _stopping) { _objects.remove(object); } _stopping.clear(); } if (_objects.empty()) { _timer.stop(); } } void AnimationManager::clipCallback(Media::Clip::Reader *reader, qint32 threadIndex, qint32 notification) { Media::Clip::Reader::callback(reader, threadIndex, Media::Clip::Notification(notification)); } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/animation.h�������������������������������������������������0000664�0000000�0000000�00000044025�13262451251�0022175�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "core/basic_types.h" #include <QtCore/QTimer> #include <QtGui/QColor> namespace Media { namespace Clip { class Reader; class ReaderPointer { public: ReaderPointer(std::nullptr_t = nullptr) { } explicit ReaderPointer(Reader *pointer) : _pointer(pointer) { } ReaderPointer(const ReaderPointer &other) = delete; ReaderPointer &operator=(const ReaderPointer &other) = delete; ReaderPointer(ReaderPointer &&other) : _pointer(base::take(other._pointer)) { } ReaderPointer &operator=(ReaderPointer &&other) { swap(other); return *this; } void swap(ReaderPointer &other) { qSwap(_pointer, other._pointer); } Reader *get() const { return valid() ? _pointer : nullptr; } Reader *operator->() const { return get(); } void setBad() { reset(); _pointer = BadPointer; } void reset() { ReaderPointer temp; swap(temp); } bool isBad() const { return (_pointer == BadPointer); } bool valid() const { return _pointer && !isBad(); } explicit operator bool() const { return valid(); } static inline ReaderPointer Bad() { ReaderPointer result; result.setBad(); return result; } ~ReaderPointer(); private: Reader *_pointer = nullptr; static Reader *const BadPointer; }; class Manager; enum Notification { NotificationReinit, NotificationRepaint, }; } // namespace Clip } // namespace Media namespace anim { enum class type { normal, instant, }; enum class activation { normal, background, }; using transition = base::lambda<float64(float64 delta, float64 dt)>; extern transition linear; extern transition sineInOut; extern transition halfSine; extern transition easeOutBack; extern transition easeInCirc; extern transition easeOutCirc; extern transition easeInCubic; extern transition easeOutCubic; extern transition easeInQuint; extern transition easeOutQuint; inline transition bumpy(float64 bump) { auto dt0 = (bump - sqrt(bump * (bump - 1.))); auto k = (1 / (2 * dt0 - 1)); return [bump, dt0, k](float64 delta, float64 dt) { return delta * (bump - k * (dt - dt0) * (dt - dt0)); }; } // Basic animated value. class value { public: using ValueType = float64; value() = default; value(float64 from) : _cur(from), _from(from) { } value(float64 from, float64 to) : _cur(from), _from(from), _delta(to - from) { } void start(float64 to) { _from = _cur; _delta = to - _from; } void restart() { _delta = _from + _delta - _cur; _from = _cur; } float64 from() const { return _from; } float64 current() const { return _cur; } float64 to() const { return _from + _delta; } void add(float64 delta) { _from += delta; _cur += delta; } value &update(float64 dt, transition func) { _cur = _from + func(_delta, dt); return *this; } void finish() { _cur = _from + _delta; _from = _cur; _delta = 0; } private: float64 _cur = 0.; float64 _from = 0.; float64 _delta = 0.; }; void startManager(); void stopManager(); void registerClipManager(Media::Clip::Manager *manager); FORCE_INLINE int interpolate(int a, int b, float64 b_ratio) { return qRound(a + float64(b - a) * b_ratio); } #ifdef ARCH_CPU_32_BITS #define SHIFTED_USE_32BIT #endif // ARCH_CPU_32_BITS #ifdef SHIFTED_USE_32BIT using ShiftedMultiplier = uint32; struct Shifted { Shifted() = default; Shifted(uint32 low, uint32 high) : low(low), high(high) { } uint32 low = 0; uint32 high = 0; }; FORCE_INLINE Shifted operator+(Shifted a, Shifted b) { return Shifted(a.low + b.low, a.high + b.high); } FORCE_INLINE Shifted operator*(Shifted shifted, ShiftedMultiplier multiplier) { return Shifted(shifted.low * multiplier, shifted.high * multiplier); } FORCE_INLINE Shifted operator*(ShiftedMultiplier multiplier, Shifted shifted) { return Shifted(shifted.low * multiplier, shifted.high * multiplier); } FORCE_INLINE Shifted shifted(uint32 components) { return Shifted( (components & 0x000000FFU) | ((components & 0x0000FF00U) << 8), ((components & 0x00FF0000U) >> 16) | ((components & 0xFF000000U) >> 8)); } FORCE_INLINE uint32 unshifted(Shifted components) { return ((components.low & 0x0000FF00U) >> 8) | ((components.low & 0xFF000000U) >> 16) | ((components.high & 0x0000FF00U) << 8) | (components.high & 0xFF000000U); } FORCE_INLINE Shifted reshifted(Shifted components) { return Shifted((components.low >> 8) & 0x00FF00FFU, (components.high >> 8) & 0x00FF00FFU); } FORCE_INLINE Shifted shifted(QColor color) { // Make it premultiplied. auto alpha = static_cast<uint32>((color.alpha() & 0xFF) + 1); auto components = Shifted(static_cast<uint32>(color.blue() & 0xFF) | (static_cast<uint32>(color.green() & 0xFF) << 16), static_cast<uint32>(color.red() & 0xFF) | (static_cast<uint32>(255) << 16)); return reshifted(components * alpha); } FORCE_INLINE uint32 getPremultiplied(QColor color) { // Make it premultiplied. auto alpha = static_cast<uint32>((color.alpha() & 0xFF) + 1); auto components = Shifted(static_cast<uint32>(color.blue() & 0xFF) | (static_cast<uint32>(color.green() & 0xFF) << 16), static_cast<uint32>(color.red() & 0xFF) | (static_cast<uint32>(255) << 16)); return unshifted(components * alpha); } FORCE_INLINE uint32 getAlpha(Shifted components) { return (components.high & 0x00FF0000U) >> 16; } FORCE_INLINE Shifted non_premultiplied(QColor color) { return Shifted(static_cast<uint32>(color.blue() & 0xFF) | (static_cast<uint32>(color.green() & 0xFF) << 16), static_cast<uint32>(color.red() & 0xFF) | (static_cast<uint32>(color.alpha() & 0xFF) << 16)); } FORCE_INLINE QColor color(QColor a, QColor b, float64 b_ratio) { auto bOpacity = snap(interpolate(0, 255, b_ratio), 0, 255) + 1; auto aOpacity = (256 - bOpacity); auto components = (non_premultiplied(a) * aOpacity + non_premultiplied(b) * bOpacity); return { static_cast<int>((components.high >> 8) & 0xFF), static_cast<int>((components.low >> 24) & 0xFF), static_cast<int>((components.low >> 8) & 0xFF), static_cast<int>((components.high >> 24) & 0xFF), }; } #else // SHIFTED_USE_32BIT using ShiftedMultiplier = uint64; struct Shifted { Shifted() = default; Shifted(uint32 value) : value(value) { } Shifted(uint64 value) : value(value) { } uint64 value = 0; }; FORCE_INLINE Shifted operator+(Shifted a, Shifted b) { return Shifted(a.value + b.value); } FORCE_INLINE Shifted operator*(Shifted shifted, ShiftedMultiplier multiplier) { return Shifted(shifted.value * multiplier); } FORCE_INLINE Shifted operator*(ShiftedMultiplier multiplier, Shifted shifted) { return Shifted(shifted.value * multiplier); } FORCE_INLINE Shifted shifted(uint32 components) { auto wide = static_cast<uint64>(components); return (wide & 0x00000000000000FFULL) | ((wide & 0x000000000000FF00ULL) << 8) | ((wide & 0x0000000000FF0000ULL) << 16) | ((wide & 0x00000000FF000000ULL) << 24); } FORCE_INLINE uint32 unshifted(Shifted components) { return static_cast<uint32>((components.value & 0x000000000000FF00ULL) >> 8) | static_cast<uint32>((components.value & 0x00000000FF000000ULL) >> 16) | static_cast<uint32>((components.value & 0x0000FF0000000000ULL) >> 24) | static_cast<uint32>((components.value & 0xFF00000000000000ULL) >> 32); } FORCE_INLINE Shifted reshifted(Shifted components) { return (components.value >> 8) & 0x00FF00FF00FF00FFULL; } FORCE_INLINE Shifted shifted(QColor color) { // Make it premultiplied. auto alpha = static_cast<uint64>((color.alpha() & 0xFF) + 1); auto components = static_cast<uint64>(color.blue() & 0xFF) | (static_cast<uint64>(color.green() & 0xFF) << 16) | (static_cast<uint64>(color.red() & 0xFF) << 32) | (static_cast<uint64>(255) << 48); return reshifted(components * alpha); } FORCE_INLINE uint32 getPremultiplied(QColor color) { // Make it premultiplied. auto alpha = static_cast<uint64>((color.alpha() & 0xFF) + 1); auto components = static_cast<uint64>(color.blue() & 0xFF) | (static_cast<uint64>(color.green() & 0xFF) << 16) | (static_cast<uint64>(color.red() & 0xFF) << 32) | (static_cast<uint64>(255) << 48); return unshifted(components * alpha); } FORCE_INLINE uint32 getAlpha(Shifted components) { return (components.value & 0x00FF000000000000ULL) >> 48; } FORCE_INLINE Shifted non_premultiplied(QColor color) { return static_cast<uint64>(color.blue() & 0xFF) | (static_cast<uint64>(color.green() & 0xFF) << 16) | (static_cast<uint64>(color.red() & 0xFF) << 32) | (static_cast<uint64>(color.alpha() & 0xFF) << 48); } FORCE_INLINE QColor color(QColor a, QColor b, float64 b_ratio) { auto bOpacity = snap(interpolate(0, 255, b_ratio), 0, 255) + 1; auto aOpacity = (256 - bOpacity); auto components = (non_premultiplied(a) * aOpacity + non_premultiplied(b) * bOpacity); return { static_cast<int>((components.value >> 40) & 0xFF), static_cast<int>((components.value >> 24) & 0xFF), static_cast<int>((components.value >> 8) & 0xFF), static_cast<int>((components.value >> 56) & 0xFF), }; } #endif // SHIFTED_USE_32BIT FORCE_INLINE QColor color(style::color a, QColor b, float64 b_ratio) { return color(a->c, b, b_ratio); } FORCE_INLINE QColor color(QColor a, style::color b, float64 b_ratio) { return color(a, b->c, b_ratio); } FORCE_INLINE QColor color(style::color a, style::color b, float64 b_ratio) { return color(a->c, b->c, b_ratio); } FORCE_INLINE QPen pen(QColor a, QColor b, float64 b_ratio) { return color(a, b, b_ratio); } FORCE_INLINE QPen pen(style::color a, QColor b, float64 b_ratio) { return (b_ratio > 0) ? pen(a->c, b, b_ratio) : a; } FORCE_INLINE QPen pen(QColor a, style::color b, float64 b_ratio) { return (b_ratio < 1) ? pen(a, b->c, b_ratio) : b; } FORCE_INLINE QPen pen(style::color a, style::color b, float64 b_ratio) { return (b_ratio > 0) ? ((b_ratio < 1) ? pen(a->c, b->c, b_ratio) : b) : a; } FORCE_INLINE QBrush brush(QColor a, QColor b, float64 b_ratio) { return color(a, b, b_ratio); } FORCE_INLINE QBrush brush(style::color a, QColor b, float64 b_ratio) { return (b_ratio > 0) ? brush(a->c, b, b_ratio) : a; } FORCE_INLINE QBrush brush(QColor a, style::color b, float64 b_ratio) { return (b_ratio < 1) ? brush(a, b->c, b_ratio) : b; } FORCE_INLINE QBrush brush(style::color a, style::color b, float64 b_ratio) { return (b_ratio > 0) ? ((b_ratio < 1) ? brush(a->c, b->c, b_ratio) : b) : a; } template <int N> QPainterPath interpolate(QPointF (&from)[N], QPointF (&to)[N], float64 k) { static_assert(N > 1, "Wrong points count in path!"); auto from_coef = 1. - k, to_coef = k; QPainterPath result; auto x = from[0].x() * from_coef + to[0].x() * to_coef; auto y = from[0].y() * from_coef + to[0].y() * to_coef; result.moveTo(x, y); for (int i = 1; i != N; ++i) { result.lineTo(from[i].x() * from_coef + to[i].x() * to_coef, from[i].y() * from_coef + to[i].y() * to_coef); } result.lineTo(x, y); return result; } template <int N> QPainterPath path(QPointF (&from)[N]) { static_assert(N > 1, "Wrong points count in path!"); QPainterPath result; auto x = from[0].x(); auto y = from[0].y(); result.moveTo(x, y); for (int i = 1; i != N; ++i) { result.lineTo(from[i].x(), from[i].y()); } result.lineTo(x, y); return result; } bool Disabled(); void SetDisabled(bool disabled); }; class BasicAnimation; class AnimationImplementation { public: virtual void start() {} virtual void step(BasicAnimation *a, TimeMs ms, bool timer) = 0; virtual ~AnimationImplementation() {} }; class AnimationCallbacks { public: AnimationCallbacks(AnimationImplementation *implementation) : _implementation(implementation) {} AnimationCallbacks(const AnimationCallbacks &other) = delete; AnimationCallbacks &operator=(const AnimationCallbacks &other) = delete; AnimationCallbacks(AnimationCallbacks &&other) : _implementation(other._implementation) { other._implementation = nullptr; } AnimationCallbacks &operator=(AnimationCallbacks &&other) { std::swap(_implementation, other._implementation); return *this; } void start() { _implementation->start(); } void step(BasicAnimation *a, TimeMs ms, bool timer) { _implementation->step(a, ms, timer); } ~AnimationCallbacks() { delete base::take(_implementation); } private: AnimationImplementation *_implementation; }; class BasicAnimation { public: BasicAnimation(AnimationCallbacks &&callbacks) : _callbacks(std::move(callbacks)) , _animating(false) { } void start(); void stop(); void step(TimeMs ms, bool timer = false) { _callbacks.step(this, ms, timer); } void step() { step(getms(), false); } bool animating() const { return _animating; } ~BasicAnimation() { if (_animating) stop(); } private: AnimationCallbacks _callbacks; bool _animating; }; template <typename Type> class AnimationCallbacksRelative : public AnimationImplementation { public: typedef void (Type::*Method)(float64, bool); AnimationCallbacksRelative(Type *obj, Method method) : _obj(obj), _method(method) { } void start() { _started = getms(); } void step(BasicAnimation *a, TimeMs ms, bool timer) { (_obj->*_method)(qMax(ms - _started, TimeMs(0)), timer); } private: TimeMs _started = 0; Type *_obj = nullptr; Method _method = nullptr; }; template <typename Type> AnimationCallbacks animation(Type *obj, typename AnimationCallbacksRelative<Type>::Method method) { return AnimationCallbacks(new AnimationCallbacksRelative<Type>(obj, method)); } template <typename Type> class AnimationCallbacksAbsolute : public AnimationImplementation { public: typedef void (Type::*Method)(TimeMs, bool); AnimationCallbacksAbsolute(Type *obj, Method method) : _obj(obj), _method(method) { } void step(BasicAnimation *a, TimeMs ms, bool timer) { (_obj->*_method)(ms, timer); } private: Type *_obj = nullptr; Method _method = nullptr; }; template <typename Type> AnimationCallbacks animation(Type *obj, typename AnimationCallbacksAbsolute<Type>::Method method) { return AnimationCallbacks(new AnimationCallbacksAbsolute<Type>(obj, method)); } template <typename Type, typename Param> class AnimationCallbacksRelativeWithParam : public AnimationImplementation { public: typedef void (Type::*Method)(Param, float64, bool); AnimationCallbacksRelativeWithParam(Param param, Type *obj, Method method) : _param(param), _obj(obj), _method(method) { } void start() { _started = getms(); } void step(BasicAnimation *a, TimeMs ms, bool timer) { (_obj->*_method)(_param, qMax(ms - _started, TimeMs(0)), timer); } private: TimeMs _started = 0; Param _param; Type *_obj = nullptr; Method _method = nullptr; }; template <typename Type, typename Param> AnimationCallbacks animation(Param param, Type *obj, typename AnimationCallbacksRelativeWithParam<Type, Param>::Method method) { return AnimationCallbacks(new AnimationCallbacksRelativeWithParam<Type, Param>(param, obj, method)); } template <typename Type, typename Param> class AnimationCallbacksAbsoluteWithParam : public AnimationImplementation { public: typedef void (Type::*Method)(Param, TimeMs, bool); AnimationCallbacksAbsoluteWithParam(Param param, Type *obj, Method method) : _param(param), _obj(obj), _method(method) { } void step(BasicAnimation *a, TimeMs ms, bool timer) { (_obj->*_method)(_param, ms, timer); } private: Param _param; Type *_obj = nullptr; Method _method = nullptr; }; template <typename Type, typename Param> AnimationCallbacks animation(Param param, Type *obj, typename AnimationCallbacksAbsoluteWithParam<Type, Param>::Method method) { return AnimationCallbacks(new AnimationCallbacksAbsoluteWithParam<Type, Param>(param, obj, method)); } class Animation { public: void step(TimeMs ms) { if (_data) { _data->a_animation.step(ms); if (_data && !_data->a_animation.animating()) { _data.reset(); } } } bool animating() const { if (_data) { if (_data->a_animation.animating()) { return true; } _data.reset(); } return false; } bool animating(TimeMs ms) { step(ms); return animating(); } float64 current() const { Assert(_data != nullptr); return _data->value.current(); } float64 current(float64 def) const { return animating() ? current() : def; } float64 current(TimeMs ms, float64 def) { return animating(ms) ? current() : def; } static constexpr auto kLongAnimationDuration = 1000; template <typename Lambda> void start(Lambda &&updateCallback, float64 from, float64 to, float64 duration, anim::transition transition = anim::linear) { auto isLong = (duration >= kLongAnimationDuration); if (_data) { if (!isLong) { _data->pause.restart(); } } else { _data = std::make_unique<Data>(from, std::forward<Lambda>(updateCallback)); } if (isLong) { _data->pause.release(); } _data->value.start(to); _data->duration = duration; _data->transition = transition; _data->a_animation.start(); } void finish() { if (_data) { _data->value.finish(); _data->a_animation.stop(); _data.reset(); } } template <typename Lambda> void setUpdateCallback(Lambda &&updateCallback) { if (_data) { _data->updateCallback = std::forward<Lambda>(updateCallback); } } private: struct Data { template <typename Lambda> Data(float64 from, Lambda updateCallback) : value(from, from) , a_animation(animation(this, &Data::step)) , updateCallback(std::move(updateCallback)) { } void step(float64 ms, bool timer) { auto dt = (ms >= duration || anim::Disabled()) ? 1. : (ms / duration); if (dt >= 1) { value.finish(); a_animation.stop(); pause.release(); } else { value.update(dt, transition); } updateCallback(); } anim::value value; BasicAnimation a_animation; base::lambda<void()> updateCallback; float64 duration = 0.; anim::transition transition = anim::linear; MTP::PauseHolder pause; }; mutable std::unique_ptr<Data> _data; }; class AnimationManager : public QObject { Q_OBJECT public: AnimationManager(); void start(BasicAnimation *obj); void stop(BasicAnimation *obj); public slots: void timeout(); void clipCallback(Media::Clip::Reader *reader, qint32 threadIndex, qint32 notification); private: using AnimatingObjects = OrderedSet<BasicAnimation*>; AnimatingObjects _objects, _starting, _stopping; QTimer _timer; bool _iterating; }; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/countryinput.cpp��������������������������������������������0000664�0000000�0000000�00000036214�13262451251�0023335�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/countryinput.h" #include "lang/lang_keys.h" #include "application.h" #include "ui/widgets/scroll_area.h" #include "ui/widgets/multi_select.h" #include "ui/effects/ripple_animation.h" #include "countries.h" #include "styles/style_boxes.h" #include "styles/style_intro.h" namespace { typedef QList<const CountryInfo *> CountriesFiltered; typedef QVector<int> CountriesIds; typedef QHash<QChar, CountriesIds> CountriesByLetter; typedef QVector<QString> CountryNames; typedef QVector<CountryNames> CountriesNames; CountriesByCode _countriesByCode; CountriesByISO2 _countriesByISO2; CountriesFiltered countriesFiltered, countriesAll, *countriesNow = &countriesAll; CountriesByLetter countriesByLetter; CountriesNames countriesNames; QString lastValidISO; int countriesCount = sizeof(countries) / sizeof(countries[0]); void initCountries() { if (!_countriesByCode.isEmpty()) return; _countriesByCode.reserve(countriesCount); _countriesByISO2.reserve(countriesCount); for (int i = 0; i < countriesCount; ++i) { const CountryInfo *info(countries + i); _countriesByCode.insert(info->code, info); CountriesByISO2::const_iterator already = _countriesByISO2.constFind(info->iso2); if (already != _countriesByISO2.cend()) { QString badISO = info->iso2; (void)badISO; } _countriesByISO2.insert(info->iso2, info); } countriesAll.reserve(countriesCount); countriesFiltered.reserve(countriesCount); countriesNames.resize(countriesCount); } } // namespace const CountriesByCode &countriesByCode() { initCountries(); return _countriesByCode; } const CountriesByISO2 &countriesByISO2() { initCountries(); return _countriesByISO2; } QString findValidCode(QString fullCode) { while (fullCode.length()) { CountriesByCode::const_iterator i = _countriesByCode.constFind(fullCode); if (i != _countriesByCode.cend()) { return (*i)->code; } fullCode = fullCode.mid(0, fullCode.length() - 1); } return ""; } CountryInput::CountryInput(QWidget *parent, const style::InputField &st) : TWidget(parent) , _st(st) , _text(lang(lng_country_code)) { initCountries(); resize(_st.width, _st.heightMin); auto availableWidth = width() - _st.textMargins.left() - _st.textMargins.right() - _st.placeholderMargins.left() - _st.placeholderMargins.right() - 1; auto placeholderFont = _st.placeholderFont->f; placeholderFont.setStyleStrategy(QFont::PreferMatch); auto metrics = QFontMetrics(placeholderFont); auto placeholder = QString();// metrics.elidedText(lang(lng_country_fake_ph), Qt::ElideRight, availableWidth); if (!placeholder.isNull()) { _placeholderPath.addText(0, QFontMetrics(placeholderFont).ascent(), placeholderFont, placeholder); } } void CountryInput::paintEvent(QPaintEvent *e) { Painter p(this); QRect r(rect().intersected(e->rect())); if (_st.textBg->c.alphaF() > 0.) { p.fillRect(r, _st.textBg); } if (_st.border) { p.fillRect(0, height() - _st.border, width(), _st.border, _st.borderFg); } st::introCountryIcon.paint(p, width() - st::introCountryIcon.width() - st::introCountryIconPosition.x(), st::introCountryIconPosition.y(), width()); p.setFont(_st.font); p.setPen(_st.textFg); p.drawText(rect().marginsRemoved(_st.textMargins), _text, _st.textAlign); if (!_placeholderPath.isEmpty()) { auto placeholderShiftDegree = 1.; p.save(); p.setClipRect(r); auto placeholderTop = anim::interpolate(0, _st.placeholderShift, placeholderShiftDegree); QRect r(rect().marginsRemoved(_st.textMargins + _st.placeholderMargins)); r.moveTop(r.top() + placeholderTop); if (rtl()) r.moveLeft(width() - r.left() - r.width()); auto placeholderScale = 1. - (1. - _st.placeholderScale) * placeholderShiftDegree; auto placeholderFg = anim::color(_st.placeholderFg, _st.placeholderFgActive, 0.); placeholderFg = anim::color(placeholderFg, _st.placeholderFgError, 0.); PainterHighQualityEnabler hq(p); p.setPen(Qt::NoPen); p.setBrush(placeholderFg); p.translate(r.topLeft()); p.scale(placeholderScale, placeholderScale); p.drawPath(_placeholderPath); p.restore(); } } void CountryInput::mouseMoveEvent(QMouseEvent *e) { bool newActive = rect().contains(e->pos()); if (_active != newActive) { _active = newActive; setCursor(_active ? style::cur_pointer : style::cur_default); } } void CountryInput::mousePressEvent(QMouseEvent *e) { mouseMoveEvent(e); if (_active) { auto box = Ui::show(Box<CountrySelectBox>()); connect(box, SIGNAL(countryChosen(const QString&)), this, SLOT(onChooseCountry(const QString&))); } } void CountryInput::enterEventHook(QEvent *e) { setMouseTracking(true); } void CountryInput::leaveEventHook(QEvent *e) { setMouseTracking(false); _active = false; setCursor(style::cur_default); } void CountryInput::onChooseCode(const QString &code) { Ui::hideLayer(); if (code.length()) { CountriesByCode::const_iterator i = _countriesByCode.constFind(code); if (i != _countriesByCode.cend()) { const CountryInfo *info = *i; lastValidISO = info->iso2; setText(QString::fromUtf8(info->name)); } else { setText(lang(lng_bad_country_code)); } } else { setText(lang(lng_country_code)); } update(); } bool CountryInput::onChooseCountry(const QString &iso) { Ui::hideLayer(); CountriesByISO2::const_iterator i = _countriesByISO2.constFind(iso); const CountryInfo *info = (i == _countriesByISO2.cend()) ? 0 : (*i); if (info) { lastValidISO = info->iso2; setText(QString::fromUtf8(info->name)); emit codeChanged(info->code); update(); return true; } return false; } void CountryInput::setText(const QString &newText) { _text = _st.font->elided(newText, width() - _st.textMargins.left() - _st.textMargins.right()); } CountrySelectBox::CountrySelectBox(QWidget*) : _select(this, st::contactsMultiSelect, langFactory(lng_country_ph)) { } void CountrySelectBox::prepare() { setTitle(langFactory(lng_country_select)); _select->resizeToWidth(st::boxWidth); _select->setQueryChangedCallback([this](const QString &query) { onFilterUpdate(query); }); _select->setSubmittedCallback([this](bool) { onSubmit(); }); _inner = setInnerWidget(object_ptr<Inner>(this), st::countriesScroll, _select->height()); addButton(langFactory(lng_close), [this] { closeBox(); }); setDimensions(st::boxWidth, st::boxMaxListHeight); connect(_inner, SIGNAL(mustScrollTo(int, int)), this, SLOT(onScrollToY(int, int))); connect(_inner, SIGNAL(countryChosen(const QString&)), this, SIGNAL(countryChosen(const QString&))); } void CountrySelectBox::onSubmit() { _inner->chooseCountry(); } void CountrySelectBox::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Down) { _inner->selectSkip(1); } else if (e->key() == Qt::Key_Up) { _inner->selectSkip(-1); } else if (e->key() == Qt::Key_PageDown) { _inner->selectSkipPage(height() - _select->height(), 1); } else if (e->key() == Qt::Key_PageUp) { _inner->selectSkipPage(height() - _select->height(), -1); } else { BoxContent::keyPressEvent(e); } } void CountrySelectBox::resizeEvent(QResizeEvent *e) { BoxContent::resizeEvent(e); _select->resizeToWidth(width()); _select->moveToLeft(0, 0); _inner->resizeToWidth(width()); } void CountrySelectBox::onFilterUpdate(const QString &query) { onScrollToY(0); _inner->updateFilter(query); } void CountrySelectBox::setInnerFocus() { _select->setInnerFocus(); } CountrySelectBox::Inner::Inner(QWidget *parent) : TWidget(parent) , _rowHeight(st::countryRowHeight) { setAttribute(Qt::WA_OpaquePaintEvent); CountriesByISO2::const_iterator l = _countriesByISO2.constFind(lastValidISO); bool seenLastValid = false; int already = countriesAll.size(); countriesByLetter.clear(); const CountryInfo *lastValid = (l == _countriesByISO2.cend()) ? 0 : (*l); for (int i = 0; i < countriesCount; ++i) { const CountryInfo *ins = lastValid ? (i ? (countries + i - (seenLastValid ? 0 : 1)) : lastValid) : (countries + i); if (lastValid && i && ins == lastValid) { seenLastValid = true; ++ins; } if (already > i) { countriesAll[i] = ins; } else { countriesAll.push_back(ins); } auto namesList = QString::fromUtf8(ins->name).toLower().split(QRegularExpression("[\\s\\-]"), QString::SkipEmptyParts); auto &names = countriesNames[i]; int l = namesList.size(); names.resize(0); names.reserve(l); for (int j = 0, l = namesList.size(); j < l; ++j) { auto name = namesList[j].trimmed(); if (!name.length()) continue; auto ch = name[0]; auto &v = countriesByLetter[ch]; if (v.isEmpty() || v.back() != i) { v.push_back(i); } names.push_back(name); } } _filter = qsl("a"); updateFilter(); } void CountrySelectBox::Inner::paintEvent(QPaintEvent *e) { Painter p(this); QRect r(e->rect()); p.setClipRect(r); auto ms = getms(); int l = countriesNow->size(); if (l) { if (r.intersects(QRect(0, 0, width(), st::countriesSkip))) { p.fillRect(r.intersected(QRect(0, 0, width(), st::countriesSkip)), st::countryRowBg); } int32 from = floorclamp(r.y() - st::countriesSkip, _rowHeight, 0, l); int32 to = ceilclamp(r.y() + r.height() - st::countriesSkip, _rowHeight, 0, l); for (int32 i = from; i < to; ++i) { auto selected = (i == (_pressed >= 0 ? _pressed : _selected)); auto y = st::countriesSkip + i * _rowHeight; p.fillRect(0, y, width(), _rowHeight, selected ? st::countryRowBgOver : st::countryRowBg); if (_ripples.size() > i && _ripples[i]) { _ripples[i]->paint(p, 0, y, width(), ms); if (_ripples[i]->empty()) { _ripples[i].reset(); } } auto code = QString("+") + (*countriesNow)[i]->code; auto codeWidth = st::countryRowCodeFont->width(code); auto name = QString::fromUtf8((*countriesNow)[i]->name); auto nameWidth = st::countryRowNameFont->width(name); auto availWidth = width() - st::countryRowPadding.left() - st::countryRowPadding.right() - codeWidth - st::boxLayerScroll.width; if (nameWidth > availWidth) { name = st::countryRowNameFont->elided(name, availWidth); nameWidth = st::countryRowNameFont->width(name); } p.setFont(st::countryRowNameFont); p.setPen(st::countryRowNameFg); p.drawTextLeft(st::countryRowPadding.left(), y + st::countryRowPadding.top(), width(), name); p.setFont(st::countryRowCodeFont); p.setPen(selected ? st::countryRowCodeFgOver : st::countryRowCodeFg); p.drawTextLeft(st::countryRowPadding.left() + nameWidth + st::countryRowPadding.right(), y + st::countryRowPadding.top(), width(), code); } } else { p.fillRect(r, st::boxBg); p.setFont(st::noContactsFont); p.setPen(st::noContactsColor); p.drawText(QRect(0, 0, width(), st::noContactsHeight), lang(lng_country_none), style::al_center); } } void CountrySelectBox::Inner::enterEventHook(QEvent *e) { setMouseTracking(true); } void CountrySelectBox::Inner::leaveEventHook(QEvent *e) { _mouseSelection = false; setMouseTracking(false); if (_selected >= 0) { updateSelectedRow(); _selected = -1; } } void CountrySelectBox::Inner::mouseMoveEvent(QMouseEvent *e) { _mouseSelection = true; updateSelected(e->pos()); } void CountrySelectBox::Inner::mousePressEvent(QMouseEvent *e) { _mouseSelection = true; updateSelected(e->pos()); setPressed(_selected); if (_pressed >= 0 && _pressed < countriesNow->size()) { if (_ripples.size() <= _pressed) { _ripples.reserve(_pressed + 1); while (_ripples.size() <= _pressed) { _ripples.push_back(nullptr); } } if (!_ripples[_pressed]) { auto mask = Ui::RippleAnimation::rectMask(QSize(width(), _rowHeight)); _ripples[_pressed] = std::make_unique<Ui::RippleAnimation>(st::countryRipple, std::move(mask), [this, index = _pressed] { updateRow(index); }); _ripples[_pressed]->add(e->pos() - QPoint(0, st::countriesSkip + _pressed * _rowHeight)); } } } void CountrySelectBox::Inner::mouseReleaseEvent(QMouseEvent *e) { auto pressed = _pressed; setPressed(-1); updateSelectedRow(); if (e->button() == Qt::LeftButton) { if ((pressed >= 0) && pressed == _selected) { chooseCountry(); } } } void CountrySelectBox::Inner::updateFilter(QString filter) { auto words = TextUtilities::PrepareSearchWords(filter); filter = words.isEmpty() ? QString() : words.join(' '); if (_filter != filter) { _filter = filter; if (_filter.isEmpty()) { countriesNow = &countriesAll; } else { QChar first = _filter[0].toLower(); CountriesIds &ids(countriesByLetter[first]); QStringList::const_iterator fb = words.cbegin(), fe = words.cend(), fi; countriesFiltered.clear(); for (CountriesIds::const_iterator i = ids.cbegin(), e = ids.cend(); i != e; ++i) { int index = *i; CountryNames &names(countriesNames[index]); CountryNames::const_iterator nb = names.cbegin(), ne = names.cend(), ni; for (fi = fb; fi != fe; ++fi) { QString filterName(*fi); for (ni = nb; ni != ne; ++ni) { if (ni->startsWith(*fi)) { break; } } if (ni == ne) { break; } } if (fi == fe) { countriesFiltered.push_back(countriesAll[index]); } } countriesNow = &countriesFiltered; } refresh(); _selected = countriesNow->isEmpty() ? -1 : 0; update(); } } void CountrySelectBox::Inner::selectSkip(int32 dir) { _mouseSelection = false; int cur = (_selected >= 0) ? _selected : -1; cur += dir; if (cur <= 0) { _selected = countriesNow->isEmpty() ? -1 : 0; } else if (cur >= countriesNow->size()) { _selected = -1; } else { _selected = cur; } if (_selected >= 0) { emit mustScrollTo(st::countriesSkip + _selected * _rowHeight, st::countriesSkip + (_selected + 1) * _rowHeight); } update(); } void CountrySelectBox::Inner::selectSkipPage(int32 h, int32 dir) { int32 points = h / _rowHeight; if (!points) return; selectSkip(points * dir); } void CountrySelectBox::Inner::chooseCountry() { QString result; if (_filter.isEmpty()) { if (_selected >= 0 && _selected < countriesAll.size()) { result = countriesAll[_selected]->iso2; } } else { if (_selected >= 0 && _selected < countriesFiltered.size()) { result = countriesFiltered[_selected]->iso2; } } emit countryChosen(result); } void CountrySelectBox::Inner::refresh() { resize(width(), countriesNow->length() ? (countriesNow->length() * _rowHeight + st::countriesSkip) : st::noContactsHeight); } void CountrySelectBox::Inner::updateSelected(QPoint localPos) { if (!_mouseSelection) return; auto in = parentWidget()->rect().contains(parentWidget()->mapFromGlobal(QCursor::pos())); auto selected = (in && localPos.y() >= st::countriesSkip && localPos.y() < st::countriesSkip + countriesNow->size() * _rowHeight) ? ((localPos.y() - st::countriesSkip) / _rowHeight) : -1; if (_selected != selected) { updateSelectedRow(); _selected = selected; updateSelectedRow(); } } void CountrySelectBox::Inner::updateSelectedRow() { updateRow(_selected); } void CountrySelectBox::Inner::updateRow(int index) { if (index >= 0) { update(0, st::countriesSkip + index * _rowHeight, width(), _rowHeight); } } void CountrySelectBox::Inner::setPressed(int pressed) { if (_pressed >= 0 && _pressed < _ripples.size() && _ripples[_pressed]) { _ripples[_pressed]->lastStop(); } _pressed = pressed; } CountrySelectBox::Inner::~Inner() = default; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/countryinput.h����������������������������������������������0000664�0000000�0000000�00000005165�13262451251�0023003�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "boxes/abstract_box.h" #include "styles/style_widgets.h" QString findValidCode(QString fullCode); namespace Ui { class MultiSelect; class RippleAnimation; } // namespace Ui class CountryInput : public TWidget { Q_OBJECT public: CountryInput(QWidget *parent, const style::InputField &st); public slots: void onChooseCode(const QString &code); bool onChooseCountry(const QString &country); signals: void codeChanged(const QString &code); protected: void paintEvent(QPaintEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void enterEventHook(QEvent *e) override; void leaveEventHook(QEvent *e) override; private: void setText(const QString &newText); const style::InputField &_st; bool _active = false; QString _text; QPainterPath _placeholderPath; }; class CountrySelectBox : public BoxContent { Q_OBJECT public: CountrySelectBox(QWidget*); signals: void countryChosen(const QString &iso); protected: void prepare() override; void setInnerFocus() override; void keyPressEvent(QKeyEvent *e) override; void resizeEvent(QResizeEvent *e) override; private slots: void onSubmit(); private: void onFilterUpdate(const QString &query); object_ptr<Ui::MultiSelect> _select; class Inner; QPointer<Inner> _inner; }; // This class is hold in header because it requires Qt preprocessing. class CountrySelectBox::Inner : public TWidget { Q_OBJECT public: Inner(QWidget *parent); void updateFilter(QString filter = QString()); void selectSkip(int32 dir); void selectSkipPage(int32 h, int32 dir); void chooseCountry(); void refresh(); ~Inner(); signals: void countryChosen(const QString &iso); void mustScrollTo(int ymin, int ymax); protected: void paintEvent(QPaintEvent *e) override; void enterEventHook(QEvent *e) override; void leaveEventHook(QEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; private: void updateSelected() { updateSelected(mapFromGlobal(QCursor::pos())); } void updateSelected(QPoint localPos); void updateSelectedRow(); void updateRow(int index); void setPressed(int pressed); int _rowHeight; int _selected = -1; int _pressed = -1; QString _filter; bool _mouseSelection = false; std::vector<std::unique_ptr<Ui::RippleAnimation>> _ripples; }; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/effects/����������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0021457�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/effects/cross_animation.cpp���������������������������������0000664�0000000�0000000�00000013147�13262451251�0025361�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/effects/cross_animation.h" namespace Ui { namespace { constexpr auto kPointCount = 12; // // 1 3 // X X X X // X X X X // 0 X X 4 // X X X X // X 2 X // X X // X X // 11 5 // X X // X X // X 8 X // X X X X // 10 X X 6 // X X X X // X X X X // 9 7 // void transformLoadingCross(float64 loading, std::array<QPointF, kPointCount> &points, int &paintPointsCount) { auto moveTo = [](QPointF &point, QPointF &to, float64 ratio) { point = point * (1. - ratio) + to * ratio; }; auto moveFrom = [](QPointF &point, QPointF &from, float64 ratio) { point = from * (1. - ratio) + point * ratio; }; auto paintPoints = [&points, &paintPointsCount](std::initializer_list<int> &&indices) { auto index = 0; for (auto paintIndex : indices) { points[index++] = points[paintIndex]; } paintPointsCount = indices.size(); }; if (loading < 0.125) { auto ratio = loading / 0.125; moveTo(points[6], points[5], ratio); moveTo(points[7], points[8], ratio); } else if (loading < 0.25) { auto ratio = (loading - 0.125) / 0.125; moveTo(points[9], points[8], ratio); moveTo(points[10], points[11], ratio); paintPoints({ 0, 1, 2, 3, 4, 9, 10, 11 }); } else if (loading < 0.375) { auto ratio = (loading - 0.25) / 0.125; moveTo(points[0], points[11], ratio); moveTo(points[1], points[2], ratio); paintPoints({ 0, 1, 2, 3, 4, 8 }); } else if (loading < 0.5) { auto ratio = (loading - 0.375) / 0.125; moveTo(points[8], points[4], ratio); moveTo(points[11], points[3], ratio); paintPoints({ 3, 4, 8, 11 }); } else if (loading < 0.625) { auto ratio = (loading - 0.5) / 0.125; moveFrom(points[8], points[4], ratio); moveFrom(points[11], points[3], ratio); paintPoints({ 3, 4, 8, 11 }); } else if (loading < 0.75) { auto ratio = (loading - 0.625) / 0.125; moveFrom(points[6], points[5], ratio); moveFrom(points[7], points[8], ratio); paintPoints({ 3, 4, 5, 6, 7, 11 }); } else if (loading < 0.875) { auto ratio = (loading - 0.75) / 0.125; moveFrom(points[9], points[8], ratio); moveFrom(points[10], points[11], ratio); paintPoints({ 3, 4, 5, 6, 7, 8, 9, 10 }); } else { auto ratio = (loading - 0.875) / 0.125; moveFrom(points[0], points[11], ratio); moveFrom(points[1], points[2], ratio); } } } // namespace void CrossAnimation::paint(Painter &p, const style::CrossAnimation &st, style::color color, int x, int y, int outerWidth, float64 shown, float64 loading) { PainterHighQualityEnabler hq(p); auto sqrt2 = sqrt(2.); auto deleteScale = shown + st.minScale * (1. - shown); auto deleteSkip = (deleteScale * st.skip) + (1. - deleteScale) * (st.size / 2); auto deleteLeft = rtlpoint(x + deleteSkip, 0, outerWidth).x() + 0.; auto deleteTop = y + deleteSkip + 0.; auto deleteWidth = st.size - 2 * deleteSkip; auto deleteHeight = st.size - 2 * deleteSkip; auto deleteStroke = st.stroke / sqrt2; std::array<QPointF, kPointCount> pathDelete = { { { deleteLeft, deleteTop + deleteStroke }, { deleteLeft + deleteStroke, deleteTop }, { deleteLeft + (deleteWidth / 2.), deleteTop + (deleteHeight / 2.) - deleteStroke }, { deleteLeft + deleteWidth - deleteStroke, deleteTop }, { deleteLeft + deleteWidth, deleteTop + deleteStroke }, { deleteLeft + (deleteWidth / 2.) + deleteStroke, deleteTop + (deleteHeight / 2.) }, { deleteLeft + deleteWidth, deleteTop + deleteHeight - deleteStroke }, { deleteLeft + deleteWidth - deleteStroke, deleteTop + deleteHeight }, { deleteLeft + (deleteWidth / 2.), deleteTop + (deleteHeight / 2.) + deleteStroke }, { deleteLeft + deleteStroke, deleteTop + deleteHeight }, { deleteLeft, deleteTop + deleteHeight - deleteStroke }, { deleteLeft + (deleteWidth / 2.) - deleteStroke, deleteTop + (deleteHeight / 2.) }, } }; auto pathDeleteSize = kPointCount; auto loadingArcLength = 0; if (loading > 0.) { transformLoadingCross(loading, pathDelete, pathDeleteSize); auto loadingArc = (loading >= 0.5) ? (loading - 1.) : loading; loadingArcLength = qRound(-loadingArc * 2 * FullArcLength); } if (shown < 1.) { auto alpha = -(shown - 1.) * M_PI_2; auto cosalpha = cos(alpha); auto sinalpha = sin(alpha); auto shiftx = deleteLeft + (deleteWidth / 2.); auto shifty = deleteTop + (deleteHeight / 2.); for (auto &point : pathDelete) { auto x = point.x() - shiftx; auto y = point.y() - shifty; point.setX(shiftx + x * cosalpha - y * sinalpha); point.setY(shifty + y * cosalpha + x * sinalpha); } } QPainterPath path; path.moveTo(pathDelete[0]); for (int i = 1; i != pathDeleteSize; ++i) { path.lineTo(pathDelete[i]); } path.lineTo(pathDelete[0]); p.fillPath(path, color); if (loadingArcLength != 0) { auto loadingArcStart = FullArcLength / 8; auto roundSkip = (st.size * (1 - sqrt2) + 2 * sqrt2 * deleteSkip + st.stroke) / 2; auto roundPart = QRectF(x + roundSkip, y + roundSkip, st.size - 2 * roundSkip, st.size - 2 * roundSkip); if (shown < 1.) { loadingArcStart -= qRound(-(shown - 1.) * FullArcLength / 4.); } p.setBrush(Qt::NoBrush); auto pen = color->p; pen.setWidthF(st.stroke); pen.setCapStyle(Qt::RoundCap); p.setPen(pen); if (loadingArcLength < 0) { loadingArcStart += loadingArcLength; loadingArcLength = -loadingArcLength; } p.drawArc(roundPart, loadingArcStart, loadingArcLength); } } } // namespace Ui �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/effects/cross_animation.h�����������������������������������0000664�0000000�0000000�00000000777�13262451251�0025033�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "styles/style_widgets.h" namespace Ui { class CrossAnimation { public: static void paint(Painter &p, const style::CrossAnimation &st, style::color color, int x, int y, int outerWidth, float64 shown, float64 loading = 0.); }; } // namespace Ui �tdesktop-1.2.17/Telegram/SourceFiles/ui/effects/fade_animation.cpp����������������������������������0000664�0000000�0000000�00000007350�13262451251�0025126�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/effects/fade_animation.h" namespace Ui { namespace { constexpr int kWideScale = 5; } // namespace FadeAnimation::FadeAnimation(TWidget *widget, float64 scale) : _widget(widget) , _scale(scale) { } bool FadeAnimation::paint(Painter &p) { if (_cache.isNull()) return false; auto opacity = _animation.current(getms(), _visible ? 1. : 0.); p.setOpacity(opacity); if (_scale < 1.) { PainterHighQualityEnabler hq(p); auto targetRect = QRect( (1 - kWideScale) / 2 * _size.width(), (1 - kWideScale) / 2 * _size.height(), kWideScale * _size.width(), kWideScale * _size.height()); auto scale = opacity + (1. - opacity) * _scale; auto shownWidth = anim::interpolate( (1 - kWideScale) / 2 * _size.width(), 0, scale); auto shownHeight = anim::interpolate( (1 - kWideScale) / 2 * _size.height(), 0, scale); auto margins = QMargins( shownWidth, shownHeight, shownWidth, shownHeight); p.drawPixmap(targetRect.marginsAdded(margins), _cache); } else { p.drawPixmap(0, 0, _cache); } return true; } void FadeAnimation::refreshCache() { if (!_cache.isNull()) { _cache = QPixmap(); _cache = grabContent(); Assert(!_cache.isNull()); } } QPixmap FadeAnimation::grabContent() { SendPendingMoveResizeEvents(_widget); _size = _widget->size(); if (_size.isEmpty()) { auto image = QImage( cIntRetinaFactor(), cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); image.fill(Qt::transparent); return App::pixmapFromImageInPlace(std::move(image)); } auto widgetContent = GrabWidget(_widget); if (_scale < 1.) { auto result = QImage(kWideScale * _size * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); result.setDevicePixelRatio(cRetinaFactor()); result.fill(Qt::transparent); { Painter p(&result); p.drawPixmap((kWideScale - 1) / 2 * _size.width(), (kWideScale - 1) / 2 * _size.height(), widgetContent); } return App::pixmapFromImageInPlace(std::move(result)); } return widgetContent; } void FadeAnimation::setFinishedCallback(FinishedCallback &&callback) { _finishedCallback = std::move(callback); } void FadeAnimation::setUpdatedCallback(UpdatedCallback &&callback) { _updatedCallback = std::move(callback); } void FadeAnimation::show() { if (!_visible) { _visible = true; _widget->showChildren(); } stopAnimation(); } void FadeAnimation::hide() { _visible = false; stopAnimation(); } void FadeAnimation::stopAnimation() { _animation.finish(); if (!_cache.isNull()) { _cache = QPixmap(); if (_visible) { _widget->showChildren(); } if (_finishedCallback) { _finishedCallback(); } } if (_visible == _widget->isHidden()) { _widget->setVisible(_visible); } } void FadeAnimation::fadeIn(int duration) { if (_visible) return; _visible = true; startAnimation(duration); } void FadeAnimation::fadeOut(int duration) { if (!_visible) return; _visible = false; startAnimation(duration); } void FadeAnimation::startAnimation(int duration) { if (_cache.isNull()) { _widget->showChildren(); _cache = grabContent(); Assert(!_cache.isNull()); _widget->hideChildren(); } auto from = _visible ? 0. : 1.; auto to = _visible ? 1. : 0.; _animation.start([this]() { updateCallback(); }, from, to, duration); updateCallback(); if (_widget->isHidden()) { _widget->show(); } } void FadeAnimation::updateCallback() { if (_animation.animating()) { _widget->update(); if (_updatedCallback) { _updatedCallback(_animation.current(_visible ? 1. : 0.)); } } else { stopAnimation(); } } } // namespace Ui ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/effects/fade_animation.h������������������������������������0000664�0000000�0000000�00000002351�13262451251�0024567�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "styles/style_widgets.h" #include "ui/rp_widget.h" namespace Ui { class FadeAnimation { public: FadeAnimation(TWidget *widget, float64 scale = 1.); bool paint(Painter &p); void refreshCache(); using FinishedCallback = base::lambda<void()>; void setFinishedCallback(FinishedCallback &&callback); using UpdatedCallback = base::lambda<void(float64)>; void setUpdatedCallback(UpdatedCallback &&callback); void show(); void hide(); void fadeIn(int duration); void fadeOut(int duration); void finish() { stopAnimation(); } bool animating() const { return _animation.animating(); } bool visible() const { return _visible; } private: void startAnimation(int duration); void stopAnimation(); void updateCallback(); QPixmap grabContent(); TWidget *_widget = nullptr; float64 _scale = 1.; Animation _animation; QSize _size; QPixmap _cache; bool _visible = false; FinishedCallback _finishedCallback; UpdatedCallback _updatedCallback; }; } // namespace Ui ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/effects/numbers_animation.cpp�������������������������������0000664�0000000�0000000�00000015242�13262451251�0025701�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/effects/numbers_animation.h" #include "lang/lang_tag.h" #include "styles/style_widgets.h" namespace Ui { NumbersAnimation::NumbersAnimation( const style::font &font, base::lambda<void()> animationCallback) : _font(font) , _animationCallback(std::move(animationCallback)) { for (auto ch = '0'; ch != '9'; ++ch) { accumulate_max(_digitWidth, _font->m.width(ch)); } } void NumbersAnimation::setText(const QString &text, int value) { if (_a_ready.animating(getms())) { _delayedText = text; _delayedValue = value; } else { realSetText(text, value); } } void NumbersAnimation::animationCallback() { if (_animationCallback) { _animationCallback(); } if (_widthChangedCallback) { _widthChangedCallback(); } if (!_a_ready.animating()) { if (!_delayedText.isEmpty()) { setText(_delayedText, _delayedValue); } } } void NumbersAnimation::realSetText(QString text, int value) { _delayedText = QString(); _delayedValue = 0; _growing = (value > _value); _value = value; auto newSize = text.size(); while (_digits.size() < newSize) { _digits.push_front(Digit()); } while (_digits.size() > newSize && !_digits.front().to.unicode()) { _digits.pop_front(); } auto oldSize = _digits.size(); auto animating = false; for (auto i = 0, size = _digits.size(); i != size; ++i) { auto &digit = _digits[i]; digit.from = digit.to; digit.fromWidth = digit.toWidth; digit.to = (newSize + i < size) ? QChar(0) : text[newSize + i - size]; digit.toWidth = digit.to.unicode() ? _font->m.width(digit.to) : 0; if (digit.from != digit.to) { animating = true; } if (!digit.from.unicode()) { --oldSize; } } _fromWidth = oldSize * _digitWidth; _toWidth = newSize * _digitWidth; if (animating) { _a_ready.start( [this] { animationCallback(); }, 0., 1., st::slideWrapDuration); } } int NumbersAnimation::countWidth() const { return anim::interpolate( _fromWidth, _toWidth, anim::easeOutCirc(1., _a_ready.current(1.))); } int NumbersAnimation::maxWidth() const { return std::max(_fromWidth, _toWidth); } void NumbersAnimation::stepAnimation(TimeMs ms) { _a_ready.step(ms); } void NumbersAnimation::finishAnimating() { auto width = countWidth(); _a_ready.finish(); if (_widthChangedCallback && countWidth() != width) { _widthChangedCallback(); } if (!_delayedText.isEmpty()) { setText(_delayedText, _delayedValue); } } void NumbersAnimation::paint(Painter &p, int x, int y, int outerWidth) { auto digitsCount = _digits.size(); if (!digitsCount) return; auto progress = anim::easeOutCirc(1., _a_ready.current(1.)); auto width = anim::interpolate(_fromWidth, _toWidth, progress); QString singleChar('0'); if (rtl()) x = outerWidth - x - width; x += width - _digits.size() * _digitWidth; auto fromTop = anim::interpolate(0, _font->height, progress) * (_growing ? 1 : -1); auto toTop = anim::interpolate(_font->height, 0, progress) * (_growing ? -1 : 1); for (auto i = 0; i != digitsCount; ++i) { auto &digit = _digits[i]; auto from = digit.from; auto to = digit.to; if (from == to) { p.setOpacity(1.); singleChar[0] = from; p.drawText(x + (_digitWidth - digit.fromWidth) / 2, y + _font->ascent, singleChar); } else { if (from.unicode()) { p.setOpacity(1. - progress); singleChar[0] = from; p.drawText(x + (_digitWidth - digit.fromWidth) / 2, y + fromTop + _font->ascent, singleChar); } if (to.unicode()) { p.setOpacity(progress); singleChar[0] = to; p.drawText(x + (_digitWidth - digit.toWidth) / 2, y + toTop + _font->ascent, singleChar); } } x += _digitWidth; } p.setOpacity(1.); } LabelWithNumbers::LabelWithNumbers( QWidget *parent, const style::FlatLabel &st, int textTop, const StringWithNumbers &value) : RpWidget(parent) , _st(st) , _textTop(textTop) , _before(GetBefore(value)) , _after(GetAfter(value)) , _numbers(_st.style.font, [this] { update(); }) , _beforeWidth(_st.style.font->width(_before)) , _afterWidth(st.style.font->width(_after)) { Expects((value.offset < 0) == (value.length == 0)); const auto numbers = GetNumbers(value); _numbers.setText(numbers, numbers.toInt()); _numbers.finishAnimating(); } QString LabelWithNumbers::GetBefore(const StringWithNumbers &value) { return value.text.mid(0, value.offset); } QString LabelWithNumbers::GetAfter(const StringWithNumbers &value) { return (value.offset >= 0) ? value.text.mid(value.offset + value.length) : QString(); } QString LabelWithNumbers::GetNumbers(const StringWithNumbers &value) { return (value.offset >= 0) ? value.text.mid(value.offset, value.length) : QString(); } void LabelWithNumbers::setValue(const StringWithNumbers &value) { _before = GetBefore(value); _after = GetAfter(value); const auto numbers = GetNumbers(value); _numbers.setText(numbers, numbers.toInt()); const auto oldBeforeWidth = std::exchange( _beforeWidth, _st.style.font->width(_before)); _beforeWidthAnimation.start( [this] { update(); }, oldBeforeWidth, _beforeWidth, st::slideWrapDuration, anim::easeOutCirc); _afterWidth = _st.style.font->width(_after); } void LabelWithNumbers::finishAnimating() { _beforeWidthAnimation.finish(); _numbers.finishAnimating(); update(); } void LabelWithNumbers::paintEvent(QPaintEvent *e) { Painter p(this); const auto ms = getms(); const auto beforeWidth = _beforeWidthAnimation.current(ms, _beforeWidth); _numbers.stepAnimation(ms); p.setFont(_st.style.font); p.setBrush(Qt::NoBrush); p.setPen(_st.textFg); auto left = 0; const auto outerWidth = width(); p.setClipRect(0, 0, left + beforeWidth, height()); p.drawTextLeft(left, _textTop, outerWidth, _before, _beforeWidth); left += beforeWidth; p.setClipping(false); _numbers.paint(p, left, _textTop, outerWidth); left += _numbers.countWidth(); const auto availableWidth = outerWidth - left; const auto text = (availableWidth < _afterWidth) ? _st.style.font->elided(_after, availableWidth) : _after; const auto textWidth = (availableWidth < _afterWidth) ? -1 : _afterWidth; p.drawTextLeft(left, _textTop, outerWidth, text, textWidth); } } // namespace Ui namespace Lang { Ui::StringWithNumbers ReplaceTag<Ui::StringWithNumbers>::Call( Ui::StringWithNumbers &&original, ushort tag, const Ui::StringWithNumbers &replacement) { original.offset = FindTagReplacementPosition(original.text, tag); original.text = ReplaceTag<QString>::Call( std::move(original.text), tag, replacement.text); original.length = replacement.text.size(); return std::move(original); } } // namespace Lang��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/effects/numbers_animation.h���������������������������������0000664�0000000�0000000�00000005200�13262451251�0025337�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/rp_widget.h" namespace style { struct FlatLabel; } // namespace style namespace Ui { class NumbersAnimation { public: NumbersAnimation( const style::font &font, base::lambda<void()> animationCallback); void setWidthChangedCallback(base::lambda<void()> callback) { _widthChangedCallback = std::move(callback); } void setText(const QString &text, int value); void stepAnimation(TimeMs ms); void finishAnimating(); void paint(Painter &p, int x, int y, int outerWidth); int countWidth() const; int maxWidth() const; private: struct Digit { QChar from = 0; QChar to = 0; int fromWidth = 0; int toWidth = 0; }; void animationCallback(); void realSetText(QString text, int value); const style::font &_font; QList<Digit> _digits; int _digitWidth = 0; int _fromWidth = 0; int _toWidth = 0; Animation _a_ready; QString _delayedText; int _delayedValue = 0; int _value = 0; bool _growing = false; base::lambda<void()> _animationCallback; base::lambda<void()> _widthChangedCallback; }; struct StringWithNumbers { QString text; int offset = -1; int length = 0; }; class LabelWithNumbers : public Ui::RpWidget { public: LabelWithNumbers( QWidget *parent, const style::FlatLabel &st, int textTop, const StringWithNumbers &value); void setValue(const StringWithNumbers &value); void finishAnimating(); int naturalWidth() const override { return _beforeWidth + _numbers.maxWidth() + _afterWidth; } protected: void paintEvent(QPaintEvent *e) override; private: static QString GetBefore(const StringWithNumbers &value); static QString GetAfter(const StringWithNumbers &value); static QString GetNumbers(const StringWithNumbers &value); const style::FlatLabel &_st; int _textTop; QString _before; QString _after; NumbersAnimation _numbers; int _beforeWidth = 0; int _afterWidth = 0; Animation _beforeWidthAnimation; }; } // namespace Ui namespace Lang { template <typename ResultString> struct StartReplacements; template <> struct StartReplacements<Ui::StringWithNumbers> { static inline Ui::StringWithNumbers Call(QString &&langString) { return { std::move(langString) }; } }; template <typename ResultString> struct ReplaceTag; template <> struct ReplaceTag<Ui::StringWithNumbers> { static Ui::StringWithNumbers Call( Ui::StringWithNumbers &&original, ushort tag, const Ui::StringWithNumbers &replacement); }; } // namespace Lang ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/effects/panel_animation.cpp���������������������������������0000664�0000000�0000000�00000044607�13262451251�0025334�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/effects/panel_animation.h" namespace Ui { void RoundShadowAnimation::start(int frameWidth, int frameHeight, float64 devicePixelRatio) { Assert(!started()); _frameWidth = frameWidth; _frameHeight = frameHeight; _frame = QImage(_frameWidth, _frameHeight, QImage::Format_ARGB32_Premultiplied); _frame.setDevicePixelRatio(devicePixelRatio); _frameIntsPerLine = (_frame.bytesPerLine() >> 2); _frameInts = reinterpret_cast<uint32*>(_frame.bits()); _frameIntsPerLineAdded = _frameIntsPerLine - _frameWidth; Assert(_frame.depth() == static_cast<int>(sizeof(uint32) << 3)); Assert(_frame.bytesPerLine() == (_frameIntsPerLine << 2)); Assert(_frameIntsPerLineAdded >= 0); } void RoundShadowAnimation::setShadow(const style::Shadow &st) { _shadow.extend = st.extend * cIntRetinaFactor(); _shadow.left = cloneImage(st.left); if (_shadow.valid()) { _shadow.topLeft = cloneImage(st.topLeft); _shadow.top = cloneImage(st.top); _shadow.topRight = cloneImage(st.topRight); _shadow.right = cloneImage(st.right); _shadow.bottomRight = cloneImage(st.bottomRight); _shadow.bottom = cloneImage(st.bottom); _shadow.bottomLeft = cloneImage(st.bottomLeft); Assert(!_shadow.topLeft.isNull() && !_shadow.top.isNull() && !_shadow.topRight.isNull() && !_shadow.right.isNull() && !_shadow.bottomRight.isNull() && !_shadow.bottom.isNull() && !_shadow.bottomLeft.isNull()); } else { _shadow.topLeft = _shadow.top = _shadow.topRight = _shadow.right = _shadow.bottomRight = _shadow.bottom = _shadow.bottomLeft = QImage(); } } void RoundShadowAnimation::setCornerMasks(const QImage &topLeft, const QImage &topRight, const QImage &bottomLeft, const QImage &bottomRight) { setCornerMask(_topLeft, topLeft); setCornerMask(_topRight, topRight); setCornerMask(_bottomLeft, bottomLeft); setCornerMask(_bottomRight, bottomRight); } void RoundShadowAnimation::setCornerMask(Corner &corner, const QImage &image) { Assert(!started()); corner.image = image; if (corner.valid()) { corner.width = corner.image.width(); corner.height = corner.image.height(); corner.bytes = corner.image.constBits(); corner.bytesPerPixel = (corner.image.depth() >> 3); corner.bytesPerLineAdded = corner.image.bytesPerLine() - corner.width * corner.bytesPerPixel; Assert(corner.image.depth() == (corner.bytesPerPixel << 3)); Assert(corner.bytesPerLineAdded >= 0); } else { corner.width = corner.height = 0; } } QImage RoundShadowAnimation::cloneImage(const style::icon &source) { if (source.empty()) return QImage(); auto result = QImage(source.size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); result.setDevicePixelRatio(cRetinaFactor()); result.fill(Qt::transparent); { Painter p(&result); source.paint(p, 0, 0, source.width()); } return result; } void RoundShadowAnimation::paintCorner(Corner &corner, int left, int top) { auto mask = corner.bytes; auto bytesPerPixel = corner.bytesPerPixel; auto bytesPerLineAdded = corner.bytesPerLineAdded; auto frameInts = _frameInts + top * _frameIntsPerLine + left; auto frameIntsPerLineAdd = _frameIntsPerLine - corner.width; for (auto y = 0; y != corner.height; ++y) { for (auto x = 0; x != corner.width; ++x) { auto alpha = static_cast<uint32>(*mask) + 1; *frameInts = anim::unshifted(anim::shifted(*frameInts) * alpha); ++frameInts; mask += bytesPerPixel; } frameInts += frameIntsPerLineAdd; mask += bytesPerLineAdded; } } void RoundShadowAnimation::paintShadow(int left, int top, int right, int bottom) { paintShadowCorner(left, top, _shadow.topLeft); paintShadowCorner(right - _shadow.topRight.width(), top, _shadow.topRight); paintShadowCorner(right - _shadow.bottomRight.width(), bottom - _shadow.bottomRight.height(), _shadow.bottomRight); paintShadowCorner(left, bottom - _shadow.bottomLeft.height(), _shadow.bottomLeft); paintShadowVertical(left, top + _shadow.topLeft.height(), bottom - _shadow.bottomLeft.height(), _shadow.left); paintShadowVertical(right - _shadow.right.width(), top + _shadow.topRight.height(), bottom - _shadow.bottomRight.height(), _shadow.right); paintShadowHorizontal(left + _shadow.topLeft.width(), right - _shadow.topRight.width(), top, _shadow.top); paintShadowHorizontal(left + _shadow.bottomLeft.width(), right - _shadow.bottomRight.width(), bottom - _shadow.bottom.height(), _shadow.bottom); } void RoundShadowAnimation::paintShadowCorner(int left, int top, const QImage &image) { auto imageWidth = image.width(); auto imageHeight = image.height(); auto imageInts = reinterpret_cast<const uint32*>(image.constBits()); auto imageIntsPerLine = (image.bytesPerLine() >> 2); auto imageIntsPerLineAdded = imageIntsPerLine - imageWidth; if (left < 0) { auto shift = -base::take(left); imageWidth -= shift; imageInts += shift; } if (top < 0) { auto shift = -base::take(top); imageHeight -= shift; imageInts += shift * imageIntsPerLine; } if (left + imageWidth > _frameWidth) { imageWidth = _frameWidth - left; } if (top + imageHeight > _frameHeight) { imageHeight = _frameHeight - top; } if (imageWidth < 0 || imageHeight < 0) return; auto frameInts = _frameInts + top * _frameIntsPerLine + left; auto frameIntsPerLineAdd = _frameIntsPerLine - imageWidth; for (auto y = 0; y != imageHeight; ++y) { for (auto x = 0; x != imageWidth; ++x) { auto source = *frameInts; auto shadowAlpha = qMax(_frameAlpha - int(source >> 24), 0); *frameInts = anim::unshifted(anim::shifted(source) * 256 + anim::shifted(*imageInts) * shadowAlpha); ++frameInts; ++imageInts; } frameInts += frameIntsPerLineAdd; imageInts += imageIntsPerLineAdded; } } void RoundShadowAnimation::paintShadowVertical(int left, int top, int bottom, const QImage &image) { auto imageWidth = image.width(); auto imageInts = reinterpret_cast<const uint32*>(image.constBits()); if (left < 0) { auto shift = -base::take(left); imageWidth -= shift; imageInts += shift; } if (top < 0) top = 0; accumulate_min(bottom, _frameHeight); accumulate_min(imageWidth, _frameWidth - left); if (imageWidth < 0 || bottom <= top) return; auto frameInts = _frameInts + top * _frameIntsPerLine + left; auto frameIntsPerLineAdd = _frameIntsPerLine - imageWidth; for (auto y = top; y != bottom; ++y) { for (auto x = 0; x != imageWidth; ++x) { auto source = *frameInts; auto shadowAlpha = _frameAlpha - (source >> 24); *frameInts = anim::unshifted(anim::shifted(source) * 256 + anim::shifted(*imageInts) * shadowAlpha); ++frameInts; ++imageInts; } frameInts += frameIntsPerLineAdd; imageInts -= imageWidth; } } void RoundShadowAnimation::paintShadowHorizontal(int left, int right, int top, const QImage &image) { auto imageHeight = image.height(); auto imageInts = reinterpret_cast<const uint32*>(image.constBits()); auto imageIntsPerLine = (image.bytesPerLine() >> 2); if (top < 0) { auto shift = -base::take(top); imageHeight -= shift; imageInts += shift * imageIntsPerLine; } if (left < 0) left = 0; accumulate_min(right, _frameWidth); accumulate_min(imageHeight, _frameHeight - top); if (imageHeight < 0 || right <= left) return; auto frameInts = _frameInts + top * _frameIntsPerLine + left; auto frameIntsPerLineAdd = _frameIntsPerLine - (right - left); for (auto y = 0; y != imageHeight; ++y) { auto imagePattern = anim::shifted(*imageInts); for (auto x = left; x != right; ++x) { auto source = *frameInts; auto shadowAlpha = _frameAlpha - (source >> 24); *frameInts = anim::unshifted(anim::shifted(source) * 256 + imagePattern * shadowAlpha); ++frameInts; } frameInts += frameIntsPerLineAdd; imageInts += imageIntsPerLine; } } void PanelAnimation::setFinalImage(QImage &&finalImage, QRect inner) { Assert(!started()); _finalImage = App::pixmapFromImageInPlace(std::move(finalImage).convertToFormat(QImage::Format_ARGB32_Premultiplied)); Assert(!_finalImage.isNull()); _finalWidth = _finalImage.width(); _finalHeight = _finalImage.height(); Assert(!(_finalWidth % cIntRetinaFactor())); Assert(!(_finalHeight % cIntRetinaFactor())); _finalInnerLeft = inner.x(); _finalInnerTop = inner.y(); _finalInnerWidth = inner.width(); _finalInnerHeight = inner.height(); Assert(!(_finalInnerLeft % cIntRetinaFactor())); Assert(!(_finalInnerTop % cIntRetinaFactor())); Assert(!(_finalInnerWidth % cIntRetinaFactor())); Assert(!(_finalInnerHeight % cIntRetinaFactor())); _finalInnerRight = _finalInnerLeft + _finalInnerWidth; _finalInnerBottom = _finalInnerTop + _finalInnerHeight; Assert(QRect(0, 0, _finalWidth, _finalHeight).contains(inner)); setStartWidth(); setStartHeight(); setStartAlpha(); setStartFadeTop(); createFadeMask(); setWidthDuration(); setHeightDuration(); setAlphaDuration(); if (!_skipShadow) { setShadow(_st.shadow); } auto checkCorner = [this, inner](Corner &corner) { if (!corner.valid()) return; if ((_startWidth >= 0 && _startWidth < _finalWidth) || (_startHeight >= 0 && _startHeight < _finalHeight)) { Assert(corner.width <= inner.width()); Assert(corner.height <= inner.height()); } }; checkCorner(_topLeft); checkCorner(_topRight); checkCorner(_bottomLeft); checkCorner(_bottomRight); } void PanelAnimation::setStartWidth() { _startWidth = qRound(_st.startWidth * _finalInnerWidth); if (_startWidth >= 0) Assert(_startWidth <= _finalInnerWidth); } void PanelAnimation::setStartHeight() { _startHeight = qRound(_st.startHeight * _finalInnerHeight); if (_startHeight >= 0) Assert(_startHeight <= _finalInnerHeight); } void PanelAnimation::setStartAlpha() { _startAlpha = qRound(_st.startOpacity * 255); Assert(_startAlpha >= 0 && _startAlpha < 256); } void PanelAnimation::setStartFadeTop() { _startFadeTop = qRound(_st.startFadeTop * _finalInnerHeight); } void PanelAnimation::createFadeMask() { auto resultHeight = qRound(_finalImage.height() * _st.fadeHeight); if (auto remove = (resultHeight % cIntRetinaFactor())) { resultHeight -= remove; } auto finalAlpha = qRound(_st.fadeOpacity * 255); Assert(finalAlpha >= 0 && finalAlpha < 256); auto result = QImage(cIntRetinaFactor(), resultHeight, QImage::Format_ARGB32_Premultiplied); auto ints = reinterpret_cast<uint32*>(result.bits()); auto intsPerLineAdded = (result.bytesPerLine() >> 2) - cIntRetinaFactor(); auto up = (_origin == PanelAnimation::Origin::BottomLeft || _origin == PanelAnimation::Origin::BottomRight); auto from = up ? resultHeight : 0, to = resultHeight - from, delta = up ? -1 : 1; auto fadeFirstAlpha = up ? (finalAlpha + 1) : 1; auto fadeLastAlpha = up ? 1 : (finalAlpha + 1); _fadeFirst = QBrush(QColor(_st.fadeBg->c.red(), _st.fadeBg->c.green(), _st.fadeBg->c.blue(), (_st.fadeBg->c.alpha() * fadeFirstAlpha) >> 8)); _fadeLast = QBrush(QColor(_st.fadeBg->c.red(), _st.fadeBg->c.green(), _st.fadeBg->c.blue(), (_st.fadeBg->c.alpha() * fadeLastAlpha) >> 8)); for (auto y = from; y != to; y += delta) { auto alpha = static_cast<uint32>(finalAlpha * y) / resultHeight; auto value = (0xFFU << 24) | (alpha << 16) | (alpha << 8) | alpha; for (auto x = 0; x != cIntRetinaFactor(); ++x) { *ints++ = value; } ints += intsPerLineAdded; } _fadeMask = App::pixmapFromImageInPlace(style::colorizeImage(result, _st.fadeBg)); _fadeHeight = _fadeMask.height(); } void PanelAnimation::setSkipShadow(bool skipShadow) { Assert(!started()); _skipShadow = skipShadow; } void PanelAnimation::setWidthDuration() { _widthDuration = _st.widthDuration; Assert(_widthDuration >= 0.); Assert(_widthDuration <= 1.); } void PanelAnimation::setHeightDuration() { Assert(!started()); _heightDuration = _st.heightDuration; Assert(_heightDuration >= 0.); Assert(_heightDuration <= 1.); } void PanelAnimation::setAlphaDuration() { Assert(!started()); _alphaDuration = _st.opacityDuration; Assert(_alphaDuration >= 0.); Assert(_alphaDuration <= 1.); } void PanelAnimation::start() { Assert(!_finalImage.isNull()); RoundShadowAnimation::start(_finalWidth, _finalHeight, _finalImage.devicePixelRatio()); auto checkCorner = [this](const Corner &corner) { if (!corner.valid()) return; if (_startWidth >= 0) Assert(corner.width <= _startWidth); if (_startHeight >= 0) Assert(corner.height <= _startHeight); Assert(corner.width <= _finalInnerWidth); Assert(corner.height <= _finalInnerHeight); }; checkCorner(_topLeft); checkCorner(_topRight); checkCorner(_bottomLeft); checkCorner(_bottomRight); } void PanelAnimation::paintFrame(QPainter &p, int x, int y, int outerWidth, float64 dt, float64 opacity) { Assert(started()); Assert(dt >= 0.); auto &transition = anim::easeOutCirc; if (dt < _alphaDuration) opacity *= transition(1., dt / _alphaDuration); _frameAlpha = anim::interpolate(1, 256, opacity); auto frameWidth = (_startWidth < 0 || dt >= _widthDuration) ? _finalInnerWidth : anim::interpolate(_startWidth, _finalInnerWidth, transition(1., dt / _widthDuration)); auto frameHeight = (_startHeight < 0 || dt >= _heightDuration) ? _finalInnerHeight : anim::interpolate(_startHeight, _finalInnerHeight, transition(1., dt / _heightDuration)); if (auto decrease = (frameWidth % cIntRetinaFactor())) { frameWidth -= decrease; } if (auto decrease = (frameHeight % cIntRetinaFactor())) { frameHeight -= decrease; } auto frameLeft = (_origin == Origin::TopLeft || _origin == Origin::BottomLeft) ? _finalInnerLeft : (_finalInnerRight - frameWidth); auto frameTop = (_origin == Origin::TopLeft || _origin == Origin::TopRight) ? _finalInnerTop : (_finalInnerBottom - frameHeight); auto frameRight = frameLeft + frameWidth; auto frameBottom = frameTop + frameHeight; auto fadeTop = (_fadeHeight > 0) ? snap(anim::interpolate(_startFadeTop, _finalInnerHeight, transition(1., dt)), 0, frameHeight) : frameHeight; if (auto decrease = (fadeTop % cIntRetinaFactor())) { fadeTop -= decrease; } auto fadeBottom = (fadeTop < frameHeight) ? qMin(fadeTop + _fadeHeight, frameHeight) : frameHeight; auto fadeSkipLines = 0; if (_origin == Origin::BottomLeft || _origin == Origin::BottomRight) { fadeTop = frameHeight - fadeTop; fadeBottom = frameHeight - fadeBottom; qSwap(fadeTop, fadeBottom); fadeSkipLines = fadeTop + _fadeHeight - fadeBottom; } fadeTop += frameTop; fadeBottom += frameTop; if (opacity < 1.) { _frame.fill(Qt::transparent); } { Painter p(&_frame); p.setOpacity(opacity); auto painterFrameLeft = frameLeft / cIntRetinaFactor(); auto painterFrameTop = frameTop / cIntRetinaFactor(); auto painterFadeBottom = fadeBottom / cIntRetinaFactor(); p.drawPixmap(painterFrameLeft, painterFrameTop, _finalImage, frameLeft, frameTop, frameWidth, frameHeight); if (_fadeHeight) { if (frameTop != fadeTop) { p.fillRect(painterFrameLeft, painterFrameTop, frameWidth, fadeTop - frameTop, _fadeFirst); } if (fadeTop != fadeBottom) { auto painterFadeTop = fadeTop / cIntRetinaFactor(); auto painterFrameWidth = frameWidth / cIntRetinaFactor(); auto painterFrameHeight = frameHeight / cIntRetinaFactor(); p.drawPixmap(painterFrameLeft, painterFadeTop, painterFrameWidth, painterFadeBottom - painterFadeTop, _fadeMask, 0, fadeSkipLines, cIntRetinaFactor(), fadeBottom - fadeTop); } if (fadeBottom != frameBottom) { p.fillRect(painterFrameLeft, painterFadeBottom, frameWidth, frameBottom - fadeBottom, _fadeLast); } } } auto frameInts = _frameInts + frameLeft + frameTop * _frameIntsPerLine; auto frameIntsPerLineAdd = (_finalWidth - frameWidth) + _frameIntsPerLineAdded; // Draw corners paintCorner(_topLeft, frameLeft, frameTop); paintCorner(_topRight, frameRight - _topRight.width, frameTop); paintCorner(_bottomLeft, frameLeft, frameBottom - _bottomLeft.height); paintCorner(_bottomRight, frameRight - _bottomRight.width, frameBottom - _bottomRight.height); // Draw shadow upon the transparent auto outerLeft = frameLeft; auto outerTop = frameTop; auto outerRight = frameRight; auto outerBottom = frameBottom; if (_shadow.valid()) { outerLeft -= _shadow.extend.left(); outerTop -= _shadow.extend.top(); outerRight += _shadow.extend.right(); outerBottom += _shadow.extend.bottom(); } if (cIntRetinaFactor() > 1) { if (auto skipLeft = (outerLeft % cIntRetinaFactor())) { outerLeft -= skipLeft; } if (auto skipTop = (outerTop % cIntRetinaFactor())) { outerTop -= skipTop; } if (auto skipRight = (outerRight % cIntRetinaFactor())) { outerRight += (cIntRetinaFactor() - skipRight); } if (auto skipBottom = (outerBottom % cIntRetinaFactor())) { outerBottom += (cIntRetinaFactor() - skipBottom); } } if (opacity == 1.) { // Fill above the frame top with transparent. auto fillTopInts = (_frameInts + outerTop * _frameIntsPerLine + outerLeft); auto fillWidth = (outerRight - outerLeft) * sizeof(uint32); for (auto fillTop = frameTop - outerTop; fillTop != 0; --fillTop) { memset(fillTopInts, 0, fillWidth); fillTopInts += _frameIntsPerLine; } // Fill to the left and to the right of the frame with transparent. auto fillLeft = (frameLeft - outerLeft) * sizeof(uint32); auto fillRight = (outerRight - frameRight) * sizeof(uint32); if (fillLeft || fillRight) { auto fillInts = _frameInts + frameTop * _frameIntsPerLine; for (auto y = frameTop; y != frameBottom; ++y) { memset(fillInts + outerLeft, 0, fillLeft); memset(fillInts + frameRight, 0, fillRight); fillInts += _frameIntsPerLine; } } // Fill below the frame bottom with transparent. auto fillBottomInts = (_frameInts + frameBottom * _frameIntsPerLine + outerLeft); for (auto fillBottom = outerBottom - frameBottom; fillBottom != 0; --fillBottom) { memset(fillBottomInts, 0, fillWidth); fillBottomInts += _frameIntsPerLine; } } if (_shadow.valid()) { paintShadow(outerLeft, outerTop, outerRight, outerBottom); } // Debug //frameInts = _frameInts; //auto pattern = anim::shifted((static_cast<uint32>(0xFF) << 24) | (static_cast<uint32>(0xFF) << 16) | (static_cast<uint32>(0xFF) << 8) | static_cast<uint32>(0xFF)); //for (auto y = 0; y != _finalHeight; ++y) { // for (auto x = 0; x != _finalWidth; ++x) { // auto source = *frameInts; // auto sourceAlpha = (source >> 24); // *frameInts = anim::unshifted(anim::shifted(source) * 256 + pattern * (256 - sourceAlpha)); // ++frameInts; // } // frameInts += _frameIntsPerLineAdded; //} p.drawImage(rtlpoint(x + (outerLeft / cIntRetinaFactor()), y + (outerTop / cIntRetinaFactor()), outerWidth), _frame, QRect(outerLeft, outerTop, outerRight - outerLeft, outerBottom - outerTop)); } } // namespace Ui �������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/effects/panel_animation.h�����������������������������������0000664�0000000�0000000�00000005744�13262451251�0025000�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "styles/style_widgets.h" namespace Ui { class RoundShadowAnimation { public: void setCornerMasks(const QImage &topLeft, const QImage &topRight, const QImage &bottomLeft, const QImage &bottomRight); protected: void start(int frameWidth, int frameHeight, float64 devicePixelRatio); void setShadow(const style::Shadow &st); bool started() const { return !_frame.isNull(); } struct Corner { QImage image; int width = 0; int height = 0; const uchar *bytes = nullptr; int bytesPerPixel = 0; int bytesPerLineAdded = 0; bool valid() const { return !image.isNull(); } }; void setCornerMask(Corner &corner, const QImage &image); void paintCorner(Corner &corner, int left, int top); struct Shadow { style::margins extend; QImage left, topLeft, top, topRight, right, bottomRight, bottom, bottomLeft; bool valid() const { return !left.isNull(); } }; QImage cloneImage(const style::icon &source); void paintShadow(int left, int top, int right, int bottom); void paintShadowCorner(int left, int top, const QImage &image); void paintShadowVertical(int left, int top, int bottom, const QImage &image); void paintShadowHorizontal(int left, int right, int top, const QImage &image); Shadow _shadow; Corner _topLeft; Corner _topRight; Corner _bottomLeft; Corner _bottomRight; QImage _frame; uint32 *_frameInts = nullptr; int _frameWidth = 0; int _frameHeight = 0; int _frameAlpha = 0; // recounted each getFrame() int _frameIntsPerLine = 0; int _frameIntsPerLineAdded = 0; }; class PanelAnimation : public RoundShadowAnimation { public: enum class Origin { TopLeft, TopRight, BottomLeft, BottomRight, }; PanelAnimation(const style::PanelAnimation &st, Origin origin) : _st(st), _origin(origin) { } void setFinalImage(QImage &&finalImage, QRect inner); void setSkipShadow(bool skipShadow); void start(); void paintFrame(QPainter &p, int x, int y, int outerWidth, float64 dt, float64 opacity); private: void setStartWidth(); void setStartHeight(); void setStartAlpha(); void setStartFadeTop(); void createFadeMask(); void setWidthDuration(); void setHeightDuration(); void setAlphaDuration(); const style::PanelAnimation &_st; Origin _origin = Origin::TopLeft; QPixmap _finalImage; int _finalWidth = 0; int _finalHeight = 0; int _finalInnerLeft = 0; int _finalInnerTop = 0; int _finalInnerRight = 0; int _finalInnerBottom = 0; int _finalInnerWidth = 0; int _finalInnerHeight = 0; bool _skipShadow = false; int _startWidth = -1; int _startHeight = -1; int _startAlpha = 0; int _startFadeTop = 0; QPixmap _fadeMask; int _fadeHeight = 0; QBrush _fadeFirst, _fadeLast; float64 _widthDuration = 1.; float64 _heightDuration = 1.; float64 _alphaDuration = 1.; }; } // namespace Ui ����������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/effects/radial_animation.cpp��������������������������������0000664�0000000�0000000�00000004420�13262451251�0025456�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/effects/radial_animation.h" namespace Ui { RadialAnimation::RadialAnimation(AnimationCallbacks &&callbacks) : a_arcStart(0, FullArcLength) , _animation(std::move(callbacks)) { } void RadialAnimation::start(float64 prg) { _firstStart = _lastStart = _lastTime = getms(); int32 iprg = qRound(qMax(prg, 0.0001) * AlmostFullArcLength), iprgstrict = qRound(prg * AlmostFullArcLength); a_arcEnd = anim::value(iprgstrict, iprg); _animation.start(); } void RadialAnimation::update(float64 prg, bool finished, TimeMs ms) { auto iprg = qRound(qMax(prg, 0.0001) * AlmostFullArcLength); if (iprg != qRound(a_arcEnd.to())) { a_arcEnd.start(iprg); _lastStart = _lastTime; } _lastTime = ms; auto dt = float64(ms - _lastStart); auto fulldt = float64(ms - _firstStart); _opacity = qMin(fulldt / st::radialDuration, 1.); if (!finished) { a_arcEnd.update(1. - (st::radialDuration / (st::radialDuration + dt)), anim::linear); } else if (dt >= st::radialDuration) { a_arcEnd.update(1., anim::linear); stop(); } else { auto r = dt / st::radialDuration; a_arcEnd.update(r, anim::linear); _opacity *= 1 - r; } auto fromstart = fulldt / st::radialPeriod; a_arcStart.update(fromstart - std::floor(fromstart), anim::linear); } void RadialAnimation::stop() { _firstStart = _lastStart = _lastTime = 0; a_arcEnd = anim::value(); _animation.stop(); } void RadialAnimation::step(TimeMs ms) { _animation.step(ms); } void RadialAnimation::draw(Painter &p, const QRect &inner, int32 thickness, style::color color) { auto o = p.opacity(); p.setOpacity(o * _opacity); auto pen = color->p; auto was = p.pen(); pen.setWidth(thickness); pen.setCapStyle(Qt::RoundCap); p.setPen(pen); auto len = MinArcLength + qRound(a_arcEnd.current()); auto from = QuarterArcLength - qRound(a_arcStart.current()) - len; if (rtl()) { from = QuarterArcLength - (from - QuarterArcLength) - len; if (from < 0) from += FullArcLength; } { PainterHighQualityEnabler hq(p); p.drawArc(inner, from, len); } p.setPen(was); p.setOpacity(o); } } // namespace Ui ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/effects/radial_animation.h����������������������������������0000664�0000000�0000000�00000001610�13262451251�0025121�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace Ui { class RadialAnimation { public: RadialAnimation(AnimationCallbacks &&callbacks); float64 opacity() const { return _opacity; } bool animating() const { return _animation.animating(); } void start(float64 prg); void update(float64 prg, bool finished, TimeMs ms); void stop(); void step(TimeMs ms); void step() { step(getms()); } void draw(Painter &p, const QRect &inner, int32 thickness, style::color color); private: TimeMs _firstStart = 0; TimeMs _lastStart = 0; TimeMs _lastTime = 0; float64 _opacity = 0.; anim::value a_arcEnd; anim::value a_arcStart; BasicAnimation _animation; }; } // namespace Ui ������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/effects/ripple_animation.cpp��������������������������������0000664�0000000�0000000�00000013744�13262451251�0025526�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/effects/ripple_animation.h" namespace Ui { class RippleAnimation::Ripple { public: Ripple(const style::RippleAnimation &st, QPoint origin, int startRadius, const QPixmap &mask, const UpdateCallback &update); Ripple(const style::RippleAnimation &st, const QPixmap &mask, const UpdateCallback &update); void paint(QPainter &p, const QPixmap &mask, TimeMs ms, const QColor *colorOverride); void stop(); void unstop(); void finish(); bool finished() const { return _hiding && !_hide.animating(); } private: const style::RippleAnimation &_st; UpdateCallback _update; QPoint _origin; int _radiusFrom = 0; int _radiusTo = 0; bool _hiding = false; Animation _show; Animation _hide; QPixmap _cache; QImage _frame; }; RippleAnimation::Ripple::Ripple(const style::RippleAnimation &st, QPoint origin, int startRadius, const QPixmap &mask, const UpdateCallback &update) : _st(st) , _update(update) , _origin(origin) , _radiusFrom(startRadius) , _frame(mask.size(), QImage::Format_ARGB32_Premultiplied) { _frame.setDevicePixelRatio(mask.devicePixelRatio()); QPoint points[] = { { 0, 0 }, { _frame.width() / cIntRetinaFactor(), 0 }, { _frame.width() / cIntRetinaFactor(), _frame.height() / cIntRetinaFactor() }, { 0, _frame.height() / cIntRetinaFactor() }, }; for (auto point : points) { accumulate_max(_radiusTo, style::point::dotProduct(_origin - point, _origin - point)); } _radiusTo = qRound(sqrt(_radiusTo)); _show.start(UpdateCallback(_update), 0., 1., _st.showDuration, anim::easeOutQuint); } RippleAnimation::Ripple::Ripple(const style::RippleAnimation &st, const QPixmap &mask, const UpdateCallback &update) : _st(st) , _update(update) , _origin(mask.width() / (2 * cIntRetinaFactor()), mask.height() / (2 * cIntRetinaFactor())) , _radiusFrom(mask.width() + mask.height()) , _frame(mask.size(), QImage::Format_ARGB32_Premultiplied) { _frame.setDevicePixelRatio(mask.devicePixelRatio()); _radiusTo = _radiusFrom; _hide.start(UpdateCallback(_update), 0., 1., _st.hideDuration); } void RippleAnimation::Ripple::paint(QPainter &p, const QPixmap &mask, TimeMs ms, const QColor *colorOverride) { auto opacity = _hide.current(ms, _hiding ? 0. : 1.); if (opacity == 0.) { return; } if (_cache.isNull() || colorOverride != nullptr) { auto radius = anim::interpolate(_radiusFrom, _radiusTo, _show.current(ms, 1.)); _frame.fill(Qt::transparent); { Painter p(&_frame); p.setPen(Qt::NoPen); if (colorOverride) { p.setBrush(*colorOverride); } else { p.setBrush(_st.color); } { PainterHighQualityEnabler hq(p); p.drawEllipse(_origin, radius, radius); } p.setCompositionMode(QPainter::CompositionMode_DestinationIn); p.drawPixmap(0, 0, mask); } if (radius == _radiusTo && colorOverride == nullptr) { _cache = App::pixmapFromImageInPlace(std::move(_frame)); } } auto saved = p.opacity(); if (opacity != 1.) p.setOpacity(saved * opacity); if (_cache.isNull()) { p.drawImage(0, 0, _frame); } else { p.drawPixmap(0, 0, _cache); } if (opacity != 1.) p.setOpacity(saved); } void RippleAnimation::Ripple::stop() { _hiding = true; _hide.start(UpdateCallback(_update), 1., 0., _st.hideDuration); } void RippleAnimation::Ripple::unstop() { if (_hiding) { if (_hide.animating()) { _hide.start(UpdateCallback(_update), 0., 1., _st.hideDuration); } _hiding = false; } } void RippleAnimation::Ripple::finish() { if (_update) { _update(); } _show.finish(); _hide.finish(); } RippleAnimation::RippleAnimation(const style::RippleAnimation &st, QImage mask, const UpdateCallback &callback) : _st(st) , _mask(App::pixmapFromImageInPlace(std::move(mask))) , _update(callback) { } void RippleAnimation::add(QPoint origin, int startRadius) { lastStop(); _ripples.push_back(new Ripple(_st, origin, startRadius, _mask, _update)); } void RippleAnimation::addFading() { lastStop(); _ripples.push_back(new Ripple(_st, _mask, _update)); } void RippleAnimation::lastStop() { if (!_ripples.isEmpty()) { _ripples.back()->stop(); } } void RippleAnimation::lastUnstop() { if (!_ripples.isEmpty()) { _ripples.back()->unstop(); } } void RippleAnimation::lastFinish() { if (!_ripples.isEmpty()) { _ripples.back()->finish(); } } void RippleAnimation::paint(QPainter &p, int x, int y, int outerWidth, TimeMs ms, const QColor *colorOverride) { if (_ripples.isEmpty()) { return; } if (rtl()) x = outerWidth - x - (_mask.width() / cIntRetinaFactor()); p.translate(x, y); for (auto ripple : _ripples) { ripple->paint(p, _mask, ms, colorOverride); } p.translate(-x, -y); clearFinished(); } QImage RippleAnimation::maskByDrawer(QSize size, bool filled, base::lambda<void(QPainter &p)> drawer) { auto result = QImage(size * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); result.setDevicePixelRatio(cRetinaFactor()); result.fill(filled ? QColor(255, 255, 255) : Qt::transparent); if (drawer) { Painter p(&result); PainterHighQualityEnabler hq(p); p.setPen(Qt::NoPen); p.setBrush(QColor(255, 255, 255)); drawer(p); } return result; } QImage RippleAnimation::rectMask(QSize size) { return maskByDrawer(size, true, base::lambda<void(QPainter&)>()); } QImage RippleAnimation::roundRectMask(QSize size, int radius) { return maskByDrawer(size, false, [size, radius](QPainter &p) { p.drawRoundedRect(0, 0, size.width(), size.height(), radius, radius); }); } QImage RippleAnimation::ellipseMask(QSize size) { return maskByDrawer(size, false, [size](QPainter &p) { p.drawEllipse(0, 0, size.width(), size.height()); }); } void RippleAnimation::clearFinished() { while (!_ripples.isEmpty() && _ripples.front()->finished()) { delete base::take(_ripples.front()); _ripples.pop_front(); } } void RippleAnimation::clear() { for (auto ripple : base::take(_ripples)) { delete ripple; } } } // namespace Ui ����������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/effects/ripple_animation.h����������������������������������0000664�0000000�0000000�00000002422�13262451251�0025162�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "styles/style_widgets.h" namespace Ui { class RippleAnimation { public: using UpdateCallback = base::lambda<void()>; // White upon transparent mask, like colorizeImage(black-white-mask, white). RippleAnimation(const style::RippleAnimation &st, QImage mask, const UpdateCallback &update); void add(QPoint origin, int startRadius = 0); void addFading(); void lastStop(); void lastUnstop(); void lastFinish(); void paint(QPainter &p, int x, int y, int outerWidth, TimeMs ms, const QColor *colorOverride = nullptr); bool empty() const { return _ripples.isEmpty(); } static QImage maskByDrawer(QSize size, bool filled, base::lambda<void(QPainter &p)> drawer); static QImage rectMask(QSize size); static QImage roundRectMask(QSize size, int radius); static QImage ellipseMask(QSize size); ~RippleAnimation() { clear(); } private: void clear(); void clearFinished(); const style::RippleAnimation &_st; QPixmap _mask; UpdateCallback _update; class Ripple; QList<Ripple*> _ripples; }; } // namespace Ui ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/effects/round_checkbox.cpp����������������������������������0000664�0000000�0000000�00000032723�13262451251�0025167�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/effects/round_checkbox.h" namespace Ui { namespace { static constexpr int kWideScale = 3; class CheckCaches : public QObject { public: CheckCaches(QObject *parent) : QObject(parent) { Expects(parent != nullptr); } void clear(); QPixmap frame( const style::RoundCheckbox *st, bool displayInactive, float64 progress); private: struct Frames { bool displayInactive = false; std::vector<QPixmap> list; QPixmap outerWide; QPixmap inner; QPixmap check; }; int countFramesCount(const style::RoundCheckbox *st); Frames &framesForStyle( const style::RoundCheckbox *st, bool displayInactive); void prepareFramesData( const style::RoundCheckbox *st, bool displayInactive, Frames &frames); QPixmap paintFrame( const style::RoundCheckbox *st, const Frames &frames, float64 progress); std::map<const style::RoundCheckbox *, Frames> _data; }; QPixmap PrepareOuterWide(const style::RoundCheckbox *st) { const auto size = st->size; const auto wideSize = size * kWideScale; auto result = QImage( QSize(wideSize, wideSize) * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); result.setDevicePixelRatio(cRetinaFactor()); result.fill(Qt::transparent); { Painter p(&result); PainterHighQualityEnabler hq(p); p.setPen(Qt::NoPen); p.setBrush(st->border); const auto half = st->width / 2.; p.drawEllipse(QRectF( (wideSize - size) / 2 - half, (wideSize - size) / 2 - half, size + 2. * half, size + 2. * half)); } return App::pixmapFromImageInPlace(std::move(result)); } QPixmap PrepareInner(const style::RoundCheckbox *st, bool displayInactive) { const auto size = st->size; auto result = QImage( QSize(size, size) * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); result.setDevicePixelRatio(cRetinaFactor()); result.fill(Qt::transparent); { Painter p(&result); PainterHighQualityEnabler hq(p); p.setPen(Qt::NoPen); p.setBrush(st->bgActive); const auto half = st->width / 2.; p.drawEllipse(QRectF( displayInactive ? 0. : half, displayInactive ? 0. : half, size - (displayInactive ? 0. : 2. * half), size - (displayInactive ? 0. : 2. * half))); } return App::pixmapFromImageInPlace(std::move(result)); } QPixmap PrepareCheck(const style::RoundCheckbox *st) { const auto size = st->size; auto result = QImage( QSize(size, size) * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); result.setDevicePixelRatio(cRetinaFactor()); result.fill(Qt::transparent); { Painter p(&result); st->check.paint(p, 0, 0, size); } return App::pixmapFromImageInPlace(std::move(result)); } QRect WideDestRect( const style::RoundCheckbox *st, int x, int y, float64 scale) { auto iconSizeFull = kWideScale * st->size; auto iconSize = qRound(iconSizeFull * scale); if (iconSize % 2 != iconSizeFull % 2) { ++iconSize; } auto iconShift = (iconSizeFull - iconSize) / 2; auto iconLeft = x - (kWideScale - 1) * st->size / 2 + iconShift; auto iconTop = y - (kWideScale - 1) * st->size / 2 + iconShift; return QRect(iconLeft, iconTop, iconSize, iconSize); } void CheckCaches::clear() { _data.clear(); } int CheckCaches::countFramesCount(const style::RoundCheckbox *st) { return (st->duration / AnimationTimerDelta) + 1; } CheckCaches::Frames &CheckCaches::framesForStyle( const style::RoundCheckbox *st, bool displayInactive) { auto i = _data.find(st); if (i == _data.end()) { i = _data.emplace(st, Frames()).first; prepareFramesData(st, displayInactive, i->second); } else if (i->second.displayInactive != displayInactive) { i->second = Frames(); prepareFramesData(st, displayInactive, i->second); } return i->second; } void CheckCaches::prepareFramesData( const style::RoundCheckbox *st, bool displayInactive, Frames &frames) { frames.list.resize(countFramesCount(st)); frames.displayInactive = displayInactive; if (!frames.displayInactive) { frames.outerWide = PrepareOuterWide(st); } frames.inner = PrepareInner(st, frames.displayInactive); frames.check = PrepareCheck(st); } QPixmap CheckCaches::frame( const style::RoundCheckbox *st, bool displayInactive, float64 progress) { auto &frames = framesForStyle(st, displayInactive); const auto frameCount = int(frames.list.size()); const auto frameIndex = int(std::round(progress * (frameCount - 1))); Assert(frameIndex >= 0 && frameIndex < frameCount); if (!frames.list[frameIndex]) { const auto frameProgress = frameIndex / float64(frameCount - 1); frames.list[frameIndex] = paintFrame(st, frames, frameProgress); } return frames.list[frameIndex]; } QPixmap CheckCaches::paintFrame( const style::RoundCheckbox *st, const Frames &frames, float64 progress) { const auto size = st->size; const auto wideSize = size * kWideScale; const auto skip = (wideSize - size) / 2; auto result = QImage(wideSize * cIntRetinaFactor(), wideSize * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); result.setDevicePixelRatio(cRetinaFactor()); result.fill(Qt::transparent); const auto roundProgress = (progress >= st->bgDuration) ? 1. : (progress / st->bgDuration); const auto checkProgress = (1. - progress >= st->fgDuration) ? 0. : (1. - (1. - progress) / st->fgDuration); { Painter p(&result); PainterHighQualityEnabler hq(p); if (!frames.displayInactive) { const auto outerMaxScale = (size - st->width) / float64(size); const auto outerScale = roundProgress + (1. - roundProgress) * outerMaxScale; const auto outerTo = WideDestRect(st, skip, skip, outerScale); const auto outerFrom = QRect( QPoint(0, 0), QSize(wideSize, wideSize) * cIntRetinaFactor()); p.drawPixmap(outerTo, frames.outerWide, outerFrom); } p.drawPixmap(skip, skip, frames.inner); const auto divider = checkProgress * st->size; const auto checkTo = QRect(skip, skip, divider, st->size); const auto checkFrom = QRect( QPoint(0, 0), QSize(divider, st->size) * cIntRetinaFactor()); p.drawPixmap(checkTo, frames.check, checkFrom); p.setCompositionMode(QPainter::CompositionMode_Source); p.setPen(Qt::NoPen); p.setBrush(Qt::transparent); const auto remove = size * (1. - roundProgress); p.drawEllipse(QRectF( (wideSize - remove) / 2., (wideSize - remove) / 2., remove, remove)); } return App::pixmapFromImageInPlace(std::move(result)); } CheckCaches *FrameCaches() { static QPointer<CheckCaches> Instance; if (auto instance = Instance.data()) { return instance; } auto result = new CheckCaches(QGuiApplication::instance()); Instance = result; return result; } void prepareCheckCaches(const style::RoundCheckbox *st, bool displayInactive, QPixmap &checkBgCache, QPixmap &checkFullCache) { auto size = st->size; auto wideSize = size * kWideScale; auto cache = QImage(wideSize * cIntRetinaFactor(), wideSize * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); cache.setDevicePixelRatio(cRetinaFactor()); cache.fill(Qt::transparent); { Painter p(&cache); PainterHighQualityEnabler hq(p); if (displayInactive) { p.setPen(Qt::NoPen); } else { auto pen = st->border->p; pen.setWidth(st->width); p.setPen(pen); } p.setBrush(st->bgActive); auto ellipse = QRect((wideSize - size) / 2, (wideSize - size) / 2, size, size); p.drawEllipse(ellipse); } auto cacheIcon = cache; { Painter p(&cacheIcon); auto ellipse = QRect((wideSize - size) / 2, (wideSize - size) / 2, size, size); st->check.paint(p, ellipse.topLeft(), wideSize); } checkBgCache = App::pixmapFromImageInPlace(std::move(cache)); checkFullCache = App::pixmapFromImageInPlace(std::move(cacheIcon)); } } // namespace RoundCheckbox::RoundCheckbox(const style::RoundCheckbox &st, base::lambda<void()> updateCallback) : _st(st) , _updateCallback(updateCallback) { } void RoundCheckbox::paint(Painter &p, TimeMs ms, int x, int y, int outerWidth, float64 masterScale) { if (!_checkedProgress.animating() && !_checked && !_displayInactive) { return; } auto cacheSize = kWideScale * _st.size * cIntRetinaFactor(); auto cacheFrom = QRect(0, 0, cacheSize, cacheSize); auto displayInactive = !_inactiveCacheBg.isNull(); auto inactiveTo = WideDestRect(&_st, x, y, masterScale); PainterHighQualityEnabler hq(p); if (!_inactiveCacheBg.isNull()) { p.drawPixmap(inactiveTo, _inactiveCacheBg, cacheFrom); } const auto progress = _checkedProgress.current(ms, _checked ? 1. : 0.); if (progress > 0.) { auto frame = FrameCaches()->frame(&_st, _displayInactive, progress); p.drawPixmap(inactiveTo, frame, cacheFrom); } if (!_inactiveCacheFg.isNull()) { p.drawPixmap(inactiveTo, _inactiveCacheFg, cacheFrom); } } void RoundCheckbox::setChecked(bool newChecked, SetStyle speed) { if (_checked == newChecked) { if (speed != SetStyle::Animated) { _checkedProgress.finish(); } return; } _checked = newChecked; _checkedProgress.start( _updateCallback, _checked ? 0. : 1., _checked ? 1. : 0., _st.duration, anim::linear); } void RoundCheckbox::invalidateCache() { FrameCaches()->clear(); if (!_inactiveCacheBg.isNull() || !_inactiveCacheFg.isNull()) { prepareInactiveCache(); } } void RoundCheckbox::setDisplayInactive(bool displayInactive) { if (_displayInactive != displayInactive) { _displayInactive = displayInactive; if (_displayInactive) { prepareInactiveCache(); } else { _inactiveCacheBg = _inactiveCacheFg = QPixmap(); } } } void RoundCheckbox::prepareInactiveCache() { auto wideSize = _st.size * kWideScale; auto ellipse = QRect((wideSize - _st.size) / 2, (wideSize - _st.size) / 2, _st.size, _st.size); auto cacheBg = QImage(wideSize * cIntRetinaFactor(), wideSize * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); cacheBg.setDevicePixelRatio(cRetinaFactor()); cacheBg.fill(Qt::transparent); auto cacheFg = cacheBg; if (_st.bgInactive) { Painter p(&cacheBg); PainterHighQualityEnabler hq(p); p.setPen(Qt::NoPen); p.setBrush(_st.bgInactive); p.drawEllipse(ellipse); } _inactiveCacheBg = App::pixmapFromImageInPlace(std::move(cacheBg)); { Painter p(&cacheFg); PainterHighQualityEnabler hq(p); auto pen = _st.border->p; pen.setWidth(_st.width); p.setPen(pen); p.setBrush(Qt::NoBrush); p.drawEllipse(ellipse); } _inactiveCacheFg = App::pixmapFromImageInPlace(std::move(cacheFg)); } RoundImageCheckbox::RoundImageCheckbox(const style::RoundImageCheckbox &st, base::lambda<void()> updateCallback, PaintRoundImage &&paintRoundImage) : _st(st) , _updateCallback(updateCallback) , _paintRoundImage(std::move(paintRoundImage)) , _check(_st.check, _updateCallback) { } void RoundImageCheckbox::paint(Painter &p, TimeMs ms, int x, int y, int outerWidth) { _selection.step(ms); auto selectionLevel = _selection.current(checked() ? 1. : 0.); if (_selection.animating()) { auto userpicRadius = qRound(kWideScale * (_st.imageRadius + (_st.imageSmallRadius - _st.imageRadius) * selectionLevel)); auto userpicShift = kWideScale * _st.imageRadius - userpicRadius; auto userpicLeft = x - (kWideScale - 1) * _st.imageRadius + userpicShift; auto userpicTop = y - (kWideScale - 1) * _st.imageRadius + userpicShift; auto to = QRect(userpicLeft, userpicTop, userpicRadius * 2, userpicRadius * 2); auto from = QRect(QPoint(0, 0), _wideCache.size()); PainterHighQualityEnabler hq(p); p.drawPixmapLeft(to, outerWidth, _wideCache, from); } else { if (!_wideCache.isNull()) { _wideCache = QPixmap(); } auto userpicRadius = checked() ? _st.imageSmallRadius : _st.imageRadius; auto userpicShift = _st.imageRadius - userpicRadius; auto userpicLeft = x + userpicShift; auto userpicTop = y + userpicShift; _paintRoundImage(p, userpicLeft, userpicTop, outerWidth, userpicRadius * 2); } if (selectionLevel > 0) { PainterHighQualityEnabler hq(p); p.setOpacity(snap(selectionLevel, 0., 1.)); p.setBrush(Qt::NoBrush); auto pen = _st.selectFg->p; pen.setWidth(_st.selectWidth); p.setPen(pen); p.drawEllipse(rtlrect(x, y, _st.imageRadius * 2, _st.imageRadius * 2, outerWidth)); p.setOpacity(1.); } auto iconLeft = x + 2 * _st.imageRadius + _st.selectWidth - _st.check.size; auto iconTop = y + 2 * _st.imageRadius + _st.selectWidth - _st.check.size; _check.paint(p, ms, iconLeft, iconTop, outerWidth); } float64 RoundImageCheckbox::checkedAnimationRatio() const { return snap(_selection.current(checked() ? 1. : 0.), 0., 1.); } void RoundImageCheckbox::setChecked(bool newChecked, SetStyle speed) { auto changed = (checked() != newChecked); _check.setChecked(newChecked, speed); if (!changed) { if (speed != SetStyle::Animated) { _selection.finish(); } return; } if (speed == SetStyle::Animated) { prepareWideCache(); _selection.start(_updateCallback, checked() ? 0 : 1, checked() ? 1 : 0, _st.selectDuration, anim::bumpy(1.25)); } else { _selection.finish(); } } void RoundImageCheckbox::prepareWideCache() { if (_wideCache.isNull()) { auto size = _st.imageRadius * 2; auto wideSize = size * kWideScale; QImage cache(wideSize * cIntRetinaFactor(), wideSize * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); cache.setDevicePixelRatio(cRetinaFactor()); { Painter p(&cache); p.setCompositionMode(QPainter::CompositionMode_Source); p.fillRect(0, 0, wideSize, wideSize, Qt::transparent); p.setCompositionMode(QPainter::CompositionMode_SourceOver); _paintRoundImage(p, (wideSize - size) / 2, (wideSize - size) / 2, wideSize, size); } _wideCache = App::pixmapFromImageInPlace(std::move(cache)); } } } // namespace Ui ���������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/effects/round_checkbox.h������������������������������������0000664�0000000�0000000�00000003463�13262451251�0024633�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "styles/style_widgets.h" namespace Ui { class RoundCheckbox { public: RoundCheckbox(const style::RoundCheckbox &st, base::lambda<void()> updateCallback); void paint(Painter &p, TimeMs ms, int x, int y, int outerWidth, float64 masterScale = 1.); void setDisplayInactive(bool displayInactive); bool checked() const { return _checked; } enum class SetStyle { Animated, Fast, }; void setChecked(bool newChecked, SetStyle speed = SetStyle::Animated); void invalidateCache(); private: void prepareInactiveCache(); const style::RoundCheckbox &_st; base::lambda<void()> _updateCallback; bool _checked = false; Animation _checkedProgress; bool _displayInactive = false; QPixmap _inactiveCacheBg, _inactiveCacheFg; }; class RoundImageCheckbox { public: using PaintRoundImage = base::lambda<void(Painter &p, int x, int y, int outerWidth, int size)>; RoundImageCheckbox(const style::RoundImageCheckbox &st, base::lambda<void()> updateCallback, PaintRoundImage &&paintRoundImage); void paint(Painter &p, TimeMs ms, int x, int y, int outerWidth); float64 checkedAnimationRatio() const; bool checked() const { return _check.checked(); } using SetStyle = RoundCheckbox::SetStyle; void setChecked(bool newChecked, SetStyle speed = SetStyle::Animated); void invalidateCache() { _check.invalidateCache(); } private: void prepareWideCache(); const style::RoundImageCheckbox &_st; base::lambda<void()> _updateCallback; PaintRoundImage _paintRoundImage; QPixmap _wideCache; Animation _selection; RoundCheckbox _check; }; } // namespace Ui �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/effects/send_action_animations.cpp��������������������������0000664�0000000�0000000�00000016007�13262451251�0026677�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/effects/send_action_animations.h" #include "styles/style_widgets.h" namespace Ui { namespace { constexpr int kTypingDotsCount = 3; constexpr int kRecordArcsCount = 4; constexpr int kUploadArrowsCount = 3; using ImplementationsMap = QMap<SendAction::Type, const SendActionAnimation::Impl::MetaData*>; NeverFreedPointer<ImplementationsMap> Implementations; class TypingAnimation : public SendActionAnimation::Impl { public: TypingAnimation() : Impl(st::historySendActionTypingDuration) { } static const MetaData kMeta; static std::unique_ptr<Impl> create() { return std::make_unique<TypingAnimation>(); } const MetaData *metaData() const override { return &kMeta; } int width() const override { return st::historySendActionTypingPosition.x() + kTypingDotsCount * st::historySendActionTypingDelta; } private: void paintFrame(Painter &p, style::color color, int x, int y, int outerWidth, int frameMs) override; }; const TypingAnimation::MetaData TypingAnimation::kMeta = { 0, &TypingAnimation::create }; void TypingAnimation::paintFrame(Painter &p, style::color color, int x, int y, int outerWidth, int frameMs) { PainterHighQualityEnabler hq(p); p.setPen(Qt::NoPen); p.setBrush(color); auto position = QPointF(x + 0.5, y - 0.5) + st::historySendActionTypingPosition; for (auto i = 0; i != kTypingDotsCount; ++i) { auto r = st::historySendActionTypingSmallNumerator / st::historySendActionTypingDenominator; if (frameMs < 2 * st::historySendActionTypingHalfPeriod) { auto delta = (st::historySendActionTypingLargeNumerator - st::historySendActionTypingSmallNumerator) / st::historySendActionTypingDenominator; if (frameMs < st::historySendActionTypingHalfPeriod) { r += delta * anim::easeOutCirc(1., float64(frameMs) / st::historySendActionTypingHalfPeriod); } else { r += delta * (1. - anim::easeOutCirc(1., float64(frameMs - st::historySendActionTypingHalfPeriod) / st::historySendActionTypingHalfPeriod)); } } p.drawEllipse(position, r, r); position.setX(position.x() + st::historySendActionTypingDelta); frameMs = (frameMs + st::historySendActionTypingDuration - st::historySendActionTypingDeltaTime) % st::historySendActionTypingDuration; } } class RecordAnimation : public SendActionAnimation::Impl { public: RecordAnimation() : Impl(st::historySendActionRecordDuration) { } static const MetaData kMeta; static std::unique_ptr<Impl> create() { return std::make_unique<RecordAnimation>(); } const MetaData *metaData() const override { return &kMeta; } int width() const override { return st::historySendActionRecordPosition.x() + (kRecordArcsCount + 1) * st::historySendActionRecordDelta; } private: void paintFrame(Painter &p, style::color color, int x, int y, int outerWidth, int frameMs) override; }; const RecordAnimation::MetaData RecordAnimation::kMeta = { 0, &RecordAnimation::create }; void RecordAnimation::paintFrame(Painter &p, style::color color, int x, int y, int outerWidth, int frameMs) { PainterHighQualityEnabler hq(p); auto pen = color->p; pen.setWidth(st::historySendActionRecordStrokeNumerator / st::historySendActionRecordDenominator); pen.setJoinStyle(Qt::RoundJoin); pen.setCapStyle(Qt::RoundCap); p.setPen(pen); p.setBrush(Qt::NoBrush); auto progress = frameMs / float64(st::historySendActionRecordDuration); auto size = st::historySendActionRecordPosition.x() + st::historySendActionRecordDelta * progress; y += st::historySendActionRecordPosition.y(); for (auto i = 0; i != kRecordArcsCount; ++i) { p.setOpacity((i == 0) ? progress : (i == kRecordArcsCount - 1) ? (1. - progress) : 1.); auto rect = QRectF(x - size, y - size, 2 * size, 2 * size); p.drawArc(rect, -FullArcLength / 24, FullArcLength / 12); size += st::historySendActionRecordDelta; } p.setOpacity(1.); } class UploadAnimation : public SendActionAnimation::Impl { public: UploadAnimation() : Impl(st::historySendActionUploadDuration) { } static const MetaData kMeta; static std::unique_ptr<Impl> create() { return std::make_unique<UploadAnimation>(); } const MetaData *metaData() const override { return &kMeta; } int width() const override { return st::historySendActionUploadPosition.x() + (kUploadArrowsCount + 1) * st::historySendActionUploadDelta; } private: void paintFrame(Painter &p, style::color color, int x, int y, int outerWidth, int frameMs) override; }; const UploadAnimation::MetaData UploadAnimation::kMeta = { 0, &UploadAnimation::create }; void UploadAnimation::paintFrame(Painter &p, style::color color, int x, int y, int outerWidth, int frameMs) { PainterHighQualityEnabler hq(p); auto pen = color->p; pen.setWidth(st::historySendActionUploadStrokeNumerator / st::historySendActionUploadDenominator); pen.setJoinStyle(Qt::RoundJoin); pen.setCapStyle(Qt::RoundCap); p.setPen(pen); p.setBrush(Qt::NoBrush); auto progress = frameMs / float64(st::historySendActionUploadDuration); auto position = QPointF(x + st::historySendActionUploadDelta * progress, y) + st::historySendActionUploadPosition; auto path = QPainterPath(); path.moveTo(0., -st::historySendActionUploadSizeNumerator / st::historySendActionUploadDenominator); path.lineTo(st::historySendActionUploadSizeNumerator / st::historySendActionUploadDenominator, 0.); path.lineTo(0., st::historySendActionUploadSizeNumerator / st::historySendActionUploadDenominator); p.translate(position); for (auto i = 0; i != kUploadArrowsCount; ++i) { p.setOpacity((i == 0) ? progress : (i == kUploadArrowsCount - 1) ? (1. - progress) : 1.); p.drawPath(path); position.setX(position.x() + st::historySendActionUploadDelta); p.translate(st::historySendActionUploadDelta, 0); } p.setOpacity(1.); p.translate(-position); } void CreateImplementationsMap() { if (Implementations) { return; } using Type = SendAction::Type; Implementations.createIfNull(); Type recordTypes[] = { Type::RecordVideo, Type::RecordVoice, Type::RecordRound, }; for_const (auto type, recordTypes) { Implementations->insert(type, &RecordAnimation::kMeta); } Type uploadTypes[] = { Type::UploadFile, Type::UploadPhoto, Type::UploadVideo, Type::UploadVoice, Type::UploadRound, }; for_const (auto type, uploadTypes) { Implementations->insert(type, &UploadAnimation::kMeta); } } } // namespace bool SendActionAnimation::Impl::supports(Type type) const { CreateImplementationsMap(); return Implementations->value(type, &TypingAnimation::kMeta) == metaData(); } void SendActionAnimation::start(Type type) { if (!_impl || !_impl->supports(type)) { _impl = createByType(type); } } void SendActionAnimation::stop() { _impl.reset(); } std::unique_ptr<SendActionAnimation::Impl> SendActionAnimation::createByType(Type type) { CreateImplementationsMap(); return Implementations->value(type, &TypingAnimation::kMeta)->creator(); } SendActionAnimation::~SendActionAnimation() = default; } // namespace Ui �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/effects/send_action_animations.h����������������������������0000664�0000000�0000000�00000002711�13262451251�0026341�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace Ui { class SendActionAnimation { public: using Type = SendAction::Type; void start(Type type); void stop(); int width() const { return _impl ? _impl->width() : 0; } void paint(Painter &p, style::color color, int x, int y, int outerWidth, TimeMs ms) { if (_impl) { _impl->paint(p, color, x, y, outerWidth, ms); } } explicit operator bool() const { return _impl != nullptr; } class Impl { public: using Type = SendAction::Type; Impl(int period) : _period(period), _started(getms()) { } struct MetaData { int index; std::unique_ptr<Impl> (*creator)(); }; virtual const MetaData *metaData() const = 0; bool supports(Type type) const; virtual int width() const = 0; void paint(Painter &p, style::color color, int x, int y, int outerWidth, TimeMs ms) { paintFrame(p, color, x, y, outerWidth, qMax(ms - _started, 0LL) % _period); } virtual ~Impl() = default; private: virtual void paintFrame(Painter &p, style::color color, int x, int y, int outerWidth, int frameMs) = 0; int _period = 1; TimeMs _started = 0; }; ~SendActionAnimation(); private: std::unique_ptr<Impl> createByType(Type type); std::unique_ptr<Impl> _impl; }; } // namespace Ui �������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/effects/slide_animation.cpp���������������������������������0000664�0000000�0000000�00000004052�13262451251�0025323�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/effects/slide_animation.h" namespace Ui { void SlideAnimation::setSnapshots(QPixmap leftSnapshot, QPixmap rightSnapshot) { _leftSnapshot = std::move(leftSnapshot); _rightSnapshot = std::move(rightSnapshot); Assert(!_leftSnapshot.isNull()); Assert(!_rightSnapshot.isNull()); _leftSnapshot.setDevicePixelRatio(cRetinaFactor()); _rightSnapshot.setDevicePixelRatio(cRetinaFactor()); } void SlideAnimation::paintFrame(Painter &p, int x, int y, int outerWidth, TimeMs ms) { auto dt = _animation.current(ms, 1.); if (!animating()) return; auto easeOut = anim::easeOutCirc(1., dt); auto easeIn = anim::easeInCirc(1., dt); auto arrivingAlpha = easeIn; auto departingAlpha = 1. - easeOut; auto leftCoord = (_slideLeft ? anim::interpolate(-_leftSnapshotWidth, 0, easeOut) : anim::interpolate(0, -_leftSnapshotWidth, easeIn)); auto leftAlpha = (_slideLeft ? arrivingAlpha : departingAlpha); auto rightCoord = (_slideLeft ? anim::interpolate(0, _rightSnapshotWidth, easeIn) : anim::interpolate(_rightSnapshotWidth, 0, easeOut)); auto rightAlpha = (_slideLeft ? departingAlpha : arrivingAlpha); if (_overflowHidden) { auto leftWidth = (_leftSnapshotWidth + leftCoord); if (leftWidth > 0) { p.setOpacity(leftAlpha); p.drawPixmap(x, y, leftWidth, _leftSnapshotHeight, _leftSnapshot, (_leftSnapshot.width() - leftWidth * cIntRetinaFactor()), 0, leftWidth * cIntRetinaFactor(), _leftSnapshot.height()); } auto rightWidth = _rightSnapshotWidth - rightCoord; if (rightWidth > 0) { p.setOpacity(rightAlpha); p.drawPixmap(x + rightCoord, y, _rightSnapshot, 0, 0, rightWidth * cIntRetinaFactor(), _rightSnapshot.height()); } } else { p.setOpacity(leftAlpha); p.drawPixmap(x + leftCoord, y, _leftSnapshot); p.setOpacity(rightAlpha); p.drawPixmap(x + rightCoord, y, _rightSnapshot); } } } // namespace Ui ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/effects/slide_animation.h�����������������������������������0000664�0000000�0000000�00000002601�13262451251�0024766�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace Ui { class SlideAnimation { public: void setSnapshots(QPixmap leftSnapshot, QPixmap rightSnapshot); void setOverflowHidden(bool hidden) { _overflowHidden = hidden; } template <typename Lambda> void start(bool slideLeft, Lambda &&updateCallback, float64 duration); void paintFrame(Painter &p, int x, int y, int outerWidth, TimeMs ms); bool animating() const { return _animation.animating(); } private: Animation _animation; QPixmap _leftSnapshot; QPixmap _rightSnapshot; bool _slideLeft = false; bool _overflowHidden = true; int _leftSnapshotWidth = 0; int _leftSnapshotHeight = 0; int _rightSnapshotWidth = 0; }; template <typename Lambda> void SlideAnimation::start(bool slideLeft, Lambda &&updateCallback, float64 duration) { _slideLeft = slideLeft; if (_slideLeft) { std::swap(_leftSnapshot, _rightSnapshot); } _leftSnapshotWidth = _leftSnapshot.width() / cIntRetinaFactor(); _leftSnapshotHeight = _leftSnapshot.height() / cIntRetinaFactor(); _rightSnapshotWidth = _rightSnapshot.width() / cIntRetinaFactor(); _animation.start(std::forward<Lambda>(updateCallback), 0., 1., duration); } } // namespace Ui �������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/effects/widget_slide_wrap.cpp�������������������������������0000664�0000000�0000000�00000007462�13262451251�0025670�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/effects/widget_slide_wrap.h" namespace Ui { PaddingWrap<RpWidget>::PaddingWrap( QWidget *parent, object_ptr<RpWidget> child, const style::margins &padding) : Parent(parent, std::move(child)) , _padding(padding) { if (auto weak = wrapped()) { auto margins = weak->getMargins(); weak->sizeValue() | rpl::on_next([this](QSize&&) { updateSize(); }) | rpl::start(lifetime()); weak->moveToLeft(_padding.left() + margins.left(), _padding.top() + margins.top()); } } void PaddingWrap<RpWidget>::updateSize() { auto inner = [this] { if (auto weak = wrapped()) { return weak->rect(); } return QRect(0, 0, _innerWidth, 0); }(); resize(inner.marginsAdded(_padding).size()); } int PaddingWrap<RpWidget>::naturalWidth() const { auto inner = [this] { if (auto weak = wrapped()) { return weak->naturalWidth(); } return RpWidget::naturalWidth(); }(); return (inner < 0) ? inner : (_padding.left() + inner + _padding.right()); } int PaddingWrap<RpWidget>::resizeGetHeight(int newWidth) { _innerWidth = newWidth; if (auto weak = wrapped()) { weak->resizeToWidth(newWidth - _padding.left() - _padding.right()); } else { updateSize(); } return height(); } SlideWrap<RpWidget>::SlideWrap( QWidget *parent, object_ptr<RpWidget> child) : SlideWrap( parent, std::move(child), style::margins(), st::slideWrapDuration) { } SlideWrap<RpWidget>::SlideWrap( QWidget *parent, object_ptr<RpWidget> child, const style::margins &padding) : SlideWrap( parent, std::move(child), padding, st::slideWrapDuration) { } SlideWrap<RpWidget>::SlideWrap( QWidget *parent, object_ptr<RpWidget> child, int duration) : SlideWrap(parent, std::move(child), style::margins(), duration) { } SlideWrap<RpWidget>::SlideWrap( QWidget *parent, object_ptr<RpWidget> child, const style::margins &padding, int duration) : Parent(parent, object_ptr<PaddingWrap<RpWidget>>(parent, std::move(child), padding)) , _duration(duration * 10) { if (auto weak = wrapped()) { weak->heightValue() | rpl::on_next([this](int newHeight) { if (_slideAnimation.animating()) { animationStep(); } else if (_visible) { resize(width(), newHeight); } }) | rpl::start(lifetime()); } } void SlideWrap<RpWidget>::animationStep() { if (wrapped()) { auto margins = getMargins(); wrapped()->moveToLeft(margins.left(), margins.top()); } auto current = _slideAnimation.current(_visible ? 1. : 0.); auto newHeight = wrapped() ? (_slideAnimation.animating() ? anim::interpolate(0, wrapped()->heightNoMargins(), current) : (_visible ? wrapped()->height() : 0)) : 0; if (newHeight != height()) { resize(width(), newHeight); } auto shouldBeHidden = !_visible && !_slideAnimation.animating(); if (shouldBeHidden != isHidden()) { setVisible(!shouldBeHidden); } } void SlideWrap<RpWidget>::toggleAnimated(bool visible) { if (_visible == visible) { animationStep(); return; } _visible = visible; _slideAnimation.start( [this] { animationStep(); }, _visible ? 0. : 1., _visible ? 1. : 0., _duration, anim::linear); animationStep(); } void SlideWrap<RpWidget>::toggleFast(bool visible) { _visible = visible; finishAnimations(); } void SlideWrap<RpWidget>::finishAnimations() { _slideAnimation.finish(); animationStep(); } QMargins SlideWrap<RpWidget>::getMargins() const { auto result = wrapped()->getMargins(); return (animating() || !_visible) ? QMargins(result.left(), 0, result.right(), 0) : result; } int SlideWrap<RpWidget>::resizeGetHeight(int newWidth) { if (wrapped()) { wrapped()->resizeToWidth(newWidth); } return height(); } } // namespace Ui ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/effects/widget_slide_wrap.h���������������������������������0000664�0000000�0000000�00000012760�13262451251�0025332�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "styles/style_widgets.h" #include "ui/rp_widget.h" namespace Ui { template <typename Widget, typename ParentType = RpWidget> class Wrap; namespace details { struct UnwrapHelper { struct Large { char data[2]; }; static char Check(...); template <typename Widget, typename Parent> static Large Check(Wrap<Widget, Parent>*); template <typename Widget, typename Parent> static Large Check(const Wrap<Widget, Parent>*); template <typename Entity> static constexpr bool Is() { return sizeof(Check(std::declval<Entity>())) == sizeof(Large); } template <typename Entity> static auto Unwrap(Entity *entity, std::true_type) { return entity ? entity->entity() : nullptr; } template <typename Entity> static Entity *Unwrap(Entity *entity, std::false_type) { return entity; } template <typename Entity> static auto Unwrap(Entity *entity) { return Unwrap( entity, std::integral_constant<bool, Is<Entity*>()>()); } }; } // namespace details template <typename Widget> class Wrap<Widget, RpWidget> : public RpWidget { public: Wrap(QWidget *parent, object_ptr<Widget> child); Widget *wrapped() { return _wrapped; } const Widget *wrapped() const { return _wrapped; } auto entity() { return details::UnwrapHelper::Unwrap(wrapped()); } auto entity() const { return details::UnwrapHelper::Unwrap(wrapped()); } QMargins getMargins() const override { if (auto weak = wrapped()) { return weak->getMargins(); } return RpWidget::getMargins(); } int naturalWidth() const override { if (auto weak = wrapped()) { return weak->naturalWidth(); } return RpWidget::naturalWidth(); } private: object_ptr<Widget> _wrapped; }; template <typename Widget> Wrap<Widget, RpWidget>::Wrap(QWidget *parent, object_ptr<Widget> child) : RpWidget(parent) , _wrapped(std::move(child)) { if (_wrapped) { resize(_wrapped->size()); AttachParentChild(this, _wrapped); _wrapped->move(0, 0); _wrapped->alive() | rpl::on_done([this] { _wrapped->setParent(nullptr); _wrapped = nullptr; delete this; }) | rpl::start(lifetime()); } } template <typename Widget, typename ParentType> class Wrap : public ParentType { public: using ParentType::ParentType; Widget *wrapped() { return static_cast<Widget*>(ParentType::wrapped()); } const Widget *wrapped() const { return static_cast<const Widget*>(ParentType::wrapped()); } auto entity() { return details::UnwrapHelper::Unwrap(wrapped()); } auto entity() const { return details::UnwrapHelper::Unwrap(wrapped()); } }; template <typename Widget> class PaddingWrap; template <> class PaddingWrap<RpWidget> : public Wrap<RpWidget> { using Parent = Wrap<RpWidget>; public: PaddingWrap( QWidget *parent, object_ptr<RpWidget> child, const style::margins &padding); PaddingWrap( QWidget *parent, const style::margins &padding) : PaddingWrap(parent, nullptr, padding) { } int naturalWidth() const override; protected: int resizeGetHeight(int newWidth) override; private: void updateSize(); int _innerWidth = 0; style::margins _padding; }; template <typename Widget> class PaddingWrap : public Wrap<Widget, PaddingWrap<RpWidget>> { using Parent = Wrap<Widget, PaddingWrap<RpWidget>>; public: PaddingWrap( QWidget *parent, object_ptr<Widget> child, const style::margins &padding) : Parent(parent, std::move(child), padding) { } PaddingWrap(QWidget *parent, const style::margins &padding) : Parent(parent, padding) { } }; template <typename Widget> class SlideWrap; template <> class SlideWrap<RpWidget> : public Wrap<PaddingWrap<RpWidget>> { using Parent = Wrap<PaddingWrap<RpWidget>>; public: SlideWrap(QWidget *parent, object_ptr<RpWidget> child); SlideWrap( QWidget *parent, object_ptr<RpWidget> child, const style::margins &padding); SlideWrap( QWidget *parent, object_ptr<RpWidget> child, int duration); SlideWrap( QWidget *parent, object_ptr<RpWidget> child, const style::margins &padding, int duration); void toggleAnimated(bool visible); void toggleFast(bool visible); void showAnimated() { toggleAnimated(true); } void hideAnimated() { toggleAnimated(false); } void showFast() { toggleFast(true); } void hideFast() { toggleFast(false); } bool animating() const { return _slideAnimation.animating(); } void finishAnimations(); QMargins getMargins() const override; bool isHiddenOrHiding() const { return !_visible; } protected: int resizeGetHeight(int newWidth) override; private: void animationStep(); bool _visible = true; Animation _slideAnimation; int _duration = 0; }; template <typename Widget> class SlideWrap : public Wrap<PaddingWrap<Widget>, SlideWrap<RpWidget>> { using Parent = Wrap<PaddingWrap<Widget>, SlideWrap<RpWidget>>; public: SlideWrap(QWidget *parent, object_ptr<Widget> child) : Parent(parent, std::move(child)) { } SlideWrap( QWidget *parent, object_ptr<Widget> child, const style::margins &padding) : Parent(parent, std::move(child), padding) { } SlideWrap( QWidget *parent, object_ptr<Widget> child, int duration) : Parent(parent, std::move(child), duration) { } SlideWrap( QWidget *parent, object_ptr<Widget> child, const style::margins &padding, int duration) : Parent(parent, std::move(child), padding, duration) { } }; } // namespace Ui ����������������tdesktop-1.2.17/Telegram/SourceFiles/ui/emoji_config.cpp��������������������������������������������0000664�0000000�0000000�00000023550�13262451251�0023201�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* WARNING! All changes made in this file will be lost! Created from 'empty' by 'codegen_emoji' This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "emoji_config.h" #include "chat_helpers/emoji_suggestions_helper.h" #include "auth_session.h" namespace Ui { namespace Emoji { namespace { constexpr auto kSaveRecentEmojiTimeout = 3000; auto WorkingIndex = -1; void AppendPartToResult(TextWithEntities &result, const QChar *start, const QChar *from, const QChar *to) { if (to <= from) { return; } for (auto &entity : result.entities) { if (entity.offset() >= to - start) break; if (entity.offset() + entity.length() < from - start) continue; if (entity.offset() >= from - start) { entity.extendToLeft(from - start - result.text.size()); } if (entity.offset() + entity.length() <= to - start) { entity.shrinkFromRight(from - start - result.text.size()); } } result.text.append(from, to - from); } bool IsReplacementPart(ushort ch) { return (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || (ch == '-') || (ch == '+') || (ch == '_'); } EmojiPtr FindReplacement(const QChar *start, const QChar *end, int *outLength) { if (start != end && *start == ':') { auto maxLength = GetSuggestionMaxLength(); for (auto till = start + 1; till != end; ++till) { if (*till == ':') { auto text = QString::fromRawData(start, till + 1 - start); auto emoji = GetSuggestionEmoji(QStringToUTF16(text)); auto result = Find(QStringFromUTF16(emoji)); if (result) { if (outLength) *outLength = (till + 1 - start); } return result; } else if (!IsReplacementPart(till->unicode()) || (till - start) > maxLength) { break; } } } return internal::FindReplace(start, end, outLength); } } // namespace void Init() { auto scaleForEmoji = cRetina() ? dbisTwo : cScale(); switch (scaleForEmoji) { case dbisOne: WorkingIndex = 0; break; case dbisOneAndQuarter: WorkingIndex = 1; break; case dbisOneAndHalf: WorkingIndex = 2; break; case dbisTwo: WorkingIndex = 3; break; }; internal::Init(); } int Index() { return WorkingIndex; } int One::variantsCount() const { return hasVariants() ? 5 : 0; } int One::variantIndex(EmojiPtr variant) const { return (variant - original()); } EmojiPtr One::variant(int index) const { return (index >= 0 && index <= variantsCount()) ? (original() + index) : this; } int One::index() const { return (this - internal::ByIndex(0)); } QString IdFromOldKey(uint64 oldKey) { auto code = uint32(oldKey >> 32); auto code2 = uint32(oldKey & 0xFFFFFFFFLLU); if (!code && code2) { code = base::take(code2); } if ((code & 0xFFFF0000U) != 0xFFFF0000U) { // code and code2 contain the whole id auto result = QString(); result.reserve(4); auto addCode = [&result](uint32 code) { if (auto high = (code >> 16)) { result.append(QChar(static_cast<ushort>(high & 0xFFFFU))); } result.append(QChar(static_cast<ushort>(code & 0xFFFFU))); }; addCode(code); if (code2) addCode(code2); return result; } // old sequence auto sequenceIndex = int(code & 0xFFFFU); switch (sequenceIndex) { case 0: return QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa7"); case 1: return QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa7\xe2\x80\x8d\xf0\x9f\x91\xa6"); case 2: return QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa6\xe2\x80\x8d\xf0\x9f\x91\xa6"); case 3: return QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa7\xe2\x80\x8d\xf0\x9f\x91\xa7"); case 4: return QString::fromUtf8("\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa6"); case 5: return QString::fromUtf8("\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa7"); case 6: return QString::fromUtf8("\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa7\xe2\x80\x8d\xf0\x9f\x91\xa6"); case 7: return QString::fromUtf8("\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa6\xe2\x80\x8d\xf0\x9f\x91\xa6"); case 8: return QString::fromUtf8("\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa7\xe2\x80\x8d\xf0\x9f\x91\xa7"); case 9: return QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa6"); case 10: return QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa7"); case 11: return QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa7\xe2\x80\x8d\xf0\x9f\x91\xa6"); case 12: return QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa6\xe2\x80\x8d\xf0\x9f\x91\xa6"); case 13: return QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa7\xe2\x80\x8d\xf0\x9f\x91\xa7"); case 14: return QString::fromUtf8("\xf0\x9f\x91\xa9\xe2\x80\x8d\xe2\x9d\xa4\xef\xb8\x8f\xe2\x80\x8d\xf0\x9f\x91\xa9"); case 15: return QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xe2\x9d\xa4\xef\xb8\x8f\xe2\x80\x8d\xf0\x9f\x91\xa8"); case 16: return QString::fromUtf8("\xf0\x9f\x91\xa9\xe2\x80\x8d\xe2\x9d\xa4\xef\xb8\x8f\xe2\x80\x8d\xf0\x9f\x92\x8b\xe2\x80\x8d\xf0\x9f\x91\xa9"); case 17: return QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xe2\x9d\xa4\xef\xb8\x8f\xe2\x80\x8d\xf0\x9f\x92\x8b\xe2\x80\x8d\xf0\x9f\x91\xa8"); case 18: return QString::fromUtf8("\xf0\x9f\x91\x81\xe2\x80\x8d\xf0\x9f\x97\xa8"); } return QString(); } void ReplaceInText(TextWithEntities &result) { auto newText = TextWithEntities(); newText.entities = std::move(result.entities); auto currentEntity = newText.entities.begin(); auto entitiesEnd = newText.entities.end(); auto emojiStart = result.text.constData(); auto emojiEnd = emojiStart; auto end = emojiStart + result.text.size(); auto canFindEmoji = true; for (auto ch = emojiEnd; ch != end;) { auto emojiLength = 0; auto emoji = canFindEmoji ? FindReplacement(ch, end, &emojiLength) : nullptr; auto newEmojiEnd = ch + emojiLength; while (currentEntity != entitiesEnd && ch >= emojiStart + currentEntity->offset() + currentEntity->length()) { ++currentEntity; } if (emoji && (ch == emojiStart || !ch->isLetterOrNumber() || !(ch - 1)->isLetterOrNumber()) && (newEmojiEnd == end || !newEmojiEnd->isLetterOrNumber() || newEmojiEnd == emojiStart || !(newEmojiEnd - 1)->isLetterOrNumber()) && (currentEntity == entitiesEnd || (ch < emojiStart + currentEntity->offset() && newEmojiEnd <= emojiStart + currentEntity->offset()) || (ch >= emojiStart + currentEntity->offset() + currentEntity->length() && newEmojiEnd > emojiStart + currentEntity->offset() + currentEntity->length())) ) { if (newText.text.isEmpty()) newText.text.reserve(result.text.size()); AppendPartToResult(newText, emojiStart, emojiEnd, ch); if (emoji->hasVariants()) { auto it = cEmojiVariants().constFind(emoji->nonColoredId()); if (it != cEmojiVariants().cend()) { emoji = emoji->variant(it.value()); } } newText.text.append(emoji->text()); ch = emojiEnd = newEmojiEnd; canFindEmoji = true; } else { if (internal::IsReplaceEdge(ch)) { canFindEmoji = true; } else { canFindEmoji = false; } ++ch; } } if (newText.text.isEmpty()) { result.entities = std::move(newText.entities); } else { AppendPartToResult(newText, emojiStart, emojiEnd, end); result = std::move(newText); } } RecentEmojiPack &GetRecent() { if (cRecentEmoji().isEmpty()) { RecentEmojiPack result; auto haveAlready = [&result](EmojiPtr emoji) { for (auto &row : result) { if (row.first->id() == emoji->id()) { return true; } } return false; }; if (!cRecentEmojiPreload().isEmpty()) { auto preload = cRecentEmojiPreload(); cSetRecentEmojiPreload(RecentEmojiPreload()); result.reserve(preload.size()); for (auto i = preload.cbegin(), e = preload.cend(); i != e; ++i) { if (auto emoji = Ui::Emoji::Find(i->first)) { if (!haveAlready(emoji)) { result.push_back(qMakePair(emoji, i->second)); } } } } auto defaultRecent = { 0xD83DDE02LLU, 0xD83DDE18LLU, 0x2764LLU, 0xD83DDE0DLLU, 0xD83DDE0ALLU, 0xD83DDE01LLU, 0xD83DDC4DLLU, 0x263ALLU, 0xD83DDE14LLU, 0xD83DDE04LLU, 0xD83DDE2DLLU, 0xD83DDC8BLLU, 0xD83DDE12LLU, 0xD83DDE33LLU, 0xD83DDE1CLLU, 0xD83DDE48LLU, 0xD83DDE09LLU, 0xD83DDE03LLU, 0xD83DDE22LLU, 0xD83DDE1DLLU, 0xD83DDE31LLU, 0xD83DDE21LLU, 0xD83DDE0FLLU, 0xD83DDE1ELLU, 0xD83DDE05LLU, 0xD83DDE1ALLU, 0xD83DDE4ALLU, 0xD83DDE0CLLU, 0xD83DDE00LLU, 0xD83DDE0BLLU, 0xD83DDE06LLU, 0xD83DDC4CLLU, 0xD83DDE10LLU, 0xD83DDE15LLU, }; for (auto oldKey : defaultRecent) { if (result.size() >= kRecentLimit) break; if (auto emoji = Ui::Emoji::FromOldKey(oldKey)) { if (!haveAlready(emoji)) { result.push_back(qMakePair(emoji, 1)); } } } cSetRecentEmoji(result); } return cRefRecentEmoji(); } void AddRecent(EmojiPtr emoji) { auto &recent = GetRecent(); auto i = recent.begin(), e = recent.end(); for (; i != e; ++i) { if (i->first == emoji) { ++i->second; if (i->second > 0x8000) { for (auto j = recent.begin(); j != e; ++j) { if (j->second > 1) { j->second /= 2; } else { j->second = 1; } } } for (; i != recent.begin(); --i) { if ((i - 1)->second > i->second) { break; } qSwap(*i, *(i - 1)); } break; } } if (i == e) { while (recent.size() >= kRecentLimit) { recent.pop_back(); } recent.push_back(qMakePair(emoji, 1)); for (i = recent.end() - 1; i != recent.begin(); --i) { if ((i - 1)->second > i->second) { break; } qSwap(*i, *(i - 1)); } } } } // namespace Emoji } // namespace Ui ��������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/emoji_config.h����������������������������������������������0000664�0000000�0000000�00000006167�13262451251�0022653�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/text/text.h" #include "emoji.h" namespace Ui { namespace Emoji { constexpr auto kPostfix = static_cast<ushort>(0xFE0F); constexpr auto kRecentLimit = 42; void Init(); class One { struct CreationTag { }; public: One(One &&other) = default; One(const QString &id, uint16 x, uint16 y, bool hasPostfix, bool colorizable, EmojiPtr original, const CreationTag &) : _id(id) , _x(x) , _y(y) , _hasPostfix(hasPostfix) , _colorizable(colorizable) , _original(original) { Expects(!_colorizable || !colored()); } QString id() const { return _id; } QString text() const { return hasPostfix() ? (_id + QChar(kPostfix)) : _id; } bool colored() const { return (_original != nullptr); } EmojiPtr original() const { return _original ? _original : this; } QString nonColoredId() const { return original()->id(); } bool hasPostfix() const { return _hasPostfix; } bool hasVariants() const { return _colorizable || colored(); } int variantsCount() const; int variantIndex(EmojiPtr variant) const; EmojiPtr variant(int index) const; int index() const; QString toUrl() const { return qsl("emoji://e.") + QString::number(index()); } int x() const { return _x; } int y() const { return _y; } private: const QString _id; const uint16 _x = 0; const uint16 _y = 0; const bool _hasPostfix = false; const bool _colorizable = false; const EmojiPtr _original = nullptr; friend void internal::Init(); }; inline EmojiPtr FromUrl(const QString &url) { auto start = qstr("emoji://e."); if (url.startsWith(start)) { return internal::ByIndex(url.midRef(start.size()).toInt()); // skip emoji://e. } return nullptr; } inline EmojiPtr Find(const QChar *start, const QChar *end, int *outLength = nullptr) { return internal::Find(start, end, outLength); } inline EmojiPtr Find(const QString &text, int *outLength = nullptr) { return Find(text.constBegin(), text.constEnd(), outLength); } QString IdFromOldKey(uint64 oldKey); inline EmojiPtr FromOldKey(uint64 oldKey) { return Find(IdFromOldKey(oldKey)); } inline int ColorIndexFromCode(uint32 code) { switch (code) { case 0xD83CDFFBU: return 1; case 0xD83CDFFCU: return 2; case 0xD83CDFFDU: return 3; case 0xD83CDFFEU: return 4; case 0xD83CDFFFU: return 5; } return 0; } inline int ColorIndexFromOldKey(uint64 oldKey) { return ColorIndexFromCode(uint32(oldKey & 0xFFFFFFFFLLU)); } inline int Size(int index = Index()) { int sizes[] = { 18, 22, 27, 36, 45 }; return sizes[index]; } inline QString Filename(int index = Index()) { const char *EmojiNames[] = { ":/gui/art/emoji.webp", ":/gui/art/emoji_125x.webp", ":/gui/art/emoji_150x.webp", ":/gui/art/emoji_200x.webp", ":/gui/art/emoji_250x.webp", }; return QString::fromLatin1(EmojiNames[index]); } void ReplaceInText(TextWithEntities &result); RecentEmojiPack &GetRecent(); void AddRecent(EmojiPtr emoji); } // namespace Emoji } // namespace Ui ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/empty_userpic.cpp�������������������������������������������0000664�0000000�0000000�00000013445�13262451251�0023443�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/empty_userpic.h" #include "data/data_peer.h" #include "styles/style_history.h" namespace Ui { EmptyUserpic::EmptyUserpic(const style::color &color, const QString &name) : _color(color) { fillString(name); } template <typename Callback> void EmptyUserpic::paint( Painter &p, int x, int y, int outerWidth, int size, Callback paintBackground) const { x = rtl() ? (outerWidth - x - size) : x; const auto fontsize = (size * 13) / 33; auto font = st::historyPeerUserpicFont->f; font.setPixelSize(fontsize); PainterHighQualityEnabler hq(p); p.setBrush(_color); p.setPen(Qt::NoPen); paintBackground(); p.setFont(font); p.setBrush(Qt::NoBrush); p.setPen(st::historyPeerUserpicFg); p.drawText(QRect(x, y, size, size), _string, QTextOption(style::al_center)); } void EmptyUserpic::paint( Painter &p, int x, int y, int outerWidth, int size) const { paint(p, x, y, outerWidth, size, [&p, x, y, size] { p.drawEllipse(x, y, size, size); }); } void EmptyUserpic::paintRounded(Painter &p, int x, int y, int outerWidth, int size) const { paint(p, x, y, outerWidth, size, [&p, x, y, size] { p.drawRoundedRect(x, y, size, size, st::buttonRadius, st::buttonRadius); }); } void EmptyUserpic::paintSquare(Painter &p, int x, int y, int outerWidth, int size) const { paint(p, x, y, outerWidth, size, [&p, x, y, size] { p.fillRect(x, y, size, size, p.brush()); }); } void EmptyUserpic::PaintSavedMessages( Painter &p, int x, int y, int outerWidth, int size) { const auto &bg = st::historyPeerSavedMessagesBg; const auto &fg = st::historyPeerUserpicFg; PaintSavedMessages(p, x, y, outerWidth, size, bg, fg); } void EmptyUserpic::PaintSavedMessages( Painter &p, int x, int y, int outerWidth, int size, const style::color &bg, const style::color &fg) { x = rtl() ? (outerWidth - x - size) : x; PainterHighQualityEnabler hq(p); p.setBrush(bg); p.setPen(Qt::NoPen); p.drawEllipse(x, y, size, size); // |<----width----->| // // XXXXXXXXXXXXXXXXXX --- // X X | // X X | // X X | // X X height // X XX X | --- // X XX XX X | | // X XX XX X | add // X XX XX X | | // XX XX --- --- const auto thinkness = std::round(size * 0.055); const auto increment = int(thinkness) % 2 + (size % 2); const auto width = std::round(size * 0.15) * 2 + increment; const auto height = std::round(size * 0.19) * 2 + increment; const auto add = std::round(size * 0.064); const auto left = x + (size - width) / 2; const auto top = y + (size - height) / 2; const auto right = left + width; const auto bottom = top + height; const auto middle = (left + right) / 2; const auto half = (top + bottom) / 2; p.setBrush(Qt::NoBrush); auto pen = fg->p; pen.setWidthF(thinkness); pen.setCapStyle(Qt::FlatCap); { // XXXXXXXXXXXXXXXXXX // X X // X X // X X // X X // X X pen.setJoinStyle(Qt::RoundJoin); p.setPen(pen); QPainterPath path; path.moveTo(left, half); path.lineTo(left, top); path.lineTo(right, top); path.lineTo(right, half); p.drawPath(path); } { // X X // X XX X // X XX XX X // X XX XX X // X XX XX X // XX XX pen.setJoinStyle(Qt::MiterJoin); p.setPen(pen); QPainterPath path; path.moveTo(left, half); path.lineTo(left, bottom); path.lineTo(middle, bottom - add); path.lineTo(right, bottom); path.lineTo(right, half); p.drawPath(path); } } StorageKey EmptyUserpic::uniqueKey() const { auto first = 0xFFFFFFFF00000000ULL | anim::getPremultiplied(_color->c); auto second = uint64(0); memcpy(&second, _string.constData(), qMin(sizeof(second), _string.size() * sizeof(QChar))); return StorageKey(first, second); } QPixmap EmptyUserpic::generate(int size) { auto result = QImage(QSize(size, size) * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); result.setDevicePixelRatio(cRetinaFactor()); result.fill(Qt::transparent); { Painter p(&result); paint(p, 0, 0, size, size); } return App::pixmapFromImageInPlace(std::move(result)); } void EmptyUserpic::fillString(const QString &name) { QList<QString> letters; QList<int> levels; auto level = 0; auto letterFound = false; auto ch = name.constData(), end = ch + name.size(); while (ch != end) { auto emojiLength = 0; if (auto emoji = Ui::Emoji::Find(ch, end, &emojiLength)) { ch += emojiLength; } else if (ch->isHighSurrogate()) { ++ch; if (ch != end && ch->isLowSurrogate()) { ++ch; } } else if (!letterFound && ch->isLetterOrNumber()) { letterFound = true; if (ch + 1 != end && chIsDiac(*(ch + 1))) { letters.push_back(QString(ch, 2)); levels.push_back(level); ++ch; } else { letters.push_back(QString(ch, 1)); levels.push_back(level); } ++ch; } else { if (*ch == ' ') { level = 0; letterFound = false; } else if (letterFound && *ch == '-') { level = 1; letterFound = true; } ++ch; } } // We prefer the second letter to be after ' ', but it can also be after '-'. _string = QString(); if (!letters.isEmpty()) { _string += letters.front(); auto bestIndex = 0; auto bestLevel = 2; for (auto i = letters.size(); i != 1;) { if (levels[--i] < bestLevel) { bestIndex = i; bestLevel = levels[i]; } } if (bestIndex > 0) { _string += letters[bestIndex]; } } _string = _string.toUpper(); } EmptyUserpic::~EmptyUserpic() = default; } // namespace Ui ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/empty_userpic.h���������������������������������������������0000664�0000000�0000000�00000002260�13262451251�0023101�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace Ui { class EmptyUserpic { public: EmptyUserpic(const style::color &color, const QString &name); void paint( Painter &p, int x, int y, int outerWidth, int size) const; void paintRounded( Painter &p, int x, int y, int outerWidth, int size) const; void paintSquare( Painter &p, int x, int y, int outerWidth, int size) const; QPixmap generate(int size); StorageKey uniqueKey() const; static void PaintSavedMessages( Painter &p, int x, int y, int outerWidth, int size); static void PaintSavedMessages( Painter &p, int x, int y, int outerWidth, int size, const style::color &bg, const style::color &fg); ~EmptyUserpic(); private: template <typename Callback> void paint( Painter &p, int x, int y, int outerWidth, int size, Callback paintBackground) const; void fillString(const QString &name); style::color _color; QString _string; }; } // namespace Ui ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/focus_persister.h�������������������������������������������0000664�0000000�0000000�00000001515�13262451251�0023432�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace Ui { class FocusPersister { public: FocusPersister(QWidget *parent, QWidget *steal = nullptr) : _weak(GrabFocused(parent)) { if (steal) { steal->setFocus(); } } ~FocusPersister() { if (auto strong = _weak.data()) { if (auto window = strong->window()) { if (window->focusWidget() != strong) { strong->setFocus(); } } } } private: static QWidget *GrabFocused(QWidget *parent) { if (auto window = parent ? parent->window() : nullptr) { return window->focusWidget(); } return nullptr; } QPointer<QWidget> _weak; }; } // namespace Ui �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/grouped_layout.cpp������������������������������������������0000664�0000000�0000000�00000037517�13262451251�0023623�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/grouped_layout.h" namespace Ui { namespace { int Round(float64 value) { return int(std::round(value)); } class Layouter { public: Layouter( const std::vector<QSize> &sizes, int maxWidth, int minWidth, int spacing); std::vector<GroupMediaLayout> layout() const; private: static std::vector<float64> CountRatios(const std::vector<QSize> &sizes); static std::string CountProportions(const std::vector<float64> &ratios); std::vector<GroupMediaLayout> layoutTwo() const; std::vector<GroupMediaLayout> layoutThree() const; std::vector<GroupMediaLayout> layoutFour() const; std::vector<GroupMediaLayout> layoutOne() const; std::vector<GroupMediaLayout> layoutTwoTopBottom() const; std::vector<GroupMediaLayout> layoutTwoLeftRightEqual() const; std::vector<GroupMediaLayout> layoutTwoLeftRight() const; std::vector<GroupMediaLayout> layoutThreeLeftAndOther() const; std::vector<GroupMediaLayout> layoutThreeTopAndOther() const; std::vector<GroupMediaLayout> layoutFourLeftAndOther() const; std::vector<GroupMediaLayout> layoutFourTopAndOther() const; const std::vector<QSize> &_sizes; const std::vector<float64> _ratios; const std::string _proportions; const int _count = 0; const int _maxWidth = 0; const int _maxHeight = 0; const int _minWidth = 0; const int _spacing = 0; const float64 _averageRatio = 1.; const float64 _maxSizeRatio = 1.; }; class ComplexLayouter { public: ComplexLayouter( const std::vector<float64> &ratios, float64 averageRatio, int maxWidth, int minWidth, int spacing); std::vector<GroupMediaLayout> layout() const; private: struct Attempt { std::vector<int> lineCounts; std::vector<float64> heights; }; static std::vector<float64> CropRatios( const std::vector<float64> &ratios, float64 averageRatio); const std::vector<float64> _ratios; const int _count = 0; const int _maxWidth = 0; const int _maxHeight = 0; const int _minWidth = 0; const int _spacing = 0; const float64 _averageRatio = 1.; }; Layouter::Layouter( const std::vector<QSize> &sizes, int maxWidth, int minWidth, int spacing) : _sizes(sizes) , _ratios(CountRatios(_sizes)) , _proportions(CountProportions(_ratios)) , _count(int(_ratios.size())) // All apps currently use square max size first. // In complex case they use maxWidth * 4 / 3 as maxHeight. , _maxWidth(maxWidth) , _maxHeight(maxWidth) , _minWidth(minWidth) , _spacing(spacing) , _averageRatio(ranges::accumulate(_ratios, 1.) / _count) , _maxSizeRatio(_maxWidth / float64(_maxHeight)) { } std::vector<float64> Layouter::CountRatios(const std::vector<QSize> &sizes) { return ranges::view::all( sizes ) | ranges::view::transform([](const QSize &size) { return size.width() / float64(size.height()); }) | ranges::to_vector; } std::string Layouter::CountProportions(const std::vector<float64> &ratios) { return ranges::view::all( ratios ) | ranges::view::transform([](float64 ratio) { return (ratio > 1.2) ? 'w' : (ratio < 0.8) ? 'n' : 'q'; }) | ranges::to_<std::string>(); } std::vector<GroupMediaLayout> Layouter::layout() const { if (!_count) { return {}; } else if (_count == 1) { return layoutOne(); } using namespace rpl::mappers; if (_count >= 5 || ranges::find_if(_ratios, _1 > 2) != _ratios.end()) { return ComplexLayouter( _ratios, _averageRatio, _maxWidth, _minWidth, _spacing).layout(); } if (_count == 2) { return layoutTwo(); } else if (_count == 3) { return layoutThree(); } return layoutFour(); } std::vector<GroupMediaLayout> Layouter::layoutTwo() const { Expects(_count == 2); if ((_proportions == "ww") && (_averageRatio > 1.4 * _maxSizeRatio) && (_ratios[1] - _ratios[0] < 0.2)) { return layoutTwoTopBottom(); } else if (_proportions == "ww" || _proportions == "qq") { return layoutTwoLeftRightEqual(); } return layoutTwoLeftRight(); } std::vector<GroupMediaLayout> Layouter::layoutThree() const { Expects(_count == 3); auto result = std::vector<GroupMediaLayout>(_count); if (_proportions[0] == 'n') { return layoutThreeLeftAndOther(); } return layoutThreeTopAndOther(); } std::vector<GroupMediaLayout> Layouter::layoutFour() const { Expects(_count == 4); auto result = std::vector<GroupMediaLayout>(_count); if (_proportions[0] == 'w') { return layoutFourTopAndOther(); } return layoutFourLeftAndOther(); } std::vector<GroupMediaLayout> Layouter::layoutOne() const { Expects(_count == 1); const auto width = _maxWidth; const auto height = (_sizes[0].height() * width) / _sizes[0].width(); return { { QRect(0, 0, width, height), RectPart::Left | RectPart::Top | RectPart::Right | RectPart::Bottom }, }; } std::vector<GroupMediaLayout> Layouter::layoutTwoTopBottom() const { Expects(_count == 2); const auto width = _maxWidth; const auto height = Round(std::min( width / _ratios[0], std::min( width / _ratios[1], (_maxHeight - _spacing) / 2.))); return { { QRect(0, 0, width, height), RectPart::Left | RectPart::Top | RectPart::Right }, { QRect(0, height + _spacing, width, height), RectPart::Left | RectPart::Bottom | RectPart::Right }, }; } std::vector<GroupMediaLayout> Layouter::layoutTwoLeftRightEqual() const { Expects(_count == 2); const auto width = (_maxWidth - _spacing) / 2; const auto height = Round(std::min( width / _ratios[0], std::min(width / _ratios[1], _maxHeight * 1.))); return { { QRect(0, 0, width, height), RectPart::Top | RectPart::Left | RectPart::Bottom }, { QRect(width + _spacing, 0, width, height), RectPart::Top | RectPart::Right | RectPart::Bottom }, }; } std::vector<GroupMediaLayout> Layouter::layoutTwoLeftRight() const { Expects(_count == 2); const auto minimalWidth = Round(_minWidth * 1.5); const auto secondWidth = std::min( Round(std::max( 0.4 * (_maxWidth - _spacing), (_maxWidth - _spacing) / _ratios[0] / (1. / _ratios[0] + 1. / _ratios[1]))), _maxWidth - _spacing - minimalWidth); const auto firstWidth = _maxWidth - secondWidth - _spacing; const auto height = std::min( _maxHeight, Round(std::min( firstWidth / _ratios[0], secondWidth / _ratios[1]))); return { { QRect(0, 0, firstWidth, height), RectPart::Top | RectPart::Left | RectPart::Bottom }, { QRect(firstWidth + _spacing, 0, secondWidth, height), RectPart::Top | RectPart::Right | RectPart::Bottom }, }; } std::vector<GroupMediaLayout> Layouter::layoutThreeLeftAndOther() const { Expects(_count == 3); const auto firstHeight = _maxHeight; const auto thirdHeight = Round(std::min( (_maxHeight - _spacing) / 2., (_ratios[1] * (_maxWidth - _spacing) / (_ratios[2] + _ratios[1])))); const auto secondHeight = firstHeight - thirdHeight - _spacing; const auto rightWidth = std::max( _minWidth, Round(std::min( (_maxWidth - _spacing) / 2., std::min( thirdHeight * _ratios[2], secondHeight * _ratios[1])))); const auto leftWidth = std::min( Round(firstHeight * _ratios[0]), _maxWidth - _spacing - rightWidth); return { { QRect(0, 0, leftWidth, firstHeight), RectPart::Top | RectPart::Left | RectPart::Bottom }, { QRect(leftWidth + _spacing, 0, rightWidth, secondHeight), RectPart::Top | RectPart::Right }, { QRect(leftWidth + _spacing, secondHeight + _spacing, rightWidth, thirdHeight), RectPart::Bottom | RectPart::Right }, }; } std::vector<GroupMediaLayout> Layouter::layoutThreeTopAndOther() const { Expects(_count == 3); const auto firstWidth = _maxWidth; const auto firstHeight = Round(std::min( firstWidth / _ratios[0], (_maxHeight - _spacing) * 0.66)); const auto secondWidth = (_maxWidth - _spacing) / 2; const auto secondHeight = std::min( _maxHeight - firstHeight - _spacing, Round(std::min( secondWidth / _ratios[1], secondWidth / _ratios[2]))); const auto thirdWidth = firstWidth - secondWidth - _spacing; return { { QRect(0, 0, firstWidth, firstHeight), RectPart::Left | RectPart::Top | RectPart::Right }, { QRect(0, firstHeight + _spacing, secondWidth, secondHeight), RectPart::Bottom | RectPart::Left }, { QRect(secondWidth + _spacing, firstHeight + _spacing, thirdWidth, secondHeight), RectPart::Bottom | RectPart::Right }, }; } std::vector<GroupMediaLayout> Layouter::layoutFourTopAndOther() const { Expects(_count == 4); const auto w = _maxWidth; const auto h0 = Round(std::min( w / _ratios[0], (_maxHeight - _spacing) * 0.66)); const auto h = Round( (_maxWidth - 2 * _spacing) / (_ratios[1] + _ratios[2] + _ratios[3])); const auto w0 = std::max( _minWidth, Round(std::min( (_maxWidth - 2 * _spacing) * 0.4, h * _ratios[1]))); const auto w2 = Round(std::max( std::max( _minWidth * 1., (_maxWidth - 2 * _spacing) * 0.33), h * _ratios[3])); const auto w1 = w - w0 - w2 - 2 * _spacing; const auto h1 = std::min( _maxHeight - h0 - _spacing, h); return { { QRect(0, 0, w, h0), RectPart::Left | RectPart::Top | RectPart::Right }, { QRect(0, h0 + _spacing, w0, h1), RectPart::Bottom | RectPart::Left }, { QRect(w0 + _spacing, h0 + _spacing, w1, h1), RectPart::Bottom, }, { QRect(w0 + _spacing + w1 + _spacing, h0 + _spacing, w2, h1), RectPart::Right | RectPart::BottomLeft }, }; } std::vector<GroupMediaLayout> Layouter::layoutFourLeftAndOther() const { Expects(_count == 4); const auto h = _maxHeight; const auto w0 = Round(std::min( h * _ratios[0], (_maxWidth - _spacing) * 0.6)); const auto w = Round( (_maxHeight - 2 * _spacing) / (1. / _ratios[1] + 1. / _ratios[2] + 1. / _ratios[3]) ); const auto h0 = Round(w / _ratios[1]); const auto h1 = Round(w / _ratios[2]); const auto h2 = h - h0 - h1 - 2 * _spacing; const auto w1 = std::max( _minWidth, std::min(_maxWidth - w0 - _spacing, w)); return { { QRect(0, 0, w0, h), RectPart::Top | RectPart::Left | RectPart::Bottom }, { QRect(w0 + _spacing, 0, w1, h0), RectPart::Top | RectPart::Right }, { QRect(w0 + _spacing, h0 + _spacing, w1, h1), RectPart::Right }, { QRect(w0 + _spacing, h0 + h1 + 2 * _spacing, w1, h2), RectPart::Bottom | RectPart::Right }, }; } ComplexLayouter::ComplexLayouter( const std::vector<float64> &ratios, float64 averageRatio, int maxWidth, int minWidth, int spacing) : _ratios(CropRatios(ratios, averageRatio)) , _count(int(_ratios.size())) // All apps currently use square max size first. // In complex case they use maxWidth * 4 / 3 as maxHeight. , _maxWidth(maxWidth) , _maxHeight(maxWidth * 4 / 3) , _minWidth(minWidth) , _spacing(spacing) , _averageRatio(averageRatio) { } std::vector<float64> ComplexLayouter::CropRatios( const std::vector<float64> &ratios, float64 averageRatio) { return ranges::view::all( ratios ) | ranges::view::transform([&](float64 ratio) { constexpr auto kMaxRatio = 2.75; constexpr auto kMinRatio = 0.6667; return (averageRatio > 1.1) ? snap(ratio, 1., kMaxRatio) : snap(ratio, kMinRatio, 1.); }) | ranges::to_vector; } std::vector<GroupMediaLayout> ComplexLayouter::layout() const { Expects(_count > 1); auto result = std::vector<GroupMediaLayout>(_count); auto attempts = std::vector<Attempt>(); const auto multiHeight = [&](int offset, int count) { const auto ratios = gsl::make_span(_ratios).subspan(offset, count); const auto sum = ranges::accumulate(ratios, 0.); return (_maxWidth - (count - 1) * _spacing) / sum; }; const auto pushAttempt = [&](std::vector<int> lineCounts) { auto heights = std::vector<float64>(); heights.reserve(lineCounts.size()); auto offset = 0; for (auto count : lineCounts) { heights.push_back(multiHeight(offset, count)); offset += count; } attempts.push_back({ std::move(lineCounts), std::move(heights) }); }; for (auto first = 1; first != _count; ++first) { const auto second = _count - first; if (first > 3 || second > 3) { continue; } pushAttempt({ first, second }); } for (auto first = 1; first != _count - 1; ++first) { for (auto second = 1; second != _count - first; ++second) { const auto third = _count - first - second; if ((first > 3) || (second > ((_averageRatio < 0.85) ? 4 : 3)) || (third > 3)) { continue; } pushAttempt({ first, second, third }); } } for (auto first = 1; first != _count - 1; ++first) { for (auto second = 1; second != _count - first; ++second) { for (auto third = 1; third != _count - first - second; ++third) { const auto fourth = _count - first - second - third; if (first > 3 || second > 3 || third > 3 || fourth > 3) { continue; } pushAttempt({ first, second, third, fourth }); } } } auto optimalAttempt = (const Attempt*)nullptr; auto optimalDiff = 0.; for (const auto &attempt : attempts) { const auto &heights = attempt.heights; const auto &counts = attempt.lineCounts; const auto lineCount = int(counts.size()); const auto totalHeight = ranges::accumulate(heights, 0.) + _spacing * (lineCount - 1); const auto minLineHeight = ranges::min(heights); const auto maxLineHeight = ranges::max(heights); const auto bad1 = (minLineHeight < _minWidth) ? 1.5 : 1.; const auto bad2 = [&] { for (auto line = 1; line != lineCount; ++line) { if (counts[line - 1] > counts[line]) { return 1.5; } } return 1.; }(); const auto diff = std::abs(totalHeight - _maxHeight) * bad1 * bad2; if (!optimalAttempt || diff < optimalDiff) { optimalAttempt = &attempt; optimalDiff = diff; } } Assert(optimalAttempt != nullptr); const auto &optimalCounts = optimalAttempt->lineCounts; const auto &optimalHeights = optimalAttempt->heights; const auto rowCount = int(optimalCounts.size()); auto index = 0; auto y = 0.; for (auto row = 0; row != rowCount; ++row) { const auto colCount = optimalCounts[row]; const auto lineHeight = optimalHeights[row]; const auto height = Round(lineHeight); auto x = 0; for (auto col = 0; col != colCount; ++col) { const auto sides = RectPart::None | (row == 0 ? RectPart::Top : RectPart::None) | (row == rowCount - 1 ? RectPart::Bottom : RectPart::None) | (col == 0 ? RectPart::Left : RectPart::None) | (col == colCount - 1 ? RectPart::Right : RectPart::None); const auto ratio = _ratios[index]; const auto width = (col == colCount - 1) ? (_maxWidth - x) : Round(ratio * lineHeight); result[index] = { QRect(x, y, width, height), sides }; x += width + _spacing; ++index; } y += height + _spacing; } return result; } } // namespace std::vector<GroupMediaLayout> LayoutMediaGroup( const std::vector<QSize> &sizes, int maxWidth, int minWidth, int spacing) { return Layouter(sizes, maxWidth, minWidth, spacing).layout(); } RectParts GetCornersFromSides(RectParts sides) { const auto convert = [&]( RectPart side1, RectPart side2, RectPart corner) { return ((sides & side1) && (sides & side2)) ? corner : RectPart::None; }; return RectPart::None | convert(RectPart::Top, RectPart::Left, RectPart::TopLeft) | convert(RectPart::Top, RectPart::Right, RectPart::TopRight) | convert(RectPart::Bottom, RectPart::Left, RectPart::BottomLeft) | convert(RectPart::Bottom, RectPart::Right, RectPart::BottomRight); } QSize GetImageScaleSizeForGeometry(QSize original, QSize geometry) { const auto width = geometry.width(); const auto height = geometry.height(); auto tw = original.width(); auto th = original.height(); if (tw * height > th * width) { if (th > height || tw * height < 2 * th * width) { tw = (height * tw) / th; th = height; } else if (tw < width) { th = (width * th) / tw; tw = width; } } else { if (tw > width || th * width < 2 * tw * height) { th = (width * th) / tw; tw = width; } else if (tw > 0 && th < height) { tw = (height * tw) / th; th = height; } } if (tw < 1) tw = 1; if (th < 1) th = 1; return { tw, th }; } } // namespace Ui ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/grouped_layout.h��������������������������������������������0000664�0000000�0000000�00000001147�13262451251�0023256�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace Ui { struct GroupMediaLayout { QRect geometry; RectParts sides = RectPart::None; }; std::vector<GroupMediaLayout> LayoutMediaGroup( const std::vector<QSize> &sizes, int maxWidth, int minWidth, int spacing); RectParts GetCornersFromSides(RectParts sides); QSize GetImageScaleSizeForGeometry(QSize original, QSize geometry); } // namespace Ui �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/images.cpp��������������������������������������������������0000664�0000000�0000000�00000126200�13262451251�0022012�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/images.h" #include "mainwidget.h" #include "storage/localstorage.h" #include "platform/platform_specific.h" #include "auth_session.h" #include "history/history_item.h" #include "history/history.h" namespace Images { namespace { FORCE_INLINE uint64 blurGetColors(const uchar *p) { return (uint64)p[0] + ((uint64)p[1] << 16) + ((uint64)p[2] << 32) + ((uint64)p[3] << 48); } const QPixmap &circleMask(int width, int height) { Assert(Global::started()); uint64 key = uint64(uint32(width)) << 32 | uint64(uint32(height)); Global::CircleMasksMap &masks(Global::RefCircleMasks()); auto i = masks.constFind(key); if (i == masks.cend()) { QImage mask(width, height, QImage::Format_ARGB32_Premultiplied); { Painter p(&mask); PainterHighQualityEnabler hq(p); p.setCompositionMode(QPainter::CompositionMode_Source); p.fillRect(0, 0, width, height, Qt::transparent); p.setBrush(Qt::white); p.setPen(Qt::NoPen); p.drawEllipse(0, 0, width, height); } mask.setDevicePixelRatio(cRetinaFactor()); i = masks.insert(key, App::pixmapFromImageInPlace(std::move(mask))); } return i.value(); } } // namespace QPixmap PixmapFast(QImage &&image) { Expects(image.format() == QImage::Format_ARGB32_Premultiplied || image.format() == QImage::Format_RGB32); return QPixmap::fromImage(std::move(image), Qt::NoFormatConversion); } QImage prepareBlur(QImage img) { auto ratio = img.devicePixelRatio(); auto fmt = img.format(); if (fmt != QImage::Format_RGB32 && fmt != QImage::Format_ARGB32_Premultiplied) { img = img.convertToFormat(QImage::Format_ARGB32_Premultiplied); img.setDevicePixelRatio(ratio); Assert(!img.isNull()); } uchar *pix = img.bits(); if (pix) { int w = img.width(), h = img.height(), wold = w, hold = h; const int radius = 3; const int r1 = radius + 1; const int div = radius * 2 + 1; const int stride = w * 4; if (radius < 16 && div < w && div < h && stride <= w * 4) { bool withalpha = img.hasAlphaChannel(); if (withalpha) { QImage imgsmall(w, h, img.format()); { Painter p(&imgsmall); PainterHighQualityEnabler hq(p); p.setCompositionMode(QPainter::CompositionMode_Source); p.fillRect(0, 0, w, h, Qt::transparent); p.drawImage(QRect(radius, radius, w - 2 * radius, h - 2 * radius), img, QRect(0, 0, w, h)); } imgsmall.setDevicePixelRatio(ratio); auto was = img; img = std::move(imgsmall); imgsmall = QImage(); Assert(!img.isNull()); pix = img.bits(); if (!pix) return was; } uint64 *rgb = new uint64[w * h]; int x, y, i; int yw = 0; const int we = w - r1; for (y = 0; y < h; y++) { uint64 cur = blurGetColors(&pix[yw]); uint64 rgballsum = -radius * cur; uint64 rgbsum = cur * ((r1 * (r1 + 1)) >> 1); for (i = 1; i <= radius; i++) { uint64 cur = blurGetColors(&pix[yw + i * 4]); rgbsum += cur * (r1 - i); rgballsum += cur; } x = 0; #define update(start, middle, end) \ rgb[y * w + x] = (rgbsum >> 4) & 0x00FF00FF00FF00FFLL; \ rgballsum += blurGetColors(&pix[yw + (start) * 4]) - 2 * blurGetColors(&pix[yw + (middle) * 4]) + blurGetColors(&pix[yw + (end) * 4]); \ rgbsum += rgballsum; \ x++; while (x < r1) { update(0, x, x + r1); } while (x < we) { update(x - r1, x, x + r1); } while (x < w) { update(x - r1, x, w - 1); } #undef update yw += stride; } const int he = h - r1; for (x = 0; x < w; x++) { uint64 rgballsum = -radius * rgb[x]; uint64 rgbsum = rgb[x] * ((r1 * (r1 + 1)) >> 1); for (i = 1; i <= radius; i++) { rgbsum += rgb[i * w + x] * (r1 - i); rgballsum += rgb[i * w + x]; } y = 0; int yi = x * 4; #define update(start, middle, end) \ uint64 res = rgbsum >> 4; \ pix[yi] = res & 0xFF; \ pix[yi + 1] = (res >> 16) & 0xFF; \ pix[yi + 2] = (res >> 32) & 0xFF; \ pix[yi + 3] = (res >> 48) & 0xFF; \ rgballsum += rgb[x + (start) * w] - 2 * rgb[x + (middle) * w] + rgb[x + (end) * w]; \ rgbsum += rgballsum; \ y++; \ yi += stride; while (y < r1) { update(0, y, y + r1); } while (y < he) { update(y - r1, y, y + r1); } while (y < h) { update(y - r1, y, h - 1); } #undef update } delete[] rgb; } } return img; } void prepareCircle(QImage &img) { Assert(!img.isNull()); img.setDevicePixelRatio(cRetinaFactor()); img = img.convertToFormat(QImage::Format_ARGB32_Premultiplied); Assert(!img.isNull()); QPixmap mask = circleMask(img.width(), img.height()); Painter p(&img); p.setCompositionMode(QPainter::CompositionMode_DestinationIn); p.drawPixmap(0, 0, mask); } void prepareRound( QImage &image, QImage *cornerMasks, RectParts corners, QRect target) { if (target.isNull()) { target = QRect(QPoint(), image.size()); } else { Assert(QRect(QPoint(), image.size()).contains(target)); } auto cornerWidth = cornerMasks[0].width(); auto cornerHeight = cornerMasks[0].height(); auto imageWidth = image.width(); auto imageHeight = image.height(); if (imageWidth < 2 * cornerWidth || imageHeight < 2 * cornerHeight) { return; } constexpr auto imageIntsPerPixel = 1; auto imageIntsPerLine = (image.bytesPerLine() >> 2); Assert(image.depth() == static_cast<int>((imageIntsPerPixel * sizeof(uint32)) << 3)); Assert(image.bytesPerLine() == (imageIntsPerLine << 2)); auto ints = reinterpret_cast<uint32*>(image.bits()); auto intsTopLeft = ints + target.x() + target.y() * imageWidth; auto intsTopRight = ints + target.x() + target.width() - cornerWidth + target.y() * imageWidth; auto intsBottomLeft = ints + target.x() + (target.y() + target.height() - cornerHeight) * imageWidth; auto intsBottomRight = ints + target.x() + target.width() - cornerWidth + (target.y() + target.height() - cornerHeight) * imageWidth; auto maskCorner = [&](uint32 *imageInts, const QImage &mask) { auto maskWidth = mask.width(); auto maskHeight = mask.height(); auto maskBytesPerPixel = (mask.depth() >> 3); auto maskBytesPerLine = mask.bytesPerLine(); auto maskBytesAdded = maskBytesPerLine - maskWidth * maskBytesPerPixel; auto maskBytes = mask.constBits(); Assert(maskBytesAdded >= 0); Assert(mask.depth() == (maskBytesPerPixel << 3)); auto imageIntsAdded = imageIntsPerLine - maskWidth * imageIntsPerPixel; Assert(imageIntsAdded >= 0); for (auto y = 0; y != maskHeight; ++y) { for (auto x = 0; x != maskWidth; ++x) { auto opacity = static_cast<anim::ShiftedMultiplier>(*maskBytes) + 1; *imageInts = anim::unshifted(anim::shifted(*imageInts) * opacity); maskBytes += maskBytesPerPixel; imageInts += imageIntsPerPixel; } maskBytes += maskBytesAdded; imageInts += imageIntsAdded; } }; if (corners & RectPart::TopLeft) maskCorner(intsTopLeft, cornerMasks[0]); if (corners & RectPart::TopRight) maskCorner(intsTopRight, cornerMasks[1]); if (corners & RectPart::BottomLeft) maskCorner(intsBottomLeft, cornerMasks[2]); if (corners & RectPart::BottomRight) maskCorner(intsBottomRight, cornerMasks[3]); } void prepareRound( QImage &image, ImageRoundRadius radius, RectParts corners, QRect target) { if (!static_cast<int>(corners)) { return; } else if (radius == ImageRoundRadius::Ellipse) { Assert((corners & RectPart::AllCorners) == RectPart::AllCorners); Assert(target.isNull()); prepareCircle(image); } Assert(!image.isNull()); image.setDevicePixelRatio(cRetinaFactor()); image = std::move(image).convertToFormat(QImage::Format_ARGB32_Premultiplied); Assert(!image.isNull()); auto masks = App::cornersMask(radius); prepareRound(image, masks, corners, target); } QImage prepareColored(style::color add, QImage image) { auto format = image.format(); if (format != QImage::Format_RGB32 && format != QImage::Format_ARGB32_Premultiplied) { image = std::move(image).convertToFormat(QImage::Format_ARGB32_Premultiplied); } if (auto pix = image.bits()) { int ca = int(add->c.alphaF() * 0xFF), cr = int(add->c.redF() * 0xFF), cg = int(add->c.greenF() * 0xFF), cb = int(add->c.blueF() * 0xFF); const int w = image.width(), h = image.height(), size = w * h * 4; for (int32 i = 0; i < size; i += 4) { int b = pix[i], g = pix[i + 1], r = pix[i + 2], a = pix[i + 3], aca = a * ca; pix[i + 0] = uchar(b + ((aca * (cb - b)) >> 16)); pix[i + 1] = uchar(g + ((aca * (cg - g)) >> 16)); pix[i + 2] = uchar(r + ((aca * (cr - r)) >> 16)); pix[i + 3] = uchar(a + ((aca * (0xFF - a)) >> 16)); } } return image; } QImage prepareOpaque(QImage image) { if (image.hasAlphaChannel()) { image = std::move(image).convertToFormat(QImage::Format_ARGB32_Premultiplied); auto ints = reinterpret_cast<uint32*>(image.bits()); auto bg = anim::shifted(st::imageBgTransparent->c); auto width = image.width(); auto height = image.height(); auto addPerLine = (image.bytesPerLine() / sizeof(uint32)) - width; for (auto y = 0; y != height; ++y) { for (auto x = 0; x != width; ++x) { auto components = anim::shifted(*ints); *ints++ = anim::unshifted(components * 256 + bg * (256 - anim::getAlpha(components))); } ints += addPerLine; } } return image; } QImage prepare(QImage img, int w, int h, Images::Options options, int outerw, int outerh, const style::color *colored) { Assert(!img.isNull()); if (options & Images::Option::Blurred) { img = prepareBlur(std::move(img)); Assert(!img.isNull()); } if (w <= 0 || (w == img.width() && (h <= 0 || h == img.height()))) { } else if (h <= 0) { img = img.scaledToWidth(w, (options & Images::Option::Smooth) ? Qt::SmoothTransformation : Qt::FastTransformation); Assert(!img.isNull()); } else { img = img.scaled(w, h, Qt::IgnoreAspectRatio, (options & Images::Option::Smooth) ? Qt::SmoothTransformation : Qt::FastTransformation); Assert(!img.isNull()); } if (outerw > 0 && outerh > 0) { outerw *= cIntRetinaFactor(); outerh *= cIntRetinaFactor(); if (outerw != w || outerh != h) { img.setDevicePixelRatio(cRetinaFactor()); auto result = QImage(outerw, outerh, QImage::Format_ARGB32_Premultiplied); result.setDevicePixelRatio(cRetinaFactor()); if (options & Images::Option::TransparentBackground) { result.fill(Qt::transparent); } { QPainter p(&result); if (w < outerw || h < outerh) { p.fillRect(0, 0, result.width(), result.height(), st::imageBg); } p.drawImage((result.width() - img.width()) / (2 * cIntRetinaFactor()), (result.height() - img.height()) / (2 * cIntRetinaFactor()), img); } img = result; Assert(!img.isNull()); } } auto corners = [](Images::Options options) { return ((options & Images::Option::RoundedTopLeft) ? RectPart::TopLeft : RectPart::None) | ((options & Images::Option::RoundedTopRight) ? RectPart::TopRight : RectPart::None) | ((options & Images::Option::RoundedBottomLeft) ? RectPart::BottomLeft : RectPart::None) | ((options & Images::Option::RoundedBottomRight) ? RectPart::BottomRight : RectPart::None); }; if (options & Images::Option::Circled) { prepareCircle(img); Assert(!img.isNull()); } else if (options & Images::Option::RoundedLarge) { prepareRound(img, ImageRoundRadius::Large, corners(options)); Assert(!img.isNull()); } else if (options & Images::Option::RoundedSmall) { prepareRound(img, ImageRoundRadius::Small, corners(options)); Assert(!img.isNull()); } if (options & Images::Option::Colored) { Assert(colored != nullptr); img = prepareColored(*colored, std::move(img)); } img.setDevicePixelRatio(cRetinaFactor()); return img; } } // namespace Images namespace { using LocalImages = QMap<QString, Image*>; LocalImages localImages; using WebImages = QMap<QString, WebImage*>; WebImages webImages; Image *generateBlankImage() { auto data = QImage(cIntRetinaFactor(), cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); data.fill(Qt::transparent); data.setDevicePixelRatio(cRetinaFactor()); return internal::getImage(App::pixmapFromImageInPlace(std::move(data)), "GIF"); } Image *blank() { static auto blankImage = generateBlankImage(); return blankImage; } using StorageImages = QMap<StorageKey, StorageImage*>; StorageImages storageImages; using WebFileImages = QMap<StorageKey, WebFileImage*>; WebFileImages webFileImages; int64 globalAcquiredSize = 0; uint64 PixKey(int width, int height, Images::Options options) { return static_cast<uint64>(width) | (static_cast<uint64>(height) << 24) | (static_cast<uint64>(options) << 48); } uint64 SinglePixKey(Images::Options options) { return PixKey(0, 0, options); } } // namespace StorageImageLocation StorageImageLocation::Null; WebFileLocation WebFileLocation::Null; bool Image::isNull() const { return (this == blank()); } ImagePtr::ImagePtr() : Parent(blank()) { } ImagePtr::ImagePtr(int32 width, int32 height, const MTPFileLocation &location, ImagePtr def) : Parent((location.type() == mtpc_fileLocation) ? (Image*)(internal::getImage(StorageImageLocation(width, height, location.c_fileLocation()))) : def.v()) { } Image::Image(const QString &file, QByteArray fmt) : _forgot(false) { _data = App::pixmapFromImageInPlace(App::readImage(file, &fmt, false, 0, &_saved)); _format = fmt; if (!_data.isNull()) { globalAcquiredSize += int64(_data.width()) * _data.height() * 4; } } Image::Image(const QByteArray &filecontent, QByteArray fmt) : _forgot(false) { _data = App::pixmapFromImageInPlace(App::readImage(filecontent, &fmt, false)); _format = fmt; _saved = filecontent; if (!_data.isNull()) { globalAcquiredSize += int64(_data.width()) * _data.height() * 4; } } Image::Image(const QPixmap &pixmap, QByteArray format) : _format(format), _forgot(false), _data(pixmap) { if (!_data.isNull()) { globalAcquiredSize += int64(_data.width()) * _data.height() * 4; } } Image::Image(const QByteArray &filecontent, QByteArray fmt, const QPixmap &pixmap) : _saved(filecontent), _format(fmt), _forgot(false), _data(pixmap) { _data = pixmap; _format = fmt; _saved = filecontent; if (!_data.isNull()) { globalAcquiredSize += int64(_data.width()) * _data.height() * 4; } } const QPixmap &Image::pix(int32 w, int32 h) const { checkload(); if (w <= 0 || !width() || !height()) { w = width(); } else if (cRetina()) { w *= cIntRetinaFactor(); h *= cIntRetinaFactor(); } auto options = Images::Option::Smooth | Images::Option::None; auto k = PixKey(w, h, options); auto i = _sizesCache.constFind(k); if (i == _sizesCache.cend()) { auto p = pixNoCache(w, h, options); if (cRetina()) p.setDevicePixelRatio(cRetinaFactor()); i = _sizesCache.insert(k, p); if (!p.isNull()) { globalAcquiredSize += int64(p.width()) * p.height() * 4; } } return i.value(); } const QPixmap &Image::pixRounded(int32 w, int32 h, ImageRoundRadius radius, RectParts corners) const { checkload(); if (w <= 0 || !width() || !height()) { w = width(); } else if (cRetina()) { w *= cIntRetinaFactor(); h *= cIntRetinaFactor(); } auto options = Images::Option::Smooth | Images::Option::None; auto cornerOptions = [](RectParts corners) { return (corners & RectPart::TopLeft ? Images::Option::RoundedTopLeft : Images::Option::None) | (corners & RectPart::TopRight ? Images::Option::RoundedTopRight : Images::Option::None) | (corners & RectPart::BottomLeft ? Images::Option::RoundedBottomLeft : Images::Option::None) | (corners & RectPart::BottomRight ? Images::Option::RoundedBottomRight : Images::Option::None); }; if (radius == ImageRoundRadius::Large) { options |= Images::Option::RoundedLarge | cornerOptions(corners); } else if (radius == ImageRoundRadius::Small) { options |= Images::Option::RoundedSmall | cornerOptions(corners); } else if (radius == ImageRoundRadius::Ellipse) { options |= Images::Option::Circled | cornerOptions(corners); } auto k = PixKey(w, h, options); auto i = _sizesCache.constFind(k); if (i == _sizesCache.cend()) { auto p = pixNoCache(w, h, options); if (cRetina()) p.setDevicePixelRatio(cRetinaFactor()); i = _sizesCache.insert(k, p); if (!p.isNull()) { globalAcquiredSize += int64(p.width()) * p.height() * 4; } } return i.value(); } const QPixmap &Image::pixCircled(int32 w, int32 h) const { checkload(); if (w <= 0 || !width() || !height()) { w = width(); } else if (cRetina()) { w *= cIntRetinaFactor(); h *= cIntRetinaFactor(); } auto options = Images::Option::Smooth | Images::Option::Circled; auto k = PixKey(w, h, options); auto i = _sizesCache.constFind(k); if (i == _sizesCache.cend()) { auto p = pixNoCache(w, h, options); if (cRetina()) p.setDevicePixelRatio(cRetinaFactor()); i = _sizesCache.insert(k, p); if (!p.isNull()) { globalAcquiredSize += int64(p.width()) * p.height() * 4; } } return i.value(); } const QPixmap &Image::pixBlurredCircled(int32 w, int32 h) const { checkload(); if (w <= 0 || !width() || !height()) { w = width(); } else if (cRetina()) { w *= cIntRetinaFactor(); h *= cIntRetinaFactor(); } auto options = Images::Option::Smooth | Images::Option::Circled | Images::Option::Blurred; auto k = PixKey(w, h, options); auto i = _sizesCache.constFind(k); if (i == _sizesCache.cend()) { auto p = pixNoCache(w, h, options); if (cRetina()) p.setDevicePixelRatio(cRetinaFactor()); i = _sizesCache.insert(k, p); if (!p.isNull()) { globalAcquiredSize += int64(p.width()) * p.height() * 4; } } return i.value(); } const QPixmap &Image::pixBlurred(int32 w, int32 h) const { checkload(); if (w <= 0 || !width() || !height()) { w = width() * cIntRetinaFactor(); } else if (cRetina()) { w *= cIntRetinaFactor(); h *= cIntRetinaFactor(); } auto options = Images::Option::Smooth | Images::Option::Blurred; auto k = PixKey(w, h, options); auto i = _sizesCache.constFind(k); if (i == _sizesCache.cend()) { auto p = pixNoCache(w, h, options); if (cRetina()) p.setDevicePixelRatio(cRetinaFactor()); i = _sizesCache.insert(k, p); if (!p.isNull()) { globalAcquiredSize += int64(p.width()) * p.height() * 4; } } return i.value(); } const QPixmap &Image::pixColored(style::color add, int32 w, int32 h) const { checkload(); if (w <= 0 || !width() || !height()) { w = width() * cIntRetinaFactor(); } else if (cRetina()) { w *= cIntRetinaFactor(); h *= cIntRetinaFactor(); } auto options = Images::Option::Smooth | Images::Option::Colored; auto k = PixKey(w, h, options); auto i = _sizesCache.constFind(k); if (i == _sizesCache.cend()) { auto p = pixColoredNoCache(add, w, h, true); if (cRetina()) p.setDevicePixelRatio(cRetinaFactor()); i = _sizesCache.insert(k, p); if (!p.isNull()) { globalAcquiredSize += int64(p.width()) * p.height() * 4; } } return i.value(); } const QPixmap &Image::pixBlurredColored(style::color add, int32 w, int32 h) const { checkload(); if (w <= 0 || !width() || !height()) { w = width() * cIntRetinaFactor(); } else if (cRetina()) { w *= cIntRetinaFactor(); h *= cIntRetinaFactor(); } auto options = Images::Option::Blurred | Images::Option::Smooth | Images::Option::Colored; auto k = PixKey(w, h, options); auto i = _sizesCache.constFind(k); if (i == _sizesCache.cend()) { auto p = pixBlurredColoredNoCache(add, w, h); if (cRetina()) p.setDevicePixelRatio(cRetinaFactor()); i = _sizesCache.insert(k, p); if (!p.isNull()) { globalAcquiredSize += int64(p.width()) * p.height() * 4; } } return i.value(); } const QPixmap &Image::pixSingle(int32 w, int32 h, int32 outerw, int32 outerh, ImageRoundRadius radius, RectParts corners, const style::color *colored) const { checkload(); if (w <= 0 || !width() || !height()) { w = width() * cIntRetinaFactor(); } else if (cRetina()) { w *= cIntRetinaFactor(); h *= cIntRetinaFactor(); } auto options = Images::Option::Smooth | Images::Option::None; auto cornerOptions = [](RectParts corners) { return (corners & RectPart::TopLeft ? Images::Option::RoundedTopLeft : Images::Option::None) | (corners & RectPart::TopRight ? Images::Option::RoundedTopRight : Images::Option::None) | (corners & RectPart::BottomLeft ? Images::Option::RoundedBottomLeft : Images::Option::None) | (corners & RectPart::BottomRight ? Images::Option::RoundedBottomRight : Images::Option::None); }; if (radius == ImageRoundRadius::Large) { options |= Images::Option::RoundedLarge | cornerOptions(corners); } else if (radius == ImageRoundRadius::Small) { options |= Images::Option::RoundedSmall | cornerOptions(corners); } else if (radius == ImageRoundRadius::Ellipse) { options |= Images::Option::Circled | cornerOptions(corners); } if (colored) { options |= Images::Option::Colored; } auto k = SinglePixKey(options); auto i = _sizesCache.constFind(k); if (i == _sizesCache.cend() || i->width() != (outerw * cIntRetinaFactor()) || i->height() != (outerh * cIntRetinaFactor())) { if (i != _sizesCache.cend()) { globalAcquiredSize -= int64(i->width()) * i->height() * 4; } auto p = pixNoCache(w, h, options, outerw, outerh, colored); if (cRetina()) p.setDevicePixelRatio(cRetinaFactor()); i = _sizesCache.insert(k, p); if (!p.isNull()) { globalAcquiredSize += int64(p.width()) * p.height() * 4; } } return i.value(); } const QPixmap &Image::pixBlurredSingle(int w, int h, int32 outerw, int32 outerh, ImageRoundRadius radius, RectParts corners) const { checkload(); if (w <= 0 || !width() || !height()) { w = width() * cIntRetinaFactor(); } else if (cRetina()) { w *= cIntRetinaFactor(); h *= cIntRetinaFactor(); } auto options = Images::Option::Smooth | Images::Option::Blurred; auto cornerOptions = [](RectParts corners) { return (corners & RectPart::TopLeft ? Images::Option::RoundedTopLeft : Images::Option::None) | (corners & RectPart::TopRight ? Images::Option::RoundedTopRight : Images::Option::None) | (corners & RectPart::BottomLeft ? Images::Option::RoundedBottomLeft : Images::Option::None) | (corners & RectPart::BottomRight ? Images::Option::RoundedBottomRight : Images::Option::None); }; if (radius == ImageRoundRadius::Large) { options |= Images::Option::RoundedLarge | cornerOptions(corners); } else if (radius == ImageRoundRadius::Small) { options |= Images::Option::RoundedSmall | cornerOptions(corners); } else if (radius == ImageRoundRadius::Ellipse) { options |= Images::Option::Circled | cornerOptions(corners); } auto k = SinglePixKey(options); auto i = _sizesCache.constFind(k); if (i == _sizesCache.cend() || i->width() != (outerw * cIntRetinaFactor()) || i->height() != (outerh * cIntRetinaFactor())) { if (i != _sizesCache.cend()) { globalAcquiredSize -= int64(i->width()) * i->height() * 4; } auto p = pixNoCache(w, h, options, outerw, outerh); if (cRetina()) p.setDevicePixelRatio(cRetinaFactor()); i = _sizesCache.insert(k, p); if (!p.isNull()) { globalAcquiredSize += int64(p.width()) * p.height() * 4; } } return i.value(); } QPixmap Image::pixNoCache(int w, int h, Images::Options options, int outerw, int outerh, const style::color *colored) const { if (!loading()) const_cast<Image*>(this)->load(); restore(); if (_data.isNull()) { if (h <= 0 && height() > 0) { h = qRound(width() * w / float64(height())); } return blank()->pixNoCache(w, h, options, outerw, outerh); } if (isNull() && outerw > 0 && outerh > 0) { outerw *= cIntRetinaFactor(); outerh *= cIntRetinaFactor(); QImage result(outerw, outerh, QImage::Format_ARGB32_Premultiplied); result.setDevicePixelRatio(cRetinaFactor()); { QPainter p(&result); if (w < outerw) { p.fillRect(0, 0, (outerw - w) / 2, result.height(), st::imageBg); p.fillRect(((outerw - w) / 2) + w, 0, result.width() - (((outerw - w) / 2) + w), result.height(), st::imageBg); } if (h < outerh) { p.fillRect(qMax(0, (outerw - w) / 2), 0, qMin(result.width(), w), (outerh - h) / 2, st::imageBg); p.fillRect(qMax(0, (outerw - w) / 2), ((outerh - h) / 2) + h, qMin(result.width(), w), result.height() - (((outerh - h) / 2) + h), st::imageBg); } p.fillRect(qMax(0, (outerw - w) / 2), qMax(0, (outerh - h) / 2), qMin(result.width(), w), qMin(result.height(), h), st::imageBgTransparent); } auto corners = [](Images::Options options) { return ((options & Images::Option::RoundedTopLeft) ? RectPart::TopLeft : RectPart::None) | ((options & Images::Option::RoundedTopRight) ? RectPart::TopRight : RectPart::None) | ((options & Images::Option::RoundedBottomLeft) ? RectPart::BottomLeft : RectPart::None) | ((options & Images::Option::RoundedBottomRight) ? RectPart::BottomRight : RectPart::None); }; if (options & Images::Option::Circled) { Images::prepareCircle(result); } else if (options & Images::Option::RoundedLarge) { Images::prepareRound(result, ImageRoundRadius::Large, corners(options)); } else if (options & Images::Option::RoundedSmall) { Images::prepareRound(result, ImageRoundRadius::Small, corners(options)); } if (options & Images::Option::Colored) { Assert(colored != nullptr); result = Images::prepareColored(*colored, std::move(result)); } return App::pixmapFromImageInPlace(std::move(result)); } return Images::pixmap(_data.toImage(), w, h, options, outerw, outerh, colored); } QPixmap Image::pixColoredNoCache(style::color add, int32 w, int32 h, bool smooth) const { const_cast<Image*>(this)->load(); restore(); if (_data.isNull()) return blank()->pix(); auto img = _data.toImage(); if (w <= 0 || !width() || !height() || (w == width() && (h <= 0 || h == height()))) { return App::pixmapFromImageInPlace(Images::prepareColored(add, std::move(img))); } if (h <= 0) { return App::pixmapFromImageInPlace(Images::prepareColored(add, img.scaledToWidth(w, smooth ? Qt::SmoothTransformation : Qt::FastTransformation))); } return App::pixmapFromImageInPlace(Images::prepareColored(add, img.scaled(w, h, Qt::IgnoreAspectRatio, smooth ? Qt::SmoothTransformation : Qt::FastTransformation))); } QPixmap Image::pixBlurredColoredNoCache(style::color add, int32 w, int32 h) const { const_cast<Image*>(this)->load(); restore(); if (_data.isNull()) return blank()->pix(); auto img = Images::prepareBlur(_data.toImage()); if (h <= 0) { img = img.scaledToWidth(w, Qt::SmoothTransformation); } else { img = img.scaled(w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } return App::pixmapFromImageInPlace(Images::prepareColored(add, img)); } void Image::forget() const { if (_forgot) return; if (_data.isNull()) return; invalidateSizeCache(); /*if (hasLocalCopy()) { _saved.clear(); } else */if (_saved.isEmpty()) { QBuffer buffer(&_saved); if (!_data.save(&buffer, _format)) { if (_data.save(&buffer, "PNG")) { _format = "PNG"; } else { return; } } } globalAcquiredSize -= int64(_data.width()) * _data.height() * 4; _data = QPixmap(); _forgot = true; } void Image::restore() const { if (!_forgot) return; QBuffer buffer(&_saved); QImageReader reader(&buffer, _format); #ifndef OS_MAC_OLD reader.setAutoTransform(true); #endif // OS_MAC_OLD _data = QPixmap::fromImageReader(&reader, Qt::ColorOnly); if (!_data.isNull()) { globalAcquiredSize += int64(_data.width()) * _data.height() * 4; } _forgot = false; } void Image::invalidateSizeCache() const { for (auto &pix : _sizesCache) { if (!pix.isNull()) { globalAcquiredSize -= int64(pix.width()) * pix.height() * 4; } } _sizesCache.clear(); } Image::~Image() { invalidateSizeCache(); if (!_data.isNull()) { globalAcquiredSize -= int64(_data.width()) * _data.height() * 4; } } void clearStorageImages() { for (auto image : base::take(storageImages)) { delete image; } for (auto image : base::take(webImages)) { delete image; } for (auto image : base::take(webFileImages)) { delete image; } } void clearAllImages() { for (auto image : base::take(localImages)) { delete image; } clearStorageImages(); } int64 imageCacheSize() { return globalAcquiredSize; } void RemoteImage::doCheckload() const { if (!amLoading() || !_loader->finished()) return; QPixmap data = _loader->imagePixmap(shrinkBox()); if (data.isNull()) { destroyLoaderDelayed(CancelledFileLoader); return; } if (!_data.isNull()) { globalAcquiredSize -= int64(_data.width()) * _data.height() * 4; } _format = _loader->imageFormat(shrinkBox()); _data = data; _saved = _loader->bytes(); const_cast<RemoteImage*>(this)->setInformation(_saved.size(), _data.width(), _data.height()); globalAcquiredSize += int64(_data.width()) * _data.height() * 4; invalidateSizeCache(); destroyLoaderDelayed(); _forgot = false; } void RemoteImage::destroyLoaderDelayed(FileLoader *newValue) const { _loader->stop(); auto loader = std::unique_ptr<FileLoader>(std::exchange(_loader, newValue)); Auth().downloader().delayedDestroyLoader(std::move(loader)); } void RemoteImage::loadLocal() { if (loaded() || amLoading()) return; _loader = createLoader(LoadFromLocalOnly, true); if (_loader) _loader->start(); } void RemoteImage::setData(QByteArray &bytes, const QByteArray &bytesFormat) { QBuffer buffer(&bytes); if (!_data.isNull()) { globalAcquiredSize -= int64(_data.width()) * _data.height() * 4; } QByteArray fmt(bytesFormat); _data = App::pixmapFromImageInPlace(App::readImage(bytes, &fmt, false)); if (!_data.isNull()) { globalAcquiredSize += int64(_data.width()) * _data.height() * 4; setInformation(bytes.size(), _data.width(), _data.height()); } invalidateSizeCache(); if (amLoading()) { destroyLoaderDelayed(); } _saved = bytes; _format = fmt; _forgot = false; } bool RemoteImage::amLoading() const { return _loader && _loader != CancelledFileLoader; } void RemoteImage::automaticLoad(const HistoryItem *item) { if (loaded()) return; if (_loader != CancelledFileLoader && item) { bool loadFromCloud = false; if (item->history()->peer->isUser()) { loadFromCloud = !(cAutoDownloadPhoto() & dbiadNoPrivate); } else { loadFromCloud = !(cAutoDownloadPhoto() & dbiadNoGroups); } if (_loader) { if (loadFromCloud) _loader->permitLoadFromCloud(); } else { _loader = createLoader(loadFromCloud ? LoadFromCloudOrLocal : LoadFromLocalOnly, true); if (_loader) _loader->start(); } } } void RemoteImage::automaticLoadSettingsChanged() { if (loaded() || _loader != CancelledFileLoader) return; _loader = 0; } void RemoteImage::load(bool loadFirst, bool prior) { if (loaded()) return; if (!_loader) { _loader = createLoader(LoadFromCloudOrLocal, false); } if (amLoading()) { _loader->start(loadFirst, prior); } } void RemoteImage::loadEvenCancelled(bool loadFirst, bool prior) { if (_loader == CancelledFileLoader) _loader = 0; return load(loadFirst, prior); } RemoteImage::~RemoteImage() { if (!_data.isNull()) { globalAcquiredSize -= int64(_data.width()) * _data.height() * 4; } if (amLoading()) { destroyLoaderDelayed(); } } bool RemoteImage::loaded() const { doCheckload(); return (!_data.isNull() || !_saved.isNull()); } bool RemoteImage::displayLoading() const { return amLoading() && (!_loader->loadingLocal() || !_loader->autoLoading()); } void RemoteImage::cancel() { if (!amLoading()) return; auto loader = std::exchange(_loader, CancelledFileLoader); loader->cancel(); loader->stop(); Auth().downloader().delayedDestroyLoader(std::unique_ptr<FileLoader>(loader)); } float64 RemoteImage::progress() const { return amLoading() ? _loader->currentProgress() : (loaded() ? 1 : 0); } int32 RemoteImage::loadOffset() const { return amLoading() ? _loader->currentOffset() : 0; } StorageImage::StorageImage(const StorageImageLocation &location, int32 size) : _location(location) , _size(size) { } StorageImage::StorageImage(const StorageImageLocation &location, QByteArray &bytes) : _location(location) , _size(bytes.size()) { setData(bytes); if (!_location.isNull()) { Local::writeImage(storageKey(_location), StorageImageSaved(bytes)); } } int32 StorageImage::countWidth() const { return _location.width(); } int32 StorageImage::countHeight() const { return _location.height(); } bool StorageImage::hasLocalCopy() const { return Local::willImageLoad(storageKey(_location)); } void StorageImage::setInformation(int32 size, int32 width, int32 height) { _size = size; _location.setSize(width, height); } FileLoader *StorageImage::createLoader(LoadFromCloudSetting fromCloud, bool autoLoading) { if (_location.isNull()) return 0; return new mtpFileLoader(&_location, _size, fromCloud, autoLoading); } WebFileImage::WebFileImage( const WebFileLocation &location, QSize box, int size) : _location(location) , _box(box) , _width(0) , _height(0) , _size(size) { } WebFileImage::WebFileImage( const WebFileLocation &location, int width, int height, int size) : _location(location) , _width(width) , _height(height) , _size(size) { } int WebFileImage::countWidth() const { return _width; } int WebFileImage::countHeight() const { return _height; } bool WebFileImage::hasLocalCopy() const { return Local::willImageLoad(storageKey(_location)); } void WebFileImage::setInformation(int size, int width, int height) { _size = size; _width = width; _height = height; } FileLoader *WebFileImage::createLoader( LoadFromCloudSetting fromCloud, bool autoLoading) { if (_location.isNull()) return 0; return new mtpFileLoader(&_location, _size, fromCloud, autoLoading); } DelayedStorageImage::DelayedStorageImage() : StorageImage(StorageImageLocation()) , _loadRequested(false) , _loadCancelled(false) , _loadFromCloud(false) { } DelayedStorageImage::DelayedStorageImage(int32 w, int32 h) : StorageImage(StorageImageLocation(w, h, 0, 0, 0, 0)) , _loadRequested(false) , _loadCancelled(false) , _loadFromCloud(false) { } DelayedStorageImage::DelayedStorageImage(QByteArray &bytes) : StorageImage(StorageImageLocation(), bytes) , _loadRequested(false) , _loadCancelled(false) , _loadFromCloud(false) { } void DelayedStorageImage::setStorageLocation(const StorageImageLocation location) { _location = location; if (_loadRequested) { if (!_loadCancelled) { if (_loadFromCloud) { load(); } else { loadLocal(); } } _loadRequested = false; } } void DelayedStorageImage::automaticLoad(const HistoryItem *item) { if (_location.isNull()) { if (!_loadCancelled && item) { bool loadFromCloud = false; if (item->history()->peer->isUser()) { loadFromCloud = !(cAutoDownloadPhoto() & dbiadNoPrivate); } else { loadFromCloud = !(cAutoDownloadPhoto() & dbiadNoGroups); } if (_loadRequested) { if (loadFromCloud) _loadFromCloud = loadFromCloud; } else { _loadFromCloud = loadFromCloud; _loadRequested = true; } } } else { StorageImage::automaticLoad(item); } } void DelayedStorageImage::automaticLoadSettingsChanged() { if (_loadCancelled) _loadCancelled = false; StorageImage::automaticLoadSettingsChanged(); } void DelayedStorageImage::load(bool loadFirst, bool prior) { if (_location.isNull()) { _loadRequested = _loadFromCloud = true; } else { StorageImage::load(loadFirst, prior); } } void DelayedStorageImage::loadEvenCancelled(bool loadFirst, bool prior) { _loadCancelled = false; StorageImage::loadEvenCancelled(loadFirst, prior); } bool DelayedStorageImage::displayLoading() const { return _location.isNull() ? true : StorageImage::displayLoading(); } void DelayedStorageImage::cancel() { if (_loadRequested) { _loadRequested = false; } StorageImage::cancel(); } WebImage::WebImage(const QString &url, QSize box) : _url(url) , _box(box) , _size(0) , _width(0) , _height(0) { } WebImage::WebImage(const QString &url, int width, int height) : _url(url) , _size(0) , _width(width) , _height(height) { } void WebImage::setSize(int width, int height) { _width = width; _height = height; } int32 WebImage::countWidth() const { return _width; } int32 WebImage::countHeight() const { return _height; } bool WebImage::hasLocalCopy() const { return Local::willWebFileLoad(_url); } void WebImage::setInformation(int32 size, int32 width, int32 height) { _size = size; setSize(width, height); } FileLoader *WebImage::createLoader(LoadFromCloudSetting fromCloud, bool autoLoading) { return new webFileLoader(_url, QString(), fromCloud, autoLoading); } namespace internal { Image *getImage(const QString &file, QByteArray format) { if (file.startsWith(qstr("http://"), Qt::CaseInsensitive) || file.startsWith(qstr("https://"), Qt::CaseInsensitive)) { QString key = file; WebImages::const_iterator i = webImages.constFind(key); if (i == webImages.cend()) { i = webImages.insert(key, new WebImage(file)); } return i.value(); } else { QFileInfo f(file); QString key = qsl("//:%1//:%2//:").arg(f.size()).arg(f.lastModified().toTime_t()) + file; LocalImages::const_iterator i = localImages.constFind(key); if (i == localImages.cend()) { i = localImages.insert(key, new Image(file, format)); } return i.value(); } } Image *getImage(const QString &url, QSize box) { QString key = qsl("//:%1//:%2//:").arg(box.width()).arg(box.height()) + url; auto i = webImages.constFind(key); if (i == webImages.cend()) { i = webImages.insert(key, new WebImage(url, box)); } return i.value(); } Image *getImage(const QString &url, int width, int height) { QString key = url; auto i = webImages.constFind(key); if (i == webImages.cend()) { i = webImages.insert(key, new WebImage(url, width, height)); } else { i.value()->setSize(width, height); } return i.value(); } Image *getImage(const QByteArray &filecontent, QByteArray format) { return new Image(filecontent, format); } Image *getImage(const QPixmap &pixmap, QByteArray format) { return new Image(pixmap, format); } Image *getImage(const QByteArray &filecontent, QByteArray format, const QPixmap &pixmap) { return new Image(filecontent, format, pixmap); } Image *getImage(int32 width, int32 height) { return new DelayedStorageImage(width, height); } StorageImage *getImage(const StorageImageLocation &location, int32 size) { StorageKey key(storageKey(location)); StorageImages::const_iterator i = storageImages.constFind(key); if (i == storageImages.cend()) { i = storageImages.insert(key, new StorageImage(location, size)); } return i.value(); } StorageImage *getImage(const StorageImageLocation &location, const QByteArray &bytes) { StorageKey key(storageKey(location)); StorageImages::const_iterator i = storageImages.constFind(key); if (i == storageImages.cend()) { QByteArray bytesArr(bytes); i = storageImages.insert(key, new StorageImage(location, bytesArr)); } else if (!i.value()->loaded()) { QByteArray bytesArr(bytes); i.value()->setData(bytesArr); if (!location.isNull()) { Local::writeImage(key, StorageImageSaved(bytes)); } } return i.value(); } QSize getImageSize(const QVector<MTPDocumentAttribute> &attributes) { for (const auto &attribute : attributes) { if (attribute.type() == mtpc_documentAttributeImageSize) { auto &size = attribute.c_documentAttributeImageSize(); return QSize(size.vw.v, size.vh.v); } } return QSize(); } Image *getImage(const MTPDwebDocument &document) { const auto size = getImageSize(document.vattributes.v); if (size.isEmpty()) { return blank(); } // We don't use size from WebDocument, because it is not reliable. // It can be > 0 and different from the real size that we get in upload.WebFile result. auto filesize = 0; // document.vsize.v; return getImage( WebFileLocation( document.vdc_id.v, document.vurl.v, document.vaccess_hash.v), size.width(), size.height(), filesize); } Image *getImage(const MTPDwebDocumentNoProxy &document) { const auto size = getImageSize(document.vattributes.v); if (size.isEmpty()) { return blank(); } return getImage(qs(document.vurl), size.width(), size.height()); } Image *getImage(const MTPDwebDocument &document, QSize box) { //const auto size = getImageSize(document.vattributes.v); //if (size.isEmpty()) { // return blank(); //} // We don't use size from WebDocument, because it is not reliable. // It can be > 0 and different from the real size that we get in upload.WebFile result. auto filesize = 0; // document.vsize.v; return getImage( WebFileLocation( document.vdc_id.v, document.vurl.v, document.vaccess_hash.v), box, filesize); } Image *getImage(const MTPDwebDocumentNoProxy &document, QSize box) { //const auto size = getImageSize(document.vattributes.v); //if (size.isEmpty()) { // return blank(); //} return getImage(qs(document.vurl), box); } Image *getImage(const MTPWebDocument &document) { switch (document.type()) { case mtpc_webDocument: return getImage(document.c_webDocument()); case mtpc_webDocumentNoProxy: return getImage(document.c_webDocumentNoProxy()); } Unexpected("Type in getImage(MTPWebDocument)."); } Image *getImage(const MTPWebDocument &document, QSize box) { switch (document.type()) { case mtpc_webDocument: return getImage(document.c_webDocument(), box); case mtpc_webDocumentNoProxy: return getImage(document.c_webDocumentNoProxy(), box); } Unexpected("Type in getImage(MTPWebDocument)."); } WebFileImage *getImage( const WebFileLocation &location, QSize box, int size) { auto key = storageKey(location); auto i = webFileImages.constFind(key); if (i == webFileImages.cend()) { i = webFileImages.insert( key, new WebFileImage(location, box, size)); } return i.value(); } WebFileImage *getImage( const WebFileLocation &location, int width, int height, int size) { auto key = storageKey(location); auto i = webFileImages.constFind(key); if (i == webFileImages.cend()) { i = webFileImages.insert( key, new WebFileImage(location, width, height, size)); } return i.value(); } } // namespace internal ReadAccessEnabler::ReadAccessEnabler(const PsFileBookmark *bookmark) : _bookmark(bookmark) , _failed(_bookmark ? !_bookmark->enable() : false) { } ReadAccessEnabler::ReadAccessEnabler( const std::shared_ptr<PsFileBookmark> &bookmark) : _bookmark(bookmark.get()) , _failed(_bookmark ? !_bookmark->enable() : false) { } ReadAccessEnabler::~ReadAccessEnabler() { if (_bookmark && !_failed) _bookmark->disable(); } FileLocation::FileLocation(const QString &name) : fname(name) { if (fname.isEmpty()) { size = 0; } else { setBookmark(psPathBookmark(name)); QFileInfo f(name); if (f.exists()) { qint64 s = f.size(); if (s > INT_MAX) { fname = QString(); _bookmark = nullptr; size = 0; } else { modified = f.lastModified(); size = qint32(s); } } else { fname = QString(); _bookmark = nullptr; size = 0; } } } bool FileLocation::check() const { if (fname.isEmpty()) return false; ReadAccessEnabler enabler(_bookmark); if (enabler.failed()) { const_cast<FileLocation*>(this)->_bookmark = nullptr; } QFileInfo f(name()); if (!f.isReadable()) return false; quint64 s = f.size(); if (s > INT_MAX) { DEBUG_LOG(("File location check: Wrong size %1").arg(s)); return false; } if (qint32(s) != size) { DEBUG_LOG(("File location check: Wrong size %1 when should be %2").arg(s).arg(size)); return false; } auto realModified = f.lastModified(); if (realModified != modified) { DEBUG_LOG(("File location check: Wrong last modified time %1 when should be %2").arg(realModified.toMSecsSinceEpoch()).arg(modified.toMSecsSinceEpoch())); return false; } return true; } const QString &FileLocation::name() const { return _bookmark ? _bookmark->name(fname) : fname; } QByteArray FileLocation::bookmark() const { return _bookmark ? _bookmark->bookmark() : QByteArray(); } void FileLocation::setBookmark(const QByteArray &bm) { _bookmark.reset(bm.isEmpty() ? nullptr : new PsFileBookmark(bm)); } bool FileLocation::accessEnable() const { return isEmpty() ? false : (_bookmark ? _bookmark->enable() : true); } void FileLocation::accessDisable() const { return _bookmark ? _bookmark->disable() : (void)0; } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/images.h����������������������������������������������������0000664�0000000�0000000�00000043056�13262451251�0021466�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "base/flags.h" enum class ImageRoundRadius { None, Large, Small, Ellipse, }; namespace Images { QPixmap PixmapFast(QImage &&image); QImage prepareBlur(QImage image); void prepareRound( QImage &image, ImageRoundRadius radius, RectParts corners = RectPart::AllCorners, QRect target = QRect()); void prepareRound( QImage &image, QImage *cornerMasks, RectParts corners = RectPart::AllCorners, QRect target = QRect()); void prepareCircle(QImage &image); QImage prepareColored(style::color add, QImage image); QImage prepareOpaque(QImage image); enum class Option { None = 0, Smooth = (1 << 0), Blurred = (1 << 1), Circled = (1 << 2), RoundedLarge = (1 << 3), RoundedSmall = (1 << 4), RoundedTopLeft = (1 << 5), RoundedTopRight = (1 << 6), RoundedBottomLeft = (1 << 7), RoundedBottomRight = (1 << 8), RoundedAll = (None | RoundedTopLeft | RoundedTopRight | RoundedBottomLeft | RoundedBottomRight), Colored = (1 << 9), TransparentBackground = (1 << 10), }; using Options = base::flags<Option>; inline constexpr auto is_flag_type(Option) { return true; }; QImage prepare(QImage img, int w, int h, Options options, int outerw, int outerh, const style::color *colored = nullptr); inline QPixmap pixmap(QImage img, int w, int h, Options options, int outerw, int outerh, const style::color *colored = nullptr) { return QPixmap::fromImage(prepare(img, w, h, options, outerw, outerh, colored), Qt::ColorOnly); } } // namespace Images class FileLoader; class mtpFileLoader; enum LoadFromCloudSetting { LoadFromCloudOrLocal, LoadFromLocalOnly, }; enum LoadToCacheSetting { LoadToFileOnly, LoadToCacheAsWell, }; inline uint32 packInt(int32 a) { return (a < 0) ? uint32(int64(a) + 0x100000000LL) : uint32(a); } inline int32 unpackInt(uint32 a) { return (a > 0x7FFFFFFFU) ? int32(int64(a) - 0x100000000LL) : int32(a); } inline uint64 packUIntUInt(uint32 a, uint32 b) { return (uint64(a) << 32) | uint64(b); } inline uint64 packUIntInt(uint32 a, int32 b) { return packUIntUInt(a, packInt(b)); } inline uint64 packIntUInt(int32 a, uint32 b) { return packUIntUInt(packInt(a), b); } inline uint64 packIntInt(int32 a, int32 b) { return packUIntUInt(packInt(a), packInt(b)); } inline uint32 unpackUIntFirst(uint64 v) { return uint32(v >> 32); } inline int32 unpackIntFirst(uint64 v) { return unpackInt(unpackUIntFirst(v)); } inline uint32 unpackUIntSecond(uint64 v) { return uint32(v & 0xFFFFFFFFULL); } inline int32 unpackIntSecond(uint64 v) { return unpackInt(unpackUIntSecond(v)); } class StorageImageLocation { public: StorageImageLocation() = default; StorageImageLocation(int32 width, int32 height, int32 dc, const uint64 &volume, int32 local, const uint64 &secret) : _widthheight(packIntInt(width, height)), _dclocal(packIntInt(dc, local)), _volume(volume), _secret(secret) { } StorageImageLocation(int32 width, int32 height, const MTPDfileLocation &location) : _widthheight(packIntInt(width, height)), _dclocal(packIntInt(location.vdc_id.v, location.vlocal_id.v)), _volume(location.vvolume_id.v), _secret(location.vsecret.v) { } bool isNull() const { return !_dclocal; } int32 width() const { return unpackIntFirst(_widthheight); } int32 height() const { return unpackIntSecond(_widthheight); } void setSize(int32 width, int32 height) { _widthheight = packIntInt(width, height); } int32 dc() const { return unpackIntFirst(_dclocal); } uint64 volume() const { return _volume; } int32 local() const { return unpackIntSecond(_dclocal); } uint64 secret() const { return _secret; } static StorageImageLocation FromMTP( int32 width, int32 height, const MTPFileLocation &location) { if (location.type() == mtpc_fileLocation) { const auto &data = location.c_fileLocation(); return StorageImageLocation(width, height, data); } return StorageImageLocation(width, height, 0, 0, 0, 0); } static StorageImageLocation FromMTP(const MTPPhotoSize &size) { switch (size.type()) { case mtpc_photoSize: { const auto &data = size.c_photoSize(); return FromMTP(data.vw.v, data.vh.v, data.vlocation); } break; case mtpc_photoCachedSize: { const auto &data = size.c_photoCachedSize(); return FromMTP(data.vw.v, data.vh.v, data.vlocation); } break; } return StorageImageLocation(); } static StorageImageLocation Null; private: uint64 _widthheight = 0; uint64 _dclocal = 0; uint64 _volume = 0; uint64 _secret = 0; friend inline bool operator==(const StorageImageLocation &a, const StorageImageLocation &b) { return (a._dclocal == b._dclocal) && (a._volume == b._volume) && (a._secret == b._secret); } }; inline bool operator!=(const StorageImageLocation &a, const StorageImageLocation &b) { return !(a == b); } class WebFileLocation { public: WebFileLocation() = default; WebFileLocation(int32 dc, const QByteArray &url, uint64 accessHash) : _accessHash(accessHash) , _url(url) , _dc(dc) { } bool isNull() const { return !_dc; } int32 dc() const { return _dc; } uint64 accessHash() const { return _accessHash; } const QByteArray &url() const { return _url; } static WebFileLocation Null; private: uint64 _accessHash = 0; QByteArray _url; int32 _dc = 0; friend inline bool operator==(const WebFileLocation &a, const WebFileLocation &b) { return (a._dc == b._dc) && (a._accessHash == b._accessHash) && (a._url == b._url); } }; inline bool operator!=(const WebFileLocation &a, const WebFileLocation &b) { return !(a == b); } class DelayedStorageImage; class HistoryItem; class Image { public: Image(const QString &file, QByteArray format = QByteArray()); Image(const QByteArray &filecontent, QByteArray format = QByteArray()); Image(const QPixmap &pixmap, QByteArray format = QByteArray()); Image(const QByteArray &filecontent, QByteArray format, const QPixmap &pixmap); virtual void automaticLoad(const HistoryItem *item) { // auto load photo } virtual void automaticLoadSettingsChanged() { } virtual bool loaded() const { return true; } virtual bool loading() const { return false; } virtual bool displayLoading() const { return false; } virtual void cancel() { } virtual float64 progress() const { return 1; } virtual int32 loadOffset() const { return 0; } const QPixmap &pix(int32 w = 0, int32 h = 0) const; const QPixmap &pixRounded(int32 w = 0, int32 h = 0, ImageRoundRadius radius = ImageRoundRadius::None, RectParts corners = RectPart::AllCorners) const; const QPixmap &pixBlurred(int32 w = 0, int32 h = 0) const; const QPixmap &pixColored(style::color add, int32 w = 0, int32 h = 0) const; const QPixmap &pixBlurredColored(style::color add, int32 w = 0, int32 h = 0) const; const QPixmap &pixSingle(int32 w, int32 h, int32 outerw, int32 outerh, ImageRoundRadius radius, RectParts corners = RectPart::AllCorners, const style::color *colored = nullptr) const; const QPixmap &pixBlurredSingle(int32 w, int32 h, int32 outerw, int32 outerh, ImageRoundRadius radius, RectParts corners = RectPart::AllCorners) const; const QPixmap &pixCircled(int32 w = 0, int32 h = 0) const; const QPixmap &pixBlurredCircled(int32 w = 0, int32 h = 0) const; QPixmap pixNoCache(int w = 0, int h = 0, Images::Options options = 0, int outerw = -1, int outerh = -1, const style::color *colored = nullptr) const; QPixmap pixColoredNoCache(style::color add, int32 w = 0, int32 h = 0, bool smooth = false) const; QPixmap pixBlurredColoredNoCache(style::color add, int32 w, int32 h = 0) const; int32 width() const { return qMax(countWidth(), 1); } int32 height() const { return qMax(countHeight(), 1); } virtual void load(bool loadFirst = false, bool prior = true) { } virtual void loadEvenCancelled(bool loadFirst = false, bool prior = true) { } virtual const StorageImageLocation &location() const { return StorageImageLocation::Null; } bool isNull() const; void forget() const; QByteArray savedFormat() const { return _format; } QByteArray savedData() const { return _saved; } virtual DelayedStorageImage *toDelayedStorageImage() { return 0; } virtual const DelayedStorageImage *toDelayedStorageImage() const { return 0; } virtual ~Image(); protected: Image(QByteArray format = "PNG") : _format(format), _forgot(false) { } void restore() const; virtual void checkload() const { } void invalidateSizeCache() const; virtual int32 countWidth() const { restore(); return _data.width(); } virtual int32 countHeight() const { restore(); return _data.height(); } virtual bool hasLocalCopy() const { return false; } mutable QByteArray _saved, _format; mutable bool _forgot; mutable QPixmap _data; private: using Sizes = QMap<uint64, QPixmap>; mutable Sizes _sizesCache; }; typedef QPair<uint64, uint64> StorageKey; inline uint64 storageMix32To64(int32 a, int32 b) { return (uint64(*reinterpret_cast<uint32*>(&a)) << 32) | uint64(*reinterpret_cast<uint32*>(&b)); } inline StorageKey storageKey(int32 dc, const uint64 &volume, int32 local) { return StorageKey(storageMix32To64(dc, local), volume); } inline StorageKey storageKey(const MTPDfileLocation &location) { return storageKey(location.vdc_id.v, location.vvolume_id.v, location.vlocal_id.v); } inline StorageKey storageKey(const StorageImageLocation &location) { return storageKey(location.dc(), location.volume(), location.local()); } inline StorageKey storageKey(const WebFileLocation &location) { auto url = location.url(); auto sha = hashSha1(url.data(), url.size()); return storageKey( location.dc(), *reinterpret_cast<const uint64*>(sha.data()), *reinterpret_cast<const int32*>(sha.data() + sizeof(uint64))); } class RemoteImage : public Image { public: void automaticLoad(const HistoryItem *item); // auto load photo void automaticLoadSettingsChanged(); bool loaded() const; bool loading() const { return amLoading(); } bool displayLoading() const; void cancel(); float64 progress() const; int32 loadOffset() const; void setData(QByteArray &bytes, const QByteArray &format = QByteArray()); void load(bool loadFirst = false, bool prior = true); void loadEvenCancelled(bool loadFirst = false, bool prior = true); ~RemoteImage(); protected: // If after loading the image we need to shrink it to fit into a // specific size, you can return this size here. virtual QSize shrinkBox() const { return QSize(); } virtual void setInformation(int32 size, int32 width, int32 height) = 0; virtual FileLoader *createLoader(LoadFromCloudSetting fromCloud, bool autoLoading) = 0; void checkload() const { doCheckload(); } void loadLocal(); private: mutable FileLoader *_loader = nullptr; bool amLoading() const; void doCheckload() const; void destroyLoaderDelayed(FileLoader *newValue = nullptr) const; }; class StorageImage : public RemoteImage { public: StorageImage(const StorageImageLocation &location, int32 size = 0); StorageImage(const StorageImageLocation &location, QByteArray &bytes); const StorageImageLocation &location() const override { return _location; } protected: void setInformation(int32 size, int32 width, int32 height) override; FileLoader *createLoader(LoadFromCloudSetting fromCloud, bool autoLoading) override; bool hasLocalCopy() const override; StorageImageLocation _location; int32 _size; int32 countWidth() const override; int32 countHeight() const override; }; class WebFileImage : public RemoteImage { public: WebFileImage(const WebFileLocation &location, QSize box, int size = 0); WebFileImage(const WebFileLocation &location, int width, int height, int size = 0); protected: void setInformation(int size, int width, int height) override; FileLoader *createLoader(LoadFromCloudSetting fromCloud, bool autoLoading) override; QSize shrinkBox() const override { return _box; } bool hasLocalCopy() const override; WebFileLocation _location; QSize _box; int _width = 0; int _height = 0; int _size = 0; int countWidth() const override; int countHeight() const override; }; class DelayedStorageImage : public StorageImage { public: DelayedStorageImage(); DelayedStorageImage(int32 w, int32 h); DelayedStorageImage(QByteArray &bytes); void setStorageLocation(const StorageImageLocation location); virtual DelayedStorageImage *toDelayedStorageImage() { return this; } virtual const DelayedStorageImage *toDelayedStorageImage() const { return this; } void automaticLoad(const HistoryItem *item); // auto load photo void automaticLoadSettingsChanged(); bool loading() const { return _location.isNull() ? _loadRequested : StorageImage::loading(); } bool displayLoading() const; void cancel(); void load(bool loadFirst = false, bool prior = true); void loadEvenCancelled(bool loadFirst = false, bool prior = true); private: bool _loadRequested, _loadCancelled, _loadFromCloud; }; class WebImage : public RemoteImage { public: // If !box.isEmpty() then resize the image to fit in this box. WebImage(const QString &url, QSize box = QSize()); WebImage(const QString &url, int width, int height); void setSize(int width, int height); protected: QSize shrinkBox() const override { return _box; } void setInformation(int32 size, int32 width, int32 height) override; FileLoader *createLoader(LoadFromCloudSetting fromCloud, bool autoLoading) override; int32 countWidth() const override; int32 countHeight() const override; bool hasLocalCopy() const override; private: QString _url; QSize _box; int32 _size, _width, _height; }; namespace internal { Image *getImage(const QString &file, QByteArray format); Image *getImage(const QString &url, QSize box); Image *getImage(const QString &url, int width, int height); Image *getImage(const QByteArray &filecontent, QByteArray format); Image *getImage(const QPixmap &pixmap, QByteArray format); Image *getImage( const QByteArray &filecontent, QByteArray format, const QPixmap &pixmap); Image *getImage(int32 width, int32 height); StorageImage *getImage(const StorageImageLocation &location, int size = 0); StorageImage *getImage( const StorageImageLocation &location, const QByteArray &bytes); Image *getImage(const MTPWebDocument &location); Image *getImage(const MTPWebDocument &location, QSize box); WebFileImage *getImage( const WebFileLocation &location, int width, int height, int size = 0); WebFileImage *getImage( const WebFileLocation &location, QSize box, int size = 0); } // namespace internal class ImagePtr : public ManagedPtr<Image> { public: ImagePtr(); ImagePtr(const QString &file, QByteArray format = QByteArray()) : Parent(internal::getImage(file, format)) { } ImagePtr(const QString &url, QSize box) : Parent(internal::getImage(url, box)) { } ImagePtr(const QString &url, int width, int height) : Parent(internal::getImage(url, width, height)) { } ImagePtr(const QByteArray &filecontent, QByteArray format = QByteArray()) : Parent(internal::getImage(filecontent, format)) { } ImagePtr(const QByteArray &filecontent, QByteArray format, const QPixmap &pixmap) : Parent(internal::getImage(filecontent, format, pixmap)) { } ImagePtr(const QPixmap &pixmap, QByteArray format) : Parent(internal::getImage(pixmap, format)) { } ImagePtr(const StorageImageLocation &location, int32 size = 0) : Parent(internal::getImage(location, size)) { } ImagePtr(const StorageImageLocation &location, const QByteArray &bytes) : Parent(internal::getImage(location, bytes)) { } ImagePtr(const MTPWebDocument &location) : Parent(internal::getImage(location)) { } ImagePtr(const MTPWebDocument &location, QSize box) : Parent(internal::getImage(location, box)) { } ImagePtr(const WebFileLocation &location, int width, int height, int size = 0) : Parent(internal::getImage(location, width, height, size)) { } ImagePtr(const WebFileLocation &location, QSize box, int size = 0) : Parent(internal::getImage(location, box, size)) { } ImagePtr(int32 width, int32 height, const MTPFileLocation &location, ImagePtr def = ImagePtr()); ImagePtr(int32 width, int32 height) : Parent(internal::getImage(width, height)) { } explicit operator bool() const { return (_data != nullptr) && !_data->isNull(); } }; inline QSize shrinkToKeepAspect(int32 width, int32 height, int32 towidth, int32 toheight) { int32 w = qMax(width, 1), h = qMax(height, 1); if (w * toheight > h * towidth) { h = qRound(h * towidth / float64(w)); w = towidth; } else { w = qRound(w * toheight / float64(h)); h = toheight; } return QSize(qMax(w, 1), qMax(h, 1)); } void clearStorageImages(); void clearAllImages(); int64 imageCacheSize(); class PsFileBookmark; class ReadAccessEnabler { public: ReadAccessEnabler(const PsFileBookmark *bookmark); ReadAccessEnabler(const std::shared_ptr<PsFileBookmark> &bookmark); bool failed() const { return _failed; } ~ReadAccessEnabler(); private: const PsFileBookmark *_bookmark; bool _failed; }; class FileLocation { public: FileLocation() = default; explicit FileLocation(const QString &name); bool check() const; const QString &name() const; void setBookmark(const QByteArray &bookmark); QByteArray bookmark() const; bool isEmpty() const { return name().isEmpty(); } bool accessEnable() const; void accessDisable() const; QString fname; QDateTime modified; qint32 size; private: std::shared_ptr<PsFileBookmark> _bookmark; }; inline bool operator==(const FileLocation &a, const FileLocation &b) { return (a.name() == b.name()) && (a.modified == b.modified) && (a.size == b.size); } inline bool operator!=(const FileLocation &a, const FileLocation &b) { return !(a == b); } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/resize_area.h�����������������������������������������������0000664�0000000�0000000�00000003113�13262451251�0022500�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/rp_widget.h" namespace Ui { class ResizeArea : public RpWidget { public: ResizeArea(QWidget *parent) : RpWidget(parent) { setCursor(style::cur_sizehor); } rpl::producer<int> moveLeft() const { return _moveLeft.events(); } template <typename Callback> void addMoveLeftCallback(Callback &&callback) { moveLeft( ) | rpl::start_with_next( std::forward<Callback>(callback), lifetime()); } rpl::producer<> moveFinished() const { return _moveFinished.events(); } template <typename Callback> void addMoveFinishedCallback(Callback &&callback) { moveFinished( ) | rpl::start_with_next( std::forward<Callback>(callback), lifetime()); } ~ResizeArea() { moveFinish(); } protected: void mousePressEvent(QMouseEvent *e) override { if (e->button() == Qt::LeftButton) { _moving = true; _moveStartLeft = e->pos().x(); } } void mouseReleaseEvent(QMouseEvent *e) override { if (e->button() == Qt::LeftButton) { moveFinish(); } } void mouseMoveEvent(QMouseEvent *e) override { if (_moving) { _moveLeft.fire(e->globalPos().x() - _moveStartLeft); } } private: void moveFinish() { if (base::take(_moving)) { _moveFinished.fire({}); } } rpl::event_stream<int> _moveLeft; rpl::event_stream<> _moveFinished; int _moveStartLeft = 0; bool _moving = false; }; } // namespace Ui �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/rp_widget.cpp�����������������������������������������������0000664�0000000�0000000�00000005763�13262451251�0022543�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/rp_widget.h" namespace Ui { rpl::producer<QRect> RpWidgetMethods::geometryValue() const { auto &stream = eventStreams().geometry; return stream.events_starting_with_copy(callGetGeometry()); } rpl::producer<QSize> RpWidgetMethods::sizeValue() const { return geometryValue() | rpl::map([](QRect &&value) { return value.size(); }) | rpl::distinct_until_changed(); } rpl::producer<int> RpWidgetMethods::heightValue() const { return geometryValue() | rpl::map([](QRect &&value) { return value.height(); }) | rpl::distinct_until_changed(); } rpl::producer<int> RpWidgetMethods::widthValue() const { return geometryValue() | rpl::map([](QRect &&value) { return value.width(); }) | rpl::distinct_until_changed(); } rpl::producer<QPoint> RpWidgetMethods::positionValue() const { return geometryValue() | rpl::map([](QRect &&value) { return value.topLeft(); }) | rpl::distinct_until_changed(); } rpl::producer<int> RpWidgetMethods::leftValue() const { return geometryValue() | rpl::map([](QRect &&value) { return value.left(); }) | rpl::distinct_until_changed(); } rpl::producer<int> RpWidgetMethods::topValue() const { return geometryValue() | rpl::map([](QRect &&value) { return value.top(); }) | rpl::distinct_until_changed(); } rpl::producer<int> RpWidgetMethods::desiredHeightValue() const { return heightValue(); } rpl::producer<bool> RpWidgetMethods::shownValue() const { auto &stream = eventStreams().shown; return stream.events_starting_with(!callIsHidden()); } rpl::producer<QRect> RpWidgetMethods::paintRequest() const { return eventStreams().paint.events(); } rpl::producer<> RpWidgetMethods::alive() const { return eventStreams().alive.events(); } rpl::lifetime &RpWidgetMethods::lifetime() { return _lifetime; } bool RpWidgetMethods::handleEvent(QEvent *event) { switch (event->type()) { case QEvent::Move: case QEvent::Resize: if (auto streams = _eventStreams.get()) { auto that = callCreateWeak(); streams->geometry.fire_copy(callGetGeometry()); if (!that) { return true; } } break; case QEvent::Paint: if (auto streams = _eventStreams.get()) { auto that = callCreateWeak(); streams->paint.fire_copy( static_cast<QPaintEvent*>(event)->rect()); if (!that) { return true; } } break; } return eventHook(event); } RpWidgetMethods::Initer::Initer(QWidget *parent) { parent->setGeometry(0, 0, 0, 0); } void RpWidgetMethods::visibilityChangedHook(bool wasVisible, bool nowVisible) { if (nowVisible != wasVisible) { if (auto streams = _eventStreams.get()) { streams->shown.fire_copy(nowVisible); } } } auto RpWidgetMethods::eventStreams() const -> EventStreams& { if (!_eventStreams) { _eventStreams = std::make_unique<EventStreams>(); } return *_eventStreams; } } // namespace Ui �������������tdesktop-1.2.17/Telegram/SourceFiles/ui/rp_widget.h�������������������������������������������������0000664�0000000�0000000�00000010141�13262451251�0022172�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/event_stream.h> #include <rpl/map.h> #include <rpl/distinct_until_changed.h> #include "base/unique_qptr.h" namespace Ui { namespace details { template <typename Value> class AttachmentOwner : public QObject { public: template <typename OtherValue> AttachmentOwner(QObject *parent, OtherValue &&value) : QObject(parent) , _value(std::forward<OtherValue>(value)) { } private: Value _value; }; } // namespace details template <typename Widget, typename ...Args> inline base::unique_qptr<Widget> CreateObject(Args &&...args) { return base::make_unique_q<Widget>( nullptr, std::forward<Args>(args)...); } template <typename Widget, typename Parent, typename ...Args> inline Widget *CreateChild( Parent *parent, Args &&...args) { Expects(parent != nullptr); return new Widget(parent, std::forward<Args>(args)...); } inline void DestroyChild(QWidget *child) { delete child; } template <typename Value> inline void AttachAsChild(not_null<QObject*> parent, Value &&value) { using PlainValue = std::decay_t<Value>; CreateChild<details::AttachmentOwner<PlainValue>>( parent.get(), std::forward<Value>(value)); } template <typename Widget> using RpWidgetParent = std::conditional_t< std::is_same_v<Widget, QWidget>, TWidget, TWidgetHelper<Widget>>; template <typename Widget> class RpWidgetWrap; class RpWidgetMethods { public: rpl::producer<QRect> geometryValue() const; rpl::producer<QSize> sizeValue() const; rpl::producer<int> heightValue() const; rpl::producer<int> widthValue() const; rpl::producer<QPoint> positionValue() const; rpl::producer<int> leftValue() const; rpl::producer<int> topValue() const; virtual rpl::producer<int> desiredHeightValue() const; rpl::producer<bool> shownValue() const; rpl::producer<QRect> paintRequest() const; rpl::producer<> alive() const; template <typename Error, typename Generator> void showOn(rpl::producer<bool, Error, Generator> &&shown) { std::move( shown ) | rpl::start_with_next([this](bool visible) { callSetVisible(visible); }, lifetime()); } rpl::lifetime &lifetime(); protected: bool handleEvent(QEvent *event); virtual bool eventHook(QEvent *event) = 0; private: template <typename Widget> friend class RpWidgetWrap; struct EventStreams { rpl::event_stream<QRect> geometry; rpl::event_stream<QRect> paint; rpl::event_stream<bool> shown; rpl::event_stream<> alive; }; struct Initer { Initer(QWidget *parent); }; virtual void callSetVisible(bool visible) = 0; virtual QPointer<QObject> callCreateWeak() = 0; virtual QRect callGetGeometry() const = 0; virtual bool callIsHidden() const = 0; void visibilityChangedHook(bool wasVisible, bool nowVisible); EventStreams &eventStreams() const; mutable std::unique_ptr<EventStreams> _eventStreams; rpl::lifetime _lifetime; }; template <typename Widget> class RpWidgetWrap : public RpWidgetParent<Widget> , public RpWidgetMethods { using Self = RpWidgetWrap<Widget>; using Parent = RpWidgetParent<Widget>; public: using Parent::Parent; void setVisible(bool visible) final override { auto wasVisible = !this->isHidden(); Parent::setVisible(visible); visibilityChangedHook(wasVisible, !this->isHidden()); } ~RpWidgetWrap() { base::take(_lifetime); base::take(_eventStreams); } protected: bool event(QEvent *event) final override { return handleEvent(event); } bool eventHook(QEvent *event) override { return Parent::event(event); } private: void callSetVisible(bool visible) override { Self::setVisible(visible); } QPointer<QObject> callCreateWeak() override { return QPointer<QObject>((QObject*)this); } QRect callGetGeometry() const override { return this->geometry(); } bool callIsHidden() const override { return this->isHidden(); } Initer _initer = { this }; }; class RpWidget : public RpWidgetWrap<QWidget> { public: using RpWidgetWrap<QWidget>::RpWidgetWrap; }; } // namespace Ui �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/search_field_controller.cpp���������������������������������0000664�0000000�0000000�00000005336�13262451251�0025426�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/search_field_controller.h" #include "styles/style_widgets.h" #include "ui/wrap/padding_wrap.h" #include "ui/widgets/input_fields.h" #include "ui/widgets/shadow.h" #include "ui/widgets/buttons.h" #include "lang/lang_keys.h" namespace Ui { SearchFieldController::SearchFieldController(const QString &query) : _query(query) { } auto SearchFieldController::createRowView( QWidget *parent, const style::SearchFieldRow &st) -> RowView { auto result = base::make_unique_q<Ui::FixedHeightWidget>( parent, st.height); auto wrap = result.get(); auto field = createField(wrap, st.field).release(); field->show(); field->connect(field, &Ui::InputField::cancelled, [=] { field->setText(QString()); }); auto cancel = CreateChild<Ui::CrossButton>( wrap, st.fieldCancel); cancel->addClickHandler([=] { field->setText(QString()); }); queryValue( ) | rpl::map([](const QString &value) { return !value.isEmpty(); }) | rpl::start_with_next([cancel](bool shown) { cancel->toggle(shown, anim::type::normal); }, cancel->lifetime()); cancel->finishAnimating(); auto shadow = CreateChild<Ui::PlainShadow>(wrap); shadow->show(); wrap->widthValue( ) | rpl::start_with_next([=, &st](int newWidth) { auto availableWidth = newWidth - st.fieldIconSkip - st.fieldCancelSkip; field->setGeometryToLeft( st.padding.left() + st.fieldIconSkip, st.padding.top(), availableWidth, field->height()); cancel->moveToRight(0, 0); shadow->setGeometry( 0, st.height - st::lineWidth, newWidth, st::lineWidth); }, wrap->lifetime()); wrap->paintRequest( ) | rpl::start_with_next([=, &st] { Painter p(wrap); st.fieldIcon.paint( p, st.padding.left(), st.padding.top(), wrap->width()); }, wrap->lifetime()); _view.release(); _view.reset(wrap); return { std::move(result), field }; } QString SearchFieldController::query() const { return _query.current(); } rpl::producer<QString> SearchFieldController::queryValue() const { return _query.value(); } rpl::producer<QString> SearchFieldController::queryChanges() const { return _query.changes(); } base::unique_qptr<Ui::InputField> SearchFieldController::createField( QWidget *parent, const style::InputField &st) { auto result = base::make_unique_q<Ui::InputField>( parent, st, langFactory(lng_dlg_filter), _query.current()); auto field = result.get(); field->connect(field, &Ui::InputField::changed, [=] { _query = field->getLastText(); }); _view.reset(field); return result; } } // namespace Ui ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/search_field_controller.h�����������������������������������0000664�0000000�0000000�00000002147�13262451251�0025070�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/variable.h> #include "ui/rp_widget.h" #include "base/unique_qptr.h" namespace style { struct SearchFieldRow; struct InputField; } // namespace style namespace Ui { class CrossButton; class InputField; class SearchFieldController { public: SearchFieldController(const QString &query); base::unique_qptr<Ui::InputField> createField( QWidget *parent, const style::InputField &st); struct RowView { base::unique_qptr<Ui::RpWidget> wrap; QPointer<Ui::InputField> field; }; RowView createRowView( QWidget *parent, const style::SearchFieldRow &st); QString query() const; rpl::producer<QString> queryValue() const; rpl::producer<QString> queryChanges() const; rpl::lifetime &lifetime() { return _lifetime; } private: base::unique_qptr<QWidget> _view; rpl::variable<QString> _query; rpl::lifetime _lifetime; }; } // namespace Ui �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/special_buttons.cpp�����������������������������������������0000664�0000000�0000000�00000062674�13262451251�0023761�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/special_buttons.h" #include "styles/style_boxes.h" #include "styles/style_history.h" #include "dialogs/dialogs_layout.h" #include "ui/effects/ripple_animation.h" #include "ui/empty_userpic.h" #include "data/data_photo.h" #include "data/data_session.h" #include "data/data_feed.h" #include "history/history.h" #include "core/file_utilities.h" #include "boxes/photo_crop_box.h" #include "boxes/confirm_box.h" #include "window/window_controller.h" #include "lang/lang_keys.h" #include "auth_session.h" #include "mainwidget.h" #include "messenger.h" #include "observer_peer.h" namespace Ui { namespace { constexpr int kWideScale = 5; template <typename Callback> QPixmap CreateSquarePixmap(int width, Callback &&paintCallback) { auto size = QSize(width, width) * cIntRetinaFactor(); auto image = QImage(size, QImage::Format_ARGB32_Premultiplied); image.setDevicePixelRatio(cRetinaFactor()); image.fill(Qt::transparent); { Painter p(&image); paintCallback(p); } return App::pixmapFromImageInPlace(std::move(image)); }; template <typename Callback> void SuggestPhoto( const QImage &image, PeerId peerForCrop, Callback &&callback) { auto badAspect = [](int a, int b) { return (a >= 10 * b); }; if (image.isNull() || badAspect(image.width(), image.height()) || badAspect(image.height(), image.width())) { Ui::show( Box<InformBox>(lang(lng_bad_photo)), LayerOption::KeepOther); return; } auto box = Ui::show( Box<PhotoCropBox>(image, peerForCrop), LayerOption::KeepOther); box->ready( ) | rpl::start_with_next( std::forward<Callback>(callback), box->lifetime()); } template <typename Callback> void SuggestPhotoFile( const FileDialog::OpenResult &result, PeerId peerForCrop, Callback &&callback) { if (result.paths.isEmpty() && result.remoteContent.isEmpty()) { return; } auto image = [&] { if (!result.remoteContent.isEmpty()) { return App::readImage(result.remoteContent); } else if (!result.paths.isEmpty()) { return App::readImage(result.paths.front()); } return QImage(); }(); SuggestPhoto( image, peerForCrop, std::forward<Callback>(callback)); } template <typename Callback> void ShowChoosePhotoBox(PeerId peerForCrop, Callback &&callback) { auto imgExtensions = cImgExtensions(); auto filter = qsl("Image files (*") + imgExtensions.join(qsl(" *")) + qsl(");;") + FileDialog::AllFilesFilter(); auto handleChosenPhoto = [ peerForCrop, callback = std::forward<Callback>(callback) ](auto &&result) mutable { SuggestPhotoFile(result, peerForCrop, std::move(callback)); }; FileDialog::GetOpenPath( lang(lng_choose_image), filter, std::move(handleChosenPhoto)); } } // namespace HistoryDownButton::HistoryDownButton(QWidget *parent, const style::TwoIconButton &st) : RippleButton(parent, st.ripple) , _st(st) { resize(_st.width, _st.height); setCursor(style::cur_pointer); hide(); } QImage HistoryDownButton::prepareRippleMask() const { return Ui::RippleAnimation::ellipseMask(QSize(_st.rippleAreaSize, _st.rippleAreaSize)); } QPoint HistoryDownButton::prepareRippleStartPosition() const { return mapFromGlobal(QCursor::pos()) - _st.rippleAreaPosition; } void HistoryDownButton::paintEvent(QPaintEvent *e) { Painter p(this); auto ms = getms(); auto over = isOver(); auto down = isDown(); ((over || down) ? _st.iconBelowOver : _st.iconBelow).paint(p, _st.iconPosition, width()); paintRipple(p, _st.rippleAreaPosition.x(), _st.rippleAreaPosition.y(), ms); ((over || down) ? _st.iconAboveOver : _st.iconAbove).paint(p, _st.iconPosition, width()); if (_unreadCount > 0) { auto unreadString = QString::number(_unreadCount); if (unreadString.size() > 4) { unreadString = qsl("..") + unreadString.mid(unreadString.size() - 4); } Dialogs::Layout::UnreadBadgeStyle st; st.align = style::al_center; st.font = st::historyToDownBadgeFont; st.size = st::historyToDownBadgeSize; st.sizeId = Dialogs::Layout::UnreadBadgeInHistoryToDown; Dialogs::Layout::paintUnreadCount(p, unreadString, width(), 0, st, nullptr); } } void HistoryDownButton::setUnreadCount(int unreadCount) { if (_unreadCount != unreadCount) { _unreadCount = unreadCount; update(); } } EmojiButton::EmojiButton(QWidget *parent, const style::IconButton &st) : RippleButton(parent, st.ripple) , _st(st) , _a_loading(animation(this, &EmojiButton::step_loading)) { resize(_st.width, _st.height); setCursor(style::cur_pointer); } void EmojiButton::paintEvent(QPaintEvent *e) { Painter p(this); auto ms = getms(); p.fillRect(e->rect(), st::historyComposeAreaBg); paintRipple(p, _st.rippleAreaPosition.x(), _st.rippleAreaPosition.y(), ms, _rippleOverride ? &(*_rippleOverride)->c : nullptr); auto loading = a_loading.current(ms, _loading ? 1 : 0); p.setOpacity(1 - loading); auto over = isOver(); auto icon = _iconOverride ? _iconOverride : &(over ? _st.iconOver : _st.icon); icon->paint(p, _st.iconPosition, width()); p.setOpacity(1.); auto pen = _colorOverride ? (*_colorOverride)->p : (over ? st::historyEmojiCircleFgOver : st::historyEmojiCircleFg)->p; pen.setWidth(st::historyEmojiCircleLine); pen.setCapStyle(Qt::RoundCap); p.setPen(pen); p.setBrush(Qt::NoBrush); PainterHighQualityEnabler hq(p); QRect inner(QPoint((width() - st::historyEmojiCircle.width()) / 2, st::historyEmojiCircleTop), st::historyEmojiCircle); if (loading > 0) { int32 full = FullArcLength; int32 start = qRound(full * float64(ms % st::historyEmojiCirclePeriod) / st::historyEmojiCirclePeriod), part = qRound(loading * full / st::historyEmojiCirclePart); p.drawArc(inner, start, full - part); } else { p.drawEllipse(inner); } } void EmojiButton::setLoading(bool loading) { if (_loading != loading) { _loading = loading; auto from = loading ? 0. : 1., to = loading ? 1. : 0.; a_loading.start([this] { update(); }, from, to, st::historyEmojiCircleDuration); if (loading) { _a_loading.start(); } else { _a_loading.stop(); } } } void EmojiButton::setColorOverrides(const style::icon *iconOverride, const style::color *colorOverride, const style::color *rippleOverride) { _iconOverride = iconOverride; _colorOverride = colorOverride; _rippleOverride = rippleOverride; update(); } void EmojiButton::onStateChanged(State was, StateChangeSource source) { RippleButton::onStateChanged(was, source); auto wasOver = static_cast<bool>(was & StateFlag::Over); if (isOver() != wasOver) { update(); } } QPoint EmojiButton::prepareRippleStartPosition() const { return mapFromGlobal(QCursor::pos()) - _st.rippleAreaPosition; } QImage EmojiButton::prepareRippleMask() const { return RippleAnimation::ellipseMask(QSize(_st.rippleAreaSize, _st.rippleAreaSize)); } SendButton::SendButton(QWidget *parent) : RippleButton(parent, st::historyReplyCancel.ripple) { resize(st::historySendSize); } void SendButton::setType(Type type) { if (_type != type) { _contentFrom = grabContent(); _type = type; _a_typeChanged.finish(); _contentTo = grabContent(); _a_typeChanged.start([this] { update(); }, 0., 1., st::historyRecordVoiceDuration); update(); } if (_type != Type::Record) { _recordActive = false; _a_recordActive.finish(); } } void SendButton::setRecordActive(bool recordActive) { if (_recordActive != recordActive) { _recordActive = recordActive; _a_recordActive.start([this] { recordAnimationCallback(); }, _recordActive ? 0. : 1., _recordActive ? 1. : 0, st::historyRecordVoiceDuration); update(); } } void SendButton::finishAnimating() { _a_typeChanged.finish(); _a_recordActive.finish(); update(); } void SendButton::mouseMoveEvent(QMouseEvent *e) { AbstractButton::mouseMoveEvent(e); if (_recording) { if (_recordUpdateCallback) { _recordUpdateCallback(e->globalPos()); } } } void SendButton::paintEvent(QPaintEvent *e) { Painter p(this); auto ms = getms(); auto over = (isDown() || isOver()); auto changed = _a_typeChanged.current(ms, 1.); if (changed < 1.) { PainterHighQualityEnabler hq(p); p.setOpacity(1. - changed); auto targetRect = QRect((1 - kWideScale) / 2 * width(), (1 - kWideScale) / 2 * height(), kWideScale * width(), kWideScale * height()); auto hiddenWidth = anim::interpolate(0, (1 - kWideScale) / 2 * width(), changed); auto hiddenHeight = anim::interpolate(0, (1 - kWideScale) / 2 * height(), changed); p.drawPixmap(targetRect.marginsAdded(QMargins(hiddenWidth, hiddenHeight, hiddenWidth, hiddenHeight)), _contentFrom); p.setOpacity(changed); auto shownWidth = anim::interpolate((1 - kWideScale) / 2 * width(), 0, changed); auto shownHeight = anim::interpolate((1 - kWideScale) / 2 * height(), 0, changed); p.drawPixmap(targetRect.marginsAdded(QMargins(shownWidth, shownHeight, shownWidth, shownHeight)), _contentTo); } else if (_type == Type::Record) { auto recordActive = recordActiveRatio(); auto rippleColor = anim::color(st::historyAttachEmoji.ripple.color, st::historyRecordVoiceRippleBgActive, recordActive); paintRipple(p, (width() - st::historyAttachEmoji.rippleAreaSize) / 2, st::historyAttachEmoji.rippleAreaPosition.y(), ms, &rippleColor); auto fastIcon = [&] { if (recordActive == 1.) { return &st::historyRecordVoiceActive; } else if (over) { return &st::historyRecordVoiceOver; } return &st::historyRecordVoice; }; fastIcon()->paintInCenter(p, rect()); if (recordActive > 0. && recordActive < 1.) { p.setOpacity(recordActive); st::historyRecordVoiceActive.paintInCenter(p, rect()); p.setOpacity(1.); } } else if (_type == Type::Save) { auto &saveIcon = over ? st::historyEditSaveIconOver : st::historyEditSaveIcon; saveIcon.paint(p, st::historySendIconPosition, width()); } else if (_type == Type::Cancel) { paintRipple(p, (width() - st::historyAttachEmoji.rippleAreaSize) / 2, st::historyAttachEmoji.rippleAreaPosition.y(), ms); auto &cancelIcon = over ? st::historyReplyCancelIconOver : st::historyReplyCancelIcon; cancelIcon.paintInCenter(p, rect()); } else { auto &sendIcon = over ? st::historySendIconOver : st::historySendIcon; sendIcon.paint(p, st::historySendIconPosition, width()); } } void SendButton::onStateChanged(State was, StateChangeSource source) { RippleButton::onStateChanged(was, source); auto down = (state() & StateFlag::Down); if ((was & StateFlag::Down) != down) { if (down) { if (_type == Type::Record) { _recording = true; if (_recordStartCallback) { _recordStartCallback(); } } } else if (_recording) { _recording = false; if (_recordStopCallback) { _recordStopCallback(_recordActive); } } } } QPixmap SendButton::grabContent() { auto result = QImage(kWideScale * size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); result.setDevicePixelRatio(cRetinaFactor()); result.fill(Qt::transparent); { Painter p(&result); p.drawPixmap( (kWideScale - 1) / 2 * width(), (kWideScale - 1) / 2 * height(), GrabWidget(this)); } return App::pixmapFromImageInPlace(std::move(result)); } QImage SendButton::prepareRippleMask() const { auto size = (_type == Type::Record) ? st::historyAttachEmoji.rippleAreaSize : st::historyReplyCancel.rippleAreaSize; return Ui::RippleAnimation::ellipseMask(QSize(size, size)); } QPoint SendButton::prepareRippleStartPosition() const { auto real = mapFromGlobal(QCursor::pos()); auto size = (_type == Type::Record) ? st::historyAttachEmoji.rippleAreaSize : st::historyReplyCancel.rippleAreaSize; auto y = (_type == Type::Record) ? st::historyAttachEmoji.rippleAreaPosition.y() : (height() - st::historyReplyCancel.rippleAreaSize) / 2; return real - QPoint((width() - size) / 2, y); } void SendButton::recordAnimationCallback() { update(); if (_recordAnimationCallback) { _recordAnimationCallback(); } } UserpicButton::UserpicButton( QWidget *parent, PeerId peerForCrop, Role role, const style::UserpicButton &st) : RippleButton(parent, st.changeButton.ripple) , _st(st) , _peerForCrop(peerForCrop) , _role(role) { Expects(_role == Role::ChangePhoto); _waiting = false; prepare(); } UserpicButton::UserpicButton( QWidget *parent, not_null<Window::Controller*> controller, not_null<PeerData*> peer, Role role, const style::UserpicButton &st) : RippleButton(parent, st.changeButton.ripple) , _st(st) , _controller(controller) , _peer(peer) , _peerForCrop(_peer->id) , _role(role) { processPeerPhoto(); prepare(); setupPeerViewers(); } void UserpicButton::prepare() { resize(_st.size); _notShownYet = _waiting; if (!_waiting) { prepareUserpicPixmap(); } setClickHandlerByRole(); } void UserpicButton::setClickHandlerByRole() { switch (_role) { case Role::ChangePhoto: addClickHandler(App::LambdaDelayed( _st.changeButton.ripple.hideDuration, this, [this] { changePhotoLazy(); })); break; case Role::OpenPhoto: addClickHandler([this] { openPeerPhoto(); }); break; case Role::OpenProfile: addClickHandler([this] { Expects(_controller != nullptr); _controller->showPeerInfo(_peer); }); break; } } void UserpicButton::changePhotoLazy() { auto callback = base::lambda_guarded( this, [this](QImage &&image) { setImage(std::move(image)); }); ShowChoosePhotoBox(_peerForCrop, std::move(callback)); } void UserpicButton::uploadNewPeerPhoto() { auto callback = base::lambda_guarded( this, [this](QImage &&image) { Messenger::Instance().uploadProfilePhoto( std::move(image), _peer->id ); }); ShowChoosePhotoBox(_peerForCrop, std::move(callback)); } void UserpicButton::openPeerPhoto() { Expects(_peer != nullptr); Expects(_controller != nullptr); if (_changeOverlayEnabled && _cursorInChangeOverlay) { uploadNewPeerPhoto(); return; } const auto id = _peer->userpicPhotoId(); if (!id) { return; } const auto photo = Auth().data().photo(id); if (photo->date) { Messenger::Instance().showPhoto(photo, _peer); } } void UserpicButton::setupPeerViewers() { Notify::PeerUpdateViewer( _peer, Notify::PeerUpdate::Flag::PhotoChanged ) | rpl::start_with_next([this] { processNewPeerPhoto(); update(); }, lifetime()); base::ObservableViewer( Auth().downloaderTaskFinished() ) | rpl::start_with_next([this] { if (_waiting && _peer->userpicLoaded()) { _waiting = false; startNewPhotoShowing(); } }, lifetime()); } void UserpicButton::paintEvent(QPaintEvent *e) { Painter p(this); if (!_waiting && _notShownYet) { _notShownYet = false; startAnimation(); } auto photoPosition = countPhotoPosition(); auto photoLeft = photoPosition.x(); auto photoTop = photoPosition.y(); auto ms = getms(); if (showSavedMessages()) { Ui::EmptyUserpic::PaintSavedMessages( p, photoPosition.x(), photoPosition.y(), width(), _st.photoSize); } else { if (_a_appearance.animating(ms)) { p.drawPixmapLeft(photoPosition, width(), _oldUserpic); p.setOpacity(_a_appearance.current()); } p.drawPixmapLeft(photoPosition, width(), _userpic); } if (_role == Role::ChangePhoto) { auto over = isOver() || isDown(); if (over) { PainterHighQualityEnabler hq(p); p.setPen(Qt::NoPen); p.setBrush(_userpicHasImage ? st::msgDateImgBg : _st.changeButton.textBgOver); p.drawEllipse( photoLeft, photoTop, _st.photoSize, _st.photoSize); } paintRipple( p, photoLeft, photoTop, ms, _userpicHasImage ? &st::shadowFg->c : &_st.changeButton.ripple.color->c); if (over || !_userpicHasImage) { auto iconLeft = (_st.changeIconPosition.x() < 0) ? (_st.photoSize - _st.changeIcon.width()) / 2 : _st.changeIconPosition.x(); auto iconTop = (_st.changeIconPosition.y() < 0) ? (_st.photoSize - _st.changeIcon.height()) / 2 : _st.changeIconPosition.y(); _st.changeIcon.paint( p, photoLeft + iconLeft, photoTop + iconTop, width()); } } else if (_changeOverlayEnabled) { auto current = _changeOverlayShown.current( ms, (isOver() || isDown()) ? 1. : 0.); auto barHeight = anim::interpolate( 0, _st.uploadHeight, current); if (barHeight > 0) { auto barLeft = photoLeft; auto barTop = photoTop + _st.photoSize - barHeight; auto rect = QRect( barLeft, barTop, _st.photoSize, barHeight); p.setClipRect(rect); { PainterHighQualityEnabler hq(p); p.setPen(Qt::NoPen); p.setBrush(_st.uploadBg); p.drawEllipse( photoLeft, photoTop, _st.photoSize, _st.photoSize); } auto iconLeft = (_st.uploadIconPosition.x() < 0) ? (_st.photoSize - _st.uploadIcon.width()) / 2 : _st.uploadIconPosition.x(); auto iconTop = (_st.uploadIconPosition.y() < 0) ? (_st.uploadHeight - _st.uploadIcon.height()) / 2 : _st.uploadIconPosition.y(); if (iconTop < barHeight) { _st.uploadIcon.paint( p, barLeft + iconLeft, barTop + iconTop, width()); } } } } QPoint UserpicButton::countPhotoPosition() const { auto photoLeft = (_st.photoPosition.x() < 0) ? (width() - _st.photoSize) / 2 : _st.photoPosition.x(); auto photoTop = (_st.photoPosition.y() < 0) ? (height() - _st.photoSize) / 2 : _st.photoPosition.y(); return { photoLeft, photoTop }; } QImage UserpicButton::prepareRippleMask() const { return Ui::RippleAnimation::ellipseMask(QSize( _st.photoSize, _st.photoSize)); } QPoint UserpicButton::prepareRippleStartPosition() const { return (_role == Role::ChangePhoto) ? mapFromGlobal(QCursor::pos()) - countPhotoPosition() : DisabledRippleStartPosition(); } void UserpicButton::processPeerPhoto() { Expects(_peer != nullptr); _waiting = !_peer->userpicLoaded(); if (_waiting) { _peer->loadUserpic(true); } if (_role == Role::OpenPhoto) { if (_peer->userpicPhotoUnknown()) { _peer->updateFullForced(); } _canOpenPhoto = (_peer->userpicPhotoId() != 0); updateCursor(); } } void UserpicButton::updateCursor() { Expects(_role == Role::OpenPhoto); auto pointer = _canOpenPhoto || (_changeOverlayEnabled && _cursorInChangeOverlay); setPointerCursor(pointer); } void UserpicButton::mouseMoveEvent(QMouseEvent *e) { RippleButton::mouseMoveEvent(e); if (_role == Role::OpenPhoto) { updateCursorInChangeOverlay(e->pos()); } } void UserpicButton::updateCursorInChangeOverlay(QPoint localPos) { auto photoPosition = countPhotoPosition(); auto overlayRect = QRect( photoPosition.x(), photoPosition.y() + _st.photoSize - _st.uploadHeight, _st.photoSize, _st.uploadHeight); auto inOverlay = overlayRect.contains(localPos); setCursorInChangeOverlay(inOverlay); } void UserpicButton::leaveEventHook(QEvent *e) { if (_role == Role::OpenPhoto) { setCursorInChangeOverlay(false); } return RippleButton::leaveEventHook(e); } void UserpicButton::setCursorInChangeOverlay(bool inOverlay) { Expects(_role == Role::OpenPhoto); if (_cursorInChangeOverlay != inOverlay) { _cursorInChangeOverlay = inOverlay; updateCursor(); } } void UserpicButton::processNewPeerPhoto() { if (_userpicCustom) { return; } processPeerPhoto(); if (!_waiting) { grabOldUserpic(); startNewPhotoShowing(); } } void UserpicButton::grabOldUserpic() { auto photoRect = QRect( countPhotoPosition(), QSize(_st.photoSize, _st.photoSize) ); _oldUserpic = GrabWidget(this, photoRect); } void UserpicButton::startNewPhotoShowing() { auto oldUniqueKey = _userpicUniqueKey; prepareUserpicPixmap(); update(); if (_notShownYet) { return; } if (oldUniqueKey != _userpicUniqueKey || _a_appearance.animating()) { startAnimation(); } } void UserpicButton::startAnimation() { _a_appearance.finish(); _a_appearance.start([this] { update(); }, 0, 1, _st.duration); } void UserpicButton::switchChangePhotoOverlay(bool enabled) { Expects(_role == Role::OpenPhoto); if (_changeOverlayEnabled != enabled) { _changeOverlayEnabled = enabled; if (enabled) { if (isOver()) { startChangeOverlayAnimation(); } updateCursorInChangeOverlay( mapFromGlobal(QCursor::pos())); } else { _changeOverlayShown.finish(); update(); } } } void UserpicButton::showSavedMessagesOnSelf(bool enabled) { if (_showSavedMessagesOnSelf != enabled) { _showSavedMessagesOnSelf = enabled; update(); } } bool UserpicButton::showSavedMessages() const { return _showSavedMessagesOnSelf && _peer && _peer->isSelf(); } void UserpicButton::startChangeOverlayAnimation() { auto over = isOver() || isDown(); _changeOverlayShown.start( [this] { update(); }, over ? 0. : 1., over ? 1. : 0., st::slideWrapDuration); update(); } void UserpicButton::onStateChanged( State was, StateChangeSource source) { RippleButton::onStateChanged(was, source); if (_changeOverlayEnabled) { auto mask = (StateFlag::Over | StateFlag::Down); auto wasOver = (was & mask) != 0; auto nowOver = (state() & mask) != 0; if (wasOver != nowOver) { startChangeOverlayAnimation(); } } } void UserpicButton::setImage(QImage &&image) { grabOldUserpic(); auto size = QSize(_st.photoSize, _st.photoSize); auto small = image.scaled( size * cIntRetinaFactor(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); Images::prepareCircle(small); _userpic = App::pixmapFromImageInPlace(std::move(small)); _userpic.setDevicePixelRatio(cRetinaFactor()); _userpicCustom = _userpicHasImage = true; _result = std::move(image); startNewPhotoShowing(); } void UserpicButton::prepareUserpicPixmap() { if (_userpicCustom) { return; } auto size = _st.photoSize; auto paintButton = [&](Painter &p, const style::color &color) { PainterHighQualityEnabler hq(p); p.setBrush(color); p.setPen(Qt::NoPen); p.drawEllipse(0, 0, size, size); }; _userpicHasImage = _peer ? (_peer->currentUserpic() || _role != Role::ChangePhoto) : false; _userpic = CreateSquarePixmap(size, [&](Painter &p) { if (_userpicHasImage) { _peer->paintUserpic(p, 0, 0, _st.photoSize); } else { paintButton(p, _st.changeButton.textBg); } }); _userpicUniqueKey = _userpicHasImage ? _peer->userpicUniqueKey() : StorageKey(); } FeedUserpicButton::FeedUserpicButton( QWidget *parent, not_null<Window::Controller*> controller, not_null<Data::Feed*> feed, const style::FeedUserpicButton &st) : AbstractButton(parent) , _st(st) , _controller(controller) , _feed(feed) { prepare(); } void FeedUserpicButton::prepare() { resize(_st.size); Auth().data().feedUpdated( ) | rpl::filter([=](const Data::FeedUpdate &update) { return (update.feed == _feed) && (update.flag == Data::FeedUpdateFlag::Channels); }) | rpl::start_with_next([=] { crl::on_main(this, [=] { checkParts(); }); }, lifetime()); refreshParts(); } void FeedUserpicButton::checkParts() { if (!partsAreValid()) { refreshParts(); } } bool FeedUserpicButton::partsAreValid() const { const auto &channels = _feed->channels(); const auto count = std::min(int(channels.size()), 4); if (count != _parts.size()) { return false; } for (auto i = 0; i != count; ++i) { if (channels[i]->peer != _parts[i].channel) { return false; } } return true; } void FeedUserpicButton::refreshParts() { const auto &channels = _feed->channels(); const auto count = std::min(int(channels.size()), 4); const auto createButton = [&](not_null<ChannelData*> channel) { auto result = base::make_unique_q<Ui::UserpicButton>( this, _controller, channel, Ui::UserpicButton::Role::Custom, _st.innerPart); result->setAttribute(Qt::WA_TransparentForMouseEvents); result->show(); return result; }; const auto position = countInnerPosition(); auto x = position.x(); auto y = position.y(); const auto delta = _st.innerSize - _st.innerPart.photoSize; _parts.clear(); for (auto i = 0; i != count; ++i) { const auto channel = channels[i]->peer->asChannel(); _parts.push_back({ channel, createButton(channel) }); _parts.back().button->moveToLeft(x, y); switch (i) { case 0: case 2: x += delta; break; case 1: x -= delta; y += delta; break; } } } QPoint FeedUserpicButton::countInnerPosition() const { auto innerLeft = (_st.innerPosition.x() < 0) ? (width() - _st.innerSize) / 2 : _st.innerPosition.x(); auto innerTop = (_st.innerPosition.y() < 0) ? (height() - _st.innerSize) / 2 : _st.innerPosition.y(); return { innerLeft, innerTop }; } SilentToggle::SilentToggle(QWidget *parent, not_null<ChannelData*> channel) : IconButton(parent, st::historySilentToggle) , _channel(channel) , _checked(_channel->notifySilentPosts()) { Expects(!_channel->notifySettingsUnknown()); if (_checked) { refreshIconOverrides(); } setMouseTracking(true); } void SilentToggle::mouseMoveEvent(QMouseEvent *e) { IconButton::mouseMoveEvent(e); if (rect().contains(e->pos())) { Ui::Tooltip::Show(1000, this); } else { Ui::Tooltip::Hide(); } } void SilentToggle::setChecked(bool checked) { if (_checked != checked) { _checked = checked; refreshIconOverrides(); } } void SilentToggle::refreshIconOverrides() { const auto iconOverride = _checked ? &st::historySilentToggleOn : nullptr; const auto iconOverOverride = _checked ? &st::historySilentToggleOnOver : nullptr; setIconOverride(iconOverride, iconOverOverride); } void SilentToggle::leaveEventHook(QEvent *e) { IconButton::leaveEventHook(e); Ui::Tooltip::Hide(); } void SilentToggle::mouseReleaseEvent(QMouseEvent *e) { setChecked(!_checked); IconButton::mouseReleaseEvent(e); Ui::Tooltip::Show(0, this); const auto silentState = _checked ? Data::NotifySettings::SilentPostsChange::Silent : Data::NotifySettings::SilentPostsChange::Notify; App::main()->updateNotifySettings( _channel, Data::NotifySettings::MuteChange::Ignore, silentState); } QString SilentToggle::tooltipText() const { return lang(_checked ? lng_wont_be_notified : lng_will_be_notified); } QPoint SilentToggle::tooltipPos() const { return QCursor::pos(); } } // namespace Ui ��������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/special_buttons.h�������������������������������������������0000664�0000000�0000000�00000014677�13262451251�0023426�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/widgets/buttons.h" #include "ui/widgets/tooltip.h" #include "styles/style_window.h" #include "styles/style_widgets.h" class PeerData; namespace Data { class Feed; } // namespace Data namespace Window { class Controller; } // namespace Window namespace Ui { class HistoryDownButton : public RippleButton { public: HistoryDownButton(QWidget *parent, const style::TwoIconButton &st); void setUnreadCount(int unreadCount); int unreadCount() const { return _unreadCount; } protected: void paintEvent(QPaintEvent *e) override; QImage prepareRippleMask() const override; QPoint prepareRippleStartPosition() const override; private: const style::TwoIconButton &_st; int _unreadCount = 0; }; class EmojiButton : public RippleButton { public: EmojiButton(QWidget *parent, const style::IconButton &st); void setLoading(bool loading); void setColorOverrides(const style::icon *iconOverride, const style::color *colorOverride, const style::color *rippleOverride); protected: void paintEvent(QPaintEvent *e) override; void onStateChanged(State was, StateChangeSource source) override; QImage prepareRippleMask() const override; QPoint prepareRippleStartPosition() const override; private: void step_loading(TimeMs ms, bool timer) { if (timer) { update(); } } const style::IconButton &_st; bool _loading = false; Animation a_loading; BasicAnimation _a_loading; const style::icon *_iconOverride = nullptr; const style::color *_colorOverride = nullptr; const style::color *_rippleOverride = nullptr; }; class SendButton : public RippleButton { public: SendButton(QWidget *parent); enum class Type { Send, Save, Record, Cancel, }; Type type() const { return _type; } void setType(Type state); void setRecordActive(bool recordActive); void finishAnimating(); void setRecordStartCallback(base::lambda<void()> callback) { _recordStartCallback = std::move(callback); } void setRecordUpdateCallback(base::lambda<void(QPoint globalPos)> callback) { _recordUpdateCallback = std::move(callback); } void setRecordStopCallback(base::lambda<void(bool active)> callback) { _recordStopCallback = std::move(callback); } void setRecordAnimationCallback(base::lambda<void()> callback) { _recordAnimationCallback = std::move(callback); } float64 recordActiveRatio() { return _a_recordActive.current(getms(), _recordActive ? 1. : 0.); } protected: void mouseMoveEvent(QMouseEvent *e) override; void paintEvent(QPaintEvent *e) override; void onStateChanged(State was, StateChangeSource source) override; QImage prepareRippleMask() const override; QPoint prepareRippleStartPosition() const override; private: void recordAnimationCallback(); QPixmap grabContent(); Type _type = Type::Send; bool _recordActive = false; QPixmap _contentFrom, _contentTo; Animation _a_typeChanged; Animation _a_recordActive; bool _recording = false; base::lambda<void()> _recordStartCallback; base::lambda<void(bool active)> _recordStopCallback; base::lambda<void(QPoint globalPos)> _recordUpdateCallback; base::lambda<void()> _recordAnimationCallback; }; class UserpicButton : public RippleButton { public: enum class Role { ChangePhoto, OpenPhoto, OpenProfile, Custom, }; UserpicButton( QWidget *parent, PeerId peerForCrop, Role role, const style::UserpicButton &st); UserpicButton( QWidget *parent, not_null<Window::Controller*> controller, not_null<PeerData*> peer, Role role, const style::UserpicButton &st); void switchChangePhotoOverlay(bool enabled); void showSavedMessagesOnSelf(bool enabled); QImage takeResultImage() { return std::move(_result); } protected: void paintEvent(QPaintEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void leaveEventHook(QEvent *e) override; void onStateChanged(State was, StateChangeSource source) override; QImage prepareRippleMask() const override; QPoint prepareRippleStartPosition() const override; private: void prepare(); void setImage(QImage &&image); void setupPeerViewers(); void startAnimation(); void processPeerPhoto(); void processNewPeerPhoto(); void startNewPhotoShowing(); void prepareUserpicPixmap(); QPoint countPhotoPosition() const; void startChangeOverlayAnimation(); void updateCursorInChangeOverlay(QPoint localPos); void setCursorInChangeOverlay(bool inOverlay); void updateCursor(); bool showSavedMessages() const; void grabOldUserpic(); void setClickHandlerByRole(); void openPeerPhoto(); void changePhotoLazy(); void uploadNewPeerPhoto(); const style::UserpicButton &_st; Window::Controller *_controller = nullptr; PeerData *_peer = nullptr; PeerId _peerForCrop = 0; Role _role = Role::ChangePhoto; bool _notShownYet = true; bool _waiting = false; QPixmap _userpic, _oldUserpic; bool _userpicHasImage = false; bool _userpicCustom = false; StorageKey _userpicUniqueKey; Animation _a_appearance; QImage _result; bool _showSavedMessagesOnSelf = false; bool _canOpenPhoto = false; bool _cursorInChangeOverlay = false; bool _changeOverlayEnabled = false; Animation _changeOverlayShown; }; class FeedUserpicButton : public AbstractButton { public: FeedUserpicButton( QWidget *parent, not_null<Window::Controller*> controller, not_null<Data::Feed*> feed, const style::FeedUserpicButton &st); private: struct Part { not_null<ChannelData*> channel; base::unique_qptr<UserpicButton> button; }; void prepare(); void checkParts(); bool partsAreValid() const; void refreshParts(); QPoint countInnerPosition() const; const style::FeedUserpicButton &_st; not_null<Window::Controller*> _controller; not_null<Data::Feed*> _feed; std::vector<Part> _parts; }; class SilentToggle : public Ui::IconButton, public Ui::AbstractTooltipShower { public: SilentToggle(QWidget *parent, not_null<ChannelData*> channel); void setChecked(bool checked); bool checked() const { return _checked; } // AbstractTooltipShower interface QString tooltipText() const override; QPoint tooltipPos() const override; protected: void mouseMoveEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void leaveEventHook(QEvent *e) override; private: void refreshIconOverrides(); not_null<ChannelData*> _channel; bool _checked = false; }; } // namespace Ui �����������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/style/������������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0021200�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/style/style_core.cpp����������������������������������������0000664�0000000�0000000�00000007710�13262451251�0024061�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/style/style_core.h" namespace style { namespace internal { namespace { using ModulesList = QList<internal::ModuleBase*>; NeverFreedPointer<ModulesList> styleModules; void startModules() { if (!styleModules) return; for_const (auto module, *styleModules) { module->start(); } } void stopModules() { if (!styleModules) return; for_const (auto module, *styleModules) { module->stop(); } } } // namespace void registerModule(ModuleBase *module) { styleModules.createIfNull(); styleModules->push_back(module); } void unregisterModule(ModuleBase *module) { styleModules->removeOne(module); if (styleModules->isEmpty()) { styleModules.clear(); } } } // namespace internal void startManager() { if (cRetina()) { cSetRealScale(dbisOne); } internal::registerFontFamily(qsl("Open Sans")); internal::startModules(); } void stopManager() { internal::stopModules(); internal::destroyFonts(); internal::destroyIcons(); } void colorizeImage(const QImage &src, QColor c, QImage *outResult, QRect srcRect, QPoint dstPoint) { if (srcRect.isNull()) { srcRect = src.rect(); } else { Assert(src.rect().contains(srcRect)); } auto width = srcRect.width(); auto height = srcRect.height(); Assert(outResult && outResult->rect().contains(QRect(dstPoint, srcRect.size()))); auto pattern = anim::shifted(c); auto resultBytesPerPixel = (src.depth() >> 3); constexpr auto resultIntsPerPixel = 1; auto resultIntsPerLine = (outResult->bytesPerLine() >> 2); auto resultIntsAdded = resultIntsPerLine - width * resultIntsPerPixel; auto resultInts = reinterpret_cast<uint32*>(outResult->bits()) + dstPoint.y() * resultIntsPerLine + dstPoint.x() * resultIntsPerPixel; Assert(resultIntsAdded >= 0); Assert(outResult->depth() == static_cast<int>((resultIntsPerPixel * sizeof(uint32)) << 3)); Assert(outResult->bytesPerLine() == (resultIntsPerLine << 2)); auto maskBytesPerPixel = (src.depth() >> 3); auto maskBytesPerLine = src.bytesPerLine(); auto maskBytesAdded = maskBytesPerLine - width * maskBytesPerPixel; auto maskBytes = src.constBits() + srcRect.y() * maskBytesPerLine + srcRect.x() * maskBytesPerPixel; Assert(maskBytesAdded >= 0); Assert(src.depth() == (maskBytesPerPixel << 3)); for (int y = 0; y != height; ++y) { for (int x = 0; x != width; ++x) { auto maskOpacity = static_cast<anim::ShiftedMultiplier>(*maskBytes) + 1; *resultInts = anim::unshifted(pattern * maskOpacity); maskBytes += maskBytesPerPixel; resultInts += resultIntsPerPixel; } maskBytes += maskBytesAdded; resultInts += resultIntsAdded; } outResult->setDevicePixelRatio(src.devicePixelRatio()); } QBrush transparentPlaceholderBrush() { auto size = st::transparentPlaceholderSize * cIntRetinaFactor(); auto transparent = QImage(2 * size, 2 * size, QImage::Format_ARGB32_Premultiplied); transparent.fill(st::mediaviewTransparentBg->c); { Painter p(&transparent); p.fillRect(rtlrect(0, size, size, size, 2 * size), st::mediaviewTransparentFg); p.fillRect(rtlrect(size, 0, size, size, 2 * size), st::mediaviewTransparentFg); } transparent.setDevicePixelRatio(cRetinaFactor()); return QBrush(transparent); } namespace internal { QImage createCircleMask(int size, QColor bg, QColor fg) { int realSize = size * cIntRetinaFactor(); #ifndef OS_MAC_OLD auto result = QImage(realSize, realSize, QImage::Format::Format_Grayscale8); #else // OS_MAC_OLD auto result = QImage(realSize, realSize, QImage::Format::Format_RGB32); #endif // OS_MAC_OLD { Painter p(&result); PainterHighQualityEnabler hq(p); p.fillRect(0, 0, realSize, realSize, bg); p.setPen(Qt::NoPen); p.setBrush(fg); p.drawEllipse(0, 0, realSize, realSize); } result.setDevicePixelRatio(cRetinaFactor()); return result; } } // namespace internal } // namespace style ��������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/style/style_core.h������������������������������������������0000664�0000000�0000000�00000005636�13262451251�0023533�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/style/style_core_types.h" inline QPoint rtlpoint(int x, int y, int outerw) { return QPoint(rtl() ? (outerw - x) : x, y); } inline QPoint rtlpoint(const QPoint &p, int outerw) { return rtl() ? QPoint(outerw - p.x(), p.y()) : p; } inline QPointF rtlpoint(const QPointF &p, int outerw) { return rtl() ? QPointF(outerw - p.x(), p.y()) : p; } inline QRect rtlrect(int x, int y, int w, int h, int outerw) { return QRect(rtl() ? (outerw - x - w) : x, y, w, h); } inline QRect rtlrect(const QRect &r, int outerw) { return rtl() ? QRect(outerw - r.x() - r.width(), r.y(), r.width(), r.height()) : r; } inline QRectF rtlrect(const QRectF &r, int outerw) { return rtl() ? QRectF(outerw - r.x() - r.width(), r.y(), r.width(), r.height()) : r; } inline QRect centerrect(const QRect &inRect, const QRect &rect) { return QRect(inRect.x() + (inRect.width() - rect.width()) / 2, inRect.y() + (inRect.height() - rect.height()) / 2, rect.width(), rect.height()); } inline QRect centerrect(const QRect &inRect, const style::icon &icon) { return centerrect(inRect, QRect(0, 0, icon.width(), icon.height())); } namespace style { namespace internal { // Objects of derived classes are created in global scope. // They call [un]registerModule() in [de|con]structor. class ModuleBase { public: virtual void start() = 0; virtual void stop() = 0; }; void registerModule(ModuleBase *module); void unregisterModule(ModuleBase *module); // This method is implemented in palette.cpp (codegen). bool setPaletteColor(QLatin1String name, uchar r, uchar g, uchar b, uchar a); } // namespace internal void startManager(); void stopManager(); // *outResult must be r.width() x r.height(), ARGB32_Premultiplied. // QRect(0, 0, src.width(), src.height()) must contain r. void colorizeImage(const QImage &src, QColor c, QImage *outResult, QRect srcRect = QRect(), QPoint dstPoint = QPoint(0, 0)); inline QImage colorizeImage(const QImage &src, QColor c, QRect srcRect = QRect()) { if (srcRect.isNull()) srcRect = src.rect(); auto result = QImage(srcRect.size(), QImage::Format_ARGB32_Premultiplied); colorizeImage(src, c, &result, srcRect); return result; } inline QImage colorizeImage(const QImage &src, const color &c, QRect srcRect = QRect()) { return colorizeImage(src, c->c, srcRect); } QBrush transparentPlaceholderBrush(); namespace internal { QImage createCircleMask(int size, QColor bg, QColor fg); } // namespace internal inline QImage createCircleMask(int size) { return internal::createCircleMask(size, QColor(0, 0, 0), QColor(255, 255, 255)); } inline QImage createInvertedCircleMask(int size) { return internal::createCircleMask(size, QColor(255, 255, 255), QColor(0, 0, 0)); } } // namespace style ��������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/style/style_core_color.cpp����������������������������������0000664�0000000�0000000�00000001504�13262451251�0025252�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/style/style_core_color.h" namespace style { namespace internal { Color::Proxy Color::operator[](const style::palette &paletteOverride) const { auto index = main_palette::indexOfColor(*this); return Proxy((index >= 0) ? paletteOverride.colorAtIndex(index) : (*this)); } ColorData::ColorData(uchar r, uchar g, uchar b, uchar a) : c(int(r), int(g), int(b), int(a)), p(c), b(c) { } void ColorData::set(uchar r, uchar g, uchar b, uchar a) { this->c = QColor(int(r), int(g), int(b), int(a)); this->p = QPen(c); this->b = QBrush(c); } } // namespace internal } // namespace style ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/style/style_core_color.h������������������������������������0000664�0000000�0000000�00000004022�13262451251�0024715�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace style { class palette; namespace internal { class Color; class ColorData { public: QColor c; QPen p; QBrush b; QColor transparent() const { return QColor(c.red(), c.green(), c.blue(), 0); } private: ColorData(uchar r, uchar g, uchar b, uchar a); ColorData(const ColorData &other) = default; ColorData &operator=(const ColorData &other) = default; void set(uchar r, uchar g, uchar b, uchar a); friend class Color; friend class style::palette; }; class Color { public: Color(Qt::Initialization = Qt::Uninitialized) { } Color(const Color &other) = default; Color &operator=(const Color &other) = default; void set(uchar r, uchar g, uchar b, uchar a) const { _data->set(r, g, b, a); } operator const QBrush &() const { return _data->b; } operator const QPen &() const { return _data->p; } ColorData *operator->() const { return _data; } ColorData *v() const { return _data; } explicit operator bool() const { return !!_data; } class Proxy; Proxy operator[](const style::palette &paletteOverride) const; private: friend class style::palette; Color(ColorData *data) : _data(data) { } ColorData *_data = nullptr; }; class Color::Proxy { public: Proxy(Color color) : _color(color) { } Proxy(const Proxy &other) = default; operator const QBrush &() const { return _color; } operator const QPen &() const { return _color; } ColorData *operator->() const { return _color.v(); } ColorData *v() const { return _color.v(); } explicit operator bool() const { return _color ? true : false; } Color clone() const { return _color; } private: Color _color; }; inline bool operator==(Color a, Color b) { return a->c == b->c; } inline bool operator!=(Color a, Color b) { return a->c != b->c; } } // namespace internal } // namespace style ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/style/style_core_font.cpp�����������������������������������0000664�0000000�0000000�00000006257�13262451251�0025114�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/style/style_core_font.h" namespace style { namespace internal { namespace { typedef QMap<QString, int> FontFamilyMap; FontFamilyMap fontFamilyMap; typedef QVector<QString> FontFamilies; FontFamilies fontFamilies; typedef QMap<uint32, FontData*> FontDatas; FontDatas fontsMap; uint32 fontKey(int size, uint32 flags, int family) { return (((uint32(family) << 10) | uint32(size)) << 3) | flags; } } // namespace void destroyFonts() { for (auto fontData : fontsMap) { delete fontData; } fontsMap.clear(); } int registerFontFamily(const QString &family) { auto result = fontFamilyMap.value(family, -1); if (result < 0) { result = fontFamilies.size(); fontFamilyMap.insert(family, result); fontFamilies.push_back(family); } return result; } FontData::FontData(int size, uint32 flags, int family, Font *other) : f(Fonts::GetOverride(fontFamilies[family])) , m(f) , _size(size) , _flags(flags) , _family(family) { if (other) { memcpy(modified, other, sizeof(modified)); } else { memset(modified, 0, sizeof(modified)); } modified[_flags] = Font(this); f.setPixelSize(size); f.setBold(_flags & FontBold); f.setItalic(_flags & FontItalic); f.setUnderline(_flags & FontUnderline); f.setStyleStrategy(QFont::PreferQuality); m = QFontMetrics(f); height = m.height(); ascent = m.ascent(); descent = m.descent(); spacew = width(QLatin1Char(' ')); elidew = width(qsl("...")); } Font FontData::bold(bool set) const { return otherFlagsFont(FontBold, set); } Font FontData::italic(bool set) const { return otherFlagsFont(FontItalic, set); } Font FontData::underline(bool set) const { return otherFlagsFont(FontUnderline, set); } int FontData::size() const { return _size; } uint32 FontData::flags() const { return _flags; } int FontData::family() const { return _family; } Font FontData::otherFlagsFont(uint32 flag, bool set) const { int32 newFlags = set ? (_flags | flag) : (_flags & ~flag); if (!modified[newFlags].v()) { modified[newFlags] = Font(_size, newFlags, _family, modified); } return modified[newFlags]; } Font::Font(int size, uint32 flags, const QString &family) { if (fontFamilyMap.isEmpty()) { for (uint32 i = 0, s = fontFamilies.size(); i != s; ++i) { fontFamilyMap.insert(fontFamilies.at(i), i); } } auto i = fontFamilyMap.constFind(family); if (i == fontFamilyMap.cend()) { fontFamilies.push_back(family); i = fontFamilyMap.insert(family, fontFamilies.size() - 1); } init(size, flags, i.value(), 0); } Font::Font(int size, uint32 flags, int family) { init(size, flags, family, 0); } Font::Font(int size, uint32 flags, int family, Font *modified) { init(size, flags, family, modified); } void Font::init(int size, uint32 flags, int family, Font *modified) { uint32 key = fontKey(size, flags, family); auto i = fontsMap.constFind(key); if (i == fontsMap.cend()) { i = fontsMap.insert(key, new FontData(size, flags, family, modified)); } ptr = i.value(); } } // namespace internal } // namespace style �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/style/style_core_font.h�������������������������������������0000664�0000000�0000000�00000004406�13262451251�0024553�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace style { namespace internal { void destroyFonts(); int registerFontFamily(const QString &family); class FontData; class Font { public: Font(Qt::Initialization = Qt::Uninitialized) : ptr(0) { } Font(int size, uint32 flags, const QString &family); Font(int size, uint32 flags, int family); Font &operator=(const Font &other) { ptr = other.ptr; return (*this); } FontData *operator->() const { return ptr; } FontData *v() const { return ptr; } operator bool() const { return !!ptr; } operator const QFont &() const; private: FontData *ptr; void init(int size, uint32 flags, int family, Font *modified); friend void startManager(); Font(FontData *p) : ptr(p) { } Font(int size, uint32 flags, int family, Font *modified); friend class FontData; }; enum FontFlags { FontBold = 0x01, FontItalic = 0x02, FontUnderline = 0x04, FontDifferentFlags = 0x08, }; class FontData { public: int32 width(const QString &str) const { return m.width(str); } int32 width(const QString &str, int32 from, int32 to) const { return width(str.mid(from, to)); } int32 width(QChar ch) const { return m.width(ch); } QString elided(const QString &str, int32 width, Qt::TextElideMode mode = Qt::ElideRight) const { return m.elidedText(str, mode, width); } Font bold(bool set = true) const; Font italic(bool set = true) const; Font underline(bool set = true) const; int size() const; uint32 flags() const; int family() const; QFont f; QFontMetrics m; int32 height, ascent, descent, spacew, elidew; private: mutable Font modified[FontDifferentFlags]; Font otherFlagsFont(uint32 flag, bool set) const; FontData(int size, uint32 flags, int family, Font *other); friend class Font; int _size; uint32 _flags; int _family; }; inline bool operator==(const Font &a, const Font &b) { return a.v() == b.v(); } inline bool operator!=(const Font &a, const Font &b) { return a.v() != b.v(); } inline Font::operator const QFont &() const { return ptr->f; } } // namespace internal } // namespace style ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/style/style_core_icon.cpp�����������������������������������0000664�0000000�0000000�00000022733�13262451251�0025073�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/style/style_core_icon.h" namespace style { namespace internal { namespace { uint32 colorKey(QColor c) { return (((((uint32(c.red()) << 8) | uint32(c.green())) << 8) | uint32(c.blue())) << 8) | uint32(c.alpha()); } using IconMasks = QMap<const IconMask*, QImage>; using IconPixmaps = QMap<QPair<const IconMask*, uint32>, QPixmap>; using IconDatas = OrderedSet<IconData*>; NeverFreedPointer<IconMasks> iconMasks; NeverFreedPointer<IconPixmaps> iconPixmaps; NeverFreedPointer<IconDatas> iconData; inline int pxAdjust(int value, int scale) { if (value < 0) { return -pxAdjust(-value, scale); } return qFloor((value * scale / 4.) + 0.1); } QImage createIconMask(const IconMask *mask, DBIScale scale) { auto maskImage = QImage::fromData(mask->data(), mask->size(), "PNG"); maskImage.setDevicePixelRatio(cRetinaFactor()); Assert(!maskImage.isNull()); // images are layouted like this: // 200x 100x // 150x 125x int width = maskImage.width() / 3; int height = qRound((maskImage.height() * 2) / 7.); auto r = QRect(0, 0, width * 2, height * 2); if (!cRetina() && scale != dbisTwo) { if (scale == dbisOne) { r = QRect(width * 2, 0, width, height); } else { int width125 = pxAdjust(width, 5); int height125 = pxAdjust(height, 5); int width150 = pxAdjust(width, 6); int height150 = pxAdjust(height, 6); if (scale == dbisOneAndQuarter) { r = QRect(width150, height * 2, width125, height125); } else { r = QRect(0, height * 2, width150, height150); } } } return maskImage.copy(r); } QSize readGeneratedSize(const IconMask *mask, DBIScale scale) { auto data = mask->data(); auto size = mask->size(); auto generateTag = qstr("GENERATE:"); if (size > generateTag.size() && !memcmp(data, generateTag.data(), generateTag.size())) { size -= generateTag.size(); data += generateTag.size(); auto sizeTag = qstr("SIZE:"); if (size > sizeTag.size() && !memcmp(data, sizeTag.data(), sizeTag.size())) { size -= sizeTag.size(); data += sizeTag.size(); auto baForStream = QByteArray::fromRawData(reinterpret_cast<const char*>(data), size); QDataStream stream(baForStream); stream.setVersion(QDataStream::Qt_5_1); qint32 width = 0, height = 0; stream >> width >> height; Assert(stream.status() == QDataStream::Ok); switch (scale) { case dbisOne: return QSize(width, height); case dbisOneAndQuarter: return QSize(pxAdjust(width, 5), pxAdjust(height, 5)); case dbisOneAndHalf: return QSize(pxAdjust(width, 6), pxAdjust(height, 6)); case dbisTwo: return QSize(width * 2, height * 2); } } else { Unexpected("Bad data in generated icon!"); } } return QSize(); } } // namespace MonoIcon::MonoIcon(const IconMask *mask, Color color, QPoint offset) : _mask(mask) , _color(std::move(color)) , _offset(offset) { } void MonoIcon::reset() const { _pixmap = QPixmap(); _size = QSize(); } int MonoIcon::width() const { ensureLoaded(); return _size.width(); } int MonoIcon::height() const { ensureLoaded(); return _size.height(); } QSize MonoIcon::size() const { ensureLoaded(); return _size; } QPoint MonoIcon::offset() const { return _offset; } void MonoIcon::paint(QPainter &p, const QPoint &pos, int outerw) const { int w = width(), h = height(); QPoint fullOffset = pos + offset(); int partPosX = rtl() ? (outerw - fullOffset.x() - w) : fullOffset.x(); int partPosY = fullOffset.y(); ensureLoaded(); if (_pixmap.isNull()) { p.fillRect(partPosX, partPosY, w, h, _color); } else { p.drawPixmap(partPosX, partPosY, _pixmap); } } void MonoIcon::fill(QPainter &p, const QRect &rect) const { ensureLoaded(); if (_pixmap.isNull()) { p.fillRect(rect, _color); } else { p.drawPixmap(rect, _pixmap, QRect(0, 0, _pixmap.width(), _pixmap.height())); } } void MonoIcon::paint(QPainter &p, const QPoint &pos, int outerw, QColor colorOverride) const { int w = width(), h = height(); QPoint fullOffset = pos + offset(); int partPosX = rtl() ? (outerw - fullOffset.x() - w) : fullOffset.x(); int partPosY = fullOffset.y(); ensureLoaded(); if (_pixmap.isNull()) { p.fillRect(partPosX, partPosY, w, h, colorOverride); } else { ensureColorizedImage(colorOverride); p.drawImage(partPosX, partPosY, _colorizedImage); } } void MonoIcon::fill(QPainter &p, const QRect &rect, QColor colorOverride) const { ensureLoaded(); if (_pixmap.isNull()) { p.fillRect(rect, colorOverride); } else { ensureColorizedImage(colorOverride); p.drawImage(rect, _colorizedImage, _colorizedImage.rect()); } } void MonoIcon::paint( QPainter &p, const QPoint &pos, int outerw, const style::palette &paletteOverride) const { auto size = readGeneratedSize(_mask, cScale()); auto maskImage = QImage(); if (size.isEmpty()) { maskImage = createIconMask(_mask, cScale()); size = maskImage.size() / cIntRetinaFactor(); } const auto w = size.width(); const auto h = size.height(); const auto fullOffset = pos + offset(); const auto partPosX = rtl() ? (outerw - fullOffset.x() - w) : fullOffset.x(); const auto partPosY = fullOffset.y(); if (!maskImage.isNull()) { auto colorizedImage = QImage( maskImage.size(), QImage::Format_ARGB32_Premultiplied); colorizeImage(maskImage, _color[paletteOverride]->c, &colorizedImage); p.drawImage(partPosX, partPosY, colorizedImage); } else { p.fillRect(partPosX, partPosY, w, h, _color[paletteOverride]); } } void MonoIcon::fill( QPainter &p, const QRect &rect, const style::palette &paletteOverride) const { auto size = readGeneratedSize(_mask, cScale()); auto maskImage = QImage(); if (size.isEmpty()) { maskImage = createIconMask(_mask, cScale()); size = maskImage.size() / cIntRetinaFactor(); } if (!maskImage.isNull()) { auto colorizedImage = QImage( maskImage.size(), QImage::Format_ARGB32_Premultiplied); colorizeImage(maskImage, _color[paletteOverride]->c, &colorizedImage); p.drawImage(rect, colorizedImage, colorizedImage.rect()); } else { p.fillRect(rect, _color[paletteOverride]); } } QImage MonoIcon::instance(QColor colorOverride, DBIScale scale) const { if (scale == dbisAuto) { ensureLoaded(); auto result = QImage(size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); result.setDevicePixelRatio(cRetinaFactor()); if (_pixmap.isNull()) { result.fill(colorOverride); } else { colorizeImage(_maskImage, colorOverride, &result); } return result; } auto size = readGeneratedSize(_mask, scale); if (!size.isEmpty()) { auto result = QImage(size * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); result.setDevicePixelRatio(cRetinaFactor()); result.fill(colorOverride); return result; } auto mask = createIconMask(_mask, scale); auto result = QImage(mask.size(), QImage::Format_ARGB32_Premultiplied); result.setDevicePixelRatio(cRetinaFactor()); colorizeImage(mask, colorOverride, &result); return result; } void MonoIcon::ensureLoaded() const { if (_size.isValid()) { return; } if (!_maskImage.isNull()) { createCachedPixmap(); return; } _size = readGeneratedSize(_mask, cScale()); if (_size.isEmpty()) { iconMasks.createIfNull(); auto i = iconMasks->constFind(_mask); if (i == iconMasks->cend()) { i = iconMasks->insert(_mask, createIconMask(_mask, cScale())); } _maskImage = i.value(); createCachedPixmap(); } } void MonoIcon::ensureColorizedImage(QColor color) const { if (_colorizedImage.isNull()) _colorizedImage = QImage(_maskImage.size(), QImage::Format_ARGB32_Premultiplied); colorizeImage(_maskImage, color, &_colorizedImage); } void MonoIcon::createCachedPixmap() const { iconPixmaps.createIfNull(); auto key = qMakePair(_mask, colorKey(_color->c)); auto j = iconPixmaps->constFind(key); if (j == iconPixmaps->cend()) { auto image = colorizeImage(_maskImage, _color); j = iconPixmaps->insert(key, App::pixmapFromImageInPlace(std::move(image))); } _pixmap = j.value(); _size = _pixmap.size() / cIntRetinaFactor(); } void IconData::created() { iconData.createIfNull(); iconData->insert(this); } void IconData::fill(QPainter &p, const QRect &rect) const { if (_parts.empty()) return; auto partSize = _parts[0].size(); for_const (auto &part, _parts) { Assert(part.offset() == QPoint(0, 0)); Assert(part.size() == partSize); part.fill(p, rect); } } void IconData::fill(QPainter &p, const QRect &rect, QColor colorOverride) const { if (_parts.empty()) return; auto partSize = _parts[0].size(); for_const (auto &part, _parts) { Assert(part.offset() == QPoint(0, 0)); Assert(part.size() == partSize); part.fill(p, rect, colorOverride); } } QImage IconData::instance(QColor colorOverride, DBIScale scale) const { Assert(_parts.size() == 1); auto &part = _parts[0]; Assert(part.offset() == QPoint(0, 0)); return part.instance(colorOverride, scale); } int IconData::width() const { if (_width < 0) { _width = 0; for_const (auto &part, _parts) { accumulate_max(_width, part.offset().x() + part.width()); } } return _width; } int IconData::height() const { if (_height < 0) { _height = 0; for_const (auto &part, _parts) { accumulate_max(_height, part.offset().x() + part.height()); } } return _height; } void resetIcons() { iconPixmaps.clear(); if (iconData) { for (auto data : *iconData) { data->reset(); } } } void destroyIcons() { iconData.clear(); iconPixmaps.clear(); iconMasks.clear(); } } // namespace internal } // namespace style �������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/style/style_core_icon.h�������������������������������������0000664�0000000�0000000�00000016667�13262451251�0024551�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/style/style_core_color.h" #include <vector> namespace style { namespace internal { class IconMask { public: template <int N> IconMask(const uchar (&data)[N]) : _data(data), _size(N) { static_assert(N > 0, "invalid image data"); } const uchar *data() const { return _data; } int size() const { return _size; } private: const uchar *_data; const int _size; }; class MonoIcon { public: MonoIcon() = default; MonoIcon(const MonoIcon &other) = delete; MonoIcon &operator=(const MonoIcon &other) = delete; MonoIcon(MonoIcon &&other) = default; MonoIcon &operator=(MonoIcon &&other) = default; MonoIcon(const IconMask *mask, Color color, QPoint offset); void reset() const; int width() const; int height() const; QSize size() const; QPoint offset() const; void paint(QPainter &p, const QPoint &pos, int outerw) const; void fill(QPainter &p, const QRect &rect) const; void paint(QPainter &p, const QPoint &pos, int outerw, QColor colorOverride) const; void fill(QPainter &p, const QRect &rect, QColor colorOverride) const; void paint(QPainter &p, const QPoint &pos, int outerw, const style::palette &paletteOverride) const; void fill(QPainter &p, const QRect &rect, const style::palette &paletteOverride) const; QImage instance(QColor colorOverride, DBIScale scale) const; ~MonoIcon() { } private: void ensureLoaded() const; void createCachedPixmap() const; void ensureColorizedImage(QColor color) const; const IconMask *_mask = nullptr; Color _color; QPoint _offset = { 0, 0 }; mutable QImage _maskImage, _colorizedImage; mutable QPixmap _pixmap; // for pixmaps mutable QSize _size; // for rects }; class IconData { public: template <typename ...MonoIcons> IconData(MonoIcons &&...icons) { created(); _parts.reserve(sizeof...(MonoIcons)); addIcons(std::forward<MonoIcons>(icons)...); } void reset() { for_const (auto &part, _parts) { part.reset(); } } bool empty() const { return _parts.empty(); } void paint(QPainter &p, const QPoint &pos, int outerw) const { for_const (auto &part, _parts) { part.paint(p, pos, outerw); } } void fill(QPainter &p, const QRect &rect) const; void paint(QPainter &p, const QPoint &pos, int outerw, QColor colorOverride) const { for_const (auto &part, _parts) { part.paint(p, pos, outerw, colorOverride); } } void fill(QPainter &p, const QRect &rect, QColor colorOverride) const; void paint(QPainter &p, const QPoint &pos, int outerw, const style::palette &paletteOverride) const { for_const (auto &part, _parts) { part.paint(p, pos, outerw, paletteOverride); } } void fill(QPainter &p, const QRect &rect, const style::palette &paletteOverride) const; QImage instance(QColor colorOverride, DBIScale scale) const; int width() const; int height() const; private: void created(); template <typename ... MonoIcons> void addIcons() { } template <typename ... MonoIcons> void addIcons(MonoIcon &&icon, MonoIcons&&... icons) { _parts.push_back(std::move(icon)); addIcons(std::forward<MonoIcons>(icons)...); } std::vector<MonoIcon> _parts; mutable int _width = -1; mutable int _height = -1; }; class Icon { public: Icon(Qt::Initialization) { } template <typename ... MonoIcons> Icon(MonoIcons&&... icons) : _data(new IconData(std::forward<MonoIcons>(icons)...)), _owner(true) { } Icon(const Icon &other) : _data(other._data) { } Icon(Icon &&other) : _data(base::take(other._data)), _owner(base::take(_owner)) { } Icon &operator=(const Icon &other) { Assert(!_owner); _data = other._data; _owner = false; return *this; } Icon &operator=(Icon &&other) { Assert(!_owner); _data = base::take(other._data); _owner = base::take(other._owner); return *this; } bool empty() const { return _data->empty(); } int width() const { return _data->width(); } int height() const { return _data->height(); } QSize size() const { return QSize(width(), height()); } void paint(QPainter &p, const QPoint &pos, int outerw) const { return _data->paint(p, pos, outerw); } void paint(QPainter &p, int x, int y, int outerw) const { return _data->paint(p, QPoint(x, y), outerw); } void paintInCenter(QPainter &p, const QRect &outer) const { return _data->paint(p, QPoint(outer.x() + (outer.width() - width()) / 2, outer.y() + (outer.height() - height()) / 2), outer.x() * 2 + outer.width()); } void fill(QPainter &p, const QRect &rect) const { return _data->fill(p, rect); } void paint(QPainter &p, const QPoint &pos, int outerw, QColor colorOverride) const { return _data->paint(p, pos, outerw, colorOverride); } void paint(QPainter &p, int x, int y, int outerw, QColor colorOverride) const { return _data->paint(p, QPoint(x, y), outerw, colorOverride); } void paintInCenter(QPainter &p, const QRect &outer, QColor colorOverride) const { return _data->paint(p, QPoint(outer.x() + (outer.width() - width()) / 2, outer.y() + (outer.height() - height()) / 2), outer.x() * 2 + outer.width(), colorOverride); } void fill(QPainter &p, const QRect &rect, QColor colorOverride) const { return _data->fill(p, rect, colorOverride); } QImage instance(QColor colorOverride, DBIScale scale = dbisAuto) const { return _data->instance(colorOverride, scale); } class Proxy { public: Proxy(const Icon &icon, const style::palette &palette) : _icon(icon), _palette(palette) { } Proxy(const Proxy &other) = default; bool empty() const { return _icon.empty(); } int width() const { return _icon.width(); } int height() const { return _icon.height(); } QSize size() const { return _icon.size(); } void paint(QPainter &p, const QPoint &pos, int outerw) const { return _icon.paintWithPalette(p, pos, outerw, _palette); } void paint(QPainter &p, int x, int y, int outerw) const { return _icon.paintWithPalette(p, x, y, outerw, _palette); } void paintInCenter(QPainter &p, const QRect &outer) const { return _icon.paintInCenterWithPalette(p, outer, _palette); } void fill(QPainter &p, const QRect &rect) const { return _icon.fillWithPalette(p, rect, _palette); } private: const Icon &_icon; const style::palette &_palette; }; Proxy operator[](const style::palette &paletteOverride) const { return Proxy(*this, paletteOverride); } ~Icon() { if (auto data = base::take(_data)) { if (_owner) { delete data; } } } private: friend class Proxy; void paintWithPalette(QPainter &p, const QPoint &pos, int outerw, const style::palette &paletteOverride) const { return _data->paint(p, pos, outerw, paletteOverride); } void paintWithPalette(QPainter &p, int x, int y, int outerw, const style::palette &paletteOverride) const { return _data->paint(p, QPoint(x, y), outerw, paletteOverride); } void paintInCenterWithPalette(QPainter &p, const QRect &outer, const style::palette &paletteOverride) const { return _data->paint(p, QPoint(outer.x() + (outer.width() - width()) / 2, outer.y() + (outer.height() - height()) / 2), outer.x() * 2 + outer.width(), paletteOverride); } void fillWithPalette(QPainter &p, const QRect &rect, const style::palette &paletteOverride) const { return _data->fill(p, rect, paletteOverride); } IconData *_data = nullptr; bool _owner = false; }; void resetIcons(); void destroyIcons(); } // namespace internal } // namespace style �������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/style/style_core_types.cpp����������������������������������0000664�0000000�0000000�00000000477�13262451251�0025310�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/style/style_core_types.h" namespace style { } // namespace style �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/style/style_core_types.h������������������������������������0000664�0000000�0000000�00000003630�13262451251�0024747�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <QtCore/QString> #include <QtGui/QColor> #include <QtCore/QPoint> #include <QtCore/QRect> #include <QtGui/QCursor> #include <QtGui/QFont> #include "ui/style/style_core_font.h" #include "ui/style/style_core_color.h" #include "ui/style/style_core_icon.h" namespace style { using string = QString; using rect = QRect; using point = QPoint; using size = QSize; using cursor = Qt::CursorShape; using align = Qt::Alignment; using margins = QMargins; using font = internal::Font; using color = internal::Color; using icon = internal::Icon; static constexpr cursor cur_default = Qt::ArrowCursor; static constexpr cursor cur_pointer = Qt::PointingHandCursor; static constexpr cursor cur_text = Qt::IBeamCursor; static constexpr cursor cur_cross = Qt::CrossCursor; static constexpr cursor cur_sizever = Qt::SizeVerCursor; static constexpr cursor cur_sizehor = Qt::SizeHorCursor; static constexpr cursor cur_sizebdiag = Qt::SizeBDiagCursor; static constexpr cursor cur_sizefdiag = Qt::SizeFDiagCursor; static constexpr cursor cur_sizeall = Qt::SizeAllCursor; static const align al_topleft = (Qt::AlignTop | Qt::AlignLeft); static const align al_top = (Qt::AlignTop | Qt::AlignHCenter); static const align al_topright = (Qt::AlignTop | Qt::AlignRight); static const align al_right = (Qt::AlignVCenter | Qt::AlignRight); static const align al_bottomright = (Qt::AlignBottom | Qt::AlignRight); static const align al_bottom = (Qt::AlignBottom | Qt::AlignHCenter); static const align al_bottomleft = (Qt::AlignBottom | Qt::AlignLeft); static const align al_left = (Qt::AlignVCenter | Qt::AlignLeft); static const align al_center = (Qt::AlignVCenter | Qt::AlignHCenter); } // namespace style ��������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/text/�������������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0021024�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/text/text.cpp�����������������������������������������������0000664�0000000�0000000�00000275033�13262451251�0022526�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/text/text.h" #include <private/qharfbuzz_p.h> #include "core/click_handler_types.h" #include "core/crash_reports.h" #include "ui/text/text_block.h" #include "lang/lang_keys.h" #include "platform/platform_specific.h" #include "boxes/confirm_box.h" #include "mainwindow.h" namespace { inline int32 countBlockHeight(const ITextBlock *b, const style::TextStyle *st) { return (b->type() == TextBlockTSkip) ? static_cast<const SkipBlock*>(b)->height() : (st->lineHeight > st->font->height) ? st->lineHeight : st->font->height; } } // namespace bool chIsBad(QChar ch) { #ifdef OS_MAC_OLD if (cIsSnowLeopard() && (ch == 8207 || ch == 8206 || ch == 8288)) { return true; } #endif // OS_MAC_OLD return (ch == 0) || (ch >= 8232 && ch < 8237) || (ch >= 65024 && ch < 65040 && ch != 65039) || (ch >= 127 && ch < 160 && ch != 156) // qt harfbuzz crash see https://github.com/telegramdesktop/tdesktop/issues/4551 || (cPlatform() == dbipMac && ch == 6158) // tmp hack see https://bugreports.qt.io/browse/QTBUG-48910 || (cPlatform() == dbipMac && ch >= 0x0B00 && ch <= 0x0B7F && chIsDiac(ch) && cIsElCapitan()); } QString textcmdSkipBlock(ushort w, ushort h) { static QString cmd(5, TextCommand); cmd[1] = QChar(TextCommandSkipBlock); cmd[2] = QChar(w); cmd[3] = QChar(h); return cmd; } QString textcmdStartLink(ushort lnkIndex) { static QString cmd(4, TextCommand); cmd[1] = QChar(TextCommandLinkIndex); cmd[2] = QChar(lnkIndex); return cmd; } QString textcmdStartLink(const QString &url) { if (url.size() >= 4096) return QString(); QString result; result.reserve(url.size() + 4); return result.append(TextCommand).append(QChar(TextCommandLinkText)).append(QChar(url.size())).append(url).append(TextCommand); } QString textcmdStopLink() { return textcmdStartLink(0); } QString textcmdLink(ushort lnkIndex, const QString &text) { QString result; result.reserve(4 + text.size() + 4); return result.append(textcmdStartLink(lnkIndex)).append(text).append(textcmdStopLink()); } QString textcmdLink(const QString &url, const QString &text) { QString result; result.reserve(4 + url.size() + text.size() + 4); return result.append(textcmdStartLink(url)).append(text).append(textcmdStopLink()); } QString textcmdStartSemibold() { QString result; result.reserve(3); return result.append(TextCommand).append(QChar(TextCommandSemibold)).append(TextCommand); } QString textcmdStopSemibold() { QString result; result.reserve(3); return result.append(TextCommand).append(QChar(TextCommandNoSemibold)).append(TextCommand); } const QChar *textSkipCommand(const QChar *from, const QChar *end, bool canLink) { const QChar *result = from + 1; if (*from != TextCommand || result >= end) return from; ushort cmd = result->unicode(); ++result; if (result >= end) return from; switch (cmd) { case TextCommandBold: case TextCommandNoBold: case TextCommandSemibold: case TextCommandNoSemibold: case TextCommandItalic: case TextCommandNoItalic: case TextCommandUnderline: case TextCommandNoUnderline: break; case TextCommandLinkIndex: if (result->unicode() > 0x7FFF) return from; ++result; break; case TextCommandLinkText: { ushort len = result->unicode(); if (len >= 4096 || !canLink) return from; result += len + 1; } break; case TextCommandSkipBlock: result += 2; break; case TextCommandLangTag: result += 1; break; } return (result < end && *result == TextCommand) ? (result + 1) : from; } class TextParser { public: static Qt::LayoutDirection stringDirection(const QString &str, int32 from, int32 to) { const ushort *p = reinterpret_cast<const ushort*>(str.unicode()) + from; const ushort *end = p + (to - from); while (p < end) { uint ucs4 = *p; if (QChar::isHighSurrogate(ucs4) && p < end - 1) { ushort low = p[1]; if (QChar::isLowSurrogate(low)) { ucs4 = QChar::surrogateToUcs4(ucs4, low); ++p; } } switch (QChar::direction(ucs4)) { case QChar::DirL: return Qt::LeftToRight; case QChar::DirR: case QChar::DirAL: return Qt::RightToLeft; default: break; } ++p; } return Qt::LayoutDirectionAuto; } void blockCreated() { sumWidth += _t->_blocks.back()->f_width(); if (sumWidth.floor().toInt() > stopAfterWidth) { sumFinished = true; } } void createBlock(int32 skipBack = 0) { if (lnkIndex < 0x8000 && lnkIndex > maxLnkIndex) maxLnkIndex = lnkIndex; int32 len = int32(_t->_text.size()) + skipBack - blockStart; if (len > 0) { bool newline = !emoji && (len == 1 && _t->_text.at(blockStart) == QChar::LineFeed); if (newlineAwaited) { newlineAwaited = false; if (!newline) { _t->_text.insert(blockStart, QChar::LineFeed); createBlock(skipBack - len); } } lastSkipped = false; if (emoji) { _t->_blocks.push_back(std::make_unique<EmojiBlock>(_t->_st->font, _t->_text, blockStart, len, flags, lnkIndex, emoji)); emoji = 0; lastSkipped = true; } else if (newline) { _t->_blocks.push_back(std::make_unique<NewlineBlock>(_t->_st->font, _t->_text, blockStart, len, flags, lnkIndex)); } else { _t->_blocks.push_back(std::make_unique<TextBlock>(_t->_st->font, _t->_text, _t->_minResizeWidth, blockStart, len, flags, lnkIndex)); } blockStart += len; blockCreated(); } } void createSkipBlock(int32 w, int32 h) { createBlock(); _t->_text.push_back('_'); _t->_blocks.push_back(std::make_unique<SkipBlock>(_t->_st->font, _t->_text, blockStart++, w, h, lnkIndex)); blockCreated(); } void createNewlineBlock() { createBlock(); _t->_text.push_back(QChar::LineFeed); createBlock(); } bool checkCommand() { bool result = false; for (QChar c = ((ptr < end) ? *ptr : 0); c == TextCommand; c = ((ptr < end) ? *ptr : 0)) { if (!readCommand()) { break; } result = true; } return result; } // Returns true if at least one entity was parsed in the current position. bool checkEntities() { while (!removeFlags.isEmpty() && (ptr >= removeFlags.firstKey() || ptr >= end)) { const QList<int32> &removing(removeFlags.first()); for (int32 i = removing.size(); i > 0;) { int32 flag = removing.at(--i); if (flags & flag) { createBlock(); flags &= ~flag; if (flag == TextBlockFPre) { newlineAwaited = true; } } } removeFlags.erase(removeFlags.begin()); } while (waitingEntity != entitiesEnd && start + waitingEntity->offset() + waitingEntity->length() <= ptr) { ++waitingEntity; } if (waitingEntity == entitiesEnd || ptr < start + waitingEntity->offset()) { return false; } int32 startFlags = 0; QString linkData, linkText; auto type = waitingEntity->type(), linkType = EntityInTextInvalid; LinkDisplayStatus linkDisplayStatus = LinkDisplayedFull; if (type == EntityInTextBold) { startFlags = TextBlockFSemibold; } else if (type == EntityInTextItalic) { startFlags = TextBlockFItalic; } else if (type == EntityInTextCode) { startFlags = TextBlockFCode; } else if (type == EntityInTextPre) { startFlags = TextBlockFPre; createBlock(); if (!_t->_blocks.empty() && _t->_blocks.back()->type() != TextBlockTNewline) { createNewlineBlock(); } } else if (type == EntityInTextUrl || type == EntityInTextEmail || type == EntityInTextMention || type == EntityInTextHashtag || type == EntityInTextCashtag || type == EntityInTextBotCommand) { linkType = type; linkData = QString(start + waitingEntity->offset(), waitingEntity->length()); if (linkType == EntityInTextUrl) { computeLinkText(linkData, &linkText, &linkDisplayStatus); } else { linkText = linkData; } } else if (type == EntityInTextCustomUrl || type == EntityInTextMentionName) { linkType = type; linkData = waitingEntity->data(); linkText = QString(start + waitingEntity->offset(), waitingEntity->length()); } if (linkType != EntityInTextInvalid) { createBlock(); links.push_back(TextLinkData(linkType, linkText, linkData, linkDisplayStatus)); lnkIndex = 0x8000 + links.size(); for (auto entityEnd = start + waitingEntity->offset() + waitingEntity->length(); ptr < entityEnd; ++ptr) { parseCurrentChar(); parseEmojiFromCurrent(); if (sumFinished || _t->_text.size() >= 0x8000) break; // 32k max } createBlock(); lnkIndex = 0; } else if (startFlags) { if (!(flags & startFlags)) { createBlock(); flags |= startFlags; removeFlags[start + waitingEntity->offset() + waitingEntity->length()].push_front(startFlags); } } ++waitingEntity; if (links.size() >= 0x7FFF) { while (waitingEntity != entitiesEnd && (isLinkEntity(*waitingEntity) || isInvalidEntity(*waitingEntity))) { ++waitingEntity; } } else { while (waitingEntity != entitiesEnd && isInvalidEntity(*waitingEntity)) { ++waitingEntity; } } return true; } bool readSkipBlockCommand() { const QChar *afterCmd = textSkipCommand(ptr, end, links.size() < 0x7FFF); if (afterCmd == ptr) { return false; } ushort cmd = (++ptr)->unicode(); ++ptr; switch (cmd) { case TextCommandSkipBlock: createSkipBlock(ptr->unicode(), (ptr + 1)->unicode()); break; } ptr = afterCmd; return true; } bool readCommand() { const QChar *afterCmd = textSkipCommand(ptr, end, links.size() < 0x7FFF); if (afterCmd == ptr) { return false; } ushort cmd = (++ptr)->unicode(); ++ptr; switch (cmd) { case TextCommandBold: if (!(flags & TextBlockFBold)) { createBlock(); flags |= TextBlockFBold; } break; case TextCommandNoBold: if (flags & TextBlockFBold) { createBlock(); flags &= ~TextBlockFBold; } break; case TextCommandSemibold: if (!(flags & TextBlockFSemibold)) { createBlock(); flags |= TextBlockFSemibold; } break; case TextCommandNoSemibold: if (flags & TextBlockFSemibold) { createBlock(); flags &= ~TextBlockFSemibold; } break; case TextCommandItalic: if (!(flags & TextBlockFItalic)) { createBlock(); flags |= TextBlockFItalic; } break; case TextCommandNoItalic: if (flags & TextBlockFItalic) { createBlock(); flags &= ~TextBlockFItalic; } break; case TextCommandUnderline: if (!(flags & TextBlockFUnderline)) { createBlock(); flags |= TextBlockFUnderline; } break; case TextCommandNoUnderline: if (flags & TextBlockFUnderline) { createBlock(); flags &= ~TextBlockFUnderline; } break; case TextCommandLinkIndex: if (ptr->unicode() != lnkIndex) { createBlock(); lnkIndex = ptr->unicode(); } break; case TextCommandLinkText: { createBlock(); int32 len = ptr->unicode(); links.push_back(TextLinkData(EntityInTextCustomUrl, QString(), QString(++ptr, len), LinkDisplayedFull)); lnkIndex = 0x8000 + links.size(); } break; case TextCommandSkipBlock: createSkipBlock(ptr->unicode(), (ptr + 1)->unicode()); break; } ptr = afterCmd; return true; } void parseCurrentChar() { int skipBack = 0; ch = ((ptr < end) ? *ptr : 0); emojiLookback = 0; bool skip = false, isNewLine = multiline && chIsNewline(ch), isSpace = chIsSpace(ch), isDiac = chIsDiac(ch), isTilde = checkTilde && (ch == '~'); if (chIsBad(ch) || ch.isLowSurrogate()) { skip = true; } else if (ch == 0xFE0F && (cPlatform() == dbipMac || cPlatform() == dbipMacOld)) { // Some sequences like 0x0E53 0xFE0F crash OS X harfbuzz text processing :( skip = true; } else if (isDiac) { if (lastSkipped || emoji || ++diacs > chMaxDiacAfterSymbol()) { skip = true; } } else if (ch.isHighSurrogate()) { if (ptr + 1 >= end || !(ptr + 1)->isLowSurrogate()) { skip = true; } else { _t->_text.push_back(ch); skipBack = -1; ++ptr; ch = *ptr; emojiLookback = 1; } } lastSkipped = skip; if (skip) { ch = 0; } else { if (isTilde) { // tilde fix in OpenSans if (!(flags & TextBlockFTilde)) { createBlock(skipBack); flags |= TextBlockFTilde; } } else { if (flags & TextBlockFTilde) { createBlock(skipBack); flags &= ~TextBlockFTilde; } } if (isNewLine) { createNewlineBlock(); } else if (isSpace) { _t->_text.push_back(QChar::Space); } else { if (emoji) createBlock(skipBack); _t->_text.push_back(ch); } if (!isDiac) diacs = 0; } } void parseEmojiFromCurrent() { int len = 0; auto e = Ui::Emoji::Find(ptr - emojiLookback, end, &len); if (!e) return; for (int l = len - emojiLookback - 1; l > 0; --l) { _t->_text.push_back(*++ptr); } if (e->hasPostfix()) { Assert(!_t->_text.isEmpty()); const auto last = _t->_text[_t->_text.size() - 1]; if (last.unicode() != Ui::Emoji::kPostfix) { _t->_text.push_back(QChar(Ui::Emoji::kPostfix)); ++len; } } createBlock(-len); emoji = e; } TextParser(Text *t, const QString &text, const TextParseOptions &options) : _t(t), source { text }, rich(options.flags & TextParseRichText), multiline(options.flags & TextParseMultiline), stopAfterWidth(QFIXED_MAX) { if (options.flags & TextParseLinks) { TextUtilities::ParseEntities(source, options.flags, rich); } parse(options); } TextParser(Text *t, const TextWithEntities &textWithEntities, const TextParseOptions &options) : _t(t), source(textWithEntities), rich(options.flags & TextParseRichText), multiline(options.flags & TextParseMultiline), stopAfterWidth(QFIXED_MAX) { auto &preparsed = textWithEntities.entities; if ((options.flags & TextParseLinks) && !preparsed.isEmpty()) { bool parseMentions = (options.flags & TextParseMentions); bool parseHashtags = (options.flags & TextParseHashtags); bool parseBotCommands = (options.flags & TextParseBotCommands); bool parseMarkdown = (options.flags & TextParseMarkdown); if (!parseMentions || !parseHashtags || !parseBotCommands || !parseMarkdown) { int32 i = 0, l = preparsed.size(); source.entities.clear(); source.entities.reserve(l); const QChar s = source.text.size(); for (; i < l; ++i) { auto type = preparsed.at(i).type(); if (((type == EntityInTextMention || type == EntityInTextMentionName) && !parseMentions) || (type == EntityInTextHashtag && !parseHashtags) || (type == EntityInTextCashtag && !parseHashtags) || (type == EntityInTextBotCommand && !parseBotCommands) || ((type == EntityInTextBold || type == EntityInTextItalic || type == EntityInTextCode || type == EntityInTextPre) && !parseMarkdown)) { continue; } source.entities.push_back(preparsed.at(i)); } } } parse(options); } bool isInvalidEntity(const EntityInText &entity) const { const auto length = entity.length(); return (start + entity.offset() + length > end) || (length <= 0); } bool isLinkEntity(const EntityInText &entity) const { const auto type = entity.type(); const auto urls = { EntityInTextUrl, EntityInTextCustomUrl, EntityInTextEmail, EntityInTextHashtag, EntityInTextCashtag, EntityInTextMention, EntityInTextMentionName, EntityInTextBotCommand }; return ranges::find(urls, type) != std::end(urls); } void parse(const TextParseOptions &options) { if (options.maxw > 0 && options.maxh > 0) { stopAfterWidth = ((options.maxh / _t->_st->font->height) + 1) * options.maxw; } start = source.text.constData(); end = start + source.text.size(); entitiesEnd = source.entities.cend(); waitingEntity = source.entities.cbegin(); while (waitingEntity != entitiesEnd && isInvalidEntity(*waitingEntity)) { ++waitingEntity; } int firstMonospaceOffset = EntityInText::firstMonospaceOffset(source.entities, end - start); ptr = start; while (ptr != end && chIsTrimmed(*ptr, rich) && ptr != start + firstMonospaceOffset) { ++ptr; } while (ptr != end && chIsTrimmed(*(end - 1), rich)) { --end; } _t->_text.resize(0); _t->_text.reserve(end - ptr); diacs = 0; sumWidth = 0; sumFinished = newlineAwaited = false; blockStart = 0; emoji = 0; ch = emojiLookback = 0; lastSkipped = false; checkTilde = !cRetina() && (_t->_st->font->size() == 13) && (_t->_st->font->flags() == 0) && (_t->_st->font->f.family() == qstr("Open Sans")); // tilde Open Sans fix for (; ptr <= end; ++ptr) { while (checkEntities() || (rich && checkCommand())) { } parseCurrentChar(); parseEmojiFromCurrent(); if (sumFinished || _t->_text.size() >= 0x8000) break; // 32k max } createBlock(); if (sumFinished && rich) { // we could've skipped the final skip block command for (; ptr < end; ++ptr) { if (*ptr == TextCommand && readSkipBlockCommand()) { break; } } } removeFlags.clear(); _t->_links.resize(maxLnkIndex); for (auto i = _t->_blocks.cbegin(), e = _t->_blocks.cend(); i != e; ++i) { auto b = i->get(); if (b->lnkIndex() > 0x8000) { lnkIndex = maxLnkIndex + (b->lnkIndex() - 0x8000); if (_t->_links.size() < lnkIndex) { _t->_links.resize(lnkIndex); auto &link = links[lnkIndex - maxLnkIndex - 1]; auto handler = ClickHandlerPtr(); switch (link.type) { case EntityInTextCustomUrl: { if (!link.data.isEmpty()) { handler = std::make_shared<HiddenUrlClickHandler>(link.data); } } break; case EntityInTextEmail: case EntityInTextUrl: handler = std::make_shared<UrlClickHandler>(link.data, link.displayStatus == LinkDisplayedFull); break; case EntityInTextBotCommand: handler = std::make_shared<BotCommandClickHandler>(link.data); break; case EntityInTextHashtag: if (options.flags & TextTwitterMentions) { handler = std::make_shared<UrlClickHandler>(qsl("https://twitter.com/hashtag/") + link.data.mid(1) + qsl("?src=hash"), true); } else if (options.flags & TextInstagramMentions) { handler = std::make_shared<UrlClickHandler>(qsl("https://instagram.com/explore/tags/") + link.data.mid(1) + '/', true); } else { handler = std::make_shared<HashtagClickHandler>(link.data); } break; case EntityInTextCashtag: handler = std::make_shared<CashtagClickHandler>(link.data); break; case EntityInTextMention: if (options.flags & TextTwitterMentions) { handler = std::make_shared<UrlClickHandler>(qsl("https://twitter.com/") + link.data.mid(1), true); } else if (options.flags & TextInstagramMentions) { handler = std::make_shared<UrlClickHandler>(qsl("https://instagram.com/") + link.data.mid(1) + '/', true); } else { handler = std::make_shared<MentionClickHandler>(link.data); } break; case EntityInTextMentionName: { auto fields = TextUtilities::MentionNameDataToFields(link.data); if (fields.userId) { handler = std::make_shared<MentionNameClickHandler>(link.text, fields.userId, fields.accessHash); } else { LOG(("Bad mention name: %1").arg(link.data)); } } break; } if (handler) { _t->setLink(lnkIndex, handler); } } b->setLnkIndex(lnkIndex); } } _t->_links.squeeze(); _t->_blocks.shrink_to_fit(); _t->_text.squeeze(); } private: enum LinkDisplayStatus { LinkDisplayedFull, LinkDisplayedElided, }; struct TextLinkData { TextLinkData() = default; TextLinkData(EntityInTextType type, const QString &text, const QString &data, LinkDisplayStatus displayStatus) : type(type) , text(text) , data(data) , displayStatus(displayStatus) { } EntityInTextType type = EntityInTextInvalid; QString text, data; LinkDisplayStatus displayStatus = LinkDisplayedFull; }; void computeLinkText(const QString &linkData, QString *outLinkText, LinkDisplayStatus *outDisplayStatus) { auto url = QUrl(linkData); auto good = QUrl(url.isValid() ? url.toEncoded() : QByteArray()); auto readable = good.isValid() ? good.toDisplayString() : linkData; *outLinkText = _t->_st->font->elided(readable, st::linkCropLimit); *outDisplayStatus = (*outLinkText == readable) ? LinkDisplayedFull : LinkDisplayedElided; } Text *_t; TextWithEntities source; const QChar *start, *end, *ptr; bool rich, multiline; EntitiesInText::const_iterator waitingEntity, entitiesEnd; typedef QVector<TextLinkData> TextLinks; TextLinks links; typedef QMap<const QChar*, QList<int32> > RemoveFlagsMap; RemoveFlagsMap removeFlags; uint16 maxLnkIndex = 0; // current state int32 flags = 0; uint16 lnkIndex = 0; EmojiPtr emoji = nullptr; // current emoji, if current word is an emoji, or zero int32 blockStart = 0; // offset in result, from which current parsed block is started int32 diacs = 0; // diac chars skipped without good char QFixed sumWidth, stopAfterWidth; // summary width of all added words bool sumFinished = false; bool newlineAwaited = false; // current char data QChar ch; // current char (low surrogate, if current char is surrogate pair) int32 emojiLookback; // how far behind the current ptr to look for current emoji bool lastSkipped; // did we skip current char bool checkTilde; // do we need a special text block for tilde symbol }; namespace { // COPIED FROM qtextengine.cpp AND MODIFIED struct BidiStatus { BidiStatus() { eor = QChar::DirON; lastStrong = QChar::DirON; last = QChar:: DirON; dir = QChar::DirON; } QChar::Direction eor; QChar::Direction lastStrong; QChar::Direction last; QChar::Direction dir; }; enum { _MaxBidiLevel = 61 }; enum { _MaxItemLength = 4096 }; struct BidiControl { inline BidiControl(bool rtl) : base(rtl ? 1 : 0), level(rtl ? 1 : 0) {} inline void embed(bool rtl, bool o = false) { unsigned int toAdd = 1; if((level%2 != 0) == rtl ) { ++toAdd; } if (level + toAdd <= _MaxBidiLevel) { ctx[cCtx].level = level; ctx[cCtx].override = override; cCtx++; override = o; level += toAdd; } } inline bool canPop() const { return cCtx != 0; } inline void pdf() { Q_ASSERT(cCtx); --cCtx; level = ctx[cCtx].level; override = ctx[cCtx].override; } inline QChar::Direction basicDirection() const { return (base ? QChar::DirR : QChar:: DirL); } inline unsigned int baseLevel() const { return base; } inline QChar::Direction direction() const { return ((level%2) ? QChar::DirR : QChar:: DirL); } struct { unsigned int level = 0; bool override = false; } ctx[_MaxBidiLevel]; unsigned int cCtx = 0; const unsigned int base; unsigned int level; bool override = false; }; static void eAppendItems(QScriptAnalysis *analysis, int &start, int &stop, const BidiControl &control, QChar::Direction dir) { if (start > stop) return; int level = control.level; if(dir != QChar::DirON && !control.override) { // add level of run (cases I1 & I2) if(level % 2) { if(dir == QChar::DirL || dir == QChar::DirAN || dir == QChar::DirEN) level++; } else { if(dir == QChar::DirR) level++; else if(dir == QChar::DirAN || dir == QChar::DirEN) level += 2; } } QScriptAnalysis *s = analysis + start; const QScriptAnalysis *e = analysis + stop; while (s <= e) { s->bidiLevel = level; ++s; } ++stop; start = stop; } } // namespace class TextPainter { public: TextPainter(Painter *p, const Text *t) : _p(p), _t(t) { } ~TextPainter() { restoreAfterElided(); if (_p) { _p->setPen(_originalPen); } } void draw(int32 left, int32 top, int32 w, style::align align, int32 yFrom, int32 yTo, TextSelection selection = { 0, 0 }, bool fullWidthSelection = true) { if (_t->isEmpty()) return; _blocksSize = _t->_blocks.size(); if (_p) { _p->setFont(_t->_st->font); _textPalette = &_p->textPalette(); _originalPen = _p->pen(); _originalPenSelected = (_textPalette->selectFg->c.alphaF() == 0) ? _originalPen : _textPalette->selectFg->p; } _x = left; _y = top; _yFrom = yFrom + top; _yTo = (yTo < 0) ? -1 : (yTo + top); _selection = selection; _fullWidthSelection = fullWidthSelection; _wLeft = _w = w; if (_elideLast) { _yToElide = _yTo; if (_elideRemoveFromEnd > 0 && !_t->_blocks.empty()) { int firstBlockHeight = countBlockHeight(_t->_blocks.front().get(), _t->_st); if (_y + firstBlockHeight >= _yToElide) { _wLeft -= _elideRemoveFromEnd; } } } _str = _t->_text.unicode(); if (_p) { auto clip = _p->hasClipping() ? _p->clipBoundingRect() : QRect(); if (clip.width() > 0 || clip.height() > 0) { if (_yFrom < clip.y()) _yFrom = clip.y(); if (_yTo < 0 || _yTo > clip.y() + clip.height()) _yTo = clip.y() + clip.height(); } } _align = align; _parDirection = _t->_startDir; if (_parDirection == Qt::LayoutDirectionAuto) _parDirection = cLangDir(); if ((*_t->_blocks.cbegin())->type() != TextBlockTNewline) { initNextParagraph(_t->_blocks.cbegin()); } _lineStart = 0; _lineStartBlock = 0; _lineHeight = 0; _fontHeight = _t->_st->font->height; auto last_rBearing = QFixed(0); _last_rPadding = QFixed(0); auto blockIndex = 0; bool longWordLine = true; auto e = _t->_blocks.cend(); for (auto i = _t->_blocks.cbegin(); i != e; ++i, ++blockIndex) { auto b = i->get(); auto _btype = b->type(); auto blockHeight = countBlockHeight(b, _t->_st); if (_btype == TextBlockTNewline) { if (!_lineHeight) _lineHeight = blockHeight; if (!drawLine((*i)->from(), i, e)) { return; } _y += _lineHeight; _lineHeight = 0; _lineStart = _t->countBlockEnd(i, e); _lineStartBlock = blockIndex + 1; last_rBearing = b->f_rbearing(); _last_rPadding = b->f_rpadding(); _wLeft = _w - (b->f_width() - last_rBearing); if (_elideLast && _elideRemoveFromEnd > 0 && (_y + blockHeight >= _yToElide)) { _wLeft -= _elideRemoveFromEnd; } _parDirection = static_cast<NewlineBlock*>(b)->nextDirection(); if (_parDirection == Qt::LayoutDirectionAuto) _parDirection = cLangDir(); initNextParagraph(i + 1); longWordLine = true; continue; } auto b__f_rbearing = b->f_rbearing(); auto newWidthLeft = _wLeft - last_rBearing - (_last_rPadding + b->f_width() - b__f_rbearing); if (newWidthLeft >= 0) { last_rBearing = b__f_rbearing; _last_rPadding = b->f_rpadding(); _wLeft = newWidthLeft; _lineHeight = qMax(_lineHeight, blockHeight); longWordLine = false; continue; } if (_btype == TextBlockTText) { auto t = static_cast<TextBlock*>(b); if (t->_words.isEmpty()) { // no words in this block, spaces only => layout this block in the same line _last_rPadding += b->f_rpadding(); _lineHeight = qMax(_lineHeight, blockHeight); longWordLine = false; continue; } auto f_wLeft = _wLeft; // vars for saving state of the last word start auto f_lineHeight = _lineHeight; // f points to the last word-start element of t->_words for (auto j = t->_words.cbegin(), en = t->_words.cend(), f = j; j != en; ++j) { auto wordEndsHere = (j->f_width() >= 0); auto j_width = wordEndsHere ? j->f_width() : -j->f_width(); auto newWidthLeft = _wLeft - last_rBearing - (_last_rPadding + j_width - j->f_rbearing()); if (newWidthLeft >= 0) { last_rBearing = j->f_rbearing(); _last_rPadding = j->f_rpadding(); _wLeft = newWidthLeft; _lineHeight = qMax(_lineHeight, blockHeight); if (wordEndsHere) { longWordLine = false; } if (wordEndsHere || longWordLine) { f = j + 1; f_wLeft = _wLeft; f_lineHeight = _lineHeight; } continue; } auto elidedLineHeight = qMax(_lineHeight, blockHeight); auto elidedLine = _elideLast && (_y + elidedLineHeight >= _yToElide); if (elidedLine) { _lineHeight = elidedLineHeight; } else if (f != j && !_breakEverywhere) { // word did not fit completely, so we roll back the state to the beginning of this long word j = f; _wLeft = f_wLeft; _lineHeight = f_lineHeight; j_width = (j->f_width() >= 0) ? j->f_width() : -j->f_width(); } if (!drawLine(elidedLine ? ((j + 1 == en) ? _t->countBlockEnd(i, e) : (j + 1)->from()) : j->from(), i, e)) { return; } _y += _lineHeight; _lineHeight = qMax(0, blockHeight); _lineStart = j->from(); _lineStartBlock = blockIndex; last_rBearing = j->f_rbearing(); _last_rPadding = j->f_rpadding(); _wLeft = _w - (j_width - last_rBearing); if (_elideLast && _elideRemoveFromEnd > 0 && (_y + blockHeight >= _yToElide)) { _wLeft -= _elideRemoveFromEnd; } longWordLine = true; f = j + 1; f_wLeft = _wLeft; f_lineHeight = _lineHeight; } continue; } auto elidedLineHeight = qMax(_lineHeight, blockHeight); auto elidedLine = _elideLast && (_y + elidedLineHeight >= _yToElide); if (elidedLine) { _lineHeight = elidedLineHeight; } if (!drawLine(elidedLine ? _t->countBlockEnd(i, e) : b->from(), i, e)) { return; } _y += _lineHeight; _lineHeight = qMax(0, blockHeight); _lineStart = b->from(); _lineStartBlock = blockIndex; last_rBearing = b__f_rbearing; _last_rPadding = b->f_rpadding(); _wLeft = _w - (b->f_width() - last_rBearing); if (_elideLast && _elideRemoveFromEnd > 0 && (_y + blockHeight >= _yToElide)) { _wLeft -= _elideRemoveFromEnd; } longWordLine = true; continue; } if (_lineStart < _t->_text.size()) { if (!drawLine(_t->_text.size(), e, e)) return; } if (!_p && _lookupSymbol) { _lookupResult.symbol = _t->_text.size(); _lookupResult.afterSymbol = false; } } void drawElided(int32 left, int32 top, int32 w, style::align align, int32 lines, int32 yFrom, int32 yTo, int32 removeFromEnd, bool breakEverywhere, TextSelection selection) { if (lines <= 0 || _t->isNull()) return; if (yTo < 0 || (lines - 1) * _t->_st->font->height < yTo) { yTo = lines * _t->_st->font->height; _elideLast = true; _elideRemoveFromEnd = removeFromEnd; } _breakEverywhere = breakEverywhere; draw(left, top, w, align, yFrom, yTo, selection); } Text::StateResult getState(QPoint point, int w, Text::StateRequest request) { if (!_t->isNull() && point.y() >= 0) { _lookupRequest = request; _lookupX = point.x(); _lookupY = point.y(); _breakEverywhere = (_lookupRequest.flags & Text::StateRequest::Flag::BreakEverywhere); _lookupSymbol = (_lookupRequest.flags & Text::StateRequest::Flag::LookupSymbol); _lookupLink = (_lookupRequest.flags & Text::StateRequest::Flag::LookupLink); if (_lookupSymbol || (_lookupX >= 0 && _lookupX < w)) { draw(0, 0, w, _lookupRequest.align, _lookupY, _lookupY + 1); } } return _lookupResult; } Text::StateResult getStateElided(QPoint point, int w, Text::StateRequestElided request) { if (!_t->isNull() && point.y() >= 0 && request.lines > 0) { _lookupRequest = request; _lookupX = point.x(); _lookupY = point.y(); _breakEverywhere = (_lookupRequest.flags & Text::StateRequest::Flag::BreakEverywhere); _lookupSymbol = (_lookupRequest.flags & Text::StateRequest::Flag::LookupSymbol); _lookupLink = (_lookupRequest.flags & Text::StateRequest::Flag::LookupLink); if (_lookupSymbol || (_lookupX >= 0 && _lookupX < w)) { int yTo = _lookupY + 1; if (yTo < 0 || (request.lines - 1) * _t->_st->font->height < yTo) { yTo = request.lines * _t->_st->font->height; _elideLast = true; _elideRemoveFromEnd = request.removeFromEnd; } draw(0, 0, w, _lookupRequest.align, _lookupY, _lookupY + 1); } } return _lookupResult; } private: void initNextParagraph(Text::TextBlocks::const_iterator i) { _parStartBlock = i; Text::TextBlocks::const_iterator e = _t->_blocks.cend(); if (i == e) { _parStart = _t->_text.size(); _parLength = 0; } else { _parStart = (*i)->from(); for (; i != e; ++i) { if ((*i)->type() == TextBlockTNewline) { break; } } _parLength = ((i == e) ? _t->_text.size() : (*i)->from()) - _parStart; } _parAnalysis.resize(0); } void initParagraphBidi() { if (!_parLength || !_parAnalysis.isEmpty()) return; Text::TextBlocks::const_iterator i = _parStartBlock, e = _t->_blocks.cend(), n = i + 1; bool ignore = false; bool rtl = (_parDirection == Qt::RightToLeft); if (!ignore && !rtl) { ignore = true; const ushort *start = reinterpret_cast<const ushort*>(_str) + _parStart; const ushort *curr = start; const ushort *end = start + _parLength; while (curr < end) { while (n != e && (*n)->from() <= _parStart + (curr - start)) { i = n; ++n; } if ((*i)->type() != TextBlockTEmoji && *curr >= 0x590) { ignore = false; break; } ++curr; } } _parAnalysis.resize(_parLength); QScriptAnalysis *analysis = _parAnalysis.data(); BidiControl control(rtl); _parHasBidi = false; if (ignore) { memset(analysis, 0, _parLength * sizeof(QScriptAnalysis)); if (rtl) { for (int i = 0; i < _parLength; ++i) analysis[i].bidiLevel = 1; _parHasBidi = true; } } else { _parHasBidi = eBidiItemize(analysis, control); } } bool drawLine(uint16 _lineEnd, const Text::TextBlocks::const_iterator &_endBlockIter, const Text::TextBlocks::const_iterator &_end) { _yDelta = (_lineHeight - _fontHeight) / 2; if (_yTo >= 0 && (_y + _yDelta >= _yTo || _y >= _yTo)) return false; if (_y + _yDelta + _fontHeight <= _yFrom) { if (_lookupSymbol) { _lookupResult.symbol = (_lineEnd > _lineStart) ? (_lineEnd - 1) : _lineStart; _lookupResult.afterSymbol = (_lineEnd > _lineStart) ? true : false; } return true; } // Trimming pending spaces, because they sometimes don't fit on the line. // They also are not counted in the line width, they're in the right padding. // Line width is a sum of block / word widths and paddings between them, without trailing one. auto trimmedLineEnd = _lineEnd; for (; trimmedLineEnd > _lineStart; --trimmedLineEnd) { auto ch = _t->_text[trimmedLineEnd - 1]; if (ch != QChar::Space && ch != QChar::LineFeed) { break; } } auto _endBlock = (_endBlockIter == _end) ? nullptr : _endBlockIter->get(); auto elidedLine = _elideLast && (_y + _lineHeight >= _yToElide); if (elidedLine) { // If we decided to draw the last line elided only because of the skip block // that did not fit on this line, we just draw the line till the very end. // Skip block is ignored in the elided lines, instead "removeFromEnd" is used. if (_endBlock && _endBlock->type() == TextBlockTSkip) { _endBlock = nullptr; } if (!_endBlock) { elidedLine = false; } } auto blockIndex = _lineStartBlock; auto currentBlock = _t->_blocks[blockIndex].get(); auto nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex].get() : nullptr; const auto extendLeft = (currentBlock->from() < _lineStart) ? qMin(_lineStart - currentBlock->from(), 2) : 0; _localFrom = _lineStart - extendLeft; const auto extendedLineEnd = (_endBlock && _endBlock->from() < trimmedLineEnd && !elidedLine) ? qMin(uint16(trimmedLineEnd + 2), _t->countBlockEnd(_endBlockIter, _end)) : trimmedLineEnd; auto lineText = _t->_text.mid(_localFrom, extendedLineEnd - _localFrom); auto lineStart = extendLeft; auto lineLength = trimmedLineEnd - _lineStart; if (elidedLine) { initParagraphBidi(); prepareElidedLine(lineText, lineStart, lineLength, _endBlock); } auto x = _x; if (_align & Qt::AlignHCenter) { x += (_wLeft / 2).toInt(); } else if (((_align & Qt::AlignLeft) && _parDirection == Qt::RightToLeft) || ((_align & Qt::AlignRight) && _parDirection == Qt::LeftToRight)) { x += _wLeft; } if (!_p) { if (_lookupX < x) { if (_lookupSymbol) { if (_parDirection == Qt::RightToLeft) { _lookupResult.symbol = (_lineEnd > _lineStart) ? (_lineEnd - 1) : _lineStart; _lookupResult.afterSymbol = (_lineEnd > _lineStart) ? true : false; // _lookupResult.uponSymbol = ((_lookupX >= _x) && (_lineEnd < _t->_text.size()) && (!_endBlock || _endBlock->type() != TextBlockTSkip)) ? true : false; } else { _lookupResult.symbol = _lineStart; _lookupResult.afterSymbol = false; // _lookupResult.uponSymbol = ((_lookupX >= _x) && (_lineStart > 0)) ? true : false; } } if (_lookupLink) { _lookupResult.link = nullptr; } _lookupResult.uponSymbol = false; return false; } else if (_lookupX >= x + (_w - _wLeft)) { if (_parDirection == Qt::RightToLeft) { _lookupResult.symbol = _lineStart; _lookupResult.afterSymbol = false; // _lookupResult.uponSymbol = ((_lookupX < _x + _w) && (_lineStart > 0)) ? true : false; } else { _lookupResult.symbol = (_lineEnd > _lineStart) ? (_lineEnd - 1) : _lineStart; _lookupResult.afterSymbol = (_lineEnd > _lineStart) ? true : false; // _lookupResult.uponSymbol = ((_lookupX < _x + _w) && (_lineEnd < _t->_text.size()) && (!_endBlock || _endBlock->type() != TextBlockTSkip)) ? true : false; } if (_lookupLink) { _lookupResult.link = nullptr; } _lookupResult.uponSymbol = false; return false; } } if (_fullWidthSelection) { const auto selectFromStart = (_selection.to > _lineStart) && (_lineStart > 0) && (_selection.from <= _lineStart); const auto selectTillEnd = (_selection.to > trimmedLineEnd) && (trimmedLineEnd < _t->_text.size()) && (_selection.from <= trimmedLineEnd) && (!_endBlock || _endBlock->type() != TextBlockTSkip); if ((selectFromStart && _parDirection == Qt::LeftToRight) || (selectTillEnd && _parDirection == Qt::RightToLeft)) { if (x > _x) { fillSelectRange(_x, x); } } if ((selectTillEnd && _parDirection == Qt::LeftToRight) || (selectFromStart && _parDirection == Qt::RightToLeft)) { if (x < _x + _wLeft) { fillSelectRange(x + _w - _wLeft, _x + _w); } } } if (trimmedLineEnd == _lineStart && !elidedLine) { return true; } if (!elidedLine) initParagraphBidi(); // if was not inited _f = _t->_st->font; QStackTextEngine engine(lineText, _f->f); engine.option.setTextDirection(_parDirection); _e = &engine; eItemize(); QScriptLine line; line.from = lineStart; line.length = lineLength; eShapeLine(line); int firstItem = engine.findItem(line.from), lastItem = engine.findItem(line.from + line.length - 1); int nItems = (firstItem >= 0 && lastItem >= firstItem) ? (lastItem - firstItem + 1) : 0; if (!nItems) { return true; } int skipIndex = -1; QVarLengthArray<int> visualOrder(nItems); QVarLengthArray<uchar> levels(nItems); for (int i = 0; i < nItems; ++i) { auto &si = engine.layoutData->items[firstItem + i]; while (nextBlock && nextBlock->from() <= _localFrom + si.position) { currentBlock = nextBlock; nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex].get() : nullptr; } auto _type = currentBlock->type(); if (_type == TextBlockTSkip) { levels[i] = si.analysis.bidiLevel = 0; skipIndex = i; } else { levels[i] = si.analysis.bidiLevel; } if (si.analysis.flags == QScriptAnalysis::Object) { if (_type == TextBlockTEmoji || _type == TextBlockTSkip) { si.width = currentBlock->f_width() + (nextBlock == _endBlock && (!nextBlock || nextBlock->from() >= trimmedLineEnd) ? 0 : currentBlock->f_rpadding()); } } } QTextEngine::bidiReorder(nItems, levels.data(), visualOrder.data()); if (rtl() && skipIndex == nItems - 1) { for (int32 i = nItems; i > 1;) { --i; visualOrder[i] = visualOrder[i - 1]; } visualOrder[0] = skipIndex; } blockIndex = _lineStartBlock; currentBlock = _t->_blocks[blockIndex].get(); nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex].get() : nullptr; int32 textY = _y + _yDelta + _t->_st->font->ascent, emojiY = (_t->_st->font->height - st::emojiSize) / 2; applyBlockProperties(currentBlock); for (int i = 0; i < nItems; ++i) { int item = firstItem + visualOrder[i]; const QScriptItem &si = engine.layoutData->items.at(item); bool rtl = (si.analysis.bidiLevel % 2); while (blockIndex > _lineStartBlock + 1 && _t->_blocks[blockIndex - 1]->from() > _localFrom + si.position) { nextBlock = currentBlock; currentBlock = _t->_blocks[--blockIndex - 1].get(); applyBlockProperties(currentBlock); } while (nextBlock && nextBlock->from() <= _localFrom + si.position) { currentBlock = nextBlock; nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex].get() : nullptr; applyBlockProperties(currentBlock); } if (si.analysis.flags >= QScriptAnalysis::TabOrObject) { TextBlockType _type = currentBlock->type(); if (!_p && _lookupX >= x && _lookupX < x + si.width) { // _lookupRequest if (_lookupLink) { if (currentBlock->lnkIndex() && _lookupY >= _y + _yDelta && _lookupY < _y + _yDelta + _fontHeight) { _lookupResult.link = _t->_links.at(currentBlock->lnkIndex() - 1); } } if (_type != TextBlockTSkip) { _lookupResult.uponSymbol = true; } if (_lookupSymbol) { if (_type == TextBlockTSkip) { if (_parDirection == Qt::RightToLeft) { _lookupResult.symbol = _lineStart; _lookupResult.afterSymbol = false; } else { _lookupResult.symbol = (trimmedLineEnd > _lineStart) ? (trimmedLineEnd - 1) : _lineStart; _lookupResult.afterSymbol = (trimmedLineEnd > _lineStart) ? true : false; } return false; } // Emoji with spaces after symbol lookup auto chFrom = _str + currentBlock->from(); auto chTo = chFrom + ((nextBlock ? nextBlock->from() : _t->_text.size()) - currentBlock->from()); auto spacesWidth = (si.width - currentBlock->f_width()); auto spacesCount = 0; while (chTo > chFrom && (chTo - 1)->unicode() == QChar::Space) { ++spacesCount; --chTo; } if (spacesCount > 0) { // Check if we're over a space. if (rtl) { if (_lookupX < x + spacesWidth) { _lookupResult.symbol = (chTo - _str); // up to a space, included, rtl _lookupResult.afterSymbol = (_lookupX < x + (spacesWidth / 2)) ? true : false; return false; } } else if (_lookupX >= x + si.width - spacesWidth) { _lookupResult.symbol = (chTo - _str); // up to a space, inclided, ltr _lookupResult.afterSymbol = (_lookupX >= x + si.width - spacesWidth + (spacesWidth / 2)) ? true : false; return false; } } if (_lookupX < x + (rtl ? (si.width - currentBlock->f_width()) : 0) + (currentBlock->f_width() / 2)) { _lookupResult.symbol = ((rtl && chTo > chFrom) ? (chTo - 1) : chFrom) - _str; _lookupResult.afterSymbol = (rtl && chTo > chFrom) ? true : false; } else { _lookupResult.symbol = ((rtl || chTo <= chFrom) ? chFrom : (chTo - 1)) - _str; _lookupResult.afterSymbol = (rtl || chTo <= chFrom) ? false : true; } } return false; } else if (_p && _type == TextBlockTEmoji) { auto glyphX = x; auto spacesWidth = (si.width - currentBlock->f_width()); if (rtl) { glyphX += spacesWidth; } if (_localFrom + si.position < _selection.to) { auto chFrom = _str + currentBlock->from(); auto chTo = chFrom + ((nextBlock ? nextBlock->from() : _t->_text.size()) - currentBlock->from()); if (_localFrom + si.position >= _selection.from) { // could be without space if (chTo == chFrom || (chTo - 1)->unicode() != QChar::Space || _selection.to >= (chTo - _str)) { fillSelectRange(x, x + si.width); } else { // or with space fillSelectRange(glyphX, glyphX + currentBlock->f_width()); } } else if (chTo > chFrom && (chTo - 1)->unicode() == QChar::Space && (chTo - 1 - _str) >= _selection.from) { if (rtl) { // rtl space only fillSelectRange(x, glyphX); } else { // ltr space only fillSelectRange(x + currentBlock->f_width(), x + si.width); } } } emojiDraw(*_p, static_cast<EmojiBlock*>(currentBlock)->emoji, (glyphX + st::emojiPadding).toInt(), _y + _yDelta + emojiY); // } else if (_p && currentBlock->type() == TextBlockSkip) { // debug // _p->fillRect(QRect(x.toInt(), _y, currentBlock->width(), static_cast<SkipBlock*>(currentBlock)->height()), QColor(0, 0, 0, 32)); } x += si.width; continue; } unsigned short *logClusters = engine.logClusters(&si); QGlyphLayout glyphs = engine.shapedGlyphs(&si); int itemStart = qMax(line.from, si.position), itemEnd; int itemLength = engine.length(item); int glyphsStart = logClusters[itemStart - si.position], glyphsEnd; if (line.from + line.length < si.position + itemLength) { itemEnd = line.from + line.length; glyphsEnd = logClusters[itemEnd - si.position]; } else { itemEnd = si.position + itemLength; glyphsEnd = si.num_glyphs; } QFixed itemWidth = 0; for (int g = glyphsStart; g < glyphsEnd; ++g) itemWidth += glyphs.effectiveAdvance(g); if (!_p && _lookupX >= x && _lookupX < x + itemWidth) { // _lookupRequest if (_lookupLink) { if (currentBlock->lnkIndex() && _lookupY >= _y + _yDelta && _lookupY < _y + _yDelta + _fontHeight) { _lookupResult.link = _t->_links.at(currentBlock->lnkIndex() - 1); } } _lookupResult.uponSymbol = true; if (_lookupSymbol) { QFixed tmpx = rtl ? (x + itemWidth) : x; for (int ch = 0, g, itemL = itemEnd - itemStart; ch < itemL;) { g = logClusters[itemStart - si.position + ch]; QFixed gwidth = glyphs.effectiveAdvance(g); // ch2 - glyph end, ch - glyph start, (ch2 - ch) - how much chars it takes int ch2 = ch + 1; while ((ch2 < itemL) && (g == logClusters[itemStart - si.position + ch2])) { ++ch2; } for (int charsCount = (ch2 - ch); ch < ch2; ++ch) { QFixed shift1 = QFixed(2 * (charsCount - (ch2 - ch)) + 2) * gwidth / QFixed(2 * charsCount), shift2 = QFixed(2 * (charsCount - (ch2 - ch)) + 1) * gwidth / QFixed(2 * charsCount); if ((rtl && _lookupX >= tmpx - shift1) || (!rtl && _lookupX < tmpx + shift1)) { _lookupResult.symbol = _localFrom + itemStart + ch; if ((rtl && _lookupX >= tmpx - shift2) || (!rtl && _lookupX < tmpx + shift2)) { _lookupResult.afterSymbol = false; } else { _lookupResult.afterSymbol = true; } return false; } } if (rtl) { tmpx -= gwidth; } else { tmpx += gwidth; } } if (itemEnd > itemStart) { _lookupResult.symbol = _localFrom + itemEnd - 1; _lookupResult.afterSymbol = true; } else { _lookupResult.symbol = _localFrom + itemStart; _lookupResult.afterSymbol = false; } } return false; } else if (_p) { QTextCharFormat format; QTextItemInt gf(glyphs.mid(glyphsStart, glyphsEnd - glyphsStart), &_e->fnt, engine.layoutData->string.unicode() + itemStart, itemEnd - itemStart, engine.fontEngine(si), format); gf.logClusters = logClusters + itemStart - si.position; gf.width = itemWidth; gf.justified = false; gf.initWithScriptItem(si); auto hasSelected = false; auto hasNotSelected = true; auto selectedRect = QRect(); if (_localFrom + itemStart < _selection.to && _localFrom + itemEnd > _selection.from) { hasSelected = true; auto selX = x; auto selWidth = itemWidth; if (_localFrom + itemStart >= _selection.from && _localFrom + itemEnd <= _selection.to) { hasNotSelected = false; } else { selWidth = 0; int itemL = itemEnd - itemStart; int selStart = _selection.from - (_localFrom + itemStart), selEnd = _selection.to - (_localFrom + itemStart); if (selStart < 0) selStart = 0; if (selEnd > itemL) selEnd = itemL; for (int ch = 0, g; ch < selEnd;) { g = logClusters[itemStart - si.position + ch]; QFixed gwidth = glyphs.effectiveAdvance(g); // ch2 - glyph end, ch - glyph start, (ch2 - ch) - how much chars it takes int ch2 = ch + 1; while ((ch2 < itemL) && (g == logClusters[itemStart - si.position + ch2])) { ++ch2; } if (ch2 <= selStart) { selX += gwidth; } else if (ch >= selStart && ch2 <= selEnd) { selWidth += gwidth; } else { int sStart = ch, sEnd = ch2; if (ch < selStart) { sStart = selStart; selX += QFixed(sStart - ch) * gwidth / QFixed(ch2 - ch); } if (ch2 >= selEnd) { sEnd = selEnd; selWidth += QFixed(sEnd - sStart) * gwidth / QFixed(ch2 - ch); break; } selWidth += QFixed(sEnd - sStart) * gwidth / QFixed(ch2 - ch); } ch = ch2; } } if (rtl) selX = x + itemWidth - (selX - x) - selWidth; selectedRect = QRect(selX.toInt(), _y + _yDelta, (selX + selWidth).toInt() - selX.toInt(), _fontHeight); fillSelectRange(selX, selX + selWidth); } if (Q_UNLIKELY(hasSelected)) { if (Q_UNLIKELY(hasNotSelected)) { auto clippingEnabled = _p->hasClipping(); auto clippingRegion = _p->clipRegion(); _p->setClipRect(selectedRect, Qt::IntersectClip); _p->setPen(*_currentPenSelected); _p->drawTextItem(QPointF(x.toReal(), textY), gf); auto externalClipping = clippingEnabled ? clippingRegion : QRegion(QRect((_x - _w).toInt(), _y - _lineHeight, (_x + 2 * _w).toInt(), _y + 2 * _lineHeight)); _p->setClipRegion(externalClipping - selectedRect); _p->setPen(*_currentPen); _p->drawTextItem(QPointF(x.toReal(), textY), gf); if (clippingEnabled) { _p->setClipRegion(clippingRegion); } else { _p->setClipping(false); } } else { _p->setPen(*_currentPenSelected); _p->drawTextItem(QPointF(x.toReal(), textY), gf); } } else { _p->setPen(*_currentPen); _p->drawTextItem(QPointF(x.toReal(), textY), gf); } } x += itemWidth; } return true; } void fillSelectRange(QFixed from, QFixed to) { auto left = from.toInt(); auto width = to.toInt() - left; _p->fillRect(left, _y + _yDelta, width, _fontHeight, _textPalette->selectBg); } void elideSaveBlock(int32 blockIndex, ITextBlock *&_endBlock, int32 elideStart, int32 elideWidth) { if (_elideSavedBlock) { restoreAfterElided(); } _elideSavedIndex = blockIndex; auto mutableText = const_cast<Text*>(_t); _elideSavedBlock = std::move(mutableText->_blocks[blockIndex]); mutableText->_blocks[blockIndex] = std::make_unique<TextBlock>(_t->_st->font, _t->_text, QFIXED_MAX, elideStart, 0, _elideSavedBlock->flags(), _elideSavedBlock->lnkIndex()); _blocksSize = blockIndex + 1; _endBlock = (blockIndex + 1 < _t->_blocks.size() ? _t->_blocks[blockIndex + 1].get() : nullptr); } void setElideBidi(int32 elideStart, int32 elideLen) { int32 newParLength = elideStart + elideLen - _parStart; if (newParLength > _parAnalysis.size()) { _parAnalysis.resize(newParLength); } for (int32 i = elideLen; i > 0; --i) { _parAnalysis[newParLength - i].bidiLevel = (_parDirection == Qt::RightToLeft) ? 1 : 0; } } void prepareElidedLine(QString &lineText, int32 lineStart, int32 &lineLength, ITextBlock *&_endBlock, int repeat = 0) { static const QString _Elide = qsl("..."); _f = _t->_st->font; QStackTextEngine engine(lineText, _f->f); engine.option.setTextDirection(_parDirection); _e = &engine; eItemize(); auto blockIndex = _lineStartBlock; auto currentBlock = _t->_blocks[blockIndex].get(); auto nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex].get() : nullptr; QScriptLine line; line.from = lineStart; line.length = lineLength; eShapeLine(line); auto elideWidth = _f->elidew; _wLeft = _w - elideWidth - _elideRemoveFromEnd; int firstItem = engine.findItem(line.from), lastItem = engine.findItem(line.from + line.length - 1); int nItems = (firstItem >= 0 && lastItem >= firstItem) ? (lastItem - firstItem + 1) : 0, i; for (i = 0; i < nItems; ++i) { QScriptItem &si(engine.layoutData->items[firstItem + i]); while (nextBlock && nextBlock->from() <= _localFrom + si.position) { currentBlock = nextBlock; nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex].get() : nullptr; } TextBlockType _type = currentBlock->type(); if (si.analysis.flags == QScriptAnalysis::Object) { if (_type == TextBlockTEmoji || _type == TextBlockTSkip) { si.width = currentBlock->f_width() + currentBlock->f_rpadding(); } } if (_type == TextBlockTEmoji || _type == TextBlockTSkip || _type == TextBlockTNewline) { if (_wLeft < si.width) { lineText = lineText.mid(0, currentBlock->from() - _localFrom) + _Elide; lineLength = currentBlock->from() + _Elide.size() - _lineStart; _selection.to = qMin(_selection.to, currentBlock->from()); setElideBidi(currentBlock->from(), _Elide.size()); elideSaveBlock(blockIndex - 1, _endBlock, currentBlock->from(), elideWidth); return; } _wLeft -= si.width; } else if (_type == TextBlockTText) { unsigned short *logClusters = engine.logClusters(&si); QGlyphLayout glyphs = engine.shapedGlyphs(&si); int itemStart = qMax(line.from, si.position), itemEnd; int itemLength = engine.length(firstItem + i); int glyphsStart = logClusters[itemStart - si.position], glyphsEnd; if (line.from + line.length < si.position + itemLength) { itemEnd = line.from + line.length; glyphsEnd = logClusters[itemEnd - si.position]; } else { itemEnd = si.position + itemLength; glyphsEnd = si.num_glyphs; } for (auto g = glyphsStart; g < glyphsEnd; ++g) { auto adv = glyphs.effectiveAdvance(g); if (_wLeft < adv) { auto pos = itemStart; while (pos < itemEnd && logClusters[pos - si.position] < g) { ++pos; } if (lineText.size() <= pos || repeat > 3) { lineText += _Elide; lineLength = _localFrom + pos + _Elide.size() - _lineStart; _selection.to = qMin(_selection.to, uint16(_localFrom + pos)); setElideBidi(_localFrom + pos, _Elide.size()); _blocksSize = blockIndex; _endBlock = nextBlock; } else { lineText = lineText.mid(0, pos); lineLength = _localFrom + pos - _lineStart; _blocksSize = blockIndex; _endBlock = nextBlock; prepareElidedLine(lineText, lineStart, lineLength, _endBlock, repeat + 1); } return; } else { _wLeft -= adv; } } } } int32 elideStart = _localFrom + lineText.size(); _selection.to = qMin(_selection.to, uint16(elideStart)); setElideBidi(elideStart, _Elide.size()); lineText += _Elide; lineLength += _Elide.size(); if (!repeat) { for (; blockIndex < _blocksSize && _t->_blocks[blockIndex].get() != _endBlock && _t->_blocks[blockIndex]->from() < elideStart; ++blockIndex) { } if (blockIndex < _blocksSize) { elideSaveBlock(blockIndex, _endBlock, elideStart, elideWidth); } } } void restoreAfterElided() { if (_elideSavedBlock) { const_cast<Text*>(_t)->_blocks[_elideSavedIndex] = std::move(_elideSavedBlock); } } // COPIED FROM qtextengine.cpp AND MODIFIED void eShapeLine(const QScriptLine &line) { int item = _e->findItem(line.from); if (item == -1) return; #ifdef OS_MAC_OLD auto end = _e->findItem(line.from + line.length - 1); #else // OS_MAC_OLD auto end = _e->findItem(line.from + line.length - 1, item); #endif // OS_MAC_OLD auto blockIndex = _lineStartBlock; auto currentBlock = _t->_blocks[blockIndex].get(); auto nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex].get() : nullptr; eSetFont(currentBlock); for (; item <= end; ++item) { QScriptItem &si = _e->layoutData->items[item]; while (nextBlock && nextBlock->from() <= _localFrom + si.position) { currentBlock = nextBlock; nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex].get() : nullptr; eSetFont(currentBlock); } _e->shape(item); } } style::font applyFlags(int32 flags, const style::font &f) { style::font result = f; if ((flags & TextBlockFPre) || (flags & TextBlockFCode)) { result = App::monofont(); if (result->size() != f->size() || result->flags() != f->flags()) { result = style::font(f->size(), f->flags(), result->family()); } } else { if (flags & TextBlockFBold) { result = result->bold(); } else if (flags & TextBlockFSemibold) { result = st::semiboldFont; if (result->size() != f->size() || result->flags() != f->flags()) { result = style::font(f->size(), f->flags(), result->family()); } } if (flags & TextBlockFItalic) result = result->italic(); if (flags & TextBlockFUnderline) result = result->underline(); if (flags & TextBlockFTilde) { // tilde fix in OpenSans result = st::semiboldFont; } } return result; } void eSetFont(ITextBlock *block) { style::font newFont = _t->_st->font; int flags = block->flags(); if (flags) { newFont = applyFlags(flags, _t->_st->font); } if (block->lnkIndex()) { if (ClickHandler::showAsActive(_t->_links.at(block->lnkIndex() - 1))) { if (_t->_st->font != _t->_st->linkFontOver) { newFont = _t->_st->linkFontOver; } } else { if (_t->_st->font != _t->_st->linkFont) { newFont = _t->_st->linkFont; } } } if (newFont != _f) { if (newFont->family() == _t->_st->font->family()) { newFont = applyFlags(flags | newFont->flags(), _t->_st->font); } _f = newFont; _e->fnt = _f->f; _e->resetFontEngineCache(); } } void eItemize() { _e->validate(); if (_e->layoutData->items.size()) return; int length = _e->layoutData->string.length(); if (!length) return; const ushort *string = reinterpret_cast<const ushort*>(_e->layoutData->string.unicode()); auto blockIndex = _lineStartBlock; auto currentBlock = _t->_blocks[blockIndex].get(); auto nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex].get() : nullptr; _e->layoutData->hasBidi = _parHasBidi; auto analysis = _parAnalysis.data() + (_localFrom - _parStart); { QVarLengthArray<uchar> scripts(length); QUnicodeTools::initScripts(string, length, scripts.data()); for (int i = 0; i < length; ++i) analysis[i].script = scripts.at(i); } blockIndex = _lineStartBlock; currentBlock = _t->_blocks[blockIndex].get(); nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex].get() : nullptr; auto start = string; auto end = start + length; while (start < end) { while (nextBlock && nextBlock->from() <= _localFrom + (start - string)) { currentBlock = nextBlock; nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex].get() : nullptr; } auto _type = currentBlock->type(); if (_type == TextBlockTEmoji || _type == TextBlockTSkip) { analysis->script = QChar::Script_Common; analysis->flags = QScriptAnalysis::Object; } else { analysis->flags = QScriptAnalysis::None; } analysis->script = hbscript_to_script(script_to_hbscript(analysis->script)); // retain the old behavior ++start; ++analysis; } { auto i_string = &_e->layoutData->string; auto i_analysis = _parAnalysis.data() + (_localFrom - _parStart); auto i_items = &_e->layoutData->items; blockIndex = _lineStartBlock; currentBlock = _t->_blocks[blockIndex].get(); nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex].get() : nullptr; auto startBlock = currentBlock; if (!length) { return; } auto start = 0; auto end = start + length; for (int i = start + 1; i < end; ++i) { while (nextBlock && nextBlock->from() <= _localFrom + i) { currentBlock = nextBlock; nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex].get() : nullptr; } // According to the unicode spec we should be treating characters in the Common script // (punctuation, spaces, etc) as being the same script as the surrounding text for the // purpose of splitting up text. This is important because, for example, a fullstop // (0x2E) can be used to indicate an abbreviation and so must be treated as part of a // word. Thus it must be passed along with the word in languages that have to calculate // word breaks. For example the thai word "ครม." has no word breaks but the word "ครม" // does. // Unfortuntely because we split up the strings for both wordwrapping and for setting // the font and because Japanese and Chinese are also aliases of the script "Common", // doing this would break too many things. So instead we only pass the full stop // along, and nothing else. if (currentBlock == startBlock && i_analysis[i].bidiLevel == i_analysis[start].bidiLevel && i_analysis[i].flags == i_analysis[start].flags && (i_analysis[i].script == i_analysis[start].script || i_string->at(i) == QLatin1Char('.')) // && i_analysis[i].flags < QScriptAnalysis::SpaceTabOrObject // only emojis are objects here, no tabs && i - start < _MaxItemLength) continue; i_items->append(QScriptItem(start, i_analysis[start])); start = i; startBlock = currentBlock; } i_items->append(QScriptItem(start, i_analysis[start])); } } QChar::Direction eSkipBoundryNeutrals(QScriptAnalysis *analysis, const ushort *unicode, int &sor, int &eor, BidiControl &control, Text::TextBlocks::const_iterator i) { Text::TextBlocks::const_iterator e = _t->_blocks.cend(), n = i + 1; QChar::Direction dir = control.basicDirection(); int level = sor > 0 ? analysis[sor - 1].bidiLevel : control.level; while (sor <= _parLength) { while (i != _parStartBlock && (*i)->from() > _parStart + sor) { n = i; --i; } while (n != e && (*n)->from() <= _parStart + sor) { i = n; ++n; } TextBlockType _itype = (*i)->type(); if (eor == _parLength) dir = control.basicDirection(); else if (_itype == TextBlockTEmoji) dir = QChar::DirCS; else if (_itype == TextBlockTSkip) dir = QChar::DirCS; else dir = QChar::direction(unicode[sor]); // Keep skipping DirBN as if it doesn't exist if (dir != QChar::DirBN) break; analysis[sor++].bidiLevel = level; } eor = sor; return dir; } // creates the next QScript items. bool eBidiItemize(QScriptAnalysis *analysis, BidiControl &control) { bool rightToLeft = (control.basicDirection() == 1); bool hasBidi = rightToLeft; int sor = 0; int eor = -1; const ushort *unicode = reinterpret_cast<const ushort*>(_t->_text.unicode()) + _parStart; int current = 0; QChar::Direction dir = rightToLeft ? QChar::DirR : QChar::DirL; BidiStatus status; Text::TextBlocks::const_iterator i = _parStartBlock, e = _t->_blocks.cend(), n = i + 1; QChar::Direction sdir; TextBlockType _stype = (*_parStartBlock)->type(); if (_stype == TextBlockTEmoji) sdir = QChar::DirCS; else if (_stype == TextBlockTSkip) sdir = QChar::DirCS; else sdir = QChar::direction(*unicode); if (sdir != QChar::DirL && sdir != QChar::DirR && sdir != QChar::DirEN && sdir != QChar::DirAN) sdir = QChar::DirON; else dir = QChar::DirON; status.eor = sdir; status.lastStrong = rightToLeft ? QChar::DirR : QChar::DirL; status.last = status.lastStrong; status.dir = sdir; while (current <= _parLength) { while (n != e && (*n)->from() <= _parStart + current) { i = n; ++n; } QChar::Direction dirCurrent; TextBlockType _itype = (*i)->type(); if (current == (int)_parLength) dirCurrent = control.basicDirection(); else if (_itype == TextBlockTEmoji) dirCurrent = QChar::DirCS; else if (_itype == TextBlockTSkip) dirCurrent = QChar::DirCS; else dirCurrent = QChar::direction(unicode[current]); switch (dirCurrent) { // embedding and overrides (X1-X9 in the BiDi specs) case QChar::DirRLE: case QChar::DirRLO: case QChar::DirLRE: case QChar::DirLRO: { bool rtl = (dirCurrent == QChar::DirRLE || dirCurrent == QChar::DirRLO); hasBidi |= rtl; bool override = (dirCurrent == QChar::DirLRO || dirCurrent == QChar::DirRLO); unsigned int level = control.level+1; if ((level%2 != 0) == rtl) ++level; if (level < _MaxBidiLevel) { eor = current-1; eAppendItems(analysis, sor, eor, control, dir); eor = current; control.embed(rtl, override); QChar::Direction edir = (rtl ? QChar::DirR : QChar::DirL); dir = status.eor = edir; status.lastStrong = edir; } break; } case QChar::DirPDF: { if (control.canPop()) { if (dir != control.direction()) { eor = current-1; eAppendItems(analysis, sor, eor, control, dir); dir = control.direction(); } eor = current; eAppendItems(analysis, sor, eor, control, dir); control.pdf(); dir = QChar::DirON; status.eor = QChar::DirON; status.last = control.direction(); if (control.override) dir = control.direction(); else dir = QChar::DirON; status.lastStrong = control.direction(); } break; } // strong types case QChar::DirL: if(dir == QChar::DirON) dir = QChar::DirL; switch(status.last) { case QChar::DirL: eor = current; status.eor = QChar::DirL; break; case QChar::DirR: case QChar::DirAL: case QChar::DirEN: case QChar::DirAN: if (eor >= 0) { eAppendItems(analysis, sor, eor, control, dir); status.eor = dir = eSkipBoundryNeutrals(analysis, unicode, sor, eor, control, i); } else { eor = current; status.eor = dir; } break; case QChar::DirES: case QChar::DirET: case QChar::DirCS: case QChar::DirBN: case QChar::DirB: case QChar::DirS: case QChar::DirWS: case QChar::DirON: if(dir != QChar::DirL) { //last stuff takes embedding dir if(control.direction() == QChar::DirR) { if(status.eor != QChar::DirR) { // AN or EN eAppendItems(analysis, sor, eor, control, dir); status.eor = QChar::DirON; dir = QChar::DirR; } eor = current - 1; eAppendItems(analysis, sor, eor, control, dir); status.eor = dir = eSkipBoundryNeutrals(analysis, unicode, sor, eor, control, i); } else { if(status.eor != QChar::DirL) { eAppendItems(analysis, sor, eor, control, dir); status.eor = QChar::DirON; dir = QChar::DirL; } else { eor = current; status.eor = QChar::DirL; break; } } } else { eor = current; status.eor = QChar::DirL; } default: break; } status.lastStrong = QChar::DirL; break; case QChar::DirAL: case QChar::DirR: hasBidi = true; if(dir == QChar::DirON) dir = QChar::DirR; switch(status.last) { case QChar::DirL: case QChar::DirEN: case QChar::DirAN: if (eor >= 0) eAppendItems(analysis, sor, eor, control, dir); // fall through case QChar::DirR: case QChar::DirAL: dir = QChar::DirR; eor = current; status.eor = QChar::DirR; break; case QChar::DirES: case QChar::DirET: case QChar::DirCS: case QChar::DirBN: case QChar::DirB: case QChar::DirS: case QChar::DirWS: case QChar::DirON: if(status.eor != QChar::DirR && status.eor != QChar::DirAL) { //last stuff takes embedding dir if(control.direction() == QChar::DirR || status.lastStrong == QChar::DirR || status.lastStrong == QChar::DirAL) { eAppendItems(analysis, sor, eor, control, dir); dir = QChar::DirR; status.eor = QChar::DirON; eor = current; } else { eor = current - 1; eAppendItems(analysis, sor, eor, control, dir); dir = QChar::DirR; status.eor = QChar::DirON; } } else { eor = current; status.eor = QChar::DirR; } default: break; } status.lastStrong = dirCurrent; break; // weak types: case QChar::DirNSM: if (eor == current-1) eor = current; break; case QChar::DirEN: // if last strong was AL change EN to AN if(status.lastStrong != QChar::DirAL) { if(dir == QChar::DirON) { if(status.lastStrong == QChar::DirL) dir = QChar::DirL; else dir = QChar::DirEN; } switch(status.last) { case QChar::DirET: if (status.lastStrong == QChar::DirR || status.lastStrong == QChar::DirAL) { eAppendItems(analysis, sor, eor, control, dir); status.eor = QChar::DirON; dir = QChar::DirAN; } [[fallthrough]]; case QChar::DirEN: case QChar::DirL: eor = current; status.eor = dirCurrent; break; case QChar::DirR: case QChar::DirAL: case QChar::DirAN: if (eor >= 0) eAppendItems(analysis, sor, eor, control, dir); else eor = current; status.eor = QChar::DirEN; dir = QChar::DirAN; break; case QChar::DirES: case QChar::DirCS: if(status.eor == QChar::DirEN || dir == QChar::DirAN) { eor = current; break; } [[fallthrough]]; case QChar::DirBN: case QChar::DirB: case QChar::DirS: case QChar::DirWS: case QChar::DirON: if(status.eor == QChar::DirR) { // neutrals go to R eor = current - 1; eAppendItems(analysis, sor, eor, control, dir); status.eor = QChar::DirEN; dir = QChar::DirAN; } else if(status.eor == QChar::DirL || (status.eor == QChar::DirEN && status.lastStrong == QChar::DirL)) { eor = current; status.eor = dirCurrent; } else { // numbers on both sides, neutrals get right to left direction if(dir != QChar::DirL) { eAppendItems(analysis, sor, eor, control, dir); status.eor = QChar::DirON; eor = current - 1; dir = QChar::DirR; eAppendItems(analysis, sor, eor, control, dir); status.eor = QChar::DirON; dir = QChar::DirAN; } else { eor = current; status.eor = dirCurrent; } } [[fallthrough]]; default: break; } break; } [[fallthrough]]; case QChar::DirAN: hasBidi = true; dirCurrent = QChar::DirAN; if(dir == QChar::DirON) dir = QChar::DirAN; switch(status.last) { case QChar::DirL: case QChar::DirAN: eor = current; status.eor = QChar::DirAN; break; case QChar::DirR: case QChar::DirAL: case QChar::DirEN: if (eor >= 0){ eAppendItems(analysis, sor, eor, control, dir); } else { eor = current; } dir = QChar::DirAN; status.eor = QChar::DirAN; break; case QChar::DirCS: if(status.eor == QChar::DirAN) { eor = current; break; } [[fallthrough]]; case QChar::DirES: case QChar::DirET: case QChar::DirBN: case QChar::DirB: case QChar::DirS: case QChar::DirWS: case QChar::DirON: if(status.eor == QChar::DirR) { // neutrals go to R eor = current - 1; eAppendItems(analysis, sor, eor, control, dir); status.eor = QChar::DirAN; dir = QChar::DirAN; } else if(status.eor == QChar::DirL || (status.eor == QChar::DirEN && status.lastStrong == QChar::DirL)) { eor = current; status.eor = dirCurrent; } else { // numbers on both sides, neutrals get right to left direction if(dir != QChar::DirL) { eAppendItems(analysis, sor, eor, control, dir); status.eor = QChar::DirON; eor = current - 1; dir = QChar::DirR; eAppendItems(analysis, sor, eor, control, dir); status.eor = QChar::DirAN; dir = QChar::DirAN; } else { eor = current; status.eor = dirCurrent; } } [[fallthrough]]; default: break; } break; case QChar::DirES: case QChar::DirCS: break; case QChar::DirET: if(status.last == QChar::DirEN) { dirCurrent = QChar::DirEN; eor = current; status.eor = dirCurrent; } break; // boundary neutrals should be ignored case QChar::DirBN: break; // neutrals case QChar::DirB: // ### what do we do with newline and paragraph separators that come to here? break; case QChar::DirS: // ### implement rule L1 break; case QChar::DirWS: case QChar::DirON: break; default: break; } if(current >= (int)_parLength) break; // set status.last as needed. switch(dirCurrent) { case QChar::DirET: case QChar::DirES: case QChar::DirCS: case QChar::DirS: case QChar::DirWS: case QChar::DirON: switch(status.last) { case QChar::DirL: case QChar::DirR: case QChar::DirAL: case QChar::DirEN: case QChar::DirAN: status.last = dirCurrent; break; default: status.last = QChar::DirON; } break; case QChar::DirNSM: case QChar::DirBN: // ignore these break; case QChar::DirLRO: case QChar::DirLRE: status.last = QChar::DirL; break; case QChar::DirRLO: case QChar::DirRLE: status.last = QChar::DirR; break; case QChar::DirEN: if (status.last == QChar::DirL) { status.last = QChar::DirL; break; } [[fallthrough]]; default: status.last = dirCurrent; } ++current; } eor = current - 1; // remove dummy char if (sor <= eor) eAppendItems(analysis, sor, eor, control, dir); return hasBidi; } private: void applyBlockProperties(ITextBlock *block) { eSetFont(block); if (_p) { if (block->lnkIndex()) { _currentPen = &_textPalette->linkFg->p; _currentPenSelected = &_textPalette->selectLinkFg->p; } else if ((block->flags() & TextBlockFCode) || (block->flags() & TextBlockFPre)) { _currentPen = &_textPalette->monoFg->p; _currentPenSelected = &_textPalette->selectMonoFg->p; } else { _currentPen = &_originalPen; _currentPenSelected = &_originalPenSelected; } } } Painter *_p = nullptr; const style::TextPalette *_textPalette = nullptr; const Text *_t = nullptr; bool _elideLast = false; bool _breakEverywhere = false; int _elideRemoveFromEnd = 0; style::align _align = style::al_topleft; QPen _originalPen; QPen _originalPenSelected; const QPen *_currentPen = nullptr; const QPen *_currentPenSelected = nullptr; int _yFrom = 0; int _yTo = 0; int _yToElide = 0; TextSelection _selection = { 0, 0 }; bool _fullWidthSelection = true; const QChar *_str = nullptr; // current paragraph data Text::TextBlocks::const_iterator _parStartBlock; Qt::LayoutDirection _parDirection; int _parStart = 0; int _parLength = 0; bool _parHasBidi = false; QVarLengthArray<QScriptAnalysis, 4096> _parAnalysis; // current line data QTextEngine *_e = nullptr; style::font _f; QFixed _x, _w, _wLeft, _last_rPadding; int32 _y, _yDelta, _lineHeight, _fontHeight; // elided hack support int _blocksSize = 0; int _elideSavedIndex = 0; std::unique_ptr<ITextBlock> _elideSavedBlock; int _lineStart = 0; int _localFrom = 0; int _lineStartBlock = 0; // link and symbol resolve QFixed _lookupX = 0; int _lookupY = 0; bool _lookupSymbol = false; bool _lookupLink = false; Text::StateRequest _lookupRequest; Text::StateResult _lookupResult; }; const TextParseOptions _defaultOptions = { TextParseLinks | TextParseMultiline, // flags 0, // maxw 0, // maxh Qt::LayoutDirectionAuto, // dir }; const TextParseOptions _textPlainOptions = { TextParseMultiline, // flags 0, // maxw 0, // maxh Qt::LayoutDirectionAuto, // dir }; Text::Text(int32 minResizeWidth) : _minResizeWidth(minResizeWidth) { } Text::Text(const style::TextStyle &st, const QString &text, const TextParseOptions &options, int32 minResizeWidth, bool richText) : _minResizeWidth(minResizeWidth) { if (richText) { setRichText(st, text, options); } else { setText(st, text, options); } } Text::Text(const Text &other) : _minResizeWidth(other._minResizeWidth) , _maxWidth(other._maxWidth) , _minHeight(other._minHeight) , _text(other._text) , _st(other._st) , _links(other._links) , _startDir(other._startDir) { _blocks.reserve(other._blocks.size()); for (auto &block : other._blocks) { _blocks.push_back(block->clone()); } } Text::Text(Text &&other) : _minResizeWidth(other._minResizeWidth) , _maxWidth(other._maxWidth) , _minHeight(other._minHeight) , _text(other._text) , _st(other._st) , _blocks(std::move(other._blocks)) , _links(other._links) , _startDir(other._startDir) { other.clearFields(); } Text &Text::operator=(const Text &other) { _minResizeWidth = other._minResizeWidth; _maxWidth = other._maxWidth; _minHeight = other._minHeight; _text = other._text; _st = other._st; _blocks = TextBlocks(other._blocks.size()); _links = other._links; _startDir = other._startDir; for (int32 i = 0, l = _blocks.size(); i < l; ++i) { _blocks[i] = other._blocks.at(i)->clone(); } return *this; } Text &Text::operator=(Text &&other) { _minResizeWidth = other._minResizeWidth; _maxWidth = other._maxWidth; _minHeight = other._minHeight; _text = other._text; _st = other._st; _blocks = std::move(other._blocks); _links = other._links; _startDir = other._startDir; other.clearFields(); return *this; } void Text::setText(const style::TextStyle &st, const QString &text, const TextParseOptions &options) { _st = &st; clear(); { TextParser parser(this, text, options); } recountNaturalSize(true, options.dir); } void Text::recountNaturalSize(bool initial, Qt::LayoutDirection optionsDir) { NewlineBlock *lastNewline = 0; _maxWidth = _minHeight = 0; int32 lineHeight = 0; int32 result = 0, lastNewlineStart = 0; QFixed _width = 0, last_rBearing = 0, last_rPadding = 0; for (auto i = _blocks.cbegin(), e = _blocks.cend(); i != e; ++i) { auto b = i->get(); auto _btype = b->type(); auto blockHeight = countBlockHeight(b, _st); if (_btype == TextBlockTNewline) { if (!lineHeight) lineHeight = blockHeight; if (initial) { Qt::LayoutDirection dir = optionsDir; if (dir == Qt::LayoutDirectionAuto) { dir = TextParser::stringDirection(_text, lastNewlineStart, b->from()); } if (lastNewline) { lastNewline->_nextDir = dir; } else { _startDir = dir; } } lastNewlineStart = b->from(); lastNewline = static_cast<NewlineBlock*>(b); _minHeight += lineHeight; lineHeight = 0; last_rBearing = b->f_rbearing(); last_rPadding = b->f_rpadding(); accumulate_max(_maxWidth, _width); _width = (b->f_width() - last_rBearing); continue; } auto b__f_rbearing = b->f_rbearing(); // cache // We need to accumulate max width after each block, because // some blocks have width less than -1 * previous right bearing. // In that cases the _width gets _smaller_ after moving to the next block. // // But when we layout block and we're sure that _maxWidth is enough // for all the blocks to fit on their line we check each block, even the // intermediate one with a large negative right bearing. accumulate_max(_maxWidth, _width); _width += last_rBearing + (last_rPadding + b->f_width() - b__f_rbearing); lineHeight = qMax(lineHeight, blockHeight); last_rBearing = b__f_rbearing; last_rPadding = b->f_rpadding(); continue; } if (initial) { Qt::LayoutDirection dir = optionsDir; if (dir == Qt::LayoutDirectionAuto) { dir = TextParser::stringDirection(_text, lastNewlineStart, _text.size()); } if (lastNewline) { lastNewline->_nextDir = dir; } else { _startDir = dir; } } if (_width > 0) { if (!lineHeight) lineHeight = countBlockHeight(_blocks.back().get(), _st); _minHeight += lineHeight; accumulate_max(_maxWidth, _width); } } void Text::setMarkedText(const style::TextStyle &st, const TextWithEntities &textWithEntities, const TextParseOptions &options) { _st = &st; clear(); { // utf codes of the text display for emoji extraction // auto text = textWithEntities.text; // auto newText = QString(); // newText.reserve(8 * text.size()); // newText.append("\t{ "); // for (const QChar *ch = text.constData(), *e = ch + text.size(); ch != e; ++ch) { // if (*ch == TextCommand) { // break; // } else if (chIsNewline(*ch)) { // newText.append("},").append(*ch).append("\t{ "); // } else { // if (ch->isHighSurrogate() || ch->isLowSurrogate()) { // if (ch->isHighSurrogate() && (ch + 1 != e) && ((ch + 1)->isLowSurrogate())) { // newText.append("0x").append(QString::number((uint32(ch->unicode()) << 16) | uint32((ch + 1)->unicode()), 16).toUpper()).append("U, "); // ++ch; // } else { // newText.append("BADx").append(QString::number(ch->unicode(), 16).toUpper()).append("U, "); // } // } else { // newText.append("0x").append(QString::number(ch->unicode(), 16).toUpper()).append("U, "); // } // } // } // newText.append("},\n\n").append(text); // TextParser parser(this, { newText, EntitiesInText() }, options); TextParser parser(this, textWithEntities, options); } recountNaturalSize(true, options.dir); } void Text::setRichText(const style::TextStyle &st, const QString &text, TextParseOptions options, const TextCustomTagsMap &custom) { QString parsed; parsed.reserve(text.size()); const QChar *s = text.constData(), *ch = s; for (const QChar *b = s, *e = b + text.size(); ch != e; ++ch) { if (ch->unicode() == '\\') { if (ch > s) parsed.append(s, ch - s); s = ch + 1; if (s < e) ++ch; continue; } if (ch->unicode() == '[') { if (ch > s) parsed.append(s, ch - s); s = ch; const QChar *tag = ch + 1; if (tag >= e) continue; bool closing = false, other = false; if (tag->unicode() == '/') { closing = true; if (++tag >= e) continue; } TextCommands cmd; switch (tag->unicode()) { case 'b': cmd = closing ? TextCommandNoBold : TextCommandBold; break; case 'i': cmd = closing ? TextCommandNoItalic : TextCommandItalic; break; case 'u': cmd = closing ? TextCommandNoUnderline : TextCommandUnderline; break; default : other = true; break; } if (!other) { if (++tag >= e || tag->unicode() != ']') continue; parsed.append(TextCommand).append(QChar(cmd)).append(TextCommand); ch = tag; s = ch + 1; continue; } if (tag->unicode() != 'a') { TextCustomTagsMap::const_iterator i = custom.constFind(*tag); if (++tag >= e || tag->unicode() != ']' || i == custom.cend()) continue; parsed.append(closing ? i->second : i->first); ch = tag; s = ch + 1; continue; } if (closing) { if (++tag >= e || tag->unicode() != ']') continue; parsed.append(textcmdStopLink()); ch = tag; s = ch + 1; continue; } if (++tag >= e || tag->unicode() != ' ') continue; while (tag < e && tag->unicode() == ' ') ++tag; if (tag + 5 < e && text.midRef(tag - b, 6) == qsl("href=\"")) { tag += 6; const QChar *tagend = tag; while (tagend < e && tagend->unicode() != '"') ++tagend; if (++tagend >= e || tagend->unicode() != ']') continue; parsed.append(textcmdStartLink(QString(tag, tagend - 1 - tag))); ch = tagend; s = ch + 1; continue; } } } if (ch > s) parsed.append(s, ch - s); s = ch; options.flags |= TextParseRichText; setText(st, parsed, options); } void Text::setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk) { if (!lnkIndex || lnkIndex > _links.size()) return; _links[lnkIndex - 1] = lnk; } bool Text::hasLinks() const { return !_links.isEmpty(); } bool Text::hasSkipBlock() const { return _blocks.empty() ? false : _blocks.back()->type() == TextBlockTSkip; } bool Text::updateSkipBlock(int width, int height) { if (!_blocks.empty() && _blocks.back()->type() == TextBlockTSkip) { const auto block = static_cast<SkipBlock*>(_blocks.back().get()); if (block->width() == width && block->height() == height) { return false; } _text.resize(block->from()); _blocks.pop_back(); } _text.push_back('_'); _blocks.push_back(std::make_unique<SkipBlock>( _st->font, _text, _text.size() - 1, width, height, 0)); recountNaturalSize(false); return true; } bool Text::removeSkipBlock() { if (_blocks.empty() || _blocks.back()->type() != TextBlockTSkip) { return false; } _text.resize(_blocks.back()->from()); _blocks.pop_back(); recountNaturalSize(false); return true; } int Text::countWidth(int width) const { if (QFixed(width) >= _maxWidth) { return _maxWidth.ceil().toInt(); } QFixed maxLineWidth = 0; enumerateLines(width, [&maxLineWidth](QFixed lineWidth, int lineHeight) { if (lineWidth > maxLineWidth) { maxLineWidth = lineWidth; } }); return maxLineWidth.ceil().toInt(); } int Text::countHeight(int width) const { if (QFixed(width) >= _maxWidth) { return _minHeight; } int result = 0; enumerateLines(width, [&result](QFixed lineWidth, int lineHeight) { result += lineHeight; }); return result; } void Text::countLineWidths(int width, QVector<int> *lineWidths) const { enumerateLines(width, [lineWidths](QFixed lineWidth, int lineHeight) { lineWidths->push_back(lineWidth.ceil().toInt()); }); } template <typename Callback> void Text::enumerateLines(int w, Callback callback) const { QFixed width = w; if (width < _minResizeWidth) width = _minResizeWidth; int lineHeight = 0; QFixed widthLeft = width, last_rBearing = 0, last_rPadding = 0; bool longWordLine = true; for (auto &b : _blocks) { auto _btype = b->type(); int blockHeight = countBlockHeight(b.get(), _st); if (_btype == TextBlockTNewline) { if (!lineHeight) lineHeight = blockHeight; callback(width - widthLeft, lineHeight); lineHeight = 0; last_rBearing = b->f_rbearing(); last_rPadding = b->f_rpadding(); widthLeft = width - (b->f_width() - last_rBearing); longWordLine = true; continue; } auto b__f_rbearing = b->f_rbearing(); auto newWidthLeft = widthLeft - last_rBearing - (last_rPadding + b->f_width() - b__f_rbearing); if (newWidthLeft >= 0) { last_rBearing = b__f_rbearing; last_rPadding = b->f_rpadding(); widthLeft = newWidthLeft; lineHeight = qMax(lineHeight, blockHeight); longWordLine = false; continue; } if (_btype == TextBlockTText) { auto t = static_cast<TextBlock*>(b.get()); if (t->_words.isEmpty()) { // no words in this block, spaces only => layout this block in the same line last_rPadding += b->f_rpadding(); lineHeight = qMax(lineHeight, blockHeight); longWordLine = false; continue; } auto f_wLeft = widthLeft; int f_lineHeight = lineHeight; for (auto j = t->_words.cbegin(), e = t->_words.cend(), f = j; j != e; ++j) { bool wordEndsHere = (j->f_width() >= 0); auto j_width = wordEndsHere ? j->f_width() : -j->f_width(); auto newWidthLeft = widthLeft - last_rBearing - (last_rPadding + j_width - j->f_rbearing()); if (newWidthLeft >= 0) { last_rBearing = j->f_rbearing(); last_rPadding = j->f_rpadding(); widthLeft = newWidthLeft; lineHeight = qMax(lineHeight, blockHeight); if (wordEndsHere) { longWordLine = false; } if (wordEndsHere || longWordLine) { f_wLeft = widthLeft; f_lineHeight = lineHeight; f = j + 1; } continue; } if (f != j) { j = f; widthLeft = f_wLeft; lineHeight = f_lineHeight; j_width = (j->f_width() >= 0) ? j->f_width() : -j->f_width(); } callback(width - widthLeft, lineHeight); lineHeight = qMax(0, blockHeight); last_rBearing = j->f_rbearing(); last_rPadding = j->f_rpadding(); widthLeft = width - (j_width - last_rBearing); longWordLine = true; f = j + 1; f_wLeft = widthLeft; f_lineHeight = lineHeight; } continue; } callback(width - widthLeft, lineHeight); lineHeight = qMax(0, blockHeight); last_rBearing = b__f_rbearing; last_rPadding = b->f_rpadding(); widthLeft = width - (b->f_width() - last_rBearing); longWordLine = true; continue; } if (widthLeft < width) { callback(width - widthLeft, lineHeight); } } void Text::draw(Painter &painter, int32 left, int32 top, int32 w, style::align align, int32 yFrom, int32 yTo, TextSelection selection, bool fullWidthSelection) const { // painter.fillRect(QRect(left, top, w, countHeight(w)), QColor(0, 0, 0, 32)); // debug TextPainter p(&painter, this); p.draw(left, top, w, align, yFrom, yTo, selection, fullWidthSelection); } void Text::drawElided(Painter &painter, int32 left, int32 top, int32 w, int32 lines, style::align align, int32 yFrom, int32 yTo, int32 removeFromEnd, bool breakEverywhere, TextSelection selection) const { // painter.fillRect(QRect(left, top, w, countHeight(w)), QColor(0, 0, 0, 32)); // debug TextPainter p(&painter, this); p.drawElided(left, top, w, align, lines, yFrom, yTo, removeFromEnd, breakEverywhere, selection); } Text::StateResult Text::getState(QPoint point, int width, StateRequest request) const { TextPainter p(0, this); return p.getState(point, width, request); } Text::StateResult Text::getStateElided(QPoint point, int width, StateRequestElided request) const { TextPainter p(0, this); return p.getStateElided(point, width, request); } TextSelection Text::adjustSelection(TextSelection selection, TextSelectType selectType) const { uint16 from = selection.from, to = selection.to; if (from < _text.size() && from <= to) { if (to > _text.size()) to = _text.size(); if (selectType == TextSelectType::Paragraphs) { if (!chIsParagraphSeparator(_text.at(from))) { while (from > 0 && !chIsParagraphSeparator(_text.at(from - 1))) { --from; } } if (to < _text.size()) { if (chIsParagraphSeparator(_text.at(to))) { ++to; } else { while (to < _text.size() && !chIsParagraphSeparator(_text.at(to))) { ++to; } } } } else if (selectType == TextSelectType::Words) { if (!chIsWordSeparator(_text.at(from))) { while (from > 0 && !chIsWordSeparator(_text.at(from - 1))) { --from; } } if (to < _text.size()) { if (chIsWordSeparator(_text.at(to))) { ++to; } else { while (to < _text.size() && !chIsWordSeparator(_text.at(to))) { ++to; } } } } } return { from, to }; } bool Text::isEmpty() const { return _blocks.empty() || _blocks[0]->type() == TextBlockTSkip; } uint16 Text::countBlockEnd(const TextBlocks::const_iterator &i, const TextBlocks::const_iterator &e) const { return (i + 1 == e) ? _text.size() : (*(i + 1))->from(); } uint16 Text::countBlockLength(const Text::TextBlocks::const_iterator &i, const Text::TextBlocks::const_iterator &e) const { return countBlockEnd(i, e) - (*i)->from(); } template <typename AppendPartCallback, typename ClickHandlerStartCallback, typename ClickHandlerFinishCallback, typename FlagsChangeCallback> void Text::enumerateText(TextSelection selection, AppendPartCallback appendPartCallback, ClickHandlerStartCallback clickHandlerStartCallback, ClickHandlerFinishCallback clickHandlerFinishCallback, FlagsChangeCallback flagsChangeCallback) const { if (isEmpty() || selection.empty()) { return; } int lnkIndex = 0; uint16 lnkFrom = 0; int32 flags = 0; for (auto i = _blocks.cbegin(), e = _blocks.cend(); true; ++i) { int blockLnkIndex = (i == e) ? 0 : (*i)->lnkIndex(); uint16 blockFrom = (i == e) ? _text.size() : (*i)->from(); int32 blockFlags = (i == e) ? 0 : (*i)->flags(); bool checkBlockFlags = (blockFrom >= selection.from) && (blockFrom <= selection.to); if (checkBlockFlags && blockFlags != flags) { flagsChangeCallback(flags, blockFlags); flags = blockFlags; } if (blockLnkIndex && !_links.at(blockLnkIndex - 1)) { // ignore empty links blockLnkIndex = 0; } if (blockLnkIndex != lnkIndex) { if (lnkIndex) { auto rangeFrom = qMax(selection.from, lnkFrom); auto rangeTo = qMin(blockFrom, selection.to); if (rangeTo > rangeFrom) { // handle click handler QStringRef r = _text.midRef(rangeFrom, rangeTo - rangeFrom); if (lnkFrom != rangeFrom || blockFrom != rangeTo) { appendPartCallback(r); } else { clickHandlerFinishCallback(r, _links.at(lnkIndex - 1)); } } } lnkIndex = blockLnkIndex; if (lnkIndex) { lnkFrom = blockFrom; clickHandlerStartCallback(); } } if (i == e || blockFrom >= selection.to) break; if ((*i)->type() == TextBlockTSkip) continue; if (!blockLnkIndex) { auto rangeFrom = qMax(selection.from, blockFrom); auto rangeTo = qMin(selection.to, uint16(blockFrom + countBlockLength(i, e))); if (rangeTo > rangeFrom) { appendPartCallback(_text.midRef(rangeFrom, rangeTo - rangeFrom)); } } } } TextWithEntities Text::originalTextWithEntities(TextSelection selection, ExpandLinksMode mode) const { TextWithEntities result; result.text.reserve(_text.size()); int lnkStart = 0, italicStart = 0, boldStart = 0, codeStart = 0, preStart = 0; auto flagsChangeCallback = [&result, &italicStart, &boldStart, &codeStart, &preStart](int32 oldFlags, int32 newFlags) { if ((oldFlags & TextBlockFItalic) && !(newFlags & TextBlockFItalic)) { // write italic result.entities.push_back(EntityInText(EntityInTextItalic, italicStart, result.text.size() - italicStart)); } else if ((newFlags & TextBlockFItalic) && !(oldFlags & TextBlockFItalic)) { italicStart = result.text.size(); } if ((oldFlags & TextBlockFSemibold) && !(newFlags & TextBlockFSemibold)) { result.entities.push_back(EntityInText(EntityInTextBold, boldStart, result.text.size() - boldStart)); } else if ((newFlags & TextBlockFSemibold) && !(oldFlags & TextBlockFSemibold)) { boldStart = result.text.size(); } if ((oldFlags & TextBlockFCode) && !(newFlags & TextBlockFCode)) { result.entities.push_back(EntityInText(EntityInTextCode, codeStart, result.text.size() - codeStart)); } else if ((newFlags & TextBlockFCode) && !(oldFlags & TextBlockFCode)) { codeStart = result.text.size(); } if ((oldFlags & TextBlockFPre) && !(newFlags & TextBlockFPre)) { result.entities.push_back(EntityInText(EntityInTextPre, preStart, result.text.size() - preStart)); } else if ((newFlags & TextBlockFPre) && !(oldFlags & TextBlockFPre)) { preStart = result.text.size(); } }; auto clickHandlerStartCallback = [&result, &lnkStart]() { lnkStart = result.text.size(); }; auto clickHandlerFinishCallback = [mode, &result, &lnkStart](const QStringRef &r, const ClickHandlerPtr &handler) { auto expanded = handler->getExpandedLinkTextWithEntities(mode, lnkStart, r); if (expanded.text.isEmpty()) { result.text += r; } else { result.text += expanded.text; } if (!expanded.entities.isEmpty()) { result.entities.append(expanded.entities); } }; auto appendPartCallback = [&result](const QStringRef &r) { result.text += r; }; enumerateText(selection, appendPartCallback, clickHandlerStartCallback, clickHandlerFinishCallback, flagsChangeCallback); return result; } QString Text::originalText(TextSelection selection, ExpandLinksMode mode) const { QString result; result.reserve(_text.size()); auto appendPartCallback = [&result](const QStringRef &r) { result += r; }; auto clickHandlerStartCallback = []() { }; auto clickHandlerFinishCallback = [mode, &result](const QStringRef &r, const ClickHandlerPtr &handler) { auto expanded = handler->getExpandedLinkText(mode, r); if (expanded.isEmpty()) { result += r; } else { result += expanded; } }; auto flagsChangeCallback = [](int32 oldFlags, int32 newFlags) { }; enumerateText(selection, appendPartCallback, clickHandlerStartCallback, clickHandlerFinishCallback, flagsChangeCallback); return result; } void Text::clear() { clearFields(); _text.clear(); } void Text::clearFields() { _blocks.clear(); _links.clear(); _maxWidth = _minHeight = 0; _startDir = Qt::LayoutDirectionAuto; } Text::~Text() = default; void emojiDraw(QPainter &p, EmojiPtr e, int x, int y) { auto size = Ui::Emoji::Size(); p.drawPixmap(QPoint(x, y), App::emoji(), QRect(e->x() * size, e->y() * size, size, size)); } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/text/text.h�������������������������������������������������0000664�0000000�0000000�00000030643�13262451251�0022167�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "private/qfontengine_p.h" #include "core/click_handler.h" #include "ui/text/text_entity.h" #include "ui/emoji_config.h" #include "base/flags.h" static const QChar TextCommand(0x0010); enum TextCommands { TextCommandBold = 0x01, TextCommandNoBold = 0x02, TextCommandItalic = 0x03, TextCommandNoItalic = 0x04, TextCommandUnderline = 0x05, TextCommandNoUnderline = 0x06, TextCommandSemibold = 0x07, TextCommandNoSemibold = 0x08, TextCommandLinkIndex = 0x09, // 0 - NoLink TextCommandLinkText = 0x0A, TextCommandSkipBlock = 0x0D, TextCommandLangTag = 0x20, }; struct TextParseOptions { int32 flags; int32 maxw; int32 maxh; Qt::LayoutDirection dir; }; extern const TextParseOptions _defaultOptions, _textPlainOptions; enum class TextSelectType { Letters = 0x01, Words = 0x02, Paragraphs = 0x03, }; struct TextSelection { constexpr TextSelection() : from(0), to(0) { } constexpr TextSelection(uint16 from, uint16 to) : from(from), to(to) { } constexpr bool empty() const { return from == to; } uint16 from; uint16 to; }; inline bool operator==(TextSelection a, TextSelection b) { return a.from == b.from && a.to == b.to; } inline bool operator!=(TextSelection a, TextSelection b) { return !(a == b); } static constexpr TextSelection AllTextSelection = { 0, 0xFFFF }; typedef QPair<QString, QString> TextCustomTag; // open str and close str typedef QMap<QChar, TextCustomTag> TextCustomTagsMap; class ITextBlock; class Text { public: Text(int32 minResizeWidth = QFIXED_MAX); Text(const style::TextStyle &st, const QString &text, const TextParseOptions &options = _defaultOptions, int32 minResizeWidth = QFIXED_MAX, bool richText = false); Text(const Text &other); Text(Text &&other); Text &operator=(const Text &other); Text &operator=(Text &&other); int countWidth(int width) const; int countHeight(int width) const; void countLineWidths(int width, QVector<int> *lineWidths) const; void setText(const style::TextStyle &st, const QString &text, const TextParseOptions &options = _defaultOptions); void setRichText(const style::TextStyle &st, const QString &text, TextParseOptions options = _defaultOptions, const TextCustomTagsMap &custom = TextCustomTagsMap()); void setMarkedText(const style::TextStyle &st, const TextWithEntities &textWithEntities, const TextParseOptions &options = _defaultOptions); void setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk); bool hasLinks() const; bool hasSkipBlock() const; bool updateSkipBlock(int width, int height); bool removeSkipBlock(); int32 maxWidth() const { return _maxWidth.ceil().toInt(); } int32 minHeight() const { return _minHeight; } void draw(Painter &p, int32 left, int32 top, int32 width, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, TextSelection selection = { 0, 0 }, bool fullWidthSelection = true) const; void drawElided(Painter &p, int32 left, int32 top, int32 width, int32 lines = 1, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, int32 removeFromEnd = 0, bool breakEverywhere = false, TextSelection selection = { 0, 0 }) const; void drawLeft(Painter &p, int32 left, int32 top, int32 width, int32 outerw, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, TextSelection selection = { 0, 0 }) const { draw(p, rtl() ? (outerw - left - width) : left, top, width, align, yFrom, yTo, selection); } void drawLeftElided(Painter &p, int32 left, int32 top, int32 width, int32 outerw, int32 lines = 1, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, int32 removeFromEnd = 0, bool breakEverywhere = false, TextSelection selection = { 0, 0 }) const { drawElided(p, rtl() ? (outerw - left - width) : left, top, width, lines, align, yFrom, yTo, removeFromEnd, breakEverywhere, selection); } void drawRight(Painter &p, int32 right, int32 top, int32 width, int32 outerw, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, TextSelection selection = { 0, 0 }) const { draw(p, rtl() ? right : (outerw - right - width), top, width, align, yFrom, yTo, selection); } void drawRightElided(Painter &p, int32 right, int32 top, int32 width, int32 outerw, int32 lines = 1, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, int32 removeFromEnd = 0, bool breakEverywhere = false, TextSelection selection = { 0, 0 }) const { drawElided(p, rtl() ? right : (outerw - right - width), top, width, lines, align, yFrom, yTo, removeFromEnd, breakEverywhere, selection); } struct StateRequest { enum class Flag { BreakEverywhere = (1 << 0), LookupSymbol = (1 << 1), LookupLink = (1 << 2), }; using Flags = base::flags<Flag>; friend inline constexpr auto is_flag_type(Flag) { return true; }; StateRequest() { } style::align align = style::al_left; Flags flags = Flag::LookupLink; }; struct StateResult { ClickHandlerPtr link; bool uponSymbol = false; bool afterSymbol = false; uint16 symbol = 0; }; StateResult getState(QPoint point, int width, StateRequest request = StateRequest()) const; StateResult getStateLeft(QPoint point, int width, int outerw, StateRequest request = StateRequest()) const { return getState(rtlpoint(point, outerw), width, request); } struct StateRequestElided : public StateRequest { StateRequestElided() { } StateRequestElided(const StateRequest &other) : StateRequest(other) { } int lines = 1; int removeFromEnd = 0; }; StateResult getStateElided(QPoint point, int width, StateRequestElided request = StateRequestElided()) const; StateResult getStateElidedLeft(QPoint point, int width, int outerw, StateRequestElided request = StateRequestElided()) const { return getStateElided(rtlpoint(point, outerw), width, request); } [[nodiscard]] TextSelection adjustSelection(TextSelection selection, TextSelectType selectType) const; bool isFullSelection(TextSelection selection) const { return (selection.from == 0) && (selection.to >= _text.size()); } bool isEmpty() const; bool isNull() const { return !_st; } int length() const { return _text.size(); } TextWithEntities originalTextWithEntities(TextSelection selection = AllTextSelection, ExpandLinksMode mode = ExpandLinksShortened) const; QString originalText(TextSelection selection = AllTextSelection, ExpandLinksMode mode = ExpandLinksShortened) const; bool lastDots(int32 dots, int32 maxdots = 3) { // hack for typing animation if (_text.size() < maxdots) return false; int32 nowDots = 0, from = _text.size() - maxdots, to = _text.size(); for (int32 i = from; i < to; ++i) { if (_text.at(i) == QChar('.')) { ++nowDots; } } if (nowDots == dots) return false; for (int32 j = from; j < from + dots; ++j) { _text[j] = QChar('.'); } for (int32 j = from + dots; j < to; ++j) { _text[j] = QChar(' '); } return true; } void clear(); ~Text(); private: using TextBlocks = std::vector<std::unique_ptr<ITextBlock>>; using TextLinks = QVector<ClickHandlerPtr>; uint16 countBlockEnd(const TextBlocks::const_iterator &i, const TextBlocks::const_iterator &e) const; uint16 countBlockLength(const Text::TextBlocks::const_iterator &i, const Text::TextBlocks::const_iterator &e) const; // Template method for originalText(), originalTextWithEntities(). template <typename AppendPartCallback, typename ClickHandlerStartCallback, typename ClickHandlerFinishCallback, typename FlagsChangeCallback> void enumerateText(TextSelection selection, AppendPartCallback appendPartCallback, ClickHandlerStartCallback clickHandlerStartCallback, ClickHandlerFinishCallback clickHandlerFinishCallback, FlagsChangeCallback flagsChangeCallback) const; // Template method for countWidth(), countHeight(), countLineWidths(). // callback(lineWidth, lineHeight) will be called for all lines with: // QFixed lineWidth, int lineHeight template <typename Callback> void enumerateLines(int w, Callback callback) const; void recountNaturalSize(bool initial, Qt::LayoutDirection optionsDir = Qt::LayoutDirectionAuto); // clear() deletes all blocks and calls this method // it is also called from move constructor / assignment operator void clearFields(); QFixed _minResizeWidth; QFixed _maxWidth = 0; int32 _minHeight = 0; QString _text; const style::TextStyle *_st = nullptr; TextBlocks _blocks; TextLinks _links; Qt::LayoutDirection _startDir = Qt::LayoutDirectionAuto; friend class TextParser; friend class TextPainter; }; inline TextSelection snapSelection(int from, int to) { return { static_cast<uint16>(snap(from, 0, 0xFFFF)), static_cast<uint16>(snap(to, 0, 0xFFFF)) }; } inline TextSelection shiftSelection(TextSelection selection, uint16 byLength) { return snapSelection(int(selection.from) + byLength, int(selection.to) + byLength); } inline TextSelection unshiftSelection(TextSelection selection, uint16 byLength) { return snapSelection(int(selection.from) - int(byLength), int(selection.to) - int(byLength)); } inline TextSelection shiftSelection(TextSelection selection, const Text &byText) { return shiftSelection(selection, byText.length()); } inline TextSelection unshiftSelection(TextSelection selection, const Text &byText) { return unshiftSelection(selection, byText.length()); } // textcmd QString textcmdSkipBlock(ushort w, ushort h); QString textcmdStartLink(ushort lnkIndex); QString textcmdStartLink(const QString &url); QString textcmdStopLink(); QString textcmdLink(ushort lnkIndex, const QString &text); QString textcmdLink(const QString &url, const QString &text); QString textcmdStartSemibold(); QString textcmdStopSemibold(); const QChar *textSkipCommand(const QChar *from, const QChar *end, bool canLink = true); inline bool chIsSpace(QChar ch, bool rich = false) { return ch.isSpace() || (ch < 32 && !(rich && ch == TextCommand)) || (ch == QChar::ParagraphSeparator) || (ch == QChar::LineSeparator) || (ch == QChar::ObjectReplacementCharacter) || (ch == QChar::CarriageReturn) || (ch == QChar::Tabulation); } inline bool chIsDiac(QChar ch) { // diac and variation selectors return (ch.category() == QChar::Mark_NonSpacing) || (ch == 1652) || (ch >= 64606 && ch <= 64611); } bool chIsBad(QChar ch); inline bool chIsTrimmed(QChar ch, bool rich = false) { return (!rich || ch != TextCommand) && (chIsSpace(ch) || chIsBad(ch)); } inline bool chReplacedBySpace(QChar ch) { // \xe2\x80[\xa8 - \xac\xad] // 8232 - 8237 // QString from1 = QString::fromUtf8("\xe2\x80\xa8"), to1 = QString::fromUtf8("\xe2\x80\xad"); // \xcc[\xb3\xbf\x8a] // 819, 831, 778 // QString bad1 = QString::fromUtf8("\xcc\xb3"), bad2 = QString::fromUtf8("\xcc\xbf"), bad3 = QString::fromUtf8("\xcc\x8a"); // [\x00\x01\x02\x07\x08\x0b-\x1f] // '\t' = 0x09 return (/*code >= 0x00 && */ch <= 0x02) || (ch >= 0x07 && ch <= 0x09) || (ch >= 0x0b && ch <= 0x1f) || (ch == 819) || (ch == 831) || (ch == 778) || (ch >= 8232 && ch <= 8237); } inline int32 chMaxDiacAfterSymbol() { return 2; } inline bool chIsNewline(QChar ch) { return (ch == QChar::LineFeed || ch == 156); } inline bool chIsLinkEnd(QChar ch) { return ch == TextCommand || chIsBad(ch) || chIsSpace(ch) || chIsNewline(ch) || ch.isLowSurrogate() || ch.isHighSurrogate(); } inline bool chIsAlmostLinkEnd(QChar ch) { switch (ch.unicode()) { case '?': case ',': case '.': case '"': case ':': case '!': case '\'': return true; default: break; } return false; } inline bool chIsWordSeparator(QChar ch) { switch (ch.unicode()) { case QChar::Space: case QChar::LineFeed: case '.': case ',': case '?': case '!': case '@': case '#': case '$': case ':': case ';': case '-': case '<': case '>': case '[': case ']': case '(': case ')': case '{': case '}': case '=': case '/': case '+': case '%': case '&': case '^': case '*': case '\'': case '"': case '`': case '~': case '|': return true; default: break; } return false; } inline bool chIsSentenceEnd(QChar ch) { switch (ch.unicode()) { case '.': case '?': case '!': return true; default: break; } return false; } inline bool chIsSentencePartEnd(QChar ch) { switch (ch.unicode()) { case ',': case ':': case ';': return true; default: break; } return false; } inline bool chIsParagraphSeparator(QChar ch) { switch (ch.unicode()) { case QChar::LineFeed: return true; default: break; } return false; } void emojiDraw(QPainter &p, EmojiPtr e, int x, int y); ���������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/text/text_block.cpp�����������������������������������������0000664�0000000�0000000�00000025564�13262451251�0023702�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/text/text_block.h" #include "core/crash_reports.h" // COPIED FROM qtextlayout.cpp AND MODIFIED namespace { struct ScriptLine { ScriptLine() : length(0), textWidth(0) { } int32 length; QFixed textWidth; }; // All members finished with "_" are internal. struct LineBreakHelper { LineBreakHelper() : glyphCount(0), maxGlyphs(INT_MAX), currentPosition(0), fontEngine(0), logClusters(0) { } ScriptLine tmpData; ScriptLine spaceData; QGlyphLayout glyphs; int glyphCount; int maxGlyphs; int currentPosition; glyph_t previousGlyph_ = 0; QFontEngine *previousFontEngine_ = nullptr; QFixed rightBearing; QFontEngine *fontEngine; const unsigned short *logClusters; inline glyph_t currentGlyph() const { Q_ASSERT(currentPosition > 0); Q_ASSERT(logClusters[currentPosition - 1] < glyphs.numGlyphs); return glyphs.glyphs[logClusters[currentPosition - 1]]; } inline void saveCurrentGlyph() { if (currentPosition > 0 && logClusters[currentPosition - 1] < glyphs.numGlyphs) { previousGlyph_ = currentGlyph(); // needed to calculate right bearing later previousFontEngine_ = fontEngine; } else { previousGlyph_ = 0; previousFontEngine_ = nullptr; } } inline void calculateRightBearing(QFontEngine *engine, glyph_t glyph) { qreal rb; engine->getGlyphBearings(glyph, 0, &rb); // We only care about negative right bearings, so we limit the range // of the bearing here so that we can assume it's negative in the rest // of the code, as well ase use QFixed(1) as a sentinel to represent // the state where we have yet to compute the right bearing. rightBearing = qMin(QFixed::fromReal(rb), QFixed(0)); } inline void calculateRightBearing() { if (currentPosition > 0 && logClusters[currentPosition - 1] < glyphs.numGlyphs) { calculateRightBearing(fontEngine, currentGlyph()); } else { rightBearing = 0; } } inline void calculateRightBearingForPreviousGlyph() { if (previousGlyph_ > 0) { calculateRightBearing(previousFontEngine_, previousGlyph_); } else { rightBearing = 0; } } // We always calculate the right bearing right before it is needed. // So we don't need caching / optimizations referred to delayed right bearing calculations. //static const QFixed RightBearingNotCalculated; //inline void resetRightBearing() //{ // rightBearing = RightBearingNotCalculated; //} // We express the negative right bearing as an absolute number // so that it can be applied to the width using addition. inline QFixed negativeRightBearing() const { //if (rightBearing == RightBearingNotCalculated) // return QFixed(0); return qAbs(rightBearing); } }; //const QFixed LineBreakHelper::RightBearingNotCalculated = QFixed(1); static inline void addNextCluster(int &pos, int end, ScriptLine &line, int &glyphCount, const QScriptItem ¤t, const unsigned short *logClusters, const QGlyphLayout &glyphs) { int glyphPosition = logClusters[pos]; do { // got to the first next cluster ++pos; ++line.length; } while (pos < end && logClusters[pos] == glyphPosition); do { // calculate the textWidth for the rest of the current cluster. if (!glyphs.attributes[glyphPosition].dontPrint) line.textWidth += glyphs.advances[glyphPosition]; ++glyphPosition; } while (glyphPosition < current.num_glyphs && !glyphs.attributes[glyphPosition].clusterStart); Q_ASSERT((pos == end && glyphPosition == current.num_glyphs) || logClusters[pos] == glyphPosition); ++glyphCount; } } // anonymous namespace class BlockParser { public: BlockParser(QTextEngine *e, TextBlock *b, QFixed minResizeWidth, int32 blockFrom, const QString &str) : block(b), eng(e), str(str) { parseWords(minResizeWidth, blockFrom); } void parseWords(QFixed minResizeWidth, int32 blockFrom) { LineBreakHelper lbh; // Helper for debugging crashes in text processing. // // auto debugChars = QString(); // debugChars.reserve(str.size() * 7); // for (const auto ch : str) { // debugChars.append( // "0x").append( // QString::number(ch.unicode(), 16).toUpper()).append( // ' '); // } // LOG(("Text: %1, chars: %2").arg(str).arg(debugChars)); int item = -1; int newItem = eng->findItem(0); style::align alignment = eng->option.alignment(); const QCharAttributes *attributes = eng->attributes(); if (!attributes) return; int end = 0; lbh.logClusters = eng->layoutData->logClustersPtr; block->_words.clear(); int wordStart = lbh.currentPosition; bool addingEachGrapheme = false; int lastGraphemeBoundaryPosition = -1; ScriptLine lastGraphemeBoundaryLine; while (newItem < eng->layoutData->items.size()) { if (newItem != item) { item = newItem; const QScriptItem ¤t = eng->layoutData->items[item]; if (!current.num_glyphs) { eng->shape(item); attributes = eng->attributes(); if (!attributes) return; lbh.logClusters = eng->layoutData->logClustersPtr; } lbh.currentPosition = current.position; end = current.position + eng->length(item); lbh.glyphs = eng->shapedGlyphs(¤t); QFontEngine *fontEngine = eng->fontEngine(current); if (lbh.fontEngine != fontEngine) { lbh.fontEngine = fontEngine; } } const QScriptItem ¤t = eng->layoutData->items[item]; if (attributes[lbh.currentPosition].whiteSpace) { while (lbh.currentPosition < end && attributes[lbh.currentPosition].whiteSpace) addNextCluster(lbh.currentPosition, end, lbh.spaceData, lbh.glyphCount, current, lbh.logClusters, lbh.glyphs); if (block->_words.isEmpty()) { block->_words.push_back(TextWord(wordStart + blockFrom, lbh.tmpData.textWidth, -lbh.negativeRightBearing())); } block->_words.back().add_rpadding(lbh.spaceData.textWidth); block->_width += lbh.spaceData.textWidth; lbh.spaceData.length = 0; lbh.spaceData.textWidth = 0; wordStart = lbh.currentPosition; addingEachGrapheme = false; lastGraphemeBoundaryPosition = -1; lastGraphemeBoundaryLine = ScriptLine(); } else { do { addNextCluster(lbh.currentPosition, end, lbh.tmpData, lbh.glyphCount, current, lbh.logClusters, lbh.glyphs); if (lbh.currentPosition >= eng->layoutData->string.length() || attributes[lbh.currentPosition].whiteSpace || isLineBreak(attributes, lbh.currentPosition)) { lbh.calculateRightBearing(); block->_words.push_back(TextWord(wordStart + blockFrom, lbh.tmpData.textWidth, -lbh.negativeRightBearing())); block->_width += lbh.tmpData.textWidth; lbh.tmpData.textWidth = 0; lbh.tmpData.length = 0; wordStart = lbh.currentPosition; break; } else if (attributes[lbh.currentPosition].graphemeBoundary) { if (!addingEachGrapheme && lbh.tmpData.textWidth > minResizeWidth) { if (lastGraphemeBoundaryPosition >= 0) { lbh.calculateRightBearingForPreviousGlyph(); block->_words.push_back(TextWord(wordStart + blockFrom, -lastGraphemeBoundaryLine.textWidth, -lbh.negativeRightBearing())); block->_width += lastGraphemeBoundaryLine.textWidth; lbh.tmpData.textWidth -= lastGraphemeBoundaryLine.textWidth; lbh.tmpData.length -= lastGraphemeBoundaryLine.length; wordStart = lastGraphemeBoundaryPosition; } addingEachGrapheme = true; } if (addingEachGrapheme) { lbh.calculateRightBearing(); block->_words.push_back(TextWord(wordStart + blockFrom, -lbh.tmpData.textWidth, -lbh.negativeRightBearing())); block->_width += lbh.tmpData.textWidth; lbh.tmpData.textWidth = 0; lbh.tmpData.length = 0; wordStart = lbh.currentPosition; } else { lastGraphemeBoundaryPosition = lbh.currentPosition; lastGraphemeBoundaryLine = lbh.tmpData; lbh.saveCurrentGlyph(); } } } while (lbh.currentPosition < end); } if (lbh.currentPosition == end) newItem = item + 1; } if (!block->_words.isEmpty()) { block->_rpadding = block->_words.back().f_rpadding(); block->_width -= block->_rpadding; block->_words.squeeze(); } } bool isLineBreak(const QCharAttributes *attributes, int32 index) { bool lineBreak = attributes[index].lineBreak; if (lineBreak && block->lnkIndex() > 0 && index > 0 && str.at(index - 1) == '/') { return false; // don't break after / in links } return lineBreak; } private: TextBlock *block; QTextEngine *eng; const QString &str; }; QFixed ITextBlock::f_rbearing() const { return (type() == TextBlockTText) ? static_cast<const TextBlock*>(this)->real_f_rbearing() : 0; } TextBlock::TextBlock(const style::font &font, const QString &str, QFixed minResizeWidth, uint16 from, uint16 length, uchar flags, uint16 lnkIndex) : ITextBlock(font, str, from, length, flags, lnkIndex) { _flags |= ((TextBlockTText & 0x0F) << 8); if (length) { style::font blockFont = font; if (!flags && lnkIndex) { // should use TextStyle lnkFlags somehow... not supported } if ((flags & TextBlockFPre) || (flags & TextBlockFCode)) { blockFont = App::monofont(); if (blockFont->size() != font->size() || blockFont->flags() != font->flags()) { blockFont = style::font(font->size(), font->flags(), blockFont->family()); } } else { if (flags & TextBlockFBold) { blockFont = blockFont->bold(); } else if (flags & TextBlockFSemibold) { blockFont = st::semiboldFont; if (blockFont->size() != font->size() || blockFont->flags() != font->flags()) { blockFont = style::font(font->size(), font->flags(), blockFont->family()); } } if (flags & TextBlockFItalic) blockFont = blockFont->italic(); if (flags & TextBlockFUnderline) blockFont = blockFont->underline(); if (flags & TextBlockFTilde) { // tilde fix in OpenSans blockFont = st::semiboldFont; } } const auto part = str.mid(_from, length); // Attempt to catch a crash in text processing CrashReports::SetAnnotationRef("CrashString", &part); QStackTextEngine engine(part, blockFont->f); BlockParser parser(&engine, this, minResizeWidth, _from, part); CrashReports::ClearAnnotationRef("CrashString"); } } EmojiBlock::EmojiBlock(const style::font &font, const QString &str, uint16 from, uint16 length, uchar flags, uint16 lnkIndex, EmojiPtr emoji) : ITextBlock(font, str, from, length, flags, lnkIndex) , emoji(emoji) { _flags |= ((TextBlockTEmoji & 0x0F) << 8); _width = int(st::emojiSize + 2 * st::emojiPadding); _rpadding = 0; for (auto i = length; i != 0;) { auto ch = str[_from + (--i)]; if (ch.unicode() == QChar::Space) { _rpadding += font->spacew; } else { break; } } } SkipBlock::SkipBlock(const style::font &font, const QString &str, uint16 from, int32 w, int32 h, uint16 lnkIndex) : ITextBlock(font, str, from, 1, 0, lnkIndex), _height(h) { _flags |= ((TextBlockTSkip & 0x0F) << 8); _width = w; } ��������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/text/text_block.h�������������������������������������������0000664�0000000�0000000�00000011207�13262451251�0023334�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "private/qfontengine_p.h" enum TextBlockType { TextBlockTNewline = 0x01, TextBlockTText = 0x02, TextBlockTEmoji = 0x03, TextBlockTSkip = 0x04, }; enum TextBlockFlags { TextBlockFBold = 0x01, TextBlockFItalic = 0x02, TextBlockFUnderline = 0x04, TextBlockFTilde = 0x08, // tilde fix in OpenSans TextBlockFSemibold = 0x10, TextBlockFCode = 0x20, TextBlockFPre = 0x40, }; class ITextBlock { public: ITextBlock(const style::font &font, const QString &str, uint16 from, uint16 length, uchar flags, uint16 lnkIndex) : _from(from), _flags((flags & 0xFF) | ((lnkIndex & 0xFFFF) << 12)) { } uint16 from() const { return _from; } int32 width() const { return _width.toInt(); } int32 rpadding() const { return _rpadding.toInt(); } QFixed f_width() const { return _width; } QFixed f_rpadding() const { return _rpadding; } // Should be virtual, but optimized throught type() call. QFixed f_rbearing() const; uint16 lnkIndex() const { return (_flags >> 12) & 0xFFFF; } void setLnkIndex(uint16 lnkIndex) { _flags = (_flags & ~(0xFFFF << 12)) | (lnkIndex << 12); } TextBlockType type() const { return TextBlockType((_flags >> 8) & 0x0F); } int32 flags() const { return (_flags & 0xFF); } virtual std::unique_ptr<ITextBlock> clone() const = 0; virtual ~ITextBlock() { } protected: uint16 _from = 0; uint32 _flags = 0; // 4 bits empty, 16 bits lnkIndex, 4 bits type, 8 bits flags QFixed _width = 0; // Right padding: spaces after the last content of the block (like a word). // This holds spaces after the end of the block, for example a text ending // with a space before a link has started. If text block has a leading spaces // (for example a text block after a link block) it is prepended with an empty // word that holds those spaces as a right padding. QFixed _rpadding = 0; }; class NewlineBlock : public ITextBlock { public: NewlineBlock(const style::font &font, const QString &str, uint16 from, uint16 length, uchar flags, uint16 lnkIndex) : ITextBlock(font, str, from, length, flags, lnkIndex), _nextDir(Qt::LayoutDirectionAuto) { _flags |= ((TextBlockTNewline & 0x0F) << 8); } Qt::LayoutDirection nextDirection() const { return _nextDir; } std::unique_ptr<ITextBlock> clone() const override { return std::make_unique<NewlineBlock>(*this); } private: Qt::LayoutDirection _nextDir; friend class Text; friend class TextParser; friend class TextPainter; }; class TextWord { public: TextWord() = default; TextWord(uint16 from, QFixed width, QFixed rbearing, QFixed rpadding = 0) : _from(from) , _width(width) , _rpadding(rpadding) , _rbearing(rbearing.value() > 0x7FFF ? 0x7FFF : (rbearing.value() < -0x7FFF ? -0x7FFF : rbearing.value())) { } uint16 from() const { return _from; } QFixed f_rbearing() const { return QFixed::fromFixed(_rbearing); } QFixed f_width() const { return _width; } QFixed f_rpadding() const { return _rpadding; } void add_rpadding(QFixed padding) { _rpadding += padding; } private: uint16 _from = 0; QFixed _width, _rpadding; int16 _rbearing = 0; }; class TextBlock : public ITextBlock { public: TextBlock(const style::font &font, const QString &str, QFixed minResizeWidth, uint16 from, uint16 length, uchar flags, uint16 lnkIndex); std::unique_ptr<ITextBlock> clone() const override { return std::make_unique<TextBlock>(*this); } private: friend class ITextBlock; QFixed real_f_rbearing() const { return _words.isEmpty() ? 0 : _words.back().f_rbearing(); } typedef QVector<TextWord> TextWords; TextWords _words; friend class Text; friend class TextParser; friend class BlockParser; friend class TextPainter; }; class EmojiBlock : public ITextBlock { public: EmojiBlock(const style::font &font, const QString &str, uint16 from, uint16 length, uchar flags, uint16 lnkIndex, EmojiPtr emoji); std::unique_ptr<ITextBlock> clone() const { return std::make_unique<EmojiBlock>(*this); } private: EmojiPtr emoji = nullptr; friend class Text; friend class TextParser; friend class TextPainter; }; class SkipBlock : public ITextBlock { public: SkipBlock(const style::font &font, const QString &str, uint16 from, int32 w, int32 h, uint16 lnkIndex); int32 height() const { return _height; } std::unique_ptr<ITextBlock> clone() const override { return std::make_unique<SkipBlock>(*this); } private: int32 _height; friend class Text; friend class TextParser; friend class TextPainter; }; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/text/text_entity.cpp����������������������������������������0000664�0000000�0000000�00000217447�13262451251�0024127�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/text/text_entity.h" #include "auth_session.h" #include "lang/lang_tag.h" namespace TextUtilities { namespace { QString ExpressionDomain() { // Matches any domain name, containing at least one '.', including "file.txt". return QString::fromUtf8("(?<![\\w\\$\\-\\_%=\\.])(?:([a-zA-Z]+)://)?((?:[A-Za-z" "\xD0\x90-\xD0\xAF\xD0\x81" "\xD0\xB0-\xD1\x8F\xD1\x91" "0-9\\-\\_]+\\.){1,10}([A-Za-z" "\xD1\x80\xD1\x84" "\\-\\d]{2,22})(\\:\\d+)?)"); } QString ExpressionDomainExplicit() { // Matches any domain name, containing a protocol, including "test://localhost". return QString::fromUtf8("(?<![\\w\\$\\-\\_%=\\.])(?:([a-zA-Z]+)://)((?:[A-Za-z" "\xD0\x90-\xD0\xAF\xD0\x81" "\xD0\xB0-\xD1\x8F\xD1\x91" "0-9\\-\\_]+\\.){0,10}([A-Za-z" "\xD1\x80\xD1\x84" "\\-\\d]{2,22})(\\:\\d+)?)"); } QString ExpressionMailNameAtEnd() { // Matches e-mail first part (before '@') at the end of the string. // First we find a domain without protocol (like "gmail.com"), then // we find '@' before it and then we look for the name before '@'. return qsl("[a-zA-Z\\-_\\.0-9]{1,256}$"); } QString ExpressionSeparators(const QString &additional) { // UTF8 quotes and ellipsis const auto quotes = QString::fromUtf8("\xC2\xAB\xC2\xBB\xE2\x80\x9C\xE2\x80\x9D\xE2\x80\x98\xE2\x80\x99\xE2\x80\xA6"); return qsl("\\s\\.,:;<>|'\"\\[\\]\\{\\}\\~\\!\\?\\%\\^\\(\\)\\-\\+=\\x10") + quotes + additional; } QString ExpressionHashtag() { return qsl("(^|[") + ExpressionSeparators(qsl("`\\*/")) + qsl("])#[\\w]{2,64}([\\W]|$)"); } QString ExpressionHashtagExclude() { return qsl("^#?\\d+$"); } QString ExpressionMention() { return qsl("(^|[") + ExpressionSeparators(qsl("`\\*/")) + qsl("])@[A-Za-z_0-9]{1,32}([\\W]|$)"); } QString ExpressionBotCommand() { return qsl("(^|[") + ExpressionSeparators(qsl("`\\*")) + qsl("])/[A-Za-z_0-9]{1,64}(@[A-Za-z_0-9]{5,32})?([\\W]|$)"); } QString ExpressionMarkdownBold() { auto separators = ExpressionSeparators(qsl("`/")); return qsl("(^|[") + separators + qsl("])(\\*\\*)[\\s\\S]+?(\\*\\*)([") + separators + qsl("]|$)"); } QString ExpressionMarkdownItalic() { auto separators = ExpressionSeparators(qsl("`\\*/")); return qsl("(^|[") + separators + qsl("])(__)[\\s\\S]+?(__)([") + separators + qsl("]|$)"); } QString ExpressionMarkdownMonoInline() { // code auto separators = ExpressionSeparators(qsl("\\*/")); return qsl("(^|[") + separators + qsl("])(`)[^\\n]+?(`)([") + separators + qsl("]|$)"); } QString ExpressionMarkdownMonoBlock() { // pre auto separators = ExpressionSeparators(qsl("\\*/")); return qsl("(^|[") + separators + qsl("])(````?)[\\s\\S]+?(````?)([") + separators + qsl("]|$)"); } QRegularExpression CreateRegExp(const QString &expression) { return QRegularExpression(expression, QRegularExpression::UseUnicodePropertiesOption); } QSet<int32> CreateValidProtocols() { auto result = QSet<int32>(); auto addOne = [&result](const QString &string) { result.insert(hashCrc32(string.constData(), string.size() * sizeof(QChar))); }; addOne(qsl("itmss")); // itunes addOne(qsl("http")); addOne(qsl("https")); addOne(qsl("ftp")); addOne(qsl("tg")); // local urls return result; } QSet<int32> CreateValidTopDomains() { auto result = QSet<int32>(); auto addOne = [&result](const QString &string) { result.insert(hashCrc32(string.constData(), string.size() * sizeof(QChar))); }; addOne(qsl("ac")); addOne(qsl("ad")); addOne(qsl("ae")); addOne(qsl("af")); addOne(qsl("ag")); addOne(qsl("ai")); addOne(qsl("al")); addOne(qsl("am")); addOne(qsl("an")); addOne(qsl("ao")); addOne(qsl("aq")); addOne(qsl("ar")); addOne(qsl("as")); addOne(qsl("at")); addOne(qsl("au")); addOne(qsl("aw")); addOne(qsl("ax")); addOne(qsl("az")); addOne(qsl("ba")); addOne(qsl("bb")); addOne(qsl("bd")); addOne(qsl("be")); addOne(qsl("bf")); addOne(qsl("bg")); addOne(qsl("bh")); addOne(qsl("bi")); addOne(qsl("bj")); addOne(qsl("bm")); addOne(qsl("bn")); addOne(qsl("bo")); addOne(qsl("br")); addOne(qsl("bs")); addOne(qsl("bt")); addOne(qsl("bv")); addOne(qsl("bw")); addOne(qsl("by")); addOne(qsl("bz")); addOne(qsl("ca")); addOne(qsl("cc")); addOne(qsl("cd")); addOne(qsl("cf")); addOne(qsl("cg")); addOne(qsl("ch")); addOne(qsl("ci")); addOne(qsl("ck")); addOne(qsl("cl")); addOne(qsl("cm")); addOne(qsl("cn")); addOne(qsl("co")); addOne(qsl("cr")); addOne(qsl("cu")); addOne(qsl("cv")); addOne(qsl("cx")); addOne(qsl("cy")); addOne(qsl("cz")); addOne(qsl("de")); addOne(qsl("dj")); addOne(qsl("dk")); addOne(qsl("dm")); addOne(qsl("do")); addOne(qsl("dz")); addOne(qsl("ec")); addOne(qsl("ee")); addOne(qsl("eg")); addOne(qsl("eh")); addOne(qsl("er")); addOne(qsl("es")); addOne(qsl("et")); addOne(qsl("eu")); addOne(qsl("fi")); addOne(qsl("fj")); addOne(qsl("fk")); addOne(qsl("fm")); addOne(qsl("fo")); addOne(qsl("fr")); addOne(qsl("ga")); addOne(qsl("gd")); addOne(qsl("ge")); addOne(qsl("gf")); addOne(qsl("gg")); addOne(qsl("gh")); addOne(qsl("gi")); addOne(qsl("gl")); addOne(qsl("gm")); addOne(qsl("gn")); addOne(qsl("gp")); addOne(qsl("gq")); addOne(qsl("gr")); addOne(qsl("gs")); addOne(qsl("gt")); addOne(qsl("gu")); addOne(qsl("gw")); addOne(qsl("gy")); addOne(qsl("hk")); addOne(qsl("hm")); addOne(qsl("hn")); addOne(qsl("hr")); addOne(qsl("ht")); addOne(qsl("hu")); addOne(qsl("id")); addOne(qsl("ie")); addOne(qsl("il")); addOne(qsl("im")); addOne(qsl("in")); addOne(qsl("io")); addOne(qsl("iq")); addOne(qsl("ir")); addOne(qsl("is")); addOne(qsl("it")); addOne(qsl("je")); addOne(qsl("jm")); addOne(qsl("jo")); addOne(qsl("jp")); addOne(qsl("ke")); addOne(qsl("kg")); addOne(qsl("kh")); addOne(qsl("ki")); addOne(qsl("km")); addOne(qsl("kn")); addOne(qsl("kp")); addOne(qsl("kr")); addOne(qsl("kw")); addOne(qsl("ky")); addOne(qsl("kz")); addOne(qsl("la")); addOne(qsl("lb")); addOne(qsl("lc")); addOne(qsl("li")); addOne(qsl("lk")); addOne(qsl("lr")); addOne(qsl("ls")); addOne(qsl("lt")); addOne(qsl("lu")); addOne(qsl("lv")); addOne(qsl("ly")); addOne(qsl("ma")); addOne(qsl("mc")); addOne(qsl("md")); addOne(qsl("me")); addOne(qsl("mg")); addOne(qsl("mh")); addOne(qsl("mk")); addOne(qsl("ml")); addOne(qsl("mm")); addOne(qsl("mn")); addOne(qsl("mo")); addOne(qsl("mp")); addOne(qsl("mq")); addOne(qsl("mr")); addOne(qsl("ms")); addOne(qsl("mt")); addOne(qsl("mu")); addOne(qsl("mv")); addOne(qsl("mw")); addOne(qsl("mx")); addOne(qsl("my")); addOne(qsl("mz")); addOne(qsl("na")); addOne(qsl("nc")); addOne(qsl("ne")); addOne(qsl("nf")); addOne(qsl("ng")); addOne(qsl("ni")); addOne(qsl("nl")); addOne(qsl("no")); addOne(qsl("np")); addOne(qsl("nr")); addOne(qsl("nu")); addOne(qsl("nz")); addOne(qsl("om")); addOne(qsl("pa")); addOne(qsl("pe")); addOne(qsl("pf")); addOne(qsl("pg")); addOne(qsl("ph")); addOne(qsl("pk")); addOne(qsl("pl")); addOne(qsl("pm")); addOne(qsl("pn")); addOne(qsl("pr")); addOne(qsl("ps")); addOne(qsl("pt")); addOne(qsl("pw")); addOne(qsl("py")); addOne(qsl("qa")); addOne(qsl("re")); addOne(qsl("ro")); addOne(qsl("ru")); addOne(qsl("rs")); addOne(qsl("rw")); addOne(qsl("sa")); addOne(qsl("sb")); addOne(qsl("sc")); addOne(qsl("sd")); addOne(qsl("se")); addOne(qsl("sg")); addOne(qsl("sh")); addOne(qsl("si")); addOne(qsl("sj")); addOne(qsl("sk")); addOne(qsl("sl")); addOne(qsl("sm")); addOne(qsl("sn")); addOne(qsl("so")); addOne(qsl("sr")); addOne(qsl("ss")); addOne(qsl("st")); addOne(qsl("su")); addOne(qsl("sv")); addOne(qsl("sx")); addOne(qsl("sy")); addOne(qsl("sz")); addOne(qsl("tc")); addOne(qsl("td")); addOne(qsl("tf")); addOne(qsl("tg")); addOne(qsl("th")); addOne(qsl("tj")); addOne(qsl("tk")); addOne(qsl("tl")); addOne(qsl("tm")); addOne(qsl("tn")); addOne(qsl("to")); addOne(qsl("tp")); addOne(qsl("tr")); addOne(qsl("tt")); addOne(qsl("tv")); addOne(qsl("tw")); addOne(qsl("tz")); addOne(qsl("ua")); addOne(qsl("ug")); addOne(qsl("uk")); addOne(qsl("um")); addOne(qsl("us")); addOne(qsl("uy")); addOne(qsl("uz")); addOne(qsl("va")); addOne(qsl("vc")); addOne(qsl("ve")); addOne(qsl("vg")); addOne(qsl("vi")); addOne(qsl("vn")); addOne(qsl("vu")); addOne(qsl("wf")); addOne(qsl("ws")); addOne(qsl("ye")); addOne(qsl("yt")); addOne(qsl("yu")); addOne(qsl("za")); addOne(qsl("zm")); addOne(qsl("zw")); addOne(qsl("arpa")); addOne(qsl("aero")); addOne(qsl("asia")); addOne(qsl("biz")); addOne(qsl("cat")); addOne(qsl("com")); addOne(qsl("coop")); addOne(qsl("info")); addOne(qsl("int")); addOne(qsl("jobs")); addOne(qsl("mobi")); addOne(qsl("museum")); addOne(qsl("name")); addOne(qsl("net")); addOne(qsl("org")); addOne(qsl("post")); addOne(qsl("pro")); addOne(qsl("tel")); addOne(qsl("travel")); addOne(qsl("xxx")); addOne(qsl("edu")); addOne(qsl("gov")); addOne(qsl("mil")); addOne(qsl("local")); addOne(qsl("xn--lgbbat1ad8j")); addOne(qsl("xn--54b7fta0cc")); addOne(qsl("xn--fiqs8s")); addOne(qsl("xn--fiqz9s")); addOne(qsl("xn--wgbh1c")); addOne(qsl("xn--node")); addOne(qsl("xn--j6w193g")); addOne(qsl("xn--h2brj9c")); addOne(qsl("xn--mgbbh1a71e")); addOne(qsl("xn--fpcrj9c3d")); addOne(qsl("xn--gecrj9c")); addOne(qsl("xn--s9brj9c")); addOne(qsl("xn--xkc2dl3a5ee0h")); addOne(qsl("xn--45brj9c")); addOne(qsl("xn--mgba3a4f16a")); addOne(qsl("xn--mgbayh7gpa")); addOne(qsl("xn--80ao21a")); addOne(qsl("xn--mgbx4cd0ab")); addOne(qsl("xn--l1acc")); addOne(qsl("xn--mgbc0a9azcg")); addOne(qsl("xn--mgb9awbf")); addOne(qsl("xn--mgbai9azgqp6j")); addOne(qsl("xn--ygbi2ammx")); addOne(qsl("xn--wgbl6a")); addOne(qsl("xn--p1ai")); addOne(qsl("xn--mgberp4a5d4ar")); addOne(qsl("xn--90a3ac")); addOne(qsl("xn--yfro4i67o")); addOne(qsl("xn--clchc0ea0b2g2a9gcd")); addOne(qsl("xn--3e0b707e")); addOne(qsl("xn--fzc2c9e2c")); addOne(qsl("xn--xkc2al3hye2a")); addOne(qsl("xn--mgbtf8fl")); addOne(qsl("xn--kprw13d")); addOne(qsl("xn--kpry57d")); addOne(qsl("xn--o3cw4h")); addOne(qsl("xn--pgbs0dh")); addOne(qsl("xn--j1amh")); addOne(qsl("xn--mgbaam7a8h")); addOne(qsl("xn--mgb2ddes")); addOne(qsl("xn--ogbpf8fl")); addOne(QString::fromUtf8("\xd1\x80\xd1\x84")); return result; } // accent char list taken from https://github.com/aristus/accent-folding inline QChar RemoveOneAccent(uint32 code) { switch (code) { case 7834: return QChar(97); case 193: return QChar(97); case 225: return QChar(97); case 192: return QChar(97); case 224: return QChar(97); case 258: return QChar(97); case 259: return QChar(97); case 7854: return QChar(97); case 7855: return QChar(97); case 7856: return QChar(97); case 7857: return QChar(97); case 7860: return QChar(97); case 7861: return QChar(97); case 7858: return QChar(97); case 7859: return QChar(97); case 194: return QChar(97); case 226: return QChar(97); case 7844: return QChar(97); case 7845: return QChar(97); case 7846: return QChar(97); case 7847: return QChar(97); case 7850: return QChar(97); case 7851: return QChar(97); case 7848: return QChar(97); case 7849: return QChar(97); case 461: return QChar(97); case 462: return QChar(97); case 197: return QChar(97); case 229: return QChar(97); case 506: return QChar(97); case 507: return QChar(97); case 196: return QChar(97); case 228: return QChar(97); case 478: return QChar(97); case 479: return QChar(97); case 195: return QChar(97); case 227: return QChar(97); case 550: return QChar(97); case 551: return QChar(97); case 480: return QChar(97); case 481: return QChar(97); case 260: return QChar(97); case 261: return QChar(97); case 256: return QChar(97); case 257: return QChar(97); case 7842: return QChar(97); case 7843: return QChar(97); case 512: return QChar(97); case 513: return QChar(97); case 514: return QChar(97); case 515: return QChar(97); case 7840: return QChar(97); case 7841: return QChar(97); case 7862: return QChar(97); case 7863: return QChar(97); case 7852: return QChar(97); case 7853: return QChar(97); case 7680: return QChar(97); case 7681: return QChar(97); case 570: return QChar(97); case 11365: return QChar(97); case 508: return QChar(97); case 509: return QChar(97); case 482: return QChar(97); case 483: return QChar(97); case 7682: return QChar(98); case 7683: return QChar(98); case 7684: return QChar(98); case 7685: return QChar(98); case 7686: return QChar(98); case 7687: return QChar(98); case 579: return QChar(98); case 384: return QChar(98); case 7532: return QChar(98); case 385: return QChar(98); case 595: return QChar(98); case 386: return QChar(98); case 387: return QChar(98); case 262: return QChar(99); case 263: return QChar(99); case 264: return QChar(99); case 265: return QChar(99); case 268: return QChar(99); case 269: return QChar(99); case 266: return QChar(99); case 267: return QChar(99); case 199: return QChar(99); case 231: return QChar(99); case 7688: return QChar(99); case 7689: return QChar(99); case 571: return QChar(99); case 572: return QChar(99); case 391: return QChar(99); case 392: return QChar(99); case 597: return QChar(99); case 270: return QChar(100); case 271: return QChar(100); case 7690: return QChar(100); case 7691: return QChar(100); case 7696: return QChar(100); case 7697: return QChar(100); case 7692: return QChar(100); case 7693: return QChar(100); case 7698: return QChar(100); case 7699: return QChar(100); case 7694: return QChar(100); case 7695: return QChar(100); case 272: return QChar(100); case 273: return QChar(100); case 7533: return QChar(100); case 393: return QChar(100); case 598: return QChar(100); case 394: return QChar(100); case 599: return QChar(100); case 395: return QChar(100); case 396: return QChar(100); case 545: return QChar(100); case 240: return QChar(100); case 201: return QChar(101); case 399: return QChar(101); case 398: return QChar(101); case 477: return QChar(101); case 233: return QChar(101); case 200: return QChar(101); case 232: return QChar(101); case 276: return QChar(101); case 277: return QChar(101); case 202: return QChar(101); case 234: return QChar(101); case 7870: return QChar(101); case 7871: return QChar(101); case 7872: return QChar(101); case 7873: return QChar(101); case 7876: return QChar(101); case 7877: return QChar(101); case 7874: return QChar(101); case 7875: return QChar(101); case 282: return QChar(101); case 283: return QChar(101); case 203: return QChar(101); case 235: return QChar(101); case 7868: return QChar(101); case 7869: return QChar(101); case 278: return QChar(101); case 279: return QChar(101); case 552: return QChar(101); case 553: return QChar(101); case 7708: return QChar(101); case 7709: return QChar(101); case 280: return QChar(101); case 281: return QChar(101); case 274: return QChar(101); case 275: return QChar(101); case 7702: return QChar(101); case 7703: return QChar(101); case 7700: return QChar(101); case 7701: return QChar(101); case 7866: return QChar(101); case 7867: return QChar(101); case 516: return QChar(101); case 517: return QChar(101); case 518: return QChar(101); case 519: return QChar(101); case 7864: return QChar(101); case 7865: return QChar(101); case 7878: return QChar(101); case 7879: return QChar(101); case 7704: return QChar(101); case 7705: return QChar(101); case 7706: return QChar(101); case 7707: return QChar(101); case 582: return QChar(101); case 583: return QChar(101); case 602: return QChar(101); case 605: return QChar(101); case 7710: return QChar(102); case 7711: return QChar(102); case 7534: return QChar(102); case 401: return QChar(102); case 402: return QChar(102); case 500: return QChar(103); case 501: return QChar(103); case 286: return QChar(103); case 287: return QChar(103); case 284: return QChar(103); case 285: return QChar(103); case 486: return QChar(103); case 487: return QChar(103); case 288: return QChar(103); case 289: return QChar(103); case 290: return QChar(103); case 291: return QChar(103); case 7712: return QChar(103); case 7713: return QChar(103); case 484: return QChar(103); case 485: return QChar(103); case 403: return QChar(103); case 608: return QChar(103); case 292: return QChar(104); case 293: return QChar(104); case 542: return QChar(104); case 543: return QChar(104); case 7718: return QChar(104); case 7719: return QChar(104); case 7714: return QChar(104); case 7715: return QChar(104); case 7720: return QChar(104); case 7721: return QChar(104); case 7716: return QChar(104); case 7717: return QChar(104); case 7722: return QChar(104); case 7723: return QChar(104); case 817: return QChar(104); case 7830: return QChar(104); case 294: return QChar(104); case 295: return QChar(104); case 11367: return QChar(104); case 11368: return QChar(104); case 205: return QChar(105); case 237: return QChar(105); case 204: return QChar(105); case 236: return QChar(105); case 300: return QChar(105); case 301: return QChar(105); case 206: return QChar(105); case 238: return QChar(105); case 463: return QChar(105); case 464: return QChar(105); case 207: return QChar(105); case 239: return QChar(105); case 7726: return QChar(105); case 7727: return QChar(105); case 296: return QChar(105); case 297: return QChar(105); case 304: return QChar(105); case 302: return QChar(105); case 303: return QChar(105); case 298: return QChar(105); case 299: return QChar(105); case 7880: return QChar(105); case 7881: return QChar(105); case 520: return QChar(105); case 521: return QChar(105); case 522: return QChar(105); case 523: return QChar(105); case 7882: return QChar(105); case 7883: return QChar(105); case 7724: return QChar(105); case 7725: return QChar(105); case 305: return QChar(105); case 407: return QChar(105); case 616: return QChar(105); case 308: return QChar(106); case 309: return QChar(106); case 780: return QChar(106); case 496: return QChar(106); case 567: return QChar(106); case 584: return QChar(106); case 585: return QChar(106); case 669: return QChar(106); case 607: return QChar(106); case 644: return QChar(106); case 7728: return QChar(107); case 7729: return QChar(107); case 488: return QChar(107); case 489: return QChar(107); case 310: return QChar(107); case 311: return QChar(107); case 7730: return QChar(107); case 7731: return QChar(107); case 7732: return QChar(107); case 7733: return QChar(107); case 408: return QChar(107); case 409: return QChar(107); case 11369: return QChar(107); case 11370: return QChar(107); case 313: return QChar(97); case 314: return QChar(108); case 317: return QChar(108); case 318: return QChar(108); case 315: return QChar(108); case 316: return QChar(108); case 7734: return QChar(108); case 7735: return QChar(108); case 7736: return QChar(108); case 7737: return QChar(108); case 7740: return QChar(108); case 7741: return QChar(108); case 7738: return QChar(108); case 7739: return QChar(108); case 321: return QChar(108); case 322: return QChar(108); case 803: return QChar(108); case 319: return QChar(108); case 320: return QChar(108); case 573: return QChar(108); case 410: return QChar(108); case 11360: return QChar(108); case 11361: return QChar(108); case 11362: return QChar(108); case 619: return QChar(108); case 620: return QChar(108); case 621: return QChar(108); case 564: return QChar(108); case 7742: return QChar(109); case 7743: return QChar(109); case 7744: return QChar(109); case 7745: return QChar(109); case 7746: return QChar(109); case 7747: return QChar(109); case 625: return QChar(109); case 323: return QChar(110); case 324: return QChar(110); case 504: return QChar(110); case 505: return QChar(110); case 327: return QChar(110); case 328: return QChar(110); case 209: return QChar(110); case 241: return QChar(110); case 7748: return QChar(110); case 7749: return QChar(110); case 325: return QChar(110); case 326: return QChar(110); case 7750: return QChar(110); case 7751: return QChar(110); case 7754: return QChar(110); case 7755: return QChar(110); case 7752: return QChar(110); case 7753: return QChar(110); case 413: return QChar(110); case 626: return QChar(110); case 544: return QChar(110); case 414: return QChar(110); case 627: return QChar(110); case 565: return QChar(110); case 776: return QChar(116); case 211: return QChar(111); case 243: return QChar(111); case 210: return QChar(111); case 242: return QChar(111); case 334: return QChar(111); case 335: return QChar(111); case 212: return QChar(111); case 244: return QChar(111); case 7888: return QChar(111); case 7889: return QChar(111); case 7890: return QChar(111); case 7891: return QChar(111); case 7894: return QChar(111); case 7895: return QChar(111); case 7892: return QChar(111); case 7893: return QChar(111); case 465: return QChar(111); case 466: return QChar(111); case 214: return QChar(111); case 246: return QChar(111); case 554: return QChar(111); case 555: return QChar(111); case 336: return QChar(111); case 337: return QChar(111); case 213: return QChar(111); case 245: return QChar(111); case 7756: return QChar(111); case 7757: return QChar(111); case 7758: return QChar(111); case 7759: return QChar(111); case 556: return QChar(111); case 557: return QChar(111); case 558: return QChar(111); case 559: return QChar(111); case 560: return QChar(111); case 561: return QChar(111); case 216: return QChar(111); case 248: return QChar(111); case 510: return QChar(111); case 511: return QChar(111); case 490: return QChar(111); case 491: return QChar(111); case 492: return QChar(111); case 493: return QChar(111); case 332: return QChar(111); case 333: return QChar(111); case 7762: return QChar(111); case 7763: return QChar(111); case 7760: return QChar(111); case 7761: return QChar(111); case 7886: return QChar(111); case 7887: return QChar(111); case 524: return QChar(111); case 525: return QChar(111); case 526: return QChar(111); case 527: return QChar(111); case 416: return QChar(111); case 417: return QChar(111); case 7898: return QChar(111); case 7899: return QChar(111); case 7900: return QChar(111); case 7901: return QChar(111); case 7904: return QChar(111); case 7905: return QChar(111); case 7902: return QChar(111); case 7903: return QChar(111); case 7906: return QChar(111); case 7907: return QChar(111); case 7884: return QChar(111); case 7885: return QChar(111); case 7896: return QChar(111); case 7897: return QChar(111); case 415: return QChar(111); case 629: return QChar(111); case 7764: return QChar(112); case 7765: return QChar(112); case 7766: return QChar(112); case 7767: return QChar(112); case 11363: return QChar(112); case 420: return QChar(112); case 421: return QChar(112); case 771: return QChar(112); case 672: return QChar(113); case 586: return QChar(113); case 587: return QChar(113); case 340: return QChar(114); case 341: return QChar(114); case 344: return QChar(114); case 345: return QChar(114); case 7768: return QChar(114); case 7769: return QChar(114); case 342: return QChar(114); case 343: return QChar(114); case 528: return QChar(114); case 529: return QChar(114); case 530: return QChar(114); case 531: return QChar(114); case 7770: return QChar(114); case 7771: return QChar(114); case 7772: return QChar(114); case 7773: return QChar(114); case 7774: return QChar(114); case 7775: return QChar(114); case 588: return QChar(114); case 589: return QChar(114); case 7538: return QChar(114); case 636: return QChar(114); case 11364: return QChar(114); case 637: return QChar(114); case 638: return QChar(114); case 7539: return QChar(114); case 223: return QChar(115); case 346: return QChar(115); case 347: return QChar(115); case 7780: return QChar(115); case 7781: return QChar(115); case 348: return QChar(115); case 349: return QChar(115); case 352: return QChar(115); case 353: return QChar(115); case 7782: return QChar(115); case 7783: return QChar(115); case 7776: return QChar(115); case 7777: return QChar(115); case 7835: return QChar(115); case 350: return QChar(115); case 351: return QChar(115); case 7778: return QChar(115); case 7779: return QChar(115); case 7784: return QChar(115); case 7785: return QChar(115); case 536: return QChar(115); case 537: return QChar(115); case 642: return QChar(115); case 809: return QChar(115); case 222: return QChar(116); case 254: return QChar(116); case 356: return QChar(116); case 357: return QChar(116); case 7831: return QChar(116); case 7786: return QChar(116); case 7787: return QChar(116); case 354: return QChar(116); case 355: return QChar(116); case 7788: return QChar(116); case 7789: return QChar(116); case 538: return QChar(116); case 539: return QChar(116); case 7792: return QChar(116); case 7793: return QChar(116); case 7790: return QChar(116); case 7791: return QChar(116); case 358: return QChar(116); case 359: return QChar(116); case 574: return QChar(116); case 11366: return QChar(116); case 7541: return QChar(116); case 427: return QChar(116); case 428: return QChar(116); case 429: return QChar(116); case 430: return QChar(116); case 648: return QChar(116); case 566: return QChar(116); case 218: return QChar(117); case 250: return QChar(117); case 217: return QChar(117); case 249: return QChar(117); case 364: return QChar(117); case 365: return QChar(117); case 219: return QChar(117); case 251: return QChar(117); case 467: return QChar(117); case 468: return QChar(117); case 366: return QChar(117); case 367: return QChar(117); case 220: return QChar(117); case 252: return QChar(117); case 471: return QChar(117); case 472: return QChar(117); case 475: return QChar(117); case 476: return QChar(117); case 473: return QChar(117); case 474: return QChar(117); case 469: return QChar(117); case 470: return QChar(117); case 368: return QChar(117); case 369: return QChar(117); case 360: return QChar(117); case 361: return QChar(117); case 7800: return QChar(117); case 7801: return QChar(117); case 370: return QChar(117); case 371: return QChar(117); case 362: return QChar(117); case 363: return QChar(117); case 7802: return QChar(117); case 7803: return QChar(117); case 7910: return QChar(117); case 7911: return QChar(117); case 532: return QChar(117); case 533: return QChar(117); case 534: return QChar(117); case 535: return QChar(117); case 431: return QChar(117); case 432: return QChar(117); case 7912: return QChar(117); case 7913: return QChar(117); case 7914: return QChar(117); case 7915: return QChar(117); case 7918: return QChar(117); case 7919: return QChar(117); case 7916: return QChar(117); case 7917: return QChar(117); case 7920: return QChar(117); case 7921: return QChar(117); case 7908: return QChar(117); case 7909: return QChar(117); case 7794: return QChar(117); case 7795: return QChar(117); case 7798: return QChar(117); case 7799: return QChar(117); case 7796: return QChar(117); case 7797: return QChar(117); case 580: return QChar(117); case 649: return QChar(117); case 7804: return QChar(118); case 7805: return QChar(118); case 7806: return QChar(118); case 7807: return QChar(118); case 434: return QChar(118); case 651: return QChar(118); case 7810: return QChar(119); case 7811: return QChar(119); case 7808: return QChar(119); case 7809: return QChar(119); case 372: return QChar(119); case 373: return QChar(119); case 778: return QChar(121); case 7832: return QChar(119); case 7812: return QChar(119); case 7813: return QChar(119); case 7814: return QChar(119); case 7815: return QChar(119); case 7816: return QChar(119); case 7817: return QChar(119); case 7820: return QChar(120); case 7821: return QChar(120); case 7818: return QChar(120); case 7819: return QChar(120); case 221: return QChar(121); case 253: return QChar(121); case 7922: return QChar(121); case 7923: return QChar(121); case 374: return QChar(121); case 375: return QChar(121); case 7833: return QChar(121); case 376: return QChar(121); case 255: return QChar(121); case 7928: return QChar(121); case 7929: return QChar(121); case 7822: return QChar(121); case 7823: return QChar(121); case 562: return QChar(121); case 563: return QChar(121); case 7926: return QChar(121); case 7927: return QChar(121); case 7924: return QChar(121); case 7925: return QChar(121); case 655: return QChar(121); case 590: return QChar(121); case 591: return QChar(121); case 435: return QChar(121); case 436: return QChar(121); case 377: return QChar(122); case 378: return QChar(122); case 7824: return QChar(122); case 7825: return QChar(122); case 381: return QChar(122); case 382: return QChar(122); case 379: return QChar(122); case 380: return QChar(122); case 7826: return QChar(122); case 7827: return QChar(122); case 7828: return QChar(122); case 7829: return QChar(122); case 437: return QChar(122); case 438: return QChar(122); case 548: return QChar(122); case 549: return QChar(122); case 656: return QChar(122); case 657: return QChar(122); case 11371: return QChar(122); case 11372: return QChar(122); case 494: return QChar(122); case 495: return QChar(122); case 442: return QChar(122); case 65298: return QChar(50); case 65302: return QChar(54); case 65314: return QChar(66); case 65318: return QChar(70); case 65322: return QChar(74); case 65326: return QChar(78); case 65330: return QChar(82); case 65334: return QChar(86); case 65338: return QChar(90); case 65346: return QChar(98); case 65350: return QChar(102); case 65354: return QChar(106); case 65358: return QChar(110); case 65362: return QChar(114); case 65366: return QChar(118); case 65370: return QChar(122); case 65297: return QChar(49); case 65301: return QChar(53); case 65305: return QChar(57); case 65313: return QChar(65); case 65317: return QChar(69); case 65321: return QChar(73); case 65325: return QChar(77); case 65329: return QChar(81); case 65333: return QChar(85); case 65337: return QChar(89); case 65345: return QChar(97); case 65349: return QChar(101); case 65353: return QChar(105); case 65357: return QChar(109); case 65361: return QChar(113); case 65365: return QChar(117); case 65369: return QChar(121); case 65296: return QChar(48); case 65300: return QChar(52); case 65304: return QChar(56); case 65316: return QChar(68); case 65320: return QChar(72); case 65324: return QChar(76); case 65328: return QChar(80); case 65332: return QChar(84); case 65336: return QChar(88); case 65348: return QChar(100); case 65352: return QChar(104); case 65356: return QChar(108); case 65360: return QChar(112); case 65364: return QChar(116); case 65368: return QChar(120); case 65299: return QChar(51); case 65303: return QChar(55); case 65315: return QChar(67); case 65319: return QChar(71); case 65323: return QChar(75); case 65327: return QChar(79); case 65331: return QChar(83); case 65335: return QChar(87); case 65347: return QChar(99); case 65351: return QChar(103); case 65355: return QChar(107); case 65359: return QChar(111); case 65363: return QChar(115); case 65367: return QChar(119); case 1105: return QChar(1077); default: break; } return QChar(0); } const QRegularExpression &RegExpWordSplit() { static const auto result = QRegularExpression (qsl("[\\@\\s\\-\\+\\(\\)\\[\\]\\{\\}\\<\\>\\,\\.\\:\\!\\_\\;\\\"\\'\\x0]")); return result; } } // namespace const QRegularExpression &RegExpDomain() { static const auto result = CreateRegExp(ExpressionDomain()); return result; } const QRegularExpression &RegExpDomainExplicit() { static const auto result = CreateRegExp(ExpressionDomainExplicit()); return result; } const QRegularExpression &RegExpMailNameAtEnd() { static const auto result = CreateRegExp(ExpressionMailNameAtEnd()); return result; } const QRegularExpression &RegExpHashtag() { static const auto result = CreateRegExp(ExpressionHashtag()); return result; } const QRegularExpression &RegExpHashtagExclude() { static const auto result = CreateRegExp(ExpressionHashtagExclude()); return result; } const QRegularExpression &RegExpMention() { static const auto result = CreateRegExp(ExpressionMention()); return result; } const QRegularExpression &RegExpBotCommand() { static const auto result = CreateRegExp(ExpressionBotCommand()); return result; } const QRegularExpression &RegExpMarkdownBold() { static const auto result = CreateRegExp(ExpressionMarkdownBold()); return result; } const QRegularExpression &RegExpMarkdownItalic() { static const auto result = CreateRegExp(ExpressionMarkdownItalic()); return result; } const QRegularExpression &RegExpMarkdownMonoInline() { static const auto result = CreateRegExp(ExpressionMarkdownMonoInline()); return result; } const QRegularExpression &RegExpMarkdownMonoBlock() { static const auto result = CreateRegExp(ExpressionMarkdownMonoBlock()); return result; } bool IsValidProtocol(const QString &protocol) { static const auto list = CreateValidProtocols(); return list.contains(hashCrc32(protocol.constData(), protocol.size() * sizeof(QChar))); } bool IsValidTopDomain(const QString &protocol) { static const auto list = CreateValidTopDomains(); return list.contains(hashCrc32(protocol.constData(), protocol.size() * sizeof(QChar))); } QString Clean(const QString &text) { auto result = text; for (auto s = text.unicode(), ch = s, e = text.unicode() + text.size(); ch != e; ++ch) { if (*ch == TextCommand) { result[int(ch - s)] = QChar::Space; } } return result; } QString EscapeForRichParsing(const QString &text) { QString result; result.reserve(text.size()); auto s = text.constData(), ch = s; for (const QChar *e = s + text.size(); ch != e; ++ch) { if (*ch == TextCommand) { if (ch > s) result.append(s, ch - s); result.append(QChar::Space); s = ch + 1; continue; } if (ch->unicode() == '\\' || ch->unicode() == '[') { if (ch > s) result.append(s, ch - s); result.append('\\'); s = ch; continue; } } if (ch > s) result.append(s, ch - s); return result; } QString SingleLine(const QString &text) { auto result = text; auto s = text.unicode(), ch = s, e = text.unicode() + text.size(); // Trim. while (s < e && chIsTrimmed(*s)) { ++s; } while (s < e && chIsTrimmed(*(e - 1))) { --e; } if (e - s != text.size()) { result = text.mid(s - text.unicode(), e - s); } for (auto ch = s; ch != e; ++ch) { if (chIsNewline(*ch) || *ch == TextCommand) { result[int(ch - s)] = QChar::Space; } } return result; } QString RemoveAccents(const QString &text) { auto result = text; auto copying = false; auto i = 0; for (auto s = text.unicode(), ch = s, e = text.unicode() + text.size(); ch != e; ++ch, ++i) { if (ch->unicode() < 128) { if (copying) result[i] = *ch; continue; } if (chIsDiac(*ch)) { copying = true; --i; continue; } if (ch->isHighSurrogate() && ch + 1 < e && (ch + 1)->isLowSurrogate()) { auto noAccent = RemoveOneAccent(QChar::surrogateToUcs4(*ch, *(ch + 1))); if (noAccent.unicode() > 0) { copying = true; result[i] = noAccent; } else { if (copying) result[i] = *ch; ++ch, ++i; if (copying) result[i] = *ch; } } else { auto noAccent = RemoveOneAccent(ch->unicode()); if (noAccent.unicode() > 0 && noAccent != *ch) { result[i] = noAccent; } else if (copying) { result[i] = *ch; } } } return (i < result.size()) ? result.mid(0, i) : result; } QStringList PrepareSearchWords(const QString &query, const QRegularExpression *SplitterOverride) { auto clean = RemoveAccents(query.trimmed().toLower()); auto result = QStringList(); if (!clean.isEmpty()) { auto list = clean.split(SplitterOverride ? *SplitterOverride : RegExpWordSplit(), QString::SkipEmptyParts); auto size = list.size(); result.reserve(list.size()); for_const (auto &word, list) { auto trimmed = word.trimmed(); if (!trimmed.isEmpty()) { result.push_back(trimmed); } } } return result; } bool CutPart(TextWithEntities &sending, TextWithEntities &left, int32 limit) { if (left.text.isEmpty() || !limit) return false; int32 currentEntity = 0, goodEntity = currentEntity, entityCount = left.entities.size(); bool goodInEntity = false, goodCanBreakEntity = false; int32 s = 0, half = limit / 2, goodLevel = 0; for (const QChar *start = left.text.constData(), *ch = start, *end = left.text.constEnd(), *good = ch; ch != end; ++ch, ++s) { while (currentEntity < entityCount && ch >= start + left.entities[currentEntity].offset() + left.entities[currentEntity].length()) { ++currentEntity; } if (s > half) { bool inEntity = (currentEntity < entityCount) && (ch > start + left.entities[currentEntity].offset()) && (ch < start + left.entities[currentEntity].offset() + left.entities[currentEntity].length()); EntityInTextType entityType = (currentEntity < entityCount) ? left.entities[currentEntity].type() : EntityInTextInvalid; bool canBreakEntity = (entityType == EntityInTextPre || entityType == EntityInTextCode); int32 noEntityLevel = inEntity ? 0 : 1; auto markGoodAsLevel = [&](int newLevel) { if (goodLevel > newLevel) { return; } goodLevel = newLevel; good = ch; goodEntity = currentEntity; goodInEntity = inEntity; goodCanBreakEntity = canBreakEntity; }; if (inEntity && !canBreakEntity) { markGoodAsLevel(0); } else { if (chIsNewline(*ch)) { if (inEntity) { if (ch + 1 < end && chIsNewline(*(ch + 1))) { markGoodAsLevel(12); } else { markGoodAsLevel(11); } } else if (ch + 1 < end && chIsNewline(*(ch + 1))) { markGoodAsLevel(15); } else if (currentEntity < entityCount && ch + 1 == start + left.entities[currentEntity].offset() && left.entities[currentEntity].type() == EntityInTextPre) { markGoodAsLevel(14); } else if (currentEntity > 0 && ch == start + left.entities[currentEntity - 1].offset() + left.entities[currentEntity - 1].length() && left.entities[currentEntity - 1].type() == EntityInTextPre) { markGoodAsLevel(14); } else { markGoodAsLevel(13); } } else if (chIsSpace(*ch)) { if (chIsSentenceEnd(*(ch - 1))) { markGoodAsLevel(9 + noEntityLevel); } else if (chIsSentencePartEnd(*(ch - 1))) { markGoodAsLevel(7 + noEntityLevel); } else { markGoodAsLevel(5 + noEntityLevel); } } else if (chIsWordSeparator(*(ch - 1))) { markGoodAsLevel(3 + noEntityLevel); } else { markGoodAsLevel(1 + noEntityLevel); } } } int elen = 0; if (auto e = Ui::Emoji::Find(ch, end, &elen)) { for (int i = 0; i < elen; ++i, ++ch, ++s) { if (ch->isHighSurrogate() && i + 1 < elen && (ch + 1)->isLowSurrogate()) { ++ch; ++i; } } --ch; --s; } else if (ch->isHighSurrogate() && ch + 1 < end && (ch + 1)->isLowSurrogate()) { ++ch; } if (s >= limit) { sending.text = left.text.mid(0, good - start); left.text = left.text.mid(good - start); if (goodInEntity) { if (goodCanBreakEntity) { sending.entities = left.entities.mid(0, goodEntity + 1); sending.entities.back().updateTextEnd(good - start); left.entities = left.entities.mid(goodEntity); for (auto &entity : left.entities) { entity.shiftLeft(good - start); } } else { sending.entities = left.entities.mid(0, goodEntity); left.entities = left.entities.mid(goodEntity + 1); } } else { sending.entities = left.entities.mid(0, goodEntity); left.entities = left.entities.mid(goodEntity); for (auto &entity : left.entities) { entity.shiftLeft(good - start); } } return true; } } sending.text = left.text; left.text = QString(); sending.entities = left.entities; left.entities = EntitiesInText(); return true; } bool textcmdStartsLink(const QChar *start, int32 len, int32 commandOffset) { if (commandOffset + 2 < len) { if (*(start + commandOffset + 1) == TextCommandLinkIndex) { return (*(start + commandOffset + 2) != 0); } return (*(start + commandOffset + 1) != TextCommandLinkText); } return false; } bool checkTagStartInCommand(const QChar *start, int32 len, int32 tagStart, int32 &commandOffset, bool &commandIsLink, bool &inLink) { bool inCommand = false; const QChar *commandEnd = start + commandOffset; while (commandOffset < len && tagStart > commandOffset) { // skip commands, evaluating are we in link or not commandEnd = textSkipCommand(start + commandOffset, start + len); if (commandEnd > start + commandOffset) { if (tagStart < (commandEnd - start)) { inCommand = true; break; } for (commandOffset = commandEnd - start; commandOffset < len; ++commandOffset) { if (*(start + commandOffset) == TextCommand) { inLink = commandIsLink; commandIsLink = textcmdStartsLink(start, len, commandOffset); break; } } if (commandOffset >= len) { inLink = commandIsLink; commandIsLink = false; } } else { break; } } if (inCommand) { commandOffset = commandEnd - start; } return inCommand; } EntitiesInText EntitiesFromMTP(const QVector<MTPMessageEntity> &entities) { auto result = EntitiesInText(); if (!entities.isEmpty()) { result.reserve(entities.size()); for_const (auto &entity, entities) { switch (entity.type()) { case mtpc_messageEntityUrl: { auto &d = entity.c_messageEntityUrl(); result.push_back(EntityInText(EntityInTextUrl, d.voffset.v, d.vlength.v)); } break; case mtpc_messageEntityTextUrl: { auto &d = entity.c_messageEntityTextUrl(); result.push_back(EntityInText(EntityInTextCustomUrl, d.voffset.v, d.vlength.v, Clean(qs(d.vurl)))); } break; case mtpc_messageEntityEmail: { auto &d = entity.c_messageEntityEmail(); result.push_back(EntityInText(EntityInTextEmail, d.voffset.v, d.vlength.v)); } break; case mtpc_messageEntityHashtag: { auto &d = entity.c_messageEntityHashtag(); result.push_back(EntityInText(EntityInTextHashtag, d.voffset.v, d.vlength.v)); } break; case mtpc_messageEntityCashtag: { auto &d = entity.c_messageEntityCashtag(); result.push_back(EntityInText(EntityInTextCashtag, d.voffset.v, d.vlength.v)); } break; case mtpc_messageEntityPhone: break; // Skipping phones. case mtpc_messageEntityMention: { auto &d = entity.c_messageEntityMention(); result.push_back(EntityInText(EntityInTextMention, d.voffset.v, d.vlength.v)); } break; case mtpc_messageEntityMentionName: { auto &d = entity.c_messageEntityMentionName(); auto data = [&d] { if (auto user = App::userLoaded(peerFromUser(d.vuser_id))) { return MentionNameDataFromFields({ d.vuser_id.v, user->accessHash() }); } return MentionNameDataFromFields(d.vuser_id.v); }; result.push_back(EntityInText(EntityInTextMentionName, d.voffset.v, d.vlength.v, data())); } break; case mtpc_inputMessageEntityMentionName: { auto &d = entity.c_inputMessageEntityMentionName(); auto data = ([&d]() -> QString { if (d.vuser_id.type() == mtpc_inputUserSelf) { return MentionNameDataFromFields(Auth().userId()); } else if (d.vuser_id.type() == mtpc_inputUser) { auto &user = d.vuser_id.c_inputUser(); return MentionNameDataFromFields({ user.vuser_id.v, user.vaccess_hash.v }); } return QString(); })(); if (!data.isEmpty()) { result.push_back(EntityInText(EntityInTextMentionName, d.voffset.v, d.vlength.v, data)); } } break; case mtpc_messageEntityBotCommand: { auto &d = entity.c_messageEntityBotCommand(); result.push_back(EntityInText(EntityInTextBotCommand, d.voffset.v, d.vlength.v)); } break; case mtpc_messageEntityBold: { auto &d = entity.c_messageEntityBold(); result.push_back(EntityInText(EntityInTextBold, d.voffset.v, d.vlength.v)); } break; case mtpc_messageEntityItalic: { auto &d = entity.c_messageEntityItalic(); result.push_back(EntityInText(EntityInTextItalic, d.voffset.v, d.vlength.v)); } break; case mtpc_messageEntityCode: { auto &d = entity.c_messageEntityCode(); result.push_back(EntityInText(EntityInTextCode, d.voffset.v, d.vlength.v)); } break; case mtpc_messageEntityPre: { auto &d = entity.c_messageEntityPre(); result.push_back(EntityInText(EntityInTextPre, d.voffset.v, d.vlength.v, Clean(qs(d.vlanguage)))); } break; } } } return result; } MTPVector<MTPMessageEntity> EntitiesToMTP(const EntitiesInText &entities, ConvertOption option) { auto v = QVector<MTPMessageEntity>(); v.reserve(entities.size()); for_const (auto &entity, entities) { if (entity.length() <= 0) continue; if (option == ConvertOption::SkipLocal && entity.type() != EntityInTextBold && entity.type() != EntityInTextItalic && entity.type() != EntityInTextCode && entity.type() != EntityInTextPre && entity.type() != EntityInTextMentionName) { continue; } auto offset = MTP_int(entity.offset()); auto length = MTP_int(entity.length()); switch (entity.type()) { case EntityInTextUrl: v.push_back(MTP_messageEntityUrl(offset, length)); break; case EntityInTextCustomUrl: v.push_back(MTP_messageEntityTextUrl(offset, length, MTP_string(entity.data()))); break; case EntityInTextEmail: v.push_back(MTP_messageEntityEmail(offset, length)); break; case EntityInTextHashtag: v.push_back(MTP_messageEntityHashtag(offset, length)); break; case EntityInTextCashtag: v.push_back(MTP_messageEntityCashtag(offset, length)); break; case EntityInTextMention: v.push_back(MTP_messageEntityMention(offset, length)); break; case EntityInTextMentionName: { auto inputUser = ([](const QString &data) -> MTPInputUser { auto fields = MentionNameDataToFields(data); if (fields.userId == Auth().userId()) { return MTP_inputUserSelf(); } else if (fields.userId) { return MTP_inputUser(MTP_int(fields.userId), MTP_long(fields.accessHash)); } return MTP_inputUserEmpty(); })(entity.data()); if (inputUser.type() != mtpc_inputUserEmpty) { v.push_back(MTP_inputMessageEntityMentionName(offset, length, inputUser)); } } break; case EntityInTextBotCommand: v.push_back(MTP_messageEntityBotCommand(offset, length)); break; case EntityInTextBold: v.push_back(MTP_messageEntityBold(offset, length)); break; case EntityInTextItalic: v.push_back(MTP_messageEntityItalic(offset, length)); break; case EntityInTextCode: v.push_back(MTP_messageEntityCode(offset, length)); break; case EntityInTextPre: v.push_back(MTP_messageEntityPre(offset, length, MTP_string(entity.data()))); break; } } return MTP_vector<MTPMessageEntity>(std::move(v)); } struct MarkdownPart { MarkdownPart() = default; MarkdownPart(EntityInTextType type) : type(type), outerStart(-1) { } EntityInTextType type = EntityInTextInvalid; int outerStart = 0; int innerStart = 0; int innerEnd = 0; int outerEnd = 0; bool addNewlineBefore = false; bool addNewlineAfter = false; }; MarkdownPart GetMarkdownPart(EntityInTextType type, const QString &text, int matchFromOffset, bool rich) { auto result = MarkdownPart(); auto regexp = [type] { switch (type) { case EntityInTextBold: return RegExpMarkdownBold(); case EntityInTextItalic: return RegExpMarkdownItalic(); case EntityInTextCode: return RegExpMarkdownMonoInline(); case EntityInTextPre: return RegExpMarkdownMonoBlock(); } Unexpected("Type in GetMardownPart()"); }; if (matchFromOffset > 1) { // If matchFromOffset is after some separator that is allowed to // start our markdown tag the tag itself will start where we want it. // So we allow to see this separator and make a match. --matchFromOffset; } auto match = regexp().match(text, matchFromOffset); if (!match.hasMatch()) { return result; } result.outerStart = match.capturedStart(); result.outerEnd = match.capturedEnd(); if (!match.capturedRef(1).isEmpty()) { ++result.outerStart; } if (!match.capturedRef(4).isEmpty()) { --result.outerEnd; } result.innerStart = result.outerStart + match.capturedLength(2); result.innerEnd = result.outerEnd - match.capturedLength(3); result.type = type; return result; } void AdjustMarkdownPrePart(MarkdownPart &result, const TextWithEntities &text, bool rich) { auto start = text.text.constData(); auto length = text.text.size(); auto lastEntityBeforeEnd = 0; auto firstEntityInsideStart = result.innerEnd; auto lastEntityInsideEnd = result.innerStart; auto firstEntityAfterStart = length; for_const (auto &entity, text.entities) { if (entity.offset() < result.outerStart) { lastEntityBeforeEnd = entity.offset() + entity.length(); } else if (entity.offset() >= result.outerEnd) { firstEntityAfterStart = entity.offset(); break; } else if (entity.offset() >= result.innerStart) { accumulate_min(firstEntityInsideStart, entity.offset()); lastEntityInsideEnd = entity.offset() + entity.length(); } } while (result.outerStart > lastEntityBeforeEnd && chIsSpace(*(start + result.outerStart - 1), rich) && !chIsNewline(*(start + result.outerStart - 1))) { --result.outerStart; } result.addNewlineBefore = (result.outerStart > 0 && !chIsNewline(*(start + result.outerStart - 1))); for (auto testInnerStart = result.innerStart; testInnerStart < firstEntityInsideStart; ++testInnerStart) { if (chIsNewline(*(start + testInnerStart))) { result.innerStart = testInnerStart + 1; break; } else if (!chIsSpace(*(start + testInnerStart))) { break; } } for (auto testInnerEnd = result.innerEnd; lastEntityInsideEnd < testInnerEnd;) { --testInnerEnd; if (chIsNewline(*(start + testInnerEnd))) { result.innerEnd = testInnerEnd; break; } else if (!chIsSpace(*(start + testInnerEnd))) { break; } } while (result.outerEnd < firstEntityAfterStart && chIsSpace(*(start + result.outerEnd)) && !chIsNewline(*(start + result.outerEnd))) { ++result.outerEnd; } result.addNewlineAfter = (result.outerEnd < length && !chIsNewline(*(start + result.outerEnd))); } void ParseMarkdown( TextWithEntities &result, const EntitiesInText &linkEntities, bool rich) { if (result.empty()) { return; } auto newResult = TextWithEntities(); MarkdownPart computedParts[4] = { { EntityInTextBold }, { EntityInTextItalic }, { EntityInTextPre }, { EntityInTextCode }, }; auto existingEntityIndex = 0; auto existingEntitiesCount = result.entities.size(); auto existingEntityShiftLeft = 0; auto copyFromOffset = 0; auto matchFromOffset = 0; auto length = result.text.size(); auto nextCommandOffset = rich ? 0 : length; auto inLink = false; auto commandIsLink = false; const auto start = result.text.constData(); for (; matchFromOffset < length;) { if (nextCommandOffset <= matchFromOffset) { for (nextCommandOffset = matchFromOffset; nextCommandOffset != length; ++nextCommandOffset) { if (*(start + nextCommandOffset) == TextCommand) { inLink = commandIsLink; commandIsLink = textcmdStartsLink(start, length, nextCommandOffset); break; } } if (nextCommandOffset >= length) { inLink = commandIsLink; commandIsLink = false; } } auto part = MarkdownPart(); auto checkType = [&part, &result, matchFromOffset, rich](MarkdownPart &computedPart) { if (computedPart.type == EntityInTextInvalid) { return; } if (matchFromOffset > computedPart.outerStart) { computedPart = GetMarkdownPart(computedPart.type, result.text, matchFromOffset, rich); if (computedPart.type == EntityInTextInvalid) { return; } } if (part.type == EntityInTextInvalid || part.outerStart > computedPart.outerStart) { part = computedPart; } }; for (auto &computedPart : computedParts) { checkType(computedPart); } if (part.type == EntityInTextInvalid) { break; } // Check if start sequence intersects a command. auto inCommand = checkTagStartInCommand( start, length, part.outerStart, nextCommandOffset, commandIsLink, inLink); if (inCommand || inLink) { matchFromOffset = nextCommandOffset; continue; } // Check if start or end sequences intersect any existing entity. auto intersectedEntityEnd = 0; for_const (auto &entity, result.entities) { if (qMin(part.innerStart, entity.offset() + entity.length()) > qMax(part.outerStart, entity.offset()) || qMin(part.outerEnd, entity.offset() + entity.length()) > qMax(part.innerEnd, entity.offset())) { intersectedEntityEnd = entity.offset() + entity.length(); break; } } // Check if any of sequence outer edges are inside a link. for_const (auto &entity, linkEntities) { const auto startIntersects = (part.outerStart >= entity.offset()) && (part.outerStart < entity.offset() + entity.length()); const auto endIntersects = (part.outerEnd > entity.offset()) && (part.outerEnd <= entity.offset() + entity.length()); if (startIntersects || endIntersects) { intersectedEntityEnd = entity.offset() + entity.length(); break; } } if (intersectedEntityEnd > 0) { matchFromOffset = qMax(part.innerStart, intersectedEntityEnd); continue; } if (part.type == EntityInTextPre) { AdjustMarkdownPrePart(part, result, rich); } if (newResult.text.isEmpty()) newResult.text.reserve(result.text.size()); for (; existingEntityIndex < existingEntitiesCount && result.entities[existingEntityIndex].offset() < part.innerStart; ++existingEntityIndex) { auto &entity = result.entities[existingEntityIndex]; newResult.entities.push_back(entity); newResult.entities.back().shiftLeft(existingEntityShiftLeft); } if (part.outerStart > copyFromOffset) { newResult.text.append(start + copyFromOffset, part.outerStart - copyFromOffset); } if (part.addNewlineBefore) newResult.text.append('\n'); existingEntityShiftLeft += (part.innerStart - part.outerStart) - (part.addNewlineBefore ? 1 : 0); auto entityStart = newResult.text.size(); auto entityLength = part.innerEnd - part.innerStart; newResult.entities.push_back(EntityInText(part.type, entityStart, entityLength)); for (; existingEntityIndex < existingEntitiesCount && result.entities[existingEntityIndex].offset() <= part.innerEnd; ++existingEntityIndex) { auto &entity = result.entities[existingEntityIndex]; newResult.entities.push_back(entity); newResult.entities.back().shiftLeft(existingEntityShiftLeft); } newResult.text.append(start + part.innerStart, entityLength); if (part.addNewlineAfter) newResult.text.append('\n'); existingEntityShiftLeft += (part.outerEnd - part.innerEnd) - (part.addNewlineAfter ? 1 : 0); copyFromOffset = matchFromOffset = part.outerEnd; } if (!newResult.empty()) { newResult.text.append(start + copyFromOffset, length - copyFromOffset); for (; existingEntityIndex < existingEntitiesCount; ++existingEntityIndex) { auto &entity = result.entities[existingEntityIndex]; newResult.entities.push_back(entity); newResult.entities.back().shiftLeft(existingEntityShiftLeft); } result = std::move(newResult); } } TextWithEntities ParseEntities(const QString &text, int32 flags) { const auto rich = ((flags & TextParseRichText) != 0); auto result = TextWithEntities{ text, EntitiesInText() }; ParseEntities(result, flags, rich); return result; } // Some code is duplicated in flattextarea.cpp! void ParseEntities(TextWithEntities &result, int32 flags, bool rich) { if (flags & TextParseMarkdown) { // parse markdown entities (bold, italic, code and pre) auto copy = TextWithEntities{ result.text, EntitiesInText() }; ParseEntities(copy, TextParseLinks, false); ParseMarkdown(result, copy.entities, rich); } constexpr auto kNotFound = std::numeric_limits<int>::max(); auto newEntities = EntitiesInText(); bool withHashtags = (flags & TextParseHashtags); bool withMentions = (flags & TextParseMentions); bool withBotCommands = (flags & TextParseBotCommands); int existingEntityIndex = 0, existingEntitiesCount = result.entities.size(); int existingEntityEnd = 0; int32 len = result.text.size(), commandOffset = rich ? 0 : len; bool inLink = false, commandIsLink = false; const QChar *start = result.text.constData(), *end = start + result.text.size(); for (int32 offset = 0, matchOffset = offset, mentionSkip = 0; offset < len;) { if (commandOffset <= offset) { for (commandOffset = offset; commandOffset < len; ++commandOffset) { if (*(start + commandOffset) == TextCommand) { inLink = commandIsLink; commandIsLink = textcmdStartsLink(start, len, commandOffset); break; } } } auto mDomain = RegExpDomain().match(result.text, matchOffset); auto mExplicitDomain = RegExpDomainExplicit().match(result.text, matchOffset); auto mHashtag = withHashtags ? RegExpHashtag().match(result.text, matchOffset) : QRegularExpressionMatch(); auto mMention = withMentions ? RegExpMention().match(result.text, qMax(mentionSkip, matchOffset)) : QRegularExpressionMatch(); auto mBotCommand = withBotCommands ? RegExpBotCommand().match(result.text, matchOffset) : QRegularExpressionMatch(); EntityInTextType lnkType = EntityInTextUrl; int32 lnkStart = 0, lnkLength = 0; auto domainStart = mDomain.hasMatch() ? mDomain.capturedStart() : kNotFound, domainEnd = mDomain.hasMatch() ? mDomain.capturedEnd() : kNotFound, explicitDomainStart = mExplicitDomain.hasMatch() ? mExplicitDomain.capturedStart() : kNotFound, explicitDomainEnd = mExplicitDomain.hasMatch() ? mExplicitDomain.capturedEnd() : kNotFound, hashtagStart = mHashtag.hasMatch() ? mHashtag.capturedStart() : kNotFound, hashtagEnd = mHashtag.hasMatch() ? mHashtag.capturedEnd() : kNotFound, mentionStart = mMention.hasMatch() ? mMention.capturedStart() : kNotFound, mentionEnd = mMention.hasMatch() ? mMention.capturedEnd() : kNotFound, botCommandStart = mBotCommand.hasMatch() ? mBotCommand.capturedStart() : kNotFound, botCommandEnd = mBotCommand.hasMatch() ? mBotCommand.capturedEnd() : kNotFound; auto hashtagIgnore = false; auto mentionIgnore = false; if (mHashtag.hasMatch()) { if (!mHashtag.capturedRef(1).isEmpty()) { ++hashtagStart; } if (!mHashtag.capturedRef(2).isEmpty()) { --hashtagEnd; } if (RegExpHashtagExclude().match( result.text.mid( hashtagStart + 1, hashtagEnd - hashtagStart - 1)).hasMatch()) { hashtagIgnore = true; } } while (mMention.hasMatch()) { if (!mMention.capturedRef(1).isEmpty()) { ++mentionStart; } if (!mMention.capturedRef(2).isEmpty()) { --mentionEnd; } if (!(start + mentionStart + 1)->isLetter() || !(start + mentionEnd - 1)->isLetterOrNumber()) { mentionSkip = mentionEnd; mMention = RegExpMention().match(result.text, qMax(mentionSkip, matchOffset)); if (mMention.hasMatch()) { mentionStart = mMention.capturedStart(); mentionEnd = mMention.capturedEnd(); } else { mentionIgnore = true; } } else { break; } } if (mBotCommand.hasMatch()) { if (!mBotCommand.capturedRef(1).isEmpty()) { ++botCommandStart; } if (!mBotCommand.capturedRef(3).isEmpty()) { --botCommandEnd; } } if (!mDomain.hasMatch() && !mExplicitDomain.hasMatch() && !mHashtag.hasMatch() && !mMention.hasMatch() && !mBotCommand.hasMatch()) { break; } if (explicitDomainStart < domainStart) { domainStart = explicitDomainStart; domainEnd = explicitDomainEnd; mDomain = mExplicitDomain; } if (mentionStart < hashtagStart && mentionStart < domainStart && mentionStart < botCommandStart) { if (mentionIgnore) { offset = matchOffset = mentionEnd; continue; } const auto inCommand = checkTagStartInCommand( start, len, mentionStart, commandOffset, commandIsLink, inLink); if (inCommand || inLink) { offset = matchOffset = commandOffset; continue; } lnkType = EntityInTextMention; lnkStart = mentionStart; lnkLength = mentionEnd - mentionStart; } else if (hashtagStart < domainStart && hashtagStart < botCommandStart) { if (hashtagIgnore) { offset = matchOffset = hashtagEnd; continue; } const auto inCommand = checkTagStartInCommand( start, len, hashtagStart, commandOffset, commandIsLink, inLink); if (inCommand || inLink) { offset = matchOffset = commandOffset; continue; } lnkType = EntityInTextHashtag; lnkStart = hashtagStart; lnkLength = hashtagEnd - hashtagStart; } else if (botCommandStart < domainStart) { const auto inCommand = checkTagStartInCommand( start, len, botCommandStart, commandOffset, commandIsLink, inLink); if (inCommand || inLink) { offset = matchOffset = commandOffset; continue; } lnkType = EntityInTextBotCommand; lnkStart = botCommandStart; lnkLength = botCommandEnd - botCommandStart; } else { const auto inCommand = checkTagStartInCommand( start, len, domainStart, commandOffset, commandIsLink, inLink); if (inCommand || inLink) { offset = matchOffset = commandOffset; continue; } auto protocol = mDomain.captured(1).toLower(); auto topDomain = mDomain.captured(3).toLower(); auto isProtocolValid = protocol.isEmpty() || IsValidProtocol(protocol); auto isTopDomainValid = !protocol.isEmpty() || IsValidTopDomain(topDomain); if (protocol.isEmpty() && domainStart > offset + 1 && *(start + domainStart - 1) == QChar('@')) { auto forMailName = result.text.mid(offset, domainStart - offset - 1); auto mMailName = RegExpMailNameAtEnd().match(forMailName); if (mMailName.hasMatch()) { auto mailStart = offset + mMailName.capturedStart(); if (mailStart < offset) { mailStart = offset; } lnkType = EntityInTextEmail; lnkStart = mailStart; lnkLength = domainEnd - mailStart; } } if (lnkType == EntityInTextUrl && !lnkLength) { if (!isProtocolValid || !isTopDomainValid) { matchOffset = domainEnd; continue; } lnkStart = domainStart; QStack<const QChar*> parenth; const QChar *domainEnd = start + mDomain.capturedEnd(), *p = domainEnd; for (; p < end; ++p) { QChar ch(*p); if (chIsLinkEnd(ch)) break; // link finished if (chIsAlmostLinkEnd(ch)) { const QChar *endTest = p + 1; while (endTest < end && chIsAlmostLinkEnd(*endTest)) { ++endTest; } if (endTest >= end || chIsLinkEnd(*endTest)) { break; // link finished at p } p = endTest; ch = *p; } if (ch == '(' || ch == '[' || ch == '{' || ch == '<') { parenth.push(p); } else if (ch == ')' || ch == ']' || ch == '}' || ch == '>') { if (parenth.isEmpty()) break; const QChar *q = parenth.pop(), open(*q); if ((ch == ')' && open != '(') || (ch == ']' && open != '[') || (ch == '}' && open != '{') || (ch == '>' && open != '<')) { p = q; break; } } } if (p > domainEnd) { // check, that domain ended if (domainEnd->unicode() != '/' && domainEnd->unicode() != '?') { matchOffset = domainEnd - start; continue; } } lnkLength = (p - start) - lnkStart; } } for (; existingEntityIndex < existingEntitiesCount && result.entities[existingEntityIndex].offset() <= lnkStart; ++existingEntityIndex) { auto &entity = result.entities[existingEntityIndex]; accumulate_max(existingEntityEnd, entity.offset() + entity.length()); newEntities.push_back(entity); } if (lnkStart >= existingEntityEnd) { result.entities.push_back(EntityInText(lnkType, lnkStart, lnkLength)); } offset = matchOffset = lnkStart + lnkLength; } if (!newEntities.isEmpty()) { for (; existingEntityIndex < existingEntitiesCount; ++existingEntityIndex) { auto &entity = result.entities[existingEntityIndex]; newEntities.push_back(entity); } result.entities = newEntities; } } QString ApplyEntities(const TextWithEntities &text) { if (text.entities.isEmpty()) return text.text; QMultiMap<int32, QString> closingTags; QMap<EntityInTextType, QString> tags; tags.insert(EntityInTextCode, qsl("`")); tags.insert(EntityInTextPre, qsl("```")); tags.insert(EntityInTextBold, qsl("**")); tags.insert(EntityInTextItalic, qsl("__")); constexpr auto kLargestOpenCloseLength = 6; QString result; int32 size = text.text.size(); const QChar *b = text.text.constData(), *already = b, *e = b + size; auto entity = text.entities.cbegin(), end = text.entities.cend(); auto skipTillRelevantAndGetTag = [&entity, &end, size, &tags] { while (entity != end) { if (entity->length() <= 0 || entity->offset() >= size) { ++entity; continue; } auto it = tags.constFind(entity->type()); if (it == tags.cend()) { ++entity; continue; } return it.value(); } return QString(); }; auto tag = skipTillRelevantAndGetTag(); while (entity != end || !closingTags.isEmpty()) { auto nextOpenEntity = (entity == end) ? (size + 1) : entity->offset(); auto nextCloseEntity = closingTags.isEmpty() ? (size + 1) : closingTags.cbegin().key(); if (nextOpenEntity <= nextCloseEntity) { if (result.isEmpty()) result.reserve(text.text.size() + text.entities.size() * kLargestOpenCloseLength); const QChar *offset = b + nextOpenEntity; if (offset > already) { result.append(already, offset - already); already = offset; } result.append(tag); closingTags.insert(qMin(entity->offset() + entity->length(), size), tag); ++entity; tag = skipTillRelevantAndGetTag(); } else { const QChar *offset = b + nextCloseEntity; if (offset > already) { result.append(already, offset - already); already = offset; } result.append(closingTags.cbegin().value()); closingTags.erase(closingTags.begin()); } } if (result.isEmpty()) { return text.text; } const QChar *offset = b + size; if (offset > already) { result.append(already, offset - already); } return result; } void MoveStringPart(TextWithEntities &result, int to, int from, int count) { if (!count) return; if (to != from) { auto start = result.text.data(); memmove(start + to, start + from, count * sizeof(QChar)); for (auto &entity : result.entities) { if (entity.offset() >= from + count) break; if (entity.offset() + entity.length() <= from) continue; if (entity.offset() >= from) { entity.extendToLeft(from - to); } if (entity.offset() + entity.length() <= from + count) { entity.shrinkFromRight(from - to); } } } } void MovePartAndGoForward(TextWithEntities &result, int &to, int &from, int count) { if (!count) return; MoveStringPart(result, to, from, count); to += count; from += count; } void ReplaceStringWithChar(const QLatin1String &from, QChar to, TextWithEntities &result, bool checkSpace = false) { Expects(from.size() > 1); auto len = from.size(), s = result.text.size(), offset = 0, length = 0; auto i = result.entities.begin(), e = result.entities.end(); for (auto start = result.text.data(); offset < s;) { auto nextOffset = result.text.indexOf(from, offset); if (nextOffset < 0) { MovePartAndGoForward(result, length, offset, s - offset); break; } if (checkSpace) { bool spaceBefore = (nextOffset > 0) && (start + nextOffset - 1)->isSpace(); bool spaceAfter = (nextOffset + len < s) && (start + nextOffset + len)->isSpace(); if (!spaceBefore && !spaceAfter) { MovePartAndGoForward(result, length, offset, nextOffset - offset + len + 1); continue; } } auto skip = false; for (; i != e; ++i) { // find and check next finishing entity if (i->offset() + i->length() > nextOffset) { skip = (i->offset() < nextOffset + len); break; } } if (skip) { MovePartAndGoForward(result, length, offset, nextOffset - offset + len); continue; } MovePartAndGoForward(result, length, offset, nextOffset - offset); *(start + length) = to; ++length; offset += len; } if (length < s) result.text.resize(length); } void PrepareForSending(TextWithEntities &result, int32 flags) { ApplyServerCleaning(result); if (flags) { ParseEntities(result, flags); } ReplaceStringWithChar(qstr("--"), QChar(8212), result, true); ReplaceStringWithChar(qstr("<<"), QChar(171), result); ReplaceStringWithChar(qstr(">>"), QChar(187), result); if (cReplaceEmojis()) { Ui::Emoji::ReplaceInText(result); } Trim(result); } // Replace bad symbols with space and remove '\r'. void ApplyServerCleaning(TextWithEntities &result) { auto len = result.text.size(); // Replace tabs with two spaces. if (auto tabs = std::count(result.text.cbegin(), result.text.cend(), '\t')) { auto replacement = qsl(" "); auto replacementLength = replacement.size(); auto shift = (replacementLength - 1); result.text.resize(len + shift * tabs); for (auto i = len, movedTill = len, to = result.text.size(); i > 0; --i) { if (result.text[i - 1] == '\t') { auto toMove = movedTill - i; to -= toMove; MoveStringPart(result, to, i, toMove); to -= replacementLength; memcpy(result.text.data() + to, replacement.constData(), replacementLength * sizeof(QChar)); movedTill = i - 1; } } len = result.text.size(); } auto to = 0; auto from = 0; auto start = result.text.data(); for (auto ch = start, end = start + len; ch < end; ++ch) { if (ch->unicode() == '\r') { MovePartAndGoForward(result, to, from, (ch - start) - from); ++from; } else if (chReplacedBySpace(*ch)) { *ch = ' '; } } MovePartAndGoForward(result, to, from, len - from); if (to < len) result.text.resize(to); } void Trim(TextWithEntities &result) { auto foundNotTrimmedChar = false; // right trim for (auto s = result.text.data(), e = s + result.text.size(), ch = e; ch != s;) { --ch; if (!chIsTrimmed(*ch)) { if (ch + 1 < e) { auto l = ch + 1 - s; for (auto &entity : result.entities) { entity.updateTextEnd(l); } result.text.resize(l); } foundNotTrimmedChar = true; break; } } if (!foundNotTrimmedChar) { result = TextWithEntities(); return; } auto firstMonospaceOffset = EntityInText::firstMonospaceOffset(result.entities, result.text.size()); // left trim for (auto s = result.text.data(), ch = s, e = s + result.text.size(); ch != e; ++ch) { if (!chIsTrimmed(*ch) || (ch - s) == firstMonospaceOffset) { if (ch > s) { auto l = ch - s; for (auto &entity : result.entities) { entity.shiftLeft(l); } result.text = result.text.mid(l); } break; } } } } // namespace TextUtilities namespace Lang { TextWithEntities ReplaceTag<TextWithEntities>::Call(TextWithEntities &&original, ushort tag, const TextWithEntities &replacement) { auto replacementPosition = FindTagReplacementPosition(original.text, tag); if (replacementPosition < 0) { return std::move(original); } auto result = TextWithEntities(); result.text = ReplaceTag<QString>::Replace(std::move(original.text), replacement.text, replacementPosition); auto originalEntitiesCount = original.entities.size(); auto replacementEntitiesCount = replacement.entities.size(); if (originalEntitiesCount != 0 || replacementEntitiesCount != 0) { result.entities.reserve(originalEntitiesCount + replacementEntitiesCount); auto replacementEnd = replacementPosition + replacement.text.size(); auto replacementEntity = replacement.entities.cbegin(); auto addReplacementEntitiesUntil = [&replacementEntity, &replacement, &result, replacementPosition, replacementEnd](int untilPosition) { while (replacementEntity != replacement.entities.cend()) { auto newOffset = replacementPosition + replacementEntity->offset(); if (newOffset >= untilPosition) { return; } auto newEnd = newOffset + replacementEntity->length(); newOffset = snap(newOffset, replacementPosition, replacementEnd); newEnd = snap(newEnd, replacementPosition, replacementEnd); if (auto newLength = newEnd - newOffset) { result.entities.push_back(EntityInText(replacementEntity->type(), newOffset, newLength, replacementEntity->data())); } ++replacementEntity; } }; for_const (auto &entity, original.entities) { // Transform the entity by the replacement. auto offset = entity.offset(); auto end = offset + entity.length(); if (offset > replacementPosition) { offset = offset + replacement.text.size() - kTagReplacementSize; } if (end > replacementPosition) { end = end + replacement.text.size() - kTagReplacementSize; } offset = snap(offset, 0, result.text.size()); end = snap(end, 0, result.text.size()); // Add all replacement entities that start before the current original entity. addReplacementEntitiesUntil(offset); // Add a modified original entity. if (auto length = end - offset) { result.entities.push_back(EntityInText(entity.type(), offset, length, entity.data())); } } // Add the remaining replacement entities. addReplacementEntitiesUntil(result.text.size()); } return result; } } // namespace Lang �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/text/text_entity.h������������������������������������������0000664�0000000�0000000�00000015234�13262451251�0023562�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once enum EntityInTextType { EntityInTextInvalid = 0, EntityInTextUrl, EntityInTextCustomUrl, EntityInTextEmail, EntityInTextHashtag, EntityInTextCashtag, EntityInTextMention, EntityInTextMentionName, EntityInTextBotCommand, EntityInTextBold, EntityInTextItalic, EntityInTextCode, // inline EntityInTextPre, // block }; class EntityInText; using EntitiesInText = QList<EntityInText>; class EntityInText { public: EntityInText(EntityInTextType type, int offset, int length, const QString &data = QString()) : _type(type) , _offset(offset) , _length(length) , _data(data) { } EntityInTextType type() const { return _type; } int offset() const { return _offset; } int length() const { return _length; } QString data() const { return _data; } void extendToLeft(int extent) { _offset -= extent; _length += extent; } void shrinkFromRight(int shrink) { _length -= shrink; } void shiftLeft(int shift) { _offset -= shift; if (_offset < 0) { _length += _offset; _offset = 0; if (_length < 0) { _length = 0; } } } void shiftRight(int shift) { _offset += shift; } void updateTextEnd(int textEnd) { if (_offset > textEnd) { _offset = textEnd; _length = 0; } else if (_offset + _length > textEnd) { _length = textEnd - _offset; } } static int firstMonospaceOffset(const EntitiesInText &entities, int textLength) { int result = textLength; for_const (auto &entity, entities) { if (entity.type() == EntityInTextPre || entity.type() == EntityInTextCode) { accumulate_min(result, entity.offset()); } } return result; } explicit operator bool() const { return type() != EntityInTextInvalid; } private: EntityInTextType _type; int _offset, _length; QString _data; }; struct TextWithEntities { QString text; EntitiesInText entities; bool empty() const { return text.isEmpty(); } }; enum { TextParseMultiline = 0x001, TextParseLinks = 0x002, TextParseRichText = 0x004, TextParseMentions = 0x008, TextParseHashtags = 0x010, TextParseBotCommands = 0x020, TextParseMarkdown = 0x040, TextTwitterMentions = 0x100, TextTwitterHashtags = 0x200, TextInstagramMentions = 0x400, TextInstagramHashtags = 0x800, }; struct TextWithTags { struct Tag { int offset, length; QString id; }; using Tags = QVector<Tag>; QString text; Tags tags; }; inline bool operator==(const TextWithTags::Tag &a, const TextWithTags::Tag &b) { return (a.offset == b.offset) && (a.length == b.length) && (a.id == b.id); } inline bool operator!=(const TextWithTags::Tag &a, const TextWithTags::Tag &b) { return !(a == b); } inline bool operator==(const TextWithTags &a, const TextWithTags &b) { return (a.text == b.text) && (a.tags == b.tags); } inline bool operator!=(const TextWithTags &a, const TextWithTags &b) { return !(a == b); } // Parsing helpers. namespace TextUtilities { bool IsValidProtocol(const QString &protocol); bool IsValidTopDomain(const QString &domain); const QRegularExpression &RegExpDomain(); const QRegularExpression &RegExpDomainExplicit(); const QRegularExpression &RegExpMailNameAtEnd(); const QRegularExpression &RegExpHashtag(); const QRegularExpression &RegExpHashtagExclude(); const QRegularExpression &RegExpMention(); const QRegularExpression &RegExpBotCommand(); const QRegularExpression &RegExpMarkdownBold(); const QRegularExpression &RegExpMarkdownItalic(); const QRegularExpression &RegExpMarkdownMonoInline(); const QRegularExpression &RegExpMarkdownMonoBlock(); inline void Append(TextWithEntities &to, TextWithEntities &&append) { auto entitiesShiftRight = to.text.size(); for (auto &entity : append.entities) { entity.shiftRight(entitiesShiftRight); } to.text += append.text; to.entities += append.entities; } // Text preprocess. QString Clean(const QString &text); QString EscapeForRichParsing(const QString &text); QString SingleLine(const QString &text); QString RemoveAccents(const QString &text); QStringList PrepareSearchWords(const QString &query, const QRegularExpression *SplitterOverride = nullptr); bool CutPart(TextWithEntities &sending, TextWithEntities &left, int limit); struct MentionNameFields { MentionNameFields(int32 userId = 0, uint64 accessHash = 0) : userId(userId), accessHash(accessHash) { } int32 userId = 0; uint64 accessHash = 0; }; inline MentionNameFields MentionNameDataToFields(const QString &data) { auto components = data.split('.'); if (!components.isEmpty()) { return { components.at(0).toInt(), (components.size() > 1) ? components.at(1).toULongLong() : 0 }; } return MentionNameFields {}; } inline QString MentionNameDataFromFields(const MentionNameFields &fields) { auto result = QString::number(fields.userId); if (fields.accessHash) { result += '.' + QString::number(fields.accessHash); } return result; } EntitiesInText EntitiesFromMTP(const QVector<MTPMessageEntity> &entities); enum class ConvertOption { WithLocal, SkipLocal, }; MTPVector<MTPMessageEntity> EntitiesToMTP(const EntitiesInText &entities, ConvertOption option = ConvertOption::WithLocal); // New entities are added to the ones that are already in result. // Changes text if (flags & TextParseMarkdown). TextWithEntities ParseEntities(const QString &text, int32 flags); void ParseEntities(TextWithEntities &result, int32 flags, bool rich = false); QString ApplyEntities(const TextWithEntities &text); void PrepareForSending(TextWithEntities &result, int32 flags); void Trim(TextWithEntities &result); enum class PrepareTextOption { IgnoreLinks, CheckLinks, }; inline QString PrepareForSending(const QString &text, PrepareTextOption option = PrepareTextOption::IgnoreLinks) { auto result = TextWithEntities { text }; auto prepareFlags = (option == PrepareTextOption::CheckLinks) ? (TextParseLinks | TextParseMentions | TextParseHashtags | TextParseBotCommands) : 0; PrepareForSending(result, prepareFlags); return result.text; } // Replace bad symbols with space and remove '\r'. void ApplyServerCleaning(TextWithEntities &result); } // namespace TextUtilities namespace Lang { template <typename ResultString> struct StartReplacements; template <> struct StartReplacements<TextWithEntities> { static inline TextWithEntities Call(QString &&langString) { return { std::move(langString), EntitiesInText() }; } }; template <typename ResultString> struct ReplaceTag; template <> struct ReplaceTag<TextWithEntities> { static TextWithEntities Call(TextWithEntities &&original, ushort tag, const TextWithEntities &replacement); }; } // namespace Lang ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/text_options.cpp��������������������������������������������0000664�0000000�0000000�00000012664�13262451251�0023314�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/text_options.h" #include "styles/style_window.h" #include "history/history.h" #include "history/history_item.h" namespace Ui { namespace { TextParseOptions HistoryTextOptions = { TextParseLinks | TextParseMentions | TextParseHashtags | TextParseMultiline | TextParseRichText | TextParseMarkdown, // flags 0, // maxw 0, // maxh Qt::LayoutDirectionAuto, // dir }; TextParseOptions HistoryBotOptions = { TextParseLinks | TextParseMentions | TextParseHashtags | TextParseBotCommands | TextParseMultiline | TextParseRichText | TextParseMarkdown, // flags 0, // maxw 0, // maxh Qt::LayoutDirectionAuto, // dir }; TextParseOptions HistoryServiceOptions = { TextParseLinks | TextParseMentions | TextParseHashtags //| TextParseMultiline | TextParseRichText, // flags 0, // maxw 0, // maxh Qt::LayoutDirectionAuto, // lang-dependent }; TextParseOptions HistoryTextNoMonoOptions = { TextParseLinks | TextParseMentions | TextParseHashtags | TextParseMultiline | TextParseRichText, // flags 0, // maxw 0, // maxh Qt::LayoutDirectionAuto, // dir }; TextParseOptions HistoryBotNoMonoOptions = { TextParseLinks | TextParseMentions | TextParseHashtags | TextParseBotCommands | TextParseMultiline | TextParseRichText, // flags 0, // maxw 0, // maxh Qt::LayoutDirectionAuto, // dir }; TextParseOptions TextNameOptions = { 0, // flags 4096, // maxw 1, // maxh Qt::LayoutDirectionAuto, // lang-dependent }; TextParseOptions TextDialogOptions = { TextParseRichText, // flags 0, // maxw is style-dependent 1, // maxh Qt::LayoutDirectionAuto, // lang-dependent }; TextParseOptions WebpageTitleOptions = { TextParseMultiline | TextParseRichText, // flags 0, // maxw 0, // maxh Qt::LayoutDirectionAuto, // dir }; TextParseOptions WebpageDescriptionOptions = { TextParseLinks | TextParseMentions | TextParseHashtags | TextParseMultiline | TextParseRichText | TextParseMarkdown, // flags 0, // maxw 0, // maxh Qt::LayoutDirectionAuto, // dir }; TextParseOptions TwitterDescriptionOptions = { TextParseLinks | TextParseMentions | TextTwitterMentions | TextParseHashtags | TextTwitterHashtags | TextParseMultiline | TextParseRichText, // flags 0, // maxw 0, // maxh Qt::LayoutDirectionAuto, // dir }; TextParseOptions InstagramDescriptionOptions = { TextParseLinks | TextParseMentions | TextInstagramMentions | TextParseHashtags | TextInstagramHashtags | TextParseMultiline | TextParseRichText, // flags 0, // maxw 0, // maxh Qt::LayoutDirectionAuto, // dir }; bool UseBotTextOptions( not_null<History*> history, not_null<PeerData*> author) { if (const auto user = history->peer->asUser()) { if (user->botInfo) { return true; } } else if (const auto chat = history->peer->asChat()) { if (chat->botStatus >= 0) { return true; } } else if (const auto group = history->peer->asMegagroup()) { if (group->mgInfo->botStatus >= 0) { return true; } } if (const auto user = author->asUser()) { if (user->botInfo) { return true; } } return false; } } // namespace void InitTextOptions() { HistoryServiceOptions.dir = TextNameOptions.dir = TextDialogOptions.dir = cLangDir(); TextDialogOptions.maxw = st::columnMaximalWidthLeft * 2; WebpageTitleOptions.maxh = st::webPageTitleFont->height * 2; WebpageTitleOptions.maxw = WebpageDescriptionOptions.maxw = TwitterDescriptionOptions.maxw = InstagramDescriptionOptions.maxw = st::msgMaxWidth - st::msgPadding.left() - st::webPageLeft - st::msgPadding.right(); WebpageDescriptionOptions.maxh = st::webPageDescriptionFont->height * 3; } const TextParseOptions &ItemTextDefaultOptions() { return HistoryTextOptions; } const TextParseOptions &ItemTextBotDefaultOptions() { return HistoryBotOptions; } const TextParseOptions &ItemTextNoMonoOptions() { return HistoryTextNoMonoOptions; } const TextParseOptions &ItemTextBotNoMonoOptions() { return HistoryBotNoMonoOptions; } const TextParseOptions &ItemTextServiceOptions() { return HistoryServiceOptions; } const TextParseOptions &WebpageTextTitleOptions() { return WebpageTitleOptions; } const TextParseOptions &WebpageTextDescriptionOptions( const QString &siteName) { if (siteName == qstr("Twitter")) { return TwitterDescriptionOptions; } else if (siteName == qstr("Instagram")) { return InstagramDescriptionOptions; } return WebpageDescriptionOptions; } const TextParseOptions &NameTextOptions() { return TextNameOptions; } const TextParseOptions &DialogTextOptions() { return TextDialogOptions; } const TextParseOptions &ItemTextOptions( not_null<History*> history, not_null<PeerData*> author) { return UseBotTextOptions(history, author) ? HistoryBotOptions : HistoryTextOptions; } const TextParseOptions &ItemTextOptions(not_null<const HistoryItem*> item) { return ItemTextOptions(item->history(), item->author()); } const TextParseOptions &ItemTextNoMonoOptions( not_null<History*> history, not_null<PeerData*> author) { return UseBotTextOptions(history, author) ? HistoryBotNoMonoOptions : HistoryTextNoMonoOptions; } const TextParseOptions &ItemTextNoMonoOptions( not_null<const HistoryItem*> item) { return ItemTextNoMonoOptions(item->history(), item->author()); } } // namespace Ui ����������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/text_options.h����������������������������������������������0000664�0000000�0000000�00000002220�13262451251�0022744�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace Ui { void InitTextOptions(); const TextParseOptions &ItemTextDefaultOptions(); const TextParseOptions &ItemTextBotDefaultOptions(); const TextParseOptions &ItemTextNoMonoOptions(); const TextParseOptions &ItemTextBotNoMonoOptions(); const TextParseOptions &ItemTextServiceOptions(); const TextParseOptions &WebpageTextTitleOptions(); const TextParseOptions &WebpageTextDescriptionOptions( const QString &siteName = QString()); const TextParseOptions &NameTextOptions(); const TextParseOptions &DialogTextOptions(); const TextParseOptions &ItemTextOptions( not_null<History*> history, not_null<PeerData*> author); const TextParseOptions &ItemTextNoMonoOptions( not_null<History*> history, not_null<PeerData*> author); const TextParseOptions &ItemTextOptions(not_null<const HistoryItem*> item); const TextParseOptions &ItemTextNoMonoOptions(not_null<const HistoryItem*> item); } // namespace Ui ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/toast/������������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0021172�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/toast/toast.cpp���������������������������������������������0000664�0000000�0000000�00000003046�13262451251�0023033�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/toast/toast.h" #include "ui/toast/toast_manager.h" #include "ui/toast/toast_widget.h" #include "mainwindow.h" namespace Ui { namespace Toast { Instance::Instance(const Config &config, QWidget *widgetParent, const Private &) : _hideAtMs(getms(true) + config.durationMs) { _widget = std::make_unique<internal::Widget>(widgetParent, config); _a_opacity.start([this] { opacityAnimationCallback(); }, 0., 1., st::toastFadeInDuration); } void Show(QWidget *parent, const Config &config) { if (auto manager = internal::Manager::instance(parent)) { auto toast = std::make_unique<Instance>(config, parent, Instance::Private()); manager->addToast(std::move(toast)); } } void Show(const Config &config) { if (auto window = App::wnd()) { Show(window->bodyWidget(), config); } } void Show(const QString &text) { Config toast; toast.text = text; Show(toast); } void Instance::opacityAnimationCallback() { _widget->setShownLevel(_a_opacity.current(_hiding ? 0. : 1.)); _widget->update(); if (!_a_opacity.animating()) { if (_hiding) { hide(); } } } void Instance::hideAnimated() { _hiding = true; _a_opacity.start([this] { opacityAnimationCallback(); }, 1., 0., st::toastFadeOutDuration); } void Instance::hide() { _widget->hide(); _widget->deleteLater(); } } // namespace Toast } // namespace Ui ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/toast/toast.h�����������������������������������������������0000664�0000000�0000000�00000002357�13262451251�0022504�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace Ui { namespace Toast { namespace internal { class Manager; class Widget; } // namespace internal static constexpr const int DefaultDuration = 1500; struct Config { QString text; int durationMs = DefaultDuration; int maxWidth = 0; QMargins padding; }; void Show(QWidget *parent, const Config &config); void Show(const Config &config); void Show(const QString &text); class Instance { struct Private { }; public: Instance(const Config &config, QWidget *widgetParent, const Private &); Instance(const Instance &other) = delete; Instance &operator=(const Instance &other) = delete; void hideAnimated(); void hide(); private: void opacityAnimationCallback(); bool _hiding = false; Animation _a_opacity; const TimeMs _hideAtMs; // ToastManager should reset _widget pointer if _widget is destroyed. friend class internal::Manager; friend void Show(QWidget *parent, const Config &config); std::unique_ptr<internal::Widget> _widget; }; } // namespace Toast } // namespace Ui ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/toast/toast_manager.cpp�������������������������������������0000664�0000000�0000000�00000006047�13262451251�0024531�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/toast/toast_manager.h" #include "application.h" #include "ui/toast/toast_widget.h" namespace Ui { namespace Toast { namespace internal { namespace { NeverFreedPointer<QMap<QObject*, Manager*>> _managers; } // namespace Manager::Manager(QWidget *parent) : QObject(parent) { connect(&_hideTimer, SIGNAL(timeout()), this, SLOT(onHideTimeout())); } bool Manager::eventFilter(QObject *o, QEvent *e) { if (e->type() == QEvent::Resize) { for (auto i = _toastByWidget.cbegin(), e = _toastByWidget.cend(); i != e; ++i) { if (i.key()->parentWidget() == o) { i.key()->onParentResized(); } } } return QObject::eventFilter(o, e); } Manager *Manager::instance(QWidget *parent) { if (!parent) { return nullptr; } _managers.createIfNull(); auto i = _managers->constFind(parent); if (i == _managers->cend()) { i = _managers->insert(parent, new Manager(parent)); } return i.value(); } void Manager::addToast(std::unique_ptr<Instance> &&toast) { _toasts.push_back(toast.release()); Instance *t = _toasts.back(); Widget *widget = t->_widget.get(); _toastByWidget.insert(widget, t); connect(widget, SIGNAL(destroyed(QObject*)), this, SLOT(onToastWidgetDestroyed(QObject*))); if (auto parent = widget->parentWidget()) { auto found = false; for (auto i = _toastParents.begin(); i != _toastParents.cend();) { if (*i == parent) { found = true; break; } else if (!*i) { i = _toastParents.erase(i); } else { ++i; } } if (!found) { _toastParents.insert(parent); parent->installEventFilter(this); } } auto oldHideNearestMs = _toastByHideTime.isEmpty() ? 0LL : _toastByHideTime.firstKey(); _toastByHideTime.insert(t->_hideAtMs, t); if (!oldHideNearestMs || _toastByHideTime.firstKey() < oldHideNearestMs) { startNextHideTimer(); } } void Manager::onHideTimeout() { auto now = getms(true); for (auto i = _toastByHideTime.begin(); i != _toastByHideTime.cend();) { if (i.key() <= now) { auto toast = i.value(); i = _toastByHideTime.erase(i); toast->hideAnimated(); } else { break; } } startNextHideTimer(); } void Manager::onToastWidgetDestroyed(QObject *widget) { auto i = _toastByWidget.find(static_cast<Widget*>(widget)); if (i != _toastByWidget.cend()) { auto toast = i.value(); _toastByWidget.erase(i); toast->_widget.release(); int index = _toasts.indexOf(toast); if (index >= 0) { _toasts.removeAt(index); delete toast; } } } void Manager::startNextHideTimer() { if (_toastByHideTime.isEmpty()) return; auto ms = getms(true); if (ms >= _toastByHideTime.firstKey()) { QMetaObject::invokeMethod(this, SLOT("onHideTimeout"), Qt::QueuedConnection); } else { _hideTimer.start(_toastByHideTime.firstKey() - ms); } } Manager::~Manager() { _managers->remove(parent()); } } // namespace internal } // namespace Toast } // namespace Ui �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/toast/toast_manager.h���������������������������������������0000664�0000000�0000000�00000002145�13262451251�0024171�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/toast/toast.h" #include "core/single_timer.h" namespace Ui { namespace Toast { namespace internal { class Widget; class Manager : public QObject { Q_OBJECT public: Manager(const Manager &other) = delete; Manager &operator=(const Manager &other) = delete; static Manager *instance(QWidget *parent); void addToast(std::unique_ptr<Instance> &&toast); ~Manager(); protected: bool eventFilter(QObject *o, QEvent *e); private slots: void onHideTimeout(); void onToastWidgetDestroyed(QObject *widget); private: Manager(QWidget *parent); void startNextHideTimer(); SingleTimer _hideTimer; TimeMs _nextHide = 0; QMultiMap<TimeMs, Instance*> _toastByHideTime; QMap<Widget*, Instance*> _toastByWidget; QList<Instance*> _toasts; OrderedSet<QPointer<QWidget>> _toastParents; }; } // namespace internal } // namespace Toast } // namespace Ui ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/toast/toast_widget.cpp��������������������������������������0000664�0000000�0000000�00000004330�13262451251�0024373�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/toast/toast_widget.h" namespace Ui { namespace Toast { namespace internal { namespace { constexpr auto kToastMaxLines = 16; } // namespace Widget::Widget(QWidget *parent, const Config &config) : TWidget(parent) , _multiline(config.maxWidth > 0) , _maxWidth((config.maxWidth > 0) ? config.maxWidth : st::toastMaxWidth) , _padding((config.padding.left() > 0) ? config.padding : st::toastPadding) , _maxTextWidth(_maxWidth - _padding.left() - _padding.right()) , _text(_multiline ? _maxTextWidth : QFIXED_MAX) { TextParseOptions toastOptions = { 0, _maxTextWidth, st::toastTextStyle.font->height, Qt::LayoutDirectionAuto }; if (_multiline) { toastOptions.maxh *= kToastMaxLines; } _text.setText(st::toastTextStyle, _multiline ? config.text : TextUtilities::SingleLine(config.text), toastOptions); setAttribute(Qt::WA_TransparentForMouseEvents); onParentResized(); show(); } void Widget::onParentResized() { auto newWidth = _maxWidth; accumulate_min(newWidth, _padding.left() + _text.maxWidth() + _padding.right()); accumulate_min(newWidth, parentWidget()->width() - 2 * st::toastMinMargin); _textWidth = newWidth - _padding.left() - _padding.right(); auto maxHeight = kToastMaxLines * st::toastTextStyle.font->height; auto textHeight = _multiline ? qMin(_text.countHeight(_textWidth), maxHeight) : _text.minHeight(); auto newHeight = _padding.top() + textHeight + _padding.bottom(); setGeometry((parentWidget()->width() - newWidth) / 2, (parentWidget()->height() - newHeight) / 2, newWidth, newHeight); } void Widget::setShownLevel(float64 shownLevel) { _shownLevel = shownLevel; } void Widget::paintEvent(QPaintEvent *e) { Painter p(this); PainterHighQualityEnabler hq(p); p.setOpacity(_shownLevel); App::roundRect(p, rect(), st::toastBg, ImageRoundRadius::Large); auto lines = _multiline ? kToastMaxLines : 1; p.setPen(st::toastFg); _text.drawElided(p, _padding.left(), _padding.top(), _textWidth + 1, lines); } } // namespace internal } // namespace Toast } // namespace Ui ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/toast/toast_widget.h����������������������������������������0000664�0000000�0000000�00000001603�13262451251�0024040�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/toast/toast.h" #include "ui/twidget.h" #include "ui/text/text.h" namespace Ui { namespace Toast { namespace internal { class Widget : public TWidget { public: Widget(QWidget *parent, const Config &config); // shownLevel=1 completely visible, shownLevel=0 completely invisible void setShownLevel(float64 shownLevel); void onParentResized(); protected: void paintEvent(QPaintEvent *e) override; private: float64 _shownLevel = 0; bool _multiline = false; int _maxWidth = 0; QMargins _padding; int _maxTextWidth = 0; int _textWidth = 0; Text _text; }; } // namespace internal } // namespace Toast } // namespace Ui �����������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/twidget.cpp�������������������������������������������������0000664�0000000�0000000�00000015442�13262451251�0022221�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "twidget.h" #include "application.h" #include "mainwindow.h" namespace Fonts { namespace { bool ValidateFont(const QString &familyName, int flags = 0) { QFont checkFont(familyName); checkFont.setPixelSize(13); checkFont.setBold(flags & style::internal::FontBold); checkFont.setItalic(flags & style::internal::FontItalic); checkFont.setUnderline(flags & style::internal::FontUnderline); checkFont.setStyleStrategy(QFont::PreferQuality); auto realFamily = QFontInfo(checkFont).family(); if (realFamily.trimmed().compare(familyName, Qt::CaseInsensitive)) { LOG(("Font Error: could not resolve '%1' font, got '%2' after feeding '%3'.").arg(familyName).arg(realFamily)); return false; } auto metrics = QFontMetrics(checkFont); if (!metrics.height()) { LOG(("Font Error: got a zero height in '%1'.").arg(familyName)); return false; } return true; } bool LoadCustomFont(const QString &filePath, const QString &familyName, int flags = 0) { auto regularId = QFontDatabase::addApplicationFont(filePath); if (regularId < 0) { LOG(("Font Error: could not add '%1'.").arg(filePath)); return false; } auto found = [&familyName, regularId] { for (auto &family : QFontDatabase::applicationFontFamilies(regularId)) { if (!family.trimmed().compare(familyName, Qt::CaseInsensitive)) { return true; } } return false; }; if (!found()) { LOG(("Font Error: could not locate '%1' font in '%2'.").arg(familyName).arg(filePath)); return false; } return ValidateFont(familyName, flags); } bool Started = false; QString OpenSansOverride; QString OpenSansSemiboldOverride; } // namespace void Start() { if (Started) { return; } Started = true; auto regular = LoadCustomFont(qsl(":/gui/fonts/OpenSans-Regular.ttf"), qsl("Open Sans")); auto bold = LoadCustomFont(qsl(":/gui/fonts/OpenSans-Bold.ttf"), qsl("Open Sans"), style::internal::FontBold); auto semibold = LoadCustomFont(qsl(":/gui/fonts/OpenSans-Semibold.ttf"), qsl("Open Sans Semibold")); #ifdef Q_OS_WIN // Attempt to workaround a strange font bug with Open Sans Semibold not loading. // See https://github.com/telegramdesktop/tdesktop/issues/3276 for details. // Crash happens on "options.maxh / _t->_st->font->height" with "division by zero". // In that place "_t->_st->font" is "semiboldFont" is "font(13 "Open Sans Semibold"). if (!regular || !bold) { if (ValidateFont(qsl("Segoe UI")) && ValidateFont(qsl("Segoe UI"), style::internal::FontBold)) { OpenSansOverride = qsl("Segoe UI"); LOG(("Fonts Info: Using Segoe UI instead of Open Sans.")); } } if (!semibold) { if (ValidateFont(qsl("Segoe UI Semibold"))) { OpenSansSemiboldOverride = qsl("Segoe UI Semibold"); LOG(("Fonts Info: Using Segoe UI Semibold instead of Open Sans Semibold.")); } } #endif // Q_OS_WIN } QString GetOverride(const QString &familyName) { if (familyName == qstr("Open Sans")) { return OpenSansOverride.isEmpty() ? familyName : OpenSansOverride; } else if (familyName == qstr("Open Sans Semibold")) { return OpenSansSemiboldOverride.isEmpty() ? familyName : OpenSansSemiboldOverride; } return familyName; } } // Fonts namespace Ui { namespace { class WidgetCreator : public QWidget { public: static void Create(not_null<QWidget*> widget) { volatile auto unknown = widget.get(); static_cast<WidgetCreator*>(unknown)->create(); } }; void CreateWidgetStateRecursive(not_null<QWidget*> target) { if (!target->testAttribute(Qt::WA_WState_Created)) { if (!target->isWindow()) { CreateWidgetStateRecursive(target->parentWidget()); WidgetCreator::Create(target); } else if (!cIsSnowLeopard()) { WidgetCreator::Create(target); } } } void SendPendingEventsRecursive(QWidget *target, bool parentHiddenFlag) { auto wasVisible = target->isVisible(); if (!wasVisible) { target->setAttribute(Qt::WA_WState_Visible, true); } if (target->testAttribute(Qt::WA_PendingMoveEvent)) { target->setAttribute(Qt::WA_PendingMoveEvent, false); QMoveEvent e(target->pos(), QPoint()); QApplication::sendEvent(target, &e); } if (target->testAttribute(Qt::WA_PendingResizeEvent)) { target->setAttribute(Qt::WA_PendingResizeEvent, false); QResizeEvent e(target->size(), QSize()); QApplication::sendEvent(target, &e); } auto removeVisibleFlag = [&] { return parentHiddenFlag || target->testAttribute(Qt::WA_WState_Hidden); }; auto &children = target->children(); for (auto i = 0; i < children.size(); ++i) { auto child = children[i]; if (child->isWidgetType()) { auto widget = static_cast<QWidget*>(child); if (!widget->isWindow()) { if (!widget->testAttribute(Qt::WA_WState_Created)) { WidgetCreator::Create(widget); } SendPendingEventsRecursive(widget, removeVisibleFlag()); } } } if (removeVisibleFlag()) { target->setAttribute(Qt::WA_WState_Visible, false); } } } // namespace void SendPendingMoveResizeEvents(not_null<QWidget*> target) { CreateWidgetStateRecursive(target); SendPendingEventsRecursive(target, !target->isVisible()); } QPixmap GrabWidget(not_null<TWidget*> target, QRect rect, QColor bg) { SendPendingMoveResizeEvents(target); if (rect.isNull()) { rect = target->rect(); } auto result = QPixmap(rect.size() * cIntRetinaFactor()); result.setDevicePixelRatio(cRetinaFactor()); if (!target->testAttribute(Qt::WA_OpaquePaintEvent)) { result.fill(bg); } target->grabStart(); target->render( &result, QPoint(0, 0), rect, QWidget::DrawChildren | QWidget::IgnoreMask); target->grabFinish(); return result; } QImage GrabWidgetToImage(not_null<TWidget*> target, QRect rect, QColor bg) { Ui::SendPendingMoveResizeEvents(target); if (rect.isNull()) { rect = target->rect(); } auto result = QImage( rect.size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); result.setDevicePixelRatio(cRetinaFactor()); if (!target->testAttribute(Qt::WA_OpaquePaintEvent)) { result.fill(bg); } target->grabStart(); target->render( &result, QPoint(0, 0), rect, QWidget::DrawChildren | QWidget::IgnoreMask); target->grabFinish(); return result; } } // namespace Ui void sendSynteticMouseEvent(QWidget *widget, QEvent::Type type, Qt::MouseButton button, const QPoint &globalPoint) { if (auto windowHandle = widget->window()->windowHandle()) { auto localPoint = windowHandle->mapFromGlobal(globalPoint); QMouseEvent ev(type , localPoint , localPoint , globalPoint , button , QGuiApplication::mouseButtons() | button , QGuiApplication::keyboardModifiers() #ifndef OS_MAC_OLD , Qt::MouseEventSynthesizedByApplication #endif // OS_MAC_OLD ); ev.setTimestamp(getms()); QGuiApplication::sendEvent(windowHandle, &ev); } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/twidget.h���������������������������������������������������0000664�0000000�0000000�00000040032�13262451251�0021657�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "base/flags.h" namespace Fonts { void Start(); QString GetOverride(const QString &familyName); } // namespace class TWidget; template <typename Object> class object_ptr; namespace Ui { inline bool InFocusChain(not_null<const QWidget*> widget) { if (auto top = widget->window()) { if (auto focused = top->focusWidget()) { return !widget->isHidden() && (focused == widget || widget->isAncestorOf(focused)); } } return false; } template <typename ChildWidget> inline ChildWidget *AttachParentChild( not_null<QWidget*> parent, const object_ptr<ChildWidget> &child) { if (auto raw = child.data()) { raw->setParent(parent); raw->show(); return raw; } return nullptr; } void SendPendingMoveResizeEvents(not_null<QWidget*> target); QPixmap GrabWidget( not_null<TWidget*> target, QRect rect = QRect(), QColor bg = QColor(255, 255, 255, 0)); QImage GrabWidgetToImage( not_null<TWidget*> target, QRect rect = QRect(), QColor bg = QColor(255, 255, 255, 0)); } // namespace Ui enum class RectPart { None = 0, TopLeft = (1 << 0), Top = (1 << 1), TopRight = (1 << 2), Left = (1 << 3), Center = (1 << 4), Right = (1 << 5), BottomLeft = (1 << 6), Bottom = (1 << 7), BottomRight = (1 << 8), FullTop = TopLeft | Top | TopRight, NoTopBottom = Left | Center | Right, FullBottom = BottomLeft | Bottom | BottomRight, NoTop = NoTopBottom | FullBottom, NoBottom = FullTop | NoTopBottom, FullLeft = TopLeft | Left | BottomLeft, NoLeftRight = Top | Center | Bottom, FullRight = TopRight | Right | BottomRight, NoLeft = NoLeftRight | FullRight, NoRight = FullLeft | NoLeftRight, AllCorners = TopLeft | TopRight | BottomLeft | BottomRight, AllSides = Top | Bottom | Left | Right, Full = FullTop | NoTop, }; using RectParts = base::flags<RectPart>; inline constexpr auto is_flag_type(RectPart) { return true; }; inline bool IsTopCorner(RectPart corner) { return (corner == RectPart::TopLeft) || (corner == RectPart::TopRight); } inline bool IsBottomCorner(RectPart corner) { return (corner == RectPart::BottomLeft) || (corner == RectPart::BottomRight); } inline bool IsLeftCorner(RectPart corner) { return (corner == RectPart::TopLeft) || (corner == RectPart::BottomLeft); } inline bool IsRightCorner(RectPart corner) { return (corner == RectPart::TopRight) || (corner == RectPart::BottomRight); } class Painter : public QPainter { public: explicit Painter(QPaintDevice *device) : QPainter(device) { } void drawTextLeft(int x, int y, int outerw, const QString &text, int textWidth = -1) { QFontMetrics m(fontMetrics()); if (rtl() && textWidth < 0) textWidth = m.width(text); drawText(rtl() ? (outerw - x - textWidth) : x, y + m.ascent(), text); } void drawTextRight(int x, int y, int outerw, const QString &text, int textWidth = -1) { QFontMetrics m(fontMetrics()); if (!rtl() && textWidth < 0) textWidth = m.width(text); drawText(rtl() ? x : (outerw - x - textWidth), y + m.ascent(), text); } void drawPixmapLeft(int x, int y, int outerw, const QPixmap &pix, const QRect &from) { drawPixmap(QPoint(rtl() ? (outerw - x - (from.width() / pix.devicePixelRatio())) : x, y), pix, from); } void drawPixmapLeft(const QPoint &p, int outerw, const QPixmap &pix, const QRect &from) { return drawPixmapLeft(p.x(), p.y(), outerw, pix, from); } void drawPixmapLeft(int x, int y, int w, int h, int outerw, const QPixmap &pix, const QRect &from) { drawPixmap(QRect(rtl() ? (outerw - x - w) : x, y, w, h), pix, from); } void drawPixmapLeft(const QRect &r, int outerw, const QPixmap &pix, const QRect &from) { return drawPixmapLeft(r.x(), r.y(), r.width(), r.height(), outerw, pix, from); } void drawPixmapLeft(int x, int y, int outerw, const QPixmap &pix) { drawPixmap(QPoint(rtl() ? (outerw - x - (pix.width() / pix.devicePixelRatio())) : x, y), pix); } void drawPixmapLeft(const QPoint &p, int outerw, const QPixmap &pix) { return drawPixmapLeft(p.x(), p.y(), outerw, pix); } void drawPixmapRight(int x, int y, int outerw, const QPixmap &pix, const QRect &from) { drawPixmap(QPoint(rtl() ? x : (outerw - x - (from.width() / pix.devicePixelRatio())), y), pix, from); } void drawPixmapRight(const QPoint &p, int outerw, const QPixmap &pix, const QRect &from) { return drawPixmapRight(p.x(), p.y(), outerw, pix, from); } void drawPixmapRight(int x, int y, int w, int h, int outerw, const QPixmap &pix, const QRect &from) { drawPixmap(QRect(rtl() ? x : (outerw - x - w), y, w, h), pix, from); } void drawPixmapRight(const QRect &r, int outerw, const QPixmap &pix, const QRect &from) { return drawPixmapRight(r.x(), r.y(), r.width(), r.height(), outerw, pix, from); } void drawPixmapRight(int x, int y, int outerw, const QPixmap &pix) { drawPixmap(QPoint(rtl() ? x : (outerw - x - (pix.width() / pix.devicePixelRatio())), y), pix); } void drawPixmapRight(const QPoint &p, int outerw, const QPixmap &pix) { return drawPixmapRight(p.x(), p.y(), outerw, pix); } void setTextPalette(const style::TextPalette &palette) { _textPalette = &palette; } void restoreTextPalette() { _textPalette = nullptr; } const style::TextPalette &textPalette() const { return _textPalette ? *_textPalette : st::defaultTextPalette; } private: const style::TextPalette *_textPalette = nullptr; }; class PainterHighQualityEnabler { public: PainterHighQualityEnabler(Painter &p) : _painter(p) { static constexpr QPainter::RenderHint Hints[] = { QPainter::Antialiasing, QPainter::SmoothPixmapTransform, QPainter::TextAntialiasing, QPainter::HighQualityAntialiasing }; auto hints = _painter.renderHints(); for_const (auto hint, Hints) { if (!(hints & hint)) { _hints |= hint; } } if (_hints) { _painter.setRenderHints(_hints); } } PainterHighQualityEnabler(const PainterHighQualityEnabler &other) = delete; PainterHighQualityEnabler &operator=(const PainterHighQualityEnabler &other) = delete; ~PainterHighQualityEnabler() { if (_hints) { _painter.setRenderHints(_hints, false); } } private: Painter &_painter; QPainter::RenderHints _hints = 0; }; template <typename Base> class TWidgetHelper : public Base { public: using Base::Base; virtual QMargins getMargins() const { return QMargins(); } void moveToLeft(int x, int y, int outerw = 0) { auto margins = getMargins(); x -= margins.left(); y -= margins.top(); Base::move(rtl() ? ((outerw > 0 ? outerw : Base::parentWidget()->width()) - x - Base::width()) : x, y); } void moveToRight(int x, int y, int outerw = 0) { auto margins = getMargins(); x -= margins.right(); y -= margins.top(); Base::move(rtl() ? x : ((outerw > 0 ? outerw : Base::parentWidget()->width()) - x - Base::width()), y); } void setGeometryToLeft(int x, int y, int w, int h, int outerw = 0) { auto margins = getMargins(); x -= margins.left(); y -= margins.top(); w -= margins.left() - margins.right(); h -= margins.top() - margins.bottom(); Base::setGeometry(rtl() ? ((outerw > 0 ? outerw : Base::parentWidget()->width()) - x - w) : x, y, w, h); } void setGeometryToRight(int x, int y, int w, int h, int outerw = 0) { auto margins = getMargins(); x -= margins.right(); y -= margins.top(); w -= margins.left() - margins.right(); h -= margins.top() - margins.bottom(); Base::setGeometry(rtl() ? x : ((outerw > 0 ? outerw : Base::parentWidget()->width()) - x - w), y, w, h); } QPoint myrtlpoint(int x, int y) const { return rtlpoint(x, y, Base::width()); } QPoint myrtlpoint(const QPoint point) const { return rtlpoint(point, Base::width()); } QRect myrtlrect(int x, int y, int w, int h) const { return rtlrect(x, y, w, h, Base::width()); } QRect myrtlrect(const QRect &rect) const { return rtlrect(rect, Base::width()); } void rtlupdate(const QRect &rect) { Base::update(myrtlrect(rect)); } void rtlupdate(int x, int y, int w, int h) { Base::update(myrtlrect(x, y, w, h)); } QPoint mapFromGlobal(const QPoint &point) const { return Base::mapFromGlobal(point); } QPoint mapToGlobal(const QPoint &point) const { return Base::mapToGlobal(point); } QRect mapFromGlobal(const QRect &rect) const { return QRect(mapFromGlobal(rect.topLeft()), rect.size()); } QRect mapToGlobal(const QRect &rect) const { return QRect(mapToGlobal(rect.topLeft()), rect.size()); } protected: void enterEvent(QEvent *e) final override { if (auto parent = tparent()) { parent->leaveToChildEvent(e, this); } return enterEventHook(e); } virtual void enterEventHook(QEvent *e) { return Base::enterEvent(e); } void leaveEvent(QEvent *e) final override { if (auto parent = tparent()) { parent->enterFromChildEvent(e, this); } return leaveEventHook(e); } virtual void leaveEventHook(QEvent *e) { return Base::leaveEvent(e); } // e - from enterEvent() of child TWidget virtual void leaveToChildEvent(QEvent *e, QWidget *child) { } // e - from leaveEvent() of child TWidget virtual void enterFromChildEvent(QEvent *e, QWidget *child) { } private: TWidget *tparent() { return qobject_cast<TWidget*>(Base::parentWidget()); } const TWidget *tparent() const { return qobject_cast<const TWidget*>(Base::parentWidget()); } template <typename OtherBase> friend class TWidgetHelper; }; class TWidget : public TWidgetHelper<QWidget> { Q_OBJECT public: TWidget(QWidget *parent = nullptr) : TWidgetHelper<QWidget>(parent) { } virtual void grabStart() { } virtual void grabFinish() { } bool inFocusChain() const { return Ui::InFocusChain(this); } void hideChildren() { for (auto child : children()) { if (child->isWidgetType()) { static_cast<QWidget*>(child)->hide(); } } } void showChildren() { for (auto child : children()) { if (child->isWidgetType()) { static_cast<QWidget*>(child)->show(); } } } // Get the size of the widget as it should be. // Negative return value means no default width. virtual int naturalWidth() const { return -1; } // Count new height for width=newWidth and resize to it. void resizeToWidth(int newWidth) { auto margins = getMargins(); auto fullWidth = margins.left() + newWidth + margins.right(); auto fullHeight = margins.top() + resizeGetHeight(newWidth) + margins.bottom(); auto newSize = QSize(fullWidth, fullHeight); if (newSize != size()) { resize(newSize); update(); } } // Resize to minimum of natural width and available width. void resizeToNaturalWidth(int newWidth) { auto maxWidth = naturalWidth(); resizeToWidth((maxWidth >= 0) ? qMin(newWidth, maxWidth) : newWidth); } QRect rectNoMargins() const { return rect().marginsRemoved(getMargins()); } int widthNoMargins() const { return rectNoMargins().width(); } int heightNoMargins() const { return rectNoMargins().height(); } int bottomNoMargins() const { auto rectWithoutMargins = rectNoMargins(); return y() + rectWithoutMargins.y() + rectWithoutMargins.height(); } QSize sizeNoMargins() const { return rectNoMargins().size(); } // Updates the area that is visible inside the scroll container. void setVisibleTopBottom(int visibleTop, int visibleBottom) { auto max = height(); visibleTopBottomUpdated( snap(visibleTop, 0, max), snap(visibleBottom, 0, max)); } signals: // Child widget is responsible for emitting this signal. void heightUpdated(); protected: void setChildVisibleTopBottom( TWidget *child, int visibleTop, int visibleBottom) { if (child) { auto top = child->y(); child->setVisibleTopBottom( visibleTop - top, visibleBottom - top); } } // Resizes content and counts natural widget height for the desired width. virtual int resizeGetHeight(int newWidth) { return heightNoMargins(); } virtual void visibleTopBottomUpdated( int visibleTop, int visibleBottom) { } }; template <typename Widget> QPointer<Widget> make_weak(Widget *object) { return QPointer<Widget>(object); } template <typename Widget> QPointer<const Widget> make_weak(const Widget *object) { return QPointer<const Widget>(object); } template <typename Widget> QPointer<Widget> make_weak(not_null<Widget*> object) { return QPointer<Widget>(object.get()); } template <typename Widget> QPointer<const Widget> make_weak(not_null<const Widget*> object) { return QPointer<const Widget>(object.get()); } class SingleQueuedInvokation : public QObject { public: SingleQueuedInvokation(base::lambda<void()> callback) : _callback(callback) { } void call() { if (_pending.testAndSetAcquire(0, 1)) { InvokeQueued(this, [this] { if (_pending.testAndSetRelease(1, 0)) { _callback(); } }); } } private: base::lambda<void()> _callback; QAtomicInt _pending = { 0 }; }; // Smart pointer for QObject*, has move semantics, destroys object if it doesn't have a parent. template <typename Object> class object_ptr { public: object_ptr(std::nullptr_t) noexcept { } // No default constructor, but constructors with at least // one argument are simply make functions. template <typename Parent, typename... Args> explicit object_ptr(Parent &&parent, Args&&... args) : _object(new Object(std::forward<Parent>(parent), std::forward<Args>(args)...)) { } static object_ptr<Object> fromRaw(Object *value) noexcept { object_ptr<Object> result = { nullptr }; result._object = value; return result; } object_ptr(const object_ptr &other) = delete; object_ptr &operator=(const object_ptr &other) = delete; object_ptr(object_ptr &&other) noexcept : _object(base::take(other._object)) { } object_ptr &operator=(object_ptr &&other) noexcept { auto temp = std::move(other); destroy(); std::swap(_object, temp._object); return *this; } template < typename OtherObject, typename = std::enable_if_t< std::is_base_of_v<Object, OtherObject>>> object_ptr(object_ptr<OtherObject> &&other) noexcept : _object(base::take(other._object)) { } template < typename OtherObject, typename = std::enable_if_t< std::is_base_of_v<Object, OtherObject>>> object_ptr &operator=(object_ptr<OtherObject> &&other) noexcept { _object = base::take(other._object); return *this; } object_ptr &operator=(std::nullptr_t) noexcept { _object = nullptr; return *this; } // So we can pass this pointer to methods like connect(). Object *data() const noexcept { return static_cast<Object*>(_object.data()); } operator Object*() const noexcept { return data(); } explicit operator bool() const noexcept { return _object != nullptr; } Object *operator->() const noexcept { return data(); } Object &operator*() const noexcept { return *data(); } // Use that instead "= new Object(parent, ...)" template <typename Parent, typename... Args> Object *create(Parent &&parent, Args&&... args) { destroy(); _object = new Object( std::forward<Parent>(parent), std::forward<Args>(args)...); return data(); } void destroy() noexcept { delete base::take(_object); } void destroyDelayed() { if (_object) { if (auto widget = base::up_cast<QWidget*>(data())) { widget->hide(); } base::take(_object)->deleteLater(); } } ~object_ptr() noexcept { if (auto pointer = _object) { if (!pointer->parent()) { destroy(); } } } template <typename ResultType, typename SourceType> friend object_ptr<ResultType> static_object_cast( object_ptr<SourceType> source); private: template <typename OtherObject> friend class object_ptr; QPointer<QObject> _object; }; template <typename ResultType, typename SourceType> inline object_ptr<ResultType> static_object_cast( object_ptr<SourceType> source) { auto result = object_ptr<ResultType>(nullptr); result._object = static_cast<ResultType*>( base::take(source._object).data()); return std::move(result); } void sendSynteticMouseEvent( QWidget *widget, QEvent::Type type, Qt::MouseButton button, const QPoint &globalPoint); inline void sendSynteticMouseEvent( QWidget *widget, QEvent::Type type, Qt::MouseButton button) { return sendSynteticMouseEvent(widget, type, button, QCursor::pos()); } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/unread_badge.cpp��������������������������������������������0000664�0000000�0000000�00000001404�13262451251�0023143�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/unread_badge.h" #include "dialogs/dialogs_layout.h" namespace Ui { void UnreadBadge::setText(const QString &text, bool active) { _text = text; _active = active; update(); } void UnreadBadge::paintEvent(QPaintEvent *e) { if (_text.isEmpty()) { return; } Painter p(this); Dialogs::Layout::UnreadBadgeStyle unreadSt; unreadSt.muted = !_active; auto unreadRight = width(); auto unreadTop = 0; Dialogs::Layout::paintUnreadCount( p, _text, unreadRight, unreadTop, unreadSt); } } // namespace Ui ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/unread_badge.h����������������������������������������������0000664�0000000�0000000�00000001043�13262451251�0022607�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/rp_widget.h" namespace Ui { class UnreadBadge : public RpWidget { public: using RpWidget::RpWidget; void setText(const QString &text, bool active); protected: void paintEvent(QPaintEvent *e) override; private: QString _text; bool _active = false; }; } // namespace Ui ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/widgets/����������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0021506�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/widgets/buttons.cpp�����������������������������������������0000664�0000000�0000000�00000036452�13262451251�0023722�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/widgets/buttons.h" #include "ui/effects/ripple_animation.h" #include "ui/effects/cross_animation.h" #include "ui/effects/numbers_animation.h" #include "lang/lang_instance.h" namespace Ui { LinkButton::LinkButton(QWidget *parent, const QString &text, const style::LinkButton &st) : AbstractButton(parent) , _text(text) , _textWidth(st.font->width(_text)) , _st(st) { resize(_textWidth, _st.font->height); setCursor(style::cur_pointer); } int LinkButton::naturalWidth() const { return _textWidth; } void LinkButton::paintEvent(QPaintEvent *e) { Painter p(this); auto &font = (isOver() ? _st.overFont : _st.font); auto &pen = (isOver() ? _st.overColor : _st.color); p.setFont(font); p.setPen(pen); if (_textWidth > width()) { p.drawText(0, font->ascent, font->elided(_text, width())); } else { p.drawText(0, font->ascent, _text); } } void LinkButton::setText(const QString &text) { _text = text; _textWidth = _st.font->width(_text); resize(_textWidth, _st.font->height); update(); } void LinkButton::onStateChanged(State was, StateChangeSource source) { update(); } RippleButton::RippleButton(QWidget *parent, const style::RippleAnimation &st) : AbstractButton(parent) , _st(st) { } void RippleButton::clearState() { AbstractButton::clearState(); if (_ripple) { _ripple.reset(); update(); } } void RippleButton::setForceRippled( bool rippled, anim::type animated) { if (_forceRippled != rippled) { _forceRippled = rippled; if (_forceRippled) { ensureRipple(); if (_ripple->empty()) { _ripple->addFading(); } else { _ripple->lastUnstop(); } } else if (_ripple) { _ripple->lastStop(); } } if (animated == anim::type::instant && _ripple) { _ripple->lastFinish(); } update(); } void RippleButton::paintRipple(QPainter &p, int x, int y, TimeMs ms, const QColor *colorOverride) { if (_ripple) { _ripple->paint(p, x, y, width(), ms, colorOverride); if (_ripple->empty()) { _ripple.reset(); } } } void RippleButton::onStateChanged(State was, StateChangeSource source) { update(); auto wasDown = static_cast<bool>(was & StateFlag::Down); auto down = isDown(); if (!_st.showDuration || down == wasDown || _forceRippled) { return; } if (down && (source == StateChangeSource::ByPress)) { // Start a ripple only from mouse press. auto position = prepareRippleStartPosition(); if (position != DisabledRippleStartPosition()) { ensureRipple(); _ripple->add(position); } } else if (!down && _ripple) { // Finish ripple anyway. _ripple->lastStop(); } } void RippleButton::ensureRipple() { if (!_ripple) { _ripple = std::make_unique<RippleAnimation>(_st, prepareRippleMask(), [this] { update(); }); } } QImage RippleButton::prepareRippleMask() const { return RippleAnimation::rectMask(size()); } QPoint RippleButton::prepareRippleStartPosition() const { return mapFromGlobal(QCursor::pos()); } RippleButton::~RippleButton() = default; FlatButton::FlatButton(QWidget *parent, const QString &text, const style::FlatButton &st) : RippleButton(parent, st.ripple) , _text(text) , _st(st) { if (_st.width < 0) { _width = textWidth() - _st.width; } else if (!_st.width) { _width = textWidth() + _st.height - _st.font->height; } else { _width = _st.width; } resize(_width, _st.height); } void FlatButton::setText(const QString &text) { _text = text; update(); } void FlatButton::setWidth(int32 w) { _width = w; if (_width < 0) { _width = textWidth() - _st.width; } else if (!_width) { _width = textWidth() + _st.height - _st.font->height; } resize(_width, height()); } int32 FlatButton::textWidth() const { return _st.font->width(_text); } void FlatButton::onStateChanged(State was, StateChangeSource source) { RippleButton::onStateChanged(was, source); update(); } void FlatButton::paintEvent(QPaintEvent *e) { QPainter p(this); QRect r(0, height() - _st.height, width(), _st.height); p.fillRect(r, isOver() ? _st.overBgColor : _st.bgColor); paintRipple(p, 0, 0, getms()); p.setFont(isOver() ? _st.overFont : _st.font); p.setRenderHint(QPainter::TextAntialiasing); p.setPen(isOver() ? _st.overColor : _st.color); r.setTop(_st.textTop); p.drawText(r, _text, style::al_top); } RoundButton::RoundButton(QWidget *parent, base::lambda<QString()> textFactory, const style::RoundButton &st) : RippleButton(parent, st.ripple) , _textFactory(std::move(textFactory)) , _st(st) { subscribe(Lang::Current().updated(), [this] { refreshText(); }); refreshText(); } void RoundButton::setTextTransform(TextTransform transform) { _transform = transform; refreshText(); } void RoundButton::setText(base::lambda<QString()> textFactory) { _textFactory = std::move(textFactory); refreshText(); } void RoundButton::setNumbersText(const QString &numbersText, int numbers) { if (numbersText.isEmpty()) { _numbers.reset(); } else { if (!_numbers) { _numbers = std::make_unique<NumbersAnimation>(_st.font, [this] { numbersAnimationCallback(); }); } _numbers->setText(numbersText, numbers); } refreshText(); } void RoundButton::setWidthChangedCallback(base::lambda<void()> callback) { if (!_numbers) { _numbers = std::make_unique<NumbersAnimation>(_st.font, [this] { numbersAnimationCallback(); }); } _numbers->setWidthChangedCallback(std::move(callback)); } void RoundButton::stepNumbersAnimation(TimeMs ms) { if (_numbers) { _numbers->stepAnimation(ms); } } void RoundButton::finishNumbersAnimation() { if (_numbers) { _numbers->finishAnimating(); } } void RoundButton::numbersAnimationCallback() { resizeToText(); update(); } void RoundButton::setFullWidth(int newFullWidth) { _fullWidthOverride = newFullWidth; resizeToText(); } void RoundButton::refreshText() { _text = computeFullText(); _textWidth = _text.isEmpty() ? 0 : _st.font->width(_text); resizeToText(); update(); } QString RoundButton::computeFullText() const { auto result = _textFactory ? _textFactory() : QString(); return (_transform == TextTransform::ToUpper) ? result.toUpper() : result; } void RoundButton::resizeToText() { int innerWidth = contentWidth(); if (_fullWidthOverride < 0) { resize(innerWidth - _fullWidthOverride, _st.height + _st.padding.top() + _st.padding.bottom()); } else if (_st.width <= 0) { resize(innerWidth - _st.width + _st.padding.left() + _st.padding.right(), _st.height + _st.padding.top() + _st.padding.bottom()); } else { if (_st.width < innerWidth + (_st.height - _st.font->height)) { _text = _st.font->elided(computeFullText(), qMax(_st.width - (_st.height - _st.font->height), 1)); _textWidth = _st.font->width(_text); } resize(_st.width + _st.padding.left() + _st.padding.right(), _st.height + _st.padding.top() + _st.padding.bottom()); } } int RoundButton::contentWidth() const { auto result = _textWidth; if (_numbers) { result += (result ? _st.numbersSkip : 0) + _numbers->countWidth(); } return result; } void RoundButton::paintEvent(QPaintEvent *e) { Painter p(this); auto innerWidth = contentWidth(); auto rounded = rect().marginsRemoved(_st.padding); if (_fullWidthOverride < 0) { rounded = QRect(0, rounded.top(), innerWidth - _fullWidthOverride, rounded.height()); } App::roundRect(p, myrtlrect(rounded), _st.textBg, ImageRoundRadius::Small); auto over = isOver(); auto down = isDown(); if (over || down) { App::roundRect(p, myrtlrect(rounded), _st.textBgOver, ImageRoundRadius::Small); } auto ms = getms(); paintRipple(p, rounded.x(), rounded.y(), ms); p.setFont(_st.font); int textLeft = _st.padding.left() + ((width() - innerWidth - _st.padding.left() - _st.padding.right()) / 2); if (_fullWidthOverride < 0) { textLeft = -_fullWidthOverride / 2; } int textTop = _st.padding.top() + _st.textTop; if (!_text.isEmpty()) { p.setPen((over || down) ? _st.textFgOver : _st.textFg); p.drawTextLeft(textLeft, textTop, width(), _text); } if (_numbers) { textLeft += _textWidth + (_textWidth ? _st.numbersSkip : 0); p.setPen((over || down) ? _st.numbersTextFgOver : _st.numbersTextFg); _numbers->paint(p, textLeft, textTop, width()); } _st.icon.paint(p, QPoint(_st.padding.left(), _st.padding.top()), width()); } QImage RoundButton::prepareRippleMask() const { auto innerWidth = contentWidth(); auto rounded = rtlrect(rect().marginsRemoved(_st.padding), width()); if (_fullWidthOverride < 0) { rounded = QRect(0, rounded.top(), innerWidth - _fullWidthOverride, rounded.height()); } return RippleAnimation::roundRectMask(rounded.size(), st::buttonRadius); } QPoint RoundButton::prepareRippleStartPosition() const { return mapFromGlobal(QCursor::pos()) - QPoint(_st.padding.left(), _st.padding.top()); } RoundButton::~RoundButton() = default; IconButton::IconButton(QWidget *parent, const style::IconButton &st) : RippleButton(parent, st.ripple) , _st(st) { resize(_st.width, _st.height); } void IconButton::setIconOverride(const style::icon *iconOverride, const style::icon *iconOverOverride) { _iconOverride = iconOverride; _iconOverrideOver = iconOverOverride; update(); } void IconButton::setRippleColorOverride(const style::color *colorOverride) { _rippleColorOverride = colorOverride; } void IconButton::paintEvent(QPaintEvent *e) { Painter p(this); auto ms = getms(); paintRipple(p, _st.rippleAreaPosition.x(), _st.rippleAreaPosition.y(), ms, _rippleColorOverride ? &(*_rippleColorOverride)->c : nullptr); auto down = isDown(); auto overIconOpacity = (down || forceRippled()) ? 1. : _a_over.current(getms(), isOver() ? 1. : 0.); auto overIcon = [this] { if (_iconOverrideOver) { return _iconOverrideOver; } else if (!_st.iconOver.empty()) { return &_st.iconOver; } else if (_iconOverride) { return _iconOverride; } return &_st.icon; }; auto justIcon = [this] { if (_iconOverride) { return _iconOverride; } return &_st.icon; }; auto icon = (overIconOpacity == 1.) ? overIcon() : justIcon(); auto position = _st.iconPosition; if (position.x() < 0) { position.setX((width() - icon->width()) / 2); } if (position.y() < 0) { position.setY((height() - icon->height()) / 2); } icon->paint(p, position, width()); if (overIconOpacity > 0. && overIconOpacity < 1.) { auto iconOver = overIcon(); if (iconOver != icon) { p.setOpacity(overIconOpacity); iconOver->paint(p, position, width()); } } } void IconButton::onStateChanged(State was, StateChangeSource source) { RippleButton::onStateChanged(was, source); auto over = isOver(); auto wasOver = static_cast<bool>(was & StateFlag::Over); if (over != wasOver) { if (_st.duration) { auto from = over ? 0. : 1.; auto to = over ? 1. : 0.; _a_over.start([this] { update(); }, from, to, _st.duration); } else { update(); } } } QPoint IconButton::prepareRippleStartPosition() const { auto result = mapFromGlobal(QCursor::pos()) - _st.rippleAreaPosition; auto rect = QRect(0, 0, _st.rippleAreaSize, _st.rippleAreaSize); return rect.contains(result) ? result : DisabledRippleStartPosition(); } QImage IconButton::prepareRippleMask() const { return RippleAnimation::ellipseMask(QSize(_st.rippleAreaSize, _st.rippleAreaSize)); } LeftOutlineButton::LeftOutlineButton(QWidget *parent, const QString &text, const style::OutlineButton &st) : RippleButton(parent, st.ripple) , _text(text) , _fullText(text) , _textWidth(st.font->width(_text)) , _fullTextWidth(_textWidth) , _st(st) { resizeToWidth(_textWidth + _st.padding.left() + _st.padding.right()); setCursor(style::cur_pointer); } void LeftOutlineButton::setText(const QString &text) { _text = text; _fullText = text; _fullTextWidth = _textWidth = _st.font->width(_text); resizeToWidth(width()); update(); } int LeftOutlineButton::resizeGetHeight(int newWidth) { int availableWidth = qMax(newWidth - _st.padding.left() - _st.padding.right(), 1); if ((availableWidth < _fullTextWidth) || (_textWidth < availableWidth)) { _text = _st.font->elided(_fullText, availableWidth); _textWidth = _st.font->width(_text); } return _st.padding.top() + _st.font->height + _st.padding.bottom(); } void LeftOutlineButton::paintEvent(QPaintEvent *e) { Painter p(this); auto over = isOver(); auto down = isDown(); if (width() > _st.outlineWidth) { p.fillRect(rtlrect(_st.outlineWidth, 0, width() - _st.outlineWidth, height(), width()), (over || down) ? _st.textBgOver : _st.textBg); paintRipple(p, 0, 0, getms()); p.fillRect(rtlrect(0, 0, _st.outlineWidth, height(), width()), (over || down) ? _st.outlineFgOver : _st.outlineFg); } p.setFont(_st.font); p.setPen((over || down) ? _st.textFgOver : _st.textFg); p.drawTextLeft(_st.padding.left(), _st.padding.top(), width(), _text, _textWidth); } CrossButton::CrossButton(QWidget *parent, const style::CrossButton &st) : RippleButton(parent, st.ripple) , _st(st) , _a_loading(animation(this, &CrossButton::step_loading)) { resize(_st.width, _st.height); setCursor(style::cur_pointer); setVisible(false); } void CrossButton::step_loading(TimeMs ms, bool timer) { if (stopLoadingAnimation(ms)) { _a_loading.stop(); } if (timer) { update(); } } void CrossButton::toggle(bool visible, anim::type animated) { if (_shown != visible) { _shown = visible; if (animated == anim::type::normal) { if (isHidden()) { setVisible(true); } _a_show.start( [this] { animationCallback(); }, _shown ? 0. : 1., _shown ? 1. : 0., _st.duration); } } if (animated == anim::type::instant) { finishAnimating(); } } void CrossButton::animationCallback() { update(); if (!_a_show.animating()) { setVisible(_shown); } } void CrossButton::paintEvent(QPaintEvent *e) { Painter p(this); auto ms = getms(); auto over = isOver(); auto shown = _a_show.current(ms, _shown ? 1. : 0.); p.setOpacity(shown); paintRipple(p, _st.crossPosition.x(), _st.crossPosition.y(), ms); auto loading = 0.; if (_a_loading.animating()) { if (stopLoadingAnimation(ms)) { _a_loading.stop(); } else { loading = ((ms - _loadingStartMs) % _st.loadingPeriod) / float64(_st.loadingPeriod); } } CrossAnimation::paint(p, _st.cross, over ? _st.crossFgOver : _st.crossFg, _st.crossPosition.x(), _st.crossPosition.y(), width(), shown, loading); } bool CrossButton::stopLoadingAnimation(TimeMs ms) { if (!_loadingStopMs) { return false; } auto stopPeriod = (_loadingStopMs - _loadingStartMs) / _st.loadingPeriod; auto currentPeriod = (ms - _loadingStartMs) / _st.loadingPeriod; if (currentPeriod != stopPeriod) { Assert(currentPeriod > stopPeriod); return true; } return false; } void CrossButton::setLoadingAnimation(bool enabled) { if (enabled) { _loadingStopMs = 0; if (!_a_loading.animating()) { _loadingStartMs = getms(); _a_loading.start(); } } else if (_a_loading.animating()) { _loadingStopMs = getms(); if (!((_loadingStopMs - _loadingStartMs) % _st.loadingPeriod)) { _a_loading.stop(); } } } void CrossButton::onStateChanged(State was, StateChangeSource source) { RippleButton::onStateChanged(was, source); auto over = isOver(); auto wasOver = static_cast<bool>(was & StateFlag::Over); if (over != wasOver) { update(); } } QPoint CrossButton::prepareRippleStartPosition() const { return mapFromGlobal(QCursor::pos()) - _st.crossPosition; } QImage CrossButton::prepareRippleMask() const { return RippleAnimation::ellipseMask(QSize(_st.cross.size, _st.cross.size)); } } // namespace Ui ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/widgets/buttons.h�������������������������������������������0000664�0000000�0000000�00000013015�13262451251�0023355�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/abstract_button.h" #include "styles/style_widgets.h" #include <memory> namespace Ui { class RippleAnimation; class NumbersAnimation; class LinkButton : public AbstractButton { public: LinkButton(QWidget *parent, const QString &text, const style::LinkButton &st = st::defaultLinkButton); int naturalWidth() const override; void setText(const QString &text); protected: void paintEvent(QPaintEvent *e) override; void onStateChanged(State was, StateChangeSource source) override; private: QString _text; int _textWidth = 0; const style::LinkButton &_st; }; class RippleButton : public AbstractButton { public: RippleButton(QWidget *parent, const style::RippleAnimation &st); void setForceRippled( bool rippled, anim::type animated = anim::type::normal); bool forceRippled() const { return _forceRippled; } static QPoint DisabledRippleStartPosition() { return QPoint(-0x3FFFFFFF, -0x3FFFFFFF); } void clearState() override; ~RippleButton(); protected: void paintRipple(QPainter &p, int x, int y, TimeMs ms, const QColor *colorOverride = nullptr); void onStateChanged(State was, StateChangeSource source) override; virtual QImage prepareRippleMask() const; virtual QPoint prepareRippleStartPosition() const; private: void ensureRipple(); void handleRipples(bool wasDown, bool wasPress); const style::RippleAnimation &_st; std::unique_ptr<RippleAnimation> _ripple; bool _forceRippled = false; }; class FlatButton : public RippleButton { public: FlatButton(QWidget *parent, const QString &text, const style::FlatButton &st); void setText(const QString &text); void setWidth(int32 w); int32 textWidth() const; protected: void paintEvent(QPaintEvent *e) override; void onStateChanged(State was, StateChangeSource source) override; private: QString _text, _textForAutoSize; int _width; const style::FlatButton &_st; }; class RoundButton : public RippleButton, private base::Subscriber { public: RoundButton(QWidget *parent, base::lambda<QString()> textFactory, const style::RoundButton &st); void setText(base::lambda<QString()> textFactory); void setNumbersText(const QString &numbersText) { setNumbersText(numbersText, numbersText.toInt()); } void setNumbersText(int numbers) { setNumbersText(QString::number(numbers), numbers); } void setWidthChangedCallback(base::lambda<void()> callback); void stepNumbersAnimation(TimeMs ms); void finishNumbersAnimation(); int contentWidth() const; void setFullWidth(int newFullWidth); enum class TextTransform { NoTransform, ToUpper, }; void setTextTransform(TextTransform transform); ~RoundButton(); protected: void paintEvent(QPaintEvent *e) override; QImage prepareRippleMask() const override; QPoint prepareRippleStartPosition() const override; private: void refreshText(); QString computeFullText() const; void setNumbersText(const QString &numbersText, int numbers); void numbersAnimationCallback(); void resizeToText(); QString _text; base::lambda<QString()> _textFactory; int _textWidth; std::unique_ptr<NumbersAnimation> _numbers; int _fullWidthOverride = 0; const style::RoundButton &_st; TextTransform _transform = TextTransform::ToUpper; }; class IconButton : public RippleButton { public: IconButton(QWidget *parent, const style::IconButton &st); // Pass nullptr to restore the default icon. void setIconOverride(const style::icon *iconOverride, const style::icon *iconOverOverride = nullptr); void setRippleColorOverride(const style::color *colorOverride); protected: void paintEvent(QPaintEvent *e) override; void onStateChanged(State was, StateChangeSource source) override; QImage prepareRippleMask() const override; QPoint prepareRippleStartPosition() const override; private: const style::IconButton &_st; const style::icon *_iconOverride = nullptr; const style::icon *_iconOverrideOver = nullptr; const style::color *_rippleColorOverride = nullptr; Animation _a_over; }; class LeftOutlineButton : public RippleButton { public: LeftOutlineButton(QWidget *parent, const QString &text, const style::OutlineButton &st = st::defaultLeftOutlineButton); void setText(const QString &text); protected: void paintEvent(QPaintEvent *e) override; int resizeGetHeight(int newWidth) override; private: QString _text, _fullText; int _textWidth, _fullTextWidth; const style::OutlineButton &_st; }; class CrossButton : public RippleButton { public: CrossButton(QWidget *parent, const style::CrossButton &st); void toggle(bool shown, anim::type animated); void show(anim::type animated) { return toggle(true, animated); } void hide(anim::type animated) { return toggle(false, animated); } void finishAnimating() { _a_show.finish(); animationCallback(); } bool toggled() const { return _shown; } void setLoadingAnimation(bool enabled); protected: void paintEvent(QPaintEvent *e) override; void onStateChanged(State was, StateChangeSource source) override; QImage prepareRippleMask() const override; QPoint prepareRippleStartPosition() const override; private: void step_loading(TimeMs ms, bool timer); bool stopLoadingAnimation(TimeMs ms); void animationCallback(); const style::CrossButton &_st; bool _shown = false; Animation _a_show; TimeMs _loadingStartMs = 0; TimeMs _loadingStopMs = 0; BasicAnimation _a_loading; }; } // namespace Ui �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/widgets/checkbox.cpp����������������������������������������0000664�0000000�0000000�00000037737�13262451251�0024021�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/widgets/checkbox.h" #include "lang/lang_keys.h" #include "ui/effects/ripple_animation.h" namespace Ui { namespace { TextParseOptions _checkboxOptions = { TextParseMultiline, // flags 0, // maxw 0, // maxh Qt::LayoutDirectionAuto, // dir }; } // namespace AbstractCheckView::AbstractCheckView(int duration, bool checked, base::lambda<void()> updateCallback) : _duration(duration) , _checked(checked) , _updateCallback(std::move(updateCallback)) { } void AbstractCheckView::setCheckedFast(bool checked) { _checked = checked; finishAnimating(); if (_updateCallback) { _updateCallback(); } } void AbstractCheckView::setUpdateCallback(base::lambda<void()> updateCallback) { _updateCallback = std::move(updateCallback); if (_toggleAnimation.animating()) { _toggleAnimation.setUpdateCallback(_updateCallback); } } void AbstractCheckView::setCheckedAnimated(bool checked) { if (_checked != checked) { _checked = checked; _toggleAnimation.start(_updateCallback, _checked ? 0. : 1., _checked ? 1. : 0., _duration); } } void AbstractCheckView::finishAnimating() { _toggleAnimation.finish(); } float64 AbstractCheckView::currentAnimationValue(TimeMs ms) { return ms ? _toggleAnimation.current(ms, _checked ? 1. : 0.) : _toggleAnimation.current(_checked ? 1. : 0.); } ToggleView::ToggleView( const style::Toggle &st, bool checked, base::lambda<void()> updateCallback) : AbstractCheckView(st.duration, checked, std::move(updateCallback)) , _st(&st) { } QSize ToggleView::getSize() const { return QSize(2 * _st->border + _st->diameter + _st->width, 2 * _st->border + _st->diameter); } void ToggleView::setStyle(const style::Toggle &st) { _st = &st; } void ToggleView::paint(Painter &p, int left, int top, int outerWidth, TimeMs ms) { left += _st->border; top += _st->border; PainterHighQualityEnabler hq(p); auto toggled = currentAnimationValue(ms); auto fullWidth = _st->diameter + _st->width; auto innerDiameter = _st->diameter - 2 * _st->shift; auto innerRadius = float64(innerDiameter) / 2.; auto toggleLeft = left + anim::interpolate(0, fullWidth - _st->diameter, toggled); auto bgRect = rtlrect(left + _st->shift, top + _st->shift, fullWidth - 2 * _st->shift, innerDiameter, outerWidth); auto fgRect = rtlrect(toggleLeft, top, _st->diameter, _st->diameter, outerWidth); auto fgBrush = anim::brush(_st->untoggledFg, _st->toggledFg, toggled); p.setPen(Qt::NoPen); p.setBrush(fgBrush); p.drawRoundedRect(bgRect, innerRadius, innerRadius); auto pen = anim::pen(_st->untoggledFg, _st->toggledFg, toggled); pen.setWidth(_st->border); p.setPen(pen); p.setBrush(anim::brush(_st->untoggledBg, _st->toggledBg, toggled)); p.drawEllipse(fgRect); if (_st->xsize > 0) { paintXV(p, toggleLeft, top, outerWidth, toggled, fgBrush); } } void ToggleView::paintXV(Painter &p, int left, int top, int outerWidth, float64 toggled, const QBrush &brush) { Assert(_st->vsize > 0); Assert(_st->stroke > 0); static const auto sqrt2 = sqrt(2.); auto stroke = (0. + _st->stroke) / sqrt2; if (toggled < 1) { // Just X or X->V. auto xSize = 0. + _st->xsize; auto xLeft = left + (_st->diameter - xSize) / 2.; auto xTop = top + (_st->diameter - xSize) / 2.; QPointF pathX[] = { { xLeft, xTop + stroke }, { xLeft + stroke, xTop }, { xLeft + (xSize / 2.), xTop + (xSize / 2.) - stroke }, { xLeft + xSize - stroke, xTop }, { xLeft + xSize, xTop + stroke }, { xLeft + (xSize / 2.) + stroke, xTop + (xSize / 2.) }, { xLeft + xSize, xTop + xSize - stroke }, { xLeft + xSize - stroke, xTop + xSize }, { xLeft + (xSize / 2.), xTop + (xSize / 2.) + stroke }, { xLeft + stroke, xTop + xSize }, { xLeft, xTop + xSize - stroke }, { xLeft + (xSize / 2.) - stroke, xTop + (xSize / 2.) }, }; for (auto &point : pathX) { point = rtlpoint(point, outerWidth); } if (toggled > 0) { // X->V. auto vSize = 0. + _st->vsize; auto fSize = (xSize + vSize - 2. * stroke); auto vLeft = left + (_st->diameter - fSize) / 2.; auto vTop = 0. + xTop + _st->vshift; QPointF pathV[] = { { vLeft, vTop + xSize - vSize + stroke }, { vLeft + stroke, vTop + xSize - vSize }, { vLeft + vSize - stroke, vTop + xSize - 2 * stroke }, { vLeft + fSize - stroke, vTop }, { vLeft + fSize, vTop + stroke }, { vLeft + vSize, vTop + xSize - stroke }, { vLeft + vSize, vTop + xSize - stroke }, { vLeft + vSize - stroke, vTop + xSize }, { vLeft + vSize - stroke, vTop + xSize }, { vLeft + vSize - stroke, vTop + xSize }, { vLeft + vSize - 2 * stroke, vTop + xSize - stroke }, { vLeft + vSize - 2 * stroke, vTop + xSize - stroke }, }; for (auto &point : pathV) { point = rtlpoint(point, outerWidth); } p.fillPath(anim::interpolate(pathX, pathV, toggled), brush); } else { // Just X. p.fillPath(anim::path(pathX), brush); } } else { // Just V. auto xSize = 0. + _st->xsize; auto xTop = top + (_st->diameter - xSize) / 2.; auto vSize = 0. + _st->vsize; auto fSize = (xSize + vSize - 2. * stroke); auto vLeft = left + (_st->diameter - (_st->xsize + _st->vsize - 2. * stroke)) / 2.; auto vTop = 0. + xTop + _st->vshift; QPointF pathV[] = { { vLeft, vTop + xSize - vSize + stroke }, { vLeft + stroke, vTop + xSize - vSize }, { vLeft + vSize - stroke, vTop + xSize - 2 * stroke }, { vLeft + fSize - stroke, vTop }, { vLeft + fSize, vTop + stroke }, { vLeft + vSize, vTop + xSize - stroke }, { vLeft + vSize, vTop + xSize - stroke }, { vLeft + vSize - stroke, vTop + xSize }, { vLeft + vSize - stroke, vTop + xSize }, { vLeft + vSize - stroke, vTop + xSize }, { vLeft + vSize - 2 * stroke, vTop + xSize - stroke }, { vLeft + vSize - 2 * stroke, vTop + xSize - stroke }, }; p.fillPath(anim::path(pathV), brush); } } QSize ToggleView::rippleSize() const { return getSize() + 2 * QSize(_st->rippleAreaPadding, _st->rippleAreaPadding); } QImage ToggleView::prepareRippleMask() const { auto size = rippleSize(); return RippleAnimation::roundRectMask(size, size.height() / 2); } bool ToggleView::checkRippleStartPosition(QPoint position) const { return QRect(QPoint(0, 0), rippleSize()).contains(position); } CheckView::CheckView(const style::Check &st, bool checked, base::lambda<void()> updateCallback) : AbstractCheckView(st.duration, checked, std::move(updateCallback)) , _st(&st) { } QSize CheckView::getSize() const { return QSize(_st->diameter, _st->diameter); } void CheckView::setStyle(const style::Check &st) { _st = &st; } void CheckView::paint(Painter &p, int left, int top, int outerWidth, TimeMs ms) { auto toggled = currentAnimationValue(ms); auto pen = anim::pen(_st->untoggledFg, _st->toggledFg, toggled); pen.setWidth(_st->thickness); p.setPen(pen); p.setBrush(anim::brush(_st->bg, anim::color(_st->untoggledFg, _st->toggledFg, toggled), toggled)); { PainterHighQualityEnabler hq(p); p.drawRoundedRect(rtlrect(QRectF(left, top, _st->diameter, _st->diameter).marginsRemoved(QMarginsF(_st->thickness / 2., _st->thickness / 2., _st->thickness / 2., _st->thickness / 2.)), outerWidth), st::buttonRadius - (_st->thickness / 2.), st::buttonRadius - (_st->thickness / 2.)); } if (toggled > 0) { _st->icon.paint(p, QPoint(left, top), outerWidth); } } QSize CheckView::rippleSize() const { return getSize() + 2 * QSize(_st->rippleAreaPadding, _st->rippleAreaPadding); } QImage CheckView::prepareRippleMask() const { return RippleAnimation::ellipseMask(rippleSize()); } bool CheckView::checkRippleStartPosition(QPoint position) const { return QRect(QPoint(0, 0), rippleSize()).contains(position); } RadioView::RadioView(const style::Radio &st, bool checked, base::lambda<void()> updateCallback) : AbstractCheckView(st.duration, checked, std::move(updateCallback)) , _st(&st) { } QSize RadioView::getSize() const { return QSize(_st->diameter, _st->diameter); } void RadioView::setStyle(const style::Radio &st) { _st = &st; } void RadioView::paint(Painter &p, int left, int top, int outerWidth, TimeMs ms) { PainterHighQualityEnabler hq(p); auto toggled = currentAnimationValue(ms); auto pen = anim::pen(_st->untoggledFg, _st->toggledFg, toggled); pen.setWidth(_st->thickness); p.setPen(pen); p.setBrush(_st->bg); //int32 skip = qCeil(_st->thickness / 2.); //p.drawEllipse(_checkRect.marginsRemoved(QMargins(skip, skip, skip, skip))); p.drawEllipse(rtlrect(QRectF(left, top, _st->diameter, _st->diameter).marginsRemoved(QMarginsF(_st->thickness / 2., _st->thickness / 2., _st->thickness / 2., _st->thickness / 2.)), outerWidth)); if (toggled > 0) { p.setPen(Qt::NoPen); p.setBrush(anim::brush(_st->untoggledFg, _st->toggledFg, toggled)); auto skip0 = _st->diameter / 2., skip1 = _st->skip / 10., checkSkip = skip0 * (1. - toggled) + skip1 * toggled; p.drawEllipse(rtlrect(QRectF(left, top, _st->diameter, _st->diameter).marginsRemoved(QMarginsF(checkSkip, checkSkip, checkSkip, checkSkip)), outerWidth)); //int32 fskip = qFloor(checkSkip), cskip = qCeil(checkSkip); //if (2 * fskip < _checkRect.width()) { // if (fskip != cskip) { // p.setOpacity(float64(cskip) - checkSkip); // p.drawEllipse(_checkRect.marginsRemoved(QMargins(fskip, fskip, fskip, fskip))); // p.setOpacity(1.); // } // if (2 * cskip < _checkRect.width()) { // p.drawEllipse(_checkRect.marginsRemoved(QMargins(cskip, cskip, cskip, cskip))); // } //} } } QSize RadioView::rippleSize() const { return getSize() + 2 * QSize(_st->rippleAreaPadding, _st->rippleAreaPadding); } QImage RadioView::prepareRippleMask() const { return RippleAnimation::ellipseMask(rippleSize()); } bool RadioView::checkRippleStartPosition(QPoint position) const { return QRect(QPoint(0, 0), rippleSize()).contains(position); } Checkbox::Checkbox(QWidget *parent, const QString &text, bool checked, const style::Checkbox &st, const style::Check &checkSt) : Checkbox(parent, text, st, std::make_unique<CheckView>(checkSt, checked, [this] { updateCheck(); })) { } Checkbox::Checkbox(QWidget *parent, const QString &text, bool checked, const style::Checkbox &st, const style::Toggle &toggleSt) : Checkbox(parent, text, st, std::make_unique<ToggleView>(toggleSt, checked, [this] { updateCheck(); })) { } Checkbox::Checkbox(QWidget *parent, const QString &text, const style::Checkbox &st, std::unique_ptr<AbstractCheckView> check) : RippleButton(parent, st.ripple) , _st(st) , _check(std::move(check)) , _text(_st.style, text, _checkboxOptions) { resizeToText(); setCursor(style::cur_pointer); } QRect Checkbox::checkRect() const { auto size = _check->getSize(); return QRect({ (_checkAlignment & Qt::AlignHCenter) ? (width() - size.width()) / 2 : (_checkAlignment & Qt::AlignRight) ? (width() - _st.checkPosition.x() - size.width()) : _st.checkPosition.x(), (_checkAlignment & Qt::AlignVCenter) ? (height() - size.height()) / 2 : (_checkAlignment & Qt::AlignBottom) ? (height() - _st.checkPosition.y() - size.height()) : _st.checkPosition.y() }, size); } void Checkbox::setText(const QString &text) { _text.setText(_st.style, text, _checkboxOptions); resizeToText(); update(); } void Checkbox::setCheckAlignment(style::align alignment) { if (_checkAlignment != alignment) { _checkAlignment = alignment; update(); } } bool Checkbox::checked() const { return _check->checked(); } void Checkbox::resizeToText() { if (_st.width <= 0) { resizeToWidth(_text.maxWidth() - _st.width); } else { resizeToWidth(_st.width); } } void Checkbox::setChecked(bool checked, NotifyAboutChange notify) { if (_check->checked() != checked) { _check->setCheckedAnimated(checked); if (notify == NotifyAboutChange::Notify) { checkedChanged.notify(checked, true); } } } void Checkbox::finishAnimating() { _check->finishAnimating(); } int Checkbox::naturalWidth() const { if (_st.width > 0) { return _st.width; } auto result = _st.checkPosition.x() + _check->getSize().width(); if (!_text.isEmpty()) { result += _st.textPosition.x() + _text.maxWidth(); } return result - _st.width; } void Checkbox::paintEvent(QPaintEvent *e) { Painter p(this); auto check = checkRect(); auto ms = getms(); if (isDisabled()) { p.setOpacity(_st.disabledOpacity); } else { auto active = _check->currentAnimationValue(ms); auto color = anim::color(_st.rippleBg, _st.rippleBgActive, active); paintRipple( p, check.x() + _st.rippleAreaPosition.x(), check.y() + _st.rippleAreaPosition.y(), ms, &color); } auto realCheckRect = myrtlrect(check); if (realCheckRect.intersects(e->rect())) { if (isDisabled()) { p.drawPixmapLeft(check.left(), check.top(), width(), _checkCache); } else { _check->paint(p, check.left(), check.top(), width()); } } if (realCheckRect.contains(e->rect())) return; auto leftSkip = _st.checkPosition.x() + check.width() + _st.textPosition.x(); auto availableTextWidth = qMax(width() - leftSkip, 1); if (!_text.isEmpty()) { Assert(!(_checkAlignment & Qt::AlignHCenter)); p.setPen(_st.textFg); auto textSkip = _st.checkPosition.x() + check.width() + _st.textPosition.x(); auto textTop = _st.margin.top() + _st.textPosition.y(); if (_checkAlignment & Qt::AlignLeft) { _text.drawLeftElided( p, textSkip, textTop, availableTextWidth, width()); } else { _text.drawRightElided( p, textSkip, textTop, availableTextWidth, width()); } } } QPixmap Checkbox::grabCheckCache() const { auto checkSize = _check->getSize(); auto image = QImage(checkSize * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); image.fill(Qt::transparent); image.setDevicePixelRatio(cRetinaFactor()); { Painter p(&image); _check->paint(p, 0, 0, checkSize.width()); } return App::pixmapFromImageInPlace(std::move(image)); } void Checkbox::onStateChanged(State was, StateChangeSource source) { RippleButton::onStateChanged(was, source); if (isDisabled() && !(was & StateFlag::Disabled)) { setCursor(style::cur_default); finishAnimating(); _checkCache = grabCheckCache(); } else if (!isDisabled() && (was & StateFlag::Disabled)) { setCursor(style::cur_pointer); _checkCache = QPixmap(); } auto now = state(); if (!isDisabled() && (was & StateFlag::Over) && (now & StateFlag::Over)) { if ((was & StateFlag::Down) && !(now & StateFlag::Down)) { handlePress(); } } } void Checkbox::handlePress() { setChecked(!checked()); } int Checkbox::resizeGetHeight(int newWidth) { return _check->getSize().height(); } QImage Checkbox::prepareRippleMask() const { return _check->prepareRippleMask(); } QPoint Checkbox::prepareRippleStartPosition() const { if (isDisabled()) { return DisabledRippleStartPosition(); } auto position = myrtlpoint(mapFromGlobal(QCursor::pos())) - checkRect().topLeft() - _st.rippleAreaPosition; return _check->checkRippleStartPosition(position) ? position : DisabledRippleStartPosition(); } void RadiobuttonGroup::setValue(int value) { if (_hasValue && _value == value) { return; } _hasValue = true; _value = value; for (auto button : _buttons) { button->handleNewGroupValue(_value); } if (_changedCallback) { _changedCallback(_value); } } Radiobutton::Radiobutton(QWidget *parent, const std::shared_ptr<RadiobuttonGroup> &group, int value, const QString &text, const style::Checkbox &st, const style::Radio &radioSt) : Checkbox(parent, text, st, std::make_unique<RadioView>(radioSt, (group->hasValue() && group->value() == value), [this] { updateCheck(); })) , _group(group) , _value(value) { _group->registerButton(this); subscribe(checkbox()->checkedChanged, [this](bool checked) { if (checked) { _group->setValue(_value); } }); } void Radiobutton::handleNewGroupValue(int value) { auto checked = (value == _value); if (checkbox()->checked() != checked) { checkbox()->setChecked(checked, Ui::Checkbox::NotifyAboutChange::DontNotify); } } void Radiobutton::handlePress() { if (!checkbox()->checked()) { checkbox()->setChecked(true); } } Radiobutton::~Radiobutton() { _group->unregisterButton(this); } } // namespace Ui ���������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/widgets/checkbox.h������������������������������������������0000664�0000000�0000000�00000016036�13262451251�0023453�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/widgets/buttons.h" #include "styles/style_widgets.h" #include <rpl/event_stream.h> namespace Ui { class AbstractCheckView { public: AbstractCheckView(int duration, bool checked, base::lambda<void()> updateCallback); void setCheckedFast(bool checked); void setCheckedAnimated(bool checked); void finishAnimating(); void setUpdateCallback(base::lambda<void()> updateCallback); bool checked() const { return _checked; } float64 currentAnimationValue(TimeMs ms); auto checkedValue() const { return _checks.events_starting_with(checked()); } virtual QSize getSize() const = 0; // Zero instead of ms value means that animation was already updated for this time. // It can be passed to currentAnimationValue() safely. virtual void paint(Painter &p, int left, int top, int outerWidth, TimeMs ms) = 0; virtual QImage prepareRippleMask() const = 0; virtual bool checkRippleStartPosition(QPoint position) const = 0; void paint(Painter &p, int left, int top, int outerWidth) { // Pass zero in ms if the animation was already updated for this time. paint(p, left, top, outerWidth, 0); } virtual ~AbstractCheckView() = default; private: int _duration = 0; bool _checked = false; base::lambda<void()> _updateCallback; Animation _toggleAnimation; rpl::event_stream<bool> _checks; }; class CheckView : public AbstractCheckView { public: CheckView(const style::Check &st, bool checked, base::lambda<void()> updateCallback); void setStyle(const style::Check &st); QSize getSize() const override; void paint(Painter &p, int left, int top, int outerWidth, TimeMs ms) override; QImage prepareRippleMask() const override; bool checkRippleStartPosition(QPoint position) const override; private: QSize rippleSize() const; not_null<const style::Check*> _st; }; class RadioView : public AbstractCheckView { public: RadioView(const style::Radio &st, bool checked, base::lambda<void()> updateCallback); void setStyle(const style::Radio &st); QSize getSize() const override; void paint(Painter &p, int left, int top, int outerWidth, TimeMs ms) override; QImage prepareRippleMask() const override; bool checkRippleStartPosition(QPoint position) const override; private: QSize rippleSize() const; not_null<const style::Radio*> _st; }; class ToggleView : public AbstractCheckView { public: ToggleView(const style::Toggle &st, bool checked, base::lambda<void()> updateCallback); void setStyle(const style::Toggle &st); QSize getSize() const override; void paint(Painter &p, int left, int top, int outerWidth, TimeMs ms) override; QImage prepareRippleMask() const override; bool checkRippleStartPosition(QPoint position) const override; private: void paintXV(Painter &p, int left, int top, int outerWidth, float64 toggled, const QBrush &brush); QSize rippleSize() const; not_null<const style::Toggle*> _st; }; class Checkbox : public RippleButton { public: Checkbox(QWidget *parent, const QString &text, bool checked = false, const style::Checkbox &st = st::defaultCheckbox, const style::Check &checkSt = st::defaultCheck); Checkbox(QWidget *parent, const QString &text, bool checked, const style::Checkbox &st, const style::Toggle &toggleSt); Checkbox(QWidget *parent, const QString &text, const style::Checkbox &st, std::unique_ptr<AbstractCheckView> check); void setText(const QString &text); void setCheckAlignment(style::align alignment); bool checked() const; enum class NotifyAboutChange { Notify, DontNotify, }; void setChecked(bool checked, NotifyAboutChange notify = NotifyAboutChange::Notify); base::Observable<bool> checkedChanged; void finishAnimating(); QMargins getMargins() const override { return _st.margin; } int naturalWidth() const override; void updateCheck() { rtlupdate(checkRect()); } QRect checkRect() const; protected: void paintEvent(QPaintEvent *e) override; void onStateChanged(State was, StateChangeSource source) override; int resizeGetHeight(int newWidth) override; QImage prepareRippleMask() const override; QPoint prepareRippleStartPosition() const override; virtual void handlePress(); private: void resizeToText(); QPixmap grabCheckCache() const; const style::Checkbox &_st; std::unique_ptr<AbstractCheckView> _check; QPixmap _checkCache; Text _text; style::align _checkAlignment = style::al_left; }; class Radiobutton; class RadiobuttonGroup { public: RadiobuttonGroup() = default; RadiobuttonGroup(int value) : _value(value), _hasValue(true) { } void setChangedCallback(base::lambda<void(int value)> callback) { _changedCallback = std::move(callback); } bool hasValue() const { return _hasValue; } int value() const { return _value; } void setValue(int value); private: friend class Radiobutton; void registerButton(Radiobutton *button) { if (!base::contains(_buttons, button)) { _buttons.push_back(button); } } void unregisterButton(Radiobutton *button) { _buttons.erase(std::remove(_buttons.begin(), _buttons.end(), button), _buttons.end()); } int _value = 0; bool _hasValue = false; base::lambda<void(int value)> _changedCallback; std::vector<Radiobutton*> _buttons; }; class Radiobutton : public Checkbox, private base::Subscriber { public: Radiobutton(QWidget *parent, const std::shared_ptr<RadiobuttonGroup> &group, int value, const QString &text, const style::Checkbox &st = st::defaultCheckbox, const style::Radio &radioSt = st::defaultRadio); ~Radiobutton(); protected: void handlePress() override; private: // Hide the names from Checkbox. bool checked() const; void setChecked(bool checked, NotifyAboutChange notify); void checkedChanged(); Checkbox *checkbox() { return this; } const Checkbox *checkbox() const { return this; } friend class RadiobuttonGroup; void handleNewGroupValue(int value); std::shared_ptr<RadiobuttonGroup> _group; int _value = 0; }; template <typename Enum> class Radioenum; template <typename Enum> class RadioenumGroup { public: RadioenumGroup() = default; RadioenumGroup(Enum value) : _group(static_cast<int>(value)) { } template <typename Callback> void setChangedCallback(Callback &&callback) { _group.setChangedCallback([callback](int value) { callback(static_cast<Enum>(value)); }); } bool hasValue() const { return _group.hasValue(); } Enum value() const { return static_cast<Enum>(_group.value()); } void setValue(Enum value) { _group.setValue(static_cast<int>(value)); } private: template <typename OtherEnum> friend class Radioenum; RadiobuttonGroup _group; }; template <typename Enum> class Radioenum : public Radiobutton { public: Radioenum(QWidget *parent, const std::shared_ptr<RadioenumGroup<Enum>> &group, Enum value, const QString &text, const style::Checkbox &st = st::defaultCheckbox) : Radiobutton(parent, std::shared_ptr<RadiobuttonGroup>(group, &group->_group), static_cast<int>(value), text, st) { } }; } // namespace Ui ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/widgets/continuous_sliders.cpp������������������������������0000664�0000000�0000000�00000017522�13262451251�0026154�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/widgets/continuous_sliders.h" namespace Ui { namespace { constexpr auto kByWheelFinishedTimeout = 1000; } // namespace ContinuousSlider::ContinuousSlider(QWidget *parent) : TWidget(parent) { setCursor(style::cur_pointer); } void ContinuousSlider::setDisabled(bool disabled) { if (_disabled != disabled) { _disabled = disabled; setCursor(_disabled ? style::cur_default : style::cur_pointer); update(); } } void ContinuousSlider::setMoveByWheel(bool move) { if (move != moveByWheel()) { if (move) { _byWheelFinished = std::make_unique<SingleTimer>(); _byWheelFinished->setTimeoutHandler([this] { if (_changeFinishedCallback) { _changeFinishedCallback(getCurrentValue()); } }); } else { _byWheelFinished.reset(); } } } void ContinuousSlider::setValue(float64 value) { _value = value; update(); } void ContinuousSlider::setFadeOpacity(float64 opacity) { _fadeOpacity = opacity; update(); } void ContinuousSlider::mouseMoveEvent(QMouseEvent *e) { if (_mouseDown) { updateDownValueFromPos(e->pos()); } } float64 ContinuousSlider::computeValue(const QPoint &pos) const { auto seekRect = myrtlrect(getSeekRect()); auto result = isHorizontal() ? (pos.x() - seekRect.x()) / float64(seekRect.width()) : (1. - (pos.y() - seekRect.y()) / float64(seekRect.height())); return snap(result, 0., 1.); } void ContinuousSlider::mousePressEvent(QMouseEvent *e) { _mouseDown = true; _downValue = computeValue(e->pos()); update(); if (_changeProgressCallback) { _changeProgressCallback(_downValue); } } void ContinuousSlider::mouseReleaseEvent(QMouseEvent *e) { if (_mouseDown) { _mouseDown = false; if (_changeFinishedCallback) { _changeFinishedCallback(_downValue); } _value = _downValue; update(); } } void ContinuousSlider::wheelEvent(QWheelEvent *e) { if (_mouseDown || !moveByWheel()) { return; } #ifdef OS_MAC_OLD constexpr auto step = 120; #else // OS_MAC_OLD constexpr auto step = static_cast<int>(QWheelEvent::DefaultDeltasPerStep); #endif // OS_MAC_OLD constexpr auto coef = 1. / (step * 10.); auto deltaX = e->angleDelta().x(), deltaY = e->angleDelta().y(); if (cPlatform() == dbipMac || cPlatform() == dbipMacOld) { deltaY *= -1; } else { deltaX *= -1; } auto delta = (qAbs(deltaX) > qAbs(deltaY)) ? deltaX : deltaY; auto finalValue = snap(_value + delta * coef, 0., 1.); setValue(finalValue); if (_changeProgressCallback) { _changeProgressCallback(finalValue); } _byWheelFinished->start(kByWheelFinishedTimeout); } void ContinuousSlider::updateDownValueFromPos(const QPoint &pos) { _downValue = computeValue(pos); update(); if (_changeProgressCallback) { _changeProgressCallback(_downValue); } } void ContinuousSlider::enterEventHook(QEvent *e) { setOver(true); } void ContinuousSlider::leaveEventHook(QEvent *e) { setOver(false); } void ContinuousSlider::setOver(bool over) { if (_over == over) return; _over = over; auto from = _over ? 0. : 1., to = _over ? 1. : 0.; _a_over.start([this] { update(); }, from, to, getOverDuration()); } FilledSlider::FilledSlider(QWidget *parent, const style::FilledSlider &st) : ContinuousSlider(parent) , _st(st) { } QRect FilledSlider::getSeekRect() const { return QRect(0, 0, width(), height()); } float64 FilledSlider::getOverDuration() const { return _st.duration; } void FilledSlider::paintEvent(QPaintEvent *e) { Painter p(this); PainterHighQualityEnabler hq(p); p.setPen(Qt::NoPen); auto masterOpacity = fadeOpacity(); auto ms = getms(); auto disabled = isDisabled(); auto over = getCurrentOverFactor(ms); auto lineWidth = _st.lineWidth + ((_st.fullWidth - _st.lineWidth) * over); auto lineWidthRounded = qFloor(lineWidth); auto lineWidthPartial = lineWidth - lineWidthRounded; auto seekRect = getSeekRect(); auto value = getCurrentValue(); auto from = seekRect.x(), mid = qRound(from + value * seekRect.width()), end = from + seekRect.width(); if (mid > from) { p.setOpacity(masterOpacity); p.fillRect(from, height() - lineWidthRounded, (mid - from), lineWidthRounded, disabled ? _st.disabledFg : _st.activeFg); if (lineWidthPartial > 0.01) { p.setOpacity(masterOpacity * lineWidthPartial); p.fillRect(from, height() - lineWidthRounded - 1, (mid - from), 1, disabled ? _st.disabledFg : _st.activeFg); } } if (end > mid && over > 0) { p.setOpacity(masterOpacity * over); p.fillRect(mid, height() - lineWidthRounded, (end - mid), lineWidthRounded, _st.inactiveFg); if (lineWidthPartial > 0.01) { p.setOpacity(masterOpacity * over * lineWidthPartial); p.fillRect(mid, height() - lineWidthRounded - 1, (end - mid), 1, _st.inactiveFg); } } } MediaSlider::MediaSlider(QWidget *parent, const style::MediaSlider &st) : ContinuousSlider(parent) , _st(st) { } QRect MediaSlider::getSeekRect() const { return isHorizontal() ? QRect(_st.seekSize.width() / 2, 0, width() - _st.seekSize.width(), height()) : QRect(0, _st.seekSize.height() / 2, width(), height() - _st.seekSize.width()); } float64 MediaSlider::getOverDuration() const { return _st.duration; } void MediaSlider::paintEvent(QPaintEvent *e) { Painter p(this); PainterHighQualityEnabler hq(p); p.setPen(Qt::NoPen); p.setOpacity(fadeOpacity()); auto horizontal = isHorizontal(); auto ms = getms(); auto radius = _st.width / 2; auto disabled = isDisabled(); auto over = getCurrentOverFactor(ms); auto seekRect = getSeekRect(); auto value = getCurrentValue(); // invert colors and value for vertical if (!horizontal) value = 1. - value; auto markerFrom = (horizontal ? seekRect.x() : seekRect.y()); auto markerLength = (horizontal ? seekRect.width() : seekRect.height()); auto from = _alwaysDisplayMarker ? 0 : markerFrom; auto length = _alwaysDisplayMarker ? (horizontal ? width() : height()) : markerLength; auto mid = qRound(from + value * length); auto end = from + length; auto activeFg = disabled ? _st.activeFgDisabled : anim::brush(_st.activeFg, _st.activeFgOver, over); auto inactiveFg = disabled ? _st.inactiveFgDisabled : anim::brush(_st.inactiveFg, _st.inactiveFgOver, over); if (mid > from) { auto fromClipRect = horizontal ? QRect(0, 0, mid, height()) : QRect(0, 0, width(), mid); auto fromRect = horizontal ? QRect(from, (height() - _st.width) / 2, mid + radius - from, _st.width) : QRect((width() - _st.width) / 2, from, _st.width, mid + radius - from); p.setClipRect(fromClipRect); p.setBrush(horizontal ? activeFg : inactiveFg); p.drawRoundedRect(fromRect, radius, radius); } if (end > mid) { auto endClipRect = horizontal ? QRect(mid, 0, width() - mid, height()) : QRect(0, mid, width(), height() - mid); auto endRect = horizontal ? QRect(mid - radius, (height() - _st.width) / 2, end - (mid - radius), _st.width) : QRect((width() - _st.width) / 2, mid - radius, _st.width, end - (mid - radius)); p.setClipRect(endClipRect); p.setBrush(horizontal ? inactiveFg : activeFg); p.drawRoundedRect(endRect, radius, radius); } auto markerSizeRatio = disabled ? 0. : (_alwaysDisplayMarker ? 1. : over); if (markerSizeRatio > 0) { auto position = qRound(markerFrom + value * markerLength) - (horizontal ? seekRect.x() : seekRect.y()); auto seekButton = horizontal ? QRect(position, (height() - _st.seekSize.height()) / 2, _st.seekSize.width(), _st.seekSize.height()) : QRect((width() - _st.seekSize.width()) / 2, position, _st.seekSize.width(), _st.seekSize.height()); auto size = horizontal ? _st.seekSize.width() : _st.seekSize.height(); auto remove = static_cast<int>(((1. - markerSizeRatio) * size) / 2.); if (remove * 2 < size) { p.setClipRect(rect()); p.setBrush(activeFg); p.drawEllipse(seekButton.marginsRemoved(QMargins(remove, remove, remove, remove))); } } } } // namespace Ui ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/widgets/continuous_sliders.h��������������������������������0000664�0000000�0000000�00000006070�13262451251�0025615�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "styles/style_widgets.h" namespace Ui { class ContinuousSlider : public TWidget { public: ContinuousSlider(QWidget *parent); enum class Direction { Horizontal, Vertical, }; void setDirection(Direction direction) { _direction = direction; update(); } float64 value() const; void setValue(float64 value); void setFadeOpacity(float64 opacity); void setDisabled(bool disabled); bool isDisabled() const { return _disabled; } using Callback = base::lambda<void(float64)>; void setChangeProgressCallback(Callback &&callback) { _changeProgressCallback = std::move(callback); } void setChangeFinishedCallback(Callback &&callback) { _changeFinishedCallback = std::move(callback); } bool isChanging() const { return _mouseDown; } void setMoveByWheel(bool move); protected: void mouseMoveEvent(QMouseEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void wheelEvent(QWheelEvent *e) override; void enterEventHook(QEvent *e) override; void leaveEventHook(QEvent *e) override; float64 fadeOpacity() const { return _fadeOpacity; } float64 getCurrentValue() { return _mouseDown ? _downValue : _value; } float64 getCurrentOverFactor(TimeMs ms) { return _disabled ? 0. : _a_over.current(ms, _over ? 1. : 0.); } Direction getDirection() const { return _direction; } bool isHorizontal() const { return (_direction == Direction::Horizontal); } private: virtual QRect getSeekRect() const = 0; virtual float64 getOverDuration() const = 0; bool moveByWheel() const { return _byWheelFinished != nullptr; } void setOver(bool over); float64 computeValue(const QPoint &pos) const; void updateDownValueFromPos(const QPoint &pos); Direction _direction = Direction::Horizontal; bool _disabled = false; std::unique_ptr<SingleTimer> _byWheelFinished; Callback _changeProgressCallback; Callback _changeFinishedCallback; bool _over = false; Animation _a_over; float64 _value = 0.; bool _mouseDown = false; float64 _downValue = 0.; float64 _fadeOpacity = 1.; }; class FilledSlider : public ContinuousSlider { public: FilledSlider(QWidget *parent, const style::FilledSlider &st); protected: void paintEvent(QPaintEvent *e) override; private: QRect getSeekRect() const override; float64 getOverDuration() const override; const style::FilledSlider &_st; }; class MediaSlider : public ContinuousSlider { public: MediaSlider(QWidget *parent, const style::MediaSlider &st); void setAlwaysDisplayMarker(bool alwaysDisplayMarker) { _alwaysDisplayMarker = alwaysDisplayMarker; update(); } protected: void paintEvent(QPaintEvent *e) override; private: QRect getSeekRect() const override; float64 getOverDuration() const override; const style::MediaSlider &_st; bool _alwaysDisplayMarker = false; }; } // namespace Ui ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/widgets/discrete_sliders.cpp��������������������������������0000664�0000000�0000000�00000020636�13262451251�0025550�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/widgets/discrete_sliders.h" #include "ui/effects/ripple_animation.h" #include "styles/style_widgets.h" namespace Ui { DiscreteSlider::DiscreteSlider(QWidget *parent) : RpWidget(parent) { setCursor(style::cur_pointer); } void DiscreteSlider::setActiveSection(int index) { if (_activeIndex != index) { _activeIndex = index; activateCallback(); } setSelectedSection(index); } void DiscreteSlider::activateCallback() { if (_timerId >= 0) { killTimer(_timerId); _timerId = -1; } auto ms = getms(); if (ms >= _callbackAfterMs) { _sectionActivated.fire_copy(_activeIndex); } else { _timerId = startTimer(_callbackAfterMs - ms, Qt::PreciseTimer); } } void DiscreteSlider::timerEvent(QTimerEvent *e) { activateCallback(); } void DiscreteSlider::setActiveSectionFast(int index) { setActiveSection(index); finishAnimating(); } void DiscreteSlider::finishAnimating() { _a_left.finish(); update(); } void DiscreteSlider::setSelectOnPress(bool selectOnPress) { _selectOnPress = selectOnPress; } void DiscreteSlider::addSection(const QString &label) { _sections.push_back(Section(label, getLabelFont())); resizeToWidth(width()); } void DiscreteSlider::setSections(const QStringList &labels) { Assert(!labels.isEmpty()); _sections.clear(); for_const (auto &label, labels) { _sections.push_back(Section(label, getLabelFont())); } stopAnimation(); if (_activeIndex >= _sections.size()) { _activeIndex = 0; } if (_selected >= _sections.size()) { _selected = 0; } resizeToWidth(width()); } int DiscreteSlider::getCurrentActiveLeft(TimeMs ms) { const auto left = _sections.empty() ? 0 : _sections[_selected].left; return _a_left.current(ms, left); } template <typename Lambda> void DiscreteSlider::enumerateSections(Lambda callback) { for (auto §ion : _sections) { if (!callback(section)) { return; } } } template <typename Lambda> void DiscreteSlider::enumerateSections(Lambda callback) const { for (auto §ion : _sections) { if (!callback(section)) { return; } } } void DiscreteSlider::mousePressEvent(QMouseEvent *e) { auto index = getIndexFromPosition(e->pos()); if (_selectOnPress) { setSelectedSection(index); } startRipple(index); _pressed = index; } void DiscreteSlider::mouseMoveEvent(QMouseEvent *e) { if (_pressed < 0) return; if (_selectOnPress) { setSelectedSection(getIndexFromPosition(e->pos())); } } void DiscreteSlider::mouseReleaseEvent(QMouseEvent *e) { auto pressed = std::exchange(_pressed, -1); if (pressed < 0) return; auto index = getIndexFromPosition(e->pos()); if (pressed < _sections.size()) { if (_sections[pressed].ripple) { _sections[pressed].ripple->lastStop(); } } if (_selectOnPress || index == pressed) { setActiveSection(index); } } void DiscreteSlider::setSelectedSection(int index) { if (index < 0 || index >= _sections.size()) return; if (_selected != index) { auto from = _sections[_selected].left; _selected = index; auto to = _sections[_selected].left; auto duration = getAnimationDuration(); _a_left.start([this] { update(); }, from, to, duration); _callbackAfterMs = getms() + duration; } } int DiscreteSlider::getIndexFromPosition(QPoint pos) { int count = _sections.size(); for (int i = 0; i != count; ++i) { if (_sections[i].left + _sections[i].width > pos.x()) { return i; } } return count - 1; } DiscreteSlider::Section::Section(const QString &label, const style::font &font) : label(label) , labelWidth(font->width(label)) { } SettingsSlider::SettingsSlider(QWidget *parent, const style::SettingsSlider &st) : DiscreteSlider(parent) , _st(st) { setSelectOnPress(_st.ripple.showDuration == 0); } void SettingsSlider::setRippleTopRoundRadius(int radius) { _rippleTopRoundRadius = radius; } const style::font &SettingsSlider::getLabelFont() const { return _st.labelFont; } int SettingsSlider::getAnimationDuration() const { return _st.duration; } void SettingsSlider::resizeSections(int newWidth) { auto count = getSectionsCount(); if (!count) return; auto sectionWidths = countSectionsWidths(newWidth); auto skip = 0; auto x = 0.; auto sectionWidth = sectionWidths.begin(); enumerateSections([&](Section §ion) { Expects(sectionWidth != sectionWidths.end()); section.left = qFloor(x) + skip; x += *sectionWidth; section.width = qRound(x) - (section.left - skip); skip += _st.barSkip; ++sectionWidth; return true; }); stopAnimation(); } std::vector<float64> SettingsSlider::countSectionsWidths( int newWidth) const { auto count = getSectionsCount(); auto sectionsWidth = newWidth - (count - 1) * _st.barSkip; auto sectionWidth = sectionsWidth / float64(count); auto result = std::vector<float64>(count, sectionWidth); auto labelsWidth = 0; auto commonWidth = true; enumerateSections([&](const Section §ion) { labelsWidth += section.labelWidth; if (section.labelWidth >= sectionWidth) { commonWidth = false; } return true; }); // If labelsWidth > sectionsWidth we're screwed anyway. if (!commonWidth && labelsWidth <= sectionsWidth) { auto padding = (sectionsWidth - labelsWidth) / (2. * count); auto currentWidth = result.begin(); enumerateSections([&](const Section §ion) { Expects(currentWidth != result.end()); *currentWidth = padding + section.labelWidth + padding; ++currentWidth; return true; }); } return result; } int SettingsSlider::resizeGetHeight(int newWidth) { resizeSections(newWidth); return _st.height; } void SettingsSlider::startRipple(int sectionIndex) { if (!_st.ripple.showDuration) return; auto index = 0; enumerateSections([this, &index, sectionIndex](Section §ion) { if (index++ == sectionIndex) { if (!section.ripple) { auto mask = prepareRippleMask(sectionIndex, section); section.ripple = std::make_unique<RippleAnimation>( _st.ripple, std::move(mask), [this] { update(); }); } const auto point = mapFromGlobal(QCursor::pos()); section.ripple->add(point - QPoint(section.left, 0)); return false; } return true; }); } QImage SettingsSlider::prepareRippleMask(int sectionIndex, const Section §ion) { auto size = QSize(section.width, height() - _st.rippleBottomSkip); if (!_rippleTopRoundRadius || (sectionIndex > 0 && sectionIndex + 1 < getSectionsCount())) { return RippleAnimation::rectMask(size); } return RippleAnimation::maskByDrawer(size, false, [this, sectionIndex, width = section.width](QPainter &p) { auto plusRadius = _rippleTopRoundRadius + 1; p.drawRoundedRect(0, 0, width, height() + plusRadius, _rippleTopRoundRadius, _rippleTopRoundRadius); if (sectionIndex > 0) { p.fillRect(0, 0, plusRadius, plusRadius, p.brush()); } if (sectionIndex + 1 < getSectionsCount()) { p.fillRect(width - plusRadius, 0, plusRadius, plusRadius, p.brush()); } }); } void SettingsSlider::paintEvent(QPaintEvent *e) { Painter p(this); auto clip = e->rect(); auto ms = getms(); auto activeLeft = getCurrentActiveLeft(ms); p.setFont(_st.labelFont); enumerateSections([this, &p, activeLeft, ms, clip](Section §ion) { auto active = 1. - snap(qAbs(activeLeft - section.left) / float64(section.width), 0., 1.); if (section.ripple) { auto color = anim::color(_st.rippleBg, _st.rippleBgActive, active); section.ripple->paint(p, section.left, 0, width(), ms, &color); if (section.ripple->empty()) { section.ripple.reset(); } } auto from = section.left, tofill = section.width; if (activeLeft > from) { auto fill = qMin(tofill, activeLeft - from); p.fillRect(myrtlrect(from, _st.barTop, fill, _st.barStroke), _st.barFg); from += fill; tofill -= fill; } if (activeLeft + section.width > from) { if (auto fill = qMin(tofill, activeLeft + section.width - from)) { p.fillRect(myrtlrect(from, _st.barTop, fill, _st.barStroke), _st.barFgActive); from += fill; tofill -= fill; } } if (tofill) { p.fillRect(myrtlrect(from, _st.barTop, tofill, _st.barStroke), _st.barFg); } if (myrtlrect(section.left, _st.labelTop, section.width, _st.labelFont->height).intersects(clip)) { p.setPen(anim::pen(_st.labelFg, _st.labelFgActive, active)); p.drawTextLeft(section.left + (section.width - section.labelWidth) / 2, _st.labelTop, width(), section.label, section.labelWidth); } return true; }); } } // namespace Ui ��������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/widgets/discrete_sliders.h����������������������������������0000664�0000000�0000000�00000005326�13262451251�0025214�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/event_stream.h> #include "ui/rp_widget.h" #include "styles/style_widgets.h" namespace Ui { class RippleAnimation; class DiscreteSlider : public RpWidget { public: DiscreteSlider(QWidget *parent); void addSection(const QString &label); void setSections(const QStringList &labels); int activeSection() const { return _activeIndex; } void setActiveSection(int index); void setActiveSectionFast(int index); void finishAnimating(); auto sectionActivated() const { return _sectionActivated.events(); } protected: void timerEvent(QTimerEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; int resizeGetHeight(int newWidth) override = 0; struct Section { Section(const QString &label, const style::font &font); int left, width; QString label; int labelWidth; std::unique_ptr<RippleAnimation> ripple; }; int getCurrentActiveLeft(TimeMs ms); int getSectionsCount() const { return _sections.size(); } template <typename Lambda> void enumerateSections(Lambda callback); template <typename Lambda> void enumerateSections(Lambda callback) const; virtual void startRipple(int sectionIndex) { } void stopAnimation() { _a_left.finish(); } void setSelectOnPress(bool selectOnPress); private: void activateCallback(); virtual const style::font &getLabelFont() const = 0; virtual int getAnimationDuration() const = 0; int getIndexFromPosition(QPoint pos); void setSelectedSection(int index); std::vector<Section> _sections; int _activeIndex = 0; bool _selectOnPress = true; rpl::event_stream<int> _sectionActivated; int _pressed = -1; int _selected = 0; Animation _a_left; int _timerId = -1; TimeMs _callbackAfterMs = 0; }; class SettingsSlider : public DiscreteSlider { public: SettingsSlider(QWidget *parent, const style::SettingsSlider &st = st::defaultSettingsSlider); void setRippleTopRoundRadius(int radius); protected: void paintEvent(QPaintEvent *e) override; int resizeGetHeight(int newWidth) override; void startRipple(int sectionIndex) override; private: const style::font &getLabelFont() const override; int getAnimationDuration() const override; QImage prepareRippleMask(int sectionIndex, const Section §ion); void resizeSections(int newWidth); std::vector<float64> countSectionsWidths(int newWidth) const; const style::SettingsSlider &_st; int _rippleTopRoundRadius = 0; }; } // namespace Ui ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/widgets/dropdown_menu.cpp�����������������������������������0000664�0000000�0000000�00000016734�13262451251�0025105�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/widgets/dropdown_menu.h" #include "application.h" #include "lang/lang_keys.h" namespace Ui { DropdownMenu::DropdownMenu(QWidget *parent, const style::DropdownMenu &st) : InnerDropdown(parent, st.wrap) , _st(st) { _menu = setOwnedWidget(object_ptr<Ui::Menu>(this, _st.menu)); init(); } // Not ready with submenus yet. //DropdownMenu::DropdownMenu(QWidget *parent, QMenu *menu, const style::DropdownMenu &st) : InnerDropdown(parent, st.wrap) //, _st(st) { // _menu = setOwnedWidget(object_ptr<Ui::Menu>(this, menu, _st.menu)); // init(); // // for (auto action : actions()) { // if (auto submenu = action->menu()) { // auto it = _submenus.insert(action, new DropdownMenu(submenu, st)); // it.value()->deleteOnHide(false); // } // } //} void DropdownMenu::init() { InnerDropdown::setHiddenCallback([this] { hideFinish(); }); _menu->setResizedCallback([this] { resizeToContent(); }); _menu->setActivatedCallback([this](QAction *action, int actionTop, TriggeredSource source) { handleActivated(action, actionTop, source); }); _menu->setTriggeredCallback([this](QAction *action, int actionTop, TriggeredSource source) { handleTriggered(action, actionTop, source); }); _menu->setKeyPressDelegate([this](int key) { return handleKeyPress(key); }); _menu->setMouseMoveDelegate([this](QPoint globalPosition) { handleMouseMove(globalPosition); }); _menu->setMousePressDelegate([this](QPoint globalPosition) { handleMousePress(globalPosition); }); _menu->setMouseReleaseDelegate([this](QPoint globalPosition) { handleMouseRelease(globalPosition); }); setMouseTracking(true); hide(); } QAction *DropdownMenu::addAction(const QString &text, const QObject *receiver, const char* member, const style::icon *icon, const style::icon *iconOver) { return _menu->addAction(text, receiver, member, icon, iconOver); } QAction *DropdownMenu::addAction(const QString &text, base::lambda<void()> callback, const style::icon *icon, const style::icon *iconOver) { return _menu->addAction(text, std::move(callback), icon, iconOver); } QAction *DropdownMenu::addSeparator() { return _menu->addSeparator(); } void DropdownMenu::clearActions() { //for (auto submenu : base::take(_submenus)) { // delete submenu; //} return _menu->clearActions(); } DropdownMenu::Actions &DropdownMenu::actions() { return _menu->actions(); } void DropdownMenu::handleActivated(QAction *action, int actionTop, TriggeredSource source) { if (source == TriggeredSource::Mouse) { if (!popupSubmenuFromAction(action, actionTop, source)) { if (auto currentSubmenu = base::take(_activeSubmenu)) { currentSubmenu->hideMenu(true); } } } } void DropdownMenu::handleTriggered(QAction *action, int actionTop, TriggeredSource source) { if (!popupSubmenuFromAction(action, actionTop, source)) { hideMenu(); _triggering = true; emit action->trigger(); _triggering = false; if (_deleteLater) { _deleteLater = false; deleteLater(); } } } // Not ready with submenus yet. bool DropdownMenu::popupSubmenuFromAction(QAction *action, int actionTop, TriggeredSource source) { //if (auto submenu = _submenus.value(action)) { // if (_activeSubmenu == submenu) { // submenu->hideMenu(true); // } else { // popupSubmenu(submenu, actionTop, source); // } // return true; //} return false; } //void DropdownMenu::popupSubmenu(SubmenuPointer submenu, int actionTop, TriggeredSource source) { // if (auto currentSubmenu = base::take(_activeSubmenu)) { // currentSubmenu->hideMenu(true); // } // if (submenu) { // auto menuTopLeft = mapFromGlobal(_menu->mapToGlobal(QPoint(0, 0))); // auto menuBottomRight = mapFromGlobal(_menu->mapToGlobal(QPoint(_menu->width(), _menu->height()))); // QPoint p(menuTopLeft.x() + (rtl() ? (width() - menuBottomRight.x()) : menuBottomRight.x()), menuTopLeft.y() + actionTop); // _activeSubmenu = submenu; // _activeSubmenu->showMenu(geometry().topLeft() + p, this, source); // // _menu->setChildShown(true); // } else { // _menu->setChildShown(false); // } //} void DropdownMenu::forwardKeyPress(int key) { if (!handleKeyPress(key)) { _menu->handleKeyPress(key); } } bool DropdownMenu::handleKeyPress(int key) { if (_activeSubmenu) { _activeSubmenu->handleKeyPress(key); return true; } else if (key == Qt::Key_Escape) { hideMenu(_parent ? true : false); return true; } else if (key == (rtl() ? Qt::Key_Right : Qt::Key_Left)) { if (_parent) { hideMenu(true); return true; } } return false; } void DropdownMenu::handleMouseMove(QPoint globalPosition) { if (_parent) { _parent->forwardMouseMove(globalPosition); } } void DropdownMenu::handleMousePress(QPoint globalPosition) { if (_parent) { _parent->forwardMousePress(globalPosition); } else { hideMenu(); } } void DropdownMenu::handleMouseRelease(QPoint globalPosition) { if (_parent) { _parent->forwardMouseRelease(globalPosition); } else { hideMenu(); } } void DropdownMenu::focusOutEvent(QFocusEvent *e) { hideMenu(); } void DropdownMenu::hideEvent(QHideEvent *e) { if (_deleteOnHide) { if (_triggering) { _deleteLater = true; } else { deleteLater(); } } } void DropdownMenu::hideMenu(bool fast) { if (isHidden()) return; if (_parent && !isHiding()) { _parent->childHiding(this); } if (fast) { hideFast(); } else { hideAnimated(); if (_parent) { _parent->hideMenu(); } } if (_activeSubmenu) { _activeSubmenu->hideMenu(fast); } } void DropdownMenu::childHiding(DropdownMenu *child) { if (_activeSubmenu && _activeSubmenu == child) { _activeSubmenu = SubmenuPointer(); } } void DropdownMenu::hideFinish() { _menu->clearSelection(); if (_hiddenCallback) { _hiddenCallback(); } } // Not ready with submenus yet. //void DropdownMenu::deleteOnHide(bool del) { // _deleteOnHide = del; //} //void DropdownMenu::popup(const QPoint &p) { // showMenu(p, nullptr, TriggeredSource::Mouse); //} // //void DropdownMenu::showMenu(const QPoint &p, DropdownMenu *parent, TriggeredSource source) { // _parent = parent; // // auto menuTopLeft = mapFromGlobal(_menu->mapToGlobal(QPoint(0, 0))); // auto w = p - QPoint(0, menuTopLeft.y()); // auto r = Sandbox::screenGeometry(p); // if (rtl()) { // if (w.x() - width() < r.x() - _padding.left()) { // if (_parent && w.x() + _parent->width() - _padding.left() - _padding.right() + width() - _padding.right() <= r.x() + r.width()) { // w.setX(w.x() + _parent->width() - _padding.left() - _padding.right()); // } else { // w.setX(r.x() - _padding.left()); // } // } else { // w.setX(w.x() - width()); // } // } else { // if (w.x() + width() - _padding.right() > r.x() + r.width()) { // if (_parent && w.x() - _parent->width() + _padding.left() + _padding.right() - width() + _padding.right() >= r.x() - _padding.left()) { // w.setX(w.x() + _padding.left() + _padding.right() - _parent->width() - width() + _padding.left() + _padding.right()); // } else { // w.setX(r.x() + r.width() - width() + _padding.right()); // } // } // } // if (w.y() + height() - _padding.bottom() > r.y() + r.height()) { // if (_parent) { // w.setY(r.y() + r.height() - height() + _padding.bottom()); // } else { // w.setY(p.y() - height() + _padding.bottom()); // } // } // if (w.y() < r.y()) { // w.setY(r.y()); // } // move(w); // // _menu->setShowSource(source); //} DropdownMenu::~DropdownMenu() { clearActions(); } } // namespace Ui ������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/widgets/dropdown_menu.h�������������������������������������0000664�0000000�0000000�00000006142�13262451251�0024542�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "styles/style_widgets.h" #include "ui/widgets/inner_dropdown.h" #include "ui/widgets/menu.h" namespace Ui { class DropdownMenu : public InnerDropdown { Q_OBJECT public: DropdownMenu(QWidget *parent, const style::DropdownMenu &st = st::defaultDropdownMenu); QAction *addAction(const QString &text, const QObject *receiver, const char* member, const style::icon *icon = nullptr, const style::icon *iconOver = nullptr); QAction *addAction(const QString &text, base::lambda<void()> callback, const style::icon *icon = nullptr, const style::icon *iconOver = nullptr); QAction *addSeparator(); void clearActions(); void setHiddenCallback(base::lambda<void()> callback) { _hiddenCallback = std::move(callback); } using Actions = Ui::Menu::Actions; Actions &actions(); ~DropdownMenu(); protected: void focusOutEvent(QFocusEvent *e) override; void hideEvent(QHideEvent *e) override; void keyPressEvent(QKeyEvent *e) override { forwardKeyPress(e->key()); } void mouseMoveEvent(QMouseEvent *e) override { forwardMouseMove(e->globalPos()); } void mousePressEvent(QMouseEvent *e) override { forwardMousePress(e->globalPos()); } private slots: void onHidden() { hideFinish(); } private: // Not ready with submenus yet. DropdownMenu(QWidget *parent, QMenu *menu, const style::DropdownMenu &st = st::defaultDropdownMenu); void deleteOnHide(bool del); void popup(const QPoint &p); void hideMenu(bool fast = false); void childHiding(DropdownMenu *child); void init(); void hideFinish(); using TriggeredSource = Ui::Menu::TriggeredSource; void handleActivated(QAction *action, int actionTop, TriggeredSource source); void handleTriggered(QAction *action, int actionTop, TriggeredSource source); void forwardKeyPress(int key); bool handleKeyPress(int key); void forwardMouseMove(QPoint globalPosition) { _menu->handleMouseMove(globalPosition); } void handleMouseMove(QPoint globalPosition); void forwardMousePress(QPoint globalPosition) { _menu->handleMousePress(globalPosition); } void handleMousePress(QPoint globalPosition); void forwardMouseRelease(QPoint globalPosition) { _menu->handleMouseRelease(globalPosition); } void handleMouseRelease(QPoint globalPosition); using SubmenuPointer = QPointer<DropdownMenu>; bool popupSubmenuFromAction(QAction *action, int actionTop, TriggeredSource source); void popupSubmenu(SubmenuPointer submenu, int actionTop, TriggeredSource source); void showMenu(const QPoint &p, DropdownMenu *parent, TriggeredSource source); const style::DropdownMenu &_st; base::lambda<void()> _hiddenCallback; QPointer<Ui::Menu> _menu; // Not ready with submenus yet. //using Submenus = QMap<QAction*, SubmenuPointer>; //Submenus _submenus; DropdownMenu *_parent = nullptr; SubmenuPointer _activeSubmenu; bool _deleteOnHide = false; bool _triggering = false; bool _deleteLater = false; }; } // namespace Ui ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/widgets/inner_dropdown.cpp����������������������������������0000664�0000000�0000000�00000024246�13262451251�0025251�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/widgets/inner_dropdown.h" #include "mainwindow.h" #include "ui/widgets/scroll_area.h" #include "ui/widgets/shadow.h" #include "ui/effects/panel_animation.h" namespace { constexpr float64 kFadeHeight = 1. / 3; constexpr int kFadeAlphaMax = 160; } // namespace namespace Ui { InnerDropdown::InnerDropdown(QWidget *parent, const style::InnerDropdown &st) : TWidget(parent) , _st(st) , _scroll(this, _st.scroll) { _hideTimer.setSingleShot(true); connect(&_hideTimer, SIGNAL(timeout()), this, SLOT(onHideAnimated())); connect(_scroll, SIGNAL(scrolled()), this, SLOT(onScroll())); if (cPlatform() == dbipMac || cPlatform() == dbipMacOld) { connect(App::wnd()->windowHandle(), SIGNAL(activeChanged()), this, SLOT(onWindowActiveChanged())); } hide(); } QPointer<TWidget> InnerDropdown::doSetOwnedWidget(object_ptr<TWidget> widget) { auto result = QPointer<TWidget>(widget); connect(widget, SIGNAL(heightUpdated()), this, SLOT(onWidgetHeightUpdated())); auto container = _scroll->setOwnedWidget(object_ptr<Container>(_scroll, std::move(widget), _st)); container->resizeToWidth(_scroll->width()); container->moveToLeft(0, 0); container->show(); result->show(); return result; } void InnerDropdown::setMaxHeight(int newMaxHeight) { _maxHeight = newMaxHeight; resizeToContent(); } void InnerDropdown::resizeToContent() { auto newWidth = _st.padding.left() + _st.scrollMargin.left() + _st.scrollMargin.right() + _st.padding.right(); auto newHeight = _st.padding.top() + _st.scrollMargin.top() + _st.scrollMargin.bottom() + _st.padding.bottom(); if (auto widget = static_cast<Container*>(_scroll->widget())) { widget->resizeToContent(); newWidth += widget->width(); newHeight += widget->height(); } if (_maxHeight > 0) { accumulate_min(newHeight, _maxHeight); } if (newWidth != width() || newHeight != height()) { resize(newWidth, newHeight); update(); finishAnimating(); } } void InnerDropdown::onWindowActiveChanged() { if (!App::wnd()->windowHandle()->isActive() && !isHidden()) { leaveEvent(nullptr); } } void InnerDropdown::resizeEvent(QResizeEvent *e) { _scroll->setGeometry(rect().marginsRemoved(_st.padding).marginsRemoved(_st.scrollMargin)); if (auto widget = static_cast<TWidget*>(_scroll->widget())) { widget->resizeToWidth(_scroll->width()); onScroll(); } } void InnerDropdown::onScroll() { if (auto widget = static_cast<TWidget*>(_scroll->widget())) { int visibleTop = _scroll->scrollTop(); int visibleBottom = visibleTop + _scroll->height(); widget->setVisibleTopBottom(visibleTop, visibleBottom); } } void InnerDropdown::paintEvent(QPaintEvent *e) { Painter p(this); auto ms = getms(); if (_a_show.animating(ms)) { if (auto opacity = _a_opacity.current(ms, _hiding ? 0. : 1.)) { // _a_opacity.current(ms)->opacityAnimationCallback()->_showAnimation.reset() if (_showAnimation) { _showAnimation->paintFrame(p, 0, 0, width(), _a_show.current(1.), opacity); } } } else if (_a_opacity.animating(ms)) { p.setOpacity(_a_opacity.current(0.)); p.drawPixmap(0, 0, _cache); } else if (_hiding || isHidden()) { hideFinished(); } else if (_showAnimation) { _showAnimation->paintFrame(p, 0, 0, width(), 1., 1.); _showAnimation.reset(); showChildren(); } else { if (!_cache.isNull()) _cache = QPixmap(); auto inner = rect().marginsRemoved(_st.padding); Shadow::paint(p, inner, width(), _st.shadow); App::roundRect(p, inner, _st.bg, ImageRoundRadius::Small); } } void InnerDropdown::enterEventHook(QEvent *e) { if (_autoHiding) { showAnimated(_origin); } return TWidget::enterEventHook(e); } void InnerDropdown::leaveEventHook(QEvent *e) { if (_autoHiding) { auto ms = getms(); if (_a_show.animating(ms) || _a_opacity.animating(ms)) { hideAnimated(); } else { _hideTimer.start(300); } } return TWidget::leaveEventHook(e); } void InnerDropdown::otherEnter() { if (_autoHiding) { showAnimated(_origin); } } void InnerDropdown::otherLeave() { if (_autoHiding) { auto ms = getms(); if (_a_show.animating(ms) || _a_opacity.animating(ms)) { hideAnimated(); } else { _hideTimer.start(0); } } } void InnerDropdown::setOrigin(PanelAnimation::Origin origin) { _origin = origin; } void InnerDropdown::showAnimated(PanelAnimation::Origin origin) { setOrigin(origin); showAnimated(); } void InnerDropdown::showAnimated() { _hideTimer.stop(); showStarted(); } void InnerDropdown::hideAnimated(HideOption option) { if (isHidden()) return; if (option == HideOption::IgnoreShow) { _ignoreShowEvents = true; } if (_hiding) return; _hideTimer.stop(); startOpacityAnimation(true); } void InnerDropdown::finishAnimating() { if (_a_show.animating()) { _a_show.finish(); showAnimationCallback(); } if (_showAnimation) { _showAnimation.reset(); showChildren(); } if (_a_opacity.animating()) { _a_opacity.finish(); opacityAnimationCallback(); } } void InnerDropdown::showFast() { _hideTimer.stop(); finishAnimating(); if (isHidden()) { showChildren(); show(); } _hiding = false; } void InnerDropdown::hideFast() { if (isHidden()) return; _hideTimer.stop(); finishAnimating(); _hiding = false; hideFinished(); } void InnerDropdown::hideFinished() { _a_show.finish(); _showAnimation.reset(); _cache = QPixmap(); _ignoreShowEvents = false; if (!isHidden()) { if (_hiddenCallback) { _hiddenCallback(); } hide(); } } void InnerDropdown::prepareCache() { if (_a_opacity.animating()) return; auto showAnimation = base::take(_a_show); auto showAnimationData = base::take(_showAnimation); showChildren(); _cache = GrabWidget(this); _showAnimation = base::take(showAnimationData); _a_show = base::take(showAnimation); if (_a_show.animating()) { hideChildren(); } } void InnerDropdown::startOpacityAnimation(bool hiding) { if (hiding) { if (_hideStartCallback) { _hideStartCallback(); } } else if (_showStartCallback) { _showStartCallback(); } _hiding = false; prepareCache(); _hiding = hiding; hideChildren(); _a_opacity.start([this] { opacityAnimationCallback(); }, _hiding ? 1. : 0., _hiding ? 0. : 1., _st.duration); } void InnerDropdown::showStarted() { if (_ignoreShowEvents) return; if (isHidden()) { show(); startShowAnimation(); return; } else if (!_hiding) { return; } startOpacityAnimation(false); } void InnerDropdown::startShowAnimation() { if (_showStartCallback) { _showStartCallback(); } if (!_a_show.animating()) { auto opacityAnimation = base::take(_a_opacity); showChildren(); auto cache = grabForPanelAnimation(); _a_opacity = base::take(opacityAnimation); _showAnimation = std::make_unique<PanelAnimation>(_st.animation, _origin); auto inner = rect().marginsRemoved(_st.padding); _showAnimation->setFinalImage(std::move(cache), QRect(inner.topLeft() * cIntRetinaFactor(), inner.size() * cIntRetinaFactor())); auto corners = App::cornersMask(ImageRoundRadius::Small); _showAnimation->setCornerMasks(corners[0], corners[1], corners[2], corners[3]); _showAnimation->start(); } hideChildren(); _a_show.start([this] { showAnimationCallback(); }, 0., 1., _st.showDuration); } QImage InnerDropdown::grabForPanelAnimation() { SendPendingMoveResizeEvents(this); auto result = QImage(size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); result.setDevicePixelRatio(cRetinaFactor()); result.fill(Qt::transparent); { Painter p(&result); App::roundRect(p, rect().marginsRemoved(_st.padding), _st.bg, ImageRoundRadius::Small); for (auto child : children()) { if (auto widget = qobject_cast<QWidget*>(child)) { widget->render(&p, widget->pos(), widget->rect(), QWidget::DrawChildren | QWidget::IgnoreMask); } } } return result; } void InnerDropdown::opacityAnimationCallback() { update(); if (!_a_opacity.animating()) { if (_hiding) { _hiding = false; hideFinished(); } else if (!_a_show.animating()) { showChildren(); } } } void InnerDropdown::showAnimationCallback() { update(); } bool InnerDropdown::eventFilter(QObject *obj, QEvent *e) { if (e->type() == QEvent::Enter) { otherEnter(); } else if (e->type() == QEvent::Leave) { otherLeave(); } else if (e->type() == QEvent::MouseButtonRelease && static_cast<QMouseEvent*>(e)->button() == Qt::LeftButton) { if (isHidden() || _hiding) { otherEnter(); } else { otherLeave(); } } return false; } int InnerDropdown::resizeGetHeight(int newWidth) { auto newHeight = _st.padding.top() + _st.scrollMargin.top() + _st.scrollMargin.bottom() + _st.padding.bottom(); if (auto widget = static_cast<TWidget*>(_scroll->widget())) { auto containerWidth = newWidth - _st.padding.left() - _st.padding.right() - _st.scrollMargin.left() - _st.scrollMargin.right(); widget->resizeToWidth(containerWidth); newHeight += widget->height(); } if (_maxHeight > 0) { accumulate_min(newHeight, _maxHeight); } return newHeight; } InnerDropdown::Container::Container(QWidget *parent, object_ptr<TWidget> child, const style::InnerDropdown &st) : TWidget(parent) , _child(std::move(child)) , _st(st) { _child->setParent(this); _child->moveToLeft(_st.scrollPadding.left(), _st.scrollPadding.top()); } void InnerDropdown::Container::visibleTopBottomUpdated( int visibleTop, int visibleBottom) { setChildVisibleTopBottom(_child, visibleTop, visibleBottom); } void InnerDropdown::Container::resizeToContent() { auto newWidth = _st.scrollPadding.left() + _st.scrollPadding.right(); auto newHeight = _st.scrollPadding.top() + _st.scrollPadding.bottom(); if (auto child = static_cast<TWidget*>(children().front())) { newWidth += child->width(); newHeight += child->height(); } if (newWidth != width() || newHeight != height()) { resize(newWidth, newHeight); } } int InnerDropdown::Container::resizeGetHeight(int newWidth) { auto innerWidth = newWidth - _st.scrollPadding.left() - _st.scrollPadding.right(); auto result = _st.scrollPadding.top() + _st.scrollPadding.bottom(); _child->resizeToWidth(innerWidth); _child->moveToLeft(_st.scrollPadding.left(), _st.scrollPadding.top()); result += _child->height(); return result; } } // namespace Ui ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/widgets/inner_dropdown.h������������������������������������0000664�0000000�0000000�00000006726�13262451251�0024721�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "styles/style_widgets.h" #include "ui/effects/panel_animation.h" namespace Ui { class ScrollArea; class InnerDropdown : public TWidget { Q_OBJECT public: InnerDropdown(QWidget *parent, const style::InnerDropdown &st = st::defaultInnerDropdown); template <typename Widget> QPointer<Widget> setOwnedWidget(object_ptr<Widget> widget) { auto result = doSetOwnedWidget(std::move(widget)); return QPointer<Widget>(static_cast<Widget*>(result.data())); } bool overlaps(const QRect &globalRect) { if (isHidden() || _a_show.animating() || _a_opacity.animating()) return false; return rect().marginsRemoved(_st.padding).contains(QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size())); } void setAutoHiding(bool autoHiding) { _autoHiding = autoHiding; } void setMaxHeight(int newMaxHeight); void resizeToContent(); void otherEnter(); void otherLeave(); void setShowStartCallback(base::lambda<void()> callback) { _showStartCallback = std::move(callback); } void setHideStartCallback(base::lambda<void()> callback) { _hideStartCallback = std::move(callback); } void setHiddenCallback(base::lambda<void()> callback) { _hiddenCallback = std::move(callback); } bool isHiding() const { return _hiding && _a_opacity.animating(); } enum class HideOption { Default, IgnoreShow, }; void showAnimated(); void setOrigin(PanelAnimation::Origin origin); void showAnimated(PanelAnimation::Origin origin); void hideAnimated(HideOption option = HideOption::Default); void finishAnimating(); void showFast(); void hideFast(); protected: void resizeEvent(QResizeEvent *e) override; void paintEvent(QPaintEvent *e) override; void enterEventHook(QEvent *e) override; void leaveEventHook(QEvent *e) override; bool eventFilter(QObject *obj, QEvent *e) override; int resizeGetHeight(int newWidth) override; private slots: void onHideAnimated() { hideAnimated(); } void onWindowActiveChanged(); void onScroll(); void onWidgetHeightUpdated() { resizeToContent(); } private: QPointer<TWidget> doSetOwnedWidget(object_ptr<TWidget> widget); QImage grabForPanelAnimation(); void startShowAnimation(); void startOpacityAnimation(bool hiding); void prepareCache(); class Container; void showAnimationCallback(); void opacityAnimationCallback(); void hideFinished(); void showStarted(); void updateHeight(); const style::InnerDropdown &_st; PanelAnimation::Origin _origin = PanelAnimation::Origin::TopLeft; std::unique_ptr<PanelAnimation> _showAnimation; Animation _a_show; bool _autoHiding = true; bool _hiding = false; QPixmap _cache; Animation _a_opacity; QTimer _hideTimer; bool _ignoreShowEvents = false; base::lambda<void()> _showStartCallback; base::lambda<void()> _hideStartCallback; base::lambda<void()> _hiddenCallback; object_ptr<Ui::ScrollArea> _scroll; int _maxHeight = 0; }; class InnerDropdown::Container : public TWidget { public: Container(QWidget *parent, object_ptr<TWidget> child, const style::InnerDropdown &st); void resizeToContent(); protected: int resizeGetHeight(int newWidth) override; void visibleTopBottomUpdated( int visibleTop, int visibleBottom) override; private: object_ptr<TWidget> _child; const style::InnerDropdown &_st; }; } // namespace Ui ������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/widgets/input_fields.cpp������������������������������������0000664�0000000�0000000�00000364444�13262451251�0024716�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/widgets/input_fields.h" #include "ui/widgets/popup_menu.h" #include "mainwindow.h" #include "ui/countryinput.h" #include "window/themes/window_theme.h" #include "lang/lang_keys.h" #include "numbers.h" #include "messenger.h" namespace Ui { namespace { constexpr auto kMaxUsernameLength = 32; template <typename InputClass> class InputStyle : public QCommonStyle { public: InputStyle() { setParent(QCoreApplication::instance()); } void drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget = 0) const { } QRect subElementRect(SubElement r, const QStyleOption *opt, const QWidget *widget = 0) const { switch (r) { case SE_LineEditContents: const InputClass *w = widget ? qobject_cast<const InputClass*>(widget) : 0; return w ? w->getTextRect() : QCommonStyle::subElementRect(r, opt, widget); break; } return QCommonStyle::subElementRect(r, opt, widget); } static InputStyle<InputClass> *instance() { if (!_instance) { if (!QGuiApplication::instance()) { return nullptr; } _instance = new InputStyle<InputClass>(); } return _instance; } ~InputStyle() { _instance = nullptr; } private: static InputStyle<InputClass> *_instance; }; template <typename InputClass> InputStyle<InputClass> *InputStyle<InputClass>::_instance = nullptr; } // namespace QByteArray FlatTextarea::serializeTagsList(const TagList &tags) { if (tags.isEmpty()) { return QByteArray(); } QByteArray tagsSerialized; { QDataStream stream(&tagsSerialized, QIODevice::WriteOnly); stream.setVersion(QDataStream::Qt_5_1); stream << qint32(tags.size()); for_const (auto &tag, tags) { stream << qint32(tag.offset) << qint32(tag.length) << tag.id; } } return tagsSerialized; } FlatTextarea::TagList FlatTextarea::deserializeTagsList(QByteArray data, int textLength) { TagList result; if (data.isEmpty()) { return result; } QDataStream stream(data); stream.setVersion(QDataStream::Qt_5_1); qint32 tagCount = 0; stream >> tagCount; if (stream.status() != QDataStream::Ok) { return result; } if (tagCount <= 0 || tagCount > textLength) { return result; } for (int i = 0; i < tagCount; ++i) { qint32 offset = 0, length = 0; QString id; stream >> offset >> length >> id; if (stream.status() != QDataStream::Ok) { return result; } if (offset < 0 || length <= 0 || offset + length > textLength) { return result; } result.push_back({ offset, length, id }); } return result; } QString FlatTextarea::tagsMimeType() { return qsl("application/x-td-field-tags"); } FlatTextarea::FlatTextarea(QWidget *parent, const style::FlatTextarea &st, base::lambda<QString()> placeholderFactory, const QString &v, const TagList &tags) : TWidgetHelper<QTextEdit>(parent) , _placeholderFactory(std::move(placeholderFactory)) , _placeholderVisible(!v.length()) , _lastTextWithTags { v, tags } , _st(st) { setCursor(style::cur_text); setAcceptRichText(false); resize(_st.width, _st.font->height); setFont(_st.font->f); setAlignment(_st.align); subscribe(Lang::Current().updated(), [this] { refreshPlaceholder(); }); refreshPlaceholder(); subscribe(Window::Theme::Background(), [this](const Window::Theme::BackgroundUpdate &update) { if (update.paletteChanged()) { updatePalette(); } }); updatePalette(); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setFrameStyle(QFrame::NoFrame | QFrame::Plain); viewport()->setAutoFillBackground(false); setContentsMargins(0, 0, 0, 0); switch (cScale()) { case dbisOneAndQuarter: _fakeMargin = 1; break; case dbisOneAndHalf: _fakeMargin = 2; break; case dbisTwo: _fakeMargin = 4; break; } setStyleSheet(qsl("QTextEdit { margin: %1px; }").arg(_fakeMargin)); viewport()->setAttribute(Qt::WA_AcceptTouchEvents); _touchTimer.setSingleShot(true); connect(&_touchTimer, SIGNAL(timeout()), this, SLOT(onTouchTimer())); connect(document(), SIGNAL(contentsChange(int, int, int)), this, SLOT(onDocumentContentsChange(int, int, int))); connect(document(), SIGNAL(contentsChanged()), this, SLOT(onDocumentContentsChanged())); connect(this, SIGNAL(undoAvailable(bool)), this, SLOT(onUndoAvailable(bool))); connect(this, SIGNAL(redoAvailable(bool)), this, SLOT(onRedoAvailable(bool))); if (App::wnd()) connect(this, SIGNAL(selectionChanged()), App::wnd(), SLOT(updateGlobalMenu())); if (!_lastTextWithTags.text.isEmpty()) { setTextWithTags(_lastTextWithTags, ClearUndoHistory); } } void FlatTextarea::updatePalette() { auto p = palette(); p.setColor(QPalette::Text, _st.textColor->c); setPalette(p); } TextWithTags FlatTextarea::getTextWithTagsPart(int start, int end) { TextWithTags result; result.text = getTextPart(start, end, &result.tags); return result; } void FlatTextarea::setTextWithTags(const TextWithTags &textWithTags, UndoHistoryAction undoHistoryAction) { _insertedTags = textWithTags.tags; _insertedTagsAreFromMime = false; _realInsertPosition = 0; _realCharsAdded = textWithTags.text.size(); auto doc = document(); auto cursor = QTextCursor(doc->docHandle(), 0); if (undoHistoryAction == ClearUndoHistory) { doc->setUndoRedoEnabled(false); cursor.beginEditBlock(); } else if (undoHistoryAction == MergeWithUndoHistory) { cursor.joinPreviousEditBlock(); } else { cursor.beginEditBlock(); } cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); cursor.insertText(textWithTags.text); cursor.movePosition(QTextCursor::End); cursor.endEditBlock(); if (undoHistoryAction == ClearUndoHistory) { doc->setUndoRedoEnabled(true); } _insertedTags.clear(); _realInsertPosition = -1; finishPlaceholder(); } void FlatTextarea::finishPlaceholder() { _a_placeholderFocused.finish(); _a_placeholderVisible.finish(); update(); } void FlatTextarea::setMaxLength(int32 maxLength) { _maxLength = maxLength; } void FlatTextarea::setMinHeight(int32 minHeight) { _minHeight = minHeight; heightAutoupdated(); } void FlatTextarea::setMaxHeight(int32 maxHeight) { _maxHeight = maxHeight; heightAutoupdated(); } bool FlatTextarea::heightAutoupdated() { if (_minHeight < 0 || _maxHeight < 0 || _inHeightCheck) return false; _inHeightCheck = true; SendPendingMoveResizeEvents(this); int newh = ceil(document()->size().height()) + 2 * fakeMargin(); if (newh > _maxHeight) { newh = _maxHeight; } else if (newh < _minHeight) { newh = _minHeight; } if (height() != newh) { resize(width(), newh); _inHeightCheck = false; return true; } _inHeightCheck = false; return false; } void FlatTextarea::onTouchTimer() { _touchRightButton = true; } bool FlatTextarea::viewportEvent(QEvent *e) { if (e->type() == QEvent::TouchBegin || e->type() == QEvent::TouchUpdate || e->type() == QEvent::TouchEnd || e->type() == QEvent::TouchCancel) { QTouchEvent *ev = static_cast<QTouchEvent*>(e); if (ev->device()->type() == QTouchDevice::TouchScreen) { touchEvent(ev); return QTextEdit::viewportEvent(e); } } return QTextEdit::viewportEvent(e); } void FlatTextarea::touchEvent(QTouchEvent *e) { switch (e->type()) { case QEvent::TouchBegin: { if (_touchPress || e->touchPoints().isEmpty()) return; _touchTimer.start(QApplication::startDragTime()); _touchPress = true; _touchMove = _touchRightButton = false; _touchStart = e->touchPoints().cbegin()->screenPos().toPoint(); } break; case QEvent::TouchUpdate: { if (!_touchPress || e->touchPoints().isEmpty()) return; if (!_touchMove && (e->touchPoints().cbegin()->screenPos().toPoint() - _touchStart).manhattanLength() >= QApplication::startDragDistance()) { _touchMove = true; } } break; case QEvent::TouchEnd: { if (!_touchPress) return; auto weak = make_weak(this); if (!_touchMove && window()) { Qt::MouseButton btn(_touchRightButton ? Qt::RightButton : Qt::LeftButton); QPoint mapped(mapFromGlobal(_touchStart)), winMapped(window()->mapFromGlobal(_touchStart)); if (_touchRightButton) { QContextMenuEvent contextEvent(QContextMenuEvent::Mouse, mapped, _touchStart); contextMenuEvent(&contextEvent); } } if (weak) { _touchTimer.stop(); _touchPress = _touchMove = _touchRightButton = false; } } break; case QEvent::TouchCancel: { _touchPress = false; _touchTimer.stop(); } break; } } QRect FlatTextarea::getTextRect() const { return rect().marginsRemoved(_st.textMrg + st::textRectMargins); } int32 FlatTextarea::fakeMargin() const { return _fakeMargin; } void FlatTextarea::paintEvent(QPaintEvent *e) { Painter p(viewport()); auto ms = getms(); auto r = rect().intersected(e->rect()); p.fillRect(r, _st.bgColor); auto placeholderOpacity = _a_placeholderVisible.current(ms, _placeholderVisible ? 1. : 0.); if (placeholderOpacity > 0.) { p.setOpacity(placeholderOpacity); auto placeholderLeft = anim::interpolate(_st.phShift, 0, placeholderOpacity); p.save(); p.setClipRect(r); p.setFont(_st.font); p.setPen(anim::pen(_st.phColor, _st.phFocusColor, _a_placeholderFocused.current(ms, _focused ? 1. : 0.))); if (_st.phAlign == style::al_topleft && _placeholderAfterSymbols > 0) { int skipWidth = placeholderSkipWidth(); p.drawText(_st.textMrg.left() - _fakeMargin + placeholderLeft + skipWidth, _st.textMrg.top() - _fakeMargin - st::lineWidth + _st.font->ascent, _placeholder); } else { QRect phRect(_st.textMrg.left() - _fakeMargin + _st.phPos.x() + placeholderLeft, _st.textMrg.top() - _fakeMargin + _st.phPos.y(), width() - _st.textMrg.left() - _st.textMrg.right(), height() - _st.textMrg.top() - _st.textMrg.bottom()); p.drawText(phRect, _placeholder, QTextOption(_st.phAlign)); } p.restore(); p.setOpacity(1); } QTextEdit::paintEvent(e); } int FlatTextarea::placeholderSkipWidth() const { if (!_placeholderAfterSymbols) { return 0; } auto text = getTextWithTags().text; auto result = _st.font->width(text.mid(0, _placeholderAfterSymbols)); if (_placeholderAfterSymbols > text.size()) { result += _st.font->spacew; } return result; } void FlatTextarea::focusInEvent(QFocusEvent *e) { if (!_focused) { _focused = true; _a_placeholderFocused.start([this] { update(); }, 0., 1., _st.phDuration); update(); } QTextEdit::focusInEvent(e); } void FlatTextarea::focusOutEvent(QFocusEvent *e) { if (_focused) { _focused = false; _a_placeholderFocused.start([this] { update(); }, 1., 0., _st.phDuration); update(); } QTextEdit::focusOutEvent(e); } QSize FlatTextarea::sizeHint() const { return geometry().size(); } QSize FlatTextarea::minimumSizeHint() const { return geometry().size(); } EmojiPtr FlatTextarea::getSingleEmoji() const { QString text; QTextFragment fragment; getSingleEmojiFragment(text, fragment); if (!text.isEmpty()) { auto format = fragment.charFormat(); auto imageName = static_cast<QTextImageFormat*>(&format)->name(); return Ui::Emoji::FromUrl(imageName); } return nullptr; } QString FlatTextarea::getInlineBotQuery(UserData **outInlineBot, QString *outInlineBotUsername) const { Assert(outInlineBot != nullptr); Assert(outInlineBotUsername != nullptr); auto &text = getTextWithTags().text; auto textLength = text.size(); int inlineUsernameStart = 1, inlineUsernameLength = 0; if (textLength > 2 && text.at(0) == '@' && text.at(1).isLetter()) { inlineUsernameLength = 1; for (int i = inlineUsernameStart + 1; i != textLength; ++i) { if (text.at(i).isLetterOrNumber() || text.at(i).unicode() == '_') { ++inlineUsernameLength; continue; } if (!text.at(i).isSpace()) { inlineUsernameLength = 0; } break; } auto inlineUsernameEnd = inlineUsernameStart + inlineUsernameLength; auto inlineUsernameEqualsText = (inlineUsernameEnd == textLength); auto validInlineUsername = false; if (inlineUsernameEqualsText) { validInlineUsername = text.endsWith(qstr("bot")); } else if (inlineUsernameEnd < textLength && inlineUsernameLength) { validInlineUsername = text.at(inlineUsernameEnd).isSpace(); } if (validInlineUsername) { auto username = text.midRef(inlineUsernameStart, inlineUsernameLength); if (username != *outInlineBotUsername) { *outInlineBotUsername = username.toString(); auto peer = App::peerByName(*outInlineBotUsername); if (peer) { if (peer->isUser()) { *outInlineBot = peer->asUser(); } else { *outInlineBot = nullptr; } } else { *outInlineBot = LookingUpInlineBot; } } if (*outInlineBot == LookingUpInlineBot) return QString(); if (*outInlineBot && (!(*outInlineBot)->botInfo || (*outInlineBot)->botInfo->inlinePlaceholder.isEmpty())) { *outInlineBot = nullptr; } else { return inlineUsernameEqualsText ? QString() : text.mid(inlineUsernameEnd + 1); } } else { inlineUsernameLength = 0; } } if (inlineUsernameLength < 3) { *outInlineBot = nullptr; *outInlineBotUsername = QString(); } return QString(); } QString FlatTextarea::getMentionHashtagBotCommandPart(bool &start) const { start = false; int32 pos = textCursor().position(); if (textCursor().anchor() != pos) return QString(); // check mention / hashtag / bot command QTextDocument *doc(document()); QTextBlock block = doc->findBlock(pos); for (QTextBlock::Iterator iter = block.begin(); !iter.atEnd(); ++iter) { QTextFragment fr(iter.fragment()); if (!fr.isValid()) continue; int32 p = fr.position(), e = (p + fr.length()); if (p >= pos || e < pos) continue; QTextCharFormat f = fr.charFormat(); if (f.isImageFormat()) continue; bool mentionInCommand = false; QString t(fr.text()); for (int i = pos - p; i > 0; --i) { if (t.at(i - 1) == '@') { if ((pos - p - i < 1 || t.at(i).isLetter()) && (i < 2 || !(t.at(i - 2).isLetterOrNumber() || t.at(i - 2) == '_'))) { start = (i == 1) && (p == 0); return t.mid(i - 1, pos - p - i + 1); } else if ((pos - p - i < 1 || t.at(i).isLetter()) && i > 2 && (t.at(i - 2).isLetterOrNumber() || t.at(i - 2) == '_') && !mentionInCommand) { mentionInCommand = true; --i; continue; } return QString(); } else if (t.at(i - 1) == '#') { if (i < 2 || !(t.at(i - 2).isLetterOrNumber() || t.at(i - 2) == '_')) { start = (i == 1) && (p == 0); return t.mid(i - 1, pos - p - i + 1); } return QString(); } else if (t.at(i - 1) == '/') { if (i < 2) { start = (i == 1) && (p == 0); return t.mid(i - 1, pos - p - i + 1); } return QString(); } if (pos - p - i > 127 || (!mentionInCommand && (pos - p - i > 63))) break; if (!t.at(i - 1).isLetterOrNumber() && t.at(i - 1) != '_') break; } break; } return QString(); } void FlatTextarea::insertTag(const QString &text, QString tagId) { auto cursor = textCursor(); int32 pos = cursor.position(); auto doc = document(); auto block = doc->findBlock(pos); for (auto iter = block.begin(); !iter.atEnd(); ++iter) { auto fragment = iter.fragment(); Assert(fragment.isValid()); int fragmentPosition = fragment.position(); int fragmentEnd = (fragmentPosition + fragment.length()); if (fragmentPosition >= pos || fragmentEnd < pos) continue; auto format = fragment.charFormat(); if (format.isImageFormat()) continue; bool mentionInCommand = false; auto fragmentText = fragment.text(); for (int i = pos - fragmentPosition; i > 0; --i) { auto previousChar = fragmentText.at(i - 1); if (previousChar == '@' || previousChar == '#' || previousChar == '/') { if ((i == pos - fragmentPosition || (previousChar == '/' ? fragmentText.at(i).isLetterOrNumber() : fragmentText.at(i).isLetter()) || previousChar == '#') && (i < 2 || !(fragmentText.at(i - 2).isLetterOrNumber() || fragmentText.at(i - 2) == '_'))) { cursor.setPosition(fragmentPosition + i - 1); int till = fragmentPosition + i; for (; (till < fragmentEnd && till < pos); ++till) { auto ch = fragmentText.at(till - fragmentPosition); if (!ch.isLetterOrNumber() && ch != '_' && ch != '@') { break; } } if (till < fragmentEnd && fragmentText.at(till - fragmentPosition) == ' ') { ++till; } cursor.setPosition(till, QTextCursor::KeepAnchor); break; } else if ((i == pos - fragmentPosition || fragmentText.at(i).isLetter()) && fragmentText.at(i - 1) == '@' && i > 2 && (fragmentText.at(i - 2).isLetterOrNumber() || fragmentText.at(i - 2) == '_') && !mentionInCommand) { mentionInCommand = true; --i; continue; } break; } if (pos - fragmentPosition - i > 127 || (!mentionInCommand && (pos - fragmentPosition - i > 63))) break; if (!fragmentText.at(i - 1).isLetterOrNumber() && fragmentText.at(i - 1) != '_') break; } break; } if (tagId.isEmpty()) { QTextCharFormat format = cursor.charFormat(); format.setAnchor(false); format.setAnchorName(QString()); format.clearForeground(); cursor.insertText(text + ' ', format); } else { _insertedTags.clear(); _insertedTags.push_back({ 0, text.size(), tagId }); _insertedTagsAreFromMime = false; cursor.insertText(text + ' '); _insertedTags.clear(); } } void FlatTextarea::setTagMimeProcessor(std::unique_ptr<TagMimeProcessor> &&processor) { _tagMimeProcessor = std::move(processor); } void FlatTextarea::getSingleEmojiFragment(QString &text, QTextFragment &fragment) const { int32 end = textCursor().position(), start = end - 1; if (textCursor().anchor() != end) return; if (start < 0) start = 0; QTextDocument *doc(document()); QTextBlock from = doc->findBlock(start), till = doc->findBlock(end); if (till.isValid()) till = till.next(); for (QTextBlock b = from; b != till; b = b.next()) { for (QTextBlock::Iterator iter = b.begin(); !iter.atEnd(); ++iter) { QTextFragment fr(iter.fragment()); if (!fr.isValid()) continue; int32 p = fr.position(), e = (p + fr.length()); if (p >= end || e <= start) { continue; } QTextCharFormat f = fr.charFormat(); QString t(fr.text()); if (p < start) { t = t.mid(start - p, end - start); } else if (e > end) { t = t.mid(0, end - p); } if (f.isImageFormat() && !t.isEmpty() && t.at(0).unicode() == QChar::ObjectReplacementCharacter) { auto imageName = static_cast<QTextImageFormat*>(&f)->name(); if (Ui::Emoji::FromUrl(imageName)) { fragment = fr; text = t; return; } } return; } } return; } void FlatTextarea::removeSingleEmoji() { QString text; QTextFragment fragment; getSingleEmojiFragment(text, fragment); if (!text.isEmpty()) { QTextCursor t(textCursor()); t.setPosition(fragment.position()); t.setPosition(fragment.position() + fragment.length(), QTextCursor::KeepAnchor); t.removeSelectedText(); setTextCursor(t); } } namespace { class TagAccumulator { public: TagAccumulator(FlatTextarea::TagList *tags) : _tags(tags) { } bool changed() const { return _changed; } void feed(const QString &randomTagId, int currentPosition) { if (randomTagId == _currentTagId) return; if (!_currentTagId.isEmpty()) { int randomPartPosition = _currentTagId.lastIndexOf('/'); Assert(randomPartPosition > 0); bool tagChanged = true; if (_currentTag < _tags->size()) { auto &alreadyTag = _tags->at(_currentTag); if (alreadyTag.offset == _currentStart && alreadyTag.length == currentPosition - _currentStart && alreadyTag.id == _currentTagId.midRef(0, randomPartPosition)) { tagChanged = false; } } if (tagChanged) { _changed = true; TextWithTags::Tag tag = { _currentStart, currentPosition - _currentStart, _currentTagId.mid(0, randomPartPosition), }; if (_currentTag < _tags->size()) { (*_tags)[_currentTag] = tag; } else { _tags->push_back(tag); } } ++_currentTag; } _currentTagId = randomTagId; _currentStart = currentPosition; }; void finish() { if (_currentTag < _tags->size()) { _tags->resize(_currentTag); _changed = true; } } private: FlatTextarea::TagList *_tags; bool _changed = false; int _currentTag = 0; int _currentStart = 0; QString _currentTagId; }; } // namespace QString FlatTextarea::getTextPart(int start, int end, TagList *outTagsList, bool *outTagsChanged) const { if (end >= 0 && end <= start) return QString(); if (start < 0) start = 0; bool full = (start == 0) && (end < 0); TagAccumulator tagAccumulator(outTagsList); QTextDocument *doc(document()); QTextBlock from = full ? doc->begin() : doc->findBlock(start), till = (end < 0) ? doc->end() : doc->findBlock(end); if (till.isValid()) till = till.next(); int32 possibleLen = 0; for (QTextBlock b = from; b != till; b = b.next()) { possibleLen += b.length(); } QString result; result.reserve(possibleLen + 1); if (!full && end < 0) { end = possibleLen; } bool tillFragmentEnd = full; for (auto b = from; b != till; b = b.next()) { for (auto iter = b.begin(); !iter.atEnd(); ++iter) { QTextFragment fragment(iter.fragment()); if (!fragment.isValid()) continue; int32 p = full ? 0 : fragment.position(), e = full ? 0 : (p + fragment.length()); if (!full) { tillFragmentEnd = (e <= end); if (p == end) { tagAccumulator.feed(fragment.charFormat().anchorName(), result.size()); } if (p >= end) { break; } if (e <= start) { continue; } } if (full || p >= start) { tagAccumulator.feed(fragment.charFormat().anchorName(), result.size()); } QTextCharFormat f = fragment.charFormat(); QString emojiText; QString t(fragment.text()); if (!full) { if (p < start) { t = t.mid(start - p, end - start); } else if (e > end) { t = t.mid(0, end - p); } } QChar *ub = t.data(), *uc = ub, *ue = uc + t.size(); for (; uc != ue; ++uc) { switch (uc->unicode()) { case 0xfdd0: // QTextBeginningOfFrame case 0xfdd1: // QTextEndOfFrame case QChar::ParagraphSeparator: case QChar::LineSeparator: { *uc = QLatin1Char('\n'); } break; case QChar::Nbsp: { *uc = QLatin1Char(' '); } break; case QChar::ObjectReplacementCharacter: { if (emojiText.isEmpty() && f.isImageFormat()) { auto imageName = static_cast<QTextImageFormat*>(&f)->name(); if (auto emoji = Ui::Emoji::FromUrl(imageName)) { emojiText = emoji->text(); } } if (uc > ub) result.append(ub, uc - ub); if (!emojiText.isEmpty()) result.append(emojiText); ub = uc + 1; } break; } } if (uc > ub) result.append(ub, uc - ub); } result.append('\n'); } result.chop(1); if (tillFragmentEnd) tagAccumulator.feed(QString(), result.size()); tagAccumulator.finish(); if (outTagsChanged) { *outTagsChanged = tagAccumulator.changed(); } return result; } bool FlatTextarea::hasText() const { QTextDocument *doc(document()); QTextBlock from = doc->begin(), till = doc->end(); if (from == till) return false; for (QTextBlock::Iterator iter = from.begin(); !iter.atEnd(); ++iter) { QTextFragment fragment(iter.fragment()); if (!fragment.isValid()) continue; if (!fragment.text().isEmpty()) return true; } return (from.next() != till); } bool FlatTextarea::isUndoAvailable() const { return _undoAvailable; } bool FlatTextarea::isRedoAvailable() const { return _redoAvailable; } void FlatTextarea::parseLinks() { // some code is duplicated in text.cpp! LinkRanges newLinks; QString text(toPlainText()); if (text.isEmpty()) { if (!_links.isEmpty()) { _links.clear(); emit linksChanged(); } return; } auto len = text.size(); const QChar *start = text.unicode(), *end = start + text.size(); for (auto offset = 0, matchOffset = offset; offset < len;) { auto m = TextUtilities::RegExpDomain().match(text, matchOffset); if (!m.hasMatch()) break; auto domainOffset = m.capturedStart(); auto protocol = m.captured(1).toLower(); auto topDomain = m.captured(3).toLower(); auto isProtocolValid = protocol.isEmpty() || TextUtilities::IsValidProtocol(protocol); auto isTopDomainValid = !protocol.isEmpty() || TextUtilities::IsValidTopDomain(topDomain); if (protocol.isEmpty() && domainOffset > offset + 1 && *(start + domainOffset - 1) == QChar('@')) { auto forMailName = text.mid(offset, domainOffset - offset - 1); auto mMailName = TextUtilities::RegExpMailNameAtEnd().match(forMailName); if (mMailName.hasMatch()) { offset = matchOffset = m.capturedEnd(); continue; } } if (!isProtocolValid || !isTopDomainValid) { offset = matchOffset = m.capturedEnd(); continue; } QStack<const QChar*> parenth; const QChar *domainEnd = start + m.capturedEnd(), *p = domainEnd; for (; p < end; ++p) { QChar ch(*p); if (chIsLinkEnd(ch)) break; // link finished if (chIsAlmostLinkEnd(ch)) { const QChar *endTest = p + 1; while (endTest < end && chIsAlmostLinkEnd(*endTest)) { ++endTest; } if (endTest >= end || chIsLinkEnd(*endTest)) { break; // link finished at p } p = endTest; ch = *p; } if (ch == '(' || ch == '[' || ch == '{' || ch == '<') { parenth.push(p); } else if (ch == ')' || ch == ']' || ch == '}' || ch == '>') { if (parenth.isEmpty()) break; const QChar *q = parenth.pop(), open(*q); if ((ch == ')' && open != '(') || (ch == ']' && open != '[') || (ch == '}' && open != '{') || (ch == '>' && open != '<')) { p = q; break; } } } if (p > domainEnd) { // check, that domain ended if (domainEnd->unicode() != '/' && domainEnd->unicode() != '?') { matchOffset = domainEnd - start; continue; } } newLinks.push_back({ domainOffset - 1, static_cast<int>(p - start - domainOffset + 2) }); offset = matchOffset = p - start; } if (newLinks != _links) { _links = newLinks; emit linksChanged(); } } QStringList FlatTextarea::linksList() const { QStringList result; if (!_links.isEmpty()) { QString text(toPlainText()); for_const (auto &link, _links) { result.push_back(text.mid(link.start + 1, link.length - 2)); } } return result; } void FlatTextarea::insertFromMimeData(const QMimeData *source) { auto mime = tagsMimeType(); auto text = source->text(); if (source->hasFormat(mime)) { auto tagsData = source->data(mime); _insertedTags = deserializeTagsList(tagsData, text.size()); _insertedTagsAreFromMime = true; } else { _insertedTags.clear(); } auto cursor = textCursor(); _realInsertPosition = qMin(cursor.position(), cursor.anchor()); _realCharsAdded = text.size(); QTextEdit::insertFromMimeData(source); if (!_inDrop) { emit spacedReturnedPasted(); _insertedTags.clear(); _realInsertPosition = -1; } } void FlatTextarea::insertEmoji(EmojiPtr emoji, QTextCursor c) { QTextImageFormat imageFormat; auto ew = Ui::Emoji::Size() + st::emojiPadding * cIntRetinaFactor() * 2; auto eh = _st.font->height * cIntRetinaFactor(); imageFormat.setWidth(ew / cIntRetinaFactor()); imageFormat.setHeight(eh / cIntRetinaFactor()); imageFormat.setName(emoji->toUrl()); imageFormat.setVerticalAlignment(QTextCharFormat::AlignBaseline); if (c.charFormat().isAnchor()) { imageFormat.setAnchor(true); imageFormat.setAnchorName(c.charFormat().anchorName()); imageFormat.setForeground(st::defaultTextPalette.linkFg); } static QString objectReplacement(QChar::ObjectReplacementCharacter); c.insertText(objectReplacement, imageFormat); } QVariant FlatTextarea::loadResource(int type, const QUrl &name) { auto imageName = name.toDisplayString(); if (auto emoji = Ui::Emoji::FromUrl(imageName)) { return QVariant(App::emojiSingle(emoji, _st.font->height)); } return QVariant(); } void FlatTextarea::checkContentHeight() { if (heightAutoupdated()) { emit resized(); } } namespace { // Optimization: with null page size document does not re-layout // on each insertText / mergeCharFormat. void prepareFormattingOptimization(QTextDocument *document) { if (!document->pageSize().isNull()) { document->setPageSize(QSizeF(0, 0)); } } void removeTags(style::color textFg, QTextDocument *document, int from, int end) { QTextCursor c(document->docHandle(), 0); c.setPosition(from); c.setPosition(end, QTextCursor::KeepAnchor); QTextCharFormat format; format.setAnchor(false); format.setAnchorName(QString()); format.setForeground(textFg); c.mergeCharFormat(format); } // Returns the position of the first inserted tag or "changedEnd" value if none found. int processInsertedTags(style::color textFg, QTextDocument *document, int changedPosition, int changedEnd, const FlatTextarea::TagList &tags, FlatTextarea::TagMimeProcessor *processor) { int firstTagStart = changedEnd; int applyNoTagFrom = changedEnd; for_const (auto &tag, tags) { int tagFrom = changedPosition + tag.offset; int tagTo = tagFrom + tag.length; accumulate_max(tagFrom, changedPosition); accumulate_min(tagTo, changedEnd); auto tagId = processor ? processor->tagFromMimeTag(tag.id) : tag.id; if (tagTo > tagFrom && !tagId.isEmpty()) { accumulate_min(firstTagStart, tagFrom); prepareFormattingOptimization(document); if (applyNoTagFrom < tagFrom) { removeTags(textFg, document, applyNoTagFrom, tagFrom); } QTextCursor c(document->docHandle(), 0); c.setPosition(tagFrom); c.setPosition(tagTo, QTextCursor::KeepAnchor); QTextCharFormat format; format.setAnchor(true); format.setAnchorName(tagId + '/' + QString::number(rand_value<uint32>())); format.setForeground(st::defaultTextPalette.linkFg); c.mergeCharFormat(format); applyNoTagFrom = tagTo; } } if (applyNoTagFrom < changedEnd) { removeTags(textFg, document, applyNoTagFrom, changedEnd); } return firstTagStart; } // When inserting a part of text inside a tag we need to have // a way to know if the insertion replaced the end of the tag // or it was strictly inside (in the middle) of the tag. bool wasInsertTillTheEndOfTag(QTextBlock block, QTextBlock::iterator fragmentIt, int insertionEnd) { auto insertTagName = fragmentIt.fragment().charFormat().anchorName(); while (true) { for (; !fragmentIt.atEnd(); ++fragmentIt) { auto fragment = fragmentIt.fragment(); bool fragmentOutsideInsertion = (fragment.position() >= insertionEnd); if (fragmentOutsideInsertion) { return (fragment.charFormat().anchorName() != insertTagName); } int fragmentEnd = fragment.position() + fragment.length(); bool notFullFragmentInserted = (fragmentEnd > insertionEnd); if (notFullFragmentInserted) { return false; } } if (block.isValid()) { fragmentIt = block.begin(); block = block.next(); } else { break; } } // Insertion goes till the end of the text => not strictly inside a tag. return true; } struct FormattingAction { enum class Type { Invalid, InsertEmoji, TildeFont, RemoveTag, }; Type type = Type::Invalid; EmojiPtr emoji = nullptr; bool isTilde = false; int intervalStart = 0; int intervalEnd = 0; }; } // namespace void FlatTextarea::processFormatting(int insertPosition, int insertEnd) { // Tilde formatting. auto tildeFormatting = !cRetina() && (font().pixelSize() == 13) && (font().family() == qstr("Open Sans")); auto isTildeFragment = false; auto tildeRegularFont = tildeFormatting ? qsl("Open Sans") : QString(); auto tildeFixedFont = tildeFormatting ? Fonts::GetOverride(qsl("Open Sans Semibold")) : QString(); // First tag handling (the one we inserted text to). bool startTagFound = false; bool breakTagOnNotLetter = false; auto doc = document(); // Apply inserted tags. auto insertedTagsProcessor = _insertedTagsAreFromMime ? _tagMimeProcessor.get() : nullptr; int breakTagOnNotLetterTill = processInsertedTags(_st.textColor, doc, insertPosition, insertEnd, _insertedTags, insertedTagsProcessor); using ActionType = FormattingAction::Type; while (true) { FormattingAction action; auto fromBlock = doc->findBlock(insertPosition); auto tillBlock = doc->findBlock(insertEnd); if (tillBlock.isValid()) tillBlock = tillBlock.next(); for (auto block = fromBlock; block != tillBlock; block = block.next()) { for (auto fragmentIt = block.begin(); !fragmentIt.atEnd(); ++fragmentIt) { auto fragment = fragmentIt.fragment(); Assert(fragment.isValid()); int fragmentPosition = fragment.position(); if (insertPosition >= fragmentPosition + fragment.length()) { continue; } int changedPositionInFragment = insertPosition - fragmentPosition; // Can be negative. int changedEndInFragment = insertEnd - fragmentPosition; if (changedEndInFragment <= 0) { break; } auto charFormat = fragment.charFormat(); if (tildeFormatting) { isTildeFragment = (charFormat.fontFamily() == tildeFixedFont); } auto fragmentText = fragment.text(); auto *textStart = fragmentText.constData(); auto *textEnd = textStart + fragmentText.size(); if (!startTagFound) { startTagFound = true; auto tagName = charFormat.anchorName(); if (!tagName.isEmpty()) { breakTagOnNotLetter = wasInsertTillTheEndOfTag(block, fragmentIt, insertEnd); } } auto *ch = textStart + qMax(changedPositionInFragment, 0); for (; ch < textEnd; ++ch) { int emojiLength = 0; if (auto emoji = Ui::Emoji::Find(ch, textEnd, &emojiLength)) { // Replace emoji if no current action is prepared. if (action.type == ActionType::Invalid) { action.type = ActionType::InsertEmoji; action.emoji = emoji; action.intervalStart = fragmentPosition + (ch - textStart); action.intervalEnd = action.intervalStart + emojiLength; } break; } if (breakTagOnNotLetter && !ch->isLetter()) { // Remove tag name till the end if no current action is prepared. if (action.type != ActionType::Invalid) { break; } breakTagOnNotLetter = false; if (fragmentPosition + (ch - textStart) < breakTagOnNotLetterTill) { action.type = ActionType::RemoveTag; action.intervalStart = fragmentPosition + (ch - textStart); action.intervalEnd = breakTagOnNotLetterTill; break; } } if (tildeFormatting) { // Tilde symbol fix in OpenSans. bool tilde = (ch->unicode() == '~'); if ((tilde && !isTildeFragment) || (!tilde && isTildeFragment)) { if (action.type == ActionType::Invalid) { action.type = ActionType::TildeFont; action.intervalStart = fragmentPosition + (ch - textStart); action.intervalEnd = action.intervalStart + 1; action.isTilde = tilde; } else { ++action.intervalEnd; } } else if (action.type == ActionType::TildeFont) { break; } } if (ch + 1 < textEnd && ch->isHighSurrogate() && (ch + 1)->isLowSurrogate()) { ++ch; ++fragmentPosition; } } if (action.type != ActionType::Invalid) break; } if (action.type != ActionType::Invalid) break; } if (action.type != ActionType::Invalid) { prepareFormattingOptimization(doc); QTextCursor c(doc->docHandle(), 0); c.setPosition(action.intervalStart); c.setPosition(action.intervalEnd, QTextCursor::KeepAnchor); if (action.type == ActionType::InsertEmoji) { insertEmoji(action.emoji, c); insertPosition = action.intervalStart + 1; } else if (action.type == ActionType::RemoveTag) { QTextCharFormat format; format.setAnchor(false); format.setAnchorName(QString()); format.setForeground(_st.textColor); c.mergeCharFormat(format); } else if (action.type == ActionType::TildeFont) { QTextCharFormat format; format.setFontFamily(action.isTilde ? tildeFixedFont : tildeRegularFont); c.mergeCharFormat(format); insertPosition = action.intervalEnd; } } else { break; } } } void FlatTextarea::onDocumentContentsChange(int position, int charsRemoved, int charsAdded) { if (_correcting) return; int insertPosition = (_realInsertPosition >= 0) ? _realInsertPosition : position; int insertLength = (_realInsertPosition >= 0) ? _realCharsAdded : charsAdded; int removePosition = position; int removeLength = charsRemoved; QTextCursor(document()->docHandle(), 0).joinPreviousEditBlock(); _correcting = true; if (_maxLength >= 0) { QTextCursor c(document()->docHandle(), 0); c.movePosition(QTextCursor::End); int32 fullSize = c.position(), toRemove = fullSize - _maxLength; if (toRemove > 0) { if (toRemove > insertLength) { if (insertLength) { c.setPosition(insertPosition); c.setPosition((insertPosition + insertLength), QTextCursor::KeepAnchor); c.removeSelectedText(); } c.setPosition(fullSize - (toRemove - insertLength)); c.setPosition(fullSize, QTextCursor::KeepAnchor); c.removeSelectedText(); } else { c.setPosition(insertPosition + (insertLength - toRemove)); c.setPosition(insertPosition + insertLength, QTextCursor::KeepAnchor); c.removeSelectedText(); } } } _correcting = false; if (insertPosition == removePosition) { if (!_links.isEmpty()) { bool changed = false; for (auto i = _links.begin(); i != _links.end();) { if (i->start + i->length <= insertPosition) { ++i; } else if (i->start >= removePosition + removeLength) { i->start += insertLength - removeLength; ++i; } else { i = _links.erase(i); changed = true; } } if (changed) emit linksChanged(); } } else { parseLinks(); } if (document()->availableRedoSteps() > 0) { QTextCursor(document()->docHandle(), 0).endEditBlock(); return; } if (insertLength <= 0) { QTextCursor(document()->docHandle(), 0).endEditBlock(); return; } _correcting = true; auto pageSize = document()->pageSize(); processFormatting(insertPosition, insertPosition + insertLength); if (document()->pageSize() != pageSize) { document()->setPageSize(pageSize); } _correcting = false; QTextCursor(document()->docHandle(), 0).endEditBlock(); } void FlatTextarea::onDocumentContentsChanged() { if (_correcting) return; auto tagsChanged = false; auto curText = getTextPart(0, -1, &_lastTextWithTags.tags, &tagsChanged); _correcting = true; correctValue(_lastTextWithTags.text, curText, _lastTextWithTags.tags); _correcting = false; bool textOrTagsChanged = tagsChanged || (_lastTextWithTags.text != curText); if (textOrTagsChanged) { _lastTextWithTags.text = curText; emit changed(); checkContentHeight(); } updatePlaceholder(); if (App::wnd()) App::wnd()->updateGlobalMenu(); } void FlatTextarea::onUndoAvailable(bool avail) { _undoAvailable = avail; if (App::wnd()) App::wnd()->updateGlobalMenu(); } void FlatTextarea::onRedoAvailable(bool avail) { _redoAvailable = avail; if (App::wnd()) App::wnd()->updateGlobalMenu(); } void FlatTextarea::setPlaceholder(base::lambda<QString()> placeholderFactory, int afterSymbols) { _placeholderFactory = std::move(placeholderFactory); if (_placeholderAfterSymbols != afterSymbols) { _placeholderAfterSymbols = afterSymbols; updatePlaceholder(); } refreshPlaceholder(); } void FlatTextarea::refreshPlaceholder() { auto skipWidth = placeholderSkipWidth(); auto placeholderText = _placeholderFactory ? _placeholderFactory() : QString(); _placeholder = _st.font->elided(placeholderText, width() - _st.textMrg.left() - _st.textMrg.right() - _st.phPos.x() - 1 - skipWidth); update(); } void FlatTextarea::updatePlaceholder() { auto textSize = (getTextWithTags().text.size() + textCursor().block().layout()->preeditAreaText().size()); auto placeholderVisible = (textSize <= _placeholderAfterSymbols); if (_placeholderVisible != placeholderVisible) { _placeholderVisible = placeholderVisible; _a_placeholderVisible.start([this] { update(); }, _placeholderVisible ? 0. : 1., _placeholderVisible ? 1. : 0., _st.phDuration); } } QMimeData *FlatTextarea::createMimeDataFromSelection() const { QMimeData *result = new QMimeData(); QTextCursor c(textCursor()); int32 start = c.selectionStart(), end = c.selectionEnd(); if (end > start) { TagList tags; result->setText(getTextPart(start, end, &tags)); if (!tags.isEmpty()) { if (_tagMimeProcessor) { for (auto &tag : tags) { tag.id = _tagMimeProcessor->mimeTagFromTag(tag.id); } } result->setData(tagsMimeType(), serializeTagsList(tags)); } } return result; } void FlatTextarea::setSubmitSettings(SubmitSettings settings) { _submitSettings = settings; } void FlatTextarea::keyPressEvent(QKeyEvent *e) { bool shift = e->modifiers().testFlag(Qt::ShiftModifier); bool macmeta = (cPlatform() == dbipMac || cPlatform() == dbipMacOld) && e->modifiers().testFlag(Qt::ControlModifier) && !e->modifiers().testFlag(Qt::MetaModifier) && !e->modifiers().testFlag(Qt::AltModifier); bool ctrl = e->modifiers().testFlag(Qt::ControlModifier) || e->modifiers().testFlag(Qt::MetaModifier); bool enterSubmit = (ctrl && shift); if (ctrl && _submitSettings != SubmitSettings::None && _submitSettings != SubmitSettings::Enter) { enterSubmit = true; } if (!ctrl && !shift && _submitSettings != SubmitSettings::None && _submitSettings != SubmitSettings::CtrlEnter) { enterSubmit = true; } bool enter = (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return); if (macmeta && e->key() == Qt::Key_Backspace) { QTextCursor tc(textCursor()), start(tc); start.movePosition(QTextCursor::StartOfLine); tc.setPosition(start.position(), QTextCursor::KeepAnchor); tc.removeSelectedText(); } else if (enter && enterSubmit) { emit submitted(ctrl && shift); } else if (e->key() == Qt::Key_Escape) { emit cancelled(); } else if (e->key() == Qt::Key_Tab || (ctrl && e->key() == Qt::Key_Backtab)) { if (ctrl) { e->ignore(); } else { emit tabbed(); } } else if (e->key() == Qt::Key_Search || e == QKeySequence::Find) { e->ignore(); #ifdef Q_OS_MAC } else if (e->key() == Qt::Key_E && e->modifiers().testFlag(Qt::ControlModifier)) { auto cursor = textCursor(); int start = cursor.selectionStart(), end = cursor.selectionEnd(); if (end > start) { TagList tags; QApplication::clipboard()->setText(getTextPart(start, end, &tags), QClipboard::FindBuffer); } #endif // Q_OS_MAC } else { QTextCursor tc(textCursor()); if (enter && ctrl) { e->setModifiers(e->modifiers() & ~Qt::ControlModifier); } bool spaceOrReturn = false; QString t(e->text()); if (!t.isEmpty() && t.size() < 3) { if (t.at(0) == '\n' || t.at(0) == '\r' || t.at(0).isSpace() || t.at(0) == QChar::LineSeparator) { spaceOrReturn = true; } } QTextEdit::keyPressEvent(e); if (tc == textCursor()) { bool check = false; if (e->key() == Qt::Key_PageUp || e->key() == Qt::Key_Up) { tc.movePosition(QTextCursor::Start, e->modifiers().testFlag(Qt::ShiftModifier) ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor); check = true; } else if (e->key() == Qt::Key_PageDown || e->key() == Qt::Key_Down) { tc.movePosition(QTextCursor::End, e->modifiers().testFlag(Qt::ShiftModifier) ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor); check = true; } if (check) { if (tc == textCursor()) { e->ignore(); } else { setTextCursor(tc); } } } if (spaceOrReturn) emit spacedReturnedPasted(); } } void FlatTextarea::resizeEvent(QResizeEvent *e) { refreshPlaceholder(); QTextEdit::resizeEvent(e); checkContentHeight(); } void FlatTextarea::mousePressEvent(QMouseEvent *e) { QTextEdit::mousePressEvent(e); } void FlatTextarea::dropEvent(QDropEvent *e) { _inDrop = true; QTextEdit::dropEvent(e); _inDrop = false; _insertedTags.clear(); _realInsertPosition = -1; emit spacedReturnedPasted(); } void FlatTextarea::contextMenuEvent(QContextMenuEvent *e) { if (auto menu = createStandardContextMenu()) { (new Ui::PopupMenu(nullptr, menu))->popup(e->globalPos()); } } FlatInput::FlatInput(QWidget *parent, const style::FlatInput &st, base::lambda<QString()> placeholderFactory, const QString &v) : TWidgetHelper<QLineEdit>(v, parent) , _oldtext(v) , _placeholderFactory(std::move(placeholderFactory)) , _placeholderVisible(!v.length()) , _st(st) , _textMrg(_st.textMrg) { setCursor(style::cur_text); resize(_st.width, _st.height); setFont(_st.font->f); setAlignment(_st.align); subscribe(Lang::Current().updated(), [this] { refreshPlaceholder(); }); refreshPlaceholder(); subscribe(Window::Theme::Background(), [this](const Window::Theme::BackgroundUpdate &update) { if (update.paletteChanged()) { updatePalette(); } }); updatePalette(); connect(this, SIGNAL(textChanged(const QString &)), this, SLOT(onTextChange(const QString &))); connect(this, SIGNAL(textEdited(const QString &)), this, SLOT(onTextEdited())); if (App::wnd()) connect(this, SIGNAL(selectionChanged()), App::wnd(), SLOT(updateGlobalMenu())); setStyle(InputStyle<FlatInput>::instance()); QLineEdit::setTextMargins(0, 0, 0, 0); setContentsMargins(0, 0, 0, 0); setAttribute(Qt::WA_AcceptTouchEvents); _touchTimer.setSingleShot(true); connect(&_touchTimer, SIGNAL(timeout()), this, SLOT(onTouchTimer())); } void FlatInput::updatePalette() { auto p = palette(); p.setColor(QPalette::Text, _st.textColor->c); setPalette(p); } void FlatInput::customUpDown(bool custom) { _customUpDown = custom; } void FlatInput::onTouchTimer() { _touchRightButton = true; } bool FlatInput::event(QEvent *e) { if (e->type() == QEvent::TouchBegin || e->type() == QEvent::TouchUpdate || e->type() == QEvent::TouchEnd || e->type() == QEvent::TouchCancel) { QTouchEvent *ev = static_cast<QTouchEvent*>(e); if (ev->device()->type() == QTouchDevice::TouchScreen) { touchEvent(ev); return QLineEdit::event(e); } } return QLineEdit::event(e); } void FlatInput::touchEvent(QTouchEvent *e) { switch (e->type()) { case QEvent::TouchBegin: { if (_touchPress || e->touchPoints().isEmpty()) return; _touchTimer.start(QApplication::startDragTime()); _touchPress = true; _touchMove = _touchRightButton = false; _touchStart = e->touchPoints().cbegin()->screenPos().toPoint(); } break; case QEvent::TouchUpdate: { if (!_touchPress || e->touchPoints().isEmpty()) return; if (!_touchMove && (e->touchPoints().cbegin()->screenPos().toPoint() - _touchStart).manhattanLength() >= QApplication::startDragDistance()) { _touchMove = true; } } break; case QEvent::TouchEnd: { if (!_touchPress) return; auto weak = make_weak(this); if (!_touchMove && window()) { Qt::MouseButton btn(_touchRightButton ? Qt::RightButton : Qt::LeftButton); QPoint mapped(mapFromGlobal(_touchStart)), winMapped(window()->mapFromGlobal(_touchStart)); if (_touchRightButton) { QContextMenuEvent contextEvent(QContextMenuEvent::Mouse, mapped, _touchStart); contextMenuEvent(&contextEvent); } } if (weak) { _touchTimer.stop(); _touchPress = _touchMove = _touchRightButton = false; } } break; case QEvent::TouchCancel: { _touchPress = false; _touchTimer.stop(); } break; } } void FlatInput::setTextMrg(const QMargins &textMrg) { _textMrg = textMrg; refreshPlaceholder(); update(); } QRect FlatInput::getTextRect() const { return rect().marginsRemoved(_textMrg + QMargins(-2, -1, -2, -1)); } void FlatInput::paintEvent(QPaintEvent *e) { Painter p(this); auto ms = getms(); auto placeholderFocused = _a_placeholderFocused.current(ms, _focused ? 1. : 0.); auto pen = anim::pen(_st.borderColor, _st.borderActive, placeholderFocused); pen.setWidth(_st.borderWidth); p.setPen(pen); p.setBrush(anim::brush(_st.bgColor, _st.bgActive, placeholderFocused)); { PainterHighQualityEnabler hq(p); p.drawRoundedRect(QRectF(0, 0, width(), height()).marginsRemoved(QMarginsF(_st.borderWidth / 2., _st.borderWidth / 2., _st.borderWidth / 2., _st.borderWidth / 2.)), st::buttonRadius - (_st.borderWidth / 2.), st::buttonRadius - (_st.borderWidth / 2.)); } if (!_st.icon.empty()) { _st.icon.paint(p, 0, 0, width()); } auto placeholderOpacity = _a_placeholderVisible.current(ms, _placeholderVisible ? 1. : 0.); if (placeholderOpacity > 0.) { p.setOpacity(placeholderOpacity); auto left = anim::interpolate(_st.phShift, 0, placeholderOpacity); p.save(); p.setClipRect(rect()); QRect phRect(placeholderRect()); phRect.moveLeft(phRect.left() + left); phPrepare(p, placeholderFocused); p.drawText(phRect, _placeholder, QTextOption(_st.phAlign)); p.restore(); } QLineEdit::paintEvent(e); } void FlatInput::focusInEvent(QFocusEvent *e) { if (!_focused) { _focused = true; _a_placeholderFocused.start([this] { update(); }, 0., 1., _st.phDuration); update(); } QLineEdit::focusInEvent(e); emit focused(); } void FlatInput::focusOutEvent(QFocusEvent *e) { if (_focused) { _focused = false; _a_placeholderFocused.start([this] { update(); }, 1., 0., _st.phDuration); update(); } QLineEdit::focusOutEvent(e); emit blurred(); } void FlatInput::resizeEvent(QResizeEvent *e) { refreshPlaceholder(); return QLineEdit::resizeEvent(e); } void FlatInput::setPlaceholder(base::lambda<QString()> placeholderFactory) { _placeholderFactory = std::move(placeholderFactory); refreshPlaceholder(); } void FlatInput::refreshPlaceholder() { auto availw = width() - _textMrg.left() - _textMrg.right() - _st.phPos.x() - 1; auto placeholderText = _placeholderFactory ? _placeholderFactory() : QString(); if (_st.font->width(placeholderText) > availw) { _placeholder = _st.font->elided(placeholderText, availw); } else { _placeholder = placeholderText; } update(); } void FlatInput::contextMenuEvent(QContextMenuEvent *e) { if (auto menu = createStandardContextMenu()) { (new Ui::PopupMenu(nullptr, menu))->popup(e->globalPos()); } } QSize FlatInput::sizeHint() const { return geometry().size(); } QSize FlatInput::minimumSizeHint() const { return geometry().size(); } void FlatInput::updatePlaceholder() { auto hasText = !text().isEmpty(); if (!hasText) { hasText = _lastPreEditTextNotEmpty; } else { _lastPreEditTextNotEmpty = false; } auto placeholderVisible = !hasText; if (_placeholderVisible != placeholderVisible) { _placeholderVisible = placeholderVisible; _a_placeholderVisible.start([this] { update(); }, _placeholderVisible ? 0. : 1., _placeholderVisible ? 1. : 0., _st.phDuration); } } void FlatInput::inputMethodEvent(QInputMethodEvent *e) { QLineEdit::inputMethodEvent(e); auto lastPreEditTextNotEmpty = !e->preeditString().isEmpty(); if (_lastPreEditTextNotEmpty != lastPreEditTextNotEmpty) { _lastPreEditTextNotEmpty = lastPreEditTextNotEmpty; updatePlaceholder(); } } QRect FlatInput::placeholderRect() const { return QRect(_textMrg.left() + _st.phPos.x(), _textMrg.top() + _st.phPos.y(), width() - _textMrg.left() - _textMrg.right(), height() - _textMrg.top() - _textMrg.bottom()); } void FlatInput::correctValue(const QString &was, QString &now) { } void FlatInput::phPrepare(Painter &p, float64 placeholderFocused) { p.setFont(_st.font); p.setPen(anim::pen(_st.phColor, _st.phFocusColor, placeholderFocused)); } void FlatInput::keyPressEvent(QKeyEvent *e) { QString wasText(_oldtext); bool shift = e->modifiers().testFlag(Qt::ShiftModifier), alt = e->modifiers().testFlag(Qt::AltModifier); bool ctrl = e->modifiers().testFlag(Qt::ControlModifier) || e->modifiers().testFlag(Qt::MetaModifier), ctrlGood = true; if (_customUpDown && (e->key() == Qt::Key_Up || e->key() == Qt::Key_Down)) { e->ignore(); } else { QLineEdit::keyPressEvent(e); } QString newText(text()); if (wasText == newText) { // call correct manually correctValue(wasText, newText); _oldtext = newText; if (wasText != _oldtext) emit changed(); updatePlaceholder(); } if (e->key() == Qt::Key_Escape) { emit cancelled(); } else if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) { emit submitted(ctrl && shift); #ifdef Q_OS_MAC } else if (e->key() == Qt::Key_E && e->modifiers().testFlag(Qt::ControlModifier)) { auto selected = selectedText(); if (!selected.isEmpty() && echoMode() == QLineEdit::Normal) { QApplication::clipboard()->setText(selected, QClipboard::FindBuffer); } #endif // Q_OS_MAC } } void FlatInput::onTextEdited() { QString wasText(_oldtext), newText(text()); correctValue(wasText, newText); _oldtext = newText; if (wasText != _oldtext) emit changed(); updatePlaceholder(); if (App::wnd()) App::wnd()->updateGlobalMenu(); } void FlatInput::onTextChange(const QString &text) { _oldtext = text; if (App::wnd()) App::wnd()->updateGlobalMenu(); } InputArea::InputArea( QWidget *parent, const style::InputField &st, base::lambda<QString()> placeholderFactory, const QString &val) : RpWidget(parent) , _st(st) , _inner(this) , _oldtext(val) , _placeholderFactory(std::move(placeholderFactory)) { _inner->setAcceptRichText(false); resize(_st.width, _st.heightMin); setAttribute(Qt::WA_OpaquePaintEvent); _inner->setFont(_st.font->f); subscribe(Lang::Current().updated(), [this] { refreshPlaceholder(); }); refreshPlaceholder(); subscribe(Window::Theme::Background(), [this](const Window::Theme::BackgroundUpdate &update) { if (update.paletteChanged()) { updatePalette(); } }); updatePalette(); _inner->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); _inner->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); _inner->setFrameStyle(QFrame::NoFrame | QFrame::Plain); _inner->viewport()->setAutoFillBackground(false); _inner->setContentsMargins(0, 0, 0, 0); _inner->document()->setDocumentMargin(0); setAttribute(Qt::WA_AcceptTouchEvents); _inner->viewport()->setAttribute(Qt::WA_AcceptTouchEvents); _touchTimer.setSingleShot(true); connect(&_touchTimer, SIGNAL(timeout()), this, SLOT(onTouchTimer())); connect(_inner->document(), SIGNAL(contentsChange(int,int,int)), this, SLOT(onDocumentContentsChange(int,int,int))); connect(_inner->document(), SIGNAL(contentsChanged()), this, SLOT(onDocumentContentsChanged())); connect(_inner, SIGNAL(undoAvailable(bool)), this, SLOT(onUndoAvailable(bool))); connect(_inner, SIGNAL(redoAvailable(bool)), this, SLOT(onRedoAvailable(bool))); if (App::wnd()) connect(_inner, SIGNAL(selectionChanged()), App::wnd(), SLOT(updateGlobalMenu())); setCursor(style::cur_text); heightAutoupdated(); if (!val.isEmpty()) { _inner->setPlainText(val); } _inner->document()->clearUndoRedoStacks(); startBorderAnimation(); startPlaceholderAnimation(); finishAnimating(); } void InputArea::updatePalette() { auto p = palette(); p.setColor(QPalette::Text, _st.textFg->c); setPalette(p); } void InputArea::onTouchTimer() { _touchRightButton = true; } bool InputArea::heightAutoupdated() { if (_st.heightMin < 0 || _st.heightMax < 0 || _inHeightCheck) return false; _inHeightCheck = true; SendPendingMoveResizeEvents(this); int newh = qCeil(_inner->document()->size().height()) + _st.textMargins.top() + _st.textMargins.bottom(); if (newh > _st.heightMax) { newh = _st.heightMax; } else if (newh < _st.heightMin) { newh = _st.heightMin; } if (height() != newh) { resize(width(), newh); _inHeightCheck = false; return true; } _inHeightCheck = false; return false; } void InputArea::checkContentHeight() { if (heightAutoupdated()) { emit resized(); } } InputArea::Inner::Inner(InputArea *parent) : QTextEdit(parent) { } bool InputArea::Inner::viewportEvent(QEvent *e) { if (e->type() == QEvent::TouchBegin || e->type() == QEvent::TouchUpdate || e->type() == QEvent::TouchEnd || e->type() == QEvent::TouchCancel) { QTouchEvent *ev = static_cast<QTouchEvent*>(e); if (ev->device()->type() == QTouchDevice::TouchScreen) { qobject_cast<InputArea*>(parentWidget())->touchEvent(ev); return QTextEdit::viewportEvent(e); } } return QTextEdit::viewportEvent(e); } void InputArea::touchEvent(QTouchEvent *e) { switch (e->type()) { case QEvent::TouchBegin: { if (_touchPress || e->touchPoints().isEmpty()) return; _touchTimer.start(QApplication::startDragTime()); _touchPress = true; _touchMove = _touchRightButton = false; _touchStart = e->touchPoints().cbegin()->screenPos().toPoint(); } break; case QEvent::TouchUpdate: { if (!_touchPress || e->touchPoints().isEmpty()) return; if (!_touchMove && (e->touchPoints().cbegin()->screenPos().toPoint() - _touchStart).manhattanLength() >= QApplication::startDragDistance()) { _touchMove = true; } } break; case QEvent::TouchEnd: { if (!_touchPress) return; auto weak = make_weak(this); if (!_touchMove && window()) { Qt::MouseButton btn(_touchRightButton ? Qt::RightButton : Qt::LeftButton); QPoint mapped(mapFromGlobal(_touchStart)), winMapped(window()->mapFromGlobal(_touchStart)); if (_touchRightButton) { QContextMenuEvent contextEvent(QContextMenuEvent::Mouse, mapped, _touchStart); contextMenuEvent(&contextEvent); } } if (weak) { _touchTimer.stop(); _touchPress = _touchMove = _touchRightButton = false; } } break; case QEvent::TouchCancel: { _touchPress = false; _touchTimer.stop(); } break; } } void InputArea::paintEvent(QPaintEvent *e) { Painter p(this); auto ms = getms(); auto r = rect().intersected(e->rect()); p.fillRect(r, _st.textBg); if (_st.border) { p.fillRect(0, height() - _st.border, width(), _st.border, _st.borderFg); } auto errorDegree = _a_error.current(ms, _error ? 1. : 0.); auto focusedDegree = _a_focused.current(ms, _focused ? 1. : 0.); auto borderShownDegree = _a_borderShown.current(ms, 1.); auto borderOpacity = _a_borderOpacity.current(ms, _borderVisible ? 1. : 0.); if (_st.borderActive && (borderOpacity > 0.)) { auto borderStart = snap(_borderAnimationStart, 0, width()); auto borderFrom = qRound(borderStart * (1. - borderShownDegree)); auto borderTo = borderStart + qRound((width() - borderStart) * borderShownDegree); if (borderTo > borderFrom) { auto borderFg = anim::brush(_st.borderFgActive, _st.borderFgError, errorDegree); p.setOpacity(borderOpacity); p.fillRect(borderFrom, height() - _st.borderActive, borderTo - borderFrom, _st.borderActive, borderFg); p.setOpacity(1); } } if (_st.placeholderScale > 0. && !_placeholderPath.isEmpty()) { auto placeholderShiftDegree = _a_placeholderShifted.current(ms, _placeholderShifted ? 1. : 0.); p.save(); p.setClipRect(r); auto placeholderTop = anim::interpolate(0, _st.placeholderShift, placeholderShiftDegree); QRect r(rect().marginsRemoved(_st.textMargins + _st.placeholderMargins)); r.moveTop(r.top() + placeholderTop); if (rtl()) r.moveLeft(width() - r.left() - r.width()); auto placeholderScale = 1. - (1. - _st.placeholderScale) * placeholderShiftDegree; auto placeholderFg = anim::color(_st.placeholderFg, _st.placeholderFgActive, focusedDegree); placeholderFg = anim::color(placeholderFg, _st.placeholderFgError, errorDegree); PainterHighQualityEnabler hq(p); p.setPen(Qt::NoPen); p.setBrush(placeholderFg); p.translate(r.topLeft()); p.scale(placeholderScale, placeholderScale); p.drawPath(_placeholderPath); p.restore(); } else if (!_placeholder.isEmpty()) { auto placeholderHiddenDegree = _a_placeholderShifted.current(ms, _placeholderShifted ? 1. : 0.); if (placeholderHiddenDegree < 1.) { p.setOpacity(1. - placeholderHiddenDegree); p.save(); p.setClipRect(r); auto placeholderLeft = anim::interpolate(0, -_st.placeholderShift, placeholderHiddenDegree); auto r = rect().marginsRemoved(_st.textMargins + _st.placeholderMargins); r.moveLeft(r.left() + placeholderLeft); if (rtl()) r.moveLeft(width() - r.left() - r.width()); p.setFont(_st.font); p.setPen(anim::pen(_st.placeholderFg, _st.placeholderFgActive, focusedDegree)); p.drawText(r, _placeholder, _st.placeholderAlign); p.restore(); } } TWidget::paintEvent(e); } void InputArea::startBorderAnimation() { auto borderVisible = (_error || _focused); if (_borderVisible != borderVisible) { _borderVisible = borderVisible; if (_borderVisible) { if (_a_borderOpacity.animating()) { _a_borderOpacity.start([this] { update(); }, 0., 1., _st.duration); } else { _a_borderShown.start([this] { update(); }, 0., 1., _st.duration); } } else { _a_borderOpacity.start([this] { update(); }, 1., 0., _st.duration); } } } void InputArea::focusInEvent(QFocusEvent *e) { _borderAnimationStart = (e->reason() == Qt::MouseFocusReason) ? mapFromGlobal(QCursor::pos()).x() : (width() / 2); QTimer::singleShot(0, this, SLOT(onFocusInner())); } void InputArea::mousePressEvent(QMouseEvent *e) { _borderAnimationStart = e->pos().x(); QTimer::singleShot(0, this, SLOT(onFocusInner())); } void InputArea::onFocusInner() { auto borderStart = _borderAnimationStart; _inner->setFocus(); _borderAnimationStart = borderStart; } void InputArea::contextMenuEvent(QContextMenuEvent *e) { _inner->contextMenuEvent(e); } void InputArea::Inner::focusInEvent(QFocusEvent *e) { f()->focusInInner(e->reason() == Qt::MouseFocusReason); QTextEdit::focusInEvent(e); emit f()->focused(); } void InputArea::focusInInner(bool focusByMouse) { _borderAnimationStart = focusByMouse ? mapFromGlobal(QCursor::pos()).x() : (width() / 2); setFocused(true); } void InputArea::Inner::focusOutEvent(QFocusEvent *e) { f()->focusOutInner(); QTextEdit::focusOutEvent(e); emit f()->blurred(); } void InputArea::focusOutInner() { setFocused(false); } void InputArea::setFocused(bool focused) { if (_focused != focused) { _focused = focused; _a_focused.start([this] { update(); }, _focused ? 0. : 1., _focused ? 1. : 0., _st.duration); startPlaceholderAnimation(); startBorderAnimation(); } } QSize InputArea::sizeHint() const { return geometry().size(); } QSize InputArea::minimumSizeHint() const { return geometry().size(); } QString InputArea::getText(int32 start, int32 end) const { if (end >= 0 && end <= start) return QString(); if (start < 0) start = 0; bool full = (start == 0) && (end < 0); QTextDocument *doc(_inner->document()); QTextBlock from = full ? doc->begin() : doc->findBlock(start), till = (end < 0) ? doc->end() : doc->findBlock(end); if (till.isValid()) till = till.next(); int32 possibleLen = 0; for (QTextBlock b = from; b != till; b = b.next()) { possibleLen += b.length(); } QString result; result.reserve(possibleLen + 1); if (!full && end < 0) { end = possibleLen; } for (QTextBlock b = from; b != till; b = b.next()) { for (QTextBlock::Iterator iter = b.begin(); !iter.atEnd(); ++iter) { QTextFragment fragment(iter.fragment()); if (!fragment.isValid()) continue; int32 p = full ? 0 : fragment.position(), e = full ? 0 : (p + fragment.length()); if (!full) { if (p >= end || e <= start) { continue; } } QTextCharFormat f = fragment.charFormat(); QString emojiText; QString t(fragment.text()); if (!full) { if (p < start) { t = t.mid(start - p, end - start); } else if (e > end) { t = t.mid(0, end - p); } } QChar *ub = t.data(), *uc = ub, *ue = uc + t.size(); for (; uc != ue; ++uc) { switch (uc->unicode()) { case 0xfdd0: // QTextBeginningOfFrame case 0xfdd1: // QTextEndOfFrame case QChar::ParagraphSeparator: case QChar::LineSeparator: { *uc = QLatin1Char('\n'); } break; case QChar::Nbsp: { *uc = QLatin1Char(' '); } break; case QChar::ObjectReplacementCharacter: { if (emojiText.isEmpty() && f.isImageFormat()) { auto imageName = static_cast<QTextImageFormat*>(&f)->name(); if (auto emoji = Ui::Emoji::FromUrl(imageName)) { emojiText = emoji->text(); } } if (uc > ub) result.append(ub, uc - ub); if (!emojiText.isEmpty()) result.append(emojiText); ub = uc + 1; } break; } } if (uc > ub) result.append(ub, uc - ub); } result.append('\n'); } result.chop(1); return result; } bool InputArea::hasText() const { QTextDocument *doc(_inner->document()); QTextBlock from = doc->begin(), till = doc->end(); if (from == till) return false; for (QTextBlock::Iterator iter = from.begin(); !iter.atEnd(); ++iter) { QTextFragment fragment(iter.fragment()); if (!fragment.isValid()) continue; if (!fragment.text().isEmpty()) return true; } return (from.next() != till); } bool InputArea::isUndoAvailable() const { return _undoAvailable; } bool InputArea::isRedoAvailable() const { return _redoAvailable; } void InputArea::insertEmoji(EmojiPtr emoji, QTextCursor c) { QTextImageFormat imageFormat; auto ew = Ui::Emoji::Size() + st::emojiPadding * cIntRetinaFactor() * 2; auto eh = _st.font->height * cIntRetinaFactor(); imageFormat.setWidth(ew / cIntRetinaFactor()); imageFormat.setHeight(eh / cIntRetinaFactor()); imageFormat.setName(emoji->toUrl()); imageFormat.setVerticalAlignment(QTextCharFormat::AlignBaseline); static QString objectReplacement(QChar::ObjectReplacementCharacter); c.insertText(objectReplacement, imageFormat); } QVariant InputArea::Inner::loadResource(int type, const QUrl &name) { auto imageName = name.toDisplayString(); if (auto emoji = Ui::Emoji::FromUrl(imageName)) { return QVariant(App::emojiSingle(emoji, f()->_st.font->height)); } return QVariant(); } void InputArea::processDocumentContentsChange(int position, int charsAdded) { int32 replacePosition = -1, replaceLen = 0; EmojiPtr emoji = nullptr; // Tilde formatting. auto tildeFormatting = !cRetina() && (font().pixelSize() == 13) && (font().family() == qstr("Open Sans")); auto isTildeFragment = false; auto tildeRegularFont = tildeFormatting ? qsl("Open Sans") : QString(); auto tildeFixedFont = tildeFormatting ? Fonts::GetOverride(qsl("Open Sans Semibold")) : QString(); QTextDocument *doc(_inner->document()); QTextCursor c(_inner->textCursor()); c.joinPreviousEditBlock(); while (true) { int32 start = position, end = position + charsAdded; QTextBlock from = doc->findBlock(start), till = doc->findBlock(end); if (till.isValid()) till = till.next(); for (QTextBlock b = from; b != till; b = b.next()) { for (QTextBlock::Iterator iter = b.begin(); !iter.atEnd(); ++iter) { QTextFragment fragment(iter.fragment()); if (!fragment.isValid()) continue; int32 fp = fragment.position(), fe = fp + fragment.length(); if (fp >= end || fe <= start) { continue; } if (tildeFormatting) { isTildeFragment = (fragment.charFormat().fontFamily() == tildeFixedFont); } QString t(fragment.text()); const QChar *ch = t.constData(), *e = ch + t.size(); for (; ch != e; ++ch, ++fp) { int32 emojiLen = 0; emoji = Ui::Emoji::Find(ch, e, &emojiLen); if (emoji) { if (replacePosition >= 0) { emoji = 0; // replace tilde char format first } else { replacePosition = fp; replaceLen = emojiLen; } break; } if (tildeFormatting && fp >= position) { // tilde fix in OpenSans bool tilde = (ch->unicode() == '~'); if ((tilde && !isTildeFragment) || (!tilde && isTildeFragment)) { if (replacePosition < 0) { replacePosition = fp; replaceLen = 1; } else { ++replaceLen; } } else if (replacePosition >= 0) { break; } } if (ch + 1 < e && ch->isHighSurrogate() && (ch + 1)->isLowSurrogate()) { ++ch; ++fp; } } if (replacePosition >= 0) break; } if (replacePosition >= 0) break; } if (replacePosition >= 0) { if (!_inner->document()->pageSize().isNull()) { _inner->document()->setPageSize(QSizeF(0, 0)); } QTextCursor c(doc->docHandle(), 0); c.setPosition(replacePosition); c.setPosition(replacePosition + replaceLen, QTextCursor::KeepAnchor); if (emoji) { insertEmoji(emoji, c); } else { QTextCharFormat format; format.setFontFamily(isTildeFragment ? tildeRegularFont : tildeFixedFont); c.mergeCharFormat(format); } charsAdded -= replacePosition + replaceLen - position; position = replacePosition + (emoji ? 1 : replaceLen); emoji = 0; replacePosition = -1; } else { break; } } c.endEditBlock(); } void InputArea::onDocumentContentsChange(int position, int charsRemoved, int charsAdded) { if (_correcting) return; QString oldtext(_oldtext); QTextCursor(_inner->document()->docHandle(), 0).joinPreviousEditBlock(); if (!position) { // Qt bug workaround https://bugreports.qt.io/browse/QTBUG-49062 QTextCursor c(_inner->document()->docHandle(), 0); c.movePosition(QTextCursor::End); if (position + charsAdded > c.position()) { int32 toSubstract = position + charsAdded - c.position(); if (charsRemoved >= toSubstract) { charsAdded -= toSubstract; charsRemoved -= toSubstract; } } } _correcting = true; if (_maxLength >= 0) { QTextCursor c(_inner->document()->docHandle(), 0); c.movePosition(QTextCursor::End); int32 fullSize = c.position(), toRemove = fullSize - _maxLength; if (toRemove > 0) { if (toRemove > charsAdded) { if (charsAdded) { c.setPosition(position); c.setPosition((position + charsAdded), QTextCursor::KeepAnchor); c.removeSelectedText(); } c.setPosition(fullSize - (toRemove - charsAdded)); c.setPosition(fullSize, QTextCursor::KeepAnchor); c.removeSelectedText(); position = _maxLength; charsAdded = 0; charsRemoved += toRemove; } else { c.setPosition(position + (charsAdded - toRemove)); c.setPosition(position + charsAdded, QTextCursor::KeepAnchor); c.removeSelectedText(); charsAdded -= toRemove; } } } _correcting = false; QTextCursor(_inner->document()->docHandle(), 0).endEditBlock(); if (_inner->document()->availableRedoSteps() > 0) return; const int takeBack = 3; position -= takeBack; charsAdded += takeBack; if (position < 0) { charsAdded += position; position = 0; } if (charsAdded <= 0) return; _correcting = true; QSizeF s = _inner->document()->pageSize(); processDocumentContentsChange(position, charsAdded); if (_inner->document()->pageSize() != s) { _inner->document()->setPageSize(s); } _correcting = false; } void InputArea::onDocumentContentsChanged() { if (_correcting) return; setErrorShown(false); auto curText = getText(); if (_oldtext != curText) { _oldtext = curText; emit changed(); checkContentHeight(); } startPlaceholderAnimation(); if (App::wnd()) App::wnd()->updateGlobalMenu(); } void InputArea::onUndoAvailable(bool avail) { _undoAvailable = avail; if (App::wnd()) App::wnd()->updateGlobalMenu(); } void InputArea::onRedoAvailable(bool avail) { _redoAvailable = avail; if (App::wnd()) App::wnd()->updateGlobalMenu(); } void InputArea::setDisplayFocused(bool focused) { setFocused(focused); finishAnimating(); } void InputArea::finishAnimating() { _a_focused.finish(); _a_error.finish(); _a_placeholderShifted.finish(); _a_borderShown.finish(); _a_borderOpacity.finish(); update(); } void InputArea::startPlaceholderAnimation() { auto placeholderShifted = (_focused && _st.placeholderScale > 0.) || !getLastText().isEmpty(); if (_placeholderShifted != placeholderShifted) { _placeholderShifted = placeholderShifted; _a_placeholderShifted.start([this] { update(); }, _placeholderShifted ? 0. : 1., _placeholderShifted ? 1. : 0., _st.duration); } } QMimeData *InputArea::Inner::createMimeDataFromSelection() const { QMimeData *result = new QMimeData(); QTextCursor c(textCursor()); int32 start = c.selectionStart(), end = c.selectionEnd(); if (end > start) { result->setText(f()->getText(start, end)); } return result; } void InputArea::customUpDown(bool custom) { _customUpDown = custom; } void InputArea::setCtrlEnterSubmit(CtrlEnterSubmit ctrlEnterSubmit) { _ctrlEnterSubmit = ctrlEnterSubmit; } void InputArea::Inner::keyPressEvent(QKeyEvent *e) { bool shift = e->modifiers().testFlag(Qt::ShiftModifier), alt = e->modifiers().testFlag(Qt::AltModifier); bool macmeta = (cPlatform() == dbipMac || cPlatform() == dbipMacOld) && e->modifiers().testFlag(Qt::ControlModifier) && !e->modifiers().testFlag(Qt::MetaModifier) && !e->modifiers().testFlag(Qt::AltModifier); bool ctrl = e->modifiers().testFlag(Qt::ControlModifier) || e->modifiers().testFlag(Qt::MetaModifier); bool ctrlGood = (ctrl && shift) || (ctrl && (f()->_ctrlEnterSubmit == CtrlEnterSubmit::CtrlEnter || f()->_ctrlEnterSubmit == CtrlEnterSubmit::Both)) || (!ctrl && !shift && (f()->_ctrlEnterSubmit == CtrlEnterSubmit::Enter || f()->_ctrlEnterSubmit == CtrlEnterSubmit::Both)); bool enter = (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return); if (macmeta && e->key() == Qt::Key_Backspace) { QTextCursor tc(textCursor()), start(tc); start.movePosition(QTextCursor::StartOfLine); tc.setPosition(start.position(), QTextCursor::KeepAnchor); tc.removeSelectedText(); } else if (enter && ctrlGood) { emit f()->submitted(ctrl && shift); } else if (e->key() == Qt::Key_Escape) { e->ignore(); emit f()->cancelled(); } else if (e->key() == Qt::Key_Tab || e->key() == Qt::Key_Backtab) { if (alt || ctrl) { e->ignore(); } else { if (!focusNextPrevChild(e->key() == Qt::Key_Tab && !shift)) { e->ignore(); } } } else if (e->key() == Qt::Key_Search || e == QKeySequence::Find) { e->ignore(); } else if (f()->_customUpDown && (e->key() == Qt::Key_Up || e->key() == Qt::Key_Down)) { e->ignore(); #ifdef Q_OS_MAC } else if (e->key() == Qt::Key_E && e->modifiers().testFlag(Qt::ControlModifier)) { auto cursor = textCursor(); int start = cursor.selectionStart(), end = cursor.selectionEnd(); if (end > start) { QApplication::clipboard()->setText(f()->getText(start, end), QClipboard::FindBuffer); } #endif // Q_OS_MAC } else { QTextCursor tc(textCursor()); if (enter && ctrl) { e->setModifiers(e->modifiers() & ~Qt::ControlModifier); } QTextEdit::keyPressEvent(e); if (tc == textCursor()) { bool check = false; if (e->key() == Qt::Key_PageUp || e->key() == Qt::Key_Up) { tc.movePosition(QTextCursor::Start, e->modifiers().testFlag(Qt::ShiftModifier) ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor); check = true; } else if (e->key() == Qt::Key_PageDown || e->key() == Qt::Key_Down) { tc.movePosition(QTextCursor::End, e->modifiers().testFlag(Qt::ShiftModifier) ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor); check = true; } if (check) { if (tc == textCursor()) { e->ignore(); } else { setTextCursor(tc); } } } } } void InputArea::Inner::contextMenuEvent(QContextMenuEvent *e) { if (auto menu = createStandardContextMenu()) { (new Ui::PopupMenu(nullptr, menu))->popup(e->globalPos()); } } bool InputArea::Inner::canInsertFromMimeData(const QMimeData *source) const { if (source && f()->_mimeDataHook && f()->_mimeDataHook(source, MimeAction::Check)) { return true; } return QTextEdit::canInsertFromMimeData(source); } void InputArea::Inner::insertFromMimeData(const QMimeData *source) { if (source && f()->_mimeDataHook && f()->_mimeDataHook(source, MimeAction::Insert)) { return; } return QTextEdit::insertFromMimeData(source); } void InputArea::resizeEvent(QResizeEvent *e) { refreshPlaceholder(); _inner->setGeometry(rect().marginsRemoved(_st.textMargins)); _borderAnimationStart = width() / 2; TWidget::resizeEvent(e); checkContentHeight(); } void InputArea::refreshPlaceholder() { auto placeholderText = _placeholderFactory ? _placeholderFactory() : QString(); auto availableWidth = width() - _st.textMargins.left() - _st.textMargins.right() - _st.placeholderMargins.left() - _st.placeholderMargins.right() - 1; if (_st.placeholderScale > 0.) { auto placeholderFont = _st.placeholderFont->f; placeholderFont.setStyleStrategy(QFont::PreferMatch); auto metrics = QFontMetrics(placeholderFont); _placeholder = metrics.elidedText(placeholderText, Qt::ElideRight, availableWidth); _placeholderPath = QPainterPath(); if (!_placeholder.isEmpty()) { _placeholderPath.addText(0, QFontMetrics(placeholderFont).ascent(), placeholderFont, _placeholder); } } else { _placeholder = _st.placeholderFont->elided(placeholderText, availableWidth); } update(); } void InputArea::setPlaceholder(base::lambda<QString()> placeholderFactory) { _placeholderFactory = std::move(placeholderFactory); refreshPlaceholder(); } void InputArea::showError() { setErrorShown(true); if (!hasFocus()) { _inner->setFocus(); } } void InputArea::setErrorShown(bool error) { if (_error != error) { _error = error; _a_error.start([this] { update(); }, _error ? 0. : 1., _error ? 1. : 0., _st.duration); startBorderAnimation(); } } InputField::InputField( QWidget *parent, const style::InputField &st, base::lambda<QString()> placeholderFactory, const QString &val) : RpWidget(parent) , _st(st) , _inner(std::make_unique<Inner>(this)) , _oldtext(val) , _placeholderFactory(std::move(placeholderFactory)) { _inner->setAcceptRichText(false); resize(_st.width, _st.heightMin); _inner->setWordWrapMode(QTextOption::NoWrap); if (_st.textBg->c.alphaF() >= 1.) { setAttribute(Qt::WA_OpaquePaintEvent); } _inner->setFont(_st.font->f); _inner->setAlignment(_st.textAlign); subscribe(Lang::Current().updated(), [this] { refreshPlaceholder(); }); refreshPlaceholder(); subscribe(Window::Theme::Background(), [this](const Window::Theme::BackgroundUpdate &update) { if (update.paletteChanged()) { updatePalette(); } }); updatePalette(); _inner->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); _inner->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); _inner->setFrameStyle(QFrame::NoFrame | QFrame::Plain); _inner->viewport()->setAutoFillBackground(false); _inner->setContentsMargins(0, 0, 0, 0); _inner->document()->setDocumentMargin(0); setAttribute(Qt::WA_AcceptTouchEvents); _inner->viewport()->setAttribute(Qt::WA_AcceptTouchEvents); _touchTimer.setSingleShot(true); connect(&_touchTimer, SIGNAL(timeout()), this, SLOT(onTouchTimer())); connect(_inner->document(), SIGNAL(contentsChange(int,int,int)), this, SLOT(onDocumentContentsChange(int,int,int))); connect(_inner->document(), SIGNAL(contentsChanged()), this, SLOT(onDocumentContentsChanged())); connect(_inner.get(), SIGNAL(undoAvailable(bool)), this, SLOT(onUndoAvailable(bool))); connect(_inner.get(), SIGNAL(redoAvailable(bool)), this, SLOT(onRedoAvailable(bool))); if (App::wnd()) connect(_inner.get(), SIGNAL(selectionChanged()), App::wnd(), SLOT(updateGlobalMenu())); setCursor(style::cur_text); if (!val.isEmpty()) { _inner->setPlainText(val); } _inner->document()->clearUndoRedoStacks(); startPlaceholderAnimation(); startBorderAnimation(); finishAnimating(); } void InputField::updatePalette() { auto p = palette(); p.setColor(QPalette::Text, _st.textFg->c); setPalette(p); } void InputField::onTouchTimer() { _touchRightButton = true; } InputField::Inner::Inner(InputField *parent) : QTextEdit(parent) { } bool InputField::Inner::viewportEvent(QEvent *e) { if (e->type() == QEvent::TouchBegin || e->type() == QEvent::TouchUpdate || e->type() == QEvent::TouchEnd || e->type() == QEvent::TouchCancel) { QTouchEvent *ev = static_cast<QTouchEvent*>(e); if (ev->device()->type() == QTouchDevice::TouchScreen) { qobject_cast<InputField*>(parentWidget())->touchEvent(ev); return QTextEdit::viewportEvent(e); } } return QTextEdit::viewportEvent(e); } void InputField::touchEvent(QTouchEvent *e) { switch (e->type()) { case QEvent::TouchBegin: { if (_touchPress || e->touchPoints().isEmpty()) return; _touchTimer.start(QApplication::startDragTime()); _touchPress = true; _touchMove = _touchRightButton = false; _touchStart = e->touchPoints().cbegin()->screenPos().toPoint(); } break; case QEvent::TouchUpdate: { if (!_touchPress || e->touchPoints().isEmpty()) return; if (!_touchMove && (e->touchPoints().cbegin()->screenPos().toPoint() - _touchStart).manhattanLength() >= QApplication::startDragDistance()) { _touchMove = true; } } break; case QEvent::TouchEnd: { if (!_touchPress) return; auto weak = make_weak(this); if (!_touchMove && window()) { Qt::MouseButton btn(_touchRightButton ? Qt::RightButton : Qt::LeftButton); QPoint mapped(mapFromGlobal(_touchStart)), winMapped(window()->mapFromGlobal(_touchStart)); if (_touchRightButton) { QContextMenuEvent contextEvent(QContextMenuEvent::Mouse, mapped, _touchStart); contextMenuEvent(&contextEvent); } } if (weak) { _touchTimer.stop(); _touchPress = _touchMove = _touchRightButton = false; } } break; case QEvent::TouchCancel: { _touchPress = false; _touchTimer.stop(); } break; } } void InputField::paintEvent(QPaintEvent *e) { Painter p(this); auto ms = getms(); QRect r(rect().intersected(e->rect())); if (_st.textBg->c.alphaF() > 0.) { p.fillRect(r, _st.textBg); } if (_st.border) { p.fillRect(0, height() - _st.border, width(), _st.border, _st.borderFg); } auto errorDegree = _a_error.current(ms, _error ? 1. : 0.); auto focusedDegree = _a_focused.current(ms, _focused ? 1. : 0.); auto borderShownDegree = _a_borderShown.current(ms, 1.); auto borderOpacity = _a_borderOpacity.current(ms, _borderVisible ? 1. : 0.); if (_st.borderActive && (borderOpacity > 0.)) { auto borderStart = snap(_borderAnimationStart, 0, width()); auto borderFrom = qRound(borderStart * (1. - borderShownDegree)); auto borderTo = borderStart + qRound((width() - borderStart) * borderShownDegree); if (borderTo > borderFrom) { auto borderFg = anim::brush(_st.borderFgActive, _st.borderFgError, errorDegree); p.setOpacity(borderOpacity); p.fillRect(borderFrom, height() - _st.borderActive, borderTo - borderFrom, _st.borderActive, borderFg); p.setOpacity(1); } } if (_st.placeholderScale > 0. && !_placeholderPath.isEmpty()) { auto placeholderShiftDegree = _a_placeholderShifted.current(ms, _placeholderShifted ? 1. : 0.); p.save(); p.setClipRect(r); auto placeholderTop = anim::interpolate(0, _st.placeholderShift, placeholderShiftDegree); QRect r(rect().marginsRemoved(_st.textMargins + _st.placeholderMargins)); r.moveTop(r.top() + placeholderTop); if (rtl()) r.moveLeft(width() - r.left() - r.width()); auto placeholderScale = 1. - (1. - _st.placeholderScale) * placeholderShiftDegree; auto placeholderFg = anim::color(_st.placeholderFg, _st.placeholderFgActive, focusedDegree); placeholderFg = anim::color(placeholderFg, _st.placeholderFgError, errorDegree); PainterHighQualityEnabler hq(p); p.setPen(Qt::NoPen); p.setBrush(placeholderFg); p.translate(r.topLeft()); p.scale(placeholderScale, placeholderScale); p.drawPath(_placeholderPath); p.restore(); } else if (!_placeholder.isEmpty()) { auto placeholderHiddenDegree = _a_placeholderShifted.current(ms, _placeholderShifted ? 1. : 0.); if (placeholderHiddenDegree < 1.) { p.setOpacity(1. - placeholderHiddenDegree); p.save(); p.setClipRect(r); auto placeholderLeft = anim::interpolate(0, -_st.placeholderShift, placeholderHiddenDegree); QRect r(rect().marginsRemoved(_st.textMargins + _st.placeholderMargins)); r.moveLeft(r.left() + placeholderLeft); if (rtl()) r.moveLeft(width() - r.left() - r.width()); p.setFont(_st.font); p.setPen(anim::pen(_st.placeholderFg, _st.placeholderFgActive, focusedDegree)); p.drawText(r, _placeholder, _st.placeholderAlign); p.restore(); } } TWidget::paintEvent(e); } void InputField::focusInEvent(QFocusEvent *e) { _borderAnimationStart = (e->reason() == Qt::MouseFocusReason) ? mapFromGlobal(QCursor::pos()).x() : (width() / 2); QTimer::singleShot(0, this, SLOT(onFocusInner())); } void InputField::mousePressEvent(QMouseEvent *e) { _borderAnimationStart = e->pos().x(); QTimer::singleShot(0, this, SLOT(onFocusInner())); } void InputField::onFocusInner() { auto borderStart = _borderAnimationStart; _inner->setFocus(); _borderAnimationStart = borderStart; } void InputField::contextMenuEvent(QContextMenuEvent *e) { _inner->contextMenuEvent(e); } void InputField::Inner::focusInEvent(QFocusEvent *e) { f()->focusInInner(e->reason() == Qt::MouseFocusReason); QTextEdit::focusInEvent(e); emit f()->focused(); } void InputField::focusInInner(bool focusByMouse) { _borderAnimationStart = focusByMouse ? mapFromGlobal(QCursor::pos()).x() : (width() / 2); setFocused(true); } void InputField::Inner::focusOutEvent(QFocusEvent *e) { f()->focusOutInner(); QTextEdit::focusOutEvent(e); emit f()->blurred(); } void InputField::focusOutInner() { setFocused(false); } void InputField::setFocused(bool focused) { if (_focused != focused) { _focused = focused; _a_focused.start([this] { update(); }, _focused ? 0. : 1., _focused ? 1. : 0., _st.duration); startPlaceholderAnimation(); startBorderAnimation(); } } void InputField::startPlaceholderAnimation() { auto placeholderShifted = _forcePlaceholderHidden || (_focused && _st.placeholderScale > 0.) || !getLastText().isEmpty(); if (_placeholderShifted != placeholderShifted) { _placeholderShifted = placeholderShifted; _a_placeholderShifted.start([this] { update(); }, _placeholderShifted ? 0. : 1., _placeholderShifted ? 1. : 0., _st.duration); } } void InputField::startBorderAnimation() { auto borderVisible = (_error || _focused); if (_borderVisible != borderVisible) { _borderVisible = borderVisible; if (_borderVisible) { if (_a_borderOpacity.animating()) { _a_borderOpacity.start([this] { update(); }, 0., 1., _st.duration); } else { _a_borderShown.start([this] { update(); }, 0., 1., _st.duration); } } else { _a_borderOpacity.start([this] { update(); }, 1., 0., _st.duration); } } } QSize InputField::sizeHint() const { return geometry().size(); } QSize InputField::minimumSizeHint() const { return geometry().size(); } QString InputField::getText(int32 start, int32 end) const { if (end >= 0 && end <= start) return QString(); if (start < 0) start = 0; bool full = (start == 0) && (end < 0); QTextDocument *doc(_inner->document()); QTextBlock from = full ? doc->begin() : doc->findBlock(start), till = (end < 0) ? doc->end() : doc->findBlock(end); if (till.isValid()) till = till.next(); int32 possibleLen = 0; for (QTextBlock b = from; b != till; b = b.next()) { possibleLen += b.length(); } QString result; result.reserve(possibleLen + 1); if (!full && end < 0) { end = possibleLen; } for (QTextBlock b = from; b != till; b = b.next()) { for (QTextBlock::Iterator iter = b.begin(); !iter.atEnd(); ++iter) { QTextFragment fragment(iter.fragment()); if (!fragment.isValid()) continue; int32 p = full ? 0 : fragment.position(), e = full ? 0 : (p + fragment.length()); if (!full) { if (p >= end || e <= start) { continue; } } QTextCharFormat f = fragment.charFormat(); QString emojiText; QString t(fragment.text()); if (!full) { if (p < start) { t = t.mid(start - p, end - start); } else if (e > end) { t = t.mid(0, end - p); } } QChar *ub = t.data(), *uc = ub, *ue = uc + t.size(); for (; uc != ue; ++uc) { switch (uc->unicode()) { case 0xfdd0: // QTextBeginningOfFrame case 0xfdd1: // QTextEndOfFrame case QChar::ParagraphSeparator: case QChar::LineSeparator: { *uc = QLatin1Char('\n'); } break; case QChar::Nbsp: { *uc = QLatin1Char(' '); } break; case QChar::ObjectReplacementCharacter: { if (emojiText.isEmpty() && f.isImageFormat()) { auto imageName = static_cast<QTextImageFormat*>(&f)->name(); if (auto emoji = Ui::Emoji::FromUrl(imageName)) { emojiText = emoji->text(); } } if (uc > ub) result.append(ub, uc - ub); if (!emojiText.isEmpty()) result.append(emojiText); ub = uc + 1; } break; } } if (uc > ub) result.append(ub, uc - ub); } result.append('\n'); } result.chop(1); return result; } bool InputField::hasText() const { QTextDocument *doc(_inner->document()); QTextBlock from = doc->begin(), till = doc->end(); if (from == till) return false; for (QTextBlock::Iterator iter = from.begin(); !iter.atEnd(); ++iter) { QTextFragment fragment(iter.fragment()); if (!fragment.isValid()) continue; if (!fragment.text().isEmpty()) return true; } return (from.next() != till); } bool InputField::isUndoAvailable() const { return _undoAvailable; } bool InputField::isRedoAvailable() const { return _redoAvailable; } void InputField::insertEmoji(EmojiPtr emoji, QTextCursor c) { QTextImageFormat imageFormat; auto ew = Ui::Emoji::Size() + st::emojiPadding * cIntRetinaFactor() * 2, eh = _st.font->height * cIntRetinaFactor(); imageFormat.setWidth(ew / cIntRetinaFactor()); imageFormat.setHeight(eh / cIntRetinaFactor()); imageFormat.setName(emoji->toUrl()); imageFormat.setVerticalAlignment(QTextCharFormat::AlignBaseline); static QString objectReplacement(QChar::ObjectReplacementCharacter); c.insertText(objectReplacement, imageFormat); } QVariant InputField::Inner::loadResource(int type, const QUrl &name) { QString imageName = name.toDisplayString(); if (auto emoji = Ui::Emoji::FromUrl(imageName)) { return QVariant(App::emojiSingle(emoji, f()->_st.font->height)); } return QVariant(); } void InputField::processDocumentContentsChange(int position, int charsAdded) { int32 replacePosition = -1, replaceLen = 0; EmojiPtr emoji = nullptr; bool newlineFound = false; // Tilde formatting. auto tildeFormatting = !cRetina() && (font().pixelSize() == 13) && (font().family() == qstr("Open Sans")); auto isTildeFragment = false; auto tildeRegularFont = tildeFormatting ? qsl("Open Sans") : QString(); auto tildeFixedFont = tildeFormatting ? Fonts::GetOverride(qsl("Open Sans Semibold")) : QString(); QTextDocument *doc(_inner->document()); QTextCursor c(_inner->textCursor()); c.joinPreviousEditBlock(); while (true) { int32 start = position, end = position + charsAdded; QTextBlock from = doc->findBlock(start), till = doc->findBlock(end); if (till.isValid()) till = till.next(); for (QTextBlock b = from; b != till; b = b.next()) { for (QTextBlock::Iterator iter = b.begin(); !iter.atEnd(); ++iter) { QTextFragment fragment(iter.fragment()); if (!fragment.isValid()) continue; int32 fp = fragment.position(), fe = fp + fragment.length(); if (fp >= end || fe <= start) { continue; } if (tildeFormatting) { isTildeFragment = (fragment.charFormat().fontFamily() == tildeFixedFont); } QString t(fragment.text()); const QChar *ch = t.constData(), *e = ch + t.size(); for (; ch != e; ++ch, ++fp) { // QTextBeginningOfFrame // QTextEndOfFrame newlineFound = (ch->unicode() == 0xfdd0 || ch->unicode() == 0xfdd1 || ch->unicode() == QChar::ParagraphSeparator || ch->unicode() == QChar::LineSeparator || ch->unicode() == '\n' || ch->unicode() == '\r'); if (newlineFound) { if (replacePosition >= 0) { newlineFound = false; // replace tilde char format first } else { replacePosition = fp; replaceLen = 1; } break; } auto emojiLen = 0; emoji = Ui::Emoji::Find(ch, e, &emojiLen); if (emoji) { if (replacePosition >= 0) { emoji = 0; // replace tilde char format first } else { replacePosition = fp; replaceLen = emojiLen; } break; } if (tildeFormatting && fp >= position) { // tilde fix in OpenSans bool tilde = (ch->unicode() == '~'); if ((tilde && !isTildeFragment) || (!tilde && isTildeFragment)) { if (replacePosition < 0) { replacePosition = fp; replaceLen = 1; } else { ++replaceLen; } } else if (replacePosition >= 0) { break; } } if (ch + 1 < e && ch->isHighSurrogate() && (ch + 1)->isLowSurrogate()) { ++ch; ++fp; } } if (replacePosition >= 0) break; } if (replacePosition >= 0) break; if (b.next() != doc->end()) { newlineFound = true; replacePosition = b.next().position() - 1; replaceLen = 1; break; } } if (replacePosition >= 0) { if (!_inner->document()->pageSize().isNull()) { _inner->document()->setPageSize(QSizeF(0, 0)); } QTextCursor c(doc->docHandle(), replacePosition); c.setPosition(replacePosition + replaceLen, QTextCursor::KeepAnchor); if (newlineFound) { QTextCharFormat format; format.setFontFamily(font().family()); c.mergeCharFormat(format); c.insertText(" "); } else if (emoji) { insertEmoji(emoji, c); } else { QTextCharFormat format; format.setFontFamily(isTildeFragment ? tildeRegularFont : tildeFixedFont); c.mergeCharFormat(format); } charsAdded -= replacePosition + replaceLen - position; position = replacePosition + ((emoji || newlineFound) ? 1 : replaceLen); newlineFound = false; emoji = 0; replacePosition = -1; } else { break; } } c.endEditBlock(); } void InputField::onDocumentContentsChange(int position, int charsRemoved, int charsAdded) { if (_correcting) return; QString oldtext(_oldtext); QTextCursor(_inner->document()->docHandle(), 0).joinPreviousEditBlock(); if (!position) { // Qt bug workaround https://bugreports.qt.io/browse/QTBUG-49062 QTextCursor c(_inner->document()->docHandle(), 0); c.movePosition(QTextCursor::End); if (position + charsAdded > c.position()) { int32 toSubstract = position + charsAdded - c.position(); if (charsRemoved >= toSubstract) { charsAdded -= toSubstract; charsRemoved -= toSubstract; } } } _correcting = true; if (_maxLength >= 0) { QTextCursor c(_inner->document()->docHandle(), 0); c.movePosition(QTextCursor::End); int32 fullSize = c.position(), toRemove = fullSize - _maxLength; if (toRemove > 0) { if (toRemove > charsAdded) { if (charsAdded) { c.setPosition(position); c.setPosition((position + charsAdded), QTextCursor::KeepAnchor); c.removeSelectedText(); } c.setPosition(fullSize - (toRemove - charsAdded)); c.setPosition(fullSize, QTextCursor::KeepAnchor); c.removeSelectedText(); position = _maxLength; charsAdded = 0; charsRemoved += toRemove; } else { c.setPosition(position + (charsAdded - toRemove)); c.setPosition(position + charsAdded, QTextCursor::KeepAnchor); c.removeSelectedText(); charsAdded -= toRemove; } } } _correcting = false; QTextCursor(_inner->document()->docHandle(), 0).endEditBlock(); if (_inner->document()->availableRedoSteps() > 0) return; const int takeBack = 3; position -= takeBack; charsAdded += takeBack; if (position < 0) { charsAdded += position; position = 0; } if (charsAdded <= 0) return; _correcting = true; QSizeF s = _inner->document()->pageSize(); processDocumentContentsChange(position, charsAdded); if (_inner->document()->pageSize() != s) { _inner->document()->setPageSize(s); } _correcting = false; } void InputField::onDocumentContentsChanged() { if (_correcting) return; setErrorShown(false); auto curText = getText(); if (_oldtext != curText) { _oldtext = curText; emit changed(); } startPlaceholderAnimation(); if (App::wnd()) App::wnd()->updateGlobalMenu(); } void InputField::onUndoAvailable(bool avail) { _undoAvailable = avail; if (App::wnd()) App::wnd()->updateGlobalMenu(); } void InputField::onRedoAvailable(bool avail) { _redoAvailable = avail; if (App::wnd()) App::wnd()->updateGlobalMenu(); } void InputField::selectAll() { auto cursor = _inner->textCursor(); cursor.setPosition(0); cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); _inner->setTextCursor(cursor); } void InputField::setDisplayFocused(bool focused) { setFocused(focused); finishAnimating(); } void InputField::finishAnimating() { _a_focused.finish(); _a_error.finish(); _a_placeholderShifted.finish(); _a_borderShown.finish(); _a_borderOpacity.finish(); update(); } void InputField::setPlaceholderHidden(bool forcePlaceholderHidden) { _forcePlaceholderHidden = forcePlaceholderHidden; startPlaceholderAnimation(); } QMimeData *InputField::Inner::createMimeDataFromSelection() const { auto result = new QMimeData(); auto cursor = textCursor(); auto start = cursor.selectionStart(); auto end = cursor.selectionEnd(); if (end > start) { result->setText(f()->getText(start, end)); } return result; } void InputField::customUpDown(bool custom) { _customUpDown = custom; } void InputField::Inner::keyPressEvent(QKeyEvent *e) { bool shift = e->modifiers().testFlag(Qt::ShiftModifier), alt = e->modifiers().testFlag(Qt::AltModifier); bool macmeta = (cPlatform() == dbipMac || cPlatform() == dbipMacOld) && e->modifiers().testFlag(Qt::ControlModifier) && !e->modifiers().testFlag(Qt::MetaModifier) && !e->modifiers().testFlag(Qt::AltModifier); bool ctrl = e->modifiers().testFlag(Qt::ControlModifier) || e->modifiers().testFlag(Qt::MetaModifier), ctrlGood = true; bool enter = (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return); if (macmeta && e->key() == Qt::Key_Backspace) { QTextCursor tc(textCursor()), start(tc); start.movePosition(QTextCursor::StartOfLine); tc.setPosition(start.position(), QTextCursor::KeepAnchor); tc.removeSelectedText(); } else if (enter && ctrlGood) { emit f()->submitted(ctrl && shift); } else if (e->key() == Qt::Key_Escape) { e->ignore(); emit f()->cancelled(); } else if (e->key() == Qt::Key_Tab || e->key() == Qt::Key_Backtab) { if (alt || ctrl) { e->ignore(); } else { if (!focusNextPrevChild(e->key() == Qt::Key_Tab && !shift)) { e->ignore(); } } } else if (e->key() == Qt::Key_Search || e == QKeySequence::Find) { e->ignore(); } else if (f()->_customUpDown && (e->key() == Qt::Key_Up || e->key() == Qt::Key_Down)) { e->ignore(); #ifdef Q_OS_MAC } else if (e->key() == Qt::Key_E && e->modifiers().testFlag(Qt::ControlModifier)) { auto cursor = textCursor(); int start = cursor.selectionStart(), end = cursor.selectionEnd(); if (end > start) { QApplication::clipboard()->setText(f()->getText(start, end), QClipboard::FindBuffer); } #endif // Q_OS_MAC } else { auto oldCursorPosition = textCursor().position(); if (enter && ctrl) { e->setModifiers(e->modifiers() & ~Qt::ControlModifier); } QTextEdit::keyPressEvent(e); auto currentCursor = textCursor(); if (textCursor().position() == oldCursorPosition) { bool check = false; if (e->key() == Qt::Key_PageUp || e->key() == Qt::Key_Up) { oldCursorPosition = currentCursor.position(); currentCursor.movePosition(QTextCursor::Start, e->modifiers().testFlag(Qt::ShiftModifier) ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor); check = true; } else if (e->key() == Qt::Key_PageDown || e->key() == Qt::Key_Down) { oldCursorPosition = currentCursor.position(); currentCursor.movePosition(QTextCursor::End, e->modifiers().testFlag(Qt::ShiftModifier) ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor); check = true; } else if (e->key() == Qt::Key_Left || e->key() == Qt::Key_Right || e->key() == Qt::Key_Backspace) { e->ignore(); } if (check) { if (oldCursorPosition == currentCursor.position()) { e->ignore(); } else { setTextCursor(currentCursor); } } } } } void InputField::Inner::contextMenuEvent(QContextMenuEvent *e) { if (auto menu = createStandardContextMenu()) { (new Ui::PopupMenu(nullptr, menu))->popup(e->globalPos()); } } void InputField::resizeEvent(QResizeEvent *e) { refreshPlaceholder(); _inner->setGeometry(rect().marginsRemoved(_st.textMargins)); _borderAnimationStart = width() / 2; TWidget::resizeEvent(e); } void InputField::refreshPlaceholder() { auto placeholderText = _placeholderFactory ? _placeholderFactory() : QString(); auto availableWidth = width() - _st.textMargins.left() - _st.textMargins.right() - _st.placeholderMargins.left() - _st.placeholderMargins.right() - 1; if (_st.placeholderScale > 0.) { auto placeholderFont = _st.placeholderFont->f; placeholderFont.setStyleStrategy(QFont::PreferMatch); auto metrics = QFontMetrics(placeholderFont); _placeholder = metrics.elidedText(placeholderText, Qt::ElideRight, availableWidth); _placeholderPath = QPainterPath(); if (!_placeholder.isEmpty()) { _placeholderPath.addText(0, QFontMetrics(placeholderFont).ascent(), placeholderFont, _placeholder); } } else { _placeholder = _st.placeholderFont->elided(placeholderText, availableWidth); } update(); } void InputField::setPlaceholder(base::lambda<QString()> placeholderFactory) { _placeholderFactory = std::move(placeholderFactory); refreshPlaceholder(); } void InputField::showError() { setErrorShown(true); if (!hasFocus()) { _inner->setFocus(); } } void InputField::setErrorShown(bool error) { if (_error != error) { _error = error; _a_error.start([this] { update(); }, _error ? 0. : 1., _error ? 1. : 0., _st.duration); startBorderAnimation(); } } MaskedInputField::MaskedInputField( QWidget *parent, const style::InputField &st, base::lambda<QString()> placeholderFactory, const QString &val) : Parent(val, parent) , _st(st) , _oldtext(val) , _placeholderFactory(std::move(placeholderFactory)) { resize(_st.width, _st.heightMin); setFont(_st.font); setAlignment(_st.textAlign); subscribe(Lang::Current().updated(), [this] { refreshPlaceholder(); }); refreshPlaceholder(); subscribe(Window::Theme::Background(), [this](const Window::Theme::BackgroundUpdate &update) { if (update.paletteChanged()) { updatePalette(); } }); updatePalette(); setAttribute(Qt::WA_OpaquePaintEvent); connect(this, SIGNAL(textChanged(const QString&)), this, SLOT(onTextChange(const QString&))); connect(this, SIGNAL(cursorPositionChanged(int,int)), this, SLOT(onCursorPositionChanged(int,int))); connect(this, SIGNAL(textEdited(const QString&)), this, SLOT(onTextEdited())); if (App::wnd()) connect(this, SIGNAL(selectionChanged()), App::wnd(), SLOT(updateGlobalMenu())); setStyle(InputStyle<MaskedInputField>::instance()); QLineEdit::setTextMargins(0, 0, 0, 0); setContentsMargins(0, 0, 0, 0); setAttribute(Qt::WA_AcceptTouchEvents); _touchTimer.setSingleShot(true); connect(&_touchTimer, SIGNAL(timeout()), this, SLOT(onTouchTimer())); setTextMargins(_st.textMargins); startPlaceholderAnimation(); startBorderAnimation(); finishAnimating(); } void MaskedInputField::updatePalette() { auto p = palette(); p.setColor(QPalette::Text, _st.textFg->c); setPalette(p); } void MaskedInputField::setCorrectedText(QString &now, int &nowCursor, const QString &newText, int newPos) { if (newPos < 0 || newPos > newText.size()) { newPos = newText.size(); } auto updateText = (newText != now); if (updateText) { now = newText; setText(now); startPlaceholderAnimation(); } auto updateCursorPosition = (newPos != nowCursor) || updateText; if (updateCursorPosition) { nowCursor = newPos; setCursorPosition(nowCursor); } } void MaskedInputField::customUpDown(bool custom) { _customUpDown = custom; } void MaskedInputField::setTextMargins(const QMargins &mrg) { _textMargins = mrg; refreshPlaceholder(); } void MaskedInputField::onTouchTimer() { _touchRightButton = true; } bool MaskedInputField::eventHook(QEvent *e) { auto type = e->type(); if (type == QEvent::TouchBegin || type == QEvent::TouchUpdate || type == QEvent::TouchEnd || type == QEvent::TouchCancel) { auto event = static_cast<QTouchEvent*>(e); if (event->device()->type() == QTouchDevice::TouchScreen) { touchEvent(event); } } return Parent::eventHook(e); } void MaskedInputField::touchEvent(QTouchEvent *e) { switch (e->type()) { case QEvent::TouchBegin: { if (_touchPress || e->touchPoints().isEmpty()) return; _touchTimer.start(QApplication::startDragTime()); _touchPress = true; _touchMove = _touchRightButton = false; _touchStart = e->touchPoints().cbegin()->screenPos().toPoint(); } break; case QEvent::TouchUpdate: { if (!_touchPress || e->touchPoints().isEmpty()) return; if (!_touchMove && (e->touchPoints().cbegin()->screenPos().toPoint() - _touchStart).manhattanLength() >= QApplication::startDragDistance()) { _touchMove = true; } } break; case QEvent::TouchEnd: { if (!_touchPress) return; auto weak = make_weak(this); if (!_touchMove && window()) { Qt::MouseButton btn(_touchRightButton ? Qt::RightButton : Qt::LeftButton); QPoint mapped(mapFromGlobal(_touchStart)), winMapped(window()->mapFromGlobal(_touchStart)); if (_touchRightButton) { QContextMenuEvent contextEvent(QContextMenuEvent::Mouse, mapped, _touchStart); contextMenuEvent(&contextEvent); } } if (weak) { _touchTimer.stop(); _touchPress = _touchMove = _touchRightButton = false; } } break; case QEvent::TouchCancel: { _touchPress = false; _touchTimer.stop(); } break; } } QRect MaskedInputField::getTextRect() const { return rect().marginsRemoved(_textMargins + QMargins(-2, -1, -2, -1)); } void MaskedInputField::paintEvent(QPaintEvent *e) { Painter p(this); auto ms = getms(); auto r = rect().intersected(e->rect()); p.fillRect(r, _st.textBg); if (_st.border) { p.fillRect(0, height() - _st.border, width(), _st.border, _st.borderFg->b); } auto errorDegree = _a_error.current(ms, _error ? 1. : 0.); auto focusedDegree = _a_focused.current(ms, _focused ? 1. : 0.); auto borderShownDegree = _a_borderShown.current(ms, 1.); auto borderOpacity = _a_borderOpacity.current(ms, _borderVisible ? 1. : 0.); if (_st.borderActive && (borderOpacity > 0.)) { auto borderStart = snap(_borderAnimationStart, 0, width()); auto borderFrom = qRound(borderStart * (1. - borderShownDegree)); auto borderTo = borderStart + qRound((width() - borderStart) * borderShownDegree); if (borderTo > borderFrom) { auto borderFg = anim::brush(_st.borderFgActive, _st.borderFgError, errorDegree); p.setOpacity(borderOpacity); p.fillRect(borderFrom, height() - _st.borderActive, borderTo - borderFrom, _st.borderActive, borderFg); p.setOpacity(1); } } p.setClipRect(r); if (_st.placeholderScale > 0. && !_placeholderPath.isEmpty()) { auto placeholderShiftDegree = _a_placeholderShifted.current(ms, _placeholderShifted ? 1. : 0.); p.save(); p.setClipRect(r); auto placeholderTop = anim::interpolate(0, _st.placeholderShift, placeholderShiftDegree); QRect r(rect().marginsRemoved(_textMargins + _st.placeholderMargins)); r.moveTop(r.top() + placeholderTop); if (rtl()) r.moveLeft(width() - r.left() - r.width()); auto placeholderScale = 1. - (1. - _st.placeholderScale) * placeholderShiftDegree; auto placeholderFg = anim::color(_st.placeholderFg, _st.placeholderFgActive, focusedDegree); placeholderFg = anim::color(placeholderFg, _st.placeholderFgError, errorDegree); PainterHighQualityEnabler hq(p); p.setPen(Qt::NoPen); p.setBrush(placeholderFg); p.translate(r.topLeft()); p.scale(placeholderScale, placeholderScale); p.drawPath(_placeholderPath); p.restore(); } else if (!_placeholder.isEmpty()) { auto placeholderHiddenDegree = _a_placeholderShifted.current(ms, _placeholderShifted ? 1. : 0.); if (placeholderHiddenDegree < 1.) { p.setOpacity(1. - placeholderHiddenDegree); p.save(); p.setClipRect(r); auto placeholderLeft = anim::interpolate(0, -_st.placeholderShift, placeholderHiddenDegree); QRect r(rect().marginsRemoved(_textMargins + _st.placeholderMargins)); r.moveLeft(r.left() + placeholderLeft); if (rtl()) r.moveLeft(width() - r.left() - r.width()); p.setFont(_st.font); p.setPen(anim::pen(_st.placeholderFg, _st.placeholderFgActive, focusedDegree)); p.drawText(r, _placeholder, _st.placeholderAlign); p.restore(); p.setOpacity(1.); } } paintAdditionalPlaceholder(p, ms); QLineEdit::paintEvent(e); } void MaskedInputField::startBorderAnimation() { auto borderVisible = (_error || _focused); if (_borderVisible != borderVisible) { _borderVisible = borderVisible; if (_borderVisible) { if (_a_borderOpacity.animating()) { _a_borderOpacity.start([this] { update(); }, 0., 1., _st.duration); } else { _a_borderShown.start([this] { update(); }, 0., 1., _st.duration); } } else if (qFuzzyCompare(_a_borderShown.current(1.), 0.)) { _a_borderShown.finish(); _a_borderOpacity.finish(); } else { _a_borderOpacity.start([this] { update(); }, 1., 0., _st.duration); } } } void MaskedInputField::focusInEvent(QFocusEvent *e) { _borderAnimationStart = (e->reason() == Qt::MouseFocusReason) ? mapFromGlobal(QCursor::pos()).x() : (width() / 2); setFocused(true); QLineEdit::focusInEvent(e); emit focused(); } void MaskedInputField::focusOutEvent(QFocusEvent *e) { setFocused(false); QLineEdit::focusOutEvent(e); emit blurred(); } void MaskedInputField::setFocused(bool focused) { if (_focused != focused) { _focused = focused; _a_focused.start([this] { update(); }, _focused ? 0. : 1., _focused ? 1. : 0., _st.duration); startPlaceholderAnimation(); startBorderAnimation(); } } void MaskedInputField::resizeEvent(QResizeEvent *e) { refreshPlaceholder(); _borderAnimationStart = width() / 2; QLineEdit::resizeEvent(e); } void MaskedInputField::refreshPlaceholder() { auto placeholderText = _placeholderFactory ? _placeholderFactory() : QString(); auto availableWidth = width() - _textMargins.left() - _textMargins.right() - _st.placeholderMargins.left() - _st.placeholderMargins.right() - 1; if (_st.placeholderScale > 0.) { auto placeholderFont = _st.placeholderFont->f; placeholderFont.setStyleStrategy(QFont::PreferMatch); auto metrics = QFontMetrics(placeholderFont); _placeholder = metrics.elidedText(placeholderText, Qt::ElideRight, availableWidth); _placeholderPath = QPainterPath(); if (!_placeholder.isEmpty()) { _placeholderPath.addText(0, QFontMetrics(placeholderFont).ascent(), placeholderFont, _placeholder); } } else { _placeholder = _st.placeholderFont->elided(placeholderText, availableWidth); } update(); } void MaskedInputField::setPlaceholder(base::lambda<QString()> placeholderFactory) { _placeholderFactory = std::move(placeholderFactory); refreshPlaceholder(); } void MaskedInputField::contextMenuEvent(QContextMenuEvent *e) { if (auto menu = createStandardContextMenu()) { (new Ui::PopupMenu(nullptr, menu))->popup(e->globalPos()); } } void MaskedInputField::inputMethodEvent(QInputMethodEvent *e) { QLineEdit::inputMethodEvent(e); _lastPreEditText = e->preeditString(); update(); } void MaskedInputField::showError() { setErrorShown(true); if (!hasFocus()) { setFocus(); } } void MaskedInputField::setErrorShown(bool error) { if (_error != error) { _error = error; _a_error.start([this] { update(); }, _error ? 0. : 1., _error ? 1. : 0., _st.duration); startBorderAnimation(); } } QSize MaskedInputField::sizeHint() const { return geometry().size(); } QSize MaskedInputField::minimumSizeHint() const { return geometry().size(); } void MaskedInputField::setDisplayFocused(bool focused) { setFocused(focused); finishAnimating(); } void MaskedInputField::finishAnimating() { _a_focused.finish(); _a_error.finish(); _a_placeholderShifted.finish(); _a_borderShown.finish(); _a_borderOpacity.finish(); update(); } void MaskedInputField::setPlaceholderHidden(bool forcePlaceholderHidden) { _forcePlaceholderHidden = forcePlaceholderHidden; startPlaceholderAnimation(); } void MaskedInputField::startPlaceholderAnimation() { auto placeholderShifted = _forcePlaceholderHidden || (_focused && _st.placeholderScale > 0.) || !getLastText().isEmpty(); if (_placeholderShifted != placeholderShifted) { _placeholderShifted = placeholderShifted; _a_placeholderShifted.start([this] { update(); }, _placeholderShifted ? 0. : 1., _placeholderShifted ? 1. : 0., _st.duration); } } QRect MaskedInputField::placeholderRect() const { return rect().marginsRemoved(_textMargins + _st.placeholderMargins); } void MaskedInputField::placeholderAdditionalPrepare(Painter &p, TimeMs ms) { p.setFont(_st.font); p.setPen(_st.placeholderFg); } void MaskedInputField::keyPressEvent(QKeyEvent *e) { QString wasText(_oldtext); int32 wasCursor(_oldcursor); bool shift = e->modifiers().testFlag(Qt::ShiftModifier), alt = e->modifiers().testFlag(Qt::AltModifier); bool ctrl = e->modifiers().testFlag(Qt::ControlModifier) || e->modifiers().testFlag(Qt::MetaModifier), ctrlGood = true; if (_customUpDown && (e->key() == Qt::Key_Up || e->key() == Qt::Key_Down)) { e->ignore(); } else { QLineEdit::keyPressEvent(e); } auto newText = text(); auto newCursor = cursorPosition(); if (wasText == newText && wasCursor == newCursor) { // call correct manually correctValue(wasText, wasCursor, newText, newCursor); _oldtext = newText; _oldcursor = newCursor; if (wasText != _oldtext) emit changed(); startPlaceholderAnimation(); } if (e->key() == Qt::Key_Escape) { e->ignore(); emit cancelled(); } else if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) { emit submitted(ctrl && shift); #ifdef Q_OS_MAC } else if (e->key() == Qt::Key_E && e->modifiers().testFlag(Qt::ControlModifier)) { auto selected = selectedText(); if (!selected.isEmpty() && echoMode() == QLineEdit::Normal) { QApplication::clipboard()->setText(selected, QClipboard::FindBuffer); } #endif // Q_OS_MAC } } void MaskedInputField::onTextEdited() { QString wasText(_oldtext), newText(text()); int32 wasCursor(_oldcursor), newCursor(cursorPosition()); correctValue(wasText, wasCursor, newText, newCursor); _oldtext = newText; _oldcursor = newCursor; if (wasText != _oldtext) emit changed(); startPlaceholderAnimation(); if (App::wnd()) App::wnd()->updateGlobalMenu(); } void MaskedInputField::onTextChange(const QString &text) { _oldtext = QLineEdit::text(); setErrorShown(false); if (App::wnd()) App::wnd()->updateGlobalMenu(); } void MaskedInputField::onCursorPositionChanged(int oldPosition, int position) { _oldcursor = position; } CountryCodeInput::CountryCodeInput(QWidget *parent, const style::InputField &st) : MaskedInputField(parent, st) , _nosignal(false) { } void CountryCodeInput::startErasing(QKeyEvent *e) { setFocus(); keyPressEvent(e); } void CountryCodeInput::codeSelected(const QString &code) { auto wasText = getLastText(); auto wasCursor = cursorPosition(); auto newText = '+' + code; auto newCursor = newText.size(); setText(newText); _nosignal = true; correctValue(wasText, wasCursor, newText, newCursor); _nosignal = false; emit changed(); } void CountryCodeInput::correctValue( const QString &was, int wasCursor, QString &now, int &nowCursor) { QString newText, addToNumber; int oldPos(nowCursor), newPos(-1), oldLen(now.length()), start = 0, digits = 5; newText.reserve(oldLen + 1); if (oldLen && now[0] == '+') { if (start == oldPos) { newPos = newText.length(); } ++start; } newText += '+'; for (int i = start; i < oldLen; ++i) { if (i == oldPos) { newPos = newText.length(); } auto ch = now[i]; if (ch.isDigit()) { if (!digits || !--digits) { addToNumber += ch; } else { newText += ch; } } } if (!addToNumber.isEmpty()) { auto validCode = findValidCode(newText.mid(1)); addToNumber = newText.mid(1 + validCode.length()) + addToNumber; newText = '+' + validCode; } setCorrectedText(now, nowCursor, newText, newPos); if (!_nosignal && was != newText) { emit codeChanged(newText.mid(1)); } if (!addToNumber.isEmpty()) { emit addedToNumber(addToNumber); } } PhonePartInput::PhonePartInput(QWidget *parent, const style::InputField &st) : MaskedInputField(parent, st/*, lang(lng_phone_ph)*/) { } void PhonePartInput::paintAdditionalPlaceholder(Painter &p, TimeMs ms) { if (!_pattern.isEmpty()) { auto t = getDisplayedText(); auto ph = _additionalPlaceholder.mid(t.size()); if (!ph.isEmpty()) { p.setClipRect(rect()); auto phRect = placeholderRect(); int tw = phFont()->width(t); if (tw < phRect.width()) { phRect.setLeft(phRect.left() + tw); placeholderAdditionalPrepare(p, ms); p.drawText(phRect, ph, style::al_topleft); } } } } void PhonePartInput::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Backspace && getLastText().isEmpty()) { emit voidBackspace(e); } else { MaskedInputField::keyPressEvent(e); } } void PhonePartInput::correctValue( const QString &was, int wasCursor, QString &now, int &nowCursor) { QString newText; int oldPos(nowCursor), newPos(-1), oldLen(now.length()), digitCount = 0; for (int i = 0; i < oldLen; ++i) { if (now[i].isDigit()) { ++digitCount; } } if (digitCount > MaxPhoneTailLength) digitCount = MaxPhoneTailLength; bool inPart = !_pattern.isEmpty(); int curPart = -1, leftInPart = 0; newText.reserve(oldLen); for (int i = 0; i < oldLen; ++i) { if (i == oldPos && newPos < 0) { newPos = newText.length(); } auto ch = now[i]; if (ch.isDigit()) { if (!digitCount--) { break; } if (inPart) { if (leftInPart) { --leftInPart; } else { newText += ' '; ++curPart; inPart = curPart < _pattern.size(); leftInPart = inPart ? (_pattern.at(curPart) - 1) : 0; ++oldPos; } } newText += ch; } else if (ch == ' ' || ch == '-' || ch == '(' || ch == ')') { if (inPart) { if (leftInPart) { } else { newText += ch; ++curPart; inPart = curPart < _pattern.size(); leftInPart = inPart ? _pattern.at(curPart) : 0; } } else { newText += ch; } } } auto newlen = newText.size(); while (newlen > 0 && newText.at(newlen - 1).isSpace()) { --newlen; } if (newlen < newText.size()) { newText = newText.mid(0, newlen); } setCorrectedText(now, nowCursor, newText, newPos); } void PhonePartInput::addedToNumber(const QString &added) { setFocus(); auto wasText = getLastText(); auto wasCursor = cursorPosition(); auto newText = added + wasText; auto newCursor = newText.size(); setText(newText); setCursorPosition(added.length()); correctValue(wasText, wasCursor, newText, newCursor); startPlaceholderAnimation(); } void PhonePartInput::onChooseCode(const QString &code) { _pattern = phoneNumberParse(code); if (!_pattern.isEmpty() && _pattern.at(0) == code.size()) { _pattern.pop_front(); } else { _pattern.clear(); } _additionalPlaceholder = QString(); if (!_pattern.isEmpty()) { _additionalPlaceholder.reserve(20); for (int i = 0, l = _pattern.size(); i < l; ++i) { _additionalPlaceholder.append(' '); _additionalPlaceholder.append(QString(_pattern.at(i), QChar(0x2212))); } } setPlaceholderHidden(!_additionalPlaceholder.isEmpty()); auto wasText = getLastText(); auto wasCursor = cursorPosition(); auto newText = getLastText(); auto newCursor = newText.size(); correctValue(wasText, wasCursor, newText, newCursor); startPlaceholderAnimation(); } PasswordInput::PasswordInput(QWidget *parent, const style::InputField &st, base::lambda<QString()> placeholderFactory, const QString &val) : MaskedInputField(parent, st, std::move(placeholderFactory), val) { setEchoMode(QLineEdit::Password); } PortInput::PortInput(QWidget *parent, const style::InputField &st, base::lambda<QString()> placeholderFactory, const QString &val) : MaskedInputField(parent, st, std::move(placeholderFactory), val) { if (!val.toInt() || val.toInt() > 65535) { setText(QString()); } } void PortInput::correctValue( const QString &was, int wasCursor, QString &now, int &nowCursor) { QString newText; newText.reserve(now.size()); auto newPos = nowCursor; for (auto i = 0, l = now.size(); i < l; ++i) { if (now.at(i).isDigit()) { newText.append(now.at(i)); } else if (i < nowCursor) { --newPos; } } if (!newText.toInt()) { newText = QString(); newPos = 0; } else if (newText.toInt() > 65535) { newText = was; newPos = wasCursor; } setCorrectedText(now, nowCursor, newText, newPos); } UsernameInput::UsernameInput(QWidget *parent, const style::InputField &st, base::lambda<QString()> placeholderFactory, const QString &val, bool isLink) : MaskedInputField(parent, st, std::move(placeholderFactory), val) { setLinkPlaceholder(isLink ? Messenger::Instance().createInternalLink(QString()) : QString()); } void UsernameInput::setLinkPlaceholder(const QString &placeholder) { _linkPlaceholder = placeholder; if (!_linkPlaceholder.isEmpty()) { setTextMargins(style::margins(_st.textMargins.left() + _st.font->width(_linkPlaceholder), _st.textMargins.top(), _st.textMargins.right(), _st.textMargins.bottom())); setPlaceholderHidden(true); } } void UsernameInput::paintAdditionalPlaceholder(Painter &p, TimeMs ms) { if (!_linkPlaceholder.isEmpty()) { p.setFont(_st.font); p.setPen(_st.placeholderFg); p.drawText(QRect(_st.textMargins.left(), _st.textMargins.top(), width(), height() - _st.textMargins.top() - _st.textMargins.bottom()), _linkPlaceholder, style::al_topleft); } } void UsernameInput::correctValue( const QString &was, int wasCursor, QString &now, int &nowCursor) { auto newPos = nowCursor; auto from = 0, len = now.size(); for (; from < len; ++from) { if (!now.at(from).isSpace()) { break; } if (newPos > 0) --newPos; } len -= from; if (len > kMaxUsernameLength) { len = kMaxUsernameLength + (now.at(from) == '@' ? 1 : 0); } for (int32 to = from + len; to > from;) { --to; if (!now.at(to).isSpace()) { break; } --len; } setCorrectedText(now, nowCursor, now.mid(from, len), newPos); } PhoneInput::PhoneInput(QWidget *parent, const style::InputField &st, base::lambda<QString()> placeholderFactory, const QString &val) : MaskedInputField(parent, st, std::move(placeholderFactory), val) { QString phone(val); if (phone.isEmpty()) { clearText(); } else { int32 pos = phone.size(); correctValue(QString(), 0, phone, pos); } } void PhoneInput::focusInEvent(QFocusEvent *e) { MaskedInputField::focusInEvent(e); setSelection(cursorPosition(), cursorPosition()); } void PhoneInput::clearText() { QString phone; if (App::self()) { QVector<int> newPattern = phoneNumberParse(App::self()->phone()); if (!newPattern.isEmpty()) { phone = App::self()->phone().mid(0, newPattern.at(0)); } } setText(phone); int32 pos = phone.size(); correctValue(QString(), 0, phone, pos); } void PhoneInput::paintAdditionalPlaceholder(Painter &p, TimeMs ms) { if (!_pattern.isEmpty()) { auto t = getDisplayedText(); auto ph = _additionalPlaceholder.mid(t.size()); if (!ph.isEmpty()) { p.setClipRect(rect()); auto phRect = placeholderRect(); int tw = phFont()->width(t); if (tw < phRect.width()) { phRect.setLeft(phRect.left() + tw); placeholderAdditionalPrepare(p, ms); p.drawText(phRect, ph, style::al_topleft); } } } } void PhoneInput::correctValue( const QString &was, int wasCursor, QString &now, int &nowCursor) { auto digits = now; digits.replace(QRegularExpression(qsl("[^\\d]")), QString()); _pattern = phoneNumberParse(digits); QString newPlaceholder; if (_pattern.isEmpty()) { newPlaceholder = QString(); } else if (_pattern.size() == 1 && _pattern.at(0) == digits.size()) { newPlaceholder = QString(_pattern.at(0) + 2, ' ') + lang(lng_contact_phone); } else { newPlaceholder.reserve(20); for (int i = 0, l = _pattern.size(); i < l; ++i) { if (i) { newPlaceholder.append(' '); } else { newPlaceholder.append('+'); } newPlaceholder.append(i ? QString(_pattern.at(i), QChar(0x2212)) : digits.mid(0, _pattern.at(i))); } } if (_additionalPlaceholder != newPlaceholder) { _additionalPlaceholder = newPlaceholder; setPlaceholderHidden(!_additionalPlaceholder.isEmpty()); update(); } QString newText; int oldPos(nowCursor), newPos(-1), oldLen(now.length()), digitCount = qMin(digits.size(), MaxPhoneCodeLength + MaxPhoneTailLength); bool inPart = !_pattern.isEmpty(), plusFound = false; int curPart = 0, leftInPart = inPart ? _pattern.at(curPart) : 0; newText.reserve(oldLen + 1); newText.append('+'); for (int i = 0; i < oldLen; ++i) { if (i == oldPos && newPos < 0) { newPos = newText.length(); } QChar ch(now[i]); if (ch.isDigit()) { if (!digitCount--) { break; } if (inPart) { if (leftInPart) { --leftInPart; } else { newText += ' '; ++curPart; inPart = curPart < _pattern.size(); leftInPart = inPart ? (_pattern.at(curPart) - 1) : 0; ++oldPos; } } newText += ch; } else if (ch == ' ' || ch == '-' || ch == '(' || ch == ')') { if (inPart) { if (leftInPart) { } else { newText += ch; ++curPart; inPart = curPart < _pattern.size(); leftInPart = inPart ? _pattern.at(curPart) : 0; } } else { newText += ch; } } else if (ch == '+') { plusFound = true; } } if (!plusFound && newText == qstr("+")) { newText = QString(); newPos = 0; } int32 newlen = newText.size(); while (newlen > 0 && newText.at(newlen - 1).isSpace()) { --newlen; } if (newlen < newText.size()) { newText = newText.mid(0, newlen); } setCorrectedText(now, nowCursor, newText, newPos); } } // namespace Ui ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/widgets/input_fields.h��������������������������������������0000664�0000000�0000000�00000055524�13262451251�0024357�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/rp_widget.h" #include "styles/style_widgets.h" class UserData; namespace Ui { static UserData * const LookingUpInlineBot = SharedMemoryLocation<UserData, 0>(); class FlatTextarea : public TWidgetHelper<QTextEdit>, private base::Subscriber { Q_OBJECT public: using TagList = TextWithTags::Tags; static QByteArray serializeTagsList(const TagList &tags); static TagList deserializeTagsList(QByteArray data, int textLength); static QString tagsMimeType(); FlatTextarea(QWidget *parent, const style::FlatTextarea &st, base::lambda<QString()> placeholderFactory = base::lambda<QString()>(), const QString &val = QString(), const TagList &tags = TagList()); void setMaxLength(int maxLength); void setMinHeight(int minHeight); void setMaxHeight(int maxHeight); void setPlaceholder(base::lambda<QString()> placeholderFactory, int afterSymbols = 0); void updatePlaceholder(); void finishPlaceholder(); QRect getTextRect() const; int fakeMargin() const; QSize sizeHint() const override; QSize minimumSizeHint() const override; EmojiPtr getSingleEmoji() const; QString getMentionHashtagBotCommandPart(bool &start) const; // Get the current inline bot and request string for it. // The *outInlineBot can be filled by LookingUpInlineBot shared ptr. // In that case the caller should lookup the bot by *outInlineBotUsername. QString getInlineBotQuery(UserData **outInlineBot, QString *outInlineBotUsername) const; void removeSingleEmoji(); bool hasText() const; bool isUndoAvailable() const; bool isRedoAvailable() const; void parseLinks(); QStringList linksList() const; void insertFromMimeData(const QMimeData *source) override; QMimeData *createMimeDataFromSelection() const override; enum class SubmitSettings { None, Enter, CtrlEnter, Both, }; void setSubmitSettings(SubmitSettings settings); const TextWithTags &getTextWithTags() const { return _lastTextWithTags; } TextWithTags getTextWithTagsPart(int start, int end = -1); void insertTag(const QString &text, QString tagId = QString()); bool isEmpty() const { return _lastTextWithTags.text.isEmpty(); } enum UndoHistoryAction { AddToUndoHistory, MergeWithUndoHistory, ClearUndoHistory }; void setTextWithTags(const TextWithTags &textWithTags, UndoHistoryAction undoHistoryAction = AddToUndoHistory); // If you need to make some preparations of tags before putting them to QMimeData // (and then to clipboard or to drag-n-drop object), here is a strategy for that. class TagMimeProcessor { public: virtual QString mimeTagFromTag(const QString &tagId) = 0; virtual QString tagFromMimeTag(const QString &mimeTag) = 0; virtual ~TagMimeProcessor() { } }; void setTagMimeProcessor(std::unique_ptr<TagMimeProcessor> &&processor); public slots: void onTouchTimer(); void onDocumentContentsChange(int position, int charsRemoved, int charsAdded); void onDocumentContentsChanged(); void onUndoAvailable(bool avail); void onRedoAvailable(bool avail); signals: void resized(); void changed(); void submitted(bool ctrlShiftEnter); void cancelled(); void tabbed(); void spacedReturnedPasted(); void linksChanged(); protected: bool viewportEvent(QEvent *e) override; void touchEvent(QTouchEvent *e); void paintEvent(QPaintEvent *e) override; void focusInEvent(QFocusEvent *e) override; void focusOutEvent(QFocusEvent *e) override; void keyPressEvent(QKeyEvent *e) override; void resizeEvent(QResizeEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void dropEvent(QDropEvent *e) override; void contextMenuEvent(QContextMenuEvent *e) override; virtual void correctValue( const QString &was, QString &now, TagList &nowTags) { } void insertEmoji(EmojiPtr emoji, QTextCursor c); QVariant loadResource(int type, const QUrl &name) override; void checkContentHeight(); private: void updatePalette(); void refreshPlaceholder(); // "start" and "end" are in coordinates of text where emoji are replaced // by ObjectReplacementCharacter. If "end" = -1 means get text till the end. QString getTextPart(int start, int end, TagList *outTagsList, bool *outTagsChanged = nullptr) const; void getSingleEmojiFragment(QString &text, QTextFragment &fragment) const; // After any characters added we must postprocess them. This includes: // 1. Replacing font family to semibold for ~ characters, if we used Open Sans 13px. // 2. Replacing font family from semibold for all non-~ characters, if we used ... // 3. Replacing emoji code sequences by ObjectReplacementCharacters with emoji pics. // 4. Interrupting tags in which the text was inserted by any char except a letter. // 5. Applying tags from "_insertedTags" in case we pasted text with tags, not just text. // Rule 4 applies only if we inserted chars not in the middle of a tag (but at the end). void processFormatting(int changedPosition, int changedEnd); bool heightAutoupdated(); int placeholderSkipWidth() const; int _minHeight = -1; // < 0 - no autosize int _maxHeight = -1; int _maxLength = -1; SubmitSettings _submitSettings = SubmitSettings::Enter; QString _placeholder; base::lambda<QString()> _placeholderFactory; int _placeholderAfterSymbols = 0; bool _focused = false; bool _placeholderVisible = true; Animation _a_placeholderFocused; Animation _a_placeholderVisible; TextWithTags _lastTextWithTags; // Tags list which we should apply while setText() call or insert from mime data. TagList _insertedTags; bool _insertedTagsAreFromMime; // Override insert position and charsAdded from complex text editing // (like drag-n-drop in the same text edit field). int _realInsertPosition = -1; int _realCharsAdded = 0; std::unique_ptr<TagMimeProcessor> _tagMimeProcessor; const style::FlatTextarea &_st; bool _undoAvailable = false; bool _redoAvailable = false; bool _inDrop = false; bool _inHeightCheck = false; int _fakeMargin = 0; QTimer _touchTimer; bool _touchPress = false; bool _touchRightButton = false; bool _touchMove = false; QPoint _touchStart; bool _correcting = false; struct LinkRange { int start; int length; }; friend bool operator==(const LinkRange &a, const LinkRange &b); friend bool operator!=(const LinkRange &a, const LinkRange &b); using LinkRanges = QVector<LinkRange>; LinkRanges _links; }; inline bool operator==(const FlatTextarea::LinkRange &a, const FlatTextarea::LinkRange &b) { return (a.start == b.start) && (a.length == b.length); } inline bool operator!=(const FlatTextarea::LinkRange &a, const FlatTextarea::LinkRange &b) { return !(a == b); } class FlatInput : public TWidgetHelper<QLineEdit>, private base::Subscriber { Q_OBJECT public: FlatInput( QWidget *parent, const style::FlatInput &st, base::lambda<QString()> placeholderFactory = nullptr, const QString &val = QString()); void updatePlaceholder(); void setPlaceholder(base::lambda<QString()> placeholderFactory); QRect placeholderRect() const; void setTextMrg(const QMargins &textMrg); QRect getTextRect() const; QSize sizeHint() const override; QSize minimumSizeHint() const override; void customUpDown(bool isCustom); const QString &getLastText() const { return _oldtext; } public slots: void onTextChange(const QString &text); void onTextEdited(); void onTouchTimer(); signals: void changed(); void cancelled(); void submitted(bool ctrlShiftEnter); void focused(); void blurred(); protected: bool event(QEvent *e) override; void touchEvent(QTouchEvent *e); void paintEvent(QPaintEvent *e) override; void focusInEvent(QFocusEvent *e) override; void focusOutEvent(QFocusEvent *e) override; void keyPressEvent(QKeyEvent *e) override; void resizeEvent(QResizeEvent *e) override; void contextMenuEvent(QContextMenuEvent *e) override; void inputMethodEvent(QInputMethodEvent *e) override; virtual void correctValue(const QString &was, QString &now); style::font phFont() { return _st.font; } void phPrepare(Painter &p, float64 placeholderFocused); private: void updatePalette(); void refreshPlaceholder(); QString _oldtext; QString _placeholder; base::lambda<QString()> _placeholderFactory; bool _customUpDown = false; bool _focused = false; bool _placeholderVisible = true; Animation _a_placeholderFocused; Animation _a_placeholderVisible; bool _lastPreEditTextNotEmpty = false; const style::FlatInput &_st; QMargins _textMrg; QTimer _touchTimer; bool _touchPress, _touchRightButton, _touchMove; QPoint _touchStart; }; enum class CtrlEnterSubmit { Enter, CtrlEnter, Both, }; class InputArea : public RpWidget, private base::Subscriber { Q_OBJECT public: InputArea( QWidget *parent, const style::InputField &st, base::lambda<QString()> placeholderFactory = base::lambda<QString()>(), const QString &val = QString()); void showError(); void setMaxLength(int maxLength) { _maxLength = maxLength; } const QString &getLastText() const { return _oldtext; } void setPlaceholder(base::lambda<QString()> placeholderFactory); void setDisplayFocused(bool focused); void finishAnimating(); void setFocusFast() { setDisplayFocused(true); setFocus(); } QSize sizeHint() const override; QSize minimumSizeHint() const override; QString getText(int start = 0, int end = -1) const; bool hasText() const; bool isUndoAvailable() const; bool isRedoAvailable() const; void customUpDown(bool isCustom); void setCtrlEnterSubmit(CtrlEnterSubmit ctrlEnterSubmit); void setTextCursor(const QTextCursor &cursor) { return _inner->setTextCursor(cursor); } QTextCursor textCursor() const { return _inner->textCursor(); } void setText(const QString &text) { _inner->setText(text); startPlaceholderAnimation(); } void clear() { _inner->clear(); startPlaceholderAnimation(); } bool hasFocus() const { return _inner->hasFocus(); } void setFocus() { _inner->setFocus(); } void clearFocus() { _inner->clearFocus(); } enum class MimeAction { Check, Insert, }; using MimeDataHook = base::lambda<bool( not_null<const QMimeData*> data, MimeAction action)>; void setMimeDataHook(MimeDataHook hook) { _mimeDataHook = std::move(hook); } private slots: void onTouchTimer(); void onDocumentContentsChange(int position, int charsRemoved, int charsAdded); void onDocumentContentsChanged(); void onUndoAvailable(bool avail); void onRedoAvailable(bool avail); void onFocusInner(); signals: void changed(); void submitted(bool ctrlShiftEnter); void cancelled(); void tabbed(); void focused(); void blurred(); void resized(); protected: void startPlaceholderAnimation(); void startBorderAnimation(); void insertEmoji(EmojiPtr emoji, QTextCursor c); TWidget *tparent() { return qobject_cast<TWidget*>(parentWidget()); } const TWidget *tparent() const { return qobject_cast<const TWidget*>(parentWidget()); } void touchEvent(QTouchEvent *e); void paintEvent(QPaintEvent *e) override; void focusInEvent(QFocusEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void contextMenuEvent(QContextMenuEvent *e) override; void resizeEvent(QResizeEvent *e) override; private: class Inner : public QTextEdit { public: Inner(InputArea *parent); QVariant loadResource(int type, const QUrl &name) override; protected: bool viewportEvent(QEvent *e) override; void focusInEvent(QFocusEvent *e) override; void focusOutEvent(QFocusEvent *e) override; void keyPressEvent(QKeyEvent *e) override; void contextMenuEvent(QContextMenuEvent *e) override; bool canInsertFromMimeData(const QMimeData *source) const override; void insertFromMimeData(const QMimeData *source) override; QMimeData *createMimeDataFromSelection() const override; private: InputArea *f() const { return static_cast<InputArea*>(parentWidget()); } friend class InputArea; }; friend class Inner; void updatePalette(); void refreshPlaceholder(); bool heightAutoupdated(); void checkContentHeight(); void setErrorShown(bool error); void focusInInner(bool focusByMouse); void focusOutInner(); void setFocused(bool focused); void processDocumentContentsChange(int position, int charsAdded); const style::InputField &_st; int _maxLength = -1; object_ptr<Inner> _inner; QString _oldtext; CtrlEnterSubmit _ctrlEnterSubmit = CtrlEnterSubmit::CtrlEnter; bool _undoAvailable = false; bool _redoAvailable = false; bool _inHeightCheck = false; bool _customUpDown = false; QString _placeholder; base::lambda<QString()> _placeholderFactory; Animation _a_placeholderShifted; bool _placeholderShifted = false; QPainterPath _placeholderPath; Animation _a_borderShown; int _borderAnimationStart = 0; Animation _a_borderOpacity; bool _borderVisible = false; Animation _a_focused; Animation _a_error; bool _focused = false; bool _error = false; QTimer _touchTimer; bool _touchPress = false; bool _touchRightButton = false; bool _touchMove = false; QPoint _touchStart; bool _correcting = false; MimeDataHook _mimeDataHook; }; class InputField : public RpWidget, private base::Subscriber { Q_OBJECT public: InputField(QWidget *parent, const style::InputField &st, base::lambda<QString()> placeholderFactory = base::lambda<QString()>(), const QString &val = QString()); void setMaxLength(int maxLength) { _maxLength = maxLength; } void showError(); const QString &getLastText() const { return _oldtext; } void setPlaceholder(base::lambda<QString()> placeholderFactory); void setPlaceholderHidden(bool forcePlaceholderHidden); void setDisplayFocused(bool focused); void finishAnimating(); void setFocusFast() { setDisplayFocused(true); setFocus(); } QSize sizeHint() const override; QSize minimumSizeHint() const override; QString getText(int start = 0, int end = -1) const; bool hasText() const; bool isUndoAvailable() const; bool isRedoAvailable() const; void customUpDown(bool isCustom); void setTextCursor(const QTextCursor &cursor) { return _inner->setTextCursor(cursor); } QTextCursor textCursor() const { return _inner->textCursor(); } void setText(const QString &text) { _inner->setText(text); startPlaceholderAnimation(); } void clear() { _inner->clear(); startPlaceholderAnimation(); } bool hasFocus() const { return _inner->hasFocus(); } void setFocus() { _inner->setFocus(); auto cursor = _inner->textCursor(); cursor.movePosition(QTextCursor::End); _inner->setTextCursor(cursor); } void clearFocus() { _inner->clearFocus(); } void setCursorPosition(int pos) { auto cursor = _inner->textCursor(); cursor.setPosition(pos); _inner->setTextCursor(cursor); } public slots: void selectAll(); private slots: void onTouchTimer(); void onDocumentContentsChange(int position, int charsRemoved, int charsAdded); void onDocumentContentsChanged(); void onUndoAvailable(bool avail); void onRedoAvailable(bool avail); void onFocusInner(); signals: void changed(); void submitted(bool ctrlShiftEnter); void cancelled(); void tabbed(); void focused(); void blurred(); protected: void startPlaceholderAnimation(); void startBorderAnimation(); void insertEmoji(EmojiPtr emoji, QTextCursor c); TWidget *tparent() { return qobject_cast<TWidget*>(parentWidget()); } const TWidget *tparent() const { return qobject_cast<const TWidget*>(parentWidget()); } void touchEvent(QTouchEvent *e); void paintEvent(QPaintEvent *e) override; void focusInEvent(QFocusEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void contextMenuEvent(QContextMenuEvent *e) override; void resizeEvent(QResizeEvent *e) override; private: class Inner : public QTextEdit { public: Inner(InputField *parent); QVariant loadResource(int type, const QUrl &name) override; protected: bool viewportEvent(QEvent *e) override; void focusInEvent(QFocusEvent *e) override; void focusOutEvent(QFocusEvent *e) override; void keyPressEvent(QKeyEvent *e) override; void contextMenuEvent(QContextMenuEvent *e) override; QMimeData *createMimeDataFromSelection() const override; private: InputField *f() const { return static_cast<InputField*>(parentWidget()); } friend class InputField; }; friend class Inner; void updatePalette(); void refreshPlaceholder(); void setErrorShown(bool error); void focusInInner(bool focusByMouse); void focusOutInner(); void setFocused(bool focused); void processDocumentContentsChange(int position, int charsAdded); const style::InputField &_st; std::unique_ptr<Inner> _inner; int _maxLength = -1; bool _forcePlaceholderHidden = false; QString _oldtext; bool _undoAvailable = false; bool _redoAvailable = false; bool _customUpDown = true; QString _placeholder; base::lambda<QString()> _placeholderFactory; Animation _a_placeholderShifted; bool _placeholderShifted = false; QPainterPath _placeholderPath; Animation _a_borderShown; int _borderAnimationStart = 0; Animation _a_borderOpacity; bool _borderVisible = false; Animation _a_focused; Animation _a_error; bool _focused = false; bool _error = false; QTimer _touchTimer; bool _touchPress = false; bool _touchRightButton = false; bool _touchMove = false; QPoint _touchStart; bool _correcting = false; }; class MaskedInputField : public RpWidgetWrap<QLineEdit> , private base::Subscriber { Q_OBJECT using Parent = RpWidgetWrap<QLineEdit>; public: MaskedInputField(QWidget *parent, const style::InputField &st, base::lambda<QString()> placeholderFactory = base::lambda<QString()>(), const QString &val = QString()); void showError(); QRect getTextRect() const; QSize sizeHint() const override; QSize minimumSizeHint() const override; void customUpDown(bool isCustom); const QString &getLastText() const { return _oldtext; } void setPlaceholder(base::lambda<QString()> placeholderFactory); void setPlaceholderHidden(bool forcePlaceholderHidden); void setDisplayFocused(bool focused); void finishAnimating(); void setFocusFast() { setDisplayFocused(true); setFocus(); } void setText(const QString &text) { QLineEdit::setText(text); startPlaceholderAnimation(); } void clear() { QLineEdit::clear(); startPlaceholderAnimation(); } public slots: void onTextChange(const QString &text); void onCursorPositionChanged(int oldPosition, int position); void onTextEdited(); void onTouchTimer(); signals: void changed(); void cancelled(); void submitted(bool ctrlShiftEnter); void focused(); void blurred(); protected: QString getDisplayedText() const { auto result = getLastText(); if (!_lastPreEditText.isEmpty()) { result = result.mid(0, _oldcursor) + _lastPreEditText + result.mid(_oldcursor); } return result; } void startBorderAnimation(); void startPlaceholderAnimation(); bool eventHook(QEvent *e) override; void touchEvent(QTouchEvent *e); void paintEvent(QPaintEvent *e) override; void focusInEvent(QFocusEvent *e) override; void focusOutEvent(QFocusEvent *e) override; void keyPressEvent(QKeyEvent *e) override; void resizeEvent(QResizeEvent *e) override; void contextMenuEvent(QContextMenuEvent *e) override; void inputMethodEvent(QInputMethodEvent *e) override; virtual void correctValue( const QString &was, int wasCursor, QString &now, int &nowCursor) { } void setCorrectedText(QString &now, int &nowCursor, const QString &newText, int newPos); virtual void paintAdditionalPlaceholder(Painter &p, TimeMs ms) { } style::font phFont() { return _st.font; } void placeholderAdditionalPrepare(Painter &p, TimeMs ms); QRect placeholderRect() const; void setTextMargins(const QMargins &mrg); const style::InputField &_st; private: void updatePalette(); void refreshPlaceholder(); void setErrorShown(bool error); void setFocused(bool focused); int _maxLength = -1; bool _forcePlaceholderHidden = false; QString _oldtext; int _oldcursor = 0; QString _lastPreEditText; bool _undoAvailable = false; bool _redoAvailable = false; bool _customUpDown = false; QString _placeholder; base::lambda<QString()> _placeholderFactory; Animation _a_placeholderShifted; bool _placeholderShifted = false; QPainterPath _placeholderPath; Animation _a_borderShown; int _borderAnimationStart = 0; Animation _a_borderOpacity; bool _borderVisible = false; Animation _a_focused; Animation _a_error; bool _focused = false; bool _error = false; style::margins _textMargins; QTimer _touchTimer; bool _touchPress = false; bool _touchRightButton = false; bool _touchMove = false; QPoint _touchStart; }; class CountryCodeInput : public MaskedInputField { Q_OBJECT public: CountryCodeInput(QWidget *parent, const style::InputField &st); public slots: void startErasing(QKeyEvent *e); void codeSelected(const QString &code); signals: void codeChanged(const QString &code); void addedToNumber(const QString &added); protected: void correctValue( const QString &was, int wasCursor, QString &now, int &nowCursor) override; private: bool _nosignal; }; class PhonePartInput : public MaskedInputField { Q_OBJECT public: PhonePartInput(QWidget *parent, const style::InputField &st); public slots: void addedToNumber(const QString &added); void onChooseCode(const QString &code); signals: void voidBackspace(QKeyEvent *e); protected: void keyPressEvent(QKeyEvent *e) override; void correctValue( const QString &was, int wasCursor, QString &now, int &nowCursor) override; void paintAdditionalPlaceholder(Painter &p, TimeMs ms) override; private: QVector<int> _pattern; QString _additionalPlaceholder; }; class PasswordInput : public MaskedInputField { public: PasswordInput(QWidget *parent, const style::InputField &st, base::lambda<QString()> placeholderFactory = base::lambda<QString()>(), const QString &val = QString()); }; class PortInput : public MaskedInputField { public: PortInput(QWidget *parent, const style::InputField &st, base::lambda<QString()> placeholderFactory, const QString &val); protected: void correctValue( const QString &was, int wasCursor, QString &now, int &nowCursor) override; }; class UsernameInput : public MaskedInputField { public: UsernameInput(QWidget *parent, const style::InputField &st, base::lambda<QString()> placeholderFactory, const QString &val, bool isLink); void setLinkPlaceholder(const QString &placeholder); protected: void correctValue( const QString &was, int wasCursor, QString &now, int &nowCursor) override; void paintAdditionalPlaceholder(Painter &p, TimeMs ms) override; private: QString _linkPlaceholder; }; class PhoneInput : public MaskedInputField { public: PhoneInput(QWidget *parent, const style::InputField &st, base::lambda<QString()> placeholderFactory, const QString &val); void clearText(); protected: void focusInEvent(QFocusEvent *e) override; void correctValue( const QString &was, int wasCursor, QString &now, int &nowCursor) override; void paintAdditionalPlaceholder(Painter &p, TimeMs ms) override; private: QVector<int> _pattern; QString _additionalPlaceholder; }; } // namespace Ui ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/widgets/labels.cpp������������������������������������������0000664�0000000�0000000�00000057133�13262451251�0023465�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/widgets/labels.h" #include "ui/widgets/popup_menu.h" #include "mainwindow.h" #include "lang/lang_keys.h" namespace Ui { namespace { TextParseOptions _labelOptions = { TextParseMultiline, // flags 0, // maxw 0, // maxh Qt::LayoutDirectionAuto, // dir }; TextParseOptions _labelMarkedOptions = { TextParseMultiline | TextParseRichText | TextParseLinks | TextParseHashtags | TextParseMentions | TextParseBotCommands | TextParseMarkdown, // flags 0, // maxw 0, // maxh Qt::LayoutDirectionAuto, // dir }; } // namespace CrossFadeAnimation::CrossFadeAnimation(style::color bg) : _bg(bg) { } void CrossFadeAnimation::addLine(Part was, Part now) { _lines.push_back(Line(std::move(was), std::move(now))); } void CrossFadeAnimation::paintFrame(Painter &p, float64 positionReady, float64 alphaWas, float64 alphaNow) { if (_lines.isEmpty()) return; for_const (auto &line, _lines) { paintLine(p, line, positionReady, alphaWas, alphaNow); } } void CrossFadeAnimation::paintLine(Painter &p, const Line &line, float64 positionReady, float64 alphaWas, float64 alphaNow) { auto &snapshotWas = line.was.snapshot; auto &snapshotNow = line.now.snapshot; if (snapshotWas.isNull() && snapshotNow.isNull()) { // This can happen if both labels have an empty line or if one // label has an empty line where the second one already ended. // In this case lineWidth is zero and snapshot is null. return; } auto positionWas = line.was.position; auto positionNow = line.now.position; auto left = anim::interpolate(positionWas.x(), positionNow.x(), positionReady); auto topDelta = (snapshotNow.height() / cIntRetinaFactor()) - (snapshotWas.height() / cIntRetinaFactor()); auto widthDelta = (snapshotNow.width() / cIntRetinaFactor()) - (snapshotWas.width() / cIntRetinaFactor()); auto topWas = anim::interpolate(positionWas.y(), positionNow.y() + topDelta, positionReady); auto topNow = topWas - topDelta; p.setOpacity(alphaWas); if (!snapshotWas.isNull()) { p.drawPixmap(left, topWas, snapshotWas); if (topDelta > 0) { p.fillRect(left, topWas - topDelta, snapshotWas.width() / cIntRetinaFactor(), topDelta, _bg); } } if (widthDelta > 0) { p.fillRect(left + (snapshotWas.width() / cIntRetinaFactor()), topNow, widthDelta, snapshotNow.height() / cIntRetinaFactor(), _bg); } p.setOpacity(alphaNow); if (!snapshotNow.isNull()) { p.drawPixmap(left, topNow, snapshotNow); if (topDelta < 0) { p.fillRect(left, topNow + topDelta, snapshotNow.width() / cIntRetinaFactor(), -topDelta, _bg); } } if (widthDelta < 0) { p.fillRect(left + (snapshotNow.width() / cIntRetinaFactor()), topWas, -widthDelta, snapshotWas.height() / cIntRetinaFactor(), _bg); } } LabelSimple::LabelSimple(QWidget *parent, const style::LabelSimple &st, const QString &value) : TWidget(parent) , _st(st) { setText(value); } void LabelSimple::setText(const QString &value, bool *outTextChanged) { if (_fullText == value) { if (outTextChanged) *outTextChanged = false; return; } _fullText = value; _fullTextWidth = _st.font->width(_fullText); if (!_st.maxWidth || _fullTextWidth <= _st.maxWidth) { _text = _fullText; _textWidth = _fullTextWidth; } else { auto newText = _st.font->elided(_fullText, _st.maxWidth); if (newText == _text) { if (outTextChanged) *outTextChanged = false; return; } _text = newText; _textWidth = _st.font->width(_text); } resize(_textWidth, _st.font->height); update(); if (outTextChanged) *outTextChanged = true; } void LabelSimple::paintEvent(QPaintEvent *e) { Painter p(this); p.setFont(_st.font); p.setPen(_st.textFg); p.drawTextLeft(0, 0, width(), _text, _textWidth); } FlatLabel::FlatLabel(QWidget *parent, const style::FlatLabel &st) : RpWidget(parent) , _text(st.minWidth ? st.minWidth : QFIXED_MAX) , _st(st) , _contextCopyText(lang(lng_context_copy_text)) { init(); } FlatLabel::FlatLabel( QWidget *parent, const QString &text, InitType initType, const style::FlatLabel &st) : RpWidget(parent) , _text(st.minWidth ? st.minWidth : QFIXED_MAX) , _st(st) , _contextCopyText(lang(lng_context_copy_text)) { if (initType == InitType::Rich) { setRichText(text); } else { setText(text); } init(); } FlatLabel::FlatLabel( QWidget *parent, rpl::producer<QString> &&text, const style::FlatLabel &st) : RpWidget(parent) , _text(st.minWidth ? st.minWidth : QFIXED_MAX) , _st(st) , _contextCopyText(lang(lng_context_copy_text)) { textUpdated(); std::move( text ) | rpl::start_with_next([this](const QString &value) { setText(value); }, lifetime()); } FlatLabel::FlatLabel( QWidget *parent, rpl::producer<TextWithEntities> &&text, const style::FlatLabel &st) : RpWidget(parent) , _text(st.minWidth ? st.minWidth : QFIXED_MAX) , _st(st) , _contextCopyText(lang(lng_context_copy_text)) { textUpdated(); std::move( text ) | rpl::start_with_next([this](const TextWithEntities &value) { setMarkedText(value); }, lifetime()); } void FlatLabel::init() { _trippleClickTimer.setSingleShot(true); _touchSelectTimer.setSingleShot(true); connect(&_touchSelectTimer, SIGNAL(timeout()), this, SLOT(onTouchSelect())); } void FlatLabel::textUpdated() { refreshSize(); setMouseTracking(_selectable || _text.hasLinks()); update(); } void FlatLabel::setText(const QString &text) { _text.setText(_st.style, text, _labelOptions); textUpdated(); } void FlatLabel::setRichText(const QString &text) { _text.setRichText(_st.style, text, _labelOptions); textUpdated(); } void FlatLabel::setMarkedText(const TextWithEntities &textWithEntities) { _text.setMarkedText(_st.style, textWithEntities, _labelMarkedOptions); textUpdated(); } void FlatLabel::setSelectable(bool selectable) { _selectable = selectable; setMouseTracking(_selectable || _text.hasLinks()); } void FlatLabel::setDoubleClickSelectsParagraph(bool doubleClickSelectsParagraph) { _doubleClickSelectsParagraph = doubleClickSelectsParagraph; } void FlatLabel::setContextCopyText(const QString ©Text) { _contextCopyText = copyText; } void FlatLabel::setExpandLinksMode(ExpandLinksMode mode) { _contextExpandLinksMode = mode; } void FlatLabel::setBreakEverywhere(bool breakEverywhere) { _breakEverywhere = breakEverywhere; } int FlatLabel::resizeGetHeight(int newWidth) { _allowedWidth = newWidth; int textWidth = countTextWidth(); int textHeight = countTextHeight(textWidth); return textHeight; } int FlatLabel::naturalWidth() const { return _text.maxWidth(); } QMargins FlatLabel::getMargins() const { return _st.margin; } int FlatLabel::countTextWidth() const { return _allowedWidth ? _allowedWidth : (_st.minWidth ? _st.minWidth : _text.maxWidth()); } int FlatLabel::countTextHeight(int textWidth) { _fullTextHeight = _text.countHeight(textWidth); return _st.maxHeight ? qMin(_fullTextHeight, _st.maxHeight) : _fullTextHeight; } void FlatLabel::refreshSize() { int textWidth = countTextWidth(); int textHeight = countTextHeight(textWidth); int fullWidth = _st.margin.left() + textWidth + _st.margin.right(); int fullHeight = _st.margin.top() + textHeight + _st.margin.bottom(); resize(fullWidth, fullHeight); } void FlatLabel::setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk) { _text.setLink(lnkIndex, lnk); } void FlatLabel::setClickHandlerHook(ClickHandlerHook &&hook) { _clickHandlerHook = std::move(hook); } void FlatLabel::mouseMoveEvent(QMouseEvent *e) { _lastMousePos = e->globalPos(); dragActionUpdate(); } void FlatLabel::mousePressEvent(QMouseEvent *e) { if (_contextMenu) { e->accept(); return; // ignore mouse press, that was hiding context menu } dragActionStart(e->globalPos(), e->button()); } Text::StateResult FlatLabel::dragActionStart(const QPoint &p, Qt::MouseButton button) { _lastMousePos = p; auto state = dragActionUpdate(); if (button != Qt::LeftButton) return state; ClickHandler::pressed(); _dragAction = NoDrag; _dragWasInactive = App::wnd()->wasInactivePress(); if (_dragWasInactive) App::wnd()->setInactivePress(false); if (ClickHandler::getPressed()) { _dragStartPosition = mapFromGlobal(_lastMousePos); _dragAction = PrepareDrag; } if (!_selectable || _dragAction != NoDrag) { return state; } if (_trippleClickTimer.isActive() && (_lastMousePos - _trippleClickPoint).manhattanLength() < QApplication::startDragDistance()) { if (state.uponSymbol) { _selection = { state.symbol, state.symbol }; _savedSelection = { 0, 0 }; _dragSymbol = state.symbol; _dragAction = Selecting; _selectionType = TextSelectType::Paragraphs; updateHover(state); _trippleClickTimer.start(QApplication::doubleClickInterval()); update(); } } if (_selectionType != TextSelectType::Paragraphs) { _dragSymbol = state.symbol; bool uponSelected = state.uponSymbol; if (uponSelected) { if (_dragSymbol < _selection.from || _dragSymbol >= _selection.to) { uponSelected = false; } } if (uponSelected) { _dragStartPosition = mapFromGlobal(_lastMousePos); _dragAction = PrepareDrag; // start text drag } else if (!_dragWasInactive) { if (state.afterSymbol) ++_dragSymbol; _selection = { _dragSymbol, _dragSymbol }; _savedSelection = { 0, 0 }; _dragAction = Selecting; update(); } } return state; } Text::StateResult FlatLabel::dragActionFinish(const QPoint &p, Qt::MouseButton button) { _lastMousePos = p; auto state = dragActionUpdate(); auto activated = ClickHandler::unpressed(); if (_dragAction == Dragging) { activated = nullptr; } else if (_dragAction == PrepareDrag) { _selection = { 0, 0 }; _savedSelection = { 0, 0 }; update(); } _dragAction = NoDrag; _selectionType = TextSelectType::Letters; if (activated) { if (!_clickHandlerHook || _clickHandlerHook(activated, button)) { App::activateClickHandler(activated, button); } } #if defined Q_OS_LINUX32 || defined Q_OS_LINUX64 if (!_selection.empty()) { QApplication::clipboard()->setText(_text.originalText(_selection, _contextExpandLinksMode), QClipboard::Selection); } #endif // Q_OS_LINUX32 || Q_OS_LINUX64 return state; } void FlatLabel::mouseReleaseEvent(QMouseEvent *e) { dragActionFinish(e->globalPos(), e->button()); if (!rect().contains(e->pos())) { leaveEvent(e); } } void FlatLabel::mouseDoubleClickEvent(QMouseEvent *e) { auto state = dragActionStart(e->globalPos(), e->button()); if (((_dragAction == Selecting) || (_dragAction == NoDrag)) && _selectionType == TextSelectType::Letters) { if (state.uponSymbol) { _dragSymbol = state.symbol; _selectionType = _doubleClickSelectsParagraph ? TextSelectType::Paragraphs : TextSelectType::Words; if (_dragAction == NoDrag) { _dragAction = Selecting; _selection = { state.symbol, state.symbol }; _savedSelection = { 0, 0 }; } mouseMoveEvent(e); _trippleClickPoint = e->globalPos(); _trippleClickTimer.start(QApplication::doubleClickInterval()); } } } void FlatLabel::enterEventHook(QEvent *e) { _lastMousePos = QCursor::pos(); dragActionUpdate(); } void FlatLabel::leaveEventHook(QEvent *e) { ClickHandler::clearActive(this); } void FlatLabel::focusOutEvent(QFocusEvent *e) { if (!_selection.empty()) { if (_contextMenu) { _savedSelection = _selection; } _selection = { 0, 0 }; update(); } } void FlatLabel::focusInEvent(QFocusEvent *e) { if (!_savedSelection.empty()) { _selection = _savedSelection; _savedSelection = { 0, 0 }; update(); } } void FlatLabel::keyPressEvent(QKeyEvent *e) { e->ignore(); if (e->key() == Qt::Key_Copy || (e->key() == Qt::Key_C && e->modifiers().testFlag(Qt::ControlModifier))) { if (!_selection.empty()) { onCopySelectedText(); e->accept(); } #ifdef Q_OS_MAC } else if (e->key() == Qt::Key_E && e->modifiers().testFlag(Qt::ControlModifier)) { auto selection = _selection.empty() ? (_contextMenu ? _savedSelection : _selection) : _selection; if (!selection.empty()) { QApplication::clipboard()->setText(_text.originalText(selection, _contextExpandLinksMode), QClipboard::FindBuffer); } #endif // Q_OS_MAC } } void FlatLabel::contextMenuEvent(QContextMenuEvent *e) { if (!_selectable) return; showContextMenu(e, ContextMenuReason::FromEvent); } bool FlatLabel::eventHook(QEvent *e) { if (e->type() == QEvent::TouchBegin || e->type() == QEvent::TouchUpdate || e->type() == QEvent::TouchEnd || e->type() == QEvent::TouchCancel) { QTouchEvent *ev = static_cast<QTouchEvent*>(e); if (ev->device()->type() == QTouchDevice::TouchScreen) { touchEvent(ev); return true; } } return RpWidget::eventHook(e); } void FlatLabel::touchEvent(QTouchEvent *e) { const Qt::TouchPointStates &states(e->touchPointStates()); if (e->type() == QEvent::TouchCancel) { // cancel if (!_touchInProgress) return; _touchInProgress = false; _touchSelectTimer.stop(); _touchSelect = false; _dragAction = NoDrag; return; } if (!e->touchPoints().isEmpty()) { _touchPrevPos = _touchPos; _touchPos = e->touchPoints().cbegin()->screenPos().toPoint(); } switch (e->type()) { case QEvent::TouchBegin: { if (_contextMenu) { e->accept(); return; // ignore mouse press, that was hiding context menu } if (_touchInProgress) return; if (e->touchPoints().isEmpty()) return; _touchInProgress = true; _touchSelectTimer.start(QApplication::startDragTime()); _touchSelect = false; _touchStart = _touchPrevPos = _touchPos; } break; case QEvent::TouchUpdate: { if (!_touchInProgress) return; if (_touchSelect) { _lastMousePos = _touchPos; dragActionUpdate(); } } break; case QEvent::TouchEnd: { if (!_touchInProgress) return; _touchInProgress = false; auto weak = make_weak(this); if (_touchSelect) { dragActionFinish(_touchPos, Qt::RightButton); QContextMenuEvent contextMenu(QContextMenuEvent::Mouse, mapFromGlobal(_touchPos), _touchPos); showContextMenu(&contextMenu, ContextMenuReason::FromTouch); } else { // one short tap -- like mouse click dragActionStart(_touchPos, Qt::LeftButton); dragActionFinish(_touchPos, Qt::LeftButton); } if (weak) { _touchSelectTimer.stop(); _touchSelect = false; } } break; } } void FlatLabel::showContextMenu(QContextMenuEvent *e, ContextMenuReason reason) { if (_contextMenu) { _contextMenu->deleteLater(); _contextMenu = nullptr; } if (e->reason() == QContextMenuEvent::Mouse) { _lastMousePos = e->globalPos(); } else { _lastMousePos = QCursor::pos(); } auto state = dragActionUpdate(); bool hasSelection = !_selection.empty(); bool uponSelection = state.uponSymbol && (state.symbol >= _selection.from) && (state.symbol < _selection.to); bool fullSelection = _text.isFullSelection(_selection); if (reason == ContextMenuReason::FromTouch && hasSelection && !uponSelection) { uponSelection = hasSelection; } _contextMenu = new Ui::PopupMenu(nullptr); if (fullSelection && !_contextCopyText.isEmpty()) { _contextMenu->addAction(_contextCopyText, this, SLOT(onCopyContextText())); } else if (uponSelection && !fullSelection) { _contextMenu->addAction(lang(lng_context_copy_selected), this, SLOT(onCopySelectedText())); } else if (!hasSelection && !_contextCopyText.isEmpty()) { _contextMenu->addAction(_contextCopyText, this, SLOT(onCopyContextText())); } if (const auto link = ClickHandler::getActive()) { const auto actionText = link->copyToClipboardContextItemText(); if (!actionText.isEmpty()) { _contextMenu->addAction( actionText, [text = link->copyToClipboardText()] { QApplication::clipboard()->setText(text); }); } } if (_contextMenu->actions().isEmpty()) { delete _contextMenu; _contextMenu = nullptr; } else { connect(_contextMenu, SIGNAL(destroyed(QObject*)), this, SLOT(onContextMenuDestroy(QObject*))); _contextMenu->popup(e->globalPos()); e->accept(); } } void FlatLabel::onCopySelectedText() { auto selection = _selection.empty() ? (_contextMenu ? _savedSelection : _selection) : _selection; if (!selection.empty()) { QApplication::clipboard()->setText(_text.originalText(selection, _contextExpandLinksMode)); } } void FlatLabel::onCopyContextText() { QApplication::clipboard()->setText(_text.originalText({ 0, 0xFFFF }, _contextExpandLinksMode)); } void FlatLabel::onTouchSelect() { _touchSelect = true; dragActionStart(_touchPos, Qt::LeftButton); } void FlatLabel::onContextMenuDestroy(QObject *obj) { if (obj == _contextMenu) { _contextMenu = nullptr; } } void FlatLabel::onExecuteDrag() { if (_dragAction != Dragging) return; auto state = getTextState(_dragStartPosition); bool uponSelected = state.uponSymbol && _selection.from <= state.symbol; if (uponSelected) { if (_dragSymbol < _selection.from || _dragSymbol >= _selection.to) { uponSelected = false; } } ClickHandlerPtr pressedHandler = ClickHandler::getPressed(); QString selectedText; if (uponSelected) { selectedText = _text.originalText(_selection, ExpandLinksAll); } else if (pressedHandler) { selectedText = pressedHandler->dragText(); } if (!selectedText.isEmpty()) { auto mimeData = new QMimeData(); mimeData->setText(selectedText); auto drag = new QDrag(App::wnd()); drag->setMimeData(mimeData); drag->exec(Qt::CopyAction); // We don't receive mouseReleaseEvent when drag is finished. ClickHandler::unpressed(); } } void FlatLabel::clickHandlerActiveChanged(const ClickHandlerPtr &action, bool active) { update(); } void FlatLabel::clickHandlerPressedChanged(const ClickHandlerPtr &action, bool active) { update(); } std::unique_ptr<CrossFadeAnimation> FlatLabel::CrossFade( not_null<FlatLabel*> from, not_null<FlatLabel*> to, style::color bg, QPoint fromPosition, QPoint toPosition) { auto result = std::make_unique<CrossFadeAnimation>(bg); struct Data { QImage full; QVector<int> lineWidths; int lineHeight = 0; int lineAddTop = 0; }; auto prepareData = [&bg](not_null<FlatLabel*> label) { auto result = Data(); result.full = GrabWidgetToImage(label, QRect(), bg->c); auto textWidth = label->width() - label->_st.margin.left() - label->_st.margin.right(); label->_text.countLineWidths(textWidth, &result.lineWidths); result.lineHeight = label->_st.style.font->height; auto addedHeight = (label->_st.style.lineHeight - result.lineHeight); if (addedHeight > 0) { result.lineAddTop = addedHeight / 2; result.lineHeight += addedHeight; } return result; }; auto was = prepareData(from); auto now = prepareData(to); auto maxLines = qMax(was.lineWidths.size(), now.lineWidths.size()); auto fillDataTill = [maxLines](Data &data) { for (auto i = data.lineWidths.size(); i != maxLines; ++i) { data.lineWidths.push_back(-1); } }; fillDataTill(was); fillDataTill(now); auto preparePart = [](FlatLabel *label, QPoint position, Data &data, int index, Data &other) { auto result = CrossFadeAnimation::Part(); auto lineWidth = data.lineWidths[index]; if (lineWidth < 0) { lineWidth = other.lineWidths[index]; } auto fullWidth = data.full.width() / cIntRetinaFactor(); auto top = index * data.lineHeight + data.lineAddTop; auto left = 0; if (label->_st.align & Qt::AlignHCenter) { left += (fullWidth - lineWidth) / 2; } else if (label->_st.align & Qt::AlignRight) { left += (fullWidth - lineWidth); } auto snapshotRect = data.full.rect().intersected(QRect(left * cIntRetinaFactor(), top * cIntRetinaFactor(), lineWidth * cIntRetinaFactor(), label->_st.style.font->height * cIntRetinaFactor())); if (!snapshotRect.isEmpty()) { result.snapshot = App::pixmapFromImageInPlace(data.full.copy(snapshotRect)); result.snapshot.setDevicePixelRatio(cRetinaFactor()); } auto positionBase = position + label->pos(); result.position = positionBase + QPoint(label->_st.margin.left() + left, label->_st.margin.top() + top); return result; }; for (int i = 0; i != maxLines; ++i) { result->addLine(preparePart(from, fromPosition, was, i, now), preparePart(to, toPosition, now, i, was)); } return result; } Text::StateResult FlatLabel::dragActionUpdate() { auto m = mapFromGlobal(_lastMousePos); auto state = getTextState(m); updateHover(state); if (_dragAction == PrepareDrag && (m - _dragStartPosition).manhattanLength() >= QApplication::startDragDistance()) { _dragAction = Dragging; QTimer::singleShot(1, this, SLOT(onExecuteDrag())); } return state; } void FlatLabel::updateHover(const Text::StateResult &state) { bool lnkChanged = ClickHandler::setActive(state.link, this); if (!_selectable) { refreshCursor(state.uponSymbol); return; } Qt::CursorShape cur = style::cur_default; if (_dragAction == NoDrag) { if (state.link) { cur = style::cur_pointer; } else if (state.uponSymbol) { cur = style::cur_text; } } else { if (_dragAction == Selecting) { uint16 second = state.symbol; if (state.afterSymbol && _selectionType == TextSelectType::Letters) { ++second; } auto selection = _text.adjustSelection({ qMin(second, _dragSymbol), qMax(second, _dragSymbol) }, _selectionType); if (_selection != selection) { _selection = selection; _savedSelection = { 0, 0 }; setFocus(); update(); } } else if (_dragAction == Dragging) { } if (ClickHandler::getPressed()) { cur = style::cur_pointer; } else if (_dragAction == Selecting) { cur = style::cur_text; } } if (_dragAction == Selecting) { // checkSelectingScroll(); } else { // noSelectingScroll(); } if (_dragAction == NoDrag && (lnkChanged || cur != _cursor)) { setCursor(_cursor = cur); } } void FlatLabel::refreshCursor(bool uponSymbol) { if (_dragAction != NoDrag) { return; } bool needTextCursor = _selectable && uponSymbol; style::cursor newCursor = needTextCursor ? style::cur_text : style::cur_default; if (ClickHandler::getActive()) { newCursor = style::cur_pointer; } if (newCursor != _cursor) { _cursor = newCursor; setCursor(_cursor); } } Text::StateResult FlatLabel::getTextState(const QPoint &m) const { Text::StateRequestElided request; request.align = _st.align; if (_selectable) { request.flags |= Text::StateRequest::Flag::LookupSymbol; } int textWidth = width() - _st.margin.left() - _st.margin.right(); Text::StateResult state; bool heightExceeded = _st.maxHeight && (_st.maxHeight < _fullTextHeight || textWidth < _text.maxWidth()); bool renderElided = _breakEverywhere || heightExceeded; if (renderElided) { auto lineHeight = qMax(_st.style.lineHeight, _st.style.font->height); auto lines = _st.maxHeight ? qMax(_st.maxHeight / lineHeight, 1) : ((height() / lineHeight) + 2); request.lines = lines; if (_breakEverywhere) { request.flags |= Text::StateRequest::Flag::BreakEverywhere; } state = _text.getStateElided(m - QPoint(_st.margin.left(), _st.margin.top()), textWidth, request); } else { state = _text.getState(m - QPoint(_st.margin.left(), _st.margin.top()), textWidth, request); } return state; } void FlatLabel::setOpacity(float64 o) { _opacity = o; update(); } void FlatLabel::paintEvent(QPaintEvent *e) { Painter p(this); p.setOpacity(_opacity); p.setPen(_st.textFg); p.setTextPalette(_st.palette); int textWidth = width() - _st.margin.left() - _st.margin.right(); auto selection = _selection.empty() ? (_contextMenu ? _savedSelection : _selection) : _selection; bool heightExceeded = _st.maxHeight && (_st.maxHeight < _fullTextHeight || textWidth < _text.maxWidth()); bool renderElided = _breakEverywhere || heightExceeded; if (renderElided) { auto lineHeight = qMax(_st.style.lineHeight, _st.style.font->height); auto lines = _st.maxHeight ? qMax(_st.maxHeight / lineHeight, 1) : ((height() / lineHeight) + 2); _text.drawElided(p, _st.margin.left(), _st.margin.top(), textWidth, lines, _st.align, e->rect().y(), e->rect().bottom(), 0, _breakEverywhere, selection); } else { _text.draw(p, _st.margin.left(), _st.margin.top(), textWidth, _st.align, e->rect().y(), e->rect().bottom(), selection); } } } // namespace Ui �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/widgets/labels.h��������������������������������������������0000664�0000000�0000000�00000013137�13262451251�0023126�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/producer.h> #include "ui/rp_widget.h" #include "styles/style_widgets.h" namespace Ui { class PopupMenu; class CrossFadeAnimation { public: CrossFadeAnimation(style::color bg); struct Part { QPixmap snapshot; QPoint position; }; void addLine(Part was, Part now); void paintFrame(Painter &p, float64 dt) { auto progress = anim::linear(1., dt); paintFrame(p, progress, 1. - progress, progress); } void paintFrame(Painter &p, float64 positionReady, float64 alphaWas, float64 alphaNow); private: struct Line { Line(Part was, Part now) : was(std::move(was)), now(std::move(now)) { } Part was; Part now; }; void paintLine(Painter &p, const Line &line, float64 positionReady, float64 alphaWas, float64 alphaNow); style::color _bg; QList<Line> _lines; }; class LabelSimple : public TWidget { public: LabelSimple(QWidget *parent, const style::LabelSimple &st = st::defaultLabelSimple, const QString &value = QString()); // This method also resizes the label. void setText(const QString &newText, bool *outTextChanged = nullptr); protected: void paintEvent(QPaintEvent *e) override; private: QString _fullText; int _fullTextWidth; QString _text; int _textWidth; const style::LabelSimple &_st; }; class FlatLabel : public RpWidget, public ClickHandlerHost { Q_OBJECT public: FlatLabel(QWidget *parent, const style::FlatLabel &st = st::defaultFlatLabel); enum class InitType { Simple, Rich, }; FlatLabel( QWidget *parent, const QString &text, InitType initType, const style::FlatLabel &st = st::defaultFlatLabel); FlatLabel( QWidget *parent, rpl::producer<QString> &&text, const style::FlatLabel &st = st::defaultFlatLabel); FlatLabel( QWidget *parent, rpl::producer<TextWithEntities> &&text, const style::FlatLabel &st = st::defaultFlatLabel); void setOpacity(float64 o); void setText(const QString &text); void setRichText(const QString &text); void setMarkedText(const TextWithEntities &textWithEntities); void setSelectable(bool selectable); void setDoubleClickSelectsParagraph(bool doubleClickSelectsParagraph); void setContextCopyText(const QString ©Text); void setExpandLinksMode(ExpandLinksMode mode); void setBreakEverywhere(bool breakEverywhere); int naturalWidth() const override; QMargins getMargins() const override; void setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk); using ClickHandlerHook = base::lambda<bool(const ClickHandlerPtr&, Qt::MouseButton)>; void setClickHandlerHook(ClickHandlerHook &&hook); // ClickHandlerHost interface void clickHandlerActiveChanged(const ClickHandlerPtr &action, bool active) override; void clickHandlerPressedChanged(const ClickHandlerPtr &action, bool pressed) override; static std::unique_ptr<CrossFadeAnimation> CrossFade( not_null<FlatLabel*> from, not_null<FlatLabel*> to, style::color bg, QPoint fromPosition = QPoint(), QPoint toPosition = QPoint()); protected: void paintEvent(QPaintEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void mouseDoubleClickEvent(QMouseEvent *e) override; void enterEventHook(QEvent *e) override; void leaveEventHook(QEvent *e) override; void focusOutEvent(QFocusEvent *e) override; void focusInEvent(QFocusEvent *e) override; void keyPressEvent(QKeyEvent *e) override; void contextMenuEvent(QContextMenuEvent *e) override; bool eventHook(QEvent *e) override; // calls touchEvent when necessary void touchEvent(QTouchEvent *e); int resizeGetHeight(int newWidth) override; private slots: void onCopySelectedText(); void onCopyContextText(); void onTouchSelect(); void onContextMenuDestroy(QObject *obj); void onExecuteDrag(); private: void init(); void textUpdated(); Text::StateResult dragActionUpdate(); Text::StateResult dragActionStart(const QPoint &p, Qt::MouseButton button); Text::StateResult dragActionFinish(const QPoint &p, Qt::MouseButton button); void updateHover(const Text::StateResult &state); Text::StateResult getTextState(const QPoint &m) const; void refreshCursor(bool uponSymbol); int countTextWidth() const; int countTextHeight(int textWidth); void refreshSize(); enum class ContextMenuReason { FromEvent, FromTouch, }; void showContextMenu(QContextMenuEvent *e, ContextMenuReason reason); Text _text; const style::FlatLabel &_st; float64 _opacity = 1.; int _allowedWidth = 0; int _fullTextHeight = 0; bool _breakEverywhere = false; style::cursor _cursor = style::cur_default; bool _selectable = false; TextSelection _selection, _savedSelection; TextSelectType _selectionType = TextSelectType::Letters; bool _doubleClickSelectsParagraph = false; enum DragAction { NoDrag = 0x00, PrepareDrag = 0x01, Dragging = 0x02, Selecting = 0x04, }; DragAction _dragAction = NoDrag; QPoint _dragStartPosition; uint16 _dragSymbol = 0; bool _dragWasInactive = false; QPoint _lastMousePos; QPoint _trippleClickPoint; QTimer _trippleClickTimer; Ui::PopupMenu *_contextMenu = nullptr; QString _contextCopyText; ExpandLinksMode _contextExpandLinksMode = ExpandLinksAll; ClickHandlerHook _clickHandlerHook; // text selection and context menu by touch support (at least Windows Surface tablets) bool _touchSelect = false; bool _touchInProgress = false; QPoint _touchStart, _touchPrevPos, _touchPos; QTimer _touchSelectTimer; }; } // namespace Ui ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/widgets/menu.cpp��������������������������������������������0000664�0000000�0000000�00000033310�13262451251�0023156�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/widgets/menu.h" #include "ui/effects/ripple_animation.h" #include "ui/widgets/checkbox.h" namespace Ui { Menu::ActionData::~ActionData() = default; Menu::Menu(QWidget *parent, const style::Menu &st) : TWidget(parent) , _st(st) , _itemHeight(_st.itemPadding.top() + _st.itemFont->height + _st.itemPadding.bottom()) , _separatorHeight(_st.separatorPadding.top() + _st.separatorWidth + _st.separatorPadding.bottom()) { init(); } Menu::Menu(QWidget *parent, QMenu *menu, const style::Menu &st) : TWidget(parent) , _st(st) , _wappedMenu(menu) , _itemHeight(_st.itemPadding.top() + _st.itemFont->height + _st.itemPadding.bottom()) , _separatorHeight(_st.separatorPadding.top() + _st.separatorWidth + _st.separatorPadding.bottom()) { init(); _wappedMenu->setParent(this); for (auto action : _wappedMenu->actions()) { addAction(action); } _wappedMenu->hide(); } void Menu::init() { resize(_forceWidth ? _forceWidth : _st.widthMin, _st.skip * 2); setMouseTracking(true); setAttribute(Qt::WA_OpaquePaintEvent); } QAction *Menu::addAction(const QString &text, const QObject *receiver, const char* member, const style::icon *icon, const style::icon *iconOver) { auto action = addAction(new QAction(text, this), icon, iconOver); connect(action, SIGNAL(triggered(bool)), receiver, member, Qt::QueuedConnection); return action; } QAction *Menu::addAction(const QString &text, base::lambda<void()> callback, const style::icon *icon, const style::icon *iconOver) { auto action = addAction(new QAction(text, this), icon, iconOver); connect(action, &QAction::triggered, action, std::move(callback), Qt::QueuedConnection); return action; } QAction *Menu::addAction(QAction *action, const style::icon *icon, const style::icon *iconOver) { connect(action, SIGNAL(changed()), this, SLOT(actionChanged())); _actions.push_back(action); auto createData = [icon, iconOver, action] { auto data = ActionData(); data.icon = icon; data.iconOver = iconOver ? iconOver : icon; data.hasSubmenu = (action->menu() != nullptr); return data; }; _actionsData.push_back(createData()); auto newWidth = qMax(width(), _st.widthMin); newWidth = processAction(action, _actions.size() - 1, newWidth); auto newHeight = height() + (action->isSeparator() ? _separatorHeight : _itemHeight); resize(_forceWidth ? _forceWidth : newWidth, newHeight); if (_resizedCallback) { _resizedCallback(); } updateSelected(QCursor::pos()); update(); return action; } QAction *Menu::addSeparator() { auto separator = new QAction(this); separator->setSeparator(true); return addAction(separator); } void Menu::clearActions() { setSelected(-1); setPressed(-1); _actionsData.clear(); for (auto action : base::take(_actions)) { if (action->parent() == this) { delete action; } } resize(_forceWidth ? _forceWidth : _st.widthMin, _st.skip * 2); if (_resizedCallback) { _resizedCallback(); } } void Menu::finishAnimating() { for (auto &data : _actionsData) { if (data.ripple) { data.ripple.reset(); } if (data.toggle) { data.toggle->finishAnimating(); } } } int Menu::processAction(QAction *action, int index, int width) { auto &data = _actionsData[index]; if (action->isSeparator() || action->text().isEmpty()) { data.text = data.shortcut = QString(); } else { auto actionTextParts = action->text().split('\t'); auto actionText = actionTextParts.empty() ? QString() : actionTextParts[0]; auto actionShortcut = (actionTextParts.size() > 1) ? actionTextParts[1] : QString(); int textw = _st.itemFont->width(actionText); int goodw = _st.itemPadding.left() + textw + _st.itemPadding.right(); if (data.hasSubmenu) { goodw += _st.itemPadding.right() + _st.arrow.width(); } else if (!actionShortcut.isEmpty()) { goodw += _st.itemPadding.right() + _st.itemFont->width(actionShortcut); } if (action->isCheckable()) { auto updateCallback = [this, index] { updateItem(index); }; if (data.toggle) { data.toggle->setUpdateCallback(updateCallback); data.toggle->setCheckedAnimated(action->isChecked()); } else { data.toggle = std::make_unique<ToggleView>(_st.itemToggle, action->isChecked(), updateCallback); } goodw += _st.itemPadding.right() + data.toggle->getSize().width() - _st.itemToggleShift; } else { data.toggle.reset(); } width = snap(goodw, width, _st.widthMax); data.text = (width < goodw) ? _st.itemFont->elided(actionText, width - (goodw - textw)) : actionText; data.shortcut = actionShortcut; } return width; } void Menu::setShowSource(TriggeredSource source) { _mouseSelection = (source == TriggeredSource::Mouse); setSelected((source == TriggeredSource::Mouse || _actions.isEmpty()) ? -1 : 0); } Menu::Actions &Menu::actions() { return _actions; } void Menu::setForceWidth(int forceWidth) { _forceWidth = forceWidth; resize(_forceWidth, height()); } void Menu::actionChanged() { auto newWidth = _st.widthMin; for (auto i = 0, count = _actions.size(); i != count; ++i) { newWidth = processAction(_actions[i], i, newWidth); } if (newWidth != width() && !_forceWidth) { resize(newWidth, height()); if (_resizedCallback) { _resizedCallback(); } } update(); } void Menu::paintEvent(QPaintEvent *e) { Painter p(this); auto ms = getms(); auto clip = e->rect(); auto topskip = QRect(0, 0, width(), _st.skip); auto bottomskip = QRect(0, height() - _st.skip, width(), _st.skip); if (clip.intersects(topskip)) p.fillRect(clip.intersected(topskip), _st.itemBg); if (clip.intersects(bottomskip)) p.fillRect(clip.intersected(bottomskip), _st.itemBg); int top = _st.skip; p.translate(0, top); p.setFont(_st.itemFont); for (int i = 0, count = _actions.size(); i != count; ++i) { if (clip.top() + clip.height() <= top) break; auto action = _actions[i]; auto &data = _actionsData[i]; auto actionHeight = action->isSeparator() ? _separatorHeight : _itemHeight; top += actionHeight; if (clip.top() < top) { if (action->isSeparator()) { p.fillRect(0, 0, width(), actionHeight, _st.itemBg); p.fillRect(_st.separatorPadding.left(), _st.separatorPadding.top(), width() - _st.separatorPadding.left() - _st.separatorPadding.right(), _st.separatorWidth, _st.separatorFg); } else { auto enabled = action->isEnabled(); auto selected = ((i == _selected || i == _pressed) && enabled); p.fillRect(0, 0, width(), actionHeight, selected ? _st.itemBgOver : _st.itemBg); if (data.ripple) { data.ripple->paint(p, 0, 0, width(), ms); if (data.ripple->empty()) { data.ripple.reset(); } } if (auto icon = (selected ? data.iconOver : data.icon)) { icon->paint(p, _st.itemIconPosition, width()); } p.setPen(selected ? _st.itemFgOver : (enabled ? _st.itemFg : _st.itemFgDisabled)); p.drawTextLeft(_st.itemPadding.left(), _st.itemPadding.top(), width(), data.text); if (data.hasSubmenu) { _st.arrow.paint(p, width() - _st.itemPadding.right() - _st.arrow.width(), (_itemHeight - _st.arrow.height()) / 2, width()); } else if (!data.shortcut.isEmpty()) { p.setPen(selected ? _st.itemFgShortcutOver : (enabled ? _st.itemFgShortcut : _st.itemFgShortcutDisabled)); p.drawTextRight(_st.itemPadding.right(), _st.itemPadding.top(), width(), data.shortcut); } else if (data.toggle) { auto toggleSize = data.toggle->getSize(); data.toggle->paint(p, width() - _st.itemPadding.right() - toggleSize.width() + _st.itemToggleShift, (_itemHeight - toggleSize.height()) / 2, width(), ms); } } } p.translate(0, actionHeight); } } void Menu::updateSelected(QPoint globalPosition) { if (!_mouseSelection) return; auto p = mapFromGlobal(globalPosition) - QPoint(0, _st.skip); auto selected = -1, top = 0; while (top <= p.y() && ++selected < _actions.size()) { top += _actions[selected]->isSeparator() ? _separatorHeight : _itemHeight; } setSelected((selected >= 0 && selected < _actions.size() && _actions[selected]->isEnabled() && !_actions[selected]->isSeparator()) ? selected : -1); } void Menu::itemPressed(TriggeredSource source) { if (source == TriggeredSource::Mouse && !_mouseSelection) { return; } if (_selected >= 0 && _selected < _actions.size() && _actions[_selected]->isEnabled()) { setPressed(_selected); if (source == TriggeredSource::Mouse) { if (!_actionsData[_pressed].ripple) { auto mask = RippleAnimation::rectMask(QSize(width(), _itemHeight)); _actionsData[_pressed].ripple = std::make_unique<RippleAnimation>(_st.ripple, std::move(mask), [this, selected = _pressed] { updateItem(selected); }); } _actionsData[_pressed].ripple->add(mapFromGlobal(QCursor::pos()) - QPoint(0, itemTop(_pressed))); } else { itemReleased(source); } } } void Menu::itemReleased(TriggeredSource source) { if (_pressed >= 0 && _pressed < _actions.size()) { auto pressed = _pressed; setPressed(-1); if (source == TriggeredSource::Mouse && _actionsData[pressed].ripple) { _actionsData[pressed].ripple->lastStop(); } if (pressed == _selected && _triggeredCallback) { _triggeredCallback(_actions[_selected], itemTop(_selected), source); } } } void Menu::keyPressEvent(QKeyEvent *e) { auto key = e->key(); if (!_keyPressDelegate || !_keyPressDelegate(key)) { handleKeyPress(key); } } void Menu::handleKeyPress(int key) { if (key == Qt::Key_Enter || key == Qt::Key_Return) { itemPressed(TriggeredSource::Keyboard); return; } if (key == (rtl() ? Qt::Key_Left : Qt::Key_Right)) { if (_selected >= 0 && _actionsData[_selected].hasSubmenu) { itemPressed(TriggeredSource::Keyboard); return; } else if (_selected < 0 && !_actions.isEmpty()) { _mouseSelection = false; setSelected(0); } } if ((key != Qt::Key_Up && key != Qt::Key_Down) || _actions.size() < 1) return; auto delta = (key == Qt::Key_Down ? 1 : -1), start = _selected; if (start < 0 || start >= _actions.size()) { start = (delta > 0) ? (_actions.size() - 1) : 0; } auto newSelected = start; do { newSelected += delta; if (newSelected < 0) { newSelected += _actions.size(); } else if (newSelected >= _actions.size()) { newSelected -= _actions.size(); } } while (newSelected != start && (!_actions[newSelected]->isEnabled() || _actions[newSelected]->isSeparator())); if (_actions[newSelected]->isEnabled() && !_actions[newSelected]->isSeparator()) { _mouseSelection = false; setSelected(newSelected); } } void Menu::clearSelection() { _mouseSelection = false; setSelected(-1); } void Menu::clearMouseSelection() { if (_mouseSelection && !_childShown) { clearSelection(); } } void Menu::enterEventHook(QEvent *e) { QPoint mouse = QCursor::pos(); if (!rect().marginsRemoved(QMargins(0, _st.skip, 0, _st.skip)).contains(mapFromGlobal(mouse))) { clearMouseSelection(); } return TWidget::enterEventHook(e); } void Menu::leaveEventHook(QEvent *e) { clearMouseSelection(); return TWidget::leaveEventHook(e); } void Menu::setSelected(int selected) { if (selected >= _actions.size()) { selected = -1; } if (_selected != selected) { updateSelectedItem(); if (_selected >= 0 && _selected != _pressed && _actionsData[_selected].toggle) { _actionsData[_selected].toggle->setStyle(_st.itemToggle); } _selected = selected; if (_selected >= 0 && _actionsData[_selected].toggle && _actions[_selected]->isEnabled()) { _actionsData[_selected].toggle->setStyle(_st.itemToggleOver); } updateSelectedItem(); if (_activatedCallback) { auto source = _mouseSelection ? TriggeredSource::Mouse : TriggeredSource::Keyboard; _activatedCallback((_selected >= 0) ? _actions[_selected] : nullptr, itemTop(_selected), source); } } } void Menu::setPressed(int pressed) { if (pressed >= _actions.size()) { pressed = -1; } if (_pressed != pressed) { if (_pressed >= 0 && _pressed != _selected && _actionsData[_pressed].toggle) { _actionsData[_pressed].toggle->setStyle(_st.itemToggle); } _pressed = pressed; if (_pressed >= 0 && _actionsData[_pressed].toggle && _actions[_pressed]->isEnabled()) { _actionsData[_pressed].toggle->setStyle(_st.itemToggleOver); } } } int Menu::itemTop(int index) { if (index > _actions.size()) { index = _actions.size(); } int top = _st.skip; for (int i = 0; i < index; ++i) { top += _actions.at(i)->isSeparator() ? _separatorHeight : _itemHeight; } return top; } void Menu::updateItem(int index) { if (index >= 0 && index < _actions.size()) { update(0, itemTop(index), width(), _actions[index]->isSeparator() ? _separatorHeight : _itemHeight); } } void Menu::updateSelectedItem() { updateItem(_selected); } void Menu::mouseMoveEvent(QMouseEvent *e) { handleMouseMove(e->globalPos()); } void Menu::handleMouseMove(QPoint globalPosition) { auto inner = rect().marginsRemoved(QMargins(0, _st.skip, 0, _st.skip)); auto localPosition = mapFromGlobal(globalPosition); if (inner.contains(localPosition)) { _mouseSelection = true; updateSelected(globalPosition); } else { clearMouseSelection(); if (_mouseMoveDelegate) { _mouseMoveDelegate(globalPosition); } } } void Menu::mousePressEvent(QMouseEvent *e) { handleMousePress(e->globalPos()); } void Menu::mouseReleaseEvent(QMouseEvent *e) { handleMouseRelease(e->globalPos()); } void Menu::handleMousePress(QPoint globalPosition) { handleMouseMove(globalPosition); if (rect().contains(mapFromGlobal(globalPosition))) { itemPressed(TriggeredSource::Mouse); } else if (_mousePressDelegate) { _mousePressDelegate(globalPosition); } } void Menu::handleMouseRelease(QPoint globalPosition) { handleMouseMove(globalPosition); itemReleased(TriggeredSource::Mouse); if (!rect().contains(mapFromGlobal(globalPosition)) && _mouseReleaseDelegate) { _mouseReleaseDelegate(globalPosition); } } } // namespace Ui ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/widgets/menu.h����������������������������������������������0000664�0000000�0000000�00000010571�13262451251�0022627�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "styles/style_widgets.h" namespace Ui { class ToggleView; class RippleAnimation; class Menu : public TWidget { Q_OBJECT public: Menu(QWidget *parent, const style::Menu &st = st::defaultMenu); Menu(QWidget *parent, QMenu *menu, const style::Menu &st = st::defaultMenu); QAction *addAction(const QString &text, const QObject *receiver, const char* member, const style::icon *icon = nullptr, const style::icon *iconOver = nullptr); QAction *addAction(const QString &text, base::lambda<void()> callback, const style::icon *icon = nullptr, const style::icon *iconOver = nullptr); QAction *addSeparator(); void clearActions(); void finishAnimating(); void clearSelection(); enum class TriggeredSource { Mouse, Keyboard, }; void setChildShown(bool shown) { _childShown = shown; } void setShowSource(TriggeredSource source); void setForceWidth(int forceWidth); using Actions = QList<QAction*>; Actions &actions(); void setResizedCallback(base::lambda<void()> callback) { _resizedCallback = std::move(callback); } void setActivatedCallback(base::lambda<void(QAction *action, int actionTop, TriggeredSource source)> callback) { _activatedCallback = std::move(callback); } void setTriggeredCallback(base::lambda<void(QAction *action, int actionTop, TriggeredSource source)> callback) { _triggeredCallback = std::move(callback); } void setKeyPressDelegate(base::lambda<bool(int key)> delegate) { _keyPressDelegate = std::move(delegate); } void handleKeyPress(int key); void setMouseMoveDelegate(base::lambda<void(QPoint globalPosition)> delegate) { _mouseMoveDelegate = std::move(delegate); } void handleMouseMove(QPoint globalPosition); void setMousePressDelegate(base::lambda<void(QPoint globalPosition)> delegate) { _mousePressDelegate = std::move(delegate); } void handleMousePress(QPoint globalPosition); void setMouseReleaseDelegate(base::lambda<void(QPoint globalPosition)> delegate) { _mouseReleaseDelegate = std::move(delegate); } void handleMouseRelease(QPoint globalPosition); protected: void paintEvent(QPaintEvent *e) override; void keyPressEvent(QKeyEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void enterEventHook(QEvent *e) override; void leaveEventHook(QEvent *e) override; private slots: void actionChanged(); private: struct ActionData { ActionData() = default; ActionData(const ActionData &other) = delete; ActionData &operator=(const ActionData &other) = delete; ActionData(ActionData &&other) = default; ActionData &operator=(ActionData &&other) = default; ~ActionData(); bool hasSubmenu = false; QString text; QString shortcut; const style::icon *icon = nullptr; const style::icon *iconOver = nullptr; std::unique_ptr<RippleAnimation> ripple; std::unique_ptr<ToggleView> toggle; }; void updateSelected(QPoint globalPosition); void init(); // Returns the new width. int processAction(QAction *action, int index, int width); QAction *addAction(QAction *a, const style::icon *icon = nullptr, const style::icon *iconOver = nullptr); void setSelected(int selected); void setPressed(int pressed); void clearMouseSelection(); int itemTop(int index); void updateItem(int index); void updateSelectedItem(); void itemPressed(TriggeredSource source); void itemReleased(TriggeredSource source); const style::Menu &_st; base::lambda<void()> _resizedCallback; base::lambda<void(QAction *action, int actionTop, TriggeredSource source)> _activatedCallback; base::lambda<void(QAction *action, int actionTop, TriggeredSource source)> _triggeredCallback; base::lambda<bool(int key)> _keyPressDelegate; base::lambda<void(QPoint globalPosition)> _mouseMoveDelegate; base::lambda<void(QPoint globalPosition)> _mousePressDelegate; base::lambda<void(QPoint globalPosition)> _mouseReleaseDelegate; QMenu *_wappedMenu = nullptr; Actions _actions; std::vector<ActionData> _actionsData; int _forceWidth = 0; int _itemHeight, _separatorHeight; bool _mouseSelection = false; int _selected = -1; int _pressed = -1; bool _childShown = false; }; } // namespace Ui ���������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/widgets/multi_select.cpp������������������������������������0000664�0000000�0000000�00000052424�13262451251�0024712�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/widgets/multi_select.h" #include "styles/style_widgets.h" #include "ui/widgets/buttons.h" #include "ui/widgets/input_fields.h" #include "ui/widgets/scroll_area.h" #include "ui/effects/cross_animation.h" #include "ui/text_options.h" #include "lang/lang_keys.h" namespace Ui { namespace { constexpr int kWideScale = 3; } // namespace MultiSelect::Item::Item(const style::MultiSelectItem &st, uint64 id, const QString &text, style::color color, PaintRoundImage &&paintRoundImage) : _st(st) , _id(id) , _color(color) , _paintRoundImage(std::move(paintRoundImage)) { setText(text); } void MultiSelect::Item::setText(const QString &text) { _text.setText(_st.style, text, NameTextOptions()); _width = _st.height + _st.padding.left() + _text.maxWidth() + _st.padding.right(); accumulate_min(_width, _st.maxWidth); } void MultiSelect::Item::paint(Painter &p, int outerWidth, TimeMs ms) { if (!_cache.isNull() && !_visibility.animating(ms)) { if (_hiding) { return; } else { _cache = QPixmap(); } } if (_copies.empty()) { paintOnce(p, _x, _y, outerWidth, ms); } else { for (auto i = _copies.begin(), e = _copies.end(); i != e;) { auto x = qRound(i->x.current(getms(), _x)); auto y = i->y; auto animating = i->x.animating(); if (animating || (y == _y)) { paintOnce(p, x, y, outerWidth, ms); } if (animating) { ++i; } else { i = _copies.erase(i); e = _copies.end(); } } } } void MultiSelect::Item::paintOnce(Painter &p, int x, int y, int outerWidth, TimeMs ms) { if (!_cache.isNull()) { paintCached(p, x, y, outerWidth); return; } auto radius = _st.height / 2; auto inner = rtlrect(x + radius, y, _width - radius, _st.height, outerWidth); auto clipEnabled = p.hasClipping(); auto clip = clipEnabled ? p.clipRegion() : QRegion(); p.setClipRect(inner); p.setPen(Qt::NoPen); p.setBrush(_active ? _st.textActiveBg : _st.textBg); { PainterHighQualityEnabler hq(p); p.drawRoundedRect(rtlrect(x, y, _width, _st.height, outerWidth), radius, radius); } if (clipEnabled) { p.setClipRegion(clip); } else { p.setClipping(false); } auto overOpacity = _overOpacity.current(ms, _over ? 1. : 0.); if (overOpacity < 1.) { _paintRoundImage(p, x, y, outerWidth, _st.height); } if (overOpacity > 0.) { paintDeleteButton(p, x, y, outerWidth, overOpacity); } auto textLeft = _st.height + _st.padding.left(); auto textWidth = _width - textLeft - _st.padding.right(); p.setPen(_active ? _st.textActiveFg : _st.textFg); _text.drawLeftElided(p, x + textLeft, y + _st.padding.top(), textWidth, outerWidth); } void MultiSelect::Item::paintDeleteButton(Painter &p, int x, int y, int outerWidth, float64 overOpacity) { p.setOpacity(overOpacity); p.setPen(Qt::NoPen); p.setBrush(_color); { PainterHighQualityEnabler hq(p); p.drawEllipse(rtlrect(x, y, _st.height, _st.height, outerWidth)); } CrossAnimation::paint(p, _st.deleteCross, _st.deleteFg, x, y, outerWidth, overOpacity); p.setOpacity(1.); } bool MultiSelect::Item::paintCached(Painter &p, int x, int y, int outerWidth) { PainterHighQualityEnabler hq(p); auto opacity = _visibility.current(_hiding ? 0. : 1.); auto scale = opacity + _st.minScale * (1. - opacity); auto height = opacity * _cache.height() / _cache.devicePixelRatio(); auto width = opacity * _cache.width() / _cache.devicePixelRatio(); p.setOpacity(opacity); p.drawPixmap(rtlrect(x + (_width - width) / 2., y + (_st.height - height) / 2., width, height, outerWidth), _cache); p.setOpacity(1.); return true; } void MultiSelect::Item::mouseMoveEvent(QPoint point) { if (!_cache.isNull()) return; _overDelete = QRect(0, 0, _st.height, _st.height).contains(point); setOver(true); } void MultiSelect::Item::leaveEvent() { _overDelete = false; setOver(false); } void MultiSelect::Item::setPosition(int x, int y, int outerWidth, int maxVisiblePadding) { if (_x >= 0 && _y >= 0 && (_x != x || _y != y)) { // Make an animation if it is not the first setPosition(). auto found = false; auto leftHidden = -_width - maxVisiblePadding; auto rightHidden = outerWidth + maxVisiblePadding; for (auto i = _copies.begin(), e = _copies.end(); i != e;) { if (i->x.animating()) { if (i->y == y) { i->x.start(_updateCallback, i->toX, x, _st.duration); found = true; } else { i->x.start(_updateCallback, i->fromX, (i->toX > i->fromX) ? rightHidden : leftHidden, _st.duration); } ++i; } else { i = _copies.erase(i); e = _copies.end(); } } if (_copies.empty()) { if (_y == y) { auto copy = SlideAnimation(_updateCallback, _x, x, _y, _st.duration); _copies.push_back(std::move(copy)); } else { auto copyHiding = SlideAnimation(_updateCallback, _x, (y > _y) ? rightHidden : leftHidden, _y, _st.duration); _copies.push_back(std::move(copyHiding)); auto copyShowing = SlideAnimation(_updateCallback, (y > _y) ? leftHidden : rightHidden, x, y, _st.duration); _copies.push_back(std::move(copyShowing)); } } else if (!found) { auto copy = SlideAnimation(_updateCallback, (y > _y) ? leftHidden : rightHidden, x, y, _st.duration); _copies.push_back(std::move(copy)); } } _x = x; _y = y; } QRect MultiSelect::Item::paintArea(int outerWidth) const { if (_copies.empty()) { return rect(); } auto yMin = 0, yMax = 0; for_const (auto ©, _copies) { accumulate_max(yMax, copy.y); if (yMin) { accumulate_min(yMin, copy.y); } else { yMin = copy.y; } } return QRect(0, yMin, outerWidth, yMax - yMin + _st.height); } void MultiSelect::Item::prepareCache() { if (!_cache.isNull()) return; Assert(!_visibility.animating()); auto cacheWidth = _width * kWideScale * cIntRetinaFactor(); auto cacheHeight = _st.height * kWideScale * cIntRetinaFactor(); auto data = QImage(cacheWidth, cacheHeight, QImage::Format_ARGB32_Premultiplied); data.fill(Qt::transparent); data.setDevicePixelRatio(cRetinaFactor()); { Painter p(&data); paintOnce(p, _width * (kWideScale - 1) / 2, _st.height * (kWideScale - 1) / 2, cacheWidth, getms()); } _cache = App::pixmapFromImageInPlace(std::move(data)); } void MultiSelect::Item::setVisibleAnimated(bool visible) { _hiding = !visible; prepareCache(); auto from = visible ? 0. : 1.; auto to = visible ? 1. : 0.; auto transition = visible ? anim::bumpy(1.0625) : anim::linear; _visibility.start(_updateCallback, from, to, _st.duration, transition); } void MultiSelect::Item::setOver(bool over) { if (over != _over) { _over = over; _overOpacity.start(_updateCallback, _over ? 0. : 1., _over ? 1. : 0., _st.duration); } } MultiSelect::MultiSelect( QWidget *parent, const style::MultiSelect &st, base::lambda<QString()> placeholderFactory) : RpWidget(parent) , _st(st) , _scroll(this, _st.scroll) { _inner = _scroll->setOwnedWidget(object_ptr<Inner>(this, st, std::move(placeholderFactory), [this](int activeTop, int activeBottom) { scrollTo(activeTop, activeBottom); })); _scroll->installEventFilter(this); _inner->setResizedCallback([this](int innerHeightDelta) { auto newHeight = resizeGetHeight(width()); if (innerHeightDelta > 0) { _scroll->scrollToY(_scroll->scrollTop() + innerHeightDelta); } if (newHeight != height()) { resize(width(), newHeight); if (_resizedCallback) { _resizedCallback(); } } }); _inner->setQueryChangedCallback([this](const QString &query) { _scroll->scrollToY(_scroll->scrollTopMax()); if (_queryChangedCallback) { _queryChangedCallback(query); } }); setAttribute(Qt::WA_OpaquePaintEvent); auto defaultWidth = _st.item.maxWidth + _st.fieldMinWidth + _st.fieldCancelSkip; resizeToWidth(_st.padding.left() + defaultWidth + _st.padding.right()); } bool MultiSelect::eventFilter(QObject *o, QEvent *e) { if (o == _scroll && e->type() == QEvent::KeyPress) { e->ignore(); return true; } return false; } void MultiSelect::scrollTo(int activeTop, int activeBottom) { auto scrollTop = _scroll->scrollTop(); auto scrollHeight = _scroll->height(); auto scrollBottom = scrollTop + scrollHeight; if (scrollTop > activeTop) { _scroll->scrollToY(activeTop); } else if (scrollBottom < activeBottom) { _scroll->scrollToY(activeBottom - scrollHeight); } } void MultiSelect::setQueryChangedCallback(base::lambda<void(const QString &query)> callback) { _queryChangedCallback = std::move(callback); } void MultiSelect::setSubmittedCallback(base::lambda<void(bool ctrlShiftEnter)> callback) { _inner->setSubmittedCallback(std::move(callback)); } void MultiSelect::setResizedCallback(base::lambda<void()> callback) { _resizedCallback = std::move(callback); } void MultiSelect::setInnerFocus() { if (_inner->setInnerFocus()) { _scroll->scrollToY(_scroll->scrollTopMax()); } } void MultiSelect::clearQuery() { _inner->clearQuery(); } QString MultiSelect::getQuery() const { return _inner->getQuery(); } void MultiSelect::addItem(uint64 itemId, const QString &text, style::color color, PaintRoundImage paintRoundImage, AddItemWay way) { addItemInBunch(itemId, text, color, std::move(paintRoundImage)); _inner->finishItemsBunch(way); } void MultiSelect::addItemInBunch(uint64 itemId, const QString &text, style::color color, PaintRoundImage paintRoundImage) { _inner->addItemInBunch(std::make_unique<Item>(_st.item, itemId, text, color, std::move(paintRoundImage))); } void MultiSelect::finishItemsBunch() { _inner->finishItemsBunch(AddItemWay::SkipAnimation); } void MultiSelect::setItemRemovedCallback(base::lambda<void(uint64 itemId)> callback) { _inner->setItemRemovedCallback(std::move(callback)); } void MultiSelect::removeItem(uint64 itemId) { _inner->removeItem(itemId); } int MultiSelect::getItemsCount() const { return _inner->getItemsCount(); } QVector<uint64> MultiSelect::getItems() const { return _inner->getItems(); } bool MultiSelect::hasItem(uint64 itemId) const { return _inner->hasItem(itemId); } int MultiSelect::resizeGetHeight(int newWidth) { if (newWidth != _inner->width()) { _inner->resizeToWidth(newWidth); } auto newHeight = qMin(_inner->height(), _st.maxHeight); _scroll->setGeometryToLeft(0, 0, newWidth, newHeight); return newHeight; } MultiSelect::Inner::Inner(QWidget *parent, const style::MultiSelect &st, base::lambda<QString()> placeholder, ScrollCallback callback) : TWidget(parent) , _st(st) , _scrollCallback(std::move(callback)) , _field(this, _st.field, std::move(placeholder)) , _cancel(this, _st.fieldCancel) { _field->customUpDown(true); connect(_field, SIGNAL(focused()), this, SLOT(onFieldFocused())); connect(_field, SIGNAL(changed()), this, SLOT(onQueryChanged())); connect(_field, SIGNAL(submitted(bool)), this, SLOT(onSubmitted(bool))); _cancel->setClickedCallback([this] { clearQuery(); _field->setFocus(); }); setMouseTracking(true); } void MultiSelect::Inner::onQueryChanged() { auto query = getQuery(); _cancel->toggle(!query.isEmpty(), anim::type::normal); updateFieldGeometry(); if (_queryChangedCallback) { _queryChangedCallback(query); } } QString MultiSelect::Inner::getQuery() const { return _field->getLastText().trimmed(); } bool MultiSelect::Inner::setInnerFocus() { if (_active >= 0) { setFocus(); } else if (!_field->hasFocus()) { _field->setFocusFast(); return true; } return false; } void MultiSelect::Inner::clearQuery() { _field->setText(QString()); } void MultiSelect::Inner::setQueryChangedCallback(base::lambda<void(const QString &query)> callback) { _queryChangedCallback = std::move(callback); } void MultiSelect::Inner::setSubmittedCallback(base::lambda<void(bool ctrlShiftEnter)> callback) { _submittedCallback = std::move(callback); } void MultiSelect::Inner::updateFieldGeometry() { auto fieldFinalWidth = _fieldWidth; if (_cancel->toggled()) { fieldFinalWidth -= _st.fieldCancelSkip; } _field->resizeToWidth(fieldFinalWidth); _field->moveToLeft(_st.padding.left() + _fieldLeft, _st.padding.top() + _fieldTop); } void MultiSelect::Inner::updateHasAnyItems(bool hasAnyItems) { _field->setPlaceholderHidden(hasAnyItems); updateCursor(); _iconOpacity.start([this] { rtlupdate(_st.padding.left(), _st.padding.top(), _st.fieldIcon.width(), _st.fieldIcon.height()); }, hasAnyItems ? 1. : 0., hasAnyItems ? 0. : 1., _st.item.duration); } void MultiSelect::Inner::updateCursor() { setCursor(_items.empty() ? style::cur_text : (_overDelete ? style::cur_pointer : style::cur_default)); } void MultiSelect::Inner::setActiveItem(int active, ChangeActiveWay skipSetFocus) { if (_active == active) return; if (_active >= 0) { Assert(_active < _items.size()); _items[_active]->setActive(false); } _active = active; if (_active >= 0) { Assert(_active < _items.size()); _items[_active]->setActive(true); } if (skipSetFocus != ChangeActiveWay::SkipSetFocus) { setInnerFocus(); } if (_scrollCallback) { auto rect = (_active >= 0) ? _items[_active]->rect() : _field->geometry().translated(-_st.padding.left(), -_st.padding.top()); _scrollCallback(rect.y(), rect.y() + rect.height() + _st.padding.top() + _st.padding.bottom()); } update(); } void MultiSelect::Inner::setActiveItemPrevious() { if (_active > 0) { setActiveItem(_active - 1); } else if (_active < 0 && !_items.empty()) { setActiveItem(_items.size() - 1); } } void MultiSelect::Inner::setActiveItemNext() { if (_active >= 0 && _active + 1 < _items.size()) { setActiveItem(_active + 1); } else { setActiveItem(-1); } } int MultiSelect::Inner::resizeGetHeight(int newWidth) { computeItemsGeometry(newWidth); updateFieldGeometry(); auto cancelLeft = _fieldLeft + _fieldWidth + _st.padding.right() - _cancel->width(); auto cancelTop = _fieldTop - _st.padding.top(); _cancel->moveToLeft(_st.padding.left() + cancelLeft, _st.padding.top() + cancelTop); return _field->y() + _field->height() + _st.padding.bottom(); } void MultiSelect::Inner::paintEvent(QPaintEvent *e) { Painter p(this); auto ms = getms(); _height.step(ms); _iconOpacity.step(ms); auto paintRect = e->rect(); p.fillRect(paintRect, _st.bg); auto offset = QPoint(rtl() ? _st.padding.right() : _st.padding.left(), _st.padding.top()); p.translate(offset); paintRect.translate(-offset); auto outerWidth = width() - _st.padding.left() - _st.padding.right(); auto iconOpacity = _iconOpacity.current(_items.empty() ? 1. : 0.); if (iconOpacity > 0.) { p.setOpacity(iconOpacity); _st.fieldIcon.paint(p, 0, 0, outerWidth); p.setOpacity(1.); } auto checkRect = myrtlrect(paintRect); auto paintMargins = itemPaintMargins(); for (auto i = _removingItems.begin(), e = _removingItems.end(); i != e;) { auto &item = *i; auto itemRect = item->paintArea(outerWidth); itemRect = itemRect.marginsAdded(paintMargins); if (checkRect.intersects(itemRect)) { item->paint(p, outerWidth, ms); } if (item->hideFinished()) { i = _removingItems.erase(i); e = _removingItems.end(); } else { ++i; } } for_const (auto &item, _items) { auto itemRect = item->paintArea(outerWidth); itemRect = itemRect.marginsAdded(paintMargins); if (checkRect.y() + checkRect.height() <= itemRect.y()) { break; } else if (checkRect.intersects(itemRect)) { item->paint(p, outerWidth, ms); } } } QMargins MultiSelect::Inner::itemPaintMargins() const { return { qMax(_st.itemSkip, _st.padding.left()), _st.itemSkip, qMax(_st.itemSkip, _st.padding.right()), _st.itemSkip, }; } void MultiSelect::Inner::leaveEventHook(QEvent *e) { clearSelection(); } void MultiSelect::Inner::mouseMoveEvent(QMouseEvent *e) { updateSelection(e->pos()); } void MultiSelect::Inner::keyPressEvent(QKeyEvent *e) { if (_active >= 0) { Expects(_active < _items.size()); if (e->key() == Qt::Key_Delete || e->key() == Qt::Key_Backspace) { auto itemId = _items[_active]->id(); setActiveItemNext(); removeItem(itemId); } else if (e->key() == Qt::Key_Left) { setActiveItemPrevious(); } else if (e->key() == Qt::Key_Right) { setActiveItemNext(); } else if (e->key() == Qt::Key_Escape) { setActiveItem(-1); } else { e->ignore(); } } else if (e->key() == Qt::Key_Left || e->key() == Qt::Key_Backspace) { setActiveItemPrevious(); } else { e->ignore(); } } void MultiSelect::Inner::onFieldFocused() { setActiveItem(-1, ChangeActiveWay::SkipSetFocus); } void MultiSelect::Inner::updateSelection(QPoint mousePosition) { auto point = myrtlpoint(mousePosition) - QPoint(_st.padding.left(), _st.padding.right()); auto selected = -1; for (auto i = 0, count = int(_items.size()); i != count; ++i) { auto itemRect = _items[i]->rect(); if (itemRect.y() > point.y()) { break; } else if (itemRect.contains(point)) { point -= itemRect.topLeft(); selected = i; break; } } if (_selected != selected) { if (_selected >= 0) { Assert(_selected < _items.size()); _items[_selected]->leaveEvent(); } _selected = selected; update(); } auto overDelete = false; if (_selected >= 0) { _items[_selected]->mouseMoveEvent(point); overDelete = _items[_selected]->isOverDelete(); } if (_overDelete != overDelete) { _overDelete = overDelete; updateCursor(); } } void MultiSelect::Inner::mousePressEvent(QMouseEvent *e) { if (_overDelete) { Expects(_selected >= 0); Expects(_selected < _items.size()); removeItem(_items[_selected]->id()); } else if (_selected >= 0) { setActiveItem(_selected); } else { setInnerFocus(); } } void MultiSelect::Inner::addItemInBunch(std::unique_ptr<Item> item) { auto wasEmpty = _items.empty(); item->setUpdateCallback([this, item = item.get()] { auto itemRect = item->paintArea(width() - _st.padding.left() - _st.padding.top()); itemRect = itemRect.translated(_st.padding.left(), _st.padding.top()); itemRect = itemRect.marginsAdded(itemPaintMargins()); rtlupdate(itemRect); }); _idsMap.insert(item->id()); _items.push_back(std::move(item)); if (wasEmpty) { updateHasAnyItems(true); } } void MultiSelect::Inner::finishItemsBunch(AddItemWay way) { updateItemsGeometry(); if (way != AddItemWay::SkipAnimation) { _items.back()->showAnimated(); } else { _field->finishAnimating(); finishHeightAnimation(); } } void MultiSelect::Inner::computeItemsGeometry(int newWidth) { newWidth -= _st.padding.left() + _st.padding.right(); auto itemLeft = 0; auto itemTop = 0; auto widthLeft = newWidth; auto maxVisiblePadding = qMax(_st.padding.left(), _st.padding.right()); for_const (auto &item, _items) { auto itemWidth = item->getWidth(); Assert(itemWidth <= newWidth); if (itemWidth > widthLeft) { itemLeft = 0; itemTop += _st.item.height + _st.itemSkip; widthLeft = newWidth; } item->setPosition(itemLeft, itemTop, newWidth, maxVisiblePadding); itemLeft += itemWidth + _st.itemSkip; widthLeft -= itemWidth + _st.itemSkip; } auto fieldMinWidth = _st.fieldMinWidth + _st.fieldCancelSkip; Assert(fieldMinWidth <= newWidth); if (fieldMinWidth > widthLeft) { _fieldLeft = 0; _fieldTop = itemTop + _st.item.height + _st.itemSkip; } else { _fieldLeft = itemLeft + (_items.empty() ? _st.fieldIconSkip : 0); _fieldTop = itemTop; } _fieldWidth = newWidth - _fieldLeft; } void MultiSelect::Inner::updateItemsGeometry() { auto newHeight = resizeGetHeight(width()); if (newHeight == _newHeight) return; _newHeight = newHeight; _height.start([this] { updateHeightStep(); }, height(), _newHeight, _st.item.duration); } void MultiSelect::Inner::updateHeightStep() { auto newHeight = qRound(_height.current(_newHeight)); if (auto heightDelta = newHeight - height()) { resize(width(), newHeight); if (_resizedCallback) { _resizedCallback(heightDelta); } update(); } } void MultiSelect::Inner::finishHeightAnimation() { _height.finish(); updateHeightStep(); } void MultiSelect::Inner::setItemText(uint64 itemId, const QString &text) { for_const (auto &item, _items) { if (item->id() == itemId) { item->setText(text); updateItemsGeometry(); return; } } } void MultiSelect::Inner::setItemRemovedCallback(base::lambda<void(uint64 itemId)> callback) { _itemRemovedCallback = std::move(callback); } void MultiSelect::Inner::setResizedCallback(base::lambda<void(int heightDelta)> callback) { _resizedCallback = std::move(callback); } void MultiSelect::Inner::removeItem(uint64 itemId) { auto found = false; for (auto i = 0, count = int(_items.size()); i != count; ++i) { auto &item = _items[i]; if (item->id() == itemId) { found = true; clearSelection(); item->hideAnimated(); _idsMap.erase(item->id()); auto inserted = _removingItems.insert(std::move(item)); _items.erase(_items.begin() + i); if (_active == i) { _active = -1; } else if (_active > i) { --_active; } updateItemsGeometry(); if (_items.empty()) { updateHasAnyItems(false); } auto point = QCursor::pos(); if (auto parent = parentWidget()) { if (parent->rect().contains(parent->mapFromGlobal(point))) { updateSelection(mapFromGlobal(point)); } } break; } } if (found && _itemRemovedCallback) { _itemRemovedCallback(itemId); } setInnerFocus(); } int MultiSelect::Inner::getItemsCount() const { return _items.size(); } QVector<uint64> MultiSelect::Inner::getItems() const { auto result = QVector<uint64>(); result.reserve(_items.size()); for_const (auto &item, _items) { result.push_back(item->id()); } return result; } bool MultiSelect::Inner::hasItem(uint64 itemId) const { return _idsMap.find(itemId) != _idsMap.cend(); } MultiSelect::Inner::~Inner() = default; } // namespace Ui ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/widgets/multi_select.h��������������������������������������0000664�0000000�0000000�00000015051�13262451251�0024352�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "styles/style_widgets.h" #include "ui/rp_widget.h" namespace Ui { class InputField; class CrossButton; class ScrollArea; class MultiSelect : public RpWidget { public: MultiSelect(QWidget *parent, const style::MultiSelect &st, base::lambda<QString()> placeholderFactory = base::lambda<QString()>()); QString getQuery() const; void setInnerFocus(); void clearQuery(); void setQueryChangedCallback(base::lambda<void(const QString &query)> callback); void setSubmittedCallback(base::lambda<void(bool ctrlShiftEnter)> callback); void setResizedCallback(base::lambda<void()> callback); enum class AddItemWay { Default, SkipAnimation, }; using PaintRoundImage = base::lambda<void(Painter &p, int x, int y, int outerWidth, int size)>; void addItem(uint64 itemId, const QString &text, style::color color, PaintRoundImage paintRoundImage, AddItemWay way = AddItemWay::Default); void addItemInBunch(uint64 itemId, const QString &text, style::color color, PaintRoundImage paintRoundImage); void finishItemsBunch(); void setItemText(uint64 itemId, const QString &text); void setItemRemovedCallback(base::lambda<void(uint64 itemId)> callback); void removeItem(uint64 itemId); int getItemsCount() const; QVector<uint64> getItems() const; bool hasItem(uint64 itemId) const; class Item; protected: int resizeGetHeight(int newWidth) override; bool eventFilter(QObject *o, QEvent *e) override; private: void scrollTo(int activeTop, int activeBottom); const style::MultiSelect &_st; object_ptr<Ui::ScrollArea> _scroll; class Inner; QPointer<Inner> _inner; base::lambda<void()> _resizedCallback; base::lambda<void(const QString &query)> _queryChangedCallback; }; // This class is hold in header because it requires Qt preprocessing. class MultiSelect::Inner : public TWidget { Q_OBJECT public: using ScrollCallback = base::lambda<void(int activeTop, int activeBottom)>; Inner(QWidget *parent, const style::MultiSelect &st, base::lambda<QString()> placeholderFactory, ScrollCallback callback); QString getQuery() const; bool setInnerFocus(); void clearQuery(); void setQueryChangedCallback(base::lambda<void(const QString &query)> callback); void setSubmittedCallback(base::lambda<void(bool ctrlShiftEnter)> callback); void addItemInBunch(std::unique_ptr<Item> item); void finishItemsBunch(AddItemWay way); void setItemText(uint64 itemId, const QString &text); void setItemRemovedCallback(base::lambda<void(uint64 itemId)> callback); void removeItem(uint64 itemId); int getItemsCount() const; QVector<uint64> getItems() const; bool hasItem(uint64 itemId) const; void setResizedCallback(base::lambda<void(int heightDelta)> callback); ~Inner(); protected: int resizeGetHeight(int newWidth) override; void paintEvent(QPaintEvent *e) override; void leaveEventHook(QEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void keyPressEvent(QKeyEvent *e) override; private slots: void onQueryChanged(); void onSubmitted(bool ctrlShiftEnter) { if (_submittedCallback) { _submittedCallback(ctrlShiftEnter); } } void onFieldFocused(); private: void computeItemsGeometry(int newWidth); void updateItemsGeometry(); void updateFieldGeometry(); void updateHasAnyItems(bool hasAnyItems); void updateSelection(QPoint mousePosition); void clearSelection() { updateSelection(QPoint(-1, -1)); } void updateCursor(); void updateHeightStep(); void finishHeightAnimation(); enum class ChangeActiveWay { Default, SkipSetFocus, }; void setActiveItem(int active, ChangeActiveWay skipSetFocus = ChangeActiveWay::Default); void setActiveItemPrevious(); void setActiveItemNext(); QMargins itemPaintMargins() const; const style::MultiSelect &_st; Animation _iconOpacity; ScrollCallback _scrollCallback; std::set<uint64> _idsMap; std::vector<std::unique_ptr<Item>> _items; std::set<std::unique_ptr<Item>> _removingItems; int _selected = -1; int _active = -1; bool _overDelete = false; int _fieldLeft = 0; int _fieldTop = 0; int _fieldWidth = 0; object_ptr<Ui::InputField> _field; object_ptr<Ui::CrossButton> _cancel; int _newHeight = 0; Animation _height; base::lambda<void(const QString &query)> _queryChangedCallback; base::lambda<void(bool ctrlShiftEnter)> _submittedCallback; base::lambda<void(uint64 itemId)> _itemRemovedCallback; base::lambda<void(int heightDelta)> _resizedCallback; }; class MultiSelect::Item { public: Item(const style::MultiSelectItem &st, uint64 id, const QString &text, style::color color, PaintRoundImage &&paintRoundImage); uint64 id() const { return _id; } int getWidth() const { return _width; } QRect rect() const { return QRect(_x, _y, _width, _st.height); } bool isOverDelete() const { return _overDelete; } void setActive(bool active) { _active = active; } void setPosition(int x, int y, int outerWidth, int maxVisiblePadding); QRect paintArea(int outerWidth) const; void setUpdateCallback(base::lambda<void()> updateCallback) { _updateCallback = updateCallback; } void setText(const QString &text); void paint(Painter &p, int outerWidth, TimeMs ms); void mouseMoveEvent(QPoint point); void leaveEvent(); void showAnimated() { setVisibleAnimated(true); } void hideAnimated() { setVisibleAnimated(false); } bool hideFinished() const { return (_hiding && !_visibility.animating()); } private: void setOver(bool over); void paintOnce(Painter &p, int x, int y, int outerWidth, TimeMs ms); void paintDeleteButton(Painter &p, int x, int y, int outerWidth, float64 overOpacity); bool paintCached(Painter &p, int x, int y, int outerWidth); void prepareCache(); void setVisibleAnimated(bool visible); const style::MultiSelectItem &_st; uint64 _id; struct SlideAnimation { SlideAnimation(base::lambda<void()> updateCallback, int fromX, int toX, int y, float64 duration) : fromX(fromX) , toX(toX) , y(y) { x.start(updateCallback, fromX, toX, duration); } Animation x; int fromX, toX; int y; }; std::vector<SlideAnimation> _copies; int _x = -1; int _y = -1; int _width = 0; Text _text; style::color _color; bool _over = false; QPixmap _cache; Animation _visibility; Animation _overOpacity; bool _overDelete = false; bool _active = false; PaintRoundImage _paintRoundImage; base::lambda<void()> _updateCallback; bool _hiding = false; }; } // namespace Ui ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/widgets/popup_menu.cpp��������������������������������������0000664�0000000�0000000�00000031365�13262451251�0024411�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/widgets/popup_menu.h" #include "ui/widgets/shadow.h" #include "platform/platform_specific.h" #include "application.h" #include "mainwindow.h" #include "messenger.h" #include "lang/lang_keys.h" namespace Ui { PopupMenu::PopupMenu(QWidget*, const style::PopupMenu &st) : TWidget(nullptr) , _st(st) , _menu(this, _st.menu) { init(); } PopupMenu::PopupMenu(QWidget*, QMenu *menu, const style::PopupMenu &st) : TWidget(nullptr) , _st(st) , _menu(this, menu, _st.menu) { init(); for (auto action : actions()) { if (auto submenu = action->menu()) { auto it = _submenus.insert(action, new PopupMenu(nullptr, submenu, st)); it.value()->deleteOnHide(false); } } } void PopupMenu::init() { subscribe(Messenger::Instance().passcodedChanged(), [this] { if (App::passcoded()) { hideMenu(true); } }); _menu->setResizedCallback([this] { handleMenuResize(); }); _menu->setActivatedCallback([this](QAction *action, int actionTop, TriggeredSource source) { handleActivated(action, actionTop, source); }); _menu->setTriggeredCallback([this](QAction *action, int actionTop, TriggeredSource source) { handleTriggered(action, actionTop, source); }); _menu->setKeyPressDelegate([this](int key) { return handleKeyPress(key); }); _menu->setMouseMoveDelegate([this](QPoint globalPosition) { handleMouseMove(globalPosition); }); _menu->setMousePressDelegate([this](QPoint globalPosition) { handleMousePress(globalPosition); }); _menu->setMouseReleaseDelegate([this](QPoint globalPosition) { handleMouseRelease(globalPosition); }); handleCompositingUpdate(); setWindowFlags(Qt::WindowFlags(Qt::FramelessWindowHint) | Qt::BypassWindowManagerHint | Qt::Popup | Qt::NoDropShadowWindowHint); setMouseTracking(true); hide(); setAttribute(Qt::WA_NoSystemBackground, true); setAttribute(Qt::WA_TranslucentBackground, true); } void PopupMenu::handleCompositingUpdate() { _padding = _useTransparency ? _st.shadow.extend : style::margins(st::lineWidth, st::lineWidth, st::lineWidth, st::lineWidth); _menu->moveToLeft(_padding.left() + _st.scrollPadding.left(), _padding.top() + _st.scrollPadding.top()); handleMenuResize(); } void PopupMenu::handleMenuResize() { auto newWidth = _padding.left() + _st.scrollPadding.left() + _menu->width() + _st.scrollPadding.right() + _padding.right(); auto newHeight = _padding.top() + _st.scrollPadding.top() + _menu->height() + _st.scrollPadding.bottom() + _padding.bottom(); resize(newWidth, newHeight); _inner = rect().marginsRemoved(_padding); } QAction *PopupMenu::addAction(const QString &text, const QObject *receiver, const char* member, const style::icon *icon, const style::icon *iconOver) { return _menu->addAction(text, receiver, member, icon, iconOver); } QAction *PopupMenu::addAction(const QString &text, base::lambda<void()> callback, const style::icon *icon, const style::icon *iconOver) { return _menu->addAction(text, std::move(callback), icon, iconOver); } QAction *PopupMenu::addSeparator() { return _menu->addSeparator(); } void PopupMenu::clearActions() { for (auto submenu : base::take(_submenus)) { delete submenu; } return _menu->clearActions(); } PopupMenu::Actions &PopupMenu::actions() { return _menu->actions(); } void PopupMenu::paintEvent(QPaintEvent *e) { Painter p(this); if (_useTransparency) { Platform::StartTranslucentPaint(p, e); } auto ms = getms(); if (_a_show.animating(ms)) { if (auto opacity = _a_opacity.current(ms, _hiding ? 0. : 1.)) { _showAnimation->paintFrame(p, 0, 0, width(), _a_show.current(1.), opacity); } } else if (_a_opacity.animating(ms)) { p.setOpacity(_a_opacity.current(0.)); p.drawPixmap(0, 0, _cache); } else if (_hiding || isHidden()) { hideFinished(); } else if (_showAnimation) { _showAnimation->paintFrame(p, 0, 0, width(), 1., 1.); _showAnimation.reset(); showChildren(); } else { paintBg(p); } } void PopupMenu::paintBg(Painter &p) { if (_useTransparency) { Shadow::paint(p, _inner, width(), _st.shadow); App::roundRect(p, _inner, _st.menu.itemBg, ImageRoundRadius::Small); } else { p.fillRect(0, 0, width() - _padding.right(), _padding.top(), _st.shadow.fallback); p.fillRect(width() - _padding.right(), 0, _padding.right(), height() - _padding.bottom(), _st.shadow.fallback); p.fillRect(_padding.left(), height() - _padding.bottom(), width() - _padding.left(), _padding.bottom(), _st.shadow.fallback); p.fillRect(0, _padding.top(), _padding.left(), height() - _padding.top(), _st.shadow.fallback); p.fillRect(_inner, _st.menu.itemBg); } } void PopupMenu::handleActivated(QAction *action, int actionTop, TriggeredSource source) { if (source == TriggeredSource::Mouse) { if (!popupSubmenuFromAction(action, actionTop, source)) { if (auto currentSubmenu = base::take(_activeSubmenu)) { currentSubmenu->hideMenu(true); } } } } void PopupMenu::handleTriggered(QAction *action, int actionTop, TriggeredSource source) { if (!popupSubmenuFromAction(action, actionTop, source)) { _triggering = true; hideMenu(); emit action->trigger(); _triggering = false; if (_deleteLater) { _deleteLater = false; deleteLater(); } } } bool PopupMenu::popupSubmenuFromAction(QAction *action, int actionTop, TriggeredSource source) { if (auto submenu = _submenus.value(action)) { if (_activeSubmenu == submenu) { submenu->hideMenu(true); } else { popupSubmenu(submenu, actionTop, source); } return true; } return false; } void PopupMenu::popupSubmenu(SubmenuPointer submenu, int actionTop, TriggeredSource source) { if (auto currentSubmenu = base::take(_activeSubmenu)) { currentSubmenu->hideMenu(true); } if (submenu) { QPoint p(_inner.x() + (rtl() ? _padding.right() : _inner.width() - _padding.left()), _inner.y() + actionTop); _activeSubmenu = submenu; _activeSubmenu->showMenu(geometry().topLeft() + p, this, source); _menu->setChildShown(true); } else { _menu->setChildShown(false); } } void PopupMenu::forwardKeyPress(int key) { if (!handleKeyPress(key)) { _menu->handleKeyPress(key); } } bool PopupMenu::handleKeyPress(int key) { if (_activeSubmenu) { _activeSubmenu->handleKeyPress(key); return true; } else if (key == Qt::Key_Escape) { hideMenu(_parent ? true : false); return true; } else if (key == (rtl() ? Qt::Key_Right : Qt::Key_Left)) { if (_parent) { hideMenu(true); return true; } } return false; } void PopupMenu::handleMouseMove(QPoint globalPosition) { if (_parent) { _parent->forwardMouseMove(globalPosition); } } void PopupMenu::handleMousePress(QPoint globalPosition) { if (_parent) { _parent->forwardMousePress(globalPosition); } else { hideMenu(); } } void PopupMenu::handleMouseRelease(QPoint globalPosition) { if (_parent) { _parent->forwardMouseRelease(globalPosition); } else { hideMenu(); } } void PopupMenu::focusOutEvent(QFocusEvent *e) { hideMenu(); } void PopupMenu::hideEvent(QHideEvent *e) { if (_deleteOnHide) { if (_triggering) { _deleteLater = true; } else { deleteLater(); } } } void PopupMenu::hideMenu(bool fast) { if (isHidden()) return; if (_parent && !_a_opacity.animating()) { _parent->childHiding(this); } if (fast) { hideFast(); } else { hideAnimated(); if (_parent) { _parent->hideMenu(); } } if (_activeSubmenu) { _activeSubmenu->hideMenu(fast); } } void PopupMenu::childHiding(PopupMenu *child) { if (_activeSubmenu && _activeSubmenu == child) { _activeSubmenu = SubmenuPointer(); } } void PopupMenu::setOrigin(PanelAnimation::Origin origin) { _origin = origin; } void PopupMenu::showAnimated(PanelAnimation::Origin origin) { setOrigin(origin); showStarted(); } void PopupMenu::hideAnimated() { if (isHidden()) return; if (_hiding) return; startOpacityAnimation(true); } void PopupMenu::hideFast() { if (isHidden()) return; _hiding = false; _a_opacity.finish(); hideFinished(); } void PopupMenu::hideFinished() { _a_show.finish(); _cache = QPixmap(); if (!isHidden()) { hide(); } } void PopupMenu::prepareCache() { if (_a_opacity.animating()) return; auto showAnimation = base::take(_a_show); auto showAnimationData = base::take(_showAnimation); showChildren(); _cache = GrabWidget(this); _showAnimation = base::take(showAnimationData); _a_show = base::take(showAnimation); } void PopupMenu::startOpacityAnimation(bool hiding) { _hiding = false; if (!_useTransparency) { _a_opacity.finish(); if (hiding) { hideFinished(); } else { update(); } return; } prepareCache(); _hiding = hiding; hideChildren(); _a_opacity.start([this] { opacityAnimationCallback(); }, _hiding ? 1. : 0., _hiding ? 0. : 1., _st.duration); } void PopupMenu::showStarted() { if (isHidden()) { show(); startShowAnimation(); return; } else if (!_hiding) { return; } startOpacityAnimation(false); } void PopupMenu::startShowAnimation() { if (!_useTransparency) { _a_show.finish(); update(); return; } if (!_a_show.animating()) { auto opacityAnimation = base::take(_a_opacity); showChildren(); auto cache = grabForPanelAnimation(); _a_opacity = base::take(opacityAnimation); _showAnimation = std::make_unique<PanelAnimation>(_st.animation, _origin); _showAnimation->setFinalImage(std::move(cache), QRect(_inner.topLeft() * cIntRetinaFactor(), _inner.size() * cIntRetinaFactor())); if (_useTransparency) { auto corners = App::cornersMask(ImageRoundRadius::Small); _showAnimation->setCornerMasks(corners[0], corners[1], corners[2], corners[3]); } else { _showAnimation->setSkipShadow(true); } _showAnimation->start(); } hideChildren(); _a_show.start([this] { showAnimationCallback(); }, 0., 1., _st.showDuration); } void PopupMenu::opacityAnimationCallback() { update(); if (!_a_opacity.animating()) { if (_hiding) { _hiding = false; hideFinished(); } else { showChildren(); } } } void PopupMenu::showAnimationCallback() { update(); } QImage PopupMenu::grabForPanelAnimation() { SendPendingMoveResizeEvents(this); auto result = QImage(size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); result.setDevicePixelRatio(cRetinaFactor()); result.fill(Qt::transparent); { Painter p(&result); if (_useTransparency) { App::roundRect(p, _inner, _st.menu.itemBg, ImageRoundRadius::Small); } else { p.fillRect(_inner, _st.menu.itemBg); } for (auto child : children()) { if (auto widget = qobject_cast<QWidget*>(child)) { widget->render(&p, widget->pos(), widget->rect(), QWidget::DrawChildren | QWidget::IgnoreMask); } } } return result; } void PopupMenu::deleteOnHide(bool del) { _deleteOnHide = del; } void PopupMenu::popup(const QPoint &p) { showMenu(p, nullptr, TriggeredSource::Mouse); } void PopupMenu::showMenu(const QPoint &p, PopupMenu *parent, TriggeredSource source) { _parent = parent; auto origin = PanelAnimation::Origin::TopLeft; auto w = p - QPoint(0, _padding.top()); auto r = Sandbox::screenGeometry(p); _useTransparency = Platform::TranslucentWindowsSupported(p); setAttribute(Qt::WA_OpaquePaintEvent, !_useTransparency); handleCompositingUpdate(); if (rtl()) { if (w.x() - width() < r.x() - _padding.left()) { if (_parent && w.x() + _parent->width() - _padding.left() - _padding.right() + width() - _padding.right() <= r.x() + r.width()) { w.setX(w.x() + _parent->width() - _padding.left() - _padding.right()); } else { w.setX(r.x() - _padding.left()); } } else { w.setX(w.x() - width()); } } else { if (w.x() + width() - _padding.right() > r.x() + r.width()) { if (_parent && w.x() - _parent->width() + _padding.left() + _padding.right() - width() + _padding.right() >= r.x() - _padding.left()) { w.setX(w.x() + _padding.left() + _padding.right() - _parent->width() - width() + _padding.left() + _padding.right()); } else { w.setX(p.x() - width() + _padding.right()); } origin = PanelAnimation::Origin::TopRight; } } if (w.y() + height() - _padding.bottom() > r.y() + r.height()) { if (_parent) { w.setY(r.y() + r.height() - height() + _padding.bottom()); } else { w.setY(p.y() - height() + _padding.bottom()); origin = (origin == PanelAnimation::Origin::TopRight) ? PanelAnimation::Origin::BottomRight : PanelAnimation::Origin::BottomLeft; } } if (w.x() < r.x()) { w.setX(r.x()); } if (w.y() < r.y()) { w.setY(r.y()); } move(w); setOrigin(origin); _menu->setShowSource(source); startShowAnimation(); psUpdateOverlayed(this); show(); psShowOverAll(this); windowHandle()->requestActivate(); activateWindow(); } PopupMenu::~PopupMenu() { for (auto submenu : base::take(_submenus)) { delete submenu; } if (auto w = App::wnd()) { w->reActivateWindow(); } if (_destroyedCallback) { _destroyedCallback(); } } } // namespace Ui ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/widgets/popup_menu.h����������������������������������������0000664�0000000�0000000�00000007274�13262451251�0024060�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "styles/style_widgets.h" #include "ui/widgets/menu.h" #include "ui/effects/panel_animation.h" namespace Ui { class PopupMenu : public TWidget, private base::Subscriber { public: PopupMenu(QWidget*, const style::PopupMenu &st = st::defaultPopupMenu); PopupMenu(QWidget*, QMenu *menu, const style::PopupMenu &st = st::defaultPopupMenu); QAction *addAction(const QString &text, const QObject *receiver, const char* member, const style::icon *icon = nullptr, const style::icon *iconOver = nullptr); QAction *addAction(const QString &text, base::lambda<void()> callback, const style::icon *icon = nullptr, const style::icon *iconOver = nullptr); QAction *addSeparator(); void clearActions(); using Actions = Ui::Menu::Actions; Actions &actions(); void deleteOnHide(bool del); void popup(const QPoint &p); void hideMenu(bool fast = false); void setDestroyedCallback(base::lambda<void()> callback) { _destroyedCallback = std::move(callback); } ~PopupMenu(); protected: void paintEvent(QPaintEvent *e) override; void focusOutEvent(QFocusEvent *e) override; void hideEvent(QHideEvent *e) override; void keyPressEvent(QKeyEvent *e) override { forwardKeyPress(e->key()); } void mouseMoveEvent(QMouseEvent *e) override { forwardMouseMove(e->globalPos()); } void mousePressEvent(QMouseEvent *e) override { forwardMousePress(e->globalPos()); } private: void paintBg(Painter &p); void hideFast(); void setOrigin(PanelAnimation::Origin origin); void showAnimated(PanelAnimation::Origin origin); void hideAnimated(); QImage grabForPanelAnimation(); void startShowAnimation(); void startOpacityAnimation(bool hiding); void prepareCache(); void childHiding(PopupMenu *child); void showAnimationCallback(); void opacityAnimationCallback(); void init(); void hideFinished(); void showStarted(); using TriggeredSource = Ui::Menu::TriggeredSource; void handleCompositingUpdate(); void handleMenuResize(); void handleActivated(QAction *action, int actionTop, TriggeredSource source); void handleTriggered(QAction *action, int actionTop, TriggeredSource source); void forwardKeyPress(int key); bool handleKeyPress(int key); void forwardMouseMove(QPoint globalPosition) { _menu->handleMouseMove(globalPosition); } void handleMouseMove(QPoint globalPosition); void forwardMousePress(QPoint globalPosition) { _menu->handleMousePress(globalPosition); } void handleMousePress(QPoint globalPosition); void forwardMouseRelease(QPoint globalPosition) { _menu->handleMouseRelease(globalPosition); } void handleMouseRelease(QPoint globalPosition); using SubmenuPointer = QPointer<PopupMenu>; bool popupSubmenuFromAction(QAction *action, int actionTop, TriggeredSource source); void popupSubmenu(SubmenuPointer submenu, int actionTop, TriggeredSource source); void showMenu(const QPoint &p, PopupMenu *parent, TriggeredSource source); const style::PopupMenu &_st; object_ptr<Ui::Menu> _menu; using Submenus = QMap<QAction*, SubmenuPointer>; Submenus _submenus; PopupMenu *_parent = nullptr; QRect _inner; style::margins _padding; SubmenuPointer _activeSubmenu; PanelAnimation::Origin _origin = PanelAnimation::Origin::TopLeft; std::unique_ptr<PanelAnimation> _showAnimation; Animation _a_show; bool _useTransparency = true; bool _hiding = false; QPixmap _cache; Animation _a_opacity; bool _deleteOnHide = true; bool _triggering = false; bool _deleteLater = false; base::lambda<void()> _destroyedCallback; }; } // namespace Ui ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/widgets/scroll_area.cpp�������������������������������������0000664�0000000�0000000�00000060110�13262451251�0024476�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/widgets/scroll_area.h" namespace Ui { // flick scroll taken from http://qt-project.org/doc/qt-4.8/demos-embedded-anomaly-src-flickcharm-cpp.html ScrollShadow::ScrollShadow(ScrollArea *parent, const style::ScrollArea *st) : QWidget(parent), _st(st) { setVisible(false); Assert(_st != nullptr); Assert(_st->shColor.v() != nullptr); } void ScrollShadow::paintEvent(QPaintEvent *e) { Painter p(this); p.fillRect(rect(), _st->shColor); } void ScrollShadow::changeVisibility(bool shown) { setVisible(shown); } ScrollBar::ScrollBar(ScrollArea *parent, bool vert, const style::ScrollArea *st) : TWidget(parent) , _st(st) , _vertical(vert) , _hiding(_st->hiding != 0) , _connected(vert ? parent->verticalScrollBar() : parent->horizontalScrollBar()) , _scrollMax(_connected->maximum()) { recountSize(); _hideTimer.setSingleShot(true); connect(&_hideTimer, SIGNAL(timeout()), this, SLOT(onHideTimer())); connect(_connected, SIGNAL(valueChanged(int)), this, SLOT(onValueChanged())); connect(_connected, SIGNAL(rangeChanged(int, int)), this, SLOT(onRangeChanged())); updateBar(); } void ScrollBar::recountSize() { setGeometry(_vertical ? QRect(rtl() ? 0 : (area()->width() - _st->width), _st->deltat, _st->width, area()->height() - _st->deltat - _st->deltab) : QRect(_st->deltat, area()->height() - _st->width, area()->width() - _st->deltat - _st->deltab, _st->width)); } void ScrollBar::onValueChanged() { area()->onScrolled(); updateBar(); } void ScrollBar::onRangeChanged() { area()->onInnerResized(); updateBar(); } void ScrollBar::updateBar(bool force) { QRect newBar; if (_connected->maximum() != _scrollMax) { int32 oldMax = _scrollMax, newMax = _connected->maximum(); _scrollMax = newMax; area()->rangeChanged(oldMax, newMax, _vertical); } if (_vertical) { int sh = area()->scrollHeight(), rh = height(), h = sh ? int32((rh * int64(area()->height())) / sh) : 0; if (h >= rh || !area()->scrollTopMax() || rh < _st->minHeight) { if (!isHidden()) hide(); bool newTopSh = (_st->topsh < 0), newBottomSh = (_st->bottomsh < 0); if (newTopSh != _topSh || force) emit topShadowVisibility(_topSh = newTopSh); if (newBottomSh != _bottomSh || force) emit bottomShadowVisibility(_bottomSh = newBottomSh); return; } if (h <= _st->minHeight) h = _st->minHeight; int stm = area()->scrollTopMax(), y = stm ? int32(((rh - h) * int64(area()->scrollTop())) / stm) : 0; if (y > rh - h) y = rh - h; newBar = QRect(_st->deltax, y, width() - 2 * _st->deltax, h); } else { int sw = area()->scrollWidth(), rw = width(), w = sw ? int32((rw * int64(area()->width())) / sw) : 0; if (w >= rw || !area()->scrollLeftMax() || rw < _st->minHeight) { if (!isHidden()) hide(); return; } if (w <= _st->minHeight) w = _st->minHeight; int slm = area()->scrollLeftMax(), x = slm ? int32(((rw - w) * int64(area()->scrollLeft())) / slm) : 0; if (x > rw - w) x = rw - w; newBar = QRect(x, _st->deltax, w, height() - 2 * _st->deltax); } if (newBar != _bar) { _bar = newBar; update(); } if (_vertical) { bool newTopSh = (_st->topsh < 0) || (area()->scrollTop() > _st->topsh), newBottomSh = (_st->bottomsh < 0) || (area()->scrollTop() < area()->scrollTopMax() - _st->bottomsh); if (newTopSh != _topSh || force) emit topShadowVisibility(_topSh = newTopSh); if (newBottomSh != _bottomSh || force) emit bottomShadowVisibility(_bottomSh = newBottomSh); } if (isHidden()) show(); } void ScrollBar::onHideTimer() { if (!_hiding) { _hiding = true; _a_opacity.start([this] { update(); }, 1., 0., _st->duration); } } ScrollArea *ScrollBar::area() { return static_cast<ScrollArea*>(parentWidget()); } void ScrollBar::setOver(bool over) { if (_over != over) { auto wasOver = (_over || _moving); _over = over; auto nowOver = (_over || _moving); if (wasOver != nowOver) { _a_over.start([this] { update(); }, nowOver ? 0. : 1., nowOver ? 1. : 0., _st->duration); } if (nowOver && _hiding) { _hiding = false; _a_opacity.start([this] { update(); }, 0., 1., _st->duration); } } } void ScrollBar::setOverBar(bool overbar) { if (_overbar != overbar) { auto wasBarOver = (_overbar || _moving); _overbar = overbar; auto nowBarOver = (_overbar || _moving); if (wasBarOver != nowBarOver) { _a_barOver.start([this] { update(); }, nowBarOver ? 0. : 1., nowBarOver ? 1. : 0., _st->duration); } } } void ScrollBar::setMoving(bool moving) { if (_moving != moving) { auto wasOver = (_over || _moving); auto wasBarOver = (_overbar || _moving); _moving = moving; auto nowBarOver = (_overbar || _moving); if (wasBarOver != nowBarOver) { _a_barOver.start([this] { update(); }, nowBarOver ? 0. : 1., nowBarOver ? 1. : 0., _st->duration); } auto nowOver = (_over || _moving); if (wasOver != nowOver) { _a_over.start([this] { update(); }, nowOver ? 0. : 1., nowOver ? 1. : 0., _st->duration); } if (!nowOver && _st->hiding && !_hiding) { _hideTimer.start(_hideIn); } } } void ScrollBar::paintEvent(QPaintEvent *e) { if (!_bar.width() && !_bar.height()) { hide(); return; } auto ms = getms(); auto opacity = _a_opacity.current(ms, _hiding ? 0. : 1.); if (opacity == 0.) return; Painter p(this); auto deltal = _vertical ? _st->deltax : 0, deltar = _vertical ? _st->deltax : 0; auto deltat = _vertical ? 0 : _st->deltax, deltab = _vertical ? 0 : _st->deltax; p.setPen(Qt::NoPen); auto bg = anim::color(_st->bg, _st->bgOver, _a_over.current(ms, (_over || _moving) ? 1. : 0.)); bg.setAlpha(anim::interpolate(0, bg.alpha(), opacity)); auto bar = anim::color(_st->barBg, _st->barBgOver, _a_barOver.current(ms, (_overbar || _moving) ? 1. : 0.)); bar.setAlpha(anim::interpolate(0, bar.alpha(), opacity)); if (_st->round) { PainterHighQualityEnabler hq(p); p.setBrush(bg); p.drawRoundedRect(QRect(deltal, deltat, width() - deltal - deltar, height() - deltat - deltab), _st->round, _st->round); p.setBrush(bar); p.drawRoundedRect(_bar, _st->round, _st->round); } else { p.fillRect(QRect(deltal, deltat, width() - deltal - deltar, height() - deltat - deltab), bg); p.fillRect(_bar, bar); } } void ScrollBar::hideTimeout(TimeMs dt) { if (_hiding && dt > 0) { _hiding = false; _a_opacity.start([this] { update(); }, 0., 1., _st->duration); } _hideIn = dt; if (!_moving) { _hideTimer.start(_hideIn); } } void ScrollBar::enterEventHook(QEvent *e) { _hideTimer.stop(); setMouseTracking(true); setOver(true); } void ScrollBar::leaveEventHook(QEvent *e) { if (!_moving) { setMouseTracking(false); } setOver(false); setOverBar(false); if (_st->hiding && !_hiding) { _hideTimer.start(_hideIn); } } void ScrollBar::mouseMoveEvent(QMouseEvent *e) { setOverBar(_bar.contains(e->pos())); if (_moving) { int delta = 0, barDelta = _vertical ? (area()->height() - _bar.height()) : (area()->width() - _bar.width()); if (barDelta > 0) { QPoint d = (e->globalPos() - _dragStart); delta = int32((_vertical ? (d.y() * int64(area()->scrollTopMax())) : (d.x() * int64(area()->scrollLeftMax()))) / barDelta); } _connected->setValue(_startFrom + delta); } } void ScrollBar::mousePressEvent(QMouseEvent *e) { if (!width() || !height()) return; _dragStart = e->globalPos(); setMoving(true); if (_overbar) { _startFrom = _connected->value(); } else { int32 val = _vertical ? e->pos().y() : e->pos().x(), div = _vertical ? height() : width(); val = (val <= _st->deltat) ? 0 : (val - _st->deltat); div = (div <= _st->deltat + _st->deltab) ? 1 : (div - _st->deltat - _st->deltab); _startFrom = _vertical ? int32((val * int64(area()->scrollTopMax())) / div) : ((val * int64(area()->scrollLeftMax())) / div); _connected->setValue(_startFrom); setOverBar(true); } area()->setMovingByScrollBar(true); emit area()->scrollStarted(); } void ScrollBar::mouseReleaseEvent(QMouseEvent *e) { if (_moving) { setMoving(false); area()->setMovingByScrollBar(false); emit area()->scrollFinished(); } if (!_over) { setMouseTracking(false); } } void ScrollBar::resizeEvent(QResizeEvent *e) { updateBar(); } void SplittedWidget::paintEvent(QPaintEvent *e) { Painter p(this); if (rtl()) { p.translate(-otherWidth(), 0); paintRegion(p, e->region().translated(otherWidth(), 0), false); } else { paintRegion(p, e->region(), false); } } void SplittedWidget::update(const QRect &r) { if (rtl()) { TWidget::update(r.translated(-otherWidth(), 0).intersected(rect())); emit updateOther(r); } else { TWidget::update(r.intersected(rect())); emit updateOther(r.translated(-width(), 0)); } } void SplittedWidget::update(const QRegion &r) { if (rtl()) { TWidget::update(r.translated(-otherWidth(), 0).intersected(rect())); emit updateOther(r); } else { TWidget::update(r.intersected(rect())); emit updateOther(r.translated(-width(), 0)); } } void SplittedWidgetOther::paintEvent(QPaintEvent *e) { Painter p(this); auto s = static_cast<SplittedWidget*>(static_cast<ScrollArea*>(parentWidget())->widget()); if (rtl()) { s->paintRegion(p, e->region(), true); } else { p.translate(-s->width(), 0); s->paintRegion(p, e->region().translated(s->width(), 0), true); } } ScrollArea::ScrollArea(QWidget *parent, const style::ScrollArea &st, bool handleTouch) : RpWidgetWrap<QScrollArea>(parent) , _st(st) , _horizontalBar(this, false, &_st) , _verticalBar(this, true, &_st) , _topShadow(this, &_st) , _bottomShadow(this, &_st) , _touchEnabled(handleTouch) { setLayoutDirection(cLangDir()); setFocusPolicy(Qt::NoFocus); connect(_verticalBar, SIGNAL(topShadowVisibility(bool)), _topShadow, SLOT(changeVisibility(bool))); connect(_verticalBar, SIGNAL(bottomShadowVisibility(bool)), _bottomShadow, SLOT(changeVisibility(bool))); _verticalBar->updateBar(true); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setFrameStyle(QFrame::NoFrame | QFrame::Plain); viewport()->setAutoFillBackground(false); _horizontalValue = horizontalScrollBar()->value(); _verticalValue = verticalScrollBar()->value(); if (_touchEnabled) { viewport()->setAttribute(Qt::WA_AcceptTouchEvents); _touchTimer.setSingleShot(true); connect(&_touchTimer, SIGNAL(timeout()), this, SLOT(onTouchTimer())); connect(&_touchScrollTimer, SIGNAL(timeout()), this, SLOT(onTouchScrollTimer())); } } void ScrollArea::touchDeaccelerate(int32 elapsed) { int32 x = _touchSpeed.x(); int32 y = _touchSpeed.y(); _touchSpeed.setX((x == 0) ? x : (x > 0) ? qMax(0, x - elapsed) : qMin(0, x + elapsed)); _touchSpeed.setY((y == 0) ? y : (y > 0) ? qMax(0, y - elapsed) : qMin(0, y + elapsed)); } void ScrollArea::onScrolled() { if (const auto inner = widget()) { SendPendingMoveResizeEvents(inner); } bool em = false; int horizontalValue = horizontalScrollBar()->value(); int verticalValue = verticalScrollBar()->value(); if (_horizontalValue != horizontalValue) { if (_disabled) { horizontalScrollBar()->setValue(_horizontalValue); } else { _horizontalValue = horizontalValue; if (_st.hiding) { _horizontalBar->hideTimeout(_st.hiding); } em = true; } } if (_verticalValue != verticalValue) { if (_disabled) { verticalScrollBar()->setValue(_verticalValue); } else { _verticalValue = verticalValue; if (_st.hiding) { _verticalBar->hideTimeout(_st.hiding); } em = true; _scrollTopUpdated.fire_copy(_verticalValue); } } if (em) { emit scrolled(); if (!_movingByScrollBar) { sendSynteticMouseEvent(this, QEvent::MouseMove, Qt::NoButton); } } } void ScrollArea::onInnerResized() { emit innerResized(); } int ScrollArea::scrollWidth() const { QWidget *w(widget()); return w ? qMax(w->width(), width()) : width(); } int ScrollArea::scrollHeight() const { QWidget *w(widget()); return w ? qMax(w->height(), height()) : height(); } int ScrollArea::scrollLeftMax() const { return scrollWidth() - width(); } int ScrollArea::scrollTopMax() const { return scrollHeight() - height(); } int ScrollArea::scrollLeft() const { return _horizontalValue; } int ScrollArea::scrollTop() const { return _verticalValue; } void ScrollArea::onTouchTimer() { _touchRightButton = true; } void ScrollArea::onTouchScrollTimer() { auto nowTime = getms(); if (_touchScrollState == TouchScrollState::Acceleration && _touchWaitingAcceleration && (nowTime - _touchAccelerationTime) > 40) { _touchScrollState = TouchScrollState::Manual; touchResetSpeed(); } else if (_touchScrollState == TouchScrollState::Auto || _touchScrollState == TouchScrollState::Acceleration) { int32 elapsed = int32(nowTime - _touchTime); QPoint delta = _touchSpeed * elapsed / 1000; bool hasScrolled = touchScroll(delta); if (_touchSpeed.isNull() || !hasScrolled) { _touchScrollState = TouchScrollState::Manual; _touchScroll = false; _touchScrollTimer.stop(); } else { _touchTime = nowTime; } touchDeaccelerate(elapsed); } } void ScrollArea::touchUpdateSpeed() { const auto nowTime = getms(); if (_touchPrevPosValid) { const int elapsed = nowTime - _touchSpeedTime; if (elapsed) { const QPoint newPixelDiff = (_touchPos - _touchPrevPos); const QPoint pixelsPerSecond = newPixelDiff * (1000 / elapsed); // fingers are inacurates, we ignore small changes to avoid stopping the autoscroll because // of a small horizontal offset when scrolling vertically const int newSpeedY = (qAbs(pixelsPerSecond.y()) > FingerAccuracyThreshold) ? pixelsPerSecond.y() : 0; const int newSpeedX = (qAbs(pixelsPerSecond.x()) > FingerAccuracyThreshold) ? pixelsPerSecond.x() : 0; if (_touchScrollState == TouchScrollState::Auto) { const int oldSpeedY = _touchSpeed.y(); const int oldSpeedX = _touchSpeed.x(); if ((oldSpeedY <= 0 && newSpeedY <= 0) || ((oldSpeedY >= 0 && newSpeedY >= 0) && (oldSpeedX <= 0 && newSpeedX <= 0)) || (oldSpeedX >= 0 && newSpeedX >= 0)) { _touchSpeed.setY(snap((oldSpeedY + (newSpeedY / 4)), -MaxScrollAccelerated, +MaxScrollAccelerated)); _touchSpeed.setX(snap((oldSpeedX + (newSpeedX / 4)), -MaxScrollAccelerated, +MaxScrollAccelerated)); } else { _touchSpeed = QPoint(); } } else { // we average the speed to avoid strange effects with the last delta if (!_touchSpeed.isNull()) { _touchSpeed.setX(snap((_touchSpeed.x() / 4) + (newSpeedX * 3 / 4), -MaxScrollFlick, +MaxScrollFlick)); _touchSpeed.setY(snap((_touchSpeed.y() / 4) + (newSpeedY * 3 / 4), -MaxScrollFlick, +MaxScrollFlick)); } else { _touchSpeed = QPoint(newSpeedX, newSpeedY); } } } } else { _touchPrevPosValid = true; } _touchSpeedTime = nowTime; _touchPrevPos = _touchPos; } void ScrollArea::touchResetSpeed() { _touchSpeed = QPoint(); _touchPrevPosValid = false; } bool ScrollArea::eventFilter(QObject *obj, QEvent *e) { bool res = QScrollArea::eventFilter(obj, e); if (e->type() == QEvent::TouchBegin || e->type() == QEvent::TouchUpdate || e->type() == QEvent::TouchEnd || e->type() == QEvent::TouchCancel) { QTouchEvent *ev = static_cast<QTouchEvent*>(e); if (_touchEnabled && ev->device()->type() == QTouchDevice::TouchScreen) { if (obj == widget()) { touchEvent(ev); return true; } } } return res; } bool ScrollArea::viewportEvent(QEvent *e) { if (e->type() == QEvent::TouchBegin || e->type() == QEvent::TouchUpdate || e->type() == QEvent::TouchEnd || e->type() == QEvent::TouchCancel) { QTouchEvent *ev = static_cast<QTouchEvent*>(e); if (_touchEnabled && ev->device()->type() == QTouchDevice::TouchScreen) { touchEvent(ev); return true; } } return QScrollArea::viewportEvent(e); } void ScrollArea::touchEvent(QTouchEvent *e) { if (!e->touchPoints().isEmpty()) { _touchPrevPos = _touchPos; _touchPos = e->touchPoints().cbegin()->screenPos().toPoint(); } switch (e->type()) { case QEvent::TouchBegin: { if (_touchPress || e->touchPoints().isEmpty()) return; _touchPress = true; if (_touchScrollState == TouchScrollState::Auto) { _touchScrollState = TouchScrollState::Acceleration; _touchWaitingAcceleration = true; _touchAccelerationTime = getms(); touchUpdateSpeed(); _touchStart = _touchPos; } else { _touchScroll = false; _touchTimer.start(QApplication::startDragTime()); } _touchStart = _touchPrevPos = _touchPos; _touchRightButton = false; } break; case QEvent::TouchUpdate: { if (!_touchPress) return; if (!_touchScroll && (_touchPos - _touchStart).manhattanLength() >= QApplication::startDragDistance()) { _touchTimer.stop(); _touchScroll = true; touchUpdateSpeed(); } if (_touchScroll) { if (_touchScrollState == TouchScrollState::Manual) { touchScrollUpdated(_touchPos); } else if (_touchScrollState == TouchScrollState::Acceleration) { touchUpdateSpeed(); _touchAccelerationTime = getms(); if (_touchSpeed.isNull()) { _touchScrollState = TouchScrollState::Manual; } } } } break; case QEvent::TouchEnd: { if (!_touchPress) return; _touchPress = false; auto weak = make_weak(this); if (_touchScroll) { if (_touchScrollState == TouchScrollState::Manual) { _touchScrollState = TouchScrollState::Auto; _touchPrevPosValid = false; _touchScrollTimer.start(15); _touchTime = getms(); } else if (_touchScrollState == TouchScrollState::Auto) { _touchScrollState = TouchScrollState::Manual; _touchScroll = false; touchResetSpeed(); } else if (_touchScrollState == TouchScrollState::Acceleration) { _touchScrollState = TouchScrollState::Auto; _touchWaitingAcceleration = false; _touchPrevPosValid = false; } } else if (window()) { // one short tap -- like left mouse click, one long tap -- like right mouse click Qt::MouseButton btn(_touchRightButton ? Qt::RightButton : Qt::LeftButton); if (weak) sendSynteticMouseEvent(this, QEvent::MouseMove, Qt::NoButton, _touchStart); if (weak) sendSynteticMouseEvent(this, QEvent::MouseButtonPress, btn, _touchStart); if (weak) sendSynteticMouseEvent(this, QEvent::MouseButtonRelease, btn, _touchStart); if (weak && _touchRightButton) { auto windowHandle = window()->windowHandle(); auto localPoint = windowHandle->mapFromGlobal(_touchStart); QContextMenuEvent ev(QContextMenuEvent::Mouse, localPoint, _touchStart, QGuiApplication::keyboardModifiers()); ev.setTimestamp(getms()); QGuiApplication::sendEvent(windowHandle, &ev); } } if (weak) { _touchTimer.stop(); _touchRightButton = false; } } break; case QEvent::TouchCancel: { _touchPress = false; _touchScroll = false; _touchScrollState = TouchScrollState::Manual; _touchTimer.stop(); } break; } } void ScrollArea::touchScrollUpdated(const QPoint &screenPos) { _touchPos = screenPos; touchScroll(_touchPos - _touchPrevPos); touchUpdateSpeed(); } void ScrollArea::disableScroll(bool dis) { _disabled = dis; if (_disabled && _st.hiding) { _horizontalBar->hideTimeout(0); _verticalBar->hideTimeout(0); } } void ScrollArea::scrollContentsBy(int dx, int dy) { if (_disabled) { return; } QScrollArea::scrollContentsBy(dx, dy); } bool ScrollArea::touchScroll(const QPoint &delta) { int32 scTop = scrollTop(), scMax = scrollTopMax(), scNew = snap(scTop - delta.y(), 0, scMax); if (scNew == scTop) return false; scrollToY(scNew); return true; } void ScrollArea::resizeEvent(QResizeEvent *e) { QScrollArea::resizeEvent(e); _horizontalBar->recountSize(); _verticalBar->recountSize(); _topShadow->setGeometry(QRect(0, 0, width(), qAbs(_st.topsh))); _bottomShadow->setGeometry(QRect(0, height() - qAbs(_st.bottomsh), width(), qAbs(_st.bottomsh))); if (SplittedWidget *w = qobject_cast<SplittedWidget*>(widget())) { w->resize(width() - w->otherWidth(), w->height()); if (!rtl()) { _other->move(w->width(), w->y()); } } emit geometryChanged(); } void ScrollArea::moveEvent(QMoveEvent *e) { QScrollArea::moveEvent(e); emit geometryChanged(); } void ScrollArea::keyPressEvent(QKeyEvent *e) { if ((e->key() == Qt::Key_Up || e->key() == Qt::Key_Down) && e->modifiers().testFlag(Qt::AltModifier)) { e->ignore(); } else if(e->key() == Qt::Key_Escape || e->key() == Qt::Key_Back) { ((QObject*)widget())->event(e); } else { QScrollArea::keyPressEvent(e); } } void ScrollArea::enterEventHook(QEvent *e) { if (_disabled) return; if (_st.hiding) { _horizontalBar->hideTimeout(_st.hiding); _verticalBar->hideTimeout(_st.hiding); } return QScrollArea::enterEvent(e); } void ScrollArea::leaveEventHook(QEvent *e) { if (_st.hiding) { _horizontalBar->hideTimeout(0); _verticalBar->hideTimeout(0); } return QScrollArea::leaveEvent(e); } void ScrollArea::scrollTo(ScrollToRequest request) { scrollToY(request.ymin, request.ymax); } void ScrollArea::scrollToWidget(not_null<QWidget*> widget) { if (auto local = this->widget()) { auto globalPosition = widget->mapToGlobal(QPoint(0, 0)); auto localPosition = local->mapFromGlobal(globalPosition); auto localTop = localPosition.y(); auto localBottom = localTop + widget->height(); scrollToY(localTop, localBottom); } } void ScrollArea::scrollToY(int toTop, int toBottom) { if (const auto inner = widget()) { SendPendingMoveResizeEvents(inner); } SendPendingMoveResizeEvents(this); int toMin = 0, toMax = scrollTopMax(); if (toTop < toMin) { toTop = toMin; } else if (toTop > toMax) { toTop = toMax; } bool exact = (toBottom < 0); int curTop = scrollTop(), curHeight = height(), curBottom = curTop + curHeight, scToTop = toTop; if (!exact && toTop >= curTop) { if (toBottom < toTop) toBottom = toTop; if (toBottom <= curBottom) return; scToTop = toBottom - curHeight; if (scToTop > toTop) scToTop = toTop; if (scToTop == curTop) return; } else { scToTop = toTop; } verticalScrollBar()->setValue(scToTop); } void ScrollArea::doSetOwnedWidget(object_ptr<TWidget> w) { auto splitted = qobject_cast<SplittedWidget*>(w.data()); if (widget() && _touchEnabled) { widget()->removeEventFilter(this); if (!_widgetAcceptsTouch) widget()->setAttribute(Qt::WA_AcceptTouchEvents, false); } if (_other && !splitted) { _other.destroy(); disconnect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(onVerticalScroll())); } else if (!_other && splitted) { _other.create(this); _other->resize(_verticalBar->width(), _other->height()); connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(onVerticalScroll())); _horizontalBar->raise(); _verticalBar->raise(); } _widget = std::move(w); QScrollArea::setWidget(_widget); if (_widget) { _widget->setAutoFillBackground(false); if (_touchEnabled) { _widget->installEventFilter(this); _widgetAcceptsTouch = _widget->testAttribute(Qt::WA_AcceptTouchEvents); _widget->setAttribute(Qt::WA_AcceptTouchEvents); } if (splitted) { splitted->setOtherWidth(_verticalBar->width()); _widget->setGeometry(rtl() ? splitted->otherWidth() : 0, 0, width() - splitted->otherWidth(), _widget->height()); connect(splitted, SIGNAL(resizeOther()), this, SLOT(onResizeOther())); connect(splitted, SIGNAL(updateOther(const QRect&)), this, SLOT(onUpdateOther(const QRect&))); connect(splitted, SIGNAL(updateOther(const QRegion&)), this, SLOT(onUpdateOther(const QRegion&))); onResizeOther(); splitted->update(); } } } object_ptr<TWidget> ScrollArea::doTakeWidget() { if (_other) { _other.destroy(); disconnect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(onVerticalScroll())); } QScrollArea::takeWidget(); return std::move(_widget); } void ScrollArea::onResizeOther() { _other->resize(_other->width(), widget()->height()); } void ScrollArea::onUpdateOther(const QRect &r) { _other->update(r.intersected(_other->rect())); } void ScrollArea::onUpdateOther(const QRegion &r) { _other->update(r.intersected(_other->rect())); } void ScrollArea::onVerticalScroll() { _other->move(_other->x(), widget()->y()); } void ScrollArea::rangeChanged(int oldMax, int newMax, bool vertical) { } void ScrollArea::updateBars() { _horizontalBar->update(); _verticalBar->update(); } bool ScrollArea::focusNextPrevChild(bool next) { if (QWidget::focusNextPrevChild(next)) { // if (QWidget *fw = focusWidget()) // ensureWidgetVisible(fw); return true; } return false; } void ScrollArea::setMovingByScrollBar(bool movingByScrollBar) { _movingByScrollBar = movingByScrollBar; } } // namespace Ui ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/widgets/scroll_area.h���������������������������������������0000664�0000000�0000000�00000015204�13262451251�0024147�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/event_stream.h> #include "ui/rp_widget.h" #include "styles/style_widgets.h" namespace Ui { enum class TouchScrollState { Manual, // Scrolling manually with the finger on the screen Auto, // Scrolling automatically Acceleration // Scrolling automatically but a finger is on the screen }; class ScrollArea; struct ScrollToRequest { ScrollToRequest(int ymin, int ymax) : ymin(ymin) , ymax(ymax) { } int ymin = 0; int ymax = 0; }; class ScrollShadow : public QWidget { Q_OBJECT public: ScrollShadow(ScrollArea *parent, const style::ScrollArea *st); void paintEvent(QPaintEvent *e); public slots: void changeVisibility(bool shown); private: const style::ScrollArea *_st; }; class ScrollBar : public TWidget { Q_OBJECT public: ScrollBar(ScrollArea *parent, bool vertical, const style::ScrollArea *st); void recountSize(); void updateBar(bool force = false); void hideTimeout(TimeMs dt); private slots: void onValueChanged(); void onRangeChanged(); void onHideTimer(); signals: void topShadowVisibility(bool); void bottomShadowVisibility(bool); protected: void paintEvent(QPaintEvent *e) override; void enterEventHook(QEvent *e) override; void leaveEventHook(QEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void resizeEvent(QResizeEvent *e) override; private: ScrollArea *area(); void setOver(bool over); void setOverBar(bool overbar); void setMoving(bool moving); const style::ScrollArea *_st; bool _vertical = true; bool _hiding = false; bool _over = false; bool _overbar = false; bool _moving = false; bool _topSh = false; bool _bottomSh = false; QPoint _dragStart; QScrollBar *_connected; int32 _startFrom, _scrollMax; TimeMs _hideIn = 0; QTimer _hideTimer; Animation _a_over; Animation _a_barOver; Animation _a_opacity; QRect _bar; }; class SplittedWidget : public Ui::RpWidget { Q_OBJECT public: SplittedWidget(QWidget *parent) : RpWidget(parent) { setAttribute(Qt::WA_OpaquePaintEvent); } void setHeight(int32 newHeight) { resize(width(), newHeight); emit resizeOther(); } void update(int x, int y, int w, int h) { update(QRect(x, y, w, h)); } void update(const QRect&); void update(const QRegion&); void rtlupdate(const QRect &r) { update(myrtlrect(r)); } void rtlupdate(int x, int y, int w, int h) { update(myrtlrect(x, y, w, h)); } public slots: void update() { update(0, 0, getFullWidth(), height()); } signals: void resizeOther(); void updateOther(const QRect&); void updateOther(const QRegion&); protected: void paintEvent(QPaintEvent *e) override; // paintEvent done through paintRegion int otherWidth() const { return _otherWidth; } int getFullWidth() const { return width() + otherWidth(); } virtual void paintRegion(Painter &p, const QRegion ®ion, bool paintingOther) = 0; private: int _otherWidth = 0; void setOtherWidth(int otherWidth) { _otherWidth = otherWidth; } void resize(int w, int h) { TWidget::resize(w, h); } friend class ScrollArea; friend class SplittedWidgetOther; }; class SplittedWidgetOther; class ScrollArea : public Ui::RpWidgetWrap<QScrollArea> { Q_OBJECT public: ScrollArea(QWidget *parent, const style::ScrollArea &st = st::defaultScrollArea, bool handleTouch = true); int scrollWidth() const; int scrollHeight() const; int scrollLeftMax() const; int scrollTopMax() const; int scrollLeft() const; int scrollTop() const; template <typename Widget> QPointer<Widget> setOwnedWidget(object_ptr<Widget> widget) { auto result = QPointer<Widget>(widget); doSetOwnedWidget(std::move(widget)); return result; } template <typename Widget> object_ptr<Widget> takeWidget() { return static_object_cast<Widget>(doTakeWidget()); } void rangeChanged(int oldMax, int newMax, bool vertical); void updateBars(); bool focusNextPrevChild(bool next) override; void setMovingByScrollBar(bool movingByScrollBar); bool viewportEvent(QEvent *e) override; void keyPressEvent(QKeyEvent *e) override; auto scrollTopValue() const { return _scrollTopUpdated.events_starting_with(scrollTop()); } void scrollTo(ScrollToRequest request); void scrollToWidget(not_null<QWidget*> widget); protected: bool eventFilter(QObject *obj, QEvent *e) override; void resizeEvent(QResizeEvent *e) override; void moveEvent(QMoveEvent *e) override; void touchEvent(QTouchEvent *e); void enterEventHook(QEvent *e) override; void leaveEventHook(QEvent *e) override; public slots: void scrollToY(int toTop, int toBottom = -1); void disableScroll(bool dis); void onScrolled(); void onInnerResized(); void onTouchTimer(); void onTouchScrollTimer(); void onResizeOther(); void onUpdateOther(const QRect&); void onUpdateOther(const QRegion&); void onVerticalScroll(); signals: void scrolled(); void innerResized(); void scrollStarted(); void scrollFinished(); void geometryChanged(); protected: void scrollContentsBy(int dx, int dy) override; private: void doSetOwnedWidget(object_ptr<TWidget> widget); object_ptr<TWidget> doTakeWidget(); void setWidget(QWidget *widget); bool touchScroll(const QPoint &delta); void touchScrollUpdated(const QPoint &screenPos); void touchResetSpeed(); void touchUpdateSpeed(); void touchDeaccelerate(int32 elapsed); bool _disabled = false; bool _movingByScrollBar = false; const style::ScrollArea &_st; object_ptr<ScrollBar> _horizontalBar, _verticalBar; object_ptr<ScrollShadow> _topShadow, _bottomShadow; int _horizontalValue, _verticalValue; bool _touchEnabled; QTimer _touchTimer; bool _touchScroll = false; bool _touchPress = false; bool _touchRightButton = false; QPoint _touchStart, _touchPrevPos, _touchPos; TouchScrollState _touchScrollState = TouchScrollState::Manual; bool _touchPrevPosValid = false; bool _touchWaitingAcceleration = false; QPoint _touchSpeed; TimeMs _touchSpeedTime = 0; TimeMs _touchAccelerationTime = 0; TimeMs _touchTime = 0; QTimer _touchScrollTimer; bool _widgetAcceptsTouch = false; friend class SplittedWidgetOther; object_ptr<SplittedWidgetOther> _other = { nullptr }; object_ptr<TWidget> _widget = { nullptr }; rpl::event_stream<int> _scrollTopUpdated; }; class SplittedWidgetOther : public TWidget { public: SplittedWidgetOther(ScrollArea *parent) : TWidget(parent) { setAttribute(Qt::WA_OpaquePaintEvent); } protected: void paintEvent(QPaintEvent *e) override; }; } // namespace Ui ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/widgets/shadow.cpp������������������������������������������0000664�0000000�0000000�00000010054�13262451251�0023477�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/widgets/shadow.h" #include "styles/style_widgets.h" namespace Ui { PlainShadow::PlainShadow(QWidget *parent) : PlainShadow(parent, st::shadowFg) { } PlainShadow::PlainShadow(QWidget *parent, style::color color) : RpWidget(parent) , _color(color) { resize(st::lineWidth, st::lineWidth); } void Shadow::paint(Painter &p, const QRect &box, int outerWidth, const style::Shadow &st, RectParts sides) { auto left = (sides & RectPart::Left); auto top = (sides & RectPart::Top); auto right = (sides & RectPart::Right); auto bottom = (sides & RectPart::Bottom); if (left) { auto from = box.y(); auto to = from + box.height(); if (top && !st.topLeft.empty()) { st.topLeft.paint(p, box.x() - st.extend.left(), box.y() - st.extend.top(), outerWidth); from += st.topLeft.height() - st.extend.top(); } if (bottom && !st.bottomLeft.empty()) { st.bottomLeft.paint(p, box.x() - st.extend.left(), box.y() + box.height() + st.extend.bottom() - st.bottomLeft.height(), outerWidth); to -= st.bottomLeft.height() - st.extend.bottom(); } if (to > from && !st.left.empty()) { st.left.fill(p, rtlrect(box.x() - st.extend.left(), from, st.left.width(), to - from, outerWidth)); } } if (right) { auto from = box.y(); auto to = from + box.height(); if (top && !st.topRight.empty()) { st.topRight.paint(p, box.x() + box.width() + st.extend.right() - st.topRight.width(), box.y() - st.extend.top(), outerWidth); from += st.topRight.height() - st.extend.top(); } if (bottom && !st.bottomRight.empty()) { st.bottomRight.paint(p, box.x() + box.width() + st.extend.right() - st.bottomRight.width(), box.y() + box.height() + st.extend.bottom() - st.bottomRight.height(), outerWidth); to -= st.bottomRight.height() - st.extend.bottom(); } if (to > from && !st.right.empty()) { st.right.fill(p, rtlrect(box.x() + box.width() + st.extend.right() - st.right.width(), from, st.right.width(), to - from, outerWidth)); } } if (top && !st.top.empty()) { auto from = box.x(); auto to = from + box.width(); if (left && !st.topLeft.empty()) from += st.topLeft.width() - st.extend.left(); if (right && !st.topRight.empty()) to -= st.topRight.width() - st.extend.right(); if (to > from) { st.top.fill(p, rtlrect(from, box.y() - st.extend.top(), to - from, st.top.height(), outerWidth)); } } if (bottom && !st.bottom.empty()) { auto from = box.x(); auto to = from + box.width(); if (left && !st.bottomLeft.empty()) from += st.bottomLeft.width() - st.extend.left(); if (right && !st.bottomRight.empty()) to -= st.bottomRight.width() - st.extend.right(); if (to > from) { st.bottom.fill(p, rtlrect(from, box.y() + box.height() + st.extend.bottom() - st.bottom.height(), to - from, st.bottom.height(), outerWidth)); } } } QPixmap Shadow::grab( not_null<TWidget*> target, const style::Shadow &shadow, RectParts sides) { SendPendingMoveResizeEvents(target); auto rect = target->rect(); auto extend = QMargins( (sides & RectPart::Left) ? shadow.extend.left() : 0, (sides & RectPart::Top) ? shadow.extend.top() : 0, (sides & RectPart::Right) ? shadow.extend.right() : 0, (sides & RectPart::Bottom) ? shadow.extend.bottom() : 0 ); auto full = QRect(0, 0, extend.left() + rect.width() + extend.right(), extend.top() + rect.height() + extend.bottom()); auto result = QPixmap(full.size() * cIntRetinaFactor()); result.setDevicePixelRatio(cRetinaFactor()); result.fill(Qt::transparent); { Painter p(&result); Ui::Shadow::paint(p, full.marginsRemoved(extend), full.width(), shadow); target->grabStart(); target->render(&p, QPoint(extend.left(), extend.top()), rect, QWidget::DrawChildren | QWidget::IgnoreMask); target->grabFinish(); } return result; } void Shadow::paintEvent(QPaintEvent *e) { Painter p(this); paint(p, rect().marginsRemoved(_st.extend), width(), _st, _sides); } } // namespace Ui ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/widgets/shadow.h��������������������������������������������0000664�0000000�0000000�00000002267�13262451251�0023153�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/rp_widget.h" namespace style { struct Shadow; } // namespace style namespace Ui { class PlainShadow : public RpWidget { public: PlainShadow(QWidget *parent); PlainShadow(QWidget *parent, style::color color); protected: void paintEvent(QPaintEvent *e) override { Painter(this).fillRect(e->rect(), _color); } private: style::color _color; }; class Shadow : public TWidget { public: Shadow( QWidget *parent, const style::Shadow &st, RectParts sides = RectPart::AllSides) : TWidget(parent) , _st(st) , _sides(sides) { } static void paint( Painter &p, const QRect &box, int outerWidth, const style::Shadow &st, RectParts sides = RectPart::AllSides); static QPixmap grab( not_null<TWidget*> target, const style::Shadow &shadow, RectParts sides = RectPart::AllSides); protected: void paintEvent(QPaintEvent *e) override; private: const style::Shadow &_st; RectParts _sides; }; } // namespace Ui �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/widgets/tooltip.cpp�����������������������������������������0000664�0000000�0000000�00000027253�13262451251�0023715�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/widgets/tooltip.h" #include "mainwindow.h" #include "styles/style_widgets.h" #include "platform/platform_specific.h" namespace Ui { Tooltip *TooltipInstance = nullptr; bool AbstractTooltipShower::tooltipWindowActive() const { if (auto window = App::wnd()) { window->updateIsActive(0); return window->isActive(); } return false; } const style::Tooltip *AbstractTooltipShower::tooltipSt() const { return &st::defaultTooltip; } AbstractTooltipShower::~AbstractTooltipShower() { if (TooltipInstance && TooltipInstance->_shower == this) { TooltipInstance->_shower = 0; } } Tooltip::Tooltip() : TWidget(nullptr) { TooltipInstance = this; setWindowFlags(Qt::WindowFlags(Qt::FramelessWindowHint) | Qt::BypassWindowManagerHint | Qt::NoDropShadowWindowHint | Qt::ToolTip); setAttribute(Qt::WA_NoSystemBackground, true); setAttribute(Qt::WA_TranslucentBackground, true); _showTimer.setCallback([=] { performShow(); }); _hideByLeaveTimer.setCallback([=] { Hide(); }); connect(App::wnd()->windowHandle(), SIGNAL(activeChanged()), this, SLOT(onWndActiveChanged())); } void Tooltip::performShow() { if (_shower) { auto text = _shower->tooltipWindowActive() ? _shower->tooltipText() : QString(); if (text.isEmpty()) { Hide(); } else { TooltipInstance->popup(_shower->tooltipPos(), text, _shower->tooltipSt()); } } } void Tooltip::onWndActiveChanged() { if (!App::wnd() || !App::wnd()->windowHandle() || !App::wnd()->windowHandle()->isActive()) { Hide(); } } bool Tooltip::eventFilter(QObject *o, QEvent *e) { if (e->type() == QEvent::Leave) { _hideByLeaveTimer.callOnce(10); } else if (e->type() == QEvent::Enter) { _hideByLeaveTimer.cancel(); } else if (e->type() == QEvent::MouseMove) { if ((QCursor::pos() - _point).manhattanLength() > QApplication::startDragDistance()) { Hide(); } } return TWidget::eventFilter(o, e); } Tooltip::~Tooltip() { if (TooltipInstance == this) { TooltipInstance = nullptr; } } void Tooltip::popup(const QPoint &m, const QString &text, const style::Tooltip *st) { if (!_isEventFilter) { _isEventFilter = true; QCoreApplication::instance()->installEventFilter(this); } _point = m; _st = st; _text = Text(_st->textStyle, text, _textPlainOptions, _st->widthMax, true); _useTransparency = Platform::TranslucentWindowsSupported(_point); setAttribute(Qt::WA_OpaquePaintEvent, !_useTransparency); int32 addw = 2 * st::lineWidth + _st->textPadding.left() + _st->textPadding.right(); int32 addh = 2 * st::lineWidth + _st->textPadding.top() + _st->textPadding.bottom(); // count tooltip size QSize s(addw + _text.maxWidth(), addh + _text.minHeight()); if (s.width() > _st->widthMax) { s.setWidth(addw + _text.countWidth(_st->widthMax - addw)); s.setHeight(addh + _text.countHeight(s.width() - addw)); } int32 maxh = addh + (_st->linesMax * _st->textStyle.font->height); if (s.height() > maxh) { s.setHeight(maxh); } // count tooltip position QPoint p(m + _st->shift); if (rtl()) { p.setX(m.x() - s.width() - _st->shift.x()); } if (s.width() < 2 * _st->shift.x()) { p.setX(m.x() - (s.width() / 2)); } // adjust tooltip position QRect r(QApplication::desktop()->screenGeometry(m)); if (r.x() + r.width() - _st->skip < p.x() + s.width() && p.x() + s.width() > m.x()) { p.setX(qMax(r.x() + r.width() - int32(_st->skip) - s.width(), m.x() - s.width())); } if (r.x() + _st->skip > p.x() && p.x() < m.x()) { p.setX(qMin(m.x(), r.x() + int32(_st->skip))); } if (r.y() + r.height() - _st->skip < p.y() + s.height()) { p.setY(m.y() - s.height() - _st->skip); } if (r.y() > p.x()) { p.setY(qMin(m.y() + _st->shift.y(), r.y() + r.height() - s.height())); } setGeometry(QRect(p, s)); _hideByLeaveTimer.cancel(); show(); } void Tooltip::paintEvent(QPaintEvent *e) { Painter p(this); if (_useTransparency) { Platform::StartTranslucentPaint(p, e); p.setPen(_st->textBorder); p.setBrush(_st->textBg); PainterHighQualityEnabler hq(p); p.drawRoundedRect(QRectF(0.5, 0.5, width() - 1., height() - 1.), st::buttonRadius, st::buttonRadius); } else { p.fillRect(rect(), _st->textBg); p.fillRect(QRect(0, 0, width(), st::lineWidth), _st->textBorder); p.fillRect(QRect(0, height() - st::lineWidth, width(), st::lineWidth), _st->textBorder); p.fillRect(QRect(0, st::lineWidth, st::lineWidth, height() - 2 * st::lineWidth), _st->textBorder); p.fillRect(QRect(width() - st::lineWidth, st::lineWidth, st::lineWidth, height() - 2 * st::lineWidth), _st->textBorder); } int32 lines = qFloor((height() - 2 * st::lineWidth - _st->textPadding.top() - _st->textPadding.bottom()) / _st->textStyle.font->height); p.setPen(_st->textFg); _text.drawElided(p, st::lineWidth + _st->textPadding.left(), st::lineWidth + _st->textPadding.top(), width() - 2 * st::lineWidth - _st->textPadding.left() - _st->textPadding.right(), lines); } void Tooltip::hideEvent(QHideEvent *e) { if (TooltipInstance == this) { Hide(); } } void Tooltip::Show(int32 delay, const AbstractTooltipShower *shower) { if (!TooltipInstance) { new Tooltip(); } TooltipInstance->_shower = shower; if (delay >= 0) { TooltipInstance->_showTimer.callOnce(delay); } else { TooltipInstance->performShow(); } } void Tooltip::Hide() { if (auto instance = TooltipInstance) { TooltipInstance = nullptr; instance->_showTimer.cancel(); instance->_hideByLeaveTimer.cancel(); instance->hide(); InvokeQueued(instance, [instance] { instance->deleteLater(); }); } } ImportantTooltip::ImportantTooltip(QWidget *parent, object_ptr<TWidget> content, const style::ImportantTooltip &st) : TWidget(parent) , _st(st) , _content(std::move(content)) { _content->setParent(this); _hideTimer.setCallback([this] { toggleAnimated(false); }); hide(); } void ImportantTooltip::pointAt(QRect area, RectParts side) { if (_area == area && _side == side) { return; } setArea(area); countApproachSide(side); updateGeometry(); update(); } void ImportantTooltip::setArea(QRect area) { Expects(parentWidget() != nullptr); _area = area; auto point = parentWidget()->mapToGlobal(_area.center()); _useTransparency = Platform::TranslucentWindowsSupported(point); setAttribute(Qt::WA_OpaquePaintEvent, !_useTransparency); auto contentWidth = parentWidget()->rect().marginsRemoved(_st.padding).width(); accumulate_min(contentWidth, _content->naturalWidth()); _content->resizeToWidth(contentWidth); auto size = _content->rect().marginsAdded(_st.padding).size(); if (_useTransparency) { size.setHeight(size.height() + _st.arrow); } if (size.width() < 2 * (_st.arrowSkipMin + _st.arrow)) { size.setWidth(2 * (_st.arrowSkipMin + _st.arrow)); } resize(size); } void ImportantTooltip::countApproachSide(RectParts preferSide) { Expects(parentWidget() != nullptr); auto requiredSpace = countInner().height() + _st.shift; if (_useTransparency) { requiredSpace += _st.arrow; } auto available = parentWidget()->rect(); auto availableAbove = _area.y() - available.y(); auto availableBelow = (available.y() + available.height()) - (_area.y() + _area.height()); auto allowedAbove = (availableAbove >= requiredSpace + _st.margin.top()); auto allowedBelow = (availableBelow >= requiredSpace + _st.margin.bottom()); if ((allowedAbove && allowedBelow) || (!allowedAbove && !allowedBelow)) { _side = preferSide; } else { _side = (allowedAbove ? RectPart::Top : RectPart::Bottom) | (preferSide & (RectPart::Left | RectPart::Center | RectPart::Right)); } if (_useTransparency) { auto arrow = QImage(QSize(_st.arrow * 2, _st.arrow) * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); arrow.fill(Qt::transparent); arrow.setDevicePixelRatio(cRetinaFactor()); { Painter p(&arrow); PainterHighQualityEnabler hq(p); QPainterPath path; path.moveTo(0, 0); path.lineTo(2 * _st.arrow, 0); path.lineTo(_st.arrow, _st.arrow); path.lineTo(0, 0); p.fillPath(path, _st.bg); } if (_side & RectPart::Bottom) { arrow = std::move(arrow).transformed(QTransform(1, 0, 0, -1, 0, 0)); } _arrow = App::pixmapFromImageInPlace(std::move(arrow)); } } void ImportantTooltip::toggleAnimated(bool visible) { if (_visible == isHidden()) { setVisible(_visible); } if (_visible != visible) { updateGeometry(); _visible = visible; refreshAnimationCache(); if (_visible) { show(); } else if (isHidden()) { return; } hideChildren(); _visibleAnimation.start([this] { animationCallback(); }, _visible ? 0. : 1., _visible ? 1. : 0., _st.duration, anim::easeOutCirc); } } void ImportantTooltip::hideAfter(TimeMs timeout) { _hideTimer.callOnce(timeout); } void ImportantTooltip::animationCallback() { updateGeometry(); update(); checkAnimationFinish(); } void ImportantTooltip::refreshAnimationCache() { if (_cache.isNull() && _useTransparency) { auto animation = base::take(_visibleAnimation); auto visible = std::exchange(_visible, true); showChildren(); _cache = GrabWidget(this); _visible = base::take(visible); _visibleAnimation = base::take(animation); } } void ImportantTooltip::toggleFast(bool visible) { if (_visible == isHidden()) { setVisible(_visible); } if (_visibleAnimation.animating() || _visible != visible) { _visibleAnimation.finish(); _visible = visible; checkAnimationFinish(); } } void ImportantTooltip::checkAnimationFinish() { if (!_visibleAnimation.animating()) { _cache = QPixmap(); showChildren(); setVisible(_visible); if (_visible) { update(); } else if (_hiddenCallback) { _hiddenCallback(); } } } void ImportantTooltip::updateGeometry() { Expects(parentWidget() != nullptr); auto parent = parentWidget(); auto areaMiddle = _area.x() + (_area.width() / 2); auto left = areaMiddle - (width() / 2); if (_side & RectPart::Left) { left = areaMiddle + _st.arrowSkip - width(); } else if (_side & RectPart::Right) { left = areaMiddle - _st.arrowSkip; } accumulate_min(left, parent->width() - _st.margin.right() - width()); accumulate_max(left, _st.margin.left()); accumulate_max(left, areaMiddle + _st.arrow + _st.arrowSkipMin - width()); accumulate_min(left, areaMiddle - _st.arrow - _st.arrowSkipMin); auto countTop = [this] { auto shift = anim::interpolate(_st.shift, 0, _visibleAnimation.current(_visible ? 1. : 0.)); if (_side & RectPart::Top) { return _area.y() - height() - shift; } return _area.y() + _area.height() + shift; }; move(left, countTop()); } void ImportantTooltip::resizeEvent(QResizeEvent *e) { auto inner = countInner(); auto contentTop = _st.padding.top(); if (_useTransparency && (_side & RectPart::Bottom)) { contentTop += _st.arrow; } _content->moveToLeft(_st.padding.left(), contentTop); } QRect ImportantTooltip::countInner() const { return _content->geometry().marginsAdded(_st.padding); } void ImportantTooltip::paintEvent(QPaintEvent *e) { Painter p(this); auto inner = countInner(); if (_useTransparency) { if (!_cache.isNull()) { auto opacity = _visibleAnimation.current(_visible ? 1. : 0.); p.setOpacity(opacity); p.drawPixmap(0, 0, _cache); } else { if (!_visible) { return; } p.setBrush(_st.bg); p.setPen(Qt::NoPen); { PainterHighQualityEnabler hq(p); p.drawRoundedRect(inner, _st.radius, _st.radius); } auto areaMiddle = _area.x() + (_area.width() / 2) - x(); auto arrowLeft = areaMiddle - _st.arrow; if (_side & RectPart::Top) { p.drawPixmapLeft(arrowLeft, inner.y() + inner.height(), width(), _arrow); } else { p.drawPixmapLeft(arrowLeft, inner.y() - _st.arrow, width(), _arrow); } } } else { p.fillRect(inner, QColor(_st.bg->c.red(), _st.bg->c.green(), _st.bg->c.blue())); } } } // namespace Ui �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/widgets/tooltip.h�������������������������������������������0000664�0000000�0000000�00000004635�13262451251�0023361�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "base/timer.h" namespace style { struct Tooltip; struct ImportantTooltip; } // namespace style namespace Ui { class AbstractTooltipShower { public: virtual QString tooltipText() const = 0; virtual QPoint tooltipPos() const = 0; virtual bool tooltipWindowActive() const; virtual const style::Tooltip *tooltipSt() const; virtual ~AbstractTooltipShower(); }; class Tooltip : public TWidget { Q_OBJECT public: static void Show(int32 delay, const AbstractTooltipShower *shower); static void Hide(); private slots: void onWndActiveChanged(); protected: void paintEvent(QPaintEvent *e) override; void hideEvent(QHideEvent *e) override; bool eventFilter(QObject *o, QEvent *e) override; private: void performShow(); Tooltip(); ~Tooltip(); void popup(const QPoint &p, const QString &text, const style::Tooltip *st); friend class AbstractTooltipShower; const AbstractTooltipShower *_shower = nullptr; base::Timer _showTimer; Text _text; QPoint _point; const style::Tooltip *_st = nullptr; base::Timer _hideByLeaveTimer; bool _isEventFilter = false; bool _useTransparency = true; }; class ImportantTooltip : public TWidget { public: ImportantTooltip(QWidget *parent, object_ptr<TWidget> content, const style::ImportantTooltip &st); void pointAt(QRect area, RectParts preferSide = RectPart::Top | RectPart::Left); void toggleAnimated(bool visible); void toggleFast(bool visible); void hideAfter(TimeMs timeout); void setHiddenCallback(base::lambda<void()> callback) { _hiddenCallback = std::move(callback); } protected: void resizeEvent(QResizeEvent *e); void paintEvent(QPaintEvent *e); private: void animationCallback(); QRect countInner() const; void setArea(QRect area); void countApproachSide(RectParts preferSide); void updateGeometry(); void checkAnimationFinish(); void refreshAnimationCache(); base::Timer _hideTimer; const style::ImportantTooltip &_st; object_ptr<TWidget> _content; QRect _area; RectParts _side = RectPart::Top | RectPart::Left; QPixmap _arrow; Animation _visibleAnimation; bool _visible = false; base::lambda<void()> _hiddenCallback; bool _useTransparency = true; QPixmap _cache; }; } // namespace Ui ���������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/widgets/widgets.style���������������������������������������0000664�0000000�0000000�00000052404�13262451251�0024243�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ using "basic.style"; LabelSimple { font: font; maxWidth: pixels; textFg: color; } FlatLabel { margin: margins; minWidth: pixels; align: align; textFg: color; maxHeight: pixels; style: TextStyle; palette: TextPalette; } LinkButton { color: color; overColor: color; font: font; overFont: font; } RippleAnimation { color: color; showDuration: int; hideDuration: int; } FlatButton { color: color; overColor: color; bgColor: color; overBgColor: color; width: pixels; height: pixels; textTop: pixels; font: font; overFont: font; ripple: RippleAnimation; } RoundButton { textFg: color; textFgOver: color; textBg: color; // rect of textBg with rounded rect of textBgOver upon it textBgOver: color; numbersTextFg: color; numbersTextFgOver: color; numbersSkip: pixels; width: pixels; height: pixels; padding: margins; textTop: pixels; icon: icon; font: font; ripple: RippleAnimation; } Toggle { toggledBg: color; toggledFg: color; untoggledBg: color; untoggledFg: color; duration: int; border: pixels; shift: pixels; diameter: pixels; width: pixels; xsize: pixels; vsize: pixels; vshift: pixels; stroke: pixels; rippleAreaPadding: pixels; } Check { bg: color; untoggledFg: color; toggledFg: color; diameter: pixels; thickness: pixels; icon: icon; duration: int; rippleAreaPadding: pixels; } Radio { bg: color; untoggledFg: color; toggledFg: color; diameter: pixels; thickness: pixels; skip: pixels; duration: int; rippleAreaPadding: pixels; } Checkbox { textFg: color; width: pixels; margin: margins; textPosition: point; checkPosition: point; style: TextStyle; rippleAreaPosition: point; rippleBg: color; rippleBgActive: color; ripple: RippleAnimation; disabledOpacity: double; } ScrollArea { bg: color; bgOver: color; barBg: color; barBgOver: color; round: pixels; width: pixels; minHeight: pixels; deltax: pixels; deltat: pixels; deltab: pixels; topsh: pixels; bottomsh: pixels; shColor: color; duration: int; hiding: int; } FlatTextarea { textColor: color; bgColor: color; width: pixels; textMrg: margins; align: align; font: font; phColor: color; phFocusColor: color; phPos: point; phAlign: align; phShift: pixels; phDuration: int; } FlatInput { textColor: color; bgColor: color; bgActive: color; width: pixels; height: pixels; textMrg: margins; align: align; font: font; icon: icon; borderWidth: pixels; borderColor: color; borderActive: color; phColor: color; phFocusColor: color; phPos: point; phAlign: align; phShift: pixels; phDuration: int; } InputField { textBg: color; textFg: color; textMargins: margins; textAlign: align; placeholderFg: color; placeholderFgActive: color; placeholderFgError: color; placeholderMargins: margins; placeholderAlign: align; placeholderScale: double; placeholderShift: pixels; placeholderFont: font; duration: int; borderFg: color; borderFgActive: color; borderFgError: color; border: pixels; borderActive: pixels; font: font; width: pixels; heightMin: pixels; heightMax: pixels; } OutlineButton { outlineWidth: pixels; outlineFg: color; outlineFgOver: color; textBg: color; textBgOver: color; textFg: color; textFgOver: color; font: font; padding: margins; ripple: RippleAnimation; } IconButton { width: pixels; height: pixels; icon: icon; iconOver: icon; iconPosition: point; duration: int; rippleAreaPosition: point; rippleAreaSize: pixels; ripple: RippleAnimation; } Shadow { left: icon; topLeft: icon; top: icon; topRight: icon; right: icon; bottomRight: icon; bottom: icon; bottomLeft: icon; extend: margins; fallback: color; } MediaSlider { width: pixels; activeFg: color; inactiveFg: color; activeFgOver: color; inactiveFgOver: color; activeFgDisabled: color; inactiveFgDisabled: color; seekSize: size; duration: int; } FilledSlider { fullWidth: pixels; lineWidth: pixels; activeFg: color; inactiveFg: color; disabledFg: color; duration: int; } RoundCheckbox { border: color; bgInactive: color; bgActive: color; width: pixels; size: pixels; sizeSmall: double; duration: int; bgDuration: double; fgDuration: double; check: icon; } RoundImageCheckbox { imageRadius: pixels; imageSmallRadius: pixels; selectWidth: pixels; selectFg: color; selectDuration: int; check: RoundCheckbox; } CrossAnimation { fg: color; size: pixels; skip: pixels; stroke: pixels; minScale: double; } CrossButton { width: pixels; height: pixels; cross: CrossAnimation; crossFg: color; crossFgOver:color; crossPosition: point; duration: int; loadingPeriod: int; ripple: RippleAnimation; } MultiSelectItem { padding: margins; maxWidth: pixels; height: pixels; style: TextStyle; textBg: color; textFg: color; textActiveBg: color; textActiveFg: color; duration: int; deleteFg: color; deleteCross: CrossAnimation; minScale: double; } MultiSelect { bg: color; padding: margins; maxHeight: pixels; scroll: ScrollArea; item: MultiSelectItem; itemSkip: pixels; field: InputField; fieldMinWidth: pixels; fieldIcon: icon; fieldIconSkip: pixels; fieldCancel: CrossButton; fieldCancelSkip: pixels; } CallButton { button: IconButton; bg: color; angle: double; outerRadius: pixels; outerBg: color; } Menu { skip: pixels; itemBg: color; itemBgOver: color; itemFg: color; itemFgOver: color; itemFgDisabled: color; itemFgShortcut: color; itemFgShortcutOver: color; itemFgShortcutDisabled: color; itemPadding: margins; itemIconPosition: point; itemFont: font; itemToggle: Toggle; itemToggleOver: Toggle; itemToggleShift: pixels; separatorPadding: margins; separatorWidth: pixels; separatorFg: color; arrow: icon; widthMin: pixels; widthMax: pixels; ripple: RippleAnimation; } PanelAnimation { startWidth: double; widthDuration: double; startHeight: double; heightDuration: double; startOpacity: double; opacityDuration: double; startFadeTop: double; fadeHeight: double; fadeOpacity: double; fadeBg: color; shadow: Shadow; } PopupMenu { shadow: Shadow; scrollPadding: margins; animation: PanelAnimation; menu: Menu; duration: int; showDuration: int; } InnerDropdown { padding: margins; shadow: Shadow; animation: PanelAnimation; duration: int; showDuration: int; width: pixels; bg: color; scroll: ScrollArea; scrollMargin: margins; scrollPadding: margins; } DropdownMenu { wrap: InnerDropdown; menu: Menu; } Tooltip { textBg: color; textFg: color; textStyle: TextStyle; textBorder: color; textPadding: margins; shift: point; skip: pixels; widthMax: pixels; linesMax: int; } ImportantTooltip { bg: color; margin: margins; padding: margins; radius: pixels; arrow: pixels; arrowSkipMin: pixels; arrowSkip: pixels; shift: pixels; duration: int; } UserpicButton { size: size; photoSize: pixels; photoPosition: point; changeButton: RoundButton; changeIcon: icon; changeIconPosition: point; duration: int; uploadHeight: pixels; uploadBg: color; uploadIcon: icon; uploadIconPosition: point; } FeedUserpicButton { size: size; innerSize: pixels; innerPosition: point; innerPart: UserpicButton; } InfoProfileButton { textFg: color; textFgOver: color; textBg: color; textBgOver: color; font: font; height: pixels; padding: margins; toggle: Toggle; toggleOver: Toggle; toggleSkip: pixels; ripple: RippleAnimation; } defaultLabelSimple: LabelSimple { font: normalFont; maxWidth: 0px; textFg: windowFg; } defaultFlatLabel: FlatLabel { minWidth: 0px; maxHeight: 0px; align: align(left); textFg: windowFg; style: defaultTextStyle; palette: defaultTextPalette; } defaultLinkButton: LinkButton { color: windowActiveTextFg; overColor: windowActiveTextFg; font: linkFont; overFont: linkOverFont; } defaultRippleAnimation: RippleAnimation { color: windowBgRipple; showDuration: 650; hideDuration: 200; } emptyRippleAnimation: RippleAnimation { } defaultActiveButton: RoundButton { textFg: activeButtonFg; textFgOver: activeButtonFgOver; numbersTextFg: activeButtonSecondaryFg; numbersTextFgOver: activeButtonSecondaryFgOver; textBg: activeButtonBg; textBgOver: activeButtonBgOver; numbersSkip: 7px; width: -34px; height: 34px; padding: margins(0px, 0px, 0px, 0px); textTop: 8px; font: semiboldFont; ripple: RippleAnimation(defaultRippleAnimation) { color: activeButtonBgRipple; } } defaultLightButton: RoundButton(defaultActiveButton) { textFg: lightButtonFg; textFgOver: lightButtonFgOver; numbersTextFg: lightButtonFg; numbersTextFgOver: lightButtonFgOver; textBg: lightButtonBg; textBgOver: lightButtonBgOver; ripple: RippleAnimation(defaultRippleAnimation) { color: lightButtonBgRipple; } } defaultScrollArea: ScrollArea { bg: scrollBg; bgOver: scrollBgOver; barBg: scrollBarBg; barBgOver: scrollBarBgOver; round: 2px; width: 10px; minHeight: 20px; deltax: 3px; deltat: 3px; deltab: 3px; topsh: 2px; bottomsh: 2px; shColor: shadowFg; duration: 150; hiding: 1000; } defaultSolidScroll: ScrollArea(defaultScrollArea) { deltax: 5px; width: 14px; deltat: 6px; deltab: 6px; topsh: 0px; bottomsh: 0px; hiding: 0; } defaultInputFont: font(17px); defaultFlatInput: FlatInput { textColor: windowFg; bgColor: filterInputInactiveBg; bgActive: filterInputActiveBg; width: 210px; height: 40px; align: align(left); textMrg: margins(5px, 5px, 5px, 5px); font: defaultInputFont; borderWidth: 2px; borderColor: filterInputInactiveBg; borderActive: filterInputBorderFg; phColor: placeholderFg; phFocusColor: placeholderFgActive; phAlign: align(left); phPos: point(2px, 0px); phShift: 50px; phDuration: 100; } defaultLeftOutlineButton: OutlineButton { outlineWidth: 3px; outlineFg: outlineButtonBg; outlineFgOver: outlineButtonOutlineFg; textBg: outlineButtonBg; textBgOver: outlineButtonBgOver; textFg: windowActiveTextFg; textFgOver: windowActiveTextFg; font: normalFont; padding: margins(11px, 5px, 11px, 5px); ripple: RippleAnimation(defaultRippleAnimation) { color: outlineButtonBgRipple; } } attentionLeftOutlineButton: OutlineButton(defaultLeftOutlineButton) { outlineFgOver: attentionButtonFg; textBgOver: attentionButtonBgOver; textFg: attentionButtonFg; textFgOver: attentionButtonFgOver; ripple: RippleAnimation(defaultRippleAnimation) { color: attentionButtonBgRipple; } } defaultInputField: InputField { textBg: windowBg; textFg: windowFg; textMargins: margins(0px, 26px, 0px, 4px); textAlign: align(topleft); placeholderFg: windowSubTextFg; placeholderFgActive: windowActiveTextFg; placeholderFgError: attentionButtonFg; placeholderMargins: margins(0px, 0px, 0px, 0px); placeholderAlign: align(topleft); placeholderScale: 0.9; placeholderShift: -20px; placeholderFont: font(semibold 14px); duration: 150; borderFg: inputBorderFg; borderFgActive: activeLineFg; borderFgError: activeLineFgError; border: 1px; borderActive: 2px; font: boxTextFont; heightMin: 52px; heightMax: 148px; } defaultCheckboxIcon: icon {{ "default_checkbox_check", windowFgActive, point(4px, 7px) }}; defaultCheck: Check { bg: transparent; untoggledFg: checkboxFg; toggledFg: windowBgActive; diameter: 22px; thickness: 2px; icon: defaultCheckboxIcon; duration: 120; rippleAreaPadding: 8px; } defaultRadio: Radio { bg: transparent; untoggledFg: checkboxFg; toggledFg: windowBgActive; diameter: 22px; thickness: 2px; skip: 65px; // * 0.1 duration: 120; rippleAreaPadding: 8px; } defaultToggle: Toggle { toggledBg: windowBg; toggledFg: windowBgActive; untoggledBg: windowBg; untoggledFg: checkboxFg; duration: 120; border: 2px; shift: 1px; diameter: 20px; width: 16px; xsize: 0px; vsize: 0px; vshift: 0px; stroke: 0px; rippleAreaPadding: 8px; } defaultCheckbox: Checkbox { textFg: windowFg; width: -44px; margin: margins(8px, 8px, 8px, 8px); textPosition: point(10px, 2px); checkPosition: point(8px, 8px); style: defaultTextStyle; rippleAreaPosition: point(-8px, -8px); rippleBg: windowBgOver; rippleBgActive: lightButtonBgOver; ripple: defaultRippleAnimation; disabledOpacity: 0.5; } defaultIconButton: IconButton { iconPosition: point(-1px, -1px); } defaultMultiSelectItem: MultiSelectItem { padding: margins(6px, 7px, 12px, 0px); maxWidth: 128px; height: 32px; style: defaultTextStyle; textBg: contactsBgOver; textFg: windowFg; textActiveBg: activeButtonBg; textActiveFg: activeButtonFg; deleteFg: activeButtonFg; deleteCross: CrossAnimation { size: 32px; skip: 10px; stroke: 2px; minScale: 0.3; } duration: 150; minScale: 0.3; } widgetSlideDuration: 200; widgetFadeDuration: 200; fieldSearchIcon: icon {{ "box_search", menuIconFg, point(9px, 8px) }}; boxFieldSearchIcon: icon {{ "box_search", menuIconFg, point(10px, 9px) }}; SettingsSlider { height: pixels; barTop: pixels; barSkip: pixels; barStroke: pixels; barFg: color; barFgActive: color; labelTop: pixels; labelFont: font; labelFg: color; labelFgActive: color; duration: int; rippleBottomSkip: pixels; rippleBg: color; rippleBgActive: color; ripple: RippleAnimation; } defaultSettingsSlider: SettingsSlider { height: 39px; barTop: 5px; barSkip: 3px; barStroke: 3px; barFg: sliderBgInactive; barFgActive: sliderBgActive; labelTop: 17px; labelFont: normalFont; labelFg: windowActiveTextFg; labelFgActive: windowActiveTextFg; duration: 150; } defaultTabsSlider: SettingsSlider(defaultSettingsSlider) { height: 53px; barTop: 50px; barSkip: 0px; barFg: transparent; labelTop: 19px; labelFont: semiboldFont; labelFg: windowSubTextFg; labelFgActive: lightButtonFg; rippleBottomSkip: 1px; rippleBg: windowBgOver; rippleBgActive: lightButtonBgOver; ripple: defaultRippleAnimation; } defaultRoundShadow: Shadow { left: icon {{ "round_shadow_left", windowShadowFg }}; topLeft: icon {{ "round_shadow_top_left", windowShadowFg }}; top: icon {{ "round_shadow_top", windowShadowFg }}; topRight: icon {{ "round_shadow_top_left-flip_horizontal", windowShadowFg }}; right: icon {{ "round_shadow_left-flip_horizontal", windowShadowFg }}; bottomRight: icon {{ "round_shadow_bottom_left-flip_horizontal", windowShadowFg }}; bottom: icon {{ "round_shadow_bottom", windowShadowFg }}; bottomLeft: icon {{ "round_shadow_bottom_left", windowShadowFg }}; extend: margins(3px, 2px, 3px, 4px); fallback: windowShadowFgFallback; } defaultEmptyShadow: Shadow { fallback: windowBg; } defaultPanelAnimation: PanelAnimation { startWidth: 0.5; widthDuration: 0.6; startHeight: 0.3; heightDuration: 0.9; startOpacity: 0.2; opacityDuration: 0.3; startFadeTop: 0.; fadeHeight: 0.2; fadeOpacity: 1.0; fadeBg: menuBg; shadow: defaultRoundShadow; } defaultRoundCheckbox: RoundCheckbox { border: windowBg; bgActive: windowBgActive; width: 2px; duration: 160; bgDuration: 0.75; fgDuration: 1.; } defaultMenuArrow: icon {{ "dropdown_submenu_arrow", menuSubmenuArrowFg }}; defaultMenuToggle: Toggle(defaultToggle) { untoggledFg: menuIconFg; } defaultMenuToggleOver: Toggle(defaultToggle) { untoggledFg: menuIconFgOver; } defaultMenu: Menu { skip: 0px; itemBg: windowBg; itemBgOver: windowBgOver; itemFg: windowFg; itemFgOver: windowFgOver; itemFgDisabled: menuFgDisabled; itemFgShortcut: windowSubTextFg; itemFgShortcutOver: windowSubTextFgOver; itemFgShortcutDisabled: menuFgDisabled; itemIconPosition: point(0px, 0px); itemPadding: margins(17px, 8px, 17px, 7px); itemFont: normalFont; itemToggle: defaultMenuToggle; itemToggleOver: defaultMenuToggleOver; itemToggleShift: 0px; separatorPadding: margins(0px, 5px, 0px, 5px); separatorWidth: 1px; separatorFg: menuSeparatorFg; arrow: defaultMenuArrow; widthMin: 180px; widthMax: 300px; ripple: defaultRippleAnimation; } defaultPopupMenu: PopupMenu { shadow: defaultRoundShadow; animation: defaultPanelAnimation; scrollPadding: margins(0px, 8px, 0px, 8px); menu: defaultMenu; duration: 150; showDuration: 200; } defaultInnerDropdown: InnerDropdown { padding: margins(10px, 10px, 10px, 10px); shadow: defaultRoundShadow; animation: defaultPanelAnimation; duration: 150; showDuration: 200; bg: menuBg; scroll: defaultSolidScroll; } defaultDropdownMenu: DropdownMenu { wrap: InnerDropdown(defaultInnerDropdown) { scrollPadding: margins(0px, 8px, 0px, 8px); } menu: defaultMenu; } defaultTooltip: Tooltip { textBg: tooltipBg; textFg: tooltipFg; textStyle: defaultTextStyle; textBorder: tooltipBorderFg; textPadding: margins(5px, 2px, 5px, 2px); shift: point(-20px, 20px); skip: 10px; widthMax: 800px; linesMax: 12; } defaultImportantTooltip: ImportantTooltip { bg: importantTooltipBg; margin: margins(4px, 4px, 4px, 4px); padding: margins(13px, 9px, 13px, 10px); radius: 6px; arrow: 9px; arrowSkipMin: 24px; arrowSkip: 66px; shift: 12px; duration: 200; } defaultImportantTooltipLabel: FlatLabel(defaultFlatLabel) { style: TextStyle(defaultTextStyle) { font: font(14px); linkFont: font(14px); linkFontOver: font(14px underline); } textFg: importantTooltipFg; palette: TextPalette(defaultTextPalette) { linkFg: importantTooltipFgLink; selectLinkFg: importantTooltipFgLink; } } defaultChangeUserpicIcon: icon {{ "new_chat_photo", activeButtonFg }}; defaultUploadUserpicIcon: icon {{ "upload_chat_photo", msgDateImgFg }}; defaultUserpicButton: UserpicButton { size: size(76px, 76px); photoSize: 76px; photoPosition: point(-1px, -1px); changeButton: defaultActiveButton; changeIcon: defaultChangeUserpicIcon; changeIconPosition: point(23px, 25px); duration: 500; uploadHeight: 24px; uploadBg: msgDateImgBgOver; uploadIcon: defaultUploadUserpicIcon; uploadIconPosition: point(-1px, 1px); } defaultFeedUserpicButton: FeedUserpicButton { size: size(76px, 76px); innerSize: 76px; innerPosition: point(-1px, -1px); innerPart: defaultUserpicButton; } historyToDownBelow: icon { { "history_down_shadow", historyToDownShadow }, { "history_down_circle", historyToDownBg, point(4px, 4px) }, }; historyToDownBelowOver: icon { { "history_down_shadow", historyToDownShadow }, { "history_down_circle", historyToDownBgOver, point(4px, 4px) }, }; contactsAddIconBelow: icon { { "history_down_shadow", historyToDownShadow }, { "history_down_circle", activeButtonBg, point(4px, 4px) }, }; contactsAddIconBelowOver: icon { { "history_down_shadow", historyToDownShadow }, { "history_down_circle", activeButtonBgOver, point(4px, 4px) }, }; BotKeyboardButton { margin: pixels; padding: pixels; height: pixels; textTop: pixels; ripple: RippleAnimation; } TwoIconButton { width: pixels; height: pixels; iconBelow: icon; iconAbove: icon; iconBelowOver: icon; iconAboveOver: icon; iconPosition: point; rippleAreaPosition: point; rippleAreaSize: pixels; ripple: RippleAnimation; } historySendActionTypingDuration: 800; historySendActionTypingHalfPeriod: 320; historySendActionTypingDeltaTime: 150; historySendActionTypingPosition: point(4px, -4px); historySendActionTypingDelta: 6px; historySendActionTypingLargeNumerator: 28px; historySendActionTypingSmallNumerator: 16px; historySendActionTypingDenominator: 12.; historySendActionRecordDuration: 500; historySendActionRecordPosition: point(1px, -4px); historySendActionRecordDelta: 4px; historySendActionRecordStrokeNumerator: 16px; historySendActionRecordDenominator: 8.; historySendActionUploadDuration: 500; historySendActionUploadPosition: point(0px, -4px); historySendActionUploadDelta: 5px; historySendActionUploadStrokeNumerator: 16px; historySendActionUploadSizeNumerator: 32px; historySendActionUploadDenominator: 8.; MediaPlayerButton { playPosition: point; playOuter: size; pausePosition: point; pauseOuter: size; pauseStroke: pixels; cancelPosition: point; cancelOuter: size; cancelStroke: pixels; rippleAreaPosition: point; rippleAreaSize: pixels; ripple: RippleAnimation; } PeerListItem { left: pixels; bottom: pixels; height: pixels; photoPosition: point; namePosition: point; nameStyle: TextStyle; statusPosition: point; photoSize: pixels; maximalWidth: pixels; button: OutlineButton; statusFg: color; statusFgOver: color; statusFgActive: color; } PeerList { padding: margins; item: PeerListItem; } defaultPeerListButton: OutlineButton { outlineWidth: 0px; textBg: windowBg; textBgOver: windowBgOver; textFg: windowSubTextFg; textFgOver: windowSubTextFgOver; font: normalFont; padding: margins(11px, 5px, 11px, 5px); ripple: defaultRippleAnimation; } defaultPeerListItem: PeerListItem { height: 58px; photoPosition: point(12px, 6px); namePosition: point(68px, 11px); nameStyle: TextStyle(defaultTextStyle) { font: semiboldFont; linkFont: semiboldFont; linkFontOver: semiboldFont; } statusPosition: point(68px, 31px); photoSize: 46px; button: defaultPeerListButton; statusFg: windowSubTextFg; statusFgOver: windowSubTextFgOver; statusFgActive: windowActiveTextFg; } defaultPeerList: PeerList { padding: margins(0px, 0px, 0px, 0px); item: defaultPeerListItem; } SearchFieldRow { height: pixels; padding: margins; field: InputField; fieldIcon: icon; fieldIconSkip: pixels; fieldCancel: CrossButton; fieldCancelSkip: pixels; } InfoTopBar { height: pixels; back: IconButton; title: FlatLabel; titlePosition: point; bg: color; mediaCancel: IconButton; mediaActionsSkip: pixels; mediaForward: IconButton; mediaDelete: IconButton; search: IconButton; searchRow: SearchFieldRow; highlightBg: color; highlightDuration: int; } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/wrap/�������������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0021011�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/wrap/fade_wrap.cpp������������������������������������������0000664�0000000�0000000�00000003426�13262451251�0023452�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/wrap/fade_wrap.h" #include "ui/widgets/shadow.h" namespace Ui { FadeWrap<RpWidget>::FadeWrap( QWidget *parent, object_ptr<RpWidget> &&child, float64 scale) : Parent(parent, std::move(child)) , _animation(this, scale) , _duration(st::fadeWrapDuration) { if (auto weak = wrapped()) { weak->show(); } } FadeWrap<RpWidget> *FadeWrap<RpWidget>::setDuration(int duration) { _duration = duration; return this; } FadeWrap<RpWidget> *FadeWrap<RpWidget>::toggle( bool shown, anim::type animated) { auto changed = (shown != _animation.visible()); if (shown) { if (animated == anim::type::normal) { _animation.fadeIn(_duration); } else { _animation.show(); } } else { if (animated == anim::type::normal) { _animation.fadeOut(_duration); } else { _animation.hide(); } } if (changed) { _toggledChanged.fire_copy(shown); } return this; } FadeWrap<RpWidget> *FadeWrap<RpWidget>::finishAnimating() { _animation.finish(); return this; } FadeWrap<RpWidget> *FadeWrap<RpWidget>::toggleOn( rpl::producer<bool> &&shown) { std::move( shown ) | rpl::start_with_next([this](bool shown) { toggle(shown, anim::type::normal); }, lifetime()); finishAnimating(); return this; } void FadeWrap<RpWidget>::paintEvent(QPaintEvent *e) { Painter p(this); _animation.paint(p); } FadeShadow::FadeShadow(QWidget *parent) : FadeShadow(parent, st::shadowFg) { } FadeShadow::FadeShadow(QWidget *parent, style::color color) : Parent(parent, object_ptr<PlainShadow>(parent, color)) { hide(anim::type::instant); } } // namespace Ui ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/wrap/fade_wrap.h��������������������������������������������0000664�0000000�0000000�00000005250�13262451251�0023114�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/wrap/wrap.h" #include "ui/effects/fade_animation.h" namespace Ui { template <typename Widget = RpWidget> class FadeWrapScaled; template <typename Widget = RpWidget> class FadeWrap; template <> class FadeWrap<RpWidget> : public Wrap<RpWidget> { using Parent = Wrap<RpWidget>; public: FadeWrap( QWidget *parent, object_ptr<RpWidget> &&child, float64 scale = 1.); FadeWrap *setDuration(int duration); FadeWrap *toggle(bool shown, anim::type animated); FadeWrap *show(anim::type animated) { return toggle(true, animated); } FadeWrap *hide(anim::type animated) { return toggle(false, animated); } FadeWrap *finishAnimating(); FadeWrap *toggleOn(rpl::producer<bool> &&shown); bool animating() const { return _animation.animating(); } bool toggled() const { return _animation.visible(); } auto toggledValue() const { return _toggledChanged.events_starting_with( _animation.visible()); } protected: void paintEvent(QPaintEvent *e) final override; private: rpl::event_stream<bool> _toggledChanged; FadeAnimation _animation; int _duration = 0; }; template <typename Widget> class FadeWrap : public Wrap<Widget, FadeWrap<RpWidget>> { using Parent = Wrap<Widget, FadeWrap<RpWidget>>; public: FadeWrap( QWidget *parent, object_ptr<Widget> &&child, float64 scale = 1.) : Parent(parent, std::move(child), scale) { } FadeWrap *setDuration(int duration) { return chain(Parent::setDuration(duration)); } FadeWrap *toggle(bool shown, anim::type animated) { return chain(Parent::toggle(shown, animated)); } FadeWrap *show(anim::type animated) { return chain(Parent::show(animated)); } FadeWrap *hide(anim::type animated) { return chain(Parent::hide(animated)); } FadeWrap *finishAnimating() { return chain(Parent::finishAnimating()); } FadeWrap *toggleOn(rpl::producer<bool> &&shown) { return chain(Parent::toggleOn(std::move(shown))); } private: FadeWrap *chain(FadeWrap<RpWidget> *result) { return static_cast<FadeWrap*>(result); } }; template <typename Widget> class FadeWrapScaled : public FadeWrap<Widget> { using Parent = FadeWrap<Widget>; public: FadeWrapScaled(QWidget *parent, object_ptr<Widget> &&child) : Parent(parent, std::move(child), 0.) { } }; class PlainShadow; class FadeShadow : public FadeWrap<PlainShadow> { using Parent = FadeWrap<PlainShadow>; public: FadeShadow(QWidget *parent); FadeShadow(QWidget *parent, style::color color); }; } // namespace Ui ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/wrap/padding_wrap.cpp���������������������������������������0000664�0000000�0000000�00000003260�13262451251�0024155�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/wrap/padding_wrap.h" namespace Ui { PaddingWrap<RpWidget>::PaddingWrap( QWidget *parent, object_ptr<RpWidget> &&child, const style::margins &padding) : Parent(parent, std::move(child)) { setPadding(padding); } void PaddingWrap<RpWidget>::setPadding(const style::margins &padding) { if (_padding != padding) { auto oldWidth = width() - _padding.left() - _padding.top(); _padding = padding; if (auto weak = wrapped()) { wrappedSizeUpdated(weak->size()); auto margins = weak->getMargins(); weak->moveToLeft( _padding.left() + margins.left(), _padding.top() + margins.top()); } else { resize(QSize( _padding.left() + oldWidth + _padding.right(), _padding.top() + _padding.bottom())); } } } void PaddingWrap<RpWidget>::wrappedSizeUpdated(QSize size) { resize(QRect(QPoint(), size).marginsAdded(_padding).size()); } int PaddingWrap<RpWidget>::naturalWidth() const { auto inner = [this] { if (auto weak = wrapped()) { return weak->naturalWidth(); } return RpWidget::naturalWidth(); }(); return (inner < 0) ? inner : (_padding.left() + inner + _padding.right()); } int PaddingWrap<RpWidget>::resizeGetHeight(int newWidth) { if (auto weak = wrapped()) { weak->resizeToWidth(newWidth - _padding.left() - _padding.right()); } else { resize(QSize( _padding.left() + newWidth + _padding.right(), _padding.top() + _padding.bottom())); } return heightNoMargins(); } } // namespace Ui ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/wrap/padding_wrap.h�����������������������������������������0000664�0000000�0000000�00000002706�13262451251�0023626�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/wrap/wrap.h" namespace Ui { template <typename Widget = RpWidget> class PaddingWrap; template <> class PaddingWrap<RpWidget> : public Wrap<RpWidget> { using Parent = Wrap<RpWidget>; public: PaddingWrap( QWidget *parent, object_ptr<RpWidget> &&child, const style::margins &padding); style::margins padding() const { return _padding; } void setPadding(const style::margins &padding); int naturalWidth() const override; protected: int resizeGetHeight(int newWidth) override; void wrappedSizeUpdated(QSize size) override; private: style::margins _padding; }; template <typename Widget> class PaddingWrap : public Wrap<Widget, PaddingWrap<RpWidget>> { using Parent = Wrap<Widget, PaddingWrap<RpWidget>>; public: PaddingWrap( QWidget *parent, object_ptr<Widget> &&child, const style::margins &padding) : Parent(parent, std::move(child), padding) { } }; class FixedHeightWidget : public RpWidget { public: FixedHeightWidget(QWidget *parent, int height) : RpWidget(parent) { resize(width(), height); } }; inline object_ptr<FixedHeightWidget> CreateSkipWidget( QWidget *parent, int skip) { return object_ptr<FixedHeightWidget>( parent, skip); } } // namespace Ui ����������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/wrap/slide_wrap.cpp�����������������������������������������0000664�0000000�0000000�00000006721�13262451251�0023654�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/wrap/slide_wrap.h" #include <rpl/combine.h> #include <range/v3/algorithm/find.hpp> namespace Ui { SlideWrap<RpWidget>::SlideWrap( QWidget *parent, object_ptr<RpWidget> &&child) : SlideWrap( parent, std::move(child), style::margins()) { } SlideWrap<RpWidget>::SlideWrap( QWidget *parent, const style::margins &padding) : SlideWrap(parent, nullptr, padding) { } SlideWrap<RpWidget>::SlideWrap( QWidget *parent, object_ptr<RpWidget> &&child, const style::margins &padding) : Parent( parent, object_ptr<PaddingWrap<RpWidget>>( parent, std::move(child), padding)) , _duration(st::slideWrapDuration) { } SlideWrap<RpWidget> *SlideWrap<RpWidget>::setDuration(int duration) { _duration = duration; return this; } SlideWrap<RpWidget> *SlideWrap<RpWidget>::toggle( bool shown, anim::type animated) { auto animate = (animated == anim::type::normal) && _duration; auto changed = (_toggled != shown); if (changed) { _toggled = shown; if (animate) { _animation.start( [this] { animationStep(); }, _toggled ? 0. : 1., _toggled ? 1. : 0., _duration, anim::linear); } } if (animate) { animationStep(); } else { finishAnimating(); } if (changed) { _toggledChanged.fire_copy(_toggled); } return this; } SlideWrap<RpWidget> *SlideWrap<RpWidget>::finishAnimating() { _animation.finish(); animationStep(); return this; } SlideWrap<RpWidget> *SlideWrap<RpWidget>::toggleOn( rpl::producer<bool> &&shown) { std::move( shown ) | rpl::start_with_next([this](bool shown) { toggle(shown, anim::type::normal); }, lifetime()); finishAnimating(); return this; } void SlideWrap<RpWidget>::animationStep() { auto newWidth = width(); if (auto weak = wrapped()) { auto margins = getMargins(); weak->moveToLeft(margins.left(), margins.top()); newWidth = weak->width(); } auto current = _animation.current(_toggled ? 1. : 0.); auto newHeight = wrapped() ? (_animation.animating() ? anim::interpolate(0, wrapped()->heightNoMargins(), current) : (_toggled ? wrapped()->height() : 0)) : 0; if (newWidth != width() || newHeight != height()) { resize(newWidth, newHeight); } auto shouldBeHidden = !_toggled && !_animation.animating(); if (shouldBeHidden != isHidden()) { const auto guard = make_weak(this); setVisible(!shouldBeHidden); if (shouldBeHidden && guard) { SendPendingMoveResizeEvents(this); } } } QMargins SlideWrap<RpWidget>::getMargins() const { auto result = wrapped()->getMargins(); return (animating() || !_toggled) ? QMargins(result.left(), 0, result.right(), 0) : result; } int SlideWrap<RpWidget>::resizeGetHeight(int newWidth) { if (wrapped()) { wrapped()->resizeToWidth(newWidth); } return heightNoMargins(); } void SlideWrap<RpWidget>::wrappedSizeUpdated(QSize size) { if (_animation.animating()) { animationStep(); } else if (_toggled) { resize(size); } } rpl::producer<bool> MultiSlideTracker::atLeastOneShownValue() const { auto shown = std::vector<rpl::producer<bool>>(); shown.reserve(_widgets.size()); for (auto &widget : _widgets) { shown.push_back(widget->toggledValue()); } return rpl::combine( std::move(shown), [](const std::vector<bool> &values) { return ranges::find(values, true) != values.end(); }); } } // namespace Ui �����������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/wrap/slide_wrap.h�������������������������������������������0000664�0000000�0000000�00000006131�13262451251�0023314�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/wrap/padding_wrap.h" namespace Ui { template <typename Widget = RpWidget> class SlideWrap; template <> class SlideWrap<RpWidget> : public Wrap<PaddingWrap<RpWidget>> { using Parent = Wrap<PaddingWrap<RpWidget>>; public: SlideWrap( QWidget *parent, object_ptr<RpWidget> &&child); SlideWrap( QWidget *parent, const style::margins &padding); SlideWrap( QWidget *parent, object_ptr<RpWidget> &&child, const style::margins &padding); SlideWrap *setDuration(int duration); SlideWrap *toggle(bool shown, anim::type animated); SlideWrap *show(anim::type animated) { return toggle(true, animated); } SlideWrap *hide(anim::type animated) { return toggle(false, animated); } SlideWrap *finishAnimating(); SlideWrap *toggleOn(rpl::producer<bool> &&shown); bool animating() const { return _animation.animating(); } bool toggled() const { return _toggled; } auto toggledValue() const { return _toggledChanged.events_starting_with_copy(_toggled); } QMargins getMargins() const override; protected: int resizeGetHeight(int newWidth) override; void wrappedSizeUpdated(QSize size) override; private: void animationStep(); bool _toggled = true; rpl::event_stream<bool> _toggledChanged; Animation _animation; int _duration = 0; }; template <typename Widget> class SlideWrap : public Wrap<PaddingWrap<Widget>, SlideWrap<RpWidget>> { using Parent = Wrap<PaddingWrap<Widget>, SlideWrap<RpWidget>>; public: SlideWrap( QWidget *parent, object_ptr<Widget> &&child) : Parent(parent, std::move(child)) { } SlideWrap( QWidget *parent, const style::margins &padding) : Parent(parent, padding) { } SlideWrap( QWidget *parent, object_ptr<Widget> &&child, const style::margins &padding) : Parent(parent, std::move(child), padding) { } SlideWrap *setDuration(int duration) { return chain(Parent::setDuration(duration)); } SlideWrap *toggle(bool shown, anim::type animated) { return chain(Parent::toggle(shown, animated)); } SlideWrap *show(anim::type animated) { return chain(Parent::show(animated)); } SlideWrap *hide(anim::type animated) { return chain(Parent::hide(animated)); } SlideWrap *finishAnimating() { return chain(Parent::finishAnimating()); } SlideWrap *toggleOn(rpl::producer<bool> &&shown) { return chain(Parent::toggleOn(std::move(shown))); } private: SlideWrap *chain(SlideWrap<RpWidget> *result) { return static_cast<SlideWrap*>(result); } }; inline object_ptr<SlideWrap<>> CreateSlideSkipWidget( QWidget *parent, int skip) { return object_ptr<SlideWrap<>>( parent, QMargins(0, 0, 0, skip)); } class MultiSlideTracker { public: template <typename Widget> void track(const Ui::SlideWrap<Widget> *wrap) { _widgets.push_back(wrap); } rpl::producer<bool> atLeastOneShownValue() const; private: std::vector<const Ui::SlideWrap<Ui::RpWidget>*> _widgets; }; } // namespace Ui ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/wrap/vertical_layout.cpp������������������������������������0000664�0000000�0000000�00000010375�13262451251�0024731�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/wrap/vertical_layout.h" namespace Ui { QMargins VerticalLayout::getMargins() const { auto result = QMargins(); if (!_rows.empty()) { auto &top = _rows.front(); auto topMargin = top.widget->getMargins().top(); result.setTop( qMax(topMargin - top.margin.top(), 0)); auto &bottom = _rows.back(); auto bottomMargin = bottom.widget->getMargins().bottom(); result.setBottom( qMax(bottomMargin - bottom.margin.bottom(), 0)); for (auto &row : _rows) { auto margins = row.widget->getMargins(); result.setLeft(qMax( margins.left() - row.margin.left(), result.left())); result.setRight(qMax( margins.right() - row.margin.right(), result.right())); } } return result; } int VerticalLayout::naturalWidth() const { auto result = 0; for (auto &row : _rows) { auto natural = row.widget->naturalWidth(); if (natural < 0) { return natural; } accumulate_max(result, natural); } return result; } int VerticalLayout::resizeGetHeight(int newWidth) { _inResize = true; auto guard = gsl::finally([&] { _inResize = false; }); auto margins = getMargins(); auto result = 0; for (auto &row : _rows) { updateChildGeometry( margins, row.widget, row.margin, newWidth, result); result += row.margin.top() + row.widget->heightNoMargins() + row.margin.bottom(); } return result - margins.bottom(); } void VerticalLayout::visibleTopBottomUpdated( int visibleTop, int visibleBottom) { for (auto &row : _rows) { setChildVisibleTopBottom( row.widget, visibleTop, visibleBottom); } } void VerticalLayout::updateChildGeometry( const style::margins &margins, RpWidget *child, const style::margins &margin, int width, int top) const { auto availRowWidth = width - margin.left() - margin.right(); child->resizeToNaturalWidth(availRowWidth); child->moveToLeft( margins.left() + margin.left(), margins.top() + margin.top() + top, width); } RpWidget *VerticalLayout::addChild( object_ptr<RpWidget> child, const style::margins &margin) { if (auto weak = AttachParentChild(this, child)) { _rows.push_back({ std::move(child), margin }); auto margins = getMargins(); updateChildGeometry( margins, weak, margin, width() - margins.left() - margins.right(), height() - margins.top() - margins.bottom()); weak->heightValue( ) | rpl::start_with_next_done([this, weak] { if (!_inResize) { childHeightUpdated(weak); } }, [this, weak] { removeChild(weak); }, lifetime()); return weak; } return nullptr; } void VerticalLayout::childHeightUpdated(RpWidget *child) { auto it = ranges::find_if(_rows, [child](const Row &row) { return (row.widget == child); }); auto margins = getMargins(); auto top = [&] { if (it == _rows.begin()) { return margins.top(); } auto prev = it - 1; return prev->widget->bottomNoMargins() + prev->margin.bottom(); }() - margins.top(); for (auto end = _rows.end(); it != end; ++it) { auto &row = *it; auto margin = row.margin; auto widget = row.widget.data(); widget->moveToLeft( margins.left() + margin.left(), margins.top() + top + margin.top()); top += margin.top() + widget->heightNoMargins() + margin.bottom(); } resize(width(), margins.top() + top + margins.bottom()); } void VerticalLayout::removeChild(RpWidget *child) { auto it = ranges::find_if(_rows, [child](const Row &row) { return (row.widget == child); }); auto end = _rows.end(); Assert(it != end); auto margins = getMargins(); auto top = [&] { if (it == _rows.begin()) { return margins.top(); } auto prev = it - 1; return prev->widget->bottomNoMargins() + prev->margin.bottom(); }() - margins.top(); for (auto next = it + 1; it != end; ++it) { auto &row = *it; auto margin = row.margin; auto widget = row.widget.data(); widget->moveToLeft( margins.left() + margin.left(), margins.top() + top + margin.top()); top += margin.top() + widget->heightNoMargins() + margin.bottom(); } _rows.erase(it); resize(width(), margins.top() + top + margins.bottom()); } } // namespace Ui �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/wrap/vertical_layout.h��������������������������������������0000664�0000000�0000000�00000002461�13262451251�0024373�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/rp_widget.h" namespace Ui { class VerticalLayout : public RpWidget { public: using RpWidget::RpWidget; template < typename Widget, typename = std::enable_if_t< std::is_base_of_v<RpWidget, Widget>>> Widget *add( object_ptr<Widget> &&child, const style::margins &margin = style::margins()) { return static_cast<Widget*>(addChild( std::move(child), margin)); } QMargins getMargins() const override; int naturalWidth() const override; protected: int resizeGetHeight(int newWidth) override; void visibleTopBottomUpdated( int visibleTop, int visibleBottom) override; private: RpWidget *addChild( object_ptr<RpWidget> child, const style::margins &margin); void childHeightUpdated(RpWidget *child); void removeChild(RpWidget *child); void updateChildGeometry( const style::margins &margins, RpWidget *child, const style::margins &margin, int width, int top) const; struct Row { object_ptr<RpWidget> widget; style::margins margin; }; std::vector<Row> _rows; bool _inResize = false; }; } // namespace Ui ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/ui/wrap/wrap.h�������������������������������������������������0000664�0000000�0000000�00000007650�13262451251�0022143�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/rp_widget.h" namespace Ui { template <typename Widget, typename ParentType = RpWidget> class Wrap; namespace details { struct UnwrapHelper { template < typename Widget, typename = typename std::decay_t<Widget>::WrapParentType> static std::true_type Is(Widget &&widget); static std::false_type Is(...); template <typename Entity> static auto Unwrap(Entity *entity, std::true_type) { return entity ? entity->entity() : nullptr; } template <typename Entity> static Entity *Unwrap(Entity *entity, std::false_type) { return entity; } template <typename Entity> static auto Unwrap(Entity *entity) { using Selector = decltype(Is(std::declval<Entity>())); return Unwrap( entity, Selector()); } }; } // namespace details template <typename Widget> class Wrap<Widget, RpWidget> : public RpWidget { public: Wrap(QWidget *parent, object_ptr<Widget> &&child); Widget *wrapped() { return _wrapped; } const Widget *wrapped() const { return _wrapped; } auto entity() { return details::UnwrapHelper::Unwrap(wrapped()); } auto entity() const { return details::UnwrapHelper::Unwrap(wrapped()); } QMargins getMargins() const override { if (auto weak = wrapped()) { return weak->getMargins(); } return RpWidget::getMargins(); } int naturalWidth() const override { if (auto weak = wrapped()) { return weak->naturalWidth(); } return RpWidget::naturalWidth(); } protected: int resizeGetHeight(int newWidth) override { if (auto weak = wrapped()) { weak->resizeToWidth(newWidth); return weak->heightNoMargins(); } return heightNoMargins(); } void visibleTopBottomUpdated( int visibleTop, int visibleBottom) override { setChildVisibleTopBottom( wrapped(), visibleTop, visibleBottom); } virtual void wrappedSizeUpdated(QSize size) { resize(size); } private: object_ptr<Widget> _wrapped; }; template <typename Widget> Wrap<Widget, RpWidget>::Wrap( QWidget *parent, object_ptr<Widget> &&child) : RpWidget(parent) , _wrapped(std::move(child)) { if (_wrapped) { _wrapped->sizeValue( ) | rpl::start_with_next([this](const QSize &value) { wrappedSizeUpdated(value); }, lifetime()); AttachParentChild(this, _wrapped); _wrapped->move(0, 0); _wrapped->alive( ) | rpl::start_with_done([this] { _wrapped->setParent(nullptr); _wrapped = nullptr; delete this; }, lifetime()); } } template <typename Widget, typename ParentType> class Wrap : public ParentType { public: using ParentType::ParentType; Widget *wrapped() { return static_cast<Widget*>(ParentType::wrapped()); } const Widget *wrapped() const { return static_cast<const Widget*>(ParentType::wrapped()); } auto entity() { return details::UnwrapHelper::Unwrap(wrapped()); } auto entity() const { return details::UnwrapHelper::Unwrap(wrapped()); } using WrapParentType = ParentType; }; class IgnoreMargins : public Wrap<RpWidget> { using Parent = Wrap<RpWidget>; public: IgnoreMargins(QWidget *parent, object_ptr<RpWidget> &&child) : Parent(parent, std::move(child)) { if (auto weak = wrapped()) { auto margins = weak->getMargins(); resizeToWidth(weak->width() - margins.left() - margins.right()); } } QMargins getMargins() const override { return QMargins(); } protected: int resizeGetHeight(int newWidth) override { if (auto weak = wrapped()) { weak->resizeToWidth(newWidth); weak->moveToLeft(0, 0); return weak->heightNoMargins(); } return height(); } private: void wrappedSizeUpdated(QSize size) override { auto margins = wrapped()->getMargins(); resize( size.width() - margins.left() - margins.right(), size.height() - margins.top() - margins.bottom()); } }; } // namespace Ui ����������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/window/��������������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0020732�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/window/layer_widget.cpp����������������������������������������0000664�0000000�0000000�00000071320�13262451251�0024120�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "window/layer_widget.h" #include "lang/lang_keys.h" #include "data/data_photo.h" #include "data/data_document.h" #include "media/media_clip_reader.h" #include "boxes/abstract_box.h" #include "application.h" #include "mainwindow.h" #include "mainwidget.h" #include "core/file_utilities.h" #include "styles/style_boxes.h" #include "styles/style_widgets.h" #include "styles/style_chat_helpers.h" #include "ui/widgets/shadow.h" #include "window/window_main_menu.h" #include "auth_session.h" #include "chat_helpers/stickers.h" #include "window/window_controller.h" namespace { constexpr int kStickerPreviewEmojiLimit = 10; } // namespace namespace Window { class LayerStackWidget::BackgroundWidget : public TWidget { public: BackgroundWidget(QWidget *parent) : TWidget(parent) { } void setDoneCallback(base::lambda<void()> callback) { _doneCallback = std::move(callback); } void setLayerBoxes(const QRect &specialLayerBox, const QRect &layerBox); void setCacheImages(QPixmap &&bodyCache, QPixmap &&mainMenuCache, QPixmap &&specialLayerCache, QPixmap &&layerCache); void startAnimation(Action action); void skipAnimation(Action action); void finishAnimating(); bool animating() const { return _a_mainMenuShown.animating() || _a_specialLayerShown.animating() || _a_layerShown.animating(); } protected: void paintEvent(QPaintEvent *e) override; private: bool isShown() const { return _mainMenuShown || _specialLayerShown || _layerShown; } void checkIfDone(); void setMainMenuShown(bool shown); void setSpecialLayerShown(bool shown); void setLayerShown(bool shown); void checkWasShown(bool wasShown); void animationCallback(); QPixmap _bodyCache; QPixmap _mainMenuCache; int _mainMenuCacheWidth = 0; QPixmap _specialLayerCache; QPixmap _layerCache; base::lambda<void()> _doneCallback; bool _wasAnimating = false; bool _inPaintEvent = false; Animation _a_shown; Animation _a_mainMenuShown; Animation _a_specialLayerShown; Animation _a_layerShown; QRect _specialLayerBox, _specialLayerCacheBox; QRect _layerBox, _layerCacheBox; int _mainMenuRight = 0; bool _mainMenuShown = false; bool _specialLayerShown = false; bool _layerShown = false; }; void LayerStackWidget::BackgroundWidget::setCacheImages(QPixmap &&bodyCache, QPixmap &&mainMenuCache, QPixmap &&specialLayerCache, QPixmap &&layerCache) { _bodyCache = std::move(bodyCache); _mainMenuCache = std::move(mainMenuCache); _specialLayerCache = std::move(specialLayerCache); _layerCache = std::move(layerCache); _specialLayerCacheBox = _specialLayerBox; _layerCacheBox = _layerBox; setAttribute(Qt::WA_OpaquePaintEvent, !_bodyCache.isNull()); } void LayerStackWidget::BackgroundWidget::startAnimation(Action action) { if (action == Action::ShowMainMenu) { setMainMenuShown(true); } else if (action != Action::HideLayer && action != Action::HideSpecialLayer) { setMainMenuShown(false); } if (action == Action::ShowSpecialLayer) { setSpecialLayerShown(true); } else if (action == Action::ShowMainMenu || action == Action::HideAll || action == Action::HideSpecialLayer) { setSpecialLayerShown(false); } if (action == Action::ShowLayer) { setLayerShown(true); } else if (action != Action::ShowSpecialLayer && action != Action::HideSpecialLayer) { setLayerShown(false); } _wasAnimating = true; checkIfDone(); } void LayerStackWidget::BackgroundWidget::skipAnimation(Action action) { startAnimation(action); finishAnimating(); } void LayerStackWidget::BackgroundWidget::checkIfDone() { if (!_wasAnimating || _inPaintEvent || animating()) { return; } _wasAnimating = false; _bodyCache = _mainMenuCache = _specialLayerCache = _layerCache = QPixmap(); setAttribute(Qt::WA_OpaquePaintEvent, false); if (_doneCallback) { _doneCallback(); } } void LayerStackWidget::BackgroundWidget::setMainMenuShown(bool shown) { auto wasShown = isShown(); if (_mainMenuShown != shown) { _mainMenuShown = shown; _a_mainMenuShown.start([this] { animationCallback(); }, _mainMenuShown ? 0. : 1., _mainMenuShown ? 1. : 0., st::boxDuration, anim::easeOutCirc); } _mainMenuCacheWidth = (_mainMenuCache.width() / cIntRetinaFactor()) - st::boxRoundShadow.extend.right(); _mainMenuRight = _mainMenuShown ? _mainMenuCacheWidth : 0; checkWasShown(wasShown); } void LayerStackWidget::BackgroundWidget::setSpecialLayerShown(bool shown) { auto wasShown = isShown(); if (_specialLayerShown != shown) { _specialLayerShown = shown; _a_specialLayerShown.start([this] { animationCallback(); }, _specialLayerShown ? 0. : 1., _specialLayerShown ? 1. : 0., st::boxDuration); } checkWasShown(wasShown); } void LayerStackWidget::BackgroundWidget::setLayerShown(bool shown) { auto wasShown = isShown(); if (_layerShown != shown) { _layerShown = shown; _a_layerShown.start([this] { animationCallback(); }, _layerShown ? 0. : 1., _layerShown ? 1. : 0., st::boxDuration); } checkWasShown(wasShown); } void LayerStackWidget::BackgroundWidget::checkWasShown(bool wasShown) { if (isShown() != wasShown) { _a_shown.start([this] { animationCallback(); }, wasShown ? 1. : 0., wasShown ? 0. : 1., st::boxDuration, anim::easeOutCirc); } } void LayerStackWidget::BackgroundWidget::setLayerBoxes(const QRect &specialLayerBox, const QRect &layerBox) { _specialLayerBox = specialLayerBox; _layerBox = layerBox; update(); } void LayerStackWidget::BackgroundWidget::paintEvent(QPaintEvent *e) { Painter p(this); _inPaintEvent = true; auto guard = gsl::finally([this] { _inPaintEvent = false; checkIfDone(); }); if (!_bodyCache.isNull()) { p.drawPixmap(0, 0, _bodyCache); } auto specialLayerBox = _specialLayerCache.isNull() ? _specialLayerBox : _specialLayerCacheBox; auto layerBox = _layerCache.isNull() ? _layerBox : _layerCacheBox; auto ms = getms(); auto mainMenuProgress = _a_mainMenuShown.current(ms, -1); auto mainMenuRight = (_mainMenuCache.isNull() || mainMenuProgress < 0) ? _mainMenuRight : (mainMenuProgress < 0) ? _mainMenuRight : anim::interpolate(0, _mainMenuCacheWidth, mainMenuProgress); if (mainMenuRight) { // Move showing boxes to the right while main menu is hiding. if (!_specialLayerCache.isNull()) { specialLayerBox.moveLeft(specialLayerBox.left() + mainMenuRight / 2); } if (!_layerCache.isNull()) { layerBox.moveLeft(layerBox.left() + mainMenuRight / 2); } } auto bgOpacity = _a_shown.current(ms, isShown() ? 1. : 0.); auto specialLayerOpacity = _a_specialLayerShown.current(ms, _specialLayerShown ? 1. : 0.); auto layerOpacity = _a_layerShown.current(ms, _layerShown ? 1. : 0.); if (bgOpacity == 0.) { return; } p.setOpacity(bgOpacity); auto overSpecialOpacity = (layerOpacity * specialLayerOpacity); auto bg = myrtlrect(mainMenuRight, 0, width() - mainMenuRight, height()); if (_mainMenuCache.isNull() && mainMenuRight > 0) { // All cache images are taken together with their shadows, // so we paint shadow only when there is no cache. Ui::Shadow::paint(p, myrtlrect(0, 0, mainMenuRight, height()), width(), st::boxRoundShadow, RectPart::Right); } if (_specialLayerCache.isNull() && !specialLayerBox.isEmpty()) { // All cache images are taken together with their shadows, // so we paint shadow only when there is no cache. auto sides = RectPart::Left | RectPart::Right; auto topCorners = (specialLayerBox.y() > 0); auto bottomCorners = (specialLayerBox.y() + specialLayerBox.height() < height()); if (topCorners) { sides |= RectPart::Top; } if (bottomCorners) { sides |= RectPart::Bottom; } if (topCorners || bottomCorners) { p.setClipRegion(QRegion(rect()) - specialLayerBox.marginsRemoved(QMargins(st::boxRadius, 0, st::boxRadius, 0)) - specialLayerBox.marginsRemoved(QMargins(0, st::boxRadius, 0, st::boxRadius))); } Ui::Shadow::paint(p, specialLayerBox, width(), st::boxRoundShadow, sides); if (topCorners || bottomCorners) { // In case of painting the shadow above the special layer we get // glitches in the corners, so we need to paint the corners once more. p.setClipping(false); auto parts = (topCorners ? (RectPart::TopLeft | RectPart::TopRight) : RectPart::None) | (bottomCorners ? (RectPart::BottomLeft | RectPart::BottomRight) : RectPart::None); App::roundRect(p, specialLayerBox, st::boxBg, BoxCorners, nullptr, parts); } } if (!layerBox.isEmpty() && !_specialLayerCache.isNull() && overSpecialOpacity < bgOpacity) { // In case of moving special layer below the background while showing a box // we need to fill special layer rect below its cache with a complex opacity // (alpha_final - alpha_current) / (1 - alpha_current) so we won't get glitches // in the transparent special layer cache corners after filling special layer // rect above its cache with alpha_current opacity. auto region = QRegion(bg) - specialLayerBox; for (auto rect : region.rects()) { p.fillRect(rect, st::layerBg); } p.setOpacity((bgOpacity - overSpecialOpacity) / (1. - (overSpecialOpacity * st::layerBg->c.alphaF()))); p.fillRect(specialLayerBox, st::layerBg); p.setOpacity(bgOpacity); } else { p.fillRect(bg, st::layerBg); } if (!_specialLayerCache.isNull() && specialLayerOpacity > 0) { p.setOpacity(specialLayerOpacity); auto cacheLeft = specialLayerBox.x() - st::boxRoundShadow.extend.left(); auto cacheTop = specialLayerBox.y() - (specialLayerBox.y() > 0 ? st::boxRoundShadow.extend.top() : 0); p.drawPixmapLeft(cacheLeft, cacheTop, width(), _specialLayerCache); } if (!layerBox.isEmpty()) { if (!_specialLayerCache.isNull()) { p.setOpacity(overSpecialOpacity); p.fillRect(specialLayerBox, st::layerBg); } if (_layerCache.isNull()) { p.setOpacity(layerOpacity); Ui::Shadow::paint(p, layerBox, width(), st::boxRoundShadow); } } if (!_layerCache.isNull() && layerOpacity > 0) { p.setOpacity(layerOpacity); p.drawPixmapLeft(layerBox.topLeft() - QPoint(st::boxRoundShadow.extend.left(), st::boxRoundShadow.extend.top()), width(), _layerCache); } if (!_mainMenuCache.isNull() && mainMenuRight > 0) { p.setOpacity(1.); auto shownWidth = mainMenuRight + st::boxRoundShadow.extend.right(); auto sourceWidth = shownWidth * cIntRetinaFactor(); auto sourceRect = rtlrect(_mainMenuCache.width() - sourceWidth, 0, sourceWidth, _mainMenuCache.height(), _mainMenuCache.width()); p.drawPixmapLeft(0, 0, shownWidth, height(), width(), _mainMenuCache, sourceRect); } } void LayerStackWidget::BackgroundWidget::finishAnimating() { _a_shown.finish(); _a_mainMenuShown.finish(); _a_specialLayerShown.finish(); _a_layerShown.finish(); checkIfDone(); } void LayerStackWidget::BackgroundWidget::animationCallback() { update(); checkIfDone(); } LayerStackWidget::LayerStackWidget( QWidget *parent, Controller *controller) : TWidget(parent) , _controller(controller) , _background(this) { setGeometry(parentWidget()->rect()); hide(); _background->setDoneCallback([this] { animationDone(); }); } void LayerWidget::setInnerFocus() { if (!isAncestorOf(App::wnd()->focusWidget())) { doSetInnerFocus(); } } bool LayerWidget::overlaps(const QRect &globalRect) { if (isHidden()) { return false; } auto testRect = QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size()); if (testAttribute(Qt::WA_OpaquePaintEvent)) { return rect().contains(testRect); } if (QRect(0, st::boxRadius, width(), height() - 2 * st::boxRadius).contains(testRect)) { return true; } if (QRect(st::boxRadius, 0, width() - 2 * st::boxRadius, height()).contains(testRect)) { return true; } return false; } void LayerStackWidget::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Escape) { hideCurrent(anim::type::normal); } } void LayerStackWidget::mousePressEvent(QMouseEvent *e) { hideCurrent(anim::type::normal); } void LayerStackWidget::hideCurrent(anim::type animated) { return currentLayer() ? hideLayers(animated) : hideAll(animated); } void LayerStackWidget::hideLayers(anim::type animated) { startAnimation([] {}, [this] { clearLayers(); }, Action::HideLayer, animated); } void LayerStackWidget::hideAll(anim::type animated) { startAnimation([] {}, [this] { clearLayers(); clearSpecialLayer(); _mainMenu.destroyDelayed(); }, Action::HideAll, animated); } void LayerStackWidget::hideTopLayer(anim::type animated) { if (_specialLayer) { hideLayers(animated); } else { hideAll(animated); } } bool LayerStackWidget::layerShown() const { return _specialLayer || currentLayer(); } void LayerStackWidget::setCacheImages() { auto bodyCache = QPixmap(), mainMenuCache = QPixmap(); auto specialLayerCache = QPixmap(); if (_specialLayer) { Ui::SendPendingMoveResizeEvents(_specialLayer); auto sides = RectPart::Left | RectPart::Right; if (_specialLayer->y() > 0) { sides |= RectPart::Top; } if (_specialLayer->y() + _specialLayer->height() < height()) { sides |= RectPart::Bottom; } specialLayerCache = Ui::Shadow::grab(_specialLayer, st::boxRoundShadow, sides); } auto layerCache = QPixmap(); if (auto layer = currentLayer()) { layerCache = Ui::Shadow::grab(layer, st::boxRoundShadow); } if (isAncestorOf(App::wnd()->focusWidget())) { setFocus(); } if (_mainMenu) { setAttribute(Qt::WA_OpaquePaintEvent, false); hideChildren(); bodyCache = Ui::GrabWidget(App::wnd()->bodyWidget()); showChildren(); mainMenuCache = Ui::Shadow::grab(_mainMenu, st::boxRoundShadow, RectPart::Right); } setAttribute(Qt::WA_OpaquePaintEvent, !bodyCache.isNull()); updateLayerBoxes(); _background->setCacheImages(std::move(bodyCache), std::move(mainMenuCache), std::move(specialLayerCache), std::move(layerCache)); } void LayerStackWidget::onLayerClosed(LayerWidget *layer) { if (!layer->setClosing()) { // This layer is already closing. return; } layer->deleteLater(); if (layer == _specialLayer) { hideAll(anim::type::normal); } else if (layer == currentLayer()) { if (_layers.size() == 1) { hideCurrent(anim::type::normal); } else { if (layer->inFocusChain()) setFocus(); layer->hide(); _layers.pop_back(); layer = currentLayer(); layer->parentResized(); if (!_background->animating()) { layer->show(); showFinished(); } } } else { for (auto i = _layers.begin(), e = _layers.end(); i != e; ++i) { if (layer == *i) { _layers.erase(i); break; } } } } void LayerStackWidget::onLayerResized() { updateLayerBoxes(); } void LayerStackWidget::updateLayerBoxes() { auto getLayerBox = [this]() { if (auto layer = currentLayer()) { return layer->geometry(); } return QRect(); }; auto getSpecialLayerBox = [this]() { return _specialLayer ? _specialLayer->geometry() : QRect(); }; _background->setLayerBoxes(getSpecialLayerBox(), getLayerBox()); update(); } void LayerStackWidget::finishAnimating() { _background->finishAnimating(); } bool LayerStackWidget::canSetFocus() const { return (currentLayer() || _specialLayer || _mainMenu); } void LayerStackWidget::setInnerFocus() { if (_background->animating()) { setFocus(); } else if (auto l = currentLayer()) { l->setInnerFocus(); } else if (_specialLayer) { _specialLayer->setInnerFocus(); } else if (_mainMenu) { _mainMenu->setInnerFocus(); } } bool LayerStackWidget::contentOverlapped(const QRect &globalRect) { if (isHidden()) { return false; } if (_specialLayer && _specialLayer->overlaps(globalRect)) { return true; } if (auto layer = currentLayer()) { return layer->overlaps(globalRect); } return false; } template <typename SetupNew, typename ClearOld> void LayerStackWidget::startAnimation( SetupNew setupNewWidgets, ClearOld clearOldWidgets, Action action, anim::type animated) { if (App::quitting()) return; if (animated == anim::type::instant) { setupNewWidgets(); clearOldWidgets(); prepareForAnimation(); _background->skipAnimation(action); } else { setupNewWidgets(); setCacheImages(); clearOldWidgets(); prepareForAnimation(); _background->startAnimation(action); } } void LayerStackWidget::resizeEvent(QResizeEvent *e) { _background->setGeometry(rect()); if (_specialLayer) { _specialLayer->parentResized(); } if (auto layer = currentLayer()) { layer->parentResized(); } if (_mainMenu) { _mainMenu->resize(_mainMenu->width(), height()); } updateLayerBoxes(); } void LayerStackWidget::showBox( object_ptr<BoxContent> box, anim::type animated) { auto pointer = pushBox(std::move(box), animated); while (!_layers.isEmpty() && _layers.front() != pointer) { auto removingLayer = _layers.front(); _layers.pop_front(); removingLayer->setClosing(); if (removingLayer->inFocusChain()) setFocus(); removingLayer->hide(); removingLayer->deleteLater(); } } void LayerStackWidget::prepareForAnimation() { if (isHidden()) { show(); } if (_mainMenu) { _mainMenu->hide(); } if (_specialLayer) { _specialLayer->hide(); } if (auto layer = currentLayer()) { layer->hide(); } } void LayerStackWidget::animationDone() { bool hidden = true; if (_mainMenu) { _mainMenu->show(); hidden = false; } if (_specialLayer) { _specialLayer->show(); hidden = false; } if (auto layer = currentLayer()) { layer->show(); hidden = false; } if (hidden) { App::wnd()->layerFinishedHide(this); } else { showFinished(); } setAttribute(Qt::WA_OpaquePaintEvent, false); } void LayerStackWidget::showFinished() { fixOrder(); sendFakeMouseEvent(); updateLayerBoxes(); if (_specialLayer) { _specialLayer->showFinished(); } if (auto layer = currentLayer()) { layer->showFinished(); } if (auto window = App::wnd()) { window->setInnerFocus(); } } void LayerStackWidget::showSpecialLayer( object_ptr<LayerWidget> layer, anim::type animated) { startAnimation([this, layer = std::move(layer)]() mutable { _specialLayer.destroyDelayed(); _specialLayer = std::move(layer); initChildLayer(_specialLayer); }, [this] { _mainMenu.destroyDelayed(); }, Action::ShowSpecialLayer, animated); } bool LayerStackWidget::showSectionInternal( not_null<Window::SectionMemento *> memento, const SectionShow ¶ms) { if (_specialLayer) { return _specialLayer->showSectionInternal(memento, params); } return false; } void LayerStackWidget::hideSpecialLayer(anim::type animated) { startAnimation([] {}, [this] { clearSpecialLayer(); _mainMenu.destroyDelayed(); }, Action::HideSpecialLayer, animated); } void LayerStackWidget::showMainMenu(anim::type animated) { startAnimation([this] { _mainMenu.create(this, _controller); _mainMenu->setGeometryToLeft(0, 0, _mainMenu->width(), height()); _mainMenu->setParent(this); }, [this] { clearLayers(); _specialLayer.destroyDelayed(); }, Action::ShowMainMenu, animated); } void LayerStackWidget::appendBox( object_ptr<BoxContent> box, anim::type animated) { pushBox(std::move(box), animated); } LayerWidget *LayerStackWidget::pushBox( object_ptr<BoxContent> box, anim::type animated) { auto oldLayer = currentLayer(); if (oldLayer) { if (oldLayer->inFocusChain()) setFocus(); oldLayer->hide(); } auto layer = object_ptr<AbstractBox>( this, _controller, std::move(box)); _layers.push_back(layer); initChildLayer(layer); if (_layers.size() > 1) { if (!_background->animating()) { layer->show(); showFinished(); } } else { startAnimation([] {}, [this] { _mainMenu.destroyDelayed(); }, Action::ShowLayer, animated); } return layer.data(); } void LayerStackWidget::prependBox( object_ptr<BoxContent> box, anim::type animated) { if (_layers.empty()) { return showBox(std::move(box), animated); } auto layer = object_ptr<AbstractBox>(this, _controller, std::move(box)); layer->hide(); _layers.push_front(layer); initChildLayer(layer); } bool LayerStackWidget::takeToThirdSection() { return _specialLayer ? _specialLayer->takeToThirdSection() : false; } void LayerStackWidget::clearLayers() { for (auto layer : base::take(_layers)) { layer->setClosing(); if (layer->inFocusChain()) setFocus(); layer->hide(); layer->deleteLater(); } } void LayerStackWidget::clearSpecialLayer() { if (_specialLayer) { _specialLayer->setClosing(); _specialLayer.destroyDelayed(); } } void LayerStackWidget::initChildLayer(LayerWidget *layer) { layer->setParent(this); layer->setClosedCallback([this, layer] { onLayerClosed(layer); }); layer->setResizedCallback([this] { onLayerResized(); }); connect(layer, SIGNAL(destroyed(QObject*)), this, SLOT(onLayerDestroyed(QObject*))); Ui::SendPendingMoveResizeEvents(layer); layer->parentResized(); } void LayerStackWidget::fixOrder() { if (auto layer = currentLayer()) { _background->raise(); layer->raise(); } else if (_specialLayer) { _specialLayer->raise(); } if (_mainMenu) { _mainMenu->raise(); } } void LayerStackWidget::sendFakeMouseEvent() { sendSynteticMouseEvent(this, QEvent::MouseMove, Qt::NoButton); } void LayerStackWidget::onLayerDestroyed(QObject *obj) { if (obj == _specialLayer) { _specialLayer = nullptr; hideAll(anim::type::normal); } else if (obj == currentLayer()) { _layers.pop_back(); if (auto newLayer = currentLayer()) { newLayer->parentResized(); if (!_background->animating()) { newLayer->show(); showFinished(); } } else if (!_specialLayer) { hideAll(anim::type::normal); } } else { for (auto i = _layers.begin(), e = _layers.end(); i != e; ++i) { if (obj == *i) { _layers.erase(i); break; } } } } LayerStackWidget::~LayerStackWidget() { // We must destroy all layers before we destroy LayerStackWidget. // Some layers in destructor call layer-related methods, like hiding // other layers, that call methods of LayerStackWidget and access // its fields, so if it is destroyed already everything crashes. for (auto layer : base::take(_layers)) { layer->setClosing(); layer->hide(); delete layer; } if (App::wnd()) App::wnd()->noLayerStack(this); } } // namespace Window MediaPreviewWidget::MediaPreviewWidget(QWidget *parent, not_null<Window::Controller*> controller) : TWidget(parent) , _controller(controller) , _emojiSize(Ui::Emoji::Size(Ui::Emoji::Index() + 1) / cIntRetinaFactor()) { setAttribute(Qt::WA_TransparentForMouseEvents); subscribe(Auth().downloaderTaskFinished(), [this] { update(); }); } void MediaPreviewWidget::paintEvent(QPaintEvent *e) { Painter p(this); QRect r(e->rect()); auto image = currentImage(); int w = image.width() / cIntRetinaFactor(), h = image.height() / cIntRetinaFactor(); auto shown = _a_shown.current(getms(), _hiding ? 0. : 1.); if (!_a_shown.animating()) { if (_hiding) { hide(); _controller->disableGifPauseReason(Window::GifPauseReason::MediaPreview); return; } } else { p.setOpacity(shown); // w = qMax(qRound(w * (st::stickerPreviewMin + ((1. - st::stickerPreviewMin) * shown)) / 2.) * 2 + int(w % 2), 1); // h = qMax(qRound(h * (st::stickerPreviewMin + ((1. - st::stickerPreviewMin) * shown)) / 2.) * 2 + int(h % 2), 1); } p.fillRect(r, st::stickerPreviewBg); p.drawPixmap((width() - w) / 2, (height() - h) / 2, image); if (!_emojiList.empty()) { auto emojiCount = _emojiList.size(); auto emojiWidth = (emojiCount * _emojiSize) + (emojiCount - 1) * st::stickerEmojiSkip; auto emojiLeft = (width() - emojiWidth) / 2; auto esize = Ui::Emoji::Size(Ui::Emoji::Index() + 1); for (auto emoji : _emojiList) { p.drawPixmapLeft(emojiLeft, (height() - h) / 2 - (_emojiSize * 2), width(), App::emojiLarge(), QRect(emoji->x() * esize, emoji->y() * esize, esize, esize)); emojiLeft += _emojiSize + st::stickerEmojiSkip; } } } void MediaPreviewWidget::resizeEvent(QResizeEvent *e) { update(); } void MediaPreviewWidget::showPreview(DocumentData *document) { if (!document || (!document->isAnimation() && !document->sticker()) || document->isVideoMessage()) { hidePreview(); return; } startShow(); _photo = nullptr; _document = document; fillEmojiString(); resetGifAndCache(); } void MediaPreviewWidget::showPreview(PhotoData *photo) { if (!photo || photo->full->isNull()) { hidePreview(); return; } startShow(); _photo = photo; _document = nullptr; fillEmojiString(); resetGifAndCache(); } void MediaPreviewWidget::startShow() { _cache = QPixmap(); if (isHidden() || _a_shown.animating()) { if (isHidden()) { show(); _controller->enableGifPauseReason(Window::GifPauseReason::MediaPreview); } _hiding = false; _a_shown.start([this] { update(); }, 0., 1., st::stickerPreviewDuration); } else { update(); } } void MediaPreviewWidget::hidePreview() { if (isHidden()) { return; } if (_gif) _cache = currentImage(); _hiding = true; _a_shown.start([this] { update(); }, 1., 0., st::stickerPreviewDuration); _photo = nullptr; _document = nullptr; resetGifAndCache(); } void MediaPreviewWidget::fillEmojiString() { _emojiList.clear(); if (_photo) { return; } if (auto sticker = _document->sticker()) { if (auto list = Stickers::GetEmojiListFromSet(_document)) { _emojiList = std::move(*list); while (_emojiList.size() > kStickerPreviewEmojiLimit) { _emojiList.pop_back(); } } else if (auto emoji = Ui::Emoji::Find(sticker->alt)) { _emojiList.push_back(emoji); } } } void MediaPreviewWidget::resetGifAndCache() { _gif.reset(); _cacheStatus = CacheNotLoaded; _cachedSize = QSize(); } QSize MediaPreviewWidget::currentDimensions() const { if (!_cachedSize.isEmpty()) { return _cachedSize; } if (!_document && !_photo) { _cachedSize = QSize(_cache.width() / cIntRetinaFactor(), _cache.height() / cIntRetinaFactor()); return _cachedSize; } QSize result, box; if (_photo) { result = QSize(_photo->full->width(), _photo->full->height()); box = QSize(width() - 2 * st::boxVerticalMargin, height() - 2 * st::boxVerticalMargin); } else { result = _document->dimensions; if (_gif && _gif->ready()) { result = QSize(_gif->width(), _gif->height()); } if (_document->sticker()) { box = QSize(st::maxStickerSize, st::maxStickerSize); } else { box = QSize(2 * st::maxStickerSize, 2 * st::maxStickerSize); } } result = QSize(qMax(convertScale(result.width()), 1), qMax(convertScale(result.height()), 1)); if (result.width() > box.width()) { result.setHeight(qMax((box.width() * result.height()) / result.width(), 1)); result.setWidth(box.width()); } if (result.height() > box.height()) { result.setWidth(qMax((box.height() * result.width()) / result.height(), 1)); result.setHeight(box.height()); } if (_photo) { _cachedSize = result; } return result; } QPixmap MediaPreviewWidget::currentImage() const { if (_document) { if (_document->sticker()) { if (_cacheStatus != CacheLoaded) { _document->checkSticker(); if (_document->sticker()->img->isNull()) { if (_cacheStatus != CacheThumbLoaded && _document->thumb->loaded()) { QSize s = currentDimensions(); _cache = _document->thumb->pixBlurred(s.width(), s.height()); _cacheStatus = CacheThumbLoaded; } } else { QSize s = currentDimensions(); _cache = _document->sticker()->img->pix(s.width(), s.height()); _cacheStatus = CacheLoaded; } } } else { _document->automaticLoad(nullptr); if (_document->loaded()) { if (!_gif && !_gif.isBad()) { auto that = const_cast<MediaPreviewWidget*>(this); that->_gif = Media::Clip::MakeReader(_document, FullMsgId(), [=](Media::Clip::Notification notification) { that->clipCallback(notification); }); if (_gif) _gif->setAutoplay(); } } if (_gif && _gif->started()) { auto s = currentDimensions(); auto paused = _controller->isGifPausedAtLeastFor(Window::GifPauseReason::MediaPreview); return _gif->current(s.width(), s.height(), s.width(), s.height(), ImageRoundRadius::None, RectPart::None, paused ? 0 : getms()); } if (_cacheStatus != CacheThumbLoaded && _document->thumb->loaded()) { QSize s = currentDimensions(); _cache = _document->thumb->pixBlurred(s.width(), s.height()); _cacheStatus = CacheThumbLoaded; } } } else if (_photo) { if (_cacheStatus != CacheLoaded) { if (_photo->full->loaded()) { QSize s = currentDimensions(); _cache = _photo->full->pix(s.width(), s.height()); _cacheStatus = CacheLoaded; } else { if (_cacheStatus != CacheThumbLoaded && _photo->thumb->loaded()) { QSize s = currentDimensions(); _cache = _photo->thumb->pixBlurred(s.width(), s.height()); _cacheStatus = CacheThumbLoaded; } _photo->thumb->load(); _photo->full->load(); } } } return _cache; } void MediaPreviewWidget::clipCallback(Media::Clip::Notification notification) { using namespace Media::Clip; switch (notification) { case NotificationReinit: { if (_gif && _gif->state() == State::Error) { _gif.setBad(); } if (_gif && _gif->ready() && !_gif->started()) { QSize s = currentDimensions(); _gif->start(s.width(), s.height(), s.width(), s.height(), ImageRoundRadius::None, RectPart::None); } update(); } break; case NotificationRepaint: { if (_gif && !_gif->currentDisplayed()) { update(); } } break; } } MediaPreviewWidget::~MediaPreviewWidget() { } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/window/layer_widget.h������������������������������������������0000664�0000000�0000000�00000012100�13262451251�0023554�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/rp_widget.h" namespace Window { class MainMenu; class Controller; class SectionMemento; struct SectionShow; class LayerWidget : public Ui::RpWidget { public: using RpWidget::RpWidget; virtual void parentResized() = 0; virtual void showFinished() { } void setInnerFocus(); bool setClosing() { if (!_closing) { _closing = true; closeHook(); return true; } return false; } bool overlaps(const QRect &globalRect); void setClosedCallback(base::lambda<void()> callback) { _closedCallback = std::move(callback); } void setResizedCallback(base::lambda<void()> callback) { _resizedCallback = std::move(callback); } virtual bool takeToThirdSection() { return false; } virtual bool showSectionInternal( not_null<SectionMemento*> memento, const SectionShow ¶ms) { return false; } protected: void closeLayer() { if (_closedCallback) { _closedCallback(); } } void mousePressEvent(QMouseEvent *e) override { e->accept(); } void resizeEvent(QResizeEvent *e) override { if (_resizedCallback) { _resizedCallback(); } } virtual void doSetInnerFocus() { setFocus(); } virtual void closeHook() { } private: bool _closing = false; base::lambda<void()> _closedCallback; base::lambda<void()> _resizedCallback; }; class LayerStackWidget : public TWidget { Q_OBJECT public: LayerStackWidget(QWidget *parent, Controller *controller); Controller *controller() const { return _controller; } void finishAnimating(); void showBox( object_ptr<BoxContent> box, anim::type animated); void showSpecialLayer( object_ptr<LayerWidget> layer, anim::type animated); void showMainMenu(anim::type animated); void appendBox( object_ptr<BoxContent> box, anim::type animated); void prependBox( object_ptr<BoxContent> box, anim::type animated); bool takeToThirdSection(); bool canSetFocus() const; void setInnerFocus(); bool contentOverlapped(const QRect &globalRect); void hideSpecialLayer(anim::type animated); void hideLayers(anim::type animated); void hideAll(anim::type animated); void hideTopLayer(anim::type animated); bool showSectionInternal( not_null<SectionMemento*> memento, const SectionShow ¶ms); bool layerShown() const; ~LayerStackWidget(); protected: void keyPressEvent(QKeyEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void resizeEvent(QResizeEvent *e) override; private slots: void onLayerDestroyed(QObject *obj); void onLayerClosed(LayerWidget *layer); void onLayerResized(); private: LayerWidget *pushBox( object_ptr<BoxContent> box, anim::type animated); void showFinished(); void hideCurrent(anim::type animated); enum class Action { ShowMainMenu, ShowSpecialLayer, ShowLayer, HideSpecialLayer, HideLayer, HideAll, }; template <typename SetupNew, typename ClearOld> void startAnimation( SetupNew setupNewWidgets, ClearOld clearOldWidgets, Action action, anim::type animated); void prepareForAnimation(); void animationDone(); void setCacheImages(); void clearLayers(); void clearSpecialLayer(); void initChildLayer(LayerWidget *layer); void updateLayerBoxes(); void fixOrder(); void sendFakeMouseEvent(); LayerWidget *currentLayer() { return _layers.empty() ? nullptr : _layers.back(); } const LayerWidget *currentLayer() const { return const_cast<LayerStackWidget*>(this)->currentLayer(); } Controller *_controller = nullptr; QList<LayerWidget*> _layers; object_ptr<LayerWidget> _specialLayer = { nullptr }; object_ptr<MainMenu> _mainMenu = { nullptr }; class BackgroundWidget; object_ptr<BackgroundWidget> _background; }; } // namespace Window class MediaPreviewWidget : public TWidget, private base::Subscriber { Q_OBJECT public: MediaPreviewWidget(QWidget *parent, not_null<Window::Controller*> controller); void showPreview(DocumentData *document); void showPreview(PhotoData *photo); void hidePreview(); ~MediaPreviewWidget(); protected: void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; private: QSize currentDimensions() const; QPixmap currentImage() const; void startShow(); void fillEmojiString(); void resetGifAndCache(); not_null<Window::Controller*> _controller; Animation _a_shown; bool _hiding = false; DocumentData *_document = nullptr; PhotoData *_photo = nullptr; Media::Clip::ReaderPointer _gif; int _emojiSize; std::vector<not_null<EmojiPtr>> _emojiList; void clipCallback(Media::Clip::Notification notification); enum CacheStatus { CacheNotLoaded, CacheThumbLoaded, CacheLoaded, }; mutable CacheStatus _cacheStatus = CacheNotLoaded; mutable QPixmap _cache; mutable QSize _cachedSize; }; template <typename BoxType, typename ...Args> inline object_ptr<BoxType> Box(Args&&... args) { auto parent = static_cast<QWidget*>(nullptr); return object_ptr<BoxType>(parent, std::forward<Args>(args)...); } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/window/main_window.cpp�����������������������������������������0000664�0000000�0000000�00000031707�13262451251�0023761�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "window/main_window.h" #include "storage/localstorage.h" #include "styles/style_window.h" #include "platform/platform_window_title.h" #include "history/history.h" #include "window/themes/window_theme.h" #include "window/window_controller.h" #include "mediaview.h" #include "messenger.h" #include "mainwindow.h" namespace Window { constexpr auto kInactivePressTimeout = 200; QImage LoadLogo() { return QImage(qsl(":/gui/art/logo_256.png")); } QImage LoadLogoNoMargin() { return QImage(qsl(":/gui/art/logo_256_no_margin.png")); } QIcon CreateOfficialIcon() { auto useNoMarginLogo = (cPlatform() == dbipMac); if (auto messenger = Messenger::InstancePointer()) { return QIcon(App::pixmapFromImageInPlace(useNoMarginLogo ? messenger->logoNoMargin() : messenger->logo())); } return QIcon(App::pixmapFromImageInPlace(useNoMarginLogo ? LoadLogoNoMargin() : LoadLogo())); } QIcon CreateIcon() { auto result = CreateOfficialIcon(); if (cPlatform() == dbipLinux32 || cPlatform() == dbipLinux64) { return QIcon::fromTheme("telegram", result); } return result; } MainWindow::MainWindow() : QWidget() , _positionUpdatedTimer(this) , _body(this) , _icon(CreateIcon()) , _titleText(qsl("Telegram")) { subscribe(Theme::Background(), [this](const Theme::BackgroundUpdate &data) { if (data.paletteChanged()) { if (_title) { _title->update(); } updatePalette(); } }); subscribe(Global::RefUnreadCounterUpdate(), [this] { updateUnreadCounter(); }); subscribe(Global::RefWorkMode(), [this](DBIWorkMode mode) { workmodeUpdated(mode); }); subscribe(Messenger::Instance().authSessionChanged(), [this] { checkAuthSession(); }); checkAuthSession(); _isActiveTimer.setCallback([this] { updateIsActive(0); }); _inactivePressTimer.setCallback([this] { setInactivePress(false); }); } bool MainWindow::hideNoQuit() { if (App::quitting()) { return false; } if (Global::WorkMode().value() == dbiwmTrayOnly || Global::WorkMode().value() == dbiwmWindowAndTray) { if (minimizeToTray()) { Ui::showChatsList(); return true; } } else if (cPlatform() == dbipMac || cPlatform() == dbipMacOld) { closeWithoutDestroy(); updateIsActive(Global::OfflineBlurTimeout()); updateGlobalMenu(); Ui::showChatsList(); return true; } return false; } void MainWindow::clearWidgets() { Ui::hideLayer(anim::type::instant); clearWidgetsHook(); updateGlobalMenu(); } void MainWindow::updateIsActive(int timeout) { if (timeout > 0) { return _isActiveTimer.callOnce(timeout); } _isActive = computeIsActive(); updateIsActiveHook(); } bool MainWindow::computeIsActive() const { return isActiveWindow() && isVisible() && !(windowState() & Qt::WindowMinimized); } void MainWindow::onReActivate() { if (auto w = App::wnd()) { if (auto f = QApplication::focusWidget()) { f->clearFocus(); } w->windowHandle()->requestActivate(); w->activate(); if (auto f = QApplication::focusWidget()) { f->clearFocus(); } w->setInnerFocus(); } } void MainWindow::updateWindowIcon() { setWindowIcon(_icon); } void MainWindow::init() { Expects(!windowHandle()); createWinId(); initHook(); updateWindowIcon(); connect(windowHandle(), &QWindow::activeChanged, this, [this] { handleActiveChanged(); }, Qt::QueuedConnection); connect(windowHandle(), &QWindow::windowStateChanged, this, [this](Qt::WindowState state) { handleStateChanged(state); }); _positionUpdatedTimer->setSingleShot(true); connect(_positionUpdatedTimer, SIGNAL(timeout()), this, SLOT(savePositionByTimer())); updatePalette(); if ((_title = Platform::CreateTitleWidget(this))) { _title->init(); } initSize(); updateUnreadCounter(); } void MainWindow::handleStateChanged(Qt::WindowState state) { stateChangedHook(state); updateIsActive((state == Qt::WindowMinimized) ? Global::OfflineBlurTimeout() : Global::OnlineFocusTimeout()); psUserActionDone(); if (state == Qt::WindowMinimized && Global::WorkMode().value() == dbiwmTrayOnly) { minimizeToTray(); } savePosition(state); } void MainWindow::handleActiveChanged() { if (isActiveWindow()) { Messenger::Instance().checkMediaViewActivation(); } App::CallDelayed(1, this, [this] { updateTrayMenu(); handleActiveChangedHook(); }); } void MainWindow::updatePalette() { auto p = palette(); p.setColor(QPalette::Window, st::windowBg->c); setPalette(p); } HitTestResult MainWindow::hitTest(const QPoint &p) const { auto titleResult = _title ? _title->hitTest(p - _title->geometry().topLeft()) : Window::HitTestResult::None; if (titleResult != Window::HitTestResult::None) { return titleResult; } else if (rect().contains(p)) { return Window::HitTestResult::Client; } return Window::HitTestResult::None; } void MainWindow::initSize() { setMinimumWidth(st::windowMinWidth); setMinimumHeight((_title ? _title->height() : 0) + st::windowMinHeight); auto position = cWindowPos(); DEBUG_LOG(("Window Pos: Initializing first %1, %2, %3, %4 (maximized %5)").arg(position.x).arg(position.y).arg(position.w).arg(position.h).arg(Logs::b(position.maximized))); auto avail = QDesktopWidget().availableGeometry(); bool maximized = false; auto geom = QRect(avail.x() + (avail.width() - st::windowDefaultWidth) / 2, avail.y() + (avail.height() - st::windowDefaultHeight) / 2, st::windowDefaultWidth, st::windowDefaultHeight); if (position.w && position.h) { for (auto screen : QGuiApplication::screens()) { if (position.moncrc == screenNameChecksum(screen->name())) { auto screenGeometry = screen->geometry(); DEBUG_LOG(("Window Pos: Screen found, screen geometry: %1, %2, %3, %4").arg(screenGeometry.x()).arg(screenGeometry.y()).arg(screenGeometry.width()).arg(screenGeometry.height())); auto w = screenGeometry.width(), h = screenGeometry.height(); if (w >= st::windowMinWidth && h >= st::windowMinHeight) { if (position.x < 0) position.x = 0; if (position.y < 0) position.y = 0; if (position.w > w) position.w = w; if (position.h > h) position.h = h; position.x += screenGeometry.x(); position.y += screenGeometry.y(); if (position.x + st::windowMinWidth <= screenGeometry.x() + screenGeometry.width() && position.y + st::windowMinHeight <= screenGeometry.y() + screenGeometry.height()) { DEBUG_LOG(("Window Pos: Resulting geometry is %1, %2, %3, %4").arg(position.x).arg(position.y).arg(position.w).arg(position.h)); geom = QRect(position.x, position.y, position.w, position.h); } } break; } } maximized = position.maximized; } DEBUG_LOG(("Window Pos: Setting first %1, %2, %3, %4").arg(geom.x()).arg(geom.y()).arg(geom.width()).arg(geom.height())); setGeometry(geom); } void MainWindow::positionUpdated() { _positionUpdatedTimer->start(SaveWindowPositionTimeout); } bool MainWindow::titleVisible() const { return _title && !_title->isHidden(); } void MainWindow::setTitleVisible(bool visible) { if (_title && (_title->isHidden() == visible)) { _title->setVisible(visible); updateControlsGeometry(); } titleVisibilityChangedHook(); } int32 MainWindow::screenNameChecksum(const QString &name) const { auto bytes = name.toUtf8(); return hashCrc32(bytes.constData(), bytes.size()); } void MainWindow::setPositionInited() { _positionInited = true; } void MainWindow::resizeEvent(QResizeEvent *e) { updateControlsGeometry(); } rpl::producer<> MainWindow::leaveEvents() const { return _leaveEvents.events(); } void MainWindow::leaveEvent(QEvent *e) { _leaveEvents.fire({}); } void MainWindow::updateControlsGeometry() { auto bodyTop = 0; auto bodyWidth = width(); if (_title && !_title->isHidden()) { _title->setGeometry(0, bodyTop, width(), _title->height()); bodyTop += _title->height(); } if (_rightColumn) { bodyWidth -= _rightColumn->width(); _rightColumn->setGeometry(bodyWidth, bodyTop, width() - bodyWidth, height() - bodyTop); } _body->setGeometry(0, bodyTop, bodyWidth, height() - bodyTop); } void MainWindow::updateUnreadCounter() { if (!Global::started() || App::quitting()) return; auto counter = App::histories().unreadBadge(); _titleText = (counter > 0) ? qsl("Telegram (%1)").arg(counter) : qsl("Telegram"); unreadCounterChangedHook(); } void MainWindow::savePosition(Qt::WindowState state) { if (state == Qt::WindowActive) state = windowHandle()->windowState(); if (state == Qt::WindowMinimized || !positionInited()) return; auto savedPosition = cWindowPos(); auto realPosition = savedPosition; if (state == Qt::WindowMaximized) { realPosition.maximized = 1; } else { auto r = geometry(); realPosition.x = r.x(); realPosition.y = r.y(); realPosition.w = r.width() - (_rightColumn ? _rightColumn->width() : 0); realPosition.h = r.height(); realPosition.maximized = 0; realPosition.moncrc = 0; } DEBUG_LOG(("Window Pos: Saving position: %1, %2, %3, %4 (maximized %5)").arg(realPosition.x).arg(realPosition.y).arg(realPosition.w).arg(realPosition.h).arg(Logs::b(realPosition.maximized))); auto centerX = realPosition.x + realPosition.w / 2; auto centerY = realPosition.y + realPosition.h / 2; int minDelta = 0; QScreen *chosen = nullptr; auto screens = QGuiApplication::screens(); for (auto screen : QGuiApplication::screens()) { auto delta = (screen->geometry().center() - QPoint(centerX, centerY)).manhattanLength(); if (!chosen || delta < minDelta) { minDelta = delta; chosen = screen; } } if (chosen) { auto screenGeometry = chosen->geometry(); DEBUG_LOG(("Window Pos: Screen found, geometry: %1, %2, %3, %4").arg(screenGeometry.x()).arg(screenGeometry.y()).arg(screenGeometry.width()).arg(screenGeometry.height())); realPosition.x -= screenGeometry.x(); realPosition.y -= screenGeometry.y(); realPosition.moncrc = screenNameChecksum(chosen->name()); } if (realPosition.w >= st::windowMinWidth && realPosition.h >= st::windowMinHeight) { if (realPosition.x != savedPosition.x || realPosition.y != savedPosition.y || realPosition.w != savedPosition.w || realPosition.h != savedPosition.h || realPosition.moncrc != savedPosition.moncrc || realPosition.maximized != savedPosition.maximized) { DEBUG_LOG(("Window Pos: Writing: %1, %2, %3, %4 (maximized %5)").arg(realPosition.x).arg(realPosition.y).arg(realPosition.w).arg(realPosition.h).arg(Logs::b(realPosition.maximized))); cSetWindowPos(realPosition); Local::writeSettings(); } } } bool MainWindow::minimizeToTray() { if (App::quitting() || !hasTrayIcon()) return false; closeWithoutDestroy(); updateIsActive(Global::OfflineBlurTimeout()); updateTrayMenu(); updateGlobalMenu(); showTrayTooltip(); return true; } void MainWindow::showRightColumn(object_ptr<TWidget> widget) { auto wasWidth = width(); auto wasRightWidth = _rightColumn ? _rightColumn->width() : 0; _rightColumn = std::move(widget); if (_rightColumn) { _rightColumn->setParent(this); _rightColumn->show(); _rightColumn->setFocus(); } else if (App::wnd()) { App::wnd()->setInnerFocus(); } auto nowRightWidth = _rightColumn ? _rightColumn->width() : 0; setMinimumWidth(st::windowMinWidth + nowRightWidth); if (!isMaximized()) { tryToExtendWidthBy(wasWidth + nowRightWidth - wasRightWidth - width()); } else { updateControlsGeometry(); } } int MainWindow::maximalExtendBy() const { auto desktop = QDesktopWidget().availableGeometry(this); return std::max(desktop.width() - geometry().width(), 0); } bool MainWindow::canExtendNoMove(int extendBy) const { auto desktop = QDesktopWidget().availableGeometry(this); auto inner = geometry(); auto innerRight = (inner.x() + inner.width() + extendBy); auto desktopRight = (desktop.x() + desktop.width()); return innerRight <= desktopRight; } int MainWindow::tryToExtendWidthBy(int addToWidth) { auto desktop = QDesktopWidget().availableGeometry(this); auto inner = geometry(); accumulate_min( addToWidth, std::max(desktop.width() - inner.width(), 0)); auto newWidth = inner.width() + addToWidth; auto newLeft = std::min( inner.x(), desktop.x() + desktop.width() - newWidth); if (inner.x() != newLeft || inner.width() != newWidth) { setGeometry(newLeft, inner.y(), newWidth, inner.height()); } else { updateControlsGeometry(); } return addToWidth; } void MainWindow::launchDrag(std::unique_ptr<QMimeData> data) { auto weak = QPointer<MainWindow>(this); auto drag = std::make_unique<QDrag>(App::wnd()); drag->setMimeData(data.release()); drag->exec(Qt::CopyAction); // We don't receive mouseReleaseEvent when drag is finished. ClickHandler::unpressed(); if (weak) { weak->dragFinished().notify(); } } void MainWindow::checkAuthSession() { if (AuthSession::Exists()) { _controller = std::make_unique<Window::Controller>(this); } else { _controller = nullptr; } } void MainWindow::setInactivePress(bool inactive) { _wasInactivePress = inactive; if (_wasInactivePress) { _inactivePressTimer.callOnce(kInactivePressTimeout); } else { _inactivePressTimer.cancel(); } } MainWindow::~MainWindow() = default; } // namespace Window ���������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/window/main_window.h�������������������������������������������0000664�0000000�0000000�00000007173�13262451251�0023426�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/event_stream.h> #include "window/window_title.h" #include "base/timer.h" class MediaView; namespace Window { class Controller; class TitleWidget; QImage LoadLogo(); QImage LoadLogoNoMargin(); QIcon CreateIcon(); class MainWindow : public QWidget, protected base::Subscriber { Q_OBJECT public: MainWindow(); Window::Controller *controller() const { return _controller.get(); } void setInactivePress(bool inactive); bool wasInactivePress() const { return _wasInactivePress; } bool hideNoQuit(); void init(); HitTestResult hitTest(const QPoint &p) const; void updateIsActive(int timeout); bool isActive() const { return _isActive; } bool positionInited() const { return _positionInited; } void positionUpdated(); bool titleVisible() const; void setTitleVisible(bool visible); QString titleText() const { return _titleText; } void reActivateWindow() { #if defined Q_OS_LINUX32 || defined Q_OS_LINUX64 onReActivate(); QTimer::singleShot(200, this, SLOT(onReActivate())); #endif // Q_OS_LINUX32 || Q_OS_LINUX64 } void showRightColumn(object_ptr<TWidget> widget); int maximalExtendBy() const; bool canExtendNoMove(int extendBy) const; // Returns how much could the window get extended. int tryToExtendWidthBy(int addToWidth); virtual void updateTrayMenu(bool force = false) { } virtual ~MainWindow(); TWidget *bodyWidget() { return _body.data(); } void launchDrag(std::unique_ptr<QMimeData> data); base::Observable<void> &dragFinished() { return _dragFinished; } rpl::producer<> leaveEvents() const; public slots: bool minimizeToTray(); void updateGlobalMenu() { updateGlobalMenuHook(); } protected: void resizeEvent(QResizeEvent *e) override; void leaveEvent(QEvent *e) override; void savePosition(Qt::WindowState state = Qt::WindowActive); void handleStateChanged(Qt::WindowState state); void handleActiveChanged(); virtual void initHook() { } virtual void updateIsActiveHook() { } virtual void handleActiveChangedHook() { } void clearWidgets(); virtual void clearWidgetsHook() { } virtual void updateWindowIcon(); virtual void stateChangedHook(Qt::WindowState state) { } virtual void titleVisibilityChangedHook() { } virtual void unreadCounterChangedHook() { } virtual void closeWithoutDestroy() { hide(); } virtual void updateGlobalMenuHook() { } virtual bool hasTrayIcon() const { return false; } virtual void showTrayTooltip() { } virtual void workmodeUpdated(DBIWorkMode mode) { } virtual void updateControlsGeometry(); // This one is overriden in Windows for historical reasons. virtual int32 screenNameChecksum(const QString &name) const; void setPositionInited(); private slots: void savePositionByTimer() { savePosition(); } void onReActivate(); private: void checkAuthSession(); void updatePalette(); void updateUnreadCounter(); void initSize(); bool computeIsActive() const; object_ptr<QTimer> _positionUpdatedTimer; bool _positionInited = false; std::unique_ptr<Window::Controller> _controller; object_ptr<TitleWidget> _title = { nullptr }; object_ptr<TWidget> _body; object_ptr<TWidget> _rightColumn = { nullptr }; QIcon _icon; QString _titleText; bool _isActive = false; base::Timer _isActiveTimer; bool _wasInactivePress = false; base::Timer _inactivePressTimer; base::Observable<void> _dragFinished; rpl::event_stream<> _leaveEvents; }; } // namespace Window �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/window/notifications_manager.cpp�������������������������������0000664�0000000�0000000�00000031371�13262451251�0026006�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "window/notifications_manager.h" #include "platform/platform_notifications_manager.h" #include "window/notifications_manager_default.h" #include "media/media_audio_track.h" #include "media/media_audio.h" #include "history/history.h" #include "history/history_item_components.h" #include "history/feed/history_feed_section.h" #include "lang/lang_keys.h" #include "window/window_controller.h" #include "mainwindow.h" #include "mainwidget.h" #include "apiwrap.h" #include "auth_session.h" namespace Window { namespace Notifications { namespace { // not more than one sound in 500ms from one peer - grouping constexpr auto kMinimalAlertDelay = TimeMs(500); } // namespace System::System(AuthSession *session) : _authSession(session) { createManager(); _waitTimer.setTimeoutHandler([this] { showNext(); }); subscribe(settingsChanged(), [this](ChangeType type) { if (type == ChangeType::DesktopEnabled) { App::wnd()->updateTrayMenu(); clearAll(); } else if (type == ChangeType::ViewParams) { updateAll(); } else if (type == ChangeType::IncludeMuted) { Notify::unreadCounterUpdated(); } }); } void System::createManager() { _manager = Platform::Notifications::Create(this); if (!_manager) { _manager = std::make_unique<Default::Manager>(this); } } void System::schedule(History *history, HistoryItem *item) { if (App::quitting() || !history->currentNotification() || !AuthSession::Exists()) return; auto notifyByFrom = (!history->peer->isUser() && item->mentionsMe()) ? item->from().get() : nullptr; if (item->isSilent()) { history->popNotification(item); return; } auto haveSetting = !history->peer->notifySettingsUnknown(); if (haveSetting) { if (history->peer->isMuted()) { if (notifyByFrom) { haveSetting = !item->from()->notifySettingsUnknown(); if (haveSetting) { if (notifyByFrom->isMuted()) { history->popNotification(item); return; } } else { Auth().api().requestNotifySetting(notifyByFrom); } } else { history->popNotification(item); return; } } } else { if (notifyByFrom && notifyByFrom->notifySettingsUnknown()) { Auth().api().requestNotifySetting(notifyByFrom); } Auth().api().requestNotifySetting(history->peer); } if (!item->notificationReady()) { haveSetting = false; } int delay = item->Has<HistoryMessageForwarded>() ? 500 : 100, t = unixtime(); auto ms = getms(true); bool isOnline = App::main()->lastWasOnline(), otherNotOld = ((cOtherOnline() * 1000LL) + Global::OnlineCloudTimeout() > t * 1000LL); bool otherLaterThanMe = (cOtherOnline() * 1000LL + (ms - App::main()->lastSetOnline()) > t * 1000LL); if (!isOnline && otherNotOld && otherLaterThanMe) { delay = Global::NotifyCloudDelay(); } else if (cOtherOnline() >= t) { delay = Global::NotifyDefaultDelay(); } auto when = ms + delay; _whenAlerts[history].insert(when, notifyByFrom); if (Global::DesktopNotify() && !Platform::Notifications::SkipToast()) { auto &whenMap = _whenMaps[history]; if (whenMap.constFind(item->id) == whenMap.cend()) { whenMap.insert(item->id, when); } auto &addTo = haveSetting ? _waiters : _settingWaiters; auto it = addTo.constFind(history); if (it == addTo.cend() || it->when > when) { addTo.insert(history, Waiter(item->id, when, notifyByFrom)); } } if (haveSetting) { if (!_waitTimer.isActive() || _waitTimer.remainingTime() > delay) { _waitTimer.start(delay); } } } void System::clearAll() { _manager->clearAll(); for (auto i = _whenMaps.cbegin(), e = _whenMaps.cend(); i != e; ++i) { i.key()->clearNotifications(); } _whenMaps.clear(); _whenAlerts.clear(); _waiters.clear(); _settingWaiters.clear(); } void System::clearFromHistory(History *history) { _manager->clearFromHistory(history); history->clearNotifications(); _whenMaps.remove(history); _whenAlerts.remove(history); _waiters.remove(history); _settingWaiters.remove(history); _waitTimer.stop(); showNext(); } void System::clearFromItem(HistoryItem *item) { _manager->clearFromItem(item); } void System::clearAllFast() { _manager->clearAllFast(); _whenMaps.clear(); _whenAlerts.clear(); _waiters.clear(); _settingWaiters.clear(); } void System::checkDelayed() { for (auto i = _settingWaiters.begin(); i != _settingWaiters.end();) { const auto history = i.key(); const auto peer = history->peer; auto loaded = false; auto muted = false; if (!peer->notifySettingsUnknown()) { if (!peer->isMuted()) { loaded = true; } else if (const auto from = i.value().notifyByFrom) { if (!from->notifySettingsUnknown()) { if (!from->isMuted()) { loaded = true; } else { loaded = muted = true; } } } else { loaded = muted = true; } } if (loaded) { const auto fullId = FullMsgId( history->channelId(), i.value().msg); if (const auto item = App::histItemById(fullId)) { if (!item->notificationReady()) { loaded = false; } } else { muted = true; } } if (loaded) { if (!muted) { _waiters.insert(i.key(), i.value()); } i = _settingWaiters.erase(i); } else { ++i; } } _waitTimer.stop(); showNext(); } void System::showNext() { if (App::quitting()) return; auto ms = getms(true), nextAlert = 0LL; bool alert = false; int32 now = unixtime(); for (auto i = _whenAlerts.begin(); i != _whenAlerts.end();) { while (!i.value().isEmpty() && i.value().begin().key() <= ms) { const auto peer = i.key()->peer; const auto peerUnknown = peer->notifySettingsUnknown(); const auto peerAlert = peerUnknown ? false : !peer->isMuted(); const auto from = i.value().begin().value(); const auto fromUnknown = (!from || from->notifySettingsUnknown()); const auto fromAlert = fromUnknown ? false : !from->isMuted(); if (peerAlert || fromAlert) { alert = true; } while (!i.value().isEmpty() && i.value().begin().key() <= ms + kMinimalAlertDelay) { i.value().erase(i.value().begin()); } } if (i.value().isEmpty()) { i = _whenAlerts.erase(i); } else { if (!nextAlert || nextAlert > i.value().begin().key()) { nextAlert = i.value().begin().key(); } ++i; } } if (alert) { Platform::Notifications::FlashBounce(); if (Global::SoundNotify() && !Platform::Notifications::SkipAudio()) { ensureSoundCreated(); _soundTrack->playOnce(); emit Media::Player::mixer()->suppressAll(_soundTrack->getLengthMs()); emit Media::Player::mixer()->faderOnTimer(); } } if (_waiters.isEmpty() || !Global::DesktopNotify() || Platform::Notifications::SkipToast()) { if (nextAlert) { _waitTimer.start(nextAlert - ms); } return; } while (true) { auto next = 0LL; HistoryItem *notifyItem = nullptr; History *notifyHistory = nullptr; for (auto i = _waiters.begin(); i != _waiters.end();) { History *history = i.key(); if (history->currentNotification() && history->currentNotification()->id != i.value().msg) { auto j = _whenMaps.find(history); if (j == _whenMaps.end()) { history->clearNotifications(); i = _waiters.erase(i); continue; } do { auto k = j.value().constFind(history->currentNotification()->id); if (k != j.value().cend()) { i.value().msg = k.key(); i.value().when = k.value(); break; } history->skipNotification(); } while (history->currentNotification()); } if (!history->currentNotification()) { _whenMaps.remove(history); i = _waiters.erase(i); continue; } auto when = i.value().when; if (!notifyItem || next > when) { next = when; notifyItem = history->currentNotification(); notifyHistory = history; } ++i; } if (notifyItem) { if (next > ms) { if (nextAlert && nextAlert < next) { next = nextAlert; nextAlert = 0; } _waitTimer.start(next - ms); break; } else { auto forwardedItem = notifyItem->Has<HistoryMessageForwarded>() ? notifyItem : nullptr; // forwarded notify grouping auto forwardedCount = 1; auto ms = getms(true); auto history = notifyItem->history(); auto j = _whenMaps.find(history); if (j == _whenMaps.cend()) { history->clearNotifications(); } else { auto nextNotify = (HistoryItem*)nullptr; do { history->skipNotification(); if (!history->hasNotification()) { break; } j.value().remove((forwardedItem ? forwardedItem : notifyItem)->id); do { auto k = j.value().constFind(history->currentNotification()->id); if (k != j.value().cend()) { nextNotify = history->currentNotification(); _waiters.insert(notifyHistory, Waiter(k.key(), k.value(), 0)); break; } history->skipNotification(); } while (history->hasNotification()); if (nextNotify) { if (forwardedItem) { auto nextForwarded = nextNotify->Has<HistoryMessageForwarded>() ? nextNotify : nullptr; if (nextForwarded && forwardedItem->author() == nextForwarded->author() && qAbs(int64(nextForwarded->date()) - int64(forwardedItem->date())) < 2) { forwardedItem = nextForwarded; ++forwardedCount; } else { nextNotify = nullptr; } } else { nextNotify = nullptr; } } } while (nextNotify); } _manager->showNotification(notifyItem, forwardedCount); if (!history->hasNotification()) { _waiters.remove(history); _whenMaps.remove(history); continue; } } } else { break; } } if (nextAlert) { _waitTimer.start(nextAlert - ms); } } void System::ensureSoundCreated() { if (_soundTrack) { return; } _soundTrack = Media::Audio::Current().createTrack(); _soundTrack->fillFromFile( Auth().settings().getSoundPath(qsl("msg_incoming"))); } void System::updateAll() { _manager->updateAll(); } Manager::DisplayOptions Manager::getNotificationOptions(HistoryItem *item) { auto hideEverything = (App::passcoded() || Global::ScreenIsLocked()); DisplayOptions result; result.hideNameAndPhoto = hideEverything || (Global::NotifyView() > dbinvShowName); result.hideMessageText = hideEverything || (Global::NotifyView() > dbinvShowPreview); result.hideReplyButton = result.hideMessageText || !item || !item->history()->peer->canWrite(); return result; } void Manager::notificationActivated(PeerId peerId, MsgId msgId) { onBeforeNotificationActivated(peerId, msgId); if (auto window = App::wnd()) { auto history = App::history(peerId); window->showFromTray(); window->reActivateWindow(); if (App::passcoded()) { window->setInnerFocus(); system()->clearAll(); } else { openNotificationMessage(history, msgId); } } onAfterNotificationActivated(peerId, msgId); } void Manager::openNotificationMessage( not_null<History*> history, MsgId messageId) { const auto openExactlyMessage = [&] { if (history->peer->isUser() || history->peer->isChannel() || !IsServerMsgId(messageId)) { return false; } const auto item = App::histItemById(history->channelId(), messageId); if (!item || !item->mentionsMe()) { return false; } return true; }(); const auto messageFeed = [&] { if (const auto channel = history->peer->asChannel()) { return channel->feed(); } return (Data::Feed*)nullptr; }(); if (openExactlyMessage) { Ui::showPeerHistory(history, messageId); } else if (messageFeed) { App::wnd()->controller()->showSection( HistoryFeed::Memento(messageFeed)); } else { Ui::showPeerHistory(history, ShowAtUnreadMsgId); } system()->clearFromHistory(history); } void Manager::notificationReplied( PeerId peerId, MsgId msgId, const QString &reply) { if (!peerId) return; auto history = App::history(peerId); auto message = MainWidget::MessageToSend(history); message.textWithTags = { reply, TextWithTags::Tags() }; message.replyTo = (msgId > 0 && !history->peer->isUser()) ? msgId : 0; message.clearDraft = false; if (auto main = App::main()) { main->sendMessage(message); } } void NativeManager::doShowNotification(HistoryItem *item, int forwardedCount) { const auto options = getNotificationOptions(item); const auto title = options.hideNameAndPhoto ? qsl("Telegram Desktop") : item->history()->peer->name; const auto subtitle = options.hideNameAndPhoto ? QString() : item->notificationHeader(); const auto text = options.hideMessageText ? lang(lng_notification_preview) : (forwardedCount < 2 ? item->notificationText() : lng_forward_messages(lt_count, forwardedCount)); doShowNativeNotification( item->history()->peer, item->id, title, subtitle, text, options.hideNameAndPhoto, options.hideReplyButton); } System::~System() = default; } // namespace Notifications } // namespace Window �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/window/notifications_manager.h���������������������������������0000664�0000000�0000000�00000007660�13262451251�0025457�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once class AuthSession; namespace Platform { namespace Notifications { class Manager; } // namespace Notifications } // namespace Platform namespace Media { namespace Audio { class Track; } // namespace Audio } // namespace Media namespace Window { namespace Notifications { enum class ChangeType { SoundEnabled, IncludeMuted, DesktopEnabled, ViewParams, MaxCount, Corner, DemoIsShown, }; } // namespace Notifications } // namespace Window namespace base { template <> struct custom_is_fast_copy_type<Window::Notifications::ChangeType> : public std::true_type { }; } // namespace base namespace Window { namespace Notifications { class Manager; class System final : private base::Subscriber { public: System(AuthSession *session); void createManager(); void checkDelayed(); void schedule(History *history, HistoryItem *item); void clearFromHistory(History *history); void clearFromItem(HistoryItem *item); void clearAll(); void clearAllFast(); void updateAll(); base::Observable<ChangeType> &settingsChanged() { return _settingsChanged; } AuthSession *authSession() { return _authSession; } ~System(); private: void showNext(); void ensureSoundCreated(); AuthSession *_authSession = nullptr; QMap<History*, QMap<MsgId, TimeMs>> _whenMaps; struct Waiter { Waiter(MsgId msg, TimeMs when, PeerData *notifyByFrom) : msg(msg) , when(when) , notifyByFrom(notifyByFrom) { } MsgId msg; TimeMs when; PeerData *notifyByFrom; }; using Waiters = QMap<History*, Waiter>; Waiters _waiters; Waiters _settingWaiters; SingleTimer _waitTimer; QMap<History*, QMap<TimeMs, PeerData*>> _whenAlerts; std::unique_ptr<Manager> _manager; base::Observable<ChangeType> _settingsChanged; std::unique_ptr<Media::Audio::Track> _soundTrack; }; class Manager { public: Manager(System *system) : _system(system) { } void showNotification(HistoryItem *item, int forwardedCount) { doShowNotification(item, forwardedCount); } void updateAll() { doUpdateAll(); } void clearAll() { doClearAll(); } void clearAllFast() { doClearAllFast(); } void clearFromItem(HistoryItem *item) { doClearFromItem(item); } void clearFromHistory(History *history) { doClearFromHistory(history); } void notificationActivated(PeerId peerId, MsgId msgId); void notificationReplied(PeerId peerId, MsgId msgId, const QString &reply); struct DisplayOptions { bool hideNameAndPhoto; bool hideMessageText; bool hideReplyButton; }; static DisplayOptions getNotificationOptions(HistoryItem *item); virtual ~Manager() = default; protected: System *system() const { return _system; } virtual void doUpdateAll() = 0; virtual void doShowNotification(HistoryItem *item, int forwardedCount) = 0; virtual void doClearAll() = 0; virtual void doClearAllFast() = 0; virtual void doClearFromItem(HistoryItem *item) = 0; virtual void doClearFromHistory(History *history) = 0; virtual void onBeforeNotificationActivated(PeerId peerId, MsgId msgId) { } virtual void onAfterNotificationActivated(PeerId peerId, MsgId msgId) { } private: void openNotificationMessage( not_null<History*> history, MsgId messageId); System *_system = nullptr; }; class NativeManager : public Manager { protected: using Manager::Manager; void doUpdateAll() override { doClearAllFast(); } void doClearAll() override { doClearAllFast(); } void doClearFromItem(HistoryItem *item) override { } void doShowNotification(HistoryItem *item, int forwardedCount) override; virtual void doShowNativeNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, const QString &msg, bool hideNameAndPhoto, bool hideReplyButton) = 0; }; } // namespace Notifications } // namespace Window ��������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/window/notifications_manager_default.cpp�����������������������0000664�0000000�0000000�00000065373�13262451251�0027523�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "window/notifications_manager_default.h" #include "platform/platform_notifications_manager.h" #include "application.h" #include "messenger.h" #include "lang/lang_keys.h" #include "ui/widgets/buttons.h" #include "ui/widgets/input_fields.h" #include "dialogs/dialogs_layout.h" #include "window/themes/window_theme.h" #include "styles/style_dialogs.h" #include "styles/style_boxes.h" #include "styles/style_window.h" #include "storage/file_download.h" #include "auth_session.h" #include "history/history.h" #include "history/history_item.h" #include "platform/platform_specific.h" namespace Window { namespace Notifications { namespace Default { namespace { int notificationMaxHeight() { return st::notifyMinHeight + st::notifyReplyArea.heightMax + st::notifyBorderWidth; } QPoint notificationStartPosition() { auto r = psDesktopRect(); auto isLeft = Notify::IsLeftCorner(Global::NotificationsCorner()); auto isTop = Notify::IsTopCorner(Global::NotificationsCorner()); auto x = (isLeft == rtl()) ? (r.x() + r.width() - st::notifyWidth - st::notifyDeltaX) : (r.x() + st::notifyDeltaX); auto y = isTop ? r.y() : (r.y() + r.height()); return QPoint(x, y); } internal::Widget::Direction notificationShiftDirection() { auto isTop = Notify::IsTopCorner(Global::NotificationsCorner()); return isTop ? internal::Widget::Direction::Down : internal::Widget::Direction::Up; } } // namespace std::unique_ptr<Manager> Create(System *system) { return std::make_unique<Manager>(system); } Manager::Manager(System *system) : Notifications::Manager(system) { subscribe(system->authSession()->downloader().taskFinished(), [this] { for_const (auto ¬ification, _notifications) { notification->updatePeerPhoto(); } }); subscribe(system->settingsChanged(), [this](ChangeType change) { settingsChanged(change); }); _inputCheckTimer.setTimeoutHandler([this] { checkLastInput(); }); } Manager::QueuedNotification::QueuedNotification( not_null<HistoryItem*> item , int forwardedCount) : history(item->history()) , peer(history->peer) , author((!peer->isUser() && !item->isPost()) ? item->author().get() : nullptr) , item((forwardedCount < 2) ? item.get() : nullptr) , forwardedCount(forwardedCount) { } QPixmap Manager::hiddenUserpicPlaceholder() const { if (_hiddenUserpicPlaceholder.isNull()) { _hiddenUserpicPlaceholder = App::pixmapFromImageInPlace(Messenger::Instance().logoNoMargin().scaled(st::notifyPhotoSize, st::notifyPhotoSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); _hiddenUserpicPlaceholder.setDevicePixelRatio(cRetinaFactor()); } return _hiddenUserpicPlaceholder; } bool Manager::hasReplyingNotification() const { for_const (auto ¬ification, _notifications) { if (notification->isReplying()) { return true; } } return false; } void Manager::settingsChanged(ChangeType change) { if (change == ChangeType::Corner) { auto startPosition = notificationStartPosition(); auto shiftDirection = notificationShiftDirection(); for_const (auto ¬ification, _notifications) { notification->updatePosition(startPosition, shiftDirection); } if (_hideAll) { _hideAll->updatePosition(startPosition, shiftDirection); } } else if (change == ChangeType::MaxCount) { int allow = Global::NotificationsCount(); for (int i = _notifications.size(); i != 0;) { auto ¬ification = _notifications[--i]; if (notification->isUnlinked()) continue; if (--allow < 0) { notification->unlinkHistory(); } } if (allow > 0) { for (int i = 0; i != allow; ++i) { showNextFromQueue(); } } } else if (change == ChangeType::DemoIsShown) { auto demoIsShown = Global::NotificationsDemoIsShown(); _demoMasterOpacity.start([this] { demoMasterOpacityCallback(); }, demoIsShown ? 1. : 0., demoIsShown ? 0. : 1., st::notifyFastAnim); } } void Manager::demoMasterOpacityCallback() { for_const (auto ¬ification, _notifications) { notification->updateOpacity(); } if (_hideAll) { _hideAll->updateOpacity(); } } float64 Manager::demoMasterOpacity() const { return _demoMasterOpacity.current(Global::NotificationsDemoIsShown() ? 0. : 1.); } void Manager::checkLastInput() { auto replying = hasReplyingNotification(); auto waiting = false; for_const (auto ¬ification, _notifications) { if (!notification->checkLastInput(replying)) { waiting = true; } } if (waiting) { _inputCheckTimer.start(300); } } void Manager::startAllHiding() { if (!hasReplyingNotification()) { int notHidingCount = 0; for_const (auto ¬ification, _notifications) { if (notification->isShowing()) { ++notHidingCount; } else { notification->startHiding(); } } notHidingCount += _queuedNotifications.size(); if (_hideAll && notHidingCount < 2) { _hideAll->startHiding(); } } } void Manager::stopAllHiding() { for_const (auto ¬ification, _notifications) { notification->stopHiding(); } if (_hideAll) { _hideAll->stopHiding(); } } void Manager::showNextFromQueue() { auto guard = gsl::finally([this] { if (_positionsOutdated) { moveWidgets(); } }); if (_queuedNotifications.empty()) { return; } int count = Global::NotificationsCount(); for_const (auto ¬ification, _notifications) { if (notification->isUnlinked()) continue; --count; } if (count <= 0) { return; } auto startPosition = notificationStartPosition(); auto startShift = 0; auto shiftDirection = notificationShiftDirection(); do { auto queued = _queuedNotifications.front(); _queuedNotifications.pop_front(); auto notification = std::make_unique<Notification>( this, queued.history, queued.peer, queued.author, queued.item, queued.forwardedCount, startPosition, startShift, shiftDirection); _notifications.push_back(std::move(notification)); --count; } while (count > 0 && !_queuedNotifications.empty()); _positionsOutdated = true; checkLastInput(); } void Manager::moveWidgets() { auto shift = st::notifyDeltaY; int lastShift = 0, lastShiftCurrent = 0, count = 0; for (int i = _notifications.size(); i != 0;) { auto ¬ification = _notifications[--i]; if (notification->isUnlinked()) continue; notification->changeShift(shift); shift += notification->height() + st::notifyDeltaY; lastShiftCurrent = notification->currentShift(); lastShift = shift; ++count; } if (count > 1 || !_queuedNotifications.empty()) { auto deltaY = st::notifyHideAllHeight + st::notifyDeltaY; if (!_hideAll) { _hideAll = std::make_unique<HideAllButton>(this, notificationStartPosition(), lastShiftCurrent, notificationShiftDirection()); } _hideAll->changeShift(lastShift); _hideAll->stopHiding(); } else if (_hideAll) { _hideAll->startHidingFast(); } } void Manager::changeNotificationHeight(Notification *notification, int newHeight) { auto deltaHeight = newHeight - notification->height(); if (!deltaHeight) return; notification->addToHeight(deltaHeight); auto it = std::find_if(_notifications.cbegin(), _notifications.cend(), [notification](auto &item) { return (item.get() == notification); }); if (it != _notifications.cend()) { for (auto i = _notifications.cbegin(); i != it; ++i) { auto ¬ification = *i; if (notification->isUnlinked()) continue; notification->addToShift(deltaHeight); } } if (_hideAll) { _hideAll->addToShift(deltaHeight); } } void Manager::unlinkFromShown(Notification *remove) { if (remove) { if (remove->unlinkHistory()) { _positionsOutdated = true; } } showNextFromQueue(); } void Manager::removeWidget(internal::Widget *remove) { if (remove == _hideAll.get()) { _hideAll.reset(); } else if (remove) { auto it = std::find_if(_notifications.cbegin(), _notifications.cend(), [remove](auto &item) { return item.get() == remove; }); if (it != _notifications.cend()) { _notifications.erase(it); _positionsOutdated = true; } } showNextFromQueue(); } void Manager::doShowNotification(HistoryItem *item, int forwardedCount) { _queuedNotifications.push_back(QueuedNotification(item, forwardedCount)); showNextFromQueue(); } void Manager::doClearAll() { _queuedNotifications.clear(); for_const (auto ¬ification, _notifications) { notification->unlinkHistory(); } showNextFromQueue(); } void Manager::doClearAllFast() { _queuedNotifications.clear(); base::take(_notifications); base::take(_hideAll); } void Manager::doClearFromHistory(History *history) { for (auto i = _queuedNotifications.begin(); i != _queuedNotifications.cend();) { if (i->history == history) { i = _queuedNotifications.erase(i); } else { ++i; } } for_const (auto ¬ification, _notifications) { if (notification->unlinkHistory(history)) { _positionsOutdated = true; } } showNextFromQueue(); } void Manager::doClearFromItem(HistoryItem *item) { _queuedNotifications.erase(std::remove_if(_queuedNotifications.begin(), _queuedNotifications.end(), [item](auto &queued) { return (queued.item == item); }), _queuedNotifications.cend()); auto showNext = false; for_const (auto ¬ification, _notifications) { if (notification->unlinkItem(item)) { showNext = true; } } if (showNext) { // This call invalidates _notifications iterators. showNextFromQueue(); } } void Manager::doUpdateAll() { for_const (auto ¬ification, _notifications) { notification->updateNotifyDisplay(); } } Manager::~Manager() { clearAllFast(); } namespace internal { Widget::Widget(Manager *manager, QPoint startPosition, int shift, Direction shiftDirection) : TWidget(nullptr) , _manager(manager) , _startPosition(startPosition) , _direction(shiftDirection) , a_shift(shift) , _a_shift(animation(this, &Widget::step_shift)) { setWindowOpacity(0.); setWindowFlags(Qt::WindowFlags(Qt::FramelessWindowHint) | Qt::WindowStaysOnTopHint | Qt::BypassWindowManagerHint | Qt::NoDropShadowWindowHint | Qt::Tool); setAttribute(Qt::WA_MacAlwaysShowToolWindow); setAttribute(Qt::WA_OpaquePaintEvent); Platform::InitOnTopPanel(this); _a_opacity.start([this] { opacityAnimationCallback(); }, 0., 1., st::notifyFastAnim); } void Widget::destroyDelayed() { hide(); if (_deleted) return; _deleted = true; // Ubuntu has a lag if a fully transparent widget is destroyed immediately. App::CallDelayed(1000, this, [this] { manager()->removeWidget(this); }); } void Widget::opacityAnimationCallback() { updateOpacity(); update(); if (!_a_opacity.animating() && _hiding) { destroyDelayed(); } } void Widget::step_shift(float64 ms, bool timer) { float64 dt = ms / float64(st::notifyFastAnim); if (dt >= 1) { a_shift.finish(); } else { a_shift.update(dt, anim::linear); } moveByShift(); } void Widget::hideSlow() { hideAnimated(st::notifySlowHide, anim::easeInCirc); } void Widget::hideFast() { hideAnimated(st::notifyFastAnim, anim::linear); } void Widget::hideStop() { if (_hiding) { _hiding = false; _a_opacity.start([this] { opacityAnimationCallback(); }, 0., 1., st::notifyFastAnim); } } void Widget::hideAnimated(float64 duration, const anim::transition &func) { _hiding = true; _a_opacity.start([this] { opacityAnimationCallback(); }, 1., 0., duration, func); } void Widget::updateOpacity() { setWindowOpacity(_a_opacity.current(_hiding ? 0. : 1.) * _manager->demoMasterOpacity()); } void Widget::changeShift(int top) { a_shift.start(top); _a_shift.start(); } void Widget::updatePosition(QPoint startPosition, Direction shiftDirection) { _startPosition = startPosition; _direction = shiftDirection; moveByShift(); } void Widget::addToHeight(int add) { auto newHeight = height() + add; auto newPosition = computePosition(newHeight); updateGeometry(newPosition.x(), newPosition.y(), width(), newHeight); psUpdateOverlayed(this); } void Widget::updateGeometry(int x, int y, int width, int height) { setGeometry(x, y, width, height); update(); } void Widget::addToShift(int add) { a_shift.add(add); moveByShift(); } void Widget::moveByShift() { move(computePosition(height())); } QPoint Widget::computePosition(int height) const { auto realShift = qRound(a_shift.current()); if (_direction == Direction::Up) { realShift = -realShift - height; } return QPoint(_startPosition.x(), _startPosition.y() + realShift); } Background::Background(QWidget *parent) : TWidget(parent) { setAttribute(Qt::WA_OpaquePaintEvent); } void Background::paintEvent(QPaintEvent *e) { Painter p(this); p.fillRect(rect(), st::notificationBg); p.fillRect(0, 0, st::notifyBorderWidth, height(), st::notifyBorder); p.fillRect(width() - st::notifyBorderWidth, 0, st::notifyBorderWidth, height(), st::notifyBorder); p.fillRect(st::notifyBorderWidth, height() - st::notifyBorderWidth, width() - 2 * st::notifyBorderWidth, st::notifyBorderWidth, st::notifyBorder); } Notification::Notification(Manager *manager, History *history, PeerData *peer, PeerData *author, HistoryItem *msg, int forwardedCount, QPoint startPosition, int shift, Direction shiftDirection) : Widget(manager, startPosition, shift, shiftDirection) , _history(history) , _peer(peer) , _author(author) , _item(msg) , _forwardedCount(forwardedCount) #ifdef Q_OS_WIN , _started(GetTickCount()) #endif // Q_OS_WIN , _close(this, st::notifyClose) , _reply(this, langFactory(lng_notification_reply), st::defaultBoxButton) { subscribe(Lang::Current().updated(), [this] { refreshLang(); }); auto position = computePosition(st::notifyMinHeight); updateGeometry(position.x(), position.y(), st::notifyWidth, st::notifyMinHeight); _userpicLoaded = _peer ? _peer->userpicLoaded() : true; updateNotifyDisplay(); _hideTimer.setSingleShot(true); connect(&_hideTimer, SIGNAL(timeout()), this, SLOT(onHideByTimer())); _close->setClickedCallback([this] { unlinkHistoryInManager(); }); _close->setAcceptBoth(true); _close->moveToRight(st::notifyClosePos.x(), st::notifyClosePos.y()); _close->show(); _reply->setClickedCallback([this] { showReplyField(); }); _replyPadding = st::notifyMinHeight - st::notifyPhotoPos.y() - st::notifyPhotoSize; updateReplyGeometry(); _reply->hide(); prepareActionsCache(); subscribe(Window::Theme::Background(), [this](const Window::Theme::BackgroundUpdate &data) { if (data.paletteChanged()) { updateNotifyDisplay(); if (!_buttonsCache.isNull()) { prepareActionsCache(); } update(); if (_background) { _background->update(); } } }); show(); } void Notification::updateReplyGeometry() { _reply->moveToRight(_replyPadding, height() - _reply->height() - _replyPadding); } void Notification::refreshLang() { InvokeQueued(this, [this] { updateReplyGeometry(); }); } void Notification::prepareActionsCache() { auto replyCache = Ui::GrabWidget(_reply); auto fadeWidth = st::notifyFadeRight.width(); auto actionsTop = st::notifyTextTop + st::msgNameFont->height; auto replyRight = _replyPadding - st::notifyBorderWidth; auto actionsCacheWidth = _reply->width() + replyRight + fadeWidth; auto actionsCacheHeight = height() - actionsTop - st::notifyBorderWidth; auto actionsCacheImg = QImage(QSize(actionsCacheWidth, actionsCacheHeight) * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); actionsCacheImg.setDevicePixelRatio(cRetinaFactor()); actionsCacheImg.fill(Qt::transparent); { Painter p(&actionsCacheImg); st::notifyFadeRight.fill(p, rtlrect(0, 0, fadeWidth, actionsCacheHeight, actionsCacheWidth)); p.fillRect(rtlrect(fadeWidth, 0, actionsCacheWidth - fadeWidth, actionsCacheHeight, actionsCacheWidth), st::notificationBg); p.drawPixmapRight(replyRight, _reply->y() - actionsTop, actionsCacheWidth, replyCache); } _buttonsCache = App::pixmapFromImageInPlace(std::move(actionsCacheImg)); } bool Notification::checkLastInput(bool hasReplyingNotifications) { if (!_waitingForInput) return true; auto wasUserInput = true; // TODO #ifdef Q_OS_WIN LASTINPUTINFO lii; lii.cbSize = sizeof(LASTINPUTINFO); BOOL res = GetLastInputInfo(&lii); wasUserInput = (!res || lii.dwTime >= _started); #endif // Q_OS_WIN if (wasUserInput) { _waitingForInput = false; if (!hasReplyingNotifications) { _hideTimer.start(st::notifyWaitLongHide); } return true; } return false; } void Notification::onReplyResize() { changeHeight(st::notifyMinHeight + _replyArea->height() + st::notifyBorderWidth); } void Notification::onReplySubmit(bool ctrlShiftEnter) { sendReply(); } void Notification::onReplyCancel() { unlinkHistoryInManager(); } void Notification::updateGeometry(int x, int y, int width, int height) { if (height > st::notifyMinHeight) { if (!_background) { _background.create(this); } _background->setGeometry(0, st::notifyMinHeight, width, height - st::notifyMinHeight); } else if (_background) { _background.destroy(); } Widget::updateGeometry(x, y, width, height); } void Notification::paintEvent(QPaintEvent *e) { Painter p(this); p.setClipRect(e->rect()); p.drawPixmap(0, 0, _cache); auto buttonsLeft = st::notifyPhotoPos.x() + st::notifyPhotoSize + st::notifyTextLeft; auto buttonsTop = st::notifyTextTop + st::msgNameFont->height; if (a_actionsOpacity.animating(getms())) { p.setOpacity(a_actionsOpacity.current()); p.drawPixmapRight(st::notifyBorderWidth, buttonsTop, width(), _buttonsCache); } else if (_actionsVisible) { p.drawPixmapRight(st::notifyBorderWidth, buttonsTop, width(), _buttonsCache); } } void Notification::actionsOpacityCallback() { update(); if (!a_actionsOpacity.animating() && _actionsVisible) { _reply->show(); } } void Notification::updateNotifyDisplay() { if (!_history || !_peer || (!_item && _forwardedCount < 2)) return; auto options = Manager::getNotificationOptions(_item); _hideReplyButton = options.hideReplyButton; int32 w = width(), h = height(); QImage img(w * cIntRetinaFactor(), h * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); if (cRetina()) img.setDevicePixelRatio(cRetinaFactor()); img.fill(st::notificationBg->c); { Painter p(&img); p.fillRect(0, 0, w - st::notifyBorderWidth, st::notifyBorderWidth, st::notifyBorder); p.fillRect(w - st::notifyBorderWidth, 0, st::notifyBorderWidth, h - st::notifyBorderWidth, st::notifyBorder); p.fillRect(st::notifyBorderWidth, h - st::notifyBorderWidth, w - st::notifyBorderWidth, st::notifyBorderWidth, st::notifyBorder); p.fillRect(0, st::notifyBorderWidth, st::notifyBorderWidth, h - st::notifyBorderWidth, st::notifyBorder); if (!options.hideNameAndPhoto) { _history->peer->loadUserpic(true, true); _history->peer->paintUserpicLeft(p, st::notifyPhotoPos.x(), st::notifyPhotoPos.y(), width(), st::notifyPhotoSize); } else { p.drawPixmap(st::notifyPhotoPos.x(), st::notifyPhotoPos.y(), manager()->hiddenUserpicPlaceholder()); } int32 itemWidth = w - st::notifyPhotoPos.x() - st::notifyPhotoSize - st::notifyTextLeft - st::notifyClosePos.x() - st::notifyClose.width; QRect rectForName(st::notifyPhotoPos.x() + st::notifyPhotoSize + st::notifyTextLeft, st::notifyTextTop, itemWidth, st::msgNameFont->height); if (!options.hideNameAndPhoto) { if (const auto chatTypeIcon = Dialogs::Layout::ChatTypeIcon(_history->peer, false, false)) { chatTypeIcon->paint(p, rectForName.topLeft(), w); rectForName.setLeft(rectForName.left() + st::dialogsChatTypeSkip); } } if (!options.hideMessageText) { const HistoryItem *textCachedFor = 0; Text itemTextCache(itemWidth); QRect r(st::notifyPhotoPos.x() + st::notifyPhotoSize + st::notifyTextLeft, st::notifyItemTop + st::msgNameFont->height, itemWidth, 2 * st::dialogsTextFont->height); if (_item) { auto active = false, selected = false; _item->drawInDialog( p, r, active, selected, HistoryItem::DrawInDialog::Normal, textCachedFor, itemTextCache); } else if (_forwardedCount > 1) { p.setFont(st::dialogsTextFont); if (_author) { itemTextCache.setText(st::dialogsTextStyle, _author->name); p.setPen(st::dialogsTextFgService); itemTextCache.drawElided(p, r.left(), r.top(), r.width()); r.setTop(r.top() + st::dialogsTextFont->height); } p.setPen(st::dialogsTextFg); p.drawText(r.left(), r.top() + st::dialogsTextFont->ascent, lng_forward_messages(lt_count, _forwardedCount)); } } else { static QString notifyText = st::dialogsTextFont->elided(lang(lng_notification_preview), itemWidth); p.setFont(st::dialogsTextFont); p.setPen(st::dialogsTextFgService); p.drawText(st::notifyPhotoPos.x() + st::notifyPhotoSize + st::notifyTextLeft, st::notifyItemTop + st::msgNameFont->height + st::dialogsTextFont->ascent, notifyText); } p.setPen(st::dialogsNameFg); if (!options.hideNameAndPhoto) { _history->peer->dialogName().drawElided(p, rectForName.left(), rectForName.top(), rectForName.width()); } else { p.setFont(st::msgNameFont); static QString notifyTitle = st::msgNameFont->elided(qsl("Telegram Desktop"), rectForName.width()); p.drawText(rectForName.left(), rectForName.top() + st::msgNameFont->ascent, notifyTitle); } } _cache = App::pixmapFromImageInPlace(std::move(img)); if (!canReply()) { toggleActionButtons(false); } update(); } void Notification::updatePeerPhoto() { if (_userpicLoaded || !_peer || !_peer->userpicLoaded()) { return; } _userpicLoaded = true; auto img = _cache.toImage(); { Painter p(&img); _peer->paintUserpicLeft(p, st::notifyPhotoPos.x(), st::notifyPhotoPos.y(), width(), st::notifyPhotoSize); } _cache = App::pixmapFromImageInPlace(std::move(img)); update(); } bool Notification::unlinkItem(HistoryItem *deleted) { auto unlink = (_item && _item == deleted); if (unlink) { _item = nullptr; unlinkHistory(); } return unlink; } bool Notification::canReply() const { return !_hideReplyButton && (_item != nullptr) && !App::passcoded() && (Global::NotifyView() <= dbinvShowPreview); } void Notification::unlinkHistoryInManager() { manager()->unlinkFromShown(this); } void Notification::toggleActionButtons(bool visible) { if (_actionsVisible != visible) { _actionsVisible = visible; a_actionsOpacity.start([this] { actionsOpacityCallback(); }, _actionsVisible ? 0. : 1., _actionsVisible ? 1. : 0., st::notifyActionsDuration); _reply->clearState(); _reply->hide(); } } void Notification::showReplyField() { activateWindow(); if (_replyArea) { _replyArea->setFocus(); return; } stopHiding(); _background.create(this); _background->setGeometry(0, st::notifyMinHeight, width(), st::notifySendReply.height + st::notifyBorderWidth); _background->show(); _replyArea.create(this, st::notifyReplyArea, langFactory(lng_message_ph), QString()); _replyArea->resize(width() - st::notifySendReply.width - 2 * st::notifyBorderWidth, st::notifySendReply.height); _replyArea->moveToLeft(st::notifyBorderWidth, st::notifyMinHeight); _replyArea->show(); _replyArea->setFocus(); _replyArea->setMaxLength(MaxMessageSize); _replyArea->setCtrlEnterSubmit(Ui::CtrlEnterSubmit::Both); // Catch mouse press event to activate the window. QCoreApplication::instance()->installEventFilter(this); connect(_replyArea, SIGNAL(resized()), this, SLOT(onReplyResize())); connect(_replyArea, SIGNAL(submitted(bool)), this, SLOT(onReplySubmit(bool))); connect(_replyArea, SIGNAL(cancelled()), this, SLOT(onReplyCancel())); _replySend.create(this, st::notifySendReply); _replySend->moveToRight(st::notifyBorderWidth, st::notifyMinHeight); _replySend->show(); _replySend->setClickedCallback([this] { sendReply(); }); toggleActionButtons(false); onReplyResize(); update(); } void Notification::sendReply() { if (!_history) return; auto peerId = _history->peer->id; auto msgId = _item ? _item->id : ShowAtUnreadMsgId; manager()->notificationReplied(peerId, msgId, _replyArea->getLastText()); manager()->startAllHiding(); } void Notification::changeHeight(int newHeight) { manager()->changeNotificationHeight(this, newHeight); } bool Notification::unlinkHistory(History *history) { auto unlink = _history && (history == _history || !history); if (unlink) { hideFast(); _history = nullptr; _item = nullptr; } return unlink; } void Notification::enterEventHook(QEvent *e) { if (!_history) return; manager()->stopAllHiding(); if (!_replyArea && canReply()) { toggleActionButtons(true); } } void Notification::leaveEventHook(QEvent *e) { if (!_history) return; manager()->startAllHiding(); toggleActionButtons(false); } void Notification::startHiding() { if (!_history) return; hideSlow(); } void Notification::mousePressEvent(QMouseEvent *e) { if (!_history) return; if (e->button() == Qt::RightButton) { unlinkHistoryInManager(); } else { e->ignore(); auto peerId = _history->peer->id; auto msgId = _item ? _item->id : ShowAtUnreadMsgId; manager()->notificationActivated(peerId, msgId); } } bool Notification::eventFilter(QObject *o, QEvent *e) { if (e->type() == QEvent::MouseButtonPress) { if (auto receiver = qobject_cast<QWidget*>(o)) { if (isAncestorOf(receiver)) { activateWindow(); } } } return false; } void Notification::stopHiding() { if (!_history) return; _hideTimer.stop(); Widget::hideStop(); } void Notification::onHideByTimer() { startHiding(); } HideAllButton::HideAllButton(Manager *manager, QPoint startPosition, int shift, Direction shiftDirection) : Widget(manager, startPosition, shift, shiftDirection) { setCursor(style::cur_pointer); auto position = computePosition(st::notifyHideAllHeight); updateGeometry(position.x(), position.y(), st::notifyWidth, st::notifyHideAllHeight); hide(); createWinId(); subscribe(Window::Theme::Background(), [this](const Window::Theme::BackgroundUpdate &data) { if (data.paletteChanged()) { update(); } }); show(); } void HideAllButton::startHiding() { hideSlow(); } void HideAllButton::startHidingFast() { hideFast(); } void HideAllButton::stopHiding() { hideStop(); } void HideAllButton::enterEventHook(QEvent *e) { _mouseOver = true; update(); } void HideAllButton::leaveEventHook(QEvent *e) { _mouseOver = false; update(); } void HideAllButton::mousePressEvent(QMouseEvent *e) { _mouseDown = true; } void HideAllButton::mouseReleaseEvent(QMouseEvent *e) { auto mouseDown = base::take(_mouseDown); if (mouseDown && _mouseOver) { manager()->clearAll(); } } void HideAllButton::paintEvent(QPaintEvent *e) { Painter p(this); p.setClipRect(e->rect()); p.fillRect(rect(), _mouseOver ? st::lightButtonBgOver : st::lightButtonBg); p.fillRect(0, 0, width(), st::notifyBorderWidth, st::notifyBorder); p.fillRect(0, height() - st::notifyBorderWidth, width(), st::notifyBorderWidth, st::notifyBorder); p.fillRect(0, st::notifyBorderWidth, st::notifyBorderWidth, height() - 2 * st::notifyBorderWidth, st::notifyBorder); p.fillRect(width() - st::notifyBorderWidth, st::notifyBorderWidth, st::notifyBorderWidth, height() - 2 * st::notifyBorderWidth, st::notifyBorder); p.setFont(st::defaultLinkButton.font); p.setPen(_mouseOver ? st::lightButtonFgOver : st::lightButtonFg); p.drawText(rect(), lang(lng_notification_hide_all), style::al_center); } } // namespace internal } // namespace Default } // namespace Notifications } // namespace Window ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/window/notifications_manager_default.h�������������������������0000664�0000000�0000000�00000014263�13262451251�0027160�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "window/notifications_manager.h" #include "core/single_timer.h" namespace Ui { class IconButton; class RoundButton; class InputArea; } // namespace Ui namespace Window { namespace Notifications { namespace Default { namespace internal { class Widget; class Notification; class HideAllButton; } // namespace internal class Manager; std::unique_ptr<Manager> Create(System *system); class Manager : public Notifications::Manager, private base::Subscriber { public: Manager(System *system); template <typename Method> void enumerateNotifications(Method method) { for_const (auto ¬ification, _notifications) { method(notification); } } ~Manager(); private: friend class internal::Notification; friend class internal::HideAllButton; friend class internal::Widget; using Notification = internal::Notification; using HideAllButton = internal::HideAllButton; QPixmap hiddenUserpicPlaceholder() const; void doUpdateAll() override; void doShowNotification(HistoryItem *item, int forwardedCount) override; void doClearAll() override; void doClearAllFast() override; void doClearFromHistory(History *history) override; void doClearFromItem(HistoryItem *item) override; void showNextFromQueue(); void unlinkFromShown(Notification *remove); void startAllHiding(); void stopAllHiding(); void checkLastInput(); void removeWidget(internal::Widget *remove); float64 demoMasterOpacity() const; void demoMasterOpacityCallback(); void moveWidgets(); void changeNotificationHeight(Notification *widget, int newHeight); void settingsChanged(ChangeType change); bool hasReplyingNotification() const; std::vector<std::unique_ptr<Notification>> _notifications; std::unique_ptr<HideAllButton> _hideAll; bool _positionsOutdated = false; SingleTimer _inputCheckTimer; struct QueuedNotification { QueuedNotification(not_null<HistoryItem*> item, int forwardedCount); not_null<History*> history; not_null<PeerData*> peer; PeerData *author; HistoryItem *item; int forwardedCount; }; std::deque<QueuedNotification> _queuedNotifications; Animation _demoMasterOpacity; mutable QPixmap _hiddenUserpicPlaceholder; }; namespace internal { class Widget : public TWidget, protected base::Subscriber { public: enum class Direction { Up, Down, }; Widget(Manager *manager, QPoint startPosition, int shift, Direction shiftDirection); bool isShowing() const { return _a_opacity.animating() && !_hiding; } void updateOpacity(); void changeShift(int top); int currentShift() const { return a_shift.current(); } void updatePosition(QPoint startPosition, Direction shiftDirection); void addToHeight(int add); void addToShift(int add); protected: void hideSlow(); void hideFast(); void hideStop(); QPoint computePosition(int height) const; virtual void updateGeometry(int x, int y, int width, int height); protected: Manager *manager() const { return _manager; } private: void opacityAnimationCallback(); void destroyDelayed(); void moveByShift(); void hideAnimated(float64 duration, const anim::transition &func); void step_shift(float64 ms, bool timer); Manager *_manager = nullptr; bool _hiding = false; bool _deleted = false; Animation _a_opacity; QPoint _startPosition; Direction _direction; anim::value a_shift; BasicAnimation _a_shift; }; class Background : public TWidget { public: Background(QWidget *parent); protected: void paintEvent(QPaintEvent *e) override; }; class Notification : public Widget { Q_OBJECT public: Notification(Manager *manager, History *history, PeerData *peer, PeerData *author, HistoryItem *item, int forwardedCount, QPoint startPosition, int shift, Direction shiftDirection); void startHiding(); void stopHiding(); void updateNotifyDisplay(); void updatePeerPhoto(); bool isUnlinked() const { return !_history; } bool isReplying() const { return _replyArea && !isUnlinked(); } // Called only by Manager. bool unlinkItem(HistoryItem *del); bool unlinkHistory(History *history = nullptr); bool checkLastInput(bool hasReplyingNotifications); protected: void enterEventHook(QEvent *e) override; void leaveEventHook(QEvent *e) override; void paintEvent(QPaintEvent *e) override; void mousePressEvent(QMouseEvent *e) override; bool eventFilter(QObject *o, QEvent *e) override; private slots: void onHideByTimer(); void onReplyResize(); void onReplySubmit(bool ctrlShiftEnter); void onReplyCancel(); private: void refreshLang(); void updateReplyGeometry(); bool canReply() const; void unlinkHistoryInManager(); void toggleActionButtons(bool visible); void prepareActionsCache(); void showReplyField(); void sendReply(); void changeHeight(int newHeight); void updateGeometry(int x, int y, int width, int height) override; void actionsOpacityCallback(); QPixmap _cache; bool _hideReplyButton = false; bool _actionsVisible = false; Animation a_actionsOpacity; QPixmap _buttonsCache; #ifdef Q_OS_WIN TimeMs _started; #endif // Q_OS_WIN History *_history; PeerData *_peer; PeerData *_author; HistoryItem *_item; int _forwardedCount; object_ptr<Ui::IconButton> _close; object_ptr<Ui::RoundButton> _reply; object_ptr<Background> _background = { nullptr }; object_ptr<Ui::InputArea> _replyArea = { nullptr }; object_ptr<Ui::IconButton> _replySend = { nullptr }; bool _waitingForInput = true; QTimer _hideTimer; int _replyPadding = 0; bool _userpicLoaded = false; }; class HideAllButton : public Widget { public: HideAllButton(Manager *manager, QPoint startPosition, int shift, Direction shiftDirection); void startHiding(); void startHidingFast(); void stopHiding(); protected: void enterEventHook(QEvent *e) override; void leaveEventHook(QEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void paintEvent(QPaintEvent *e) override; private: bool _mouseOver = false; bool _mouseDown = false; }; } // namespace internal } // namespace Default } // namespace Notifications } // namespace Window ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/window/notifications_utilities.cpp�����������������������������0000664�0000000�0000000�00000005132�13262451251�0026403�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "window/notifications_utilities.h" #include "platform/platform_specific.h" #include "messenger.h" #include "styles/style_window.h" namespace Window { namespace Notifications { namespace { // Delete notify photo file after 1 minute of not using. constexpr int kNotifyDeletePhotoAfterMs = 60000; } // namespace CachedUserpics::CachedUserpics(Type type) : _type(type) { connect(&_clearTimer, SIGNAL(timeout()), this, SLOT(onClear())); QDir().mkpath(cWorkingDir() + qsl("tdata/temp")); } QString CachedUserpics::get(const StorageKey &key, PeerData *peer) { auto ms = getms(true); auto i = _images.find(key); if (i != _images.cend()) { if (i->until) { i->until = ms + kNotifyDeletePhotoAfterMs; clearInMs(-kNotifyDeletePhotoAfterMs); } } else { Image v; if (key.first) { v.until = ms + kNotifyDeletePhotoAfterMs; clearInMs(-kNotifyDeletePhotoAfterMs); } else { v.until = 0; } v.path = cWorkingDir() + qsl("tdata/temp/") + QString::number(rand_value<uint64>(), 16) + qsl(".png"); if (key.first || key.second) { if (_type == Type::Rounded) { peer->saveUserpicRounded(v.path, st::notifyMacPhotoSize); } else { peer->saveUserpic(v.path, st::notifyMacPhotoSize); } } else { Messenger::Instance().logoNoMargin().save(v.path, "PNG"); } i = _images.insert(key, v); _someSavedFlag = true; } return i->path; } TimeMs CachedUserpics::clear(TimeMs ms) { TimeMs result = 0; for (auto i = _images.begin(); i != _images.end();) { if (!i->until) { ++i; continue; } if (i->until <= ms) { QFile(i->path).remove(); i = _images.erase(i); } else { if (!result) { result = i->until; } else { accumulate_min(result, i->until); } ++i; } } return result; } void CachedUserpics::clearInMs(int ms) { if (ms < 0) { ms = -ms; if (_clearTimer.isActive() && _clearTimer.remainingTime() <= ms) { return; } } _clearTimer.start(ms); } void CachedUserpics::onClear() { auto ms = getms(true); auto minuntil = clear(ms); if (minuntil) { clearInMs(int(minuntil - ms)); } } CachedUserpics::~CachedUserpics() { if (_someSavedFlag) { TimeMs result = 0; for_const (auto &item, _images) { QFile(item.path).remove(); } // This works about 1200ms on Windows for a folder with one image O_o // psDeleteDir(cWorkingDir() + qsl("tdata/temp")); } } } // namespace Notifications } // namespace Window ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/window/notifications_utilities.h�������������������������������0000664�0000000�0000000�00000001637�13262451251�0026056�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "window/notifications_manager.h" #include "core/single_timer.h" namespace Window { namespace Notifications { class CachedUserpics : public QObject { Q_OBJECT public: enum class Type { Rounded, Circled, }; CachedUserpics(Type type); QString get(const StorageKey &key, PeerData *peer); ~CachedUserpics(); private slots: void onClear(); private: void clearInMs(int ms); TimeMs clear(TimeMs ms); Type _type = Type::Rounded; struct Image { TimeMs until; QString path; }; using Images = QMap<StorageKey, Image>; Images _images; bool _someSavedFlag = false; SingleTimer _clearTimer; }; } // namesapce Notifications } // namespace Window �������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/window/player_wrap_widget.cpp����������������������������������0000664�0000000�0000000�00000001472�13262451251�0025332�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "window/player_wrap_widget.h" #include "ui/widgets/shadow.h" namespace Window { PlayerWrapWidget::PlayerWrapWidget(QWidget *parent) : Parent(parent, object_ptr<Media::Player::Widget>(parent)) { sizeValue( ) | rpl::start_with_next([this](const QSize &size) { updateShadowGeometry(size); }, lifetime()); } void PlayerWrapWidget::updateShadowGeometry(const QSize &size) { auto skip = Adaptive::OneColumn() ? 0 : st::lineWidth; entity()->setShadowGeometryToLeft( skip, size.height() - st::lineWidth, size.width() - skip, st::lineWidth); } } // namespace Window ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/window/player_wrap_widget.h������������������������������������0000664�0000000�0000000�00000001227�13262451251�0024775�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include "ui/wrap/slide_wrap.h" #include "media/player/media_player_widget.h" namespace Ui { class PlainShadow; } // namespace Ui namespace Window { class PlayerWrapWidget : public Ui::SlideWrap<Media::Player::Widget> { using Parent = Ui::SlideWrap<Media::Player::Widget>; public: PlayerWrapWidget(QWidget *parent); void updateAdaptiveLayout() { updateShadowGeometry(size()); } void showShadow() { entity()->showShadow(); } void hideShadow() { entity()->hideShadow(); } int contentHeight() const { return qMax(height() - st::lineWidth, 0); } private: void updateShadowGeometry(const QSize &size); }; } // namespace Window �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/window/section_memento.h���������������������������������������0000664�0000000�0000000�00000001420�13262451251�0024270�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace Window { class Controller; class SectionWidget; class LayerWidget; enum class Column; class SectionMemento { public: virtual object_ptr<SectionWidget> createWidget( QWidget *parent, not_null<Controller*> controller, Column column, const QRect &geometry) = 0; virtual object_ptr<LayerWidget> createLayer( not_null<Controller*> controller, const QRect &geometry) { return nullptr; } virtual bool instant() const { return false; } virtual ~SectionMemento() = default; }; } // namespace Window ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/window/section_widget.cpp��������������������������������������0000664�0000000�0000000�00000006731�13262451251�0024454�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "window/section_widget.h" #include <rpl/range.h> #include "application.h" #include "mainwidget.h" #include "window/section_memento.h" #include "window/window_slide_animation.h" #include "window/themes/window_theme.h" namespace Window { SectionWidget::SectionWidget( QWidget *parent, not_null<Window::Controller*> controller) : AbstractSectionWidget(parent, controller) { } void SectionWidget::setGeometryWithTopMoved( const QRect &newGeometry, int topDelta) { _topDelta = topDelta; bool willBeResized = (size() != newGeometry.size()); if (geometry() != newGeometry) { auto weak = make_weak(this); setGeometry(newGeometry); if (!weak) { return; } } if (!willBeResized) { resizeEvent(nullptr); } _topDelta = 0; } void SectionWidget::showAnimated( SlideDirection direction, const SectionSlideParams ¶ms) { if (_showAnimation) return; showChildren(); auto myContentCache = grabForShowAnimation(params); hideChildren(); showAnimatedHook(params); _showAnimation = std::make_unique<SlideAnimation>(); _showAnimation->setDirection(direction); _showAnimation->setRepaintCallback([this] { update(); }); _showAnimation->setFinishedCallback([this] { showFinished(); }); _showAnimation->setPixmaps( params.oldContentCache, myContentCache); _showAnimation->setTopBarShadow(params.withTopBarShadow); _showAnimation->setWithFade(params.withFade); _showAnimation->start(); show(); } std::unique_ptr<SectionMemento> SectionWidget::createMemento() { return nullptr; } void SectionWidget::showFast() { show(); showFinished(); } void SectionWidget::PaintBackground(QWidget *widget, QPaintEvent *event) { Painter p(widget); auto clip = event->rect(); auto fill = QRect(0, 0, widget->width(), App::main()->height()); auto fromy = App::main()->backgroundFromY(); auto x = 0, y = 0; auto cached = App::main()->cachedBackground(fill, x, y); if (cached.isNull()) { if (Window::Theme::Background()->tile()) { auto &pix = Window::Theme::Background()->pixmapForTiled(); auto left = clip.left(); auto top = clip.top(); auto right = clip.left() + clip.width(); auto bottom = clip.top() + clip.height(); auto w = pix.width() / cRetinaFactor(); auto h = pix.height() / cRetinaFactor(); auto sx = qFloor(left / w); auto sy = qFloor((top - fromy) / h); auto cx = qCeil(right / w); auto cy = qCeil((bottom - fromy) / h); for (auto i = sx; i < cx; ++i) { for (auto j = sy; j < cy; ++j) { p.drawPixmap(QPointF(i * w, fromy + j * h), pix); } } } else { PainterHighQualityEnabler hq(p); auto &pix = Window::Theme::Background()->pixmap(); QRect to, from; Window::Theme::ComputeBackgroundRects(fill, pix.size(), to, from); to.moveTop(to.top() + fromy); p.drawPixmap(to, pix, from); } } else { p.drawPixmap(x, fromy + y, cached); } } void SectionWidget::paintEvent(QPaintEvent *e) { if (_showAnimation) { Painter p(this); _showAnimation->paintContents(p, e->rect()); } } void SectionWidget::showFinished() { _showAnimation.reset(); if (isHidden()) return; showChildren(); showFinishedHook(); setInnerFocus(); } rpl::producer<int> SectionWidget::desiredHeight() const { return rpl::single(height()); } SectionWidget::~SectionWidget() = default; } // namespace Window ���������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/window/section_widget.h����������������������������������������0000664�0000000�0000000�00000010022�13262451251�0024105�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/rp_widget.h" #include "dialogs/dialogs_key.h" namespace Window { class Controller; class LayerWidget; class SlideAnimation; struct SectionShow; enum class SlideDirection; enum class Column { First, Second, Third, }; class AbstractSectionWidget : public Ui::RpWidget , protected base::Subscriber { public: AbstractSectionWidget( QWidget *parent, not_null<Window::Controller*> controller) : RpWidget(parent) , _controller(controller) { } // Float player interface. virtual bool wheelEventFromFloatPlayer(QEvent *e) { return false; } virtual QRect rectForFloatPlayer() const { return mapToGlobal(rect()); } protected: not_null<Window::Controller*> controller() const { return _controller; } private: not_null<Window::Controller*> _controller; }; class SectionMemento; struct SectionSlideParams { QPixmap oldContentCache; bool withTopBarShadow = false; bool withTabs = false; bool withFade = false; explicit operator bool() const { return !oldContentCache.isNull(); } }; class SectionWidget : public AbstractSectionWidget { public: SectionWidget(QWidget *parent, not_null<Window::Controller*> controller); virtual Dialogs::RowDescriptor activeChat() const { return {}; } // When resizing the widget with top edge moved up or down and we // want to add this top movement to the scroll position, so inner // content will not move. void setGeometryWithTopMoved(const QRect &newGeometry, int topDelta); virtual bool hasTopBarShadow() const { return false; } virtual bool forceAnimateBack() const { return false; } void showAnimated( SlideDirection direction, const SectionSlideParams ¶ms); void showFast(); // This can be used to grab with or without top bar shadow. // This will be protected when animation preparation will be done inside. virtual QPixmap grabForShowAnimation(const SectionSlideParams ¶ms) { return Ui::GrabWidget(this); } // Attempt to show the required section inside the existing one. // For example if this section already shows exactly the required // memento it can simply return true - it is shown already. // // If this method returns false it is not supposed to modify the memento. // If this method returns true it may modify the memento ("take" heavy items). virtual bool showInternal( not_null<SectionMemento*> memento, const SectionShow ¶ms) = 0; // Create a memento of that section to store it in the history stack. // This method may modify the section ("take" heavy items). virtual std::unique_ptr<SectionMemento> createMemento(); void setInnerFocus() { doSetInnerFocus(); } virtual rpl::producer<int> desiredHeight() const; // Some sections convert to layers on some geometry sizes. virtual object_ptr<LayerWidget> moveContentToLayer( QRect bodyGeometry) { return nullptr; } // Global shortcut handler. For now that ugly :( virtual bool cmd_search() { return false; } static void PaintBackground(QWidget *widget, QPaintEvent *event); protected: void paintEvent(QPaintEvent *e) override; // Temp variable used in resizeEvent() implementation, that is passed // to setGeometryWithTopMoved() to adjust the scroll position with the resize. int topDelta() const { return _topDelta; } // Called after the hideChildren() call in showAnimated(). virtual void showAnimatedHook( const Window::SectionSlideParams ¶ms) { } // Called after the showChildren() call in showFinished(). virtual void showFinishedHook() { } virtual void doSetInnerFocus() { setFocus(); } bool animating() const { return _showAnimation != nullptr; } ~SectionWidget(); private: void showFinished(); std::unique_ptr<SlideAnimation> _showAnimation; // Saving here topDelta in setGeometryWithTopMoved() to get it passed to resizeEvent(). int _topDelta = 0; }; } // namespace Window ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/window/themes/�������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0022217�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/window/themes/window_theme.cpp���������������������������������0000664�0000000�0000000�00000065670�13262451251�0025432�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "window/themes/window_theme.h" #include "mainwidget.h" #include "storage/localstorage.h" #include "base/parse_helper.h" #include "base/zlib_help.h" #include "styles/style_widgets.h" #include "styles/style_history.h" #include "boxes/background_box.h" namespace Window { namespace Theme { namespace { constexpr auto kThemeFileSizeLimit = 5 * 1024 * 1024; constexpr auto kThemeBackgroundSizeLimit = 4 * 1024 * 1024; constexpr auto kThemeSchemeSizeLimit = 1024 * 1024; constexpr auto kMinimumTiledSize = 512; constexpr auto kNightThemeFile = str_const(":/gui/night.tdesktop-theme"); struct Data { struct Applying { QString path; QByteArray content; QByteArray paletteForRevert; Cached cached; }; ChatBackground background; Applying applying; }; NeverFreedPointer<Data> instance; inline bool AreTestingTheme() { if (instance) { return !instance->applying.paletteForRevert.isEmpty(); } return false; }; QByteArray readThemeContent(const QString &path) { QFile file(path); if (!file.exists()) { LOG(("Theme Error: theme file not found: %1").arg(path)); return QByteArray(); } if (file.size() > kThemeFileSizeLimit) { LOG(("Theme Error: theme file too large: %1 (should be less than 5 MB, got %2)").arg(path).arg(file.size())); return QByteArray(); } if (!file.open(QIODevice::ReadOnly)) { LOG(("Theme Error: could not open theme file: %1").arg(path)); return QByteArray(); } return file.readAll(); } inline uchar readHexUchar(char code, bool &error) { if (code >= '0' && code <= '9') { return ((code - '0') & 0xFF); } else if (code >= 'a' && code <= 'f') { return ((code + 10 - 'a') & 0xFF); } else if (code >= 'A' && code <= 'F') { return ((code + 10 - 'A') & 0xFF); } error = true; return 0xFF; } inline uchar readHexUchar(char char1, char char2, bool &error) { return ((readHexUchar(char1, error) & 0x0F) << 4) | (readHexUchar(char2, error) & 0x0F); } bool readNameAndValue(const char *&from, const char *end, QLatin1String *outName, QLatin1String *outValue) { using base::parse::skipWhitespaces; using base::parse::readName; if (!skipWhitespaces(from, end)) return true; *outName = readName(from, end); if (outName->size() == 0) { LOG(("Theme Error: Could not read name in the color scheme.")); return false; } if (!skipWhitespaces(from, end)) { LOG(("Theme Error: Unexpected end of the color scheme.")); return false; } if (*from != ':') { LOG(("Theme Error: Expected ':' between each name and value in the color scheme (while reading key '%1')").arg(*outName)); return false; } if (!skipWhitespaces(++from, end)) { LOG(("Theme Error: Unexpected end of the color scheme.")); return false; } auto valueStart = from; if (*from == '#') ++from; if (readName(from, end).size() == 0) { LOG(("Theme Error: Expected a color value in #rrggbb or #rrggbbaa format in the color scheme (while reading key '%1')").arg(*outName)); return false; } *outValue = QLatin1String(valueStart, from - valueStart); if (!skipWhitespaces(from, end)) { LOG(("Theme Error: Unexpected end of the color scheme.")); return false; } if (*from != ';') { LOG(("Theme Error: Expected ';' after each value in the color scheme (while reading key '%1')").arg(*outName)); return false; } ++from; return true; } enum class SetResult { Ok, Bad, NotFound, }; SetResult setColorSchemeValue(QLatin1String name, QLatin1String value, Instance *out) { auto result = style::palette::SetResult::Ok; auto size = value.size(); auto data = value.data(); if (data[0] == '#' && (size == 7 || size == 9)) { auto error = false; auto r = readHexUchar(data[1], data[2], error); auto g = readHexUchar(data[3], data[4], error); auto b = readHexUchar(data[5], data[6], error); auto a = (size == 9) ? readHexUchar(data[7], data[8], error) : uchar(255); if (error) { LOG(("Theme Warning: Skipping value '%1: %2' (expected a color value in #rrggbb or #rrggbbaa or a previously defined key in the color scheme)").arg(name).arg(value)); return SetResult::Ok; } else if (out) { result = out->palette.setColor(name, r, g, b, a); } else { result = style::main_palette::setColor(name, r, g, b, a); } } else { if (out) { result = out->palette.setColor(name, value); } else { result = style::main_palette::setColor(name, value); } } if (result == style::palette::SetResult::Ok) { return SetResult::Ok; } else if (result == style::palette::SetResult::KeyNotFound) { return SetResult::NotFound; } else if (result == style::palette::SetResult::ValueNotFound) { LOG(("Theme Warning: Skipping value '%1: %2' (expected a color value in #rrggbb or #rrggbbaa or a previously defined key in the color scheme)").arg(name).arg(value)); return SetResult::Ok; } else if (result == style::palette::SetResult::Duplicate) { LOG(("Theme Warning: Color value appears more than once in the color scheme (while applying '%1: %2')").arg(name).arg(value)); return SetResult::Ok; } else { LOG(("Theme Error: Unexpected internal error.")); } return SetResult::Bad; } bool loadColorScheme(const QByteArray &content, Instance *out) { auto unsupported = QMap<QLatin1String, QLatin1String>(); return ReadPaletteValues(content, [&unsupported, out](QLatin1String name, QLatin1String value) { // Find the named value in the already read unsupported list. value = unsupported.value(value, value); auto result = setColorSchemeValue(name, value, out); if (result == SetResult::Bad) { return false; } else if (result == SetResult::NotFound) { unsupported.insert(name, value); } return true; }); } void applyBackground(QImage &&background, bool tiled, Instance *out) { if (out) { out->background = std::move(background); out->tiled = tiled; } else { Background()->setThemeData(std::move(background), tiled); } } bool loadThemeFromCache(const QByteArray &content, Cached &cache) { if (cache.paletteChecksum != style::palette::Checksum()) { return false; } if (cache.contentChecksum != hashCrc32(content.constData(), content.size())) { return false; } QImage background; if (!cache.background.isEmpty()) { QBuffer buffer(&cache.background); QImageReader reader(&buffer); #ifndef OS_MAC_OLD reader.setAutoTransform(true); #endif // OS_MAC_OLD if (!reader.read(&background) || background.isNull()) { return false; } } if (!style::main_palette::load(cache.colors)) { return false; } if (!background.isNull()) { applyBackground(std::move(background), cache.tiled, nullptr); } return true; } enum class LoadResult { Loaded, Failed, NotFound, }; LoadResult loadBackgroundFromFile(zlib::FileToRead &file, const char *filename, QByteArray *outBackground) { *outBackground = file.readFileContent(filename, zlib::kCaseInsensitive, kThemeBackgroundSizeLimit); if (file.error() == UNZ_OK) { return LoadResult::Loaded; } else if (file.error() == UNZ_END_OF_LIST_OF_FILE) { file.clearError(); return LoadResult::NotFound; } LOG(("Theme Error: could not read '%1' in the theme file.").arg(filename)); return LoadResult::Failed; } bool loadBackground(zlib::FileToRead &file, QByteArray *outBackground, bool *outTiled) { auto result = loadBackgroundFromFile(file, "background.jpg", outBackground); if (result != LoadResult::NotFound) return (result == LoadResult::Loaded); result = loadBackgroundFromFile(file, "background.png", outBackground); if (result != LoadResult::NotFound) return (result == LoadResult::Loaded); *outTiled = true; result = loadBackgroundFromFile(file, "tiled.jpg", outBackground); if (result != LoadResult::NotFound) return (result == LoadResult::Loaded); result = loadBackgroundFromFile(file, "tiled.png", outBackground); if (result != LoadResult::NotFound) return (result == LoadResult::Loaded); return true; } bool loadTheme(const QByteArray &content, Cached &cache, Instance *out = nullptr) { cache = Cached(); zlib::FileToRead file(content); unz_global_info globalInfo = { 0 }; file.getGlobalInfo(&globalInfo); if (file.error() == UNZ_OK) { auto schemeContent = file.readFileContent("colors.tdesktop-theme", zlib::kCaseInsensitive, kThemeSchemeSizeLimit); if (file.error() == UNZ_END_OF_LIST_OF_FILE) { file.clearError(); schemeContent = file.readFileContent("colors.tdesktop-palette", zlib::kCaseInsensitive, kThemeSchemeSizeLimit); } if (file.error() != UNZ_OK) { LOG(("Theme Error: could not read 'colors.tdesktop-theme' or 'colors.tdesktop-palette' in the theme file.")); return false; } if (!loadColorScheme(schemeContent, out)) { return false; } auto backgroundTiled = false; auto backgroundContent = QByteArray(); if (!loadBackground(file, &backgroundContent, &backgroundTiled)) { return false; } if (!backgroundContent.isEmpty()) { auto background = App::readImage(backgroundContent); if (background.isNull()) { LOG(("Theme Error: could not read background image in the theme file.")); return false; } QBuffer buffer(&cache.background); if (!background.save(&buffer, "BMP")) { LOG(("Theme Error: could not write background image as a BMP to cache.")); return false; } cache.tiled = backgroundTiled; applyBackground(std::move(background), cache.tiled, out); } } else { // Looks like it is not a .zip theme. if (!loadColorScheme(content, out)) { return false; } } if (out) { cache.colors = out->palette.save(); } else { cache.colors = style::main_palette::save(); } cache.paletteChecksum = style::palette::Checksum(); cache.contentChecksum = hashCrc32(content.constData(), content.size()); return true; } QImage prepareBackgroundImage(QImage &&image) { if (image.format() != QImage::Format_ARGB32 && image.format() != QImage::Format_ARGB32_Premultiplied && image.format() != QImage::Format_RGB32) { image = std::move(image).convertToFormat(QImage::Format_RGB32); } image.setDevicePixelRatio(cRetinaFactor()); return std::move(image); } void adjustColor(style::color color, float64 hue, float64 saturation) { auto original = color->c; original.setHslF(hue, saturation, original.lightnessF(), original.alphaF()); color.set(original.red(), original.green(), original.blue(), original.alpha()); } void adjustColorsUsingBackground(const QImage &img) { Assert(img.format() == QImage::Format_ARGB32_Premultiplied); uint64 components[3] = { 0 }; uint64 componentsScroll[3] = { 0 }; auto w = img.width(); auto h = img.height(); auto size = w * h; if (auto pix = img.constBits()) { for (auto i = 0, l = size * 4; i != l; i += 4) { components[2] += pix[i + 0]; components[1] += pix[i + 1]; components[0] += pix[i + 2]; } } if (size) { for (auto i = 0; i != 3; ++i) { components[i] /= size; } } auto bgColor = QColor(components[0], components[1], components[2]); auto hue = bgColor.hslHueF(); auto saturation = bgColor.hslSaturationF(); adjustColor(st::msgServiceBg, hue, saturation); adjustColor(st::msgServiceBgSelected, hue, saturation); adjustColor(st::historyScroll.bg, hue, saturation); adjustColor(st::historyScroll.bgOver, hue, saturation); adjustColor(st::historyScroll.barBg, hue, saturation); adjustColor(st::historyScroll.barBgOver, hue, saturation); } } // namespace void ChatBackground::setThemeData(QImage &&themeImage, bool themeTile) { _themeImage = prepareBackgroundImage(std::move(themeImage)); _themeTile = themeTile; } void ChatBackground::start() { if (_id == internal::kUninitializedBackground) { if (!Local::readBackground()) { setImage(kThemeBackground); } } } void ChatBackground::setImage(int32 id, QImage &&image) { auto resetPalette = (id == kDefaultBackground && _id != kDefaultBackground && !Local::hasTheme()); if (id == kThemeBackground && _themeImage.isNull()) { id = kDefaultBackground; } else if (resetPalette) { // If we had a default color theme with non-default background, // and we switch to default background we must somehow switch from // adjusted service colors to default (non-adjusted) service colors. // The only way to do that right now is through full palette reset. style::main_palette::reset(); } _id = id; if (_id == kThemeBackground) { _tile = _themeTile; setPreparedImage(QImage(_themeImage)); } else if (_id == internal::kTestingThemeBackground || _id == internal::kTestingDefaultBackground || _id == internal::kTestingEditorBackground) { if (_id == internal::kTestingDefaultBackground || image.isNull()) { image.load(qsl(":/gui/art/bg.jpg")); _id = internal::kTestingDefaultBackground; } setPreparedImage(std::move(image)); } else { if (_id == kInitialBackground) { image.load(qsl(":/gui/art/bg_initial.jpg")); if (cRetina()) { image = image.scaledToWidth(image.width() * 2, Qt::SmoothTransformation); } else if (cScale() != dbisOne) { image = image.scaledToWidth(convertScale(image.width()), Qt::SmoothTransformation); } } else if (_id == kDefaultBackground || image.isNull()) { _id = kDefaultBackground; image.load(qsl(":/gui/art/bg.jpg")); } Local::writeBackground(_id, (_id == kDefaultBackground || _id == kInitialBackground) ? QImage() : image); setPreparedImage(prepareBackgroundImage(std::move(image))); } Assert(!_pixmap.isNull() && !_pixmapForTiled.isNull()); notify(BackgroundUpdate(BackgroundUpdate::Type::New, _tile)); if (resetPalette) { notify(BackgroundUpdate(BackgroundUpdate::Type::TestingTheme, _tile), true); notify(BackgroundUpdate(BackgroundUpdate::Type::ApplyingTheme, _tile), true); } } void ChatBackground::setPreparedImage(QImage &&image) { image = std::move(image).convertToFormat(QImage::Format_ARGB32_Premultiplied); image.setDevicePixelRatio(cRetinaFactor()); auto adjustColors = [this] { auto someThemeApplied = [] { if (AreTestingTheme()) { return !instance->applying.path.isEmpty(); } return IsNonDefaultUsed() || IsNightTheme(); }; auto usingThemeBackground = [this] { return (_id == kThemeBackground || _id == internal::kTestingThemeBackground); }; auto usingDefaultBackground = [this] { return (_id == kDefaultBackground || _id == internal::kTestingDefaultBackground); }; auto testingPalette = [] { if (AreTestingTheme()) { return IsPaletteTestingPath(instance->applying.path); } return !Local::themePaletteAbsolutePath().isEmpty(); }; if (someThemeApplied()) { return !usingThemeBackground() && !testingPalette(); } return !usingDefaultBackground(); }; if (adjustColors()) { adjustColorsUsingBackground(image); } auto width = image.width(); auto height = image.height(); Assert(width > 0 && height > 0); auto isSmallForTiled = (width < kMinimumTiledSize || height < kMinimumTiledSize); if (isSmallForTiled) { auto repeatTimesX = qCeil(kMinimumTiledSize / float64(width)); auto repeatTimesY = qCeil(kMinimumTiledSize / float64(height)); auto imageForTiled = QImage(width * repeatTimesX, height * repeatTimesY, QImage::Format_ARGB32_Premultiplied); imageForTiled.setDevicePixelRatio(image.devicePixelRatio()); auto imageForTiledBytes = imageForTiled.bits(); auto bytesInLine = width * sizeof(uint32); for (auto timesY = 0; timesY != repeatTimesY; ++timesY) { auto imageBytes = image.constBits(); for (auto y = 0; y != height; ++y) { for (auto timesX = 0; timesX != repeatTimesX; ++timesX) { memcpy(imageForTiledBytes, imageBytes, bytesInLine); imageForTiledBytes += bytesInLine; } imageBytes += image.bytesPerLine(); imageForTiledBytes += imageForTiled.bytesPerLine() - (repeatTimesX * bytesInLine); } } _pixmapForTiled = App::pixmapFromImageInPlace(std::move(imageForTiled)); } _pixmap = App::pixmapFromImageInPlace(std::move(image)); if (!isSmallForTiled) { _pixmapForTiled = _pixmap; } } int32 ChatBackground::id() const { return _id; } bool ChatBackground::tile() const { return _tile; } bool ChatBackground::tileForSave() const { if (_id == internal::kTestingThemeBackground || _id == internal::kTestingDefaultBackground) { return _tileForRevert; } return tile(); } void ChatBackground::ensureStarted() { if (_pixmap.isNull()) { // We should start first, otherwise the default call // to start() will reset this value to _themeTile. start(); } } void ChatBackground::setTile(bool tile) { ensureStarted(); if (_tile != tile) { _tile = tile; if (_id != internal::kTestingThemeBackground && _id != internal::kTestingDefaultBackground) { Local::writeUserSettings(); } notify(BackgroundUpdate(BackgroundUpdate::Type::Changed, _tile)); } } void ChatBackground::reset() { if (_id == internal::kTestingThemeBackground || _id == internal::kTestingDefaultBackground) { if (_themeImage.isNull()) { _idForRevert = kDefaultBackground; _imageForRevert = QImage(); _tileForRevert = false; } else { _idForRevert = kThemeBackground; _imageForRevert = _themeImage; _tileForRevert = _themeTile; } } else { setImage(kThemeBackground); } } void ChatBackground::saveForRevert() { ensureStarted(); if (_id != internal::kTestingThemeBackground && _id != internal::kTestingDefaultBackground) { _idForRevert = _id; _imageForRevert = std::move(_pixmap).toImage(); _tileForRevert = _tile; } } void ChatBackground::setTestingTheme(Instance &&theme, ChangeMode mode) { style::main_palette::apply(theme.palette); auto switchToThemeBackground = (mode == ChangeMode::SwitchToThemeBackground && !theme.background.isNull()) || (_id == kThemeBackground) || (_id == kDefaultBackground && !Local::hasTheme()); if (AreTestingTheme() && IsPaletteTestingPath(instance->applying.path)) { // Grab current background image if it is not already custom if (_id != kCustomBackground) { saveForRevert(); setImage(internal::kTestingEditorBackground, std::move(_pixmap).toImage()); } } else if (switchToThemeBackground) { saveForRevert(); setImage(internal::kTestingThemeBackground, std::move(theme.background)); setTile(theme.tiled); } else { // Apply current background image so that service bg colors are recounted. setImage(_id, std::move(_pixmap).toImage()); } notify(BackgroundUpdate(BackgroundUpdate::Type::TestingTheme, _tile), true); } void ChatBackground::setTestingDefaultTheme() { style::main_palette::reset(); if (_id == kThemeBackground) { saveForRevert(); setImage(internal::kTestingDefaultBackground); setTile(false); } else { // Apply current background image so that service bg colors are recounted. setImage(_id, std::move(_pixmap).toImage()); } notify(BackgroundUpdate(BackgroundUpdate::Type::TestingTheme, _tile), true); } void ChatBackground::keepApplied() { if (_id == internal::kTestingEditorBackground) { _id = kCustomBackground; _themeImage = QImage(); _themeTile = false; writeNewBackgroundSettings(); } else if (_id == internal::kTestingThemeBackground) { _id = kThemeBackground; _themeImage = _pixmap.toImage(); _themeTile = _tile; writeNewBackgroundSettings(); } else if (_id == internal::kTestingDefaultBackground) { _id = kDefaultBackground; _themeImage = QImage(); _themeTile = false; writeNewBackgroundSettings(); } notify(BackgroundUpdate(BackgroundUpdate::Type::ApplyingTheme, _tile), true); } void ChatBackground::writeNewBackgroundSettings() { if (_tile != _tileForRevert) { Local::writeUserSettings(); } Local::writeBackground(_id, (_id == kThemeBackground || _id == kDefaultBackground) ? QImage() : _pixmap.toImage()); } void ChatBackground::revert() { if (_id == internal::kTestingThemeBackground || _id == internal::kTestingDefaultBackground || _id == internal::kTestingEditorBackground) { setTile(_tileForRevert); setImage(_idForRevert, std::move(_imageForRevert)); } else { // Apply current background image so that service bg colors are recounted. setImage(_id, std::move(_pixmap).toImage()); } notify(BackgroundUpdate(BackgroundUpdate::Type::RevertingTheme, _tile), true); } ChatBackground *Background() { instance.createIfNull(); return &instance->background; } bool Load(const QString &pathRelative, const QString &pathAbsolute, const QByteArray &content, Cached &cache) { if (content.size() < 4) { LOG(("Theme Error: Could not load theme from '%1' (%2)").arg(pathRelative).arg(pathAbsolute)); return false; } instance.createIfNull(); if (loadThemeFromCache(content, cache)) { return true; } if (!loadTheme(content, cache)) { return false; } Local::writeTheme(pathRelative, pathAbsolute, content, cache); return true; } void Unload() { instance.clear(); } bool Apply(const QString &filepath) { auto preview = std::make_unique<Preview>(); preview->path = filepath; if (!LoadFromFile(preview->path, &preview->instance, &preview->content)) { return false; } return Apply(std::move(preview)); } void SwitchNightTheme(bool enabled) { if (enabled) { auto preview = std::make_unique<Preview>(); preview->path = str_const_toString(kNightThemeFile); if (!LoadFromFile(preview->path, &preview->instance, &preview->content)) { return; } instance.createIfNull(); instance->applying.path = std::move(preview->path); instance->applying.content = std::move(preview->content); instance->applying.cached = std::move(preview->instance.cached); if (instance->applying.paletteForRevert.isEmpty()) { instance->applying.paletteForRevert = style::main_palette::save(); } Background()->setTestingTheme(std::move(preview->instance), ChatBackground::ChangeMode::LeaveCurrentCustomBackground); } else { Window::Theme::ApplyDefault(); } KeepApplied(); } bool Apply(std::unique_ptr<Preview> preview) { instance.createIfNull(); instance->applying.path = std::move(preview->path); instance->applying.content = std::move(preview->content); instance->applying.cached = std::move(preview->instance.cached); if (instance->applying.paletteForRevert.isEmpty()) { instance->applying.paletteForRevert = style::main_palette::save(); } Background()->setTestingTheme(std::move(preview->instance)); return true; } void ApplyDefault() { instance.createIfNull(); instance->applying.path = QString(); instance->applying.content = QByteArray(); instance->applying.cached = Cached(); if (instance->applying.paletteForRevert.isEmpty()) { instance->applying.paletteForRevert = style::main_palette::save(); } Background()->setTestingDefaultTheme(); } bool ApplyEditedPalette(const QString &path, const QByteArray &content) { Instance out; if (!loadColorScheme(content, &out)) { return false; } out.cached.colors = out.palette.save(); out.cached.paletteChecksum = style::palette::Checksum(); out.cached.contentChecksum = hashCrc32(content.constData(), content.size()); instance.createIfNull(); instance->applying.path = path; instance->applying.content = content; instance->applying.cached = out.cached; if (instance->applying.paletteForRevert.isEmpty()) { instance->applying.paletteForRevert = style::main_palette::save(); } Background()->setTestingTheme(std::move(out)); KeepApplied(); return true; } void KeepApplied() { if (!instance) { return; } auto filepath = instance->applying.path; auto pathRelative = filepath.isEmpty() ? QString() : QDir().relativeFilePath(filepath); auto pathAbsolute = filepath.isEmpty() ? QString() : QFileInfo(filepath).absoluteFilePath(); Local::writeTheme(pathRelative, pathAbsolute, instance->applying.content, instance->applying.cached); instance->applying = Data::Applying(); Background()->keepApplied(); } void Revert() { if (!instance->applying.paletteForRevert.isEmpty()) { style::main_palette::load(instance->applying.paletteForRevert); } instance->applying = Data::Applying(); Background()->revert(); } bool IsNightTheme() { return (Local::themeAbsolutePath() == str_const_toString(kNightThemeFile)); } bool IsNonDefaultUsed() { return Local::hasTheme() && !IsNightTheme(); } bool LoadFromFile(const QString &path, Instance *out, QByteArray *outContent) { *outContent = readThemeContent(path); if (outContent->size() < 4) { LOG(("Theme Error: Could not load theme from %1").arg(path)); return false; } return loadTheme(*outContent, out->cached, out); } bool IsPaletteTestingPath(const QString &path) { if (path.endsWith(qstr(".tdesktop-palette"), Qt::CaseInsensitive)) { return QFileInfo(path).exists(); } return false; } void ComputeBackgroundRects(QRect wholeFill, QSize imageSize, QRect &to, QRect &from) { if (uint64(imageSize.width()) * wholeFill.height() > uint64(imageSize.height()) * wholeFill.width()) { float64 pxsize = wholeFill.height() / float64(imageSize.height()); int takewidth = qCeil(wholeFill.width() / pxsize); if (takewidth > imageSize.width()) { takewidth = imageSize.width(); } else if ((imageSize.width() % 2) != (takewidth % 2)) { ++takewidth; } to = QRect(int((wholeFill.width() - takewidth * pxsize) / 2.), 0, qCeil(takewidth * pxsize), wholeFill.height()); from = QRect((imageSize.width() - takewidth) / 2, 0, takewidth, imageSize.height()); } else { float64 pxsize = wholeFill.width() / float64(imageSize.width()); int takeheight = qCeil(wholeFill.height() / pxsize); if (takeheight > imageSize.height()) { takeheight = imageSize.height(); } else if ((imageSize.height() % 2) != (takeheight % 2)) { ++takeheight; } to = QRect(0, int((wholeFill.height() - takeheight * pxsize) / 2.), wholeFill.width(), qCeil(takeheight * pxsize)); from = QRect(0, (imageSize.height() - takeheight) / 2, imageSize.width(), takeheight); } } bool CopyColorsToPalette(const QString &path, const QByteArray &themeContent) { auto paletteContent = themeContent; zlib::FileToRead file(themeContent); unz_global_info globalInfo = { 0 }; file.getGlobalInfo(&globalInfo); if (file.error() == UNZ_OK) { paletteContent = file.readFileContent("colors.tdesktop-theme", zlib::kCaseInsensitive, kThemeSchemeSizeLimit); if (file.error() == UNZ_END_OF_LIST_OF_FILE) { file.clearError(); paletteContent = file.readFileContent("colors.tdesktop-palette", zlib::kCaseInsensitive, kThemeSchemeSizeLimit); } if (file.error() != UNZ_OK) { LOG(("Theme Error: could not read 'colors.tdesktop-theme' or 'colors.tdesktop-palette' in the theme file, while copying to '%1'.").arg(path)); return false; } } QFile f(path); if (!f.open(QIODevice::WriteOnly)) { LOG(("Theme Error: could not open file for write '%1'").arg(path)); return false; } if (f.write(paletteContent) != paletteContent.size()) { LOG(("Theme Error: could not write palette to '%1'").arg(path)); return false; } return true; } bool ReadPaletteValues(const QByteArray &content, base::lambda<bool(QLatin1String name, QLatin1String value)> callback) { if (content.size() > kThemeSchemeSizeLimit) { LOG(("Theme Error: color scheme file too large (should be less than 1 MB, got %2)").arg(content.size())); return false; } auto data = base::parse::stripComments(content); auto from = data.constData(), end = from + data.size(); while (from != end) { auto name = QLatin1String(""); auto value = QLatin1String(""); if (!readNameAndValue(from, end, &name, &value)) { return false; } if (name.size() == 0) { // End of content reached. return true; } if (!callback(name, value)) { return false; } } return true; } } // namespace Theme } // namespace Window ������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/window/themes/window_theme.h�����������������������������������0000664�0000000�0000000�00000006712�13262451251�0025067�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace Window { namespace Theme { namespace internal { constexpr int32 kUninitializedBackground = -999; constexpr int32 kTestingThemeBackground = -666; constexpr int32 kTestingDefaultBackground = -665; constexpr int32 kTestingEditorBackground = -664; } // namespace internal constexpr int32 kThemeBackground = -2; constexpr int32 kCustomBackground = -1; constexpr int32 kInitialBackground = 0; constexpr int32 kDefaultBackground = 105; struct Cached { QByteArray colors; QByteArray background; bool tiled = false; int32 paletteChecksum = 0; int32 contentChecksum = 0; }; bool Load(const QString &pathRelative, const QString &pathAbsolute, const QByteArray &content, Cached &cache); void Unload(); struct Instance { style::palette palette; QImage background; Cached cached; bool tiled = false; }; struct Preview { QString path; Instance instance; QByteArray content; QImage preview; }; bool Apply(const QString &filepath); bool Apply(std::unique_ptr<Preview> preview); void ApplyDefault(); bool ApplyEditedPalette(const QString &path, const QByteArray &content); void KeepApplied(); bool IsNonDefaultUsed(); bool IsNightTheme(); void SwitchNightTheme(bool enabled); void Revert(); bool LoadFromFile(const QString &file, Instance *out, QByteArray *outContent); bool IsPaletteTestingPath(const QString &path); struct BackgroundUpdate { enum class Type { New, Changed, Start, TestingTheme, RevertingTheme, ApplyingTheme, }; BackgroundUpdate(Type type, bool tiled) : type(type), tiled(tiled) { } bool paletteChanged() const { return (type == Type::TestingTheme || type == Type::RevertingTheme); } Type type; bool tiled; }; class ChatBackground : public base::Observable<BackgroundUpdate> { public: // This method is allowed to (and should) be called before start(). void setThemeData(QImage &&themeImage, bool themeTile); // This method is setting the default (themed) image if none was set yet. void start(); void setImage(int32 id, QImage &&image = QImage()); void setTile(bool tile); void reset(); enum class ChangeMode { SwitchToThemeBackground, LeaveCurrentCustomBackground, }; void setTestingTheme(Instance &&theme, ChangeMode mode = ChangeMode::SwitchToThemeBackground); void setTestingDefaultTheme(); void keepApplied(); void revert(); int32 id() const; const QPixmap &pixmap() const { return _pixmap; } const QPixmap &pixmapForTiled() const { return _pixmapForTiled; } bool tile() const; bool tileForSave() const; private: void ensureStarted(); void saveForRevert(); void setPreparedImage(QImage &&image); void writeNewBackgroundSettings(); int32 _id = internal::kUninitializedBackground; QPixmap _pixmap; QPixmap _pixmapForTiled; bool _tile = false; QImage _themeImage; bool _themeTile = false; int32 _idForRevert = internal::kUninitializedBackground; QImage _imageForRevert; bool _tileForRevert = false; }; ChatBackground *Background(); void ComputeBackgroundRects(QRect wholeFill, QSize imageSize, QRect &to, QRect &from); bool CopyColorsToPalette(const QString &path, const QByteArray &themeContent); bool ReadPaletteValues(const QByteArray &content, base::lambda<bool(QLatin1String name, QLatin1String value)> callback); } // namespace Theme } // namespace Window ������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/window/themes/window_theme_editor.cpp��������������������������0000664�0000000�0000000�00000060426�13262451251�0026772�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "window/themes/window_theme_editor.h" #include "window/themes/window_theme.h" #include "window/themes/window_theme_editor_block.h" #include "mainwindow.h" #include "layout.h" #include "storage/localstorage.h" #include "boxes/confirm_box.h" #include "styles/style_window.h" #include "styles/style_settings.h" #include "styles/style_dialogs.h" #include "styles/style_boxes.h" #include "ui/widgets/scroll_area.h" #include "ui/widgets/shadow.h" #include "ui/widgets/buttons.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/multi_select.h" #include "base/parse_helper.h" #include "base/zlib_help.h" #include "ui/toast/toast.h" #include "core/file_utilities.h" #include "boxes/edit_color_box.h" #include "lang/lang_keys.h" namespace Window { namespace Theme { namespace { struct ReadColorResult { ReadColorResult(QColor color, bool error = false) : color(color), error(error) { } QColor color; bool error = false; }; ReadColorResult colorError(const QString &name) { return { QColor(), true }; } ReadColorResult readColor(const QString &name, const char *data, int size) { if (size != 6 && size != 8) { return colorError(name); } auto readHex = [](char ch) { if (ch >= '0' && ch <= '9') { return (ch - '0'); } else if (ch >= 'a' && ch <= 'f') { return (ch - 'a' + 10); } else if (ch >= 'A' && ch <= 'F') { return (ch - 'A' + 10); } return -1; }; auto readValue = [readHex](const char *data) { auto high = readHex(data[0]); auto low = readHex(data[1]); return (high >= 0 && low >= 0) ? (high * 0x10 + low) : -1; }; auto r = readValue(data); auto g = readValue(data + 2); auto b = readValue(data + 4); auto a = (size == 8) ? readValue(data + 6) : 255; if (r < 0 || g < 0 || b < 0 || a < 0) { return colorError(name); } return { QColor(r, g, b, a) }; } bool skipComment(const char *&data, const char *end) { if (data == end) return false; if (*data == '/' && data + 1 != end) { if (*(data + 1) == '/') { data += 2; while (data != end && *data != '\n') { ++data; } return true; } else if (*(data + 1) == '*') { data += 2; while (true) { while (data != end && *data != '*') { ++data; } if (data != end) { ++data; if (data != end && *data == '/') { ++data; break; } } if (data == end) { break; } } return true; } } return false; } void skipWhitespacesAndComments(const char *&data, const char *end) { while (data != end) { if (!base::parse::skipWhitespaces(data, end)) return; if (!skipComment(data, end)) return; } } QLatin1String readValue(const char *&data, const char *end) { auto start = data; if (data != end && *data == '#') { ++data; } base::parse::readName(data, end); return QLatin1String(start, data - start); } bool isValidColorValue(QLatin1String value) { auto isValidHexChar = [](char ch) { return (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F') || (ch >= 'a' && ch <= 'f'); }; auto data = value.data(); auto size = value.size(); if ((size != 7 && size != 9) || data[0] != '#') { return false; } for (auto i = 1; i != size; ++i) { if (!isValidHexChar(data[i])) { return false; } } return true; } QByteArray replaceValueInContent(const QByteArray &content, const QByteArray &name, const QByteArray &value) { auto validNames = OrderedSet<QLatin1String>(); auto start = content.constBegin(), data = start, end = data + content.size(); auto lastValidValueStart = end, lastValidValueEnd = end; while (data != end) { skipWhitespacesAndComments(data, end); if (data == end) break; auto foundName = base::parse::readName(data, end); skipWhitespacesAndComments(data, end); if (data == end || *data != ':') { return "error"; } ++data; skipWhitespacesAndComments(data, end); auto valueStart = data; auto value = readValue(data, end); auto valueEnd = data; if (value.size() == 0) { return "error"; } auto validValue = validNames.contains(value) || isValidColorValue(value); if (validValue) { validNames.insert(foundName); if (foundName == name) { lastValidValueStart = valueStart; lastValidValueEnd = valueEnd; } } skipWhitespacesAndComments(data, end); if (data == end || *data != ';') { return "error"; } ++data; } if (lastValidValueStart != end) { auto result = QByteArray(); result.reserve((lastValidValueStart - start) + value.size() + (end - lastValidValueEnd)); result.append(start, lastValidValueStart - start); result.append(value); if (end - lastValidValueEnd > 0) result.append(lastValidValueEnd, end - lastValidValueEnd); return result; } return QByteArray(); } QString bytesToUtf8(QLatin1String bytes) { return QString::fromUtf8(bytes.data(), bytes.size()); } } // namespace class Editor::Inner : public TWidget, private base::Subscriber { public: Inner(QWidget *parent, const QString &path); void setErrorCallback(base::lambda<void()> callback) { _errorCallback = std::move(callback); } void setFocusCallback(base::lambda<void()> callback) { _focusCallback = std::move(callback); } void setScrollCallback(base::lambda<void(int top, int bottom)> callback) { _scrollCallback = std::move(callback); } void prepare(); base::lambda<void()> exportCallback(); void filterRows(const QString &query); void chooseRow(); void selectSkip(int direction); void selectSkipPage(int delta, int direction); ~Inner() { if (_context.box) _context.box->closeBox(); } protected: void paintEvent(QPaintEvent *e) override; int resizeGetHeight(int newWidth) override; private: bool readData(); bool readExistingRows(); bool feedExistingRow(const QString &name, QLatin1String value); void error() { if (_errorCallback) { _errorCallback(); } } void applyEditing(const QString &name, const QString ©Of, QColor value); EditorBlock::Context _context; QString _path; QByteArray _paletteContent; base::lambda<void()> _errorCallback; base::lambda<void()> _focusCallback; base::lambda<void(int top, int bottom)> _scrollCallback; object_ptr<EditorBlock> _existingRows; object_ptr<EditorBlock> _newRows; bool _applyingUpdate = false; }; class ThemeExportBox : public BoxContent { public: ThemeExportBox(QWidget*, const QByteArray &paletteContent, const QImage &background, const QByteArray &backgroundContent, bool tileBackground); protected: void prepare() override; void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; private: void updateThumbnail(); void chooseBackgroundFromFile(); void exportTheme(); QByteArray _paletteContent; QImage _background; QByteArray _backgroundContent; bool _isPng = false; QString _imageText; QPixmap _thumbnail; object_ptr<Ui::LinkButton> _chooseFromFile; object_ptr<Ui::Checkbox> _tileBackground; }; Editor::Inner::Inner(QWidget *parent, const QString &path) : TWidget(parent) , _path(path) , _existingRows(this, EditorBlock::Type::Existing, &_context) , _newRows(this, EditorBlock::Type::New, &_context) { resize(st::windowMinWidth, st::windowMinHeight); subscribe(_context.resized, [this] { resizeToWidth(width()); }); subscribe(_context.pending, [this](const EditorBlock::Context::EditionData &data) { applyEditing(data.name, data.copyOf, data.value); }); subscribe(_context.updated, [this] { if (_context.name.isEmpty() && _focusCallback) { _focusCallback(); } }); subscribe(_context.scroll, [this](const EditorBlock::Context::ScrollData &data) { if (_scrollCallback) { auto top = (data.type == EditorBlock::Type::Existing ? _existingRows : _newRows)->y(); top += data.position; _scrollCallback(top, top + data.height); } }); subscribe(Background(), [this](const BackgroundUpdate &update) { if (_applyingUpdate) return; if (update.type == BackgroundUpdate::Type::TestingTheme) { Revert(); App::CallDelayed(st::slideDuration, this, [] { Ui::show(Box<InformBox>(lang(lng_theme_editor_cant_change_theme))); }); } }); } void Editor::Inner::prepare() { if (!readData()) { error(); } } base::lambda<void()> Editor::Inner::exportCallback() { return App::LambdaDelayed(st::defaultRippleAnimation.hideDuration, this, [this] { auto background = Background()->pixmap().toImage(); auto backgroundContent = QByteArray(); auto tiled = Background()->tile(); { QBuffer buffer(&backgroundContent); background.save(&buffer, "JPG", 87); } Ui::show(Box<ThemeExportBox>(_paletteContent, background, backgroundContent, tiled)); }); } void Editor::Inner::filterRows(const QString &query) { _existingRows->filterRows(query); _newRows->filterRows(query); } void Editor::Inner::chooseRow() { if (!_existingRows->hasSelected() && !_newRows->hasSelected()) { selectSkip(1); } if (_existingRows->hasSelected()) { _existingRows->chooseRow(); } else if (_newRows->hasSelected()) { _newRows->chooseRow(); } } // Block::selectSkip(-1) removes the selection if it can't select anything // Block::selectSkip(1) leaves the selection if it can't select anything void Editor::Inner::selectSkip(int direction) { if (direction > 0) { if (_newRows->hasSelected()) { _existingRows->clearSelected(); _newRows->selectSkip(direction); } else if (_existingRows->hasSelected()) { if (!_existingRows->selectSkip(direction)) { if (_newRows->selectSkip(direction)) { _existingRows->clearSelected(); } } } else { if (!_existingRows->selectSkip(direction)) { _newRows->selectSkip(direction); } } } else { if (_existingRows->hasSelected()) { _newRows->clearSelected(); _existingRows->selectSkip(direction); } else if (_newRows->hasSelected()) { if (!_newRows->selectSkip(direction)) { _existingRows->selectSkip(direction); } } } } void Editor::Inner::selectSkipPage(int delta, int direction) { auto defaultRowHeight = st::themeEditorMargin.top() + st::themeEditorSampleSize.height() + st::themeEditorDescriptionSkip + st::defaultTextStyle.font->height + st::themeEditorMargin.bottom(); for (auto i = 0, count = ceilclamp(delta, defaultRowHeight, 1, delta); i != count; ++i) { selectSkip(direction); } } void Editor::Inner::paintEvent(QPaintEvent *e) { Painter p(this); p.setFont(st::settingsFixedBarFont); p.setPen(st::windowFg); if (!_newRows->isHidden()) { p.drawTextLeft(st::themeEditorMargin.left(), _existingRows->y() + _existingRows->height() + st::settingsFixedBarTextPosition.y(), width(), lang(lng_theme_editor_new_keys)); } } int Editor::Inner::resizeGetHeight(int newWidth) { auto rowsWidth = newWidth; _existingRows->resizeToWidth(rowsWidth); _newRows->resizeToWidth(rowsWidth); _existingRows->moveToLeft(0, 0); _newRows->moveToLeft(0, _existingRows->height() + st::settingsFixedBarHeight); auto lowest = (_newRows->isHidden() ? _existingRows : _newRows).data(); return lowest->y() + lowest->height(); } bool Editor::Inner::readData() { if (!readExistingRows()) { return false; } auto rows = style::main_palette::data(); for_const (auto &row, rows) { auto name = bytesToUtf8(row.name); auto description = bytesToUtf8(row.description); if (!_existingRows->feedDescription(name, description)) { if (row.value.data()[0] == '#') { auto result = readColor(name, row.value.data() + 1, row.value.size() - 1); Assert(!result.error); _newRows->feed(name, result.color); //if (!_newRows->feedFallbackName(name, str_const_toString(row.fallback))) { // Unexpected("Row for fallback not found"); //} } else { auto copyOf = bytesToUtf8(row.value); if (auto result = _existingRows->find(copyOf)) { _newRows->feed(name, *result, copyOf); } else if (!_newRows->feedCopy(name, copyOf)) { Unexpected("Copy of unknown value in the default palette"); } Assert(row.fallback.size() == 0); } if (!_newRows->feedDescription(name, description)) { Unexpected("Row for description not found"); } } } return true; } bool Editor::Inner::readExistingRows() { QFile f(_path); if (!f.open(QIODevice::ReadOnly)) { LOG(("Theme Error: could not open color palette file '%1'").arg(_path)); return false; } _paletteContent = f.readAll(); if (f.error() != QFileDevice::NoError) { LOG(("Theme Error: could not read content from palette file '%1'").arg(_path)); return false; } f.close(); return ReadPaletteValues(_paletteContent, [this](QLatin1String name, QLatin1String value) { return feedExistingRow(name, value); }); } bool Editor::Inner::feedExistingRow(const QString &name, QLatin1String value) { auto data = value.data(); auto size = value.size(); if (data[0] != '#') { return _existingRows->feedCopy(name, QString(value)); } auto result = readColor(name, data + 1, size - 1); if (result.error) { LOG(("Theme Warning: Skipping value '%1: %2' (expected a color value in #rrggbb or #rrggbbaa or a previously defined key in the color scheme)").arg(name).arg(value)); } else { _existingRows->feed(name, result.color); } return true; } QString colorString(QColor color) { auto result = QString(); result.reserve(9); result.append('#'); auto addHex = [&result](int code) { if (code >= 0 && code < 10) { result.append('0' + code); } else if (code >= 10 && code < 16) { result.append('a' + (code - 10)); } }; auto addValue = [addHex](int code) { addHex(code / 16); addHex(code % 16); }; addValue(color.red()); addValue(color.green()); addValue(color.blue()); if (color.alpha() != 255) { addValue(color.alpha()); } return result; } void Editor::Inner::applyEditing(const QString &name, const QString ©Of, QColor value) { auto plainName = name.toLatin1(); auto plainValue = (copyOf.isEmpty() ? colorString(value) : copyOf).toLatin1(); auto newContent = replaceValueInContent(_paletteContent, plainName, plainValue); if (newContent == "error") { LOG(("Theme Error: could not replace '%1: %2' in content").arg(name).arg(copyOf.isEmpty() ? colorString(value) : copyOf)); error(); return; } if (newContent.isEmpty()) { auto newline = (_paletteContent.indexOf("\r\n") >= 0 ? "\r\n" : "\n"); auto addedline = (_paletteContent.endsWith('\n') ? "" : newline); newContent = _paletteContent + addedline + plainName + ": " + plainValue + ";" + newline; } QFile f(_path); if (!f.open(QIODevice::WriteOnly)) { LOG(("Theme Error: could not open '%1' for writing a palette update.").arg(_path)); error(); return; } if (f.write(newContent) != newContent.size()) { LOG(("Theme Error: could not write all content to '%1' while writing a palette update.").arg(_path)); error(); return; } f.close(); _applyingUpdate = true; if (!ApplyEditedPalette(_path, newContent)) { LOG(("Theme Error: could not apply newly composed content :(")); error(); return; } _applyingUpdate = false; _paletteContent = newContent; } void writeDefaultPalette(const QString &path) { QFile f(path); if (!f.open(QIODevice::WriteOnly)) { LOG(("Theme Error: could not open '%1' for writing.").arg(path)); return; } QTextStream stream(&f); stream.setCodec("UTF-8"); auto rows = style::main_palette::data(); for_const (auto &row, rows) { stream << bytesToUtf8(row.name) << ": " << bytesToUtf8(row.value) << "; // " << bytesToUtf8(row.description).replace('\n', ' ').replace('\r', ' ') << "\n"; } } ThemeExportBox::ThemeExportBox(QWidget*, const QByteArray &paletteContent, const QImage &background, const QByteArray &backgroundContent, bool tileBackground) : BoxContent() , _paletteContent(paletteContent) , _background(background) , _backgroundContent(backgroundContent) , _chooseFromFile(this, lang(lng_settings_bg_from_file), st::boxLinkButton) , _tileBackground(this, lang(lng_settings_bg_tile), tileBackground, st::defaultBoxCheckbox) { _imageText = lng_theme_editor_saved_to_jpg(lt_size, formatSizeText(_backgroundContent.size())); _chooseFromFile->setClickedCallback([this] { chooseBackgroundFromFile(); }); } void ThemeExportBox::prepare() { setTitle(langFactory(lng_theme_editor_background_image)); addButton(langFactory(lng_theme_editor_export), [this] { exportTheme(); }); addButton(langFactory(lng_cancel), [this] { closeBox(); }); auto height = st::settingsSmallSkip + st::settingsBackgroundSize + st::settingsSmallSkip + _tileBackground->height(); setDimensions(st::boxWideWidth, height); updateThumbnail(); } void ThemeExportBox::paintEvent(QPaintEvent *e) { BoxContent::paintEvent(e); Painter p(this); auto linkLeft = st::boxPadding.left() + st::settingsBackgroundSize + st::settingsSmallSkip; p.setPen(st::boxTextFg); p.setFont(st::boxTextFont); p.drawTextLeft(linkLeft, st::settingsSmallSkip, width(), _imageText); p.drawPixmapLeft(st::boxPadding.left(), st::settingsSmallSkip, width(), _thumbnail); } void ThemeExportBox::resizeEvent(QResizeEvent *e) { auto linkLeft = st::boxPadding.left() + st::settingsBackgroundSize + st::settingsSmallSkip; _chooseFromFile->moveToLeft(linkLeft, st::settingsSmallSkip + st::boxTextFont->height + st::settingsSmallSkip); _tileBackground->moveToLeft(st::boxPadding.left(), st::settingsSmallSkip + st::settingsBackgroundSize + 2 * st::settingsSmallSkip); } void ThemeExportBox::updateThumbnail() { int32 size = st::settingsBackgroundSize * cIntRetinaFactor(); QImage back(size, size, QImage::Format_ARGB32_Premultiplied); back.setDevicePixelRatio(cRetinaFactor()); { Painter p(&back); PainterHighQualityEnabler hq(p); auto &pix = _background; int sx = (pix.width() > pix.height()) ? ((pix.width() - pix.height()) / 2) : 0; int sy = (pix.height() > pix.width()) ? ((pix.height() - pix.width()) / 2) : 0; int s = (pix.width() > pix.height()) ? pix.height() : pix.width(); p.drawImage(QRect(0, 0, st::settingsBackgroundSize, st::settingsBackgroundSize), pix, QRect(sx, sy, s, s)); } Images::prepareRound(back, ImageRoundRadius::Small); _thumbnail = App::pixmapFromImageInPlace(std::move(back)); _thumbnail.setDevicePixelRatio(cRetinaFactor()); update(); } void ThemeExportBox::chooseBackgroundFromFile() { FileDialog::GetOpenPath(lang(lng_theme_editor_choose_image), "Image files (*.jpeg *.jpg *.png)", base::lambda_guarded(this, [this](const FileDialog::OpenResult &result) { auto content = result.remoteContent; if (!result.paths.isEmpty()) { QFile f(result.paths.front()); if (f.open(QIODevice::ReadOnly)) { content = f.readAll(); f.close(); } } if (!content.isEmpty()) { auto format = QByteArray(); auto image = App::readImage(content, &format); if (!image.isNull() && (format == "jpeg" || format == "jpg" || format == "png")) { _background = image; _backgroundContent = content; _isPng = (format == "png"); auto sizeText = formatSizeText(_backgroundContent.size()); _imageText = _isPng ? lng_theme_editor_read_from_png(lt_size, sizeText) : lng_theme_editor_read_from_jpg(lt_size, sizeText); _tileBackground->setChecked(false); updateThumbnail(); } } })); } void ThemeExportBox::exportTheme() { App::CallDelayed(st::defaultRippleAnimation.hideDuration, this, [this] { auto caption = lang(lng_theme_editor_choose_name); auto filter = "Themes (*.tdesktop-theme)"; auto name = "awesome.tdesktop-theme"; FileDialog::GetWritePath(caption, filter, name, base::lambda_guarded(this, [this](const QString &path) { zlib::FileToWrite zip; zip_fileinfo zfi = { { 0, 0, 0, 0, 0, 0 }, 0, 0, 0 }; auto background = std::string(_tileBackground->checked() ? "tiled" : "background") + (_isPng ? ".png" : ".jpg"); zip.openNewFile(background.c_str(), &zfi, nullptr, 0, nullptr, 0, nullptr, Z_DEFLATED, Z_DEFAULT_COMPRESSION); zip.writeInFile(_backgroundContent.constData(), _backgroundContent.size()); zip.closeFile(); auto scheme = "colors.tdesktop-theme"; zip.openNewFile(scheme, &zfi, nullptr, 0, nullptr, 0, nullptr, Z_DEFLATED, Z_DEFAULT_COMPRESSION); zip.writeInFile(_paletteContent.constData(), _paletteContent.size()); zip.closeFile(); zip.close(); if (zip.error() != ZIP_OK) { LOG(("Theme Error: could not export zip-ed theme, status: %1").arg(zip.error())); Ui::show(Box<InformBox>(lang(lng_theme_editor_error))); return; } auto result = zip.result(); QFile f(path); if (!f.open(QIODevice::WriteOnly)) { LOG(("Theme Error: could not open zip-ed theme file '%1' for writing").arg(path)); Ui::show(Box<InformBox>(lang(lng_theme_editor_error))); return; } if (f.write(result) != result.size()) { LOG(("Theme Error: could not write zip-ed theme to file '%1'").arg(path)); Ui::show(Box<InformBox>(lang(lng_theme_editor_error))); return; } Ui::hideLayer(); Ui::Toast::Show(lang(lng_theme_editor_done)); })); }); } Editor::Editor(QWidget*, const QString &path) : _scroll(this, st::settingsScroll) , _close(this, st::contactsMultiSelect.fieldCancel) , _select(this, st::contactsMultiSelect, langFactory(lng_country_ph)) , _leftShadow(this) , _topShadow(this) , _export(this, lang(lng_theme_editor_export_button).toUpper(), st::dialogsUpdateButton) { _inner = _scroll->setOwnedWidget(object_ptr<Inner>(this, path)); _export->setClickedCallback(_inner->exportCallback()); _inner->setErrorCallback([this] { Ui::show(Box<InformBox>(lang(lng_theme_editor_error))); // This could be from inner->_context observable notification. // We should not destroy it while iterating in subscribers. crl::on_main(this, [=] { closeEditor(); }); }); _inner->setFocusCallback([this] { App::CallDelayed(2 * st::boxDuration, this, [this] { _select->setInnerFocus(); }); }); _inner->setScrollCallback([this](int top, int bottom) { _scroll->scrollToY(top, bottom); }); _close->setClickedCallback([this] { closeEditor(); }); _close->show(anim::type::instant); _select->resizeToWidth(st::windowMinWidth); _select->setQueryChangedCallback([this](const QString &query) { _inner->filterRows(query); _scroll->scrollToY(0); }); _select->setSubmittedCallback([this](bool) { _inner->chooseRow(); }); _inner->prepare(); resizeToWidth(st::windowMinWidth); } void Editor::resizeEvent(QResizeEvent *e) { _export->resizeToWidth(width()); _close->moveToRight(0, 0); _select->resizeToWidth(width()); _select->moveToLeft(0, _close->height()); auto shadowTop = _select->y() + _select->height(); _topShadow->resize(width() - st::lineWidth, st::lineWidth); _topShadow->moveToLeft(st::lineWidth, shadowTop); _leftShadow->resize(st::lineWidth, height()); _leftShadow->moveToLeft(0, 0); auto scrollSize = QSize(width(), height() - shadowTop - _export->height()); if (_scroll->size() != scrollSize) { _scroll->resize(scrollSize); } _inner->resizeToWidth(width()); _scroll->moveToLeft(0, shadowTop); if (!_scroll->isHidden()) { auto scrollTop = _scroll->scrollTop(); _inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height()); } _export->moveToLeft(0, _scroll->y() + _scroll->height()); } void Editor::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Escape) { if (!_select->getQuery().isEmpty()) { _select->clearQuery(); } else if (auto window = App::wnd()) { window->setInnerFocus(); } } else if (e->key() == Qt::Key_Down) { _inner->selectSkip(1); } else if (e->key() == Qt::Key_Up) { _inner->selectSkip(-1); } else if (e->key() == Qt::Key_PageDown) { _inner->selectSkipPage(_scroll->height(), 1); } else if (e->key() == Qt::Key_PageUp) { _inner->selectSkipPage(_scroll->height(), -1); } } void Editor::focusInEvent(QFocusEvent *e) { _select->setInnerFocus(); } void Editor::paintEvent(QPaintEvent *e) { Painter p(this); p.fillRect(e->rect(), st::dialogsBg); p.setFont(st::settingsFixedBarFont); p.setPen(st::windowFg); p.drawTextLeft(st::themeEditorMargin.left(), st::themeEditorMargin.top(), width(), lang(lng_theme_editor_title)); } void Editor::Start() { auto palettePath = Local::themePaletteAbsolutePath(); if (palettePath.isEmpty()) { FileDialog::GetWritePath(lang(lng_theme_editor_save_palette), "Palette (*.tdesktop-palette)", "colors.tdesktop-palette", [](const QString &path) { if (!Local::copyThemeColorsToPalette(path)) { writeDefaultPalette(path); } if (!Apply(path)) { Ui::show(Box<InformBox>(lang(lng_theme_editor_error))); return; } KeepApplied(); if (auto window = App::wnd()) { window->showRightColumn(Box<Editor>(path)); } }); } else if (auto window = App::wnd()) { window->showRightColumn(Box<Editor>(palettePath)); } } void Editor::closeEditor() { if (auto window = App::wnd()) { window->showRightColumn(nullptr); } } } // namespace Theme } // namespace Window ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/window/themes/window_theme_editor.h����������������������������0000664�0000000�0000000�00000002062�13262451251�0026427�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace Ui { class FlatButton; class ScrollArea; class CrossButton; class MultiSelect; class PlainShadow; } // namespace Ui namespace Window { namespace Theme { class Editor : public TWidget { Q_OBJECT public: Editor(QWidget*, const QString &path); static void Start(); protected: void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; void keyPressEvent(QKeyEvent *e) override; void focusInEvent(QFocusEvent *e) override; private: void closeEditor(); object_ptr<Ui::ScrollArea> _scroll; class Inner; QPointer<Inner> _inner; object_ptr<Ui::CrossButton> _close; object_ptr<Ui::MultiSelect> _select; object_ptr<Ui::PlainShadow> _leftShadow; object_ptr<Ui::PlainShadow> _topShadow; object_ptr<Ui::FlatButton> _export; }; } // namespace Theme } // namespace Window ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/window/themes/window_theme_editor_block.cpp��������������������0000664�0000000�0000000�00000047470�13262451251�0030150�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "window/themes/window_theme_editor_block.h" #include "styles/style_window.h" #include "ui/effects/ripple_animation.h" #include "ui/widgets/shadow.h" #include "boxes/edit_color_box.h" #include "lang/lang_keys.h" namespace Window { namespace Theme { namespace { auto SearchSplitter = QRegularExpression(qsl("[\\@\\s\\-\\+\\(\\)\\[\\]\\{\\}\\<\\>\\,\\.\\:\\!\\_\\;\\\"\\'\\x0\\#]")); } // namespace class EditorBlock::Row { public: Row(const QString &name, const QString ©Of, QColor value); QString name() const { return _name; } void setCopyOf(const QString ©Of) { _copyOf = copyOf; fillSearchIndex(); } QString copyOf() const { return _copyOf; } void setValue(QColor value); const QColor &value() const { return _value; } QString description() const { return _description.originalText(); } const Text &descriptionText() const { return _description; } void setDescription(const QString &description) { _description.setText(st::defaultTextStyle, description); fillSearchIndex(); } const OrderedSet<QString> &searchWords() const { return _searchWords; } bool searchWordsContain(const QString &needle) const { for_const (auto &word, _searchWords) { if (word.startsWith(needle)) { return true; } } return false; } const OrderedSet<QChar> &searchStartChars() const { return _searchStartChars; } void setTop(int top) { _top = top; } int top() const { return _top; } void setHeight(int height) { _height = height; } int height() const { return _height; } Ui::RippleAnimation *ripple() const { return _ripple.get(); } Ui::RippleAnimation *setRipple(std::unique_ptr<Ui::RippleAnimation> ripple) const { _ripple = std::move(ripple); return _ripple.get(); } void resetRipple() const { _ripple = nullptr; } private: void fillValueString(); void fillSearchIndex(); QString _name; QString _copyOf; QColor _value; QString _valueString; Text _description = { st::windowMinWidth / 2 }; OrderedSet<QString> _searchWords; OrderedSet<QChar> _searchStartChars; int _top = 0; int _height = 0; mutable std::unique_ptr<Ui::RippleAnimation> _ripple; }; EditorBlock::Row::Row(const QString &name, const QString ©Of, QColor value) : _name(name) , _copyOf(copyOf) { setValue(value); } void EditorBlock::Row::setValue(QColor value) { _value = value; fillValueString(); fillSearchIndex(); } void EditorBlock::Row::fillValueString() { auto addHex = [=](int code) { if (code >= 0 && code < 10) { _valueString.append('0' + code); } else if (code >= 10 && code < 16) { _valueString.append('a' + (code - 10)); } }; auto addCode = [=](int code) { addHex(code / 16); addHex(code % 16); }; _valueString.resize(0); _valueString.reserve(9); _valueString.append('#'); addCode(_value.red()); addCode(_value.green()); addCode(_value.blue()); if (_value.alpha() != 255) { addCode(_value.alpha()); } } void EditorBlock::Row::fillSearchIndex() { _searchWords.clear(); _searchStartChars.clear(); auto toIndex = _name + ' ' + _copyOf + ' ' + TextUtilities::RemoveAccents(_description.originalText()) + ' ' + _valueString; auto words = toIndex.toLower().split(SearchSplitter, QString::SkipEmptyParts); for_const (auto &word, words) { _searchWords.insert(word); _searchStartChars.insert(word[0]); } } EditorBlock::EditorBlock(QWidget *parent, Type type, Context *context) : TWidget(parent) , _type(type) , _context(context) , _transparent(style::transparentPlaceholderBrush()) { setMouseTracking(true); subscribe(_context->updated, [this] { if (_mouseSelection) { _lastGlobalPos = QCursor::pos(); updateSelected(mapFromGlobal(_lastGlobalPos)); } update(); }); if (_type == Type::Existing) { subscribe(_context->appended, [this](const Context::AppendData &added) { auto name = added.name; auto value = added.value; feed(name, value); feedDescription(name, added.description); auto row = findRow(name); Assert(row != nullptr); auto possibleCopyOf = added.possibleCopyOf; auto copyOf = checkCopyOf(findRowIndex(row), possibleCopyOf) ? possibleCopyOf : QString(); removeFromSearch(*row); row->setCopyOf(copyOf); addToSearch(*row); _context->changed.notify({ QStringList(name), value }, true); _context->resized.notify(); _context->pending.notify({ name, copyOf, value }, true); }); } else { subscribe(_context->changed, [this](const Context::ChangeData &data) { checkCopiesChanged(0, data.names, data.value); }); } } void EditorBlock::feed(const QString &name, QColor value, const QString ©OfExisting) { if (findRow(name)) { // Remove the existing row and mark all its copies as unique keys. LOG(("Theme Warning: Color value '%1' appears more than once in the color scheme.").arg(name)); removeRow(name); } addRow(name, copyOfExisting, value); } bool EditorBlock::feedCopy(const QString &name, const QString ©Of) { if (auto row = findRow(copyOf)) { if (findRow(name)) { // Remove the existing row and mark all its copies as unique keys. LOG(("Theme Warning: Color value '%1' appears more than once in the color scheme.").arg(name)); removeRow(name); // row was invalidated by removeRow() call. row = findRow(copyOf); } addRow(name, copyOf, row->value()); } else { LOG(("Theme Warning: Skipping value '%1: %2' (expected a color value in #rrggbb or #rrggbbaa or a previously defined key in the color scheme)").arg(name).arg(copyOf)); } return true; } void EditorBlock::removeRow(const QString &name, bool removeCopyReferences) { auto it = _indices.find(name); Assert(it != _indices.cend()); auto index = it.value(); for (auto i = index + 1, count = static_cast<int>(_data.size()); i != count; ++i) { auto &row = _data[i]; removeFromSearch(row); _indices[row.name()] = i - 1; if (removeCopyReferences && row.copyOf() == name) { row.setCopyOf(QString()); } } _data.erase(_data.begin() + index); _indices.erase(it); for (auto i = index, count = static_cast<int>(_data.size()); i != count; ++i) { addToSearch(_data[i]); } } void EditorBlock::addToSearch(const Row &row) { auto query = _searchQuery; if (!query.isEmpty()) resetSearch(); auto index = findRowIndex(&row); for_const (auto ch, row.searchStartChars()) { _searchIndex[ch].insert(index); } if (!query.isEmpty()) searchByQuery(query); } void EditorBlock::removeFromSearch(const Row &row) { auto query = _searchQuery; if (!query.isEmpty()) resetSearch(); auto index = findRowIndex(&row); for_const (auto ch, row.searchStartChars()) { auto it = _searchIndex.find(ch); if (it != _searchIndex.cend()) { it->remove(index); if (it->isEmpty()) { _searchIndex.erase(it); } } } if (!query.isEmpty()) searchByQuery(query); } void EditorBlock::filterRows(const QString &query) { searchByQuery(query); } void EditorBlock::chooseRow() { if (_selected < 0) { return; } activateRow(rowAtIndex(_selected)); } void EditorBlock::activateRow(const Row &row) { if (_context->box) { if (_type == Type::Existing) { _context->possibleCopyOf = row.name(); _context->box->showColor(row.value()); } } else { _editing = findRowIndex(&row); if (auto box = Ui::show(Box<EditColorBox>(row.name(), row.value()))) { box->setSaveCallback(base::lambda_guarded(this, [this](QColor value) { saveEditing(value); })); box->setCancelCallback(base::lambda_guarded(this, [this] { cancelEditing(); })); _context->box = box; _context->name = row.name(); _context->updated.notify(); } } } bool EditorBlock::selectSkip(int direction) { _mouseSelection = false; auto maxSelected = (isSearch() ? _searchResults.size() : _data.size()) - 1; auto newSelected = _selected + direction; if (newSelected < -1 || newSelected > maxSelected) { newSelected = maxSelected; } if (auto changed = (newSelected != _selected)) { setSelected(newSelected); scrollToSelected(); return (newSelected >= 0); } return false; } void EditorBlock::scrollToSelected() { if (_selected >= 0) { Context::ScrollData update; update.type = _type; update.position = rowAtIndex(_selected).top(); update.height = rowAtIndex(_selected).height(); _context->scroll.notify(update, true); } } void EditorBlock::searchByQuery(QString query) { auto words = TextUtilities::PrepareSearchWords(query, &SearchSplitter); query = words.isEmpty() ? QString() : words.join(' '); if (_searchQuery != query) { setSelected(-1); setPressed(-1); _searchQuery = query; _searchResults.clear(); auto toFilter = OrderedSet<int>(); for_const (auto &word, words) { if (word.isEmpty()) continue; auto testToFilter = _searchIndex.value(word[0]); if (testToFilter.isEmpty()) { toFilter.clear(); break; } else if (toFilter.isEmpty() || testToFilter.size() < toFilter.size()) { toFilter = testToFilter; } } if (!toFilter.isEmpty()) { auto allWordsFound = [&words](const Row &row) { for_const (auto &word, words) { if (!row.searchWordsContain(word)) { return false; } } return true; }; for_const (auto index, toFilter) { if (allWordsFound(_data[index])) { _searchResults.push_back(index); } } } _context->resized.notify(true); } } const QColor *EditorBlock::find(const QString &name) { if (auto row = findRow(name)) { return &row->value(); } return nullptr; } bool EditorBlock::feedDescription(const QString &name, const QString &description) { if (auto row = findRow(name)) { removeFromSearch(*row); row->setDescription(description); addToSearch(*row); return true; } return false; } template <typename Callback> void EditorBlock::enumerateRows(Callback callback) { if (isSearch()) { for_const (auto index, _searchResults) { if (!callback(_data[index])) { break; } } } else { for (auto &row : _data) { if (!callback(row)) { break; } } } } template <typename Callback> void EditorBlock::enumerateRows(Callback callback) const { if (isSearch()) { for_const (auto index, _searchResults) { if (!callback(_data[index])) { break; } } } else { for_const (auto &row, _data) { if (!callback(row)) { break; } } } } template <typename Callback> void EditorBlock::enumerateRowsFrom(int top, Callback callback) { auto started = false; auto index = 0; enumerateRows([top, callback, &started, &index](Row &row) { if (!started) { if (row.top() + row.height() <= top) { ++index; return true; } started = true; } return callback(index++, row); }); } template <typename Callback> void EditorBlock::enumerateRowsFrom(int top, Callback callback) const { auto started = false; enumerateRows([top, callback, &started](const Row &row) { if (!started) { if (row.top() + row.height() <= top) { return true; } started = true; } return callback(row); }); } int EditorBlock::resizeGetHeight(int newWidth) { auto result = 0; auto descriptionWidth = newWidth - st::themeEditorMargin.left() - st::themeEditorMargin.right(); enumerateRows([&](Row &row) { row.setTop(result); auto height = row.height(); if (!height) { height = st::themeEditorMargin.top() + st::themeEditorSampleSize.height(); if (!row.descriptionText().isEmpty()) { height += st::themeEditorDescriptionSkip + row.descriptionText().countHeight(descriptionWidth); } height += st::themeEditorMargin.bottom(); row.setHeight(height); } result += row.height(); return true; }); if (_type == Type::New) { setHidden(!result); } if (_type == Type::Existing && !result && !isSearch()) { return st::noContactsHeight; } return result; } void EditorBlock::mousePressEvent(QMouseEvent *e) { updateSelected(e->pos()); setPressed(_selected); } void EditorBlock::mouseReleaseEvent(QMouseEvent *e) { auto pressed = _pressed; setPressed(-1); if (pressed == _selected) { if (_context->box) { chooseRow(); } else if (_selected >= 0) { App::CallDelayed(st::defaultRippleAnimation.hideDuration, this, [this, index = findRowIndex(&rowAtIndex(_selected))] { if (index >= 0 && index < _data.size()) { activateRow(_data[index]); } }); } } } void EditorBlock::saveEditing(QColor value) { if (_editing < 0) { return; } auto &row = _data[_editing]; auto name = row.name(); if (_type == Type::New) { auto removing = std::exchange(_editing, -1); setSelected(-1); setPressed(-1); auto possibleCopyOf = _context->possibleCopyOf.isEmpty() ? row.copyOf() : _context->possibleCopyOf; auto color = value; auto description = row.description(); removeRow(name, false); _context->appended.notify({ name, possibleCopyOf, color, description }, true); } else if (_type == Type::Existing) { removeFromSearch(row); auto valueChanged = (row.value() != value); if (valueChanged) { row.setValue(value); } auto possibleCopyOf = _context->possibleCopyOf.isEmpty() ? row.copyOf() : _context->possibleCopyOf; auto copyOf = checkCopyOf(_editing, possibleCopyOf) ? possibleCopyOf : QString(); auto copyOfChanged = (row.copyOf() != copyOf); if (copyOfChanged) { row.setCopyOf(copyOf); } addToSearch(row); if (valueChanged || copyOfChanged) { checkCopiesChanged(_editing + 1, QStringList(name), value); _context->pending.notify({ name, copyOf, value }, true); } } cancelEditing(); } void EditorBlock::checkCopiesChanged(int startIndex, QStringList names, QColor value) { for (auto i = startIndex, count = static_cast<int>(_data.size()); i != count; ++i) { auto &checkIfIsCopy = _data[i]; if (names.contains(checkIfIsCopy.copyOf())) { removeFromSearch(checkIfIsCopy); checkIfIsCopy.setValue(value); names.push_back(checkIfIsCopy.name()); addToSearch(checkIfIsCopy); } } if (_type == Type::Existing) { _context->changed.notify({ names, value }, true); } } void EditorBlock::cancelEditing() { if (_editing >= 0) { updateRow(_data[_editing]); } _editing = -1; if (auto box = base::take(_context->box)) { box->closeBox(); } _context->possibleCopyOf = QString(); if (!_context->name.isEmpty()) { _context->name = QString(); _context->updated.notify(); } } bool EditorBlock::checkCopyOf(int index, const QString &possibleCopyOf) { auto copyOfIndex = findRowIndex(possibleCopyOf); return (copyOfIndex >= 0 && index > copyOfIndex && _data[copyOfIndex].value().toRgb() == _data[index].value().toRgb()); } void EditorBlock::mouseMoveEvent(QMouseEvent *e) { if (_lastGlobalPos != e->globalPos() || _mouseSelection) { _lastGlobalPos = e->globalPos(); updateSelected(e->pos()); } } void EditorBlock::updateSelected(QPoint localPosition) { _mouseSelection = true; auto top = localPosition.y(); auto underMouseIndex = -1; enumerateRowsFrom(top, [&underMouseIndex, top](int index, const Row &row) { if (row.top() <= top) { underMouseIndex = index; } return false; }); setSelected(underMouseIndex); } void EditorBlock::leaveEventHook(QEvent *e) { _mouseSelection = false; setSelected(-1); } void EditorBlock::paintEvent(QPaintEvent *e) { Painter p(this); auto clip = e->rect(); if (_data.empty()) { p.fillRect(clip, st::dialogsBg); p.setFont(st::noContactsFont); p.setPen(st::noContactsColor); p.drawText(QRect(0, 0, width(), st::noContactsHeight), lang(lng_theme_editor_no_keys)); } auto ms = getms(); auto cliptop = clip.y(); auto clipbottom = cliptop + clip.height(); enumerateRowsFrom(cliptop, [this, &p, clipbottom, ms](int index, const Row &row) { if (row.top() >= clipbottom) { return false; } paintRow(p, index, row, ms); return true; }); } void EditorBlock::paintRow(Painter &p, int index, const Row &row, TimeMs ms) { auto rowTop = row.top() + st::themeEditorMargin.top(); auto rect = QRect(0, row.top(), width(), row.height()); auto selected = (_pressed >= 0) ? (index == _pressed) : (index == _selected); auto active = (findRowIndex(&row) == _editing); p.fillRect(rect, active ? st::dialogsBgActive : selected ? st::dialogsBgOver : st::dialogsBg); if (auto ripple = row.ripple()) { ripple->paint(p, 0, row.top(), width(), ms, &(active ? st::activeButtonBgRipple : st::windowBgRipple)->c); if (ripple->empty()) { row.resetRipple(); } } auto sample = QRect(width() - st::themeEditorMargin.right() - st::themeEditorSampleSize.width(), rowTop, st::themeEditorSampleSize.width(), st::themeEditorSampleSize.height()); Ui::Shadow::paint(p, sample, width(), st::defaultRoundShadow); if (row.value().alpha() != 255) { p.fillRect(myrtlrect(sample), _transparent); } p.fillRect(myrtlrect(sample), row.value()); auto rowWidth = width() - st::themeEditorMargin.left() - st::themeEditorMargin.right(); auto nameWidth = rowWidth - st::themeEditorSampleSize.width() - st::themeEditorDescriptionSkip; p.setFont(st::themeEditorNameFont); p.setPen(active ? st::dialogsNameFgActive : selected ? st::dialogsNameFgOver : st::dialogsNameFg); p.drawTextLeft(st::themeEditorMargin.left(), rowTop, width(), st::themeEditorNameFont->elided(row.name(), nameWidth)); if (!row.copyOf().isEmpty()) { auto copyTop = rowTop + st::themeEditorNameFont->height; p.setFont(st::themeEditorCopyNameFont); p.drawTextLeft(st::themeEditorMargin.left(), copyTop, width(), st::themeEditorCopyNameFont->elided("= " + row.copyOf(), nameWidth)); } if (!row.descriptionText().isEmpty()) { auto descriptionTop = rowTop + st::themeEditorSampleSize.height() + st::themeEditorDescriptionSkip; p.setPen(active ? st::dialogsTextFgActive : selected ? st::dialogsTextFgOver : st::dialogsTextFg); row.descriptionText().drawLeft(p, st::themeEditorMargin.left(), descriptionTop, rowWidth, width()); } if (isEditing() && !active && (_type == Type::New || (_editing >= 0 && findRowIndex(&row) >= _editing))) { p.fillRect(rect, st::layerBg); } } void EditorBlock::setSelected(int selected) { if (isEditing()) { if (_type == Type::New) { selected = -1; } else if (_editing >= 0 && selected >= 0 && findRowIndex(&rowAtIndex(selected)) >= _editing) { selected = -1; } } if (_selected != selected) { if (_selected >= 0) updateRow(rowAtIndex(_selected)); _selected = selected; if (_selected >= 0) updateRow(rowAtIndex(_selected)); setCursor((_selected >= 0) ? style::cur_pointer : style::cur_default); } } void EditorBlock::setPressed(int pressed) { if (_pressed != pressed) { if (_pressed >= 0) { updateRow(rowAtIndex(_pressed)); stopLastRipple(_pressed); } _pressed = pressed; if (_pressed >= 0) { addRowRipple(_pressed); updateRow(rowAtIndex(_pressed)); } } } void EditorBlock::addRowRipple(int index) { auto &row = rowAtIndex(index); auto ripple = row.ripple(); if (!ripple) { auto mask = Ui::RippleAnimation::rectMask(QSize(width(), row.height())); ripple = row.setRipple(std::make_unique<Ui::RippleAnimation>(st::defaultRippleAnimation, std::move(mask), [this, index = findRowIndex(&row)] { updateRow(_data[index]); })); } auto origin = mapFromGlobal(QCursor::pos()) - QPoint(0, row.top()); ripple->add(origin); } void EditorBlock::stopLastRipple(int index) { auto &row = rowAtIndex(index); if (row.ripple()) { row.ripple()->lastStop(); } } void EditorBlock::updateRow(const Row &row) { update(0, row.top(), width(), row.height()); } void EditorBlock::addRow(const QString &name, const QString ©Of, QColor value) { _data.push_back({ name, copyOf, value }); _indices.insert(name, _data.size() - 1); addToSearch(_data.back()); } EditorBlock::Row &EditorBlock::rowAtIndex(int index) { if (isSearch()) { return _data[_searchResults[index]]; } return _data[index]; } int EditorBlock::findRowIndex(const QString &name) const { return _indices.value(name, -1);; } EditorBlock::Row *EditorBlock::findRow(const QString &name) { auto index = findRowIndex(name); return (index >= 0) ? &_data[index] : nullptr; } int EditorBlock::findRowIndex(const Row *row) { return row ? (row - &_data[0]) : -1; } } // namespace Theme } // namespace Window ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/window/themes/window_theme_editor_block.h����������������������0000664�0000000�0000000�00000007254�13262451251�0027611�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once class EditColorBox; namespace Window { namespace Theme { class EditorBlock : public TWidget, private base::Subscriber { public: enum class Type { Existing, New, }; struct Context { QPointer<EditColorBox> box; QString name; QString possibleCopyOf; base::Observable<void> updated; base::Observable<void> resized; struct AppendData { QString name; QString possibleCopyOf; QColor value; QString description; }; base::Observable<AppendData> appended; struct ChangeData { QStringList names; QColor value; }; base::Observable<ChangeData> changed; struct EditionData { QString name; QString copyOf; QColor value; }; base::Observable<EditionData> pending; struct ScrollData { Type type; int position; int height; }; base::Observable<ScrollData> scroll; }; EditorBlock(QWidget *parent, Type type, Context *context); void filterRows(const QString &query); void chooseRow(); bool hasSelected() const { return (_selected >= 0); } void clearSelected() { setSelected(-1); } bool selectSkip(int direction); void feed(const QString &name, QColor value, const QString ©OfExisting = QString()); bool feedCopy(const QString &name, const QString ©Of); const QColor *find(const QString &name); bool feedDescription(const QString &name, const QString &description); protected: void paintEvent(QPaintEvent *e) override; int resizeGetHeight(int newWidth) override; void mousePressEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void leaveEventHook(QEvent *e) override; private: class Row; void addRow(const QString &name, const QString ©Of, QColor value); void removeRow(const QString &name, bool removeCopyReferences = true); void addToSearch(const Row &row); void removeFromSearch(const Row &row); template <typename Callback> void enumerateRows(Callback callback); template <typename Callback> void enumerateRows(Callback callback) const; template <typename Callback> void enumerateRowsFrom(int top, Callback callback); template <typename Callback> void enumerateRowsFrom(int top, Callback callback) const; Row &rowAtIndex(int index); int findRowIndex(const QString &name) const; Row *findRow(const QString &name); int findRowIndex(const Row *row); void updateRow(const Row &row); void paintRow(Painter &p, int index, const Row &row, TimeMs ms); void updateSelected(QPoint localPosition); void setSelected(int selected); void setPressed(int pressed); void addRowRipple(int index); void stopLastRipple(int index); void scrollToSelected(); bool isEditing() const { return !_context->name.isEmpty(); } void saveEditing(QColor value); void cancelEditing(); bool checkCopyOf(int index, const QString &possibleCopyOf); void checkCopiesChanged(int startIndex, QStringList names, QColor value); void activateRow(const Row &row); bool isSearch() const { return !_searchQuery.isEmpty(); } void searchByQuery(QString query); void resetSearch() { searchByQuery(QString()); } Type _type = Type::Existing; Context *_context = nullptr; std::vector<Row> _data; QMap<QString, int> _indices; QString _searchQuery; QVector<int> _searchResults; QMap<QChar, OrderedSet<int>> _searchIndex; int _selected = -1; int _pressed = -1; int _editing = -1; QPoint _lastGlobalPos; bool _mouseSelection = false; QBrush _transparent; }; } // namespace Theme } // namespace Window ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/window/themes/window_theme_preview.cpp�������������������������0000664�0000000�0000000�00000123121�13262451251�0027155�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "window/themes/window_theme_preview.h" #include "window/themes/window_theme.h" #include "lang/lang_keys.h" #include "platform/platform_window_title.h" #include "ui/text_options.h" #include "styles/style_widgets.h" #include "styles/style_window.h" #include "styles/style_mediaview.h" #include "styles/style_history.h" #include "styles/style_dialogs.h" #include "styles/style_info.h" namespace Window { namespace Theme { namespace { QString fillLetters(const QString &name) { QList<QString> letters; QList<int> levels; auto level = 0; auto letterFound = false; auto ch = name.constData(), end = ch + name.size(); while (ch != end) { auto emojiLength = 0; if (auto emoji = Ui::Emoji::Find(ch, end, &emojiLength)) { ch += emojiLength; } else if (ch->isHighSurrogate()) { ++ch; if (ch != end && ch->isLowSurrogate()) { ++ch; } } else if (!letterFound && ch->isLetterOrNumber()) { letterFound = true; if (ch + 1 != end && chIsDiac(*(ch + 1))) { letters.push_back(QString(ch, 2)); levels.push_back(level); ++ch; } else { letters.push_back(QString(ch, 1)); levels.push_back(level); } ++ch; } else { if (*ch == ' ') { level = 0; letterFound = false; } else if (letterFound && *ch == '-') { level = 1; letterFound = true; } ++ch; } } // We prefer the second letter to be after ' ', but it can also be after '-'. auto result = QString(); if (!letters.isEmpty()) { result += letters.front(); auto bestIndex = 0; auto bestLevel = 2; for (auto i = letters.size(); i != 1;) { if (levels[--i] < bestLevel) { bestIndex = i; bestLevel = levels[i]; } } if (bestIndex > 0) { result += letters[bestIndex]; } } return result.toUpper(); } class Generator { public: Generator(const Instance &theme, CurrentData &¤t); QImage generate(); private: enum class Status { None, Sent, Received }; struct Row { Text name; QString letters; enum class Type { User, Group, Channel }; Type type = Type::User; int peerIndex = 0; int unreadCounter = 0; bool muted = false; bool pinned = false; QString date; Text text; Status status = Status::None; bool selected = false; bool active = false; }; struct Bubble { int width = 0; int height = 0; bool outbg = false; Status status = Status::None; QString date; bool attached = false; bool tail = true; Text text = { st::msgMinWidth }; QVector<int> waveform; int waveactive = 0; QString wavestatus; QImage photo; int photoWidth = 0; int photoHeight = 0; Text replyName = { st::msgMinWidth }; Text replyText = { st::msgMinWidth }; }; void prepare(); void addRow(QString name, int peerIndex, QString date, QString text); void addBubble(Bubble bubble, int width, int height, QString date, Status status); void addAudioBubble(QVector<int> waveform, int waveactive, QString wavestatus, QString date, Status status); void addTextBubble(QString text, QString date, Status status); void addDateBubble(QString date); void addPhotoBubble(QString image, QString caption, QString date, Status status); QSize computeSkipBlock(Status status, QString date); int computeInfoWidth(Status status, QString date); void generateData(); void paintHistoryList(); void paintHistoryBackground(); void paintTopBar(); void paintComposeArea(); void paintDialogs(); void paintDialogsList(); void paintHistoryShadows(); void paintRow(const Row &row); void paintBubble(const Bubble &bubble); void paintService(QString text); void paintUserpic(int x, int y, Row::Type type, int index, QString letters); void setTextPalette(const style::TextPalette &st); void restoreTextPalette(); const Instance &_theme; const style::palette &_palette; CurrentData _current; Painter *_p = nullptr; QRect _rect; QRect _inner; QRect _body; QRect _dialogs; QRect _dialogsList; QRect _topBar; QRect _composeArea; QRect _history; int _rowsTop = 0; std::vector<Row> _rows; Text _topBarName; QString _topBarStatus; bool _topBarStatusActive = false; int _historyBottom = 0; std::vector<Bubble> _bubbles; style::TextPalette _textPalette; }; void Generator::prepare() { _rect = QRect(0, 0, st::themePreviewMargin.left() + st::themePreviewSize.width() + st::themePreviewMargin.right(), st::themePreviewMargin.top() + st::themePreviewSize.height() + st::themePreviewMargin.bottom()); _inner = _rect.marginsRemoved(st::themePreviewMargin); _body = _inner.marginsRemoved(QMargins(0, Platform::PreviewTitleHeight(), 0, 0)); _dialogs = QRect(_body.x(), _body.y(), st::themePreviewDialogsWidth, _body.height()); _dialogsList = _dialogs.marginsRemoved(QMargins(0, st::dialogsFilterPadding.y() + st::dialogsMenuToggle.height + st::dialogsFilterPadding.y(), 0, st::dialogsPadding.y())); _topBar = QRect(_dialogs.x() + _dialogs.width(), _dialogs.y(), _body.width() - _dialogs.width(), st::topBarHeight); _composeArea = QRect(_topBar.x(), _body.y() + _body.height() - st::historySendSize.height(), _topBar.width(), st::historySendSize.height()); _history = QRect(_topBar.x(), _topBar.y() + _topBar.height(), _topBar.width(), _body.height() - _topBar.height() - _composeArea.height()); generateData(); } void Generator::addRow(QString name, int peerIndex, QString date, QString text) { Row row; row.name.setText(st::msgNameStyle, name, Ui::NameTextOptions()); row.letters = fillLetters(name); row.peerIndex = peerIndex; row.date = date; row.text.setRichText(st::dialogsTextStyle, text, Ui::DialogTextOptions()); _rows.push_back(std::move(row)); } void Generator::addBubble(Bubble bubble, int width, int height, QString date, Status status) { bubble.width = width; bubble.height = height; bubble.date = date; bubble.status = status; _bubbles.push_back(std::move(bubble)); } void Generator::addAudioBubble(QVector<int> waveform, int waveactive, QString wavestatus, QString date, Status status) { Bubble bubble; bubble.waveform = waveform; bubble.waveactive = waveactive; bubble.wavestatus = wavestatus; auto skipBlock = computeSkipBlock(status, date); auto width = st::msgFileMinWidth; auto tleft = 0, tright = 0; tleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right(); tright = st::msgFileThumbPadding.left(); accumulate_max(width, tleft + st::normalFont->width(wavestatus) + skipBlock.width() + st::msgPadding.right()); accumulate_min(width, st::msgMaxWidth); auto height = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom(); addBubble(std::move(bubble), width, height, date, status); } QSize Generator::computeSkipBlock(Status status, QString date) { auto infoWidth = computeInfoWidth(status, date); auto width = st::msgDateSpace + infoWidth - st::msgDateDelta.x(); auto height = st::msgDateFont->height - st::msgDateDelta.y(); return QSize(width, height); } int Generator::computeInfoWidth(Status status, QString date) { auto result = st::msgDateFont->width(date); if (status != Status::None) { result += st::historySendStateSpace; } return result; } void Generator::addTextBubble(QString text, QString date, Status status) { Bubble bubble; auto skipBlock = computeSkipBlock(status, date); bubble.text.setRichText(st::messageTextStyle, text + textcmdSkipBlock(skipBlock.width(), skipBlock.height()), Ui::ItemTextDefaultOptions()); auto width = _history.width() - st::msgMargin.left() - st::msgMargin.right(); accumulate_min(width, st::msgPadding.left() + bubble.text.maxWidth() + st::msgPadding.right()); accumulate_min(width, st::msgMaxWidth); auto textWidth = qMax(width - st::msgPadding.left() - st::msgPadding.right(), 1); auto textHeight = bubble.text.countHeight(textWidth); auto height = st::msgPadding.top() + textHeight + st::msgPadding.bottom(); addBubble(std::move(bubble), width, height, date, status); } void Generator::addDateBubble(QString date) { Bubble bubble; addBubble(std::move(bubble), 0, 0, date, Status::None); } void Generator::addPhotoBubble(QString image, QString caption, QString date, Status status) { Bubble bubble; bubble.photo.load(image); bubble.photoWidth = convertScale(bubble.photo.width() / 2); bubble.photoHeight = convertScale(bubble.photo.height() / 2); auto skipBlock = computeSkipBlock(status, date); bubble.text.setRichText(st::messageTextStyle, caption + textcmdSkipBlock(skipBlock.width(), skipBlock.height()), Ui::ItemTextDefaultOptions()); auto width = _history.width() - st::msgMargin.left() - st::msgMargin.right(); accumulate_min(width, bubble.photoWidth); accumulate_min(width, st::msgMaxWidth); auto textWidth = qMax(width - st::msgPadding.left() - st::msgPadding.right(), 1); auto textHeight = bubble.text.countHeight(textWidth); auto height = st::mediaCaptionSkip + textHeight + st::msgPadding.bottom(); addBubble(std::move(bubble), width, height, date, status); } void Generator::generateData() { _rows.reserve(9); addRow("Eva Summer", 0, "11:00", "Reminds me of a Chinese proverb: the best time to plant a tree was 20 years ago."); _rows.back().active = true; _rows.back().pinned = true; addRow("Alexandra Smith", 7, "10:00", "This is amazing!"); _rows.back().unreadCounter = 2; addRow("Mike Apple", 2, "9:00", textcmdLink(1, QChar(55357) + QString() + QChar(56836) + " Sticker")); _rows.back().unreadCounter = 2; _rows.back().muted = true; addRow("Evening Club", 1, "8:00", textcmdLink(1, "Eva: Photo")); _rows.back().type = Row::Type::Group; addRow("Old Pirates", 6, "7:00", textcmdLink(1, "Max:") + " Yo-ho-ho!"); _rows.back().type = Row::Type::Group; addRow("Max Bright", 3, "6:00", "How about some coffee?"); _rows.back().status = Status::Received; addRow("Natalie Parker", 4, "5:00", "OK, great)"); _rows.back().status = Status::Received; addRow("Davy Jones", 5, "4:00", textcmdLink(1, "Keynote.pdf")); _topBarName.setText(st::msgNameStyle, "Eva Summer", Ui::NameTextOptions()); _topBarStatus = "online"; _topBarStatusActive = true; addPhotoBubble(":/gui/art/sunrise.jpg", "Nearly missed this sunrise", "7:00", Status::None); int wavedata[] = { 0, 0, 0, 0, 27, 31, 4, 1, 0, 0, 23, 30, 18, 9, 7, 19, 4, 2, 2, 2, 0, 0, 15, 15, 15, 15, 3, 15, 19, 3, 2, 0, 0, 0, 0, 0, 3, 12, 16, 6, 4, 6, 14, 12, 2, 12, 12, 11, 3, 0, 7, 5, 7, 4, 7, 5, 2, 4, 0, 9, 5, 7, 6, 2, 2, 0, 0 }; auto waveform = QVector<int>(base::array_size(wavedata)); memcpy(waveform.data(), wavedata, sizeof(wavedata)); addAudioBubble(waveform, 33, "0:07", "8:00", Status::None); _bubbles.back().outbg = true; _bubbles.back().status = Status::Received; addDateBubble("December 26"); addTextBubble("Twenty years from now you will be more disappointed by the things that you didn't do than by the ones you did do, so throw off the bowlines, sail away from safe harbor, catch the trade winds in your sails.", "10:00", Status::Received); _bubbles.back().tail = false; _bubbles.back().outbg = true; addTextBubble("Mark Twain said that " + QString() + QChar(9757) + QChar(55356) + QChar(57339), "10:00", Status::Received); _bubbles.back().outbg = true; _bubbles.back().attached = true; _bubbles.back().tail = true; addTextBubble("Reminds me of a Chinese proverb: the best time to plant a tree was 20 years ago. The second best time is now.", "11:00", Status::None); _bubbles.back().replyName.setText(st::msgNameStyle, "Alex Cassio", Ui::NameTextOptions()); _bubbles.back().replyText.setText(st::messageTextStyle, "Mark Twain said that " + QString() + QChar(9757) + QChar(55356) + QChar(57339), Ui::DialogTextOptions()); } Generator::Generator(const Instance &theme, CurrentData &¤t) : _theme(theme) , _palette(_theme.palette) , _current(std::move(current)) { } QImage Generator::generate() { prepare(); auto result = QImage( _rect.size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); result.setDevicePixelRatio(cRetinaFactor()); result.fill(st::themePreviewBg->c); { Painter p(&result); PainterHighQualityEnabler hq(p); _p = &p; _p->fillRect(_body, QColor(0, 0, 0)); _p->fillRect(_body, st::windowBg[_palette]); paintHistoryList(); paintTopBar(); paintComposeArea(); paintDialogs(); paintHistoryShadows(); } Platform::PreviewWindowFramePaint(result, _palette, _body, _rect.width()); return result; } void Generator::paintHistoryList() { paintHistoryBackground(); _historyBottom = _history.y() + _history.height(); _historyBottom -= st::historyPaddingBottom; for (auto i = _bubbles.size(); i != 0;) { auto &bubble = _bubbles[--i]; if (bubble.width > 0) { paintBubble(bubble); } else { paintService(bubble.date); } } _p->setClipping(false); } void Generator::paintHistoryBackground() { auto fromy = (-st::topBarHeight); auto background = _theme.background; auto tiled = _theme.tiled; if (background.isNull()) { if (_current.backgroundId == Window::Theme::kThemeBackground) { background.load(qsl(":/gui/art/bg.jpg")); tiled = false; } else { background = std::move(_current.backgroundImage); tiled = _current.backgroundTiled; } } background = std::move(background).convertToFormat( QImage::Format_ARGB32_Premultiplied); background.setDevicePixelRatio(cRetinaFactor()); _p->setClipRect(_history); if (tiled) { auto width = background.width(); auto height = background.height(); auto repeatTimesX = qCeil(_history.width() * cIntRetinaFactor() / float64(width)); auto repeatTimesY = qCeil((_history.height() - fromy) * cIntRetinaFactor() / float64(height)); auto imageForTiled = QImage( width * repeatTimesX, height * repeatTimesY, QImage::Format_ARGB32_Premultiplied); imageForTiled.setDevicePixelRatio(background.devicePixelRatio()); auto imageForTiledBytes = imageForTiled.bits(); auto bytesInLine = width * sizeof(uint32); for (auto timesY = 0; timesY != repeatTimesY; ++timesY) { auto imageBytes = background.constBits(); for (auto y = 0; y != height; ++y) { for (auto timesX = 0; timesX != repeatTimesX; ++timesX) { memcpy(imageForTiledBytes, imageBytes, bytesInLine); imageForTiledBytes += bytesInLine; } imageBytes += background.bytesPerLine(); imageForTiledBytes += imageForTiled.bytesPerLine() - (repeatTimesX * bytesInLine); } } _p->drawImage(_history.x(), _history.y() + fromy, imageForTiled); } else { PainterHighQualityEnabler hq(*_p); auto fill = QRect(_topBar.x(), _topBar.y(), _topBar.width(), _body.height()); QRect to, from; ComputeBackgroundRects(fill, background.size(), to, from); to.moveTop(to.top() + fromy); to.moveTopLeft(to.topLeft() + _history.topLeft()); _p->drawImage(to, background, from); } _p->setClipping(false); } void Generator::paintTopBar() { _p->fillRect(_topBar, st::topBarBg[_palette]); auto right = st::topBarMenuToggle.width; st::topBarMenuToggle.icon[_palette].paint(*_p, _topBar.x() + _topBar.width() - right + st::topBarMenuToggle.iconPosition.x(), _topBar.y() + st::topBarMenuToggle.iconPosition.y(), _rect.width()); right += st::topBarSearch.width; st::topBarSearch.icon[_palette].paint(*_p, _topBar.x() + _topBar.width() - right + st::topBarSearch.iconPosition.x(), _topBar.y() + st::topBarSearch.iconPosition.y(), _rect.width()); right += st::topBarCallSkip + st::topBarCall.width; st::topBarCall.icon[_palette].paint(*_p, _topBar.x() + _topBar.width() - right + st::topBarCall.iconPosition.x(), _topBar.y() + st::topBarCall.iconPosition.y(), _rect.width()); auto decreaseWidth = st::topBarCall.width + st::topBarCallSkip + st::topBarSearch.width + st::topBarMenuToggle.width; auto nameleft = _topBar.x() + st::topBarArrowPadding.right(); auto nametop = _topBar.y() + st::topBarArrowPadding.top(); auto statustop = _topBar.y() + st::topBarHeight - st::topBarArrowPadding.bottom() - st::dialogsTextFont->height; auto namewidth = _topBar.x() + _topBar.width() - decreaseWidth - nameleft - st::topBarArrowPadding.right(); _p->setFont(st::dialogsTextFont); _p->setPen(_topBarStatusActive ? st::historyStatusFgActive[_palette] : st::historyStatusFg[_palette]); _p->drawText(nameleft, statustop + st::dialogsTextFont->ascent, _topBarStatus); _p->setPen(st::dialogsNameFg[_palette]); _topBarName.drawElided(*_p, nameleft, nametop, namewidth); } void Generator::paintComposeArea() { _p->fillRect(_composeArea, st::historyReplyBg[_palette]); auto controlsTop = _composeArea.y() + _composeArea.height() - st::historySendSize.height(); st::historyAttach.icon[_palette].paint(*_p, _composeArea.x() + st::historyAttach.iconPosition.x(), controlsTop + st::historyAttach.iconPosition.y(), _rect.width()); auto right = st::historySendRight + st::historySendSize.width(); st::historyRecordVoice[_palette].paintInCenter(*_p, QRect(_composeArea.x() + _composeArea.width() - right, controlsTop, st::historySendSize.width(), st::historySendSize.height())); right += st::historyAttachEmoji.width; auto attachEmojiLeft = _composeArea.x() + _composeArea.width() - right; _p->fillRect(attachEmojiLeft, controlsTop, st::historyAttachEmoji.width, st::historyAttachEmoji.height, st::historyComposeAreaBg[_palette]); st::historyAttachEmoji.icon[_palette].paint(*_p, attachEmojiLeft + st::historyAttachEmoji.iconPosition.x(), controlsTop + st::historyAttachEmoji.iconPosition.y(), _rect.width()); auto pen = st::historyEmojiCircleFg[_palette]->p; pen.setWidth(st::historyEmojiCircleLine); pen.setCapStyle(Qt::RoundCap); _p->setPen(pen); _p->setBrush(Qt::NoBrush); PainterHighQualityEnabler hq(*_p); auto inner = QRect(QPoint(attachEmojiLeft + (st::historyAttachEmoji.width - st::historyEmojiCircle.width()) / 2, controlsTop + st::historyEmojiCircleTop), st::historyEmojiCircle); _p->drawEllipse(inner); auto fakeMargin = 0; switch (cScale()) { case dbisOneAndQuarter: fakeMargin = 1; break; case dbisOneAndHalf: fakeMargin = 2; break; case dbisTwo: fakeMargin = 4; break; } auto fieldLeft = _composeArea.x() + st::historyAttach.width + fakeMargin; auto fieldTop = _composeArea.y() + _composeArea.height() - st::historyAttach.height + st::historySendPadding + fakeMargin; auto fieldWidth = _composeArea.width() - st::historyAttach.width - st::historySendSize.width() - st::historySendRight - st::historyAttachEmoji.width - 2 * fakeMargin; auto fieldHeight = st::historySendSize.height() - 2 * st::historySendPadding - 2 * fakeMargin; auto field = QRect(fieldLeft, fieldTop, fieldWidth, fieldHeight); _p->fillRect(field, st::historyComposeField.bgColor[_palette]); _p->save(); _p->setClipRect(field); _p->setFont(st::historyComposeField.font); _p->setPen(st::historyComposeField.phColor[_palette]); auto phRect = QRect(field.x() + st::historyComposeField.textMrg.left() - fakeMargin + st::historyComposeField.phPos.x(), field.y() + st::historyComposeField.textMrg.top() - fakeMargin + st::historyComposeField.phPos.y(), field.width() - st::historyComposeField.textMrg.left() - st::historyComposeField.textMrg.right(), field.height() - st::historyComposeField.textMrg.top() - st::historyComposeField.textMrg.bottom()); _p->drawText(phRect, lang(lng_message_ph), QTextOption(st::historyComposeField.phAlign)); _p->restore(); _p->setClipping(false); } void Generator::paintDialogs() { _p->fillRect(_dialogs, st::dialogsBg[_palette]); st::dialogsMenuToggle.icon[_palette].paint(*_p, _dialogs.x() + st::dialogsFilterPadding.x() + st::dialogsMenuToggle.iconPosition.x(), _dialogs.y() + st::dialogsFilterPadding.y() + st::dialogsMenuToggle.iconPosition.y(), _rect.width()); auto filterLeft = _dialogs.x() + st::dialogsFilterPadding.x() + st::dialogsMenuToggle.width + st::dialogsFilterPadding.x(); auto filterRight = st::dialogsFilterSkip + st::dialogsFilterPadding.x(); auto filterWidth = _dialogs.x() + _dialogs.width() - filterLeft - filterRight; auto filterAreaHeight = st::dialogsFilterPadding.y() + st::dialogsMenuToggle.height + st::dialogsFilterPadding.y(); auto filterTop = _dialogs.y() + (filterAreaHeight - st::dialogsFilter.height) / 2; auto filter = QRect(filterLeft, filterTop, filterWidth, st::dialogsFilter.height); auto pen = st::dialogsFilter.borderColor[_palette]->p; pen.setWidth(st::dialogsFilter.borderWidth); _p->setPen(pen); _p->setBrush(st::dialogsFilter.bgColor[_palette]); { PainterHighQualityEnabler hq(*_p); _p->drawRoundedRect(QRectF(filter).marginsRemoved(QMarginsF(st::dialogsFilter.borderWidth / 2., st::dialogsFilter.borderWidth / 2., st::dialogsFilter.borderWidth / 2., st::dialogsFilter.borderWidth / 2.)), st::buttonRadius - (st::dialogsFilter.borderWidth / 2.), st::buttonRadius - (st::dialogsFilter.borderWidth / 2.)); } if (!st::dialogsFilter.icon.empty()) { st::dialogsFilter.icon[_palette].paint(*_p, filter.x(), filter.y(), _rect.width()); } _p->save(); _p->setClipRect(filter); auto phRect = QRect(filter.x() + st::dialogsFilter.textMrg.left() + st::dialogsFilter.phPos.x(), filter.y() + st::dialogsFilter.textMrg.top() + st::dialogsFilter.phPos.y(), filter.width() - st::dialogsFilter.textMrg.left() - st::dialogsFilter.textMrg.right(), filter.height() - st::dialogsFilter.textMrg.top() - st::dialogsFilter.textMrg.bottom());; _p->setFont(st::dialogsFilter.font); _p->setPen(st::dialogsFilter.phColor[_palette]); _p->drawText(phRect, lang(lng_dlg_filter), QTextOption(st::dialogsFilter.phAlign)); _p->restore(); _p->setClipping(false); paintDialogsList(); } void Generator::paintDialogsList() { _p->setClipRect(_dialogsList); _rowsTop = _dialogsList.y(); for (auto &row : _rows) { paintRow(row); _rowsTop += st::dialogsRowHeight; } _p->setClipping(false); } void Generator::paintRow(const Row &row) { auto x = _dialogsList.x(); auto y = _rowsTop; auto fullWidth = _dialogsList.width(); auto fullRect = QRect(x, y, fullWidth, st::dialogsRowHeight); if (row.active || row.selected) { _p->fillRect(fullRect, row.active ? st::dialogsBgActive[_palette] : st::dialogsBgOver[_palette]); } paintUserpic(x + st::dialogsPadding.x(), y + st::dialogsPadding.y(), row.type, row.peerIndex, row.letters); auto nameleft = x + st::dialogsPadding.x() + st::dialogsPhotoSize + st::dialogsPhotoPadding; auto namewidth = x + fullWidth - nameleft - st::dialogsPadding.x(); auto rectForName = QRect(nameleft, y + st::dialogsPadding.y() + st::dialogsNameTop, namewidth, st::msgNameFont->height); auto chatTypeIcon = ([&row]() -> const style::icon * { if (row.type == Row::Type::Group) { return &(row.active ? st::dialogsChatIconActive : (row.selected ? st::dialogsChatIconOver : st::dialogsChatIcon)); } else if (row.type == Row::Type::Channel) { return &(row.active ? st::dialogsChannelIconActive : (row.selected ? st::dialogsChannelIconOver : st::dialogsChannelIcon)); } return nullptr; })(); if (chatTypeIcon) { (*chatTypeIcon)[_palette].paint(*_p, rectForName.topLeft(), fullWidth); rectForName.setLeft(rectForName.left() + st::dialogsChatTypeSkip); } auto texttop = y + st::dialogsPadding.y() + st::msgNameFont->height + st::dialogsSkip; auto dateWidth = st::dialogsDateFont->width(row.date); rectForName.setWidth(rectForName.width() - dateWidth - st::dialogsDateSkip); _p->setFont(st::dialogsDateFont); _p->setPen(row.active ? st::dialogsDateFgActive[_palette] : (row.selected ? st::dialogsDateFgOver[_palette] : st::dialogsDateFg[_palette])); _p->drawText(rectForName.left() + rectForName.width() + st::dialogsDateSkip, rectForName.top() + st::msgNameFont->height - st::msgDateFont->descent, row.date); auto availableWidth = namewidth; if (row.unreadCounter) { auto counter = QString::number(row.unreadCounter); auto mutedCounter = row.muted; auto unreadRight = x + fullWidth - st::dialogsPadding.x(); auto unreadTop = texttop + st::dialogsTextFont->ascent - st::dialogsUnreadFont->ascent - (st::dialogsUnreadHeight - st::dialogsUnreadFont->height) / 2; auto unreadWidth = st::dialogsUnreadFont->width(counter); auto unreadRectWidth = unreadWidth + 2 * st::dialogsUnreadPadding; auto unreadRectHeight = st::dialogsUnreadHeight; accumulate_max(unreadRectWidth, unreadRectHeight); auto unreadRectLeft = unreadRight - unreadRectWidth; auto unreadRectTop = unreadTop; availableWidth -= unreadRectWidth + st::dialogsUnreadPadding; style::color bg[] = { st::dialogsUnreadBg, st::dialogsUnreadBgOver, st::dialogsUnreadBgActive, st::dialogsUnreadBgMuted, st::dialogsUnreadBgMutedOver, st::dialogsUnreadBgMutedActive }; auto index = (row.active ? 2 : row.selected ? 1 : 0) + (row.muted ? 3 : 0); _p->setPen(Qt::NoPen); _p->setBrush(bg[index][_palette]); _p->drawRoundedRect(QRectF(unreadRectLeft, unreadRectTop, unreadRectWidth, unreadRectHeight), unreadRectHeight / 2., unreadRectHeight / 2.); auto textTop = (unreadRectHeight - st::dialogsUnreadFont->height) / 2; _p->setFont(st::dialogsUnreadFont); _p->setPen(row.active ? st::dialogsUnreadFgActive[_palette] : (row.selected ? st::dialogsUnreadFgOver[_palette] : st::dialogsUnreadFg[_palette])); _p->drawText(unreadRectLeft + (unreadRectWidth - unreadWidth) / 2, unreadRectTop + textTop + st::dialogsUnreadFont->ascent, counter); } else if (row.pinned) { auto icon = (row.active ? st::dialogsPinnedIconActive[_palette] : (row.selected ? st::dialogsPinnedIconOver[_palette] : st::dialogsPinnedIcon[_palette])); icon.paint(*_p, x + fullWidth - st::dialogsPadding.x() - icon.width(), texttop, fullWidth); availableWidth -= icon.width() + st::dialogsUnreadPadding; } auto textRect = QRect(nameleft, texttop, availableWidth, st::dialogsTextFont->height); setTextPalette(row.active ? st::dialogsTextPaletteActive : (row.selected ? st::dialogsTextPaletteOver : st::dialogsTextPalette)); _p->setFont(st::dialogsTextFont); _p->setPen(row.active ? st::dialogsTextFgActive[_palette] : (row.selected ? st::dialogsTextFgOver[_palette] : st::dialogsTextFg[_palette])); row.text.drawElided(*_p, textRect.left(), textRect.top(), textRect.width(), textRect.height() / st::dialogsTextFont->height); restoreTextPalette(); auto sendStateIcon = ([&row]() -> const style::icon* { if (row.status == Status::Sent) { return &(row.active ? st::dialogsSentIconActive : (row.selected ? st::dialogsSentIconOver : st::dialogsSentIcon)); } else if (row.status == Status::Received) { return &(row.active ? st::dialogsReceivedIconActive : (row.selected ? st::dialogsReceivedIconOver : st::dialogsReceivedIcon)); } return nullptr; })(); if (sendStateIcon) { rectForName.setWidth(rectForName.width() - st::dialogsSendStateSkip); (*sendStateIcon)[_palette].paint(*_p, rectForName.topLeft() + QPoint(rectForName.width(), 0), fullWidth); } _p->setPen(row.active ? st::dialogsNameFgActive[_palette] : (row.selected ? st::dialogsNameFgOver[_palette] : st::dialogsNameFg[_palette])); row.name.drawElided(*_p, rectForName.left(), rectForName.top(), rectForName.width()); } void Generator::paintBubble(const Bubble &bubble) { auto height = bubble.height; if (!bubble.replyName.isEmpty()) { height += st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); } auto isPhoto = !bubble.photo.isNull(); auto x = _history.x(); auto y = _historyBottom - st::msgMargin.bottom() - height; auto bubbleTop = y; auto bubbleHeight = height; if (isPhoto) { bubbleTop -= st::historyMessageRadius + 1; bubbleHeight += st::historyMessageRadius + 1; } auto left = bubble.outbg ? st::msgMargin.right() : st::msgMargin.left(); if (bubble.outbg) { left += _history.width() - st::msgMargin.left() - st::msgMargin.right() - bubble.width; } x += left; _p->setPen(Qt::NoPen); auto tailclip = st::historyMessageRadius + 1; if (bubble.tail) { if (bubble.outbg) { _p->setClipRegion(QRegion(_history) - QRect(x + bubble.width - tailclip, bubbleTop + bubbleHeight - tailclip, tailclip + st::historyMessageRadius, tailclip + st::historyMessageRadius)); } else { _p->setClipRegion(QRegion(_history) - QRect(x - st::historyMessageRadius, bubbleTop + bubbleHeight - tailclip, tailclip + st::historyMessageRadius, tailclip + st::historyMessageRadius)); } } auto sh = bubble.outbg ? st::msgOutShadow[_palette] : st::msgInShadow[_palette]; _p->setBrush(sh); _p->drawRoundedRect(x, bubbleTop + st::msgShadow, bubble.width, bubbleHeight, st::historyMessageRadius, st::historyMessageRadius); auto bg = bubble.outbg ? st::msgOutBg[_palette] : st::msgInBg[_palette]; _p->setBrush(bg); _p->drawRoundedRect(x, bubbleTop, bubble.width, bubbleHeight, st::historyMessageRadius, st::historyMessageRadius); if (bubble.tail) { _p->setClipRect(_history); if (bubble.outbg) { _p->fillRect(QRect(x + bubble.width - tailclip, bubbleTop + bubbleHeight - tailclip, tailclip, tailclip), bg); _p->fillRect(QRect(x + bubble.width - tailclip, bubbleTop + bubbleHeight, tailclip + st::historyBubbleTailOutRight.width(), st::msgShadow), sh); st::historyBubbleTailOutRight[_palette].paint(*_p, x + bubble.width, bubbleTop + bubbleHeight - st::historyBubbleTailOutRight.height(), _rect.width()); } else { _p->fillRect(QRect(x, bubbleTop + bubbleHeight - tailclip, tailclip, tailclip), bg); _p->fillRect(QRect(x - st::historyBubbleTailInLeft.width(), bubbleTop + bubbleHeight, tailclip + st::historyBubbleTailInLeft.width(), st::msgShadow), sh); st::historyBubbleTailInLeft[_palette].paint(*_p, x - st::historyBubbleTailInLeft.width(), bubbleTop + bubbleHeight - st::historyBubbleTailOutRight.height(), _rect.width()); } } auto trect = QRect(x, y, bubble.width, bubble.height); if (isPhoto) { trect = trect.marginsRemoved(QMargins(st::msgPadding.left(), st::mediaCaptionSkip, st::msgPadding.right(), st::msgPadding.bottom())); } else { trect = trect.marginsRemoved(st::msgPadding); } if (!bubble.replyName.isEmpty()) { auto h = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); auto bar = (bubble.outbg ? st::msgOutReplyBarColor[_palette] : st::msgInReplyBarColor[_palette]); auto rbar = rtlrect(trect.x() + st::msgReplyBarPos.x(), trect.y() + st::msgReplyPadding.top() + st::msgReplyBarPos.y(), st::msgReplyBarSize.width(), st::msgReplyBarSize.height(), _rect.width()); _p->fillRect(rbar, bar); _p->setPen(bubble.outbg ? st::msgOutServiceFg[_palette] : st::msgInServiceFg[_palette]); bubble.replyName.drawLeftElided(*_p, trect.x() + st::msgReplyBarSkip, trect.y() + st::msgReplyPadding.top(), bubble.width - st::msgReplyBarSkip, _rect.width()); _p->setPen(bubble.outbg ? st::historyTextOutFg[_palette] : st::historyTextInFg[_palette]); bubble.replyText.drawLeftElided(*_p, trect.x() + st::msgReplyBarSkip, trect.y() + st::msgReplyPadding.top() + st::msgServiceNameFont->height, bubble.width - st::msgReplyBarSkip, _rect.width()); trect.setY(trect.y() + h); } if (!bubble.text.isEmpty()) { setTextPalette(bubble.outbg ? st::outTextPalette : st::inTextPalette); _p->setPen(bubble.outbg ? st::historyTextOutFg[_palette] : st::historyTextInFg[_palette]); _p->setFont(st::msgFont); bubble.text.draw(*_p, trect.x(), trect.y(), trect.width()); } else if (!bubble.waveform.isEmpty()) { auto nameleft = x + st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right(); auto nametop = y + st::msgFileNameTop; auto nameright = st::msgFilePadding.left(); auto statustop = y + st::msgFileStatusTop; auto bottom = y + st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom(); auto inner = rtlrect(x + st::msgFilePadding.left(), y + st::msgFilePadding.top(), st::msgFileSize, st::msgFileSize, _rect.width()); _p->setPen(Qt::NoPen); _p->setBrush(bubble.outbg ? st::msgFileOutBg[_palette] : st::msgFileInBg[_palette]); _p->drawEllipse(inner); auto icon = ([&bubble] { return &(bubble.outbg ? st::historyFileOutPlay : st::historyFileInPlay); })(); (*icon)[_palette].paintInCenter(*_p, inner); auto namewidth = x + bubble.width - nameleft - nameright; // rescale waveform by going in waveform.size * bar_count 1D grid auto active = bubble.outbg ? st::msgWaveformOutActive[_palette] : st::msgWaveformInActive[_palette]; auto inactive = bubble.outbg ? st::msgWaveformOutInactive[_palette] : st::msgWaveformInInactive[_palette]; int32 wf_size = bubble.waveform.size(), availw = namewidth + st::msgWaveformSkip; int32 bar_count = wf_size; int32 max_delta = st::msgWaveformMax - st::msgWaveformMin; auto wave_bottom = y + st::msgFilePadding.top() + st::msgWaveformMax; _p->setPen(Qt::NoPen); auto norm_value = uchar(31); for (auto i = 0, bar_x = 0; i != wf_size; ++i) { uchar value = bubble.waveform[i]; auto max_value = value; int32 bar_value = ((max_value * max_delta) + ((norm_value + 1) / 2)) / (norm_value + 1); if (i >= bubble.waveactive) { _p->fillRect(nameleft + bar_x, wave_bottom - bar_value, st::msgWaveformBar, st::msgWaveformMin + bar_value, inactive); } else { _p->fillRect(nameleft + bar_x, wave_bottom - bar_value, st::msgWaveformBar, st::msgWaveformMin + bar_value, active); } bar_x += st::msgWaveformBar + st::msgWaveformSkip; } auto status = bubble.outbg ? st::mediaOutFg[_palette] : st::mediaInFg[_palette]; _p->setFont(st::normalFont); _p->setPen(status); _p->drawTextLeft(nameleft, statustop, _rect.width(), bubble.wavestatus); } _p->setFont(st::msgDateFont); auto infoRight = x + bubble.width - st::msgPadding.right() + st::msgDateDelta.x(); auto infoBottom = y + height - st::msgPadding.bottom() + st::msgDateDelta.y(); _p->setPen(bubble.outbg ? st::msgOutDateFg[_palette] : st::msgInDateFg[_palette]); auto infoWidth = computeInfoWidth(bubble.status, bubble.date); auto dateX = infoRight - infoWidth; auto dateY = infoBottom - st::msgDateFont->height; _p->drawText(dateX, dateY + st::msgDateFont->ascent, bubble.date); auto icon = ([&bubble]() -> const style::icon * { if (bubble.status == Status::Sent) { return &st::historySentIcon; } else if (bubble.status == Status::Received) { return &st::historyReceivedIcon; } return nullptr; })(); if (icon) { (*icon)[_palette].paint(*_p, QPoint(infoRight, infoBottom) + st::historySendStatePosition, _rect.width()); } _historyBottom = y - (bubble.attached ? st::msgMarginTopAttached : st::msgMargin.top()); if (isPhoto) { auto image = bubble.photo.scaled(bubble.photoWidth * cIntRetinaFactor(), bubble.photoHeight * cIntRetinaFactor(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); image.setDevicePixelRatio(cRetinaFactor()); _p->drawImage(x, y - bubble.photoHeight, image); _historyBottom -= bubble.photoHeight; } } void Generator::paintService(QString text) { auto bubbleHeight = st::msgServicePadding.top() + st::msgServiceFont->height + st::msgServicePadding.bottom(); auto bubbleTop = _historyBottom - st::msgServiceMargin.bottom() - bubbleHeight; auto textWidth = st::msgServiceFont->width(text); auto bubbleWidth = st::msgServicePadding.left() + textWidth + st::msgServicePadding.right(); auto radius = bubbleHeight / 2; _p->setPen(Qt::NoPen); _p->setBrush(st::msgServiceBg[_palette]); auto bubbleLeft = _history.x() + (_history.width() - bubbleWidth) / 2; _p->drawRoundedRect(bubbleLeft, bubbleTop, bubbleWidth, bubbleHeight, radius, radius); _p->setPen(st::msgServiceFg[_palette]); _p->setFont(st::msgServiceFont); _p->drawText(bubbleLeft + st::msgServicePadding.left(), bubbleTop + st::msgServicePadding.top() + st::msgServiceFont->ascent, text); _historyBottom = bubbleTop - st::msgServiceMargin.top(); } void Generator::paintUserpic(int x, int y, Row::Type type, int index, QString letters) { style::color colors[] = { st::historyPeer1UserpicBg, st::historyPeer2UserpicBg, st::historyPeer3UserpicBg, st::historyPeer4UserpicBg, st::historyPeer5UserpicBg, st::historyPeer6UserpicBg, st::historyPeer7UserpicBg, st::historyPeer8UserpicBg, }; auto color = colors[index % base::array_size(colors)]; auto image = QImage(st::dialogsPhotoSize * cIntRetinaFactor(), st::dialogsPhotoSize * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); image.setDevicePixelRatio(cRetinaFactor()); image.fill(color[_palette]->c); { Painter p(&image); auto fontsize = (st::dialogsPhotoSize * 13) / 33; auto font = st::historyPeerUserpicFont->f; font.setPixelSize(fontsize); p.setFont(font); p.setBrush(Qt::NoBrush); p.setPen(st::historyPeerUserpicFg[_palette]); p.drawText(QRect(0, 0, st::dialogsPhotoSize, st::dialogsPhotoSize), letters, QTextOption(style::al_center)); } Images::prepareCircle(image); _p->drawImage(rtl() ? (_rect.width() - x - st::dialogsPhotoSize) : x, y, image); } void Generator::paintHistoryShadows() { _p->fillRect(_history.x() + st::lineWidth, _history.y(), _history.width() - st::lineWidth, st::lineWidth, st::shadowFg[_palette]); _p->fillRect(_history.x() + st::lineWidth, _history.y() + _history.height() - st::lineWidth, _history.width() - st::lineWidth, st::lineWidth, st::shadowFg[_palette]); _p->fillRect(_history.x(), _body.y(), st::lineWidth, _body.height(), st::shadowFg[_palette]); } void Generator::setTextPalette(const style::TextPalette &st) { _textPalette.linkFg = st.linkFg[_palette].clone(); _textPalette.monoFg = st.monoFg[_palette].clone(); _textPalette.selectBg = st.selectBg[_palette].clone(); _textPalette.selectFg = st.selectFg[_palette].clone(); _textPalette.selectLinkFg = st.selectLinkFg[_palette].clone(); _textPalette.selectMonoFg = st.selectMonoFg[_palette].clone(); _textPalette.selectOverlay = st.selectOverlay[_palette].clone(); _p->setTextPalette(_textPalette); } void Generator::restoreTextPalette() { _p->restoreTextPalette(); } } // namespace std::unique_ptr<Preview> GeneratePreview( const QString &filepath, CurrentData &&data) { auto result = std::make_unique<Preview>(); result->path = filepath; if (!LoadFromFile(filepath, &result->instance, &result->content)) { return nullptr; } result->preview = Generator( result->instance, std::move(data) ).generate(); return result; } int DefaultPreviewTitleHeight() { return st::titleHeight; } void DefaultPreviewWindowTitle(Painter &p, const style::palette &palette, QRect body, int outerWidth) { auto titleRect = QRect(body.x(), body.y() - st::titleHeight, body.width(), st::titleHeight); p.fillRect(titleRect, QColor(0, 0, 0)); p.fillRect(titleRect, st::titleBgActive[palette]); auto right = st::titleButtonClose.width; st::titleButtonClose.icon[palette].paint(p, titleRect.x() + titleRect.width() - right + st::titleButtonClose.iconPosition.x(), titleRect.y() + st::titleButtonClose.iconPosition.y(), outerWidth); right += st::titleButtonMaximize.width; st::titleButtonMaximize.icon[palette].paint(p, titleRect.x() + titleRect.width() - right + st::titleButtonMaximize.iconPosition.x(), titleRect.y() + st::titleButtonMaximize.iconPosition.y(), outerWidth); right += st::titleButtonMinimize.width; st::titleButtonMinimize.icon[palette].paint(p, titleRect.x() + titleRect.width() - right + st::titleButtonMinimize.iconPosition.x(), titleRect.y() + st::titleButtonMinimize.iconPosition.y(), outerWidth); p.fillRect(titleRect.x(), titleRect.y() + titleRect.height() - st::lineWidth, titleRect.width(), st::lineWidth, st::titleShadow[palette]); } void DefaultPreviewWindowFramePaint(QImage &preview, const style::palette &palette, QRect body, int outerWidth) { auto mask = QImage(st::windowShadow.size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); mask.setDevicePixelRatio(cRetinaFactor()); { Painter p(&mask); p.setCompositionMode(QPainter::CompositionMode_Source); st::windowShadow.paint(p, 0, 0, st::windowShadow.width(), QColor(0, 0, 0)); } auto maxSize = 0; auto currentInt = static_cast<uint32>(0); auto lastLineInts = reinterpret_cast<const uint32*>(mask.constBits() + (mask.height() - 1) * mask.bytesPerLine()); for (auto end = lastLineInts + mask.width(); lastLineInts != end; ++lastLineInts) { if (*lastLineInts < currentInt) { break; } currentInt = *lastLineInts; ++maxSize; } if (cRetina() && (maxSize % cIntRetinaFactor())) { maxSize -= (maxSize % cIntRetinaFactor()); } auto size = maxSize / cIntRetinaFactor(); auto bottom = size; auto left = size - st::windowShadowShift; auto right = left; auto top = size - 2 * st::windowShadowShift; auto sprite = st::windowShadow[palette]; auto topLeft = QImage(sprite.size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); topLeft.setDevicePixelRatio(cRetinaFactor()); { Painter p(&topLeft); p.setCompositionMode(QPainter::CompositionMode_Source); sprite.paint(p, 0, 0, sprite.width()); } auto width = sprite.width(); auto height = sprite.height(); auto topRight = topLeft.mirrored(true, false); auto bottomRight = topLeft.mirrored(true, true); auto bottomLeft = topLeft.mirrored(false, true); Painter p(&preview); DefaultPreviewWindowTitle(p, palette, body, outerWidth); auto inner = QRect(body.x(), body.y() - st::titleHeight, body.width(), body.height() + st::titleHeight); p.setClipRegion(QRegion(inner.marginsAdded(QMargins(size, size, size, size))) - inner); p.drawImage(inner.x() - left, inner.y() - top, topLeft); p.drawImage(inner.x() + inner.width() + right - width, inner.y() - top, topRight); p.drawImage(inner.x() + inner.width() + right - width, inner.y() + inner.height() + bottom - height, bottomRight); p.drawImage(inner.x() - left, inner.y() + inner.height() + bottom - height, bottomLeft); p.drawImage(QRect(inner.x() - left, inner.y() - top + height, left, top + inner.height() + bottom - 2 * height), topLeft, QRect(0, topLeft.height() - cIntRetinaFactor(), left * cIntRetinaFactor(), cIntRetinaFactor())); p.drawImage(QRect(inner.x() - left + width, inner.y() - top, left + inner.width() + right - 2 * width, top), topLeft, QRect(topLeft.width() - cIntRetinaFactor(), 0, cIntRetinaFactor(), top * cIntRetinaFactor())); p.drawImage(QRect(inner.x() + inner.width(), inner.y() - top + height, right, top + inner.height() + bottom - 2 * height), topRight, QRect(topRight.width() - right * cIntRetinaFactor(), topRight.height() - cIntRetinaFactor(), right * cIntRetinaFactor(), cIntRetinaFactor())); p.drawImage(QRect(inner.x() - left + width, inner.y() + inner.height(), left + inner.width() + right - 2 * width, bottom), bottomRight, QRect(0, bottomRight.height() - bottom * cIntRetinaFactor(), cIntRetinaFactor(), bottom * cIntRetinaFactor())); } } // namespace Theme } // namespace Window �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/window/themes/window_theme_preview.h���������������������������0000664�0000000�0000000�00000001325�13262451251�0026623�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "window/themes/window_theme.h" namespace Window { namespace Theme { struct CurrentData { int32 backgroundId = 0; QImage backgroundImage; bool backgroundTiled = false; }; std::unique_ptr<Preview> GeneratePreview( const QString &filepath, CurrentData &&data); int DefaultPreviewTitleHeight(); void DefaultPreviewWindowFramePaint( QImage &preview, const style::palette &palette, QRect body, int outerWidth); } // namespace Theme } // namespace Window �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/window/themes/window_theme_warning.cpp�������������������������0000664�0000000�0000000�00000007435�13262451251�0027152�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "window/themes/window_theme_warning.h" #include "styles/style_boxes.h" #include "ui/widgets/buttons.h" #include "ui/widgets/shadow.h" #include "window/themes/window_theme.h" #include "lang/lang_keys.h" namespace Window { namespace Theme { namespace { constexpr int kWaitBeforeRevertMs = 15999; } // namespace WarningWidget::WarningWidget(QWidget *parent) : TWidget(parent) , _secondsLeft(kWaitBeforeRevertMs / 1000) , _keepChanges(this, langFactory(lng_theme_keep_changes), st::defaultBoxButton) , _revert(this, langFactory(lng_theme_revert), st::defaultBoxButton) { _keepChanges->setClickedCallback([] { Window::Theme::KeepApplied(); }); _revert->setClickedCallback([] { Window::Theme::Revert(); }); _timer.setTimeoutHandler([this] { handleTimer(); }); updateText(); } void WarningWidget::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Escape) { Window::Theme::Revert(); } } void WarningWidget::paintEvent(QPaintEvent *e) { Painter p(this); if (!_cache.isNull()) { if (!_animation.animating(getms())) { if (isHidden()) { return; } } p.setOpacity(_animation.current(_hiding ? 0. : 1.)); p.drawPixmap(_outer.topLeft(), _cache); if (!_animation.animating()) { _cache = QPixmap(); showChildren(); _started = getms(true); _timer.start(100); } return; } Ui::Shadow::paint(p, _inner, width(), st::boxRoundShadow); App::roundRect(p, _inner, st::boxBg, BoxCorners); p.setFont(st::boxTitleFont); p.setPen(st::boxTitleFg); p.drawTextLeft(_inner.x() + st::boxTitlePosition.x(), _inner.y() + st::boxTitlePosition.y(), width(), lang(lng_theme_sure_keep)); p.setFont(st::boxTextFont); p.setPen(st::boxTextFg); p.drawTextLeft(_inner.x() + st::boxTitlePosition.x(), _inner.y() + st::themeWarningTextTop, width(), _text); } void WarningWidget::resizeEvent(QResizeEvent *e) { _inner = QRect((width() - st::themeWarningWidth) / 2, (height() - st::themeWarningHeight) / 2, st::themeWarningWidth, st::themeWarningHeight); _outer = _inner.marginsAdded(st::boxRoundShadow.extend); updateControlsGeometry(); update(); } void WarningWidget::updateControlsGeometry() { auto left = _inner.x() + _inner.width() - st::boxButtonPadding.right() - _keepChanges->width(); _keepChanges->moveToLeft(left, _inner.y() + _inner.height() - st::boxButtonPadding.bottom() - _keepChanges->height()); _revert->moveToLeft(left - st::boxButtonPadding.left() - _revert->width(), _keepChanges->y()); } void WarningWidget::refreshLang() { InvokeQueued(this, [this] { updateControlsGeometry(); }); } void WarningWidget::handleTimer() { auto msPassed = getms(true) - _started; setSecondsLeft((kWaitBeforeRevertMs - msPassed) / 1000); } void WarningWidget::setSecondsLeft(int secondsLeft) { if (secondsLeft <= 0) { Window::Theme::Revert(); } else { if (_secondsLeft != secondsLeft) { _secondsLeft = secondsLeft; updateText(); update(); } _timer.start(100); } } void WarningWidget::updateText() { _text = lng_theme_reverting(lt_count, _secondsLeft); } void WarningWidget::showAnimated() { startAnimation(false); show(); setFocus(); } void WarningWidget::hideAnimated() { startAnimation(true); } void WarningWidget::startAnimation(bool hiding) { _timer.stop(); _hiding = hiding; if (_cache.isNull()) { showChildren(); Ui::SendPendingMoveResizeEvents(this); _cache = Ui::GrabWidget(this, _outer); } hideChildren(); _animation.start([this] { update(); if (_hiding) { hide(); if (_hiddenCallback) { _hiddenCallback(); } } }, _hiding ? 1. : 0., _hiding ? 0. : 1., st::boxDuration); } } // namespace Theme } // namespace Window �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/window/themes/window_theme_warning.h���������������������������0000664�0000000�0000000�00000002322�13262451251�0026605�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace Ui { class RoundButton; } // namespace Ui namespace Window { namespace Theme { class WarningWidget : public TWidget { public: WarningWidget(QWidget *parent); void setHiddenCallback(base::lambda<void()> callback) { _hiddenCallback = std::move(callback); } void showAnimated(); void hideAnimated(); protected: void keyPressEvent(QKeyEvent *e) override; void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; private: void refreshLang(); void updateControlsGeometry(); void setSecondsLeft(int secondsLeft); void startAnimation(bool hiding); void updateText(); void handleTimer(); bool _hiding = false; Animation _animation; QPixmap _cache; QRect _inner, _outer; SingleTimer _timer; TimeMs _started = 0; int _secondsLeft = 0; QString _text; object_ptr<Ui::RoundButton> _keepChanges; object_ptr<Ui::RoundButton> _revert; base::lambda<void()> _hiddenCallback; }; } // namespace Theme } // namespace Window ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/window/window.style��������������������������������������������0000664�0000000�0000000�00000016523�13262451251�0023332�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ using "basic.style"; using "ui/widgets/widgets.style"; using "history/history.style"; windowMinWidth: 380px; windowMinHeight: 480px; windowDefaultWidth: 800px; windowDefaultHeight: 600px; windowShadow: icon {{ "window_shadow", windowShadowFg }}; windowShadowShift: 1px; columnMinimalWidthLeft: 260px; columnMaximalWidthLeft: 540px; columnMinimalWidthMain: 380px; columnDesiredWidthMain: 512px; columnMinimalWidthThird: 292px; columnMaximalWidthThird: 392px; adaptiveChatWideWidth: 880px; notifyBorder: windowShadowFgFallback; notifyBorderWidth: 1px; notifySlowHide: 4000; notifyPhotoSize: 62px; notifyMacPhotoSize: 64px; notifyPhotoPos: point(9px, 9px); notifyClosePos: point(1px, 2px); notifyClose: IconButton { width: 30px; height: 30px; icon: smallCloseIcon; iconOver: smallCloseIconOver; iconPosition: point(10px, 10px); rippleAreaPosition: point(5px, 5px); rippleAreaSize: 20px; ripple: RippleAnimation(defaultRippleAnimation) { color: windowBgOver; } } notifyItemTop: 12px; notifyTextLeft: 12px; notifyTextTop: 7px; notifyWaitLongHide: 3000; notifyFastAnim: 150; notifyWidth: 320px; notifyMinHeight: 80px; notifyDeltaX: 6px; notifyDeltaY: 7px; notifyActionsDuration: 200; notifyHideAllHeight: 36px; notifyReplyArea: InputField(defaultInputField) { font: normalFont; textMargins: margins(8px, 8px, 8px, 6px); heightMin: 36px; heightMax: 72px; placeholderFg: placeholderFg; placeholderFgActive: placeholderFgActive; placeholderFgError: placeholderFgActive; placeholderMargins: margins(2px, 0px, 2px, 0px); placeholderScale: 0.; placeholderFont: normalFont; border: 0px; borderActive: 0px; } notifySendReply: IconButton { width: 36px; height: 36px; icon: historySendIcon; iconOver: historySendIconOver; iconPosition: point(6px, 6px); } titleUnreadCounterTop: 5px; titleUnreadCounterRight: 35px; mainMenuWidth: 274px; mainMenuCoverHeight: 134px; mainMenuUserpicLeft: 24px; mainMenuUserpicTop: 20px; mainMenuUserpic: UserpicButton(defaultUserpicButton) { size: size(48px, 48px); photoSize: 48px; } mainMenuCloudButton: IconButton { width: 64px; height: 64px; icon: icon { // { "menu_cloud", mainMenuCloudFg }, }; iconPosition: point(22px, 22px); } mainMenuCloudSize: 32px; mainMenuCoverTextLeft: 30px; mainMenuCoverNameTop: 84px; mainMenuCoverStatusTop: 102px; mainMenuSkip: 13px; mainMenu: Menu(defaultMenu) { itemFg: windowBoldFg; itemFgOver: windowBoldFgOver; itemFont: semiboldFont; itemIconPosition: point(28px, 10px); itemPadding: margins(76px, 13px, 28px, 13px); itemToggle: Toggle(defaultMenuToggle) { toggledFg: mainMenuCoverBg; } itemToggleOver: Toggle(defaultMenuToggleOver) { toggledFg: mainMenuCoverBg; } itemToggleShift: 11px; } mainMenuNewGroup: icon {{ "menu_new_group", menuIconFg }}; mainMenuNewGroupOver: icon {{ "menu_new_group", menuIconFgOver }}; mainMenuNewChannel: icon {{ "menu_new_channel", menuIconFg }}; mainMenuNewChannelOver: icon {{ "menu_new_channel", menuIconFgOver }}; mainMenuContacts: icon {{ "menu_contacts", menuIconFg }}; mainMenuContactsOver: icon {{ "menu_contacts", menuIconFgOver }}; mainMenuCalls: icon {{ "menu_calls", menuIconFg }}; mainMenuCallsOver: icon {{ "menu_calls", menuIconFgOver }}; mainMenuSettings: icon {{ "menu_settings", menuIconFg }}; mainMenuSettingsOver: icon {{ "menu_settings", menuIconFgOver }}; mainMenuNightMode: icon {{ "menu_night_mode", menuIconFg }}; mainMenuNightModeOver: icon {{ "menu_night_mode", menuIconFgOver }}; mainMenuFooterLeft: 30px; mainMenuTelegramLabel: FlatLabel(defaultFlatLabel) { align: align(left); textFg: windowSubTextFg; style: TextStyle(defaultTextStyle) { font: semiboldFont; linkFont: semiboldFont; linkFontOver: font(fsize semibold underline); } palette: TextPalette(defaultTextPalette) { linkFg: windowSubTextFg; } } mainMenuTelegramBottom: 43px; mainMenuVersionLabel: FlatLabel(mainMenuTelegramLabel) { style: defaultTextStyle; } mainMenuVersionBottom: 21px; // Windows specific title titleHeight: 21px; titleButtonMinimize: IconButton { width: 24px; height: 21px; icon: icon { { size(24px, 21px), titleButtonBg }, { "title_button_minimize", titleButtonFg, point(4px, 4px) }, }; iconOver: icon { { size(24px, 21px), titleButtonBgOver }, { "title_button_minimize", titleButtonFgOver, point(4px, 4px) }, }; iconPosition: point(0px, 0px); } titleButtonMinimizeIconActive: icon { { size(24px, 21px), titleButtonBgActive }, { "title_button_minimize", titleButtonFgActive, point(4px, 4px) }, }; titleButtonMinimizeIconActiveOver: icon { { size(24px, 21px), titleButtonBgActiveOver }, { "title_button_minimize", titleButtonFgActiveOver, point(4px, 4px) }, }; titleButtonMaximize: IconButton(titleButtonMinimize) { icon: icon { { size(24px, 21px), titleButtonBg }, { "title_button_maximize", titleButtonFg, point(4px, 4px) }, }; iconOver: icon { { size(24px, 21px), titleButtonBgOver }, { "title_button_maximize", titleButtonFgOver, point(4px, 4px) }, }; } titleButtonMaximizeIconActive: icon { { size(24px, 21px), titleButtonBgActive }, { "title_button_maximize", titleButtonFgActive, point(4px, 4px) }, }; titleButtonMaximizeIconActiveOver: icon { { size(24px, 21px), titleButtonBgActiveOver }, { "title_button_maximize", titleButtonFgActiveOver, point(4px, 4px) }, }; titleButtonRestoreIcon: icon { { size(24px, 21px), titleButtonBg }, { "title_button_restore", titleButtonFg, point(4px, 4px) }, }; titleButtonRestoreIconOver: icon { { size(24px, 21px), titleButtonBgOver }, { "title_button_restore", titleButtonFgOver, point(4px, 4px) }, }; titleButtonRestoreIconActive: icon { { size(24px, 21px), titleButtonBgActive }, { "title_button_restore", titleButtonFgActive, point(4px, 4px) }, }; titleButtonRestoreIconActiveOver: icon { { size(24px, 21px), titleButtonBgActiveOver }, { "title_button_restore", titleButtonFgActiveOver, point(4px, 4px) }, }; titleButtonClose: IconButton(titleButtonMinimize) { width: 25px; icon: icon { { size(25px, 21px), titleButtonCloseBg }, { "title_button_close", titleButtonCloseFg, point(5px, 4px) }, }; iconOver: icon { { size(25px, 21px), titleButtonCloseBgOver }, { "title_button_close", titleButtonCloseFgOver, point(5px, 4px) }, }; } titleButtonCloseIconActive: icon { { size(25px, 21px), titleButtonCloseBgActive }, { "title_button_close", titleButtonCloseFgActive, point(5px, 4px) }, }; titleButtonCloseIconActiveOver: icon { { size(25px, 21px), titleButtonCloseBgActiveOver }, { "title_button_close", titleButtonCloseFgActiveOver, point(5px, 4px) }, }; themeEditorSampleSize: size(90px, 51px); themeEditorMargin: margins(17px, 10px, 17px, 10px); themeEditorDescriptionSkip: 10px; themeEditorNameFont: font(15px semibold); themeEditorCopyNameFont: font(fsize semibold); windowEmojiSuggestionsPopup: PopupMenu(defaultPopupMenu) { menu: Menu(defaultMenu) { itemPadding: margins(48px, 8px, 17px, 7px); widthMax: 512px; } } // Mac specific macAccessoryWidth: 450.; macAccessoryHeight: 90.; macEnableFilterAdd: 2; macEnableFilterTop: 5; macSelectorTop: 6; macAlwaysThisAppTop: 4; macAppHintTop: 8; macCautionIconSize: 16; macWindowRoundRadius: 5; macWindowShadowTopLeft: icon {{ "mac_window_shadow_top_left", windowShadowFg }}; macTrayIcon: icon {{ "mac_tray_icon", windowFg }}; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/window/window_controller.cpp�����������������������������������0000664�0000000�0000000�00000035736�13262451251�0025226�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "window/window_controller.h" #include "window/main_window.h" #include "info/info_memento.h" #include "history/history.h" #include "history/history_item.h" #include "history/view/history_view_element.h" #include "media/player/media_player_round_controller.h" #include "data/data_session.h" #include "data/data_feed.h" #include "boxes/calendar_box.h" #include "mainwidget.h" #include "mainwindow.h" #include "auth_session.h" #include "apiwrap.h" #include "styles/style_window.h" #include "styles/style_dialogs.h" namespace Window { DateClickHandler::DateClickHandler(Dialogs::Key chat, QDate date) : _chat(chat) , _date(date) { } void DateClickHandler::setDate(QDate date) { _date = date; } void DateClickHandler::onClick(Qt::MouseButton) const { App::wnd()->controller()->showJumpToDate(_chat, _date); } Controller::Controller(not_null<MainWindow*> window) : _window(window) { Auth().data().animationPlayInlineRequest( ) | rpl::start_with_next([this](auto item) { if (const auto video = roundVideo(item)) { video->pauseResume(); } else { startRoundVideo(item); } }, lifetime()); } void Controller::setActiveChatEntry(Dialogs::RowDescriptor row) { _activeChatEntry = row; } void Controller::setActiveChatEntry(Dialogs::Key key) { setActiveChatEntry({ key, FullMsgId() }); } Dialogs::RowDescriptor Controller::activeChatEntryCurrent() const { return _activeChatEntry.current(); } Dialogs::Key Controller::activeChatCurrent() const { return activeChatEntryCurrent().key; } auto Controller::activeChatEntryChanges() const -> rpl::producer<Dialogs::RowDescriptor> { return _activeChatEntry.changes(); } rpl::producer<Dialogs::Key> Controller::activeChatChanges() const { return activeChatEntryChanges( ) | rpl::map([](const Dialogs::RowDescriptor &value) { return value.key; }) | rpl::distinct_until_changed(); } auto Controller::activeChatEntryValue() const -> rpl::producer<Dialogs::RowDescriptor> { return _activeChatEntry.value(); } rpl::producer<Dialogs::Key> Controller::activeChatValue() const { return activeChatEntryValue( ) | rpl::map([](const Dialogs::RowDescriptor &value) { return value.key; }) | rpl::distinct_until_changed(); } void Controller::enableGifPauseReason(GifPauseReason reason) { if (!(_gifPauseReasons & reason)) { auto notify = (static_cast<int>(_gifPauseReasons) < static_cast<int>(reason)); _gifPauseReasons |= reason; if (notify) { _gifPauseLevelChanged.notify(); } } } void Controller::disableGifPauseReason(GifPauseReason reason) { if (_gifPauseReasons & reason) { _gifPauseReasons &= ~reason; if (_gifPauseReasons < reason) { _gifPauseLevelChanged.notify(); } } } bool Controller::isGifPausedAtLeastFor(GifPauseReason reason) const { if (reason == GifPauseReason::Any) { return (_gifPauseReasons != 0) || !window()->isActive(); } return (static_cast<int>(_gifPauseReasons) >= 2 * static_cast<int>(reason)) || !window()->isActive(); } int Controller::dialogsSmallColumnWidth() const { return st::dialogsPadding.x() + st::dialogsPhotoSize + st::dialogsPadding.x(); } int Controller::minimalThreeColumnWidth() const { return st::columnMinimalWidthLeft + st::columnMinimalWidthMain + st::columnMinimalWidthThird; } bool Controller::forceWideDialogs() const { if (dialogsListDisplayForced().value()) { return true; } else if (dialogsListFocused().value()) { return true; } return !App::main()->isMainSectionShown(); } Controller::ColumnLayout Controller::computeColumnLayout() const { auto layout = Adaptive::WindowLayout::OneColumn; auto bodyWidth = window()->bodyWidget()->width(); auto dialogsWidth = 0, chatWidth = 0, thirdWidth = 0; auto useOneColumnLayout = [&] { auto minimalNormal = st::columnMinimalWidthLeft + st::columnMinimalWidthMain; if (bodyWidth < minimalNormal) { return true; } return false; }; auto useNormalLayout = [&] { // Used if useSmallColumnLayout() == false. if (bodyWidth < minimalThreeColumnWidth()) { return true; } if (!Auth().settings().tabbedSelectorSectionEnabled() && !Auth().settings().thirdSectionInfoEnabled()) { return true; } return false; }; if (useOneColumnLayout()) { dialogsWidth = chatWidth = bodyWidth; } else if (useNormalLayout()) { layout = Adaptive::WindowLayout::Normal; dialogsWidth = countDialogsWidthFromRatio(bodyWidth); accumulate_min(dialogsWidth, bodyWidth - st::columnMinimalWidthMain); chatWidth = bodyWidth - dialogsWidth; } else { layout = Adaptive::WindowLayout::ThreeColumn; dialogsWidth = countDialogsWidthFromRatio(bodyWidth); thirdWidth = countThirdColumnWidthFromRatio(bodyWidth); auto shrink = shrinkDialogsAndThirdColumns( dialogsWidth, thirdWidth, bodyWidth); dialogsWidth = shrink.dialogsWidth; thirdWidth = shrink.thirdWidth; chatWidth = bodyWidth - dialogsWidth - thirdWidth; } return { bodyWidth, dialogsWidth, chatWidth, thirdWidth, layout }; } int Controller::countDialogsWidthFromRatio(int bodyWidth) const { auto result = qRound(bodyWidth * Auth().settings().dialogsWidthRatio()); accumulate_max(result, st::columnMinimalWidthLeft); // accumulate_min(result, st::columnMaximalWidthLeft); return result; } int Controller::countThirdColumnWidthFromRatio(int bodyWidth) const { auto result = Auth().settings().thirdColumnWidth(); accumulate_max(result, st::columnMinimalWidthThird); accumulate_min(result, st::columnMaximalWidthThird); return result; } Controller::ShrinkResult Controller::shrinkDialogsAndThirdColumns( int dialogsWidth, int thirdWidth, int bodyWidth) const { auto chatWidth = st::columnMinimalWidthMain; if (dialogsWidth + thirdWidth + chatWidth <= bodyWidth) { return { dialogsWidth, thirdWidth }; } auto thirdWidthNew = ((bodyWidth - chatWidth) * thirdWidth) / (dialogsWidth + thirdWidth); auto dialogsWidthNew = ((bodyWidth - chatWidth) * dialogsWidth) / (dialogsWidth + thirdWidth); if (thirdWidthNew < st::columnMinimalWidthThird) { thirdWidthNew = st::columnMinimalWidthThird; dialogsWidthNew = bodyWidth - thirdWidthNew - chatWidth; Assert(dialogsWidthNew >= st::columnMinimalWidthLeft); } else if (dialogsWidthNew < st::columnMinimalWidthLeft) { dialogsWidthNew = st::columnMinimalWidthLeft; thirdWidthNew = bodyWidth - dialogsWidthNew - chatWidth; Assert(thirdWidthNew >= st::columnMinimalWidthThird); } return { dialogsWidthNew, thirdWidthNew }; } bool Controller::canShowThirdSection() const { auto currentLayout = computeColumnLayout(); auto minimalExtendBy = minimalThreeColumnWidth() - currentLayout.bodyWidth; return (minimalExtendBy <= window()->maximalExtendBy()); } bool Controller::canShowThirdSectionWithoutResize() const { auto currentWidth = computeColumnLayout().bodyWidth; return currentWidth >= minimalThreeColumnWidth(); } bool Controller::takeThirdSectionFromLayer() { return App::wnd()->takeThirdSectionFromLayer(); } void Controller::resizeForThirdSection() { if (Adaptive::ThreeColumn()) { return; } auto layout = computeColumnLayout(); auto tabbedSelectorSectionEnabled = Auth().settings().tabbedSelectorSectionEnabled(); auto thirdSectionInfoEnabled = Auth().settings().thirdSectionInfoEnabled(); Auth().settings().setTabbedSelectorSectionEnabled(false); Auth().settings().setThirdSectionInfoEnabled(false); auto wanted = countThirdColumnWidthFromRatio(layout.bodyWidth); auto minimal = st::columnMinimalWidthThird; auto extendBy = wanted; auto extendedBy = [&] { // Best - extend by third column without moving the window. // Next - extend by minimal third column without moving. // Next - show third column inside the window without moving. // Last - extend with moving. if (window()->canExtendNoMove(wanted)) { return window()->tryToExtendWidthBy(wanted); } else if (window()->canExtendNoMove(minimal)) { extendBy = minimal; return window()->tryToExtendWidthBy(minimal); } else if (layout.bodyWidth >= minimalThreeColumnWidth()) { return 0; } return window()->tryToExtendWidthBy(minimal); }(); if (extendedBy) { if (extendBy != Auth().settings().thirdColumnWidth()) { Auth().settings().setThirdColumnWidth(extendBy); } auto newBodyWidth = layout.bodyWidth + extendedBy; auto currentRatio = Auth().settings().dialogsWidthRatio(); Auth().settings().setDialogsWidthRatio( (currentRatio * layout.bodyWidth) / newBodyWidth); } auto savedValue = (extendedBy == extendBy) ? -1 : extendedBy; Auth().settings().setThirdSectionExtendedBy(savedValue); Auth().settings().setTabbedSelectorSectionEnabled( tabbedSelectorSectionEnabled); Auth().settings().setThirdSectionInfoEnabled( thirdSectionInfoEnabled); } void Controller::closeThirdSection() { auto newWindowSize = window()->size(); auto layout = computeColumnLayout(); if (layout.windowLayout == Adaptive::WindowLayout::ThreeColumn) { auto noResize = window()->isFullScreen() || window()->isMaximized(); auto savedValue = Auth().settings().thirdSectionExtendedBy(); auto extendedBy = (savedValue == -1) ? layout.thirdWidth : savedValue; auto newBodyWidth = noResize ? layout.bodyWidth : (layout.bodyWidth - extendedBy); auto currentRatio = Auth().settings().dialogsWidthRatio(); Auth().settings().setDialogsWidthRatio( (currentRatio * layout.bodyWidth) / newBodyWidth); newWindowSize = QSize( window()->width() + (newBodyWidth - layout.bodyWidth), window()->height()); } Auth().settings().setTabbedSelectorSectionEnabled(false); Auth().settings().setThirdSectionInfoEnabled(false); Auth().saveSettingsDelayed(); if (window()->size() != newWindowSize) { window()->resize(newWindowSize); } else { updateColumnLayout(); } } void Controller::showJumpToDate(Dialogs::Key chat, QDate requestedDate) { const auto currentPeerDate = [&] { if (const auto history = chat.history()) { if (history->scrollTopItem) { return history->scrollTopItem->dateTime().date(); } else if (history->loadedAtTop() && !history->isEmpty() && history->peer->migrateFrom()) { if (const auto migrated = App::historyLoaded(history->peer->migrateFrom())) { if (migrated->scrollTopItem) { // We're up in the migrated history. // So current date is the date of first message here. return history->blocks.front()->messages.front()->dateTime().date(); } } } else if (!history->chatsListDate().isNull()) { return history->chatsListDate().date(); } } else if (const auto feed = chat.feed()) { /*if (chatScrollPosition(feed)) { // #TODO feeds save position } else */if (!feed->chatsListDate().isNull()) { return feed->chatsListDate().date(); } } return QDate::currentDate(); }; const auto maxPeerDate = [](Dialogs::Key chat) { if (auto history = chat.history()) { if (const auto channel = history->peer->migrateTo()) { history = App::historyLoaded(channel); } if (history && !history->chatsListDate().isNull()) { return history->chatsListDate().date(); } } else if (const auto feed = chat.feed()) { if (!feed->chatsListDate().isNull()) { return feed->chatsListDate().date(); } } return QDate::currentDate(); }; const auto minPeerDate = [](Dialogs::Key chat) { const auto startDate = [] { // Telegram was launched in August 2013 :) return QDate(2013, 8, 1); }; if (const auto history = chat.history()) { if (const auto chat = history->peer->migrateFrom()) { if (const auto history = App::historyLoaded(chat)) { if (history->loadedAtTop()) { if (!history->isEmpty()) { return history->blocks.front()->messages.front()->dateTime().date(); } } else { return startDate(); } } } if (history->loadedAtTop()) { if (!history->isEmpty()) { return history->blocks.front()->messages.front()->dateTime().date(); } return QDate::currentDate(); } } else if (const auto feed = chat.feed()) { return startDate(); } return startDate(); }; const auto highlighted = requestedDate.isNull() ? currentPeerDate() : requestedDate; const auto month = highlighted; auto callback = [=](const QDate &date) { Auth().api().jumpToDate(chat, date); }; auto box = Box<CalendarBox>( month, highlighted, std::move(callback)); box->setMinDate(minPeerDate(chat)); box->setMaxDate(maxPeerDate(chat)); Ui::show(std::move(box)); } void Controller::updateColumnLayout() { App::main()->updateColumnLayout(); } void Controller::showPeerHistory( PeerId peerId, const SectionShow ¶ms, MsgId msgId) { App::main()->ui_showPeerHistory( peerId, params, msgId); } void Controller::showPeerHistory( not_null<PeerData*> peer, const SectionShow ¶ms, MsgId msgId) { showPeerHistory( peer->id, params, msgId); } void Controller::showPeerHistory( not_null<History*> history, const SectionShow ¶ms, MsgId msgId) { showPeerHistory( history->peer->id, params, msgId); } void Navigation::showPeerInfo( PeerId peerId, const SectionShow ¶ms) { //if (Adaptive::ThreeColumn() // && !Auth().settings().thirdSectionInfoEnabled()) { // Auth().settings().setThirdSectionInfoEnabled(true); // Auth().saveSettingsDelayed(); //} showSection(Info::Memento(peerId), params); } void Navigation::showPeerInfo( not_null<PeerData*> peer, const SectionShow ¶ms) { showPeerInfo(peer->id, params); } void Navigation::showPeerInfo( not_null<History*> history, const SectionShow ¶ms) { showPeerInfo(history->peer->id, params); } void Controller::showSection( SectionMemento &&memento, const SectionShow ¶ms) { if (App::wnd()->showSectionInExistingLayer( &memento, params) && !params.thirdColumn) { return; } App::main()->showSection(std::move(memento), params); } void Controller::showBackFromStack(const SectionShow ¶ms) { chats()->showBackFromStack(params); } void Controller::showSpecialLayer( object_ptr<LayerWidget> &&layer, anim::type animated) { App::wnd()->showSpecialLayer(std::move(layer), animated); } not_null<MainWidget*> Controller::chats() const { return App::wnd()->chatsWidget(); } bool Controller::startRoundVideo(not_null<HistoryItem*> context) { if (auto video = RoundController::TryStart(this, context)) { enableGifPauseReason(Window::GifPauseReason::RoundPlaying); _roundVideo = std::move(video); return true; } return false; } auto Controller::currentRoundVideo() const -> RoundController* { return _roundVideo.get(); } auto Controller::roundVideo(not_null<const HistoryItem*> context) const -> RoundController* { return roundVideo(context->fullId()); } auto Controller::roundVideo(FullMsgId contextId) const -> RoundController* { if (const auto result = currentRoundVideo()) { if (result->contextId() == contextId) { return result; } } return nullptr; } void Controller::roundVideoFinished(not_null<RoundController*> video) { if (video == _roundVideo.get()) { _roundVideo = nullptr; disableGifPauseReason(Window::GifPauseReason::RoundPlaying); } } Controller::~Controller() = default; } // namespace Window ����������������������������������tdesktop-1.2.17/Telegram/SourceFiles/window/window_controller.h�������������������������������������0000664�0000000�0000000�00000015233�13262451251�0024661�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <rpl/variable.h> #include "base/flags.h" #include "dialogs/dialogs_key.h" class MainWidget; class HistoryMessage; class HistoryService; namespace Media { namespace Player { class RoundController; } // namespace Player } // namespace Media namespace Window { class LayerWidget; class MainWindow; class SectionMemento; enum class GifPauseReason { Any = 0, InlineResults = (1 << 0), SavedGifs = (1 << 1), Layer = (1 << 2), RoundPlaying = (1 << 3), MediaPreview = (1 << 4), }; using GifPauseReasons = base::flags<GifPauseReason>; inline constexpr bool is_flag_type(GifPauseReason) { return true; }; class DateClickHandler : public ClickHandler { public: DateClickHandler(Dialogs::Key chat, QDate date); void setDate(QDate date); void onClick(Qt::MouseButton) const override; private: Dialogs::Key _chat; QDate _date; }; struct SectionShow { enum class Way { Forward, Backward, ClearStack, }; SectionShow( Way way = Way::Forward, anim::type animated = anim::type::normal, anim::activation activation = anim::activation::normal) : way(way) , animated(animated) , activation(activation) { } SectionShow( anim::type animated, anim::activation activation = anim::activation::normal) : animated(animated) , activation(activation) { } SectionShow withWay(Way newWay) const { return SectionShow(newWay, animated, activation); } SectionShow withThirdColumn() const { auto copy = *this; copy.thirdColumn = true; return copy; } Way way = Way::Forward; anim::type animated = anim::type::normal; anim::activation activation = anim::activation::normal; bool thirdColumn = false; }; class Controller; class Navigation { public: virtual void showSection( SectionMemento &&memento, const SectionShow ¶ms = SectionShow()) = 0; virtual void showBackFromStack( const SectionShow ¶ms = SectionShow()) = 0; virtual not_null<Controller*> parentController() = 0; void showPeerInfo( PeerId peerId, const SectionShow ¶ms = SectionShow()); void showPeerInfo( not_null<PeerData*> peer, const SectionShow ¶ms = SectionShow()); void showPeerInfo( not_null<History*> history, const SectionShow ¶ms = SectionShow()); virtual ~Navigation() = default; }; class Controller : public Navigation { public: Controller(not_null<MainWindow*> window); not_null<MainWindow*> window() const { return _window; } // This is needed for History TopBar updating when searchInChat // is changed in the DialogsWidget of the current window. rpl::variable<Dialogs::Key> searchInChat; void setActiveChatEntry(Dialogs::RowDescriptor row); void setActiveChatEntry(Dialogs::Key key); Dialogs::RowDescriptor activeChatEntryCurrent() const; Dialogs::Key activeChatCurrent() const; rpl::producer<Dialogs::RowDescriptor> activeChatEntryChanges() const; rpl::producer<Dialogs::Key> activeChatChanges() const; rpl::producer<Dialogs::RowDescriptor> activeChatEntryValue() const; rpl::producer<Dialogs::Key> activeChatValue() const; void enableGifPauseReason(GifPauseReason reason); void disableGifPauseReason(GifPauseReason reason); base::Observable<void> &gifPauseLevelChanged() { return _gifPauseLevelChanged; } bool isGifPausedAtLeastFor(GifPauseReason reason) const; base::Observable<void> &floatPlayerAreaUpdated() { return _floatPlayerAreaUpdated; } struct ColumnLayout { int bodyWidth; int dialogsWidth; int chatWidth; int thirdWidth; Adaptive::WindowLayout windowLayout; }; ColumnLayout computeColumnLayout() const; int dialogsSmallColumnWidth() const; bool forceWideDialogs() const; void updateColumnLayout(); bool canShowThirdSection() const; bool canShowThirdSectionWithoutResize() const; bool takeThirdSectionFromLayer(); void resizeForThirdSection(); void closeThirdSection(); void showSection( SectionMemento &&memento, const SectionShow ¶ms = SectionShow()) override; void showBackFromStack( const SectionShow ¶ms = SectionShow()) override; void showPeerHistory( PeerId peerId, const SectionShow ¶ms = SectionShow::Way::ClearStack, MsgId msgId = ShowAtUnreadMsgId); void showPeerHistory( not_null<PeerData*> peer, const SectionShow ¶ms = SectionShow::Way::ClearStack, MsgId msgId = ShowAtUnreadMsgId); void showPeerHistory( not_null<History*> history, const SectionShow ¶ms = SectionShow::Way::ClearStack, MsgId msgId = ShowAtUnreadMsgId); void clearSectionStack( const SectionShow ¶ms = SectionShow::Way::ClearStack) { showPeerHistory( PeerId(0), params, ShowAtUnreadMsgId); } void showSpecialLayer( object_ptr<LayerWidget> &&layer, anim::type animated = anim::type::normal); void hideSpecialLayer( anim::type animated = anim::type::normal) { showSpecialLayer(nullptr, animated); } void showJumpToDate( Dialogs::Key chat, QDate requestedDate); base::Variable<bool> &dialogsListFocused() { return _dialogsListFocused; } const base::Variable<bool> &dialogsListFocused() const { return _dialogsListFocused; } base::Variable<bool> &dialogsListDisplayForced() { return _dialogsListDisplayForced; } const base::Variable<bool> &dialogsListDisplayForced() const { return _dialogsListDisplayForced; } not_null<Controller*> parentController() override { return this; } using RoundController = Media::Player::RoundController; bool startRoundVideo(not_null<HistoryItem*> context); RoundController *currentRoundVideo() const; RoundController *roundVideo(not_null<const HistoryItem*> context) const; RoundController *roundVideo(FullMsgId contextId) const; void roundVideoFinished(not_null<RoundController*> video); rpl::lifetime &lifetime() { return _lifetime; } ~Controller(); private: int minimalThreeColumnWidth() const; not_null<MainWidget*> chats() const; int countDialogsWidthFromRatio(int bodyWidth) const; int countThirdColumnWidthFromRatio(int bodyWidth) const; struct ShrinkResult { int dialogsWidth; int thirdWidth; }; ShrinkResult shrinkDialogsAndThirdColumns( int dialogsWidth, int thirdWidth, int bodyWidth) const; not_null<MainWindow*> _window; GifPauseReasons _gifPauseReasons = 0; base::Observable<void> _gifPauseLevelChanged; base::Observable<void> _floatPlayerAreaUpdated; rpl::variable<Dialogs::RowDescriptor> _activeChatEntry; base::Variable<bool> _dialogsListFocused = { false }; base::Variable<bool> _dialogsListDisplayForced = { false }; std::unique_ptr<RoundController> _roundVideo; rpl::lifetime _lifetime; }; } // namespace Window ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/window/window_main_menu.cpp������������������������������������0000664�0000000�0000000�00000016533�13262451251�0025005�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "window/window_main_menu.h" #include "styles/style_window.h" #include "styles/style_dialogs.h" #include "window/themes/window_theme.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" #include "ui/widgets/menu.h" #include "ui/special_buttons.h" #include "ui/empty_userpic.h" #include "mainwindow.h" #include "storage/localstorage.h" #include "boxes/about_box.h" #include "boxes/peer_list_controllers.h" #include "calls/calls_box_controller.h" #include "lang/lang_keys.h" #include "core/click_handler_types.h" #include "observer_peer.h" #include "auth_session.h" #include "mainwidget.h" namespace Window { MainMenu::MainMenu( QWidget *parent, not_null<Controller*> controller) : TWidget(parent) , _controller(controller) , _menu(this, st::mainMenu) , _telegram(this, st::mainMenuTelegramLabel) , _version(this, st::mainMenuVersionLabel) { setAttribute(Qt::WA_OpaquePaintEvent); subscribe(Global::RefSelfChanged(), [this] { checkSelf(); }); checkSelf(); _nightThemeSwitch.setCallback([this] { if (auto action = *_nightThemeAction) { if (action->isChecked() != Window::Theme::IsNightTheme()) { Window::Theme::SwitchNightTheme(action->isChecked()); } } }); resize(st::mainMenuWidth, parentWidget()->height()); _menu->setTriggeredCallback([](QAction *action, int actionTop, Ui::Menu::TriggeredSource source) { emit action->triggered(); }); refreshMenu(); _telegram->setRichText(textcmdLink(1, qsl("Telegram Desktop"))); _telegram->setLink(1, std::make_shared<UrlClickHandler>(qsl("https://desktop.telegram.org"))); _version->setRichText(textcmdLink(1, lng_settings_current_version(lt_version, currentVersionText())) + QChar(' ') + QChar(8211) + QChar(' ') + textcmdLink(2, lang(lng_menu_about))); _version->setLink(1, std::make_shared<UrlClickHandler>(qsl("https://desktop.telegram.org/changelog"))); _version->setLink(2, std::make_shared<LambdaClickHandler>([] { Ui::show(Box<AboutBox>()); })); subscribe(Auth().downloaderTaskFinished(), [this] { update(); }); subscribe(Auth().downloaderTaskFinished(), [this] { update(); }); subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(Notify::PeerUpdate::Flag::UserPhoneChanged, [this](const Notify::PeerUpdate &update) { if (update.peer->isSelf()) { updatePhone(); } })); subscribe(Global::RefPhoneCallsEnabledChanged(), [this] { refreshMenu(); }); subscribe(Window::Theme::Background(), [this](const Window::Theme::BackgroundUpdate &update) { if (update.type == Window::Theme::BackgroundUpdate::Type::ApplyingTheme) { refreshMenu(); } }); updatePhone(); } void MainMenu::refreshMenu() { _menu->clearActions(); _menu->addAction(lang(lng_create_group_title), [] { App::wnd()->onShowNewGroup(); }, &st::mainMenuNewGroup, &st::mainMenuNewGroupOver); _menu->addAction(lang(lng_create_channel_title), [] { App::wnd()->onShowNewChannel(); }, &st::mainMenuNewChannel, &st::mainMenuNewChannelOver); _menu->addAction(lang(lng_menu_contacts), [] { Ui::show(Box<PeerListBox>(std::make_unique<ContactsBoxController>(), [](not_null<PeerListBox*> box) { box->addButton(langFactory(lng_close), [box] { box->closeBox(); }); box->addLeftButton(langFactory(lng_profile_add_contact), [] { App::wnd()->onShowAddContact(); }); })); }, &st::mainMenuContacts, &st::mainMenuContactsOver); if (Global::PhoneCallsEnabled()) { _menu->addAction(lang(lng_menu_calls), [] { Ui::show(Box<PeerListBox>(std::make_unique<Calls::BoxController>(), [](not_null<PeerListBox*> box) { box->addButton(langFactory(lng_close), [box] { box->closeBox(); }); })); }, &st::mainMenuCalls, &st::mainMenuCallsOver); } _menu->addAction(lang(lng_menu_settings), [] { App::wnd()->showSettings(); }, &st::mainMenuSettings, &st::mainMenuSettingsOver); if (!Window::Theme::IsNonDefaultUsed()) { _nightThemeAction = std::make_shared<QPointer<QAction>>(nullptr); auto action = _menu->addAction(lang(lng_menu_night_mode), [this] { if (auto action = *_nightThemeAction) { action->setChecked(!action->isChecked()); _nightThemeSwitch.callOnce(st::mainMenu.itemToggle.duration); } }, &st::mainMenuNightMode, &st::mainMenuNightModeOver); *_nightThemeAction = action; action->setCheckable(true); action->setChecked(Window::Theme::IsNightTheme()); _menu->finishAnimating(); } updatePhone(); } void MainMenu::checkSelf() { if (auto self = App::self()) { auto showSelfChat = [] { if (auto self = App::self()) { App::main()->choosePeer(self->id, ShowAtUnreadMsgId); } }; _userpicButton.create( this, _controller, self, Ui::UserpicButton::Role::Custom, st::mainMenuUserpic); _userpicButton->setClickedCallback(showSelfChat); _userpicButton->show(); _cloudButton.create(this, st::mainMenuCloudButton); _cloudButton->setClickedCallback(showSelfChat); _cloudButton->show(); update(); updateControlsGeometry(); } else { _userpicButton.destroy(); _cloudButton.destroy(); } } void MainMenu::resizeEvent(QResizeEvent *e) { _menu->setForceWidth(width()); updateControlsGeometry(); } void MainMenu::updateControlsGeometry() { if (_userpicButton) { _userpicButton->moveToLeft(st::mainMenuUserpicLeft, st::mainMenuUserpicTop); } if (_cloudButton) { _cloudButton->moveToRight(0, st::mainMenuCoverHeight - _cloudButton->height()); } _menu->moveToLeft(0, st::mainMenuCoverHeight + st::mainMenuSkip); _telegram->moveToLeft(st::mainMenuFooterLeft, height() - st::mainMenuTelegramBottom - _telegram->height()); _version->moveToLeft(st::mainMenuFooterLeft, height() - st::mainMenuVersionBottom - _version->height()); } void MainMenu::updatePhone() { if (auto self = App::self()) { _phoneText = App::formatPhone(self->phone()); } else { _phoneText = QString(); } update(); } void MainMenu::paintEvent(QPaintEvent *e) { Painter p(this); auto clip = e->rect(); auto cover = QRect(0, 0, width(), st::mainMenuCoverHeight).intersected(clip); if (!cover.isEmpty()) { p.fillRect(cover, st::mainMenuCoverBg); p.setPen(st::mainMenuCoverFg); p.setFont(st::semiboldFont); if (auto self = App::self()) { self->nameText.drawLeftElided(p, st::mainMenuCoverTextLeft, st::mainMenuCoverNameTop, width() - 2 * st::mainMenuCoverTextLeft, width()); p.setFont(st::normalFont); p.drawTextLeft(st::mainMenuCoverTextLeft, st::mainMenuCoverStatusTop, width(), _phoneText); } if (_cloudButton) { Ui::EmptyUserpic::PaintSavedMessages( p, _cloudButton->x() + (_cloudButton->width() - st::mainMenuCloudSize) / 2, _cloudButton->y() + (_cloudButton->height() - st::mainMenuCloudSize) / 2, width(), st::mainMenuCloudSize, st::mainMenuCloudBg, st::mainMenuCloudFg); //PainterHighQualityEnabler hq(p); //p.setPen(Qt::NoPen); //p.setBrush(st::mainMenuCloudBg); //auto cloudBg = QRect( // _cloudButton->x() + (_cloudButton->width() - st::mainMenuCloudSize) / 2, // _cloudButton->y() + (_cloudButton->height() - st::mainMenuCloudSize) / 2, // st::mainMenuCloudSize, // st::mainMenuCloudSize); //p.drawEllipse(cloudBg); } } auto other = QRect(0, st::mainMenuCoverHeight, width(), height() - st::mainMenuCoverHeight).intersected(clip); if (!other.isEmpty()) { p.fillRect(other, st::mainMenuBg); } } } // namespace Window ���������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/window/window_main_menu.h��������������������������������������0000664�0000000�0000000�00000002245�13262451251�0024445�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "base/timer.h" namespace Ui { class IconButton; class FlatLabel; class Menu; class UserpicButton; } // namespace Ui namespace Window { class Controller; class MainMenu : public TWidget, private base::Subscriber { public: MainMenu(QWidget *parent, not_null<Controller*> controller); void setInnerFocus() { setFocus(); } protected: void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; private: void checkSelf(); void updateControlsGeometry(); void updatePhone(); void refreshMenu(); not_null<Controller*> _controller; object_ptr<Ui::UserpicButton> _userpicButton = { nullptr }; object_ptr<Ui::IconButton> _cloudButton = { nullptr }; object_ptr<Ui::Menu> _menu; object_ptr<Ui::FlatLabel> _telegram; object_ptr<Ui::FlatLabel> _version; std::shared_ptr<QPointer<QAction>> _nightThemeAction; base::Timer _nightThemeSwitch; QString _phoneText; }; } // namespace Window �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/window/window_peer_menu.cpp������������������������������������0000664�0000000�0000000�00000050171�13262451251�0025010�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "window/window_peer_menu.h" #include "lang/lang_keys.h" #include "boxes/confirm_box.h" #include "boxes/mute_settings_box.h" #include "boxes/add_contact_box.h" #include "boxes/report_box.h" #include "boxes/peer_list_controllers.h" #include "boxes/peers/manage_peer_box.h" #include "boxes/peers/edit_peer_info_box.h" #include "ui/toast/toast.h" #include "core/tl_help.h" #include "auth_session.h" #include "apiwrap.h" #include "mainwidget.h" #include "mainwindow.h" #include "observer_peer.h" #include "styles/style_boxes.h" #include "history/history.h" #include "window/window_controller.h" #include "info/info_memento.h" #include "info/info_controller.h" #include "info/feed/info_feed_channels_controllers.h" #include "data/data_session.h" #include "data/data_feed.h" #include "dialogs/dialogs_key.h" namespace Window { namespace { class Filler { public: Filler( not_null<Controller*> controller, not_null<PeerData*> peer, const PeerMenuCallback &addAction, PeerMenuSource source); void fill(); private: bool showInfo(); void addPinToggle(); void addInfo(); void addSearch(); void addUserActions(not_null<UserData*> user); void addBlockUser(not_null<UserData*> user); void addChatActions(not_null<ChatData*> chat); void addChannelActions(not_null<ChannelData*> channel); not_null<Controller*> _controller; not_null<PeerData*> _peer; const PeerMenuCallback &_addAction; PeerMenuSource _source; }; class FeedFiller { public: FeedFiller( not_null<Controller*> controller, not_null<Data::Feed*> feed, const PeerMenuCallback &addAction, PeerMenuSource source); void fill(); private: bool showInfo(); void addPinToggle(); void addInfo(); void addSearch(); void addNotifications(); void addUngroup(); not_null<Controller*> _controller; not_null<Data::Feed*> _feed; const PeerMenuCallback &_addAction; PeerMenuSource _source; }; History *FindWastedPin() { const auto &order = Auth().data().pinnedDialogsOrder(); for (const auto pinned : order) { if (const auto history = pinned.history()) { if (history->peer->isChat() && history->peer->asChat()->isDeactivated() && !history->inChatList(Dialogs::Mode::All)) { return history; } } } return nullptr; } void AddChatMembers(not_null<ChatData*> chat) { if (chat->count >= Global::ChatSizeMax() && chat->amCreator()) { Ui::show(Box<ConvertToSupergroupBox>(chat)); } else { AddParticipantsBoxController::Start(chat); } } bool PinnedLimitReached(Dialogs::Key key) { const auto pinnedCount = Auth().data().pinnedDialogsCount(); const auto pinnedMax = Global::PinnedDialogsCountMax(); if (pinnedCount < pinnedMax) { return false; } // Some old chat, that was converted, maybe is still pinned. if (auto wasted = FindWastedPin()) { Auth().data().setPinnedDialog(wasted, false); Auth().data().setPinnedDialog(key, true); Auth().api().savePinnedOrder(); } else { auto errorText = lng_error_pinned_max( lt_count, pinnedMax); Ui::show(Box<InformBox>(errorText)); } return true; } void TogglePinnedDialog(Dialogs::Key key) { const auto isPinned = !key.entry()->isPinnedDialog(); if (isPinned && PinnedLimitReached(key)) { return; } Auth().data().setPinnedDialog(key, isPinned); auto flags = MTPmessages_ToggleDialogPin::Flags(0); if (isPinned) { flags |= MTPmessages_ToggleDialogPin::Flag::f_pinned; } //MTP::send(MTPmessages_ToggleDialogPin( // #feed // MTP_flags(flags), // key.history() // ? MTP_inputDialogPeer(key.history()->peer->input) // : MTP_inputDialogPeerFeed(MTP_int(key.feed()->id())))); if (key.history()) { MTP::send(MTPmessages_ToggleDialogPin( MTP_flags(flags), MTP_inputDialogPeer(key.history()->peer->input))); } if (isPinned) { if (const auto main = App::main()) { main->dialogsToUp(); } } } Filler::Filler( not_null<Controller*> controller, not_null<PeerData*> peer, const PeerMenuCallback &addAction, PeerMenuSource source) : _controller(controller) , _peer(peer) , _addAction(addAction) , _source(source) { } bool Filler::showInfo() { if (_source == PeerMenuSource::Profile || _peer->isSelf()) { return false; } else if (_controller->activeChatCurrent().peer() != _peer) { return true; } else if (!Adaptive::ThreeColumn()) { return true; } else if ( !Auth().settings().thirdSectionInfoEnabled() && !Auth().settings().tabbedReplacedWithInfo()) { return true; } return false; } void Filler::addPinToggle() { auto peer = _peer; auto isPinned = false; if (auto history = App::historyLoaded(peer)) { isPinned = history->isPinnedDialog(); } auto pinText = [](bool isPinned) { return lang(isPinned ? lng_context_unpin_from_top : lng_context_pin_to_top); }; auto pinToggle = [=] { TogglePinnedDialog(App::history(peer)); }; auto pinAction = _addAction(pinText(isPinned), pinToggle); auto lifetime = Notify::PeerUpdateViewer( peer, Notify::PeerUpdate::Flag::PinnedChanged ) | rpl::start_with_next([peer, pinAction, pinText] { auto isPinned = App::history(peer)->isPinnedDialog(); pinAction->setText(pinText(isPinned)); }); Ui::AttachAsChild(pinAction, std::move(lifetime)); } void Filler::addInfo() { auto controller = _controller; auto peer = _peer; auto infoKey = (peer->isChat() || peer->isMegagroup()) ? lng_context_view_group : (peer->isUser() ? lng_context_view_profile : lng_context_view_channel); _addAction(lang(infoKey), [=] { controller->showPeerInfo(peer); }); } void Filler::addSearch() { _addAction(lang(lng_profile_search_messages), [peer = _peer] { App::main()->searchInChat(App::history(peer)); }); } void Filler::addBlockUser(not_null<UserData*> user) { auto blockText = [](not_null<UserData*> user) { return lang(user->isBlocked() ? (user->botInfo ? lng_profile_unblock_bot : lng_profile_unblock_user) : (user->botInfo ? lng_profile_block_bot : lng_profile_block_user)); }; auto blockAction = _addAction(blockText(user), [user] { auto willBeBlocked = !user->isBlocked(); auto handler = ::rpcDone([user, willBeBlocked](const MTPBool &result) { user->setBlockStatus(willBeBlocked ? UserData::BlockStatus::Blocked : UserData::BlockStatus::NotBlocked); }); if (willBeBlocked) { MTP::send( MTPcontacts_Block(user->inputUser), std::move(handler)); } else { MTP::send( MTPcontacts_Unblock(user->inputUser), std::move(handler)); } }); auto lifetime = Notify::PeerUpdateViewer( _peer, Notify::PeerUpdate::Flag::UserIsBlocked ) | rpl::start_with_next([=] { blockAction->setText(blockText(user)); }); Ui::AttachAsChild(blockAction, std::move(lifetime)); if (user->blockStatus() == UserData::BlockStatus::Unknown) { Auth().api().requestFullPeer(user); } } void Filler::addUserActions(not_null<UserData*> user) { if (_source != PeerMenuSource::ChatsList) { if (user->isContact()) { if (!user->isSelf()) { _addAction( lang(lng_info_share_contact), [user] { PeerMenuShareContactBox(user); }); _addAction( lang(lng_info_edit_contact), [user] { Ui::show(Box<AddContactBox>(user)); }); _addAction( lang(lng_info_delete_contact), [user] { PeerMenuDeleteContact(user); }); } } else if (user->canShareThisContact()) { if (!user->isSelf()) { _addAction( lang(lng_info_add_as_contact), [user] { PeerMenuAddContact(user); }); } _addAction( lang(lng_info_share_contact), [user] { PeerMenuShareContactBox(user); }); } else if (user->botInfo && !user->botInfo->cantJoinGroups) { _addAction( lang(lng_profile_invite_to_group), [user] { AddBotToGroupBoxController::Start(user); }); } } _addAction( lang(lng_profile_delete_conversation), DeleteAndLeaveHandler(user)); _addAction( lang(lng_profile_clear_history), ClearHistoryHandler(user)); if (!user->isInaccessible() && user != App::self()) { addBlockUser(user); } } void Filler::addChatActions(not_null<ChatData*> chat) { if (_source != PeerMenuSource::ChatsList) { if (chat->canEdit()) { _addAction( lang(lng_manage_group_title), [chat] { Ui::show(Box<EditPeerInfoBox>(chat)); }); _addAction( lang(lng_profile_add_participant), [chat] { AddChatMembers(chat); }); } } _addAction( lang(lng_profile_clear_and_exit), DeleteAndLeaveHandler(_peer)); _addAction( lang(lng_profile_clear_history), ClearHistoryHandler(_peer)); } void Filler::addChannelActions(not_null<ChannelData*> channel) { auto isGroup = channel->isMegagroup(); if (!isGroup) { const auto feed = channel->feed(); const auto grouped = (feed != nullptr); if (!grouped || feed->channels().size() > 1) { //_addAction( // #feed // lang(grouped ? lng_feed_ungroup : lng_feed_group), // [=] { ToggleChannelGrouping(channel, !grouped); }); } } if (_source != PeerMenuSource::ChatsList) { if (ManagePeerBox::Available(channel)) { auto text = lang(isGroup ? lng_manage_group_title : lng_manage_channel_title); _addAction(text, [channel] { Ui::show(Box<ManagePeerBox>(channel)); }); } if (channel->canAddMembers()) { _addAction( lang(lng_channel_add_members), [channel] { PeerMenuAddChannelMembers(channel); }); } } if (channel->amIn()) { if (isGroup && !channel->isPublic()) { _addAction( lang(lng_profile_clear_history), ClearHistoryHandler(channel)); } auto text = lang(isGroup ? lng_profile_leave_group : lng_profile_leave_channel); _addAction(text, DeleteAndLeaveHandler(channel)); } else { auto text = lang(isGroup ? lng_profile_join_group : lng_profile_join_channel); _addAction( text, [channel] { Auth().api().joinChannel(channel); }); } if (_source != PeerMenuSource::ChatsList) { auto needReport = !channel->amCreator() && (!isGroup || channel->isPublic()); if (needReport) { _addAction(lang(lng_profile_report), [channel] { Ui::show(Box<ReportBox>(channel)); }); } } } void Filler::fill() { if (_source == PeerMenuSource::ChatsList) { addPinToggle(); } if (showInfo()) { addInfo(); } if (_source != PeerMenuSource::Profile && !_peer->isSelf()) { PeerMenuAddMuteAction(_peer, _addAction); } if (_source == PeerMenuSource::ChatsList) { addSearch(); } if (const auto user = _peer->asUser()) { addUserActions(user); } else if (const auto chat = _peer->asChat()) { addChatActions(chat); } else if (const auto channel = _peer->asChannel()) { addChannelActions(channel); } } FeedFiller::FeedFiller( not_null<Controller*> controller, not_null<Data::Feed*> feed, const PeerMenuCallback &addAction, PeerMenuSource source) : _controller(controller) , _feed(feed) , _addAction(addAction) , _source(source) { } void FeedFiller::fill() { if (_source == PeerMenuSource::ChatsList) { addPinToggle(); } if (showInfo()) { addInfo(); } addNotifications(); if (_source == PeerMenuSource::ChatsList) { addSearch(); } addUngroup(); } bool FeedFiller::showInfo() { if (_source == PeerMenuSource::Profile) { return false; } else if (_controller->activeChatCurrent().feed() != _feed) { return true; } else if (!Adaptive::ThreeColumn()) { return true; } else if ( !Auth().settings().thirdSectionInfoEnabled() && !Auth().settings().tabbedReplacedWithInfo()) { return true; } return false; } void FeedFiller::addPinToggle() { const auto feed = _feed; const auto isPinned = feed->isPinnedDialog(); const auto pinText = [](bool isPinned) { return lang(isPinned ? lng_context_unpin_from_top : lng_context_pin_to_top); }; _addAction(pinText(isPinned), [=] { TogglePinnedDialog(feed); }); } void FeedFiller::addInfo() { auto controller = _controller; auto feed = _feed; _addAction(lang(lng_context_view_feed_info), [=] { controller->showSection(Info::Memento( feed, Info::Section(Info::Section::Type::Profile))); }); } void FeedFiller::addNotifications() { const auto feed = _feed; _addAction(lang(lng_feed_notifications), [=] { Info::FeedProfile::NotificationsController::Start(feed); }); } void FeedFiller::addSearch() { const auto feed = _feed; _addAction(lang(lng_profile_search_messages), [=] { App::main()->searchInChat(feed); }); } void FeedFiller::addUngroup() { const auto feed = _feed; //_addAction(lang(lng_feed_ungroup_all), [=] { // #feed // PeerMenuUngroupFeed(feed); //}); } } // namespace void PeerMenuDeleteContact(not_null<UserData*> user) { auto text = lng_sure_delete_contact( lt_contact, App::peerName(user)); auto deleteSure = [=] { Ui::hideLayer(); MTP::send( MTPcontacts_DeleteContact(user->inputUser), App::main()->rpcDone( &MainWidget::deletedContact, user.get())); }; auto box = Box<ConfirmBox>( text, lang(lng_box_delete), std::move(deleteSure)); Ui::show(std::move(box)); } void PeerMenuAddContact(not_null<UserData*> user) { Ui::show(Box<AddContactBox>( user->firstName, user->lastName, Auth().data().findContactPhone(user))); } void PeerMenuShareContactBox(not_null<UserData*> user) { const auto weak = std::make_shared<QPointer<PeerListBox>>(); auto callback = [=](not_null<PeerData*> peer) { if (!peer->canWrite()) { Ui::show(Box<InformBox>( lang(lng_forward_share_cant)), LayerOption::KeepOther); return; } else if (peer->isSelf()) { auto options = ApiWrap::SendOptions(App::history(peer)); Auth().api().shareContact(user, options); Ui::Toast::Show(lang(lng_share_done)); if (auto strong = *weak) { strong->closeBox(); } return; } auto recipient = peer->isUser() ? peer->name : '\xAB' + peer->name + '\xBB'; Ui::show(Box<ConfirmBox>( lng_forward_share_contact(lt_recipient, recipient), lang(lng_forward_send), [peer, user] { const auto history = App::history(peer); Ui::showPeerHistory(history, ShowAtTheEndMsgId); auto options = ApiWrap::SendOptions(history); Auth().api().shareContact(user, options); }), LayerOption::KeepOther); }; *weak = Ui::show(Box<PeerListBox>( std::make_unique<ChooseRecipientBoxController>(std::move(callback)), [](not_null<PeerListBox*> box) { box->addButton(langFactory(lng_cancel), [box] { box->closeBox(); }); })); } QPointer<Ui::RpWidget> ShowForwardMessagesBox( MessageIdsList &&items, base::lambda_once<void()> &&successCallback) { const auto weak = std::make_shared<QPointer<PeerListBox>>(); auto callback = [ ids = std::move(items), callback = std::move(successCallback), weak ](not_null<PeerData*> peer) mutable { if (peer->isSelf()) { auto items = Auth().data().idsToItems(ids); if (!items.empty()) { auto options = ApiWrap::SendOptions(App::history(peer)); options.generateLocal = false; Auth().api().forwardMessages(std::move(items), options, [] { Ui::Toast::Show(lang(lng_share_done)); }); } } else { App::main()->setForwardDraft(peer->id, std::move(ids)); } if (const auto strong = *weak) { strong->closeBox(); } if (callback) { callback(); } }; auto initBox = [](not_null<PeerListBox*> box) { box->addButton(langFactory(lng_cancel), [box] { box->closeBox(); }); }; *weak = Ui::show(Box<PeerListBox>( std::make_unique<ChooseRecipientBoxController>(std::move(callback)), std::move(initBox)), LayerOption::KeepOther); return weak->data(); } void PeerMenuAddChannelMembers(not_null<ChannelData*> channel) { if (!channel->isMegagroup() && channel->membersCount() >= Global::ChatSizeMax()) { Ui::show( Box<MaxInviteBox>(channel), LayerOption::KeepOther); return; } auto callback = [channel](const MTPchannels_ChannelParticipants &result) { Auth().api().parseChannelParticipants(channel, result, [&]( int availableCount, const QVector<MTPChannelParticipant> &list) { auto already = ( list ) | ranges::view::transform([&](auto &&p) { return TLHelp::ReadChannelParticipantUserId(p); }) | ranges::view::transform([](UserId userId) { return App::userLoaded(userId); }) | ranges::view::filter([](UserData *user) { return (user != nullptr); }) | ranges::to_vector; AddParticipantsBoxController::Start( channel, { already.begin(), already.end() }); }); }; Auth().api().requestChannelMembersForAdd(channel, callback); } void PeerMenuAddMuteAction( not_null<PeerData*> peer, const PeerMenuCallback &addAction) { if (peer->notifySettingsUnknown()) { Auth().api().requestNotifySetting(peer); } auto muteText = [](bool isMuted) { return lang(isMuted ? lng_enable_notifications_from_tray : lng_disable_notifications_from_tray); }; auto muteAction = addAction(muteText(peer->isMuted()), [=] { if (!peer->isMuted()) { Ui::show(Box<MuteSettingsBox>(peer)); } else { App::main()->updateNotifySettings( peer, Data::NotifySettings::MuteChange::Unmute); } }); auto lifetime = Notify::PeerUpdateViewer( peer, Notify::PeerUpdate::Flag::NotificationsEnabled ) | rpl::map([=] { return peer->isMuted(); }) | rpl::distinct_until_changed( ) | rpl::start_with_next([=](bool muted) { muteAction->setText(muteText(muted)); }); Ui::AttachAsChild(muteAction, std::move(lifetime)); } // #feed //void PeerMenuUngroupFeed(not_null<Data::Feed*> feed) { // Ui::show(Box<ConfirmBox>( // lang(lng_feed_sure_ungroup_all), // lang(lng_feed_ungroup_sure), // [=] { Ui::hideLayer(); Auth().api().ungroupAllFromFeed(feed); })); //} // //void ToggleChannelGrouping(not_null<ChannelData*> channel, bool group) { // const auto callback = [=] { // Ui::Toast::Show(lang(group // ? lng_feed_channel_added // : lng_feed_channel_removed)); // }; // if (group) { // const auto feed = Auth().data().feed(Data::Feed::kId); // if (feed->channels().size() < 2) { // Info::FeedProfile::EditController::Start(feed, channel); // return; // } // } // Auth().api().toggleChannelGrouping( // channel, // group, // callback); //} base::lambda<void()> ClearHistoryHandler(not_null<PeerData*> peer) { return [peer] { const auto weak = std::make_shared<QPointer<ConfirmBox>>(); const auto text = peer->isSelf() ? lang(lng_sure_delete_saved_messages) : peer->isUser() ? lng_sure_delete_history(lt_contact, peer->name) : lng_sure_delete_group_history(lt_group, peer->name); auto callback = [=] { if (auto strong = *weak) { strong->closeBox(); } Auth().api().clearHistory(peer); }; *weak = Ui::show( Box<ConfirmBox>( text, lang(lng_box_delete), st::attentionBoxButton, std::move(callback)), LayerOption::KeepOther); }; } base::lambda<void()> DeleteAndLeaveHandler(not_null<PeerData*> peer) { return [peer] { const auto warningText = peer->isSelf() ? lang(lng_sure_delete_saved_messages) : peer->isUser() ? lng_sure_delete_history(lt_contact, peer->name) : peer->isChat() ? lng_sure_delete_and_exit(lt_group, peer->name) : lang(peer->isMegagroup() ? lng_sure_leave_group : lng_sure_leave_channel); const auto confirmText = lang(peer->isUser() ? lng_box_delete : lng_box_leave); const auto &confirmStyle = peer->isChannel() ? st::defaultBoxButton : st::attentionBoxButton; auto callback = [peer] { Ui::hideLayer(); const auto controller = App::wnd()->controller(); if (controller->activeChatCurrent().peer() == peer) { Ui::showChatsList(); } if (peer->isUser()) { App::main()->deleteConversation(peer); } else if (const auto chat = peer->asChat()) { App::main()->deleteAndExit(chat); } else if (const auto channel = peer->asChannel()) { // Don't delete old history by default, // because Android app doesn't. // //if (auto migrateFrom = channel->migrateFrom()) { // App::main()->deleteConversation(migrateFrom); //} Auth().api().leaveChannel(channel); } }; Ui::show( Box<ConfirmBox>( warningText, confirmText, confirmStyle, std::move(callback)), LayerOption::KeepOther); }; } void FillPeerMenu( not_null<Controller*> controller, not_null<PeerData*> peer, const PeerMenuCallback &callback, PeerMenuSource source) { Filler filler(controller, peer, callback, source); filler.fill(); } void FillFeedMenu( not_null<Controller*> controller, not_null<Data::Feed*> feed, const PeerMenuCallback &callback, PeerMenuSource source) { FeedFiller filler(controller, feed, callback, source); filler.fill(); } } // namespace Window �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/window/window_peer_menu.h��������������������������������������0000664�0000000�0000000�00000003106�13262451251�0024451�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace Ui { class RpWidget; } // namespace Ui namespace Data { class Feed; } // namespace Data namespace Window { class Controller; enum class PeerMenuSource { ChatsList, History, Profile, }; using PeerMenuCallback = base::lambda<QAction*( const QString &text, base::lambda<void()> handler)>; void FillPeerMenu( not_null<Controller*> controller, not_null<PeerData*> peer, const PeerMenuCallback &addAction, PeerMenuSource source); void FillFeedMenu( not_null<Controller*> controller, not_null<Data::Feed*> feed, const PeerMenuCallback &addAction, PeerMenuSource source); void PeerMenuAddMuteAction( not_null<PeerData*> peer, const PeerMenuCallback &addAction); void PeerMenuDeleteContact(not_null<UserData*> user); void PeerMenuShareContactBox(not_null<UserData*> user); void PeerMenuAddContact(not_null<UserData*> user); void PeerMenuAddChannelMembers(not_null<ChannelData*> channel); //void PeerMenuUngroupFeed(not_null<Data::Feed*> feed); // #feed //void ToggleChannelGrouping(not_null<ChannelData*> channel, bool group); // #feed base::lambda<void()> ClearHistoryHandler(not_null<PeerData*> peer); base::lambda<void()> DeleteAndLeaveHandler(not_null<PeerData*> peer); QPointer<Ui::RpWidget> ShowForwardMessagesBox( MessageIdsList &&items, base::lambda_once<void()> &&successCallback = nullptr); } // namespace Window ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/window/window_slide_animation.cpp������������������������������0000664�0000000�0000000�00000007555�13262451251�0026200�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "window/window_slide_animation.h" #include "styles/style_window.h" #include "styles/style_boxes.h" namespace Window { void SlideAnimation::paintContents(Painter &p, const QRect &update) const { int retina = cIntRetinaFactor(); // Animation callback can destroy "this", so we don't pass "ms". auto progress = _animation.current((_direction == SlideDirection::FromLeft) ? 0. : 1.); if (_withFade) { p.fillRect(update, st::boxBg); auto slideLeft = (_direction == SlideDirection::FromLeft); auto dt = slideLeft ? (1. - progress) : progress; auto easeOut = anim::easeOutCirc(1., dt); auto easeIn = anim::easeInCirc(1., dt); auto arrivingAlpha = easeIn; auto departingAlpha = 1. - easeOut; auto leftWidthFull = _cacheUnder.width() / cIntRetinaFactor(); auto rightWidthFull = _cacheOver.width() / cIntRetinaFactor(); auto leftCoord = (slideLeft ? anim::interpolate(-leftWidthFull, 0, easeOut) : anim::interpolate(0, -leftWidthFull, easeIn)); auto leftAlpha = (slideLeft ? arrivingAlpha : departingAlpha); auto rightCoord = (slideLeft ? anim::interpolate(0, rightWidthFull, easeIn) : anim::interpolate(rightWidthFull, 0, easeOut)); auto rightAlpha = (slideLeft ? departingAlpha : arrivingAlpha); auto leftWidth = (leftWidthFull + leftCoord); if (leftWidth > 0) { p.setOpacity(leftAlpha); p.drawPixmap(0, 0, leftWidth, _cacheUnder.height() / retina, _cacheUnder, (_cacheUnder.width() - leftWidth * cIntRetinaFactor()), 0, leftWidth * cIntRetinaFactor(), _cacheUnder.height()); } auto rightWidth = rightWidthFull - rightCoord; if (rightWidth > 0) { p.setOpacity(rightAlpha); p.drawPixmap(rightCoord, 0, _cacheOver, 0, 0, rightWidth * cIntRetinaFactor(), _cacheOver.height()); } } else { auto coordUnder = anim::interpolate(0, -st::slideShift, progress); auto coordOver = anim::interpolate(_cacheOver.width() / cIntRetinaFactor(), 0, progress); if (coordOver) { p.drawPixmap(QRect(0, 0, coordOver, _cacheUnder.height() / retina), _cacheUnder, QRect(-coordUnder * retina, 0, coordOver * retina, _cacheUnder.height())); p.setOpacity(progress); p.fillRect(0, 0, coordOver, _cacheUnder.height() / retina, st::slideFadeOutBg); p.setOpacity(1); } p.drawPixmap(QRect(coordOver, 0, _cacheOver.width() / retina, _cacheOver.height() / retina), _cacheOver, QRect(0, 0, _cacheOver.width(), _cacheOver.height())); p.setOpacity(progress); st::slideShadow.fill(p, QRect(coordOver - st::slideShadow.width(), 0, st::slideShadow.width(), _cacheOver.height() / retina)); } } void SlideAnimation::setDirection(SlideDirection direction) { _direction = direction; } void SlideAnimation::setPixmaps(const QPixmap &oldContentCache, const QPixmap &newContentCache) { _cacheUnder = oldContentCache; _cacheOver = newContentCache; } void SlideAnimation::setTopBarShadow(bool enabled) { _topBarShadowEnabled = enabled; } void SlideAnimation::setWithFade(bool withFade) { _withFade = withFade; } void SlideAnimation::setRepaintCallback(RepaintCallback &&callback) { _repaintCallback = std::move(callback); } void SlideAnimation::setFinishedCallback(FinishedCallback &&callback) { _finishedCallback = std::move(callback); } void SlideAnimation::start() { auto delta = st::slideShift; auto fromLeft = (_direction == SlideDirection::FromLeft); if (fromLeft) std::swap(_cacheUnder, _cacheOver); _animation.start( [this] { animationCallback(); }, fromLeft ? 1. : 0., fromLeft ? 0. : 1., st::slideDuration, transition()); _repaintCallback(); } void SlideAnimation::animationCallback() { _repaintCallback(); if (!_animation.animating()) { if (_finishedCallback) { _finishedCallback(); } } } } // namespace Window ���������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/window/window_slide_animation.h��������������������������������0000664�0000000�0000000�00000002333�13262451251�0025632�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace Window { enum class SlideDirection { FromRight, FromLeft, }; class SlideAnimation { public: void paintContents(Painter &p, const QRect &update) const; void setDirection(SlideDirection direction); void setPixmaps(const QPixmap &oldContentCache, const QPixmap &newContentCache); void setTopBarShadow(bool enabled); void setWithFade(bool withFade); using RepaintCallback = base::lambda<void()>; void setRepaintCallback(RepaintCallback &&callback); using FinishedCallback = base::lambda<void()>; void setFinishedCallback(FinishedCallback &&callback); void start(); static const anim::transition &transition() { return anim::easeOutCirc; } private: void animationCallback(); SlideDirection _direction = SlideDirection::FromRight; bool _topBarShadowEnabled = false; bool _withFade = false; mutable Animation _animation; QPixmap _cacheUnder, _cacheOver; RepaintCallback _repaintCallback; FinishedCallback _finishedCallback; }; } // namespace Window �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/SourceFiles/window/window_title.h������������������������������������������0000664�0000000�0000000�00000001265�13262451251�0023617�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once namespace Window { enum class HitTestResult { None = 0, Client, SysButton, Caption, Top, TopRight, Right, BottomRight, Bottom, BottomLeft, Left, TopLeft, }; class TitleWidget : public TWidget { public: using TWidget::TWidget; virtual void init() { } virtual HitTestResult hitTest(const QPoint &p) const { return HitTestResult::None; } virtual QRect iconRect() const { return QRect(); } }; } // namespace Window �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/Telegram.plist�������������������������������������������������������������0000664�0000000�0000000�00000002515�13262451251�0020020�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>CFBundleExecutable</key> <string>$(PRODUCT_NAME)</string> <key>CFBundleGetInfoString</key> <string>Telegram Desktop messaging app</string> <key>CFBundleIdentifier</key> <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> <key>CFBundlePackageType</key> <string>APPL</string> <key>CFBundleShortVersionString</key> <string>$(CURRENT_PROJECT_VERSION)</string> <key>CFBundleSignature</key> <string>????</string> <key>CFBundleURLTypes</key> <array> <dict> <key>CFBundleTypeRole</key> <string>Viewer</string> <key>CFBundleURLIconFile</key> <string>Icon.icns</string> <key>CFBundleURLName</key> <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> <key>CFBundleURLSchemes</key> <array> <string>tg</string> </array> </dict> </array> <key>CFBundleVersion</key> <string>$(CURRENT_PROJECT_VERSION)</string> <key>LSApplicationCategoryType</key> <string>public.app-category.social-networking</string> <key>LSMinimumSystemVersion</key> <string>$(MACOSX_DEPLOYMENT_TARGET)</string> <key>NOTE</key> <string></string> <key>NSPrincipalClass</key> <string>NSApplication</string> <key>NSSupportsAutomaticGraphicsSwitching</key> <true/> </dict> </plist> �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/Telegram/������������������������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0016740�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/Telegram/Images.xcassets/��������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0022001�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/Telegram/Images.xcassets/AppIcon.appiconset/�������������������������������0000775�0000000�0000000�00000000000�13262451251�0025476�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/Telegram/Images.xcassets/AppIcon.appiconset/Contents.json������������������0000664�0000000�0000000�00000002346�13262451251�0030173�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ "images" : [ { "size" : "16x16", "idiom" : "mac", "filename" : "icon16.png", "scale" : "1x" }, { "size" : "16x16", "idiom" : "mac", "filename" : "icon16@2x.png", "scale" : "2x" }, { "size" : "32x32", "idiom" : "mac", "filename" : "icon32.png", "scale" : "1x" }, { "size" : "32x32", "idiom" : "mac", "filename" : "icon32@2x.png", "scale" : "2x" }, { "size" : "128x128", "idiom" : "mac", "filename" : "icon128.png", "scale" : "1x" }, { "size" : "128x128", "idiom" : "mac", "filename" : "icon128@2x.png", "scale" : "2x" }, { "size" : "256x256", "idiom" : "mac", "filename" : "icon256.png", "scale" : "1x" }, { "size" : "256x256", "idiom" : "mac", "filename" : "icon256@2x.png", "scale" : "2x" }, { "size" : "512x512", "idiom" : "mac", "filename" : "icon512.png", "scale" : "1x" }, { "size" : "512x512", "idiom" : "mac", "filename" : "icon512@2x.png", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } }������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/Telegram/Images.xcassets/AppIcon.appiconset/icon128.png��������������������0000664�0000000�0000000�00000022146�13262451251�0027374�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������PNG  ��� IHDR���������>a�� �IDATx}yxW[{6˒,˲=^Bp}ql$d|yo3,f`f 3�<yM&L!!!!doؖ]^$K-Z=UZRZvꮪUg?[09ast`n@^毯BDA/ � d̎ Dt؎АLFcjb�۱bs:Uk=m]օZ}hm"HAY)#Ƞ5twT#{^MI[Ug5h 7]y=ӂLsD e,089`B}$ر%gbh=^;:[_uM7XuzS[ȶ@ R �U Ls�@$q-۞'O8F.^y u`M+�e@ʮ*'dl$c gЖ_Ž ծeaV3^7o^Ӧ;t.^s>c HuӊqpLh0ȏ<qϏR==nZ)JZ7яͻw[:*ӀZ7kR`BxaGbC/⁾_~[#{Ժ]1@x5>횿] mպY?db$ÃqoqȑZ7+Qdxw,;4=R @2/tbK$AҪ9w\xX_n~Y' &4;w>;'kٞjƫޮk2s<sD JAoZt?]=Ḍ& @G_|o΄*3Yf$mR,t+7^߿m g3�kw}_v;U*Yw᪥ ءT3لe!w[2g56@#1΢?3Sמ1hyק?&t2) "[n]<'HZf[}`/kl{gHF1G +7pitS+#%�LhͿO@sğd&X$k#;~LFGu1�{ѧ{_ߎJղ-"۟xL&ոNU M[ox+cUcs dp5w|sdoUXW t7'v?'Y-Z.bx˯~NfI3@{?LD0G@Y& ڽ W~ч*Y(zwbD!gL%뗁$u?t[1p/Xzg9s5/r DpW'zzRwWa�?qXP gWon7vdoghVuK.T�* YD~ LLE1m m/|]sfdp/Xqosi1V<LA+/8oK㇦zi~᫮%dPsf[8m7*bc1e XwMץds6 iղ}S9ǔi._'zS[UXa<Hޥoyk~!cCg&)Mw|Էr[PA!wN�oh˼zz3w6ɏX*'w?z{N  `H ң2W4?/K2_os/\BY^M[R6-V7q�j�<z0{:J'K R῾RgG&sƛntY!/KՐ-K@!yn $%~v`>"I,$/j#8/Yg�]?q&Q$؊pbnV/4�S*\9V5U@ox[hO*zwŅ)#7)LE0lB@縨Ճ[qMB$$ҥ#aţ@C]z2 ¶ [Bn௾l?fTʳz_ۖ M0p-"`83pgM. ք=b�hhLjی2*n֮.i#@`H%,W3aw$9#a\>E))^]-yr +u^@N IsԖA?--h1#:9RL@&t/zcAD+Rc K\3n. TD86B$eAi�#LVd[{kRFbpc'd�hXY 2ϽJ;tM^͋Bui.+ sy�T9oH.xl00``EjʶtqbKw YvҤ9P HIaqHqKW]u.08 >ZO4h|sh0M 3+�4y"py^3}Xr :kOp3SWwqh'`U<cC74;G°ؒph8 X ӂ;'ileph\2y3h.mlʑhXۺBqq� HلXҞ pj8$ZCxtq/È!qt83<έ30FOeµpZ9sq2j,[l8:E@{PǦ:ܺ4 -^\ILrߌԆcNwk]-!|:8�3LL  @ � \7eۖz'3{9Йr \ [Bc$=yH}ʔ8#7uij ! EpID .y+Y -`K4O k7$<He�U \ sve+BJtΰp V7sMNip@ 'qz8V �~!xt Nͽg &<Kʌj(1HQbL@Jձzxnj�γ"$+;x{W[}w;*ް )!~°q"G4ifzu,n%8 6 Fًd^Mg8-a޴($mZ,*4m$oU>Uk:XԳv?2 gN?TN_*B߅Mp$,Ih,Sf 02m3JJXF 36x]s6ze߿6IԸ5mI]7\Qif\hI89@°9)AGs�@I<p0p<&FMJ;9HIH#r9.(&$/T'dd#X/ME^"`�$Fv\fQ,-^u2D Cq FS "tV*z/4�L- cI &,' )< [vA)2MUf"Loi7+zDIf4lEHڄz+;Bx{WW, կA7* 5#X HUDhm EjBp`0%w<-%L]X#Q7!h`=ܜU5+rҲD9i`@JN.QS.2iC1 DS`}@%Bkm c� $s8G:β?˩\'AZOpN�DƦR?f�O8UHtjukCuYկÒj҉raM#eʬƵ7127%`_6pqlpMk 6}< *jSRA*`YtuX䁖Yqΰ9] O`)Ơ~C I YQU-r>-D7Zlcs/1R%$Gu!4y5RYhi7$t$ 8GGs�u>״%?3It܄XŤ<C?#d)4"⚀@�%s^PVؼ8ЉK nI{OqDNB'_ǢA<<ApOHyF73T`>qJy @�\ـKTԓ/" %q&4�l*/?0�{<nj.-+ b ~`CM&= :glܲqb)  u,j¥l[h,Seldy,Iyb*֔>�Ic")nҰp|rhdB8‘(ڍTWGGs<3[8<.X."$9{\>� h>*V$O ռ Dz] H'?6upmWk#vAsSL8=KRz A:IBG80F,_={S9$YgYddw )}SK`3la~pW?V7qzlZҀU2+g0-cҡ"BcЃ&?>Q ΁ߕ)X`3>2_(Od| ygw4L hz"p*e�py8Wml&YRWF/�&0F9[/JC)¼:/Z} "XEXƻ>G c$29H~vyۙDJv O )3b)}:`p :wq]W#.ZB[ n R`(K- >P~J(g@”Hf�hvk<m)s28X^MIؔ`$aDV�RtknMÈTF{@%A\Ո C5 FR8I䄄\-$~!8DZHGG3GsHnFӒOr�(rC9T[K;FrH掶ylu � 0:6,s..h z[i` Vh3< 9߀rH~2U%?}~!9sT[Ǜ !Nջ%usip\8#pV=Fi-?Ԝ1WO`e ng%Ь?*< Iyfd Qp9S{3qe CM `((B07Ne`p&Ȅc3R13"ᘼ2k_ g!*Bq;Z'zfnNq =6!�.>/�.fN;ЁAg%?SKd(Py&�Nx@2}*>b.{IŔzxZ|Ec@0`+/6 qCbFA<' O;9ɟlXqg*}!36hL.eo({qr0]UƀXÜcxv% ,?6+y11ޞkSU)ޑZE0zFBӦav dk3 $NfQcYIH0eYztHK7/Y1;rHOUFW#j.EᕾDQ*t_~8Ɩ}'Iz8gNx} �GYVzQ ,OYOQ3aܹ}#YGv"I0^U.w) v@"ƜG p4^?x$LtS(Zg Qd?OU6̞G E1!ȑ3oX{_檹ht3 ˜sKJl9^A>)B 6˓Q^xJ>�9voLW5vҟYLti|R*Қb$,颥]'#Y^;J}v=fz=Li.XolIņƵ2�==NQ`!4<D̒WvW)�MpD<AVdCܔƁ1zL_.'<aɀ]% u�2|Ugndf "p<jn;w0�^]3q|sqhb&A '%Q 2}Nk!|{˓崻W~`j!?g#e q<u8j b\G؆8n}\:Ѣ  a(iCkՖ|�Lw|(/9+H>ʞcg3vő2 7[1Sp{ eu<s$ܻt"k*Dxp&a:pȬg$=ơqz�(+u[QWr5W]3p 1炭d 8D޿O&DgMӢ >$ \>]kNFCp1ӆ( I~S~s?'IMZolrlH 8všȼ[pD 8zЅ*'mB/LJ؆Qz^6 I8u\ gΡl$�ʞwRn=w.p6h"G^YKJ$��q2f;OgȀqsŘ}&6 p ##)fi4xuyISM{�~�;8WQzVv_sy`ߺ=_,}mLU&zR88J97Ƿ_ш#s+^]ðIc{_w#aN0*d4*F|0;7hL:'3<K7P RKmcS1\"' _H,;cZʉ8>K;,5m|wqD?` \^X=/H>IH?0G:}uws_ЇJb8ñS# gX1FDO?X$*%v➝3_ Y0gL:eRjT kj~c¾p -&n'*1L['.8FL!fXU8Z.` kme~p+M깟|ezrnWzcֹn5T 3h.8P" 0V3eE4` /+5kw\ZQ*]:Tt0?FFy1M,G ly ۄLwL\#rc [ h`JHʆWyT,Ư G⿾c_0%[<EÇ!&+/5/"�drjkK b!�'�k/$�P0򞴏yM[jѶtlո٩q&4do\0: lԼ3@sF~NJŋO�X&_%ڇ_ݯ6 x ,cLpf(#̬1C43D|.|o?'{>|s HRpn&3>`1pm8yHw/k?Ajf2w%ɘ#&v㚆"ʼn_ /}fo֎ǟI=?0ngrQ.><ڽW V08TF~x֋0]xh$y@/ lJJC;vjmGDc0QQ!`vj#{?15rETPVrK  g0 +0EW �#LO2;zj$2wa m4 2cW'c*}2}_<~Z|qb[M퍳)Ggᡃ!K;Hק �@ ן*W][>�c-?H��!IDATW6@L8y?!@W O0S'gc m-mcV �gzUdY _(C}'�u=K: �U31҃ EX�$1LB~>C]Fy<5/I}ϢJj?35ԇ�DmϨpoB[b Nh SwEIx?3SM3=+N*{teE &a(<<E2z (}>w�?ꗬ*NaӨgSlS�֮Йӟ`p7+6@v?\/}B!8euj7߫#_[�0;)I7|{_@*UvJ} Içמz\xU㳦w*J| i>ӿKٳ�pvo2-:+o7}beCLͺqJ�נz=f˟�-@XVɗ]x!fv?<#\@~W|�>G ΞM 3a&/_|95 ̶q �= iB_yA!p&l`62@Fh_*rûĚcMM/.�N,(2=[_;X4^8Yб w\wXuftŬ01c2 t@)PL)<Y qf%38[ YW_ȗnww.˄SiN4Y:A*/>qL:utVecx� u֥kkyҕ}'臦#;ES*A0OP{d:}p Rŗ8F#n�A0-,؊PS+ 4gFx 7�0$4`$F)>F,|):xCa[�"�Rp|*je\b8ߣ߳HHIp\9as09̡B;N}w����IENDB`��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/Telegram/Images.xcassets/AppIcon.appiconset/icon128@2x.png�����������������0000664�0000000�0000000�00000105455�13262451251�0027753�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������PNG  ��� IHDR���������\rf��� pHYs��%��%IR$��;tiTXtXML:com.adobe.xmp�����<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.6-c138 79.159824, 2016/09/14-01:09:01 "> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <rdf:Description rdf:about="" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/" xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#" xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#" xmlns:xmp="http://ns.adobe.com/xap/1.0/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/" xmlns:tiff="http://ns.adobe.com/tiff/1.0/" xmlns:exif="http://ns.adobe.com/exif/1.0/"> <xmpMM:OriginalDocumentID>xmp.did:1ACBF4C4D24FE4119247E59A0A0429A0</xmpMM:OriginalDocumentID> <xmpMM:DocumentID>adobe:docid:photoshop:167a618f-0639-117a-8f65-8cab695dcc76</xmpMM:DocumentID> <xmpMM:InstanceID>xmp.iid:ef977eab-e8ee-43be-9712-dae3b7cac539</xmpMM:InstanceID> <xmpMM:DerivedFrom rdf:parseType="Resource"> <stRef:instanceID>xmp.iid:1BCBF4C4D24FE4119247E59A0A0429A0</stRef:instanceID> <stRef:documentID>xmp.did:1ACBF4C4D24FE4119247E59A0A0429A0</stRef:documentID> </xmpMM:DerivedFrom> <xmpMM:History> <rdf:Seq> <rdf:li rdf:parseType="Resource"> <stEvt:action>saved</stEvt:action> <stEvt:instanceID>xmp.iid:013ff985-91ab-474d-807d-b60bda015c0b</stEvt:instanceID> <stEvt:when>2016-12-19T19:15:42+03:00</stEvt:when> <stEvt:softwareAgent>Adobe Photoshop CC 2017 (Macintosh)</stEvt:softwareAgent> <stEvt:changed>/</stEvt:changed> </rdf:li> <rdf:li rdf:parseType="Resource"> <stEvt:action>saved</stEvt:action> <stEvt:instanceID>xmp.iid:ef977eab-e8ee-43be-9712-dae3b7cac539</stEvt:instanceID> <stEvt:when>2016-12-19T19:19:58+03:00</stEvt:when> <stEvt:softwareAgent>Adobe Photoshop CC 2017 (Macintosh)</stEvt:softwareAgent> <stEvt:changed>/</stEvt:changed> </rdf:li> </rdf:Seq> </xmpMM:History> <xmp:CreatorTool>Adobe Photoshop CC 2017 (Macintosh)</xmp:CreatorTool> <xmp:CreateDate>2016-12-19T18:26:51+03:00</xmp:CreateDate> <xmp:ModifyDate>2016-12-19T19:19:58+03:00</xmp:ModifyDate> <xmp:MetadataDate>2016-12-19T19:19:58+03:00</xmp:MetadataDate> <dc:format>image/png</dc:format> <photoshop:ColorMode>3</photoshop:ColorMode> <tiff:Orientation>1</tiff:Orientation> <tiff:XResolution>1440000/10000</tiff:XResolution> <tiff:YResolution>1440000/10000</tiff:YResolution> <tiff:ResolutionUnit>2</tiff:ResolutionUnit> <exif:ColorSpace>65535</exif:ColorSpace> <exif:PixelXDimension>256</exif:PixelXDimension> <exif:PixelYDimension>256</exif:PixelYDimension> </rdf:Description> </rdf:RDF> </x:xmpmeta> <?xpacket end="w"?>D��� cHRM��z%��������u0��`��:��o_F��O3IDATx}wdU*wd`vM+u b\]%($ L`r+t«Uy ={%!H27K E�)RH�"E�)RH�"E�)RH�"E�)RH�"EJZH^*%FD*�ܛ8B-'4 ,B)W7-`.0oY3 [Cju-%MĔF 󣤨! (�{<@8aBNICB>{;ӱ[;dѹL2#Fu (Lki [,yˏ .XZo^L/Pc j!R41W9t[8� R0`g cA8t:3Ӷk[zF=fo[;7Ӧ{H�D^ .>zUd՚ëN=3XC+i:; ;NON.H0D �) F '98`wܾ%{nwWBWt\سΎs9%ǝ5[Zp{D٫{( HA -CC;_M||s&wndut˻/`NJpʥ;'vEE:ij$6]}jTh kPz^{걡O<޷eplG �VBK[]{KjOQc 2 {V)؀4p #spdžMO<0Ï$$lӒ+F@K`ug\~YY׾5jJ&&l2\^ @ LAné?|?fy$�TDjO† =ιLil ne�ν *S^h.hL2_玁'PP�*_|Ƌ7g݁N$!͌Go?4ÿ7?8�*Fj\|Nep)Es#ad X ne/=/nxt<-i媏c3/%EeHAd]|)S@LAWzoG+~ 7\7\^\pnTb@ Av<t?|/ye$�LbSuw嚏kxIg 7e |e ,}ho#a׃7bI��0 yoF>a{wt?xwůPJip7N!MU⭁BHߺjw >ښ%�X__[?B 2 R%�Hn[}Ir>ߠյ,p U^JU`¶2;uK<tًEY{!$|)0({텔O� Hml?s}wR 7RRYf{|@`े<}&spQK/\/;ӜTB8 Ig}x�PPo{l H�`$ݟT5i"eZoz[?0g{Hz�Zs’"GqtIc0{ $�R�h#X}K 4LR|)üN» _F*]͔@@˯Djb>7]ȍK)ȡkcenU qe?ix:!ٴ#elJ>tXs@z�Uԟ75 /e\CAĬ~m� �>1]Lܓ2ް�)`rO_I'B "�)>=/WtŇ?dR %"ӏ?~@`/ZsouK4a̎=[|NyJ�@Z-^?-r9!bL:)`졞z'+ ��\r+?+N\R H B8uJ��noIkV~7՚t#2'e=ym�>基wZ]"nH2 P@ /^i P^x1kZ~~;RL9u<QU?3]察͔T~)36m)>͟4{߇P㪉ERf=y筕 ̶H;篔/@q{gy@5P� ֥K_cT`L�B~Qse< � ƕQqiTBiTkquj3Ef  ˮ5]&Y' |0= m/r}�@XXsRT�n5/^_~|Gg*`�x[>o o;lRuDj0wT@�xW];]-T~)'Nrugz_qVi[|+xh<ar%IbF ?xo31]j�-~6!!-7ZvšJT�0�_FhikL]1yȣZn[`XJ/P)�@�x}ᒿFB+֜_ޙ\޿5 FZΠ`j # oxQ!$EW~އBO=G2cnzv}C;6OUPZ h}'?>LRfLбD}K�kc*cNN&I^p}LR�bX/?DpA =_x{^}i@S�Ow;GDl@8:_zx.Q�#GyAg/<1gW|c3Ef --�92gghy'>[~,f`t� ǜuc-ܮtl(5s(ӭ\GU"¶ "L7^G0J 2�O~Jom"VNY Ti.� �<|]FN<�L#5Ø2i�"hy@)>A#?j}_vM /"�l jӢMW0Mi2RȲ/SrCVIRR 癝L8P!A@kz[GCr>yȣ!J8an�iڦ_Ȕ)t`sGT4 I7`ɘ⠩�'fFR)(�2@PT@U;WFp6^j/`*c�T-=Zqi�L\ c q\<DT`8;Lܻ;%2TW@L\~ٹﵩSi"rirЇ#w2☆qNk ",p1>k?IPuK__1E O@,;wr7_)uMGt Θc8a-\[F3]նD4VuߟNj{/?؍q8238/| )rk3kg.ێAC!c4�D2plh k` �P}T= -jpQW-B:!q&D5tiC]iw\oT/&91� ֥GNZJz.> 0\4?GqZ#s->֏˅& m0 [\e ,£<:l�sv%ZrKo)>bs73± \4˗Eqt#ϭ/�@Ck+T-�Q}(A݅| T,�@7Sȴp8�DU\0WpFks7'd`6R}݈7AуUH#t K?5sJYǟwa`eO�`9#P30pG5?k?Y/8H @@PL9-o�`R �j͹o~H.~ w]+EqlC�BKEmG`_ #s{kU3iw߹g]IǟwM߭şVq5/]Oӳ.# fl@!/ HnmjhU2[2|y&6<|'&)8Y�\zRTSqsDLŏj kp.Y}Hӯd*zR6L\ uЂ᲋ff\PsֵoMlxwp� @ vocKVǥKrY '6۞zI ]qbVR@�Z:@@; jm2{{w%Q�_qLh嚵gv[@sH%a\,!< Q$va9@@|lp\E�!#±k xCnd�ur.5D䤋.aڰ `+; ؞bTMA\<KưV`)2yTޗ$@YFb­F5,ÈzCpkf�@d.n-]pYQS[B-ԱyVy㞾FqD'H!Ahi)5=@ִpepIgpKfze׬EQ̏Å<Iөf�,2`0Sz-X+рQgXЮ�CǞ}RP32_JF8^eKcnξ1� oN5J΄(,O13U+K .$�-rykJ:L:t-Z#\Ƶ+bX;?7U�h `҂pB8� *XE^y?V:[ tlf{F=� +֬ �TX^@/2U+bX$e5n@/cs$!GUOO0bA |@�Mrt%;$r.ShT`env *�l ZҕGjL�,>8iR!)\ dIW Bi sF0,m})&2p]aIӳP>blMi둘JB!%tڳ2{_ x$�@�>̵1![+B 虎@}PfE Әʠ/v0#j:ZW8~ 4a8h V9 9BG=O7`��W֭nNcby /*;B{sqᠭ/ "p@,ayk 4m c mРQ( �$|Nf#pۄC@, ZcOfy<5t`F(� ZEǜ$l3'"e:,|vfs�E4\׬¨ 9{a@G iwrpu�Ġxϛ Q-�]01R sAC-dz8bʌ{±Ե,>*s��_ZK? E43:oX0nskFx$i pXK I z�H ~~ߢm$pf)5h`�@�XvIH jX4VpRs!`3Wv8RLs1l6EMيU"�ݼbS(�D+�Ost � =8DPOX H?U~v6*.Z\kW܅aUـ1.)04q/a1g #vMp�#>eJ%S~o}gJ%с_±Xt ju¶z1%/88a[Obd㛂rY .[�rċWHޟBo< V,j6ΧfOFicw_:S!+!"#v6�簒Ih0HQg aR~�XUmZL8TIt G 1-585�d* '[ӛ0ޗDrS{GUbhLD ΔH +4y&åP!0u@sP6._aw4�Kh],�:rƏ Ο.Z6ܙ7*_a堭?141,k.3ť*c88Av?Ka_/+>bG?kLꌀ�*EG1a��}U$lKj8y=ndTFXUKcry 5P [ ^M8̛5F@ ͋?°{f* ՛p)^ +RBwc�dA`,:Om' �6oQnD_9:]ְVDp͊ZFCPn5#Uقa/F<MDꔺyZ�;zS^_Z\ sC# @9/qɤA HU86+A,�ֺ|p+Crsg̋5diKWL@{:)t pip@Ųueڔn#75\o�AF>'OQ)#}|Pa'  �hӢܱ4_:IN �T-4_$#0 9Vp(\^C{.~%̌RhHòh 6ςTo�P~Oa߇ߣ$/U/4SBpp]R?>0=�Xc+$�=Rni WŹ h}ܬ|IahHgOk#,iBelR �`R|G || >C)p� <(L RӴj߽~Z�@m߬Dk]@ٝpB*Ú0\QuKbXQ�#7W.~bs i?WCY7ƂX4ݽ)M! ؾ\GWAI#0L4m)*ye0J�,k&5s˜3?Q`0  ,%Q\kZCj]]F0l)!Fh aQԔMx;7@750scy/%5�Dz! p5[L��Қ̃1aYfe [ż M%} l~o< $^iͶ$A9ۀa.Jq~ʵr{A0{rǥl@@pe|OǕ�X<ȷJ8)˗džrU}V6<fS{A{_ I;Nkac-u" e,OCc[3bGJ'ߟr (x@cndPb 04�G f ,ipqWũanYLOLHs c*~VAƨ[W#n#a*ƾA+|<;඘tO@,Rbj�ƅMp}j ^5+kq(嚼j_aAO<mR~.",iJ{ye,�U!Ocp}�q@S<kхOkȧEzsN8\؞'0I8Pkj s#58���*z=E ( Xjpw6 Wdp1f/WUais 5!b?KL|vBCFrB#C862I иyD@;J-a 筈ڕX [_W7:ޗDWL`YKlZK{p.;ŋjzG~ y  Uay A1 nWTNk 㪕Xktά m2k DY~uZb l.*dg�OAUh |p~3\802^@xrD b ]i8C=S �Z@ W7 bް47)!D}Ͼ>)iO"cfS{45,mASpG_%]: 4R>'?~_sO/>q\8`L=  EH�40%P /9TpbKo9-6\7g_9G׫g&cqWVaO_ry}xzq@F!aT8clS` � "md`eJ {m lJY}geXE3Z;֝94>P` |7_L{Q<7 _O�<Sxe�P~pI ?g̏@!X71Btї`|#w x,SM #<c:[x2CT쫈F)912u* ir} \BepEZ}t o06oʹ@k]?!n՛F,_rZ+|~Ak� }ᨅO �e��ar|r|~6-e{ })$+V" 0!p߯kcᡌr"^bxMH0^|+\4t.i�ܶlGas]ECPEœ ܂tYc)?�,rEc=) 6tEc! $roBeA0_C =>ؐS8�V#01}T'dDmֶ( T㿓ّݕU[(�-JuQ oq0VPxiTOڠA{ߧL'k!S1�aߠQQ /vFl$Ƹ znP(jBz)?�؜cKW#/F9{ b9xBqM?(ȋ;.qCA|T_i̙M;``i]�)/PAn�:R9ok%1DpO5]'F)8ς%M90'~GOsX%B8\p峖 @G>[氆TG8Ha09"]⩽tTp/^ފ$ ~-4/Cs!_pn$Y)|(I[(;mbܛt'}nn;FJxl .<ATW~qU`݈܂=L޹#z;zRaHKE|Gceۡ(q@ebB\ǘ� 6aJv#W"pZ\kPTwG =)F@ Cv@,aqSyuE1tmLQܷ?b t*97ق<>qs@šB/pܿ*z,Յc[ԔS�a3ԛR篬h10a_ĎNleudENl6znn5FPNzUڈEMQ /-W ֮ z(.!64">A%JII͍� ln$)U<Z5αsĖm+XEpzA4tN7x=ξ[\w2>;;aV(=) PYvgyC<@E'il_1㶑f:3k9�-lkpM,T=�U�>v61\/MDlZ1=}C#*)k a^[gIvTe JY=.s{ƌƑQ(/|?ϧ� 1y��lv$ M!8ǫ]ilhKগㄖ.^р cuC}҆cnJX>ЬP;03mD/`ΟTO rӨ�8]~<F 8^jOCC qҼް/Ê4L۝3pg OjU;nHh-W_/Jc($qeܘ��O'cA󾁢BSB<{hO@CH)bde[R%uA`V FOH-]A]40+4lNL�*rskcyj_y=03.�aw5`=j@qA<a/%+qZ, ۈdlۊ¼΁4RSSԄupGm[ph(w L ^|xb8]/J(7~Ʌչ b�<倠HUT]0r<g�CkTÙ kne.;%p8\ esc)0,j"Ԫ2M@K+.89ǡs"tMoɡ!%ZW/"**Hpދ?n˜jnU#XXS\ )Xr)kqX\@♧~ 6go܇E c(E8X;OLb'X}\\ NoumݘScmkIÙ8bQS:�k_Qt^M2h,#8af,>>!@P2v_[Pyx#(`C q -0m>\?GC\FM#@!(ޕ^yC6r\@9Ok: F^y?51߿�cpNGD< �sxAJ!"3!H P��. XsGvᅩo Τ6vSCeE?23 `w$rqa*c�cwݝBR8,W{XQ9o=e{� aKW�+yS7sAqr~#0>p' q6A�v6(y@^Jv&o+-^5xk @kN0/pobx@s |gQ;R!bw`b5���=;$pREc ; 'MF;B.МÖpkw *cwNc"K~~*ңa)`1�{:C(R$M=iGWAS]sүRP<�((?ơq)?PA "쎽'�(�ұ=;4n˝ $.EMQ4ĂSc]I eE[䅓� D,A`鉉8mNo�@:<< M!lϠ'eM(H� ˆ#8ܹs?jGSF*F$_>A\?G|dvǮauN�8�aܺǐ! &FSz:26<%xѩ.�HVS)W~~#X)w 2�ցmHO��m{O(,@a}gjXslӉ{:a;<Q+a`w*SF-*cqa>R:}m</M7)3�powN2Paso{S6AUu mїHl=EWvГPiNM%=~QPP 0}? >7`D�@W_HM/q( Lޗ�k `__oÞw[9@ Fx#T7Ͼih}9W5^޶���-H)dz�lLbo0+;a:Z�rfM;?{^y @|?0B\�[^54±]djOC]s\:" `N\ނI[X1TFK[ޛRcJ+yqh0%P~`[0wS op!MjRK�'mw{ߡ* ^?GSp{RhPrhF{RX~;֡׷�A2_Ys_sp˝IG?<[: }]xyWLˁ̞,0֑@.b{kȉ^b-!|{ߐ=.,SwڻiHgE:a@acw)m(l^KOz{CLAS̆6vć7{D /,9SpGԈspkvm6q�}heU!6;ecȦa0ca[wc#*H> H>5ǚ7rz[Oy_4�~c3_)N/#JWjSlHƆȘVYqh&�R̃b⒀R|||AQ3:RuX& �q�_D&L'8/w&a4f z�/փHAeJt&7>Q>r F>S�~O>7gG �piᝯm;f 87ܙVB) IK;chAWgK7`c{ܵevTX=c<mXc٧&L�>sBGqK`SW (ܜ9Mև?ӈԊVacKw6 (?P oaݴ9H �Y`g6=(O'LAL~7#lJmTp7TsW< :ބ 2`@fd0(/h'&3ߟ<ܻ�M%�p�ؽڻet9-߯; 'JO v=�qא�TqˇC>zKALj a[WBO)>Q ( 18gn}a{2^v0hlx~2sAJ>%6`:x;9jsБ@uu{MHsc ؎޵3zz�^ir7;  0+J I _xod �Y O>zT>Ϝ)/� lLsBn཯wۺb BC(ğwtݿ|ށh@ML&^Ik<*O>:B"yY}� E�`>'"-({˰?.oTx5Fv`j+`0{6:C^U>�zMr@֭ j�2=7ΌM* 3g@+7a<wx- E  Nݝ/a:F\Da-&>G6k_jȼv7&8:��29m뭝_$5 y9�6t&J i r$2t$ pA֓i!V7 =xϯ?nmGDWgC*4ߠӋ~ƞE>3vkȃ*�B eZUgܓFo*J 8({)p!2A _ B@`0Gq8vyH[6u �&PSnу0^yڝJl0б>}x.Ru L;ї pp("�!aCG?y0z2* !j*Ň܀=iBh`OF{>?]D*ndzݞOZo*` <{P4Ȋ�Gd>6c1/ľ~ܲ I`Mxif<?  ߇Eܻ aL5 4 8)X�C07?ӹwcְN:L�EQS4yw)DPwέmD0܆"^˭qGuiM#`9!mjDW0Y3#KC OOUS��73N 5� $g)AȫtQ hKZ՟W(s�TFms;b*1*b3)࿕1-WTLp_w= ><;$JWM#K-{J&)`L�rtBMB~Wp qw޽w5�x�TUsb Q\(3Lǚ;±n ~wo:Lf Pa]}8?W\Oz !?�` H <{oHA~*R0 |**Cwď6Ʈ$TM|)?U~(z or|°RaQrhlW,MO{z4eٔ{t:v[eH0ߥvx BEx�!a@?X�{2Tud8s"ןP|OHLPtc=v`ᕶ!y TⳒ﷤{ *\ݚ aN_YT�!!҉}Pu9�ۺ12k ;LP7 QԎ\=+ Ҍt ~P( mc!4m?6 O:[+٠]wyl=> bE�=b?SzHs�l)0𳍇AP=<sp�?}0xku{dqï-@ AS6 eO CP wSd "d&�ɩ�Y/ #,-O @n'^B kxh n|v/7m}s,(T\9GTjnyʟ)nSUhz�|ݱG((+ѝ;MD8G D`{=/ysܣrNБ]@@ @SKsFJj}r,a&REՆs %ƾT/exg0L|x)așnƟv#Gh\v wy$FR<�xnL@O?0Ӗ{SdA@vlK Yp hnOF$zAO@(~?z@G>V\fč(AHSmOʢ wEѵhs>7ޓ#-7xviihs�@xizs܅@D6l@z \A*=W)j|;GEӮpb67@HLqǦøx�jZ�~2XQ�{OZ֞W^3_m՟L@;]NBp`%)OG9WR5֘np Bv';7ىh@EDWRš<s~e҉J ±./7mlOW p�"kF6`,l N&]'VR9ɟ~n2?`[3q`6ՅpQ8gi#icKW҉])u?1gf@2O}=ܼ|2"e4@6 X!rO_sD&!3cc PK0*89 IS;v8H H/ �fbrIM{7לBz8TQ�1J0'.o%~r@D %yT]=AcP o4}(R{<z<J0CAj~.c`*X0<7#9/pX,E[ v7kd1~!}>}_V+7��KcHK_'@ X0 Nˤr*rPKBFxJ}ӱP~ n_ S\Wi��/ҙ0;_m2+PE (%\V~BՅg ӿ"9;kg]n#kDUfʹY bßC(q [PT{Qt"cxb܀�<_;^|0g�ȥ?dͭ#T@Vq,�1%/JF (0%P=~ET}ka;^x9}[ərGn�ȇsDF } w=6\҂oCc0 VcXՀ@oG�=nʬ�͎y#z�1ufY7PLG*?c b"u7zSW"�d@@mJuXJ7Sտ`R~qX*h02f{fL�HP}Ɵ~92(Xbtɞ"dpO(Zրȵ`Ƈ3\ޟx])$�(S,[7t\x@@u'0 ǁ۰U%1N%YBǪF# @�zk۬/*(` /^B0!Ю5'n 1DA!x%&NE:׳[,@)` hñ~fֺb9̴T.FdžplDe)?Sr~CQNnO8{^gWSOAH ]ݐEǏ ݔE*. ♻eg+ÍWLЯZ� HH}/OH%-UO~? i;֫)n_E.J�kG;P4s[Bo�[jxB!۷_Oc( 㡛n2zڕ)3 e2w}�rRǻFSUSO7c?*RH5hQ63WqTVlѤ 8M,FQ|1(4,r>?Xcv""0.}ͳU� 0�+ LQog e\�`FcYo*yʶ^ˏ`wc_ЇxU| �Z�Q+ 37�B1 Ex@I*ZjuO~q~n fw1jBh'{o7|49Wp 9JҎaO7Ɵ~uWv�(zyo -m@N?#-Y!0Mc|@)\�\QVzy]_okhfFT~_Q4HwCs�rA  :7|,]LRD@p?jW~-�XF:s׿j;দ+67W� t�Q�k=175":=).W9@|˸+_pƞl"s �+KZx|N;˅rhJ98rO([^7gy g؅5� T]1�%' '!"b}~/ooƽ/z%� /8pov $ c _�?#@xV?;GTm��un A.?3O}VYv".2AO0`wovҨQ^� ВU7E86`[\` �QbW7Wylϩ% @沷W'(X/T:3U/��8S-?cw}^}J@a\ Q 5-95p'>Ɏ9g-4јQ'W:xy?H?U-|_D(fw_B((0R Q@\+�`}q=Um$�N B%W'<]XdW(A5Y~b`sA|'<oU/`|DAE=-/CTTL8Xtu|T=ئcxqH oϝkgō�0~o H N}.�K6@ġo߷Ig I�<o`$6�ĔZG%'YEX@AuYOWtbdZגּ�f b�P׾U=E2[PMw~:7ӷZ{wx ݖ{Vp} �l3<o y!ԯR}ǻӯy35 *e8~[y1)gsg}WyYZ H NP}ǻ5]H]fFAE)~-8m6#%}6/`hA �'(g@! +#S3Ύ矱Nwpte<x?z%�L.-A8 ԰xk߬|T7 ئTEu'O~`9o5ŗ�0@XJK/WOjj]�`rT8d(*Dg:/?G޹5=i~&`@�|娳SOj|F4+)#ڞw,;}y?Dp{�0}1l [ur%λ,ce\ [/_}n$?)19nUap#�wligP]k#�7V  :4?e'/<'}0K{oznT| �+dEꗰ}ylIk U5{P@HC6;۟ .nXOXYY'`v  O@"uؒNbX˖t 5.\@s 3;)=� 1nw"߷e߾nq i%�TW<{`SEkb-V'LO^ZM-]pl7vEu=b+D_to~hߵCd{oyG&ˠY zg. Yl1caVP58wAArLqwHbw+xm&:ڲY+oXz�M@E9)uTJ y+VP˲eT` kpz8�Us-#|Gz[^OMޤ N,Ek{hleaq R%�y@ɉh9Gy(Z@цjXJVkmXc3ZG"!AU;oȂv` l3%҃ $DjG.sS$;`w/2ϲ*p�MrGr�@$E "\"-Su0 "�-8 1aɀl*psSK� (Cvf@Pr@$wZg9;E܃(s �U^\i0v*89*�Tb� )U(� ~E+&Rf3r)sWJ"E)R$�H"E)R$�H"E)R$�H"E)R$�H"E)RU�[:z s����IENDB`�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/Telegram/Images.xcassets/AppIcon.appiconset/icon16.png���������������������0000664�0000000�0000000�00000001472�13262451251�0027307�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������PNG  ��� IHDR���������a��IDAT8}Kh\eϙL'$$M)iF5.1MXAtEZt確JmH#j@K^riɘ99774T>{0h@ۥ/Y}KmGNQsI˷n^<w@Lk>ox9WF~ *Dֳ17K*[=K5x0ڠ ݽ={z>?{tSor z牵eS[Zj%YIQ/ܘI'5?}V6t8ΡZwoj_j\^$lnU;t<fMbc1:3ўF:S$FqeHZ.CpvOa#]V0MRޝD_ u2 ]:JePEIAZDԪ=]+5QN1%dBI[,)RIxc0ܕ:".8˩yִjFl]-'M6O]R!! o]`lHcm/KE8p2 # kR8<7#Җ;e.ͮUYPN*ON)KkK^`19VcK6P8-P J ;LJİK*Jv?1 Y "=$~nGogd[ׁcQ _-uW2#_qg~6dGԱr˭LNJ?$zh����IENDB`������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/Telegram/Images.xcassets/AppIcon.appiconset/icon16@2x.png������������������0000664�0000000�0000000�00000041437�13262451251�0027666�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������PNG  ��� IHDR��� ��� ���szz��� pHYs��%��%IR$��;riTXtXML:com.adobe.xmp�����<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.6-c138 79.159824, 2016/09/14-01:09:01 "> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <rdf:Description rdf:about="" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/" xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#" xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#" xmlns:xmp="http://ns.adobe.com/xap/1.0/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/" xmlns:tiff="http://ns.adobe.com/tiff/1.0/" xmlns:exif="http://ns.adobe.com/exif/1.0/"> <xmpMM:OriginalDocumentID>xmp.did:1ACBF4C4D24FE4119247E59A0A0429A0</xmpMM:OriginalDocumentID> <xmpMM:DocumentID>adobe:docid:photoshop:3ecbf9ca-0639-117a-8f65-8cab695dcc76</xmpMM:DocumentID> <xmpMM:InstanceID>xmp.iid:271b741f-ca3e-4132-9d99-b5c2a2056dd2</xmpMM:InstanceID> <xmpMM:DerivedFrom rdf:parseType="Resource"> <stRef:instanceID>xmp.iid:1BCBF4C4D24FE4119247E59A0A0429A0</stRef:instanceID> <stRef:documentID>xmp.did:1ACBF4C4D24FE4119247E59A0A0429A0</stRef:documentID> </xmpMM:DerivedFrom> <xmpMM:History> <rdf:Seq> <rdf:li rdf:parseType="Resource"> <stEvt:action>saved</stEvt:action> <stEvt:instanceID>xmp.iid:013ff985-91ab-474d-807d-b60bda015c0b</stEvt:instanceID> <stEvt:when>2016-12-19T19:15:42+03:00</stEvt:when> <stEvt:softwareAgent>Adobe Photoshop CC 2017 (Macintosh)</stEvt:softwareAgent> <stEvt:changed>/</stEvt:changed> </rdf:li> <rdf:li rdf:parseType="Resource"> <stEvt:action>saved</stEvt:action> <stEvt:instanceID>xmp.iid:271b741f-ca3e-4132-9d99-b5c2a2056dd2</stEvt:instanceID> <stEvt:when>2016-12-19T19:21:09+03:00</stEvt:when> <stEvt:softwareAgent>Adobe Photoshop CC 2017 (Macintosh)</stEvt:softwareAgent> <stEvt:changed>/</stEvt:changed> </rdf:li> </rdf:Seq> </xmpMM:History> <xmp:CreatorTool>Adobe Photoshop CC 2017 (Macintosh)</xmp:CreatorTool> <xmp:CreateDate>2016-12-19T18:26:51+03:00</xmp:CreateDate> <xmp:ModifyDate>2016-12-19T19:21:09+03:00</xmp:ModifyDate> <xmp:MetadataDate>2016-12-19T19:21:09+03:00</xmp:MetadataDate> <dc:format>image/png</dc:format> <photoshop:ColorMode>3</photoshop:ColorMode> <tiff:Orientation>1</tiff:Orientation> <tiff:XResolution>1440000/10000</tiff:XResolution> <tiff:YResolution>1440000/10000</tiff:YResolution> <tiff:ResolutionUnit>2</tiff:ResolutionUnit> <exif:ColorSpace>65535</exif:ColorSpace> <exif:PixelXDimension>32</exif:PixelXDimension> <exif:PixelYDimension>32</exif:PixelYDimension> </rdf:Description> </rdf:RDF> </x:xmpmeta> <?xpacket end="w"?>hy��� cHRM��z%��������u0��`��:��o_F��'IDATxėTW?fvfggge,WPX[HhԶԦUhMc%HZmZӀPicBV d ݲ ;Ώ{e@Yd澜{9߫Ds�J)=\/?l7OC)¡|!8;3ǎتOş�M3f5ܲ5j/(bA�A[ .ߵ烾kJDz֯={}oZ[ |$hS zDh®?/A| \@}B-˿`+EĄ"xl uuN@rFǧ]ɛ: Nez5G^kixr?3̳~1F0_|joM/g#b퍙Rm־i%&eh⏆;=RtՊ4ErfP Á"D!j?s"�Lgݻ'&_'Fa?"4[<LL[N&4-{׾p)O^+g<6{(o�yvs^$�QFP|=͚Yl)O Tnmdk[ EKejł$+ֱj^O6&qbT2MPa$R­I>ތ@ 5PF]N"!aze,k;r,mMSԄʡPDl:ȗ Қ(GN$Φֹ 8vVA+Ų4׳|V3"^1"Vmh#2JؙR0dž!(Ya"""s<sL4$q40Rh0VpP&Wdfs"AŪs((0 � A%Rq�bLdG_.W#N%4-VDC*e?!E[C-v�\8_ 8{:�*Z0kh)6+*8_]%}F-k:z|DֆZZr)&ƄR$]w<<PXk0B~nS1Vk-Z%\R]PD+hΥސ&q݊aQ "\~95 *{Έҗu8!)FBAGCy$=Q!Fb83 Qp|R+6Qtuq<W4}%.+Gϡ:./GtK}ʨ#8Óf`V*|Q:lK_ː_%98J5XHV>GUr0z:7KO0\(U}j?c5IrbߙA]1)pɇ$8_aסWԄN˜[3ݲO^c$0>.5Ji|#%]_|oHwsd 5pWs|}c&9! An|BƋA,j5JKQyHU\j_Z-K\1QtowJQm?船*։X~#Q�QtUAwO'}`dzD:eY޺=>먖:'vTZB".d\`<ٹEYUm.5`"D̤Jip<.Ȓl Ӓ9QWkigo[f-$Xl -nyEzޞp�&WiUT2](Vtȹ28�v|׊����IENDB`���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/Telegram/Images.xcassets/AppIcon.appiconset/icon256.png��������������������0000664�0000000�0000000�00000051146�13262451251�0027400�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������PNG  ��� IHDR���������\rf�� �IDATxwu'}Uݓg6 cwA$�F09lK?~eg$+X>?s:9gS&%HZ$e@$rZ8:U=ӳݝݙYNWWW{R|Ç>|Ç>|Ç>|Ç>|Ç �|,=x8�cT"R�p� ſ�`I)M�0LJc<@A-, ߲[֩-]΍*5Ĉ+a (@-I HRf2XLOK)&PߨԳ3' ZԈ6|bXV6sp @33;ܽ Bk97б[Uma*MB@@syI00n&?K i[ɉi+ԆOk#gϜwH; =/CQ ZC=g%s.*aŁu=J�RX ),@X9^.i7q1�A MVjzJ˞:7uto9>vzxB H[c\}M ozڶBܞWɉqs84 lSwҋûvC7/ymwR H2!M+qR BZ&LߞW<tg߻_ZUqm2"i͗vkwZ% R@e(?+ 5� ̌=uhWbOS3_-Mݨ `\ӻ;h]zmKӄ44[S %�adɃOS/ԑ7zx+>XHS[ol};U4!, ] ?D63PT6uOoLc#5 V o^uk~ιгLP A\19?7JyZmg�U@tQ-BB #FLxGO}適W +zl ,L -׼{>[-WN\aBKCE|K"@82W??C4G'j=F�  nwwߍl~3g^@j,6t_'ecbhj$ BPVvji_ E =hNAAh{Ww>r >/lF6wh?S_Jk=z�JA9^w՚Kn BM�R`02'gr'? /k�<\ӻa[IQ"Y9Rg HQ|$]_iX 7?ҹhdP4<xP/_|5/˿EDqg|k},%i�R*W_zϼʦGwCIl@hs|/|?"یV2H z퉇OdO<V1gt7~ \}Uj!iozaO]o.u?��X(ZKO>6`eS xˎ{^%}WDǵ8�"l_~3\R@"=^r5&'g}(}$]��XKu~2L*Jg��<_?5_@ʈԵZGNJm;w/W_2XL`%3� |]?ke>|x@Y^#eO8T1-%V2@5/>\ݳMd4up8ג{^xcZ*X6|ogVȥ`Ko=f[>KT^�l_ O C2k=. )! .,zUW}+5 b+Q}֞?|ߛVjڷX8}c_{8V62V@�d{Cǿic%AΖXh3� C/rΟ<XhӶ+}cAZ:X8r˞:p nދo|7й|A> ĕPz>r/sbo^hH4* �2zeW~mB> S[~sjt7Ѡ/_#2�qu|Lij_'4hPH)�k`țЀ/a1� #\zU|Tw4s`gbWu11؟9vI�lK{?cjs:;IgC=o+ B�t#'gNݍz)Z7:/HXUܣݛ=u`?�pF7 �2еic'sٴ]>Q'|wgy]<z ;E~;ܽrn!-/w194:Y05ۭLu<} ǣMzKb׏f&'Q/-��k?ۦ+Ju|4$BKCpׯhs+x[z��îOYDS|ȃ M5k6wO3�lwm= cNHCC q9sϢ=�lw߆u P ,-?kƅ4u.z}ءun�x8_y$|GhxH@7ߒ|̩!K]O ?6]yJ, G\q ~[|0� WzG?cu)8H@{ܾ*: o }DHSG/3gީCu�X0/|=<idk=>mLzVb|5fb�d}VdS5K iౖ E z<�]tm>?>',W|XrH@pÖ^gS?}5ԔH|?ו.>*X&"[vçQ#UV*@N›^d쁴L`$w"G-$�;ߖnXгJxC|XvHSGhݹ_F TH�O}:{�rK�vݿmʯ[ٙeއ<Q[;;_ޣX-ǖ]PWݼ?Eb<a{Bm>x� *�@?)]]ԗk>R�RP?i `rI�v>aͯ|葉qG2ڴLNe ,@D,$)oBK#{\@�D;]|"}0 (״ [D^r\gMm]ޏclMXnª{~}|8l`q'DZAK-b{0%IΠy{Kp9`�Ѵwz/.tBZ3€GL-! H!ͱU|¦%ӥt50RXL}Z0D֔rUA'kX,CS:=gs:㐆<;ӇT, �w޿]Q?9}4(Ș%6q|v6XF{#0486pOO ~ܟ+RG4?yKd X@,?zͅ|}nDruq(:v,nI �A'/+Ukj J'F>~Q,X* @F\usHCk00=mAʶ|Vlm" bnP8~ 8k8�J9=6>X [vK EeЖ + BY6ܭq}pEg�% y)1�G �khy/< qTY X  ծMۢ.c s".$Z 7ޞn\EWTɉ͗~C4qXwޖإݕx{_E W؁?׽x5.-}xYK"qǦaKk� K}''SB[W5وPK�<䠹Quk~>,!178 #`6I2cTi"=1hk;x pL@hqchӶ˲'TͶ�nycpݹ[_aIhDSa Q@)x±4ce!=1p[;FcRて9Pj3�w|Y>pn-mA(` `Z'�"!Ce`Գ_~cߊlj֞ٶv+AaXlߎ_QpoNĿzuM!)lIr/#LgMNdlaq۠6uuȶ7zaTX-`~onodQs"~Leر:{zucuS /?)D06gcD�DvjhnށF�~{gv=؆a� @0~]2Ԥz%X|F@Os�o8.!LsE3?ER7b�"�+P"$ H=koQ;ѣhZ @޼}2!a%mc4ڵE "~FB"d9rDO�% D {& -JEߐx;Q5 ne戕N8FDGͭ,B={}S 4S"]wA$YX h3�$H v4b=R6ewܛx;_Ps�oS~9#q8r7Uܴ!zpYg�uLQQxl" h},]I:$m& )$�Y4{N޴j?�ھns+oT8}͹6qD@^ %0Пr.@r`:@9wRrj@G+y#mo �\ÛښD`|k Mqg*\vFB )\�?1$$x2CnyoZT F~XyG=CJ{7DWT"xWo;D0sk*j27js©`6, (`dҀ1x[cH#+vR .bZ4`#ػ}0ìSFΠQ. 8nCwm˚e0QnV09kZ#T*0pt< Ò|Pr(OYU`d3w i@ش9 }o/2�pV}&Bڄ 65p㒎*nJ9#h4g,�gժÔ `K;uE% ;c,@�r =ݒh q\>zp}n%l2XFY L8#XR)@WkB. 6;:E�ΟBu2@,& [v!�:Dx1 νlW_z K;pWOwvDZ-�N>{5 3N %KH*z*ł3T@95(G*CIBA,/ ] .a"8vp3; -> kf XXUM&\:Ll ~BH M14i - 3W (0̭s Q@_z4$,ӀL%DA\ ҹ6wXh;a�2fJy^Tejs(65 3 Ed٫,!acG DT_NLf-TPe~PA89K O 4SP#1: %X DghX( �v_x1LfˁAnM1afg@9;6w5=qW]""@+ ]�q%XfWeH@ց$ =_ @`bv2�{.n ^'Zyum!7mh»67utFyV[NH!`̞C uQiW-a3�VJskEğ{R`gK@p IQ[i-K�x<4| `WC wtqnk=u3n N1@LJo9 !fOFщLOn}(<(|-"$ T j8JM$iPl⭫ךۺ2}үYuX Qۄ+"h 2hDzg4 N5 8 "lh=8iɂ�xQ r -1L<Sj�Ec^s4P x�Z<6D>ήKj1!kXLarFu9!g 1D8Xa ̘Xzu~݃ȋs` f6&L@[<,t! � sH 44qrXȚ #⸻'m!B֔H"˯7`h* %įr&*epd<0;qRl!ωל=KXZ@ TK iןw>lZlP=Rcę]Fq_o3v-gUǁ|@OJ318tF-D%/A ѠbRӹui%9%F@=T8w @p MAV6 @RlU cfBy.d @P镖*g(m­bؔK5ELcd*Kxtw6!F{xHrN>`#;\N4;B[kN;i�W!,m: ljY�HQmz)?9D`I@3zuS w4ᢎ0"9>330B*[%$baݝMP9[6l&55qb*�8/*s}.wOI\�<[�S H)"-uu<:1@Wug�p=Vpۦ8܌FQ`|:`XSi%JzܰDs41(U5(00 &g|\]t={Cȭq@@CP$ěG_ b�ʪ5<jK�2/5;ۄ6#W"D910B6CyXaC{C{+GH fS$Gϧ >i,fq=_8�$ ;H 5?%D|K� ll 1ی]aTnyiah2|%$:XjiB{+#hV;r:z! PAizPY g+;BeᏳgeL` @mY R!��W+U;, 1ܷ ;ǰ:zSkǓY Nyx)s|ִB{kn7;89 =5 ŋ}\²UeDL@ s</�15�}>7~@!\Ɲ=ٰ\|r=]{YDS)7$$֭ڻߙ!5pl2\4™'p9.)(e 2!�;N4#xm5 *Ƃ�(KPKUܸ!{{qYWM;,V;Gbd* kω bvhGUCI C3w(&y9q şm~Eğ_dD,|V& ). HiEWXRdsBZKHׅJ9q8܌ǰ =/g|@O2c*"~!%86vB{7N`Znl�� �IDAT}�  ۧ(l];#0Y&-!͜$P%XfEϭ BJ5x%KpM΍m}p O1|y)pMq4պ!~V�Π]rn{B2IF m"W ƫj{B$�ZD?TZ茨Ż67c(B tKn} ɌRg3GSsbp ~lG²WJV& AJ Bj0y&7 "%M"D|̐pyWlnm�'tkŘ6Y.gd*Ds!D(3P%ɲj|Ói(Hn/9 ?\]|^ܖa؞a _Z0�5Hj037;S�k*nϜӂKˆ|):je=)dkѐMq ]WOFai d Sws=^v=S$+*9% ƃ@RS@ᄋ:#xy- l ao!0<[3ܰC><F86dքʙ3CO/"1@bBU={YG`3 KX`*1CN+�H-O pGo~6\.xkǓY OkzܐX3t!r <;碩;Y&]C9K糧\p٦O�BEH{|�z6OH�Cƍq]rMlIws`D i{ gBJa ֭1xr}s_\ݳ=Trw.q6G1yO C$[֪w#k Aa!~#zF7*!DWK[#u!82J@N=']; ]w $~*]Ⱦg9m1W~e"̵ܪfЅ >c $P޾Ղ_Hc kB>wM[-"}ci >@h1"w\,=ˢ8;91J@�eر6hTV.%uu[)TF86Ɣf"y}tNK$ ~:~ RdOpF\<ݗ/*�PX=.YHu-.D�!14.|c(쮯J6�8>YsKFV0EQ]򼖇"}*"Hiգ @8>յz`)L$y!6t4)l;Ӭ^^F.L(D�_lO(suTRH] ˲5ANxd}S6�Q"p�`rFTڱ_;∆�FzNY`1ѽNu^^:B[Bː\JKHa1W5kp mQ6 CSiL�ƎҬN8=@R "OHwn}򤟯gn_:xJ>`Cw>m E8�B{Xs&tVOƯ|::i!_\#;4g\o #ewFU,Dso}T. xt7 _u,{K!9ZļWJaz*ۣ|#1\nn5qXNQomOk&H6X KH*6Ǡp!;(w$ iᎾCA]V:fN-s;)v9wY;Dr]%׊[i^W¼U�i蚕O+k6׵ 3O80z{¸᢮(CJn7Q7F2MdaU)XJr{�Eg c A@(SM(uz�H)2)J'�$ M2R*"Rֱ|A*zpsO+uF r{f[Қ50<z-Wcv ֬+9# @a)wM/O]6ٲRݭ;sɴg\OZVy�`JӘoG�ƠPA@X?~]q~G7Vli"d #X3.* "{u*p|2SxϞz)g�pwWVCʥey4ɡ -hH05ȡB v ƅQۆzZqn[Af%})o&:'hk>w KtF^ 6"*U\.[/x ]qNEʨ ^Y?ǻ8Dr"!�$�˚ңT`A ˲`/'^9WqKoԂ޶0Tf3+?t35c欟mQ5_R{fr'"KSh(- -{^<{*K@ "99 !+b! �"33"e}E߀+P9G@ /'܉)\:[7all Aa0*$0P3F2m1 ]*XpE?# Y7*d�* 6"UED?=tF&G`o 2/B4GN K(XV r"+p$%ҖgNLItD\ n^k64c}SHc!غfX N@޵h ӳ?鬝ƹJ,W,f wl}KN_*@T1'`o 2/\0*M($) B 'M#芩j]3n܆뚱&n'dZkY-r\C{ַ W TN8<TB`v?AM^[ʷ?⟿RB4] ZlMT"c-R;a!A`\A+ )0[qر WkB[XtT37DPXoJxg8 CH\>Z ΢2-_D-nSGf!Jt~M2`?`A*L(92H"0EEHQ�)0YQ<k|5EJ *XC@YO�tSbۺ +W~nE }:C6#H=k`7>RzҜ\A>2<r)( d7B7 qW z!�D.DU.Ɇp2B9t*v8$E;l eywJMOYCR1��dAu p(ie %$�6gS {ac/L.*-s ̡+^ q!MM*�9w#cWQ4Dk,5m+'RpN; � R-=[JYNw(쳼_e*0CX3' ��i=*M=WT{a)A xc$ ӪVZ"{m.,}�;# [?g�?"*%SiݐGPã5&E ׈#G�g �0 @9vꄕN(BeBe 4 ttEYw @JΖ:W^ho%o4 - (/*1%K/&%}V1/&#`Bm�RL YC睉�%eX auk-aXR.bh܃3©D'"PA(Ou=>] Afӆ9w4W2aV<)-c<vLQ@xc8d}{ m%{PH Y:pgIf<s[#p ʕ;(< ?qX)慅2� @8?܇ 'K r�0aI;=iҋ`!KQT^~iy9gWE\9t4Xf e�4Nsi04 S^׏P|6�d ܯ(_WI]*_r a6 P�8rLOHΫV!%,^2ƒx0:6 :Y�#3Nf0׻ !1'xJc|;ԡ6r<H�BLGo({D3d֜wVb p#xyiLd[a|8Ñ4R&xE}$Ay\s/_tHLLpfXT"�}oMV06qp"ϓH KYBW9tZO Д-YpFxghtStn~/'WYyj} 픻T)*#Dr|� tc��80)a�5&؁?E:$g!G[8gK��=,0Jr9WA9\Np؛Hb?@<�G)޶ ٳ4x>Pc$t N%$%%/AS L4\Ӊ0*-0F88svOਸ& ݕ@%{Y$2L_M a*~ԾݤhjeC!锉#^g P8C2qtprQ;+TFHc0;hW0W}�9ɡa\IM�` J^y�),7SXH>8s|onXPᝡ aRTtwc�u=!߸d,Kw۽Kf\R?23c˃˃sF3HD4pz,cl: exk({".FР3l`g+�sAs~(E6r0‰Y+a&㕃qx`|Ґ-- MAe@F"",<3`ʉ_m}/'L3�{�BLj^1=CPH[oxZJaZC`ND(vDVYcz F81ABe�#8߳)Cu*gH 8Mkbˣ2 >cMXv�o e:%/ O}02x4 #AR g+俭L :=Eu! x.u  c?'TjBc ~A@mpfCqT`Ҟeev\%7pjҪ_|Yt"j1�œG݁eA$ #is%Xss&7:xp1FfbhtojhY%5oXA,G&�3̌.1u%?1®RAH{sjnU,>/_)>4xPڄY2�ƿye3hoyj1�@XCG} 5PvVAJphRL  @Xaxu`4Rp:C R[`*kI{CQ/{Z3K!&&}?]T+D%4/V& !eXm Dnb&I_}f0 }``>$.߰ ] : #pɂ%͓^Ώ?zg C{j� vE =?yZL ׿YHWNi>8٫=0o`v!?80XP]ML F@ʰL! (!~J%~=* @�0EjγOb+ *g?H.UgX 3`J='N)MQ2/B:~]eJ퓼*a0Ed6� Y)�SG) d�6gҲ~1gM Fy{$"8NOC ( $XWUH;_5L!Tj i/1q]/f"BX`rX vؔڳj{,?W@ !\t�,,KTl) 3>iBUՂc%όh?:05al[l wS2  ^O P$�7ףf~��W S?zv@Ud�6 *}ߨYgPÞ 3F;N da!px<y':TEE� %BJoz},_U$%2CIM,$~�A{ؑU݅g)|@&�3GH .A΀ጅCiQ~VA@ 2J8eXJ EU�>ûpl|fYUY f{?E%h|c餖飏&ffc9X�tiCup#^10%PJx ?{|_=0no_4lRB0H$NL|@$5XVPÁB/]u%� p[Mg)M5zb4p-Q9a.!!cXiCl F4Lg1|({h8\ VK�I\i{3+-<Y;6!L*m헎r4 N J=ӣ`UϹT @`04x%kR0"$u ;"X) n`&{2�$wy|+T+G1;GD&0nr|'qoXL k 0,'{_Z@=�y*TWL~=9 /*4 5aL @3 i<r 1VM>tl(۶^95kݧVմrFLj82aN,+6ہ?</H>1� o H'/> RH(HIݚM{RF:׮~5K=?wVnh0Ĥ.ᅢ?}r4S VO3&&MZď;æ%K чRa$€^FX�K+ Ǧ2'ql* UQ e~N\u\mZ`: 'l/Át8zҹk-K725+>{b y,uget4|[BܰAcrVNՏ�綛fBC 4.˭nWSS)<y`-�.Y 9D=I 9BDh)CT:#xI@K\ߪ:گsĒ1g i)M뚑Lk�)|Ad, ~n)R a{.&~&EQ1~tp#I WmlE<x0gYk0ÈJjM̟ceS'Q `ICiZ493.NR ǡhVfq|L00=YVJQ]Rccש $v55#Ҽ9:+6�i_)B1h/<_{Ig`$� @F+]Wk<9GTL?2Vbim3xA׵`UpFdgb?BNI=3�{_r9 w" ? 9)ɼNf)ݣ*ܒ̝!:PQh��IDAT1⪍mh@%/s.yx_`0ҏ?Gv_2r.�HPyrΕ2 ) >EO KG8 b +x$^:>;]48'§:cawy? !`b([n p>hK7q8gco/!;o @UU 'xb 2@ϪڣA!*XZǟ?{AXuogVr?ma͌˝Ŷ ڌLgqo9sHˀw<ބLğCQxoOɩ t I843ų&Lk?� Ǡo?}ğEy\9�h߁"35J2aeӘMUu߻|.7fڻ#nI- mb?�@ @NӯOlE*�Ծ%KHWC$\:x*"yxS_%3 'yq?,'󜅏!}~=V OC/D2@ �Lff&ezT/GY0 e$$r]>(^[QHDc~?&DTkٳo8>8#HQ�e$gb Yy51r?Ч6`KW]PN}H;)g1 J;h h?11~\ {?jV5?;!qmH¬O(}~[%Gݗ/\u&~WE?lGScs0ι~t? �R8_w55ȫ\<@M=KA�R- $u=7y_@?/WRu~@Q_{#$DaϚ �pD}^:7"' H! s@%LT\;+#bi ]jPoPɉԗ?25u޿V7� !,{z-wR8VˆV?<)~28oh@g :i U9>n1A\ɹs6QZ̿,OXQdz~3G0�i믶]QRMgxל?͠qIe<Tkf~8|:4jB=1�~j�nس5oTz.b.&P58]_v,gw0OL4l_~'�y{� ïן)[ݻNFcCPd^dn/=THEEk%s H A&FG_ѽgnY #�l)@BXyyW_O͝y"[[8m("Rz"ҏW�Ȥ_5A2=�3 m7B6ؔ'�c"}<(U �HMZ'W/hvaos))<={ g_MIFg�LNCՋnΏ]WT݃ KR{\<WO~s3l?%һw�RS}rB@D F9�@ F=>7 ?ܣnh ��}@Bv FRg4s#Se.uΗ) g+ib_h> `g'& =�0!~q�S3| ;AD$0OP 8 nUP柁+ #@3�(/e#1�f�6<ON N+[o �X;P_M⟏QA E?GI4�vL 'OM([ ƹWp {\T�7-Y�~A ?#~1�d֝;U r-7EW %7.5o8(ןz�LAh\�8ɣ֩G-W_"Ma?bpp3=U1>?`~ÉN42� 6'#ᛯ;@>=K%&7%~5z&_; ~^ht�$ș!sme]ݫp XߕGYb`bzd$Ч>nx1|440+�`MM<"5wF>*Ƭ$P(q_Tuj M}|wnQwΜsjm0IL Zi(jRTjoUUDUm!Pꀹppv\|ڻ2Ӈ/ƀ^~iww~Bl~-~x �M@sO qfVfBn^>=ٷ-9vK8|y\(&�B�t^k[Z-Prpn0/iܐcobOQ(>�|p�@ uym% 5(98'@ cCo;g4$tKP��RFnǟHYhhZd7saxX7!}̾=oy.G2E9,V�|WO7U,8(%~ Ya�L8{7sPDQ&Η_WNUWh/쁌@xFv3{Zfv]H1%D)�] dRrC"q˟.,/BpǓ!o  ɞCD�0}H;.x]/^j*|D`87샿~{8WuȟK)FJN qc,GQ"Psv޿vI(/Y!.  @U.ӟ_'!t0,0+e_k?%x݊bUP1��(7߶ M뤓(P÷}={�ڽPgP(d� A6Zjm} s~̪žIP 0)ӏ9/?e�JB0 D(W5~h뼼z$#_Sw2n뾿;_^8$TOI,d� bPFa˚47}'dc@(ӭnu% 2F F�bzEfWPVY' 5X Wwlu}mo*͝~z5(�d�A Y#`_3F˯D]%\t30Uwg/|3DYyU.sWYmj�EOX!0!cG H${/C%."f `h#9(0Rm;-?xԂHRQM!><{Ϋޥ#.?*'z^0ԧq#B5f/uk/4^Vݰ Uƕ]gώ[\8)BeP= 2'GXPTD�DX(b-[Y Pp: :)qvq)91 o J6TO� ӇB�b,^7/|k_qKTjd`��257txmgn5T/}=/ؼ@0?p@' #K@yCFv7ʍ_gU+q̠XkpC\r$7Ί rjށg@A0言C BPf2V5YWnhf7ϱ5K(\;Ďq%tTa@N ˁq3w~6-z]ązc&3 ,�1cK5~:Vn[[\LKX`c`jϊ-%@ouU"=rbF$׆;{z3 `@Ԧ`Aڜč[T]*W5kXͪUbYKհXyB0LKR(2LYz&*H~8W=V\J'ɱq9vw@ܹ!@oyM$doхZm׽<~ tt AGW±+de5UԲDm5+ZHbVVQ+gF(PĄa`"9p(6 )8ұpI95:蘜'nݓScͤL 'A'Fz`N ^`X'B0lц0#̰"-2+7C<M0f�Ae@x6\;-'ǧNCii=ŬCज़<LIy@~0zYks`=vP y @a+l [ 5W3L&A d�\?k5AAAAAAAAAAAD~Pw����IENDB`��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/Telegram/Images.xcassets/AppIcon.appiconset/icon256@2x.png�����������������0000664�0000000�0000000�00000167707�13262451251�0027765�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������PNG  ��� IHDR���������x��� pHYs��%��%IR$��;tiTXtXML:com.adobe.xmp�����<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.6-c138 79.159824, 2016/09/14-01:09:01 "> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <rdf:Description rdf:about="" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/" xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#" xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#" xmlns:xmp="http://ns.adobe.com/xap/1.0/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/" xmlns:tiff="http://ns.adobe.com/tiff/1.0/" xmlns:exif="http://ns.adobe.com/exif/1.0/"> <xmpMM:OriginalDocumentID>xmp.did:1ACBF4C4D24FE4119247E59A0A0429A0</xmpMM:OriginalDocumentID> <xmpMM:DocumentID>adobe:docid:photoshop:e8fe49e2-0638-117a-8f65-8cab695dcc76</xmpMM:DocumentID> <xmpMM:InstanceID>xmp.iid:085ff37e-a1ac-4208-a119-0498a9c35b14</xmpMM:InstanceID> <xmpMM:DerivedFrom rdf:parseType="Resource"> <stRef:instanceID>xmp.iid:1BCBF4C4D24FE4119247E59A0A0429A0</stRef:instanceID> <stRef:documentID>xmp.did:1ACBF4C4D24FE4119247E59A0A0429A0</stRef:documentID> </xmpMM:DerivedFrom> <xmpMM:History> <rdf:Seq> <rdf:li rdf:parseType="Resource"> <stEvt:action>saved</stEvt:action> <stEvt:instanceID>xmp.iid:013ff985-91ab-474d-807d-b60bda015c0b</stEvt:instanceID> <stEvt:when>2016-12-19T19:15:42+03:00</stEvt:when> <stEvt:softwareAgent>Adobe Photoshop CC 2017 (Macintosh)</stEvt:softwareAgent> <stEvt:changed>/</stEvt:changed> </rdf:li> <rdf:li rdf:parseType="Resource"> <stEvt:action>saved</stEvt:action> <stEvt:instanceID>xmp.iid:085ff37e-a1ac-4208-a119-0498a9c35b14</stEvt:instanceID> <stEvt:when>2016-12-19T19:18:32+03:00</stEvt:when> <stEvt:softwareAgent>Adobe Photoshop CC 2017 (Macintosh)</stEvt:softwareAgent> <stEvt:changed>/</stEvt:changed> </rdf:li> </rdf:Seq> </xmpMM:History> <xmp:CreatorTool>Adobe Photoshop CC 2017 (Macintosh)</xmp:CreatorTool> <xmp:CreateDate>2016-12-19T18:26:51+03:00</xmp:CreateDate> <xmp:ModifyDate>2016-12-19T19:18:32+03:00</xmp:ModifyDate> <xmp:MetadataDate>2016-12-19T19:18:32+03:00</xmp:MetadataDate> <dc:format>image/png</dc:format> <photoshop:ColorMode>3</photoshop:ColorMode> <tiff:Orientation>1</tiff:Orientation> <tiff:XResolution>1440000/10000</tiff:XResolution> <tiff:YResolution>1440000/10000</tiff:YResolution> <tiff:ResolutionUnit>2</tiff:ResolutionUnit> <exif:ColorSpace>65535</exif:ColorSpace> <exif:PixelXDimension>512</exif:PixelXDimension> <exif:PixelYDimension>512</exif:PixelYDimension> </rdf:Description> </rdf:RDF> </x:xmpmeta> <?xpacket end="w"?>}K ��� cHRM��z%��������u0��`��:��o_F��IDATx}w$WuyUU ;3gwViPN(D913p"c36I`!!BB(ksyrT]鞮sװU{߽2@ ,.p:@ � @@  �@ $�@ � @@  �@ $�@ � @@  �@ $�@ � @@  �@ $�@ � aQBeё%fPjn/=)dUUދy=U$Œ 3"xV'[ꅸb\cL��`-?V(@)$\@I(R:u:S*   �aёdͿ!Xx8f$ι]od΄h筌)緄M0#B(<#`,0̻5q0S_)ۀrPʄ2Qv.ǒ$с1䨒rDrC[weA)SNNZd"* |/Ns]T@Xd_U!p,0]\pR}j&Ls\Zn%eࢋkFH, sM7��H\ThS*1 q0�|b_RId&i;(;\OfS'x8vBIW'Ar,5I0 &a�0d?AC10K{MWx婧3Y{vw􋺙Z4[kjz( c AOsW0g|B80qR#7;vs8<n6u5S{6=tbTnZ2v-@@�PSŸBkL;g_iY-]wjx)Bh魝x8cC:8{xq,!M0>368p;ܘM'‚D҅ ��Œ>O(.�01F֞4~\|˙Zlm9EK'8S㑼PnC87́=קM;{no}f_d HH�� ~Y硈!bm~ֲ o>Xsk=CZ�i>ٻtDvQ``e#'ٔMlI{I�$�|'|CѰdyGE7Z}Y%],Tk[i,ӏN0GSnjd[zƧһx:spS<fFg 2 1@ @Ȣ| @2MZ^}o\kDo\ι̙>/Ɠt|CA7 3Ɍڙ޷摝 Ĥs�$�MH_�D<ҵ3.ۮ4Zo[qy|׏ `|B8cC#nrM>9'd S-CL Pz@�I�-?[οbkSŭvt7҉Cν-@ڣ}Wm~e.ct1! ��!}YhiKon~ z3BZO/�t0# '94bض?6=LgJ � @/GA&=g,ܶo5ry7m](Bʈp <4ikcOG3owf:]B Ȃ$�H�]otjW_FOpڱF9�F6\F'}_rvK+D!uH�� s_�_^x ^s޾L:D\xf e?gܟ:ޠ%N" � (Os='ν-'+0,>a3Mglo;o?{:9-IY �a>WU/oy7v\C̙Pҡ=}HS4 ɭ#ѧ*-3GY �h|o! _/kkg(FR>^ceͣ~30;ԏ Œ�)V �} 5:Wu_;o|kkκkzX(Ok=һ}9ЏV:�h{@@_揭Λ^nѱeN4+` |GgCL�EO6 [/UmƗh|ea;Bß\7G<8gѻ~iok � ,�Gⱎk_~M׆75rʹg\ {` >?Lzr?��iHOkhUuEߨ\I;Gi~0|_7:>}?շ3uKMB@Ј(W�cՒ[^7<W9(ۢx�~#O_ ~62�ywuwtww/Vʱi_$qPʵɭ|y{W|�'B4XkÛ^qat(OlJ�<\7ʗGS5@�._kl]sÛktpb~r>FP2NߟF_` $�H�1E$ z^yʕ2gBXL}/?>H�a#~fzH59fi0gWG2'G}?{9>�2�$�s|OAʶnt+3.y-(;GG@+Cbp#g{YGBA�$�ϼe/?o?cq3i!j(r wL1@�a?B{:eɵ/}Ļ]3M>B]nٿ? ?rףpo P}� �� nCZ׭o~ݗH3CzX0yv_~䝟\zSm !�� @kba݅^q+@+Apӣ~?8O~M @$�H�D婝+_j%gB["sY:JB_BGď>C׵[ $�H�Ɖ7dumx uu_"siG 49]_:y?ٿ[� �¤_oθUЇZ.g2J|3HH޻Ͻw'ʱ\( � ~/բfY.vHm~.L- � Es�Y=sg] ~ld6uӉ;>=i_T@@ӓ]nDu֏H[i:B¢DCjc)@@G.vegv??;㢗J3 %&� <43G?޿_@$�H�4%-_r韼c"X+s:Jb] |KGw[hz3)@@s O=+_я^|[e.C{[(1='B|� pp:oy+_m]g!Ph <~cObg[dB@o]_Ai!'AD=yx@Ş @/~Y|2}歮}!b�?G윃A @_]yp{ß~i�p!Ƈ>ٿ)o%L[$�H�%/qݫ> siF~a6с-Gab�m � QD瞵=(O l 0G~_S#<H��X�7yo{gEe%s 8FGGv vtACH��) �i7sTO WD=~xwr#( @ ë~G$'5F2=vGx_O.�:& iK�a9Ϲu+%ZG$B7V}#_(wP]� �FS oz7D8FZI @ CD}o6wV@B�av``\~w|\v B=@D0ϾOWH�fL~<7w$"Io?wm8IPq �B@&pw}泉s|IuD sCc? &4�!�# [=K\K~1!s푯KPOŁ$�Ӡ?quo|SΞ(#zW"ol3IP&�tO:m_FdstBj "/i?q(@P/Wxڨҟ@ 42x$쾍w;es$H�c͏pVco]wFYfXI@h|=O͙}k ?~'u۟K Hj#M$B}Oߘnx&H`Q?c\5w$yFNjdOml` x_3?.Uwctf0p#7<poyCrӃ H�,B7"~׾]nz@X�=ie;0~G"E$n/~?}?@Xt n�} h; x[_l˥K"\5p_|$H�4;z>r-w3Da�rl/�8Қ?h_?@ �wڮ�rLx$�jEFX[/DO "t͟^z�,5 )wgL3'گj@(p!(<~ozur %hȟ y+?@ T 8;fW7?Kh� �3_rëGO Dź7vs3�$H�4L/m_ͯ{' k=m?/>Р "Pw䟏5�7[;o{˿Y'3 u"=箄W玸pN-{_zk2 CT?@q<Y}{GQ`] |o_[Wo>,ȟ@ f ܽωhK`ͥL� ?~';~wSTPmJtta.D@6׼]w&4^tYE?2v%}rittadh%r3c'~CI)0 @)#Vұ_HWD0Pp3ItlxGވ �Q.H�$-~#F>Yckd.KG@ M(H3-轟jN$�H�Ԅ5�q.W{+d&E (@o PMz|%vfNh8_`|?}n6ɨן@ j a8+_jvsSR qwk dhۗ_ݟ&(F��En瞷y:1H'4ӈ~ɟz~qw $@�sN�Bu/~G⫕c!Mb7P@ܨ"O Mwl@ уxKƞaͶ4Mj�j/�<)Uo%.@O Do]b abpu�(u/!':vcw~"bb� #<kBB]53z+o Y�AB Q E=vէDNP+87+?EP x?Bz˿]dYD, eWͧbg^~&.@c^_pdK  "_ՊVnˆ�i]YZh)`3G) o4\XG[�G QD[OHȟ@ Bu| o|@L�sW_d7Ӿ?@ 40Mm+>iG'>׽?d.!?n(M _g4V�m}䟷E{}ǕcB@2vW#W@]$� 4u޾tr,bD@7^po,fPhkw]H\r\%UvV,VP.ZE/2e'&&\u`|gk`>יfhO'z"sBYϽe7>4U0wWK_򗯏# � q_+0�- ȟEOh}LK@ D$_C) X e�f/�C_"k~z ÄK,&U3/4Ӡ?@ ,2(JǴe((Db(Jמs?uEK?@ ,VH_}@Yw�f,�ԿW_D;?@ ,r گ{^s Ab�^|K˥NftbtֲӍH_�hw{ۗu.}|T9N@��"rvɟM͑A� ܮ;e=* Ap,: 9x lNl"6Hx@ Gđ%9�g3З6D@=-~57wڮyٟDϾEO !6R/6imfdf�{;km:^)@295mA`3f� ܮ=ll@ TNXt=s9E6l6ES=~\ј_@ �k]>́3q gl^mY@ U5hO=0Tm$�zxY_ !47­K_WgB4UG@3 �4z8@Q@ e?ڗ\k,@SM lP]/{̙taY�Hw{h fy(^򼷿OJ[(j#,Z&Bwc5/0ͼ/@CL3d� M^y{k3@ �ʶYoZD!0>OD[Z:n{_("@ ̍swyy_~% i /6Vv1a.!mKn}˟>r&|ϼѷ�?�ږv.( 0Y�ǂ|y7ᥘ@,�ދo|5g+Ǧ3@ s/l7Z[RLԟ5l-@# ZkWW ~('|el]kju/j,@ ׼H_RO `�HJ Rj7Lk]]\ԐYF�KGgy;FҶH! Ɓ#q$\R /�\ҵYƠpZ{#3TO RTBgX`Ú(ZmF5C֕8vs£3,Fr."`t D�k lo妆F @CI4+y=39�#)K\lF4$?o: !67 V7?|rC9|o(ڗDU X0H'w y)%P3/h  o~Wq): B]G9W�4\2zlY-aHrdBWqg�<'u։^$s � I�0Ǎp׿E:6D@ cO/bCO׭;�lW_0]ׯ!w8.t}B_z0|!H@翛ׄמ}eL$A �\s pzem g- \93U�…xyKϏAυ밶^_9 �θoz훠@ ,4)~`yTU+'˗GКO^.omɴn^w 7\1/~7I�=95�(eΏvi?@~AtY z~Uk:~!rE 58 8 ڮ{k}w ȏ`Du|C!P@r?9imnZ-kb8#㟿pZm�,lD+9sRE�hu oz-E5\2zx0"gF*QC*ePCBD^g7c׻hPh]XRK [ R ʕQlXcXۢqQ0mQ#A .\+ŷ\__,-�DD{WZ&Y̥a]cذ6t9\WO/FLT{֔' ޶tm˕/zO?,@]Ի�`i7蝫ϔft0Wq4x4*pn[+#XѠGY:ZڗJCʢbޯ@2i4=p#�@qꗼa6K*)VDp8b],IH #$"Hkd'Fhi/鲱~[_%@OF_soEg@1\s4j(n]ǹa,?mtpx4 MXQ� v( �0r^6D*n�,�x�P׼̈D*1Wjذ֫bE:NWX ,4X$"$<s}Bg~&wd^yK`#�wXIˤ3@ =1elXÍkbXj@UDE $RGGs�`�NJ"0F[5U�.x?ciG@ |뵶e=G D#!b1ܶ.ˆT4#Cpcc&R;>AΦDږxJD@턧E_<}[$^�1=)\,PmH%aVqkO׬bY[rlh\th8 W͌qfjdѶ�}yn8=[9 � rgמsr@ ,HG4s;øeM 76`޸]G6mM,LG"!$na` pLD: P3q!='y )(�x�pW<$u |o ʘc'E78gKv?F` %Ol+5<Hp<Z:Vs]_vcb@NmplI›_(sY@)l%P8][*"znu :KOH2p^`URǺv[@h:e\x C~;>ݔz�2v֞'sTO ,uT*BH0_b lXÙK{Nˁ3 c8<bB0>j� sd:yP/|Я#(iQ� osr"}ԗE5\*%pZdŻ.pb4Q'�I&m xq<$S8B ;4s~`뢎M�ֵ,6(;Gg싥ߌ'ap\,[&pcw k:cQBɁ#d &&HS< ;:Hk;"0oJVG[7l߸%mz�7~Mkmz@hh_t7yMqo^gaq*�C#Yd]"jZW uFA"`ބC[ngl+b@��7J-+BsFB3aCO[+=yJق%2Ojz'〔0Gni sݵaXwIdegf>4&I�L?;V~ !N-#ذ&pJkLޫ L[H#@jBpPtVHP"?2[y�y?"�&sEkJ?ȐkaMcmM/m{%��`?39pD\k�+7~/);D7�e� B�1 ŒdYA 4Ԃ*}[bܖ\*eQ (P?#pbİi309YB7)=ø�\FW#Oz�h˖N:iYEB]GU*g?,㖵qܲ&3Ca �"H06PD|I_MNܟKJAӯ5, Eιև6[ku~LϻRkU F<+b:]í= \<֐RMn˻88A֖R+)(O+R쀹Nxr4d�,vB woĞ9Z1I`RiJ80"N'?v: ( = 0u/]sQx{J`uϸj2!8CO&p߳8,Wt\Jo2YhBfAo;eaI̅іhˮyj#@%ob]}٩$�:6 RhiE[+X=(Zb@vphĄ|bH\DSlJF,Nফ~oX]5L\Y4@Xhߑ 9_6:g/c8> 9 ?@)P<A?lk#G(0AiF<zWRO ,+*nQܾ. Fbpϋ ` CYdl !,rP[PgRBv.>KFALXZ@�@xݹ=ڒA:t5x8nVt�I}u#D,�~ْߝ\ `؀y5]PRbG/�ȧ?�7r%hKTf{@up}W�\ߖWymqVGag++<T�WgqOXI&~kb!�i%%xAhRs~sy(D!,6�.W^\'y0-jje K+"h=Eг/-%RY h{E(c.-�?+ eA'L@5u!ZNy PY�8G;C_a_UziM=:4`:1/=pBZ8eYK]rsZ. `xUJOaH?d,4ذc0b-$R� ,Cg_c�w1e�Xa#km]kH�s>sНq8nurr(&8i81P2"XH6Zc#Ñ,Frʏe3$V q`�D@P `[ݥC|)氀u� )�"@ϼ!h/0cWzm֯fu ,>L]pΦ)5QQÁ,7R'6:u`c pD@ .]h1,`B �3E] 3#}w CxzXBH0Bfa#Lʼn4F29/S.勵jIQnE,LG!y?R D/ǡ\vD@ tv{9cy [�L$::%+/ҙA $~GJ:^= \<_Cx8cJw4#8,Iyg Xݙ@gKɟ1oLȷu,AIvz z4$*2ƌ] ^Ll, P>ZKRIBh?_9"zaM kZtpƐs$Rg?LHV٨cX4%PF@v8�zy`,IT3%3)hn. `=ڋ؝N(YG -�lEF#BU nu FLȹ 9G'/Z 'G3uUYAXە@k~3qlTQwT,e{Rዀt e:ږ'Z::Y,t�Pk/TEBAnu \*e1 ,wqGNja4mpig"t z]?�hHȀM]?z!Y I B(+zUg=ra,6B ��<X"8OI!JXѾ 0`8#[{'B0 l[Hc&>J!bhY@$=ƀCdl^1GY;V3d"2%:;W_�"�=Ek Pa?>dz]qMwk#hQA_7Iz>Yˁ[;JTHDt,M ,+� eaKP",`e<M7s� SB9מ!a�R�ڒ2=ĩ}Kziq(n_ kX0}A_9i8>p*(ϓ[ڥ 肏;�}1āLdTќ?O~'0$ !hKPt~,@B�1.‘xHHʓqV7u <c\=APAoɑ r;!%0v!8Nrq`8�':f� Bag�HttOxufP8FJi!j�3Xh-SQ4ЬC*r\ tD4ܴsjUK\ẖ[9LJ2 }D� Vwļv ? )ޗ18,�&+�h0V̖?BuciKym$-z=KY/1=qܼ& !£@#U,1cYda20+�J),ob�%Xkc`iGOK[-<ֱPPa )X~y3f�8B>D5&): M{sfs(ZCy[V"``Hem iZ?n(*pIhc-("j ɧ&dR=mlJ<$2_1Vεsq9L7Zfi�h-gq~e]!ϡu \:Uqgۛ$~p8 cif*�5)UCwG3QDt_r*>wٔY2,0 "JGHյsIԸ7�uze(!4 [ *v7M FDz% t/{o>,8u[vƱ$Qlۈǝ3``S eXiWdfbǨ 98Htъb%%x|IGI=qԸ`!�ZX 2�"4;M* X^sWD),33Xɑ f־b}t&V־�0j88b ^4't3FJ)w']^�mKxJ�-T Jk/hltKGpkO7ME`]*2�S<C4L{uv%[6o!Ñ,Fr.9R) Xgenxtl8E_�cũ kȧ6d O֨43 YqÚ8n[uҵ5g Yʼn 9Dyu&1&Y6os}^i%<a0յl />顸s)4@>�N(N"_;RaIX՞-իXӡ_G=3 }FӰ|k_K+a=]oe8  -!)ׁcBa0DәΥQ:Z �K1Od�D҇߳*5tq8nYim!T?5>e3)Ek<k_Wmc3z+1�G?~r]9"!/ ;oYvOjt#n<Z\)_Iٿ;[{ZprgvKᡳvfQzG3uz{<5 #fm8bp(_ BM+)\8 X"@XXyک|"PT�ȸp � Aʯ]0sWq\:Uq (HY⟛(#T R͔ߕ`ոos%i 'gTP~ȿT<6}r@I89;@f,]竝��9K:J 8ۻ7xKBh4g> 8Hf00fB* ~(51Mp m=Ie JUi7}@_M1Pck hb8_ xrjRP"@e<u BL^*aV(L}Lb\ `yTիbIX^ :pb8 }JJa}\i8} (�RSDrט dRP9 5J׆L7b2GQmZnp�5E[=[^:+ kXb3o@O \m|sDHC܌ }:lkߠ]`px}V`&Bȿ+!ƫ.Ħy~wl+R`zx5c,dmZ = }Y*o$0k> 3k3pӚ6M0bz?й ,Їaê:ᆷ  t0&n?~{u}E`G" pm:&fti]kZzь[�L#uTE+&aóp4ʞn]ׂ+VDS= }N1i>K*Mptw 5opA,G8졦O7# \+ h"W "z]mݵ M ($VQ�aįH+9.^ņ ܓ@O3oK(U*,F29'ğ'CXG"l,*�3bt �Y %}~)rV0\"@)@hGPN>h z%_ Iwg7cCO/"sX“s鼪EtCʴqb(igú5KހC0L#mu 79 c_ݯl2RsP~Uϧ'UbM` Ѵx8%5r~* T)~[*8RaID QܶW-BEuz2q?AO] ti{3QFSɿȟ j#p_J7H59~tF V6M �+֥t1d|?1Ɔu-im`gQ?C2kP))2• -k&`hɟ3p`%yVfaAI=+A⓯A_ 8J<*5u}9� z�%h3Mj(P-�1^.�&_:aqlXׂ}[^UKjێDH,}k_)o/t 4@O2/WxDW?/Z*|?W3FqV@ MPl]q3d-o{XU ܺvDZ*]d˻D� s89A֚O9Hojkߠؘ=>9EzZ[cW* !օ:^\C�hzLy+zRES�% q& pKO nI`0,., qvqr8TG "X$rC�:yW(]wWV<<Й�$XXQ4I|26]4Ϲqtqۺ.]E{Dvl)cA8Ld }zR PPX$mQ(("�7F֑ Qɫ9'$䟇tƭ P <1=̽Oo??x =.{P~*._ǭ;Չ[^lytDk_6pcϬU1rI]mEcb@vw0;18 �lɿ5W~uJO%P҅t6Y8 ǖbj+`mLc4/!)Cim଎&tV,{Jd}Y؎t3`uGK!H?�Cvg�To*Cj_]Vc%ĵ>~ŗU�Ve" ʿt: B荥,zjP-�0#neޜğO+�c:Zí=-lEa n~>:4d`Hgm 78tGxƥ?iD2aA/AtE L:RU6nT/@8>)7j �3x8Q&%> UqlX kXOˑ-o*e00Լ _oKX_t־A idl OMejfҏab+<xF%yWM$\dž rY�}pѾi!M9 NW*Ni3p6m9]a:X89A:7?}? L Z־dưw(- inL?c{L!,?q+Vi)UqP v:u-kgRBmP̈Ch4 "4`佮[zb}] \{ {>i r2> T QCÚºX=˜#w03 �f; M_RpOzJVp�lg� \�C(0 Tq(ni= k5sgR>Nd`ZG7)63C#;�ݟy`l5-,OUPg"իU3 I3 Ǖz)m OE NbهoZp(Z oz}t3# -0_8 !kN82jNT�UWGFXnS /&꒏ cLWM4 @Ah|3C+}]׭I`ULMk$d`;r^ }ʑD;d|12H9cyw|x#j^ZGW4 q�MKHL�4���-Yr@ Mlbm:g8ͳГD4mO`,kgj)Z"X$Z@^`�DBV`}IAM Tʐ 6Li_Uq�!x�$%+"؞ !@Ε1~ ݺK_2ΈWH但]%7bo, ו5#~Z}Yg E־^+g xu �o=Hf>D%rJawJ!3�lBgDV~0Ч8.\ŭZqZO̳CF IZLЧf䯼hqE{ K[#PJ} R]i�0I# la13{(Yg"@h$b~,6m? J'xhY-~Az€ЀQo7Xr }J?cʎ:־doᬍC#&4cevԞ$�䯦!3}a`: d%]Y%?v<P5s=Y�00&#ED-yi1�DG}Cl }&C*olb!rB,8 f̹`L;�t 7P_)1J/p&@14=׆(� "B/} *8RaYL kxxy `mEHéTZ_Aݝq$"lbΰk  yUTd|oT% @UNWC*o+|Ox)S"[5qTS"@pR0/W*#%!<6<@{+ 's g{> '&+YId3R :( w0~5/GAɿ9JJ-vb `"e�883V)_Khõqt5Xf" 2GШ]r{ ]orW>6]|J<cT~UYcPo+U2k*?uJ._��CEڠ c([Jz: ma +\V)opf7-2JxXǚ8Y!zS6d<2d6݄U9f A&4GlSDG%v䧧~%/D@;*ug#8^yV;^N튀3+& 0c:89A2k-Hԟ'֨84A־ssi$mist{WJ~LpWi-}ϊY?&�&k\PXP7K}P#W.Ǖ�i1n3XcY8Z0ϓx:}^�0L#�| W+mw&GP`6o /[ߎ^ K2C*cH> BgK+G�i/s @KF Z24B)ﯤSȵq2�s$wZUk4w f }J^J-)|@2kd9?zaәF*}U]J ĭDgǖaJ)0oj,�e�f�[*xߥLȿMYI#k57yЊw^!p"mh`%81xڿ$iVzLPjL2?ݶG@n-Bn.O0^(Q`xAQ`F\+Wqj[9ȿ٢~7I0)E+&k4R 01n3`fH+9W"V~.½wnɿLi_J*Ͽy�]d�3; \24 ־,,g\�Vu}sy>a@i["'P C_eo~v;Ʀ1Kמ0(H@2nr�U/R!8# '?_Â#lAHcY e3=kUqNsn·-{ =Y+ 0UKЫԣ*$*r} ZT" {;vV\u2t1H7Eh+"ϓ twd[+Ҷ]iZ=Ri Y.JfAH݆*TUcURY*[-M!�Op钟MǠqyoO4;E\XCR=k߈A k?Tȿ^ԔQ�(Ij�sZQ[%q"mce€CJ~cY e**XHǚN]Ÿ�hI|rͅ*GzyLIWOolVTx$4UL#,xk|/oJ9TP}9xx,i?;A&-pk_)oXN־ #î42D,, RǨ !V>1}9B>V� OUDU)5 PJ$c^#oC"~d #-bQpl0}Id,[?pB[45 h/H.v Ko iYȿֿӿ<zTY2S&5� JI. {31%H2e[GR9d`}zTH}V(!e9Y:WlBAv`?PFɿRo((i5�RR)i"˕N┶0.\�3 FXZRj`YgKž3GG8^ĢJPX@p1YR)ڞ2{*M<Ϊ4ೠ1|*(90UZF.8V FsSꢮuπ1Nb$C),k/, ?zײ"VȺ5J'*.beʯ3g,B.}c%eKqSKg)NJ5Q.�^Jp.#q2_3 nh@23}IJίXҶ($/ rnqGrKT?X`V7.YPmڟU=6 JeU<Af˜_P%RRICXȺ྽Cxde b: a:.̯bf 510['\G{"Du$ Ӷiy9Jȿ6n1+&�HG9V. J pƲ W&pCO;݂gȑE[V9 Ysv]"Vw5Jᨧsi,kc`9W B1qJ*W_K*X@�Du% drܻ{._݊׵-X7s%$Gq$L &sJu T XG<:g?pD 2l Z{䧨 Sj$?0?"U%L.=qU6*xyk`;'Sa.Rp.4Ę 6~?ُU-!\7 t odBpY˭;CR5twڷ^џFv6_*D3;g? XL8sI4d:}S>BLД&T8{mn 5m~].\Ƌ¨:ce~o ,:7R!GHOm}v%v\d:6izr5l1(+c;'ő��\;E�3@1T LIyb@R)5wqܱ$z#--⥈-W#-| ézGmZ=sg훈X.xQOj3�hG_ %˩ġ]8g> U3\b(Y^#jF� >>.f:ix>�>B�xnZ)2�` v'qiu=m8gi -! Tŕ(MEHyC/ )Zc!ApO P_c{{;`_NW2a,سKFEo6TnJ !@CK�\9֘w3PLR�]讄!<ٟƖ)|Xu=뻢HT]v7b3?[FThOڗ.8v \MK�|^jI}ςjl=Rq? `Pe�&8찒7*V 0.̀IPRaKo ϜH]1\ӆk׵ↀ*8�]ޑ,RY[&k%:[X$` ;R8 1s&"IT\7Wq*榆6�ވ=!D q%:6ǏOù⸮g YۊuK"+auDzqUC8+Q,mxD !8KM�\4?�pSʵIڐ`ٴ"1ҥՠHŀPP\S\34r$pxA{D+~];Zӊmat#�/YdL"`:Z^&q4C*`G+�RJs <*8T~\ tWJD5(|\ g݅Ȅ؞(U鹵<Ræ$c0`R+_ dlƒ5pJO \݊-aսqЧ, '־ +D6ڗ. GFLl'lAHV ?Qfu7960<)NZm@1f)rZ h  .pd}CX3p\[C+t 0)E3%o4R Cv"gȎ%!_9'Ϟ/�)/([g�SVfp$3�8pepw㗻<[:".4I[@Ek:HocIj*?>t{UU@Њ& {@ Y LjNN\v6�(>tg@A3L �J"%['BbMiūZY{ڼH2 ,ocG!M`uWFߠ`HlKA`q `0-S2PY]䯪|\G_s9&�ps`-D9sMZZ0�J"1d|wnŚְgEӎ -G͹`�Xg4d4RVw4HȿQ!З(M�Μ+,ɿ|_%Τog&xMan&iڇTZ �&(79OKA A@ A(:pxĞq^EpV\׳.-͙1g R) e10f7i<J!ֱ#>ڗЀ@pHc̒L�N俠{`edz4?@ηE` @VR̙�})&%tb@ap;R +m8{i qb1oIr7ElC/ZVu9 M�3K!cKDCd�D俘?m�, dJq\& ``R)08Tr2y8z=Kӷ"v 1Ra0P2 WrHD+ۣ�"Lyn%S2 boxLU_"i4Ϊ~<Aڅ`~i- HNL2P4Nf@H Cq�JW0+%6Lc'pR.vQڊ8L }#Yd-Nj%-a,k|BÁ!7�x8ݪ*ިP%jL~`߽ȿ„uv4��;қfj@q�1ySV\"ccx(oEWmúb:HXXéğj5 (E&&dї.o�4=)d$FA*rG R�TV2'Tm5 (_ p)^s]<zxƒ3pmw GMӐ}S m1t$A;RH ]�MZMjV)<.TTlU^u7Vҁco; k(�;c;Ѫ@(rakp5RPB#bd,?v/<4)/+=DM W*lK $+O՜ӈb1s<g#5,v� (J[G.@(x>\w0&bS_G4)g 舡5:ן4!mySݪ8II~ӓU/O- Iּ@M�x Z>ȓ� qEWbJ!5JMlxR)+3C0` m$ dJl#Lɟcg`5'KOy]j p:[6-Ѳ7Wqa3  2ircgc9 6QPvf}3l;/?Acbwl('*P(QKqZ՝AR– xaCCwWQCKM Vȿz%�Ma%LA<o[k erpLey8BH_3mrZ Xȟ.D�hG O2uR_UW'Z9Y/TS0Ig3|ނ̜ᓇ'.jU0 iwl 0#B`砉lclJ!1( coqsAkgBlgLs}pa ʱL ?HPNAK� H! n\V�R*CX$@俈 #-屦IQ *�9fh`eOTUC#Ik߳ @t;| = U+I5`ڊaS[X$ۈ7�J.xY"2M0?)s}:-<gAeGejx 5Y-jߧP剔*tM``#[w�־ahGPp]})ت0֟S>B'䧎 {~N5��jWXo<ꦇx"Lq]C\nlx. KۢhP3bk_ (VE?7삑44 hF#xCOW͢��L\62c �ʓ` 6ykoqp2á:/�D#x{>[*1jF,3oFInjx�οPqPʱ-{ffe2Ř`w־+;bhO!%N`ޗDgV(5dɿ:JWCeLж=>�r ѱCf@ Ysq"maa: úJ3\C"bu)OŰ7 ӑ T Aj"A͐U'}|_�=G �B�䕍%sY@ЂIqguM}i\<`و.bI w?2^ !c;ޗ8))Ɗ�*(΂^,/+55 ޥklR\O_K's0kSUrۗ)ERBu'S~14il:gu- D\fT!0{ ]0=[�lz++ U(Ub[6gY~ک|OʗToO`~bN�4M`,#nrh9fr#IGs3=LJq_/WmtHat<GVrltui5g\[#};领s�j-�d‘ʱ֞'w0AjIϸ /�Ǖ8;wHJ[�ޛB)�T`/X]; ԷP]#_p>g�h @a7�C;&cNS56i&028;8^t` 0mSޞ9 @^*xX^27}AmɿiL~njx.Ebu¶qG@2=nDMf�80fh2S"ڝK+]02qfwºCCr? ~B~ @͒|sy"Rj~#BE6 6T=3sjv. �{Me40 rm�Jj匁sC}cx|1 &iK`1A 8!* `S?QXD%MAojٹ�,�7m0,P= kƱ/ӭ6�vwkc4mDZ0m , c[o3�SH`;˥|Vcg*<w}AI]s?ʌ۷a ͅRoU`B /`;bdƪF4 sփI8 C-&F~�PiQ\ W5W`%e5CWpa %�  ж-*;jka� [i !R[cHc Yۅ(\) �r/]p\MrSӓ$oQ<g/>̫>er,P P-Bαer� "̌' M}iXIP)וKc}N  3HzDziĪg�կ,rlRv`� ԎT5bؼ 40 E �9@Ɔ@�@CJm쾓0mBq1AC0K!i?@] ?/0:y#&kN %� �e[7o@_W C9mςuRTUc BpH?^#"`[_ 3C9Ǫ()9qiuJ]S�M2=B LW(}iܰut̶48me;NY\@#3 mڛ&xXKm-:Ͽ,tާU=_ U�<p>fsBA&m03�\,[;ٽ'alh2a)�36 fi97Rh�}h>;��(P=` Z;{Q�a"sĎ,t>SWͺ`8>c;o$Q}`C;13/ٞ0)drhVPE!�&�<'ڊ&f¦TXxn$&8Ҧ'wC~�pm)d%R5re v֐BcܾC}7.�"?�f~l?+!ZQ3; e QAD瘗mu�ipRyd�#,YۅXؘl` PE#6? ٖy4 nNT9eI|}5k)#'!e p %�rQ�|i:(YRɴY󴧮< 6<?C)<N F[u FL;%k8JfūF̸>U_ȿ 4RPfڶo|�g�(v=h0a`J`cozޒ i#ydJі@ua@{IMf"Ɲ翰/mܼݤJ�0kcOpD�a lO{,sL�<>:w@ڴK )lR`JMM&jD7̉b΍`j {wp*G^Tq4e }m!o^| [#^oDـ`:.&'y")Wu�ǙH־gD |ͧՓ��M\(4=mU iܗ܏`pTHMl3{N`~H&fe3lKDAt1| :XLJJvTi_;7S$pGZ;[o�`c&Yf~Jy/ƾ 2;XslO:d6manB0Π7mNkȟQ { �Փ�(8ch  @IU88ÑttwuSi< $!8brsm)$-՚(JCicUDU @!}[`|�Je:c?yΥM1afƖ6qfG;w׳+e]}'02q%ЄZ 5[Wb$\U* UjyWSx59<{xNIVU<g*?\tBsȑ֎?<?m0_УQy)�֮̔mPn38ƾ4 9:cC8b½'-2lM 8yqcKv5s +<r^"t`/,9�}p^V ̊&8͑Z�#o $3xbQK`0بc&DexM3[mU@#�K eڣ: "2�–td64Α]l:p֗DҖ~RU aUxa>ȿ1js~M��6�߫ld {Ӱ;M\Wc OǑ5sl Rޗ%p4bCv=L@:�޾9ff !kDƚ.~W,Oe�ºX&i:9ιJ[LAJN8ٟQ5<%;p(mtn"\�`aȒڟ"L0t`:wwl?޻ NsN&s? *'5c_?":~񇍨 n?&FUmV6fwy�#Ba8Nc:gg?|"fC֛hNc%J8걀~ ! T[>?�kT �9) ݾeT Hq Ѕ,ά qո p qMZDZc C0H(.392 |0-98&s e9*`bL~CYxoPsS'yhuwU\no7ιP@ 5si\ Yg瓣'�4|I f,8hZ07GG@r$6jb -'SЄT-p)i) ϛ�5I&\iPh#;o̺S| 0kpN$O�la)`S_zV[~�8Zў8>ol< ]p'%  hBAOÈ40?eK.�͠12E #�= ɳ\뷶=!:&61j:u(x- qƠq{[za.ɑߗ,9! ?xy tƝVk {S5$??T`Vuˌ @dS Q@&-ϱ+t3_yfJf��k#]; z)5EVK�.p9GH6mϷ`(c!jP-H<w ټT(?=9`}aw[� �m1 3]tR@ r} B䟯{TSBD5T m hBg-?xO.�|lMA �rOKOF��JȩdmJ)GϚ,׫YBuMTja�c4[NGGapoX.eq%@vroI|и%@(`2=ihHM/"t8'mv<'uX_TYx<vT Hɤh,JǕ R8N$sDZ7`_0ӒP֛] k_o_l39hK`jFpOb(\?_g @Qvnu׳�(82=ڗ௠n"7c8 zSU!@qD=W=#Y4 ??M6aE@rkGXpܹ('L"c%t z@&?1ZM ׳��ܓO,f(/T8RBֈ"O$ga I<!R^T? ;ƗB]sI=Tl頉5ø_*aXf`x>Y<>�st.{S\{e#px,ӗDaUy͏C#kg)  /GތH+3+A "Uj!ҏ8Boc|観-,1 e,' |AU|cA8[MCA~Jl|4:Qp?9Q2�=l�wA(?cH9 {S&Ly?c�~g�? os2uP(Ί].B ql1O3s|S^D8̍˫廬ỳ}pc7WS7��=,�Sα]Nc p})dmx}SJqռl I3خ޽ ϞO(*+&iӮr k?/<g?B fAA [N!mKTw6ȿ2�?Rf1u_n$T3 @Y[u!w$) Zn�HPIŷ#GF �EޥOG &SU%,؍ݵ FM m2-)0|)_ lƜN_ѿۻ鷏P"�Je{If`a̖ܗ΃MnSrL‚7mǦc4?6A3Vz['<!jh3ot$EU77%4[2C~�Ⱥ?c WO枹nF8FN JB8 ggaHaC2/ZqdK1Lbbi'T &02$.DM A4LNuLm� �? @Q;PpXXǕsZ|ԛ6G_~ @='p%H<kd= |u ^^?LEL!'E\ m�<z( c Uw<s[oMΏ}8=>-4uQjM({TcsdF-C]x}z4\f1YŖI3)5IpfL~q7=:bΟʡN6�P  GnKz%t�ԛ`įi\ߪr~o? ە/h+0/E .ɽӕ{\SBe GGx]h-F6 Qpv՗NL:tUh=Ϳ{pߤ_Aat:Z�{bn{C1=B#݂uw6i{�fB?ч{{漜QJy6ٴ2�h|{"-g\xY CJދY nhMQǞ]͐g(3LEѺP/d]_(�$ �rR5яU.CI79L0� !2l> A�+Wۮ?6-M[r|[~4qK�gr2% +`10Kf`a3°?km@~v �Ρ۬mnj0Dlڊaco Rɲ_)Gμ $82699`P4)| G0"�#j/ބ|`,W"-qLŖމYt 2M߿ oF�=,�ߡ,Ӥ,�a&B@ d0sJno=ͰqpW9݃i *'O LsWIdV8Yip]s>6bO@c�20 gllMMSD9l(WF.Ș�9�9e_SP�/cc`6�:\Zo6?-})|٣82}dtDc9yKq!厧qk%soJ?m3g>; PɿQ�P\ em( @ɂhTYs\ %U%38xttc qG3aJ@o L KoƿvLG"7&_u71Eܼ c `@? y2#Bg)HOxD{J�H%_ق3p߾A| \"WrfP!]?w߳ٗD<T]wof� cLRwT�`'Q~@)!.�iA*v LaZ>?.a]5^ 0pC;Ɲ# >7_8FvF�ШtƝI pܞxBWLK~ml9Q�*iR]0dm;>18Jl#LSůJgҥBP%� @To \ơ54 dr=wih n]=BgM [�4߽6V|VjP@eƲ1ynֿf�Lk\PF`Y�? 8ѱ;P*q':cxtΠBGJ*?T�V L�+>Uƍmɼl '?~N!PO3l.WUJh6"X$̯rO Lu{=}LO<nt~8#iŞlK*(nqdW9)8z>y.lzJo˧8N#&;M8нv;?Y\CxGg.O&5~6Dn\L ~G0i@.PC=@,y�IKOE2@ RN1ac@_y(f\ΌW`,~n5?Uz+2VU  H&54d-މdEtcZї&x~6&?N=F'~CwCLkfgl9tȏi!:{ O$` f姏•޾ʢŘ3 lKpֆ.m~T?NAh&v|JadӠ O~ 6t7x{~"%M8v8T6kryn�{?sO4bB4OJ!CG?18tG7Gvi~DJ_0%yl<6|#.jLq<ytigS)"E#-'0QpP#=]jr�@@@n\_ϟPVTHJ6L[b]V`hGxg}ng_~-^_+^$raK[4t d\1Xxs5/?Szݛ>w(;7 LL6NcnM|~6 Э~ONjO/0$`BC]|w c1{3s86u?~&W@1w<{;z-qiw;2 Ẇ4' c`0#_l�2o~]ٹLde39Lv:̯udLRe+(& 4]ׅ�J|_ΌWjMA rЋ%4 'G?o?yg0qK ?;{ue({[[z ߳XpRig) A,t zHRhPx$ &_lm޼/^dD,@YUcYVK 8#ewC܊1?]Ʊ?q#=ΫJG]s}*C|CMF87u,ڲ C< ([7tYc5g+,p|Xj6ɇN2'B0o>})|3q%s%qm>i𸲪|b7=?pގ|,e�kQ^X~7"K<7? <4i)dSLL < ?m�q,g U 䃚Ln/y} x]k;zx==~b"֓5}M~*ê~]qd_L'&oƍ@&�3g&K4 "U-`t&{֬r_ef`'<ͱV`06./?Cܘg!8,ŧޏN3ɞP_W?*{7RC'1E4U1|-@5\*$̑x8�#593V%o !�K?zv3wW/g*!Ŀn80m$"?"PGo?h$>�.=0  @eiZB(-^b^J|ƣ\p"gKq)xݸt$\U<ƙQ`�>6098&&Y?C9;cHknVnp_�D|]X869ћJ,딈r a$J!Hu˺ʵX8\/wY[bG wn=1ED G1dOI�LE@^ �!0eg9kf&D�?+[[Pȹ!.\نӺk@v1qw0m) aGwnߍ@+GoϹV@Sfʱȿ8q-W)XT wTA _ȿ9_� fgϓ ?/cn٫J A\_L x#) @;p,FVt*a{f ͩ*yBӔ>raUsB14x�b]`=eW>WsC,Fy LYh¿$� V@=|[_f^pэnsҤ ktd qJ_9ǹr߅Ur5 7'(g⎈ U?{v};EE9�y8G�rD(J9�gq<h:Ǖbڮ& r?םs}kPRZٟTvl 8l:-[\ZtwlsY8֡&0L(cOKRzTR I�?O$BTQO&;@$LY6b_lTO亴?ũ]KpZ uOݕ{[_bAWa^:fu}ƑlPTFUg )IDfR,#EIz;}>'dKE!�P(vPG~S{#P¼zLF_&sL3W<L87;s(1w*=ڏECM��Gis-GN@"!̓0|I=)̀X B t!}(ݏ?¿%05 pܾC_~sk.H¼-@Xf srC[RU._V̽YONB_ 2ȟȟ0'T69듟R9R{n ( |Rv/|ݿ!h'4 <-/TM~Y/̀o|=} (K[W 'F 0S/<O7@A.mUS:⮚?e#%"& P 2b�cY�̓^.V<.NB@@F6'_,8?4/.6Y N)6Y�2w~ho{~ ̷eBx!ho>#Vet(OȪxlCMߘơ#G@T �m"eٻ?iSYS`(@J|&|4cBSǂq*&@b3XWy5'}Cꌁ}+nI`/|AW�k_rEM Atq4T҅s<_ӂȿU{af P ~}OϢ8Xچj�M3J_2�r 0!B=5_s?;{?~=_?$�B܃}?z�BmJa`5Ϝgr BdjOȟ� )`K sٻ?9y|VfDHjpp#pNOOx̙f+sl| ^V7│�|`pGWyZ-h\7� {(};D_?I�TD^!8̟|ߙ55&?3dJTyQ"&J !Pxܨ :u�"�b:%ޫrjma>l J ϡE頇OH}!I? FEz �׼ߵTH* 0.(ȟc꓿ /cK$�f-[y`DI'?$j.X?[) o.`;ٟgD?2o!0+E@@NeƆw|crA@B-Ey4'"r~?'4оj�!y@םq䍟4B6869~jNu VC7F_cseShK{C2�,=we,U �2?|M " R ~-E^QJdLE@5)tVELq( g3~T̈Oo~*\8ݙp015wQRg.Ul-K/H#bM<cbp4sr2IIL4hI8&cą(@( & MUuo{۷zz{~馭v[n�eUUop%(Chzׄ}L;z`Wz]wAn_xޟP�Dv4zʬ[;Kp =G$;Ҝ']Wsw;Q7 ~ @JB~}6p2+x޲9KJ"K\ f�w= / e'T:쾟;7*�ҍgA^PTJ@ZYwV&b p )z3V3kU}˛~SykVnx9жw.3;Jd1%_^9b4eJ#:=/EŜn z{O$:}+!+'w(={�wÍ~w�8))tDAmO?R�2<'q6Xt]ʟmL%ݿ/H?f%@4 Ķ�JrΟt+"?fk?R�z`'Cڑz?ƚc){$ h x-M/"EUMWsޟ#;)];[S}v>K ށ`Ww{'{,wng=GP [o~aY{RbW \=.X`}>irb%ep,HYxnub3~8w-ÿx)/Z4*[�{F殔 'p)&)z'�mD d'WL:[+_J=spHeO�ٻ4VSHGZ@H.]{+;@:!W9Oc OpZlX~#p IwH�|'o\~a2wXw.fD MWH '+F RQēX~枟8WYvr^S�[&\0'wHEqB*}m ;!gz"VK3<s~+6f4l,4)> .> V}g4~3k(bS�)#9ldx�A) /2)݄e >O�就_QSgL#ƃ<?)=`yt � 0#`#Fƾ|t!'D3�3/g¿V??_J@@L$ž5f\~4fOp#$O:2{~7�P� C7KnUXRq# $@^{/o[ ݜRG3Uw*徿m_C}�J@Hb! H� o :HTsm SEWU�ÿ'+wӫzonӯ�PGvM@n̻nn&y! Nn:3m= Vwߖ 3w희 ӑʟOHG9ǾҲ܁AQڹE_BXyi8W �N8;5eZ;COn$=JMtWZ*?zS =>�Ҫ H#c~;ڴ [$ Z15F R5֊K~u U?aH / ,5?w7K @ҍ�сEf`fOq|j�6JYKƼ߆�IwtX*۷uc:Tß@ڎDmŁBtQEK(5H$w�ÿfٴ|+Jn�N  rnӦVG&(N"R 2Z~~gWS�H'H�@90y_ JI6> `a(Vu̳=V?dJ?tr7@Guq�>kUg/tpX</t0R8WZ+|P:zį~>~!)�$ 80XP?5eZ&ǝ?S#T+1bc=[k#OAߠZA+V�Qx]@ �]ēC̫kE7@) I'JO7]o@ܗypm|M[\G�>/;A7 @g^wgu8|, >݄~o{h:o\G�t% !Od~۷s.4๼r8:W6Enjj{#_EWs@A>/ X L qM}f)n7߳~  xjxA];>p �@x]@ Hm i3@8 `5,QVP?[� @k�t/b\zŵ7@L^9CWq/h!XȾ]֋}f?3h- �I@0%^ tb�tK y(x.tN�ÿUZ<򸪯>O#w?)�^&� ErPS7Uzhz�RN�ÿxH q#vZCލ"o-|�P�z[0hڔ9s̅߼E:= NRN�ÿUp?k}R5֜Dn{_~A2)�^t�H (3kX4{0  G=ЎUy}V?}<U?Q�(�}| $ Nc0�$](/]/�3P}y}kK+;S{PW @g݀`j@%kꟼ&1x(Xin$Z^ol+ �w; # rqƂo.uVc!=VwSG_V-V� �n`�е6Ǜ .cͫGHzXskKS 5Ǒ;/wXS�(�hhS 8� M/5*ZŢl `zmǼ{k?YS�(�;N8Y壍_Z%q}�!݀n�Rwx=\FF4?S�(�$D)L ;�M:h}*h;BڇÄ[^\ ׮ݟA;O[�P�H"@0-0) n\,_8Pm Ӄ_0Pu'>v~鋪Q͖Tvȏ)��f [ GE@@Ȥ6S|<{\x 9w8X[ {3ʃ~-VGo�P�HD :-$ �_p~u׉Iu(Ig[Y``[_Ϫ@ "i="bAd63WOΊ FHi[{Zge^wۺ3)��%݀@ CY\Xt1a ;߳~a2mۋy~ �]dHcGsEĈ SklUH 0b@!^ycrr{4ër@ .@t`Nb%gi3U\9zL(�~C1S5uM/>ع'V)��{D�1HB˱W@3%4q2o�49c_-+VzGUSs??@z,B&)3ٟ6ye"10 oAL;C�z :z`_W9:u ~x?B~V� �" |@ �`ʑS&j>}>}R> Ho%߷ӮwM֗|u3tr-~?�> -h�0\?˴YW_)FN 8o<DzOm'ݽ_s\ݿ](?ˎw@`z@G遘@3jS.*myHfWj_7υ:z`}guCnPsq�>nN"`gac1|y ;î�WtBP'p]ٰuJ�?�>/vDe�ts6<O\Ms  OH^HB7F;mwW]P'?x Q&�D s z .+Ιw6}|9<}XJ:9&:PG{6lpw[Uϩ_nJ?zb_+�~DZ+@Q_p 1l}%O\"'rvB�騟JׇjulgXա [> �!:@BSa!�(HT>g6b6u1t4 vT|[Moߦލc~-|GC?` �!@Ed ,:rQSig_,ϝ7[ ?] LuwB ]x-6NC~B h@HhV |7B ]M ?[NuRq,4gAew?UB}lu[ݷe7RG*}jCcz'�B+P3ܙЈ<? '9fCLjxP*Ps/2?!M@CuwNMo{}̩P9'�BX࣎  3>H:V>|9qy|t ) H]GIҪ~o^mU݉y ~ p'� �F!:O O_% N'''Κ*FOE鐳u�ɞ9)+|)R@.jOGyo[}G3~xP[w#>[@ =Cin|e@)"V:H9A w?s:f2C#^b H(7]"}n_H(4ԩޡ﫣wvQ'><;]ywP>'_{O(��d 5!{� 3Q*GL,L13&!P2h ̸!ry~fP :^ٟ{-+[J~WUn߯N}ϫ^kO ~G'� �q;!)Z RG@B7JaKFʳΞ c+EbJbBӳozҏEm| kvEQJêJUsV@*h)]4ߛ0 @�h>UPhAX 0&MOB7C䈉g#IEɠ' VfH!5(nA \|.腦g#X Fi8jVyB}uwT՞#*X;݀ca(ĝ>pm!@ H=݁B-D xo2QMOҲ|p!b|pY6j(eˆ!VR3nB%!*, ɮNTNk={Rc[62S5ȤNޑ}QwJS'>8 N4*zмh3 @HBoAnATSЀ!4r蘡H ,`AуaE<9�FaƠAJ !Eѳ GU( *'s= ;SʱdIթTMw'j`[5*Pj<+f(XB؋Tn /h+m}B�r&oA)<<(A:x^L̀:Erp\ 8+!%0 h)4݄ 5R ]Գ6 Л� @)ʵZs, ;VF4֦c)8x p`u:FChzK|t}w+|B�%R #roaTD o!, <џQU{W(ܲVgO(�D%%}A'elsي@( .BR fh{~FBn O  _k)E-t /$B�^m_h}x+p !�Bz+ -ޒ\ ZcK_!P�݂|!ך Tɷ36HB2 �B!b vB(�.߶!%� !ҳBB!�B!P�!B BB!�B!P�!B BB!�B!P�!B BB!�B!P�!B BB�B!@! �!B ?�n$G� ����IENDB`���������������������������������������������������������tdesktop-1.2.17/Telegram/Telegram/Images.xcassets/AppIcon.appiconset/icon32.png���������������������0000664�0000000�0000000�00000003571�13262451251�0027307�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������PNG  ��� IHDR��� ��� ���szz��@IDATXŖmU{mwtm*6҈ jhCH &D D ?LI0iIHEVMj+X>navfwvwfgޯ{?~@KM8w9{E]ҟ]/XsMf=C^[b"-#7]=q } \{oqޕu2 JX@k>́gߵ`bt�%zt.!\`B:&͗N=-;~ST+ kƛlxIR>⫣|E$]G?bhVKYı߽zw}ɥVX\;t06~/~*̿{B=-WoXg*R@553֚t|׻qeV -Xpu-'U_<};7~d&gϽ]1 QpɑBU\יO/S Eqq2?Ϭ10к;2wNo,"Зxe wqÒaS�k+{oM_qFЖڗa7Йv yHdF|>~ 3E5LPE.�`jŪ$]WjQG+CLV̥ihFUJf@)T$Zu-gc.?8jf,B,’˦9jg}O&P, ALgq@B iZkЉ$3׳|ui QH{`7fشZyԞ#Kُ*hVk1xPA)]3&jmNGϲfD:gE?O,[YՑB)h0VU,VhoIߕEP@9 OUpEmEc T"k1[Оtx8Z VBƋT4-ƕ�\xg՚z)TXc&�\,8Fl Ww} 6,ma"rY4 gƄRRF$]AP(5r�ZG iW19)GN 3,[UZJ9¸e39m 0v ǏG4R?:YOQ } UHz#5Z^=Y(4Dq&�&^Z{^kyybkrI8ZR@)|RxG^+?w�_޹4lufBяIOgk<CҞ#N:# ;"8?ֆf7sS%+ntQpNS@)4d˞cv%xcĞ)VweYOS$]Pm@2Mxx?Fn.6l}Q^c8[0>.5Ji|#&]Z{xgѢo{N�d~f\*{ۈCDd*sA Jṵ,9"klf[p"+7\z]Иt�ZJt5ZoT*Kۃ>��#/?#Y\`u gZp֫hi} l_�};^QL{S|LښҐ Ւ|p&��`Ǐ+>)nSVt[O&`P7^ +qڮ3G|\̭:z?WnӝV-VNmYsE[{{sx{^,Ie{TK{Jf�%auVg&N~({T$ޥ����IENDB`���������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/Telegram/Images.xcassets/AppIcon.appiconset/icon32@2x.png������������������0000664�0000000�0000000�00000045712�13262451251�0027664�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������PNG  ��� IHDR���@���@���iq��� pHYs��%��%IR$��;riTXtXML:com.adobe.xmp�����<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.6-c138 79.159824, 2016/09/14-01:09:01 "> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <rdf:Description rdf:about="" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/" xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#" xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#" xmlns:xmp="http://ns.adobe.com/xap/1.0/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/" xmlns:tiff="http://ns.adobe.com/tiff/1.0/" xmlns:exif="http://ns.adobe.com/exif/1.0/"> <xmpMM:OriginalDocumentID>xmp.did:1ACBF4C4D24FE4119247E59A0A0429A0</xmpMM:OriginalDocumentID> <xmpMM:DocumentID>adobe:docid:photoshop:340e2f69-0639-117a-8f65-8cab695dcc76</xmpMM:DocumentID> <xmpMM:InstanceID>xmp.iid:6ba59e2f-132e-443f-84e0-f4ff492393d6</xmpMM:InstanceID> <xmpMM:DerivedFrom rdf:parseType="Resource"> <stRef:instanceID>xmp.iid:1BCBF4C4D24FE4119247E59A0A0429A0</stRef:instanceID> <stRef:documentID>xmp.did:1ACBF4C4D24FE4119247E59A0A0429A0</stRef:documentID> </xmpMM:DerivedFrom> <xmpMM:History> <rdf:Seq> <rdf:li rdf:parseType="Resource"> <stEvt:action>saved</stEvt:action> <stEvt:instanceID>xmp.iid:013ff985-91ab-474d-807d-b60bda015c0b</stEvt:instanceID> <stEvt:when>2016-12-19T19:15:42+03:00</stEvt:when> <stEvt:softwareAgent>Adobe Photoshop CC 2017 (Macintosh)</stEvt:softwareAgent> <stEvt:changed>/</stEvt:changed> </rdf:li> <rdf:li rdf:parseType="Resource"> <stEvt:action>saved</stEvt:action> <stEvt:instanceID>xmp.iid:6ba59e2f-132e-443f-84e0-f4ff492393d6</stEvt:instanceID> <stEvt:when>2016-12-19T19:20:46+03:00</stEvt:when> <stEvt:softwareAgent>Adobe Photoshop CC 2017 (Macintosh)</stEvt:softwareAgent> <stEvt:changed>/</stEvt:changed> </rdf:li> </rdf:Seq> </xmpMM:History> <xmp:CreatorTool>Adobe Photoshop CC 2017 (Macintosh)</xmp:CreatorTool> <xmp:CreateDate>2016-12-19T18:26:51+03:00</xmp:CreateDate> <xmp:ModifyDate>2016-12-19T19:20:46+03:00</xmp:ModifyDate> <xmp:MetadataDate>2016-12-19T19:20:46+03:00</xmp:MetadataDate> <dc:format>image/png</dc:format> <photoshop:ColorMode>3</photoshop:ColorMode> <tiff:Orientation>1</tiff:Orientation> <tiff:XResolution>1440000/10000</tiff:XResolution> <tiff:YResolution>1440000/10000</tiff:YResolution> <tiff:ResolutionUnit>2</tiff:ResolutionUnit> <exif:ColorSpace>65535</exif:ColorSpace> <exif:PixelXDimension>64</exif:PixelXDimension> <exif:PixelYDimension>64</exif:PixelYDimension> </rdf:Description> </rdf:RDF> </x:xmpmeta> <?xpacket end="w"?>��� cHRM��z%��������u0��`��:��o_F��IDATx[yUՙ}孽B@D ۔!QJDDjR1nj5$N1f*cʑhjtNQfԨ8(: " Mot^vsz7tׯ{|}o}$/O2JߍFZ&+e"&!c�vt1 *ۓaJIyOM='yƅ'{alfӧi2$Ӷl(Uݝn=}l~cSA焝I �RZtI͢J/rL 5|VC" 2 ik|OW~kqR�"QVnM6 sA�`DK�Y1Ρw<>|@L)�U\~woKqEУ2iL ;x꾟uoXG YSp7/\fhxR,dX{Zfe@y_Zz=cv*dC&�l~{c4�ns�:Y0,a_z][twc d=|Z|�7@݊WG*3{J*�guo^�֑!~.f\}e)WSU/77+�c< ho|<n9ydI͏ $$`|`_Ⱦg'mdڱYkO'EqR�f]#]S7txڕA N akVOi4_n;IM'u(HLI}sm ^iI|0Gzif8FS{ -c�iۅHRi+f% I ؎~x@mPAKT23\4cfӒXڔºb ! ~͐bPr0h+߸iԒXx:u-((bLcYS2fk"¾| ѫh- qhTz?-6g̙ ?@3ajKp4>]c_B>)Ym9_' f"5V)Y1-:g ?&�s/ r93* O1VKciSH"&(4|MP ) ^'@’psYfԘڪUĂ˖ fl޹kߝr�|2y8.+榱dns+,h4PW)u]&k`oø_, 0c16n<sju1="�fͬF]<xSbC7peSέ#n܀lWA3%Ggo )L/ !'0b5/!Z4o{yd�f6}FҖ&_b0|2KOMaYS5$Q0.6QZ298FS]!Y4f 0~*0ȄkXN9}�LoBNJp%İdn_J D0<}TeK B4:z-B J&a$BwGK C4K hG�@Z.֬8d9z�03 4L\X*pFh5#L7@20wFqۄG5CZt<H!ʇD "0V´W+ڙ 2C–:c@m|%q)8 R4"ϺGB<_ 9uiئ3&ۖnhJ@ {Q~V "Q5x@f�d1Xx�(f$MUӫq8$bcDYm=AT R B#yAߠXk �,NiŎ6Y4~~l,_P @ƨYR(|W)7T$,:= )B?�O1bR𨘙AR �04Vac^9Wa)=j[+HJiAКQPtrFwGsXNxXH F)2|(6jXbn�Ԃ߿ۉ%spZmpW|B LyLJ E']6 sה}Et<HI9σ`T�A{"shlOzIΌa͖|vzͩ%0&S \hkŨ*A:ed&b0hҙt='?T*vh<(-K�ZkzgOkqn!"ؘIVƎIR=.AC\z.G*8(8BL %<6'JX5ITc�]8`(0E_R/Wt;jQ4ؿ[#9֋42>fDVZ3 q)PLP* /_Ck+o۳st�2{Ç@r;D^᭎<d)N%`#Uc)t=_.ݕ1 ोT_W6hk>zY\9lC]1>RD>Р(w[{+ )ń_`[{ 8kH'Hw|_˘}w׫ZWNpŇ}.if@kw:eDq蟅wt`HQ&^7Ꞙ€k{ۣ�>> 1y3 �_cKghJ צ!{~NH"2$ˑ_=aOwYCU┴`=3laa@4 3CcKGbHl!\s7Յ;)xHZr3]ytC~hi`keZcΫ~=Zs^H}O-1݉~u  - ?yyV[:sbenM4_ၭ⇽؜ qpoýB Y2 �^Y+x3`[OSsܥYŠI`I׍_n9-y qToL^jƵoCGA6`K]9y(RX]�c@9NL� _wd? fHa<jv&m/6g걷8zyS`7^Lb%n[>hIMP,Րogw)cQ8[2UQX0(Иhp3- \gź}$.<~Mxnz;a**xXVAR_C d�FAa/wO�@{nI H�@ۓ?)  @ H9r.%Pa9Xs Rp6<[Oɐ�#v*S" |00#BS)TVSx})꟪1yo=ԔF1!C,׷Uc ? tRVV ]NnJgUxR?L&R1x {6;?HO71/5<ѹR]D՞/X }yo!:'ԙ,�!),Vc$*c`yhis"1c@[!�wⷛ Me7+矿}PiCNc4@VЮ}sݪdFq荕+n67wR};?'7=>Y5&|qoЌ935c&0,@Lkgp6cM)�lyfjq 4Î _0F 0LF4esSjdž\=% u�U/g.ϾE3(JôÓ-3*\ �'#mc.}pWV�S:u2�K#U=UuHq�J9ps\?\ �zOf> �rb3j|I]o�];\����IENDB`������������������������������������������������������tdesktop-1.2.17/Telegram/Telegram/Images.xcassets/AppIcon.appiconset/icon512.png��������������������0000664�0000000�0000000�00000134002�13262451251�0027364�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������PNG  ��� IHDR���������x�� �IDATxweGu/_'9M9i&' BX6H2aлw}`_6� (gF0I{zs>3'#MOصj*B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B @VVF�!BSiYv=WSE.2D�!S^u034�40 �yb}~(@�PJ)PR:J8B giϥ&!�BfD|a/~g 0Ƽ`ý3tKnhdY4GaOkO ׌�83}`<�3�08%6l(eAJBɤ2+;5ĆGb/?6"N+%JJǏ<}:q,(TII+) O+x] _ !�2}rb BNƽc ^٬kd45tsF uA {n`\c�c`�cR9fs:OV00t$ҒI AJ8'ebȳ;n~ℲF[/ CrղOL!3�Bd}�cmMf, _tO7.= c NA '̹m"c�K602nxҗqF"1|9j>:]ok"9}?6T<2)Br(� dj q=7:ӍsaChE n,>6g.4}il^ Xc ۸Nx:-!3`z+3";"񗇟a/'|וXak�GDDBH �Xc ��{|_ࢍKPݲڽPB`PEsy:8;rl+e<Wx>cڃz"X2�ّ �&igjHCtz~Udfk*=Ya@AZI@tc_F.ca&mY2bɻJ?DǞD|d(}ܕ�Rx+{ֆȚ=K<M6hs CYt^={22Ac"64*F#z4~GF"><E �H-)˗�$ -vο(vF=ܰΈ4s.Stc_[s Äĝc<`~򞇒?VNAn@dR(� .pO�hP0p}O`ڭ6-=[hs7tڂ8pt=Crl d& Z�j9sz1EwWܦGjp03Ԏ}?� S%z垡o𓿹7q٣2Ox2!T>Yp}dkvu- 4i% >kn^8#z;ΑyI&cqLq" Hՠ�T7[�c5{6Ebm-'E:Ŭo>0/~9}~E2`@LHE�Tܥ]@zNllon|Eu/?o>=Э;2Ui郲ԩ7zۆ#)di5H�JQ8/H-]t]o;`5/R¡FL[=m_2_U‘p7ZbȮ$)R(� .-߶neWzsӔF,t<;7?~S9+R~(� (+oKv5\wz_PPҡ9}28=`CӡG)Ш�)s�r2nn3nߊ͗3fzqOtpX/]ÁWB@�E(� Vۗ�P]۽x? L?)s&=x١qo~'~Z G�hz �H,\7q{n8xоJ٩t$elTJ?~_?I 1M�̶†fiD_ݰׅWJd*nCHc04]%x3w%|$ܩL+dVQ�@fK2>ִ>s F L7=^'|r F>4=@f�d&} >д|] w)0ihԌL]}k;~xyDGz[jP @f�dW�q{.kuݍ9-*9'�i%{#x, HdP�@[6M] Ma׵iZ+c~{ O!?c#O'o7cPC_2]2*hдw4&sM'Lqp"6}?:y71>ȴ�\qYz1|lwfclF? \MgJG_~( Ӏ|~ 4}ߡC7}fJB rs{p;w߿gx|L �ȹs�63<zk5_ǷV ʱJ{Tơynڷc/=� D)�Lo?3)ocX*i[PvGHH5'�'6ʙ~?ʿX^�`9�LŸ Zȇe* :2;~N}Ŀӧd* �2~QYD2F|)Ov>{rU3@�L0ã7K[.VOs:| nz׷_a @��R(,;{'L1 zW[??сQд���$cp>mW4^=BBY1M3}Hү7?-kl @jviu�io!luIOHP ʱ`D7_noעO'PǏ��6X_Te47i%(ɏ K0sB3H@M�v}{ow|Kv(%Rmަ} /67KOY<lOj�'�Xś跿ذOB:i'K{$oy+ޮñ|JIGD@m�Hn>y}>mOkf-&Z)ӍHx«v.JS7 h4P�P2LomX8cJݶ+?d{DH5SJ@̖%u[LDFFtn� 5ꖻOvǮ3iH&c')Z8bۃK%>4h4fP�Prmܕ=7MT6!)e[YQw/b/<Ӆ�B�Tܹ~iKzn} !ccȆ,Oy!grc7+A@4:�G}7}j/3MkeBH-xɎԱWIPE@(LKl^:;jErTuQ-rF11=I P�Pr7a�Dۻ>~M׍'uBF�Z`p9#O?SJ8*�/oߨo v}JFm!) <m{GKaД@ՠ�r_iќO|;'# !F967R+Pȓw?%xVnK{>md2Vڣ#T) [.[?31#�MT$ �*OnZMW`BfRPv k.ٖ8ɣo ?/T �*G&ol۹o~nە.1'8XЃκ͗M ZA6T �*GׯK,{R-Bjn#k^nԵ:Ï@:JW �߸|]/jX&SB,QJ8Zp+c/>zL ?/NLe򖻾qGsS۵I|?!{=k/=v}Ӡ򕷋}4xTܤ~BHP#4'О:v 2F@)Lޮ E7c}!F hoS9{MV�6N`sn|Q!Ii=o7Bÿa@qPr`Y�d(:Ths>u2/B( %\m ?v={n2B@yv@GϚ78%T>{>s߯ebB:ԇG\�^jct^WMkVmCLN۳duhţ=kg(((�V2? }C;>5A%!\ X0E^{_LJ-LJ]|^Zqv#T #u_|7O(9 �J#wΟ1}铍DbC!(@D:".[ 7^P�0znn}EB64_)о'NyT0d(�]y[rg~kEl T;%h_]t'_[/ Jٓ|AO-\+orB J 0xh'G{ 40(�0跾t"6\C#٧�t3Yu'G{* �f^a跾^DBH L'_2ڳ/f �3+}[_ O?!�`Ꮼ۷/ˏ'x%P0+(�9{n"5C 0Dۛ|Ro|8 �fFR?#_Lݖ+o9B)B 0?7~Grf�/4w~lB`7pɞo::� �o/c\u} lQO!S 4l8#ÿOq (�F�L-}7|DbćF!CIݷy~eG6`@`ZP�0=r Mn{r΄i ޵v~ {4�eZgnyG*Sqv# =T\yc/Ji `P�p2�n;w|G9vB0J0{w =xN 4�pd7MkT)BHuPoefKOd%L7 �_&�  w^e[%>4B.ҶjmF=P.�'�mN}'=u%>4BNʱXbo>Gv�@";"@szh欼D&%>4Br%Nx< ' �M^8]lz 2>!!'r mzPy�`r�W"1B>!)=pxu#OЩsFe2 �vÞj_V¤B?2˄=Tj?MٖH_C')�`jrM˻>05BܕBLP OkB~_A)=sUIBP�pv}HRZG! k61}GM JlgA4]׾蟿JQ?!7^xRlR @�`r2G_u!|(𪝛Fv{ Q�0 �&VKۮK2D!eF hHygK v� -/}_qޠRcl鞯G1N'R? �MZ.hl_TBdc?F|!O!H@ �TMdćF!dJ7n?7O"Q�Po7]궿#"1JBʱym]/葦 yt߅Ta(ZNJ!D F}[m҇}x�r5~`6T!J 5&^MP�[񁿻5zм?!T: 7n=sv~ЦAc(�_m@շ~^h?!T<)C_? EfNO�d �۵f $mC!@ y{4fZ�2:u/ΊJ!L'?o͆+cuP^؍oBO$FK|XBiHS+xS#�C?o͂?�adm~!)@kj;w � g4lRRܥצz^F O@.W{ 7]5BH q~򯍆&djnF�r៿fq>-HjR#inՂQ5wF Z�;w7`B-J8Y& x0jp*�2ɻ6^VB�(%zt@�gɜ?-0DhBj0Zڹ/ywm#jfU@�d<4O|L u)!HQꛃ˷ 51 P @p[^=2ABH͓Lmj`t�QRyWɽO!pyʶƞݣȮ �x*G�2C:�?cw!r�+C7}۽x<(@ՎT@^/+x6#r>Fp9( n P<0gi^> o !PL" AzU: P#�� _}>)| !L麧sAoJؙ%J�Mwɗν6J#2%M]JGP �dp�D˻oPhkU*^"RwْGI_Zu��&1oyW e!LЃuuF.d+UTc`:�`[npB!@$c\t 6@B`U- � ohծ nC!%Mo?izf5@U�@ci6]y-s B92Gpo|pGjj �r+n8$S! 3~TY�P-I?{|>9-\KBJ80So<|ඛU"F�r3z{nSVćE!)@6k8xro *-F�2Ñ?E S_B!B /=8Ł*zV�pk"[�!2<zOe*B6� ̲ M67/qY7B))׵s<*(T9�nuWMKc!T#e'QڛhS3g P@zn57*Bʱa4u/ˑ.88y(rg'T.@*@(G* U/W)e%QHSmQETj@fq !H(6>ïstsH( [*p+"JB67W?*.^8t�Ndo7Z{YFE�� �IDAT!C* Thjǖv?DM4ux58s|ފwL tP�Bt57?CȎTTP!wٟ}МgFse[BjJ'ΰ jbOw�{BX\og7a=~(<۟¿02PPP2/cM?s)TLPĭ?�nj'%>,BT@JS[8Ć^HjlMY9Ӛph p,PP"0a;rX�ldJM>ZjBʋJ'_ + jAs"&8l8SHSpo'$0Ě&/z}1KBtJ&k%<<*0L ]~BYI  !eAMc _gڅQ|l]޿[lo) t ho`Pf`I0ƙ^;4TRغ5nuC)^!3!eV-~ acTHwx[_�:i[3vE篑? PS�;I-+}V6G) 69ַ7~t ims9Rѯas[�A)�(Ƙ~uoA��ȮGtfǣJ#̞J(a^�v Sg½^L gQqDIl>䛻zi?(@'VJ�0{:V\%>$BHW@_v􆰩ՋO#{3 T a~C*PR/}ݕW~$vU`lQg� 쯈r6!̜!ݏ} 'lqQHڥ;+apKsV2-kǑm(�@B]#dOB*no }< i10aKUYgJhsOx@vs=�Ȕ�8ፗ4;hnc fC8E>t(6bg0ߏp]}8e%|~T< (\ @zdWA!\�L6ˏ , GGcEҧG:G6et[\{?Uk(%�de޾;m!L(sވ]]~ by2 c T8:iF�(xu>�0-w? ;S��d(4�*W0STrC ~z,|h$8-qt0]�("S,rkS?2|I`%�hhk s>&BH]08ֵ']ELh,Jjsq1r�@)ر@I �c4e:�F"[޶e"\̰@ܡG[zi!lƀ be~CH;=_4p�cn8 EHYB^q_LGP?P�njЦK/K}L2%kPհ3!lk%RBVloു1㰓 3G(Eq` عvB/ ,� ܕ =K+!YC>cy{ ]cBґu֝F &t$BG~$/�i 5-Х#vw(@Y�dNj�Dr((#ߤBh ֶ49dzi=Cuxm0 mWW t.YJ!|AsɣoL1�ǽ=TPB'BȬQNז QMm b{g�A@ O1-pd scbcv F,@ 5vobv(ir �r+]k͖2E"!B1,7'},wws|"qK`�} 1g ¶w�K/%-aer �k㼌^/B s컍z_֎�ŇWL$=/hP CIP,`jM :hIA RO-޾im4@�?L6m٧T2Ôt1ɱŏ=! ;d1φs` Gi}X�cAbH4CAL ۓ<SHm(e�hKLJ|X"BQ{Q{^xuGY4k $D@KO(`?3(!/Rݤ `(+N QG6,c�޳R}T,BOǾ^?. vyW&pd s4y̿ɡxQ/3@ f[: O(ɀe�x6!89ey׶;̉T{1-`:cgg3T6м~@?<;vǟ!@]_r �@VܤEe dR8cV6gV{1 f H{�)V5WVp[rd(�LY)V3_Wvj�%WN�ڹ2,Y!drmzG_ĩ^4pl8 ү*턧#C(Xa:^0%lM] 7,=}(��d׵z.-+=E=BY,&f߫1,iboO{XP灡1w^O7M^H H0]7_+s)YEcR#CR}n]0oyi.� Ŏz ) +0pqg�{CCģCU^ đ%7Xgp?r~eFG`D顬+w8rd� ҭ;J|< dzva[:FGW�!  %@<p=ϥ/$@i{oe_G�cƆ7]~+!|d9CotK($A31-pd ]ӊBL%aoцye@nX}^2�BJ/\J*ӱ͏K͏f^O0b;xm0 &iw;R^8e[ڽ?9{T�/ܰiWmCH)dzTHIΰً}鄾9QgHQ_cxk8=�pq( 똉K VA#�c?g/ݺzR@BH0tMl`_}<5# m y!W3g~RP(kIJhfm# P� �}{eS%'" ڃ8�:� I }e`IG"u�NI6N*` `ΙmCOD.e��u?2ȍ$ZKO54yQ{z/;_kܤ6O*U[VXMelPEDjD 5&@H!ene2֏O2�`cq-ټA jO22g r܄f- aS >}l~k Iˁ QtcCx5Sx9Y eA  4F΁Zioƹ:lD��p7z:W]!z Mx􆰻;ވ1$Icpl % RHœpYrs Y $k#w'l'ϩPnұaņa <~ӵdc@v@pJ�%e/ТM�ra2ln9c ā^w&YB욹q-pl GiF6~Jk1L9>+<γggiǮ q`fFt H+>HB �J�d�ZM̹Z]oK:Y:hلVz1((N@-9T6?ë $ mP>0 z ktF S0%ܷN549C'D �J�h�8tŎumt>Ʊ> Oo <h IbsxJX (\@"768ܟ@Qp v8cLHcup9`�jhWAd ЗJA[;8†V?XZ݄3N %p|0ǑE~ ΀^2lswS|u wdu J رaaPp6yk)�˶. ͒RTno?486p7t pƐr$Fkl?W?t֙Fք~-u1 ꃞs�q[@"�fgڋ 2ΡNP[LVuSJŽB&Pv a?\H ;r>R�hY ]cjEHPH'vu/^ PH9*{8#qr(!Ԅ ?74"ܺ;C6NIU 3*x K&>Ş#F�IA=ڼB 748( �2ҋW+*57HnI[^ ;8֎�Z:,Q۽ l(fᭁbI6CCosAQ֍?�`)6yxmxa g=6^$ PRB 76nY4|D� �8�p_^o[$m!JjW椪O: ^aq{CF%pz8 mp9G/�tGܖ+�ąp<+%>g0vCf0s;P#@ٶZM9Ԟ>-A0ˏ}aoC }Ōq $,Zzid !<V (�'`KOP vƿh@ƿY. n^e[-x9~UB �J�m93<�Z۷;28.j`_;` 5WL&/i 5hjJ~m~=!�X8cH:s"466 :ƿYDP 9u 7<Q)S(A@)�1y}K.ZGؼrsވ aYC6/Yk"SPHb6B*i B㨘p?:qKՁ(q.Oɿ4yd�y4j_' 4@)r�O9KmKP9S* B > }[:hݎzeRc(]g*~�R9CgCѮ΁3q 'FmhA7TP ��$?dmsşèffAIT!~�B }>aE{¼:Ɛl£0_ ~M8cRp'rï�(>�@AITksg9 |s|#|hks[J@AAzi6��ٹp 2;*M� ,ւL!2$\qf Ho³ŏn~Kd'o4a8FqTҾFP*JB&)Oe)gY^+8?zq./O1+@Bܦ(;߂Ka>ڰxF�p"0NR9 ꥰmXR"q+ aGgA[wԒqX38‰N % Ԓ'ݶu6AJUѯ#W9uT~ �0tzIg/r2@X4_JB/vj0�G�oJgWXDNjD2LGI`pu&􄰷'% ^tw~HB}܋,L}Z6kocҾs|&kG1I_> W,q"W~EnrGۂT _مRR|6u>t<RL0au�"#KS@K@%n6?| }>3X8ΌLOLie^w8,#IpO� $6AsR0?Q0|~}'uR8p  P hZy!{xVV�?:] JUh`KXv61w]J;$x['-9K_>uR)xt =M!2} 4-sZ?.G`ܯMee{'NRA|F)ms#Z"l�d6wyZ ,>=!+ʭ9CO 􅰲ч&݄ڳbp8b)n5u53 JV{apWθ _ٍg8P*c. ʶv>3<AeFQS�޿@q_`^)Gyt>[*{5tn%`@tB?oy}bҾs?%mJQEHr�3}V˔p$=^b|LCN0y��wΪ9T�NH'yue^ boOx4w?nSRXAx[q 'έO!!">Mni_,I`1$lW5*O|;qϹC JBx™$xӷ%c7e0f9JX7l'=ck/ bo[5( }jg B)PBI=!t7q6 Ġk9mAaJ�g[ܞϑL3W+wepRH@ R}s=rfl�y kAR - JgB!0w4 OFe4aᭁ8F|7|+[΀31 G,pѿ8y3M?JOh{a7ک�<+@AIY)?Gsư3fQ>6 gCqNB* -s?:Sڷ9G{]�`]x? vz:1:!1+2(P^Ai oD2 IT1ǁoe7n3+y�}dp0I` o`k{#GW#!~J^ق>)})VUh%Sڷ�е.<5 MLX`sN<zcT* k:( }^3̀Ciٜ�3<HGsVHܲn2.jb_o;C ݠgUnߔ-qb0yh JN-W`cʼ ^�U4x?ss9")': Up$4To312%�`lNhp"W@ʑ08C_ы1JJpO$qb +], J)71KNU+g0%q9q<ƟMgT{~U\�wO&;b T� oEYzZRMY,]WB(ٯcso�¸͏FgNן0K/ÿD4੊ҾS0j L@^:?g@X)4#C)yzwZop A;2rW TH Տ}=! 7lB I'](P'㰝 [ڗK*SD[uυ&0 Snz"W`�� �IDAT흩?R 4s_`_y 03HT�P&-CO ُaIwLM8h R)x !MwZcj /!f 9[~ua.\[sP@UmmU0w"R3�exlhDQ�)-Pӱˏ}alPYAq*=ܕ <^kF)3qXR^sF;Wd*tl� \7±ᙳ#g)`UL��zcg;7,|7 cC>aE܈Ɛ &`$aX I'tB*}&C05^?gnġ쾈y} sAv$S' T\P>IqEB� FJ::[nfkS٘`\c@/Hg3tLl b__key-`)KfHR]ҾBҾ]AvR^:^7Ac>Q?1-&om& &>LߑOpWG$M_W��1a6)ƩVXYnY:-! :N@,q$ /S !/U]w4pgΉ@lzu$=V\[c&NL,0vIwʹJ͜ @}o;g,pV  X{r~R(x5 coo wKPYޒ8C8>Gh >�%Z>ܳ02 t׼+ϻK_0Q_ق)n1<QsGȹT !�TnfhG@��?hkg)IC[ 􅰾ŏ:[(Rb>%qN`bthP}331$!ۓWžw,?C Q9J{|ے|ѧ?g&� /[^n^hw)kQYRqƐH986p-5]K/b>fJNc@xL"q� mrѴ7gG8)/�() VЧJ)0Mh77QO5�KaO?3ğyo~{B;S1ү?g B*IP#}) Aԇ<R�Cx?7eb}luy.~`E;dURALw`m=10h,)�0a׼:L4į� l`oh P2}cq&l0fwWpt7 T�wԈc#)hc-V+g*oY{q*c?ǙSH lh>G@]NJEQ T&/}: K#}=!;^%pj8C fd?C*CsKF͕*Ccx{ɒucsg� !)(aci ƆBml(BҊJʀb�F b@ 0~wo˧9gWeuwGT̓'2;< USV+ؿy隺ccC;0>0@݅c'XM1C!|KJ ϥ=`/'Y#hֿ]pY(/Z2)ͳ'gNi߸pD^ 3p-4<]LNv׷icXv޷-sZ'k1`�gc8{ @IOC;d%ԸkW;5g{cO\9\~*̩Y&RwaZ+4Um `~sDN;z3ﭝ`%C#,a-<rs@F")OfQS#|,MRa~m,n(Ag.4;晓ӤA$L�V$UYvOo|EIۓ+)zAU>;M,qOBxtO7$@ }/kLHúSOE~#G{^%]ᬿ,nȻH}Z!0iI}cC+y<*5|G]>N38D8׷wjWwDS#4H̊HU}~cPI $g0,*zGҚ9=˯<7?3˙dqBO7}iXfx Ji߸p9Ϡ)":b-]boOEo{ff3F(=a��(O8<_?.~CJ  eyY~$N";n}eōEw0c9udǧj+A8"LfRֹ._n8V̿c!4�Zm~_KZ^{c?AkD`AMN,,NNb=SI3㧧99p!7VP"ְYbyH%VX(M}{�^`ZDS &oWy2ob+` *=S'zUoD)IFAogt/<>/=wO?&9JBY?vJ%/,+># c<vtkT^!"܀J`43oN1>cۑi~֌G&g;Vv�v$51I$oh7:r6ŧ|(/>>őJBAC$購U`m鳠O#"i'f9I&J`qoRO?i[_׿)4ӂdHGO} ͮNۻD[xF!'@R-:`�EdEm}_3)~|9FR`&E]oU}iOfn:V^+-zZo.32oEm̿l`@ `ADDdeb Q'7O[쬧#a ,<> W?pxbT"<ý{R,mȕ*E(x G W<T0.ɮ]<aݭFh{#hs)B)R -/3$@D� eߠD sGx~.${߲-Y셂>}y,Ӊ! 7 յRZAVP'&eR?n;4i\=!XcoH�+'�]*PN?a lTJgf/hO>=É $!݀l?ɴ'gH;I>@DȻ7Vt}5#v_[vIplC?mXFKy긽vujo@e+9C(UMg "*65AO0R-s,͇矜fR:+[Eu>~cH̩Dw,<oqtD8¿2Zlo#so$U&b q(t燐}B9jZc*Fx}XD4<�BTSP cw?zDP n@$J>Eap}TODw?(ŭ<Y/@2$͜o/?_|3rO׿v5 81PQlހ<�0`3`ڐ4gf�*nd0>HPJv"~0wXd8sbiky@�@[&:��@9*H<�avcp"A\eq8pAV0rO#%?N#m47|>6KoѶ M[̩Y]'}O؊0QE׳ "IʾW?8?& |߰U`=[O#"iSs<q,%KQ�VYCr8ohۮDFC' -u_Ӷe]ڶ3ìH309�5x�vXNϤ[?8S)MO A$nr.Kyn0pAFPT"Wh2:ۿiE> GKؠAV@~;9�{VȰMO<5&( $(%A\ ӈH382HVZ˓s#q `-/@Rp7or=G}&FS J}`C� zŒVӧgp\ÀO =gK,oq+>r-™Tt>9İRy0ѕ(U.ݗcm~Ǧ|}tiKMԶ, nP+d4?|b?Z'BHŲfǰ}I91Fi ({j&+Ctﯜe }z.Ҷ27mw~;^Oc;&{uT(_̤&t9Zl3~?h>9L&H"B󹾖't)rwߺte9඗_ڷ Fw dZ,`uaQi_챴Y$WO#LBit?Hh%l<.~ؚU$߮MfZ۴aZ:A�{&a!qz6Xc?0nY.X;F2IDwH s.*w[oӒ_m90Hmh:Ik[S1˾>6�"`�Ȗ}ޞOLrD4\6 >2ҾFrd2S'fH%ҾCJkB�f!ؚmmѭmCuصjӾM=9. 'HڊK#ArWlY^";BAˣ<w\d 0 Ϝђ E/ju8HP\t7̊;nnގ.o4 HcqVT?|IDYFffRO%'f'x2*B 'KY7o_tw3kl#n?G_SP jւ5 IXk$;DBEǧ* �%h%yAFHP7Z ,J-y A麘@n};Iٺ2ykm<`;igv6]ouylZvs6X,Ljy�7S2|xaTܴk%,n+$>J�ԎEZ+{A,^G޵ߒjFTgZ;_pxt+Ю9"ЦmnBs73jSۥV/km�5_4&;|2J`&qT{YHe_2-8$CGt\_)P7/5)ftkS+^̿qz;{{@ùv ћ-TAP˗!Zkl`hL8b~k#N֊oBXV?<}|cDD yjT"'PU�64f!#b ǽd<B@<�0@uւo{<>Y~1~#͠PN}E/,mɗ=FIЧֆ OT$2aqc+-j7ݢB׶(Fn栾a:�ovJiIZ(k|O̤駎駎LA(FXgX.-c c-ix 3z]CGJ (HeL[)꾺6ߩM!jk]ɿ-ո_- Jv=m8w~`\M}{U G(0-ZVxe/_[̑ (p?qzicDlei@ FNЧZ2)ON'Ҿ#V\]ɓ&Ҫ1o{k38ރ<ookEўpNQx 2�_CũwM NaFLc-,d|xyfgss,'SX,e$. gz9h*2!ѽ /0\[Ԍ#`{sƻ}c[_%?�l8VT$5 1Rc-J:7K9{lP1~&CD*%|\"JHª }g'<urVu3C%�)XlfSSҸVM1.1ZUKwվ ~PEb0k� om"`/!-PM0*k |$<{?;Ǐ>6͑o,^p<Yd+qp4ZaJʶJptڔGhl)4m:T̿u;o}N}m|{״&Y꤀�.�vx63"A/" : XSI H J9><:1ϟ='CMk<csb"IJ]beG}OXfxD)'hjkBg.7oߺw6v= 1Vn@c0I \ܰ&JJFŠ$jD3` ch1K9][#9>1>xl ,9XƀHHŒf\eKjak,'Npn*זs|tI)!I^rq (ﮃoP |}>gpBC@!# ~`x6o<3o?⣏gs̤n`xk+%emv?$ZǎM$. ڭ�xhW"ml8/xnnbޓ4f K'bX2Ƹx&&S|Y>1~<;7A&(?2F?mu֟/z,m(|Dc|'OsD9}pՕ|�ؔU!yy]7_׺8qޮN|'Cנ],oyhu{{Eځ;P*Q^)&gh"IT 1e&+@ usgSi^<(O e䥈#A"ƎϬ"i_)}GCO%sZ t:2tlBs3}n6N^ݠ~߷JA�" '|{ AZ Jeo<6S)'GP8Zo;ﲼY4>}H'ŕ9/ N\۱̿v_㖸>v1t?"�cM7 `1Aٺ%&6,,(<S_[OnlE1H l]Y?~)Gq xBT&BʏqN{ۼ5&6ON̿{lܱ:z\ ZS.5\�X+۫+2F}Z!V1aJ\Hf3s|?q'*Rľ<m$賙-U*}c4뇊y '!1Bȗ}./ptU,yljGӭm׮fФ qy�� �IDAT^@ŻT?;B�2s N bMٻue#?JݎK0eK5hZc`b g|pygNRg5RĮo@"ABL;n~2i2 Whp]gzlO]3lynb^@f{1yLEPȖWVk(b`Jϗ`AcuU}RA J<7K8;7={?{>1܄oRJc-EVK;>Zf&R91$`jmנEm5'߱ÃأuߊcB%+&%6"&TkcHb Xno?^XJ?;Ǐ<6ъqƀBY,/O+rd*͙(% �.(xl�PB;<v+X Du=:� e|? bG}P4FEl@Z i%kb}o:1=·NVE}PZzH`Xe7lǦd �][T@s ndWi6-ko$:>A[yڵ޾ch{| ^&j bPI[L$b@U�e-�e ieHk!0 YޝYK1>xq4ϕ\7]?5#<>7&C% �Sw޻Wc'ӯD~ڞQlvq!:{ b6 Ž#\ʚRnUOX0ˆ)bepmxgުH?wd:(7nȕҾ1&~K({jnSGI>8"@֣*{Id%tqy7bnSڣy0*�X[b&!Q΀(&`- zoS)> CGX*Qr,|bnG'#"`Ҋ9rO: muXYSMע޶z FοJtmɿO`uKjNX T&{BWͣz`Q" 'iN!J3RL57oo[ǘ`+/O XbI X, D cdz;6F}<nd"yz< kOVaݒ>je� �^|nq j]VD;"i( |$ҾiN5'80P"0h,S̕{\/B/ۿ<?w8od" �$�Rn& b!\:):)D;_)P }N07AXͻl�jBnvto'g'X(0d]DWSϙ}028m 6\4:f9Ly,3t"sҊk+yT<9xK=渞ιP`{!�  �h ydPNp Py [j/XDS3L$@JPG@UbؾA[NA 1ٵm[Ϋ%qa;Aq~U>LgR>1Mu3q#�CQrMo-]k4Ҿiվq^2-o,ޯ7#`P9�J ۫wQQ AJkX+G 4O!D (%8P%k0ߦ v9u>g>r!#"X5&~r!Q! MOB*`]@WV gFL'O-,wJ�q,H6uR+Rt$8u']]!mv ofݧj�*;�WD)g+N3i*F "ă&= :&yv?BQ-f6p@FAF%Xmb`D=6%6h.,#T}Ns A`ósNpguXO 36eɿ7#GYX�KA~c]M;̊ R]##],Mqd?P"܀K9iڮ GqlNQ<(V:t}Lޭw޷&(2@ �l``Ŭ-l/*�$ a/{A5}8^%RJX̕Y"j� `gRC:_I{q:r qcP Y4`fo$DOG\\-]v =}bcT&Upe9K֭Ya-MI H!Xcl>1:(-n{oq�=� DƱ|͛P z(.6K %8k-h%>>d `!�Vuumn ۸ﴍAvލknH|#m=_!%� ȲIkMhdL*8'Υ�L֨\Y(CJV mس^Cֽ]{:.1rW_U�D<9`@[o߷ٵ5sqki%X\X͓RJt.14Scږ@B <nIigwI $uvw[mvmgUx݆V~w;=87cL�y�&XdoEVY[e!$*nίs~`v_}4\ͱQP�� t!wk>1: 6făTeݛo]$& *C@!\Xq> k&o\f.\u܆G),5 �.IvU_WD?iЄJaroRo�D_ NRj�{W1$cORsy^eZdr4o\&J j{++0ns}bRo6:^2{q}D6n  U�9 S6J#ABk͝m2MuiÅ;lJ|SL4~ro(se%.DhCi=fwZ p/jm#Vw{7_C~ƔIf/J�3 Z(z`;KAv$+&a&{ZJ)-oGea{Es9u_3?և yc!x]|�K�rډ(-( aU�G+%޼1H8Hi\(�ʿ$Fy_g|i~+}]1Ϙ?Kwe �aX!0ZW.M~U&H"ݔ,83!ܠ" +Ob2$!hۢݽbsGZ{Kp뷯`{%6㟱g4!Jd?bHP]h[%3AGY ly+y @ZExegOq1C�ϥ)uy4n_PGjvD;&M]p-=eTv9ϸIݻx&CJ��> `G7MvN0PݠnP_v Bk-A`bъB7o,p{a! 4h%,dtx֝?1ۨ3R3»{zԄ� �cnzw.'Y(.�i^d:�a ޹Rc|wn/R|])..�tu9G *;3iv�j"M%΅sT'P's^8, `% W k_OV 8ךDZp5Y*Bduk//*//(4bmV�? Jl,ݨG 0s�"iֻM~< `!ֆ+U[ Y5 78Z-y<}矜C) z1%w.-p GqkmȿUhr7z0 ƽ{6𶨏V�Tlxg>X `75Ea�R>kwkRq}aqs|]?ܐ[s�Z{!$H֙40 kI5{w%HKDORJpuHJwU>?{CZׯͳwP֊kY6K~0(p@kLv}ӽ%"GqŰ ډ, "쮭5L\]lv_�hE֍n<\{"cRBúo3m[+*ֹv9`ޅ`ca9R8Pk7^b6Wdi?J0ְaJ>)E_6Oh%Xk`|}bC (Vu@|dHymjԡc1:o|qP98)W^} crT1w<̀c�c ۫k%%oϐVżϭ">-6-z9^ō="l|[pPVnx\R/-=soPI#�ڲ{IJW [͙�OpъB \{IH`T(j#MX!^?l;nxw/ܮli&(m�{wLn# vj�h.CQ}[*"@WFX _r*DJ+.-yE! b\H$?txxzM+�Q0�vd;E+5!# :J0qgDje%6*\E8 #(WO3~ד/L{7|F {:Q+`mt{YOv:</\XαL11XxW`mЛO0 (i깴HVݯqFܾE]˵Hi~q4C*PmİGKm�wv)N;؝Z+-(N4J u hϓ-չ5Kyo+_<x)lP� M�(°Y6zU=WV]T$lrveRuQ!V7=jT8m9RK9 sMl8^ zlBAh5#亡ax:��K{y#?WB$#, ,|~ Dg (y>^`+Wϝ9uX` <AuømguC,%]Gc.tŽs#l}_}&fseѽ6T]C4F!>yQI /(}J0 3Gm!R5O*bkGg&̤vr߸F9@k�]:`.}OfA׾g7GR P0�T!v^,߻T$=њۛ%.> @a hlXxCl!C:J<.c.KL% &某aq/ !G?�y�UwI%%"z,Wr>�!Q({,r21I@֊Y s"=x?q6 !C9E?2BDy�E"˯b aD8 ' 0#7)D7x<Rɔ<0r#$Lv}�A@>`Wn7Q’zw9] Ox/`"Y.r/wI% %B#u<!C 0-_ yͣzmҨP&W>oᤆo&%KDXw V @0J� ox |}Kï]&WLBa1[B3ۨMúo7N�_^9FQ!c4a5@鯊Y AC/0Liӳ{ZXP|r%wrߐzw/.r~)dJz6Ǐ)|cGccL4߻ƗM$3LR~k_t/|eF,?(y�Ȏ$X{k$& j�)Vdض[R G0湺#*tZu~o{'U3,dк{"VV.)v7G%?Pº%|F?�z�&2_&{e 2*Pб ?6K7s]7vBHWW |K20JcDa+טVPp}yclxG'Sx#R8NP"|!y? 3X29xŸƚ2P&{0jӀD@+o wM{kr~OO%( Kޜͳ^H9@4*)_4vH)?}7y,ʒ +y[y]& t{x&Hxd0z@�)lg2ýcGk.*; pÝQo..QDa*ԥ" J1qho޼KJ dQد#\YʱU2 ᤊS!-c1hBiLv}ݽW96�0j�ԫz-7l&� v[ kX*\/ޭ�fXז1ƠuH;Kƣ+e@iLk%o|T:uCȹ¥m!hm0@R[o/X}Z�Hh�߿8I2`ݠ2HP R~׽Xkw4P,7nk٨ ;åK>o[g&9I C$̥éxM `J?ڟPȈb j._*i]9jm7@G;8nVl}~?xEZ (M MEé*4̤5_zz C DX+x\|F +?<'p{w+"#`#S·^ #IL#BxC)Üϝ"]~`&ńXȖ{\ZʒтJ̟ӑFzSBC`2)+/_`5_f: 4!V*+vGqCHȿ%,׿j#7+`/ LY\RO?OGO%<>g{*T"<| }�Z߹l=aQ)( W;i>xr&DJˋrotiʷG&PSȮ/-lo7F @S~+_l1IL;o<7fOJ@$7{jLZG/۹h5f@pj"b&xkqgϢ2H1߹< wE~3X}xjUFat=�)f{u+u><'@ zCTDȕ=~YNM bE|٥4� %BJ){/\^e~FTX' H_Ұ=T*¾RJ^ztoD4G E} Ae2l- _ѦA+D!x/a$EFlG1Z/[zK_L |˅Ll-}.B3 U~A'4@_11 |EUhL8/_z_r{FGw`CJbEG8F~OwB㥆�� �IDAT8i^nsZ��.`ݫ?~NR2 v R[QLZؾD|G ^`•%vk-LњqGҖGZDy{'Si(`Lj{@8JM31"; ƴ ) c4g�?T./%� v[SZskBʼnY `a'ipm�ng/.MRPHcL<[W%q4n~yUb:?<K9r4٢]aB8ijeS4ҿZ�^�}fm~b$l sb `MD&b)s~q;\OϠ_Y!ɔkWo\f*턋p(6>WWu+)6v?|pҔڗlN8Q<�#M0%X,/I>@]V0@ݒ B` :�Fϼۛ&(ٯ:"ZV t;y?<ǿ~&Z&x@Z+nX-x O !`Ko˕-&q0O> ~ů*j *^dƚ7!$K9~<2?jk kQ1vB* {,A2#.`KY ^ HBR%�#({Fv( TΪO9 6) LR)<w4ÇOLu(E<rEw_h)nˋ*?Z k*넮B>,VBJ ח|NfCCc>fTʉa=׆cBҘR~3aȗb �E�Ѐc6/_3,)0gNHZ"B/{�JP"(-TWky?uUƹNIk;MF7/Q />u G8pYW}>Ϣ5z:uBC'q?,?"F/8�"yͭo:O<H" .dgH=sɔn/'Bw G N(_ȿıwoҭ4j>" 9Z05,gGyrvό$+Ҏ6/]\Q= -^]^g#C@KWr!N;$[Yhpe%OC%�yZ(x߽w(Jȿޮ87'TJm]Қ;7/W.28c^%(b[ t^ $={՗׮RqcMKu}e0K'{e *gsK9]e7�hj7=m2Z~=~oWt%?ےAYh*Y*ta"c(ya-/q?DaR^Z76@@̋WqoI0,H%@I gq�B[".d�&ݭs~E2LIkHf;Kj@p`\\#OI}7Ty..1Z'?tHfⷿZ>OH�W ,ƿ{{{/'^=! \Vl0ֆ0n 0h..{Yȕ*f57ڦ(tZKK L8H)<+MHP@+KKBh�@�>|}zr gP R-{UiG񃇛|<%G2?` Ch2l߼N7LhbUA&FB.>QX4b2 %6x.\p/5IO'R歅,+i*&E5>wyrUCm@@ yoF %oq?ǵ,3gUͭ:tױ;tTbF�^[l)'. 7j<y|&i<Pv`+`AƗj\盛v;;/f&5.3hR->Q@#uoS$={[_^{/ Q*f{u]|qG>j}wȗ`l `+\O9tJ E ]DRZ(_^8:L2 Jքjk toӸ/Qѱn v6T>7o.^xcdp4xR? twݴABc nq;mn}Ș7bʵ^( (}?~67Pz Fm Vezk~Vj:'fb&)%XTEݯhKp-cv)6& tmbkr4ZϾuqyq>h%\?nݢ7B‚&p/ X#�p:\^LāT%pg#GNNl& d .n�L8%~< d k#h3k1H22ۙwnƻ݅kE!i 6 fR|# -0wr{U${ wK_la{?'NHc?a�@u@*X(iLbI0@DĮPtnsL~�[A` Gqe5g).bwh8k i܉l+7h:qvmKh$A(>߼J! 8g7%l]s.f$!d&)uB1�Xc,NJ?%H� b@B+-dK=9%g %0Ҝ_;ɖ}RZ7mn6mvvuw?Fƺ/aEaQ8>>|Y=>o: GփM~"xbS76Ia-g k ,|=q 3VL Y(Rrkȣl@p�-2mE ^ֲ!!V׈E!in!Ï>b8talv lHj˟op_;`�@ir4WB!c/C^/qqi mrn@3_˼ro?(vvoк? B ./d /XDȻkV2蝲⮙zvlhBR+LD?A1�j};RϿ$F@ )Ųίxooq~ib( []7!ݐuwh>mƃMܩ;>=DJ ~GNeh/ip !f�''2r_ Y((Al�"#sQZxJۺ׎v&.W7VB2 Kٍe2c2B(^|%?!L]~˿[`J?, @5!0mӏ9g?q@l薎1!0R}|vå-%6M}:]7#Ƶt kF C/۫<*98[yƋ麣e&Q"!s_n.El �\�{}d&g" ֬6[, C|bNZj3�W3oC106['g L8;rorcll,!цd(uWsV8@;mm~g.J'X`h,ϥuس㻺ks--H\3 tnOMH@voXъ*eLi..n߾Fl2?pՇ/_-Fe 5�:) ?v7C?:IB`BF *%8$ڍw]|BW򏝫Pmo+yH/TfOހV~:W1mK.KHQ_ρpG8@`�B_e$!0!J#ځk[ qȿv8Cm()|:'f2|4N gEOs?Dʩ:vKuG»7o~w`DZ�!/Y>6<%Q*4L@T '6m$E5�d4ʼ|}ˋY&S<uttc㈽3 qJ o?}|-4PpNyh-7 f gρJA5� |-Uq 8;|2uds9 8j$߰{ȿjH O/ޣ-<c91h&TZ(;8;VUw#0gRAbTDS2er8;a̱ٳgaivmYǒ%9ieIʢ5,Qh&I0$@TUw ]]@7wNN`U}_M@HX M}ٞ 8u}�_P=bkz`Dh�w>Q\ߓP VlIyk|O+� J &qX1] pcę8яPPtPG2?mTYKY?`�5R50m$Շ2ȚNϵCS>O#�K)$LR5ga4,ſ ? G0_URC�..S@)a]9blxs"P^T P Hթ!YYll!m$Ǭ׍۾*Edm~: DHФ 4]i~ߕ noX(s "%vm{ {+Ue⟗Z0��`nf<Os`2H�lJyVћ+_?LJ"IC!FEW1=B{&" bϿFVGˉ8�ʊِLxTjqxZyW2D1ΫB@a^|a<*ZObL)C i3(!l;}WE{}oϿjB� $len1sˆsV�mn@(8C[E˹["S]@|dP+#m}~Ž$�Cwv6cj 4* 5?vWR+SDBaXWϟcHWmſlԚHe euYm-Ky+r�@ٖEO޿C?) !b[~N߿OM?P{^J ƺI @hZ:)U"S~5*)RAIz?^@fߚך Q],lW$) PSsUP? Bu.=|fTߥV @P@rꜙkV�)B L-)9*ş�gMJ+ҵ? 5j�< @370l('͘a㵄Bʴ ڨx ޥĿ(F[SI  ?8ٴ=0?D�/³iHbv{ gwBF!`;sn="Ĩ!/|r]G<?FP*/ �y ]W!5ӸM\2" /0*@vyK{Yo%eF!؝m-=w {knK� � fm%ڼHʊB: ذTmFr[PϿP/Ex_"%>b]n> ' 4�d(c?Ӑ7a R�rV7>Cj>$헻G "W(H5{ h3 7Fx7$H<z%?~ 8B?@{*?`wm56|Zȹ3R&ca ̔.5]@5\J$9.RDElzc'q 8�mmP*aܲi3l"Zj8@k(Wf0J#0N >G@政OPԬ\8W[f eGHMbXEq&ͺx"@2}S~/ �h� AJ-SN=3HBӠ (E/m"Al? yw9G -[(4�q\ojd,n]<qR_"\ǥIBjPH r[`qmM'e@Dskp?4�v Z@@cR );H�T (D%Eݩhm ءhHP  0K߾l+ 4ߟ7m4�QG7 d_=*4Wmǩ󉔟 11zw '1Pm7UU8=~/��tr¹:IqL2q/tB'~4H{>uq8~pA�{iզ/X@R)0;v@z!_H f=n'qE�'9jHƓֹ#G57)Sr BL@?h gg~v+2qN @~]/<}` p=jBr"KW)YNJg=myҼY l͊\i@rWb=3 ҟ;ߝG @xoMѮۍ!83T !$_b XJ/~<3`dc0x?2+Q 4'Vh9,kиͰyޑʑ69ιRſ؞A-)UzDV}n?Jx�@QӗqI%qLHEf}_sAoـc$# {rW[!3[ @d&@N;"͛-Zw Ę@R{�Q@|+o|úxVc(R7 j nY]5H%01C�T,1w#,f"(lw ˴̳Kn]+g̟T ̵ !� 6= L69O,q[y\Rw!ARӧi"=ݏ%B02Ts uA}])f1'T!D &a"ׇ>h} /|߂`#/N|܋oI%C0:t7[on8mM�$B@ V1O`/VKſ#xW#p ; `n阱Mkj&@9V,1R6Sg׋_"s?4�v�,!|ݾp줱M74J"pPHW^OЏ7=B0x ݾx┱MòSD*v,TsO#|X8<?ƛ[԰%F$J~ eGሿwi_Eh�F5!;tӛ1@*pL@awQLa ƏPGj"Ȩ "0KO�5O0 �>(:dw]l_9sV_s`D"4xL@OG2T0?QiDmS{B? @��0.Wδk6 �Ra jp]G8d8[$̸;m��IDAT)eK h_>uZ_}߽"�H 1l!?,"҈'oom{I$}p_ߟ l:/_.?|�2d�?$_mSz[_&h�*C P]W.Wo("bA1 !ЭU89 :İ&PWqPz$-ғ?e(rv!La@RqprM Y_ "NWO~1MkWi+UL1&T!SX RRk)#E|O g?�T `.ڒ[isW$B� KjP[6�Rf&I#G04�c t5o5+ܼ\&4d5R"ŷ']'hſB�A&@@:kq<m g 1@"wB撾1`lq<Hƒr-fB}E'idOaM}l=o['w}>׏sC0x܋@yb S[z۝B(Tϔ I"qFxSǞp=kcƷ >pwk`m1`  XgV}״w =b RQp6w8Ȟ!Hvǟjk +%}C0>H_nkٗ7WNV})DHBxOѬ s#$D_xIy NO+\oA0>O }quv'ڒ[Wsfq (B@ 4HJ~_&^X�_4Nrr%!@*ʐ AW#4J!cd'O%?|8Hg~( IH@:.Qh7� P )3쀌HxkOlRPTy ~?7' 39жռ{?W݆P8@R1DԲ_CDa='߶/7Gf_Pu?`\A0~ 4'ewOe^�nb`jv�ſt@'ߏtu?7ӟ14�iN^@ו6y>9kMrEZBʏ"xoߟy:ο63'$4� O>]2QrѺ R)3:Cem<@ZGc6d?o?C0x7 ۲S{ [׉ bA ԀP̦ގ]7x?{ �?903/YUK̛qHT K)# f4oI#w@�L<kx7@S}V;E^ ׬v#Iʉk[< .36(8_xo2CL�Ll3f2i5ޯ_:-^ZOY2,Gr(Ő:`D`~Nxj`&(�bڷOX0W]=<aFd)PJOoOx@PMꖦ{ͦv¶kV#TB<yw )P=֦SzA}:6M5ʶs>"\"f̟AR1cۊ;k'V]ሿ7яV!4�Շ8 BkSS+@D7?~MXgȿN< ujA6o1uL( -ޓa�*a}Oh9DhjK!: *�T7.]Hh nzHg4Qˊ ~-/?WzU @mpݼD2No_:qLb6o6g rK W#V򳉗~42{n+Lbh�\ m? 9*CN !e`4At TCzH}w[ϟT)jy�� �!�Vz-墵C"Wܵ f\Sd4 t09sT2 cnvw?&j\#F!eqWIL= != ։|j_9s'{4�71FjF�p_ACH{!Fܩy^<4�?``x4�k+x?\r]0#p`O͝[_U=+">�}APshwX.(ƿ~ML8m1ƈ�J:n;u pWJ~Id&xMx!�RL_oWP?y t:?I~Az>'<xq`s܀02#�M_N[sCt8Rn!ϙyakrJͻ8V%�tnrM.@ر dq<mo8+n A@25B6h?)}[y[a[MHu!$�֑?mO~5u<1' ? @h�H>tMk�tc~sˤ \Ƭc~vi7DfߝOB@ ?,nڝ_zPKV8F V~#$)v?Y'w0wo{nm:=WȬO'y᝘l4nƯ|UYB7a~q]8?) �R aoYawnnƯ|EYp#0R0Z'wo}5/ެ~^󓒠 #oa`ghܬm/6@09} {g{c}x3 ?Ɉ AF�1E[e~J.ZZHŒ6(jF8|4nvGK ҽyo?=~' �MdF@m ڝ_Deƹ1.�0 �q:;?};ڐ{]g�R3#N=�Bre_v˃6w>,Ytd'c}>߲1.8"n"B@ʅw@6#]}0'r Ĥsuܧωy+6@f N_3NԞ'oc<S~7G'eHx©6fLVݳAsi܄iS`% z-s'Gw|n\szL#Tl0RĬE[6k>E^!3*@!݀:Tgrpմ=ywDwܽ>~Rvh�XܸF[sڊŔa+Jp B�RCPAӾrutLJ։]{Up^!-R^yB@eA7O_ap �<kv󦻴[67߁ C%d`�˄rj޽:Gv3>8祷�>'+h�x& KNub֒{75+. "BL8 iHI@Jg\ a;ռgռ(]H $ޱ}&q �Oϣwx h�(ܼT[mզEE�)!2{jm>ouju)|nh?{q �jnF�1I_X[v]MwYK֊uL 犾f:`gwL t_##~>R'w�;<F-P{k$9{MrɕosG-Fj!tg $Uw{}va±֙'̞۳}2O54�d"T\((2Lh6 \'&Ϝ%߽Z.^{\rz1yBi0e {e2qI hSǶڅ} Mw#Sw{޹ q}Ʉw62 : Pdj\p:5rh>K!1 N־*%틩-vv۩&Iw:l{�ޞ>ELHh�H57Ȁx@irmkW Vq aDL62!})R@/zo\/kVWNZۑowʞ   �6A刽f= "8E.ٰTZ|\apF>$ ,OE1|!u(30ӧU-' GՍWro/  SI@@&3^cy�u59+W`ڢDŨ2!ňf+Cb9f+SvuԢحMjfe|>Jh�H&N- BTݼ@B7aDM7-. -/BݤiׇC)O씬԰P_󷲁dBoPEԵVj}dڅD@\o!3O蓪B`PA^S}O97@5 9nSً9)ip}!Ԡl+5F \�Lh Mw# $ U|h/lCW~I5_Q~$cp޻(E=zo^PE>>9h�H#l?o C3uq$9wl4N# e3i.tRʲ5 ӮQ5"!:%{C锲SL$Ld 5i_9s }7:Tlk넙"T=too}퟇=E$4�d4wz(Ð2$Ν$g.E82F1c1ydi#= 4F( ͐t RJ)=oEХ<RRB e۰-i!7a%L#Sў~F[o Q.{}|7(l++Rq8b,_>$~cA|  �!m27=>)X CH]4LI3D]c=BuuЌ4= 2 9( ;n@{x<lۄRpJ(L2Hc*>0ިs5 3R&l; e%anvwd62g{�B#~`5 "̅;$7{򛓠qt/x!{O>>{ �!#{ MA6s.}c}N'A4>H̳_ >!%C@Hyy![>'Zg� dAb~{BF�B*_^"/~^zkB)4�6Ŵ/lœMEEh�XdKhNa'd@@Hu3�B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!BeAPf����IENDB`������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/Telegram/Images.xcassets/AppIcon.appiconset/icon512@2x.png�����������������0000664�0000000�0000000�00000276171�13262451251�0027754�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������PNG  ��� IHDR���������+��� pHYs��%��%IR$��;viTXtXML:com.adobe.xmp�����<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.6-c138 79.159824, 2016/09/14-01:09:01 "> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <rdf:Description rdf:about="" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/" xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#" xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#" xmlns:xmp="http://ns.adobe.com/xap/1.0/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/" xmlns:tiff="http://ns.adobe.com/tiff/1.0/" xmlns:exif="http://ns.adobe.com/exif/1.0/"> <xmpMM:OriginalDocumentID>xmp.did:1ACBF4C4D24FE4119247E59A0A0429A0</xmpMM:OriginalDocumentID> <xmpMM:DocumentID>adobe:docid:photoshop:743a6e8e-0638-117a-8f65-8cab695dcc76</xmpMM:DocumentID> <xmpMM:InstanceID>xmp.iid:8f964db6-7fb0-4916-8d8b-8a96ff3cd37a</xmpMM:InstanceID> <xmpMM:DerivedFrom rdf:parseType="Resource"> <stRef:instanceID>xmp.iid:1BCBF4C4D24FE4119247E59A0A0429A0</stRef:instanceID> <stRef:documentID>xmp.did:1ACBF4C4D24FE4119247E59A0A0429A0</stRef:documentID> </xmpMM:DerivedFrom> <xmpMM:History> <rdf:Seq> <rdf:li rdf:parseType="Resource"> <stEvt:action>saved</stEvt:action> <stEvt:instanceID>xmp.iid:013ff985-91ab-474d-807d-b60bda015c0b</stEvt:instanceID> <stEvt:when>2016-12-19T19:15:42+03:00</stEvt:when> <stEvt:softwareAgent>Adobe Photoshop CC 2017 (Macintosh)</stEvt:softwareAgent> <stEvt:changed>/</stEvt:changed> </rdf:li> <rdf:li rdf:parseType="Resource"> <stEvt:action>saved</stEvt:action> <stEvt:instanceID>xmp.iid:8f964db6-7fb0-4916-8d8b-8a96ff3cd37a</stEvt:instanceID> <stEvt:when>2016-12-19T19:15:42+03:00</stEvt:when> <stEvt:softwareAgent>Adobe Photoshop CC 2017 (Macintosh)</stEvt:softwareAgent> <stEvt:changed>/</stEvt:changed> </rdf:li> </rdf:Seq> </xmpMM:History> <xmp:CreatorTool>Adobe Photoshop CC 2017 (Macintosh)</xmp:CreatorTool> <xmp:CreateDate>2016-12-19T18:26:51+03:00</xmp:CreateDate> <xmp:ModifyDate>2016-12-19T19:15:42+03:00</xmp:ModifyDate> <xmp:MetadataDate>2016-12-19T19:15:42+03:00</xmp:MetadataDate> <dc:format>image/png</dc:format> <photoshop:ColorMode>3</photoshop:ColorMode> <tiff:Orientation>1</tiff:Orientation> <tiff:XResolution>1440000/10000</tiff:XResolution> <tiff:YResolution>1440000/10000</tiff:YResolution> <tiff:ResolutionUnit>2</tiff:ResolutionUnit> <exif:ColorSpace>65535</exif:ColorSpace> <exif:PixelXDimension>1024</exif:PixelXDimension> <exif:PixelYDimension>1024</exif:PixelYDimension> </rdf:Description> </rdf:RDF> </x:xmpmeta> <?xpacket end="w"?>��� cHRM��z%��������u0��`��:��o_F�@}IDATxwxdg}9L/e[+.w{48;@$@$cmZ4sh43FҔjU9)<����f�����P��������������������@������P��������������������@������ �������������������(������� �������������������(������@������P��������������������@������P��������������������(������� �������������������(������� �������������������(������@������P�������������@iq5j�v{\��ҿ5:L��dh7{������:Л|)ϲR^?��@&��hܬ -$="w ^��P���_Ph7[1� ^[f�@9Or�@5y*poB߿ԿZXn[{=ۿ N�r��� _VxZ0vԙ5ƍ1;[Hj l'n,+(ce%ٲI2dd$[1aIo17.y)<ot!I纇%I{HRsc+8Q/19NrG'GO94to/yr?/LSE��rfa̪|]F��@Y\9=߿g3ԵrM N ?`l_dzc;23]#y.4 zn< zAy%wbl 1|pēO}SYB(P)(( �@%VV ���\JCR~lI,~ܹ;m3`e;MƲZe7X*ʲd V2ML=MJ <Ci˳`PbE��@��`~oV cV0nFڌei11^R/xC^bj厏99zxϼaWrlͣS�� ��(��� K% 8s{׉Zp}=qzȘ�N&<7G/z̝cbУytxbbr\T ���(�P���>Z$^G7lC#/pcUASLћx096ԁ'wOγ0@Q��(�P����Ua?WԞr [`xd,{#ڡs;daoj~w|*PhQ�@���(_H؟{CQun9ƎmBb,{ֹ2𤛘ߝ]rw}3mB��@��Ls=g"N#ؑQ?tXSd^wrɑLwO|G%يRE(��� ���؁ЧV?~q`*c>*(0幉{ɉ&Gw~^Lwg>2 ���(�9��H7Bַ7ċNrjO,Ңa71دo~'>}o@!y(�C��l!?huvb'cٽ8&M:91ݏ㟹垱?ݗԁ �P��@��~Km;=:~>rʘ(-'9&& br?wޟOh~J ��� ��P%mr3j6n#YDU-_#?x绿oPgB�(�P����eSC~g\/5מzYp3S| j#9Ahm`] S^2ϧ'>W!^ Xj ��� ��P!?}M姏 tmcg[.ɟ'Gob@(��@��p)䧿m9sH+:Kƪr.{܉$oܝ*(��@��X_)twol? 3o - Tn9KN玏dr?~⮿@1��� ���`BR\Z{ ,,,v'#9|G2:�� +�,�P7?7DŽ:/1-\#��</1~3~#Y E1�@(�P�� D6=O7m F.6wMtGyr?tQ.���(�P�� V?`n;߰a]*cк�P 8N`ҝ?>g)�P��@��֡O}zd5WӴ /11Z*l݉:O~?:ohyS(��@� j ͗T|ً.vWX2&DXb;9p?~PZ ]�P��@�@BC\;xSt+c,*L?I]_R �@���SyVoB][=ĹgX2&B(b;1O') .nz�(�P��� >CQ{_}PJVP�w 96O_&dž�I!��� ���J_*g/}]mcO7AZ@7x7{5?5 ��� ��J"2.?Ӷ?É_c1()$~:?ן (zKN��� ��V;g K=7M'_|mBKѪ�Mc?ܿ?O~lJVb�� ��(�|�%n.i%{玌ysp2V#- �fwwrtkCGp@aB!�@~� ��5g KoXUW|q|'Ѣ�g)`j◉C~g0 RGP �@���y@ioƱgO1QZ�ݓN}mZ+P�@���0KK=4=8hM�(r:1?S{zӯd\G)�(�P���`Y~st)/CkdLծѡ/ _{/xDG0=��� ��� Z/eƶZZ8U��Vڿ>y'yDK!��� ��oM=F;Zc٭&�J% ;9|wҝ���(�P��Z07ݽ-rĉ73dLRxᯍ<O=g߫eN�P��@�@EO}u-Φ[|Ŗ/p�eU p&~:O|JB�@���esE_pεѶk^lfcziM�(J@r~{0#X'��@� KY{ʥ7sd8 �V p'GӧgI E� �� ���y_-_?V |qhM�Bp'F=o>Z'�P��@�@ymܸ?x�UY p'}|z] t)��(�P��?mo^oxq|Ӓ��IS7mO{(H,P��@�@iP@za+;�d'xx6&(��(�P��Pߎn>5ʏ5Xv- �ȫ&?~e?/ǵp@ �� ���bO K^7u_i,�,dbpɿB�k��(�jx �@IM|K<Ʋ7В�?QH-7)�פ�@�@|WkN0qӻn "�` }䉿s#K2@!��@�x)ϻyNM+eЊ�5:OWwwG -P�(�P���hqW}WXʹ"�` {~9 L-P�(�P�XN`/sE[I-][|qeӊ�Ҩ$wN z#_=Ӌ�iR���T:[A+�JL<4OzM;6�P��@� gڳn~ &�eQHL.7O H*� �� �� _2_<9wܛ-_ੴ"�S?=x-;'F2(��(�P��2g[?}mm;MV |I�P<wbCGu}�F(��(�P��6g $[/v$~$V�TDr?SH*(��(�P��'g|;P>p52VV�T4=<up쭟>Q� ��(��%3+럯7XFZ�P]u}O.F;>�@��$>mI}pqw"-�B@bף<WOr/h��� �@[}l �0܉Ϳu ��� �λ.7Ɖ־LhE��2&O/��(�B�.Z?=Lcیe7ӂ�� ީ;o ~%e}�̇ �_�ѧ�@�@?IV_91;�2Db#M_)�٧P�(�daq�3gZo͑k{-u��,q|'F<城?׷HoS←{u�^[�`�`R;NM;\CezZ�"Ɂ]w>+>8oJ  Lh�`~ꝪL��TH7-ot⻍;�`�ߎ<gZ`Z�@ +�� wf|_p薧�g߱3|M:5i�_G�Sz,I3-oX�{'<^}W%c�};-�Uw㛏h�ud@kGOnH�tF�`�#�@_T�0Z{__-c ��%gL,"�&G%Gۧ6z��p x`Co}/g2i@��@�P_>ߒ4욍׿-v$~.�@p#?6w�o�� ��*Kmgy.um}vZ�'urhHl[R�� �� 5] ׼v;{V��2$GB�@��BÿY";㋃k,��nr·_w*0��(�P��4ko<W+}�@rdž;yӞox � ��(��lymnD�P<wGn`m�P��P(�_O7\^n+A�P}ϼɯ{  p�@u�,@_ڳ=7)?e.ozl�P<wzZ< H"P}g ��R$_wؑb��0Y#gt~{h*�Q�� �� g6O o:vh=�� ÿv/4?% uZR � �eZ�8mI[Q["O��dcl+߿%}T8? S:K�ù�F�,-}][bǜ1��@=C>ݯ̣rS޹��3L F|>ŏ?G��P(}4p$2D�(mu[7~Wz z��`yU�յ}km7ܺ1G`A"�P6S�R~ 9G.um~��h<7_-H@ ʵz S��!n헾ПofBGM��f,1}3$/_aJ�P3#� 7G՞z';��KL_<Q� -\Q;?�`?=>Ir?;}Ǐ ��`wli?{VjD R5�(�� %9OޏXQZ��mZ?rgXSM)@iU?KL:VO/�,{u槌r��`yģCG
 .pܗu{Pٚ��o𗲯͎?.��U&&^uҊ�I%B�(�9��ssv追�@1&o|{G'\SScJ�@�ߑzG}]dcr��5=^#pq@;0F�X[L` �6K VS�b��(;<W>mgN SbJ�ֶOz>S��d V/ܸ)tԧ~wG�@1V4~߽3}TXӣG,*���¿7>#X)��+86on|Z<% ?ĺ���b̦n J�MM}E��k��V/K/XoEdC� u۽/?_ }@ZWk�@=RmzZ追w�@s| m>c@�(��e%6W}|;v8lZ��TEEyͧu� �@/}-��q|G5sgp�� �@Ʌ\|[?[ c-��*?[?_/H�G,P��J9)'Ե5|g}2O�.G} um kH-P�Vpڱ 9 ߑiL/mj}B��,Nb~-%]XN~վv9d/�K�ۅ?ӂfukNvzh9��,)x9K)$R �7K�㫺��nw|S/�� 7c;ݵ^Mʼ&Se�(��Zo~cػeZ�� *U=-sq@���`5ÿe$lj7}o7wW8��@!E�;޻SS �pz�k� /_Si;?ir���+N40`Y�KWk�PYߤ/|��P_q{wtjt�;_ƺ���?=υ7~q|h9��1o[_8>C�U ;߿(vY6B��B[bG#��`JΖy?'c"��jVL$s][>ǔ}����`EߖX'~fK{fn<���X}>[@دkP�r`�33)7[twocj���#9:?׌1I �a;nvg �`aO?;W{5G_ ��/;{f۾^Q#ɧ̣fU�a-WeK��@ |rիZ}:�E��3/xw<`|h5��a|c[v>e @�e%¿-~ͧO+l��PE�;`Gj}7;dʇ3<w Ib]�TF�swRsE"��x[jN۽o-m�1�� %scGU��(*@m3,e&"�(��Uٷ#H _1Z���&= G;���Sÿ-ɷ}PO?-��PE�k6kz$M���_4��|فmTҧP��\`sq43����ʛox۶Mi����Tca߶O5��@qj_S }P͌֗Ƭ9xݏsTd7|DK��Tȡ/>~]b̛;楼ػh ![3�Il/��PHڂkMNFP�@5m`| mHͳi5��**cWo~Ϗ>?>@�(o\_C[wGp왴��@`t{ߛp!-\"�*Zկp k�TssLJzOX��@us'Fw\?vT ]`�ǰ&@%Uڬ�R;6?C��$Y~}N9w@4!G<9{Wi5���+n;=J��2c=Yy���2~g?H�P��<K"��`"yo)�PW!/} F/���d8 F.yӗ>� �@+=���P;c@�(?{qvX}wPJZ ���(8?z"E�̔^dS|qr/ߑXoˇ>;Rs��H85蔤[R;楼/>j%+VkCf�*+E��@1ؑgm }}N p=#@�X?7~{.��uC� �@\OoGuM��bsb/_IOy �P�� 4S=]2o_m~5-��m~e� �¿yG_7��j7w볕y=�+J��ÿ6{u6+Z��`ml㟮HfR��`ߤ[oə���kumD[yNjT GP��`vwtgeV��3t5&@�(�#+CO]zͧl=2VV��pdiOk�l8l=+ԬrQ+uLOmI/xg ���J&<o_zg)Iɔ7w͛y1_GkXc 5#�PWk&��Pεw7_e u/@�US�sǟoU7M���rի>?oץ� � k|G>a|i5���l?Tؐ@��P}IeoKr,ȷ\HS��U0r~Ai񢀋���Tjgoz]E��\U[5=]T`�֚S k_9¿s|e>���("@mˎ|Ox5Ol13]:F V#/7wN��\;o/ESd~H�P�@YYwG~p���@C]>sߟd/�P?לgvO 2VV��@3&vg6\kX� �;xÅ7VZ ���S얆W�"�(�{i_]kT���ΰܷ}WP�l �(o9V_Po i1���Tl F?x2OXP`�VKo>oXz%9GͿ_���JgGsOfI#s.Dq1�S$֯ob?���T S[ j�J6goKr_M#Oj���"v؏vc۔{W�C��Pn?}iK[j|���UX}iK[x�E�P�@\ΔcPѡ^ h*���Tmu5_O}:�;�JOڢVw$9ݯ?t:-��cЩ=|,�+(�P�@)+{ox.-���Lu=="@z!"�(�dLsZ{���=wtjHE�(�;gZo5kO?���1Qg~7k(LkP94Gr+/yge���@Ne4\O{^o&!5 �,oζ߂U{n,xZ ���X"w)z�)v3 ��f?J ȿki1��� ?v#_RuQ@�� =}ӗN7w-���WokQ@��յԢÛO$���m_x)luǢ��]ʼ;Ʋi1���`5�nh Q�# � �X`[yD���+ kI=oV- X3vEL]a46���îi|Q;?z?hᶀqƒJctǑ#�o:_w��� um}2OHxP�@�;XSI+FS���[31gŕ}1@�,= |i|���J5�k>NI>e^`(�P�rqbR\`ӻvZ ���X]vg;?P�rw:_cmL���l载U?VG0�YU.�l=NG�?q|L2!���X+&;OÿA-K Э]0�,+Kr6]wK���k\p|=_;S$3ӿ(� cO=62Wgb���OP>P��ds nN ���X_;z��B3?ص5T{���Ό{3>Z�T�P�?wQyݯ-?x,-���H 8w1߮0��ݩ ?+ƛi1���87kgPc&`W o7/y\,���χ_^qoxT TPyNO-f �CƲi1���D+�xK>h>-0mkQ�@`ѰIN;p ?���J<O7+�l H�MUgvK޷z ���(7}[Rʰ+�-E�U"eֹ`)}Pi1���L9;65? cQ��Pl[_k|h1���j� �Y@�MP]S|/���ԵwT� �YV D񁙿���(OvtpAMz_L�2mHr_E���8mʾ+�SS U5@W[߼Ȋ=����*{V۾Ϗμ)qh6{*8hwU?Gp󛃽Ǽ[���*Jw5\͏H`${@�geoKr6D���XvS^yo h*�E� �s^YV;pR ���Lv8vim]`W*Qk�T"�>'v$~���z[ݹ}iRަssz$!T F�Tv>n@yZ ���lƲZymg*s @�ew����fؑ7.S(�23[o���9g^]�w`��Eu5FZ ���.ƶ[{_iT�#P�@1֯oGjN3���Ɏ=_=W  Pن xqm;Ų���@53? kWh%@T�F�TV;pl5wbl���`m7*��R;c?uof��� IN|$-^0=[�JAU}Os���� IVkl懴"$FP�@齞[9[pD����Rǿ_I o �XoKo\NSi-����7YoJ)d (� �X?+cԟw;d0-��� #c{;xA�,H�y oV(z>��� g8 F/Q S�Z0QxqM먷Z����).U`��mG"vZ ���@>mlxߐ%) 8�^>iGk8���7\O}$3oL|J7 #�}oA'z9���� D~;pܯSp�(�`o;Yq4���e_|Ԝ!�s_,INW6��� 9zMP>~ 2] 5Ͼ9koy]/&����XQ[嵇[jZ�߽2UU�e=OHnzV'x���x m7gkjPQ�72 �geO2����1vKo7O4Yr (�`%{V0 ���@QCc |J[~Uj R�@eh;���|cskԂ(i,X>U+u?wV +o����X0b[-mϿ;pA?2XU�%-77ouj_@k���XMNm Zoxk> P�@Vsoug=M2O���XUkϼM$)π�9ŴӭP ���Puo Ȃ�9ύ�qd,���`ME<V;+TL�Cy7}8f���A<S��#CȖDm^Kk���X }lyJ4C m[@FP�@>2mgKr^n����KX涛-^ 5ǀ�gۿK_kp3���`=[onV- �Hve7?+B���Xb//xKE �R?߆߱ى_Kk���(vm/x- �(� "\5_'cl���@icOZ< �JS p-Iv+>v .8f���R ;엒)9ƛy3tߍ}?i#�JQU%9:Q=���P9f&8ʼ+\��YF)Ch:_3`4Z ���@I`7|l- `tRS�s[PJoI/Ox(���{ÛHjz*=ܙIATm-勗Zб4���Q]}(� �-E߉7]���P][_9�S�wl}u���:_wՏ/iz�w b䜵up{o{5���7:ع%ţ�0@E3i'‚Ɨ}9L����*8/s�M�5SJ|- M'F|M]/����#_SKBNd) (� �"}~m/cM4��� ;;s@e[%5uLk���(gCG}7�FP� 9uƶi"����ezlE^�Pmy9:k~1���`-� �U'7Yƶ[h"����~l}]Jg�ʖRʺcKRZ ���@%5w8sR;Q��*Q${>����le}r S�(XB<���P|/ vl +�ȡ Ds_mL���9l䖐ƧQG~[ai"ixBN象If~gL "n<o39My$͞@H�Vj],^7|]7q��C(3tYwT;bZ:6FQWo/)m=cΣCџG4w, M/oԁ')ț9=iz$�Ps#�6c;k���6KRwܧ"<tJkHtˤ>|XApip`s|m_+{뎯Q��Z&S7/9��.G.hGoL4b~K/V뎨?pPwA3=�$6&r #ҵʣeY 6#ux~I .$$pQxS��`u1)-!ڭ3sxJ`4/oz]@[b-�$Ozw_jW9dkF�}gٷ`/y���V'KR{̧=Q]ӆ|w-mQx{GxP=ǼXҿd)�xO`zRg/>r± ��P[3+:%T-;hV٧>:ċf''?b&NpS@�xJ`I[O{)��P?=?}Q]!yw-=Y<4{&xPBGRInZ ,2<_4ۏsi-��ٷO/=^߻K?.뎕d|3[ /$zGd_BM ڱ/z1m��Ugi{_LMѩ[0ԵG}E՟ұ.~T��ҕ;Îm���H'4Ӛ/WuOi %29^V @#W4>핝iE�#FP�({Uzg=_h"��;RG7]g.lӎޘ¾ajbOvԞi(�(�¿%ɊԘSt ��=|{Wvԫ#ZYO.95MDzjL(� �|ʸ_uoV̈́/��SNk֩&o$a�`ٱ|rR$lob>_sǍ4 ��45h{oLW*)Rjj'9(P|7Z]PR[W#� mԅ+F�l|G1��mtiOT?we^zL:7KR[qqE<`~$��V+ |��Tch1AΈ~Cٖ[=(z7HL~J)oRJ+Ǖw2lhvAk�j oMaGVol]1J;Y!�IV(zzso;Rw` 1+@_��UeQflm;40{ x z}L5&��Vzݡ /y*$kXZ ��Tj};1[Omҿ<K<Ygn$/e< 'ְkpu�6(R )+^rgLC ��{Ϥ1vivAmj i'Ep?~-\ uW�N �%s_\0Tũk; m4��6:==1mw k~3�NC۳֏&w'x�(�2G  cP��%iKC@;zc'xB~tu}594%�$c7=kQ�YkKz7 �kTXx̸_%�rA[Due_\X_QMu]4 )q!c% 2<#7Zi��(}QV.前U<=68&irdHi@|ĖE3=zd.��PL xฦdEayPm=LN{rmbK@ �%vm ;5MO��@G3'ijlD\j �54]?ݛY@`(h$幷].ˮ��@)aetNG!]�UF6j׵xZ8i�+S?��Aɤί1]S]ֻ�p`d)~ϓQbb\cka$�Cu�R �XF� VfBZ��`=z8u5eaK-d �^b@brBsUV[o|6-^Q�_ dG;��`M;%:͖BY-ƥS�fwjR)\(cQAw~�X Eȶ3cNJ��u藤!Q5敲:4`6}tIƦ* mg1rp�.S�[^.ci-��Z?=G).n7wyj eLj0?=n"&4gKLX+.ky&M��X>#ҕ1]Uȡ[n002.FF^�&دp},n<jI_Vu�P�F�!yC@k�bL=ֈӷg.lL=z`t~�okH)ZyCp(.~;g ��XaG#]1:#}qUJ`l+nrNcXu5:_>?<N;b�!/(XϩkyM��%阦vtQgTQ?6+GR^|o'<~jd4(*?ַ>͎Mr@B,lM2?07e:N �� 7=o[Cw]QO+dR'Q]i4**i-m/~Y> ``@N�X޽z`KG?��ˈrFG7 80*ד<#K<Oc( ѸhWKg4F�P�-#$V)p|4��TO/ueoLWU)U�gzEA #5*{_|wOZ.s:˯n"lz&C`_C?��SYuIOT;Zϼ-�Τ|/5iGC |JoOw?(�힞Є-5_M��@'!;-ctz[X;zc:#"?c##)[�هIF`)S71tHШH34l>si1,<훍?�:CR7O҇ݠ H0?ޒG]?kr&GhhT۶沌�3b@w+͎}֕4��1ΨQ똦 E&|hl&,u`g?vrdX)8ϺJҽ2Z�L�ZPԱM;h"��;Ҏ*`yl`Tk4݃|Jay jhxT;x;6u�"@L ez`E98VZ � }z}N}6]@gW?W4<*Z7Ӵpvf@�޿`瘫h��*䖰ҏBMO;p c`6P]ۮSe ��V<lrbuD��T~藤cEQ J �3;�X�Rr5sbb\c-ŀ7eb e^"@,%jɲc��s o 9z:}s{>wqєna6Y 瑉P q+|[K F�7Xr�g{2:=qWQ~:ɩI<Pmo۴]@h舓v\�>q]SX%:0;�,:(U ĔP^xG##Ggb@^8Sd]$c/��es ۺ}N}iud FeM#?DBs0( lh~'4 ҙzMoa?��%~N!11XC.�*Lhlf$eۼ@(K֞+$}K �̎�%/ǝWcbgL��O藤_;zc7Fz,IP^͹cbG;fuJ 0^4yze$^(c��MwF}s[;muߓ<\WML" o_K/ʔ& �K'flI�Ig{)!6=Q)&Reg?hxPV|-ݗJLnK*20�+/9��J.KRkاǴ7 =Q*48ɤ73K[+/j;_<iZ>|ءi3�i��r 7oK9f��(-юNn PI`Yo3 fzt V#C,Wׯj4-i�U誾�eހuQ!i#�va?S-uA]%1ضe}w�_c$19l`?{| jel.ue� �9-�}Ep4��k]ӕ}q24Fgo9}eɡRTrPp'G\0m:_gl1P��a跌maΈCqs`$Áiz=O gryr!^D 4\sx �~-�F�,lkE4 ��EYrHW̯uyomP?=LA'ZY#C<OP%{TE�wߒd7H94��EY2Hرt~WTWu|sG4(U]*uLK+_8 eGj5nO4� �ys%YM׿tYv �gcBzz\w;�,K0Fy(Qm?Nul H g1`n @i��;B.iG_Lxޏ -�O`W^Vb|L$"�JSs%D@ ` ;&�`eyñl M5:-,n� FT�Rw�(?W7)ر$=� �VqT$-7(6p�DȒk1l U#Kx?H|H&)({kkm~mWQ�U; �ٷ�BG|�@a#uqwT;:]tQݦ;8.+Ԥ&O`NSΓtoZc!@ � �6S���p#[Ҏ*`�I1%]O^ EC jd(TBn]ْ>�mG�^ V �@]j7}1G}4摁y27<,?MLibLEǺb'_V??ߣJL>M�siO;[Lh�Tsϒ'ѹQ=/[YY�?@Z('� M `QžU{5C`Ty 4=�TeRԎ.)єT�?ze�ɤƇ)H�%y弪)��8 m~;;�P5?Kۺ'7ըO�RTJf^2�6;`NC[ 1+S�r1j,�d?|ݛOeG9m��-ctz[X;z:#,?ceL1 ϹsgyCGlcXv7#2*] Ȍŀ@泫xg�@,iM/Btځzt`T,cyLz?wd ޒk6i?X!#ynRCP tn9Wϔy T͋�f` �*&gO3]Օqb[w�S�v><f/,y/E#TjqY`ѺӲ^v,XEES,_?�lּ04/_sfYFl^<�``3�SЙ3/_h_'1KXi|,E�1_pS95ɗv*v"@�!�iA[;⺲?8 UDI '46d")wɴ"ZjÊR>�W@D_~_Q'M q&c]y桟|kb;,�J’d:<�M_";tƈV-ד j|Mi4<>=RC`7:? -[xKƍ )kM88f|gJj �#�Hi&�K<TGz =Xd^yud{-"id2]%˞J &G' O1�T:�#Hkz[4i�(пD,]Վis )JGt`hϝH$546xy 55:"y:5ZvS5o8bݷߣ_`@(Б0�Pz?G7NhjG\wy^iF'Iw_c=DLiC,Xu39>e^��UR�X0_6�(/IaGbW{y?щ$ L�<?W.0X;>VO[Ti_4߿/h'rz��-/,#Єv u0+є7� Vb?p`XI }'0 @^}$�%QUjtiOLq5hF̘rFƧ5#j9Ζ�v�3!Wz<պZ^sғ{O2dAE_y ��XпD['qmg,dyҾCc{hLW>1RGcLk_l[2R?W<OHqiC �y 5�XI� Nn 06ԟk|*QiYF]M1XoE;"y29%FG رS}L)�F�:-hN �%|}1mJ{jᱢTm˨90WĔ&G),`G~=j(Td3uz\�%6:3+c:5,ؔvh2QRoK\AM#C;�=rS/ݤS'ЋS�fEu @m7q g�([zS.*K"Q k[qbyd`TqQ�wɄF(h|zOZ8` dٱXK�ޛ Z'+cA?ȤvQ2kzZrlV`W�̰%z_R){yl}3~Ʉ&Xrv,j)�¿3`r:��m#юꈰ 6&WG>u7dslcKe/㳇Dɿ)1}<oMjrdHH"�V{SY¿ R|屒&�Wn/m)bjXǵ\wu:(p@]MV<om0^K t0["gU�u5920 E�]zO~*]z&f�y]ܛ FtEuզbA?,dRO htbjվG}, U+�d)tQZ+ ~{46UbmNSU:�ռx<�;WэA])bV󤽇ƴ>sj iC]_E'3[|R,B7y~~~߯8yE牧-)`X݉pXU@9Gj}V0|<�3w>F냶.eA?xB;F4>Xﳡ. ?"#3][hϛE|oV +|,iVXF 9֮i%˵@Ū&bM3^U4�*yΈM݃?4Ǻ:RZmIWe 1)EBaY^PU>j]oe_ }"2-Vkۉ�P?_t|zZ\YuxtJ;5 [QWSL0!i-<:0I3;+3_`O/#P@$:KNh=8�ctAC%vĪ/2i+xV?"/~QQ9lE�p}7Ob@5m^ݱPX� K1MA">C OhׁQ%\wտϲWBikZ�78>:W]㣒B}~B ǎQ]�*~!jY`sj?�T`3m]׎t:&\<05~VOK\ Yk�a+q#9JI'Z ploww)-Tjd#'\t>�TE?Xҙ#FgYƓиvM2w^*%�\`bzAM�G);ww+"bEe q#BkG_\WTǜhɤ?Ě}hЧl'z4?}AZ<ON-/8I!/{WY �Vh{�ПY'}qDg<98}ǖ]L5a:Ѳn?2s*p0hVrrB<9&G)�L(>bmY{�(G7Nh iG_\t6Fzb`X~hPvXg'K^jf`"Ԥ$ON0@h+v܉Ie)�e$Y;^~��!˜氭5zڦڣ)Ay%VWkpT{7łjp�W+iL>F c]o JZ�H鰂=͝�J<g}?FgG/"b3ڡIєSF7Ԇ\C)Yt 0ʚ+L>?;&ϯWp 8Ԙ+_($1NiB''^ePqЩk9�J0%w'mS]@Woѥ=qlu7N!(+ OS�d\/{?+j?tx^gɄfFNRp]˱mA/(�npR 5[ĴF[ >X?z?QgcT5a?/D 1L&v�АLhj|T`"�揘`t�� nb��E:B~^#ѕ5:#"s*&\<0u1j+r8J̃Lh\[^f@8@MFRwIM e(@ ;uIe2 �1>w�XsOc>m}1mp<i1=4&[>ϲW(`󂔠dX$c%W1}|kTܤc#3E�A1KowUb�մ%;E� G/tAWTWu|KP&Ƨ3m[=-q|Rо�f(%D1[Դῐ'*S\WSc0}زMySk�d|kq*YئuIwLJy38cA稧%.MI=qe<2\??w_:,Bxcra{O5kʏ�7[,+a� aGƵ?8 M#L$|if˒62e/#:#~yJSvV8YU``Ej|?�V#9љ#&zzGglbEɀ_y3d,ب@ 8>KL 5�J_qKnꖱ; �sw6/{b vX%褞<0)]6PGctf8M{p$#/uGB-T45>&'eS΍%]ʽ`ER9z3G8�#~.kGSwEJzy`X&KiV)#7=[O)LOɲ*;a{!(T�Ʈke?�R跌tJkX;:3�מQ%ϵֆ”M�09%H�"@5q7l�+u EO%Yv8��(8u|W+zcш(y W;521U2][}T QF˔WCGdd2o7W�L_?W E*bGj*B' k�Y20 �@^?wW+ӎNn ʂ'i1=4V@U=:c F9zƦ\ɶ%c3_;%&xY> UΉv`@aM"@v9`y_m i{_\uedB;F4>(6F]M1Er �$}#O]l9ru#ў%r%k�Z_VI&zGph g$5]׎ 'G9<OjxI\e9\*k7{+ ~Ԥ<I6E=#G~YdR֗j5wIw�?S'Nюe?ؔ80d~.m%(/�Vg9tv ^7ῐs>5rw;5!yl?tT2_fɓ{Vj?Wi;^��߽kS]@;jtYOLuA?ByJvщ>[=qlE�ϧE[ĽRX' yhÿVY\ 1h(E5s=�PloqX_@&`뒞hk}FDY;8:'*%>u7Ge[J02ԮeB)\?E`}@�(>oC��gF:mfGT~M%]dI|_]M̧FIxpLXN. [:n"!y�OC9�%^"'\To]��*.^;8=5~mq5ʰh\{uKF_ !*eyh<, }@o+ ΞgRX09_[A:n")CUƮxqȯdJ)�zoI2? ytlb~KuǵFG7YAJjF'%36ĂjbUk~|3AaO 8+ϟx}I%W9"Ǟ7V�QE�Wl4N ��;g,#֎SsyӾCcYw\+-!5ׄy*�00z$WJ{ݤrA�'�*u oe?r ^#5q]_  32΁aML%Klk!ʈJjf `G\*/zMHfA7Y> Hj"@Ű=Z<\i��(r�g�|IΘvְX<OG504^籑:by*46J`a"??y\Wqف }{`:7ZYreE�g�M^tSH;ktQWLQ|O4CcSy`X[?e:c}}xJs^U"@ɦscEP�ZcFg� MaGƵFq0%\WFuxtV2n+gwj M* y cqco N��[}uMn .X<FgwDFE⏪082'~ :S|t{ ^124῜k)11!&_g8ӯj;< Pi�.>#@Ioij{_.+0pFƦl9GC#9\@ZPGwKzT@Ok[:9/!}5 А' i1y^y<T u7d*ÓI:<!Yv~a_]?6&@@&㱂 :?R ��`/F<tZ[DO?VgGxdR; kl"Q6?s$SWcLsr΃{lO_ɉ �e d*�` _K10S��M/iw_ktyo\M!*Aq=O{iᱲJu4FXJ=ox&d�}"@YD!zp~X<cB L/D.iG_n ѐZ :0ɩdY6GE^gr_*<R}Ņ|Ň&kev͗1HNMȒ_EgB<E�K0w8>8c�&7ְΘ6+z=8ቲٛjBj "V{`f@8i>^"pg3B/{X~_QȓNMJɲW}=aŽ;1jȜL(>3"c";FR{̯+jtE_\">UvQ"ZQc,ȋX&p`LMxX@?_<r(nGϸ~1ePn `,�`MCg邮!Ƿ& HJ$]<0ã! a`D כ]ijzc#x-Ĕ$OÁR=~Oi/qE,Xѻ�u6rX^?H'4Fw%0v*hIu4IǴ_Yn%W]/$G"u*t+J[pa � .+ҟvtZm=! WO kd|<;Qws\�t1C?_["<\ӯb)DX1Ylg2+hkEoo?P`{GЇ({@U`�0gV_�y<>gՎZ[i}hOwxLʳ -uZ7߰g2�B1[/V# LwȬ dR7)GXȆ\9J^X ��+a~ĖƠҞ 2Lj&e;|zZٜXȕྑԸtOܤSJ d"%|Vf��U~O|u!GueX� <O{jxYϼ u7ャJJ3[yY � /` ? Y&V1qϵ$W?_pQ(s:Wg43�_ُp,3#_37F0ih|JF4H 9,;Wld+x]=e$@#m_koVS�(v_[h1YVп9_FUd/`)݃(%)*D@ff$L_4#gyloE6Bm;3>v&秅7pU3ϒx֥qmֆ ȤQ"R !*XN',�MhmclzTP�:͝�JoYFFgwDˊ!R&v hhl"~ƚ6Ԇ9�0G'<ϣAU}*-̾8v�P5~}q5j`h\u+#,o 1gǦ�̙"f @`5Ta˽�i[ �@ԏ-]׎DGX)WO kt"Q!(;{ �d?"/c;FfxIҳ'�s`Ͳf[Oe~cNSYY& yҾcwhl!^�X� s kS.Ԕ,"Z3y�S�J0.t2=--yFWŵ!1Hh&;ٖQgsL�SP-�ݛ%2W`.k{=xR)sRI,J |]Mίʝ)Xk/^\眐k(1(п|.iG_o 6?`Y=Fu`h~/ǶSgs} �Y�ta}+SBfYwB<7Ey3y~=wjJ㓱(z7*.�>4oJ]vΘ>n@1 MinE^~VwsL~kDB;K'J'~+$(MR+"LXpf:ZC 5}5W{OcEH=8t|.�8s҇{� ְjuvm/yp2/@O`320mg6Bi�*&/it^gL:5"( UҭqO]MQ\8B �ޝfϕ D].ls#y3C&-i%lb߱(/I4OQW\Q?m&v hx|"Xȯƈ,VF<zO'_/$dْ)ۦ&I+eypfZJ {yrtY_vը!j<i`x\{*FYkc}Td�wx4%7{b-LWunWbfkα^YW"lP&<�7*�ewXgnX�X,;_vDbdžXP¼(G$ْc~( 1'Ũ~-k}F,T&2,�E5+Mܙ@`U2>+pJ0ϦTK{j0XMi1?<.GǵԆ񂣨7"דdiɧzY+Op�ʝ;U.�sonpV+  ~[uE_6iH` L$&{M>h/�OY�<O/?U"@1P�`^@K��E>fo,td a]uD`M$]OjpxOƨ!0`u?3?/YdPmhGz%M^?Sqr]yT�gyY˶S[�f1k9\"<S7F_+ј:<:'G4t+,ƘA:XaX9BwT^Q~":*k-+Kez>= ZؾG+omΘvur+ km*:<6Y1RWSL!?]a$<O~/'?<W^"!C`=+3-WJ /S ~藤- Ae5YX'਒nO9 8\o;0'Y_v0\y)Y#2W�peNe2NUkf{Zױ^&v hdb*~߀VwsL>)דK`ysɒ;RYP1U A;-maA?`yhLcېQwsL6 K�d?zvs UKٲ⪠X�:}M/u9qLk==]WÂ~@ LjׁO&wlȢӊ,�gRS^rg.e 2¿W`YϑW_F~_[n 뭴s~'Ʉ,ۦm Lzx|,Z6 LU#Im<RO</aA?D=t`h\^ոk"~Gbm7IɒV^OݗĽX?Y?Sb|x'7P@׶]Z"J`$-a&BFPRy`DSdU긭c=>8ቄd;7Ko}}?,ϓ""ҁ-u'��Jޙ;_&}Azkt %'zzrpDG&wo 9 w=uӂ K%{z:�EtH �TW_~KFmhGJYЏ3qhtRO*two!+�iῈO_?"ɒIU#�LD9LQ񡿀Kjou %m*jਆǪ奔1Ɔj~s`xfl!&K4Pdh �gC1b~/s:c!".@Cr껑Yƨ)h@Jz5]02m[?h Z?}R4! KY03-|]/l[ưԢ/]*R�XLzu6rbCθ_WQ}5C )WOTUeWos0`ipTIs), ,e, ,/?Ǘ)֓EEdR #қn:_,Tw:0)*bSYFt#juR~�%ʓcU߂c)ZpϞ!))G'WA@/,Kb$@)�X'~AGO~bt2'516lu5shtܿgxqT#+,f �hY-),FŞAIy~|x=t`h!wmDi^�0G 2Η&Z*a o;Auf֕i ujOr3<6'G4p"A:2K'ݿoXǩe/KZ*zkM?*loY_~u¿5[o|V/ E�fru*%7i9�@نEuʻNlu:3&>3P^"jQjo4Jң#r%  >ϴ_XTE�Z%lmD8}c]N}u (CF&{pDIiuрZ뙲uޡ!O?W\dܩN$,J`�f (gqqdU! ]F52SIj\ =�SCN?O'WBP$7YE�+K˰ :Wd|¿2ְAo@y$8<Ʀ;LPk]X 77ӓ2d%/]f9냙? J�E/Le\?Ma~To;ݗIrm5�U8#�*�%rߒѹ]1xT5AdRht2Ac7GU(y CGfKPxYOM^߬Oܗur2QI>_lWb 6&U9.AJ`*c;F[s53N(giߡqYƨ1hGc,a) �h �?Ὶ=s`lgv[ �(Yz֖z]^ !DBF4H3l˨)pk}{CYf$+<T 4.۲"P϶uz1M 2S(wiQ1RlK]MqXV�;4}634O?{dRƮ~e ȧ%?RJ<GQ԰P ƦM%]#E!ܻgX5�hO�?UQT @1�%1gnԙQ�ɤ]:<~B>G]Q6g*ꡁѬ?Gz*)w4:/a �JI6FX� '਒.|hfRGF5t%kkl�$ ?K&0u/0*^+R�Xi< T2#Θ^qR `r*]F521Ecd1 w)�e?S¿W.!WR? ¿W_쬐~Vs|"%w)��| >37A!ZP{O:04}䲷_FuрZ#Ku\&Lq+z&7E}\aOE ŎDE|9Rr'�EtQdRF4>1hR!PC3wb�O" z\qk�ȲXm o[z:9DʔywpLCZjjiTɤGƖy*`1�(uAҶtg,PFzr`D$ZQM-MQ97DҕlO'/}*@d%AHS;b9<LO9XhccT;ܷwX1 ֑2Z~'y/Z?s%2 J+^~$#ϛ`�wx ,!"-rxtRG!+2h)` *�0Y*<߬4a/^O|1je QAEϡɁ" ="DՓ#ck%; Φ~@EdҞx.# +E馤Lg��-al({hTIv)~VWST> i"ꑁyY/$n"n-C-㢑XfE_c,^["5[ydʽP?<#�^ѱMa369ԮQN?Φ|Ecb=D M<=p")S~Uy_Ϣ𿲯Q_gI'O��)mqJIi^)1*uMP}x>4Wz  (��+X آ v hb*Ac)icCT2T Oc% P�P;"ˊb@ <=44Fj#~m]3ƽ{Y< .  ,xnEu&*q�;GP2ǧQM%4FAօiTѩ{_F'Y<DלTQ�Дix*IW{$Q暰AU徽Cr=ie-�H'=Wư,<^F)W{FĨY`}ރJ.Q`_nC]D�s e9[ W."@Yϊ{d%#ְV&ӱ!. X/݃gkF|4�0$yfmG`W]zx׻T1{+y&?xˈ&(j LGg\5 */+"LUҵӊW(thZ:04G&/ou6dj/4p.+;uɿYV h/A?_tz%5/YIF�>'=t`\t7Xe[M߲nΦ~@ԓCO'jO-tț7^uGa{Gt1% ڹMoCeٖ =|s Ja-e @i]F`͍'\!K0X aa?<ƶpluZ} O'W=ϝ}*kJ[\wXOcjX (G&hu4d["% {fF Ȅ?<o?(WJ�`} O} jS]@ؔvak}t3,iɌ ;^ 7Xo[[o e Ao}�HXQSzY>g21­ cC:<A`+t?D_xدHM,,YoFy_o^œ5 :|UBҦ�` �ևI_`@AGkc/@Lh਒L_hPuaHsϞ^jg7`Cye\os|e*,}(>l o#ܓ:-^�X4su5z}r,]WO~2�d"F521EcAS<ƚ d*�>8L9+H6֕>6.'#Uӓ"+:�^9J*�,~ڦi,3G ]Qc~r׾cry_~Vs]D�d-�g[I'K0uMNTB/vLdl|MskC)tܷ_5nT_m@H�`dROT(F?HmQl?l;�xy|& S�E=2|db<G>Uu2/@se]#z?=&<\ozk"fn:` dҕ9 >yS̽<siI)�ySbz=t킪4:ЮM%4FgS\Mc�Kwϐ=9YXX�*hKL�JӋ낺F]W;%]O{Guht(MܶSG-�A'K7^KLg&�+I��d9z_}?\-Q}sxlJ*4F[MQKl wکن)K/孬s "#CPE�ۖIN_CLKۣ/nb]_uDwM%][[﨣1*-F O&1?_>E�o|t(7(b@�l�wt0BPU�yOKN=F]yRwIu׆ݵA eepxB{j7FeP{v$͞;R+RVR=\EE?)+zV9{YW:o�O`�ʠ`K+㥌=4>vpLN}W;uDCXzm7JTRG5:!ic}tM=*o< DzbbR_I)'y|;UÓ2 R0'yanq(XeIIw2ziI)<40F_>mQ]^֩9–_(ٓƵи:j#ևY'X�4/d?&1s(C,N�r͵$y`4em^H;=#ݳwXg! u~_jmu16ԓ#bk&DC�+3 �fD tQ)�vQ`_Q�@T�%rdI;WM]N<ɕ_:_:wO:= unO~s=O0[ڰȟkplJU?RLJ˝l; OnrXv?1<ygMob@Σ^Jz~CNх}:VAǢQt#c dVQ<gRBssJ?OO&d-$R�X"@dbX�A<{3brݛt'<cZ]W;k)`AӞQ1V%ƈ"!w^Ʈ{}<#+{6fe9*E,!uJ_KHH,,z!^\:dLM I P)(�3S ŀDR?xd@?xd@1sztQNifq )SUe[FM1LwO'36qG^1Ku`߬'U'J53ɿY%>h D{Vt>a-u*-%CDTr1@;S�ȿ04wܧ>OuAE :-& ݃1Vl[h*:@L$\=tz : :b 4^8gQ#�%'5 _@)2))}㾽}{z]^[l1&И\Y-ϱ{5tYN_?_-?KrdR �T$;&b7ޑI}V[,tQ65i*69ԓF561@悔)"u:wP�_' U%yCZ<]�9K�NpCU<k&E ŀ]C7O<ڠ.ԠUxLzM:2oCZO+-�X ӝv,#if7%nJ W11}vSSG4uz]W G^&}`DSIc>mls`a`(TO'Wc\eɝe �^ʫ#qCuL?gztl1 衁Q}O訖.o}uj iJ8^\O{ipxXCZ"4F'<˖ǂ?rg)�qa`w$Wy UB `l|sc1$r%J(PB9զٜsC TwWuWtJ33syiny�1i+uq3 dZ1#g,w_Ze.Liگ>ir1Pcvfv)6:ֆG#@lV_l^5v��J)Q 0R&ϑGDgopN~1y'ɻ_JzɲfMm;22h3XV-@ �`G#;]X�A1�gs=�\<3@!r -ϜJZ,[Ӳ yW;_-o9w,ꢪy1ITTlZDXTNZJ @><'du R*je?W:M|^/VWA2#o5AUX^k/"bќ6 ڈ;-Qd@SH FNѮ%㇧S+$o9wUgZ5&f;2<H~PJz,[-�)�/2Q\/&"Z;UDhU^է̝"Y[Lt^QڱfT{5? �%Pe˃'Kv VZ.!} 'd|:&NPreOJtDA?ߵ~rm[^Bc�Qx9&��52ҭ%0}cr19yq]W\  Ӗ7&2SIo7u1�͎ٔhG#a"�((>VK pET`LLe׈ܲkDN]#8.cLer6Ny ٧,.?@#68qC[`? /"2hΖ5- fh31̀HRn>"7l3/wpUx[2<i3 '$Y&!^�4WZ#Ŀ׌{u�Vl�xu�qbt"2J%:]JRJB3rO ̓rA9 V_JYA˴ ؎LG FY%gY&!:�b&aɱh#Ja 7K/[[ ֺjᯧ)om}NU oZ j95& /3" 2lj sY,2_Rm<3 ő\1&WlY"`Ur^ۃ)#Qlh wV/"� elꖓs- Xdh:j_U.o NtvD�$ ÚP3@)jOGD0t yiwYB?uvdx**s1X#^*HƳ}hN?S":D7W%_Wsb.9HC/-pf'=t3o6^ZD5rl@3U%ZltE;FdHX~挓/\-o?dYZD2:!ݩѬZ+n@ep&}H nP 8_5@_sC/-b[FĿ_˦S@Αؿax{; @*&0o6DlM:I}jy'˲lJ4NkfeEhl-k$S�rG#;Q$zsD�47̉D0r+hv3 T_3 8=>#=!Cp y_)ɹLѨFDUKe(�MšDM[B#ph`߬� @7Hջ  PD+-CN2u6[MIҶ#Sđ)Y7?g$=-ܒ-n2<D]Jl4ۇDN5GZZ�¿%%@ fb�E�nݤ h<3@1ӒNC'dyOHUםBB-rrDL\&0z,b0��T/"ĈpnY%NƆENNf@ HHp3`.i{crq篖W\5. dd**esB4M3dșkɢФl:G#;]D4c�8@ @ϝXx%@;zY3;(mLLuרܺkTN]#<SdMGe&of!Y&]Y�)g⢍Pꜰ?'-mSbA4Vоf@ab)[:[@Ph$!oʻ/X-`\1-fIe;MDOwH>etV2G#�)eS7Ȟ$ìt�QEh Jl\<(WoN^,JXTtJ8n6{5$lRWr%d_c3)YQeĿ5xUUg'K��4`ݯ}ɣt0̀#SQbcL//^TUWzڧ# X6Ku,C�Y^+:/4Xgob?8#Nәl("UdPNc u-9㢵ψ2z�:/(LEmosT"sry,k=i2<X"IKzUK�-$8*42wus׽?vC7d<""qI3}I>sLsNm� j�hrt2>@+@D6JheMwÈLdN"ۇgeg,?`y'ˊESE&qfӿ)YWN[�h!E$f9 :?gf&u2n�6pm}61��=Y(QJ,x#JP&9rǑY?}T~ V[=Y/>IK4oV֬X,/b �Zl[кw??Ț1;!iQ =a,;"�x,E2D+~PjH`OZgOɳǧː7Ru*y'Ko(U5Zfb2N4)JDN=y\J@k�s❞[^ QikUW*2G@**:Kky/U&ӗK o$S]�,TJl7+ׂ2QnIˑǏL'eqwHW&Seb..EkJU @uh&-jUu2 L/<WSW&U7� </m.@6"~n�PBEhCt 9FUR̴嚭CSI=] pb(%gY&Kzy�ZhRfPBvw{rX�h& ytDZEywF� Pd�i-(NEoZkAmFo(9elM-߁Wb)U WEr5_jHtMC_D$qdv"6z$ݟ;"-n�Fh#$:mtmٕGn3MHW!眺KfV;^@Bm"_ڢ_j>4mWk+aF.ox;4Z#6C6f@Hb."c3o&zCr)˥+`�0&ߢG#Kİ1%oSYڨ`;�~.yt2J�� �;m GZ˶$, 9.`�Ȯ2<u d/".М�M oV@,|#�t"R# m]"nFHDi�M’.9{r )`� #֜F#8qN/oS�ehq#PNxJRQJFr4@/,VlMZ!1`v[GJb� ^F@F�up R)3 nk1eereh?v [# [�P|0G�D4oKU7B@#X}"9mA'[3�~Wֈ?giG~�gĶf$E#n�L V,�}#a>M h8e9yY|О2N!$]G#y8L|S*&/-�1�(Uޛy8QcIV �ڙKزo2&/Y1J~R9iIȢ zόPJ@( TySWK _7l%+)x-IG�csM@h* @ .76�`� zT]eZܻ2| ;;҅(_gF1��NZ�5%d(9etQ|*5#Ŀ?iv1�Ŀߎhkn�Pɨ`rJwYrt& Հ!g\ :p?у>mZ�O7$?tÉo7|3�J-R#rEGYkIw`0�:}ca1mGt97_U5P +X눔q=LUzk@/Q_3N|#�nn+%?F'VrS�3P, ٧ :ó"F^E/*oT藽l}g"�ڽ@{Ü�P);9�2I2HU`Ioft3�sHR!ȮCR'F@&q�ƒ0 dn9k21 ;�Ȏ"bGG#>�ZNi09�73y 1> `9s-h$)"*guDR!Ǟ/-�۪ `;J.ֲ<}�uoZ)1q[vueE @uhVR\@([D<qW}8-,u'yd>o,odD'GE; Nq�hP.X�64[W.[@Wv-IF[7?)�M&K'b:H^i�՛H(kyvƪjy/�uh6=3J̽?|j':kJa[ {ꎴܨED;EK_ʩ�S=1,}^*Kuө�$D4US%-+?'Eȥj�[:0o=5�9QR!#y4̠!d(9etQ7�""sxVE"�94^<-O�l+@}�PY? o!g\P(�ٚnS_6G#<{[/ ЮE�B7ёn/Z�P)$mAʣ+$gLzC �-7j?iڊ>}~/c<R33ԉh8a�%;c{-eLv$dB?�ȎṴ*GCQᘈRE�X+#1"3{C��И)jWu44fqOz �(XXbIG]�KWAjK|Wƥn_}{C&8]NiS� GjlÍ]-ꖳ ói#?Fꐝ?g?;=1SJU@�x,0Gy8~،QJD2ddT^zIg|-'/N^>wW� [g5T*Q\?rIشG]v5�N8G.{?0q@&*Z-l wȊ�bd#�qZ�T3oXeZ@W)o-KzArٯ}Aϯ,kcG"O^DrũІ&ˉKNzg>�4Hχ*%}sW*MGې3V-�_F KFlZWU|k )f’*�(Ҟ5��㈈'ws@]'$]8CiD%Gg9. ˖Gd1q(��>lSK#(� �W!? GOX:�l w8ض-""'乽�Py/G`ޙ'۾�`;�# :ķ=�7EV4MÝi�X<Ks{N ��ó2G#Mo�m)�3rB;b}h31̩�u %d(AGdǑQm�$fڲ<#tG2>�og _{F8]�ʝai�c~mp2,p,ɉGk\:(ȴ;#gjs?s;K��?5) Csꘑ(~yk5'q�t(ۇfSRj^k=?.h^�SE@{ FKU!ݓ'�J-H�c$m�ujVZ$d*nuXp]eR�:-CS^bMW>Vm>_3Qbj ylg~ґmE�wwёG+�FXZw:iMEd&8g�:[�C##_G#�b& pt<b9љ\�И)*3eΩih”,@p`,,Qӑ#6]Nt֔aD�*Z�V.�Ĥ}JɞɘDvGUAH91.[e�dtI:l`zd�ض{ �GD-\�JHGͶSڽ2!%�:8__G#V) o;3�h_`Dfג7(�?M)g-%}sۿe/,!dþyᙫVpJ!ۆDH^Kb�U?*XʫbPD/Ap]UX/.5rs,}R`l+=@ ̎'n�=kE%EObZ6'@14ѰYS sEzڿ^{{ѣQ20�i��xZlk4�LT闩ps M:4*/6Go{/)�^&W;@߻}K�:Q#^ N_޾7D8ēl?(<owJal/%kyTw_/p{*⟸f#N"Я`s;ص;DkK�e(%[FY�Sp}E LJ߁!1MR�Z�ͭuUԑXڊ<vV)�pĿ~@)��ߋ 4�%aӑ=`ڵN=29DhAff?CN1��V. �h!Y}[:7$L[6CCSE @}hV GC)`ftsZ&Aߛp|&. �h TzlkۻB[�)t`H"lMυytQG#خ 1`�hw"_U�OT Z2d2njo9-p09glhch)G#!<,_6mc�`܉�昵Tvih-LMӑध�́e;sdB#TGS O @,K�Mگhqtдl?$qR�ݣaI z#(g11 ;c�vuڷ@`Ʈ_q.� :� %y{ϭW1qynwSK8L?\�3Q;ʽB.Ǣſ*mzM⿥H}nxwTH��9i�W{K�5QH�EdCX ´rpXOCJ�@�sc;MD9;V]cD_K{�LXi��<W: o �q%$%�8�U^@屲C#PjN$ULwL'�^߳5|d �4fҹk۴Qrx&!f&"@92/G# #pO=F?m9.�hohQU* ��,-[8�4-3BW&ѱwoIWhg@b#:=̵�MC:`Pm{7l7(фPgR?r?eDHbc,#̀N�(Z sӣ�@f)4Q '3-i8Ms{ex*y PW`&[SΕ(؉G#!CZו(<[>u%�T#: h-#ay+Zmk~xT&%g`!PK2< \q+u5ń႞_ן,:M&ki[Mk<�ա_K@[Dm]?Ze�M(%Zlkyi{s2Iȫ^p,]y P# ̦Sk6>ETo=s`uR嵘r:eVUS.G4<p<qU.�~ N:\5�И*/ Qs," i?/z ±/s�2�{N &j_UU^PڊZ8aP脧Et_Gtr�b�G@T>lֲ}Yv1mGvGGŦK�@:85<WqGCg0у#ޑ�8uw<+�@j(JD+CDljv_9Y_±$',@rd*2<+͈?2k'' ^5:NHl{tT#z�A7BC@)Jɖᰴre'e~|[fEOs58]'%XЦ&@�=~i.�hT-`rdxXve1~d.Fep6[t~նЂ?/ة)� &>,�4j}@g#�Z= k�1<9' \�J:8/G#O?+�#=Lr @sJDM\)&n_(e˞b GC5<r+c�^�mgzn��РJ{Jt–C6׎=eR�`\Am?ApGwtN6��a�8Oq@&GRi�Zlj4� Q<2vL4 mnGCP̣۟vb�)#䶊^5#VJ!Jٲi8,S[ls&Mٰo@^VpaLQf@kU5G{v@7{F)�R#"=tҞ �4hҞ $lk0wZXh?.[ iٜ�Zt d j<k'OGuI)1�:(`c$�4j]%�l3G#ܞ~$<o>|EG#c;p8[�Pp; s8Q �bR)SJíucx% ic�ۆR?q/}NwI�1�<ſP̙_P@cf4BYrd&.ce$MR�DD κ淀JGC_gO{??� sҘw?e� On4�x""2>u{ej.N7GGZ:�oG#^s|]�  `}ʵ/KBː@sJ(4`UKn�7-t`@?}w2:QY"F(ƒ],=ſ^:ga$\VqgnbqҚ0T�И)*xZD) o2"I[N 5_;hC2+=Ez-Cguh&ФNE(Sy<WjzlE{*ݪ:NHyQ8s V�~�<;X�� +.CQX21u{db.FT8GG[*k[(W%?O;k`c��z^�#W?#1.�hiKgȦa V'aYb:-3>n$r#W=#u܄� �F$q]3NxaMQO3mHeS>;|xxRJ"i1 ֌24e5P?t�Nx<kƭG~7`�~i�)�"b['<i�c(eE5ޠLd~2жl9q9?̫_�m�!{)f8"D8 �>Ii˝�PI˖͇d r϶ٔWE':?8N<rߥOi7?@F|9gn|-�ܟΧlĻ&@E%NJ�[fEK(?;5r]??PG@A@ e�Md(%QӖ㑦}65�*f:{el:`@[0dN#yxk(E�9m %�wYq0�u<�he(%8 r�X"Ƕea?!�hq J&`:?t5'�50rBJѸ=5$�M1[�6e8֪ptdZ~.1вlI[n!WGCG'tBP %� ${QR%'r<zǰܼcHuZ̓3e`3�N%w]y �(2r?ۢ(�uĕj(J5e7�"\uPN %}v{%B Mڲo,PuGCgHm_.~/9&-"65v⑮3N#�h#8%/? �"�xԔ d,Xߴ_wr% 4=ۇgrDrV�/&d5wꪨD�@5i�&IJ! ��UJFI96h2{p˥\?_2i7:'n<ggk:?U$}&__M=n⿮ Gmc�Tu^s@#.�$=ZDIlS^Z�VƎrD֩%{h$,;[8)lI]g~ӈ?@ȁһc�Tlx�"b[C`�-mq:6)�̉a爘3/u*CzҒ_<}{|7dA#i;k4\C#Dk2 �G~P9�h\il\F#fӼR E98!H {)/8*})3 u7僿 wd@9<'I[#(~tCx O]Dʺd@r gv|qҚ7r:@I{6 .X"ṙB}S+NТ$k쒾S򹷿D`h8[f\sk27uRBU}a <(E\-+83cϘ6OJ�gQ�1T�JMv2 |u&Ӗ_l]8{)?8HGHZ]Y/ 24�ɯ vK(B<Ŀ-�Xkla /rOۚf�ߵVJOd6 %LLoPrvC3?uJ 7R&Ɉ| r8}x6-:=?̱;'L [�Dk~3aeQJ#Q�lB.hS;_fQ!q Zg+e$m[nvH8i_ZG3?@zbC:5{?�`�Tt �4Ǥ. 'J6 5�h\e0%ȵ[+ц/ߨҏeT|hϰ|ot7�ʖ٬AD#,oGWw Jqxؓ݌�4 *DvG=h�8܇X70+9,߶yJ1~ yB_(c~%-(rb**aܼo�Ξ)Egwƞ娇'`䆖Oտ^ �=�D!ehc�H(hR7&N&_\F&G{s9F&@r&-G~>\nPy@l+|9�sh'={/!5N�Y3 r.�h$.nc�, Zn5*OJ WVKi)!ٔB'Hvxݵe0)P;Me.agĢ]?I*�>'f�?Kn44 ߤ,[~uHK/D\�*9rST* D#7nru`&;'eN]Gx<0'T�hZ̑{ �:Y1iqh(SqK.#SmĨ|)[|)m~e;'g*31 JL<̍@#{v�  =^OGgw1r�P__xj~qx!m,`8) h$)S(sO]V-*0oh̡qցi.,$"[g_YLGCG'S �Uy�汝w0\�)K�(%[҈,N?0RmTJDWM�E”Ṹ|췛嚍DŽ X(&23GMZW� GmQM �;[yZ$m7mйÒjܣ_U拾)v`;\m[d:䚂285#S*�ۚ?Ac�T�X`;@#m988:p.UJuH4\n�EÓr˞QN/:nſCq?aΏt$2_IuG'o NLj<8:U.qYͫ` :j!?jZcף?sTF8~A{r0XR?�5 |#~N�4zҙ4�Q׀v>l-r˞1yhn؞/{CO-+Uw~G)qP6*![ mol_;"%@2> O?,p_{[~?v1�G(��@v٧hRN+;( n9r!59^qF(5LD@HE8ڑOܺE&H e4L2t.c^:K�hگ`N*uh �uqv- i�0̓r`2Z 57@KF|J7^/MrAI#�A!C.@WC%oS'g5 �Y�ihVKN6;�'C27e^lT6g_wRJ~l0++Yh\>uן+} $Ă|<0=2m'E1?,_ݰ`kUƲ?Z n۾Gc�^3zT1t,u>̩� Mp$)/oZ\cX](35%ſ -2�9F@5r$K"cM�G;uGdK|O^..[lɦd{w.G|8ڮB^﫺sP;h?R =xN '�)� _Y}D; �61,n�u*h;- WۆR_gC7 e@I}je20%sy80NHLeRXUCR ]8W&r3A["b6M0r�drpllǏNͻGvtnΎs_aJM(�+[@gL,)s䩃mo@p̤ՏGC'aO >b4)�hta1< �:]͇ttܪ8mהX;"OeζRXM# U %Z]P# tT>rfd8:gMj?iT\&�0�?}A'<p@&'4TNw_XV{�Lڎ\cHΊY&`7ozF F@KΡik7Ƹ;Ru@#mq"OxP#1�O4�(�`-+ `c�,G\Җ+ މhW6Ŀ ne|JLLJ\”ݽC.zbGtB#I96}(,恍~c�4ٚtwf|MUi�ǣ5k+̬6&Ҿ~韋{?(L Ph[b&%-'n`y0U�(͘ox� @ɋp>ϏLZw@n^i�ifi-[Fj`љ\7(q5ķw/}n<NbK&_#�^Xwߦd2;?Ww赶wc*�hDR�lmiv aՖAZw?U_?ңU`HҒ/ܻC>IZt=U) c#wo j"bÇ.�h`HD5ڥwi3'f]#bj'c ,hZ]M�h�WW2csێǛd rd27?sR 8<l {[D;�uR;1ۑc[4_k{˽Ƴmt6%P8* ?6'&y`e?f>*�qJ4�KDև.�ϙi�d`m�XN[gÖ6Ŀtw�nw}LJ"QӒ/?KYHHJ? gjև0�I-F@|m9E~~\uPvE?s*gS w wɇnC.6"/)G#Dǟz?@sRP CZ �3MZ2eDW+�LLo@=6-%?%@O rx",pSܵ{Hu:c87�gv|]vk@Kᅳ{~S\NO&o[DĖ_ֿ}# �~az~cNtjIqhN^fiUe}l\>,ᤕLпJW(�ui1 WR∑Ў hZIܴ핍ǧ x,qݶ([gR3Yk׹V_,K_zu" GP;_%"vKv �1!>�7 V}q 5�vG-):o_ZEλKru Yn71:*}_]⿮ ^k=oHRX_e]' `}�@f?%#1yU{fOX;0#'x9GUٽ`Q^ϬЕkyZV0 ZH)ZTD>|&[_(3P[-miW/z)}>\ ב&ݺHD�TU{Z0 :DnZf�j?�Wjm44[_׬E�hB7/'t]|PqovR@UR dZm-c H.l’'VSv'%I<r4r "D�=k�m7}S�?(88'Uym .눥ܼ{D@y&_e+ Ŀ*!1~@ϭ?Tt�f>:bRZDg3<T[{\MVW=ι7 uG9n2񏬂:br8%+C!^�kX~mG=�7D)KP8Q wc-2-UZſ@WMmTÕ>{"ptł e D7ɍ[Y69grZA=7zLF~$w?? @�c�zۯ@dױp: �j?+ϕTi.`*fʥ}rx:V L;) VIۑ>_>wMX\j� D XvޠcDO?@kW +rVry2/R�D6T@3%2IĿΊK1Jj2<4.wFQppjڈ?JGnzx?�-+ {qfD�@]D)TLbIɯSaQmdT21|T?A7B秇g# RRo\L巛M'j" ΈW?@!ֱ78#1)w?`�Q�~�'ܭ8�u�(84�i={\mH-Gu�Ŀ<Ժ0 7% J333wn阹@ˊǦi?X~Xɉo}nG ?? k8#Qj�@'eV"a�40@Ç'ֽbk'`͟.h~\몬 aJGzWZkyȄo6ցx>z#-u| HG{@kQF@wi+9@EJLD%`k-GN_vo/b"BRoX8.zj1ql’#DD[-߹+g^�L �vFJPѲyxaQ�S�qˑ IЬKHon6Fw�qܱM&I:up&q#�iot&"G_w|߽S[I �5AL� Il-η%mQlULߜCT@hYlR>x&#grhU Sdo!vN�h]i�^Fg7�ɡ}|7Q��Psџ(*UphD;8p8)55ſKu ȉpEOD۷뎈.li~+;hycnrk1�`]* wm��5)1i:e&%+&O4|<o0)$'s6·|vn?@ 0 <��P_O@͛�,^�mCm VZ o�sբe) eqRjEN?^uC.kkpGdݱM߼] �j5!4mMwjo�<vtJ~gdwI^&rSEd*OݱM.yؚ5e-'l!ж 31c�t&(�GG�fbk=�Y{,Uv'}c�5Lvm{Gٴ6'r:?йlAJ@^4Ml8~<83x- ux5HT>ЎKn u"z!ZMFyAn`VNPIۑ_o CwGy %@GhѲmpZ>tC<sx M98D#؉_O:>}JO}-B~8BX2wU=zDž�PIPgZkQJD %h-xفήA pҖ \<؝+@gx~J=X}O\X ^}m`WFhGkGDb#uVi%lݳo| B*e8ZAEUZl_*氶[yHyW:N�v5^|�3d�Pdvgu]p4K6(q-Fy^B_BZ>Hw ȽɦRYh'#"TʦTU:&k+xA�9M|Cy��}*S«@~GfrcW2z�PuFN/*3twv#1tSLB2-ݏmh|h=2+&yAlNG#HnJqlSwݺ)� 2_In@e.yٛe'S�̛ !"Jֲw<"f<PaylS8:Л\.܋F/`1ʎ/.q[@~QD뜔e#Z(1Ju- Zd.n%wΔO|7)&"*[pC{*%+iG.Aɪv�ruL++H�&7jy)Q�""lpx?J�Ow%6.kU x sBGdny]Qn߼YNLytf҆͵ѨqIlE?�.X̌e�v&@ci$d:aʵ>B#�k{`lޔG#] qTȕs}L %<0]@#SpfǞ=tv V/� &&( (ԍW6#�5&&JJD+yȸ?1Y\sH֞K6_ !H^@w$ QiIvw/ aZo`:m̩:7NY>h;MKGr# N\mwpMX{!{ؽ �RC7P"Q1mbˀ sGW#�a,7ɱ(Cy7GC'aO?C\�0�:(n��Xѻ~r86� +_ZĐ>wTfa0R%]�|SyVwJ xX>w0SwGCG8ؽJ wsmC}]a�&.42EttĒ=Rme(E-[6(r#^G/PNܔ�K 0 RIK^{%f'"Tj\]SGCa QqmX z�h0EjLM6T(�7L-?w*.zLǓq'\y@ qWO#]OGWOG�)Zܻ{X>tct`kO{ֺF Vr2o]/ ɮ)^ mHܷ �j;-&L45oI<r@axqK=Is<_ :HcǛ6;:vf:8-S13o|GC'b+{xXD#/T _{DGg1�P_ 0/_"I-?pBXELG˵;$bZS_m Lj�$aɗ#YJ*u?Z{\{0߭]`).9L/Žpgk~WPıĦ{~-cl� |^o*,(d։LDPrErqt ѩ?12?ZT:j~(e:;UlzCFdȜ|/#[<vh<=>93)Cr?V2YB#1�p"�F `H(ve[{^gaz�2Db'b"#sEg8^G7ROvghD)%hh-*mh91O>Ιm?l<>%Ѥ !83ckwt&! \pGLc2�PK`ޝ`JKQBb.ΦdL~ZnZ..G7JMs/'% ] U"u6'mG 풹s={s!nj?ze>īv0�ZF@bӽC2d�P7 PXZ曔PiwE !$s~/ fc]cdwf2'O$uD#}GܙxQ)/Vo@o�ً.o]+9@Mb_)CU1]],*+؟*,WLL F@s31t4Go"7noTqa1eNEnUe:s=�{*hIʴ1U ><kXeK9O�J�ZFpCZXt<&Z;ٞas..$rǥntUʹ/ 6]:S2?B~*ѝۮV%F@`.@kmˏ:(+|ȭSתJҺm bYLUy`ЁYwoP@�h2NΓ^u�rj::�:[9=3jV%C!_<UPXzrOUmH>gcwnk9,i#+ 0_90} }}hx4**Jo2q?}?:'|ݗws?S9t|n ~[~x|-�@퍀:(/,GDl'guJ$nCDtn nH%( _n&s^sNK.Q-ۑk6H/F qtSZLq8vRS&D�4!+SQ�W-|͊?z.g~F�6(8[iXTU6?e/mEdbascmǑ=,qv^Ӟl\( ivV8)^߻_Mw*�%^]~=S @Dm?|ه& u �M%bǢ�*/_RE!eWPOWItRfz U o>?#!Qh�ɴ LE ;:)w&T^sʖj#I[._wT!ګ,uRmF2hCq_)D�4)+`K֑mͿb9M5 ZXV_*Vj&-L{L2 R۷/7_n8&SX*$Ng>@o:7.;:s=a� WЫ-)"V/ޥ �4#EK� P`̷ tҵ�rS\|m2M6uw,,7n@#o|G֐z�kPrjdJ&koh0l�Ь&݃Gw>O0R]tްIjӴ{$8A?:֟d,)unxD#��E#i.om?M�%913U 0Iܹ].غ9Ó#_|pI"c?򫝒UNm0�: i�9Go\C�k,omg]d�N .rcwoM6Zth$)_y<|xU�Vr"z󷮔yԥ@S0(`~[@:8c~F�FԳHňks[^l@gJ|wl#suc-NL%h%O?- �o_jC«-h|k@31u?6NZ �4 #DDoy6kѢDTI`*ZTH߆Sמ-:cElB~mP7.QF#q&6DoRX/H̷5CxDx"bOq"�4 GB#m!3k23n| <sdB[_n*۩^]߄ȱ\yP=r?@q/?S Lyp\3&!"!a&=:W}QfFuuKh!-/hEQbvbAHL9A%gdЌY+}i򾗜&ZR_3m91'LTLlGtB%>_Do@ff-�~@& v3\Ooߩ-}C�o(Q?-ĿHT%6 J L*DFX$!k[[#UShb:XN=3MXo<"fehX8v@#ȯ?g>wwsנ8�PRX PC#[~-�5$xI:@#-/y@VZTZk'SZD\uODe r jI,E!1G$m-Ѥ#3qS"#Hŝhsu~㑄xsG�Wq �^ݩ�Y# ȯwwN=>�hz 1\n@D^N<Vn;"ᤨp2*S5?{=]JWa?zAħ ` +zÅWh31(@K�FHBx,Z+Jfb4zW.HQ' �j?92cnynj � ԵF#Cfh$7?#{ȌߒP2&J_vqѳ]ޣƪ罃�ZPZDxLRsHXW2"g,֫| )|{ݿ}{A_U^{ns8s5\RꢇC@ O\f^,9(@�/eu.E[[[_K᢮vUƚ,oйP_͟O' �X Pv:utDr?c\� -ZR@#?@)m>gR ׈ �h9#z7sGf�L�6B?(W0L>{Hf=ԕY\+e(w~L|Mր۾H{� `,Z,N"&ڶ$-K=Tջ'W|_mU͵6CCa[{os=a��J/ha7w<W\�b.I&@fn+W,T^ԂTſ -[u ~g]\(E?h;O:}B w&w&vh � +UPݫӯ %"ffR�M�UwR+WbVU[;[P”hC鑧71 V߭'RT�4�>fX,�@zU@#?)9?Us#��ul ("uxxr?b�Mn?G؏C}RX< o�O*S$31�-{S@#?/ñ[xOQ?�( kk31(@K�=?G_R' �h5/e؃M@{ �|s G#hs؃n\O� p6ÇncQ=b"?:{{~Qks=a�@3�$3\+u<rQ6=L�?GC{k>w:_rCw�sKȦ蹉h_Gc2|�&@"?GC3د̌E;ʡ\O�4H󌀜ܠ37lQ7B]b.F#?=֑mW%iW?�4 P(`ޤ&0�&@HE?mᩭ>{/' �h9!{8v2z#9@˛�F(E G4!9{NJ,( �T|#+ In}"5 � P@#D'>|o݈xoY¿V!M[/!uaI9b}/'t9�o(1zzIE.cq+\Aa9ł?\k}?8NUumjU{a@{c7v^y�I�"bEi �cPU^Cb;ƿw@gI5x+ds `�@Y(0h_|C'H@�], ?h='pG". z�V7:x18d[w �ct$@G#Z s'qC� �(*1u_Ǚ|2zEGkxM4��ԘT�G7t26H@;�FO G#�MNϏR<>Iv�trMؓMuȔtPHO"Dz]ѣ3ǯF>S1.%�h@.HvŒ*u/ |E⾍LchL<q7ѣ3E �@ ��F!3HnK@րn:w aHLv�%�+[EWtU*Pߪk  ?k:}~xR|ߒ^0I, P2 zu&b$M� VWTsnT$ky~T*]3pba/�bh _ 53�v7^Q.?h8:9s?v;???`?�t  ? =$Et&/8v3z|M�? ?p7ɡW[q=a�@g��9w1sc?2�]Mn?hMR(�PH^=�Gr�)[=|vF�-F@#za#v˷+Oy\O� _mx_~ H@;A#3oy\O��F Rrn7ŶB�hOKTw7Gۚo\k- �Õ P J[h@[�.Q=?Pkow-�o ^g껒5B&�GTPߕ/V޺<`?�@q[{D[1�&@OGT gu{���U] `0#WttvC�mk!Qݽ?Ewtv_O(o]okG?� X '@G۾5&� ``G#�>X?LG?v$p?h 33#)(_dga}T޵Cgnz["Qs�C-gU=K-yU BVWqT]W=L7zH5$V♛nyfHU/ C�AM�P $joŌ$�5* ]W::;�MoʼnG~= -s=�T(}ngؽ$� \&�G=vo/#?�5į` P`F~uxjC�oT&h?@,#;"~kg) 2 �h]Q�F(�O#@G52� G#б???`�@mLD_ T{/8v7zzDT-;/[N˿̚ڽ"\O�1�f} b4] `? |o$7{ص>. �h&̆6te !�t G G#: _Q +745ߓ./ݬXmsy/�# �mOwivAF7\k?@K =!s=�@=Mo<v'3GO]߯>S8Q�hwTW%)@@3tRU;?k/T13>z'/s ~EP>�@UL�/ 뤓x~S#GM�@ZSvGwWMtGFod<yE��F@z�D\~Q$ �c?@cS۾Eg茔S 0�y(U7`ⱫD{@� ԕ6?@[8_hyvPJ+ל7`�@璎3E$i/3M�?Vhm%cvq=� /NhK7ZP&�:Uf ֮'R~?0�%M�qMbA�c즯?dv �eta >M_C{E�ڈ�2u( `H2O=v/ �cT@?в8ӿvI/-�К&aQ&]h@�Fq�G4-zft}4O�~C�<Jn=�\aA4N_~::a7?@.x7u2ɼW^'�ħ(WM�T�gj(_# �d݈?26_v,߫_E �h+W:)"I趱=?1(�t FwOqG#Hnby ] X}LGܗߡ<�Zcjђ/@B]U� @Dz<YzTyXWŗz[ojPolk._4<p9��PW@r;,nS0mO^eq8# �c(1.\\VY)�$O^sO#�: o$vG.-F�:6?@J>wW_C+ c �8&@Hn4<maDL"�?.1>n�Λ>00EČODH>r.a?>WkڟoRڿ^?� ��Q�^&@�I6vo# �ww�?>؇2vW-> �Ţ� &Us# �h(VG#,7I ��oTa@/5pɈ@D�?`8Ç^[J��&_:@H�3r.qFLj@G�G#j!G=cw*�@�@$7j~=�KWNF.8'bDLP G#,O<$vvzV,tup6 GWޑY92׊HY%c?\C@G�Fhmh]^@sAmʿY73#X#AɁǢ~EĿ#?��^,:mHzr3d\E/,_'fF�:PhqzWUok&�.xB[ � �)�Д&̯ 'Y�:O.ȷgQ�4e ݈ڠGIG�Uw��"0acW۷:F�:oT@#:@ό^։hLK#�`&@~:2^o蹉M(�` �_?7)zǾ#"߫~??`��,d>h$}Mkzv ��:-@#:BE/ׯ��2Q�L�ٞEώ~zfl= �@#:@ώm^�� _H|€�й&@93c~:< !�&@k.o:SCO1�y& h �!E/_��h2,j$%3 ��?@L ?c("ͼu%0��}L�K>}g*�` �$']O*! و��hn32ы?#g}*�t c^OxHC5ۚ&#:w7)vl_~c?7?# �e!qDf_7}m,=K?~8v �&KI.؉ĞU�<@Z*U_o~{n^џ!�� )"fO>Ja�:p�?@[~⓿*L"�=M�w=Dޑ]Zh0�Б&QmG4JkObWyb?? B�@{��7~>sMF�:0<o>)a0��|L�Is-cY�@#Zǎvaa0��p\WtăoH>q͌,�` �-mO\nXs���h#1<#O\3q%%fb�L�?1S.L �65܅% wqgt26@g�A ?@cP>c@#�LR֎Oįut�# �gL�?A?:{ /} )o!��T4@>},vcd��h?79v}dۨV/U��h �K fo&DL|ՙN4a �M3x|ř蟑,?@ΡukJ&kpt᯾SVLp]"Jv=#o#Գ#?p̗_F�:v?a{oSv+q΍5�1,ZH�6�؉oŢ@GTq@?hX^Wc'߫:C8:_{DGR�95ǩs_zǻ_'#1a#' h;nn{Vwvm?@ "�#M<1_ЯM`~>YBD{l_~N,sN3 k4 jeN%'SY#bMWߙYQ92[Ynm>{g?z.T@�9�?@]hyfP wCmw8?t(D��Fw�L &!f޵+?zfl# �h �U3cW|?= w�����3??\ 3BZV �";? ]mZ>A`��5�^f7$3]t�sw@#ڶ$v~.f".&W?�� P8Wr-_ 3�Q6@&�mnJUj%^�PF_:@B䃗oL0 ��??:NL>xF)Z��j&l~%ڂ�Ék?:<3Y?:^Gw%X}P˽C`��7fo^]㱋?g.�t `?t:رb=ĿWGhׅR).ÜKRU0D$ؕKD]GE}ំ^a�4G4~bж}`|NPoLGCqnl O?��(ү= n'UhM�Ion|A# �2 f>Yٛ͗J^9;o]r"�F:kDd�2G~$@ ^wz_Ԓ/ftSZY{ :`$7OK{3%_C</B ��t4;"M_$@N}pp/g#.�t J)H�?@L>cv?uB w[��&@&gJOEbE 6# �j mP/b?wȜ *�� C@.u_|/e0�i&�ͱYٛ+%"$?J���Mgx�97#OnF�:(Б=/D_?��@Ә�JN@ h0 S���Z:xgg9O;Bޞ@�X82]22C@t]~-aB�±ֶG~'p#%ED4,��BY@q@_7<qH>c#0�="�-&JqA;RV??��@s�R(:]8'e#M�?@x4v>mm}xt? Ŀ 0��Zp<L�HI==2ه/v?SqQ1�-Oc?Ek2Ŀ��hE@ĻEW4Cq&nɇ.O 2�&Qbt26|x~,_3'-"Q8@`:zDX}IOk#F�_]8&?x/_L ̦|~5s=A "�b�@M ߕo�dL�||co]�M�?@v?}io=!Zoz Z@ �@Yza�68I-Y$0qLOAF�e =O%nփ]8??_|'� �1 P;@jmhg3xvs�t wß=rDJlTHa?G NOXB �ԙ%2 N (H yϿ^(@K?@%z)w=v$P?R��,IEm-kQ=%鹉2�-K?t znbk/[?'Cws@� �1$�peO>?_㘌4� �NqLP߯b?؇Z�/'H �K stO^Ώ|V-^F�ڒzaiV,||Y9,mBh ��h$R,O u⭾{EtF��E="Q_;N[?6sP>F@'� �5DO (jL4O|ןdl�L�?'ذį?'3�!\O����"ޭ<m$Jm]O8Gd��=PuD7\O� eR�|d] H}î{KWJF�ڊji)X洵偟&sRnX*?-S=?4pC\Ф&U]1B.#; 4_ uod��xL =8wN]'����bot1DWD��&�:˜v<vixX 0�0�0��5@v]>>\>4�t N,&LK=Sy*!K+���� 1$ 0 ?v&ݽm�(#�펙==?]++&����1&�L�W>jr;[@G�hs&o�+]/_ ����6D,U y_{,:5Θ1ヒ\C[,׿B\O�Pk ? 2Ļ6e9{y҇S}o.@�F�h\;GOcR/_f?����Mbx K 2 /zZ\F�@CQk-O]Kb!ez � � �z�"\..@W"F�Z\&!Vp}t ɛ.M'b2 s=���@�Uyeo~^ՊS_h@{�h3yflS.w?=XJ2v����&H�ŌT[^U8��@0֮'Lqѓίƻ����h&#`!�FqO/|8Q$��Nh#'o5����hF@$X4_"zi_Ւ0���5كs\j>}<ao*#���ٍh�w E |=K$F��֬ݯ~L/گ]?`�`�`��  P,-.t`E_|{(b��&lgȃۿ3|xF w/p����hxEK (7տe^��T $“̧oZA|UG���@+�R0L��fat7#���b&&O_qJ~Sw^_�F@nA 3k_b��< 3qwoa_��`�|�:Zh rWQ��pucW q' � � �v7 )6U# ֿ{Edŋu��ho{\b>Hp\v ?`�`�`��t PH!��D=?z>$݋0��Qqk3$qюAC3T??����@F_Z@1Kfq__ʿk)#��mcEnOxL=Цb ��_b_+H~ 㬗;-B<��vF<dݓ.A%*{נ0�0�0��0rW~&@Ј�!zz_ZyXy��@[H 37\mw4O/dK ����F@F+)6�Gt_H-[JF��ZR'wXx!?n~ ��&�(6JEP?ЫjɊ}��h 9do{CWl~߫WW?����nF@n 1R?dz�@S Dduɻ~L^eJ#����cw (cѳ}G腯Z�@`oLqÒ%xy5 � � ��P*- >_T@~D@HDҕ$tޫW2��ls>Vܣ#q)/㟿Hp?`�`�t @UL?.P>@��jũKS =^���?6ܧgF#$Կy����_nj@0P'Yz_W/];���5JLGwfxT w ?Կ����P#>B"B"RV-O7t> ��P%Ĥ}tm=?%;uG`�`�`��p�NJ R#xOW@{W.��]?z@' b9^�����m`Pu`~j@t?E.��@0lͼGt,0߫#0�0�0��(75 TXlQ~ꏌ ^ջ,��\'"[{~򸎇%-7P#0�0�0��PwW퍡%+]���=]{Kދ oq����h~# Hrv/}@U��:Tύow<q=|/ǿΕ�����k xaUgI N��ƻoq -Ѓ+W� � � �3jix}=zz_q )FR �6ñGo޻vHrwu?�����cF_z@g-~G:wTz�$cw]Ha1_ ?����@*3J [0?Kj��h-v{sw\_>~~?����=J&@z퟾3欷2yG��U;p]֦{ !_!y� � � ���.G@DBY/=~q֋+]xG��+1 |'=D__;&� � � ��DxjѲw _^W��4xku<(o����#@$xr5D$zݟ8圷Z̻�Pc;x׽V}G||1_C`�`�`��`䯂4J~Q>OEzwی=j��TyQ;/"^;C`�`�`��@FPR (jH EW=ƚ,Fh �@8v\O <mm}k-!no |�� ejɊ��\@Gg9w=j>zSz[|Tn[G`�`�`��@13(f` By;SV u�JL:#G6⿃_&+����Z  \->_v׈R!�X6F{GGĪ_RAwD?�� `��tZ*X Qs^qrכY/#hٹ;�1?6{91kpxbTnnC`�`�`��@=�ex ̏ 0ߐ�izퟞw87KSx�0ѧm=fmH�џo�8o!����jU9u 0<�e/7^Ʒkz.@XQ=9*Y/_*Ŀt~?����� $=V@C+*Pt_:5oQ'zZ @Koyz꺍zv<& A+p?�����ì|Q+O_}|:WR]S�<_[zvlsb='#R_mџ/ ��6ܫbJEqy˻8V򻢌-�wzftsbZk#❓_  ?�����4PNT@" `/~߼8oP+O=1By�f8vLOlvXk>u&==v (vE"P7`��$b2 ϫeuo8UN>Z;� VDO orl]k=>)+&KUo~?�����PnT@~@Hw0|>hYw-yj~O;�1czr>u [ul6!2 �GvSk9͛�e0�0���lx32̀zퟞz_N=uj(w�\KVGGfѣ=Oo7sDK~G*/ӏ� � � ��hZ3@0 Ps8%+C5שZ%s�cEVgp&{]|Pbs~� � *�p�@o,) "Pa =W4תU{Z{�틎Α-gCf_l7!?j0�0���jn,fǂ8wV~ソk~^%]=+yZ+9G;؛>!avń]D w/F5�0�0�0��]�PN' f@*O V<j2zy'$n=vlwV{GE;na'ڃ�� � � �� ( ! 9ҕ?_/3yԪ3^@h׶S;c۶أSq)^p//4������(a (UPTj@cߡV8?yq_V=j2zxGj)LГ;vٛݫgFc>OG�����@ �/ Pn@Czx+_VrZ̻ �;S.؎{tGZ/z?�0�0�0���jh*j]y K^z,>wذ<'9'v6}H1 d?%� � � �� 1ύ_2􊷽X~^x]D @gX19GOsN* rD~v�����f@1#)Pi@Q&D+z1k~,_}Z Bۡ27q?9eb[nНb������-bPn@)s TBF5E˺W㜗@>lyjsb::wT'C])yzP_yJ;>B}���fHTrR5ʉ 0<_2Bt\֜uҚϗUPĤӳGΡCD;._I~9b]~D?�`�`�`��� i1h**) i- Qu稥'/?S{p@ =5|\>n_w\ ./%T'?s� 3�ȳ�h3ehNp|>P%|P;92-";rS4?8[qjige'-;C*J8CgfȰO#z1{ߺzXDwˋ+g ]~~�6�� 1(@C @�=v̴Ǝ͈Ȯڳ+ן3;C dhwinΖv)؈ttfXώ驡a=>aT1+/^tǿ_i>v�:R�H�tr >Vx0 ^8eS'F-[uZ|,ZFzz#3qN%xxL&txrTO ;'v:7v)/%>VN>�:k=75�0���Z !Pn@P h�"CoT8kStʩjj5һdt{WH{gRm&fLLI":cs:2=!cرQϸ9J bN[ ,���"���8A<c ! R`A1S\#y#Gȑi[߭- 3_|q'IkV+WEOENRWJeճ\e^&Ju^֦fX[ɰXfX䜘NĦ%Yٱig7cV�% U*"ƃk "E�@��@ xatYX@9>E~gQCQ$~T+O_VL-_X-]TX-^Tz,UݽE.ճh(Ţn uJ}{Ibz (*XQ.lѬl̨hǎm%D;Xɘhmd<"ڱL6L$$\D1ḏ-UNAB{5?�@e뙚=7���6.!~)P0/0rL(b~xzz8g}b؂4%Vh9>W"X?!�����Me;G!Q  &Q (߿|)C J1 ; IiÇ�)���P黈 j T )_/uRB P�/L%A~)S��R���+׿UTb c|祌RU[~ PJ}�� ����UK@)\x FiTjTs~������ +Wj " __IAJ�]cA?) r=B��0����h (&b|rDu BDWK2 >��`����Ac ,T[P K .�žt<h*>WPc������-b H@A)_W[ DzF@9_+<xP�� ���7SE*zkTu]G������AQ3\bVm=v^Ty��0����"q|{ՀIP��� ���~_S^ ��0����ښjb���4 Jk��������������`�����������������`�����������������`�����������������`�������������������������`�����������������`�����������������`�����������������`��������`�����������������`�����������������`�����������������`�������������������������`�����������������`�����������������`�����������������`�������������������������`�����������������`�����������������`�����������������`��������`�����������������(�"D����IENDB`�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/Telegram/Images.xcassets/Icon.iconset/�������������������������������������0000775�0000000�0000000�00000000000�13262451251�0024334�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/Telegram/Images.xcassets/Icon.iconset/icon_128x128.png���������������������0000664�0000000�0000000�00000022146�13262451251�0027014�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������PNG  ��� IHDR���������>a�� �IDATx}yxW[{6˒,˲=^Bp}ql$d|yo3,f`f 3�<yM&L!!!!doؖ]^$K-Z=UZRZvꮪUg?[09ast`n@^毯BDA/ � d̎ Dt؎АLFcjb�۱bs:Uk=m]օZ}hm"HAY)#Ƞ5twT#{^MI[Ug5h 7]y=ӂLsD e,089`B}$ر%gbh=^;:[_uM7XuzS[ȶ@ R �U Ls�@$q-۞'O8F.^y u`M+�e@ʮ*'dl$c gЖ_Ž ծeaV3^7o^Ӧ;t.^s>c HuӊqpLh0ȏ<qϏR==nZ)JZ7яͻw[:*ӀZ7kR`BxaGbC/⁾_~[#{Ժ]1@x5>횿] mպY?db$ÃqoqȑZ7+Qdxw,;4=R @2/tbK$AҪ9w\xX_n~Y' &4;w>;'kٞjƫޮk2s<sD JAoZt?]=Ḍ& @G_|o΄*3Yf$mR,t+7^߿m g3�kw}_v;U*Yw᪥ ءT3لe!w[2g56@#1΢?3Sמ1hyק?&t2) "[n]<'HZf[}`/kl{gHF1G +7pitS+#%�LhͿO@sğd&X$k#;~LFGu1�{ѧ{_ߎJղ-"۟xL&ոNU M[ox+cUcs dp5w|sdoUXW t7'v?'Y-Z.bx˯~NfI3@{?LD0G@Y& ڽ W~ч*Y(zwbD!gL%뗁$u?t[1p/Xzg9s5/r DpW'zzRwWa�?qXP gWon7vdoghVuK.T�* YD~ LLE1m m/|]sfdp/Xqosi1V<LA+/8oK㇦zi~᫮%dPsf[8m7*bc1e XwMץds6 iղ}S9ǔi._'zS[UXa<Hޥoyk~!cCg&)Mw|Էr[PA!wN�oh˼zz3w6ɏX*'w?z{N  `H ң2W4?/K2_os/\BY^M[R6-V7q�j�<z0{:J'K R῾RgG&sƛntY!/KՐ-K@!yn $%~v`>"I,$/j#8/Yg�]?q&Q$؊pbnV/4�S*\9V5U@ox[hO*zwŅ)#7)LE0lB@縨Ճ[qMB$$ҥ#aţ@C]z2 ¶ [Bn௾l?fTʳz_ۖ M0p-"`83pgM. ք=b�hhLjی2*n֮.i#@`H%,W3aw$9#a\>E))^]-yr +u^@N IsԖA?--h1#:9RL@&t/zcAD+Rc K\3n. TD86B$eAi�#LVd[{kRFbpc'd�hXY 2ϽJ;tM^͋Bui.+ sy�T9oH.xl00``EjʶtqbKw YvҤ9P HIaqHqKW]u.08 >ZO4h|sh0M 3+�4y"py^3}Xr :kOp3SWwqh'`U<cC74;G°ؒph8 X ӂ;'ileph\2y3h.mlʑhXۺBqq� HلXҞ pj8$ZCxtq/È!qt83<έ30FOeµpZ9sq2j,[l8:E@{PǦ:ܺ4 -^\ILrߌԆcNwk]-!|:8�3LL  @ � \7eۖz'3{9Йr \ [Bc$=yH}ʔ8#7uij ! EpID .y+Y -`K4O k7$<He�U \ sve+BJtΰp V7sMNip@ 'qz8V �~!xt Nͽg &<Kʌj(1HQbL@Jձzxnj�γ"$+;x{W[}w;*ް )!~°q"G4ifzu,n%8 6 Fًd^Mg8-a޴($mZ,*4m$oU>Uk:XԳv?2 gN?TN_*B߅Mp$,Ih,Sf 02m3JJXF 36x]s6ze߿6IԸ5mI]7\Qif\hI89@°9)AGs�@I<p0p<&FMJ;9HIH#r9.(&$/T'dd#X/ME^"`�$Fv\fQ,-^u2D Cq FS "tV*z/4�L- cI &,' )< [vA)2MUf"Loi7+zDIf4lEHڄz+;Bx{WW, կA7* 5#X HUDhm EjBp`0%w<-%L]X#Q7!h`=ܜU5+rҲD9i`@JN.QS.2iC1 DS`}@%Bkm c� $s8G:β?˩\'AZOpN�DƦR?f�O8UHtjukCuYկÒj҉raM#eʬƵ7127%`_6pqlpMk 6}< *jSRA*`YtuX䁖Yqΰ9] O`)Ơ~C I YQU-r>-D7Zlcs/1R%$Gu!4y5RYhi7$t$ 8GGs�u>״%?3It܄XŤ<C?#d)4"⚀@�%s^PVؼ8ЉK nI{OqDNB'_ǢA<<ApOHyF73T`>qJy @�\ـKTԓ/" %q&4�l*/?0�{<nj.-+ b ~`CM&= :glܲqb)  u,j¥l[h,Seldy,Iyb*֔>�Ic")nҰp|rhdB8‘(ڍTWGGs<3[8<.X."$9{\>� h>*V$O ռ Dz] H'?6upmWk#vAsSL8=KRz A:IBG80F,_={S9$YgYddw )}SK`3la~pW?V7qzlZҀU2+g0-cҡ"BcЃ&?>Q ΁ߕ)X`3>2_(Od| ygw4L hz"p*e�py8Wml&YRWF/�&0F9[/JC)¼:/Z} "XEXƻ>G c$29H~vyۙDJv O )3b)}:`p :wq]W#.ZB[ n R`(K- >P~J(g@”Hf�hvk<m)s28X^MIؔ`$aDV�RtknMÈTF{@%A\Ո C5 FR8I䄄\-$~!8DZHGG3GsHnFӒOr�(rC9T[K;FrH掶ylu � 0:6,s..h z[i` Vh3< 9߀rH~2U%?}~!9sT[Ǜ !Nջ%usip\8#pV=Fi-?Ԝ1WO`e ng%Ь?*< Iyfd Qp9S{3qe CM `((B07Ne`p&Ȅc3R13"ᘼ2k_ g!*Bq;Z'zfnNq =6!�.>/�.fN;ЁAg%?SKd(Py&�Nx@2}*>b.{IŔzxZ|Ec@0`+/6 qCbFA<' O;9ɟlXqg*}!36hL.eo({qr0]UƀXÜcxv% ,?6+y11ޞkSU)ޑZE0zFBӦav dk3 $NfQcYIH0eYztHK7/Y1;rHOUFW#j.EᕾDQ*t_~8Ɩ}'Iz8gNx} �GYVzQ ,OYOQ3aܹ}#YGv"I0^U.w) v@"ƜG p4^?x$LtS(Zg Qd?OU6̞G E1!ȑ3oX{_檹ht3 ˜sKJl9^A>)B 6˓Q^xJ>�9voLW5vҟYLti|R*Қb$,颥]'#Y^;J}v=fz=Li.XolIņƵ2�==NQ`!4<D̒WvW)�MpD<AVdCܔƁ1zL_.'<aɀ]% u�2|Ugndf "p<jn;w0�^]3q|sqhb&A '%Q 2}Nk!|{˓崻W~`j!?g#e q<u8j b\G؆8n}\:Ѣ  a(iCkՖ|�Lw|(/9+H>ʞcg3vő2 7[1Sp{ eu<s$ܻt"k*Dxp&a:pȬg$=ơqz�(+u[QWr5W]3p 1炭d 8D޿O&DgMӢ >$ \>]kNFCp1ӆ( I~S~s?'IMZolrlH 8všȼ[pD 8zЅ*'mB/LJ؆Qz^6 I8u\ gΡl$�ʞwRn=w.p6h"G^YKJ$��q2f;OgȀqsŘ}&6 p ##)fi4xuyISM{�~�;8WQzVv_sy`ߺ=_,}mLU&zR88J97Ƿ_ш#s+^]ðIc{_w#aN0*d4*F|0;7hL:'3<K7P RKmcS1\"' _H,;cZʉ8>K;,5m|wqD?` \^X=/H>IH?0G:}uws_ЇJb8ñS# gX1FDO?X$*%v➝3_ Y0gL:eRjT kj~c¾p -&n'*1L['.8FL!fXU8Z.` kme~p+M깟|ezrnWzcֹn5T 3h.8P" 0V3eE4` /+5kw\ZQ*]:Tt0?FFy1M,G ly ۄLwL\#rc [ h`JHʆWyT,Ư G⿾c_0%[<EÇ!&+/5/"�drjkK b!�'�k/$�P0򞴏yM[jѶtlո٩q&4do\0: lԼ3@sF~NJŋO�X&_%ڇ_ݯ6 x ,cLpf(#̬1C43D|.|o?'{>|s HRpn&3>`1pm8yHw/k?Ajf2w%ɘ#&v㚆"ʼn_ /}fo֎ǟI=?0ngrQ.><ڽW V08TF~x֋0]xh$y@/ lJJC;vjmGDc0QQ!`vj#{?15rETPVrK  g0 +0EW �#LO2;zj$2wa m4 2cW'c*}2}_<~Z|qb[M퍳)Ggᡃ!K;Hק �@ ן*W][>�c-?H��!IDATW6@L8y?!@W O0S'gc m-mcV �gzUdY _(C}'�u=K: �U31҃ EX�$1LB~>C]Fy<5/I}ϢJj?35ԇ�DmϨpoB[b Nh SwEIx?3SM3=+N*{teE &a(<<E2z (}>w�?ꗬ*NaӨgSlS�֮Йӟ`p7+6@v?\/}B!8euj7߫#_[�0;)I7|{_@*UvJ} Içמz\xU㳦w*J| i>ӿKٳ�pvo2-:+o7}beCLͺqJ�נz=f˟�-@XVɗ]x!fv?<#\@~W|�>G ΞM 3a&/_|95 ̶q �= iB_yA!p&l`62@Fh_*rûĚcMM/.�N,(2=[_;X4^8Yб w\wXuftŬ01c2 t@)PL)<Y qf%38[ YW_ȗnww.˄SiN4Y:A*/>qL:utVecx� u֥kkyҕ}'臦#;ES*A0OP{d:}p Rŗ8F#n�A0-,؊PS+ 4gFx 7�0$4`$F)>F,|):xCa[�"�Rp|*je\b8ߣ߳HHIp\9as09̡B;N}w����IENDB`��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/Telegram/Images.xcassets/Icon.iconset/icon_128x128@2x.png������������������0000664�0000000�0000000�00000105455�13262451251�0027373�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������PNG  ��� IHDR���������\rf��� pHYs��%��%IR$��;tiTXtXML:com.adobe.xmp�����<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.6-c138 79.159824, 2016/09/14-01:09:01 "> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <rdf:Description rdf:about="" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/" xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#" xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#" xmlns:xmp="http://ns.adobe.com/xap/1.0/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/" xmlns:tiff="http://ns.adobe.com/tiff/1.0/" xmlns:exif="http://ns.adobe.com/exif/1.0/"> <xmpMM:OriginalDocumentID>xmp.did:1ACBF4C4D24FE4119247E59A0A0429A0</xmpMM:OriginalDocumentID> <xmpMM:DocumentID>adobe:docid:photoshop:167a618f-0639-117a-8f65-8cab695dcc76</xmpMM:DocumentID> <xmpMM:InstanceID>xmp.iid:ef977eab-e8ee-43be-9712-dae3b7cac539</xmpMM:InstanceID> <xmpMM:DerivedFrom rdf:parseType="Resource"> <stRef:instanceID>xmp.iid:1BCBF4C4D24FE4119247E59A0A0429A0</stRef:instanceID> <stRef:documentID>xmp.did:1ACBF4C4D24FE4119247E59A0A0429A0</stRef:documentID> </xmpMM:DerivedFrom> <xmpMM:History> <rdf:Seq> <rdf:li rdf:parseType="Resource"> <stEvt:action>saved</stEvt:action> <stEvt:instanceID>xmp.iid:013ff985-91ab-474d-807d-b60bda015c0b</stEvt:instanceID> <stEvt:when>2016-12-19T19:15:42+03:00</stEvt:when> <stEvt:softwareAgent>Adobe Photoshop CC 2017 (Macintosh)</stEvt:softwareAgent> <stEvt:changed>/</stEvt:changed> </rdf:li> <rdf:li rdf:parseType="Resource"> <stEvt:action>saved</stEvt:action> <stEvt:instanceID>xmp.iid:ef977eab-e8ee-43be-9712-dae3b7cac539</stEvt:instanceID> <stEvt:when>2016-12-19T19:19:58+03:00</stEvt:when> <stEvt:softwareAgent>Adobe Photoshop CC 2017 (Macintosh)</stEvt:softwareAgent> <stEvt:changed>/</stEvt:changed> </rdf:li> </rdf:Seq> </xmpMM:History> <xmp:CreatorTool>Adobe Photoshop CC 2017 (Macintosh)</xmp:CreatorTool> <xmp:CreateDate>2016-12-19T18:26:51+03:00</xmp:CreateDate> <xmp:ModifyDate>2016-12-19T19:19:58+03:00</xmp:ModifyDate> <xmp:MetadataDate>2016-12-19T19:19:58+03:00</xmp:MetadataDate> <dc:format>image/png</dc:format> <photoshop:ColorMode>3</photoshop:ColorMode> <tiff:Orientation>1</tiff:Orientation> <tiff:XResolution>1440000/10000</tiff:XResolution> <tiff:YResolution>1440000/10000</tiff:YResolution> <tiff:ResolutionUnit>2</tiff:ResolutionUnit> <exif:ColorSpace>65535</exif:ColorSpace> <exif:PixelXDimension>256</exif:PixelXDimension> <exif:PixelYDimension>256</exif:PixelYDimension> </rdf:Description> </rdf:RDF> </x:xmpmeta> <?xpacket end="w"?>D��� cHRM��z%��������u0��`��:��o_F��O3IDATx}wdU*wd`vM+u b\]%($ L`r+t«Uy ={%!H27K E�)RH�"E�)RH�"E�)RH�"E�)RH�"EJZH^*%FD*�ܛ8B-'4 ,B)W7-`.0oY3 [Cju-%MĔF 󣤨! (�{<@8aBNICB>{;ӱ[;dѹL2#Fu (Lki [,yˏ .XZo^L/Pc j!R41W9t[8� R0`g cA8t:3Ӷk[zF=fo[;7Ӧ{H�D^ .>zUd՚ëN=3XC+i:; ;NON.H0D �) F '98`wܾ%{nwWBWt\سΎs9%ǝ5[Zp{D٫{( HA -CC;_M||s&wndut˻/`NJpʥ;'vEE:ij$6]}jTh kPz^{걡O<޷eplG �VBK[]{KjOQc 2 {V)؀4p #spdžMO<0Ï$$lӒ+F@K`ug\~YY׾5jJ&&l2\^ @ LAné?|?fy$�TDjO† =ιLil ne�ν *S^h.hL2_玁'PP�*_|Ƌ7g݁N$!͌Go?4ÿ7?8�*Fj\|Nep)Es#ad X ne/=/nxt<-i媏c3/%EeHAd]|)S@LAWzoG+~ 7\7\^\pnTb@ Av<t?|/ye$�LbSuw嚏kxIg 7e |e ,}ho#a׃7bI��0 yoF>a{wt?xwůPJip7N!MU⭁BHߺjw >ښ%�X__[?B 2 R%�Hn[}Ir>ߠյ,p U^JU`¶2;uK<tًEY{!$|)0({텔O� Hml?s}wR 7RRYf{|@`े<}&spQK/\/;ӜTB8 Ig}x�PPo{l H�`$ݟT5i"eZoz[?0g{Hz�Zs’"GqtIc0{ $�R�h#X}K 4LR|)üN» _F*]͔@@˯Djb>7]ȍK)ȡkcenU qe?ix:!ٴ#elJ>tXs@z�Uԟ75 /e\CAĬ~m� �>1]Lܓ2ް�)`rO_I'B "�)>=/WtŇ?dR %"ӏ?~@`/ZsouK4a̎=[|NyJ�@Z-^?-r9!bL:)`졞z'+ ��\r+?+N\R H B8uJ��noIkV~7՚t#2'e=ym�>基wZ]"nH2 P@ /^i P^x1kZ~~;RL9u<QU?3]察͔T~)36m)>͟4{߇P㪉ERf=y筕 ̶H;篔/@q{gy@5P� ֥K_cT`L�B~Qse< � ƕQqiTBiTkquj3Ef  ˮ5]&Y' |0= m/r}�@XXsRT�n5/^_~|Gg*`�x[>o o;lRuDj0wT@�xW];]-T~)'Nrugz_qVi[|+xh<ar%IbF ?xo31]j�-~6!!-7ZvšJT�0�_FhikL]1yȣZn[`XJ/P)�@�x}ᒿFB+֜_ޙ\޿5 FZΠ`j # oxQ!$EW~އBO=G2cnzv}C;6OUPZ h}'?>LRfLбD}K�kc*cNN&I^p}LR�bX/?DpA =_x{^}i@S�Ow;GDl@8:_zx.Q�#GyAg/<1gW|c3Ef --�92gghy'>[~,f`t� ǜuc-ܮtl(5s(ӭ\GU"¶ "L7^G0J 2�O~Jom"VNY Ti.� �<|]FN<�L#5Ø2i�"hy@)>A#?j}_vM /"�l jӢMW0Mi2RȲ/SrCVIRR 癝L8P!A@kz[GCr>yȣ!J8an�iڦ_Ȕ)t`sGT4 I7`ɘ⠩�'fFR)(�2@PT@U;WFp6^j/`*c�T-=Zqi�L\ c q\<DT`8;Lܻ;%2TW@L\~ٹﵩSi"rirЇ#w2☆qNk ",p1>k?IPuK__1E O@,;wr7_)uMGt Θc8a-\[F3]նD4VuߟNj{/?؍q8238/| )rk3kg.ێAC!c4�D2plh k` �P}T= -jpQW-B:!q&D5tiC]iw\oT/&91� ֥GNZJz.> 0\4?GqZ#s->֏˅& m0 [\e ,£<:l�sv%ZrKo)>bs73± \4˗Eqt#ϭ/�@Ck+T-�Q}(A݅| T,�@7Sȴp8�DU\0WpFks7'd`6R}݈7AуUH#t K?5sJYǟwa`eO�`9#P30pG5?k?Y/8H @@PL9-o�`R �j͹o~H.~ w]+EqlC�BKEmG`_ #s{kU3iw߹g]IǟwM߭şVq5/]Oӳ.# fl@!/ HnmjhU2[2|y&6<|'&)8Y�\zRTSqsDLŏj kp.Y}Hӯd*zR6L\ uЂ᲋ff\PsֵoMlxwp� @ vocKVǥKrY '6۞zI ]qbVR@�Z:@@; jm2{{w%Q�_qLh嚵gv[@sH%a\,!< Q$va9@@|lp\E�!#±k xCnd�ur.5D䤋.aڰ `+; ؞bTMA\<KưV`)2yTޗ$@YFb­F5,ÈzCpkf�@d.n-]pYQS[B-ԱyVy㞾FqD'H!Ahi)5=@ִpepIgpKfze׬EQ̏Å<Iөf�,2`0Sz-X+рQgXЮ�CǞ}RP32_JF8^eKcnξ1� oN5J΄(,O13U+K .$�-rykJ:L:t-Z#\Ƶ+bX;?7U�h `҂pB8� *XE^y?V:[ tlf{F=� +֬ �TX^@/2U+bX$e5n@/cs$!GUOO0bA |@�Mrt%;$r.ShT`env *�l ZҕGjL�,>8iR!)\ dIW Bi sF0,m})&2p]aIӳP>blMi둘JB!%tڳ2{_ x$�@�>̵1![+B 虎@}PfE Әʠ/v0#j:ZW8~ 4a8h V9 9BG=O7`��W֭nNcby /*;B{sqᠭ/ "p@,ayk 4m c mРQ( �$|Nf#pۄC@, ZcOfy<5t`F(� ZEǜ$l3'"e:,|vfs�E4\׬¨ 9{a@G iwrpu�Ġxϛ Q-�]01R sAC-dz8bʌ{±Ե,>*s��_ZK? E43:oX0nskFx$i pXK I z�H ~~ߢm$pf)5h`�@�XvIH jX4VpRs!`3Wv8RLs1l6EMيU"�ݼbS(�D+�Ost � =8DPOX H?U~v6*.Z\kW܅aUـ1.)04q/a1g #vMp�#>eJ%S~o}gJ%с_±Xt ju¶z1%/88a[Obd㛂rY .[�rċWHޟBo< V,j6ΧfOFicw_:S!+!"#v6�簒Ih0HQg aR~�XUmZL8TIt G 1-585�d* '[ӛ0ޗDrS{GUbhLD ΔH +4y&åP!0u@sP6._aw4�Kh],�:rƏ Ο.Z6ܙ7*_a堭?141,k.3ť*c88Av?Ka_/+>bG?kLꌀ�*EG1a��}U$lKj8y=ndTFXUKcry 5P [ ^M8̛5F@ ͋?°{f* ՛p)^ +RBwc�dA`,:Om' �6oQnD_9:]ְVDp͊ZFCPn5#Uقa/F<MDꔺyZ�;zS^_Z\ sC# @9/qɤA HU86+A,�ֺ|p+Crsg̋5diKWL@{:)t pip@Ųueڔn#75\o�AF>'OQ)#}|Pa'  �hӢܱ4_:IN �T-4_$#0 9Vp(\^C{.~%̌RhHòh 6ςTo�P~Oa߇ߣ$/U/4SBpp]R?>0=�Xc+$�=Rni WŹ h}ܬ|IahHgOk#,iBelR �`R|G || >C)p� <(L RӴj߽~Z�@m߬Dk]@ٝpB*Ú0\QuKbXQ�#7W.~bs i?WCY7ƂX4ݽ)M! ؾ\GWAI#0L4m)*ye0J�,k&5s˜3?Q`0  ,%Q\kZCj]]F0l)!Fh aQԔMx;7@750scy/%5�Dz! p5[L��Қ̃1aYfe [ż M%} l~o< $^iͶ$A9ۀa.Jq~ʵr{A0{rǥl@@pe|OǕ�X<ȷJ8)˗džrU}V6<fS{A{_ I;Nkac-u" e,OCc[3bGJ'ߟr (x@cndPb 04�G f ,ipqWũanYLOLHs c*~VAƨ[W#n#a*ƾA+|<;඘tO@,Rbj�ƅMp}j ^5+kq(嚼j_aAO<mR~.",iJ{ye,�U!Ocp}�q@S<kхOkȧEzsN8\؞'0I8Pkj s#58���*z=E ( Xjpw6 Wdp1f/WUais 5!b?KL|vBCFrB#C862I иyD@;J-a 筈ڕX [_W7:ޗDWL`YKlZK{p.;ŋjzG~ y  Uay A1 nWTNk 㪕Xktά m2k DY~uZb l.*dg�OAUh |p~3\802^@xrD b ]i8C=S �Z@ W7 bް47)!D}Ͼ>)iO"cfS{45,mASpG_%]: 4R>'?~_sO/>q\8`L=  EH�40%P /9TpbKo9-6\7g_9G׫g&cqWVaO_ry}xzq@F!aT8clS` � "md`eJ {m lJY}geXE3Z;֝94>P` |7_L{Q<7 _O�<Sxe�P~pI ?g̏@!X71Btї`|#w x,SM #<c:[x2CT쫈F)912u* ir} \BepEZ}t o06oʹ@k]?!n՛F,_rZ+|~Ak� }ᨅO �e��ar|r|~6-e{ })$+V" 0!p߯kcᡌr"^bxMH0^|+\4t.i�ܶlGas]ECPEœ ܂tYc)?�,rEc=) 6tEc! $roBeA0_C =>ؐS8�V#01}T'dDmֶ( T㿓ّݕU[(�-JuQ oq0VPxiTOڠA{ߧL'k!S1�aߠQQ /vFl$Ƹ znP(jBz)?�؜cKW#/F9{ b9xBqM?(ȋ;.qCA|T_i̙M;``i]�)/PAn�:R9ok%1DpO5]'F)8ς%M90'~GOsX%B8\p峖 @G>[氆TG8Ha09"]⩽tTp/^ފ$ ~-4/Cs!_pn$Y)|(I[(;mbܛt'}nn;FJxl .<ATW~qU`݈܂=L޹#z;zRaHKE|Gceۡ(q@ebB\ǘ� 6aJv#W"pZ\kPTwG =)F@ Cv@,aqSyuE1tmLQܷ?b t*97ق<>qs@šB/pܿ*z,Յc[ԔS�a3ԛR篬h10a_ĎNleudENl6znn5FPNzUڈEMQ /-W ֮ z(.!64">A%JII͍� ln$)U<Z5αsĖm+XEpzA4tN7x=ξ[\w2>;;aV(=) PYvgyC<@E'il_1㶑f:3k9�-lkpM,T=�U�>v61\/MDlZ1=}C#*)k a^[gIvTe JY=.s{ƌƑQ(/|?ϧ� 1y��lv$ M!8ǫ]ilhKগㄖ.^р cuC}҆cnJX>ЬP;03mD/`ΟTO rӨ�8]~<F 8^jOCC qҼް/Ê4L۝3pg OjU;nHh-W_/Jc($qeܘ��O'cA󾁢BSB<{hO@CH)bde[R%uA`V FOH-]A]40+4lNL�*rskcyj_y=03.�aw5`=j@qA<a/%+qZ, ۈdlۊ¼΁4RSSԄupGm[ph(w L ^|xb8]/J(7~Ʌչ b�<倠HUT]0r<g�CkTÙ kne.;%p8\ esc)0,j"Ԫ2M@K+.89ǡs"tMoɡ!%ZW/"**Hpދ?n˜jnU#XXS\ )Xr)kqX\@♧~ 6go܇E c(E8X;OLb'X}\\ NoumݘScmkIÙ8bQS:�k_Qt^M2h,#8af,>>!@P2v_[Pyx#(`C q -0m>\?GC\FM#@!(ޕ^yC6r\@9Ok: F^y?51߿�cpNGD< �sxAJ!"3!H P��. XsGvᅩo Τ6vSCeE?23 `w$rqa*c�cwݝBR8,W{XQ9o=e{� aKW�+yS7sAqr~#0>p' q6A�v6(y@^Jv&o+-^5xk @kN0/pobx@s |gQ;R!bw`b5���=;$pREc ; 'MF;B.МÖpkw *cwNc"K~~*ңa)`1�{:C(R$M=iGWAS]sүRP<�((?ơq)?PA "쎽'�(�ұ=;4n˝ $.EMQ4ĂSc]I eE[䅓� D,A`鉉8mNo�@:<< M!lϠ'eM(H� ˆ#8ܹs?jGSF*F$_>A\?G|dvǮauN�8�aܺǐ! &FSz:26<%xѩ.�HVS)W~~#X)w 2�ցmHO��m{O(,@a}gjXslӉ{:a;<Q+a`w*SF-*cqa>R:}m</M7)3�powN2Paso{S6AUu mїHl=EWvГPiNM%=~QPP 0}? >7`D�@W_HM/q( Lޗ�k `__oÞw[9@ Fx#T7Ͼih}9W5^޶���-H)dz�lLbo0+;a:Z�rfM;?{^y @|?0B\�[^54±]djOC]s\:" `N\ނI[X1TFK[ޛRcJ+yqh0%P~`[0wS op!MjRK�'mw{ߡ* ^?GSp{RhPrhF{RX~;֡׷�A2_Ys_sp˝IG?<[: }]xyWLˁ̞,0֑@.b{kȉ^b-!|{ߐ=.,SwڻiHgE:a@acw)m(l^KOz{CLAS̆6vć7{D /,9SpGԈspkvm6q�}heU!6;ecȦa0ca[wc#*H> H>5ǚ7rz[Oy_4�~c3_)N/#JWjSlHƆȘVYqh&�R̃b⒀R|||AQ3:RuX& �q�_D&L'8/w&a4f z�/փHAeJt&7>Q>r F>S�~O>7gG �piᝯm;f 87ܙVB) IK;chAWgK7`c{ܵevTX=c<mXc٧&L�>sBGqK`SW (ܜ9Mև?ӈԊVacKw6 (?P oaݴ9H �Y`g6=(O'LAL~7#lJmTp7TsW< :ބ 2`@fd0(/h'&3ߟ<ܻ�M%�p�ؽڻet9-߯; 'JO v=�qא�TqˇC>zKALj a[WBO)>Q ( 18gn}a{2^v0hlx~2sAJ>%6`:x;9jsБ@uu{MHsc ؎޵3zz�^ir7;  0+J I _xod �Y O>zT>Ϝ)/� lLsBn཯wۺb BC(ğwtݿ|ށh@ML&^Ik<*O>:B"yY}� E�`>'"-({˰?.oTx5Fv`j+`0{6:C^U>�zMr@֭ j�2=7ΌM* 3g@+7a<wx- E  Nݝ/a:F\Da-&>G6k_jȼv7&8:��29m뭝_$5 y9�6t&J i r$2t$ pA֓i!V7 =xϯ?nmGDWgC*4ߠӋ~ƞE>3vkȃ*�B eZUgܓFo*J 8({)p!2A _ B@`0Gq8vyH[6u �&PSnу0^yڝJl0б>}x.Ru L;ї pp("�!aCG?y0z2* !j*Ň܀=iBh`OF{>?]D*ndzݞOZo*` <{P4Ȋ�Gd>6c1/ľ~ܲ I`Mxif<?  ߇Eܻ aL5 4 8)X�C07?ӹwcְN:L�EQS4yw)DPwέmD0܆"^˭qGuiM#`9!mjDW0Y3#KC OOUS��73N 5� $g)AȫtQ hKZ՟W(s�TFms;b*1*b3)࿕1-WTLp_w= ><;$JWM#K-{J&)`L�rtBMB~Wp qw޽w5�x�TUsb Q\(3Lǚ;±n ~wo:Lf Pa]}8?W\Oz !?�` H <{oHA~*R0 |**Cwď6Ʈ$TM|)?U~(z or|°RaQrhlW,MO{z4eٔ{t:v[eH0ߥvx BEx�!a@?X�{2Tud8s"ןP|OHLPtc=v`ᕶ!y TⳒ﷤{ *\ݚ aN_YT�!!҉}Pu9�ۺ12k ;LP7 QԎ\=+ Ҍt ~P( mc!4m?6 O:[+٠]wyl=> bE�=b?SzHs�l)0𳍇AP=<sp�?}0xku{dqï-@ AS6 eO CP wSd "d&�ɩ�Y/ #,-O @n'^B kxh n|v/7m}s,(T\9GTjnyʟ)nSUhz�|ݱG((+ѝ;MD8G D`{=/ysܣrNБ]@@ @SKsFJj}r,a&REՆs %ƾT/exg0L|x)așnƟv#Gh\v wy$FR<�xnL@O?0Ӗ{SdA@vlK Yp hnOF$zAO@(~?z@G>V\fč(AHSmOʢ wEѵhs>7ޓ#-7xviihs�@xizs܅@D6l@z \A*=W)j|;GEӮpb67@HLqǦøx�jZ�~2XQ�{OZ֞W^3_m՟L@;]NBp`%)OG9WR5֘np Bv';7ىh@EDWRš<s~e҉J ±./7mlOW p�"kF6`,l N&]'VR9ɟ~n2?`[3q`6ՅpQ8gi#icKW҉])u?1gf@2O}=ܼ|2"e4@6 X!rO_sD&!3cc PK0*89 IS;v8H H/ �fbrIM{7לBz8TQ�1J0'.o%~r@D %yT]=AcP o4}(R{<z<J0CAj~.c`*X0<7#9/pX,E[ v7kd1~!}>}_V+7��KcHK_'@ X0 Nˤr*rPKBFxJ}ӱP~ n_ S\Wi��/ҙ0;_m2+PE (%\V~BՅg ӿ"9;kg]n#kDUfʹY bßC(q [PT{Qt"cxb܀�<_;^|0g�ȥ?dͭ#T@Vq,�1%/JF (0%P=~ET}ka;^x9}[ərGn�ȇsDF } w=6\҂oCc0 VcXՀ@oG�=nʬ�͎y#z�1ufY7PLG*?c b"u7zSW"�d@@mJuXJ7Sտ`R~qX*h02f{fL�HP}Ɵ~92(Xbtɞ"dpO(Zրȵ`Ƈ3\ޟx])$�(S,[7t\x@@u'0 ǁ۰U%1N%YBǪF# @�zk۬/*(` /^B0!Ю5'n 1DA!x%&NE:׳[,@)` hñ~fֺb9̴T.FdžplDe)?Sr~CQNnO8{^gWSOAH ]ݐEǏ ݔE*. ♻eg+ÍWLЯZ� HH}/OH%-UO~? i;֫)n_E.J�kG;P4s[Bo�[jxB!۷_Oc( 㡛n2zڕ)3 e2w}�rRǻFSUSO7c?*RH5hQ63WqTVlѤ 8M,FQ|1(4,r>?Xcv""0.}ͳU� 0�+ LQog e\�`FcYo*yʶ^ˏ`wc_ЇxU| �Z�Q+ 37�B1 Ex@I*ZjuO~q~n fw1jBh'{o7|49Wp 9JҎaO7Ɵ~uWv�(zyo -m@N?#-Y!0Mc|@)\�\QVzy]_okhfFT~_Q4HwCs�rA  :7|,]LRD@p?jW~-�XF:s׿j;দ+67W� t�Q�k=175":=).W9@|˸+_pƞl"s �+KZx|N;˅rhJ98rO([^7gy g؅5� T]1�%' '!"b}~/ooƽ/z%� /8pov $ c _�?#@xV?;GTm��un A.?3O}VYv".2AO0`wovҨQ^� ВU7E86`[\` �QbW7Wylϩ% @沷W'(X/T:3U/��8S-?cw}^}J@a\ Q 5-95p'>Ɏ9g-4јQ'W:xy?H?U-|_D(fw_B((0R Q@\+�`}q=Um$�N B%W'<]XdW(A5Y~b`sA|'<oU/`|DAE=-/CTTL8Xtu|T=ئcxqH oϝkgō�0~o H N}.�K6@ġo߷Ig I�<o`$6�ĔZG%'YEX@AuYOWtbdZגּ�f b�P׾U=E2[PMw~:7ӷZ{wx ݖ{Vp} �l3<o y!ԯR}ǻӯy35 *e8~[y1)gsg}WyYZ H NP}ǻ5]H]fFAE)~-8m6#%}6/`hA �'(g@! +#S3Ύ矱Nwpte<x?z%�L.-A8 ԰xk߬|T7 ئTEu'O~`9o5ŗ�0@XJK/WOjj]�`rT8d(*Dg:/?G޹5=i~&`@�|娳SOj|F4+)#ڞw,;}y?Dp{�0}1l [ur%λ,ce\ [/_}n$?)19nUap#�wligP]k#�7V  :4?e'/<'}0K{oznT| �+dEꗰ}ylIk U5{P@HC6;۟ .nXOXYY'`v  O@"uؒNbX˖t 5.\@s 3;)=� 1nw"߷e߾nq i%�TW<{`SEkb-V'LO^ZM-]pl7vEu=b+D_to~hߵCd{oyG&ˠY zg. Yl1caVP58wAArLqwHbw+xm&:ڲY+oXz�M@E9)uTJ y+VP˲eT` kpz8�Us-#|Gz[^OMޤ N,Ek{hleaq R%�y@ɉh9Gy(Z@цjXJVkmXc3ZG"!AU;oȂv` l3%҃ $DjG.sS$;`w/2ϲ*p�MrGr�@$E "\"-Su0 "�-8 1aɀl*psSK� (Cvf@Pr@$wZg9;E܃(s �U^\i0v*89*�Tb� )U(� ~E+&Rf3r)sWJ"E)R$�H"E)R$�H"E)R$�H"E)R$�H"E)RU�[:z s����IENDB`�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/Telegram/Images.xcassets/Icon.iconset/icon_16x16.png�����������������������0000664�0000000�0000000�00000001472�13262451251�0026643�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������PNG  ��� IHDR���������a��IDAT8}Kh\eϙL'$$M)iF5.1MXAtEZt確JmH#j@K^riɘ99774T>{0h@ۥ/Y}KmGNQsI˷n^<w@Lk>ox9WF~ *Dֳ17K*[=K5x0ڠ ݽ={z>?{tSor z牵eS[Zj%YIQ/ܘI'5?}V6t8ΡZwoj_j\^$lnU;t<fMbc1:3ўF:S$FqeHZ.CpvOa#]V0MRޝD_ u2 ]:JePEIAZDԪ=]+5QN1%dBI[,)RIxc0ܕ:".8˩yִjFl]-'M6O]R!! o]`lHcm/KE8p2 # kR8<7#Җ;e.ͮUYPN*ON)KkK^`19VcK6P8-P J ;LJİK*Jv?1 Y "=$~nGogd[ׁcQ _-uW2#_qg~6dGԱr˭LNJ?$zh����IENDB`������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/Telegram/Images.xcassets/Icon.iconset/icon_16x16@2x.png��������������������0000664�0000000�0000000�00000041437�13262451251�0027222�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������PNG  ��� IHDR��� ��� ���szz��� pHYs��%��%IR$��;riTXtXML:com.adobe.xmp�����<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.6-c138 79.159824, 2016/09/14-01:09:01 "> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <rdf:Description rdf:about="" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/" xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#" xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#" xmlns:xmp="http://ns.adobe.com/xap/1.0/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/" xmlns:tiff="http://ns.adobe.com/tiff/1.0/" xmlns:exif="http://ns.adobe.com/exif/1.0/"> <xmpMM:OriginalDocumentID>xmp.did:1ACBF4C4D24FE4119247E59A0A0429A0</xmpMM:OriginalDocumentID> <xmpMM:DocumentID>adobe:docid:photoshop:3ecbf9ca-0639-117a-8f65-8cab695dcc76</xmpMM:DocumentID> <xmpMM:InstanceID>xmp.iid:271b741f-ca3e-4132-9d99-b5c2a2056dd2</xmpMM:InstanceID> <xmpMM:DerivedFrom rdf:parseType="Resource"> <stRef:instanceID>xmp.iid:1BCBF4C4D24FE4119247E59A0A0429A0</stRef:instanceID> <stRef:documentID>xmp.did:1ACBF4C4D24FE4119247E59A0A0429A0</stRef:documentID> </xmpMM:DerivedFrom> <xmpMM:History> <rdf:Seq> <rdf:li rdf:parseType="Resource"> <stEvt:action>saved</stEvt:action> <stEvt:instanceID>xmp.iid:013ff985-91ab-474d-807d-b60bda015c0b</stEvt:instanceID> <stEvt:when>2016-12-19T19:15:42+03:00</stEvt:when> <stEvt:softwareAgent>Adobe Photoshop CC 2017 (Macintosh)</stEvt:softwareAgent> <stEvt:changed>/</stEvt:changed> </rdf:li> <rdf:li rdf:parseType="Resource"> <stEvt:action>saved</stEvt:action> <stEvt:instanceID>xmp.iid:271b741f-ca3e-4132-9d99-b5c2a2056dd2</stEvt:instanceID> <stEvt:when>2016-12-19T19:21:09+03:00</stEvt:when> <stEvt:softwareAgent>Adobe Photoshop CC 2017 (Macintosh)</stEvt:softwareAgent> <stEvt:changed>/</stEvt:changed> </rdf:li> </rdf:Seq> </xmpMM:History> <xmp:CreatorTool>Adobe Photoshop CC 2017 (Macintosh)</xmp:CreatorTool> <xmp:CreateDate>2016-12-19T18:26:51+03:00</xmp:CreateDate> <xmp:ModifyDate>2016-12-19T19:21:09+03:00</xmp:ModifyDate> <xmp:MetadataDate>2016-12-19T19:21:09+03:00</xmp:MetadataDate> <dc:format>image/png</dc:format> <photoshop:ColorMode>3</photoshop:ColorMode> <tiff:Orientation>1</tiff:Orientation> <tiff:XResolution>1440000/10000</tiff:XResolution> <tiff:YResolution>1440000/10000</tiff:YResolution> <tiff:ResolutionUnit>2</tiff:ResolutionUnit> <exif:ColorSpace>65535</exif:ColorSpace> <exif:PixelXDimension>32</exif:PixelXDimension> <exif:PixelYDimension>32</exif:PixelYDimension> </rdf:Description> </rdf:RDF> </x:xmpmeta> <?xpacket end="w"?>hy��� cHRM��z%��������u0��`��:��o_F��'IDATxėTW?fvfggge,WPX[HhԶԦUhMc%HZmZӀPicBV d ݲ ;Ώ{e@Yd澜{9߫Ds�J)=\/?l7OC)¡|!8;3ǎتOş�M3f5ܲ5j/(bA�A[ .ߵ烾kJDz֯={}oZ[ |$hS zDh®?/A| \@}B-˿`+EĄ"xl uuN@rFǧ]ɛ: Nez5G^kixr?3̳~1F0_|joM/g#b퍙Rm־i%&eh⏆;=RtՊ4ErfP Á"D!j?s"�Lgݻ'&_'Fa?"4[<LL[N&4-{׾p)O^+g<6{(o�yvs^$�QFP|=͚Yl)O Tnmdk[ EKejł$+ֱj^O6&qbT2MPa$R­I>ތ@ 5PF]N"!aze,k;r,mMSԄʡPDl:ȗ Қ(GN$Φֹ 8vVA+Ų4׳|V3"^1"Vmh#2JؙR0dž!(Ya"""s<sL4$q40Rh0VpP&Wdfs"AŪs((0 � A%Rq�bLdG_.W#N%4-VDC*e?!E[C-v�\8_ 8{:�*Z0kh)6+*8_]%}F-k:z|DֆZZr)&ƄR$]w<<PXk0B~nS1Vk-Z%\R]PD+hΥސ&q݊aQ "\~95 *{Έҗu8!)FBAGCy$=Q!Fb83 Qp|R+6Qtuq<W4}%.+Gϡ:./GtK}ʨ#8Óf`V*|Q:lK_ː_%98J5XHV>GUr0z:7KO0\(U}j?c5IrbߙA]1)pɇ$8_aסWԄN˜[3ݲO^c$0>.5Ji|#%]_|oHwsd 5pWs|}c&9! An|BƋA,j5JKQyHU\j_Z-K\1QtowJQm?船*։X~#Q�QtUAwO'}`dzD:eY޺=>먖:'vTZB".d\`<ٹEYUm.5`"D̤Jip<.Ȓl Ӓ9QWkigo[f-$Xl -nyEzޞp�&WiUT2](Vtȹ28�v|׊����IENDB`���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/Telegram/Images.xcassets/Icon.iconset/icon_256x256.png���������������������0000664�0000000�0000000�00000051146�13262451251�0027022�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������PNG  ��� IHDR���������\rf�� �IDATxwu'}Uݓg6 cwA$�F09lK?~eg$+X>?s:9gS&%HZ$e@$rZ8:U=ӳݝݙYNWWW{R|Ç>|Ç>|Ç>|Ç>|Ç �|,=x8�cT"R�p� ſ�`I)M�0LJc<@A-, ߲[֩-]΍*5Ĉ+a (@-I HRf2XLOK)&PߨԳ3' ZԈ6|bXV6sp @33;ܽ Bk97б[Uma*MB@@syI00n&?K i[ɉi+ԆOk#gϜwH; =/CQ ZC=g%s.*aŁu=J�RX ),@X9^.i7q1�A MVjzJ˞:7uto9>vzxB H[c\}M ozڶBܞWɉqs84 lSwҋûvC7/ymwR H2!M+qR BZ&LߞW<tg߻_ZUqm2"i͗vkwZ% R@e(?+ 5� ̌=uhWbOS3_-Mݨ `\ӻ;h]zmKӄ44[S %�adɃOS/ԑ7zx+>XHS[ol};U4!, ] ?D63PT6uOoLc#5 V o^uk~ιгLP A\19?7JyZmg�U@tQ-BB #FLxGO}適W +zl ,L -׼{>[-WN\aBKCE|K"@82W??C4G'j=F�  nwwߍl~3g^@j,6t_'ecbhj$ BPVvji_ E =hNAAh{Ww>r >/lF6wh?S_Jk=z�JA9^w՚Kn BM�R`02'gr'? /k�<\ӻa[IQ"Y9Rg HQ|$]_iX 7?ҹhdP4<xP/_|5/˿EDqg|k},%i�R*W_zϼʦGwCIl@hs|/|?"یV2H z퉇OdO<V1gt7~ \}Uj!iozaO]o.u?��X(ZKO>6`eS xˎ{^%}WDǵ8�"l_~3\R@"=^r5&'g}(}$]��XKu~2L*Jg��<_?5_@ʈԵZGNJm;w/W_2XL`%3� |]?ke>|x@Y^#eO8T1-%V2@5/>\ݳMd4up8ג{^xcZ*X6|ogVȥ`Ko=f[>KT^�l_ O C2k=. )! .,zUW}+5 b+Q}֞?|ߛVjڷX8}c_{8V62V@�d{Cǿic%AΖXh3� C/rΟ<XhӶ+}cAZ:X8r˞:p nދo|7й|A> ĕPz>r/sbo^hH4* �2zeW~mB> S[~sjt7Ѡ/_#2�qu|Lij_'4hPH)�k`țЀ/a1� #\zU|Tw4s`gbWu11؟9vI�lK{?cjs:;IgC=o+ B�t#'gNݍz)Z7:/HXUܣݛ=u`?�pF7 �2еic'sٴ]>Q'|wgy]<z ;E~;ܽrn!-/w194:Y05ۭLu<} ǣMzKb׏f&'Q/-��k?ۦ+Ju|4$BKCpׯhs+x[z��îOYDS|ȃ M5k6wO3�lwm= cNHCC q9sϢ=�lw߆u P ,-?kƅ4u.z}ءun�x8_y$|GhxH@7ߒ|̩!K]O ?6]yJ, G\q ~[|0� WzG?cu)8H@{ܾ*: o }DHSG/3gީCu�X0/|=<idk=>mLzVb|5fb�d}VdS5K iౖ E z<�]tm>?>',W|XrH@pÖ^gS?}5ԔH|?ו.>*X&"[vçQ#UV*@N›^d쁴L`$w"G-$�;ߖnXгJxC|XvHSGhݹ_F TH�O}:{�rK�vݿmʯ[ٙeއ<Q[;;_ޣX-ǖ]PWݼ?Eb<a{Bm>x� *�@?)]]ԗk>R�RP?i `rI�v>aͯ|葉qG2ڴLNe ,@D,$)oBK#{\@�D;]|"}0 (״ [D^r\gMm]ޏclMXnª{~}|8l`q'DZAK-b{0%IΠy{Kp9`�Ѵwz/.tBZ3€GL-! H!ͱU|¦%ӥt50RXL}Z0D֔rUA'kX,CS:=gs:㐆<;ӇT, �w޿]Q?9}4(Ș%6q|v6XF{#0486pOO ~ܟ+RG4?yKd X@,?zͅ|}nDruq(:v,nI �A'/+Ukj J'F>~Q,X* @F\usHCk00=mAʶ|Vlm" bnP8~ 8k8�J9=6>X [vK EeЖ + BY6ܭq}pEg�% y)1�G �khy/< qTY X  ծMۢ.c s".$Z 7ޞn\EWTɉ͗~C4qXwޖإݕx{_E W؁?׽x5.-}xYK"qǦaKk� K}''SB[W5وPK�<䠹Quk~>,!178 #`6I2cTi"=1hk;x pL@hqchӶ˲'TͶ�nycpݹ[_aIhDSa Q@)x±4ce!=1p[;FcRて9Pj3�w|Y>pn-mA(` `Z'�"!Ce`Գ_~cߊlj֞ٶv+AaXlߎ_QpoNĿzuM!)lIr/#LgMNdlaq۠6uuȶ7zaTX-`~onodQs"~Leر:{zucuS /?)D06gcD�DvjhnށF�~{gv=؆a� @0~]2Ԥz%X|F@Os�o8.!LsE3?ER7b�"�+P"$ H=koQ;ѣhZ @޼}2!a%mc4ڵE "~FB"d9rDO�% D {& -JEߐx;Q5 ne戕N8FDGͭ,B={}S 4S"]wA$YX h3�$H v4b=R6ewܛx;_Ps�oS~9#q8r7Uܴ!zpYg�uLQQxl" h},]I:$m& )$�Y4{N޴j?�ھns+oT8}͹6qD@^ %0Пr.@r`:@9wRrj@G+y#mo �\ÛښD`|k Mqg*\vFB )\�?1$$x2CnyoZT F~XyG=CJ{7DWT"xWo;D0sk*j27js©`6, (`dҀ1x[cH#+vR .bZ4`#ػ}0ìSFΠQ. 8nCwm˚e0QnV09kZ#T*0pt< Ò|Pr(OYU`d3w i@ش9 }o/2�pV}&Bڄ 65p㒎*nJ9#h4g,�gժÔ `K;uE% ;c,@�r =ݒh q\>zp}n%l2XFY L8#XR)@WkB. 6;:E�ΟBu2@,& [v!�:Dx1 νlW_z K;pWOwvDZ-�N>{5 3N %KH*z*ł3T@95(G*CIBA,/ ] .a"8vp3; -> kf XXUM&\:Ll ~BH M14i - 3W (0̭s Q@_z4$,ӀL%DA\ ҹ6wXh;a�2fJy^Tejs(65 3 Ed٫,!acG DT_NLf-TPe~PA89K O 4SP#1: %X DghX( �v_x1LfˁAnM1afg@9;6w5=qW]""@+ ]�q%XfWeH@ց$ =_ @`bv2�{.n ^'Zyum!7mh»67utFyV[NH!`̞C uQiW-a3�VJskEğ{R`gK@p IQ[i-K�x<4| `WC wtqnk=u3n N1@LJo9 !fOFщLOn}(<(|-"$ T j8JM$iPl⭫ךۺ2}үYuX Qۄ+"h 2hDzg4 N5 8 "lh=8iɂ�xQ r -1L<Sj�Ec^s4P x�Z<6D>ήKj1!kXLarFu9!g 1D8Xa ̘Xzu~݃ȋs` f6&L@[<,t! � sH 44qrXȚ #⸻'m!B֔H"˯7`h* %įr&*epd<0;qRl!ωל=KXZ@ TK iןw>lZlP=Rcę]Fq_o3v-gUǁ|@OJ318tF-D%/A ѠbRӹui%9%F@=T8w @p MAV6 @RlU cfBy.d @P镖*g(m­bؔK5ELcd*Kxtw6!F{xHrN>`#;\N4;B[kN;i�W!,m: ljY�HQmz)?9D`I@3zuS w4ᢎ0"9>330B*[%$baݝMP9[6l&55qb*�8/*s}.wOI\�<[�S H)"-uu<:1@Wug�p=Vpۦ8܌FQ`|:`XSi%JzܰDs41(U5(00 &g|\]t={Cȭq@@CP$ěG_ b�ʪ5<jK�2/5;ۄ6#W"D910B6CyXaC{C{+GH fS$Gϧ >i,fq=_8�$ ;H 5?%D|K� ll 1ی]aTnyiah2|%$:XjiB{+#hV;r:z! PAizPY g+;BeᏳgeL` @mY R!��W+U;, 1ܷ ;ǰ:zSkǓY Nyx)s|ִB{kn7;89 =5 ŋ}\²UeDL@ s</�15�}>7~@!\Ɲ=ٰ\|r=]{YDS)7$$֭ڻߙ!5pl2\4™'p9.)(e 2!�;N4#xm5 *Ƃ�(KPKUܸ!{{qYWM;,V;Gbd* kω bvhGUCI C3w(&y9q şm~Eğ_dD,|V& ). HiEWXRdsBZKHׅJ9q8܌ǰ =/g|@O2c*"~!%86vB{7N`Znl�� �IDAT}�  ۧ(l];#0Y&-!͜$P%XfEϭ BJ5x%KpM΍m}p O1|y)pMq4պ!~V�Π]rn{B2IF m"W ƫj{B$�ZD?TZ茨Ż67c(B tKn} ɌRg3GSsbp ~lG²WJV& AJ Bj0y&7 "%M"D|̐pyWlnm�'tkŘ6Y.gd*Ds!D(3P%ɲj|Ói(Hn/9 ?\]|^ܖa؞a _Z0�5Hj037;S�k*nϜӂKˆ|):je=)dkѐMq ]WOFai d Sws=^v=S$+*9% ƃ@RS@ᄋ:#xy- l ao!0<[3ܰC><F86dքʙ3CO/"1@bBU={YG`3 KX`*1CN+�H-O pGo~6\.xkǓY OkzܐX3t!r <;碩;Y&]C9K糧\p٦O�BEH{|�z6OH�Cƍq]rMlIws`D i{ gBJa ֭1xr}s_\ݳ=Trw.q6G1yO C$[֪w#k Aa!~#zF7*!DWK[#u!82J@N=']; ]w $~*]Ⱦg9m1W~e"̵ܪfЅ >c $P޾Ղ_Hc kB>wM[-"}ci >@h1"w\,=ˢ8;91J@�eر6hTV.%uu[)TF86Ɣf"y}tNK$ ~:~ RdOpF\<ݗ/*�PX=.YHu-.D�!14.|c(쮯J6�8>YsKFV0EQ]򼖇"}*"Hiգ @8>յz`)L$y!6t4)l;Ӭ^^F.L(D�_lO(suTRH] ˲5ANxd}S6�Q"p�`rFTڱ_;∆�FzNY`1ѽNu^^:B[Bː\JKHa1W5kp mQ6 CSiL�ƎҬN8=@R "OHwn}򤟯gn_:xJ>`Cw>m E8�B{Xs&tVOƯ|::i!_\#;4g\o #ewFU,Dso}T. xt7 _u,{K!9ZļWJaz*ۣ|#1\nn5qXNQomOk&H6X KH*6Ǡp!;(w$ iᎾCA]V:fN-s;)v9wY;Dr]%׊[i^W¼U�i蚕O+k6׵ 3O80z{¸᢮(CJn7Q7F2MdaU)XJr{�Eg c A@(SM(uz�H)2)J'�$ M2R*"Rֱ|A*zpsO+uF r{f[Қ50<z-Wcv ֬+9# @a)wM/O]6ٲRݭ;sɴg\OZVy�`JӘoG�ƠPA@X?~]q~G7Vli"d #X3.* "{u*p|2SxϞz)g�pwWVCʥey4ɡ -hH05ȡB v ƅQۆzZqn[Af%})o&:'hk>w KtF^ 6"*U\.[/x ]qNEʨ ^Y?ǻ8Dr"!�$�˚ңT`A ˲`/'^9WqKoԂ޶0Tf3+?t35c欟mQ5_R{fr'"KSh(- -{^<{*K@ "99 !+b! �"33"e}E߀+P9G@ /'܉)\:[7all Aa0*$0P3F2m1 ]*XpE?# Y7*d�* 6"UED?=tF&G`o 2/B4GN K(XV r"+p$%ҖgNLItD\ n^k64c}SHc!غfX N@޵h ӳ?鬝ƹJ,W,f wl}KN_*@T1'`o 2/\0*M($) B 'M#芩j]3n܆뚱&n'dZkY-r\C{ַ W TN8<TB`v?AM^[ʷ?⟿RB4] ZlMT"c-R;a!A`\A+ )0[qر WkB[XtT37DPXoJxg8 CH\>Z ΢2-_D-nSGf!Jt~M2`?`A*L(92H"0EEHQ�)0YQ<k|5EJ *XC@YO�tSbۺ +W~nE }:C6#H=k`7>RzҜ\A>2<r)( d7B7 qW z!�D.DU.Ɇp2B9t*v8$E;l eywJMOYCR1��dAu p(ie %$�6gS {ac/L.*-s ̡+^ q!MM*�9w#cWQ4Dk,5m+'RpN; � R-=[JYNw(쳼_e*0CX3' ��i=*M=WT{a)A xc$ ӪVZ"{m.,}�;# [?g�?"*%SiݐGPã5&E ׈#G�g �0 @9vꄕN(BeBe 4 ttEYw @JΖ:W^ho%o4 - (/*1%K/&%}V1/&#`Bm�RL YC睉�%eX auk-aXR.bh܃3©D'"PA(Ou=>] Afӆ9w4W2aV<)-c<vLQ@xc8d}{ m%{PH Y:pgIf<s[#p ʕ;(< ?qX)慅2� @8?܇ 'K r�0aI;=iҋ`!KQT^~iy9gWE\9t4Xf e�4Nsi04 S^׏P|6�d ܯ(_WI]*_r a6 P�8rLOHΫV!%,^2ƒx0:6 :Y�#3Nf0׻ !1'xJc|;ԡ6r<H�BLGo({D3d֜wVb p#xyiLd[a|8Ñ4R&xE}$Ay\s/_tHLLpfXT"�}oMV06qp"ϓH KYBW9tZO Д-YpFxghtStn~/'WYyj} 픻T)*#Dr|� tc��80)a�5&؁?E:$g!G[8gK��=,0Jr9WA9\Np؛Hb?@<�G)޶ ٳ4x>Pc$t N%$%%/AS L4\Ӊ0*-0F88svOਸ& ݕ@%{Y$2L_M a*~ԾݤhjeC!锉#^g P8C2qtprQ;+TFHc0;hW0W}�9ɡa\IM�` J^y�),7SXH>8s|onXPᝡ aRTtwc�u=!߸d,Kw۽Kf\R?23c˃˃sF3HD4pz,cl: exk({".FР3l`g+�sAs~(E6r0‰Y+a&㕃qx`|Ґ-- MAe@F"",<3`ʉ_m}/'L3�{�BLj^1=CPH[oxZJaZC`ND(vDVYcz F81ABe�#8߳)Cu*gH 8Mkbˣ2 >cMXv�o e:%/ O}02x4 #AR g+俭L :=Eu! x.u  c?'TjBc ~A@mpfCqT`Ҟeev\%7pjҪ_|Yt"j1�œG݁eA$ #is%Xss&7:xp1FfbhtojhY%5oXA,G&�3̌.1u%?1®RAH{sjnU,>/_)>4xPڄY2�ƿye3hoyj1�@XCG} 5PvVAJphRL  @Xaxu`4Rp:C R[`*kI{CQ/{Z3K!&&}?]T+D%4/V& !eXm Dnb&I_}f0 }``>$.߰ ] : #pɂ%͓^Ώ?zg C{j� vE =?yZL ׿YHWNi>8٫=0o`v!?80XP]ML F@ʰL! (!~J%~=* @�0EjγOb+ *g?H.UgX 3`J='N)MQ2/B:~]eJ퓼*a0Ed6� Y)�SG) d�6gҲ~1gM Fy{$"8NOC ( $XWUH;_5L!Tj i/1q]/f"BX`rX vؔڳj{,?W@ !\t�,,KTl) 3>iBUՂc%όh?:05al[l wS2  ^O P$�7ףf~��W S?zv@Ud�6 *}ߨYgPÞ 3F;N da!px<y':TEE� %BJoz},_U$%2CIM,$~�A{ؑU݅g)|@&�3GH .A΀ጅCiQ~VA@ 2J8eXJ EU�>ûpl|fYUY f{?E%h|c餖飏&ffc9X�tiCup#^10%PJx ?{|_=0no_4lRB0H$NL|@$5XVPÁB/]u%� p[Mg)M5zb4p-Q9a.!!cXiCl F4Lg1|({h8\ VK�I\i{3+-<Y;6!L*m헎r4 N J=ӣ`UϹT @`04x%kR0"$u ;"X) n`&{2�$wy|+T+G1;GD&0nr|'qoXL k 0,'{_Z@=�y*TWL~=9 /*4 5aL @3 i<r 1VM>tl(۶^95kݧVմrFLj82aN,+6ہ?</H>1� o H'/> RH(HIݚM{RF:׮~5K=?wVnh0Ĥ.ᅢ?}r4S VO3&&MZď;æ%K чRa$€^FX�K+ Ǧ2'ql* UQ e~N\u\mZ`: 'l/Át8zҹk-K725+>{b y,uget4|[BܰAcrVNՏ�綛fBC 4.˭nWSS)<y`-�.Y 9D=I 9BDh)CT:#xI@K\ߪ:گsĒ1g i)M뚑Lk�)|Ad, ~n)R a{.&~&EQ1~tp#I WmlE<x0gYk0ÈJjM̟ceS'Q `ICiZ493.NR ǡhVfq|L00=YVJQ]Rccש $v55#Ҽ9:+6�i_)B1h/<_{Ig`$� @F+]Wk<9GTL?2Vbim3xA׵`UpFdgb?BNI=3�{_r9 w" ? 9)ɼNf)ݣ*ܒ̝!:PQh��IDAT1⪍mh@%/s.yx_`0ҏ?Gv_2r.�HPyrΕ2 ) >EO KG8 b +x$^:>;]48'§:cawy? !`b([n p>hK7q8gco/!;o @UU 'xb 2@ϪڣA!*XZǟ?{AXuogVr?ma͌˝Ŷ ڌLgqo9sHˀw<ބLğCQxoOɩ t I843ų&Lk?� Ǡo?}ğEy\9�h߁"35J2aeӘMUu߻|.7fڻ#nI- mb?�@ @NӯOlE*�Ծ%KHWC$\:x*"yxS_%3 'yq?,'󜅏!}~=V OC/D2@ �Lff&ezT/GY0 e$$r]>(^[QHDc~?&DTkٳo8>8#HQ�e$gb Yy51r?Ч6`KW]PN}H;)g1 J;h h?11~\ {?jV5?;!qmH¬O(}~[%Gݗ/\u&~WE?lGScs0ι~t? �R8_w55ȫ\<@M=KA�R- $u=7y_@?/WRu~@Q_{#$DaϚ �pD}^:7"' H! s@%LT\;+#bi ]jPoPɉԗ?25u޿V7� !,{z-wR8VˆV?<)~28oh@g :i U9>n1A\ɹs6QZ̿,OXQdz~3G0�i믶]QRMgxל?͠qIe<Tkf~8|:4jB=1�~j�nس5oTz.b.&P58]_v,gw0OL4l_~'�y{� ïן)[ݻNFcCPd^dn/=THEEk%s H A&FG_ѽgnY #�l)@BXyyW_O͝y"[[8m("Rz"ҏW�Ȥ_5A2=�3 m7B6ؔ'�c"}<(U �HMZ'W/hvaos))<={ g_MIFg�LNCՋnΏ]WT݃ KR{\<WO~s3l?%һw�RS}rB@D F9�@ F=>7 ?ܣnh ��}@Bv FRg4s#Se.uΗ) g+ib_h> `g'& =�0!~q�S3| ;AD$0OP 8 nUP柁+ #@3�(/e#1�f�6<ON N+[o �X;P_M⟏QA E?GI4�vL 'OM([ ƹWp {\T�7-Y�~A ?#~1�d֝;U r-7EW %7.5o8(ןz�LAh\�8ɣ֩G-W_"Ma?bpp3=U1>?`~ÉN42� 6'#ᛯ;@>=K%&7%~5z&_; ~^ht�$ș!sme]ݫp XߕGYb`bzd$Ч>nx1|440+�`MM<"5wF>*Ƭ$P(q_Tuj M}|wnQwΜsjm0IL Zi(jRTjoUUDUm!Pꀹppv\|ڻ2Ӈ/ƀ^~iww~Bl~-~x �M@sO qfVfBn^>=ٷ-9vK8|y\(&�B�t^k[Z-Prpn0/iܐcobOQ(>�|p�@ uym% 5(98'@ cCo;g4$tKP��RFnǟHYhhZd7saxX7!}̾=oy.G2E9,V�|WO7U,8(%~ Ya�L8{7sPDQ&Η_WNUWh/쁌@xFv3{Zfv]H1%D)�] dRrC"q˟.,/BpǓ!o  ɞCD�0}H;.x]/^j*|D`87샿~{8WuȟK)FJN qc,GQ"Psv޿vI(/Y!.  @U.ӟ_'!t0,0+e_k?%x݊bUP1��(7߶ M뤓(P÷}={�ڽPgP(d� A6Zjm} s~̪žIP 0)ӏ9/?e�JB0 D(W5~h뼼z$#_Sw2n뾿;_^8$TOI,d� bPFa˚47}'dc@(ӭnu% 2F F�bzEfWPVY' 5X Wwlu}mo*͝~z5(�d�A Y#`_3F˯D]%\t30Uwg/|3DYyU.sWYmj�EOX!0!cG H${/C%."f `h#9(0Rm;-?xԂHRQM!><{Ϋޥ#.?*'z^0ԧq#B5f/uk/4^Vݰ Uƕ]gώ[\8)BeP= 2'GXPTD�DX(b-[Y Pp: :)qvq)91 o J6TO� ӇB�b,^7/|k_qKTjd`��257txmgn5T/}=/ؼ@0?p@' #K@yCFv7ʍ_gU+q̠XkpC\r$7Ί rjށg@A0言C BPf2V5YWnhf7ϱ5K(\;Ďq%tTa@N ˁq3w~6-z]ązc&3 ,�1cK5~:Vn[[\LKX`c`jϊ-%@ouU"=rbF$׆;{z3 `@Ԧ`Aڜč[T]*W5kXͪUbYKհXyB0LKR(2LYz&*H~8W=V\J'ɱq9vw@ܹ!@oyM$doхZm׽<~ tt AGW±+de5UԲDm5+ZHbVVQ+gF(PĄa`"9p(6 )8ұpI95:蘜'nݓScͤL 'A'Fz`N ^`X'B0lц0#̰"-2+7C<M0f�Ae@x6\;-'ǧNCii=ŬCज़<LIy@~0zYks`=vP y @a+l [ 5W3L&A d�\?k5AAAAAAAAAAAD~Pw����IENDB`��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/Telegram/Images.xcassets/Icon.iconset/icon_256x256@2x.png������������������0000664�0000000�0000000�00000167707�13262451251�0027407�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������PNG  ��� IHDR���������x��� pHYs��%��%IR$��;tiTXtXML:com.adobe.xmp�����<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.6-c138 79.159824, 2016/09/14-01:09:01 "> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <rdf:Description rdf:about="" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/" xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#" xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#" xmlns:xmp="http://ns.adobe.com/xap/1.0/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/" xmlns:tiff="http://ns.adobe.com/tiff/1.0/" xmlns:exif="http://ns.adobe.com/exif/1.0/"> <xmpMM:OriginalDocumentID>xmp.did:1ACBF4C4D24FE4119247E59A0A0429A0</xmpMM:OriginalDocumentID> <xmpMM:DocumentID>adobe:docid:photoshop:e8fe49e2-0638-117a-8f65-8cab695dcc76</xmpMM:DocumentID> <xmpMM:InstanceID>xmp.iid:085ff37e-a1ac-4208-a119-0498a9c35b14</xmpMM:InstanceID> <xmpMM:DerivedFrom rdf:parseType="Resource"> <stRef:instanceID>xmp.iid:1BCBF4C4D24FE4119247E59A0A0429A0</stRef:instanceID> <stRef:documentID>xmp.did:1ACBF4C4D24FE4119247E59A0A0429A0</stRef:documentID> </xmpMM:DerivedFrom> <xmpMM:History> <rdf:Seq> <rdf:li rdf:parseType="Resource"> <stEvt:action>saved</stEvt:action> <stEvt:instanceID>xmp.iid:013ff985-91ab-474d-807d-b60bda015c0b</stEvt:instanceID> <stEvt:when>2016-12-19T19:15:42+03:00</stEvt:when> <stEvt:softwareAgent>Adobe Photoshop CC 2017 (Macintosh)</stEvt:softwareAgent> <stEvt:changed>/</stEvt:changed> </rdf:li> <rdf:li rdf:parseType="Resource"> <stEvt:action>saved</stEvt:action> <stEvt:instanceID>xmp.iid:085ff37e-a1ac-4208-a119-0498a9c35b14</stEvt:instanceID> <stEvt:when>2016-12-19T19:18:32+03:00</stEvt:when> <stEvt:softwareAgent>Adobe Photoshop CC 2017 (Macintosh)</stEvt:softwareAgent> <stEvt:changed>/</stEvt:changed> </rdf:li> </rdf:Seq> </xmpMM:History> <xmp:CreatorTool>Adobe Photoshop CC 2017 (Macintosh)</xmp:CreatorTool> <xmp:CreateDate>2016-12-19T18:26:51+03:00</xmp:CreateDate> <xmp:ModifyDate>2016-12-19T19:18:32+03:00</xmp:ModifyDate> <xmp:MetadataDate>2016-12-19T19:18:32+03:00</xmp:MetadataDate> <dc:format>image/png</dc:format> <photoshop:ColorMode>3</photoshop:ColorMode> <tiff:Orientation>1</tiff:Orientation> <tiff:XResolution>1440000/10000</tiff:XResolution> <tiff:YResolution>1440000/10000</tiff:YResolution> <tiff:ResolutionUnit>2</tiff:ResolutionUnit> <exif:ColorSpace>65535</exif:ColorSpace> <exif:PixelXDimension>512</exif:PixelXDimension> <exif:PixelYDimension>512</exif:PixelYDimension> </rdf:Description> </rdf:RDF> </x:xmpmeta> <?xpacket end="w"?>}K ��� cHRM��z%��������u0��`��:��o_F��IDATx}w$WuyUU ;3gwViPN(D913p"c36I`!!BB(ksyrT]鞮sװU{߽2@ ,.p:@ � @@  �@ $�@ � @@  �@ $�@ � @@  �@ $�@ � @@  �@ $�@ � aQBeё%fPjn/=)dUUދy=U$Œ 3"xV'[ꅸb\cL��`-?V(@)$\@I(R:u:S*   �aёdͿ!Xx8f$ι]od΄h筌)緄M0#B(<#`,0̻5q0S_)ۀrPʄ2Qv.ǒ$с1䨒rDrC[weA)SNNZd"* |/Ns]T@Xd_U!p,0]\pR}j&Ls\Zn%eࢋkFH, sM7��H\ThS*1 q0�|b_RId&i;(;\OfS'x8vBIW'Ar,5I0 &a�0d?AC10K{MWx婧3Y{vw􋺙Z4[kjz( c AOsW0g|B80qR#7;vs8<n6u5S{6=tbTnZ2v-@@�PSŸBkL;g_iY-]wjx)Bh魝x8cC:8{xq,!M0>368p;ܘM'‚D҅ ��Œ>O(.�01F֞4~\|˙Zlm9EK'8S㑼PnC87́=קM;{no}f_d HH�� ~Y硈!bm~ֲ o>Xsk=CZ�i>ٻtDvQ``e#'ٔMlI{I�$�|'|CѰdyGE7Z}Y%],Tk[i,ӏN0GSnjd[zƧһx:spS<fFg 2 1@ @Ȣ| @2MZ^}o\kDo\ι̙>/Ɠt|CA7 3Ɍڙ޷摝 Ĥs�$�MH_�D<ҵ3.ۮ4Zo[qy|׏ `|B8cC#nrM>9'd S-CL Pz@�I�-?[οbkSŭvt7҉Cν-@ڣ}Wm~e.ct1! ��!}YhiKon~ z3BZO/�t0# '94bض?6=LgJ � @/GA&=g,ܶo5ry7m](Bʈp <4ikcOG3owf:]B Ȃ$�H�]otjW_FOpڱF9�F6\F'}_rvK+D!uH�� s_�_^x ^s޾L:D\xf e?gܟ:ޠ%N" � (Os='ν-'+0,>a3Mglo;o?{:9-IY �a>WU/oy7v\C̙Pҡ=}HS4 ɭ#ѧ*-3GY �h|o! _/kkg(FR>^ceͣ~30;ԏ Œ�)V �} 5:Wu_;o|kkκkzX(Ok=һ}9ЏV:�h{@@_揭Λ^nѱeN4+` |GgCL�EO6 [/UmƗh|ea;Bß\7G<8gѻ~iok � ,�Gⱎk_~M׆75rʹg\ {` >?Lzr?��iHOkhUuEߨ\I;Gi~0|_7:>}?շ3uKMB@Ј(W�cՒ[^7<W9(ۢx�~#O_ ~62�ywuwtww/Vʱi_$qPʵɭ|y{W|�'B4XkÛ^qat(OlJ�<\7ʗGS5@�._kl]sÛktpb~r>FP2NߟF_` $�H�1E$ z^yʕ2gBXL}/?>H�a#~fzH59fi0gWG2'G}?{9>�2�$�s|OAʶnt+3.y-(;GG@+Cbp#g{YGBA�$�ϼe/?o?cq3i!j(r wL1@�a?B{:eɵ/}Ļ]3M>B]nٿ? ?rףpo P}� �� nCZ׭o~ݗH3CzX0yv_~䝟\zSm !�� @kba݅^q+@+Apӣ~?8O~M @$�H�D婝+_j%gB["sY:JB_BGď>C׵[ $�H�Ɖ7dumx uu_"siG 49]_:y?ٿ[� �¤_oθUЇZ.g2J|3HH޻Ͻw'ʱ\( � ~/բfY.vHm~.L- � Es�Y=sg] ~ld6uӉ;>=i_T@@ӓ]nDu֏H[i:B¢DCjc)@@G.vegv??;㢗J3 %&� <43G?޿_@$�H�4%-_r韼c"X+s:Jb] |KGw[hz3)@@s O=+_я^|[e.C{[(1='B|� pp:oy+_m]g!Ph <~cObg[dB@o]_Ai!'AD=yx@Ş @/~Y|2}歮}!b�?G윃A @_]yp{ß~i�p!Ƈ>ٿ)o%L[$�H�%/qݫ> siF~a6с-Gab�m � QD瞵=(O l 0G~_S#<H��X�7yo{gEe%s 8FGGv vtACH��) �i7sTO WD=~xwr#( @ ë~G$'5F2=vGx_O.�:& iK�a9Ϲu+%ZG$B7V}#_(wP]� �FS oz7D8FZI @ CD}o6wV@B�av``\~w|\v B=@D0ϾOWH�fL~<7w$"Io?wm8IPq �B@&pw}泉s|IuD sCc? &4�!�# [=K\K~1!s푯KPOŁ$�Ӡ?quo|SΞ(#zW"ol3IP&�tO:m_FdstBj "/i?q(@P/Wxڨҟ@ 42x$쾍w;es$H�c͏pVco]wFYfXI@h|=O͙}k ?~'u۟K Hj#M$B}Oߘnx&H`Q?c\5w$yFNjdOml` x_3?.Uwctf0p#7<poyCrӃ H�,B7"~׾]nz@X�=ie;0~G"E$n/~?}?@Xt n�} h; x[_l˥K"\5p_|$H�4;z>r-w3Da�rl/�8Қ?h_?@ �wڮ�rLx$�jEFX[/DO "t͟^z�,5 )wgL3'گj@(p!(<~ozur %hȟ y+?@ T 8;fW7?Kh� �3_rëGO Dź7vs3�$H�4L/m_ͯ{' k=m?/>Р "Pw䟏5�7[;o{˿Y'3 u"=箄W玸pN-{_zk2 CT?@q<Y}{GQ`] |o_[Wo>,ȟ@ f ܽωhK`ͥL� ?~';~wSTPmJtta.D@6׼]w&4^tYE?2v%}rittadh%r3c'~CI)0 @)#Vұ_HWD0Pp3ItlxGވ �Q.H�$-~#F>Yckd.KG@ M(H3-轟jN$�H�Ԅ5�q.W{+d&E (@o PMz|%vfNh8_`|?}n6ɨן@ j a8+_jvsSR qwk dhۗ_ݟ&(F��En瞷y:1H'4ӈ~ɟz~qw $@�sN�Bu/~G⫕c!Mb7P@ܨ"O Mwl@ уxKƞaͶ4Mj�j/�<)Uo%.@O Do]b abpu�(u/!':vcw~"bb� #<kBB]53z+o Y�AB Q E=vէDNP+87+?EP x?Bz˿]dYD, eWͧbg^~&.@c^_pdK  "_ՊVnˆ�i]YZh)`3G) o4\XG[�G QD[OHȟ@ Bu| o|@L�sW_d7Ӿ?@ 40Mm+>iG'>׽?d.!?n(M _g4V�m}䟷E{}ǕcB@2vW#W@]$� 4u޾tr,bD@7^po,fPhkw]H\r\%UvV,VP.ZE/2e'&&\u`|gk`>יfhO'z"sBYϽe7>4U0wWK_򗯏# � q_+0�- ȟEOh}LK@ D$_C) X e�f/�C_"k~z ÄK,&U3/4Ӡ?@ ,2(JǴe((Db(Jמs?uEK?@ ,VH_}@Yw�f,�ԿW_D;?@ ,r گ{^s Ab�^|K˥NftbtֲӍH_�hw{ۗu.}|T9N@��"rvɟM͑A� ܮ;e=* Ap,: 9x lNl"6Hx@ Gđ%9�g3З6D@=-~57wڮyٟDϾEO !6R/6imfdf�{;km:^)@295mA`3f� ܮ=ll@ TNXt=s9E6l6ES=~\ј_@ �k]>́3q gl^mY@ U5hO=0Tm$�zxY_ !47­K_WgB4UG@3 �4z8@Q@ e?ڗ\k,@SM lP]/{̙taY�Hw{h fy(^򼷿OJ[(j#,Z&Bwc5/0ͼ/@CL3d� M^y{k3@ �ʶYoZD!0>OD[Z:n{_("@ ̍swyy_~% i /6Vv1a.!mKn}˟>r&|ϼѷ�?�ږv.( 0Y�ǂ|y7ᥘ@,�ދo|5g+Ǧ3@ s/l7Z[RLԟ5l-@# ZkWW ~('|el]kju/j,@ ׼H_RO `�HJ Rj7Lk]]\ԐYF�KGgy;FҶH! Ɓ#q$\R /�\ҵYƠpZ{#3TO RTBgX`Ú(ZmF5C֕8vs£3,Fr."`t D�k lo妆F @CI4+y=39�#)K\lF4$?o: !67 V7?|rC9|o(ڗDU X0H'w y)%P3/h  o~Wq): B]G9W�4\2zlY-aHrdBWqg�<'u։^$s � I�0Ǎp׿E:6D@ cO/bCO׭;�lW_0]ׯ!w8.t}B_z0|!H@翛ׄמ}eL$A �\s pzem g- \93U�…xyKϏAυ밶^_9 �θoz훠@ ,4)~`yTU+'˗GКO^.omɴn^w 7\1/~7I�=95�(eΏvi?@~AtY z~Uk:~!rE 58 8 ڮ{k}w ȏ`Du|C!P@r?9imnZ-kb8#㟿pZm�,lD+9sRE�hu oz-E5\2zx0"gF*QC*ePCBD^g7c׻hPh]XRK [ R ʕQlXcXۢqQ0mQ#A .\+ŷ\__,-�DD{WZ&Y̥a]cذ6t9\WO/FLT{֔' ޶tm˕/zO?,@]Ի�`i7蝫ϔft0Wq4x4*pn[+#XѠGY:ZڗJCʢbޯ@2i4=p#�@qꗼa6K*)VDp8b],IH #$"Hkd'Fhi/鲱~[_%@OF_soEg@1\s4j(n]ǹa,?mtpx4 MXQ� v( �0r^6D*n�,�x�P׼̈D*1Wjذ֫bE:NWX ,4X$"$<s}Bg~&wd^yK`#�wXIˤ3@ =1elXÍkbXj@UDE $RGGs�`�NJ"0F[5U�.x?ciG@ |뵶e=G D#!b1ܶ.ˆT4#Cpcc&R;>AΦDږxJD@턧E_<}[$^�1=)\,PmH%aVqkO׬bY[rlh\th8 W͌qfjdѶ�}yn8=[9 � rgמsr@ ,HG4s;øeM 76`޸]G6mM,LG"!$na` pLD: P3q!='y )(�x�pW<$u |o ʘc'E78gKv?F` %Ol+5<Hp<Z:Vs]_vcb@NmplI›_(sY@)l%P8][*"znu :KOH2p^`URǺv[@h:e\x C~;>ݔz�2v֞'sTO ,uT*BH0_b lXÙK{Nˁ3 c8<bB0>j� sd:yP/|Я#(iQ� osr"}ԗE5\*%pZdŻ.pb4Q'�I&m xq<$S8B ;4s~`뢎M�ֵ,6(;Gg싥ߌ'ap\,[&pcw k:cQBɁ#d &&HS< ;:Hk;"0oJVG[7l߸%mz�7~Mkmz@hh_t7yMqo^gaq*�C#Yd]"jZW uFA"`ބC[ngl+b@��7J-+BsFB3aCO[+=yJق%2Ojz'〔0Gni sݵaXwIdegf>4&I�L?;V~ !N-#ذ&pJkLޫ L[H#@jBpPtVHP"?2[y�y?"�&sEkJ?ȐkaMcmM/m{%��`?39pD\k�+7~/);D7�e� B�1 ŒdYA 4Ԃ*}[bܖ\*eQ (P?#pbİi309YB7)=ø�\FW#Oz�h˖N:iYEB]GU*g?,㖵qܲ&3Ca �"H06PD|I_MNܟKJAӯ5, Eιև6[ku~LϻRkU F<+b:]í= \<֐RMn˻88A֖R+)(O+R쀹Nxr4d�,vB woĞ9Z1I`RiJ80"N'?v: ( = 0u/]sQx{J`uϸj2!8CO&p߳8,Wt\Jo2YhBfAo;eaI̅іhˮyj#@%ob]}٩$�:6 RhiE[+X=(Zb@vphĄ|bH\DSlJF,Nফ~oX]5L\Y4@Xhߑ 9_6:g/c8> 9 ?@)P<A?lk#G(0AiF<zWRO ,+*nQܾ. Fbpϋ ` CYdl !,rP[PgRBv.>KFALXZ@�@xݹ=ڒA:t5x8nVt�I}u#D,�~ْߝ\ `؀y5]PRbG/�ȧ?�7r%hKTf{@up}W�\ߖWymqVGag++<T�WgqOXI&~kb!�i%%xAhRs~sy(D!,6�.W^\'y0-jje K+"h=Eг/-%RY h{E(c.-�?+ eA'L@5u!ZNy PY�8G;C_a_UziM=:4`:1/=pBZ8eYK]rsZ. `xUJOaH?d,4ذc0b-$R� ,Cg_c�w1e�Xa#km]kH�s>sНq8nurr(&8i81P2"XH6Zc#Ñ,Frʏe3$V q`�D@P `[ݥC|)氀u� )�"@ϼ!h/0cWzm֯fu ,>L]pΦ)5QQÁ,7R'6:u`c pD@ .]h1,`B �3E] 3#}w CxzXBH0Bfa#Lʼn4F29/S.勵jIQnE,LG!y?R D/ǡ\vD@ tv{9cy [�L$::%+/ҙA $~GJ:^= \<_Cx8cJw4#8,Iyg Xݙ@gKɟ1oLȷu,AIvz z4$*2ƌ] ^Ll, P>ZKRIBh?_9"zaM kZtpƐs$Rg?LHV٨cX4%PF@v8�zy`,IT3%3)hn. `=ڋ؝N(YG -�lEF#BU nu FLȹ 9G'/Z 'G3uUYAXە@k~3qlTQwT,e{Rዀt e:ږ'Z::Y,t�Pk/TEBAnu \*e1 ,wqGNja4mpig"t z]?�hHȀM]?z!Y I B(+zUg=ra,6B ��<X"8OI!JXѾ 0`8#[{'B0 l[Hc&>J!bhY@$=ƀCdl^1GY;V3d"2%:;W_�"�=Ek Pa?>dz]qMwk#hQA_7Iz>Yˁ[;JTHDt,M ,+� eaKP",`e<M7s� SB9מ!a�R�ڒ2=ĩ}Kziq(n_ kX0}A_9i8>p*(ϓ[ڥ 肏;�}1āLdTќ?O~'0$ !hKPt~,@B�1.‘xHHʓqV7u <c\=APAoɑ r;!%0v!8Nrq`8�':f� Bag�HttOxufP8FJi!j�3Xh-SQ4ЬC*r\ tD4ܴsjUK\ẖ[9LJ2 }D� Vwļv ? )ޗ18,�&+�h0V̖?BuciKym$-z=KY/1=qܼ& !£@#U,1cYda20+�J),ob�%Xkc`iGOK[-<ֱPPa )X~y3f�8B>D5&): M{sfs(ZCy[V"``Hem iZ?n(*pIhc-("j ɧ&dR=mlJ<$2_1Vεsq9L7Zfi�h-gq~e]!ϡu \:Uqgۛ$~p8 cif*�5)UCwG3QDt_r*>wٔY2,0 "JGHյsIԸ7�uze(!4 [ *v7M FDz% t/{o>,8u[vƱ$Qlۈǝ3``S eXiWdfbǨ 98Htъb%%x|IGI=qԸ`!�ZX 2�"4;M* X^sWD),33Xɑ f־b}t&V־�0j88b ^4't3FJ)w']^�mKxJ�-T Jk/hltKGpkO7ME`]*2�S<C4L{uv%[6o!Ñ,Fr.9R) Xgenxtl8E_�cũ kȧ6d O֨43 YqÚ8n[uҵ5g Yʼn 9Dyu&1&Y6os}^i%<a0յl />顸s)4@>�N(N"_;RaIX՞-իXӡ_G=3 }FӰ|k_K+a=]oe8  -!)ׁcBa0DәΥQ:Z �K1Od�D҇߳*5tq8nYim!T?5>e3)Ek<k_Wmc3z+1�G?~r]9"!/ ;oYvOjt#n<Z\)_Iٿ;[{ZprgvKᡳvfQzG3uz{<5 #fm8bp(_ BM+)\8 X"@XXyک|"PT�ȸp � Aʯ]0sWq\:Uq (HY⟛(#T R͔ߕ`ոos%i 'gTP~ȿT<6}r@I89;@f,]竝��9K:J 8ۻ7xKBh4g> 8Hf00fB* ~(51Mp m=Ie JUi7}@_M1Pck hb8_ xrjRP"@e<u BL^*aV(L}Lb\ `yTիbIX^ :pb8 }JJa}\i8} (�RSDrט dRP9 5J׆L7b2GQmZnp�5E[=[^:+ kXb3o@O \m|sDHC܌ }:lkߠ]`px}V`&Bȿ+!ƫ.Ħy~wl+R`zx5c,dmZ = }Y*o$0k> 3k3pӚ6M0bz?й ,Їaê:ᆷ  t0&n?~{u}E`G" pm:&fti]kZzь[�L#uTE+&aóp4ʞn]ׂ+VDS= }N1i>K*Mptw 5opA,G8졦O7# \+ h"W "z]mݵ M ($VQ�aįH+9.^ņ ܓ@O3oK(U*,F29'ğ'CXG"l,*�3bt �Y %}~)rV0\"@)@hGPN>h z%_ Iwg7cCO/"sX“s鼪EtCʴqb(igú5KހC0L#mu 79 c_ݯl2RsP~Uϧ'UbM` Ѵx8%5r~* T)~[*8RaID QܶW-BEuz2q?AO] ti{3QFSɿȟ j#p_J7H59~tF V6M �+֥t1d|?1Ɔu-im`gQ?C2kP))2• -k&`hɟ3p`%yVfaAI=+A⓯A_ 8J<*5u}9� z�%h3Mj(P-�1^.�&_:aqlXׂ}[^UKjێDH,}k_)o/t 4@O2/WxDW?/Z*|?W3FqV@ MPl]q3d-o{XU ܺvDZ*]d˻D� s89A֚O9Hojkߠؘ=>9EzZ[cW* !օ:^\C�hzLy+zRES�% q& pKO nI`0,., qvqr8TG "X$rC�:yW(]wWV<<Й�$XXQ4I|26]4Ϲqtqۺ.]E{Dvl)cA8Ld }zR PPX$mQ(("�7F֑ Qɫ9'$䟇tƭ P <1=̽Oo??x =.{P~*._ǭ;Չ[^lytDk_6pcϬU1rI]mEcb@vw0;18 �lɿ5W~uJO%P҅t6Y8 ǖbj+`mLc4/!)Cim଎&tV,{Jd}Y؎t3`uGK!H?�Cvg�To*Cj_]Vc%ĵ>~ŗU�Ve" ʿt: B荥,zjP-�0#neޜğO+�c:Zí=-lEa n~>:4d`Hgm 78tGxƥ?iD2aA/AtE L:RU6nT/@8>)7j �3x8Q&%> UqlX kXOˑ-o*e00Լ _oKX_t־A idl OMejfҏab+<xF%yWM$\dž rY�}pѾi!M9 NW*Ni3p6m9]a:X89A:7?}? L Z־dưw(- inL?c{L!,?q+Vi)UqP v:u-kgRBmP̈Ch4 "4`佮[zb}] \{ {>i r2> T QCÚºX=˜#w03 �f; M_RpOzJVp�lg� \�C(0 Tq(ni= k5sgR>Nd`ZG7)63C#;�ݟy`l5-,OUPg"իU3 I3 Ǖz)m OE NbهoZp(Z oz}t3# -0_8 !kN82jNT�UWGFXnS /&꒏ cLWM4 @Ah|3C+}]׭I`ULMk$d`;r^ }ʑD;d|12H9cyw|x#j^ZGW4 q�MKHL�4���-Yr@ Mlbm:g8ͳГD4mO`,kgj)Z"X$Z@^`�DBV`}IAM Tʐ 6Li_Uq�!x�$%+"؞ !@Ε1~ ݺK_2ΈWH但]%7bo, ו5#~Z}Yg E־^+g xu �o=Hf>D%rJawJ!3�lBgDV~0Ч8.\ŭZqZO̳CF IZLЧf䯼hqE{ K[#PJ} R]i�0I# la13{(Yg"@h$b~,6m? J'xhY-~Az€ЀQo7Xr }J?cʎ:־doᬍC#&4cevԞ$�䯦!3}a`: d%]Y%?v<P5s=Y�00&#ED-yi1�DG}Cl }&C*olb!rB,8 f̹`L;�t 7P_)1J/p&@14=׆(� "B/} *8RaYL kxxy `mEHéTZ_Aݝq$"lbΰk  yUTd|oT% @UNWC*o+|Ox)S"[5qTS"@pR0/W*#%!<6<@{+ 's g{> '&+YId3R :( w0~5/GAɿ9JJ-vb `"e�883V)_Khõqt5Xf" 2GШ]r{ ]orW>6]|J<cT~UYcPo+U2k*?uJ._��CEڠ c([Jz: ma +\V)opf7-2JxXǚ8Y!zS6d<2d6݄U9f A&4GlSDG%v䧧~%/D@;*ug#8^yV;^N튀3+& 0c:89A2k-Hԟ'֨84A־ssi$mist{WJ~LpWi-}ϊY?&�&k\PXP7K}P#W.Ǖ�i1n3XcY8Z0ϓx:}^�0L#�| W+mw&GP`6o /[ߎ^ K2C*cH> BgK+G�i/s @KF Z24B)ﯤSȵq2�s$wZUk4w f }J^J-)|@2kd9?zaәF*}U]J ĭDgǖaJ)0oj,�e�f�[*xߥLȿMYI#k57yЊw^!p"mh`%81xڿ$iVzLPjL2?ݶG@n-Bn.O0^(Q`xAQ`F\+Wqj[9ȿ٢~7I0)E+&k4R 01n3`fH+9W"V~.½wnɿLi_J*Ͽy�]d�3; \24 ־,,g\�Vu}sy>a@i["'P C_eo~v;Ʀ1Kמ0(H@2nr�U/R!8# '?_Â#lAHcY e3=kUqNsn·-{ =Y+ 0UKЫԣ*$*r} ZT" {;vV\u2t1H7Eh+"ϓ twd[+Ҷ]iZ=Ri Y.JfAH݆*TUcURY*[-M!�Op钟MǠqyoO4;E\XCR=k߈A k?Tȿ^ԔQ�(Ij�sZQ[%q"mce€CJ~cY e**XHǚN]Ÿ�hI|rͅ*GzyLIWOolVTx$4UL#,xk|/oJ9TP}9xx,i?;A&-pk_)oXN־ #î42D,, RǨ !V>1}9B>V� OUDU)5 PJ$c^#oC"~d #-bQpl0}Id,[?pB[45 h/H.v Ko iYȿֿӿ<zTY2S&5� JI. {31%H2e[GR9d`}zTH}V(!e9Y:WlBAv`?PFɿRo((i5�RR)i"˕N┶0.\�3 FXZRj`YgKž3GG8^ĢJPX@p1YR)ڞ2{*M<Ϊ4ೠ1|*(90UZF.8V FsSꢮuπ1Nb$C),k/, ?zײ"VȺ5J'*.beʯ3g,B.}c%eKqSKg)NJ5Q.�^Jp.#q2_3 nh@23}IJίXҶ($/ rnqGrKT?X`V7.YPmڟU=6 JeU<Af˜_P%RRICXȺ྽Cxde b: a:.̯bf 510['\G{"Du$ Ӷiy9Jȿ6n1+&�HG9V. J pƲ W&pCO;݂gȑE[V9 Ysv]"Vw5Jᨧsi,kc`9W B1qJ*W_K*X@�Du% drܻ{._݊׵-X7s%$Gq$L &sJu T XG<:g?pD 2l Z{䧨 Sj$?0?"U%L.=qU6*xyk`;'Sa.Rp.4Ę 6~?ُU-!\7 t odBpY˭;CR5twڷ^џFv6_*D3;g? XL8sI4d:}S>BLД&T8{mn 5m~].\Ƌ¨:ce~o ,:7R!GHOm}v%v\d:6izr5l1(+c;'ő��\;E�3@1T LIyb@R)5wqܱ$z#--⥈-W#-| ézGmZ=sg훈X.xQOj3�hG_ %˩ġ]8g> U3\b(Y^#jF� >>.f:ix>�>B�xnZ)2�` v'qiu=m8gi -! Tŕ(MEHyC/ )Zc!ApO P_c{{;`_NW2a,سKFEo6TnJ !@CK�\9֘w3PLR�]讄!<ٟƖ)|Xu=뻢HT]v7b3?[FThOڗ.8v \MK�|^jI}ςjl=Rq? `Pe�&8찒7*V 0.̀IPRaKo ϜH]1\ӆk׵ↀ*8�]ޑ,RY[&k%:[X$` ;R8 1s&"IT\7Wq*榆6�ވ=!D q%:6ǏOù⸮g YۊuK"+auDzqUC8+Q,mxD !8KM�\4?�pSʵIڐ`ٴ"1ҥՠHŀPP\S\34r$pxA{D+~];Zӊmat#�/YdL"`:Z^&q4C*`G+�RJs <*8T~\ tWJD5(|\ g݅Ȅ؞(U鹵<Ræ$c0`R+_ dlƒ5pJO \݊-aսqЧ, '־ +D6ڗ. GFLl'lAHV ?Qfu7960<)NZm@1f)rZ h  .pd}CX3p\[C+t 0)E3%o4R Cv"gȎ%!_9'Ϟ/�)/([g�SVfp$3�8pepw㗻<[:".4I[@Ek:HocIj*?>t{UU@Њ& {@ Y LjNN\v6�(>tg@A3L �J"%['BbMiūZY{ڼH2 ,ocG!M`uWFߠ`HlKA`q `0-S2PY]䯪|\G_s9&�ps`-D9sMZZ0�J"1d|wnŚְgEӎ -G͹`�Xg4d4RVw4HȿQ!З(M�Μ+,ɿ|_%Τog&xMan&iڇTZ �&(79OKA A@ A(:pxĞq^EpV\׳.-͙1g R) e10f7i<J!ֱ#>ڗЀ@pHc̒L�N俠{`edz4?@ηE` @VR̙�})&%tb@ap;R +m8{i qb1oIr7ElC/ZVu9 M�3K!cKDCd�D俘?m�, dJq\& ``R)08Tr2y8z=Kӷ"v 1Ra0P2 WrHD+ۣ�"Lyn%S2 boxLU_"i4Ϊ~<Aڅ`~i- HNL2P4Nf@H Cq�JW0+%6Lc'pR.vQڊ8L }#Yd-Nj%-a,k|BÁ!7�x8ݪ*ިP%jL~`߽ȿ„uv4��;қfj@q�1ySV\"ccx(oEWmúb:HXXéğj5 (E&&dї.o�4=)d$FA*rG R�TV2'Tm5 (_ p)^s]<zxƒ3pmw GMӐ}S m1t$A;RH ]�MZMjV)<.TTlU^u7Vҁco; k(�;c;Ѫ@(rakp5RPB#bd,?v/<4)/+=DM W*lK $+O՜ӈb1s<g#5,v� (J[G.@(x>\w0&bS_G4)g 舡5:ן4!mySݪ8II~ӓU/O- Iּ@M�x Z>ȓ� qEWbJ!5JMlxR)+3C0` m$ dJl#Lɟcg`5'KOy]j p:[6-Ѳ7Wqa3  2ircgc9 6QPvf}3l;/?Acbwl('*P(QKqZ՝AR– xaCCwWQCKM Vȿz%�Ma%LA<o[k erpLey8BH_3mrZ Xȟ.D�hG O2uR_UW'Z9Y/TS0Ig3|ނ̜ᓇ'.jU0 iwl 0#B`砉lclJ!1( coqsAkgBlgLs}pa ʱL ?HPNAK� H! n\V�R*CX$@俈 #-屦IQ *�9fh`eOTUC#Ik߳ @t;| = U+I5`ڊaS[X$ۈ7�J.xY"2M0?)s}:-<gAeGejx 5Y-jߧP剔*tM``#[w�־ahGPp]})ت0֟S>B'䧎 {~N5��jWXo<ꦇx"Lq]C\nlx. KۢhP3bk_ (VE?7삑44 hF#xCOW͢��L\62c �ʓ` 6ykoqp2á:/�D#x{>[*1jF,3oFInjx�οPqPʱ-{ffe2Ř`w־+;bhO!%N`ޗDgV(5dɿ:JWCeLж=>�r ѱCf@ Ysq"maa: úJ3\C"bu)OŰ7 ӑ T Aj"A͐U'}|_�=G �B�䕍%sY@ЂIqguM}i\<`و.bI w?2^ !c;ޗ8))Ɗ�*(΂^,/+55 ޥklR\O_K's0kSUrۗ)ERBu'S~14il:gu- D\fT!0{ ]0=[�lz++ U(Ub[6gY~ک|OʗToO`~bN�4M`,#nrh9fr#IGs3=LJq_/WmtHat<GVrltui5g\[#};领s�j-�d‘ʱ֞'w0AjIϸ /�Ǖ8;wHJ[�ޛB)�T`/X]; ԷP]#_p>g�h @a7�C;&cNS56i&028;8^t` 0mSޞ9 @^*xX^27}AmɿiL~njx.Ebu¶qG@2=nDMf�80fh2S"ڝK+]02qfwºCCr? ~B~ @͒|sy"Rj~#BE6 6T=3sjv. �{Me40 rm�Jj匁sC}cx|1 &iK`1A 8!* `S?QXD%MAojٹ�,�7m0,P= kƱ/ӭ6�vwkc4mDZ0m , c[o3�SH`;˥|Vcg*<w}AI]s?ʌ۷a ͅRoU`B /`;bdƪF4 sփI8 C-&F~�PiQ\ W5W`%e5CWpa %�  ж-*;jka� [i !R[cHc Yۅ(\) �r/]p\MrSӓ$oQ<g/>̫>er,P P-Bαer� "̌' M}iXIP)וKc}N  3HzDziĪg�կ,rlRv`� ԎT5bؼ 40 E �9@Ɔ@�@CJm쾓0mBq1AC0K!i?@] ?/0:y#&kN %� �e[7o@_W C9mςuRTUc BpH?^#"`[_ 3C9Ǫ()9qiuJ]S�M2=B LW(}iܰut̶48me;NY\@#3 mڛ&xXKm-:Ͽ,tާU=_ U�<p>fsBA&m03�\,[;ٽ'alh2a)�36 fi97Rh�}h>;��(P=` Z;{Q�a"sĎ,t>SWͺ`8>c;o$Q}`C;13/ٞ0)drhVPE!�&�<'ڊ&f¦TXxn$&8Ҧ'wC~�pm)d%R5re v֐BcܾC}7.�"?�f~l?+!ZQ3; e QAD瘗mu�ipRyd�#,YۅXؘl` PE#6? ٖy4 nNT9eI|}5k)#'!e p %�rQ�|i:(YRɴY󴧮< 6<?C)<N F[u FL;%k8JfūF̸>U_ȿ 4RPfڶo|�g�(v=h0a`J`cozޒ i#ydJі@ua@{IMf"Ɲ翰/mܼݤJ�0kcOpD�a lO{,sL�<>:w@ڴK )lR`JMM&jD7̉b΍`j {wp*G^Tq4e }m!o^| [#^oDـ`:.&'y")Wu�ǙH־gD |ͧՓ��M\(4=mU iܗ܏`pTHMl3{N`~H&fe3lKDAt1| :XLJJvTi_;7S$pGZ;[o�`c&Yf~Jy/ƾ 2;XslO:d6manB0Π7mNkȟQ { �Փ�(8ch  @IU88ÑttwuSi< $!8brsm)$-՚(JCicUDU @!}[`|�Je:c?yΥM1afƖ6qfG;w׳+e]}'02q%ЄZ 5[Wb$\U* UjyWSx59<{xNIVU<g*?\tBsȑ֎?<?m0_УQy)�֮̔mPn38ƾ4 9:cC8b½'-2lM 8yqcKv5s +<r^"t`/,9�}p^V ̊&8͑Z�#o $3xbQK`0بc&DexM3[mU@#�K eڣ: "2�–td64Α]l:p֗DҖ~RU aUxa>ȿ1js~M��6�߫ld {Ӱ;M\Wc OǑ5sl Rޗ%p4bCv=L@:�޾9ff !kDƚ.~W,Oe�ºX&i:9ιJ[LAJN8ٟQ5<%;p(mtn"\�`aȒڟ"L0t`:wwl?޻ NsN&s? *'5c_?":~񇍨 n?&FUmV6fwy�#Ba8Nc:gg?|"fC֛hNc%J8걀~ ! T[>?�kT �9) ݾeT Hq Ѕ,ά qո p qMZDZc C0H(.392 |0-98&s e9*`bL~CYxoPsS'yhuwU\no7ιP@ 5si\ Yg瓣'�4|I f,8hZ07GG@r$6jb -'SЄT-p)i) ϛ�5I&\iPh#;o̺S| 0kpN$O�la)`S_zV[~�8Zў8>ol< ]p'%  hBAOÈ40?eK.�͠12E #�= ɳ\뷶=!:&61j:u(x- qƠq{[za.ɑߗ,9! ?xy tƝVk {S5$??T`Vuˌ @dS Q@&-ϱ+t3_yfJf��k#]; z)5EVK�.p9GH6mϷ`(c!jP-H<w ټT(?=9`}aw[� �m1 3]tR@ r} B䟯{TSBD5T m hBg-?xO.�|lMA �rOKOF��JȩdmJ)GϚ,׫YBuMTja�c4[NGGapoX.eq%@vroI|и%@(`2=ihHM/"t8'mv<'uX_TYx<vT Hɤh,JǕ R8N$sDZ7`_0ӒP֛] k_o_l39hK`jFpOb(\?_g @Qvnu׳�(82=ڗ௠n"7c8 zSU!@qD=W=#Y4 ??M6aE@rkGXpܹ('L"c%t z@&?1ZM ׳��ܓO,f(/T8RBֈ"O$ga I<!R^T? ;ƗB]sI=Tl頉5ø_*aXf`x>Y<>�st.{S\{e#px,ӗDaUy͏C#kg)  /GތH+3+A "Uj!ҏ8Boc|観-,1 e,' |AU|cA8[MCA~Jl|4:Qp?9Q2�=l�wA(?cH9 {S&Ly?c�~g�? os2uP(Ί].B ql1O3s|S^D8̍˫廬ỳ}pc7WS7��=,�Sα]Nc p})dmx}SJqռl I3خ޽ ϞO(*+&iӮr k?/<g?B fAA [N!mKTw6ȿ2�?Rf1u_n$T3 @Y[u!w$) Zn�HPIŷ#GF �EޥOG &SU%,؍ݵ FM m2-)0|)_ lƜN_ѿۻ鷏P"�Je{If`a̖ܗ΃MnSrL‚7mǦc4?6A3Vz['<!jh3ot$EU77%4[2C~�Ⱥ?c WO枹nF8FN JB8 ggaHaC2/ZqdK1Lbbi'T &02$.DM A4LNuLm� �? @Q;PpXXǕsZ|ԛ6G_~ @='p%H<kd= |u ^^?LEL!'E\ m�<z( c Uw<s[oMΏ}8=>-4uQjM({TcsdF-C]x}z4\f1YŖI3)5IpfL~q7=:bΟʡN6�P  GnKz%t�ԛ`įi\ߪr~o? ە/h+0/E .ɽӕ{\SBe GGx]h-F6 Qpv՗NL:tUh=Ϳ{pߤ_Aat:Z�{bn{C1=B#݂uw6i{�fB?ч{{漜QJy6ٴ2�h|{"-g\xY CJދY nhMQǞ]͐g(3LEѺP/d]_(�$ �rR5яU.CI79L0� !2l> A�+Wۮ?6-M[r|[~4qK�gr2% +`10Kf`a3°?km@~v �Ρ۬mnj0Dlڊaco Rɲ_)Gμ $82699`P4)| G0"�#j/ބ|`,W"-qLŖމYt 2M߿ oF�=,�ߡ,Ӥ,�a&B@ d0sJno=ͰqpW9݃i *'O LsWIdV8Yip]s>6bO@c�20 gllMMSD9l(WF.Ș�9�9e_SP�/cc`6�:\Zo6?-})|٣82}dtDc9yKq!厧qk%soJ?m3g>; PɿQ�P\ em( @ɂhTYs\ %U%38xttc qG3aJ@o L KoƿvLG"7&_u71Eܼ c `@? y2#Bg)HOxD{J�H%_ق3p߾A| \"WrfP!]?w߳ٗD<T]wof� cLRwT�`'Q~@)!.�iA*v LaZ>?.a]5^ 0pC;Ɲ# >7_8FvF�ШtƝI pܞxBWLK~ml9Q�*iR]0dm;>18Jl#LSůJgҥBP%� @To \ơ54 dr=wih n]=BgM [�4߽6V|VjP@eƲ1ynֿf�Lk\PF`Y�? 8ѱ;P*q':cxtΠBGJ*?T�V L�+>Uƍmɼl '?~N!PO3l.WUJh6"X$̯rO Lu{=}LO<nt~8#iŞlK*(nqdW9)8z>y.lzJo˧8N#&;M8нv;?Y\CxGg.O&5~6Dn\L ~G0i@.PC=@,y�IKOE2@ RN1ac@_y(f\ΌW`,~n5?Uz+2VU  H&54d-މdEtcZї&x~6&?N=F'~CwCLkfgl9tȏi!:{ O$` f姏•޾ʢŘ3 lKpֆ.m~T?NAh&v|JadӠ O~ 6t7x{~"%M8v8T6kryn�{?sO4bB4OJ!CG?18tG7Gvi~DJ_0%yl<6|#.jLq<ytigS)"E#-'0QpP#=]jr�@@@n\_ϟPVTHJ6L[b]V`hGxg}ng_~-^_+^$raK[4t d\1Xxs5/?Szݛ>w(;7 LL6NcnM|~6 Э~ONjO/0$`BC]|w c1{3s86u?~&W@1w<{;z-qiw;2 Ẇ4' c`0#_l�2o~]ٹLde39Lv:̯udLRe+(& 4]ׅ�J|_ΌWjMA rЋ%4 'G?o?yg0qK ?;{ue({[[z ߳XpRig) A,t zHRhPx$ &_lm޼/^dD,@YUcYVK 8#ewC܊1?]Ʊ?q#=ΫJG]s}*C|CMF87u,ڲ C< ([7tYc5g+,p|Xj6ɇN2'B0o>})|3q%s%qm>i𸲪|b7=?pގ|,e�kQ^X~7"K<7? <4i)dSLL < ?m�q,g U 䃚Ln/y} x]k;zx==~b"֓5}M~*ê~]qd_L'&oƍ@&�3g&K4 "U-`t&{֬r_ef`'<ͱV`06./?Cܘg!8,ŧޏN3ɞP_W?*{7RC'1E4U1|-@5\*$̑x8�#593V%o !�K?zv3wW/g*!Ŀn80m$"?"PGo?h$>�.=0  @eiZB(-^b^J|ƣ\p"gKq)xݸt$\U<ƙQ`�>6098&&Y?C9;cHknVnp_�D|]X869ћJ,딈r a$J!Hu˺ʵX8\/wY[bG wn=1ED G1dOI�LE@^ �!0eg9kf&D�?+[[Pȹ!.\نӺk@v1qw0m) aGwnߍ@+GoϹV@Sfʱȿ8q-W)XT wTA _ȿ9_� fgϓ ?/cn٫J A\_L x#) @;p,FVt*a{f ͩ*yBӔ>raUsB14x�b]`=eW>WsC,Fy LYh¿$� V@=|[_f^pэnsҤ ktd qJ_9ǹr߅Ur5 7'(g⎈ U?{v};EE9�y8G�rD(J9�gq<h:Ǖbڮ& r?םs}kPRZٟTvl 8l:-[\ZtwlsY8֡&0L(cOKRzTR I�?O$BTQO&;@$LY6b_lTO亴?ũ]KpZ uOݕ{[_bAWa^:fu}ƑlPTFUg )IDfR,#EIz;}>'dKE!�P(vPG~S{#P¼zLF_&sL3W<L87;s(1w*=ڏECM��Gis-GN@"!̓0|I=)̀X B t!}(ݏ?¿%05 pܾC_~sk.H¼-@Xf srC[RU._V̽YONB_ 2ȟȟ0'T69듟R9R{n ( |Rv/|ݿ!h'4 <-/TM~Y/̀o|=} (K[W 'F 0S/<O7@A.mUS:⮚?e#%"& P 2b�cY�̓^.V<.NB@@F6'_,8?4/.6Y N)6Y�2w~ho{~ ̷eBx!ho>#Vet(OȪxlCMߘơ#G@T �m"eٻ?iSYS`(@J|&|4cBSǂq*&@b3XWy5'}Cꌁ}+nI`/|AW�k_rEM Atq4T҅s<_ӂȿU{af P ~}OϢ8Xچj�M3J_2�r 0!B=5_s?;{?~=_?$�B܃}?z�BmJa`5Ϝgr BdjOȟ� )`K sٻ?9y|VfDHjpp#pNOOx̙f+sl| ^V7│�|`pGWyZ-h\7� {(};D_?I�TD^!8̟|ߙ55&?3dJTyQ"&J !Pxܨ :u�"�b:%ޫrjma>l J ϡE頇OH}!I? FEz �׼ߵTH* 0.(ȟc꓿ /cK$�f-[y`DI'?$j.X?[) o.`;ٟgD?2o!0+E@@NeƆw|crA@B-Ey4'"r~?'4оj�!y@םq䍟4B6869~jNu VC7F_cseShK{C2�,=we,U �2?|M " R ~-E^QJdLE@5)tVELq( g3~T̈Oo~*\8ݙp015wQRg.Ul-K/H#bM<cbp4sr2IIL4hI8&cą(@( & MUuo{۷zz{~馭v[n�eUUop%(Chzׄ}L;z`Wz]wAn_xޟP�Dv4zʬ[;Kp =G$;Ҝ']Wsw;Q7 ~ @JB~}6p2+x޲9KJ"K\ f�w= / e'T:쾟;7*�ҍgA^PTJ@ZYwV&b p )z3V3kU}˛~SykVnx9жw.3;Jd1%_^9b4eJ#:=/EŜn z{O$:}+!+'w(={�wÍ~w�8))tDAmO?R�2<'q6Xt]ʟmL%ݿ/H?f%@4 Ķ�JrΟt+"?fk?R�z`'Cڑz?ƚc){$ h x-M/"EUMWsޟ#;)];[S}v>K ށ`Ww{'{,wng=GP [o~aY{RbW \=.X`}>irb%ep,HYxnub3~8w-ÿx)/Z4*[�{F殔 'p)&)z'�mD d'WL:[+_J=spHeO�ٻ4VSHGZ@H.]{+;@:!W9Oc OpZlX~#p IwH�|'o\~a2wXw.fD MWH '+F RQēX~枟8WYvr^S�[&\0'wHEqB*}m ;!gz"VK3<s~+6f4l,4)> .> V}g4~3k(bS�)#9ldx�A) /2)݄e >O�就_QSgL#ƃ<?)=`yt � 0#`#Fƾ|t!'D3�3/g¿V??_J@@L$ž5f\~4fOp#$O:2{~7�P� C7KnUXRq# $@^{/o[ ݜRG3Uw*徿m_C}�J@Hb! H� o :HTsm SEWU�ÿ'+wӫzonӯ�PGvM@n̻nn&y! Nn:3m= Vwߖ 3w희 ӑʟOHG9ǾҲ܁AQڹE_BXyi8W �N8;5eZ;COn$=JMtWZ*?zS =>�Ҫ H#c~;ڴ [$ Z15F R5֊K~u U?aH / ,5?w7K @ҍ�сEf`fOq|j�6JYKƼ߆�IwtX*۷uc:Tß@ڎDmŁBtQEK(5H$w�ÿfٴ|+Jn�N  rnӦVG&(N"R 2Z~~gWS�H'H�@90y_ JI6> `a(Vu̳=V?dJ?tr7@Guq�>kUg/tpX</t0R8WZ+|P:zį~>~!)�$ 80XP?5eZ&ǝ?S#T+1bc=[k#OAߠZA+V�Qx]@ �]ēC̫kE7@) I'JO7]o@ܗypm|M[\G�>/;A7 @g^wgu8|, >݄~o{h:o\G�t% !Od~۷s.4๼r8:W6Enjj{#_EWs@A>/ X L qM}f)n7߳~  xjxA];>p �@x]@ Hm i3@8 `5,QVP?[� @k�t/b\zŵ7@L^9CWq/h!XȾ]֋}f?3h- �I@0%^ tb�tK y(x.tN�ÿUZ<򸪯>O#w?)�^&� ErPS7Uzhz�RN�ÿxH q#vZCލ"o-|�P�z[0hڔ9s̅߼E:= NRN�ÿUp?k}R5֜Dn{_~A2)�^t�H (3kX4{0  G=ЎUy}V?}<U?Q�(�}| $ Nc0�$](/]/�3P}y}kK+;S{PW @g݀`j@%kꟼ&1x(Xin$Z^ol+ �w; # rqƂo.uVc!=VwSG_V-V� �n`�е6Ǜ .cͫGHzXskKS 5Ǒ;/wXS�(�hhS 8� M/5*ZŢl `zmǼ{k?YS�(�;N8Y壍_Z%q}�!݀n�Rwx=\FF4?S�(�$D)L ;�M:h}*h;BڇÄ[^\ ׮ݟA;O[�P�H"@0-0) n\,_8Pm Ӄ_0Pu'>v~鋪Q͖Tvȏ)��f [ GE@@Ȥ6S|<{\x 9w8X[ {3ʃ~-VGo�P�HD :-$ �_p~u׉Iu(Ig[Y``[_Ϫ@ "i="bAd63WOΊ FHi[{Zge^wۺ3)��%݀@ CY\Xt1a ;߳~a2mۋy~ �]dHcGsEĈ SklUH 0b@!^ycrr{4ër@ .@t`Nb%gi3U\9zL(�~C1S5uM/>ع'V)��{D�1HB˱W@3%4q2o�49c_-+VzGUSs??@z,B&)3ٟ6ye"10 oAL;C�z :z`_W9:u ~x?B~V� �" |@ �`ʑS&j>}>}R> Ho%߷ӮwM֗|u3tr-~?�> -h�0\?˴YW_)FN 8o<DzOm'ݽ_s\ݿ](?ˎw@`z@G遘@3jS.*myHfWj_7υ:z`}guCnPsq�>nN"`gac1|y ;î�WtBP'p]ٰuJ�?�>/vDe�ts6<O\Ms  OH^HB7F;mwW]P'?x Q&�D s z .+Ιw6}|9<}XJ:9&:PG{6lpw[Uϩ_nJ?zb_+�~DZ+@Q_p 1l}%O\"'rvB�騟JׇjulgXա [> �!:@BSa!�(HT>g6b6u1t4 vT|[Moߦލc~-|GC?` �!@Ed ,:rQSig_,ϝ7[ ?] LuwB ]x-6NC~B h@HhV |7B ]M ?[NuRq,4gAew?UB}lu[ݷe7RG*}jCcz'�B+P3ܙЈ<? '9fCLjxP*Ps/2?!M@CuwNMo{}̩P9'�BX࣎  3>H:V>|9qy|t ) H]GIҪ~o^mU݉y ~ p'� �F!:O O_% N'''Κ*FOE鐳u�ɞ9)+|)R@.jOGyo[}G3~xP[w#>[@ =Cin|e@)"V:H9A w?s:f2C#^b H(7]"}n_H(4ԩޡ﫣wvQ'><;]ywP>'_{O(��d 5!{� 3Q*GL,L13&!P2h ̸!ry~fP :^ٟ{-+[J~WUn߯N}ϫ^kO ~G'� �q;!)Z RG@B7JaKFʳΞ c+EbJbBӳozҏEm| kvEQJêJUsV@*h)]4ߛ0 @�h>UPhAX 0&MOB7C䈉g#IEɠ' VfH!5(nA \|.腦g#X Fi8jVyB}uwT՞#*X;݀ca(ĝ>pm!@ H=݁B-D xo2QMOҲ|p!b|pY6j(eˆ!VR3nB%!*, ɮNTNk={Rc[62S5ȤNޑ}QwJS'>8 N4*zмh3 @HBoAnATSЀ!4r蘡H ,`AуaE<9�FaƠAJ !Eѳ GU( *'s= ;SʱdIթTMw'j`[5*Pj<+f(XB؋Tn /h+m}B�r&oA)<<(A:x^L̀:Erp\ 8+!%0 h)4݄ 5R ]Գ6 Л� @)ʵZs, ;VF4֦c)8x p`u:FChzK|t}w+|B�%R #roaTD o!, <џQU{W(ܲVgO(�D%%}A'elsي@( .BR fh{~FBn O  _k)E-t /$B�^m_h}x+p !�Bz+ -ޒ\ ZcK_!P�݂|!ך Tɷ36HB2 �B!b vB(�.߶!%� !ҳBB!�B!P�!B BB!�B!P�!B BB!�B!P�!B BB!�B!P�!B BB�B!@! �!B ?�n$G� ����IENDB`���������������������������������������������������������tdesktop-1.2.17/Telegram/Telegram/Images.xcassets/Icon.iconset/icon_32x32.png�����������������������0000664�0000000�0000000�00000003571�13262451251�0026641�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������PNG  ��� IHDR��� ��� ���szz��@IDATXŖmU{mwtm*6҈ jhCH &D D ?LI0iIHEVMj+X>navfwvwfgޯ{?~@KM8w9{E]ҟ]/XsMf=C^[b"-#7]=q } \{oqޕu2 JX@k>́gߵ`bt�%zt.!\`B:&͗N=-;~ST+ kƛlxIR>⫣|E$]G?bhVKYı߽zw}ɥVX\;t06~/~*̿{B=-WoXg*R@553֚t|׻qeV -Xpu-'U_<};7~d&gϽ]1 QpɑBU\יO/S Eqq2?Ϭ10к;2wNo,"Зxe wqÒaS�k+{oM_qFЖڗa7Йv yHdF|>~ 3E5LPE.�`jŪ$]WjQG+CLV̥ihFUJf@)T$Zu-gc.?8jf,B,’˦9jg}O&P, ALgq@B iZkЉ$3׳|ui QH{`7fشZyԞ#Kُ*hVk1xPA)]3&jmNGϲfD:gE?O,[YՑB)h0VU,VhoIߕEP@9 OUpEmEc T"k1[Оtx8Z VBƋT4-ƕ�\xg՚z)TXc&�\,8Fl Ww} 6,ma"rY4 gƄRRF$]AP(5r�ZG iW19)GN 3,[UZJ9¸e39m 0v ǏG4R?:YOQ } UHz#5Z^=Y(4Dq&�&^Z{^kyybkrI8ZR@)|RxG^+?w�_޹4lufBяIOgk<CҞ#N:# ;"8?ֆf7sS%+ntQpNS@)4d˞cv%xcĞ)VweYOS$]Pm@2Mxx?Fn.6l}Q^c8[0>.5Ji|#&]Z{xgѢo{N�d~f\*{ۈCDd*sA Jṵ,9"klf[p"+7\z]Иt�ZJt5ZoT*Kۃ>��#/?#Y\`u gZp֫hi} l_�};^QL{S|LښҐ Ւ|p&��`Ǐ+>)nSVt[O&`P7^ +qڮ3G|\̭:z?WnӝV-VNmYsE[{{sx{^,Ie{TK{Jf�%auVg&N~({T$ޥ����IENDB`���������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/Telegram/Images.xcassets/Icon.iconset/icon_32x32@2x.png��������������������0000664�0000000�0000000�00000045712�13262451251�0027216�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������PNG  ��� IHDR���@���@���iq��� pHYs��%��%IR$��;riTXtXML:com.adobe.xmp�����<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.6-c138 79.159824, 2016/09/14-01:09:01 "> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <rdf:Description rdf:about="" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/" xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#" xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#" xmlns:xmp="http://ns.adobe.com/xap/1.0/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/" xmlns:tiff="http://ns.adobe.com/tiff/1.0/" xmlns:exif="http://ns.adobe.com/exif/1.0/"> <xmpMM:OriginalDocumentID>xmp.did:1ACBF4C4D24FE4119247E59A0A0429A0</xmpMM:OriginalDocumentID> <xmpMM:DocumentID>adobe:docid:photoshop:340e2f69-0639-117a-8f65-8cab695dcc76</xmpMM:DocumentID> <xmpMM:InstanceID>xmp.iid:6ba59e2f-132e-443f-84e0-f4ff492393d6</xmpMM:InstanceID> <xmpMM:DerivedFrom rdf:parseType="Resource"> <stRef:instanceID>xmp.iid:1BCBF4C4D24FE4119247E59A0A0429A0</stRef:instanceID> <stRef:documentID>xmp.did:1ACBF4C4D24FE4119247E59A0A0429A0</stRef:documentID> </xmpMM:DerivedFrom> <xmpMM:History> <rdf:Seq> <rdf:li rdf:parseType="Resource"> <stEvt:action>saved</stEvt:action> <stEvt:instanceID>xmp.iid:013ff985-91ab-474d-807d-b60bda015c0b</stEvt:instanceID> <stEvt:when>2016-12-19T19:15:42+03:00</stEvt:when> <stEvt:softwareAgent>Adobe Photoshop CC 2017 (Macintosh)</stEvt:softwareAgent> <stEvt:changed>/</stEvt:changed> </rdf:li> <rdf:li rdf:parseType="Resource"> <stEvt:action>saved</stEvt:action> <stEvt:instanceID>xmp.iid:6ba59e2f-132e-443f-84e0-f4ff492393d6</stEvt:instanceID> <stEvt:when>2016-12-19T19:20:46+03:00</stEvt:when> <stEvt:softwareAgent>Adobe Photoshop CC 2017 (Macintosh)</stEvt:softwareAgent> <stEvt:changed>/</stEvt:changed> </rdf:li> </rdf:Seq> </xmpMM:History> <xmp:CreatorTool>Adobe Photoshop CC 2017 (Macintosh)</xmp:CreatorTool> <xmp:CreateDate>2016-12-19T18:26:51+03:00</xmp:CreateDate> <xmp:ModifyDate>2016-12-19T19:20:46+03:00</xmp:ModifyDate> <xmp:MetadataDate>2016-12-19T19:20:46+03:00</xmp:MetadataDate> <dc:format>image/png</dc:format> <photoshop:ColorMode>3</photoshop:ColorMode> <tiff:Orientation>1</tiff:Orientation> <tiff:XResolution>1440000/10000</tiff:XResolution> <tiff:YResolution>1440000/10000</tiff:YResolution> <tiff:ResolutionUnit>2</tiff:ResolutionUnit> <exif:ColorSpace>65535</exif:ColorSpace> <exif:PixelXDimension>64</exif:PixelXDimension> <exif:PixelYDimension>64</exif:PixelYDimension> </rdf:Description> </rdf:RDF> </x:xmpmeta> <?xpacket end="w"?>��� cHRM��z%��������u0��`��:��o_F��IDATx[yUՙ}孽B@D ۔!QJDDjR1nj5$N1f*cʑhjtNQfԨ8(: " Mot^vsz7tׯ{|}o}$/O2JߍFZ&+e"&!c�vt1 *ۓaJIyOM='yƅ'{alfӧi2$Ӷl(Uݝn=}l~cSA焝I �RZtI͢J/rL 5|VC" 2 ik|OW~kqR�"QVnM6 sA�`DK�Y1Ρw<>|@L)�U\~woKqEУ2iL ;x꾟uoXG YSp7/\fhxR,dX{Zfe@y_Zz=cv*dC&�l~{c4�ns�:Y0,a_z][twc d=|Z|�7@݊WG*3{J*�guo^�֑!~.f\}e)WSU/77+�c< ho|<n9ydI͏ $$`|`_Ⱦg'mdڱYkO'EqR�f]#]S7txڕA N akVOi4_n;IM'u(HLI}sm ^iI|0Gzif8FS{ -c�iۅHRi+f% I ؎~x@mPAKT23\4cfӒXڔºb ! ~͐bPr0h+߸iԒXx:u-((bLcYS2fk"¾| ѫh- qhTz?-6g̙ ?@3ajKp4>]c_B>)Ym9_' f"5V)Y1-:g ?&�s/ r93* O1VKciSH"&(4|MP ) ^'@’psYfԘڪUĂ˖ fl޹kߝr�|2y8.+榱dns+,h4PW)u]&k`oø_, 0c16n<sju1="�fͬF]<xSbC7peSέ#n܀lWA3%Ggo )L/ !'0b5/!Z4o{yd�f6}FҖ&_b0|2KOMaYS5$Q0.6QZ298FS]!Y4f 0~*0ȄkXN9}�LoBNJp%İdn_J D0<}TeK B4:z-B J&a$BwGK C4K hG�@Z.֬8d9z�03 4L\X*pFh5#L7@20wFqۄG5CZt<H!ʇD "0V´W+ڙ 2C–:c@m|%q)8 R4"ϺGB<_ 9uiئ3&ۖnhJ@ {Q~V "Q5x@f�d1Xx�(f$MUӫq8$bcDYm=AT R B#yAߠXk �,NiŎ6Y4~~l,_P @ƨYR(|W)7T$,:= )B?�O1bR𨘙AR �04Vac^9Wa)=j[+HJiAКQPtrFwGsXNxXH F)2|(6jXbn�Ԃ߿ۉ%spZmpW|B LyLJ E']6 sה}Et<HI9σ`T�A{"shlOzIΌa͖|vzͩ%0&S \hkŨ*A:ed&b0hҙt='?T*vh<(-K�ZkzgOkqn!"ؘIVƎIR=.AC\z.G*8(8BL %<6'JX5ITc�]8`(0E_R/Wt;jQ4ؿ[#9֋42>fDVZ3 q)PLP* /_Ck+o۳st�2{Ç@r;D^᭎<d)N%`#Uc)t=_.ݕ1 ोT_W6hk>zY\9lC]1>RD>Р(w[{+ )ń_`[{ 8kH'Hw|_˘}w׫ZWNpŇ}.if@kw:eDq蟅wt`HQ&^7Ꞙ€k{ۣ�>> 1y3 �_cKghJ צ!{~NH"2$ˑ_=aOwYCU┴`=3laa@4 3CcKGbHl!\s7Յ;)xHZr3]ytC~hi`keZcΫ~=Zs^H}O-1݉~u  - ?yyV[:sbenM4_ၭ⇽؜ qpoýB Y2 �^Y+x3`[OSsܥYŠI`I׍_n9-y qToL^jƵoCGA6`K]9y(RX]�c@9NL� _wd? fHa<jv&m/6g걷8zyS`7^Lb%n[>hIMP,Րogw)cQ8[2UQX0(Иhp3- \gź}$.<~Mxnz;a**xXVAR_C d�FAa/wO�@{nI H�@ۓ?)  @ H9r.%Pa9Xs Rp6<[Oɐ�#v*S" |00#BS)TVSx})꟪1yo=ԔF1!C,׷Uc ? tRVV ]NnJgUxR?L&R1x {6;?HO71/5<ѹR]D՞/X }yo!:'ԙ,�!),Vc$*c`yhis"1c@[!�wⷛ Me7+矿}PiCNc4@VЮ}sݪdFq荕+n67wR};?'7=>Y5&|qoЌ935c&0,@Lkgp6cM)�lyfjq 4Î _0F 0LF4esSjdž\=% u�U/g.ϾE3(JôÓ-3*\ �'#mc.}pWV�S:u2�K#U=UuHq�J9ps\?\ �zOf> �rb3j|I]o�];\����IENDB`������������������������������������������������������tdesktop-1.2.17/Telegram/Telegram/Images.xcassets/Icon.iconset/icon_512x512.png���������������������0000664�0000000�0000000�00000134002�13262451251�0027001�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������PNG  ��� IHDR���������x�� �IDATxweGu/_'9M9i&' BX6H2aлw}`_6� (gF0I{zs>3'#MOصj*B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B @VVF�!BSiYv=WSE.2D�!S^u034�40 �yb}~(@�PJ)PR:J8B giϥ&!�BfD|a/~g 0Ƽ`ý3tKnhdY4GaOkO ׌�83}`<�3�08%6l(eAJBɤ2+;5ĆGb/?6"N+%JJǏ<}:q,(TII+) O+x] _ !�2}rb BNƽc ^٬kd45tsF uA {n`\c�c`�cR9fs:OV00t$ҒI AJ8'ebȳ;n~ℲF[/ CrղOL!3�Bd}�cmMf, _tO7.= c NA '̹m"c�K602nxҗqF"1|9j>:]ok"9}?6T<2)Br(� dj q=7:ӍsaChE n,>6g.4}il^ Xc ۸Nx:-!3`z+3";"񗇟a/'|וXak�GDDBH �Xc ��{|_ࢍKPݲڽPB`PEsy:8;rl+e<Wx>cڃz"X2�ّ �&igjHCtz~Udfk*=Ya@AZI@tc_F.ca&mY2bɻJ?DǞD|d(}ܕ�Rx+{ֆȚ=K<M6hs CYt^={22Ac"64*F#z4~GF"><E �H-)˗�$ -vο(vF=ܰΈ4s.Stc_[s Äĝc<`~򞇒?VNAn@dR(� .pO�hP0p}O`ڭ6-=[hs7tڂ8pt=Crl d& Z�j9sz1EwWܦGjp03Ԏ}?� S%z垡o𓿹7q٣2Ox2!T>Yp}dkvu- 4i% >kn^8#z;ΑyI&cqLq" Hՠ�T7[�c5{6Ebm-'E:Ŭo>0/~9}~E2`@LHE�Tܥ]@zNllon|Eu/?o>=Э;2Ui郲ԩ7zۆ#)di5H�JQ8/H-]t]o;`5/R¡FL[=m_2_U‘p7ZbȮ$)R(� .-߶neWzsӔF,t<;7?~S9+R~(� (+oKv5\wz_PPҡ9}28=`CӡG)Ш�)s�r2nn3nߊ͗3fzqOtpX/]ÁWB@�E(� Vۗ�P]۽x? L?)s&=x١qo~'~Z G�hz �H,\7q{n8xоJ٩t$elTJ?~_?I 1M�̶†fiD_ݰׅWJd*nCHc04]%x3w%|$ܩL+dVQ�@fK2>ִ>s F L7=^'|r F>4=@f�d&} >д|] w)0ihԌL]}k;~xyDGz[jP @f�dW�q{.kuݍ9-*9'�i%{#x, HdP�@[6M] Ma׵iZ+c~{ O!?c#O'o7cPC_2]2*hдw4&sM'Lqp"6}?:y71>ȴ�\qYz1|lwfclF? \MgJG_~( Ӏ|~ 4}ߡC7}fJB rs{p;w߿gx|L �ȹs�63<zk5_ǷV ʱJ{Tơynڷc/=� D)�Lo?3)ocX*i[PvGHH5'�'6ʙ~?ʿX^�`9�LŸ Zȇe* :2;~N}Ŀӧd* �2~QYD2F|)Ov>{rU3@�L0ã7K[.VOs:| nz׷_a @��R(,;{'L1 zW[??сQд���$cp>mW4^=BBY1M3}Hү7?-kl @jviu�io!luIOHP ʱ`D7_noעO'PǏ��6X_Te47i%(ɏ K0sB3H@M�v}{ow|Kv(%Rmަ} /67KOY<lOj�'�Xś跿ذOB:i'K{$oy+ޮñ|JIGD@m�Hn>y}>mOkf-&Z)ӍHx«v.JS7 h4P�P2LomX8cJݶ+?d{DH5SJ@̖%u[LDFFtn� 5ꖻOvǮ3iH&c')Z8bۃK%>4h4fP�Prmܕ=7MT6!)e[YQw/b/<Ӆ�B�Tܹ~iKzn} !ccȆ,Oy!grc7+A@4:�G}7}j/3MkeBH-xɎԱWIPE@(LKl^:;jErTuQ-rF11=I P�Pr7a�Dۻ>~M׍'uBF�Z`p9#O?SJ8*�/oߨo v}JFm!) <m{GKaД@ՠ�r_iќO|;'# !F967R+Pȓw?%xVnK{>md2Vڣ#T) [.[?31#�MT$ �*OnZMW`BfRPv k.ٖ8ɣo ?/T �*G&ol۹o~nە.1'8XЃκ͗M ZA6T �*GׯK,{R-Bjn#k^nԵ:Ï@:JW �߸|]/jX&SB,QJ8Zp+c/>zL ?/NLe򖻾qGsS۵I|?!{=k/=v}Ӡ򕷋}4xTܤ~BHP#4'О:v 2F@)Lޮ E7c}!F hoS9{MV�6N`sn|Q!Ii=o7Bÿa@qPr`Y�d(:Ths>u2/B( %\m ?v={n2B@yv@GϚ78%T>{>s߯ebB:ԇG\�^jct^WMkVmCLN۳duhţ=kg(((�V2? }C;>5A%!\ X0E^{_LJ-LJ]|^Zqv#T #u_|7O(9 �J#wΟ1}铍DbC!(@D:".[ 7^P�0znn}EB64_)о'NyT0d(�]y[rg~kEl T;%h_]t'_[/ Jٓ|AO-\+orB J 0xh'G{ 40(�0跾t"6\C#٧�t3Yu'G{* �f^a跾^DBH L'_2ڳ/f �3+}[_ O?!�`Ꮼ۷/ˏ'x%P0+(�9{n"5C 0Dۛ|Ro|8 �fFR?#_Lݖ+o9B)B 0?7~Grf�/4w~lB`7pɞo::� �o/c\u} lQO!S 4l8#ÿOq (�F�L-}7|DbćF!CIݷy~eG6`@`ZP�0=r Mn{r΄i ޵v~ {4�eZgnyG*Sqv# =T\yc/Ji `P�p2�n;w|G9vB0J0{w =xN 4�pd7MkT)BHuPoefKOd%L7 �_&�  w^e[%>4B.ҶjmF=P.�'�mN}'=u%>4BNʱXbo>Gv�@";"@szh欼D&%>4Br%Nx< ' �M^8]lz 2>!!'r mzPy�`r�W"1B>!)=pxu#OЩsFe2 �vÞj_V¤B?2˄=Tj?MٖH_C')�`jrM˻>05BܕBLP OkB~_A)=sUIBP�pv}HRZG! k61}GM JlgA4]׾蟿JQ?!7^xRlR @�`r2G_u!|(𪝛Fv{ Q�0 �&VKۮK2D!eF hHygK v� -/}_qޠRcl鞯G1N'R? �MZ.hl_TBdc?F|!O!H@ �TMdćF!dJ7n?7O"Q�Po7]궿#"1JBʱym]/葦 yt߅Ta(ZNJ!D F}[m҇}x�r5~`6T!J 5&^MP�[񁿻5zм?!T: 7n=sv~ЦAc(�_m@շ~^h?!T<)C_? EfNO�d �۵f $mC!@ y{4fZ�2:u/ΊJ!L'?o͆+cuP^؍oBO$FK|XBiHS+xS#�C?o͂?�adm~!)@kj;w � g4lRRܥצz^F O@.W{ 7]5BH q~򯍆&djnF�r៿fq>-HjR#inՂQ5wF Z�;w7`B-J8Y& x0jp*�2ɻ6^VB�(%zt@�gɜ?-0DhBj0Zڹ/ywm#jfU@�d<4O|L u)!HQꛃ˷ 51 P @p[^=2ABH͓Lmj`t�QRyWɽO!pyʶƞݣȮ �x*G�2C:�?cw!r�+C7}۽x<(@ՎT@^/+x6#r>Fp9( n P<0gi^> o !PL" AzU: P#�� _}>)| !L麧sAoJؙ%J�Mwɗν6J#2%M]JGP �dp�D˻oPhkU*^"RwْGI_Zu��&1oyW e!LЃuuF.d+UTc`:�`[npB!@$c\t 6@B`U- � ohծ nC!%Mo?izf5@U�@ci6]y-s B92Gpo|pGjj �r+n8$S! 3~TY�P-I?{|>9-\KBJ80So<|ඛU"F�r3z{nSVćE!)@6k8xro *-F�2Ñ?E S_B!B /=8Ł*zV�pk"[�!2<zOe*B6� ̲ M67/qY7B))׵s<*(T9�nuWMKc!T#e'QڛhS3g P@zn57*Bʱa4u/ˑ.88y(rg'T.@*@(G* U/W)e%QHSmQETj@fq !H(6>ïstsH( [*p+"JB67W?*.^8t�Ndo7Z{YFE�� �IDAT!C* Thjǖv?DM4ux58s|ފwL tP�Bt57?CȎTTP!wٟ}МgFse[BjJ'ΰ jbOw�{BX\og7a=~(<۟¿02PPP2/cM?s)TLPĭ?�nj'%>,BT@JS[8Ć^HjlMY9Ӛph p,PP"0a;rX�ldJM>ZjBʋJ'_ + jAs"&8l8SHSpo'$0Ě&/z}1KBtJ&k%<<*0L ]~BYI  !eAMc _gڅQ|l]޿[lo) t ho`Pf`I0ƙ^;4TRغ5nuC)^!3!eV-~ acTHwx[_�:i[3vE篑? PS�;I-+}V6G) 69ַ7~t ims9Rѯas[�A)�(Ƙ~uoA��ȮGtfǣJ#̞J(a^�v Sg½^L gQqDIl>䛻zi?(@'VJ�0{:V\%>$BHW@_v􆰩ՋO#{3 T a~C*PR/}ݕW~$vU`lQg� 쯈r6!̜!ݏ} 'lqQHڥ;+apKsV2-kǑm(�@B]#dOB*no }< i10aKUYgJhsOx@vs=�Ȕ�8ፗ4;hnc fC8E>t(6bg0ߏp]}8e%|~T< (\ @zdWA!\�L6ˏ , GGcEҧG:G6et[\{?Uk(%�de޾;m!L(sވ]]~ by2 c T8:iF�(xu>�0-w? ;S��d(4�*W0STrC ~z,|h$8-qt0]�("S,rkS?2|I`%�hhk s>&BH]08ֵ']ELh,Jjsq1r�@)ر@I �c4e:�F"[޶e"\̰@ܡG[zi!lƀ be~CH;=_4p�cn8 EHYB^q_LGP?P�njЦK/K}L2%kPհ3!lk%RBVloു1㰓 3G(Eq` عvB/ ,� ܕ =K+!YC>cy{ ]cBґu֝F &t$BG~$/�i 5-Х#vw(@Y�dNj�Dr((#ߤBh ֶ49dzi=Cuxm0 mWW t.YJ!|AsɣoL1�ǽ=TPB'BȬQNז QMm b{g�A@ O1-pd scbcv F,@ 5vobv(ir �r+]k͖2E"!B1,7'},wws|"qK`�} 1g ¶w�K/%-aer �k㼌^/B s컍z_֎�ŇWL$=/hP CIP,`jM :hIA RO-޾im4@�?L6m٧T2Ôt1ɱŏ=! ;d1φs` Gi}X�cAbH4CAL ۓ<SHm(e�hKLJ|X"BQ{Q{^xuGY4k $D@KO(`?3(!/Rݤ `(+N QG6,c�޳R}T,BOǾ^?. vyW&pd s4y̿ɡxQ/3@ f[: O(ɀe�x6!89ey׶;̉T{1-`:cgg3T6м~@?<;vǟ!@]_r �@VܤEe dR8cV6gV{1 f H{�)V5WVp[rd(�LY)V3_Wvj�%WN�ڹ2,Y!drmzG_ĩ^4pl8 ү*턧#C(Xa:^0%lM] 7,=}(��d׵z.-+=E=BY,&f߫1,iboO{XP灡1w^O7M^H H0]7_+s)YEcR#CR}n]0oyi.� Ŏz ) +0pqg�{CCģCU^ đ%7Xgp?r~eFG`D顬+w8rd� ҭ;J|< dzva[:FGW�!  %@<p=ϥ/$@i{oe_G�cƆ7]~+!|d9CotK($A31-pd ]ӊBL%aoцye@nX}^2�BJ/\J*ӱ͏K͏f^O0b;xm0 &iw;R^8e[ڽ?9{T�/ܰiWmCH)dzTHIΰً}鄾9QgHQ_cxk8=�pq( 똉K VA#�c?g/ݺzR@BH0tMl`_}<5# m y!W3g~RP(kIJhfm# P� �}{eS%'" ڃ8�:� I }e`IG"u�NI6N*` `ΙmCOD.e��u?2ȍ$ZKO54yQ{z/;_kܤ6O*U[VXMelPEDjD 5&@H!ene2֏O2�`cq-ټA jO22g r܄f- aS >}l~k Iˁ QtcCx5Sx9Y eA  4F΁Zioƹ:lD��p7z:W]!z Mx􆰻;ވ1$Icpl % RHœpYrs Y $k#w'l'ϩPnұaņa <~ӵdc@v@pJ�%e/ТM�ra2ln9c ā^w&YB욹q-pl GiF6~Jk1L9>+<γggiǮ q`fFt H+>HB �J�d�ZM̹Z]oK:Y:hلVz1((N@-9T6?ë $ mP>0 z ktF S0%ܷN549C'D �J�h�8tŎumt>Ʊ> Oo <h IbsxJX (\@"768ܟ@Qp v8cLHcup9`�jhWAd ЗJA[;8†V?XZ݄3N %p|0ǑE~ ΀^2lswS|u wdu J رaaPp6yk)�˶. ͒RTno?486p7t pƐr$Fkl?W?t֙Fք~-u1 ꃞs�q[@"�fgڋ 2ΡNP[LVuSJŽB&Pv a?\H ;r>R�hY ]cjEHPH'vu/^ PH9*{8#qr(!Ԅ ?74"ܺ;C6NIU 3*x K&>Ş#F�IA=ڼB 748( �2ҋW+*57HnI[^ ;8֎�Z:,Q۽ l(fᭁbI6CCosAQ֍?�`)6yxmxa g=6^$ PRB 76nY4|D� �8�p_^o[$m!JjW椪O: ^aq{CF%pz8 mp9G/�tGܖ+�ąp<+%>g0vCf0s;P#@ٶZM9Ԟ>-A0ˏ}aoC }Ōq $,Zzid !<V (�'`KOP vƿh@ƿY. n^e[-x9~UB �J�m93<�Z۷;28.j`_;` 5WL&/i 5hjJ~m~=!�X8cH:s"466 :ƿYDP 9u 7<Q)S(A@)�1y}K.ZGؼrsވ aYC6/Yk"SPHb6B*i B㨘p?:qKՁ(q.Oɿ4yd�y4j_' 4@)r�O9KmKP9S* B > }[:hݎzeRc(]g*~�R9CgCѮ΁3q 'FmhA7TP ��$?dmsşèffAIT!~�B }>aE{¼:Ɛl£0_ ~M8cRp'rï�(>�@AITksg9 |s|#|hks[J@AAzi6��ٹp 2;*M� ,ւL!2$\qf Ho³ŏn~Kd'o4a8FqTҾFP*JB&)Oe)gY^+8?zq./O1+@Bܦ(;߂Ka>ڰxF�p"0NR9 ꥰmXR"q+ aGgA[wԒqX38‰N % Ԓ'ݶu6AJUѯ#W9uT~ �0tzIg/r2@X4_JB/vj0�G�oJgWXDNjD2LGI`pu&􄰷'% ^tw~HB}܋,L}Z6kocҾs|&kG1I_> W,q"W~EnrGۂT _مRR|6u>t<RL0au�"#KS@K@%n6?| }>3X8ΌLOLie^w8,#IpO� $6AsR0?Q0|~}'uR8p  P hZy!{xVV�?:] JUh`KXv61w]J;$x['-9K_>uR)xt =M!2} 4-sZ?.G`ܯMee{'NRA|F)ms#Z"l�d6wyZ ,>=!+ʭ9CO 􅰲ч&݄ڳbp8b)n5u53 JV{apWθ _ٍg8P*c. ʶv>3<AeFQS�޿@q_`^)Gyt>[*{5tn%`@tB?oy}bҾs?%mJQEHr�3}V˔p$=^b|LCN0y��wΪ9T�NH'yue^ boOx4w?nSRXAx[q 'έO!!">Mni_,I`1$lW5*O|;qϹC JBx™$xӷ%c7e0f9JX7l'=ck/ bo[5( }jg B)PBI=!t7q6 Ġk9mAaJ�g[ܞϑL3W+wepRH@ R}s=rfl�y kAR - JgB!0w4 OFe4aᭁ8F|7|+[΀31 G,pѿ8y3M?JOh{a7ک�<+@AIY)?Gsư3fQ>6 gCqNB* -s?:Sڷ9G{]�`]x? vz:1:!1+2(P^Ai oD2 IT1ǁoe7n3+y�}dp0I` o`k{#GW#!~J^ق>)})VUh%Sڷ�е.<5 MLX`sN<zcT* k:( }^3̀Ciٜ�3<HGsVHܲn2.jb_o;C ݠgUnߔ-qb0yh JN-W`cʼ ^�U4x?ss9")': Up$4To312%�`lNhp"W@ʑ08C_ы1JJpO$qb +], J)71KNU+g0%q9q<ƟMgT{~U\�wO&;b T� oEYzZRMY,]WB(ٯcso�¸͏FgNן0K/ÿD4੊ҾS0j L@^:?g@X)4#C)yzwZop A;2rW TH Տ}=! 7lB I'](P'㰝 [ڗK*SD[uυ&0 Snz"W`�� �IDAT흩?R 4s_`_y 03HT�P&-CO ُaIwLM8h R)x !MwZcj /!f 9[~ua.\[sP@UmmU0w"R3�exlhDQ�)-Pӱˏ}alPYAq*=ܕ <^kF)3qXR^sF;Wd*tl� \7±ᙳ#g)`UL��zcg;7,|7 cC>aE܈Ɛ &`$aX I'tB*}&C05^?gnġ쾈y} sAv$S' T\P>IqEB� FJ::[nfkS٘`\c@/Hg3tLl b__key-`)KfHR]ҾBҾ]AvR^:^7Ac>Q?1-&om& &>LߑOpWG$M_W��1a6)ƩVXYnY:-! :N@,q$ /S !/U]w4pgΉ@lzu$=V\[c&NL,0vIwʹJ͜ @}o;g,pV  X{r~R(x5 coo wKPYޒ8C8>Gh >�%Z>ܳ02 t׼+ϻK_0Q_ق)n1<QsGȹT !�TnfhG@��?hkg)IC[ 􅰾ŏ:[(Rb>%qN`bthP}331$!ۓWžw,?C Q9J{|ے|ѧ?g&� /[^n^hw)kQYRqƐH986p-5]K/b>fJNc@xL"q� mrѴ7gG8)/�() VЧJ)0Mh77QO5�KaO?3ğyo~{B;S1ү?g B*IP#}) Aԇ<R�Cx?7eb}luy.~`E;dURALw`m=10h,)�0a׼:L4į� l`oh P2}cq&l0fwWpt7 T�wԈc#)hc-V+g*oY{q*c?ǙSH lh>G@]NJEQ T&/}: K#}=!;^%pj8C fd?C*CsKF͕*Ccx{ɒucsg� !)(aci ƆBml(BҊJʀb�F b@ 0~wo˧9gWeuwGT̓'2;< USV+ؿy隺ccC;0>0@݅c'XM1C!|KJ ϥ=`/'Y#hֿ]pY(/Z2)ͳ'gNi߸pD^ 3p-4<]LNv׷icXv޷-sZ'k1`�gc8{ @IOC;d%ԸkW;5g{cO\9\~*̩Y&RwaZ+4Um `~sDN;z3ﭝ`%C#,a-<rs@F")OfQS#|,MRa~m,n(Ag.4;晓ӤA$L�V$UYvOo|EIۓ+)zAU>;M,qOBxtO7$@ }/kLHúSOE~#G{^%]ᬿ,nȻH}Z!0iI}cC+y<*5|G]>N38D8׷wjWwDS#4H̊HU}~cPI $g0,*zGҚ9=˯<7?3˙dqBO7}iXfx Ji߸p9Ϡ)":b-]boOEo{ff3F(=a��(O8<_?.~CJ  eyY~$N";n}eōEw0c9udǧj+A8"LfRֹ._n8V̿c!4�Zm~_KZ^{c?AkD`AMN,,NNb=SI3㧧99p!7VP"ְYbyH%VX(M}{�^`ZDS &oWy2ob+` *=S'zUoD)IFAogt/<>/=wO?&9JBY?vJ%/,+># c<vtkT^!"܀J`43oN1>cۑi~֌G&g;Vv�v$51I$oh7:r6ŧ|(/>>őJBAC$購U`m鳠O#"i'f9I&J`qoRO?i[_׿)4ӂdHGO} ͮNۻD[xF!'@R-:`�EdEm}_3)~|9FR`&E]oU}iOfn:V^+-zZo.32oEm̿l`@ `ADDdeb Q'7O[쬧#a ,<> W?pxbT"<ý{R,mȕ*E(x G W<T0.ɮ]<aݭFh{#hs)B)R -/3$@D� eߠD sGx~.${߲-Y셂>}y,Ӊ! 7 յRZAVP'&eR?n;4i\=!XcoH�+'�]*PN?a lTJgf/hO>=É $!݀l?ɴ'gH;I>@DȻ7Vt}5#v_[vIplC?mXFKy긽vujo@e+9C(UMg "*65AO0R-s,͇矜fR:+[Eu>~cH̩Dw,<oqtD8¿2Zlo#so$U&b q(t燐}B9jZc*Fx}XD4<�BTSP cw?zDP n@$J>Eap}TODw?(ŭ<Y/@2$͜o/?_|3rO׿v5 81PQlހ<�0`3`ڐ4gf�*nd0>HPJv"~0wXd8sbiky@�@[&:��@9*H<�avcp"A\eq8pAV0rO#%?N#m47|>6KoѶ M[̩Y]'}O؊0QE׳ "IʾW?8?& |߰U`=[O#"iSs<q,%KQ�VYCr8ohۮDFC' -u_Ӷe]ڶ3ìH309�5x�vXNϤ[?8S)MO A$nr.Kyn0pAFPT"Wh2:ۿiE> GKؠAV@~;9�{VȰMO<5&( $(%A\ ӈH382HVZ˓s#q `-/@Rp7or=G}&FS J}`C� zŒVӧgp\ÀO =gK,oq+>r-™Tt>9İRy0ѕ(U.ݗcm~Ǧ|}tiKMԶ, nP+d4?|b?Z'BHŲfǰ}I91Fi ({j&+Ctﯜe }z.Ҷ27mw~;^Oc;&{uT(_̤&t9Zl3~?h>9L&H"B󹾖't)rwߺte9඗_ڷ Fw dZ,`uaQi_챴Y$WO#LBit?Hh%l<.~ؚU$߮MfZ۴aZ:A�{&a!qz6Xc?0nY.X;F2IDwH s.*w[oӒ_m90Hmh:Ik[S1˾>6�"`�Ȗ}ޞOLrD4\6 >2ҾFrd2S'fH%ҾCJkB�f!ؚmmѭmCuصjӾM=9. 'HڊK#ArWlY^";BAˣ<w\d 0 Ϝђ E/ju8HP\t7̊;nnގ.o4 HcqVT?|IDYFffRO%'f'x2*B 'KY7o_tw3kl#n?G_SP jւ5 IXk$;DBEǧ* �%h%yAFHP7Z ,J-y A麘@n};Iٺ2ykm<`;igv6]ouylZvs6X,Ljy�7S2|xaTܴk%,n+$>J�ԎEZ+{A,^G޵ߒjFTgZ;_pxt+Ю9"ЦmnBs73jSۥV/km�5_4&;|2J`&qT{YHe_2-8$CGt\_)P7/5)ftkS+^̿qz;{{@ùv ћ-TAP˗!Zkl`hL8b~k#N֊oBXV?<}|cDD yjT"'PU�64f!#b ǽd<B@<�0@uւo{<>Y~1~#͠PN}E/,mɗ=FIЧֆ OT$2aqc+-j7ݢB׶(Fn栾a:�ovJiIZ(k|O̤駎駎LA(FXgX.-c c-ix 3z]CGJ (HeL[)꾺6ߩM!jk]ɿ-ո_- Jv=m8w~`\M}{U G(0-ZVxe/_[̑ (p?qzicDlei@ FNЧZ2)ON'Ҿ#V\]ɓ&Ҫ1o{k38ރ<ookEўpNQx 2�_CũwM NaFLc-,d|xyfgss,'SX,e$. gz9h*2!ѽ /0\[Ԍ#`{sƻ}c[_%?�l8VT$5 1Rc-J:7K9{lP1~&CD*%|\"JHª }g'<urVu3C%�)XlfSSҸVM1.1ZUKwվ ~PEb0k� om"`/!-PM0*k |$<{?;Ǐ>6͑o,^p<Yd+qp4ZaJʶJptڔGhl)4m:T̿u;o}N}m|{״&Y꤀�.�vx63"A/" : XSI H J9><:1ϟ='CMk<csb"IJ]beG}OXfxD)'hjkBg.7oߺw6v= 1Vn@c0I \ܰ&JJFŠ$jD3` ch1K9][#9>1>xl ,9XƀHHŒf\eKjak,'Npn*זs|tI)!I^rq (ﮃoP |}>gpBC@!# ~`x6o<3o?⣏gs̤n`xk+%emv?$ZǎM$. ڭ�xhW"ml8/xnnbޓ4f K'bX2Ƹx&&S|Y>1~<;7A&(?2F?mu֟/z,m(|Dc|'OsD9}pՕ|�ؔU!yy]7_׺8qޮN|'Cנ],oyhu{{Eځ;P*Q^)&gh"IT 1e&+@ usgSi^<(O e䥈#A"ƎϬ"i_)}GCO%sZ t:2tlBs3}n6N^ݠ~߷JA�" '|{ AZ Jeo<6S)'GP8Zo;ﲼY4>}H'ŕ9/ N\۱̿v_㖸>v1t?"�cM7 `1Aٺ%&6,,(<S_[OnlE1H l]Y?~)Gq xBT&BʏqN{ۼ5&6ON̿{lܱ:z\ ZS.5\�X+۫+2F}Z!V1aJ\Hf3s|?q'*Rľ<m$賙-U*}c4뇊y '!1Bȗ}./ptU,yljGӭm׮fФ qy�� �IDAT^@ŻT?;B�2s N bMٻue#?JݎK0eK5hZc`b g|pygNRg5RĮo@"ABL;n~2i2 Whp]gzlO]3lynb^@f{1yLEPȖWVk(b`Jϗ`AcuU}RA J<7K8;7={?{>1܄oRJc-EVK;>Zf&R91$`jmנEm5'߱ÃأuߊcB%+&%6"&TkcHb Xno?^XJ?;Ǐ<6ъqƀBY,/O+rd*͙(% �.(xl�PB;<v+X Du=:� e|? bG}P4FEl@Z i%kb}o:1=·NVE}PZzH`Xe7lǦd �][T@s ndWi6-ko$:>A[yڵ޾ch{| ^&j bPI[L$b@U�e-�e ieHk!0 YޝYK1>xq4ϕ\7]?5#<>7&C% �Sw޻Wc'ӯD~ڞQlvq!:{ b6 Ž#\ʚRnUOX0ˆ)bepmxgުH?wd:(7nȕҾ1&~K({jnSGI>8"@֣*{Id%tqy7bnSڣy0*�X[b&!Q΀(&`- zoS)> CGX*Qr,|bnG'#"`Ҋ9rO: muXYSMע޶z FοJtmɿO`uKjNX T&{BWͣz`Q" 'iN!J3RL57oo[ǘ`+/O XbI X, D cdz;6F}<nd"yz< kOVaݒ>je� �^|nq j]VD;"i( |$ҾiN5'80P"0h,S̕{\/B/ۿ<?w8od" �$�Rn& b!\:):)D;_)P }N07AXͻl�jBnvto'g'X(0d]DWSϙ}028m 6\4:f9Ly,3t"sҊk+yT<9xK=渞ιP`{!�  �h ydPNp Py [j/XDS3L$@JPG@UbؾA[NA 1ٵm[Ϋ%qa;Aq~U>LgR>1Mu3q#�CQrMo-]k4Ҿiվq^2-o,ޯ7#`P9�J ۫wQQ AJkX+G 4O!D (%8P%k0ߦ v9u>g>r!#"X5&~r!Q! MOB*`]@WV gFL'O-,wJ�q,H6uR+Rt$8u']]!mv ofݧj�*;�WD)g+N3i*F "ă&= :&yv?BQ-f6p@FAF%Xmb`D=6%6h.,#T}Ns A`ósNpguXO 36eɿ7#GYX�KA~c]M;̊ R]##],Mqd?P"܀K9iڮ GqlNQ<(V:t}Lޭw޷&(2@ �l``Ŭ-l/*�$ a/{A5}8^%RJX̕Y"j� `gRC:_I{q:r qcP Y4`fo$DOG\\-]v =}bcT&Upe9K֭Ya-MI H!Xcl>1:(-n{oq�=� DƱ|͛P z(.6K %8k-h%>>d `!�Vuumn ۸ﴍAvލknH|#m=_!%� ȲIkMhdL*8'Υ�L֨\Y(CJV mس^Cֽ]{:.1rW_U�D<9`@[o߷ٵ5sqki%X\X͓RJt.14Scږ@B <nIigwI $uvw[mvmgUx݆V~w;=87cL�y�&XdoEVY[e!$*nίs~`v_}4\ͱQP�� t!wk>1: 6făTeݛo]$& *C@!\Xq> k&o\f.\u܆G),5 �.IvU_WD?iЄJaroRo�D_ NRj�{W1$cORsy^eZdr4o\&J j{++0ns}bRo6:^2{q}D6n  U�9 S6J#ABk͝m2MuiÅ;lJ|SL4~ro(se%.DhCi=fwZ p/jm#Vw{7_C~ƔIf/J�3 Z(z`;KAv$+&a&{ZJ)-oGea{Es9u_3?և yc!x]|�K�rډ(-( aU�G+%޼1H8Hi\(�ʿ$Fy_g|i~+}]1Ϙ?Kwe �aX!0ZW.M~U&H"ݔ,83!ܠ" +Ob2$!hۢݽbsGZ{Kp뷯`{%6㟱g4!Jd?bHP]h[%3AGY ly+y @ZExegOq1C�ϥ)uy4n_PGjvD;&M]p-=eTv9ϸIݻx&CJ��> `G7MvN0PݠnP_v Bk-A`bъB7o,p{a! 4h%,dtx֝?1ۨ3R3»{zԄ� �cnzw.'Y(.�i^d:�a ޹Rc|wn/R|])..�tu9G *;3iv�j"M%΅sT'P's^8, `% W k_OV 8ךDZp5Y*Bduk//*//(4bmV�? Jl,ݨG 0s�"iֻM~< `!ֆ+U[ Y5 78Z-y<}矜C) z1%w.-p GqkmȿUhr7z0 ƽ{6𶨏V�Tlxg>X `75Ea�R>kwkRq}aqs|]?ܐ[s�Z{!$H֙40 kI5{w%HKDORJpuHJwU>?{CZׯͳwP֊kY6K~0(p@kLv}ӽ%"GqŰ ډ, "쮭5L\]lv_�hE֍n<\{"cRBúo3m[+*ֹv9`ޅ`ca9R8Pk7^b6Wdi?J0ְaJ>)E_6Oh%Xk`|}bC (Vu@|dHymjԡc1:o|qP98)W^} crT1w<̀c�c ۫k%%oϐVżϭ">-6-z9^ō="l|[pPVnx\R/-=soPI#�ڲ{IJW [͙�OpъB \{IH`T(j#MX!^?l;nxw/ܮli&(m�{wLn# vj�h.CQ}[*"@WFX _r*DJ+.-yE! b\H$?txxzM+�Q0�vd;E+5!# :J0qgDje%6*\E8 #(WO3~ד/L{7|F {:Q+`mt{YOv:</\XαL11XxW`mЛO0 (i깴HVݯqFܾE]˵Hi~q4C*PmİGKm�wv)N;؝Z+-(N4J u hϓ-չ5Kyo+_<x)lP� M�(°Y6zU=WV]T$lrveRuQ!V7=jT8m9RK9 sMl8^ zlBAh5#亡ax:��K{y#?WB$#, ,|~ Dg (y>^`+Wϝ9uX` <AuømguC,%]Gc.tŽs#l}_}&fseѽ6T]C4F!>yQI /(}J0 3Gm!R5O*bkGg&̤vr߸F9@k�]:`.}OfA׾g7GR P0�T!v^,߻T$=њۛ%.> @a hlXxCl!C:J<.c.KL% &某aq/ !G?�y�UwI%%"z,Wr>�!Q({,r21I@֊Y s"=x?q6 !C9E?2BDy�E"˯b aD8 ' 0#7)D7x<Rɔ<0r#$Lv}�A@>`Wn7Q’zw9] Ox/`"Y.r/wI% %B#u<!C 0-_ yͣzmҨP&W>oᤆo&%KDXw V @0J� ox |}Kï]&WLBa1[B3ۨMúo7N�_^9FQ!c4a5@鯊Y AC/0Liӳ{ZXP|r%wrߐzw/.r~)dJz6Ǐ)|cGccL4߻ƗM$3LR~k_t/|eF,?(y�Ȏ$X{k$& j�)Vdض[R G0湺#*tZu~o{'U3,dк{"VV.)v7G%?Pº%|F?�z�&2_&{e 2*Pб ?6K7s]7vBHWW |K20JcDa+טVPp}yclxG'Sx#R8NP"|!y? 3X29xŸƚ2P&{0jӀD@+o wM{kr~OO%( Kޜͳ^H9@4*)_4vH)?}7y,ʒ +y[y]& t{x&Hxd0z@�)lg2ýcGk.*; pÝQo..QDa*ԥ" J1qho޼KJ dQد#\YʱU2 ᤊS!-c1hBiLv}ݽW96�0j�ԫz-7l&� v[ kX*\/ޭ�fXז1ƠuH;Kƣ+e@iLk%o|T:uCȹ¥m!hm0@R[o/X}Z�Hh�߿8I2`ݠ2HP R~׽Xkw4P,7nk٨ ;åK>o[g&9I C$̥éxM `J?ڟPȈb j._*i]9jm7@G;8nVl}~?xEZ (M MEé*4̤5_zz C DX+x\|F +?<'p{w+"#`#S·^ #IL#BxC)Üϝ"]~`&ńXȖ{\ZʒтJ̟ӑFzSBC`2)+/_`5_f: 4!V*+vGqCHȿ%,׿j#7+`/ LY\RO?OGO%<>g{*T"<| }�Z߹l=aQ)( W;i>xr&DJˋrotiʷG&PSȮ/-lo7F @S~+_l1IL;o<7fOJ@$7{jLZG/۹h5f@pj"b&xkqgϢ2H1߹< wE~3X}xjUFat=�)f{u+u><'@ zCTDȕ=~YNM bE|٥4� %BJ){/\^e~FTX' H_Ұ=T*¾RJ^ztoD4G E} Ae2l- _ѦA+D!x/a$EFlG1Z/[zK_L |˅Ll-}.B3 U~A'4@_11 |EUhL8/_z_r{FGw`CJbEG8F~OwB㥆�� �IDAT8i^nsZ��.`ݫ?~NR2 v R[QLZؾD|G ^`•%vk-LњqGҖGZDy{'Si(`Lj{@8JM31"; ƴ ) c4g�?T./%� v[SZskBʼnY `a'ipm�ng/.MRPHcL<[W%q4n~yUb:?<K9r4٢]aB8ijeS4ҿZ�^�}fm~b$l sb `MD&b)s~q;\OϠ_Y!ɔkWo\f*턋p(6>WWu+)6v?|pҔڗlN8Q<�#M0%X,/I>@]V0@ݒ B` :�Fϼۛ&(ٯ:"ZV t;y?<ǿ~&Z&x@Z+nX-x O !`Ko˕-&q0O> ~ů*j *^dƚ7!$K9~<2?jk kQ1vB* {,A2#.`KY ^ HBR%�#({Fv( TΪO9 6) LR)<w4ÇOLu(E<rEw_h)nˋ*?Z k*넮B>,VBJ ח|NfCCc>fTʉa=׆cBҘR~3aȗb �E�Ѐc6/_3,)0gNHZ"B/{�JP"(-TWky?uUƹNIk;MF7/Q />u G8pYW}>Ϣ5z:uBC'q?,?"F/8�"yͭo:O<H" .dgH=sɔn/'Bw G N(_ȿıwoҭ4j>" 9Z05,gGyrvό$+Ҏ6/]\Q= -^]^g#C@KWr!N;$[Yhpe%OC%�yZ(x߽w(Jȿޮ87'TJm]Қ;7/W.28c^%(b[ t^ $={՗׮RqcMKu}e0K'{e *gsK9]e7�hj7=m2Z~=~oWt%?ےAYh*Y*ta"c(ya-/q?DaR^Z76@@̋WqoI0,H%@I gq�B[".d�&ݭs~E2LIkHf;Kj@p`\\#OI}7Ty..1Z'?tHfⷿZ>OH�W ,ƿ{{{/'^=! \Vl0ֆ0n 0h..{Yȕ*f57ڦ(tZKK L8H)<+MHP@+KKBh�@�>|}zr gP R-{UiG񃇛|<%G2?` Ch2l߼N7LhbUA&FB.>QX4b2 %6x.\p/5IO'R歅,+i*&E5>wyrUCm@@ yoF %oq?ǵ,3gUͭ:tױ;tTbF�^[l)'. 7j<y|&i<Pv`+`AƗj\盛v;;/f&5.3hR->Q@#uoS$={[_^{/ Q*f{u]|qG>j}wȗ`l `+\O9tJ E ]DRZ(_^8:L2 Jքjk toӸ/Qѱn v6T>7o.^xcdp4xR? twݴABc nq;mn}Ș7bʵ^( (}?~67Pz Fm Vezk~Vj:'fb&)%XTEݯhKp-cv)6& tmbkr4ZϾuqyq>h%\?nݢ7B‚&p/ X#�p:\^LāT%pg#GNNl& d .n�L8%~< d k#h3k1H22ۙwnƻ݅kE!i 6 fR|# -0wr{U${ wK_la{?'NHc?a�@u@*X(iLbI0@DĮPtnsL~�[A` Gqe5g).bwh8k i܉l+7h:qvmKh$A(>߼J! 8g7%l]s.f$!d&)uB1�Xc,NJ?%H� b@B+-dK=9%g %0Ҝ_;ɖ}RZ7mn6mvvuw?Fƺ/aEaQ8>>|Y=>o: GփM~"xbS76Ia-g k ,|=q 3VL Y(Rrkȣl@p�-2mE ^ֲ!!V׈E!in!Ï>b8talv lHj˟op_;`�@ir4WB!c/C^/qqi mrn@3_˼ro?(vvoк? B ./d /XDȻkV2蝲⮙zvlhBR+LD?A1�j};RϿ$F@ )Ųίxooq~ib( []7!ݐuwh>mƃMܩ;>=DJ ~GNeh/ip !f�''2r_ Y((Al�"#sQZxJۺ׎v&.W7VB2 Kٍe2c2B(^|%?!L]~˿[`J?, @5!0mӏ9g?q@l薎1!0R}|vå-%6M}:]7#Ƶt kF C/۫<*98[yƋ麣e&Q"!s_n.El �\�{}d&g" ֬6[, C|bNZj3�W3oC106['g L8;rorcll,!цd(uWsV8@;mm~g.J'X`h,ϥuس㻺ks--H\3 tnOMH@voXъ*eLi..n߾Fl2?pՇ/_-Fe 5�:) ?v7C?:IB`BF *%8$ڍw]|BW򏝫Pmo+yH/TfOހV~:W1mK.KHQ_ρpG8@`�B_e$!0!J#ځk[ qȿv8Cm()|:'f2|4N gEOs?Dʩ:vKuG»7o~w`DZ�!/Y>6<%Q*4L@T '6m$E5�d4ʼ|}ˋY&S<uttc㈽3 qJ o?}|-4PpNyh-7 f gρJA5� |-Uq 8;|2uds9 8j$߰{ȿjH O/ޣ-<c91h&TZ(;8;VUw#0gRAbTDS2er8;a̱ٳgaivmYǒ%9ieIʢ5,Qh&I0$@TUw ]]@7wNN`U}_M@HX M}ٞ 8u}�_P=bkz`Dh�w>Q\ߓP VlIyk|O+� J &qX1] pcę8яPPtPG2?mTYKY?`�5R50m$Շ2ȚNϵCS>O#�K)$LR5ga4,ſ ? G0_URC�..S@)a]9blxs"P^T P Hթ!YYll!m$Ǭ׍۾*Edm~: DHФ 4]i~ߕ noX(s "%vm{ {+Ue⟗Z0��`nf<Os`2H�lJyVћ+_?LJ"IC!FEW1=B{&" bϿFVGˉ8�ʊِLxTjqxZyW2D1ΫB@a^|a<*ZObL)C i3(!l;}WE{}oϿjB� $len1sˆsV�mn@(8C[E˹["S]@|dP+#m}~Ž$�Cwv6cj 4* 5?vWR+SDBaXWϟcHWmſlԚHe euYm-Ky+r�@ٖEO޿C?) !b[~N߿OM?P{^J ƺI @hZ:)U"S~5*)RAIz?^@fߚך Q],lW$) PSsUP? Bu.=|fTߥV @P@rꜙkV�)B L-)9*ş�gMJ+ҵ? 5j�< @370l('͘a㵄Bʴ ڨx ޥĿ(F[SI  ?8ٴ=0?D�/³iHbv{ gwBF!`;sn="Ĩ!/|r]G<?FP*/ �y ]W!5ӸM\2" /0*@vyK{Yo%eF!؝m-=w {knK� � fm%ڼHʊB: ذTmFr[PϿP/Ex_"%>b]n> ' 4�d(c?Ӑ7a R�rV7>Cj>$헻G "W(H5{ h3 7Fx7$H<z%?~ 8B?@{*?`wm56|Zȹ3R&ca ̔.5]@5\J$9.RDElzc'q 8�mmP*aܲi3l"Zj8@k(Wf0J#0N >G@政OPԬ\8W[f eGHMbXEq&ͺx"@2}S~/ �h� AJ-SN=3HBӠ (E/m"Al? yw9G -[(4�q\ojd,n]<qR_"\ǥIBjPH r[`qmM'e@Dskp?4�v Z@@cR );H�T (D%Eݩhm ءhHP  0K߾l+ 4ߟ7m4�QG7 d_=*4Wmǩ󉔟 11zw '1Pm7UU8=~/��tr¹:IqL2q/tB'~4H{>uq8~pA�{iզ/X@R)0;v@z!_H f=n'qE�'9jHƓֹ#G57)Sr BL@?h gg~v+2qN @~]/<}` p=jBr"KW)YNJg=myҼY l͊\i@rWb=3 ҟ;ߝG @xoMѮۍ!83T !$_b XJ/~<3`dc0x?2+Q 4'Vh9,kиͰyޑʑ69ιRſ؞A-)UzDV}n?Jx�@QӗqI%qLHEf}_sAoـc$# {rW[!3[ @d&@N;"͛-Zw Ę@R{�Q@|+o|úxVc(R7 j nY]5H%01C�T,1w#,f"(lw ˴̳Kn]+g̟T ̵ !� 6= L69O,q[y\Rw!ARӧi"=ݏ%B02Ts uA}])f1'T!D &a"ׇ>h} /|߂`#/N|܋oI%C0:t7[on8mM�$B@ V1O`/VKſ#xW#p ; `n阱Mkj&@9V,1R6Sg׋_"s?4�v�,!|ݾp줱M74J"pPHW^OЏ7=B0x ݾx┱MòSD*v,TsO#|X8<?ƛ[԰%F$J~ eGሿwi_Eh�F5!;tӛ1@*pL@awQLa ƏPGj"Ȩ "0KO�5O0 �>(:dw]l_9sV_s`D"4xL@OG2T0?QiDmS{B? @��0.Wδk6 �Ra jp]G8d8[$̸;m��IDAT)eK h_>uZ_}߽"�H 1l!?,"҈'oom{I$}p_ߟ l:/_.?|�2d�?$_mSz[_&h�*C P]W.Wo("bA1 !ЭU89 :İ&PWqPz$-ғ?e(rv!La@RqprM Y_ "NWO~1MkWi+UL1&T!SX RRk)#E|O g?�T `.ڒ[isW$B� KjP[6�Rf&I#G04�c t5o5+ܼ\&4d5R"ŷ']'hſB�A&@@:kq<m g 1@"wB撾1`lq<Hƒr-fB}E'idOaM}l=o['w}>׏sC0x܋@yb S[z۝B(Tϔ I"qFxSǞp=kcƷ >pwk`m1`  XgV}״w =b RQp6w8Ȟ!Hvǟjk +%}C0>H_nkٗ7WNV})DHBxOѬ s#$D_xIy NO+\oA0>O }quv'ڒ[Wsfq (B@ 4HJ~_&^X�_4Nrr%!@*ʐ AW#4J!cd'O%?|8Hg~( IH@:.Qh7� P )3쀌HxkOlRPTy ~?7' 39жռ{?W݆P8@R1DԲ_CDa='߶/7Gf_Pu?`\A0~ 4'ewOe^�nb`jv�ſt@'ߏtu?7ӟ14�iN^@ו6y>9kMrEZBʏ"xoߟy:ο63'$4� O>]2QrѺ R)3:Cem<@ZGc6d?o?C0x7 ۲S{ [׉ bA ԀP̦ގ]7x?{ �?903/YUK̛qHT K)# f4oI#w@�L<kx7@S}V;E^ ׬v#Iʉk[< .36(8_xo2CL�Ll3f2i5ޯ_:-^ZOY2,Gr(Ő:`D`~Nxj`&(�bڷOX0W]=<aFd)PJOoOx@PMꖦ{ͦv¶kV#TB<yw )P=֦SzA}:6M5ʶs>"\"f̟AR1cۊ;k'V]ሿ7яV!4�Շ8 BkSS+@D7?~MXgȿN< ujA6o1uL( -ޓa�*a}Oh9DhjK!: *�T7.]Hh nzHg4Qˊ ~-/?WzU @mpݼD2No_:qLb6o6g rK W#V򳉗~42{n+Lbh�\ m? 9*CN !e`4At TCzH}w[ϟT)jy�� �!�Vz-墵C"Wܵ f\Sd4 t09sT2 cnvw?&j\#F!eqWIL= != ։|j_9s'{4�71FjF�p_ACH{!Fܩy^<4�?``x4�k+x?\r]0#p`O͝[_U=+">�}APshwX.(ƿ~ML8m1ƈ�J:n;u pWJ~Id&xMx!�RL_oWP?y t:?I~Az>'<xq`s܀02#�M_N[sCt8Rn!ϙyakrJͻ8V%�tnrM.@ر dq<mo8+n A@25B6h?)}[y[a[MHu!$�֑?mO~5u<1' ? @h�H>tMk�tc~sˤ \Ƭc~vi7DfߝOB@ ?,nڝ_zPKV8F V~#$)v?Y'w0wo{nm:=WȬO'y᝘l4nƯ|UYB7a~q]8?) �R aoYawnnƯ|EYp#0R0Z'wo}5/ެ~^󓒠 #oa`ghܬm/6@09} {g{c}x3 ?Ɉ AF�1E[e~J.ZZHŒ6(jF8|4nvGK ҽyo?=~' �MdF@m ڝ_Deƹ1.�0 �q:;?};ڐ{]g�R3#N=�Bre_v˃6w>,Ytd'c}>߲1.8"n"B@ʅw@6#]}0'r Ĥsuܧωy+6@f N_3NԞ'oc<S~7G'eHx©6fLVݳAsi܄iS`% z-s'Gw|n\szL#Tl0RĬE[6k>E^!3*@!݀:Tgrpմ=ywDwܽ>~Rvh�XܸF[sڊŔa+Jp B�RCPAӾrutLJ։]{Up^!-R^yB@eA7O_ap �<kv󦻴[67߁ C%d`�˄rj޽:Gv3>8祷�>'+h�x& KNub֒{75+. "BL8 iHI@Jg\ a;ռgռ(]H $ޱ}&q �Oϣwx h�(ܼT[mզEE�)!2{jm>ouju)|nh?{q �jnF�1I_X[v]MwYK֊uL 犾f:`gwL t_##~>R'w�;<F-P{k$9{MrɕosG-Fj!tg $Uw{}va±֙'̞۳}2O54�d"T\((2Lh6 \'&Ϝ%߽Z.^{\rz1yBi0e {e2qI hSǶڅ} Mw#Sw{޹ q}Ʉw62 : Pdj\p:5rh>K!1 N־*%틩-vv۩&Iw:l{�ޞ>ELHh�H57Ȁx@irmkW Vq aDL62!})R@/zo\/kVWNZۑowʞ   �6A刽f= "8E.ٰTZ|\apF>$ ,OE1|!u(30ӧU-' GՍWro/  SI@@&3^cy�u59+W`ڢDŨ2!ňf+Cb9f+SvuԢحMjfe|>Jh�H&N- BTݼ@B7aDM7-. -/BݤiׇC)O씬԰P_󷲁dBoPEԵVj}dڅD@\o!3O蓪B`PA^S}O97@5 9nSً9)ip}!Ԡl+5F \�Lh Mw# $ U|h/lCW~I5_Q~$cp޻(E=zo^PE>>9h�H#l?o C3uq$9wl4N# e3i.tRʲ5 ӮQ5"!:%{C锲SL$Ld 5i_9s }7:Tlk넙"T=too}퟇=E$4�d4wz(Ð2$Ν$g.E82F1c1ydi#= 4F( ͐t RJ)=oEХ<RRB e۰-i!7a%L#Sў~F[o Q.{}|7(l++Rq8b,_>$~cA|  �!m27=>)X CH]4LI3D]c=BuuЌ4= 2 9( ;n@{x<lۄRpJ(L2Hc*>0ިs5 3R&l; e%anvwd62g{�B#~`5 "̅;$7{򛓠qt/x!{O>>{ �!#{ MA6s.}c}N'A4>H̳_ >!%C@Hyy![>'Zg� dAb~{BF�B*_^"/~^zkB)4�6Ŵ/lœMEEh�XdKhNa'd@@Hu3�B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!BeAPf����IENDB`������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/Telegram/Images.xcassets/Icon.iconset/icon_512x512@2x.png������������������0000664�0000000�0000000�00000276171�13262451251�0027371�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������PNG  ��� IHDR���������+��� pHYs��%��%IR$��;viTXtXML:com.adobe.xmp�����<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.6-c138 79.159824, 2016/09/14-01:09:01 "> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <rdf:Description rdf:about="" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/" xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#" xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#" xmlns:xmp="http://ns.adobe.com/xap/1.0/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/" xmlns:tiff="http://ns.adobe.com/tiff/1.0/" xmlns:exif="http://ns.adobe.com/exif/1.0/"> <xmpMM:OriginalDocumentID>xmp.did:1ACBF4C4D24FE4119247E59A0A0429A0</xmpMM:OriginalDocumentID> <xmpMM:DocumentID>adobe:docid:photoshop:743a6e8e-0638-117a-8f65-8cab695dcc76</xmpMM:DocumentID> <xmpMM:InstanceID>xmp.iid:8f964db6-7fb0-4916-8d8b-8a96ff3cd37a</xmpMM:InstanceID> <xmpMM:DerivedFrom rdf:parseType="Resource"> <stRef:instanceID>xmp.iid:1BCBF4C4D24FE4119247E59A0A0429A0</stRef:instanceID> <stRef:documentID>xmp.did:1ACBF4C4D24FE4119247E59A0A0429A0</stRef:documentID> </xmpMM:DerivedFrom> <xmpMM:History> <rdf:Seq> <rdf:li rdf:parseType="Resource"> <stEvt:action>saved</stEvt:action> <stEvt:instanceID>xmp.iid:013ff985-91ab-474d-807d-b60bda015c0b</stEvt:instanceID> <stEvt:when>2016-12-19T19:15:42+03:00</stEvt:when> <stEvt:softwareAgent>Adobe Photoshop CC 2017 (Macintosh)</stEvt:softwareAgent> <stEvt:changed>/</stEvt:changed> </rdf:li> <rdf:li rdf:parseType="Resource"> <stEvt:action>saved</stEvt:action> <stEvt:instanceID>xmp.iid:8f964db6-7fb0-4916-8d8b-8a96ff3cd37a</stEvt:instanceID> <stEvt:when>2016-12-19T19:15:42+03:00</stEvt:when> <stEvt:softwareAgent>Adobe Photoshop CC 2017 (Macintosh)</stEvt:softwareAgent> <stEvt:changed>/</stEvt:changed> </rdf:li> </rdf:Seq> </xmpMM:History> <xmp:CreatorTool>Adobe Photoshop CC 2017 (Macintosh)</xmp:CreatorTool> <xmp:CreateDate>2016-12-19T18:26:51+03:00</xmp:CreateDate> <xmp:ModifyDate>2016-12-19T19:15:42+03:00</xmp:ModifyDate> <xmp:MetadataDate>2016-12-19T19:15:42+03:00</xmp:MetadataDate> <dc:format>image/png</dc:format> <photoshop:ColorMode>3</photoshop:ColorMode> <tiff:Orientation>1</tiff:Orientation> <tiff:XResolution>1440000/10000</tiff:XResolution> <tiff:YResolution>1440000/10000</tiff:YResolution> <tiff:ResolutionUnit>2</tiff:ResolutionUnit> <exif:ColorSpace>65535</exif:ColorSpace> <exif:PixelXDimension>1024</exif:PixelXDimension> <exif:PixelYDimension>1024</exif:PixelYDimension> </rdf:Description> </rdf:RDF> </x:xmpmeta> <?xpacket end="w"?>��� cHRM��z%��������u0��`��:��o_F�@}IDATxwxdg}9L/e[+.w{48;@$@$cmZ4sh43FҔjU9)<����f�����P��������������������@������P��������������������@������ �������������������(������� �������������������(������@������P��������������������@������P��������������������(������� �������������������(������� �������������������(������@������P�������������@iq5j�v{\��ҿ5:L��dh7{������:Л|)ϲR^?��@&��hܬ -$="w ^��P���_Ph7[1� ^[f�@9Or�@5y*poB߿ԿZXn[{=ۿ N�r��� _VxZ0vԙ5ƍ1;[Hj l'n,+(ce%ٲI2dd$[1aIo17.y)<ot!I纇%I{HRsc+8Q/19NrG'GO94to/yr?/LSE��rfa̪|]F��@Y\9=߿g3ԵrM N ?`l_dzc;23]#y.4 zn< zAy%wbl 1|pēO}SYB(P)(( �@%VV ���\JCR~lI,~ܹ;m3`e;MƲZe7X*ʲd V2ML=MJ <Ci˳`PbE��@��`~oV cV0nFڌei11^R/xC^bj厏99zxϼaWrlͣS�� ��(��� K% 8s{׉Zp}=qzȘ�N&<7G/z̝cbУytxbbr\T ���(�P���>Z$^G7lC#/pcUASLћx096ԁ'wOγ0@Q��(�P����Ua?WԞr [`xd,{#ڡs;daoj~w|*PhQ�@���(_H؟{CQun9ƎmBb,{ֹ2𤛘ߝ]rw}3mB��@��Ls=g"N#ؑQ?tXSd^wrɑLwO|G%يRE(��� ���؁ЧV?~q`*c>*(0幉{ɉ&Gw~^Lwg>2 ���(�9��H7Bַ7ċNrjO,Ңa71دo~'>}o@!y(�C��l!?huvb'cٽ8&M:91ݏ㟹垱?ݗԁ �P��@��~Km;=:~>rʘ(-'9&& br?wޟOh~J ��� ��P%mr3j6n#YDU-_#?x绿oPgB�(�P����eSC~g\/5מzYp3S| j#9Ahm`] S^2ϧ'>W!^ Xj ��� ��P!?}M姏 tmcg[.ɟ'Gob@(��@��p)䧿m9sH+:Kƪr.{܉$oܝ*(��@��X_)twol? 3o - Tn9KN玏dr?~⮿@1��� ���`BR\Z{ ,,,v'#9|G2:�� +�,�P7?7DŽ:/1-\#��</1~3~#Y E1�@(�P�� D6=O7m F.6wMtGyr?tQ.���(�P�� V?`n;߰a]*cк�P 8N`ҝ?>g)�P��@��֡O}zd5WӴ /11Z*l݉:O~?:ohyS(��@� j ͗T|ً.vWX2&DXb;9p?~PZ ]�P��@�@BC\;xSt+c,*L?I]_R �@���SyVoB][=ĹgX2&B(b;1O') .nz�(�P��� >CQ{_}PJVP�w 96O_&dž�I!��� ���J_*g/}]mcO7AZ@7x7{5?5 ��� ��J"2.?Ӷ?É_c1()$~:?ן (zKN��� ��V;g K=7M'_|mBKѪ�Mc?ܿ?O~lJVb�� ��(�|�%n.i%{玌ysp2V#- �fwwrtkCGp@aB!�@~� ��5g KoXUW|q|'Ѣ�g)`j◉C~g0 RGP �@���y@ioƱgO1QZ�ݓN}mZ+P�@���0KK=4=8hM�(r:1?S{zӯd\G)�(�P���`Y~st)/CkdLծѡ/ _{/xDG0=��� ��� Z/eƶZZ8U��Vڿ>y'yDK!��� ��oM=F;Zc٭&�J% ;9|wҝ���(�P��Z07ݽ-rĉ73dLRxᯍ<O=g߫eN�P��@�@EO}u-Φ[|Ŗ/p�eU p&~:O|JB�@���esE_pεѶk^lfcziM�(J@r~{0#X'��@� KY{ʥ7sd8 �V p'GӧgI E� �� ���y_-_?V |qhM�Bp'F=o>Z'�P��@�@ymܸ?x�UY p'}|z] t)��(�P��?mo^oxq|Ӓ��IS7mO{(H,P��@�@iP@za+;�d'xx6&(��(�P��Pߎn>5ʏ5Xv- �ȫ&?~e?/ǵp@ �� ���bO K^7u_i,�,dbpɿB�k��(�jx �@IM|K<Ʋ7В�?QH-7)�פ�@�@|WkN0qӻn "�` }䉿s#K2@!��@�x)ϻyNM+eЊ�5:OWwwG -P�(�P���hqW}WXʹ"�` {~9 L-P�(�P�XN`/sE[I-][|qeӊ�Ҩ$wN z#_=Ӌ�iR���T:[A+�JL<4OzM;6�P��@� gڳn~ &�eQHL.7O H*� �� �� _2_<9wܛ-_ੴ"�S?=x-;'F2(��(�P��2g[?}mm;MV |I�P<wbCGu}�F(��(�P��6g $[/v$~$V�TDr?SH*(��(�P��'g|;P>p52VV�T4=<up쭟>Q� ��(��%3+럯7XFZ�P]u}O.F;>�@��$>mI}pqw"-�B@bף<WOr/h��� �@[}l �0܉Ϳu ��� �λ.7Ɖ־LhE��2&O/��(�B�.Z?=Lcیe7ӂ�� ީ;o ~%e}�̇ �_�ѧ�@�@?IV_91;�2Db#M_)�٧P�(�daq�3gZo͑k{-u��,q|'F<城?׷HoS←{u�^[�`�`R;NM;\CezZ�"Ɂ]w>+>8oJ  Lh�`~ꝪL��TH7-ot⻍;�`�ߎ<gZ`Z�@ +�� wf|_p薧�g߱3|M:5i�_G�Sz,I3-oX�{'<^}W%c�};-�Uw㛏h�ud@kGOnH�tF�`�#�@_T�0Z{__-c ��%gL,"�&G%Gۧ6z��p x`Co}/g2i@��@�P_>ߒ4욍׿-v$~.�@p#?6w�o�� ��*Kmgy.um}vZ�'urhHl[R�� �� 5] ׼v;{V��2$GB�@��BÿY";㋃k,��nr·_w*0��(�P��4ko<W+}�@rdž;yӞox � ��(��lymnD�P<wGn`m�P��P(�_O7\^n+A�P}ϼɯ{  p�@u�,@_ڳ=7)?e.ozl�P<wzZ< H"P}g ��R$_wؑb��0Y#gt~{h*�Q�� �� g6O o:vh=�� ÿv/4?% uZR � �eZ�8mI[Q["O��dcl+߿%}T8? S:K�ù�F�,-}][bǜ1��@=C>ݯ̣rS޹��3L F|>ŏ?G��P(}4p$2D�(mu[7~Wz z��`yU�յ}km7ܺ1G`A"�P6S�R~ 9G.um~��h<7_-H@ ʵz S��!n헾ПofBGM��f,1}3$/_aJ�P3#� 7G՞z';��KL_<Q� -\Q;?�`?=>Ir?;}Ǐ ��`wli?{VjD R5�(�� %9OޏXQZ��mZ?rgXSM)@iU?KL:VO/�,{u槌r��`yģCG
 .pܗu{Pٚ��o𗲯͎?.��U&&^uҊ�I%B�(�9��ssv追�@1&o|{G'\SScJ�@�ߑzG}]dcr��5=^#pq@;0F�X[L` �6K VS�b��(;<W>mgN SbJ�ֶOz>S��d V/ܸ)tԧ~wG�@1V4~߽3}TXӣG,*���¿7>#X)��+86on|Z<% ?ĺ���b̦n J�MM}E��k��V/K/XoEdC� u۽/?_ }@ZWk�@=RmzZ追w�@s| m>c@�(��e%6W}|;v8lZ��TEEyͧu� �@/}-��q|G5sgp�� �@Ʌ\|[?[ c-��*?[?_/H�G,P��J9)'Ե5|g}2O�.G} um kH-P�Vpڱ 9 ߑiL/mj}B��,Nb~-%]XN~վv9d/�K�ۅ?ӂfukNvzh9��,)x9K)$R �7K�㫺��nw|S/�� 7c;ݵ^Mʼ&Se�(��Zo~cػeZ�� *U=-sq@���`5ÿe$lj7}o7wW8��@!E�;޻SS �pz�k� /_Si;?ir���+N40`Y�KWk�PYߤ/|��P_q{wtjt�;_ƺ���?=υ7~q|h9��1o[_8>C�U ;߿(vY6B��B[bG#��`JΖy?'c"��jVL$s][>ǔ}����`EߖX'~fK{fn<���X}>[@دkP�r`�33)7[twocj���#9:?׌1I �a;nvg �`aO?;W{5G_ ��/;{f۾^Q#ɧ̣fU�a-WeK��@ |rիZ}:�E��3/xw<`|h5��a|c[v>e @�e%¿-~ͧO+l��PE�;`Gj}7;dʇ3<w Ib]�TF�swRsE"��x[jN۽o-m�1�� %scGU��(*@m3,e&"�(��Uٷ#H _1Z���&= G;���Sÿ-ɷ}PO?-��PE�k6kz$M���_4��|فmTҧP��\`sq43����ʛox۶Mi����Tca߶O5��@qj_S }P͌֗Ƭ9xݏsTd7|DK��Tȡ/>~]b̛;楼ػh ![3�Il/��PHڂkMNFP�@5m`| mHͳi5��**cWo~Ϗ>?>@�(o\_C[wGp왴��@`t{ߛp!-\"�*Zկp k�TssLJzOX��@us'Fw\?vT ]`�ǰ&@%Uڬ�R;6?C��$Y~}N9w@4!G<9{Wi5���+n;=J��2c=Yy���2~g?H�P��<K"��`"yo)�PW!/} F/���d8 F.yӗ>� �@+=���P;c@�(?{qvX}wPJZ ���(8?z"E�̔^dS|qr/ߑXoˇ>;Rs��H85蔤[R;楼/>j%+VkCf�*+E��@1ؑgm }}N p=#@�X?7~{.��uC� �@\OoGuM��bsb/_IOy �P�� 4S=]2o_m~5-��m~e� �¿yG_7��j7w볕y=�+J��ÿ6{u6+Z��`ml㟮HfR��`ߤ[oə���kumD[yNjT GP��`vwtgeV��3t5&@�(�#+CO]zͧl=2VV��pdiOk�l8l=+ԬrQ+uLOmI/xg ���J&<o_zg)Iɔ7w͛y1_GkXc 5#�PWk&��Pεw7_e u/@�US�sǟoU7M���rի>?oץ� � k|G>a|i5���l?Tؐ@��P}IeoKr,ȷ\HS��U0r~Ai񢀋���Tjgoz]E��\U[5=]T`�֚S k_9¿s|e>���("@mˎ|Ox5Ol13]:F V#/7wN��\;o/ESd~H�P�@YYwG~p���@C]>sߟd/�P?לgvO 2VV��@3&vg6\kX� �;xÅ7VZ ���S얆W�"�(�{i_]kT���ΰܷ}WP�l �(o9V_Po i1���Tl F?x2OXP`�VKo>oXz%9GͿ_���JgGsOfI#s.Dq1�S$֯ob?���T S[ j�J6goKr_M#Oj���"v؏vc۔{W�C��Pn?}iK[j|���UX}iK[x�E�P�@\ΔcPѡ^ h*���Tmu5_O}:�;�JOڢVw$9ݯ?t:-��cЩ=|,�+(�P�@)+{ox.-���Lu=="@z!"�(�dLsZ{���=wtjHE�(�;gZo5kO?���1Qg~7k(LkP94Gr+/yge���@Ne4\O{^o&!5 �,oζ߂U{n,xZ ���X"w)z�)v3 ��f?J ȿki1��� ?v#_RuQ@�� =}ӗN7w-���WokQ@��յԢÛO$���m_x)luǢ��]ʼ;Ʋi1���`5�nh Q�# � �X`[yD���+ kI=oV- X3vEL]a46���îi|Q;?z?hᶀqƒJctǑ#�o:_w��� um}2OHxP�@�;XSI+FS���[31gŕ}1@�,= |i|���J5�k>NI>e^`(�P�rqbR\`ӻvZ ���X]vg;?P�rw:_cmL���l载U?VG0�YU.�l=NG�?q|L2!���X+&;OÿA-K Э]0�,+Kr6]wK���k\p|=_;S$3ӿ(� cO=62Wgb���OP>P��ds nN ���X_;z��B3?ص5T{���Ό{3>Z�T�P�?wQyݯ-?x,-���H 8w1߮0��ݩ ?+ƛi1���87kgPc&`W o7/y\,���χ_^qoxT TPyNO-f �CƲi1���D+�xK>h>-0mkQ�@`ѰIN;p ?���J<O7+�l H�MUgvK޷z ���(7}[Rʰ+�-E�U"eֹ`)}Pi1���L9;65? cQ��Pl[_k|h1���j� �Y@�MP]S|/���ԵwT� �YV D񁙿���(OvtpAMz_L�2mHr_E���8mʾ+�SS U5@W[߼Ȋ=����*{V۾Ϗμ)qh6{*8hwU?Gp󛃽Ǽ[���*Jw5\͏H`${@�geoKr6D���XvS^yo h*�E� �s^YV;pR ���Lv8vim]`W*Qk�T"�>'v$~���z[ݹ}iRަssz$!T F�Tv>n@yZ ���lƲZymg*s @�ew����fؑ7.S(�23[o���9g^]�w`��Eu5FZ ���.ƶ[{_iT�#P�@1֯oGjN3���Ɏ=_=W  Pن xqm;Ų���@53? kWh%@T�F�TV;pl5wbl���`m7*��R;c?uof��� IN|$-^0=[�JAU}Os���� IVkl懴"$FP�@齞[9[pD����Rǿ_I o �XoKo\NSi-����7YoJ)d (� �X?+cԟw;d0-��� #c{;xA�,H�y oV(z>��� g8 F/Q S�Z0QxqM먷Z����).U`��mG"vZ ���@>mlxߐ%) 8�^>iGk8���7\O}$3oL|J7 #�}oA'z9���� D~;pܯSp�(�`o;Yq4���e_|Ԝ!�s_,INW6��� 9zMP>~ 2] 5Ͼ9koy]/&����XQ[嵇[jZ�߽2UU�e=OHnzV'x���x m7gkjPQ�72 �geO2����1vKo7O4Yr (�`%{V0 ���@QCc |J[~Uj R�@eh;���|cskԂ(i,X>U+u?wV +o����X0b[-mϿ;pA?2XU�%-77ouj_@k���XMNm Zoxk> P�@Vsoug=M2O���XUkϼM$)π�9ŴӭP ���Puo Ȃ�9ύ�qd,���`ME<V;+TL�Cy7}8f���A<S��#CȖDm^Kk���X }lyJ4C m[@FP�@>2mgKr^n����KX涛-^ 5ǀ�gۿK_kp3���`=[onV- �Hve7?+B���Xb//xKE �R?߆߱ى_Kk���(vm/x- �(� "\5_'cl���@icOZ< �JS p-Iv+>v .8f���R ;엒)9ƛy3tߍ}?i#�JQU%9:Q=���P9f&8ʼ+\��YF)Ch:_3`4Z ���@I`7|l- `tRS�s[PJoI/Ox(���{ÛHjz*=ܙIATm-勗Zб4���Q]}(� �-E߉7]���P][_9�S�wl}u���:_wՏ/iz�w b䜵up{o{5���7:ع%ţ�0@E3i'‚Ɨ}9L����*8/s�M�5SJ|- M'F|M]/����#_SKBNd) (� �"}~m/cM4��� ;;s@e[%5uLk���(gCG}7�FP� 9uƶi"����ezlE^�Pmy9:k~1���`-� �U'7Yƶ[h"����~l}]Jg�ʖRʺcKRZ ���@%5w8sR;Q��*Q${>����le}r S�(XB<���P|/ vl +�ȡ Ds_mL���9l䖐ƧQG~[ai"ixBN象If~gL "n<o39My$͞@H�Vj],^7|]7q��C(3tYwT;bZ:6FQWo/)m=cΣCџG4w, M/oԁ')ț9=iz$�Ps#�6c;k���6KRwܧ"<tJkHtˤ>|XApip`s|m_+{뎯Q��Z&S7/9��.G.hGoL4b~K/V뎨?pPwA3=�$6&r #ҵʣeY 6#ux~I .$$pQxS��`u1)-!ڭ3sxJ`4/oz]@[b-�$Ozw_jW9dkF�}gٷ`/y���V'KR{̧=Q]ӆ|w-mQx{GxP=ǼXҿd)�xO`zRg/>r± ��P[3+:%T-;hV٧>:ċf''?b&NpS@�xJ`I[O{)��P?=?}Q]!yw-=Y<4{&xPBGRInZ ,2<_4ۏsi-��ٷO/=^߻K?.뎕d|3[ /$zGd_BM ڱ/z1m��Ugi{_LMѩ[0ԵG}E՟ұ.~T��ҕ;Îm���H'4Ӛ/WuOi %29^V @#W4>핝iE�#FP�({Uzg=_h"��;RG7]g.lӎޘ¾ajbOvԞi(�(�¿%ɊԘSt ��=|{Wvԫ#ZYO.95MDzjL(� �|ʸ_uoV̈́/��SNk֩&o$a�`ٱ|rR$lob>_sǍ4 ��45h{oLW*)Rjj'9(P|7Z]PR[W#� mԅ+F�l|G1��mtiOT?we^zL:7KR[qqE<`~$��V+ |��Tch1AΈ~Cٖ[=(z7HL~J)oRJ+Ǖw2lhvAk�j oMaGVol]1J;Y!�IV(zzso;Rw` 1+@_��UeQflm;40{ x z}L5&��Vzݡ /y*$kXZ ��Tj};1[Omҿ<K<Ygn$/e< 'ְkpu�6(R )+^rgLC ��{Ϥ1vivAmj i'Ep?~-\ uW�N �%s_\0Tũk; m4��6:==1mw k~3�NC۳֏&w'x�(�2G  cP��%iKC@;zc'xB~tu}594%�$c7=kQ�YkKz7 �kTXx̸_%�rA[Due_\X_QMu]4 )q!c% 2<#7Zi��(}QV.前U<=68&irdHi@|ĖE3=zd.��PL xฦdEayPm=LN{rmbK@ �%vm ;5MO��@G3'ijlD\j �54]?ݛY@`(h$幷].ˮ��@)aetNG!]�UF6j׵xZ8i�+S?��Aɤί1]S]ֻ�p`d)~ϓQbb\cka$�Cu�R �XF� VfBZ��`=z8u5eaK-d �^b@brBsUV[o|6-^Q�_ dG;��`M;%:͖BY-ƥS�fwjR)\(cQAw~�X Eȶ3cNJ��u藤!Q5敲:4`6}tIƦ* mg1rp�.S�[^.ci-��Z?=G).n7wyj eLj0?=n"&4gKLX+.ky&M��X>#ҕ1]Uȡ[n002.FF^�&دp},n<jI_Vu�P�F�!yC@k�bL=ֈӷg.lL=z`t~�okH)ZyCp(.~;g ��XaG#]1:#}qUJ`l+nrNcXu5:_>?<N;b�!/(XϩkyM��%阦vtQgTQ?6+GR^|o'<~jd4(*?ַ>͎Mr@B,lM2?07e:N �� 7=o[Cw]QO+dR'Q]i4**i-m/~Y> ``@N�X޽z`KG?��ˈrFG7 80*ד<#K<Oc( ѸhWKg4F�P�-#$V)p|4��TO/ueoLWU)U�gzEA #5*{_|wOZ.s:˯n"lz&C`_C?��SYuIOT;Zϼ-�Τ|/5iGC |JoOw?(�힞Є-5_M��@'!;-ctz[X;zc:#"?c##)[�هIF`)S71tHШH34l>si1,<훍?�:CR7O҇ݠ H0?ޒG]?kr&GhhT۶沌�3b@w+͎}֕4��1ΨQ똦 E&|hl&,u`g?vrdX)8ϺJҽ2Z�L�ZPԱM;h"��;Ҏ*`yl`Tk4݃|Jay jhxT;x;6u�"@L ez`E98VZ � }z}N}6]@gW?W4<*Z7Ӵpvf@�޿`瘫h��*䖰ҏBMO;p c`6P]ۮSe ��V<lrbuD��T~藤cEQ J �3;�X�Rr5sbb\c-ŀ7eb e^"@,%jɲc��s o 9z:}s{>wqєna6Y 瑉P q+|[K F�7Xr�g{2:=qWQ~:ɩI<Pmo۴]@h舓v\�>q]SX%:0;�,:(U ĔP^xG##Ggb@^8Sd]$c/��es ۺ}N}iud FeM#?DBs0( lh~'4 ҙzMoa?��%~N!11XC.�*Lhlf$eۼ@(K֞+$}K �̎�%/ǝWcbgL��O藤_;zc7Fz,IP^͹cbG;fuJ 0^4yze$^(c��MwF}s[;muߓ<\WML" o_K/ʔ& �K'flI�Ig{)!6=Q)&Reg?hxPV|-ݗJLnK*20�+/9��J.KRkاǴ7 =Q*48ɤ73K[+/j;_<iZ>|ءi3�i��r 7oK9f��(-юNn PI`Yo3 fzt V#C,Wׯj4-i�U誾�eހuQ!i#�va?S-uA]%1ضe}w�_c$19l`?{| jel.ue� �9-�}Ep4��k]ӕ}q24Fgo9}eɡRTrPp'G\0m:_gl1P��a跌maΈCqs`$Áiz=O gryr!^D 4\sx �~-�F�,lkE4 ��EYrHW̯uyomP?=LA'ZY#C<OP%{TE�wߒd7H94��EY2Hرt~WTWu|sG4(U]*uLK+_8 eGj5nO4� �ys%YM׿tYv �gcBzz\w;�,K0Fy(Qm?Nul H g1`n @i��;B.iG_Lxޏ -�O`W^Vb|L$"�JSs%D@ ` ;&�`eyñl M5:-,n� FT�Rw�(?W7)ر$=� �VqT$-7(6p�DȒk1l U#Kx?H|H&)({kkm~mWQ�U; �ٷ�BG|�@a#uqwT;:]tQݦ;8.+Ԥ&O`NSΓtoZc!@ � �6S���p#[Ҏ*`�I1%]O^ EC jd(TBn]ْ>�mG�^ V �@]j7}1G}4摁y27<,?MLibLEǺb'_V??ߣJL>M�siO;[Lh�Tsϒ'ѹQ=/[YY�?@Z('� M `QžU{5C`Ty 4=�TeRԎ.)єT�?ze�ɤƇ)H�%y弪)��8 m~;;�P5?Kۺ'7ըO�RTJf^2�6;`NC[ 1+S�r1j,�d?|ݛOeG9m��-ctz[X;z:#,?ceL1 ϹsgyCGlcXv7#2*] Ȍŀ@泫xg�@,iM/Btځzt`T,cyLz?wd ޒk6i?X!#ynRCP tn9Wϔy T͋�f` �*&gO3]Օqb[w�S�v><f/,y/E#TjqY`ѺӲ^v,XEES,_?�lּ04/_sfYFl^<�``3�SЙ3/_h_'1KXi|,E�1_pS95ɗv*v"@�!�iA[;⺲?8 UDI '46d")wɴ"ZjÊR>�W@D_~_Q'M q&c]y桟|kb;,�J’d:<�M_";tƈV-ד j|Mi4<>=RC`7:? -[xKƍ )kM88f|gJj �#�Hi&�K<TGz =Xd^yud{-"id2]%˞J &G' O1�T:�#Hkz[4i�(пD,]Վis )JGt`hϝH$546xy 55:"y:5ZvS5o8bݷߣ_`@(Б0�Pz?G7NhjG\wy^iF'Iw_c=DLiC,Xu39>e^��UR�X0_6�(/IaGbW{y?щ$ L�<?W.0X;>VO[Ti_4߿/h'rz��-/,#Єv u0+є7� Vb?p`XI }'0 @^}$�%QUjtiOLq5hF̘rFƧ5#j9Ζ�v�3!Wz<պZ^sғ{O2dAE_y ��XпD['qmg,dyҾCc{hLW>1RGcLk_l[2R?W<OHqiC �y 5�XI� Nn 06ԟk|*QiYF]M1XoE;"y29%FG رS}L)�F�:-hN �%|}1mJ{jᱢTm˨90WĔ&G),`G~=j(Td3uz\�%6:3+c:5,ؔvh2QRoK\AM#C;�=rS/ݤS'ЋS�fEu @m7q g�([zS.*K"Q k[qbyd`TqQ�wɄF(h|zOZ8` dٱXK�ޛ Z'+cA?ȤvQ2kzZrlV`W�̰%z_R){yl}3~Ʉ&Xrv,j)�¿3`r:��m#юꈰ 6&WG>u7dslcKe/㳇Dɿ)1}<oMjrdHH"�V{SY¿ R|屒&�Wn/m)bjXǵ\wu:(p@]MV<om0^K t0["gU�u5920 E�]zO~*]z&f�y]ܛ FtEuզbA?,dRO htbjվG}, U+�d)tQZ+ ~{46UbmNSU:�ռx<�;WэA])bV󤽇ƴ>sj iC]_E'3[|R,B7y~~~߯8yE牧-)`X݉pXU@9Gj}V0|<�3w>F냶.eA?xB;F4>Xﳡ. ?"#3][hϛE|oV +|,iVXF 9֮i%˵@Ū&bM3^U4�*yΈM݃?4Ǻ:RZmIWe 1)EBaY^PU>j]oe_ }"2-Vkۉ�P?_t|zZ\YuxtJ;5 [QWSL0!i-<:0I3;+3_`O/#P@$:KNh=8�ctAC%vĪ/2i+xV?"/~QQ9lE�p}7Ob@5m^ݱPX� K1MA">C OhׁQ%\wտϲWBikZ�78>:W]㣒B}~B ǎQ]�*~!jY`sj?�T`3m]׎t:&\<05~VOK\ Yk�a+q#9JI'Z ploww)-Tjd#'\t>�TE?Xҙ#FgYƓиvM2w^*%�\`bzAM�G);ww+"bEe q#BkG_\WTǜhɤ?Ě}hЧl'z4?}AZ<ON-/8I!/{WY �Vh{�ПY'}qDg<98}ǖ]L5a:Ѳn?2s*p0hVrrB<9&G)�L(>bmY{�(G7Nh iG_\t6Fzb`X~hPvXg'K^jf`"Ԥ$ON0@h+v܉Ie)�e$Y;^~��!˜氭5zڦڣ)Ay%VWkpT{7łjp�W+iL>F c]o JZ�H鰂=͝�J<g}?FgG/"b3ڡIєSF7Ԇ\C)Yt 0ʚ+L>?;&ϯWp 8Ԙ+_($1NiB''^ePqЩk9�J0%w'mS]@Woѥ=qlu7N!(+ OS�d\/{?+j?tx^gɄfFNRp]˱mA/(�npR 5[ĴF[ >X?z?QgcT5a?/D 1L&v�АLhj|T`"�揘`t�� nb��E:B~^#ѕ5:#"s*&\<0u1j+r8J̃Lh\[^f@8@MFRwIM e(@ ;uIe2 �1>w�XsOc>m}1mp<i1=4&[>ϲW(`󂔠dX$c%W1}|kTܤc#3E�A1KowUb�մ%;E� G/tAWTWu|KP&Ƨ3m[=-q|Rо�f(%D1[Դῐ'*S\WSc0}زMySk�d|kq*YئuIwLJy38cA稧%.MI=qe<2\??w_:,Bxcra{O5kʏ�7[,+a� aGƵ?8 M#L$|if˒62e/#:#~yJSvV8YU``Ej|?�V#9љ#&zzGglbEɀ_y3d,ب@ 8>KL 5�J_qKnꖱ; �sw6/{b vX%褞<0)]6PGctf8M{p$#/uGB-T45>&'eS΍%]ʽ`ER9z3G8�#~.kGSwEJzy`X&KiV)#7=[O)LOɲ*;a{!(T�Ʈke?�R跌tJkX;:3�מQ%ϵֆ”M�09%H�"@5q7l�+u EO%Yv8��(8u|W+zcш(y W;521U2][}T QF˔WCGdd2o7W�L_?W E*bGj*B' k�Y20 �@^?wW+ӎNn ʂ'i1=4V@U=:c F9zƦ\ɶ%c3_;%&xY> UΉv`@aM"@v9`y_m i{_\uedB;F4>(6F]M1Er �$}#O]l9ru#ў%r%k�Z_VI&zGph g$5]׎ 'G9<OjxI\e9\*k7{+ ~Ԥ<I6E=#G~YdR֗j5wIw�?S'Nюe?ؔ80d~.m%(/�Vg9tv ^7ῐs>5rw;5!yl?tT2_fɓ{Vj?Wi;^��߽kS]@;jtYOLuA?ByJvщ>[=qlE�ϧE[ĽRX' yhÿVY\ 1h(E5s=�PloqX_@&`뒞hk}FDY;8:'*%>u7Ge[J02ԮeB)\?E`}@�(>oC��gF:mfGT~M%]dI|_]M̧FIxpLXN. [:n"!y�OC9�%^"'\To]��*.^;8=5~mq5ʰh\{uKF_ !*eyh<, }@o+ ΞgRX09_[A:n")CUƮxqȯdJ)�zoI2? ytlb~KuǵFG7YAJjF'%36ĂjbUk~|3AaO 8+ϟx}I%W9"Ǟ7V�QE�Wl4N ��;g,#֎SsyӾCcYw\+-!5ׄy*�00z$WJ{ݤrA�'�*u oe?r ^#5q]_  32΁aML%Klk!ʈJjf `G\*/zMHfA7Y> Hj"@Ű=Z<\i��(r�g�|IΘvְX<OG504^籑:by*46J`a"??y\Wqف }{`:7ZYreE�g�M^tSH;ktQWLQ|O4CcSy`X[?e:c}}xJs^U"@ɦscEP�ZcFg� MaGƵFq0%\WFuxtV2n+gwj M* y cqco N��[}uMn .X<FgwDFE⏪082'~ :S|t{ ^124῜k)11!&_g8ӯj;< Pi�.>#@Ioij{_.+0pFƦl9GC#9\@ZPGwKzT@Ok[:9/!}5 А' i1y^y<T u7d*ÓI:<!Yv~a_]?6&@@&㱂 :?R ��`/F<tZ[DO?VgGxdR; kl"Q6?s$SWcLsr΃{lO_ɉ �e d*�` _K10S��M/iw_ktyo\M!*Aq=O{iᱲJu4FXJ=ox&d�}"@YD!zp~X<cB L/D.iG_n ѐZ :0ɩdY6GE^gr_*<R}Ņ|Ň&kev͗1HNMȒ_EgB<E�K0w8>8c�&7ְΘ6+z=8ቲٛjBj "V{`f@8i>^"pg3B/{X~_QȓNMJɲW}=aŽ;1jȜL(>3"c";FR{̯+jtE_\">UvQ"ZQc,ȋX&p`LMxX@?_<r(nGϸ~1ePn `,�`MCg邮!Ƿ& HJ$]<0ã! a`D כ]ijzc#x-Ĕ$OÁR=~Oi/qE,Xѻ�u6rX^?H'4Fw%0v*hIu4IǴ_Yn%W]/$G"u*t+J[pa � .+ҟvtZm=! WO kd|<;Qws\�t1C?_["<\ӯb)DX1Ylg2+hkEoo?P`{GЇ({@U`�0gV_�y<>gՎZ[i}hOwxLʳ -uZ7߰g2�B1[/V# LwȬ dR7)GXȆ\9J^X ��+a~ĖƠҞ 2Lj&e;|zZٜXȕྑԸtOܤSJ d"%|Vf��U~O|u!GueX� <O{jxYϼ u7ャJJ3[yY � /` ? Y&V1qϵ$W?_pQ(s:Wg43�_ُp,3#_37F0ih|JF4H 9,;Wld+x]=e$@#m_koVS�(v_[h1YVп9_FUd/`)݃(%)*D@ff$L_4#gyloE6Bm;3>v&秅7pU3ϒx֥qmֆ ȤQ"R !*XN',�MhmclzTP�:͝�JoYFFgwDˊ!R&v hhl"~ƚ6Ԇ9�0G'<ϣAU}*-̾8v�P5~}q5j`h\u+#,o 1gǦ�̙"f @`5Ta˽�i[ �@ԏ-]׎DGX)WO kt"Q!(;{ �d?"/c;FfxIҳ'�s`Ͳf[Oe~cNSYY& yҾcwhl!^�X� s kS.Ԕ,"Z3y�S�J0.t2=--yFWŵ!1Hh&;ٖQgsL�SP-�ݛ%2W`.k{=xR)sRI,J |]Mίʝ)Xk/^\眐k(1(п|.iG_o 6?`Y=Fu`h~/ǶSgs} �Y�ta}+SBfYwB<7Ey3y~=wjJ㓱(z7*.�>4oJ]vΘ>n@1 MinE^~VwsL~kDB;K'J'~+$(MR+"LXpf:ZC 5}5W{OcEH=8t|.�8s҇{� ְjuvm/yp2/@O`320mg6Bi�*&/it^gL:5"( UҭqO]MQ\8B �ޝfϕ D].ls#y3C&-i%lb߱(/I4OQW\Q?m&v hx|"Xȯƈ,VF<zO'_/$dْ)ۦ&I+eypfZJ {yrtY_vը!j<i`x\{*FYkc}Td�wx4%7{b-LWunWbfkα^YW"lP&<�7*�ewXgnX�X,;_vDbdžXP¼(G$ْc~( 1'Ũ~-k}F,T&2,�E5+Mܙ@`U2>+pJ0ϦTK{j0XMi1?<.GǵԆ񂣨7"דdiɧzY+Op�ʝ;U.�sonpV+  ~[uE_6iH` L$&{M>h/�OY�<O/?U"@1P�`^@K��E>fo,td a]uD`M$]OjpxOƨ!0`u?3?/YdPmhGz%M^?Sqr]yT�gyY˶S[�f1k9\"<S7F_+ј:<:'G4t+,ƘA:XaX9BwT^Q~":*k-+Kez>= ZؾG+omΘvur+ km*:<6Y1RWSL!?]a$<O~/'?<W^"!C`=+3-WJ /S ~藤- Ae5YX'਒nO9 8\o;0'Y_v0\y)Y#2W�peNe2NUkf{Zױ^&v hdb*~߀VwsL>)דK`ysɒ;RYP1U A;-maA?`yhLcېQwsL6 K�d?zvs UKٲ⪠X�:}M/u9qLk==]WÂ~@ LjׁO&wlȢӊ,�gRS^rg.e 2¿W`YϑW_F~_[n 뭴s~'Ʉ,ۦm Lzx|,Z6 LU#Im<RO</aA?D=t`h\^ոk"~Gbm7IɒV^OݗĽX?Y?Sb|x'7P@׶]Z"J`$-a&BFPRy`DSdU긭c=>8ቄd;7Ko}}?,ϓ""ҁ-u'��Jޙ;_&}Azkt %'zzrpDG&wo 9 w=uӂ K%{z:�EtH �TW_~KFmhGJYЏ3qhtRO*two!+�iῈO_?"ɒIU#�LD9LQ񡿀Kjou %m*jਆǪ奔1Ɔj~s`xfl!&K4Pdh �gC1b~/s:c!".@Cr껑Yƨ)h@Jz5]02m[?h Z?}R4! KY03-|]/l[ưԢ/]*R�XLzu6rbCθ_WQ}5C )WOTUeWos0`ipTIs), ,e, ,/?Ǘ)֓EEdR #қn:_,Tw:0)*bSYFt#juR~�%ʓcU߂c)ZpϞ!))G'WA@/,Kb$@)�X'~AGO~bt2'516lu5shtܿgxqT#+,f �hY-),FŞAIy~|x=t`h!wmDi^�0G 2Η&Z*a o;Auf֕i ujOr3<6'G4p"A:2K'ݿoXǩe/KZ*zkM?*loY_~u¿5[o|V/ E�fru*%7i9�@نEuʻNlu:3&>3P^"jQjo4Jң#r%  >ϴ_XTE�Z%lmD8}c]N}u (CF&{pDIiuрZ뙲uޡ!O?W\dܩN$,J`�f (gqqdU! ]F52SIj\ =�SCN?O'WBP$7YE�+K˰ :Wd|¿2ְAo@y$8<Ʀ;LPk]X 77ӓ2d%/]f9냙? J�E/Le\?Ma~To;ݗIrm5�U8#�*�%rߒѹ]1xT5AdRht2Ac7GU(y CGfKPxYOM^߬Oܗur2QI>_lWb 6&U9.AJ`*c;F[s53N(giߡqYƨ1hGc,a) �h �?Ὶ=s`lgv[ �(Yz֖z]^ !DBF4H3l˨)pk}{CYf$+<T 4.۲"P϶uz1M 2S(wiQ1RlK]MqXV�;4}634O?{dRƮ~e ȧ%?RJ<GQ԰P ƦM%]#E!ܻgX5�hO�?UQT @1�%1gnԙQ�ɤ]:<~B>G]Q6g*ꡁѬ?Gz*)w4:/a �JI6FX� '਒.|hfRGF5t%kkl�$ ?K&0u/0*^+R�Xi< T2#Θ^qR `r*]F521Ecd1 w)�e?S¿W.!WR? ¿W_쬐~Vs|"%w)��| >37A!ZP{O:04}䲷_FuрZ#Ku\&Lq+z&7E}\aOE ŎDE|9Rr'�EtQdRF4>1hR!PC3wb�O" z\qk�ȲXm o[z:9DʔywpLCZjjiTɤGƖy*`1�(uAҶtg,PFzr`D$ZQM-MQ97DҕlO'/}*@d%AHS;b9<LO9XhccT;ܷwX1 ֑2Z~'y/Z?s%2 J+^~$#ϛ`�wx ,!"-rxtRG!+2h)` *�0Y*<߬4a/^O|1je QAEϡɁ" ="DՓ#ck%; Φ~@EdҞx.# +E馤Lg��-al({hTIv)~VWST> i"ꑁyY/$n"n-C-㢑XfE_c,^["5[ydʽP?<#�^ѱMa369ԮQN?Φ|Ecb=D M<=p")S~Uy_Ϣ𿲯Q_gI'O��)mqJIi^)1*uMP}x>4Wz  (��+X آ v hb*Ac)icCT2T Oc% P�P;"ˊb@ <=44Fj#~m]3ƽ{Y< .  ,xnEu&*q�;GP2ǧQM%4FAօiTѩ{_F'Y<DלTQ�Дix*IW{$Q暰AU徽Cr=ie-�H'=Wư,<^F)W{FĨY`}ރJ.Q`_nC]D�s e9[ W."@Yϊ{d%#ְV&ӱ!. X/݃gkF|4�0$yfmG`W]zx׻T1{+y&?xˈ&(j LGg\5 */+"LUҵӊW(thZ:04G&/ou6dj/4p.+;uɿYV h/A?_tz%5/YIF�>'=t`\t7Xe[M߲nΦ~@ԓCO'jO-tț7^uGa{Gt1% ڹMoCeٖ =|s Ja-e @i]F`͍'\!K0X aa?<ƶpluZ} O'W=ϝ}*kJ[\wXOcjX (G&hu4d["% {fF Ȅ?<o?(WJ�`} O} jS]@ؔvak}t3,iɌ ;^ 7Xo[[o e Ao}�HXQSzY>g21­ cC:<A`+t?D_xدHM,,YoFy_o^œ5 :|UBҦ�` �ևI_`@AGkc/@Lh਒L_hPuaHsϞ^jg7`Cye\os|e*,}(>l o#ܓ:-^�X4su5z}r,]WO~2�d"F521EcAS<ƚ d*�>8L9+H6֕>6.'#Uӓ"+:�^9J*�,~ڦi,3G ]Qc~r׾cry_~Vs]D�d-�g[I'K0uMNTB/vLdl|MskC)tܷ_5nT_m@H�`dROT(F?HmQl?l;�xy|& S�E=2|db<G>Uu2/@se]#z?=&<\ozk"fn:` dҕ9 >yS̽<siI)�ySbz=t킪4:ЮM%4FgS\Mc�Kwϐ=9YXX�*hKL�JӋ낺F]W;%]O{Guht(MܶSG-�A'K7^KLg&�+I��d9z_}?\-Q}sxlJ*4F[MQKl wکن)K/孬s "#CPE�ۖIN_CLKۣ/nb]_uDwM%][[﨣1*-F O&1?_>E�o|t(7(b@�l�wt0BPU�yOKN=F]yRwIu׆ݵA eepxB{j7FeP{v$͞;R+RVR=\EE?)+zV9{YW:o�O`�ʠ`K+㥌=4>vpLN}W;uDCXzm7JTRG5:!ic}tM=*o< DzbbR_I)'y|;UÓ2 R0'yanq(XeIIw2ziI)<40F_>mQ]^֩9–_(ٓƵи:j#ևY'X�4/d?&1s(C,N�r͵$y`4em^H;=#ݳwXg! u~_jmu16ԓ#bk&DC�+3 �fD tQ)�vQ`_Q�@T�%rdI;WM]N<ɕ_:_:wO:= unO~s=O0[ڰȟkplJU?RLJ˝l; OnrXv?1<ygMob@Σ^Jz~CNх}:VAǢQt#c dVQ<gRBssJ?OO&d-$R�X"@dbX�A<{3brݛt'<cZ]W;k)`AӞQ1V%ƈ"!w^Ʈ{}<#+{6fe9*E,!uJ_KHH,,z!^\:dLM I P)(�3S ŀDR?xd@?xd@1sztQNifq )SUe[FM1LwO'36qG^1Ku`߬'U'J53ɿY%>h D{Vt>a-u*-%CDTr1@;S�ȿ04wܧ>OuAE :-& ݃1Vl[h*:@L$\=tz : :b 4^8gQ#�%'5 _@)2))}㾽}{z]^[l1&И\Y-ϱ{5tYN_?_-?KrdR �T$;&b7ޑI}V[,tQ65i*69ԓF561@悔)"u:wP�_' U%yCZ<]�9K�NpCU<k&E ŀ]C7O<ڠ.ԠUxLzM:2oCZO+-�X ӝv,#if7%nJ W11}vSSG4uz]W G^&}`DSIc>mls`a`(TO'Wc\eɝe �^ʫ#qCuL?gztl1 衁Q}O訖.o}uj iJ8^\O{ipxXCZ"4F'<˖ǂ?rg)�qa`w$Wy UB `l|sc1$r%J(PB9զٜsC TwWuWtJ33syiny�1i+uq3 dZ1#g,w_Ze.Liگ>ir1Pcvfv)6:ֆG#@lV_l^5v��J)Q 0R&ϑGDgopN~1y'ɻ_JzɲfMm;22h3XV-@ �`G#;]X�A1�gs=�\<3@!r -ϜJZ,[Ӳ yW;_-o9w,ꢪy1ITTlZDXTNZJ @><'du R*je?W:M|^/VWA2#o5AUX^k/"bќ6 ڈ;-Qd@SH FNѮ%㇧S+$o9wUgZ5&f;2<H~PJz,[-�)�/2Q\/&"Z;UDhU^է̝"Y[Lt^QڱfT{5? �%Pe˃'Kv VZ.!} 'd|:&NPreOJtDA?ߵ~rm[^Bc�Qx9&��52ҭ%0}cr19yq]W\  Ӗ7&2SIo7u1�͎ٔhG#a"�((>VK pET`LLe׈ܲkDN]#8.cLer6Ny ٧,.?@#68qC[`? /"2hΖ5- fh31̀HRn>"7l3/wpUx[2<i3 '$Y&!^�4WZ#Ŀ׌{u�Vl�xu�qbt"2J%:]JRJB3rO ̓rA9 V_JYA˴ ؎LG FY%gY&!:�b&aɱh#Ja 7K/[[ ֺjᯧ)om}NU oZ j95& /3" 2lj sY,2_Rm<3 ő\1&WlY"`Ur^ۃ)#Qlh wV/"� elꖓs- Xdh:j_U.o NtvD�$ ÚP3@)jOGD0t yiwYB?uvdx**s1X#^*HƳ}hN?S":D7W%_Wsb.9HC/-pf'=t3o6^ZD5rl@3U%ZltE;FdHX~挓/\-o?dYZD2:!ݩѬZ+n@ep&}H nP 8_5@_sC/-b[FĿ_˦S@Αؿax{; @*&0o6DlM:I}jy'˲lJ4NkfeEhl-k$S�rG#;Q$zsD�47̉D0r+hv3 T_3 8=>#=!Cp y_)ɹLѨFDUKe(�MšDM[B#ph`߬� @7Hջ  PD+-CN2u6[MIҶ#Sđ)Y7?g$=-ܒ-n2<D]Jl4ۇDN5GZZ�¿%%@ fb�E�nݤ h<3@1ӒNC'dyOHUםBB-rrDL\&0z,b0��T/"ĈpnY%NƆENNf@ HHp3`.i{crq篖W\5. dd**esB4M3dșkɢФl:G#;]D4c�8@ @ϝXx%@;zY3;(mLLuרܺkTN]#<SdMGe&of!Y&]Y�)g⢍Pꜰ?'-mSbA4Vоf@ab)[:[@Ph$!oʻ/X-`\1-fIe;MDOwH>etV2G#�)eS7Ȟ$ìt�QEh Jl\<(WoN^,JXTtJ8n6{5$lRWr%d_c3)YQeĿ5xUUg'K��4`ݯ}ɣt0̀#SQbcL//^TUWzڧ# X6Ku,C�Y^+:/4Xgob?8#Nәl("UdPNc u-9㢵ψ2z�:/(LEmosT"sry,k=i2<X"IKzUK�-$8*42wus׽?vC7d<""qI3}I>sLsNm� j�hrt2>@+@D6JheMwÈLdN"ۇgeg,?`y'ˊESE&qfӿ)YWN[�h!E$f9 :?gf&u2n�6pm}61��=Y(QJ,x#JP&9rǑY?}T~ V[=Y/>IK4oV֬X,/b �Zl[кw??Ț1;!iQ =a,;"�x,E2D+~PjH`OZgOɳǧː7Ru*y'Ko(U5Zfb2N4)JDN=y\J@k�s❞[^ QikUW*2G@**:Kky/U&ӗK o$S]�,TJl7+ׂ2QnIˑǏL'eqwHW&Seb..EkJU @uh&-jUu2 L/<WSW&U7� </m.@6"~n�PBEhCt 9FUR̴嚭CSI=] pb(%gY&Kzy�ZhRfPBvw{rX�h& ytDZEywF� Pd�i-(NEoZkAmFo(9elM-߁Wb)U WEr5_jHtMC_D$qdv"6z$ݟ;"-n�Fh#$:mtmٕGn3MHW!眺KfV;^@Bm"_ڢ_j>4mWk+aF.ox;4Z#6C6f@Hb."c3o&zCr)˥+`�0&ߢG#Kİ1%oSYڨ`;�~.yt2J�� �;m GZ˶$, 9.`�Ȯ2<u d/".М�M oV@,|#�t"R# m]"nFHDi�M’.9{r )`� #֜F#8qN/oS�ehq#PNxJRQJFr4@/,VlMZ!1`v[GJb� ^F@F�up R)3 nk1eereh?v [# [�P|0G�D4oKU7B@#X}"9mA'[3�~Wֈ?giG~�gĶf$E#n�L V,�}#a>M h8e9yY|О2N!$]G#y8L|S*&/-�1�(Uޛy8QcIV �ڙKزo2&/Y1J~R9iIȢ zόPJ@( TySWK _7l%+)x-IG�csM@h* @ .76�`� zT]eZܻ2| ;;҅(_gF1��NZ�5%d(9etQ|*5#Ŀ?iv1�Ŀߎhkn�Pɨ`rJwYrt& Հ!g\ :p?у>mZ�O7$?tÉo7|3�J-R#rEGYkIw`0�:}ca1mGt97_U5P +X눔q=LUzk@/Q_3N|#�nn+%?F'VrS�3P, ٧ :ó"F^E/*oT藽l}g"�ڽ@{Ü�P);9�2I2HU`Ioft3�sHR!ȮCR'F@&q�ƒ0 dn9k21 ;�Ȏ"bGG#>�ZNi09�73y 1> `9s-h$)"*guDR!Ǟ/-�۪ `;J.ֲ<}�uoZ)1q[vueE @uhVR\@([D<qW}8-,u'yd>o,odD'GE; Nq�hP.X�64[W.[@Wv-IF[7?)�M&K'b:H^i�՛H(kyvƪjy/�uh6=3J̽?|j':kJa[ {ꎴܨED;EK_ʩ�S=1,}^*Kuө�$D4US%-+?'Eȥj�[:0o=5�9QR!#y4̠!d(9etQ7�""sxVE"�94^<-O�l+@}�PY? o!g\P(�ٚnS_6G#<{[/ ЮE�B7ёn/Z�P)$mAʣ+$gLzC �-7j?iڊ>}~/c<R33ԉh8a�%;c{-eLv$dB?�ȎṴ*GCQᘈRE�X+#1"3{C��И)jWu44fqOz �(XXbIG]�KWAjK|Wƥn_}{C&8]NiS� GjlÍ]-ꖳ ói#?Fꐝ?g?;=1SJU@�x,0Gy8~،QJD2ddT^zIg|-'/N^>wW� [g5T*Q\?rIشG]v5�N8G.{?0q@&*Z-l wȊ�bd#�qZ�T3oXeZ@W)o-KzArٯ}Aϯ,kcG"O^DrũІ&ˉKNzg>�4Hχ*%}sW*MGې3V-�_F KFlZWU|k )f’*�(Ҟ5��㈈'ws@]'$]8CiD%Gg9. ˖Gd1q(��>lSK#(� �W!? GOX:�l w8ض-""'乽�Py/G`ޙ'۾�`;�# :ķ=�7EV4MÝi�X<Ks{N ��ó2G#Mo�m)�3rB;b}h31̩�u %d(AGdǑQm�$fڲ<#tG2>�og _{F8]�ʝai�c~mp2,p,ɉGk\:(ȴ;#gjs?s;K��?5) Csꘑ(~yk5'q�t(ۇfSRj^k=?.h^�SE@{ FKU!ݓ'�J-H�c$m�ujVZ$d*nuXp]eR�:-CS^bMW>Vm>_3Qbj ylg~ґmE�wwёG+�FXZw:iMEd&8g�:[�C##_G#�b& pt<b9љ\�И)*3eΩih”,@p`,,Qӑ#6]Nt֔aD�*Z�V.�Ĥ}JɞɘDvGUAH91.[e�dtI:l`zd�ض{ �GD-\�JHGͶSڽ2!%�:8__G#V) o;3�h_`Dfג7(�?M)g-%}sۿe/,!dþyᙫVpJ!ۆDH^Kb�U?*XʫbPD/Ap]UX/.5rs,}R`l+=@ ̎'n�=kE%EObZ6'@14ѰYS sEzڿ^{{ѣQ20�i��xZlk4�LT闩ps M:4*/6Go{/)�^&W;@߻}K�:Q#^ N_޾7D8ēl?(<owJal/%kyTw_/p{*⟸f#N"Я`s;ص;DkK�e(%[FY�Sp}E LJ߁!1MR�Z�ͭuUԑXڊ<vV)�pĿ~@)��ߋ 4�%aӑ=`ڵN=29DhAff?CN1��V. �h!Y}[:7$L[6CCSE @}hV GC)`ftsZ&Aߛp|&. �h TzlkۻB[�)t`H"lMυytQG#خ 1`�hw"_U�OT Z2d2njo9-p09glhch)G#!<,_6mc�`܉�昵Tvih-LMӑध�́e;sdB#TGS O @,K�Mگhqtдl?$qR�ݣaI z#(g11 ;c�vuڷ@`Ʈ_q.� :� %y{ϭW1qynwSK8L?\�3Q;ʽB.Ǣſ*mzM⿥H}nxwTH��9i�W{K�5QH�EdCX ´rpXOCJ�@�sc;MD9;V]cD_K{�LXi��<W: o �q%$%�8�U^@屲C#PjN$ULwL'�^߳5|d �4fҹk۴Qrx&!f&"@92/G# #pO=F?m9.�hohQU* ��,-[8�4-3BW&ѱwoIWhg@b#:=̵�MC:`Pm{7l7(фPgR?r?eDHbc,#̀N�(Z sӣ�@f)4Q '3-i8Ms{ex*y PW`&[SΕ(؉G#!CZו(<[>u%�T#: h-#ay+Zmk~xT&%g`!PK2< \q+u5ń႞_ן,:M&ki[Mk<�ա_K@[Dm]?Ze�M(%Zlkyi{s2Iȫ^p,]y P# ̦Sk6>ETo=s`uR嵘r:eVUS.G4<p<qU.�~ N:\5�И*/ Qs," i?/z ±/s�2�{N &j_UU^PڊZ8aP脧Et_Gtr�b�G@T>lֲ}Yv1mGvGGŦK�@:85<WqGCg0у#ޑ�8uw<+�@j(JD+CDljv_9Y_±$',@rd*2<+͈?2k'' ^5:NHl{tT#z�A7BC@)Jɖᰴre'e~|[fEOs58]'%XЦ&@�=~i.�hT-`rdxXve1~d.Fep6[t~նЂ?/ة)� &>,�4j}@g#�Z= k�1<9' \�J:8/G#O?+�#=Lr @sJDM\)&n_(e˞b GC5<r+c�^�mgzn��РJ{Jt–C6׎=eR�`\Am?ApGwtN6��a�8Oq@&GRi�Zlj4� Q<2vL4 mnGCP̣۟vb�)#䶊^5#VJ!Jٲi8,S[ls&Mٰo@^VpaLQf@kU5G{v@7{F)�R#"=tҞ �4hҞ $lk0wZXh?.[ iٜ�Zt d j<k'OGuI)1�:(`c$�4j]%�l3G#ܞ~$<o>|EG#c;p8[�Pp; s8Q �bR)SJíucx% ic�ۆR?q/}NwI�1�<ſP̙_P@cf4BYrd&.ce$MR�DD κ淀JGC_gO{??� sҘw?e� On4�x""2>u{ej.N7GGZ:�oG#^s|]�  `}ʵ/KBː@sJ(4`UKn�7-t`@?}w2:QY"F(ƒ],=ſ^:ga$\VqgnbqҚ0T�И)*xZD) o2"I[N 5_;hC2+=Ez-Cguh&ФNE(Sy<WjzlE{*ݪ:NHyQ8s V�~�<;X�� +.CQX21u{db.FT8GG[*k[(W%?O;k`c��z^�#W?#1.�hiKgȦa V'aYb:-3>n$r#W=#u܄� �F$q]3NxaMQO3mHeS>;|xxRJ"i1 ֌24e5P?t�Nx<kƭG~7`�~i�)�"b['<i�c(eE5ޠLd~2жl9q9?̫_�m�!{)f8"D8 �>Ii˝�PI˖͇d r϶ٔWE':?8N<rߥOi7?@F|9gn|-�ܟΧlĻ&@E%NJ�[fEK(?;5r]??PG@A@ e�Md(%QӖ㑦}65�*f:{el:`@[0dN#yxk(E�9m %�wYq0�u<�he(%8 r�X"Ƕea?!�hq J&`:?t5'�50rBJѸ=5$�M1[�6e8֪ptdZ~.1вlI[n!WGCG'tBP %� ${QR%'r<zǰܼcHuZ̓3e`3�N%w]y �(2r?ۢ(�uĕj(J5e7�"\uPN %}v{%B Mڲo,PuGCgHm_.~/9&-"65v⑮3N#�h#8%/? �"�xԔ d,Xߴ_wr% 4=ۇgrDrV�/&d5wꪨD�@5i�&IJ! ��UJFI96h2{p˥\?_2i7:'n<ggk:?U$}&__M=n⿮ Gmc�Tu^s@#.�$=ZDIlS^Z�VƎrD֩%{h$,;[8)lI]g~ӈ?@ȁһc�Tlx�"b[C`�-mq:6)�̉a爘3/u*CzҒ_<}{|7dA#i;k4\C#Dk2 �G~P9�h\il\F#fӼR E98!H {)/8*})3 u7僿 wd@9<'I[#(~tCx O]Dʺd@r gv|qҚ7r:@I{6 .X"ṙB}S+NТ$k쒾S򹷿D`h8[f\sk27uRBU}a <(E\-+83cϘ6OJ�gQ�1T�JMv2 |u&Ӗ_l]8{)?8HGHZ]Y/ 24�ɯ vK(B<Ŀ-�Xkla /rOۚf�ߵVJOd6 %LLoPrvC3?uJ 7R&Ɉ| r8}x6-:=?̱;'L [�Dk~3aeQJ#Q�lB.hS;_fQ!q Zg+e$m[nvH8i_ZG3?@zbC:5{?�`�Tt �4Ǥ. 'J6 5�h\e0%ȵ[+ц/ߨҏeT|hϰ|ot7�ʖ٬AD#,oGWw Jqxؓ݌�4 *DvG=h�8܇X70+9,߶yJ1~ yB_(c~%-(rb**aܼo�Ξ)Egwƞ娇'`䆖Oտ^ �=�D!ehc�H(hR7&N&_\F&G{s9F&@r&-G~>\nPy@l+|9�sh'={/!5N�Y3 r.�h$.nc�, Zn5*OJ WVKi)!ٔB'Hvxݵe0)P;Me.agĢ]?I*�>'f�?Kn44 ߤ,[~uHK/D\�*9rST* D#7nru`&;'eN]Gx<0'T�hZ̑{ �:Y1iqh(SqK.#SmĨ|)[|)m~e;'g*31 JL<̍@#{v�  =^OGgw1r�P__xj~qx!m,`8) h$)S(sO]V-*0oh̡qցi.,$"[g_YLGCG'S �Uy�汝w0\�)K�(%[҈,N?0RmTJDWM�E”Ṹ|췛嚍DŽ X(&23GMZW� GmQM �;[yZ$m7mйÒjܣ_U拾)v`;\m[d:䚂285#S*�ۚ?Ac�T�X`;@#m988:p.UJuH4\n�EÓr˞QN/:nſCq?aΏt$2_IuG'o NLj<8:U.qYͫ` :j!?jZcף?sTF8~A{r0XR?�5 |#~N�4zҙ4�Q׀v>l-r˞1yhn؞/{CO-+Uw~G)qP6*![ mol_;"%@2> O?,p_{[~?v1�G(��@v٧hRN+;( n9r!59^qF(5LD@HE8ڑOܺE&H e4L2t.c^:K�hگ`N*uh �uqv- i�0̓r`2Z 57@KF|J7^/MrAI#�A!C.@WC%oS'g5 �Y�ihVKN6;�'C27e^lT6g_wRJ~l0++Yh\>uן+} $Ă|<0=2m'E1?,_ݰ`kUƲ?Z n۾Gc�^3zT1t,u>̩� Mp$)/oZ\cX](35%ſ -2�9F@5r$K"cM�G;uGdK|O^..[lɦd{w.G|8ڮB^﫺sP;h?R =xN '�)� _Y}D; �61,n�u*h;- WۆR_gC7 e@I}je20%sy80NHLeRXUCR ]8W&r3A["b6M0r�drpllǏNͻGvtnΎs_aJM(�+[@gL,)s䩃mo@p̤ՏGC'aO >b4)�hta1< �:]͇ttܪ8mהX;"OeζRXM# U %Z]P# tT>rfd8:gMj?iT\&�0�?}A'<p@&'4TNw_XV{�Lڎ\cHΊY&`7ozF F@KΡik7Ƹ;Ru@#mq"OxP#1�O4�(�`-+ `c�,G\Җ+ މhW6Ŀ ne|JLLJ\”ݽC.zbGtB#I96}(,恍~c�4ٚtwf|MUi�ǣ5k+̬6&Ҿ~韋{?(L Ph[b&%-'n`y0U�(͘ox� @ɋp>ϏLZw@n^i�ifi-[Fj`љ\7(q5ķw/}n<NbK&_#�^Xwߦd2;?Ww赶wc*�hDR�lmiv aՖAZw?U_?ңU`HҒ/ܻC>IZt=U) c#wo j"bÇ.�h`HD5ڥwi3'f]#bj'c ,hZ]M�h�WW2csێǛd rd27?sR 8<l {[D;�uR;1ۑc[4_k{˽Ƴmt6%P8* ?6'&y`e?f>*�qJ4�KDև.�ϙi�d`m�XN[gÖ6Ŀtw�nw}LJ"QӒ/?KYHHJ? gjև0�I-F@|m9E~~\uPvE?s*gS w wɇnC.6"/)G#Dǟz?@sRP CZ �3MZ2eDW+�LLo@=6-%?%@O rx",pSܵ{Hu:c87�gv|]vk@Kᅳ{~S\NO&o[DĖ_ֿ}# �~az~cNtjIqhN^fiUe}l\>,ᤕLпJW(�ui1 WR∑Ў hZIܴ핍ǧ x,qݶ([gR3Yk׹V_,K_zu" GP;_%"vKv �1!>�7 V}q 5�vG-):o_ZEλKru Yn71:*}_]⿮ ^k=oHRX_e]' `}�@f?%#1yU{fOX;0#'x9GUٽ`Q^ϬЕkyZV0 ZH)ZTD>|&[_(3P[-miW/z)}>\ ב&ݺHD�TU{Z0 :DnZf�j?�Wjm44[_׬E�hB7/'t]|PqovR@UR dZm-c H.l’'VSv'%I<r4r "D�=k�m7}S�?(88'Uym .눥ܼ{D@y&_e+ Ŀ*!1~@ϭ?Tt�f>:bRZDg3<T[{\MVW=ι7 uG9n2񏬂:br8%+C!^�kX~mG=�7D)KP8Q wc-2-UZſ@WMmTÕ>{"ptł e D7ɍ[Y69grZA=7zLF~$w?? @�c�zۯ@dױp: �j?+ϕTi.`*fʥ}rx:V L;) VIۑ>_>wMX\j� D XvޠcDO?@kW +rVry2/R�D6T@3%2IĿΊK1Jj2<4.wFQppjڈ?JGnzx?�-+ {qfD�@]D)TLbIɯSaQmdT21|T?A7B秇g# RRo\L巛M'j" ΈW?@!ֱ78#1)w?`�Q�~�'ܭ8�u�(84�i={\mH-Gu�Ŀ<Ժ0 7% J333wn阹@ˊǦi?X~Xɉo}nG ?? k8#Qj�@'eV"a�40@Ç'ֽbk'`͟.h~\몬 aJGzWZkyȄo6ցx>z#-u| HG{@kQF@wi+9@EJLD%`k-GN_vo/b"BRoX8.zj1ql’#DD[-߹+g^�L �vFJPѲyxaQ�S�qˑ IЬKHon6Fw�qܱM&I:up&q#�iot&"G_w|߽S[I �5AL� Il-η%mQlULߜCT@hYlR>x&#grhU Sdo!vN�h]i�^Fg7�ɡ}|7Q��Psџ(*UphD;8p8)55ſKu ȉpEOD۷뎈.li~+;hycnrk1�`]* wm��5)1i:e&%+&O4|<o0)$'s6·|vn?@ 0 <��P_O@͛�,^�mCm VZ o�sբe) eqRjEN?^uC.kkpGdݱM߼] �j5!4mMwjo�<vtJ~gdwI^&rSEd*OݱM.yؚ5e-'l!ж 31c�t&(�GG�fbk=�Y{,Uv'}c�5Lvm{Gٴ6'r:?йlAJ@^4Ml8~<83x- ux5HT>ЎKn u"z!ZMFyAn`VNPIۑ_o CwGy %@GhѲmpZ>tC<sx M98D#؉_O:>}JO}-B~8BX2wU=zDž�PIPgZkQJD %h-xفήA pҖ \<؝+@gx~J=X}O\X ^}m`WFhGkGDb#uVi%lݳo| B*e8ZAEUZl_*氶[yHyW:N�v5^|�3d�Pdvgu]p4K6(q-Fy^B_BZ>Hw ȽɦRYh'#"TʦTU:&k+xA�9M|Cy��}*S«@~GfrcW2z�PuFN/*3twv#1tSLB2-ݏmh|h=2+&yAlNG#HnJqlSwݺ)� 2_In@e.yٛe'S�̛ !"Jֲw<"f<PaylS8:Л\.܋F/`1ʎ/.q[@~QD뜔e#Z(1Ju- Zd.n%wΔO|7)&"*[pC{*%+iG.Aɪv�ruL++H�&7jy)Q�""lpx?J�Ow%6.kU x sBGdny]Qn߼YNLytf҆͵ѨqIlE?�.X̌e�v&@ci$d:aʵ>B#�k{`lޔG#] qTȕs}L %<0]@#SpfǞ=tv V/� &&( (ԍW6#�5&&JJD+yȸ?1Y\sH֞K6_ !H^@w$ QiIvw/ aZo`:m̩:7NY>h;MKGr# N\mwpMX{!{ؽ �RC7P"Q1mbˀ sGW#�a,7ɱ(Cy7GC'aO?C\�0�:(n��Xѻ~r86� +_ZĐ>wTfa0R%]�|SyVwJ xX>w0SwGCG8ؽJ wsmC}]a�&.42EttĒ=Rme(E-[6(r#^G/PNܔ�K 0 RIK^{%f'"Tj\]SGCa QqmX z�h0EjLM6T(�7L-?w*.zLǓq'\y@ qWO#]OGWOG�)Zܻ{X>tct`kO{ֺF Vr2o]/ ɮ)^ mHܷ �j;-&L45oI<r@axqK=Is<_ :HcǛ6;:vf:8-S13o|GC'b+{xXD#/T _{DGg1�P_ 0/_"I-?pBXELG˵;$bZS_m Lj�$aɗ#YJ*u?Z{\{0߭]`).9L/Žpgk~WPıĦ{~-cl� |^o*,(d։LDPrErqt ѩ?12?ZT:j~(e:;UlzCFdȜ|/#[<vh<=>93)Cr?V2YB#1�p"�F `H(ve[{^gaz�2Db'b"#sEg8^G7ROvghD)%hh-*mh91O>Ιm?l<>%Ѥ !83ckwt&! \pGLc2�PK`ޝ`JKQBb.ΦdL~ZnZ..G7JMs/'% ] U"u6'mG 풹s={s!nj?ze>īv0�ZF@bӽC2d�P7 PXZ曔PiwE !$s~/ fc]cdwf2'O$uD#}GܙxQ)/Vo@o�ً.o]+9@Mb_)CU1]],*+؟*,WLL F@s31t4Go"7noTqa1eNEnUe:s=�{*hIʴ1U ><kXeK9O�J�ZFpCZXt<&Z;ٞas..$rǥntUʹ/ 6]:S2?B~*ѝۮV%F@`.@kmˏ:(+|ȭSתJҺm bYLUy`ЁYwoP@�h2NΓ^u�rj::�:[9=3jV%C!_<UPXzrOUmH>gcwnk9,i#+ 0_90} }}hx4**Jo2q?}?:'|ݗws?S9t|n ~[~x|-�@퍀:(/,GDl'guJ$nCDtn nH%( _n&s^sNK.Q-ۑk6H/F qtSZLq8vRS&D�4!+SQ�W-|͊?z.g~F�6(8[iXTU6?e/mEdbascmǑ=,qv^Ӟl\( ivV8)^߻_Mw*�%^]~=S @Dm?|ه& u �M%bǢ�*/_RE!eWPOWItRfz U o>?#!Qh�ɴ LE ;:)w&T^sʖj#I[._wT!ګ,uRmF2hCq_)D�4)+`K֑mͿb9M5 ZXV_*Vj&-L{L2 R۷/7_n8&SX*$Ng>@o:7.;:s=a� WЫ-)"V/ޥ �4#EK� P`̷ tҵ�rS\|m2M6uw,,7n@#o|G֐z�kPrjdJ&koh0l�Ь&݃Gw>O0R]tްIjӴ{$8A?:֟d,)unxD#��E#i.om?M�%913U 0Iܹ].غ9Ó#_|pI"c?򫝒UNm0�: i�9Go\C�k,omg]d�N .rcwoM6Zth$)_y<|xU�Vr"z󷮔yԥ@S0(`~[@:8c~F�FԳHňks[^l@gJ|wl#suc-NL%h%O?- �o_jC«-h|k@31u?6NZ �4 #DDoy6kѢDTI`*ZTH߆Sמ-:cElB~mP7.QF#q&6DoRX/H̷5CxDx"bOq"�4 GB#m!3k23n| <sdB[_n*۩^]߄ȱ\yP=r?@q/?S Lyp\3&!"!a&=:W}QfFuuKh!-/hEQbvbAHL9A%gdЌY+}i򾗜&ZR_3m91'LTLlGtB%>_Do@ff-�~@& v3\Ooߩ-}C�o(Q?-ĿHT%6 J L*DFX$!k[[#UShb:XN=3MXo<"fehX8v@#ȯ?g>wwsנ8�PRX PC#[~-�5$xI:@#-/y@VZTZk'SZD\uODe r jI,E!1G$m-Ѥ#3qS"#Hŝhsu~㑄xsG�Wq �^ݩ�Y# ȯwwN=>�hz 1\n@D^N<Vn;"ᤨp2*S5?{=]JWa?zAħ ` +zÅWh31(@K�FHBx,Z+Jfb4zW.HQ' �j?92cnynj � ԵF#Cfh$7?#{ȌߒP2&J_vqѳ]ޣƪ罃�ZPZDxLRsHXW2"g,֫| )|{ݿ}{A_U^{ns8s5\RꢇC@ O\f^,9(@�/eu.E[[[_K᢮vUƚ,oйP_͟O' �X Pv:utDr?c\� -ZR@#?@)m>gR ׈ �h9#z7sGf�L�6B?(W0L>{Hf=ԕY\+e(w~L|Mր۾H{� `,Z,N"&ڶ$-K=Tջ'W|_mU͵6CCa[{os=a��J/ha7w<W\�b.I&@fn+W,T^ԂTſ -[u ~g]\(E?h;O:}B w&w&vh � +UPݫӯ %"ffR�M�UwR+WbVU[;[P”hC鑧71 V߭'RT�4�>fX,�@zU@#?)9?Us#��ul ("uxxr?b�Mn?G؏C}RX< o�O*S$31�-{S@#?/ñ[xOQ?�( kk31(@K�=?G_R' �h5/e؃M@{ �|s G#hs؃n\O� p6ÇncQ=b"?:{{~Qks=a�@3�$3\+u<rQ6=L�?GC{k>w:_rCw�sKȦ蹉h_Gc2|�&@"?GC3د̌E;ʡ\O�4H󌀜ܠ37lQ7B]b.F#?=֑mW%iW?�4 P(`ޤ&0�&@HE?mᩭ>{/' �h9!{8v2z#9@˛�F(E G4!9{NJ,( �T|#+ In}"5 � P@#D'>|o݈xoY¿V!M[/!uaI9b}/'t9�o(1zzIE.cq+\Aa9ł?\k}?8NUumjU{a@{c7v^y�I�"bEi �cPU^Cb;ƿw@gI5x+ds `�@Y(0h_|C'H@�], ?h='pG". z�V7:x18d[w �ct$@G#Z s'qC� �(*1u_Ǚ|2zEGkxM4��ԘT�G7t26H@;�FO G#�MNϏR<>Iv�trMؓMuȔtPHO"Dz]ѣ3ǯF>S1.%�h@.HvŒ*u/ |E⾍LchL<q7ѣ3E �@ ��F!3HnK@րn:w aHLv�%�+[EWtU*Pߪk  ?k:}~xR|ߒ^0I, P2 zu&b$M� VWTsnT$ky~T*]3pba/�bh _ 53�v7^Q.?h8:9s?v;???`?�t  ? =$Et&/8v3z|M�? ?p7ɡW[q=a�@g��9w1sc?2�]Mn?hMR(�PH^=�Gr�)[=|vF�-F@#za#v˷+Oy\O� _mx_~ H@;A#3oy\O��F Rrn7ŶB�hOKTw7Gۚo\k- �Õ P J[h@[�.Q=?Pkow-�o ^g껒5B&�GTPߕ/V޺<`?�@q[{D[1�&@OGT gu{���U] `0#WttvC�mk!Qݽ?Ewtv_O(o]okG?� X '@G۾5&� ``G#�>X?LG?v$p?h 33#)(_dga}T޵Cgnz["Qs�C-gU=K-yU BVWqT]W=L7zH5$V♛nyfHU/ C�AM�P $joŌ$�5* ]W::;�MoʼnG~= -s=�T(}ngؽ$� \&�G=vo/#?�5į` P`F~uxjC�oT&h?@,#;"~kg) 2 �h]Q�F(�O#@G52� G#б???`�@mLD_ T{/8v7zzDT-;/[N˿̚ڽ"\O�1�f} b4] `? |o$7{ص>. �h&̆6te !�t G G#: _Q +745ߓ./ݬXmsy/�# �mOwivAF7\k?@K =!s=�@=Mo<v'3GO]߯>S8Q�hwTW%)@@3tRU;?k/T13>z'/s ~EP>�@UL�/ 뤓x~S#GM�@ZSvGwWMtGFod<yE��F@z�D\~Q$ �c?@cS۾Eg茔S 0�y(U7`ⱫD{@� ԕ6?@[8_hyvPJ+ל7`�@璎3E$i/3M�?Vhm%cvq=� /NhK7ZP&�:Uf ֮'R~?0�%M�qMbA�c즯?dv �eta >M_C{E�ڈ�2u( `H2O=v/ �cT@?в8ӿvI/-�К&aQ&]h@�Fq�G4-zft}4O�~C�<Jn=�\aA4N_~::a7?@.x7u2ɼW^'�ħ(WM�T�gj(_# �d݈?26_v,߫_E �h+W:)"I趱=?1(�t FwOqG#Hnby ] X}LGܗߡ<�Zcjђ/@B]U� @Dz<YzTyXWŗz[ojPolk._4<p9��PW@r;,nS0mO^eq8# �c(1.\\VY)�$O^sO#�: o$vG.-F�:6?@J>wW_C+ c �8&@Hn4<maDL"�?.1>n�Λ>00EČODH>r.a?>WkڟoRڿ^?� ��Q�^&@�I6vo# �ww�?>؇2vW-> �Ţ� &Us# �h(VG#,7I ��oTa@/5pɈ@D�?`8Ç^[J��&_:@H�3r.qFLj@G�G#j!G=cw*�@�@$7j~=�KWNF.8'bDLP G#,O<$vvzV,tup6 GWޑY92׊HY%c?\C@G�Fhmh]^@sAmʿY73#X#AɁǢ~EĿ#?��^,:mHzr3d\E/,_'fF�:PhqzWUok&�.xB[ � �)�Д&̯ 'Y�:O.ȷgQ�4e ݈ڠGIG�Uw��"0acW۷:F�:oT@#:@ό^։hLK#�`&@~:2^o蹉M(�` �_?7)zǾ#"߫~??`��,d>h$}Mkzv ��:-@#:BE/ׯ��2Q�L�ٞEώ~zfl= �@#:@ώm^�� _H|€�й&@93c~:< !�&@k.o:SCO1�y& h �!E/_��h2,j$%3 ��?@L ?c("ͼu%0��}L�K>}g*�` �$']O*! و��hn32ы?#g}*�t c^OxHC5ۚ&#:w7)vl_~c?7?# �e!qDf_7}m,=K?~8v �&KI.؉ĞU�<@Z*U_o~{n^џ!�� )"fO>Ja�:p�?@[~⓿*L"�=M�w=Dޑ]Zh0�Б&QmG4JkObWyb?? B�@{��7~>sMF�:0<o>)a0��|L�Is-cY�@#Zǎvaa0��p\WtăoH>q͌,�` �-mO\nXs���h#1<#O\3q%%fb�L�?1S.L �65܅% wqgt26@g�A ?@cP>c@#�LR֎Oįut�# �gL�?A?:{ /} )o!��T4@>},vcd��h?79v}dۨV/U��h �K fo&DL|ՙN4a �M3x|ř蟑,?@ΡukJ&kpt᯾SVLp]"Jv=#o#Գ#?p̗_F�:v?a{oSv+q΍5�1,ZH�6�؉oŢ@GTq@?hX^Wc'߫:C8:_{DGR�95ǩs_zǻ_'#1a#' h;nn{Vwvm?@ "�#M<1_ЯM`~>YBD{l_~N,sN3 k4 jeN%'SY#bMWߙYQ92[Ynm>{g?z.T@�9�?@]hyfP wCmw8?t(D��Fw�L &!f޵+?zfl# �h �U3cW|?= w�����3??\ 3BZV �";? ]mZ>A`��5�^f7$3]t�sw@#ڶ$v~.f".&W?�� P8Wr-_ 3�Q6@&�mnJUj%^�PF_:@B䃗oL0 ��??:NL>xF)Z��j&l~%ڂ�Ék?:<3Y?:^Gw%X}P˽C`��7fo^]㱋?g.�t `?t:رb=ĿWGhׅR).ÜKRU0D$ؕKD]GE}ំ^a�4G4~bж}`|NPoLGCqnl O?��(ү= n'UhM�Ion|A# �2 f>Yٛ͗J^9;o]r"�F:kDd�2G~$@ ^wz_Ԓ/ftSZY{ :`$7OK{3%_C</B ��t4;"M_$@N}pp/g#.�t J)H�?@L>cv?uB w[��&@&gJOEbE 6# �j mP/b?wȜ *�� C@.u_|/e0�i&�ͱYٛ+%"$?J���Mgx�97#OnF�:(Б=/D_?��@Ә�JN@ h0 S���Z:xgg9O;Bޞ@�X82]22C@t]~-aB�±ֶG~'p#%ED4,��BY@q@_7<qH>c#0�="�-&JqA;RV??��@s�R(:]8'e#M�?@x4v>mm}xt? Ŀ 0��Zp<L�HI==2ه/v?SqQ1�-Oc?Ek2Ŀ��hE@ĻEW4Cq&nɇ.O 2�&Qbt26|x~,_3'-"Q8@`:zDX}IOk#F�_]8&?x/_L ̦|~5s=A "�b�@M ߕo�dL�||co]�M�?@v?}io=!Zoz Z@ �@Yza�68I-Y$0qLOAF�e =O%nփ]8??_|'� �1 P;@jmhg3xvs�t wß=rDJlTHa?G NOXB �ԙ%2 N (H yϿ^(@K?@%z)w=v$P?R��,IEm-kQ=%鹉2�-K?t znbk/[?'Cws@� �1$�peO>?_㘌4� �NqLP߯b?؇Z�/'H �K stO^Ώ|V-^F�ڒzaiV,||Y9,mBh ��h$R,O u⭾{EtF��E="Q_;N[?6sP>F@'� �5DO (jL4O|ןdl�L�?'ذį?'3�!\O����"ޭ<m$Jm]O8Gd��=PuD7\O� eR�|d] H}î{KWJF�ڊji)X洵偟&sRnX*?-S=?4pC\Ф&U]1B.#; 4_ uod��xL =8wN]'����bot1DWD��&�:˜v<vixX 0�0�0��5@v]>>\>4�t N,&LK=Sy*!K+���� 1$ 0 ?v&ݽm�(#�펙==?]++&����1&�L�W>jr;[@G�hs&o�+]/_ ����6D,U y_{,:5Θ1ヒ\C[,׿B\O�Pk ? 2Ļ6e9{y҇S}o.@�F�h\;GOcR/_f?����Mbx K 2 /zZ\F�@CQk-O]Kb!ez � � �z�"\..@W"F�Z\&!Vp}t ɛ.M'b2 s=���@�Uyeo~^ՊS_h@{�h3yflS.w?=XJ2v����&H�ŌT[^U8��@0֮'Lqѓίƻ����h&#`!�FqO/|8Q$��Nh#'o5����hF@$X4_"zi_Ւ0���5كs\j>}<ao*#���ٍh�w E |=K$F��֬ݯ~L/گ]?`�`�`��  P,-.t`E_|{(b��&lgȃۿ3|xF w/p����hxEK (7տe^��T $“̧oZA|UG���@+�R0L��fat7#���b&&O_qJ~Sw^_�F@nA 3k_b��< 3qwoa_��`�|�:Zh rWQ��pucW q' � � �v7 )6U# ֿ{Edŋu��ho{\b>Hp\v ?`�`�`��t PH!��D=?z>$݋0��Qqk3$qюAC3T??����@F_Z@1Kfq__ʿk)#��mcEnOxL=Цb ��_b_+H~ 㬗;-B<��vF<dݓ.A%*{נ0�0�0��0rW~&@Ј�!zz_ZyXy��@[H 37\mw4O/dK ����F@F+)6�Gt_H-[JF��ZR'wXx!?n~ ��&�(6JEP?ЫjɊ}��h 9do{CWl~߫WW?����nF@n 1R?dz�@S Dduɻ~L^eJ#����cw (cѳ}G腯Z�@`oLqÒ%xy5 � � ��P*- >_T@~D@HDҕ$tޫW2��ls>Vܣ#q)/㟿Hp?`�`�t @UL?.P>@��jũKS =^���?6ܧgF#$Կy����_nj@0P'Yz_W/];���5JLGwfxT w ?Կ����P#>B"B"RV-O7t> ��P%Ĥ}tm=?%;uG`�`�`��p�NJ R#xOW@{W.��]?z@' b9^�����m`Pu`~j@t?E.��@0lͼGt,0߫#0�0�0��(75 TXlQ~ꏌ ^ջ,��\'"[{~򸎇%-7P#0�0�0��PwW퍡%+]���=]{Kދ oq����h~# Hrv/}@U��:Tύow<q=|/ǿΕ�����k xaUgI N��ƻoq -Ѓ+W� � � �3jix}=zz_q )FR �6ñGo޻vHrwu?�����cF_z@g-~G:wTz�$cw]Ha1_ ?����@*3J [0?Kj��h-v{sw\_>~~?����=J&@z퟾3欷2yG��U;p]֦{ !_!y� � � ���.G@DBY/=~q֋+]xG��+1 |'=D__;&� � � ��DxjѲw _^W��4xku<(o����#@$xr5D$zݟ8圷Z̻�Pc;x׽V}G||1_C`�`�`��`䯂4J~Q>OEzwی=j��TyQ;/"^;C`�`�`��@FPR (jH EW=ƚ,Fh �@8v\O <mm}k-!no |�� ejɊ��\@Gg9w=j>zSz[|Tn[G`�`�`��@13(f` By;SV u�JL:#G6⿃_&+����Z  \->_v׈R!�X6F{GGĪ_RAwD?�� `��tZ*X Qs^qrכY/#hٹ;�1?6{91kpxbTnnC`�`�`��@=�ex ̏ 0ߐ�izퟞw87KSx�0ѧm=fmH�џo�8o!����jU9u 0<�e/7^Ʒkz.@XQ=9*Y/_*Ŀt~?����� $=V@C+*Pt_:5oQ'zZ @Koyz꺍zv<& A+p?�����ì|Q+O_}|:WR]S�<_[zvlsb='#R_mџ/ ��6ܫbJEqy˻8V򻢌-�wzftsbZk#❓_  ?�����4PNT@" `/~߼8oP+O=1By�f8vLOlvXk>u&==v (vE"P7`��$b2 ϫeuo8UN>Z;� VDO orl]k=>)+&KUo~?�����PnT@~@Hw0|>hYw-yj~O;�1czr>u [ul6!2 �GvSk9͛�e0�0���lx32̀zퟞz_N=uj(w�\KVGGfѣ=Oo7sDK~G*/ӏ� � � ��hZ3@0 Ps8%+C5שZ%s�cEVgp&{]|Pbs~� � *�p�@o,) "Pa =W4תU{Z{�틎Α-gCf_l7!?j0�0���jn,fǂ8wV~ソk~^%]=+yZ+9G;؛>!avń]D w/F5�0�0�0��]�PN' f@*O V<j2zy'$n=vlwV{GE;na'ڃ�� � � �� ( ! 9ҕ?_/3yԪ3^@h׶S;c۶أSq)^p//4������(a (UPTj@cߡV8?yq_V=j2zxGj)LГ;vٛݫgFc>OG�����@ �/ Pn@Czx+_VrZ̻ �;S.؎{tGZ/z?�0�0�0���jh*j]y K^z,>wذ<'9'v6}H1 d?%� � � �� 1ύ_2􊷽X~^x]D @gX19GOsN* rD~v�����f@1#)Pi@Q&D+z1k~,_}Z Bۡ27q?9eb[nНb������-bPn@)s TBF5E˺W㜗@>lyjsb::wT'C])yzP_yJ;>B}���fHTrR5ʉ 0<_2Bt\֜uҚϗUPĤӳGΡCD;._I~9b]~D?�`�`�`��� i1h**) i- Qu稥'/?S{p@ =5|\>n_w\ ./%T'?s� 3�ȳ�h3ehNp|>P%|P;92-";rS4?8[qjige'-;C*J8CgfȰO#z1{ߺzXDwˋ+g ]~~�6�� 1(@C @�=v̴Ǝ͈Ȯڳ+ן3;C dhwinΖv)؈ttfXώ驡a=>aT1+/^tǿ_i>v�:R�H�tr >Vx0 ^8eS'F-[uZ|,ZFzz#3qN%xxL&txrTO ;'v:7v)/%>VN>�:k=75�0���Z !Pn@P h�"CoT8kStʩjj5һdt{WH{gRm&fLLI":cs:2=!cرQϸ9J bN[ ,���"���8A<c ! R`A1S\#y#Gȑi[߭- 3_|q'IkV+WEOENRWJeճ\e^&Ju^֦fX[ɰXfX䜘NĦ%Yٱig7cV�% U*"ƃk "E�@��@ xatYX@9>E~gQCQ$~T+O_VL-_X-]TX-^Tz,UݽE.ճh(Ţn uJ}{Ibz (*XQ.lѬl̨hǎm%D;Xɘhmd<"ڱL6L$$\D1ḏ-UNAB{5?�@e뙚=7���6.!~)P0/0rL(b~xzz8g}b؂4%Vh9>W"X?!�����Me;G!Q  &Q (߿|)C J1 ; IiÇ�)���P黈 j T )_/uRB P�/L%A~)S��R���+׿UTb c|祌RU[~ PJ}�� ����UK@)\x FiTjTs~������ +Wj " __IAJ�]cA?) r=B��0����h (&b|rDu BDWK2 >��`����Ac ,T[P K .�žt<h*>WPc������-b H@A)_W[ DzF@9_+<xP�� ���7SE*zkTu]G������AQ3\bVm=v^Ty��0����"q|{ՀIP��� ���~_S^ ��0����ښjb���4 Jk��������������`�����������������`�����������������`�����������������`�������������������������`�����������������`�����������������`�����������������`��������`�����������������`�����������������`�����������������`�������������������������`�����������������`�����������������`�����������������`�������������������������`�����������������`�����������������`�����������������`��������`�����������������(�"D����IENDB`�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/Telegram/Telegram Desktop.entitlements�������������������������������������0000664�0000000�0000000�00000001052�13262451251�0024525�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>com.apple.security.app-sandbox</key> <true/> <key>com.apple.security.files.downloads.read-write</key> <true/> <key>com.apple.security.files.user-selected.read-write</key> <true/> <key>com.apple.security.files.bookmarks.app-scope</key> <true/> <key>com.apple.security.network.client</key> <true/> <key>com.apple.security.network.server</key> <true/> </dict> </plist> ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/ThirdParty/����������������������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0017272�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/ThirdParty/Catch/����������������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0020314�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/ThirdParty/GSL/������������������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0017717�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/ThirdParty/SPMediaKeyTap/��������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0021672�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/ThirdParty/SPMediaKeyTap/SPInvocationGrabbing/�����������������������������0000775�0000000�0000000�00000000000�13262451251�0025702�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������NSObject+SPInvocationGrabbing.h���������������������������������������������������������������������0000664�0000000�0000000�00000001511�13262451251�0033445�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000�tdesktop-1.2.17/Telegram/ThirdParty/SPMediaKeyTap/SPInvocationGrabbing�������������������������������������������������������������������������������������������������#import <Foundation/Foundation.h> @interface SPInvocationGrabber : NSObject { id _object; NSInvocation *_invocation; int frameCount; char **frameStrings; BOOL backgroundAfterForward; BOOL onMainAfterForward; BOOL waitUntilDone; } -(id)initWithObject:(id)obj; -(id)initWithObject:(id)obj stacktraceSaving:(BOOL)saveStack; @property (readonly, retain, nonatomic) id object; @property (readonly, retain, nonatomic) NSInvocation *invocation; @property BOOL backgroundAfterForward; @property BOOL onMainAfterForward; @property BOOL waitUntilDone; -(void)invoke; // will release object and invocation -(void)printBacktrace; -(void)saveBacktrace; @end @interface NSObject (SPInvocationGrabbing) -(id)grab; -(id)invokeAfter:(NSTimeInterval)delta; -(id)nextRunloop; -(id)inBackground; -(id)onMainAsync:(BOOL)async; @end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������NSObject+SPInvocationGrabbing.m���������������������������������������������������������������������0000664�0000000�0000000�00000005514�13262451251�0033461�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000�tdesktop-1.2.17/Telegram/ThirdParty/SPMediaKeyTap/SPInvocationGrabbing�������������������������������������������������������������������������������������������������#import "NSObject+SPInvocationGrabbing.h" #import <execinfo.h> #pragma mark Invocation grabbing @interface SPInvocationGrabber () @property (readwrite, retain, nonatomic) id object; @property (readwrite, retain, nonatomic) NSInvocation *invocation; @end @implementation SPInvocationGrabber - (id)initWithObject:(id)obj { return [self initWithObject:obj stacktraceSaving:YES]; } -(id)initWithObject:(id)obj stacktraceSaving:(BOOL)saveStack { self.object = obj; if(saveStack) [self saveBacktrace]; return self; } -(void)dealloc { free(frameStrings); self.object = nil; self.invocation = nil; [super dealloc]; } @synthesize invocation = _invocation, object = _object; @synthesize backgroundAfterForward, onMainAfterForward, waitUntilDone; - (void)runInBackground { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; @try { [self invoke]; } @finally { [pool drain]; } } - (void)forwardInvocation:(NSInvocation *)anInvocation { [anInvocation retainArguments]; anInvocation.target = _object; self.invocation = anInvocation; if(backgroundAfterForward) [NSThread detachNewThreadSelector:@selector(runInBackground) toTarget:self withObject:nil]; else if(onMainAfterForward) [self performSelectorOnMainThread:@selector(invoke) withObject:nil waitUntilDone:waitUntilDone]; } - (NSMethodSignature *)methodSignatureForSelector:(SEL)inSelector { NSMethodSignature *signature = [super methodSignatureForSelector:inSelector]; if (signature == NULL) signature = [_object methodSignatureForSelector:inSelector]; return signature; } - (void)invoke { @try { [_invocation invoke]; } @catch (NSException * e) { NSLog(@"SPInvocationGrabber's target raised %@:\n\t%@\nInvocation was originally scheduled at:", e.name, e); [self printBacktrace]; printf("\n"); [e raise]; } self.invocation = nil; self.object = nil; } -(void)saveBacktrace { void *backtraceFrames[128]; frameCount = backtrace(&backtraceFrames[0], 128); frameStrings = backtrace_symbols(&backtraceFrames[0], frameCount); } -(void)printBacktrace { for(int x = 3; x < frameCount; x++) { if(frameStrings[x] == NULL) { break; } printf("%s\n", frameStrings[x]); } } @end @implementation NSObject (SPInvocationGrabbing) -(id)grab { return [[[SPInvocationGrabber alloc] initWithObject:self] autorelease]; } -(id)invokeAfter:(NSTimeInterval)delta { id grabber = [self grab]; [NSTimer scheduledTimerWithTimeInterval:delta target:grabber selector:@selector(invoke) userInfo:nil repeats:NO]; return grabber; } - (id)nextRunloop { return [self invokeAfter:0]; } -(id)inBackground { SPInvocationGrabber *grabber = [self grab]; grabber.backgroundAfterForward = YES; return grabber; } -(id)onMainAsync:(BOOL)async { SPInvocationGrabber *grabber = [self grab]; grabber.onMainAfterForward = YES; grabber.waitUntilDone = !async; return grabber; } @end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/ThirdParty/SPMediaKeyTap/SPMediaKeyTap.h�����������������������������������0000664�0000000�0000000�00000002074�13262451251�0024446�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <Cocoa/Cocoa.h> #import <IOKit/hidsystem/ev_keymap.h> #import <Carbon/Carbon.h> // http://overooped.com/post/2593597587/mediakeys #define SPSystemDefinedEventMediaKeys 8 @interface SPMediaKeyTap : NSObject { EventHandlerRef _app_switching_ref; EventHandlerRef _app_terminating_ref; CFMachPortRef _eventPort; CFRunLoopSourceRef _eventPortSource; CFRunLoopRef _tapThreadRL; BOOL _shouldInterceptMediaKeyEvents; id _delegate; // The app that is frontmost in this list owns media keys NSMutableArray *_mediaKeyAppList; } + (NSArray*)defaultMediaKeyUserBundleIdentifiers; -(id)initWithDelegate:(id)delegate; +(BOOL)usesGlobalMediaKeyTap; -(void)startWatchingMediaKeys; -(void)stopWatchingMediaKeys; -(void)handleAndReleaseMediaKeyEvent:(NSEvent *)event; @end @interface NSObject (SPMediaKeyTapDelegate) -(void)mediaKeyTap:(SPMediaKeyTap*)keyTap receivedMediaKeyEvent:(NSEvent*)event; @end #ifdef __cplusplus extern "C" { #endif extern NSString *kMediaKeyUsingBundleIdentifiersDefaultsKey; extern NSString *kIgnoreMediaKeysDefaultsKey; #ifdef __cplusplus } #endif��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/ThirdParty/SPMediaKeyTap/SPMediaKeyTap.m�����������������������������������0000664�0000000�0000000�00000023620�13262451251�0024453�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// Copyright (c) 2010 Spotify AB #import "SPMediaKeyTap.h" #import "SPInvocationGrabbing/NSObject+SPInvocationGrabbing.h" // https://gist.github.com/511181, in submodule @interface SPMediaKeyTap () -(BOOL)shouldInterceptMediaKeyEvents; -(void)setShouldInterceptMediaKeyEvents:(BOOL)newSetting; -(void)startWatchingAppSwitching; -(void)stopWatchingAppSwitching; -(void)eventTapThread; @end static SPMediaKeyTap *singleton = nil; static pascal OSStatus appSwitched (EventHandlerCallRef nextHandler, EventRef evt, void* userData); static pascal OSStatus appTerminated (EventHandlerCallRef nextHandler, EventRef evt, void* userData); static CGEventRef tapEventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon); // Inspired by http://gist.github.com/546311 @implementation SPMediaKeyTap #pragma mark - #pragma mark Setup and teardown -(id)initWithDelegate:(id)delegate { _delegate = delegate; [self startWatchingAppSwitching]; singleton = self; _mediaKeyAppList = [NSMutableArray new]; _tapThreadRL=nil; _eventPort=nil; _eventPortSource=nil; return self; } -(void)dealloc { [self stopWatchingMediaKeys]; [self stopWatchingAppSwitching]; [_mediaKeyAppList release]; [super dealloc]; } -(void)startWatchingAppSwitching { // Listen to "app switched" event, so that we don't intercept media keys if we // weren't the last "media key listening" app to be active EventTypeSpec eventType = { kEventClassApplication, kEventAppFrontSwitched }; OSStatus err = InstallApplicationEventHandler(NewEventHandlerUPP(appSwitched), 1, &eventType, self, &_app_switching_ref); assert(err == noErr); eventType.eventKind = kEventAppTerminated; err = InstallApplicationEventHandler(NewEventHandlerUPP(appTerminated), 1, &eventType, self, &_app_terminating_ref); assert(err == noErr); } -(void)stopWatchingAppSwitching { if(!_app_switching_ref) return; RemoveEventHandler(_app_switching_ref); _app_switching_ref = NULL; if(!_app_terminating_ref) return; RemoveEventHandler(_app_terminating_ref); _app_terminating_ref = NULL; } -(void)startWatchingMediaKeys { // Prevent having multiple mediaKeys threads [self stopWatchingMediaKeys]; [self setShouldInterceptMediaKeyEvents:YES]; // Add an event tap to intercept the system defined media key events _eventPort = CGEventTapCreate(kCGSessionEventTap, kCGHeadInsertEventTap, kCGEventTapOptionDefault, CGEventMaskBit(NX_SYSDEFINED), tapEventCallback, self); assert(_eventPort != NULL); _eventPortSource = CFMachPortCreateRunLoopSource(kCFAllocatorSystemDefault, _eventPort, 0); assert(_eventPortSource != NULL); // Let's do this in a separate thread so that a slow app doesn't lag the event tap [NSThread detachNewThreadSelector:@selector(eventTapThread) toTarget:self withObject:nil]; } -(void)stopWatchingMediaKeys { // TODO<nevyn>: Shut down thread, remove event tap port and source if(_tapThreadRL){ CFRunLoopStop(_tapThreadRL); _tapThreadRL=nil; } if(_eventPort){ CFMachPortInvalidate(_eventPort); CFRelease(_eventPort); _eventPort=nil; } if(_eventPortSource){ CFRelease(_eventPortSource); _eventPortSource=nil; } } #pragma mark - #pragma mark Accessors +(BOOL)usesGlobalMediaKeyTap { #ifdef _DEBUG // breaking in gdb with a key tap inserted sometimes locks up all mouse and keyboard input forever, forcing reboot return NO; #else // XXX(nevyn): MediaKey event tap doesn't work on 10.4, feel free to figure out why if you have the energy. return ![[NSUserDefaults standardUserDefaults] boolForKey:kIgnoreMediaKeysDefaultsKey] && floor(NSAppKitVersionNumber) >= 949/*NSAppKitVersionNumber10_5*/; #endif } + (NSArray*)defaultMediaKeyUserBundleIdentifiers { return [NSArray arrayWithObjects: [[NSBundle mainBundle] bundleIdentifier], // your app @"com.spotify.client", @"com.apple.iTunes", @"com.apple.QuickTimePlayerX", @"com.apple.quicktimeplayer", @"com.apple.iWork.Keynote", @"com.apple.iPhoto", @"org.videolan.vlc", @"com.apple.Aperture", @"com.plexsquared.Plex", @"com.soundcloud.desktop", @"org.niltsh.MPlayerX", @"com.ilabs.PandorasHelper", @"com.mahasoftware.pandabar", @"com.bitcartel.pandorajam", @"org.clementine-player.clementine", @"fm.last.Last.fm", @"fm.last.Scrobbler", @"com.beatport.BeatportPro", @"com.Timenut.SongKey", @"com.macromedia.fireworks", // the tap messes up their mouse input @"at.justp.Theremin", @"ru.ya.themblsha.YandexMusic", @"com.jriver.MediaCenter18", @"com.jriver.MediaCenter19", @"com.jriver.MediaCenter20", @"co.rackit.mate", @"com.ttitt.b-music", @"com.beardedspice.BeardedSpice", @"com.plug.Plug", @"com.plug.Plug2", @"com.netease.163music", @"org.quodlibet.quodlibet", nil ]; } -(BOOL)shouldInterceptMediaKeyEvents { BOOL shouldIntercept = NO; @synchronized(self) { shouldIntercept = _shouldInterceptMediaKeyEvents; } return shouldIntercept; } -(void)pauseTapOnTapThread:(BOOL)yeahno { CGEventTapEnable(self->_eventPort, yeahno); } -(void)setShouldInterceptMediaKeyEvents:(BOOL)newSetting { BOOL oldSetting; @synchronized(self) { oldSetting = _shouldInterceptMediaKeyEvents; _shouldInterceptMediaKeyEvents = newSetting; } if(_tapThreadRL && oldSetting != newSetting) { id grab = [self grab]; [grab pauseTapOnTapThread:newSetting]; NSTimer *timer = [NSTimer timerWithTimeInterval:0 invocation:[grab invocation] repeats:NO]; CFRunLoopAddTimer(_tapThreadRL, (CFRunLoopTimerRef)timer, kCFRunLoopCommonModes); } } #pragma mark #pragma mark - #pragma mark Event tap callbacks // Note: method called on background thread static CGEventRef tapEventCallback2(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) { SPMediaKeyTap *self = refcon; if(type == kCGEventTapDisabledByTimeout) { NSLog(@"Media key event tap was disabled by timeout"); CGEventTapEnable(self->_eventPort, TRUE); return event; } else if(type == kCGEventTapDisabledByUserInput) { // Was disabled manually by -[pauseTapOnTapThread] return event; } NSEvent *nsEvent = nil; @try { nsEvent = [NSEvent eventWithCGEvent:event]; } @catch (NSException * e) { NSLog(@"Strange CGEventType: %d: %@", type, e); assert(0); return event; } if (type != NX_SYSDEFINED || [nsEvent subtype] != SPSystemDefinedEventMediaKeys) return event; int keyCode = (([nsEvent data1] & 0xFFFF0000) >> 16); if (keyCode != NX_KEYTYPE_PLAY && keyCode != NX_KEYTYPE_FAST && keyCode != NX_KEYTYPE_REWIND && keyCode != NX_KEYTYPE_PREVIOUS && keyCode != NX_KEYTYPE_NEXT) return event; if (![self shouldInterceptMediaKeyEvents]) return event; [nsEvent retain]; // matched in handleAndReleaseMediaKeyEvent: [self performSelectorOnMainThread:@selector(handleAndReleaseMediaKeyEvent:) withObject:nsEvent waitUntilDone:NO]; return NULL; } static CGEventRef tapEventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) { NSAutoreleasePool *pool = [NSAutoreleasePool new]; CGEventRef ret = tapEventCallback2(proxy, type, event, refcon); [pool drain]; return ret; } // event will have been retained in the other thread -(void)handleAndReleaseMediaKeyEvent:(NSEvent *)event { [event autorelease]; [_delegate mediaKeyTap:self receivedMediaKeyEvent:event]; } -(void)eventTapThread { _tapThreadRL = CFRunLoopGetCurrent(); CFRunLoopAddSource(_tapThreadRL, _eventPortSource, kCFRunLoopCommonModes); CFRunLoopRun(); } #pragma mark Task switching callbacks NSString *kMediaKeyUsingBundleIdentifiersDefaultsKey = @"SPApplicationsNeedingMediaKeys"; NSString *kIgnoreMediaKeysDefaultsKey = @"SPIgnoreMediaKeys"; -(void)mediaKeyAppListChanged { if([_mediaKeyAppList count] == 0) return; /*NSLog(@"--"); int i = 0; for (NSValue *psnv in _mediaKeyAppList) { ProcessSerialNumber psn; [psnv getValue:&psn]; NSDictionary *processInfo = [(id)ProcessInformationCopyDictionary( &psn, kProcessDictionaryIncludeAllInformationMask ) autorelease]; NSString *bundleIdentifier = [processInfo objectForKey:(id)kCFBundleIdentifierKey]; NSLog(@"%d: %@", i++, bundleIdentifier); }*/ ProcessSerialNumber mySerial, topSerial; GetCurrentProcess(&mySerial); [[_mediaKeyAppList objectAtIndex:0] getValue:&topSerial]; Boolean same; OSErr err = SameProcess(&mySerial, &topSerial, &same); [self setShouldInterceptMediaKeyEvents:(err == noErr && same)]; } -(void)appIsNowFrontmost:(ProcessSerialNumber)psn { NSValue *psnv = [NSValue valueWithBytes:&psn objCType:@encode(ProcessSerialNumber)]; NSDictionary *processInfo = [(id)ProcessInformationCopyDictionary( &psn, kProcessDictionaryIncludeAllInformationMask ) autorelease]; NSString *bundleIdentifier = [processInfo objectForKey:(id)kCFBundleIdentifierKey]; NSArray *whitelistIdentifiers = [[NSUserDefaults standardUserDefaults] arrayForKey:kMediaKeyUsingBundleIdentifiersDefaultsKey]; if(![whitelistIdentifiers containsObject:bundleIdentifier]) return; [_mediaKeyAppList removeObject:psnv]; [_mediaKeyAppList insertObject:psnv atIndex:0]; [self mediaKeyAppListChanged]; } -(void)appTerminated:(ProcessSerialNumber)psn { NSValue *psnv = [NSValue valueWithBytes:&psn objCType:@encode(ProcessSerialNumber)]; [_mediaKeyAppList removeObject:psnv]; [self mediaKeyAppListChanged]; } static pascal OSStatus appSwitched (EventHandlerCallRef nextHandler, EventRef evt, void* userData) { SPMediaKeyTap *self = (id)userData; ProcessSerialNumber newSerial; GetFrontProcess(&newSerial); [self appIsNowFrontmost:newSerial]; return CallNextEventHandler(nextHandler, evt); } static pascal OSStatus appTerminated (EventHandlerCallRef nextHandler, EventRef evt, void* userData) { SPMediaKeyTap *self = (id)userData; ProcessSerialNumber deadPSN; GetEventParameter( evt, kEventParamProcessID, typeProcessSerialNumber, NULL, sizeof(deadPSN), NULL, &deadPSN ); [self appTerminated:deadPSN]; return CallNextEventHandler(nextHandler, evt); } @end ����������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/ThirdParty/crl/������������������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0020052�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/ThirdParty/emoji_suggestions/����������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0023027�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/ThirdParty/emoji_suggestions/emoji_suggestions.cpp�������������������������0000664�0000000�0000000�00000026214�13262451251�0027275�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "emoji_suggestions.h" #include <algorithm> #include "emoji_suggestions_data.h" #ifndef Expects #include <cassert> #define Expects(condition) assert(condition) #endif // Expects namespace Ui { namespace Emoji { namespace internal { namespace { checksum Crc32Table[256]; class Crc32Initializer { public: Crc32Initializer() { checksum poly = 0x04C11DB7U; for (auto i = 0; i != 256; ++i) { Crc32Table[i] = reflect(i, 8) << 24; for (auto j = 0; j != 8; ++j) { Crc32Table[i] = (Crc32Table[i] << 1) ^ (Crc32Table[i] & (1 << 31) ? poly : 0); } Crc32Table[i] = reflect(Crc32Table[i], 32); } } private: checksum reflect(checksum val, char ch) { checksum result = 0; for (int i = 1; i < (ch + 1); ++i) { if (val & 1) { result |= 1 << (ch - i); } val >>= 1; } return result; } }; } // namespace checksum countChecksum(const void *data, std::size_t size) { static Crc32Initializer InitTable; auto buffer = static_cast<const unsigned char*>(data); auto result = checksum(0xFFFFFFFFU); for (auto i = std::size_t(0); i != size; ++i) { result = (result >> 8) ^ Crc32Table[(result & 0xFFU) ^ buffer[i]]; } return (result ^ 0xFFFFFFFFU); } } // namespace internal namespace { class string_span { public: string_span() = default; string_span(const utf16string *data, std::size_t size) : begin_(data), size_(size) { } string_span(const std::vector<utf16string> &data) : begin_(data.data()), size_(data.size()) { } string_span(const string_span &other) = default; string_span &operator=(const string_span &other) = default; const utf16string *begin() const { return begin_; } const utf16string *end() const { return begin_ + size_; } std::size_t size() const { return size_; } string_span subspan(std::size_t offset, std::size_t size) { return string_span(begin_ + offset, size); } private: const utf16string *begin_ = nullptr; std::size_t size_ = 0; }; bool IsNumber(utf16char ch) { return (ch >= '0' && ch <= '9'); } bool IsLetterOrNumber(utf16char ch) { return (ch >= 'a' && ch <= 'z') || IsNumber(ch); } using Replacement = internal::Replacement; class Completer { public: Completer(utf16string query); std::vector<Suggestion> resolve(); private: struct Result { const Replacement *replacement; int wordsUsed; }; static std::vector<utf16char> NormalizeQuery(utf16string query); void addResult(const Replacement *replacement); bool isDuplicateOfLastResult(const Replacement *replacement) const; bool isBetterThanLastResult(const Replacement *replacement) const; void processInitialList(); void filterInitialList(); void initWordsTracking(); bool matchQueryForCurrentItem(); bool matchQueryTailStartingFrom(int position); string_span findWordsStartingWith(utf16char ch); int findEqualCharsCount(int position, const utf16string *word); std::vector<Suggestion> prepareResult(); bool startsWithQuery(utf16string word); bool isExactMatch(utf16string replacement); std::vector<Result> _result; utf16string _initialQuery; const std::vector<utf16char> _query; const utf16char *_queryBegin = nullptr; int _querySize = 0; const std::vector<const Replacement*> *_initialList = nullptr; string_span _currentItemWords; int _currentItemWordsUsedCount = 0; class UsedWordGuard { public: UsedWordGuard(std::vector<small> &map, int index); UsedWordGuard(const UsedWordGuard &other) = delete; UsedWordGuard(UsedWordGuard &&other); UsedWordGuard &operator=(const UsedWordGuard &other) = delete; UsedWordGuard &operator=(UsedWordGuard &&other) = delete; explicit operator bool() const; ~UsedWordGuard(); private: std::vector<small> &_map; int _index = 0; small _guarded = 0; }; std::vector<small> _currentItemWordsUsedMap; }; Completer::UsedWordGuard::UsedWordGuard(std::vector<small> &map, int index) : _map(map), _index(index) { Expects(_map.size() > _index); if (!_map[_index]) { _guarded = _map[_index] = 1; } } Completer::UsedWordGuard::UsedWordGuard(UsedWordGuard &&other) : _map(other._map), _index(other._index), _guarded(other._guarded) { other._guarded = 0; } Completer::UsedWordGuard::operator bool() const { return _guarded; } Completer::UsedWordGuard::~UsedWordGuard() { if (_guarded) { _map[_index] = 0; } } Completer::Completer(utf16string query) : _initialQuery(query), _query(NormalizeQuery(query)) { } // Remove all non-letters-or-numbers. // Leave '-' and '+' only if they're followed by a number or // at the end of the query (so it is possibly followed by a number). std::vector<utf16char> Completer::NormalizeQuery(utf16string query) { auto result = std::vector<utf16char>(); result.reserve(query.size()); auto copyFrom = query.data(); auto e = copyFrom + query.size(); auto copyTo = result.data(); for (auto i = query.data(); i != e; ++i) { if (IsLetterOrNumber(*i)) { continue; } else if (*i == '-' || *i == '+') { if (i + 1 == e || IsNumber(*(i + 1))) { continue; } } if (i > copyFrom) { result.resize(result.size() + (i - copyFrom)); memcpy(copyTo, copyFrom, (i - copyFrom) * sizeof(utf16char)); copyTo += (i - copyFrom); } copyFrom = i + 1; } if (e > copyFrom) { result.resize(result.size() + (e - copyFrom)); memcpy(copyTo, copyFrom, (e - copyFrom) * sizeof(utf16char)); copyTo += (e - copyFrom); } return result; } std::vector<Suggestion> Completer::resolve() { _queryBegin = _query.data(); _querySize = _query.size(); if (!_querySize) { return std::vector<Suggestion>(); } _initialList = Ui::Emoji::internal::GetReplacements(*_queryBegin); if (!_initialList) { return std::vector<Suggestion>(); } _result.reserve(_initialList->size()); processInitialList(); return prepareResult(); } bool Completer::isDuplicateOfLastResult(const Replacement *item) const { if (_result.empty()) { return false; } return (_result.back().replacement->emoji == item->emoji); } bool Completer::isBetterThanLastResult(const Replacement *item) const { Expects(!_result.empty()); auto &last = _result.back(); if (_currentItemWordsUsedCount < last.wordsUsed) { return true; } auto firstCharOfQuery = _query[0]; auto firstCharAfterColonLast = last.replacement->replacement[1]; auto firstCharAfterColonCurrent = item->replacement[1]; auto goodLast = (firstCharAfterColonLast == firstCharOfQuery); auto goodCurrent = (firstCharAfterColonCurrent == firstCharOfQuery); return !goodLast && goodCurrent; } void Completer::addResult(const Replacement *item) { if (!isDuplicateOfLastResult(item)) { _result.push_back({ item, _currentItemWordsUsedCount }); } else if (isBetterThanLastResult(item)) { _result.back() = { item, _currentItemWordsUsedCount }; } } void Completer::processInitialList() { if (_querySize > 1) { filterInitialList(); } else { _currentItemWordsUsedCount = 1; for (auto item : *_initialList) { addResult(item); } } } void Completer::initWordsTracking() { auto maxWordsCount = 0; for (auto item : *_initialList) { auto wordsCount = item->words.size(); if (maxWordsCount < wordsCount) { maxWordsCount = wordsCount; } } _currentItemWordsUsedMap = std::vector<small>(maxWordsCount, 0); } void Completer::filterInitialList() { initWordsTracking(); for (auto item : *_initialList) { _currentItemWords = string_span(item->words); _currentItemWordsUsedCount = 1; if (matchQueryForCurrentItem()) { addResult(item); } _currentItemWordsUsedCount = 0; } } bool Completer::matchQueryForCurrentItem() { Expects(_currentItemWords.size() != 0); if (_currentItemWords.size() < 2) { return startsWithQuery(*_currentItemWords.begin()); } return matchQueryTailStartingFrom(0); } bool Completer::startsWithQuery(utf16string word) { if (word.size() < _query.size()) { return false; } for (auto i = std::size_t(0), size = _query.size(); i != size; ++i) { if (word[i] != _query[i]) { return false; } } return true; } bool Completer::isExactMatch(utf16string replacement) { if (replacement.size() != _initialQuery.size() + 1) { return false; } for (auto i = std::size_t(0), size = _initialQuery.size(); i != size; ++i) { if (replacement[i] != _initialQuery[i]) { return false; } } return true; } bool Completer::matchQueryTailStartingFrom(int position) { auto charsLeftToMatch = (_querySize - position); if (!charsLeftToMatch) { return true; } auto firstCharToMatch = *(_queryBegin + position); auto foundWords = findWordsStartingWith(firstCharToMatch); for (auto word = foundWords.begin(), foundWordsEnd = word + foundWords.size(); word != foundWordsEnd; ++word) { auto wordIndex = word - _currentItemWords.begin(); if (auto guard = UsedWordGuard(_currentItemWordsUsedMap, wordIndex)) { ++_currentItemWordsUsedCount; auto equalCharsCount = findEqualCharsCount(position, word); for (auto check = equalCharsCount; check != 0; --check) { if (matchQueryTailStartingFrom(position + check)) { return true; } } --_currentItemWordsUsedCount; } } return false; } int Completer::findEqualCharsCount(int position, const utf16string *word) { auto charsLeft = (_querySize - position); auto wordBegin = word->data(); auto wordSize = word->size(); auto possibleEqualCharsCount = (charsLeft > wordSize ? wordSize : charsLeft); for (auto equalTill = 1; equalTill != possibleEqualCharsCount; ++equalTill) { auto wordCh = *(wordBegin + equalTill); auto queryCh = *(_queryBegin + position + equalTill); if (wordCh != queryCh) { return equalTill; } } return possibleEqualCharsCount; } std::vector<Suggestion> Completer::prepareResult() { auto firstCharOfQuery = _query[0]; auto reorder = [&](auto &&predicate) { std::stable_partition( std::begin(_result), std::end(_result), std::forward<decltype(predicate)>(predicate)); }; reorder([firstCharOfQuery](Result &result) { auto firstCharAfterColon = result.replacement->replacement[1]; return (firstCharAfterColon == firstCharOfQuery); }); reorder([](Result &result) { return (result.wordsUsed < 2); }); reorder([](Result &result) { return (result.wordsUsed < 3); }); reorder([&](Result &result) { return isExactMatch(result.replacement->replacement); }); auto result = std::vector<Suggestion>(); result.reserve(_result.size()); for (auto &item : _result) { result.emplace_back( item.replacement->emoji, item.replacement->replacement, item.replacement->replacement); } return result; } string_span Completer::findWordsStartingWith(utf16char ch) { auto begin = std::lower_bound( std::begin(_currentItemWords), std::end(_currentItemWords), ch, [](utf16string word, utf16char ch) { return word[0] < ch; }); auto end = std::upper_bound( std::begin(_currentItemWords), std::end(_currentItemWords), ch, [](utf16char ch, utf16string word) { return ch < word[0]; }); return _currentItemWords.subspan( begin - _currentItemWords.begin(), end - begin); } } // namespace std::vector<Suggestion> GetSuggestions(utf16string query) { return Completer(query).resolve(); } int GetSuggestionMaxLength() { return internal::kReplacementMaxLength; } } // namespace Emoji } // namespace Ui ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/ThirdParty/emoji_suggestions/emoji_suggestions.h���������������������������0000664�0000000�0000000�00000004110�13262451251�0026731�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include <vector> namespace Ui { namespace Emoji { using small = unsigned char; using medium = unsigned short; using utf16char = unsigned short; static_assert(sizeof(utf16char) == 2, "Bad UTF-16 character size."); class utf16string { public: utf16string() = default; utf16string(const utf16char *data, std::size_t size) : data_(data), size_(size) { } utf16string(const utf16string &other) = default; utf16string &operator=(const utf16string &other) = default; const utf16char *data() const { return data_; } std::size_t size() const { return size_; } utf16char operator[](int index) const { return data_[index]; } private: const utf16char *data_ = nullptr; std::size_t size_ = 0; }; inline bool operator==(utf16string a, utf16string b) { return (a.size() == b.size()) && (!a.size() || !memcmp(a.data(), b.data(), a.size() * sizeof(utf16char))); } namespace internal { using checksum = unsigned int; checksum countChecksum(const void *data, std::size_t size); utf16string GetReplacementEmoji(utf16string replacement); } // namespace internal class Suggestion { public: Suggestion() = default; Suggestion(utf16string emoji, utf16string label, utf16string replacement) : emoji_(emoji), label_(label), replacement_(replacement) { } Suggestion(const Suggestion &other) = default; Suggestion &operator=(const Suggestion &other) = default; utf16string emoji() const { return emoji_; } utf16string label() const { return label_; } utf16string replacement() const { return replacement_; } private: utf16string emoji_; utf16string label_; utf16string replacement_; }; std::vector<Suggestion> GetSuggestions(utf16string query); inline utf16string GetSuggestionEmoji(utf16string replacement) { return internal::GetReplacementEmoji(replacement); } int GetSuggestionMaxLength(); } // namespace Emoji } // namespace Ui ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/ThirdParty/libtgvoip/������������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0021271�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/ThirdParty/minizip/��������������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0020751�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/ThirdParty/minizip/crypt.h�������������������������������������������������0000664�0000000�0000000�00000011177�13262451251�0022272�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* crypt.h -- base code for crypt/uncrypt ZIPfile Version 1.01e, February 12th, 2005 Copyright (C) 1998-2005 Gilles Vollant This code is a modified version of crypting code in Infozip distribution The encryption/decryption parts of this source code (as opposed to the non-echoing password parts) were originally written in Europe. The whole source package can be freely distributed, including from the USA. (Prior to January 2000, re-export from the US was a violation of US law.) This encryption code is a direct transcription of the algorithm from Roger Schlafly, described by Phil Katz in the file appnote.txt. This file (appnote.txt) is distributed with the PKZIP program (even in the version without encryption capabilities). If you don't need crypting in your application, just define symbols NOCRYPT and NOUNCRYPT. This code support the "Traditional PKWARE Encryption". The new AES encryption added on Zip format by Winzip (see the page http://www.winzip.com/aes_info.htm ) and PKWare PKZip 5.x Strong Encryption is not supported. */ #define CRC32(c, b) ((*(pcrc_32_tab+(((int)(c) ^ (b)) & 0xff))) ^ ((c) >> 8)) /*********************************************************************** * Return the next byte in the pseudo-random sequence */ static int decrypt_byte(unsigned long* pkeys, const z_crc_t* pcrc_32_tab) { unsigned temp; /* POTENTIAL BUG: temp*(temp^1) may overflow in an * unpredictable manner on 16-bit systems; not a problem * with any known compiler so far, though */ temp = ((unsigned)(*(pkeys+2)) & 0xffff) | 2; return (int)(((temp * (temp ^ 1)) >> 8) & 0xff); } /*********************************************************************** * Update the encryption keys with the next byte of plain text */ static int update_keys(unsigned long* pkeys,const z_crc_t* pcrc_32_tab,int c) { (*(pkeys+0)) = CRC32((*(pkeys+0)), c); (*(pkeys+1)) += (*(pkeys+0)) & 0xff; (*(pkeys+1)) = (*(pkeys+1)) * 134775813L + 1; { register int keyshift = (int)((*(pkeys+1)) >> 24); (*(pkeys+2)) = CRC32((*(pkeys+2)), keyshift); } return c; } /*********************************************************************** * Initialize the encryption keys and the random header according to * the given password. */ static void init_keys(const char* passwd,unsigned long* pkeys,const z_crc_t* pcrc_32_tab) { *(pkeys+0) = 305419896L; *(pkeys+1) = 591751049L; *(pkeys+2) = 878082192L; while (*passwd != '\0') { update_keys(pkeys,pcrc_32_tab,(int)*passwd); passwd++; } } #define zdecode(pkeys,pcrc_32_tab,c) \ (update_keys(pkeys,pcrc_32_tab,c ^= decrypt_byte(pkeys,pcrc_32_tab))) #define zencode(pkeys,pcrc_32_tab,c,t) \ (t=decrypt_byte(pkeys,pcrc_32_tab), update_keys(pkeys,pcrc_32_tab,c), t^(c)) #ifdef INCLUDECRYPTINGCODE_IFCRYPTALLOWED #define RAND_HEAD_LEN 12 /* "last resort" source for second part of crypt seed pattern */ # ifndef ZCR_SEED2 # define ZCR_SEED2 3141592654UL /* use PI as default pattern */ # endif static int crypthead(const char* passwd, /* password string */ unsigned char* buf, /* where to write header */ int bufSize, unsigned long* pkeys, const z_crc_t* pcrc_32_tab, unsigned long crcForCrypting) { int n; /* index in random header */ int t; /* temporary */ int c; /* random byte */ unsigned char header[RAND_HEAD_LEN-2]; /* random header */ static unsigned calls = 0; /* ensure different random header each time */ if (bufSize<RAND_HEAD_LEN) return 0; /* First generate RAND_HEAD_LEN-2 random bytes. We encrypt the * output of rand() to get less predictability, since rand() is * often poorly implemented. */ if (++calls == 1) { srand((unsigned)(time(NULL) ^ ZCR_SEED2)); } init_keys(passwd, pkeys, pcrc_32_tab); for (n = 0; n < RAND_HEAD_LEN-2; n++) { c = (rand() >> 7) & 0xff; header[n] = (unsigned char)zencode(pkeys, pcrc_32_tab, c, t); } /* Encrypt random header (last two bytes is high word of crc) */ init_keys(passwd, pkeys, pcrc_32_tab); for (n = 0; n < RAND_HEAD_LEN-2; n++) { buf[n] = (unsigned char)zencode(pkeys, pcrc_32_tab, header[n], t); } buf[n++] = (unsigned char)zencode(pkeys, pcrc_32_tab, (int)(crcForCrypting >> 16) & 0xff, t); buf[n++] = (unsigned char)zencode(pkeys, pcrc_32_tab, (int)(crcForCrypting >> 24) & 0xff, t); return n; } #endif �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/ThirdParty/minizip/ioapi.c�������������������������������������������������0000664�0000000�0000000�00000020041�13262451251�0022213�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ioapi.h -- IO base function header for compress/uncompress .zip part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html ) Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html ) Modifications for Zip64 support Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com ) For more info read MiniZip_info.txt */ #if defined(_WIN32) && (!(defined(_CRT_SECURE_NO_WARNINGS))) #define _CRT_SECURE_NO_WARNINGS #endif #if defined(__APPLE__) || defined(IOAPI_NO_64) // In darwin and perhaps other BSD variants off_t is a 64 bit value, hence no need for specific 64 bit functions #define FOPEN_FUNC(filename, mode) fopen(filename, mode) #define FTELLO_FUNC(stream) ftello(stream) #define FSEEKO_FUNC(stream, offset, origin) fseeko(stream, offset, origin) #else #define FOPEN_FUNC(filename, mode) fopen64(filename, mode) #define FTELLO_FUNC(stream) ftello64(stream) #define FSEEKO_FUNC(stream, offset, origin) fseeko64(stream, offset, origin) #endif #include "ioapi.h" voidpf call_zopen64 (const zlib_filefunc64_32_def* pfilefunc,const void*filename,int mode) { if (pfilefunc->zfile_func64.zopen64_file != NULL) return (*(pfilefunc->zfile_func64.zopen64_file)) (pfilefunc->zfile_func64.opaque,filename,mode); else { return (*(pfilefunc->zopen32_file))(pfilefunc->zfile_func64.opaque,(const char*)filename,mode); } } long call_zseek64 (const zlib_filefunc64_32_def* pfilefunc,voidpf filestream, ZPOS64_T offset, int origin) { if (pfilefunc->zfile_func64.zseek64_file != NULL) return (*(pfilefunc->zfile_func64.zseek64_file)) (pfilefunc->zfile_func64.opaque,filestream,offset,origin); else { uLong offsetTruncated = (uLong)offset; if (offsetTruncated != offset) return -1; else return (*(pfilefunc->zseek32_file))(pfilefunc->zfile_func64.opaque,filestream,offsetTruncated,origin); } } ZPOS64_T call_ztell64 (const zlib_filefunc64_32_def* pfilefunc,voidpf filestream) { if (pfilefunc->zfile_func64.zseek64_file != NULL) return (*(pfilefunc->zfile_func64.ztell64_file)) (pfilefunc->zfile_func64.opaque,filestream); else { uLong tell_uLong = (*(pfilefunc->ztell32_file))(pfilefunc->zfile_func64.opaque,filestream); if ((tell_uLong) == MAXU32) return (ZPOS64_T)-1; else return tell_uLong; } } void fill_zlib_filefunc64_32_def_from_filefunc32(zlib_filefunc64_32_def* p_filefunc64_32,const zlib_filefunc_def* p_filefunc32) { p_filefunc64_32->zfile_func64.zopen64_file = NULL; p_filefunc64_32->zopen32_file = p_filefunc32->zopen_file; p_filefunc64_32->zfile_func64.zerror_file = p_filefunc32->zerror_file; p_filefunc64_32->zfile_func64.zread_file = p_filefunc32->zread_file; p_filefunc64_32->zfile_func64.zwrite_file = p_filefunc32->zwrite_file; p_filefunc64_32->zfile_func64.ztell64_file = NULL; p_filefunc64_32->zfile_func64.zseek64_file = NULL; p_filefunc64_32->zfile_func64.zclose_file = p_filefunc32->zclose_file; p_filefunc64_32->zfile_func64.zerror_file = p_filefunc32->zerror_file; p_filefunc64_32->zfile_func64.opaque = p_filefunc32->opaque; p_filefunc64_32->zseek32_file = p_filefunc32->zseek_file; p_filefunc64_32->ztell32_file = p_filefunc32->ztell_file; } static voidpf ZCALLBACK fopen_file_func OF((voidpf opaque, const char* filename, int mode)); static uLong ZCALLBACK fread_file_func OF((voidpf opaque, voidpf stream, void* buf, uLong size)); static uLong ZCALLBACK fwrite_file_func OF((voidpf opaque, voidpf stream, const void* buf,uLong size)); static ZPOS64_T ZCALLBACK ftell64_file_func OF((voidpf opaque, voidpf stream)); static long ZCALLBACK fseek64_file_func OF((voidpf opaque, voidpf stream, ZPOS64_T offset, int origin)); static int ZCALLBACK fclose_file_func OF((voidpf opaque, voidpf stream)); static int ZCALLBACK ferror_file_func OF((voidpf opaque, voidpf stream)); static voidpf ZCALLBACK fopen_file_func (voidpf opaque, const char* filename, int mode) { FILE* file = NULL; const char* mode_fopen = NULL; if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER)==ZLIB_FILEFUNC_MODE_READ) mode_fopen = "rb"; else if (mode & ZLIB_FILEFUNC_MODE_EXISTING) mode_fopen = "r+b"; else if (mode & ZLIB_FILEFUNC_MODE_CREATE) mode_fopen = "wb"; if ((filename!=NULL) && (mode_fopen != NULL)) file = fopen(filename, mode_fopen); return file; } static voidpf ZCALLBACK fopen64_file_func (voidpf opaque, const void* filename, int mode) { FILE* file = NULL; const char* mode_fopen = NULL; if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER)==ZLIB_FILEFUNC_MODE_READ) mode_fopen = "rb"; else if (mode & ZLIB_FILEFUNC_MODE_EXISTING) mode_fopen = "r+b"; else if (mode & ZLIB_FILEFUNC_MODE_CREATE) mode_fopen = "wb"; if ((filename!=NULL) && (mode_fopen != NULL)) file = FOPEN_FUNC((const char*)filename, mode_fopen); return file; } static uLong ZCALLBACK fread_file_func (voidpf opaque, voidpf stream, void* buf, uLong size) { uLong ret; ret = (uLong)fread(buf, 1, (size_t)size, (FILE *)stream); return ret; } static uLong ZCALLBACK fwrite_file_func (voidpf opaque, voidpf stream, const void* buf, uLong size) { uLong ret; ret = (uLong)fwrite(buf, 1, (size_t)size, (FILE *)stream); return ret; } static long ZCALLBACK ftell_file_func (voidpf opaque, voidpf stream) { long ret; ret = ftell((FILE *)stream); return ret; } static ZPOS64_T ZCALLBACK ftell64_file_func (voidpf opaque, voidpf stream) { ZPOS64_T ret; ret = FTELLO_FUNC((FILE *)stream); return ret; } static long ZCALLBACK fseek_file_func (voidpf opaque, voidpf stream, uLong offset, int origin) { int fseek_origin=0; long ret; switch (origin) { case ZLIB_FILEFUNC_SEEK_CUR : fseek_origin = SEEK_CUR; break; case ZLIB_FILEFUNC_SEEK_END : fseek_origin = SEEK_END; break; case ZLIB_FILEFUNC_SEEK_SET : fseek_origin = SEEK_SET; break; default: return -1; } ret = 0; if (fseek((FILE *)stream, offset, fseek_origin) != 0) ret = -1; return ret; } static long ZCALLBACK fseek64_file_func (voidpf opaque, voidpf stream, ZPOS64_T offset, int origin) { int fseek_origin=0; long ret; switch (origin) { case ZLIB_FILEFUNC_SEEK_CUR : fseek_origin = SEEK_CUR; break; case ZLIB_FILEFUNC_SEEK_END : fseek_origin = SEEK_END; break; case ZLIB_FILEFUNC_SEEK_SET : fseek_origin = SEEK_SET; break; default: return -1; } ret = 0; if(FSEEKO_FUNC((FILE *)stream, offset, fseek_origin) != 0) ret = -1; return ret; } static int ZCALLBACK fclose_file_func (voidpf opaque, voidpf stream) { int ret; ret = fclose((FILE *)stream); return ret; } static int ZCALLBACK ferror_file_func (voidpf opaque, voidpf stream) { int ret; ret = ferror((FILE *)stream); return ret; } void fill_fopen_filefunc (pzlib_filefunc_def) zlib_filefunc_def* pzlib_filefunc_def; { pzlib_filefunc_def->zopen_file = fopen_file_func; pzlib_filefunc_def->zread_file = fread_file_func; pzlib_filefunc_def->zwrite_file = fwrite_file_func; pzlib_filefunc_def->ztell_file = ftell_file_func; pzlib_filefunc_def->zseek_file = fseek_file_func; pzlib_filefunc_def->zclose_file = fclose_file_func; pzlib_filefunc_def->zerror_file = ferror_file_func; pzlib_filefunc_def->opaque = NULL; } void fill_fopen64_filefunc (zlib_filefunc64_def* pzlib_filefunc_def) { pzlib_filefunc_def->zopen64_file = fopen64_file_func; pzlib_filefunc_def->zread_file = fread_file_func; pzlib_filefunc_def->zwrite_file = fwrite_file_func; pzlib_filefunc_def->ztell64_file = ftell64_file_func; pzlib_filefunc_def->zseek64_file = fseek64_file_func; pzlib_filefunc_def->zclose_file = fclose_file_func; pzlib_filefunc_def->zerror_file = ferror_file_func; pzlib_filefunc_def->opaque = NULL; } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/ThirdParty/minizip/ioapi.h�������������������������������������������������0000664�0000000�0000000�00000015613�13262451251�0022231�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ioapi.h -- IO base function header for compress/uncompress .zip part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html ) Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html ) Modifications for Zip64 support Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com ) For more info read MiniZip_info.txt Changes Oct-2009 - Defined ZPOS64_T to fpos_t on windows and u_int64_t on linux. (might need to find a better why for this) Oct-2009 - Change to fseeko64, ftello64 and fopen64 so large files would work on linux. More if/def section may be needed to support other platforms Oct-2009 - Defined fxxxx64 calls to normal fopen/ftell/fseek so they would compile on windows. (but you should use iowin32.c for windows instead) */ #ifndef _ZLIBIOAPI64_H #define _ZLIBIOAPI64_H #if (!defined(_WIN32)) && (!defined(WIN32)) && (!defined(__APPLE__)) // Linux needs this to support file operation on files larger then 4+GB // But might need better if/def to select just the platforms that needs them. #ifndef __USE_FILE_OFFSET64 #define __USE_FILE_OFFSET64 #endif #ifndef __USE_LARGEFILE64 #define __USE_LARGEFILE64 #endif #ifndef _LARGEFILE64_SOURCE #define _LARGEFILE64_SOURCE #endif #ifndef _FILE_OFFSET_BIT #define _FILE_OFFSET_BIT 64 #endif #endif #include <stdio.h> #include <stdlib.h> #include "zlib.h" #if defined(USE_FILE32API) #define fopen64 fopen #define ftello64 ftell #define fseeko64 fseek #else #ifdef __FreeBSD__ #define fopen64 fopen #define ftello64 ftello #define fseeko64 fseeko #endif #ifdef _MSC_VER #define fopen64 fopen #if (_MSC_VER >= 1400) && (!(defined(NO_MSCVER_FILE64_FUNC))) #define ftello64 _ftelli64 #define fseeko64 _fseeki64 #else // old MSC #define ftello64 ftell #define fseeko64 fseek #endif #endif #endif /* #ifndef ZPOS64_T #ifdef _WIN32 #define ZPOS64_T fpos_t #else #include <stdint.h> #define ZPOS64_T uint64_t #endif #endif */ #ifdef HAVE_MINIZIP64_CONF_H #include "mz64conf.h" #endif /* a type choosen by DEFINE */ #ifdef HAVE_64BIT_INT_CUSTOM typedef 64BIT_INT_CUSTOM_TYPE ZPOS64_T; #else #ifdef HAS_STDINT_H #include "stdint.h" typedef uint64_t ZPOS64_T; #else /* Maximum unsigned 32-bit value used as placeholder for zip64 */ #define MAXU32 0xffffffff #if defined(_MSC_VER) || defined(__BORLANDC__) typedef unsigned __int64 ZPOS64_T; #else typedef unsigned long long int ZPOS64_T; #endif #endif #endif #ifdef __cplusplus extern "C" { #endif #define ZLIB_FILEFUNC_SEEK_CUR (1) #define ZLIB_FILEFUNC_SEEK_END (2) #define ZLIB_FILEFUNC_SEEK_SET (0) #define ZLIB_FILEFUNC_MODE_READ (1) #define ZLIB_FILEFUNC_MODE_WRITE (2) #define ZLIB_FILEFUNC_MODE_READWRITEFILTER (3) #define ZLIB_FILEFUNC_MODE_EXISTING (4) #define ZLIB_FILEFUNC_MODE_CREATE (8) #ifndef ZCALLBACK #if (defined(WIN32) || defined(_WIN32) || defined (WINDOWS) || defined (_WINDOWS)) && defined(CALLBACK) && defined (USEWINDOWS_CALLBACK) #define ZCALLBACK CALLBACK #else #define ZCALLBACK #endif #endif typedef voidpf (ZCALLBACK *open_file_func) OF((voidpf opaque, const char* filename, int mode)); typedef uLong (ZCALLBACK *read_file_func) OF((voidpf opaque, voidpf stream, void* buf, uLong size)); typedef uLong (ZCALLBACK *write_file_func) OF((voidpf opaque, voidpf stream, const void* buf, uLong size)); typedef int (ZCALLBACK *close_file_func) OF((voidpf opaque, voidpf stream)); typedef int (ZCALLBACK *testerror_file_func) OF((voidpf opaque, voidpf stream)); typedef long (ZCALLBACK *tell_file_func) OF((voidpf opaque, voidpf stream)); typedef long (ZCALLBACK *seek_file_func) OF((voidpf opaque, voidpf stream, uLong offset, int origin)); /* here is the "old" 32 bits structure structure */ typedef struct zlib_filefunc_def_s { open_file_func zopen_file; read_file_func zread_file; write_file_func zwrite_file; tell_file_func ztell_file; seek_file_func zseek_file; close_file_func zclose_file; testerror_file_func zerror_file; voidpf opaque; } zlib_filefunc_def; typedef ZPOS64_T (ZCALLBACK *tell64_file_func) OF((voidpf opaque, voidpf stream)); typedef long (ZCALLBACK *seek64_file_func) OF((voidpf opaque, voidpf stream, ZPOS64_T offset, int origin)); typedef voidpf (ZCALLBACK *open64_file_func) OF((voidpf opaque, const void* filename, int mode)); typedef struct zlib_filefunc64_def_s { open64_file_func zopen64_file; read_file_func zread_file; write_file_func zwrite_file; tell64_file_func ztell64_file; seek64_file_func zseek64_file; close_file_func zclose_file; testerror_file_func zerror_file; voidpf opaque; } zlib_filefunc64_def; void fill_fopen64_filefunc OF((zlib_filefunc64_def* pzlib_filefunc_def)); void fill_fopen_filefunc OF((zlib_filefunc_def* pzlib_filefunc_def)); /* now internal definition, only for zip.c and unzip.h */ typedef struct zlib_filefunc64_32_def_s { zlib_filefunc64_def zfile_func64; open_file_func zopen32_file; tell_file_func ztell32_file; seek_file_func zseek32_file; } zlib_filefunc64_32_def; #define ZREAD64(filefunc,filestream,buf,size) ((*((filefunc).zfile_func64.zread_file)) ((filefunc).zfile_func64.opaque,filestream,buf,size)) #define ZWRITE64(filefunc,filestream,buf,size) ((*((filefunc).zfile_func64.zwrite_file)) ((filefunc).zfile_func64.opaque,filestream,buf,size)) //#define ZTELL64(filefunc,filestream) ((*((filefunc).ztell64_file)) ((filefunc).opaque,filestream)) //#define ZSEEK64(filefunc,filestream,pos,mode) ((*((filefunc).zseek64_file)) ((filefunc).opaque,filestream,pos,mode)) #define ZCLOSE64(filefunc,filestream) ((*((filefunc).zfile_func64.zclose_file)) ((filefunc).zfile_func64.opaque,filestream)) #define ZERROR64(filefunc,filestream) ((*((filefunc).zfile_func64.zerror_file)) ((filefunc).zfile_func64.opaque,filestream)) voidpf call_zopen64 OF((const zlib_filefunc64_32_def* pfilefunc,const void*filename,int mode)); long call_zseek64 OF((const zlib_filefunc64_32_def* pfilefunc,voidpf filestream, ZPOS64_T offset, int origin)); ZPOS64_T call_ztell64 OF((const zlib_filefunc64_32_def* pfilefunc,voidpf filestream)); void fill_zlib_filefunc64_32_def_from_filefunc32(zlib_filefunc64_32_def* p_filefunc64_32,const zlib_filefunc_def* p_filefunc32); #define ZOPEN64(filefunc,filename,mode) (call_zopen64((&(filefunc)),(filename),(mode))) #define ZTELL64(filefunc,filestream) (call_ztell64((&(filefunc)),(filestream))) #define ZSEEK64(filefunc,filestream,pos,mode) (call_zseek64((&(filefunc)),(filestream),(pos),(mode))) #ifdef __cplusplus } #endif #endif ���������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/ThirdParty/minizip/unzip.c�������������������������������������������������0000664�0000000�0000000�00000212616�13262451251�0022272�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* unzip.c -- IO for uncompress .zip files using zlib Version 1.1, February 14h, 2010 part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html ) Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html ) Modifications of Unzip for Zip64 Copyright (C) 2007-2008 Even Rouault Modifications for Zip64 support on both zip and unzip Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com ) For more info read MiniZip_info.txt ------------------------------------------------------------------------------------ Decryption code comes from crypt.c by Info-ZIP but has been greatly reduced in terms of compatibility with older software. The following is from the original crypt.c. Code woven in by Terry Thorsen 1/2003. Copyright (c) 1990-2000 Info-ZIP. All rights reserved. See the accompanying file LICENSE, version 2000-Apr-09 or later (the contents of which are also included in zip.h) for terms of use. If, for some reason, all these files are missing, the Info-ZIP license also may be found at: ftp://ftp.info-zip.org/pub/infozip/license.html crypt.c (full version) by Info-ZIP. Last revised: [see crypt.h] The encryption/decryption parts of this source code (as opposed to the non-echoing password parts) were originally written in Europe. The whole source package can be freely distributed, including from the USA. (Prior to January 2000, re-export from the US was a violation of US law.) This encryption code is a direct transcription of the algorithm from Roger Schlafly, described by Phil Katz in the file appnote.txt. This file (appnote.txt) is distributed with the PKZIP program (even in the version without encryption capabilities). ------------------------------------------------------------------------------------ Changes in unzip.c 2007-2008 - Even Rouault - Addition of cpl_unzGetCurrentFileZStreamPos 2007-2008 - Even Rouault - Decoration of symbol names unz* -> cpl_unz* 2007-2008 - Even Rouault - Remove old C style function prototypes 2007-2008 - Even Rouault - Add unzip support for ZIP64 Copyright (C) 2007-2008 Even Rouault Oct-2009 - Mathias Svensson - Removed cpl_* from symbol names (Even Rouault added them but since this is now moved to a new project (minizip64) I renamed them again). Oct-2009 - Mathias Svensson - Fixed problem if uncompressed size was > 4G and compressed size was <4G should only read the compressed/uncompressed size from the Zip64 format if the size from normal header was 0xFFFFFFFF Oct-2009 - Mathias Svensson - Applied some bug fixes from paches recived from Gilles Vollant Oct-2009 - Mathias Svensson - Applied support to unzip files with compression mathod BZIP2 (bzip2 lib is required) Patch created by Daniel Borca Jan-2010 - back to unzip and minizip 1.0 name scheme, with compatibility layer Copyright (C) 1998 - 2010 Gilles Vollant, Even Rouault, Mathias Svensson */ #include <stdio.h> #include <stdlib.h> #include <string.h> #ifndef NOUNCRYPT #define NOUNCRYPT #endif #include "zlib.h" #include "unzip.h" #ifdef STDC # include <stddef.h> # include <string.h> # include <stdlib.h> #endif #ifdef NO_ERRNO_H extern int errno; #else # include <errno.h> #endif #ifndef local # define local static #endif /* compile with -Dlocal if your debugger can't find static symbols */ #ifndef CASESENSITIVITYDEFAULT_NO # if !defined(unix) && !defined(CASESENSITIVITYDEFAULT_YES) # define CASESENSITIVITYDEFAULT_NO # endif #endif #ifndef UNZ_BUFSIZE #define UNZ_BUFSIZE (16384) #endif #ifndef UNZ_MAXFILENAMEINZIP #define UNZ_MAXFILENAMEINZIP (256) #endif #ifndef ALLOC # define ALLOC(size) (malloc(size)) #endif #ifndef TRYFREE # define TRYFREE(p) {if (p) free(p);} #endif #define SIZECENTRALDIRITEM (0x2e) #define SIZEZIPLOCALHEADER (0x1e) const char unz_copyright[] = " unzip 1.01 Copyright 1998-2004 Gilles Vollant - http://www.winimage.com/zLibDll"; /* unz_file_info_interntal contain internal info about a file in zipfile*/ typedef struct unz_file_info64_internal_s { ZPOS64_T offset_curfile;/* relative offset of local header 8 bytes */ } unz_file_info64_internal; /* file_in_zip_read_info_s contain internal information about a file in zipfile, when reading and decompress it */ typedef struct { char *read_buffer; /* internal buffer for compressed data */ z_stream stream; /* zLib stream structure for inflate */ #ifdef HAVE_BZIP2 bz_stream bstream; /* bzLib stream structure for bziped */ #endif ZPOS64_T pos_in_zipfile; /* position in byte on the zipfile, for fseek*/ uLong stream_initialised; /* flag set if stream structure is initialised*/ ZPOS64_T offset_local_extrafield;/* offset of the local extra field */ uInt size_local_extrafield;/* size of the local extra field */ ZPOS64_T pos_local_extrafield; /* position in the local extra field in read*/ ZPOS64_T total_out_64; uLong crc32; /* crc32 of all data uncompressed */ uLong crc32_wait; /* crc32 we must obtain after decompress all */ ZPOS64_T rest_read_compressed; /* number of byte to be decompressed */ ZPOS64_T rest_read_uncompressed;/*number of byte to be obtained after decomp*/ zlib_filefunc64_32_def z_filefunc; voidpf filestream; /* io structore of the zipfile */ uLong compression_method; /* compression method (0==store) */ ZPOS64_T byte_before_the_zipfile;/* byte before the zipfile, (>0 for sfx)*/ int raw; } file_in_zip64_read_info_s; /* unz64_s contain internal information about the zipfile */ typedef struct { zlib_filefunc64_32_def z_filefunc; int is64bitOpenFunction; voidpf filestream; /* io structore of the zipfile */ unz_global_info64 gi; /* public global information */ ZPOS64_T byte_before_the_zipfile;/* byte before the zipfile, (>0 for sfx)*/ ZPOS64_T num_file; /* number of the current file in the zipfile*/ ZPOS64_T pos_in_central_dir; /* pos of the current file in the central dir*/ ZPOS64_T current_file_ok; /* flag about the usability of the current file*/ ZPOS64_T central_pos; /* position of the beginning of the central dir*/ ZPOS64_T size_central_dir; /* size of the central directory */ ZPOS64_T offset_central_dir; /* offset of start of central directory with respect to the starting disk number */ unz_file_info64 cur_file_info; /* public info about the current file in zip*/ unz_file_info64_internal cur_file_info_internal; /* private info about it*/ file_in_zip64_read_info_s* pfile_in_zip_read; /* structure about the current file if we are decompressing it */ int encrypted; int isZip64; # ifndef NOUNCRYPT unsigned long keys[3]; /* keys defining the pseudo-random sequence */ const z_crc_t* pcrc_32_tab; # endif } unz64_s; #ifndef NOUNCRYPT #include "crypt.h" #endif /* =========================================================================== Read a byte from a gz_stream; update next_in and avail_in. Return EOF for end of file. IN assertion: the stream s has been sucessfully opened for reading. */ local int unz64local_getByte OF(( const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, int *pi)); local int unz64local_getByte(const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, int *pi) { unsigned char c; int err = (int)ZREAD64(*pzlib_filefunc_def,filestream,&c,1); if (err==1) { *pi = (int)c; return UNZ_OK; } else { if (ZERROR64(*pzlib_filefunc_def,filestream)) return UNZ_ERRNO; else return UNZ_EOF; } } /* =========================================================================== Reads a long in LSB order from the given gz_stream. Sets */ local int unz64local_getShort OF(( const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong *pX)); local int unz64local_getShort (const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong *pX) { uLong x ; int i = 0; int err; err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); x = (uLong)i; if (err==UNZ_OK) err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); x |= ((uLong)i)<<8; if (err==UNZ_OK) *pX = x; else *pX = 0; return err; } local int unz64local_getLong OF(( const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong *pX)); local int unz64local_getLong (const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong *pX) { uLong x ; int i = 0; int err; err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); x = (uLong)i; if (err==UNZ_OK) err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); x |= ((uLong)i)<<8; if (err==UNZ_OK) err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); x |= ((uLong)i)<<16; if (err==UNZ_OK) err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); x += ((uLong)i)<<24; if (err==UNZ_OK) *pX = x; else *pX = 0; return err; } local int unz64local_getLong64 OF(( const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, ZPOS64_T *pX)); local int unz64local_getLong64 (const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, ZPOS64_T *pX) { ZPOS64_T x ; int i = 0; int err; err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); x = (ZPOS64_T)i; if (err==UNZ_OK) err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); x |= ((ZPOS64_T)i)<<8; if (err==UNZ_OK) err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); x |= ((ZPOS64_T)i)<<16; if (err==UNZ_OK) err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); x |= ((ZPOS64_T)i)<<24; if (err==UNZ_OK) err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); x |= ((ZPOS64_T)i)<<32; if (err==UNZ_OK) err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); x |= ((ZPOS64_T)i)<<40; if (err==UNZ_OK) err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); x |= ((ZPOS64_T)i)<<48; if (err==UNZ_OK) err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); x |= ((ZPOS64_T)i)<<56; if (err==UNZ_OK) *pX = x; else *pX = 0; return err; } /* My own strcmpi / strcasecmp */ local int strcmpcasenosensitive_internal (const char* fileName1, const char* fileName2) { for (;;) { char c1=*(fileName1++); char c2=*(fileName2++); if ((c1>='a') && (c1<='z')) c1 -= 0x20; if ((c2>='a') && (c2<='z')) c2 -= 0x20; if (c1=='\0') return ((c2=='\0') ? 0 : -1); if (c2=='\0') return 1; if (c1<c2) return -1; if (c1>c2) return 1; } } #ifdef CASESENSITIVITYDEFAULT_NO #define CASESENSITIVITYDEFAULTVALUE 2 #else #define CASESENSITIVITYDEFAULTVALUE 1 #endif #ifndef STRCMPCASENOSENTIVEFUNCTION #define STRCMPCASENOSENTIVEFUNCTION strcmpcasenosensitive_internal #endif /* Compare two filename (fileName1,fileName2). If iCaseSenisivity = 1, comparision is case sensitivity (like strcmp) If iCaseSenisivity = 2, comparision is not case sensitivity (like strcmpi or strcasecmp) If iCaseSenisivity = 0, case sensitivity is defaut of your operating system (like 1 on Unix, 2 on Windows) */ extern int ZEXPORT unzStringFileNameCompare (const char* fileName1, const char* fileName2, int iCaseSensitivity) { if (iCaseSensitivity==0) iCaseSensitivity=CASESENSITIVITYDEFAULTVALUE; if (iCaseSensitivity==1) return strcmp(fileName1,fileName2); return STRCMPCASENOSENTIVEFUNCTION(fileName1,fileName2); } #ifndef BUFREADCOMMENT #define BUFREADCOMMENT (0x400) #endif /* Locate the Central directory of a zipfile (at the end, just before the global comment) */ local ZPOS64_T unz64local_SearchCentralDir OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream)); local ZPOS64_T unz64local_SearchCentralDir(const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream) { unsigned char* buf; ZPOS64_T uSizeFile; ZPOS64_T uBackRead; ZPOS64_T uMaxBack=0xffff; /* maximum size of global comment */ ZPOS64_T uPosFound=0; if (ZSEEK64(*pzlib_filefunc_def,filestream,0,ZLIB_FILEFUNC_SEEK_END) != 0) return 0; uSizeFile = ZTELL64(*pzlib_filefunc_def,filestream); if (uMaxBack>uSizeFile) uMaxBack = uSizeFile; buf = (unsigned char*)ALLOC(BUFREADCOMMENT+4); if (buf==NULL) return 0; uBackRead = 4; while (uBackRead<uMaxBack) { uLong uReadSize; ZPOS64_T uReadPos ; int i; if (uBackRead+BUFREADCOMMENT>uMaxBack) uBackRead = uMaxBack; else uBackRead+=BUFREADCOMMENT; uReadPos = uSizeFile-uBackRead ; uReadSize = ((BUFREADCOMMENT+4) < (uSizeFile-uReadPos)) ? (BUFREADCOMMENT+4) : (uLong)(uSizeFile-uReadPos); if (ZSEEK64(*pzlib_filefunc_def,filestream,uReadPos,ZLIB_FILEFUNC_SEEK_SET)!=0) break; if (ZREAD64(*pzlib_filefunc_def,filestream,buf,uReadSize)!=uReadSize) break; for (i=(int)uReadSize-3; (i--)>0;) if (((*(buf+i))==0x50) && ((*(buf+i+1))==0x4b) && ((*(buf+i+2))==0x05) && ((*(buf+i+3))==0x06)) { uPosFound = uReadPos+i; break; } if (uPosFound!=0) break; } TRYFREE(buf); return uPosFound; } /* Locate the Central directory 64 of a zipfile (at the end, just before the global comment) */ local ZPOS64_T unz64local_SearchCentralDir64 OF(( const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream)); local ZPOS64_T unz64local_SearchCentralDir64(const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream) { unsigned char* buf; ZPOS64_T uSizeFile; ZPOS64_T uBackRead; ZPOS64_T uMaxBack=0xffff; /* maximum size of global comment */ ZPOS64_T uPosFound=0; uLong uL; ZPOS64_T relativeOffset; if (ZSEEK64(*pzlib_filefunc_def,filestream,0,ZLIB_FILEFUNC_SEEK_END) != 0) return 0; uSizeFile = ZTELL64(*pzlib_filefunc_def,filestream); if (uMaxBack>uSizeFile) uMaxBack = uSizeFile; buf = (unsigned char*)ALLOC(BUFREADCOMMENT+4); if (buf==NULL) return 0; uBackRead = 4; while (uBackRead<uMaxBack) { uLong uReadSize; ZPOS64_T uReadPos; int i; if (uBackRead+BUFREADCOMMENT>uMaxBack) uBackRead = uMaxBack; else uBackRead+=BUFREADCOMMENT; uReadPos = uSizeFile-uBackRead ; uReadSize = ((BUFREADCOMMENT+4) < (uSizeFile-uReadPos)) ? (BUFREADCOMMENT+4) : (uLong)(uSizeFile-uReadPos); if (ZSEEK64(*pzlib_filefunc_def,filestream,uReadPos,ZLIB_FILEFUNC_SEEK_SET)!=0) break; if (ZREAD64(*pzlib_filefunc_def,filestream,buf,uReadSize)!=uReadSize) break; for (i=(int)uReadSize-3; (i--)>0;) if (((*(buf+i))==0x50) && ((*(buf+i+1))==0x4b) && ((*(buf+i+2))==0x06) && ((*(buf+i+3))==0x07)) { uPosFound = uReadPos+i; break; } if (uPosFound!=0) break; } TRYFREE(buf); if (uPosFound == 0) return 0; /* Zip64 end of central directory locator */ if (ZSEEK64(*pzlib_filefunc_def,filestream, uPosFound,ZLIB_FILEFUNC_SEEK_SET)!=0) return 0; /* the signature, already checked */ if (unz64local_getLong(pzlib_filefunc_def,filestream,&uL)!=UNZ_OK) return 0; /* number of the disk with the start of the zip64 end of central directory */ if (unz64local_getLong(pzlib_filefunc_def,filestream,&uL)!=UNZ_OK) return 0; if (uL != 0) return 0; /* relative offset of the zip64 end of central directory record */ if (unz64local_getLong64(pzlib_filefunc_def,filestream,&relativeOffset)!=UNZ_OK) return 0; /* total number of disks */ if (unz64local_getLong(pzlib_filefunc_def,filestream,&uL)!=UNZ_OK) return 0; if (uL != 1) return 0; /* Goto end of central directory record */ if (ZSEEK64(*pzlib_filefunc_def,filestream, relativeOffset,ZLIB_FILEFUNC_SEEK_SET)!=0) return 0; /* the signature */ if (unz64local_getLong(pzlib_filefunc_def,filestream,&uL)!=UNZ_OK) return 0; if (uL != 0x06064b50) return 0; return relativeOffset; } /* Open a Zip file. path contain the full pathname (by example, on a Windows NT computer "c:\\test\\zlib114.zip" or on an Unix computer "zlib/zlib114.zip". If the zipfile cannot be opened (file doesn't exist or in not valid), the return value is NULL. Else, the return value is a unzFile Handle, usable with other function of this unzip package. */ local unzFile unzOpenInternal (const void *path, zlib_filefunc64_32_def* pzlib_filefunc64_32_def, int is64bitOpenFunction) { unz64_s us; unz64_s *s; ZPOS64_T central_pos; uLong uL; uLong number_disk; /* number of the current dist, used for spaning ZIP, unsupported, always 0*/ uLong number_disk_with_CD; /* number the the disk with central dir, used for spaning ZIP, unsupported, always 0*/ ZPOS64_T number_entry_CD; /* total number of entries in the central dir (same than number_entry on nospan) */ int err=UNZ_OK; if (unz_copyright[0]!=' ') return NULL; us.z_filefunc.zseek32_file = NULL; us.z_filefunc.ztell32_file = NULL; if (pzlib_filefunc64_32_def==NULL) fill_fopen64_filefunc(&us.z_filefunc.zfile_func64); else us.z_filefunc = *pzlib_filefunc64_32_def; us.is64bitOpenFunction = is64bitOpenFunction; us.filestream = ZOPEN64(us.z_filefunc, path, ZLIB_FILEFUNC_MODE_READ | ZLIB_FILEFUNC_MODE_EXISTING); if (us.filestream==NULL) return NULL; central_pos = unz64local_SearchCentralDir64(&us.z_filefunc,us.filestream); if (central_pos) { uLong uS; ZPOS64_T uL64; us.isZip64 = 1; if (ZSEEK64(us.z_filefunc, us.filestream, central_pos,ZLIB_FILEFUNC_SEEK_SET)!=0) err=UNZ_ERRNO; /* the signature, already checked */ if (unz64local_getLong(&us.z_filefunc, us.filestream,&uL)!=UNZ_OK) err=UNZ_ERRNO; /* size of zip64 end of central directory record */ if (unz64local_getLong64(&us.z_filefunc, us.filestream,&uL64)!=UNZ_OK) err=UNZ_ERRNO; /* version made by */ if (unz64local_getShort(&us.z_filefunc, us.filestream,&uS)!=UNZ_OK) err=UNZ_ERRNO; /* version needed to extract */ if (unz64local_getShort(&us.z_filefunc, us.filestream,&uS)!=UNZ_OK) err=UNZ_ERRNO; /* number of this disk */ if (unz64local_getLong(&us.z_filefunc, us.filestream,&number_disk)!=UNZ_OK) err=UNZ_ERRNO; /* number of the disk with the start of the central directory */ if (unz64local_getLong(&us.z_filefunc, us.filestream,&number_disk_with_CD)!=UNZ_OK) err=UNZ_ERRNO; /* total number of entries in the central directory on this disk */ if (unz64local_getLong64(&us.z_filefunc, us.filestream,&us.gi.number_entry)!=UNZ_OK) err=UNZ_ERRNO; /* total number of entries in the central directory */ if (unz64local_getLong64(&us.z_filefunc, us.filestream,&number_entry_CD)!=UNZ_OK) err=UNZ_ERRNO; if ((number_entry_CD!=us.gi.number_entry) || (number_disk_with_CD!=0) || (number_disk!=0)) err=UNZ_BADZIPFILE; /* size of the central directory */ if (unz64local_getLong64(&us.z_filefunc, us.filestream,&us.size_central_dir)!=UNZ_OK) err=UNZ_ERRNO; /* offset of start of central directory with respect to the starting disk number */ if (unz64local_getLong64(&us.z_filefunc, us.filestream,&us.offset_central_dir)!=UNZ_OK) err=UNZ_ERRNO; us.gi.size_comment = 0; } else { central_pos = unz64local_SearchCentralDir(&us.z_filefunc,us.filestream); if (central_pos==0) err=UNZ_ERRNO; us.isZip64 = 0; if (ZSEEK64(us.z_filefunc, us.filestream, central_pos,ZLIB_FILEFUNC_SEEK_SET)!=0) err=UNZ_ERRNO; /* the signature, already checked */ if (unz64local_getLong(&us.z_filefunc, us.filestream,&uL)!=UNZ_OK) err=UNZ_ERRNO; /* number of this disk */ if (unz64local_getShort(&us.z_filefunc, us.filestream,&number_disk)!=UNZ_OK) err=UNZ_ERRNO; /* number of the disk with the start of the central directory */ if (unz64local_getShort(&us.z_filefunc, us.filestream,&number_disk_with_CD)!=UNZ_OK) err=UNZ_ERRNO; /* total number of entries in the central dir on this disk */ if (unz64local_getShort(&us.z_filefunc, us.filestream,&uL)!=UNZ_OK) err=UNZ_ERRNO; us.gi.number_entry = uL; /* total number of entries in the central dir */ if (unz64local_getShort(&us.z_filefunc, us.filestream,&uL)!=UNZ_OK) err=UNZ_ERRNO; number_entry_CD = uL; if ((number_entry_CD!=us.gi.number_entry) || (number_disk_with_CD!=0) || (number_disk!=0)) err=UNZ_BADZIPFILE; /* size of the central directory */ if (unz64local_getLong(&us.z_filefunc, us.filestream,&uL)!=UNZ_OK) err=UNZ_ERRNO; us.size_central_dir = uL; /* offset of start of central directory with respect to the starting disk number */ if (unz64local_getLong(&us.z_filefunc, us.filestream,&uL)!=UNZ_OK) err=UNZ_ERRNO; us.offset_central_dir = uL; /* zipfile comment length */ if (unz64local_getShort(&us.z_filefunc, us.filestream,&us.gi.size_comment)!=UNZ_OK) err=UNZ_ERRNO; } if ((central_pos<us.offset_central_dir+us.size_central_dir) && (err==UNZ_OK)) err=UNZ_BADZIPFILE; if (err!=UNZ_OK) { ZCLOSE64(us.z_filefunc, us.filestream); return NULL; } us.byte_before_the_zipfile = central_pos - (us.offset_central_dir+us.size_central_dir); us.central_pos = central_pos; us.pfile_in_zip_read = NULL; us.encrypted = 0; s=(unz64_s*)ALLOC(sizeof(unz64_s)); if( s != NULL) { *s=us; unzGoToFirstFile((unzFile)s); } return (unzFile)s; } extern unzFile ZEXPORT unzOpen2 (const char *path, zlib_filefunc_def* pzlib_filefunc32_def) { if (pzlib_filefunc32_def != NULL) { zlib_filefunc64_32_def zlib_filefunc64_32_def_fill; fill_zlib_filefunc64_32_def_from_filefunc32(&zlib_filefunc64_32_def_fill,pzlib_filefunc32_def); return unzOpenInternal(path, &zlib_filefunc64_32_def_fill, 0); } else return unzOpenInternal(path, NULL, 0); } extern unzFile ZEXPORT unzOpen2_64 (const void *path, zlib_filefunc64_def* pzlib_filefunc_def) { if (pzlib_filefunc_def != NULL) { zlib_filefunc64_32_def zlib_filefunc64_32_def_fill; zlib_filefunc64_32_def_fill.zfile_func64 = *pzlib_filefunc_def; zlib_filefunc64_32_def_fill.ztell32_file = NULL; zlib_filefunc64_32_def_fill.zseek32_file = NULL; return unzOpenInternal(path, &zlib_filefunc64_32_def_fill, 1); } else return unzOpenInternal(path, NULL, 1); } extern unzFile ZEXPORT unzOpen (const char *path) { return unzOpenInternal(path, NULL, 0); } extern unzFile ZEXPORT unzOpen64 (const void *path) { return unzOpenInternal(path, NULL, 1); } /* Close a ZipFile opened with unzOpen. If there is files inside the .Zip opened with unzOpenCurrentFile (see later), these files MUST be closed with unzCloseCurrentFile before call unzClose. return UNZ_OK if there is no problem. */ extern int ZEXPORT unzClose (unzFile file) { unz64_s* s; if (file==NULL) return UNZ_PARAMERROR; s=(unz64_s*)file; if (s->pfile_in_zip_read!=NULL) unzCloseCurrentFile(file); ZCLOSE64(s->z_filefunc, s->filestream); TRYFREE(s); return UNZ_OK; } /* Write info about the ZipFile in the *pglobal_info structure. No preparation of the structure is needed return UNZ_OK if there is no problem. */ extern int ZEXPORT unzGetGlobalInfo64 (unzFile file, unz_global_info64* pglobal_info) { unz64_s* s; if (file==NULL) return UNZ_PARAMERROR; s=(unz64_s*)file; *pglobal_info=s->gi; return UNZ_OK; } extern int ZEXPORT unzGetGlobalInfo (unzFile file, unz_global_info* pglobal_info32) { unz64_s* s; if (file==NULL) return UNZ_PARAMERROR; s=(unz64_s*)file; /* to do : check if number_entry is not truncated */ pglobal_info32->number_entry = (uLong)s->gi.number_entry; pglobal_info32->size_comment = s->gi.size_comment; return UNZ_OK; } /* Translate date/time from Dos format to tm_unz (readable more easilty) */ local void unz64local_DosDateToTmuDate (ZPOS64_T ulDosDate, tm_unz* ptm) { ZPOS64_T uDate; uDate = (ZPOS64_T)(ulDosDate>>16); ptm->tm_mday = (uInt)(uDate&0x1f) ; ptm->tm_mon = (uInt)((((uDate)&0x1E0)/0x20)-1) ; ptm->tm_year = (uInt)(((uDate&0x0FE00)/0x0200)+1980) ; ptm->tm_hour = (uInt) ((ulDosDate &0xF800)/0x800); ptm->tm_min = (uInt) ((ulDosDate&0x7E0)/0x20) ; ptm->tm_sec = (uInt) (2*(ulDosDate&0x1f)) ; } /* Get Info about the current file in the zipfile, with internal only info */ local int unz64local_GetCurrentFileInfoInternal OF((unzFile file, unz_file_info64 *pfile_info, unz_file_info64_internal *pfile_info_internal, char *szFileName, uLong fileNameBufferSize, void *extraField, uLong extraFieldBufferSize, char *szComment, uLong commentBufferSize)); local int unz64local_GetCurrentFileInfoInternal (unzFile file, unz_file_info64 *pfile_info, unz_file_info64_internal *pfile_info_internal, char *szFileName, uLong fileNameBufferSize, void *extraField, uLong extraFieldBufferSize, char *szComment, uLong commentBufferSize) { unz64_s* s; unz_file_info64 file_info; unz_file_info64_internal file_info_internal; int err=UNZ_OK; uLong uMagic; long lSeek=0; uLong uL; if (file==NULL) return UNZ_PARAMERROR; s=(unz64_s*)file; if (ZSEEK64(s->z_filefunc, s->filestream, s->pos_in_central_dir+s->byte_before_the_zipfile, ZLIB_FILEFUNC_SEEK_SET)!=0) err=UNZ_ERRNO; /* we check the magic */ if (err==UNZ_OK) { if (unz64local_getLong(&s->z_filefunc, s->filestream,&uMagic) != UNZ_OK) err=UNZ_ERRNO; else if (uMagic!=0x02014b50) err=UNZ_BADZIPFILE; } if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.version) != UNZ_OK) err=UNZ_ERRNO; if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.version_needed) != UNZ_OK) err=UNZ_ERRNO; if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.flag) != UNZ_OK) err=UNZ_ERRNO; if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.compression_method) != UNZ_OK) err=UNZ_ERRNO; if (unz64local_getLong(&s->z_filefunc, s->filestream,&file_info.dosDate) != UNZ_OK) err=UNZ_ERRNO; unz64local_DosDateToTmuDate(file_info.dosDate,&file_info.tmu_date); if (unz64local_getLong(&s->z_filefunc, s->filestream,&file_info.crc) != UNZ_OK) err=UNZ_ERRNO; if (unz64local_getLong(&s->z_filefunc, s->filestream,&uL) != UNZ_OK) err=UNZ_ERRNO; file_info.compressed_size = uL; if (unz64local_getLong(&s->z_filefunc, s->filestream,&uL) != UNZ_OK) err=UNZ_ERRNO; file_info.uncompressed_size = uL; if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.size_filename) != UNZ_OK) err=UNZ_ERRNO; if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.size_file_extra) != UNZ_OK) err=UNZ_ERRNO; if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.size_file_comment) != UNZ_OK) err=UNZ_ERRNO; if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.disk_num_start) != UNZ_OK) err=UNZ_ERRNO; if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.internal_fa) != UNZ_OK) err=UNZ_ERRNO; if (unz64local_getLong(&s->z_filefunc, s->filestream,&file_info.external_fa) != UNZ_OK) err=UNZ_ERRNO; // relative offset of local header if (unz64local_getLong(&s->z_filefunc, s->filestream,&uL) != UNZ_OK) err=UNZ_ERRNO; file_info_internal.offset_curfile = uL; lSeek+=file_info.size_filename; if ((err==UNZ_OK) && (szFileName!=NULL)) { uLong uSizeRead ; if (file_info.size_filename<fileNameBufferSize) { *(szFileName+file_info.size_filename)='\0'; uSizeRead = file_info.size_filename; } else uSizeRead = fileNameBufferSize; if ((file_info.size_filename>0) && (fileNameBufferSize>0)) if (ZREAD64(s->z_filefunc, s->filestream,szFileName,uSizeRead)!=uSizeRead) err=UNZ_ERRNO; lSeek -= uSizeRead; } // Read extrafield if ((err==UNZ_OK) && (extraField!=NULL)) { ZPOS64_T uSizeRead ; if (file_info.size_file_extra<extraFieldBufferSize) uSizeRead = file_info.size_file_extra; else uSizeRead = extraFieldBufferSize; if (lSeek!=0) { if (ZSEEK64(s->z_filefunc, s->filestream,lSeek,ZLIB_FILEFUNC_SEEK_CUR)==0) lSeek=0; else err=UNZ_ERRNO; } if ((file_info.size_file_extra>0) && (extraFieldBufferSize>0)) if (ZREAD64(s->z_filefunc, s->filestream,extraField,(uLong)uSizeRead)!=uSizeRead) err=UNZ_ERRNO; lSeek += file_info.size_file_extra - (uLong)uSizeRead; } else lSeek += file_info.size_file_extra; if ((err==UNZ_OK) && (file_info.size_file_extra != 0)) { uLong acc = 0; // since lSeek now points to after the extra field we need to move back lSeek -= file_info.size_file_extra; if (lSeek!=0) { if (ZSEEK64(s->z_filefunc, s->filestream,lSeek,ZLIB_FILEFUNC_SEEK_CUR)==0) lSeek=0; else err=UNZ_ERRNO; } while(acc < file_info.size_file_extra) { uLong headerId; uLong dataSize; if (unz64local_getShort(&s->z_filefunc, s->filestream,&headerId) != UNZ_OK) err=UNZ_ERRNO; if (unz64local_getShort(&s->z_filefunc, s->filestream,&dataSize) != UNZ_OK) err=UNZ_ERRNO; /* ZIP64 extra fields */ if (headerId == 0x0001) { uLong uL; if(file_info.uncompressed_size == MAXU32) { if (unz64local_getLong64(&s->z_filefunc, s->filestream,&file_info.uncompressed_size) != UNZ_OK) err=UNZ_ERRNO; } if(file_info.compressed_size == MAXU32) { if (unz64local_getLong64(&s->z_filefunc, s->filestream,&file_info.compressed_size) != UNZ_OK) err=UNZ_ERRNO; } if(file_info_internal.offset_curfile == MAXU32) { /* Relative Header offset */ if (unz64local_getLong64(&s->z_filefunc, s->filestream,&file_info_internal.offset_curfile) != UNZ_OK) err=UNZ_ERRNO; } if(file_info.disk_num_start == MAXU32) { /* Disk Start Number */ if (unz64local_getLong(&s->z_filefunc, s->filestream,&uL) != UNZ_OK) err=UNZ_ERRNO; } } else { if (ZSEEK64(s->z_filefunc, s->filestream,dataSize,ZLIB_FILEFUNC_SEEK_CUR)!=0) err=UNZ_ERRNO; } acc += 2 + 2 + dataSize; } } if ((err==UNZ_OK) && (szComment!=NULL)) { uLong uSizeRead ; if (file_info.size_file_comment<commentBufferSize) { *(szComment+file_info.size_file_comment)='\0'; uSizeRead = file_info.size_file_comment; } else uSizeRead = commentBufferSize; if (lSeek!=0) { if (ZSEEK64(s->z_filefunc, s->filestream,lSeek,ZLIB_FILEFUNC_SEEK_CUR)==0) lSeek=0; else err=UNZ_ERRNO; } if ((file_info.size_file_comment>0) && (commentBufferSize>0)) if (ZREAD64(s->z_filefunc, s->filestream,szComment,uSizeRead)!=uSizeRead) err=UNZ_ERRNO; lSeek+=file_info.size_file_comment - uSizeRead; } else lSeek+=file_info.size_file_comment; if ((err==UNZ_OK) && (pfile_info!=NULL)) *pfile_info=file_info; if ((err==UNZ_OK) && (pfile_info_internal!=NULL)) *pfile_info_internal=file_info_internal; return err; } /* Write info about the ZipFile in the *pglobal_info structure. No preparation of the structure is needed return UNZ_OK if there is no problem. */ extern int ZEXPORT unzGetCurrentFileInfo64 (unzFile file, unz_file_info64 * pfile_info, char * szFileName, uLong fileNameBufferSize, void *extraField, uLong extraFieldBufferSize, char* szComment, uLong commentBufferSize) { return unz64local_GetCurrentFileInfoInternal(file,pfile_info,NULL, szFileName,fileNameBufferSize, extraField,extraFieldBufferSize, szComment,commentBufferSize); } extern int ZEXPORT unzGetCurrentFileInfo (unzFile file, unz_file_info * pfile_info, char * szFileName, uLong fileNameBufferSize, void *extraField, uLong extraFieldBufferSize, char* szComment, uLong commentBufferSize) { int err; unz_file_info64 file_info64; err = unz64local_GetCurrentFileInfoInternal(file,&file_info64,NULL, szFileName,fileNameBufferSize, extraField,extraFieldBufferSize, szComment,commentBufferSize); if ((err==UNZ_OK) && (pfile_info != NULL)) { pfile_info->version = file_info64.version; pfile_info->version_needed = file_info64.version_needed; pfile_info->flag = file_info64.flag; pfile_info->compression_method = file_info64.compression_method; pfile_info->dosDate = file_info64.dosDate; pfile_info->crc = file_info64.crc; pfile_info->size_filename = file_info64.size_filename; pfile_info->size_file_extra = file_info64.size_file_extra; pfile_info->size_file_comment = file_info64.size_file_comment; pfile_info->disk_num_start = file_info64.disk_num_start; pfile_info->internal_fa = file_info64.internal_fa; pfile_info->external_fa = file_info64.external_fa; pfile_info->tmu_date = file_info64.tmu_date, pfile_info->compressed_size = (uLong)file_info64.compressed_size; pfile_info->uncompressed_size = (uLong)file_info64.uncompressed_size; } return err; } /* Set the current file of the zipfile to the first file. return UNZ_OK if there is no problem */ extern int ZEXPORT unzGoToFirstFile (unzFile file) { int err=UNZ_OK; unz64_s* s; if (file==NULL) return UNZ_PARAMERROR; s=(unz64_s*)file; s->pos_in_central_dir=s->offset_central_dir; s->num_file=0; err=unz64local_GetCurrentFileInfoInternal(file,&s->cur_file_info, &s->cur_file_info_internal, NULL,0,NULL,0,NULL,0); s->current_file_ok = (err == UNZ_OK); return err; } /* Set the current file of the zipfile to the next file. return UNZ_OK if there is no problem return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest. */ extern int ZEXPORT unzGoToNextFile (unzFile file) { unz64_s* s; int err; if (file==NULL) return UNZ_PARAMERROR; s=(unz64_s*)file; if (!s->current_file_ok) return UNZ_END_OF_LIST_OF_FILE; if (s->gi.number_entry != 0xffff) /* 2^16 files overflow hack */ if (s->num_file+1==s->gi.number_entry) return UNZ_END_OF_LIST_OF_FILE; s->pos_in_central_dir += SIZECENTRALDIRITEM + s->cur_file_info.size_filename + s->cur_file_info.size_file_extra + s->cur_file_info.size_file_comment ; s->num_file++; err = unz64local_GetCurrentFileInfoInternal(file,&s->cur_file_info, &s->cur_file_info_internal, NULL,0,NULL,0,NULL,0); s->current_file_ok = (err == UNZ_OK); return err; } /* Try locate the file szFileName in the zipfile. For the iCaseSensitivity signification, see unzStringFileNameCompare return value : UNZ_OK if the file is found. It becomes the current file. UNZ_END_OF_LIST_OF_FILE if the file is not found */ extern int ZEXPORT unzLocateFile (unzFile file, const char *szFileName, int iCaseSensitivity) { unz64_s* s; int err; /* We remember the 'current' position in the file so that we can jump * back there if we fail. */ unz_file_info64 cur_file_infoSaved; unz_file_info64_internal cur_file_info_internalSaved; ZPOS64_T num_fileSaved; ZPOS64_T pos_in_central_dirSaved; if (file==NULL) return UNZ_PARAMERROR; if (strlen(szFileName)>=UNZ_MAXFILENAMEINZIP) return UNZ_PARAMERROR; s=(unz64_s*)file; if (!s->current_file_ok) return UNZ_END_OF_LIST_OF_FILE; /* Save the current state */ num_fileSaved = s->num_file; pos_in_central_dirSaved = s->pos_in_central_dir; cur_file_infoSaved = s->cur_file_info; cur_file_info_internalSaved = s->cur_file_info_internal; err = unzGoToFirstFile(file); while (err == UNZ_OK) { char szCurrentFileName[UNZ_MAXFILENAMEINZIP+1]; err = unzGetCurrentFileInfo64(file,NULL, szCurrentFileName,sizeof(szCurrentFileName)-1, NULL,0,NULL,0); if (err == UNZ_OK) { if (unzStringFileNameCompare(szCurrentFileName, szFileName,iCaseSensitivity)==0) return UNZ_OK; err = unzGoToNextFile(file); } } /* We failed, so restore the state of the 'current file' to where we * were. */ s->num_file = num_fileSaved ; s->pos_in_central_dir = pos_in_central_dirSaved ; s->cur_file_info = cur_file_infoSaved; s->cur_file_info_internal = cur_file_info_internalSaved; return err; } /* /////////////////////////////////////////// // Contributed by Ryan Haksi (mailto://cryogen@infoserve.net) // I need random access // // Further optimization could be realized by adding an ability // to cache the directory in memory. The goal being a single // comprehensive file read to put the file I need in a memory. */ /* typedef struct unz_file_pos_s { ZPOS64_T pos_in_zip_directory; // offset in file ZPOS64_T num_of_file; // # of file } unz_file_pos; */ extern int ZEXPORT unzGetFilePos64(unzFile file, unz64_file_pos* file_pos) { unz64_s* s; if (file==NULL || file_pos==NULL) return UNZ_PARAMERROR; s=(unz64_s*)file; if (!s->current_file_ok) return UNZ_END_OF_LIST_OF_FILE; file_pos->pos_in_zip_directory = s->pos_in_central_dir; file_pos->num_of_file = s->num_file; return UNZ_OK; } extern int ZEXPORT unzGetFilePos( unzFile file, unz_file_pos* file_pos) { unz64_file_pos file_pos64; int err = unzGetFilePos64(file,&file_pos64); if (err==UNZ_OK) { file_pos->pos_in_zip_directory = (uLong)file_pos64.pos_in_zip_directory; file_pos->num_of_file = (uLong)file_pos64.num_of_file; } return err; } extern int ZEXPORT unzGoToFilePos64(unzFile file, const unz64_file_pos* file_pos) { unz64_s* s; int err; if (file==NULL || file_pos==NULL) return UNZ_PARAMERROR; s=(unz64_s*)file; /* jump to the right spot */ s->pos_in_central_dir = file_pos->pos_in_zip_directory; s->num_file = file_pos->num_of_file; /* set the current file */ err = unz64local_GetCurrentFileInfoInternal(file,&s->cur_file_info, &s->cur_file_info_internal, NULL,0,NULL,0,NULL,0); /* return results */ s->current_file_ok = (err == UNZ_OK); return err; } extern int ZEXPORT unzGoToFilePos( unzFile file, unz_file_pos* file_pos) { unz64_file_pos file_pos64; if (file_pos == NULL) return UNZ_PARAMERROR; file_pos64.pos_in_zip_directory = file_pos->pos_in_zip_directory; file_pos64.num_of_file = file_pos->num_of_file; return unzGoToFilePos64(file,&file_pos64); } /* // Unzip Helper Functions - should be here? /////////////////////////////////////////// */ /* Read the local header of the current zipfile Check the coherency of the local header and info in the end of central directory about this file store in *piSizeVar the size of extra info in local header (filename and size of extra field data) */ local int unz64local_CheckCurrentFileCoherencyHeader (unz64_s* s, uInt* piSizeVar, ZPOS64_T * poffset_local_extrafield, uInt * psize_local_extrafield) { uLong uMagic,uData,uFlags; uLong size_filename; uLong size_extra_field; int err=UNZ_OK; *piSizeVar = 0; *poffset_local_extrafield = 0; *psize_local_extrafield = 0; if (ZSEEK64(s->z_filefunc, s->filestream,s->cur_file_info_internal.offset_curfile + s->byte_before_the_zipfile,ZLIB_FILEFUNC_SEEK_SET)!=0) return UNZ_ERRNO; if (err==UNZ_OK) { if (unz64local_getLong(&s->z_filefunc, s->filestream,&uMagic) != UNZ_OK) err=UNZ_ERRNO; else if (uMagic!=0x04034b50) err=UNZ_BADZIPFILE; } if (unz64local_getShort(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) err=UNZ_ERRNO; /* else if ((err==UNZ_OK) && (uData!=s->cur_file_info.wVersion)) err=UNZ_BADZIPFILE; */ if (unz64local_getShort(&s->z_filefunc, s->filestream,&uFlags) != UNZ_OK) err=UNZ_ERRNO; if (unz64local_getShort(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) err=UNZ_ERRNO; else if ((err==UNZ_OK) && (uData!=s->cur_file_info.compression_method)) err=UNZ_BADZIPFILE; if ((err==UNZ_OK) && (s->cur_file_info.compression_method!=0) && /* #ifdef HAVE_BZIP2 */ (s->cur_file_info.compression_method!=Z_BZIP2ED) && /* #endif */ (s->cur_file_info.compression_method!=Z_DEFLATED)) err=UNZ_BADZIPFILE; if (unz64local_getLong(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) /* date/time */ err=UNZ_ERRNO; if (unz64local_getLong(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) /* crc */ err=UNZ_ERRNO; else if ((err==UNZ_OK) && (uData!=s->cur_file_info.crc) && ((uFlags & 8)==0)) err=UNZ_BADZIPFILE; if (unz64local_getLong(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) /* size compr */ err=UNZ_ERRNO; else if (uData != 0xFFFFFFFF && (err==UNZ_OK) && (uData!=s->cur_file_info.compressed_size) && ((uFlags & 8)==0)) err=UNZ_BADZIPFILE; if (unz64local_getLong(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) /* size uncompr */ err=UNZ_ERRNO; else if (uData != 0xFFFFFFFF && (err==UNZ_OK) && (uData!=s->cur_file_info.uncompressed_size) && ((uFlags & 8)==0)) err=UNZ_BADZIPFILE; if (unz64local_getShort(&s->z_filefunc, s->filestream,&size_filename) != UNZ_OK) err=UNZ_ERRNO; else if ((err==UNZ_OK) && (size_filename!=s->cur_file_info.size_filename)) err=UNZ_BADZIPFILE; *piSizeVar += (uInt)size_filename; if (unz64local_getShort(&s->z_filefunc, s->filestream,&size_extra_field) != UNZ_OK) err=UNZ_ERRNO; *poffset_local_extrafield= s->cur_file_info_internal.offset_curfile + SIZEZIPLOCALHEADER + size_filename; *psize_local_extrafield = (uInt)size_extra_field; *piSizeVar += (uInt)size_extra_field; return err; } /* Open for reading data the current file in the zipfile. If there is no error and the file is opened, the return value is UNZ_OK. */ extern int ZEXPORT unzOpenCurrentFile3 (unzFile file, int* method, int* level, int raw, const char* password) { int err=UNZ_OK; uInt iSizeVar; unz64_s* s; file_in_zip64_read_info_s* pfile_in_zip_read_info; ZPOS64_T offset_local_extrafield; /* offset of the local extra field */ uInt size_local_extrafield; /* size of the local extra field */ # ifndef NOUNCRYPT char source[12]; # else if (password != NULL) return UNZ_PARAMERROR; # endif if (file==NULL) return UNZ_PARAMERROR; s=(unz64_s*)file; if (!s->current_file_ok) return UNZ_PARAMERROR; if (s->pfile_in_zip_read != NULL) unzCloseCurrentFile(file); if (unz64local_CheckCurrentFileCoherencyHeader(s,&iSizeVar, &offset_local_extrafield,&size_local_extrafield)!=UNZ_OK) return UNZ_BADZIPFILE; pfile_in_zip_read_info = (file_in_zip64_read_info_s*)ALLOC(sizeof(file_in_zip64_read_info_s)); if (pfile_in_zip_read_info==NULL) return UNZ_INTERNALERROR; pfile_in_zip_read_info->read_buffer=(char*)ALLOC(UNZ_BUFSIZE); pfile_in_zip_read_info->offset_local_extrafield = offset_local_extrafield; pfile_in_zip_read_info->size_local_extrafield = size_local_extrafield; pfile_in_zip_read_info->pos_local_extrafield=0; pfile_in_zip_read_info->raw=raw; if (pfile_in_zip_read_info->read_buffer==NULL) { TRYFREE(pfile_in_zip_read_info); return UNZ_INTERNALERROR; } pfile_in_zip_read_info->stream_initialised=0; if (method!=NULL) *method = (int)s->cur_file_info.compression_method; if (level!=NULL) { *level = 6; switch (s->cur_file_info.flag & 0x06) { case 6 : *level = 1; break; case 4 : *level = 2; break; case 2 : *level = 9; break; } } if ((s->cur_file_info.compression_method!=0) && /* #ifdef HAVE_BZIP2 */ (s->cur_file_info.compression_method!=Z_BZIP2ED) && /* #endif */ (s->cur_file_info.compression_method!=Z_DEFLATED)) err=UNZ_BADZIPFILE; pfile_in_zip_read_info->crc32_wait=s->cur_file_info.crc; pfile_in_zip_read_info->crc32=0; pfile_in_zip_read_info->total_out_64=0; pfile_in_zip_read_info->compression_method = s->cur_file_info.compression_method; pfile_in_zip_read_info->filestream=s->filestream; pfile_in_zip_read_info->z_filefunc=s->z_filefunc; pfile_in_zip_read_info->byte_before_the_zipfile=s->byte_before_the_zipfile; pfile_in_zip_read_info->stream.total_out = 0; if ((s->cur_file_info.compression_method==Z_BZIP2ED) && (!raw)) { #ifdef HAVE_BZIP2 pfile_in_zip_read_info->bstream.bzalloc = (void *(*) (void *, int, int))0; pfile_in_zip_read_info->bstream.bzfree = (free_func)0; pfile_in_zip_read_info->bstream.opaque = (voidpf)0; pfile_in_zip_read_info->bstream.state = (voidpf)0; pfile_in_zip_read_info->stream.zalloc = (alloc_func)0; pfile_in_zip_read_info->stream.zfree = (free_func)0; pfile_in_zip_read_info->stream.opaque = (voidpf)0; pfile_in_zip_read_info->stream.next_in = (voidpf)0; pfile_in_zip_read_info->stream.avail_in = 0; err=BZ2_bzDecompressInit(&pfile_in_zip_read_info->bstream, 0, 0); if (err == Z_OK) pfile_in_zip_read_info->stream_initialised=Z_BZIP2ED; else { TRYFREE(pfile_in_zip_read_info); return err; } #else pfile_in_zip_read_info->raw=1; #endif } else if ((s->cur_file_info.compression_method==Z_DEFLATED) && (!raw)) { pfile_in_zip_read_info->stream.zalloc = (alloc_func)0; pfile_in_zip_read_info->stream.zfree = (free_func)0; pfile_in_zip_read_info->stream.opaque = (voidpf)0; pfile_in_zip_read_info->stream.next_in = 0; pfile_in_zip_read_info->stream.avail_in = 0; err=inflateInit2(&pfile_in_zip_read_info->stream, -MAX_WBITS); if (err == Z_OK) pfile_in_zip_read_info->stream_initialised=Z_DEFLATED; else { TRYFREE(pfile_in_zip_read_info); return err; } /* windowBits is passed < 0 to tell that there is no zlib header. * Note that in this case inflate *requires* an extra "dummy" byte * after the compressed stream in order to complete decompression and * return Z_STREAM_END. * In unzip, i don't wait absolutely Z_STREAM_END because I known the * size of both compressed and uncompressed data */ } pfile_in_zip_read_info->rest_read_compressed = s->cur_file_info.compressed_size ; pfile_in_zip_read_info->rest_read_uncompressed = s->cur_file_info.uncompressed_size ; pfile_in_zip_read_info->pos_in_zipfile = s->cur_file_info_internal.offset_curfile + SIZEZIPLOCALHEADER + iSizeVar; pfile_in_zip_read_info->stream.avail_in = (uInt)0; s->pfile_in_zip_read = pfile_in_zip_read_info; s->encrypted = 0; # ifndef NOUNCRYPT if (password != NULL) { int i; s->pcrc_32_tab = get_crc_table(); init_keys(password,s->keys,s->pcrc_32_tab); if (ZSEEK64(s->z_filefunc, s->filestream, s->pfile_in_zip_read->pos_in_zipfile + s->pfile_in_zip_read->byte_before_the_zipfile, SEEK_SET)!=0) return UNZ_INTERNALERROR; if(ZREAD64(s->z_filefunc, s->filestream,source, 12)<12) return UNZ_INTERNALERROR; for (i = 0; i<12; i++) zdecode(s->keys,s->pcrc_32_tab,source[i]); s->pfile_in_zip_read->pos_in_zipfile+=12; s->encrypted=1; } # endif return UNZ_OK; } extern int ZEXPORT unzOpenCurrentFile (unzFile file) { return unzOpenCurrentFile3(file, NULL, NULL, 0, NULL); } extern int ZEXPORT unzOpenCurrentFilePassword (unzFile file, const char* password) { return unzOpenCurrentFile3(file, NULL, NULL, 0, password); } extern int ZEXPORT unzOpenCurrentFile2 (unzFile file, int* method, int* level, int raw) { return unzOpenCurrentFile3(file, method, level, raw, NULL); } /** Addition for GDAL : START */ extern ZPOS64_T ZEXPORT unzGetCurrentFileZStreamPos64( unzFile file) { unz64_s* s; file_in_zip64_read_info_s* pfile_in_zip_read_info; s=(unz64_s*)file; if (file==NULL) return 0; //UNZ_PARAMERROR; pfile_in_zip_read_info=s->pfile_in_zip_read; if (pfile_in_zip_read_info==NULL) return 0; //UNZ_PARAMERROR; return pfile_in_zip_read_info->pos_in_zipfile + pfile_in_zip_read_info->byte_before_the_zipfile; } /** Addition for GDAL : END */ /* Read bytes from the current file. buf contain buffer where data must be copied len the size of buf. return the number of byte copied if somes bytes are copied return 0 if the end of file was reached return <0 with error code if there is an error (UNZ_ERRNO for IO error, or zLib error for uncompress error) */ extern int ZEXPORT unzReadCurrentFile (unzFile file, voidp buf, unsigned len) { int err=UNZ_OK; uInt iRead = 0; unz64_s* s; file_in_zip64_read_info_s* pfile_in_zip_read_info; if (file==NULL) return UNZ_PARAMERROR; s=(unz64_s*)file; pfile_in_zip_read_info=s->pfile_in_zip_read; if (pfile_in_zip_read_info==NULL) return UNZ_PARAMERROR; if (pfile_in_zip_read_info->read_buffer == NULL) return UNZ_END_OF_LIST_OF_FILE; if (len==0) return 0; pfile_in_zip_read_info->stream.next_out = (Bytef*)buf; pfile_in_zip_read_info->stream.avail_out = (uInt)len; if ((len>pfile_in_zip_read_info->rest_read_uncompressed) && (!(pfile_in_zip_read_info->raw))) pfile_in_zip_read_info->stream.avail_out = (uInt)pfile_in_zip_read_info->rest_read_uncompressed; if ((len>pfile_in_zip_read_info->rest_read_compressed+ pfile_in_zip_read_info->stream.avail_in) && (pfile_in_zip_read_info->raw)) pfile_in_zip_read_info->stream.avail_out = (uInt)pfile_in_zip_read_info->rest_read_compressed+ pfile_in_zip_read_info->stream.avail_in; while (pfile_in_zip_read_info->stream.avail_out>0) { if ((pfile_in_zip_read_info->stream.avail_in==0) && (pfile_in_zip_read_info->rest_read_compressed>0)) { uInt uReadThis = UNZ_BUFSIZE; if (pfile_in_zip_read_info->rest_read_compressed<uReadThis) uReadThis = (uInt)pfile_in_zip_read_info->rest_read_compressed; if (uReadThis == 0) return UNZ_EOF; if (ZSEEK64(pfile_in_zip_read_info->z_filefunc, pfile_in_zip_read_info->filestream, pfile_in_zip_read_info->pos_in_zipfile + pfile_in_zip_read_info->byte_before_the_zipfile, ZLIB_FILEFUNC_SEEK_SET)!=0) return UNZ_ERRNO; if (ZREAD64(pfile_in_zip_read_info->z_filefunc, pfile_in_zip_read_info->filestream, pfile_in_zip_read_info->read_buffer, uReadThis)!=uReadThis) return UNZ_ERRNO; # ifndef NOUNCRYPT if(s->encrypted) { uInt i; for(i=0;i<uReadThis;i++) pfile_in_zip_read_info->read_buffer[i] = zdecode(s->keys,s->pcrc_32_tab, pfile_in_zip_read_info->read_buffer[i]); } # endif pfile_in_zip_read_info->pos_in_zipfile += uReadThis; pfile_in_zip_read_info->rest_read_compressed-=uReadThis; pfile_in_zip_read_info->stream.next_in = (Bytef*)pfile_in_zip_read_info->read_buffer; pfile_in_zip_read_info->stream.avail_in = (uInt)uReadThis; } if ((pfile_in_zip_read_info->compression_method==0) || (pfile_in_zip_read_info->raw)) { uInt uDoCopy,i ; if ((pfile_in_zip_read_info->stream.avail_in == 0) && (pfile_in_zip_read_info->rest_read_compressed == 0)) return (iRead==0) ? UNZ_EOF : iRead; if (pfile_in_zip_read_info->stream.avail_out < pfile_in_zip_read_info->stream.avail_in) uDoCopy = pfile_in_zip_read_info->stream.avail_out ; else uDoCopy = pfile_in_zip_read_info->stream.avail_in ; for (i=0;i<uDoCopy;i++) *(pfile_in_zip_read_info->stream.next_out+i) = *(pfile_in_zip_read_info->stream.next_in+i); pfile_in_zip_read_info->total_out_64 = pfile_in_zip_read_info->total_out_64 + uDoCopy; pfile_in_zip_read_info->crc32 = crc32(pfile_in_zip_read_info->crc32, pfile_in_zip_read_info->stream.next_out, uDoCopy); pfile_in_zip_read_info->rest_read_uncompressed-=uDoCopy; pfile_in_zip_read_info->stream.avail_in -= uDoCopy; pfile_in_zip_read_info->stream.avail_out -= uDoCopy; pfile_in_zip_read_info->stream.next_out += uDoCopy; pfile_in_zip_read_info->stream.next_in += uDoCopy; pfile_in_zip_read_info->stream.total_out += uDoCopy; iRead += uDoCopy; } else if (pfile_in_zip_read_info->compression_method==Z_BZIP2ED) { #ifdef HAVE_BZIP2 uLong uTotalOutBefore,uTotalOutAfter; const Bytef *bufBefore; uLong uOutThis; pfile_in_zip_read_info->bstream.next_in = (char*)pfile_in_zip_read_info->stream.next_in; pfile_in_zip_read_info->bstream.avail_in = pfile_in_zip_read_info->stream.avail_in; pfile_in_zip_read_info->bstream.total_in_lo32 = pfile_in_zip_read_info->stream.total_in; pfile_in_zip_read_info->bstream.total_in_hi32 = 0; pfile_in_zip_read_info->bstream.next_out = (char*)pfile_in_zip_read_info->stream.next_out; pfile_in_zip_read_info->bstream.avail_out = pfile_in_zip_read_info->stream.avail_out; pfile_in_zip_read_info->bstream.total_out_lo32 = pfile_in_zip_read_info->stream.total_out; pfile_in_zip_read_info->bstream.total_out_hi32 = 0; uTotalOutBefore = pfile_in_zip_read_info->bstream.total_out_lo32; bufBefore = (const Bytef *)pfile_in_zip_read_info->bstream.next_out; err=BZ2_bzDecompress(&pfile_in_zip_read_info->bstream); uTotalOutAfter = pfile_in_zip_read_info->bstream.total_out_lo32; uOutThis = uTotalOutAfter-uTotalOutBefore; pfile_in_zip_read_info->total_out_64 = pfile_in_zip_read_info->total_out_64 + uOutThis; pfile_in_zip_read_info->crc32 = crc32(pfile_in_zip_read_info->crc32,bufBefore, (uInt)(uOutThis)); pfile_in_zip_read_info->rest_read_uncompressed -= uOutThis; iRead += (uInt)(uTotalOutAfter - uTotalOutBefore); pfile_in_zip_read_info->stream.next_in = (Bytef*)pfile_in_zip_read_info->bstream.next_in; pfile_in_zip_read_info->stream.avail_in = pfile_in_zip_read_info->bstream.avail_in; pfile_in_zip_read_info->stream.total_in = pfile_in_zip_read_info->bstream.total_in_lo32; pfile_in_zip_read_info->stream.next_out = (Bytef*)pfile_in_zip_read_info->bstream.next_out; pfile_in_zip_read_info->stream.avail_out = pfile_in_zip_read_info->bstream.avail_out; pfile_in_zip_read_info->stream.total_out = pfile_in_zip_read_info->bstream.total_out_lo32; if (err==BZ_STREAM_END) return (iRead==0) ? UNZ_EOF : iRead; if (err!=BZ_OK) break; #endif } // end Z_BZIP2ED else { ZPOS64_T uTotalOutBefore,uTotalOutAfter; const Bytef *bufBefore; ZPOS64_T uOutThis; int flush=Z_SYNC_FLUSH; uTotalOutBefore = pfile_in_zip_read_info->stream.total_out; bufBefore = pfile_in_zip_read_info->stream.next_out; /* if ((pfile_in_zip_read_info->rest_read_uncompressed == pfile_in_zip_read_info->stream.avail_out) && (pfile_in_zip_read_info->rest_read_compressed == 0)) flush = Z_FINISH; */ err=inflate(&pfile_in_zip_read_info->stream,flush); if ((err>=0) && (pfile_in_zip_read_info->stream.msg!=NULL)) err = Z_DATA_ERROR; uTotalOutAfter = pfile_in_zip_read_info->stream.total_out; uOutThis = uTotalOutAfter-uTotalOutBefore; pfile_in_zip_read_info->total_out_64 = pfile_in_zip_read_info->total_out_64 + uOutThis; pfile_in_zip_read_info->crc32 = crc32(pfile_in_zip_read_info->crc32,bufBefore, (uInt)(uOutThis)); pfile_in_zip_read_info->rest_read_uncompressed -= uOutThis; iRead += (uInt)(uTotalOutAfter - uTotalOutBefore); if (err==Z_STREAM_END) return (iRead==0) ? UNZ_EOF : iRead; if (err!=Z_OK) break; } } if (err==Z_OK) return iRead; return err; } /* Give the current position in uncompressed data */ extern z_off_t ZEXPORT unztell (unzFile file) { unz64_s* s; file_in_zip64_read_info_s* pfile_in_zip_read_info; if (file==NULL) return UNZ_PARAMERROR; s=(unz64_s*)file; pfile_in_zip_read_info=s->pfile_in_zip_read; if (pfile_in_zip_read_info==NULL) return UNZ_PARAMERROR; return (z_off_t)pfile_in_zip_read_info->stream.total_out; } extern ZPOS64_T ZEXPORT unztell64 (unzFile file) { unz64_s* s; file_in_zip64_read_info_s* pfile_in_zip_read_info; if (file==NULL) return (ZPOS64_T)-1; s=(unz64_s*)file; pfile_in_zip_read_info=s->pfile_in_zip_read; if (pfile_in_zip_read_info==NULL) return (ZPOS64_T)-1; return pfile_in_zip_read_info->total_out_64; } /* return 1 if the end of file was reached, 0 elsewhere */ extern int ZEXPORT unzeof (unzFile file) { unz64_s* s; file_in_zip64_read_info_s* pfile_in_zip_read_info; if (file==NULL) return UNZ_PARAMERROR; s=(unz64_s*)file; pfile_in_zip_read_info=s->pfile_in_zip_read; if (pfile_in_zip_read_info==NULL) return UNZ_PARAMERROR; if (pfile_in_zip_read_info->rest_read_uncompressed == 0) return 1; else return 0; } /* Read extra field from the current file (opened by unzOpenCurrentFile) This is the local-header version of the extra field (sometimes, there is more info in the local-header version than in the central-header) if buf==NULL, it return the size of the local extra field that can be read if buf!=NULL, len is the size of the buffer, the extra header is copied in buf. the return value is the number of bytes copied in buf, or (if <0) the error code */ extern int ZEXPORT unzGetLocalExtrafield (unzFile file, voidp buf, unsigned len) { unz64_s* s; file_in_zip64_read_info_s* pfile_in_zip_read_info; uInt read_now; ZPOS64_T size_to_read; if (file==NULL) return UNZ_PARAMERROR; s=(unz64_s*)file; pfile_in_zip_read_info=s->pfile_in_zip_read; if (pfile_in_zip_read_info==NULL) return UNZ_PARAMERROR; size_to_read = (pfile_in_zip_read_info->size_local_extrafield - pfile_in_zip_read_info->pos_local_extrafield); if (buf==NULL) return (int)size_to_read; if (len>size_to_read) read_now = (uInt)size_to_read; else read_now = (uInt)len ; if (read_now==0) return 0; if (ZSEEK64(pfile_in_zip_read_info->z_filefunc, pfile_in_zip_read_info->filestream, pfile_in_zip_read_info->offset_local_extrafield + pfile_in_zip_read_info->pos_local_extrafield, ZLIB_FILEFUNC_SEEK_SET)!=0) return UNZ_ERRNO; if (ZREAD64(pfile_in_zip_read_info->z_filefunc, pfile_in_zip_read_info->filestream, buf,read_now)!=read_now) return UNZ_ERRNO; return (int)read_now; } /* Close the file in zip opened with unzOpenCurrentFile Return UNZ_CRCERROR if all the file was read but the CRC is not good */ extern int ZEXPORT unzCloseCurrentFile (unzFile file) { int err=UNZ_OK; unz64_s* s; file_in_zip64_read_info_s* pfile_in_zip_read_info; if (file==NULL) return UNZ_PARAMERROR; s=(unz64_s*)file; pfile_in_zip_read_info=s->pfile_in_zip_read; if (pfile_in_zip_read_info==NULL) return UNZ_PARAMERROR; if ((pfile_in_zip_read_info->rest_read_uncompressed == 0) && (!pfile_in_zip_read_info->raw)) { if (pfile_in_zip_read_info->crc32 != pfile_in_zip_read_info->crc32_wait) err=UNZ_CRCERROR; } TRYFREE(pfile_in_zip_read_info->read_buffer); pfile_in_zip_read_info->read_buffer = NULL; if (pfile_in_zip_read_info->stream_initialised == Z_DEFLATED) inflateEnd(&pfile_in_zip_read_info->stream); #ifdef HAVE_BZIP2 else if (pfile_in_zip_read_info->stream_initialised == Z_BZIP2ED) BZ2_bzDecompressEnd(&pfile_in_zip_read_info->bstream); #endif pfile_in_zip_read_info->stream_initialised = 0; TRYFREE(pfile_in_zip_read_info); s->pfile_in_zip_read=NULL; return err; } /* Get the global comment string of the ZipFile, in the szComment buffer. uSizeBuf is the size of the szComment buffer. return the number of byte copied or an error code <0 */ extern int ZEXPORT unzGetGlobalComment (unzFile file, char * szComment, uLong uSizeBuf) { unz64_s* s; uLong uReadThis ; if (file==NULL) return (int)UNZ_PARAMERROR; s=(unz64_s*)file; uReadThis = uSizeBuf; if (uReadThis>s->gi.size_comment) uReadThis = s->gi.size_comment; if (ZSEEK64(s->z_filefunc,s->filestream,s->central_pos+22,ZLIB_FILEFUNC_SEEK_SET)!=0) return UNZ_ERRNO; if (uReadThis>0) { *szComment='\0'; if (ZREAD64(s->z_filefunc,s->filestream,szComment,uReadThis)!=uReadThis) return UNZ_ERRNO; } if ((szComment != NULL) && (uSizeBuf > s->gi.size_comment)) *(szComment+s->gi.size_comment)='\0'; return (int)uReadThis; } /* Additions by RX '2004 */ extern ZPOS64_T ZEXPORT unzGetOffset64(unzFile file) { unz64_s* s; if (file==NULL) return 0; //UNZ_PARAMERROR; s=(unz64_s*)file; if (!s->current_file_ok) return 0; if (s->gi.number_entry != 0 && s->gi.number_entry != 0xffff) if (s->num_file==s->gi.number_entry) return 0; return s->pos_in_central_dir; } extern uLong ZEXPORT unzGetOffset (unzFile file) { ZPOS64_T offset64; if (file==NULL) return 0; //UNZ_PARAMERROR; offset64 = unzGetOffset64(file); return (uLong)offset64; } extern int ZEXPORT unzSetOffset64(unzFile file, ZPOS64_T pos) { unz64_s* s; int err; if (file==NULL) return UNZ_PARAMERROR; s=(unz64_s*)file; s->pos_in_central_dir = pos; s->num_file = s->gi.number_entry; /* hack */ err = unz64local_GetCurrentFileInfoInternal(file,&s->cur_file_info, &s->cur_file_info_internal, NULL,0,NULL,0,NULL,0); s->current_file_ok = (err == UNZ_OK); return err; } extern int ZEXPORT unzSetOffset (unzFile file, uLong pos) { return unzSetOffset64(file,pos); } ������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/ThirdParty/minizip/unzip.h�������������������������������������������������0000664�0000000�0000000�00000037740�13262451251�0022302�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* unzip.h -- IO for uncompress .zip files using zlib Version 1.1, February 14h, 2010 part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html ) Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html ) Modifications of Unzip for Zip64 Copyright (C) 2007-2008 Even Rouault Modifications for Zip64 support on both zip and unzip Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com ) For more info read MiniZip_info.txt --------------------------------------------------------------------------------- Condition of use and distribution are the same than zlib : This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. --------------------------------------------------------------------------------- Changes See header of unzip64.c */ #ifndef _unz64_H #define _unz64_H #ifdef __cplusplus extern "C" { #endif #ifndef _ZLIB_H #include "zlib.h" #endif #ifndef _ZLIBIOAPI_H #include "ioapi.h" #endif #ifdef HAVE_BZIP2 #include "bzlib.h" #endif #define Z_BZIP2ED 12 #if defined(STRICTUNZIP) || defined(STRICTZIPUNZIP) /* like the STRICT of WIN32, we define a pointer that cannot be converted from (void*) without cast */ typedef struct TagunzFile__ { int unused; } unzFile__; typedef unzFile__ *unzFile; #else typedef voidp unzFile; #endif #define UNZ_OK (0) #define UNZ_END_OF_LIST_OF_FILE (-100) #define UNZ_ERRNO (Z_ERRNO) #define UNZ_EOF (0) #define UNZ_PARAMERROR (-102) #define UNZ_BADZIPFILE (-103) #define UNZ_INTERNALERROR (-104) #define UNZ_CRCERROR (-105) /* tm_unz contain date/time info */ typedef struct tm_unz_s { uInt tm_sec; /* seconds after the minute - [0,59] */ uInt tm_min; /* minutes after the hour - [0,59] */ uInt tm_hour; /* hours since midnight - [0,23] */ uInt tm_mday; /* day of the month - [1,31] */ uInt tm_mon; /* months since January - [0,11] */ uInt tm_year; /* years - [1980..2044] */ } tm_unz; /* unz_global_info structure contain global data about the ZIPfile These data comes from the end of central dir */ typedef struct unz_global_info64_s { ZPOS64_T number_entry; /* total number of entries in the central dir on this disk */ uLong size_comment; /* size of the global comment of the zipfile */ } unz_global_info64; typedef struct unz_global_info_s { uLong number_entry; /* total number of entries in the central dir on this disk */ uLong size_comment; /* size of the global comment of the zipfile */ } unz_global_info; /* unz_file_info contain information about a file in the zipfile */ typedef struct unz_file_info64_s { uLong version; /* version made by 2 bytes */ uLong version_needed; /* version needed to extract 2 bytes */ uLong flag; /* general purpose bit flag 2 bytes */ uLong compression_method; /* compression method 2 bytes */ uLong dosDate; /* last mod file date in Dos fmt 4 bytes */ uLong crc; /* crc-32 4 bytes */ ZPOS64_T compressed_size; /* compressed size 8 bytes */ ZPOS64_T uncompressed_size; /* uncompressed size 8 bytes */ uLong size_filename; /* filename length 2 bytes */ uLong size_file_extra; /* extra field length 2 bytes */ uLong size_file_comment; /* file comment length 2 bytes */ uLong disk_num_start; /* disk number start 2 bytes */ uLong internal_fa; /* internal file attributes 2 bytes */ uLong external_fa; /* external file attributes 4 bytes */ tm_unz tmu_date; } unz_file_info64; typedef struct unz_file_info_s { uLong version; /* version made by 2 bytes */ uLong version_needed; /* version needed to extract 2 bytes */ uLong flag; /* general purpose bit flag 2 bytes */ uLong compression_method; /* compression method 2 bytes */ uLong dosDate; /* last mod file date in Dos fmt 4 bytes */ uLong crc; /* crc-32 4 bytes */ uLong compressed_size; /* compressed size 4 bytes */ uLong uncompressed_size; /* uncompressed size 4 bytes */ uLong size_filename; /* filename length 2 bytes */ uLong size_file_extra; /* extra field length 2 bytes */ uLong size_file_comment; /* file comment length 2 bytes */ uLong disk_num_start; /* disk number start 2 bytes */ uLong internal_fa; /* internal file attributes 2 bytes */ uLong external_fa; /* external file attributes 4 bytes */ tm_unz tmu_date; } unz_file_info; extern int ZEXPORT unzStringFileNameCompare OF ((const char* fileName1, const char* fileName2, int iCaseSensitivity)); /* Compare two filename (fileName1,fileName2). If iCaseSenisivity = 1, comparision is case sensitivity (like strcmp) If iCaseSenisivity = 2, comparision is not case sensitivity (like strcmpi or strcasecmp) If iCaseSenisivity = 0, case sensitivity is defaut of your operating system (like 1 on Unix, 2 on Windows) */ extern unzFile ZEXPORT unzOpen OF((const char *path)); extern unzFile ZEXPORT unzOpen64 OF((const void *path)); /* Open a Zip file. path contain the full pathname (by example, on a Windows XP computer "c:\\zlib\\zlib113.zip" or on an Unix computer "zlib/zlib113.zip". If the zipfile cannot be opened (file don't exist or in not valid), the return value is NULL. Else, the return value is a unzFile Handle, usable with other function of this unzip package. the "64" function take a const void* pointer, because the path is just the value passed to the open64_file_func callback. Under Windows, if UNICODE is defined, using fill_fopen64_filefunc, the path is a pointer to a wide unicode string (LPCTSTR is LPCWSTR), so const char* does not describe the reality */ extern unzFile ZEXPORT unzOpen2 OF((const char *path, zlib_filefunc_def* pzlib_filefunc_def)); /* Open a Zip file, like unzOpen, but provide a set of file low level API for read/write the zip file (see ioapi.h) */ extern unzFile ZEXPORT unzOpen2_64 OF((const void *path, zlib_filefunc64_def* pzlib_filefunc_def)); /* Open a Zip file, like unz64Open, but provide a set of file low level API for read/write the zip file (see ioapi.h) */ extern int ZEXPORT unzClose OF((unzFile file)); /* Close a ZipFile opened with unzOpen. If there is files inside the .Zip opened with unzOpenCurrentFile (see later), these files MUST be closed with unzCloseCurrentFile before call unzClose. return UNZ_OK if there is no problem. */ extern int ZEXPORT unzGetGlobalInfo OF((unzFile file, unz_global_info *pglobal_info)); extern int ZEXPORT unzGetGlobalInfo64 OF((unzFile file, unz_global_info64 *pglobal_info)); /* Write info about the ZipFile in the *pglobal_info structure. No preparation of the structure is needed return UNZ_OK if there is no problem. */ extern int ZEXPORT unzGetGlobalComment OF((unzFile file, char *szComment, uLong uSizeBuf)); /* Get the global comment string of the ZipFile, in the szComment buffer. uSizeBuf is the size of the szComment buffer. return the number of byte copied or an error code <0 */ /***************************************************************************/ /* Unzip package allow you browse the directory of the zipfile */ extern int ZEXPORT unzGoToFirstFile OF((unzFile file)); /* Set the current file of the zipfile to the first file. return UNZ_OK if there is no problem */ extern int ZEXPORT unzGoToNextFile OF((unzFile file)); /* Set the current file of the zipfile to the next file. return UNZ_OK if there is no problem return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest. */ extern int ZEXPORT unzLocateFile OF((unzFile file, const char *szFileName, int iCaseSensitivity)); /* Try locate the file szFileName in the zipfile. For the iCaseSensitivity signification, see unzStringFileNameCompare return value : UNZ_OK if the file is found. It becomes the current file. UNZ_END_OF_LIST_OF_FILE if the file is not found */ /* ****************************************** */ /* Ryan supplied functions */ /* unz_file_info contain information about a file in the zipfile */ typedef struct unz_file_pos_s { uLong pos_in_zip_directory; /* offset in zip file directory */ uLong num_of_file; /* # of file */ } unz_file_pos; extern int ZEXPORT unzGetFilePos( unzFile file, unz_file_pos* file_pos); extern int ZEXPORT unzGoToFilePos( unzFile file, unz_file_pos* file_pos); typedef struct unz64_file_pos_s { ZPOS64_T pos_in_zip_directory; /* offset in zip file directory */ ZPOS64_T num_of_file; /* # of file */ } unz64_file_pos; extern int ZEXPORT unzGetFilePos64( unzFile file, unz64_file_pos* file_pos); extern int ZEXPORT unzGoToFilePos64( unzFile file, const unz64_file_pos* file_pos); /* ****************************************** */ extern int ZEXPORT unzGetCurrentFileInfo64 OF((unzFile file, unz_file_info64 *pfile_info, char *szFileName, uLong fileNameBufferSize, void *extraField, uLong extraFieldBufferSize, char *szComment, uLong commentBufferSize)); extern int ZEXPORT unzGetCurrentFileInfo OF((unzFile file, unz_file_info *pfile_info, char *szFileName, uLong fileNameBufferSize, void *extraField, uLong extraFieldBufferSize, char *szComment, uLong commentBufferSize)); /* Get Info about the current file if pfile_info!=NULL, the *pfile_info structure will contain somes info about the current file if szFileName!=NULL, the filemane string will be copied in szFileName (fileNameBufferSize is the size of the buffer) if extraField!=NULL, the extra field information will be copied in extraField (extraFieldBufferSize is the size of the buffer). This is the Central-header version of the extra field if szComment!=NULL, the comment string of the file will be copied in szComment (commentBufferSize is the size of the buffer) */ /** Addition for GDAL : START */ extern ZPOS64_T ZEXPORT unzGetCurrentFileZStreamPos64 OF((unzFile file)); /** Addition for GDAL : END */ /***************************************************************************/ /* for reading the content of the current zipfile, you can open it, read data from it, and close it (you can close it before reading all the file) */ extern int ZEXPORT unzOpenCurrentFile OF((unzFile file)); /* Open for reading data the current file in the zipfile. If there is no error, the return value is UNZ_OK. */ extern int ZEXPORT unzOpenCurrentFilePassword OF((unzFile file, const char* password)); /* Open for reading data the current file in the zipfile. password is a crypting password If there is no error, the return value is UNZ_OK. */ extern int ZEXPORT unzOpenCurrentFile2 OF((unzFile file, int* method, int* level, int raw)); /* Same than unzOpenCurrentFile, but open for read raw the file (not uncompress) if raw==1 *method will receive method of compression, *level will receive level of compression note : you can set level parameter as NULL (if you did not want known level, but you CANNOT set method parameter as NULL */ extern int ZEXPORT unzOpenCurrentFile3 OF((unzFile file, int* method, int* level, int raw, const char* password)); /* Same than unzOpenCurrentFile, but open for read raw the file (not uncompress) if raw==1 *method will receive method of compression, *level will receive level of compression note : you can set level parameter as NULL (if you did not want known level, but you CANNOT set method parameter as NULL */ extern int ZEXPORT unzCloseCurrentFile OF((unzFile file)); /* Close the file in zip opened with unzOpenCurrentFile Return UNZ_CRCERROR if all the file was read but the CRC is not good */ extern int ZEXPORT unzReadCurrentFile OF((unzFile file, voidp buf, unsigned len)); /* Read bytes from the current file (opened by unzOpenCurrentFile) buf contain buffer where data must be copied len the size of buf. return the number of byte copied if somes bytes are copied return 0 if the end of file was reached return <0 with error code if there is an error (UNZ_ERRNO for IO error, or zLib error for uncompress error) */ extern z_off_t ZEXPORT unztell OF((unzFile file)); extern ZPOS64_T ZEXPORT unztell64 OF((unzFile file)); /* Give the current position in uncompressed data */ extern int ZEXPORT unzeof OF((unzFile file)); /* return 1 if the end of file was reached, 0 elsewhere */ extern int ZEXPORT unzGetLocalExtrafield OF((unzFile file, voidp buf, unsigned len)); /* Read extra field from the current file (opened by unzOpenCurrentFile) This is the local-header version of the extra field (sometimes, there is more info in the local-header version than in the central-header) if buf==NULL, it return the size of the local extra field if buf!=NULL, len is the size of the buffer, the extra header is copied in buf. the return value is the number of bytes copied in buf, or (if <0) the error code */ /***************************************************************************/ /* Get the current file offset */ extern ZPOS64_T ZEXPORT unzGetOffset64 (unzFile file); extern uLong ZEXPORT unzGetOffset (unzFile file); /* Set the current file offset */ extern int ZEXPORT unzSetOffset64 (unzFile file, ZPOS64_T pos); extern int ZEXPORT unzSetOffset (unzFile file, uLong pos); #ifdef __cplusplus } #endif #endif /* _unz64_H */ ��������������������������������tdesktop-1.2.17/Telegram/ThirdParty/minizip/zip.c���������������������������������������������������0000664�0000000�0000000�00000200505�13262451251�0021721�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* zip.c -- IO on .zip files using zlib Version 1.1, February 14h, 2010 part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html ) Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html ) Modifications for Zip64 support Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com ) For more info read MiniZip_info.txt Changes Oct-2009 - Mathias Svensson - Remove old C style function prototypes Oct-2009 - Mathias Svensson - Added Zip64 Support when creating new file archives Oct-2009 - Mathias Svensson - Did some code cleanup and refactoring to get better overview of some functions. Oct-2009 - Mathias Svensson - Added zipRemoveExtraInfoBlock to strip extra field data from its ZIP64 data It is used when recreting zip archive with RAW when deleting items from a zip. ZIP64 data is automaticly added to items that needs it, and existing ZIP64 data need to be removed. Oct-2009 - Mathias Svensson - Added support for BZIP2 as compression mode (bzip2 lib is required) Jan-2010 - back to unzip and minizip 1.0 name scheme, with compatibility layer */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include "zlib.h" #include "zip.h" #ifdef STDC # include <stddef.h> # include <string.h> # include <stdlib.h> #endif #ifdef NO_ERRNO_H extern int errno; #else # include <errno.h> #endif #ifndef local # define local static #endif /* compile with -Dlocal if your debugger can't find static symbols */ #ifndef VERSIONMADEBY # define VERSIONMADEBY (0x0) /* platform depedent */ #endif #ifndef Z_BUFSIZE #define Z_BUFSIZE (64*1024) //(16384) #endif #ifndef Z_MAXFILENAMEINZIP #define Z_MAXFILENAMEINZIP (256) #endif #ifndef ALLOC # define ALLOC(size) (malloc(size)) #endif #ifndef TRYFREE # define TRYFREE(p) {if (p) free(p);} #endif /* #define SIZECENTRALDIRITEM (0x2e) #define SIZEZIPLOCALHEADER (0x1e) */ /* I've found an old Unix (a SunOS 4.1.3_U1) without all SEEK_* defined.... */ // NOT sure that this work on ALL platform #define MAKEULONG64(a, b) ((ZPOS64_T)(((unsigned long)(a)) | ((ZPOS64_T)((unsigned long)(b))) << 32)) #ifndef SEEK_CUR #define SEEK_CUR 1 #endif #ifndef SEEK_END #define SEEK_END 2 #endif #ifndef SEEK_SET #define SEEK_SET 0 #endif #ifndef DEF_MEM_LEVEL #if MAX_MEM_LEVEL >= 8 # define DEF_MEM_LEVEL 8 #else # define DEF_MEM_LEVEL MAX_MEM_LEVEL #endif #endif const char zip_copyright[] =" zip 1.01 Copyright 1998-2004 Gilles Vollant - http://www.winimage.com/zLibDll"; #define SIZEDATA_INDATABLOCK (4096-(4*4)) #define LOCALHEADERMAGIC (0x04034b50) #define CENTRALHEADERMAGIC (0x02014b50) #define ENDHEADERMAGIC (0x06054b50) #define ZIP64ENDHEADERMAGIC (0x6064b50) #define ZIP64ENDLOCHEADERMAGIC (0x7064b50) #define FLAG_LOCALHEADER_OFFSET (0x06) #define CRC_LOCALHEADER_OFFSET (0x0e) #define SIZECENTRALHEADER (0x2e) /* 46 */ typedef struct linkedlist_datablock_internal_s { struct linkedlist_datablock_internal_s* next_datablock; uLong avail_in_this_block; uLong filled_in_this_block; uLong unused; /* for future use and alignement */ unsigned char data[SIZEDATA_INDATABLOCK]; } linkedlist_datablock_internal; typedef struct linkedlist_data_s { linkedlist_datablock_internal* first_block; linkedlist_datablock_internal* last_block; } linkedlist_data; typedef struct { z_stream stream; /* zLib stream structure for inflate */ #ifdef HAVE_BZIP2 bz_stream bstream; /* bzLib stream structure for bziped */ #endif int stream_initialised; /* 1 is stream is initialised */ uInt pos_in_buffered_data; /* last written byte in buffered_data */ ZPOS64_T pos_local_header; /* offset of the local header of the file currenty writing */ char* central_header; /* central header data for the current file */ uLong size_centralExtra; uLong size_centralheader; /* size of the central header for cur file */ uLong size_centralExtraFree; /* Extra bytes allocated to the centralheader but that are not used */ uLong flag; /* flag of the file currently writing */ int method; /* compression method of file currenty wr.*/ int raw; /* 1 for directly writing raw data */ Byte buffered_data[Z_BUFSIZE];/* buffer contain compressed data to be writ*/ uLong dosDate; uLong crc32; int encrypt; int zip64; /* Add ZIP64 extened information in the extra field */ ZPOS64_T pos_zip64extrainfo; ZPOS64_T totalCompressedData; ZPOS64_T totalUncompressedData; #ifndef NOCRYPT unsigned long keys[3]; /* keys defining the pseudo-random sequence */ const z_crc_t* pcrc_32_tab; int crypt_header_size; #endif } curfile64_info; typedef struct { zlib_filefunc64_32_def z_filefunc; voidpf filestream; /* io structore of the zipfile */ linkedlist_data central_dir;/* datablock with central dir in construction*/ int in_opened_file_inzip; /* 1 if a file in the zip is currently writ.*/ curfile64_info ci; /* info on the file curretly writing */ ZPOS64_T begin_pos; /* position of the beginning of the zipfile */ ZPOS64_T add_position_when_writting_offset; ZPOS64_T number_entry; #ifndef NO_ADDFILEINEXISTINGZIP char *globalcomment; #endif } zip64_internal; #ifndef NOCRYPT #define INCLUDECRYPTINGCODE_IFCRYPTALLOWED #include "crypt.h" #endif local linkedlist_datablock_internal* allocate_new_datablock() { linkedlist_datablock_internal* ldi; ldi = (linkedlist_datablock_internal*) ALLOC(sizeof(linkedlist_datablock_internal)); if (ldi!=NULL) { ldi->next_datablock = NULL ; ldi->filled_in_this_block = 0 ; ldi->avail_in_this_block = SIZEDATA_INDATABLOCK ; } return ldi; } local void free_datablock(linkedlist_datablock_internal* ldi) { while (ldi!=NULL) { linkedlist_datablock_internal* ldinext = ldi->next_datablock; TRYFREE(ldi); ldi = ldinext; } } local void init_linkedlist(linkedlist_data* ll) { ll->first_block = ll->last_block = NULL; } local void free_linkedlist(linkedlist_data* ll) { free_datablock(ll->first_block); ll->first_block = ll->last_block = NULL; } local int add_data_in_datablock(linkedlist_data* ll, const void* buf, uLong len) { linkedlist_datablock_internal* ldi; const unsigned char* from_copy; if (ll==NULL) return ZIP_INTERNALERROR; if (ll->last_block == NULL) { ll->first_block = ll->last_block = allocate_new_datablock(); if (ll->first_block == NULL) return ZIP_INTERNALERROR; } ldi = ll->last_block; from_copy = (unsigned char*)buf; while (len>0) { uInt copy_this; uInt i; unsigned char* to_copy; if (ldi->avail_in_this_block==0) { ldi->next_datablock = allocate_new_datablock(); if (ldi->next_datablock == NULL) return ZIP_INTERNALERROR; ldi = ldi->next_datablock ; ll->last_block = ldi; } if (ldi->avail_in_this_block < len) copy_this = (uInt)ldi->avail_in_this_block; else copy_this = (uInt)len; to_copy = &(ldi->data[ldi->filled_in_this_block]); for (i=0;i<copy_this;i++) *(to_copy+i)=*(from_copy+i); ldi->filled_in_this_block += copy_this; ldi->avail_in_this_block -= copy_this; from_copy += copy_this ; len -= copy_this; } return ZIP_OK; } /****************************************************************************/ #ifndef NO_ADDFILEINEXISTINGZIP /* =========================================================================== Inputs a long in LSB order to the given file nbByte == 1, 2 ,4 or 8 (byte, short or long, ZPOS64_T) */ local int zip64local_putValue OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, ZPOS64_T x, int nbByte)); local int zip64local_putValue (const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, ZPOS64_T x, int nbByte) { unsigned char buf[8]; int n; for (n = 0; n < nbByte; n++) { buf[n] = (unsigned char)(x & 0xff); x >>= 8; } if (x != 0) { /* data overflow - hack for ZIP64 (X Roche) */ for (n = 0; n < nbByte; n++) { buf[n] = 0xff; } } if (ZWRITE64(*pzlib_filefunc_def,filestream,buf,nbByte)!=(uLong)nbByte) return ZIP_ERRNO; else return ZIP_OK; } local void zip64local_putValue_inmemory OF((void* dest, ZPOS64_T x, int nbByte)); local void zip64local_putValue_inmemory (void* dest, ZPOS64_T x, int nbByte) { unsigned char* buf=(unsigned char*)dest; int n; for (n = 0; n < nbByte; n++) { buf[n] = (unsigned char)(x & 0xff); x >>= 8; } if (x != 0) { /* data overflow - hack for ZIP64 */ for (n = 0; n < nbByte; n++) { buf[n] = 0xff; } } } /****************************************************************************/ local uLong zip64local_TmzDateToDosDate(const tm_zip* ptm) { uLong year = (uLong)ptm->tm_year; if (year>=1980) year-=1980; else if (year>=80) year-=80; return (uLong) (((ptm->tm_mday) + (32 * (ptm->tm_mon+1)) + (512 * year)) << 16) | ((ptm->tm_sec/2) + (32* ptm->tm_min) + (2048 * (uLong)ptm->tm_hour)); } /****************************************************************************/ local int zip64local_getByte OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, int *pi)); local int zip64local_getByte(const zlib_filefunc64_32_def* pzlib_filefunc_def,voidpf filestream,int* pi) { unsigned char c; int err = (int)ZREAD64(*pzlib_filefunc_def,filestream,&c,1); if (err==1) { *pi = (int)c; return ZIP_OK; } else { if (ZERROR64(*pzlib_filefunc_def,filestream)) return ZIP_ERRNO; else return ZIP_EOF; } } /* =========================================================================== Reads a long in LSB order from the given gz_stream. Sets */ local int zip64local_getShort OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong *pX)); local int zip64local_getShort (const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong* pX) { uLong x ; int i = 0; int err; err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); x = (uLong)i; if (err==ZIP_OK) err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); x += ((uLong)i)<<8; if (err==ZIP_OK) *pX = x; else *pX = 0; return err; } local int zip64local_getLong OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong *pX)); local int zip64local_getLong (const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong* pX) { uLong x ; int i = 0; int err; err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); x = (uLong)i; if (err==ZIP_OK) err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); x += ((uLong)i)<<8; if (err==ZIP_OK) err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); x += ((uLong)i)<<16; if (err==ZIP_OK) err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); x += ((uLong)i)<<24; if (err==ZIP_OK) *pX = x; else *pX = 0; return err; } local int zip64local_getLong64 OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, ZPOS64_T *pX)); local int zip64local_getLong64 (const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, ZPOS64_T *pX) { ZPOS64_T x; int i = 0; int err; err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); x = (ZPOS64_T)i; if (err==ZIP_OK) err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); x += ((ZPOS64_T)i)<<8; if (err==ZIP_OK) err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); x += ((ZPOS64_T)i)<<16; if (err==ZIP_OK) err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); x += ((ZPOS64_T)i)<<24; if (err==ZIP_OK) err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); x += ((ZPOS64_T)i)<<32; if (err==ZIP_OK) err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); x += ((ZPOS64_T)i)<<40; if (err==ZIP_OK) err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); x += ((ZPOS64_T)i)<<48; if (err==ZIP_OK) err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); x += ((ZPOS64_T)i)<<56; if (err==ZIP_OK) *pX = x; else *pX = 0; return err; } #ifndef BUFREADCOMMENT #define BUFREADCOMMENT (0x400) #endif /* Locate the Central directory of a zipfile (at the end, just before the global comment) */ local ZPOS64_T zip64local_SearchCentralDir OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream)); local ZPOS64_T zip64local_SearchCentralDir(const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream) { unsigned char* buf; ZPOS64_T uSizeFile; ZPOS64_T uBackRead; ZPOS64_T uMaxBack=0xffff; /* maximum size of global comment */ ZPOS64_T uPosFound=0; if (ZSEEK64(*pzlib_filefunc_def,filestream,0,ZLIB_FILEFUNC_SEEK_END) != 0) return 0; uSizeFile = ZTELL64(*pzlib_filefunc_def,filestream); if (uMaxBack>uSizeFile) uMaxBack = uSizeFile; buf = (unsigned char*)ALLOC(BUFREADCOMMENT+4); if (buf==NULL) return 0; uBackRead = 4; while (uBackRead<uMaxBack) { uLong uReadSize; ZPOS64_T uReadPos ; int i; if (uBackRead+BUFREADCOMMENT>uMaxBack) uBackRead = uMaxBack; else uBackRead+=BUFREADCOMMENT; uReadPos = uSizeFile-uBackRead ; uReadSize = ((BUFREADCOMMENT+4) < (uSizeFile-uReadPos)) ? (BUFREADCOMMENT+4) : (uLong)(uSizeFile-uReadPos); if (ZSEEK64(*pzlib_filefunc_def,filestream,uReadPos,ZLIB_FILEFUNC_SEEK_SET)!=0) break; if (ZREAD64(*pzlib_filefunc_def,filestream,buf,uReadSize)!=uReadSize) break; for (i=(int)uReadSize-3; (i--)>0;) if (((*(buf+i))==0x50) && ((*(buf+i+1))==0x4b) && ((*(buf+i+2))==0x05) && ((*(buf+i+3))==0x06)) { uPosFound = uReadPos+i; break; } if (uPosFound!=0) break; } TRYFREE(buf); return uPosFound; } /* Locate the End of Zip64 Central directory locator and from there find the CD of a zipfile (at the end, just before the global comment) */ local ZPOS64_T zip64local_SearchCentralDir64 OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream)); local ZPOS64_T zip64local_SearchCentralDir64(const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream) { unsigned char* buf; ZPOS64_T uSizeFile; ZPOS64_T uBackRead; ZPOS64_T uMaxBack=0xffff; /* maximum size of global comment */ ZPOS64_T uPosFound=0; uLong uL; ZPOS64_T relativeOffset; if (ZSEEK64(*pzlib_filefunc_def,filestream,0,ZLIB_FILEFUNC_SEEK_END) != 0) return 0; uSizeFile = ZTELL64(*pzlib_filefunc_def,filestream); if (uMaxBack>uSizeFile) uMaxBack = uSizeFile; buf = (unsigned char*)ALLOC(BUFREADCOMMENT+4); if (buf==NULL) return 0; uBackRead = 4; while (uBackRead<uMaxBack) { uLong uReadSize; ZPOS64_T uReadPos; int i; if (uBackRead+BUFREADCOMMENT>uMaxBack) uBackRead = uMaxBack; else uBackRead+=BUFREADCOMMENT; uReadPos = uSizeFile-uBackRead ; uReadSize = ((BUFREADCOMMENT+4) < (uSizeFile-uReadPos)) ? (BUFREADCOMMENT+4) : (uLong)(uSizeFile-uReadPos); if (ZSEEK64(*pzlib_filefunc_def,filestream,uReadPos,ZLIB_FILEFUNC_SEEK_SET)!=0) break; if (ZREAD64(*pzlib_filefunc_def,filestream,buf,uReadSize)!=uReadSize) break; for (i=(int)uReadSize-3; (i--)>0;) { // Signature "0x07064b50" Zip64 end of central directory locater if (((*(buf+i))==0x50) && ((*(buf+i+1))==0x4b) && ((*(buf+i+2))==0x06) && ((*(buf+i+3))==0x07)) { uPosFound = uReadPos+i; break; } } if (uPosFound!=0) break; } TRYFREE(buf); if (uPosFound == 0) return 0; /* Zip64 end of central directory locator */ if (ZSEEK64(*pzlib_filefunc_def,filestream, uPosFound,ZLIB_FILEFUNC_SEEK_SET)!=0) return 0; /* the signature, already checked */ if (zip64local_getLong(pzlib_filefunc_def,filestream,&uL)!=ZIP_OK) return 0; /* number of the disk with the start of the zip64 end of central directory */ if (zip64local_getLong(pzlib_filefunc_def,filestream,&uL)!=ZIP_OK) return 0; if (uL != 0) return 0; /* relative offset of the zip64 end of central directory record */ if (zip64local_getLong64(pzlib_filefunc_def,filestream,&relativeOffset)!=ZIP_OK) return 0; /* total number of disks */ if (zip64local_getLong(pzlib_filefunc_def,filestream,&uL)!=ZIP_OK) return 0; if (uL != 1) return 0; /* Goto Zip64 end of central directory record */ if (ZSEEK64(*pzlib_filefunc_def,filestream, relativeOffset,ZLIB_FILEFUNC_SEEK_SET)!=0) return 0; /* the signature */ if (zip64local_getLong(pzlib_filefunc_def,filestream,&uL)!=ZIP_OK) return 0; if (uL != 0x06064b50) // signature of 'Zip64 end of central directory' return 0; return relativeOffset; } int LoadCentralDirectoryRecord(zip64_internal* pziinit) { int err=ZIP_OK; ZPOS64_T byte_before_the_zipfile;/* byte before the zipfile, (>0 for sfx)*/ ZPOS64_T size_central_dir; /* size of the central directory */ ZPOS64_T offset_central_dir; /* offset of start of central directory */ ZPOS64_T central_pos; uLong uL; uLong number_disk; /* number of the current dist, used for spaning ZIP, unsupported, always 0*/ uLong number_disk_with_CD; /* number the the disk with central dir, used for spaning ZIP, unsupported, always 0*/ ZPOS64_T number_entry; ZPOS64_T number_entry_CD; /* total number of entries in the central dir (same than number_entry on nospan) */ uLong VersionMadeBy; uLong VersionNeeded; uLong size_comment; int hasZIP64Record = 0; // check first if we find a ZIP64 record central_pos = zip64local_SearchCentralDir64(&pziinit->z_filefunc,pziinit->filestream); if(central_pos > 0) { hasZIP64Record = 1; } else if(central_pos == 0) { central_pos = zip64local_SearchCentralDir(&pziinit->z_filefunc,pziinit->filestream); } /* disable to allow appending to empty ZIP archive if (central_pos==0) err=ZIP_ERRNO; */ if(hasZIP64Record) { ZPOS64_T sizeEndOfCentralDirectory; if (ZSEEK64(pziinit->z_filefunc, pziinit->filestream, central_pos, ZLIB_FILEFUNC_SEEK_SET) != 0) err=ZIP_ERRNO; /* the signature, already checked */ if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream,&uL)!=ZIP_OK) err=ZIP_ERRNO; /* size of zip64 end of central directory record */ if (zip64local_getLong64(&pziinit->z_filefunc, pziinit->filestream, &sizeEndOfCentralDirectory)!=ZIP_OK) err=ZIP_ERRNO; /* version made by */ if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream, &VersionMadeBy)!=ZIP_OK) err=ZIP_ERRNO; /* version needed to extract */ if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream, &VersionNeeded)!=ZIP_OK) err=ZIP_ERRNO; /* number of this disk */ if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream,&number_disk)!=ZIP_OK) err=ZIP_ERRNO; /* number of the disk with the start of the central directory */ if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream,&number_disk_with_CD)!=ZIP_OK) err=ZIP_ERRNO; /* total number of entries in the central directory on this disk */ if (zip64local_getLong64(&pziinit->z_filefunc, pziinit->filestream, &number_entry)!=ZIP_OK) err=ZIP_ERRNO; /* total number of entries in the central directory */ if (zip64local_getLong64(&pziinit->z_filefunc, pziinit->filestream,&number_entry_CD)!=ZIP_OK) err=ZIP_ERRNO; if ((number_entry_CD!=number_entry) || (number_disk_with_CD!=0) || (number_disk!=0)) err=ZIP_BADZIPFILE; /* size of the central directory */ if (zip64local_getLong64(&pziinit->z_filefunc, pziinit->filestream,&size_central_dir)!=ZIP_OK) err=ZIP_ERRNO; /* offset of start of central directory with respect to the starting disk number */ if (zip64local_getLong64(&pziinit->z_filefunc, pziinit->filestream,&offset_central_dir)!=ZIP_OK) err=ZIP_ERRNO; // TODO.. // read the comment from the standard central header. size_comment = 0; } else { // Read End of central Directory info if (ZSEEK64(pziinit->z_filefunc, pziinit->filestream, central_pos,ZLIB_FILEFUNC_SEEK_SET)!=0) err=ZIP_ERRNO; /* the signature, already checked */ if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream,&uL)!=ZIP_OK) err=ZIP_ERRNO; /* number of this disk */ if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream,&number_disk)!=ZIP_OK) err=ZIP_ERRNO; /* number of the disk with the start of the central directory */ if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream,&number_disk_with_CD)!=ZIP_OK) err=ZIP_ERRNO; /* total number of entries in the central dir on this disk */ number_entry = 0; if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream, &uL)!=ZIP_OK) err=ZIP_ERRNO; else number_entry = uL; /* total number of entries in the central dir */ number_entry_CD = 0; if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream, &uL)!=ZIP_OK) err=ZIP_ERRNO; else number_entry_CD = uL; if ((number_entry_CD!=number_entry) || (number_disk_with_CD!=0) || (number_disk!=0)) err=ZIP_BADZIPFILE; /* size of the central directory */ size_central_dir = 0; if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream, &uL)!=ZIP_OK) err=ZIP_ERRNO; else size_central_dir = uL; /* offset of start of central directory with respect to the starting disk number */ offset_central_dir = 0; if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream, &uL)!=ZIP_OK) err=ZIP_ERRNO; else offset_central_dir = uL; /* zipfile global comment length */ if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream, &size_comment)!=ZIP_OK) err=ZIP_ERRNO; } if ((central_pos<offset_central_dir+size_central_dir) && (err==ZIP_OK)) err=ZIP_BADZIPFILE; if (err!=ZIP_OK) { ZCLOSE64(pziinit->z_filefunc, pziinit->filestream); return ZIP_ERRNO; } if (size_comment>0) { pziinit->globalcomment = (char*)ALLOC(size_comment+1); if (pziinit->globalcomment) { size_comment = ZREAD64(pziinit->z_filefunc, pziinit->filestream, pziinit->globalcomment,size_comment); pziinit->globalcomment[size_comment]=0; } } byte_before_the_zipfile = central_pos - (offset_central_dir+size_central_dir); pziinit->add_position_when_writting_offset = byte_before_the_zipfile; { ZPOS64_T size_central_dir_to_read = size_central_dir; size_t buf_size = SIZEDATA_INDATABLOCK; void* buf_read = (void*)ALLOC(buf_size); if (ZSEEK64(pziinit->z_filefunc, pziinit->filestream, offset_central_dir + byte_before_the_zipfile, ZLIB_FILEFUNC_SEEK_SET) != 0) err=ZIP_ERRNO; while ((size_central_dir_to_read>0) && (err==ZIP_OK)) { ZPOS64_T read_this = SIZEDATA_INDATABLOCK; if (read_this > size_central_dir_to_read) read_this = size_central_dir_to_read; if (ZREAD64(pziinit->z_filefunc, pziinit->filestream,buf_read,(uLong)read_this) != read_this) err=ZIP_ERRNO; if (err==ZIP_OK) err = add_data_in_datablock(&pziinit->central_dir,buf_read, (uLong)read_this); size_central_dir_to_read-=read_this; } TRYFREE(buf_read); } pziinit->begin_pos = byte_before_the_zipfile; pziinit->number_entry = number_entry_CD; if (ZSEEK64(pziinit->z_filefunc, pziinit->filestream, offset_central_dir+byte_before_the_zipfile,ZLIB_FILEFUNC_SEEK_SET) != 0) err=ZIP_ERRNO; return err; } #endif /* !NO_ADDFILEINEXISTINGZIP*/ /************************************************************/ extern zipFile ZEXPORT zipOpen3 (const void *pathname, int append, zipcharpc* globalcomment, zlib_filefunc64_32_def* pzlib_filefunc64_32_def) { zip64_internal ziinit; zip64_internal* zi; int err=ZIP_OK; ziinit.z_filefunc.zseek32_file = NULL; ziinit.z_filefunc.ztell32_file = NULL; if (pzlib_filefunc64_32_def==NULL) fill_fopen64_filefunc(&ziinit.z_filefunc.zfile_func64); else ziinit.z_filefunc = *pzlib_filefunc64_32_def; ziinit.filestream = ZOPEN64(ziinit.z_filefunc, pathname, (append == APPEND_STATUS_CREATE) ? (ZLIB_FILEFUNC_MODE_READ | ZLIB_FILEFUNC_MODE_WRITE | ZLIB_FILEFUNC_MODE_CREATE) : (ZLIB_FILEFUNC_MODE_READ | ZLIB_FILEFUNC_MODE_WRITE | ZLIB_FILEFUNC_MODE_EXISTING)); if (ziinit.filestream == NULL) return NULL; if (append == APPEND_STATUS_CREATEAFTER) ZSEEK64(ziinit.z_filefunc,ziinit.filestream,0,SEEK_END); ziinit.begin_pos = ZTELL64(ziinit.z_filefunc,ziinit.filestream); ziinit.in_opened_file_inzip = 0; ziinit.ci.stream_initialised = 0; ziinit.number_entry = 0; ziinit.add_position_when_writting_offset = 0; init_linkedlist(&(ziinit.central_dir)); zi = (zip64_internal*)ALLOC(sizeof(zip64_internal)); if (zi==NULL) { ZCLOSE64(ziinit.z_filefunc,ziinit.filestream); return NULL; } /* now we add file in a zipfile */ # ifndef NO_ADDFILEINEXISTINGZIP ziinit.globalcomment = NULL; if (append == APPEND_STATUS_ADDINZIP) { // Read and Cache Central Directory Records err = LoadCentralDirectoryRecord(&ziinit); } if (globalcomment) { *globalcomment = ziinit.globalcomment; } # endif /* !NO_ADDFILEINEXISTINGZIP*/ if (err != ZIP_OK) { # ifndef NO_ADDFILEINEXISTINGZIP TRYFREE(ziinit.globalcomment); # endif /* !NO_ADDFILEINEXISTINGZIP*/ TRYFREE(zi); return NULL; } else { *zi = ziinit; return (zipFile)zi; } } extern zipFile ZEXPORT zipOpen2 (const char *pathname, int append, zipcharpc* globalcomment, zlib_filefunc_def* pzlib_filefunc32_def) { if (pzlib_filefunc32_def != NULL) { zlib_filefunc64_32_def zlib_filefunc64_32_def_fill; fill_zlib_filefunc64_32_def_from_filefunc32(&zlib_filefunc64_32_def_fill,pzlib_filefunc32_def); return zipOpen3(pathname, append, globalcomment, &zlib_filefunc64_32_def_fill); } else return zipOpen3(pathname, append, globalcomment, NULL); } extern zipFile ZEXPORT zipOpen2_64 (const void *pathname, int append, zipcharpc* globalcomment, zlib_filefunc64_def* pzlib_filefunc_def) { if (pzlib_filefunc_def != NULL) { zlib_filefunc64_32_def zlib_filefunc64_32_def_fill; zlib_filefunc64_32_def_fill.zfile_func64 = *pzlib_filefunc_def; zlib_filefunc64_32_def_fill.ztell32_file = NULL; zlib_filefunc64_32_def_fill.zseek32_file = NULL; return zipOpen3(pathname, append, globalcomment, &zlib_filefunc64_32_def_fill); } else return zipOpen3(pathname, append, globalcomment, NULL); } extern zipFile ZEXPORT zipOpen (const char* pathname, int append) { return zipOpen3((const void*)pathname,append,NULL,NULL); } extern zipFile ZEXPORT zipOpen64 (const void* pathname, int append) { return zipOpen3(pathname,append,NULL,NULL); } int Write_LocalFileHeader(zip64_internal* zi, const char* filename, uInt size_extrafield_local, const void* extrafield_local) { /* write the local header */ int err; uInt size_filename = (uInt)strlen(filename); uInt size_extrafield = size_extrafield_local; err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)LOCALHEADERMAGIC, 4); if (err==ZIP_OK) { if(zi->ci.zip64) err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)45,2);/* version needed to extract */ else err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)20,2);/* version needed to extract */ } if (err==ZIP_OK) err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->ci.flag,2); if (err==ZIP_OK) err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->ci.method,2); if (err==ZIP_OK) err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->ci.dosDate,4); // CRC / Compressed size / Uncompressed size will be filled in later and rewritten later if (err==ZIP_OK) err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4); /* crc 32, unknown */ if (err==ZIP_OK) { if(zi->ci.zip64) err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0xFFFFFFFF,4); /* compressed size, unknown */ else err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4); /* compressed size, unknown */ } if (err==ZIP_OK) { if(zi->ci.zip64) err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0xFFFFFFFF,4); /* uncompressed size, unknown */ else err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4); /* uncompressed size, unknown */ } if (err==ZIP_OK) err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)size_filename,2); if(zi->ci.zip64) { size_extrafield += 20; } if (err==ZIP_OK) err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)size_extrafield,2); if ((err==ZIP_OK) && (size_filename > 0)) { if (ZWRITE64(zi->z_filefunc,zi->filestream,filename,size_filename)!=size_filename) err = ZIP_ERRNO; } if ((err==ZIP_OK) && (size_extrafield_local > 0)) { if (ZWRITE64(zi->z_filefunc, zi->filestream, extrafield_local, size_extrafield_local) != size_extrafield_local) err = ZIP_ERRNO; } if ((err==ZIP_OK) && (zi->ci.zip64)) { // write the Zip64 extended info short HeaderID = 1; short DataSize = 16; ZPOS64_T CompressedSize = 0; ZPOS64_T UncompressedSize = 0; // Remember position of Zip64 extended info for the local file header. (needed when we update size after done with file) zi->ci.pos_zip64extrainfo = ZTELL64(zi->z_filefunc,zi->filestream); err = zip64local_putValue(&zi->z_filefunc, zi->filestream, (short)HeaderID,2); err = zip64local_putValue(&zi->z_filefunc, zi->filestream, (short)DataSize,2); err = zip64local_putValue(&zi->z_filefunc, zi->filestream, (ZPOS64_T)UncompressedSize,8); err = zip64local_putValue(&zi->z_filefunc, zi->filestream, (ZPOS64_T)CompressedSize,8); } return err; } /* NOTE. When writing RAW the ZIP64 extended information in extrafield_local and extrafield_global needs to be stripped before calling this function it can be done with zipRemoveExtraInfoBlock It is not done here because then we need to realloc a new buffer since parameters are 'const' and I want to minimize unnecessary allocations. */ extern int ZEXPORT zipOpenNewFileInZip4_64 (zipFile file, const char* filename, const zip_fileinfo* zipfi, const void* extrafield_local, uInt size_extrafield_local, const void* extrafield_global, uInt size_extrafield_global, const char* comment, int method, int level, int raw, int windowBits,int memLevel, int strategy, const char* password, uLong crcForCrypting, uLong versionMadeBy, uLong flagBase, int zip64) { zip64_internal* zi; uInt size_filename; uInt size_comment; uInt i; int err = ZIP_OK; # ifdef NOCRYPT (crcForCrypting); if (password != NULL) return ZIP_PARAMERROR; # endif if (file == NULL) return ZIP_PARAMERROR; #ifdef HAVE_BZIP2 if ((method!=0) && (method!=Z_DEFLATED) && (method!=Z_BZIP2ED)) return ZIP_PARAMERROR; #else if ((method!=0) && (method!=Z_DEFLATED)) return ZIP_PARAMERROR; #endif zi = (zip64_internal*)file; if (zi->in_opened_file_inzip == 1) { err = zipCloseFileInZip (file); if (err != ZIP_OK) return err; } if (filename==NULL) filename="-"; if (comment==NULL) size_comment = 0; else size_comment = (uInt)strlen(comment); size_filename = (uInt)strlen(filename); if (zipfi == NULL) zi->ci.dosDate = 0; else { if (zipfi->dosDate != 0) zi->ci.dosDate = zipfi->dosDate; else zi->ci.dosDate = zip64local_TmzDateToDosDate(&zipfi->tmz_date); } zi->ci.flag = flagBase; if ((level==8) || (level==9)) zi->ci.flag |= 2; if (level==2) zi->ci.flag |= 4; if (level==1) zi->ci.flag |= 6; if (password != NULL) zi->ci.flag |= 1; zi->ci.crc32 = 0; zi->ci.method = method; zi->ci.encrypt = 0; zi->ci.stream_initialised = 0; zi->ci.pos_in_buffered_data = 0; zi->ci.raw = raw; zi->ci.pos_local_header = ZTELL64(zi->z_filefunc,zi->filestream); zi->ci.size_centralheader = SIZECENTRALHEADER + size_filename + size_extrafield_global + size_comment; zi->ci.size_centralExtraFree = 32; // Extra space we have reserved in case we need to add ZIP64 extra info data zi->ci.central_header = (char*)ALLOC((uInt)zi->ci.size_centralheader + zi->ci.size_centralExtraFree); zi->ci.size_centralExtra = size_extrafield_global; zip64local_putValue_inmemory(zi->ci.central_header,(uLong)CENTRALHEADERMAGIC,4); /* version info */ zip64local_putValue_inmemory(zi->ci.central_header+4,(uLong)versionMadeBy,2); zip64local_putValue_inmemory(zi->ci.central_header+6,(uLong)20,2); zip64local_putValue_inmemory(zi->ci.central_header+8,(uLong)zi->ci.flag,2); zip64local_putValue_inmemory(zi->ci.central_header+10,(uLong)zi->ci.method,2); zip64local_putValue_inmemory(zi->ci.central_header+12,(uLong)zi->ci.dosDate,4); zip64local_putValue_inmemory(zi->ci.central_header+16,(uLong)0,4); /*crc*/ zip64local_putValue_inmemory(zi->ci.central_header+20,(uLong)0,4); /*compr size*/ zip64local_putValue_inmemory(zi->ci.central_header+24,(uLong)0,4); /*uncompr size*/ zip64local_putValue_inmemory(zi->ci.central_header+28,(uLong)size_filename,2); zip64local_putValue_inmemory(zi->ci.central_header+30,(uLong)size_extrafield_global,2); zip64local_putValue_inmemory(zi->ci.central_header+32,(uLong)size_comment,2); zip64local_putValue_inmemory(zi->ci.central_header+34,(uLong)0,2); /*disk nm start*/ if (zipfi==NULL) zip64local_putValue_inmemory(zi->ci.central_header+36,(uLong)0,2); else zip64local_putValue_inmemory(zi->ci.central_header+36,(uLong)zipfi->internal_fa,2); if (zipfi==NULL) zip64local_putValue_inmemory(zi->ci.central_header+38,(uLong)0,4); else zip64local_putValue_inmemory(zi->ci.central_header+38,(uLong)zipfi->external_fa,4); if(zi->ci.pos_local_header >= 0xffffffff) zip64local_putValue_inmemory(zi->ci.central_header+42,(uLong)0xffffffff,4); else zip64local_putValue_inmemory(zi->ci.central_header+42,(uLong)zi->ci.pos_local_header - zi->add_position_when_writting_offset,4); for (i=0;i<size_filename;i++) *(zi->ci.central_header+SIZECENTRALHEADER+i) = *(filename+i); for (i=0;i<size_extrafield_global;i++) *(zi->ci.central_header+SIZECENTRALHEADER+size_filename+i) = *(((const char*)extrafield_global)+i); for (i=0;i<size_comment;i++) *(zi->ci.central_header+SIZECENTRALHEADER+size_filename+ size_extrafield_global+i) = *(comment+i); if (zi->ci.central_header == NULL) return ZIP_INTERNALERROR; zi->ci.zip64 = zip64; zi->ci.totalCompressedData = 0; zi->ci.totalUncompressedData = 0; zi->ci.pos_zip64extrainfo = 0; err = Write_LocalFileHeader(zi, filename, size_extrafield_local, extrafield_local); #ifdef HAVE_BZIP2 zi->ci.bstream.avail_in = (uInt)0; zi->ci.bstream.avail_out = (uInt)Z_BUFSIZE; zi->ci.bstream.next_out = (char*)zi->ci.buffered_data; zi->ci.bstream.total_in_hi32 = 0; zi->ci.bstream.total_in_lo32 = 0; zi->ci.bstream.total_out_hi32 = 0; zi->ci.bstream.total_out_lo32 = 0; #endif zi->ci.stream.avail_in = (uInt)0; zi->ci.stream.avail_out = (uInt)Z_BUFSIZE; zi->ci.stream.next_out = zi->ci.buffered_data; zi->ci.stream.total_in = 0; zi->ci.stream.total_out = 0; zi->ci.stream.data_type = Z_BINARY; #ifdef HAVE_BZIP2 if ((err==ZIP_OK) && (zi->ci.method == Z_DEFLATED || zi->ci.method == Z_BZIP2ED) && (!zi->ci.raw)) #else if ((err==ZIP_OK) && (zi->ci.method == Z_DEFLATED) && (!zi->ci.raw)) #endif { if(zi->ci.method == Z_DEFLATED) { zi->ci.stream.zalloc = (alloc_func)0; zi->ci.stream.zfree = (free_func)0; zi->ci.stream.opaque = (voidpf)0; if (windowBits>0) windowBits = -windowBits; err = deflateInit2(&zi->ci.stream, level, Z_DEFLATED, windowBits, memLevel, strategy); if (err==Z_OK) zi->ci.stream_initialised = Z_DEFLATED; } else if(zi->ci.method == Z_BZIP2ED) { #ifdef HAVE_BZIP2 // Init BZip stuff here zi->ci.bstream.bzalloc = 0; zi->ci.bstream.bzfree = 0; zi->ci.bstream.opaque = (voidpf)0; err = BZ2_bzCompressInit(&zi->ci.bstream, level, 0,35); if(err == BZ_OK) zi->ci.stream_initialised = Z_BZIP2ED; #endif } } # ifndef NOCRYPT zi->ci.crypt_header_size = 0; if ((err==Z_OK) && (password != NULL)) { unsigned char bufHead[RAND_HEAD_LEN]; unsigned int sizeHead; zi->ci.encrypt = 1; zi->ci.pcrc_32_tab = get_crc_table(); /*init_keys(password,zi->ci.keys,zi->ci.pcrc_32_tab);*/ sizeHead=crypthead(password,bufHead,RAND_HEAD_LEN,zi->ci.keys,zi->ci.pcrc_32_tab,crcForCrypting); zi->ci.crypt_header_size = sizeHead; if (ZWRITE64(zi->z_filefunc,zi->filestream,bufHead,sizeHead) != sizeHead) err = ZIP_ERRNO; } # endif if (err==Z_OK) zi->in_opened_file_inzip = 1; return err; } extern int ZEXPORT zipOpenNewFileInZip4 (zipFile file, const char* filename, const zip_fileinfo* zipfi, const void* extrafield_local, uInt size_extrafield_local, const void* extrafield_global, uInt size_extrafield_global, const char* comment, int method, int level, int raw, int windowBits,int memLevel, int strategy, const char* password, uLong crcForCrypting, uLong versionMadeBy, uLong flagBase) { return zipOpenNewFileInZip4_64 (file, filename, zipfi, extrafield_local, size_extrafield_local, extrafield_global, size_extrafield_global, comment, method, level, raw, windowBits, memLevel, strategy, password, crcForCrypting, versionMadeBy, flagBase, 0); } extern int ZEXPORT zipOpenNewFileInZip3 (zipFile file, const char* filename, const zip_fileinfo* zipfi, const void* extrafield_local, uInt size_extrafield_local, const void* extrafield_global, uInt size_extrafield_global, const char* comment, int method, int level, int raw, int windowBits,int memLevel, int strategy, const char* password, uLong crcForCrypting) { return zipOpenNewFileInZip4_64 (file, filename, zipfi, extrafield_local, size_extrafield_local, extrafield_global, size_extrafield_global, comment, method, level, raw, windowBits, memLevel, strategy, password, crcForCrypting, VERSIONMADEBY, 0, 0); } extern int ZEXPORT zipOpenNewFileInZip3_64(zipFile file, const char* filename, const zip_fileinfo* zipfi, const void* extrafield_local, uInt size_extrafield_local, const void* extrafield_global, uInt size_extrafield_global, const char* comment, int method, int level, int raw, int windowBits,int memLevel, int strategy, const char* password, uLong crcForCrypting, int zip64) { return zipOpenNewFileInZip4_64 (file, filename, zipfi, extrafield_local, size_extrafield_local, extrafield_global, size_extrafield_global, comment, method, level, raw, windowBits, memLevel, strategy, password, crcForCrypting, VERSIONMADEBY, 0, zip64); } extern int ZEXPORT zipOpenNewFileInZip2(zipFile file, const char* filename, const zip_fileinfo* zipfi, const void* extrafield_local, uInt size_extrafield_local, const void* extrafield_global, uInt size_extrafield_global, const char* comment, int method, int level, int raw) { return zipOpenNewFileInZip4_64 (file, filename, zipfi, extrafield_local, size_extrafield_local, extrafield_global, size_extrafield_global, comment, method, level, raw, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY, NULL, 0, VERSIONMADEBY, 0, 0); } extern int ZEXPORT zipOpenNewFileInZip2_64(zipFile file, const char* filename, const zip_fileinfo* zipfi, const void* extrafield_local, uInt size_extrafield_local, const void* extrafield_global, uInt size_extrafield_global, const char* comment, int method, int level, int raw, int zip64) { return zipOpenNewFileInZip4_64 (file, filename, zipfi, extrafield_local, size_extrafield_local, extrafield_global, size_extrafield_global, comment, method, level, raw, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY, NULL, 0, VERSIONMADEBY, 0, zip64); } extern int ZEXPORT zipOpenNewFileInZip64 (zipFile file, const char* filename, const zip_fileinfo* zipfi, const void* extrafield_local, uInt size_extrafield_local, const void*extrafield_global, uInt size_extrafield_global, const char* comment, int method, int level, int zip64) { return zipOpenNewFileInZip4_64 (file, filename, zipfi, extrafield_local, size_extrafield_local, extrafield_global, size_extrafield_global, comment, method, level, 0, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY, NULL, 0, VERSIONMADEBY, 0, zip64); } extern int ZEXPORT zipOpenNewFileInZip (zipFile file, const char* filename, const zip_fileinfo* zipfi, const void* extrafield_local, uInt size_extrafield_local, const void*extrafield_global, uInt size_extrafield_global, const char* comment, int method, int level) { return zipOpenNewFileInZip4_64 (file, filename, zipfi, extrafield_local, size_extrafield_local, extrafield_global, size_extrafield_global, comment, method, level, 0, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY, NULL, 0, VERSIONMADEBY, 0, 0); } local int zip64FlushWriteBuffer(zip64_internal* zi) { int err=ZIP_OK; if (zi->ci.encrypt != 0) { #ifndef NOCRYPT uInt i; int t; for (i=0;i<zi->ci.pos_in_buffered_data;i++) zi->ci.buffered_data[i] = zencode(zi->ci.keys, zi->ci.pcrc_32_tab, zi->ci.buffered_data[i],t); #endif } if (ZWRITE64(zi->z_filefunc,zi->filestream,zi->ci.buffered_data,zi->ci.pos_in_buffered_data) != zi->ci.pos_in_buffered_data) err = ZIP_ERRNO; zi->ci.totalCompressedData += zi->ci.pos_in_buffered_data; #ifdef HAVE_BZIP2 if(zi->ci.method == Z_BZIP2ED) { zi->ci.totalUncompressedData += zi->ci.bstream.total_in_lo32; zi->ci.bstream.total_in_lo32 = 0; zi->ci.bstream.total_in_hi32 = 0; } else #endif { zi->ci.totalUncompressedData += zi->ci.stream.total_in; zi->ci.stream.total_in = 0; } zi->ci.pos_in_buffered_data = 0; return err; } extern int ZEXPORT zipWriteInFileInZip (zipFile file,const void* buf,unsigned int len) { zip64_internal* zi; int err=ZIP_OK; if (file == NULL) return ZIP_PARAMERROR; zi = (zip64_internal*)file; if (zi->in_opened_file_inzip == 0) return ZIP_PARAMERROR; zi->ci.crc32 = crc32(zi->ci.crc32,(const Bytef *)buf,(uInt)len); #ifdef HAVE_BZIP2 if(zi->ci.method == Z_BZIP2ED && (!zi->ci.raw)) { zi->ci.bstream.next_in = (void*)buf; zi->ci.bstream.avail_in = len; err = BZ_RUN_OK; while ((err==BZ_RUN_OK) && (zi->ci.bstream.avail_in>0)) { if (zi->ci.bstream.avail_out == 0) { if (zip64FlushWriteBuffer(zi) == ZIP_ERRNO) err = ZIP_ERRNO; zi->ci.bstream.avail_out = (uInt)Z_BUFSIZE; zi->ci.bstream.next_out = (char*)zi->ci.buffered_data; } if(err != BZ_RUN_OK) break; if ((zi->ci.method == Z_BZIP2ED) && (!zi->ci.raw)) { uLong uTotalOutBefore_lo = zi->ci.bstream.total_out_lo32; // uLong uTotalOutBefore_hi = zi->ci.bstream.total_out_hi32; err=BZ2_bzCompress(&zi->ci.bstream, BZ_RUN); zi->ci.pos_in_buffered_data += (uInt)(zi->ci.bstream.total_out_lo32 - uTotalOutBefore_lo) ; } } if(err == BZ_RUN_OK) err = ZIP_OK; } else #endif { zi->ci.stream.next_in = (Bytef*)buf; zi->ci.stream.avail_in = len; while ((err==ZIP_OK) && (zi->ci.stream.avail_in>0)) { if (zi->ci.stream.avail_out == 0) { if (zip64FlushWriteBuffer(zi) == ZIP_ERRNO) err = ZIP_ERRNO; zi->ci.stream.avail_out = (uInt)Z_BUFSIZE; zi->ci.stream.next_out = zi->ci.buffered_data; } if(err != ZIP_OK) break; if ((zi->ci.method == Z_DEFLATED) && (!zi->ci.raw)) { uLong uTotalOutBefore = zi->ci.stream.total_out; err=deflate(&zi->ci.stream, Z_NO_FLUSH); if(uTotalOutBefore > zi->ci.stream.total_out) { int bBreak = 0; bBreak++; } zi->ci.pos_in_buffered_data += (uInt)(zi->ci.stream.total_out - uTotalOutBefore) ; } else { uInt copy_this,i; if (zi->ci.stream.avail_in < zi->ci.stream.avail_out) copy_this = zi->ci.stream.avail_in; else copy_this = zi->ci.stream.avail_out; for (i = 0; i < copy_this; i++) *(((char*)zi->ci.stream.next_out)+i) = *(((const char*)zi->ci.stream.next_in)+i); { zi->ci.stream.avail_in -= copy_this; zi->ci.stream.avail_out-= copy_this; zi->ci.stream.next_in+= copy_this; zi->ci.stream.next_out+= copy_this; zi->ci.stream.total_in+= copy_this; zi->ci.stream.total_out+= copy_this; zi->ci.pos_in_buffered_data += copy_this; } } }// while(...) } return err; } extern int ZEXPORT zipCloseFileInZipRaw (zipFile file, uLong uncompressed_size, uLong crc32) { return zipCloseFileInZipRaw64 (file, uncompressed_size, crc32); } extern int ZEXPORT zipCloseFileInZipRaw64 (zipFile file, ZPOS64_T uncompressed_size, uLong crc32) { zip64_internal* zi; ZPOS64_T compressed_size; uLong invalidValue = 0xffffffff; short datasize = 0; int err=ZIP_OK; if (file == NULL) return ZIP_PARAMERROR; zi = (zip64_internal*)file; if (zi->in_opened_file_inzip == 0) return ZIP_PARAMERROR; zi->ci.stream.avail_in = 0; if ((zi->ci.method == Z_DEFLATED) && (!zi->ci.raw)) { while (err==ZIP_OK) { uLong uTotalOutBefore; if (zi->ci.stream.avail_out == 0) { if (zip64FlushWriteBuffer(zi) == ZIP_ERRNO) err = ZIP_ERRNO; zi->ci.stream.avail_out = (uInt)Z_BUFSIZE; zi->ci.stream.next_out = zi->ci.buffered_data; } uTotalOutBefore = zi->ci.stream.total_out; err=deflate(&zi->ci.stream, Z_FINISH); zi->ci.pos_in_buffered_data += (uInt)(zi->ci.stream.total_out - uTotalOutBefore) ; } } else if ((zi->ci.method == Z_BZIP2ED) && (!zi->ci.raw)) { #ifdef HAVE_BZIP2 err = BZ_FINISH_OK; while (err==BZ_FINISH_OK) { uLong uTotalOutBefore; if (zi->ci.bstream.avail_out == 0) { if (zip64FlushWriteBuffer(zi) == ZIP_ERRNO) err = ZIP_ERRNO; zi->ci.bstream.avail_out = (uInt)Z_BUFSIZE; zi->ci.bstream.next_out = (char*)zi->ci.buffered_data; } uTotalOutBefore = zi->ci.bstream.total_out_lo32; err=BZ2_bzCompress(&zi->ci.bstream, BZ_FINISH); if(err == BZ_STREAM_END) err = Z_STREAM_END; zi->ci.pos_in_buffered_data += (uInt)(zi->ci.bstream.total_out_lo32 - uTotalOutBefore); } if(err == BZ_FINISH_OK) err = ZIP_OK; #endif } if (err==Z_STREAM_END) err=ZIP_OK; /* this is normal */ if ((zi->ci.pos_in_buffered_data>0) && (err==ZIP_OK)) { if (zip64FlushWriteBuffer(zi)==ZIP_ERRNO) err = ZIP_ERRNO; } if ((zi->ci.method == Z_DEFLATED) && (!zi->ci.raw)) { int tmp_err = deflateEnd(&zi->ci.stream); if (err == ZIP_OK) err = tmp_err; zi->ci.stream_initialised = 0; } #ifdef HAVE_BZIP2 else if((zi->ci.method == Z_BZIP2ED) && (!zi->ci.raw)) { int tmperr = BZ2_bzCompressEnd(&zi->ci.bstream); if (err==ZIP_OK) err = tmperr; zi->ci.stream_initialised = 0; } #endif if (!zi->ci.raw) { crc32 = (uLong)zi->ci.crc32; uncompressed_size = zi->ci.totalUncompressedData; } compressed_size = zi->ci.totalCompressedData; # ifndef NOCRYPT compressed_size += zi->ci.crypt_header_size; # endif // update Current Item crc and sizes, if(compressed_size >= 0xffffffff || uncompressed_size >= 0xffffffff || zi->ci.pos_local_header >= 0xffffffff) { /*version Made by*/ zip64local_putValue_inmemory(zi->ci.central_header+4,(uLong)45,2); /*version needed*/ zip64local_putValue_inmemory(zi->ci.central_header+6,(uLong)45,2); } zip64local_putValue_inmemory(zi->ci.central_header+16,crc32,4); /*crc*/ if(compressed_size >= 0xffffffff) zip64local_putValue_inmemory(zi->ci.central_header+20, invalidValue,4); /*compr size*/ else zip64local_putValue_inmemory(zi->ci.central_header+20, compressed_size,4); /*compr size*/ /// set internal file attributes field if (zi->ci.stream.data_type == Z_ASCII) zip64local_putValue_inmemory(zi->ci.central_header+36,(uLong)Z_ASCII,2); if(uncompressed_size >= 0xffffffff) zip64local_putValue_inmemory(zi->ci.central_header+24, invalidValue,4); /*uncompr size*/ else zip64local_putValue_inmemory(zi->ci.central_header+24, uncompressed_size,4); /*uncompr size*/ // Add ZIP64 extra info field for uncompressed size if(uncompressed_size >= 0xffffffff) datasize += 8; // Add ZIP64 extra info field for compressed size if(compressed_size >= 0xffffffff) datasize += 8; // Add ZIP64 extra info field for relative offset to local file header of current file if(zi->ci.pos_local_header >= 0xffffffff) datasize += 8; if(datasize > 0) { char* p = NULL; if((uLong)(datasize + 4) > zi->ci.size_centralExtraFree) { // we can not write more data to the buffer that we have room for. return ZIP_BADZIPFILE; } p = zi->ci.central_header + zi->ci.size_centralheader; // Add Extra Information Header for 'ZIP64 information' zip64local_putValue_inmemory(p, 0x0001, 2); // HeaderID p += 2; zip64local_putValue_inmemory(p, datasize, 2); // DataSize p += 2; if(uncompressed_size >= 0xffffffff) { zip64local_putValue_inmemory(p, uncompressed_size, 8); p += 8; } if(compressed_size >= 0xffffffff) { zip64local_putValue_inmemory(p, compressed_size, 8); p += 8; } if(zi->ci.pos_local_header >= 0xffffffff) { zip64local_putValue_inmemory(p, zi->ci.pos_local_header, 8); p += 8; } // Update how much extra free space we got in the memory buffer // and increase the centralheader size so the new ZIP64 fields are included // ( 4 below is the size of HeaderID and DataSize field ) zi->ci.size_centralExtraFree -= datasize + 4; zi->ci.size_centralheader += datasize + 4; // Update the extra info size field zi->ci.size_centralExtra += datasize + 4; zip64local_putValue_inmemory(zi->ci.central_header+30,(uLong)zi->ci.size_centralExtra,2); } if (err==ZIP_OK) err = add_data_in_datablock(&zi->central_dir, zi->ci.central_header, (uLong)zi->ci.size_centralheader); free(zi->ci.central_header); if (err==ZIP_OK) { // Update the LocalFileHeader with the new values. ZPOS64_T cur_pos_inzip = ZTELL64(zi->z_filefunc,zi->filestream); if (ZSEEK64(zi->z_filefunc,zi->filestream, zi->ci.pos_local_header + 14,ZLIB_FILEFUNC_SEEK_SET)!=0) err = ZIP_ERRNO; if (err==ZIP_OK) err = zip64local_putValue(&zi->z_filefunc,zi->filestream,crc32,4); /* crc 32, unknown */ if(uncompressed_size >= 0xffffffff || compressed_size >= 0xffffffff ) { if(zi->ci.pos_zip64extrainfo > 0) { // Update the size in the ZIP64 extended field. if (ZSEEK64(zi->z_filefunc,zi->filestream, zi->ci.pos_zip64extrainfo + 4,ZLIB_FILEFUNC_SEEK_SET)!=0) err = ZIP_ERRNO; if (err==ZIP_OK) /* compressed size, unknown */ err = zip64local_putValue(&zi->z_filefunc, zi->filestream, uncompressed_size, 8); if (err==ZIP_OK) /* uncompressed size, unknown */ err = zip64local_putValue(&zi->z_filefunc, zi->filestream, compressed_size, 8); } else err = ZIP_BADZIPFILE; // Caller passed zip64 = 0, so no room for zip64 info -> fatal } else { if (err==ZIP_OK) /* compressed size, unknown */ err = zip64local_putValue(&zi->z_filefunc,zi->filestream,compressed_size,4); if (err==ZIP_OK) /* uncompressed size, unknown */ err = zip64local_putValue(&zi->z_filefunc,zi->filestream,uncompressed_size,4); } if (ZSEEK64(zi->z_filefunc,zi->filestream, cur_pos_inzip,ZLIB_FILEFUNC_SEEK_SET)!=0) err = ZIP_ERRNO; } zi->number_entry ++; zi->in_opened_file_inzip = 0; return err; } extern int ZEXPORT zipCloseFileInZip (zipFile file) { return zipCloseFileInZipRaw (file,0,0); } int Write_Zip64EndOfCentralDirectoryLocator(zip64_internal* zi, ZPOS64_T zip64eocd_pos_inzip) { int err = ZIP_OK; ZPOS64_T pos = zip64eocd_pos_inzip - zi->add_position_when_writting_offset; err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)ZIP64ENDLOCHEADERMAGIC,4); /*num disks*/ if (err==ZIP_OK) /* number of the disk with the start of the central directory */ err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4); /*relative offset*/ if (err==ZIP_OK) /* Relative offset to the Zip64EndOfCentralDirectory */ err = zip64local_putValue(&zi->z_filefunc,zi->filestream, pos,8); /*total disks*/ /* Do not support spawning of disk so always say 1 here*/ if (err==ZIP_OK) /* number of the disk with the start of the central directory */ err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)1,4); return err; } int Write_Zip64EndOfCentralDirectoryRecord(zip64_internal* zi, uLong size_centraldir, ZPOS64_T centraldir_pos_inzip) { int err = ZIP_OK; uLong Zip64DataSize = 44; err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)ZIP64ENDHEADERMAGIC,4); if (err==ZIP_OK) /* size of this 'zip64 end of central directory' */ err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(ZPOS64_T)Zip64DataSize,8); // why ZPOS64_T of this ? if (err==ZIP_OK) /* version made by */ err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)45,2); if (err==ZIP_OK) /* version needed */ err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)45,2); if (err==ZIP_OK) /* number of this disk */ err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4); if (err==ZIP_OK) /* number of the disk with the start of the central directory */ err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4); if (err==ZIP_OK) /* total number of entries in the central dir on this disk */ err = zip64local_putValue(&zi->z_filefunc, zi->filestream, zi->number_entry, 8); if (err==ZIP_OK) /* total number of entries in the central dir */ err = zip64local_putValue(&zi->z_filefunc, zi->filestream, zi->number_entry, 8); if (err==ZIP_OK) /* size of the central directory */ err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(ZPOS64_T)size_centraldir,8); if (err==ZIP_OK) /* offset of start of central directory with respect to the starting disk number */ { ZPOS64_T pos = centraldir_pos_inzip - zi->add_position_when_writting_offset; err = zip64local_putValue(&zi->z_filefunc,zi->filestream, (ZPOS64_T)pos,8); } return err; } int Write_EndOfCentralDirectoryRecord(zip64_internal* zi, uLong size_centraldir, ZPOS64_T centraldir_pos_inzip) { int err = ZIP_OK; /*signature*/ err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)ENDHEADERMAGIC,4); if (err==ZIP_OK) /* number of this disk */ err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,2); if (err==ZIP_OK) /* number of the disk with the start of the central directory */ err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,2); if (err==ZIP_OK) /* total number of entries in the central dir on this disk */ { { if(zi->number_entry >= 0xFFFF) err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0xffff,2); // use value in ZIP64 record else err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->number_entry,2); } } if (err==ZIP_OK) /* total number of entries in the central dir */ { if(zi->number_entry >= 0xFFFF) err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0xffff,2); // use value in ZIP64 record else err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->number_entry,2); } if (err==ZIP_OK) /* size of the central directory */ err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)size_centraldir,4); if (err==ZIP_OK) /* offset of start of central directory with respect to the starting disk number */ { ZPOS64_T pos = centraldir_pos_inzip - zi->add_position_when_writting_offset; if(pos >= 0xffffffff) { err = zip64local_putValue(&zi->z_filefunc,zi->filestream, (uLong)0xffffffff,4); } else err = zip64local_putValue(&zi->z_filefunc,zi->filestream, (uLong)(centraldir_pos_inzip - zi->add_position_when_writting_offset),4); } return err; } int Write_GlobalComment(zip64_internal* zi, const char* global_comment) { int err = ZIP_OK; uInt size_global_comment = 0; if(global_comment != NULL) size_global_comment = (uInt)strlen(global_comment); err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)size_global_comment,2); if (err == ZIP_OK && size_global_comment > 0) { if (ZWRITE64(zi->z_filefunc,zi->filestream, global_comment, size_global_comment) != size_global_comment) err = ZIP_ERRNO; } return err; } extern int ZEXPORT zipClose (zipFile file, const char* global_comment) { zip64_internal* zi; int err = 0; uLong size_centraldir = 0; ZPOS64_T centraldir_pos_inzip; ZPOS64_T pos; if (file == NULL) return ZIP_PARAMERROR; zi = (zip64_internal*)file; if (zi->in_opened_file_inzip == 1) { err = zipCloseFileInZip (file); } #ifndef NO_ADDFILEINEXISTINGZIP if (global_comment==NULL) global_comment = zi->globalcomment; #endif centraldir_pos_inzip = ZTELL64(zi->z_filefunc,zi->filestream); if (err==ZIP_OK) { linkedlist_datablock_internal* ldi = zi->central_dir.first_block; while (ldi!=NULL) { if ((err==ZIP_OK) && (ldi->filled_in_this_block>0)) { if (ZWRITE64(zi->z_filefunc,zi->filestream, ldi->data, ldi->filled_in_this_block) != ldi->filled_in_this_block) err = ZIP_ERRNO; } size_centraldir += ldi->filled_in_this_block; ldi = ldi->next_datablock; } } free_linkedlist(&(zi->central_dir)); pos = centraldir_pos_inzip - zi->add_position_when_writting_offset; if(pos >= 0xffffffff || zi->number_entry > 0xFFFF) { ZPOS64_T Zip64EOCDpos = ZTELL64(zi->z_filefunc,zi->filestream); Write_Zip64EndOfCentralDirectoryRecord(zi, size_centraldir, centraldir_pos_inzip); Write_Zip64EndOfCentralDirectoryLocator(zi, Zip64EOCDpos); } if (err==ZIP_OK) err = Write_EndOfCentralDirectoryRecord(zi, size_centraldir, centraldir_pos_inzip); if(err == ZIP_OK) err = Write_GlobalComment(zi, global_comment); if (ZCLOSE64(zi->z_filefunc,zi->filestream) != 0) if (err == ZIP_OK) err = ZIP_ERRNO; #ifndef NO_ADDFILEINEXISTINGZIP TRYFREE(zi->globalcomment); #endif TRYFREE(zi); return err; } extern int ZEXPORT zipRemoveExtraInfoBlock (char* pData, int* dataLen, short sHeader) { char* p = pData; int size = 0; char* pNewHeader; char* pTmp; short header; short dataSize; int retVal = ZIP_OK; if(pData == NULL || *dataLen < 4) return ZIP_PARAMERROR; pNewHeader = (char*)ALLOC(*dataLen); pTmp = pNewHeader; while(p < (pData + *dataLen)) { header = *(short*)p; dataSize = *(((short*)p)+1); if( header == sHeader ) // Header found. { p += dataSize + 4; // skip it. do not copy to temp buffer } else { // Extra Info block should not be removed, So copy it to the temp buffer. memcpy(pTmp, p, dataSize + 4); p += dataSize + 4; size += dataSize + 4; } } if(size < *dataLen) { // clean old extra info block. memset(pData,0, *dataLen); // copy the new extra info block over the old if(size > 0) memcpy(pData, pNewHeader, size); // set the new extra info size *dataLen = size; retVal = ZIP_OK; } else retVal = ZIP_ERRNO; TRYFREE(pNewHeader); return retVal; } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/ThirdParty/minizip/zip.h���������������������������������������������������0000664�0000000�0000000�00000036006�13262451251�0021731�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* zip.h -- IO on .zip files using zlib Version 1.1, February 14h, 2010 part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html ) Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html ) Modifications for Zip64 support Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com ) For more info read MiniZip_info.txt --------------------------------------------------------------------------- Condition of use and distribution are the same than zlib : This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. --------------------------------------------------------------------------- Changes See header of zip.h */ #ifndef _zip12_H #define _zip12_H #ifdef __cplusplus extern "C" { #endif //#define HAVE_BZIP2 #ifndef _ZLIB_H #include "zlib.h" #endif #ifndef _ZLIBIOAPI_H #include "ioapi.h" #endif #ifdef HAVE_BZIP2 #include "bzlib.h" #endif #define Z_BZIP2ED 12 #if defined(STRICTZIP) || defined(STRICTZIPUNZIP) /* like the STRICT of WIN32, we define a pointer that cannot be converted from (void*) without cast */ typedef struct TagzipFile__ { int unused; } zipFile__; typedef zipFile__ *zipFile; #else typedef voidp zipFile; #endif #define ZIP_OK (0) #define ZIP_EOF (0) #define ZIP_ERRNO (Z_ERRNO) #define ZIP_PARAMERROR (-102) #define ZIP_BADZIPFILE (-103) #define ZIP_INTERNALERROR (-104) #ifndef DEF_MEM_LEVEL # if MAX_MEM_LEVEL >= 8 # define DEF_MEM_LEVEL 8 # else # define DEF_MEM_LEVEL MAX_MEM_LEVEL # endif #endif /* default memLevel */ /* tm_zip contain date/time info */ typedef struct tm_zip_s { uInt tm_sec; /* seconds after the minute - [0,59] */ uInt tm_min; /* minutes after the hour - [0,59] */ uInt tm_hour; /* hours since midnight - [0,23] */ uInt tm_mday; /* day of the month - [1,31] */ uInt tm_mon; /* months since January - [0,11] */ uInt tm_year; /* years - [1980..2044] */ } tm_zip; typedef struct { tm_zip tmz_date; /* date in understandable format */ uLong dosDate; /* if dos_date == 0, tmu_date is used */ /* uLong flag; */ /* general purpose bit flag 2 bytes */ uLong internal_fa; /* internal file attributes 2 bytes */ uLong external_fa; /* external file attributes 4 bytes */ } zip_fileinfo; typedef const char* zipcharpc; #define APPEND_STATUS_CREATE (0) #define APPEND_STATUS_CREATEAFTER (1) #define APPEND_STATUS_ADDINZIP (2) extern zipFile ZEXPORT zipOpen OF((const char *pathname, int append)); extern zipFile ZEXPORT zipOpen64 OF((const void *pathname, int append)); /* Create a zipfile. pathname contain on Windows XP a filename like "c:\\zlib\\zlib113.zip" or on an Unix computer "zlib/zlib113.zip". if the file pathname exist and append==APPEND_STATUS_CREATEAFTER, the zip will be created at the end of the file. (useful if the file contain a self extractor code) if the file pathname exist and append==APPEND_STATUS_ADDINZIP, we will add files in existing zip (be sure you don't add file that doesn't exist) If the zipfile cannot be opened, the return value is NULL. Else, the return value is a zipFile Handle, usable with other function of this zip package. */ /* Note : there is no delete function into a zipfile. If you want delete file into a zipfile, you must open a zipfile, and create another Of couse, you can use RAW reading and writing to copy the file you did not want delte */ extern zipFile ZEXPORT zipOpen2 OF((const char *pathname, int append, zipcharpc* globalcomment, zlib_filefunc_def* pzlib_filefunc_def)); extern zipFile ZEXPORT zipOpen2_64 OF((const void *pathname, int append, zipcharpc* globalcomment, zlib_filefunc64_def* pzlib_filefunc_def)); extern int ZEXPORT zipOpenNewFileInZip OF((zipFile file, const char* filename, const zip_fileinfo* zipfi, const void* extrafield_local, uInt size_extrafield_local, const void* extrafield_global, uInt size_extrafield_global, const char* comment, int method, int level)); extern int ZEXPORT zipOpenNewFileInZip64 OF((zipFile file, const char* filename, const zip_fileinfo* zipfi, const void* extrafield_local, uInt size_extrafield_local, const void* extrafield_global, uInt size_extrafield_global, const char* comment, int method, int level, int zip64)); /* Open a file in the ZIP for writing. filename : the filename in zip (if NULL, '-' without quote will be used *zipfi contain supplemental information if extrafield_local!=NULL and size_extrafield_local>0, extrafield_local contains the extrafield data the the local header if extrafield_global!=NULL and size_extrafield_global>0, extrafield_global contains the extrafield data the the local header if comment != NULL, comment contain the comment string method contain the compression method (0 for store, Z_DEFLATED for deflate) level contain the level of compression (can be Z_DEFAULT_COMPRESSION) zip64 is set to 1 if a zip64 extended information block should be added to the local file header. this MUST be '1' if the uncompressed size is >= 0xffffffff. */ extern int ZEXPORT zipOpenNewFileInZip2 OF((zipFile file, const char* filename, const zip_fileinfo* zipfi, const void* extrafield_local, uInt size_extrafield_local, const void* extrafield_global, uInt size_extrafield_global, const char* comment, int method, int level, int raw)); extern int ZEXPORT zipOpenNewFileInZip2_64 OF((zipFile file, const char* filename, const zip_fileinfo* zipfi, const void* extrafield_local, uInt size_extrafield_local, const void* extrafield_global, uInt size_extrafield_global, const char* comment, int method, int level, int raw, int zip64)); /* Same than zipOpenNewFileInZip, except if raw=1, we write raw file */ extern int ZEXPORT zipOpenNewFileInZip3 OF((zipFile file, const char* filename, const zip_fileinfo* zipfi, const void* extrafield_local, uInt size_extrafield_local, const void* extrafield_global, uInt size_extrafield_global, const char* comment, int method, int level, int raw, int windowBits, int memLevel, int strategy, const char* password, uLong crcForCrypting)); extern int ZEXPORT zipOpenNewFileInZip3_64 OF((zipFile file, const char* filename, const zip_fileinfo* zipfi, const void* extrafield_local, uInt size_extrafield_local, const void* extrafield_global, uInt size_extrafield_global, const char* comment, int method, int level, int raw, int windowBits, int memLevel, int strategy, const char* password, uLong crcForCrypting, int zip64 )); /* Same than zipOpenNewFileInZip2, except windowBits,memLevel,,strategy : see parameter strategy in deflateInit2 password : crypting password (NULL for no crypting) crcForCrypting : crc of file to compress (needed for crypting) */ extern int ZEXPORT zipOpenNewFileInZip4 OF((zipFile file, const char* filename, const zip_fileinfo* zipfi, const void* extrafield_local, uInt size_extrafield_local, const void* extrafield_global, uInt size_extrafield_global, const char* comment, int method, int level, int raw, int windowBits, int memLevel, int strategy, const char* password, uLong crcForCrypting, uLong versionMadeBy, uLong flagBase )); extern int ZEXPORT zipOpenNewFileInZip4_64 OF((zipFile file, const char* filename, const zip_fileinfo* zipfi, const void* extrafield_local, uInt size_extrafield_local, const void* extrafield_global, uInt size_extrafield_global, const char* comment, int method, int level, int raw, int windowBits, int memLevel, int strategy, const char* password, uLong crcForCrypting, uLong versionMadeBy, uLong flagBase, int zip64 )); /* Same than zipOpenNewFileInZip4, except versionMadeBy : value for Version made by field flag : value for flag field (compression level info will be added) */ extern int ZEXPORT zipWriteInFileInZip OF((zipFile file, const void* buf, unsigned len)); /* Write data in the zipfile */ extern int ZEXPORT zipCloseFileInZip OF((zipFile file)); /* Close the current file in the zipfile */ extern int ZEXPORT zipCloseFileInZipRaw OF((zipFile file, uLong uncompressed_size, uLong crc32)); extern int ZEXPORT zipCloseFileInZipRaw64 OF((zipFile file, ZPOS64_T uncompressed_size, uLong crc32)); /* Close the current file in the zipfile, for file opened with parameter raw=1 in zipOpenNewFileInZip2 uncompressed_size and crc32 are value for the uncompressed size */ extern int ZEXPORT zipClose OF((zipFile file, const char* global_comment)); /* Close the zipfile */ extern int ZEXPORT zipRemoveExtraInfoBlock OF((char* pData, int* dataLen, short sHeader)); /* zipRemoveExtraInfoBlock - Added by Mathias Svensson Remove extra information block from a extra information data for the local file header or central directory header It is needed to remove ZIP64 extra information blocks when before data is written if using RAW mode. 0x0001 is the signature header for the ZIP64 extra information blocks usage. Remove ZIP64 Extra information from a central director extra field data zipRemoveExtraInfoBlock(pCenDirExtraFieldData, &nCenDirExtraFieldDataLen, 0x0001); Remove ZIP64 Extra information from a Local File Header extra field data zipRemoveExtraInfoBlock(pLocalHeaderExtraFieldData, &nLocalHeaderExtraFieldDataLen, 0x0001); */ #ifdef __cplusplus } #endif #endif /* _zip64_H */ ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/ThirdParty/variant/��������������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0020736�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/build/���������������������������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0016277�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/build/build.bat������������������������������������������������������������0000664�0000000�0000000�00000024725�13262451251�0020100�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������@echo OFF setlocal enabledelayedexpansion set "FullScriptPath=%~dp0" set "FullExecPath=%cd%" if not exist "%FullScriptPath%..\..\..\TelegramPrivate" ( echo. echo This script is for building the production version of Telegram Desktop. echo. echo For building custom versions please visit the build instructions page at: echo https://github.com/telegramdesktop/tdesktop/#build-instructions exit /b ) FOR /F "tokens=1* delims= " %%i in (%FullScriptPath%target) do set "BuildTarget=%%i" if "%BuildTarget%" equ "uwp" ( set "BuildUWP=1" ) else ( set "BuildUWP=0" ) FOR /F "tokens=1,2* delims= " %%i in (%FullScriptPath%version) do set "%%i=%%j" set "VersionForPacker=%AppVersion%" if %BetaVersion% neq 0 ( set "AppVersion=%BetaVersion%" set "AppVersionStrFull=%AppVersionStr%_%BetaVersion%" set "AlphaBetaParam=-beta %BetaVersion%" set "BetaKeyFile=tbeta_%BetaVersion%_key" ) else ( if %AlphaChannel% neq 0 ( set "AlphaBetaParam=-alpha" set "AppVersionStrFull=%AppVersionStr%.alpha" ) else ( set "AlphaBetaParam=" set "AppVersionStrFull=%AppVersionStr%" ) ) echo. if %BuildUWP% neq 0 ( echo Building version %AppVersionStrFull% for UWP.. ) else ( echo Building version %AppVersionStrFull% for Windows.. ) echo. set "HomePath=%FullScriptPath%.." set "ResourcesPath=%HomePath%\Resources" set "SolutionPath=%HomePath%\.." set "UpdateFile=tupdate%AppVersion%" set "SetupFile=tsetup.%AppVersionStrFull%.exe" set "PortableFile=tportable.%AppVersionStrFull%.zip" set "ReleasePath=%HomePath%\..\out\Release" set "DeployPath=%ReleasePath%\deploy\%AppVersionStrMajor%\%AppVersionStrFull%" set "SignPath=%HomePath%\..\..\TelegramPrivate\Sign.bat" set "SignAppxPath=%HomePath%\..\..\TelegramPrivate\AppxSign.bat" set "BinaryName=Telegram" set "DropboxSymbolsPath=Y:\Telegram\symbols" set "FinalReleasePath=Z:\Telegram\backup" if not exist %DropboxSymbolsPath% ( echo Dropbox path %DropboxSymbolsPath% not found! exit /b 1 ) if not exist %FinalReleasePath% ( echo Release path %FinalReleasePath% not found! exit /b 1 ) if %BuildUWP% neq 0 ( if exist %ReleasePath%\AppX\ ( echo Result folder out\Release\AppX already exists! exit /b 1 ) ) if %BetaVersion% neq 0 ( if exist %DeployPath%\ ( echo Deploy folder for version %AppVersionStr% already exists! exit /b 1 ) if exist %ReleasePath%\%BetaKeyFile% ( echo Beta version key file for version %AppVersion% already exists! exit /b 1 ) ) else ( if exist %ReleasePath%\deploy\%AppVersionStrMajor%\%AppVersionStr%.alpha\ ( echo Deploy folder for version %AppVersionStr%.alpha already exists! exit /b 1 ) if exist %ReleasePath%\deploy\%AppVersionStrMajor%\%AppVersionStr%.dev\ ( echo Deploy folder for version %AppVersionStr%.dev already exists! exit /b 1 ) if exist %ReleasePath%\deploy\%AppVersionStrMajor%\%AppVersionStr%\ ( echo Deploy folder for version %AppVersionStr% already exists! exit /b 1 ) if exist %ReleasePath%\tupdate%AppVersion% ( echo Update file for version %AppVersion% already exists! exit /b 1 ) ) cd "%HomePath%" call gyp\refresh.bat if %errorlevel% neq 0 goto error cd "%SolutionPath%" call ninja -C out/Release Telegram if %errorlevel% neq 0 goto error echo. echo Version %AppVersionStrFull% build successfull. Preparing.. echo. echo Dumping debug symbols.. xcopy "%ReleasePath%\%BinaryName%.exe" "%ReleasePath%\%BinaryName%.exe.exe*" call "%SolutionPath%\..\Libraries\breakpad\src\tools\windows\dump_syms\Release\dump_syms.exe" "%ReleasePath%\%BinaryName%.exe.pdb" > "%ReleasePath%\%BinaryName%.exe.sym" del "%ReleasePath%\%BinaryName%.exe.exe" echo Done! set "PATH=%PATH%;C:\Program Files\7-Zip;C:\Program Files (x86)\Inno Setup 5" cd "%ReleasePath%" call "%SignPath%" "%BinaryName%.exe" if %errorlevel% neq 0 goto error if %BuildUWP% equ 0 ( call "%SignPath%" "Updater.exe" if %errorlevel% neq 0 goto error if %BetaVersion% equ 0 ( iscc /dMyAppVersion=%AppVersionStrSmall% /dMyAppVersionZero=%AppVersionStr% /dMyAppVersionFull=%AppVersionStrFull% "/dReleasePath=%ReleasePath%" "%FullScriptPath%setup.iss" if %errorlevel% neq 0 goto error if not exist "tsetup.%AppVersionStrFull%.exe" goto error call "%SignPath%" "tsetup.%AppVersionStrFull%.exe" if %errorlevel% neq 0 goto error ) call Packer.exe -version %VersionForPacker% -path %BinaryName%.exe -path Updater.exe %AlphaBetaParam% if %errorlevel% neq 0 goto error if %BetaVersion% neq 0 ( if not exist "%ReleasePath%\%BetaKeyFile%" ( echo Beta version key file not found! exit /b 1 ) FOR /F "tokens=1* delims= " %%i in (%ReleasePath%\%BetaKeyFile%) do set "BetaSignature=%%i" ) if %errorlevel% neq 0 goto error if %BetaVersion% neq 0 ( set "UpdateFile=!UpdateFile!_!BetaSignature!" set "PortableFile=tbeta!BetaVersion!_!BetaSignature!.zip" ) ) for /f ^"usebackq^ eol^=^ ^ delims^=^" %%a in (%ReleasePath%\%BinaryName%.exe.sym) do ( set "SymbolsHashLine=%%a" goto symbolslinedone ) :symbolslinedone FOR /F "tokens=1,2,3,4* delims= " %%i in ("%SymbolsHashLine%") do set "SymbolsHash=%%l" echo Copying %BinaryName%.exe.sym to %DropboxSymbolsPath%\%BinaryName%.exe.pdb\%SymbolsHash% if not exist %DropboxSymbolsPath%\%BinaryName%.exe.pdb mkdir %DropboxSymbolsPath%\%BinaryName%.exe.pdb if not exist %DropboxSymbolsPath%\%BinaryName%.exe.pdb\%SymbolsHash% mkdir %DropboxSymbolsPath%\%BinaryName%.exe.pdb\%SymbolsHash% move "%ReleasePath%\%BinaryName%.exe.sym" %DropboxSymbolsPath%\%BinaryName%.exe.pdb\%SymbolsHash%\ echo Done! if %BuildUWP% neq 0 ( cd "%HomePath%" mkdir "%ReleasePath%\AppX_x86" xcopy "Resources\uwp\AppX\*" "%ReleasePath%\AppX_x86\" /E set "ResourcePath=%ReleasePath%\AppX_x86\AppxManifest.xml" call :repl "Argument= (ProcessorArchitecture=)"ARCHITECTURE"/ $1"x86"" "Filename=!ResourcePath!" || goto error makepri new /pr Resources\uwp\AppX\ /cf Resources\uwp\priconfig.xml /mn %ReleasePath%\AppX_x86\AppxManifest.xml /of %ReleasePath%\AppX_x86\resources.pri if %errorlevel% neq 0 goto error xcopy "%ReleasePath%\%BinaryName%.exe" "%ReleasePath%\AppX_x86\" MakeAppx.exe pack /d "%ReleasePath%\AppX_x86" /l /p ..\out\Release\%BinaryName%.x86.appx if %errorlevel% neq 0 goto error mkdir "%ReleasePath%\AppX_x64" xcopy "Resources\uwp\AppX\*" "%ReleasePath%\AppX_x64\" /E set "ResourcePath=%ReleasePath%\AppX_x64\AppxManifest.xml" call :repl "Argument= (ProcessorArchitecture=)"ARCHITECTURE"/ $1"x64"" "Filename=!ResourcePath!" || goto error makepri new /pr Resources\uwp\AppX\ /cf Resources\uwp\priconfig.xml /mn %ReleasePath%\AppX_x64\AppxManifest.xml /of %ReleasePath%\AppX_x64\resources.pri if %errorlevel% neq 0 goto error xcopy "%ReleasePath%\%BinaryName%.exe" "%ReleasePath%\AppX_x64\" MakeAppx.exe pack /d "%ReleasePath%\AppX_x64" /l /p ..\out\Release\%BinaryName%.x64.appx if %errorlevel% neq 0 goto error if not exist "%ReleasePath%\deploy" mkdir "%ReleasePath%\deploy" if not exist "%ReleasePath%\deploy\%AppVersionStrMajor%" mkdir "%ReleasePath%\deploy\%AppVersionStrMajor%" mkdir "%DeployPath%" xcopy "%ReleasePath%\%BinaryName%.pdb" "%DeployPath%\" move "%ReleasePath%\%BinaryName%.exe.pdb" "%DeployPath%\" move "%ReleasePath%\%BinaryName%.x86.appx" "%DeployPath%\" move "%ReleasePath%\%BinaryName%.x64.appx" "%DeployPath%\" move "%ReleasePath%\%BinaryName%.exe" "%DeployPath%\" if "%AlphaBetaParam%" equ "" ( move "%ReleasePath%\AppX_x86" "%DeployPath%\AppX_x86" move "%ReleasePath%\AppX_x64" "%DeployPath%\AppX_x64" ) else ( echo Leaving result in out\Release\AppX_arch for now.. ) ) else ( if not exist "%ReleasePath%\deploy" mkdir "%ReleasePath%\deploy" if not exist "%ReleasePath%\deploy\%AppVersionStrMajor%" mkdir "%ReleasePath%\deploy\%AppVersionStrMajor%" mkdir "%DeployPath%" mkdir "%DeployPath%\%BinaryName%" if %errorlevel% neq 0 goto error move "%ReleasePath%\%BinaryName%.exe" "%DeployPath%\%BinaryName%\" move "%ReleasePath%\Updater.exe" "%DeployPath%\" xcopy "%ReleasePath%\%BinaryName%.pdb" "%DeployPath%\" xcopy "%ReleasePath%\Updater.pdb" "%DeployPath%\" move "%ReleasePath%\%BinaryName%.exe.pdb" "%DeployPath%\" move "%ReleasePath%\Updater.exe.pdb" "%DeployPath%\" if %BetaVersion% equ 0 ( move "%ReleasePath%\%SetupFile%" "%DeployPath%\" ) else ( move "%ReleasePath%\%BetaKeyFile%" "%DeployPath%\" ) move "%ReleasePath%\%UpdateFile%" "%DeployPath%\" if %errorlevel% neq 0 goto error cd "%DeployPath%" 7z a -mx9 %PortableFile% %BinaryName%\ if %errorlevel% neq 0 goto error ) set "FinalDeployPath=%FinalReleasePath%\%AppVersionStrMajor%\%AppVersionStrFull%\tsetup" if %BuildUWP% equ 0 ( echo. echo Version %AppVersionStrFull% is ready for deploy! echo. if not exist "%DeployPath%\%UpdateFile%" goto error if not exist "%DeployPath%\%PortableFile%" goto error if %BetaVersion% equ 0 ( if not exist "%DeployPath%\%SetupFile%" goto error ) if not exist "%DeployPath%\%BinaryName%.pdb" goto error if not exist "%DeployPath%\%BinaryName%.exe.pdb" goto error if not exist "%DeployPath%\Updater.exe" goto error if not exist "%DeployPath%\Updater.pdb" goto error if not exist "%DeployPath%\Updater.exe.pdb" goto error md "%FinalDeployPath%" xcopy "%DeployPath%\%UpdateFile%" "%FinalDeployPath%\" /Y xcopy "%DeployPath%\%PortableFile%" "%FinalDeployPath%\" /Y if %BetaVersion% equ 0 ( xcopy "%DeployPath%\%SetupFile%" "%FinalDeployPath%\" /Y ) else ( xcopy "%DeployPath%\%BetaKeyFile%" "%FinalDeployPath%\" /Y ) ) echo Version %AppVersionStrFull% is ready! cd "%FullExecPath%" exit /b :error ( set ErrorCode=%errorlevel% if !ErrorCode! neq 0 ( echo Error !ErrorCode! ) else ( echo Error 666 set ErrorCode=666 ) cd "%FullExecPath%" exit /b !ErrorCode! ) :repl ( set %1 set %2 set "TempFilename=!Filename!__tmp__" cscript //Nologo "%FullScriptPath%replace.vbs" "Replace" "!Argument!" < "!Filename!" > "!TempFilename!" || goto :repl_finish xcopy /Y !TempFilename! !Filename! >NUL || goto :repl_finish goto :repl_finish ) :repl_finish ( set ErrorCode=%errorlevel% if !ErrorCode! neq 0 ( echo Replace error !ErrorCode! echo While replacing "%Replace%" echo In file "%Filename%" ) del %TempFilename% exit /b !ErrorCode! ) �������������������������������������������tdesktop-1.2.17/Telegram/build/build.sh�������������������������������������������������������������0000775�0000000�0000000�00000034753�13262451251�0017751�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������set -e FullExecPath=$PWD pushd `dirname $0` > /dev/null FullScriptPath=`pwd` popd > /dev/null if [ ! -d "$FullScriptPath/../../../TelegramPrivate" ]; then echo "" echo "This script is for building the production version of Telegram Desktop." echo "" echo "For building custom versions please visit the build instructions page at:" echo "https://github.com/telegramdesktop/tdesktop/#build-instructions" exit fi Error () { cd $FullExecPath echo "$1" exit 1 } if [ ! -f "$FullScriptPath/target" ]; then Error "Build target not found!" fi while IFS='' read -r line || [[ -n "$line" ]]; do BuildTarget="$line" done < "$FullScriptPath/target" while IFS='' read -r line || [[ -n "$line" ]]; do set $line eval $1="$2" done < "$FullScriptPath/version" VersionForPacker="$AppVersion" if [ "$BetaVersion" != "0" ]; then AppVersion="$BetaVersion" AppVersionStrFull="${AppVersionStr}_${BetaVersion}" AlphaBetaParam="-beta $BetaVersion" BetaKeyFile="tbeta_${AppVersion}_key" elif [ "$AlphaChannel" == "0" ]; then AppVersionStrFull="$AppVersionStr" AlphaBetaParam='' else AppVersionStrFull="$AppVersionStr.alpha" AlphaBetaParam='-alpha' fi echo "" HomePath="$FullScriptPath/.." if [ "$BuildTarget" == "linux" ]; then echo "Building version $AppVersionStrFull for Linux 64bit.." UpdateFile="tlinuxupd$AppVersion" SetupFile="tsetup.$AppVersionStrFull.tar.xz" ReleasePath="$HomePath/../out/Release" BinaryName="Telegram" elif [ "$BuildTarget" == "linux32" ]; then echo "Building version $AppVersionStrFull for Linux 32bit.." UpdateFile="tlinux32upd$AppVersion" SetupFile="tsetup32.$AppVersionStrFull.tar.xz" ReleasePath="$HomePath/../out/Release" BinaryName="Telegram" elif [ "$BuildTarget" == "mac" ]; then echo "Building version $AppVersionStrFull for OS X 10.8+.." UpdateFile="tmacupd$AppVersion" SetupFile="tsetup.$AppVersionStrFull.dmg" ReleasePath="$HomePath/../out/Release" BinaryName="Telegram" elif [ "$BuildTarget" == "mac32" ]; then echo "Building version $AppVersionStrFull for OS X 10.6 and 10.7.." UpdateFile="tmac32upd$AppVersion" SetupFile="tsetup32.$AppVersionStrFull.dmg" ReleasePath="$HomePath/../out/Release" BinaryName="Telegram" elif [ "$BuildTarget" == "macstore" ]; then if [ "$BetaVersion" != "0" ]; then Error "Can't build macstore beta version!" fi echo "Building version $AppVersionStrFull for Mac App Store.." ReleasePath="$HomePath/../out/Release" BinaryName="Telegram Desktop" else Error "Invalid target!" fi #if [ "$BuildTarget" == "linux" ] || [ "$BuildTarget" == "linux32" ] || [ "$BuildTarget" == "mac" ] || [ "$BuildTarget" == "mac32" ] || [ "$BuildTarget" == "macstore" ]; then if [ "$BetaVersion" != "0" ]; then if [ -f "$ReleasePath/$BetaKeyFile" ]; then Error "Beta version key file for version $AppVersion already exists!" fi if [ -d "$ReleasePath/deploy/$AppVersionStrMajor/$AppVersionStrFull" ]; then Error "Deploy folder for version $AppVersionStrFull already exists!" fi else if [ -d "$ReleasePath/deploy/$AppVersionStrMajor/$AppVersionStr.alpha" ]; then Error "Deploy folder for version $AppVersionStr.alpha already exists!" fi if [ -d "$ReleasePath/deploy/$AppVersionStrMajor/$AppVersionStr.dev" ]; then Error "Deploy folder for version $AppVersionStr.dev already exists!" fi if [ -d "$ReleasePath/deploy/$AppVersionStrMajor/$AppVersionStr" ]; then Error "Deploy folder for version $AppVersionStr already exists!" fi if [ -f "$ReleasePath/$UpdateFile" ]; then Error "Update file for version $AppVersion already exists!" fi fi DeployPath="$ReleasePath/deploy/$AppVersionStrMajor/$AppVersionStrFull" #fi if [ "$BuildTarget" == "linux" ] || [ "$BuildTarget" == "linux32" ]; then DropboxSymbolsPath="/media/psf/Dropbox/Telegram/symbols" if [ ! -d "$DropboxSymbolsPath" ]; then Error "Dropbox path not found!" fi BackupPath="/media/psf/backup/$AppVersionStrMajor/$AppVersionStrFull/t$BuildTarget" if [ ! -d "/media/psf/backup" ]; then Error "Backup folder not found!" fi gyp/refresh.sh cd $ReleasePath make -j4 echo "$BinaryName build complete!" if [ ! -f "$ReleasePath/$BinaryName" ]; then Error "$BinaryName not found!" fi BadCount=`objdump -T $ReleasePath/$BinaryName | grep GLIBC_2\.1[6-9] | wc -l` if [ "$BadCount" != "0" ]; then Error "Bad GLIBC usages found: $BadCount" fi BadCount=`objdump -T $ReleasePath/$BinaryName | grep GLIBC_2\.2[0-9] | wc -l` if [ "$BadCount" != "0" ]; then Error "Bad GLIBC usages found: $BadCount" fi BadCount=`objdump -T $ReleasePath/$BinaryName | grep GCC_4\.[3-9] | wc -l` if [ "$BadCount" != "0" ]; then Error "Bad GCC usages found: $BadCount" fi BadCount=`objdump -T $ReleasePath/$BinaryName | grep GCC_[5-9]\. | wc -l` if [ "$BadCount" != "0" ]; then Error "Bad GCC usages found: $BadCount" fi if [ ! -f "$ReleasePath/Updater" ]; then Error "Updater not found!" fi BadCount=`objdump -T $ReleasePath/Updater | grep GLIBC_2\.1[6-9] | wc -l` if [ "$BadCount" != "0" ]; then Error "Bad GLIBC usages found: $BadCount" fi BadCount=`objdump -T $ReleasePath/Updater | grep GLIBC_2\.2[0-9] | wc -l` if [ "$BadCount" != "0" ]; then Error "Bad GLIBC usages found: $BadCount" fi BadCount=`objdump -T $ReleasePath/Updater | grep GCC_4\.[3-9] | wc -l` if [ "$BadCount" != "0" ]; then Error "Bad GCC usages found: $BadCount" fi BadCount=`objdump -T $ReleasePath/Updater | grep GCC_[5-9]\. | wc -l` if [ "$BadCount" != "0" ]; then Error "Bad GCC usages found: $BadCount" fi echo "Dumping debug symbols.." "$HomePath/../../Libraries/breakpad/out/Default/dump_syms" "$ReleasePath/$BinaryName" > "$ReleasePath/$BinaryName.sym" echo "Done!" echo "Stripping the executable.." strip -s "$ReleasePath/$BinaryName" echo "Done!" echo "Preparing version $AppVersionStrFull, executing Packer.." cd "$ReleasePath" "./Packer" -path "$BinaryName" -path Updater -version $VersionForPacker $AlphaBetaParam echo "Packer done!" if [ "$BetaVersion" != "0" ]; then if [ ! -f "$ReleasePath/$BetaKeyFile" ]; then Error "Beta version key file not found!" fi while IFS='' read -r line || [[ -n "$line" ]]; do BetaSignature="$line" done < "$ReleasePath/$BetaKeyFile" UpdateFile="${UpdateFile}_${BetaSignature}" SetupFile="tbeta${BetaVersion}_${BetaSignature}.tar.xz" fi SymbolsHash=`head -n 1 "$ReleasePath/$BinaryName.sym" | awk -F " " 'END {print $4}'` echo "Copying $BinaryName.sym to $DropboxSymbolsPath/$BinaryName/$SymbolsHash" mkdir -p "$DropboxSymbolsPath/$BinaryName/$SymbolsHash" cp "$ReleasePath/$BinaryName.sym" "$DropboxSymbolsPath/$BinaryName/$SymbolsHash/" echo "Done!" if [ ! -d "$ReleasePath/deploy" ]; then mkdir "$ReleasePath/deploy" fi if [ ! -d "$ReleasePath/deploy/$AppVersionStrMajor" ]; then mkdir "$ReleasePath/deploy/$AppVersionStrMajor" fi echo "Copying $BinaryName, Updater and $UpdateFile to deploy/$AppVersionStrMajor/$AppVersionStrFull.."; mkdir "$DeployPath" mkdir "$DeployPath/$BinaryName" mv "$ReleasePath/$BinaryName" "$DeployPath/$BinaryName/" mv "$ReleasePath/Updater" "$DeployPath/$BinaryName/" mv "$ReleasePath/$UpdateFile" "$DeployPath/" if [ "$BetaVersion" != "0" ]; then mv "$ReleasePath/$BetaKeyFile" "$DeployPath/" fi cd "$DeployPath" tar -cJvf "$SetupFile" "$BinaryName/" mkdir -p $BackupPath cp "$SetupFile" "$BackupPath/" cp "$UpdateFile" "$BackupPath/" if [ "$BetaVersion" != "0" ]; then cp -v "$BetaKeyFile" "$BackupPath/" fi fi if [ "$BuildTarget" == "mac" ] || [ "$BuildTarget" == "mac32" ] || [ "$BuildTarget" == "macstore" ]; then DropboxSymbolsPath="$HOME/Dropbox/Telegram/symbols" if [ ! -d "$DropboxSymbolsPath" ]; then Error "Dropbox path not found!" fi BackupPath="$HOME/Telegram/backup/$AppVersionStrMajor/$AppVersionStrFull" if [ ! -d "$HOME/Telegram/backup" ]; then Error "Backup path not found!" fi gyp/refresh.sh xcodebuild -project Telegram.xcodeproj -alltargets -configuration Release build if [ ! -d "$ReleasePath/$BinaryName.app" ]; then Error "$BinaryName.app not found!" fi if [ ! -d "$ReleasePath/$BinaryName.app.dSYM" ]; then Error "$BinaryName.app.dSYM not found!" fi if [ "$BuildTarget" == "mac" ] || [ "$BuildTarget" == "mac32" ]; then if [ ! -f "$ReleasePath/$BinaryName.app/Contents/Frameworks/Updater" ]; then Error "Updater not found!" fi if [ ! -f "$ReleasePath/$BinaryName.app/Contents/Helpers/crashpad_handler" ]; then Error "crashpad_handler not found!" fi fi if [ "$BuildTarget" == "macstore" ]; then if [ ! -d "$ReleasePath/$BinaryName.app/Contents/Frameworks/Breakpad.framework" ]; then Error "Breakpad.framework not found!" fi fi echo "Dumping debug symbols.." "$HomePath/../../Libraries/breakpad/src/tools/mac/dump_syms/build/Release/dump_syms" "$ReleasePath/$BinaryName.app.dSYM" > "$ReleasePath/$BinaryName.sym" 2>/dev/null echo "Done!" echo "Stripping the executable.." strip "$ReleasePath/$BinaryName.app/Contents/MacOS/$BinaryName" echo "Done!" echo "Signing the application.." if [ "$BuildTarget" == "mac" ] || [ "$BuildTarget" == "mac32" ]; then codesign --force --deep --sign "Developer ID Application: John Preston" "$ReleasePath/$BinaryName.app" elif [ "$BuildTarget" == "macstore" ]; then codesign --force --deep --sign "3rd Party Mac Developer Application: TELEGRAM MESSENGER LLP (6N38VWS5BX)" "$ReleasePath/$BinaryName.app" --entitlements "$HomePath/Telegram/Telegram Desktop.entitlements" echo "Making an installer.." productbuild --sign "3rd Party Mac Developer Installer: TELEGRAM MESSENGER LLP (6N38VWS5BX)" --component "$ReleasePath/$BinaryName.app" /Applications "$ReleasePath/$BinaryName.pkg" fi echo "Done!" AppUUID=`dwarfdump -u "$ReleasePath/$BinaryName.app/Contents/MacOS/$BinaryName" | awk -F " " '{print $2}'` DsymUUID=`dwarfdump -u "$ReleasePath/$BinaryName.app.dSYM" | awk -F " " '{print $2}'` if [ "$AppUUID" != "$DsymUUID" ]; then Error "UUID of binary '$AppUUID' and dSYM '$DsymUUID' differ!" fi if [ ! -f "$ReleasePath/$BinaryName.app/Contents/Resources/Icon.icns" ]; then Error "Icon.icns not found in Resources!" fi if [ ! -f "$ReleasePath/$BinaryName.app/Contents/MacOS/$BinaryName" ]; then Error "$BinaryName not found in MacOS!" fi if [ ! -d "$ReleasePath/$BinaryName.app/Contents/_CodeSignature" ]; then Error "$BinaryName signature not found!" fi if [ "$BuildTarget" == "mac" ] || [ "$BuildTarget" == "mac32" ]; then if [ ! -f "$ReleasePath/$BinaryName.app/Contents/Frameworks/Updater" ]; then Error "Updater not found in Frameworks!" fi elif [ "$BuildTarget" == "macstore" ]; then if [ ! -f "$ReleasePath/$BinaryName.pkg" ]; then Error "$BinaryName.pkg not found!" fi fi SymbolsHash=`head -n 1 "$ReleasePath/$BinaryName.sym" | awk -F " " 'END {print $4}'` echo "Copying $BinaryName.sym to $DropboxSymbolsPath/$BinaryName/$SymbolsHash" mkdir -p "$DropboxSymbolsPath/$BinaryName/$SymbolsHash" cp "$ReleasePath/$BinaryName.sym" "$DropboxSymbolsPath/$BinaryName/$SymbolsHash/" echo "Done!" if [ "$BuildTarget" == "mac" ] || [ "$BuildTarget" == "mac32" ]; then if [ "$BetaVersion" == "0" ]; then cd "$ReleasePath" cp -f tsetup_template.dmg tsetup.temp.dmg TempDiskPath=`hdiutil attach -nobrowse -noautoopenrw -readwrite tsetup.temp.dmg | awk -F "\t" 'END {print $3}'` cp -R "./$BinaryName.app" "$TempDiskPath/" bless --folder "$TempDiskPath/" --openfolder "$TempDiskPath/" hdiutil detach "$TempDiskPath" hdiutil convert tsetup.temp.dmg -format UDZO -imagekey zlib-level=9 -ov -o "$SetupFile" rm tsetup.temp.dmg fi cd "$ReleasePath" "./Packer" -path "$BinaryName.app" -target "$BuildTarget" -version $VersionForPacker $AlphaBetaParam echo "Packer done!" if [ "$BetaVersion" != "0" ]; then if [ ! -f "$ReleasePath/$BetaKeyFile" ]; then Error "Beta version key file not found!" fi while IFS='' read -r line || [[ -n "$line" ]]; do BetaSignature="$line" done < "$ReleasePath/$BetaKeyFile" UpdateFile="${UpdateFile}_${BetaSignature}" SetupFile="tbeta${BetaVersion}_${BetaSignature}.zip" fi fi if [ ! -d "$ReleasePath/deploy" ]; then mkdir "$ReleasePath/deploy" fi if [ ! -d "$ReleasePath/deploy/$AppVersionStrMajor" ]; then mkdir "$ReleasePath/deploy/$AppVersionStrMajor" fi if [ "$BuildTarget" == "mac" ] || [ "$BuildTarget" == "mac32" ]; then echo "Copying $BinaryName.app and $UpdateFile to deploy/$AppVersionStrMajor/$AppVersionStr.."; mkdir "$DeployPath" mkdir "$DeployPath/$BinaryName" cp -r "$ReleasePath/$BinaryName.app" "$DeployPath/$BinaryName/" if [ "$BetaVersion" != "0" ]; then cd "$DeployPath" zip -r "$SetupFile" "$BinaryName" mv "$SetupFile" "$ReleasePath/" mv "$ReleasePath/$BetaKeyFile" "$DeployPath/" fi mv "$ReleasePath/$BinaryName.app.dSYM" "$DeployPath/" rm "$ReleasePath/$BinaryName.app/Contents/MacOS/$BinaryName" rm "$ReleasePath/$BinaryName.app/Contents/Frameworks/Updater" rm "$ReleasePath/$BinaryName.app/Contents/Info.plist" rm -rf "$ReleasePath/$BinaryName.app/Contents/_CodeSignature" mv "$ReleasePath/$UpdateFile" "$DeployPath/" mv "$ReleasePath/$SetupFile" "$DeployPath/" if [ "$BuildTarget" == "mac" ]; then mkdir -p "$BackupPath/tmac" cp "$DeployPath/$UpdateFile" "$BackupPath/tmac/" cp "$DeployPath/$SetupFile" "$BackupPath/tmac/" if [ "$BetaVersion" != "0" ]; then cp -v "$DeployPath/$BetaKeyFile" "$BackupPath/tmac/" fi fi if [ "$BuildTarget" == "mac32" ]; then mkdir -p "$BackupPath/tmac32" cp "$DeployPath/$UpdateFile" "$BackupPath/tmac32/" cp "$DeployPath/$SetupFile" "$BackupPath/tmac32/" if [ "$BetaVersion" != "0" ]; then cp -v "$DeployPath/$BetaKeyFile" "$BackupPath/tmac32/" fi fi elif [ "$BuildTarget" == "macstore" ]; then echo "Copying $BinaryName.app to deploy/$AppVersionStrMajor/$AppVersionStr.."; mkdir "$DeployPath" cp -r "$ReleasePath/$BinaryName.app" "$DeployPath/" mv "$ReleasePath/$BinaryName.pkg" "$DeployPath/" mv "$ReleasePath/$BinaryName.app.dSYM" "$DeployPath/" rm "$ReleasePath/$BinaryName.app/Contents/MacOS/$BinaryName" rm "$ReleasePath/$BinaryName.app/Contents/Info.plist" rm -rf "$ReleasePath/$BinaryName.app/Contents/_CodeSignature" fi fi echo "Version $AppVersionStrFull is ready!"; echo -en "\007"; sleep 1; echo -en "\007"; sleep 1; echo -en "\007"; ���������������������tdesktop-1.2.17/Telegram/build/deploy.sh������������������������������������������������������������0000775�0000000�0000000�00000015454�13262451251�0020143�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������set -e FullExecPath=$PWD pushd `dirname $0` > /dev/null FullScriptPath=`pwd` popd > /dev/null if [ ! -d "$FullScriptPath/../../../TelegramPrivate" ]; then echo "" echo "This script is for building the production version of Telegram Desktop." echo "" echo "For building custom versions please visit the build instructions page at:" echo "https://github.com/telegramdesktop/tdesktop/#build-instructions" exit fi Error () { cd $FullExecPath echo "$1" exit 1 } DeployTarget="$1" if [ ! -f "$FullScriptPath/target" ]; then Error "Build target not found!" fi while IFS='' read -r line || [[ -n "$line" ]]; do BuildTarget="$line" done < "$FullScriptPath/target" while IFS='' read -r line || [[ -n "$line" ]]; do set $line eval $1="$2" done < "$FullScriptPath/version" if [ "$BetaVersion" != "0" ]; then AppVersion="$BetaVersion" AppVersionStrFull="${AppVersionStr}_${BetaVersion}" BetaKeyFile="tbeta_${AppVersion}_key" elif [ "$AlphaChannel" == "0" ]; then AppVersionStrFull="$AppVersionStr" else AppVersionStrFull="$AppVersionStr.alpha" fi echo "" HomePath="$FullScriptPath/.." DeployMac="0" DeployMac32="0" DeployWin="0" DeployLinux="0" DeployLinux32="0" if [ "$DeployTarget" == "mac" ]; then DeployMac="1" echo "Deploying version $AppVersionStrFull for OS X 10.8+.." elif [ "$DeployTarget" == "mac32" ]; then DeployMac32="1" echo "Deploying version $AppVersionStrFull for OS X 10.6 and 10.7.." elif [ "$DeployTarget" == "win" ]; then DeployWin="1" echo "Deploying version $AppVersionStrFull for Windows.." elif [ "$DeployTarget" == "linux" ]; then DeployLinux="1" echo "Deploying version $AppVersionStrFull for Linux 64 bit.." elif [ "$DeployTarget" == "linux32" ]; then DeployLinux32="1" echo "Deploying version $AppVersionStrFull for Linux 32 bit.." else DeployMac="1" DeployWin="1" DeployLinux="1" if [ "$BetaVersion" == "0" ]; then DeployMac32="1" DeployLinux32="1" echo "Deploying five versions of $AppVersionStrFull: for Windows, OS X 10.6 and 10.7, OS X 10.8+, Linux 64 bit and Linux 32 bit.." else echo "Deploying three versions of $AppVersionStrFull: for Windows, OS X 10.8+ and Linux 64 bit.." fi fi if [ "$BuildTarget" == "mac" ]; then BackupPath="$HOME/Telegram/backup" elif [ "$BuildTarget" == "linux" ]; then BackupPath="/media/psf/Home/Telegram/backup" else Error "Can't deploy here" fi MacDeployPath="$BackupPath/$AppVersionStrMajor/$AppVersionStrFull/tmac" MacUpdateFile="tmacupd$AppVersion" MacSetupFile="tsetup.$AppVersionStrFull.dmg" MacRemoteFolder="tmac" Mac32DeployPath="$BackupPath/$AppVersionStrMajor/$AppVersionStrFull/tmac32" Mac32UpdateFile="tmac32upd$AppVersion" Mac32SetupFile="tsetup32.$AppVersionStrFull.dmg" Mac32RemoteFolder="tmac32" WinDeployPath="$BackupPath/$AppVersionStrMajor/$AppVersionStrFull/tsetup" WinUpdateFile="tupdate$AppVersion" WinSetupFile="tsetup.$AppVersionStrFull.exe" WinPortableFile="tportable.$AppVersionStrFull.zip" WinRemoteFolder="tsetup" LinuxDeployPath="$BackupPath/$AppVersionStrMajor/$AppVersionStrFull/tlinux" LinuxUpdateFile="tlinuxupd$AppVersion" LinuxSetupFile="tsetup.$AppVersionStrFull.tar.xz" LinuxRemoteFolder="tlinux" Linux32DeployPath="$BackupPath/$AppVersionStrMajor/$AppVersionStrFull/tlinux32" Linux32UpdateFile="tlinux32upd$AppVersion" Linux32SetupFile="tsetup32.$AppVersionStrFull.tar.xz" Linux32RemoteFolder="tlinux32" DeployPath="$BackupPath/$AppVersionStrMajor/$AppVersionStrFull" if [ "$BetaVersion" != "0" ]; then if [ "$DeployTarget" == "win" ]; then BetaFilePath="$WinDeployPath/$BetaKeyFile" elif [ "$DeployTarget" == "mac32" ]; then BetaFilePath="$Mac32DeployPath/$BetaKeyFile" elif [ "$DeployTarget" == "linux" ]; then BetaFilePath="$LinuxDeployPath/$BetaKeyFile" elif [ "$DeployTarget" == "linux32" ]; then BetaFilePath="$Linux32DeployPath/$BetaKeyFile" else BetaFilePath="$MacDeployPath/$BetaKeyFile" fi if [ ! -f "$BetaFilePath" ]; then Error "Beta key file for $AppVersionStrFull not found." fi while IFS='' read -r line || [[ -n "$line" ]]; do BetaSignature="$line" done < "$BetaFilePath" MacUpdateFile="${MacUpdateFile}_${BetaSignature}" MacSetupFile="tbeta${BetaVersion}_${BetaSignature}.zip" Mac32UpdateFile="${Mac32UpdateFile}_${BetaSignature}" Mac32SetupFile="tbeta${BetaVersion}_${BetaSignature}.zip" WinUpdateFile="${WinUpdateFile}_${BetaSignature}" WinPortableFile="tbeta${BetaVersion}_${BetaSignature}.zip" LinuxUpdateFile="${LinuxUpdateFile}_${BetaSignature}" LinuxSetupFile="tbeta${BetaVersion}_${BetaSignature}.tar.xz" Linux32UpdateFile="${Linux32UpdateFile}_${BetaSignature}" Linux32SetupFile="tbeta${BetaVersion}_${BetaSignature}.tar.xz" fi if [ "$DeployMac" == "1" ]; then if [ ! -f "$MacDeployPath/$MacUpdateFile" ]; then Error "$MacDeployPath/$MacUpdateFile not found!"; fi if [ ! -f "$MacDeployPath/$MacSetupFile" ]; then Error "$MacDeployPath/$MacSetupFile not found!" fi fi if [ "$DeployMac32" == "1" ]; then if [ ! -f "$Mac32DeployPath/$Mac32UpdateFile" ]; then Error "$Mac32UpdateFile not found!" fi if [ ! -f "$Mac32DeployPath/$Mac32SetupFile" ]; then Error "$Mac32SetupFile not found!" fi fi if [ "$DeployWin" == "1" ]; then if [ ! -f "$WinDeployPath/$WinUpdateFile" ]; then Error "$WinUpdateFile not found!" fi if [ "$BetaVersion" == "0" ]; then if [ ! -f "$WinDeployPath/$WinSetupFile" ]; then Error "$WinSetupFile not found!" fi fi if [ ! -f "$WinDeployPath/$WinPortableFile" ]; then Error "$WinPortableFile not found!" fi fi if [ "$DeployLinux" == "1" ]; then if [ ! -f "$LinuxDeployPath/$LinuxUpdateFile" ]; then Error "$LinuxDeployPath/$LinuxUpdateFile not found!" fi if [ ! -f "$LinuxDeployPath/$LinuxSetupFile" ]; then Error "$LinuxDeployPath/$LinuxSetupFile not found!" fi fi if [ "$DeployLinux32" == "1" ]; then if [ ! -f "$Linux32DeployPath/$Linux32UpdateFile" ]; then Error "$Linux32DeployPath/$Linux32UpdateFile not found!" fi if [ ! -f "$Linux32DeployPath/$Linux32SetupFile" ]; then Error "$Linux32DeployPath/$Linux32SetupFile not found!" fi fi $FullScriptPath/../../../TelegramPrivate/mount.sh declare -a Files if [ "$DeployMac" == "1" ]; then Files+=("tmac/$MacUpdateFile" "tmac/$MacSetupFile") fi if [ "$DeployMac32" == "1" ]; then Files+=("tmac32/$Mac32UpdateFile" "tmac32/$Mac32SetupFile") fi if [ "$DeployWin" == "1" ]; then Files+=("tsetup/$WinUpdateFile" "tsetup/$WinPortableFile") if [ "$BetaVersion" == "0" ]; then Files+=("tsetup/$WinSetupFile") fi fi if [ "$DeployLinux" == "1" ]; then Files+=("tlinux/$LinuxUpdateFile" "tlinux/$LinuxSetupFile") fi if [ "$DeployLinux32" == "1" ]; then Files+=("tlinux32/$Linux32UpdateFile" "tlinux32/$Linux32SetupFile") fi cd $DeployPath rsync -avR --progress ${Files[@]} "$FullScriptPath/../../../TelegramPrivate/remote/files" echo "Version $AppVersionStrFull was deployed!" cd $FullExecPath ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/build/release.py�����������������������������������������������������������0000664�0000000�0000000�00000016426�13262451251�0020302�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������import os, sys, requests, pprint, re, json from uritemplate import URITemplate, expand from subprocess import call from os.path import expanduser changelog_file = '../../changelog.txt' token_file = '../../../TelegramPrivate/github-releases-token.txt' version = '' commit = '' for arg in sys.argv: if re.match(r'\d+\.\d+', arg): version = arg elif re.match(r'^[a-f0-9]{40}$', arg): commit = arg # thanks http://stackoverflow.com/questions/13909900/progress-of-python-requests-post class upload_in_chunks(object): def __init__(self, filename, chunksize=1 << 13): self.filename = filename self.chunksize = chunksize self.totalsize = os.path.getsize(filename) self.readsofar = 0 def __iter__(self): with open(self.filename, 'rb') as file: while True: data = file.read(self.chunksize) if not data: sys.stderr.write("\n") break self.readsofar += len(data) percent = self.readsofar * 1e2 / self.totalsize sys.stderr.write("\r{percent:3.0f}%".format(percent=percent)) yield data def __len__(self): return self.totalsize class IterableToFileAdapter(object): def __init__(self, iterable): self.iterator = iter(iterable) self.length = len(iterable) def read(self, size=-1): # TBD: add buffer for `len(data) > size` case return next(self.iterator, b'') def __len__(self): return self.length def checkResponseCode(result, right_code): if (result.status_code != right_code): print('Wrong result code: ' + str(result.status_code) + ', should be ' + str(right_code)) sys.exit(1) pp = pprint.PrettyPrinter(indent=2) url = 'https://api.github.com/' version_parts = version.split('.') stable = 1 alpha = 0 dev = 0 if len(version_parts) < 2: print('Error: expected at least major version ' + version) sys.exit(1) if len(version_parts) > 4: print('Error: bad version passed ' + version) sys.exit(1) version_major = version_parts[0] + '.' + version_parts[1] if len(version_parts) == 2: version = version_major + '.0' version_full = version else: version = version_major + '.' + version_parts[2] version_full = version if len(version_parts) == 4: if version_parts[3] == 'dev': dev = 1 stable = 0 version_full = version + '.dev' elif version_parts[3] == 'alpha': alpha = 1 stable = 0 version_full = version + '.alpha' else: print('Error: unexpected version part ' + version_parts[3]) sys.exit(1) access_token = '' if os.path.isfile(token_file): with open(token_file) as f: for line in f: access_token = line.replace('\n', '') if access_token == '': print('Access token not found!') sys.exit(1) print('Version: ' + version_full); local_folder = expanduser("~") + '/Telegram/backup/' + version_major + '/' + version_full if stable == 1: if os.path.isdir(local_folder + '.dev'): dev = 1 stable = 0 version_full = version + '.dev' local_folder = local_folder + '.dev' elif os.path.isdir(local_folder + '.alpha'): alpha = 1 stable = 0 version_full = version + '.alpha' local_folder = local_folder + '.alpha' if not os.path.isdir(local_folder): print('Storage path not found: ' + local_folder) sys.exit(1) local_folder = local_folder + '/' files = [] files.append({ 'local': 'tsetup.' + version_full + '.exe', 'remote': 'tsetup.' + version_full + '.exe', 'backup_folder': 'tsetup', 'mime': 'application/octet-stream', 'label': 'Windows: Installer', }) files.append({ 'local': 'tportable.' + version_full + '.zip', 'remote': 'tportable.' + version_full + '.zip', 'backup_folder': 'tsetup', 'mime': 'application/zip', 'label': 'Windows: Portable', }) files.append({ 'local': 'tsetup.' + version_full + '.dmg', 'remote': 'tsetup.' + version_full + '.dmg', 'backup_folder': 'tmac', 'mime': 'application/octet-stream', 'label': 'macOS and OS X 10.8+: Installer', }) files.append({ 'local': 'tsetup32.' + version_full + '.dmg', 'remote': 'tsetup32.' + version_full + '.dmg', 'backup_folder': 'tmac32', 'mime': 'application/octet-stream', 'label': 'OS X 10.6 and 10.7: Installer', }) files.append({ 'local': 'tsetup.' + version_full + '.tar.xz', 'remote': 'tsetup.' + version_full + '.tar.xz', 'backup_folder': 'tlinux', 'mime': 'application/octet-stream', 'label': 'Linux 64 bit: Binary', }) files.append({ 'local': 'tsetup32.' + version_full + '.tar.xz', 'remote': 'tsetup32.' + version_full + '.tar.xz', 'backup_folder': 'tlinux32', 'mime': 'application/octet-stream', 'label': 'Linux 32 bit: Binary', }) r = requests.get(url + 'repos/telegramdesktop/tdesktop/releases/tags/v' + version) if r.status_code == 404: print('Release not found, creating.') if commit == '': print('Error: specify the commit.') sys.exit(1) if not os.path.isfile(changelog_file): print('Error: Changelog file not found.') sys.exit(1) changelog = '' started = 0 with open(changelog_file) as f: for line in f: if started == 1: if re.match(r'^\d+\.\d+', line): break; if re.match(r'^\s+$', line): continue changelog += line else: if re.match(r'^\d+\.\d+', line): if line[0:len(version) + 1] == version + ' ': started = 1 elif line[0:len(version_major) + 1] == version_major + ' ': if version_major + '.0' == version: started = 1 if started != 1: print('Error: Changelog not found.') sys.exit(1) changelog = changelog.strip() print('Changelog: '); print(changelog); r = requests.post(url + 'repos/telegramdesktop/tdesktop/releases', headers={'Authorization': 'token ' + access_token}, data=json.dumps({ 'tag_name': 'v' + version, 'target_commitish': commit, 'name': 'v ' + version, 'body': changelog, 'prerelease': (dev == 1 or alpha == 1), })) checkResponseCode(r, 201) r = requests.get(url + 'repos/telegramdesktop/tdesktop/releases/tags/v' + version) checkResponseCode(r, 200); release_data = r.json() #pp.pprint(release_data) release_id = release_data['id'] print('Release ID: ' + str(release_id)) r = requests.get(url + 'repos/telegramdesktop/tdesktop/releases/' + str(release_id) + '/assets'); checkResponseCode(r, 200); assets = release_data['assets'] for asset in assets: name = asset['name'] found = 0 for file in files: if file['remote'] == name: print('Already uploaded: ' + name) file['already'] = 1 found = 1 break if found == 0: print('Warning: strange asset: ' + name) for file in files: if 'already' in file: continue file_path = local_folder + file['backup_folder'] + '/' + file['local'] if not os.path.isfile(file_path): print('Warning: file not found ' + file['local']) continue upload_url = expand(release_data['upload_url'], {'name': file['remote'], 'label': file['label']}) + '&access_token=' + access_token; content = upload_in_chunks(file_path, 10) print('Uploading: ' + file['remote'] + ' (' + str(round(len(content) / 10000) / 100.) + ' MB)') r = requests.post(upload_url, headers={"Content-Type": file['mime']}, data=IterableToFileAdapter(content)) checkResponseCode(r, 201) print('Success! Removing.') return_code = call(["rm", file_path]) if return_code != 0: print('Bad rm code: ' + str(return_code)) sys.exit(1) sys.exit() ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/build/release.sh�����������������������������������������������������������0000775�0000000�0000000�00000001763�13262451251�0020265�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������set -e FullExecPath=$PWD pushd `dirname $0` > /dev/null FullScriptPath=`pwd` popd > /dev/null Param1="$1" Param2="$2" Param3="$3" Param4="$4" if [ ! -d "$FullScriptPath/../../../TelegramPrivate" ]; then echo "" echo "This script is for building the production version of Telegram Desktop." echo "" echo "For building custom versions please visit the build instructions page at:" echo "https://github.com/telegramdesktop/tdesktop/#build-instructions" exit fi Error () { cd $FullExecPath echo "$1" exit 1 } while IFS='' read -r line || [[ -n "$line" ]]; do set $line eval $1="$2" done < "$FullScriptPath/version" VersionForPacker="$AppVersion" if [ "$BetaVersion" != "0" ]; then Error "No releases for closed beta versions" elif [ "$AlphaChannel" == "0" ]; then AppVersionStrFull="$AppVersionStr" AlphaBetaParam='' else AppVersionStrFull="$AppVersionStr.alpha" AlphaBetaParam='-alpha' fi cd "$FullScriptPath" python3 release.py $AppVersionStr $Param1 $Param2 $Param3 $Param4 �������������tdesktop-1.2.17/Telegram/build/replace.vbs����������������������������������������������������������0000664�0000000�0000000�00000001360�13262451251�0020426�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Dim action, pat, patparts, rxp, inp, matchCount action = WScript.Arguments(0) pat = WScript.Arguments(1) pat = Replace(pat, """, chr(34)) pat = Replace(pat, "&hat;", "^") pat = Replace(pat, "&", "&") Set rxp = new RegExp rxp.Global = True rxp.Multiline = False If action = "Replace" Then patparts = Split(pat, "/") rxp.Pattern = patparts(0) Else rxp.Pattern = pat End If matchCount = 0 Do While Not WScript.StdIn.AtEndOfStream inp = WScript.StdIn.ReadLine() If rxp.Test(inp) Then matchCount = matchCount + 1 End If If action = "Replace" Then WScript.Echo rxp.Replace(inp, patparts(1)) End If Loop If action = "Replace" Then If matchCount = 0 Then WScript.Quit(2) End If Else WScript.Echo matchCount End If ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/build/set_version.bat������������������������������������������������������0000664�0000000�0000000�00000015231�13262451251�0021331�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������@echo OFF setlocal enabledelayedexpansion set "FullScriptPath=%~dp0" set "InputVersion=%1" for /F "tokens=1,2,3,4 delims=. " %%a in ("%InputVersion%") do ( set "VersionMajor=%%a" set "VersionMinor=%%b" set "VersionPatch=%%c" if "%%d" == "" ( set "VersionBeta=0" set "VersionAlpha=0" ) else if "%%d" == "alpha" ( set "VersionBeta=0" set "VersionAlpha=1" ) else ( set "VersionBeta=%%d" set "VersionAlpha=0" ) ) set /a "VersionMajorCleared=%VersionMajor% %% 1000" if "%VersionMajorCleared%" neq "%VersionMajor%" ( echo Bad major version! exit /b 1 ) set /a "VersionMinorCleared=%VersionMinor% %% 1000" if "%VersionMinorCleared%" neq "%VersionMinor%" ( echo Bad minor version! exit /b 1 ) set /a "VersionPatchCleared=%VersionPatch% %% 1000" if "%VersionPatchCleared%" neq "%VersionPatch%" ( echo Bad patch version! exit /b 1 ) if "%VersionAlpha%" neq "0" ( if "%VersionAlpha%" neq "1" ( echo Bad alpha version! exit /b 1 ) set "VersionAlphaBool=true" ) else ( set "VersionAlphaBool=false" ) set /a "VersionFull=%VersionMajor% * 1000000 + %VersionMinor% * 1000 + %VersionPatch%" if "%VersionBeta%" neq "0" ( set /a "VersionBetaCleared=%VersionBeta% %% 1000" if "!VersionBetaCleared!" neq "%VersionBeta%" ( echo Bad beta version! exit /b 1 ) set /a "VersionBetaMul=1000 + %VersionBeta%" set "VersionFullBeta=%VersionFull%!VersionBetaMul:~1!" ) else ( set "VersionFullBeta=0" ) set "VersionStr=%VersionMajor%.%VersionMinor%.%VersionPatch%" if "%VersionPatch%" neq "0" ( set "VersionStrSmall=%VersionStr%" ) else ( set "VersionStrSmall=%VersionMajor%.%VersionMinor%" ) if "%VersionAlpha%" neq "0" ( echo Setting version: %VersionStr% alpha ) else if "%VersionBeta%" neq "0" ( echo Setting version: %VersionStr%.%VersionBeta% closed beta ) else ( echo Setting version: %VersionStr% stable ) echo Checking changelog... set "ChangelogFile=%FullScriptPath%..\..\changelog.txt" call :count "Argument=^%VersionStr% " "Filename=%ChangelogFile%" if "%FoundCount%" equ "0" ( call :count "Argument=^%VersionStrSmall% " "Filename=%ChangelogFile%" if "!FoundCount!" equ "0" ( echo Changelog entry not found! exit /b 1 ) else if "!FoundCount!" neq "1" ( echo Wrong changelog entries count found: %FoundCount% exit /b 1 ) ) else if "%FoundCount%" neq "1" ( echo Wrong changelog entries count found: %FoundCount% exit /b 1 ) echo Patching build/version... set "VersionFilePath=%FullScriptPath%version" call :repl "Argument=(AppVersion) (\s*)\d+/$1$2 %VersionFull%" "Filename=%VersionFilePath%" || goto :error call :repl "Argument=(AppVersionStrMajor) (\s*)[\d\.]+/$1$2 %VersionMajor%.%VersionMinor%" "Filename=%VersionFilePath%" || goto :error call :repl "Argument=(AppVersionStrSmall) (\s*)[\d\.]+/$1$2 %VersionStrSmall%" "Filename=%VersionFilePath%" || goto :error call :repl "Argument=(AppVersionStr) (\s*)[\d\.]+/$1$2 %VersionStr%" "Filename=%VersionFilePath%" || goto :error call :repl "Argument=(AlphaChannel) (\s*)[\d\.]+/$1$2 %VersionAlpha%" "Filename=%VersionFilePath%" || goto :error call :repl "Argument=(BetaVersion) (\s*)\d+/$1$2 %VersionFullBeta%" "Filename=%VersionFilePath%" || goto :error echo Patching core/version.h... set "VersionHeaderPath=%FullScriptPath%..\SourceFiles\core\version.h" call :repl "Argument=(BETA_VERSION_MACRO\s+)\(\d+ULL\)/$1(%VersionFullBeta%ULL)" "Filename=%VersionHeaderPath%" || goto :error call :repl "Argument=(AppVersion\s+=) (\s*)\d+/$1$2 %VersionFull%" "Filename=%VersionHeaderPath%" || goto :error call :repl "Argument=(AppVersionStr\s+=) (\s*)[&hat;;]+/$1$2 "%VersionStrSmall%"" "Filename=%VersionHeaderPath%" || goto :error call :repl "Argument=(AppAlphaVersion\s+=) (\s*)[a-z]+/$1$2 %VersionAlphaBool%" "Filename=%VersionHeaderPath%" || goto :error echo Patching Telegram.rc... set "ResourcePath=%FullScriptPath%..\Resources\winrc\Telegram.rc" call :repl "Argument=(FILEVERSION) (\s*)\d+,\d+,\d+,\d+/$1$2 %VersionMajor%,%VersionMinor%,%VersionPatch%,%VersionBeta%" "Filename=%ResourcePath%" || goto :error call :repl "Argument=(PRODUCTVERSION) (\s*)\d+,\d+,\d+,\d+/$1$2 %VersionMajor%,%VersionMinor%,%VersionPatch%,%VersionBeta%" "Filename=%ResourcePath%" || goto :error call :repl "Argument=("FileVersion",) (\s*)"\d+.\d+.\d+.\d+"/$1$2 "%VersionMajor%.%VersionMinor%.%VersionPatch%.%VersionBeta%"" "Filename=%ResourcePath%" || goto :error call :repl "Argument=("ProductVersion",) (\s*)"\d+.\d+.\d+.\d+"/$1$2 "%VersionMajor%.%VersionMinor%.%VersionPatch%.%VersionBeta%"" "Filename=%ResourcePath%" || goto :error echo Patching Updater.rc... set "ResourcePath=%FullScriptPath%..\Resources\winrc\Updater.rc" call :repl "Argument=(FILEVERSION) (\s*)\d+,\d+,\d+,\d+/$1$2 %VersionMajor%,%VersionMinor%,%VersionPatch%,%VersionBeta%" "Filename=%ResourcePath%" || goto :error call :repl "Argument=(PRODUCTVERSION) (\s*)\d+,\d+,\d+,\d+/$1$2 %VersionMajor%,%VersionMinor%,%VersionPatch%,%VersionBeta%" "Filename=%ResourcePath%" || goto :error call :repl "Argument=("FileVersion",) (\s*)"\d+.\d+.\d+.\d+"/$1$2 "%VersionMajor%.%VersionMinor%.%VersionPatch%.%VersionBeta%"" "Filename=%ResourcePath%" || goto :error call :repl "Argument=("ProductVersion",) (\s*)"\d+.\d+.\d+.\d+"/$1$2 "%VersionMajor%.%VersionMinor%.%VersionPatch%.%VersionBeta%"" "Filename=%ResourcePath%" || goto :error echo Patching appxmanifest.xml... set "ResourcePath=%FullScriptPath%..\Resources\uwp\AppX\AppxManifest.xml" call :repl "Argument= (Version=)"\d+.\d+.\d+.\d+"/ $1"%VersionMajor%.%VersionMinor%.%VersionPatch%.%VersionBeta%"" "Filename=%ResourcePath%" || goto :error exit /b :error ( set ErrorCode=%errorlevel% echo Error !ErrorCode! exit /b !ErrorCode! ) :repl ( set %1 set %2 set "TempFilename=!Filename!__tmp__" cscript //Nologo "%FullScriptPath%replace.vbs" "Replace" "!Argument!" < "!Filename!" > "!TempFilename!" || goto :repl_finish xcopy /Y !TempFilename! !Filename! >NUL || goto :repl_finish goto :repl_finish ) :count ( set %1 set %2 set "TempFilename=!Filename!__tmp__" cscript //Nologo "%FullScriptPath%replace.vbs" "Count" "!Argument!" < "!Filename!" > "!TempFilename!" || goto :repl_finish FOR /F "tokens=1,2* delims= " %%i in (!TempFilename!) do set "FoundCount=%%i" goto :repl_finish ) :repl_finish ( set ErrorCode=%errorlevel% if !ErrorCode! neq 0 ( echo Replace error !ErrorCode! echo While replacing "%Replace%" echo In file "%Filename%" ) del %TempFilename% exit /b !ErrorCode! ) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/build/set_version.sh�������������������������������������������������������0000775�0000000�0000000�00000013203�13262451251�0021175�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������set -e pushd `dirname $0` > /dev/null FullScriptPath=`pwd` popd > /dev/null Error () { echo "$1" exit 1 } InputVersion="$1" IFS='.' read -ra VersionParts <<< "$InputVersion" VersionMajor="${VersionParts[0]}" VersionMinor="${VersionParts[1]}" VersionPatch="${VersionParts[2]}" if [ "${VersionParts[3]}" == "" ]; then VersionBeta=0 VersionAlpha=0 elif [ "${VersionParts[3]}" == "alpha" ]; then VersionBeta=0 VersionAlpha=1 else VersionBeta="${VersionParts[3]}" VersionAlpha=0 fi VersionMajorCleared=`echo "$VersionMajor % 1000" | bc` if [ "$VersionMajorCleared" != "$VersionMajor" ]; then Error "Bad major version!" fi VersionMinorCleared=`echo "$VersionMinor % 1000" | bc` if [ "$VersionMinorCleared" != "$VersionMinor" ]; then Error "Bad minor version!" fi VersionPatchCleared=`echo "$VersionPatch % 1000" | bc` if [ "$VersionPatchCleared" != "$VersionPatch" ]; then Error "Bad patch version!" fi if [ "$VersionAlpha" != "0" ]; then if [ "$VersionAlpha" != "1" ]; then Error "Bad alpha version!" fi VersionAlphaBool=true else VersionAlphaBool=false fi VersionFull=`echo "$VersionMajor * 1000000 + $VersionMinor * 1000 + $VersionPatch" | bc` if [ "$VersionBeta" != "0" ]; then VersionBetaCleared=`echo "$VersionBeta % 1000" | bc` if [ "$VersionBetaCleared" != "$VersionBeta" ]; then Error "Bad beta version!" fi VersionBetaMul=`echo "$VersionBeta + 1000" | bc` VersionFullBeta="$VersionFull${VersionBetaMul:1}" else VersionFullBeta=0 fi VersionStr="$VersionMajor.$VersionMinor.$VersionPatch" if [ "$VersionPatch" != "0" ]; then VersionStrSmall="$VersionStr" else VersionStrSmall="$VersionMajor.$VersionMinor" fi if [ "$VersionAlpha" != "0" ]; then echo "Setting version: $VersionStr alpha" elif [ "$VersionBeta" != "0" ]; then echo "Setting version: $VersionStr.$VersionBeta closed beta" else echo "Setting version: $VersionStr stable" fi repl () { Pattern="$1" Replacement="$2" File="$3" CheckCommand="grep -sc '$Pattern' $File" set +e CheckCount=`eval $CheckCommand` set -e if [ "$CheckCount" -gt 0 ]; then ReplaceCommand="sed -i'.~' 's/$Pattern/$Replacement/g' $File" eval $ReplaceCommand else echo "Not found $Pattern" Error "While processing $File" fi } echo "Checking changelog..." ChangelogFile="$FullScriptPath/../../changelog.txt" ChangelogCommand="grep -sc '^$VersionStr ' $ChangelogFile" set +e FoundCount=`eval $ChangelogCommand` set -e if [ "$FoundCount" == "0" ]; then ChangelogCommand="grep -sc '^$VersionStrSmall ' $ChangelogFile" set +e FoundCount=`eval $ChangelogCommand` set -e if [ "$FoundCount" == "0" ]; then Error "Changelog entry not found!" elif [ "$FoundCount" != "1" ]; then Error "Wrong changelog entries count found: $FoundCount" fi elif [ "$FoundCount" != "1" ]; then Error "Wrong changelog entries count found: $FoundCount" fi echo "Patching build/version..." VersionFilePath="$FullScriptPath/version" repl "\(AppVersion\) \([ ]*\)[0-9][0-9]*" "\1\2 $VersionFull" "$VersionFilePath" repl "\(AppVersionStrMajor\) \([ ]*\)[0-9][0-9\.]*" "\1\2 $VersionMajor.$VersionMinor" "$VersionFilePath" repl "\(AppVersionStrSmall\) \([ ]*\)[0-9][0-9\.]*" "\1\2 $VersionStrSmall" "$VersionFilePath" repl "\(AppVersionStr\) \([ ]*\)[0-9][0-9\.]*" "\1\2 $VersionStr" "$VersionFilePath" repl "\(AlphaChannel\) \([ ]*\)[0-9][0-9]*" "\1\2 $VersionAlpha" "$VersionFilePath" repl "\(BetaVersion\) \([ ]*\)[0-9][0-9]*" "\1\2 $VersionFullBeta" "$VersionFilePath" echo "Patching core/version.h..." VersionHeaderPath="$FullScriptPath/../SourceFiles/core/version.h" repl "\(BETA_VERSION_MACRO [ ]*\)([0-9][0-9]*ULL)" "\1(${VersionFullBeta}ULL)" "$VersionHeaderPath" repl "\(AppVersion [ ]*=\) \([ ]*\)[0-9][0-9]*" "\1\2 $VersionFull" "$VersionHeaderPath" repl "\(AppVersionStr [ ]*=\) \([ ]*\)[^;][^;]*" "\1\2 \"$VersionStrSmall\"" "$VersionHeaderPath" repl "\(AppAlphaVersion [ ]*=\) \([ ]*\)[a-z][a-z]*" "\1\2 $VersionAlphaBool" "$VersionHeaderPath" echo "Patching Telegram.rc..." ResourcePath="$FullScriptPath/../Resources/winrc/Telegram.rc" repl "\(FILEVERSION\) \([ ]*\)[0-9][0-9]*,[0-9][0-9]*,[0-9][0-9]*,[0-9][0-9]*" "\1\2 $VersionMajor,$VersionMinor,$VersionPatch,$VersionBeta" "$ResourcePath" repl "\(PRODUCTVERSION\) \([ ]*\)[0-9][0-9]*,[0-9][0-9]*,[0-9][0-9]*,[0-9][0-9]*" "\1\2 $VersionMajor,$VersionMinor,$VersionPatch,$VersionBeta" "$ResourcePath" repl "\(\"FileVersion\",\) \([ ]*\)\"[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\"" "\1\2 \"$VersionMajor.$VersionMinor.$VersionPatch.$VersionBeta\"" "$ResourcePath" repl "\(\"ProductVersion\",\) \([ ]*\)\"[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\"" "\1\2 \"$VersionMajor.$VersionMinor.$VersionPatch.$VersionBeta\"" "$ResourcePath" echo "Patching Updater.rc..." ResourcePath="$FullScriptPath/../Resources/winrc/Updater.rc" repl "\(FILEVERSION\) \([ ]*\)[0-9][0-9]*,[0-9][0-9]*,[0-9][0-9]*,[0-9][0-9]*" "\1\2 $VersionMajor,$VersionMinor,$VersionPatch,$VersionBeta" "$ResourcePath" repl "\(PRODUCTVERSION\) \([ ]*\)[0-9][0-9]*,[0-9][0-9]*,[0-9][0-9]*,[0-9][0-9]*" "\1\2 $VersionMajor,$VersionMinor,$VersionPatch,$VersionBeta" "$ResourcePath" repl "\(\"FileVersion\",\) \([ ]*\)\"[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\"" "\1\2 \"$VersionMajor.$VersionMinor.$VersionPatch.$VersionBeta\"" "$ResourcePath" repl "\(\"ProductVersion\",\) \([ ]*\)\"[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\"" "\1\2 \"$VersionMajor.$VersionMinor.$VersionPatch.$VersionBeta\"" "$ResourcePath" echo "Patching appxmanifest.xml..." ResourcePath="$FullScriptPath/../Resources/uwp/AppX/AppxManifest.xml" repl " \(Version=\)\"[0-9][0-9]*.[0-9][0-9]*.[0-9][0-9]*.[0-9][0-9]*\"" " \1\"$VersionMajor.$VersionMinor.$VersionPatch.$VersionBeta\"" "$ResourcePath" ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/build/setup.iss������������������������������������������������������������0000664�0000000�0000000�00000012374�13262451251�0020166�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#define MyAppShortName "Telegram" #define MyAppName "Telegram Desktop" #define MyAppPublisher "Telegram Messenger LLP" #define MyAppURL "https://tdesktop.com" #define MyAppExeName "Telegram.exe" #define MyAppId "53F49750-6209-4FBF-9CA8-7A333C87D1ED" [Setup] ; NOTE: The value of AppId uniquely identifies this application. ; Do not use the same AppId value in installers for other applications. ; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) AppId={{{#MyAppId}} AppName={#MyAppName} AppVersion={#MyAppVersion} AppPublisher={#MyAppPublisher} AppPublisherURL={#MyAppURL} AppSupportURL={#MyAppURL} AppUpdatesURL={#MyAppURL} DefaultDirName={userappdata}\{#MyAppName} DefaultGroupName={#MyAppName} AllowNoIcons=yes OutputDir={#ReleasePath} OutputBaseFilename=tsetup.{#MyAppVersionFull} SetupIconFile={#SourcePath}..\Resources\art\icon256.ico UninstallDisplayIcon={app}\Telegram.exe Compression=lzma SolidCompression=yes DisableStartupPrompt=yes PrivilegesRequired=lowest VersionInfoVersion={#MyAppVersion}.0 CloseApplications=force DisableDirPage=no DisableProgramGroupPage=no [Languages] Name: "english"; MessagesFile: "compiler:Default.isl" Name: "it"; MessagesFile: "compiler:Languages\Italian.isl" Name: "es"; MessagesFile: "compiler:Languages\Spanish.isl" Name: "de"; MessagesFile: "compiler:Languages\German.isl" Name: "nl"; MessagesFile: "compiler:Languages\Dutch.isl" Name: "pt_BR"; MessagesFile: "compiler:Languages\BrazilianPortuguese.isl" [Tasks] Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}" Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; OnlyBelowVersion: 0,6.1 [Files] Source: "{#ReleasePath}\Telegram.exe"; DestDir: "{app}"; Flags: ignoreversion Source: "{#ReleasePath}\Updater.exe"; DestDir: "{app}"; Flags: ignoreversion ; NOTE: Don't use "Flags: ignoreversion" on any shared system files [Icons] Name: "{group}\{#MyAppShortName}"; Filename: "{app}\{#MyAppExeName}" Name: "{group}\{cm:UninstallProgram,{#MyAppShortName}}"; Filename: "{uninstallexe}" Name: "{userdesktop}\{#MyAppShortName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\{#MyAppShortName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: quicklaunchicon [Run] Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppShortName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent [UninstallDelete] Type: files; Name: "{app}\data" Type: files; Name: "{app}\data_config" Type: files; Name: "{app}\log.txt" Type: filesandordirs; Name: "{app}\DebugLogs" Type: filesandordirs; Name: "{app}\tupdates" Type: filesandordirs; Name: "{app}\tdata" Type: filesandordirs; Name: "{app}\tcache" Type: filesandordirs; Name: "{app}\tdumps" Type: dirifempty; Name: "{app}" Type: files; Name: "{userappdata}\{#MyAppName}\data" Type: files; Name: "{userappdata}\{#MyAppName}\data_config" Type: files; Name: "{userappdata}\{#MyAppName}\log.txt" Type: filesandordirs; Name: "{userappdata}\{#MyAppName}\DebugLogs" Type: filesandordirs; Name: "{userappdata}\{#MyAppName}\tupdates" Type: filesandordirs; Name: "{userappdata}\{#MyAppName}\tdata" Type: filesandordirs; Name: "{userappdata}\{#MyAppName}\tcache" Type: filesandordirs; Name: "{userappdata}\{#MyAppName}\tdumps" Type: dirifempty; Name: "{userappdata}\{#MyAppName}" [Code] procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep); var ResultCode: Integer; begin if CurUninstallStep = usUninstall then begin ShellExec('', ExpandConstant('{app}\{#MyAppExeName}'), '-cleanup', '', SW_SHOW, ewWaitUntilTerminated, ResultCode); end; end; const CSIDL_DESKTOPDIRECTORY = $0010; CSIDL_COMMON_DESKTOPDIRECTORY = $0019; procedure CurStepChanged(CurStep: TSetupStep); var ResultCode: Integer; HasOldKey: Boolean; HasNewKey: Boolean; HasOldLnk: Boolean; HasNewLnk: Boolean; UserDesktopLnk: String; CommonDesktopLnk: String; begin if CurStep = ssPostInstall then begin HasNewKey := RegKeyExists(HKEY_CURRENT_USER, 'Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{{#MyAppId}}_is1') or RegKeyExists(HKEY_CURRENT_USER, 'Software\Microsoft\Windows\CurrentVersion\Uninstall\{{#MyAppId}}_is1'); HasOldKey := RegKeyExists(HKEY_LOCAL_MACHINE, 'SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{{#MyAppId}}_is1') or RegKeyExists(HKEY_LOCAL_MACHINE, 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{{#MyAppId}}_is1'); UserDesktopLnk := ExpandFileName(GetShellFolderByCSIDL(CSIDL_DESKTOPDIRECTORY, False) + '\{#MyAppShortName}.lnk'); CommonDesktopLnk := ExpandFileName(GetShellFolderByCSIDL(CSIDL_COMMON_DESKTOPDIRECTORY, False) + '\{#MyAppShortName}.lnk'); HasNewLnk := FileExists(UserDesktopLnk); HasOldLnk := FileExists(CommonDesktopLnk) and (UserDesktopLnk <> CommonDesktopLnk); if (HasOldKey and HasNewKey) or (HasOldLnk and HasNewLnk) then begin if (GetWindowsVersion >= $06000000) then // Vista or later ShellExec('runas', ExpandConstant('{app}\{#MyAppExeName}'), '-fixprevious', '', SW_SHOW, ewWaitUntilTerminated, ResultCode) else ShellExec('', ExpandConstant('{app}\{#MyAppExeName}'), '-fixprevious', '', SW_SHOW, ewWaitUntilTerminated, ResultCode); end; end; end; ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/build/test_package.bat�����������������������������������������������������0000664�0000000�0000000�00000004661�13262451251�0021430�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������@echo OFF setlocal enabledelayedexpansion set "FullScriptPath=%~dp0" set "FullExecPath=%cd%" if not exist "%FullScriptPath%..\..\..\TelegramPrivate" ( echo. echo This script is for building the production version of Telegram Desktop. echo. echo For building custom versions please visit the build instructions page at: echo https://github.com/telegramdesktop/tdesktop/#build-instructions exit /b ) set "HomePath=%FullScriptPath%.." set "SignAppxPath=%HomePath%\..\..\TelegramPrivate\AppxSign.bat" set "ResourcesPath=%HomePath%\Resources" set "SolutionPath=%HomePath%\.." set "ReleasePath=%HomePath%\..\out\Debug" set "BinaryName=Telegram" if exist %ReleasePath%\AppX\ ( echo Result folder out\Debug\AppX already exists! exit /b 1 ) cd "%HomePath%" call gyp\refresh.bat if %errorlevel% neq 0 goto error cd "%SolutionPath%" call ninja -C out/Debug Telegram if %errorlevel% neq 0 goto error cd "%HomePath%" mkdir "%ReleasePath%\AppX" xcopy "Resources\uwp\AppX\*" "%ReleasePath%\AppX\" /E set "ResourcePath=%ReleasePath%\AppX\AppxManifest.xml" call :repl "Argument= (Publisher=)"CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"/ $1"CN=Telegram Messenger LLP, O=Telegram Messenger LLP, L=London, C=GB"" "Filename=%ResourcePath%" || goto :error call :repl "Argument= (ProcessorArchitecture=)"ARCHITECTURE"/ $1"x64"" "Filename=%ResourcePath%" || goto :error makepri new /pr Resources\uwp\AppX\ /cf Resources\uwp\priconfig.xml /mn %ReleasePath%\AppX\AppxManifest.xml /of %ReleasePath%\AppX\resources.pri if %errorlevel% neq 0 goto error xcopy "%ReleasePath%\%BinaryName%.exe" "%ReleasePath%\AppX\" MakeAppx.exe pack /d "%ReleasePath%\AppX" /l /p ..\out\Debug\%BinaryName%.appx if %errorlevel% neq 0 goto error call "%SignAppxPath%" "..\out\Debug\%BinaryName%.appx" move "%ReleasePath%\%BinaryName%.appx" "%ReleasePath%\AppX\" echo Done. exit /b :repl ( set %1 set %2 set "TempFilename=!Filename!__tmp__" cscript //Nologo "%FullScriptPath%replace.vbs" "Replace" "!Argument!" < "!Filename!" > "!TempFilename!" || goto :repl_finish xcopy /Y !TempFilename! !Filename! >NUL || goto :repl_finish goto :repl_finish ) :repl_finish ( set ErrorCode=%errorlevel% if !ErrorCode! neq 0 ( echo Replace error !ErrorCode! echo While replacing "%Replace%" echo In file "%Filename%" ) del %TempFilename% exit /b !ErrorCode! ) �������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/build/version��������������������������������������������������������������0000664�0000000�0000000�00000000220�13262451251�0017701�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������AppVersion 1002017 AppVersionStrMajor 1.2 AppVersionStrSmall 1.2.17 AppVersionStr 1.2.17 AlphaChannel 0 BetaVersion 0 ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/create.bat�����������������������������������������������������������������0000664�0000000�0000000�00000006333�13262451251�0017140�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������@echo off setlocal enabledelayedexpansion set "FullScriptPath=%~dp0" set "FullExecPath=%cd%" set "Command=%1" if "%Command%" == "test" ( call :write_test %2 exit /b %errorlevel% ) else if "%Command%" == "header" ( call :write_header %2 exit /b %errorlevel% ) else if "%Command%" == "source" ( call :write_source %2 exit /b %errorlevel% ) else if "%Command%" == "" ( echo This is an utility for fast blank module creation. echo Please provide module path. exit /b ) call :write_module %Command% exit /b %errorlevel% :write_module ( set "CommandPath=%1" set "CommandPathUnix=!CommandPath:\=/!" if "!CommandPathUnix!" == "" ( echo Provide module path. exit /b 1 ) echo Generating module !CommandPathUnix!.. call create.bat header !CommandPathUnix! call create.bat source !CommandPathUnix! exit /b ) :write_header ( set "CommandPath=%1" set "CommandPathUnix=!CommandPath:\=/!" set "CommandPathWin=!CommandPath:/=\!" if "!CommandPathUnix!" == "" ( echo Provide header path. exit /b 1 ) else if exist "SourceFiles\!CommandPathWin!.h" ( echo This header already exists. exit /b 1 ) echo Generating header !CommandPathUnix!.h.. mkdir "SourceFiles\!CommandPathWin!.h" rmdir "SourceFiles\!CommandPathWin!.h" call :write_comment !CommandPathWin!.h set "header1=#pragma once" ( echo !header1! echo. )>> "SourceFiles\!CommandPathWin!.h" exit /b ) :write_source ( set "CommandPath=%1" set "CommandPathUnix=!CommandPath:\=/!" set "CommandPathWin=!CommandPath:/=\!" if "!CommandPathUnix!" == "" ( echo Provide source path. exit /b 1 ) else if exist "SourceFiles\!CommandPathWin!.cpp" ( echo This source already exists. exit /b 1 ) echo Generating source !CommandPathUnix!.cpp.. mkdir "SourceFiles\!CommandPathWin!.cpp" rmdir "SourceFiles\!CommandPathWin!.cpp" call :write_comment !CommandPathWin!.cpp set "quote=""" set "quote=!quote:~0,1!" set "source1=#include !quote!!CommandPathUnix!.h!quote!" ( echo !source1! echo. )>> "SourceFiles\!CommandPathWin!.cpp" exit /b ) :write_test ( set "CommandPath=%1" set "CommandPathUnix=!CommandPath:\=/!" set "CommandPathWin=!CommandPath:/=\!" if "!CommandPathUnix!" == "" ( echo Provide source path. exit /b 1 ) else if exist "SourceFiles\!CommandPathWin!.cpp" ( echo This source already exists. exit /b 1 ) echo Generating test !CommandPathUnix!.cpp.. mkdir "SourceFiles\!CommandPathWin!.cpp" rmdir "SourceFiles\!CommandPathWin!.cpp" call :write_comment !CommandPathWin!.cpp set "quote=""" set "quote=!quote:~0,1!" set "source1=#include !quote!catch.hpp!quote!" ( echo !source1! echo. )>> "SourceFiles\!CommandPathWin!.cpp" exit /b ) :write_comment ( set "Path=%1" ( echo /* echo This file is part of Telegram Desktop, echo the official desktop application for the Telegram messaging service. echo. echo For license and copyright information please follow this link: echo https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL echo */ )> "SourceFiles\!Path!" exit /b ) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/gyp/�����������������������������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0015777�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/gyp/PrecompiledHeader.cmake������������������������������������������������0000664�0000000�0000000�00000015342�13262451251�0022362�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������## ## Modified for Telegram Desktop project by Telegram Desktop authors. ## # Function for setting up precompiled headers. Usage: # # add_library/executable(target # pchheader.c pchheader.cpp pchheader.h) # # add_precompiled_header(target pchheader.h # [FORCEINCLUDE] # [SOURCE_C pchheader.c] # [SOURCE_CXX pchheader.cpp]) # # Options: # # FORCEINCLUDE: Add compiler flags to automatically include the # pchheader.h from every source file. Works with both GCC and # MSVC. This is recommended. # # SOURCE_C/CXX: Specifies the .c/.cpp source file that includes # pchheader.h for generating the pre-compiled header # output. Defaults to pchheader.c. Only required for MSVC. # # Caveats: # # * Its not currently possible to use the same precompiled-header in # more than a single target in the same directory (No way to set # the source file properties differently for each target). # # * MSVC: A source file with the same name as the header must exist # and be included in the target (E.g. header.cpp). Name of file # can be changed using the SOURCE_CXX/SOURCE_C options. # # License: # # Copyright (C) 2009-2013 Lars Christensen <larsch@belunktum.dk> # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation files # (the 'Software') deal in the Software without restriction, # including without limitation the rights to use, copy, modify, merge, # publish, distribute, sublicense, and/or sell copies of the Software, # and to permit persons to whom the Software is furnished to do so, # subject to the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. include(CMakeParseArguments) macro(combine_arguments _variable) set(_result "") foreach(_element ${${_variable}}) set(_result "${_result} \"${_element}\"") endforeach() string(STRIP "${_result}" _result) set(${_variable} "${_result}") endmacro() function(export_all_flags _filename _source_name_for_flags) set(_include_directories "$<TARGET_PROPERTY:${_target},INCLUDE_DIRECTORIES>") set(_compile_definitions "$<TARGET_PROPERTY:${_target},COMPILE_DEFINITIONS>") get_source_file_property(_compile_flags "${_source_name_for_flags}" COMPILE_FLAGS) set(_compile_options "$<TARGET_PROPERTY:${_target},COMPILE_OPTIONS>") set(_include_directories "$<$<BOOL:${_include_directories}>:-I$<JOIN:${_include_directories},\n-I>\n>") set(_compile_definitions "$<$<BOOL:${_compile_definitions}>:-D$<JOIN:${_compile_definitions},\n-D>\n>") set(_compile_flags "$<$<BOOL:${_compile_flags}>:$<JOIN:${_compile_flags},\n>\n>") set(_compile_options "$<$<BOOL:${_compile_options}>:$<JOIN:${_compile_options},\n>\n>") file(GENERATE OUTPUT "${_filename}" CONTENT "${_compile_definitions}${_include_directories}${_compile_flags}${_compile_options}\n") endfunction() function(add_precompiled_header _target _input) if(CMAKE_COMPILER_IS_GNUCXX) get_filename_component(_name ${_input} NAME) set(_pch_header "${CMAKE_CURRENT_SOURCE_DIR}/${_input}") set(_pch_binary_dir "${CMAKE_CURRENT_BINARY_DIR}/${_target}_pch") set(_pchfile "${_pch_binary_dir}/${_name}") set(_outdir "${_pch_binary_dir}/${_name}.gch") make_directory(${_outdir}) set(_output_cxx "${_outdir}/.c++") set(_output_c "${_outdir}/.c") get_property(_sources TARGET ${_target} PROPERTY SOURCES) foreach(_source ${_sources}) if(_source MATCHES \\.\(c\)$ AND NOT _source_for_c_flags) set(_source_for_c_flags "${_source}") elseif(_source MATCHES \\.\(cc|cxx|cpp\)$ AND NOT _source_for_cpp_flags) set(_source_for_cpp_flags "${_source}") endif() endforeach() add_custom_command( OUTPUT "${_pchfile}" COMMAND "${CMAKE_COMMAND}" -E copy "${_pch_header}" "${_pchfile}" DEPENDS "${_pch_header}" IMPLICIT_DEPENDS CXX "${_pch_header}" IMPLICIT_DEPENDS C "${_pch_header}" COMMENT "Updating ${_name}") if(_source_for_c_flags) set(_pch_c_flags_file "${_pch_binary_dir}/compile_flags_c.rsp") export_all_flags("${_pch_c_flags_file}" "${_source_for_c_flags}") set(_compiler_FLAGS "@${_pch_c_flags_file}") add_custom_command( OUTPUT "${_output_c}" COMMAND "${CMAKE_C_COMPILER}" ${_compiler_FLAGS} -x c-header -o "${_output_c}" -c "${_pchfile}" DEPENDS "${_pchfile}" "${_pch_c_flags_file}" IMPLICIT_DEPENDS C "${_pch_header}" COMMENT "Precompiling ${_name} for ${_target} (C)") endif() if(_source_for_cpp_flags) set(_pch_cpp_flags_file "${_pch_binary_dir}/compile_flags_cpp.rsp") export_all_flags("${_pch_cpp_flags_file}" "${_source_for_cpp_flags}") set(_compiler_FLAGS "@${_pch_cpp_flags_file}") add_custom_command( OUTPUT "${_output_cxx}" COMMAND "${CMAKE_CXX_COMPILER}" ${_compiler_FLAGS} -x c++-header -o "${_output_cxx}" -c "${_pchfile}" DEPENDS "${_pchfile}" "${_pch_cpp_flags_file}" IMPLICIT_DEPENDS CXX "${_pch_header}" COMMENT "Precompiling ${_name} for ${_target} (C++)") endif() foreach(_source ${_sources}) set(_pch_compile_flags "") if(_source MATCHES \\.\(cc|cxx|cpp|c\)$) get_source_file_property(_pch_compile_flags "${_source}" COMPILE_FLAGS) if(NOT _pch_compile_flags) set(_pch_compile_flags) endif() separate_arguments(_pch_compile_flags) if(_source MATCHES \\.\(cc|cxx|cpp\)$) list(APPEND _pch_compile_flags -include "${_pchfile}") else() list(APPEND _pch_compile_flags "-I${_pch_binary_dir}") endif() get_source_file_property(_object_depends "${_source}" OBJECT_DEPENDS) if(NOT _object_depends) set(_object_depends) endif() list(APPEND _object_depends "${_pchfile}") if(_source MATCHES \\.\(cc|cxx|cpp\)$) list(APPEND _object_depends "${_output_cxx}") else() list(APPEND _object_depends "${_output_c}") endif() combine_arguments(_pch_compile_flags) set_source_files_properties(${_source} PROPERTIES COMPILE_FLAGS "${_pch_compile_flags}" OBJECT_DEPENDS "${_object_depends}") endif() endforeach() endif(CMAKE_COMPILER_IS_GNUCXX) endfunction() ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/gyp/Telegram.gyp�����������������������������������������������������������0000664�0000000�0000000�00000006742�13262451251�0020271�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of Telegram Desktop, # the official desktop application for the Telegram messaging service. # # For license and copyright information please follow this link: # https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL { 'includes': [ 'common.gypi', ], 'targets': [{ 'target_name': 'Telegram', 'variables': { 'variables': { 'libs_loc': '../../../Libraries', }, 'libs_loc': '<(libs_loc)', 'src_loc': '../SourceFiles', 'res_loc': '../Resources', 'submodules_loc': '../ThirdParty', 'minizip_loc': '<(submodules_loc)/minizip', 'sp_media_key_tap_loc': '<(submodules_loc)/SPMediaKeyTap', 'emoji_suggestions_loc': '<(submodules_loc)/emoji_suggestions', 'style_files': [ '<(res_loc)/colors.palette', '<(res_loc)/basic.style', '<(src_loc)/boxes/boxes.style', '<(src_loc)/calls/calls.style', '<(src_loc)/dialogs/dialogs.style', '<(src_loc)/history/history.style', '<(src_loc)/info/info.style', '<(src_loc)/intro/intro.style', '<(src_loc)/media/view/mediaview.style', '<(src_loc)/media/player/media_player.style', '<(src_loc)/overview/overview.style', '<(src_loc)/profile/profile.style', '<(src_loc)/settings/settings.style', '<(src_loc)/chat_helpers/chat_helpers.style', '<(src_loc)/ui/widgets/widgets.style', '<(src_loc)/window/window.style', ], 'langpacks': [ 'en', 'de', 'es', 'it', 'nl', 'ko', 'pt-BR', ], 'build_defines%': '', 'list_sources_command': 'python <(DEPTH)/list_sources.py --input <(DEPTH)/telegram_sources.txt --replace src_loc=<(src_loc)', }, 'includes': [ 'common_executable.gypi', 'telegram_qrc.gypi', 'telegram_win.gypi', 'telegram_mac.gypi', 'telegram_linux.gypi', 'qt.gypi', 'qt_moc.gypi', 'qt_rcc.gypi', 'codegen_rules.gypi', ], 'dependencies': [ 'codegen.gyp:codegen_emoji', 'codegen.gyp:codegen_lang', 'codegen.gyp:codegen_numbers', 'codegen.gyp:codegen_style', 'tests/tests.gyp:tests', 'utils.gyp:Updater', '../ThirdParty/libtgvoip/libtgvoip.gyp:libtgvoip', 'crl.gyp:crl', ], 'defines': [ 'AL_LIBTYPE_STATIC', 'AL_ALEXT_PROTOTYPES', 'TGVOIP_USE_CXX11_LIB', '<!@(python -c "for s in \'<(build_defines)\'.split(\',\'): print(s)")', ], 'include_dirs': [ '<(src_loc)', '<(SHARED_INTERMEDIATE_DIR)', '<(libs_loc)/breakpad/src', '<(libs_loc)/lzma/C', '<(libs_loc)/zlib', '<(libs_loc)/ffmpeg', '<(libs_loc)/openal-soft/include', '<(libs_loc)/opus/include', '<(libs_loc)/range-v3/include', '<(minizip_loc)', '<(sp_media_key_tap_loc)', '<(emoji_suggestions_loc)', '<(submodules_loc)/GSL/include', '<(submodules_loc)/variant/include', '<(submodules_loc)/crl/src', ], 'sources': [ '<@(qrc_files)', '<@(style_files)', '<!@(<(list_sources_command) <(qt_moc_list_sources_arg))', 'telegram_sources.txt', ], 'sources!': [ '<!@(<(list_sources_command) <(qt_moc_list_sources_arg) --exclude_for <(build_os))', ], 'conditions': [ [ '"<(official_build_target)" != ""', { 'defines': [ 'CUSTOM_API_ID', ], 'dependencies': [ 'utils.gyp:Packer', ], }], ], }], } ������������������������������tdesktop-1.2.17/Telegram/gyp/codegen.gyp������������������������������������������������������������0000664�0000000�0000000�00000012361�13262451251�0020127�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of Telegram Desktop, # the official desktop application for the Telegram messaging service. # # For license and copyright information please follow this link: # https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL { 'includes': [ 'common.gypi', ], 'targets': [{ 'target_name': 'codegen_lang', 'variables': { 'src_loc': '../SourceFiles', 'mac_target': '10.10', }, 'includes': [ 'common_executable.gypi', 'qt.gypi', ], 'include_dirs': [ '<(src_loc)', ], 'sources': [ '<(src_loc)/codegen/common/basic_tokenized_file.cpp', '<(src_loc)/codegen/common/basic_tokenized_file.h', '<(src_loc)/codegen/common/checked_utf8_string.cpp', '<(src_loc)/codegen/common/checked_utf8_string.h', '<(src_loc)/codegen/common/clean_file.cpp', '<(src_loc)/codegen/common/clean_file.h', '<(src_loc)/codegen/common/clean_file_reader.h', '<(src_loc)/codegen/common/const_utf8_string.h', '<(src_loc)/codegen/common/cpp_file.cpp', '<(src_loc)/codegen/common/cpp_file.h', '<(src_loc)/codegen/common/logging.cpp', '<(src_loc)/codegen/common/logging.h', '<(src_loc)/codegen/lang/generator.cpp', '<(src_loc)/codegen/lang/generator.h', '<(src_loc)/codegen/lang/main.cpp', '<(src_loc)/codegen/lang/options.cpp', '<(src_loc)/codegen/lang/options.h', '<(src_loc)/codegen/lang/parsed_file.cpp', '<(src_loc)/codegen/lang/parsed_file.h', '<(src_loc)/codegen/lang/processor.cpp', '<(src_loc)/codegen/lang/processor.h', ], }, { 'target_name': 'codegen_style', 'variables': { 'src_loc': '../SourceFiles', 'mac_target': '10.10', }, 'includes': [ 'common_executable.gypi', 'qt.gypi', ], 'include_dirs': [ '<(src_loc)', ], 'sources': [ '<(src_loc)/codegen/common/basic_tokenized_file.cpp', '<(src_loc)/codegen/common/basic_tokenized_file.h', '<(src_loc)/codegen/common/checked_utf8_string.cpp', '<(src_loc)/codegen/common/checked_utf8_string.h', '<(src_loc)/codegen/common/clean_file.cpp', '<(src_loc)/codegen/common/clean_file.h', '<(src_loc)/codegen/common/clean_file_reader.h', '<(src_loc)/codegen/common/const_utf8_string.h', '<(src_loc)/codegen/common/cpp_file.cpp', '<(src_loc)/codegen/common/cpp_file.h', '<(src_loc)/codegen/common/logging.cpp', '<(src_loc)/codegen/common/logging.h', '<(src_loc)/codegen/style/generator.cpp', '<(src_loc)/codegen/style/generator.h', '<(src_loc)/codegen/style/main.cpp', '<(src_loc)/codegen/style/module.cpp', '<(src_loc)/codegen/style/module.h', '<(src_loc)/codegen/style/options.cpp', '<(src_loc)/codegen/style/options.h', '<(src_loc)/codegen/style/parsed_file.cpp', '<(src_loc)/codegen/style/parsed_file.h', '<(src_loc)/codegen/style/processor.cpp', '<(src_loc)/codegen/style/processor.h', '<(src_loc)/codegen/style/structure_types.cpp', '<(src_loc)/codegen/style/structure_types.h', ], }, { 'target_name': 'codegen_numbers', 'variables': { 'src_loc': '../SourceFiles', 'mac_target': '10.10', }, 'includes': [ 'common_executable.gypi', 'qt.gypi', ], 'include_dirs': [ '<(src_loc)', ], 'sources': [ '<(src_loc)/codegen/common/basic_tokenized_file.cpp', '<(src_loc)/codegen/common/basic_tokenized_file.h', '<(src_loc)/codegen/common/checked_utf8_string.cpp', '<(src_loc)/codegen/common/checked_utf8_string.h', '<(src_loc)/codegen/common/clean_file.cpp', '<(src_loc)/codegen/common/clean_file.h', '<(src_loc)/codegen/common/clean_file_reader.h', '<(src_loc)/codegen/common/const_utf8_string.h', '<(src_loc)/codegen/common/cpp_file.cpp', '<(src_loc)/codegen/common/cpp_file.h', '<(src_loc)/codegen/common/logging.cpp', '<(src_loc)/codegen/common/logging.h', '<(src_loc)/codegen/numbers/generator.cpp', '<(src_loc)/codegen/numbers/generator.h', '<(src_loc)/codegen/numbers/main.cpp', '<(src_loc)/codegen/numbers/options.cpp', '<(src_loc)/codegen/numbers/options.h', '<(src_loc)/codegen/numbers/parsed_file.cpp', '<(src_loc)/codegen/numbers/parsed_file.h', '<(src_loc)/codegen/numbers/processor.cpp', '<(src_loc)/codegen/numbers/processor.h', ], }, { 'target_name': 'codegen_emoji', 'variables': { 'src_loc': '../SourceFiles', 'mac_target': '10.10', }, 'includes': [ 'common_executable.gypi', 'qt.gypi', ], 'include_dirs': [ '<(src_loc)', ], 'sources': [ '<(src_loc)/codegen/common/cpp_file.cpp', '<(src_loc)/codegen/common/cpp_file.h', '<(src_loc)/codegen/common/logging.cpp', '<(src_loc)/codegen/common/logging.h', '<(src_loc)/codegen/emoji/data.cpp', '<(src_loc)/codegen/emoji/data.h', '<(src_loc)/codegen/emoji/generator.cpp', '<(src_loc)/codegen/emoji/generator.h', '<(src_loc)/codegen/emoji/main.cpp', '<(src_loc)/codegen/emoji/options.cpp', '<(src_loc)/codegen/emoji/options.h', '<(src_loc)/codegen/emoji/replaces.cpp', '<(src_loc)/codegen/emoji/replaces.h', ], }], } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/gyp/codegen_rules.gypi�����������������������������������������������������0000664�0000000�0000000�00000012707�13262451251�0021516�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of Telegram Desktop, # the official desktop application for the Telegram messaging service. # # For license and copyright information please follow this link: # https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL { 'actions': [{ 'action_name': 'update_dependent_styles', 'inputs': [ '<(DEPTH)/update_dependent.py', '<@(style_files)', ], 'outputs': [ '<(SHARED_INTERMEDIATE_DIR)/update_dependent_styles.timestamp', ], 'action': [ 'python', '<(DEPTH)/update_dependent.py', '--styles', '-I', '<(res_loc)', '-I', '<(src_loc)', '-o', '<(SHARED_INTERMEDIATE_DIR)/update_dependent_styles.timestamp', '<@(style_files)', ], 'message': 'Updating dependent style files..', }, { 'action_name': 'update_dependent_qrc', 'inputs': [ '<(DEPTH)/update_dependent.py', '<@(qrc_files)', '<!@(python <(DEPTH)/update_dependent.py --qrc_list <@(qrc_files))', ], 'outputs': [ '<(SHARED_INTERMEDIATE_DIR)/update_dependent_qrc.timestamp', ], 'action': [ 'python', '<(DEPTH)/update_dependent.py', '--qrc', '-o', '<(SHARED_INTERMEDIATE_DIR)/update_dependent_qrc.timestamp', '<@(qrc_files)', ], 'message': 'Updating dependent qrc files..', }, { 'action_name': 'codegen_palette', 'inputs': [ '<(PRODUCT_DIR)/codegen_style<(exe_ext)', '<(SHARED_INTERMEDIATE_DIR)/update_dependent_styles.timestamp', '<(res_loc)/colors.palette', ], 'outputs': [ '<(SHARED_INTERMEDIATE_DIR)/styles/palette.h', '<(SHARED_INTERMEDIATE_DIR)/styles/palette.cpp', ], 'action': [ '<(PRODUCT_DIR)/codegen_style<(exe_ext)', '-I', '<(res_loc)', '-I', '<(src_loc)', '-o', '<(SHARED_INTERMEDIATE_DIR)/styles', '-w', '<(PRODUCT_DIR)/..', # GYP/Ninja bug workaround: if we specify just <(RULE_INPUT_PATH) # the <(RULE_INPUT_ROOT) variables won't be available in Ninja, # and the 'message' will be just 'codegen_style-ing .style..' # Looks like the using the <(RULE_INPUT_ROOT) here "exports" it # for using in the 'message' field. '<(res_loc)/colors.palette', ], 'message': 'codegen_palette-ing colors..', 'process_outputs_as_sources': 1, }, { 'action_name': 'codegen_lang', 'inputs': [ '<(PRODUCT_DIR)/codegen_lang<(exe_ext)', '<(res_loc)/langs/lang.strings', ], 'outputs': [ '<(SHARED_INTERMEDIATE_DIR)/lang_auto.cpp', '<(SHARED_INTERMEDIATE_DIR)/lang_auto.h', ], 'action': [ '<(PRODUCT_DIR)/codegen_lang<(exe_ext)', '-o', '<(SHARED_INTERMEDIATE_DIR)', '<(res_loc)/langs/lang.strings', '-w', '<(PRODUCT_DIR)/..', ], 'message': 'codegen_lang-ing lang.strings..', 'process_outputs_as_sources': 1, }, { 'action_name': 'codegen_numbers', 'inputs': [ '<(PRODUCT_DIR)/codegen_numbers<(exe_ext)', '<(res_loc)/numbers.txt', ], 'outputs': [ '<(SHARED_INTERMEDIATE_DIR)/numbers.cpp', '<(SHARED_INTERMEDIATE_DIR)/numbers.h', ], 'action': [ '<(PRODUCT_DIR)/codegen_numbers<(exe_ext)', '-o', '<(SHARED_INTERMEDIATE_DIR)', '<(res_loc)/numbers.txt', '-w', '<(PRODUCT_DIR)/..', ], 'message': 'codegen_numbers-ing numbers.txt..', 'process_outputs_as_sources': 1, }, { 'action_name': 'codegen_scheme', 'inputs': [ '<(src_loc)/codegen/scheme/codegen_scheme.py', '<(res_loc)/scheme.tl', ], 'outputs': [ '<(SHARED_INTERMEDIATE_DIR)/scheme.cpp', '<(SHARED_INTERMEDIATE_DIR)/scheme.h', ], 'action': [ 'python', '<(src_loc)/codegen/scheme/codegen_scheme.py', '-o', '<(SHARED_INTERMEDIATE_DIR)', '<(res_loc)/scheme.tl', ], 'message': 'codegen_scheme-ing scheme.tl..', 'process_outputs_as_sources': 1, }, { 'action_name': 'codegen_emoji', 'inputs': [ '<(PRODUCT_DIR)/codegen_emoji<(exe_ext)', '<(res_loc)/emoji_autocomplete.json', ], 'outputs': [ '<(SHARED_INTERMEDIATE_DIR)/emoji.cpp', '<(SHARED_INTERMEDIATE_DIR)/emoji.h', '<(SHARED_INTERMEDIATE_DIR)/emoji_suggestions_data.cpp', '<(SHARED_INTERMEDIATE_DIR)/emoji_suggestions_data.h', ], 'action': [ '<(PRODUCT_DIR)/codegen_emoji<(exe_ext)', '<(res_loc)/emoji_autocomplete.json', '-o', '<(SHARED_INTERMEDIATE_DIR)', ], 'message': 'codegen_emoji-ing..', 'process_outputs_as_sources': 1, }], 'rules': [{ 'rule_name': 'codegen_style', 'extension': 'style', 'inputs': [ '<(PRODUCT_DIR)/codegen_style<(exe_ext)', '<(SHARED_INTERMEDIATE_DIR)/update_dependent_styles.timestamp', ], 'outputs': [ '<(SHARED_INTERMEDIATE_DIR)/styles/style_<(RULE_INPUT_ROOT).h', '<(SHARED_INTERMEDIATE_DIR)/styles/style_<(RULE_INPUT_ROOT).cpp', ], 'action': [ '<(PRODUCT_DIR)/codegen_style<(exe_ext)', '-I', '<(res_loc)', '-I', '<(src_loc)', '-o', '<(SHARED_INTERMEDIATE_DIR)/styles', '-w', '<(PRODUCT_DIR)/..', # GYP/Ninja bug workaround: if we specify just <(RULE_INPUT_PATH) # the <(RULE_INPUT_ROOT) variables won't be available in Ninja, # and the 'message' will be just 'codegen_style-ing .style..' # Looks like the using the <(RULE_INPUT_ROOT) here "exports" it # for using in the 'message' field. '<(RULE_INPUT_DIRNAME)/<(RULE_INPUT_ROOT)<(RULE_INPUT_EXT)', ], 'message': 'codegen_style-ing <(RULE_INPUT_ROOT).style..', 'process_outputs_as_sources': 1, }], } ���������������������������������������������������������tdesktop-1.2.17/Telegram/gyp/common.gypi������������������������������������������������������������0000664�0000000�0000000�00000005246�13262451251�0020170�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of Telegram Desktop, # the official desktop application for the Telegram messaging service. # # For license and copyright information please follow this link: # https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL { 'includes': [ 'settings_win.gypi', 'settings_mac.gypi', 'settings_linux.gypi', ], 'variables': { 'variables': { 'variables': { 'variables': { 'variables': { 'build_os%': '<(OS)', }, 'build_os%': '<(build_os)', 'conditions': [ [ 'build_os == "win"', { 'build_win': 1, }, { 'build_win': 0, }], [ 'build_os == "mac"', { 'build_mac': 1, }, { 'build_mac': 0, }], [ 'build_os == "linux"', { 'build_linux': 1, }, { 'build_linux': 0, }], ], }, 'build_os%': '<(build_os)', 'build_win%': '<(build_win)', 'build_mac%': '<(build_mac)', 'build_linux%': '<(build_linux)', }, 'build_os%': '<(build_os)', 'build_win%': '<(build_win)', 'build_mac%': '<(build_mac)', 'build_linux%': '<(build_linux)', 'official_build_target%': '', }, 'build_os%': '<(build_os)', 'build_win%': '<(build_win)', 'build_mac%': '<(build_mac)', 'build_linux%': '<(build_linux)', 'official_build_target%': '<(official_build_target)', # GYP does not support per-configuration libraries :( # So they will be emulated through additional link flags, # which will contain <(ld_lib_prefix)LibraryName<(ld_lib_postfix) 'conditions': [ [ 'build_win', { 'ld_lib_prefix': '', 'ld_lib_postfix': '.lib', 'exe_ext': '.exe', }, { 'ld_lib_prefix': '-l', 'ld_lib_postfix': '', 'exe_ext': '', }], [ '"<(official_build_target)" == "mac32"', { 'mac_target%': '10.6', 'build_macold': 1, }, { 'mac_target%': '10.8', 'build_macold': 0, }], [ '"<(official_build_target)" == "macstore"', { 'build_macstore': 1, }, { 'build_macstore': 0, }], [ '"<(official_build_target)" == "uwp"', { 'build_uwp': 1, }, { 'build_uwp': 0, }], ], 'ld_lib_prefix': '<(ld_lib_prefix)', 'ld_lib_postfix': '<(ld_lib_postfix)', 'exe_ext': '<(exe_ext)', 'library%': 'static_library', }, 'configurations': { 'Debug': { 'defines': [ '_DEBUG', ], }, 'Release': { 'defines': [ 'NDEBUG', ], }, }, } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/gyp/common_executable.gypi�������������������������������������������������0000664�0000000�0000000�00000001031�13262451251�0022355�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of Telegram Desktop, # the official desktop application for the Telegram messaging service. # # For license and copyright information please follow this link: # https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL { 'type': 'executable', 'variables': { 'win_subsystem': '2', # Windows application }, 'includes': [ 'common.gypi', ], 'msvs_settings': { 'VCLinkerTool': { 'SubSystem': '<(win_subsystem)', 'ImportLibrary': '<(PRODUCT_DIR)/<(_target_name).lib', }, }, } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/gyp/create_env.py����������������������������������������������������������0000664�0000000�0000000�00000002153�13262451251�0020465�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������''' This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL ''' import glob import re import os # Generate custom environment.x86 for ninja # We use msbuild.log to extract some variables variables = [ 'TMP', 'SYSTEMROOT', 'TEMP', 'LIB', 'LIBPATH', 'PATH', 'PATHEXT', 'INCLUDE', ] var_values = {} for var_name in variables: var_values[var_name] = os.environ[var_name] next_contains_var = 0 with open('msbuild.log') as f: for line in f: if (re.match(r'^\s*Task "SetEnv"\s*$', line)): next_contains_var = 1 elif next_contains_var: cleanline = re.sub(r'^\s*|\s*$', '', line) name_value_pair = re.match(r'^([A-Z]+)=(.+)$', cleanline) if name_value_pair: var_values[name_value_pair.group(1)] = name_value_pair.group(2) next_contains_var = 0 out = open('environment.x86', 'wb') for var_name in variables: out.write(var_name + '=' + var_values[var_name] + '\0') out.write('\0') out.close() ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/gyp/crl.gyp����������������������������������������������������������������0000664�0000000�0000000�00000004724�13262451251�0017307�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of Telegram Desktop, # the official desktop application for the Telegram messaging service. # # For license and copyright information please follow this link: # https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL { 'includes': [ 'common.gypi', ], 'targets': [{ 'target_name': 'crl', 'type': 'static_library', 'dependencies': [], 'includes': [ 'common.gypi', 'qt.gypi', ], 'defines': [ ], 'conditions': [[ 'build_macold', { 'xcode_settings': { 'OTHER_CPLUSPLUSFLAGS': [ '-nostdinc++' ], }, 'include_dirs': [ '/usr/local/macold/include/c++/v1', ], }]], 'variables': { 'crl_src_loc': '../ThirdParty/crl/src/crl', 'official_build_target%': '', }, 'include_dirs': [ '../ThirdParty/crl/src', ], 'sources': [ '<(crl_src_loc)/common/crl_common_config.h', '<(crl_src_loc)/common/crl_common_list.cpp', '<(crl_src_loc)/common/crl_common_list.h', '<(crl_src_loc)/common/crl_common_on_main.cpp', '<(crl_src_loc)/common/crl_common_on_main.h', '<(crl_src_loc)/common/crl_common_on_main_guarded.h', '<(crl_src_loc)/common/crl_common_queue.cpp', '<(crl_src_loc)/common/crl_common_queue.h', '<(crl_src_loc)/common/crl_common_sync.h', '<(crl_src_loc)/common/crl_common_utils.h', '<(crl_src_loc)/dispatch/crl_dispatch_async.cpp', '<(crl_src_loc)/dispatch/crl_dispatch_async.h', '<(crl_src_loc)/dispatch/crl_dispatch_on_main.h', '<(crl_src_loc)/dispatch/crl_dispatch_queue.cpp', '<(crl_src_loc)/dispatch/crl_dispatch_queue.h', '<(crl_src_loc)/dispatch/crl_dispatch_semaphore.cpp', '<(crl_src_loc)/dispatch/crl_dispatch_semaphore.h', '<(crl_src_loc)/qt/crl_qt_async.cpp', '<(crl_src_loc)/qt/crl_qt_async.h', '<(crl_src_loc)/qt/crl_qt_semaphore.cpp', '<(crl_src_loc)/qt/crl_qt_semaphore.h', '<(crl_src_loc)/winapi/crl_winapi_async.cpp', '<(crl_src_loc)/winapi/crl_winapi_async.h', '<(crl_src_loc)/winapi/crl_winapi_dll.h', '<(crl_src_loc)/winapi/crl_winapi_list.cpp', '<(crl_src_loc)/winapi/crl_winapi_list.h', '<(crl_src_loc)/winapi/crl_winapi_semaphore.cpp', '<(crl_src_loc)/winapi/crl_winapi_semaphore.h', '<(crl_src_loc)/crl.h', '<(crl_src_loc)/crl_async.h', '<(crl_src_loc)/crl_on_main.h', '<(crl_src_loc)/crl_queue.h', '<(crl_src_loc)/crl_semaphore.h', ], }], } ��������������������������������������������tdesktop-1.2.17/Telegram/gyp/linux_glibc_wraps.gyp��������������������������������������������������0000664�0000000�0000000�00000001355�13262451251�0022237�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of Telegram Desktop, # the official desktop application for the Telegram messaging service. # # For license and copyright information please follow this link: # https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL { 'includes': [ 'common.gypi', ], 'targets': [{ 'target_name': 'linux_glibc_wraps', 'type': 'static_library', 'sources': [ '../SourceFiles/platform/linux/linux_glibc_wraps.c', ], 'conditions': [[ '"<!(uname -m)" == "x86_64" or "<!(uname -m)" == "aarch64"', { 'sources': [ '../SourceFiles/platform/linux/linux_glibc_wraps_64.c', ], }, { 'sources': [ '../SourceFiles/platform/linux/linux_glibc_wraps_32.c', ], }]], }], } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/gyp/list_sources.py��������������������������������������������������������0000664�0000000�0000000�00000007774�13262451251�0021106�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������''' This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL ''' from __future__ import print_function import sys import os import re import time import codecs def eprint(*args, **kwargs): print(*args, file=sys.stderr, **kwargs) sys.exit(1) def check_non_empty_moc(file_path): if not os.path.isfile(file_path): return False if re.search(r'\.h$', file_path): with codecs.open(file_path, mode="r", encoding="utf-8") as f: for line in f: if re.search(r'(^|\s)Q_OBJECT(\s|$)', line): return True return False def should_exclude(rules, exclude_for): for rule in rules: if rule[0:1] == '!': return (rule[1:] == exclude_for) elif rule == exclude_for: return False return len(rules) > 0 my_path = os.path.dirname(os.path.realpath(__file__)).replace('\\', '/') file_paths = [] platform_rules = {} next_input_path = 0 input_path = '' next_moc_prefix = 0 moc_prefix = '' next_replace = 0 replaces = [] next_exclude_for = 0 exclude_for = '' next_self = 1 for arg in sys.argv: if next_self != 0: next_self = 0 continue if arg == '--moc-prefix': next_moc_prefix = 1 continue elif next_moc_prefix == 1: next_moc_prefix = 0 moc_prefix = arg.replace('SHARED_INTERMEDIATE_DIR', '<(SHARED_INTERMEDIATE_DIR)') continue if arg == '--input': next_input_path = 1 continue elif next_input_path == 1: next_input_path = 0 input_path = arg continue if arg == '--replace': next_replace = 1 continue elif next_replace == 1: next_replace = 0 replaces.append(arg) continue if arg == '--exclude_for': next_exclude_for = 1 continue elif next_exclude_for == 1: next_exclude_for = 0 exclude_for = arg continue file_paths.append(arg) if input_path != '': if len(file_paths) != 0: eprint('You need to specify input file or input paths in command line.') elif not os.path.isfile(input_path): eprint('Input path not found.') else: platforms = [] with open(input_path, 'r') as f: for line in f: file_path = line.strip() if file_path[0:10] == 'platforms:': platforms_list = file_path[10:].split(' ') platforms = [] for platform in file_path[10:].split(' '): platform = platform.strip() if platform != '': platforms.append(platform) elif file_path[0:2] != '//' and file_path != '': file_paths.append(file_path) if len(platforms): platform_rules[file_path] = platforms elif '/platform/win/' in file_path: platform_rules[file_path] = [ 'win' ] elif '/platform/mac/' in file_path: platform_rules[file_path] = [ 'mac' ] elif '/platform/linux/' in file_path: platform_rules[file_path] = [ 'linux' ] for replace in replaces: replace_parts = replace.split('=', 1) if len(replace_parts) != 2: eprint('Bad replace: ' + replace) real_paths = [] real_platform_rules = {} for file_path in file_paths: real_path = file_path.replace('<(' + replace_parts[0] + ')', replace_parts[1]) real_paths.append(real_path) if file_path in platform_rules: real_platform_rules[real_path] = platform_rules[file_path] file_paths = real_paths platform_rules = real_platform_rules if exclude_for != '': real_paths = [] for file_path in file_paths: if not file_path in platform_rules: continue if not should_exclude(platform_rules[file_path], exclude_for): continue real_paths.append(file_path) file_paths = real_paths for file_path in file_paths: print(file_path) if moc_prefix != '': for file_path in file_paths: if check_non_empty_moc(file_path): m = re.search(r'(^|/)([^/]+)\.h$', file_path) if not m: eprint('Bad file path: ' + file_path) print(moc_prefix + m.group(2) + '.cpp') ����tdesktop-1.2.17/Telegram/gyp/print_version.sh�������������������������������������������������������0000775�0000000�0000000�00000000405�13262451251�0021236�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/env bash set -e FullExecPath=$PWD pushd `dirname $0` > /dev/null FullScriptPath=`pwd` popd > /dev/null while IFS='' read -r line || [[ -n "$line" ]]; do set $line eval $1="$2" done < "$FullScriptPath/../build/version" echo $AppVersionStr exit �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/gyp/qt.gypi����������������������������������������������������������������0000664�0000000�0000000�00000015346�13262451251�0017326�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of Telegram Desktop, # the official desktop application for the Telegram messaging service. # # For license and copyright information please follow this link: # https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL { 'variables': { 'variables': { 'variables': { 'variables': { 'variables': { 'conditions': [ [ 'build_macold', { 'qt_version%': '5.3.2', }, { 'qt_version%': '5.6.2', }] ], }, 'qt_libs': [ 'qwebp', 'Qt5PrintSupport', 'Qt5PlatformSupport', 'Qt5Network', 'Qt5Widgets', 'Qt5Gui', 'qtharfbuzzng', ], 'qt_version%': '<(qt_version)', 'conditions': [ [ 'build_macold', { 'linux_path_qt%': '/usr/local/macold/Qt-<(qt_version)', }, { 'linux_path_qt%': '/usr/local/tdesktop/Qt-<(qt_version)', }] ] }, 'qt_version%': '<(qt_version)', 'qt_loc_unix': '<(linux_path_qt)', 'conditions': [ [ 'build_win', { 'qt_lib_prefix': '<(ld_lib_prefix)', 'qt_lib_debug_postfix': 'd<(ld_lib_postfix)', 'qt_lib_release_postfix': '<(ld_lib_postfix)', 'qt_libs': [ '<@(qt_libs)', 'Qt5Core', 'qtmain', 'qwindows', 'qtfreetype', 'qtpcre', ], }], [ 'build_mac', { 'qt_lib_prefix': '<(ld_lib_prefix)', 'qt_lib_debug_postfix': '_debug<(ld_lib_postfix)', 'qt_lib_release_postfix': '<(ld_lib_postfix)', 'qt_libs': [ '<@(qt_libs)', 'Qt5Core', 'qgenericbearer', 'qcocoa', ], }], [ 'build_mac and not build_macold', { 'qt_libs': [ '<@(qt_libs)', 'Qt5Core', 'qtfreetype', 'qtpcre', ], }], [ 'build_linux', { 'qt_lib_prefix': 'lib', 'qt_lib_debug_postfix': '.a', 'qt_lib_release_postfix': '.a', 'qt_libs': [ 'qxcb', 'Qt5XcbQpa', 'qconnmanbearer', 'qgenericbearer', 'qnmbearer', '<@(qt_libs)', 'Qt5DBus', 'Qt5Core', 'qtpcre', 'Xi', 'Xext', 'Xfixes', 'SM', 'ICE', 'fontconfig', 'expat', 'freetype', 'z', 'xcb-shm', 'xcb-xfixes', 'xcb-render', 'xcb-static', ], }], ], }, 'qt_version%': '<(qt_version)', 'qt_loc_unix': '<(qt_loc_unix)', 'qt_version_loc': '<!(python -c "print(\'<(qt_version)\'.replace(\'.\', \'_\'))")', 'qt_libs_debug': [ '<!@(python -c "for s in \'<@(qt_libs)\'.split(\' \'): print(\'<(qt_lib_prefix)\' + s + \'<(qt_lib_debug_postfix)\')")', ], 'qt_libs_release': [ '<!@(python -c "for s in \'<@(qt_libs)\'.split(\' \'): print(\'<(qt_lib_prefix)\' + s + \'<(qt_lib_release_postfix)\')")', ], }, 'qt_libs_debug': [ '<@(qt_libs_debug)' ], 'qt_libs_release': [ '<@(qt_libs_release)' ], 'qt_version%': '<(qt_version)', 'conditions': [ [ 'build_win', { 'qt_loc': '<(DEPTH)/../../../Libraries/qt<(qt_version_loc)/qtbase', }, { 'qt_loc': '<(qt_loc_unix)', }], ], # If you need moc sources include a line in your 'sources': # '<!@(python <(DEPTH)/list_sources.py [sources] <(qt_moc_list_sources_arg))' # where [sources] contains all your source files 'qt_moc_list_sources_arg': '--moc-prefix SHARED_INTERMEDIATE_DIR/<(_target_name)/moc/moc_', 'linux_path_xkbcommon%': '/usr/local', 'linux_lib_ssl%': '/usr/local/ssl/lib/libssl.a', 'linux_lib_crypto%': '/usr/local/ssl/lib/libcrypto.a', 'linux_lib_icu%': 'libicutu.a libicui18n.a libicuuc.a libicudata.a', }, 'configurations': { 'Debug': { 'conditions' : [ [ 'build_win', { 'msvs_settings': { 'VCLinkerTool': { 'AdditionalDependencies': [ '<@(qt_libs_debug)', ], }, }, }], [ 'build_mac', { 'xcode_settings': { 'OTHER_LDFLAGS': [ '<@(qt_libs_debug)', '/usr/local/lib/libz.a', ], }, }], ], }, 'Release': { 'conditions' : [ [ 'build_win', { 'msvs_settings': { 'VCLinkerTool': { 'AdditionalDependencies': [ '<@(qt_libs_release)', ], }, }, }], [ 'build_mac', { 'xcode_settings': { 'OTHER_LDFLAGS': [ '<@(qt_libs_release)', '/usr/local/lib/libz.a', ], }, }], ], }, }, 'include_dirs': [ '<(qt_loc)/include', '<(qt_loc)/include/QtCore', '<(qt_loc)/include/QtGui', '<(qt_loc)/include/QtDBus', '<(qt_loc)/include/QtCore/<(qt_version)', '<(qt_loc)/include/QtGui/<(qt_version)', '<(qt_loc)/include/QtCore/<(qt_version)/QtCore', '<(qt_loc)/include/QtGui/<(qt_version)/QtGui', ], 'library_dirs': [ '<(qt_loc)/lib', '<(qt_loc)/plugins', '<(qt_loc)/plugins/bearer', '<(qt_loc)/plugins/platforms', '<(qt_loc)/plugins/imageformats', ], 'defines': [ 'QT_WIDGETS_LIB', 'QT_NETWORK_LIB', 'QT_GUI_LIB', 'QT_CORE_LIB', ], 'conditions': [ [ 'build_linux', { 'dependencies': [ '<(DEPTH)/linux_glibc_wraps.gyp:linux_glibc_wraps', ], 'library_dirs': [ '<(qt_loc)/plugins/platforminputcontexts', ], 'libraries': [ '<(PRODUCT_DIR)/obj.target/liblinux_glibc_wraps.a', '<(linux_path_xkbcommon)/lib/libxkbcommon.a', '<@(qt_libs_release)', '<(linux_lib_ssl)', '<(linux_lib_crypto)', '<!@(python -c "for s in \'<(linux_lib_icu)\'.split(\' \'): print(s)")', '-lxcb', '-lX11', '-lX11-xcb', '-ldbus-1', '-ldl', '-lgthread-2.0', '-lglib-2.0', '-lpthread', ], 'include_dirs': [ '<(qt_loc)/mkspecs/linux-g++', ], 'ldflags': [ '-static-libstdc++', '-pthread', '-rdynamic', ], }], [ 'build_mac', { 'xcode_settings': { 'OTHER_LDFLAGS': [ '-lcups', ], }, }], ], } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/gyp/qt_moc.gypi������������������������������������������������������������0000664�0000000�0000000�00000001627�13262451251�0020161�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of Telegram Desktop, # the official desktop application for the Telegram messaging service. # # For license and copyright information please follow this link: # https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL { 'rules': [{ 'rule_name': 'qt_moc', 'extension': 'h', 'outputs': [ '<(SHARED_INTERMEDIATE_DIR)/<(_target_name)/moc/moc_<(RULE_INPUT_ROOT).cpp', ], 'action': [ '<(qt_loc)/bin/moc<(exe_ext)', # Silence "Note: No relevant classes found. No output generated." '--no-notes', '<!@(python -c "for s in \'<@(_defines)\'.split(\' \'): print(\'-D\' + s)")', # '<!@(python -c "for s in \'<@(_include_dirs)\'.split(\' \'): print(\'-I\' + s)")', '<(RULE_INPUT_PATH)', '-o', '<(SHARED_INTERMEDIATE_DIR)/<(_target_name)/moc/moc_<(RULE_INPUT_ROOT).cpp', ], 'message': 'Moc-ing <(RULE_INPUT_ROOT).h..', }], } ���������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/gyp/qt_rcc.gypi������������������������������������������������������������0000664�0000000�0000000�00000001510�13262451251�0020141�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of Telegram Desktop, # the official desktop application for the Telegram messaging service. # # For license and copyright information please follow this link: # https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL { 'rules': [{ 'rule_name': 'qt_rcc', 'extension': 'qrc', 'inputs': [ '<(SHARED_INTERMEDIATE_DIR)/update_dependent_qrc.timestamp', ], 'outputs': [ '<(SHARED_INTERMEDIATE_DIR)/<(_target_name)/qrc/qrc_<(RULE_INPUT_ROOT).cpp', ], 'action': [ '<(qt_loc)/bin/rcc<(exe_ext)', '-name', '<(RULE_INPUT_ROOT)', '-no-compress', '<(RULE_INPUT_PATH)', '-o', '<(SHARED_INTERMEDIATE_DIR)/<(_target_name)/qrc/qrc_<(RULE_INPUT_ROOT).cpp', ], 'message': 'Rcc-ing <(RULE_INPUT_ROOT).qrc..', 'process_outputs_as_sources': 1, }], } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/gyp/refresh.bat������������������������������������������������������������0000664�0000000�0000000�00000004564�13262451251�0020136�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������@echo OFF setlocal EnableDelayedExpansion set "FullScriptPath=%~dp0" set "FullExecPath=%cd%" set "Silence=>nul" if "%1" == "-v" set "Silence=" if exist "%FullScriptPath%..\build\target" ( FOR /F "tokens=1* delims= " %%i in (%FullScriptPath%..\build\target) do set "BuildTarget=%%i" ) else ( set "BuildTarget=" ) rem strangely linking of Release Telegram build complains about the absence of lib.pdb if exist "%FullScriptPath%..\..\..\Libraries\openssl\tmp32\lib.pdb" ( if not exist "%FullScriptPath%..\..\..\Libraries\openssl\Release\lib\lib.pdb" ( xcopy "%FullScriptPath%..\..\..\Libraries\openssl\tmp32\lib.pdb" "%FullScriptPath%..\..\..\Libraries\openssl\Release\lib\" %Silence% ) ) set BUILD_DEFINES= if not "%TDESKTOP_BUILD_DEFINES%" == "" ( set "BUILD_DEFINES=-Dbuild_defines=%TDESKTOP_BUILD_DEFINES%" echo [INFO] Set build defines to !BUILD_DEFINES! ) set GYP_MSVS_VERSION=2017 cd "%FullScriptPath%" call gyp --depth=. --generator-output=.. -Goutput_dir=../out !BUILD_DEFINES! -Dofficial_build_target=%BuildTarget% Telegram.gyp --format=ninja if %errorlevel% neq 0 goto error call gyp --depth=. --generator-output=.. -Goutput_dir=../out !BUILD_DEFINES! -Dofficial_build_target=%BuildTarget% Telegram.gyp --format=msvs-ninja if %errorlevel% neq 0 goto error cd ../.. rem looks like ninja build works without sdk 7.1 which was used by generating custom environment.arch files rem cd "%FullScriptPath%" rem call gyp --depth=. --generator-output=../.. -Goutput_dir=out -Gninja_use_custom_environment_files=1 Telegram.gyp --format=ninja rem if %errorlevel% neq 0 goto error rem call gyp --depth=. --generator-output=../.. -Goutput_dir=out -Gninja_use_custom_environment_files=1 Telegram.gyp --format=msvs-ninja rem if %errorlevel% neq 0 goto error rem cd ../.. rem call msbuild /target:SetBuildDefaultEnvironmentVariables Telegram.vcxproj /fileLogger %Silence% rem if %errorlevel% neq 0 goto error rem call python "%FullScriptPath%create_env.py" rem if %errorlevel% neq 0 goto error rem call move environment.x86 out\Debug\ %Silence% rem if %errorlevel% neq 0 goto error cd "%FullExecPath%" exit /b :error echo FAILED if exist "%FullScriptPath%..\..\msbuild.log" del "%FullScriptPath%..\..\msbuild.log" if exist "%FullScriptPath%..\..\environment.x86" del "%FullScriptPath%..\..\environment.x86" cd "%FullExecPath%" exit /b 1 ��������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/gyp/refresh.sh�������������������������������������������������������������0000775�0000000�0000000�00000002207�13262451251�0017775�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/env bash set -e FullExecPath=$PWD pushd `dirname $0` > /dev/null FullScriptPath=`pwd` popd > /dev/null if [ -f "$FullScriptPath/../build/target" ]; then while IFS='' read -r line || [[ -n "$line" ]]; do BuildTarget="$line" done < "$FullScriptPath/../build/target" else BuildTarget="" fi MySystem=`uname -s` cd $FullScriptPath if [ "$MySystem" == "Linux" ]; then ../../../Libraries/gyp/gyp --depth=. --generator-output=.. -Goutput_dir=../out -Dofficial_build_target=$BuildTarget Telegram.gyp --format=cmake cd ../../out/Debug cmake . cd ../Release cmake . cd ../../Telegram/gyp else #gyp --depth=. --generator-output=../.. -Goutput_dir=out Telegram.gyp --format=ninja #gyp --depth=. --generator-output=../.. -Goutput_dir=out Telegram.gyp --format=xcode-ninja #gyp --depth=. --generator-output=../.. -Goutput_dir=out Telegram.gyp --format=xcode # use patched gyp with Xcode project generator ../../../Libraries/gyp/gyp --depth=. --generator-output=.. -Goutput_dir=../out -Gxcode_upgrade_check_project_version=930 -Dofficial_build_target=$BuildTarget Telegram.gyp --format=xcode fi cd ../.. cd $FullExecPath exit �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/gyp/settings_linux.gypi����������������������������������������������������0000664�0000000�0000000�00000003451�13262451251�0021753�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of Telegram Desktop, # the official desktop application for the Telegram messaging service. # # For license and copyright information please follow this link: # https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL { 'conditions': [ [ 'build_linux', { 'variables': { 'linux_common_flags': [ '-pipe', '-Wall', '-Werror', '-W', '-fPIC', '-Wno-unused-variable', '-Wno-unused-parameter', '-Wno-unused-function', '-Wno-switch', '-Wno-comment', '-Wno-unused-but-set-variable', '-Wno-missing-field-initializers', '-Wno-sign-compare', ], }, 'conditions': [ [ '"<!(uname -m)" == "x86_64" or "<!(uname -m)" == "aarch64"', { 'defines': [ 'Q_OS_LINUX64', ], 'conditions': [ [ '"<(official_build_target)" != "" and "<(official_build_target)" != "linux"', { 'sources': [ '__Wrong_Official_Build_Target_<(official_build_target)_' ], }], ], }, { 'defines': [ 'Q_OS_LINUX32', ], 'conditions': [ [ '"<(official_build_target)" != "" and "<(official_build_target)" != "linux32"', { 'sources': [ '__Wrong_Official_Build_Target_<(official_build_target)_' ], }], ], }], ], 'defines': [ '_REENTRANT', 'QT_STATICPLUGIN', 'QT_PLUGIN', ], 'cflags_c': [ '<@(linux_common_flags)', '-std=gnu11', ], 'cflags_cc': [ '<@(linux_common_flags)', '-std=c++1z', '-Wno-register', ], 'configurations': { 'Debug': { }, }, }], ], } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/gyp/settings_mac.gypi������������������������������������������������������0000664�0000000�0000000�00000005341�13262451251�0021354�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of Telegram Desktop, # the official desktop application for the Telegram messaging service. # # For license and copyright information please follow this link: # https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL { 'conditions': [ [ 'build_mac', { 'variables': { 'mac_frameworks': [ 'Cocoa', 'CoreFoundation', 'CoreServices', 'CoreText', 'CoreGraphics', 'OpenGL', 'AudioUnit', 'ApplicationServices', 'Foundation', 'AGL', 'Security', 'SystemConfiguration', 'Carbon', 'AudioToolbox', 'CoreAudio', 'QuartzCore', 'AppKit', 'CoreWLAN', 'IOKit', ], 'mac_common_flags': [ '-pipe', '-g', '-Wall', '-Werror', '-W', '-fPIE', '-Wno-unused-variable', '-Wno-unused-parameter', '-Wno-unused-function', '-Wno-switch', '-Wno-comment', '-Wno-missing-field-initializers', '-Wno-sign-compare', ], }, 'xcode_settings': { 'SYMROOT': '../../out', 'OTHER_CFLAGS': [ '<@(mac_common_flags)', ], 'OTHER_CPLUSPLUSFLAGS': [ '<@(mac_common_flags)', ], 'OTHER_LDFLAGS': [ '<!@(python -c "for s in \'<@(mac_frameworks)\'.split(\' \'): print(\'-framework \' + s)")', ], 'MACOSX_DEPLOYMENT_TARGET': '<(mac_target)', 'COMBINE_HIDPI_IMAGES': 'YES', 'COPY_PHASE_STRIP': 'NO', 'CLANG_CXX_LANGUAGE_STANDARD': 'c++1z', }, 'configurations': { 'Debug': { 'xcode_settings': { 'ENABLE_TESTABILITY': 'YES', 'ONLY_ACTIVE_ARCH': 'YES', }, }, }, 'conditions': [ [ '"<(official_build_target)" != "" and "<(official_build_target)" != "mac" and "<(official_build_target)" != "mac32" and "<(official_build_target)" != "macstore"', { 'sources': [ '__Wrong_Official_Build_Target__' ], }], ], }], [ 'build_macold', { 'xcode_settings': { 'OTHER_CPLUSPLUSFLAGS': [ '-Wno-inconsistent-missing-override', ], 'OTHER_LDFLAGS': [ '-w', # Suppress 'libstdc++ is deprecated' warning. ], }, }, { 'xcode_settings': { 'CLANG_CXX_LIBRARY': 'libc++', 'CLANG_ENABLE_OBJC_WEAK': 'YES', 'OTHER_LDFLAGS': [ '-framework', 'VideoToolbox', '-framework', 'VideoDecodeAcceleration', '-framework', 'AVFoundation', '-framework', 'CoreMedia', ], }, }], ], } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/gyp/settings_win.gypi������������������������������������������������������0000664�0000000�0000000�00000007032�13262451251�0021410�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of Telegram Desktop, # the official desktop application for the Telegram messaging service. # # For license and copyright information please follow this link: # https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL { 'conditions': [ [ 'build_win', { 'defines': [ 'WIN32', '_WINDOWS', '_UNICODE', 'UNICODE', 'HAVE_STDINT_H', 'ZLIB_WINAPI', '_SCL_SECURE_NO_WARNINGS', '_USING_V110_SDK71_', ], 'msvs_cygwin_shell': 0, 'msvs_settings': { 'VCCLCompilerTool': { 'ProgramDataBaseFileName': '$(OutDir)\\$(ProjectName).pdb', 'DebugInformationFormat': '3', # Program Database (/Zi) 'WarnAsError': 'true', 'AdditionalOptions': [ '/std:c++latest', '/permissive-', '/Qspectre', '/MP', # Enable multi process build. '/EHsc', # Catch C++ exceptions only, extern C functions never throw a C++ exception. '/w14834', # [[nodiscard]] ], 'TreatWChar_tAsBuiltInType': 'false', }, 'VCLinkerTool': { 'MinimumRequiredVersion': '5.01', 'ImageHasSafeExceptionHandlers': 'false', # Disable /SAFESEH }, }, 'msvs_external_builder_build_cmd': [ 'ninja.exe', '-C', '$(OutDir)', '-k0', '$(ProjectName)', ], 'libraries': [ '-lwinmm', '-limm32', '-lws2_32', '-lkernel32', '-luser32', '-lgdi32', '-lwinspool', '-lcomdlg32', '-ladvapi32', '-lshell32', '-lole32', '-loleaut32', '-luuid', '-lodbc32', '-lodbccp32', '-lShlwapi', '-lIphlpapi', '-lGdiplus', '-lStrmiids', ], 'configurations': { 'Debug': { 'msvs_settings': { 'VCCLCompilerTool': { 'Optimization': '0', # Disabled (/Od) 'RuntimeLibrary': '1', # Multi-threaded Debug (/MTd) }, 'VCLinkerTool': { 'GenerateDebugInformation': 'true', # true (/DEBUG) 'IgnoreDefaultLibraryNames': 'LIBCMT', 'LinkIncremental': '2', # Yes (/INCREMENTAL) }, }, }, 'Release': { 'msvs_settings': { 'VCCLCompilerTool': { 'Optimization': '2', # Maximize Speed (/O2) 'InlineFunctionExpansion': '2', # Any suitable (/Ob2) 'EnableIntrinsicFunctions': 'true', # Yes (/Oi) 'FavorSizeOrSpeed': '1', # Favor fast code (/Ot) 'RuntimeLibrary': '0', # Multi-threaded (/MT) 'EnableEnhancedInstructionSet': '2', # Streaming SIMD Extensions 2 (/arch:SSE2) 'WholeProgramOptimization': 'true', # /GL }, 'VCLinkerTool': { 'GenerateDebugInformation': 'true', # /DEBUG 'OptimizeReferences': '2', 'LinkTimeCodeGeneration': '1', # /LTCG }, 'VCLibrarianTool': { 'LinkTimeCodeGeneration': 'true', # /LTCG }, }, }, }, 'conditions': [ [ '"<(official_build_target)" != "" and "<(official_build_target)" != "win" and "<(official_build_target)" != "uwp"', { 'sources': [ '__Wrong_Official_Build_Target__' ], }], ], }], ], } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/gyp/telegram_linux.gypi����������������������������������������������������0000664�0000000�0000000�00000006736�13262451251�0021724�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of Telegram Desktop, # the official desktop application for the Telegram messaging service. # # For license and copyright information please follow this link: # https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL { 'conditions': [[ 'build_linux', { 'variables': { 'not_need_gtk%': '<!(python -c "print(\'TDESKTOP_DISABLE_GTK_INTEGRATION\' in \'<(build_defines)\')")', 'pkgconfig_libs': [ # In order to work libxkbcommon must be linked statically, # PKGCONFIG links it like "-L/usr/local/lib -lxkbcommon" # which makes a dynamic link which leads to segfault in # QApplication() -> createPlatformIntegration -> QXcbIntegrationPlugin::create #'xkbcommon', ], 'linux_path_ffmpeg%': '/usr/local', 'linux_path_openal%': '/usr/local', 'linux_path_va%': '/usr/local', 'linux_path_vdpau%': '/usr/local', 'linux_path_breakpad%': '/usr/local', 'linux_path_opus_include%': '<(libs_loc)/opus/include', 'linux_path_range%': '/usr/local', }, 'include_dirs': [ '/usr/local/include', '<(linux_path_ffmpeg)/include', '<(linux_path_openal)/include', '<(linux_path_breakpad)/include/breakpad', '<(linux_path_opus_include)', '<(linux_path_range)/include', ], 'library_dirs': [ '/usr/local/lib', '<(linux_path_ffmpeg)/lib', '<(linux_path_openal)/lib', '<(linux_path_va)/lib', '<(linux_path_vdpau)/lib', '<(linux_path_breakpad)/lib', ], 'libraries': [ 'breakpad_client', 'composeplatforminputcontextplugin', 'ibusplatforminputcontextplugin', 'fcitxplatforminputcontextplugin', 'himeplatforminputcontextplugin', 'liblzma.a', 'libopenal.a', 'libavformat.a', 'libavcodec.a', 'libswresample.a', 'libswscale.a', 'libavutil.a', 'libopus.a', 'libva-x11.a', 'libva-drm.a', 'libva.a', 'libvdpau.a', 'libdrm.a', 'libz.a', # '<!(pkg-config 2> /dev/null --libs <@(pkgconfig_libs))', ], 'cflags_cc': [ '-Wno-strict-overflow', ], 'ldflags': [ '-Wl,-wrap,aligned_alloc', '-Wl,-wrap,secure_getenv', '-Wl,-wrap,clock_gettime', '-Wl,--no-as-needed,-lrt', ], 'configurations': { 'Release': { 'cflags_c': [ '-Ofast', '-fno-strict-aliasing', ], 'cflags_cc': [ '-Ofast', '-fno-strict-aliasing', ], 'ldflags': [ '-Ofast', ], }, }, 'conditions': [ [ '"<!(uname -p)" == "x86_64"', { # 32 bit version can't be linked with debug info or LTO, # virtual memory exhausted :( 'cflags_c': [ '-g' ], 'cflags_cc': [ '-g' ], 'ldflags': [ '-g' ], 'configurations': { 'Release': { 'cflags_c': [ '-flto' ], 'cflags_cc': [ '-flto' ], 'ldflags': [ '-flto' ], }, }, }, { 'ldflags': [ '-Wl,-wrap,__divmoddi4', ], }], ['not_need_gtk!="True"', { 'cflags_cc': [ '<!(pkg-config 2> /dev/null --cflags appindicator-0.1)', '<!(pkg-config 2> /dev/null --cflags gtk+-2.0)', '<!(pkg-config 2> /dev/null --cflags glib-2.0)', '<!(pkg-config 2> /dev/null --cflags dee-1.0)', ], }] ], 'cmake_precompiled_header': '<(src_loc)/stdafx.h', 'cmake_precompiled_header_script': 'PrecompiledHeader.cmake', }]], } ����������������������������������tdesktop-1.2.17/Telegram/gyp/telegram_mac.gypi������������������������������������������������������0000664�0000000�0000000�00000015252�13262451251�0021316�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of Telegram Desktop, # the official desktop application for the Telegram messaging service. # # For license and copyright information please follow this link: # https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL { 'conditions': [[ 'build_mac', { 'xcode_settings': { 'GCC_PREFIX_HEADER': '<(src_loc)/stdafx.h', 'GCC_PRECOMPILE_PREFIX_HEADER': 'YES', 'INFOPLIST_FILE': '../Telegram.plist', 'CURRENT_PROJECT_VERSION': '<!(./print_version.sh)', 'ASSETCATALOG_COMPILER_APPICON_NAME': 'AppIcon', 'OTHER_LDFLAGS': [ '-lbsm', '-lm', '/usr/local/lib/liblzma.a', ], }, 'include_dirs': [ '/usr/local/include', ], 'library_dirs': [ '/usr/local/lib', ], 'configurations': { 'Debug': { 'xcode_settings': { 'GCC_OPTIMIZATION_LEVEL': '0', }, }, 'Release': { 'xcode_settings': { 'DEBUG_INFORMATION_FORMAT': 'dwarf-with-dsym', 'LLVM_LTO': 'YES', 'GCC_OPTIMIZATION_LEVEL': 'fast', }, }, }, 'mac_bundle': '1', 'mac_bundle_resources': [ '<!@(python -c "for s in \'<@(langpacks)\'.split(\' \'): print(\'<(res_loc)/langs/\' + s + \'.lproj/Localizable.strings\')")', '../Telegram/Images.xcassets', ], }], [ 'build_macold', { 'xcode_settings': { 'PRODUCT_BUNDLE_IDENTIFIER': 'com.tdesktop.Telegram', 'OTHER_CPLUSPLUSFLAGS': [ '-nostdinc++' ], 'OTHER_LDFLAGS': [ '-lbase', '-lcrashpad_client', '-lcrashpad_util', '/usr/local/macold/lib/libz.a', '/usr/local/macold/lib/libopus.a', '/usr/local/macold/lib/libopenal.a', '/usr/local/macold/lib/libiconv.a', '/usr/local/macold/lib/libavcodec.a', '/usr/local/macold/lib/libavformat.a', '/usr/local/macold/lib/libavutil.a', '/usr/local/macold/lib/libswscale.a', '/usr/local/macold/lib/libswresample.a', '/usr/local/macold/lib/libexif.a', '/usr/local/macold/lib/libc++.a', '/usr/local/macold/lib/libc++abi.a', '<(libs_loc)/macold/openssl/libssl.a', '<(libs_loc)/macold/openssl/libcrypto.a', ], }, 'include_dirs': [ '/usr/local/macold', '/usr/local/macold/include/c++/v1', '<(libs_loc)/macold/openssl/include', '<(libs_loc)/macold/libexif-0.6.20', '<(libs_loc)/macold/crashpad', '<(libs_loc)/macold/crashpad/third_party/mini_chromium/mini_chromium', ], 'configurations': { 'Debug': { 'library_dirs': [ '<(libs_loc)/macold/crashpad/out/Debug', ], }, 'Release': { 'library_dirs': [ '<(libs_loc)/macold/crashpad/out/Release', ], }, }, 'postbuilds': [{ 'postbuild_name': 'Force Frameworks path', 'action': [ 'mkdir', '-p', '${BUILT_PRODUCTS_DIR}/Telegram.app/Contents/Frameworks/' ], }, { 'postbuild_name': 'Copy Updater to Frameworks', 'action': [ 'cp', '${BUILT_PRODUCTS_DIR}/Updater', '${BUILT_PRODUCTS_DIR}/Telegram.app/Contents/Frameworks/', ], }, { 'postbuild_name': 'Force Helpers path', 'action': [ 'mkdir', '-p', '${BUILT_PRODUCTS_DIR}/Telegram.app/Contents/Helpers/' ], }, { 'postbuild_name': 'Copy crashpad_handler to Helpers', 'action': [ 'cp', '<(libs_loc)/macold/crashpad/out/${CONFIGURATION}/crashpad_handler', '${BUILT_PRODUCTS_DIR}/Telegram.app/Contents/Helpers/', ], }], }, { 'xcode_settings': { 'OTHER_LDFLAGS': [ '/usr/local/lib/libz.a', '/usr/local/lib/libopus.a', '/usr/local/lib/libopenal.a', '/usr/local/lib/libiconv.a', '/usr/local/lib/libavcodec.a', '/usr/local/lib/libavformat.a', '/usr/local/lib/libavutil.a', '/usr/local/lib/libswscale.a', '/usr/local/lib/libswresample.a', '<(libs_loc)/openssl/libssl.a', '<(libs_loc)/openssl/libcrypto.a', ], }, 'include_dirs': [ '<(libs_loc)/crashpad', '<(libs_loc)/crashpad/third_party/mini_chromium/mini_chromium', '<(libs_loc)/openssl/include' ], 'configurations': { 'Debug': { 'library_dirs': [ '<(libs_loc)/crashpad/out/Debug', ], }, 'Release': { 'library_dirs': [ '<(libs_loc)/crashpad/out/Release', ], }, }, }], [ '"<(build_macold)" != "1" and "<(build_macstore)" != "1"', { 'xcode_settings': { 'PRODUCT_BUNDLE_IDENTIFIER': 'com.tdesktop.Telegram', 'OTHER_LDFLAGS': [ '-lbase', '-lcrashpad_client', '-lcrashpad_util', ], }, 'postbuilds': [{ 'postbuild_name': 'Force Frameworks path', 'action': [ 'mkdir', '-p', '${BUILT_PRODUCTS_DIR}/Telegram.app/Contents/Frameworks/' ], }, { 'postbuild_name': 'Copy Updater to Frameworks', 'action': [ 'cp', '${BUILT_PRODUCTS_DIR}/Updater', '${BUILT_PRODUCTS_DIR}/Telegram.app/Contents/Frameworks/', ], }, { 'postbuild_name': 'Force Helpers path', 'action': [ 'mkdir', '-p', '${BUILT_PRODUCTS_DIR}/Telegram.app/Contents/Helpers/' ], }, { 'postbuild_name': 'Copy crashpad_client to Helpers', 'action': [ 'cp', '<(libs_loc)/crashpad/out/${CONFIGURATION}/crashpad_handler', '${BUILT_PRODUCTS_DIR}/Telegram.app/Contents/Helpers/', ], }], }], [ 'build_macstore', { 'xcode_settings': { 'PRODUCT_BUNDLE_IDENTIFIER': 'org.telegram.desktop', 'OTHER_LDFLAGS': [ '-framework', 'Breakpad', ], 'FRAMEWORK_SEARCH_PATHS': [ '<(libs_loc)/breakpad/src/client/mac/build/Release', ], }, 'mac_sandbox': 1, 'mac_sandbox_development_team': '6N38VWS5BX', 'product_name': 'Telegram Desktop', 'sources': [ '../Telegram/Telegram Desktop.entitlements', ], 'defines': [ 'TDESKTOP_DISABLE_AUTOUPDATE', 'OS_MAC_STORE', ], 'postbuilds': [{ 'postbuild_name': 'Clear Frameworks path', 'action': [ 'rm', '-rf', '${BUILT_PRODUCTS_DIR}/Telegram Desktop.app/Contents/Frameworks' ], }, { 'postbuild_name': 'Force Frameworks path', 'action': [ 'mkdir', '-p', '${BUILT_PRODUCTS_DIR}/Telegram Desktop.app/Contents/Frameworks/' ], }, { 'postbuild_name': 'Copy Breakpad.framework to Frameworks', 'action': [ 'cp', '-a', '<(libs_loc)/breakpad/src/client/mac/build/Release/Breakpad.framework', '${BUILT_PRODUCTS_DIR}/Telegram Desktop.app/Contents/Frameworks/Breakpad.framework', ], }] }]], } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/gyp/telegram_qrc.gypi������������������������������������������������������0000664�0000000�0000000�00000001614�13262451251�0021340�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of Telegram Desktop, # the official desktop application for the Telegram messaging service. # # For license and copyright information please follow this link: # https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL { 'variables': { 'qrc_files': [ '<(res_loc)/qrc/telegram.qrc', '<(res_loc)/qrc/telegram_emoji.qrc', '<(res_loc)/qrc/telegram_emoji_large.qrc', '<(res_loc)/qrc/telegram_sounds.qrc', ], }, 'conditions': [ [ 'build_linux', { 'variables': { 'qrc_files': [ '<(res_loc)/qrc/telegram_linux.qrc', ], } }], [ 'build_mac', { 'variables': { 'qrc_files': [ '<(res_loc)/qrc/telegram_mac.qrc', ], }, }], [ 'build_win', { 'variables': { 'qrc_files': [ '<(res_loc)/qrc/telegram_wnd.qrc', ], } }], ], } ��������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/gyp/telegram_sources.txt���������������������������������������������������0000664�0000000�0000000�00000072373�13262451251�0022117�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<(src_loc)/base/algorithm.h <(src_loc)/base/assertion.h <(src_loc)/base/build_config.h <(src_loc)/base/flags.h <(src_loc)/base/enum_mask.h <(src_loc)/base/flat_map.h <(src_loc)/base/flat_set.h <(src_loc)/base/functors.h <(src_loc)/base/lambda.h <(src_loc)/base/lambda_guard.h <(src_loc)/base/index_based_iterator.h <(src_loc)/base/observer.cpp <(src_loc)/base/observer.h <(src_loc)/base/ordered_set.h <(src_loc)/base/openssl_help.h <(src_loc)/base/optional.h <(src_loc)/base/overload.h <(src_loc)/base/parse_helper.cpp <(src_loc)/base/parse_helper.h <(src_loc)/base/qthelp_regex.h <(src_loc)/base/qthelp_url.cpp <(src_loc)/base/qthelp_url.h <(src_loc)/base/runtime_composer.cpp <(src_loc)/base/runtime_composer.h <(src_loc)/base/timer.cpp <(src_loc)/base/timer.h <(src_loc)/base/type_traits.h <(src_loc)/base/unique_any.h <(src_loc)/base/unique_function.h <(src_loc)/base/unique_qptr.h <(src_loc)/base/value_ordering.h <(src_loc)/base/variant.h <(src_loc)/base/virtual_method.h <(src_loc)/base/weak_ptr.h <(src_loc)/base/zlib_help.h <(src_loc)/boxes/peers/edit_peer_info_box.cpp <(src_loc)/boxes/peers/edit_peer_info_box.h <(src_loc)/boxes/peers/manage_peer_box.cpp <(src_loc)/boxes/peers/manage_peer_box.h <(src_loc)/boxes/about_box.cpp <(src_loc)/boxes/about_box.h <(src_loc)/boxes/abstract_box.cpp <(src_loc)/boxes/abstract_box.h <(src_loc)/boxes/add_contact_box.cpp <(src_loc)/boxes/add_contact_box.h <(src_loc)/boxes/autolock_box.cpp <(src_loc)/boxes/autolock_box.h <(src_loc)/boxes/background_box.cpp <(src_loc)/boxes/background_box.h <(src_loc)/boxes/calendar_box.cpp <(src_loc)/boxes/calendar_box.h <(src_loc)/boxes/change_phone_box.cpp <(src_loc)/boxes/change_phone_box.h <(src_loc)/boxes/confirm_box.cpp <(src_loc)/boxes/confirm_box.h <(src_loc)/boxes/confirm_phone_box.cpp <(src_loc)/boxes/confirm_phone_box.h <(src_loc)/boxes/connection_box.cpp <(src_loc)/boxes/connection_box.h <(src_loc)/boxes/download_path_box.cpp <(src_loc)/boxes/download_path_box.h <(src_loc)/boxes/edit_caption_box.cpp <(src_loc)/boxes/edit_caption_box.h <(src_loc)/boxes/edit_color_box.cpp <(src_loc)/boxes/edit_color_box.h <(src_loc)/boxes/edit_participant_box.cpp <(src_loc)/boxes/edit_participant_box.h <(src_loc)/boxes/edit_privacy_box.cpp <(src_loc)/boxes/edit_privacy_box.h <(src_loc)/boxes/language_box.cpp <(src_loc)/boxes/language_box.h <(src_loc)/boxes/local_storage_box.cpp <(src_loc)/boxes/local_storage_box.h <(src_loc)/boxes/mute_settings_box.cpp <(src_loc)/boxes/mute_settings_box.h <(src_loc)/boxes/notifications_box.cpp <(src_loc)/boxes/notifications_box.h <(src_loc)/boxes/peer_list_box.cpp <(src_loc)/boxes/peer_list_box.h <(src_loc)/boxes/peer_list_controllers.cpp <(src_loc)/boxes/peer_list_controllers.h <(src_loc)/boxes/passcode_box.cpp <(src_loc)/boxes/passcode_box.h <(src_loc)/boxes/photo_crop_box.cpp <(src_loc)/boxes/photo_crop_box.h <(src_loc)/boxes/rate_call_box.cpp <(src_loc)/boxes/rate_call_box.h <(src_loc)/boxes/report_box.cpp <(src_loc)/boxes/report_box.h <(src_loc)/boxes/self_destruction_box.cpp <(src_loc)/boxes/self_destruction_box.h <(src_loc)/boxes/send_files_box.cpp <(src_loc)/boxes/send_files_box.h <(src_loc)/boxes/sessions_box.cpp <(src_loc)/boxes/sessions_box.h <(src_loc)/boxes/share_box.cpp <(src_loc)/boxes/share_box.h <(src_loc)/boxes/sticker_set_box.cpp <(src_loc)/boxes/sticker_set_box.h <(src_loc)/boxes/stickers_box.cpp <(src_loc)/boxes/stickers_box.h <(src_loc)/boxes/username_box.cpp <(src_loc)/boxes/username_box.h <(src_loc)/calls/calls_box_controller.cpp <(src_loc)/calls/calls_box_controller.h <(src_loc)/calls/calls_call.cpp <(src_loc)/calls/calls_call.h <(src_loc)/calls/calls_emoji_fingerprint.cpp <(src_loc)/calls/calls_emoji_fingerprint.h <(src_loc)/calls/calls_instance.cpp <(src_loc)/calls/calls_instance.h <(src_loc)/calls/calls_panel.cpp <(src_loc)/calls/calls_panel.h <(src_loc)/calls/calls_top_bar.cpp <(src_loc)/calls/calls_top_bar.h <(src_loc)/chat_helpers/bot_keyboard.cpp <(src_loc)/chat_helpers/bot_keyboard.h <(src_loc)/chat_helpers/emoji_list_widget.cpp <(src_loc)/chat_helpers/emoji_list_widget.h <(src_loc)/chat_helpers/emoji_suggestions_helper.h <(src_loc)/chat_helpers/emoji_suggestions_widget.cpp <(src_loc)/chat_helpers/emoji_suggestions_widget.h <(src_loc)/chat_helpers/field_autocomplete.cpp <(src_loc)/chat_helpers/field_autocomplete.h <(src_loc)/chat_helpers/gifs_list_widget.cpp <(src_loc)/chat_helpers/gifs_list_widget.h <(src_loc)/chat_helpers/message_field.cpp <(src_loc)/chat_helpers/message_field.h <(src_loc)/chat_helpers/stickers.cpp <(src_loc)/chat_helpers/stickers.h <(src_loc)/chat_helpers/stickers_list_widget.cpp <(src_loc)/chat_helpers/stickers_list_widget.h <(src_loc)/chat_helpers/tabbed_panel.cpp <(src_loc)/chat_helpers/tabbed_panel.h <(src_loc)/chat_helpers/tabbed_section.cpp <(src_loc)/chat_helpers/tabbed_section.h <(src_loc)/chat_helpers/tabbed_selector.cpp <(src_loc)/chat_helpers/tabbed_selector.h <(src_loc)/core/basic_types.h <(src_loc)/core/changelogs.cpp <(src_loc)/core/changelogs.h <(src_loc)/core/click_handler.cpp <(src_loc)/core/click_handler.h <(src_loc)/core/click_handler_types.cpp <(src_loc)/core/click_handler_types.h <(src_loc)/core/crash_report_window.cpp <(src_loc)/core/crash_report_window.h <(src_loc)/core/crash_reports.cpp <(src_loc)/core/crash_reports.h <(src_loc)/core/event_filter.cpp <(src_loc)/core/event_filter.h <(src_loc)/core/file_utilities.cpp <(src_loc)/core/file_utilities.h <(src_loc)/core/launcher.cpp <(src_loc)/core/launcher.h <(src_loc)/core/main_queue_processor.cpp <(src_loc)/core/main_queue_processor.h <(src_loc)/core/single_timer.cpp <(src_loc)/core/single_timer.h <(src_loc)/core/tl_help.h <(src_loc)/core/utils.cpp <(src_loc)/core/utils.h <(src_loc)/core/version.h <(src_loc)/data/data_abstract_structure.cpp <(src_loc)/data/data_abstract_structure.h <(src_loc)/data/data_channel_admins.cpp <(src_loc)/data/data_channel_admins.h <(src_loc)/data/data_document.cpp <(src_loc)/data/data_document.h <(src_loc)/data/data_drafts.cpp <(src_loc)/data/data_drafts.h <(src_loc)/data/data_feed.cpp <(src_loc)/data/data_feed.h <(src_loc)/data/data_feed_messages.cpp <(src_loc)/data/data_feed_messages.h <(src_loc)/data/data_flags.h <(src_loc)/data/data_game.h <(src_loc)/data/data_groups.cpp <(src_loc)/data/data_groups.h <(src_loc)/data/data_media_types.cpp <(src_loc)/data/data_media_types.h <(src_loc)/data/data_messages.cpp <(src_loc)/data/data_messages.h <(src_loc)/data/data_notify_settings.cpp <(src_loc)/data/data_notify_settings.h <(src_loc)/data/data_peer.cpp <(src_loc)/data/data_peer.h <(src_loc)/data/data_peer_values.cpp <(src_loc)/data/data_peer_values.h <(src_loc)/data/data_photo.cpp <(src_loc)/data/data_photo.h <(src_loc)/data/data_search_controller.cpp <(src_loc)/data/data_search_controller.h <(src_loc)/data/data_session.cpp <(src_loc)/data/data_session.h <(src_loc)/data/data_shared_media.cpp <(src_loc)/data/data_shared_media.h <(src_loc)/data/data_sparse_ids.cpp <(src_loc)/data/data_sparse_ids.h <(src_loc)/data/data_types.cpp <(src_loc)/data/data_types.h <(src_loc)/data/data_user_photos.cpp <(src_loc)/data/data_user_photos.h <(src_loc)/data/data_web_page.cpp <(src_loc)/data/data_web_page.h <(src_loc)/dialogs/dialogs_entry.cpp <(src_loc)/dialogs/dialogs_entry.h <(src_loc)/dialogs/dialogs_indexed_list.cpp <(src_loc)/dialogs/dialogs_indexed_list.h <(src_loc)/dialogs/dialogs_inner_widget.cpp <(src_loc)/dialogs/dialogs_inner_widget.h <(src_loc)/dialogs/dialogs_key.cpp <(src_loc)/dialogs/dialogs_key.h <(src_loc)/dialogs/dialogs_layout.cpp <(src_loc)/dialogs/dialogs_layout.h <(src_loc)/dialogs/dialogs_list.cpp <(src_loc)/dialogs/dialogs_list.h <(src_loc)/dialogs/dialogs_row.cpp <(src_loc)/dialogs/dialogs_row.h <(src_loc)/dialogs/dialogs_search_from_controllers.cpp <(src_loc)/dialogs/dialogs_search_from_controllers.h <(src_loc)/dialogs/dialogs_widget.cpp <(src_loc)/dialogs/dialogs_widget.h <(src_loc)/history/admin_log/history_admin_log_filter.cpp <(src_loc)/history/admin_log/history_admin_log_filter.h <(src_loc)/history/admin_log/history_admin_log_inner.cpp <(src_loc)/history/admin_log/history_admin_log_inner.h <(src_loc)/history/admin_log/history_admin_log_item.cpp <(src_loc)/history/admin_log/history_admin_log_item.h <(src_loc)/history/admin_log/history_admin_log_section.cpp <(src_loc)/history/admin_log/history_admin_log_section.h <(src_loc)/history/feed/history_feed_section.cpp <(src_loc)/history/feed/history_feed_section.h <(src_loc)/history/view/history_view_context_menu.cpp <(src_loc)/history/view/history_view_context_menu.h <(src_loc)/history/view/history_view_cursor_state.cpp <(src_loc)/history/view/history_view_cursor_state.h <(src_loc)/history/view/history_view_element.cpp <(src_loc)/history/view/history_view_element.h <(src_loc)/history/view/history_view_list_widget.cpp <(src_loc)/history/view/history_view_list_widget.h <(src_loc)/history/view/history_view_message.cpp <(src_loc)/history/view/history_view_message.h <(src_loc)/history/view/history_view_object.h <(src_loc)/history/view/history_view_service_message.cpp <(src_loc)/history/view/history_view_service_message.h <(src_loc)/history/view/history_view_top_bar_widget.cpp <(src_loc)/history/view/history_view_top_bar_widget.h <(src_loc)/history/history.cpp <(src_loc)/history/history.h <(src_loc)/history/history_drag_area.cpp <(src_loc)/history/history_drag_area.h <(src_loc)/history/history_item.cpp <(src_loc)/history/history_item.h <(src_loc)/history/history_item_components.cpp <(src_loc)/history/history_item_components.h <(src_loc)/history/history_item_text.cpp <(src_loc)/history/history_item_text.h <(src_loc)/history/history_inner_widget.cpp <(src_loc)/history/history_inner_widget.h <(src_loc)/history/history_location_manager.cpp <(src_loc)/history/history_location_manager.h <(src_loc)/history/history_media.h <(src_loc)/history/history_media.cpp <(src_loc)/history/history_media_grouped.h <(src_loc)/history/history_media_grouped.cpp <(src_loc)/history/history_media_types.cpp <(src_loc)/history/history_media_types.h <(src_loc)/history/history_message.cpp <(src_loc)/history/history_message.h <(src_loc)/history/history_service.cpp <(src_loc)/history/history_service.h <(src_loc)/history/history_widget.cpp <(src_loc)/history/history_widget.h <(src_loc)/info/info_content_widget.cpp <(src_loc)/info/info_content_widget.h <(src_loc)/info/info_controller.cpp <(src_loc)/info/info_controller.h <(src_loc)/info/info_layer_widget.cpp <(src_loc)/info/info_layer_widget.h <(src_loc)/info/info_memento.cpp <(src_loc)/info/info_memento.h <(src_loc)/info/info_section_widget.cpp <(src_loc)/info/info_section_widget.h <(src_loc)/info/info_top_bar.cpp <(src_loc)/info/info_top_bar.h <(src_loc)/info/info_wrap_widget.cpp <(src_loc)/info/info_wrap_widget.h <(src_loc)/info/channels/info_channels_widget.cpp <(src_loc)/info/channels/info_channels_widget.h <(src_loc)/info/common_groups/info_common_groups_inner_widget.cpp <(src_loc)/info/common_groups/info_common_groups_inner_widget.h <(src_loc)/info/common_groups/info_common_groups_widget.cpp <(src_loc)/info/common_groups/info_common_groups_widget.h <(src_loc)/info/feed/info_feed_channels.cpp <(src_loc)/info/feed/info_feed_channels.h <(src_loc)/info/feed/info_feed_channels_controllers.cpp <(src_loc)/info/feed/info_feed_channels_controllers.h <(src_loc)/info/feed/info_feed_cover.cpp <(src_loc)/info/feed/info_feed_cover.h <(src_loc)/info/feed/info_feed_profile_inner_widget.cpp <(src_loc)/info/feed/info_feed_profile_inner_widget.h <(src_loc)/info/feed/info_feed_profile_widget.cpp <(src_loc)/info/feed/info_feed_profile_widget.h <(src_loc)/info/media/info_media_buttons.h <(src_loc)/info/media/info_media_empty_widget.cpp <(src_loc)/info/media/info_media_empty_widget.h <(src_loc)/info/media/info_media_inner_widget.cpp <(src_loc)/info/media/info_media_inner_widget.h <(src_loc)/info/media/info_media_list_widget.cpp <(src_loc)/info/media/info_media_list_widget.h <(src_loc)/info/media/info_media_widget.cpp <(src_loc)/info/media/info_media_widget.h <(src_loc)/info/members/info_members_widget.cpp <(src_loc)/info/members/info_members_widget.h <(src_loc)/info/profile/info_profile_actions.cpp <(src_loc)/info/profile/info_profile_actions.h <(src_loc)/info/profile/info_profile_button.cpp <(src_loc)/info/profile/info_profile_button.h <(src_loc)/info/profile/info_profile_cover.cpp <(src_loc)/info/profile/info_profile_cover.h <(src_loc)/info/profile/info_profile_icon.cpp <(src_loc)/info/profile/info_profile_icon.h <(src_loc)/info/profile/info_profile_inner_widget.cpp <(src_loc)/info/profile/info_profile_inner_widget.h <(src_loc)/info/profile/info_profile_members.cpp <(src_loc)/info/profile/info_profile_members.h <(src_loc)/info/profile/info_profile_members_controllers.cpp <(src_loc)/info/profile/info_profile_members_controllers.h <(src_loc)/info/profile/info_profile_text.cpp <(src_loc)/info/profile/info_profile_text.h <(src_loc)/info/profile/info_profile_values.cpp <(src_loc)/info/profile/info_profile_values.h <(src_loc)/info/profile/info_profile_widget.cpp <(src_loc)/info/profile/info_profile_widget.h <(src_loc)/inline_bots/inline_bot_layout_internal.cpp <(src_loc)/inline_bots/inline_bot_layout_internal.h <(src_loc)/inline_bots/inline_bot_layout_item.cpp <(src_loc)/inline_bots/inline_bot_layout_item.h <(src_loc)/inline_bots/inline_bot_result.cpp <(src_loc)/inline_bots/inline_bot_result.h <(src_loc)/inline_bots/inline_bot_send_data.cpp <(src_loc)/inline_bots/inline_bot_send_data.h <(src_loc)/inline_bots/inline_results_widget.cpp <(src_loc)/inline_bots/inline_results_widget.h <(src_loc)/intro/introwidget.cpp <(src_loc)/intro/introwidget.h <(src_loc)/intro/introcode.cpp <(src_loc)/intro/introcode.h <(src_loc)/intro/introphone.cpp <(src_loc)/intro/introphone.h <(src_loc)/intro/intropwdcheck.cpp <(src_loc)/intro/intropwdcheck.h <(src_loc)/intro/introsignup.cpp <(src_loc)/intro/introsignup.h <(src_loc)/intro/introstart.cpp <(src_loc)/intro/introstart.h <(src_loc)/lang/lang_cloud_manager.cpp <(src_loc)/lang/lang_cloud_manager.h <(src_loc)/lang/lang_file_parser.cpp <(src_loc)/lang/lang_file_parser.h <(src_loc)/lang/lang_hardcoded.h <(src_loc)/lang/lang_instance.cpp <(src_loc)/lang/lang_instance.h <(src_loc)/lang/lang_keys.cpp <(src_loc)/lang/lang_keys.h <(src_loc)/lang/lang_tag.cpp <(src_loc)/lang/lang_tag.h <(src_loc)/lang/lang_translator.cpp <(src_loc)/lang/lang_translator.h <(src_loc)/media/player/media_player_button.cpp <(src_loc)/media/player/media_player_button.h <(src_loc)/media/player/media_player_cover.cpp <(src_loc)/media/player/media_player_cover.h <(src_loc)/media/player/media_player_float.cpp <(src_loc)/media/player/media_player_float.h <(src_loc)/media/player/media_player_instance.cpp <(src_loc)/media/player/media_player_instance.h <(src_loc)/media/player/media_player_panel.cpp <(src_loc)/media/player/media_player_panel.h <(src_loc)/media/player/media_player_round_controller.cpp <(src_loc)/media/player/media_player_round_controller.h <(src_loc)/media/player/media_player_volume_controller.cpp <(src_loc)/media/player/media_player_volume_controller.h <(src_loc)/media/player/media_player_widget.cpp <(src_loc)/media/player/media_player_widget.h <(src_loc)/media/view/media_clip_controller.cpp <(src_loc)/media/view/media_clip_controller.h <(src_loc)/media/view/media_clip_playback.cpp <(src_loc)/media/view/media_clip_playback.h <(src_loc)/media/view/media_clip_volume_controller.cpp <(src_loc)/media/view/media_clip_volume_controller.h <(src_loc)/media/view/media_view_group_thumbs.cpp <(src_loc)/media/view/media_view_group_thumbs.h <(src_loc)/media/media_audio.cpp <(src_loc)/media/media_audio.h <(src_loc)/media/media_audio_capture.cpp <(src_loc)/media/media_audio_capture.h <(src_loc)/media/media_audio_ffmpeg_loader.cpp <(src_loc)/media/media_audio_ffmpeg_loader.h <(src_loc)/media/media_audio_loader.cpp <(src_loc)/media/media_audio_loader.h <(src_loc)/media/media_audio_loaders.cpp <(src_loc)/media/media_audio_loaders.h <(src_loc)/media/media_audio_track.cpp <(src_loc)/media/media_audio_track.h <(src_loc)/media/media_child_ffmpeg_loader.cpp <(src_loc)/media/media_child_ffmpeg_loader.h <(src_loc)/media/media_clip_ffmpeg.cpp <(src_loc)/media/media_clip_ffmpeg.h <(src_loc)/media/media_clip_implementation.cpp <(src_loc)/media/media_clip_implementation.h <(src_loc)/media/media_clip_qtgif.cpp <(src_loc)/media/media_clip_qtgif.h <(src_loc)/media/media_clip_reader.cpp <(src_loc)/media/media_clip_reader.h <(src_loc)/mtproto/auth_key.cpp <(src_loc)/mtproto/auth_key.h <(src_loc)/mtproto/config_loader.cpp <(src_loc)/mtproto/config_loader.h <(src_loc)/mtproto/connection.cpp <(src_loc)/mtproto/connection.h <(src_loc)/mtproto/connection_abstract.cpp <(src_loc)/mtproto/connection_abstract.h <(src_loc)/mtproto/connection_auto.cpp <(src_loc)/mtproto/connection_auto.h <(src_loc)/mtproto/connection_http.cpp <(src_loc)/mtproto/connection_http.h <(src_loc)/mtproto/connection_tcp.cpp <(src_loc)/mtproto/connection_tcp.h <(src_loc)/mtproto/core_types.cpp <(src_loc)/mtproto/core_types.h <(src_loc)/mtproto/dcenter.cpp <(src_loc)/mtproto/dcenter.h <(src_loc)/mtproto/dc_options.cpp <(src_loc)/mtproto/dc_options.h <(src_loc)/mtproto/facade.cpp <(src_loc)/mtproto/facade.h <(src_loc)/mtproto/mtp_instance.cpp <(src_loc)/mtproto/mtp_instance.h <(src_loc)/mtproto/rsa_public_key.cpp <(src_loc)/mtproto/rsa_public_key.h <(src_loc)/mtproto/rpc_sender.cpp <(src_loc)/mtproto/rpc_sender.h <(src_loc)/mtproto/sender.h <(src_loc)/mtproto/session.cpp <(src_loc)/mtproto/session.h <(src_loc)/mtproto/special_config_request.cpp <(src_loc)/mtproto/special_config_request.h <(src_loc)/mtproto/type_utils.cpp <(src_loc)/mtproto/type_utils.h <(src_loc)/overview/overview_layout.cpp <(src_loc)/overview/overview_layout.h <(src_loc)/platform/linux/linux_desktop_environment.cpp <(src_loc)/platform/linux/linux_desktop_environment.h <(src_loc)/platform/linux/linux_gdk_helper.cpp <(src_loc)/platform/linux/linux_gdk_helper.h <(src_loc)/platform/linux/linux_libnotify.cpp <(src_loc)/platform/linux/linux_libnotify.h <(src_loc)/platform/linux/linux_libs.cpp <(src_loc)/platform/linux/linux_libs.h <(src_loc)/platform/linux/file_utilities_linux.cpp <(src_loc)/platform/linux/file_utilities_linux.h <(src_loc)/platform/linux/launcher_linux.cpp <(src_loc)/platform/linux/launcher_linux.h <(src_loc)/platform/linux/main_window_linux.cpp <(src_loc)/platform/linux/main_window_linux.h <(src_loc)/platform/linux/notifications_manager_linux.cpp <(src_loc)/platform/linux/notifications_manager_linux.h <(src_loc)/platform/linux/specific_linux.cpp <(src_loc)/platform/linux/specific_linux.h <(src_loc)/platform/mac/file_utilities_mac.mm <(src_loc)/platform/mac/file_utilities_mac.h <(src_loc)/platform/mac/launcher_mac.mm <(src_loc)/platform/mac/launcher_mac.h <(src_loc)/platform/mac/mac_iconv_helper.c <(src_loc)/platform/mac/mac_utilities.mm <(src_loc)/platform/mac/mac_utilities.h <(src_loc)/platform/mac/main_window_mac.mm <(src_loc)/platform/mac/main_window_mac.h <(src_loc)/platform/mac/notifications_manager_mac.mm <(src_loc)/platform/mac/notifications_manager_mac.h <(src_loc)/platform/mac/specific_mac.mm <(src_loc)/platform/mac/specific_mac.h <(src_loc)/platform/mac/specific_mac_p.mm <(src_loc)/platform/mac/specific_mac_p.h <(src_loc)/platform/mac/window_title_mac.mm <(src_loc)/platform/mac/window_title_mac.h <(src_loc)/platform/win/audio_win.cpp <(src_loc)/platform/win/audio_win.h <(src_loc)/platform/win/file_utilities_win.cpp <(src_loc)/platform/win/file_utilities_win.h <(src_loc)/platform/win/launcher_win.cpp <(src_loc)/platform/win/launcher_win.h <(src_loc)/platform/win/main_window_win.cpp <(src_loc)/platform/win/main_window_win.h <(src_loc)/platform/win/notifications_manager_win.cpp <(src_loc)/platform/win/notifications_manager_win.h <(src_loc)/platform/win/specific_win.cpp <(src_loc)/platform/win/specific_win.h <(src_loc)/platform/win/window_title_win.cpp <(src_loc)/platform/win/window_title_win.h <(src_loc)/platform/win/windows_app_user_model_id.cpp <(src_loc)/platform/win/windows_app_user_model_id.h <(src_loc)/platform/win/windows_dlls.cpp <(src_loc)/platform/win/windows_dlls.h <(src_loc)/platform/win/windows_event_filter.cpp <(src_loc)/platform/win/windows_event_filter.h <(src_loc)/platform/win/windows_range_v3_helpers.h <(src_loc)/platform/platform_audio.h <(src_loc)/platform/platform_file_utilities.h <(src_loc)/platform/platform_launcher.h <(src_loc)/platform/platform_main_window.h <(src_loc)/platform/platform_notifications_manager.h <(src_loc)/platform/platform_specific.h <(src_loc)/platform/platform_window_title.h <(src_loc)/profile/profile_back_button.cpp <(src_loc)/profile/profile_back_button.h <(src_loc)/profile/profile_block_group_members.cpp <(src_loc)/profile/profile_block_group_members.h <(src_loc)/profile/profile_block_peer_list.cpp <(src_loc)/profile/profile_block_peer_list.h <(src_loc)/profile/profile_block_widget.cpp <(src_loc)/profile/profile_block_widget.h <(src_loc)/profile/profile_channel_controllers.cpp <(src_loc)/profile/profile_channel_controllers.h <(src_loc)/profile/profile_cover_drop_area.cpp <(src_loc)/profile/profile_cover_drop_area.h <(src_loc)/settings/settings_advanced_widget.cpp <(src_loc)/settings/settings_advanced_widget.h <(src_loc)/settings/settings_background_widget.cpp <(src_loc)/settings/settings_background_widget.h <(src_loc)/settings/settings_block_widget.cpp <(src_loc)/settings/settings_block_widget.h <(src_loc)/settings/settings_chat_settings_widget.cpp <(src_loc)/settings/settings_chat_settings_widget.h <(src_loc)/settings/settings_cover.cpp <(src_loc)/settings/settings_cover.h <(src_loc)/settings/settings_fixed_bar.cpp <(src_loc)/settings/settings_fixed_bar.h <(src_loc)/settings/settings_general_widget.cpp <(src_loc)/settings/settings_general_widget.h <(src_loc)/settings/settings_info_widget.cpp <(src_loc)/settings/settings_info_widget.h <(src_loc)/settings/settings_inner_widget.cpp <(src_loc)/settings/settings_inner_widget.h <(src_loc)/settings/settings_layer.cpp <(src_loc)/settings/settings_layer.h <(src_loc)/settings/settings_notifications_widget.cpp <(src_loc)/settings/settings_notifications_widget.h <(src_loc)/settings/settings_privacy_controllers.cpp <(src_loc)/settings/settings_privacy_controllers.h <(src_loc)/settings/settings_privacy_widget.cpp <(src_loc)/settings/settings_privacy_widget.h <(src_loc)/settings/settings_scale_widget.cpp <(src_loc)/settings/settings_scale_widget.h <(src_loc)/settings/settings_widget.cpp <(src_loc)/settings/settings_widget.h <(src_loc)/storage/file_download.cpp <(src_loc)/storage/file_download.h <(src_loc)/storage/file_upload.cpp <(src_loc)/storage/file_upload.h <(src_loc)/storage/localimageloader.cpp <(src_loc)/storage/localimageloader.h <(src_loc)/storage/localstorage.cpp <(src_loc)/storage/localstorage.h <(src_loc)/storage/serialize_common.cpp <(src_loc)/storage/serialize_common.h <(src_loc)/storage/serialize_document.cpp <(src_loc)/storage/serialize_document.h <(src_loc)/storage/storage_facade.cpp <(src_loc)/storage/storage_facade.h <(src_loc)/storage/storage_feed_messages.cpp <(src_loc)/storage/storage_feed_messages.h <(src_loc)/storage/storage_media_prepare.cpp <(src_loc)/storage/storage_media_prepare.h <(src_loc)/storage/storage_shared_media.cpp <(src_loc)/storage/storage_shared_media.h <(src_loc)/storage/storage_sparse_ids_list.cpp <(src_loc)/storage/storage_sparse_ids_list.h <(src_loc)/storage/storage_user_photos.cpp <(src_loc)/storage/storage_user_photos.h <(src_loc)/ui/effects/cross_animation.cpp <(src_loc)/ui/effects/cross_animation.h <(src_loc)/ui/effects/fade_animation.cpp <(src_loc)/ui/effects/fade_animation.h <(src_loc)/ui/effects/numbers_animation.cpp <(src_loc)/ui/effects/numbers_animation.h <(src_loc)/ui/effects/panel_animation.cpp <(src_loc)/ui/effects/panel_animation.h <(src_loc)/ui/effects/radial_animation.cpp <(src_loc)/ui/effects/radial_animation.h <(src_loc)/ui/effects/ripple_animation.cpp <(src_loc)/ui/effects/ripple_animation.h <(src_loc)/ui/effects/round_checkbox.cpp <(src_loc)/ui/effects/round_checkbox.h <(src_loc)/ui/effects/send_action_animations.cpp <(src_loc)/ui/effects/send_action_animations.h <(src_loc)/ui/effects/slide_animation.cpp <(src_loc)/ui/effects/slide_animation.h <(src_loc)/ui/style/style_core.cpp <(src_loc)/ui/style/style_core.h <(src_loc)/ui/style/style_core_color.cpp <(src_loc)/ui/style/style_core_color.h <(src_loc)/ui/style/style_core_font.cpp <(src_loc)/ui/style/style_core_font.h <(src_loc)/ui/style/style_core_icon.cpp <(src_loc)/ui/style/style_core_icon.h <(src_loc)/ui/style/style_core_types.cpp <(src_loc)/ui/style/style_core_types.h <(src_loc)/ui/text/text.cpp <(src_loc)/ui/text/text.h <(src_loc)/ui/text/text_block.cpp <(src_loc)/ui/text/text_block.h <(src_loc)/ui/text/text_entity.cpp <(src_loc)/ui/text/text_entity.h <(src_loc)/ui/toast/toast.cpp <(src_loc)/ui/toast/toast.h <(src_loc)/ui/toast/toast_manager.cpp <(src_loc)/ui/toast/toast_manager.h <(src_loc)/ui/toast/toast_widget.cpp <(src_loc)/ui/toast/toast_widget.h <(src_loc)/ui/widgets/buttons.cpp <(src_loc)/ui/widgets/buttons.h <(src_loc)/ui/widgets/checkbox.cpp <(src_loc)/ui/widgets/checkbox.h <(src_loc)/ui/widgets/continuous_sliders.cpp <(src_loc)/ui/widgets/continuous_sliders.h <(src_loc)/ui/widgets/discrete_sliders.cpp <(src_loc)/ui/widgets/discrete_sliders.h <(src_loc)/ui/widgets/dropdown_menu.cpp <(src_loc)/ui/widgets/dropdown_menu.h <(src_loc)/ui/widgets/inner_dropdown.cpp <(src_loc)/ui/widgets/inner_dropdown.h <(src_loc)/ui/widgets/input_fields.cpp <(src_loc)/ui/widgets/input_fields.h <(src_loc)/ui/widgets/labels.cpp <(src_loc)/ui/widgets/labels.h <(src_loc)/ui/widgets/menu.cpp <(src_loc)/ui/widgets/menu.h <(src_loc)/ui/widgets/multi_select.cpp <(src_loc)/ui/widgets/multi_select.h <(src_loc)/ui/widgets/popup_menu.cpp <(src_loc)/ui/widgets/popup_menu.h <(src_loc)/ui/widgets/scroll_area.cpp <(src_loc)/ui/widgets/scroll_area.h <(src_loc)/ui/widgets/shadow.cpp <(src_loc)/ui/widgets/shadow.h <(src_loc)/ui/widgets/tooltip.cpp <(src_loc)/ui/widgets/tooltip.h <(src_loc)/ui/wrap/fade_wrap.cpp <(src_loc)/ui/wrap/fade_wrap.h <(src_loc)/ui/wrap/padding_wrap.cpp <(src_loc)/ui/wrap/padding_wrap.h <(src_loc)/ui/wrap/slide_wrap.cpp <(src_loc)/ui/wrap/slide_wrap.h <(src_loc)/ui/wrap/vertical_layout.cpp <(src_loc)/ui/wrap/vertical_layout.h <(src_loc)/ui/wrap/wrap.h <(src_loc)/ui/abstract_button.cpp <(src_loc)/ui/abstract_button.h <(src_loc)/ui/animation.cpp <(src_loc)/ui/animation.h <(src_loc)/ui/countryinput.cpp <(src_loc)/ui/countryinput.h <(src_loc)/ui/emoji_config.cpp <(src_loc)/ui/emoji_config.h <(src_loc)/ui/empty_userpic.cpp <(src_loc)/ui/empty_userpic.h <(src_loc)/ui/focus_persister.h <(src_loc)/ui/grouped_layout.cpp <(src_loc)/ui/grouped_layout.h <(src_loc)/ui/images.cpp <(src_loc)/ui/images.h <(src_loc)/ui/resize_area.h <(src_loc)/ui/rp_widget.cpp <(src_loc)/ui/rp_widget.h <(src_loc)/ui/search_field_controller.cpp <(src_loc)/ui/search_field_controller.h <(src_loc)/ui/special_buttons.cpp <(src_loc)/ui/special_buttons.h <(src_loc)/ui/text_options.cpp <(src_loc)/ui/text_options.h <(src_loc)/ui/twidget.cpp <(src_loc)/ui/twidget.h <(src_loc)/ui/unread_badge.cpp <(src_loc)/ui/unread_badge.h <(src_loc)/window/layer_widget.cpp <(src_loc)/window/layer_widget.h <(src_loc)/window/main_window.cpp <(src_loc)/window/main_window.h <(src_loc)/window/notifications_manager.cpp <(src_loc)/window/notifications_manager.h <(src_loc)/window/notifications_manager_default.cpp <(src_loc)/window/notifications_manager_default.h <(src_loc)/window/notifications_utilities.cpp <(src_loc)/window/notifications_utilities.h <(src_loc)/window/player_wrap_widget.cpp <(src_loc)/window/player_wrap_widget.h <(src_loc)/window/section_memento.h <(src_loc)/window/section_widget.cpp <(src_loc)/window/section_widget.h <(src_loc)/window/window_controller.cpp <(src_loc)/window/window_controller.h <(src_loc)/window/window_main_menu.cpp <(src_loc)/window/window_main_menu.h <(src_loc)/window/window_peer_menu.cpp <(src_loc)/window/window_peer_menu.h <(src_loc)/window/window_slide_animation.cpp <(src_loc)/window/window_slide_animation.h <(src_loc)/window/window_title.h <(src_loc)/window/themes/window_theme.cpp <(src_loc)/window/themes/window_theme.h <(src_loc)/window/themes/window_theme_editor.cpp <(src_loc)/window/themes/window_theme_editor.h <(src_loc)/window/themes/window_theme_editor_block.cpp <(src_loc)/window/themes/window_theme_editor_block.h <(src_loc)/window/themes/window_theme_preview.cpp <(src_loc)/window/themes/window_theme_preview.h <(src_loc)/window/themes/window_theme_warning.cpp <(src_loc)/window/themes/window_theme_warning.h <(src_loc)/apiwrap.cpp <(src_loc)/apiwrap.h <(src_loc)/app.cpp <(src_loc)/app.h <(src_loc)/application.cpp <(src_loc)/application.h <(src_loc)/auth_session.cpp <(src_loc)/auth_session.h <(src_loc)/autoupdater.cpp <(src_loc)/autoupdater.h <(src_loc)/config.h <(src_loc)/countries.h <(src_loc)/facades.cpp <(src_loc)/facades.h <(src_loc)/layout.cpp <(src_loc)/layout.h <(src_loc)/logs.cpp <(src_loc)/logs.h <(src_loc)/main.cpp <(src_loc)/mainwidget.cpp <(src_loc)/mainwidget.h <(src_loc)/mainwindow.cpp <(src_loc)/mainwindow.h <(src_loc)/mediaview.cpp <(src_loc)/mediaview.h <(src_loc)/messenger.cpp <(src_loc)/messenger.h <(src_loc)/observer_peer.cpp <(src_loc)/observer_peer.h <(src_loc)/passcodewidget.cpp <(src_loc)/passcodewidget.h <(src_loc)/qt_static_plugins.cpp <(src_loc)/settings.cpp <(src_loc)/settings.h <(src_loc)/shortcuts.cpp <(src_loc)/shortcuts.h <(src_loc)/stdafx.cpp <(src_loc)/stdafx.h <(emoji_suggestions_loc)/emoji_suggestions.cpp <(emoji_suggestions_loc)/emoji_suggestions.h platforms: !win <(minizip_loc)/crypt.h <(minizip_loc)/ioapi.c <(minizip_loc)/ioapi.h <(minizip_loc)/zip.c <(minizip_loc)/zip.h <(minizip_loc)/unzip.c <(minizip_loc)/unzip.h platforms: mac <(sp_media_key_tap_loc)/SPMediaKeyTap.m <(sp_media_key_tap_loc)/SPMediaKeyTap.h <(sp_media_key_tap_loc)/SPInvocationGrabbing/NSObject+SPInvocationGrabbing.m <(sp_media_key_tap_loc)/SPInvocationGrabbing/NSObject+SPInvocationGrabbing.h ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/gyp/telegram_win.gypi������������������������������������������������������0000664�0000000�0000000�00000004366�13262451251�0021357�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of Telegram Desktop, # the official desktop application for the Telegram messaging service. # # For license and copyright information please follow this link: # https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL { 'conditions': [[ 'build_win', { 'msvs_precompiled_source': '<(src_loc)/stdafx.cpp', 'msvs_precompiled_header': '<(src_loc)/stdafx.h', 'msbuild_toolset': 'v141', 'sources': [ '<(res_loc)/winrc/Telegram.rc', ], 'library_dirs': [ '<(libs_loc)/ffmpeg', ], 'libraries': [ '-llibeay32', '-lssleay32', '-lCrypt32', '-lzlibstat', '-lLzmaLib', '-lUxTheme', '-lDbgHelp', '-lOpenAL32', '-lcommon', '-lopus', 'windows/common', 'windows/handler/exception_handler', 'windows/crash_generation/crash_generation_client', ], 'msvs_settings': { 'VCLinkerTool': { 'AdditionalOptions': [ 'libavformat/libavformat.a', 'libavcodec/libavcodec.a', 'libavutil/libavutil.a', 'libswresample/libswresample.a', 'libswscale/libswscale.a', ], }, }, 'configurations': { 'Debug': { 'include_dirs': [ '<(libs_loc)/openssl/Debug/include', ], 'library_dirs': [ '<(libs_loc)/openssl/Debug/lib', '<(libs_loc)/lzma/C/Util/LzmaLib/Debug', '<(libs_loc)/opus/win32/VS2015/Win32/Debug', '<(libs_loc)/openal-soft/build/Debug', '<(libs_loc)/zlib/contrib/vstudio/vc14/x86/ZlibStatDebug', '<(libs_loc)/breakpad/src/out/Debug/obj/client', ], }, 'Release': { 'include_dirs': [ '<(libs_loc)/openssl/Release/include', ], 'library_dirs': [ '<(libs_loc)/openssl/Release/lib', '<(libs_loc)/lzma/C/Util/LzmaLib/Release', '<(libs_loc)/opus/win32/VS2015/Win32/Release', '<(libs_loc)/openal-soft/build/Release', '<(libs_loc)/zlib/contrib/vstudio/vc14/x86/ZlibStatReleaseWithoutAsm', '<(libs_loc)/breakpad/src/out/Release/obj/client', ], }, }, }], [ 'build_uwp', { 'defines': [ 'TDESKTOP_DISABLE_AUTOUPDATE', 'OS_WIN_STORE', ] }]], } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/gyp/tests/�����������������������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0017141�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/gyp/tests/common_test.gypi�������������������������������������������������0000664�0000000�0000000�00000001076�13262451251�0022366�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of Telegram Desktop, # the official desktop application for the Telegram messaging service. # # For license and copyright information please follow this link: # https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL { 'includes': [ '../common_executable.gypi', '../qt.gypi', ], 'include_dirs': [ '<(src_loc)', '<(submodules_loc)/GSL/include', '<(submodules_loc)/variant/include', '<(submodules_loc)/Catch/include', '<(libs_loc)/range-v3/include', ], 'sources': [ '<(src_loc)/base/tests_main.cpp', ], } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/gyp/tests/list_tests.py����������������������������������������������������0000664�0000000�0000000�00000003006�13262451251�0021707�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������''' This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL ''' from __future__ import print_function import sys import os import re import time import codecs def eprint(*args, **kwargs): print(*args, file=sys.stderr, **kwargs) sys.exit(1) my_path = os.path.dirname(os.path.realpath(__file__)).replace('\\', '/') next_input_path = 0 input_path = '' write_sources = 0 next_self = 1 for arg in sys.argv: if next_self != 0: next_self = 0 continue if arg == '--sources': write_sources = 1 continue if arg == '--input': next_input_path = 1 continue elif next_input_path == 1: next_input_path = 0 input_path = arg continue tests_names = [] if input_path != '': if not os.path.isfile(input_path): eprint('Input path not found.') else: with open(input_path, 'r') as f: for line in f: test_name = line.strip() if test_name[0:2] != '//' and test_name != '': tests_names.append(test_name) if write_sources != 0: tests_path = my_path + '/'; if not os.path.isdir(tests_path): os.mkdir(tests_path) for test_name in tests_names: test_path = tests_path + test_name + '.test' if not os.path.isfile(test_path): with open(test_path, 'w') as out: out.write('1') print(test_name + '.test') else: for test_name in tests_names: print(test_name) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/gyp/tests/tests.gyp��������������������������������������������������������0000664�0000000�0000000�00000006337�13262451251�0021035�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of Telegram Desktop, # the official desktop application for the Telegram messaging service. # # For license and copyright information please follow this link: # https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL { 'includes': [ '../common.gypi', ], 'variables': { 'libs_loc': '../../../../Libraries', 'src_loc': '../../SourceFiles', 'submodules_loc': '../../ThirdParty', 'mac_target': '10.10', 'list_tests_command': 'python <(DEPTH)/tests/list_tests.py --input <(DEPTH)/tests/tests_list.txt', }, 'targets': [{ 'target_name': 'tests', 'type': 'none', 'includes': [ '../common.gypi', ], 'dependencies': [ '<!@(<(list_tests_command))', ], 'sources': [ '<!@(<(list_tests_command) --sources)', ], 'rules': [{ 'rule_name': 'run_tests', 'extension': 'test', 'inputs': [ '<(PRODUCT_DIR)/<(RULE_INPUT_ROOT)<(exe_ext)', ], 'outputs': [ '<(SHARED_INTERMEDIATE_DIR)/<(RULE_INPUT_ROOT).timestamp', ], 'action': [ '<(PRODUCT_DIR)/<(RULE_INPUT_ROOT)<(exe_ext)', '--touch', '<(SHARED_INTERMEDIATE_DIR)/<(RULE_INPUT_ROOT).timestamp', ], 'message': 'Running <(RULE_INPUT_ROOT)..', }] }, { 'target_name': 'tests_algorithm', 'includes': [ 'common_test.gypi', ], 'sources': [ '<(src_loc)/base/algorithm.h', '<(src_loc)/base/algorithm_tests.cpp', ], }, { 'target_name': 'tests_flags', 'includes': [ 'common_test.gypi', ], 'sources': [ '<(src_loc)/base/flags.h', '<(src_loc)/base/flags_tests.cpp', ], }, { 'target_name': 'tests_flat_map', 'includes': [ 'common_test.gypi', ], 'sources': [ '<(src_loc)/base/flat_map.h', '<(src_loc)/base/flat_map_tests.cpp', ], }, { 'target_name': 'tests_flat_set', 'includes': [ 'common_test.gypi', ], 'sources': [ '<(src_loc)/base/flat_set.h', '<(src_loc)/base/flat_set_tests.cpp', ], }, { 'target_name': 'tests_rpl', 'includes': [ 'common_test.gypi', ], 'sources': [ '<(src_loc)/rpl/details/callable.h', '<(src_loc)/rpl/details/superset_type.h', '<(src_loc)/rpl/details/type_list.h', '<(src_loc)/rpl/after_next.h', '<(src_loc)/rpl/before_next.h', '<(src_loc)/rpl/combine.h', '<(src_loc)/rpl/combine_previous.h', '<(src_loc)/rpl/complete.h', '<(src_loc)/rpl/consumer.h', '<(src_loc)/rpl/deferred.h', '<(src_loc)/rpl/distinct_until_changed.h', '<(src_loc)/rpl/event_stream.h', '<(src_loc)/rpl/fail.h', '<(src_loc)/rpl/filter.h', '<(src_loc)/rpl/flatten_latest.h', '<(src_loc)/rpl/lifetime.h', '<(src_loc)/rpl/map.h', '<(src_loc)/rpl/mappers.h', '<(src_loc)/rpl/merge.h', '<(src_loc)/rpl/never.h', '<(src_loc)/rpl/operators_tests.cpp', '<(src_loc)/rpl/producer.h', '<(src_loc)/rpl/producer_tests.cpp', '<(src_loc)/rpl/range.h', '<(src_loc)/rpl/rpl.h', '<(src_loc)/rpl/take.h', '<(src_loc)/rpl/then.h', '<(src_loc)/rpl/type_erased.h', '<(src_loc)/rpl/variable.h', '<(src_loc)/rpl/variable_tests.cpp', ], }], } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/gyp/tests/tests_list.txt���������������������������������������������������0000664�0000000�0000000�00000000103�13262451251�0022071�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tests_algorithm tests_flags tests_flat_map tests_flat_set tests_rpl�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/gyp/update_dependent.py����������������������������������������������������0000664�0000000�0000000�00000011315�13262451251�0021662�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������''' This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL ''' from __future__ import print_function import sys import os import re import time def eprint(*args, **kwargs): print(*args, file=sys.stderr, **kwargs) sys.exit(1) my_path = os.path.dirname(os.path.realpath(__file__)).replace('\\', '/') def get_qrc_dependencies(file_path): global one_modified dependencies = {} if not os.path.isfile(file_path): eprint('File not found: ' + file_path) dir_name = os.path.dirname(file_path).replace('\\', '/') with open(file_path) as f: for line in f: file_match = re.match('^\s*<file(\s[^>]*)?>([^<]+)</file>', line) if file_match: full_path = dir_name + '/' + file_match.group(2) dependencies[full_path] = 1 return dependencies def list_qrc_dependencies(file_path): global one_modified dependencies = get_qrc_dependencies(file_path) for path in dependencies: print(path) sys.exit(0) one_modified = 0 def handle_qrc_dependencies(file_path): global one_modified dependencies = get_qrc_dependencies(file_path) file_modified = os.path.getmtime(file_path) latest_modified = file_modified for path in dependencies: if os.path.isfile(path): dependency_modified = os.path.getmtime(path) if latest_modified < dependency_modified: latest_modified = dependency_modified else: eprint('File not found: ' + path) if file_modified < latest_modified: os.utime(file_path, None); one_modified = 1 def get_direct_style_dependencies(file_path): dependencies = {} dependencies[file_path] = 1 if not os.path.isfile(file_path): eprint('File not found: ' + file_path) with open(file_path) as f: for line in f: using_match = re.match('^\s*using "([^"]+)"', line) if using_match: path = using_match.group(1) found = 0 for include_dir in include_dirs: full_path = include_dir + '/' + path if os.path.isfile(full_path): try: if dependencies[full_path]: eprint('Cyclic dependencies: ' + full_path) except KeyError: dependencies[full_path] = 1 found = 1 break if found != 1: eprint('File not found: ' + path) return dependencies include_dirs = [] def handle_style_dependencies(file_path): global one_modified all_dependencies = {} all_dependencies[file_path] = 1 added_from = {} while len(added_from) != len(all_dependencies): for dependency in all_dependencies: try: if added_from[dependency]: continue except KeyError: added_from[dependency] = 1 add = get_direct_style_dependencies(dependency) for new_dependency in add: all_dependencies[new_dependency] = 1 break file_modified = os.path.getmtime(file_path) latest_modified = file_modified for path in all_dependencies: if path != file_path: dependency_modified = os.path.getmtime(path) if latest_modified < dependency_modified: latest_modified = dependency_modified if file_modified < latest_modified: os.utime(file_path, None); one_modified = 1 file_paths = [] request = '' output_file = '' next_include_dir = 0 next_output_file = 0 next_self = 1 for arg in sys.argv: if next_self != 0: next_self = 0 continue if arg == '--styles' or arg == '--qrc_list' or arg == '--qrc': if request == '': request = arg[2:] else: eprint('Only one request required.') continue if next_include_dir != 0: next_include_dir = 0 include_dirs.append(arg) continue if next_output_file != 0: next_output_file = 0 output_file = arg continue include_dir_match = re.match(r'^\-I(.*)$', arg) if include_dir_match: include_dir = include_dir_match.group(1) if include_dir == '': next_include_dir = 1 else: include_dirs.append(include_dir) continue output_match = re.match(r'^-o(.*)$', arg) if output_match: output_file = output_match.group(1) if output_file == '': next_output_file = 1 continue file_paths.append(arg) if request == 'styles': for file_path in file_paths: handle_style_dependencies(file_path) elif request == 'qrc': for file_path in file_paths: handle_qrc_dependencies(file_path) elif request == 'qrc_list': for file_path in file_paths: list_qrc_dependencies(file_path) else: eprint('Request required.') if not os.path.isfile(output_file): with open(output_file, "w") as f: f.write('1') elif one_modified != 0: os.utime(output_file, None); �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/Telegram/gyp/utils.gyp��������������������������������������������������������������0000664�0000000�0000000�00000007261�13262451251�0017666�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of Telegram Desktop, # the official desktop application for the Telegram messaging service. # # For license and copyright information please follow this link: # https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL { 'includes': [ 'common.gypi', ], 'targets': [{ 'target_name': 'Updater', 'variables': { 'libs_loc': '../../../Libraries', 'src_loc': '../SourceFiles', 'res_loc': '../Resources', }, 'includes': [ 'common_executable.gypi', ], 'include_dirs': [ '<(src_loc)', ], 'sources': [ '<(src_loc)/_other/updater.cpp', '<(src_loc)/_other/updater.h', '<(src_loc)/_other/updater_linux.cpp', '<(src_loc)/_other/updater_osx.m', ], 'conditions': [ [ 'build_win', { 'sources': [ '<(res_loc)/winrc/Updater.rc', ], }], [ '"<(build_linux)" != "1"', { 'sources!': [ '<(src_loc)/_other/updater_linux.cpp', ], }, { 'ldflags': [ '-static-libstdc++', ], }], [ '"<(build_mac)" != "1"', { 'sources!': [ '<(src_loc)/_other/updater_osx.m', ], }], [ '"<(build_win)" != "1"', { 'sources!': [ '<(src_loc)/_other/updater.cpp', ], }], ], }, { 'target_name': 'Packer', 'variables': { 'libs_loc': '../../../Libraries', 'src_loc': '../SourceFiles', 'mac_target': '10.10', }, 'includes': [ 'common_executable.gypi', 'qt.gypi', ], 'conditions': [ [ 'build_win', { 'libraries': [ 'libeay32', 'ssleay32', 'Crypt32', 'zlibstat', 'LzmaLib', ], }], [ 'build_linux', { 'libraries': [ 'ssl', 'crypto', 'lzma', ], }], [ 'build_mac', { 'include_dirs': [ '<(libs_loc)/openssl/include' ], 'library_dirs': [ '<(libs_loc)/openssl', ], 'xcode_settings': { 'OTHER_LDFLAGS': [ '-lssl', '-lcrypto', '-llzma', ], }, }], ], 'include_dirs': [ '<(src_loc)', '<(libs_loc)/lzma/C', '<(libs_loc)/zlib', ], 'sources': [ '<(src_loc)/_other/packer.cpp', '<(src_loc)/_other/packer.h', ], 'configurations': { 'Debug': { 'conditions': [ [ 'build_win', { 'include_dirs': [ '<(libs_loc)/openssl/Debug/include', ], 'library_dirs': [ '<(libs_loc)/openssl/Debug/lib', '<(libs_loc)/lzma/C/Util/LzmaLib/Debug', '<(libs_loc)/zlib/contrib/vstudio/vc14/x86/ZlibStatDebug', ], }, { 'include_dirs': [ '/usr/local/include', '<(libs_loc)/openssl/include' ], 'library_dirs': [ '/usr/local/lib', ], }] ], }, 'Release': { 'conditions': [ [ 'build_win', { 'include_dirs': [ '<(libs_loc)/openssl/Release/include', ], 'library_dirs': [ '<(libs_loc)/openssl/Release/lib', '<(libs_loc)/lzma/C/Util/LzmaLib/Release', '<(libs_loc)/zlib/contrib/vstudio/vc14/x86/ZlibStatReleaseWithoutAsm', ], }, { 'include_dirs': [ '/usr/local/include', '<(libs_loc)/openssl/include' ], 'library_dirs': [ '/usr/local/lib', ], }] ], }, }, }], } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/appveyor.yml������������������������������������������������������������������������0000664�0000000�0000000�00000001054�13262451251�0016030�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������image: Visual Studio 2017 clone_folder: C:\TBuild\tdesktop test: off deploy: off environment: matrix: - BUILD_VERSION: - BUILD_VERSION: disable_autoupdate - BUILD_VERSION: disable_register_custom_scheme - BUILD_VERSION: disable_crash_reports - BUILD_VERSION: disable_network_proxy matrix: fast_finish: true before_build: - .\.appveyor\install.bat build: project: Telegram\Telegram.sln artifacts: - path: out\Debug\Telegram.exe name: Telegram_debug.exe - path: out\Debug\Telegram.exe.pdb name: Telegram_debug.exe.pdb ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/changelog.txt�����������������������������������������������������������������������0000664�0000000�0000000�00000131610�13262451251�0016132�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������1.2.17 (08.04.18) - Bug fixes and other minor improvements. 1.2.16 (07.04.18) - Bug fixes and other minor improvements. 1.2.15 (26.03.18) - Bug fixes and other minor improvements. 1.2.14 (21.03.18) - Discover new stickers. Type one emoji to see suggestions from popular sticker sets. Suggestions from your installed sticker sets will come first. - Search for Stickers. Click on the new search icon to access your sticker sets or find new ones. - Quick Reply. Double click near a message for a quick reply. 1.2.13 alpha (21.03.18) - Bug fixes and other minor improvements. 1.2.12 alpha (12.03.18) - Bug fixes and other minor improvements. 1.2.11 alpha (10.03.18) - Bug fixes and other minor improvements. 1.2.10 alpha (09.03.18) - Bug fixes and other minor improvements. 1.2.9 alpha (07.03.18) - Quick Reply. Double click near a message for a quick reply. - Search for Stickers. Click on the new search icon to access your sticker sets or find new ones. 1.2.8 alpha (03.01.18) - Bug fixes and other minor improvements. 1.2.7 alpha (31.12.17) - Use fast reply button in group chats. - Select a message you want to reply to by pressing Ctrl+Up and Ctrl+Down. 1.2.6 (30.12.17) - Grouped Photos. Group media into an album when sharing multiple photos and videos. Choose the exact order of media you send. 1.2.5 alpha (29.12.17) - When viewing a photo from an album, you'll see other pictures from the same group as thumbnails in the lower part of the screen. - When composing an album paste additional media from the clipboard. - Bug fixes and other minor improvements. 1.2.4 alpha (26.12.17) - Group media into an album when sharing multiple photos and videos. - Bug fixes and other minor improvements. 1.2.3 alpha (17.12.17) - Several crash fixes. 1.2.2 alpha (16.12.17) - Grouped photos and videos are displayed as albums. 1.2.1 (12.12.17) - Bug fixes and other minor improvements. 1.2.0 (10.12.17) - Radically improved navigation. New side panel on the right with quick access to shared media and group members. - Saved Messages. Bookmark messages by forwarding them to "Saved Messages". Access them from the Chats list or from the side menu. - Pinned Messages. If you are a channel admin, pin messages to focus your subscribers' attention on important announcements. - Easily recognize messages from group admins by the new 'admin' badge. - Also supported clearing history in supergroups and added a host of minor improvements. 1.1.29 alpha (09.12.17) - Bug fixes and other minor improvements. 1.1.28 alpha (09.12.17) - Bug fixes and other minor improvements. 1.1.27 alpha (06.12.17) - Bookmark messages by forwarding them to "Saved Messages". Access them from the Chats list or from the side menu. 1.1.26 alpha (02.12.17) - Admin badges in supergroup messages. - Fix crashing on launch in OS X 10.6. - Bug fixes and other minor improvements. 1.1.25 alpha (30.11.17) - Bug fixes and other minor improvements. 1.1.24 alpha (29.11.17) - Radically improved navigation. New side panel on the right with quick access to shared media and group members. - Pinned Messages. If you are a channel admin, pin messages to focus your subscribers' attention on important announcements. - Also supported clearing history in supergroups and added a host of minor improvements. 1.1.23 (05.09.17) - See the message author photo and name while searching specific chat messages. - Fix "Send To" menu action on Windows. 1.1.22 (04.09.17) - Bug fixes and other minor improvements. 1.1.21 (03.09.17) - Bug fixes and other minor improvements. 1.1.20 (03.09.17) - Groups with unread mentions and replies are now marked with an '@' badge in the chats list. - Navigate new mentions and replies in a group using the new '@' button. - Mark your stickers as “favorite” to quickly access them from the redesigned sticker panel. - Add an official sticker set for your group which all members will be able to use while chatting in your group (100+ member groups only). 1.1.19 (01.08.17) - Search by messages of specific group members. - Bug fixes and other minor improvements. 1.1.18 (27.07.17) - Bug fixes and other minor improvements. 1.1.17 (26.07.17) - Bug fixes and other minor improvements. 1.1.16 (26.07.17) - Autocompletion for emoji. Start typing :e to get suggestions. - Fixed a bug with forwarding messages. 1.1.15 (23.07.17) - Send **bold** and __italic__ text in your messages. - Get a share link for posts in public supergroups. - Quickly share posts from channels and media messages from bots. - Search large supergroup members by name. - Search channel members by name for admins. - Use search in the service actions log. - Ban supergroup members via the right click menu in the service actions log. 1.1.14 alpha (19.07.17) - Bug fixes and other minor improvements. 1.1.13 alpha (14.07.17) - Various bug fixes. 1.1.12 alpha (11.07.17) - Click on forwarded messages bar to change the recipient chat in case you chose a wrong one first. - Quickly share posts from channels and media messages from bots. - Search in large supergroup members by name. - Search in channel members by name if you're a channel admin. - Copy links to messages in public supergroups. 1.1.11 alpha (06.07.17) - Send **bold** and __italic__ text in your messages (in addition to already supported `monospace` and ```multiline monospace```). - Search in channel and supergroup admin event log. - Ban members from right click menu in supergroup admin event log. 1.1.10 (04.07.17) - Add event log filter for channel or supergroup event log. - Fix search by username in privacy exceptions editor. - Fix adding admins in channels. 1.1.9 (30.06.17) - Supergroups can now have up to 10.000 members. - Appoint supergroup admins with granular rights. Choose who can add users, manage messages, block members, edit group info & username, add new admins, etc. - Restrict and ban supergroup members with granular precision. Read-only bans, GIF & sticker bans, media bans, temporary bans and restrictions. - Check the new event log to see all service actions taken by members and admins of a channel or supergroup in the last 48 hours. - Toggle night mode in the main menu. 1.1.8 alpha (30.06.17) - Toggle night mode in the main menu. 1.1.7 (30.05.17) - Improved video messages: radial playback progress, Picture-in-Picture support, duration countdown. - Voice and video messages now automatically play one after another. 1.1.6 alpha (26.05.17) - Bug fixes and other minor improvements. 1.1.5 alpha (26.05.17) - Bug fixes and other minor improvements. 1.1.4 (24.05.17) - Fix a crash in animated history scrolling. - Fix a bug with pinned chat to supergroup upgrade. 1.1.3 (24.05.17) - Improved video messages playback. - Video and audio messages play one after another. 1.1.2 (17.05.17) - Emoji icon tooltip about hiding the sidebar. - Fix possible calls deadlock in Linux. - Preserve Emoji/Stickers/GIFs panel state between activations. 1.1.1 alpha (17.05.17) - Emoji icon tooltip about hiding the sidebar. - Fix possible calls deadlock in Linux. 1.1.0 (14.05.17) - Telegram Calls are now available on desktops: secure, crystal-clear, constantly improved by artificial intelligence. - The new Emoji, Stickers, and Saved GIFs panel becomes a separate space on the right when Telegram is running in a wide enough window. - Manage blocked users list in your supergroups. - Chat admins can delete messages by other members. 1.0.38 alpha (13.05.17) - Fix crashes in Windows XP. - Fix calls in Linux without SSE4.1 support. - PulseAudio support in calls. - Bug fixes and other minor improvements. 1.0.37 alpha (10.05.17) - Multiple crash fixes. 1.0.36 alpha (09.05.17) - Telegram Calls are now available on desktops: secure, crystal-clear, constantly improved by artificial intelligence. - Bug fixes and other minor improvements. 1.0.35 alpha (30.04.17) - Chat admins can delete other participants' messages. - Bug fixes and other minor improvements. 1.0.34 alpha (21.04.17) - Bug fixes and other minor improvements. 1.0.33 alpha (16.04.17) - Bug fixes and other minor improvements. 1.0.32 alpha (12.04.17) - Testing a build with the latest API layer. - Bug fixes and other minor improvements. 1.0.31 alpha (11.04.17) - Bug fixes and other minor improvements. 1.0.30 alpha (11.04.17) - The new Emoji, Stickers, and Saved GIFs panel becomes a separate space on the right when Telegram is running in a wide enough window. - Manage blocked users list in your supergroups. 1.0.29 (05.04.17) - Improved Emoji, Stickers, and Saved GIFs panel. - Bug fixes and other minor improvements. 1.0.28 alpha (03.04.17) - Better Emoji & Stickers & Saved GIFs panel. - Linux: Tray icon should work better in GNOME based Desktop Environments. - Bug fixes and other minor improvements. 1.0.27 (31.03.17) - Fix launch in Ubuntu 17.04. - Update API scheme. 1.0.26 (30.03.17) — Send MP4/MOV files as videos that will play right inside Telegram. — Click on the date in any chat to quickly jump to messages from a specific day. — Change your phone number in Settings. — Edit who can see your last seen time and who can add you to groups in Settings. — Edit your list of blocked users in Settings. — App now respects the "Do Not Disturb" setting for macOS notifications. — Bug fixes and other minor improvements. 1.0.25 alpha (21.03.17) - Edit your account phone number in Settings. 1.0.24 alpha (19.03.17) - Added Last Seen and Group Invite privacy settings. 1.0.23 alpha (15.03.17) - Edit list of blocked users in Settings. 1.0.22 alpha (13.03.17) - Bug fixes and other minor improvements. 1.0.21 alpha (11.03.17) - Send MP4/MOV files as videos that will play right inside Telegram. - Bug fixes and other minor improvements. 1.0.20 alpha (08.03.17) - Bug fixes and other minor improvements. 1.0.19 alpha (08.03.17) - Go to date. Click on the date in a chat to jump to a specific day. - Respect macOS "Do not disturb" settings for sound notifications. - Bug fixes and other minor improvements. 1.0.18 alpha (01.03.17) - Bug fixes and other minor improvements. 1.0.17 alpha (28.02.17) - Bug fixes and other minor improvements. 1.0.16 alpha (27.02.17) - Bug fixes and other minor improvements. 1.0.15 alpha (27.02.17) - Wrong supergroup members display fix. - RTL text layout fix. - Linux GTK file choose dialog should show image previews. - Bug fixes and other minor improvements. 1.0.14 (20.02.17) - Bug fixes and other minor improvements. 1.0.13 (20.02.17) - Bug fixes and other minor improvements. 1.0.12 (19.02.17) - Support for more emoji. - Click and drag on waveform to play audio from a chosen moment. - Added Theme editor to Settings. - Bug fixes and other minor improvements. 1.0.11 alpha (17.02.17) - Bug fixes and other minor improvements. 1.0.10 alpha (17.02.17) - Support for more emoji. - Bug fixes and other minor improvements. 1.0.9 alpha (11.02.17) - Bug fixes and other minor improvements. 1.0.8 alpha (11.02.17) - Click and drag on waveform to play audio from a chosen moment. 1.0.7 alpha (07.02.17) - Added Theme editor to Settings. 1.0.6 (01.02.17) - Bug fixes and other minor improvements. 1.0.5 (31.01.17) - Click and drag to reorder pinned chats. - Bug fixes and other minor improvements. 1.0.4 alpha (30.01.17) - Click and drag to reorder pinned chats. 1.0.3 alpha (27.01.17) - Audio device is opened only when some sound is played. - On Windows Vista and later audio device should switch after the system default changes. 1.0.2 (19.01.17) - New option to minimize the chat list into a column of profile pictures. Resize the list by clicking and dragging. - Fixed drag-n-drop images from Firefox on Windows. - Bug fixes and other minor improvements. 1.0.1 alpha (16.01.17) - Resize chats list with mouse press-and-drag. - Drag-n-drop images from Firefox fixed in Windows. - Bug fixes and other minor improvements. 1.0.0 (11.01.17) - Fabulous new material-style design and animations. - Support for custom themes! Check out some themes here: @TelegramThemes. - Convenient tools for building your own themes coming soon. - Delete messages for everyone. When you delete your messages in groups and one-on-one chats, you can now choose to delete them for everyone in the chat, not just yourself. This works only for recently sent messages (same as editing). - Pin important chats to the top of the list so that you never miss a new message (right click on a chat, then choose 'Pin to top'). - Groups in common. A new option in your contacts' profiles that shows a list of all groups you share with that person. 0.10.27 alpha (11.01.17) - Appoint admins in your supergroups from members list context menu. - Bug fixes and other minor improvements. 0.10.26 alpha (07.01.17) - You can use t.me instead of telegram.me. - OpenAL updated to the latest version. - Bug fixes and other minor improvements. 0.10.25 alpha (05.01.17) - Bug fixes and other minor improvements. 0.10.24 alpha (05.01.17) - Bug fixes and other minor improvements. 0.10.23 alpha (01.01.17) - Bug fixes and other minor improvements. 0.10.22 alpha (31.12.16) - Bug fixes and other minor improvements. 0.10.21 alpha (30.12.16) - Fabulous new material-style design and animations. - This version already supports custom themes that are coming soon. - Pin important chats to the top of the list so that you never miss a new message (right click on a chat, then choose 'Pin to top'). - Groups in common. A new option in your contacts' profiles that shows a list of all groups you share with that person. 0.10.20 (18.12.16) - Bug fixes and other minor improvements. 0.10.19 (25.10.16) - Bug fixes and other minor improvements. 0.10.18 (24.10.16) - New cute design for adding members to your groups. 0.10.17 alpha (23.10.16) - New cute control for adding members to your groups. 0.10.16 (19.10.16) - New audio player design. - Quick reply from notifications. - Hide all notifications button added. - Change notifications location and maximum count. - Respecting quiet hours for the notifications. - You can enable native notifications in Settings. 0.10.15 alpha (18.10.16) - Bug fixes and other minor improvements. 0.10.14 alpha (18.10.16) - New audio player design. - Moved to Qt library version 5.6.2. - Windows 10: Respecting quite hours for the notifications. 0.10.13 alpha (07.10.16) - Bug fixes and other minor improvements. 0.10.12 alpha (07.10.16) - Quick reply from notifications. - Hide all notifications button added. - Change notifications location and maximum count. - Linux: You can enable native notifications in Settings. 0.10.11 (03.10.16) - Bug fixes and other minor improvements. 0.10.10 (03.10.16) - Bug fixes and other minor improvements. 0.10.9 (03.10.16) - Bug fixes and other minor improvements. 0.10.8 (23.09.16) - Bug fixes and other minor improvements. 0.10.7 (20.09.16) - Bug fixes and other minor improvements. 0.10.6 (13.09.16) - Bug fixes and other minor improvements. 0.10.5 (12.09.16) - New cute design for the Settings page. - Bug fixes and other minor improvements. 0.10.4 alpha (03.09.16) - Bug fixes and other minor improvements. 0.10.3 alpha (02.09.16) - New cute design for the Settings page. 0.10.2 alpha (14.08.16) - Bug fixes and other minor improvements. 0.10.1 (05.08.16) - Bug fixes and other minor improvements. 0.10.0 (03.08.16) - Trending stickers. Check out and install noteworthy sticker packs from the new tab in Settings. - Archived stickers. Unused stickers are now archived automatically when you go over the 200 limit. - Group previews. Preview groups before joining them via invite link – see who else is in the group before joining. - New internal video player. - Improved design for chats. 0.9.60 alpha (24.07.16) - Bug fixes and other minor improvements. 0.9.59 alpha (23.07.16) - Bug fixes and other minor improvements. 0.9.58 alpha (22.07.16) - Alpha version of an embedded video player. 0.9.57 alpha (07.07.16) - Design improvements. - Linux: trying to use GTK file chooser when it is available. 0.9.56 stable (26.06.16) - Fixed photo viewer to handle screen resolution change correctly. - Fixed forwarding photos via drag-n-drop. - Various design improvements and other bug fixes. 0.9.55 alpha (24.06.16) - Main window position and size are saved between the launches in Windows. - Dock and top bar hiding fixed in OS X. - Various design improvements and other bug fixes. 0.9.54 alpha (22.06.16) - Photo viewer handles screen resolution change. - Forward photo by drag-n-drop fixed. - Some design improvements and bug fixes. 0.9.53 alpha (21.06.16) - Put your cursor over the members count in a group chat to see the members list. - Bug fixes and other minor improvements. 0.9.52 alpha (17.06.16) - Bug fixes and other minor improvements. 0.9.51 stable (15.06.16) - Bug fixes and other minor improvements. 0.9.50 stable (14.06.16) - Introducing Drafts: Seamless syncing for unsent messages on all your devices. Drafts are now visible in your chats list. - Completely redesigned group and user profiles. - Unread messages counter on the 'Scroll to bottom' button. 0.9.49 stable (16.05.16) - Edit your messages everywhere within 2 days after posting (press the up arrow button to edit your last message). - Mention people in groups by typing @ and selecting them from the list — even if they don't have a username. 0.9.48 stable (10.05.16) - Bug fixes and other minor improvements. 0.9.47 alpha (02.05.16) - Bug fixes and other minor improvements. 0.9.46 alpha (27.04.16) - Alpha versions instead of dev. - Bug fixes and other minor improvements. 0.9.45 dev (22.04.16) - Bug fixes and other minor improvements. 0.9.44 stable (20.04.16) - Bug fixes and other minor improvements. 0.9.43 dev (14.04.16) - Select and copy text in photo / video captions and web page previews. - Media player shortcuts are enabled only when player is opened. 0.9.42 stable (12.04.16) - Bug fixes and other minor improvements. 0.9.41 stable (11.04.16) - Introducing Bot API 2.0, the biggest update to our bot platform since June 2015. - Bots can now update existing messages on the fly as you interact with them. - New Inline keyboards with callback, 'open URL' or 'switch to inline mode' buttons help create seamless interfaces. - Inline bots can now send all attachments supported by Telegram (videos, music, stickers, locations, etc.). 0.9.40 stable (05.04.16) - Visual improvements. 0.9.39 dev (01.04.16) - Bug fixes and other minor improvements. 0.9.38 dev (01.04.16) - Bug fixes and other minor improvements. 0.9.37 dev (31.03.16) - Bug fixes and other minor improvements. 0.9.36 dev (26.03.16) - Bug fixes and other minor improvements. 0.9.35 dev (25.03.16) - Design improvements. 0.9.34 dev (17.03.16) - Bug fixes and other minor improvements. 0.9.33 stable (16.03.16) - Linux version critical bug fixed. 0.9.32 stable (14.03.16) - Bug fixes and other minor improvements. 0.9.31 stable (14.03.16) - Supergroups can now have 5,000 members (up from 1,000). - Groups of any size may be converted to supergroups. - Make your supergroup public by setting up a public link – anyone will be able to view the chat and join it. - Pin messages to keep important updates visible and notify all members. 'Select messages to delete, report as spam, block users, or remove all messages from a user' 0.9.30 dev (03.03.16) - Bug fixes and other minor improvements. 0.9.29 dev (01.03.16) - Ctrl+W or Ctrl+F4 for close window (Cmd in OS X). - Ctrl+L to lock Telegram if you use a local passcode (Cmd in OS X). 0.9.28 stable (26.02.16) - Edit your messages in channels and supergroups. - Share links to specific posts in channels via the post context menu. - Add admin signatures to messages in channels. - Send silent messages in channels that will not notify members, useful for non-urgent or late night posting. 0.9.27 dev (25.02.16) - Edit your messages in channels and supergroups. - Share links to specific posts in channels via the post context menu. - Add admin signatures to messages in channels. - Send silent messages in channels that will not notify members, useful for non-urgent or late night posting. 0.9.26 stable (22.02.16) - Adaptive layout for wide screens switch added to Settings. - Linux version crash fix. 0.9.25 dev (21.02.16) - Adaptive layout for wide screens switch added to Settings. - Linux version crash fix. 0.9.24 stable (16.02.16) - New waveform visualizations for voice messages. - Sticker suggestions when you type an emoji. 0.9.23 dev (15.02.16) - Bug fixes and other minor improvements. 0.9.22 dev (14.02.16) - Voice messages waveform visualizations. 0.9.21 dev (09.02.16) - Bug fixes and other minor improvements. 0.9.20 dev (08.02.16) - Testing new crash reporting system. - Conversation history is centered in wide windows. - New cute link and timestamp tooltips design. - Ctrl+W or Ctrl+F4 closes Telegram window. 0.9.19 dev (09.01.16) - Choose an emoticon and see the suggested stickers. 0.9.18 stable (05.01.16) - Bug fixes and minor improvements. 0.9.17 stable (04.01.16) - Crash fixed. 0.9.16 stable (04.01.16) - GIFs autoplay and automatic download. - Inline bots support. - Automatic download settings added. - All media redesigned. 0.9.15 stable (09.12.15) - Sticker management: manually rearrange your sticker packs, pack order is now synced across all your devices. - Click and hold on a sticker to preview it before sending. - New context menu for chats in chats list. - Support for all existing emoji. 0.9.14 dev (09.12.15) - Sticker management: manually rearrange your sticker packs, pack order is now synced across all your devices. - Click and hold on a sticker to preview it before sending. - New context menu for chats in chats list. - Support for all existing emoji. 0.9.13 stable (25.11.15) - Groups can now have multiple administrators with the ability to edit the name and logo, and add and remove members. - Groups that have reached their capacity of 200 users can be upgraded to supergroups of up to 1,000 members. 0.9.12 dev (22.11.15) - Bug fixes and other minor improvements. 0.9.11 dev (22.11.15) - Groups can now have multiple administrators with the ability to edit the name and logo, and add and remove members. - Groups that have reached their capacity of 200 users can be upgraded to supergroups of up to 1,000 members. 0.9.10 stable (04.11.15) - New shared audio files section. - New design for popup menus. - Add captions to any photos you send. - Surround text with `single backticks` for inline monowidth code and ```triple backticks``` for blocks of code. - The Tilde symbol is now displayed correctly. 0.9.9 dev (02.11.15) - New popup menus in text input fields. - Error is displayed when trying to paste or drop a folder instead of a file. - Some bugfixes and improvements. 0.9.8 dev (28.10.15) - Tilde symbol fixed in message input field. - Add a caption to any photo you send. - Bad sound quality bug fixed. - Some bugfixes and improvements. 0.9.7 dev (23.10.15) - Tilde symbol display fixed. - Bold and Italic text display from bots supported. - Send code without text and emoji replaces using `such syntax` for inline parts and ```such syntax``` for blocks of code. - Some bugfixes and improvements. 0.9.6 stable (14.10.15) - Bug fixes and other minor improvements. ), ), 0.9.5 stable (14.10.15) - Improved speed. - First steps towards a new design. ), ), 0.9.4 dev (12.10.15) - New design for all modal windows. - Toggle notifications from tray menu. 0.9.3 dev (01.10.15) - Dialogs and emoji render made much faster. 0.9.2 stable (25.09.15) - Bug fixes and other minor improvements. 0.9.1 stable (24.09.15) - Bug fixes and other minor improvements. 0.9 stable (24.09.15) - Introducing Channels — a great new way to broadcast your messages to unlimited audiences. - Other fixes and improvements. 0.8.59 dev (23.09.15) - Channels members and admins management added. 0.8.58 dev (22.09.15) - Limited Channels support added. 0.8.57 stable (13.09.15) - Bug fixes and other minor improvements. 0.8.56 stable (10.09.15) - Spam report buttons in new chats. - Other fixes and improvements. - OS X 10.8 notifications fixed. 0.8.55 stable (02.09.15) - Include muted chats in unread count in Settings. - Shared links overview and search in shared media. - Preview when sending links to GIF animations and PDF files. 0.8.54 dev (31.08.15) - Preview when sending links to GIF animations and PDF files. - Full date and time shown when mouse over message timestamp. 0.8.53 dev (28.08.15) - Include muted chats in unread count in Settings. - Shared links overview and search in shared media. 0.8.52 stable (19.08.15) - Bug fixes and other minor improvements. 0.8.51 stable (17.08.15) - Block users from their profile page. - Added support for Windows toast notifications. - Fixed input methods on Linux (Fcitx and IBus). 0.8.50 dev (14.08.15) - Bug fixes in Windows notifications. - Fixed input methods on Linux (Fcitx and IBus). 0.8.49 dev (12.08.15) - Block users from user profile. - Ask support team a question from Settings. - Windows toast notifications support added. 0.8.48 stable (08.08.15) - Search for messages in conversation. - Clear messages history in groups. - Contacts without messages are hidden from the conversations list. 0.8.47 dev (07.08.15) - Search for messages in conversation. - Clear messages history in groups. - Contacts without messages are hidden from the conversations list. 0.8.46 stable (05.08.15) - Crash fix on sending .webp files. 0.8.45 stable (03.08.15) - Bug fixes and other minor improvements. 0.8.44 dev (01.08.15) - Sending media and recording audio status display. 0.8.43 stable (31.07.15) - Bug fixes and other minor improvements. 0.8.42 dev (31.07.15) - Dev version will now get updated to stable as well. 0.8.41 dev (21.07.15) - Pretty phone number formatting. - Fixed shared contacts display. - Fix KDE crash, use Qt tray icon in all Linux systems. 0.8.40 dev (17.07.15) - Fixed critical Qt bug in image scale. - Huge amount of unread messages are loaded much faster. - Some bugfixes and optimizations. 0.8.39 dev (14.07.15) - Moved to Qt 5.5 and Xcode 6.4. - Some bugfixes and optimizations. - In OS X 10.10.3 location marks sent from mobile should be displayed now. 0.8.38 stable (03.07.15) - Improved in-app media playback. 0.8.37 dev (03.07.15) - Improved in-app media playback. 0.8.36 stable (01.07.15) - Forward photos, media and stickers with drag-n-drop. - Drag-n-drop text messages by timestamp to forward them. - Larger stickers panel. - IPv6 checkbox added to Connection Type in Settings. 0.8.35 dev (30.06.15) - Bug fixes and minor improvements. 0.8.34 dev (28.06.15) - Forward photos, media and stickers with drag-n-drop. - Drag-n-drop text messages by timestamp to forward them. - Larger stickers panel. 0.8.33 dev (25.06.15) - IPv6 connection checkbox added to Connection Type box in Settings. 0.8.32 stable (25.06.15) - Critical bug fixed. - IPv6 connection temporarily disabled. 0.8.31 dev (24.06.15) - Bots support using the new bot API. - Drag-n-drop selected text and links from messages. - Forward selected messages by drag-n-drop. 0.8.30 stable (24.06.15) - Bots support using the new bot API. 0.8.29 dev (22.06.15) - Bug fixes and minor improvements. 0.8.28 dev (18.06.15) - Bug fixes and minor improvements. 0.8.27 dev (17.06.15) - Bug fixes and minor improvements. 0.8.26 dev (17.06.15) - Bug fixes and minor improvements. 0.8.25 dev (15.06.15) - IPv6 connections support. 0.8.24 stable (08.06.15) - Improved sticker panel. 0.8.23 dev (05.06.15) - Improved sticker panel. - New autoupdate check. 0.8.22 dev (04.06.15) - OpenAL updated to a new version. - Autoupdating improved. - Bugfixes. 0.8.21 stable (02.06.15) - Added sending voice messages. - Fixed disappearing stickers. 0.8.20 dev (02.06.15) - Minor bug fixes. - Translations improved. 0.8.19 dev (02.06.15) - Fixed disappearing stickers. 0.8.18 dev (01.06.15) - Voice messages recording and sending. 0.8.17 stable (21.05.15) - Crash fix. 0.8.16 stable (20.05.15) - Added support for sticker packs. - New emoji and sticker panel. 0.8.15 dev (20.05.15) - Video captions are displayed. - Photo captions are displayed in photo viewer. - Round corners for messages. 0.8.14 dev (19.05.15) - Added support for sticker packs. - New emoji and sticker panel. 0.8.13 stable (12.05.15) - Added support for new emoji. - Improved emoji and stickers panel. 0.8.12 dev (08.05.15) - New emojis support added. - Emojis and stickers panel improved. 0.8.11 stable (01.05.15) - Invite links for group chats. - Gray unread badge for muted conversations. 0.8.10 dev (01.05.15) - Critical bug fixes for messages history loading. 0.8.9 dev (30.04.15) - Fix for invite links. 0.8.8 dev (30.04.15) - Invite links for group chats. - Gray unread badge for muted conversations. 0.8.7 stable (24.04.15) - New photo viewer design. - Switch between files in the photo viewer. - Grouped notifications when several messages are forwarded. - New default chat background image (you can change it in Settings). 0.8.6 dev (24.04.15) - Old default chat background image placed first in background Gallery. - Forwarded files, videos, audios and contacts original sender name is displayed. - Grouped notifications when several messages are forwarded. 0.8.5 dev (22.04.15) - New photoviewer design. - Switch through files overview in photoviewer. - New default chat background image. 0.8.4 stable (13.04.15) - Link preview layout bug fix. 0.8.3 stable (08.04.15) - Link previews for Twitter, YouTube, Instagram and certain other links. - Two-step verification. - View all your Telegram sessions, terminate specific sessions. 0.8.2 dev (07.04.15) - Link previews bugfixes. - Links in preview descriptions are now clickable. - Twitter and Instagram mentions and hashtags in previews are clickable. - Fixed file uploading. - Fixed photo, document and sticker forwarding. 0.8.1 dev (05.04.15) - Link previews for Twitter, YouTube, Instagram and certain other links. - Two-step verification. - View all your Telegram sessions, terminate specific sessions. - Text is pasted from clipboard when clipboard has both text and image and image sending was cancelled. 0.8 stable (26.03.15) - Add a comment before forwarded messages. - Hashtags suggestions in new message and search fields (based on recent searches). - Button to jump back to a reply after viewing the original message. - Add people to groups by username. 0.7.26 dev (26.03.15) - Some bugfixes. 0.7.25 dev (25.03.15) - Add people to groups by username. - Copy @username from profile context menu. 0.7.24 dev (24.03.15) - Forward messages with comment or sticker before them. - Recent hashtags autocomplete in message and search fields. - Move from reply to original message and back. 0.7.23 stable (20.03.15) - Reply to specific messages in groups. - Mention @usernames in groups to notify multiple users. 0.7.22 dev (20.03.15) - Critical bug in messages delivery fixed. 0.7.21 dev (19.03.15) - Reply to specific messages in groups. - Mention @usernames in groups to notify multiple users. 0.7.20 stable (13.03.15) - Lock your app with a passcode. 0.7.19 dev (03.03.15) - Passcode lock option added to Settings. 0.7.18 dev (20.02.15) - Windows: crash on start fixed for some Intel cards. - Linux: tray icon returned in Pantheon and Gnome. 0.7.17 stable (19.02.15) - Some minor improvements. - Windows: crash on app launch fixed for some Intel graphic cards. 0.7.16 stable (17.02.15) - Some translations improvements. - Linux: tray icon should work better in non-Unity environments. 0.7.15 dev (16.02.15) - Some translation keys added. - Linux: fixed semibold font. - Linux: tray icon should work better in non-Unity environments. 0.7.14 dev (12.02.15) - Moved to Qt 5.4.0 version, please check everything. - Time values should be displayed like in your system. - Linux: fixed keyboard shortcuts when not english keyboard layout. 0.7.13 stable (10.02.15) - You can now change the chat background. 0.7.12 dev (06.02.15) - Chat background settings translated. - Mac OS X and Linux autoupdate for dev-version fixed. 0.7.11 dev (05.02.15) - Chat background can be changed. - First dev-channel version. 0.7.10 (29.01.15) - Online updated only while current window is active. 0.7.9 (23.01.15) - Added Korean language. - Quick «open with» menu on Windows and OSX. 0.7.8 (19.01.15) - Some graphics changed. - Windows: when activating window by click selection is not cleared. - Linux: tray icon and unity launcher counter support improved. 0.7.7 (16.01.15) - Windows: system sleep / hibernation fixed when app is running. - Mac OS X: crashes on OS X 10.7 and 10.8 fixed. - Linux: tray icon support and unity launcher counter for unread messages added in Ubuntu. 0.7.6 (06.01.15) - Some improvements for stickers. - Some translation improvements. 0.7.5 (03.01.15) - Stickers support. - Local caching for voice messages. - Added Portuguese language. 0.7.4 (23.12.14) - German and Dutch languages added. 0.7.3 (21.12.14) - Italian and Spanish languages added. 0.7.2 (16.12.14) - Title buttons bug fixed in single column layout. 0.7.1 (16.12.14) - Linux: font for Korean characters added. - Various bugs fixed. 0.7 (16.12.14) - Improved single column navigation. - Improved image viewer for large documents. 0.6.21 (13.12.14) - Crash fixed for contacts without names. 0.6.20 (13.12.14) - Crash fixed in single column layout. 0.6.19 (13.12.14) - Some bugs fixed. 0.6.18 (12.12.14) - Single-column layout support added for small chat window. - Photos are sent up to 1280x1280 size. - New device login notification added. - New version notification added. 0.6.17 (08.12.14) - Filter in contacts lists (contacts, new group, add member) now is case-insensitive. - Win: telegram.me/username links handling fixed. 0.6.16 (05.12.14) - telegram.me/username links supported. - Downloaded documents local cache added. - Some network improvements. 0.6.15 (26.11.14) - Intro and login were slightly redesigned. - Some various bug fixes. 0.6.14 (24.11.14) - Work over HTTP crashing fixed. 0.6.13 (24.11.14) - Application icon changed. - Vimeo video links shown with preview. - Some iOS emojis display fixed. - Windows version crash when notification clicked fixed. 0.6.12 (22.11.14) - Message drafts are saved. - Local photos cache added. - Shared contacts display and youtube links preview fixed. 0.6.11 (18.11.14) - Some minor fixes. 0.6.10 (18.11.14) - Portrait oriented photos display fixed. - Photo thumbs size decreased in conversations. - OS X: tray icon fixed in dark status bar theme. - OS X: main menu added. 0.6.9 (15.11.14) - Some network problems fixed. 0.6.8 (13.11.14) - YouTube and Instagram links are displayed with preview. - Location messages are displayed as a map with a link. - OS X: .dmg setup image now has a design. - OS X: updating after notebook wakeup fixed. 0.6.7 (05.11.14) - Long texts are now split to several messages. - Some network improvements. 0.6.6 (29.10.14) - Some network improvements. - Upload and download speed increased. 0.6.5 (22.10.14) - Username support added. 0.6.4 (17.10.14) - Photoviewer redesigned. - Default recent emojis filled. - Linux photo and document save from photoviewer fixed. 0.6.3 (10.10.14) - Inline animated GIF documents view. - Crash in forward from search results fixed. 0.6.2 (07.10.14) - Some documents (PNG and JPG) are opened in app photo viewer. - Photo and document viewer supports Ctrl + / Ctrl - / Ctrl 0 zoom hotkeys and image dragging. 0.6.1 (30.09.14) - Windows only: fixed context menus. - OS X only: fixed crash with disabled tray icon. 0.6 (30.09.14) - Photo loading indicator added. - Custom context menus added, including tray menu in Windows. - Default download folder to "Downloads/Telegram Desktop". - Improved OS X tray icon view and behaviour. 0.5.21 (21.09.14) - Loading photos blurred in photos overview. - Full voice message duration displayed when playing. - Multimonitor photo view fixed. 0.5.20 (20.09.14) - Loading photos blurred. - Default emoji images used. - OS X tray icon support added. - Windows version is now digitally signed. 0.5.19 (05.09.14) - Long voice message playing fix. 0.5.18 (05.09.14) - Voice messages auto download and in-app playing support. - Audio now is working through OpenAL library. - Linux version now is not dependent on libpulse. 0.5.17 (21.08.14) - Multi selection added to media overview. - Photos in overview are displayed in right order now. - Some crashes fixed. 0.5.16 (15.08.14) - Navigate through photos fixed. - Media overview in profiles (photos, video files, documents, voice messages). - Going to current message in history from media overview context menu. - Some protocol+network bugs fixed. - OS X application signed by Developer ID certificate. 0.5.15 (12.08.14) - Fixes in new photo viewer. 0.5.14 (11.08.14) - New photo viewer, fullscreen, with controls and moving through conversation photos. - Checkbox for sending images from clipboard without compression as documents. 0.5.13 (02.08.14) - Some network and protocol issues fixed. 0.5.12 (30.07.14) - Fixed diacritics input in Linux. - Fixed saving files with incorrect server-side filenames in Windows. 0.5.11 (26.07.14) - High definition graphics added (2x interface scale and OS X retina displays). - Some invalid links highlighting removed. 0.5.10 (24.07.14) - Escape key closes photo view. - On OS X now Ctrl+Tab and Ctrl+Shift+Tab are working for dialogs switching. - On Linux fixed notifies (they don't grab focus and aren't seen in windows switcher), fixed keyboard layout change. - On Linux all application data is now stored in ~/.TelegramDesktop directory. 0.5.9 (18.07.14) - Choose between notification with preview, with only sender's name (message preview hidden) or without any information. - Sending documents from "Send to" explorer context menu added, see a checkbox in Settings (only Windows version). - Little features: Ctrl+Tab / Ctrl+Shift+Tab switch to near dialogs, links without top-level domain highlight, version in About dialog now leads to version history. - Some bugs fixed: maximize button in Ubuntu, running different copies of app under different users at the same time in Windows, Surface touchscreen support fixed. - Added 32 bit Ubuntu version. 0.5.8 (16.07.14) - Fixed crash at contact message history clear. - Ubuntu 12.04 is supported now. - Added hashtag highlight and messages search by hashtags. 0.5.7 (15.07.14) - All platforms are updated up to one version and code (Windows, OS X, Ubuntu). - Fixed crash at document open on OS X. - Message search results shown below the filtered dialogs when searching. 0.5.6 (04.07.14) - Windows test version (no autoupdate) with server-side messages search. - First version of Ubuntu tdesktop build released. 0.5.5 (25.06.14) - First version of OS X tdesktop build released. 0.5.4 (17.06.14) - Font changed to Open Sans. 0.5.3 (13.06.14) - Taskbar icon hide fixed (once again!), sorry :(. 0.5.2 (12.06.14) - Two crashes fixed. - Ellipsis text overflow display fixed in dialogs list. 0.5.1 (01.06.14) - Fixed audio notifications. - Fixed taskbar icon hiding. - source code: ./_qt_5_3_0_patch/ updated, Qt 5.3 rebuild required. 0.5.0 (30.05.14) - Application source code is published on GitHub under GPL v3 0.4.18 (28.05.14) - Crash in other users profile fixed. 0.4.17 (28.05.14) - Desktop notifications bug fixed. 0.4.16 (28.05.14) - Desktop and sound notifications one second delay added. - Chat and contact notifications settings cloud-stored and synced, loaded from server. - Mobile sign up skip added, you can join from desktop now. 0.4.15 (29.04.14) - Default tcp and http connection types merged to one. - Using tcp connection by default, using http-transport if plain tcp is unavailable. - Files less than 10mb are md5-signed now. 0.4.14 (19.04.14) - Documents and files up to 1500 Mb can be sent now. - Some lang phrases are fixed. - Memory leak fixed in file upload. 0.4.13 (16.04.14) - Critical auth-storing bug fixed (auto-logout on app relaunch in some cases). 0.4.12 (16.04.14) - Window shadow and resize area now scale with the rest of the interface. - Multiple delete and forward buttons and scale select design improved. - Portable version (the same Telegram.exe, actually) .zip archive added here. 0.4.11 (09.04.14) - Emoji smiles support in message field added. - Hover-button for emoji tab showing added to the left from Send button. 0.4.10 (31.03.14) - Some texts fixes and autoupdate check. 0.4.9 (31.03.14) - High dpi support added, in settings: 1x, 1.25x, 1.5x, 2x interface scale. 0.4.8 (25.03.14) - No changes, just autoupdate testing version. 0.4.7 (25.03.14) - One more critical autoupdate bug fixed, users with not latin symbols in their Windows login names will have to install this version manually by setup executable. - Autoupdate behind authentication-requiring proxy network settings fixed. 0.4.6 (22.03.14) - Critical autoupdate bug fixed, users with not latin (utf) symbols in their Windows login names will have to install this version manually by setup executable. - Voice messages extension changed from .mp3 to .ogg, because they are in OGG container, with Opus audio codec. 0.4.5 (22.03.14) - Connection bug fixed. 0.4.4 (22.03.14) - Fixed voice mail attach download, it works like video and documents now. - After video, audio or document download Download button becomes Open With instead of Save As. 0.4.3 (16.03.14) - Default download path introduced, no folder select for each video, audio or other file. - You can choose old approach (choose download path for each file) in Settings. - In downloaded video, audio and file context menu (right-click) added "Show in Folder" option. 0.4.2 (14.03.14) - Network bug fixed, file upload on weak connections should be better now. - Selected items clear after message forward fixed. - Suggested filename fixed in save file dialog box. 0.4.1 (11.03.14) - Auto-update critical WinXP bug fixed. - You should install this version manually, if app was broken on WinXP. 0.4 (10.03.14) - Better touch-screen support (tested on Surface Pro 2). - Moved to new versions of ide+compiler and of some libs. 0.3.13 (07.03.14) - Crash on right mouse button press fixed. 0.3.12 (07.03.14) - Text and messages selection. - Copy selected to clipboard (Ctrl+C and from context menu). - Multiple message forward and delete support. 0.3.11 (04.03.14) - Long phone numbers enabled (12 digits after country code). 0.3.10 (02.03.14) - Bad reconnect delay bug fixed. 0.3.9 (27.02.14) - Some scroll bugs fixed. - Message forward confirm by Enter added. 0.3.8 (26.02.14) - Dropdown with two attach types added, File (document) or Photo. - Different drag-n-drop zones done for photo and document sending. - File dialog open speed increased in some cases. 0.3.7 (24.02.14) - New text layout used in messages, rtl/bidi support done. - Emoji auto replace in message text added (replace list in settings). - Clickable user links added to service messages (smbd invited smbd to group, etc). 0.3.6 (23.02.14) - New text layout testing. - Email highlight in messages and click response fixed. 0.3.5 (20.02.14) - Reconnect timeout added, must work better after sleep mode. - Minor design improvements and layout fixes. 0.3.4 (13.02.14) - New design fully implemented. 0.3.3 (12.02.14) - Test version of new design implementation. 0.3.2 (11.02.14) - First (private) version of new design implementation. 0.3.1 (26.01.14) - Rare protocol bug (with incorrect system time handle) fixed. 0.3.0 (26.01.14) - Long links tooltips added. - Intro and settings animation improved. - Bug fixes (protocol, group creation, etc). 0.2.9 (12.01.14) - Minor bug fixes. 0.2.8 (12.01.14) - Differenct connection type supported (http-transport support added). - Connecting with system and custom proxy settings supported (http and socks5 proxy support added). 0.2.7 (07.01.14) - Window resize areas are now positioned outside the window area, so they are wider and do not conflict with message history scrollbar. - Some bugs fixed, one connection problem solved. 0.2.6 (06.01.14) - Document sharing supported, downloading and uploading up to 100 mb files. - Download videos, that were sent from mobile apps, as documents. 0.2.5 (30.12.13) - Group photo upload fixed for some photo sizes. 0.2.4 (30.12.13) - Delete history and Delete contact buttons in contacts, Delete and exit button in groups. - Adding and removing group participants supported. - New group creation supported. 0.2.3 (20.12.13) - Couple of crashing bugs fixed. 0.2.2 (20.12.13) - User and group profile pages now available, click photo or name in window title area. - Group name and photo change support from group profile page. - Group participants list with last activity time and online status displayed on group profile page. - Adding users to your contact list and editing their contact names from user profile page. - Contact sharing supported from user profile page. 0.2.1 (16.12.13) - User name and profile photo update in settings. 0.2 (11.12.13) - Auto-update support. - Setup no longer requires admin rights (except first installation over a previous version, that was installed using admin rights). - Application window retains screen position upon relaunch. 0.1.16 (29.11.13) - Added «Delete Message» and «Forward Message» items to context menus. - No PUSH-notifications are sent to your mobile phone from the currently active Telegram Win conversation. - Fixed issue with broken long links in messages copied to clipboard. ������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/docs/�������������������������������������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0014370�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/docs/assets/������������������������������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0015672�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/docs/assets/preview.png�������������������������������������������������������������0000664�0000000�0000000�00004611107�13262451251�0020073�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������PNG  ��� IHDR��d����� �� �IDATxkpOw%Ylɶ`$cY-َ1M 1ᄵnJRTm$g go9UyUΩ"U{̒ k `I/`XF.eYF3[sӌ4U%43_Ũϰm���������� �����������,T����������'����������'����������'����������'����������'����������'@,qf ����������90f *d��������� OR!;JW*d���������̖|T,S��������� a\dNv.YIJ ���������`2 Sq'v fڶݎf���������BjǒiN/Y*a����������kJW7̴u:COB6U;](K ��������� 2z8aO|ll5T/r ۶}!!wHc0dvv���������`:nfiOCaxYTYöS5c c ����������٘Bֽ;>ecfNٌ+d 9ߎ]/ Y����������M ގ aWkΔ,vw'd&k����������c!߱aZt*K۶e۶,˚RG����������`E O3 iT!º#HV%���������\D*hU ?p|0����������0ٶWfNZΚ7>ud���������|*MN&Uźc,B���������|?mk4SyN���������|u:YY#S! ���������`><e:6! ���������`K-VƁl$N���������`)8>/TV VfS����������b3 e:����������UsFM0���������B30Ǘc����������&wќO䴽ltuuWuuuYmڪrUVVNg3u ����������եO>Ļi(ҢWSrRΆ,���������n_zU---nㆱژ+d���������̺:]ֻ?Y(Ʈ]6롎gC��������� HszpU7hu%f����������S5Y(0v٨aIۣB���������*=+c]����������t4?XI2g����������L]]֮]| c%Y����������Y����������sRKKKPRt▖YQd���������9aڵk}ϗP60����������X9ce<,����������غyU)K,���������G洽'x"e+3& c]cR Y����������.05_*e d���������̺ da+>mc`b����������Rۛuuu2 C^sa۶l8oqd۶Dd۶"r=�����������ȕ~Y)˲dc Ð$w<,���������<!���������<!���������<!���������<!���������<!���������<!���������<!���������<!���������< H$&���������`^B����������$+Vu���]]]Y ������0snܸ���������<!���������<!���������<!���������<!���������<!���������<!���������<!���������<!���������<!���������<!���������<!���������<!���������<!���������<!���������<!���������<!���������<!���������<!���������<!���������<!���������< v0wa*vW��@ eP�������A422P(4���D"VAAf;ȳ_Wjii$,������"a,��ʶҥKiש͛gGҢCȾxcc*++U__L8q.\ޮFmݺUjIϟWGGݻw/�����L@v Ä��SPH@` _|%=s{VZ^M]kk$iٲecxxX/+$]ڵko\9L>|XTggg8p@OHMMMIzG�����p̮��_O' vݻW{K/4}O2\:LK:x|ͬ*uuvvgU{{{m����iqS H$2]���Ӱ_~AI>__ɪ:g<] TD>pvܩ!uuu7 ǹrڼy]o|C555ѣG} /~:*٦&{0 ԫ[K/g?Y �����e���v/Z[[//:{OS544 Նueի5<o2n{eXs۷ؾ}ӣ[n%l?ѵk׼S=v_X}:m޼YvҥK|˗/g</ɓ'}8=w}!?�����L,���͛7W{vvvwIhK/%TJIøa;^h2 mjjҋ/ :SVNt۶mg=ITMMMWZ ;r:v^C;'^o$%Tǒ2u׮]v;m|)..=_1,������s��`A)..?{^KXĉڵkW0V{M^|Y=\ڹB<G{g:t(gϞw8qbJmܰs2S9^{-iray_h޽S~'LcݱcG:[nokkˮs�����`^B��� NCC~|xӣ=' i}YBt ^~566SkjjU߿_|A_&=>w.7|s][3Ϥw*譿m6ڵK`PZMئTOO^y__^xᅌyӦM W dG�����0?��`)..֮]|]kkWo*58s666rSWWv8 mkkYw !^z%8Z[[w^oٳgӓ|ڿ?^y8p a^Tc_wͭ٩Iݿ7k{{V^1tQIJ7m駟θ/Iׯ_z�����00d1���+V\_ew߾}ڶm[%־}|am\VA>0szg|\v-6U<xP{O~N1o߾ם[5V|igyF?ϼж:uА7m0?qVmvuued+�����P! ��Eexxm۶MNmWڪYJ>4n6/IxuC˗_~9P™ѵkt%ڈs- �����7Y���,H$-[LtegϞM&ӣ7x7_Lȶ 4ݻuq ɳ>z+ƙhjjJTd:$s:󪙇ue;vfs̕7 {����Xd��$IMO?tB%i߿?puO;Ωկ /䰧U\\͛7k s�;v,c.))z����̜/Qoo5::*I*,,˵j*u]ad��q׮]qo/ :pjjj~zkÆ wzٵk*++w^ﱩ̇ӓ߿_7fUWWþt6wttd[jkkKOw,����rG|z{{.Ȉzzz֦rmٲEWb#��ޞ07 v|c___VUK/~xx8iS8ֵk|߯g\v ^Xu>S ����H$3g>j>{{}vY/W\Qkkn߾-IZt ggs9����rI|۶mݻ}V̞={V>Cwy'_>}OWSSN8rykkkmez/~UMMM)W_=VUUÇuᄊ'|һ֋/[֭����`B{ャX}?( gӕ+Wti httTЩSf| Y���KǎӥK$E~??9wFZJ:w>C U^|E}׭[ܜлMMM:|jjjt%b9=C)  !{Ϝ9Ƈ2=F7o։'ꫯ ڥKs%=K.^K{xΝ힞`PoVZ%Iڻw^yo[]YYW_}mr61���� 3HDUVVZoVoo:::_ױc}mQWʶzsUKK Ud��0/K/ ^Kz=:pLF:t׏gw޿Ia׮] ƙ}CvvvBِJҁ|A|˝vn޼tvv|_T����>(!---C=-[e˖J:qoڝ~9sF>`>WglYϷ�� u޽;26~-_\/BV޽[vJ9m۴k׮ߧIeee~= g&Q\\^>lu^xQRR?ϵo>c˖-KXoվ?500 9����}駾V^|+ &V\ݻw?3UUU% ۶d ~;#۶H$"۶Dp8<"M/J4[=4JUVd;�JKKnWW=sAkk7Dq2%%%ڰaCYmmmR01Ԥ'Oz۔iӦMڽ{7|ۯd===>x=>Ҷo*ﶵuִi&Ǒj:m߾]7ok1.fG|^{W )Ç{O<ĉ=ؘ����ȍ&zKKK{XpX/_)//?'GI>UVV={pr]@@ey?@@iʲ,!0$@v`_nH)I*XZe*)\GJ2~Dj\7=�S%�����9r?tLw=OhŊS|٩SN%#[XXzHk׮MO?d1*[]>gە8sUX@޷FmRQIExo��������ʕ+Scwy >c  %I---ޜK.U}} cas"d҇]vڵ"O޷f��������iYUU dA/Er >3yVm[ Qݺѯ+u>ߴ~aV�������?}cEEŴیo͛n ,fD= =awɒBjUZ_j%X��������0 Pwxm.]w?~U ,f͗z{y5ӄcOm|$K|ݴQũPNuv(p8@rUVmԆWjIeʫUeK+ Y kzpJVC}xoPX5w,K܏eIy}vWG(.Wƍ^Uy��������83]"G Y=ۆ\?>?6_P${nzʾG[۫:{N=w* cv3g5*iCyP^>&^oFz:u9mxzl?طԊm"������� ;dz߸k.|W\Qkkn߾-)Z=\WWǼ13nR7裦fep)gL[R`P+yQw-fnai^_'։|?ΒO0Z*e7to}a +Ṷ Sƅ~ cK+ֽ(sLNw{j��������@n/>+WFGG500SNs7g, _׳o۳݅Yt]F;ΛWt*$N}mKu=X=1o]i4_^|:,aǖ'4(>/ѝifWsڄn:F וUuD:64pRlݩ<UK&d>oOHgm_r.�������� cVҵk׼VjojmmM:ZZZGg׾%zG>П?d |a$- uM[|a$dw'+d<lz,I+]ٳ XIZRRUe& =XI*ܪA ��������Hn:iU+> wl-6K_?GPNm:s^stφUI۽{]SeYjNUڱ%.c48~)ۤ%YN^p֕&fYĸ��������;j?W8ypma<yXEEʦG,l ~baKVhW}ey]wo]oIcvmX!),{ܱ}_\k|ˇu]P6+>\T ˳ҡ8xR�������ٲe7o￯~ 2> ߲eKN:,]42D-@9[>L}I}I]ut="I郓ZR Ebխ[C[! uJ6rf\fMe+��������{ѥKzzztQ=Cק'O&sOBbRWWSN%^= >RZy<Z^BJK#R5N:׬kYEeo-5Uuu�� �IDAT*-+^@TgoQ$am$"#2mI\�������\i&}ro޼&YFժPqqtLa#aXI*//gsQ0$xs.]TZvlvmN!c eh!)XDR*,χ%ݡ`ݺ3qo}~ c͊-&ƭS^^,MwR:4*[VN-��������;ΝS[[w0 _(+Iθr}_eY9| `e;%03b*N#vr|FMXwӗOc%[}}S맫Ȼ!yӮ ��������fB|+I)a=c*((nHbƍ k, WyZuJꪚZיtmA'Kp燓W<%kax"Wt̯��������ҒJ!uxڵ+_+**}M;v2Yab̰Q}r.r]ZfV,_€R UoVVk AYv<]:CSVm |~@VܹRGJJ[vJ.ถhMO֍jغA[oе/uїWZ$��������I5z ʕ+FGGe TZZUV鮻RYYL׿.|ߞ.u.OJaڽ.M]?rDے"l;ċ[$3ⅺKެ$Eu:gƫ]m)fXKVmz\:vUMjo?�������� c7mڔxYYa4\rE}hr]]X,%v/E)w6ծQb!z`~/|S[I KuU*Y__KzN}]W@x*tKU0K:|F]A.\Kڶ $ݖ��0ԩrs6n8=B{ ���X|Z[[Ғx0s>}Zc޹lav҄q8mD"m[pXH êO gmبo*lOe*)t<_[C MLɖ¥*\zj! %2m"A €M-]ZB*�Aiii^$UVVxz{{}ǬZJk׮ͺPHIÃ>+W&]whhH+,,TEEjkkUPP0Yܺ[oIUWW7=:::tMIRuuJJJrM.?[sEXWW=���A;9ʴgϞQn+Ȳ,'4MY%40d$yQ!Yc.)Tɒ鴠2gŒe4p�bpE?~\### :::$Ijll8miiх [nMXghhHO… :svܩ d̙3:}wb@6mٚz{{c$���9Sfs"���kiiQsswH7nF.\Piio>i]]]*Uk֬ц  Ңرc7Q"P({{z||‰C5����F9m���jkkuI}luuux{=IӧU[[6 B:zhF޾}UUUGyn]]~ߩ[###:wr@~)~6l'7nTyyBм���@4=w\ㄱ3cҥc!@���s^AAoJR0hƍjkkSww0Bf구,mxUWWMAAmۦ#GHoͩ B\2au  $Elõ ι;}Kɏ?MeCCCbי~2q!5knݺI&ן?wS Bsц+՜ѹt>����3M3-[,>uuu:uTB([XXYC ��yahÆ ^hNGG7lUUod҅'ә7Ko$iϞ=KАN:t(;vNEgΜI'L?p8r:::TUU[OcO<m tW5WUULtK淪J{-i[__p^ΝS}}mrJ'sW__y8q"i_H6}篱WN����w}?XaL }K.U}}֮];]Sd�� tvvNА;&)=#joo~c9íMܹsFN rNIБ#GciƍIH9}mmt'[N;n*Ύuww멧JNFFFS9sL>CV߆_>jʩl3L>[{}ŋ)__){mڵkڷoߴNu}' e��i8$΂`0H@���^(&B‘۷礒իUVM"544x՜.\w_?®ӧOxˋ}v* ܹs޲Ǐ;Lj˖-Zr|UnѣG522]p!mTVII:::tY/~wo}˷~./6u B16mllTmm ߯WS`0CO%Xeg+G5km6UUU) >#^UkeP(w}&ʻCǎȈ2���b1% ���3~w>iKIjhhNmܸQ*((P]]]BSOihhК5kTJNΝ;쎌.++ӣ>rz}&sNoyOOOwuCͪ*=***vVIc=mqFo*說*_rJiϞ=9yO5|҉}}ReeeOzٱc͛7_& .x3YF{>UUUz'u3V����0��`^xB2ِCCC^0TTTi7 ɻcǎiUG%R7vȟ6lnz 5k$tڑM/})JضbJ7$pQQQ@ \}}}\_}}}!c{䳕NSVV}o}g)۴iS@8vNLem۶%l[YY]H122����sC��`ǽ ICѦ&/ڹsgNkq͚5Ӯ;&]'XbCիW' }b+nܸ˗OɆM5$_o+6xRGGFFFt!|ֹ$V:OMMMbtstt4en*=Iכ7o.����@���T~m/KU;vǏU~+_v;g';j*d;whJKKt;Lc=w{_YY6m)dF&-|ޚt|����@���N!z͒iyyyBZlelE_j0IP4ü|0Of+((о}tEyuܹ3'l5f32?����@���J6իW###:rHڶV}ل cgBcc㴇UΧءboƍڸq|U;δMs9ϒ[����X\���� c[;_6C񕔔Aw|߶h?r%yӽ>is=����,NT��`^J`T]]\| Z?X?/ cǎ9[ݭndž|sJJJm۶I|]P^^N?޻nݺiw.Xn7g4���E Y���yS JJJTYY'6૨w0V윦MMMJX/ KPh&)---zgZZZ-Ǝk_/)?_Yu^RRz~ŋK֯_}϶8w\׾ Z����X],R$n��),k0#N:6vTr1ocWWo?###:tPm\<򈺻522njkkUQQP(/ܻw/iz׵qF-_\~卍*((m3SܬfiݺuZ|B.]UBJѪlW(oc"ߟz]x{}ҽ;v_.UVVjǎ:}M555*((Poo]UUUiϞ=c����@>.rn��)CHDO=}]/8p.\0=󫯯׹s4224Ot XWQQ|C~ռ|^pݱco> =xPv`` ����X\d@ Y��d@s{Q0Kjll-KJJeٴSmۿrJ}[ŋ۫AoYaa***vڄ3y3y~R{uuuW{{oUViڵi+'y|իhB ջ7onj:{uZrۗ Ny</}JXeݯkMx_KeP����a۶l8oqd۶Dd۶"}Cta!�`)((PQQQ^%I fQ`9r.yfa�����vY)˲dc Ð$wQRI)htt9e�,Raaᢩ37"����`<x�_���9 EuY �����Mo����c+WԞ={f�����`0g����������P���������@���������@���������@���������@���������@���������@���������@���������@���������@���������@���������@���������@���������@���������@���������@���������@���������@���������@���������@r_?r$���fGT4eߖi*0lˑdXɒc؎dc2" 9e#˒8#a(m16a;[oI +`i,Xk=ysPV q ;1GcD{e8 3đْdJVLGIoX#ÔƟqv|}ߦusHF%IV(^pؖdD+e=JrޑΧ��� *z6~N؍|;x+ş'kjb]quok:!pdn_#fAC1U^\۩,~'_ebQHޯy̶_k#Ma8L\gHN#3.m2a8ٲȔ!G #"p2Ci;GH5$CeHEGNrI=!{;&{HMǐ)3)IsI#Im#?_?ey4���cIKc(옲hiɔ +pX11=3HdLiʔdrLG%dҘȐ!vOԌ 'T4 V@!'ȨӐ1z[@FGGć#LS1Gx3 b5޾c; l1Ȗۑ鞬D:Uyɖ¢rUv/2SZ\ҩlhN}<O;���03a2f}HX8vat{4dO=Lj +bfǙ8LK3:7]�NFR dƇL&Yn#;.dLO'&(Iߒ?nk($K̸r/؎,T4GdʲRX\{48<"\"{,%ky$I'{{hW;#[6dIs1 rELZn²7E���HZD1h.jZbɃcF;tuG^MS2 'zaJDOLҒ%ѓi)b@@xjY .[d]ɇ^krLSɉ&52cxHv$Zbj? Ð\"+n3ddv~Ů ����Ȝuʩ/&Ɖ.LҌgg1fO_KQ28D<IJp}eN;hQ1dzIKq!tlڲb~~XDakƍujkk.9Ė%َ#I gbd/9ړ;t}i4lkiȭa + '(d��T؎0 YqFFG5<<q8N]]]WXx*xi(Ȏ؊8 R$bkҥ2,GH4u 2x^HT%!pHDiȴ,eˎز sbH E�7ceIno?9<����̾^2nc2wGm0M%k<mǻ@G5s.?.SQJڜH(OA+Am]ImEl8QC9fjO1 ˲,mI#c[p8mVOT^A"o2,E&)z2Y���$ ]m,]RipPnڴ,K7n裏>۷e۶lo~֘!tl)bѓIvQPn_W铓j_iH;PM71'N1L!m`D `#HؚSlOONVnMʦ u' 2����7s M},gdfZ;k@ p+ul&&IN fǿBu:l|ɇv܄ntf*`eDgm_WӰ{T0eDvrl%C"cY{~|onbevᰭ\*Yl[2LS;2^ɝ���HʶUvMPH ۺx,RvudDQ ²eZBY@tnWӔ!{<uOҘXS4+/މ%C{7'˲d_)qs&I`嚨ub+%gpfj]����@J9i< Jwm'bM6|m&8̡2GG2̀"\igw{C.a`+<N9\ CW{ eڜlS4^18R `Q4 u C.ɖ-2&&VTXv:y~ p ,Sa)]a5!��@ZcccsiN !d#Ӳ BOǑ)ɉ8rƑѐ?YbjdpP13)҅}nճ'c_vC(M\m{bLόd9Aq3ht���슎�3 x$#Z3ce\�$-64U8))cK‚\Btcp@#Q/MՍ\]t2x2`H&.Ď/s3=VEϽ8~ 9 %Ӕig10Lz 'ũm٦aʑ!0eLjy@���IVt?\?dGvtdؖc<m,ÌVȚjYlj֍A "z؉ ,w"%[MÔ-GŖ{uxHDiz2�� �IDATC[ò#jى ?'~Mo1RcƮ ����[s ( ' .`6R+ۛL 7�r0 (+SZZXm=ڦiJʮtu=Gi73sn;SU!2}etCt'~ɒ!,TG'2hȉ^~liN&8v; p9!1˒i(/aD ��kzO`S1lȈkѫG9~Up Yd;Rg(ٓc[A#`TTP<'Fr$Q_1$*F jL1hP"#"\r;;3U?ٙ]>y,QGtPJyC*8Vw_0TWUQ[WG,CI呵‘?%[nѺ c=dC"D!B} ;΅a]3dlɥ]ѦQJa;b[RBH!e(ٖ Y6ٜܞhڄl.*5?2$2jYBԗRʅT5 gSh}"DVxc%ʉubȥCHȆ"D!BD&!d\fzFJ!MꆆHrR P BBRįD:!N|S %O!71_뺗ֲ,oXR:j+NnB|!™t!B"D!BhkhB`D*iZvlk pܕҲm|Mcm,c4McMeYyb[گ%[rvLe/þ~5kZ-E2u HBJ*!$dC"D!B"5NnD@iّb9|PR h䍵͗V 8Ƿ:]8y@<)M~}B'*<de o!B"D!BMRC{oZ|bMMNL`趍kluWeaTŞҔ()sozԓڭ[ۺ"StDӃp8f<Mܯ "D!B~ɀ\~“0XB"#٤klRv~%<56˙J+<)b?4ڱN ډM ]cpM0MF{OaDJ&4 LzQ`K/qi3¼}Hfq8 /`m )r;D!B"D!B;ZB=%}TqS5P]Ӽn!iT7S<J2g>#0 kJoSxonmu SwD˲lJ6lDmm!DJ؞;ґ l3%=/|GE/B*q׊ σ "D!BM ,R?oɌޔB*QŒ&ID4,;, C4;F;gkRM$(@shO 㯃mfɺVJG2 M 1(? $M* R) MhXrφRXN@ њF$<C"D!B %@srB~Z( 9K@)hJIKo@H4MGJ PhEԶ`n<O!m#@ Mi趮{խs={}vzBWK[O(bZwݙ4QʎBWJb(B ZRC*P%k9N>bv-_Jmk}# $dC"D!BB*Ea3mW6tڕ;dZ6lVF ,Ԯ>^p:49^Nԩ)-BP]]͆oGhNO8שPF([BFίB .0QDl# "D!B"Dr[�f)\o>䘔)%,'?}*4ZG5r 4/MJJ)ZJJ8ܛ%]>SA%+Jg8{E];񚮥90/jY992;mՎm!Hn^CbJVѰKF ^׳ezD4QBRK/C+BTtAtA,'Yl'ї"D!BU fS�^eRVVFuU5M1ۿT[T=ef{f@f2IEEjq{,fYw>>f/+&ӷ7\d[6t<jrf 7@MJGkG00N݈P`Y@Ftx$D�i),]"D!B"D!rXb+w|d^, 4lml@J[+E/ר xck޹F(DD MФA\ԕBWݞ-@1d;_^&F:mb\)&]GZHWr?FEV7[zl0_Ӵ#W# ^tD,|@5]l߂hD&XeYĥӽ{w͛T#GUbzQ&+/9D!B-W#!eDǦ4 i)궒0MH 4ݎ94'?<EZ&U?)W4FhNUe;7of㦍v,‰tu sJظqd{v Cw9[LҌhe50HL$B)4 @7RhciChNlBH."Da4wJp}{[]GA @EGW(ķ&_.I mE:AEҵۂe%Jٹ@r#cJ4E®H)_Nަ4c :'SRaF,XB͟zlϹXE"%@9y hteg4git鴤/UjmR$7a[4|&MBRmZn=X1I~hBZi;�"ʤl؝5l1jXMVk&B!D!B P )�BDS2CGG F4d\U/ij*7לaPUUE" @ BC;oac(` Xf RFriqs~1:Nu,%+ KSIE>UݭxO6',IL;DJY2cz$Љ3V$NTWΛW9dŌ\fuGΤ "P]]Qz=~ߪc.Po{C7O]�0*BCdvtB5Zi"='%RcJõ\Gi'OI˲PhB#֔nz.۬Em-B٤l)/K_"#L:ʊB>3U!!e%iXm>MwPXNJ;+ mN"@J;*VIw?$v۷D9+wYr E-ߵ>cޝ* Y?D!B0cM<;]5{Y$XRb&Raww׳nMF [нx7lbҒDSQPOcc#Ruu='~a-+#LP<=%䂢Tm#OSʉD#\-! OP>dulyUoA:fDL*l54b}?`1F)΃:s;%xMiݝh|GatS} 9Q�\E*;;:H E4 �!l#|Coj>3E~d(!.ߐGEFuAQAŐiAǸ9aoJR 4<Y\;NVIhLLt#,aeJ)'tBjn\@9}�U$QC{d"zJ"gl_̵7 YVm<K ӂ;wrOC4Mb1LҞ9FgbQ*E"? jf6*>E/U "D!B(9j͖e9#;2'L&ct;r,a6!۫ڕx<N]m7N3|ww̅SrGJ)b1G +rJ#i]OR/O:Yg_"rQ[)eY^mVbK'@ "E\UEhQE6VąDGعVTs#-;B1ywj9KU*7opk-+oaݰr9|&߃% %R^kf.Ya~"OyvesKǮQPA<rV`ZKs|Ұ:-KˆI  编J:|m춯SI>CR}1(iKJ|vswLPߧꃘd=|AΝs6bY&i" $v'sRrm><wn3R|^j5ҟOD/̢C"D! p1a$I RB+R(tðіLia`9!4Yz5멭HQMdz$߈0\@]oy7<bͧr"BhLQkYi+iHaTN"6-ʷ�xp^}5ʔ(%._N<am{= Ff`@JVDQH\HP| 'sA(%uġg0:vGއ*4|$So9P?DR |~($38,<jDMZB|ϕy|R[5]et y.eSNe!-់u}huhﶂviIl”&h6I+M{_(jBvےY$}Sҥ;#v/:֕蓒4ZfsHiZ ?H+;ubvWx|)O"D!UЕ-l&aC߿?,|5k֥� m{GzR9ׯgÆ 6)`xvt켮B !Pjngy{ oᤨEJw̗$af1S*@( B a~tNcADC#'MmXg7"%lL̨(#"H"B'F=]Cj-|wvMi/tc"|"D) "`sQOYn1HCuL)iqvF%lۈ4)(iZBHZ @"1=ߐxws КeP )tCcM,ZelSY9L;uT.uVoZÚ'og?Vd.Q)LD2L["!) E#L&YנcuC> Rq!B"DZAyy9;^7v.vWR+䒵DZ!;MӴ(\ Bs<7շN4D:]hEyu P^!#RD"Q[f>o ` %XvZ K".JQKQ&5*NRH#GyWY]׾Gq[ü'~Tgn5z~]Dq*w9rˤne^p -A×yk;ro nKYu5?bũsc^u^g@=վ<4OͱJ Fy8{'pA`nw~/_HzQ~Tu?K *A}Qv[" vs};f/SWޕu;R.;(E^Xf*!۠:i2# omWg3ϿrT{w=ݧn|̋,^Z@e;~38aG3F'ǎcxMx1bőa߳\= oy)l UArG帎6¢c Y(J80?(8Ǜ˙K'46>i'g3/{ +{(+c9قbnJk 6赗R56PYӛ܇=z[Xo=$?}H$s?9so<8>HF 3 UḇtI\Tְn{rT\ [x>ս%}n/?XY嫈76H%'О̚mJ[_;<?e?=c;Ǜx%^z >^r!Av}y^~m$g nB˴7mhEs<d :;o{qA- B4sƉxuV5ET(ʓ]0jQ $a(,MHv$RKE{7-+kX8lFC->{GV7m9! l5DKB˲Rb&ey44l u D"4B9v'2Y)%0 LӟS͆~H2jjj>% L+ӟ_ff.7&jҳ=1D!B-^eIDY9J))++D�48Qq9|2 掙t]!!<Hw˪Mfz a Dtoߜ2B>'_!$Ѩ'k쒴iǫ�ۆvE(K K2%-jiP&a2lB:{ Eid֙ p* ?ƈ\)9{p,~~jb->ywnZWXp6-#cq7>ӏRG<~xӝ g۳9XtH�?<ԇKͿcz׆#+M`}3FL-oa?ch=U*'ܔù谝m៞ʄ|;KحD98},~>5ts߹x-ß;Cx&m+㦱6nq f};]x[1mD-+wr(g$Q�UM3jVsp[u\;K9п4~5='ſO߀9j`ٮT&ܐycf6zozfTfW׳+A$Vi|GM=AO獯czZ_VD]rjN޲:=s�ܶ7/ۍ{tW٢59{?sr#p0\X+E箛w+yy{0%x^M aŁݶ'/7߅ja) R?4u >rw.]4G8"({NqĠvjaEeZſ! C/{ T҄�%4'e^LJǚ57+= =?}FȶҳS.4͇ʹFy}NBiԕ):Y1]{Ыk7n[جY$#E6%ysSxצD~QH Eo9SU�  #"B* t$`dJKRd:dF%lw0 t]>ai{t eF$ u=|imw#cӢW=ai&O&yIXS Mr;Lbw{I>jWm"D!EP HߺmRb89b HnPKjS*/WTy_ٻJȕ =b`LW IG_@lKu#s +~eY$I/Z; \X4i&#k`&BJĄBZqUlrwv [ �� �IDATLG,Q^H6ZuK]+Xf=/mbaݡ!\1m0n[W\¤Du{r5ܳ9Fw?T9/);7 ߐLmp&9oo*SG)^q=qٳg.&켺- OpV=]<Ή/y񰘋-:e'b W^{e5}Rv_Ԕ-"ltޅ9{/'~SvT/޿-nqMsab 3>60rVyx߄.RlC^9r$'{d$CEWpŽ~C/Fd!Yqi~X9$�K 8 @vυ 1f'dd+^y|kڿ,s| gv ùw[W< 0y?\{KFz'=`;H]Uֺ'cfE֥�.veu 6>2vI?C6W�8?is҃8HH:Aq}No5pH(>} !puB럷<rս>J0mqѿO^[3&pEߧo-o)&Kf9^Ƚߧ[eqA=YLnx#�>Yc3Jr_{3plwXmEEeZS{ܲ߀z!Co.ziDS9(쿠>T�#ԫPYܐ[v4=6}a;g76t\a+RK*"u ]*_BaۺbE#HMx%MAKK A&͙(ikQ50- 4{A)O;"Xko{N[A&6|o"ji6*%#&#<4OdHu҉;F&I{r3#Ot'-uC5k7d"D!B MבrT>4M0RGwLy�jZ1d =Qgu< lTVUu4cjbwsV Fijj"H C*:O RZPVl+b\-l{g&q8mxo]'6!blbR&O>^/*b0z4R�fk1>zwF-)/>C3 /pxQ1ӧDo}];%G:yws`5G]:~<|H&)8"W=;osƋw(qxE:S>9>7 J)[rmv\տ/oSa?"z&}1?<5ji[X^_3zwvBVF6O5S_P6l$G =9 q"ఫXL=K/~biϰ}}uvυ몧K{^7g0&_r:NRQY8>G**?S1uNjCg<לSW!Ɲx.LMv[5Nso^gL~eԤ ᚖh]oKX̓Qw=F�`4&[z{߸4iV=}\Va4 ܳq%+c]K}[Q"wo;-]wT~0LF^_`(\ֹ6y>6G!S| 9O�z=}~s"q5猟ΰ~}nSbx%L9%m QRR?e䲃-H#c(P'S 򝣐|a5EcgKP;P7W%$E݉"^e3[f1տ,ITh>U0RtKMuT-%s1'3Z93(%As֡ykA3gxGFѤ MaӈZI)I$4ĚCi$Ihhh`۶mlۺ[[.LJB*jV&3sD"*++ k/h1Ge{zNЗ?Y!B"D>(R޲ F: P ]R-ä@Jom={dTVVjn-5ڼMᒫ^.!?S#MZeca,=dMӼhd"i+nkL>^}73/S%[78gYsGW|5|W8)7?++3grN;{ƅ,}42@t3͜xrvϛS}ӛ蛠ˏ ~<0nM`YWk|۶07{)[_dmSәs'~̍3ۛ֕o-u֟ks~!jpЏc뎠L 2QE>3=2E1{賕y3_wuvηX{(;s%Ytڛק : ?sbfܟK"t -S6/W1GPk,o@B.y;я%3ݲΉh*>ݲ6}yQ1< 8MK�9C3̆|䢍2_֢N ̑zйS9YߞheMoR֏y4Oܞ"cg 9o s'cm|jg}VGei=hl}V}yʱ!JDKȵ 96L)נv)\|BP=#lXXF6nm_:/ۓm)Be}L%Mu#4LNEKV{G7m<r5VCґՋH)߿?;Οrp[oe]wrǝ?iӦ1rH$X̖SܺWW~r/59mz sy!,N뗴u3"D!P " URxopRy){+?PUYIyy9eNV~gxd>H>_-zK !DJ^ɍ,VNv^-ݬe uc:eBځ8mMڴ%\aM?`7'dh`P7NN6>NjE ~UuKH ggQj_θ zP] AlZ<NSOfx,wdg&`{cwn\۪|,zaU].,v76,"?:y|kynЋ9Ovχ۵iK!S >Y~ɕ~~W #m\ZĖ>moD~׏JG%~zlq]Z_2?=D~e �grz g~D.Q\[V^^7ʹ(J[A[qq'8˧r֑A?YgA9鰉z_|^(eEeZͿ%m QR XbpvL&䣭_ןхAf[!:Gƺgx4)pS@/l-(l%RK= "km/H>Az<T#4"›Hp/AFoXآLe=Ammɹ~Oyy=q_uu5D =nyxE+~󩪪Jd<~bwҕ+5; iD"OMux{ڸ7'h~=ŧ۷vh_w ݵ^ ;g~{"Df7LGÕu&סL0p!58pq_ 7 dM7HCcޮWjlEqi(use5T(lڐYsեD!e{fHz4 ʒ%1j[1=Y cw{@dc|#ibN{$'%k9sR]9%>i2ç<b`\2"%7{\z%)/1;*j'2[ gT<\FwFn 1 n6 34$i2/lc=[Q9:Oegmk{t΍=fc[[^~ydA}r];muw?9vo}n.K&QЙdGn>W׹˩!GIE(P|݆=g@4͏վn9h\mo>N}|Kun1̸ۖh0.b 3S˰~MkS8n;cono2o,t)n3.ŷWi5 1P%5K}3 6ٽN)SJdȔ*ce.[1�hG\e$iqUn>i_axTْu7WQa_Wkݵ9S5L}mB&JLv pJH$(!mmIZSSEf/to*@DR1;v 7Ns2M$B?^ )hH$B4{Z4IIPp!cq#t$ef$F24Mo"X,F" )r'{2H`:D4FWѹ`q!&4@UuW4Ӆ_0I֡sUOT X-[lmv^et:?uM<um*kA"Rk͐!gٕe _e[~O܉>%17]Z~~ӯ!=9fזym&\UȾ)Կ(!DvPGe'MQf(MCjig24M% )Rj4ymͶaD'2K1 &J*XxbjeaHmíP}%D:q );ʮm䞠k{=OQ@=m0PBd[ZX<|I (}?=Ξf>n#*�k)9y9i+PC|mT_;̇7.?ǧ>k;ܝLcۼ-WF(Yܫ5 rL1:~M|+/<dP &[[.@,X\~2�^u5ϺmO<e'�C8d8q <xw‘xtTG/k@3'~}+LEo 9Y5Uu)· Nu1or;Knj 1;x ^}N`5KN_ɢ_u)J[ߡ_lggK-qM˔5DOēX,F?/ڞ(iJ2 \Qe;v#dsiYYǹHYP P B'3ֶ=S3)&pʤ$I/@-%WQң,J R+a굜}ݑ́(9 Ysc*'o<ByJf<E\Y*MWBmHo!k}bJިkG?7nB)Eyy9`6)kdI/b N1~4"l<�䌅ihF$,***&O-fgN6y2;'3?Avh<2H د776}əM ~ʢ0 <B6ѹK57>]rM[yi&\˗zDž`=9c*xf~fyf:uw۸ u}B|#p.U ݩ- ,ۘ(74嫜;E9ݛ=GcECz~̂ 7&d!9lp*%/2iZwDvQXG","ZVFM\ -B :]=/` KHCJm 4I$B<mJJtvhud2\CS_rR>"1{k֖*3&3!+G}p(J"|]Ͻ;Ud= }1S~snWLjv9?;٧J ǨICw8iZR*;%X$wD43Iϟl*^|cmx[|_~v  aB/H믻|Gwx銙i1kӼ{ 5tWlgκk +%Q<jW߳v6 ׸%B'Iv7\Ҟ'/kk(y]Z_Nyxr[u==4#S8l X:go+X4g bJ1a9Ա9F5<g(i/*_{Rg} 2m іP&4Z .yulhg}>)<k4 3HSJ!HLEf]D*1s^G][/_\PN̞MīhXN ճ=rŢ1w{Us|`d6;Nvd[X0X>҉Z9, U@(&3+(BVZBd8Fď1oH?xUiI!QJP4mzOCC;yߚOYY7 6!!x2nhwNa~>wLeeeD"΋eky'vo FҢY:<=9w~eKv,_/ÁUPrg/ !Җԧ;OSL#ѭ kε)S_vpyF5Mؙ9+1 꼊ɋB=y.9ۯKgxHʆ.QE2ߎݹ`ҷsYx2nnH>7t ?~im)GeT%e{idl[Bh-[,I,.ݫi']z&3PV^FyyxMhq>Gc%WuujBPUYeYԹdAۥDQ555|l޲%-vbrR-|{w C9_yW3_l̟ >hf oNWlùқ``uMS/7ǐJR޻zQh'~F_do{cGhyU` 0p\DvW0ffκޜ} s�<_Cy0ُSܜ6&qqؐ˸ڽpҮ=yڍ9#OڃGq㳫m\hş7gausIR\] nD?r"Wxpng?oYB?j0_ygmγGot-& Jœm-o/OqԼF)}::eZ$ȶ¸ ͷYwSA*`U{ : { *?dr A\1!yrs͖]tdGU*.R"KH MPK v՞n\0`k^l2\xmyOmJݱ㭋+̗nJ&HTd2ɸqhjSVVFN(/4MO^4-J))SiDx3@DlfN1w?J ^\ \돐u+ ߙ}C܁a`(7͋A875GɃ;y7Շ]_J�� �IDATVЋix,/GG(/#͌8ūggvңS9}:R12d|K2+h}v A%7g8h?8+CyϮa_\:o ɼ$*u%i]:/2�|]}Fu9YƄ=jL^Y`OMس~_RJee^>D"]+Vs"<B*4Ф(MC9st]O HQ4^_OSpZu&}eРADѴ~y'! ޶#c=ݴ;4]<|#Xj>rM=2^VW01nϝ?o޶N8{5}㼰`sγ9T>%/aw0Z\~M//S-̙oJ[n.}Կ{jcm2MfCNcz[dj]>{3Y{^$hg4*�mo{5g3ԘZ_buaߛ#bfQhgqkuWB1'UZs)Ld,`[WJ EW{EgUosJNY;X!JM(4R]y[|OeHG3%tБhH4,4Jf|YdK* )%%>;#c.hF=ק/e ZhNt)� 8R|d;N˂sb?*y|SBfjJ5s 7&ۑMCR||9]=s_~n5]2@ZN3\_C7 2+*SU5U`l�$iiĬ̈pz"]ݿd#k3'M$mTY$f{a`(NǡhTF4PŬ1s!k(>d։ڎM3ՙ?: :eXC];^"A{v򤈃䶅*?8Wp#_r#_0+*Nݖ: *E`ӛ +JN{b Xɘk0w2wa>CAeV>c߿3|B ;0N8$=OO2cUqLɏ?ЏUvG6s^:z)bH #vju 2OId}2H4JӯvR&  ,ha!A=ǰrֹhll1 .ӫ6P<R2?dyDiTVVRQa;�g0-5h E󦱷. =|\ԛ[`\p^˭Gg9BaGs8w(np&9謈AuQ壀%96'[?D{1q`o!LcSwqu7sKJ*h ;B/I7.`r?Җ `澋URDR9O夣SxZj(]waO“f1u٪~ T>ypdK RLos7wTD~r)}y<vn :|o&Nq" "<܅{Ŗ򧖼WPl-75hA{uo}Ѹ??4oǥդS gy;-m־ßog>_`_g|A>Ď@ lC H(̔MIkHUUUtɂZ͈D,J!YÖ5d{DFtN%KO\{XJ`c?N~_ʶ/3y*mlٻi،4Ұ,;)tee9]k9s`g1X3wFNيJ2 !,YyoW\'%\GZ߃zQIJLRo&3/kO" H$НI%U<v72;Dz:1eDv G2UKK$Qɿ?䉯Gݗ:sۉx~ Ng dΩ}Nf߽Y0=}d 3"w=G=f1}y�څ9'Eֻ<mݯ~{1w'>w-˹^>22G̝Gczcs|5QoyϘviU3g _ dYQsizj99__qt^\\'G7MFyy~RKﴟg{`Hẹ!tq}ѽѽi^_{֜/gP#\*fgP)zӿm+XB)y_˲<%wOϐYR|![kJQbhB ϰ8CǘW=RL�aGϺ&_Yg_\ҏ>"L)D#Ѩ9dSrb֕P)z*pmn#pxrc-r2x!'b=8|\)#JQ^(߇͜S8{~wY#*bǮzݯcR9 1ݛ$~jh"~kdٻ+Gz'>Is5Wpqymc*;@ɗ{pP[_O}}-+} qYr3bU|X,F)#EEE ๕>L)S؟ɓ< /mQ-k1՛yL|Y[ilr>ןҙ >u2|-7Z|+u+_-%K1u4,>\x{*jck8SKM'_V%7\Y[_O}FnlAT5QG8o\ʘ a:LӤ~reCNS+8a޷f`:k7wx7qɿx˿<t~~ 13Vgo?+ &~bY.ӝ)A-F ګCI~'erawLxqR-goYv%1�.bW\63z Zd ~{Վشp):ߕMB > -~�44Bɕmepb@5%sZHaa$ڰq׮!Ԕ]20u+GiOMtD>EeT=a!R%>p#[[E 4K6/I y?{gWU{6s&3) !ԄPB" \UY`"^i\T)$@Hd9ecOs&g}^{^gyU -͓P myȎ!qT-$GE! ,W=lMQI&s'QFI2 tڋ C̚5N=T"o̓oYބJ))s꺎i^.Y;vaYt0uj>Ntr VMWot*لP{v}8]K1{prw,-|)ģ!f7۹)?p_eK˼a7?mn;oYɅJ+N;j^O!4p⦝dy SȩF41Dp-Zȅ9b~ݣ[=?GSgcʪN1VjbT}_<I*dGW&8%,|mU t}p.5߱usʫI^s༜7xȓA]97ys7޸>SZ9uit!r\E rѧL[hZ"M JS2 QfK;KZD*1jҒ:IJG:Ɵ"uT)bUUsypra0Jye3KERp7Eѹ##/2zhxCF>:7REWeڷ]y_wޑ3~xL'Mx`/,uGp�5,Yk+lbm[3t{e;sү?_!VGicwjz#/q'�tQϛ: \vn.t*Gr7kgYg>r{7S^WcߤO[>]`g*pBN7D |7 _;<ЖZfu|#ke oNs3n?~ߧhiio~Of?3/%Ft pxKsח 8Oc?̏p<�p?= >Zm;'[O?z"?"ZgỾ2eq%P~O|] 0k ?~`*oW3B:.)%e*ZQaH7�?hL Ma;4+QG '']=kEIW!Oȷ+5 (.i+?+խp}]L+~-o#bk{N +=RR4zje:YcttwUr'j*##t줷%YxN88׻>u_7<[?<x㍜q^[<�_YԬu[\`{Q"b*T6?7I{.+;PRXNj |7/:ib2j٫(go}V hBF+7DܥꦑW-N-LPD#Fj#&99PW?E|a?:.;Z1=2T 5?R]T&@Ŋ/ Ѭ-/~�-đQOǖ h ڿ'EΪmY`ϣY'OGn gIZ }T'f2( #@d V qn~E<UKJ0ɂTG&izI:9&JcPE)0w<5HRxC-^{~.q N~g'.Sr 5wPI]r2]\St;>cgs瓝cYUJ8>cr'Zygi+rہYɍ9{@ dlM]>~Exqf=?5/]r?8Q"w\vx6߹)F{znx>3Dۛk꫹cGscj*Ը8#$~ə%Ov&o+0mϙ]Z D pq_3oTBn.C}=?i_=3ЖbD>Q>uxb㜷4Z_q6-f}CUӼJzP %Ϳ:^+Wme9uaʇ{Y:ϺǼ<Wyc wt_wCd-{RxMw߭u܂Hvɱ<3`YS"_,fn-39߃|40w%_g% c'ܭI׊<UBB`iJ* PPĒ&&3݅AkZӖ\DV R#W- ]ezZ ;^ؿOQ+\gW%[$;]Vz"Tb"}B`J!aYߞBi7?hG�.O]Ww}ё!v>X]'ɠ:CCCz|dҘI]}Hr'"Tp8"Na$IGk?E.^F75M# ,^ z䩪hF8$!aߤ(^?aҩ?�h4ot4o6ܚ䪿;vI8LN%};K~V !}>qR,�6r1K]=tÔ5o>ɷ6g(>eǷ枽];xc9O Ap>GS< w2iF~s; Uu}!fG#\q\/z2%`0餄hwF~cOi[,]ޱ�׾c'PGFS|{s9G,\<Kr#,{`;w "4yOg;1г<9�! `UQ"⼽DM8h8ϏC/>=_"mc'gԹ\#|7�f�W5ۋ|h0}8#Յ+9z=ɾQjJם%Ο'\ }I;*N_}EQm]ߕ?(maRsf.:vg OnO畬{ǟ�PdL!liU)XB (eΚSZ <E7_z }@y+B4 ٬׮JA~fw]tdrSmx}|i3,4ݽa0HJ#NƖⱩ뙯pܖbtWݮnFRZ6}[qFjΞX tPxIDz^6JX6Vt?_u=zH:Z|Ԕٲ x1:ZC+[|Wܖ\9Ǘ9=sy##`<35%jRh'>ERA-nٖqEg``4Z[WF~ 4b&:5%z:OK,H[#NF1)=DkK[~T~hZJu)z:{H>^ik/`닐~(~i|FCjO[9kF1ރ}d-)ubI9&K$AJT': M�Bo S[nu+>OBXEox.dDWKNORSB8]Z-sVMnY J9W*;N~"v<rEEk, E(BpǡG{M<. ={dqy!8b-0tË6uogQGc$D4ɤ(J٨J2 9:J"sg'ϯ{_q: ~<Ä#tƺ?ÍFK7y(ntFa1nyq G0 (xk]1ZdEi绉dM~dql9ŏP,!:+ʾ F+O[br\unxBpHù˚&=ڄ4'q~ UX>$xyt{n·\s%fy^uR]({z6P߰I\<-#dwLf<cT]|B,]ƥk˅'ĶUx4@@mQ9aiNnϖq>?>EGfpIZiEo 1Qㄥn]W_->~i#m˓�X?>5zޏR:CHe0Y`X:r;WESg7n\r6*{"-MͤiZ[ , u Wk¿d\MQIlXCm!%=z;3khi?PpX Vw9l7h0d]?F…zXX|FaA�� �IDAT|#evD~2d,@q]zԸqZw,2oL5ӖE-Vřprɸx E[SZmň6rjPx3]_6R[KgzΚ9i绦�-Ί((v( 쏢UʐS:6i e}jۃRX.dže!%m lRp MҒedRtE=)+gMEj&RmTkh[%))N0Q {$!+,Paa_.YWW/Knm5G j wzQ0i_(U,knX46Ÿ_cm1Lz9`R!n[Ε&.EJ})sXw#1O]غ!D v(,wi_?ۆ\\f;9cL'6\Iu(%wokf?ztHtd]FuؑGVXMi}:;'*M@L9&A]uRrnF`u 6gyY1V3TߘR s!7Or,2FzIƎ i><僘ZݗzD0eH�3w̑Ou$0T{j'jHֻK8vq܋^=~i#fMѓc.h⧏)ܓ1y#錥 ^{V4H])6 CXmc9(Dz )фعqXyKrƩtX:aGꆑw>PzaEղ8Z! _4e6:ح}ZsihkN #uzM}B�!μWc !P,IԔE1%ja!Tm5B:u[@^2Ԥ 23)t%Ux e>U &P 0M iY"dbqx6oBƐC/Uv9ygL:!*uNCCW]u}q"ѨG^{\uU CRi9P׿O|^| G.\;7"՝\Rԝ?nta4|bFnrKJTEAS/46աN"[ dܰm8[$M\ ["M<Ya Ðo ʕ/\Y4!SVr~q֞_VvUД-XC:n}WₕTbdAVN nq 3 = a݄FKCQKC0wn =tO*U �N_ʹc'=$M#~_=#R߼oEf-dx~(8Z ؜Y{޾.#[ƟGw\һӕbu"5] \g2ˢ.=0ezyb115}-^]75M4MR9Wέ3+ǔ;*hw+[m-c0Pqf4Đh}n sct%!0LԖ\z7=^“ 5Rl}1n4rSsh /4" ˲0-;פR)嬫"?{vJ4h!ΛG(c.z#hDw@ N[#m*XRډ|v6US2-5W-}nɢ;Zhw$iw;49:`^8d̋n.੧N!W)9k<e lNgW<xJ\^IG(AHpw.(%J;w.{RJvڅWnf=(<H~O'[lf|_OUS0w\N>xlᆟw#-\}iWk: aY'[: 7]Q%R"} WciL.}laax87c.K廻 FhBܿ7Tkgm0MHLmFҹ<F|bkN/:8ƹocPjdp(l(qL7G \ً]ߕ3Rt:<BvA#NKW=Fxa̪%̑QN{rϣY7L:²$RΠIc*)2)Ibxēn$NU_DQH2<<쭋\Ao Rۇj k?&2r⸧_ˮxBf'x_^S@!D/ Eo::x+<#<M�{.:@�^l)X$m,b 3< B 1´V*L$=ej-RٝZydkBƎMX(Jo$@A"K8{0=3yD bUwՉ"k9ysh##  H__s埏>ΒŋQU5kp}cӦMYx}[WRc:RO6BU5;0n-5y`݉+b>'t]!B&EEd==pT34M~"EjёSpת"`ߖgEߨ I}6d(UJҒA<TTR<>sJ!(ٵ+3tL#q.=br5m'${3G0oˏmc5I!3c4}Jr]9sֳD~)(,,ijL'v Fwj#W-ɩ1A'-!Ov$r O(v<b-rdZ$G2jf=,9VBΚi$I ð#Zq(aTy릑(jCk ͇J9I 4אN=zn?6- `zq~ƶg|�0' .oG0_�۝_JJ�Sշw_./f(JvitDDL$ )<BzVp˹-E#ݶZ^H&`-UєxٻNRpQ /kN][c`<_ L˾RJBf;2&{H{<U g/NM|G#tz=*m/F `;BSV{!+ ޿عa)- #mjh@`O6B!GZ EG?իY>sYD2(455a~ab1|).b9GE͵B%J^nY?oI&[ؤl(c ]un~!V+E-I[h'-"ʃeJ8ln ꉇ+|#YG;F5'6rƾU2Dy\_/YN{)HZnmf:ksiщe`~[/m,)K֝1*)va^ "fk4[ٗJدn�lfV=%I~-<BswN3A!=/+%#bXXhHi9sDJ2,$GS9U˷6+ |{0rv,3&ߨvTsBJ/4v𫄪�!~SZhk v~33ҏbQ^K�]vleĀXnM�SRv#ۡʚ # KgKLUDlNcr.S+G" _݅Q~i6 l̒$[BE gc7"VA!of(iuCY!rĽ.=+J}ٶ!M$inm%2)[g-_q˕=܍.\QwQ plpaa\u=rɬPÑu,"jj,g2›N<{E~]QS4WA/_Eo|裏mgn͎<uO5M##2Mk% G"!EYUTp(:0GwO躞'i[JrN]/rZҖ:ڪInyio^in^"o>mQ$'˕&-ho7Ϧpdu~Fn>YEE˚#A9dud$A hD@-}C mE%5E6c4Uf5q1҄EGESL,r%7Krm_nhbWJ]�ӏ"db[De}0l~$aÐωQVYGr @(,<ܱLG$|O pJDz,cg=InݒΤdSe6qui \j RDn]Wۋ]&as*@`G,B,$ -!(FoE* W:ǎ|&0,H%iG%T\KTk)B[ɒ-꒱$§&U*:x$'cEEWܔڿ*u˖.%J 婰n'n~rLm2}Hqaa;1n.DU'>ݣ١oF 0B`8 +B"/g%$SI/4 D8̗իV!B_?#VWG&{Ns!N8x uֳ'?w܁ii@QňF# G#C~k!q {4v i 0ME? }1MӴCe~DiNmYM !{P[oe>Qml3{;y'nI~n/0FHcYMPX[X;0qH1|+%8;3]ƒt>dQxs= >tYĩzV_DXN%{".Y 1C/%x'Ft;|'\g|`z,hhm2i۟OpP~UGNZȻmL z<);^E.qS[wp[fZ-ظ{K^8ezcG f-nyqlB{r]1+B$c;R%SUlJxdK;mܱX*T&5}wRZZ<3EQ0,a7N:+,O)"D( B(XY$?.gdV`-eH*jF4%JaFichwnYׁs<9 @�0 L`t 6 iI9*4A,iD!) @�Ӎ|KˑV{A!ȆT,!/M-Xu>P5yʃW&]=(Qr"I7(8_e8˹9n@qRUFRU)Q}QB(^GMnK*BH*D^[!}X5 ]0^Rݘ Hrs pNBNjXYXeiؤa,g!iin!Ͳn:8QUSN9UWW4 ϟ?O~_ꫯ#<}ݏP8D:sio7ȿ/w_=\~ܬ>^bXJcc#YCeKM׎7@+6 QUU^-Y_؍ i!/JË8]QUðI#!(~0 .b!+׶lʲą5g[<F!SOtpmwaqֹG¼Ŝ<agR4ׅ:˻z@$umhct$ɲePr+L[F\惿. (,^ͼ0d1SrO:oštXdX4zx3xebc494}gsi$tׅw<fh-65rڬnv`<8Ҝ#C1< 1ݐ_?ϧ39@2x7 WtOyu0Bv0:s_ؕkD'Q9pn]޵:ݛF8U;ОlH H Jd+#)eu_W|֋`/[Y WXHH!0} yu`YyRґiK.k^4կ  @�0Idt:;YR?`kGz )42(PF#-44xQ{Y(~˚7Hd`/u)Js"2͗Lb*)R*B(T)eX!0]* ,)Q(ӧTHzdYm26|WEu>)<Z1)%w ZECRq"gټu+i8Azv!E6[EB"6c1 Xb{I'D]C&Ȣџwv.2`MM̀d4%n9SiN,c͛7M׮..,ZxCl6K"9 "2,KfHi|#J1p}&؆e S ƺF0ӷ !D"yRņa2ǚf1MۼKa/֍uZ4Jv k~.mO\]7E-fAzh>|P;|Q~|k=fA4Q Os)MyǢ!j&,Z^صq'99sP '*:c1*uMĖ,,y|!N!I;׃Hb;:泯wf7{V9�JaES(/k*Gì/-=?ՑڇK~6Sɘ<ّvn$'' O&&/SS5 [p QPU,吮 GP<B];E"L$�FXQKBN$Lzk5o,`H @�<3<bԐ$9& *ً"^ {oHEƬc.;lؔ o$8he8| 8@�?rť#$KFSUm\Q`/ZXf yےpn& )ij#dhY/"ӑ'R"2qYNjO@%z,{=sa;xݸPHZ9T7œgog.i_-ZD�"w?\[*/վT7 Fr{!>PrU t7:!2݁Blۺ 7tR>D:[~é+V0<2pxǹHTj˲7Բ4#CF80lɼK/|Jb[[Iۙ7w.fȪÏ`3OЪ$9o/:;o7"ݬsIR] BdYFGOws%Q~Q&E26ϡO1!;{5k NǮ$[7MFŲÿWK2-v͹xb30jz$MLHg(|nmS`<6)Qjr᦭pL*" 1?|A"D2 -pnƨWH&mc7/cvוic~U#͎Q1E7o狧夹лk}� W�Ɇ!s~xwuc7dkl)w{?m稽bi SZڗWU7.>V�]#vۑ*|H6oXl:[;(-~c$3$e{t"^up 9o֋m:Fiinf4B1ugiP+4<rٵCLΕ#'Ha)M}ؔ�� �IDAT+5V>' @;vsu`)u%Ksjfq iӀe`tw"`e� %ڊ2g9\gh]Fx扭<t<37p֪M|0o^4@�7rvY1њ\#dI'@BuĢI%RתSC ivD+G( QD"cNj=ɶ-[(GʖO)U/bzT#`UUE %a|"Z˥od`Jm;8DK; aYV{\2q7\ag`:aa E`!\{ zʎ:fiRCCC47p[z&}}$C̟|`dYr"(-H8%ҰeR),KD_oMM`o: nMDUB!{-aU{<{x�tړ(v fEQ4p8L:F<9bn5pIh4/s=G<0/MQKȾjhS{2QeZ߱7RS3 YE`KR &uNhE ɦ\.jyd{ 2lMOԇX^q9E ۪>#Ӥ!8dENt&BS9)HnL<:To[cv 긱>›Z5ҦEưx  @Z0?_!Hb*!bHYG"k-u;2EBٲl廒+o(HX,ad2;~cyq>p]Z_aP}RɐR_TWQ� [vsWIa s}6ow/fXJ0aT B"lo Ie&锉[@7x(8t)M<6<,`tvFŏ?w, P @Y9b(+@ZŤl9DlM1@* Pl9ҵʓMVj.HlbצuS1h?EV\5K -eO]lסՇ}\gU7ץe_N kƒ0Z+%m{9VX8Be'j vO@2Ȩ�wZiw9<?7{gDn}9Ki!;HH)ĺuQ##סP]\{<#rʩd3inVqK.gGaաЀ%-ڲxMD {}ŶmX`hd``h}ƦfF{h]plZ[[9-3ohdӦM(XX&p {w.ܿݨ_EQhXHKNMKo)WwЭO "ԷcCN%!@"y/35cQc]HDGR#9d{JA11 H䙾E%:Rݸ '3ܞ|/�#`#J$cn[}f9<|i!oG$JR#BkrrI BIBr?Ui @8@�0^rSl〵7CB6`N9D"*O[-̮_0{Ac;Z@ ۑVV0Hfʟ NZysj,YĆzږɶ,wpO+O @P)g;<+RVRN VdEdI/7kۺ){r;YW٪Lq^x ʞ^&Iw\2ֻM[ҢLF`Eg9Jd R*sQjEq)ri_sL,LٱF4U!`Ŏڅ(dY I7gj8᾿}EUɤv|t!71O4j aN$e׮]$4YA9,od::_u]Ǎ7hG^ h3l&ˮ|咋?D,A itm|-!TiZyF!tڋ?.!+0'p8썉0㎗[ޭ_ݵ  @�3Y @1bλN_a8-[f-cKY.z3\N#cЂq,%C[Vm4H^7_ȗdv˹wj!Ll� d9Jbp`9l {6КЖ'[WǾKĒjl!GofL I{ʫu_hyn2ZqT#0)6C @�UCys*piO 8;*&T'Tu$G9ֻTYleɦ/#�||mQ{,c"V_HNMd'srT"zY"Jv"*IvN|gt'dz4-“miiJ8E0|pQǐL$0 tDb4BaE([nASU>lzzzH|sG ohmm'sÏ~c?nzEhims6 =;vq+ Һu$փ -ܱ@i%=)B!uaQ.i[^Hϫ87<@�^O88f˲IŴnlկ)Jt 7_tڧ~I.9¯F⇔LA^z(jO 00ضa=C:4]¶l3_^–1ۇ!IJ GЇkq^}1~wo#VbJ+" Tt;X@J*P1Q"a�F$~(ڗ3;LtdCpwf{ @�8t5vy)*)֡8.;{rZKnDKƂMKT#>p:>EX3 o3bce9LatUb U<߱'-#kǁR{,}޼(`ߘN3p=qWƍj(ٙLC=}'[lEHI89C-%7'JDBnϸ2<<ƍY~= W\0DN=VQGŜmdi¡is6Li!$44ETSMٍFms+!뒰EBp~!ypz~[Oκu2 smjnl�`GV7</ӲBy'4M0ji[,(-c__']SE3(5;.Um_5YG3M鲏J4H$A'3|`#<b* vvM#NqD;k3ݢZd8>Պt[[oȿ羧4/˂eKh3 W Э H%oiP0)Ԑ}C u&A 9+hnN 8P kۂنOCpwv/ZJ]rokŶC`Ա% Q.[JrJrIټH۱DVU~g$jm+a%]_'DD& =mnU!7乺+ACCx}!hmnAƖfnhhhcV._]p!-[iii%CC/zaW}^z!a_d#ٙ0]_h$2T::w [΍Gdb/UUP(D6E4/(D"=wэu)zL6B6@�x]~1Mf>M&q|`()BX,sAblGʂBA6W2)wQHV'T3|;9?W(q0o{xYKh +3ȝgRUuhh2jFËǁx-a2Zbju v~4oX¯o<W{!ٸ7sh7xKMZo'^%R )11J61JpQ"յ5t;wbXdz @�C6[)gI˶u!_s-*ͨ*%sKn/&9!!Q>(?B\j+oncxQ9ZteĒmG;ѹy*^ouP#ȗ9� YIg% KJrDdcwɃIy,6B!{ijY԰a>K>af=k-a:wqK̟uqVZ͛O~3Xݰ M#N[ii2Yhζ-[_t!^E1R#zY‘0[6"4 mItuBJmDK_wcOJQn>Y!avlWp=E!;wk3u @�}Oi vFxcǻhF'Ws8xyKi㏆(P]׽TS*D?[tn~SÐ(;n^7r"?|yҶq!^z? v>1!5xoO/~cu =E0_ވƢ;_ ]=*Bl~ Xi6ާKX`b!,I U(h xHeY`^{+CBںoQZWt*򎴞lXY Y/UJ"l&GĖ$gE`m%Ra=o<<dTDS@hwᑊm,PꞺW(P sKIr$'[~RQSIvLk3HE+(.ht:ͧ?iݽUCB#,Z+^.'IdRI&QPQK"TBTE(FXd ދv9]<g0U=5J$ɮ k %ĴL/b}\bTud5mr%UU%Jyi&yuig�mk� *V,)Ib(aB:4-iҲ7S%GFlղ,dT/b- 1F3ʔ#Fi0 UUQbIU` !$�SXyjQR(yIPd΀V#F T'\EH$E(^ ]8D/5vHڟ?$:!B!#o>[s||3<DƷ3=(/:6..Oฃe]w%?~�}24 [`bEV;,=KWRy*m"-12FьhV6@0Q$4AHXDCшJ4dK"V'dvo]_úyi`w%?-={PE'03zvQmbNz; QbkYR_XݜFY?Os]u䋽 t,MȹDXYEZ#cE˴w/�Xߪ. \R-nGrc`vY/P@HoŽ5P-!Q$/5dHe(Rb"ĠK*RIIؔgA$!&j6 HԀ0 �BRB t'tj,r7ƅ%InOZ~BaaFtErjmcchh؋"U޳JI,etd&gvb2TE%Jc{-AQUeѬgW8HRo=C{7 浣L%-ŠJPl2d? a^\r԰ Hq]ױBN OԺܵitM] @�p -1tR02!e+lP$:Bel ʁ_2-^)9Eڠ!$;BLx{ <*Wi!dkuFp$a zKU4S"6IayAbFҩWK:Wq! [H?;Ίsܶޥ),}hl-$K履=h4T5b4*v1** EQ 9{wݻ$~|]ri3;|wWDu0uڞ̎:r,S߾_u 1�?=y sS RVK_z[W͋yeoREgwE|._?z>}Ў#G((%DJ#_Ep e%(i7A EKe KqJKK;suEQDa̬ĭ=Ws8ֆ)(v<2!5¬2qݱmt�a]hKӵ7.,s<Xsn{ %$g@vi|0(c $ˀBJhzKHxK`7bl@đĠȀ"cS`0(!w*O:,Zm1�$UOY (HRH) �i)cD2ɴS<x۶Ղ�+d,EzOJb%DZ?vmT WjlO?dΈDŒcX<Ǜinno O>NKc-h6d 6%lqpuvݳ別X"-MPicD:n l}dwl۹}[DEQDE0CCcCE rH],K36XJ6M$)=oV¹&Gel|W/#}W!?cgIJoy,UHb ^Z7-^l)A g=1~̺-�t?8SsNL~~VK,xIŸG[󺷘P5fIz9/yZ93#@lE=U?Z?4sPllX布2;͔{x ':t2 }ИnNYϗjC2Yƌf2,?uݺwykY?#mې 1cצ{TЦ=b1;d4ȾT nx ^y_[Ʀ�JaܤiLg?&F<7~g9#fsڗ+M?6$)z/ cū2q-ye֓liFO'L]Qgr{ehu--@Sɉ'ʀNӒxgXV-~cyigyݻ/hcNcfE2uݶc; ]=ogHfZ^{ $af1}di|>}'<P2L:~2ĸG3UYz^CQdIqnDJ ÔEf:yQ5=7U " hB(Usͥz:Ns:Cp,c!l%}4ɥ"![DEC./�))*k ?C|usW@J+ މG byvt}6+<Rgy2Vo{% Zx:#lfmCz^T 4,O,&d"Qe$Cg�Iyf;IMOWj(/wFORWB;$wceCZk8~8}c<RH \ ŢR)4Pe ۶mHL4@YY9DDZ #dRD()az$I$BIɵsy<3I00GBZSTe"_s5"7l/b?t`.k۞ek6HP[["(" Qfs͖tm\mFmd&d; B\4Ơ]%%·{BTS]Ξl|zR!!HoLX!ƛK2{9&tؐTL�� �IDATvBGpkRx߰Z{DB&euL{6lX̼w<pi<rEsf5̽Tξ{;:\J3O}9 k89p e*w/.9|pru<4r>s%=P~}gN?/Mld]'e]b|<c97d3n^+ձnݚٿ0#$Y;¶qaկDum;f w!/Sus%p?>Fw\Z~3gOp ̍ ˙yW󼽿uU1im;Gry.Ӯu,oߜgq~Tξ9wb\X}t#]{-\0yFwEվh_#?ѽq~~ h^us�ӷYyY'ky87s\[iYr  N FBZPW[]|xUq8i8PD#Ù؋<:BQX߸9Rw)BECbDUz(">tgvOT7p5x zQ,\Cw9e/6)y#ۑ.W{DIM:O ~̅ıL{2DK,#`KB B{FF` "!*-XR BcKJSя.:Vƥ4) 5ZjhOUڀS5 ac\r=m;Sul'd'CPH lQ!0`@x c=;  {Ja'ۧiH[ؿpX,F$IKPUU&jRB!,BYbٙg| y.ڤV׮۶QQQEhiiœVRJ3N!OV#2#;ےPȓӞYMNe x0e4ƎG7R"("؝ PRbYʏ{o) %I%_uAJ t60/L$؟Q#GRk vj|<`a2B¦d[a}k HYEzVHX+=U�%!|gXqB #i *!ؼ̗+wsAP=_ $Hks .{9!>?zNޓ/徯&=aMqp٥3H\UK9qZ`@< `1K݃71ąW@@ QޣY}~ q@x'<zŭL*fjAÂ_њ}[yH=ъ\ݫmrì*Ihi2{T ֳ:wv\}4g o__mmpfa5q!㡡oy�p#[oǢpз&U} l; _J7n%Wqɔ\>˜?qνoc&CMwO:=GDp`3氍8~h> B_pg"Vk(hfr G!]oE ,!;mO8|g!~kM_²ebdoW}nk/M/|R!{F[ZzYdWs0WܟN7;wr͡gB۷Sws$|?ϑ&q*WEFKs()iFa{5o#)" 6Vk俎>)EFB"?!UQ{n("v瞅̲I=ke'~tq|;*_t )I{ >i:h\Y񍻃b{SWx ^h,#G;RJXRr "\0Ba!R6(ap'[  C(BY!c94^Y7fFVH[0}4/7~ LRBi*blXkֲ8`<U٦:BhC2`Ҕ)X0m{'K!ŐRD<y`%QRS446z*F4KK~ğgj1MNJЮCKC=:`˶9 ue̖ HllOᠬ޲7 i/d2n@cH$7Na1kuEQDERҏ`"R*mp5(ȫU]b\Pqq駱zZ~l݊T@kb Ttk}Yn /6X+Re!P.EbB$׆0^{Z"Lb�HO*A>a!G!;7g'p>yz_ĥ͗NGq'>]m~ ξ |n5xN=ޢ]ϡ8i}'?q_ֽ{VO%0ѥxL]),οcොg#;-wˬ/Ӎ|2c2@<o; w6B�v.ڞl>Πu{|Ňכ﹟u+R>np;ȫ+mۼGZsg{-w6m7g+rmx鹷1p<sM}2܋:O߽΅v^x$]+/: Ͼ_0N 3'./e_<]wC|<L,MNO<W\|%{p.zX~9=w.W8j^~|9bon^+nMza^]|;{c=z2.ޯ;}S�K{ه72/)q{0`9S8y2z{M˩,BhG8kSO(@> 50v@뗑JB MieZp lM()5 4arJlEKO&o=ۗsgRiR8; A|*%wlh Ff S/͎J绦mA\ǽWgDa@FjH)u WzQRSl՞0BP§[6SeY$ͧk!,l 񤓵$ Yޭur6cEyw"\Ŷtl^}*injECߊJ}n̛^ !H&DceG>cơ!G#D#*++Dן}X H5kƛoiFlD"A]]=@4#1`�J4bK$_zN-n[ kP)eXٱh4XX4J<eH$H$R)Rdɓ'SYه5֑hn%}\DEQDEHẄ�hBWZ#AKHТX,;wK !mx"m۸*= N>[Yu9ҴE1a0J!* %F@HWG˧F%%)K)v)))aTUUaBOK"ѵ{]L^y¹!7bC Jf 8wWWhu֊ѷoV3e9K=ۇun" {?Sݕ[Mh'r�aٻ0 f ;7}ʴDŽ+p_! [@}zԶp:٘Uʆqao9{Oxݖ˯ƜGz+{ W8BŁvE @^p_Su>.:Bן W[_zkֹZ틙MXm3Oɣ{^$ni4t^ k V^_dnq~yo}9M?E{m;H֭%wwqN=8ᶰy#|'3CHlߺ 91i/ش NV8ս^"(FWz]yƼmtqa4ն!{ƗzSw57a9+fQRy̫]HIRъ}xmM͹|+< BF z)j!<b%ene|I!BIvR'ELC0hڋABEhLz}خIH4 #Z\,?B.+cIWuHw Vv{BVk.VjǨ"M644tbCMJJXqO<8 ~h41ds%`4}V!X]p +*ӗ>H}())# {G"GEcS3MM-׳u[ AH$hiia۶m{MIIF.p8Ү~8i4Hy655H$nqI&WVҿ>קO***̓j Yee] 2UDEQDE }(!ж=VGcIKH,<ݬo&v4`vVZţ{ ?c%VjmnZ9Ch,zMDv%@*$Ғi/V),EX*  CjA:f[8ɪLF_ ]yV5osoz`e}QV^ʮ,Og&8_a/)?r14X|j>F qޓRmU(3ZvRo/cs{H9F_LO κ'\y>S{UiA^AWBVtbSmN"N4}\zQް(/Mt_w B˳QL檼$K0F_ $1&Ј�5{8F/cȈF? mF- sN!g=}ۭEb$F0IWCE쾰)]ٹQsZꨮfj>][M5rU-DҭI]ww۰G wBQ>ꨫSȤ˅lCyKyrayf %4^]vnzr[6͵>h %RVy !<ctO8{@00\ 'O܋&L࠱UQNq QEVK0Za yfjh Il#237Ho՞Oo_ǐ i7oPTzK$!q֯_Q#ilh~ǠA?~C4VG^.)B _oi~GM͞i4JIi)-cE"hia͚L\N<hC~>|8ZkxDM2@F#2' l_gVy a"$ ҂"1d2I4EeYX,tI-"(F AIJp]h8#z&I$hc{Э0dO&~,nޯ>}4(\ʝ} >̙VW"Fz0J ]*ŠX "EBJ\' #DJ!㰽%ڗ!nhlmRvRv .5=oEp[˷yX+x0n€5�� zͩ82=o%V?3�rG}>sbnzo*/&nw8qV~ǩ㞵mtsy?W d.a&RN=~6SFq?_֔Ŝ2l~ΛnʿY ;;s8_ p/ s.L{ g! e[`BdMMcǎܵvgwuK֪vbo.<#rq|ׁg=z]YQ_t>{=}5,!rۛ^n>d<$L޷)qtB݄ЩJ?liu D% c(v#ocFa}  ihV>Q {O=]3|D;Kh̳QI޹ll u}Q#wMKk+3 ƎʹqC(i¼g? F'v#~x>5}P] }Qomw<4g~&:%ڕw|OCIN\Z<8W:o]>:d;"]a sA8lb6T_Fᚌ_|\t“)_ׯaBh$}0ӆ ca#¸%84r a,Gz^ٔ(H,iHIS+$"&Wye7[F6yݶrȜv&v{B о׀z*²BH!H$1%IRWŚ5kFFYd |0?Ox0ldMHIߊ{bQfj/WJq/I$c޴#F0rH^|'OfĈ܉Sŋm;�׆aReee( lol�k`y0+Vdذa477c0}Ų,ƎKƍ!hiiaǎ)0rEQDEQNX` Lw:%%1|->]SZBb&N!{>_'BJB:خ׀PIO6rciC89p%ؾ',x"Q \W1>匮D<af$(ųP5B 1l)]P )miFkMȲKz2!_eh/`kgH}'bmrn8v c; TCz)S``1.Qw=Ffg>afGi8,<\xU,`_Iܟ^ԴtjYkNP@x4.a^M^9seLc# aװ9?+0燮>:]&:*Ӧ>3:#z/]_{}S s <9,<~uI+ٺ|s6gɛLq x p,C-RR.ZzHYf9+X[mUk"Кpsli[XNxe;iHX7w`[ _ݖ>^!/YcR@' Rf͊J ׊vg%-$&k@, f8<"_ҝ?6' ewlЮʜ سCLR?O&8ˣVk'z/te$9-h�!v8.RU!$I;P%SQF4FIR8A`<B7w !r=ڤG.6Ą$hҸa_juWee;")`h<7#Ʊb,Jhjj$u]mٳYGcGc㕌9oom[kPR!J4560qFYRoh@.6mblX*b(< L4P(}RWWGyy9heMf-yf7b| [jjdXt 6l`$SI6n$jMXV+V{"("ح!L6B ޹cS8%+pdxFhW%RkkIirNo;1_NCm:0oi\  *"r?2³5#4!)"!p H% I Fki@ bA&px{{c Y[~yQ>X{LUV!lԬbɒUGԍ/p^&VvZۈ k[Q{tn~%ޫ;H'dJ�� �IDAToyxʹUo3gluف[>f0uop+ L|a+~Cz NͤQY1B78r؍sὼuTz 4DAR=2t`$Bo_telUS\ `Cxxxκm.):O$Oh"8Fyҟ :u|eO VrD"YD;A>d12DC^_"U}shN9KQ9 #X2qLևkE{m]Gn6x+ȺuY?1jG"8e5LG5B; ޽0B(T)9s6uTM6 *W)Cuk=n[BJb1**iiN\.C+cP$ՆE3^.ڗU“d#bJk48q Ha4Iצ9ނўv] -Y?pWK{,V1^ؐXäBA TGdp2չǖʸ+E)mg>S8(KQZRmH)bTWWsiqwRVVƎ:GyzjsL%N*ߦ񔔖z|( -4668[la>}ۏҲ2ǘxͷbضͻIÐ!C_~b4jHq3ײv^&_+/[ÔDXz5G=իWJ00bƏOSc#1`UDEQDE'?;B7g}S#_vG('4hPTM0HzB<%DA58EK%5|939pWog4+PR` 0&9HjM6h$voY<ƛN@*Zk4̴K2"s6p?ˉ~}zDÙ ,WoF!'#vYMn1c,) 42y yNk ppl蚂 BI3goؚqŶo97U~#ybN>_2t ߁pQa 1Gs͗++`A\pRDzl[p*.s'1u<(.Ft<qC8hBۥP<{l͘I_4?_쪿)oa<xYh*|\.78|Δz>bC',?Ô \ڏng͆? 00ZD@!0[ ƑHps0nI:('`R`[%T g">/X ܹȊOIbڔ\Qnؑ+ӗCymMC݊U{(Vj>mL 6nK2zDBmnUTEח#FFb#@lg$͝ -kֱ}4� ku*ۼC5/ˁ\1AMVpT#iק+M##)+|tB4Fg23^(+|qB 1fޯ,<їTUU1|0j'iǽtmxBo�<3]+�t�KkQ44B Rz %qp֔jĔT]5uuAXXGie .F<C.#p(@H-!]$ !,\㠍ccRBڒh4W`+|ZK,hap\2M[3K|g$.yɲy~W˲�O7 /j*6l̙3ijn&oƈQ#yrShD;Rp׾i6mD*$HU~D"6n/Aolॗ^gITTTxH)(++CA8SZZJ2 Jzc쁗l(JKg ڲ2EI5kؼy3HH48�/cǍ#dǎӇx<֚[jHYJWQDEQDXͬ߰D"O# BX36s`R"2:1Ķj멭'jW bzU(;-'ckO!GK "Dj 8GCR ײ&v r]^ҟ@!<U+V{P'YUJ /=KTCbgc<6]/;]] !vP_8X y;-ˉ(`ϳns~QW =H}F9zm{87={Ҏ/>džwUtxiBBo 9x@4W~@}|-իrы_6TL󷟽{pS3k^yы}[hl<M/+^Ad앏Ęyw[2 T>�9dMFZex n!Wn5*'BxX!<U:t*ZNߋV+f= cl ,2"p 4dTlm"u4i*GgDocr=<&lNMHז'rt26#@XK.eU:#<^7Uhq=vd4,�Sҏ廞Ȟu #邟�Wd ӎmC= VPJJs"NJw0"p`P-H&.wdog<뢤̹VDLR >mDa)o.1F}J]B\H Hڰr^h /,˖겥BhBh a,O 4- ) ))H <ո(M H K91E[! vȓ9Ƽ7~5l[iC؁;mnOZ-+MʪP2RSO>{3zhBSm+*cƌ_pN> ,%K':d(˖`,\_|F*?̳LYY#GvV^p#G2l0BGg|ӯlɼlK`R)$mZ -]"0p@jjjxx< /H$k^H)O7?eeeT$b oߎ8|ÔV-"("vch`1 Bx։FJR)Y{+| )M(亮֗51w& n;˅a* B$R8ڛhԡ}HA 1Ժ,m'g1WXPF8WfVdM+^IqL~8G>̽G{]v.J/=P}9d96/}co#N'Oɽ&Ҟu\Lnznsk: >wYŹ5D BObW1yN<~zy3Vp 9/1ﺯM4hw]rNSlD'sw;7_mC|ݹ9Fn v.zפ{AWvθIy'x/ߜ~W"-ASsp垟{H?v!AX Hҷr]3z7<,[\xz1ܸ}+c 7OG"vG|S_&5*tX|5IdK,necןT3(HY룴4̨I#WWk톎JӮB>cE_pLN2<3o@%٦ͥ,lrDsOg٤_vxIc u|3lBW |dlMƸ=],Occt=6-$Fz!<"}-aJKC"2vf+Yͻְn;MFEuN+Z3\i5(41 *a Y%Ѳqh1("+ iIUؒI#\ʦ9lb9FhA:I۶-?rMWA{i@tJ%E8Xzx�.m3fX.$S)O8#˯4x(s9gÒ%dG<SQ^N*e?+>k_*׬&O0p F^'=2xcGf̘TT q3^iݩ^t(.(J)q=j*I ~РACaUC�L0`X,Ɩ-[ظq#V"8jwp;hmniڱKnsNz("CZF 2m;ճ(eLXn#dM{Ygz`Yw$/[ JOթvuI i=/Xd6ğ<,K`m;}'-y68U0a,H;S0B3Gfw' 58[׽ō_|cv2p=EEz&njn+[{?~v+&|[Oqp5uMJ:w[pt\rz$_+6j^sGw/bZi嵕5ձz\3A6/ 4qXO]Szto9Oq"x�G ۗ^umeݧ+x홿py'ߗ'^eƅ7%.ԃ;�lQ<~|/SD<Ħ%3qѼ2.c=8GN|PBKK֧ϊ^eW3M~r#G8ě6;NgvW}tglw}Io_uK;8&>[(_)!s"Pz_<b."wywbmSoqb.zٜynt NEzqala(\{(G΂-0Uxj~<>oo -3+oB{i8`/;9JRDGȿ7ޱFvXe'؉V Eo-{>rDjn;/?kYà `ê@Ӣ#ctL+L\E>A89јw-[ Is1|kbfx{㧺xt@jmҲŹj/hONUGȰmiH '[ʏ.tutYڎ6ሼ孟\Oj4q=i|C*|FZ)=.hz¢IYl!h,BHT <CU(+pم)qK{@C]a4n vdT"(JA kZfGÐTϱNڳ {}%k8Ziu]MH*ܬFu'}>j6f~TkXE$'C~f߾:|(555 2ulݺQ#GRVZβ? a;v*EEi[m|4 !Bxn/;u>oxR$_{K~5j :h hC+W)eYi-[P lT0ʹp-E2 ; m@HXũ*Dh QDEc( F*I$ʟ(ǠW,%=62%'o#ps\_'1="Cf(Z_H{rXqNdh{̬ݽ >_Ϙs\ї=<p�xx\|Wj`+5Bsw'<m� 47.c%Վ?p[l99׹-AJo(5^èNf1'c<[3*{[#Y]GN@ßh?F;vtmS)`1^|:-T/eyY̼U6Xy&yv3d39s�Kfr%_]иȋ?zK À'L`wU7@q/ eسȝgknV}Ћe\%M+]:i{o;_%3Jtޭ앋>C&!cW֙1@ +w˹ߘ6be 5Ր{?( A1^LȍgV# | 4la? T"oa$cH[TvEh-^k%I&T#Fמ#*YFyJۛor r˓n=ݷj`6iu-&͈2UCX]k ɴalLpm/dwo7lAi]_rJO4 b"UJ/P4u!qf?mi`ZohX+)q|*@h8tR�YcA1MIqp~ F#G [c2^ waDg! )|Nǀh!=}c0FO*9V"+Rx^S ]Ld2cF[^" %!RF H5ooXykҖsUv{B64|'HӃ4LbR(+D8"kqޤe眙ewJjؒm, L i|I(CI _! ! !)BBIB1Ɩ%Y-ْJ^ns~LevWl<Jޙ9sܹs|ym/_ 5iﻟ6n{pScxdUzzhkm%J!- ('fll3d2ATBҴ>3){̳)2"2ֲ,fggEJŚիt\su8#1l#hε̂Rb)}, fF]7t)k,^{;<4̏zf5jKJ<{s$L $`AJPjZl ܍rJiM%pB¨X LUjB `_7~P \ʊ|ގ&'2fBkXTb Zow/me/`[wCO]׽7=[A4cz &U͎h_"4]+<5<V+2r[4͵_s~Η~3_yξek/~ݵ~a8[|_g^s6(&Y+g܎y4oC{Ǹ/>;{ l[v_VvnSşEOmo3~=o`W`5oyڋҾnsil)l>xEw7{qO~ԗ_ǦW WK/Ru/?o? \kzu[ΑqWMZZyZ~_z.ϛ?ü6omo4W󰪫Rwҽ ׼\g( ʩ!?/?xIYz-< O=wed,/ (ON @r 1K۴{R.ҚE3Jd5.қ ~pрPtrULŁ+wE\,TuɻsU4Gz#ba] K0/[6ӳBSMp`>Frli<WkտNC0v3ۮc$m}y/74?B.Hڑ%*Jpj cL%+k#e+$Wo�� �IDATmBF[!r\_3W,1/Fr^1}Dq:*kP,|P݊Q$\ Sզ$Hh"J4F`F |ߠ }�Ąe^S=< *$t20W{_c0h<BZ" 4ѼO8#R0Yrj4 ҲiQ³\la+\!(%)-p XZ! 8H0~̷"Z/\9s>E; DbT`YkPџyFsX8ءU4-6rr(e}-S,(x^(sssze/x 918Bky7 S<0SSy< IjR T0@:bmvy>Պۨ˲HӤS) "bvf^^{/Gl6t3gFbrbrT*ѽuwEjO.OK4ox"'Q2(|-d4AB! ~ d@j Ԣ-=׬[> $` $xR'S6-^%%D 1Is[b01)`Ȯ.^(78WLU<V_jǴ ڭQ�4HLm˪�J}$\1bco %dW`dyEGw?+*|y:48Lv21܎c3 &2w?qICxXVLq,CCxVA{{ Wpn6XK=63C,2i^֥궏2|vzVP T{Rx5*L0tf+}^=eW&=2Lm9dʟ۵X1ns/szXV\{Gxu)pow+Z)_3?K̿rxl{z!.1zUp2_A:,`4 E \ly1"HJe`Fazn#V% ..y йw\v=/W)afT]M47eH�nkϞ�5>�|,0_UK2𙚚aT+tnnz:W0` w,g;P|'Ug=WZ,ǟBTܔ¶ؘTE\e8�Ď>R] DRFcl\~(bE)Q%17at#UT OHP]أ̠TX/Z".Ol\`FBg4Wo  D!|0t O{B%x$V5f%AV+6Ro<,  ChJ: b}qh!42L&HwV OI9KCS.岏EI!Q, >$&k:"._Cl#5mOŲDRʘRIE^јZcYnennT*Z}_ |?Akk+RHff `VYp?i<üou]&'&q=r88MuZN+- 8x!q *(WKJjjO VdXJ! 099Ŗ-۸Kٽ{7G۲`6lnMMRBHsLS.q]ѽ1!cL`s]}>fD=f̓sbZ SSЊ!}>t l|_ds]J $H u>-MI{bmIhe+5*okxL`lG=oKu#حd[Bkc`A%T [ơ7$ʴӟYjGrZw/n|O:]zVpn ݙ^Zo:?/:2 Cwo!_ͮ2]{Kn8|۵X1ns/sz,Gb8|Oc2 /}.|?W֧`5u֜ݳ3>~:\9 @/$^L@y2 \N9Fun ~l1"[&dyd,c- s">? oc1#d,4[Gßdhf& `Kٝl]'̝@vu<QF<i3B*q [m\z|KqrE'BY )DW祒ܰpY&H2xy kj hhZ\C9sޯ50s8m_j)oĖmM)~Ų AFDHZ&L7aI&?8i+$>}]$ ; a:"B !hqښy`dzAǡQ X NV]3 `~jʏ jBFemURsl DzaRlzC'9>y ^FFimkuhmhjR(ɏQ.t:e[tvtb6cccHۮ[oQܨF,,A)ceQ._׿K^H{k w gpp\=e ҎC\Rֶ6eQvLOOw˅ .]%zcL]cI(d\i#'"@o_'N۷m,,( Q:, {%H A_2 i|%['xG=P |Nԉ8#h u d.*G�{ aMnGqЧ.*ҕfwbEQYY<A SKyf; AjO*5^�Ij`9Gf"#PʦbHTaY<HuGM?~{Y}c?~a n3?Ͷa/aFJX�:}c;7?uu Ui5Hv֮YERmA#) ctYmcxKέ�" 1TJDUmvr_\:gx\+*m렁SCMN/Uw#Q~js!%B$rFRcV*�)TD7:_\we/AR !iij,O /H>RBA*m@ܖ RHdtL#X@ƇYsG&׃ ͱ,Ŷ~؄m™F͏ W5f}(ԥ5y"Y~MV.J^Dmfkbh+HKL&ۍݠT ˶\66]r OX,M>}{!|7pQJr|457eV:;;YK_?m4ۻ9nvFGcaJ1M@F賈Xludm#R7DVFF0998slt&NJ}|!kdX,"B@TB)E.׆tRU.w4cE \ȥ'UMd4p}bW];9=R6a? QmL3y6} $`속 F̥6+$brj !UXcA 1 uS)yM4$̈́])_Fy Y&-e=l5 p+Uc6Aݺ5>eN ~1;14ă|~cowrӓEB 1T{㷽g䆭W܇χ7s2Vea_K ?hHY XpD0 sed7K&H<PbeY a#;ŕ7̎sN6q3a9Yls+GM<8X`FG4fcx 5raۣla"%X _BĉU f}L%Y& ZΩOBUJR%`ېX<-^,DdA%p]*D|rVGgD\2"sDT{l۪E}1xOڀT<t0r|HPVjtpW($h&Be@-T8a~@}a2~} 5aCZN5>Ӹ"r5YeV (?.DBkABŋ1EA:ԁ7;ׯUJN}? BzRd>>G xׯ~t/L\.0f34t_sN>?[33Ȑlu=\P(оy37ߴ/f(h–m)Lc\.177G& #qljQt:M￟ ҩ433{Ͻ32<0;wJ\Irr1R Nʝ;);̑'.B⺞4 g!Gؘ0[DC 4C38~nrЪDGauWڲR2<<:J30?eX<c övMl#e~02vn gn|{W7Ӕ]7w#|k+l)A H�A<5D*JeP`$~#D<D㲞n?vkB4$QkDYÒՄ]:4B ?C''kj",JzDb9A?(w\˗-x'=J'k^Ǘ^Wy-;xc`o~O bMg#v+!iv!0 eO"z_K?5"S!A9JT&\ B,Z-pͻ稲h_}{C0IZX]<u,PhN6.P$o#pEV(B%mkPExT/(6#7nq1딨!ciY%Xux#5"7`;1rK9XHWIe,#JJ-QHe^j٨SJ)|R03;ˑGW{H;&(!Aj_ht.$x" m#0BA߫"6"1]cH!D"ʲpd0 In<V(<1R GB%FϮV.D>xb4mFAuݘRbWkq_z[ԩ;Gٶm+:eYlܴ k)\>o}?vK_](ˊ, @[ɿ|_/RSSSLzGP<J".~\ֲ,դldal6l'=?h(\s5|ߧf֭[GKK l˲8z(?/^ʗ=őS_דVt7?ghyΔΟQl[ & +[E P.e)ɵK< ӳSdO6A)LGg]*<:/koenj hi ө)&&H@s۲ ַ\fZFhniq4td-Y'H JdWіV|QUiRK2(G2W9̺%ma#r3aM(ƐvR2LRo$硤ލK#TUi5:Ps[ d2O OՊWDU-(Kͯ*H~=$Hpp?_|Wؖy{&_,m} :&v*oxgnI,t{nTN7=-Jg~<spBH]lކ|56#,B]4S GٿQԱ5㾊)q:~j\E^sn.k;jRj;%w|g eO&ZX6W3/\A2 ˒ Wc0aUX.=!0Jg%06Waquyſ+FоX+E4UbhQ˥ HO"!rP0y -xha0a)HYlD:~*DŃPk6Bb!Y+581c|| BFJc@0\bt@Қ$D4wS*>f̏扴xaf}t]( `›]=_4:^f,WE3]FBRT*E[{\˶(K:5DXD`r۷32z!)'~X(x;fȵxozx36?|>_C 2~zr9sロB+x!eh?*TFjV  sߏ36_Zioo[y3_ҿ, VNeypƆ .޺_-{;Ww*Y7J* :Plgp%!in`sO')ҖW^;L!϶FNs&j d l (%Q"(Ǡ@mg6I|&&y~德4 $XMj$vibAF&LKAb�m0GUD9zz{evfV&ȉjm,U9Tg/4"mČռ {o 7gƙ.yii=ablm3`ehض׿]"~SE 4g.iyBeېkva7 8)9%@nCX)3=qhg@>7pYԨ)9p¥6ESe۲ >Cz詚:/{:|i}*<ֿC~k?wdͧ =̋4?HQpVTܡ ba\nPf2c$h!JPVnDİ,UqjL%;&![*R̟e67^OTʛTBFDbR2Wȓ&v)C)F *%}!7g|(fx{ۢ*k+VCRބL8,t/rWDmæLf~ '.ӱյ˹Xn]]czzӧ8u;sd\a=;GTַmtuuq )V//w<tݻw}_>}{`IN Q,xK_mیOLo}Y,˚_T218D׈ڈMRf6?}{mJ2R<H9MMM(ܴ--=r$3woJ.u}SyU>(3[ivNͲI[R-3O*yzrLFe&KggN1/a}, u?Wn!c{g?]M3ܰ]Ւqy $5c{)AONԓ�-QRH~@Jf@!:,h*hnH9V$bIEQIJѺLaXmqQՁP]p9oL>wX` $Hpin'Ӝ}‘ig:$�蘿 R͐jFe;0v&O�$H {QJX]w2Xlgo7R̶s~Vwђu%'FaMV6W])`5 a+/4G0pFσJ]K?'bbm[{"T%GUC`!eo �� �IDATc<56h D1zGq]pu|XIT>Ás~[PMuŖg X*ejy$ |He^lW5v!,Z!%~MKzWϣ,ƄG`@) DZӥ5x&#  ]Xڇ?p�^KLǸ硃?њؾ$R^rئNq#Mo#|ꓟu9x7n Jwwto^~S :MP)|eY8*^!BnctѲmqb[Ak b}5SSӼMR~ _?Zkfd-z֓cs._?<23ϖZx M \B*X|LˌMdh+is8:z-31>BLI'Fhos%,) rVpZW~`W=+χ&l3}WԷ $mj)8N>l<6BA @Z2oIQs]W$N#à@' ~ gڥ5^Ѡ}$muh}o $H -‰ ̇?y}!A {l gmr7=A^"?Ɂ}XuOyWU☩9116tjnX#{~%l$Б�`s[]l\ xV`[S]բ'A$ %8`\ڦ1kF (rJJ#*wb۳LUoaɠ&IFFSU뵚쮲dUW^%Y&GX먚e %^n?cY(1\וɫ36:D6#H RTjGq6T_>|ɼkA@g }ct0Xie@Qj"9`!kmy*= _#?;G5r۶,K6oĄʋ+$1>>c޽q%~aڐRry~~=Xŵ^˩ӧV8NbիyGmr\FFdџ2&t?vJQ, Kpft4V~_ ۶nAW^}5u]lljau $ r-5dD}a̒ح6Z*KʰWNuMLCS [C_דQg]o);˲P(�ZZ$MXiu(vfyl/sjK(GV1],)'h}&BRqz5uyiotw]; ._VXi޲HP-𵩳O\G4*֭Nk۹+hIoS}L?]R%-?0Vi .dl¹Җ|YRH,%1F`\e `kX A`ql�#q]YJ2RHvd!5f~FD=cJ $H A b)0q0 3AԻBƦyڎxxRcu\sz3>;{Kzq>Fnq%I9@ AdWm9XnC+QV)9nx\A#Bb ]Vv pgr$^G"A`"œ!JLuݲŎ*15 zJysUvhޖ.m^%n 2#!مzzEl}˚Cn3|iFBt *XŔ5K$[b— R #%JJ<?:=`wF nI !TLl6e[(ϣX,&#|kmT:1!ehfxdGxK^Zk^ӯ䕯zLOO31>6MV[[;=== q!>(AKmmmLLLĪȪk9kYVme;-Y~}}s<xAC2,ey.ѿf ~ֳ6Qk� 3+_g<++V=csdRlg@3*wGT]ד:ո-G5Mֳ�;ܝbܼ͹|GWlj7 _ּ?ϙF<2;(f!]CS䍳~47ΙcE)kQpG~o#ebJ,@^u9OjWp;Uu<wtatq]_Rq@k MܴϏ}xmkyvGwwo~~?]-)9 {5⾻y=vP[J3 \X !"eNqP&0k|#@YA X2p02rCATrƍu,UvOa a*Q uU E4s#K<%H A $Hª7 ۗͥO{-qe\ؼT$_(W"ۜ]]Rwn~nl-izٺ.p+c DFz>+'|1=ph4 R( !?i K 58:XVT"GM56\7 $ÿjW*Alu^+|#gp V%oGa6ːMWR!Jn5Z wu:6݌CE3"Rp,Lp^Z r)$(U \OmL,lԿ;㳓"K"Eo[ g;Ě58 2 �!cg8^{X<"d2az4h͸yM[{}}}twu U c RSNz.mm~kaʥ33؎±ldȾwvdٷo]]q֙ir\e3]_5eNQJ133B|ߏ-ˢl6RוRB*AwlL&7GCJz<RKEy0g'2Dn]{ۍ:H_?u ַ\΁o[wkx|Y'<! pӦ:ҋMvUYG9'B6z; 'a(Ձho”|9tխD CW_/=Wr]һ~xlN.Y[IMiF_f A<99š(> OYդxB؎77#%_yXvUSg<H+ZO?4y2Vpj|4߬Z窭}|Z)rVf%Ht{?+=kVz[+Δ(*6\;U $xb9dlK ~.cc$Zhi4M4tb\C`_, | ˆJgrkVB7 -RKHԌ֯>DE A $H AyJSiZSd .�Ҿ,*,0Qgbr 4a='", ,%({cSS} e TB ᰱHb,^? $ZR*T*bwD\ATy!摰5|jesڠLU W{asPxcvy)O4R#ElGkl#ÒoX7b"pT9ԐP+[}<YҗzbFXzk BIJКkTtGkaE )wIGD.OZ#d!{8UU8C!5o +S.P58q5'N|lܰ=(:J2Z[ҋmG;:;:og޽ yTP(( am]~YM477Ǫ؈Wh_}br'� ˅j[ֶvxٱc;l 3.1R)sԟi!8?w`Yv49 :yJYGu$?eN)w󪸯*Wlj wu$o|J;kg~tCS.yS{h_}QKѽ؁VAFUSbU{7qQiRk1,7 0n^M~ܢӵqw =24x,ۮRͫg6GNĬOq?E?]i8۾2r( A3Wgx7bU` iuy˭븭7TjXA?:/X3?cG?+Hʸtg7=Y} "̑ AY-)2rtd?,(< .ƈHɤjv6[Ͼ}!�[JfRV`,ĠJrYXd0e+a%zp? CL|p㍎|ej=%H A $H AQ\�% ٗ8,Դ( }eX5i4Fi<$ZH&;EA ʶB 2T%:( V'SGZNhd,# ԹBZۗWz"A/ּ ։_csQBДJ݅&'FҸJ'iͅT[WC5q Y߸TE !O[!{RVڳʠCu O 3Y~B�;n4V=EGt=g( 1r4S %<v1g馥Ʈ8@9Ӓ173yx$r9Xs?r:;H9\3MobϞ=(p)$sssXE&!jE|Dz-tlmfQ* s"R6.kJZL=fZˡ>W\q--H)pR)</|1>ѽk`f~TepἴP$q\j_W_) 'ʼNoc[ݛr_3Ɵ?{۾v/z@LFlLu$K+Zn %(P <S Zpnĺ f5b 2g44h*?jVic4Kfx;|ЎWp.>ٙwE+S.X!{"co|v]{z^=V|_No^:W^Fm<v!nu OoS|`2SbQ E4+ޓ>G7r^D9 Ґ-:XݻǎcY h0Q�ТZ8JP%+!h1O ,-5gH'){f|u{CK $H A $H X1aT Ȣ.. #Fb |5h%느FTmE^*\~  F E UdZH?$lu+AZYcDvo#j9SlTX@&pfxCLp}j"Մ]hiQjm 8Rcb}[kh,qN#. ʵ[ft"DkR%6%b#\_^~i1tgU:O;/|!MMMhcU48T qhmm<xbdd)ǡ۶IRiZ[[{yXŷ︃_{mL&MMM ۔" `18BrP+7ζЃ0==Mgg'6\ץ| cc;v[sLox+~/养xBTΪFơ3E,Ta{_<훿' ;dɭks˗u Lp!M{]O:&^ϔ|SKҡQAڐͤ(ϔ8qz\uT}k@*(NJA6 tҹr"G! rR:0`C�'x?gpCw~inp5keG}W:/NGw[~[j+޴>k],p ?zpF?x(m [p>8 \Dp7rſK۾6s/1 5#º#f> qABFEqըNuFhCW|,A7B3ϖ|fyu@Dq~T<vz$!d$H A $H<chv:t2+ >bR|ape@Z&BFPP@^cPXH#)]ب@4&p]ɩ))Ǻ!uCccגX}j9l*JVF`e)=C{Rb.-fOhc)vȑZ[o-\Ss #d}61" g4B6O#D$QO ϗ6/b\,@nnr[o`)J<z0\ |R$s9\7 R)%=!xeUVŪHZ*)9C~p]Q(d0==[.#"JdbbvXviiΒd</'eY"Vk"Tdrbqlb=;Xۇ<OǶm\q5\Bگ]Q1QtWwٱv2Uz%ͭkD5R樳tPJJP.x堮lɞgvt<ꁇ~^Q`a7'(MBJA1 �Rͷ\L1ϻ ۖc%-tE8^-{ۏ]bkLz_mJ{#Sny&+iNWp1͞E. (Sen ّ9xx -a8urDI) H))9CHƊR4lV3՟3%M+A3 j^2]s/;9wn?-%H A $H A?тr$$DJTY.a2P`,/ 4hzB(|)hi(!Fc\#eT@tIQ)_"3쇇~jηm:."&ac,elq=7J*/ :tlѩv9$c,@VU߈mbb "&CDyneq_SL_y}w #q6)*^z-XEKyosgͣ,:Ͻ^f̬}RY-l#[MX6+cL7k�fni9zhpX0bLdIX奴UIUwF܈oɬRxw7-۷Є22  7m"ř?iwAN)%]w[6>g(ijh4:t__C)E/േlXZ\d~~e$A tk&[nC5]A`=s߽<xM;piJaq~>]ۚjkn<HmؼW^JFK#h߱fHqBàp̍G>PJQBBh#?tضױB˭LcN:COkzqB4owr;ǞF :xA tJ.0]ZlKr =�yۑ!9Vdž`#{T#ژn>؄/UwT%>|:-)uj2F>E�>| qn5>}ꊐ`7B�� �IDAT &6@#Ð>Yx8J \�/R0rL<F##U &ߚ t^8 0BR z 9|<kFruY?+0Ȯhb%}JF5jԨQF5jZk9uJ0'-5Ah! O։b"&1 I+$Z &! 5!xP i22p+B,;(Ysi6ǫv9_1&W\y0: M.7c6ZeJb96HiEÖraqˤ2#~5joES<)m7?G#F!+.y|q4(K,?zIPڮsFteRՆ4!mɾkC)EFH)9y,C_/6۶g<ewI0;;˙3g}>OpwWn뮻Ow~yi6!7oaݻ<xѤFLMM21dMDa@ԈlLxb֡횜ahO]'OEg$uA+Κ~)N'g$\h+|׌-s?~/g+?|s?rfcmS<uî7_?{8VyZJ BC! l S']M.HX[YBF�]UDң| x߷˧擟%"=r=>" ;o.9g\Cro<kW K_9WƐwų7רQK!e4 KEe>RG Ri;,/H@�mVK&`t[L[Qrl۶ X\\$vRbd֞Ybژ*ko5ۘV,2yS.s7Yk5jԨQF5jy3kK@Z/' [&͈*b2/Bz*aRQ4U"54dE'K2lVg+,W!3{B+ᘵ1L`m9e@ kX"T z(~GgG/UT[!}"Z˞+tNpK_.mkn Ǣ {:Cze&i9`l~Mlzݻ%>D3-g,$7 vDRx[B)Ν;$c4nG)v֯8NXZZka~~3V*K<أYة43پ}4&qE d۶h3"6SPv]oBs9^7rv l۵Ьu9h Tw_{<TCg*kG3;^5~%~Hwo]2!O~%]a&EJdXmMr߳wX;סD Ҵd9fg!cI&5<oC_A.0zvj8}In>Rckz<ym!d;kfׇǽWxbʫv@?>ﮗQ !e ΣR( Bt)O?uǞeCKInZABc_DA{P=n˖-Z-.nׄ%$zGC #k(t&s.h!J٘iS* C[5W4;25YmWF5jԨQF5C'<H":HI5RښJjBIIzB d,B1ZN ( uԘWVTX:ܦUyJ} &7A0Cqpڕ@*@ ^gmiZ8IͰ4IC"矅5HS0MPWb^0<t_`d,YC71D1m7焬#_wG>>F.^&3gX[[#FFvaI;u;<L#c>b-\svSNu6@%i37RJ `v,'O`5I{MH"3gؽgB fʁoWW&y8My*0y-&dvS s/]%g[0.lk"3/gayms|!:B?ɾ7;_ԤduɄٞڪ`9e"av퓻ޏ!w-||_}ϒ&LshFŠyN;NEr"~I|wu{) ߷lEص7m]5jԸ$ys:U!cz"-(҆,kk]P]R!abD|2j]ZkΝ?O87Z3؊(Bfg2qyZD1*Ơ~_$F5jԨQF5^Y]+m8W+++^& T(/pLXT!H "BJHZmt*QZ<'taPFȥʒ즽Y}GKmrDR2oԆH!H7whye32UkB;Ytʮ!8p/?)^D븸2s 9bwln4xl!UM":5 OA@aHE4 MozK0dֺ0 hl6BqF !<& 1rغQ15543==Fq3==yql@zmw:&rmwX]Ycaa8Wݻw-n_@NM*ӏ-elioye[v5Ɨm;gʇ<cMGkH&LL>#O/>4ֺtO9q9>Ľ^,hD vcW~sQn}7w_]{kw\랱gk6RG^G$psí5 wc>kԨV:.^2�Le&I iBIlBM}XMySJYaS{]+0Ҝղ 1dQ+BhT.lfGZ8!o\K{]^o>E zF5jԨQF564'jQ@5& 'ɉ2mUmNQBx Ta* Dj+FjmQhUXGO̐'[hp<YB]'>059m {nu}TH!b0><P׋/ YƎ$o+K@ BD`%(2yfodYH]X|WfcCFf0W_6o^ewrq>'d(mr D@$IKBA8v{v#^53C{uS'#̅\ۘsݕ)CpUyɾYh޲;A߱2ni5[x ZBY>w AgM~ߵß<V׆}97n:c}8~n~׽"摯<Eh|M+I&}NV k_y=2/y~~拼Y�jh.>V|A^%8+Ϯqt>ܚf7lov"_GլeuMBeNÔ8<6/r3ƻ&k|hF+䕻x㍳L=w\Pg)~M�zY>ٕ˹ͼo+@ķz^stu3dlxY?:a5^� Ǟ nW32X@ @)qЁ"i `H[2Xr $Q4BJ"-&MSD`]Z la/L'JyYurm={ࢡۋi6"~Og/ ?:|q5jԨQF5jv>99vDh<ROwO:PiD tڒRA@S689(WQE=4WޖIN';0(F rfy;DZe9\?_YOӤͧ*X\*R&6Gآ2zP}cP_"G wljx6\' o7ƍEC}0uُu Ĕu9dLLLa E{p!|I{1I&I{.fѵRJ4)WBI)3bxee0 yS=rԴGJ$ J#_buu)lBaH0n̅ xi4'aW_2+ǁ-ƃ}.>]3J݃[YX2:ʏS TC?5޷_Bow\嵏.o[oMnnm6Ԇ*clhN5$_cNb8RL{O3qOw,rFsŞ+Xp5lj,9 _yw- ;,꼚;&p;|n$ -9w{gWMo<`Hf~LWX3<T*]$o5ioo+^iG@ #gk@w㡏ӉQ⟝U3�Co㧮_P*PGc5j<QEƞ[MtSu4WB{†ZS&Ea_xa>!c:<čcsR,"$%:`"̈^0M#@Yަ'nmmLVzʞCXAn�Z!ѴQ_>{#?o[ \F5jԨQF/(_zs㲋@!xg+0LD@/0eG RH)B*4:2sQXG"Ԏ[e ΊYQec ms@[XcwP=[z1,,9yKl NAiUX+Mn/*BG'=_adF: %,İ͠"InF[>|efggY[[c֭ni4ulq*N4k;C$nӴ֐R2jz 2̈́>*#Aףnu#Lh7ݔ|5[x-[ #i+\}HYG:}tW9s]�֊ʿX]e ԟ#<škp װu.a:H(;6_{$Ϝ>شy3KgX[1rʮG㓟Ͳev:{xg9x7z''M9=||􅕌u8س^~S-֬؏^e>{]~v͊Rٞ*4_�ٓv&7Nlm~Gwtx<qdޱmQSZY|+|nWs iJBhgli1\+ƓU\(҇Ί:aP: \3ҳNiC tnؽ!("]E% /!*BkHPEZt�9w~?ciV|+8{;n=gkԨQF5jԨE;9^"8?<Ιz$Z:BgkEH<Z!dTB4Jhh;(mIYm٬N}P}{.=GƂI)%e}I[H1 4.&T:Ճ r({7!%uև줱,@>!4V˶/ru#x�W|)nx F$I@'xY6^/qFΰ=FkzV*n={1|/ :1f8 0#|Ď(#j׭R&^$IlϪB8AZFFQDhr?Iv~q(~y>~x^?ِk+K] rt/_m/T.lsU>rffn!?Ox*s]<;{rz)S0 gVʳk5?u&jH^pJp\MɡC/c˖YǾO[fI7XulDl޽sy:سS+M =8~b<Ss)+:{ Lxb. 1.4߻4wN2%j{3!whY覜Z3w?'ֆ'0ΝM݄nS^9N,-^;/'G>ydx_,MO` ɥOtmTkԨVȯ !c~ ᓲ_=as5-0GdU¥Ͷ\M ЙɡxdVEC [OqZ)";XGiTBNHiv_h yfgx!= {1]!hfϞ˻O]~.N*YY<ܓ<3vڅR2ֶhyBLWɌKuygk^iƦEXdJҍeދb=;%-+x6M*R6<) m^-[O^zYd1׏jc3`\Zzږ?;2uQw~6t1}ԦËn}0 & Vi@(^PLqLb,4סω?.L-C#Dip@+Mkskg-g a 3}{}vN<ə Q|Wo{ <0]6,/F8"sXsgF >SjHS~EVu|pU+*PHGDqѶ9i"n5 ~^wXuS[R!I ZSDQDۥngeMeEᤂȅC<vdPW^[F؁RS)Aq 7{n:5?"6z_`ǥ0*e߹v3AIӾa9^F^ٔ.@B.x4SpR8R>B^ܯZskQ1v܄Q)AzR${h3WF{|͔BU&|\G^?IfZu~]%ʼ/(aMcY{Kƹyy(R6a{hzSp}ݿI8Bk3ۮIAZ6tv-{OaM+xgё}V$O6cknd-hq,̃^㴿g5~3`[Nzn%xcC>2R b%`xQ�;?{G3OUGʺ e{҆!Qqwr]wq݆Y\X330 _wB)6m`4k@DmˁVvhyFp;6|cqZAe?XE^؁qzԮݔc5"{v.Oe-|Yh ☮4Є"drvW9~^sy&7mC $S$h%@ILXUFI3<& {zc^2ƪ[5s82v2׿pC4~;LU+ߴh{\#hP8!Ԇtv0C2牖af^�*MA ˴]+QM:isa#$hz&HR�� �IDATDʜ4M:ڟVe뾏RF#,JLfrr";O!-qV`H)2r(ė\:>dPT]O;J^_. ]PK::unjZ (HˢSU(^L}$ 9H[h,XŅ&D "RHARR@Yu7}1AƄJ{ۈ\Hsmؾ};Ofe97{t 㪸F)7@B09a SVVI2b@ze YV*Lc>U/ `X0㯭1? d@o?W0M&Mk^K:\ZeaqӕsȌ kHPT  cUU5@W))3#F"_/6S{Q=eN ިh*훛)ܞBd)殟<[OV՗_76f_m6,1Ԑ$y'9~IB JMPLof/, &[l(eͻDžr kAU)rrٱIr.?agiߵ$0FBޮ t_sgcdIaY)1rkQ*^icZ賕CDV^qǔ/�C+ץQV9jwO 8oPnJ]ڄNj;sָV&$l;[z _ϾE@loP>kC!(=|ID^'h4^04B"dv, buzk~J>^�Ȇ{+ Y.3.e>ޤW<.�՟ִ Ufͭ Cn+-! uߵx:>86f+21MS.˨$,--ؼi@(Cnwh\uAnFAj^N!R)ASOFxKC)5B oA}䌭q٨rnGYZnD]zY\eB8ܦks_:@"ڬjA/B*Ϝe%1I$1P:ԨQK̀_~xdï~ݰ=?r@i&HQbVc!;*DJ9q Q}N>ԏ&/0^`O% ٤z=w�´K)$vn{yQy5$^ebAec k栖OXU7pl Q1p<Nj|G*yƶ^K+2jkNΛgE =(7q.D8.(Fx)Kԕ`7ta0ќ *RӆA B+ )Ÿ(7)SrJ {nuܚe!t<Bdx,)<ΪVRTuQ*(0;YD4M8~8KK\8z=Le'dҵ=ꪎ uXA�\z"a>gXU$3c^ʄΘ1`D#K>@,|akq> ;\OҔvM͘_~qSHT $ 4aegJ4®h3C?#}+=_*jQ\9#/HTS2; "Xk_YO}4q1ΓZE_A)̝bHAR|y\@. &XJnfl)SPZ2 ʞ(Ǭ=GjM@ A*�%1P=KF[B5?(hZj:iFZ6#KB 68B<'`)U{^p*l/UFSAZ#4- zl* +u#g|a$iS~*_ܮwi^ߴ5(Dx/jB Y}Mj04]v^� A;�s]b)%^!ؽk_xr!dey4M�luC4 ;!cZk3wvvUaH$@,//Gŋ$IbȦYc?y+ӄ;^ѡF mͲ$dץ%(Dn{&Q;z<ن^!ܾ-A*B N'ضt4`"Vn݆V/5jԨbě7[$cx_TDB%%S< 6dc&<[=v㮌%hjM!&^G! $BH)NӕD΄N+y(Q9%!^P,6L3Ӑ:RiIJ5ۭ); ʄ $)~a ۰)1T2R %I(Ak`m>2x:_GUE/+T]F4q7;?>GJ5-J*;lMmNV8<GBfW*j$<}Ūj8˜NњڝkkCH!pVRZuR;YpF7f Wu5펉"DAfHt2Gnfp\՛}r(ϐȾ{_ 2R;\kM'|DH$R vFA8Nx1'a~"Kzݩq]^٭<~acѾr_ D}A n/cA43B+5H#~Z\pTQצVj7(aIC: KT.i?#V})HIJtEj.qcvHBCvYAkS!ɂ횵952& RH")hKP(GwRyly{VҺ =.H`hEPRmirA4Jdgs0׳v 6.s^;<x_-z9e egWdQV Ѣgt<Ժ?`cg#8Ҳ:~lԊuaW G%}BC#ֵTQ~C1N,q*uJv4SYY)Mmo Sv?pW'U+K$SZjʖ9nNAPZݜcUNc7ϛr{ʖ>࢐T< ]gux1[0XPnvW|ϏV!-1bpMx}>{Ë: >B$D XYYajj;3%$ gϞe~~M6133R?_p?Y]]%IEĽ^ A@E,--rv,t::n H33 $aAբ1Idj}-fv}G:5GfAy ?kHB J(*ύAaϩF! IDhQ*YȀiP5jԨ$JJ'GkB_vDv¤ ˅%*@oE%%.lg^VWxSIB#(eojviLl J3_<@ Xwp*MiMZWR)C2{ʥ~ŧθ׮A%9|iʆr{DY)93JzԬE.R T} F+^hT*_a5l ^y+xGE!?}FJ" GpN{eeP ͗'^ ߤ![e^ v b~B9ʌFGoc'FSH1xR (ҟ1$V^`'I@ JqGx> a\(A3�$IL,iW%Zհ=P \SEqmϔUvQZ)>%U&LӫYlnLlK|m\偊h[G]s]DmlNw(tiQ Z2 K,|sMM֨bm獔OAhZR [yn7} |7r2a‘:>ib.2BhI& 2)uKA}?;{VymR]!_U[5ŦB\.{kA8@!yd2Q%!}s(T#BE+d@ M" .MSE!׶:"uv-Sބ�,~k@3@&ZaK?p!+1 }{d{x Xwh«7*Y0բxv{ұF%s__ ci{wr0=J^ʣ\!\.GY~ ѳwai^rWsFjTCǎ zvnTl;cV_>$YX$oGxt:�DQđ#GR299ˬv9{,*Mټi3r # `,/-rĬ;'b@̘$i͉IڝJ+7�M;~A䋭qe!&_>~jDŽ`"EB=$^S4 1krnhi_'S֨QG|'+w+Hq2yu:PڲMA F M AJ&5* hE]XaGV ^n]>BdY .Z+kmIԺHVY[[ܹs6iMѰJTPiBEDR֦lq`Wv\*F_->*ettaEU0aNjWygU*u~sJ4PźmҊť%VVPJHV(hmby(-U.T#2)oKL@ O/*7:{0[­ZPI ~BV]Q+J*eak{*@HI8N?{Ga5a`U#`ϘaeRTml& 6S2JqѤ:evf3rՕR>J[r1sH[7ܒ.MqL?=47IJYoz ^2 G;ڹ^$cU AgT~vgc0P4vҎ(ɽ烱+_~nU!(@ E2`Y׬]}ʼؤΌ(ĕ&cjؼo^ȸ6t '8rn_52P-?|TQƝR42C=Dn~?bp# Dҙb:mػx,t\gd}պ/<!sLtk]XKVWr0z;Shוb ^2Å-7m"4{PqB,[oٙZS2 c9s v""n&]wD(8u??Z I0?iE}\䩩) 8u$3-$%Ar-ni5`-#WgPk<!|و8Lw \{aX&`Tzx!KM6 w_OceW5jPptnƆQx9L;䌈`C*ڔB@%NIPDDmXTYS!L]'�85Fwsss k#(!0j6х֎po=V@<oO&I0r*Rg_{[gdj9hԨQF5j<qvics|#|9xyHu%oNv愐O.Se R2ۚf=/-rns& G�'oS)}vU 1RqN,UpN.NH*jlPo. _q8,D<KLRWV aç V].GyNGJ/]/ eD>)kEi!Tmh'ŐzLxuy֖aZ7??ISŏ҅+ u%b:Wcy9s?/\ pm)#Cec#z/9B`A5k>Ȧsыcy,\t:Z-VWWٱc 1<1ŋDk a211+&Rl6 4M �)%F^$M{g}<ycǎjziJsM׾x"FSUW2hEq?hD 7@ѠjԨQ[Byh|O V1jDT[4M$d]l\G.HRLJUh &0̑#Gt:iZ c `mw6Lq5jԨQƋSomO>E~?ǩ m&laبLʂG(ߥ@jC 5R,-ݧh#Ē*P{e*e)Z>-_ j^Qš +GHhl"#0Fö<F`"~ "gS#HH<k qR]qa9#ht(O3r~[oǸcƖFWǵi=T8ecU.sDeRf~}EQ6 w{YP¼;l)9.<^^LD+ϑ�^12OP219on=^I$|vQ. ߇7ΠyI5ly[ނx{iLOO۷ogӦMA@�VDs8`ee%{ 6j-<WqLe*V4͉ ffgi~�n}-o#!È涽4MQF/0LCzÈpTxeٿ`MLNMZ8MPw)E倲) QzӎF,iK<_UN z*E+Md턍eA*~Wȍ$I8<qgW+?/iMN]~5jԨQ R>xkxﭯࣟSdzdf'un~~+k�h!6O`өl(t<kKE{}R\?(q:C)Z R%bqRRA8 ߭am7vp_9ڕ!3oŐϖu!q8Jgၫ02 ĜqS5jIvW6rht a"BYos)뾗Q&W7h=j,2#4+uwkժ6ZdY_[as#~ʤ<~ec5:զKN*ɑe *~Q$z=fյ1Y֋4! -9'xqN8O<֚i.4A@E 8Fu:aHdI (2Zg޳NIEeʟ�̌Պ5jԨQs )4D5'9)Z_>!+M;/}n7 Cf!6\Fѷ))XB8/\) Qؿe7uoy#;vm'w{7gNDA`9(e#]Z w3?`YF5jԨ1 S?ȿ˻{ EUݰUᣊ+2TҔ#=|dw\&O2¤EG̈0RvxT/KhZ)ϾLDb9 K:!:^Z?kI&P6B�iI]9Vzc>YʒUDtnvWǫEa^T%VBAUbGxK� !x3ϕDz<5³BpPI:r4~^;znٳyb'MvlߎRsst]bSwUD5.7 \Ʋ0OwѼ%O:tx}7C skxx�� �IDATnJݦlO54&&رuVfs"9'I=h4hZY(9qg/n 䁛Yy?JM5jԨQ% J\sH|.,<m hNi�)r BstDz>g}— lb)S ^WsngivO⹋IbrM4ѢR$7#K7w]b-5jԨQF/l?wpja<OYa:&?M!iGg𘓍Zkd M:$m}˵Rr;};3w'Ea1DUf0oqHJ T$E2 ڦs#JU"1]Kl`㼍C֐y=$-JXmXHBuȆgźd/3kԍ{ږ3@|˫|(3S& <x}e�7jCNtV߾ױk?WWER֟c|RWy,?kV$M3OzG c|bFv<&kG)Eӡƃz\6QHa?W6<(9Xr5VeȐ sMg| u*qh4h4(mn޴ djj`HUI)DQDabb4;gu+l<.5jԨQF<d5FْaޫJ#*.r+Xs(/<AW"u{<9z3ϓ&@ Qk6I 1|r~HdU&Z؛+(HIIM M 9*$M t&Ma"޹z^F5jԨf_٥e[rȎbO,<y[JdBu"e̴& #REаRA^ }˲]sdO 3FX=G]c\_&LE'}JJ?ҰFe(G^_>='|CfIpyki[ $3zZHF^D"&$p7;ǜ8vUBۚ4 ?$,뮭l 8S&:[xi3U.See 2:m~[yƦMi!z.ΞCi ( :5= 4ENZ6K4+Aք[ly+XC;Jœe eiy4UlݺD�(X$&ܱyҦilN!weD_AM;ԨQF5^XA ?Fkg>7r]6BRh=,Ϟ~DRHR-HVQXK%%Bcɞ +$.4Sa@{u/>0:l۱O駏F*  @QBRww:gl5jԨQFFSc_(Tyz"aG<"&NS @hP`BKuLo1;V#?]m?|5qW\z:A'k8^&3JsL=B͉L]%Ap+@z=89j<o:3(�{PHQ;B'K6^"KoM^=^>YD {m/U|㬬!OĪ?]NV<]1Ȝ*3z5eCadYkWbyhL]qЈ>br2j9z)^ gUvac0rBu}2_Q g~ٚVCtڋ,XJVe bF]@ Ð--RIИD6[[ "ܺ]hlߟ5jԨQF'̘z DJk$AhMR&5\9,2:GܰV :5E"Vaԝ>a)z,=ŐSX#O<9S.^X! n ‰J)&sT۽뇖_F5jԨRo{5g �X,e<B a|Ey[U#$%$Rxƈp@c#- c vdGB~>>WWŲsE8"ёRL({)ZhrH񂜄rE}H?jbuUն-vLu@$ILhK|Е]QCEbcò0J:+Eeϕ%i+ lUwlv3L6ނ^W,dzY8ttF~}ۯXkn1s"0x^NݟH 8mټk`mm'N$S{z4ahDvDa"REQ{G@_wt}oh[́gWN{Ɗ*[QԚ M�}_M QF5jԸLHB-V"w|-`a"~0N)&G!4@RB V#6a$i!#_2K]Kok娋R6Z,/+J)M(P@@ҁX"'qAY%VH0Af^w]7}!~kԨQF5j n߽|9Q36% BZR{ zR-SI`H Q3H\]L2NU c"2EimAKU i-roCCxs,iQ.pL)@5d;)/T;34QzR@j+O!tDBCJf� Պn X 5q TjM" jg€ͳPEiHmwD-^>ޑUZd$c P$p0k@z K2KgцBZ)1!+1~7%IĎGCe%3+,6E$ 5 R Bh@  h.Юm$t/z�(5PEVfVӟpߍp3-#n߻7xn~fo#K6'  q"ِ綊 [Us:g$ƣ6Y ˣ6C $ WFuZK]9�q=\"@+Ⱦ#'.LO,}} [A5}JgD]齷vݵ <nYbCĒ%~Hl6Ӝm5읻hV O5sS 7 sJfAVn-FS: Ęz= �P6D XC9CKCsjfsL\ē'aŵNNOvg l6':d~6Ba->(/+ |x7qv~n.%PVB@ ʊ$"<JYuӚ<Ǿ?dnٗ(A%'U[I#W�V88fv'>oCCCCCCC:o>4\&)1Ż_x g8 P噚b|9K̢Z�Z2_kJW ;ZG ͒UZ8@m; ꘀjɎN2ʘqu+LGS[D/VK^KHYijyv Y#aZ+ eiS[%cʢo5ܷq]S*͒896~IJ-9Yu"@R#yٔ#F2DIMα85+Hn9<flM#d)[CT#Hůo_{}m.USREB)`0#HE@V(q66?p|nWJH.ޛ/+t"azqt I8%Jy$T,f"w*'fhb B}=ˍنU-1sde@w[(6# J ;RT@]BB*YҢ L)H| j<[xc^;$0WX,SF2vmcek/|܊$j) 0D#À݀ mBȴn2C]T,jHRFG@hM>Q̽zէoʦJd'+Mhdֳu8Ո^.DOH '0j1cL֘7#Cuw:uYVextWYYXKLX+2' HH*ײw5]"aG@? QC aL~ΝCJN'I6!tnhc׹"VsRN>^Cb] -<B ercSW)}Zso<~Cի*N.V;dD듡p(nk H?7֜eq\X>T!I S:w�qRUl42][&hlijذl2bGIxRPǀ;LL<j6B8lY5)&~3W8=g1r[HPAV8ej,Ť}mŇ7'!<o �DzԔ٩LFzo^qYn֗_ �3o|،0FUo##DP2-}~iNwkK*%Pj_2\_ʹ>,V9Զ!ģNݫ\QP>)^n0݋rɪ@Je(t'DFAS c׈LOD̺e4v/~{e)b}v fLЪgDDq:�# ᒀ- nH9>K|L|y\NYqO 1k5<ax/90|09R\=|/ɂauV*HD=UN2r6 81I:6xYqkoqRD1#3RJfbvO)[n=ao,%.D0aztR*{ϻ'0L|{j#@$Qʊ+AA+#^ 7Ba<EhUdDV94gǫ| ?z~m�)Rl!bx&BՒ9o'b\SUY3-Ri+*oLs霸g7,O P eɝb7Sqb7V ڍ>&c/BN2L+т- QWU)(˶j2<N܁Noc.G5B 5E!�8K<r_?k[ΕD{E5Yq4.Y\l Щ!K%T^#׈Cjb:U:%lTщA;@W" ~[w1Ʈm߁(=뚚NOMl2vWArܻ COךjOUũUվ%Xf9\W5FŽԹ~/朋JVS+fZ}agc|\$Lǟ χ񳠪]U<�VQXɬ-/H#,.hS?}w<+{KXAKw+={->>'HÉ0Ot+c)'mhhhhhhhh8h]@rv1�mz< чmnq-#b4 plP9PO&~Ypsnk{hUd\zKc-{Kv>|M.444444444<.VXAoksFd@V,}jq PMr,~s$EQWae%Mk(u~ :R׫Z$q҈F.�‹xnc{c9,1XBv0vjXFrj=6n2&nTccq|>CMV}g@kj\Cf"t۫sƦKf jCkD1`aTYUW-Bwa5(IKjRmd;U6>63ڶ$ 'caN䭕RT7 j]4ȤxpTu[e;Ӥս[ΕInYgkZHr + 2W6^/!猾&x#lJhY{w_R,R<"o<qp=]<3a9e/oXR>r#Yp"3Pʳ/^^pO|_|q9 v9q9uF644444444V SZ6L0:1{5fG =js2Ir <�ucpZcM d\ Ƀ8 hQZYD9:>GddѣicZ?0;(JzN !5^E,Q 3$ u Rj:թem_:#+RfL<eaX-Ygp&M J�bo2’׈}>*U�}L;ʈaY,*�4DWTYg>~LV˕k!k%"TlK_zFtPSnK{V&nc͘l|TM }38u+)aIdԩFd#qdu{tMuYK,jH@aS<SC/N3"0'0sRCKD 5o_b.Ta$j$7g*<{S?TX5 $1"Iu_ok$rjſk@AB6k;U}`ܽs''xvɶd0#nW[|ONu؎;ePG07!а(KDS`INfHL"xO~.%۠bj6>nhvtdzTU @BAQ{9-7mGQ-˺}Q{WBq<ʢuubXm8LrTϪPWy""RړzYx@\T֭Q+dKr@lY]?#-X?' 1pK g DFaRVDXҨI0b-vDF*&""�zd,Wu"4R,;q#x_ >=o+_ |83*W!fadI0cŨ$ؔ@٭`#r=ٜ[\nw%yx�:TZTb{ؒ8 蟚$8e\1S絆⸀3ĉ׸]_J_'dʞ( ޯ78nr2ztK-G׊Y#Um7bn];ZiϿ(Sdk)ROe63u.ޞC約ӯ~WK|QdYm~i$$,f ኁ컎ȂNY94BaG**Llb^]L�mq AH%h]ųe~2rmu8 E s%压Sme�>#*ee"Vk}1}F,_+Ԉ0fՙC7Km(Nv6H2fƦ!" Uޥ<>Avm#W0#ev4Op~)uRlNuy@".*,b%. a ɄmOY&5n"Bx8ru}MbX`HK P:KE1O5%#nv;#cU7m,0@$Ȕ1v &\t&%p5]p Qp{1>E @F5!k(4͡P(2Q+n*c9%tCD˪P% ku`|*P0w`f�� �IDAT1vVȟZ 3H)% QQϡQ!w#1u~ Ӄ<fO|Dʢؿ/)oݶn4ӄqaD3Beo&u ز;+/{8oq;}0[F644444444c>*gfH)@&$ V $S9KO]ˬRD,֍VpMbL傆V9RY>mhhhhhhhxՁGFB" MeJ;hEB+y{$[942'P t01qk6v ]K@A5VzMksw+>4g྇:ICW<b AWFԔСXbcjj�`NqX(f;&Pߕzce+;?rǛHA}2v>v]k;x65o=c%Q B\rt KFBR\ikՄ*݀q0PA_%?vq-vMT.y3(QRBnK4k XU JduZGVo6 "#fK{?9Ie\_zLN*'OcQy\K,<5ST{~1Y\rFPB[?z o62t,{Bx~cGã݀Da3UejAmhhhhhhhh8�|%3,.yl y.C a[ c,ˊ p־~O]מ^ k4a6-,Y<"ɗ.eۆ'BW!k(Z;uQ'K"mE}ٴu*i=m/Yfܾun8`:R:"4&g7*m X#"L*@JTØѥnČ_z 6n9U0͎-Y;�Kر+;U.3sslTqsyhU0tb-<f�RNJUk!Gg{@ږ[C7,]iф'%%=> HXu+l5m<S=WXS|b#٥,:iLB %M }a[FZ iPcԍXRTG:D\l_<V!uW<;t]7\dB(0 . Z2˚r[,ɤ܈7RHG>`9~Rl 1o_M>貹S_3QHd G16,)%aJzmu˫]�sU$Ό4u}z>%d�oYt1HmhhhhhhhhXGrIjf~S[acYLV }O!w@d#/f׹yQg;kVpHlડ%@v, Ee]GCCCCCCCC 6_/d�|a+sȈ:.+TMl\5;FH!)2N|:#3_Gu  _eo?|- I 4ڷ3Kb'\lM[a%MTUD_4X/"sX]J@Ļ�2#0c6/g�[f�vS]huŶ\jr4A&/F4;m]\d'[c',l Zk sUҔK\1.1aW"fJLx0n:Щ$}1&`ݧM jD\&4zU%#Q#"0d6h3adfc nQ}qĮ=knJ%"D`Nƣ^H?SY2 d7 'e6%hbh"l6'}QHawmELU)Fm $gIڳ%\UBƴm2dvbS6׻v'Pg(Ǩ1Kbv 7*s.` $${6H`(D P- �{p 727%vV-Vfׄegg8 [a}/ Pk޻^JOim mhhhhhhhhxlԶZkٿ@dO{xSi8&+fWr7)p YM&K! rY?͔aowzrg۫+{prݦşe|~U-M}(4\c៪gM./r0AqWu!zr 'J _?zw_a8?/ S}^bLTu~�l6r*PEǪWF1%j ?|Mg83#| ˘Byz%6^Z9TcBLìH]NFgŪ3%qL[s:;!1cGl=-լ+*#K\)ᵴ"<�Fp\?9:~86[-dpxWp8)-5k}P'ػ%N,qR5)aHЫ+}nsG%q av4YrHY17a}^w>D?}�4OKeޭS'e|[kب4qUJV [ݡ N6=.K'@ =:;nnVI,xH= ^yUYa"pJq,Hg:EJ#d>J5�m*a8s)H" $Wչłb5&8WbvY`폪%\R�fEL rUq,aڞY'zF64444444<U, */4Vx`RA M ٣TI4Y+l1;b %#j!"QfF.Z-G g·~Wѣxp1:BYM04 RvdwHדT22PTɏ5Cs^n�c3O:Dh�:h֩!BZ?TR0s!]k"dO&VPJ@4>`[Sd^׾x~ A,6U05kX\~o9qzJ{q"yzNdXܯh'yozD] rjD@ux/ >@٠K cn:%AE*e{fS{{B$C8BsWWb;CyVrL6hʂLc!uOOO<{1a�D ji%-2垬RJ>m/B) ھ7eBpLa$KTD@82 @=W_|%u 7~O.wPЉ4CRb75H*Ğ)w'`W)~la]"&B"啧N엞!�?OU{ � |q\2OeCڜIxv`GX ,Ʒ.q8@ZLR2E[{UBƁCxCCCCCCCCqOUdJANN?#%ytU8%�|Ὶ/]9+Ge"tO}Q`9nLF:QeW LLb1< 1Y]0Hw@�+ &:8yv ! 1C8:q"]^W%n:^HDT5S%F&'%#%N}uxwh1J%x2^S9xV[1fhTgqX51V)dJM-##V} H)AU1Tɕ<qoO" I4T3BbZlH}�qKaS&�ɂasFy7QP#3ǢUcKi7بF6tF74SW#zb<{Y@۫-Tܱ+UpUĈ oVk1;Ն]`"ŽI*tt /7{||2%df8A0;Ǣ~6ԞŽׇW_gXR�(ekq]M!p-f$U,X ~ũ�Q`lG'eg;goԸ?/j̃rgE|% &3STČ v [(#kN͔Id*RB8^yR/3mԡlv(]HDرiWaآھ2;ʘ:l ({;2Ft_|./�W m J $ƿjvd -.ĘI :́$NFeEW^y_sr7xW?OɥWߜPA Ԥ-T}N ,|QU#Qu)^픗s|T%)7^J7_)AtR>nݽŎ0�~4gLG4-Kl%",j:BDA`rJj' rΥqB5똳=k$8,DZ.YJVĀY7*PUČ龈ZK .!9\p,Bn~{qn]ו80 Cl6LSy_۝I㵟Ϳ+>ޑ}8/f*A:(_wCxk/$8)c .qoE L"C?(Z~־hBFT=ءD0%: cO mhhhhhhhhYe,>qҍ%Lؖǡ ˌpfLirѤ,$sא4--HJ 1- s;bf8$Ԫ�Q͇#~h[F 8V ;Nϸp/<3Lo<Չ ?c8y:L:S'נsfA >S;..``kۚ1O֒ꑔ1mbn˫N4B]b$%`EdC"*S \Qsi@۷7_7z'}\ eFMzQ-TUaJbLAKTF}soM u/0Ո  j)ՂT^z<Q}VXBqH[х} J!*վ: B$cRx3ŪӆS̎{,5dQDOab=KM)!uau ڬF$3{|}8F |V&e)Ê#Cf/x3YDɆ0 Ɯ1*yh]*�4 Ɯgc$RJ g2#Ar@.ck O$;)}_{%|o2NJh]AY059^@^OXX6>DPm#bԄW7 1(Q Qy?X4 ަҧk%4Bz'˵JUՔU Omb+ t]UUW"hYSd;sAjUGC`0V"WdG&oGim\.||Wz#+;чW8n/v].Gw{z ͗OsgȇnhhhhhhIáI Wr*NHRMȷI3WɌDZ9T&I)*HQAG얐ٔ @uԦXƞ%XafSTv)D:TXݰ3'Éz2Ys^)E2_+MVUac?Sނ;ZK%gW.k}BK-,̓�<ikl}PLkD+]JNȊRJrcM;Syfc8VTEU nZ(j꾗H95# !ìA(vCz]JLe?+qӥN PSw-s7*Zɖ,"p`To"2I<.%ơp9:RrXjA<%t)YjZb5l5Tv8~zO`%֒Q:v;�ǃĦEKZ0=/qR1uݵϗ#3C $?!>ީI#AF$1Nļ X*[dvŸѸXWk|نf i,ƈHY*&;yv:ߖqzzjG*J2+#?_ѲT7gI["J|3Q!y#8$I=hsmQJ7 ʾ,xQ%7.Y~'٠gl�^ۯomTCCCCCO;:bdU-NSYQjVdBLǜA)gZVCEk]lVwVJ`V@$#' x1Q`2dG=MP桢.[oa_o-\\m~Jz^ls{άKGeoC}G+P:3%"&vy i!&Җ[NfS[XD0!e>u[m"՞7w98`^s(*r霪+uKm]SoYb'03GXTdLZXW##il$SrJHjvsŴQWT"%A%�Y3DdɂS,)y#л@]u8YWNR)fff{0'Ɛ[O1#\%UеK!1T> 8<> lE%^?,\F +cF^Q@�ڈBծ~M_KP)G8NVb9U�wm; ,-G;A@g ;(9U̮Ь>OkMdYB$[cި1h:ew]gypue5RruE_*omc?;g;=_)kS "BD-9};XRZQkXuj,tD "e6*j,nk p^QsOD1&#`56PZ98uL\^MY+Z$gb#~]ǐ!Pd$̉ .#9,8E�\Ai2/l:)ܾI1gt1~N1ČDM兀%tGu& &u.3g"շm  W.1AjN'o@JRF?X‚x F @�}XZ=Ov6kmƥϪg  %JR)$vce016]Q,CQn!c(5cb8+FDa/T@:n@w\H5fmَ+"( DG@zs%NT=EN*ھ,.jO'Umb䰣=|YDu]ӯh^h?KJ MމH1%*qńKY-ź 7~*5v|m֢A{Ǒ!pˠ̨i3'5xLjvW9ggYˀmn%Ȩ]?۷%FuNGZ''%e�Y�^T?͟4 ݿ?=|԰?d}?MO~.~7b j1/sH-0O-g7d] roIK�� �IDAT*SW#5DmJL1,`.v1 E(3l Ť]ot%u'_ ɩZTb'溻TF0' Js Y?nDT͈FT [-Žfy K �NOOq_\#NH* Nnn7+>dFb]ԟ "v~ 8fsдϱ?#g#K gس tf� LSMֵ6k77,9FH,~nD*vp^ć?C䫺|\Zlmc"C9JX+ PjjUUH(aZI,L ػ&ܝr>fy9S⁈c.joAbSP1+$(FؘL\!:f)uN ~J:c:㉘+cYu,DASgg0dcx ,0a#!;rL˽>kۭٓhlCCCCCCCC:"gWj(OX"~X[3G`=K�obەTcagٕS+0Ia@Kxg_?;܍k>HՏK쳀gg ;SIDPXgs/zG|' ~˿~/׭ʾhs??{7smCCCCCCÓBFBDh/U%TwE  A>%(19"&Θxfw<)!ɓzI{2pX`3mN6Qg�/FԪ=[*RU]us}RP~`J U[Dgdzf2 UJw,Bh?vvV̂N-2bBNn:D�i:#!%evȽѢdRuQVqC s j[žb h28T"Q\M8$ ev\؃ -NѣZWq rF~qw6Tx<={ ġP('1wt -dpbLl$%.qav[`Y f]L" [d )D24;KqE�N oJH΅[Ij&$v]}11|aKߑ{ILAF}1'y=,'Ay.'ci<ڊPR:N='jnZ3ҸN)a|~0zߑDUjc�WaO x%b6ڠw|x+/3DŽ/"nolCCCCCCCC*@ Fgkj95GϝW_Y9Va{Cِs r�/bvFM`Afk?MTVnfT) /L a$(W0NNN��`ISo}X0Y?+d_=?G/)7‡9s-jhhhhhI"ؤVj1)~yTl;h62 @Jp : Y(pX.RX2l5#z1#hJ�շ%UZ9{Ialj#엃c"ݜaSӪEm~="6/;JE8̡jTyRJ!tFvՋuRѾDȕX-DTyrՎUU#$g~Nq N+BDd3cUX M" �Pa-> VKXIuRwSgd8!;(JizL\Ĉhd4Q47`Rhw�ݬQb{QI=QiG_ǑTXUs�Q#:͊818@E #$@ڸ22^#{"g,7.Ec�Թ3R'""8n%)�l6]^Mˢ ?U)w78bJn&0?T%.o|5{gZ;b~~n`P6وK/9.,رoe`N1b)<rØwJrLv k 3A:72U=Ivɞ'Ovob5as.DjmY^֏ٳ lu9C0ۋ 0'hZ],;;~,,D <x�U0 eq |/~mGQ?;g[?hw?wͻlCCCCCO0zNFe!Fh6K0bn~dZdI@FPHQ2Ar$*xJ&LAiR�(` R3A\1d#c;ca+`'.YqU~R,2TQcRi(Ơ7>W!YPXx˽>UJ~1 ʂ>ptXWfab?sv gkz.jqJevG` & ⺾ # Aɲa,ڽ59N\3t uGfq.V1@]w9g#Tk5rmMl"B*(X1'\:sճV2Bx"C%8 PDf;:ϠYؕu=qQ{oO8Iz䃰N%Qꥂ"2\ a1jK, m3 08mS,v*VK,ڣr$Uhq!&+œu)}Jw5AdqLxX[+<,ھOb$v?4Xׄnj<W.U+yMنU})iDZ G!e}B<]Tr+WkEr٧XﵿTqv~ϲQhZ(]0.|7rjI>O+;}|ng¨?+yr] ?$ U⡪Z-Ji�+c @nGI&f(G"ԎW,9#@btHd;[ n2{Ivh(Ln߹2ɇP"'|KM"NDgVq.R)L\&J" |c㖝Σ T56}b޳)%tIY Je7D\ͬmN,ў–H¾gïH|GR$WVMFpYseib;]qTZ Gյ[H8Dd*2c K^/�Uy]UOH R@DCSPhJBæA}l٭ڕJJg꒷SXV-A ْEV&(,1WŐ:HϕyƓNBԑ'{M<=TL B^g8 4qB}z$,e\?${9,M))3j9θka|~+mea^)yy�7 ټ\P>V;cf4A}_еĩŧ@`z_*N-e!j*�?ƴ�f:Ahv::/}=iV8~Itݏ;/9 qR*$fD+\E04ۍ*sQI:,$ A5lhP2R jv jF}]ipGe:kST)$�P~qU;OjɺF6 Lrկ!*$ԭE\-U)i#0յsŵ8qAv˸(rK(ƒ@m@ [a`$ ء238MjZ*#1!q7ǒج!F~M6wȘ̛xehqrH &W{񫿆<g0hغ)TMeQƃWo]e3cmLؠ:^iXl{�"\$#D%WyZ_8RI䄿RuY}Ԯ"@Nv}oj\wzȣ+ `d8 hPSv 4LvG/kZD ;Uo-յmH ޾S�g&B"h)+�tDRSpc=[ZH쮉x7vaf<|GuV=I㻒a0J )G6>td=gwJ#dVA%J @urZϓDs[.4dQ_,d&P:a-[ݲ C Wd\"K~ O}M\p6(qů?kw, 7)&V圑]$촳ӦSR'/Zk c?a%͑[j͂ �9CGٔlH~Z%Pb'!sZbnsk61TdcaG!Q )2gWTd=Sh.wF]+qBD׬ZlYNp9M_ݫD Ô{ 605Aiɳ r}h\KݿwgLP֣y=D80`7,*#K%a7osH"aqkē%DĂ$PnJ&#F#o3;w8j='1a D!Y W:wR"KN^e( FrbP)nS#??:i@ٮLE̶Ju 6;r<黶JfS߲]OJɨ]QTcqezQJ {.M)d/s7n^4; riu=.#pY{g~m#;J<1npS%OAp s~/` A:Sm|f 'ufs`ikxܼhס㍘]ز##H$VHGu}bSͰ cE&[NQE8==8nbt91 Tw-}W-PC!ο|.z?%gT`FĖ5:11ͽ:u\1@NQ�s :NPBDB7R$2XcvBT謮dWTȄ^E eМ `&dNl䱐:Tt7u]?oy(vIPWi ,IG&]%jS9'dLF<JVㄾ@Lt@DQS3:Ԯ):l9ԁ1ʼn7S4fU01"u NR.lA;Ήwm{qrHEaG~K?ťf1 ݌Za)Κ5kUlJy {\%Qukcgx=]^/*CAG{yМ1:Q2]ĩ$(%jubcL"j1#1wd%KDM { &bEw\vbVqR*V'''%vMF?{$Sկ1'{D0l ^W">f dI&:1MjN�pvpUPJ|)GI#O|u]>+oQ.R$f]%zDq8ff 5gXj~ iG#dVIr)XqEep٬[V'Sv�U'Ec__D<f} ÀnWseֱ>- LJ`@jFd2[qyyeaf)ocԋK@e;}_tS~l;ǽG?yaCCCCC× xJ`\mC$`_�<O6,BNjޱEY@4%@ζqFl1=ʐADLZN.|)u$O2#u0yB$ЁJ9HV\VMzE:hD }aJ8"N0sJEYXmݢNI9:-o\_<U 0$Ce4UƇGۮePkF.W}R,\Jv d DNsP7“iQ"br\c d0i=2C-^ǃ]a?SP߁D]#HL%,Q0hfeFR1)TILH}dR@El?$3j[ lաM- OHY6 0b0\v;yDAf1+YrJG0A`>5H|�3EoM||.[L͜:F7ڻw u7c fbp52m4aNa\\Z@4s_4PY{>y?^<mhhhhhhhhX%:X0+1ޛ[VTgߪ  "b 0SLb$G2Wf 2,@pcUsG-}>>gUk׭Uz"hX AmRI1۽k^;Ifŋy#<C= XArH{H|ҧ;*l\|[sN} ! dž0lܗϛ1E]tE]lHx%"D*IHdGrұτS+Z|(GA ?HkRiNJ$ P_HdP(o[ I<riX4jH x`mxŶ91& -w5HN NcSٴ]JW2w35 |̊)$Fؙ:7/9F *SeP2RKI\<^r3XOTX)[k_@ʼnryv+PM]Jlu{*H: Ǵ9ۭd՘B:Qː$ާ䀛6.:|& B ]bhhO>47ӫQ<8-,hdc,=ZqBnFOCv9KEDȃ b0.SGaӸvbZdHm4^n6GOO>u%l  b-nd{X⫥n5$jWSG o4_m+y'*l*zѡt;jZoұ#9$ǃF!attc31~*ʷrű1L$!yN)Z+r%~:͛ <G�Lag͚qn4vޙn+&>.袋.bh8CC.],BV0T@7}%9JQ!_:̫ƾv؁ŋ~xb<ԮT\3# cdYƊ+z^zA0#d~彇 Qꢋ.袋CjVlhP2w.]eg 3,mt�4>`ňD@t #ė44#X kcX$O3XȌ{C�A"*dh\m IZ%uJ  yq|+9U9q.F�&B<fn(Jk9t|R2PԙFÑP< /cecDl*cTJ"c֢E@󕶂Yxx٦aeXX<2D8hƇiQQSQώvYG'T1PFJZnؾ裷bL|hy*dYOAVF!yC$uōe4Z^p:0X~ph B M;<{j׋j9\Hj\uLu ɱVP\lC9I9tF�� �IDATvS 4 i|+f Moj _M̉}Tyj)TxWenک}jkU5ҳ벁@ۥ ?G|qwJ&2$l _ sOzݛ%Kgo.袋.b:25>fjdgA !XAC2LN\�K*v*<;s8yβehrVx ffppV+WBD2 /^2毚;w./{᪫bݺupmog#~ov3v<|̟afOwQUx�4fƒ}LsfC ]].ؾalT\2=J>B%ImVPYVC5a6A,ֵ9hL-|7 u > V,b+iL$%Y 012aТbaHsD&^\ Oe:6KGU숼$sPR}MD+e:A$5dqS/1dAS(VU\>گJhDPpm5ju1!8>37l`Ū5^SνRvp<J%"U ,CJhB펨۟v&Qe~H|ECe\ڗ:FYRG\'LD  C4do遞^%c'^{sdɾ,Bfk˰謬wKȲa1slѐ>/914!ݳ,#18gGV+Inh`cb\ U 1G2K1<R(kO"MjY:v A@Z@iF i؍L^#:a:̫>ohotMb6nD{B_a[t  N axxgR.ea{{E8@^.9E]tE]L+B΁VqB =ʋlɸ$OQVh1 ޱ}n(\ T4\\ Zj5W{!3L٢*R,⒩xHkp~ f=QQĚ>iDZLb#'] .kϙq}]~i ܜZKٳRzol2_QghdV>HQer;[4Oo?;k{7|U=#VC4p!첈 N{\~si-6|Zξ~\Bk.^i"DR9%қ2,.Q4 } s>7DBW'#ⶍ4r_!AM|B$ 1xkcQc%]GzF##X �-num[U"U(*Ǝ KKI ÷2N.ws  j͐&Ωƚ3c%>=+yըj-A#d͋Q*N ?cL+}*9 `X>o2;//>'x3X<jFԔfU:A T3"�Tmgkjno \^S^PG7%q}$M$ W:߮l V9F94x`=V{!G*m%<ZS_OOy!@oA5idLFPre D:UPsʄpԩ]Y=!:+u1"<9{$89iP:Ф}Uig5xzsgL9U:ȬF-X~=k׬%uYٗMM>?B'5;9^FCx ci|Hծ->sty#uFsv^c]w۹%TGf-~3+|a |:d;;/K]tE]tEfjejaY?f+W2^ �#/O4Ŋ<#؊c"c ^ɫٚ2lf3hLv!�,H).tZhN3߿[~~3!fCWȓJub]u6ְ?~fMig\Wtw2~wV6e\ ܼ+pQXr;9naW9/SpOj9l|9g>~kn go o$n|y/wǾ;gSNo׳}Skg%{opɍط=Zų>>)cVĢ Mݑ bVbJ,R[Vf.Qj:F*)[ UR:tWr�1<D!);!9)cjFR%vJ₽!%!^P*D\'IG1Zק+<>*cݴ4t' 4"#J 1?l0.t]mylt"J=&E,ڰa?8k֬ÊaςXA{$"YʾZUq[P^l,`z!O&-EX*SuY$xSN *QҰagpoTkS$fgNJKd*rm[K_"oUjLv;ơ<I6+B,hf]D^?awa5l;\Oۓ{\rC"l�B(Ag,yWVhUb9C OEj! >GU:w;wmlTEGT:X0EB]].@_%k[5Xd"i]ʩSjaxxMљHJKE1C-|`3"`ǥ}~:.:||rk~O}li!ct<O?bE]tE]lgp.鵘 ;g&K=c' ˰Fq&k D6[{p=Ψ0=F4RH ꙫk+OmA ! 6qF<yiިM9*ٶ<z[V޴<LoS<$ F\oT{rDe]]~RoWpMveǾ[/}L^x>'lpkgz 39yx"[7\Wd 2˾Yo;o3'].}/� ̜t=!�##T" [gʶv{U%RɹUB$*!_@Cl4.*Ry!7-xe TEVUy͙$@Xph {ZC-4i6s\A<շڎiq[B:hSH$¯--S]Q/"8cye6PLÍHD6�@ Zg4tft qJPZӳki垧V;G%#笣a,lT8Z d*-Iύ鳋vMj<V"㊮Pu21⦰ %{DsSJLnlzD豖dȊBX J&r9.whWF׮[/ڱվk LUfq.ysfs<z2 U_WP 'h?hTE-}Y 0f|P%n+'nnro1!(*BCoZڽDe}=='e|!.(XڂS@(dc:YD"Hg!C0آ3IF ͵jo@FDFZ?h'];a5tNN Ƙ{h40z[\BC#=hd~R{U](;Uv69WB9bp ޠ7[ݮ |~훬2pǝwSY%fWuE]tE $ g vdE;;3w%ÐU>akZldTqA p**.{I"i'scFV z{{i[ړR;UccA|~]ۚblwm/=N:1,+[{|A۶R k.+-3-j6_?e &9ley| Kױts}o3G_ۼ䜏rHA(c6>>uQmɇ6Eb;@J&@ 'UIERҾ9agFbXUE/y)*%FR.JD221,qLTX2cu^4 'o\Ŋj洆Zs/1Q]E ¡ҨV`t֝Uک#a$2+iSnNcͿBu""Y!-򼅳Fs'Ѩz3Š!rh(3-UJX%N'FB'[DmCnRTk#2ZM#(QD_$_}9 s 5v}Nmϡ0&!:4AL ī8 jm{9s|V4C`�`dyakcdlUP-όtS'm!V! 'chk֭a'X)6Ȝ%d Gމ|2MXt)h4�_%-EؼVIkEkhXS4|po6<W%m!c\;ķr C2F#'ˊ'r*Y,W@}Sv+Q4P(}**F2=Rl[g%8C1d96&NTDz0zAj0eX={&׬cIIT3c8j[>g㔾{k]tJт}vXlpO,=a`1gKE]tE]<|sp2dhǗ>ݳgMV,_ 9$@f Vn!#{'äB^Uӆ=VG !-Eg-f<NEnI ALZن 9ݷ푍}_?{ϙ-h_6n2Gz|p͋}+?vTD/<%ն -_Y'G/Rў\y{r7xɇ eI)c9?<}6&s.swE|py#]}CHxaGN8].'X$5Q4cTZ :#B\nXG<"_YTNXB"#y)i= F1%BTATId[CzHEPr DkA:$6k d#">FHsok)~NT%jpJ>a9y!La`a"\,sZ ^yϘ'7q?GDSV\٩`"/V%H@f#ϥodZdQ6* )!ה[ k ^CLz7W%6E7Ĝ%愘xco;9SU@L`XZIs8eHa64twv%ƨ@@.~6}Nh51Ӡʣ!ULa*mZ&̘1Pz^7J [y I-T$}h7b,xpYL8^jo|ahGPUZbaaS TWOԟ_% =L_`-xBO|f|34~s^smbӿ)MT;00> kJIc"UnŞ2u7#~MiJy^ŶdN0tki!޳.d^.R]tE]tlE\4ii8<yrœYj<6x %3VCz T̚B ش6ۏvx"cS\x+-e kضYλugnix�w 7 .z.L ^z;᢯U1<ﻙ/Un ޹_]v*N4WОsrٟg =ocvpM~.Olyc]s?S!Fc#y[pfӛY~=0{o9o[sD\p!dل!ٝd 累A~ïOl`=0o%Ok[yą|Bd3OgX)+7<E[1{9|k1b5u!|89K\/|cs?7oN; 0|gkX8Ug^ǃN?#?| y`9w[n~p+8sȒ&M4Έ64nǼ7ֶ paZnk}ϫHUwq -a]@$z$BĨTVUrYO/9|j7TT \4-H$TI:̭D$ yQBKTk,ALf˒R :#iy'm"ʌe5Tg>$V8Dud|TUL6X$4%dUh $_|XU.s_(G*!䩋del�%4]Y1D)FE) r l+ ڢj2"ϯoG"uΩ.hQo!6@oUf<W({1EIYl`lX_f ? k֬9Lxd E3u3ᷭO;MGg}p侅9c\2U[!4i4h4N\ I} o>@^d$$(iKTj $44C{B"&QY.6q U9 2'+[` *[:p0aV&{⋛JΛHjےQmZ-zךcE9v7ָ6VHSwhh?տ9Ugr\ң_)~&GP6C1?S~oe'Y3g38<P)^kneanY ?ob߶|:V鿘#㢧Z]tE]tŖC1T1 zzkO("4[%jX"jsUPm{4X)u/QEOz6 ` ڡude6~tW57}U.5{ϘR; ?wa'o/?8 ,r}k8faI>K]m'^~w+,M~Gcs1</|y{mEnnǼ*9߻OeN5 :KOuK>|0}҇Ϯ:#d3s):ˮq'.苜ugjak (_Z-![·?g|}G|ǵ? Or{sDSXsz/ G.x†a.vkqe Tߍ`?>ѱ|?w;7%cc|cDW|(%dkm <O9lQHsyӺ8{~2l$zJ*Ȑlt1cLYFsz]Gs8Zg%MSF6]WIɻr[UM DB\CVխ"8-1X#Jxr0)Ī)Í 1l3dHMu50@9ݮ/Ot+IjۇS�� �IDATv"ݙs4gw؁kVy`EbpMzysvU'*Dl$#%+yQ+JvUkFlz !qGVWR9KGМe 5m{c(KTArk)NqT9J]P!RSL싐YիXz` H,O l*uK,BMW R`dĢ"q$[Tq>rm'L$dh5?ڟvQj SD2 $XpOL>@�M_ˆ,`DDtrb#8rt̓`:ǁ֎zBca!IAǰu  xg=szڸlb EUL9F;Δ>ڟ&C;ڏOa2Ѓh)R)q:~1޲Bj䋒[*#cH \MiZ j=g0M^gղ[E|ZV}YAX ׬tE]tE2ܓ9Phlnһ́MfbY0!yR.BDiLm[#eZb ( W;޶ ]'G !Ա8)i79n+_.덴UR|1~ʦ>G/9Q}zk]Ͽ_9*^S:[n'\__p-<RֽVޱhdlOyιƾy,:`26� :'@Vq~9c}7KXw8Iq 3A̟9ŏK:%+w[jBu:Uǡ s.Xdl3_r Fjx;ڶ#dl~+xl !(&W\T}&\A|ELH+o[Jg yҔ2TgT=>&k+lԒԏe~JUkڨ$CIXvBZ󪺱:0#~RFIg e8aLm$CPt.2;AI2tnU.wFw7~(A]|F_Bp~cDLg5ra HI\$2:(_a>r-$.[<w"'sSἍA*Mb[<roW#)hX Mq.ډp4BnDڔ67Y]Z�E9BZoޖ@rmSFs|}"Z}td8D`VoDVmt{ i<lU-V𴂎MY l]オ#,KAÍKZ-i 7#ACIEo9{%b\=!c9Cnu A=6!o/F_2H`%(Qkz~J~=!8+L/g1pgfR<5yWL1%5rtɢ]9QlrITGRn@~aUp{<|4Dbt1VQg 9oS+Wv:&{,c,c/;=q'=g=!;Y]c_?o.袋.袋 "0~x0܄&&~!f nCQ[6=mGܾL ;k9[iMa<Lwu c+׀bnkYrnc`G ]8qteۗO=mos/*v{>ǜQoaen.ëڷ+U|ZvWM]b'97׶*Xo{kymOUh!ޟ~,x98sC[VK&~y`bʵW{4s*֮\YbmVYZ~Ğ~]!GV}'Ofƫnb(]ze|;:'<}$k׮7vǿ]$.ex¹ l *i~[]R8|Z;Hb`JgG^NjNHC,[˗|r-_'c ' {Z]ڕǏ}rdx%RTEoƩ8%y`e"[1)o[ uaK $D #+ʽg}Ud^5Wh}k5$l !T_qz*TH$=BV RۤPΩ #ig$ b;aa`hp 1gOvO$6hY`,qGvCH3mJQSqĬF) yL!c uL]+u2FՖΌ<#̩BmA1K4$}7JVȽ."! ΑgM$,V-@WyuX4/j~W_u?49ڡ9F2]DU5 yL ,IK1:9y`u,#ӛK#cxc9yEkIjElN=I9gʈVsnM| ^?7[Ĝ:r6d9HH$ GT%kSXC=%:T2ޗHl)LluHL hg0m`xpChy^Ɣv2vxS'RuѰ?#j;Uk/09]`3yZ !$p$B!04OgM,~Ĵ 5pEeYQ Egh=yѮ lg5!ͬW ӻ@dJ?tE]tE<@+-C`>PPb8Sʥ%�br}5'NFu'iW;yrSjyVX/L WzJnnAEG|~u/}潒/|_Ըk9M'76N<JDwy}Wpܹ+O?AמTl7p*~X2%HT0n/}b pFdv\q?MKcgϒE2 KOUww6…Y{5h|-^ Yh5>u…1c>pտW9bSopW*rΛ`癆3>[OopW,k_:N?Lkcן%1|<|KUhq]dF-U}ʾ g1c8pp+*BsfȲcx?^νqCQXڋEh"Z/B-_\�.5+=ug½8ryy}Sv3+A$MRIf2r5=M UeبX3Qq .vHIKQ5 21QiI"c}t(8gͬ%SI mFea<.fQV%,:yK( uaܺ2W1oy\{uC6!FsYV*u푠OX<_}F尗K|1f̨/+ONE?*:7XRQta_c^Kūj$Uq"R/۱ ǰ$gJ*B&ƂXwVm"d E+oᚍU[K#(ٙ[KY2ÐCSMAݖ4m-0E6}(l1{5mKwi-IHT H&U)DhSۤHwFOѠ襷FkXrl 񹏄JyN©  NH1<mdB}s+)[Фf-JVG<U"-{xA2RHy&FR"KK~z%CC9#@yk ?1=nOrqq6`m19B /M bTeCsB[=QF~kPSE'u:4ϊVr ZŸ&1Ɵ\\CAl'_Ɔ+tE]tEa~<S7y'W-:ܗcb8J-Pg4S7Ɲ0ڍ6NV-Q7ҧъl-GIdžxrs;D|g˦ϩGWNϓ[6t>i aы_]u-7PĿ[%gnBz#xdR^Wd}qeMc^ 3gk)<4<?)_?sě8W@|#JXbei_汫/ _:^c}d8vh' Nz /lcݞ3Wn3ぼ:3+ϩI4 ):br _o{v<܌Wٵ-?)^֒G4_Q%b"T6!!yD  xR).}xTK(chjx7L %qKhdk 6ٴaC%Le6�m`A�āF'8+lU%)quB` <lԯ!(%٩EUPRLSHܤ6F FO5dHՕy 6CQԮT}bMPUT)|g#D*U3 ^)lʻbPH&։G8q1Ll$$ʺZ*] k5C7Y)K{Un'GCG ˜κ>.I7UѠ$$bSe)pK$C^>940VKCwe+sce-n"C"E!xT&x"b}Ÿm)(GJbAB$p B0e umh6#LtLnI(jIi )<XTߥJ3괽='BCMNn=.'@[H cXP[ypG=cgxxҟbBQ& .~1mӫ!ϙ{<M{y?={6sV.і�r <�˖-+/^wߝW.袋gfɃj^s,[M6{3{VYt)˗//`3k׮eҥZvŋ=z)V^=9sN;Z̙ŋ'tkO'<&~bzus$4 Ӛ2dLZ̙ F=X봙:ȄfRyr.v @u=Fl Y.B CJWftj[g.}h3{ƴ{|Gb߽yc3<wRW<O}}ܶuxx9wu{�I+[#-9H-8�9/~sC%^xȄ|[QX4geN>?εnQ: /g_|RxTCKpxw"\:;7U߿ ?{$ԩ/e5S$L;_er :l_>ph2u<N?}??9z{8OfKCw7$nն#p-ܢ% lN]l'0Y"@Cy!jmXtZhFMS2wau(kzT>%\s:GbI2%RLZ+X %ZF&^W,=X))٠dY,AԱHm7 »S5t$�"u_R* }:FN[C.QLFU )cKV3]-HlܼY_"i >& F:!lcxD's jK:xi%1(ICŠ@6j[/]ZDB?J".#sa Gu#bn`5R1(F$)w"=yF&53 Kc347l\Ahb\μ1 7?ޗ- T"&JZk#JHSg %%p K64CR G--JF7 692gGTER6jN]SڎͳXU8_veһWuʩbLu3 R9եێHx(s`o,";HcLVoZAX 㟣$C WS{4% ),#t<.$ #g($cpŕ5!˘{k(|`BǾo d_}FtA_;*U]tŖڵk~u4�۹ۊ ,4!mVxy+_9*y-pwwώ;Ѱzjnq[x1z뷟; i"c*0q!)՞PY@Lp@jRW;La͝;0͡a RAJqkR�e؛cc"9Q؎E-!##\P*NMQl!;N$х &k&C=nWā ;;_ܵə?[qt->{zMY|䓼 D; G>٧qv爏~7|'O>>GG̫88 �s5kcys&^-w3x_O7 b%'ɮϤޝלa^so|;Vv3yqpdp,lv8 BV_FuC~֭bqOvd<<]6[K9/??]q=v ڻa|5Zɝ]l0yj8JdQ kEsVH>#zKTH$7JQҽJ!Jni.+J9cpjϢ@R#X/!TC$DB@pϦ#+1+S4e.* 5B5rIIr�kɳj齲\mDbN:!>Dt:E˖9 BU)=,$81⩧W1L>s 6ܔ)RK![ 5}5"]%jN1\85``0ġJ[&%GJqy=X}\(>2vuv;˗x+ȸjEk|$R%b;ι@$ Ȍǵkf݊*>hRY%ul2Fp,="dA7H�<l"e 0A066%Zc$X$SMg\Zk qY-BO}P=N/!ָꌆ],+Ώz$Y"o=bB$z~m\׶zHrZ(UyI|EP~ϯߊ:On'w/̞x;C4[G.it L@3x`F><vXIl#VL*[I.7^ G0<u<?Eա]�eMwQ{9S]tE[=r CCC:lr"L\rI /f{!9餓FsOLoڴz8G?o}[7osJjݗ-[O?=.;44IJeU6Q -DڸRjOѽ%v7%KOJlE*dt$1 zYp)(o\2֎ߨl[.\[> ci8bv" UgdUK/Xot⪥Csߥ3xY88 &zu @ƁG \ܕvq/]XΛ/n�� �IDATHNFrܦ>G99¨ZxΫǼS_KYfq'pgtal29T.?ko?/z nM\Eahuv@ˀG'umȦE_w[ wAn]{G s#w`]o/r袋qcKժ3p0LZ�QM W. lflB54\$?(mJkLZ]Vh(H^Ju'WRm]1Y[8v:YQdUztLUUhuRlk7GPQ ܓe<D]:6)5kJê}jT}Y|c rӔ5Eeybd]ҁ4¨'ڂ16}a{롆vp8-HYߩ`>3gD[Kg i…hV[ڍ4\(dFN0*rS~554M[)yrcM;>cqf1yu2h5i i>XѩD42yrm|~ &."Pն,N)1@cEժidѦ %*!Vg/&72�6.B<UP' W^8/Cbl נ-ZӺTcyL1^/&Y)zg"۾VߪU|ݨ=Y7> �6$%)o!811?pI7eoFR쇉cv(t qEƣzg !;t}.6u_7^8E};jx%.::]wݕ~˗~˗OXE]t-]:ڕ/}KYx1~xoO~~i9찺^|?k*҃>.344ă>?rE/qn⮻⨣:>踄hjᭁ2ܘS֊^,ryBZ7!,SjSAek 5*ABT846QEGؔ a1Joj8zP3 ʥrg#|tr'C⛯$)r׎y3H|w^{ɇBzex{gVvr$F}~)�/j̪or 'doȥ/Ͽmʖ'JN.ci=o\%7dsl;l9\}dO?V=Lg/3?g-G7͊/t5grT+~񯫄A~?pǎzeZki_._]lG*sнخH$)E˅~sD<sZS( MTIBuym,K%$ѲehaoS( 6! ,%q'>ĒHIT>L]SUM%10RcuF$mCCeB')LH4ƐBԜ,Յ)\nA@-P\O~#زJjFMQ׺ i0"J{[ Z)8BTKp S1}�F:(RozVUj[ ʕMmV$w;0)4nZ~ۢUh*"Dt {!Vf n2{,˰bp-OD5( wt9YyZ4Sc E3yP9#)BPB"ZV+{,TqM_ZPSPC%B56YZ#K0x>vLg4T xk8sU;WE?K<X8a8 "ؼ۞|;kW^ň L&dH /J½qJ@JԫTckRo-KjO&}Q-`7E D $g2/~Z{sμ%j9zkZ i}ߕ@-υrƛvb257qǰu\zXxN"cǎzܹ#{/X�k֬AgDzxظq#N=(İq000ǣ3v:u*zzzټy3<81#!.C'<cW<%2vŊx7yp'IVU޽�p'>[d ̙>�aٲeg� y5Xmo{ۘu:T,ZӦMA;6mݴi?Q~Mf10G!lAb``�~I6ٟ"98Zsy<`@NtIĶmཇY"DuyOV<ѵ>sWd= fΚ {wZsEQ}9_ xa=ӏw�"ϖ.OԖt2\ lx.wl?WXcI?♿,Yzw͗(3~K?GYޗJL:Fۿ ;2n;9kttOwV-n9NpѨXi7+ނOG>X-APAGCo+\|>IYo4.z)r޼`hC9tLΎo!$ $6dC%+k(YRR!_UJ*?BQ!dDkJjYĂيrM1 ȳ(DAMrp(z#m8VBg#)KȈM mIK7AFKK�ֈ5!1/ע|I"1_,Z^ n=i/䁘ZY=4a   YFʌ9UY2J(>AӖ~']?V$ @NіeȝӭW_Z;J\mnXٲ6S15&Je"s.g$B xsp!T@C =]�mւ١V!F9HTp%["YFRv*懐9 Bsb lk P YISb _cC 'c܎ӚrNT) @BƊYJ`(f{=MC;U^2plNg 18pC*1b M/M2 [WcPF:;4&'³&Ū,+~itǶj#ӀNae?K.5Ij jq(, aח5\r%8㿁ꋵkז??۸q#\r% qUW^x!<䓇U˗Hj.^￿TޔDMs7ڵkf~-]7|3yf|Xti1wqGӶMycƍkJۋK.%\1if߽ꪫp%`͚5M˸qR `͚5 lƇ磽Xrr.]4eC<PR4#cRC(/N<D۶mkz̙37o|wt#D6[6CC6Pl@8PՐU2dS$6:XlfSn䔉$J5*~zى: rJ-hW�jT;ާ9ty%=xf1Oo{l\ŐWlD=y?yJ�89ⷚ \z1;v# gpn';3kO/i{&|Jxw_?|q?ۥZ=^>ލ_ǟVj5y{1jyzҏ!_kuŵ[v 3kʄx0pUى (}v۷܁O-7{1w=ul@');0}f!.Ǔ_NtvVp'RrJ~\)Kh܃|0 x.}aZ#3�gEG1c*Yg@$`ya$`Y:g4%JVA[JV#aX3qr/܉S\#_51Ѯ8ghU1ȬqD%fgqKϾxt .ew4IñIprkד S*SDl 2 XU ΋u/כ�15 %] /S*ZQo=?hP5V3RFHf )ra2jDY(uhS=e-ˬwQ%iBt7cs?S+EKkx)shO˪mc0:;T9sq98m*:j5c+~a0 >{.��`x ӺFKpwCؿ�$'0$ &zc3(Iu@ $A-rI2V{Į>=?%r_5%6cИוC} ysgyNLYaWI9FRAD[A;xkD1Áy ~KZ3 &Gn~O QJ_s?{ l8և|_?xãm> Zc]CƵ^UVqW7oox;W.~z|]w݅C*ދn===_?g{/^~ea֭MYjۇ|3#gڵXy[SoxLhk6m믿~zx/|aD{Ş={35v -Јٳg}㵏_|9N=QIzD00sLݻ۷oǶmņ)S>)1cƘw9[� /h0dɒ I {9d*Qc=D3Ms.wpp vMp`4F—>Or6=T&0JÌZJe\@8x݀axY y(-8&bX6H7s΃L�c߾=ynBⓍ:A-LOm3b��_I /W,_Gp,3tE/o~Dzysu9#? N~g/?s+<ǿP؞kj})y+p+Ͼw|xkvpҙ]8a[1⇖{:}ك蹇k.�GswƮ]i$?w&!S&ӟ%#߮LJ>f{+}n,Ҹ ïo#7 o?1<L_>Z\ a_Z2/o_qոոƬ}yٿAn6 ZR%SL 3�KJyhU$JPKԗ]), ϡDu4K,GEB28aub9 TvNpXU? )HX)�d*Ls.BCJSHkaITĢU;YABؙtR73!xmk!I5R@&k&m .F-Ѿ()ViP*)HźJixπbPAߤĵ2nYծi' U|W~FM&cqB婅@@շZ&4i Bv ^3!o%.p8MXDC(=蚰Y3pYؾw'14 GDف2!Ѭ^RʱLEX0 pϞ9dFmxYf5C"xp^3hV%�f\&K9J:ԅ"H$Ƅ5=58pyސd$.BrdyHB6h$RWnfeÔ)Aj'](ø ϡ_&AKe0|VH2VPnf L^"Q\|8< {X=%1> K/D~l dSb.eۋ `X~}<뮻tݏ|#[ԝ,_,i=`ʕxKs=e`} gzٲe8yq7[fM kŴipwr]WP_Z{"dl=&JkfsVJt~[˱h"TU[[lx2_"Gj9sDx޽Mj^q?aC @ RUJц mmm5{ "WŢ(!mCZ7E}l,yIgfpaY0ppa(䠁nnp56yxeS DoxzE;}D0ޞ? *z/YMM,+'|Gs/EI~zjwX{'nUVa_vw}*y|Gpһ w6/Ps OȨRY.Ki=:0҄\I w{qm/Qn:Y3ŋG8yySGehq7؀)ɯrHC�kw5;1�t`)!e|5ǜs<gkgQep.G? L* + ʮGZC?#֑{Jstӣx1f||띓@`$J@2;Av\d l-Fq ) - 1R:=dk$y5v L;^Y3:"!ݿ/Υ9BGB jR%TvKd)0fA\3Wqw4T^N.c قմgT:4%1/P:'B r0ʠ8*Y)yx_b̤7ڔ#ukzc$щ!&*CrƔ).St;hw�ԖU ~X@挚s7aN1u/,wΑl9{dL3[ 0�!wNV\1r2Pk yc uC} �`F.%PϧcW1,*뽗@ uc%kE$Jj,GѶm۱g^ I[-� ˲܋ {k!)Hw,Vd@Dp^!@�99*#׉Jl1^:d4BMBʖ'_<\Ş�~)ꪒn3 ŋcٲe;k׮mPR=x9k-p׿ux㍸꫱f͚ҹn:edغu+z\s5GW>RXlx u]xGqu5ۋ{v~R`Æ ߩߎG}wu׈ֻ _q]wo/}V>zu oF|n(UT-X�~;{9<sƬW -ptb ,\ .Ĺ;gyf1K,6CCCxG} _Wi&�B^|ŇYFTRƲW>si-[DH FXLZ\#2#XM~O QwT /FM=.PFZH߸TaaLDȲ YY!Y  J'Sj1!�� �IDATU_Ef́ j<@9<-At?Z|_c@dcz3o(\{Uuov533ߊ_~g F8\c޷NJK|peD26ڎgO҈\_IN҅/o |W#EG+d;6%-US1K_|.^øで1qnC_kOucm6#S}Uֽ߆y)iI|Góq6^3w<WVnOƹJ o (Oeml�nǵ<υõ򐏕L"  em8gUkXɨ–2Lf#AŞMy<ةmFN/J5v޹40ʠ\^JO! ,d,SI!Dϰ%I-yUY#!>B@foJŢc)J FHXw%܉Nс{.v ޮ@J{flfU T ɢL1^>@T tQZAXFm ָ#%pnzkq_5X cԊqՏfm0ܢFfP`(cFgS<8認p78ͯ?y Ͽ;0 ‹E4j]=3L&yE) so$cYRL2tkts{E2U'5PseXU;<uhM &(^v ǴrL@;2\F[9szZ@jl !351/uEz6ahpvT*$$Z#Ta)}1ekΚŜ N`ajp "p4Gy` [I獴q2&�b|0�8&co}Ǻ=W]yrHG>RRx[WZ:3Mm;;;}8/]w`~z_ ,M7݄.lDħ?R쪫*)xs\,K[j˞\d }M7TW_}5VZUjzczzzNx㍥vްaèJիWΑB -1hѢ Sj)Qͻ|r Gv"̙3{HJϗ֑7s!yWq+k x/ 3gDWWߏ6:(-��$HpDo*3&$:Eb0,sqўe:;;1<<jtbU))QfYsbؾ};S&j5QeYv>Fb6n"1X1dcf`̘�/0B](?=ӏdNïajY3̼S`(GL|1xqO|<ރ'> fϡ#>;vQ VYѬ~ң}; Yl?bxѹO^=d::c֬g�`%(nGq_bϞ=80X Ӻ=k Cqpa< :N'ֽKOׁfݟ;n"^]ô`OnVɌ;G:}8xo`} :an&__zVp9KѭM]rG/mx˒kXP @e|tw[]-#D>.dBOaCAME�V:B` A%t$ n $#) {jQ"U&ڄHCJF!BX8X [c!|K+'(Z}8.Ee>Ql&V 6K~z>HsD2 6a] kPH`ha)kF("3+6]mBsQP@2h9`Ú{SurxTDL.mkY(-+m; nVNY7vR%bTEr.%J�'e�RdZR"W0`` <0<P=O;0v;�2-$秪[ +r;84wk,f^/QTY&A̠@j}d\tZRJuڬ <Ri$6a=%\(QkGFg�;5('MM\l/ xb3n x+Jg"0ڬB9<Y;!T2iK+i[634O20 ƃݱVY&SؚH%jo=G<ހQSj޷c]~S_{E]tQի#)Wڜ|Î;/&k׮ŝwِ6U5d .(^xqCY=8 ]+hx/;˗7ԸQV}nE›O?t7oވj~:ػw/|A<fϞ} *fhooǥ^:s[[<X-[D?+/\]]]Gg͚9s0<4~_$cӜ `T dhkUx`Ȉ5F7[�=,Ӄ;v`˖-i"%: 3gN9TU޽\+JETuVj%o&M7M͡b*,ŠCy0r0D< p4T?hݓ|և<\90{>Y,?%`gzl@Ǭn2C_霚YfuwFM>2n;]y:I'hG#^k:{i8}i!C)ǨZ8a(^},B@B]Q|Tʣ{ 1A[㽋zM6@-T~ UpAVO8@ ȴYPQ&#BkԦ ℵ q;7>,gX` *B1jP$Go9 1EPԥyS+f:zX2mmJjmk" ɭukh@6Բ%7&wkMB }UbzH~ؑ\<O_vD* `)K!Rq hڷa’QJR8@MtmWoj<"p$el#"tMi3yb}=~2ןpBku93Cw#@3g mN曜˜I^hb7.7MeӺjfeeu8$+Ź)V/�1uQLrRRQ@± ӟpM]Lԯ@ JUi+R,#Dyd cxn&R_qavǡ;//A8D`vq.]�\.X5p,A<<7K(н !a<:f"|g2q\|6%Økࣄx4lRci Tk׮wѠ̜,n 7܀|6\,n#r}@,z¬YGg:Q?_z]~xڢZ8G򳽽_|qIIbݺuloo9d_z%|ߏ9[ W^ya)1o<,X�gyǩ /DB6%O;I)x:cm?؏5:] ߉@y" %#UtB oBD|נ䏵�yW|9b -j_ǁڵPU* bX],C {RE"콇M�:qk<?Ŧa F" e5E^W?+V.n λi/<B -FT=(hȆ8CB;K|D(i!Lt?<@�s$0GLy3J=hiˌdd,Ό I*5 E6.'$Y#y. 9V DJ4VTy%KPAV0r? K =]ڊb԰/D-&dXg CL1F#j� 9P!elX<p [PMUe U8UrF^2f3Cij,kIw TlQ5ɐ6TI{ [ORU^otȡQ|, "Z50vr/Iz>G/kohU7b{i?*"C7Xc&8Lb8ElEԪ} 2%D@ {m՜,5T0"F[VBQ m#*#]TRy+PtdT+jGx3Y8x_9pm0SA`M'AO{D 2lVV[c 3 Vj/N88/jfOuvہ0!hAt) / 6<"[ c +6)썄:?E"=xB{�5ڣW5OK(n_ĝE [ܳ!pDx$0@ ̓'K=JSDpkc`ع-[76<Lo\]*>cիK]vek֬)'=7ވ{J{q 7g+Oj˗O*j[h&dh$K/T"cH.Z .O?{CCCxǚڏ]tѤu]p!fΜ{bؽ{7fϞVE;9K!cpxλt,Q T6|)..%T`Ρdf!v0c bUy{^ڽk3<E2|,JˣX.&<ئrFd=E;[0~%x&]]P6<x0yjK!{ذSMS/ZhF � 5�p.Z"&,LJL"�<Ea y0J8(tl`ɳ5-I`S B<{0B8U0@ YKR10r0yVgxw $/@씘$00ԳhY0bx<x"u $gPznRh=Bδ]`Ii@᪊5#2A'e -Yx`Që Jj/�ܢ.V򑸬 kHT'mm)TO"!ҟTߘ 4UR6 rVBj;fTkNƼJ x85))9zېm5PCbU֖e`o*ף j0Ԫ Zg@ p<c(ާ0%gR`pv(ޓ�`9P.s@FUsA 9UcX 8Fa. f@EMj`MQi0<r=#֞sK@TSp1PsRYOD u0_hP.2BSIxKm VcJֆYf`]j-A=r97p8!nBK(�> ^99%l2@Y#_rKh58^o}CdNj .� ,ֵw}7.n*5+W>̛7===O kg>ݻd;�cǎI-o}LwܹG4-±DX�عsg} *ն6wyx饗044۷Z[zpg੧�l޼h-ZtTƭ55?Ibw Qb-FV2BIu8Ydk5cea =:Рl%\Y$"P8vP?6�VxאDk^ܸPtm MYAmg} ;h& -w& ա2R7æ:$OwuS;(u+!rAFU,>Zx[< 9Yd*{_ d,d9-jjK Q6>Sҙ3!0HT\JV8~ <#!_\6-#AJT=%Uʄ$X1m ;dS" &x}y D) !.sR5hQA,M\>`/l=9MaKe2:*t $^$(!#+ySu'6)Ie0/P.OAޤHo~W@ٷW,Yd\$\l#Sg0$g͍ځf%iCTߚ<$8`_-CYM朴LI�4Qp 2&c W |||^>KJ,eꚆGW T5lێ۶"`e!^1 R3 yJ"!W5ΜZPJB9m뀙Ӧaif&ع΅4NK2G*Y.n_r:O E)GiQA!M`OI ㊐w0q <ڍɱg<> ֭[;,}^\rem'ܪYN45.+Zh͏@y^62 ͋Yw=f#3<3?<Nyр:먖%l4[:eeC&+_�y,lzPX6媎y<nRy`7yH6�YK馌c�`YYs^Ctv6\Hj� *m݄QҶ*ZAG m7jr fCx{[h`[eSeH@k)nj"<5lsxxH`p`m,(7|TX Ȭ9% ! ϕ#9*� % H˩9_+Q?5t8=DzxQ#1HE 6VE( @.X)G ˀ"'D)S1&KIN:U}\lU|s:5J(F;asZˆDKFTۂ@Z ԆŔQx p QGrF5%!* 9(C놖% '; bZKS"o�mC0&C"@f!Dr|1`1$* IF,q 1/-p17xTMIft&}q Ka!(5WwA#ENo)0{,0{6aޝ?8 g�4b@> HNPpF e.TblCG94"nB$HeKXk 7F +CS 9).5>IL15 9m7:+Bi&aUWLy{Jה[l.QE^ >aݺu袋駟.``͊z;KjZh͇jݻwhC#c`ʔ)H۷/>G]]]Xp!lقh<o޼Iq;^`KF4!cć#mÆaxhȲm| VIGw ŃnJx/5dMT8p@)͘g6 ǂsLa5s`?vޭdfbI#0E(i#FVQG [*#ն-Է[`0 8% lz�und[ZiGLQm)'AFJ:!QS9r$_5TlT -D`ڎo%H!ÞUKZ|J*h=j@*R jM)ϼly6򌚩m''&|GdL܌EArz�� �IDATfU- U(gbYI4c(qBDɹ4¶Bm+K5@O)LV7[>/9sȺCx؈V-.6`"U*a@�9^`Pӱ-ˊXhLS.ʨE!{, BeT:hhph;fkQB6LdHzHM\_2;e3YٽZz/6>g-SӒxy <(ccˉ8%_暺W";OơA?iLB mD!10FE LAՍd5ۓdhD po032#.`2F2T2+iCT'KB(HG.٠aLxHMup@>}ho0HHx$m f߿/%CػwI{5 [C;d�&0䙍([}fcPMQuF`ͪ,y`(%#u9!Pa2p>u1t8oOaRc~#@[zXvmGƼުUpg`x'/9Z O;vVǽ+p' ۶m÷a`zu%U��^z)mۆ;df\P㓟$>яFzxgo~fjP:B z!l߾@c.C%c`Μ9N8ỏ?8 �9sN;-vN?cRænTKf ׆@RSJR `f.SMhaM ^^7^Ni a{ܵ ޹윙esC[E"GRXH~d·^Ih]ɯ"tYEF[1 Ҝ1lZh O&YW7Nyc<&Ij~Db +,�0;%>dYCyS"tϪ!c =r~Cq#z-LiABzyNWč* Q qeƂ] �66U-iVa()Y<ˇQ2&X!HՅVb!Q0)bc#5�m,U!"ZHhjXSHGYoXq1FIeV{ڐRU`ql"VYuJ6D3*m-U·##36a?xgpŒKڙER$<JddB؄S是YFI"x�JC#9_b7ԥ\e 0pUX#w֊u- he\+A 9#99Sbi뜹 ]bl6קb%')C&-@ ;m͐�g%%G3QCSOœ*$Tb佈LM_CVط?f8)>bVm]ÛLںkW6D8ˣ3 -T͐:rmԦ&NZ\F!@pso9s2zֲ7;^0P 3d"c-<zIMA7G)hU㆐z˻1бtAV\@ʯ4%dկ�pꩧnݺW]ua9֮]۴L)n喒sXlYt]jըmf/btʕGh-B c!m۶HИ){ggP}gbΜ9Vx# `Lw =>Gal[q8saRVYŇk/9GI1*UŪ+-|$hW~/HST#Kw6k1{TuɨX‹/;vZdLŷ0^{u DISCtª J K`1#/BZhZhZ@ %Tid,i8.%Eќ"Ggq1yiĞLzoGRUjyEZƪ-gϠWܣ'9 Y,1Áa�EPU?rx(>D/#lHZ_()N1^@{JXj.�`F$d$`P3sX=@we^AIQ+WRE,j]h[Z ȬEHЃ I;Ε-e?Y Z{.qjg]}&Tdv@NӡDd d!� ^>Ki8�]EnwBPcFFBPz\ѶZied,*{b$/yB>hrUhka$(ܟEY&!c6(jک#Ui C8^KalXAw!sqzR>/AcVJ8z޽ 9tuM0gs| N�ǧj3N_ʨ䷒!Vy(; D\ƕuy({|}XV(A_bW|pb@-w:\El+;gZl$11gu޴8^vX`A齕+W6 -2nֆSNq ,]wՔ\^jըm7#zzzzj,[XZ8Hmmmm+1sަMn:<S%2vŊXp]pֆEK.E[[Q/G|M6ݱIyq5Q9|(h`mBq޸igqb#X͞1B\z%XS*p҉'ysQ�2p`pb,^ 22h !d%ސ $Gy܄zZ9%hZh7'R&$M=ֻ�G"2d$ϞL|~&U3iAbIO ui8.XQl"UwPVת`@c3ݸό50U@DĪXx u 8y%.2yN *0Z6(ɜɑ~ U] 2�!T !3c}9mw3%U1$rt<Xk ADc)=|́si'νPB9<Gm*j�3,@ !ð5)SR,5Z')DVcZ+a*3=uhD6Cb6:*bQQjgQGw 4W}! r+%>Iѥ}$iz2HQ&�9$#Zv^k |)xnJXtC�GR.|V&!uy;?jyIGphҮ!Ժi@@3SA {4e*vډt3^<�sɥ52WBG%­>D)P%Y B0ddž~w9&A$BNJILBB-_KNO:Y0[ xr,<�C\"a^Aދ}Y1ctUDx0oL&±.ƛ_�9(;z3>cG $k#O<~a<%Ko;/^͝;w\O< 6^+Yg5j.Ԟ|_ǺuJfgΜwƍqW�hl-Gj;wnf3c~~9Eh'|6mjhӋ.'xbm:B p83gNHӊ++��=~WWטE류={6>`˖-صkgSL9ssHVi;7ҥK1m4�>Og2KreC1F [̌J%[?\p̙3ݳazg::R@p ޏ}{aO_y Z#SF3y0v؃iUk98̳100m^mmؚbVDF U!k$p{˨#}U7-LqGB -B <ZR|6!W-VA1%}rhxJz2Y68XS<'zS!R%US鳼s>!|L2Mõ$e%[,8Nh$|IɄr9IÖKH*` Vxf񌌄�"8b(232`gd0᠔#w^N%Ye6-PA }{d!v֋E9Y!Ew0 XMWDuZT*XkV1"l.N{LP+-$h kkP7}v嵘( šOK:% 8|0DB'P %4=,.TfTB>QDo}5WqX+HD&cDFن` JNl :^x{`[{.ַEX$d]>�:%hf#C4_LyZ_0^K,(6*leh�Yf2qE6G2{dD1x' ja"MGoOCXgI{бGr/yuT'E   ͔%mr:κ7A�8n^ x.'N'x"NYM/bێ^Aolro>_ŐF<csyg 9%qפ7\)g-ZhZhg m4rDk\[xd!,nr i]xq;-1knn82ԆؿWQTp0} 88x#;뾏G,^E+хo>m`[Y0{Ӈ]v Hr%uy\N9aQ_z>7n"uzҨ_wLff_B -B -|s^U=Y V"PP-ιB! jQ9 {|! FMdPV1E'VIl`g6A(7B̂H2Ȋ82J[bb#,z+AJBI1cZ :;1ThC+]`NTؕ $v„]S՝*2Ua].eFۍsx U2@KQhȱ[ܙ_k0>Y0 mB9m}3^vR>60AG༇7TD%輏Q}Q Z0fdur(yZR p@ 4sV#R$8 K ٢.uǥu9hpDh?QM26lȯ5QLCzլQyBw֫cSZ):8$ ?Lfx8(m !A —YmݹPzYas[)E:Ar/KØ J{O,gä2AQP?A$d9SbDs"QI=O:FJ#ַ1�G� Q"W �"†vtEc[Ƅevb4k𵥗5r#&JОp\(d~XaavzO|o߉|۶cVMZl -B -PA6]BTm|r-O&t͉~K9^^gLceZ j-[~z ?ݲ/e8c 5Sde3NÞݻ1<<yNĻ.>,?�]swyQyK,�C884J]0dpз۷f ŴlX7vB̲%11<<X^j"w嵿á`Z<Rhm -B -d֖^ ǩ耪DS)%)9I jG!:w U]r >_@z4� QPJR$B0]$=<{q !GX\!.k~Ԗ3#U٥6lĶ-Oy;3ǮQe3F,Bn>SAJ4 2ب'\Tl^߯&| >/+ BVAfŨճFrE1K4 y~ 8p$�Dw fDX 4 Exy$ҟ(&_$cb QUڈ=lp%JdE zTm{Wzd˚SB]@E1D9YzR^ $+mY( ŬVi:> D`3)Kd }a/ckmsү~uy|(x5ZZAjb 4ƈᯱ^=9$)p$ 2k' ܯ`.4W.׭e KuE4\ &(𝲺c;[;9, 0U85{ٳ{ll۾ 5 �XۄPVgtђ}w#2گt7FI4E (R)[gd#cKgle>7=fg,۲fHeE @{~{Ueĝ?nDd@@/^Ȭw; #RrPȎyo'Kw}Տ|lǿN&KgY>Yݏ_t8;}G}G}\a-әVM| 9Km3>k{F^j}/fj$-* ͘s/|sy 9W_g}m?.>=|{>w'fks<z'|KorGt¾kyN8nvsP%js{lpPU ̈́*E &QfUE4"a1B/~w\kBϧ1?{'_>^hZ|YL裏>e=S:4vSƴՔN(!}pVZilKK)W%@մ_ CIS  TՕtў3d4?Q!*;:4YK D�NsJ3D3d{Mc)JnDpDSz aVHf@�(tZ@\&QxbSp(On$fgl.Ւs]g:I+ R[ E2U\8jzoF%NH>@(;7pHH:/c4Ed4%5_V}&W*Y0 @6+<O,p,..}/%MJBK@�LT`sڮPzYaj3(fGK{mi7ҹf/2-`{Ab޴'X4˖ùnqԎ}R`9m:04qBN{rr4tu|{zâiw ĵj|1G>X‡NIDR-U8{4A| l!I.x;cH֑!t>IƔ.sWƋJqm|6=޾Oʺ S۸˟Ͽg&ȶjۗuj㼭O=YQTb UH\{Z]s aןIPOcǏ @,NYG4=\%^^3{mO=ucwO|73smKT='ؽ1y)t2AG}G}|u~fbCw<fN'y;\_?wuԕ(QX?vo{>d<bP\u V֘07YڽdkxA|ClI}a6yG[j;?yITlxDi,CVm1WD+Dsqڰ87\x<ቧdusD+FaJiÝE{1В.P|E/4]o裏>9$ؐ* Xh[U->Y3mzskDMVgx=Db C* !mS0p*]-9J�� �IDATAҒa!Sr%O&Ugⱚp9Cb!ўmi) "~i>UFɲX|RЕA|RQВĢB9vTҬ$nR &Yk6G hř)=QQp[¶vx'8TUM]{Av޵)ZWtΉ dwl)Ab/xUz'ܶrNx'(fl޿r'bU--uU^BQѐ-Ţ]\BGj:JVLt~%9r )m#JD( dcx>gWJ8ζL*ɻ* Uך%$Kqpb|ɵ;hBv1%ԈrITis6i\%3A{6*Yua9 vmbNk"7;c_zv`j DR!m;gOX]smN}1ޛېgt~=q21s9u4Q`um@ f:eUSb䟷k /v\@y(_1w gf8˿rZ5͑c^sQG}G}\Q{xv )N;?=s!gpᙧ7ʗ�)NO?FUW۽'1p�]annȃ_A℣GsYQ/ jٮ J~?|s:}&_)S͹f&^&Ȫ!eڄt7:g?Υ,xӶyqKWXy|GܷzA>|^!G}G&\H[ly^ĠaR"JΫ .=gYwX1gfS[d}GmM*,333lnn:ꈹm3{VKTŞ?5iTM�} -]6�ɥ)b'250a4U[Duţ?D' 5|Km! aڒ3CeqUuX}hǯ E(4&T1Et 6% g.̋ V1b<^M)OFIӤd icItJ3 URzZ}EpJ\mS[B fUkqtfj1 WWbn,wt/%)tQsJ;yE%nĤVZ;1zS`3N{Nj)KDRY-2lJsKRT@F9BnĎv;gi ]>S3dJ)wbc~4Z{5% Fk-s$ 44r3Zj3u�kkiZGGe;t[I*H XKgMz`Jz0UN}%0Usy'sEdPNqԲ'AiYiZƞk̞Kh�N}R7/׶SީGOkokx=_~??Ld裏>裏ݬ֝⅂X`jѭ/յ=0{?w) �˿8F>FXYYS,.bkΜ<ѭ-6VYڳpYiX^cۜ>ppֈo1ͤYҟ>͙lH!?d]Đl4 #k<{fx,8D *8Yi>.54gP;~%O=Osζqݮ~G}G/[>-@4 sjQ89v7y_>󒠢TuhɤII1 u;={xv4bXש~zT 5v5c 3ue) &*a<i$JsM4kK?Ï܇t#A##^ tJ&Bpj!Ҡ;�Unium}2R[:dţNm/b ضY\&da>t;/Qh2{  19Pv6;V:-+V6D¨af0N+8 <%X))i5H<U%hӠTN ʶD*/ |U3B(gmVx&Xjq<$ DS&{x kuP*6M-.תmHV_4+v*wT̥Ĕ!uelj?-/r'|J6M)35'eg>=/p[5b;K#%X mspQOU !6ÀAܴ9HOUi;ieЮ !}WtenpEJ)m{g%#Z\SV}iڵvtM#JZƜkMci-~j4v w>N:-m*bߞ-XSǺ~ܩ:=.رKS^0;l3%Mm+[oi]4 \}G}G}\>q.sE>'ƈEF[[[eەx+%[Tr϶IE'/SO 1p)N<C xk $lPEv 8kz"{X33XasmӧN379ax{>ȵ^hԔv[3|}+@$řożWHsry}r {1D&Yern~] 64?{ŁW _1?//!]咪裏>9b78֞. PIV 4! i0*JsAp0`ҌiB;%G%SAA}2oU3RO^|Ru"f{LMO vC3f[`AH#AȊHLJ6C3%sT;Ԩ E:}vPI'i[Rُ 92�8R,@E} f U NHTĉ1#a5À<0XhD;%q-hHPWI3AU &:I5;TεFЀOs S+$fU'!rٕ2:tiƫI)OtZƧTb?y *b ;[t|.01J["o/q{OqhWmiDIIc-ڵ?S=Z:BffGM@o7)%\3`�-칝sJk]l$zj|@è Qa.9CBmVMW ȵ%q#F;[PtP4ZKN;N[k�ӯg9m-῍6 bz=$ڒӹv cdۺui/Sq90f;_lrvMZ>+o޹r]gK݄kpY0'ޅ:©m_}G}#[Eԟ-"βo>:~o�KKKmCǖ*ʱz/gNdn~A;ƞy?*!s73\A<O577˃O<ͱSPxwm/.z5ʡ?#O[_) ]42!yU_6&3~MP(:[Ɉ5Zm^C>EwIT_ ^wŋ) ?L.>۞7^˯>裏>�۶M4;W$"&e^Q)YU&t<<ULN )JPtb=|Ǧkc 󰶺̠&8'[FmUp¾,8yR%/Mj&DARW"bqa\ducf2�MmdؤTB&F&uBzAnn!5^6sM ]~>eO\՚@/*1޹di\$6рXmKM cY OuI]:1[1upERE;ƀ' * <N id+ZcdP6jL@ON6�=>K`}k0>a&#y_$]W]p/̭cW)׽vm.Tj[@aM BJHl^>5=`vI}xgQf6 FsBҶiX:$5^ T`di5pEnP]/XkUݶĉS}TOJib7sWDH#N09)Vc bkˋEsM0kyN7\ X$k)ci՟lKdFWU vGvYmN%(uŤ|Lrb'%,Y=wAVn?|lvvx &esh|\&p3S'αE\[c|(+^f/>裏>^>l_(( 08y$Mm'9q?c _gh[<c U*L67xŵr.b3ʥسgq=4ORή!ëo [01m! *>4O=4C?hISY~0~~ɊOqyg׉FOpS0 сTDڝ3Hŋ\9@`+I~+i <>u7ě\&G}7o$pm<R'.wgOT-,PT$;SY[b lmM "Wpv0JCwjF©屈„Q5UF9SJPP@UX[�6 ?v AvV9}o L*#+ "۸:U«UĝxM- qFH[fbOonV!W*۸*4q`f@L$%|Lj@^hƖؙΗa43k8&6j%k<i;hjfx|TRye-v Xd.o9CZkeJa(ڔ"RjGC ckF~|NH\6yivV50EgR_ZJV%Ƥ|t+'rhE|]4滶 Ʊ:,�6ۗBZ2tY%u b Xmϖ]ȘTbś6神l5_R`l>k h缋;5$QzMsƹ4E\yKN׭:R^F;.e8k{{>q*@N9oR^ / ~9uz.U<gY;˽裏>裏 /i7st4:u֕ !黾6~eT$'ڌŐe>'_J>W\uO5^1 5s8 TDpBڵ`c+fgk!GynMwYcvsb_jl%[IR"rS(D&q0 /NB/U#y>YƦ<Ϯ5NrztSeo>裏>f֖1+@X@̐3/ޗfIhhoWֵ͡dŴ= Ze[ F:D8g4ɋ{kD hc{+Y5lsU@C*[|AaҰJU94tvR E""vd1kf=ui,Im]j@ .ƷuKY1XTeڠ1٢FvZ8w`jc s]QSш*6TjMSzlOa2W8˜ 4�!L 4vELuZ@!IӘߪyp1ow1)3T̊/F;yko]A &&#'W0E$%pp;BZ9ljlcm|:]d`!g6hj{bmD ֔XZ˯[fLVpJ@qNJ_ztĻRR4bhMC ޷qw}._Ju%^GUNuj!1uqCJ*%yVl y. wRZhKiKyO~1ض\$Է֝-vB՝\Z۲2E(mwiyiPd�q-xh=ֵcݞBMS\}\o )HlxyCO3mwG>v [s>裏>x i7OBUUS/ee4q>˓_?knyg1Qh" \�3!a{vὧrFjpf*]3,q`O;%VyGٿ�+gV \wÍCKkɏE- ge*b6Q!2i#4w_y$!X[Ch Dfp8d#Ng1_ak^Zzb+?ſz>ϝbxy+#ا8š.)[}G}l#P-FGqK 2xȶSpufU/34Es΢xll(};5 H@&*>G0ԁն(/7[.Vi#' u'c U|<ⴵ&‘9D11$a�[j&c*碳_R ih&Z2%5 S[zum"g2"hY�z@ DJU &T+âbM-b@UpjP[ԐfUYW*4:Ƈf<1(ULtkqA٘iBpF" $N(.*&,-|rJTml/t [-v{?)D5_d܎7u)Ps_; mwz Tv xl*K$A<'B%ЪPq @Ud} fg�txsN[pOZt_eHP0@'kyWV۽:  +-bJ䬢ƹZSfU7`4!$55'%aA="7X/ms5;t%ihmҞt: _Gbe_c3Œ^$'Yj:.M0]tjF:JN9)\G+0׮:]9}g:lm{xtuq!?o.b΋yʂx0sMl|l~){Qe}G}Dž{ FU"жaX\_ciqRDa汯=]W>7cfpas9:6'aX3;7 |CN4ٳo"288ƁX䑇f8ʼn__3 P fW?Y^Y+}(9V<!:U[:/8GU57kKh'n羼zI=O߻sCI|29>p/c2`ye D%{aOh'W V!3۷z [Xp 5mE}F >W#[M*U1S֨qU]}G3=NHu1 &~U`mykWƔFg⮂_ 5[Ŏ]koq×|+kJ$IQ$IYLLJ*{&行AG'V4*hc L̕%YT:h]^2I}yTdDT1_}MĩVioRie\HO>/ ZԢmTm?VM�� �IDAT d@O  xZ(KΰΙ5S$oD`*`јl_)@pרTAj{h`8"f�%Ϳ g;^D! J6%(b4뜭$0|I`*dVG|Qz5ex['j.YHt PgYDrOi3dPkH#gFL٥ZUz- 55WMr/MHV�KܟΙܫSlAT5[|#ؚ[avmI{oJm!RStn%sf)g@$ϟ s%:NM4.CH|&ܯ1vǪ<YJIQ'edUt8T]ל4!hi]@{hV>-.صg/x INSӱGDRBGoI4m;;K 9ShR=.$ѥnKw;=7֎势CSPvx o=n6[<r}G}/A|,^هeAc4?"7;:ӟj6{=]>™g]SiƑfkLF#.533fv0$Gx7`vnݻxϮ]8z(7Uأr+ٻ{7ox?p#IֹE? uj&BM �A8&^] omoc^x xxß4<&GA'ONoh:~a}qC<}%sp}?y>;nfsy?{ߧPoz{֫Q; 𥓓?<fxv|%[GGCWWջ}G# 0fea=?vk< m_nAfZVڲb>V0(TY+AٿoU~iάb@Ֆs VIYu+J9T)% NA)DE'DYCzo6GC ^1D՞A2)DL-#p*T4fEc@j2_IC9JDK KOH֭J>کiL)RFԹ4])kvڅUBh/c9?A@*jMKCsW "  'uQ){1RMըZyb QuI-bKus)Em,qAc6 ij69'R"KBBFLc(:-3mrw>#1�4v`jE@ڼe͙['SFpු[bm*4*ѶpỴĄ?Bmvn]o6đm}07vyIevi?M|OԗJ9q8$S!Dj׮xoז%KG&q-4Hȉ#2mܺ|M'P>D3T\w8X]%쁳^bR{McS"7z@xYZMJ 1ul[{b/v{w>纮R€5i9U/e\@!'~gY[oa[f 9r>裏>裍 js>+>vb IvYmh=O?uGz3'Ncٻo4?G%¸iL&eL„'NP g[ҀnF#j_2l0\ľ]l'-o}+O>4Ǐgaa?=|nz%Ic:o|?]ceRnRYZlBhI \0XdġE]~S_޵M&*|^7\Qo_XG6z/oy߻yͮζ|#p{={OECSO?¯6 qgO|cAP[»F)ƺ|np0<g lÏu|裏>x BԭhԳE>B/=u'Y(/D͞'JJ>/Ģi9^<rru+(DW>Kʼ bDwmUd1j0{c1`ڌ훭*r?U4DpЌcc`Q#+Hr=b++b&&@mGS/":c9+IIQ8># *!9aHx l\-ƧϰIt46XSvb !:Q1NBI zQ_Qk C.2 N"U]1GEhA:Fs"16L+jAA: -ܗlZ𝂴R4^ kP5pzmgZb:2ٮ4ALE[`KT[c Ŋ9mxj\|dm+*P~!v"Didmw쫛 16׳ev&HMk?'AUtJQ{F�lM2+�i(EwE%ou9�41\3A-;Ò\$>Sd.-1AR1%ޤ3WpMNjָ{׃)W%5%WR NNN| 4dm~M N}Ǧ=O ?@[J8gV$?)=*EMSΪ)8b4SZwe2sSs{O9Ct-c0وuž\zaqY7~S(7g~WX?d;;UWq>q̺>裏>k$4 o._]:D9S[8v(6Vbaf<19@gnv]33ལ44uER[UU= 4^^&4<ϲ07ˑ#GxǸWceeٙxvrC�wS+Mx (Rȭ6#fg<<V7<;/<ԫg/w?e;lT=>s7iOUF 5 ]�"Ǹ3'6sg+ߟ #엾R`.^L'S"F#||�M1w}+#{_5z{&_ J+9wg|wΪ]ā\uz+ [8 'NGL+Gn=ͳ/i裏>؛W#)ҊդK#צ4@<1N=gkeG }4%IYx6g-}IT`s 5&=(O/:6FS8\Lj'O-- lZy .&,ΡQ9qL"�@VFՙrSL-B 9a EhNNQcMq|M)0͊5Z4 iAL-VCFBok'�Jp3(H`}s TfB4 KLC*O<(I L5l*&NIdW{FhBՙWՠF@4 Z�d5J(1q:rUJ]?rM`-5sn[]y_FZqNԤLp+S/o՞1,ڹqTo-/G]'ig`XRj‚BSuJS]-gunAZ<ES隮|.8(糪<1fr, U]QUU:/!]1蘬̽ySs)q#TukU$ugXʦܩ]!Ud࢚5xUM5PU6BMhT `6S K) 2KKۿV5ՊYEy4z) >7/>">; (ZL;FI-rݶ4M=gg};)' )犮kv,ϓF|c y/g5Gp3̾ ׼73mGt{K9VC裏>裏?r SƾT}ߵ*Nh*ڭ!}Ǩ< k1p VVYY9q`i sY\bPyfv !!G[r2[#|5d8?<Sk\=duu-{H /C/g?N͜Gk?Tf);6 'l] {^7ÿ˧+=Ů{,!Oݻ޼:+y"qm)|S�1Joe>1Y]t$1GNl`w \yܾkwyak{e*-wGzOqW9o=_\~fw|q9]{^+ynO�c27'G}Dž "ىf*I)@S R-L :r&'8}b"5EPȌ("h5+}mY PfX"x1Sؑk(V]aX5 Q|U!M Jy1fbB_!eЄ 1ϊ-{B AdbP{Ļ|M;vmhRwuAT6ڴB=3i$Ğ5h[Ď(*%\c6%e}=H %&Ǣ>'ÞX!گ)_@ Ll 9M6htxuDW1цf<$4 \9G D/D SVSA.-Q[J㨌?ItĤ^sS-̹DJ߄?Թ D\Sx*W6vohǔKo4r麍iL2Zt*Tq LMSêTk!lj2y5=?mBv{}TUUi>Br[-e~r2@81ų+]PઢI,x4&*T9d<o|?ڽQ"yy&ڠ10=sMNlI~:kS�UQpI(>f) 'c┅ـdޞۤpV/ʹb/ZN{MྵH(Iq[ߙz5wRҎ9,Hs%a<G_4sKچ)Kqmu40;Xo3;m4N]4XY}G}!RvŏW*}9)Y[[[lmlڢ8ط0i暫9zx_13a83瘝=~fDΜԱc,30du سg?8^ZbfvWi& *_?пWo}5w`ntx56#Q EiT2q⬵Q9C]'>sI۱S#,?>~c| Zxʱe0 c0�zFlyo|$DkE:wn^CCWo~t[\SxD~1)G}Dž ե)4]RQeng$ٳpa)ʮ].`zqY5I0./;4)Kl{Uk*Q [:LD Z[e Yaff3+'&64!SE _ !"$ )|c .m+c{vlcN:`֝;*tiPPEa=\}!N:cq fE$-W1nQ{ ;y;hRw:`ӌ'K Nh8#T^A<C#%0+!�: hcl휺!Aj%Q*ovf5JT7MKF:Jmh,΁)'\+^c=]eNTSoтM_[K"궊8RngISVI*JN$E:wE[f;cׯK| e+s]-x,RMBBR0tMSe2�j `R W>%|83%TI 7m"|`' Agrv s 毚kw#0/^؉vvi>2T;#c$ KK~N Lg9})MKJ`8Xs$3I*ʥh|)9jXwOޅ޵@>Ds[$dY|!~~K݌ ͞}tрl}G}F N=y%)l#]i"WÍ8q {Y_]#c<0 kgq{wW<sKTFY30tBm!qU3'8cZ}Qn&<4> ^/w?N(;_vٚ93xܗV9kv^X{.Qu|ͧau?<^:gȵU^^%voeoY:ҏT|_Ho{'v^$룏>LֻW}p4+,)NRTo@4纼 ]yNt?I'BY}>kOl?7Ëo}S֠$A33C*DȱEAX-Ll_:K1ƈ8' ,}m[[eUpIU-\-Y RV ",iN<Iv2+ `Ua{uxk C 4nQFi38%B2ǎ�6m=YF"4Do H!tD15ɸ7b[e=p{)bk-ʤڼ8W�VL5uྈ+cVu-5;�k{,j}ZIRŊΙ]sD&=J<~V923b'y۾8/ɼvnN+{I-}:-\S{J2tΓjz83twu}Mq5juT$!2L']6MCTegP6Z]_yuEtB bݞs_/aGlkK m7뚹t w#ξӁYA 4YacJH?W>o=kLqk7I:}|Q,;ct^.F\@v<q[-,0{s͑#~q¸ڽW0~ T u}G}G{;+m%l}EIۻg/ -e'O<{ф2kfk߳ǟ|Ӝ<yյuFr+pud<bs}ӧɘz~d̮^[X8}zh#bKWfuuy']w7G/@DBTp9v߶j^÷ qk(-,@.Oǀ-{{z֖N'Z+o>p<GJE7~CF>AR_uǷ`O>z|soG}Keuk-jӖЈƴ|.mQRT: .4֦UcEk-Qk jR*cD|LYe bY1{E\LE* ȮBdsk`8U   ־"pfu% >8TU59S9;GɠQWC0O&*Sjia)z$Hudf@5pIAMOVD�5QI:Pce2PfWv\Y WM�� �IDAT(ͤ;;/bڧZd7PK~gv"CLc UT0v1nhiZ3Sς8hPʖ Vәir9H]mSck'u*�O5%Ф̋GMk͖3WFf'ZlSALc"cv0?T pG15T_vkv'༝TC9w]KjRo}z:v@cL&lE](L*dUTC H|uASՀUu;>]î=Ga<[=T&?JhQTgnt8_1T 5M!�c1h).;@:vGVtmis29CA]qsvb ֨D0lK ݳ"ˬ&l^\k_mIJϲR۞TpVHdz?{NB7?_}ۿ{ĵ5&G2[`m߻7꣏>裏>DN\`~^/i7{``P&&R )s}AO<qn,A:+kqHmfn 8±3Ooµ_G] sL66%2sf g7!fff}KKoXhgV9z8y:" */52t_k 塲fG6n"n?;n0iK}'W⡓+w&Zk_Ƿ|/?}U\q~y$_q*GGr 7^}b,O>XN~s-hl9Ӱr}QXN +1 G} z{]J2) yX8/)\E`ϴM85e/>fv]o s'b"I8f˲eGmVێIwٝJ*/]yIU]IWRyK\ɕNuݓXe%˲L8J" H.tk/Zsq/(½a5ݽd 6)�C(qA1E"- %{lS~L#@pa%OTi;FPkV t |ղʅUp,H  X<w")D$&B d#"4 uPZc݅ʛ:g>KʦR6T~R*d,+(W75gΞ!%vYJڽy'D"] =cq oh!zCc&bEe}Qm$J,ecQŢh%~ߪxqYZ):Kd苶tLdGv֑9QV9C[s$gy R"5 ccڔxo"-i 9XW{\~8LNHK5=zָ+kb +2L[$_`hHQjlQr"gh}[ Plj2WJsV3|L>E2+IҖϥKDYÅ<,tij=00imnHX2>SeA�Wl3 ҫ*NE࿲3Sf@,IBrDH6rP%B xߡnn)`2Bjr[A-J_zV.,&K;Ox79ADplĂHjVAf04MM|e<ݶ~E$+8G78,W܊yβ}eCF:r}^[n,[-mweg2;FILb$&qå2{JƕN>6]![7/ yApB[f9oXWV8p^9z`lbnCx^|'z|CLU;~M;ŎmSgϳxvγLua-[YY]evn,|ܱo1ᅦK+/:x # SX5M kV~Kg$&qqxS[2Dsexk~^O +?#=᫬a)nD\_{n._+/[><pILbՄsVYL2Zy(rVdYgLND62;3o)Kg0Sig-ē32j'2-7f'z?!F40e[mlN+,7irbĔ0"4EӘ%k30D$bqի{kP^ `M ;J([T &b#CD'D X~E KqLePmPd4qTa+4 [ S$uYxf I>Ik � ޺!XGd<Z"C: iԶ&ϟ`mmJn Āt:tlxDuS3tS=$y3X;O]X$ALhvn򳤂@Z&)Y!1(t$dYޜ\Юd$9˅2{(Vbԑ f;w2MQdpNe+sܵ3*)[�^/JlwՒ$etWMaE(=+o8g@QWUOaMv㳧lٻXP;&4aln+:51b$%U[:zWTUES~TU419ziLJ8 xΛD480^"Bv/ _8eum܂#m[):={28u$.`1s,UVWVZfՂQyʹ1:fڿǛ_b̍3l$SsE_bʶ;<Ct^w7G.n}&1ILb#UDWMG`]$ K"hS4p �8̅AÙa{z]]nxoY׎‘ciί5ao5~ۤ2CgzlٺmsY^\?sI3=;˹xX"K믂6X|˟G~_xtMz7|r֛/fFa"~F_YW7tم8wy>x@M s?r+gNɵkvILb,'Jla{I<wdvm2C9_a30kͰpfYX- Xnkg%Q2}TR%%jC,8�>tKV}\}76w# Ç jRBHIՑD$8,ͮt}o,14P9Tr*`YXk,<ơLn)2�0"Uq%|LU)ϠcF!ČM]_[?CTӽkNT$SQա &7g. *X28h˥z|ϼ'9j$-4Bl"ua:ԑ++VjY֪eR"hsc9;zL*&bF`IY c`0LXGH:7I=z<pLug1ft36&Me9پD%6e3xc19DŽ,e+hw}ϲodvqNYN[%|f1r~aB  խ5xln}f^ #&j:*:Tv*b <wZ13Hazfn[v/өuq9|Z殶gP{+SU4.;2Хw/}w}9v7 vf`! :}FyCLbބϬ}ݕpOcըlՀ+ny߻N7 ۷{:6-<]Ϸ<Am,?m/~س';`cg{& ILb$nDYL<s:نKSkՠ/ް{rpΊ'oܳХ,&c+ wSyd fDj\mƶmHM͖-Mdiu&i1>>uY6RXiz4Q8v}>ÖiIף:UŎ;xYYX^]33 k^ᱧ>y\gO7ޔdjf(#Tb[!ó|䀗6~Zw<g?y@+6ȱcos*Psxh5}Vɋ,Qw7x°¿ |sO]h玼;Z[nbǁ8q C}vt:Ӳ>~#{IMȞf|M^9J\}|~g/z$&1)iNF) N߽L!ka,geXZC/C8XaU?Mb>ZezMK CQ٥M9>$vAAO0Tv7ɐ1WOST`g$Q(i[՟$3�_C#}\Gn+-(ShlUz.WVBbgEu]b A*>5[U�(X/4QuLzĪRJ1Ĩz@J,X/ MU0HB7H2fpIbJ n ZV}E [ԑH$IzzK PHjz:2K8*lrIYHbZXD TzyB z~Zܠ<$?-sX!e3K| ε Ɣ]b .JW� c"DM.0oeX;⓻an-vdbsYD>) B)"WˆiȢ;Oӥөsˉ!(D!B J&ԁ )lT8M1Lou 1SCSyB ;e72Fi/$B9qaL NM@(KʊY^P1*/Ulq;|$T_X^[?>:O5xTYDR:wN%|S1[Qf}t9.&_l:66-uNnY==z0yuͣ/~ };G߾RWU+j~L5Ś$&1ILzZ}^9uȟա-yWuwŔq} ɼg6Jvf/ie{qXFCq|_2$ 4Mmzw[>Dž>,6D]<aAJ/p=ٻwǎt,TA,γ?8LƅgK1)`IFoo%`|=Se3^e!oP@'/e!~|>gvkn>v|ϼu'8VW`e}x `#_RK|h~q?ŇT49�>4Gn :u773˞}wo}%Nx'1IL!xjPe�0xLi ̦ H8U0Gϕ,U:|,&ʒe7g?I\VPVUe: ΁QSa `7Iٱ$y4c^ * 6 P OdӨF{⛨̬HCw`K΁uzl rXOӈo~N&u-/%%dbHJ!pYD"1kuXN 7̭s 6ef4{MGw8!SDꘔ5`Ra#[i0ɟdY)%RԒ =ìuH%ER D 8HmnI"kkt#0_M,`a+,*q;}nj}A˕{^N5F2,@zzTχlm%1^X;2miE}:1.Pelu@laO<?}XrCV(ZUt:gʎ sH)[9ׂå<9gS7L*H 4W:1E`Z&&&u]@4!jcGT<Nk,+k zTUhDH1 ^I՛ǜd\ˊ"N#3c狟ɫYk-g-ItU9!a/,@@49G FΑ2͓"w}l$u;>F-t5?',,0a/>y!X=wݐLb$&Qq/$W?~`Lktp7�ℼ6\՟uzpNܱl 3<<cd7/|ş)ytxw [,Ut{̻m3ܽ?x/ggv֭[u?PDޏQضm ;fNDcjz>!6nXmUU#xٍ%ۆ ]^ڍ[)x~#W8wrK|d3l6~#TSny3j_Qmn/ys`0?=G_rrg}'g_ G.{4x`mj�l=Lb?SRp(gF$edd8 u{I7e(BT);JXZkTUDI Bx,D2*-ӲԌ/f f`Q6JڹNgiBz6oA/0%f40u쁔EQ( 15Ėم8\YM(H!D1f&\JDk?dˆ?t;T4!=bI3/KEQ*$X0+to.^_;Ϲ Ě*A7hj, IеAGWXoiHS"Hg0`Ej3K̔k%Ma+„HxO;JTx-E@t }b$F#6t:)|דBbiB J%?(b(i0f܃3'pR:u7! { i*PZZ@Oe1sY{ȐMHica=e^'`0k]1 $2gz KxQ}$lݺ#/RMil>hY2/J!5cL]ӈW!j`PVt~-AJ*mSJCm칵Inқ6'd2Ě"KN"IC)3_-!Mht4c-X*~ʂ$ 1D2̃�3F+Wzeç̮Fꜘ^M$Ur(P;u;`ui&u%BVUHym羶MS+.BJR$&TB@/e%y޶㎑=S׎Emz56}_k~f=׾̧~'^+7y{$&1IL!^N^sz\-ϭGwaMmExŒԣyό$H z4Mc>ʧ> Ӭ,b2y;ff~4+! 2kuCi;oy\aΝnBkk+̳34̀D-!H".Νg?w֑2VOϾƶ7�� �IDAT\b2J(`|r~d{E<2'_^c7[yELGx}s|ѽ@sa {}dxkڱlyOq>q >Ο<ɫ-gn�\;ILb&aM P�"+YBS S;ƌ1ۗ5 7 $m)ւFvrϡC,,,W_%Hj^S~`laزr$t}wO_K+8 IɀeT6+~~ZPCFDhb>>3,R sy"XH62VT ;,שDMF.sH)U@A~ Φ m-$LS9Dkβl#&z6V Iy{C g=h!!) H�<ͼc` 5lu-C$*1혅Y8ֈ01X|˅@CϨ-@S74\V(=Tbei_Iq6K*lDkkuMu.{[DB _RyH{=,vDJ*c�IDI+2ReRSbiF/�p<D�g.$T}{4 ++kMȌcI~-\9~sc1BT �&>:fU&qYXΥŻ֢@|N88$qnMlm$QX` /`9Q%ň&chk-33 1023=wVYT:,ǮsMRi#IK-{Җe۶yڀ !I - K0mфr12A%\'O,! 8DvnZ7`=(MQ86g zϰ{+樛 Fѭ?�d;t;Ƞ![X◮uصG}i{0Cwߔ{Ob$&<^./,d!{ ){M]O`Ps讋I/MBn;ūU>pv`=auί}3|[رS޶e9v܎MvMM0XWQutg?`ue۶Q5 V!̌Jiu9}g}wbLn3ܾ{Em:rփ[Kk+7(75~ iKĦQʻhۄ!?Gyv]ܷ`O˿ G[}WCmsvSt$&qQn+XPF ˯[>oV*4 "L̽wZ+*; Vvc )f9x<yu}~2<i;nm=Wɠj'OfD48 {qD<F=) Qa cS1Z$s-i$NjҺPqYZXRHD4Q0*D]^Iՙb�T8i,<,8kN%XrgXP $XQP҅V64e]J_;kǼ74-,4vM 02h^2]ID J$@ |y屮W�5%bi.{:d)Q[k,[U5$* lX 1D@|eg6Ih,UԪ �ldfa� `Ŵ˸VF8w-:_'z1nA7%Ĵ#k< M x=i˙Ƶ-@]Y8Cb{٭40MF Ht;]8ƨ8/S!ƆUUߵCPYu,yeMF}Yc ԃ ieSTURYE"g bB !wV=[c$@evj& +kn::t, ٿy^N#7瞐_ …E: ikuFLKZzY /Fyb̀UiqFMsEBHQIo-.wcT / 3bdߧ6D_x_�{|gE]�|�]aW>,gΜ3M.OG,,, /ꫯ޽xÇ߿,�pweQWn%+7oѹ.)ם+1w~ɲlYوpsn:pCef;=nc]tffk>Zs,6ef TA==TT)Bs9s2༣7=^4!$VW_?ç~Nc>myusه0?|b ?qf/1?|buU }L_1eص{>:}oĮ\cg?gFI$v{:/;?q{7sskmd_ַs?avKl32Ö\$&1I\Hnv쓚HC`##VTeQLH@􂩀D]A g g{bFs8q8!9VJ̎>(|ow H|ɒ&Ўl'Q$c ;DX665T1S)&eFZes$`\f=zqI bz)DJ ) iF"o,b!enf<%ˁZej$!V4k g]bMDX ^XIF0}ct<)- $QviA(c Sk-:)B\LV~RqlbjcM9B&}1u,]Q6Ɍ^ 8k\Al9BP :z2h7Vp1oR o5Tj,&1&r#;;4u8$u1'rgQ*I!pIEn݈hQVcI�CCTOc[s^ܜRIDpYx+[[JQUm]Xc屦�u1y0,غ~Zgc >2WIF"Q�j*&$R'6LQHj`SsD%UPJT:%|p|ziUZm_g<PKʀuhT!Rjԫ6'Hw+5RI,UKrFGF:xۭSur3gđ!ۖa|A{'UbGo=O<ٳg7~nn;v0;a ׾5^}U@s}6/gΜ{ E:uC݄m˼曜={`~vٹs'cu]}7K6'O}>CI8q|{t.(^e~fud^F)>?}u] }U/ VWx욛'3 YZ)EvݶYp)Kwn]{0e,-eN9űop~4Ps tzl l߳?*_yWZhXy |k7874~)GʵWߝܾ{[2 8}<Q3H(vܮ(aoۜPk|سmK`q9^{ٻ/,c:xhϻg9^Y2Yw?T{m>wO Ÿt?EWNk+Kp䍓m=츁$&1ư){2ʈ%{d_[7ƕm$YfͬT6~![MkBa$UhT-9C  ~=Peg3@3_^a[$34 {`n`'qE5JSmV+,7}M0LuR([%ctSjaRJ_ .iޓuj6 B G3t1NCO_gj SW Gh5-1q1Eqz^2 waRZmB"'gJE먜tؔT^6ƔZ35 &`=;>zӈ�VXmE[X֒Hۡ*;m̀w61Ǧ}i/lm?0w eICXضEv8$!].o#=?=YN61 h3zffeB+W]u ,FeMlc'cduunumF8uYM�haX A~jYNc,屮Oa&iy]I H M:B$4dWt.3VSf'I! ٦)LsM `q 멜DX~),euK~IV0u+2Ex(EZk+7,&_ szLtD<'Hsssl۶`gX^^m \oDll o] .ֱv{6"];.a}A/^ {~ i[Z7gϞSOC( ϳ={nr&q^h޽{7YZR;&j<7yIy~ٳʀ:vOt]J7-~U~88wm=e Fk&.w{y_k˜|MVV/^ܶ0X[uزm{,o}fpv<+>K;ٹc;~YYeM2hVXpsߡI5)b=7e~zyc;zĉ78u{k[̻>z~w}clZO|Qf<eh‘#pFO?2Tv~w/�;o_{ks"p?|ͻf / ok_x[8񯳟Q.֣;ǎ2'oa~ף|ILbĵ H D o2+"4M uVsM2A{FDR%3VEQ&K55e9GL1d?&[N~ ⒥:M VtM`n ŴRZ(#{FeJR0ɂ(A@ַ5%C(8c bU2ڼR _U(v<pcڡ3Y(c,"E9'ݖ\2:ЫofovdڶѶAJx[ؤhJ}; V,&q$ i"&)CRmg]묂bU4%!e" vU@L:̞uTC,<<(|^3H[d[ umi:7-3UTl&[zhjʒ ڪ)% 2;祐`u; X! 1D ցeސem ]> dWVu1^))g(Q0VJׯ*2ySxc*J}c3)a-Q,Dh>&*7 Nt:kc"D#I x07$ ZƐz:xvyk:@DD@") aȾW8cX輖j=Ũv3A3hں>&uKr,b^#FNCTn0cc*ߝì;gw2裿 Oxf?oj:7q=ҟf'*fzMѣG9u+bsyU'q1*SKK7}XXX_b ؽ{7^Ŷ8pݻRalrͮ;Ij~V Dcsw^=Zk#^R/4++Ə8s S[paeW]ŝv_Y?hضm33t]X\YR",P7/]`nq-']Jվ9le6`yf:S[Ze9v* bY:ǎ=E[SSW2(Vc>mS]9~[*׏&}p{x=w?g}ܨ {�6i;.v<1K|<:NYX>g%XW M<l/+eSަQ?? 0\8?2n]]нo˭F$&1ޮVW%EVF 4?OPsXe%2pA֔̐EZJ@EL>L?UNdu&^&Q]P2l+,2H5xQ'FeF;M]gUйE2V+ܦpƑR �CRF, j2;l3`@̕=>lqTɲQXXB#$eY:2Ʀ0dԗitF 0Qeg o�>!D22[&& DJʀ.2T<i*O,ʈMN >^[^m.DdGHLz$,ɨl4=3F )DS)i}ÀikCףUZ+cARgsӂKN (+23ϭpJ (R*k63:c5e0 S-Nb+ifH,k-.WG QIIASU BZ]UEZPZYR&Uܤ`͠*ۑybEt.(6YYSBhhꆘ"kZ ML\ZˤLM B#Sf5\)l>YU1REe X{̞u "Gadccs8v\z0M{va#%aeegPA]|EeEʹ0eKwY !U F}C!7ҝ72.bCWE%Ql{κoDo�Y}c̫h!e٥HtaaǏ ~ϋ/G?ћU+/| ,,,0;;;d baa|};siG<fǎu=aO':H6L[<8gsV_\Nt:/8|m .5b6Uսy KX Wk^<}Z lsgffee[y꘨ 8+g8},3Sl۶)BS$a<͙WޤcsӬ,Cb֭4o0GY^Y!D4QpgPz}Ϡn`k˕azpzۿ>rsgV<#W}uMS+VutS;y;y*Wif=s[�qu_R1wrNgϥ[U>eCƯ~u{.$&1͠ބIXOqPӼ^851v}W? yDJ`}]+glj+&D$˛# rzFlOr1Fb @ze/mՕ|/$LʢY6Ӡ03 [ҍ'C>2ɻ.Rq?LxL9]=,@B+vYᢲz``$PX!$Ǭ',]`!r :4CpXM^t:8H1QxL�B�Z14c�f(hkƤ\Ye|)L˔IA}- 6IԣSf,۱N$ "*Ozn1FeIa&DLftJ8MD8qmDpBҫ1e@D岍 dTZ.%eOe�k=UAFA]R8~1GQ,-ƚA@Җ6nk hIה Hn2ư2ݹ$@WF�QP�Io}�� �IDATRs\n}b 8H`i(C�Nm2XKe;x4I%'όZ&@xoUfXr)e5s] ({dAJDb!DDBdTd=7!U'V_KFe$1*:5ɡMZe礃ʩG6{",y MJhF}Sa8H dȫ?Ym(`;O؂ XYY&%aP0UFfPmoQpsoVhBhJg >yܮX7R[Ď~@ wlgчY7(?1[ʾkdŮY/ծNʰe]Rv:+w>yr\.Zf׺N‚5Gr]qb}ب$o`ls܆pa>|Y׽zz/JjՎ὏qKRW׻nT}|0ww ]~.jެ_or}yWŔ6PpC{g9~+c3QYwp9Cg{ǎfέܱO:4)PʒRC0Л27;4]hlEBpC=~n-=ȱWg=}8z=gy�QҰ^7-\H"=_y^^x׭E<~b3e<Wd�a{M&1I܀hN+Wu&/?Ri/~wKOO>]vN<|k\7C{2mpb ً7˿w>+*UEq;*% 2˞}تOԤe )cQ=:3GN=I0-t&1rfE%E060^X$J1"1" ;|fy"ɓ E2Ȝ*?+$DXb!E%df_K49aӴbY:Nٖ94)눔ڀ "4!%eo%t)T* LVh$.3w#1bF^p X xoD$b`L=ybP] 5M~VLΐ} Nb0QAs1[p~H"C{'9${*@@ɔoId\:*_+8XjQAZYMY]ѩu(9Cvk SxMfG"r%ff]I WX{G(rփ9#T,zĔ&+.)9Rs$c,-3 81t:kb6$ Q_&IL Q�J"'Ry7W{\޻QۭC u`2C6H B#DcH2ƚ1;%3^ ָ\ge.U8eY@%=M"*_?ڶM41;%֋rdIk+CYi[l CuV28'b`ARf[V3U~vaqC&],cFƯ5dXv9g}6J�QUt_ҖcG7zh;#1f*yW0٥d{c,ٍbaa|;zb><'O�g>;3<˛'/ۿwG駟ԩS]G駟6*h,//3όݻyuZ⥗^gO^Vǥd~~{8y$ǏȈѣG-///km߯Oyyyy\5>YK/tU{5}㽌G|y?yַ5V}JK/{/ԧ6}okkc}g]9'h�H$ .R#Õό'&žƬ�ስ7pF(AÚFci8 ̀֐.�A� $@4h2e?de @J2<y=y9<Ͻ*˻%7[7sW)vkh=KE࿼[G7%۞o(Փ@+=9t(?P-,6($_/055)}]Fr!߾GvkpujN4̔VvYrM1Ah:=ۼi~~f_7M1;lj~ģ/bKΓOw_:yzw_<Z(oJd 듃VlVlV|L{"nmv:S_R-]+ڼ1y<<z+[& 4Ǚ?ɷy_hZ@C.ٹ'xr 5aYxG@)g�A~R-�2<Z'T$TXu (#*G{!D4FخSu2i+#:"^W0} ժd|7,ia>y9xc><MX{A҇yP@g>CcJ W:x&T&SkƗ%zB(V$EWL7E sΔJ>ʑ&(�:JfUG!OD2Qyp:9ϔs#x 'hWĬ@[& ]oN<#H=.�9s@h'2::M`Q@u*o.l<Bl6 >e .Ƞtc4icQz3@q@[ 8`4֖qqʑJj׹(!.q`l.TWRYdWvANU i!N|՗362B(t':¸r! ,bSZ~0eO$vߞd@S@pӹ+XCMw}묍1RP:sR=tm X+_Iat\ndxUJ #š$]ySL"Unt¬ר8I5>jm0&GG@\);UIȔ#rs1B*8wV:Iw:UX'F 3ׅ�AGL-EW vK{q^/},^yo@iW}jk^s~q/ٲ;@wO?Œ){?ڒY7c牙ɓ\zuC?~ɓ1996(u3�tx⚀d}W^k_ڪΞ=+rvK֊oO�ڵz>}W9y~_|}\W^ajjlcx;(]???- kxk)1xuInXWbC*?~6?p@u9s =ܚ9yj=`1Y3=`K8rz/L4;FS}ձܱbB 5Vܽ /}{Y.:\r~9,}uV'?)-\<O=uKr5fvq傎S\grnNrvgJogNN^8ϗ/k?y_?meg奵[?H&i?_9,o\\%{_<3ݣVlVlVݼhorjm[幽rS<C؎i϶V2 pSG8of+xz^Tv)bѽ"|s</ޕ<>oW~m^3sX3WyfLC޼zs{moi%3@d!8_zľ4a,^j ,glt`צSxَ}.}`&EW2\:1Ri_S4Y㹧A5 &ܰ%4ʑfFh0?vHbxvקY\Z씄L$qUnL,k=J < Q54o!E> "ΖN*t%?- S+BditdC XEȃ:EEvVpZӊXEPx6FL&^ p]G@{WbUd!pb_|~R‹pj[avSߚK99F!8 �B,-vI*9=eNW)W1敡 uVy#h]=쩯1{Y}o\U_W}<noNVPM6u-Yܜx bcxTH�X'y཯I�dQZ%ao'�9 eYFxj}ux VfچH3;ٖÙ�>aj )XX\%U1IZ1 KYWiV[d;ohº eQ&od=5HP[9!EŜZ~M\IO_;cQQv?]X/~ Ωi+9]]rim걱ZԴNQ:.ks޽U0uqvc9K0_e䛕V^,c7qӨKgVsEjx"?hLNNrԩ V+̙3ؐ�\ :ǎcIݻwO311AQ=۟r)xW{_5Ƙ?ޅ ~/[~!/_fnn<DE:u>1>LQ<yW_eϞ=,{ZE U? \pjɓ'@gyO~LMMUٽ{7*9sÇo߾ʋٳñns.]T?z-/w{NOObz?+vNsN½ŵbe>Iݻn_ߍrQvTwLLLTcy|r{W3w{O%=OZx~[;ssF!)f؆,؛w߮X2;W˝+x'ۻX`yi5@7&'`cza=]A%>Q}=^k3 rfj 3,,hetxc'~|ũGxO_K/Td*;Uz1da;ua9.߾]}arL'oVlVlŽz:eҚ˹Jxxl'{opu!~y~xkMOk~x�\ppc&m%?: uN_k;3 qپ.wS{j:B,꧆ȼRSrn$L4Sx% R;\ MV@>u"(Bd`H.NwgE3R0Ragkב#cR u2h ` MAJFt#۷vϨn۷3MSҢI 8͜>cP�'1hcx,Z4h d$G+B;ȼ$ċ`DV Gb7P>zFa&ߣ6t{}(#9ȔlCLsPr\u|0{ɕ�& ,d 0(왊Kaê CR ldD8G,!xu6t*$%d-*R8gC`ޡLyQsEe8e(ť6fSdmǭx"8ҧ!yvA2 x\f.6Ȍa2P: l0nj!OE*RJGp|vc�$gzh_A4Ν@5may-m# a9b"r(VZ@RK9(Op4y, Bn�G)X b^D7$P|A6HR& 6*dXQ^ż&+ptQ ✕5F.�wRP(йd2oxi^ڠ5^+)12nүF$_C(Y){CL^e8CW:zF_O\il"o/!rQ(c1/5*r&PXV#1Te�.2ʍ&-� :vY9Wɏ#sIBȲ8'RM[ ZDPǡv`/0yMX'۹A7cXXXG?QJ ̙3ݻy嗫&&&WRv =~xp1.]N[yq^ZDw:::m4;v(*&|2uiccc|zl?_W`bbbSdBE^xjgU .2̙3`L>}l-jQ.LLL8tN~ӹ֙;vlθ_);<66ooXS^NsN½ŕV>&j-o~޽gܼG|UlWS8pz~Wo@l_ͯݟhWw٦׮Zlxkq;h :6BC}u%}8קi5]X};e5(nNԔ|rg?ÙNsC<~�ً\z!,aL[:._cϱr~rIs?K2>>s>GkZOU6yMkNx* |___}sK-}Az|Kx+b+W3%#y&;-eڦ'>z3 6o?—7x|NH<֨2g'ϯ_3w[f 9Py_Xns4gnpya h7sȴMhZ$UIrR $*D3AJ [�(efv֕2q<$UBS/W2h}Gbc)}ʟLƔm> Cpp87z8~MR¦ >$w'ݽL�<gffNQXy(CHɁ28_2A{*z_>JsjW(>2SF,SFqosCe56fNUmh- aASlQئ18kYF^ Q}QZ0fC<v.A|͖|:}"'kL`S)m DZUω3xRA$l*V^%Y"#R)d!xR.^ơܰMT,YR$6FN:k+3,ֽjǡֿ!1!OlDeTu^FW.3AEWaBUL(#%[P*I#'M׶' VA<8os ^AL�kdPZ+/mDZo;*".;YF -1$q?5F\gɵ) ex=Њdd&~'JP+c}~u3r%k Ko^�[@ 9 m (s7ZαC(ȭka\C㫚ljQ-�"|U}_]–kRȚMʛG ՁصFfvf+_&v>mc<fĥK8uTWݻ9|eR<ӫ9>>@n{ض]ٵo߾Ěۣ>Zh׍Gɬݻw>5ꩧΝ;ϟJ]re�� �IDAT܋>rJo-�'fq+yر=33n>ݫopwZjbzznrN½ŕ؝(֗|饗eMOLLj6,,,,W/Xg^>&SSۦ{PXM3_\^}=]dz5IZ"tXY{\xo޾NsyzE._{1{_o1:}l߶ ]24϶4[ ӟx,@aft.}3<>ZO_ wKx*cV|w/Ԏ[dxjGο<>7~{h[[+|'Jc5rvJ:Ksˊ$]I;BM7{7 NHo;v~\%黽w`[x55I 4r\#jh9Y4Fa;a#*Ju-TR{s{)Ee`[kUsh!*tY)"gZ*YMQA_::m.L_go~; <+A Q{QJi2`e~nN]Bp2"8|`~~M!(vÖWOdpH1Šɍp5BS%uC8gq[KYB'3v$g XʡpԅyhbX4͌*_)#Ru`|RFUiЁ I`s;}VZ+mmy'>G(sgCd5 xǺ@i]- epʤJ&_IFWW ^ZGˡd<x 4Ơ=vܨ_COUTXv,&,pI(mV2ƐlBtr-T1䙉~G@fik݆�ZBJ`]ҁ 2ʪ&k(]S&u[�<[dѧ6㻑4Abp`QFGht封oojt o'em &L)@,8 (1]J&ȦK{wA.r  JY,1La'6k8Q6\89/UR9%z+]77lyiӵ ۡʓA}#dk{*X,j>׋j?4Őj6k?>|b?}Tss|6+6J|'g> (l _82rs:q+ŕȕ@ZLn=y$>Gw,\{ѧupv׮]k:xvv}+ cU婩;bJE4ձ_ɒ~ nɍ; 2WzAv}Sݻ7o4:tbdցGyz?~ƻs;i=;uy٬eH*x|n>Oscfy[th9wb039:W'gh(bpt! ht"ó)Zm(7h4 CC{Q qYbM=<&,j6⸑?X]Hs&+'~(a̾|K ^hK\Z?9Рy@}G؊؊X_~6X)V纅fkO[o28[X\\[ũm?j">K%yMYt(dF!>A)|r"@UF,L*YQ*z F`u+L PA [<dU$u'zz nƇ%=so:oRR(TxSulQ.x,.t@uN&ƵR9bvJ̿ TP �TbiБ)]\JLPx�t(#}ڥ^ZB%ٙ-4t^*2Ғ7ZYY+!uĶTI,XU=̆pd9yd5̮]@,#h* Z {k0Yb?"U1$1hƠUqn*%31`V!,Y/#e'aH]Fu"I_' Ԇow'a4J(-(Kxj5@ D:V4_F._ 49CCig2%g&dZkyN#ϢxW12g h|1!peϙSy.,@4Zlt|p5&1yWP%/ hV1(#y PQ"=C{IWN3 F=|C@G�8uD*.IgxZtQ:2svL]lC tebyNTHˋqN8_M%Ot:@wxb\z۲JsYR=,BxYOݔE <yO<*FGG+ovv"97$MQ'Zg=Ub#�jզ_W^v N8Çy' l}Z\/RnlԟVoW\g?ٺ wun3^߻]ݎ{!S7$<X]xaaLoZzqmynv7x}onmDo5N<>N\qWW|3Ô20{ ٵgSO0o39};)  Jիױ}} hC_ v 567Z3\>Xg)˂O~ L�@\^}rrxb4ľ A.Y~|W ~|B9c`0W|vwn&٭lVlVlůD|taSB\_`=+E_A 0Nb߿cgvZ?ko#<ʝΝzs˺Wm޺0cGVOsbPc9MQΓ5E.<(EiHٗ�X '1 h.j &^^iuv]FȊA Tkw;([42K` ]|1u}:4PxvA92T-}N+oQnԧV:`둿 =X#yP:�j_|K>zXg 8c@ix ŵ ݄{Ch+^J5@nq@XJHi__�p+YjcFҡLW9ob.}F, 2P#d96`z cj# P!(HI{.FWi~vPO"tA,8?rM#2EB]Wxe`%�WṬ5%CyyLIO|-"争 KH@Ԓ:2V䍜V!~FT"ep pq<h5[4dM1$;;Smfr5RdC_^YQdFKT u֊wub%^HCmC{t@iMɛMZ4LFE&9V9T43S+} !!:�<#*l鲍XRbG)[laldtb67f>WReqqѠ 6TmAB<�'E;حUduT H�BUF c;֊UsLJ2@=Fkc P{>K~"gwf=z/o|m.^ٳgהݬ ۃݻ:Ğ={8y$gϞ3gp>s=wG{}qgV` &;v`~~zQ6{/ȝ{9t63:: 333LOO366ƅ :tcwv�;|;ZfI"9lv+ {nk+U59ȁ޵֪6׫lT�a?8^N<s,qiqVV#g oo5CX1:xZ4#茢ihRѡYxi g~Y_`iig?<zl {?;W|;.нW!p`8pxoޜys-g ,Ybsȡ? 䊡\Pa3֕yۊ؊؊P$l|?zMIFs4\i:,־Ks{Mν YO b<oS;f 8nx?=O0Z[yO�ۙ:뿸=Ukwa06́< �\Sl&{*wZ -j +yO4LD%ؠPu G$?Ժ*qŸ Km!D7tܶ:]^a,{ VY85*U<I7& +ʌHn"b10͠=GO;TJr>;u̯wQ(_Y余C @Mz�$N?(6yfoVr%J&VWS7Jp*D6dDB]ےTkOd^:/CEv^M* ȭX @GlI@c1<VY @0^c]YN9K1 DI\n |f3*AiիMw[ANqX5A: 6@=ﺅͺ~e L&Um&s(iaWslhGuss֢|oL^1(SLLua9fSR YZZՉd.\؏ <x!֪ p.lMBi)'ZaZk3GFA4y�(JZtdweX0`mZڀ(h <6xʀccogt`iQ 1hYJB ^ٮ9pV)sBwQ^[E8mG HHMxk]tJM= 9s5؀[b?c;`4 >Os �^}U&&&6?ռU܌9889~8gϞ'?IF;s f;{5}F?QjZ2W\c@vΝ0;Rʸܸӱpsq=)R=z:__p1y/Xތ72 U&3zU~$ hH=InU](-?~'=lUfU򐎒J!2#K.<?K%YƻW6{G52̒eMҢTVNP7f5Ǎy?3lblt<'֎s΋?[qЪ nVlVlV˘:w|~]l֌ßY�; 08ON_y8a_s;o>3yзݩ2"|￳Oy;2~S<6c<\*P g9>3q,gߞDnvrt@y'*>S6yRv6@)}FȲ@2vs:2Fs}qmBZkj̝:L\:JIxQ~xb B)w&,97(\Ae2 ,D)E4W1>EP1j#xW`Th쬴ߵ~IĸLd ep�C6x'HWD=tYuDF+-DZV%֩ܰݕ%~m/b&ZTM`ާ d)eI"uE<q=S9FktQXe)G5T�_zJ'BeXkeF5 l2tF<$.ciM A$쨈�mOBE6BM�ABWNUH:hdbr^�tv=csvJEp`2yca|sB*(\, AI*2CG`,K_R'E*)m]PR+)-EQ4>(_.V@HdYdg;2]&wϑdNU|*s!+,KgsB1RX#N,21RfZ!Y)חoɲVU_e#uI${LH!k/_Fm29&$&)sK#,-OQ\_9Y鶣^|_TвF=۫t" wYP5_s5>V:=V_%5xWKe?#lO_КGd;oUQcǎ1:*26vSNMU:q}/bMСCx�lvuҍrz{6c5::W '}+QI-6;7n6e.{b#؍b3rsαPmttt|?~@vwhs08>wc3ڴV%cozh&6շhzxןT�N opf昚ߝ#<s;Mp3ݻ룯Edhx]vscfù~cr:m2:6sˎKt:Afgaq<g6}5Z>yGk7޼fnv|ԯdVlVlVlŃY~rz>ӏ-ݻQ{x/[_ ~ =0>rOsuOv=O{|;/}b텛i._cCv|a+'޿vp|M0r6TKޱQfҔ9\i,+y/U1D2ՈϟJ+&x_oibBVEuQb=TF pDAieQhM2:z"X A\D6pР2�9P+90TM@o[aa@ϔ"W`s#% %TzK裫ȋ&+_:HR#FKD4Um24 r-Ip&y#Tk-[J:;I{F8@v`ɋ}E`(8YQ'cEwz!!aZ):zvI)KayMYFP4>ı-�f}ׄXK((#dm2@4ݨ/vޅ.jFpщo=%lsYD1U@/!p]VV!X�ɕ5Db4[h2Mt2)NpJk)4Q&Ge9:ёYZQF7pAtNOY&`6ؠiy0,o,--23ȍEh)Յt4 / cm,-:L!鼗IdsXk��v%+\I G;%,JiaV2Y&h`�Xgk2>x>d9;VVi`hyagp`@ϨVdep2X ];['BtN.c)) uywkU["K3S>f|b.Oy͸XWHuoķ~?pkN}H;|TbϞ=3gΰo/vU_/Ꞙ+۳W/~;^gMWWTE *7 2Wz.j݌<bgffzGZ4W\YsI0W�� �IDAT(rʚE}ncwqmXucD̅ȃTU|\]9jpR^J<o0?;56ʡ1<Ķ~vmc.އ}6F0 ˒s( N:n,2ahh�g . |i}Y~7)?<Yr>ާ7nL5fLٕTCVlVlVlV]̽{q06]őgဉ˓\ݠ^o7ӟ]>6`Z4F|{ww|?—_o<;omfak> \.`Q@=8)8i3LEJ&Jx@ʪ' @߳vFS`T'!JYJP{Ek*j:68oLQ45i 65QdSj_DW�)D ȥkO@KF*{PddUj3!QC@zUAxbqL122D#oV@gb hf'u ޲ :/q^a 谖 B)VFO}ϫs!j#CN\Xp"+�zr\)`b|K![XMGfݎkQZaU/stSţaJkC(-}}}vFFIcL6,ޕ%9go7 <#ThevAYUԁwv?BruA*ȹ*)fi6ț &yA 0o5ihț-Q:C3z-JKpXqb `]qAP lctl'}x4xep(,;` y5,.,ܶ,-w]`zzE:%NISaqrSXGJ/TF kCӕ�AuWR:Oie%Z<MΟ!)EѤj7Qb8%}9F@ۛo}|{8K,h펭X&nF m'8BDXQ iɧu:AʂN,֚M|j3ӈIY׭6gޛ9_@` }݌L }%IJW�?яz?x`ɓkhA&'}UO8j{$pWƕ+W8{MOO3;;9 Ĩ>�7du?#r'&&�YLNNjS`OQ/phٟNZ9u=73&7d,\\k}7<HzRٳ).^7Moo}kU~;6W]6n3։`]wۦ͈.c[7N�s vqGgdp5@EaznצhZX y đ80#c hQ%ΰPXṉXt|f3gr<ڍ<g!ߣ79OPD[ p,,Wb+b+b+bc<)4xȽ*l7ν'W_O~K<n/O nhO8z8<??@#wԻ4M X+Juš5C52#c)`+%Ӥyt%Q`(hC-Zc%HB%wշ9!"g %8Mbn1%MO; 1*@uô#'|BlA5׆LK4F&QR3:oM'dc4yf0@+3cB99k%l,glvPK D0km+PMv酊Xz�ݢ,"/ޯFT)>DR.G 2*&4hgj9P Z@])5&񋕇!a>LX-eRW[:RN)*/Ik-;d,EW8δ}h" 3uaF 9_PT<kl6󆀢:[1 3\|֚,B,i  g&*TzTT o䑩dY.g92h 63 K,,8Gt;2_ɰ^QXG:q˒n0==NIQ8:mKm) OѱtK.i tX694P*#xwb<;' -(6չv.2iC3ia<̠ "FЗ7h4i<vΐ :Se'mj *@4VJQRV� bC|a~aҋVe&lX@p x˜2=ۮ#`o5JR+{+WZ)4^|݌&''9uT>~_`<c4 z*333LLL/?8tn779|0;v`jjͶVLMMq ^y&&&*wu?<m&''׿^wQ\v߹e\zW8}4>(؈?8VvMFΝLv/" '8qw﮶_ݱc=7;47t,ܫ\\+VclZwnV:tW_}G&ϝ|vc.]雍ی8#Z`l bfVEayy`Q|QćJ4oU; |Μ=C{iihleqn )qKط=ƹ:y be5(E]1uS22<ŽcL_Ω=h#Yw/md1(%[[[႓('@q-o^0&6gANE߽PY?+RdPʈdDZ{Zdw »"+0j?@ $&jN*W҉z'I̯:w: &HŒ xJ,Q�*f QZ3KȾpZV`_Keg|k5Z<C<21cbƅMTJػxbI@R,h9t:�@WTy%zeh>X"#"!nwd`٭ޯbO4KwHh<vmeup$Hj}|q?SqB�T,@BWTD;9i5O"Sel1Zr.ʎ(e~~p c43{%~%x_O$cMW=b B Vn'06#bjHv*k$ј̐eZ|C@}',-Q*L`"pp'y+R@$rҀJeHc"8Ԧ(;el9 >`]_:v< ]9*%*TIN=AXQa@jJ-޻[rw_fVso~-iZjFY؆X;^a61X6`#,xYb``DL�a yzL؋g1c-ٚ[}=*3/NR{d7{TeeeZPI6ogSPWKC4fbVv bm~}5d֊�%75ZI/gu\عVlh>X%/_" -E17BzcvMɿ°uw=aWd%|Sk3uC#dzjWˮ]S2{țO~�} 1ɂ__Cc=0… ߿|` z5i>s.93\U}y)˾6s{Ї>4m[>Fތ <̯%${~g~f eG}~9CѶ-G<3fnX;4ywEfzh,+fkk< /^L Ϋ+>lǯnfsНC$/"ba/ eAB!\i2ҧ>п~{yujeƧO][[[|UvkfElr!.\.h+)fv΃s:HЄfkkݝo;66盜:yŒUE_~>UߋTELU6G# zI&L0a„ +/Rys‚g^-ji]|cD~{GnSMN񑳊\$C<|9Qr!@y 3/R  H7Imπ-7+ڭ/R+֋ bx!r!v</ە}Ttݲ+Ċ-~7ш#RJQ A1z2 !K6>1sQx&IDiW\TO\v0^QH�F^�Hv!n\r%.]O`uBP^\W\@>\n(Xآ{.zRp#8H[qB6L9+[OQ\MٔjbĄJH(vE-gE)KIƔ5S%\!NMYGGTUjlwjmg΋ɜX IڊVidG"bdWY#d+~=G9rQ]fnRߑl*!Q˞ydt"$Dp '1Fvɓwqyg^zoLE-jYjfkOp(Me YC8 蠲ud-W2yQ7lMs,򭫵tU`'5PTGDT>责OK3Xj"Zq$׭fJ:VU|c9͉Z S$-;ٝ[P|ۤ2Gdژp.& jEGԃd*Of3RNA6ZB@-3^FL!ޥ.[Ǟ,g3=dEL-QAݿbjl x@Y /mB?7D=ys5KrU ٺ0vj6պU4Rxʠ<gR>' ՜m(gY=q(ؕ- S-q.UE%mj$dA.Q�8{ [cǎw{#-w]{ӧOOϯ}<xp xc_O^kkjmyж-M'> Μ9åKm;ӧ9wU~ifۼk;~8'NaEO=ʷ_qް:'Npj?XKرc<C]éSozꩵk2ǎc\~NdҩS8u< W3;vlm \=&^+\[9+^!y92+}Q<8>Fי3gX.?~}? |ɗ\n<xSycL{sk/y=y.^7!!ǃ?ՑF .K.]D^*%m^x%Ώ گRk5sR<#~lj)sq˻8-^~,"#^ċ/+% Aޱzol0;roc3C8y]<쳖Ք"v|Y*ul<$'7{m/9\ 'o`&L0a„Exb坯FM^xe8C8͉] [G6I|/ñ3R!̓okqO[m~+oyԌ�Y=g 6*ީ)kQW 2R @h\jji7\ ]&A#fZsgk[bʮ/j"Z IF&dAbiFq1DWTԵrsV;v >gZq踯UW8R9g%9gB=,f -k#e]36${=@ l8\G!0"iRYWw\~`IFZ|vsDO4 mN(ݲIS%v좑9шa2 0q>B;MYNB)&˲(CʊJvXh=j$Pz8IͶ[Xu4iڶibt]Ow>)\}Q:8CODT<1&BVvfJI}oUoc#]2e'bX'$rxhJQH)NLZƐwH1uҝuF,klΦiChJADrڋXf˦&S"kQOs!gsTE>'}$2;{G=-Yд6Ŋ=0!B )ղ^Km-$1ռ#5軎sΒwlI[rqws;/ +tM>_X!]`ۏGq槺m%W߉Ws}:GWsjkc3<bw}7ů>ɹ佽7)ncovS&L0aOO J?ܚ 7j?k·bm67G |�quίj9d\ ßf~ ϩ q[ŸOB*,<~ ͑4m#t]+bolD|F8~0wqw/w9Vعx;Ƒ÷q=e8I}?V[Z+Txꓟ;_f Eu-lD-w塷N* &L0" Xo{>ZI/oۗKί>aZ>?b6 7[_ x{'g??K|剖j�-'/ 0}-ce"ɏqJ\ć󛧾o-*w쟵XUoa Ww]Ok6TR�\ $<K+j1/h3z~88v YC,lJ=B6WEM F*$u?B)L4xuУɚf=r~]7Q?la=] bSi|0*7ZVJh }vѤPCy )ne'*Cu}ZV[V-$ B! "~BYy)+k'?qE٥ Ț=B0բ Z*9łxB0dԷx!l�}6X[R Jz>s9R{ĘL а:.]!#4v3v.JV/EB|zT)ːSR&DiDx*Z)bVh]!e!!4-rdgweoK˦v8*m#Lyy7:d9|h,xGڵb,Yi|ӀK'<]!@T =mc[n ?ۖH"j6r$`y*cؼ;mp^jٮALiomiflηh][.}Mql C9z=ˁfAh:]b%=I1XDJ9)roZTRd#$ hX$"-rP3�� �IDATժ}*C(T>}5չ5U2گe-_K[h]^kQJ-nYʏ]|wmK �Y'0u>ߟ~5kgVV?Źtݜ[I$ƕX&L0a„7{{rz`k-H+?7+7XaxPHxws>;|R7 WϜ#\]*wv%w)"7mrG8x`Ƭm9zp;=wCsxss. ^EO})> 25 'a)߽> ;V o*d -WDN0a„ 73|BYTtw$ca? /g !{'>~'^c/ֈˋ/[,=-tn]0< "FmOrȾ WxC-P7 BZ4eU4~zpYQdTW+MYHocˮLOO1gՒhJHhL3PTXﲑ-Wϓ$-w.)h)5WGS:J]."F�  e:: Y\Vh֭`C2ukWoW<Ƌ I}!s,WbtP/Y! /_zzkmjݧ )b6N0"TcEJ[#dLc͆Yl ~roixЈǎD&]fh  ԯNiXXLh!|CF muΙ%qԌ$q8 ֳ3:> Q_ǴPC)"}GCHpEaާ>&ݒf-1Kyde%iJ0Ayj+%>+]P5cs`~]v9E.+$rLr9ci[T)ڽrblIUY&v ;(R\|@|kSAJ«cO_"<ق dZ :zb$dKs=%uZS&R E@2F$k=<u5 ^Q]u"Wf͂Mevp00_׿t_u~z֙o-V o/5$"lk8{~o^FȄ &L0ư?c׵]o 6­Wy-*9R=,æt쯜>s"1ǃ~g;?B\0͸,Y.;Je)SJy`Y٘7<[iּxɻ}k۶6#,/ ch9G8{2.vEgyH+O|_<s;e$l V^N~-6ٙ0a„ 8q[ģqu -~;Wd8_I~8z4ѻN叽6}}n <,/͹ޏ\ oy>f|%‰w )~ʉR R)e.W,PxLvI}N-&##ڦѕ2r:,U+D))w}Ss? Ͽr ")WΕF/Ǩ+Wg Umq9Ѣd h0 dlU"fKhLEmR.,"wEpШBG=PTȸqwmk6ys0;JH Θf%ȕP]t:Ĕe.iv�_)qt'J2Fʠ%S7äWK-l#V:[jmU%YhJF3qqms^|9أ,a)&(Dlc6â=圃,`ߔܬ}y ơٛ̚KSdN;S|)u \ԧ+6P mދ1{R+Hx!s5kLv=bln~Lu\Z5+f󆶴ݲP XeԳq\USb &\Nl4^\.Hp&cv9Ӵ-x7Ml*g@c;Sh!+ 4I}1#$.1|bm7N31[64l*%SgC%sVpWhPl*L!F e+шB ,:^K^l-( ʈ4i,~UvuU^!F۫&TǮ_8+ Vl%bǟ~{j|Q�=G0i.J<?ᶣoaM0a„ &�`YozϷ=WǮAjsU_/^Wſ}Ov'xWU`๧?Ïw.HmV\6UĬ!q>of-;xf-7fHq@49sqO<Y7?̧+Kupy?_;.VF_zƲ'L0a„ ׇ;j#7qtmr]Fi9yN~~`w[)R]`sk蝏qx˻~p[r8{wooxc xWHTE"HUk0ҁHÕp|֞ jWOW#t5PcLm<yŗоǵbi)y <)$gffjYL͚JTF*dy4KΒ*{P:Ldv1%,>lmBAiT2;sf(9jɥ<H˪.lu"ZԷ@BA=[ ,]@2U+kc˹Zcݮs]RʩzU"|XrW;7-p[QPv<dFR 1*! deˊMqY<J>X+,nu>* JBDd8\DeͬguH!\pEa9\ 9sh#u*ǍFB)LJjиmx1[\,5MQQ#1m[GrjV䒇[N%Dd")E*6S;|yi'lFhf,ذW.'fiC3\-cȈ3H\#/ RF%xőqЦV/=xdu%{kĒ-h"ɑ\,f< +*fgFv9?t\MYZk܇VZ1e.YBRk>UqҺU- DjUϺ%WPcuECV6?å\^72{~Ws2{]'L0a„ }Їىj _IrJԅ1~)<x=eS$O<M\Q}7wt}{_JVlի9wޱ#e pM&ioAv=[yKM/l C?w]._<p,Ω[.c_Ӟuw" w &L0a­kkK59gf­Y`ϋbUuFvcUW?<g\G A3"/;(۬{eX몥J%;SM/Gy#lKHbbvwA<@2Ee';bwo/Ŝi\`YH򲀢Xr`3&�*_٧Z!Yq*qxQ�+6vnt*5uq%ι޵9Y_~\/e;5B^7l6ْ ["@)YׄR/U[ѪУ'+( \*ƒ}ڊr1,"fD -rfR䠐9*LŞgR"B=.GFZֲTikS]oDeiGrN%oV 匢fo}"6%b 3lFzRJf[ BW#t5 Ec"\rjMb+ٟ˻/?\K4F6lqh ۗ}M|.Y"5A3R9#\!%bg%5&slllrQ8ā4%x>ߠϭX@ST*F){w4UGh/ #`'%1c)Dj#B+҈A`HJFzٞ#vSD#,鲖\h-9_|HԬ &8>Eb*7E�gsN"(Ζ%^63h(r^ ۜX?_άӔϓݏ+"]f틎Yozq_kv×oXG9E86-M0a"?M S+73n5&~|^#&2w0{]syww}~? vw#ΟcooA̙SO=)fxqi@;k薝-"v5%[vx2w9~'ߎJٳpiW^llg%; bU>Q7C,)kSJF(~0BVoaw„ &L0{oq͈jU hmx-wOԕ"\nF(cuQm+ }p/^4ՓӢȴv:@VjkL lfŦ),+D;_t|*(,?LXڵfZM/ccuNu ypkVuE;nF]B E P6 xvB5WBz;T~OAoĬY vg8V>lRsNS'*sJ!}jŊF-^3fV[\]&^~Ex/2Yjx4j*꿘IQ=q$Rs^[1&K,Hb*$O@b�lV욿i*ljl@;bͦCzƉ;#1oI9x0; bUG$|4 fDTL&,$~%gm1r$F#9{v ^z.olsB 1ErIRI'D4ѴD$ӕHڮ:Ag\R 4잫jt_MǕ e^fy+I: YKф3quP 1aqYr-{W,mO^7:NWCJWe`=\?VUa4Ղ:_ =XٟSo0ú=]87_lE8v;G/6bve 猈/K8/&L07={7 nD}&᣻u|͉:ĕ\ڍ<Ʈe[yeOV�4dggv>7$/F:;;;.l)Y Ww~.r91g\pq{>%Ʊ\F^�K)_g~~ =/0+9nX V%w[o &L0a+^L6дMJ-ZV݈ȪY׭Ak<^s8wp(W,T_Yrxv],ysVb;\0XKr�Y)UY!Ռ:"Р} <\8{eےkFF) /‘Ca2N2YL%8bخx",I+0\-jbm%lZȎ@Z6hK"FQ8!DlRbggRy'Ĵ}{/0/&4`$YSI-N(`\`KTqr6a}Bb1mcSpf+_ bJB;sg9r٣q M2>6L2ݢ'v�{(3ph66upbəw}HYvh`+BjnqI"F4 ʚpVZG|,+%;Yh39n Y<y .\H X.;"']90kSy+d~AY-y5W*u}^[ǬmBVKˢ0uuKu29l^gD9 8`YlĤ7QT$qsU)!il9$播\UmyBhȁejkL.*+[_14/S)6ܧepP0GX%0&r)Hb[Yr cI}lAV04J^bKsFmwkc}ks׿Z/jBGwr9v~~?.߸jfom_Ɓz7oj[&L0a„ 7e3]vo}fWl\2vBP`Q~,e]7o{)Fw=ܫ*mg3;;cl*J{EW>|U4 {]df4M&c>k].hC+?}>k]A!P)i~„ &L0zܹF 4 TGº"k(`$՞b<cX%YʶT圖zJ,TӚʩU]|@TAs*ٜJpe 5Ҵ-3$ËZe ,<}_B"<% 䠨S#9^ST#h+;tzy(RMol+5Iԍn`ʱ ml3eOa#uDljmQZ:猔.ͅvrK. ZW7gX RN)9ĵ2{+͙Tԉ+YQD<|>md{~oAHm$hQ5ѱIy?J۶  ac/%":6h=HRRNEmY]4v05kHI_¬EHxo]gXJ)[Ub9>x10S SRE3@}onRDWz9qeV;[*4YҾot2@*#e߁d|D2x|ɺ+r0&2͍Ľ@BC^ bY9kZP S;}sP+qyBj^ef-Z9UO̢#e&eO s(1�+YZ灡8\xDhEŮSAziutK) `/R1 ʅյ}U0}G^a_;|vt#waUn~w+HifpQ8O~ů>I^.?/ww/}w*C&L0a„ )O4ps3>z5~?^X*/ucL^m*ZZIpˆ#W5Ţ$;YRe;qpy9g*fI1e{-æq8h0k<+Dx6l{TZ ?c?·?2{U劬=_='L0a„ STkOg7Uf%嫪h"E\9쩖MUrfHd\- B tE*\=yYAҹ蔒'(~X dM@IF 1srWOBhlّl5-Ι3 /chAN/_5]Q>v@/jğ+.FDa)l9S#okEͮk,3tS k\E;9 k9읓bsܞ^;9Uלs!kj88?�� �IDATu5]J'K& \AY#~P_٬4jRxog Ż31LjsfSf8}J,,mKnPv1w[&4Tf'` a6F;L}\ vwwI11kZ߂2(TSJdMkE|UcO\vmJ}Ǽ)KgmC r)& ޿q .czbĔۙ XDi}S]9H%r!- z2Jv Mc(1E4r;vbyє{z:@=n;x=<G܆ofxUvI9Sw#I%YQǑs1f\6s#h7KLL+R^+q">03͜.GΧ[^J&9o|Of,s2Z}%"YU11*~kAFylna>Ji#j 4a. ֏s۰~TWer5~__FZm[ {6}GASgXگ=*sc>V &L0a›O>Oqܪ->#Wօ ˪q8R]- l-1]J%gwGNx}\-6Xwb=wgX\^֪4fR,f)j3m f[ns.RyDZcicO>s zWdG?p/=;a„ &L0}4H"i##BPj&U˨D..V+7zP#|Y̎@1QQ!Ů5d:g.atu&q$G-EQ%B,*u"=,s(tv&\Xd#tN ](-3͢:De^~ۃ|?>Ğ?]a?9L-$fs. F<Y0Ržc8>#yE9vL1eqf*1@URˊP%`4 /k܂?ZTrGW]LBX2:cv7lV:px}1^1ArF5B xRlyƇL,ъ/bE4ͦ@ TYKK-%@~7ԛ2ɓՑq ]$1+%F{%H�D^!~9g!D78+ћX.tSsZY!L]ZBoJL 0rFJw@B]r$Ps‡AQb"DV GAf- $+9zBPCΦQW:܌elxJ&! !ev9e3rJR’J^pNޙ:O&vsvcKrL[\`%^D 5ta.IJ)ʰ9Wp9!bך".'v7j,^p.qnH{yj<6ނq)/$pX6@b] ŹlXne N3 sU2_#4Zdaq>�Y'yhQDff!9AeУʮSGBgF=O8<y1~�Kt/H|UsI;;4hg &wA{-5`'L0a„/pxPᆵ'??Sn,Cozo|kVycbGµ__oWx?C>;9yhnCR.,^~*rLp{; /qYMJ^{{ڶ!T 1Bpx M\A`ֶlmmq9r^}T;SۣUq-w|0a„ &LO}c|y;X-W{M1EBƌ1^d3qtJvTl`maȺ/Jja9jqȨ T+XU($*_"ml3'�\1lJWmqP:=;=^9*^sg9x뗺\!t&gmHQNR3Y̊Ѹvū iP6kX;F]q3sɘ4&Y^|hqQ-vУRLzem}Ǡ^AVʱjc,β9O{cǎį=sg~PRh*ZA#Ki4O@ bH9X 8iBQ|fM4&}^1Y&q% )G#)w=H.J`GJ1rٗQ#9[ǫ뜣]) .lQۆƊ*rQ:*j >Ŭ]>₷<VMx'xNmJgaOEm2O1 mkH ,LUSZ@.`<Pzkqhs MiKͪx@&&!C;l_`ooܳp2;; /%hٸD<b߫yP)B2ve梨selš,rr /Ĕ2rȅ՜]\ |>^ӾL?G s*Sh_^~$,tRSiV$;ǟr|G綟%Q+(AI<eNSˍVG^u- $Мnʄ &L07;N3>y1D>~{%kv+wn8Nt<z<x[_WHW% U^cӤq9|Y2/]"H&Y,={ۏAΙm8|)|P^Sb\"Bʉg$0r䤄`ޓwsɷs~q{ɻ{oQ&L0a„ :,3Jc!*WJ+>JDUhʼ7S |$l (4\6H|Ebf|37o͜re\=V*Ukģ7qa+#ZiJښFTuَ X/Y SAbWI}3-f NK.Ze)D'}m)!Y:7TQE}֡,@U;]D-Y1fs%Wʾm>r"Fb5+kÿ˘Q'p.GG~ +Ar; PlEW\),W̑2Pq2 (U)6Om FnZ-c`7lʑDž@q.#fSJijqy9ALbIFBiIJV)n Ylأjylk-i<M sx7sY麎؛PS7H\TԥoboQW%lvYnּR5G7L:}}…fݨhD-dhR%Lj:q{ƟX6frK̝':g%=I$8 @ Gřr`*IIZr@ a~>s&С-;^]nGO B+#crLx3LJ@wkW }X}g) y4G�*ܨpuuu܎D,|,ѡ8~XW_ٿ9v\@X98<gI@㋒[NEH]# 䛈0a„ &L<Î7)7 7jRjGoyo�/owfKn;hncg{2g Yl .Yuud؜)[pS+m^Dh�҉{㩗EӵZ~f}V?=mXM0a„ &|B; p9!8QKGD*'R7XH B6K$;:|TS b(%h%޲'Yu-ϝRHa^)EwGRaȚRKKΩ{gS*nVj s^d/gWWj ,/0{p3_ *K.%N+PU_eZ3W__%Ko#*-J%٬e8E6{آ:*xxdnQ3kk펪tl*JۙuΙ_zμ Xry5RH(;T\ #ME%)X#i/+frm2$x#io>ڽ휑um:M޹ltfg!' oˎ /%bg-]Kbk61,Ub<llMo6v/lc/I09#4w-I~'">UU~Lp8 @" �V/�-$h'h)" !fzUS][qʹ0s?'"ުt 0p+<q}}FW& gkm.jl/ב)8km:${bgzX}̂t["a 0D=o0{_GfҶ)Kb+ڰ$͆)5Jؤ"8*�$Ak\7.˝S5J*\N[ xz3egoR!۴/ӧ4;簴G^HPb�C>xL2~upܯȄ4:ÿkD D;GF YBԠ)7> 's@6Kݳaہ* P Ȟlg;voVq_ז_eU3?~tolkk~:Wv%}Wܿw7xlxdkHi3QrS~ E%S6v$a3v|M_fizw S?We%tlg;vWO�mFQ;3dR*xP{Sܝj.�vq!F_E*֙P4uP$i/ަE8m39c\"y B*!`\;paIgEIqTtvgjmH%;,B= 'weBF g%:4'gUQ=SpAzE9$$vx+oC>3nn0tbfwO"/�Ez eA%t3`G&ggM Km3:PrF �S)�ÌLA�n 0zoP,-Jk85mjnhhKybݯ~5e_{<{Kn{JJ(p\me2*grqq(0혛T`]uFg9Gg߫gmjmVDNWVQ*PQu9T`nyzU/  R@GJJV21_aSI29T1t>sJHm)~&^L1Sӄ0BBi1Fʔ)oes#@H?:Z5"eZ6|w7^]g\'%%D%3 Z٣t𗜀]/QhvbHjC`9^+"BL#vSS+I$=)UJi ,#*+Kxd��`RB8$ShYߖٳlg;vmvدmD>zn<zx<ϔ"Թrߓo:O Fںl9<sybP@mޥw! 2_d"¿oPCg;vlp_g҅&vCS% 3c.`5q`}LQLI9Hʮ\h8&c*V+H0 @g f6;raI;%].oّp'g.ҥ(-_V�)a!S怱_=б0`�b`SZ�mjZ ~{F.rk>*PI"uūm>2M6>|0~ST 8œ5*n�, 0Wkz~)e*s`g`Y: 96[<x0u VIX|j\+LHXL\ ..x?_El6l2M sm$EB[]5 .)n ՚3q jS.ڌQ[cYApIck%I|@)%F*ܷ֨ҌzPY. `Fpu"[2LjI((Ք+zщh~'@JyH9g�N9SJbp\`JvnW1rIBU`s(Q2ڂDQ48I|zH-)˖. ;sL+{`#*d(,8ch1t|w/tj.FNvKgj.K3[a�d>+Fks鉜@JA!$;۹icȖH""rs\Bvdvlg;v/_^{_×+gzCo s /vlGxOҴQ3O6H)$f{/ӛ5+X,qm}%lg;vľ?'0Or!LD s �%*8z44 =5;\<x>_>  )݃fg#6%޻ryq~ g&gƛ :PC%BKKn`' W*6|BR0$ybt$KDf!)4~D`E;k%]E%'t : AJ�ީ ŜQ#2%A%l[c<z??LI$ / Pk$؋,8Yӝ5<ػYqю ^)U *bOثL欺3f"Ќ󚽽s 3c[k$ Mpq|w(ۉyqu e*9k[CR�ÞO>V=/yqsîvQFk&1ZoV3D[\ߥ{nXx["ԭ֓@l p:ZfJəv̔3$iJ̤Jmz9>z)lo6֘DH)SNM`rS7#V@ s(VHiq֬I䔙vSl&./l6P Y(lS&ġU4dڒ$,LD{3&O,5exqDa[DŽ(ls 7IIblK" ֔bBeέ)1j@p#*|EX"H 4?KR1M=|a4d w! icLpQ2+ݷž̟}O>q@N]8yz΀vlg;m!yU.}Fy=t莹5^{M^^p1.[sF-z."r ߢ[dzlg;vlw8_>y̿?>Ϟ ew75Q6̓K񜫊utȝ)Xݺz;p͌f5rf\^o_;pgLiy7yyssPbmfDVx V+]~TdIcHɨpVR<)FEIgdK/ TTK7 H5?t+٪@tBthsf%i[J؁Y#d]U))>?+�0us2ԼϦ�NI�� �IDAT,2]hOpvZ\;i0Ee<)oM)%$eg-XCX9MiEɸvVeoi9$d4eTag^5<bw8R$\X'8@xqu~bURJj&U $ݼ6qLmJ3<W6Q7s{Z=I0siir₩L:Q<$D)S ZKhK/Eܬ#WrW܇y򼍜,9sT:f�| T�%Ml`Pr[s6jfR2$ib_:cdJ>M8QI=4aޓX& ">gQM\H 9ckx꾹pD|{]+69٣K2ǽ̺1",[HK>ќکo)X%.81ݾ1I*MwYZ~+L1q\ fG eπvlg;3!~{c%(_@6={�rj!$?xG-8߳1Wno(r/_魲<| #Gn9EM]BIU] i~JJK[ߵƁ{Drw{q ,I.Ս;,ny~öfm|1t\ftgf%'Ws n!ܵ'Nb*>wY)gJJGw.wIE֘$$ZA|9ѻ'ߺ)mf_.'GsǃNMehvO鴕!YPȽ9\Lu<AcҨe\N콸w^'F c}WJSaʙ֔}u3.)Ù /Eu\f'zX48B3 1zgg N=~<ެp;q2^CtuXJ! v+]Koj݉H*/7(C8�s^ K DA 5MK$5ey#VK\ט`u}?-QU7]lw7 r>u.f"klÒK5Ԡ,ZzM6,?UtTĄVsB�V^BiNrgdD l'aݺ-K@H rnFSg~ԘRC}D-I <s8\spĘKrZwJ-65r߭Y$cyV65;C%*{Q7kHLqi#R@zzG].Tk?C z .w cm[ML/4@TVwę pٓ,kH8˘oƶ(Jٷ7@PŨTCL`Cܪ1K)$Қ AXsl)} ZTBʸ9sZϣK}h7{>ZkY^iK zF[mS`fLĴ0m2#kJ c�,|h=:Yۙ d6'd Ib&rN>y.5<M8|_3WU*)ԪTI#QPK%^=})ʺ^ΚN!01D>c 2!Hk{�R'lΞ-xv95"<3g4]pd!͆Zxn.rJ)K犔w0fk #q2Ƀ7:PxxNP4M(֘R%eSt%6ĘBTr&L̤Bj{g *eƞٱ)!&e6$?Ϟq5_95R(SHW4S!)sj K}~dt~ZI�V{{nt~>#:s<~d[#d!zv>Wy-2We T&HŒ Y8D*&kC. \ٳlg;v-q}_M;Y\?�PGW.fڙ2ٺrsf~폿%N?ȝeN8q;*݅Xݗ~ߞ"777A$> '-'YY_v KVxnwklj{#�W#+w"GՈpv.ke}SdWo3�tNAc0VI%7� ThZi(ɥT 3Gr,cetVCbpL]'}.ڟ�͆3ÁJur .7{<q}Bӆp}s<w7yghu֛#9~9}� Q2 OO8:jX9zkNK_cF@9AH-U's];Y0֗2sp`\խn`WQ%LQ^Ũjh\'3ۢ䔹iy*;ѱr5GqrwaCt23͆λ_|˳6\ao88b`EdeAQH<{LHjR!1 -! Ruoj/n6/zyNC(w\"$R{/h<â*oFsxtBL-Ք'*eڰIZhHD*BrPIvڷԔl\ʟgZ)Q̃SHL)x/>@L4rvfD+ մ1Ȓ�2$.=ɖ˱HO/�/>s2]n`5-bI'Y,tܷJ'"IDjNPBN4)jK9g__brhO\_ ίRKI{t|1qG~8KQfX$S2y@getǚڳAՖcS%͍XZZF;s~HvLL)3@�z_\A9eLyS|a14FjS{kX۴̢)SPk0 䧃k3TH>m1֜EyK\=.0E`kH%Dk2.q}!]OR9cGUUR�@)gߛX6Ʀdɭq&|Mrskf�TN)lE ૌ eA;s߳=:y .H8܂o>Ok.j?s-encLqzZN}Kn_GtyN1YK?Ǐw$s99S:'3ǪVa!z#(Yobg@lg;vlw[?elW AO${UJգDhzo;el8O/wZȡ P=-3^Iy8 }:J˶L֮�0]:K];?nnwAюʼnee|C:~Ӌ,ο;}֙;٭Oo!~bn]K((cAFUh#{z%[&Iܓ! g}31.o0>=< /_<iHرG'ƿuI IA՝Nε$]]_}̃l _<E_jZJ,0gpFaچ폘ڑaw˯22qyyk9[BnG-[/%|q"OCש?Х3[ ɾR,s3<GԖu5F,gi8hi)5߫;OѶU59kZ}+,kd8pgz64ul4 pg $5XGDNIWxtEGeysC./<~]Ųxd�6ri�eb%iVAWX/.6\ld# ya:Ah0}eȅ)iUIx~ӧ|9՚សP�1Jxr{ߏaHșA[kƴ֝G7$HN!cڔLWI\{%anl3O톼 l^kj!p3EoWC8uflL;>!.}Hlq0 S382A $ #&BoT^!i�暩[sHugb͌#ν-Ja=Hb\{ܱHb|R<\1DlFP&wH(B�n1` Ew7ϩ] b-QJ�Sriþ:�r43~Sֶܱcz$1:<l/.[&*H%_wJCa 6u%CSv0na&$SB)3rkjJ)Τ&S_sRYNj6Pim&IrR?,F2dm h:|,"aS&\^/0vE )m̓Jk,uDll`.l+sbuZ}vf-[R- I>WIg>߃x1)I"BN!xPBT׃4د}*tg;\ $SdZ,}KIUؓ9k@EJB/'v<~|Vn7X=`&C'TH76bϧmW~$rGtlJ_xr8\oLȧ -ݲJ=I iFN2ןIQY[??/rfrPPyOf}wBSb]]=ǖ1ejb;g;vlgӺ<Y6M2g0Pv/)KJq܉'3q;wQ|wݬ'A>^XFOͫU[%2z>u$㹭:S%ugYw(;h-Xk?5<ᦷ~urnշff$Bmu0W9gLk C~g|w2la e-Ը| []>ުp#x_UX8u$8^ҁ0ˏ|g/.\o; (ŝ"ss }ܿOO>ܠ^%ڟGQ8ԝ{vB<}*֚'1^o,.}r2D.x>Rn8|qV~cmyo񁫽D TCH8Tp$XUK}AX aKhwtݍKElGDLWQ,x}DI& TVpbFY1ʉm ! F�Hj(I)^+hީxc.ޞY1(tn3n5,.8[иt8~ɍr"vYw`AX9%I\\\PM8]1eqorU.s[w~^{ȏ>~oث;Kg>j!!v9 *m(AygxR,QpP n5eZ>qfH7\]]'<#eom�iƼ%Uݔ4σA֔hf#! Zox@.33a$ $2eTPQtVF\h¥5UvmTKXNlB~cbV5R*l҄f̮9bPP>ռr<%S:$vV`*[c,فdo0jIF(iv~:'pK>΃盾DGR)XX9ӯ-99`v73BI\" LZ%@xZ@,$:t76\}0@%qm7*ooF'r}8pMZs 9ȺgԺ,tӛu} j0Xkdɘ(\e�Nea PD]c$@;͙\p@ٲ e`(x)%rސKܭlQ̵aՁiʔV6!;UCK;v�d" ϙ r<pD*^:;`*IP7\El־R$mI2kn'5<LMɥ`%adG#DJh Mkx[ڪ 88fYZJMq9T$j8r*aЄ. 8C{k<{?)y`I栕תlT"8N.JFʆ0.})'6&g-Y Ϊ[4H)HR)<ą ov0O\O>=\1{ܚX̫Bԝ<Sb> &3'ps}su) :'fKBZMp2j-j}3e1YsOQK4w5VKn!{Y]7,ž_?N?wP[h9skyA\*ϳ[)oH!5HU]@#w%QT#`9~vdvlg;vI:;Ñ5းN*!Iq{+or~pV}{�G5={|*a3|!$TΤ-ާIRmQ<z؝ ɽT"_5�_:,(HR.8!Wnv {tǎ?Tu01@W};._Htrgv/~3՝LIJu7u/vy>1s{tgP|iP Wn9 )DEiG`F$6&_W$mXrc{LB#,Q)ݮwv�rnXj'9SJoSH۝+}8&In(у1gg! ;,IKޱ;ͣ{wOk.ghx`ڑz`#c}5ey+MtabNwׯ vIn}o r?qO#fuG|?7_g<}t f|o6lrpN3S;PZK4{p,`C);3K{GHـrM#6]$Xve౑[cmǟ~B+35;jB:gİ" eؠ5jhr(~W5o|- ;V=PV).Q+† M͕Y&fw暔ћmT6³R"A'>-5v퀉 *f6ZԌj)'t8[>kEaO,IF `+15jQJ1>q]F^R <3>$c H9KlV+\ڬȾ!x->3>1<^ DK1ٮ$4ۙ2pdv2ҧlba9VEn w?�??__EF&HxާQ. %yѿCHsї/A^;ȈՒױҬ.5YX:$ rUy'k@qs}=оc1@m"Toleq`wv[θiJ=x)Ten(%l:CuNMA<_gk$` YP[sY)M8x) V4yl`% zS"jXm41fC@U$5j5_՛z�� �IDAT{_ "2ѲQJS;_tQ__IT )0͔)/,0RKplB{9)^S*,<;[~OL8P$3͞u fmk\1K,%Ǫљg GRHӆ6lw^+ۜI 8(Mf2]"3[C}د ѯ_M5ݘFVscww 9% d]8Kf*Ńmb*9y,;54̹ =oٳlg;viPVYiq4G�PG`#'>V~[W�ݯcbqF)f@-/I4 ~/!}7h Q9_8?ʨ wy:t2_c[j<Vm"2r^~sIωStgPݑ&~uη{OH͐t0$hsM䨶,֎_8;E^)SO^J/sd@d&DJLUd6 v77ÙJmO+ y9'׽w'\9%l ͺ|Οen@r uB7i0K {vgp#%<KwMGC˚խ,ra.KǬMN\M_ivW/X+_b}S™Pu~ʼnCn'si%YIV^xawT@ZY\ًG?yǺN K㝬ޏ9jF⫧I5&gS%qDh"i5ޖ]P{�̝}h4mZBK &@gwlHo'bF%ĹBBDf&!AkYNerKMIHE( v"GBmA~޳Ʌbvz`ZP ͉ l=j BkC35j\kIV6O5Q r*:@!YHE(D15ЀE %|x,]O giF9vRz:@&>iy!w@"ě}GNtzGpe~g ;I^`Hg|Ycwd5,}k:ِT .|;o17)8YS~ [?vRwyXU]%2$K]:g;.A"O)I0We\~Z YZ~U% \l6rNfCƆ I Vw2y rhzNLӆiٓd$4mT<H(A|Lp�cN&\rl9P˔2:>A0Ў1ڍ6IZ\#y̍lJٸpD .oXsџδh9 +3S;_}m~\)B z? %dP#&ok_z/$69ͅ"Uk kbTkkK^w||C @:5I9jWYT9Ճc]S.Ҙ �Q7eB8\ߠ+bZnɛB0u?]`{@$NzcZg7>eMv]q施_p|c?x_+^b.`.]GNՙ}?$KC Y=`ae%yi0o΀vlg;4 V;}?V9̴H9yN7k'w?9ڥ8gׯHGN_湿N<RѲ�!=^}g?qůQw9zRs2!fu (2LPW{&Bd>:BU2�D܁NJxu|nawD_VzWZ0\} S3'Sjmfv77Ȕ#=}_5 ,B]x܁`Uy䝅p IF7jy1i헎nlK]-Zk\]ԻJV#E 4E\^ON7;0LSbǀԝY)G?zv#s9RwMZRNS؝/ruQ،_se{NfEG_Xwkgwǒuջ4o\U9j �aV`;_[w5iJJw^k'_σ/VS[ǻ )P/lyv=|鐼pދ$(> Q~GOSI^LK.;鴜QљҐ]pR8j[7>],u "k9.{Z8=DOT%qbp$~\\=[!*'¶y=r 4|a,yU!B)( %SLř;&P6.bËkQHIb6Wo'f7=u�q;8V6A)_{jqJ2sh )$'Zs2򔃥.SU oK9;0OALΔTR^=ԭ3~ݚ ҪAJуUDU\�X�ůisVl?>V95ZC{.9/{MΤ_~1?K|/8R'H/'Q}dBض=USL3C[)@`ZQ`CL@LӆI:<qv$fڢm)EP=JENĒex^w0l$::fa %<%R)4kVm?:j\ myG_ۇ,j3 ul{y)ְ&,a9Ks%h |41~Ro*m�咹>͖ObvsWJV]Y-]T))k?X?6TLNSY6$"HN:ml&r0%%6c&ŒbVBhk|,cvMy\ ˒GkGהM9�"R ;[4e xPٰݖJAU8ZSoS5Z�x['[@/ynj˺rlpy#Vĵ9pZe= M#H%gܕ b0 8b Xh$xFJ2-җ3 {lg;v;-d٠Iϫ%X2<Ԑ&!s{ _D&z9EI~'DvsG?#?E2n%0줃qGlqONj"\9E=9|ugtG^CHG9娭<qqwwc阈 k}r:t{#r7p:I0 NϫhmHmA/So(R d>3^<?ڻg}aFr4' #0";(LUYbh+ہONR͹!>.F{-o+hk|z@w`@%'XJmxՙWl.[Lw_v:?vؙ,K e]##^SL�B2U#iܷI<íqiQN֔� g"sqq)^UÉS5jv&Ywt֦v2agg)6(@J:WS {MGLx˝λWM벌gˉ'] Ga`>HͨIx^2CF -PcCzi`1Seue#)E~;m2s݉sL X 9. yԓ&H//qb$S<7p{~t5 4\b� ;CrPJgSv1;vkfB0%o~|ݰ5*g3$dQ^-" kP0VZShC¹}!y# HUZQۜshX v<!/IS*σ, #VF4`;YL,(ͻSȤ$g+Fc{" 6 4cQֿ};ғt@dOi}2IFOHsrWu8ќ? P/?F'EuM{)p|Ȅ n XDy6(:X4G2t]ޱmn\!0)l3%`=63F=@ǎF)wy'>Sw%(3/M#̓TL gKM ^g}WDΙR 5j>D_LrδBChCfUxNbމqYwcԃZsI$s3++}x5=mǘC9i# Y/xw7=/^\E门jS<S>x�*˲7W#f�@g<{:ѝyP܄L~)ngOqUJFɠJjʠR}?<0,X p`<zLO`[. <WjchݎzOU&5ZK[jle[O U}εz$h3YrY?bZ_LU_~M$qӱ}X^˱߱<U0X>[jKਙ^RN UoB!A=ı0ozJpߴٳlg;viSPL9W;$4EoulL׹~Z1<Gz2O3\glWD 牼_P/`!:D8\K?GG%>܇uګ%Un `Эnf+.w A:`:;1 l,!R19rr熳HyXN>rxN�Xl# <Lj>.rq{ܥdL?ˋ O<7{qqG~07w/>wL~`6w$9{T|}__g:D Q=7>0ZLԄd|npWI٠WϞq̑3 Wx6=yp|kPelmH��ݏ8:u:MRJx j'cNF/�ַh0K-\g}n<̎iBUvl[ rgwuDRGF.0eRb!Q?K:!!Ir vob5/T dVYjN:[)v|khEsGD,ϊ%(H1_ؾw{ΰZ˛Fn>7x\:[0ξr x=bH_nԌ=mer܎0Y&JN Us:Z:eY]}i\wu\rHK.!"$Wh6͕f; ZH)!sEgSC;yy> Pg&=ڨ<y/%7W`o=wLmJcX N޻ܖ%][YUYYnXL[6F !,Yd 1SL FvaG̛׊`|ʪ6fG{^?@ ૖@FPzH6`ͼ |/q3|'MvdCY/):T&"頻�|b^ [':9WH6<߰$@DxJbgi걖EQRsiZU:۷{[uj) }k|1]!LAs~A69Rї-k%@5]Ato�q-Fщ|zoE{7Hd*:y] ŅB4HH㵳B^W=fQs�\{ 2n㑮�BYT2nͱ].�sC`.'ܿ|8P`eO@E$Jz. n(X &*(7MDO̗x_=E$f}cd.жTpQ~k kc~ۿΧgӜ r>#<zjH&L%zhm4Uê%8jx�m|9Is{ZQˉ'ϞRimtRQm"xwc22b-=4k1"7v_;m q91e߻)=@"5l g_y_ Oo퇏9:, :\[ϞG>=}vd)Cun Yb5k4STtX;"T?g�|h/nF=-ܘ[skH/+k}2~v s.&1;=+k}o޳sicscƹ]9g/7Ko5<G*ͻ]8JXatUnb^snnv$C=j@K_3jywNzR݋90t%?}“gH6tXbS~-9 ^uȹ8ω}Hg|߀Ai2$˖ٯYW_WLAF>-Gm- ֈ_?�q"L~4Cd>!(x8'8[Nea/Z@ͺ;`tL%p6CB3Ǽ e/5js siOyO>)?#g"^zp𕌂DILfs|_c#>c. IDC \ӖʧX W|Pp& nz�{:@@<g~FueݹՕECy^ܝºJ2Pv8ѳ:+9nsϳ%ꥳ,Ӊxb)X5wW Lcm^dav pHGN2rVi/lԒreE4"EZ030ou2^$>_FwYWYX~Uˬܩ;ػ,F:G0sr97~]u<W5dd); ̘7qƑ3K`H]ޣmٵ}-5sْCJgPI!c5=%U!rX&Pր(NG$x?ߠzgǙIo0Un2| a ]K5 <3&H֪WLkv9q\<,9Q|{㝷gDf>E8F+Ҙ<sJtL!06 *E | ܛ<Jqb钩1l4z=7zH up3V9X9 ,(H /SgH>ӑgiqx<F縐Ƣ%$pgЂr$y�.,h�zJ4uVG&P\<YϾ9W`WSާԾ`R%%羷42<K˼ 6rr_*ZL7s(#VBbXW u]7L4.5eNZhV?_m N}Ub{  ^=s&?xD&N"SFI4'aEJ֠L?~9<xpI ǖ2-eMW3e�� �IDAT{; +cs Fb>3챨мQ_-r<ܘn>}!}+sTkr }F8`{CHH]GPǺPBQ rs᭷?a9-R8N1Xk<U"!sPk%UAt ;sm35h0EwAdد3JVՠFsUVfR(bK3>>!Oh< Ed^XuX*,֕ 7-GO!(]&yjK,XsY1T,xkD6.}"k3hn3hc%Džļ4B8'j:^ \kAꭸr<ѻp-AsߺV8M`.Ӂ7ƛ?rw~>|X`; nnUky0P3,4N} lٍM~тFAyWyyxvfs;?8^<gn:&`HNJKCR.f/̏b˭N<|Ǭq!i}T3BK_Z >ٳggGp~]_3]lA*܍p'UI8aНr_<Q#q-LJ>\ kS2ZEaɼC4}lWe"'R<^#$sEaQRf[Ax˲MNؔHF]fGhɳ<|) ^7 ijH&+`8G[u_^d�ݩ<D>~!sw>^UN@:|:/^yV&1N˱h{Op}wr'H0<~^j6W;g`bW,H$1DS4׼;bTѐD?`%xsKݾ]3Xymm/7yz^_=uDlhQ_uQ7NQVf}{{tt :=f=+3ϲƸ3%pqu w#wo704",>9\-Xox`+G?'BfԛCȷGHe ٟpw%Ɉ{Ø(26ȧR,tNV Jr` H₉$3,%qK_ V~2f/\꜏RڸφI˯`<y)2%*+sizcyT(4=-{ї( Y�y f$P*1Uo\@ M&Q^c[u`;5F,ͤ:<~:I:uPL& -Sq<3q} ,f1'؁'>|OǚT1`'Zˀ.GZd<I:6ݙeځ6HH*їܝ'P9LeV>D]@mh)L%yxx<Qk{ܶңH2%s2OtjsO5�eKY׽.$}V~;<|)<sLeR=Z{5sll!{7Udö�GИ:% iNp96nY(Xez=R}v! ZfN)9ޞwr\+"!UCx~ 1#M2 Scsi8BӅoY#+^ܧL&qn&X̩4jqd*xX< <X#u N ~g6-S+\iΜ91r̅{!ōZkJ!sRhAKt#s Oˏ'd5?˹cgnu[|_6q?3f?K`/~կ/Am'?nnnWͧ`ī_n^|xzJ&FKGU29Pk6qѣGV#G NsKg{ ^�ZlG.iY߿\kO,իn_d?)Q<*GZy߻uYZDΝ�حL??/̷-=z|$4r:nOrf"I+f́/eN'>|`R Gb/chq+MOZ#cyQw'y:2_qZi~x _M>`N94Ofg{uO˪wep-`$UZ<h̍olx}6$[d 7?~o>ylN^WMTjFwwke=v&7xgM%dOu1|ŕx̹kcu;TPǖ#�>#+X$\4oCMߣP/+J[ x㏹}v˲SQ B 'r e}țNPtrlE#ԢppfLdnJV2N(UaE.^hG;{1 6(gϔѥ Z 9tsqLZ6{t!~y֟$!99]=@cWu ,`\5Ks2|DcvSgkhphޚc0["I tno&wyDoA\*Њ&�\l:`'#LʽIN*)qA~juyТ71O<.Kuj4.9PNU@4BAix"B¡(4s|`3& So&1>yp=y̓ds Pשd�mq,#̛>"rδTS1 j0D`3z(zxl3csNJa8{K ;. � f6$>9y5q}<PwPFs&._d<PcSEyI;x-u0%Qe :ÁVOy7�Sxa#Hd1GTVmJ09~G #`n�_TF}cmcweNimϠ{g Ù`&tTIE8A�[ y/csށa rz>iPch9_;T!9�>})jNJw�WnB=UN2MH Ǟ1U513&(g+(�kmy=Uu91eҐHۭV曛P8bn)H."ѮP͓(l'vZ@ȥ9B2x!T K=y)?iF[L<沢4 ,UH 9Ű%G@ch*X*pD |Ls4M(ӉD lva5Ѻ:xG2 f3!r}jD`ATS*"鍑xm}( vzփFF[Yξw^5y8cG}`ޭ;WRt*4wO<qɓ'Q'nn]/U~拯__;BB rX7J;!%j|!?fQA<>?;s2-`fԌVݺ_n?mgo6GS%=37փ#5ᓌ g]?uԁXo]GR3]hBQg~1Nj~vvqwψ=6W'f+HNiД-b{�fÙwfbEU-׋D~=qL2gZs\9"Q87<XJ&&tX8.9\+@t^갲඀_y+#s1͟Q/qu}\:fNh�8|KVP]"axӚ]5.ĺǎdYzTA)q(K~7KGtԦԡ%G00cHt6kLIz^/]̝>P..Z#{;z^GA5VdAx.$#7"ۭ]tKmd:gٺZĐOYzs#<WO/ҞvN="%{`E%C;"E�ϭ0’N86'07UвIsJHFCP&&E 8 U(s g;Fm8y(@̒j$k5":U2b,l\ ܅IK!u nY, mޞmS @ʲ.֭eE>[ߨ~usq{{o,J_';nO@읧V 'V[ BJ3Y5鄊2O-΅4Q knj"2C9 T$qY9:Fk3SʦsH2+괿yFaJpJsg2`+R�`V�ўc>:�#ݵ?^=خ}v N}0""]z`z/=DnF0R0Th-*_{4KYr>jP4%cwY[o M~ۘNIJ,\1zL"1 S'b9>ʺ�>xW}}*y}[U-jeIz# #(p3nY5X`b}>R`K"UFڌ~ *}I0"ّs)p&q05G,*".U,تZu깆n(K]}I�G,Ԝ&0rFDϭ^ͨ5Huch 3bk-Ƙ/? 7֏5SR~͙e@ sb{yR9]P9Nk@c&noowǷx~ǵmvmv/?x?~/"} _CkC)؆ W6[nY=zLơb6QqR.8+l7{;e $1S-ìh>mw g9ِT׻s愼H)D=*u.!uw#wYq8h-N( ǰ T6:;"* 8WEvQTxW(kG$yx{)xٙlhFW7f97\&<}A#˽W;ش􃸦\Z�nnXI'[0s:x`t@vw\Z.w Z w֢.W{D G5"!!'X~ivdI23#"yϽșCc{UbD^;`Qkmdy~9 jP$c;߁Mip"Ŏ瘨Z6(y+2A ^n$)`V]h J+o_yw{6vB)d۴ti%qMkmqiw8{2p sh0ɾ)Y996Lk~5^d3;Pzn-ʎ3 6nZmNCfNF>`\ggh']YBt]۶o_7Juh�e0X :D%?e'BT"ڥ`ZII De9V ZjTq&B B=Lh$ؒuլavׇ\�xBP- 2ai8X<CEp5Ai"L@@(#2=`٦<�`H 2]*;�=sÇE:K2_2eHYj'lن)megl-L0#gKo¬3MȻl6CK$0GESN-z j36 �K,1dǢgsֳ-R9MYmI_H`M%.^:ELuLqK�9g_beq+�ܾrE Ԛ}~t!Y쏶ݚ؋�R pf%X tU@skI6쁜F o->{uԓ7Jm،fY%X*o~73>}ʤ%|0E3-ۿyw0 �$ߥk%Y3".,0?ؽwwY5X4k_g/, n9 9mFNpzrNщ`֑ZEJa9هZF]@kj|2OLMPREв7N9FZsL*Lgbqk^nc&r2_:CS>$EBU˦LEYR)GmmHEq:Qz7Lb2PZ9�s+ رZ=G?6ςd�Cɴr6]s$s:l}|{曫Z αҏ8+Nᓇ|駹%kh9nnvn & O>W~Wy 촠:fߺlu`9@n<i2"3#՟RkΜWv9S[^J~8]Dn>wXUtXe8w~TO=wgmWT9ʷ;kC`DC�iqKg}  Rgx1xå8e/y:G<}�S9JᆳL$D.Mg-<K:hǠE6G^9itEJ8KEyS`uFe`ClA:kɪ2w%t^߆; NJ^V9<e/k|]Ŕ|0vĸ=Tm2?1 Yw8W21p֯8WFs;fng@lҜsQk_8ai(^{U~G8yϧ%X:S"T~Gί6?x +B�Á^=fyW?qfC6MCξun9o )b3,lqeF:&XGz,`Rxk=1+!Y51ds ȑY!9m`n,ALB$ Q3mbgA]X%JC"2>c$#+84ڼp"nDՉA4֍I5{DS"b=J9E[$[3RMti_ o~k<;c&/ OXXf8;rrfNB5VllPȈ& ,Xu,ׯs8HyMi\_FuZjsuF^H=&Ui Z#*TQEdpX4'a*8 ֌Ss6!sa>LPc?`_j$q+>(k\2c".-8ږ5߬D[g6Y՘cEe;{V2$X=.dYLeF財-sY>3$B-炾OTl>+VxK Vu1̊#u\5 K%~F UUVglg-@+,(=h:d93d(}iRtF%JĄhWdE_:MJ9̔ÌNya'n__<waz�+ C!:E s�ͬ2 < hpQQ(yU .TRV8+_2o~ > ^m4#dɧ �#p-ƜCA3'4^P]KA5dZ:qAo9g&;iXs{VZRerM*z#m �� �IDAT-C "%V*i05sE@7"qam1yݧ-ΖB:PW L,F|}ΚG<|@gØ( ?L}(lמG:{srB vkcõߍy+^z?W1Kf,\#vZ&A$›$3dwmvmvm 6S$Jan�Ub죭S?wx\nENϼ9>:ԲFD}nӲk׬KݻwW^}[>|*gamN=4{[C%6v7X1wq zo#z{ 0BK9dL%iSa2L Yx/,?| 2MzVya[Ym)GWVIKFěje<Έi\lVۂk)(e9 k@yp*oru-g֋7nʫ_ɑO>zhY5*CJ8uQaIĜQR7 {~4/Aݱ[6N{ܽ6RX|Fvb}~ݪ y]w;72z̑-s<׮N, 9)KT69<\.-cde�w=rYdOsMtCVkx_&y뻼hۓ7bi,ZxK[wwt: <)ѹK \yLʻkqݥklڪK:pc;C:k#k}\4U;}cQ4[.X<*zwYrlǧt+l_Nר7ALÿ/<ݪ-Fǻ۴ZlP8hQg$Pr#`W2R74O|B3GI @%h?)U" T8L-|cOgET96K(TI N{~qJ0:]c+̥dμ]ڌE6.rdC/J[,1[cƘocw>?F�זs%�Z#彛t/F)m3.u0|I9%L 0!)9H9iWcʞFwotw3ULd@5" ImX{F3(LJ2=f6=_�oR@PcҠEVf[46/y֋z##j"=e^�Nה>go;Ab"`֍ "OB~1nfV@'<3<˲p:wsyx|KFJm8 -֔QP2O[xJ2YCyc \:,Y|)RLH4Nk}N>wljhԳMJAk Η^y'>#*#v_+pf4\-󽖱6vX# Z�J&nmE"߮mY(eRh2󌶘gO(Fd<pJ)pƭ H%L {XcG.X=j-v]9jZ14@> uK 7_jeZTӃ<`3b}pg"5-Ha$d+*�艹q vqEsUA۷tg<+V "y$x%}nnnWk_~G-<nxK)Sj,P ,186 KĖ+=R5.??Ĭ &1w{l87Nt^Ѕo:'|gm,n?Oco˿ӥmٟ~Khϖ<yI<ÃF^H%sl,: ;MAfk<hn+̂< rE .ɮkUd0HkzCD�emXeBM%cUhSFPl*-BU $7``hQ$BP7(CBB@JԣJalSɓG=r:>f�yuЪrS��%%%9-`ŢCVGΡdIX/x=93D`*r:tnnne,Uy:k눽`X҈׾?{{_uJp27A�ӣ$%E{S})$0RP>"tF @d� }6}9TyB "W E n}RfwS:h2e[:֐/CHٳWZsܻϣGl-@PHׇ4yoY>f3φ˥K6d9ԴԔ60ej?m }?}gx6V$#nj`,@%wmvmv&?ӏ4kU#쑏H<4~U޹j=zT<<@7mQ,6?]yO͆^J0MS[㕗?/?:nnߏv>_zkE/̻O>]yI IV7H2 0؆9Oei"d 7Ov[)e{ln Keb&Q5YPh+"CxFd!<`<}}ڥI@əiNE2glJڪZ\!%'(Di�ADnfF862y:0UJ oooywxwidL"#uB\ɪhfS[ >k� `&fM9Z6@H?LW'}믿G|b) , e[vE`jEX~Ǐ2pa*K&&h/"P`2x`>Lbw;#hC/:Fp3̓p2Lš7+)`[,*P}ȿ 4jh.RjWI,㸵9{p5Nlyy_gnx6SmvCKYY^ѓ%lzk#ݥS|3}:x T ya"^#5gc;?g_b4`(:|IiUnnnWO>ѓ'ԥbSQ\L;"UӲ=툘z7@fwB='.MOjgY3Qk㥗v&nnn3_' ^Ww!0κ?_ξlȃD@o!KOCeg y.^<Y+]k+ٹ*]6nSt6)OjjUw07o2v6ʕ�* }?e:CV~rLUT�S-$Eo03e*RVѢTk~!jc9-LSvvD;[7 '(,I0υG b#5n$ʃ)hxg z-~1=/=t|F[Lxވa&_7mh2p&>yַX'T}+֔ gH֭3fn-NgXmZ fQa: ZԛUTC}JS\ !8+;zYB͓ͩ"oGNڃ.R/H2[m !i*8%k 5\ K8R1^;U>!'1-�CV>ȝ +"6KFdf3[Yσ)< N+ <`)*_^?nnnW7}a>DdjΝE CLF_2L_Kq<RpwZg}y(.'kt<x@?S'dvmvmvw[mK [[K'[ӓ;4QRT�D2ZU,nR惕yg 5Z0,%W6k xzT':)FE=ww.ŀd<2^ҙxAه:<~xOk6- GN$OJZY\N-rZI0o3楗? 1\HYR? X(F=;!uyiZo)O&`G)<~OQ-Q̅B3d=G)3@[5\eWdxv:EG Be(*To!뎉p0!,ֳNifN"ԝUH63-#GJqV-:s\Q#X\| =M8$#deMVthRۑ;T"GUZiVY_tpbGt: 2sJ&-C.U)YNCuJM5[QaBf4,d'=K|aS߲b ֮?qh~X>ܣ'MّLsN[A %?nnnW5nn(ՈЂDXuG]~Q׀3Ý0ψg(q@Lwl3SI?K g?'nn&4?x ~mU�i:;қW^EUס95 `&,S_17d[Kgv@(d_+Dwf\dEx:0!; oIS!S49zyHye:<wGN ,VKa�,5jd~_ȯkFmkcI qAaN`-eo Ry)!c4A`7Dl(cg5z2m@s� gݗ6H:(nFѐuy>�V#bR"e6h+(TiBZ[ifhߙJPe.:䋥YUc<i) ٝ=֒A\&FyOYb*$'H3Ԗ,mc|l_]*pq\@ot@7-H7Z <y Wf|�p kr Nś^t 62֠{B=HGt3_A1n$/mMuWwvŶ3H6gߊ cn?vmvmݮ)QaXAWŀ >?s֙Mcҍ$ٿ7+Z1f)0!?񞼗yOj-w#1_/)<mvmvm~gM?oo>tdU@ThBQ +^R~4 A}5O YYY@NU0�NNL6\ s )nޫۀαL:c;m _{orHf=^͝Ӳp<Fphk-1 v57Ti:� k F`j 6*sQ ByzyQEg")`CIxYimgl�pN3az"ܢi 2wVŀ)Yo-/R& Q )An iHnZl J-رGyb."B)`jxn1<QЪSdm[p� lIi�YB[�%WrJc ?FdR"@ NbFn6�yq>'X]`5@kܱ)J"re!P:Hl{/p־`j p5 ynV כE`Jw>h~CHL+P`SSJ5EjU#XvU:oNg@w?u#`-sm[d\g,N =ޟnnnaqcbc [&šdz؜}:eϳkǡ|D+T2>3*n3s yўr^eߝN'?`nn?v>_?_GD1$۬T ` o8aLR`q# ɦ`5?T܋qmV�Pgv:%O%/\@ Ӡj0.ʄN`h8 h5qL $TS݃79}% <4r穠 uiӲQT(*5l#;(%[-xu4إƎ?Ѝd*S;cS.+ݥlT& MEǁ֔3P@'hj̪`ရxe*l<ϜN'5&QE o];* TNU\,S (2MqѲE@Öys;9PA&h:n'Itff j5[|r)[o "PwxVq},r\*5%$%˻15(l#1~̍]I6 p<13cShbx3#e%$9f_?Wv]l[efy&`K:\罟"рmvmv9694MddAOrezu~.a݈_~r6q_`D_-x*ڭ+=/S􍯿Ο'*nnnn}7l@SLUՐVE$KŖXk`8yaIUYZ+4E43X;)N%J%`[z29G/ |y$7olnmA-;5rZ%yĂ{nw+y[Q(Ӵa9m4h(R+:V%Ja k%$QZ,)`xvE5@VT6d`g- 35ԑE~[fnK^a:=ѣ8?A1[(O(:}6e*h+duZ228-,Z2O(fBqF4c*47>hKYf!<:Opo J Gժ" f�<KY\?+ӼS?.9}؝eYX%ddv гk<B<eEۍoC*x 4IJm 5M4]=[:E?%Ns S`#Ӌ|Ňq-<wkݟ]nbKe]KQjVrH Ildwmvmvj[i?^6#Ϣ3E?ݼd}X>ZwA-47ԪR\y0jˈsK:g$9W%g%CC.)4!nnn}w?3 d2c3O`--s5&K6U2�$r3g%%۠P;c|y]:fnFBB]~.P$'=u ^D QK|aPL3=6)iX+ܳL�(@G>x<0;P>ֳg^& sew I\DؗރTo!S| aJpt%H:;3ۥ\'C.m y^ @V(H)#hʌ03E^BkPkŽä՟K/qZ#*F+S"PH4GSQ0M}tJIajlkxͼT FQo-M) ((T+7d;|2@ZY' :˖2`pO 6<wfd4}͢.c su=KgNRJEkh E88a,7KY#G{1T0SCPfLZq*ZŽ4 ͝-N`h^:U6Œ�� �IDATE(ז c9cw>[.δ_kud9O|YttvY+MvV?vmvmݮGz_eer| F~vs|%[)LE11̜{h(^J _>ۨMgǺ;?qw{X˒﷪jsi{("E2'dSv,ۢ8,Ċc!lD@`@B�;p$@�8p`١Y#;�L-?I%J|pÞ~ٻjjs=iuvo}7bĈ#F1x]g H UECfh%be<ߒE.(L 1=9V}_+lPoU*g) mOX=-aZ s^Ϥ>R,ez[J'k0sAWw 6)9!s4mMɅX N^R_ Tjf u8d$VW6]XHZ-cHF1uu(jneQ4Wb =bK}":PJ ňYmRr4a*fV7He՝mkb;zN۾a2%fo2;_E$5\ eX4OVfbHx6mHվYf6Z UN?|Nk[t3bL&DJRve!ȲU5^` ppP+= }2fY Vޟ  ߥN8oQ,~n, hkNu@)"4+G{v.9f&!Qp[Fc-} u}Jjv8n^oS۬jZS 6J CՋ9W/x i p?7}ؾ}k./\ի=KWo4#+7iVX]=y'=O)} N:V_҈#F1bġHA|x~-e oY3OyҎq\9xEIsݪC;;K׃|G1bĈ#Fz#G*K /R(:V=ӱ\S X-e'eM= A<s3-WdUŬ-cȠXuu 03WJYGlNguBPډԼM^ijFVV9Ĭ(X큇H�qP&sfF4<c|;_yųv=/p|ADvk f:lSH)CUĚkX]z(3#BQ℣Aj-s.wNEK&5LRk#h ѵsڮEږe ! Qj-E4rq=֍WV1XH(E|~mE pE"hJr3'Vog_ȗzn\ U5[}%*k\v\ $zN[?J .'M@Tgg"rg{Ru?*+{-!L:T<Um ^2j4u1!颈" UcYT - Z eLV(@@a*I)#2y<v`<a\{7A,{ g#ǺS#GxSO]ů|]W4[]d6tCٛvWx 糟M>/3g ++R Wnrd}kbnm}3'70'Nȓ'OȻ^?V_#F1b=q7RqyL>:}Z1<?>?'בv7!8=s?#F1bĈ#pj@8!G+)hcue~"RɽJWyAQګ:<˶½o`Vb KA*m. l猱THjIbϕU 0^96p=,ʐg`"2Z~RJ ]Gof iҥPUJN)w2)1D4UD n<JVB1'VQs5fWb(kk_)FڎQ[HH%"1*Y@D "<gg6ׯ]f|X$?g +/]ʵklmm$BB硆!U͵䕸M#ʄw~<:r&[=nz IB Dn3\m}{ZD/]‚)* c'%n0ZϓY¾{)R\f}}]4da䜽Z*õf }qz%Fbj^+s֋�dQ&cۦ--bv_~%SV$bqUR(EhJ%S@z^\/ž౤a m륷R d4 8H._o/~ + 뷶X cfPʢ5)rlsv9i}mr-nm8qt[7npuwori?XxaĈ#FM4,}q1;-l#mkU];n_<6T #KdH'cu2s&AkcɎ1bĈ#Fccp%y3.-:n[+g'J*X1+U!s{qًgZ%Wzޚ'W=lHzDB}&Uu+:u&Itu0ayDVed9FY 5(ɍHogLa^ߑu/\իm*^(>Wj~?@!n1SUHhN!HSӂRJP5^B6HL~MgRi</BUR beWu6gbLNtUAlF-J"y9 hBf5V )6o@m{L ~}m^ %gP%v}C#UixۑS<~.JtLn&Zd$^17bS1'y=F(`D3m ˤd^cʲ4O1xacԹZ) zgJv0%\ Jy-E20RnגKG,E,8,B(klXI'pcL4GGYMۻWr{n021 SH@CLPِl4V-hHòaZF~{a􄿷J m{y׳Y%-\;:ՃPɎ^<~퓜p8w:;o{Ü<οޝbB !7tX[p.gN!jńp:nlqtudȵD5>?y })Ooi1bĈ#9?a19q8)z_}.=Yϒ~nzqXFF]QU)9gXj6I#F1bĈ#FBd#SRKD5' y!6TCp^֕zs"ղ:W6ρ~J.FeI[]%SfUu LJt{XtA>UqA\YQV]w'}[{F+H{? "JAӥ׺t,utB)h-5Jtm|Fυ6k۹-)T|ToԔ-erajt#} D'P֕V˝I6'-BV(i2T傠(UbF<G-iHnc;-+M v10Se-E1c 5qChSȅ3BW/w^+TaOb,6,ۗN J l~.=Fɭ^0DԔHfJ&evw >2i@jHВjA2eIYsd؛ ykƌ3A=8R*)jHD 6vI8]l\]r ! 'iT潊yyڋzWsn ZCe<u^Y?Ǽ5x)F.^7 _[2#X՛[3NY)om];sD(Z+b /?y~Ԉ#F1 ;vY-L~ީ= -:1;[~_^D: !Ф]-󎣽{ _q%d)O<u]-~sTFvĈ#F1b7#J.X񿵸0ϪSI0W|5٬]:B \?JRV.Oԟ>ѺNR*mpgA L -9YS1cjBCA$@ͻTSJ�fml)9 ZddcRԨ!"[ "bf)ֶړ޵+%C $DB 9ďYV8Sk;T Z\=:LVX]0N3WŖJ7HeT3ljBef{{rFZ45 XQJ.Ԑ&U?˨AL$Ax3qB45WbgbD1A Ȕ}g S Xʻ~%,#H1x�Iٰ ?L7ϨpB4XS[=g1\y_)y.4'LWxWJ`6 x 3};ubhB[\4cf/60 S c+L /\W^&J+F'Pڐ~ RH*gD腁@DbɄHݔ IIDZ&ɖ*;D4deZ2%ž(e:IBZ D}!rAL_w-[j%#]:A?I`6kƎ Irn3*dd\~%>O!ƿ!7v8{*ǎ{Yٌ.6gЧM'3/\ͧQx_b6:A bW'\zcklO)j^H Rl'~/>+}5|ϻ1bĈ#F|`sI%{{xحoA+ edr6Q3PJA]W5P|?8{{{~u6yĈ#F1bĈoXQ~zGZnfE!0٨}T[VW)iһ-O}RI>&B%|%j?\V2wjR1AjIsfb1lLB"c tI5u-Ȓ?Jhm?՜- bʁ0pS~uT"Ir0R߯[2h9W],=UZrU2*LP8s W[USLU"UU,gpVc["\0A [ AXvܵmC"N|aV67;n& RPJ*eKn%*"ԉbsj `\ܾ'= 7ŸsyFNB Mt((E1cIjy+lcҔ^n%c)x_Ǵrd{g7#WEh1 6T[3")Eɥc$J/7]hbu63ڒ!4n9]m<N)ʼnDûxqTE|}ֱg,\({Y;4mbq9, ( X:JR;gW2!6ni ٘kCКBLAZTAOiLj!1:NK׷^7 ߔM|Wol񮷟*9xΌlK!�)FR³/qq $xdp7o2mH8f79JA&dlQxD '>/#q䉷)G1bĈ03Yw0"pb[,O=3?>GeU*W'UΫYkz}<he>s{u&6˯#F1bĈ#Vpiy[2BZ4KJsX81A gL/ ((FRJ~bu5]"qWU=vߴ_tueoOhxg[ 18g! ׏l\`;r $&8c1'˕q+9C.*rN!gu<#UU}bL&+KJ!@#M JnQ<Mn n=6X+xP3OS-AFPV [(Y!8~$D $"&#\q[۷ޚ1 "tEEs_ki E ܹ"ieJX]._!MtF@ 4,Ej[B=ͨvn\cͥ g͆J{D·q!+߸q3C-x's)hۖs ^hRɽwSb\f<sklRL@4V(E)f]N-!19+ىl2-)%ϸ-%#<h9z-1DD&@4ѥUe]*u{=ˏ=^vo; {b譋JzzS-9,l|os/p1ӆ^a<F!wW?jLRl&[\*j ]ۜ<AJC'qc'6>ԠR&’K{3~??<oq1bĈ#Qqໟ0L?yH|,*mg<[ݯj~afÂEϛSa:bĈ#F1b/b@3HVrL05w|BjJm'>%"T;UϞuyH:r`Dku%UyGnb)bTup1:sN[.^ =IAZ^KV%!1adh665JC4EgH\ b52ww2Y%lEK\z%SLR`<{W DItŲ h#kΥTE 6+ٳ]aZHHISW ɝ11Wcȥ%T?j-J\]Xi %R ȠJE9SJ Fl Yf2e2c tee2Eȕ]n{W-Yb17~BOI}J_-R(YZ mQBLhQ[?&>V>f\>7]2]V>r.uxl H1l3k[,g3f9&ftjKF9v8?8[[;֋*^aeC'2-dՖ]A6i"UfQ(4$"PcY/ ;v*I[0jyYJYXe >cu@=<uoBvg{rzix˗8>屇OW[Jh@<{kgIlOY[4[3nm­;Ѣɝr])l:JRNPUnlp6u~Q~OG6ߪ1bĈ#FZzJ߾`'Ou$5Zd XL?ܭ<ۊW(A圌1bĈ#F5v>GB˥ڵK&Rf 3D'l9y#{%U2 HgOޭRķ1zk2l[?SUVu[�� �IDATI^H(O=ݿyy,WvnRKJ5 Y"w(bKBj"J y="KG?U*!Е\ ޔ^ףZqYy[g{AAB tѕ>GRJW$nQ;- !I>yN8lo_zk 5rUHWCaYBGDm 8<HJg0%c7K/L.1og=RB1,;k"?C M"^/!sSrtbjZե&@Ht"µmE[ĊVm:4$'MkiBO>_N~츺7IX$mO&_[Xe֡*z.N!5 E~rOEj>Tf{N#"Jd!UdWJmkAbgDĔ {!aPQIvTRIsemN7<m%*GŸ5/ʈ2|ޅp7ӥ B K>u9홼\ -CV (dq]V_cZo?,~ Rx]O.rgN#;*'=^\Љ#1VS$@B� /p+9IJ Cb(~ڝɹL6%ge{tp:k+ML&P~o~7~{+wĈ#F + AkXH+&sl`ض~A1 O**"#s$՗W"æG ‚<2X%/!hgʺPdB~hŅRvĈ#F1b7|jfזtVp s p}&B0ՀU)qKZNw{_`ʂ51 J.:Հ[֊zT5bt'Xul+6@j4+SD<`2ĐXUCm1'|+AjbmU !`uuJ5$L V:Abt_D1ZYboVUήzFIRhsGL+&PPĄLR$@:JH$|F!)NNG'bgo\gآAY T rz=5am!QJfVfn"7^v+V!4Sn)`B$B&hu_튓04V+n):Us\KQ3føFy!<}KYAQh-pŌNcλb\!D3aK:əsP zh˜f2!o> ϰV 2IP%J0`8bDm^Lɵ v;VB߁u+Y~Ҏc7@UAkn1fWc4>/[2l=i ?gX;ѽX8Z*ߧގىRi}_?>$2VmkCe??/ſȭ(O>̻z|ǏqQ\K/ o1L2{`suZh׮Yd3zJJ?nQb ɴagw[�4)O-j`_ I~~#F1bĈoL-R2g P I.,t ߥb|OO Z N.UW>eqGY.?/_+^AXՇΥUPz/nĈ#F1bĈ{uUqcOb<>7BE%8?/,@#NaIjJDUȸ҈ jOVҵJ%2<T*YTe`%bUD+s&DQ㥗^/\&gXU{N:V*z3EIM5E̞$m%qUR'՜ ! JW-a0(%K]WJ+N&@I˅BPS()ILB*jJ۵hH-50 {-٭Ip>jiA85m8-։"D(:dVQɨAhdSZ ɹ^)( 4۴杆h+ bP L!F&/]V2o烂<IP1BuO u 1ALXD&1Y۝hnB .~ ( nInjn[']�V aQ\@T1ۚQL!o7Q<kY$_jUBh� c`P%ŔLp[3랾b̩L?AR1:BTAhY4nQ\դ"~hFPb_eυDk1KbZR/q}y!K-l E+!zztSqs`f,|Cn}#gY_]w?J^X*栩7[7w8~dEAIRe)rvwHj<q& IClRs/kH WH1p:G6Vqk[sV3G7WRom?{>|yĈ#FJZlY3Z@=efŧa~ 6}BUVqٿ !x3pm{{<e?v &G1bĈ#Ff|*DT<<Ӳ+9Jq"6:Г9Y j@Lh.b, dYU ;Ab}kb3W NBD4i"rOĈ۫Wr-'F? c\H?WJ ϾOEщ퀩Rj6ktB!M0\vP}o,B@sRF`~sVVYjU]m&t9YAAs&HtT mUUi$R$v7_!m+Wٛn+ k/�XkQfJɊ+&s)]rO}YU.;U3LU&4 [.NWj$Ԕ3n[JewF5q@isbp ,_]H!PX$6V8v+hXG$XǞo2(h 8QǖO}q/Pt^ͭ KjA^'>BohTX%sת;-;fbN<?hbz@O>ZbĘ麎4|\\M^4J֪6=џsa 5:t9F彻X]r֩4w ˪Lj@6;`EO_tv߿ࢠa ٿWO_Hᯯ3/0iAB@UI惽I].\ʷ<vɐC60 7ڥkeVp|m'y{{ *m<+οo7pIbgeg &).r{9O4G<bĈ#FPMJ_5L X|+kg-սRRUd@쟕�*>˨q72~_okH5@{�&|g֧>~mL}-<#F1bhRdb5GDBOH5cnI@@jI F_L'rX&~ޢ4Ϙ *:jiB B-{X=TBCsqqON…J\WՆAҵB%ĪfnmpF0܇]-YiHo*(r{YQHnLb@lB@]eP691Nt<v#s)4c jC1M  )bAP `Df]a/}$soutqŰєN( Pr٤$ y`O()F')c$Lfr)"*M ,&N@ttHiRt BJ_ d-tmK ǻnP6)bX0U`LXmIj86qx+o,cV+读zJ1m'5_V&5>ùzϨ*v!,xr?NK- WF/X)Tqطn`>U㡨c(CGb:+Ջ2 C=qAUuѯ{yuڷogz`.OvV=:D'`eeׯs%JV:2\M/e~g~vpc{oF2'X]Yy#ZO 9UC:IBRX$| \x[;~-NO]> (9$JQ63i"G6"/rNm2AU#9ql3?//|gw}O~ kt'Gy|Eg7bĈGd$#nwڶܹs?|t:G'd2.ܹslmSO=ƾs7\tk׮MN<y&O>}\pa}:w>zYEAuY:P^ u~s|_;v2k2{3qw {gJ~+0{y/{mO<_Mg_`Mz#y3}?#F1b=!N,E۾+JP&b(Y9j OTTb䜁H,ؐA} 0궦b,u3ZP]d nL=dJl&X%D" a4תm6b\3xyJM)5tBt$$D&`¤ |>"PR 6pT'BΞ=Ѵ޸El8}4?8| VlFyNlyq BD@J\|MJPDJ?pMWJ='#5*)6tmA0,W[jE3to&m;Z)EUbvN-J}{a+uX%9HL<' fL"$1z{ܺv@ngLd+exj!y 0KUek%X=SֱL -l߼z؟:OCpka?7 5fD|-CJs*b}Cu )!k2UA)50L9v(ۻ29(lA_#7/~N(Žh\%`mc{cGqi[l߾JpY8/z,A?W*gfT;l)㛼1ԼrTQfmǫW8ql /= 7nf}u㏜0RJ4ш"y6W.^L8qtN7I$y۱*O>~ɤK/\ʭmj,I1.\+79ytG>r̘LkSz$+tkebL|Tʞ={~G?:Oⳟ/�>(??Sg\z+W�}$G:nO';a~:+͛7xرc|ĉ{o>~ԩ;/˜;Aڵkw'|z~~eecK_sܾc<BjT,Y,LfbϑT\h׆ ҁI 8_uE. Z5.0>ỶJ~&o6rAc9տz_npC1> 0{y�dm}ݓ\g?>~v~#F1bĈ_]DʸjT!ۓ+OHipŚ76+@D )jlfNΞT</ڿDqnvn'=k" UAj*9=oem JAԈ1rV 7qd*ڿ6ަo`,"^i(DBlLxGx|>% V*cMm+gF';N^">sٌ0g\0-4MojU(Upp銷c2DX[֏e:e6cBE0ّ-&a(F7o)]JbPaq]#ԀYDaeuJHLm0F*J\8o'rQ蕛B4;Uq_ bt(QXMSolpbcݣ96% !2^z_JBzxߧ8AȆIQF U >rEz}JD H͌ Kjؾ\|~/L_75>,(=n=X RW8qwseξ*5T2PC /}˾H$}@<mX&R糟8~ E[?!b~a-"? 7nlW.:!lrmN?5>sLd}mZj|0hB |\~2=)GͿϳ Wwv+/79zdG>gƹ r.m1nw<_ΙSG]W>EqVSrQ(�?Co2O8ϟ~|##r#F<X$ٝg9,dnw_%f$~�>3͸y&Knl۶&ZoT{1=:|~Ν?_f;w*ٶmΐ ?$+(F%Z=2B)^g -ȃU0(R+6uOXEb.C6_߄"ȃ߼ýz&˭X'!d_7:$~'8'$t�??gXÙ֞~pZY?qNVW^1֞~r#F1bĈ_sZ0y8̽hO&ThFŪk *`a:rNsR@(Pzr9rP�oDMBa8߾}b(4w߆Ü2;V iźM y uDSF#%7.*}o{Z t7iv]<ia+ГC NQ֭3b4!u6/rׯd$(_ĔZO e-Y )kk4% miisKYJ-p3ܖyTf9{ T3RT B%Խ7BWgh&4is4h?RJttZJQRL(d+H)n$4e" Ǐ'yUvZr)D˕6,B WE*,)_"eEǟm3-, ٓulkE}}Ru1!Bii%@li= d$� {kw!Q~uVW61!&2ƍ+>no]&=afM5(_RR[[ݺ( zlomcss[/9;כ7 !??j#ë[{lp෾;3-Mr7N%@R VUf{9gRHr ׯĺ:O{y@TX˩S8u8O=~ǫ]{�+oym<w{vsUY?|_/dƧ?;G}[[ٳ>?a6777z2vĈ_X&Y{| 2̙3??Ǹ|2ٌ|3|}0|C.HtG'Uߨj}@<xԧ>l6c6ۿ_s?/= { AJ_E#bRJkIAPRd-[z�� �IDAT&D9 w&YT9VA EP:L(-P'9q|2u3^3`mkz9.ba~桇�Н~7/͛\' eg>V=l/|76[l׿{蹍1bĈ#F|=Ij3lN 'AHT^%Dxi %@ƶHI{ 6'b 1DPVbhVb }Bg` V o%ojfV6 P*a@齎!WjEi�1"M '+nk 2YiB6폣KK;sF&s ˂pɉ3>, ";{tA>+Mä#Q?@.r,#RI TgN؂ī9{({JoFm X`iĐ%' if5q73ɐWd:i OzzIXĸcАuBv%[YOK%Uխ{9{ϹzĆ]K}><}NMAAQd(uE~HN-^qh\,CHN,(w漨`֑,1=1֐ Z |A &V|VyƱc/J帊r L%IS 98W!f jjZW1QuZoܗ1O,xy_G2wQ*"cTjVڠ~.;yPm\ٲe"#yc ǏyYWJpq_{J/yQW�׎,HLJV>x<xWhK^;p@kѦg7`<SgI[} n^rNM,##M xۻ) Gb{f{%QZL y3jl(HUTcǦƹ@a9o$֒w3=HgrF/ok6fO(<Nv):UuHaw_Ó\ Y:6R=֐&tWJL,3݌/>,w{.Z1&VR!g|̊.˯ޜvͮ]]t)Yo۶z=::իOi]գޏ}6ׯo7o>y;qFS^tllg!~P1W/ Oi#}<4My;Oa֭p }dYƑ#G�K]ZW\qEm駟^ o8m bڵ,Y"|o~BBvMoZ6?\2VҔ*H ()@9lmP),uʉɔաZ.YBaә,1hTr=̮R]zv.tl*i 6^ٱ7pync'߳ȧ?7̫(x=S;KUۘNz{utjϿSmȵη=/!;ޑk z}P6Sʹ33dimc!b!F*=*4?.*?{ԂwCMqb&4FiT6*r ĮR5D5! o: |D 0eu^e"gtgqYmFJ)PFǾ MbW ?UP֠Tx/*aCN<Y xŮ=]cb7q_ƪ%4}P#snC=Cj-S@hJCݖZlb(Qx!lb"q�,8QY� ZQzY!D"Ǘ6ʈTMISR8'F] JXFEv i6&9O󡼸4 y:>UrXRm4Vi0iCJID)it`Ai)|N,Cr]T � &!^Bzߛ ,u+{,Q*{"ҁlց.qa) J� .3;.U1m#,UITJ_iomeQIVTkqyL֍=e.B;Bo{QzQ(U*ݥS:D+g 9:Us pU埊|MmUON?·~wy ijt##)EO}QK$FT 2^syރhONәVKWj5/s_%W^οLh;\z5O[,i$$HEPcm|^.pT_xT%3]V]/b].8o +ŀf@__-'ٟw뛜/1>>?9߬g?ټy3{[nÆ o7xmmy/e˖9w]wqmt-[T+W>SO=׎/˕J~�{שּׁه~/[o(#|6m~x|sSU[ݾob!q.ة>u\bu})BK-&7V]wulݺ뮻7|^tkoi-|z%l6~ٿ,ݽ{,m#GTSYCӾJD/XQjj.|.#:tlhUf5\�hEmONyscC/vч|F!-۷؜u/3�?JBsZ.W*k^?1_g7-[eHVf{;M{`;X66&{c_gWM-_asƅ7Fd-GUs:}w׿~Q36,ݴeoYٱ:;#g>ӷܚ?C 1C 1D$F2^+ލdVQ=y8WΎJx[1$6rù@T)eV>* HR.hQypJ5d$Ѷ5*T++țrѳ-REF/TBZ!BU"2Bٿ&JUrc�{aBZ!uGH&,蚊$ %,(q;Ens\HR+YM-.VkT%j$IJv)R%L\˾"X#D r ?8nTѲ&Hݱ[B,Ppn @hQc ^<")QG(Dݫ"1)IjQj4 <Q@l EyE-i(K*uc,F}xQݲ8V=kZzT]9}{,B"M9O+Kx _!ȶӲ96TΤ| QsIн;zF Wh4sGean�}[ZI󽲓g̙'6!w>ߊfj[LKFjHCV8fYm*V%л`x‚ǧx~f3$ZL UV7Fj5Ns8o\4Y&.[yMpG>ҋ.ffj&f%BXsۿˮ?}nCKcߡ<slߵ=A.{_sxfq$cAHM6Kx{Se⩧=yϜ]vqw.͛7kkkJg{ 'T{CU}ww{ݻ:>,2Dxy{;qw93Cp.XDٜwY ONN}W�o~36m:)Pak6B=[v>3̻\|Do0_"8Hm+uk122J=OolQ1PUuͼs8Qr|rN[ \E w畀2aNvr0Z.T\ &NbҬ_0s�{|w}ou޼yN_}Ʋ}1}?{?7g-3'\tc>^nOL0ϰc̻_g>:˗pC 1C 1J QcL$tfg* DkX[M/J<="U*$ڄFIԞ1FA64A b)&I֐amj5iFF]2B&V^>ک-I`4>v(틫qF9Kc-L % SO<6MHҔh5h-间pt.)rUXMiITnHsߧt]$] "UJ$ J) f9>f:]\iplb ,`2ti]: i6i5⋂"<ZN2hY噐͑;$ŘTuR6*%.QJ Kp^y+6Q5T"cK_17mP]Z*warjNCB]ȝ#E,XS3Kk^UNs\ڇ8nYDr�JbUW: 8B }Q(pe}ֽ*t3,/.T߿#n(%@کMi 峸\(j=ЮbSqI\Ӗ16:AJWja,| ȹm'Z0) ;xU_8$Ppp4RKZeE4k=YW 7WXkHjZk5-C8ve^w.~嚟5?c.ɉIvoc9>% 2TZwspyN;:YH+KΧؾ% ^J|_ѿ=Ig>{[o0>>[�k֬aʕ[o{wo-[#,j%JVn uG>2kR:66ƭʆ [os3HƖ-[Ї>tcocg!k2^Y-jFެc.u`jj"d&֭;#۝k?uy_p,Le%t.; -G4>$`Q_ 3C>cqh  :*ZU8bإd  l*~6L.j.s~XWm, }y4s.@ž(c,"Z~%ctCE\h]Z͒[nawZݷܑOz}Օu4^ Ʒ}/>ҫZp�뮣qu>?2@&#7}6kYvNuvEbУ_x,}nʑ|mu31C 1?PZJ[\0 4bwDYT%^j VԍN}v y ( +P& &MЉA7,I+%mFAjUhHRm!ih ZK؆EEd++x4Y(RX#YڈӰD** 17F+g@i3e^qSF%LH[ l+%i%JP3 k0tKGX4Z MF[442:t1] 7 s” 12 qxݙ TDPLpB&ngx'W%q$\E$2B(@y\R`P] !vBx ": Dm("+#!1 &(m@iQqrIN13 'Q34:p0ELd +W\)-$ gA>E &DEY*JH>p!b,B@E h/D+5xɏ5%]^ss;GZmbC- Uk>,A<wAb"VT-DP TF>^>ql }<NYTۻd{R:kzh)ׄ`Ӟ$ɰ*jTJSk'x(rP@ri@yrxp ׅͥU(?xZ8}. 4 c֘^T\"{b&*jH"I+BUlc-N7/|^?aHH1Ӟm₣;S4}Y-"]ib/=H͹nHF@Mi{6/t(CGHeY**|�|lܸOTυ+Wr}U$m,yz):ԧRؿ,ݕ+WgeUW^y aÆ }Dm۪c[x衇l?\.1>>-~||~oAq>W}m6j]ZvTC !cVrԜ )@'~;w/T}ڵkM& ,˸[3υkGڮ]f{,υ:y.9u X=!8 /~G`z X/IׇIhqa{mb_ ꭭cZN)NPy} VʆsqjѸ>cG_.H6^j/V$w/Z\4Vw/I|fVu(u,˗soVƺ.\9ʕ}GNqmsM{~lWTGzr.=>0, ^UǕǬ!b!bd; :*ۄ\+K1GhHipTr ѡ1nZmm%(!1e6ǜI48M c#Xm(38 eK qYTtI+HH\F|.[ZȏcJQ ,wҡ}I &1HjjV >/0 h5Ȳw8|5ns lPȞJtu~J+Vz$&X*Er*Fh Z<t] (h !r+*դyƂ2yQ] _*TJ᭵!T$X^T2=ugQ^΁c!*-٧]J`(h5R.Fp|LwFԴ.' r wW\ _IPju1:-Z@Q>TLW9\(hXCKƸ˘8vp=¯$]s$79\eJu|/y_)d݃'Ţ'<37W\º,l̪ݞ:J=AS M4fqgh8ҟw[.!kE)CeLkO/{xZj vM*ܛ+R<ڒ=gmUcy!Ac&0h6ol 'JCihՊTCj )튗 t|o?0gjo}%ky{_æ]ʿ.v[/�V$F}Q~fyQ7v&&֭,/Hþ|e"PS{|srmo7|sDſ{G._ooZoo:HNcX,hndd{͛7ϲ*μzׯ_?KEO,S=?C o&YԲ+2D]ie˖>㩩)O1:krM7qM7gY^7d199ٿ?;v'#~?Adn:KI\Ճ˞UĨr(UVULtsOJa*ޠ̢9 Bb~Ln쉶jS"�� �IDATEߎ@I;`,]{jlm}b~ggQc7wY+ge*V}}ml]M7-SImsŃvŃ]{ b{>tf)y;}nGVe޲:ClmVKC 1C 1&<q1WAQYj*1MgU]  ٮDG$WeY+ ( B㳌F-b]I(Q*<X<th6mj5i4Rx >D;ᠤT*%c}VzX.jKKYU)BUJTil4iib8II(!� =)b!VhK[6]:�HlBRQvj]8,Z=k^5ijH$>\!LF-PAH-I<d݌t]R$֢B ^Ht.P8Frg( ,#rNQ="0TKbm4TH!QBת%9-JnkhTTO#`F(l#!M-մ5+/U+`i+%}.0!IPd]<#sw\ /Zc�=rW-JW#HR RyV>zQtUT'+5\荾˨H�֮x6+(x~Dl~y/v:fVr-[~"rr6zv^Z-vyr Q+-QGS `+m J9:~r3WB;xx!ptrQ\2W(Z0�,_Ch_= {&}a nb){^LZ&wab12R!S,c[˥hkcȱIںk_{9KFi|v;(1\rR952#͔k/XM)׾);#h}J 6\+drmԜ֒$_{キ"am^V^}o۳Աsmo{}~:Mُq3>~ee"Y. ┖;H~/zJ+uT,nڗI[Go} 4eݺuUٳLlWZX}P[Qq^ӝz0D38AtAut8W 8a,_:;Mgh3ՠ үGP2TuIm[n=BAet.' n oX6d8r/quH]KvMQܷ7=ͱ-_,( KneH1C 1BI@2\ ~V=X#emI|ڨArI#aT$4 V,mhO8]^8_iIj2c*rBlRHUxVF %Tٙ ZBY\56TTz(%xCY̋ny'.<Eߥ=!%;DI#B*Ev'z42%A06?K٠q.'@*{ s5$!1k9HR+�KZXg9YSҊZctA,ծ] D*cmopM!*iF+GUogy&Λi<HJL JBhj5h4t dywy"&DQ!2QVVXyE<Eba9ZNL*Φ(mvIVx"e/zrx Uq9ȀdG.ۡG5L2G&WfOD<3X}˟ T/P)>z*zC-s=pԺ!+/$:^g=8?"ϸ~+ƣ%PYH @)RPm+ժoƿHgī??ҾXcPѺw$k G&QJl!"?./cXvI|*E$&`4<`"RcF `T`lt%<X}asr~LG4cdK8?a.C{& &##M-BXY&4N~${9>fz&Kqťs~ X>59t;O|c׮]}zz'©(B:<͛OkߋbT= 8K5<>>~Rsaܜwys.7'R0bw!_䑿f�/ 0')J cAl7mU.u"v,[lVfēOe\sYG/fʕ[nіk֬gy"dJٟ1T$j^+E{"!ȠGGĺr&-,_G9AāaВgɇ9އ= Zɇs|@L ݟ|Q}9eO >)mvŊ>K֭ѦnWܸ9TnP;ߝ sO H.b!b!~pT@U&cH"bq.HbemY*JO�<(-ëB:/D]MUʠ Jnѥ(QHyNF)ƊVREPbg_dV5¡$X 1}ў8h-=k-6!�\܎'zfA#Mq#;>- q@.VMqLиPT)&}TʹW ^ AGcHl;E5$(#d( WشцgZ82%dFA$B�yh6ҤsN7(ZAp$J 2  8CQj;jm#-JjZQX=ȋ\JV&eْ1&(\ޗm۞i{^m1iGuK MP9yαN)%2zWK@lHh\޷r'xih ,F{,#3fU CnQ} c' X:Fkp-HWZ{K+|fT=;H ϩlr[T>t\֪S5s6B+E٬Tޗj}])Z b*/~/)̪*gu(ۧ+)x`qI'W!7k[py} rɛjwi5YVeH]F+(E=-: e- L%'/Θh{E�3}UkVs h&а GvKڰTci?xIbɋb9 7oX~9\v .9);v <%\q! 9/vֽYq'8tr,­ڗ#sa`ΌW(]f)TC|?5#+2v.RBƖKyd%KXjO>dE.YDرcG]j UƍHUX|98p#GbŊbl0lFqBWf nVYj?~AM`b.68q�r@>U!e Wں窒suel>[(EUGp_+ Y.FArAZn!b!L!{5XC>3 IU]EnHiY*ނESG)OЪ%)bNR@QEȋ]mutXȕfE#M#(26j^E1Q*Zzn'PճTq]@Y٘ц4m DT<xX FFr3q{ VSA!U ]]`EQ` G&*7#YE^ iFt*">vrr<#/h Nf#5Ħ6Zʺ.'fk*C9\ᄐVBHR'n'Z^7 g\/"l--\x\(0Z2hőJ3WF ԡTxGr=2<adtnu'&5SZ͔fI%V;m I=FX-τ0X\,@I6F<AQ:U+D%i֢}BWś@%g\hUMO gF!m*dAg_J\:B\Aڞ^o] :IؾRg܄no[zMDvxi>$%˲ձ6 D@M)!TNoR- =|g :Bl!>A, Jy`hcO!pdT ;VrcRX#Qj*JSڵ1w_<]M;W65ƫ K~=Ѕ|9LN�, ϧvq# >5yɐ?n(kX&&gx=L]1ÚUs޲Q<?HZ19}G&*T/}ˬW/!;>>?sx]r%wuW_}5k֬addꔷ?8Op7.c=[?Gԧ>ŏȏ0>~깑ʡC, %:){"uNV^y:#?.|߬^SIꫯ�wfrr"׮]hA@!Vr!hbuRq ~IH;M S@C\Kն`.Vg\ .)($W�h&`t8.os.T;s0b'U*!ca6بe9G"G6% .r%Ƕ|#wN mqo&ZWb߭�-b!b!?!ăL ef S*ÌAkpip,\A (c$(%A@5 >%(h-IY*J\T9\*2:(Q"r4b@$D$B2JDYe*f0:t6{V!17`*cm&q=J ^9Q'" $h�V[H@C0%郸? '־IT;I҄7n4mOq|rRDKjF'IDv#q5ƠFmBOq& 6i@ۥ*/Wh'$s\~ND)hZ evQ<d)3;l1j\ARt;PmBb >(TTIĂJP^ ֐XCx <qxWG,.c&޹Xؠȝ']-:7Q]~cew ]Y`)zX\!$iX7Z%s䞔ҶX}UG1P9~70U{V&CMW]ycǢpL%*Y|V=Z.]Uq_ZE9#B �Q(Xim<%sb"d7zEHJ6>/(2䞖%1$@ZɑM=q#M$LZړhM;̵k8|(uWi~ W\/$4F ,AQ&BCrEϯÏ6hy/V[ZŃyk.Y;_"y" bV__cuҩbP/ϫ=[ؼ^ j1܂*??{3?39tЂЇx_({买z=v{mϕ;C̍Hgyi^uo;ϛ{˝K߿2^|o],vѷs"nݺk^׾ߓ6h|giqJ2ii^0elE~dSʹPUj0Fa IƏ tF@lc~7y5n&%_uzV5`azݽ{>+=msG6S3?=} ]oZ3[ni]`^lms.7̿|Y=22gC 1C 1ę82_\WHRjA  bOZ.z\t=vU) ] ByGyRL⃒ Q(LG<Ơh)HlI*BP)eK2 U!`r*K#Q]$ 磃dVܓ"7R$Jԧ]9 h('fEАg(LbI[ BFI (@,UH~ B 61i)\xI71h;<ImR6 J*S9ށQV)!5,#!]ʋhL+-B~&xסBENВ]qux6B|u vQm<ډ"6B>zW*]%v cڠSNrwC@x"/R)Z&s9Vs$<F#/6!8U/yȕ2^*r$KRʇʚ�68mE\.J٘kz|\عQL#sTҋI互~slp UG%>RJ(PNg4N)t"\QR2iUVWlґlM/afff(ʊE�)N.e{tOl؆ѤFӴLe5-I*iXZ2 1炑c4KbŻɻnnp删$p(j`F@hXk:l8S|ZVyXLJVвVIf$IJa4Sˁ=r<I% }/cs;gQWq>S<&yލ7اpݻw/;3/O|bV;Y}S}}}?6NMM޷37.jC 1~wd=>7rYWﯽ9ٿ??߹7o}R066V;N{,`. Ȩ?VAR[-2̸*Mz|!7#?miCsXAcke, :'>9MsHMU9dsN~zm+=WXZ͒׿⦅pR~0"lOU{OLplWfmc7u!b!t!D�1QQXs{ђ# /V1^&GРCu39y'w!#):9 > Mђpo~ĊT ٌcH*+l NGBIP$&%MRStg2vtE'PNN>3B%G7 Θ:>M("g<d_x<\Yp:PhNHJ,:I14 ZooO>ɱIQ֬akGfQ&%9F)r.eP %SȽy%-y.oƖ_8Rc9֜bW]y%+V@)"'x 5Ɗ]6Xl`A xLV i+HlaCQ8fffDek-4!Dtp�4r sW G[Q⢅bl"ejt ɧp\yW ׄ s.^IXQrY/qDd%m)sC$]]M[1`ZQXT=W{ZV=`lz>Tr)QUT¯*ء!7W{Yx֢(B.W/ٳV} 8+P &tekëF!;=5dWY4F D0ZZ=14ZlbD5 ZJsW�� �IDAT Hb -hyK/'YG)㖴e+UTA{0)|[kK@uaLAx[:1 xxLhCN�c}I"f4AHh(=EISK$; ^BɏOwvֽZ$oկYXo}{_yg߸q#^x!v[OpRi|nczzG}j7~7x{S߲e x;_%֭[=gfe{ウTV6l[nࡇ7V}=>OW_Mԧ>U}www^nYCBv!NJ$녍Kx[Oܹ[l(T׭m/}ZL岂<Jf9_I+g2%ѫHQ}uOyM]+Uʃ?t{窯tr>g-nʦiniS'_M;| |1xO8Ytlt_8۹%.Nt5v&>9YEsZO?C>v$[rlWhfGFXset8w'©ݒ~gOOk龰Yu\=뮫o=g~D%?2a,b!b!8g<מ2JiQDrQpLRVkTeX A_2NQ T1S/Jo+ťJ+m:_}\ RYF V昣ʕD,/.!nmbi4Xjm"+fyc6PJSQgz7V#sdFusL+xϢ5#nA$1WSH9K^ XQZGc%vd ,5D[EtRB VkQG\h_+J)yN(5>ȼě"N2s Z"ټtRI>( e O1Ɛi)gFriKO5+M,EۭD;=X!=$KVJXI&ՆĘX :6TJL'x!]V*XPD}+Duߢ m4.DkwRPr-Pk0V,pC1t<ރ-J:℈*Hȗ օ07iz9˻KbVh?Po_]U=oT%zucɒW=/|w?ǧ)}-y>_scyw} wYs{O<8_ E@c*aVXCb5RцX*]RI`q9XɘXّc<dhK4+%PЛ8,Il5 G3Vov:Sl<kGf"0:Y'7<<<blP$&`T:3F[Kw4k ÇqA.{幐.oo3A.7n#7o|¶,nF>Ol޽}ֹp]w"Sw=J{aӦMwwy'_=W>>NزeK)/| /:7 '?SHbt [$YܙƓO>ǹ �x+}6gj}{1B駟^u;9'UVl6X{C"*DUlبY}Du̝UR|;Kj$bƲXhC/)nO2zAX<7O2>/h7tE8�@mym]>7Ok$l#m0$t . R@RI%@7 PN�46c2±lY5[қt{k}ν> K `==~U^d,G?"~gmʔ// qicO=o`oel?O35i_",}ۥy6 JWʉLnӨ;tСC D"0QLP% m3_if $UِJ[9dn,1g*wQ4 ͊&LiS*B Ih D는MJیQz3BKP$Pdr49Φ(dʈmss GcOh4kF1'2NSi*B)+&j:fL&gRArD!@/x8v,rQ8C>ʉ30 B5Elbmzb6X66b0N\Y.NVtʾUem^OMڼ""4[3챽D`,"!xx P * 12%WH6[bmxj膐FXgg%5uGB !Uy)֠'c)BR>8КB8+5FX'-bpDܶ&ͦuolJ%!TX隀|#s1Ke$u} ٬ˁLf>f|jMvSqHd si7>h̉:i Vͷy1 u͒~o5n{4iB䊤MOCLTb#TJ_syVV74vfǎ9~s˙(tcc\oe~SCzCibX[*ZҤ05I~olh 'd\ f~p57\kFjM2[:YSrm~bJ[j0%kknݳ~aS1&_{V(f à08C }k( F0(LdvLX_bMA96Fq]{OYMw;cmf%/'~'o~6_  wy>l?7tmy+RЇ>ءCgc;xU?G|%N:Ń>]w]wݵd=t|;/z='AmBWe!ok!$TTqV$*o@Oco|M?:UdN -Jc><s-<;2V#b;}nA}9>2s US#o6my M1 t0x2 }u,bZx;u>ʚxm9svf0`3V.`ܡC:tCMR)8y6IugJ}eRMTO!٤fKKmujHcb"^X5jq^CO{BQb "SfD$+UMIXgRic*x2EpCBV`1u` +ϳј)ۂP{:}DU ֊3!H+FՈhZ+bӘhMQ+^1Qͭ{ko/<Lc%FVKa_DX%">d7f`/u6G|ߨ4faȊؘL3ؒ+uOϑQU@}POpSgO2\g[yodmH)2?7KJlgKBlRvjj�qڂPG%W✣t&>+`C& )l߳ yIg xAkQ!$aD%~]0a]cKӽgMFqF1&$F407Ň*_Iˤܤ}Bڋi1)==IoJ%B~h̊TME_^uK&4}R97p]pKQC5yZaҒܦ}y7ڄGTCzjx|5wX:&F<I8vo#;M'&"\u*vnN_<-F(dϟ[!y72I "0y*fv;3QMRF.ܤ58CEHaMW Wݶαot0L E0*G}3 1[b/fڗcfJN9.gywx\f-!&& e_U \/̍?C"bC9}nHTƣ1s `xUMJ#gNJAk_zmo{wu?KS4򖷴6ssso{iCԧnwqqo;5ŋmRpmя~ロǏs]wmWUn;w y{˝wu]!)>as3Mmu^|Ck\9p3o|۬[niݵk3>34zo|7j[7<�`܆z^T2IlD+h0}_7TC[UZ?0xsx@9Wx;slnϝ֞mR^MKŕROq]X'k͘|n:>MslĴvs7չ8Ď_ycff(8% 󯺝g>K#5W3F^۽۹s3zϸr icIAE6{6onf/r{s/{9kwϸ~Y^||þ@ʷ-vXΝڮwšk:tС4ujH$G&D�+ֵyNʯ#$"%ejk ,Si'fPTJä!}gUCU@1iEYMҸmjdԞO&Y]RS֬f"B\":|Q Y ;D*kxUA(0:2(C1…O:!5l]՜[;NqZq n@U 1: כeTD-jKx0Y97$$OV^SujNiɍEը mIjL8نkiЊ&>"559g,{ _mazyDh ` I|e%BX0P{1W-=̜3MRI t,YXGx@[d929?N犬(MӭbTTfi<ɖ`Gfue VcA&(JkE1,ج&Ac*ݨ>6VHnOdkg*MϤ)D<aJlRԐ Ƃx $uKn^\9D�Fs@%$5ج5yF}<ypęZr4J cr6Tu {OsW>;{o{*zXk!ʐ^a)aYlU,s)KI#%xu,V L-\<ozeg<rx>TG3#j=>䊗~;7tf|`q{_a޽́kWY>Os6u<)snﺃ`a0;öY}XFu`9~f;üշwuСCku ߦQfw XStiaoY,kv0]^2* vC Ǯ۹|X2W<Ǐ2~)re)r<6v{>KcۼGޘޡC:tСf~lb9+I$?>qkBɻx4#'!YuPTL#"dW:)#6$>kpe- mH&eg"d%٭-LbⳢ)UHf7ƀ D@RD:Vj !\`HU]c|I$okƾj F%,&+ K"x?b*;s\l?(OY+_Iꧭ3*e3nW&[gi4d fT#)6)RSL jL}CyQB XWRSzeמ #;cز@Q|Uc-eYvBZg)J c4|3A1/)1$[Q�%cU!- CDu D:39.<_0Vh(CC *z<uxLJL& gv ^(<7X]!s:8shQmőlK #+=gs5Ϣi5h'm5l X"\h"BiX'OL] ~—]$n3u/R݄f)I…e|$K.s陙 ,1&% ǥ݁sh~kmk(o7|{y9~,=DzefO 3=ΦP'%zO k);l 윟>G7TDe^CП41{ƥR(‚G<gmF>VyWXY354*7v//\ض0s9J=#Yi~+ :7o?.ĩz=<EH8udGvСC. z\ޙV/BnS\.3iImg婫>_<1Z]0;s_{ض{;{n2Ux{ٗ!28nİhXI)KtСC:tiQliqrd<@K%U^VENl">fB'RuЋ@LDda!ihYOD!xqK ZY zidjdBZK(3lv5eWbĊPXH}+| X'">|"FEقYRĞqZ +<uv3+ !l LD4"Y3|DkCLVO*U%cPyN8f%|#>&UNJ !tT"Ho7Ė6Hٮ!!dRHQ^c}x"G_jSX3G+ H4 1uq`@!lQm08*X3&!delxMkk9KIo5 ,#e3Ϝ! c&7J7j DOa`mmo<W(S8lm5nnJk pA1n3S8q| &:J݉!'rO#P71/.m<7ocM];œ쥍Mv)Vn#I%c ˊr@1|OȪ*gO᪫qY^Z#9~8G:W| WT] j;hX+ :x΍*=apV{Př$[cvPkǖ+N8}4qG9q*Y[f s U{v|1=7枯9k/s:Kduo7KT5{wz O>Mc=Kn6.ʲ3?đO~ݻs5Ij糧O:tСCоP_klJ}χtd4i>uF/~)?'\,),!TTq䅐rtF#>$+GO^Vǎ>~=g~-O#v҄ԁml0ۡC:tС 3_%rA6b>%-w2)l6l*V٨hmcC!&{Lg-.QTbd1\$AL*T5.>Mb!&ULZ4 %'@c\P'5,S9�� �IDATv,>AAc@$Ɔug-ok|LdlR$*.jͥHNc#e 6${`-lcMO6"�H MLޜDM@AHB 1n,tMIlDP%޴h:f&F lRtE%&TmlR|q) c-3KKQu. X&"QܦLti5*|cuLHjZ10u?{/1oFsRW3X1 S9A2qj*EGN}hLud8l_=yY?q(*xr߸6\)Ҿ JGl{&l:#ag#bl/6o"J:NԿ^l5L U>3f_G~'}b\CH1{w-w<F`me:UReuu( * a}#n|sg2`-NGgoСcG+sDQZꚊ7nG8Ƕ9˃8qܒ/:{2%82b Y}k{|>>+cye~/}|ms[Sgwr=ʙk_'5T1~TwСC^ؐA_7loA2yoBVK` ~C'ٯc6L#ˠc?~ eI0uMjBxY#DkL0la56m{5r<qռ̮5p.cN"??a/>^6iزiRA 9ۡC:t(V,h*lDMaJ6gr(ھkf=AO֬QѠHf}͔j-H@i8B$jGP8E"i!5`,(q#^EQЛ0j* 8ևM:duWJ1XV5uU A髚'Kj8T!MD0kr|D# RcN +NpE?V) L�&N N{"ZFQN¦k'~Ϫz4o?]+0\_gܜ$dw]3jŀ,*bĈ!J7]Y_ぇDk cFAM $2[*]}L@Øl1*\xJ!b$*ULj` otcߙBId&hCZj{/EDl*VhC Yks8%2V&}|V'U)6*"^bHDDnd%2G0`Q %xX }e 16ْ97dbmd%4!:n7loi&ӟnt!_1yzKܜ� |$t*(hhgF,U'jxv)^*8}hBw:]N~km(F�c뚙t h ƌ.qAȱS5{A1cZ("D4 J92fa] 鏞c~Ai9vש=v^ػo׼X^|qϗWxk߷OV-SY{`LIy?}q^eGĉpˋ᥷q*}>vO[n`4:tСC-!ӵUu|Tzl"/-XӃ5wT툛Avs W|g~c ʂ0fk!M߼Ru^*`{7,ΰvu|fnSǡ]ߛ1+_?׽<TohMT7KiQN:tСC:F٬je94nu,[6G$G^Hpε^ !B93В+LȐD0Hc-H" l%Ydcc l`E:b XsgY+DH,.ln`nfy:N Q)Z%Qrn#~8CLzQ+<jH!j&I&c-1FbPEM B 26BOyxO%5{Gk>BVFE A)tk [XdEi!M6LDlRs+[X|9[$2jHm<1zTE/Yg\Wਫ਼|WV H�!&ŠI$1 !sj{T"FC[HV=g `5!U1Ʉo@`EbL 1aJHDp6]lAcl7fjv'Flȶj{\LqQRK`ZTzqU!̹SgNsZF!!Ӷ5ԉǚX7Dkw?LgBtOrb XQϒls~&֒x=Q@lr'֖P,SYj ٕ󛾙\x:ϯ{0 ! e᲍Ɉxw>{_Ku1Moe4hT#l!<uNP% EUxUF?: G@Ayv}=;@"gcH1)tfeo|c{p ɇQ {`8)NJ +ܽ wСC^xv/MKMW7ͶMu ɟ+onZRUd;ʵ7_cCmd#֚9 M[" 9H#eѧp}|Lwl.8//wg~@<N zjivl"N:ڵC:tСCHjNh80 Q(T5!d! 񘩾Mkf~@E&@ SAK_rI[rfzT;5U5Ly~CچgW&Ejְknnfzk1O=u`F+pqY񘲠?u XC"W3 jx"(q(:z*_S{6k A )rPQ\mԄI͜�9mx!')5DP *nմaäw\�d'QF#WK ̈́vdlDM3ʂш*eYfEM}ސMa){=KЈAbLHv('R�Ugm{ĜkPZ}dܨc:VME HUS&΢!B;ƈe"5cTR,{T Q| 6N)`$2 #:*e(x,GHUN[ҵ0?;KguyI,bLvȥYM<#i 5v`8vr=na̦5\l{Ip]HrEm\!Ӯh29†ђf$ųcr$ ][]46JwVZr8$Fr1THm0ZsvIy&I1y)qfŝ+n}-1P咃 } ǎ4] O+}߳Tbyd; gq/(z ?Jlo} ¿S[ }Dfiˢ`mm^mݡC:tK{ ҈TvS%f^`˗߭^miy^VXOrnx׼_X>~9!sØԁLz}WpR֥bZ FZ^r˘6n6>GlBF3gzKng4X Y5)ـ9+InqŠbθL:tСC:mFz!nHe'‚$KѤRZ5sZ! iBiZ1M,fWVTuͮؿo/N3e2vZ(JfsF+kG *3~TrnG2ɓh mmK)$+ڠ$[0!ޔ՜M) LL9;jnF&2Hk頻On- c G)e(LP\v lɐt}D51Qn5.&[Q>;+m4\&9 +zD[ !Z*ƉW#ƚ&3 zֲD%!l$bpHv$b ιLHRFA#!$Yּ4#!b#OLQ Q5{r9FМ-}PMd"~ZQ27CvlSLk ɶYԴ}D B1J!} Ĉ5]wkqΝ]&dܬGJƘN6SO;mRN<L[bdܔ[4'恑SRZ6ܗU\'dW.N#Q*fjV׸I _6g"8ǵ<_2ۏ夼1_Sp ! ց evBX;F[́=CoZHoGoB$F˙sxwƖ 0éZbaq;g9X# }~I:܎y&."}]WW;tСCZlz7m:�MPkMSNS6ld4YgcPfۿ�1ރ|joʙ]_RmH.+@Y,  cx�x'yÛng`4Z<Ywe).4:l:tB3lߵUUs9v*:t Պ+U49 L HRco6iӷ;JA1%o&dJ)*'[F=ٽ'%u<sT(ֲX}(l۶auqД%Z{D O*eQlHH"U%KW11^1!1^#V&T2 Ĝ9H">}U}dQy*RNJaPL-X8eg#g2/d+(d֤ߔ]TD' Dn5lXfZ6NiV]qrK Գg" 3[_cug2C@MęW: ({%~k-_!1)H]Y>p׭ԘiK`G,R1b�r~kUU kYx4 ֘tcI+>`}PH Lʔ b J$[ ~-o"PC1 ,ak " sJV4){KEE& `4�SJaxb ,_Bcd4e4UGcX;3WV !"&)]e*3wov<m5 <K L['Vb*VMNInyZz2gc|{Hf/8[@Dsx3`Q #x۪g-1 V,/|DTÁuᱧ FK'ٹḿlգJ΂-)[CO`0_rvALqmcm,*/zo<Q8uẕ#Cn{Nzf?ff0)\V=0fvn]{v"_Jzc*>Ayws':>xΰ>!"O:tСC .Zu ge~c~1^jK_>M. y,%YzqDs}޶= }+]ʙ~ :ջĂ8^yKQNiS5ծOGvСC:lSG{9}?G{;S<葿wۉI>+ٖ6Ad,Uj`M214ZT5&RV&"4IГ~.xf[IبYF'N`}}ͅ!\PJkZkΜ:*у : g)DU'9~cXID1(05u (!gjHh`TLL�|u , 1L!ZG$%f*q5b}}:=>jV#[l&풲CX5Sk3;E4FIjD"L97WH4 Ȯm/;FnzĚVT!POCK6 YeGg~v~O\"!bX'rS+KEQP(=n؉nVaXBMr7VƲk";p&[(OeO83(~e8!:'DmC ̷'&2fuBuQ<P(԰kv+wdWe^8v8Im,1,,,wb@n~l=EfN)ura6<߶~zϥ:bʥ7t7i˅c-yk[Ć^WUuìT_B=Gy z'�_oзxwff!U`la,d*n5]ARe.u' \w 5GNF::$a}s,/WC6~8Q@THXbb0`ii;w~ޫ.MW#gqŵ] w}~S+zpdy|:tСFrU}J;1� BlnS%*`֌v>[1ɘ! $hqi"F!(!ԑ1%e,ʂzXAQpY~#>xxag*r!l'epM4Ns[fj4}ǡC:tkCėoz]t='䱇X]]Ykd8wƕ|W?r3O9uxX* ɺɠ">iw\ y*Sj=y@jbP)bYi;!HgL"HQ4FXW cccY-AΊd[W c4(>Bcmo Q p{'D5XQ+DcX= *VS_ɇhX%fk!YMh�jq(Hb*A4$)ԢzR1$U%$pTVB޷,٫v?E6L(f9 ]`DPx ˞;ٻ O7B2_Ձ=9l}. 3%,bBL 3d늢5c03`f0K5Yٚ3K!*T>*DT!%bK܋Y*>W]y }qVhٵ"\ 6dB8ԏ&5ar6Dl+L* D{_I"U K1J/FZ`bx:*{\gb`MRpI%|~#++skGT2A=1]r1g$C G'. Ye/\Q+S}[m;rz~pv<cy'd}{e. gYjxh鱾>ݴn\1c;oy |!{l[beުl_>KSqܓ#Μrrjzzϲl?. >@]aG zXU=W"fOam} u�>ȕǮ]*#~qlig0UBc<Kh:tС1mB,SʆfvP7Un6MiS,jTkO{u숵[^g0:bfgAEd8k7:Yu5=u7ĩ'̉,W5,N< Ӄpn\�� �IDATu*D|ooY CmGhC?xdtСC:Dc td8w~?xoۯf=,,cؑ|?>}_X:{0UE({ ;77q;KK+{$߸1{GE({%\<^K;x?ɬlXT<*46ma`Ɯ$emLƆcY! $L´DɪHjAKk2ղ!| DH%v9ƚU.bR&uX AGѦ>$ 0KmM'h`$oUDL`2SʼnQ)*h%QB& mahh,N7V+]sCHDcʶM'YX- W 9MSffEYZ=}=|*_ k:&E,ReGQXXgHS'8acPe `f@R6p D_^v9$ꊪ#ɺ:E&11UUY_+9nh76[[RjY!۬E'i0QKƩ HR@Im,N n'ό_#kuP4M ɒQLI*sgrn|"VڤH<&!2F*qORUIw`@nur7a"T3>ڜ\cUdklf<!6 TYK;¦*# rw9ro>9&m<5{0rӷo_<G'y;qYz}A JAȩ#Taa׀=WqvSoLYu�`>vaǾ?:D?ˮ?g>Wٽ!Dt8kŸ|a~9~_tE:ӷu:t"stjbôޠ8ݜ(oohHԶIixo<}ٔr)(Jq-<>ƠWbQqQuU4sQsUpQڒJ PV/s6P+Q ڶYA�yoZ"Dm<sAi::tСCAoP0O1 <L9`o< z(ԁwr՞+پ}eQTUҙ>wO}O'>LJM^tc78Ԭ3bH :tV2#"1 ä:!g]erkzB ! �[fEłmd^b":u,ΨGQǂXP�^n[N۫9ސ�Ac9kz~# B\=lQ}* J%NxAX4idw A0!"9>wHBpiE DNĚ@X^cs{ZXe>Ø8XcWԆB" k`SƢL^U.,j6mNݻgR.(<`T£p4!c^OԳ uL ($Z|ޑCj<`y%)\F#ku.ZbmTWY0M $֒i͠5,[d*6 -(0΢@ b@RСw4XKHx%A'ٳgR,Yv-hpZ I$J#Vx`!EibZڈաޤ%4gKsIg2Z֊Z!MJ=)5b>ȳ lL5JER;{]|HdlP?K,8Iv! }:+! ഀU1,߸;v18Xe򴶒LxP>G,#kOͱ"փ'>'#GM8A¶N[J:2SeכeaO};2/njn&\ROfsoL$JQO-Z!6Rٰjkg9ea+_;",k0qR汵tM,_Z?uSvt&N3u^EG'Nbf֢IAv@#^9- Ip_`.V.)S:paӘ`>~%(uv/O:ڵ(;(wvR=umFmCEu F Y\fsG*ܲ`6^fqܸn#;($ gL03t6|B,B@BHBzlxƢ %DVI*t'X!<. 0eyyM=ސ)UCدU`mFmFftJJ MQӷe'f's-\Ȕ)S( aq;SHHٶ{'+]Ɋ台mt^&hq)u&wR61Ȱ,Hxp\)۟fdψs7z1EP zTQ4B(Yw8ҧ(8e[<BZԜ"cfS:Rl%Eo*#Yc 0ƒ Wa- K $ktT^QoO?6M19u; R8+qB5A #$BIz!HhMP:7R$�ZBNX>^6cE -'O۷3\ɫ 4g`#(1JBz|N9֡�>8'T y)љS*I(T:!:ULZ :)'C;! 5>OE8GI+J"q$ZcB!U֧AŬ5Jh&`Q^DhTR+E9A4 S\E'5SK9λH;2H}2tߥPHB[✈\/|$e=Fxtz;6^>8IqUV4#-Hq6IQ<N"*C"v#[XkGd :"ddnؑSM1-xON|JUVBĺby|*weJږ@pßJ#tXBP.& *"/B)znoN_b2; ?2QXc2oL>I<\T"1)[n]ˎ13=`[ STnl\O;29P -+ktO?`; ʦ`ڗ3:IDuh= 3}\H 2 k5N<?5S, 6mXOC1.-JttIm/ܵFmFc0"GRC,B\C+dI:VByfb}ǝud)7a!#NZx1b-2L6@:iTkal-JF.H$!/OsHfPz/䱣N,BGJv*6h6hc|L2Y gI(HHp FR{1}NB8[X  gY8'|wv<*K<)5R*42{&Oǫ6B`G<H!E!wQI&1t *2X O{T  DY8G +*{s,}sXJ` s'J`jC)mg 74ܡ$r LT@yUېw9˖P:T W!=x)@h!( BȆVp$$: VUqD|2(- D-ZQi̱)!ybTd fbtFY ZR[IdZCAa�k-N9EV9%B@TKegp.Im͛74P.iHy iIN@[8?(%F�Άva Hstpc Vo }6Ѧ[:T:_u;([A*eFX::$E'*v̼_8_kDb ) K >uΏh xu>UXkQJm:?.)B k}*Di-#`_UN=�ߢyd~t8dlnY O3{Ent|0 '];cJ{lb1`/!dʆOsyK{7Kqa'Ƥaͬo8.*3<3UEpW=-QStvJ(kj{y~G?MT$!ip+^1KΆa>YcIo,84SzIW Y(~5) aÖ,[#D)43gͤ^=6h6h&F[䟛9&!́d͊f9>8̑/ j=]ԨӘwaڲl۵tq"Z,Ii>@ !Ph>D[c0xzc BHCUa҃pBH}GpQѩ/* I{cO\Zs>"?. iOl >zg,mC,xP-Ðt:H$eG'#1șt=WVv5'/9;~[Jx) 4ἳY[Xgs7�6[8Nb%txs_ so憐PHK5j %'mR8LȿڴA>J43AȜeMȾdy]㾑z՚\:7)L^r@eXE,Q!m z6B@rb,ę6*wj{5H\O*G`̇w#.p;tNrE6Pp4kX)0J!S ;R6_{C" H- ކ<}}} QT1$R ](0<\yM,Q)#.'#ǡUG!A(2(1D$!.hВH-2cmtylrЉb2�@G<J=Q'g ˄>9\\ Bܡ ڣw!o#UF*.l2"Lk3/>Ϟ\0?aC}H #OH4wt-{B,<'TX%+!dr8oiPhg>*eslDd礣 2# ":T8dgBf3Zixw0h&d[_C -kI%;WEtH <G<JquPR ;5P fKBԸ(w(ekVL).}9x;{3-fL^ޙY3w9b^`Xx:KÆH!bݲ;gob�,8,1iλurbg7'Ob¬:6,Gai--=#硥wrOsf ]) 8g<n]FmF8<Hx#a_0JX78AUZޖ�e A59jxֿ ,8ws-o{q1?ٰP!.m�A5X R8;pFJJzr?PPt*H8 GFL KjQjhc6ѭ╠/5t�;aC RR7]gi s:\43eٵ┫.[8+0|?q YӟҌ{6!|mZlsls4s曾ϱ/ n觥O Þ[شa'iJtL1{B/9RݶA*u_ijN<P !Zsl^`) ƃ'Hcz褌�j =KNyrUF"c*0 M�OS/[E="G[ԡZ%9qbH@E'x\!dTzyf <ITzs*u)P*GZxWwZ %$N(K^ӨYD\ sű1VASc@Dʅ΅%E vĥšE �d'RIH΂`HS]TLkhc9%'|P&G$i?մF^GE,5N(JiJj@lfdq5҅R䖪"QJ# $ S`"dCxk=(O Aq+5RJRTB@ֆuQNW"ܹsS1vU483g}Ֆؚ%IF@jc�c>T2hעȌa B Blmdn+BWBjAAy5#Hs:Mif7b?sֆ/ 9}V…Via-h=sv}!%b7wt^XD׵(K[-[W\JБ$c>rBd8N, Svl}>- ax9{>X<W>=~.!ʆpO|JISO u* & >n:Ȇ x)gu7SDzp/y~kn䄋/fH+ ʖ-r=~ֹL_|"s=OaKoAM$ES&6J*6`}S]MM#?mƢePL-ǽd HERkT8j"I0fLeyDYfRcۮzz'pmfjz\6h6h}ͤZ0rR-jؑ;F\[BУV6 i:cXШ pǰ98kIlذD7s(/02C%8oc&5#2T4lnjr<(\TQfnU0AT* ձOr쫯┫n^=r~唃\{Eo8 .\P{~{1ө䍹*P LdĨ]S;mC,8t?~³/cʮAxY�zF'c7.E?3Q>a1K?Ι7/?}&@c$5i&t*C{!&/$7B`J5*'x"}غs C#T%9lm~$TM _reY[[sO ! 9MZ37l$z5B|!D +rÌ\AɊ C%5I6�_ԕJ$ J^\ ,i&(uq%ؐV& zF^ >]̋KPQ<X)ITѲG΋>#?8 DKɓY0HضA  P`7U9X vm k3q DT6g.{ih1<8+-AՐVT ?]lB�[)>m`Fy7l[O>ܫR) Գ 9+' bpt$ cݶ y G ST5 Bci٘Y6FmQkc@`.<@n:AƠTM+ sn?;*C"l!<"SQeTܞW٤Z#D^}YP[`ZGcO׎}X?9ZVBv'z8Dd,)~7- ދ`7 %CZ~G8eq3K|VTvtL)nAmJVj-uWԹs֒;4Xdpn6z)qŗ9y Ǽ2/dwÃ,k-/Da"ɄyL<k /@'w~kز^ι]rN=8@RRTy[0MΐZts1WȚGW31\Kٱk2.~rWxܺn6h6A=-Q 9;Dm`P2p٬Ft�F&�� �IDATP߅ R[ٳ{oqc goi _F&sR%EcQ,1JQ4p`cݘQ՟t+π.6iN,n tX ه1X8vΔC{jϲuW̟!W|(vOVKuΝ&N|6s;yj/`عe]3f'w}OÏ|]챌_r|T*7r?Lɗ(pQ % &e#) CPc Q!j/X*luƸ+ܨU< o[85)XWrRy63焛sHH@$#i& iI{AByTFIV `ҘHj  $WG xnіs6(5C؅ȨPXHd:a\;8O%$N(% i}k̜ɒs٧0zY1q 5L[<KdiKi)6EkEu3QlE#eb,͌hy=GjFG+(ZX)㮞 E5'8 du  `@"uȧiXryk)@8"&8VHߘ1HeŚTcPdP@h>Cysۓ4gA-B>6 Η6Sf}-{ LcuQ5-BDgPGf]\HkD'J EF,8ϖ ĴzIN r¶VEr+Hē6*v')w>IqH^y<jv<"㹷s 1vblv>3~],o`&d+Bh^F:ԗj唣( ([b̋PPއ(p^=�~)VC$I(OAJI lcƭ,p0; = 8 b0Bt^awÏ~g)Bj|KJP As؂yb)G塊3ud U?y.sORr'&6h6hR'E[Fyq[è UiM+G.dgm=g]ȶ\ʪ7 %HZE gYJ~N<( Il$Dv`4Xu68J"fH!)kMOuv 38@*wPVYlZPqt�CT#= j(-_(G.K3iG[\yh㙋KN .W}*�3>}Ex}4w|q8={?qW_1]&֚y 7n[0{!R+ %T{| "VzqP &uNWOnj2\3oQmR4Ŋ>*b|el$_[2hl7#)rXsPL'%ކ9M VDT 4DHmRVJt/SJA6\LQ0qQ'Dyp%mL#u\-dNbr*C2G$Vki`\\/ PHuӠ =&t2wDeʊl(uqǰo`5< T4,^DQ2: u4LgT8͟ J83r<,ܛH %Gc[ 8HK ]<} FH=@L#ZH@I!QR(e**=^(Rkxl1HTx>u!"= cN`\N2 $JWp6??ՁPx? է,{I.֗|9.E =&=6Zg}XJI5ZkxoCx4 p258G|-%Y.Zn-{dM3|c] 0[2##"W<YTmuՂJӬx_{ 7!3~=y0w Jǿ U PTۗ9"[vtqC߾I;8l?<~yߌB̤91H~0{fMc׺ͬMX9sÂV _G~v .z+8괓Cz!dS)Hz(8q T5)( 3̎,<^|ً6X B$tumFmh΋0@eѢyl naa6L B3N$cmJct6<v-qaҌ{n_ R)!gXs/hAGioKk;KAyRQfΌLВZ5+sq!FUCbv\lD1ӑFv<f)-vc<6͹o> Wz &vs CZ6caG}6,LeX I'.x66>Ƿ‘|Rʣ֮`Eo0D &ka+ntЃҠ4BJdR@KB >D`m=ФB1ƥ(oq OY+56FW{C1R۔3-JGHh,{3 H"0Σ D b< |nޅ򅜧j2?>*uXk#(A)0`=Z)$ k\wkJ qQD둙.L2Lg7IB|R)PJ!nRWX[MbԷ4dŐ$ځ7X9e=qPoX2*efգ-VyupVAy*Eɉv#A.B)PMTk-. /:TV EQl^9tCl wkI JkPّ<RkT8cq2l[Q7X)tJ **! )aOlv,�a47ll[@!4AlRF5m Qҷ) AAmx29#\O*Ҡ)!DFXk.WR#Mb.dn ziF(nE3>kjIJIp|FN^%VQ;pvVvD`b[~x]c΄cXjm!;0L)쳸_Qa^ݯ; u dp1WqeC_o}ˮdh:'MCGR5R={ΡE29 GLghnl`7A%b9@$)1*=}v�cgKQAGVbS4kZ+vlwu֬\SRe⌙e/>IumFm rGD*asMpb6C4'\&VceʈE!>iȃ [QۺB]Ul\JJ1ƅɭ7qbQE<bhQ¥i,I@g)  {*4e9/vrgxvD=^ !<?BhrV#`Ѷ,0;r-wT)<>\rQ#&3f#tj�<,:F԰Y9,YϿYgCfwtz\̒.! wR6 ՙv,7n[|o ߯0S<jA]t|~ĞJs.E/<-2U< ^r^09bm[:8l|*]cDxع7pݔesҩsڢ>|�F~qؾu|؉<d,lx~,_zB.>K^t1NݗFf_x -poB*9 i_e1'/dJR=aEpđ|OIUwp wož0TO8cɲ[oxw}^3THSHq0ny(S/~>'>] qg'w[}ɵ'Y=k㻹߰sNSXQ}_ݱ �cq'rɧp)cՃf\/<{-Z;w[_h2:#[%QpAnEٻiI&aP pҡ &<烎:a�)5Hє餛{\UsµABޫs8!E'ZB[l#;D>dYiTX \T9g>zQBh*,IOBAJ@i ir07RFe\L`+#g-NI1@ETJ@6R\7 T7P~LBBjҁ oB4[R$&XP*#9àIml܋ CbA8dB"80H)${ISVfLd$)^1WEJ 2:EGJTNI)QZAY %Nz쳽h-*Q=N%:܋TDT  U t zK:BX}wF"] !5NHTx΋З6Ȟs(鑥TB4*@̋`mea,@Dr3vHF+ BYVC$rjH@I)Rh-ўS7ԕ5)�ށHj#@pYfKo@pX 2vE9 hDq\װ"1!c1qwD"?Ԑ(H@'ٺK%=¹|"_m<4ϙ{H^Z~=#+2*ǵ܌kR#ޓ76NT<|~)^]3RɞfMOvC$]5+U*bc!fΚ[f1~tYnDٯM|]%Ů~ْ[at?[I}>H'vP,3s'ӏk*;v3e;=L{x3q֨b93=ڠ06ok2>-(_kU<iuA' IGrW=;G1mt̺GV쾇z˔S޳g` RGz)-26h6hE8F,;^b#"9⃒&Ȩd<tιюc(ݺfmTuJH"($:9lƨv)ˇPT $R@Ɲ;w!nݴ=^p>[O%fI**%[i"NF)c[Y<"Viky婯1~G|5U5_[\yȃj^Og\~ΒK�џ OlZ_ʭџ8}?q K&֥~_^F٘n~/'~ �ڝpYǸq[xYoʉo<t˸s5y"z|o^593|i0X1b~_>}ื|[y#F ϲ?ħ/?~Ķ?y^u7aÆ˯G~/sޱdƨvW|\6t>q6WZQ{_^:=ޞ}Y8?rљo wo\ϟ;iW5_[jiX\}[.nev1(twiǯs򓾏?M6/ƻU ST-ݕƼ'c6A2^ҭ){&(T$l3Q(H+##:80{-ݾK:ìw-of G?~!h`[D8:r$ZMB$#cK y<jy2r5;NޜWퟩT#ߛ2*sh$1!C^U2'ZKgjHqAշòrJ )Իanlr]PG5.PЅ5 .pԼ95{l=8?$KQ@' J>\/INԚy籁u3ޓM+ٌCx3j׻@ Aj-^jFmvԱA >(F3e#gn'D ]g%*&W j[pR#!mse5Y^W?o-.༳r᳷y pJ6p΢mlZ\*AʨҌj&]rYPV!cڞLq.B" F3 E(bMȧ< ĮV�ֹ-trώ `3B(.c{Δ25,RSe˘s#֍-zO9G^3O{ ,\=x]Ez-,69~~͘zH VX|ɼ s̩<tÜ~i4*U~nBrgw>{ ɖ)G({?[7_ Tvlng4& g =39m2\lFx&X$%3 H}KZJi0З"& /z dKK]*ٺz +[F=&1iJSX6K.fμ W?FC*tuwG_zmkۗΩǠ;dLdW6h6suV7qp0jBw1:Vh禨.XöYG|T -}ܲ0NJXgA!IH^k օɷ -PJ4L.(:Re$X2Ĺ (ָmciL(Ťny?lpcnq*-N&е~z576)\?@Zo8{- ~]yy6ݛkW=B_EsYq^~ _ >dR cJ`9UgoO}%'3:P0LqG?^?z{>Iegq囜:t5Mme/C6Eᣟ ~,:KZ x/c4ώ_ z?6KfK[^ֿY3= ^0a^2X~GX?w5ׇ߿IrG^$~]{_|9_''>+8o}`f sa?pq8翓ȩ-5k!cnz<t׹w{ȇ=f?x|l9|᧾0F_ٸq;TS΂:T̉3ߕg[fhlO^�>>^22pa/C?|e?N~ O?vdc<m)wl!Ux9G-nEG^;ȬSc؍zv321VQӠNEeII( ɦ֠ٳg2oQt{ j!bwC4H az6oބZ1u < Aݻlm24Ƀ1 F۪j7[M{,R#~oH!FZX8\~kU*EPv.Q İ d2X"' j<!5 J$ .yNSM-:p(Ԅ!$;8 A%*Q$it1>X8+AG5 ݜv{"vP_իVTXBDTJ4M>EIAAkH90i"-skOyy%@v)*Uz$ WdmAFb$eU f>""�� �IDATzRH7w bYϠ6(ZRO@ko<)8#)&֮=0R:"dҁYEl7A&eW{XAYk}PX{'׆Fhxts8@ X2ȉ:sɂUѨ9hN%|R,A,@cZCj9,ĵoYľdaB*>f<r1<9bD$ $8%=65b@)=*B$({1c;ی>8cPkNk [kWx+uCcTwFgeu- r˿YSg<![(顾s'*D _GVX(1i yٜzIL:\eaSI? Skpˍ?uJ0% ră|\7|ʹ߰T6o\DϴH%iTjTv{攐W÷2> !< ?Tvqs_Ԛf͖"[ҌRu9fʴI{S1Ԫ 7H{g_x~�zz(cv(a$]qnm* b�P"\+#Yx>zmFmֈ-;njR ZT XF!;]bLX5OhY0&Dj(s49KW 120kCX'>E)锊RfL܉=1c Y|;qaU%-4uONsE,"ϣ&sbj켣5OW!>qm'xׅq_y?G?l\ȅo_ Y{|G?s?l3*wߘ-hsKx:̹obK?J7xמ|WL*[|뗟Kgo?Dqoz׼i6oxoMϟfQ䳬9 ܗ=<;T}b?6-d۹ϲ$@$_U{5 0,foz~׷9X7]g|yH;ط]ū՜˸W$Zq~ķoZǩ ?-:KN C䜗r+8x8z+~1ŀ__mE_"|z{JF@~>W^?fF_o?'76<0Q?A'SF=C8f)>=KSGf?5nˏ,O9l^EtRr"w,͇sygǑ4' F*xtrnZN7=wG>LH(gْ%KA8�x1,iIxš56 xyDc 8`䜐%+XeI(kF9]UU}hFEo羮ꮮUsߏ%(4^(h GM/nϣ,GFUNBD c z)YP3RW%-q6E1BJ .eF\xOb*R9b1,È(etٞ \la%ޭ滅3;]m;UA$a5A*I)6a@Bs#ah)a`A!2T~nlsA@(mrzr5sA>w/ś'*d+,SLO`m<StutJk8tD J))r̪,ϬHrJoHg,`b"iȅyvtKj ZhrJ%;JE@Rx+RX6N c4\b)]V22>xb  cБx5b<Rᬲ#\ʣTǸ<JXn %Fx_ձ"DIˬJY#/#^9Z \RB1~(owmi,8f \fˮ:qsQ!]m3=g\]Sul+QՐG;9d[fE2S1ҡ_|r)!m՗b%%vw{IӦre>^} y畎 hάcOsdK[[Y[ziPM?|-ϭdxӿ}/aERXv|Q΅ͱl9k|/lbD֭7JX�r!ݶ][MXmhji$@i&#cK''u Mkjm;~(]AWnE.~?ѫ>Kx<s?Gtu'^J49?D)v:b;`#d fDݫwMite5`Hqlu(ş%ceIJ_'ĉ$8) ij0ڒdck1 I31TIر{7+6oLoibb."1T4hW ⊩mrQ٣1fꊱod|.* nQ$dEh<ɃMLҲp_>+Kr  @0^}\}yů}?y|}uw|m qE6x6Ws^Y#Y@Pg0ɻr ՟OO^nw ~^Kyc/,u)-fq]~koXϷ_ |2֡0n: w?UI,^>u{4~;o}<]q_퉇<y/ij]Gנ^o{+e kk/h ^Hxvz|/_~ij>̽_X?}?Q}lu(0m<s[aCUgMjcu4>ήНY? cP;K*vhǵ# $""DQHP uL^KxEs)Eo|/q|BK+>mSO=7\|_xsE.#א#̇2D:bt�l,Ft$\XgR~gZtgQ`X J Xtu ]]y:5^i=^H+Q8@DaHFAH $:IHa@Lj<6m5qi XrR6l1^) Ð@)$FII>(ETeXC<lI#ߐ'!$(4Q7J0mDa�$GR.JI J?su&3u+=-G|ܭ\2=-R2Xkl}NbAP+[3#5Չ©HEvuǗIISMW)\H..8`vmb4ZZS˔":MR@X,q/ rr] 6L=cg4ڊZW c`EZ #DH0xD %Rb}I1VWx`ށJUya}{ָ{l;ݣ*˱>Ǐg5g\?i�$BTu@auMdy_ ?[<TUv:$0wô_B`Y}bE={aT,S3{pɬ|oб~=Wٵy3 --4Tkl[|#t,I=u"Sˎu)uDM(r7'E ==|n 6tP.9؝;;S=ٚ+=T:26D]˝G![_GQH` hkSwCtu"s=x8F0쥍;whۏQmYoG.p/7Vmlx*vY~]ȗgשh#F02x`08l?c=e+'pvXc~譇(\"cK qb x)ctX"򁢻Dgo4 !?\A&)ϘB( M.ھNd-c `8v7e">zCl5>N>?U,>9 p{Zǻ|K˷|y [λmݗOؑpg8W4X~8Y4ȳ,>|9NWÄTWsnoeѸA 8.s[Ū(7}jq߱xMZY/cjp|sIc֢wҋ' 5>^xpW>~Jp =� gS.%wͼc=T_|c`W\pz5~GNZXm s* 9\H$0 ,avlKc+eC:XSXt10cf8WiDDi]ʗI9f;Ȁ|8<M.ǃ; zzĎ3ĸqILkA2ieg2*8>hC ⠪uB@fU^5 hll޾ \uudCGT-xWV.(k$s(  @)E*,II$Fi!R`-ISlj\T>Ϧ!Vêk[߉2E"QJSvdNo\dgyqJF 47(NX"P fX7$c4&T d9d%Fk>笷5DXܲu )K.?2%aƵJMfWܶMFb^+%ZH:{U2bWg}2[C g[(D45 Pt{ nlNZ)*9TFQH$R.;pX B3#a3o\vuwnxnA+ K` EiA *ɞ5qpfِjA5Q>{2g/UdV6뺯;|qmk3U3S$&MOzMQvrm `~i pjc2@[;EʺUe0QTbwUyPc1 dǴ/(T3~B;'ODM'TuWű.qs9I9{'Sڹ-y,>,.JI_Kٽ%?v5`ɍnacٺ ʑL7(–^;_BL\|1N܏yEo)O~GB0}!Q7 d@˓%&ܼ cرI2HUB1{F޹’Pì㑳.uZIM3kw7;z bݴg|i%-E�n`#^ fÀr}'Ȋsd;1_CX+\Rw&5EZKIS}df�XB!EC)X+pJS[iԹy4S`H=``m;w`͹OW mOlqtAo^| *"˖_֡N?y1!ڵh@M_̇sMcL9{1k[Mp*W'/=mGWrڼ}oVU֪/ PZ;dU{87x<<p{9r{߿ .\|lZ`wT|݂=p~N;(],{x YAo�:yI/_yEUޖU#yjEeK3p=;*Ivyup?gϻ+5/lC~5x ;|,C~<.^<hp0A}JNo<,el(8\@PD9K>DBģ'odH"1<K#\v&F)Ty1e b}l3}aDhu s޳m'ٽ-ҥXt#_njg跣=yY?j39nD>[QdG?>A20FբJ*yLNi s1,8f>7m$_!3kvxWdZR@I@Ԥ`',J(rSIK8F-B"a [κf'+*cqZ1)J F#t;iqyh" Zr5DQD^I$ g',0 r̜>b_xea-V;sI2>)NGMn͎D2Nmu*VIEkr1BRISJRyE !AuWa%g h#A}"2P"E47hhȣFk5IjF8R;+d%gm (a.bvqɥF4ڢ�xVP܋Gu&q5ݬ  嬔SEʀPHyH7 q8 d9c3c AŪsޡREcI]#i¦M]JTxej0 =O39کL_7k2[JE; 6W23g9廯5j?xEQ.Ǹv6_~|!6ŲU'Qjljc[>e27}s<}=8RlY4]RPsìCR`Y=Ȁd\E (Ŏѳh8Z3E(vENxåp!gz�X+VN4]6!k"1s-`JtҞ~D>r A'#]�ig%xCUnNJ3N84g(LhJyuXk~"ˊzo8atKֺcqێ`#Fp$1HJ7uIGqC)IabwkJt ec I![Ӓ1!ϨBM:t)%a �A1 .ƼB%.%Բxx1|sF6ǂ Q ],ͷqT>$Ԁ1PĚJZo1wT&U99=ٔK싘Lݕ].H 5>4<y/Ʌ5ܕ)9 XI/M<mzG�*G'>_C\ EQ~e<AwۜS,|"?5W<#>Io*eTyr-WIxH/ mq$qhP> Ovg);ųiÉ yy'}unY4&{C^~ >x޿EZk3~ M$D\> j LGK)"L0q1xܨv&NSc&l�AC5$$F!YbB"Rƌ=,C c zx^'F촾Od"fټ{W>uؤ{Rwܲ<l5\p6Gl/!QŗX=}^\R#5-{W,u:2bkKK ӧ J).+MlV^dVo%K58+VOL I% VyW r2Gdhk(ʤ2"pƃ�� �IDAT!-mE˅mm%Szz0()2 ;L)C*$5aH). IwU8l.4k�O6B�LJ2)ݧ#A@)j]c,Ƨ͎D-OCB<HiRL+S &**q使.o)YVeg}Rk2D"tn OcK1:Օ>M !B`iB 0ƑĴB`r\;.Ɉ*Y%_Z[_<v(@JmDn+5hc#:A -(B( McRJ坯WD瀕ZpgoG4+vrf^Tz>g?P%[y>@\cke eazV7%W_pHbk-JN+"I J~(kˮ ȯ~Y9}}vXzE5`%F ߏ+4u ׯop[-+&Nmmili]z?[7l`wGa>9ʄSrb2~<vlf˂13c8h֒l{Ql0n,$fϊL\{fV#1to?|䛚uɬ[$g߾m7-c0$K,Нhԡ"6-M{o1#'�子7ݍh6>z\wPr_YO)#?w\=m}_Q)}_YVt/mK4!;`#ƠёÀyE o]m{ )Xfrv:1eVXSJX[,jw7%)(Mj`Jk ƶЖϓ;]*C0 a-+bm $F)؈'q bFs㚲S:>Աa]㤼9{FYˆ^8?fK U,JWؚ[8!w%xo8Yg�wyzg{dcxa~GW j86gI^hаk3߷_zV= l!߾<y%Nyu ~B_ؾ O`夷~(CϾ0oy{8[;;s(XXG/_$7'4ݮm^Uf?meޞwL{hioK_gnw/>g9q@A)SǵNy0eDK4# $*'CPrMci݄( 3&M# :^8dd6yDO)Q80#H#7 <Vw,e:˵a8eKUh7wǷI ܨZZȱ2h$NZ0icɇX{m ,˺p;S @Æ Y%#xO1ShCjWm,ܗ5̳yV޽kdvzE~RJЎC@Zf͛ڽ;hSRF֜Ƹ22lU[,2'w%85t2^1\cՎhV1L5i F 2aL<a2g}6;wO>Boo͈(!42ֺ|0"ˁ(_ v(RkCWR\DJ%IuD!Yb+AP!!I<b#)$>5TEխmūpHR$JԠ 7]/]lu.kVAF!HYzmC9T Œ H`hIIA(&$ߔ܀PCT#6M XU J6 Ŵ`!1>(VM X"u,SNeӦvvG&RI#,q\?*%YHUM(i.4R_'oLLj|H{x&u VtdZBswN'DfuI; $5ۺkآ'EI܊!U6: DG<碖� 1fY ';7;!B˝^@ak{ma"%I2{ż0FPu�OF}2]ummmk%:$0 .fѓƹI_f˚h?z)Hvcs&si2ucre$lyl ($aglbFf Yzׯٱn#]FGaskHR6*Ra܌Zg󀏂q&Ol{q [~nCN8ND܁ƕ�D؞i鹗keS?zd\>+vwsnQ2`#Fpa}i}W~P0V2e7^_=mtG&~zLY[N]dͶ@F"洏flq-<JwICI)Y S45ф-*)9zLcF09Zz҆ؾyc9)[wqQԟi`[P`J.L9[[<<#3f^C&!ΫN N<՜WXl'5oh)Upp/NUzy}GWgx39$3d\ϱ֯r;ʛ/]Y]hmft{&~T\ĭ+yrI\ sH1 >vn.S4e-~޿y!3Ghe_] xg^xtp `+^%{0q({b=$VN:t[=/ {<^د@62Uss&!6{ G&^[&1(#Iœ@~Û6N˙ Fǜ}Y X~5Wj0DD* QRaA)g2m# <ت58n IwWŮxT8&MgA8z&,l9zB} L=D.Nn}'ǏcLJkvHc0i4No~ ; vbK& >g(Kyx^a͇ ~:uY9{JۊjJJs %$q[8Ϋu 3ۿ\_; J9\A[& O*G,xXteqDk<aiV!_ȡ=tIM6[{ BB.OhjjI*#)%a*Eծ* A ΙE#B֩v&H)Ʌ4)eJ9'YI=,yA TcIYoӟw$!Z uc5Kő,ނثFNi-A7| lLl*cVM \kLJB@PabHvTcufAm\R=rXyW#ƝZ/zH!i_ n`@b%p:nZ)IdB]6mFQ*[Z8qѴ6ܳ^]!@[MSum}܂D:pl5.hRn0UIĚkoMH!R=,%e1FE RbZIjhgw/5oF7Dꡔ>pK25^@0$d"c߳tDz RgrOb\8YAwtc9 ߡ!d0d򴩬Yj-j',ֺH)l%"dα93(>izȉúg'&맥|\¯&~[z"0Wk :4RΝIe!7 y:ZCy Cr*:;{BjZ\~}% ;EdēM% j`<w/-.'QLGҵKb#3m:Pn Fq韱=(Ց-oN=,og SN~ŏG0`S0gher+Gؿfe Y@ic�<X#AII ]nXut(!SGbVf4]xjS7kwv2*T.X`|@\)F!eIxifrQ-b]\6a {߫kYz)@Xzlu7:PDU'k7_*:b7q;Y|S~KnEk}m}>G^Vk缿 y']]+9w~X?ɺ8ޡL8Y'LA+g7>"ph]Q=l-恤";ֱ0U_zVz2?Z͗ߴoݑCen7-C{/w=>e{ wӽ01l ڱz1z9|;zXw@U{Dc0 >(Yx7~,sOkLD4#Pӎ]¯ϨBՀ0r 6lH%@6)R4=ьv{OnK�mޞT%U` 'q*yeh_I3h1 \@2AUd^uV~SnI{*`H˜5u(Akv0ɪ#rZvs @�i+4 eR NԑO68 Ch}~E #/չkt_{)KG>J龓B P҄Wu)!\`S=-h0͇{FI/75qATiUsXHiE!Q"$hjjX.(I*rQH$ PZRFzAS7ij5Z YՒ@ZL#}e4E4cG?s_}`|֒zbO�cn+E5fg B!$0DHtG<I@{4nW"PJaBSC&$q er! ͍h,) 0_wScҩ]g0D[Ajqй~2F[ VHXkkAA|ҐHr@Rz- :D  9v}Wa,kOc.Z⪂]d)#Cl(Xg.C\ƫh1Xx+gQQN0Y'rZo@Hҫh Yu.UZ6{֢r}SQ5vkfw!3dʄ)}ۃ?9ܴg2j=jQak^w1( $ql+\zx1nʨ14` &3sq-cW!c+JKݝlw09qVJC(c&2i\}.D)Z]yN:awy7^\P阺+E)Y?Ү/1>LЇ:6키 m[xq`vC~}:6ncF벿N少,w:Ƨ6V;zP;my~D~FN_Yis<X%cm۱D_Ys ݿO`#+kf5hdcMp/JGV\lDzy6.d W^JS@/`{0`tC|Db,mϮ[?Í.;_hEwI^5I'cNlΏo!j1Y˘(`A{+[^⳨[&MFJHɞE[ĊosT9¬s/#3~-pWla-a\ȍ^.>.sK?㟿t[|E,/HuhMsFv xf_7=8}[6NyYY+L]C!,o}_^n[ѻ}Д]|j.Cj+yô@=x8x ׾eyՕ_<3!޻?8?<>ȑWoƐZYp==\1̃}v/m8BCS_h饓DŽNj9)gr{h1A#:HxaJ|dD>3ִșt-b~1- 'BþL_yo ^n:vyeG] $Okӡo*d/uhlh>*-s!SMwm7ij*>D EXP)R(ai(NSkkPX &L1& SlbSZ#1)`$ʿ;u@X/#k)+bC,H<StYCT, t!kʆ lQ J!)H-BֽM;)%©!B ED@L-*CCc@)C>nQbL0N_k H*E"elH{{!.&zviiTFI"'IHeL�c,ib0 ˔YN[#F D#I $4(QB"BmS�&p9cU@b,q1@0X8(a%)R@`, bSFaBJQHcK xcdB)Ŗ &vdw* )%IC΅$V٫ (m)DFj2P8B_q\$RXH Q* KD6@hƶQDat3-mM9z2Ę4q 1*(`k'vεбKJ#yX..YKH+H$;WU"ߘ"W딷Q\H,^^ P2f9oՖ @DD&$҆h$ B$ g]7Y7cD}=عBE(Q: F:AbA#0Bh!] WgLY<+DpembIpJT?[Zi}č# FdCAksF"/h`y(By(&NfTboA')'M`If֕K1?O9_4"G5U%F2e"\{fcDz_Ntu[g .N[F?6)S\5> " ?ЉfYgavDP,k#N5Bs\)'=wG׮NGgXgL2Q Ɇ r.nH{!F>=5ԝO!="<O]|wW T/c7`d. ؉ =3nYY| K+ u6V-o{OMnBް|owW#oX=yXWּk*}֯Q?{曖#oXMnX=ez݊]_1ȌF0nLfU@2e,' $)sf6oIm4jyL -4r!TJ[9d;w?LiR`6[9ydVHQli #Flɓq Yj_aOvրY &kWs˿\?;?bO?G·U6ׯCxJ.:ޞP:/,!y(ɺFU*"Kov:X`u\*]:/rϲtvc|o 7gzBĵ>ʆE<|p2j %^e!:ΆUOpqРFtȝ#y?l^k>r^;\|O+}?{ߵr>nQG@Mc=Ν|"}lIgv6<?}>}|e8}#?&Օ{OOx`f�� �IDATL{ N8sv9Ŕ 5,AǧSY"ŴHgZo9s 4uU}w[|7�G.cI}t;+2ՠ J!B]?>˚Pa�I@DD}-:M{t>} NRmǶ8r�:HR-]Pљ]<Xu>hCU3WthT׳n߾fO3@6@"SU jFTWܭPW!Z*98]~yԨĀq\ mED"CTU8էֺ:~bI4NH1b:& }.˧SJj!@8;yWljl|糕r( -XCT\.# ?ig{6^~9̙;ƦQ !Q.B+bҴ1nJb=?Z;5rk䭵4MkY;V`EjP:v5맔rVѢ>+S)wy$I씰qfcT<zc:ǀ$iտRƵqhl()֤i1T~nw%bY1;XQSIlr*oD!(B\:wS):Y6rعe;wdǮ]S/>*D!^]ǟy^xX'aMcLd2^Q^ iUieϘڜ{չV Ux?**jEz,o9}>۶cWdgn(G9M+%I�xYN45._ TT׷^VȠ[%^.ya[<k>GNf͜ hhh(5^1=MYƳc $NNq/y-ټۿO# [䷼'Īgo&&u4Ҏͤ[֑C1s@~dSCAR8B:T~-.RophnkCשqY$$qʤqcHS)''o]D!h0̙;烀Y`NH~tӌiyo / ԦOI_f_ԅCU) 6LU^KOCMoMU�S;x~ăKW={ú%.}-ˊCG.l+kmfrȰz>q֐mp|<`#_b~!n 궭#ik* QS ҄RXƶ?HC!G_0 y)rLSYK lN?IN:a͋vr)Lĸ1ͬ9Sظ^{:NiCscTJ ['Вi5YWKܤlMi.7?<�W=\/3\sFM5&k.Uמ1gG~ kbO= _ۜ]>Wv=L{%0w.gݴ;jH]_۾4)\KQ>kY{ �yy{�;:SU}䤑fdI8$/ },{vwr\x^qab10dIVf4y9]Usf4dp0 +/nU$ ;_�|ǧvΦZ.?y67ü+Oaω'^o'e8y<'[?w|=צY(O�_ƫtnY.`g3vg4BX}̏缕O].rl洛:m~w-|k9718i|Bп3V9xj HZLFA)wrt 5}oO~x56蚡Zf:DWk_-8E |[e," ;6r`"NMNeJ JRB K,aӺ%t5${6ir}L2sv6jGdo_h]dڅ@zìmqv<V{KZ~~V%={ 8u0>23ccg ag֭?73 @P,/hxBW8{b#JyPZ=A"-, :-Z#Lj )4ncmpV%'4A QYԦ$Ha)˴=bQBkP.tOo/Wb~Z:905cӨ "()RհZRXMEXkID-Hrj :t DxKܽIsR'9d!9IGfJOB]=rLkc8bUItXr FJ  J-!NT$)!R)#bkR BV8W%@l,Ȝ逄YYG>1c!gAי& Wg 6ӄcÄ*"gq}S8Uj4IH`-'nk>_KYVx5u廱1tcxl~&4z` `oX'Y pwss:|q "BTpXDgAv%Wn b:D =|W5;_PW6憞c) Z<8RB)D'1i:; չ3J! Sd];9`E%e>*Gٿ )]{I%qL5<OK>CuL>ڞmDӇ^eѓS ձqPuۍgmXnHa 78ϣg Ryow↥"Tj1jEq$Mu<߻=/YUU�ަ8x㇩Who Qb!`fcLZ)Bt¾>$P; l35zK[ק]ywsl">~[oXVo,ma|iI?=oG/ -'oFbuy޾]o^\r|`/|~9#c_z*p�+{vCM4DM=fLO<3jB`j^X+\o^r/ٯ= d&R%M-JKE[h5((7Zb $1"t-l{[V2EBRBz;ZDJ ] NfZlxiTy|*bΧ (d׹SƵg<jW%G_'_;^=}+_ {۳0-%7ٍ0SʦݟpPsmm3a{P>'CEȮo|0W7X19+st{/[;~clᆿ(/]=s|WogM_9y |:5]>Fvƻ^q0ݶN-,z]631vL[v}n4G#i{?{aw==={(\yilD kC;sEѮ6=|}=1Gdz0ğ8x_RW=NO>E7w?WS<:}z>Šwb#gVgw58[А+gPT*M\:q+S�S|b |7xx5/&5g8zVCP%LV&A8s^,6_Ri~=>Lp˸/׾plBAIYb05]#՚8;~[z?~ls߷>[d좍pUK| (Y7dj??=Y`g!m rzsC{}rԗu݆E@S)SxAܱ>q9EɉU71a3mS8=O D/NF\+rŭH%ԓi]gmNfBH0'$hl٫Z6.k!ݟ ) ( .opS+( QDPT*#T ÀbDT$֚Ǔ M2ZB SvK"RV#+B-A ]ЭZ$ i:h-q6UBx"B`!S x^gWR2ϕS]'qif!z!u}T 0OjZOtkSC$h"D"B,ZGL; (ߟ}R!Jp9SN~1{u% )Bڼ>uh!$ B$bEH7Oq2$ RD&ZPB,8;ծϺKy{7821USv_Dy^n.³1zs>Ov"5̇F7\A�VNoS7mfc R/g5,_(*Pw?Yp&_,̈́ 4X%CWH4ٻg7?p?Ss#mBiN]gi|s^۾>I ,2qX-�%bcGg>ͪu+yӻ5u<\t1ƎMHK\]1{SR\G(E>iu?^)#~k{ .`q X}!/_Dp$N6\ʭ q]947xȽ/ssÇ&dib0`|box .-q'w"y ;:έ>[nZv+:VcDe9-ןeCrVXq}k˻$ufm<z"xS*ton>̟wdl2vyn,[%?4w&h_-Xu-PI*#-ƿfEΜY,m7r'5F *}?KGw'5 654!MRg3]athD)bC @Q26>A`-+ʼl(ds6߆ &=:MRGs8bc]Pݳ.aކرU:y{Lwc 13'ynBȱLT\=n~yfrH'9x8)R֮.JORZ R NWW뼶MFO#(hfLu:9ȑ h/A% I*;xJ APZ@ד(z'o I;+[XV{_kk߸bx*x2>evR;D%@BH{WSt *¶ݧl?Y!JXL+']98qP;8~zzx[>y+}k?:W19H^:;;-ѝ㲫/CTf;Ώc[t>}L O222cC}X2^u˧.ЃwN#޽r~cHqeBBkK4?# [ W_˥}(bvs/�G y籺}U['%266Tm ۪Ks4 |!Xux@~]8 5}&>^Ѱyم~l<#$s(SzeNsd^D#yWWh !&7NT Kuq'rrEH/0+.NNj'-`&T#D jTX�I#@! Q4DP,P,5&0D!aBgWA(#0 Z3=5(T`SKh/962ёSI@*BR eJ&5K5QԒRncItJob4BiN|rWa%p1ڢEz>D`QY 0ƩZ5X R TuTԤNU>o-=#=kLNP(\l* H!+@B1�ty@B(-JǩH -FEyZk쨅 ёR8n'֓d޶hpc-ZRSs\T(arst)].g@I%4)an&Ƨ*#R^b3 b+JfO>3R#9;5 d!4koc/z@v۠ |R('#׾t5)kbIPεXD= @.gV._ҥK䡇fbriM}hWB�pkj2[6Է*<Ѱs�??$ Q1+^N]R t蓑'#hY 5yb*EW \Ԋ,kiZѶm?t-ZmY\65L?54գBA:6m@!h矞-<ZoY0ȕJAp'?_+/e^TdZS-Y CF&X^z ]߽[6n^[d O  tIY Q(Uh]I^Ǿ+dΞ z݃+~{16h3 ]/ {<+HogE=Eme|sO=+d⿿b i<._p16r`L~`̓sO:~M4Dph7d''1=ii&![s`!uof/~+VQni$ 1;{GvMW@)l&VTT62Fgw'CYpJl{J!qC֐ňE]9pZskiV!n`ֱ>A|'-bGs!#KPVSp6qZXAKO%K]ZϮ\֮β8g}K>|󐱮LWW_om9շYg2}yyeJtβ,Oux8(tŜ^.Yz9< cLXLIhaa"u.QKmRc 2W-H̑a&G>Te\wXzӒ11{mgn/.:˹*.~?ɖn̏8fPR CCع c5A<z~óӄ\[KA/x#\Sݾf'5<y?3hISRZ Bz 9l]`"pdF5~= )eV:5[lq1|%WO@61IjUF#q0X˴F(  :X)InRjCTl ÌNVТPD&(]AbQ[MS֎Q$*!V@-Ic5 @ԩ'FǸ>lcflܭW!Ty*b,M?.p&hLNYxm=嗵HH�)2P9AlqdI5aѸkE PB 1UMfA#8#kHeG }:ׯTʩc5ۘs7E;k0i0:#X\cmd,$Z8k``aB6X}TB; ?udr.ILs8'/|aͯ e=]FH?~s4 !? wxB8 ?{.&]?D s6*D+qiDqpTT|Ѩ:!3n Vg_ %Ə@ oLNfDtFk<# Y6pp~&n"3pk+|P�� �IDATO+t,`UW//>w.QMCg+ߵ (VʋR^ڊrfB@ɧn6K B q�N5:,^Bk4W]{5]rn₍+}069-u !jIX RÂx%; I.\*�\4Xbti elu-Ww|91ov;N,)]} 5\ coyj気j<OkQɳ<x7<~M4Dj0  $l#5aj=:QSdBum\&{i)(! H!)Jku6~;x259AebP*ZZ;I2Ύl`ULK#%K۰xx 12:pphR-FmMQt"X8&$I(򕖬.J{%h 4l&8Sd!0#'D9?ߝ6&x&"?9۾)~J/6c%JJY@-j c 1&q\W"nZKg/;9w?A,U^s~+gq;٫r ,ŁۻXb6m:m;F�ع}/-p⨳p$hb~:OKu.eua;k}d/C8s/)ظ Kv"5 A80^Y&\^P6`iGc5P4gG#aVsLnyXd3uns 8:K]#!e쒄NY irJc bb5)մPsj5qZhʫC-4%N5QhH#S+*j@IbEۄX, qZ^qtdrg[;݌026R$3:B9HfO%(TN09u@�)W9:5[8ΚZ8rڤH\2^FvӎԲF '2j)%J*dB!NUkTӄTBB +[6)0`R¨"ZejITm$SS a]޶Usu-]FB:/pwc?W)6Z6F()Qa@bR,D!5 !$啌Il,((Tk5RTP^�ଛsLB콳sBPd*МĵyvKF_2@~y>7~^?!m ӕ)M}` ħB֕ T,~p O0d㬤4099tBdwh%eFO x}/g) g֫uA0"!?xQ /9g'nhɋdnw[V0}˔Xo? ~n骣xݯ02 YtX QKҢ~6lZ'—= 1ݷAKw'+}T, kI^z(FS,^:@'T$Kqlda~,fΑ7BT(Fd,ĈPP{W.\zp< ȱVe+LĢ|"t"t"~ku8'25w޴I#$Fu;^|^iƍa+6M4D6g=)KÃs#s4"{+)ߌZX .߾B kZ N_e?%tvE.8R!RHIӘ(H*.WK`k2EKt?ODh D*W45n $q r aGfIʹцlg36;mߙM4įT2 ^\pGysߴ2c;~?K[V.hNhWBYi-rlރ<Q'(uABYf/G5*N0jӷ˞w/^ͲK:gܷm<eÓeڥez 3 ٸvvL DeC5N sO~޳|«E6`q`U\m4 l91<H숭Wic0p.O_Zfζ uq' *B zr7gSk:q HȻZ+,?/`[‘ƂTl糧,K˖r6#$B(IH !grzcq#k ` *p唾N*bcjr$ѩF;C RIM B*x*Y R X,FkR FYz5*sGb)%Uu)P3QdLkG#|!β7h: r4Noި�a JD'tN+`zܵb,v]OU]>OnY 7:C)9 zֵW4'0TeB 1*]? )e|dd|3-un˓#fX۞(]gu'q|݆ٮWsԏOn[kikkeʕtuv3]kף:*"i8jF=!;Zσ\bk'6Nn9t5@HtY^gvh~ޓK5 povC nkDΣ;xx�Yj{v<º0kd#d2Aw׾\f&zWVրSIZ󶶙%o_\>ή~ױk(!? d̾іbkEǂ>#yv`p!ohm-F.s@`!}DX[e*q(Jw z{]bEUզa|"pel -pt_[k; I*b{9+:bUk޺V4_>̛Æi:Om\LM4DM<V_'Ay6QB8#K&)#$[w;%aC|?锶i<DQ*! E%:hk-SSl^ ׬-0쮽D:S0e Vݰ j٢ b/ai`M�:Iz&hix?._ |߽v3 l޼wxwgBBM4qRH)P(Ƣe}p$GDDP Y'Lt .b,YE iiiy:j*=E!!b@;+>}'xxZ` kKruK. Qz�!K 6?Yg)hiB#唒HȠEI-vJQ\N Vh.)xEIJ"R=A:ZhYEe!%үkgLB 0RX\N)ߜVچIV cSU$`m B؁ IczqVAThMIc4I d CtbcW$+Yd ĩMm58DJJ;MttljYa$v*6o֐)UguvkӌoʛZ1 #,QIJhm BA!HӔP)`iq$X@ DH֫!R $M4#J(/u ihR"^UY:gSHng{&•ҩ#+ ҷ46)m$Φ"h[%@|xnvٕa@կg{/l6 M>S`fI]=,Z8@X&''Cחt֠TgDaSڵ1B"8�+A!vY;VDrnyc`ܻ9w6سd|V4?:- 3:2rqk Еݍp}l=\zżߠ1/g1nx;AϊeZAm@ dE^dwol"se?֑JTs?UϾu׾�ڿzW-?t݃  y fx_'EXX*n1 ˷os71ΞUL: Ҡ KvoYEW|?B R2,_:k5/}�l=|ьO&6,J޷޺ItR5DM4񌅘1<l}t_z>Ѻ ݻ^>w= qR.n)9Rj_%)X0=U S(ٸe ^l}}~oK{:XYPq|]c? -'hHӆiᔪ ?eСxj$\yWqh__lzgx|+;s�>}7ͼhchWRJZ[[Y|),\ ah(#G'FǎPQHm tӳ=IEOSe,@"[ݴqx*犈Έy`|BM=-bl' g\h&W֑� <g8Z\ ePl?!B ɟ+=/ g�Ӟ|۬J J*s.&SkpjSĉ1hm]^G)QRB+ cHT !R,Fv`0B8hjHkE#+ZhNIiФ<V4Mn*h.Տ&w1X Ģͫ/˽+D5lu<r9R3_XP©j<(Ij0*@a^vvXR(BAP$*ʗU5X\W+U|&ZAj]_D(*\֕d}דn.zYO<�4r;+HK(vlp HFagՋg,"}F FhOY ]QWg9~"!?o$#ͬq(g̿g~w&s3sĞp@*A>SjmS*ig7wd�t:'XaL]in-Qv4etb^'[Ƶ{u̪i l[^(l~y_q?9v<< 0˸#ƒ-ZB:0 C\s57^HerS1zz%/f~ƿ\ :O~Cnr^4B y?~zǺksq("UP3X iYf1 HbJ-P.1><d.dYf9JOCbxk#aqsi.k_O*  iT-$IʱcClO3�=A{!Q́EOz"5gz9j � q77f{Ea9=OM4Df RFchT*T*6%0_ݣ{_oG}Z;ݢv8+: TTk0  ՄԤ,YPr ,=]Ȗ2_FI෺ZG?2012g`2i'Ν~OEן~h_{,ݟ r?DwWM&B@߂>::;_Ī &'NWI]NJIT( Fkk+Q=mUc1ZWp^iK=~8@-hc͆ dd)c1yn\ X।6ͤ)Q&Q/Y:MHi4E- RkU,OYS߷qV:%T#?t 5NT2:"0N7fFLX<:5 ꢳ: T#}Y-BJ)JER.lRNS ]$2>5RI$nnF&A*gEjS>m0 ֐$8PX $FHEDH9p3\N {dkkmzm%\_pJKib|{8Fg\'z8Dn dթ&?5t+K " ڸ5Z$SNDa1X*:2CSwAcN N0fRAƸe+<n.y�y7W6,a:rTw^՜,#>%RBjDaIRCÒػ΢9sYa'ZcH2Dz}Aڜ-]'ΎWڙuH37.M~ Qw=]:vZ4 )VK#Xk6iކ k2ҾACp.9h`X(XkjLj>eh66,(jv[eWcNxu }WA UN.~v]'~g i%T.g߿>J~+ٗ=|a.Y .LĖa0~ןbL`Mhvl)qdWkAJda\.!k156s},Y0HӄZ%!Qru/c_ޢ\J8n$]ʼnk|ulJV@wO/--y$mLWf·~vzH3b=iѺ*8pMc�^o mügw=/Z<C3yh&5@ɱQ}\NF]]] ÇϴnXmI709:ė?D2iF!*Ik)[܄\1m"K-6=B[K+tJRх C $ӣS|o_qYO;2{6gLsf\5mGq>=ug\/ DMZ"hb]&hi)%bB@gg'ii`m0 " UTO<mzq姰M_�Wm jGۨ8o�=#B #GM#]p٪\폥/``/5?#d|߁+(Z8.eU1 %铦"[6XaIvi@IG�i\OC3IJdi)&V:Şc6ύP:EK1?VJNG)4SOb)+'9-^}k€&#ES5͕g!aLUt^gRAk(# eD*R:Irš-g!e)*" 1HG!F*X툐Ԝ& |j]bYb[O9' “@n#Rج.-q;Kd+"}[pj�B8hc0 +Qʭc[mHSg+Rb ٽ "4ITT+!PA@X QBDNJQd7A(z"R "�JS &Eؐ* IBF-A�U¦( DV3vH)Q"@ET"!|C fk.:vJJ* HD u2QD (QǧձhJlYLAf}2Vz׍5QxbQ I_SmXvڊsQRRNkn8BP(#]mPA{CFFl#/QE\/Wv�֘ CV!]'_6+7gd#+tcU55,tu߳s_ B`p* ~}'|7{a+"F'ogaz+Yr9yYfk֮ T %iPWsn�/?G|!ͱIgGh)7!FkV S�� �IDATKגĕ Q#|4W;,Xe5H?qBuQkK3IG[[;-{ ݇ jt;o܇,u 2Y:gИa&b+Jy}$-^KΪt!ƪKeqkNpf;X6�|`:GtxƙpMLI_;[+Σ/dggqWp=CxM4DMz̃<gJ2cUC$fbc*՘Ԃ0,,BDDa2-vz{h-u(i3p R80a5R E )zX. V*rpz;x>>^�:$&}Ә;6'~ώֲ 65M4DM4Ĺ bWc@>LuN%;0Peӣ>ʎeمǏG˶pפṄ6.sp{ξ&Cq)I hrJAx2O"Gy#2]UT&gٕf ?! Nj=A!Df?,5T nmx1땚YR-SfΆQXv䇶) Hb9uSKA@KRj)"%I\DHEcD4>-i*PS)HO:2:MRt]5.(DutTkGvTZ[UJa XZZJH)r Y }[%0F`KC,PRy(V{" u/:$֒b!iK#aHFhj54Ǥ:)PJ9kb(DH6S̞bb#hZF#pZ ,�]<V AaA T|P*Os,'p}Y2ouJd NuJP([hQe*&i =Q)Z-nC˯}Ad/?W~'m🟊vZgmpk*R,YKwp#d;˕YJR:+\BGgPo6S{m80bOL�@HBFP²zfd\ex-YKz ,fg&~eYb~*U!( ޹o}vtu7.}s~AWW'+Vp@U"@I6+_懟4 }F]�t+L%(D_ϲ/$TQa,+lnxn`RDmeHۻN'~%k~dl5qt, c1:z>a+K& qj:E=%*zR ˕o젯7׈ju=-מՓ/Xijn,@8X.ZU.aj;ZCYyolCr?}77+7v2QIIn>0Wx/͇Wc5<?<go(� /wm褭pi~rho ǧu&h&~a\({:wq"eq&hMKH%ab& ۉM4JLLL2<<B! h-9|� j ! Çm`pʾ*NraOk%0ܼ6e3 >?q'SO2kcgԛa~%^&M4DM4DM̋H5h-Ipa- dlgo^G2\{{2u:β&_{jux</l pu,9C\oP]9^Naj6,=Q�SDO6͘MG$X)ŬH$DR G~fAhf?pXp˶@(%Lnj&Sh"{%U[{g9]WWuUwuU?n "`xM clf<1ga"<v8ba3`@S @$ZRwKwwUuo{#sڙ[VW ZEtS'_;w̓kFfB1խ)9h ]իHS~z 1KK&#NpݾZݩ/ _[eLND0`4D$RVjX� M 1X,I$&o>iMBL,sS$FކH !&BHTUԁ``5niy5][RPp.[.3#d)pPr3[>9.^| )<ޗc�И(,B(:ժ,ꔆ(A)3NS%LKWD񘺮 )"J+eAYf,HV?:lwА fIq *,ā)*R ; , K:G=޻f)ۿ1IsĬH9d<⅋xW0LA4\lЪlC$(loR4+35.BҘ<ڌ|t5Ƨڪy|lQ5U+9\e]7甹;OacIKʶ%(Tq3o77kOWWYm*:wm'$<s-v|#f/$m=۱}LΟӌ, !3AAoo+B#zj[;OQV|w?ίүr>;݋')z~,ΝGDbUQ)<oÔE>~ d{GZJT%8+Y0bPzo}h #';уDQV%_P/CNVV׸U3SD7&?_1|n!RB »~`ǞL0K0gysX݂#<~<}3Y�ǎGwن>%RQGQG֓m-6~ZGۣG=zt ;W烇Vvh%*Yd0589|(3%K捛G#?{10Q$l<O+Gye -ݿwyז`:v\g%b ]&vs#XH{|׿ѣG=z|_>ƶ9r:[رչ%?ʩL'L $D|$idH$bmW)$y|Ǜۖ8^t{o#oo)(Fs$b̘]9j"Sg:5%%o)g+rQr1RO I%oa$DW]U# T!%%YYyھ2Q0 e5F%EKbַ[M},-,8�`bmGHkED*Uҕ9q!)I=&4(Af18BL =NJW/\ 4 "HNƏԽs*DK͊`WiG&rllĭeQģ)ܺPBYMzdoϤ{2@s7oTEeQ2 HƘλ-ƮjǓd+O_dNMY;֬.*8}k<{"#UBJ&gKV)bˢD\&Vn#[5l0mUJ R֌#*(Nngj"F:hh42k8HͪeEMNK$n1.+0#5͈I7Ϣ𬾿Ju^}~?;bSNd.#Ɔ_}7.3mظs?;YMj-cg4̶ӹﺏaoev\oN~۩.lgnߏla6g\[! ;߁sӟ%MşE>O˙S8y0:я}׮;JM78!"fΑ4v|g~[=/VJ-=,Oӿn\LYU eobbe9,mS9{cC05}<wszחS A݄8|�M^PSE-WB'J]o%NCd|Vj­O_qs}b[Oi=˼y4޶]c?-V[]ctA >Loy"Zݷ?o47zzqַ8oήmnߣG=fhiBxaM9[/k޾hחF٨֨\ST%k+Uk4'BlC/ܼv:P|AL%1oIQxled.^rfu+ H<⛛{U+�Ӱq\kyBeXav-w9գG=zu*sr0dmfl`.Zw j &S>ň&v,}|^ Qd) ۉLb*ML4QKrGGQ[-Rl;["$ɶߦ8Y1Ff$,L54 ׯ_giiȑX]^bii?('1Q Lk`6  ERҤD@4m:#ՕUNUeWoބ%Ȍxq!3 cmNKϝx穛Ç+W'i]gaa0`})r["6 `ȥw:ٳ/W^e@[ [U=Pe+f{uPZ_].E%Аg<jMJIBL8gc١[\ޣUy%SƢD&$=R8`z8+ߘbvDfm*ց!\dGa>FqV1;kVƹmGZt1'.d`$lV37v-NKWjolo(R.|*w{7-P[O񅧪*Bس|ʻϽZjif:{>ngAg+1+bm^ivػqjy5ׅyͶmAve蝯ےx)}\z|cuyU+||7}KKCit)a~up3G=Opꝏun^x+W3x"%ɔ"k\.Z&CU<nQw/9r`zߗ>nǹϑ/| sp兆Mu,;.9ckJ+Z<ÜxQUݶ>?RuӽVu';4C]wm^yMѣG_9EAK%cvyO)c~UsU7n~&N@="ظqw< {1+KC\QcC%0F|?SVSs|4pe\ʭuVk2?e.m8DL=-u!@n&c99ns){=zѣG="cRO!Z:ACBL\4:uP)XeS;ƙgc5ª�Fi_yȨwRgbQSGQNR[&b&ʚWb5Ms2EmٶJ/S"[ʂacc!UUd o!M(ӄJH Ct(!R{BiR&dUks}9ŋN!8&ձ !B~dwk{'56Q|&FG Nʬ5k"+KVs ¾U>/]&j龬̈́�+ƬuCp6MւB>ei S2gif`N(D|gkbhEc9O%WGH=iVyϾ"WnӨШtZb1wiwc4Mq&"ޗ] ]H)լ{)HMY @^ČT,A<EcWo[<I~uX%mmvݱHT$?rʜ0du/En-Chmݾ!?ߗwR w.>s=EL6sM6"t)�&XƄ,B%Wޯ}d 5?|oaPh&q6Z#j)| sT Gv2!4WXt<E5q'8y�L"Bd*N&f˅b*K5_ȟk2wOƁ'\FoY#__[*^�2=t>z\ G=zg7 8{jNg[q>s25Z<7rm IV)<  DX]Y%òՐŁPܟ>ƘM 3y}fde.o٥~M~%p>[KKp7#|)Дs ټ,ͤKIݦ} [uѣG=zѣ W7n!Q$& U1C;D5S(x2u.[*s֞yVSJd4ы,#՜d$HXmmBi׎L!M* dDYl6BK|uV54J̗U:\8,TG֨Cqu}X%6qBHHBj 7mxmyOf -y2I$H&T4>K.%{k7Mdkc**4)kޜ@QqfsPxMPjWEjTɊ&!")BDb F|_HYfbSzWe}\G,]V^1j ]9juQJ_\ōhbZzo �pB!t"ՀX3i٪E&<ocúը$6u׷Ff`L]E)\BL%u:ͤlsgIHi݇qMޮI<KخF\\ccr9o;Hn Zu#ֽM}^H}P2Žw+yu{3ח}o;F5$̟SE4ݬ}ε Hٷ5! ßG/1&y[ȇ[y]Q"N`>֥[9۠b^OˆȞ܃>(ϝgu<<Gܺe?vb5 ACGߍ+ E~0+<X^'bكُlQp}g& "{ p҇YYxFp})Ua<=4tzt׾O>=zmϻzޱ/EeLw/ub"+jl]V7Hح j@QV9hlHT'јO~s,:VK|4CJOqK|ֈRs %cӒ삳3 g-볿_G=zѣGpUF#wpJ#kWmDI#m;XK "Y͚.F\F~ @  ΑQ.c*މ5.f#uN'ܱzkz:4Ebl,b-_VVUG(AGBcPxky昧/\dBb@i೪QL9uJFA$ȤYZ%)&+׮~%#Λ29_cʕ<oqm4"9pkBӘMne4 H*($YӎlznHjÈK�U;GU[I4!R8hgPTa,0M|JEIU24&$CaPU#4 jPUսެ:}{%iv\Վx^2.ŮS@Gq<ڝR:IכCx=LTļ-@_Hm홳ݳrx&V�� �IDATǥw^6yBu~]ecNZfmsWČ}q+Ϳ?vdBȾ窉 86̯flBmͲ-ȶ/+sek3)<qZs|尢.lhLUũ'S,.X~Iˇp+G\#?\gY]ҋTA=Xxgӫb{ѣG/,eލp{Ӿ ;纀xeeeX__vmlS4\%NNpn+'Id:o08a<ڤ*KNѐX^(A|>%[[b3e"qpa'?я1# -%^h|]RvS?1=¯OKsr=F̧m3pw0 w|oѣG=zv>xsBCò )H((h/B>Imif%3RVDr=m!YƪVṞM;͟,�G%idm`v3%`H b}l%*pȰZd41N8#EEH {A9yl?/DU6nmr4Z  "#1ЈfܻB *Q+54%;Vm+\v]h*5{#ݜճGL&&eҔqxq'[$%eQGEhb`ROQ V'XM4 N )qYr|S'?A#c;fKgig>&/ZY\Vy<H |^yB<9$Z)ILLSI*=UQ|NzV6 eE4DjdeɠSMnNi{\Fqk۝U0bVo;s :3a%f lY+_|=´#?$yQ[7di�bs~G+o۴]2#_6w<=٭v;)2zG%k|E�?!N}4Rh4xը,-"qҭ#e`19EHv@G#?/+ b=нy}ʲpdz9[;N~y c-g#J_;[X[G{:M=Ubae{VVڣG=zxђ\uWv rv{u]oW;Z$ƎtK#Z"8J3 Tf2BQB �V!D^x.l7O} ܘ&tYl7,/|*O*ZXqL|Y+~|rOM=̭{ϓ]?G=zѣG9*glR=@<;Ͼy/8I$[ RЪRğ̑mtB'GBgۧI܉dcOM{h3l]Yݪdi2`Jfg+:KUDv6&R Yag 1EA+J#p}C,0pȱX)JC< w =}'˯]`c2 7%;fΪR4em{ώ”bm/e|mR7 sPxRLL ǘΓ㼧(KYCMhLjVCjڕsAKKr&�'đj yD#1}.jΥfAlM,꘦tZ|U"S"4.Yz�`2 ,U$F*S't:1$RQU�E&U8gaJ)''8g%#M ŒqY=馴fCNP#wSvㇹgz֝%[gMw:;H;E0G ϵmwVz�ާg<ʞ#߸_,$FT#X\XndSbV+mԼ\<)?#%eqm� |wt?o?EQp ʂCX޷7pg!VW,{v2-,Z8xݔ=zѣ.lv~kX2xzbAYJnhMbu#ajs3)PUAB� S + _cϼ|"׷&"aar2a ~ȣ|] ˜N`fRiWoe~rˍ=w%zZp߉Gd8=zѣG=zO|7[}X+ (ı9y).]̫_˖j+:IX|)3GSTNDlE AzY HSjzRHY8Q̦aEZ8s*>a&=M]3)8',- FÊE4 M3&@QyAIQ52i)3ٳpqFxE՝bsi$k2aj&gЖyq;קU[j"+HS~j| l{#"* KAa!%&aR77ʰ mDce%KSaA.鳊kS8T D"XYFm6Z#9#A BSCYP0 14&I*4u @)D zZ3h}\%!h`qTeّeQUl!.܀_w6+ vH=oݜ̌ l :ss "3!lώ÷mҺҩFHYک=SΫ;;zDʝ7#wްtm~Zwv~}i=ooly4S'9{<LCݶ_q,/+N0k/?WM޼^\[dPoMi49iμ�0 6^c)b0X[et}7)e^7`-8: N` BOѣG=hc]v ,i$l%Bi=9gS};q$\/)R`uE `U%XN\"\Χ/^r`?^J?6.\`%g'A rA(�njOfDhiyvtA笢w9vOfѣG=zq=Gjb&'lNG|晧x.^@ S&ȊS1%b+Rɳε#fmՋ% wv/Me5H#C"ʘw&$|psQ/ٌG!א "Ihqj�Y*(D(c0(ΛkY۷J9(:^x7G=BK6'p1(Q?*!&Ǣ[`4|Oꕋ\q 7(s.2/̳͢2W?mv. M7TK)1}mT#E%X(/@xqQ?qj0i"EX8|R$auEY".^ɯtBp6n:y,Ājp(+͛5 ѥ"BySARGJO " ܷJ*y!R$8h 6,cHMeW!UvX)&b\8Db K7!tе{oՙj\9^?e5+!+SР6شk)ۜI@tmZfǚ͒Kg$lenkGftAm8כڶm{.තӲ=ڑïclqVW$!bay3?֭3\<_Dۇ~p_4+X%%ֈ 4 k,_d~o p8s[UI)rܽ8{B .ѣG=z|y{{^.]rͪԤPGnhf8RY !D|sVǫ ec}^8*6,-q(=ٺtfsk/{N>0^<Gʜf $r~Y׫S\x ~#r`_=zѣG=zY*9 tԅZMEI8}^xU2f>3 UG಍5^Qbx/yߑEʪbc2u@I8/8 !D4EIBLB!4bCvflb 36S gVӨ&byi | YXZ`<0ciH4<w>ye.^ /_╛L#I_T0i*zQMczLMdTӘUfO:kbPVr ZfcN7|Tj (A{qH&&YKV6.kABJ PTژ7@AHp$^P, qpUaP&*N<E2%ɸ+JmIo%N2N 0՘\oѬc q"HA1Ffj%Y7 A_Fƈs(pQ `4PgUnstjkʎĔ%_oS\mYs2vg)'wU꩛Sa'٭1F9vTPlԼEno%,B&_pɈ[n1,xMȶXZ~& ֯\`ԓR-TK%f֧_MM5'3{|rPQˇ1XZGcۏgNp30lDdruM=zѣp/ƻ)9~KS=ڻFQP,>8.dI_H 4[<y 5_(+!1&µTQǍX/p߀&X~^SY2?=Yw;;eՇݮ{>wyѣG=z U~c,ںb<I(!"u*uVP@M#F4zyp4!̒K3C$Y +H4(\K65A7@xR%dS喝hNycd8ʼn#D$J){|YP EYSd<ɸT^ y &!150:Y;̉HHuuz.[9 el:V% ty&=Lڕ GCjJ9:IDN)$)AƥL:\!gEuD$LcOMCFdt#3DR+54dU' %,!TU%>Z2IiRnSک1%Y_{`V O1bH-4:層dZ,^xpb@¬֪XX=.ǵeQ2\v6gv#hǀ%n5;.}&"3>1m9^*̽wSv8Hw_J"KOyܺɵuSO=W1MzBvɳlݼʭkodifT燝1@5\{ oZE_ڷʁG9օk@U1~&}w H b&q{ѣG=|nUɁM 8"*.z#NS"Mb3 6n<}<xp �)ɅQ x&O'xC\WUk,J^~<~*K'i1qE ~mćC.H- ۋfK!5wԾzѣG=zx;`\OS1e&^5&#!]2EC\u ~gAڭmimulqАqsuN**_x:)Qm[Mwqqܕk6'hh$yw\V iNs >$xU55հ4# ٢4KK L0IL)DIHAJ`Qo$_i�xfR<)"/hR4b3AxnPkةV(XS#&3Tw@i;:%U U. #.EJ(zHT BD@B!8iLĢbteIt8pejy슷1BY,KR"j[\7H̊R;''lMfK ݇Ik)fYC4!^~, +ڞc޺ " B&p>Հ׍\lG�;xhc7'u)Y[UU٦؋}K0%*φۏg@�"1ע*VV'dwzos+_HI)M'9C HM0qkQPT7.^\p\}.<w<b )ǿG=z v{6]6XiĶubvnwwM;hIo['/Mwq~ܚc-6v&[9{//p1++\L9W~:+L+|̭Ƿf4j\x_*,o>zѣG=zx; WoAXk HyBJ8<Ny]Hj3H[r9a2C yv9!i, >•& nR &6cnllqsB҂V!|ʄQ!Yq�m#\e8KZhbʹ2Leb$uHIDs MoO S&' Zؔґ*#Ęf*el+Kf6]USՋdl",R7jJRYP@AhiH J<)+SXLGE)jDLM@QШ<&f3HHڭRBXD.qJkCZJOLmgm%DQMh#RC>͸ɲeAsIb/$BL]aZ{Sy/<>]Io=56oldmL1O\x;,xgSu羶xw'cV~1^9y1 ~ڈS'Q0 7/] /7 9}4i2&7onQ L&zﻑqCBBHQ~OG=zchlD:<_ݤ}m�*f&kp`̄{*G^O<?zU>%'\^y[[ y^^)UViwңG=zѣW&)W7ֹx*S|py&3&uhUrK[ "VKS-M96J@ ۩XfJHҪ3?j֔H(ÒGRdž0nh WqZCRq-he؀/(|(8)YPP*3b P)DfYU KA"L)u A=!*MER5ҤD#!ER2aSB -m6Ś"X^\֭[֤m@0[U%*bVYY LؔO]V*FX;kG \b%uLdP> | Z*|L,PI zj 8JU*uh3 )u{舲HY J4eV>{(R $ Ng]\2p[$%D3tVh{zv <Sg=ܧBHؘµs7, ꦡ�e0U"ٜ@{.oA(Lj{4w:#Mz˒ڝ}:;!=#N<2=Hl/9y"?Gfs#ySk&vI;Jrzٲ;)cw{~R󭿝nx[2[}((X9|S=z|ğqzѣG=b Td(Fo{mmv ό+G>_~pCap+Hk{([wsw̭k?eCﺟYC{Ҟ)"naǙsv;?;]{^Q}t�� �IDATt.H;;Ve{=).C?%;(^[I`nc&k4m 1Dps@r(mT n21s*T[wv|X4w viSgorg`mܔ?IM(Fp8\NsaD RSDݽթ_w *zMdv2T^|iaIwVg-/ck& yd<ylπ]'1U{ i8LA"ֲ?%gSrsbWH�gs6ׄn'`% 81es"/]Wi؍}ڍ]:߷5e6os֘{(wrX}x=_<N^t8xC�"48JR(J#%u$z!PaBJ.)D4߫>A~/lzNZ;(ND/M0*EPB匄)<p+]yƇ+ hBc 4 u]B�5>SC5!c$0NUUPt2bcqnX[]cad4(+N%;΅!㾓'9J5g8|_x??bV=Z_V\y-''0usDv8ȊI)ཧ 'E4EBVlc7~f#kĢ1DzJX3"c0+c'س(=tRK6$1SBiMӳݳsL$3aYj4}*Y<OJ)TFvLzDhj|ܮkSv<w%Z^U(:aks{iM 2@34LJ`{MDQ$}Ĕ+\.CE3%WXX%qsc0E|&nR#.lJRS%J"y&DRjOm 9|0;s=KdkS�uخCKض 1!Ɉ`AӾػؽ T*k+<qa>/N6='T>$S jVƩ ńaF+iHެS y<kв/8 1wlcjU 5ɊKvNUcבtԸJ$|]&KR !_hvx0h[ U mkіIg]sJ%;C\s;f<SzN1Y2@ʶ)miUVWW {fdl{u3{|*֟Ok 3粽~`Qҍ]byɚzΞuK =zѣG͐ѽ߁a<WۗԢpwY 7[8E2[ M;nwW7ێssв3vT{;k]z<,0wjn&^wyv{fY<Ȭs{A�(ER#i(1٘l~|#hHb $nZ>#3N۷M8Vv['+3VH}}tqh� rZ5r3אK &s 3;HX� 9b5vkokݻ٤{o]z.y,q4Y⹭ <:=4sxL:b;0GR. DWa6gih C<Ī$NvO6h�M\tr]qT́<[{.ـz{Б"ߖ21FtFb%|<7BtKG,>�c1<Gt*~lb֊&h15{*J.u"p\ 4ejׁ㱁>'ưWP}-O#Y3I ]rу!Ydb$@`mc c,t!P"6=J FA 9*5J/=E! lUcG "%DT,alRfͪ븼Ջ^*x׸ % I!P PBJ1FP%k܍w;ۭ tBL֑$Ī30{jՑbYU<XpKU"k. ݪ#&ެC-\n!TZ\\^Pnv4_<Fx;%v9W+$FH)Q`KuiRTIFRk"ƀjr댝�Y)lWR-`ꀽe]-3^ٱOnd7 ]wZ-n{};~Zcux7<{6QGƚO!cm{I [@oEB;W<fk{PJT1٧g @.DvkEsa'28DukrFc7рmw֊R.9q!@1Uy`Ck1WbȥgKVR]2 MϹZ_B@LЦ4 + ]츼"�C߂YeH<x??.O+ ^5x# ]27]Z~3=j8r5TCd*|Ӧk˽j=)$yL%;O.fZTT'UԡZ UUmfX?Hn*#Vmu_-م"1g''Y9;rPZ?oK[~f#d:6)[~_^ uB<5=+�; 8l3? Ҝ~JxymڿO\014<Yl9r*r*r*r*}&},hX弹{/HvѝϏ?k).}U+-A.^/-^;}Nnۜ/#]Pp)۝/+p;O @�wdeoYA.h,5עưBr_L,͠lhl<MbjyersG^{c]8aw2͚q̉ylV|Qm.%?~$϶RA٪ދN*iN@XvV6/<sс/}QG;!i dKǬ=@<}Cg{nG$(x;&a0[u Y'CTܟ/CC8[cg1Z?Kj.#nLu],Wӂz,߶w}- ֯"Q&q  HEyPq#B$6`GL>%sJ,J5WBKסRz֠`<j&>DR M\ vD@V?{|ۜ?xn~@|FL -&]z{}\_]N2xJ[X"0VL1Т$WvSB8ڳެbzC2&D6^cyijgϸbYw=yIxۛ[gA ^uҁd5[&Y=8c ;*WWnoЪoּ1}JR麎ng܎^U6v`Wǽ&mik+g *|C<g@0'e2m+:cYyMSȼsچo;uqeU%N X@OZ4充INuZ j5Z/cΜ6@Z=;BLfmuhS6X;G=.~ jL$Pe40(c Ě[쪟Fu"_|X+>88x_%iؓ(jvoHbJS@6[xf?ZQ=a" 0 j/.xqyI.rV 1ٕ@ggַ_Qܞv�1n). MʺKG?gO2ha͟3Bsym$1aUdfſ_o}{og )H Kn[u VuOMBP< f,$H5YmsƂ|Z0$/Z(ѴlHJ3^h� )hJv#h�*L%"&@=eH_qs\M1VԤTrJ,݅ >1 暫kʘ_'hhYQM>=׼C1g[׎o_RN쩜ʩʩʩʩRy%9ٗPMJsrJ98pRGǾeQwٰ0ٳSws"6u#k  '<q^{|!Ӿ Щ>"}c>4ԣ+$lPYYsz9B9As-d5ZPe ShZ-{͗0t;*47,8[G̀fj2+@Y_wsKZNiTrWə4= �N=[arY\uZ:bs;Uw4b jby;E:33B4Vp:Uq^Zr0YޮOq`LEE[_E YsPzI ]n 5q"T a,k޺sZ}?0T6GkoS0X/Ӟh\d`j 3[<ıHt_@.9j.0Z Fݺcb ˳ /[n0Fz|=؀P0o 2!a;ֱcr j-qe a:J9"[./.+- ɓԲ  3FbW5\ daG> lֽϕ%ۛ8ێ?<d]MJ^[=cJ6#ggȳ\oo /~ݑ >!)PDݍl[aRzͪP631Ȯ.ШĐ) t]bӯ/.]B-.6z*`A,apX}N T=|İ`NkNk,j f"t YK چSq6mpX}@&\2sNjmZXV݆n\F�IxϋF>Rk54 V)SN3qU΂E$w2&$3u&C|pe&)gAskW"J:VUd hH69^{HJ 53ցR!FT=ՙVU) (�b�&?, [W&n]Z[}͵-Z@ܢ}p}nMg=rS @7 ?`-B ib%Jo|3|)@/4bEr-^{]>5g5vdJ wR~j1_kpHBJ5cg39K'hG%U~'> sv+</&]]'K*_Wb518T6qb lf�b7Ç<ykSpp{<Xg| )y8{=ҖߦL;s~=u}oȞʩʩʩʩ{,Y [tJO숟hWJppo//KvA旡e"bҴxDu<`,ԑ2;fw{аVʜA sD_:;Qh`[ |̷͇Y<gͭicZi2X,u~+(o$Z^]bAknDwCovBN@lk2:;bY2u1!ZB<#:#HT:;{|1HSn÷^2X8i984-w~1DWVI]g# 5͡RQ(\Ǽ1/.2ikZء (0y1 %⢘}Nx|E˃og 3V}Yn9sO^?!;fޚnr̶5M^d<1~_mt;8B '>h;Vw^Ut.U;Ps2Φׇ9JnK0cLr|tr3Ҁ}_,uJYs)] 懟V.ڞ bY�WLmRʈ1XɧPLf64FARz6p:/53GoGxWW@\'V\lB͙F~܌7R.PX9cE"f|"!H:VaGΙ\FFLTK!T%픋6+n/y([F H Um.!S5hE) BHt75{B.v#ca1~!m %oR9<z08{"X q7:KXU.11\ l2JeYF&sn hWb29 Hlmx`C"7CtG,@H k-d-d2E #<`nU�ȡGRv]]~r4Ftq{ܦ(ٮ=P6&LA( Lm@9\Õplbu) G_ϟ<m OHbǼ70Z ${ aYR3hi9@@+XZt>Ԃ@Z +WϞ?}S~ 5ZDH00Imb$12ZG'>z"v]P)Bonm?,pq6Ձ,sI &?g*3TZmQ ?c+5׷\aHHN.&:Q)IXsaB!2 f <XSk&ݚ{`BD r?߼O2;(S"UAW|ڗ gzW `[}YYƥf5iu,A\ !m{xGRJ%v]* &s_kvv/nv I3.g~ud%ޗYy)ФOƪWk!f_-�naz>I^H-+K@55w|,ZE{ֻv,J_5}1 =S9S9S9S9+wfG|w 0MKUλ//K^ٲ_ZO!0EԾ䲣u[WgXL_[ޝc@}z Np{\@ e~qţzx=saQꟀEZ)21d3(s2#|Rmꎁ,~_;Ki~8-ALM MY.TqkH@+]Lt"DIՀueT(V,hNr͔&FBASVصV]aY1EB\;Z;9fDqƪ]6]V+B :IۓUOAjc++9UdjSM)o>>amvG%A</֭م4d楩zE.Q./.S"L{RrdxMfNnWf($5=j{lb6B$b."HSL9eRf<ov-K6cԦfN`Ff�y6mU˗:i͗m%eg7Ϙ:X=/\{f#/TV`;u�� �IDATc :ĭI2v|+oh@88@"X{lJV}X3w1{|q9??f7 t]"hfb6ƚggk]} 2 TcH-lU,KUD VEH 0 8(/G�/n 7 [nHƒ^ XoBEę-$z b"Z/J&9cLh(TXu=)LBNTQmkc&%%VBQPbU2m 5Yt(#QuW2(:a̙>YGs~~;Պ .//` ֪wyVu3 #VbJnRwa1or˱4#mVj2Ql9Ng1U"4kc-9hNWDt[yn7.dZngCmMv&[)記|yOP!sY5@*[<d'SjJ+�L-0*ؕL-B7 a( vOx ^ h];ϔS8NΡV8ҧĺjӭE29g8.mmq)Ǡt+.V sP 䊑 LBODBdT:"d(U@:\<= }xբn[Q'~|Lcg[�-K$Hb%Awzoӿ|?k.|&*Rΐ՚7Zcf%~]{m>s~c!vgf}o #Y1im!:L1b2@9zЂ2XI[!4S?'WP*:y0f?[!zo<* .˵PF%d/E,`6=(DI\X%YuJ?=l#i]hlWe5 ہZ,0Db1ۇ4z"Aq^d:|,hq<vCˈWyу_me} =S9S9S9S9r |C]p \+pss.,e+sTo=P[|6OBK'߲_B曬k<yսkn⹯P~۞;8kl/KݫUNNM}7Y75_Ja9m`=}x !pnsڝj^YqTvYPJKULKaO.(5Dj0H1y9!nT&ť_cKD4iʋ qqʺQw9B`(8 @J* nW";]{}1vrӮ1.Y,*& zݑmQ0hۜ=܀~YB`,Ǭk-) Ř{6q)l<Es- jXd-hB �(yvh[qhLEM""7Lomyi an["*c791ƊjvJ_<JELRӖR>fqiMv ZkC3falM=1W] 4EFfR`RBE:H];g)q/*[sw͇q N)PmtPk%a@3sܡjmyxn 470]c27fpvlr!FRRXVdR%h)l8Ps摚-¬2J-_#~"1E*}M.)v<~+n-fjDb٬7KBRƁ/S+ e܀0!S2 lbOH[cUc0RKFoŋ Bh L{Zl8??gm\653aboqV.P|N'1Z (5Fe uAguٮ5V scܢɆ;dcR-Hwoc۵dh9Reov]ws}__Z/(2دmmy .F^�cM 9PR {MHqL:궢#d"[g_V<ϗ)$$v %%$˥l)fVl{.q&`&HuV똳ɗWR켫v^Sݟt.6BNXEiah,܌;v*TzRXC|ڈŮdvQ15uk[DqmL tYK-! ĿWgߣI T=xÇ<qq}D џ/OlSABfdf*ϭ޾h9-e x^Ma+s=V; �_}ZhQ!?kpdIBDRK% "DL"YDLyhq)-(ad-~HZ*%Xt-x}>3nn6gR8c9Xۻ5GK|[<׾}[m_^7K}ހC?Pe>tWT9r*r*r*r*r|竃y_=_R7T|7gW~~vs8v19͆+KWƯv՗L~]٤_c/wq@(Vst)11fG"2=L/ `oMIxqnEd77TW]s5ؘ{QQzbt>J7 1(1{[fƲis;p#`rNzBċ+ڐ4"`Aawab}P!B(j̠̪h.\^^EcjƎ\xc i=!ӿg;B[m(eB@e(uF^tr/1Kt.n:zƌ{U<8`T=h=hҮ*oNaTqXʑ"ۣBU p(>laVnZugu \msNOW=g̙<h.%m ./M[{~ iR騱!m�+%xM\dV:,>n^1 %ua{'|9O.rAD٬{XJf^1\_yv^=n%nDEjPIm\+TK&`LXMX})D4[1XQ93`ȪHI&X~ f-TJZ0` MD,kbJ7ggDavI]dL^lXZz^`0v7Kaw3"E5L(˱bGm9%�&j{0 e@%J a `PU5kg}NzMC+)FxD$Tm9h jgyvA<9[ kLϳ9%ЀF?~dY :ˇ k X 1D èM::OϿ=OYuyi?-?MVDT TXLhg3.(!&Bb֕^NbWW< <^ < ) u{ Eʘe} <|e0ݪ7V\ConrpvqCq)֏&kleEK!} W?[~OkFHH7Al=vse@bGc_6=MuJiڕVg7=x%wޥߜ.Q,0 yWa(ϞGRЛM.[w:`@iMny!�g)mR$ 1gio}EfђQ5vr ;ZpVZE[jW1R4 ji@RKbE[]gL0mVdqS8,M�kY`&K/Q9c >-/wŻ1{cN{g;Tg10^S}rdOTNTNTNTNK!<r+mxWY {} (qzgc|giw%}�N '^McZG?4s|Gb,nrO9d Mҹ9\WY~ŽKyL7@!H(lʷyo=z=yj!`b)~fq4aCjeMLu�6,vÁ"Z`A9){w/%GVvC te#z]>S~'6tsiBnNR+J[#\Ν۹Yl-׻d٤;m6P:㰨9)XbRu~0o%>VA ܎4>^ v9Qm4Ur0_TH~ g s `Ϫ˕cq̬㊳3nonn-Rd<"}g,ɉkc)tv5:|~ HjsC0QHB<8'0s&RO>ɋ(d֫D.#9<mWהRy91%^<ypuKv&5V|3pRUL.D`rXM&@y Ē/ % Q"nGݎ:!Z]k!yľ#HE1".ko<򊋫+ψ}Oي :0! }s93WnIaM./]쉛HtirYNA9ZM.Y!2@vXm/9$R:(1&WZr1Y £{x_b^3ؑk&�gU$TB)!O6~ L MP,do|Vsk<p!8>YssscʝwiBpiTg.ZRn )Bm fәXAv lʅ3uA<ߗ'y T5 ƑnkN4$;1iL (Q5v_\c^<O>ϯ97.WRϠrt]l%w7ك3_~S4r9#V4RV?;.D!TWPjw=;_kBx_|g-ՀHOdVG0JXyCPenɻP+X-/mA&@n0#ŎaozB[-|'g?7~f,7R[te[L^ t ?RT)R1y*0ԚO}`f,GkLp@0NgtXed!Y>U4ims[JyzhyS(@G։離)(}?�%j}$;ţ6I^;i%_)vcIj=f3nf&l;N78*h P&cE?�S9S9S9S9SHK/};x9~9<{Y<?쎳l-9v"pߋS-}Y5ԫ̱^}j�967�e>|[oŋ KPu;pw;" &oleשY͑:D(k rGD*>?[:"ZU-~euZ7XO-]8[0FdnX g\Ss,WEAbr5y#ezk~V}=G|٧nb/{z]9c>c0;!uQf#fb+ՙw ڶj_śjRwcl%" kDmhn<ϭHN9mnCs׈OuAZEEmi8{`yis&9ϥyq.BJ->gH.^ŘsE^ea"[NGÓ#(j@:bg}b$%dR!=g\k68N\ y[t% //dsC!Jcp7$Ƚݚ̪*%csJ .qqt) &lA5m%P̩f0-奶g5i#t]GY3n+ ;$>R(2р\ IͣrDIv1f LtZLF0F=?" (ZlE UM^2bRY)uB4Z^ǔہv˰bOu;P*1ј^ k1z6y}` PKvг.6!h,J{;?c2ٳ&icL&q֪Zq*4UedX:sN}S2IYF�&<g9ՙq80 t:7$H]@<t3agmA=7Î?Gs\rsP(y H`LCT10l2 ;bnٍ[^<{5 }{ p(RlnR3F7&ҬXGoox?`0UpQ@V+ z|~})œnC6]!J\+]HQ(<\-ha2{G/~ 6-pb&PµTY*Q]BӊgI_BzT0Yĝ[m&e}; \mg(ik&XcZ.Y.\]1A\ 1Mj!ĺW)8#Le=Ժm4:LxjV!*m?jqőv~%}$hV)Tm{{W-_6N%oYN쩜ʩʩʩʩw-UkVU9??GUrM>8a]f6;1,� Js~뾬4'fa29!f^2?>�Y< [3yny锫k B zYVԞfp$XМwDM?iNK11{Z_? GNv*"J,#)Dz-yxß="\\esj#bp۞sise ޣ|A4]*_sXz^:_ںk!Sژ-rZy)?/x~Xj?Zbdp EXzJifWLk{lB hPek΢Su d9kv'w=66ШVE 򼩈I (e꫈!H HALbnp&hĜ}hD[i!<SrM=JQ +J;h;s)Db7sysB4yzJ0N n0V`l5 U1Mk߱1h9V'։nXNժ3{&Q +9!u\v!ǫ57`#&iyDɢr$5V`BuZ okE8k*h*sNDsuAk>75$X:)|jjEa%DD[S:Il6lhQnM v`w{C$Bb")h,GB2p@dVG F<3]@ft)Q31D^@w+~Qr}}2lcJ1clzR2Ц:f[m"hP\JS TuP- 8)8c ˔/9=TnC-qkBp\_X[c9yC\kk[Kv&1\7`ZVc8mZ'ppfk.�� �IDATg/̢cU[z:dgJdJ`;H3@Bȅ:H- j jA5 Y[:޿kF>y@t}$3⺧zEĂL5ϯ3jybBR.-RCթ_bk J ƴ.Îr{uo&)>藌+cVc3V$BJ+2›7??a< a'{'Rq)|ZG(xgf-?_rqZF!Ϩ)RKWuǮB!Ї�@~&t=1΂x%#Xke-*afA+~ֳk**՘⹪ >2BZU-fW؞`kJ-V]&#NPJ%t Spz:t(51<tqޟrhiZ)T=;Ͻf3(Ӻ˪GǮZZ&;2I_vZj-:N_.9z}]n-(EdVyEleZ>藽缊 dU1n9r*r*r*r*rO9~`?|YWh /8H 2NQUoN)vK+.ض_Q8hїW˻ѳc3:e51o4;˾X~PmK1KOX ~$)νν>?3Id4>Q+jr=.:య9>Q Q,~sveɳg|*Ia,Tg[NMew jg`&FĶM[;�Oǁyjp@C,lx5_0}.)TPe771<(\Kr ·{L[{+2l<=cfl h.?T~yy0mͼ&,Aak 27NuREKeͩ/9o+V)j4[i%4<ٍOs£kÇ+l+mϩb@t^kc{|[Or�8QM;dg˰7!)EBLg3n?!dfb*\ٍ߬АPbز w|O>Ƿ΂ -J-#cerXJܖVtיQZBLUgR0{$X!@=4.1֏9[>’!%Ku֦t~#t}[%>!uE�.DJͅaȠ �]3egw]G78Y9 \LSM~}\(gbyC J>ut뎒Gcm0 O*jJ:%J>}o X '`` \d-hE\ca@D_|/,/hdKك2EoA;U+T[4uo + <ĀbL䶗R<Ӎ]^)Xq�>*58`|Hp[kT~J1]Q)jky9 j By:JNې&5G'Ή:-]Ƒg\^pssCL+ug_;U]2{k]*+2g/' WË'\&R:$+Bbs~V8xkİ^mLD1vzD)Ɓ2fT+%[nJ[H]2-RH,�ŎSՊJ d ::?Z%S~ %R2_T.[rܮiֶRդCE4Ўm 6ua[?fcLh|y0)xVz~ >sx1t< j qG.´w_g*MDb,2? ֽ{kToZ 2],ǀcևzcqW|NZ5n[S*'@TNTNTNTNT) d_~վ˒%0xI <1s<K㿤.z)wX%82p/LF3F.{e rw򮇀uo~0%491p�A%.)iNY@D"/һMC|ّfyrz*teM $._o4)jy%\� n]̷w:1VW+SUQ va栙Կd,$gU%ywm@?9`ύ}|1[-b3keߙ6wb#i:`3KEkU<zdL=҂b,C\ҭ 9/t4s0י0`eMhe74*-/@LJ2FT̃XoH`]˜L3Sb3J+m|wa-~ XBC$ Wg{?+>{/v7r|Z HdaF{2=A!8=()H4O])Q!FB<ޫ. Z~ZUBqܑRGׯIEC. YM5=}+rUre%U|>IV*S. )EdE(c&{n9ԍ19~ՁөJs`İ<,kv61jQ%&dKG;jO:g*g ޤz{5e%-5$D(n @\`,HA}<"RH!PsV$W(1hXB"8 n8Rd4IUOXW\(+.u}vawƁ1tmf*Z}oB Iוfm\?5Ɩ}}[CR$EJ%(QlG,Dd  q"q`$"C_ pN B,ŀ0g$i>˙;ݷO9U{|X{W9}N߾w;NwSk׮:u/h<V%R;o6 SA3k5u=5ӃI/ܼz-ֲq(#FYئڗȉ9)Fr/1 >C97t~왝kg^ 6nO>$?#&ϕ? k2FĊ #)k1UZzεq UG=wgOz%}ٌvAz}!"YBHIm~an񃱱#h [ݦzl{5^9fԓ"S okX6w{o\G׮sOvlO/B<BIcK0q s/<GKٶ{E*{kDV][ئ{gddrk׎z}ls FcG5<Q؜sTSU5>lT 9oջ8 f|U>ٞ f=4X2TR|χ?ćA>s_$`xSW0mڶ;6Km`z0B~ aF} 3ظ'3hF�bbSjjc8`RXgCUEUWL8010kx.~_\¾6$rq1"2r ;Ň?ZqQق la|K2,VuV^JDhpsVM62%- ܡ 5]wLKxuD̅CoE C{zA#oD ma\| `H^]jEvl 1{^dεbu~4,h�TE8h!N"H X LFe !.*\?U=OƋҢQUaem?7nfl!<u:2 m!g]XIvKQ\"bĕXǨϙ4(!И1] hc,} Rx痲[>h42U^;[ث[(S_D9t0T?w<K+ta捼!T} ^"h,R3HWh\B13IKGϝW>93S{bhĬ[yu" sZ;\oŖEu_=箓LZqSH]I1 U娼9 +T]RV[m6eTN@-h8h2kx Ӷ-xoGy=D´XC{}A*BP<O<N ɦAi,7[WG tJ;k:ٷ2 =&&@pf10Yg%fDVUw%h4F6PUUZUQ762_۫2Uaz>MKsNg#Nnm2<-^mcClX-#a Т!ⵢ%BFLhCNX4tb?5diݓxddF^whďΒ*GDР%�s3L<f2-H>¹̤~z.iW+?n(M&'Nd;;KFb<^$⽣}R5!WquiYBRXZ(s6KjL:<D![EdmB0%Fdⓢ}*1: f##`@D[S{1-3qiϟ_-K(i&;O5'om#cYld1}Rws`Gt{)2PFZ'ij4A!}bu ݼɗy&{{׸~ ^y#Rbf;͘䤣jou.11<5y.mNde "1@>]T3X*A v`mcFkVCufomw9מּSض4)mXFqko[ 8"*zO#Y0q]>۷|uogH:[K;2Wql~12瘂=B(3sw YY :F.3*A|>y\r~.ݵ~ oY=<U G)k-gp4yw1齷 c!DlF!d &DKG@2uO%uYQʼeǻ#1S?VD C;-fm^z]loR%+lj~sZwQg2nq/F#J58y(yYJ&]ޥAp4D`kMs?XT>WR"fYd'w p [u᳐9A$:QݛC2e 9M*!eH؞T�~)a(^r}<1R{Y}8b'[f<Os9񍗸}kD3kL0Ӥ`0grnXPE-~c<c9u׮1ͬ>3F9vH\hǝ<#m585uMt4|TNc<qnmq6_hw-)aZY(V2ެT܋Oۭ;{_vW?D B(LR[ݸWhh9n[Po1w8Τ{uy%Eu9gtU娫 7&^%%ܴEbm DS-UM*SnSW#l5fFi@]Eynǖ68L%"Os ^՚hdk%fd7r9hDT]cġD$2^%uaXcS4g9#Ӧ>[u^̊6ұVr4#bR9ljuڃZ]C!x̉[l9LNv ȳ^մPW:bQZBd0 ViMf{[9[\ 잪a#_{1vwwyWЦU_9FUXZ`lj&jXb뢨-s$~{;=2Oln ԶG#B9#<ē+ #pΖIYQljߛ!_]TuM&CT"G;f2gkB:_sQ|N>i!R*ꮶV7 TQhZW_ؒo\L3khǕ`h"nD:*uPF*LUmu5B@hkٌ;41૊{{L(ht<O)f* n ":қ4{c䴛܂6^}{d0aݷN-qԴ}nfٟկqoܶ4}F cOޙ&%B羁);G)#p&WTP99T>+RIC]=M*T1h*4mK3s`D!oNՍMll0s>.^Ww"w[@;kq[SKfmc4א4vd0\}4<կ_gw:%DU4Z[0TΣMkrzv̚l+igϵoSdEsIL|Gb-%V!מ'W\AcLᖌҳ~][a47]ŘcNuyl;*I4>cB,E]hCH I!WySP:>8o)[z5e\oR@5R\ ~8x[ի!Acvz9`]#Zltȓi2\K/k:= :W?.+} 7NuwYh~YUײc]ӓ tPS5x36V)J�yxF2jw͑shwIǡ tMc%\=W8t[tHF[,|-DF%&!Usc>.ݓ}6OZefj=#:1ub$f$>%c4e=I~l\6BԾgohTsOg\~_k{^OhT5/>[U~z&n6|O#s*H8qZZ6p'c?s_U$:|SZ:utwa"YE>sc?iQQ(*B3Lw(͞)oƞ1EωSbNҳ'ұExv"1Nּf]l8p#a֋pp81kfHZƀkbֻ޾զM m$zj_ږ$R9BD>8$*1jD~TM+HWV+[M9c$f!$$^f38n*FQox[ukrp0%H%@] x\QS{#;j'+gDFBje\{ǬqzlGC꾆&V;S'c}T!+9˙ӧpuJ`2r-^U>1Z! x !sW% hrPKZq66ֹr &xL]~Y[_c<]fYIHmאgo(Yblb丐uyY*bjiU67`o7_<͙c=9\ݳ'?B4S!bqubSuM$JL ՊF9}+u_C&B2*!\J$jok>1]'琘zcuܘbA[;UZoo<Ǧz%5=BJIjⶵk&йd(lESz�� �IDAT[9q4'6sܞNۛCӄįPLBJ4*KZpEI- Kţݸ>t-|4]]k12SN{l=n-[ȘY =QPuDQxpt^Hh`a<~SS'3J[b <vo.abS2DzG1^_c4ٵ  춝cTU_@\>V4)VcmmbB5Sη,E%fUNg4X4)qh1ƔHaRD_tкBA#ڶT"lnl664ǚYl+-Czyœ?`TD ^ٚcHONDM]ܾLo2qcfcęTچ7GeC]2Y-}q}bZż"=|?(&AzB`)66L&]�ԯb/ wdCL3+"8#Tc6|VMmݛD^n7+j"c'OGyGp,kkAA;O?|S;QPvĩǁ' &)WnmͫW/җyM4YU{^hQZhxc� -!ʚTV/\*KUEr)JR򑑔K*K FOHhmCGc> U9\fZHHRӖ6oj&FI $l#h@h\YJ̕c@vI|{JN !:EjRǜ ABK~H\\u7ɴ#h˥#l=WJeC.L yk Olz7A-((((((((X,[6לq'3 եW^V%]pƶ|@m}ce/^x ?_ ^ yxO~wgK/r6AWTc=ĈmҶ !fgFΛ3BR˛#_ К?t:r[$ZŤ4KvsW0+^xaW/6(C 9`\do:11 QlFu*o2&4;B{9e75{'x{Bstrmr*_erה!=#efJ%k.SmsOvMa,~f}K?AK1LP3붣jM!LX,f; %AUGn!wՅ6Vu5ȁۧ?qE [PPPPPPPPPPPp8~?Ow՗dֺBB05k#a5ۆmQ8$wnΧF+U*1DBD&2zƮ VXL\_":,f9L0lTcoI.Vʧ+04k�?U{SVUŸ6iCjǎKkXJ8穼+)LCo}""b~oPbXCvپ$Lj f>�UMb҈mkL:jCy$-d!rI=kakJI/((((((((xCLWM^T 8]PR=㴻|6KV CKǹ&tAۑ٩+W'-dlAAAAAAAAAAA]`c<_GF#<m 41kmQDl#!("T{~Fd-Mжi#V;H VGV%ƐT6T ;!hT|~ڡ3i4g()5W%:sUUB$- 8DꏣV#W`t7\]\nX/|]XS-"TFuFpUNsmh%Z}Cj%ufu{x>58.B`4YќQ\|e33=4{?sA4릹WbhuDmF�2*[\,oWE|ſ +RPPPPPPPPPPPP"/Oq`<1(QFk#|]@ZH\,3+.~ !fЄp8By*|](S5>cĶES쁕kX; % IbhH &ZjZ"md4FBњv-}s^9TR։åTkve`S'MlJ\gk`](%4tqe]Yf߻9ꘋXTwMAnBioUjgJ{HU&~Jb5`93Ym.@EL:,2O([mc31Km-x_򂂂w D{?[ոBIXUSPW*O۴F !W4h-Ξ9[7o%d20(^9.iNĚKbt$Fju Q%T[#Zz Y%LLۆ`sd3<5 iPX#aRpY*.+[E,TTÁRCFdu;5Mq722$GXVu*Ŷ?YOZE/I(lAAAAAAAA d1\*bTeOsk-lt_.A;mC9\l lF1ί*U]!UD}֮jRO:A|<ІiqQhQm .qy>G`W^~^|]ӌwxޣX\rUFEC @L\Fv)fƜ\ TB6<D4SUKI}5)FSQ=g<ڦa:y#HaL}^2p.j@Y)5zŲb$aq[$qw=>}V5GoQ, <DFz"9fŚ0`ߣr YqXPX,:u5۫xy~/~/xcuwx>1WwiD1)GdP{T<DbRXEB&4EX?O=y/uUc`4s^A5PyOUU8'4Mi[�Xrթ#՚%)^ll8ﺀ#kcY]_UD:UT1\6hGJpb5cՈij׻9{ΒY68=iHk7$斩cJ]ZHdlHO|AvǴ^gpvqmݩ2AqzO,}ؽ`npR`U 8`?Z/QlDPo( K^s-xؾ#|ǹw*Vp\țNŻ .?]Bz#r<?@13i!ڮJgGŹ-9Ŭ!$+TT'$ckJܗI֞8 nP7ŽTJ@C@ ,3I֎#AbJS:*T;Quq7k$l]%a"cmEIa)vQ*樥H`UhIz6NYEae5p'8EQ>q&wmRa']Eׇ^Vxv}ᬖ77fO|t ؋2=>Ͼf+jYhiJܔg!@񸎜 Lv<`ksY;mv&FAgjL(�҆✩JxMl,QbǘF&{G"%գ%rxsj#\6 O͈ ډOKv8hCKUU1r6e:Tk7=&5!4:X7rodX͍㭴;u\ޡIo<h{.y-((((((((X Q.)x$+#b~;2hCH|2�E>}dM#^9pGbqZX-;> ?“fڶmM*9�ґSO<z*x(w&{~_PPPPPPPPpӴ1EMɹ:E@m#SLcV [ 8٭7_ENq#KܼqI\1VS@T(xui+-vQ#B-t6UIY8,5*! +J`#JԘTӈTԽBڶJtn94RWU"1 K;RzjUZެ/LJ[_s䬨'E2Q B\< v29/dTI胱_Rdsݡ0H?HljVY [N,{9cҽgYK[[譏 ![PPPPPPPPld8NsX(uɄ}Mdf˦~gZՈĖq ^{%pC-:* O7)R҅=DzAZq"fu;,q2¹GP}@!\ݸ/mAAAAAAAome.P(-fbCݎ!`uU%FZ='rr$/Nns v9F#EEÌ:Ul|EEA%U_x#03AsH"�+#kjUGj#{U w6D;lc t"D%?kqKc`uA5+bJgw]"rD<6 y`l%d##1D'.OΛ6N;"T54Ua~Õ!B1Ζi\c]Kb2w@/#FW_WGxXi ucDt̽+]o:y ![PPPPPPPP~7 6ωw19ى 9pə_8"lḸ' 38E S Ujž=?nCg1L^0Xs? B!] G;-(((((((x` QQI T4j* DX{ ΞctyqH+[JUEJF#<;J)\9VT;SD6um0)&4 ~-@V36okfej0il_VzODjV0f*84QP hmsD@Z>wvŪx'DJg9Q"1UU.R <ةcr*Ls٢T8Ϊmu"ȓssaa`~,Iż^1K6$B!d #[Lݦ>#e g]]N3: *D-x#(U9cgrb:e{$4DV&9t^G]o>2ekޡa9ׇ;sVlAaqϽ jCCDk.i5Z "hG 6!2-{8˻l]B!ڸy+CFZ!֤~qTn'{5 !ey:$L]Nޣ=CBbQ#cR:;=v&Eq1 'E+J*9$N)N:vT#wh۝R($w\U@ GbCkM[MJULu"8, ~, ə˥Ło wǒU˲z~s=b9qn dZBHd5՗ UTZaEcRTHZ>͸lFldd٦\W;o]_2th=j@w W Y[PPPPPPPp"hQ#,M9R!0[WmG#WGhW;m[ U*#)q8'LBa rrPc4w8rNe~FMoj/B2 UӘ87 wiRFzkVZ.YJ'3_:~`]?,晧fkcW_ymw} LN%8DO7mW!ҩ`}%lnnϬiQ!�N:h? MľЫ.s]T+z9w+OqV!d n/vHGL-Mή]}=H &G6-o49N-mh*O<Qb~=|\ ҡ. κZ1qe< pw!j '$̵B5QE.V^%I$T׉PR41ŬٌM"6&i5)<5BFD#nUͪԅ9ueNL:𕑮qE<>P!$h&"7ג#.)c^J6vB 3'O|gNBB>;芦bvAcYUs}Wc%ƍMmx}Q>p}YGudmGCqDT|M[Y/'Wt# uaߍU\ !{>Ml0;اjb{|kk?x~S37bEttz<.-zcC9$cqxy`G7'0u*BlO NiGwfOWbd{&z RAPơ<Ι2+>]LZ<WM[*MTwđǩhZ\ puĔ~F ƎU,yXpe)cs8Nͺ,oU!AYq|"5]۵ܵ#2!T9uH7neDGhj㪚CpI`؛ܜŰ#>R`u[,  a s{k;V8]| Sfz^l5}#'a8.%cU:"P٥ݻFlgi aV#:u[;7?[6{.V.s'^Nq3>4݊o5dKC[]('݃e2k @EN;;&uPGAm1\UgrUB I[PPPPPPPv 7#IH!1QZ� mZ8\"=5)bd y)l-.xMjA?L:_՞ݱ+<dLΉ9N#QI c#Jlڦ;G=HLлL~ZqxSɆ_z͛7 1jo%eS`fԝXۭ81_@#rٙLes&ʇԍ,/ Fwk:Q\Y^} KVgp,aQ=;s_T%PU\2.\^dDpmn#tߺ5N"/'{Ni]?c}͑P=Sv't;<Μ:7~3=ڹN=8?zCQPPPPPPPtwYpN9{ Rn']6c;AQ\ brwwŬD*:)?*8>g yWcb! 4}G()Rc4EY@ jKS5zDH"Jv6"jfE$tӮ_1~Z8&sC^H(ζR6Ch bY&w0 JĒۖEE,Fڨ1S:Y:u!kY{U༑ٌ+ׯ#"Mc#7k!xsN?>CLA۽8̄[ݝ_:/t]f?=l�� �IDATf]*UUq7xΣEāS%p# އB0ٻk~nw7ݤ^?zd62 +;WpW^|טܦ6<`kLwl^%>Z8l<F<)((((((xcM\͔sq]F~YV%Y eXQ#jSB;-ZC绔]՞ms'u~ۨhrA-(((((((3:K;;ެD$j*H(2&] DTLPD<%4l+=1;TڬUDd {؍ 1m펛hܖĎI\TxEbh~m"TF0IHnT$KInӶԾ*B`UZ]iC; ھ+LQbQ]M]_[Jec#'66xꩧ9{4錝]#CU.)=x3.[[:X\[),0=sk\z_4­[۬fbru8uUqEF#p<l`edc5sbc_z>iK!ꪢL.7^q'O>ݎ4?D,izV7fC1n3%3:$6|}G!` 8"PzQPPPPPPPA!vQ-)N1&UNX P lJ٧I8/l,H9궪*!65!eVlfr6+^ `4xwo&Kj\rު$Ο?G?Lvw]SVVcYfB`t::e]RfX:&U ػ!jNiZSWIU!KGƚvլ56p &Sv&D,! i{YxK1ekˎ7|o%Մlˋ~嫼|kՎnܣr{߼{9+/*8} ryѓxYcN-%::ٌצ42[dī^ |Gm7֨6/<ο𰇮]!>wsukڲt}.: g湞BSD'f#`*gK#TsRzu]ҵ, (<nso~<wG]$ʹP`&M)>H"-uDi$H˦ᩒ6ⓢԈT\鈹>N'w.v%Qŝ.?81eŋSj[VY\b.bؑm1uv}WH9pQr"$G,#ĝsBR%KgB ,A[YOA_\DQū _N{UvhER37Aljyb[vxr%} 7F3cKdw"cU׹dXGmy~]Ir<MxʅAË7vpkLi/>n_# '>M2l}щ?$"#/mX_[g@ؿ=p{gd(|+/.vrαn<uf6cw \yx?ιxaeAAAAAA54 ~Bw蹺.ld/X''ڹҿ.N҄_ ͼxMo]ˬoxRS@^ ^"/z۳OYoG?z7~G=Y^~Xn[S}QAAAAAAAAAAAH8FIyq=hP|" ]�Dރ%FRUS4.iCĩŲ!DT#2P1Zդ\SstUlU;gY,uqV \mkH&aڄ}mv'DR=݈s#פ̈́h%25 G=1!zM F&%vVnEB.&kw|ӵŧslڶ-sk, 5~9 U11!m)UQr~x-ÿvzDNۭ}qcײ~<Yt3?չ!|VM]G^}טܶ kWKl<4xG>ęSʩ3/)ۣ^x9.>:8h9}QNħ6W}k'5acfSfލ=vn3 MtdTq ._? ͔{\lg#llۥx^? w̙'?򰇴]νvJ8*8~q9 ee uKBߟs<4oG<Y~o}v6=a GB*/cm]AAAAAAAAAD\jG22ʪJV 0sT"H*p#<2<3eVdqaG 3r-Y#8fv5g#1<Y`)m&"Yyd‹/( csId,'UcWw!_~x'7+mHtw[UՉ${=+3ދмFaa,qpVB_D>0,8J{sd;GʐPIRC-·׹_%0m=7wK_yNo#6bw>s=ϴ?ɕkyo#ɳ<A`z&u}٥mwhC ~A`oQ^|+kf\ք1=P^z<-mWLpuf!p6cq\k8_?-((((((6"Y8$Thwqԯ‚F=E9&ދ}1e(wA` 7:Hv8쁴jdUկ 9q>E :H_Muj AfSbh42SqRcDcĉChL".Ֆdm,8!Tޥ:ш枼 �A#${O67{Ғ&翴A_BTOFif3Ry3f68퓚dj[qxƁdECl0ު}dӱ [#&1md-Mr]%txҺJZFb1iZwze{»my 1ӝ}doyۼE7^czx}޸rwmqш'N|^d O\ ;)OϱA mh catm˵74{OlUg8θy{~,sL&67P o3ֹgP|(e靶_=j|Zh5naw;n밍)&^S uy{o~ז?޿{Nkw߿/ཛ|!}K۬K)27t:Ƙ!jF #Тa1ckլ1,g_ΟvI1fʐjɱJ$5B]B0&WaK:7EݮrD1b<yCQ5r3\lkql!)hjgڇ>ؕ9t8[7XnUuźM6f^X(LJ_$u:A]O".`S`3ljVnwΖxxa Eu|a>>9}K'W.ssr? nۯ.5fۑ/]gǟW9s1Nn<q{W/b{[<rZxXy?#m#[5O$r{r߸D;SZ҆)Yq*!쀰}upft"1lnj6^g:&W_<|򂂂w-\ "]qm{玺dkd\ 0 }\Fpv?Y1~xڳlv̏7?8q{}.m1k\\>qaif7_~m?z'O=08w9.kkq}3oy] pB$G;t1Q#^ACRCIqIQ]2q&|RjR+QcɁCxq8+<{27Pb&moC@UP3;B)0p!cr:"{0O}g\xq!4pÄ[Mas˔FYUuH2OO~}%i]';cmm#Mж- ){/j^6u?mM_:W^xmkϣOcw<{Wh&SDN9NUc^~|̟y??֦9k_rW_7N]x'v a>!o2^%&us5x̶M}K{Ϭq6n N:I{Sqhqĩgߙu4_}wa}AAAAAAeo..nw{ e6ciDv͏GsG~_{ϼ֥ʍ7͏=_~ղ3?___J\}Ǐ_\\Wޛq{g9:,ֱ�6 P.Hxy<<^RBʥ[T]uq} $B N!`la[%֬#h:k?Vw4X_{^Q}x�&GwZTcǾ#z|X3x//HN"E)RHZ--CZ*7PNJAWziLT|ѐ!1f57tHv`Ҷ)(F)b987nqh}=_Nc^TJeRjCFJ0@Z7|Xġ%ڪXX4hu-vtC]h)CH^hlyYmcސZ!N;+o6sBX_\׆5eQ=~\ZWb/Z-XuKȎ<Á~WObjljx,J5Y9253ΑS%켏JWG}^<r:7vwku+ŪN01gV)^9ԋlb;OVyN317KG`ef&'LSwAScfftt1?Wƭmʣ& ttwR.(]:r[9^Z`_ڷ E)RHqc1"]"cDE˱Y1:J!XѷU+c(?M@`Rm.O-5ڣR/ȩ%$ߴ%KC+Ꮯ߿s ᅠ_8,= z|tɺ#+Jʆ鹿cn/L=ߗ!|olBƉ鹿"E)RH"ŵ _+l 5%-A69dQ!#')m �5SJGZZ3Ƃal0GVJS^Z eh!o+L~W*PC<e`@ ].!!,CXh%$P X`Pֱ\Mj\fSJ%*] ͖cI:YCg'=BjZaHSnllZ4XY+EZm:P}VD4P<NX_&}!. _ȗNJssleI?LEaƦ_1\'F^43~*+7[tQ;8y)}yVPЇ_K8ٳM,04Ź |ΐZS3km}ffjX\3Z~ʥ\ӕXtBpfcd3w|jߊq9wM7u%}ǙO۹s')RH.###Rբ wus FGG}]]]lܸUV-zÇgvv6ѧ5k,yB8{, dŊttbm> 6,IN>mgY6o޼̞__(Fig8 Ѿ`lU|1䥙?6*RZE//V$mcD:ybߥT-WM"n Fzȷ3"6omEpAdyľ w'H?{+Y[{�o]Ds_�LƆjMs~o_ugwm ۗmw_?UʦH"E)Rx5R+eRH!|Cض4W|Q>pA0_@Q1jF!s0e8416_ hMYц<BF�٫ !&ul!9DD[BBL оjm>!WEi& &a߉]TWөB9ETaaYPfE�:xDtN۷Lȶ+r}2UJup=7aM2MH"v=Ѧ^'~-99N?A.+:s >7v癜aEQwҋ:#202>KW֡+?H-wSg8}Jc(0᭬H]Zqd3;;˙ӸAK13pvz7'u 78u뇨瘜+'H˥>;dY9@yFR%sd9,e;}Kȑ#}v\1ヨ?pJȦH"U$V-FFFسg۷o;ls8|0wn[}wFFF�fΝd2 8O=Ԓ6l2\.ǧ>e}Aۗh"d/7h%?xVm&i+ ZeK| ] pi&]c_eI՜,=J|oKvM oM&J0ʫ3'a|m{t!&Wl-~/Q><\7@_fm%<d#8X9&O~pR_i]`ˬ760z/K[GodA~ϡ'n?Nۿ{7>N+ŷEto^*_7_[SH"E)R+edŬuV-$z@)ЖD8PThkl5Jd 6*el5@Pk1eIZ46 k[Vb4h lr$,E0ye IG"(WnLB4ܤnk<8Bp1,Ǒl)7m&qC2V�)~9 K?#aPj>~X>.O:"e4ϜK8_ ^W쩃O~oE'1YUv=2^r} ujgǑv]7iJy 6[-[f: Ne{�gsۂN-C_ZC*Ӎs OᣜPX%N87 6bﱳ:aB1[97z'W <YezZxtwn۔g+29Ǚ`W, ?8'N`hhh2YN"EW&\.͛ѣ;w0igg'۶m[vpo��� �IDAT?{zzزe :thzBx}Cŭz须;;w|**###Kd]M Yۗhm-.T 3˩{-aeqm˾ {>ؗw ׼~{.Wݷg(k$!޻郁 |m}[ S` d//dBy{{A2a.6{Nl)[>}HR:E)RH"Ekc+DHC&%Z&j7 ym ,)QJ4RVv<F I_#e76>udR kP@!Rɢ5Xe`:G)?j_Vn位4lzGMNSheb˲kfd UKyR0l<gola5f2X1u P!^r76HY!d4{:ڵ4z!u^ ^7ك<O"v̡N%a<,[ՙ?OEyyfs<'m޴v6ܼ?sfSO{<V(v8>g/UXVO~S3xX.X̹Wmv^|, ̂emY?r;?LGg/W"}AHefr ˊAl2Wőyv<Vljߢe'X}G\gRH"ŲqС}{_8ݶm[}/}ü]7C&hghh([oMm6>{axxxA{ᥰe˖1չj_|1v8~lHB_ 4^U@a6qBXƤQ)uik_ iƫ+laa۶;0$ g' FWO$a<x'fE߽ R/GD+m<qEωo[Weߦ-pꢺxYqϦ-Ho/."M'Wyq=ܵ=ܵm+go_Ӫ;>@Ik02{<%dSH"E)R.<eP+)<=B"TaNL BRגRX5RXJV)=e\QZR!9QӾh)!G˒]$BFjk aNX!-i,c%Q"d4  V @!1Bq5rZR` G%rUkI3Vah\d‘JD5*Va%ncMz1&#r.F� Ž! T"`,B%vV H% . <0G.:Z?5J^ph /!g8q6h5NYZ B2?a}āi2dz6ꁆԔ˱9nV:TWnsh1_gpB3"FAr+y^@ZE'Jw<`*r Va1к)nX3(mGg9s 6 \O�>Nl-#X`fUN/Ŏ"i\G1u80tUr~h{ĉ<eb(z6nHPH/?~|2)RHzƎ;ZmK/t[I\ץ#A\;w&ogKĉ1d>Жhݼy3/RޝhBB5? "u)BСC~ѣ[EFsl,3D 'vzddvیEV8-^Z9hdɝm_$!z!85s<}\Bi{1;m.YޝL/+ J8O'=q[<q[X'?w%xx@|n[c)R:J"E�Tr~!YB ؕ:qB!B{aal=qrXR*!|RXm&'(G+ lX m`LH}4>H)l i[Hi[Y_ǩT!g4%i@#fDp^ O@+bd4݆J2t9 _{ U2$nc ){E%X:Ϝ(e -u6eyX1iqv,$ TA5mD&M[:9U.?gL7wXQ XKxJXk.׃"L+TFI}thǧVd=�%%7Ot7x/L~%nua8V7&?s_:؄Y\J58u>2@P0d2u2YfU H._"'w TT,NrEu)3w=_۵(ݻmO<q8qo~|_o9vZx%Loari/?g?YJ_"V<�)RxC#Ͷ?22£> cXDH9s&*~y^"Bvtt4T]`#BUr }YVZRi I*7crr2oޫ"V0IGzj$_vب )exI ^JJP Godb> (X;-/xZ݃\.\Zn8xA8ıIU{;ܞxN"E7(.p1E)RN!Z+tT. �SD'xƚUm_IoO'GN233mɫCVa߄ؖQ⊈HXWB`,SRbYZ%+|2Jcέ^d3 i;LH!DD Tdk-t$NUMt#txF,Ϩh% Aw9mxB y2'TҨR"!{uR*itK׆p|&כu<{"ei*ѕPƆ ٱ_<e괦ӳ_cM~Rǎ+(37CPy*ue\_?ӓrWQi}3ѽ6n{kff%5<ž#k[O*R٧yT*`ِ ֐K7YV5Nfy_@Jrdzds$68Y"5ʳĭW)dՋ%V̎a]k9'>ϴw}߱c{]~}s=>::Ϯ]W(Yz}-x졇ȑ#-֗efff?E"E+d+b{وHIWՄR5~e</k<wrˉ%-[D/҂۶m ѐXyx|6T/ k8im;K^y~| տ,;kt巿0ؾ2{xG{ǎ~K3[{˯oKzƺ!>ȧ󓓏F6`_(nW{RH hH"E4l[4<q3% "ұ1H!\nSiY& :k:NjZTHT J-"m+qh||ciƎoP.p;( ])Co l ,DVHk8flBYP[N (,; ihkɿk6(U�,2 `-%: -4ƀ<S Xtb兑d񠄦6%4l;X#ki? V ܀UfTd?Yj.LHzl apP*0J&59Sg.g|r b(=+7SA^+ e;| ɣ'ؽ'T0Q$Rlue l(UarkkܲKG.: yRdY: 9lى�Re<ue &\t&Z8p A~SZd]رcGb?Ο.vbڤ޽{m}o)e"E6l@. )Gۏ=XToo͵G\B6l? 7N^.'Ա`Ӝ=j.:v͚5͈&E b4\#Mu4,%v8I-ML/|Ysj=kB?o}ob߿/rR~=|z[9rl;N0ܿu}~>z'gǞ=-c'f{ǒy~U)RHzAv$E)R!!lMԏ֑JY%iHNUyYE4J7HW_8{,'\.#f.k;O_m+o$h˲|9 dY쥴A5s5D$ډTcFf~F]nӠu\uTQH$O_T)M5aj"ByiU<m%aL gkm F9]RC=kzRrVutHEW פnpZyyxttg3:R) Ta]yR0u :�OävtRHv t ?BO1s~v3R%_TKB A]թ YWǩ X2K_IJd$HfP.R[Լ:)ʵ28B>OF8Vl?}ʂ=.w}駟NO~}߱c7tӢ?C ݵk?y? Lz衖6cǎ 9ξ}?s~/mH"L&>=t7G}H!s0lذo=A^8299r=}vȗu q7c˖Ebdd$ )h/?(很E~3! s nYGMt\I"1#[ǚ~*U/1koh, GN=߼_/w&+2=#㱣߻^;~}ox[/wKD=ˉ}X Q)J?'E)RqFJCD@!*5H- cUH!*V:$q}P mS!)Jز�CƆBIi<=Ph3ܬѦyA3c :Wi!!RU !C-"MedY ? !J1dlp\cH3: EHjSŠ)#roHHĵ7l|6uK\π}Y 2>˨MEC*-)*p5y$:F_Ǹx:K" a%pZOz1Q`gl#')'/<S|le] s3x+mVu* 9toKxnԏٷ5(aXƄH:y&KƪX)|%*tt6ޯ+[HK)*(OV*(G6B9,\֡^x9m)jr,2N|W֨V= =B+';L;l=\BAW~#Yf200moݺx6>QLJ>ܴ_җ"B>7TH"}}}ø:tΝmL&û+M�>̾}�6ݾ}g,H:77OX-oYȶx߾}vm رcW~˳JVt|RAD;wEfd ΏG.xS^#.G):g;cNV#5Wk= "[gw0[MX<=pV^8 LX^(>6r?>}˼2燣?\u2O,D-:ܵ\<䇸{Z6tod]z'N> aruq`>O?M޿7ā-=)ވfNɻ)RHeYdhp>ҲZz>WqHF*@( RF:>()"FzRDKZP\U٤L JI^V Y FOS͜:p2G*Y_(aViU4ln-(kn- B"L2 $ꑀ¬/޸ԚH, s7MBBJp,| K)P _+*_ 'BZ.@&z&@7ϋT8A.[VTBZĹ_Wgc̎TgP0_0&V�l9\/ly:L̝cPN' y Mkdig7oaD'N\FRd_YXuW_~zT|(�eKyTl-}pd-* K:sӋt@ݦ^Vd ;PW M>'ɢuZգ\ =V +mLP(/0jGO]W/XW\nQ {{ޓ^*'m3YukrQn׮] XRɛ"E'={6A6㩧u]n+ܳ$&''ٽaywF9k/fr9w.hW"0<<̡C�ݼy3`zB{ 6qEŸ́9d "u76'&nbIŪ-ILB÷|'%gɲ5/W֍&wo#vOwAx+=DZENeE*<Ƿ{6~ohWlM\vڌ(B6բfm]kΟx}z2GJϵ;޴xʑ)hhO"E)R\_,˨NŽP=ؿJ|6ܖ Ry.gV@vd 0w0g-"rVdbej-Ƭin.|[dVBcY6Ҷl+ J26Rz6H{ QJ5rIK:h-MT4swtd5Hǔ}tDJ4^D@ۨ77K\ u2Jd,%,< O [i<ϥ.Ln`Ȓ# 25+5elp!hwMZH 27?-0\ic}6:QJ q]ZO1Yu'keVA057g)UJɕ@XP pǦlYNT  f}9 ]m؝}؅ UQV% 3yo'(A·.pl(UVB(f, usyBc1VT}r T.aI[Jlia[6 ,V-B1K.owضM^m#>}m KQq�yh;ҳ6m˅Ja{Мj'E)a>OO|">ݳgO Ҙ~۷GD.cÆ ܹ{wI7nKش`\,11htZɫyKmKi.n:ޮ%kjkmZ$];۾zl)w~ߺtޚDm¢/җYw1]7*~/뺇οL݃w~SX\oT7-E)RHq:m&.~U)B5ߕRxܫ`[a=qfHBr~K?'MTA^ E,:R*36nkeG)PK6+x!t%P{ 6FX2MFCm+mξ�� �IDAT?Rh)Hi#,| S-Êwh1-#C,3Fj T4Z J* RlMF8}le]-KB(^!l-(+rABmvnjM+y)dZ)Z&gyyDKurgQY( n]rb܇@^R̫1 .l|#kd 7 8T9P]`3go?~<'`f\ (bO㹰;@W}\ BMcgX(z<E63@y>Vevl֢R֫ ,$nݥ>yW ukFgy;3a R9E)R$//fk;37F}f͚e]]]ܗԮ}!; ņ azzs199I__^H^i,yA g<G6ηNx\`DkmgSPU˛$wS^{ِs |O8>}哉2]N7z0PdC&z L0w74?9(Dm oeeaMd}_LךDgN  nMl}&:ŵ=ԽhKCa0qPA[ڻ}-OX=ϓ'r7leg3o]uDzƐEL"E)RhE8_u'RAr,0Z #cɈgM�/HK> N6䯀1_(1CYR HViIC3*R>HCҠV n $TCB9)m Vuli }<f1i}W)63־!W k =/a3И7=CҬspX0V{>ݎCOg'0V*Ps0cA$ G^Ɖкtfm֚<zjf�R@< b(W^w?`~j<]]&::k:qFhI#}B}czfSh:{97:Eyv 233ƙ#l^.r"NVU[Pi2Vtg~4{y]/j=( ~ @1gԲ٩qOhre-L|<uujM\(t9K"&s2 c hQT+H)dl|u|&Y{7-׿Ζ-[ǛmۡX,^TQH"EV3== @OOO[brժUzQԉemC;_hOȾdM7Ğ={�8qsssQ7oޜ+Z#> U\QӜe[|VHY@[E2d0z4Y+D?"R/ 6}\]}אΪfv Go-fӻ\ ߾B?U~uo{~c<ú!uq!o]}I)m4L )RH"E e ԩSz4 IQKH|$9 NZT$\Աh,WH.[d}Q]\a}eԵ09e}d@@ [|~Ov+z2yp}$_*eT+VQR- up8ވ ~_+N_bߴ HcLHk eM4_kwIDlj5B+N_(Bլ#+lYWGΏsV-eȷ/Фu6DA[m@Սv]P �+느yǶq:Vw.N>r瘝cLI UjJ3+s::Y]W~A>I!{v,Jijy.BfpDZgO=`]W /$5mt|vXwf ̗ <gDrY"p<zv mXd, C2?W­( O>g=$)w鹺76m1}?b3CCCݻwS.[=p@bZ]O"Ek\ o(ƖUw3!VDƂ{ ~}o&Odu%(DHM&a*8)MLii-Uc'[Xn2AP*!]%l;% l#UDh%h׬y#hT\;tq).m6x/RH"E).eۋR>Ѫ }}7(f !l3n!Qc"LWZ)P TbS' e@()lAJL[BbImtm !-G*MLZC{ܖep=! me0(`}2n !@k tmv|=}<‹,Z8 9kuUx-MZJ|-6M*P K}-5BjPpFp֊ 2\.).dxRY ! \zm-WP("LΕT=JgdCcLR\뱖fT*\ܟ09 K@FoKpB`b +duseuEȖa N>OD[6E73y 9FkgrgFf(i AsqƧy zST;n$Sa},|L/YT=<4.jevg<#mXjfʔg(-cZ:2=CjkX -A 9 "~ <Uŭը! 6N–IRA EqU(jXR`KAWGB-5BW}/kyomر{۝}ӟGGGȇ?DJlܹ"z"E)͙Eߛ'''q]uCs7pCK FaFFFVQAh_ [B~*'V^4 ^C%j aITm7Xp@ɦ9b*VT-GvQm$)(ݞH"E)RH">ǏGAm{, j9l65j)EVqrN7@IJd|`0*GhKdGh)JCtlj Lِnp uc^>/jT*^t0XPF-#9rƱ3$RZ8v|&֨JZ͵ V"ՂkܫBo~P Ɔd-f _oإ@ M6eU V;WXQ/ 3%ć8S[dC[ (`Y_),ik$Zxf)eDr_I\7V>s#B"mAW##GDXM29s)4Y"ˡïvXY8Rѓt9yTп"ҫ#sOQ͡:kyg<Gu`Ϣ"KgJTEP̃Co>PJ-p]\7-69<:_P3f'Ȣ Y,fjlڼG`ْBGǶ)dr6cea%"3]+|#-ڵk/hCP"(v<c<CsRB6E).}}}r9*<z뭉2޽;ڎeGFFxG#{HL&ٷo�O=z׻ p=!6mڔaJ_.(whУJv R!\lD} /%'4R6DB/G?jOHmuhDO4n,\hT"E)RH"E1bl|L|6G\Ґ@Z5_HjYqh~ pк0y]Na!P}Wm!m𑁕1_ R 5(8J{xuH]%E@^rP)UWENU:?БjYgC Ji~*fp:{(9}z(QBDrܕJ$"cñIiɪ$l47ܱmZihE7*l2WSH3W"QMmG R$7 ~hn2S )$L.Cj{؁U}jsqQT\Zuk 2?=+Rz(e4છy7103 3P+Z-3溸m`Ig;njM^:<N!vrBEUk{v7{=a +p:]tuIf`Y;� Mu{Jgr*8/�lB3=1FyvUTٜm yīJұ�er@Ξ$w .7}{Clݺx G $7d``:"E)"ٳ#GpM7dT`7ojd"W9|p=<[lioO(H}وطo_m"FaÆ1<<|q$'01Gx\@"1y[OvfAh5"Z`vBpܺpianq"<SH"E)RH⍆ByPpP%]Zke2XVd<p B/TC&idf EmV+Q !@ $6·P-|@!|* 4J l`7kd]sB)jƆ)0seʮ6>V,Z֪iM@HW|XZ XrM6Y\򘛛HòD ܥv٠P]0NP )c<9vmuh5|V9=1XgB(c(iGf>} "T`( 0}ٱ, K:\hO1LFD}]uC֦"mUp2i+V󬾡ΎTvruѣTEnH|uتʢM8x8۷~\QLR-SA?<^()+v6" PN'yK)PgrV0_ YPE[7 sKu}JNgƹtW nkYXƫ{LczjC, Zզil-k AYTdsQ١ܲg>:;;`k''ڵb"E)l۶ uܧ8y޷h]͖|Ԯbxxm۶](d26o۷oo.W HQM9Oe0M0-t#G1f@O ]J8'_۵r^"91m9/%eSH"E)RHի9~ruSw]:mm#[h"X@H BH!4@pYM\&"o5DW*nM%XxI)Ja+H>D@. M U1#ML$i RFiI !3(LYBFQBW!|rRJvl&vmtV-U<bsvI9YCRqocLBH^ۋ%崯},)B2?_bUqlId\Fcԍ Zֈ@rʉ#HR'UMІPi^s]gf:+Ma˖ YZ$O]�BVS>863e͆ΜYޱkǢjU.[7nfGZfbtT+{G|�wm>8,<<NN/*Ţ`\fxu$s5 ۬/b vw=fW:z|X9K__ +8;w: L`)<Lާ2snė dq:Ek,]7*?k{hi! H4^gMLF3Hn(!D\&z A&QDPhtyUkkn}Z֪j׻}"(WR9RT8fXv-Gf׳gϞ.cŋ;_xqz7q5|c~xx3<K/t||GRk׮YO 4/x֭駟fddfڷh",Xʕ+gnݺVE̓:|^csAV^Qa͚5XL,\%w`>{lذR`3/JGнt<y34;hVھzF<E<'!V�uuPBa;k>5gϞP@ (P@`tt㌥^l6(Em R|.K'\SƴsiZgJ ִn/y+[Mg،8(PI5) E"vm"kA*O:F{lȓ6rK:Gc(4Y9NH3 Ĺ4Zi�O:ڒ O"h6$6 !*re#{9 Z{DodkD)ڙ) [ή'|"V%OL1Vl߅TFɸISE'.뙱kvkӑi~ YK-Y.^&! C1l޲gRd6k%gi8 =cccL֚UVN&;I P~e)Ql{|C˖3r){ib*6DȐ2$1##r,^İbmHJ 04/$ N Ty�Q80F Χ`Rgb;m0PE+ pC,c8i@XЎs>իsp. flT deY,柲Q 1$cOngێ]41aP>_HR2Pfp}*zzMR=d (P@!AX-넃,/N*#Ent6 )Y-Q'U=(@NʩՅ.dxd)m9nUtFv;bxr]\$o>%<yl_fd (P@ 8j?DR Ix"IsQ@ZlE( 2*%l;aFf�ָVkORA1Fc'1\RRBpH)P@) hKdR7“�Dm4nO. yRN>sf9urE gD8 i0L�i=YYJί+B|GZ! T Y04DQgdd/͸CctukMNڵ9B/j#R8ݙ.ysXc%56z '$HRΐ A8֏YgݤfDig:ϯ[=6RB6I5F9k$Y:RbP¢@J[%"/ }ݤٵ+;xqFXb"Z]ڹf#t4ڏ}ĻXlX<q>W&,Yd%I4M f6JBLS[xbӽ4j)E!a3<xkJYr)}%3icQH4SH9d` |P CՀ,蟇ڋK[ьڻ Φ24Q 4֠9FufRo((a.rU(+ !IEHC}r[A(P@:0;dd!e֓Hk޸^p� B6:C1>icA.H|"̙4wy!;ۜmH ̅+/P@ (p<S/:5a Yxfd ` �� �IDAT-Y 0 ֢ۨd"yhSvd-lۨ:Ms"L H) �!Ēg5Y\�aTaQ(3qZNZœsъOJZ-[8GP b+w۝E4J!p2i߿=8L6kPgJ<i1odWѽyn3[jhM| II],qv8<oaW,p֋Q6陿G?|c'X:)[Em IfO!;}p;AΛ! $(Bb%O<YR,SӬM_J K/eX 3݈)U*:F &GٳkAPQӆyMOj^y*p2FZIS"H !HH* D,ʒKsx/&DBPJ_p4qŠd$c{G2i�0>:tg2] dD Q N\Dwf@ (pHo�cA e93Bv%Q&vy)m]ff\EG͟I9 ;Z:|Jsr>=+VvAA(P@ 8Z8J)8'q f3fzj4kjIOY#d([AԅVYU@z@^::+ԑPJ"SVA@X8#q`K8)-?{]"9b[YXgHOYhGfQy [Q4#W:[Vؔ tmKJNxo8g1y_)[F6%c1ړg(zȐ9`t[dp÷<^"nB Jωq@YA 'FۮWw l\e|.~}rWB8f-bV:Qʋ]hKe˜sfW<;ZDfjJjaH@4ex<ԾIS[ Ƨj<o(~@46.ĠmGhWOTP!ӼғY|$=8N#n04AhUK&X:4@y EfUk1`׮$1]{!I}$1P]‚W1^ yfV=o$cHaMk%J>_VDH!3F)mbj#x c#[@ 8-u{1+H�-@ [a=y*@ZO"6C^חɽGGfL̍bD2eEۢ9yxmu.Wn(t2*z*Sb (P@ 8>2횦yl],Jxu||i5H)I@JN �gff ѳB9RSIA O#ZJFe}dl̮'>bIwvVi.;snY]進4,q0 ѷ> kՉTW6B-;_>jRҋNZ?Zp'ù,B״Ov+١Qf (pZtiTk^mlƚCF$wK:#a<G;:O|υE} پWbψH 妭E¦KI!f8&ld?NxB6ӣ8' C UFFg5l~ajSDSa5G#.d"U#a Qz)z\o2v`Q&\ Qa$S \uX<\I=`$$ ;PṼ۸jQ>B5HSM!B"T} 1� T@ QZ,[8uLLSשZ�B J,c%$qq% ]@r'* (P@<cD }Ԛn-9d;쨬u7:WL=˴h\E4J[EE>]0X#[䪛; (P@ (P1l"_ZP,:8@`ʸKSќεI,Sm=A,dVJlJ]S[BaZ7yd>D\c IJ9a�SuB$60hYvs>2SiNv?&+mqkqڡTJ楾z9b!$BJ'$Κ-|X9} !9g\']+ VA)=SҲzD>XJE;KU͔'m Bn_~#lZSGuWMg5ƭBJLU9=U)6#zrG1 O&SpF!J)"%2 ?_SQ;0<o>A'AP_ ԌMR4#>@9Q1 q&G(bU &&yQXeX–'Gx葆(Ô@‚\|J/^I `aaTՁApV!ʥ>ذucv06Ch2F2@3Ah9M CC1`>a%`$ ʥU'8,hN&^}@ (p@u*f6�Xe1R*_00FY-!4+} o0ƧH^h[e(2sT.u3Ow/%?Ef/9/3[r٘ljfWr}α8,-P@ (P@g !8Rz'a!PdDc0@[  h$ b%<pѱ OaQQqȔLJB pZ)3eL5>P # /8@H@l 0ցyr~8msL5$ tv8AwъtnRah`ztB 6_PZkMi["+!JHuXJJޱ<rhd۹n3 c`Ȩ^߲yX,WY55riR\YG3r$c{;M>mk4rl` ;+ !v.7,i~a\K '<!OFI�mbb- Keaq45<?C<]O* Tx,drj$zFLO#U?[v[ᚚׯ\bE__SM(Ga806xcB^Wr7g9%X4CQ)i@Pe]Q*$Z -7nP?ۚhMVe`p }b$iB C /`x"/^bzbQN!M *E(%{8^@ 8a!}3nAP4O <YH)QR-Fm}{syvp<} ;s- s+L/͖CkfܷБ(+?M>:6G (Pเ7@ZiupuB mZѠ˖.s?ȁQ( MoϦ-ҴKTJVdy31%:9y9"DC A֝uu¥sIDmL8OYgF))f3%|?d8!uY:V.#g.];_ôERGO\s<:_^Jc>&]伎? G(|v{m Y FwY`dOxBGp$(Y" `t(xoN## qU'-w.jM@r/I#f,'GHkT2:n0z`GD⁈8! 4k-\EIIrY$,e!ȵ4 aJN[e* V*8Շ g\bX88 t W?GQ30]x1ohK*Ӭdxh$FPo$8'Xr W,&(ELLN0>9` R2�kcc0bm7H))E< (P@29IU*;6&rPR+ Q�>wNf\%th_YXY`Lb3;r$:I^;=>];O,5wYn [@ <GS@~lRba VV Qbl"@ ܵFabc9y N:$a}LMMXK^L嘼_)w91M#<dI+Vjbpz4P86g'[eL=Vٌ986sc BJָTι[U{@mW|}0$%[aIEy1/)˦etdZMR93I^rgםPuGzD{h0ʴaӨr.nG<'<!lNaDD J"*E'`YD iE\ry dǎ]ݷNxjNz2"=M9ECXcl٨D'|�}B7qϚYVcw"6=wkNZ* C~@am $Tb65g{?N4#AID˲i4jT4 m@ZN?}=˗/8Si V0&#0.`cQ86Fs (P@'*W8rsX0 u`e#JLiC,IlKi5W3 mQ D<:ԣ 7\}rHY"Yޔc'yVmf&\z,*(@>ÖqS/P 0^.0766׵O>m[Pg xx+5`,?ԒL2ʥŞ0l ! {0R$JS8syꩧЉ&I\N} .%D*"%m;JED~9�RHpX)aBX'_kIz̡@Y@:\pv>%U`]AZqgZO~ZOhMsBe}.QCX{U"ermQۊ![R=K&7QEG]V jyx \du&I|$#GzNv=GԫÉD7 ¨D%b,^+5$18V$qog0f ɾ=$ƱkM }# (�K,&]yA PDAgR*``>BJU9u<܎E R%*XLBj~APLغ{v/96ϜoGȞ:*1ؠI6 KZ!cjFZ% ˔K!Z'k0&EIIH%$ZjScw (P@'hCqs,_mJ1U8o([uh!B2iRn,/"EA!Q%:J9T*G#T`w;Yn#Gw;Zq~]/<Z&?}۶6d}Gѓ73-W;7u/p(pHw�sE8!1ŷ3ܾɥnz^wp%wǿK}_9O~;<1:->yWppŃǻGfAimꌜy?RhQ2P)l(Q*ܿoywq`t8Φ N)vg~VyH^KzT9% M@H,+e"i:bH,JHdJfuQl*eyQ �)6/r9II I!mlH}xB}lFtZħ$16;lwOۙۥn ÐO:ZޑFt>Jc|ghtύ Dz#\2mf7]yGDX  6@`N%R°uFXE(f#Ѩ"pSS,di Rn7"Ǟ:{`W0moTj 8ˢ*SoCEuBaNq Cl@5c" Aa*B:hv|g2liW,ԾMId#A*2Cd߁H_Z.#U@8nЌc54:Z*""bC],P@ TB/-OW$;9"),UP !@ idPgd&-6 eo46-kaӞvDJA26ۗyU>-ȗ bF8EPs4˛._Џ{w'O/G>?} rbsk� ?;6Ts0Gq(pP"xT'81I>W�XU}Zܓ:ny"/ܲy~ Pak΋)?aRH I -'U1$O Oiyr6vIVUB`,, [< �֡QdJy XP!Eo'`h�#(H0“Z³!(!䯶RQHh6HPJh';B+! jEAO|5nžl!uR ɜomBڣl):H}sn$IB288*&O\soXȟw].H#Dl11W.C<A}?c8kI aXAj? J:I2TJJ#n"T M dL#"Nq=56'/\|[ضb M$I_A%{-r^Ǒ`TV*'Aa4!d~iv_cO%00z38S7q(o`rB܌Ρ%JzPDA5*QB=؆ (Ps ^r-`}N dnLNM@C1Q-y}LHT/?9r>e=NavoҎ@T6)O>^^׶ecWM]VcZ5p+3V.8"jǯ hx_{/?΋)}ϥ=QIW.<](P�jw7o~ן?Q0`a}nB P( 1MDQDf"'| ve 0 ai: T@dJR*gF'2;UI LZS^%00RHiJaCC:t)YKv1x˗s(v)},X7'`ع%elEZ9MM9.һu~^ZI4uMzFK)}9:?HYO;Hka3w+>x6 [>Fe8t`{F+X8f>&'H}Coo6lrj]uФ?'<!)$!%1Hg!i hJ%he(T m,-9t`�Zʥ"E#vD Ty镗o N]a\$i1MM$ Vhcm!ڕ_uO%T QQ@1 ֨qj0=X8_ 0jeT޽�.bllrBi0"B4iX BfBer HtIp!ä@ (prj*eRI59 i^zY#]u۫?K^H.2qN]皗 j継I܈Fɏ1)>FrQ%!7<7\IMbXrЏ~y=w㔇zEEta`Ջwj#o>-P ;`//?ϧ'~?W<�� �IDATv+JwavO>b;xpjDZ0uymR %V6U$֤>@jEH)ٽg/q]"ΡA !.X9D(OĶV~TmikA8靝[6p5Tj3v_!�A֬YE*eDkCbք$R-vւXkQG45o\gZJ!Sd'A"q~藦L ^V@rԦ�h9 ?9 a؁ؖm?�B^,G]gjˉ>W#:Y=wc~G ˆ@FKCT  jFhGX*xrtl|ȼXbsR޳W]$aW 0K4" UdNd76DK !8)aꥢpր0 V+,Z'GXvNAi>KV, ¥ 'cR@( T)PqL%ӵiJ(da(P@~2>c  PR`X4Av>opTnXuy9 ,6κ<nei#J% I$I49Qܳ:7oVxP qJfl!]ʆ{Q`e?R-b#/>xN8~?o՟dC|N@ds{lx8Hg\Kkn]*~յcwG.9a|f*گ)(eHIl-gʓP`]񄤐l}I/3l42NDq0Sy[VJxr3˱T T!u>X9+ rb?֧48�m- Hk90:O<؄'Q$PTL@uZhPI%dY58|psεLOx/]!&906JpLQuK+%p!ml\3Lׅ*eYjnI1)@ALvV>;|q'3 Cq}3[)y dE!S@GhmMר ^c\SR(R0L'Mdl4|y%Yh>/ GzbGU(cDE�3QEfbP ;"aQE86$BCi&k5,? 4F['J4Xc>tAAi>gzz  _!QkkM^@ (Ѕ<8 QI`uD H@HK$aCitM nC&:[ѱs|ɊԎrZexh FFFEaz 2/ޞ]<Õ:!ĭ"(؆"@垯r]?`Qj@e?{INrwcs^w;7k5�̳9X¹ NniR]y%7.8L UtsǗfB_|X;of;azxAz7~8P)ocm'֯}OIkli}y,f]:^~z}7'v۽W:W_ˋ/^s}7N}fn[QθEWA]T`޲՜\.<N#] }<7jT<fFTg 6̫]_wSu: ?ظaQD?}e0s7+?-VkԘ_+7uG4oG*cJ \?|J_c[ɭ[ǁy\qϰhJ[7X^OYlorQ[Õ׼2=7i8m+8{$߻fn=Z^3ż5^v?7}FnOgͮ$:fȺVؽpr{Vm_I?_s+f >N }1Wޛ?{n.tߏ_Ӻ/|/;Q >?[4n N{9{^u/7^a!_}mYk Ik^�ҵl8|;gi~oc^_r\}jzնmxu4vm{f<KdّN^m=«VyN~n3s5 r:ɽ_SM"._Ӿjs~:껷pl>->=|嫷#cP9YPj}ыY?|a]1uw#O~?ɃT*c3yyrY'=ޕk]JgWO+}Cmf4k5jTY}żg_3{ذPgm|˷1V{7'oۇQ굜*߯&nx/u϶giB㱯sݝ?y䟾of~=~&y_KuB57Es8zl 7ߺ\uۿcwkd}u!:E\~z] uXp*WyEO|rYtL=Eʔ$2oxs= gsGd8mٻoNm`YlJzTuZB%B⌗L s+9pʔTt�$:Ja% +'nC OkpQerz q$ OA ];on)<oSVX[ky8u!(E93֞[MOcSZCk ?pH1ۙCn;wϐG#5y;:׊n6ƴDk2uOCHfjdYZW9V]Wyk=;67 NL\#]Y(fժUǴ(O1lb@ʕ Q9"Kt\Љ28^mb7SLLMa1+-RR0he|iv=4JƆ$VSN#44&ēYt,BT)kM @\i(MI>“6#˗+pHdIgsb8U$ra bkD99 F8n=Ą�;W@  44Rރ4/ɒdR ZB)ISx5}]ۄҁyJƐ]<3-|NtKߓ"%J3_نuj_jJ8-cՒCEtUi梑Ӄ28ۑP(~/ǁwϗC;mKkg3ofq~wxl֮{/}M}~9[� 6z?cl|Ex�gs˞ٸʻ_cۃu/\l2~{K x!8W/rqW\ŷǿڞa{7 "fMw9Vgnм :Kgۿ;8҅{nⵗѯ;% kyM[f`C<r| TռY_sg:x?8_�Ə]čIW6t1_}_y/7Ms^/;:~^uv=u`5>q)FGý^?ǽU >, �xnsoYY]^wqзK:#̓#:�Xv~£Bk �_i^+܏g�'v~O\�gC%an^<t9w�ng5rseociwlor:g}]A`?򫟞Y5.1|&^{/??X?Yz^w/qA7,۾Ư_d>wAo㓿z-ݴY;Cp> { .wf99kws_*'/3A7,5n~y<݇5/⇟~?_u8_lA:ߙ_>8ίUr%y{sƯ7k;>εWޜt?\p۹=_8m0y|Q~\}=5y O |}-l_UM|'?7wϛ;Zw1|O?~yU%-i52#<9iG~jkg߼Sg X6UsrxQ�!V8N5\+Xd 9߾͏mA kqcDeQL&PP*Vg[/xO<=9 �ug]9ͧj&P`c`9r4"9ѾBٖH L[!dK*�фRW-ni %<z'ғNbqHHtB\xι~il}b+?0$, mz!&6!'<gd6iRV%Ww9ka&M$y|5 Ecb栎'k-72̞6u=~ykNBY녵2s?Y#ٵU>{ŵAR/xT #?3V'1z=A$><ZI)A"aiKSDŽŸfLL269T3f}ˬZPLh }BYEY%A\o"D�8&M L1m QB1W!nLA*'6!DHŢbZ@a\BGG\8 . V$V"BTXl9:I(W*'!0& k4Z�a5c O (P VThyBrezZoΓUB2C+Dhz#|U`C".} Aځё;:)m*'5$QƵe4j Fksm#+gL>iYT{OÊ+|S'k <HS^t�;Os_R!+ҏYlH:z:{no와`r'/w6_-Oodv#k%JŒKU!GĻCyx0Z w2'jo%-{2C+sdUŎ};?iOȚ܂nsyaևY7|5$2%\}䃼 Ƴȟ*mrCUɅ=H0_|Cov_0@ig0zecw_|~h#?Еjsӟխ=lЅsvn.~:䒥7~؏7?9 kljt.Km>/W;�| gχdC[>=)n)soClk5$e~zr*ul<f�MSc3z}yڅP+|oƫlBV%_vs?c㬨6=3sX`EX�Q( XA50 (="RT@`HyrݻD}\vʙ39]vTԮeBXW[Nxƥ iڟ>W2\}^n_<:V@8\Ĭ{ӥ^V?٨fk#_goan߲nL&<{׮aL<^~,>k&My;W}u uU笱|5w{V~i{3nX ԶziT wiyn{Ȋo-) <Xq;IczS'\?&UV2񜘆@5unN~mn{ =p*?%~Pvk{|:3�"G%޼a_*o&#MCƎ`pbsɪ_^tõ?{d6RYssSʣgVPnc3oڜ>'%Mk݃ݚ6AN/\y|g@{3h"%w6=_SSWO᱃pZ#V<k_qv  xåLuB{֢;g2xvr\獆[�wWscRvJxcuc{7w=}Gv70S8e% f�3-g#S9�êU75oZo9nЌ1z)ͽZqv-햇e !L,ҢTC(&j1ʉ*`i! k*ǩ=�5M"ƉBS֚էb_wePİ|>k̕X )4@cmsٺ*(,AC"A k Xu(R441 i&4KM((H)QOhը ՍT@0MihBA1PEX"|ukٸypʘf&:* B`ak-/vLh@\BL%$5;Hhݏ"CeY%Hc˸xB2h:M!+sgejޓNCgc}l KiFlwcd*-X0NA~ ݒ1iR*:&4d4F̶ii"|>"]]|7 J|X4B"ENn="(l.ڍRRR?DІ9A1t%HE"t*4M)•1±YLN⋒<G5h?(#d# iԸ1s , SZAב1UX}4CGۉZԢE-j?ik:,&$B21La*^W$c(aEf!$OZI0m]a/ļ%#Jnyw1 :rNqxwL6\9#YTlZjܽ'a9[u{=F\ ©_4Z}kޙ钿<ȣ':dlVOp)7p{an|;X:v:5EH<a~FZysM [$1tLñqc6Cwv'w[f~?~8޸;ßXͫD?z$ʧ?=܍??;Mk7W><:1lpW feɩL>2+MLDwg=[2s!.\Ǜbʈ6\;qHL}#N9 xmv۾`{O}<NdvpDVͺ];8K[ŋ'3nәP[ϏXwO'޵q? 3?c!8uz:\iڇeޞqaM#le?\Mp5=0^wɝ}:Ə7mo:oJ7ioE'ǬW[}X wl?&=!lR Lޖ6>Lj4kq9$�� �IDAT=z1yh�z1F&{%vy7$EӣOmWluN׿?׏}my>vݙ/ǟ77nœ7�o3òJょ_ A<4~:jHM]n쬟ybw˷o$ZjM=yГ 2CY^o߅V~.8zǪhw LK=o,bރ2v~|^-V>۵Mu2u,ƎΩvyL~^|+p3ϡѫGq\8e8=XɌ/ Wav<s/ǽ6ٞ_M_>s K‘)^ַ[3xSylyLQ;{8!־w}mإp+7㊧kܶ3f�/`C<3Oui#,ex=-%cE>|꒸[aڕ3X{ ?g@c^>KSUME|v-a|niM&)EdD 7c6 ɲ2,뢪m9-J8^PY_'!4MB>~M!3%HT2+mZU%Q0%TFEH4DU^R"%))r-U9a"K[)42f7\ީj+iŋJ8JRnX~P@&bҶ&FzrKMTWt,i5QG9c+['Yhj[v$ .DX+zv-P@j > ,UJ͈ 1=fu(hcȽTFJ }ٵEQLԐIRh4Շ/Gc{6vmd';$5-&?/DN(P4 `_NHc4Kp4գ -ިi#7YIC-h߹+\#VJA~= AM(/l.BPQJ tb.c2�0э;0ZBE-jQZ!=zzR.*,mN,lϓi=x/ iWta[1=w~|ᛍ"yArY8 a`ǎШ"g߰~_UF~M_6 ڏۺg"*VSnkyҫSzIx6ǟ&Rds͛\dW03DoӞ쳻x뫽R0UK'&�>ciHWf5ɯny>93CVt͹qou),.OW[?k>Co{>[K2$)]yCZѲ];%.dE=#�u'52Ψ=~Ͷ eP96b^Y`,ڟz ѝQ>Jc>}7S'Ej$7dMx_[-h Oq~W=jO|ˑ&ݢ}hom8qƕ٘~SU}ZoUPܷ 4(d{PM;nuV.$xȴ)&@q-}v7o-bFHg,=yڱiۮtmSP[{q>~ ڌKN4+HB=e7:@d}};6KQftّ;#}gcd/6럲]v״v:Y7sMB [ҶER+dGX g ;Ss޸X']6shƔ_j\'b7fĀÒ6coظ+~q6(݇F:wsޕ#=+2S^o`͊4NN Ot?ϗz<cR'twZ<uD9?b_ / 5G?xw؇)wWxu`:�MU#^yy+=Mڄ,9鬭UE @4OF ̪f!KP)|D*&�GlEG �@& 1)1T=(7M?S c 6Lkf8&LLi)9(V),Z7V0RǽZDk2ٚl_%M =?ɲ` |!4@IKJ*bS qo8+Ҳ̚ L:n-L&'dC90cjpL7P4KZUT(hX8B25 a}>ERދhDt^aR'ckiAS Q}Jp)(:"_LE8L~^m~-ZTܺuQ0b&1 ") b j iD&/ۈ^ -+ Mn"P@SU[vlTVSm@Ѩq%0/l3\ZԢE-~!SȄk2e"$xM&fHCfti?-VRUEw_&HsmZ 6+EBH1AIY=�^cA aCRJp*lZ1 o7«zNՊ3rfzͳ%< `۷e4%p½~Ɗv=b/Jnt{ЮМ 6K7^=K hUtF7{8Ofϟƙp{c{KP>'p[V֤MyoޯzS[wӆ>`IlKf]nb'L_lzy\6_z ꊽ.>5JWwqRoNj~rW|Sdqǀ_aAǻf$Kn0 "Ohf՝�!fڽ¥KS'Ӱ'׌r­fkس0mg>KJ[=poy˼Q˸YToQ!FA^ODQ⊦>7]ՏPu}Thk:4GN,;c遆lT<'doz.}~̵Ș yHӃ<YDž6dErڝ_] \yWTCצoe?Bu<,}$"9D|6 F2Ɩ1S ĭ^7N_HU*5nb~ۍIFqxSy~v+V|dk2p}sÄ7-uv/?Ǟ?]FmǝKa=)l ᒍJs eZiX4̎mٽWXc*מ Sx<^bgjbGJMC4|> İ-:ڱ`K7ҒzpN'-W4)mR^ITT�ЅIJe3(VbkJ 4:iZ7kg0 %Pv|&goϤO&.)+`0߲.k C@ KsV` biJװIfw?%EbUg [b1AXn5É'(I:Ŕf)i7oj@n  C1 bDcK4PUX,j (R V3H4FEL"f%ԫ越JyE9H BhфE(j M  j̞rt+~Z (tw[֯|͏jG T@7Ђ~"H"f ] LPt*# j8 K mDiQ9>M%VYIYEF % F rފr %Hb i"uˤ4- 4ɩ᫮E-jQZo??dJ&*fVIa$H:UhϺ| aƖ$KV"QT.N,sH�gҾqE") !A[u>`{9Ùs_8wƝ~]\~Qزf:7Y LpR~ 7nh'U`}9wPv՜ٳ_&Y%=Y<1<V M'G{{ N>Ek43*Y{8gyݪS7sI ')`N,t}iR3yM+b|-YÚ_}:ge=m03F:?�%TVWzkVz qb13,  s`2o=wyߞ?oi@G>piƟa杅GmV+Oj\ Lf]K!ӫW7Ba b?eI46,gN}.gsRLჴrLjPTśVp|'OW�#G\he%<sMCsNY)Ds_sηҧS{s v=+:}*e�n,=逍(޼/[m_iI E~ZxG gG|Ɵq<]O_~>guImkXKtJ*9&l_svާCiwmk]ӾiZMjB_—P^.bJKvS]udga:+6=GaD | ;Z9s`X6ItV,)+2O^v[iCuj/ڲgFCg:gyj4~X<SȀ.-)b^r\Nyg!ʯL {BBA*`i* a)`K޴Ep-#cH.I EQ1 lYEQD" � 6TJqsqk7.2q{#k7' "lQEX5 K!O( Y됲a ͐ 4-܊"HMM0ь6sxk9TL(l*<D¦I:VzܜZ9|~ݵ;lmYZ? 1ݼ(kE8)צ-E[ZeI-p9ez$!^g#%1S ~j`m/&b> 4F$H!f&X=D{+1~4R4bt!s퍠 R͛cVv)C&uCM4bOQ%<_Gjlܸo,9#1L0LU0$ tC`",AA"e b"//%[̞2@ ϫ!%aE,:\Q0 PA>PXvĥħ0 ô;r.jQZԢ,rD:恰$&֗h4[A+c=uHm L]8S,, g_zJR{4Y| bc߰Ǔv,ώ0q'LIfLMzY:mz2 +fú^Ig9q"+L'҉OgO'̙MX'FV^meL*nSfԻ<L8}יb3͈qBH3tᤫOŤ٫@ھ1e _n(FUm&Po:F!ϰ|O\Sx,?kPf]MY @{3 [ [ܴ-|Ko>i=8Ù<;9wAAe /6-lSS1݊KD6}*TmCnU�ƮEVߚs�!:e ;'SX >/6}ӷ]ŭ3`=,vPc^edj ҈E{,߹{Ť9kP!<5 ?w|&oϵq( k=qރmx.̙LEMkΦeεC"B={IǁׯIec8qI>ň-Pÿ`ƽ pL<\upʸZkKӅKό-?ͽ.:,ݚS#7 }jۼ=G"V1 u)wYl7 !ιf< kͷpA|坧ZvZ$DR&r=5pLz�i(BEJPT ^_+�^ǵ mr6I[Sa)vEEV*^/k' >d=鲖UKl4MT2Z`֎eJĉP TPlKQ>+\m?+L߮bYrEDQ3n_U6l5xMx$fRN4mrr )@[)M7U BTaiJPH(su5aeBY$/-њqpj<BIUuR̆iF6>jԺ)@]DH$~GEh Ul'vqtA0hLgOy9D %JX<􈎮W"r.`oy5d¿V`* BՍAr:KXq젮P9!ԭCUP%(´MY*$+*(Ac[c ֣A#i>Щ{JGE$L$ԖAp4 bFc+I0GNnh RƧb141=+*y [GE-jQZ|t5=?8~O`MM<i /8 (dino 業pځ_7FP[:r>מ~6Af:N{_ó1W^eJx{ù' rAiy ج;7W+>9n}t^ c|Ā4:u^:k,S vt9Mz>i?iQ7|^#(|�tM@>֛D/+aGxat[xƺyی}{&?#<7?1�e?з!iWDq*.jyh۔)Wg9!?#�mܳ? .d-qݽӛL;g#Ӳ~"F'p[g|ҴD$�"+< 8vPJZr_O[[25i I 9aB8WvƗWq}htm=g9,}ɟ[Qڱ ^-nP 4+^YO!N:fW^ZsMv-u_0́鉙ϥ~'sNf;15Ԑ)_pMlS.~o[X]8!"  ֽq ']{>pƒ\>TnՀ^Dί2ٵ,�dK1%m.vPX%XW] fn&⾋nH_z;X:n]tnx= �~<J`?rJ>֣S:>UԎ1j 0eV2* (Bڄį)XFiHy^ 2(q<ZY)m.".i ftڼe/5e C;;0BLM!,סj[:D@aX K!&MabNTҘ"of 6k^3ypHlIԈ^E+ ypզ%J,-h;ﲅC'' $Wڄ%o;4,KbVsZb:,>qma٥b7mRv Z.a⢩*|eoE ,݁B4ACףFSQ"1UkVұqTR݋*c (z4.)DM_URF~n>xN~-!+xJ؂0"a@ahZ _>yA4 C|TDĤ. ~#ԯOƇPZYN^AkCt3/ؐ^"[^JD _w!"߃�� �IDAT>S7DVG3jP1a nj�_ncy ZԢE-j? 1jj?`V ďu믢("+9]diERĻ֡էөtp3f`R2L6="3_~C?wcة^90=bBײ3Cؙpsx}tK6EZe*w#aX(ĶmdtPczw5 T֮EvKǝ�l8 nst :cqnVXo%To}~7X.Gqg'}9<'U/&Klm>/}C+?P}JMO8m VқG|j5]bË9nz"W̹xj^ڻ';w#Kgκ9VQUGjno&Lǝ}V}bvRz_ZtbMzݝ,(|Sѥ\޶건U6FJ"dukBT]ocS#]�>\D>ͧ2iJλU?n/4hΌ~/+BMUW_AٚQ1v� ypdᭇ^ yyMaIW\Mؗ\Ma*8 JG)Q{S/fg'SH`_ࣻ:2hO]7rwwYRTX}iǾQ W}bN;̹7ajlzۃ׃.T#KAXڙ2+,՚}*HL˥0-_*BueM*B�*PT@U8kX;`gam|Ɇck BRRZĶ}Zt;NlN&'Mqb4fh'lwO7Mv`G0igO)?o|سc=KX2MH$iJC(鬜9L Ĕqmfd32n-5ߗ`;:rw['-7kGÆ/yMF,ukR Vr# . (~?_MƨEP@�4SS\^0Bӯ4߀l)y'-<d)- P*".guÏlٴ_FÂCh1 c#40MB#3 Gu*+Tu6lXO4Z¡`a S<hČJt1a|ʽ$bC4ïA(@ @@4 \ZԢE-G >qUl5&?H>ggl\H{}xb/«f-9M _ŏcMa L'g~2}' rTq\v{j"f:*G9in]7߲?ɮ�_̩=ϴgђC{Py^uln{|Ai� ~}Otɶ w~޷+ZMz>[>/x7ݍ=ח?gĜ/'VTc?g=͟|g.ZZ3@# _fGәΛ3WnCkLK./|ˎ<N.nhL^u'']ɷ%3ě%tS۷w.)O[>H_26~J:b,='>y09ҍ]ru_PЦ:WbS៦sMjbb1b5 c-$4:_L'ޚD6^e!d,`lJf]"c{0_#/NjX }mmж k Z(D(駑i#nv{/L ެ/)lǺ=U=O\9Уژ>\`./t|yJ-8vfqR_ib`JL0LQ0k Ji"%Ҵb[@%FLG55Y4 #έ55MH[rH]C+R LPMPMju4-VnomֈҺhb5L9E:NJX±CHWU"44{n@|4si/))a|՗|**4˗0mA7t‘hӔU^豘V0bnܮлM *0]xY$o9ZL.ON4A2L$;_0 B_%Œ"ً*`E1P[@She )||ܺ*c=(!-R. K~.!AJ�4^4 9qkZ=SdTo~ݺCTUB,N4#3vvDC[#U HGIM0z" )%D4bW tU(ȨIPQQI؋b)p%NLC'AzZԢE-j?BÁ cSzTt>}Y&_KЄM2lT– NVs|}[W=ztD%]cXbփ`G:s&miia�Rgvs(M<ckoG?9"MF6^qPN<c+ʫ{"]{)JO?|748EGzHp *d[-M5ln++x?f"ݽZ;:;f<{6>mx{&Ⱥ9OMVϿsU|ˤAϸ:1q=4p^= &vG;`;Y<8iu8 %ƗPwgNbMWj:zCS7tyX-9̪9^Fr\nJQ$SdsřM;w' xjmXO냬M'!ϠywƯ/wW5{|m:s:0xX.R#8VYNRkwnw:y ޟ26.G)obgv}ݼڣʰA{[_YfW e#-H#al!*јz<uAѫ]/G2mu8t{ "pS_y߅kELJiMXOqIu@/XPfn5{ 'K۔pکC bbJ)+u X_MZ,rq'l[EQ61 W8P8#&hF#X~$2\Y2JkCZe+@U5TMKZ;'x9ǹr9&ݶ>ESڙ:J_x8Ldk!S(>.`4&@MS%ѰaXRML7|>`G&ڍ!/?p,4tҠ�4 pt8XT5_ӧpX#huDsr  `Ǯ("Hai~14j֊  5DGԄ!٫e/;J~do9uS4{#uj�1eݧ(+ SW|�RQ�~[wPg/T,0u4rDKCQCE%I*jZ7|S2uT^y>tj;裏ȭ۞=m- k;sBYJ"&jĖ ~5єHYX&6q+1L6I=ȴ7i>b'pgWRWRuxjz߳ X/Klۏ{U+-;9lw4jy>-:3Oشla- ޙG<6ʘ:$zɝ5o vSYYIey1?,ˍ}" O'>o$o-LWʒYPK7OFp6/y[7l<r n'�ڜykQ^^IyF?- N*S7HWѺSGοPyAGՉ7zꭠ %^:IBt;/DZ50ۊ8m4Vl-Auwn`#9nG3Yl*@l]cv6[Û 5'އ8kS^^έ(@?OғqFD3jܓS9{Fbþ<7Swe[zNκ']3؇a /?Q+)8SG>{<~[JK61u4:iBVٗH=t�>{S[}gbKvu|k5y㩚S=~xYrvns권CCF[,eXwo?thVci<7K張|'κ/sqE4챱Ew uOSgd]\;e]lWv#2f7r$6gGޥVVs*]f?!\zڭ%dӺe9 -G?eky9l[7m9=@_ e?v ɍw>͇7'z^ƕm"]1Þ[R} vm{9' eW?9;/}_Jqy9v>IcSqRwu4TyJ㌛w n8Х0ofl1OtxvH!M ö)P@|Z>OL``Z^8i-TZK9iJLðיIB5,5/�5eV#)lSbDJ$RH$MOef"PG&c]PW ٱh q YsvIS “,'CʺpBh*lrvi">]7HKKC[(00Ili~CڦӔa5]3vץ1С#iSu"㦸3 ;{~q{P޽4;m0Po߇, z:Ps!VP-gv8im uХbPT/p)y~ziC1J4jА`Ms怾jJĈ&McG6׫CzK#hBu}6ȣA@*aITQ|~"z%vJjњʘIn0D^Du@(CSV=P�>|*(.݃Jj( @+jQ)х0 {b Zs}x}vvUe R~}yƪUf͚Q~'\ ر〿عs'7n=_.Ѩ[͛lkh=m4p$*3![LW])\îgo8$CYx=Qb )Z Hlq?G6O_yrޫM,}4m=׊$hU2(hPQ`2ف>sps xz0҆}|՗1SV0qD_& .՜,c Q~!3ȜsY~19.|ɋrlWrҫigw!i屢ׅ>!>V灕�osqSB<v5].b".c2t Fxojr asH״Kׇ*�ő5rژ+hkU>Q]qq[;Y䐬SOfq^YqK7=gadnpmWw;"{ݺ*Cd)1$͹j1a]sŴ/1øZpE˝ yl|ojNA\55zO32fz`%/9=xVOx6غ?Ϝc>xǏ殆5ΙİU pin:,c~S=c~_&DJ;׼smiߗ6IΘ浩OrÕ\5/\jw<jf~G!�iohnf]Oc.zFn(�/ˋLhY>G~:i&^=/6weQNv-~˵ 0 hseO?6I/q̟ mH}Dѩq)ӜӇl=vxr hڟ,|=lN޲`�CS}l4J ! *a(H%(R)MTć@vS:*R k+B(F+fK*ai )хCRgTSTP%(CJ|R XښMk[a/Sg=@'�!k*<|5N<kuep �S&vRk~1YղT>'2aXi5I[g,ʃͷK"Ŵ]UEMlnT”K $@PJ.bV5:<ͧj*~FI)^({H ]߂pBI1)AQU[7~LN6rZMBC@S0 ]OSFU|ArB9R)VTB4c*XHeaҨA;v!1:B0|zR'7h=r/u8%eDumJ~>'/G(7J=J_FQԯߜ2EQ4_]R?j(||/WϔEPBX4iꨪ.%]GiTB*ÄÕDcQ6?^v/7w\^~e-ZDyyͷn{DU"oN&PTT@vޝ3<ݻӭ۾="}vIowp>E ;o_]%IU1NvH UA 0l2VZY" 捄-15]iHLfjZ&5RͯBNɥq|uׄV\@xs2w΄,޼ &YDW+90͹ٕ|Wf><{q|%7uvc .p %O H 1^o>[�b-L#E74{+&ByGs7;3p^fq+(]ųL0JVoiVVQ?X3Y΃WJ<}3_vBIN�o2naFzsp)]șhtƝ%k3lfWf?6ReZc jg|cNC[_\{:9x^)}6؎~n7ARu~z⸞.G34hؿp J)'b֒-L=4}YżuKx檁i f꼟xnIW% ޻Gqw~{oUuL3x@$P0H4(K"7hj%8g71}nwٓVo|묽Gurm%iQ-"@ �3yuOwWս7ܪ׼i}|_ek<6-HJNۯ0?_/e>G)>C§SG~<l0(ٷL{/Xŏlb;?{|7w?=HJңӅoߞ ~z-/W?{ [{\:Os~ӟS?o7?N/^}__]gˮy6NO3}=G><ߟ|;}?r+'ݕ-'}>3|~]�� �IDAT\S~~U ##OuFwvs'яoPzqQ ߬7_x<gL~׹-:HI[`ϵ۞?'{wo̯G srs.ox!PN|k!ІBh N<}16s R6-3|+#zB!T-,DB DG^LEhQ<W'3, xրe*GDnoQ'$22cu[ݮ=lz?nwDٳO < HmR  HRaAX`ENvt=4pi,Wkv ++.;!mݮ�Q~Jd ->1 <Rht$`w#BVcz֦SN.(uah1ǎJɷ#2_J@ Upx7h6 #ͭ[9xWQr1 n/4 ^y°P'R|mVjCO<š,- "yk'bS}I^eVgVciA=l& [(Q(#8xC14<83uZ* 7ko6aFʓC>RAP*Pr2 ZcbԆ( hcON… <�36֒ZZZbqqbٳg9~|y@fggg?O~-77i4<3<u-}/<*}"퇙zꩽ=Y$>{ǯi !=>p۹v'pn?Xk fZѩ3BGy ^״H^.V L`ttQX\ZArM[lZ!yf[_G\gaazyLNYig*uV%"|F'&(/ZT.PYEIOlwc,faVw^iwf[\ĥQzdrO0JJ%OOUQq}[ u8;Ptz:wJ=<JQ׫gw[G%~C+Of^/ST]Ae%JFLj'876v׹,c<ZrmnQS#V>LmyϾ[Ew+faպAiu:j2#e&:~ rkiy~' sUskvokxN?+k׉u~g_Z]sSC;Յ\#ɉ]:syL37?#;?}_@C<�{>|kӜ:}dBw̽1G &sxmI\_ ^Q_ v#a HT* @*R"C$t2BJ gX@A)]tbGX#mIs<<%GhaER"Yc-M)ƒ( cI,$qvN!NOU˙B͟GtMvHAZ+Z !!DIx~${ ]en:΃YJK"71 Ҝ%T (,VxJa0Ԕ@P&hMMR֞9Xܨ;()[+Һkh&Gw&]ʤ_R'ё1 Vl6{t_cNF/\JI<}|Ϭ+P>c<υ !!nI|_"vѱebb/JP]\2Hͅ:šqB[^|_be\tX^X"n~sCh1CaTbjpyD[71ɀi?ƑblCFM •U" !՛z WE( L'MF+)%VYq=4}⎓8uT}*}կ~EΝ#fff^>qD[Th4;zÇq[\`^ pnÝ-ϡiGtM;"v!BjJ)boE*4D~ Vv}i7^ޘm>ޢ!s+11]]K<A%{LLK.A/MLsk@ ^iåɸ@y&%{y^uJ=bc|-' ZJJ{G]cbȦyypϱMkkqkK_>d,@Su;˝CG?mSX/>w+~ P<]|kD{eJL1=x V{�ab j�=Kdc<4`#C- i*qj'!qҴ ѓ"tfZJh}J$v q%J0'+|eL-"#*HDBb,Bm雔d[Zmsm62YѲ�H̺ RL+W>z#_:gNN,Ou/qtH#Th*aXK]VY|m3i{58|5uᮒ}bCl?7n26/8'z0%cܷ7x|gYуXG4BI< 4X O?TGW-ORm,s0aI3<$YkL+6oy5\9jʾQir3­,UHPRbQ:r-BT2<ZCLN16YbbjX7*yLF9BKn,r{:P.1:QX#6hl044&Dq쾨J1L>Ǐ333>,O敝ޗe͛-ceC3o+W6}^h6f'ZfrA̍[T7aDZ׆aؕCw/]ld|<rAy[꯭O7ێ{uYtݫ۹m"DhgNN٤1yQ#89(k ssaJy13]e*5h6(״ze|``mlf]`Kɿ k\:VnG׿ˏ'd620 Δ< Clh!Xچ0D |G$hA )J%v4 $Y-NNUH1qgqL3lȾ(d ^Sa1`,c$.Oh|,�Id eb _^8BW)%OCzCFڕgtr:[?xY `P&E>g| fWhc] 4: )}.D~Zs6OާNRST a>TU! xQY֗]XaĖPD͘Q f@J̱u+aRؐAYpIn7�&.Ќ%/_brd1X 1- nWf-ypQZk%`ls1 .7$ C)OS[Q]E�JQj֫a( (Y"ިrqyh&LfwOi5O?4;CѠhpҥRj^z8y{zJe6z)n߾ͅ "[yǺ"t|M*WW|&&k +an-Sr}�-ϙ|IΝ;v{]p/vKB|iR_r[)Μ9ã>F\p!իگZߺB* }}}<'x'c[|kI%(^ʋ/S&Gْ˗xbHr_pԩ2/.\/7|oNÐ|+?LSV!/2/Rױa=^JˮV}￞d^mwy4 ]M3H >w<b}8_AZd>H M-A q#T* ,-/uhc*F)e:i>0:Z cm&ػk7�l{IIe;#}&&տ|w>~EOљ?,ޤO[biq~-L3<tx^5f/A@,)JHSqHR%aƠAc1Cs4o H#e5kpѰRg;J؅͈ֆ& VuaC"K٣,hc1B+6DX<!)jq<4~")QszDn;2\^;х t^W#O;G+j\q[}haMJoEvtuR`tŕׯ`ZssF(]Bc3@k7ҵ= n2Lnk6;f֚`V$[&O4A:ԩѝx)BS'Ϳ8)1KEBP}Z 8Γտ\ XZTgu5QFF=knFM\6~F(T"=xLC0:>FqT҈"Z5b"^!`tF0:>NaxzMWXf~BZgXDh֖ʗ4Ð8(> 2 ,i=DzB&|iغ(C�0DX́Op/jA#dkEVT*{;wyΞ=^[\\<O=TF$TՌow[ARڼwF[Tr/ FpKPXܺu~:#.z'3W^Z>ONTn͠ߜo4<󬬬l=Js= }*g֔y&΋^x' O>ia'~fשaYWn{JجUǭZ}wyyfEv'3_SG\h4iޕ)d]R<&M%Hk1I=ۃq:öL [km'@rwJ݈i]J~!Ϝ7ڌ侽wwj*x-m� ~�³~Ϝ.pQ}hqoO|3 pg,_:'_'{BDfk4BL S4\+|[7)5“؂ t!EZ!3IJI*" EZhB橤RJ&Fxqnj-dHTYK(,bAvLŠ+0:0%l?Ϯ.=SQ^mo뛒r޹ߒXReTYTN"MKW#D.M(TaniHaSKwfz*m9XGdGchn%AJKhM[:w&=s۵c\$ZI}{Ye.Y~iv ! :"By E @Ŕ Xc%>z+xCC@Xa|B0()AI266B50&7o/ppjaL9fS3?K5 KTW* 3oE }ef%\Ahm2 Ҁb(:Br)@Un1<|�/(0R:i)HE!F{מfϛI&aض ~w333CZʕ+qyFFF֍{9vrJ—A3�E\;v)^�zǙYOtrr2+_/'>3g8|0a+O jf$ug4h| _933÷399իW?S7o… Cv{챬܍3'O=Ij?slXo|355՗$lCq1&''_/^GW\iLNN!7n,//s̙̋ .dF8q0 9|v^СC=euw?u*6<8qpd /#9ZǭEXٳٽ/]<'UK/qq˵Nb͜9\4l:6̽[g`ڎ[$p*e##S]@Ih+cZIK k5w#^r57c wK=`G}70%ڲmr^caד7a8<1|E~'Oo9+˯_5~ D0^rv"a rvV1Vb}W1"`_ѧ sQVYD A�P" MT0! k1l4d6I3ʂ@"hLf[E֑g$(#tuBMglǴ$k 9-פѬkgl2g8bMy=l" RܱmV.9�X^{np1 @b$`@#ё*+%$$f.ru]$) w9I_TO{cc  do'>'[[YO0󔕏_(tG11@tp]prZuHGYAXgbd?+ P1Byzk|[ٯJQW<0ׯȱ4t9j&J CT /cc90ADmh:f=$>j  l6-: *0"$An1&Fa) h( ɾ1,:j&.]6ۢrFj$$O?42AN$gMMMm*abګo7gΜi;\.ceQhi=SmR333|Έ<ib|)F]|}ybH`̽>w}V.y'x7h4]׮]?cw>fffznf^~mNgϞeyyWh4rʚݾJtb380/yٷo{&H>lgϞmӧOsڵ~m;gϞ֭[Y+J\˵Na|ZZO90+p6~E0&/sU91jWR@ i"mbg;;![5 0�lzU|7Y4yΒ"X7W mm�>?nUV^ɉ K-{M J3|'~ϲ8wz:42D܀`u *GqXkVjU_8G&9U>Vm36OJ`%4),JXD6&s VJ9ʴj\|02LAVXi_`]D'F W嵕X+p!&V.wtmwLNcY3[O\>ݻGfv &0݊BtWׂJv7r;Y/Vܫ#")<Q dX7XDXb@%28b8JS$1k:`'^Ъz$ck7ke Hu (O1wSo4cqu&n2\9ua!H#BI<-2T<4/b&#HSa.`nEq*J2Ⱳ*\cRbȠ G)E^k@qH }9]�� �IDAT@ZE�EP.-P,)E$W;WlT16v,6Ca*fL9^7ըV<gDӧ\lrbO"&%RT*-}7}�DkyuddxP///k<ȁ�'ݺVSNKW_}5{ɞDnkfqԩI#'gYm"_:;ٶ^?57RdBc+TRis*ud۸o[X{_^|ff>;ܩߍ?c<rLPhcM`̿=>넵lN'/,:̈́4/K˻8m繨Xc](2<G׎xa)Atr� vVG8rS=ڿGȡ 2qONH:<F>G!eA)�IaW^߼j dFz 09Ƹ+%BI0XMj8 cj!:)FjP,jZ q!e&TwypxV2҈) .Q G$wdY훟uiuR׉]o & Qܬm}-"4=7}yJ)YtaHbSb F %ԕ OД" ܓ(mdbO44Ii%=.lhL]Y<ݵ)wv_=8X5(Bb _66z8dbDQDB#㌔$M, 0 OU7̍X!ef7ꌍM2\zD6&f Qn/`Q~qHXZ'BP<a R(M5B ` &p,yD>rp=RG>ђ⡇Bʓ!kaȩNKDCBa]RbeeeGfff(4 .]{,ҺRdnmNv2yYΈRay|Ο?ϛoɩS8qĶe[߿yw4Wzn<NCfjj-R|J0669yrvm/<$a:|۷~cW(ЉdHeq`gz21I|u]9ᶨPJǚ84L,֕UR2w,dB`i`M6nDxR:38Q 0b{" 5][9l im (zҥLJqc~\TUD$%X<\Vh/%V ښDV"9UylicȄH%0)68 =E[ܪBdJMA9tz!U_:/:s!,Fx~Mr )#ᨳ:kY*"̌uHZ® Ҩ_׊rM dIQdL)1vu[ڋM  k'#@2 35Jl6n"1EVRTYGVo.Yֻ=Iyr&i0)726/;2gں`YBVc+ TB"#nH]ؿ�#t"Q!7btt̾IAܘGO\_D`tlqcMWF%+ii0`92s ED#^īH@8@I0:Ɠ q$J)PhAMź@H<Y (JPcxrw_]һW>�8tPcyB… ;vm<od"6Rεl6=g3Ǐg ҴW\G*c+vkf+Wv!.]_T>҃Or9E?<8q~xG|~XK.x}cԝۭnwbߋmw"R;sh�hmk@"Ub3*E3aS>2uM䍭|d59O&-d- )tƣu9d]}Tz$fQR M{ɪWY9I*KbZ xeg7: \sUe�2*t?4�A H`vH 0=Al BBl;EӤCPU%E%j!R"5$4q.Ҳ]Ib48['R+։pBvʼnLrѺ%!\rl(%MmGD)yk˪BR4H 4|mIj"1[H"u"b$wn^(L؊'uHs ^'>|[ T 6ikR*H@y98Es{$M(ѾIIj@)!Fcn q$6 )}<!16%S^?ٲ}|(۪Pf,#ӔO6+EhӕE'<X 16V$%ض*YBAF?@z yjta-x%I73|:SE[ Ce@P2,4V,21>)h4 biysvJ_T,Q*Jb/7j  ʅ VVn7jb23\,5(aBIE Bu"h k"*F5)1R*S(z}A":vPSݎarr2l4<s?~.^YbqOw'O||~cǎE6;YV|sk :>>A6G:qq:|rFX^tK.q =nퟝz1YܭkZ{y265R26"\^^VyXk5qO,]flJ]%?k ' xi@H5D*#d&cOPHrXs�gD\Z0I%=Vml4GJV-k�o{od v@0b>� Ђe¸HEGZ05hPY" %DXiр2pIcXgK8EM!sF`:VKƂh !k B* R1R7 U%iJ@kQr%X�b J<\ ](Fԙ9{K[Nm'h4dVy hw?++[;I4lcKcT-1_ IA d#=]XFhbRcbh`4kݼҦE^x@Ҋup9&2WtD1~LFƚqr .1IJUiíoXls YϰTE4|(4 b+7òh9 | l6LPSsOxCx&fyeJc( ')CCeMn3<Zdhx[W_g%^ #u(|84 >DxA,a�YذPk:n0:yw}dFi甼I|ɌXj4]dz>I8͛7Y\\R333;5Q䉺;HåK6E>w}ڵ5 rٳg9{,/__jvK( =uǒgn;FgnԽ^;{kp^ZK|b .7l9%aEBčh}dZ<nkWHђS. fqyp4Dfٔ8NJs𿑇o-Ӑ$b$퀜`h`�0B(!A( + QBDJ %VR+R!D"gגYdl?9o)\8PFJ"QB Z)hHd}VerV4, ./ܢiRxNV* M+,뜞EKZPJTA%D61(svΦoԒ7XH*eh]:fgdM\fCf^=cT*q{6(D&{(G%ߤ71$:) "0g>AƫsR(D+6囼 Ӷ+kXUt<uzm+lBZ!"lr#ӿw;=O 0r?ʿC98Xp(hl=.V1xmO]{4Z&2q$*7r%XX&+V+X\]bjlQ/+ }K,Qr5ZCa|oKHQC O0Q%r #bX@"�ה0fD3\eeѣ*J)kDCP؅tT,xÇ||<#֪nԩSY]rM>:No[֒N!]?{lOw3m<~8Ǐ… <,!$w`/c-Vp/}̩!hZ#},2R&9I\Dm[nr= *; n(Jlo.lrm'Hy}Tj5̂v/=`M ºw;}㠫].-: `?:wF fL`V@iĠ1iR!$&KLT,҇8`)@gsjm^rũs㜋v8T$ 5f/hD&}kEXTJU6Yt <V!cIr"E"52E, `4V`%`-X.$8j>5v);hr M[a߾}j5Q6ѶQ򦩒KiHGxZda#65.m!((~Bd\Щ]Q)-@R2$P<'n)l3G4VIX:Z6 J,X/:6ibk)wn= U>D#EXH^ X3R{AG:B*JB8"ZL)(c8C^SCeNR_j4qh4ju1 !ImeUcLLatd0Ux!B #ij ?x`,`X@(%bTDI3ew0m J{.{||Ǐ+ҳjʥKw2F9t+q|ӧOg#G[ύ` bW_}˗/.jqffo\YYyիW7т{~>;;sXtH2|i8yE~,.]DZtv;?A>oїx}WwN{em]6ϥN ;EqSnX H\.D^O܋>#hieH˄A'{yJ)/3rE!pYy(=޽"Av=�pg?zS|FG�|2=j#n`iFF.OB8 YW'+ُ)z}瑵Dkk6cz ` c"V Fdc[I,b#F?Ƣ#ؖAqS|kƳ&۵y6ȑq~+u0# "I,e{%I77.%cfooXējim' .4^M"  :^o|?M=98=s>GV" 2usQ-I46;m46a Ҵi-9:ww>"9ձN+;|^n9[&4qBKt<)� "<,eO<=! xR"s7Ym9p0o^JڠJ?5ai\C>?4&^?TdLǬ,/R[]X4-24x RM>!(#(G )}$,RP)b !JBѱяw|bog V\p{Ї>u3gi<0_b^IewwW\avv x;ۚGRR͛7< Ȉl?~vw߾}mwֽR /l=AٟYV$~;0*/bgggꑢRdk߼(mDŞlޑ(v1998H48^xaOwj} -knEIP+53qRzHܿ1Z#Hur.6-[8634<"qlu2Ia M"ѺwNzc]I$+uct+*Vi\#:1EcZgw/y]x5.|mW˃�(o�kBkK | AO66$MYg<*a *K�5Be@ .ζ T$r2wJ!#<4  LONp!')x iHR1x q$|Jb))Fi4aQX<k.m,19pH}]f8 OZjȢpsaI R !$R)IP/`͍szmZ5}~@3lR5.:55gJ*RߍuRB +4DR` i1zsWߤ6mi]jM319}w E IA1F%B&q6ȈVc\TI}`3CsHsRH{Yƪ yb,#8tHWo})F@'ZZ"<6 #e.?y5ZcӇciB1fV',.GXSɘfZXSFFB 5B q(2C !BZ"u<hF)d"!EHlcБ[PO,<|O2z1|\xM3KGXhr{^^z%�Ν;ŋyYYYm]lM;Qp.]lr}q5xh߭N<Et"vR9sG}4#9<V/CܺuEfffxꩧK?s>}fr׹ɓ]oJߔ Kɮ{Ǐ322|[d^`lŵk׸6yyΝ;L&MymOy/[[n޼oos Ð<̎c;>g?YGn⡇\d^a/q7mSBJ4Pv}:`!Hk[$279oP԰M][ltvFLrZ:=۴r0z^-ڬv1o|,E0v8GxWpk1z(el}f,mD֨ L{6[= Vx <5~V˷R[uȄrdc”X.llbMI X,J(mIc(Ŝ kfm*O9hFTaAIn^l`B[K&QX NVX836!E4rXH V  TSFI%[[ I_m 5뻴sZ:=MG.nj4._^eUu)$‘֠BI\˖ߙ.բEIHkDɦe B Q)XM&ݛV!lyrn1(P.4f) }X5xH$al>7 H\;ۻ6?6MCq-hk֗\�Ȓ6%8$j*##<7ӌܘ7"a#AcARœ%RPQ a*K?1/g�� �IDAT4)41%Z[IP((i6xbe& zx%B cmf i 0Ǿ#,..%"<'NG!yf8Їht 333 s}g.NAZm#i6<m2bqq1{{챌Pj4&''H5I>,ht㌍QT8mKg{RG{y>}k׮iyO^w:K133ã>FE\(_ҥKw ߩ*<ȓO>ɹs߽3gt;{emi{$G>F?V)E|v~(N~99gE兵N9m!$h@78gw(љsj"{Y?{'EwUf"I$e9%(9܉30(ޙ)pI3xz*(*(HR²ivGu&@s?V>OhL/{QACk>˦ ;r4�k翽K#|1l>{L_0y;ðZY!b1y|M9t�&v.:p�wʔ/|Q{g>èG )7exQg1dB`dbظz%6HiЍbooRDZeѩ V#ND"Ծ<KmCxD1%CYik)gdRMZBJ隟ERҐB6q Is&2. e)\s-/ApZ !4q  [t5;WCU;ZHܡMSGHmkMϹ^(砱j <mMʪJ1/1"w aj瑛7Ҷ\c�ep^Ji<<}ct m6kgWEK4-,LWaiIѷz$d~BhҚZSԳ7;c|l)2b5F Bzf -u ~ }Z"k?X6aeW YkP9T'j,;h4.6h�oWHc /Z~^>8YXZUqw_׍ j*(G]vvD ʊyHÑqAv$AX.Xq KZƒDD.Vdo?ZQXXaV,Zn]g>t׏]_͚5Vo@Z޽{@6<cXr%6l25 A=jC<W_ ":t萒#m۶Š+8RH=dOM6dڮ];:,VXAqqq۝:u H<ӿZbX[c!++BzEIIImii( i׮+Wd֭A(Lغd4XիYYY<[0cC Z?yeСCugOϞZzIkpY>"W7iw+k<51eׅLygC'MyǦ(e!9s(Zk8tZ)c܋0j+Rs6/&D0+M$|E+Tqz'tg~iX]}�3yګ�R%E߿uW4@Kv'YkAGt9w(IM AF55�%|܃h*>VØSg"&r%^8A Ѯ@mpciڇ<_^u tkwjӣqӣd6=;qܨv-Y z<y"??v0-*nСFY%4~dSshT;+j+ B^]1F @Jx12SKA(GaeYh+N꼖V8XގBKBV{U eHYW@wu8'HK#P1$ڨTUW ]%Jܧ&PxjvC`S&mr뛀TF@I*Mkf@y'Hܛh$4)U.;d%k ńSNF ¯‹ĪHk%)Bb6)$ Lxcmyto8YM,K,tage_|J!6ցǭkԾA) 퉑IimcPm�Z9a!uD"m%K;TWTog1z`~!JA"dS]UjX2WW \7#b e5uBv6eʈXT6.Evv-mU.88.HE,[UW,l[ )u&ڵNBӯ_F^gϞ}}C+}{|7)da2ҥ ;w KJJj<]3???D]Z*ܵkW{*u;>+[W}A8ﮠ1bOؕٓT}cxWv'dlL !I ֶ=җCQlۦE$ op5reBA! )*dql*:_ǫ-pu+b8$F !^n-'׭RFX*I6l8"Aa/䄊WE9Bj|?e5JQ^ {?XG~�mל3)#srFv|,|p 33;fu{hX~#፩8,'\rA)߯tU__o6?.\n>k(~&o^wxт:1{W̞Usl6w<z˃l:n`ؾq˷цTTGk%@*p10zlCʀ<M5iaH_[HBZi۸*BY6ppH"BJ9~{KJ-Z2c?,Z"JJtCB&Ah$ 4q\ D&%TW{z}8óMJ~)">AR>k]݌6a jr7 aHIkRzlmb)$=V"CcM1}6ofeiBuklw;(I˜}JϵL%^ !I"]S0l/+)޴neǎJ$"|~U-V 2GZPZV*)$ \ 4YYC^4Bm#QqmeflݸDyK$+X!bжuZ6Ln^; %%8Y89͉nCv؅miۖv-,H.YfH -i>l h'cÅԚo5S}+W .]|Є&4ᇈP//~f t#|}m Ncxj mc4~?Sd4iORx\8$0yrmY2Y^ KH,xxufˡ0D>Cֆnk0tsNyv^3oPcp঩󃄙9oо?+39[ð3nc-LMW-gɒ,_K>OOySh;Y]Lק3>-]h ;屗Y;Jؼk,xw^82]njӘ{!hٹ#'?ZMɎ j*wl~}yam cj2-[^ѩξ7.7c_}bqkľ. aRhGiL~LeiZ+Ghjҫ ڋƄQTfÐYF41H-qH#UXģٯ_)I-.Xʔsm$ wQpl%\[)$Z JWeGH0O"("Yua[R a[1vZ WT"LVxF>)F϶<Yx%Ҳy<L}۔Rgx@EJd(b׬,VH!Cnok`BXww,  )A-ZjB(-q(͟J!c 54㱥(`KV.^GK>cgNZTY;x侍v{0+3^?Dw?YKc?oȒ`nƺbT"P[aYWЂC;bYUDvغ-FeUͳpITJrs $$R;hצ=غh4JAXv%‰QUYFYyAA| D ",D#ɉUV*CBW#s[lx{ ?&,_VZe it 7fvvw ;,^88޽^ MhB]gN8[BemHnL06%j ֣<0~9߲BBAy|e,SVXڨ /c2,@Fiw˖(L$! J*~;,ק;(m!>4&Ґc <8;W !j~c2Zn:ӾeM=Ǒ} ~`L4dh'L> %<;|l~?|;NtݏѧO9rO5kJߛPq{Cb Ͼk>6˭%H>L68Σea{z<M2,w>7?,gC8_ vv >~ ˟@�`[kJuE{ٟ ݻoh~y\ap>Ieb7k[  Ac_$ 7P XV6" !LlZ}r&Itzj ?DܣȈyƓQk&FȳK#`IJc&=&ƥU PBDJC@HJY߯]uCXf7u]\ZϱBaOkAll!E2)%~6T0w>o<By0zxg~6i_9]縷Yk !1Z*\B4R(v*m*k@ 7WBIGV  +pD-˄`VB]~+Nfd ~:~L{"}\? Wq/OU r((EGesqbN-qA,_Jg;RJ"Y(JP-vq</DYq=rrr\ҝt%;Kؾc'ݯ<AU(ݶ N[`E"dy-DDعP2stv77G]0alBN[neڵb}%ʕ+&t@.]MhBtTuq]{xOMϣZ߫q4  O^#?>)|K$M2NEx֠ӤþlE!ʷM*`Bi̮"3Wdb /۪rɁѷ`H9U[-c٪uTWVRI.ЧshYĉ{۴x}M%UTњaIhNW׀.ٱ|o}h^wF{0glgK%]=C:z^[cWzҾ[O b}h9ͦ%o_拍uFxJ1>~ic RYYIn9ߡP~1.?Z5Qm(^='[Sv>|~Ґ a,58|a<Xt0D疩rTeG dr6都ܖ|=958żTPMnTay !Cڂ8'xhOmrn)hpWysާl޴J O8ٻuۤ 9/>K.rn?98-v!Rsܖ9 0>[ñ?dzNe|﹬^GHQuzo*KCFrqc)][XAeY~rرvp3]M|leqCvu>7\UѪƞ}4p̈́MK2!kxY>LP4:- O\s>:%,|}o^]-]ݢFʬUvz^w ] w~}yt3$_w- T ~zqÎ5 x7Yr Maxiȣ2mnUm`΋Gˁlk#9#lڏxۂ<> 6I$:BcG>}2z~on]*ZX5.6<r ^VY=2owxŗYIC Zu3b]EUWURUG۶g趩- OӴ>'/y?s VU(v?Z~3"zֶi~6E>B>U[kmUU%9-vpޘruZyzTFwqeÑu0`0`]ɂZјj=޹FOauW )fPJ׼[l!+~Zxz=}\ou=zWJn7kۍ /+mCs~߽q٨K8 W RmK-O?<Dz,,6dCR(9MI]W!v tQ!Ec[Gsha !P$XO^ <T&=-&͢UVtڿ Rmlݴa .qL_ 4T(3p&Z#,h idW6^z% !L_!Gg7d]ǒ8aw*0QLJ`  lL/G8ؖ@#ІmWk 6/OLQ=wbs5bq{r ,@JPDYSJpض=P Fqmdi]iR:WQ�V4NY"6~02 "YT\ʫ-*vR]ǪPk^Yp$B ƍhTBZ.Ynke|a-hݪ2JJwҪu!o7nd3L?ABAm%%#pp;*Kh޲y]N%нݵM!??ѣG3|bAtdgg3p]Gf͚kG->|^ MhB~@Pe?PB|LU&Ca!ULdQ9-|٥'تZWa2*5lOSsڄr"W(*nH =ЍLnL{SvO2~Fa C휇9hcLyS8ワ} cZ}oƟso+j_kLksg\0yo2,wr=9@;lʡމbv\8cE-:yϦ[W(=,&{k>oϿ۞H/0_S;~M<G]Sqir5z;S.xQxWJk6Eoxk+|<w5SE7"�73̺y42GO%?SrfӆK憿{cG39[ \;V~f}'p.m/Cexӹr㥕L9kVeB%q؃2 oC]ˣUO~𴫠M|<Eaʥı.CK9bvk?n.g,/yc;eڥ.kqft�� �IDAT8aRz4f>n_ĵG qS` O_:eZ5b l[YGcYza2y (l˖D/Z^0L\C~KF&9׬ǶҔ9B';9$]'g>ZJxa꩜qyVn<8޶Ot͗o_Zc QC7 5{\{^&|;aN7O(#3.Ŀ�p1NM#yZ=7,4g:']Ok[҇i;0OpBwrаg:f=t nwzPcCZ!E;?ev(j_?LہS䞐&w1if '2_ }ooK[kpI17Ō鵔K=-|*Kv}a?ܭ}:Jz^6=>ЌžzFΜ8wK`ho<~h&̨k=wK ]rf.mm{_WU5.=>vU})!N+ᅨd2t0`tCW c6ެE==BS0H ["d{)N"aJ'}}.Xڄrku^`ތ 呱PFT) lۂ 7o,ۢ\rmIҸD,(ľm1ywEgu^[U|+q]Ʋiցrz刺هs"s3#9v0AZ&.TD@l)q ,!HXi35y $<l+[gTLѬHd $niuUa` #\t˲X+b1\I0FɄLD|C2?BG.Ӫ=aDQ<U1dDWQR\6 ȉDv[bUeNv 8l/S4Ϸ)(ȢŨjG̭d2ܸK۴)z`gEI2;"mފTb+pTV$ch_D'iYoBKٓ.]b عsg;RPP@33[رc^zS26 Mh¾ +$B$7lOuD K{Fu!@eK ȅ q꘩W"*M%K<Jʚ eB+k;C_,U{ݡNl{nڍ%Xn?l\+3b pGE4~tŊ7'Dӽa9}[( ,ew2˃KG,7&,+CKeP~ú'aE;;Yrጕ;[O~-_~3 {tv;"[ j>v7�q%}>q548&e36x­΍p׌y�;?[^c㺥\\~C檴g;X=:32? 2ݨ_i`<d_?8^4-{rgy$9=I;nƋ�w2{ \Rۺ'fyםg뜉Lẇ 7J!@%P7u.q˼gN1 X6%s~/8`/^|`Jv|>)m9C5c6�7̔ {W2^s cLL휼* k}r ='Xv'gy$K8"y#>q]#[^|;$c,(c-|' tlygbq 3ꚏ\ݰ4uN}B*'汹pe먠uzd&"cz{?Mqx=3 )E"cϼu&:3|/x, xiP0fSktw^zx3xnKt$,ĬMߟRݴ.+<Pū׍6;-G(j )_sy9ʹ7 sCtXNV\~ĕAFN7-no^ַLٚ"b__x]-l\gkvʺ>I'KNQVy5{޳cap\z׵\MI[AΌqsl:fz=f<5YLėAVy ӽwyږݬ'@׳>{)p:>\ni|y7M0uu)g`pʹ㋗'s@=Gno* kCh=kғf/'Ywɒ߆ZɵvҠ.:Ic&KpWЄ$<fH_ #զA׆+voH {WL=iX3 {k%c,L-ZۮJ`mɶƍFcV KFqqBO*BJ"jh7 X J),)oXH[}~B6{t%bGOL$(V2=O r!{ey H]hiړ9R(,iaiR(A[k2- -(+ہN$LSV"ZSBe#7#Q5FO&Xr/gP.ZaY͛5I$UW& w@k$ H`yn>B ̞F*7/DHHn0~ Z{=-<\m`> b}r֌e%7\>mK~^>7l4&L1tmۿ.#$.BUnC(2Vfa ӹ8tv|W|mېHT͔UVR,*j&p)lY@$Kw*+b _^ku=HbRrsrhټ% WQxU;;B~mڭ;mڶ w;n ) QD)ټ:Nu<F$NM}MhBЄ&4GG`GL@hC顅)֔ū YWBaI҂'ceA0!Y.˜+| W-Ha7\7,M@}Fa|6?H_.|d._tX([\KIFD-G^ѧ>Y�Θ)??p?dڸ!a;\s< ˟^C6K˂Қ��E"O'>!/N�lgƘ{XtIaE|˰I/u/HS?#o.̥Oz|iwW{ܤ|<ynG_]̒أMa`_9OaJ,O~3f|Ɠ0c9^VsM?魲p3i,(gWr'4偋˿> ]ѳ<e;69[vGz}r.> \ċS>wd�ޗʧ]yu_z<pvz=ϔpn0q@ߛXTi|Kx7<6pkj.�jxtuw4o7ѡ+?DS桓R |[zHGi C]o7k9|\ "/YdSn#UՍ:o9>IV|kٛ>мxFqxmzx>ndƄ.;>~Y `=,{'ǯeXIBkD2m ]L%8"yΰ}> 3`o"F|,ym.ŀcՕ\Ū})JTz]\Q*a,?eȁ:2OmA{;xʟ25?,@3.(d7GsѷxF|3W8ytr~OC˙]ʹᅷ#/PxL[>r6#~s O\@/".h6''4,<>5f?1g^c�:ܴ׍a; Q3O9^O] Ee\ܿ3_vVh <=eyc<.!c"Ü?;k 'lChGk3Mivѣǡ*f<`X)_?N20=׽t9 qƝGp43eH/yAi8ۙSstݬZ۾ ^_9rLv<} EϨqK㣁pB&$oqp<bIH d^(Ϡ׏dYթipP 4:V^&Oݯ6o!yQD`` lҨSSsxj"ȵ�%]d3XZ ۖ7b bn X%], '*@KC$KHH�\8^hD{\C>{fܞ>orF"QTO>YW_QϳQ&uY:a=LȚ}3Iw Z!#4J^AQ$[%+'-[KBp]qX+oD2c' : c}/n`h/ A}-J{IHۦu6D-Mu':g~(O"ՈX~6ȴO~Zk^8 l6ƽ?۶RbYVKJrj3Q m7:"5H \\]mkl)q\ \עN411}<tŎT.U*88X\ÇqЁnFhK lqCeW%kGaִݏ#&PtMdlЄ&4 M+:J/лCTl|A ޿Wx֟e|TՐ?N7ԆLC`JjGq-b�!,ʧo%*{ S{C  Ca2 ].rN>WœJkZ_q.Ғ7Q^7fNgA3xt3##}I3waFE<&c)%-Wf?ys  iU,yGOn?Nd,g4cݿ-BйO* bϙtֈT2|x_j*2k('fGfIyxp Wg+}GGĨ?n&VWQ�i)d,@~ üeǒRy3f<S#wWm~:[GJ!Bfw42qřSXևsCL{N)so9=liοM-L{nrUxF0w{/xq]_|2iamw?Ϟ][%̪S*L u!QwX>!,}^^>V!^iG$3_s s. �vG{ n:p폲<h=Ln>/jmʹ҅/xd,ǧRXvï[3ØxU&c c_\|ۙ0 fj01>'.,mI>MߧgaђOǧ]$gn>H:6䥅qj4UZJAd@LݱA9u&Kc2 [мy Zʈ{?dl*M#; X1ոrF<kǺۆ6}5QrG_�rXhv*AĒdE#T}SGJ9ОNHŅϜj-5X pQ BÖo7a:T<a<K$I\aX3HLCBCz5(܈ϥA9~d Eȱrِpq)hcqe)ejm!DuRP`rcIqq۶A@"qxGQZ>5Ҟ_ʰǡR E8!%ıFGƻWʢZY<P [uM.٨E+p͞GBjS@<mH]=[CĨ2F\c&#Flڴ{}`w ? }_WI Yu)tdz mw Tǫ)ʲTUQpϢY3aY˩ɡU+r˥O~9 hN<@'p]/D(B[XZ(>4~6{njBЄ&4 ?RaTjKl&5S.H&s w4*y/b3D c=(O;Bs($BX c9l!\G(PIX+g1ǾT_b7M[/Zy4$rj"%g�+:'tB+jޙkT8ږ mI#&0Cr?9iBm6K53q.pF" ~꙾oS|yx{}5wj{IO: ֈK!s6;egoyczfcg~Q'##Iz |sj/ >ZpҹK>0 7{D:,B bkRcQ&pL }.?a97Qsa.ʔp|])K>?%C<;wa9kvlב">cH-RugWXeDl t:Ŝq옞djSƧ睫\\s3,mJbc"3ødscT6}!͝x'A7F;V3Ri4Nكqwz,j(G̿|Wr=wktZ ~y*U_ qԩ2Zt ;۟ Jm _Z.X>g[צ:[�0CkY[Secdk|4Dӟ0!|KV4B|rrrLQe<,UxR)O42~%$? 2$}.=hm<5دK)l!XIWEgꑞᰁuկӞϫRy2D2j *GhRZx@*T kruM"G)%pEGTqU52ƁѬ|sZ"RyU鉌R ѸPd/ٸa=J'Z!LU4:ȥBaLe|d^_Hne�$ci aY8H\G#\ X: - P"% HP]0~Z4f\!4{v&9RCc#`v<ޱo`B՞!ϮI[] ]>~t9dBN?=Wq$>%$E lC$G4?D"#dG[` Ivv.Z$ ܂<K$P!,A$'M xqJ$#vN{dVnBЄ&4 MT g! [e<9[j)-k KTx݆_{d;EHB+S3BF%/Jxf -P D(wȊJ\GPY-OQ56r*ҐFPŗ!)ҽm#l>,]s|Q^n<Gwncnepv-}s}ЬX Ō8:1fy%#1fݩ�Pb/ (ׂZղ% Β }6qj툱C45f<.e|e �;JK);QԅGϓm'OY]34dg-tx!5NFK;ɧ֙=+w᢯<{ŋ髁9\\r4�� �IDAT(>z c;~jr%7s8f%+I*έڈ 9ja9k|bn5suޡ͗ױ]3{gKK۸S311'X;5<i,;Fw,{7ClOKСU9Q; ;ܺNTD.RCvnL*XK13u)'aN}>!pt̙R  fJ X?5ufG|,:jkێ=նSwΌq][6O?|s6Y.g3.kA%,p96o`ǎRJ[ϕhxvL(`+_0bC$!QUKܟɹ0o捌y#+4 oyttyݻqθ_L=[o/_ȉrə0fANwOep]_[wg|4NE[bmؾߢ9R8n„<G2kVBʣarn ~%iY{Of7B {w*tH[h+hMRH(O ъX%Vbڵl+Dcj%Ƥ J9*%AڀMvN9eQt@WZbS)JW!eGk\5x�q/@+* -]"58E}!<RtB3fQcоgl D]+qBKAT@|[3ԉ#Df⺊%;B&L$Vxɝ¢ #I-2:C!Rk!3 !pUvMldҹ:C1w^r-Uۿ$^UIYd6o.H:6oE~,1rrZӲy![6GD,***(>Ann͚+ nC "}04 MhBЄ&4By #DS nMs .„ )\Wжͫ85<d])%heHT|ؼDf؃7|B+Xzy|5sdվL-@(BY&";Ҳe[sÆTPBF}NwIZT[ƶmI:l}1yՀpXT{_e@t+~2'rÓ7V͸σ? ʻ)ϝb=wXOڴN̮9CO[ۨ{5akx<5v綋.Yg,3a<9m>w1YK~CkyWMsLCcT{CɈwQ .~orp;w<yEb3kֲ]hG=؍9 ;D2<úZ.c?N;2ew;}Go O~ nccE<L: r~"޻wƊmaޓ8ajSS|CÞvc~u#n,\Z�ޜt2̚ <H疒ysƏ?L�aoU<l\?5!ؽGdKrM[_Ǥ?Oʐ__G==ΑEgFp>#N88ǖ,sf'7Ҁ̴ԶLkE^nuw0ryf7{_asf]7FNTWWCVs:ϰi ] I"nΘƼӀ"xo\~bCv 2.m}7G hpD5q'\A]e2J/MhXҀѳ1TvM!?`פk(BĄU4 ]ƳՒ8Z%-4Ҳ6lZNP2%!rOB/ED6y-ݩ'D>sV:? ǵYF*UHK`KI\ހFcY$\wqrTU{gF&%" �+KT@eWW@EATYW�7)&@IfLLWުI&!@=|Tݪԭ:99FuԣuDΎW~L1.nYPl=#>SN2~7:Z(DD,;m:Ww+_3TS~~k J4Z5v6TUiYJʇ9`+[rI-N$r;'uX!g}M#\,؉b4m&q8p8�(?b-70! (si#FG]9mL21il:gmF QkK4G9rȑc~aEFWIv~nVW|QP(0m4(⅕+^m9KIom"MlGikRj5ݖԋ|U5ur=6u/*ygF4ٲ>[]/p1 xc+$ޣqZYJS(cYXz!cF<vW'vOgIaV[Yo͘EPLf2?ZGλ[~S.~a~||hQH ]Σ<Ԅ)l-G{-K`ԣsA 4wN躏k1Ic<ikeoih8k~>_r'ݶe:,Yhfhg똓穻?g |#>yh ksT3fV- $\qyhSBqZ=?;5W~ozʮ>/;ܢSu:uts볯 ~dx?48Z9=UBfԪ6}q\m꘳_kwU'=ޏX= |p2(wLgfK͞}v?~|RG\w>Cԅyr}͎5[VmQNus>tvl4=ch 8 qݾfv!=^ɝΣ<(*PNݱ~~X}kvS+ ;gp q6?x+>s;ȯ,6]WC|j,{Cʩynsob=_ygm]W4W[xx֊C_qɥ_̖n TdjiC|Kr/~?{ey{\uo2oc!>#G\׷bDX�Qn[{sښBg;~,_ H>J*5?aUIM}7HYMZk|T8 sDj]팖CFʑ#D+l�e\Z0 JQ vBQ40>ZHPcP"D ;l1VJwJ^$a Z4T"n /)n%_}$8WiPxNόX+iظ&B 6dM7D,xhQ+46r94ʯce8+V戭˄wmo+Uwv=9vSv_NȾ:Ω>l-c6G9rȑ㥇40󸚗X 쌑b*ӧM՛lCRBa$'6n|(^Ɵ3+RQBeQֵZ ` q;=2e A3oٛ2T&(8aeeX~l^ݽI/"D&Ć6!f{w3ije,_ mjVfIc5쪑SɖuMv3wO]?MJ><91'fω(]j0rB}mDgWȏ1skM`Sצ~6ٝ#= xwqXz1N'O<BϰW]7)?_p%InӮ@#Bv[�_Yݟz6rЙ>'b߲+thv}|;7q� xG~J߭2r ;_*j9[?pp3 >۲4ҦwgԵnynyg<^ی-ϼD~0.{GX{>kXsdM_KO? jP3K^>߰##Gr4^?6Jo۾-o~Au,_skyikovJ.ISs}o^Ks}J^>aM}[l;>2~5RJ qCB=Run9o^gmS (҄~o"C#Ur˘oGGyy.*+P 5x@¢0@) )E!`VEt-6'hK]+x"GZSwuz*M'`i=5I3oۂ81uGG[+ib"[v۱tY7ZC E9(-hш.-Xk hBH_#c$t֮rmD YĢ(#,}v1#%CpaF^L{{}}CGG H&ܰhQ1Nڸ9"6%Kl(G`n"k 79\#V1, rE�C&1YT} кHgk#riO;ۄ>R,,ĪjUsIHW2ԏaFƙX[ x$͘w%ɑ#G9rG:4Z+U\[& e^ N5V1481&Cipm[MV[oŦmJ 2sӟJݧ?bAQB|X!BQKe.|e!?A[A[XOZI<N1$ -2<2LMBϊoOXѼ2_uGԒ@?<4 O+ه.[h];+pOHO]&}I?ϚBmҫfw[E%&$@Ԓ3wpO*zc_}ܓeJݘD<}T;:n?ܟG3[;K]K%m$M4}S3PI똾ү[[\4n:9':}q?`IL'} =~F~"`~󨷅J\^}o+S.y/?̣_d26 j7{?G*9p[?eOƞuÌh{7N c*OOϡqG{kX ZB$SzmOwmT'>Tݷ b)h9;v[{\H?k>]ud< h`ojaM\",KI;`{ˬdSo+֐͏<㞃BB MFyRP,Cr}jU uqg["[I>B`PD0OY̵Ĕl%~^1 L,(@6bWƀh@dirm k5nHe@"G%Teej,+^_>/bhFMO=EO/~bT `xGiq8߫ӍEl2yݒڌ)QaD cQ (6r׮o6X=);& A֑R "O>~쿌LXMd]q&aB( 5FO.<!\vVIcyWgT$ϫb xދgǩYl|io/6!e.rB6G9rȑ#G&1v6R!(3G J@T9$#X`&R yK<21P(\<Q%yfk_El8v { 1 _SUeQojh34$,x,F :4erzz/h2"^#p>Jqc5x$ 城XO<U$°l3i)Awlѧlᷞϩ=^2jK;|ey|Icݶb/uǭ^sm0$=5nko82z.b?Opǡ=QW: Gswܾr'Ξ�c3WrRƦ{бsgwx6 kblx;s~<BV93FOpu3QNO_&o\ۛP۶A0onY7o~:|w1/UB)Ϲ:i厍Jta[t XX~9ܹlåϬP=ٿfY(>Ugy]l?ɩiߎ~]Q=lV-}f`29K}4ńlQXn=l >kbl?\Xv (o/Hy6ﰭQ@g[VcVe~}/wXCd':*х�cXg#rhm7pV9J)82LDPZF)]G:G8ҳP>2&ܾG1-%K'@ 6LEZ]noIDuR"~j ^b}*u7?4O1?X8Rb(0EM2ñPGKc{!@)^5m;l=݆SEt\ "VD:ݳ]I{nV"+ʣ<׿P, -*}>_<ϯCš=kmL|ƉtƐ!nMfx_Ul9rȑ#G6 j#C-cǎʬ%;-ā Y,5c 7b/&Ye8- .rřQQ#`k$ĨR.Nh4TVh1t$, *7Hbk%*?k[ObKu{짷{)G.:|WIMN`pg3޲'ܘ]O&V%6ytB ̟c%e\uD'ïzK{. 3\{o⌷HM)Kn~t||i,[ʣu]okW|`[X7HF/߼_H"a;62ړ7e|ӿs;ˊJt/yfkS{G/TTg7q[Xp~̧K|d\|泿/�[rN>LpwOc=Nϐ5DIǎwn-Q{805 =Sg ӪwCsşS޴k9XO/K\]7_9Qx)6=ߢsW>Ǣ,Q Kw/OW|}]P�6NgER,Q_m;{2!}]5KO_.GOw']yv-C-s*Ͻi6E>w'-4k6M˖hfޑïn%=}J%J38h\#@޴89+徥L bb_=~}̥އ}K)!aytg^z4\frj=OOtbgpb{\x olλv˒ŋk9gN܇zR%u 7=, =˺)6cOޑz3͉kFnoz߸ogv{iY�� �IDATٿ{k%8\?L.|:yXt]ʳٽ} yv9jE28Xh~Lo?1,zr)ݽ=t/]t؇O_:Jؽ=E2X,1ؿۯ<mMv?~7͚swÓ=_{+`7?zq-'ož1kz'8ﳠ0 ).憋?#80UGMCaioy}7s[v_mv}g[ݽ23G~Ǟk}/5x[e%JmR(`P.D Q*(`Ea,(A BdȸX0}lb<IX:5)ȶ[8&kmb[*@[Vle 0B`Z\ eK.cyy~ ^lF f͚ٳi@)OBnal{ s c C(BK\ Џk` DclW1iXq~<PbSK~f#C%RXDC6 :5Z2 Q&r+6"b5)/\Ɔr=MLXWgq43f*51V&LV&G9rȑ#G/*qʕ5n kS-G"UdlzmY31udla<wxgTwYR&!P!6pV,e)uH}X3&e-珍ɑW.Q77PWjٟ6 OSow2?8-gnf.HE;رN«(~|v8;Ӥis~ 8gN:+͙s0Qלۮ97@aeMemk&u}/}|ˎHu&G_xFk.#7^Wo4:8ʅ<Oh0,拜͹�.o'MMcR똓!&9.4:xש߄ ͟=e,8[ӕȹ#`/Ʒ8/�umʭEb #ay\:y=ƺNըoV4.cΘ# ˙k1}[3KG=06Lä̱T$۩ϸ쫧GbwK];N&\}`6ߵe<t{qܽ-O8jϟdoo|M)o5W]'c{NW\VN݇}𭜗U6eʩftl~0zp;o8|$|GgnZ䨯~Ob�~p` UZ "G푝Kq]ᾞ x2VF5۟=׫*?sO5+  |ذG �.jŵ~k8�pEmJo)ʱ g< G"/#/�*G_>C+c;y)5�w{;KmCSi<j̏Uh IHS+Վx5 Fe (h sFđkJ)ujN#fu.T-R%M Tl9ɕY$.1Ƹ(ZJAs",6 jkCcUДFGxjּP,"Jy2V`UL8ԵL3֓J@:"X@+GK +Q"Z X{ັd] giS`UEPIjXt| X㌆Xڵu5X˸e+N*S-E2KUL-R!h%M"dEŞtW[i{Bۺ6wuuϚYkF#TGȑ#G9rD\L×b8 թt啗6c Y}YCXT* N_N^}[?w@!\ g@; T!H9*b0C]Je)XE>JM}M5IZ </$ʥ\~ty"& VyR#X/hMEMfV\Q>Ltq-%4;O6 9kΟ^]ġ;d7QU8=SyK9(#kM5 �8<|EO/`9okrm& *fr2?\唹c'M\u>.q!Ϫ+/<\h_|pagwuadړv9nF}_.PLs0-& 9ʇx9~qewGoSp;O"1S_)ui%ힵ~ jJF8rqVF `3nd S~33mz~<m^]!7p(p+/~cǼ~ .ss cR,5'p39k&׭F ,)*HuW8w/'ݻ&ZhKۆ'u9s9s󩓪)}G<6+><ߏU[8gZyŜ.[Hc/FxfvS嚿.4&c=^ڏ3ܿ7(1>u1cYf;vZ͢9hU^Ǽ6R87G38vO-M_/bd䁱SVm.=R8# ~:bn9~tR7M�͞Gw9ggM?>ɊGo{{2EMERgۣ7}=VHm=[՘-*2╜% #Z} PLhZL9S!V5dEEZݚmT-؞w#>ˆH &,XC?S˖1j"ʣeaHE,_s=3<:B",i[I}d\dD:K&@Ll@ndžeڔb7gͷ@#EA *HWWfOҔ#PX慑aFg#͕5(bIll4{w+QYZYKȊu$Ah%.bY+S bEE0tdRUu: mZEikk֧Zrc™16h(}Z֮lc2km[2<1&0d6[ Α#G9rȱv�;$A[}IU/~{%3c#θjy⺶#mk92:k1#i45N))9_="c"f2At h[p{Zp#;~ݠ}ka ;JS>%cѭDow/ sLw#">Qe}@t2k}&6\o@aL֒E~ tvBLs]]'@(kU9̜2YYS6]3jڔ2aoA-{.O3Ktv<~-bh뤬^Ȳ̌la<:;;5q.:X :(S`jtth%)E5}S/^z9Dz"AGe z=K{\rH5 Z]OѸMb_U2sls8{,7wq# 'zVae+ dJWُy;g̱Ƣ)-휔춟۾panK�?g̪Bzi]әwC͊ sdZqDO7k&W_ K}tSs}*3#bDԘNoyLWaHJJaEs?:Naz̓C0 :;6T8P"$rV֟9=nػRh::1s֞b}9[!,Gv϶鍞m՚k}/ac0>͢PBlXMW"v$Ҵ3::Hy4ES3s##%KZDZy-㘐tѫZKݞ&\4dˉ8NY\dF+B4ʂDaDyP qR* J!VŦJ(jSXwNi6~l!˺.Ϯ7h dծ]MHM/ls"L`n�FR QT.h`đ#lB((CduJƓ'ObYX|9AF2*&ym +"]ƚ*yDQQ9&bUFEwPDWƢ+*+k`lybT?*rQAAT۴jI:sB6G9rȑ#GfY) sxi=y!%"_1U"d 7&fLp7Lk=e@a(8Q hvl aU0 1RvFhH K8). ׈ Zgٗ:RK| ,\f>�GLE9rL ZR| 3`o'48U7/NeZ kƈʍ͹o/zxᷙi4'd[Zfɑ#GUΟ* ?G*N۲Ƅ("GC(fm;ۉX b&[mfm3K䓋.A{`+һrVfL:&c\4o !r21,#+8ya.]ܹք#1'm%RƩeZ1#e'wׯ2.j3Pe! U#dq<r,N2 e]$ٲFID% CtD,X,"[u<!+Q0X\ TƍX ʊ8ͷ܂KsIJْmizSU"aHgG'ʮG1F\26i[![k|uq;&}ȑ#G9rhDjF@SB{%3Q1r9q mYmˉ{bNHU@,2V,o@Rrj֯*�ȈuhT�5r`PʧI狑G%5̊2sA[U'z J@t$|i7}e �E޵F/qZjxn8##G/}%\r.P !.=G" C:U 7î;'xԶ֛BoOPʑ~R1#$i@r`wŻE WJ!qA)҂,a9Dȑ Kd#DZn$ uy\AUMHPIґk ݆1#pXqjS+Oyk땤R@0بHMQX"D9կ(K{[,E6wM2^08Y>W1tXbDp<R+5 `CҎLc+2ҁ5Xf֬ ( <t##*6&fkx6(!#G9rȑ#U/cF@pQ$_9QprC�d%[Kc<X2=�W6B$jGB hQRp}H" 7Č?I=x:>:b_?0}/,t94KI9OSg*sȧ8hYn _c$/cN~5+?W0}Ԉy[8=kƓgP-J7GH9rF|(,KX:V9bLaMc2k<,|h!�BI~z _<?ƄX:$"Wx PhX6lFD&D(U8Uڂ2TrAhDQP@e h5hKkP&@"9[*1㔧CDy Qb#TG9++Iy_{f3559zCG]ld"t!@bĔ)[Fc*jgS{F!(jN D("V\3)#( 5ŚOdozw!D*#WNi7^M\ǰQչkyl9rȑ#GLZ]LX{\ P)I$U)#y:[֭"SA))ao>[H�%M"lb$I*{o8Xbٞ8w*_Xm5di[X\hx={3n#ZDN>O##̻ `;̳,\I>۬fUZ؝/|Cx9mr<;� YX7$sov1#G&BC|H,V VY 5#X,^ݽ=Hƈ0@o_=˟GpqZS#6vBbVBcl"ZJָh8MB8S(Gro I2H؎W>W|J"Pz{\ \~[A=8ŏ8y*]RX+E@ XEd]I"˳x]Ħb[rY?r kH_1b Q3ʙ^/IgRxYA2D<sh尌HP7?ZcXKi9rȑ#G92QP"F|FH}ilTdiu& }Y'*5@fjyO@K &u-]D2xѮ2Iۣ>J|eQ㎋DRP9.lwNƮ}ZolR(G w犕OKbd2vN>'�g뉪yvn?k>}ɶ >o#gĻ#GQLD$0\HH{WY+Q +V p,+Qn00X)Ӯ mf iP Bc"*r"Ą8Tk9gLuԢ#\Y1XݫMYqIl?s'bcG)1v鰁XebU+Xt j45hv^ݸ4?b[E0V#&@b I+(#*Y ,ʪ68)"Q Q`"hCeA#@C/ D " Y/l h1(U0eI̪U#h TRK ڸaTĤ:h+EBg(GXePeʺLYIтhOx=?UP, 9hq9ˑuc1FPVcӟf}04ss@|i~B1$ƘE.t> Cf?afMx#sȑ#G9r}lm#Bxz@ ;e.$iEKHI>6ΟEo*҉ƑR5}*j35n_}p88kBQXG&g\:r9 W_.>~VC]v*K /4 ybJs wMYE鵗 y~@E9dFtt6"/ȑ#GW6v:L"NX\XZ6bM 4TQz)f06".6,*LTzZ~#^y+VqQ@$eDL=6Û؝r?#!eCZqiBD6kWtmDHmc¸u&${xٴTkϓ>s$N_b (! E% R.<X-c8ie"pIzr%n}m4.1N;wMkn(1S:blS~C 僸�� �IDAT'\t:q5I1e!,Α#G9rȑ mIKIX&xo\tiW 6HJRQ^eؿ[OVIڕҬ]֦턉EysV&G9&/%ĦO1e:sb[Nkq d5sC."Lg)W/ȑ#G~J B!Ƞ mDr.q0Bd7U"A\(l\nZ bMDIfŬL+G%2"ni3`$[oAa3jtR^ Qlq7oXZ:4%ckX%.O•mml٦yyfR/FN8h+XːbBVj67+8 6BXrL26%ٝiL6,2mmS&OFi�YW 9rȑ#GPKw؋0yuouDƋ:icϙR!7 sT5DY$r:ޯէ*c4nPW :jcyI`<c99QcG/VYI9rxBⵢH"HцX#e@V X/NW)KdhUthEhx"6O|Z!) K<Ji(g,z/<j10/4<B81"˷jG46yƺunF-ISZWhYog+VuuzUO׮IB*jr.+"8d'^\qi;gOZ0cO+mw[ܜvs2q˖ǤTROhL:rɓ:;w.BxѦ*rB6G9rȑ#G67qPeRԼ fO#2w8)hfLlRp| ۸m([Ϧ} Mj[I<k"7;bRg`حpXW#XWLCrpxeMɵ@KӿUT6(I;MώB[ޔN=hJ(䊝«L j[|pcxRxGdUIdmox>&d]d{ ?1pr2+tc�XuY%#Ӱ>:$eīb&+JuL|umFPkXYd C %C'+i^Rl|^#cZ,((zC6֍똬ހ V|j4pRW&B%(؝KY?ǔRaDoo/rö.rB6G9rȑ#G&WOOSmx䃌U$ٱ-^H1ѼO.[*%Mp!1b'Fm6\g3k#+mMήƕtXm[;+9"^&|_y*G9rՈ#e&g�ml&̘*z`kdX"@Y՘`LUIe\6E :EV#OM5}CXe0"2 JamB@`M+0Sypt(Y$5k > [q�o$ $kk ͢x+n\4_X�X4.?X\&@_ΰ@L:GuB 5RP=W[+iHqlwj.![%;նʺ/"(FGG"kl~l9rȑ#GL8z'0b4z_mbXd+oΆPBgV6/.'O|Zwl-\Q+̃ ˠi&*6dqܐ|: >%nOwdLL5Wٷ58k!7GuJ1Z0iezf)c(*qCCָZ &&\DHKQZ$դWOE2ڰqd,q"67o*bM[{XӜ jWu;ҒƠcZAKLZx Ƥobxw l@ \+hTFf FD#E}_gNȑ#G9rhʛ蘰Ӻ !Umc\$mM]γy"GX'6D<Y+ٯYѰ,d2<KKMYQezxq X{5{EuƜY7Z#+TE&MW.(bFMuѱmZq$-ڞBvjS8�1QUq|6 lqd_GG''1:<#c5N.׊Y~:udPޟY6lsvnVe}As\2Zk=j Q^KxEN[V .U z>m +J,-aB|dnM;k{ɘd]e q_6*w,HJyD"'dsȑ#G9rdBlVL'+jr\,clłt#&uE\^T݉Y֬>%rlZ/SCUsF$sr} @/rcX7"E׍V(GZ'鬁@D&bT[\XEVvlRY qĜI*P(`ւV*ɇ!cV5L]~k1:RyS�މyLa27d^׎tr# &s6i]' Db%j[I{ps%+DL2Ƭa! .ڷYo,3 �>ֵi*L`n2Fdl.rB6G9rȑ#G&j#R鍇dL{6:OYCe( ,y#e*⥏g光ڬ(sѦDzgx)h<PGǮ!k5}EyY]bBθ_uˑ+ qRJq1h\yfMрF.U),bB 6Hj]`1\aA: j-ր87hwc5J Z+L,)e Qyeˣ9pc&Jy`5c!`9zgl?֭8F KQ鱨=~.N&#ՌQ&n+fE)^(iᅡU874;q٪`Bř?ٖE¦9rȑ#G9a%BUHg<_xqyV?I^]T')W&Qdonsm{2Rib뽖I }Իϖ`lvb9o > ܍Ysp{_5lLϱ:_>6o>X[gȒGeeޒMf6V=Y{~]}X'X3P+#.zʰެ-xfZ9b S 6>Ulʁ+債Kc Z k΢WZ(co-{u?# D:M:8OB B414X$A Q"VK䔍TVa#'[5#c1.DGՍ^K NyĤl]ΧEYF.KJ;'vU6 @rxJ`6NU[!Y$$֍ƦfWO&ƮD$)]u;D`g͑#G9ri#ZĐ|lHPF3@5&DQB!/ ΂s8OG9VGQ]XMlRؽ<+iTju YѪJ&$M[$ c1 lD(D?� ؍ڡ?;> 88j'Uy,hveWv#3*۵/__jwcv㠫|Xr-߯|"n}2iƅR˺nǣnn19&�žAʼn7^>v}wv8Z8v}9GϫwrWwqRiVP@A$*$)ASQF| ,O0L,A΄ K ]Q==3=wJOwUuUuuu={½n–ړPĶBB4ra(VZ*DZ!@-@*D1X帠5mkcvO0u!4Z(8Z+U`H Cq{]!BaHX:XJ#+4;ncaK(HypS,L_@'ք㚰Hhbhb@ %1!IATjTQ"*d8 &lGZG!B 132z\($o}mR@L(b&.4q!6ATBL*BhS%ȳV\`)qZQZ8Ob )qIJ<@250Ft l!x89)Lat�D)،5\KV7 3JB6r!r!@hNߢ4EՖѓ$n ؄uqBTu;y!0Bk hui)1oes{ϦnkCM�7)?K0߻2?@V2c7psá"vt-t!cZ r-WБ<8D=__fმDswge / Zo! 9T�_=7#/|n墖ck,j4p-Hͼ+o{}氷 c!5XhhQTĆ KA<ne6vFPvK dF*[Q;g_zD%hOAXiE*m!,<Ơ2t&N9?\N*rk<[~GXҧDw6VO"f9sadh%t1$]y\`Z۠5"n64kU Hn,gҖRUZ~rpx es2ISKm3qxl,(FLތ: Wz/+fbVn=g Y" YƚN1 e{e_%g!C9C9CFxZOs;Z5au]BHG#,4uB:zUzJS¸n2riPZdljn:R$LjQiR4G)\a *7Z<7Be^xLnW5^d\e7Mw0ocT^Z7JǷ&2r|>9%6ΚȨw2chѸ]Vrzc'LNMgZo#;OY$Fς?zK40C`!VmOH"&N\DChX: 5ca!�UȯFsu BX"e+vb !<Bյ% B8qd ɤ/!]NSermLV(% IrTL"RkD-rd98'f$ąDICƺ>6X KPRTYy $gKո(%A۫^ "4Aƚ tE:1nY SV CJ!eP(l;Jk_4AdAc$elH6vM&~zi*T[R:pB6r!r! *5ܭLmv!eYDT[ , IC"e,b2 y)'dH!*ڧHr<t+#DGKT@&|J OWO4+Qt79ew哦@Bq LUG\x%_Jg‹螫9:y>fTbV3AsGӹ~ |9j[Ђ'H1׶c4UL7zm-'~;X;xzD+w+k[q?1k2 EJr%Rڨ+Yai%-6Bą"[!,'V'[m! 9fCHXiqңC{-SZZJز\.m#!%WN=7.`?5ig*1U%B ə]_Yݸ -HBKe"&)A"e)y:lbH)0* X1RQR[$$%!-Ҷ?b&[RD& u򖉛[ c|@)p,j ɶW[w/c ?Q>ɛ#{eO--),T6Kr!r!~?NQ֤IR%!A@ٸy3)ڎ- !C^.If*2!i}IqsjM>j\xW2"%D"O k<Rrd^oܽ-9`\}y,DH#`\so?fL.sJ^g[8F:yȂwu;Fy33|,'ƧkdT.y9RIsSl?ִ;FK:(2gh!2i&[-+5yE,H!_NUZY+�,>n3b!iK9EW1dn]}ozV}Yh8)̨NXX !咵^T:eKBFYZpmbJKb L_C+A<C5Zb!H-ZRp\#cR0eN")}L1u/)($iĵ~MyNp[?\8ڋZPZFJ*8vcor!r!* 'H9d&J %שC+m;̢\ӈK׺;rZ"̞%,8[bxLeLmGr7m݅@aN\L"QZz*;cym?XV@ slρ u4T?sbyD*޶}NAeǥil^{-$B ׌}ҧ]ϖo;YOΙk [CSzyĦ],yováRXk~ ɦbJJJدi{ :6 f| Xr69m5lDGsQ&]wM{Ojx<w{L^YzĈ!T5}7hr'۴ih.X ahwC:NA3:ӾA�^~Z~(E3kl:Ϥjtއ4pepm*Mr|߶v>jMhe O=s-_1TR֢_6m9Ae]5@|Zp.@Qq1GוOH*d&R^m?,gsy>0EU7|NH28Mfzߩlb>n՜:i[ W}ʲkXSLUٞNA b>^#WHnRO˒SoNh}ӆ8_!^WJ()-azGtn^ڒt PRh|dwћ <O{r$ZbjѦ�NvDuhWoJ^GS=,xk?Gѱh yĘ{%uEZ%aWzйY:Q%~w>˾KVm2yՀV?7>XUR\RL9e`  ,}WNMż\ �USت9<P$L=h^i>�J62w\(Čg &a1smӫJ62g^y6EiV w_} 晗Ʉpc ;Y<~t;Z%[]wJ\^02n-ey7YmU@ q8фv/>f%F]延Owj]ۉTcOw�� �IDAT40l_Vo&zl,bۋOۍ lZ.ȯz6O7b!?JFӭe7s|@-uS x[wIܥ,KE?㥻oOW_ɦԢgOgn*S�썟KoOUK(&ʩ/ӺUwX~+oJNw/aΧ!ǝ|l'g^xk: %%Ȯ 8Wrld&ve"9O\ [(<ԪMTk۵K(42!$'( KY'osvDQhVIĐrU.*RW~2Ir)=%lru] BX:^ĸZ%HY#^ӤT ZR-m7@+"NX Yp2KlaO;,JZD%B"@AV&@KE}ph` nqTXC-$eXNgM&)m`+Hy02 2^HGri&{/edr5J;N WJa6J)8m{xƍWz%s!r!rqQ'�Ol\bgOQ&$ Q azo5~ڼ!P҉!*Xԏ$ YծH)[ABڵ[9zYVAto@FXB'B AHJ,!F #{jΎ_* NrZvmy4[x,.:=FR6NgE˿/!n+OcȤ OdGҬ 9ϳ0e' `Wh%P '89OC5񫂓~n*qB�Z]Dױ$zqWicN㜲[`SzI []#537pGE=I#Ff{OrW<ܑ/îKv0d df\=}Dzӕ>}&r"L}<b;#ZVei5+7/䖱eQr^2Uo,0 7Q?1CxrS�3ώZV|6͙'^E׼O8s+qe<|k;?䥫noY$=;Xg~5dZ~|6G;qw'|=_”tfۚe:!w,bǰ{Dj}߼->݋9V^mQ69ۘ8y[e5@S߷#SÆ猒258/&?g^=W]gy�gN$K=?sSVq=geS|q5cWjtnţiR$>[˛RMd,F1.u1ЕEx_ʻ?̢[L?ye<u& .3)OCg3f"|tA%SNY�\=;n]+)j98>۹W ,׾O1ˀ.ڿgUdiM]Sh6}>"+̗HLeCj)m-Ng^ੳ5s<w+91lmsÍ0^ڌ~ + N:z35.nf5P>9~V?vq3 jvb*py}Qޥ%Ĝ-}[=MGfn{66?ϧׁ= ib<W\N\E<ګ`ђwXF{p?!s,m'JMƍ8}G"|)˾XZdؖj$.@ZhRXfaYV S#KfJD2%.%KĩM&j|zdV.J$I/kCHV6:!(4!+FA8%Vy44O8oALk"V%,6döBcxH wF'wȩ=!tb*c,[f|w ᘂg#&3t>Jwǣ3<bwq#;M(²,/ !20g3l9C9C{tc#b o6 EmC*4,@ �mIvY SISCv�'M"!jN nݠlJGr\%Zڨڶ]Wӆgߝ/MٺzO/s#'>Bd0|2ඁq?yt|Z}Fz].[&/gl\9wx}|PW.TZn5s'M1aͻXf62׎ 4R̗}!/9#Nub'4n,1-xUcCθK;l SMaLm"@1y QF ]z%Og5&rsDgf=1ϐ@O_y;~hT2Ka͝ i>x,7c錛<Ӝru$>^?[ןcM/Zo8=6r_FI]c9__ӿen;q6Gvcz>VXWRiڔaZoaϧ{T]7!iVra8rS)/;16xi[)[S߀RC'6[vҾ9|L4gCS/{vwro<ޔ-Kg0xO.Gu7޾辅$[Ln[:^|X#i㟘 ?qMVInb9wv8J-LͿ6u$ߵ;d.اk/f0qƗNC/nD pj|&}}]&3&~KNbH]iZ:2[a7)a�3h~yqq's>R>"Y=YνI/]K`qhբ=f|9<:9k3п9|;%W^^#`),&m7RܽΙOyxHK9<tKVwwwKӭeu~\6[Gq1裒j}i Ԕ_g]Sq@S8\pmskw<qdh3lY \p}k90%3n?H6>wXRy-jfޠ26Q/> FLe׏ҢyfܐuOdz%9#cU޶.ѡNuɝtkY V﹏6}8m/ا%^r %d {y87vE4j]rە%ޙ[oC &plb>u- B2!gpǓG/n|+_GԧipNî[<bV0O9 >;5:p%M7v,~;MO x}@{x?!#YBZPP ߏh4>!OHlBhCc;ya,4K8J8Y}BFu]@>Օk[˸:FwJK?O:a+H N fh,7[a!C(Ql%Q(T,UƒH1y'h[eնl۽$-5G Hs| +I[kYºɨDW=. :`,˰4RcW RVP.@_!C9C9C |cR JF`%Xnr+eg@#-}nL,h' 2I$x"AB-?N2Z| '[SZuce@jǿk/G1_\ӻ,F<kO:,F7}{=zϻ6+ơ2rS"VO?3~Fw{aßI&,j^ 1r*Z|l}�PĚwch =Ħu}0|D[XNnos'2zf.e:to;){xH{6w9ND'<f L=I3qn}Hf)_s0>x6Oe[[韭p︬߬C7 Ξ]&ӿC-w!rGa,xcӅ†&؋t\7^2n9'_8z4/_uC&x?6۬Yz`>3y<p[S]a16{a+_޷C#{>e}E҆uͣ3큪9q` GsgWL>>'1;[.Ci6_si腜2Z2isYxۺdl!fŝn6/CsNz g׼9v+Ȑpt<ޡ>Vvowu8b�&=x# +BtdBkscމl\)ÇHCq|5zn}dhX>5~.=)z0 /ocMxX8/O˖8Fy�'t83:O4{4 '_>�@{-dGo1Sb|<L;ngGR?SˢMk_M_5@}X'-ir"㏇`ڈ?ri,^uጼENhs>+zJ|KWrmZ .ʤ @5s' xk"o<~Sܰky6gv3ZچwL]Ǣ7u:t8#.~~uS#[W" 0^R>{kP7-Eo*>6Ҕ~񠾭=rzûm]8ZԶ5mu,|Nys{U-|SsݱՈI3ᢖ.Ssķ1挙0/ ١{y{9 k/bf~ X;?z̆<%G[2񃷸SΚDjO”7V1t o)?vnggm'KJBɧlŏFE+tr]241AøJܸg{ʹStE)|Zo9w k;' $r᷀:eրw0@kiET+meIvIl,BvmCHYB`lD&o^l)IIH rm)1V>6Q%))G,˳Pt.d 1U.O_Z8-eer!r! .猖`+`k5R"R@UB7*oR_=E'ވD$iDžJ*T)b4}qs(ޖW$9 O3!\ZB, X` Yͺܐ"\)l;X^z ĩ9]=۞;o_ZɭO]ne:1sZ~߻:?k&�]s  ]Ckޞq1zO1N>wಁ;\26 E�WJ+&Bw>4"-M^@8^ӯ}VmN c955f=x\.qP;˃>>lЯE4q-uVkJAakPP!׾[J38sCtuҬnsk|"YR#c ?坚tnXv޷t-Di4d]00kۿxh5x^!<4OqN#-ZMڶ`s<hIdx]ז C'c҈Q~)kp᩵KLlFK+\r0h9(ØhўAsG<RX].e#_Lwgw9~XDPھ|۶Lp=R PKu;½y"ɸ_EV޾w e^țϏKj1{4s6& bn_٪ߞ>\{W@!U<F:mIy))wv7'X3g*n{6ŭmE8rY;|NNs=Is ^gښV;6{[m(nNj>{ulzTUa|Id,� [3m뾹<'c -[yo%sg¶eyQp慛9n̼P\W:-&zfvÜ\cUARV;f|x)s?[BHI\klr(D1 Paj"M2%Skl5(Muֶ]*ӈueh, pM+cX< D@|YNLRF#5ҾɁo\5!&l¶!ZD]|i߯$m %-ALjlIMLXt) ^6쀦Z }GȇoE)-TM/#ߥ4K9  f+9B(V%/"_W O)<PѢgxj4^>wa/KYi ܂m˾a9C9hD٘2$5KLujmsuW &rT6]qo E O86)ץ~KX0= sTȘ3[⮖JqI!0>c(9 <ADnS( en$l^:La"φNhI˹fpX_3 ڝ<n#a״5 ds1 JY̍q!gMwYY=st@Z$'�~P@}9uY+/k֊gָpb..BYM@i;42S޷ªV4 8m[4"jӆ~rFdKT1?,*m|Z+_�k>gc,y9ww1^93l苤Fi_/ґ}yQp: | \*oID͏_$=Oxޏ]C;(HBlGr8& oh<Ņd8RA{3,jr"YP�z7S|}:q;\64u3̅vkOϓ; &twٗgo-bϚ c9ib;iv2?LknhkNj sRQ őoG˰<KL;Ga^h8/4Ns'?w$]-#qN3K"єXRَs<T J "%1K"呱BP4H2䐲!5 NT1׵JL+"byHir%$T7R$.M9J%Z jеys:-ن~M\WZT绹aM$eUʿJwps_n/fv:_ZⰢ4 )"?f ;YMpm^%o!t}*)[A5 'my۽}i契_%EK ߒsY\"v--lnbVvۙ4Ą2X%-b6nwP.6m4Ր&GP~ ȳ„du5C9C9(TS2 Ǎ/Zb 0RtRr[ ( *mF eHIǂ#]qJJK@+RHE$d.U�� �IDATi o 'qZk-"2M lB"DFJS(i%%P+&Qb^ пvr+_8V&7R OJׯ S٬E,1Px 0'{$Nl'n7 Y{mà{^gŴ~b:ңxM`wx 2r60(*}M}i~أo]h*O4dГ3W;y+uRr͈]u*}8I<j$.٘#T m}+!s d=%n731y#v8Fu*=AL/|+/dΜ%,v@۷ Y@Ʃq} S#I㩄U sW׆'tӽ�,[hsDZc�g�񑭁"KlZz]S)Wluθ5&8h(::폠fMX2EX5s$](eHClt?WQɮZu+L( T$_W_>JXquJNk܋3{A+tw&U+=%ȭ=[ZCYd1+m <)ucVb"גiTa7!?W&\:0鋯vN3n龗ѩ6tyy0JVpl0q`*DUx!8:оYnQ=itqr[kg]5 PzWоhTx4 ]"uyM^} OXZ}+\+aբļpzݙ</f^8` /yrq<,|3+~&aA,ΓD1F(X^eWFZ#Q(mbj Yu,%sj9^l<v($V塟lK~w${akZS-2I)rDT/J]kVZ8bb!ȋKBƖ6JUSF5j!\/Bl՗6:Tb!&BD $;EJ#H-bղZK,e!u#oNK(f %n#0(Kiٓ}3+.Xi w̥2mvpOiwJ e{{RαvP\] ҊnJPx`_agt7xPHA۠cf"%3XXt,NȆ<G0(I<C"2#gs!r!iKI*rM<CFqP֒'ͥpma_]\As#p\PR q IyDc1v 턤͖B!_JmvOS8"R60VBi6ZEX;`kө'M3Cn}`IgXBSgK_voggꆒ m4Ϣxdp=y' 繤(ˮ {,c5!u3.`ǒ]@V#N>g2C,ӽ G$+G_8ݎmZa< ˳ĿZU\cI8?1}x&-Oy�aF]َJp茏Y覕zu]ty. pAf7q% P_1M9<rE}ѝoa[B{YF ,IL.cU[Sg�SpN=g4)ٲv.w =s;krۜ-qvOsq A5cSlʉgnz03YM2\>kjiqQeJ#N]x- ?-}nu"՝GvpW$Ɩoi F!X;.jy8匹BNpV\ Lx3vCquϖަkHfָ) HlWm έ^w׻ZFzl^G Τ|nCwN\.y~ �{ /;O]WO! %J, %$q &EҮ^V!Q\Fi#2ak$H[7*lYAִ-P>ƫX8t{ l,$Sia;]oBAz{DҐ "c&%$ !RR!#0c+@ ɕ܏Kڍ*SIi163겺.}d"c\ scbk'ŋȀHK"Sji=,4ek#dP6*%)A*Em] ʧ޾u(&RH]RJB;QݑD(TFZƉ;YAmQ*N^~VpqE缼h:N,Abbi%Ra#r!rg,iǢSHXJMWp"g5jDFG`PZB Cv G 6cei&X$~a2h <¨em]J^8< Q q$@)ALA^hMY?){) '[$~Џ2:y ga[ +Bro~|vܶcv6,W2f`+=DE>꼼5E="hI{V72ɒv'F^s�Ϣ'q|}Nsp܁{Zze6PNfV <-WF;UN^|f߶Y޷nHy ?;5MPO_+޾�Tls[ iU-5 \XCC{GҴN~A4Qy_A\rf2{W5Gɒ$ϕeY*uspKpcs3+Sn[FQ פiU-U_/jy]orYWiτ5fvVtQjwdp;xAћNvgsJ{_M8YT\Iʆ:xÀrL{3P>~ɉb0k¤ #{ нup[7emkBm _=DYsk/`PHwn+/xSoe:)<ZƼ{N~x�ʛuL}yErʯߟ6ae쵒єuX/)/vHZdɚro2Vig%QXR"D9# PLҥOUM Tj%y `')~W;䞟u_iWf_bwq Km;"|i#KHY(QX<vGD@EոB(5K: 4G14 H)0I#}i(+M#;`Ɯk Rӧ##[RڀО2 e s= 9Bv /Eq@Sfb',6jjEKagy%T [Nvk<XS:JLkB`)j&bG Ki6(@)c Q\\yѿiuTp<r!r! :N߱B %h }C0bKƖR WFvkQ+!VG1F ,i+m\AЎ-7Q-YR+,ij㶍 D)FTĕ>6PBN?|6*ҭ=] kְ;Yu>r@ mRkNG)QѮ<俚̫p7{:n�>| /ߚ= o.=b\[5$d!?in*+ NU@aWsxᣟg-;fDtU}`yUFCۥ:Vmؒ`whT=mR%tߩNUY89yMpBY5dۍ=߿8I0`hgwymz6Mgh}GѤ~f ,'X̓06 Fp.4k>/,Lsp�sR8աS݁+8WpߐBκt0 ;lkw0 ȷ_H%>)`<2}6sx6uIbc8[X;]y3tEfҵ[77vw.LSuXzù$*TPot /}E@(.ۤ3&>]UKe|!|㤑=2GtLGe%hQ;q%UgYoo`8uusܚ[فG2So-yl:t;L�/4z|*gt=ȗaIۄ50RČ5,XZ/++R'5iRdjvzF'cԵMT2*UZH-8sߜ(KG"6%BhM\ ReXZc q"D5+(!bHʄJ,m%N,ش.7ӈFXiw$[W(3Z皸)I϶qR8E6Vf̘7"9C*W2ʹ cرDEE1v^X(FLF1찍2/|c_RGKЁ49JcRu؏jȈĊ1l;?Kv;إ(Z~k=Cїϵ^+lW9C9ބֆl51l <WJ~8YjX!'{XORhC"s9cL&]3^ *C!kxr<ZNA8ڕH&։VH(իR~TZe:H_KT.  !u7Uۃ?U�ekIwxؘ6?ѹMUG:oT=V-:K'z6)}s ^cs,La3U/Iyf f끌u=~<kUgtȘ<Xޮ#d kzSъ_;1NeSڏC`}RT7xM J%c{ӷ|75j3 YVyM;O$.c| Ӻ$}2 /N#m⸡Sm+_[坃KJkUD"DD>{^ W_/0򍯽؛>fC5L'T*)!}U^^XVIuQ[oLgUYc3ѧrsy,zZsɓf][fe=]OrkϽ"ܣƑ=ҳ|j$fYruAںkKpe3>庿ͭV-CRs{[^+~̚6GruM˳Xa GWOi؄MiIXHBBJ? ! >nvU]O\JO B9T!].k! U\BRTZfByV 2B  W5Tkҩ+Mr<�Npdm%⊐c0dw\'<d¶ AląE $"QRR,YZciMXC8`ح?ޟF)%9 M9k|*JDZ+ 9 YPNm%,IIAȱ@M^&d8VH@*! SvU,TCileZ|,G\쌗St+?lRbqJQ6gk}U+W"ۋx'ݻ:͛7޽C9C9IFkf#6 |+mLB ]_u#j, ([ya,3I:S5$.Q1]-,+BK@`!3-3] XCh;aCZim$6G_]BJUm=XkM\)#:IUyQ}̽{$M/H  R THQy,UPQ& tP %H !&̝͜enZ2393s-=o5|p|ԶͲױv+Ǜ㷮){ )|Bs:.n]FOYdؽ-я7ۂ/ڳ7z)/ Ue6ؕ~Esr,/?{v2b7<l,Nz[t[uT|w\7e,U='-<o[鸍['ԕ7cl<YU773h3nTr|3"}yCqԛzNwB/͉P(�ucp8ѝ/ђ_W[>_|]p噕qgq쑓*n ,d ͯOgV6zubck'U/x3wޗ6mbq`j3KpWIbp/7]+ߜa_c>=`q[W1?_b4al6=uϗK|oq)WC`7_s)c+qgrzO{9kOk7|)㭫Y[׀,*_/>$՛=Ybqw듷q"/7Oc_S5S8^p-/Ta<J=}7| A|+.wg> BcK%Ѷ@ m¥jCBnp57]JQ� Vk"VJeLuuuH!Q]<9C WA uJ,lEtkX:_[>$` ALZ5dQ8`%H`,Dָ1`pzr l"J'-C(ppBGHo"o?*OI-C1D<!=8?:[gGQWiƀ<nޢY}!3(g#4_XXXXB(%*A,9'CVe8RϺ[KEcpn4s%PdCtYM8(ssqȑ#`X|\7GfTn;G~x[fUTQEUT!"2D*E&}LX >'UIJUZOv#`qF!CX{7,کۥ[a_%qZUhV!d3 m660lD#4JE!Cږh频L+=_;^ŕ-$;S$eY7 ȘO©?ʶ$мE=q0ޱKq_d(6_/>ӯ WFڕ,~WZbg\z-m)Rm,{/|S = ):!Vv8i־79�G۵2ѭ U%h^_y{n!crxwf`|~5N-iG gwd5kI:L_q%1Gj;]ɓET'_3[Za�\Oۏ/\giow/\W("*.vqHv&iYd0=S4}Cw[ZIR[bP+3pt߹i$RI.Yʿf3{ ?OZO(?)V6eo_gK +oM^kΔ[}.#88VL< \?<dnBZSN*yqie Kcuʹ�� �IDAT䝰]� ɯO'~fN4NHw쵿rg,>c*p-i_,W|fR>3R~9|D _<?|w晜ywwO~U:Ϳ{(p|jA螧xf_g+܏E<2s?gzwO{V{]qS{r<Y gü.f¼7LH9-c |cS"+rƜGx?X㺓sƝ ĈHM/Ete/=YMsvdTx|_;ё+|ܷ 2~{?;֯sWydm~rìoutz^n#C΃-{><A[><n7O2Ehl5מ[/cuj%v9]|f\YJ[[ +-+q6Nj/> Zڠ<"ZW C C(i7MԼ FaنZEWWW ;;QRR`6mf[TzKZ7*s2x)Zo+ĎŰ6dr`x!MW J-@ pb[xCX6I#qkoI% .pM|!+PXJ+OBQ(WMxRuei1JDr{$k%|dC4{dҘK>P~_U6$gҸrh4.sFc->Di#c!P 7B%hҩ4H$jJhmm%N3|0Y'И`LlrpqP: jP4YEL;73Y|)6e\(M.-x|K'rιW_CSS cUTQEUTQE}˜12V,8汐"| Ѽ* ,B^Mp]˲hhhKvZa|w)>NUkV\^㢅@cIZu-5h KđRU6to,X̺U+YT*-Pk  ǗATۧ0kQWpWT}a:R�?^V<'%#М >o9|sLG~)wdZ�3(`\^IV�plw{'G9X帋n.?4/:& 1hn;4C|aCAy>˦6?Ʒޝ~oYN=#~.cS4{|E.b/ł\Urua#-JxL7XݬGQϱyΘZqO/cuo醕yӔO`s w,pihJ�g9OL) P3J =n=8�OsqsAu ἧ}?w3=x`Y<Ü>%'qJ%>oR4~*<ԏ9OwӯosR:%y 7>:xw戫rr� rM+;uB'^?:=]bmV ;n|o0^1ʫ5q ǘ_\UQ@y: c&Z C?e.=?&1ߒKHݡ3î|ټ-'+}jT#?9O,Zx}½?,Kk5E]WNL8 x/~$n_A}W)^(e=s3pԾ*||++WZߘµgŵ2N.^ 5>\܍3ٵdھÁ-)+A%Z1E#ә*s+zTH!ȂJi:,!"PTvW(&W+ UeQ/  ٪43qndwDzޠL1:$`-ՆvKi6r8l8ʯG,r> ̸K ncDZe#׿K6Ch=݋X[F2c_ԘTrzAˀ׾sZ2g"ޟǵk6xZ~a!slhD,7z{n\6GWWMZo, =mʼ?Ï\y%s~S6|ݬ[A ̰M jg@C C Btd;R8,Ml@GgM3|HΆ m(4\6wy'G}46**C o*�P 1+mn2kʄD>u"%}o4~ M!*R B+V]WY?)kWqZ@6'hc)d?@օ5Ho[-rcyxfK<+C?-ږ=O9d_V~ˡCKoj~[/<L ۳ޛ9̜Z&޳C/G-_?)8n hYoKR? Na=+oCc_oL 5q# /*u`,VЪqz>)_gưR/6'fIW8K6uM5<Ҭs>|h}Q铿cZ?fohz4+m5|^ W_d8 0g1gG$٨OT9:ycS<>%h> *fHplb1sכY@y:̹K|,P#6CWij 玹˸i%~_C?;R/.?^0CN&?JBg y>/919HQkP,΂o(r8;yPUCkf)^u]_]嗝îm')\u^s9_{ &_c8ܻt>+pa 7)k}frȔtYiϞ;} ?fsO, :-l(>6~xͱF~?(s&?~cNVZvjȓCwyG/dÒߕgAv!C5qco{7u w{!u=OOOQ"i|hgnG/eS\C_?^V*ڱ|9_Ƀ|<kKwRVbӚǔeN#f<p1nzǗy/L9\2O>c|B--l$GH$&T ]մP {Bd@ʶz?'=]xWm!r>W+ؖDZVF+E̲1| Bs]vڐ?cU4z&g465!}#@֊(*%J*A}� FS ,Ktq2Dq!\$6h+!p"g)2R`$9)p$@ %%8c`Ә6i23}˲",v}NXT^+tJA(LjWOfޜ1R=jJ)\׳t\ a}-Jێ֡9ࡶR W)r,lv^ /_λ.gݺXj71lpkVYG!Š'gӈ@͸FA3Ӆ@X$:mU=cB\'Il;+/c_+/L*Ggg-mHmayp #NӲĘ:t(7t 'x**bĤ>]ݖ]!WGRH,ipQ.h-<s@"=0nv|}FCe]Ix]㖨q8"AZ6"6V(^Ne$F("-kqhv \.X9a G9B6(W ~ ~tRm4wQcGS'Jskq�ahԳ$X܁][ 466ulmcG`chbTT~3spCNښqZrbhCy'E+;6D;!9YZ-!ٺ]KJ܈e^d/W6x3{lMCi`$kB*cpP7ϐʺAm-Onems+L5uLuTg+mtcw[)׶r-jTjnS86#eSkC/h`& /pk! N֔yOڵ=$Yf#.`bPsr-C[K3)SrQ q傺)^姮dB"O]{ o,^cWKvۛ_j,_ܐ5YA0qX n{D\Ja|6cQ6{+�j.xe5'eQW~q]6UwIQ}%6{闝`Ԩ ߭^޵-Iן[̀&1pϯŮsN!]makTmd9!9Ϫ72+HK'GkGRB: ˶P@NH GKC|jT -Rhl&s1U^|Kޒ_C ^2Ҳi؈R.xBHvf8ی椋cO0h`Nmm9 <868XlZzĸC2|9b 6,)A+ך=O䀽|rNqv|w7B{AʏF#>$Ws"lZ߭ g *Ph9Q2[{^¼ Udh0wa,gAiwIa6emĻb)^v jSp껯BHG4K)f2^ 6bJ~ҕ$dÆV!ѱq#t۶Iwe:v,N{k+OF9ʄ'2r1tf;iMHYHR;̶u R8Cۆv:SY�,i!Lv0`@24s_Wpŗl푮**vb]s c@ŨpⓆRF^rCyJE5R%ˋvm㷡d[ *BE V4=*2nƐ(A)!]\)CmjF8#aZr2؉&Fڒ[yjoD6ü4ܞlj6koܮg=&khECQyۼs99 c[QLϨ1bF64a Ja';o:\[Dm}oʪeب}Rw_"z;lJOzGg�vmj7 ޵ Pd�FFDi:Rn>peHUZo&_;| z݉W0{�9ơs_;>/8'ڒ;ymVW-]w jq>|/lop] [ -qB 2.WKC b b@>!Szza RJ)6sshCpYRcƎEHɪe2؞T ?Q*.߽RБZ\$Ҳ$j^Q)7j䕥CezS!ݕuL8%<@N-$ʏ*4Yԫt!摃ʿz`tlH&&^Ĵ[e`x! N(DsϴOr+r$ Rm!2Tg^]y,;1!P[xCZc1l۶Yd1?0-XcadrV ێcԒu԰D DdKJ66~F.z]O|T##EMm u亲,_խu.8N:Iv&h\*-%xl6ljk8N{o*)[EŘ;w.O<Dpo|ct*b6#[˯D L*+ML] ):>R-U!E)ye@=kD^ Y_O+ nyG- xZBv؏_Bp-4F� $.HH4dQ&֏h$B[h*# L,"WVv)8܉VQ}N NC;jn\ikìQ!I[AăBEIJ} ,WvT{÷f拱9jsjst-UԺo{wmƏNeJ*ضpmdY1Eg'H@4ȥkނPre ^О~($fY HܶXzu7w-\RaK>ݧy/Z^}9Yjp%(\eq,!^Z~e[y*u_ͱHKeS( Lܤ 0mX,sUK!2&Dx@xa KJu~{+`Gi!-KN]a/q,lajt{CG'Mj&ȧ~o~R |VA<oh޹mxOjJG:?=%A)F]ɀ씄v7ݎKoH~u\.֍d3}Y 2X:H$1;Ʈn@C61qXkIvuz*6mL"`+xQSG`HvcAcIR.JbL3ƶV n,;*.\ՕŽ%KΦ8ɶ ; -ZҥKnCd?8?Ӷak*#<744pGM[xg̘aú-޼y[>r}ԩ=ӧSWWW&wUTQ+Vih2--waٲe� 6n:^u�ƌԩE󴴴 `ܸqշqF֬Y0iҤ^gZDJ!*d,kcCnZBS yO˲Ю4l!FFGkv!^,61z^2jkS_Ѯ'csB+M-rN4B7!G";;#o;$iD"ǻ=~Z#mݶ؜1ݓ] +lXQ`n]ܿ,lpp{8,?cA{.T+Q/7#cN</η⭗}A!&Z֫Oїq_窯7TQE;%|aU<9Uy ɧb0"]? h@@^6,lE-ɔ*HJI6cŊ-qq< ۍfޕkԊ'j-(:0vSJaIC^qzyVP{$@.ȼN$0o{|<60o!0(e4Jkc9})+񜖑 $/+%$[`,ͻ D}y #i~a.BְB`J/4eK + 5I~5VDZNsQ+S ÏI\NGj:#2Oœ9e <dlo,`tvupۤ,aaIXfA :F~0cJ}CC%Nӱl%?_(Z73qEf vVd'*؄=lW lXĎňB֥>n!tƜ*G*q]C0_`pWm{3aҥ\|=6mva�� �IDATW_}5{]&}}=9%d ᣏ>Z7ogf%1cp%0si~U|ꫯ/C"BvKK& nC),_+V�^!QH$+,^ ƍBG/! q*1] CE([vaW_5MDLߊ |mBwT6P^y*;c#Ё0alQBRZah!?s-}7V[Cgrk`oOfy]?rX?n!VЉ{2O[[l QU g9xsڟs{OpQasyNw> >RvsG*"ǫYSR-#\+O,B@X <SςTJjCrv{{{pnyh9  .y6`]v!м~=1ybxH9# g#u>vr֛~LUi[j439Hʾ,[ɾ+\my)8@\4-M³t]옠i`#Ͳ=-G÷5^J}b$+(5]6xR{yߓT^ G۠<OG=hW SMRriOqeJѹ֍<?ヌxoJ'0jg cǍeݱdIQW[ϠAH$sa#'?ó?ϊXl9m:X{̐3zt@8Y>Amܶpq5!Ic9G"Php\VXĵ4nE+kAz&',[ٳg3f.'N,waټ !>}:]vVk9;%~1w\9眊Ү^/իWsys˪bD6_W@Rn/=s}Nf9Ɗ+*vN+WX߶B!YWWG.AG2I& 5+.`ǁM$ՓJHvtEZ!B(/t!'oh-b) ih"=2ڨhv=YZ5Ot@l6d]g:nc;jk̶Veg ?ן}G8T:; Q%h; Xmca~Ż{˯ ~r3�,\0S.#!orgz];lPMùgOٱ*yRey2 )+$E*O8˧ZP0 (oiXhY¤50eϽ3?5k9xԁcgJj:[|BiP+z}B'-eFG6wm)Q(Xg,/l@+e\c{^,f8쏥]K$;ZF D\=K[*ߗ{)Gvמ`/'80եJ+Xc&gkqH(no-NCj7v6A?hEJvGz7^_ %d2.v"؟#8Ő!:tmQ__R"įCFd){k̛Z̊~QM1{%#L:`/ֽo,:bJ:>v<p]\e곙(#$t_\1n8}--[ 'MkO>$d„ K.O$LΝK/<%\˜1c�x#~ꪪ;+"cnjgɓihh`ҥXΙ3pꩧn&WQv7o.\gq\|y J-Yn)|ռ}qdY555k -LŚk@zmu]+b5k hq_(,ԯ>A:m,48gP; r ,Ub=!RĆ*E^o< ]1%}/Áj[TgG[Iki=~ǖ7VLJRa[奔~.U$`pc FuVd;mМ)ڙmwqЭ]*v輕l _ {Eo;OT}&lc~_ꑌպ 5"{q\L:M[k+!XB#M6TeHO!ѐq\vZ)#1!5dЀF N[{;6lq]CzĶvU.9K]4 &�q)Fx-:ol[)x` ^O+ݗ}!BW.JȔJP?w BVi5qg!-Xtvnb5,\&/<G ykP.cGs1D"NC�ϥ!x<vlY6Q#)M: S,Z)ދ΁6Ҵ4F\livh)1>j 7YIt(7ϻ~Mo1{cӦM\z뭑?O#dfϞާ;3gvW{իW`:蠒-&ͬ_>80aB{Ggg'�Ç1j)lI ٳgGΏ<H~_Dڶ{3sLnV̙\+9ꨣ6k,bgEXYeĈ;6f,7n`ȑ[\^!6nx^hll37 ,Yk̕{w7}LHu]:;qr!F!.n "9ɠT<_pEs\o""xm55J!1� J<BJ·ۈXj bƖ(~*mGwþr;0Ci[WU uWb?%w*ÎMS ʎ;V`741oH9ea8*G([2I] oX$ۖ^J_.!+7X<zG˵_AWW˖/ubN@#=Ƕ,FNcc#[Xn8:OU.ڳ,%gYQ#F0y=Xv-d\g'.0!\al5wt {j47EZvrnu0,E)q]Amm(wUcY MF5nq*nS~T+VFr#[X7r8n-{ӟ%vF e\ MC?~=Hw C AJI" O6!+`?JB,f#, ۶؁3q8zk 1^ce-ƵiQp>9:VnI !M̎љu],F"PJweVP ur-g]us7ƆA[Xzx犮 UV_a}\yu]]]<<c̝;d\r gqF1h"?xۊr+#<œ9slj'Ȍ3 |wrw;m4f͚ͬLΝz3f ^{mٱ<?~d,lU>`gO… {>�<+3<va<製.6n{Ĉnޫ(,DIK$}~'"**Hg2dYYjG*e<HX?]8N^k2 t@zBk<|)ح@ӋuQSxNp@5.%&D tqYꡑn'8[(Q+2lmlU/tG AvBdc? )pu[URv*B Q1S<ޕAaWI]@iZEAZ[;t$*ϣMV1FAgWɮN,ƶ퀌B*f hJ^5E"^m'QW+Z8^)YtZ]MǦM8wܶM8K@x>q=k[4%nIRJt:lϝ2j\X宗"}}T xIr:҅gkh\3uERsV2Φ.e;K Y'ۊv3@~ϏhT֬f޼W;4]<CyO|P;lmqjjHiL}M1X#hAH4B`oĸIY#.ljZ{hgGveа!)RU.mBVJp]V'&X485 VELZ2˺~\}.i,˹cΙ3Yd1wܲdh;?8￿$ƖN8c̟?/|=ZtIW *aL0ҵ;$#wǖƫ!{7RCL¨QB7߬2eJEM:ז½EX\*e<UL<N킐$ 4-w]h:2ҞH RI,_kRI߮d J 1jk Md :<NS[ ,}X e#5wynoU ڭh} 0c۾)w'-dHwѨ"ݭbdž*sR["5!h:rKO}Wb8E, K($L4-~ p;mYqk]׍4GCjUј}뚛ضL&ɷFHCD V4{J~z6ulq e%^){fyc"]XRb[~@Zh[ y-cXEQy=J+ƍs:ďlwO '@oT<"Be(!등UN S&R"V+L'E9:;;yw#8 k[(2hP=#G YV ۲q%g2(X+)`TBP[SԽ$^_Kg[t'IRC&.ʱVگݵKZCd$RcD Uh׼]i}gͤOLAӕIKFex'W:u?q3fGrH#'L1c4Eի#noΝ˓O>̙3{o̘1L>^x!ROyE$cO<Ģч/RǴi8شi<L@j^r%=P1#a *`KHϚ>+oݺuiX1AZ ,Np>n88ƍcŊnݺ>tJ\p}DѣGY_ʡi*JG+!ZkV8R#_7vRy\ׇ%<b8^P<I(N4͏B[JsB6 `$qiZF%m֞B ۨ(o~zjT ?׸lŦr.k?|M6#3~A1X깰>Bx895lb6֯OE7bX=` Uwi+XҐ~ZyF,'VRf(mV+RެBzWS/KzV<ٝui#Gҩ ړQ^;QSA+H+tvl2R&.BЙLIp;C;Je6VGHd`*QDmR  b nEp|TcB{!EJ/1FBquKdl.7V"/A,}/|EKjMU.%p];7C푴mmms6<;֡95nU.G a!beURQ1 C BZ(,,v˗O:L*c{ںz3ț.!r b%^Bdqr nBM<sIgLubQAajV+]Ι^ӦpyVg/=:&'|_=r}РA\zE/2^z饢.׶̛7/BJ~q]]_=֭ӟtzt̘17|GB6o1 xB%Gɵ^X>#=ZnI ?ASO=5pܓPoOØ8qby6mZ]hf* >Ol2�l>)x  g}'N/_^1-/\__˛ LZc6CA-- q6n)+04Bf 'r_x0*SrQٛ�1@qdLeBaDeiVheb:NdGZ(x<n]Qwj'ٿ+#m!]}CVG%cBxn5W9v[Џq"ոZ?[S/ͷIviB%Ą>av0>qhƎNjM;ڌ2g~֛6U|(luU_ReFSw(�Wi 'CA,Ͻ'#)/E Bh,WU֚D2.+ˇ!j%IEd=Xy<m[  u2OCM$.9mY(egimb.^':X1pc,=jmE lcy<Zrˏo9؞OwuKc)&(Xx9U^u;PH|-jgБ~пګ?jV'a \\6 aY8h +_|;Ze׀5J۸r]vgucwkxWF-3 gy^3^ʛ!5OSߐ<hɮNM8 N m> )?<Y,~mha$ M([QZ\xɷx<|SƓBi5McmO?~<> _=>,`,c/RƏ_~ĉ<̞=;1{!pe1a„>oO?ҥK,<}yE<蠃J5s,iӦqyuuur)B�9袋"xgΜ?#W}Hٺ&zrWcwZVQEbҤIL4O\pa^1c}|sƍw]`p@EDɓBTp}'O&LE7 Y}[m4EЄ\0'[ؖiJ9"מ�ruOwnh|ד/2M T5d-vrFVJ$;PB#,i])lP𭏅uPYO~77*c6olsVG?R~hm"l䮇r jkc 1=dȐP:R9Z6tx_K?;CI{ 7l/k_<ԾŽeiِ*h|s#ʬ2t~E?eOUQEVEAJ  n ZR*[0Kת#]/ WڗGEpݍ{r9g}zO g^J?KxA#֖yߋp<)I"7!^ Bi#)ԡX~ ?EbZ)eaA.Oҍ Ghjx͇ FHAB<:-Q(Q\lAym = Zd{6UA>7 !vHBu3HoCq\%^�OqrYrNd{8jbRsA,f]__Ä.ch0�!nh �f"~!_4RH_`6f}|u:ӑ"bT@6[u6i<u![>W6bc'Li@ + 5rġ<EƏCGK3Crc٨iMdlZW?~<rK'Nȯ~~lQְa3N1�� �IDATe_hdumVݳf*Vq|G1cFCas{樣*-VX*1bD[&@ 1bǙ4i ,�`ɒ%Ǚ<y2K, ƍGCCV!dEt/E:\h, \ -H)q];뺸JERݑZ),�!'b؀5&ȄThr'<`!ZH\׸}VXh[ue$TX%P=c^Jn3xaضҫ}[KCf;8ϽӶ/KGbWDhb45hb4&K+M|F1jGv@D@@Җlߝv=̝;gqsSSq WfCs; 6f;ycI I\v_ÒϠ)߄qT |qK6|u'o.-spAosha GW@*jSuA˭N0)}\j11%$<w4iѐf]H:Bf]oFcyK4EHˋR:dj>"q AbE-u<2]N}s,,Gu!%z\n.ZH@NJ^{ι*b m,\quKVkiMLvF2YO= XJN!2ҵԑWl ;,P]fpXHZLUEQjkqmھ{M%f%ɫ  ߓOF i>d2-_= R 8~? l޼b%Thر=ǘ b2r?i"S:lFie|ߑòwQD~*~g2KtVk9CIɚaܹ|_L#kkkyꩧs!jjj#Fd-@0c OI\]]ͼy7o\r W]uK3%,9ś6mʹ<ȣ{x73ת;M`ݼys΄СC׭[r H}QDVJsO,CZJb6ax& LڝQ2sFG:;?M 먊0 ! |ϹAa*ym# f(5 mfܹ;Ntvmn .ѝ'G׍ϗq2iPN<iO5 FFX0=ewzw#?YMje9;%G&/p+p(ґg{S@�z уK9ɼǖ|q?;(ȑ:< ӫ#$$;l=+ǪISCڑk0N|L9v ޲dVs&jE1 m$mM=lބv[N9q;tyH_JL=˃dM|Y_sBIlG>ۣ#ת,>+6S݊cm,̖ i ۥXyqxpM\UUUakZpk 6mbsuG@QM+χ}� j`_A {$R@m W((sSٳw/?G"n濬f΢_WvԡO/hz+D}q4]ɦP~ƶWCҫ Gsu7G}v�ٳ' bر=:-=ʕ+ys~1w\ |1bCMʫj/| I^Ӯ`޴r$\r%I^[l鐐mooO WG/^Z=#hmmMDbscڵ9˳w^s <x`illLϮ/ 1x 5*BBTUu5MfR[KˎK ҸC4G%3ce[gLɱu_f*$GmmƯܱV`YƇ.sܔ ;6n)e}GPU:>#~\Z6l#F:%+�{f؈(8jmGpVyORqRkΆf1|tAYC/·7#RN=(׋"P )5@%L"RA*U0QTwWso?AJ ]jGN=u"SEY!44&<V| +a9#&4ܱ밃6^͏]yt0MWȰ<TU e_cgI'-3vSn%_'Hs Wbf8fk/&h+|:2kp`=<ɡ-)2|0Vu>%)/f? !mn<f{.wT wpy&˸ B6A aꝢgSP0Î b|[H)t [yiljǐt#T!t|~CSk>Շ ٣PbQDPZ 0 Llm,KIYI F\C74JJ 83ye+58mQ"t ;FA]涗1y0ϞcG1v*~% 54C#%"ɭ 5z*ҥKyꩧXhY-++O_bR^Ӊ'SOOtSNəx{ꩧ0w$w,SW`a֬Y̚5 ꫯHkYtg8:+K,I>qă.Wyp{Y 셽{sf\<Y?𭬬̸VWW;a7nȴirĈN[ҒD0璏;kr,-([au[z+ĦMxzɖ%]g.%.o( "tY}>8vDܻ@Of&!U|c4�s1\8D;ogܹ+rǾv㿙ؓ뛢?8?kx~.1;]Rg7Exi]m ;l84$;ct3ZxG:Ӈ^sϬHޔ[Wr2ު٤!c?t1[<L<{@,AACQAJ]E*@aVt ? 'L"t<Z(؉CnͥIW});!,_/Q`.]u%8<8:ŠkuKo:q:w.VWY*CjVRhBB v"o ÿ@Jfn7P4@jq"z^/im.y2"~;\rzyzɑdl 6>s8e?UZ^RZ9f ,hL^.6E_ԽdU 2ֳ8YM4)7:Xh{+-lt k~ZT5Ha(*CJ0! m۷PRQT,;Kc[ (b} t_E٫w=x}u(- D�}aw֊?j�[fŨSSYћb!9|'"0l~!0 EAl(0ʕ+sg}g}I&qwrG ?)Vbƍ^7mH"sۻT8DbŊe6L>ӧsUW1{$w՟1' ?F*7<Ŗ-[cǎ2dgO8cr"SGk׮eРAιQF ɖJFEpY۟S e d pK0L[d8OsuxՙUL"SJ$O #w2CX~qaT㌌ sOȢvK`Ѓތl+o�+*iy{V$Cgu;"*=˛kᲩ]gsƟ @ɱ̞'36tld#&�C3\=<cS:%f wRCS+yd1B0BCJ4iy Gh];YAd&56G e!GN/fʇ]K';%_±0kKݝY} Iq?!S#C4PpRf  *BuΖk6CK}!M]UU'B: =7f,MՌ<%lMMº#]%yI:C8)-=Vֻ]}oR=k2(=|2jr:Cpmk[J<v( !R8(hmGWhytr>b=x\mD"Xl榛456PS[{oh-ATUAU>~pֶ0BQ@Htv/zR H@tRC0c$K0dɱۃ* TP Q~RTPsW**o&di4Sh6!P\F;y*Z"1bA) Pf@@Cc3˕+W/|Sdl*VX_섎|̞=;ku=7Y`Av\jøq;e '՘2eJZ?G?H6Oʉ 0s.1<>bٳ | :QYY2dHe:$k(jS\\Lee%`FN5rvDʜwJWz1CߕRw6@R ^]TBٙmtC9%dʤ|qZ[[ u_i)~ҹ>f~7w6=`)s;䐋йRWfd+3{ykO!Ͽvp?XJv~pr` $7-<ٿ]lس5zHEڱ-z9s~ۻk?Vb7ΥںvT3nt/R G5ś1bm0F4 Zbc(Њa42~L9CBGx;ݤ_F;vT%ɧˆ|MVd#ڎkGZ[,IGx#3N#C "`mEg%YO$l#~'$ CJ/$&G*117EQ^VNII)Bf) D%Z{6hҜX&b+P&9+"lr]rE&y`H!<Sёyp^!e9wċB)I!BBsd~pv B^JT6YhjC܈>6̈k&PX,ޚuuD48 z<>ini$U.kaݜtHTTϜO ͇0 *XxڊsKPT3i-QҴ>146o^=F<A<ҫ(DaРEo%@2uͬ;9e E=IO-K.`udʋ&m;Ǩ xvnv' 'Щ׾F[[/榛n:$nI&i&tl~d&M9޺ukg#7_|_p!3gkaȑgy}&? /2{r }P(ԩn|%رcv[.( Æ K#p0uȺdm+S/xItdVKp6R_sRD ̗Bw}޼R$;d9cXe vueH6] sÃ'0kӣ* 殫 J?ΠqNzj. C.'l"tl_lE&}7?9ӣN;Id\uwlg\}_ xπnوnoX/Fq5qNdjuИ۶n|8q<7uj]zŴcrOM}1|h1=66}A0ǂT\sPhe-gǞ](ҠO%?XDya 1b=k} xʷW>^1z+h;>YGKoؾgK>_В(!ߙ[), r|.$i*< l8f庽 3{ff4}K‚BEc()fR֟O+g#G>#k+-)b‘!Y'7 f27vj GoNO\ߤ!=:-lcgWT ̞=Q]f$H`ief:e~Jr #3UB%@NP "hX -#u+SC[8b^RRWW"tCsVUUu%,@b2�EA:R*=  A2[o^olO:a#o^][YbxR"4Q1. ެDy/AZ$klZa}AaÆ ;sr_ߕ8r{{R3f0`ц8qbιySLa\{α\;w.W^yeKGؤ%߁]u9s <x`BH{JqqI9rd;}Hv^gMvRov_/ʑsE:~*3.SH˪ߜdH!mjO޶"띡G ]x7V݃Q‰_N7͏E}=4xxs1ћP4 ~|Y6)Ot'3J[%4m_]ĬǺR ;?DQS쫧f'P>=AJvn^NcM=itkVAPY-0``_Pݽ?]Ô'SQ1^>b~>mU\tQ wJ;XmB|;,ZFX-]0%ߢO0uI$ūL'e�yR6<>+,ulzI yx<xLl0DI"@z2vDu3OUM'6Ұ j;FYbz0{Ȑbm^Hhv6EJSypol޳^? mKœnrLec6먊+I}-S0$ CqBvoS5& UN<)RRS@YY/K|c�7k0!Fcfh⭟ngj@CU5GjN(ao(J2O+?@!0{+o i2fu@h*(j/DHZ�<9$lѶ3,нl&O(`֝ 3f`immL8ӧw)I[XX_$R֍sf;0qn'0}tq ܹs5kV7IG/$mFcc#{@ G5k�3I:}߄ r?{F.JQ5_4 >uhZz0!@=a2UMս0/10C n *yH!E4 Y_-ߟ'^Cĉ=S&"@c{/ww@A8s9it>]ϧy@ =t.l&{MgmۗԂi# G*|e|i8 VO>}dBjyhANAy7U5qIeپ}`=6 N; =r*g{czwia+oP38yx9� ۗ]xú'd>ĵ8DT Lu2j»4W1Srv^zn�¼&�� �IDATN9gq 7÷Yb^C?EuLFdq8|^aѻp$LϡSK<Ǯm_ *N<JFD]S* 1ql_cxb~Ɯuَdz>׍߫j 㴙GcK޾})o<}>9YQe(#s5 OIԺg"X[P@z.C͹$mYʿ_z?S oLx E\Zx_o-J9i5Ww|iUiWhrD향 i=PЃ!#28v|UgnkYiѰ,03Ι, mR^_V =siZϢ5[i'B>f:*rZWoZW_x'_)ÒaɽΒ;{tm#<3'D > l�B0ӹҳu اx<·>޽ ʋ6meE>9=04uÈ{`jm:o:}{ MZrI3( %Yj5w ~?k8~~*zEhkL&N:=MN- ۑt*AUXr ˗-Π4Tȷfj?d'e�^#3<3Ǚuܢdֿs]f=u/&e;3zVkkkLE5];qՙn&$M 3iٵ<( uV}P%ud$wNpwp#eŝ:ʓj6(3/c.GLkk\t@qɝ63D.S>ND5kDaAƵ(M 4?&40S}܀EG �%%hN0DJI$!1))**H$BII* H!109TsIUӻi=|Ng뼢 :1 4|dģ1j6JîjXb~"5 ]DĈBg *oZWZZCDF CsĈ#}Bϟ… Yl_^^رc1cmmm 0� cO>9jȐ!+k,^9>x`N9ƍǨQz wqgy&vbڵIƏO>ٍΌ)Sxyسg_K,I"G͌3:-wy|^QQQd~fO;4l1CRy\^G  **r ['mIIIZ}c„ }O;_\\_8RVJtد_?Ma[{Hʧ>Is}B^UWi Da \ŐP))b+u�.#3+س\{ѳP1ڲYx#ݷ˜k?\W@}'|5ig/QbY Lwi\�. F=HGmn']w.f͓=-\v2Tn7\s=ړOKr&ҶλВi oL{%]_缻߿_lB5w ]-7Gˉ_w~jmP6ڔ_ _wv=e eyKkmEop_sG'\.=ഴq@扛J].^rkұwFR>M+Oy6k˷%g>ƴbsy1]'G3I~~_Mp僋IŃ. I&pe-Sz'Toryq}t?}&~?wfJqBk<q\@zl<M{p,C~qؤcN!Oܪ+e.zoQ89o=˵'{c溺8L[nWKƑ/bd</~p;3]NWϟg9<U]mv}cFPQ‡,cԈA=,z$ !d`U;{jkV F"jk63}єD׻X]NFϢ0ZΖM[Y+jk6]&oMo%%Aˮ[AI&Q\oSV/fԩIo@<){�ȭ}GH5>&N[0*YD^ލnTt~( 4P3d!i!MX WK 9쐱n$ /$m?a(- mXM8} 1OoI1k0ҧFRT3oYl'YkE0tYaŽd6 Ik(]0 BֽĢ:`1A4%G4jeOm V#A$', SPSbNb! P} h855u< xl?ԑO#ϏB:>{8�8@QVHcuU(r;  zP Q@BM2tp|!EBbH̀Vb` 7o>,7nҝ>}zFұ0e޽sLvDo pLݟ12eJR=[nM2^p!7p?^#7a#8"'$۳gOOoڎ+..*gG&֨8!gl؆` 0X!>x<N<0 #V"+j[ŦMaaiPn@=`)H ͓,K!ɠXFH/ݪX IsW'Gj.P$-1sݼ?:WrDvPrH|_2v.{y7? 9di{^]Gr^=0G]eړǿi['{I?R > eL �ˀ^UP&YcOry3Unt{t'n_9SH=B[ o[ ';NI4 !.$6e�yU~8*O^XG]wj6'Poi0ןLga#y{Z6[B3|5QA(w_yN=䁿T~{q'Hl&prt?'r1GS{-V[kA˞w S~ʶcT1x ~*q A/Y�"®579{=q!Hj6~ěju:"cLs5K;s5>&οV&ha©\LFV,~ N<}v=N/L_O4nٟӯ縆sL֩c̥?i{xGsuZWאS\rsUp2ܙ#݋`}e;7To\?{Ӭukm7)FS,Us'QU5(Pm.GU>ـ؇j V#B@mg>(+%aXF!N=xǓDwУ@hc-0scǧ[PY Li|KԦȧ|%.||I;Qh%ߎm[~ܐEF:لCz9V`)= jU* X2J~5ZfJg]T'GpB.k( Č9l楎Kjz[b6LHd/׍tRj&{J'yCYЎ&+SАaR:W?)4R_s:X2DH:)td3z&d)dqca=ܽ^8,=-ۨ7E4"~ݏ R@}S=k֭c|TV#4M I(RbyҠ=*TCEDcQĭ(k}HCb|~?(M0!TxMl{V*Hѯw.2G3h:{%}ˆ3h "&u4GD!mKb!(JmBAۨe}lyN2dկ˝cyR6<<3żS="+UaSUxw�hmmEQcyjL&fWluBਃJBgFH1  dM? v$#tK %E5n6V1pCN+Bq,m$fn^M)⾟]c~EsLkn_9mYxݓl' w4;&c'Т `Pu+iܙyţ_< ?8f߹3~O M!3ɣ].>t9'$sjdR:w!<n}0H7fε|߰hW �O &tş%1?b[wpkbΜe ?\dzoU2Ơ7?㹕 '512RӣOW=u#s`Njt}bі{8 {sƶpUU宾6fr91?drLvpCƎOЕ[g731`@_Bt7݄;m>MqVMgNG8_'t.ĎN9u.ua/{Ŝ4,^{_W8ƿkxՍdc7n䒉~uȕ<c|1 x{]G9ޟy/ $MOkx 5{ 0KvEFn>uvbxp|Jg%} >pm|%zkuxXSM}Ca lBU W(*\ q!uH TJ|( B�ZAL~|1xH$RMR6WCj[2%ߠAnKV* TJS(  4 eo_Iz|;O##TwCEHt;U) -#\a\}R${cA"q>-So^jrQꯝG T U"t;D*̨֭JհSۀٰ oa` 1Ͷ %oc.& ONw>IK!r2?"�j5#ǗzCJP~A<\mə@Ο ļ1Z �LN2yLyk&M]qgX<[ZjR ){5Y:d�NXt+,t Ѣ!Hڛh6P۲eWlrJIy)xښݨB!zIqq1E}~Vb`Pa!ӦM#  ;!w_w]J,%<;HL xM '% bHS|Dd>#Y̾F93!qbZ T"|>sT */0wJ?i G)2eJRc0IG}(<ȣ(֛m^LN(JG޽{I6g=<>ү>.,8`,!&u$gZJHvJ#Ak;:ᄌdQEtk~!J'ܿ~ c-nf}[O.e<D={6o<vw<f;!(d[s`V2ka7Bs[K,|gGϱX(pWm}^͟: r.L"~o}>Eo4AZ7囼Ny5?sjX09ͭL8Mƚ]6Ϟs{g}^6T;k`F*wgLIٖt|5~IB ~kd,?2<frn!}5t_6ȅ5QP1UrM&5-Lu1zZ<~?w&zrTM95>\AirgR\ l_3$]ָY~i}lII}Wy<={;x8=͔[)T(+  q-N,Cb۳!Z)R6Q6RU~Ǵ#t ]3 Az )-h(^| 6So@i4D0!_kBR_+žV& ,W^^ex)Y>?/߃Ry48a]W 0u;#Y?ƛOZj}zr0Id3;D|ͤu*\ <UUuth^@\'<~R\z ̾oYw8޶&/^>K wH-VRY`}0h T( Ceۮ|m{vQڧޕ(2F  D"1MQ}*BHZZ(.CEc[ Zظ~E5GO=ŧK "@ULl4A&2̅Z @ T) X,@QĈ@5T$V1@ӭ5D Ha[uz#aP>#Cf͢{9v2`NQ#<8\)r4 Vx4MCA04sm+Eiɶl$1NRaz ekpB9D ',ʓBPX,f]Z#\3pSݹ= XVZ&pY!Χ]e4'yvsʼKǤ Nܣ~<OjD~=X(r1_pӳlh;g5?zH8/'4nޗ;|_�KhnJù '^ɗsnۧ@"583C\wwљ<}#yu,&x T 0={آX έh~ 9Kj԰|M={fE!SqEy).gi)W s@~q 7Kj:3HV~$u3 "V+гDew[=mZ+~%h:VW$$IĈPջz6[豧`e|eǚE,WOaE9ȧYjB=m[.E S-gԸTE[.)H>g8pX<Gȗq?4ZZZim 3@7ѵ4U0@:hA$9QB:\Db.N@X*I46QDNHUH شꕔVҌCC1i`1('K̞@*9y<6;Q=֝IV{dtNqit׭Ydsv( SzE8K:w'd=oھ֚61rTQ$==У*>O,"Q454PTXH,37�Qhaq *Lii)p ? 'V݊/"*7ӎBiY 3OؓW$VA!3̐`hE h`5D{�nzk.=&P݅tΛ $ K44/<1g̙YGyr=&}>89dmT\T%ʭuE&&}/] cI#LYgEeIO>y4+r!xqңSz]5b}^`֬,pblplrX*[Y߭#3SRJLfVϚ% }مզc0ϥͿKiV?-OX#'@BFmJɈѽ3+jG�"~་HL'1!q]v7V�� �IDAT_7)ՙ3rִQ׸l94,Lm{=WsLq1i_�o?4Ik12}8zd̕}xyY]x=oZO,6,^QRjiY%wS}r?1N|M9šwe}^%C s9_&eYk$HtdlZ&CFNF~X7}MK8jT jf˶dAI]̪ a#'erdx ǧ7!VOYyK{o$HБolZCFgͦ|G*0BK|eYєo |$0!֐Y<)D".^^M[' C.3Z-/,/5Y �FQ}2Kx}GHjA Æ a°y5QKq?�D@Q\:i(Gt)y&ǕKG2c;)<79J餯&0HQU(X^ 1h}04;i,3)iE-uNjO#;h[GmO-P\]_D:֔3ͩsk[e![`~Xgbo%WȔX`J>8 F_+U}inَVA Ckg5nC6(%!Z %G W@X,JA(D<k:*@H<{R__O}>B>_ٱ5=fZL:v"U[ @J0TE gqBA`ՎׂBH*RRG(O%hn'jEc膎�ĮP>gY{f3*n] �T]`=4&'d#<#d}u)LDQTUEQ~?RJt]O 8w[YLRlJqWQݰ R\䬰æiw2uҭԄBoX1$f@C Wk4ZZŽzbEsgnfdxF7:lXrzk\4VL,aT}L;[P'B=vP+m>D}SSm鄮~iy2٨=Ѯӎ֥u Fɒ=.�ov0!C.`ݭpcx{<ޞ?}=?iSh |qCsKc3_PYdiXۙ.L��[I5S=Bo^=Vn1(V Bu5k7v0et8k}ĢgVjD Tq%[lREډ5Ŕ"-hZ3 ӡ|U] Jőjk i%``%G>b3h"s|<U1ydˮXVb|'/ԿX>k_s:foSwtð R[b;*[ϭl=y~^[7|Ȟ1|ḁ9<TW>h |1ǜz\t!  -Rͦ+tI}tn{MD2Ҋd1)_P &:Iltew8^bl4Vؚ1(H7tIKЍtlsR=`reZfrxםd8k)H9\s.YvMvk=kuðᥭ'ÓDZB#}MfcE݅Cm**4"RRB\π1b-avUbsh'-TTTb4脫[%*{R)! , !: |Ҳ"֮cҧg?ɟ}-ZP ?1ݠ'OrT:aX ҐR,saZ@5NSs={iБe CQLf$[Hiz i-`HdaqC'n6ƕA#<#<k$)'/ f%'1:M<BS_-c&2˓֭(3y@QU3,4WСu}^")'ꓖLd봷;^mt;<cG4&wX=D[#(vvЬAOH'!F3^'+|E3fpyiTȜkc]pr^o>Wr9q$v42\wy!w 2,Å`Σu + ÜOT$5'o>bC1}+[c 7]<]! 'l 5pϧ 2>׳x~7.<1{YBWH{@9L󯿋_x:c"d=`%ww 7pON"pq=6Ws5jEl5HfZZJ{ѧP?NcJd+#J<.^:#4ioo0THaik`sz*l$J;hDJTGi|̈́{)gq> }Lk"bq}g:ʻ9ݕj\dY.H6J1 ׄ 䅼ĄSBɛ7B `11ccl,ɖdYJ+mys=[U}u)3ϔ3<{JI& yztuNy,m"__=ǹkÖOyx2Aex3w춿_Cmq/8򲝝 ٵװjI)WÏn֭�Td ZYO~2k3iǎeO{~vKw=3Tx*v<4ʆ<]WR7fzŠ l<ja(Pa ecRKT`9ņx\x`p,JbZnaud[G06Dl*%]y=R6jS3"z\Fۯ`\ުbeFmo0 $}8ë#fW1XxFru}_0Xd&`ڜ,*>"tOqv2NRLp`0K`jjT*E:FWQS** m,RmG1@*E*#Xגn٤ aKJ޲v0:*w (+)rS$)O$aELOU0h|QQ>Zې(*~c|6x~QG֚8-B -Ʉ3jf XOmMšōnd F) RDZ&6Rb'DkNX]I!@WeyFFl: yufc5*PWu CAOO#9xpԆ(ٓDo_\y3.ja5 _NxAJ5퍮,нCkp|K#GC<ߖ_ʳrqSPz' ~W~2eƿNg~u7]BH ͼcuXu ߷"op ޓ͹ ڶ�n}dvh7 c-]wlaH-?%;\|Yu/{O?/Ix?eܣE<: 2g\ur1xWאs^1C5ur7/]gN̓=.Z~CU\rul>NH[{W_yQ`~*]uEX3�I48𣬿 &=`|7?Lړ}4}lx{vt4َVl;.Ӈػk\||p!%Ws%X_/򵝊L3pOA6m ؾ}Ѿ%'/{ ҷtWx8dR<@DeK:k.N_Z#4+Nר;m�3yYͲTG?dy1n⸎g/c蝥={ |e 7hڪ^3v_H5-thZs-򃴉%RhrSVo]hҲ9'h)bހ8Qd"A"9f, Qo(͛`AxVHܫϷ*#lȡ<WuS%B3odktn;HN~B2~WF#ddd2E=O;8bHÛJ>^E{AR|s!2,~ųB<ؾ[7ў<AHWgI/}s]ET@G&�=|ZQ$4HljG0`b1^JHRD&%Zʓ<!Y:"QJXWh 7V(.o2 -B -B 1^\K>[6 ]洍維CgBUQ+҈¶jjf`W8BE&+6$1O Bv5 +ܨwn8y7WC@5;ܵyK.iIr\]:q_d,>"$S77n>3qkp/du8p_1qQ0mԼ_ Y@oEy"wGVgmW!'|>5ږUwYyq۾7K_a&[AE`t,!;A[w{Ǘ~_8䇹7t}xi= Eznp,?\]C^xI#ܯ9�gƷ�׼ !w={<֤8~^q֝Fyt[,M1_+ mYA-a8I1=>=NҗZ/y\D}ʤ8cU|g{۸v^ _B dcuI@<tL!u*&_/,pr+kNؖ>qCklGws gB憷q}#4gVߓT9|gqhK-,BN5 !~N';e_#Ó|8Jc2l~Xbq@`2 : fsG^\T=#k!53rR m<BRcf ;̪Z8`+B�PB`\g^leW4=?_D:-%E u*Wi؝e5k9 6)D* -o/hR\;m!}<D :۵d<^ű$g6Q())K0 tdEt�}>z)䧦),]Fҕ48Wc(d2):;:hko'LIg=q {5*of TRyE c|r?߅YUOxEʕR2XbG+yx6t OG1;wkjN?ʞn1se2Ƹ7θ7΄7Ƅ77ɴ?EQ9r$9/G1(V/2^>|bZhZ8*,Sχަ*b +vBoaYc25XezX p]n~=FEBc!dǺ G3pa8]m4>u^O.8g= /?CQW Nw+42Oj7S"(3$~[9OMe ;Di1'/],Mٻf*/^Wp>n?q}St{ohp47^6r5O{Ժo6?;>E__D>W)׶.H|0@O 7wow~\)m\|M/} !WQɯ?0{><P?ܼoGn/<Q 16!~|ݱ+_S]?j9!PS돶ANEGiSH†Е_rD@D$ u3h U[o3 Lз@c>|z]ӵʗp|%dLv > 7on|W}8~},y1)< D/?{u,͉ϣw3Y(0U7r؁s%GYtj*KHڪ!m WjbGKp*ͮ<6i`=V_wkiHy\#B}:x apPppX49DS֍ !X gͳL֬^M٫xV1?=~.\Het8. O)Q̑FlL|?'LcI._8{>; ٲ*BjգG&JB3dN7I{"M~AJG&0Z|;\!LRio@hАp\/_I[w7KxUO+iGt ^p&޳YtRPbzzR07=O+[f-@ $)<JWP@`2B(J{_f_ir4R|!GPS(( x"J"Z1^<y(UAyeP442#sG -B -A\ɉ+H^X|EYLޯkY5s]FѫEc B<BWF 0FQ 4ZY9Q'cjND"A"dZ# 1}lh;?cyr>8ΪkU̇~+{G'?Ğs_\Dn4mz ˃/x'k0s\Hnt'7Ulx皗ѶDE}r;'_ņ|qp9̻>+Cb?]WyzBN+jǯ};7m&1:4D9oȵ/}u m_K=9|gtϯu/<\qozi�yM"C~[6kWϙAwXq `Ql |cV?~]o[S.l2h>d^7t>&r{n 6 >K3$^ӻ;:AX`=7g<'4NGE8ǯ{;7m!1:<<cDF>C`Qlq^Ӷq[x?V}w?c)giIXå~]ȍ2<"?g.㏿LOW_KSgsTOm`b{/u>zїGS^NnuC *g\|- EtvXg SGH�iIIpӐ d~nM®ɍ Z'"<d2g>Z@$g\|- %t2&z)ȗ@" L Sq!`ݙ۟ +9)גʼnZ ָ!~sI5]vqbDW1fMr1c`1~uHuFaF3Qe!82I{%vϴ:`{OY֭PGŅ@V e(:OIK66p#4 jjlb ݴ, CؘB10#Ȫ1޲"Jpj_(l1A$0]**WT=Ym R%Kd3^Az̳gǦ)N;u-O:I,VWk>`f6fk272,:)NȑY\qܳX,nc lK%ӓLNOVlv}t:EXP,TrL2EkE:Ibʫ6r>|h/UW]ʕ}teII0b~+NIg|LB% @h(aId2!29-<Uf8a.7o�� �IDAT?Dz!H!%R\EHk-b4h1+WZhZxNշQ>xHnABꕓ:cָBAycִb CCkXe+݄@`IЋ8 ?dӖ3F} 2ЖZpn({FFU?ZiFnPbGW~V>w FEjm^?eW_�`�o'yE/M_|5V{7O]͗6/g)eDO[n6ovtÿ=tFd镼3.\x'YTG7F![}߸-.bn| ̝~i׽†|nx7͘Wno6ןþ �xoa2kGxzs/?ͲKa<k>Ly!ݏ~Po.=7[[}99˚>|rxm�|': x3_Ŗol圗^3 L}O=cQŚ+-xp;fsYu^)@?/}evΣv^_]W=>]|S>{Y:>5n^|7}3[Lre g-?2ڡ' +`f8m#J$IC$$\pRՆޥ';QP-QLim\dCs7QѓYX5@ύW�'邛Ar kWC2)Ph{1^u$w<1ZyT8]SC\:S0fk$vB?嗴SGR(d+Wr+5{ā}{}ul=2zY];DhӗۻF2vOv]1ضL)ls$=rȐĆ $̺)ց=MY}XߪGI3eBBghn�l,b 74jM"Xsҍ.+Jּ({.L<ЛQoٺ]v1>1HAF(43A?51ZR ׬ap "{= 75DԨH ;VQV,k5WUcj-&>U7&̀'{{*Np(waCy`ۇvel~d3wo wy]_Lxy$L '֊,==RItuuɤ)JL_|~}<m'qz#/~6@^z*E| &G(LMQ*S/+U%JJB|rB3bzcsHMǣ)zS>J[.ST׺ךPZp#(qbB -B '?d/njk 5B#NUl>c!ym*U9<. G5 T&Ƒ{JiLXb.8#%`UઢWX"O^�_=ߒ.sBH0Lzwж]3m, hoHt$bNy羛np+ۏ^FGԳw9ߚtiMMznw_woSF6o0[v3#;֦;~�Oç<VS4љ l'da&gי߭׽o})y Ͽ#cw_qT%xV'_m~׽gyr]Dg{G97\`$po|Oty[erǿWΧZy4K.~ w\P~8yN߸KS<ɳdž߹{.٫ fMOq̹^F7{~0O,g>cgSۃg}ƳOu͚x[noeM 3\g}#W4觿{]ުdP'SFnYۛ>t.l|o. ?Z2k xGF Yr6|LKVJPী%?M HCג^p?!bvs9Wtz ,m A:}K.9B )pb &I�I{I.G򥲉(Ͷl3s=4=:9V'Xf7ow1V4 [yy5Ok XJ|'_+E8=M?),~73u˯}]vF^x+_JzsFi|5{fqa4u� \XUQu@㠕FjЦFvlԓAHSWD& nYB@сk͟=m##H?F9_ 0n=DPw B1"¿9d56ت/@K+ҚqGP&!&cޚ"SKS(Ơevla! Hn F|:$_{Ǎϛ $m7elaڣQ6Q]'9 ؽ)MynvXZ5UaX7mJ)|SNYT5{SF`" 8[/T<Q7BlѴdȨM"e4DI<VH7#xm;|NY;ȅΩ+n2Cw�mm]$}==qR ALmBMIgI& RJHN E!]#2 Rù!yˍ{2S<PD!HvIDZ 8n"Nz؆^ءՇ@:d'\:~-B -cϾ �7{F6 4R@}^(xA&T%K}cPR SM` hpQYJe*�.&PyUtm֤R(=aU%F2o-/ *8>.Ln2:!.<tОqEe!Ftd]W|<߀}[8)b.L'ͮQ3C}kg1;rCMnLg;=37w}b"/~vlQ98^ BnҠAn^ \wޱ=fI~}4tf%oڮ=|[~qiL<DcƧ +mm&7Q7>ƧpY>gqѺ1<4K:n]kHr,ut>ockfmߑWXwr&gӹ=;JQ;&Yyکp޴eC 溳add;<# NGe?? MvC4x'1w ż<7n<tg;.xH{g#^D%4n}'I#N18{r9B[:~}N6tgio˒ɱQv<o]zO7hpͷC5~oNv-傍Y5?C(n'A|aÜ̑Ek/x{#B˸dy=w6ٗ^ƾw?)[rL׽`#Mk\?Gae/8 ]<c 2 HZQXӯ15Ifyf�X'Qs92c4D9.z(zÚP檾__xn8&Cpm8ҷ̭zڐ̍%>j;0'c)b>jKn+M$*BuQA&9^cVڷxB^@[άa<,=VBP 1$"l@lf!g\߯~Z>.s])%D6aZF#>dq0Ŵ?ԁ6vs7 \Cͥԕ <>Q>IHNr]IqH\ZD$R )%]m<8N.ϺL;=tvf($_'6.AsZ3=-A6NG4d;qYAPڠM%~} OӘL)R$mX=T k}!{RT�#pXmDmZhZhƘ?5oRՄ/ҡ&wį*BXE,ww\.3<21u\ בiBѤR.g;իV[ٽoB{�y( 38L8rBE[\q<mN;W)q47š֩qq,W3=${f!sL#H3H<wn󕾙^xd,a0s$Xp{Os|Gaf|s2ݬZ=L{7YWy R{rfj~e γ ˏY<&;׺:5dvݿy۶,6-_IgBYӞ`%}M[g=T8oPH&*i@U~ "AL[U>?OёCL%I%<l{kʷ=t!!hO%m+bԲiyama'E']]cObIJlN~ys}V]$^~!GJWC<{2y_z SW.J&ndzx9<?w8W[Ïl ~D Cl 8m'Ґ0<cl$!@Zn +RZPPpQC6B<Sx6fVuV2)X"rqf͘zM N(ZIⲈ#ݹ|#fG6&%meԥ�2Q8XYZC-į'c^r3p3_`Dek/�sٹVKlyk& 8\J)Xͤdl|v$N[ےvH4 nc'd|U,3.2000_ 4KKb4Sxƛ*ӳB!R$R<OHT*eT|%=<8Ei=2;*Wp(+N&$YE9[wsk׮Y\)"%dD*- ݿUR*r%NYQhоF7D �"ioC! 9hB -B -%R^wq'~sFIH\:Ɔ/HPh!$vzrxt r(Tj"`�R:1Zh0! V '>TB}l/kYX[8o@ af8shl;o �$L: ĐktK.]*hQ&�6r~FA2K HBPsenhWq@ʗJ£|tfJ\r@ _% ,�YxE+,^xӰX>aqGw2Ug08'y"ͺ.`_[ D XUǁ;zHwT/]>wm�ں WBELv2P$ݬZ1GHpV6*up7Crp (U%3HHQdm''jB6r3d׭>p4U6tak=*CbP%C™$ 5h ]DMԆ.6u&xBQ0h,U8Shfǫ"r6rLΫc,k` #o--ڷ2mHbBT#uF# =cEdm\'=!i|0G&p) ma'%H4;4vwJ ֋Uib*He߷I<dIWC n8~Lґh5ET LL"w�?m cc>GC/> 87xayx0U(3^Rq ohBa<GS|tR bAl#&.dP -B -B% 93ǫ/9Y  =m;DZGZGfa3V`0B[3'Wex1؜AұzE7 \c l4ґ8B>Zp\-q1_&UM1qFg"j~iH=ɿupJ>ƋNh-,t YK:Nl1чI.(MJlbefRm%ܔD)Oep[KNfν*'&LUYc!M"=N:�Eg#[.ͪ%ܴokY yk,-FN?2Y5}㱈:F''Njna L휾* BX|}+ۊQ:H sgGK/`Cadi`<gˮ" 0mY04ݎ`} NmCl9<¾r�iRPF5(|q>98){DVYsd\5^ouQbHTS󽖄Q;6Ye5{�m&1[.|tmԫvQ-^ހ865JBb3j(]cQ!mߘ_8O!m_ .†B<=*:~X'd;iG{hv"0#7}^ \/c2P iD$|RɆ;֒T"C"`P@+?pgT*|MOU,R(<hI2+|ER!+"d8v`avI$.J6g>tq +)>va:'8` F(dI/2tx ]Ɣ؉}QN,!0Bٙ.%n,#B -B -</޸[,s׀*gY}\ޯBJǾGF+DaöK)RcLiګDpHb ZJ+X[#J1$7aǸʡ\P?BѠ4BHk x7Y@K}9'21ƖyOzg=BbֳH/~pyb񤙗g1v+ I#MEwh0C)/vs==缆sh,q]aDa\Lyf1B.A e;) dԠb||R^2uox >yv7权麜%|ㅙ"cwR*dĢvB;g''Njv;]` v�{mmXݫN!~ b݋Wj Gz)^ O=v<H?d$uCY;>tq##]ޥ<eu(IyťmՆܤ5ľFg<<j.РD 󼊰=1"S~m>טvHB6hY3pOrD,|G2ht-C OJkM A Fx6z I1R >Rl o�پ08EiJP(UH{*+dT*+],jhSR`4KQ888Aw[�īvm]]tv۷GyMr`d|Q$qN"EE)R4C(5ӥF!Q Z e�� �IDATO e5`L)l1ÇVװn@X+zsx/ G:t'21O -B -8Z. MظRD#EMjf8ֵQ&j0ijmȔ͑!\06 RtJ㺮CJm ED^ I U\LU0%4_.V*;< `?;9D rTt{ΊM.S6"ZGy 1lx*&|#w= '`nAXv=~&F﴿gҽ|Vѽi<[CG;1>n%J0JH/{wo&MٓD7cc _W?s`׵<VGf4ihJcL//zD:k35�<0B}֊cXW=A]I <#6J3]Dg<k`IjJF31=Qy<GH?쀳@f{G{=Ľd fRs4'e1#FcUF7bΪӑEtt;1@#kf1Y4UK!2m Bp_VT;M'8K2J)%c%rw mA"h0*|=Ыµd|]L gi%#ެRha7>~J^>7IGG7e~lX,JH%J&x`.{ȦJIT& JKDW(K/Mw~\'AG[#(0|dd&CRkEw݋XL;KwS2ҲhT ءz}Fh�G:H)R=C)PA;L%=-B -IF/'Gi6"^uՒچ#{u*RFyOBWM8!>RJ#|ﬓ1A[3P L+|tb|p95AC0`1 +/~f'H;ue󊸙ǭ9^?N胛g|Fc#Lm:cv,+O�"l1M:B۾G[ PBO%]Œ~mеR$mwvE*aWwāwM1"Cұju;#pj+4JU yEWl{;m=t/;JR,m]tvuΤYU|773r.F%t˯{|m8~.m5%U!n;;m3ޕiJ|ؾ\IUHN4{z϶Et=rV9\~ ˎ!<Pv࠮ݻؿo\DNEE^’ZA;ԓa '/LO_g%bص5,Y"0#.e#OaWkcnKDd:5(qPC¸zlHTU㿮W[bTlEQu|c0MK%GF=%4c妌 E.tIg2HJ"Œ Ak=p3"kP争c 욶UhF@PBi{"&)4kP#ՙ@+R2N0)%r Ji@e7Ӥn_k&'Yz�')mi/(?#\dMO~2_y۷t6Hzzip tvvS)Wq!NrӹO%˟2 "Efƒ&Lfznpr&{-+Gb>#Xݶn -B -\ lVG^'{{ז$ [~@abu�fqkj@ $ M橕#Htr Pw@B"*a1"Hh+L+ 8E$ln{?Onpv\8kB쾴R'cMt6)%We{#J#<UkDJI[[L<7H$R)k^{&x ~}7I+W3jMNPPfmU<UedҔ{<?%JE^{&W/9<<DL{/B f#i%e cOPRl߲0 kr<a;x%A~|Jn֯]`o$ Q>=~Ty5Nnݫ_"JMsl;P] GU,g( U n BqWQؗ 0$#I(H@AI4 ;O9ұaչЭ3yͯ"ݹ!EzdGW6bHmٖ-xۆ0mHccBDhUWN]Tm#"u^3aHPBkmSpJKZ#v0;�G&ĚSO9˖ba8JH W T#18['?!۱YV]!+F6[04J! ɴ@ktސ6pR-H$M$~5Fe*:J7ܿWTH'|\Iuҷ /9щqƋ-0Pn׿7zj ##Ȥ3teB0U`ț"NAؽ>cPʠQ8SAH80`a A!riK [l -B -PEMM0z$޵qD1:ޥuXǛjXq9^}kT1 �e-5&P1dY4 RVԼ VADJ ZWڦ0{q4G'QB /sSpwBOxfkoئ@<uRJ2 L?.^ϒ%\vm8.|.x,Kx" I>>Zq@Madu:˓sQnԟ2AzEqm['6K){ u|ёY%X+U6:9{m];$Vpz}opDR ZNSdD*녨1mYqq]zɆM/ Ƚ !P[fyij (4{6YD&NG4(3.~,"8ѰǛJWI@,11rg. :}ѼtHҤ)ikaSIV\Y΢aW!!,i>!jj=dnf̓cxweY\D (/0AF;\hQERT` H$\†sAeww}RYյw.ukkIiF#Ԭf<'4#aC@'G!; 0!0!<�0hak[ ꮽ*3+{sߒYՕYoܳs-4&vB]y٧ï}sp9zQz ,/zUaP{_ŏ؏o; UeypFp=q(o ȮѲ{O`G ONi^%9c9cc'M7KѕcL"V63،{#jG E(ΏQĀBx_c8t!~Ξ;&g U&$1˘Eݮ`ϝ7'`Na5^f<m֍^lz{!:}Gzivk Ź/ĕăr3z,/X ctXK7Ïh<~7p|,4kFO䋫>X9t;oykH1 ,\h":{| NI^e q X_QGrQB]`@ iFW C7?BvhIl`t#ŎK6d}:ޣ"ݣ7緖(l伫>T4D;CƎ0 ˜GXL%^l.uJLnJ;BdNXc#^^5^駟N/c8X\]wcG16̎ǎqyzI+=ل>GOQ-vΦƷwH}yBm<p՜=}6[>�"w8udP. ղQ \W;!( \X?ҁcuc^. ΀> 0}ϫ9tp?gϝf*z#Ib'=LeP;[cHB|c [w<.ro"j=68 s1s̱!"DZrL e>ms:NWqqbgZ +, PF&Ȇƃ x# Mh1 NZj-=`Od/:j)>h=[ 5pH.x9MjM]re'^ΫR[ku7X=׶9); #G|?ʸϾӘ%_)qr \RoA :VfvyfX9lxsW-R8j8g߼6Mg|7gn?vkDc*< r3bRł VWl =$2֫N!Cgc琹czr#P 9.Vl |1ג6dB"c%,ш{'"T#FtBT(D"1**:{_2UKO fy340\;Z$̂$镜P '^1ؘ@! A-&2V(m'@u^?<!k//sx8p>2a!=, as7zZ(FQ`L(1Ը ֛/pWVG\ڨ9~IN; 6?-8G=MCAseñ4Xa_=caQ~q( j0oBGQwK oWy0޺s1s1/-:֘f Mҵu3 vFDz4UUᜲopy677ⵙ4N&e6~,/m=RAld 8s q.d2eqyzz9k(I 1HeczMtN1ǭz9b_Ci^'.y]$/=V]Оk7/<0=8!`Z  wMN3>f/QF!fqE{nYe$Lv<n'#Ndجs[u6iٹ"oϬmċ9}WuLg)&F*7|$b,!-/]` OǛPYP61_)Y&F"i.u(P"'܋/zb27n"6s<pCbpYN:h8 s+9銦Ojw;sHb'l&<! co{4x.QX|p|fS8ت5}E fQzUK671R4   `+hpucON3X--{#8v(},` :s "1 p?*ta[WX2&&8n'HbY[oU094<dwK4s1s1-ffoxMCƄ%.{)2pn9}4+իԴH(Z+,hA5} 7qymo|Ο;CضElln0`N175wocoqC'Rn!jNhIٗ^y-casT;a@=P6Nb!-ܱ[#]s;&zF?By+lID.SUcp.JU:ګ,DNGo{&TQn5tgܮT^":ٮ.@[jб:hn΀> _zϟb "+zc3O? sljۑrxgF"ޥl:36UqZ;YnNpKo>&nw0A b85aLkP30 &-!VǣF =&YB`@= g H73Ϝ䡿,aX`8DZ0~գx.Qzv!LTC*qǥ 6 8ً8YU(y7:_<^�c76s1s0k빵 P;j)m:cU i^nm2/MP\l)vY޶b7 ڱ}E(ɯ- \XЀ1&uNkW;Μz!=(|ɎDR8s"uGoșN״I9ػz55ub-_fN(ms6;MhH{#tʻ~~ /qS =Ű1ENȭ,:%U;RuL#Ayܒ?-5~4KN%t#8]0}c15r{?sQbDS7.輶u{X˫﹇ሧ~]Et0$iNM#h9FS;IW[3gm·4#Q0cC:)ц4>z.?ڥK<W] E XCdz*t :ڶrcQq{xvQ}C ӣ+,xtC]ԛ쯖q\Lvq`LqFV+: ZpK>L<wn/}}Nf + 4#RէoԵÛT Ӽ0|ic𦢪uK*.zZ 5H.,*PūEQy`x}ֱ+<tktcpvޱ9ҟ^㏯R9c9 r+26lv)w nSnN \p /FJ;哻_ӄ] +ieZ⋼%(Nh4hPU'O/~ ._\Wo_eNI95;fool0~I/ǹmae~~ /q G4e @C05Q̆(:em8z52–y_S>ry DƖVH&_^j^L(_OU<( daL3.o}9xI9"r6Je;4<yV'/hfOz1.q{wGeu}#-M˗PoE*Nu]ꭦCTA2p! ;>r|\E`KaE7 )z Fi91WEPo j5{5;K(@s)>w<p&Qϟ=MSk^FNqTpӬu=| Vbn托&^\.7X[~ ~o؏<rmGX}.E9A'7s1ǍFm)DL!l]dscwS{0(ysƄ=UCkJ)6Ql֧o-}] ԸGSB.dDֲʷ-Ę`ܹ(wu(cAsa6[zCԁ{)F{EWc}7 {z[[{~㕈/=�V}ًɞAltD�3qhCώH)g 9RZ)o!�i�� �IDATZm;Tp:%1fp㿩*n*XP:{d"O^;EFһk?-F J?z%bYA:j {9XQWW,~ &jl c\p E0!dW0mh л=4<ZCKߥ7 :nx˒<qԇ_bXup/şs 6>Mܪ(!t#,|h&onps軹< ͺ [aFYg0K/ 8 uX4X hEг͍S kp.( WN~ӣ<{ Ǟ䅳8~^Nx׾4˞'.<η_" Tҧ2}#%XU6D|{sR$5b0T=Gý cx G򕋇yA<_?;839S<O}y><ls1+jXݬ-9 lÚ7d֕E}3 Ga+Isʯ>e 2j\СɂzG„bLigfD|?fqsv9^)蒳]-;\u;t\i4JnX[n~+n~ 㥅W勏?@@& J rl2q#fSv(Hdլ{^-+KbtZHY2\D٘7IZA^#4OTM䙬-IF ;QXʠ"8% Kև +cU;[Bv-Him1% 2tBG@@�'*!2<*uأ G/!l\ !>c0é6hT~;:nBvOiUC ū/ Vk>zJP_AȢ`,~#9I0xh@G~u�^"NgYs9{yrk_-so 3e>0H9j8o ,*L+V<kō$ rE]lH cH˂"B`ł?zCݔ1MMKH>~7o8~/K?rͫ<rͮs1{ „0'^f)yT#."ᙽgK[k ٠xUYf_r4$2h͚H[߄"!ZK8DTr??\.$sP*3vY1 \2=:F_G7& #qxes.>ŋ7>˖1_gaߍS%7[yκ,w\Ӳw^yyVwhz${mb ^/H(gVT!fzDꨇ0BR\nbJ)搝 Gy M^0}g!Y_𜅵Y3lAB/&~9C$A2SO<\+E|n!d~ď~uWdujA0<0W Wׯ7͕KϤɢX1x<Xͅs38C:HG,>NHc>ų֞#78<w<Ypĺ2;Tm55b-l q%(l\Ub ̛3#^<ʟ_}d偝zl ;w~߃8q[~ �~W%d:=|r-}fZmG.ݨs1{6ؓ_^vVvSGE,S3  yIGuڞP:yZ uӘ`+<-ŏY)+N=~' W˼霈c9Z]dlpb{Mٹi{+;պ1a8n#۳s !_b@AE"M< [C`u,@J$tEBŴ\[9k2hAdMѹmtynEna)1�8\U 2R2yNEBc1!i6\4ZT1A&55mnF9nGWGrTWE.:! ^vC-;#)[xyn)Bx{?8Bp0Q_4T`3p`EՁS5\CoAN(M9dy)X39.GYY.4,pe18o`L?XxBi1c U}77ɆcWݸe~7B'>}伭2yfuo\唘ÿ>y2x~։]k͋6kS ̇OO>,>7c}秦w_<\WyZ3WάϾs1/vVg$Z*5+chd%j}\80n:2#)x c1!TҽIk,`bĖqNx_h\ٳk_B.c">sܢ!y͸D (켯_}Rjn][9昣 UsfuF= dlB2�N_cDlv\$,RqoF ;,씔1,]kVf3~L>T&QӀlykfJXch0"X5R\CxF%F[\:W[[CGiA^FS˘X I ?~yX9[C4wht?+`M:LpwM%j)f`?v[ޓc1c ѳ*b-T}]X\7<(P11j0@  '%K"I7A6lzʠs=8O _[/?×/sRWYCFRqMðQ75<x,DUաFhQ GhuΦS7m|s^y~%uJ~~~T2'2k/l+xˡՏ'�Y>ߙۓg7yo>:F?d~̾I~oY<yvse1sqs!D*2((Qp?`P >b΅Ȍ2aL<VA*5&# *%m%T0N'6ᜎǿFڀaš``P|.Zs)Wo(ʟ+O"&ck®/5aw<=8.;Kۀkp()__9PUO/<�wLTE߬%wRN`4s+ xJ+ӱH 9F D!wTI| mѲm|k2v{muɣ./aWxyAib8d+(>jgNc1?r:*0q6K&ٝ9T\_MD䜕 QQWܱA}| YG<];v7Zer_�ZpKyi <t= I{! k 0bBΙlR;GUōh6j{M%(Xl õ'^q?Ǫtƿ M$�P&xzW1!wz75F 5`le>rZ 8/TXcW-"GÛ?xGkw_~TO3LyBH7ƻ[`y`'~Kޢ -L@u~%>c ɓWxW3ZfuĿ}R>_9]9~~Nw.?A|_ql pt_<q_i>y o8?|kgz1s̱76L =HU$i$H5QL [6#L{}p660RVQ`j^ A` a0Y8e`rs͓E< b7Ú``lέ3EtUp_x۹c9\+Zɮ9vׄiO]*ewU쬟_Qٻ݁܃Ō z1h}Diԓx"9VȊ.F692Nl20n+Igj&=>^N7Tc8(DB3I6eM|KruT M~5)k%&ʔ24ﻆĊw6aDRd/N+HApڎ/QM;HP($t~ES!]-\z p4 Ej Mꔱkp&Eac>/(F]H! wmBHe>^㵘N#IŃ:n9B?gӟ[-VFЁZa8j591\' xWtlmcqy0'X[m:P- ل4]4NZ<9|Gq18xU(YUF@)4ƠH7ɱQ%>xa6~g3Oa|>:F|܈#|[&߃XLLy~OK9nWTVf>mZX|bჟ? ީLƎt%ճ'/ڙd|w~\~ǙO>n+{9cibĂ]5n~Lk>p6MP]GP& !Gl21 $k)ᇶ#'CW5d o<&{dpD|K_g?GZd9;s\+:{ {pq}ģwp}պTN}g7 zJncޙhxO_s[]}O:Y7p}d[d|GW&�}OK~f€G3 0_g^+-_5E ِ{l\5qC?$Vngzm(̿@sv6[5gAǽ S;�hN!jǫjەD6LX#,1V6H7WW:-IȾfG'>{sAŨOWQƂ3 Zdd]|2ϞU05 }80CK -X{֦S ')ޡ^lA\RIXg$ڰ;$I1|ட5Qóc$n>CCo?§#y&6,{^m+b~'W;<+sF<|2<O]!sc3ۏL*{W V}mGy(}ɵ[]w,�sBv9BI_ArC-OOCijF). ?L @\f;U"$&e#0P qQI{RPʖ"W%[^Fy0kر+W,-!3>o>η}+w8Α9;'acD\9{}<iƵnNc:~:q ɢHu7x[_n"0W6gWWyc|G9z9dM?gJu+_ -߉UDZp 吴&`{H@}룇.ܲ%[ZNQ) 1Uˋ:06H4PJ]HpBN8jy68}ZdsK륬I)/%۱lm=o+b=PZid5Eŕ,yxLo$! Y>ŸS"apqAqSKeXyFfph@gIowuFh6jv+ڀmaVc %:Nj^lgL!РFWO7Hko,I'kHքc-ai"HZ*>pO:gP-C~ >;oOC3I;g78uia0-|+gsqy-В4݇o w?7Ƨ#nny\^c9#K 3"IJ< yZrtچVh$ɦW5r-nڐ Y缉$$ﴴ{I)k$KijdsBә3W}/l\X0vPnS CF`1$wga?-neDI^#Q$>U4;׷2'NJ*Hll\RXd+bB1ˑ?0} 05A 6boeIg&gJ#& 8qM1X/EZdlὟv|i\O2at"b 7{!.2C{S;JlQK٩c46!LbCD|"iF=^h4^o!*-,8~ A}`f%R"4g#QcmõCjcla4cz9j)CW^e;b!qEGXuREUY-#}oYߣ8`uUcױc+5~l" >FpZ j@4E%c""TCmqp.^C@z#[{iDA0O)AStCi.cEZS䇎躞7}m)St"cٺIW~b;OZIMq^\@z/7asT*tK`&%]X ƹx3:AlgF<F% H71uXc+q>{ G ݌BE15=Lբ+ nF$1&vR�2~>R9*q?31qD#&wԋ!^vbֆ9ptuqObཧq]S9qlEj+ҙ{T}fdBXFc$ )exIUX-5D-JM#a"B�AX@K$MnX44L{t58=}װDRPF`wQnCk .C.GY%Ŝ1 wԽ^8(>~IּϵX<K~EjY�q`iW@_y9#32{A+)LHPKbx! ' S=L,p!(M *!%_\˽-ãbPQ>m6!`eEMRZGvM~j7(i5iX<3N"M{oMfb,OʫRU=P75�x�u1Tu0'ߪ4VQ {eOK=BJ=h?ݍɠ<rǮ3V5?oqIDu["x{tg:Y) o]L'}_I{sQ }XǶm ?mn}sY[浿_ +}jȿj,(ZU*hи!(ܐgk)^U>t6 ,A5V%4~eϯvZ'B++ V TԆj� 8 !8U0b&l~~dWVa2�� �IDAT4]V<7gOmĹ\Iy3E{\^=x9U_}VتY *g3v9C+p #1 H] a] >R Jm". ; `P$5+s)$j"J 8~1;#A-] GVߟ؆ 8KKEP9tΕM֠ZY 8ԓ2KaW+\~[A!9R;Y\W;v =^hH*x­-qR6 UDɜ|=2JAb⽜T=1~mU-z<SIrnr;=WRn.6DJ%FW%4Uű*wm}<8sc;8xAA&j) ixX^CX… "ِ/3n"Mh8B ĭG&*ubO G UeڕJަnB, ,cJMSm *1A-OXއB ќ*6F#+Q~8تR on|KM %enۿGCWUec#k"U&U=D/>(t04!g upabFX\JJ=xZhȧpn3?7+tL.ṘE\T%#l$ѹ^Rإ*][ߊU{⌅/HMÅz]HZJhc0@SC�Z{jjH+/ . CC a0tWi}֘"QCyשkGnL .DON=sRk/vS> 55 dD&Z%3y(SxC hKg|680.59³#bhgj^{\|9֩>rH ]S)}>I7agB\bĚ_il5*%-U٤DN~OǁJAs3LޒT\0}?U+YOI)KkU^ ֠.1P70`TM/uH'1XU*[|bc³yp!=ׯ> p?=O2d&<V^eR>-1>ŷt{�I/*5iW&4h*N\%aPMjQB%1;*l$>&[k|ݤ\s-n&$;_zV\|9xq^UȬ`rFB' l'e%㔰W)B "%TU1ݏ9VdY 1[KrI{&6XӮfg?Z1U.> Jd&MH:pI"�1%6hkyNt10s,! w?o="!В`oT2Hd9:<(&xQlP/гxp{1eΉv3罆@WP354F,F*jFh(5= 216ŋIuLS7.c7gyr> -Xtb. xn՝bNh1sq31m[&y7JOIm^|u8DC v&8K(# QȮi֬I8 יlVDegm{`ԧ7,02viEm} ]/_ZZgmHFJxfӘ[_ɤf&%B4' U4*iKsjǎ9�JţU'>u,1d 1C \CXd_,T(Aa' Ô}\xjYa.rGM̩4Wjjspn^5=yߒSQD4 ]ʢP Hu7 .0 m+TBhwq\z|/-|T }eMP  6āJkUH/t:TC}`UDC`籶aqЧú'KO`ᄪ"8UҫpH$*k_@-MmóUDz\dmmgː]#^P/h]!84wx8&ܭ`!J{V 2ó;.^\ˀP zM@^ZT{$OV|,;x"gΜ7T0Τ xP)YA+aoMglOkS~Z'v"ɐighHP*:(j }z̲ mshtei@aWrx�= O_<Cbٟ Is%_& ITcB?xmlvKn11_oD]TZ=;9ӳ5:"I'+xK(=O4ZAHxmq.)M%_6DוZ껆SE�Şxhz~eQA}QguyDvᜫwHZ{yxP>!Ǔ[Թ*-;qL c+FOmnCީ{:ǤY` ixF26f]@G4Bkza Ti6h6UQTt#j@mk͐>4.^by5\SI@"T(<̋O"H!M5!gytmZڥ&focokB@NUABH h.E }<NS7 _{a6/~O))`wt&ci+^EZ_D赇=s͔gw2V)Nk$o$v$[tNcF$,,. 02PnhGu77,3. 2pٶ"`]w@MV-_;Us'M{zV֥xjClȰڗƥ <0/#`R%xҖװlWpf!?>JV&EslS6vJի�5!ne>ێ|.7]?+PvD[oڅKDf@C]07>l@B^80TCXIճH_aPVcWF G>(*ԝsŝ?[P5k0BC&,aUC,m7BYج1lUx7n`Dxx<G.]|6GXJ?>t~>wCᄑCp>|rmf89c9Ȋ9cP 9ZEq"i}l_C;e#$KRIIAW *T(֦⠴zֹ0d!gFj-ig'QdRȢ5ART4]h4-XKc bJn^Uäfp壟f`y u jtތWa˱}|GO d6[&s%> E縉RᒉaNvk0Hx6V&*z\`%)".F%]P֥IXvdxR,)S},\HyMLk** JkKR( -.LJ #{HI<9fa|[6pc>XprQ4EZ[a4U+<$(>$%}@65oliĐ�6#1<sk$F!Hs5T-2h kC 1%%*V%z BO3EzsbǠju8P'?KU�kWniCem4hD .ދ LA ADZ`nᮭ Ϲ{jW#^zʍFTJ i K$xdd՜;pD!U\ҔbsFuY &2q=J1eZJ;B~ӆ+O&α֞a 0aI I8\L=)i86$DBoѱ(`&k$‡&q}�+,-׿7h q[iڊDx *2<M쳼/(G j^8ooTGOY x 7ň1rBT짷|҇i.[z.Ί/S>v1jaxlp -'Y]_kS% O9t!SP@f#\)1AKg}041.jmޥKCqB #Ʒr,9}6uڏ6i+v0gB;lcxGs9}"=;b(=.}8ش{9/qYep)RD+}U0d+;]S˱gB)ܚT)//q|/`ST^,ȉ]יn?~�u>zԍc1)C۬?U,q-zvNzqSlDv(?0+,/.r;pMó>waOٺc}!eSfynDBus mϬEhε2RTDa|gne ]M뀋\qų]]M^0b0`#8>veN= C ``Ēi҈<P~ x?{^+nyBv?{/_O93::-|x!1V!WTbhj<wT}=fhw_׃>ϕUXУzQcyc&09p&]TBBfGl &h4'#G7wT <|Gķ^hyLsv#w&^}w.ɓWcUGȩ+=qߒߏ6'nO?h9c= O08% ʚ!Gha _vʴ ^QA91V_v?mۤktUqӔrKrH'ƽT8SDf !0E7s6k;{a*O?4W664Y4;'T'`Γ_O>Σ^WFŷ1) r mYIzBV V! )U쿤3%_e^@ikHeb3~UN'nn֬cwԇlabے,nBo$(#'De~m !̢?}IRRxe1B7GQ)7^4RI nsαԯXxKh|Yڝ>\B$k<^Fh 00oF1 q]Cz9F@�*4aUU,..UFS4ʅy1,JkZno ^'k mBF bzAcҵVt(:Go:kL uP,%+ѡ"b<TC5XR[GFC¹Hk0Zv 4x!b`,xtCi<Pb# WY\eq#h\@ iG1`+fXD.yFPcp^gRwc9VP{F$c<1b)pc?{l[vƜks}Zj $yp $!!<?q JLb8+rTpBJ<*B*xTl,n${o9g0ƜkԒttzj5WhY qOyŽ5d4VԮS#ɗ{^ s<[NkMkYH!+qEuP]Gh Yũ+s{'T~$^u TQA^Q =GBVa3\;ë׮r3YG%0fX2SU4r{ekC<0b>a1Ftt<|/[o~<i֤1ƨ)'Nj!ND/qi>=<4[y1[;| ͓=\ݣ1Zǜrfz:hujr2 6"yv-_eЌqz7rHJ:^ Ap,?8"LJk~1uO>mf5(J`$Bk \CB~{lhx B#kOGgcF%th,ŀh} /eHK(>fcca{E˘kD׈gٳ a$nj8 1 n<39ulΚEE{0"Kc;i;R>0g$&vf:D9L׈9U%)u.)yw{`dm%*D֑OH9s{klouUޮ k{Z,M-9{?3Ri>D-Vgãc9wq9Z:M5L}˰P}H{sWy{]*F)}+TNEŪQ�ٯqsto?+uh&sݮ@5+RDZ~͘#C3 Oԯʰe"e֫P%1;.]]\ps"kIckn]n6vxZi{{Bu2knf HPB0-ט$gW-!np,J &yH/ ,w2SrJB Cb�Q8=ʈc6X cVJf~\xj8 >pz/0~ }𡽝ǮF>/R>vWnognԿ]xh7iǦܶ? xC6>ŕrZNR�݀ˡ2}ܒ F9+B3n6.O<jVErORL2p%HD HdV˧BtPi˦Dwql6ڒVӝ}qd+B] *%;»$ z_᝻+{i qhҫ`ۼ0p3w+PK-s,m.m:0aVR#3I=\́ NO]TɕŋOn IՖ05ǯk@)LpWxCWT@:& L-f> پ(>ugGսX<㲊1o!.Õ2^*|Y Fx/ f>F^G:7N`+ung<|';w _5OqׯѝN(FkfL@|9g6 �)#fB9DUH"G!3hfa^Cˑ7ydr{V+bXҰaF`zrA2>@GM{0"b$$nbyFsўy:y<r|@{+lf<g-nݼ%5cf#y_0 02 F02YK^NfH֨3[e;L2Mi=nx0a͚%'YY1u}d>}3Gwy :HؤbבS)x@P 65<T0Nqa32ϳRs䀆}RP6Ayo;!X_xXk9?A#6RY=HMb;e#W^HpOm!g-wtV'F7vų%ʽ3.=zrpz6E"vp%]!rep P9 ,:yl�"d.#l`f*�uWq=P5{? 2/<<W\͛5tIRSG=~)#vۼ,}᠌YIBʜt/^+9bk$9Iݪ7Cn\ T7؈D,qzgDv6z^w7HԽ$K|�� �IDAT{,@'<_h">d B=tF:2A19[ COmtJdS1e{߽Hw<gR L:*7 dS4u9)<*| n|9^xPc:Z�Kk1ɏk�+-a7#5;:EK(~)_$AHyt;<䤌U6kG}*E_b;Lo)#oeuήT"�c,lVOfOiˏC լ ǛBIFz>9]yߢ-#7&]6"j镺s;{3g-jMJ=.OeV^%K|I�'?ѿi9$a% A$#f629 >)W.YjRl~.? {w!@<P[hv@3$fBrdQG:H6+<JVW_GV9_=s/rwf/Rs*???WI5o\=W5.2s[~C~j+~__﬏1]84?-崜TX\g{&85iqUÏ_}3gp ^|lhY(ږ`c-B6OZw$mE{N'EmS>("xT++y?/ѳd`yJo޼ŧ_~kn0F# M YϦ5~kh.xs`mɤ4? ?>X[2/J=030wD pA< s̹Jx朌,R`x hhG%)-dU* Qi  PO*xBlH:}3@8RCi8:H@uyg }``o(yr¬|縛-&$0#*~ߣj9CkRJ<@"[c%.DTFܕȁ83b@)ޒ"ʪ )F4O.0843'!<fZǃ~^ס)pB ծpU⊠ c C.=Cf_c C$tX:` UV(tɼ҄.->-d'X,QDe.x>4d6G92 ֶ/3:gʞ]]+%AզYZ�5`rX4NK#}Ubr@!b5^;R#9kwV9I&#} MAfd0hs&dL&@@TDW Òg2pU~= ǃ LQ} sɻdpI='m*9u J]4n6CnmI)Seȅ)ɂg`ޱ+:HR7͂<!@/Qg(o4b۵%0yriL׮\kNwFX<ej}֌Wn# 9 ~R/8RMBZo8<˸j+ռfTkΖb{QƔy;apV6Q�^ ]a &zLLVDC2 y$j t ?C<d:<j�q\q$"g0hfLu.5v-sJ0).;|r .IG%>ڐ>5Obk4lZ\ ]cA=mI՝R&ZX+/<MׯPs歁 3ÜY{,Ss述)ժdtդ~z)z47vmےs^lmI>idTu" ÈDKrGymoW_+W&bJ_PYIp=gq^Gr"8p>;a{f]Q fa9|�~G/H9,rB%#1[G2##Hmk<tEz y z H5lЇb;]<GP6��P@""X Shq?ok/<?w'WK_uO=q"O%>J|&}zms27NJ|~/|co/|՗x_5Swn= {/#73/Xqr>_:c? ~k?~cև4>-崜/2Yr &�tu_{Vܼz3g9~pPqJ&LZe):O4BH(@JY598R֛刺/{mg JfV{{|_͟ow~g^xM'+`֓?7޸Nl8}<KdZ-[^ G3]RX<f&&^'P&W msɺոE>Nr*&)gڨ`6B4�.oRGrM-݋/zx1t;[ܹ{a 8 BvOM1>7^Y!~˚bVZ(ZZ0|K}|X gO g@Z*41eK ƼAbd3tcGY϶dT!u0 N"\?:3pqՊ>F{EFz 5tF@}f`a*$ ƞG}gUm:�IB{td2c#xzld]$tb^C9%{R$Yx ڋd!A':`uX9f4 :9VĘBogMx!gG'εk{[w rqrLU 6 zQ<[Ll@s֢DaCxGf6c fB;q(a|2 PV®uLﺲ,+rR3a?6ߺW/預AAMa:.? 1 #=B\B˂{I%$I( S6Zac@ǑI^džd 0DaP󘋪Č{uMxIU6Zlġ%gϲznH#.{_A?vxU~d6̍Q/Y=ϙfo-̳eU-uj^$1lO_)C2!gQv `y󰏥y]œ|kۭ2ZuWӧ%=[UƪdJ}lBnݽ˝CRe!J;nhQW.N@ !K(Զœsf/AB1 "U ̘eYKW'WasM0M/`mQ2#&\@}EyP1 d?kISVR4{h_QN2Iȥ~4h cO,Ҷu:i4_ɚIGL*H!v,sks0YQ阳'b?1ڴ*/d{︳O|1ڜk%:" bpML&@ @@W>έ8~ő~{ ezlN%ZP]p3IG-/3_cmܯj:ȶr!c ϟm=έ^7d?>B^i^ʶs~c~w~v jdp7g6fxN=d[~͸|1 (Y2QAGP/8?am *tb026F^{Fqjb*�&lIoiƊ˨f})x~C?_{~AT]yJGoݗ~1moAg7F0)X?;'g+?>~W?/tGi[/|co{[ԲrZNi9-_UJ$Uc.1]Dxs|tϿ)W(uWHNϛZ $(Uv:UI{Q׼zGm)'~ =N`xVW5+s .\xnظa͕W_%�0T Nf3kF^-`?k~!,wn9{"K̯m*�-b"RC #sᡇx镗q&]6LO@V_NɌ80}XF-08yCM<rdnt6#AҬϦ|QS)857ݧ@Y32,myq27۱*Cy{ (�!HN/!VRB531+Qaq@֚ `T% fD,Oo10 lFtd3|w?>aRZH/0D{gѠƜ?ok];e 4iD}D=sxqǼ#08 &P];I"iG3吾S<awHKox׮r->1E!?rNl^9cV !GrY9z1[NՌ4 xdx'XT�!Mshܚ@X.^vu1zۻ}SuМ}C0ճ[b<gbG.2˗֧>ͫ?#nY h`U1�ۛRL/D <fVbJŀhfb& 'CBAE>1Z-Ag0>TPŽQ> Y!Z.Pz]<dB@ljV1pslZ�ɔ>B&<Dž'cm ;nYP"@C"*ȝB^J !WK7v(Z({b`n-PiL01#BҎ:$'nV#nTil-ͨ,d4ͨE'ㆃ1Xu=okb$B+p]_r>'>Ąl1{>ɘHDC;A4W0[_"x8䌄Nr+`eh=1f`d;<ϡAy+'6uodUN^ծrô39EpPJʉf6H?H):`E,׼YNFUVn7ekպ:E'sYՓ圲ь۽:O%|D/әl77VQTBL/kj.ۓm]| ?]֣75wAy~gREF !~Ɣ=Ӌֽ} hb9ܲC7G[YKαS+1q+o\#Dl ՖϾ"KAed =0! ;cU3&BsA_r,D'Jfc$R.(( =+e2pg. }%v+BՅ OIKuE­s;GBa۸]n)P}f|O[ڧoutB_>W컸N'oʋ?k|գ?~7<O=q}N[?.[Xo{:Ҷ^;|/O5_XwW7^]ןo>?g~ϥ0x>wz=>ݿ~|!rZNi"-%HJr歔H!'B t]O:a̙U\[떒 e705cwChau Q93xj7@` yY>ͳ,GGzXɋ&As"y=nNcՖe[Ou:)oʳ$p)r?3`*=gR*7anV'40}sBܸq<D{s6*P@:'eNt RmfgƮ suv*鵋nX¢6}k{XoJQ NJ3 "]vt:7}tN}^ 9Q?FNBf߻RCɳ$7R'ݬV=AsfIt}G0k"p[{)q[qv}B&mՊ!8:#h`tXǀB9Ye9'70SG n.1{{'v g;wWhG iHs7gFW4DƔA,Z2qP$w=\jEڈtĕKˊlaL#!XnR )'ϥ]+d/\y%l/=ּ@Xğ^ɍTFۚ_88w1 vbndSr0jmNއ:ȉbgtTDB٬t=l& #ˉ~TZ07#wAؾGpXh)o昕=EA[ ; Xji8yjnE:Pv|I\g}mb:(^*>G) D2y!yN1%Ɣ=KQDLyeX^3*^$uF L{[G!d=2o7mꄯ4%)gD0Vv�.!By֊쟅VByU>x<#W&4ă 83_`6O뺖I-Xbζ:B9\<bΈS A0#Y1ҒTŤZ:N2"q<C_ Qâ+ Ouo]F|҆yW0\(Zls2;[}-Lw?>"eRUuCɸ$2\& :gW'i}Q-%37-DsŨڜH{WՒeQM6."m|*-|zz9lV_7@ nܸ۷@MFCU~vyszP¯Ke97gHYaSh3qG=f0aed\˗$! c?o4iM4 cҡ$$)cKKwjRTXksoYث0v`K(- 5.ϟjStugo_ޒ"=3Oz?sm烼Q.}6!wmzӯ8-崜E\?'fiN鬂v*/*xoksx9%V"N +J̔F٬¾4rU'Ѽ\˰+֌Ra|m]Z1)MZ돈GԚmEY4 t")!ԟ)bϤ@k[9iR{[ݱ�d- E-?AH9)[Zk\y0}eK6,w\}ct�ֽbqd*3<@+8I8̧ޚ'ͻ.�-yٳ M/d+iFp=v[nƵl@քG2y̯%HbNTS8!^C>X6zŠw<QF̈́QQZ_8o-{]O:4m{dLH0O):Wͭ۷M>r| cӌJf #Apx[{qEBNW5[Ut�Ւ'Ĉ!RqC߯,&rAuVĸA]:ϟOS/|eu{![|ßܼu+Q=Ȋ1&Va<dHAIH )ч._&z>K Op�� �IDAT덝5b{m m vnD<0̞bppp@ʉj qJ \2'h@綴, 胖>J$v8w11+? !Æn' dQt\G,G q9<5(1 [u9<2yͭ<f"3e K3bIVT5mMJHG̼ yif%FH#@7Ԧ/>Yɞ)df{jν2db$xl|6mNM8C3XIs*NxD5"Y]1EhokeZIXQdDcͅ<߫(0."XLX}˨4Ĉ�&iv&pQ,y~Άh^BT.\#<W* RaZю"HYy#Z`z![ E,zYO$�l:,m5%B  "xOjs=q*2T,ČemB,ԟXׁ݂R2soWu,o#Jk;siN \vٺm@']SdNcYy4=Ti|ױz.reNU⩲o4 0HfkRl.,6doUE-tby,/=h u-4d{ߤAU;L�sx!e$ Gq49L߿k]k"w|/YB|__畣|?Q!vfA#B JZj-4wb$=#}, AXfl9FnRbe`ĆHՂM,<(??Ϳ|vi9-崜rZJ NzQ4Ivm{IX+n^'>17]|ۯ+Iޤ2B C[i]i.n YâXH)z֫Qg,;g|#@ A-!*HMMGYV a\,u 0UL-ZoiƦ2︷kJ[gRPr$"D$ ଘ}|& `ֵҋvf3puBZ3Nmq�z"\&lBz0oӬ '\:kO�*м �^Q39[ɠvgϋaI%Pj_v{=1i.3 ㅄ@7 !1Ҩ捓ͰACDϲ ÆWh+LB`r:WstAc؇taLL ~}DbzH$Ȉ $ lzmy*T�o00cC0Ó<%lh…%ȍۯǀ3U^|U2;ad e!$5\5,JLa[ I@2Ȑ ym\It@]<Iifam.YFy尘odȷ}霪GxLožX<:hi8+BrXGD`^sG|fs >*uAQ&Ò9S3kuTnT~.O<KWÊ;_!'C {G5RA ɈV!z(i[{hjk_r{+yI%];Ł/]S&yQWpD҆%d6!Q3vTQgĥ7WO>Ǖ7-o` ,ui=`{.KlH27&@|"sl5 @{9>:9 �:`sɖa!LBY\Si;�̂A2S~%}oQ5AR~ BI%h+t. 9E}!Eh`fVQGema䜉!( J:R֦xAvgY%׷ԝ22C,j r9nYOI).$id*/^T~Pڻ=N?E]"g5$ؾze"Ls_Hѓ)F keuY<}ۖU9=Ckٺ{S [,f$.?ϣ>ݻq q`=oS]w|$.tx)j̶ -1ΊbQJ=Hfazg.#c >U\}U^x9#e-e:<+<)~Kgxυ~'W[e!5$DrȄ_Q@H.-(~HɭQ6<Մ@t5u%tqrW#䜚7_o)ҩ[i9-崜rZ޺R):$B @bE/: |5͍;[w51|xAۆ-?K%O@t*VѢF>Ӌҟ�<WA+u̮)حUx^Ӝuq6yT8'ex{r* ٛ�?۟V)newU/3ݟKQyu,JXFE^*3at{'Ue Z޿?>^>)fՆre!*(Ec 4{[fU%.dx04ͣ]su+1ښ O; Y(gZn϶ y\ LsJ V0+m<BUrRAvH@pYs ғH3[0:cd07|SqO{$ى Znժc@?&TQRUI. ],@$1F)e߼BF7d|dz`axxTs,o߁HF5>WlxI>|^r21BJzy51+)>t!R YF#)?a#xt{ܼy1I3PVF-D{<z>Bj9BlZص&wᒭlMjً}j.z@IuLF *˷,Mќ f#sN}WqP,CDBd=4gBD{{\x)FBL/R2.Fz.^fXsY(\ '�DD DöBHsNycX֚r<'6F: )m Z㫦;=<i&.o+uyA< =g 7A:"(¥~\~ݥ\{ `Yhp /f�1K;wZL{6IYcX"okvc! 5ym^P4=&OX,C=8Ù3\W9{TJg%1M+2Sn>r?ıfˆGax}mrIR^ACYmv<4E'*7yt}O"I{ 'WdPr1TSDoGF?SVBg " 'cVf=V-u] ew1_ۆ�}V@"zh H)%#5aѥkwld3&?Kﵭ\ҞWABCmgl]ktFq@fܐJtysۚJ.s${̴9Iw 9Eɵoa<gk-FKx͋/ȵW}_c:SZ}m̓9:=97@ڱHW0Qө8{o)Cy U=4Qyio{۷yg\xXX+0oT YZ x5"cIa+m /=wFȊeslq%UT'vQ,C0.@)B&,!fHyU.]yZNi9-崜/zL 8TJ"}߳N8 8u& t)ӑaGY%1hVKZ*M0 e}GV3bD0VX� �.e:Nlէ$UA檕EVQ N.㹧IZgp|gkˋ PhüWۻ/RTrTj*m"[Id,HR !Y{guC;`U>VaRv6}νw㭻㙑Aל=v y4WJ`! B֑[qYRJ[C@v/(FP <dd�Q\_ Px˿j^A Gd9wpΠGG޽è}ɒYoFB4/w!jFyq*xX^!o ^EXr(g !z#U @�OռRxQT&#ܐj!SZ[=̍µ׹z+!R eHIʪ'kHb2~ VIX>ύ(ש'")(1! y\{!"we:'˳.j3C; ðe6ƚEη6 uga|a`gَ}]2G,-bUCĈ2A:4 CJqi6y0N<+Q"'4Y8Y5gpm7 *$Ky$a`˗Wn-)IJ)n:>6FP҉Z?%:y73{zKې-?]AcДIA3' e[xݶ}Q0L9w> gWq&I((UWpEBkdPblRy[ߗXQs6$Il$)zn RIXbkq*srj˴܌jAG<Z9dCF ZM )<>ǿޯ-^D(6v wyh Į f&!§dۢbS?b4B5&}~D{߷wN]LWwsHqDTQ$(`tWj ̏oQ.ͽEV&~woxO#V^oĞhߥh1_}9q >Os [<MV_'V˛j|l/(ok~'{{~{wTbaUIq9Od.ʕmD=Ao@o\}#Ú\oS`Hu=8ߊeA\ܻ|zwkO[džr9dN&Hwt92-1BwJ50YgYڔ P?Y.ROhD6eMgе _sUm#Oi9-崜eSZx ]י Ҝ!%6! 8r@@'j^dIi$tU>F4 <p]@cgNT,: "湯DX&�zz;j1̒qôVu<V#VzEg/Ғ}L̽9(5O}_)%YQPOzORWެk3d# @3b qI! KR {^fb,/ sr̺Imۉ#ٮY_TK$(1vd^Ah '3{:Nd Qor R U$ c(N{_"1!.\Gn߼9FP.=t#t4@vUKculT$#dh\N`6ltbJIE HI298\~]s֝chFHim"avJ4 SEsi1�,dH6۬#èx]^nz"ёyh:Xd0<V׀DI10�Y%de3g;@BD: 0F5 8 2Z< 'k Vr1(g"=sk/.=фſYcQ�Mr/% <C9:2)Ӟ#`9s1 kBݣQ7sBbZ k^y}F ؚQx AܧLJ>9LT8yɾH },+XjIOiMsd>jX=BdR]r*眸B1jKva?~a@ ]1ZpiC/lD8`)x.~}$Q�j_׿uU:*r6}:h _ؤ<Ks Yq H!w2n4PR`,e+v͋Ժ_<۸ e3J cU)mP F`nWjRشf+mh NkM[1m/t*SdX;5j9Xl!U̫RH]Õq`ږf"EMo7C꾙2[3F_(Yc7 \ȥ^oF'ؖE'Y $FsTKFT{t޻wyM.i%kGy3Oʲ<xj]}GhΖIη] q3^V471g> kizcJ[_lb4v=.;0�17=@'(_6l)[f?{-o ! 3!tX<lz{,ytAmlP#TU<3!Q`֜n'&\F*b8ǿ?Cd:񴜖rZNi9-_e/'&V+{(*&f4�1<idu#wEZ-{4^r6.2zȬ`d~6zU<GY T ҂wM{ھ)hхrx> 9VT94'PTBԭ*aOZVɞ�J/U[^STxF)/l:Y׮ƒ*Y)<͒wXԆ DQS-9 YzTnOhX=jvkSYRs{֮q-n4P%Ѽ s` ܸq&NEȢ5tѭ{Jb)kb>Um Hblܺuwxy.=tc;d"KhmlHbB,'@`Ξ%!hF, lXLGK|~.\? <_0C3=" .ް؎U:91B38@$@a@\hH mmvV#ұϢHĜTX![G#+2F)YnTƑ~I,uqțq{G;R&oޚOu|D_'rO]OSt#]NyEG'B$IkN-:h%3j8 6�lnt]'戗^<&hĽEI:27.UX>~ά~7^xU`/'TK`$X=PE^dd绅nDb}~>*Ȃљ'cT70Q{nR 3*uR Q1-KSg-DI(!uDZ5NY=kT“WdCVGZl3;E*LjgJɵ}v&ЬSeL<$Gw(?ˇcn3WK[Dܹd~)܉DrM؈w dCzN *bʌFl\Z)HoS1-d�LnY$UP�CJ4D0OSlZ_wM & FNk"=MKxKͪsyYp?rll�� �IDATE@:.'Q4X(c1/;MGl#wzu-Gܯsbzwg= ze!:xfY[m �拹/=D_bGlx4sig"VkҒҳ~^Ͽ ;Z^V_/| '-r/xJN,E3ټG7o{L ֗/;BуGWg~_/q8wUeLC6]Pd[ ׍g~mIߊsn]dl6,QM�b �-~wÂ!Es(EZfWuw u33"VDdd<[$9gV58bm)x9>?Jȯ'__C9C9C9C9r1]L2hE r"]Z+N#"%K֜,9|q }Ǎ7YWo9}H$M/{:5_z`ͩK0%D-HUD %xLtU,QmNj@_b=L@\cZpW@�gٿЄKC:5g ֖LHY6r.AqF|FA^j_MT\tlZOij -.<~m )ES_!+ [Vbe^zyM#0\M@-z*TIWsׁ )D3@ z[-)VsK4{h&6 D-b'1yy$Fps3v12\\dݹK"y`v](CP:ˊ 7VGX}SVFp:DV[nkwg@v#!$Hp# / r&MtKGJ&5HF܊Έ2's!Xu^@V=)(mSOp'{9Tlq4 >%*<ׄ)yAyk7 ?zG܊(uPG!BLtb-.bkU4]uQI=Y© Ӱ-HY+X/9TȆ7{^XHk̑,|ؚtO; ,ni<Fdk=sS2c J^,w|.-޻υJ yt;H b*!C]f oi©^c^̢Ә/%LYڄ#M%5jfo՗>8ˮ b'^Z=҉ۓ6BvL:VC%WO6W<<Vsy"B t=snLW[/Wݪ&w-G{{.cq;O%zdHR0㣲懧b4y!ϭӎYb?Ges<x_x9\ +/Fѥ<Z 1b=UMn;lO:3=/�͐ȵsUMVh'YLWF?l`Q:| !0NR/hizל5g,B:yʒ'd5@<>RIn0蔐L'u JY qtʨWSgøʻ?&̍~m)6% ߆ic:?2B/=K/zN-̌,p̘%)̫'~I f1=ǦS4O脗k LNx6^#yijͧm) h8:KURMـwۥ9U٪>i[_I|_q^P*ے.9&>W|p2 9ⳢhA-F{_xo͟C9C9C9C9Jj{A1%n*ܠt$dHD$2$%ǩQ!ڃŞ̣�Qg`u_/oq?C~ ,$g^;YsOm_hjZ8lFx&ȄXy5udBCI%3OݯSj;Yox迴r$]bfspm_EOxp]5=SB";n7n! Zb!kfcHFpIIa(i (z{Z~ƂIiPL$wy6<p@N}W&dx˱ �IO"m$y &:Yh$%L qX 6$.}jxqΑvtQSF'r9 qȽ@Nwك'DqݎF6ahO>'Ot # [Vĝ;SNO{6 jF9] $EI} H;\\B�qdwD #hyٰ zbT4&\85=zn#膓k|_fFO=}Xѭ7۸Crb~r%F6[>lA̳Rb@qұ14@<xnɌ#c%;iLfТ%eu�]9 ^,gURHIfdò"3VTT{s~/D<K5j; qÏ?% iӫG[0"vLq 24eΏ),D#n!rJp={ܠn9.qRdB_}^PLqZ3#1J$I|9J"!yĀSG{ܠ8M85c,o,].λ,Ÿd2J\ż-ޣeOY[\`4*H NnC!"G12#Z5P8^[s̑oZ EKBuz /'Įs\fs?\o޼9$ڊEI5O5E/r}t $]gD@EKrY\"R=v0X C-&su$eQjX~0.{y{\Z J$t]$J#SMc{٫յ:ߥxָbpG.g9u_)lvf)?¹hp[OWكHh2K [='^j-2_gyŻ_b3%hl^1_B}~yk-ׄF!H1({t%r3 'MȖ2/Ͼ?_ӿc T"uB'2]UF-Tmy*hyf,xHvU~|S~CPPPP`<nIEn;Ty_~8䏹{uJo' Ix4%1=H5VMRٹ]IF\:!uBy|wx7O?pT xܖ&qYmSn%L)W tz*D*)ߖf߇Q* @0Rt~,B-MJPXZ J/[(.@b FOaRƫc<^j(w8?ӥVJ=d|؞T>72Drγjj),em>S`u<2*]$>֜#.jbv=z@L*ēk@{'$@{;Z1 s%K`1:!lnu3vخ�XR| p \͡[')Em#Lllx&Kp'aE|D!-b^g-OGO(5 z"glыЊ@)e|+@{G"1D KʟH;a)dc]d#H߭xzo}"$hv+ v!1$G 2 ֹ-S3Ac" o̰۲8ù-GGJW;#v0�^W2D$\"v6Q`uq|b)y/Hȅ"iaڵ k9|s  !'舲<Bg^]B5gn l k:Gǜ=e7lq>r|s|f؞A4ofM)M֜dLT @mc& FJcK} [Q#p%űC%}Gc<vDD!Hx sPX]-  #& Y5l?>˃BK<ǹ6Hh/%{`YB }O1#Hk.Nb\U$ 8)Sŭ tcJ>0=@3=|&-. 9< =hm*$9bYy<)y\sxդFb8%j̘N(FX {CC9Qv|;_xSܽcX*,bD e8=@Buoצ<cGԄHDEmJ&:]``X)4{qK^93j@Ҙt<5?O5b\-'6:_+[=.uFX]^+E}R#0=XK*[{;SP^G1)9><wA,mIhƗɼɓ/?:_# rHy nգj[jIHOb:$R {+3[͆ )X$\y!&E}մ%>wUy!{Yt`mͿ.4i4.E֙$;pe9\rtko!ELl]7yDVf>mlo4Z Ӿi4F,c4lm|YkkytT, iq,dShIj5ͩ5j_5V%K/TcVIĿOw$QQZIjFzʌ J$ǪƘM[F9}MJM uFrablʚ�A|Uh|F0S<z% wŞtT`w~?[lSW~}>=s>5lSL吝NRe)eE#H2ZW_7^u~_;yC9C9C9C92S͒,*w>oC5Bi=ſpz4V0 r@Ίx&/bs�6axHNVGe2]GYx<c Bd"rrqo%s8Ъdb/lXg%X,]{Ff>6 iHjb+FC}yz- )z σ @h9�MKSӀ$4`㜚zA%.2iTfNyE2Q^1BKkP3{xga2`:3pH~6 -Jy'9d;J|6vDUŀchhkbf9{Wu ew4C$@/}^OY8֗yTQ87o3Ϙ FSݲp0*^ !@7OIjNs<ԌC][y@C4b&{?'yM B&l8 t^qg7$6v QXug(rlxD񋜞>;'5O6]b用*ĭSDuFPqx؜ .7G^׉.۷Ͼ/O$֞&v@\OH +޾%1qD€ ~~FQ|NNs;#O nC fiTkV4,8Iܴ6v}w晪<;F +uB$N,(рz{,D a Vfc@4RˢNc+ ATei1|I%N<v"DU4Esu#W1{0ώLE+_O#qZA:J>ͺny`ɑ$,h+CACt8*gG/MdOk3Lv>sgiD @LY\A&a+SG lDp)7?۷3>}8ƹ!r,jRCR|oJ8TEErYWPp  gL %5Xik䶛tF7 #)ƹu:Ȱ-uޡ%mˆwNL;[ͧNneY-|!Q},9{yVа)7sJpy8hQTL9G33 EN1 缒`<u8%V`λHJ,Kn^ HudiӦ;s-u^(quqh]E<Nl(p$oweߜu䌁i >@ܯZDWn~K{vH*rTcEzf9CEժL@k_Мߓyjᚽ[t#=gq]2MxQ,Y!X!HJ03C `Dä2iUlƕ v_FerMnds@&!Okr;"U1:Ӑ^f]C!P^Z?O;|y? -}>\|d6GH, X+@^:YO\1~_W_e~忟>C9C9C9C]@K%2*???b2ޒ,{z4�c 2PSF2O~\|rMq wK^/}{.&7+F>U%�d?\"'I�XLer<i#gm_<6k^,~0vH΂6`T!А&o[q2 eB,7VִNO�.3/]؅rhf`xqTP}ҵ|ͷyHA [Hv%O<M9\p()sfB2k ɼV'ׯsqvن3!9"?&IŹnAL�fHw~gc%j9nf3~YM[,MU9bQ"1װs9Ѥb@%EG#Db;0 I" 1䙀yͶ!r~~ARڍtk\ttaaUGrDw@y ֫#`#'~-~7)#D`Վ-HΑCs^a|;&@JJ{4OgoWhN[6m6Lp\ll;^muDb Z,^LeØ,\Cg {[T蹫R[bI.E:Q:MuvNQ*+E\w҄J}`yy BUcw F5g멡LǎȤj2)S@ NtSrɷBt^ Sa>Vz2j&.\yϔ3c,x)ks]Fv&ycycs9gxL̈eoNř7d!&?̐<~>9iS<<erjm44)ՈH ۧEȫ?_+7tb!py,TW1%arfȀ}@ /#T㡼ϵ@{;mh~Y/LNx5Sz*2Q~1gO,I^2^Ɖdmnugh26q: aܭ:<9cE#IS| nm0o.1 ۏۘ4T=x;t3_-cyO-=j/zu}ij{ekJsՠEtsv]sA, {#"w(F Z 랛٥ަ 9k%gY Z( 'Ϟc:*7D}oYFO{ɻ)v6dv&\.pq*ޡfQ ^}ׯ#tld9F}թ2o|'hRQח}laܘ!%,�� �IDATRHRɿ8>^[{ dSn?o[�|wOG #zwk^捣ɻ|W?Əo=ʡʡʡ�PA ɝOD%d*XAaBz{ܜDj䜐vqG/yX7;?}*)RI̋-Ė~ۋ{/Z@ sR?%U.)W= \ lLf!uqkZl_>CX*3~2'0f)$f Vπ%s@w|#b^?)?>cm2Y3ͳhuc!2)q 1%<=70a2P8 t //'|vc ONpsv~vMv˹) ;Br$bK7/ 0n5IAkƧ؂dQm(?a9eBe2$ Qvu+bAIRi)YzDgG; 5tnؒH霕ǬRn.`[AQ6a[ў6;d{n_ x;ǯvtbevSoa'SX1\v|L;.z1hs x0zo?CP՚0dQS!^/S{{yT$96pWj2( 2eyuNNe(Q]4f92{T1w=wﮁj|,7za{a" d19R-cC4Nofo/6M)$FmN"n2F`1п(|?TJ_|Ri^J0|%,DATۓ޻(CҠY?UęThA{RDUBqEB_<!XnyqD7OU+_9} gZos^"fH<yQY_ΉLLԯZŖͩ}}\hNUWCH MX*"ݦØ Iz}]D:pW$4E "JOGgyqcLޛQYJr;Th[58idD{X]-OJ[WZx1}hPPljm?okrF}85 Ss~.j6]3-|zn j'N mԢ1]J_Ff 5ҟ]ikӚ1ZTTu{v}&\?l*fƍꫜ0}Rjc#</<9UKDf"ՙUb<#} !  `Y4M|r d_vt׎nwSPPPPVfK1H٥<3 Qa  h,|hk Z6KcMk{3 ^rAUİ^:.pw~#0;s rrN}s5Ks{v}=./ @hcCFRW+Bw6h?{yL LA/wn\S.cb-uq8i/@&8:q~G3HyMYF{P=f}),|z&PpU]?/vر~Hj`vTJ]r{̻cQ5)(8YgؤM /sk0%}[=XȠxJ ~ч.[*L#k5m-$w+3LGP,S .}Ļ!9zT^zytBw%)Q8CvyBwd�:߁(N4Ƞ< u[\Es{v/co OY=߭ٹ-J)aŏ@ZW/~m>l9W!D duwXuqNm+,^6R|{jqf/㓤.jUclԗ\ =Q$ZYGGπ`A ;¥4s8s9*)Dݺœ'Oxʜ:0Y#h@R +uUg{Ƥ& yVv#k`$du_R*9j60ABK ~�шߤKM٣C/~SZ`t3I%ıc;zͅyLwyk7kzf>RWd:v T٫ #p"hLxu<~a7w2k-)8RRRX]_xZqH*8RN;U_0ʤ8{J�#Xko}_t/#?BTwRGؽer "RR \#BH1B\N+4.e]\Kis8id]kufYM_><v*{FW,q6S2YUdbLM{饗 1C5kIo 2kv_l=^g`W)K:yi5w5?ZݧIe}ۑ!hMZ+ݯvy>g M6r`zZkG#NݟRGJUrd x!O4cx]OϱƟA&϶T?΅i}(~RDƔE=rvyv?G9r(r(r(r(r҂ሑމ [ 10zCVҤ#s+*X=3h4E78:n8v ň�BDpٞ �us{l�̼lW?ثcˈ 0&gDU{gS@l'\_cUo^FȖi:n}ػu  jaEM,c\FO9щ)~36,$PT.'aa,+%OpdO` CbsALC&#Dj(2BA6Fm7|ŀ'.DV~e!kXEa6PSY5yt},y4S0!1\kw#6)Lv.G+p죄4V#MR1[C#\�ߑ4Zri粷a£L֥�ǫcVGb@A.DeOCG<&\;#n̲>*r֯͗S w?Ö~u!uŃD=95HL ))& C 5l<sTð۟À=2nEa$t)iZyϒ2x<jYRq)]:byt4Y%It9xĉQvAQ 뗹;gCV:XX\Uj 9הn{W^(鼑-�͚L3^𖐖 eֵ]֑@qk.ҞK,p!UHScd 京> H{;#XB#j$~{Z*H0'kjjBcB@2_2ϫ�t*^V�ٕ5IdbWI1rd͆OO ;KD,-eWrкX^s@TY0NƳ:qKY'#M`>|<@'Dd35F~WOuDUZPol]4UgFK%?RJMܾ{O>$%XIϪI>aف!kS4Ot4WhԨm3f0b]xJ;xN8x}yd/^Grsg^j7g)\jT_vn7T?ѳ͞Xf׭ӱZ"Z"r6zTY|EkZ\^ /Ѩ1ƛUY|i,-ټDF/Ϳ9W~/]Ϗ˳9}CrG_!oyzz';~9WdZp>Ï>']pw%2=?n i}~6]n<)�s&5]w=ͅEDrU<ʉ\ʡʡʡʡ< B)B3&fF=lKe)AGlKz޳n1a<{ W8b~bZͰsxf"ݻr~f=g:>WHҽCT*2KHt=HPl+9!s_?>T /Z-.`eW5X"oCETlaHLB-+�5`J&W,2(2)(YN$ہO>nKF9?m d�:=y,ǤuF<zg`{*l f>(x9)q}_Hpk^{f1`YөnD%u=P]J MBG`B5H<T-a:B2BBwW bq& 9e!E'NjWC c�qi)Tzr^ zOs_*{NnV+ [>}t?oqypQ S ЁkG;hƝg<y|#$D9l ۷?u %6P$*Cg < A o8n޼믽{xQ8RLNhkwu-ԹZ 6D @r* +Xxzrlf,!=xK 2sA&fB-lBܤm8yVH b>Ki99]Z/`,\G]s-T+C!Z`xWknܼ0 <~#֗>- 5[+/ Z|by\Ea%Ƙ୵+i7[daS9"ٌb=7Ddm3%ux.M+ w5{-c_ZfcUᥥE)!lޝVnh;p[A^[Ouv#ld 3}y\',(ղRٻ.g3Fh2T|3Z4+nI-uH0\lXJmwR IxTt ߞװdW,W|)wtIuM+.+Krӑ]%}x*_m`c}3gg9b<~^S6īT'udRj{;f /2}ץOur\uy*=]Bj>oKL F' Lü5Yd*JZ(ګyq>Ҳ(2Z]3krL~+5HC0q&H_9ll|;_VzHO]~\Zs[#4P;{:c?-`{^uW^~oɓ'Vo~XLgC9C9C9C9CY,hY1BM1�c/}#a P Y}oLoEd@ZJ u0[)&4pe^y驢doL\,gѥkr4߬3ێL7׋fmNW :/YV2 &,S*` Ńh~h1D1,T·Jƌhnc)<Lojx!zCtӁԹS?7ggȜHgUsv tv,$+X8֦2J2C-i@"{BlGR�jO;1;V}ORv3dfks8{4a 90g-0ʵ"ZB Ɉˑ 4`FlZ\tp BX pC|&p#F-Ty;$hpqq^M]c:E]L|OJJu�wr<<r|tW||'O;}/J* "*\5qv|kotgppg�U ߅$l]o`RvÎ 9Srߝ� ݸI;N/nou4 B IW尡(Bye-׼q~y|OaDK I)H{m`s8r\1 C6pS|92[-32zݷ(� cgJB\^{_g?S!l`!^\Z.;63->TIȿQVk6 hGЈ%uyW.}1 nu ><s"Io.(Nz~fp0k= by?m(7K7nr]6ͣiWW1 s©g+y]%<12iKJ]93B>ْ8]FdgSJJWBL̰Ӌ8{gDD!&yOh1EX>XH)#^<D$fChFK!yvD])sf9NF 2?܏k'}ЪBRuzooTj.=`c͟:>wy`~,|W>ce+Ifħp"X=}1yFOw%v|%窶A Օ=n<,_4+ګ}?#{�5O߶( î|5W3de+fCHcZaj-z=_Q4QN_R2#[uW+!r[LWi!{(r(r(r(r(x`lrF$~qFɟzR@=l6JpH=+_F4rTj|�"W.@,jlhx8=̕i᪠E)nvuzbYMY@TB*OmM=W)sp摕;mB%i!HRiɾT!H}ݣGNy۫)K`>kVd6*+ʼ-lz;ge2wtF,U=ȄsϛIdG!F|&sv�"4t]5ShlE"<9B՚k'B K+FBTd 'J2-3zVJ)?_SzL}TF|"prDjH 䐻`=fē42 !�;˃30n!B 苌Ǟc@D{͵!Jvok=!E:#!|PV q:? xu!!NNHHbs. &޸u_u7y[|N#e\]%qkHC byz,G&ǜ]Jb4+~-\;}@PUw:?Zmb1JĪυH) ׄsm9+''tzV#}G(=yيGh$xC\ld Z|O!F2i,dޚBڿgW?$_sviYRQ"1b.hN)*yQBǮȒFQ^Ai7m1pܣ!>_ΦJc[({ɨ$l췔ҨSNƍ1L~;mZhOZXuBҶ|a ;z$K!ReE*[jկq:o&O>ePש*q4܂LM(5o^TA k+-c͉D>OeLl`w$:F>#N\SS;HHԡ6':|7ڭ ŽNl .rNv{c'9Iߪ)+._Ԉۖi}vUN3y痫K2*o�� �IDATdݾ`}Wm|VKˏ|ml{0kx]F/k=YNv/u{ge~\*;cw[V}ϵc!1C${b8 w>kq~=Kg̓ٛ<5Jʹcc꿉V_t6U7( }bB5<cdeuj^_)%V5]@ CgoOÇl[֫U>T6rm*BPPPPPK'] h6/P1DCP᫥foƚIZa$~{4U.!*I!uJy"R4Q\R<SK~{~!p2_2_ +9L:lZjB2 KaRoC#{~.p]jMZ<9ˏmg N2KW>\Kzxh>pg fml=9jf0q/rCe.* j ]/U<:-R,䖅 *{qB朊hiP޹rtȰq=ە>a4WSƠ%1- hu5AT%Bb C@s^=#j&UF9i&ƶ$Mx:|!%0Б&nes~0$BTBNNPr .xz m8q>G<aEG;zC t&F P8H2BM "FbHH/OOc^ؑDdYs1aJ==a[`2=zf5bQo6X-{lZ}7"-IE %xqAn%X᧜>>e y[wm0}}KFrYKZ.5FkvqYv@f%';i<qm|W:Z-%%_UOn_3..'μ1u@1V(b1mB3:)-PuZj4ܵEW+aGe\k$9ԥ=b%+PtYCھ!R3Z,s/HJ[o3yBO"Ψ"_ 6S,"=bv>T!uVq}8mlJ37u=Tt$%)^t*pȝ[Py2%;ܮu΢ !Ch+ѯz>|ņ"$JnMJԝ1Gt=шK#7^{/} :.>85X;ͅ@1Z&JY焔}Tod_ _\fwW ;-(mam|1yv#bMO|W5"q(~-Kmohi쬌?s22}4ȫ_,?yQK>_zYKKb?9f1I%b|fE مW_YG6];xG#iJٟ� 1 gfCLAZG$zm| nO>]qR9S:|Y|~Nn@ʡʡʡʡbYV_=FGGGF_sҷXa Htpm*y3I )40W%G tT\za/~ֶuzl{؂N>R-)گ1Ɯӱ]hgan<( =0Z+5AˬW썇S&RhχF co}�ͧf �pLcƽ@W#j^[%cԶgo:G.T:ok~կ899PtxW~-dMʙ'Eҙu6DD"Ā[u3S711,\R�eޜ/)@XLjV`jd.p2P-#cXkxz(nBx* {81?5QOT!`<{{o$ P ht7P̓A!! N|vt=3{D=??{]nzv'GB GOwx=KD"ADL'\<J)-y; <1{ ()\ͼ~_'{allL친 r_~FQy;wrUoKΛ*nBҦ"# -kH6 HP+9UйO0y$+{'f}3~SDxJ62 [B61.R<]:n/SzDՖfrtx,?-ߤV76u~!XyNh9 <ލdhp~d}m+j;eckU&߃*SZfjtw1̠՘&2TU f:c{=c9R(l 9oTx[7 yzKщMŘ崰m9;;cIߋZ2=]qw\=[0{NjU/F˞`-�uoBa\˹֕Ӊ)D:^_g}tĿ/#>mNS^x 4,. g''"Wz_//~O=6%e ׁ:^~GzBє0ai+LƭY4=CWs:΋),O ><-lKyZf˒5ߙ~sW¸n˽0vks,T!I}#,ߚϮBz.49>?Ovrē&{8W6AGW_yvH�񋽴?daiI`:o%럪ª9>>ϩSJh"˻|>TU0w]6o{$<@ʡʡʡʡbi!t_/^:�EY|%Wۜnji;*0A⼣s4e [h؍.TDk5'mucεsNZڲM0N E*EHA EC .W$l'mw{9cιڗs=T{˼9 9zb֚Ͱ[pCh}Gb@<ex/#5p$5_˸};Ay !t6*JB>HNSVSkuN)nTêj7: lc !j1V5iJ{ q$2F6Ř~U5DHq6@+V*RexkNVfB^I%Iv4!S,'1A^kJr),!=W#H4OyZdZq)mb R2*߯QCn*PGg˼u>4qr1O|^ƫ=Z5Mk)fnux3}Xy2dȆi|#^ ӷܿ:~\\>Lwl(EbFR1FrKjB%C%z; �3ͤRb" lAv|w[Wo7/\f A6^p2d^3֙b!Mg%Ml>ࣧ1ٖ?'V/mR$`QĒ a]⋏yb"ge ;�|w1zʒoO=Hd+AeYKw*f3uE(VܘeV#g 5%2EH^OXSK%1+H ܟfw>>#6 F+lveZxp< C&(꿣mB0c2(/˪RҢA1i ΕGNS#Գ[ꐕ Fg?`ȵ]z'q@ 愡^u\= zVMo`n266u:OUfs3jZ[*`ZH^>䏚`U k^Xe5w)_QM/?CQ#9Vjڳi�xZhLț!Ddi{`ީq!}ST(4T2F!?1x�]{)5w'ղwX]Re\ e2-䂕B. 6E)X;=)6^.DLɳTMw[_%ees1 D69IB N B*?`xo\SKsw]*9*Ʀ_FĂKr(FK_ryBN!?ҽ>wb^t:nuM8:yzucї,<fue$f(X%+hU~mG?˻2hC9yvw;ovS[ˬE?u=鋏YߘA54!9~oLt<8Qk]߿U;͛WmT?WWlItAq02}<9 ȞLg:әt3(?M\_ׂ#OYxt[\TٔQ4VpdR|VE"bz/\�ҠD4;KkXDchtvPI{`^N*͋84zP}9B�)Rx ݮ%{zGwS&% x]$@n4[ka)o 7Kb[ޚqmq=/9/Դ#r*B!X-ZFТt D"/kL ҁ7{U !x)׻-I uLB$7W:ݳ\ߏ-O-lhT..B4Mkp-O :^#z:q5^k+k*P级 ixӺr()_{9v_P(~&d9UXh>oOw|9>,s{%C R2V ejB7w6[_^`)02R))B*) ! l" f.J./e>z݆9TOFj*"h$" lN<I<ß`I!P0ybJb!!7wW|qr!{�$JKM^ TLjlԖnwG x{>}̣Pt2Z8Hd,4qM;ȥ(e8W7ro%c:z.68/QBI"\[Ar)bd-zh6V L2̶ @&59T.(9 d5|ߘ͎{ 9Ͼ+C u'ji"Eˡ|R:2%POZK;aګ7g ,E*f涘QҁFCDh[#T;ŧ펠.knšZO豥89-$ʪSSOIuoňY9zP \d�IRK [mAku*\A:Sw�_3} wgODhKmMK$(d*w `(E`kF[{}O1vnޢ_*:*C=bXZzS> njrw\xY>?+-y-86A:cPLߓV<I>ui4$g-~ןS|2isYP`# n3&4m>%@HϮ6yjX>x@WWdA�mk]+PzW=*Iuv maL_u>J˨B=_v>r7WW<b>/bfKbT\nAw:Ҳ?:g:әt3Lg:JlO[;fy5[ؽ؊W)r=x)KfUX@!Ž=*5wxHR_9ޛeHezS4Eꍩcn-MѾ5qraߵ~W*xuoolüLȼ_b, 9zB1uoYhV}Y+f5=,K2Bk#5J9fIvS:=55R@Om 4UU|"hjWcZyQ]kBFpoQj}s6Pנ�bNKn[qV}=ܳv8ϛ}ykk�guoVLav56O)&^ 1%X)\^ \QbLqXs qwww=0/SMcYפ 1*ɖeF/??{\&PLL ,&܋>﷩&F g$ 6d "X"Σ--B$#5-e.F 脊0fS#3,ma"o-F0㠶 XIfnvD06x]bn�1r^@#f8%TLJlJ*g4[ɨz:Lp:Pe+HMn׬񸞣Td&`}0Cj�%d~K * &j]L@)Ẉ0eLy ok\hY9A\Q#FY!KMJCĝ$DZWͿ-32njVtg`P,S@wG֎ײa߹i{L5ǨMAKŸ64{6#Mo.G,j<nteR^ɖg=bFO{ hQ4( .beFH1bMISZᶜ>ņf^ #ȒySCd> 7#fKk, zJioeenR'WR-3Ff ׭ebNԌ 2뱅{l `WssBA EH"$eqm|v{.0Z%"4'=:1Wu ] G5nk 9$`4EP1v'\2HXαy diTTKf̖4m)sy<_�?E={ ݛW{p8 Dy+d99> Be5<sB˾ʹy<ԓv=΀t3Lg:әҾ? l5<,1)MtPm4MZ#rf(*cgk7hPnAU6u=a481j5Oڳu=~QZsACsw 7<o{h?}SJH;ז}aŠXgwYonݒ6˔|`sU)KNac}|2ahLGtV<jVQ[.pCH@i9Km8 mNOn՘+1Ekt} F7!AىR,{jТ|6&ᥙ jwA*TZ(ɸ25͇@봌G}ہq:f%[v9G+QUOJƥ*5~_)V\\)[OJE猈U0넦R1 oۛ[=~ Rч?yvSeZDB&x~q?& keg)*I)a6:٩#]M!Gqj&)0L,3fmfyJ1#_y/oSZd.p?'HʅyD~$uX${vS0OڼC�� �IDATz 3ng-QBї.#]akJ||bxpg52}7_zΨ(K\׎Y~f<}0pc]|G6*|ev3|9en)Ao49~VPcfZ!.Y"E{1rZQjњw!g =Dhrh(]Ⱥ0yǜ3ꑄ̵&/ֹVϴjg] \ux :``60fg{c2!T+-[CƝ&SlGZ݁k02O{< (%0uoiB_Z / m<^mtB,p/+)Ef'h|)nJ1ߤ4yl1rɞ*W\kP0H;15*FWzITS@� d 2q=g?0:ZV^iÕ"S{UJSfտо\"`W FٳWI},p|᛼7_|S~j vQd6%3h8{gw+<#3%ߗf6 9U8Ł}AzA_dk#rVՕJRޘ<Nm6+@MN9NOw=\yM(B˴HUne]: z81߭m/^Nsտ ?&7lꎼԳ, 2yocwl=dXF}ە,Gxgms3 {3Lg:әtҨLyd 9ov(;h;x.H@QUkiܠ@ZƐ5R{j3[x]kIUǻr,MЫб{%rYyo^ôG%%G}?�n=pG2?6f?jqXtU4` X?19qhP=WkfO<7doʐ;jK�Gnp  x]'/.Ȍ ZZe8 usEz'wzdJ(%2)V,Z[@b^?K6j-E7(螱lFuAuoo}-bq6WJE\Mo$hx}RkzNXt"DJE+>֝v77v}v%W[|p�Iu蓸'\:~DJ+>9DveDSAD{@S{agw7570Pj4u- L2TAȅ( )(&Lf[Oou|KԄl 6A,E< 8(Z<n)맨HM3BdD ":Eۙ.g#Y9,F8|ws_U \]n6BٹC=zy.FD.(avv+̖mۑ YS^0W>+VH17,Ã{;b7g-a1!^ ַڨ/`VoMkF�uSʩ"SQ(Zߜn?yGCkubiu~(y~HwP4D7wx1~D_r"C6OܢWz1X3j[1С^X8;Z qqz ӆ?OmF+^IS-?tL]vCGt::v7Uy|,˃MwxhN;!E!A)\l˯k|x](Θx28: |oscUVՁҦF TK ˞s1E%8ϪVƎ%y ǼLDAWrI\j E|}bbs<RC-MpA�ĈmUɯw/ϱe]P$V*K~^>ȝy` zQoP4@K%}ER8pm:|2ϣmZu%7KK7 +e!IMSʪS+dIA_X7`/nӸ&y0:Py?r9eT#1viZՒ}:js(5E54gr]ɴKBq8=әt3Lg: i?"`?,>tY&.~梋Hk,)lbd\ @xVpZfѥ2H195V=Ǵ~VyiN)ɓ'oV;g-9lSZ:о :[\7*߷釘=a3CSsF$+@vmサݰTDdIo<27IZO0K8Z U/eʠж74XWB^x4}̌n-̓0Oƌl {x[1R6b ucy}s3V.veO )=Z(fLSD,z-B/jxhܗ \ aYD V?vkA?׶ԍVu r۝Vэ8hMZQǣ{J,5dnD=gTB$n.[eg5! # D$!667S,XbQQ7hZvL$1JqP*>yGbڄ0( 1KFK5.E F9l3!/)^�M~ 'oQ̱&L8R$ھ^۬G޿_y<yJJXSD7:ϩֵPm~ ;͆Vl}ēe'4NX1b̹c]s3ؘxuҍ$C[DLzA˅SĄeӦvl~MnY$wy}L^/y.okG\Ztl:s12<(CYW" 9,Ԣh[4usZLCy3r)]Z̝'"3VQ+#{4X"':JCQ8пCZ43uT Ō9.zIM브EW!1|m2rvpEB Zj u~yh;d$B,>G[Uea;rWxm7| GO-7 ؜}6Ԓ MS,:Kxj{_Tkj>[܉Do"8zWSrQyoQ,^Gܲ:p S%Dc /w@m{P9@^}]bW7e3Y*\>~}x\v8 HW^QܱVSqMFe폲?m]u̯T>h/3I~םE?L^U+;D:w\GhjԲ�Qg# XA۳uy^kCw^ aF- xk:weo?Mޣ}Z:/"̻%//e{w3Z 0�imlԌr0/fltdt3Lg:әtipN +sxN'1;tVCm:Pb&W !zvWz"JjF iMc5a=G~MQ P t#^S*5QLݬcƃcbXCiݶnFWf?:~#TKW-cܯ UJ5;hUF` n-`,N5KK=U3^-:</mU߷w7u Q !,LFfl{GU0=sz`l<1bT7䵰s^+qZқ6G,~Ra5�mqXh Hi * Ժ%rlKD~9!"\^^RJa)_ۼ!| O<&(<՘ʄaک$^%^P%{Q4e[B*ĬMMΌ咍]bPd3V5̤CBBB�ApZ$u )fDSRl.LL\^\('<zvG?F$0f5ČT7޲l8$./_(p{{흧 HM4 &CtG"c(BymԀ,; Z#УGU'(@r"!L9\ʒ"_Fk(̍<Ed^h2xGAT"6y- W|9e̷1ɛxt2ձ4Hr~j$D+eoa(fWްDԃYsgH=X<:QL5{?l<nse0a34矱xָ]3z6{@E[ uskIicpZ0jlSzWVC?S[̌"gZtbuǕSk7JdvmkUёjP*fz]>6yFBm04Ű o=ko_e"Vתc z\\fКft^sǪ~R-{mT0EPn.[" kչ;֫.rqЄ#4<ݴD2MrEx6v'Y�g}T. D#N9covnG+0x%.:h1ü #ؾ<nu/WzӾg;7ۓshs0un ymW2~ʱٟ;fo[NGk>+Ө{gm}5ΖR c;V6;?|]>Cv+[mg۫G?Z}Y@l3 {3Lg:әt4(|r2(=zbxŨ8 -I-Rndbӷ"*T=UkJmjFNZU!2je N*FG ?#2xB %-UTLl�nKƮ?uW'w{ ?ZzM9@׍I@H/2D w>)vڷ' Z pv10A4$Й7Pz%BDʅϱdPS~{׸j6EE3N`@`*{^7XX{p3dȼֽ5 |&j5LXMx2L^n[pϏKnYu)E܌DHizJEFkL/cՈ_J!�ZS*i&nGyvK :$-ɤjf˺J i5xH@=.C\klBJ.dsX]n 4 L!{gc.4r`AS$טeǟ|?}B6lK05"?jB,U`5q"2ygl..لk<yn#HsY}PZl7,p*{'a'F,FG~@R\' IJ¬ZG"2m.0Q aڮ|7|yA.\<4^[- 5J"D۹לZT C>ox5S@ չÙs~d}g.;U~xwyf\LVd7bΪVpkY'JgL玾$@?V:#QLcߘl!tA�L?=rv!ٍVO\’Ztmq\ZԹd?:LjHjx-HBg>?C?z7#! JJ^>!R-xL ^vah),^JN$,HWOO=Όy]̳Q0RS[ׄ J&4FwP0wirH.ͺϝE_VXq{\q -KSK({>(Usi?Os.-",}3j9J rZ<u;{8zͪL;|^rҕzfk}`_GyP8L[_7#zݾ^1c?zE>xK~']M,Ҙ{ c]?lLii s,ou=Iww9͎1hë9Y[C̞3Lg:әt3=^ʇ7/}E'eKuC,֍V%RsN+6Qib%Ŧ0mZ>Ay`ϫ�[Vź2zxD,ޮwyב!95J͈qh AC%y`Z빶)Y)"%77`pX/x4-5O GqqhRbh@}-(ьhy_mZơ}cS'HD BF(ٰfy Qv<yvţOy\;+BIyXbY|B3CB"SsSk*=:XI(a/q[gĨԪTEt;QV7ֱQܸ"5L ̂Lj !ܟ)aa/}.EؕLV;<zM+xJcq7ω3)&A J0'#YFcEv24n0Hɰ0`22LLBp'„ D!Nhmud![LpcsM$nRrGn'O ejBZĠ BxrU2nEh5Z¤!ZṼ4+ E(J,G)!TM2fڪ"h@1gv|<wf@rdЃ1Z mոJr^E.IfaSyX�2!RjJ v0jlhm(-.׌E!XsX3"/-ߗ:Pl�V*P ΆО+vk 7 1eR՚9"W{ڪu\Ayk`fn+U\#åEٕEƭmuF K>X'sΛYb1ҠQrN8l2ɣ13t)J5s?L<gIqB׿bAI,~Lye[e RVkώ,UGRXAR~H0uFu1g]`0݂AOTBhr*$+-ajR/Mԓ6~^^`G֖,1|r genm5P1c,*FE۞96Tei/껌T'hIKC]%wƚmXϴAfxBmrҷa>Wk2sp-'5Wց lУ<'YID61Fww>umslhۦI ৞\~{sü-v^߉y3Lg:әt34=W>(7%G19wG8fm,5#`53ܩLӏu#hY3OA_l|-eVc7U!ЇWq<^Fcs�ǢUd߫C;%{J!�}rmq@a8{qߎ�/_~^[ ]◙nc2^󜸻 u!.7ͻC1mxlAA12EUE jh0Hb6rmxlC2e :{1<eyЀD/fl\$cS7oEw{_~o} _'?|ìֹ�� �IDATw%fi|D 9%T{qqqϺ:x@l;V c˨N[�ہeRހ8Ӷ1jdW d5plżj@-- m׉ RMK~2}5"#(F.B1%LU A<{t]t6( &l)(cDuQ</&w.PS_a:%GK\q3V `uC\DFĝIl|e 4{M>S91MRya�7whzŊ_mzmM iY ^ m,dS ;5LxFFfR=uhF2h&IYkbYGHlDbMnj$f.b_6rG~]ʔR#�w_3}r LaCRzIwwd+5D+0nr1R4^S .+8X cֲqTk4z ̜\΁籈Tp_OA"uv9x7#ڼ5H^*uGڜ5gʬ"}n9olnEhg-+K3ːFal[@RP9Y=QMPxk5\Ga+Y6LLh.H1Pc냉g[0j]ڝ(?fa,̚xjQdR-ڵHf)xI%%W LZ="X-3TZjO 10m/|i-eaZ 9/sjNN&$U+`FfߛYF. w|_5Zb4/uS%+im`8=XÑZsOjq<ک==mrpGb|L?;҉A?{_lHS7QQۻeרS6NYz޵~]??z4Zc>m.3QWkU]'&֓Ky{푽{)Cꔡ-/5/-=әt3Lg:sE)0EFܿR Ϟ]Z#=C [X*b t"5EzDBZ`Jɣ2V%h覘?+tWUOg9VC>`<X*G}4 >Lja͋訣o_rwwW9Q _4՘*5}ں c_mCO $6STpmRvb$SJ,VҪHPb=D֖ J Dipq[p!~k/i,𵯿Wy:g_?~G?뫫-RQ`VxP9eD<E0]G7бSY#2Ԛ d<EF~_Ɠdk9Ptc_+jA~]wb^"Gs,Zr]:Xh^*<FwI%a3P,7x% Ӽ#ha;#Lʙk N h(E)ybq&|]~|)D1ǟr&9p9"D_hk ! Kyf痄ZppS7h9ϐ ~yw?ȽP/5sdX_̜t>:(>]ܟ„&Xl "kcMFEH2ţK$u){:}(E\[,^C@7- @ʙevd U?yOx{V`Y:@KMs!%45m1=5\ae<;f /nuU( P:FAY!=df5Zr�X)~YK�^~~NR3m-mN-yLsQygRHː г^ H N);׆vp9`AQ\Kz1*ٟ[K)n`a\v,JR %1(6"b L)r?mfU ¦!Ր_U6t9wVd>"6|@6>Z﷾ff0/`LrZ#JHw@K"b LzV}YX`]5:|(+.iWN>k%Hw\>U=#ܳ*9rf*P45 ]x>zLmewϨ4M"<t3m?- 'Bc?m}:y:իc;gs8pq:iIJeuՑze/ ZCED>mwԥ_Qy5u6ֿ7�MJQx7PU=}YKY<ܞ:i߱7c/H1]#yy΀t3Lg:әR˖ {dw2hq%ϚijGDqn5XF_bGcQ;P l4vr JXpEk^VFjnHRРJqI?c boa74شބl'M}h|1Z2 GΓcYFkM*$Y.C 5EboZ5R(R@}R )2ꆾtq)k &&$ͲPSe-[= eFvj$ͱ{Btĕj5#t཯ Q@ۘZ}fH:nFx`o[/̯_˿S4`Q1f<a_y?/䷿?a{{QkY랭Q>nNr'[$xD ӾO7HX #ڊVhRACk}Ym _V|h٢3>TR[kiM\/#ݼmokam]R@:t#V!y$7ZB޺o"K}㔌LB$4'^O[ \Dao/|O{<eK"vW%6JN(,0Ȟ7LB%oU>|׾O3v-5#9TH~&7œS!rJ s,<oYJFͳ'|EЊ9�5[kӬaCT$͘.\o[D<ex1,ri !ZDڌv%b}$8b`FBPSR&Ip1=`6c[2!LMB#[22~_\_qW%x_,,l$&TwD^{pɿW}_\ߒ4lÍKv1(0sD|` i/M`cGYd=۶hM9Y K_ڳ|)UGzt˞ }\ 5r3ď2^^6 ef&+ž9'pGSHưRCrgDգRA&YKk`KɶsVPAFh#|3zק7<9Prݿ0C1Em\� I"" 6ɘk9u֏m;7K}=!2bx0)=={Ɠ5y-"`=\Z_r P'-< J#L8*ug<;1|�-b;.tLNihW9za5�NjLA(A< >{*r/WQ'Eۇ4E(Ꝥ%:8 s.Z[clߺ "mu�(@Ekh pqM/).Ca{pxCs:qy4翂/8M<@~}>l^^~\<|�iuc($gUIՊP,. ~LSd;nwuh#Tys {ճhxv&n|5%p^arKX˞?/P:g:әt3Lg:M'15[37!_ r,3?FD{V/՜./~U?!׷n`fl8ը -JjtK/!@=*KG;FZmP4n0`O'�SJG4#] SYg>ߺ4]}@іԔ^ͼՌ~c.9+%޲ۆ8�24=5r5<`dD<79p1mw9~P.*!evZ4KeV{E\$+_[Z@I lAo0 2{lo2ۯ_gowˣGVrһa衍Hb1,v7d#i>7,qo{FYfQxLiLvFt]ꆲ%|c!Jn JkZub֋κzߵՖТ5q_<rC~~_џGC$e%QO)g%ER+K`o7N r>wI`|㧨GT#`PMHD'ۙgMJ(3bFPHv[ " 777?ӧO}ak^gt3vVC2Alr-]a뙱\9[w*)q]m{B]ww()o leb%Ͳ;ꮮ*mw{l=3x!!x'< hFfnw3*k/"ɓUݶbU<q"}Y֓3D Z9uTr|;'<Fy70,(o7|r_ 6hV#^~<7`] a @hQ",B)~knT. ֶ}i=i9YDq@�rn=m>C/kmVur hwI'`}[9}.gV]cEsȮ~j=J19թ_x ]=)p+@:71ͤk ,@6'<8ܡG)pJ>BU{uWr@&#ay߄nT'4L"lro8ZULSepz1RorhuL!YS*>#}s7OoTO[ i==#_4,8}b\/2=ݻ=dqWc̵>hy{yg}rY0 /+3.u[ܺ3vi̮U-\z '|o}'Ӣ<Nt|VM 5)0k^eNv32>h0砶{ZߝS{vz-Q4+/'#ywO 7�ك 9Ar]{w'<{,d- jM)XBEda1D(̂վS rոgLý=}]<mF%;nXȳFB\ 12S�ç0c_Ct33TB_Av ^_^:ӑvޞCgD1`=(`,^Gn~2_v绍=9^lJBuDr1^rqsٟ̍yP{<y"h0 U;<K0]# NF0mo\gA12 я'Q7֋!FBK'@$1 bJ7!/!~WO~cwyY1Qh-ZOD#%+NmoQ}o+QJɤRzN <öL&Cd*S[ 1 %(-qYy˜^^�Q&P T්rds18԰ـBplBc[]$#~B if| >y—yq ! !b:KtL<IF8(JwOR@ eIa}b߸[o`̊J93c1Zw?Fp PD(,\Dz0 1q (! ,)v3pn'dޢ\φS>uvn=%b@G5#)0 +=~!X8c3h@֒yP3΂ T=6%Djzè 3O#yXQ‚1G4E %P,Tbԁ v#mS;PٔWg4=^@~5wL<ZX`$Jci=![=BGֹ@9g?{[Rg,j1u>^jR!ch{Hq`랳ֱ<"TeulLtw)ú2ijst;w))%q3FJ(qKU X ACإPCv0SfiE5BۥW{:&~lXā G''4ڞ(Y DK,J0y~PlHMQVM2c5GҠ|-E 䩠y{1/~־cݳlJ!rK}Uݻ:ޝm8UmX4O7wY>26y|{x?Ϝ1bgu 0/vh)ojn&[tex9yrw>HdN~1w9<y(! lm6{9w._'x{#}/ 6+mv}5\=l7۳: 0� 9Ar\ ) - ST ьe~*HloN:!CmWwg70pl\a:cMRʎX+6;CAU[DA?h. ð ̸IRX-WCMl|�6^W mCy ?"FnxxͦQW TaPε�Խ{]R=o7;tnPAr6Z3MF7ό_ΡS ;]u+sn^RA1Fe 7~g^J^svl@ rf&elϩZiy+jxXsQmxwsg^AJJ(k]r' vӃ)/Z, ^9B|G(gB~5>ɏ|kM|^8o〱ǔR,$})L=rEhA!8 WnTrd܌T֬b(fFE1zn!�@.Zv2FU FCdsK.0 h(* ΰ$+\e׾8)!_>Q> l@Ԃ5)'Pi4g W.ќ-\LJjU 1˛C z|X" iC \C}x3cll!0ZJ&cFV *GH dM9ٱ;8ukWw]SӼY6/.4^azbY9%L`N,B$ȰVaXqs(JI GK ¨ai$dM$c-51Fhngěo=`^',P1XN풉E LFvz[tl�8`AbZ3O{gb`0c|,l~5̮GGr;;\�� �IDAT!xCK ?[zsY:S[YhZ ;K)V-][~a2[CP6N0E{:OTCc%Sea9uƀMI{Lqɵ[OR&w |VT7=&N{yS*ݩؑt:1ZH1PÏwRXl F[�]AU4@q}v�<Y^K[/Ѯ~l@=FΙ!Daopvvfe2evj}erZZib*mUn_Hw0?fЭ;yqKw΀g߿^|Y; q5㴿1+ ݕ a1x붇ϙbH]'aHKM"FW-cזJgrIN#$,.jt"?_je�M7.yJAr 9AKo=[xã~h8G:S5Rb9d3r Ck8[rg'v kLDb!ܨC3fs|vо'_(X I3GGWXg>žk:Z0okׯ\8>=b!iފ;gyϚ i:ŞLǥX3Čbhyq>%FT\`U3>3 pnV~7}1.Z@)pFOrDEH) (%B}3-@{],%ҏ}cx,Ї9 ±9"Ks�ã{'+WVC2 pt(_Ύ!\}7kIWr,/_bu.wY-'RsZ80 -6<}RqT nC? .0P4<Y�9Pީ&BMgrN\z^>{Ayd )匒 !D)!\#b"b9N)Jb9Ḿ b箣'OHcgaʒ |~Z~yj)$1O NG?zY浛.lM,B d t`JI  fLc?Q~'w;}"BsB ,N@*#4B(2XH0@ �y!!"6g0~Q˻=oFWwxPc)qA=êmˮ/Trx&B-*5-RH F P,,7_.q1ov+aIv% SYBbku* "|D"%%C؈.`9#�ꡠEQ( 5D}E(~KM‚8�ۧ3l[0ܕm/[n%ݿZ_jYP Q}*01! ra[Y\nkVΰg.65Gtu:NaA+Tߖ: !ZǢ~ݽfxDCӝیaujOsխ?X@\bۇ"j٬}]­8N#_W8}fX.wsJ|-vŴ/t;TƢ* -5,z-11ZPbml naP ĢLjVڮ38h`^±}9B|fuEl#'9}�RZ1c Eٌ#osou&|PMOK@O):qѵl9ݗNg}_waޯҢS;˵ Zuge8�}՝]SMw!h{L==t4?Oιg;ؽxqT7%+Cs=Z*@u tŽ][-/98O-c `n:t2y@ 9Ar ,ǎ.T cGr  ꬦ>Q@4Bmɣwvg}4T7@8K.S3Y` D r8əxb"_(xۯoa0ӷC@]t95s8L`g~GN37g7>vZ ~Vc6Ct]ڝY^7LW\@Y5@rup7[+H\C|W7[7I',XGP+esocLt,Jq=Ɔ[, ޽ڗC>d.{\p=tAByll8"2Lc3Na˶q HQBRfݻ&g,e͖NX‘@ȧf. |g_ş77x[p<r.hCPGu:pu }'>&*Xo܀t9trHɺ#fQ�659'Sv,/xbA\Y|1߻KXtZtG2֜s@ "wPX 9zpY.Rc8ؼ:$3U8[ob fGXa@JbB0V#D2) 2PF} k|_>'ь>RPHHed(B#E ,4P4RdQ2QF|ngAh,5<de9[XڂRYa�et=T6go B3_`e5nF@l{b]W,thM-f"߾Ýw& 9rt9`C$kMe$kX@Pus2g2+E@G7xX;yڢB"₢KrR6P--$ѕJD%ej+EU)~L 趥rdž`Li}랪{~ob )xJ41sËu}Zp*X^t`VUZy_g׋NPuϤU=ow0uػZkG_I+cyjx̴Vٸ//a>TBH#ģnb>%*Ē*S7}Eǩ 16vln1K)zi Y! Bڹ [n`3bSEƂb ўY--շ2xH9Խ0ݯزe}6 !L9hno>ʱ$&\>+(TֻHle4h[֠=ArT9[_jw<_ҽ=;iiVyN]?x}^ܦ ߗǹ9\G鄝x6}cKz5ulg6Du-8D#$cjЫv/qwS~#zItymvq)r�dr 9AW,\ sv;aa {U ePV+yI<皃ۇ~c=Ҿuj`jō7x ''A&v<l++,I @B%FC%A1q$? xWu03*;m^wtϫ_3Cd)<~Ā{3Y;F$,'tVSo;ĦPӉkD ^'M |p]J"2LR[_0c)KI3 Ҿ<rR0fLsr6kn~ >yӜ8B!]aA< |3u*9'b80V&0id証e�lk,SX~Q@iR0A7O›o|R ~|\}^")%1ٌc e`ho ѡ'9cf=rϛg�E:31La\"<|BAd}KrR [ Reor8@X\s)O J%όpWODAGxIow�JYRDB`)BLMP1b$ny/} ֛=b,kT s CXSF%+Z6l2c IRR hf\@e!GǛ8${V'H)58ߖ" yyZgZPYҝ^٦SpN5N|C+LO+C]U "6Z2pad"AlFim!U?J@URV Z(>3�+W @p@-e# ]?}䖋VE:e{+eZUh= ZyJj�b dڑյbI2H}Aظ:nO8եhuPgEΥBske@PE)miKРN״6׭=Lku&GVJ$]\je1Ps&vTc<gM[z@2PIuWӴ#ā[g<7~4XsڗX2ZX gVkB?\:;ޙTh}-=T' mFF@$ZaU;UA[&`(G2vѶ1չs{clu)? M?@K[~s}#}wIݦA47 ORή{QM9̀St}s֬H]<UکN֗ٳo8ֱSkOuT�/MݝwyyC[g,Cuhuϳ \Jfuʼº[}ӵRVVE\KG Zl 杩zzwTKa=Ar 9ȅRE-9ש&z.J644()Bfma f$nqToDqzl*a7T{BXPsvnB0fB  9=c<>%!3oݾw1ׄs?Ps^[BhOaʕ4Q12j!>q;ܘ7}{b�˞kPtx=4sѣ3Rme Oz056 C* q@1fjj`,, #Wg{L^U\+b׀L4H J0xđ, q c'  q IF}%XU4e~wwwsoX,Rm*.UEJ o| xu>c"%r?#2gF xW>&!@? X&W/Wx𱇌tqF8(V鑍_ʗ_qq &Cx_q<sn4fz (ZT\(Μ`eyQU88H;C@QBdQ@5Y)yh; ر7odF>~a5`g3ՅǶkQ)0w##<7b&g6(X ªXN@ FTȺ&5}q3rQHJ"DѨh k Eh>H`%+�TAl<jI>A26/,0&%FgU#r@dr, a䄔6MfkCm֮=0 vvX"Zֈl_0f6n6V95bФL1vSC4Fe~<;c<=Ey$$:oM)R̹!:觍l<"B}5<,M~ [^9;VGgQNc[5{o|_K @^sw?D[TXZٌVm,PAbI@0s-m5T`ԜrO!OqX{A)d2Ǝ鯞2`DHmsx3fZ3m>ߖ5_lAA%`3Xd0&mX4CM އUԇ;u!D @Sj-AiI9 $b"sJ:ElƸ %뛣k(Ld8ܙfV)=gy`fLQAcc_9j;HYd':{헑@ç>GYwkdw-oVmvwE?6O]km<Ad׹J_iySo)H'T%J_ϙ*g3ޏ_Γ&y➳Vih>JwlǍsO)35.<;޿ { 9Arn3X{C+! 2`鬭 yUPbu#_ Mgn뙺);Æ`Ca2'z8X;kxaU &CʵO$?o~ hfP!p:n89;e!0XxvvC[x8uj |~H>2=/Q{ھ JS^`sS~@wKJ$[U[|!n{~ 8 vL37fዻ9Fۈhocv ̎XBED*XDyI҄aJrp%@PbCx*c D0"40vŮwAt??o姐-wHzj%"ē׸z@/r{z[*B! 0߿} Lh~k0/tcp(" Fm$T2IK˙KexdVBwߜAό.Wok1d͌iDH-.dcr�e@1VaTƼ  G #<|D, BD]B*@2 {5,6vJ&5ǏOJsffdFEG9x40"Fe#%R2CL]I_�:fY]A,?btR29l;뜫sݫ:ymXrMSd#+c@}65 moYq3[,GH)تcYD!: X9glt{blȅ,XTZ`z('!ץ %yxɁh_mE},֘jyT qfcսܕ[˭x\hЁS@Uɍ9Z矲2_Zy]'fWZPh(-ls%R Cbx啗)q֛de̜}}vi_nL}jmm=l/-D)FsV3KRVnpȮ]uY{Jwd7h1֐}~&+Aa:|(8ќ9"|ޙWf 𝇍 uo;v|Vd�bvmc>dEenޯԔ;McR#vf8[z̦=k/{iM+2`Eh:Wy?88Sb{Q%}.=Ar 9~r7SXx"R 0FZXLm8VP8oG!1=G`" y Vg9>>�� .tߩտ#Ս׆#~?q޻7d$Ns  &%x[rj>0>L$mhs݁y WfXWv¬-K5U<Nf:|{g{-ԗtaYؐI1<2ܿ{;a03.de;65WeltuO~q=%E)@ qE)[{(eHBJiȽsSx VG ؀j9TunӬ]T-(rf Z؜>?o6B\xrG.ZEM%^~瑰�� �IDATB> ohsZ,P}h~-<r??CK5@-mHCFk YeՒ|#LB@d| d<ԭ;w]c to@M36F$DSA],n"1jHF!ZmѾ0r’Űl%b#62°@qPC 1dX�aXRe` 2qPӼ$E Pd`?U_[S2a;sW?(3OymKL)6rV֛ RN怡 >N 9ݣ:.q奰1@ ]ߊ`,1eƔ}c aΊ%c# [ r$o! @B$#zIFʢ6)61,Mǃ׹FU2f 3׋@D"@l~,e;)b?LZd >C0c2>Aw =kZqu϶!z`kuݧEw ~/:Ԇ"\ɫZăPC2@ X8ۋ~l3TfZ,g{Uhg(Z,"iHDgC >M J5&P+f AL]ڦn9[ƈލaccVv3y-&h4PQԐe_+K0wIYƝ>ȸ;Iv^d}5؛2)lu{,ϑ1Hy77s Kz:ϢLik5E\ǝfW?mgֈCV>j3|jekDVe=}ioٞ6Lsp1Wǽs`떯0uj>GgSpo]JAr 9Aցo]+,y qY[EQBakAA8vмuAҾ|a o9"bݰFfi`@V6g|oۖKR\ ł W2t/>od V-g܀pѽH*3ZjX!0=7nOy+;^S_38(DRJ,�c&OXJ$hL}eU6s9nTځ`BBC rIapPYXbLh $H$0/3Xa JQ ;OI &jaAc~_U~B)<zX,M {"ea~!w6MZIbbbƫj|X4W(>[}m4L`0(wDwZ=`-f\!QC)%3c !]wӦ"Es޶f4Tpӵ3՝tzpJ;hU P"ܺe,\<:ДCb,YPe,a-g%@ *c]6^xXyBL"H*(=C.]3@bw0H=ff3bFңFBia۵WI-v0y_o{6de[ǀ.%x&j$OEB<$,6- [HN#s͢7 8KRBY\LwLj#VK5;0%3\“yuE }[ D&lʉ<ee-x5aS'Jy{ti4Lzஆ=j]0Gep8 +Y]%ɴ,t[o 9gRX2GDm]scG[RkvjKxp QYC:BPf!DIb&YnZӭSV&mk6:Myd+c4w 07!Z#d_!VB׾1?+|op"=ڝW/*ڱS(?kϕ굿h;wC{w W2I{+_p]VWV2+Os Wcݽ~}<S m?k- ub׹BT}~B^M5~ָhnu~$})]$^O\pCAr 9AJb#_ca3$0m0J4ÓJfj C!Y3GGG8>;l6ꉩJV;TOhg+4K3;ؚK3LTb e9/ I rH,_feY,|r(b GCKQ%vo0Jaԙ!ȕlRJ,15.T/؞mV@ } r1H3xĎ4哌17e^m7@5LK+oht]u2}.<Ս~p +֜vJgưZ,ף3C%AͅW("b)e@Bi֌A5agT)1[S$#JZ,|q!aٌHk]% Jl6d0 A"1(0pA6ИBd:;-Jp `xw~o\q:pgqor|wLaoHdGf;T`:2PqoĄab:T(z[xow@Pc(TPS s4+8MjigɸXcZ�\_(jԘZ"B$3hǸlV`1DD ā""`2" 5xLXqg2[ʹIoSIhI Sh dd;IhɨTZ8HB4 z2384Y_a3.ModPH15VbT?MlDa( :J+\uyp7*l(!M/v*?kc>4㓮)H5UO!+"%5`8ض>9:wzb!A"DR\A BJ0}N"Bʤ2"j %wN2ܸu[7nq睻<9=eM&t"~ uR'_}=)\xq{vl= b a)t5M NBosb;B!g5Ѣ=]jX9 gˁ*E)d5Nع80(r+oT-ptG =oO+赊:סYt 1]#Z2HsE$ l<�qH.<IMTs Rtœ3s|Q O; 2i|S(hr?G)- t{1SJBzi/A/% \,W}^;Q <)㫴ȖC&s^)Ż`A3j 3`k/vXs@E7CЃ}Nz0ݞ+3AF-t):_kOߩ:MJ"2k#?c}}vm�?+zOΗߜMڙ[V>"~F᪦A)o*[iWF=p)ճ]VڌZns=a34v]s;MGw(Xc=<oWMVu8{eO?^un.V; :`Bq NƎ &ϋzQeއ92s;jZFI@^~ps 9Ar_I M50 PjD5]de€0p-G>\|8/80HZEQ!r5>#?׉̖>g01>A?$T1̰8k?wnĀ!e`X QYd!zG?j}ڵkܸySUЙ 8AB냣+Gܼu尜=Qd hѽYOIUGD|DBdp#]5׼dFO`\�l^(1V-^Pe%6{=BvmjPjcŒ0cg˓][Tݰ/./aA~fVX5\Թcbq} "ߐk+H声MbuJB֖.CL7&vٙg֩PilpŽٮ9g ]C4tr*{=XO5�̩x�6[h i ̎"3B jEZ8[."z3Krf1 {&)!>ZZkχm 3Q/>,1>j�b"Ȁ*ȸI>)+Mfpmt@C,EY!&eHgyև6q#{i.-ܬ:kp!4N/۬"vQRP!0kr˸wѯ5wsΙ6ݚS縮�^3#"8L S9ks)bS_i|YH1#2[}jKO#ܠݣ_&^aB:zDZ:ٙ!Xjc7<Fb\zO||=N =Qώ6\@F7Bܴo۲'6 kr@#!@4'26([I�C4y~e1 bk>w2ť' 8Xn{ hWnv=$Hs~`L1}$N1s@K=@B4ǨֺF?"U<$m{ ghc=1 dYp->Oq5΋xZ3B~P^X|_$",W+?~3PE3ݾ__wMsQw5ümC}^q?۹ggK& {NV4upzž=�C} 5Mtgϼu ˝sŀ枖Yع"ѝy־g-Ar 9AK1C#{gvsfͫ4*W]h/+s3j曏]M uYmBbaX.y?i;&'s6{� �l1ժI.o."HkG+!>;FTpiFOɵ{^c9Wt)-tƢ^02B V\z3fO @f hTdܾO>qz&qM `G޸ 5b1R1#;VgPmSͨ:xvKG"o6R5` ugޕjavg4E65̶3*J(J!iFVW~F8>>'W`<G!!}v+׀tؘ 6BKcTQ �s^jS3>iMhAXWYkuM�:1憠hN"~ Kc^eŹ`dlb6_{!]%zsV{=1>.Z %ٸ)8XNs"PWٖ*,?B[<3㪀<ztOM:DwNSVI@A3̡H+',,疡73sSMo(|&mft[vWnQc=8j$5v8:@-8eml39Q5cǜnXoj*ؐy].e =Hd&RWi;8_C#sE[=>!RNlMm|{w1;@}"S йAM<}>ߡTî=w_ԞKKmE,C%xA(U�1YIid|ÃQ-뵌Igp? )-!o}b n6HVv;f4JXխgF z,Dw O:96 |4 IPAZ|,I"nTr<m*kccͽuFL{?geֱo _޽{8PUnc{Oe[=S\<$*o6^ze~;oW+7:t5nL`͑ss\E ,wXNЯ;>D9ޮl[v4tcO{@^F9kkz8QTМCy}W2s j4} �>|1wON9+G5p#]p}Zr:fnA,,ZٛGXX.k\z 9A!jF܏׸}6 ӡ D-[,0ݚ\ 8F*(Ơ C 3 -\g{;6�j=dho}vW ,ѳ3G%kb v)X"1;٣7v(Pј-8 JHc]/ͰV7BBeMf9p wP7g 4RjEF29N$gy r/c}o: Ur);:ذ 4baXh[_t l(Zf(oUn<\5u H;Vzxdf7^LRiƟ`Fr W;Zf}S UwJABDDi6NG{Ɗnh 燕ɩCjbxxH <cx?έ›oA~5Rb"ұ ap 㝡@?շti-<oR kyTa@BW04_ YyBsO7ΎuPRg0֋� Q }{51HPئ_Ӛŀj:hV0@${}1 T6]iVǶQxEp{n<4(Csڝ]=zGYQp(UEKepK3D΅aX\vfsPakZHA(%9E5#LzmW:pB2`a ۜ u5!ձ잡)Hhlma\rzrwc[ 0Z%ڛuB%UWe36ة u޵ ؄rjlr"7<w|Pݪ9eY5./4tnO~7p5̾W(> ^n@Eu{Ojy;wTw�I+BZځE\Kt.9 0yófGcvIu9W?+΅6?U~}1;)۶;�OT. TW%غSF窼Ysn?aY<uL~1Mt@ow�tL1`s +Ouѿrګu0@sɪ\Q|;?0w У}䢼u_x!߾7>b` IkvKջyI$y|aauu˜ gEutď޸+7/?2>; 9Ar74~k;֭y$$b[(Ǣ%mю91N6@byA12 ERb"iQbQ$ERT7yW֞sέ&)5@ݨ {X{5T7kln3?u-n,pL^\ܘ,858O[ ]4yըSV(œL=<_�� �IDATA {}E:Z{ $9Rj`>yy "μ`:Yo4a-I}`470nn \Tө{OK`4.# *>NAຂ`A reQ?u4݇٫9?+ ¸Hh0oQ1OjDp9kGd]e:HJ� Z%C}t&{4&>)#4@8rM�R7,v,;�:~>:'ws?" 6G5�Н6ttKܾ}'n&<2ޒ^!@x(w6$ӂI:VʢxbG4WuP!UGd=m{?0('~gxo㗸|"uFNJ熣-D459wR#aIDPl QD")S眘)HNl<ռ-TzSB"3^U!|g!|IuAPR@!jd29(اG"˺7�}UAfdTQO<N#wn< >GGv!O6]r߬9=Ay~ !!)BA:2٥YRs9A%S:zx׽U;g"膴&D z걡O&g(^Py4g "t:uGyQ"�ev,",<Ϗ-Dr)r}PtQfQEa-D:<ȇǟxW^*7>%!{:o;_<m_)sE2CKw6-_3RHؼ bm,otfT$b \VY$yJSAhl!NO* `ƾޛXzz 8wc UIZt9i~ٳb]uJm-2j*uH{{q/8ϊ,1| -"fڹ2_YRMûlD%8}fJݷ":8h̕W>d~>i[YEҖ..r<K=ⲻE.7˳ݞ{#]xwGfR|yzeVIZxWc{#cǣ567F)JOP,&+P~rt,Vo߼_ҥ|1!(p0WmBΈSZp6SGpMzU^}cv_o.;<j|cr\q9.帼%s|_͍B#·VU|=Do`gP̦=”uZl:fǍA#ye�,j6_:s 0lROC@&ni!)g@f}0, +;S(7#ι4wjG�+NJ$DW+A+@+�TI|�(Ƴ6}zڽ6CPR>@|bK~!c9wI~_-DDj&OsԖ挚A7҂M)7cs͞8%i@ޤW^3P2r-,] 9}{0~w?c'Ё>`~03,*L=zFo�T xZ=itbi#J :L`,ρ/\4:!*t u6~mLQtrΜb }aڍ}yOUB`*2Z,ϒ惶*sJy2 !ƺi+YfKZGBL5%X= gΫfb( DgP m*Jk9Pr80F8V:9qUu6#U }ܩհd~-1t+WyfG[h0ym Vob&4p)s֨="]%5Fq3P>jD4=dtzVp΃L*5B/Q9\~ކn&w_ʋd]h�gHφ3c@C9r^CgS}ٛ-Եƣ~ת)<Y uƼ\�ySCBT+uW&Pr�=ʚt"rrc8ɣ>{<t"/ W]z|1gB1XI#ZJh҅GRtK |^KJ1X$_:VR!}r]S:^ۚ%m <5!6\<WT^Ff̓4IٳC]tS{vG242bjc[b G.C0==_m-GUffx/K;GhuL2 s@Fﱷu)3e&vqz+Q=} K|TE\-+>oao׾%ct6lk̸bW=G}W0#pm?f߭?=˽nD|O?4>|k\z, @Y7ku^lsMj˯;?rL6tΗ/N5t0l뗮c76WncxCٛ)wœNn-ܜ*3g7؟+Qqڳmsf Qxsn]1?#̩kq9.]X:oawn79y26%)X�@@FI�c?.#85/j蒗Cp?WBtdΠ wK}=YВb@Ȩf/ O >y:9Ԡe1TҦ o('tN+$0Ji򘵏OFcm[ Pfn}nH%y5yT@P1n@eNEb@BRq0myYMESؕ U4Ii p+] j) jwZ+:GK/e˿1vЯ|2{A&^b1w! ac ߠB1U!X7ULsͯiv& xTp^ dj=)X~ CV}/w :<8\Ù:wsg_oruFNLr2gjǞ-ӈSW2-hGo%J2`mw8;bX<H}&.IрdW+LJ91b3G<y$q'rf*9u)61 Ώ8#&.j%Gɝ}.yYDFllnC}�T&>k.,yiˈyqy2q>uZi51~BuUFK*:恒<H(Ҳ#Rl`|6IcHԽǂY Fv%]tRN> {o^\ i-vI]׳.=c^K0i7{oD~(ݤI'׽_9'}`o|^/ ܑ .D$8b<ē|._s۷o1z:3 <#DUx.ַcEsFʏ%͏XK->h냭5+.x녩X8\4<jaU_Ζu#JHtҞ'.#r-2.ERy|$I9i{Sӗ\2Kޫ1ݙҶ!Eu9!8ڃVܶ SheMr0\iwLw1F yBƋa9Sߢ$,Q[ʵ}׶o܍աVƖpE~-:;˰5y9^$m^˵elQ'=)zwnoŰ6bccڈa^zZ8rZ(+ #;9߳׬l asp+7261!扇xjgH%Wo88?VO?kCcCcϩ7k(r;"<)<+ow/|<8ON]u\q9." T203r/D"XQMa7 3@M34ޥn}iZ @tl!rʊ rVIJ!?~Jؓ N#4;DēSxҏRx7{k-A8inyHqh],w#h򢕂ޭ>n�j뭢q8 U}XKysU"?rAтq1H`B7tD^FbsE 04~MuS8yV$�\[IpO;,_R"B:g՘G>1yX(cmHNaeݐ!>e`g?%pG' Ν;/0 D�!.A8w|u"m(:$E*J,{j_l+./`CoslL(/45\426"KeJjc2:IѮcYw^ԯCFA- P% YuTޣҐ%G;P~ye3T@|_v3ŹL2/xN$yMyb QllnpzgCPיzAɉ<pAn֝ۄ>XQ }hehaY苆P6nݼA Jxu9pQ Z&"5SeDRh*b!El,JSd;ErTBOs-/QSޅyTϺr۵ޗ{Dፑl] ˢgGN}}*'<G4z8�_Φ1Z]YgĮ+:^nRud=v&e28Y慔X[ZYAVbQ�}Up7wp~*/*?W_ƛ]g!!Źs?(oml0 .M]˂/Uޣ,3JZT .yڢ6RmK'Z1ߛ|qɲ'uW٢ퟅ2%D*W[8b(a J Gf`Q9u}٫s߸dke刴PɏMS]!]1^[G1LOqCƊ7.#dy$i1uOjK1Z<!4 ѪCKuHT;>}|b$DM7ky5!<.y`: _S4cudw,ijۢPm"Df&r痽uQ{ug)jlRV?Cou/]/wrI^}y?orUv@S(b3u%J;o%\^%֭|W߻v߹|w̉mNn8NUxfkgO_y|QvMxxg88FlɌwmNpuwܞq}Ϻ8*p2?p oo=p9'a]/+]ϼ1owr\q9.廠d`CpC廓 L6K!q#y_pĀ\"ˇ#M"œ#ErX6;B<5IX^5*yֳ- t佘FSUMV5yh�~S0 #h4BDgk"{{KhÖ P[n z.V*]>I ]%h1$vK;�3Wzv)QIþv͉UPAh9bcnd=FJ[l0c4O()Rڀr(-`7'.B&7h6CoH4uh@Xr%+Vn>񛠞?iTC8mcٙf>?9䓟4oywh];y8w ,㝱mdjIfb'"Э?8i=3h\=*yEUBT\ Im93HD2Prpkn]&wXnp1A-Ǟ-6gϘL/RY+Ii`\C*asˉ?!.x Y1y{jO@ nqF| |t D#ђ>6˺+o#<M03}'dCm*9kF ,+7Hss4}rjL1pni?:zҵdZ7{US%bR@Hc۲RS}Y٣,$*6"V8_M$ZXKFnE7i'JCi$5i].H ;M!dk7$+"J(6qɰ+_-24xikAV8" Nx n^gK'猝'J5WP5i*PڣG2>si$Bɩ~vwi rN*<B 1"z^7Ϸ`'T'b COצֳ8; RrƊru= #S]BmlwUuɾsHa<[1`&Eݧ}#)y'yw0_{C}C:/ ª.=tN9\ļoK!Z ǥO5}}:Džz4}E_2#P"*9=]7or1);ôߓr3]6tyoꭁW||&IT1V ВEdU$ _Uz^ǝ`KwBMsV5IuBko] ˆBh}}mf{}ehN{�,*UD}o|jxD&<ܜL8! |Sڝ[<#pfm l;e_X8=<ͻNm=ܳExD+<vrNs{Ƕx.c} Ȇ؂=RYtg4?}O1r\q9.ADIj h_WQ i,^uˁYEa$D  _Mh9' ɓ;<qy5+yM9Q[B6VMyIj00Ju)0a.jjk έwΦ9PPR~3@rAdq<X>k.fz`|iC\ 06]6fl'c1pzk2dr^̈́3/A״Ai<s gO # žjdhyZ nMƣ5|w\t4r)0�*ds+&ҧ7nWſb:Տ#3�`u;oQ1uDb6rNH/o?dJH&h (283�\Rųsޞ&XDUw)_Ǩ| j29fՙ1@<t$0CQ|& Hcp`pm|!Ͻ~Q}{W797x9nN \ϋApӥֺ-K} jw8Is,i eg1>UΏ#!F9{{}My "4ל3ƈW4zFH<w( wPi`yh g5˛32$VU3>ufylcQd#C ):+pv g![U$SqnH�Z ֶVuoU?jU:C;�� �IDAT.pC0:5 ={6%{iu#D8yzTڜ Y[c6n`9d['U(bzw^ʅtauq8Z)Jn!E NF6䣏@c Nz6'3:mj0/YUbd~>_ٝRb4Phٱ]|"]Tf:YPƪ(tYFv RK$RF3޴sB{T[bE&H0OF,iv9o;9)ĥ:9y(:Hc4nA+{9-cvo?&S&S_F%[~زke|(KW{X&}A0գ%}ƴzDdj&%_pJ9w='FwlU/oŘlqZ/<_J7N7T]M )e$6ʍ7d[}dU' jY}xMIdD"1ɌRlmnri& lRelR|C,_L9R*GVoPdxỐ]wY6<QzLw89&7u5{ϫ.s #'8+&8\؟l8?N8KWwӛuֹ:9D$nrFر<;}Og8 q/¯~Ϳ䙷Kq9.ZXԨ%޲u> ̈ST X\U #y?Tjp=+^$%tЄj}kG@ A *h$klX@KkDg!;1{b1%`WTpɇ>x/)qKJfb,^3�56Ʈ-Dk))coH2<YMY<׮/<8 s^imRߛGDe2zu3# (j m\e ,DeCnOQ0_t0[gtH+JzO% 3+1IٝD)*O �}c{{ۈ[ٟ P}"@r湐A : ZЪ\Ͽg8y~?O1֭[X~dl@Q^|^~j[In6%7Ec?TFP֦ WW#-h f<62HCT(;y~T$CS~`WRΦ8AЇ�^"^}uz9Rb8Y߆͍ni^Ƨ>9^zuMNf`?˨]롈>񈵵;swpPB#DF+cINp;KPq}DOawCď,lʍHTs t_G;M=%}FW% p%X@ao/r;ٓ6bxuHJc2Q ~PUe5?V]kw[Z \FrhIL0i1*;'{,65!l%, nIû"\ÕoRgR)$I6{<CB CG;FMܥS6[**; HR 2lܝKVsEi`^Ș>,Z= sg$h$#%XX8z5Q%>[V1ˣҾ?F( -U$Ea)s5c 1�9Ɗ\֪AF[#%dڤ]~(UX?Xve_u{9=b 8MsX+w nY*u]G\DX9isDcd:wd!}lB 0ָetRec[d5wYl% 4QFR%/mȬrEUŒ+oϫҠUf("]*ӎ3WƕF^[}YO=s("]a8:R {aRL& .LfXښ+Eyf9({[EY4&N͛L'Va،Rs~{܇KRɈѲP&NuZ,I癯~Xn[,q"7/yN֣[]E~7īg麎'vy#g9chd9vN>sm~"'_S9>)Mx|s3fxhxDT /]t>gј0Ń)&s6!9Б3Z <7f=ޙFı9Nv/^k#zr\q9.oL$ktN+bi3.%sH�Xڂ:,XxW !J>f@.堲' R Tqgh ȏ_O=!FBp ll7@#5ISTQB<ãsa?J7?rzoaI[XA�v~ѰX.ljZ Oa%]P01aHg KE4xYcn%yšV1hBy9D& !J쁚Sh[dL!CNRl�LjV&_'O31$T4ykCtlޱs$!N';w)?`SAuLg=_buXk(v]&I*!0#9]] /sO} g<z^yco3-a"HWBآ2~#RHM .JD$} k*>ih? ҖJreCN2@mfa~}!Dthp^<=Ogt]GO12r5QBs1I֥`vNnG[of"&pY`�{<x?o 8?'|泟+c@KSw1h +M҇'x'y{<ˣ?;wt =oopaJPr<[whɛpn4F܈͓<#L|qr�an0sT-8>9c0VH"P`,B8>v]o cc L'@rTZr%/B%Șk i~%`J ڟe5A K'ݛv#9wsHtN{ eu]+{�\{-Yu? =XQ BhevŎ~TjJg_o1WgFۇc.R8o&y4`_o/꺥pj_r_IRb5Bg :(%GTȋK[#"fu0D!l?a䷦,۟CN`պéx֢"c=Z@ $R;_:(y mjaM +O#ivM檼x'p(!Oqݘ=יjuI5JbU3蜝B4bץBL+-t[3O[9f+/}[.3͸r2:?L^Վ&o+$|_eu٘4Q�<jSpNI2x6Q#NV*qK+h ii⋾Ц-ɟuYX67hB^[%p/@R96G; h bi?5$o+?! 8yMJh|vyi 4^E i=]K]wrr%&LN{Ơ'jd4ԓO3+_7lFL-8[]- (]Um=j^#e)lu*L6qo, Bd?Kp`z&O=ܜ69;O`۾cewW_⡵56F: >O3;cX<1b#~so<WL@ 1SV>Ű?@"q\81BPvkڈkM? ;'r\q+Q655h 7\^oX9'$zH)>9�<k1\=!|>OVQҵη"p_%[WP4{:A#{bc@yH`<ҵGZP߫S7}.m Q[0{\{qڴd 1_ȸ6!9cd �QkMZ4m2c?xjm.y@_%y:ҳrC$ՕҊqm~ϨJHGKxR=!BӎC{y!-cnbXoq_Kk|cw'|\zu^k;wlnώjn? )7h\IQrRA,%k 0dz]˽[R}EϘi8o�XM K lnn0-\=Nl?Wx,=C`1xbelll<ًg=SE*&\~m:�???Jglsאvg |+_ϲ?CZ?t*灃3g \kwaT hxLTTAfM4 ܬQ=}?Uby\Q̷8"pv3y"Y\v.g{._kt2P�,{B.)$Rm9b)k|</%CG\AkR{(&1$Ó-,lmdhLR{SZ\tYIKirVcI ͚) _V˗-5O o8,4)FOAd%tH0pHpm}S�ly+{~|X˱<m c!M$r'̤|'RE!F|aY,Iy =8WHjG:/e{r=`xy귪{ǭ}뎍q M1te*rRBxH,Ql>s|ůvFk*X{ 1>(dW,%}A6.PF-I]9ܥ_W̍ۏ7s "kg|{%p:D"k;'[̦u\=8agsldTr+J�bKRM';=>MlE(:찊ί:^QEv*{)ugeq4Qwܼ;|a$ Oixo֭yQ҆>*Tu~nl<׬L iu:v//|e.LQ#0hF;;oqgN '}뷹p ?lmm2#N/|ŕ!cx:^'K̃2Fp1 NSI09,3dHC40͹6xdci O]~\q9."+aucȞ&3@hR6ְn͛!,o^z s\OFUW$k+9*cmm,)<e8^{8N^ Gј,- ' Oj?#[lmmp )(z?=jI<<Ȯn_q+>αO<;{ӧw?<\|o?Oe ^|�`oH ,J�9Ņ^kC17%)XQ8y$(3%Lq}d- v Cp@w -krUfI/ MQN QU3b%xjUZ 4ɟ1LF' .r>emmLuL&SyKWԹݑ= 1XT(�u{-Io*v(ٓ+67G+gҎVpju&{CI*(9z qǣg.nl?㳟4??G?e�k/@Bwx]H| ܃x!?Z_K1*B"1dP;;[O AqT fSGt?/_7~qir_G1F6~*L'\zkW3X(8y,#8g;8IňD[fbsыqǝ0O T&Iy+S<E`$\J<kxZHY$0*R-?gNE nյz),N6޳$~&=&I*SVVyT.O s՝lb]cs fH@Zuya4\t:OM^3"R?;WRb{m-q _tk,W{{wy,KnD<{Mmy dm{!|bQcTJmNuGBz M M�7J> xc5yt ]"bYmJ{4eec)&6=o([%b;=ywQȇƴYLkjHlౘy ۼ7sX5"!DW"1D#ȯUEl~Qd;= 퍍֙L&?wɄn4PxFguL|gGjMkS<G&+H6^r&,8|;neVb:o$oU߯I)F#;}}5ْ!Ѩ.RiEXgcrJmtʙ&G󨣚U%ȊkH51p._B)UGg߄l$W!B EE?k$oEYyY;koٿ;ܚ\851qvױẉp!>*(0\zt|*39.ܾctqׁ*<@Pր1Vep<8=5<Pz<xL߫9 SHŎ/S$G0釾r\q9.|''S*E]٭^Z Tp@-3J |$3mf'/+Y%,ܠjڻ% ְ Xj1`@�9i{tNxS<f3yŻt-~DJF%Lr(WKxt>?<#db$+׹v&|޹"<4<. AW|L<c:f5X[; ")2ogF-Q|fMXtoGS[TSK3b!T3@ɩ`mB%}@">;&#!+g]&cpId&cpA= ICLK eZxe5 (FdҀ<Wy13R*H@r-#Ι?%G}grܷ:H=f)MyǴSO?s k8Ad*ֿ2^9>$>3/pLz[\qHHs;bSgtx 4Mky/8y /^0L$j`|2{LS DY-<z2&M((ZQQRK!CB,&3<"Q6�BI\~ZSV]=Lzr3zg+ϫJYt-H7M~à K>�� �IDATb=Wx7ڰ\$U9$ j9c?eŌ}Bi6Bx@@ ltB'NYis%sor[Z׻AR˜ۑ 02԰3еY5OUS.OZT?!bR#!(ɘ8[s{1A{Q?M =c�!HQr+h:(P@d �y;YhNs$j) C&}5ZP(l}7^d4ۈ[ô-JA^IdD@LjFjxgODݫ>W)9I9by ]2K^9aF?3@Y\x .0ւs'x'X[s4nyaj4cxCɈFSh`<מ}ȞKY;r?OZ\3HsedĪr ,\fL]<VlV>/أ0姎"Kkq<uzdT=jm !)gʜPZʈOQE,:s]3{%<~w,!_x$]N9;;1 Wu"a2go7o1yE޸?t+op'z&9jCs#ܸx Nb2焉 >0`}w=:<WD#OMLaĦ2f1rc`;e29~xue}Q?Ǽ �}[~>[)??r\q9.p%=f)H|8Jy-B^n?z/NJVOnAZ|:GՋ}|I,b9Fy}sZit)P Ԭl2a3w;pxx9|"eY=<f=)s"xw*}?޾ O?O??S|ů}kׯ3g?\"+RX{s2izŨ* 6`9%t0+o0ȄD&wڲp޸6_A fkht9/MT@7y\U ɣeTK^ic<91)] 0")@N�&DXN%8QƣڈxXx)oif]' C� !鐬W*q~jlۥ8+b|u UKTyQ#׮p}c6~??#|x'9xEFǠ/"}~vai??C *.^se>/_ S'?n$㛄=A g'<x"u}; M׌DOdjЈs'Xn8!1A (əZJ˛ȁ2v!8[\ C/y\PC*+U,U{: :l,%g<w_qF)Tk,oݺB,G^m[xQeiuwT]3sɡp%kD\9'\SBL+0ёo FA28I$0Y }kWx%֘/s8qgT{1ҡ\Y"֟6iD;oZ^'2*,#WoKzM{ d3kU |%[SH܅ܣY:gc J3=yN{S뫑ZTb^*!TNR$ W3umDKEW$Y-okHX.0]&[01kT󸓈>`9glI :ڔtҼIìI6g`jI/{Y,gbA&wegFǪ7I6$ǥAËE*K9<' 1p b,xG{XjKûˣ>jA{}غq}<UϹЊbsY=vWYxM._´)*sM˔A\zY (6=j<S:oF}\Yy_|q%T={_rafK$%.TH]N,s94y5k foP]# W_3':ݛ<.3dވ8͸u{kwys8~~} mu4vosby)smʙ-ϫ3n} 10=0s|:<[8d :yTҀd}&<{\N2bl?"~CW^ooryΞ=[>?u}{yyg^x__/ꫯ6=ܑ?;g1){\q8{,.\Ν;'x={{{?|ƙ3g-ҥK|_.zw_\v} yzߍ7pB{mmwoQ< ~RfW3TLc .BM:U X+"]dGMIRELIl )צτOB \Qrcڸ"Էƒ1!0M-3@:bbr;ǭ[s; Obi3bKʡrdo is#d@E'?{T"sF#%Gz?Bq|C}|7~_O_fvlr} 9Z6uLكȈ4n?O}s\󞘅|^U-`EKER>6&A3Lz@C~wԅ-&mILk8@TkE?ga|A w } l!r0r]]}=;'9u$Nmsb56679u4'vN i*ٌsmy]W}Yk}ΝfY-ɲ,,ve'O&tp*BH7WbTu%W&/<B <ay@jϴZXs+ƁxUI}^{ [j)Fc7i,th,th62z˲uY$9Ҫ`�7Up'>b$ { Q 꼟 $ ,)zw&*9Aē(E`VD]4gg(R :ւ4$W.Gex[ğ~~qfz)`dxkW@C Νi *} `;%zF;qwomq҇4EgAh[pN*]t-)Vv4M=i9zqJGQ.pQ3RΣBjci6uP<SK$⭢^W.eP)# l.G9g3Ps]~0(^b8tx`4c Mnh!5P%|c\ܚV47!d(j~J)]^ԒƦ̧B]m,C (+o)�Ʀ\dcn.&cfrxlodw;5b|}ź>3 ^OcYp([H|4 #,5+nK*dycaHumE|ҲB{ 麲Kh`8c$Sċ5 ¸@"@7!2I4  y[VxOZoW*"x[ u3i?񗄟oRzpMtE_i^CTkZM󌙙s :ngk5 gx= x,GhFhEW]Av |b:@eY"z/K[grMnw%ا%!d㢫$YYШ$%I yo= i륇~E1~6vJcD6]1yXv '&&hLd.O em zrƘEJ ΰ!r%*ZƁ  WWi q1EJI�xEֈ-A�P~�ٿ>?䊵k81Ш ;5t.ا f7t+x8\xǏFML076fƆ9Н֭;HM&<wkڍ3jcntX03&k5bp %I`p�j5KymCVC-I6 mJj)d1,rZp2'^ ;y4 ~w|Cbͯ|.߿PWoquC`ONNrS\y -zVN÷튡J('W~WXbOyx ׷}Orw.=_9x`l̒�3gs=Kz޽{ٳgOiӦW¢ҙZ^$<q» w� ݠ\"Bڒ%+`]LZQQ* |)׊!<�qQ: ag J]SUh`\:E 댌n(b|?<^A2xI'Z)tYQQ()^ɡAn{M\wՠ-Bi`8JNAQ]!-o~ӭ<s=zt,Ar:z%A.W!,ql36Ҝƒ6xxh�bhZݣ*@{H+k\\x\+Z"oP:c˃'(ЭPĸ*%RJqQiS(9 =BA FQd7mW}èCCCC,[>5ٸa=W\ΆױajT]JqFMRe$3<=ə)Oc8~)2=@sC@B;m\] ׮?D3!=p& [s Im`Zmװa*j9H:#{0UY8XH4k+ FhiZΫT y= pKdvz+HPc/Ɲ!L"o=A>_w"kQhz˛̳{_따~`t0PnLq4Oq@.䰲 0#u&ggiԐԌ_s~흜<6GO4B<oyK7BGF[2֭aRzp;sϟi WA4wmPkBJ=YQ+DŞKK@_s}Pt<+`s \b7S Bzű31ha8[LJ:R,UB*:}VEn�o\� r3.m諱(\?'@[>ƹ8u$3؅0m~[, &@!%I7�(D�;h*2I:PPն=*.5Z3ڝ!nOH.͓N,n6/4ΰ%cu9G?x"~0j=U-OF �1<-fQ`"�&m/ ?'>ՁQ~eU IRd^lE]tA V**V5rݏ)ڏ$JiE9=yfIdnnks0"2 {Th4ȲN_J00gLVi]Oti*KۏzYs`8 q?kUj|=*e1į8ۯas(-c�Idp`ё&5grNPsMu i1Źl }bsx͇wU/_';6Iц$I9 pze "/y+?W|+CqrFF"p?m^gH%h rRNZM6!6nrOc]ƺ)F: Ik4ZLIYNNz|mXgPk43at(C (L;9&_h2kVdՒVBEI])F@A@@4!h:+Ž3뷾3sqw}QIsN>e˖ w}<#!۹{ꪫ^4 zhIK_G _¿ZPv)婧⮻//صkW~eU.?>@~V~?\Lo:gzzVWU\GjkrUWtxghZZ-wKө>|VJ-[KtPSXB SPUŔA)Va2C{P858BJ.+|MpIJz �9oScւu&;%/EwY zxpFk-p<Kbz<XZ LzۃqEPG |<v{1m޸w߻sP-`?fGL/ But=U kQrL7kq8x4懏=ѣ'hZ1)ʀABoeenB*N̨l98K-@HQ�ƸE[|Diʹbb[u=8-B%XGr' t+hp!ZY.С[h!M;MqO__RYb9U[6MlڴFmœIgr1Lߒ^¤imIgq7 ^ط#GpMIΞf~s O&̟="֒xXwXx馝GqjZhoFYӥvb~)Fk]+Ҭ_:$j=pu$09"789ɷ{&Sβw\a-(-SCIHk-<Ij�kXr+7㙽/Qp˧ll*yL;g1SKT #džnt:#-D ڝfƸ=d"!Iةy[@qn;:{/Z4e书sBҷ1nY P/-d¸|UO @c^qjA1=aޅ>;6<.>+aY}lZ:y !d3&5Ȏ[K z!DJ(!qt-Ԓ˖+Xcff&QfK!vT<^N4<CTݢ=t%eisX(@nח<P"oeb?x4*Q>A)BA*뗯\gCi]Pj=D1:B l¸L2=k\8^>U�BH홏l+)m񡋵{m0M[at%X_q1D6xJAa{fHG)2A(5g%$ZBx :@ |^c:d:'k}W BjDLcKbU9;Y�) ="QaK׀iׂE9ڍ6AJi-/"p3T<׊=ୗ>rlYF!CGBޥW෋+ :..f%-My5KXRe߰;w=HI>p+1$?7'v( N;Ԇ QBpB FFSS ZzlcaòQN,̰lXZ²afg8h\\'�� �IDATc#Ғ #:Q,N3hJ֖Ge:|Ag;N0NXpTd0X笰\3"xHy4̷~erWyV8JHo|<c<\y啕ݻz!q/,l?0vܹ[2??Ϗ~s_YzKn+Y>яa?OqGnG}ӧO3<<}V^+Nx޽;zر|;۷Vŏ~#nJ;vfӦMqk_NjgϞjWw{K]sy2K۫ "zX:[ 9@U"eR7($TE/HUB Rxh<oe80΃17! U eք\U/B!*gl'1\LOO</<X(N< ʃ٭ *aqbl!(i; Rm۶a:~ΠOW _f89Tx h,l``vdf]a*d]uڸ]o&<Ml5]O߷bJJ.kqdU!lA4VG+)/1:*:c~.tXZo}?7Pt(Ś+y* k'*ڋp5UFo!X% J.K�e!F=CNVC)H\USиU/zƊxnvt;_ּF' #UOж2Xk$CVrlJyvJdtto-;n 8|}{C:tǎ3yv4ש* tdYe9>DZt0 e ׮!$$%zFk74 W0faVW`G"yCGyC<q[&� 2Yf4r?fL yorV,v):[p;H~ڈM͠ d&e `Bb9E|NM65P"Qƀ0hJC(?~I:ZkchGu_0!zYقdƔR*_**riRwa~!t!dm0`%O=@l}Z7~e wGzAֆvEZu sdE(#$c Bx he hb  <`i9+VssowѹMN5C21XJ"?8ƿ' z0buD7F3u?^fgg]&9_!zHڨYڒ0H(n#tܔֆDx zs:"no71$Q­;ȓO>Ϳ<VӁRVLߔEƎ8|8 &$*Er.:{4`=ЖXog(O/RC*ؗQ&,1 ە8ZJ G:X<\eiStY /@_ƀObnK}0=ߧh!@$XkY1 $ &(ѸiS%jZG*ڇ\$4pnzf�gR/xͿTEYW/+[n�{w?; Ag(0֢3o-< "*+yr~ްh)"/UeU,ٳ^' # ŸAltIy_/.t?..0><ǧ_v.R^J ם6YwS{frrV9Zb[eYdC˸^K=;wUVq (.f읚EX`pp:д<`HXidfzÇشusMVjV#SYttd΁yKtњFGfAd/h̴9Zs.W:i-aɕ`` /dktwm4hY P0|_jd7yO??\? wC@ʷms=1O?ۿ}o4<x0~%?Xw͇>~ٳn->z:�k֬|9 6=z~V^rСyΝ=w^xt:LMM/믿UVjl߾�})=\l2ٷorE{._|zjN VOV YR6h8$X H &ֽZSf:*aBrEZT�RÝŚ!,lBy5Q&Sgg9REĢnFǍ3.ZxΆ\K<erDz TΨ*%b �/;?fhdXZ Ηh7E24 R96aڕooo'8vQP;SZSV`Q]T[Q^ ѣ<DQa<!?W;ϺҜ=S9(;@L 1UIWAocqxo-#ٿ׆�`rJPN�c-B>ױ{* {pؾ:n{-t NYh˅EtܿDmRK2jtpֵ @V S^i,޳):J rJK!I'4IZm֫G$O?<9pGhhJڒWl_{eGR(e T$AE;s+Yby1m"93@=W`7ܣi/#0Xf n5}qa@2[آ1gtϻjhr\gn~}aAHbÅ*)Lַ@#;mVT<-4FJ5ߤv 1 E9626c tynbꚋo\<gd;G��1(PsJ;(ްe¼47.!)7s<3.o!CQho~8z1!rL F1"b -)4 >hǖ9fM{R'�NnbCQY}aK?=S(^Pڎmyq#=ݿzQ*.,Iw`9618rV\$R|Rbǹܱ 5GML,yM r֠&0FhґnBncaaK\Q)͉aS!SRzKn45h BIotf\QkqT�|_*KK{wYVprOHŊ+p$Jqq&OpCY+Yo hug%Kk#%R$hmF#g _E⮅ ~n%c?*^e(;*|Tw_z=p_BǠ|Cѵ^ãXoҥP 5ZMqz9ґO\o'^~ϭJ(RLYkBYҞ~щMWϊvE5/EK/QJUF2̲w^N>5ut ğ\~.�{78<12 ASˎ{$Ϝ6Pc"�Fg t 33alh,@(2\[GI&tf%̷d*16g(lrID3czPMb2'8lIké4eK P+! m|ZqrV!$Gϱ)mYh� Yo_}cW{[ʹ~k<%յ �cCټy3y{}E53L G?ʮ]Bb͛->Ok'O}Y;L⩧|s}_be|�“=9}׶Cgg= k.>/ z+_ _B|뵐ɯsѣ{~jl۶-z<yOU[n;vlu/ˑ7ohC-),@+o``(JHqp@qp!?�HQc eePnZNa#+xDTז@1\B?xkJ >壣,[BɳgX2ZkyT)y4]-$IZpťcA(]#10)Ib Wʊҁ&'zTC.ee"҂A.[ X@Oɏ1m\,ц$yڧ@;In7 @+^_|#oy䱧%*RX[rt%|hr a+ῸXpbK>o"P ̵aλ)Km4˚eO5%%+-]ZOG8.*|F?X"I7h (ֺTы%.F[+Ųe\sxӭL2P KM9` ->qty>ѓHQdټ`�JD: W\r` u: ,} t mé'z#G&<;E9oB�`K +!]^5]Vn\$U LzF>D2dh A#&-VnaWY[?xg9z{;iV^CRjƙʠ<]KJJȲ;u"#:֩,)+-VFBxO0۰6q` 'kI@Y-$\\(ZrJ@[p#;EcL^([K40%lB*} ˊEA΋[JI$(N'tJҔqLOOncDM[~Ow("2="lȃX# K]7a7X_YOwU[igBJ|OQ+¸\e-k\$&̰gjXkӘ[`@%R;CD0JD)!pQ c\Hيr9 K<fyI+E1dQUvq=_(89z{D坢Ơ lIn"g!b(V$[?cm z6Ľb<B`$kƠր2.2B"evwIiF^;a|gc@;t'Q?ʝ9�q&F|ҢѾ^%U (!u_Tħ,mi(Ƥ̃/m=vu\u{ ظq#7|3I3{>7ED.R X0пt]:0ݠ|!"<HF|FA FUy[:D{\]�9}خҞtacK-xyH}/.U`-FYj*|L9xfT3ha$3n-oV ,})VOkC mۿE@YŜ Oqٮy)Nt]/g}Rr)y\R꺊{Az-A.B>t*ͣRL=):wm?cMO==Y⧗|r)?{Y=qǩ<=fP !Mm˕fJ9:ZhhH85=˰ɘ8t s33{)ڈe 0j)4@04.DGZd %LfU PW 348ȰJAũssԍa E#! rXІ9B^D]@BLqftح-Ղ 5ϝ#_G>~u'e.Udַvml߾#<({w''&&G>.,ɟ/| }Pz)V<~0B%xC @ݯ|k} }+z<XCYjUd=sL(-??99}R64y)e޽^˺u" s-k\}_=O<ĥ6}iE96TYspé )T,5XE g}NgPQY NEC:WEt/k]_58# F"Aᓱ!$՜s<"giKU-: SQo+ RƃaTFX "K2xN%EXŏۢ"Vٮk}:\F#a~BCC\ߝla 9%F)(JΑN4y 730~ B^ &#nf#|sz {5̶Ѐq!˜c@SJ"Џ xq%uBN<m8lȭ& 8kr)?4.笧-J=Xttr2 mQVb|M@ h&nP%@F&Rh aINiW hNW ,<))]mW_%ݮ;Ik)idi#=�;3t&di>ϐtl,t/�u>[3Ls=㢮2}5RmD&10p@� ~Ɔ@,$dvN<q<hx ^x7@5d@)j4dEZ;!p9&%l 4͢f# K-!XwPN#QCun۱VN9˼ȏ<w*Nr}̵Yh؋` A}NV`" G&qqGFFʢVzY*,(bFpFU! @5kp1yNEM(ÇJi{b3!Yl<ؽS -`[jq7Lfllyff]H~P-gUs o8`}N{`$S(*tm^N$(o(�+4\+s~:t^[ʵ/ !y<l׬ M5~5MN.,hB'Եs?f)jF2uX^= .^JP‡Zq)@% bl #H)#,(%* _K[`5^ `sAZc {7dݖw2:gq;M\kQR17/Sڍ*f-Mi]_B0.<1h 7^j`!Dy]۹"XՈ@�N(5GpƍA_@(( օm\#弍!S4S%@WZWJτ@60'~wj+g!w׆shU&ပ3F?EcaIQ֥kiV m3rCK=OtEy0ED>G*7e*^ӎf|'~jR)!J' p3][vR¡{ѽz[ΎYRdyzƲe̜#M-a*T6YkKouPx !'.0ia@gkOyHx@¸ (FV bzUNVU'kEŚUxkTdzu]HPI<jkНB hl"=_dkj~8u!P}})gzӭ;6 saZB\;t:slIY?2unα@~(9V`14fFiAnjCtx5fB$�� �IDAT9ZX9<9)+ɶ&OjiwHa:ilFkֆ#uIv.#8AK* &Kk$Fp:k$hNMT5hp\#uf Ѡsmm]7՞E˛BoeΝ|{wRK ΃r)amwV�~!W>۷ogʕWlw_JY[nw۷k.6lO}*###޽Wٵk˗/߇|ca׮]oɛ~G}4d>s|k_sΥ ksR|V;΢1宻?~<~\ߝN^x!~ߴiZM6qa{v+V^=W~@_ᗽTnEyJϐ-"-WQtYR2Xc; A"]�^۬E9V9&w񊚳WN%C@ HF39?LE YkA=|GTa\~ځU:|!i0<\TU6 N"pAho;@\G$*ߢVMg0>=l\6bwBESmy?UanQa6hڮ]Uq W sB,Bsae+=k3C:É\t Btރ̅4 I\ d:@";2:*}")b:j o7~.ؼ&A$p8Ğ}Nt<2K͑!Y-mP,wӲLzV$־�sRn tdl$?.j5h7nq; <wcsQg֖n/ b<CJXMy^Ń/kM"ˡ-[K{#`fvgoP3uGEO DgY6|-X3Hry`Swn֛nrNf3r:< d` @{k҃_ĉjs V=|@jLPT(r^wg{y:qjd8^!>A_hUh`Y-цŮeV+慷6<jcs-&w5(iD!<D, q߈rG=݊{)+/R0UPA<֔}d?l�#�UƱw'0VR@)irf!5ԥ U,BaK ,B92Q""Eǐn �*H;WVtw{L=æJsqZ3vw GW@ۗ^˛.$^yyYRY: �ecol 8ǰh àL~B>W񟯗T aqFb!'彾V{˗"D<KG1zu.&/NiLN`O~9p�V!,Vk? ѷI=8 o⪬(_f-< Xnl/,Eyo޷(tbS._JHNM,h6tP sd๫8Z_ؖ~ᅡ:�J<S9m-O[Ts3~[sG/}_7T<K2bQmVj,(8R/^[ kWTX+cLw�$]h02\b#eu5 C) fm:ãL:đ}`2V\9&b5VNcfB 618?˙r~dL:.NsJ4kE\m7i!-T]!HmiɌ\30TgaTRGQ9tÇKX;I-fLS:ε 30t!@*!K5 ~KĪ *yG\}{q߿}Kz[A*-_Q`>_RwC7oСCYS3~[>9nZ.}[NLLp!6o̮]صk_W*b! я~4ٹsg'?'> }YN<yɠkZ.z`~�7e{2??_,ݶmE 1d 7@,lÇ8xv^+o/;Oj~ a!B]RJ:gq}X=^Q VFޥVsZUchN+Ayn *Q<A(B,#afh|@C,]`֭<wP_~+ ؖOJ,bYPi)0ɋJ g?J:<J2z;Rn;rv?C- uYiZRJyѐDHt]9ڪBR›U^/J �2{RDŸu9.XSƭ ʴ`�#)PN,F9ࡦϚw"ɤ%Lrڬ #b Sq !ʖif*y˲c8?G$90 g074yyRbThzp b(`s70w\!k7lF&[HvakuA4 7~]ܵW~8/;\ o) H2ɬAt0y*5;m&NDKCjR0C04}YQTKR V Gk5PR8uWYb5[lk"bx΁!l k/OlOPb/8Ǡs_�y~E+- D -pϔg*^ZV|�M13;zNӡ"(;�96ٛK"XG.)v+tTa2 7%\Bg&GjHb#ZvZ$Vy*Ԣtm b<S$Xp(B@N6W@CGٮ{3 2(Sk-(%PI1,PJć,.deOmП [=@DDl1S$|L[Dȋl qJK|7J)by΋;b4�+Fn5_GCFVX2V k{^{06z-+h ed/nޅ19>qSΑԙG[!5J^_%5 j5{hg1}"ӃnOƱ".KԯDC]1lp,9t0Z-乵Z2@|n4 nhy'xVyE6\3?6lB𾬥Dfq1wyc{n\{^SNP`[lAl {?so¹)e%/_zY&b KBN;Qt.e2yk暑Wg9## ӭ6jlXsslF<SܾV)QFYZstEX y={t_#ĂSsj5Vkh6IcC()JaPO 2unl^NN'o05s �/z Y50Z(BRL4s߯9}͛7O>͛{߲e _b;{w+emۆ tRO?]{ zj>w],z޵kW߱_j>'>sOp]XX6tw]}衇" ]/;vT뮻Pů%NS r ###TW�ʞiӦuϞ=|=_ٶm[d=W~߶m. RV 8y+^}{ p|,xC5J [k10 J<Psvy`I*N{KdR"�Ơs XJ Š,s9t+SU! X^hĹy!WoOGEPpw*,)p/s 6P܂f8#<aS//'GE.&𑊊c]H JظBr>0&[yһLi~1ֺC΍ub_wnEQAmLECGu3hi}f)Rh=\HgX!ei^p`p7_w76i!!}Y`~ܷP$u \I E NZEpi *a!{8m`~oc`ٍ\ j:Q\'E1 w_+_&F3ґ.4yt9v'O)Z %$$Z['0.>/qghVɚ|c|RҌ* s<#$ rٳoA^v&b�j!NV㚦_N<|J�AՃB?v1ȎE)Q y!x ld<̅Γ D ޲%>p!�<;9I|@U)%IRcȲYV-[I[ .z�IPw߶h@q_"#@J RΡtKh{ً6Uۣ "4H%j@*R)H A$$ZCHVLX!"}F2L`PKq?a2JU#Tܶ(YyDN{JڊRʊ@+ݴITTUy]yk]iPJTZ)1MQ|?ZūȻ5K=@[jQGźܳ'*=庺e[c-6Qm_)xZVDj\#_2;EN~ C|D&TbC'7ΠOsB,B)DAa{`CWo$ $`hl;MbWvtb'0mj\񘤺UmSru%8Lbw&fȉF@Bz?սk>ܫ+Y`9z?ߣ +ctp} dض, c:S g G^JcdFqq"Pr>> CnIZ=J |vʒ.`BՑn Z{[5Ӑ)DUp|4чʰ(nÄWQ]aO/ԍS$GF/*U^9пDx3{EKSyۦ)TGloQfTike)c6V"0y].<BYCv trC֙y6N6ɄnwDn2ÊeN-LO6xiffaH9F}jzFQv&_3E7g@g 43NN` I\g5+WtAG)FBPj()P"'AF+/ M+Qk5iC0m50Y6j|BHS VsAf $Vsϯ&޴~9G!7o|_9zdU{{6lp /uÆ |%w ?^L#vBp <C>?X߇~7wuwqy~Cސ7G/?xMv 4 VSSSeZƕW^SO=ݻZƶmBL:~ߦMMYdHljr;#PrwXvnq$\FDI^jԛH!v֝6XgJUSm( vF۸[y^@LHsEtr̗ :Q4 m MM $Ŕe`W e=*<0pY0qqX`|Ґ:iIY ?D{Oi6CX>P~_fK/#qP et[ JdM+r1⭱*@rSg\*_p@}ȻjfiMgY~k[]xi B(C1 FTHTnƺ4ka)R$BY mf##5#N6s]v%+V֟z XF "鐈yGT"]3+ZB7_CIsXj]A]I ?G1*ZoGն![@րXdk?O_7xOq:/6fNgYNJc}xW~V25_�68F8\ob" ƫ{^{'Wo P!GYQ?z/~>SmP1 Y$ ?/V( Pyup)?[AE֚ZVgosm(L4vn k!45PJl,w5("/n�cug\X4MŶeIsA|vXW._M xD 5y>=?ki%DT/2ʧ\ %-Dҷwȃ�X a]7K1gs*ں4-*!U"c@ש@,A Eu(ρ\>48yJdk,eD|eVw1qhLTI7-R%_@:Ze1ڎ?cleMk\zY_J ڶe]8,sn™!&.p P!JI20-\gU #etB<f,$|lUY@8¹t~_g@j9j5%ZF*l( & :ϐ"yH8F,g<@Ğ/~}tZY,D\RȰ.1Q?FWZ(AQӮ=v,gyN&璡4hFtpWfY֠eGT.ntn|u O7!QƁ"m/z֟#kY{"\t--鈌 }ϵ0w~_PGeFh)*D $$B\=I_<y]'837lhP'D(2Le= :g&ϙfmMR7=v5LBs$grfϊKW3/zAΝW eNhjMe35\01tnI C0=7u_ʆS6vYsns l\5EPZ+֯#_cr <MrrEh,Q+WSH�z6Gܒ/Y*z ~۽,du76r}qwnxΝ;? z1_|ߵky8sO<< i~<]!obn I??$QISih{ٳdw˖-!g}6. 1j͹E-Z4MY>˙>sǎiDT'c(~6nD`vv6�hM,I;H@)j h,�Swv1` T"%"؃4D;8BK0ŀS"@V}?6Ů0z_3[Ebp| ` <In)|^4nyW7)L45+T%ɢ,7>[L4IA ڶo{"AT׽"uIΥo ɨ>` Pkkϳ�� �IDAT$M "uhXU+DU,ZK �`*lEl5Ink ґxqh B w�VaB7[:I6^ʝz};]:ER$(ηI4 F<.bǑ{ JDڹQ! m&t<cϓP[͛a@\\ 0 P0>?_g! _{s#.@B"v.R  #ldRqmv"載d3j՝3ŷwlL\}=H2ltB-3Aؑ\yivܲד+^~v7f sTfu&grbeLML0lRi$ibhe}YF'tv;3LOLY>ӳ̵tz<$1[‘4MYlY1;;kH1<oI/-IJert52u(GKIiJ~XGYSńkrqP1NI,ZlýIr5uCdK ;|>a+C{ NDcՍ[k뼕(&HF�)%ґWo}^r{Bv}7L&4=y9Rck@}11$zF <,/C1\cjYXTǥmvjW J`λDT%-{޲_X+<zp,&Ѵ=b^6(>*kXOJ& *ơKε~kQ,ѾѯvH nx cՌA`c$!u`j89ch]۽(?|VB?46-v }21B# iEcj)r". < is٦e/~(T[>eu(震KQ Y!b62g2Tr+WuﯕyXCǓxa{2\wvt*sXO˽atD1 {2o 3Ato0Oj/qr ;p}ŧ(կQ 2l}֟?geym7,d?KWCX97˙\N195F>?sDY%,SYƎfNMh54fۥ ^fn.9oz'4(#M):>#?rL4lpq:= ;odϞg5ACvٺa-˖s(`} (juZI"ט<cL@_24@Mh,_^ZM,4 LA &qrah]LȾ<oټ47E /3<Á8s �+V`ƍرj'K_Ғ_lݺ}$Ç+o zk%w_֭[ٲekB.~mΝ;ԧ>5D:t|#vm||}CEɅ=Hƾ]bŊ/O?=vO^6mӕ6m|_XRT{#-9[*QVԁ9|xomN8. JȳnvVZ#1I'MSºW- 9` !(E $V_& A"դ6-3CiVظjU#Aوi@V>5Vmk^gڈ �N%i1$nnCH hyk)E|vw9*qh�rhBiJQVVZB"ԕ.d$ Lݷ1j#T+)֮W)tQȔX]hz`-} GU`)m0h)cȢGhʤx\ô0ƆN\ơ6ՆP3ƺtFktӛi$0 џD͕ΣܣX"s.\J՞dK4Ϟ7~ح7Az K@OLH6o_>/~WI7jo*%y,]?`=\iԓI zFq`\Bi2�9ђ{.e9rB}{ #\ɉqyk.aڵwӴZ 6^5+Wn֯YɺU+Xv k]%W[-]8eOa aKQ+Kбr啣8v f9;7kASQը*10&wdC<?kTF c+œ JJ9hWU ̮_ 09\:&[Oj 1Cy_WJ㉁\$ kqLW_.k\ 1$8"MR J(UhMnCiy.Dpi ]Lqc,lt%|]Ӕ(N SX<Q6Bt.f&!{ >.p;Jbss?B 3V_tゖ~0t!V`[dR%KI.i8L6U!jY| u1"(rt-k M 1# ]IdbI^oQl|}2/!tPn[{牅}ߩDkρ$JkM;μ"6ca<}av̀\&GO=K枣UQĨ\e94c%xudЈsYΝ:F-2XNΙ�q:b獘s|[*T\* -*ubJ%`A1V|躈K"R6%I<pS2J8,q wwс:g1Wj}kX2Nc!(5+nc7{ey=Ǟr="*BwQyΓd0:Ϝ<kLʙyUݜb=1-˺Fc8Yο^YcU8ƭ;88zVl}imzsXI H}'۳tǨ3o8ykMݳb\p^|n8롴0 Yo֭Z3|sh=l4Yv=d[t!, H cF2L32Ͽ!#/~o}[={v{-[pwsM77p_˿%ݿYyΝ8q_W+/46k%z$'ػ=]͛7}|#vym޼O|o> +V;w䳟삱gߐ7䟣۠,'O '&&^/6/v+yߡCB_xn%uV{1�lxߕW^yV"1P16>FQϵX6AbffNbڲ"I0EA{nisyHW `]< ,j;i>ccǀ<ΈƈgNI έ.%. !Y] =fI>g{P Aɢ!fC(QQDm\6g-z4k y9@y Q L2NqZEZpejI^hf{!`Il۾IN8i D hc}>nR$!P`^.7ʺ<8�|.%cbJ!#; I0>6F򼴆qdF̃Pګʝg6ΐBR)Lbr?B@DQre|RE &:9o}3?nZ/+=I~ꛨ} JS*` "c w o;@�u'/B¿v~m;~?4KΈԺx �%6Ci)'"FLr _2C t !ot(vٶȰUc @k-$A7Z!)8xpPj(%<h-;2>Bm`oھVizݾºrh^Cyw nGĝP%$26b)n*g(˳{A=ƉyF)kQ`Tac fc>OxjԺRiy(FhRt5qzs待G}Q}1X(n]7`1Xt[X)JdkI"I|]G G~׋rlkDS^yƺZԾ\1Ɔ@`E=4e\{Ȩ[g:~*)|<ֻ%ƃ 7Ar0$SVIED>`OU/Eo"8}`{Qqye�;HwB#go!Ewry^8"VR8RD:%Δ0JE!#J>"'Q=N4=r.Gscm)!e0Z ZgA!ʓG*M3t,Ӣxd>x OFG+~e-.B? 6rz*-e| J_(6-Vza=JT_S\٧" #rcb`O~1o,HSj|1`Q_[yaK}Zu }ԵOt6T!c\/}D3Z=Y^W6ؓ}Ɣ?3ǤV$x&FMř)\'J:#$ረ3k0۟RHɜY]C0(d%4׮Gב9B @M]B!-T%pC̜8Y{1Пeժlٴ݃P;g[)9vN/g E`L:0j>P\J$S:yѦ@H&% ,ӛ#Z&I6:)&[>vmkWHs|'X3333|+_+_ 7x#7Jv^8KիWH/> |G]*�֚6|饗v+~-wu'*E/EԩS?zj|ݹs/N֮]b.dz!Jb�{ wرາy@Ⱦ⋼o^m۶@>lܸ1}&-dO҄Ve^;vm6C"颔B&<0|NC^dH%к@:rl (0ENXRJΜvʬ;<Lذ|)a1z%0-CgP*A(PBam2O&HFAZԨ8 ?am@Dq|ARv,,M=Zpo<{x|>Χ%i}+ ? a/,ؑ(I eusaKL!_`~hl޼e<y ysҁ$.KPWO"?|j%@d+Á6vmq=DG,kt,wIJ"- ]VXt#nRrw|r\}Ј\[1s} oߪ9cb7[jې�(MM H6DkE2XW)#1!ksz=E|&D V#DIp|OÏ3=3pvO*9<??"OPr Y0]$qm&˰qs[#LOpÎM\e3$㥗q+-c)n7򦫮bz] (FХ FƮA8͜_DڿXsν7gg_vs{ҁ}rn9sPGB%rMF(JuG$aݛ9Ɇ-^ZXΟ5sD\ E?L?7z] ]ĕ=crbw\M᥽{sXF/]>B( b%&v~״LSX7FkJc BF%ve R<@f,(]] WRe2h[QcMT'6ۨRg>ˋ4.`M}*- QWJϨo!B<\p%(^(}kOf=:g{>P|w)#y=8o>dj_gze>#,MQZUUs!] TbqlB(EA))$E4-Z^z+j@}:C#Rϟ4>kdU*Ͼyw#׬0ۿ>`ύϓH:Jo~ ,rM=eCu  Us/8G߉PCVY GUnGZX,{B_#?" &yW }?[D寞CB),U9y^/z3[~ }ݜN"X_?0M-2Ӯ9 4k>J/s*GKIwV>|9MF2<CӟUd[u!AF>6řnPRC}МJ!T'am+a\&Lk8w&A5$I*H<Y?c<,m0WTaXb%Z=vI5 ;Н' $Ed9IAI+UBO<h}n$I?ȇ?%ݿyW!d~֯_m6t}ʵ\Z~sMC-9ד vn|ӟ>4F^]v};2jqs@¿Zqpߐ7Y.@۷o윛?]n]~ӧK.jگ;77HFx ._R86mbtPƵk?oNܚ;F$8!H!nsRGsz�dZP<ZRhDF֊,FVJi:WmV#Z:# M C"JDHT8~֕8#fу(i3aP <,|IyHK(ں ,/}UD?_5ds󧠖+K>#d1\X>hIz�[ta,X-u5Z乍q[[ZWh )]3 +~M(9ڮL鐏8<zP>2<nD% &-P@Gܮ5CZhºƓƐ9BS$$/ N<왳{]H<XYVK_XV�Р :7r>Nib,PD>EYBFɳ�g6@ ov~ N^KMad !|C>~G%8WR$ڸ WXձR;|;v*~~ނf%YG!ˤ5 ><U(^ oV[bS <߽7Ѩ׉]yL=~Q^wS˸fVL[wh}ѐ肜9>mh ,Bm; r hb[l 0 E!$額Yfپr4>~o=]|'NeCU(^P GmAJ~$hs\I,"J@*.(k0:Аh S7*>=)(-8O>P=Dk2zЖ)-8_E b>j\*tąSB F@]Ǎ 5VٓŠqQ-̇'[V wUT)Q9 .~ΎLM vǐLa[JehcUZG+ŻAe".~iRZ$9�� �IDAT{ɞg5bZ0vu 1 -+u7R n}l$.F4ǎ%wIEwґ(-yT SrnȳPt~x8a+TkO}S>bB\dh(N4YL#gHh1g1<ZZ tF̓+etm*bF|ZޥIe_G$aTwS.$sXeBYmx 66?,r/^I``zuCfE7~< Xn߀4M2zQr0*:YizsVpL :y팙Y6,SL-'ͻam1΢VG_rɘ̉t}5$אZ); ڐ2NN# sᓧ3X>|0'f ƸRg'络{]BPd9ס4Yѣ(r2oR s҉VvWU#1=f/?cEs>n>׿u�nmJ{ᮻ[na֭=z^xb) a~~s7aÆ w׮]|}{<C"֭[kxGg>Sq g>>=z[nիW6nvzST}gx]wwW\͛i<Ca yC^[s>cl޼B7z-p^_ 2,1e);B}%{iˇ\5/yΩC=r,ϭkaݾ?HIj+%q$ӎiqA;eXT"Th@4u֭_XcGOp46$Ja+ĥi@A)A$u)I 1 C#(a5 Fb,J�@⣎�䚀@l/Ic-~&[르KGµRd,?~={eF*P"DtFK(*pωGR@tBX|2>.ů{ږi NdY-W0(]셾'=Sa̤ls4Ɣr]X0 /ta%B*kkcj mRX B*{*!:HkK`]#0 Ѭo=u!~7wIM쥖P5D,jȂ΅KE�vK \BůĺKW_k݇ѹmc7NH%mPM%kmJk'Sgbٲqqj-Ԫ)Y|Ճfa  ]v`-ew ˧ovcc^Ƥ$ۇN &m*]m]B1ٓ{1́ 5#͌}.cjFYI@G00- #mCBcSJֱkB6)՗?w}}Qv́CGt:ZMnٌf}N9 Dj-E>/B̸Yps^]xnp^ Z�\URT`g>5D˵)ױkOT)||ZZ]DQ1hxx``H 4 K]hPvb4i\B'!p1ѱ{>mFG{G˒R̓!ҹ${EVپ=4zM-=TkCpVK7j@{ aW!qYGˀ BDCpbK7�!v<)lJ\n|hm"(6 \* @w{/\WW:&ׂ* @Re4|Ŵ@E\eu~5l*Z-Ξ駞g%&Ij"wcSM\x;W2?CmG("vt'Jsٰpy<)$IS$K%[R;.a{7 r9SMr}btrD8,=BDnMa(>sCVam78 2Ve?9x!9m-+%2k1Cmn u*+{0+T3%nR\`r.T}| @S5jxuC>{Gvmc)[ɔӗmRF)ZB:&I={vgSp)pD3l5kVs^>QgMc_c28~9ı.Wo`4tVA}ΜVK7]AOJ<f'\S JJf}; I0ΙY1byFS* fz)v)mh ,]\KAF0RfUB)Qy)&TJڢEyܱ״=s=>/l_|p{ua>ϝu,X7uΝws-[TCwҸ+_:W榛n\mpwWoAy[x뮻zK/4`Iѷ-C{<ѣG v9~g >xӦM4 .ǎcnnnIl۶]lbZxL4g+_{w ' Qe7ݥ6t_2AtN%$G :caYy$nYSƚ] J[ Rh0,c)mB}d'rdf\r%Nic5!s`qi �b=,.ڥ;mZMg$pDA`ak o` 肓'N3q)|0l\'U$bҬ:y]l#e0a {-k큆</Fс 1fArbFm7k#Lh68_v֋볮b-(,4)0֍"F E t㹠^bE*$IBBh%k_?,ҹ ?$ܹ($֥ "GcFu^Dȏ#Y> َJY]=VyiyQH �M Ho-JbC "jl Bk?}t;\ tD 5ho=F3]B5` rWV;^8LSJKRq8C~�z`n/t8ʣ�"Rt_ٴM2QKI멏A)\b`jj7mv\SCˣ?xўG`zm?Ig99 aJ$ZP PHki p6X#�o#\Me>$LR: b:O_Ke_pB0# Ұxy}W/D2zS0\>!Zڔ ?LI{e*עn>6b+aTΘI7i {�3gi)11"Ւ2 B{-n]"#jo+[7j6Ƞ6)X]CgSfј8�vY%G<1bL¨-&T1 Sy߇V@#Jv?Maq`%!/4~ju0V` 70J,!ߑόo YB{)‚B \HV]ÖW2~ �&[l+rN?3v4\`W1B->Ats*5Fڧ@Cc?c?ς" ˣRE='2JFcAw\5Ȧ[Wvgcϑ?6lTw[:Fi-V4_CGY?YTfҭOHO3wՏ2}] ]c9wsˁuw3ƌbrZE1я <~<s5,_L'^y(-_˖hxSb dD];Vx+3Vu{~5w]n/= "NZh,Cvid^Z0J& m:;Ot&8<=K2>i26dM,3/eSӴ B4Μqf^Io4BMur@ IN 0]9{)(s:ISB=\;]:Ee-Vڙ`1N4:ۡ&aդLƲ^hAޝ'jH>z(w} AL|oZ|u]|d/wy'Z]6l|'|AH8sς|ӟ^RvCDBr211Y^Tߐ7_xz@d(X[o='Y{kXi_ ٿ >VqW|޽o񼟻U+!)<H^YTQvPJk$@‘ !p4º!˲ǎqvzypqQXM`5\v٥(!PڴE( 'xesIN>Cwƀ25I ºD KDD`cešEQU�; 8Fy ]z(+D(?)qs)~PJ^Ap@ xm+Ƞvrk!ks 33P]:^QQ@/bΔrV.jbIƎ"G3p$(|5% /P 9fBFXF;V SWRP. Nz᳖ޚT u)dƍү k7)He~@wANe5 Up1y|?z3{!Hވ\c)_&ۗ|4`֑S nz g?Ǟ^c)I*Tf}0:UPm@k2c8_ԙd8 d D7P|7Nyȋm+n lC򕖖>`RW=Ȑt@t1tAu4Y!Bwnx@ZO1d,F7O DH~n,z?fKц\O?zscWX j5I!LjY߸N~W?_d(�Jr%EQJeNdPPcH%sdLƃNDGG�K;Z"ZC=A7e {.W}CP;ʼn'D֤IjX4yQDFQ�Rzu%xI'[$1 WC;˽jWchg kd]R YJI5F^gzL#[ijEGĈ6 9 l JOڔ.< N+<v/]K1sk[r61λAjp)n-yC0uow *TCR`=]gK!0FȲѼs!> Pix]{|%h|.\{Nhn,dDjk!!6vRDJϨGylRw^{PY4Ez>||_قÒ@*.<- ,n/$FH0Hxc즠sbJ9^9AɸU!jyzf\;'з6`t R#rOCE34ϙ@:춿E}7Z]hL*Z~{5׈jQU\RCEؠWZ0C^6 e5\C;<uY;`Uy?F T V(ƻeYFCYMP78t;= ֚>+WhSn?E}jyC>;+/= RZ9޴PS˸tF\BhsljTohwsLϡv&Zx"j,BM&W4S0cs69poND T"m/r MaPh4ęf:]5?/СCWSSSر믿Dw.D {z|߮[nog~~;v,BuV~~/|_fy=f͚Ew.xGxykokׇذz}vl]mٲr~wVߴi6ln\s5ܹ'|rϼ!oȿDٲe /2gϞ 7l0Uf޽�Mo(i/yjչR}v&&&B;[~9̾h8h LKW bҹ))19-$:@I$I"ɋܔ #%Du֥qŲZ*YƯչ&%`lT)d>^CrI; Ib$Z[7cFXbE@榱]L'\I)!KH,6@ĎJH@0l}C)jGPhfkY/xWR! Tۙ9O` %ȰC7!$h55)<un@7!<H[Sj;owdtzu 7FG䬫AW;}/RA53>rGeqV_,1,uD|,O**JW(Id(ekso zB!t9pxpt1C[0 )HcB Wy]Bp2+ &`_';T[\N!- j~WO9|�jٸq5u,P1q6\"G(CGNo<</@Zl,khIr�p.m,y3w[A^y 5Tt]O=?RujJX~`"ڻ"$<Ȓ2LϽ]쁋��% @II ZV>Bюp8!GCGXHSR$MQ �8ޝkU#3^;w{u|� phךЦ 1E}h=_Pmł+,_`}3Dތท,Les!"XG?A~w>ɟ} <}ES -:O8KՙVA%jPNWkĿjy1cfMm,gUX\pKps݀,|vr5dj;4fv쀇 MKb,s7`ޮ7" =\;#AS(vАXyG:g>i� ~4*"WFu[H4֐H܌iζ\Z2CM4vMjlUppZ7M]hK"=-d qX6 ҏu'[gTK<@9X�zȼI+\᭼Aįk%i$XdO?ʌηW@0X,ۻlm2:=b)f{{=34!3,8kL0N8$*TyS[,NАf@ҮOJεnEHXndL申*8e07^?/mt{c !z}.4BH,\:ѡ iyڧI_m^7H{Qiq�w+v <diÇvc�øI7DŽ�ѕq$jiԆ=A/O^Ty47ݽp%<, 2@ sNx`?Y H�� �IDATx 꺾#ַج-hcSQr5Y0Ul`<|~s)Ps f`+cѣvc̽p# oR NQnF*;8G`ZÚi\5@ߢLїsypϽϕϳZXC.w٫+/!=v\K`T 4MK\t-T5AVraq`p59~=񑷾;w'$tNI˛� 5uJϓ"S-1d-""c۸8 jJ(%dyK6fm| Z(ʒ"("1Nx Y XhlB#ٌilޣ,O8*2X(w&uAH2^7GQX+ H#8,ܗƘCK:QcEhLtm@~Wq<_EQX2k`/m|~A$F1&6[5KǓ$Qza?L~ߏ;6Rψ(EؙJ J Ynmc$!co5j}Ǐ8 -炥R8պoX 5m vP";B( %`㺁*kc2?cKp3pq$H[v-)5ԫ[ȅ ?Gܾ"ϏwWx٧|7.aȰxK=<gbq%s?QZPG)`xxϱi\º CP@`B|j[<o&{W 6g>EDϡߣn92SAk`y=YN7xp~=W_͛TDcV 48 JeɭP޽Vii`k׀UV ~u3rt>Ǥ\ifkJ<M0F坸oV ϋ:_l$5B9u+)bHH `LX}sUJya!*9ȴkkBx6-+=-[K77rHisQl*s2^ƶs{kId D&g ݼ褙x>旺Y%ZYG$k}TC<ӭg-e{=OSK&&Ym㼜T8EG6Ƅ=H#i\(0Ǎw"ͻgr>.xB0'њ8Ξ㡷7MGvxO7$ūkYj1@jj2fnuc|g$۹خsa q4)'+ɡc�.'{>Wks(4ړgEQ"ֵY5y; Plmp[xC,r%u]7J,Dâ<SOHJW<Mv3+%|[0w:-=×[jCI/jf7~lzhmhN+!)]rO.Ĉ%�WI;Jǽ__%2Oe((E| :׶y/Wl AmQ_zq斍<g_ð+Ë{T ye]=W*zk4eplsn\,Q_t2 LEf*y:9*Ԭ"[r´SESəMY"cEɝ"T%c[b}l0b$.F䪠[ȨTn(D3C+U\tZn'$tNIȌa׸nqϔ焳((E,YI];.48յ>VapiS7YLzrvFNޢ%EGP>e``xF)?pZ7ʻ5S7arA<RW>{:{wC@^:F,7_-:i=/ +x*$u'uwG 8S̹ Ng] R՗S)mDx}ݼbBBV."?cP`AD7ڿ>߱!@ XOm � 8l@pqN c5/2G_L *7OSM<IİQ,SP8XfC7T)0wo,ltV騫WQ6Sk-|K_˷xW~gc�.R9y~ ?F=I&igU%\&`8EYm0ٟ2-Sg,JxN3zi~*OGrJ(pnsqې@�VVOw?o/|1gkj2`R2NF}@̉t.h =wcxTa=u=•fHNS YkBY:nkir0]ku|IK]}s{ Jp x PڽyAH& {Ƅe:M[ε[Ƞ%r%%N@<(\K`~4ηMZMY�|1Bl]iX։s]٤zVN$QJCi5qNxs>UPMȉ9мhT^!#{QU 4d"B9u'hv:Q*0Zd|kumPCWǵMC Y9okI.6P}r66xOp-z%vww`ϊ0cjt\o%4&W\g@9m8L.# זq~z|kiNwBD ,ݲlA ͼA3uÆ1C'#CHaW! ܖg -}ľCm d7qY|DZMt]pwSDl  ›sw1U9с Ԟ�QGEߝZsBmTRx^XLY6__o_ɍZ}x!מ[SsU\W" 5+KlgC<g[ Ys9[ 4uX%cF|K˔7]}B4[8 _@Ozn E5ƞ_,-+q{ G d:GlO LgNܷ:bu4dT>/WS̔3jlUt쵍?(YzuKzZ)K5"YAUj!X"'wY52azbxNI:I'$W__?p3sxKy5auuxBYV *X|*ݒsRdDC9ZXTִ֢1un6k9Y5Y^xwi"FyzΈW09H1hk-sF+xiw<V ^8Yh61sg��GZW|f:2kǃQ XGU~k|DA=*Yw01MB` ЧAB0y/ﲻGstpSWr@k2-nm" @cu=+<aN0pswԎ5HI4�lw�qB .3kG$?6Tm|״aOxאe+c2n)w~qϡ#(_]\0nSd1)~6n+ܾc\d}br(jSyK+qhա(р86vyx`yyĽ\2Jb8O= ك`_]F\YdѤq~}/70sFj| ߤe!lj1oqv_^d^E d f՗(gdXDbQhV{hxΘE=t:PX8h'_ oo@1`Jl{o:y'7�4hȁd][DUh1yE/D$wʸºfeh]ƥ2Q(4nv87V"m|chH#le:`#(߄5D1Q1+ UcTאҾ n9˫D^iZtm`dSb1旗?ZgIa)-t?N )Ý5c?U,]!QK:![c{KR8B#wگWZF CQ9b4x/:U#6kttMP cTgުQ5pW$i-K$ۛO+XE$xHf A& /q`9Vڄ\BaoeNQ*ݤtڧǏ</W!9vV|h"PGG5r+|R^9gr%}&W("Ք]6?_/++|Lrqjǡ3,�=XjFpLkFvz#EPYw-q1.aZc)=Ox vnm[yݑNEGf41O0ִ2Hz:'/򂊈oJӡ&8$nN+]w& zaKQY6 Ǥw`S*sq^le ~']VlO6qv窚53Ǻ(nך\!:gavLƞ3(&{)s*:2՛k&LӊroWNn4@&SdZ.njPp> %jBfJN_ܨNUrf8FʢCzyn:oD *c|k0n9Kt̩UU=KYV9B恶|Q9ﱼT0 Kv8,ÌfVx~os1NI:I'$AKg%0%!I{>+X [f% ^Hcm<|y&Lg޺qp8; 2382O |(T҂X:ZV5JgFJ稍7@cAcC>{ssW^n#@T@7jD`H;%ls o#B΃zstL377Y_d<GG\F[PV�K>i*[|z:KZr?A4ɌLs9R( !yC .ug 9i;�eȳzWШ n@7�rR%e[:Q!%Je(=XF7؂ЫH?Z�dv^Z*п8?D(UI ! 4xl~߰MUn>�ݦ=^">*S<CʋWxgY[fgoƬa QyCk*kg锗o_Wj7! j,"Gq J;|Ͽ[ll19:".CO&h3Eɋ l�._9ntvS:qλsǁ2/2|"߆^h}L4X\myycocow=%>c- R ]BFTm#ZefuʘB[ho�C;\ iy!eMA)8 ȍfL,cKޖҬz^"<4Fz9(tl uvrǼEB\fol˼�I"e74n7mIy3RہH {SSGhCcc1H6-c&Η::Qm%m:M|Bif926<יs@ :c'H9e> J;LPN,˼%WSoTaFُSV{XLTUDoO㺽wM"]ueG㜭6 6 s@HUk<D4BYH {\x?;.d.GR<rjcY^^ᡇfy4b{{; w#Ü2 D|dwO8Xhc֐H 5~GpnF1g81̏YK9AYX>�PUx*Srه zi:C)~Jy\RڵyH�taߘ'{1Cw{YȞŹ:l6ҙ3o|Q~?ƅr%vwڐ2Bu|.D8gb; 9VF; ~/)z�> G;Xu~YBUm^Z%Ԕ<a >[ ~=K=pڲm *kٯCz?  Gصf 6½p:gB rK /]fsZElC}r>S1XefB}&JjR={) Ng.T88�縼,jP udLbSC>4rvY9Aي+&u)*Lu,+jG;d53Y ˣwtNI:I'UUmqH܈w+Wp1>DUW䧔4`@6 h$M􊂺*1Uep)޵qgRYY)*=ZTh#IR՞{\2׽( !ZY*hȴKۇ- {Bx_Z0d]�P, ;k6]X6~~_T(7�!; _C4\<2 7a/V鴉HCv2V!6klthQ49Ȕ~c}ׂWȌ.mp2qp-{ E?ˡȆ# 8C{oKĂ>ZȄK.j['�M#�!ﻏYpc%&/m>d f)ݦԪ8F)۟`0+osO<$ilPh 3t/ 5Jk umX*qk7(xoYj^+ǚ T(}�l�&2} h˜:9ӊ>=p}.&yu }cG on‡` "Z53+C˿|OOg^`<9>O^!-Y`دs[dӬI~q3GʴqM{ .S&Ms[xӂlld&GGhi;(:.b^ͺ59l+sJ9Z! ]pJ/Y)-wm| <ϘM^:oQp$c7Fί %^YYغN7_ 87|lWӸq=vJVeoT ΙXwﯖV~G(#s8񖰱ty /!Z_=C-ڡ"*"Z ʆ Y7>~7O/^Vwn>;Y%He֬Cb]՝\sC}dTT@|媪1xoq 5WiK?mX2-Cy{/],WLv׵:fLg*<:!eksBuy->:Bzz&W^FUz'`ëp'I?^yt`4c?=s/׿ vpY,n.M;!v6s-*xhΟ~DZ 6o%2= n˹^wȪ|�� �IDATc/\'׿}^5jsbA᧙MWp8ʪB=鏛i N4wLe 1f_x=5{)H!ӫ}qk堶%ۓQ=,u&e(Œ b aZUzd4>ަ/#V/2GJFKp RA!z6ELAjX2YSk8&9SK_U<R$t >`5g) $,Y<;lOq)Wx܀sr^[~'3%PrE{u-Q;#Q }tNI:I'$jRv: ?!T݉dD\{Q�JwkױME6h|:g1c"A#se-<ܹTemD;N)g%y�(̂KFN cY\ AOw6 cc?qb7\x%dJSq_2EDexM66?¹ %}4j�O;e1Y)t`d HcUِ⾾R oj2hdӒ Jф]9Ҁm'@ڑ� ͜w5`;8ֵn<ft(^6dR6xhu)pSCFHФP< `vcK{_@Aid-_N{l2YLi%HۨA5hEn*9Y�RGy.oq9.r 6w|;0fDAm8Dyx7{^>w=�o}y * cc˘ʐ/!DfpmɗCCty2;Am;Shpw) *{C<G2Lup]l,' vѪ"緿HLk@psl'E"40^onH.lRv\yvOKڥQ\>Z]H=k^!M{wsVd<H>w�DT$ajs^v�o=G{&onz1/N#DxG z&àqK.ݬŭ%*-58b^}BS5»NFIvP1iJZx 9y9 Ixs*,[=^z: eId8{}% ^aYyR]Zռ[xJ [yvߧFp H>n,k֜w ʎnG /#g;;|_c|O9+9NߝkSV0Ja#쬆@זy,Xֲp)u8'hQ5U=[전tH(l\V%w=|ܹ3;wu1Uv c]A'ΡK^ԵJuimKZ9n~VӼ;߇.N󦑝""<60ְ4 㽓Uz_ok]ӎek,6uh$gޣ3wS簿PcV#nQA |7FܫwΖpgʘ zlFlW%ckɩ՘}1,c=ڭ[gM,(s9\3).߃R9&ӭu 3exfmcd*v̈́=q ('dqgγl^?g3>,!M>^إUNsP8\cV<Xq,0k{iLrsLrr5)2 W,/b 2lsOI:I'$J vwr�MZ pG"ȑ@*t|Fû릮UH,WЌ.xuSg=X gh% 1WpjЇAx{zR{zJ Ңu5a%چia"=|X X@ˢ3"C,paYoIh XC/+|2 /ߺd*cYix٫<ů *Az FY<d0a� !I$'ƊjeZeRʻMqi\O<aǽ6}8l_^i=ILZ=X2{=d9KZ#7]?%W|17kɄx'wn}?1o -`�LE?v|Gg._mTN Ͽ3.=f?w?Ο?\#KlQt˚DZ r XX1U͵mzn^ *;Am ?$gM7Ci]8BS>>?LqOG>`\w +'(g#g>V=}ԟ|;阗1Bn 8tpjUp w"(윷`[YY 666CF1@"'<aǦS f6ϱYǑM]@,Ƙ5shԲ;6sٓQA'Z@46G53>6DǓ)ld/@9t9D^#ﵨ_:mDxu]c^7%RQt%]"ycS+G}(%7~0wDw2ߋ)4K<Q߼w+Dĉ8j.:RT[ΰ[֞d  =5O? Z76 eKInHmAel~+JPpt˖a݈ P[N[^N')֑[o"1C/$pa}V'?Fz>y)u]5:Ĭ *\⚄ܝu4(ZLf\"έrin W0UX1ͺB<λ ) ڻϲ 5?5Z_V-DKg E3`٣) *aǵյE8b~G)7|َU9BNImfdڰ(lǰO_i#Df灳>S�yyhG!ǡzkZ#rn5wK UO~>SeMVKN)ak`v2 YUN2\^>˭HM3Qtm~5a3Ʃ)J-\?g2?S+Kk-` {7 ٛ|p6@JL g/ld  +$(fyQgR ^9qP1;)N^` S`Wݽ ;fƈ!ko)! ^wz vF6>!dOI:I'$MIDpKB,ũMZXp*r!MrP| )& u]Qk'gcn|~֑E \X \lx'z(k() Nm܍7,4-/s-#GƼ:οϻ4a-"eheM1c*t5m,4`uk?7oyɿN4؂ # 'S_5 ij7~D tРd9Θ� c�o5)иrja`?o]sf�V΍sx \q̩y!gH .s6OMEry-ԩO'Y q�aX (u{ǻhx\ ?=3ir/Ko$ >'( ,er}˔{(q,W^z?76!sjk1u@{wX#SX©,?He)fϬw_;K䞐Fg OOٺ{3 = j&m 0ex}Iq\AOP} 9mp[Et%xH�c}s2v1WԥCh: 2?3o} O O}}+ln!5-`p3sO-3\1O(˒_\ @aopDlL(pD~kT`A2U kS[E麄7I>V\smN񞮓01Cw[׃ā00u7xg\KF T8.2kȺX#F3趝hQDG[98<M[KN@(c&I"(;2[q .c[)2Jsۦ \�HKu'Ȅs=YZ9(R:0UU]&eHSok5ʊg_|4Y+ܴkڹ8gup?v\o;f$Y }i9*Ax,Jwu|X v(,TlIq$g8k3I{\_D6\HGKۛT,iloUI:#mMY:_E/g6>`S\d|Iz-6Or5 W_S._/>uLI~NurQؙ;hgy㣏pSO=ƟkT\K:KƸw ˋN\^wRFX+o*~NWm1Xt#u CMh۸7`쬣.˰p+kHqm]#7?{p$\p7ܸyg|ʔiNvunJzB lf Az 嬦0Jӷ3 r>(SՖyԠֵk VN3e jBqV.ru?j]mWx2jR V`|OY]n8 Ac tSC {i6S}nnj%sӯF9CKKCiE-{,F1*d/X,fl%AI#8yni'$tNI:IiJyHሇQN%Tk>#ĎRzQ ڵ�e� -7 yߙ t{ ( ¡1#4*kйWhiFrߚR pڒY흡) JQ(oZb1g( Wpt]KFnc( %!kyws5,wq�:9聞o $X]f:XCUW: �Rt {;g%ԛTۿG,0>\CvjB \xϱͬ!35,R!V`'ҭ*{h|;qS. _NۘuxK�߷Y> C)((H�$b]o>"Eϖ 4amʑ!Z p4^c>`o Sm󧘔_BP0ֵ 1{Q]8{"deg2P/B%VϝG?vc<o�<dAAiǑ<ac:hG]d("g|?3_+OUIoK3paK f B͖g.0 q\zEe8 W~6OP# 4CodYqc5s(d16|R 2qsc1eYb% HqkjM،4@\;R0Ze:2Pצqk ftw3LB}׻C0U| AHEIETSV%PBع/J_ Ao/}~+LjcKж:1nЯQ#ǁF8r[[لLi^0Ƥ "?7ltXVf #Cūxw)QnHX'hM!.0gtYJlk~b)TS5sP&r} cZO LuYֱaWq ؇UU3Jʂ,(z9¼BŢqhw^A:6Vh\S"õ;}ü|/g C^[X뢚Qj' [B:?0 ]2{L`*Ee*`i]_>FI<!s9(^1SX_[k!X,"?bl]geUgIY/},˙SuZ,vM:"|4c*L9a:ޣ0 89#J\p C:K$>ytj%# D:\lma ♕v,vra\Qcw\qB͞;' u&J|o% \xtRtB5;wRZiۤyCzˉ ӫ=7pSg8?9-٫657j˅ uSvuJ]Myi fFk8[ͬ?-g!K^,gN}Yp7݅~Ȩ+Ço/~Ag nwx /Q\8lw!gϔly9;[l:ejN ˒�p ;7^=f63V}\/ǝָ *㘎Ǭ(ű]BijBg:NpNI:I'$S?5CFiL*!XX3(TEg-d)m1+ '\dAIXQ>fQ[]oڋJ{yq^a?hZx�6*.�"PbzZLX f8>j1.S9uU2Lш%XHmRaaRVXc!8up{8gϞԠ쳰ϲԣ Ud sC*]Ԁw>C駞<K`2U[TU�4kZ`y2$.rүĔX�M7.MBq_l8b#I3gVrze(ڲc)t�3,c[R|Lj1x3w/?D]k^QҠ1Ln/#*G'ܗgLekv !r48kZp~hGe k739o~~{P`х 7a;MO?sx26-\7 PVSPgh]svPk׎P_3JWGJa;'� ) d7/~F@d= 5^f3�*Ήw,Yhck{ %I|ҮdN+t@9bń. lxxYy,1]L]椳*p%#ѵ:ҷ{m~v Ŷ`us] &V|x>2tws%iQ}O>'8Odڻ@72cT!xpC zEݱlCnH4!ZI{rj]|x@^SYjMt�a@%\:[ ZF y+ib!oeYr'L{'e]!l.íUgQN8#4.q�~d|*U@k-JNNx3G]Hoe۽ tlvIvxWNI ﱷ` 6hy+m8oQkRfں gE}z,ܣğ--ഢr"|RԳ)^48(0 ntr+~(ˠC;kO}q 7᚜ lbXס�� �IDAT|b:j~ۖw=v(z�~ef}{?$}|}FK\TL3aN#+nBȞuy/7oOʲYw<Ǣ*wi[x3p ;!,1 Y8jTifaw%dRQ[Ÿ6#Y\A|uJsϡbg8xY*g;ȭ˧QJ{9L0<5OsР d5N#Nv8QK<ezQؐWXbP8S3) /uϸ.WlDgcM-Iɬ[{�3 `H$v$@B"AhK2 єË7}+$٤%G2CH�i"1XftLwߥ*CfVe{_w`w:^Vez2lܽƉg*;*++}T`wRc}Z(3-u?A]Wp1117!q1ld9Aʙ�C  SPxu bPc� UT([c1-0|LYRE`59*s ň4PʢA!l() 3`"1L*nN*j;3oR`V2xFdhLos9K Y+" <@5 @W +&4X0NלRH AIQ ֪Da{?Q�Up<d=HM }%eS;(]P4E p =DJy'~:->Oyt[LӦ�m01ĝH� UB?/iB)&x IDfJ&wТ2r@yL׼.!�]y_Û z+Wv0zg.QK`x+:B^m7ݬc`->QO5| _~la"̵dHkT`<XSz|緾g~CSq]{~ M`lC==z6ӻT<F!0 mhQ5ZW[DpYa'@?;/_ߡN$xYFL㾰[%I?yuo#KF+%E%шc f/K-c*xΆ_Xݲ0\挐+N\ <wv1Fsa*U1!CgZB1 c=Dźf9!*(!d1F9,g&݄A使`6P!ƒ\JilkyBz\�pymk1۩Xxu<# ^^4D+mε4(Xcb qb^QU c3ΑWU.jy^h˟u9xTV:/LhX-^ji]njkja+<v+iOiuxek ^HXcJ]R�T6i;mh< h0Tꨠ s*1I,7YKgrq04ިPؐ&BPycȄqng@#,,& /%+lEJ/p9ڧэS[ r3ØC{+%޹ek\6##oKUU95[+ TxqRԷ{`~!u2v~M7/l0><烬:?ܢ_f񿽝+ڿ777f-xq[;[YcWg.sesG(Ypp0=Cu*v:e~[Lf8 [A5!ح]صU~1iD7X/Op`FUM`P =p)ۻdOA{C6.+nnRZzRi`1ʪVˌs+Nꚙqw, 0SD+٘IM1>A`Zng2C|t阎阎阎雗�BX%{F iF1F(X"A xjQ`RΝ4/(yv ?`m*b/,òǰ,0rf` \ RuE)RTBXCq/X$zsr�M}#hKSg5O+64BQ 5IWw5ZpW-ݤlx2{0ЀDY`$w pBWcl=|?jHKpŒrGjeT!CI_Ǯ~c@1E,XXmo{ q;P`lVaLnm!Q,pIxyxOX~M^b; )4Y˅|O޼m%S.o24Dz> 0?s?ڜd2+ \dNXR[m}} hn{%Bm -H/?4ϿRg,=cQo xL>*[85lm]?o+-!,: ~z`aILH{7hV[|ܨ2ۓ0ĻG)͒Yco;tdyG|"Zȿ֨܊,P$eH_ zP<戰=EQ<HM[[\JG^sY†9kSѳ7WvxZ=ؗyf hMvj^!F݇$V2N]Fu@qڳb2X1XHPl{Pb 0l<4UU3Ԅr5G\<.(0=A>sv=1BLЧ*-%|A:*?lOjvn]4yYi¤:Bt\5zi .x(jT60Y,} ]<ky[n67:S`^PVWQdkIHJUQli#B,nykJ괵)9OМ1W|zd_YL^G5WH:uX{4o[VD=Ir,ٸjV lGe&F e<ϭ(}k(S <R $ R !i #7b~ZDUn/, -KI9Ml'Grwi]ei3'߇dܞ8~S\bEYGҪi>>{w4?_Tt\wؾ=dǶ+Ln\' W0Le2b'[E%Z u³c^p9hukWmvO=Nq<ՃoqV7X9}O n06娏GPp'S٭Nv{mQf9Z kPyK,s0Xq{~J]-{SNVҧo*UIRupVM R!t<agr Lf+}a18ܾ+=S6z阎阎阎雘Lx�OUÁؚ(!ˆg=yawx̴A 6rU \<k<(0h�!l8e')B7P%"PHs7!>{2'c)D(:(}�f$I~%L2o4NC6jͽ\mAA2|c w[aVڀ>MBwBŰ^ᙶ]f}cadgՋ?K/J( OJZ( ew#`wp$7uĩ?G_=)vCr !7P Kɣ �"IlF3ٚ8rt#%v#6w59 Y=ׂ l<x+;_bb}Ϸs2=Ʉk׮E])PoAؒI|b>:wb Xӛïjaпt9ط"nG?UwTI @&|{1GpbcyS=N-fx,1(*"*2$xŖ&x wO18svz5ZQ3/$7ar_0NM Kc~w=/i&ӪEwJVaJ0+P%KkB:<fhڕ-( CR#11cpciLyc<xyH�U=7]Z9V�R.Jsh=RF%I赚OOIٛfK�p:.AE<E!B"Q %OghhS Icpq,! uJ)|Y;{<i#^NuaHs9ޞ31c%D'y٩])ҝk^A5`H 5uM7L&ἢLPM olp&F&O{ a5n 1ED6vV,^ox~?$(2GϙN-Ԗw*2<,{qȺtE%Uk K=(c c)B. -x&cCj$ܬmYh|"_+LgUH#8YUSDHyj: T=(XD`PR{l]+0k8ǚHMʹL7v ="uI2~ëR:ABQ7׷HQ}}y!OxW ͞-C&_k \7o9nw7}0 dh  1H;Oj׽B Ng0qQ*zS[և#޶V\嘞8]_e<06߯*'w{\}Yq ˔+\˩a=*p]KW'+CwƉg_ڡ z6}+vŗoL1!cXf`) vz@] l]-`<suSn=Ӛ^ϋMfLwä3Z #ʍjʠ/- rrH~up{)шg Vac;f3%11119rAxv(e.T4‚�On-!ġpx.RX/z|]񡷿ͨUa qxH,-VC !<Y- K�gRP=ƢZ}SAXuJ- �S炒,q 8( ؒ !IhmA&TJBYrPM@M�'Pi),1dBt u3Z ,!_`u#&@N9[+*fM.ܗi]FBHhcbljPð6qqM@K-P \�Nms]0S(H -WbD:-xQ@@i=@8彧!~ �B#lCyxCc 𩇴/' U]w#?Cz=f3t8Kn=/4{�7?ňBN+T[Y,26OdI*-�_W~ H[MDqk~\@9^~w~׮Ԇ9o-6aGkr^H9{;'pXam"ԟ'g>YqIKk`a-{Q׮s]QM{赁oeJV'bֱOm ]Ao>O9<ɔeFҠU2rtL;~fdqBi `FU+u<|UBTmF|9V)6.V#�<ˍ <?fs| GF1M_AP6=,͚3o_9cnrfj{hvTh mYp4R%o  z 6(Z}\)w*1$lGc/ݿm%4o(cJm@n{ ߆Ky4xCC/La͵Lk$WT6c? ?- cUΊAD1E8 c$Lј ;the0Oʧt.iQ;S79enG&/QRW5 `u=Q򒌑L`\/&,WqII[aqBGM2O7>>c45M{޷U;2EYۥYCL61I\g 5s!w0Ѹ$yY!3EO 7c`7V8n~dpʵ\Sk^b ݳnҎh u950//]_sSc&xd=ۦ*}A&ڠ@W6Iy3UdƉåGB˲-Tcdcre<"va}W<D!BxOJ>S+彋76sN jf1HW^_~8ameOn╫8,Ԟ}wgp0pX?tZV v9x9,=|>Ezoxb4}6Ns/Co~QSOp?/j@ ::EǩQRA+xxΞ\{ԇ; W^#sIcz2]Pz\ҟfAuE1'oKr89dw:[VWװc6z5LG쮬R\[z@阎阎阎%P{NxyX;YB7U.vB"ڣFkꨪsם7scw?"{i#H3Q<n S) b2b(c hhR -bz`>*1E  <=QԵT5 E1b A lic/@VLrLs|N+'F3 ]~NpJ'OF� (Qsp $0L08@ͷk 0I*[k5# T/P)<Lomc(os !Ze7, vwRvLoi<yo#P^b~*.4*ݗ�0)pk Jp{++,ӗu{DKI%ȫGTPi/_SO={{{+QEQ0Lb@J`42Y p=#dn#YU`FwbE kR۠S�ƧsDxw~_җ-DJH181`/&;'}e 4'-%-ngyŬXsP50gWYYy,/'*&uHEt~z'^蟹]�Cy�? ?_eٛNޜ[ڂ^29vyGsF Ο=]qIVW }P88<dgw_/^Wrcw12Ō70y{Ý�?wKێ:j؊yA|I.])Lq,+|.5$. NwXm0(б ~Niw$Pqﱖ(ZgϞbg/=X%Ukf~yhZ{kYt18HþMgl}d ֐*(4C7lUƊΪ>g"! H"UeY3mE]RXHHK`LR$o #AYzҥ2?FXV֌'iǥEtcHw.埝[�Im%ʣ[Q'|v[RJMSkRY.eBReWbނwh>Q{{\ҚxW~򪚨�tﳾ`E{څ�� �IDAT,:C{iDl¼՚6mob4\9'_s_~,}|8MNsqD?eq~/k15ZGd.E7 y&0NN0 "0z*`Ù }ڥ_<]Yg3ۦij e0`TRj/~NmkA dy3,wڨMttXu=ɁL pֆ +B+olџPOY)Wg gUάseuIQƘ/|gᕫL�R 1c&/<"<S#<3XɔrꙌ'ؒS 5†)N"EŋS,%+e_|s)Njqn`+3|փ?sLCfSz#kT *<jr+J"lLZ;6Wp111딖y$/H aH!3F%: y*3#츊ˇ\<cTO<g6EK"%2RXZ|C,Eg<rX(BI ϙAkKf< K LgL Q[<aQlx,V̶$5i—؄EVo)"(x8ΧK�FPӊC6O=$2\$z&?фK%AY<zyu]hYGᓟ qh'D2! a <qGO4%A0.F)L9Eڙ?8t6Gp;)ZuP1?+=@8,Kvwwm-kK G -/=4R><m 2U[9Zuf;äC`tp>׶Bd<_ (V Ϡis;'rI"D1QZSi~#(@ŸKԻ2Z;4IYH�?tr,o5gk)'xw?]{{xN}/Rz᮳gg{A~ fv[v"I}<tyeJ9_Ɐ|g_^ޝ= + l%/[dmo}ފy%vpc K0iyH6 {Oώt;Oek7koYO!3yҋ/abV52־I`%z4]֪ 1lZI i9{|kėrq'|Vj;4M>HkŃ/*1CH녴M B4Ҙ6(μ(P`zBݫ<%LA2~}{!)N0v*Q9qx_PɦIA&A{khƃ̛nhWNhR8fBi},>m)ZL28{ jQ Ho7L]>{s;{T5F y]sԮfe8bm:^PZ+yL0]6 hR'xǰ4}I{eQq='C*h溼1IofyNdq-iAT iGJ]|.DXr 1n>yBw{ BdQw57-~GΖn[|s+dXm_{nrkԇcSW5 .;|p;x\8("CvCċR ԦĜ<CMa83Qw+ؽ {eUL`>jC&(Yņ u9ᔃ ?q{S2uMYwRxtrW1`5׎1\`0b8XAqfZa:OयcspEi;0a=Wp0j8阎阎阎){f HV$੍R`@B'3U5'_x*S x Bj֞,b( k) òO_ up^q)èGA=xotKAL bDq.�()h+$Yh=i>fI<c< 6_j,$I-3Zii- X(w˷2==kg 0U4bq Z_˾3tz`z `c0s,B3/D T uμRGd)2zIim.mYznod62b- `uGΉ<ʅ wV!7:ؖ¨ ЬA))* Xi�fSMCl/u@׬|& `ewv&`HM |˃SOSW!|YGF!Dk`4xU!5}~I yt~y iyx!  ?/ZPJ{DwcvD&O�GuJF.ῄaX( I?e>ȣ'YPx쎊"*nyR<x|;>8{2á_L1cy={a\ _x;83$jsyCж} mtϼwY(v޾o�]l y"nùw( Ҽ.WF;{CR{|?ė4s!xC,PӺ}&IVl9Kѻnҵv4|+3_ oI㙌 ԣ. ueL;&:e1pJ%hs7fZF js6;mzW2v{n1z[cE<GA@caDX<x>og$c iC%{&WK26WUΒ 񡱆'O0N١RDi櫜ωŧ'`E\ߺt6 _.Ԇ<mJgPO~+&67XC>NƧi RXWq\ial5zE])o(;:cM8G[{[Tl ar,U2Z.ܧ1s)CZIPoc*ISvȣҖνmY[/Lejw?;o8s/x*w90=sMl]s}2j*{}8lmN{Vb^mop]pt)$لٝ'p ."s{;蕋{@O`5*]?m?KezJdwf',3~O'B6O!Õ]vCVArȠXc[5w]gף\pjT9Za{gZ͘MƜ_[111ӟj9P!W$ŏs97rSsν6AބPKkpckǟ?"Sj%ܝ)MdP+#Rc+~ch *F(BB![jV=U-1EYIJ?Z & e7N3]ל,J"sю_fma4`J=#ɔhNU1)r֜$a >'=Vj(zw~_e!qMA8wz̓!(y{"3~K 7:~'wq73O?GpP Rhk 11:.y ;QuP&D}�BV$ik,E:9 2|Q1! xwc` ׯ_g<SG<-L '!{ *kF;O=Q^ot{d 'N>Ao{N~S y M^Co~  F�/Cidv[ bү 9x`ږ?&R[k1O_GH6M#뫋_4 ry3VG~'?Q[=%|7<S7{{R�DjjmJs^&Peb|ooč`Հ{h/ *sr~<_t OJ(:SCC.Z(cG`;lwWYs@mcA\U D x};!o1B}Hm2˫(7@jKOSoA{-;KeO7g"5a^֘7V'Lsce,ۃijzX k1"t4ܶ~v8әtmhؠWOqKEZ:tIz>ꩆJz']K#[/)Kw.LM~ueNYu,kQsk|s3aՎ^/>ۏ3pWҹWm{n5M]n;5+j6cgDub1eS z٩%)Sv':]aZ*&3fO#QzC 6-ćbho:$e$KJ)n_g6-mhλ&j̲=Bd|k3: Z�(@-lozh\X`Fw-ӓpKDa@Zt`x4~5<vu5>[N{3e0QFuᐑɬ{7O0,.ۻp$ 1, U&ΣCW7a* FHуk;x\atL�{~:L ϜX[cwg-HHAO9#dflܳj8Ss %3dOYreRsapn̡PLg21RW15`ܘx67vn2l*\5,>c:c:c:R>$4[oQPc8@޲3`](wV; % Cm ST(�ux^4R(X/J-BB�u 8ģ& P#R``mBEYAY >Z &fP?1e|\PI8P و$:HHS_)|R51tNXƱktSHW +؊Jx3ְ;oT#n4yeacc�œѻ9ӫ'0\6R_UYzpnVzg`1uEJN /4�&:Lmj<CsPC_#myI(ZS޼,G*;;;7l]׍CSI,}~>Aۅ#sb!dnJ;v{^~e>UUja8@TN1=/=*xS%7ݞ H\Pz߉V{/\VZRF�Jp;sg"%czo*erF^Y3R~Jaؽ }R/%]BfzЫط[!{'U�kF~'|QPvm+"zXb)27_Q(ZyEp�LABz&<I!�dHet+o(EOO~=GQf1\9*]3=OJ׭1LVJsJ@g$އ >Saڿ}٘`&:$x`xǤ4Nt~/H�v 6F<ydꪨ 4Lfy"YXck~[T kx;DJ0Dl¬)D|(K5Uuo)tI(R!1|ކz L+XLHJ-5MR??򾫘ht+4J)swwgI 禪$֠ _5Ⱦ4;qMeu_-S4&[Α ASCmG<WuB܈lwg|{4kIZs~>j 1(b[Y¥1;W LLDdqr3zgREJK/̳5!ح5Ag@ʻ&wi-\#ݬۛRk#9su˘W _χl?T4ATpb g]lomqWpuEi8ؾ|6i닳6o:̼r8LN"*U=c<Ĕ"1\/f5 SEKLoO烜GM=Su<-nF=z`uea eԀSppw�V²>0;=O ]YȹUW.s)OakV`Udp<:eTѐUt [W;\g*zF]Xʗ1oxna&Y5+Cw^ݒGW:ulO6F9-؟ZqfoVMY6XP9~]r㘎阎阎\jπ3"Ȓi<A$ 'AJCYP B`*/!U`x*`\AX|:q xjow!._SO<B4Łzqƙ,(EkG;6+}665;=jg`jF+(I|2I)+-Ks!up&"lhb|h񢱖6\`|T#I1d t.Z$Ky'˯ߢ^õygO~k~¿J:$a.90CS}x;ȷ�<wA٣NZ<m%xIBƻ)󲓲{`_"{{{86Z C%Ή¢7s:4zҀ@y[(#"b;ڵk\~3g0E,41xT7s [ Ƀ{qȎ\ Fu0?4wX.yqj_E*)X_}POciG+2|э=&F: 0 ,%(e_Qce0s<[ s�ViK)%Eߨv+8ufO{?_S!zxQ\ )cN\'?l"!8)Wk"̞3T u=4&DXa7/[1.Sp_gx2ɗtF FIgjC9s=S\.\yK:_:Wg]:\O|~(M;2ȢFuaR5޻ <`U\PvjX(mSS5Dp%3b~HcY=u7) Z3 ڜ/+5B]h۸_km{i=RwTZӲ,k67'ۜu͇sH=QIJ6 /ÂH y+I{sJOvkh3H4U#rJv:I =c|I{Y62yy2Ex{zׅ:,&kO3I3j|g:rWwl=[٦݉h\v]^״Sr>.?!M>פJDrhPJ6$WQZ?y ^Q/3RIbhdn`旂*xvm[@?8~pA47Υ4cws`pٖXÛc=vwnD,y F,9%k+6m;N S�bXr,J5�� �IDATk�x -(Ň >"6=ʤ]g̏>Z yxF4t=}-u=~]V#<F,u9@>+#V#.t/BQ{ήg)ܔbTG8\A'S /!')<%WLL/|eZ)EG(l@#׷э¤z=NobD/?L;T0PJ`@Q=q[:SpftȾ�+=zʪ{QZٮXkŀ>𕍳x;yFa*E_џ kЊ{&Fӯ`111늂<O *s{cJUZ˝gȋ/ij>Zz*W<x-k2�E v3,wCx;SO»Fŕ`2+L:!xgF=\amqQj{zOt2ef)(L(ѨTHA 'J �;C#YcqH\$'8a ] /#|OcQaAn@%Jƺ= '~lo?7 `pܽ_dsx} C~X"ud9,'U"@�m:Aݽ hW(>8+AQֹyؚ�8 6NjLk$Qu;O%T@+7KP/Ia<-q3WYR� 666F6܌<2"J ztߑ9Gy}k TP]A*Dz[oC kPhȇ~'I= qP~#P h #7cu,#A׆^EEQZtY0w@P7?W~W.Q׾.ס2۰2~/|7#T&!S1a<~sSX$Vb̳ZI\ [y 齕l E1_鯼O=^:}xgw9pP R%z:h'+99tm%SڥӮmv%rTRF,yyKS纠1#xĴ}TU|Gbk(YUFO;Oܣwi $ 'ms6<~ɚf)E:Rm1FQ]Aos#WӚ9O&CY\1o yb('XR݄ȫ.sy^☥UBx#Kyutܑ}@n(rrl72[ݛN}NaMoZeeRxhyv.-N}U;٨7v[$(KxJS a9KkTae NJՔsk"*%DHuWap|6&y.|Qô(&inysλv H$V$@bH[,ʎmّ%%鲣(*ʧ|+*%;[RȦdqpb',3^,5bf>~g ^ SF{{آ@#֏ ϸԪjsF& քwU7L|zY>ݺny~H.0ߖfg68:ircc(N*,~gZ.UǙѐ;|F:NS \٣ZyeAJ( K4۴7o@1f$褊̡:87^0 Kd|#tP NeNB10 iO>Н� /|Gwb{vLah tEI <@iGGA_9"(i5턵Y5 ib9ph< zN8=fɑɑɑd7\3j h+�P[YlQVR2x:Η6$R"'q N^iv6wk*Xyiɀ䶠N벚uȔImĤ{ aeeȡ(=,�sb\�b?+� S+M/UZ{ ȮA G)P!oBnt۷[㯨CȌ:-~>p/WX@@=L\U/R_`6zxT]U_r \�rP%B3 , qhyބz�WpPZ<Ɔ1TcXrB|4MM'<o!"EAj7hфP,&z?70~{H:#(^)zF*4gN_bΝ [VqT h-J 2y7zX'@'doz:#+1cqqv&MۅO(t5%S%4]Z@bw zvNO,nΙ۬:Ҩkd;<_J 1F-Wv!@>c\~n UTE7HAˇ8/4NF�1j }_aw3h"TC]MMqx�@o0پLo/`a 8{O<�Ͼx"l_irzļU=Te>$_MT.hȧUڷFBkz/kM.9བճ*07e#8Ī\ fAхϖIbHIEe*6 aϋTȃ>.:'ߺK];�g<1'ex7x9%9!Itut r5P1ke~zɸo)Mc~Jk՞>fEZyIPny ·dF66r[)VUG5VzH'B di?U`ܶ)OȊBNNȢ NeX[(Jָ2h e"'Hsx=Hī>(R/G]1ة7W^&L: +@]+&V"ӛH<˿/ B= K}k#'[GMXs!|~aH`H9p4"Iʲu=oy?=︇w&`:vQl_B;JqtWO3Oh͸;{ɴM/Pe4:=Lf%Ar&灵3ҳg(BYM)qВ#1Ӈ r$]G97/_eh+d% THA]~;xLiL2+/#Ʀ, $ qrM5)PN !1LГ@ۏ,>#9#9#9Zys-Tcc3k-Ͽ<{!EAȧ'5(|icZBJ0IWv#MґnJ|&r=;n$ ^rX)MzIJ9F)uu"8r'h6(ˑxi4��xh{8PU $Y$(dJ}8 Ĝ<50Gd>`Db^+n FA"Tq&Or~'~'HFg!{;${5=lWD"ޓ/!m+4ݺMPTHT,">uAeu?b*Wλ* >YCGW'İxQ%De*-!YzZr ԵOׂUpEg}/]9˽o;.]W^S666c/mqN(h1j L^�*%c͟oe~5Mie&G ՠ:pqP]Ο=;q<BE6`@f pO1Wdr4 n/{|hJZC#竩âxZX}/(ʄ-(ȭeg9F]Hfʍ)w�Hju|ſA?n̂9b_;35wn~b`o>�sӂ{~O}$~UHM:)-C$hnĕ4E;4~kpg=ga c䇹#~Z�&!<A)B+0XǿBݴ)%l N:q^zL QI~ƳqwNE.CZEԻMJ$4J5f͍yѤO-WMҘUS7T*ݥq>a ־ʈd<h?oFw5xл?oszTzʫsVW?s-u&3}EW7+ A+ :m4m7 ub 8'a┢T-Πi:9 L9qkʹ~@0Tfγ7kMjϻ!N:D%>.6 ~]AGet{6Zuo/qAOUgQu`~R]}%x@XF|/}IJ)~6qFí2Zsl4>puZuD[g)?{3$ufZ 3*6넴dp8̖(w!}:*ٷc9f6SX4Cv|AhLu3'ʛ$ZS&8@n}Y<f{з RYdoLA]ohpidJ9 HY^… cؕƅc!nݲcsNt2J(p+)eˆxɧLʂIy)9sM#9#9#9#y+IE5MfJɺ=CsY$afEid4"J)$!1 +++ G{8uA!Q /+�6*ଠuJ(ĕh 2HpiYPqa_MtHT+%QXcz6)EC ie5l05 P JTV++J"c:,!xU umAg7T ranIawww}{~cCxGiDpN,!wV)]J`/ $ (f2Б,(QD4N NrWZiמ5@s�)ި|v!֊#Z0uv=,"u,W1LSGkx<M�{VĪ\77׀qI_̿w1$F1XZg^6,+qlmuIȋi(O#J3)r,DiP=,]\a[q^o,;�>С}&$R_QkgX^0L\8{4MI"K~aON'(5C 9&W9ni0IWLWߠLc`:gQ;f>)M8DZ[t[*vf{B'W:8V >3?pO"Z KcO<*, R݄;O/a7 HPݮWn%V;0}B]i჏?ǹq3n_- $@bWŽF@m ryCDBv4jh\yјLyp}CJ 1*:( P3n7f3:Jwk<'AL͓`1=9CE՗^ơ7Hjij|{ uT\ 29-šr;u"nRϽ9{2a8P[QdcQG_TT=U~vXЃ>_;!ngyre.$cוPnd?]cm-zfE}>'jQn=u]4C"iŠ.h+J+`AR9yYbELB+Pla*V<MWmg$=:<͐A Q\Yd,PJcksh0`D[8WD:,bB><-u5.x ;yIUƿU߻Hƶ7m[x ~+䞕ur[3#Q$ǎ30CFyO[0yfNǎS'Xfl0tX*g$XYt_zBOE,I hXۅ^X^t\Փh `RH݌i.ĐOgdS2S[h+7;Ԥ]b9r"Ƈ3N7'9i>Ƥ,s˲Vt(K zYI pl.Αɑɑɑ ,-;캦44MNI8pkK:qmmF>K>GWHL M^BO&5 ˤIYÂbYY`2-f9rMQJA 'T "(с?qhh1DKJP0S CI9j@EG+DB4/ZFPNCc0ʇ1VU6ѓ9U=/4Rh֣΋ om'!mf ˥/s '<(Ti(`k謝ao(ϒޣ}hP꧒iE4( ʢD+1eG\V�ZpEPTאkUI>X0cb=/.,ycc8v лH�WM޺9v z'S>??Ks<*8Wyc+7hH:\vNZN:Ơ!K!,38N)(/A҇TӴ-0z$_s׽NrmP{g`x)N%+y8UbJiOql�侒.JMY7e;7hhH!ҪbeZ K|@�7 48y!8o>s3hqV9>?M$߂E:Z.Fֹ6QFr(^6ABv2='/yyD5Ӝs$!NrEVq|E^mŸs=2wR zWk~#,~; (Qg>_Ϲ@EW7;xף6&kF셝NS'N2vЇ'ix*+X׮^csstL-4,Ko<VHU$_ь-qUR։9[E?;Ue:,"oe \ iIh4 l)\g ^F] A<"xq=wE4{|;`WRo!ZsrA[Jdֶ΍kHzְE5ks5QC 0*V %)2PxDR s![``-WhqF;l\XOr!Om_o:!Jn8\?knHGH XܑhMi-,QVHPQ~=\\ϹEgz7C6"#8,E֯߉&^;XF*r[hcW-ZW_ʜ7tvbU8} 7 \]gx_6U8+\nۛ]bMW/3Ǚwfx|@mb CR:R=vpsH.58Bu:0B9u>z7IvN͓Ļ7Xu}ebsm3ɔF&4`&{̦2[p2YA\93[w U@#Ѭf tٜA+hn:xԱ7{H9�� �IDATHH-&2AR@mmr"nEK Qs6ol0!ޱ>_T^'k!zdhȋ=dI1)Y1$Frt=-09_Cb,y9GsLE0bE(Dp ʘ74hAx` ,Tʌj5AFx巉6SkU| $4>< W"c*Bh6WGULw_Mg\tWo׾'x| bNC]3}w@15_jD.|t*3a56MMړÓЈ㥐b.aoH`EP& $.IrOGO٦½yV;=m#x,@n+y,�{E_/2E'Ƴ| JQh$\*3ӡ}ͭmǎ2 pj0rBy,dYx)8~N!TIgi0ABNUN.bhH$}={~/mq΂BJ['뷮A}Sk eouGݡC:0݄\UR˿WO>M(ҀKK}/=BK OSNwiM6vH&vXm_awH@yÒShia64j.R_p:J :~u{6_{~,$i[S^~JI!𯋕phקj-$% |;D'Z`L H߃mX?vS'Ou>$ڟ9/BLg7[ZZb}}ͭMvwq;"j1kk >W3U˫/#=})(Oki3p:AܧTK;si<5O*M5ʡ3B-N1L7o0^=)6l< ,ԞyJhM"q4[~'"X4e^w/gQٷU{=C2(iXF-G1CCIWPJRs<ٹI.J9RW Hn%M ƺ灦~sJ(0sHMEs+mzƋ#uPw}]D0Mb3h`#A^E廂}u˥qD1+S}qn.{W9ʋ7LjqӛTۅ4+~ی;\xN1+-Z;.v0 R q>ga:D4 t E`)ΐ#$,9+k,T85 kGd.灵skK|sS NG%tL?nweU;$钥)rxwӔBIR_9;z=o^ɫ�O&!|_kt|+_ƍ�Л\'9s7?Ey"Bgee/| ܸqA&4=B5ʇ1r5:ͰGc:2(Tg c#ѥ,"%J2f[t;,!)F[%n`b$u-5"gGY3L"ʐlNl>FCi'S5Q)CZyGD�-h4 9P7h ΡEBNQg[UY*pU@"Y<cU rz0$I*Wb[V(vW\_~Os{p9x=<BEp 2w),IUS(aU LS} A}x9ZE+UeJ Fx`R$tzCNw;}677 ?`˒pBSS.Q+.IHې CJX1bCBd1!&1#{A,EnyOp핗پ+-LH]ud7z`6s}^GQ> Y\#9,U6cj#]ɖjSX)`놸<i8{#j5P'45:/E1<.,kg~ao9Cr ȚCк-4 tH bNuD'_Y'?.]̥k׈:w{ɏ Sno0fybXQH& c"+.F+Jݷ=Z8py&穰.4�ET q<`<=xfVs޴?"0U-2`TJ!M)1_ &4Ki V NVynlR ۮkyZ$ӛQR4 5 O%U(b58lQgx?>Ty [4䔪SZX5ּ:izF]9V7| pڅ1 G$jؚ:H[Ɠ O֙QI|c-q.iJ3j8'4 gH$}x="o海uk>Jnt['Ѣp kCA:Լ^RsZp:::^#xAW#r.*ngLS}֟8G6٬<QRTB4k};!S4j^!%f./ܩAtvtF]m?kh>VG8`͖ B63?o?r7Iؼq gɗz>}e^vCy}pW0U =`ꕅY408~p;dI(#(pkiSN@e�KWށߜzxazoRY}>}>dOo,`gS|em{[0XvFz뢧C̬d%LTU2: ztɔˣ:VS'}Ґo_$k]}~S:{nwOW{,-n׿9/�ϟßZƍU]4a~3i 9?!/b>z7v$GDDQ9(˒lø53u<V0h˰,?#pڴJ@z{;ؑV(KԚL IQUKM88u\NI. wZ K Ȱtz5֖U}_OH:ڃ�Ĝk ҶohaI眠OZED \J5 !1|B0ʏ8MIx JC 9g(FFMDַ]ߍM6ɯ>MϜx/?ĉ2E#UK$ z)d=) ) k:}Q/)WV>u&`=`{P)AjAGR¿ xV働Vw~R1_E YJ`ăј �> nF {1hR##zYq~!Z2@Z.]-f9sє2:}E,ګ- !j@΃>=UE[v.$ =HK?N<qs_yB0V@IqTĖdɚ$4Ơ@]cRЖ:1gL7/ :Pk؏era,20ywyuAi~xY~듿 \{aPJIpfI7dRY[m% /8H==}/^b:Q-rjxZ,KeYUodZuڀr\E61E ϡ^FηCu^@I�V7ÆN>Ji*{mC wS~OkA *%8bsR9EQԷz0h "ZńڗqY#Q]p~>:z).⑮"ߑ L氮yF 5>?Xw,sNf;|'^̑ۇ ʝQR͓ߵugWzr?U#NZT$K&"VJp`+Hi.I K[-N@ʶ?S@&NyR.ttue|@+^|$* k7>vm|*rdž&z$mPR=c6TXEyÆeoV,ăpe#!u5c72#5CZ1X_6k_ _g6ߎk Bs?s(m(򜒂I>(:d4&;$3)tztRڃB6Br~b.A);]96p ;X'Y`=H( !*K^zֿ>HY[m[NH{'$I> })b }ݔUY]?pcXv>R wn_?'X:}Kq cL'9O2@SR 8skYf$Lv6nscw}<}}�<yw[.]T5{{{|C:g}~�t]xR#9#yd8V5G֗E<yq1y55eYVӚIB KA)X"v^G#ć] 5 P$bC\<HbZLɷGl2; &Ͱ &Ҳ7aRG6%c܎+Ԑkk.-sJs֓u iZK{jjQ8~K$<\4 dVԶ\^1'HKźU[֬J)49#CB@ �Y6@Xeh4K|g_2'Q�?+_c?"w̼g0vXZ}�Zw _K_op+'OʚړCTM`6Vk}XDYR:,q<D޳JamR>4qr6ڇmY4fhM;HҤ&� JeQ ^a^h<Xڞ8M7i, Go.wY͉K<~6H I1+<#1NW_qH'S1 gbOyTy$J7P45H='`_M' 72'wAw^PQ7N CȣLì9$ M@l\EY$ Iy2C?#Էz[]Fߜ&|`=j_ ]fs6NY,[[xV k|{K0o矻㿍&yz;o̗dw8ȮYO e"^UÓ>B >4*iF9DҮ"Oԩ" 55ח.;VPUx{R50ytk2K+⣄(.rtgQW˪Lħ73Gf^ ?#H)5=jgUC+ վ\Qy[g^&YWN!2ЌoS;72~T4,UMrn-s金~0Iۛ/a\C:ttS۞#.޼`<Bmx,9LG} 59y֤!K3dEfQq*-u)꣆^|+'ѓZ]V:>Qq}a/Z"$\yK4(pۣr4^ ~ϭo֪-^E^67̓e$)'V8F#g9U=v/:$TskZ[K?8>oq,K1(N5GY~|eTiNiѡ{kB'9lƥrNYsWآdu8Bf{{{Jotd &CSoz4?[.>UϠP 7H+rm_Hr!)f&ì,Vɲq92jZ3 Uۜ,72k7(;^N?)HѨeYa`,{\}Q}f(֎҅1X^t[& VVȖvg#2*>7\!*B;_j>Oq}$Gr$Gr$oTK{{{8I`ヲ@ 2҃J^!ʆOF)LW(8O\!>R�HoT*AWh:AT(CQ)Kz!M4GSc9єYZf%*T39(U%sHPHK[S=Q`nCI�f#% *ߨ *Gj&dCC lBPNUȪnۀA5x@߸A(tuT\3_BxJ޾Fc&|n'.BŃHSd˨W.!1iC1)Fz|?~#<'_ j0F+Z{D Z >p! KJőctJj-淛J'up@wUO>W 1ET;XOb4:e\<p{έs޷]8Ug^?!]oro`b6ZC]:E1@:$=R(7y;W€^ Ic>ʞ{ԾT K'Q_%UsyHY]p,;*ؤ'V$pdaҜOFہ[؛'O"r! _Į|7^nsy:z/u;,%RWabSgoo<CY:N_==C]e:~Izyoѱ6rp)HsO?O|=d3M"=7'ĵVCQ*uCȆ7_KIs`+T'@g3DR#!S'[JFH D* jMbBpu$Ԧ& TES8( q쳹zW|QչٛgyYE Jh3:y8H?4Oeqfr1im<mQ*ۢ<cHMյWJ4,"8R[VCZa~WUپ4?ux¾owO9k i[l"x6#AsuHAP \y =D"/%TO?|娬:b41US뜶ZjRusܱUؐ[8^R}׮~]oF`ߞ%A^7P� 9\'!5IQ7CE5{Jm�W0 qQb` r�Qߝz/�F'nj]2{})b^I{C "mrÙ9 {^7[&ՍP][[7^0!dY#%Nr.)Mc錆A 3Zrzh7zRнJ`~tx؂K_.ñNαuf[iٓk30g.~|ıE2foA7CCDZx;<b{L;l[7w3Μ=Y .]ϳ‹.)yN gx)1ݔb6 VRga#Zyp.D'=kou{1z)�/|du>,|mmȋHHHD�y cLw;xi}ze3a<0 ë&M N(b)Iȑ (]cIQbÒ_�� �IDATP*!G n(L@Y`cl,B*1 t3JxEYKi<Wr@C?8zT9Ӝ9~ 8$ybJgeR<Taix4.RME4A@&,--bss"1iR* QZPJyMCK yxÿ?SO0, 2FJ"=`d8d=Qeh|oX|(f(Jo8_,U*"]Z{$G ARVQ!`I1ij}7<@@n�IEh2&ʍKny *Ǡx/ ?O4"SLu}&+jt{W8q;ta3f3d7%Y^:\\Dt{Z{֏®W­R=S!;A zdq~6@3!sQK3�1F,24UC1aqf[@_gR;>T@ ȓL˽p\#$k#2ߥv!9=ݓz97#/S KKN{//|\|2[; _> mVyۀ!g[)a2%9dYb<1|?h%i FtmFOVee'WonLeP!1d߱*z͆r 8GEI\9kc1`,7ysI83T,U6&!Z:ل`'N@O %5z+ig}sat؏bVT$j ~АH۸rH"yں^"o-TuxW۸:ۆD-ͳ|]U46a'x!h~MK&BC86a Jd!gq}g&zVTMPŕ'`$vGLU^r& osP{.TMP?4'Y0}l*|`8_}_ulk\ۺd~Ym_}cWEXL ꬡ?WuhSSoL]DH40]s7Jnc>ƶFyR:Umg{";}߯j׭/$LEF?" QŢcސAUoq=[pF* %}u@X'ϰQyaMQN6=q?=]仆GW>e^~lDցQjY1&Os 3 ̛*�!tb(iʢK<3\rl/}oO_͛x鯳5or߱^λ8|驯Fl8r֎d͛8eeOySEFye9IyF.7t+k'(YgV⦅'̘ٛS]q0yc Jʼ`3tc0 ֏LGwf-^ݼ=_F4~_$Du S3+]ny衇8sL}|s~',իKKKohppXEg>=Y~W{{i7tq|ox6Gr$oT ׼BXz1@B6JLVZFBNˠܫZj&>$jLwC kӲ$1jNmН.v%& & T& Z4x+m@+,EabC`OPМ*7#jcJ84#)E^Z+!V2x@T<J[YZ]R<CSӺ ʆs"z� (Hk۹'?߉I;aC N-`<:.vzXDP�t0IɃLJ?/=" p@'J;}կtjmhIb=cuWxF@:ìF1AkC*x_0t<o?αSL)Ki:rI@AiJ۬)5+8h<&}ݽc P%:tk00pp\)s.3U?n'-* 1ZN xϠ| MͲP>`�!"is%sd{%}s} �aE$EiABb,JeV$EfX,'$Kv8UU. U("a$%..ݻw=3g} PC-9gf_G"/ 9=&m-#!@J2uCfzШoekfۧB*5"BNV/aܞwiNk0z?RM |Ǚw|:q22B|FD<ly-[F_%ppX4 ,O^k6g]AᷰxY&{)=UWQRla޵5$pQrm3MvLO#ʕ+Ô=Ic$R٠ %^㐆ZUT37~ ,pST`u,VF7gީbβB< "q._|L?Fւ=2}G #'<ʍ5&Pm[Q"-H^`1bP/nV�T }?4aaR4;}?`Mp!z(%D%8�†Y0ID˚>BD^h|[tjL\-UZYC 2t::n^wܶ#Fd[Frd-5ݖ_a)<Xleh)핧GИUƦjlJ]@nbHBNg8^5F naA땒 ]ֿC8lFƈ^/6|Q'zU 3Ru-dzgTPvh+ E[ 3q|d61:*s> nHԟߛ6?zKwc︇WϼJ^[J3mszIO!OƜ;v3B ܎-\FT@ߦ'A N_J6f;vtpn ;7 e3COymnf*z3,g .W s/ ۻK/2fA*K f�+K,dГ,`K2}0LK^A=~.ak}v϶h{Xvr 96Aa-KB6Y[[c8ţZOW7/߹Pju~)~F_|v? h43g k�|>CϞyek�SܷiPvrrw<�<%ɓ 8rGU}YN<9T ?3ۥ~s=Z}/GG'N_̽}c:vغ!J|_Y׻`ӘN:ŋ/еV#<rSWWW9~8N`LG=yϺ`q0(F[}Zv'9{,GCO=TZ<<|??Çl_w1mz'�KʰU,22%cZ28z XmInAu^C6 2g)Fa@g.ߍR0.-k1r8Zg;0` f0pf"o#i[I ~+'!ZGu)dˤHs4w(Qt^YR 'ƙ`ueՕe>BJTY iqaD=ع)9a&YYvC>NZ{^ \'Ͻ +}u}H96-f'R(Mw!�i @MAh;(~'wJtXX&^m5L311~$Rb  ϧx]˗uKc0n6D]\?ٳg7Ґ`蠒 a43<5WH~()4Svr,M"ˤ6>~*adbdEZӐ R]fde_0Vx@RF@`{$ \lbaoŋgc*Xo=-aރd lUz=Π @̡FD`/x otsl<h4X;~8nY#kF{m{$tLR<>֖TTC >Ad<FjK\ߏ1/+,.-,�!Iw\) #@Y y-T;1Kub~bnҺPnG*Cx~#^7KBa-(.K:6Z/OS eī'�^b5T5F,(ÑbU+LUQA�+BlʰKW4b: ccc4 v%ǃEүYRfIsg- )Υfsy՝J ^Q? d^.DX^PXR=xFjm[52K~>Xsj_YN~dـA=�{ Eʆ[AQ@f߫a֤{=JCHɾ}{I/^D$w}.]b~~`�O7O[d$ݘd~7~~ǝs,e]S-ҳ. ~;my2>[pM h/}i6A՝;yq }2&r^sIvxuvWϵdi3V,4Ơ�[[F4ZIFhE2>Nnnvַy~,s_al11SK1]ɽ4I" $իL Ş{5j͇{n+`L_9—.O=0-|Z 7Rwc*m7"rK^='NrYfgg9p@z;Yznaa??�yꩧG4?z'G>R ʎcug?n].]G~G>}ꩧF^v<�7 J:{,sss| n7gZ̵v?_ s6MoebpnrtdR^Z:/zef4McH1ArH)0 AZ.hcIB,*ƐdZiMb]\]^duMkh& cS8z-m™$BVD�1FbJ+Dz{LzӠ4E&l5`,N{*SXi҇JB{$\ �lInvxygVW|˿8|!$ G)o.]B/[?Ú]4uPVyǃ]k ɝ/qWP_|(j),GS|{y|'?&,TU8+b+]P-6UM=0k5N [~XR$ Gyc4ʂ\AvADr4* $V7]b.Q+W5NaIn.PR/Wk3\vfN{ B7G/@2Ѕ)(d=" lX"1 lӃ:VVx(-76@KVH$x䑇ɞ^$"OEi7X^AS k/<ʽL Eq ?~ |h><- \db TAk*rVwj(ߘ_NCivL0ۘT",r@kLٱI,R_lbP WO'9Yg[4]" ^~ PfMϥ0ȉW�cK5(n2Z;ܞ*jف7 bhwhwD7+7uE[<CрK-nf�!<wTcFW5Z*fceJ\ wK8SSS,ce0Fy%9x� Wr{,E pcٳjXHZeHibanJDysRyuܛ_`ak y1p{ōGFgm% sA!ltY%JBCunUV5؉lH/ Fz4p=<aϿYC^$i4X^^feeū:}ez^v^P)w]g1d޽Qݟ{''Z-tBL=WibE"h$,,/֝."&RIX2�$ 52o,u+.q 2q#$rb'RZMr'c!fmVɁ{_z NV*Jl^6;eʤЭc RaJJ:C0ѾdsVc e4vu Gr}z~"~hG}`‹ /xGWxi4??yxx{ίrGn_?ono~uC%O6<H/٪wl5q ߿ȑ#k?~=cyLNNW=Ȟ8qϟ={ov?>o9n}9yd'sssd[٧vbz=^Nv5/4@ɓw}>|#Gh48u< n77"cx1c\+1z1:D/sĉ}=wP6y8TY)Coɼ|6ŭ * m&c :Ll2d(íYW5a JB{YYDbI F::ӘƘjt"fdbHw8ıo8ηL)RPvI)Z9+,Ez@Or))sORwh,,1080I1 ޢHcX HRR'*Dt* |b> #L^,�vsrP?WJJkk|O?W>8Xἣ54P l3@KX[~҃=k<ˋ+iDG(P otJ,0?wvP =Ή<s!Gì*Jx-ȵH֚/Ra}3 iJq�O[".0V8pMv~9XT'u=TD<O uYl1] d(:V7p^rk1;XtZqxu#σ}K)\g>qQU/``}4Zz=n_quY_u0P < 6qi=dT(ί(6?| E`ϓu4S"02}kD~ޫA"6=PdxkC]\ `g5AQu k-vg~)%mJ*f*x2j5#WNsfJ�#ᰙU)dY#id<x 6NKn/h*J+�W8 d^/,8Y;5mސ ^c[J/u(#'.�� �IDAT FXVZGAa|*ZP5J PCߴְ@߃չc%1`@g3Ɇ_E!"Yƨ' xwa/R{ D|MsVnk%<g|T檠t {pD{Y o%yZ5JutOܞgpT@\7!̻"_!@E.n8j}mn6Q__#Qs�/l#-{ |J*gTŸ_pR:R*OrK7l}s.^=-|/|\>w/ ˎ,c4G07K[]r@iS7~S`5fH.j;2XI+$5Z |^,sVݩs]$%[Ye93ue!u:[:ڵY.TBcW? UCvu;+G8(-AFXlpyvA4'i&g4t@kon�ngsJOW�^O^q(Mţl.<}mvC0Xjy`l[i>{ :qD}+333<9P499v=crvO<oo�z ^9}Y\\UhCs͕3OyNNN~svkC(:rԧ~?z(^/^n9w~w6U1gЇjcĉ#<z(w3̺Mo[FAa e@"2fBC9X^^izqY j\C g,F<F)Aƛt3zjp]ws#y׭SR…om&cB16i <<Db2- JHY + A{V!eY+{Z{/յ5TL!ӆD%$*%l9P!y$ !GDk:|o�+lMDZX6+Q_BQy {#usW}G>9Ku\`>-l#M|?Ҫ٪|֛HUy6ϿL BKYYMiFPEXUQ<3/W ۗo)!JPRr͂,h޻T<uKt_a|)tha0M L݁t#) �sH`q.zH3_ЌގR?+c@@ǀ;`r?.@JbrMo(vHw u 4H6?#! {lX+{6 0*0,ޏw[B[nU}ښt[|EUhC|/"C _epKxZuGSLXjs, dʽ~)Lcr*Lx߻g45}LbbBT j02k]>y鵪E-s縴b^C#͘xHJA@C%ɐq`,8o,vX.1 2CףDϛuߕam<M@*AHyR,nw~.%?"e(3QGX2R̋~禒2={۬,k7: 醴p| #Xxց-E~urLCPVX0 w#{L'IXt|s"7"J .gϝ#Y\H_:Jۂ(Wf6 'ĺ gҹ bRўz<Խ3'k Ƶ&<l@bks:bC�֥%y?]F\^(^ez_ྙ՛sD=;lD$ϡlkruTޡI%^4w%*!, )Vo&mN=<]j' K`'<zz =e&,}$`;tW鷡fAS*͠߁^I&sK2 R9Vg%D ]ZƮu0ڃN!f? ^?!&w4-kL<qxwyڋ},qʀ4eoģW90քdg=S|#o0n7ep-_ףuʗ.g)r?tcwo*m5R˓y…?\QC133P-Pժ F L[XX?Ζ@Q?3g\�>`m:t(<?_V݊>,_ӛUzgϞsoUZoĿ_r%|3f?vH々 ܵkW15M6M,cO=?S@y naaYt �.R ڂ^Lc4LNNRˎݻON>d sgyĹӧQIֺ8Z'l'Ry{HǠHGyh2c08JRy<Ň9X MVR܁-uǒHc}\?XeYW]݁CfWֆWy!~}z2 q퐢1Y޽{ycA)Uoڰ\{\ۄW+TXYfYmwY]0Ȭ;ǡꅛ^mz%?y*pt\d<XVct~C%zXa@wk4Q逾FhstV' iطS^O T@S@EW^a.|<ע}EA;Gǡy;|)0 jAccdJWN% 1|1uʹ~A!(�=X|sv|@jqk37nd\0.X"/)pU4,!}?H_+u@hȠ{;5>!)hȱ9gg+gUo VkT*4b,MH|M(cϨ\9Jɽ `FCN% ?/Eo!)BMadμ /ss}}j�Z]ſUfwϱl[L.UK% #jthZ[~>+l죨nXœ.& L2ř25`yQ<;~:*m-@m%)~v)F9liU`;g*gup.L)L/Y{Q%!WɨOT넿nam]H<GK)�u*93;Ԯ!c8L EA3C<vFTFVuuofχ׋5Yux'pO֋4 iRθ뢍X~/LQBuz3|'>o~k>;^n3 {w I:`N&Ϡ?ΚӰN"e &Xaz�PLA› 9O<hMx{amBbǦj�Wzs bX Z zY[QMfW41˷>�ømj69�J*`kuEj4g;\\ްG+ @}7XcwKyaCG[֐cdžRl t[\b\b{L܆s3FQ3Vi-,,K}t] }XYY]<֛318~3c6={e~65gߦ֒T"kP ʘu-aAFWHC(^T8J!YH唀֒XIi<y8} +6/9ZG Bpe`g$XL]hC XAT$*3D!B `56$aB 0+!7jrkH[<Qj+W){hFdI^Wmr*7V]ݱx!-J?κ[抢,˼o<p< .Q#I8|d�rI_k$_bzN> ln|LCfwS$8X=]b@}ԯ<^gA1v),v-^B^[¯~og/�K0S 5}`vAoM)+gY[�W&<Xk^e&H]woQkd&^ˇ}P 4ഌk_FZ7dzSoMfZ}o^RCduQ<@+7լ[G[M? Xr=c;i�4Uh^50*acB0"{1ބ~$ 0c&;&<## 3ФBriʅYV:m,r\Դy'NJ0fG7"S6"̰>|zt"oC?/)5:>z\ ش9X>1eFve%IB0gPJ߭˵._z*Kথr`zQad'lU*Z;Dlri^j3Qpg L.\18OYb [Zݒm8^w.M$<x_ >\oJ\WJOK78w|S2qkۅoq{$ILcYEQ!RgQ醼ϻZ %U6Q9lF=.`׮]:t\r%1ذDY{u֧ e s7>GgZx+Uڨc>EǓׁT?x_s@Dg~u'nZ[3c|hcH4?lHaYʂ| ,PF11JNqd'[M7ȿ  c. cim',,^A$)@{3v=h+rMgqڃRA3)J(4S +}Y7UI7@ X)b=Tgi*)LfIV<z6y>L88>5ǾËYufp2S=:Fq+b@$ ˍ=fCvޢQ<ʯR `*m;gz=;x;SO{!*ouԵjH䘪Wݻw]>6 #Յx-]^K!m[fYkȧkx9jݜmzL8|SgMYAno@�,`ʠZ#r9pptJDI KY "I%~ϝcګm~b˗xyvk25ha5,, $ H\, i2&}64k&�6V.ȏhHi%(… ) ޚYJDdUm4ƂZՏy^PLeHUε*@X<Qa&x*ϻcPxz3I\:zP֒6R4q`ua[n,dYEmcbj%7V:cl*r^wP p@R L[!,W_GXe K( W[M H>Oߕbnu>ȕ!$HkNh ϕycTHwߵ64K+ų7uYn*06rVހ h#-a̾͢TVB5aa7d.&*Œ(F7rʭ4G_j赧HvC%v񏐺 m^2cZx% EXk=' cAO*58gfh6,.^m#D&J|Hѻ޲8eVBsEtgU͸F!2"ޥ(:Gz[žo"cZ#nNb*ZY梅={6=8#>wĆk5S l<]7ʔjأ,/kkR2eoKBEb c@? \\cpY_`p19ʣlDB&xlr鰜Zms<s@Xkx͏5PSZeGCI8@:sw6-G;9/Gr]qݕUη68|y{yrJnGĊQ_bzBKŖ֤PLxݷYحӵY]ˣdyd{gȹVj 'zDE s"JgPV\` c\z'*S޸Qz�?x?Gٗ^Effk&Xe$ckR8BfKHHOk|ܚ-mCƸRzd\�bڤ 2wF5pTcg֐(Xv}vN]^DqbM^bh~|$kV_qL; )SRs}i]}x|r6E_xq~)fZsj?bG&7wZѣy.͆;t#Eo%^>Kwgffرc{h/N*Hm6ٳ?a9* ẓ=_ߦ?/T=ȮwUǪ0%YF:N/ܚqYRJou%jf q5ˬW)R2!2YXA p)!$Q@Mh`B6qaSf{sHW)R5W0V[ E[.@Ba]8h)% A/r>>;zq%]D[nU>OB�'Az>-sy$؋QHizMvfyi9XaQЀWITԑZwvLE"ѥrBj^[6K .c+VMԔ~ÅBU""/�z?xFd޿K[}/KFC!mH 6Y& +Gm+ |M$03& zȲLL>j\"x)ۤJT<BDt_g8i:[l1 hw$|z0'Um aD?&+.0ʳ&�oPNM񜬡$_񼎿Cr,7:$i4g/|T͒ *0k<j+BtEg~CN HEC5|�f=P!n21DJ c(Xk9uHa\H(s9.z87�pa'WPe>j(Ê^akC2\a]x'ޏ:2ʉ4N.,ci?6%Y)3E`G)g-0Vų5:v]EA.|m:(6VU=6l r,#JF׺Q{Gm-VD2ms,؋.o!$yiU>K#eտC;y. 7ӣjWG&<X9orحznkuTY�yxbNi@1\kI9kaku/@eAD9ƅ&Kws:_1^fEPQ]IkFQYQw_ !JP?{NR>Ma}j"k{e;ou8n%dI#I80iTþbjlWV__=Db-J& >$M ˴S(DJHdB?{Z/@(Yf s&JdEVkK�� �IDAT|&NYR ~~ɫK\}s\hkdj&bZ 5g.N]^`PE`\_aZ._M[*j4oŻ(y 7=63oόPQ={xF^{3(>ɓ's0jApMd><Lرc]*Mq(EדA\oV\#OtF޲Vz,cmP\9B󂲌B<ᔜ5Ǵq!vDB,gFg8t|M2H@6RIlƢuPZ$B[ JN `ZH :sJ;r"Y YX,  |\Wb9h%崏"*]hjHG'F[%JJp^0R h0leU0Ji4Nm%C]Y1bA(PH.tB ._Pwp<Ƙ&C)`:6]qEqua^9ǻ]:{a/0BҀt�P}oi;s޻I?PQ3߾(A�V-. A<0yQEKQJ.g’2~%<d̵+%i!O>G_elP&vNxy>̎]A$h&�4*)!΁:+t5O_ H¶I9|61̀^F~#כR3UW'('+kQEIh"ѵ%^i<psGaE(o_R%0 Z`PHҒvH~^[H \- 8vGI EyEaEz* h""#(#�GW r�4CIE{H' }{0i1eEwW7.")ys" 4""<&I/d瑆�@ [ yJ"*2b*8.KU?syNfM@nPSK?igj(KJ�uk,dD (xs¸{s*FTfڊ,~z9Zš i 턹`'Evk k5,IbV#O"ov6MPUFכ5ΈFs4Z¯P}.l8xϯ m2U 4M)Um÷.[Y"Qǻ 5)]'3+]I R[R|l7Q\m#:[آn'xAs%wasаbҕ u6UwNQn'㶋PFIF[]A~tFfcA2J:0q+G1iU櫮Ȱ2ugs7aSGOʐ{xW7 H]ЭpK0EDڹO׋l_7qg;yi+ 'y Ic.fVA@Z>$@"U-we4o Gl WZ=¬,m]cp -6H;`Ji&DkZT%w嫫@;:mtw3-՜<7O:Xhɹ \p`!p tfۏn ><tjw#<y7{uO�| yaٳqtK( y'7bo\ܧu455UK333kuo]kn.\~Gkf1^XX뛙Gww܏qhk3דz|9=y|;7 M6MrOF_+5WظCin!sq�5֚R^ +@ YzhR )wOůfT4;w:j(dHiZ,^%Q)&1 gRznP4/ARxSpbd \9`\[Dg*e)T 2PJT0cwu.]bpH!=N!ϬqA Uv72Fe. f77޸- _ B$W !nug@k2aL6~�1=q??~u9=$c.zPLy=rr7[;wx֖ �A? wŔ €H@}vڐy.=[: ?_Ǟ1<_Wuq޶2faP%wCfod�Ecw=heeP){$FMD766mݟtK\eڥLUuiEV~h< Pm3nbul#=SǽC>΢jB9m p@ kUCF<Jb b=_ ӱҼTAVz|T>b /PO.&h_nKbgEa_RPf%�v;Þϔ>51t:8;:*$L=lfRUlL|2܎԰ ˈpc}`)idͼkTtHPG=YY Jx�@=i/opH amڰE*{ [k�XeuZ(yE? 1SW5USb -6x{4n`8*go[ њ~  !8D:#*(XZH7V )c@4/j)(گ=\cd˟l<ǷR.O_)>WPfhO~B/^ SدrOދrcqR !eΘ湢nKNT^2&N2MΛdLN︟oqg}|c\8;V I0e,1}CylOf~ F= @?pa3 ]Ж^ČOП􅢹k7t^B{j_3м{&4d6@:tfVÿyn0mޱ25͓WHQ}o^XO_g+|^ƁMݷo_˵*'O̿o6ݻwO<ɻZ_?~7*4#ΖLڱ>LK/X]]}=̵:t(|9~GӧmzPN %B q*iu�C<n,D:)'%'|H^:&κpM{{ɝw>Y.Ù i~LR{=1t{= H^J,xH1 x%g,`$ S"'8$mc+}<$c$~v˗/s6$JT>)ey(T})SYkI9@Փ1YD Y?O <~S|5vdCtM3{@ga4)ZhcPFf.&8}hSs0pG}i鼫kP8@x-H q`lsЮ�qXV2:_3jGl&@)D V( c G.Pua4ѿ[HIr<uѭaiĺ?ǞWf`{D�o6a ן[{|X#5©ok|n-!3+V4n "D r#0AFJt be}|�G׿oZv‰³p땷Дƚ{偓?A=k)K" >F?2|ot=_^( 9&HmD^E|_()UF50Ft|[/lFlv-]Ki:�%?'3.$3fβ!ҀG,$FXP(58rM_me3og{ g]p+[u9@dM<F^| =)/2$*B;8]ImO2{kĎ!3WnX.�Y<?3_}Ι), d+k VA* di5a̺}1 t %obJ4'UW '<pqoqA)mԦ)Y,z׮v;fv Śe%P=Y4 ,gBR K{OdLtavO޷.<p`,{<^^3z޵lx)3,{ p%^o;v,zqѣn_bȑ#orrExpuԩRػ ާ'N駟\2%UoEsssC@'8{˨jq9gh׮]̰H駟`gyfPgϞ'ܜ'>6av#=y.Ds+mF#(B0.d Z@^6@(IC%dڹ<묕L)W,R9PVJW&MԐ5 0ޛus}�p Z-eEdɲd)Jbǖ)fK2]SS3?LMe2cWJ3-o,[ )QDQ)R"AKs~9sF.g~oAm.,,pi~*+ +` Ƿ N<9_w:>�0*UbZ@ LHMDI[Z\XWb7Eljos> s1-VWxgB>Oo#;Lo%˲B_W]V ccz={UζYYqyNZq,KccueVˇ$S""<۷n hnhIvl?zu!kx<Ϝ_a c}bB|ho- \c*2CZ~Whxg � (JN grg?o@@:_bjCah3҄ Uv>Pxy^p#h %\[/uixRKX\sx6 m@j?$'PQt/`TJ[XycDA'~O">HZu(U7h2j5 xQ=#czWU*KdԊY $w$y�9VF7ǟk7JC.˲+eGe:@> 1 (/_|ZogWW<ޗLAt&7_F'eht5kqaT,=rzF^noi8T ZoƮʉgB~g-Z1.|v@a;3kFj'LuV\Pȝ h=2w8s^y>vME ~ۘHI!} ^R| R=0~tS 7充�^ΟN{eVaAR څ<nB HڔPwK ֑N cܵ}NY%Wlή3ǐ%PMhNEV )tVW(iÏzqtpg Z]{O7zϹ?~|x{.>@~3|Y\g+y~C7,_=ZU'ⅲ-Z9};k_�gyǹ[X\\СCEi\9q'[駟xy睗%r]옦iyf6Ѿ} K_Ge߾}>}{hϞ=LOOtt:|(F?Se֜s= Ŭn({'? xoޓ'OZ\,J:gϞbB$QD(uo\�co9$]\͍;<.Ͻuj㢕WE[E&?7~>7yXZ\|, (VRhy, [r=׼ 8vElW έÊ`Rp#3ZyLiV/f\Z:x * w\EAD" 19X MiSXp0E}VTXGQxa:>Û|?˝ܡDw+~:=tnQvfǎtzbDO5*txˏ/_˿sܺ,y�}~cNHMͻawRk�C(x+!(CDوyɪ>%9iUDA%l V KK+<yQ.яWDkC\PDf*l7TZ_v4RˬJծ}Еf KOmv;񠬡tJck.]uPVj?/7x�@hFV_Wf TYfIzoux?<X*X ߀?`e k:c4~כo=g|ņ58Ŷb{jgv@Fx^Љ̉Tjn*V0>MsqSxUI{edcG bm( qH0!tJqOya U7YMZv=zS4ƜUr2ƟM<uS乁m D"(cn} w<-'h.鍭a{@eqasVl%DZigg/cgvvks|DZVzcQb!G^p&V}8+_ BE- ez8Lf�kW4: as8}_�net >:+O̴_ Sp8tVS 2baKە5j.l阸̃J7& LIחe6W:P-P]gf8;s5˸480&&?z}{mN?{=G P8YwS^ 0H+ѕ #oV';7uy>;v]xe8qb y;yES￟'Nt*ލN+’.fL9R~-]ѣG P瞫xpNOOs@堷m|/@zȑ#ku؜<x1GŮjZg>>pƺD }/PE{-B#)R)AG6*6P m ٿ?現<xCi,^I  rۜ6EiqqcST)zpmUa8:< b-N|Jr+�,41>`I~V@ćUR <acάL|>,p!uwJ]c2\QIϊx/z<Շu۶pXkn[9xͿ{9Gף:ZuShT./}SGO;oﻛqgx>Gt ڇl{φa.v,|j?R!" HK#߉raGO+<s!,EZ@}/0ǷOAy3  WWy{7pugfqz&�� �IDAT7G,ִDW~iPfAt{./p./9 B @h;|CؕQ[W6]r]bm$mι"!7Z(>m=' G EF4!e= �_{gzkRj5޽:R)P7xrC<Z1�QR``T74#H3 #i1:4p[A:9?Z#xlV<d4):|mQsS|x.%PsIŨ:-Q[Ψ}}]ʔ.l)ځZ?C形N) *v9V8B^2"&( z_*K޴w'˩;~ oY`4mdXYY!c. DW@Lk/m>ve 9><'`+2Uޤܛ|z@٤\ ~^΁uG)CjZx2/}�󮻸S<`1bWz79][گSTfić>w;eFs|b⇧5 t4meC$>`NؾmڹYNt{8Q7nw΃(_svcoe:{邱{w߲W3ţg9oW=>G!!GymkpғG^|`O9e廯G$;c+X^gfdqof߾r~tM>|s֭!w7Bssss=�ڵk67;v�\po>n馋?SS͹7:)pws!=J-ڷk.8P [Q^ZJoCO>$N*_}ݻ={k.8c|sLFf.[okvdc7tN>MꫯfǎjX M%IA8BeaV&IPlnPK<s>G$lAC DYAgPlJ?Uȏ+TRU�%.(D52Dw%NX89u^GR <T(L+OZyU]QSDED0,(r;`Pt=; 7("@uk!W<Gk͖-3Gcd΁ng+9ï wgzݜ3ұ9H] 8£~'}|r+yߎ˜'? _{:>EP+TE4hQxVƮA87" + S"A�\f-΁QR!%[ J@1FcBޭ.,7c>EkV{y'qշ"䀆8NY:)S/k-z hEP/P.y6K@4bmoֲ_lZ,$QqQ4|C}HV -<9Oa.*,- eJUmRuwk p\}c)YPa$ >`/45vz)Pݻj36 -c 8kqccڼ HO:#YQz +N�#9E<u07KA,Fk ^Yh&),Z[m#dKUY}-5 0ҡd)Hix%h\Qjk|JUyd)*/8<ȱ9J D{,�d"׌`�WjYo\u)Sgx4߳^?Ya2 RQIJl�jJ8k%͠cFQo01B$o)~@f(8 C0Xlkk~Ljp~{frCLIђ=vV_tMȍҿOIhz>3}s|ؕ)+:!2P,d~mNUV=k7u 6LPA[![w@k&�}׿O~3 m u>.XȆ8#.ZMj/!?<Tx~÷\PХǏk80KtBЄMoFDhJq8ޛ"*G�Sҗ dT)@7+BBku9LZd`mNš3`5EKkˢѭ z\< z&H"b4d4v|YrC"z*ý *UzϘhژ̐^8Ng!=@눀,]V^as`T1ːEU zr I*ׇR={=?(.@v8n&*T|/>ÅE0hTN0ZO~ޣja&G17fy[s끛L /wOa~!qzy9Kz@Vl դHǽF;x?!E V[+B1ho/aN+Zb[\hj0Jo) [z::MquM"9Ɯ% ǵdBk7K{)mta0a,N" =e+Mc.-mLyQՍYPq_bX5s_~GzOb-A(@V 8P\ŠB\䕭x`&"`]UxaxGPd60KQ* G b)�Ra}?i#2%p+�¨HO8Ǝ݊fໍ1sJk͋w\ᙬF@5TǶ|"{ʃ Ѣ˹a<% **NSxtOWI_ TgC\kp /uӠ`vm:D ry^((c}+@s}G}ud֢N[bȭ!;zjؘ_?SԔ>|a%T ~X_UvܿHv/*pӺv~6"U@6}^rz�9к彽C*+J#l{plۖiYZ\dyeh0J VTu7 !M/˲"duԍ?&K?vɧ8piV;~\/�+9s$vq 4خVB"l8\0/ȁ )Ň6n [>jPrh�⯞~3A>ۗYhW !usjA7vEz-z7}d%p> }ӧ_W^LhBХV"pDODfX0(W ^#@3Z< Sm8ud8B<៴xzXcC@A?�^> ?FgV6!FD}.L=98OVeй�:_q \uU7ܵ2Cϱ೨JTP_]!&=;;.J-yiﺗv�pm>1\X^罐,s k(z,_Ï}vjagN†ɮ;Lٕ,M6N/M""U>Hg5FaCLs@nNyh%b_Z(m\tx$*8UXbģǟ`v 1:hmPu}S]Rz6@ KU {eܸ93sJ2v@ MҦ1 RѶt $S%S>UzD#AWZbIxuaJ<M j贬�վ f7V(qqz#oAƗ)+@L+# z?+qܚXOֶQ�^;3iA9: sb:\-'jهe/- <w@J(~/ hq8dHHoPؐ0ӆy9Y|9ɥ::0~"v֬[GMk+ֺL1u 1NҖXw޵rYY%!E+h|Y]Ah.`z y8T;c۶yz.Kˈzԛ㬯Z365 o͇Ǔ'RNi:%0au[ ,qgf8akj=hU磕\ 8 m;B{1KK0 -sܶ_x;=K]ӂ,wl>>pC^.z>x5 ;< ԩq7u֑ax'4 M{Q%€%14q-OtxoZjͩ�Sf.݊8 <RA\NjxUĔQ|eΈ"Bኵa}5g=/&U4AF ⃛Ơ"X|,X=�)9 dcXܮ &*1T$(s5 5mjEQ+MH0Z[Y+֭(r2`^u$SӇaAÃwV_J㋟ +Е HM+[k9‹/[ 8-e0_@Ekjt\R�iFj»|(o/2s+{1ݜG? shg\61PR骖 (~Ï$ {+kpX=&^γ_`qivgEMd'tEIA k8P2` f ]6mz=I]=%�>jGW xP <Ï t]7;ltʳemNwm=ʐ�ᑺ4^փ X1u79innjemY}F> Vt ~\U\o([:ަtlHׂփs"0VoBt}XSZ-N`U4*Ɩ%*YG Ib(|QuPi 8]ކ:Ox c_1b+EY X/x3 F�XY Ìw a̅BbS/%K6޾X<%]XSMZa=.hXt?_!՘(;$]*�-ncnn s,-/1q %sٜr SQ;ju&q|]Z4tsY d^j>0Ӑ˻o%}8rۺ V!MZ0==^Ar9j矝ȕtп\82^1>LO7 r|~')9:p#SFK~7^s1 m+݄ MhBC$Wt9)!GkGP͇SeH=Ug"( A3>׫ܔsoj1m-VYec!EʼlaiJLecIAw^e^έq>uL |>sP.0q\+x1<qE~`yc=h /W@uTo?rEn=9g!?3= cf!s ;Pzi?o9w<|yPW :b^8,yoy U?L޷TF(^ Yci@5F$J^,}6q2̟k(w謣js5¬y ZeLUJZ)cB??7 +7uiyS҈RS<cm`%U0\ \_tI +^)*\oSb\M땦+S9}Z6ml(�dsU*(>-j?![RrnpF[PX);LETU:�زuuQ(esz׋_3gg֤PFڮ> <Z3k#PSS5,?$^0ԯ,)\ Y9+|J�YI`pM^̿!3*`lxQsb"b=pa|s2�ꨀؑfDY"w@ 2򎓸M +:Rg r%ؐjs2Fa'kU {~iwTIZmXڨ1+ Etbz6s֐c;t3 ҢbgȘ ۊejzu.cuy{cu] t( nNO KE{EUazȵ[{[/Sq.884f&s8 3[ V`mhu`Fs\pt;`,? qpoyf ,9K+8MxW}bFAMhBЄ&4E1͔2գ5" yf[4 �Wf$<xECFW<�QBru-Srp%+<SpB69[s< QXK"ysփPA~Kn-܇7jA}ýjȰ"Nx! ƒx 9)|c+A�E1]+!x00 |Hb qJw�VJ*Q WJJ@:F9(UTԛ&*c 7_`L_a%Qvr>?ʩx٣�RFiWωl۾om!Vkyƴ1)L? H�tU< e:غ \T2"q+d6iDK : Zj1mV.s ne>//Um\+t o.Q8`Daą0cK֎ gO3򖷼V{+*}l{ Nz4\EQ:@%V1PbRRUAwe2MB6qr J�.ԣ3ȋu1.D$T;pʇEՎBEu/q+o$ )!1jP I<[mM ?Yog҉wo6YT@WÛ(M /` CMI 'mB:I1PxO~ Qꊵi�CnYu\\].!7NV1R>] PGIAO'>}\dg9<ÇN-#8)YhčWKb? BØ7ZI{6ΛxϸqvsJj}4E }McMc<nek v S u r;C^{7|Ghq`E<5VOAcnTǑqQ7o8eɺ0𕯴E*NW DhX֜ YC^]Q},߰g)S`3>U;Ngu$*:2+r1\8Tuʼ68qy} ӡMgw?I$~0ki J~c뤗 o|%_7| }.zkbvbW]W܅< ZUtN0;=rnۃ @4t}h;!г^Ax6K}oV)Xip{ͫ.= MhBЄ^U  ]b=D/-A$GG%*#Rb&*$aa,czf /XU╕]V2:q�� �IDATg AkXBbPl<p6S(5::&2C+kcի~u`22V+q9EkM#~gV"^Z*b2Pa!Sy� l)BA^ajK]`V0Ȉ@{J`_H)]R<~GOp作,ہ^siÖFAo <Q? gϜ|a<p]]ɓY]uV8z$2En(ykѦ( C8՞HE *]Ά ѳWIro(C"70t"~/=²k4�H- k^0QWXEQNe.qiem�<(ZW :+ڐol E!!gJ8#eƫJtiT_47qHJ), ʪ{~xCRYW<6QmZ>#KlPuK6d )mG|0ot5)GO^*.IGƸZT=v<aۛo}?gRRm-<BU2v"B0"Cº[(?YBr8z$8>u' cQ"U/8Ƶg%P=WU@%Ot(#thP[FԕiKŽb0 ԀTҎx&cSjGa Rb >b=D;kŲ TchEBTKM{AiϯI! 7VӺ0+(d*uP0Տu|6VTgY#s!S0I눭Ua cܳbZ' -}ep t:?6/Pdvo_XW̷b ٺn_CK9V)8ݷm7\~<Ƹ<p*خ.+ʇ<6\f~6f[Ouʛ?Wn&4 MhBˆ2 C+XsEF8azo�jREN*R9$A8VZTgtƭ7&_\wqxqbPCF`�, 4VyAOqE|Se@|(HB;z@Jc@p%iWDGڠG*qEЈ(fWdx ͂28QBg.yDV/\@֊Ocұ9nb7pZ!WwOj7f[`g9u< 1�mWAH3~3.zT9wڠJ#pbY+CcљW;;LWUx.O+C1ٹ(>O;_�N;LMgl6Νlj) .tYXXeaan_Wefk="Ӈ9~;mnCfN{UX. PhJƂjh9Al!:u/I#ri9JIkw_˾ײYڽ;yp$eFX(yT)_j``牏|sI՚66c)8ڥ3}ҒZ_rx\N&�f, n8(!Q4+8MU^ }G[)驽hT ʨ5ѣ>y@TFړн5SGX凘2 p 7<<@ @8z";1U|g:)/KzJ9)_Z�zwZnd襨?@ 6DI#\ �fko֦a-9>*6~Z(Kpb )FAr7NC׸Zg,~ h|gm<kijZߍhyZb ߪ^eEFb?oiFZ�,8Nb{E@|xvaPڮt5|fx-9[׻f7J/K@6mOO={O\_:v`YV{� 4]\G9sVa�e .(ٵ~eƇ"B_9"sK}+?7\њЄ&4 MhB/7j# zDDl QT*3@QQX*a|(<TP8NPۮ]s'z]|m,=ϧ3-h-+`-�yr-փXr!K^ѲZrb9+㯴^`sWxv]Ru"[ " <P(\XC`TBXMX�e2jgMO}f,h g6\"QBOP|[8|0�3[~ޅa1$2"`;е̖,l__da_܃,zaO}+AW9蓜~)2EcF`_.?lЫ mZ16aryһP*k>A@VR*DZn=p `ϞݾX%@6]8r(G};³s\XZ*֡cx_^g\-_.(;Kv ]w9}F4A@lψ_HtATiW6CTodm[O}ѵև#h"Vҕ* ^qb)B[1^1׷񭳞ĤGp ujњc@?Hm*S&!DamԦ;,רԏܛk`aQj(٤z Eh] |ڧ#[*SJst|Y"G5i![kxɧ8s,q:"oyJdzG*4KƇVJymH˂* .-\y;6^&>ѰBئ2g  6gN<zo:D޳O 05cX rEz'TcJ[s\Cu]:RY-'.} 뿏Q80nz ZnzL7VP3/#qLQwѴP`C4b OWj(B|-6Hj{JYzHj?j?O;_>yۜ <׃ԔTc!|R\[@ u>_lTuгoBV%@Cyo3͇z?λy]LhBЄ&4- +^I$x4hCT,!T,?�,3LOM1YP`hAy=^iJb("T�]9 P >@ yN+5}81ss!'JpZ?Ax/^%TgŬ.PdխɛOB T3MU9G~ic ]so쉯2gvc>ɮ-7-_f+_|Ր'7C` 3)ɉał{oQb@Faq.￙ìtjinAE4D; BL@q4rw&~~4.G*=]hAܙs<ǾwraiNo='q/ZWo ~ Lπ=f ķA%r(پhCUWf桕[%GFnGVyCv\"zVoHg9Dǎ!J3C WX" ~9_>$>$}=weZQZy7fQ0FO='@{.FZ #EOyYUUː0144*rk.RZg rQŪ,3miꉗzj"Gl#TRv /9.ڤiB E+Q[s9Ν98g0�OQ]J|Gq;|?j.sΡ(gзYRhw�>ӻ4!Vq\7b"w8JAOY糲rz�&Z隩J&( 2#FKKFIمa͠xw w-s>W42Vf!ySk @.\W@/ I�ǧFW7&lB^O>!zyۏlC/7l<g;=α(*Y},[5-hoxk_<bq.~?p;; MhBЄhJ[D$o�^Z6*FQA"Ǥx%6Qp>x t9s,*CJ[qsQE91-{P͡ Ϧus wb iF.¯:gQDO9ZJ,9"6)߶T^ $(r.&AL!BtW˭)|>Zra]ZVՃ*l+aBXu~0GyWݶ0`y[Zw}s3QV>͌F)o/؋y* -PJ}CMqV:SdzӔ^kxźIce;)݋sWU(>у$ C=JM(Z={vwOO̒+APZ=}mڙ +-(v\u?#G䝜=wO}CsG8QΟ=m;q2r{S詫T/F^/Y3`ݣ z5xPVybӟm ZMF F4#;rCLR16=x[G Ra2)?ÔAP<dj5:4 Rp˖9X^^Da% ֪)WǢaE-4 {cD^E[JGA$:xZ4H"Jta4[\m*: <o\w\7G*box�ywώH\Rc4"ywˉ'8qD&Pdt3oj4Tܹ1Ŝ.bU%XDc翕BgPF2(31LkZ`("hUU-Ն*T/)y J EқIyJoG@$�aii# k-<1yވh 4J.滛jhG K 1*�d(=>T.gơ oY%׈~w hܳ.gj 劜/kQW)*${)</VU62̠8jeb ӀVa.� {V>7᷾~<1;v}S SӠZb2!CHgP x~!ͻ;xӾ]|7q˞]WЄ&4 MhBt=;ҊAY`+<bW* 9 Kٶs*ijAOweIJ6Z誐$:ie8g~&PPtD4FAa $Nz,#ϽUT` !>$ŁU(BxsPi_P),!ܱuD4<X]p Sx r^x>tigo<HE(ئɶ<LxӜ9}u[B;AEkEwQ׷X)CFJ (FcX >X(q~:Sy衯t,кRSe^SRЅ&;,YК-|ݫ͘i^IKN*,[Au9T6a;ҙ5 48ر{?3'}|׾=wݹ4";Pڷ_>E\>R@VVa?-A ]`(wPi 1m$iD7<.5qkYChjz6iN7O=uX*Ѡ�X씀2cbo@"8k-tM|ۏsC8kF]bҴ�iyE9R<*q=sXǾ<ϫ!%lc2lA!l-:f8H.[1 `!_SFynJ7M`r-m zXRU�"(Nɓƒr"霠k[ ?@(tUPޗVEķ@ъ-- @D?:! S2^*6&5e OCkKNPV2H "i#F%BF-Oy-&Hm\v)@ԋթ[7ˈXnq϶Q@~ݘcX18ۺaろPiPۇ <Cu63:O):�ƪ^T=اK4@v v<|""<v < K<q,Orn+tsK_Fmj'̶l3m3Sܺsڽ믚]W;hvC&4 MhBЄ.3mn $U4N+Dl)hkA;�XW,.kQ^#>T/BUiqAD0V]9ǔtǂC_NTdB(tbBʶS2 C!NJ*q9W LjbV\DSpz(8T #rmzͷ=C:q>t-k"hK (z� ƨ>_3mj/|+ V_n tpn)5TO?;.,,!W0JA~Zr+֏Q!-وÍ)ʱr7:<"F0f~`a4e$Jyx[:7Fi[PzX`a{:eZ[@L= 0̡[w]|Nu@dd'u+V牾\7|k kFW{ m\6 t)|:ۊ n(u{zZ+me?+*�~Eos*}07!Aă5*,F_(4Ƨ@MpEA| 'QFiP<书 Mzp46]UHa 碼B tbVx\%}a<T M9'8m <+ѸJ_ɨr+$e4ht$vޞM݃+ JUyCwriLRy]'�QK>F&;Y;Vi֭}5ss[v8 K]q.Mx_1I*c8g\Yz �`kw\x_ p"vԴb[G ȸ5^#̤j;KnsD 3X.5([\U o>jl~+WǠ VCfc /Q:Tϕ0.%)3n%aIe`N�uR{vqp: MhBЄ^477UlЈ$.fna% ,shޒ=@_ >F1hnb(2wΣ'>lJ.H)`R[<(<Gw{!԰wTB9ˎ#V%*!ߺlR aVZ j"!*ų;{X畖Jʰı mRC9|?e{ `�moF:_ Ss`ݫ jg36>ʹs+w= X%%7RD:XJI~l08<fх9~u 5c7U߱s+};?(rhhSS'/#x^I{@f=@v|3�� �IDATki} l{3Vso{q"bYad;zqSFxD$ A*JXX=3lE5/*B ; zi]O3r@ Hg(4FEqԿ{uGJaCMg͟eˮdT^* J$H-m 44MqhDnc M3Z B !Rիzxs֞ɓyw߫‘Ey=kZkm?DA<4ݎ�mx *0QE?}m׌\EϨD5 {֫AA!+i*Jb ' 8N\ lj"kx$F0� 6-A,1.;/r{Ps)6D}Rm掍'\]5j^B. aqtWIt?$!2Nc9ڦa>x|&Obwg>Qa-Է3�\δ6t<&3%sr}s&x ڹυQ{d"(tō<ث4h6Kk۸fo/y2E, [\hc̾ dxLY7S-lx)קe )pۺ זb=?Q3OJ5iMkZӚִ5 R3k liU`2aڀpRt&Ey9 `hk Bjg%3|P.v#s5j>(R|YwEgm`ZڍB7!M4u%#9lԔ]{1։3>XYd3%r!: o=4u-߸~�1n([ػ6Su@ZaW=1TOQ9yW.^ Y�}A[=a5)Ѻ Z!iD:BEihzofs]rϝ[#?Z=mZP0.;pve.jE*.{D{7y4$'@7EG#kRН/ޱt@Miބ%a25kFE L{9ВJU{tpp6Os@tKOK!CI؏ʠ#}-vv}^WC]DY�Ǎ_,MKK[3׮09Bg!(jC|{OT+ !*բv_ ֟J>s8ݹ6Z0j� EqGq] rטnOzoGs=$-i3f.]A �m]-Y> ]l?8Sk 2QΑΞg?^YORՎ3& nW?Q[%>[4V23(>|k*iž-aKeN}YO5g`vYl&+<HGYj#d+hɷ,QS,y-ɇhOM2Lx|z >ʇ?wX!ɪ|{'USb^D#ʍ>^yyzŝfZn%uE{zzνw;)X J/gPI[Cɏրִ5iMkZӚW<,\}G+ţey_h69bȭlom#7=ҥKm p@5LsTxh@\kᏩPs.fйg>{Z*Wxڶ J"^KKqlc `xsPW ]X*IY7r՜PQ~V1}uIg2&o_ wgҳx]F (Wnn9cY_|C< j/ % 6~̫7G_ÁzfУ%> 4xFT76*B mTʋw.'Nl=E5VvZo^ iCyx3|w|-�8*@uٹs y&("?>tdK ~ٵ7R9<Tgyχq>jgՏVɧ& p`^;[̿. �\e'֣`ΪtT`e%}c+{Ip`/-$L8, -} =\>$n�D#KַɃO#�"+ڦ;ꃫ*c療_6R5M{aJhl5|@>Ga$ +v @,ߍ{y%)/w@�W{m8Bts"MZ0~b}0r>87UMިEpl�\Vq4CU#hWOf rP<y$N5 0ɼq)<C`eQe|\:jty<GIw2W# @Uw\^w<{{_sH<1�VhTs z[[s/+*\۸pK�9⺪$IZ{+±#yF]#{NylS|&_+ˤS> h�d{WÌ~â#bQJ5 5iMkZӚִA~6f ZR*8WZMI^4 F[-԰H8üݝ]~~ /D<w*Eh3^՜j*h[m9G:%-DEB^>^ϫXE]Z*K{O3U(Uٺ568Cqoj�LnkeLҝ)b(aqZ@S'XOփM .Im iM YT4)5-BQV|S/ou~ײq9{ld;p;1~?;ϱ})&^;fʄׯ~\z)[SR4_e"8{{P PR(:MZ κA88-J,hpz4_~)G x}W_/~ F@mWaut5Tb@>Hsll7`Z*E2ju6_L&d"t2Q0g4X|�pIczW5y6p".lo`aM"1zW` OZLeɽPʕ,:`Zs䰫=6y <frSJ_1]ߖGi4JX oG&iA<U]/QtwIpXʫyVZAKK['NHf6W-*F7/eunn/%{# �O\z&n2k1VpcYWKWGrx+$ {Qcd H|4$DI\ Mo ! ӟƿ$iHuI3" PǂkKі"WPo' F~Y9\Ua0}$8n}(x<\59&QC[Ox,' Ww6g:C=�͝n �xxxVui ٮƜKm'NHk.�.݀гq<;@ۆҫ,<p ay5iy{f<S¢/G-O:C5;ј%h(}/ѿ…4m3RCBvWUe.I15+eg#\ ^~+QX_c o~ծW{x^ 6iylo03N<S}67o}dlv {U[Wλw]Ӛִ5iMkZ MBEh4lueV-h h*BW" zTA۶ǣ !6RpuIR g6 C|`#ly.^Q-E)&HkJ Iw~! hBy�ýgKRh i7K!/@B% 9i�x:ۏV̠Rѕ)ѻ#ݷZ-wvw?�:sӊz͵WM`m,S pn̦TNsCwn\d/1zvϬ(7_=-Vw(ڱOIuӧO uO=`:rj <6/{G˳ 3)Zxs 3pn[w8a:k` LǀxtQVJ7U\(TarΛ+`�SetP%EZIh+Ǥ9K1S*˯ᐫc(1RpzK МQqlMTp#n?cq_ p+p`r  KQp[@VVr$DajY2]܄X>6b!׀]`at*25uC|DH,ʹw|BLo8Є$C쌀j�F Hϛ%0 B~ 4r!׺:4~%y0K$dxu!|} &MZwVH.Uo-Jhjސ22~y.�aˇƟt4E_&<9#7Eؑ<cu1xp@%^Φ\tO~F'Ϝdw>K/`ǂyIF D"P3�mGb*/ir y|W$s̍W[;;U2O5JOB yƿ;s?o//b9nhs k:.\$:˟?wUR/et3z%`%c sI.d>BYf{a͙YE3^<LhϻvsgUZkZӚִ5iMkie@lJTжj 3Y/ڵ9*Wa^z%w0͸xjѹgPE(ٳP/A4P{CXdggq 0D #oP ζ2/^ -`SPxC$h| ˜Wpf.pQN*[FmK@i kewo RySqO)CSx9ʼn6S<荷gEH =/'Y ;wݝ]weabhV`kҒ/W"Yq\nC#6DUt<O˿KS.W L[BT; '!Pw:u7O187h}.cN-N6S { v{|0H`.pkP x@6!,wT3*NL`  @T y~%`q&ЎC_Ϡ;{[G%yZ8> cϝS@B΀N|]6-!`hꠝB{ƧG1PHtmHO~(SUMD`Lm ^#SW5*1ha3>*9c o **k=zkO/W^ר,#ES ϕ%/zfh uCA>~~7'g Zd�FUCzc:\4:-\w�GƷ d= u[#^ W�CfWQl޷hQZ 8ƥMx/Z*ލ?kWfM]H4!2wMQZF}0a[}�/y^zxq-{bԫ1q3y& sݥ9λC嚰*XaBцG~^VІC˩|X\Cy({nOCowcCC0E1>l ļõk׸yM3 AX\Bod״5iMkZӚn8& |SZo襍vW* P8qA &UU {{|cK"UK F}Gǂ1 *8+ic<bBJNFEL&]* Dm}K5�J5CCH,[e(y82ϚV#_&|dy=/[O*H,g/-RF% 7o=> ;^X@/az.fYݺ ?˩~Os{}/^!.+GB1_::M"0QFj>p !gS\i%cgļ(ox'~Z\@nYOҿc}@¶d;FjW@%Q_WB7`F/=d"`0p g`vFUqQ[ E;=p pBzS F\0I0x0-�{\o65K}@j7}gW!kq㸼KA7R0:0CZĈQ܆k0-BUXeha;b>O\;FZaP-=Pf3N<c=FUU ܺu){j Ns{_xĎmΨoΠK7bl|2=uħލVX(Yi|K-A;d NNCLP_"_&z#,P`Ha -`/ =g]6g ުe8ӅgT7@U DPU !LwQn[i26kXTeّ!ŷ*8Uv]8vk >Xh*4i} c[f:l7.آʞe.S}wGK;.&f)=�pF==@9a rp�̘85N6/C\_9ˢ$ac:uVsEpnߺt6K<Jl>lNE=^ƷkZӚִ5iMkzW#Ԇ8Gh=^%!̭tqN\H@@hSTUgbp؇�̉|9Xb4pIƓ ZibCD ոF!YeU,V6| Px q plhݢ7L"]w&Z,sCE) kPD~Z(9SU:ݳ}w0rQ|Q Xh \y(NR)η<G;<c@'4Zw]sڣBa#s 9,*۹Om <9:s홟ck!5Yr,mͻdb�ޤ0o80-<$o{!G f_?9na{ {` `Vxվ0@ SY؁'M3x*ϞUPA`klG:>%p yfj:8/eX K!ڙ}FS8D(u>*h}`P E塝i DOB�@z#Ξ>SlLFppQY蒙 h-<J=qI& hG]ny#f5O{<߹2C}ۣԣm&T$'t.[7rҸp΅.\=ςrp!wQ �Z񞺮xyܠi7JJCv~k^ FlҢWmi 4P=Xk0>FA} i{W&"U L4Bס[gzә./2tpw,,_iVTY0V˥7o5NxީԶ>x{X4wm+ۊ�� �IDATrZ}=?}e?=Tq )b ɳ$L?}{ObDCIDΈ"ao6MڦɆ8DLꗅw_kZӚִ5iMkI]UIQ 4ҰT=åxN�"]p]j,tX6r%jW�yY3ewo> svJ!1,X>9JPy;j_�La慔z5oadNm80Ob)g9pTARiGR[iPMaʹ PBX%'Z 03( =cS=[ x_|?x{A=&:6~5ZչB%5a{wO ׭ӌ$__~Wq4#l1ip(<Z+(+Z`V0rnn/{-_8 7Uh?_fzy=6d"qxm̞gW ݷsp ce̠8jGt剧`K*l`EiQ�Uíg_ L9`ٴQ;;[V//5Id9 `l*7tRk p0x.BTW:Dr'e׿O3MpEB<OcV.a {y=*y<ܧ#$εMaU9W._+pwQ7 0]=bc<m`k!0cVRt[SH@(j:;�\}�KK>/:u*VIΗ ޱ=H,u לY\҉v&)5IU;j̦3:%1O`pb{\ s*UbUdEwu(*ڽmٷfmJkZt6R=x}˞wvQQOv,_P S/3 SݼV1&YV7P򾡉-KT| 1rxƣڶ k:c>`hͥzby95ɑ2f&[ar䱕zGޱIO(oswoH&>"H+Α,͔u H!״5iMkZӚ4H x*XƼzmx1>9* £vQx4{t:4%-D/Lf?in<n4xҶx൥if׬}k,R Y ۚP.C"ښ�85꣞-+zu E{BK; ښx,Z!)^O! EFi~.ٿ^'1BLh톔> c,s;t:�'xaiݿ ʐ%.mlcܸqws.*UQ|T@_ dCc`0MeNsO�E*O m@md~㊤=xO f";5Uz̞=Nu~M9 w\WŤO"W.*^L4_G� sJAox+ h<ٞ >%ߪ\=`8c̮HI/ Rh oEʿ:j_: @=^7Ers=2?ٱh<?¼REj[[űW3\sl[02iqNg\vNc,N%{2ϧa%" |fIgLj)-xXfoМCӅ[P<$һOg"֞7hC\WBO!"r t/ӫa x/t#t "멐sb Sl"=_޿?̖Eq K#JV8I1>52 񏚯w@Ϸ> [ (ez+g<@y) J ncX<<yWvm<h8YsF!Jw'QjۏtɈW=*f_b֘l {G=Af2FY4ʿC<*@'en:eXolJ1t~3 $sb+M3j\]ѴJ,ۘw#CE*!ɐD}cĵ2ף ߽^{ȮiMkZӚִ5il`f6KLd鼘 0WU;,dBUI1)3vN7C Kv hRZLg:Kx04SG`+U[D}4 ~tB&K�5MSjv[6k�RuYk{CVrޞ!Qғ 7ןOU[fAs?N ~0y>ʽpũj[~qOOE_lmo5s.#8 z8iTU'EeS$R_' < hvwNH,zzp'%0"fxn-Ywt UQď2<vB3ofQ d=B>6z4(k-�*L?Ǽ�/\#< oikOYxIEāX0oMQ*w.G{`lOrǛk ei:c0%5|!L[9A~\<xq Eμ[rk[ 33hZ <DRvK*^S_x͵Zz2˽ Wj)M3p{_\%ij.~`{|5N9}I NZ"(-y 8GBbrU"hz}xJ?@Kq'Ӭ�4z 9UxL+ƒtKϒz>wivۨh -mfMC64mKZyxdR062W}k Wߎ;<7KxtMw:($`}˘{/Y`ڢr/YT{iucƅ{FO`kkbYiE`i&lւJ0 w<fGġ^us}8U YGu\[憶>n>,5&l0k+fDq]x.HYѨr/>~y3DG˫CvMkZӚִ5iM[{,/Y05/РJ̯n#(rkgĚA<ވ!Ix&枏޵VVC_g UQiGK+eԢbQ)8Ў9 J1]*.bݢ2'v2IzP)/vP01$:7 j; &.ۻϧ>q'z֍ق7#LHN/3Ng_{lGȏ/\Cp^vR䴾Z>]Oj'Jܼ:ˎ,W?w|1Z^uS-Ϟ16N&+)WN5yO�P]vf-AYi/Y`;8ZN9\= P5GC]cmZd{7k~\op~?wwBl4vd1\{d0Xeh15$m8 ^^ZTBPN`;xƨ<|M}.eK˪ K0ׁb 8&?y00A}>c|߄p"Aш ! ĝoI]z̳5j?E+Sa.:>;448B"4B<x|*'Q ..| Ĥɇ9 !kYXBb¼i>W^}ug$gXl+!؛lT% V\Y9߼GiQzJ.h]Em=|jH90e-rDz<毳X3x0U꼚J?B;k*yg딿Om*sjax<ezQN֝ڳj{]U-Qw^hՁvH2e>Ccz^,u}ӹq|2kZ'퓧~/]b6"#wt{![K, ]Ӛִ5iMkZ yvCFl|Ȉ S6YWU 4Kqd $(ted-=1jVRE~ aPTQv ·mp0oV}82&`,EqP^Z - Џ w(޼tn'~^X+C }5#jnWR(tw-il<*?S? n­ Nn|>}/kf`zoPoxx^7}[x/^F%ZUD3v.ͱ[g=j*DkgV TbדXb3?3yӞ/­#{glyH[Y7̓^Z3^|pˆ W^8H%/ n9UaA"׃0w[P0rAN0]pP=<`M'do '2, xt ~7=lob![,4VJĕt^2;/Y'Jͺ+=#`O~,[y4dpy)iŰFw^ w'5.Tm*z<GkޗSÀ2G dF&#/&!9B�\# u4=4.ZAb~hQS΢̫(`ce,ky>L^}9 #F@P\ 2i;`hg#${ZEExhlb s 3J`pq[�c}!�>9\ Ey]�K;4]*-.n9y\RIP Gm�-SLX"1IJGQ2D2($ն$mA?/Z';K.vql~%EXy2/ud%@Բrx:3}jQHO{]HtmovDH=cيD:cyeTꊦi8qb|ׯs…e9}9ÞwE 2XfȀ+1y[2j~qy_cz_O'PmiTmLs?P־goYրִ5iMkZӚk7)¤̙"Dtx4NEJ)R7zQv_XȟZQ 쉒+Vne T8*2c$uEa<}}ҏsPLPPe.Cզ[#瑕+m&[rS ~P`$ցhL2fJOIXH!H/EVW\L;g.kƫ0~[Lۼ/)g;1e;QL�_>LŘ zwp ;TZ 5Ǧ|~ J k?愧~-7> b@ш/}9oП.[&yUX�נ \' #<{M`Jҧ݂66v827n2l=tBrl#Mxm_ < gJ1WI10e' _T \":6[ ;]݁9p6L6GTQ$ii&elF{]iG"L8x' š޵ےėw@-͔^PUh[\�r=. Xh\l*)h' V#냖*xjkT >?yE1^Y-5I0Jg-=a!rHC"X�oVF.U?ZwC8/B0oK44<Q]4d.[ lCSGnm^ 1RW6Uw@pXJ[q\ Mw|-saiW㢟R OwC mJ�_Kعʨy(`]8c~}^o=g!Μ =.\Lٶ|a]i!C�f|yN*Z|+TFlnnrYvo{66O[pPKj`kl8h\y{t5 5iMkZӚִAr(.014V 6@ndILW1+lq`ŏ4\qPo G;4XzofRzI*9Y+l:GS pUIt4+j U##TX]7ߚgJs-DT W=8-MLILK~ag◀oh> i$twJ̽"zS ?}mm͓_ŭ c^ EV7};a&vOq ~7[;Rsu^@A\ZD*9^5ojQ5<gE*dR<{׿uT7՛⤃Q]RRԏ0q #Oo9a%*뜉מj4y吆]΂<{2)1uuNC8p ).P2`+O0ożcl x`6sΑBv>r_h걕ۡJkN߅)66j=.:T= O4) {0vCi `'/Txx >  D<MH-̤wYs43+4U6SwhL^'zO]Um6u~zVČb<FpR#NihP<N u55Opb`QKԆ_ҺXi@VҍG@%.\npҺLcA%ɥ4Wŀ]!z&O墌%(>x7ug&e^yX0#sя{|a7Q:> h30)AuH EFhʣ"E ^NNkmΐu=՘o,KCiwC/^ C4.%&&)E%$|`lq"Yr@!S{_7HyuD"e˃> hG[ίhQ]�Ƶa_l-Aӻ1Y~wքw"|+݄':,QEZUQk~샅pi%=pְ Ҟ^j/e\;xuft2y\}?/]C% U% {W}8#54J<γ@^]Ӛִ5iMkZ PqCj:5b�Y!J7--νJ (1oM_UR1QПJH�lIPb P*v'.>;Zoi�,N}mTxBӉШT uBI٦~2@"Y2Y*nYY~}|w_W=b6g`5xty3wl[P~O{}k,mbbT*W C'ʹQR(cúT*>oy-G�ٿg3& VZSII ,*w=U`n+Jgz$;Kw,msuej[ ;59z !t 5oعbsdL Pv!lqzC8siGFb, Wn 7F=8w}j;rӐpp0?:W63 ܲ�� �IDAT .tւ:lkXliJ)64R;d،ײ2ŌGOgpV쌿(󠴭9 ^&oݣ xRŹ:O:2aDzv;ì @sFvwvxއNcΔ=SF|w+bj<SLPkN]|[ ,Svp%m`]6fs!y%⁇<+dH&]bBe~Z�JӗޡC|Qz.,u/b�$"8R_!'EEJ3q`ދ*}?H�ʔ'RZ%]]NV|WTAc}k Dop?WYiS_Z}QkC5N}0�0\KZt-iN7B>y)λ@@d5g}UPvs]2U1U7{ /xcq93zǍ7q&氳( J[ 4-aeKCB/!^_suh 4J�ɹ7o^?~јݝ={;8  w4ePe9‌PQ% Md~ܫ"Қִ5iMkZӚ^.عO(K֣1>;~;XExytd!ZڹV(4SJVRZ -=mۦ"DNHf3 p{)ML;; pJNJ:<ɪZE%V<#΄k똽m>vwqѧ-x9 Ȁ>g:4L4N[-0~.1mMx<:"eQ٣nc#vxE\n3cVk--J[ 331 >='jc,JPo#a3xi9 )Pt-4? 'Aw+�El&+)؅pܞxz߇BXa MĞޡ ƫax} xI*wGЗm A[o/hj˾j#0 =zG[Fv֧Yp@/XY^,Û~c||J<7GEΦs1Bhr/F`ui>ȹs=(["¬mOM ||1Ny; H{4e43 [1MiۖZvbQ"<KEPc,<o7v[�$Aq`CN3IjqRrn{u+1Ӯ]yFV4Θcno:]g򚆼ry.kn`r{Y ^kgC{ߚ3?gQ )M?ɽ(K>eCuJJ(i#)@Pr~r(~/W2pTԭ_"@[S&[fCab} o|61qm 7F91wƪ8".6$[|IV~ Kcz.}Nbe|y\/<+4" "H:0,fPz6=d״5iMkZӚ4HYiFfGƷd*h_cI;]@Wk뤈#ӞOpuK  c8uB[xj위І9k Q׾"3eqnymݘېP]Bo'S.^A-qπaA6zUs.<A<'~s'warEJcWﮤ!侴7&4UxGmF9yf)2*wJ5)΄A`tmWa\a`&p|+cg̪HT{؜6}6_ %->CqmhjEPG}zy?zt.r# _*-|-9M-58GhY?l91g?ǿ9~/֗x.Plyuv6$w.ގ5ap(4=eM7Yf,Vj,@@ c'lu�ΰO669s 7nk((D1<` ^OcN%wv̉4q?;=.<Ybk?cifv;W6aq h0u9éc3_Ʒ9^]j6 LmQ Sϗk Ty02}.F֡|qs6:˨ ‚hUF0*닲Q!j2Dn٠ϵ_"U瞼r�|2 ߷-k̻1xd0H%0zaPlCQ(O{ukcVw<O2 [ܲX^˪CBDUw^l)zyv=V'Puw :WN(8rfy4fG3nXUKw|ĂCtn?5iMkZӚִ5 S`VtOA\c={xXI�eO^g1(c&!R.ARMļ om b RZ1eC{o&U J{VhqaY$у }Y->%-Wywo^ʌ-Ɠ'qχ[Sj;q2a<S~q#T|.GL.@ T(;7Iqj2_?,rКp ڨf <.4jv^鬹%]p]tQDi< u:k+J-Yhn@u+;0I{]51P?Cc?anEPqVVCJ~0 Wr )p?r-1*PQ~'/Tpr?` _ ox I}Y-s607lgv7tp ݿ gIG|.;dq7y*-ɥ[Rw3l$(-%<cQ�kWqM/}[? ż]]A9~~ܸ~tvRVsC ѯ`?Vs5sE8|mlmZ dln{c&Uiq=Ô>;V2EWs ]ANfoYm( sJ,-aKcI2�(#<f60˝$Y .F\4RE÷]&y\cڭT8kyGyӭC|~>1B,}ʏo rIFR&Kq]/1oju,WKd;uQҕ ,1͹2vέ.)ˮe9JD@}5xr/^5\'˨n˟ykL@=-]O{eQಐwY5iMkZӚִHU^vMC8B{ZP5 P遾ʨܧ\BNjԁgmB&aӤ*UM4%R[;GUUIqUkⳈ2�=<aվ.r?kx&p6 W/ɬ`4�6`;}x& >m_ϙx/bQFeQ}I= cGӶl8W_U<x}8jjSQfMchE<0*vَk/"yÞ6iap@yET |.2"PPd.Sdz#zyGԇOĮ:߶Sfr�c zn a<-[D.Y:WUM ]l~ꟿĥK6d;3ySP ?[U;�Lw1$"` ݌Lb�f8kX`J}cW/#YwCSlX@D (# 1N| ;{�rV*W^Ǟ dhЇser2 oErѭo;FoœVO VUQ <~U[Z?iYW@]^D�YUxFpʯı檊ɄQ5 p7@eOy*)_3c E~9 {Y⢭A6Y2:o ? g ƣA}['Fg hJ;z}9I.}7q|~~t__2;Ag =3_yk-<<7tOTs:g#:};.7@cI?-T><}{IGȷʺlwv w(.Wn"Tq bޛGKr}{oug�@\��A�\@ \D6ɖˑ,E-[r$؊s>r$+ʡE-E   A 3`޼_wU_Vݪ @ӯk{~bcqt;+BqJ{2D='aƃP` 6Pyjј({�-*~Vi[j>6O|s�Є&4 MhBjP7_QBYQܥ|YnQ-y-BiU娫bI EPeǕaeÔ="Mӌ, `l޺r)Vlmjb2(<b 8(F±kRUڤqGO~n9tH3oνQw.|4r[ _C] ~g~wk׎RIM:R|/r;`q<lwIqy`,\Ⱦ\8}kIGc#/B A4єSOFu/v̻<f OAv?q^(6at5tLpY;`(*:8L% }8ku_E {8P:Qϫ3CK J(ƖO<G;:?ꭟl,o>g✶~~w~Ϸ(M'/=~=TqK'*Ue8HiNe=0uV! BxM}00Ѧ|jB/"'Pݫ(E; P{*<'c4Zz* Dq� s!�2I$<а)xЁkXK+Iسk;x4 3JAV2a.C[bb \ qU4)s-@Ҩ2+<޶f$7a *uV{iWTc DQ5ͨ7=#6z*~<gbSaCpLd)@Q k*߿ֹe1w, h�`l#ݭ@mml x,^*U5:ڕb݆`o;}c1*" SN!,j\WhR~to⹊fm|h$T^ IA*K k(qƦ)}lŘH:`Є&4 MhBP3):AǦ$ &qEצ-BДqH+WTtDq|bP,HA)&W) GwDX)`lXDQ( ݵR|m**BѵAwH{튕u~m@*6B#5Oi 18rOpŅ |cUHpz-Hj7qvK)2ϱ~7`^1j,*/c0h nԉR5g+ YDr[ -?|s+(e&EgOBvPCH<Az_'MRScym6~}X>h` :;\8_ȉ#Az1{9)zsBe6rhMA{"�T6GyLn,zH?0yHo9}-w鯞qCW]E}OnA%hs=~nPqY1?Ai;JU++R�AETJ˨_G)x RMic5+{ih\-+~@A/˩t"Hfup vŭ zvwpPwS`fq`|]U,%T.QB?a{b5ѷc"+9?M)C�\hqf�|nbX.E*)AB!_Q4ʨ4kVBü52 {85/C[l`۶[8uE?s 3 9;DZ*z^bUH$)"  �qm?2ejǚjFͲo(eXo]j;RhXF=CA\-@rC-%(/0Z,Uu< 4a t`NhBЄ&4 MhNffn;vt�iRV*y!$qyRRB34BlX, 2bZL(x(%PH)Gx)R F^Sxc UoZp.D(ZV<ǘS[EP+淨ߩj"j+u$zy)]FY<Zfu~?IM �s-sw#=bhOgQfd>wNgt Q$%(\n}%!o*/è k]S|vx| @܈[ɒ2{ y /w40ПP^~+Tp-`cdLzg = ,PŬK6X}Ys;2)Y7r�>xr1fH_転=~řsϰvy/moM <s$O=uv+ dx>f ip!ꐃz&6z*p𾏽%Ue9&QK}EX(B[7U8+hr`[تu+h񼠻Np4vJ._\O#bve](_c J{-^` R9ht _t`vJTɗ ~;LZzhg<&fZC =ܼ:x.} l/fuLGh~ĩA{}5CNK7ZTAl"["<6ҨsnVn/.ǃוc[[QT74Rqo R8 .x+쇑L u`mDTAj(vq><l97atR 9-ch'?1ﳓ"jqF7^^vߐ#֚ʸ4ZD1L�m'CR7V^KeTw±mokx\VwNlR0a-׼zq޸ 1$-60ʽcT.F ; MhBЄ&4ѤyR#{P^<D'(P)e E0wJvN Y kJL"U0WcU!. yZxJP PEreʹ"$V !)x�0 6$iJݡnҫġe >)jWPnJ%7]W,[ׁsMᐝWb*}mdcv\G a<;}߅ˠb"y]W/wb $UC@kPj=(뗥pcy ,0$L+tYUÁų</ -d6pF-SݾNoB`q)�� �IDAT}0{v]3˧ho5k"D­0sC¹c%юP-20=xy~ |~H̏r㓙ғ87Sy^óf]s{]HWyuS@\4η#r)ϯgy_u8uƁ 04ބ>mB; *stE�01SD(VWJrϚ*0VaEB<p<n �-U;8Tkה' Wt:gtf]F]ÂRA)E/GJ?#X OsXS;'fTJVyZ !Qg<ϱ{1kz;ue:Xou^Hg{EFvK't>W~Z).w?'�J(  >V?D:,((p]kW{  1 9UNR-R.#x-9Qqģў7C lo(  q d]Jϙxٸ4[W\Wzv,̗2l!/T{~o 1` ᥘ <m2( , ϋ2]rx(35vK4Ǧİq_ 0A6Ɩ{^JQni,tl?lЄ&4 MhBP*1n +بq1hc woVAh #Z)ٴr cm!^G޲E6pyzi `i T^9Q+Q]qp#QAq W7+H^jxO1cF6%3 ';XJ*Q+3omW(?1BBAuz")J0@@eV/`省/R6y뙛(#)s`ŅF$ sZRF PkЪ0P.6.}l`c^1C ֧oq^i󍘧o) Dž֠v{YJq*"ZJ\گܰʌ'>G6 IYfրg30rTWV@ 8z gp<y 胾 J[k 3{vp\Ԋt>{#C??VT`J]w!s)h0a)s\[-m4a}Li%nC?=}\#@ ,˰"ܩEp_Hm@l|H�="ե;['`j9cټ&iطow4 ^oJpȣl68wkkS{ي7`VàT5衑 !5LE(bt1JAS��ܰLd3!#)/16 # A?0^Pn|w[na-!|2!~W6>,޾G]RS:/IC-Et[ (EFy*3O{TjfU | Pǡvr(,}Qmgߤ0?WeffWUVx])IH*)Z Td!'f}UQGc U§H |YLRBw礲VF/E4"V Uڈ_V\N{|䱠c4LOcg{JwExc} ) MhBЄ&4 }3 6MS^qˡsa:cmX EiD9K2-I\THhaB{]BWZ\ RsSN3ʚ]^C Ԗ\?URQ*wQ(BZkam}KiMM13;V Zn c!Ј-ҥ"A"".la$j\ R!WWQP Q"s ޖIZ�5VWS8�i@f}䏖רf儦~mE }5Nc_կɏUÚqyJ\H:G+C::ѺU(,>9 &anKЕ}qHgB.܄O"ώ8a ܲ7a4U+c+8 5�= -' ] jfK\Vո3`Z[ ,3_؞/}r_KOn2_y;e yFI M zg`Ee)$L5갗5534AL]^1Ƿq+Zs2&>,m+vĹSX<" W@m?p �Л/%Ur7e7UaXSX˱nR{l-TkEl*ϻ8v%w !X }'GeFiH {TH>I+)� ~I)^Z2!iwJ2tkQ3ؑtq#bQE,b[Jpco+֥H@ige r7<Ϲ|a 8Y۰ú6hU{a-%h<s )U4 BfXOMkT&SR9E<,zF9P:MBKJ%oiWE`dN[\Fܽ*_}I0PDyZm aMT@R.YJE_ocWot5uuVHLsi+˅^rحK^f0ojD˹h/h+W=Sr@MR} @#BMWAw*Ah[ mlغ2 W/O{]N/K;4JVDdJTO-qsՇ8{'O@_Mq9ip$24d'4 MhBЄ&HJk$ϽT*犜SB7Y`RRPe(eHL"WJcQc\?!.j) {5q˶a.3WMFs+'F:o�zFIe=VօB2:=<_Ԛ)s,ZUFT@g{QĤ頔bcc#Z|(o!CvB7TFT7Tmu7kafPjDc  = KKN�'!,b322DF]awa1si1;?3/n,g}:K6ҁ ppK%ǀE`cӐ=ähHG}1˄?ow<czt<!U,GwaA.pa)9h[%(:܍!7 k]4Z ӔŮC+t ![S�I~)xԫ?]"{X_YHƘ{GUW#X_nJk_>֨Ma16`nH !VU] 9mFVir*p=e'+#/YCNS~ӧh虝qFNSgxy7xyM);(ı2;Bnh-@�jݻ7 `Ǿ!2H5R!i] rqdm.d41ae=I ڔM�'CA�o/?c={%,9E<Val)r!;u/0ݑ/~o֓Kصf4&Yh{6h(i_]]gmm Fc%xĦ6 WRjf<:]QU`,>6<0I+&m i�QF4hΔ2.u2;v15=󬭭;4 P[}G+g|`NhBЄ&4 MhI*X<{PAY70N / j-93iOabm9JHBK.̮/ʺ#Σ 8c(X*߰CHfonPPr.h B={Tyؘen0>ctXüb^uA徬YV�§?}+0s}{A:K`k ˻anʀX&ͅOۿGe|:ep-R.29ʃ( A(vp~Z_{5\}5^w+gvn,9{,O? O>}ǾG:3Guő“O<?~?u^E.i6sۘʞǦOt?i4FOpmQ AM>**S8r2j++9\; .$q�(\]4 s\�Vpv(B4$ʷ RZ[OwǕ^wM|돱|EX[Bޔ6Q sot<MFv`6C3cqVQ6}͍=lfY[_Gv9 m wB)!8~ mڇF[k KC@jYAC.rB>j;,�)Zkj џ-vU/wȆLxʥC$U\M-M)@$3ż-(`chgŨUx5G]\WS?cPg1,"3R,..D paMFa"Eγ+ sd(VSۭXF*cp֚eΟ?_K6ȨcfwR\rp`Xq0�[[C}z�.>^ȩجqWqFRk7Ǡsœ{JcvλЕuߓh@O|dDH6hRZmgkL}qxt\E1:p.hC<c9zVK-!~ף c!߁]M� MhBЄ&4 5R5}ByIEtlX`CX<1)]_2P}ΞFrAT3(\%_ ī+ǠdiDE[)B]n r@M`{Z-#eߪVtT(o& kS5UUx};ZmRiӚ~]f{*iT7榀6.{AC[~fgݟySW]+ @TVVX_;ņi355á}0T+)t$࡫8x[^"x4gZі.7ko}fƕqIg�at:l~|W բ k~*[h`3(Ug[ zl7 \n8MvX:9*hkv f-$8<*#or $s4{VUzSmRp /"'!p[ny}%;hDKWDF8M`0H<, Ҿw.칂tcF~Em5hf,25]!=B4ru٬FU@.<`|NdH=HeYzȌm'a/:(EE/~f>jȅ}Y 99%26.<-/Ay]=!KoUu CqZHum0JsY>яammE[MT5Jllkc)G7 v|@7YU )Qm d)cy^/#PX;Eٕ†Y~o;am5̋FF j2Rw;p(JI8EUĤᘫjذY5KNC_J0,bݡ"AIv`KekRqu&h v.0==sz7^cSjuɌ߰$!)K˘.,zF.׷9R1)BcKs5Є&4 MhBP*/*836(Lj;탦iqף%ΝsJ%+XɽEmlǗUsP^5�K3E�7@\Tx_B+Qscs[l{U9c`sefE{5X1dFoYA]B13Px е.y{^EK1йщ+u`4 SmtO>[Iԕdzes~zK_|BY2Kh'^p㍯'"{_y˳c\bPV@:w vqED"\yp/?z?K>rdi֊uW9vY>x^nա"Js 'ӻ10L9g0^DoJ ._oU~4 k`糠z8oֈfDJdoρJa(]1Qjmȁ5s`UZ:p;}%ă\*Tzw䜌MLQs_r@1Cm�oEIm?B'Q t~Vzc'rm17j4ϭs˭/ :˜fK6~}0ub'jYs9Z9lP*zNl] Pyn>v2= ]!2ϚN+96�>̂.=<~R'*xx�leek-vG>R|>ZPf%6X@p ȃX'QUϻdǵkJtqaL]cF0z85F\zS\nP+b%gp lN"f1!L|oyv1oq(. v&m~b=ZFv<4irkG.cPiC`+h%➐;b75uu6w:J#Q\J=E)S'9u4"4N"^=z+T.౺%�Є&4 MhBIJJPqD+qTNSiJwmJbWYf^."^)ެSO\nP:+¬\ !|V=p^NȲ܅­B׺TR-&a)V*ʲBn2 xۤIAVZ X)GQzҲhsxQl8YO1X!֞;$jk V ozǛxӷ溺w9n%,w Dsk1Lt;>{v=XP9 ` d: $; 22M5gq[n!GXer"K,_~\{͕享oR%i]v9HrmH8`$`2nXy v"6f ; ?$.< LkC ՗Ȫ-33*d/1BJuC؎د^#|m9 $ wk҅?}L<ٿ&dh 7@L/w<KO?A(A U$&nOS܊HXWƐ 7@ QPËY$z{�~lJΝ<gΜe}}{I�� �IDAT~_bi"=tȑBEc"z+3Ja1ȳEfcSkeL= ~83iuy@QMkk=rɫ*0,᷇z׀a}їpIR=\9jecOht^zYp{GD5Luvx%rjZbv0h�5ZJLC1dM]N�BGRטO|nK/MuFKBcy#++wߚ??~F{V>†2^^ M`ו# x.5M� MhBЄ&4 5JoPZR%\ " &1YB)W8=y9}Ϟ4Cgj°T](Ck:*Xtc;)R)EZ->Rplz JA^On&J؃u ,mԢ_eRSX]]3=Ȼ^z-0zt|‘).,F4;QOeev̾mE6ŏzJ)=v#O>OSG9S<G9~$-wO>m `* E΁]Ǧ)H\;C{a`Ș wt o枏%.{hSrӜz_WykI{w[..A">eeb.|3R}]r^J5^SpkȽpź;<wxymػ<uh-&�^py�Ya.Tܬ.@|+gx ד/!%I ZA-1A7|<tS 9M/S 9++kt\ bFkgR;QPǑ֦k;kn syk|=.PN]Y?4|SÌFl5 CTAQzF VQUzP)SمІe U7T==F$Ƨc\}T=%mZiZdYFc\QR?/P@܉_qJ"`P@>N{Xr8^1%?4J)!"egjcT/bo4W({2s1+6lZ^,yʲjhq/+kVf=K)!TSryG)T=US5!zuR^[w[5OEKz!1N2|ؼ-_0iVΈg,cgd)&IJa^r_g3O5}ԹONĿ&grw]}s_n"M�-R '._sj%jι~JϒYd'$Z1̶ {fϷ8͕ -ZH&4 MhBЄJv!JXRPLX@ ᪃ƒ_ıgFYW( !4%] WxJ2Ԁ+KDyxUv+^c U/R,@eƠAP'դ=}1>~܁|uO| 9HT8-"`:{`;H: mf Mk5WqވԓOC>X_η_ &GhF)/=I#͟^�Bٝax-jLkݞS'o8vɏ ׿?|o{0ʔ9/<akOGڝ6)R`g]tl`;g H.w}?u;4{v?d<(]0dW{( "RRء^}NO߱>#2_I;ZpT}Ż^zK1YĵuՍ~@qֻ7z^y5WHaeeՇhV'4w ѐZxlB/4a|ugCEðiZkQA♩޳=`,v}c/R.5^NGR% 2TH9| s wokN+XSP0Ǧ1O\$qix\.D{ܙ`LWiqr`fzj 2+~>֣Ɉy^;3L [ȕ\o@Ch[%Aևod汯?jrI TQ4-t׶TpPMы;7]+`j$?Bi\l}&,- y_ټm<_%)ܰ?h{ 6,"kG ٺc7ͦr�"@xd.ؖ`eRC9d7 O.qtGޔzs sF+:x`vHLhBЄ&4 X( ʊLœԬhc4CM"r?ϞlG$Ec_AEJՅfք&.8l)kVoSX"rt=N{`[/?X,^u_,7*9rylV0kX/Qt|SN^ʗEΫ0ۑz�T4IK�㬝_iϭv9{(458W6yˮ_r'3? 9Vui4-OkL%ǐ̵ Xρ~r8)z6Zs9}cyfZ)?/~iwxeV\{!aV@u!] /˿ 5)xY\Udvodm영~�A;)s +XHRX=nB\_% wrn|{t3(;h|& _3WU ֦T MN@ XP-x>$<knDCz^wϴJDi%X3ii˰.4 ;9dKEYk<14K`.]n΀4�@ [F!e\nt�tT _H >E(5yVx%ot۫p lNH7֩7IqRy1Zc#Wi(r0\ddʯa䠄E*O%YOq)nքMIU\D|VlbtW-r9V :FFjQczUO|<z9Cs#+s^U[k2hhbԵVlc7Hu]&ޏu5TF&JwjoȞud{t޲<zSQFI̅CxAhԕua˭FFZN!>ML/2nU˪Aak k$m ?|:fۯs<un[f*| .oڄ&4 MhBЋD\V=Z67)1bQOOt˅G-֦(%9JHԪJ\ =U`@hbD°VP$`rIgA!rDܺalC&Ń54qȩ:~Y~~0 X#1w~ <Pxj/PiIZ_O\x6ԫ�;q$`; S|K›Fgr${�-}ICѩ}rۃ4h3 2 4D/`Z-ُJi>ܙ{v=ݼ .8I\ܓ)` K{Dn0_vB2s73S`9{o?jk?ʳG~wX_v= ߹t`(̬<e(CJ,k"օς@HqrFiz>OYYxnd Nbڳh`vf@dȵ5r"Q.'_Q7D7]^RX'2"EC=),g~zldp;jsC$p9?ultV.Dc䏆6T 6F^JEZ}x�VJrSSJ\s8O4k-Yg=967[F0mS ^e]<O)l ܁F~lnѫeQ=XIqS'EkGo8`A͹t׻ecv Ne\8t.xmd%6AN{%T}uq+[i3/igWBQ@ȲI<6xrn~Tm߿5hlorPÃ*CJK.^Fn[J20u0cq&o>\Oxg[.-WDaB}'lDgRyj^cod[sVnt}äxBЄ&4 MhW(XuC)ML*NDem8Q4BB 4H &B[!HᒱJXk wSR9 Q*x#iQ Y<(B2=[ks0 x<)g]5J.})ֹ/۾.$iӷCr'xO-i=(8b{Ξxy3Skfh|AjEXJХ"Y!6fcw1 [rsJSDϙ56.ThZպ<߉QI=>cj]�Z~9ڝ'Ⱥr| SiT<m60+Eyܡ(�xOQCdN{ˁ&X٘AP\=W3o FqhQsLC(c]lTۥMA3WR0^tl:O>hמܳ=O6z .-$ H f#SYf7x 9zML%4m $9@Zw2΋1ftL Mʅ t0RʏA /s 60ty߳<sy9qy%)X<+DgǨVxht�Qh ʺwν=G A (oUmҤ6^McU=_Ti*Tۍ*5qW⣮8`ԧpJߥcP03;ëny8s-A8OB\8~Aڂ=q$g,#u}U:g�hVG|l3n'Mo@c. U!�DqyKy-o:6/uV_*@hRV\<ab}⚚1ѱxHQFˆ"WQJ?EqJ]$?&럣9wAblc�XF(jTQ~9  鄭/<:zKTi[fW761ZL+:][zOOVQpM`B5&M:7+x .6C*(y=ADd_/4#<G}aԷzxTb0LcY)׼*g1muñE0&M�Y٥?t[chėVz9> z,u MhBЄ&4KD*wVJ�+0k^#oRX*[T-Yk}C+@k b├�$1D||b+۶dmYT'H q_' B-U(E&.zYKb(ʀN}e]]䰢PsaQ:C lB-H-‘nʇWf. Ru݌zԮ{]$#N586gN};r̐-s3ڌ#0<]xأnD;!4Lif&aĴPSa5(W돷Ty?/{@nZ~̏W;N6biw#_z~Nڠ{wQUa}"?U} XK_ʆ,F,ZrtѢKZ ?TISZ:.{Ah }5�t:`uқgW!6VZDX.0cgniN_nY=YЊEQ?X!}XyLn6ېkf'@qg) {ޟf=u9qoeH!BzRXq:h^"CYe]_ :Z~y(px.^w-Hni%eq}}tUWv.=: Ů]]:UIWUئbe\PۭhM,H)增Yg쯍!W En "9"oCeToYzFif*̥NH&3֕r0rZq|an "B8~РQB 5QD0 FROBy5l|+[u,Ydy�WRc̵lhw=ënvs2J>6ry0U=/Kmr<f#~R)5#4( 8oEݘ/YVhU�sqQqxQ]�uQry\�B~ Ue d`U>zڣfyrk.nݔ{KJ;6a#Ѧ\`1P(kf7.6Xv*g`1R89R,Zos낓ƙq" 9*3 vb% )IYmnKR,* ɝjb$Ъf#_1޾EPaCtl\XH4zEY] Q>kP]PgŃ/GR(9{swR^mɳ/WewXa|1sr8)a]%nR*+^Rh=򁇗k/:?]ϭfyM;bn1; MhBЄ.?y&8ֽݭ% u`ܢRq(a080aj) [l wؖ!mCo-ue);PG9&3|0ZyKrErZ*DEp KDPCFՔqb 9jbKܖبS |_|ZeM蹂OS#|/wI>nGZ^d{֗Bw7Ɵ@qdv}ߊ8lô(R@Fy3f'?}w3"$د~#Ia>F7B<]MBzVH DfiY Ju1zN\@ԗ AU&=@C/#ݛj|V[DY2h5OO-2a!I9ŭNc�j6(01⎛Z/Hz;/P�'ӟYC`7e.,7 :g� p2chJ =Y\usv3=kd6em}5W-k~ yH P�hNr8�ʩƥEC丟B?A?P;5a\}vNyeJDێ>+b1/}X%qs"}\9u{ˊVˠP~=F[(o(U= Őyw ~{} H@"E=I<0,YUvT9U5UAhٟɗx*դ<)&XE{$ٖD۴DI&ESID$��\<^{gs.<ݻ{u~^^kxyEξXcJ|L*ݍ1$)MT"e~=i+xčV;urP^Kj6Z z{^�򊌂?c1lU5mXټ�.T0 .VH�� �IDATPjֽR;J?{ sͶb�(b8\v_ j (ۓ:_pޭƁ!Zrԑ.+QTV U1M6V~K[;MMZ}7x>TUhL+?$Tžz|Sָ1[ku=^ԣJ)o@D5_w,?&tVn ESuIl}}Vxr///_/&kf;ʃw.{I7ߤMڤMڤMڤM d,@1NUuYF�kZ OѸE{#<Vˈl=.b)j"]'JS^/QhQ$qy¢RK$?gAJI@^'teUN0;sgxT8<_״Z 0XuchLJHjL86׬8. Kϳg =/K5}Wm 4砵 &0M[(.+C>:7S諙KGY604ͥ4Vڀed {;w3#K&um(u@AђOʢEt*D\E;iȯ8i C~:]KRn*5gŬJpnŁǿ±ݷlc˖-0J؍xDŽO̗s`l[ÝZlCeN::Ib! d i C, <A8^=\� - mFˤ"\�N+ցJ)n}?{Vo`i).\ZrE$ kd=dKD bw ܴ>cJe h:XF2#4iL%bi+1V)o骊U=1Wwg}lq�-MS,G)h+u2O~3'++|=gI%v;Fei(1<Yox42.Oc=\ׂ?-91XbHJU%|sSM(4�c y\u1̓ևG cUK8[+R`u# ./U Io#I]> ~b]q&q�m#(ؔ\5::[}Ic"ފ=*Z1nkЈuyĹ0pG@0hXC@ߨb &4Eɕ W:@=wrf%֬\VxK|U供f5KIIIByJS8EZR'ӟ,k i\qjzu6Nm@FڰiM+ʭ/N{`⇅b.\ nse\!{ЮT;A+z< 'nT ˍ Ƞ.˩cft^8VVI|Ë/?KN03Ka HWrxpB'+֮eP[[tk@r�g'RS;�1;s[|Z  GVAsvo񸮹1j. yvH 6hHmдGIBVտdqrS+6D{l=b"VL-2Z`o!} _G530o3q}{YR1TT,!Ckέ.9mι�ؾ5aW#(m\lFfv:wDqk^$wPQ'ǁnH$i^5Z .u ,4д-$�VHj8s,xgO$ilf/"+΍5ɆZ)fmVCf~`@.4qVQ;䄊EW|%IBr[(Ljv}ìK@J9ds4)86&w4Cz�q.4ud$.*b�GX&7(5$T^Ҁ'N o6 qW9/Me5' <LC#k>P{ƚzFTA "ƁZywBW}Rꛅ�U4Q,*mM6of콰-eGGc{+tEW֚rC.0'7LIܤSq7(4?'f]4�v@kРŇ#cY|8D{oཅi' հ+�WCd}<ҥkoYO][x.6&m&m&m&m�&]8[[W7qEO(bry*ܧJK;tj{Hlb7vaɺ56+,0¦ÎZ@RIrnl�-&1HSś ;FZkTa1+ޏk0y<ˬk"TIeSV{ZuGP\x)>fABwOA럲r?0ׁ|8uqQAK;br׍]w0{?|HekpY\Izz6I|uHwEth1hz7՗~[UQƾMvLbJ[xζ zHCd+*TՍ6~.od(/rmwrÞ+dvǧJ<!O- Z ړG"7@ H~HiO3عzy\{xz4,-)u3#յJXW z;6Fn 0p={\(EJ+,x+Cˀ9Z̤'`enѕkMx(EX_b _ŗT!Vh35^{$I_XcJ++ ȡ+Ӟ D)`:Vcyk<s#9*@,lP!\$K(XMSoB!0fntz(, 2~&oL+d̊"ڹWMӔ<i[;wrm7yɚ\T:w=*1X8ޝwT%xYz;dr-e:YY;yȸ.a 4T{B]W^tz$A0^lci>S4ET [UD%2[ZF/<gq*RQiZc4QP('ZQ"k VJ $Z1icm0R k2 5 �{}W郱e> ]Xj8woʛ j#ՄˉI=pZVSTeUzi'6Tt+XeFb4L2/g-?g)BV4&wlFfٙ.[la0XYZz -aעqoOQſW @J"G N6"K8nvPMڤMڤMڤMz YE"V *\7�c$c*y)nYbM c n鴮*8b=SGƊJ"3λ,m5ب:"UUpLxPOś-m2.^ =zS@�·fz1@?_糹~eYx>x@3^n:Et0A92Jvu}q1ss/unZjA#ʡ +eX(/tP2KBׁp'(C Xbcwއ23X}4?K8PyG5LmhX Ȇ$uVYc;v;C{+)Ƃ;<{c%.vRpqA.9j%aSe&GzhxrIq! 8nEh\wyD�,/r2m`˖-;bm,ZXq z^_ "nNA@6EiVDJP6@:l IY$IHpIVPhjUPi[/!2Z O.]IXs'&UpZRxfF)(LCC 3⧞lRu58�`w<1^s.^068E;|'d&')풨LW壞glt%_#Q59-:f{ ֞_\ c(RhmS)ɺEfNL. ZER)^{ޣph:*3Q5SX@ V JR޵fm^K9J0ڹ8r>F}!Rke$13^3( q㧩e)/kФ~`|�*eP(/#'yTY1[ؠI<6gUp\!bhXrӞ=?'fh.|`a˖-ܱ?O/[uǑ&B)&:(suw<7{o-]F#_ݼoϦ MڤMڤMڤM*6&8 Ĵm'̋)Ƙ&8_T6u4xgحo&4dgG,^k˛ò̒jP5hƙbiԑ bPYXQXB¢ĂobZ7&y4W6m�&͚4 {SVGEBۯ es0~gu5١k� J~z:jʌ&2p%`~ mo=nZZ,PPwr΂bu Lj!a#wFz}$ڜznk[g^"Q8aFkhϟe ނpy.HYg~` $]\t$y@lAm_^ Zn-p9s"*@6)8y [aᄑ^ۂ[m;ns,zCN>JڰuVlt\`ٹYZilYX];/項!as"36īHQuRՊE}_�.-/T4_K,K`*ϓ6(D{I/U _.A" 67 ]@ɲ6 1[(T>σs9BfR(^qB(ȕ򤵶/RpRAʴ"G{s8(&gg�UdV�ZF2bι8�T�7\1W`uP;֪n+fC8\(&�^Kg9x>6zkQ?"IS sÖ-??ban/}ݹ6N0/|}"[08͵@c6]=or$Ui ,8Zp=o|FK4*#)y`_R0-IDӔ2 zzzƺ5_s-njsw˯/ VH2_>yҥ%x W{3 R$VPS)Š$:qb?8Rl4]_`ly9vMڤMڤMڤMڤ)T<IE^P $Iطolٲ<ϋBE76!jPŵgC)7Aq7qV)k 4* w8w-+D+Z&QtZxuU'\Y=nk_ѐ@jHJZ_ر|)BPf ֮H2#2hvg˜eAd$n Iv/�EK+�4ә|s<%iĞ% :/FF[rcUi[8I12PnoMo>+ß7|܅PJ-L}\h�u t%-08 25fpq,ebE`@,)#U2 *^nI\-9z_v;;0~9'K0ltdtNրm$wi"Mڜp I9w[]5iZjT, e7`[n)f$`,"5zL]]+n%zR6ZY/[Xλ�VUĖԶqa?*K}VVz Y$ DT''vTpKK!K*廬vZU߻^m%?RuBJgT>- \iӨלf;\TLJ W@[u$ [UuiNPV=ѐZXjTխ==_w =\9=b І.nR 33ˍ7\[oeϞgR G-aPGsj_նmwz˄{Ie.BYeK}`l-VOFo_^$swR4ky |QTQz*ThDXY^fiΝ]d8|zw:aS,-/�ff~:]kNjp6+eyb*<irk!/g.\k6RY.s~ۯ5;tѣGyx߾ay_}/G7,MڤMڤM>HAcR$I2΃ܶ6n{ Nܹst:AUU�ڛu2'V|Z*:ɛj}[�ږ.!ૌ*sF1CJ4 yybJYpꊻfШbi f{%;qjڱ!t,ӭ}gІdtYF'?CV @\VN[N Vu7RU'S:@';Y dK;ceh<% |__faǃ0- `H6Ơ5AfE%]VVr=}-ŲPvBF; Hs+li% 5C;dw,m͠ y22X5PûH.oM=Gk�n+T$M1� V}<1V4d36*K+=zg6I֥0V6X?k7<�sAR[eeJ;q ܱӭB JF1زř>\.XNQ8l%ab@84}#)B1uebF%:`r,P#�`9*ϊBw[M-@p!*jeITKyw̮N+/(AeptOb`sE<6RV|qMU~O:ptXZ)d8̅;TPS�#֞=xKNb1b1<Jc_ k,6䂨֕XX%eX9L:ʶ O : !�9T:h\!DWji'ȷfznC7?bRk,zqDoɚMqVnKh'RAtBWw-=r34!hD&y)`UܿG㮡o]\ Ka-FU%FyTHuuUƩT/#.06̈́аojLm7+JVSq d[3Zߩ!7`uOoe$qdʺ([SX؊皦-?77{jkWv2Ɛ$ 3s`-~6ޑ{|ҵfџHn};5+c>|r{k>㏳\{ j馛ji9~8�{_/裏nX~IIjmetMtIΜ9S^XX`߾}S={9N8Qt:y睗NM*u^i]<&/^DkMe$Z{M'\[^QK[)kb)m`b5MZUEՕTֹR fJc)vAJm$XRe0cq�:"ٚRLAt9ܦ XPm٤m3 ՗gOcyrx-�� �IDAT:@dᅳ�ZF$+: :+⏓ t'պ˳$7Vvn^EwӚ`F233ö-[t:,( :s@lhH"CfE|#*Wqp@$o&oƅg8Ձ}Sh)F1d$օi[ρvYm8WrO>�-v3ֈ5 -u(ʮ@ϜrM{1ƐYKo ˽h`-4<>nV(Zyd!xq%-XԼe8>6bT.γrE4qUJmy0Z:hnAE9ף(T;Kq1߰DW0}6){+ EQ`QV|OD'FᷦcB!tA/vJ`]oc`7O 0}M'G&X/&:↙b&0Vo'6EO~J+HNem(vLyo:Yr��ʳk ,R/۱lϪ ,N+J|4Eָ i(2/)C-˯p8#W]KmN0PչU%@rVM.K4UUhH rTqRVkm4ekmv[cXZGKtN $J[]eueDزZiU7 '�pEQf<2�ݻv2;3ˑ#G#@@?x _"꺵=|0+|A>OO|nqq__/~m=#<C=9}4�:o&mۓV*>rpљ3gx'n__ٗ^z^xo߾w, Pv.. D,E#kX5�Z)Tl!ißQв+9\C8^]qODJ 'c`Q&˦ xt5E*zNl#̹8ͧN8Kv4(M,;ͳc<'Ϝe)<h56 ߛx uiNk̵{T.ED. :@"!{䟡ݎNc~$?$YDc&q=P0ke/^`0кɞu I+y7 VG2;3 78w@̜^d{$QZauH 0}t)mh334钶RҶB`Eۄi NC)9j :_sNrmFVɽjJ ثei<V_e-{ٵu3\cYZYˉgBvB dD*Xc: DȟxUQхp&"` ỵXnk{�+YqQU׼8& ]#P@z>QkA?ӟ ^pV2IWgQjp: x_RqT#Nɕssm`QX8yP*`(s ,2r|]l'H~Y<_fZIe,ve;xP7ʧ :)!<Rjb +/ZT4ˉ<{ `EԚe*+hlHg#S{:ZiZGjz}J5 mӐo?E[YVhz 2$ŋ@YC5[n~,&¾e]mG_G>ňz;vxT^RQ|#!#JM(#, QQAP7?VƷ,65a K/�M],cA+4pRSI7VuEu -{�\CV(֨bו5"h"U)Zh:Z|YU2ai㆒VWX ynjB; +KךEϤԻ5+S?ѣGǦt:~8y+/_||37Bx6i@+iӭ>ǎ[Jv8Vw>[B)qF1-71Ė[VƿbTWu4βÊĠuqy|7|a J@+)$YuY) ʢEtZF*KD $>[u}ܵVVΝ_}kIĀ PY*J:)tB:nJ)jY12hĚ9R֬E"X' hoiۣ}׽t}7ѿ%u�`n$ ^hgYYsv &Try+r͸{ns;/%nR$dUnFoٜ10w`!Ŧ/ҵ]YL»Mw<BҚ풴3?.3]ZJhUZf `Wxzg! o4{ }J @OW0pK qjo< -?l>DE 3 f@ZY8yOYv�yΘmjXtzr X`0z$fp k(EPʙ]+  g+/Ek|y/ ]RLXm뇰5h %B.&@bV%]z"B" ,F; (  5VHp0șfĻJh(%2t|9&Z.ƥ =}ޜ4M+k]?HUTQ|\b+cY= |]p?5lN)y~J\U_)tؗ(GekײZ%}0Ddkhx*yÄ8ۡ,\OۅZ̋T2F?=2wńhS <p$tc ynIƣ/V 3s<6xu涜H4`Vk{?vOI*5 GMNq)WgB.,5s\٪#M:0ֹ]94Mn5d,8kHsa,*5~0c.e$zw\ -"hI:`D% Qگ\ C)$ڥE#ᐗV([:iOh)e,!5 XbJc_ >w>{Zq>{m3)<sYoLd/c-￿bu!/WX}Q~~]Χ?i</HݤMڤNA@;;/;Б#GdlHYo/q�E+A,UD*%>klhJJg jTqlbNKmc[!:]x:9 zE%B4+"&'U=vw~NpWQg(갹5nXnʳɚ5_DTg#14`$5Pv8O_7{=lCGAr{T"+˷6Hf~zְ:s$syrVJ\+%UM|ny?&KK1tob)FffY^|{}VicR& Bz�X -z䦃7b;7;OhHXM 3cw:Ҳ0-1^ƛ2`W8{~vt;3WCtgIu8Iunוi0q͔tI߿6 bWTVKp28ЅGsk э례;܋˼~yw6R#6ҥ%V{=VL+Aؾm;j"2J;mE 033G[9 3g[ayݼ|-7"n1B GR'ίQc-1Rw:BY>RvvfpsqdpJa �<sEHN4bAbB"';2.yǵit&Er[ Y0xW)=Er)$RSs[WP}yU.@Bk r_5XVtf(,MHzo\;1$ @e=TmIN`.o=BH2iQƑ*)*2-)rnc<ĸX}x()9�HxG�,Y&IB.?+{#{꘼R*naDPLW7M\ц-Pn!߉֖Dam(wqoMy5R}Dfs1*?3k20Ӻ.Aװ!q%*J5-Ty$p`6 PRcYX˕qB'C?Kf�}_ʗҤ?;_^)+ЄR(1d*1K@Rg^dMq?d>"ƅֵfgM?G?{G-<K;\npq|>?}__ɓS巺ʑ#G߱Geee�0O?ߤMڤw5O~].?~ǘW^+o'^'kj#gPZbI#~<΅N˭"7>^�RXY X+0A>"*UqVGUJ]&~EJ B6ZVӮ3z, s;{Ir]Juq@DNyN(VW{<g}6af1A*AIr}Uo+T-}dn?A{:�::Ikrܹ>"stnG_be/4`X͠JCN;ȥ4ڤ3 %ʻ 7lSyI9x󲓛16N8Z 6]1Hs8tdgp۸ynϞ.(:te 9wCƻ4%MR CP3|Fe|MDD3,-gd֭[L' 2K�Y:1WȻPv$Ii?c7לWjei1xݴb8V:4Y (q*PD9#^ɮ0QAuZk.VY ;UމL ]"Z8{֑NC Z!b GмC.x`'>CˎeQ Uŷc ٳ^S~cE)uKYSY0*O+:ƚy6. 9K!קvlzeDƶ2nR).@\`.Gn }Z~*qѓk̹qj@nrSiI%7 $Z{a;pQ$ >]@Ѿ)qJL|5ƽ 'E=ʶi2\Ӈ novpoK̼u!k�3jg*ijjqaV,΋C5OWTSO}Q�K,>X5偯P*ڴIx AR ќxO �u PZ9oV8XDL9O @uL.bcxP8V,X>Eo{@vh{r^v P/o<vr}k{no+OGGqS~~~byzA~gw^T�U~F؝g~g&1?0ɟɟ[[t8_u}"} /| T1f'QOS{/..}߭l0ޤMz'�n۶ .�P{tΝԩS9_СCE1w}w}ɓ'>!Ξ=ˋ/8Rm?e ݩ} IQrj3)M[ 4(}R ltSBU j IQV [(D kQv�3Z;f&T6׵#ۋh&cbx+-qVV9kH\ )Ƨyp/sfkl)h:%g]mT&~ $U룠hh2MUQOoO8c_y=mi3܍ ~TXxowФKBw� lEw`U&.e[*giWD[cRv{,0`/]=؅@jS xgr5FyDf&{]jʸo7 |(c)�ȮƹlCn!Q}_`ӆ?* R`;9( nVwgY:ȲB)$bP*A)`0 PJY(qRߦ'qSěG30wv;e؇`*9qj@'qݘJ<U!:z"c~nXW5FAI�iK+8` Ӯ# Ax QښTdXX 2.?땭Vo #Bo `r΄ysf5y`N". v F U/ĹEC[7͇3ǠGx_18[p֑$I;d! $Z7غ΂ҪIrE 9<cupA`lq^U\+<P\̳ 7]Ǒ/(wʹ.[+m$"$i $g*yLS 8Kk-\�(q:##8%e?U~/@ƕ=G�N#飌~@-mPr,I}l/OBY8@G}9u5D%:ZUSZ"16cFa]9=50J pI'N:"kH_>?[r͹<<,FĕK}J<'`,^s<x{U]l4x�2eK9޸8\;94䏟?w٘/5^?_|&= P>^~ԧ>UyST%χ~>(+2ݻw<~~Sk2.ޱ5O>9n&m]`,=S|?tt/R}hSO=œO> )M^{1o7.\رcksYU[FIyQR,JdQgS9<S8E89-߸ثN I!I,[hxU 1vfzSf<Wacm�_oݦ;hҜbbw0(Li. ?3/rUm?{aM^t�_mb'mnZkՔ%B|ܦOsIE<r CgwJe<ø`ԗ`@%i+Խ+v]\(hmL7n? ;^#ig::eg8}Y{ 玲|,˱@(Mڝd[x8[(ԣ0t`UŽ:v؃ g' m/8nGo `_m@45QmP*Y>d8"ڹ\˭ua?C[Ms4a�=Iv,ٍ/7`p`{o؍!n%YNfsgϓ�[yց>ݙnZ$v҂A8tW!ЇKX=\3q'#mHRN٧u^*y攖q� Ì'~Ƈr\*,K,"b|'bQ8JE['c(\hc ڢQT�rb mw2U*jƥHkVjJ0v),�WW8z('OIe|OËSz;95x jDžKd%Ѻ�4]>�pVu,lq}k�� �IDATL"()-18- <De7p`#4lNS>I~HeJܷH y4lk=TFq.$Qj^=ɕRћkp8`hLqY:& ˀ3tN xGJ=bauEw[[oGQj>wyro@%f}y/nP֢a0?VGpEսHe}TEI6iCIICP:'o(oSL~/+pc/ѐˡ5 _;tx/ /RF<x{]G<@Gᓟۿە߿Z3? O?]uw^~WuM^C)O|ɚ'~'.{O~c彀k/~SG/e}~~~x<<gnnnl&mۇ0`׮]l۶ p�gp/ 8؛oyb/"/B~_e>q7kõy<E@eH,qVT~ ^iGXZLB@a%c bs)is j*C)wBPSډTkg cL1.ZW(15)ܖIGaqջQV UA/&Ȋ>@)�XQ.,<+tCHx  3RX o*y_!2ƾkJ[6)Yco?+c%KAm8P!2{&t>Me4S6yN5W`f�Z*k|&WަVkZAmx߭gW}wos購z%wy|KZj!fuLf$~2o׮uMbxC}& NJ Z_FSq{TvknpHFi}o%f=qNKo],]Bb-Fw֒Y)vsH5ڧ~I5*g!S`o﮻u`̙tg Sg΢۷ !d9 p;dA k(i,>  wFId ^DRsU<7BS!8]xjZ?*ur3 q4I~]7`c YEZU%[.kN_Ejuv[HNJU e gD<G*UL * IH3EC+Str6O,ͽڅHEcmrVXQhj\GQt}V;T8479 4I+VͥbI0Ǜ@"Xq{7*R3(‹� a6nBRlʯF5Y. <IIRxR*)@ ƺS.Bk$SS9BTQId˪wh?ՃxQ񼻑4~oCcd7$vFeVd;x97AUŖsyӜXhDU*(ըub(=u'<{賈aa<w}'︝O,e+}cl K[TuNRCz�N$Yt)J8N.7rU.V(lcmx[>^B|g?~f:(ǏOW\>￟C='?I8P`}衇G?Z(?^q{ny>|OO'>Q)77(SO=ů><TG &޽{yGx衇�*1s'Ç+տWEȑ#sϭi w^Ϳ7E{Ev]_*>x6L{'ȑ##1^;VXNcx3cϞ=|… \pcǎqwͯǎ;?78q[&Ip8,{nm)ʹֺ4֩ er#QRo$HJ&ᄯF7Rje(U4(tMkKM䕊Cխs#mRf"$ 2g4b( K7OUa]18em¸[OPyB=[A(\xo n,vhߋnc| /Jl4�ϸ$YX{MM<)eq[ضpi [g47o;͖y+VZ-lݱtgs:&-Zið)OV: jgrڧ=}iw?A9[H-̂ >K2k-HZL^He "yZNS@څ SGٿ2Ű:-XYZl੮W7(h4i33S�*/CZ <hAŇ5Yplk<!ġVHJ <VGo,Y Z+{P:{A;ȁBĠnb�y3̱r<:0)u="Q k VbHpVBipn ԭJxw*7Nv(�ΩƌmAh!w"kW큦 2b([HOo=ֲ9Qֻrgɳg<00 L2R MG!iDZBӊ͈nFFjt0&؎㲫\z{y[ks[J||{}WʙVk+_XXFڄUxoJRsVc44ipJE偱^7f^IMde$<c8 PZ8B�l<6GX՞kR͓P'mY+ܔaݧ_?߸*<[+xCg1J1 K(jM}[;ʷFm<`+sWϮqqKxyōP W149[Tq6>4@SZ$HuՕ˜:u``=l*T/^,+oɩ5Z@3*d63*0^amTYW/s{\YcY/_QP |"g{]Jv׉o'59/~V?c?6S<#c★j#<2`G>V Ѝɓ'y'ӧ >cLy=<#,..8 $�"Rp'{`]_~7fJ{-�sO3όY~hMFNJ޽{[n{4???1<vz7CJIӢ;|p'㤡AD" qgdgmmx�914Fu0-8+*c(hEzywN>օ&kQfa xWIxu]z0Ġ!6?ͺn߄}_demUx'^Oßf 0~~@7psR3`?@/`ըS-i">#q|sw冧ٻw~}s#VV+ ~Z}S,-`Am^ #(CJbn{isk{q |;,(422ŒN'0)SBU yO4\[<O.eQb_r톃8v(a .~gΝ`t~ļt"[ߗkŦܻyXga=<nEAP0 1lIǣ $z憠|]H> 6s,.¨*PuX옂}{ws ~v/`A)RM0]K~m9x#]-N$܅èd(,~�6O:70VihKȧ% (~a}~%6oxxr% R_,u 2 Et&{ҽ="Oxͳ-Gԃ[:6@FyLj{  c^*rѥ[{"֕ ƿfђ6|jڳtMUkhu=ڍY)מn͛L$%v040 l>p?27ڳ_'-[V<Wi|W 2S>c ^*�ZdR c ?_  )aoQí}7�^A`AiPF�X]{A=Bg;*9GT lGTq0unmB.$=QRʈ?{]˼2Z?UE};`z?̙3?~~!xuq^Gs?sfzSjEFMg3g~7>Urqvh+į=/_SӶљضtWn hc߷vKKKS:{l>ͣCN&F=(B3ͷʻp C|�'P* \�<ķ2Z$ QDܛ" #g\0z9Uc{^+&<ZW,zL ?ʨqT/E{Z+aG9M,YҢݤlfYh*Nf4й&DL{5/_ F2N}M0NF@Ҙ> xծE(#&XWmEYu{ˈJ%n; -?—Xev/}n\|C<aP~y._whi=ʈ[ܲIVèOB`.{^d0\)AnTpdu!Q((j )N؆txϽ>|obK{7<_Pʰ^SOS(XU]K>NiEA'a8 +379U�K.[ڶfp<׹蝗S6O|'f}9 Zp jYJTD|)<G! EղQ)hAS 6u 5 ,FZXdqi:wA{X^XZKY(Q` h-t-b夓0t066wxM`IifuU}⁶p\�ڌ%;a+ QT(egլ}Ł؞TEhLuA ISWs<ۯ Lqc <NjwN$255ԻzUNmJM$5c乂 qn~ncN:OkK 8Smzl? 8W5{..0Th< b⇦%jhk KK끧ʞϭIGX k{co_J(%oqRTRe@{"ϘwMғB?SdhFw{?{{tRWnK,f+i%kʏe:>wh2]n}KO<SO=?O??:th& ˧z__t\E>O?c>O|szk _k1]|I>~'~bb>9pQjrO8QǏ=+/;C;t?{_'�|ΟK7 dC߻tA~;/�=\r!qʳn^N[qe ~׾5Ο;ӧ+G<a>hK4b|�Qu!2 W,*Vk{(i; zhrCIŴv7].5-"\h%}߃"bT *J/Qg=(X5 >ƒ}[10A &Vjm;k^yU?1?Dнή{[%TA`eEJ�U3>8ہ3R}RϵٽNFݬ=q<;˞][Y( 8·Ws08z/.;7N ͹An7,=_`PT<F{g8߿[5,Bye0XzQU&?`  | PZa^caߞ=w#Ɩ;ohǕRw ^`� m_1uWF0x .X T$G!w&|0�5B<`saY(wx $ 0e,N�@++[RCl0v 4p$^x/Q� ? K{Xtu\|Yb`b)ˉܪ}׼+YiLז51<T"9"H;M:9"u<ŗ H|>ΰu^B}=FNuDy'O[Z5**8јw n9%�#Em`lk{foz+E4VIUԎ|jI@)cA9|ւf3YMSy1MDuT*$5,ƓGA=x1"xlIgY"(bۧ}G?q}>C8c=}/r?a=ʉB1ݖ�ukXεuFݶ7_J7S9~vc2 ʎ#?+yS|M_~V}9g|\h4[Xڳ̮ˬ\Ӭ{()fQ{IFXB \1 : 1T !_ wh*}᛫νܼ~D<�GMO?4>`- s'oocmNfVZ7R}{_ g>3 }?O;;<<䓵snwhvۃ~ҍ^`,޽{pOܹs߿g}UբYm!c7^0b%D_4uhWR*8Y.WL䆌 ޅ3j7!*\rVH ܜECNHVrT`Eơ$;꧃5Wc=ya;}t`{TZwF |y;MpټuƁ3<<4iף .y?Џy47a UG96HjR*$Bdؘ 0_^"JBj*gNY>t^1,/l̓}qνNCxkW#*nrY34/9 4N8C)xpY]}u�`Kz?b1Yu rW7^OÞnfTvW13/rw%�>dv\0xY译@xHƄ ʒ.*%_LJ:.u?Ӂ&�2Ă u&� &m^[VBZl7M߂EW(k'oJoAo�)-e83;hOr9JoYPFcx͢*]d]Uz+n`XN- d^y+И(4Qk<M(ɻGMYTyp>S"twSBSG}B�3ggJyZ x,mڐv-o Pcl0A؟ { * ڑҶ{5'`ZZm�`=0*Gֆa: k='r$XDŽO-S g{RW/-KɁבo2k{bmb? u6 s3>k@c;wq_z\tcD ZcPإT]䚃_&b݅�� �IDATֹ&(:X>(=,>X׾i϶95A>޸Hk ^Wֵ@U+EYʢ#3&T Qeek%q ]J()PJJqnCٷ)&~S*U=s gN)CyzŒ??ٟYǙ3g}Cxꩧx饗ԧ>n>^__n 56_IZ\\'sc{1ym];ﻡr'oIr_7fJw(w1| ^y~{ѷF`ٲrrIS^8y޹D lHs%6 Sh՟t憮 L]=m&F]x\F<*hB7Ex]b�<2SK!=C`x)=NxRw66&F4S`n^| nVe r.|tZ:3vIHV|�C=%4_Õ%/1:oޘerKaOah>˞\${eo2+ك0\A>F@}[6/}Y哖g_qpnvF0aO08EoӰz0x @:R7%X�>XXA5 ƸF 0o.Ioy9̜G8曧X.w7q~ٱ";ssst zk7nK>`ЍG(Fy #>*S SIaUQgPަעgXwUN-Vi<+b՚9�@ڒK5 챌ֲ[cקbKMƠ1{NFSnU= ƶGs U[QH#FFJ?t{ˆwe/Y[99 _yz/67j|"\Ysx}U<A/ q>mpk:]:ʤ0'a2$1}f< E> Vz2K*�tanGXn{>-|3ުڽ!'c+hk|C@\"�w n!{VFfB77lOV\lډRZUȬgkp`'OW<1x3AaҚ>Ni s)NZMcMվ׼_LA5V}ɟZxW|| -PL/Wc^3,ey>*cTWZgE%Y됭1|Q8k9sM+;{ӯ'Wqa%·4fUmUߧ1Ro]߰ƸW+VK`nz]-9b}cG>6C_]v1~o㧞z6իM/R -]z!wqرf;C;tP-enȩ'/}O~_}NMw5{ _�>,zkwc<<lΝ;7y_+d s֍0$RiM*3ƵΛхBd R C6BRh+ͼC84b5 ʸuVA[ 8VCQtU:ŋP@�3?x }NϡC)\95^}@mx}h4g D r�nd!^67gͧr39}/@t K\gxnjBW^M3q^ .sqðvUqթZa�(" <, |ŋ.]t;-RLt?.ЖP9 kNtko),A M6,ЃNʀǮwoBNVk}?Z ;tCFáX<]޺c^ 0]t:X;+dW"@7|"35JX[GYlO'[73<$vUƮޖi7-u׀emt{Xr׳IPr. A">F#TJQr!8,0hcpJ9Xxx[+Q%kVkҹ%[sٶ6[<jUgu &sc !xEAgK;[/ }ڪi.}ӽFLRW8{֋ctbt`47(>`:|٬kV{׭wQ>iG^M箧[h(iJs_J)5Dž(T6g=C3Q o6 eJwS<Mcɷ\KϵL6>vϞ=˩3g3_ua>5k1]͂ףezlO~mjIyn6 Wְe}3iKVg[}i6#T>ZʯE ZoQCpYʗ[X˗pr4;4VM3sݺԕgv/_\y_tUIw><z^g>?~fe{QͿ7|Mc={1W~{+1bg__I?O햰+++ok;C;9j_ )>E߮MMwiiin-Y7x#϶_8p }xOo-|{Z#tgNq5MҼ`1-J "X a+Xc" N˜; Z�b=YІ�q^6K+p̕+J75L]5dЩx Ptc[J{hFhʊJr9k`Jen<=Ym`F%imqJ]F%|9� .W.R5f 84e֪j%/;BW#AIkZƏ!ƣptƑB�|\2tt߂+PPV `Tu7 ]ގ}\9 ڠ9j/)hmPFR(CA;#V-Ŭ>ցTetI@_={ a_n27Xv=y秿)㶣GX/po>OуcҾVI ╡(> 8>t yzGq3K-ٚqZHNOƲ E/}m{]9+аdJ m$&r.+A)w#"Fۜr[8gQ3rg()^,~`<6Vi`IcJ{#=%p0k�AFuqnJ \-]C k+0nbIk`^uƫ-=GH [Enb D `6%R**k�BG?=+^;gi_k]i)e^L1M!D@)CX::|YZ6TK@cgoc6 ^q5ڽxM867&Ys*6Sb%*K[=ƦBsNz%!`1.|</Gv�e ؂OYuV )f;-翼\iWGq P1@ѣ<$wu{/.{Gk}Q^}њ//ۿ3Y׿u>Ϧߗ.]ڟr'hMă>n~~+}?u=rwhvhe#5ACsy;W^yv;%~y@*P/~[X'2Y5X x ex0JaHؽ)kbbF+صZN!R�-؀;80 HCtƸTޙ&\nUڳy*yrq ̄| V)$[ -%GJ eЄس-ur 3`*z=Ocݦcu`Kfx.FrySpK-[n]vr6ʳgj\]SSv-T΀8s0|K@Y=YOdvgW9uO`Wn9|`H*u,XfkxgdmP: 8!tJi<H9b5}5#2gī07wrn:Hߣk e˼u,Ɨ ߯}D%18'}�u=-$YFjC t~eeX˵�NyE}KpfBE�|R'@Ko$b+cS\ b1e lroBI%nH xJ'0R WD!ip }n=\a2̍{~ @T1Ԕh�pg8ADpĺIxjG8F01~JYE7)ꙉǍsX0$`e0(s(Y=NQIy`{tnb'M׳,3w=_OZUOh2bv\cj8<MnkK֛Z 9Մ{)ØS,Vb5S!Ǐ)kϕ�Y8kllrr1Ix5wC<T<Ol;hw=D Yߎ[Ru2(8L}>}%eYQ}Y^᝺Ma 6V,e~kt�_yc˃྽7xfxf oѣ d<y$??:sFGy}s)ꗿe>OKK3ӗn#=OԦ}Q>OT]~ꩧxꩧ??dwh#j?rC 6vmϧر 7B?/"~ӧO;;},//K/q?bҤ' |JrE�RQ~.i. UWqJ),.w!x4=y30_ʙXpc+Wy,)�6}Mh%B#%RoTJ)Q2 @Jў.Ai BeJ|[*' ,((.lɱ]Ѥ6EN%(lRN6sT`@g Rܙs~_鹠pgH*hB\kV]&K^bu{[)Moy`8\f{_ @g^|Ò#ͬq*YheZa= kC-zj[iYSwA oBҙiJy]3a)†_~t a:FatFݻ t qQ(ۨ:P)X_gQ 2Gx3l2M#S!mqDk!{|:QV*? .s2# 7HOc85TZaãER)1)f?nq-P<Q5kY&ri¤tϞ˲rΥx16�1A|ؔҀ e8%਀Z^ܨ`Q? ȀZl,붵9E")VO&Vf̖q㜄ƈubUy|9R)n6i}csIA3F>ՐQ @e)3@C3(R7[S_X?&_6lmnvwz\6,Eׁ1k\Eckrx)NJ=9G87[.)uP3[Ϻlyw2W2klFNko;ƻڵfli?i{V7yG?4??_{=گڶZ8qblO4Ǐu8v>svh^ȝ?{/Y~rIn;k֭?#?7@gsT|H -[z*0@G0mZ5WQUTB9Ѱg-{/PP(-Dd<tn4{1h ڡqA�Q*sU2!Yeۼ&scEJNT ^AlpcTGY`֔ΣGKqٕCG9RF;t!0PJ<Dk=`v"4AF)&`^9Y\_[di-ܴ*\m<g3skoz!X*/V/\̛oecE_eqtT]5betŒ*G +A/J k7KCemcbAmeY2c\_uCQ 4t%.'\ȰQNr]=v<\|y˿ϤPFq9AH/l0 A↮k-A$nc4 54R8^F0ZEޣ5!z= )g->|Z|J5.^ Fz5h4ݧuByh9!UZC_B#^<NbsYXU:xS3VO8M/#ŕ&Ʋx zShUKgi]_ & ζx<H:TeM*s tOBIm޾JTpn(9Q0`)hm.rœ  $G /APL ҧ$Egs l6vƶgG?Ws։EܜUƠ:=||j?Q5j_.,SQEHI®fz8N)uA{ۼ{m3K^vl|foSxZ=W MR1Kf ,t4gYX2/jcD^it 9}8b(Dͽӄ;D9 p>vF9Z!a_ʞ~Wvpح#{a++6Bt| Q:Uu]|Sx~8uTp o~OOxg/}=rHU_ZZ{1CO|V؀hZr@lwu~~ z~'2Y=z5͍׿;wR95w} x;x駹pBC]<whvh:w|tDHw%S׮￟e�92vii)=?77ZyGx9{,.]J=ʱc&y؎;#ًfLpUbE Spt9aT;Ewk=ByU^Zd＀K]:9J$V F 9*P%$IUDemc6y1iXj Dj)E_yũA�< @%JC[a{*|l8uӭ2&y25ݻsy&WIkmoG`Det82w0;B9~b [b[ `IU.sL?ЏaxPovwu%fF9d kvU^\2& 5XX-no)cå (mBP~2ƿXyiKThaծĂv0GT€3!uByKhӰW:KEtȣ'O;;-,^{Xf~�� �IDATe~~n+� { Wt$` `T:g<_޾u9TiY҆ugȿQXN.Rg{]rjI}֛Ӟu"DRUZɻqzU>5eI|oK r֣IM/6.y?U @[r;/-n&22}@I?;ZL߬T*|&V\ZOѐCY�.E>3_t�OfZަ'x=JZv<ΟY� ƞok\coiiϷ=`_JṠ"<!DziĶQ]U}Y329zD%rп{NeB򱒖X^/4DA5΃(H:FWyc7({%�U)d 9k8_{S6:uN惆HsN+}̀AyAx̀{oje{ k&�ۤC%7Omnw׫׬F{9Mr150'رcc>ro(fׇ~CޡڡjÇOu}ݷ9}5Yh7F) 3*@N,Kű" 9ha60( p"�. *XXoKvEx>/g `!nxWCԤ4Yݲ#ydT©<lLʞQrx0x6+pN0]YF byA;gA{r& b>S+ם Jc߼^ƄzVYCzy/ks\)2XiUD: jW gĵ[h; `SZ}<O@{1/ ;W5dFwjWtH*ufṢ?0H2jB{O|uydypぃKzшpuQ#FMŘbya΀(N | s7*ި)S`<0 7`v!Ї? ĪspyKz!!PڰҲaJP.|0ǰp\žG#x >Cv`@6|',v+}xOeRTU?$O]9jt WǬD+L+"P$3RFZsN(MY }i(UU C_ c>59G{ RTͯ{R87 R36/F{Z:3m QoNdc1l3TOɢ-nu�r./{9|M}V{xWpMAټOi~'y˧V}}G�ET՘)s˳i+ύ 35Q@MW5 t2B qC< Px6vvX]Yj!sHȑVe)T=] 7O0J):x1`8…sAyQFh5y+dT: xx{(,bE0R,M2|:lL(!t]�ϝ^y~=нƮo*%~q&_-y5F髧׮* C宊|V0vvhvhvh T!vl sV1 ^(jhz(E.>:v9x�~ ρwt]ءǖb7t<i.^8g.8warnL >04@JF!LG03Mڣy[ xr75]*qU[J?-;z",ŁGSA&~B5HuXUmn2ǾG0{8ri ֧׳Hiqc�(UQQ+O{2( )z͚g0C a7^1oGSIǖK_w vRp; En3]k0Ug-\9b!"8K\T\  N]c={yW%|W)ͨ1 k`J+1d 1Y|]:f?3IòK6lQׄ0PUq9k {X+ؒA2k/> cX'.*y Ņ�z/kr ѪOeT9ر>v}*=y=q̆O1]A&<px8:Ʃʾ\T_[Nj)=_myml$,;ǩS:3i)1X(Jw̪a&m*.ՠ晱͢,W<h '(6ʀ<)C! ˬm4MK .ߚ'ڐkߠz?vqs F#VWQZ`ώEG`w-F9"~ڪAuҼCL伴 }m_5Hy!0n{SnjLN7Io5,.1CȀџăKZ*|6^!7{}y睬֣_Ck!|QC\$Qމ7(]BR\<`6}8ΓL|I'&�d_8ӿjevG/\3ܷXoCнy+^N߼z}Cݻ7}'1 ǏOvmU}+R`5QIʸx5R ԕO#tbAPhќ]-JtAi$�H& - !Wr"c;'vK3vLe�" q`Y xO1vkdD~iy H+Lb))ɜ t( {ܔ5Ĝڢzg"?>Tqc4ڠq(ZTHJP#jSh&X n�VNXXNMּ'\V#X }iK.Bw YnYKegyGcisVjC@$dMK8DX00pC_#}}ͮ/<*&KU\*e^C+ݠ$~ϊnbŒo.}-PF2¨"4m� J/51bn K R0p흼W)o>C4rP2 V$l\֔C[:pgm+駈8>ǀU?5DÍls<dc)L~70Gm9{BPV哐ub*{6zyqVa$$ c#) Q)]vV _VI|YWF%j[p.҉ڽꙦأiޅ/l^OަZ Z82鼬v0P+#g^/g^o8S"ʗ5GOV1c HUW)KCx=�RRש8]3Jq%._$.P&ىe3ڣizmS2g@:xpmƨq8jp6xzmfEڿXY=K;s6(P䰲p8ڲRhb|I&@֢B#9PJΝ;h$nJ/a+<}v ';oX《_V@dۚϩT^Klz|7h< ;Wηko=t҆�ٛ;wM{q`Wx!#yܡ}㏧??ѣGyۿ8҇>-lޡڡڡTMk@O7JΜ `Tu%-D4L+<wN,c6rtM̿Vwa^UiFj:sĎȬIbav$qp &G[dq*; )P l9"E7s.XⱸԯQ0V1*EF 0u. �M`#2;DRџߓo8eES sz[9 Tj1l2]cȭ/$6RATcDiJz8U{.Ӱ }_u {Y$2(3D[@bﺖGo@  _1Y YQ�ʰRN9<X. (Zog+=fȢ~hSDLruJ,tWKH0(kH}0�?pM:-RU[%`yhq K|텳x:ne 4=t] GCsH,IO`-b8y@?>�bvA$0&oB1�'ם 9X6{ 8,|%yNH۳o9Rqų(!"oy>Eҝb<v�욟c0Q(:FIPKpڲId̨qXM-9n0�މ̣>+> )Dү?%܍`n2=2;�RsIwr`a0 E/֋>TmTF[S51f뼬$J[z0c^U>Tn-TY65RE p,MGٹ&|T ZL.9hK-U&Oa7Dcq!ݤljۡӝc03=Jw*R52o Mex7 a(MJsА 4P%9٤E8Y=s�"ЖQ diFEeTBx=9Y2/:p=mu]~iPC'y$ȋT(mcYKJ*#<xUKR=kc]ի u&\dj>IR=^۟RFi٨Wg/([|o3^<)?uk`'V7WǮ߾?}}+%v_2ۨt/m_C.=|ScXvI|=Ը;C;C;FuiPM3o \%0BQ)0Z L](29Q`F}0T(oq.Tüà`}:8epe J9+ TiVOb<ISE46!gu$y~{Uk<:ޚ0Hni2:p^ sS+wX0''ȵt vJ|a nA\ xbJ"Ɗ @pBIil:F$(T٬PVe8%(Ga8=f}Dkk&R9&_=(mjţZ 9mCX40?2,Bgfzy8+ H,  ܷ <Ȯ,y@ :يIYi'Y /XvNk^3B@!p[wp.05W啥?ZP, hK'^eڿ`;w;OS)F#YwxMs,˭[CP߂�Ѫ[dmuD7A_Pw9?~MFJ[ƹ^<X5[g){0ץ0]ME3d({ޡqFjmR:/xkk0,B:yNF(P�"3a,yF7X^/`{u2FI0G[Yk5qNrF-LvJIX{n={$V0 hgeC;tcCZx�:ޅ*MAym̿dz]S8iy-g7z^NNps5V^hfpPG*Qcwؚ"*\Z?'OjRP[ٳ$S6r$b.jGe;,cmcІ<mUCMݺ2�ͭE椊heX % QT:Fͦސk6deYm1땕/[قxUaRY^>&4t5+xU'*LTm_b;N6B0n%_ZΙZi(';gqeeDs6 5Yo^eNUZA&ީ+Jρs@|:hM{PbP^'UVƎ]kU0m.^=Hw2O|Li[*_|c<Fߐر7rӞt,n @ېyz!>sq…z%|A|vhvhMEHfJZU gd&t~' "+/nGks&JDc8)JaTS+l&" f!@$Xy j"o\7}5v]Z8jLQ*7FMZbxE3ƠM9fY!}'>v( y6_w_];S\77Fu!H\ Y86XBo1h V�{s7%;~kULwp{,j e4c E$R" p$ȋc 8 DȊDP$MTs۷t=TZ_ZVծ>{yIqSja-a9  p֛ʿK@L ̹ }Ph뗽7 ߅'!q5~}kΥ1D{ 0zئn83j߁K-Dy1Tנ;y v/z7ʹ8Wܹ¹8u2';Kq}D Oy ػzvJOo`2s6a!+rL]3ͨj*M`PC*�D X`r0a(B[q<KsL=crj+r.#6Jt>` e4T@j2kQ+hBUZԦ~C,@:xAAP__9T%z0Sm:vGZ`r6qnUJ.R�\`zh6e^m ZT5m?A CףK>Z{߷A-uji�A}m1vvd(pbݽ)߾v"qֲxA/P{|V.)͵S`bNk o#/W*[õ<ϬXF.)3.jqx{~`7uv#Y{в9̶rUP61Zÿ�H(sU{2@^VSNh[VVE3-WkgZ@hҩ9-dUPV.VΟscStoW86v2Y} tWWT=Mc Öi^i'/?g]ݵ~}.]ėe/5iMk)R Zhd9t!^flG6h+]*bjw׊&ZjbG hjPd(4Kcp"DPG/-aTKXۘj)7L1]bL͛7NgDAQZ4#AXcCմ /mt_nlE+lm3Zk,k@>aܲ> [l-[s-YdhdQ AiAiR"3[h{T;"ق7h M" kbkch4 :Y* T�r1=83çqfxW>ĹGyl&^;UrT|Bq�l9Z'PNV*C5'n09!|Ba8z=A_O~x˗ʒ,μ 6>U%uUR x{�� �IDAT2tEE+~Z孳qߠ}P}�涷]k%j(hA!ػC~m[\-'%3  =s}\}W-޿)xO8$] `\|NͦPͼ2oUH 9d9}`0&P}�ʻ* O5ܭL99݀`{7o8\ @FJn:|,g01b]LFp gkVpDŬTPnE η'IؐOB{] 0u#xɓJ" E�Ȅڤ*i\RS7c0gNXخ ߈a]k4h՚b:v,:!Է- 7'ڎ5+<,:9(`{\jsE` IF]<0ԡ)B>at DYNg.^ D:.ߞ(/Phbq*- T/ӱSoZHRw>o߾we}~pPߓrn\=󹧶Dܳwf ugGű;ޛ^Ӛִ5iM?HXc0Zyas=xǘ($t�f~ɔ S�95VΝ�2�lQNHߢf2(lO[#-Ńփu]SU5EQ4�z2S%Z:p YD i*Nا}Zt> ,EyK[-koDi4dN4-V)j=@ɘVx:z"ipLy]p ـR_ _w;?yƷ@`|8hZ~lIC~6gpfHR[)0%T7!א=.=A`�1Εq] 5pdj뮵CZՔy;sؙR=f0;tq݆dM%~ qV#y]~g]um%<kNB%Ny uZk&y3 ":cXQ j1js֤40�evd  4{Bܭ> [v^~o̻1LәEcj9fC| \ss7W߭{<W>ݽAQ8kW=U0ȳ̝iPΠ.3g_ܫeha}}x ̼ F4~:`UCIxk?otY{`/�_yENLKQph\h9fu56}c%Im)߲;A 4iҬSr �٭[7cL6\>I*߻w& 0Ǚ8]+ۓ,�ߥ>Mc݆ӦEVҟ9} iuVg.E+Ck-?iGz)w79u?|4:}JxP\];w-^ȩwWneQ^ /ĥ]0~+ÿioRh ";9]VOw ]ַ^]oّ=Tk]U꾀;U|yվ%r;k`2RRK.A= {slNt 6G ~oۧ^wھnhzzMkZӚִ5hQ:$YI/"C/Ļ+X+$Xc(_K� w(Ѕ{jL4ޚWamK`+۠,T4~>"ˤ!0$㖠VoUMU0Ƣ%i…ʰ"^ Xɸ3Wj Q~St *D)c{ҥ1h,ViΖZrQNkJ6 a0zF ;Il yY{Lc�{/\oI8{cY{V mu)欏{Ih X >ToB7w+c5QsP #Šh 0:�L�nUt/kΡ` esR3os1O[G+@:iŵ+h}6668ؿ1t#QvqʣfUU.nyvlI7g9\&;>&o 8Oaͪڸ >䩸~iTnJE7!7=~LהA2D:.2Ny흷y>[G.?}bsķyx5>3t2֠d eγ)%!nBбEcQ{G6JvH>o ۖTQR?Y Zu%ǥ70lbZ]>+KY\\~Qx{Ǎ&&[ b}ugLz0_59{BL> @qF/+v_fmʋk%c*h&习^w6t\n7y{i.5"z -j'`PPΜ"DeHmK()s4B' ƽZ<jg5l�1?�$Kkޅ$jl /5 2:d\]/ Ik,<ϙL&T#];fr\w/ ^S፼_\RdޅlZxV~h^E9էG�=;o�|;7+oĿ>+?َ驗\kZӚִ5)!�a`FS]+˒<;gQI8=bw:v؃RJh!'.x7�UR 7_! @},yK`}jiY5wk}jf#M<�@f*@:RvBTp�PG)ո`N) \= " ڪ¶ k5V; VՖa,PZxJD2@h̾?q( *$@6.f*ԇL@?, E.f(+c`c�<B[h _#esz=G'[xm(6An' qq8F4q1PKWM~ׄ( X^!uMC6p@YّG,/q>P <pTUI(ȪN~U%ټpmj7\3mAEaʷ/>rs*g-S( u2 Ck_kB%Q0L}6Hx%} yw>}_ywpƴ (0JXt{h5 w7v_S@c=U0iHfc W-/A][,oMFLYEi˺Z9/NA-2r1<Z u,ŷ*T˼cwe]*#4w;5739Fi -ka?0YV<zEi;M~OT)Zx2 NAnq:i"NTn[|b.}uqK�ؚ"޴@GO4NU)\"PJNU{~_"Pߧǡ՗G)o'WRJ~'BW;m a0WIlς=#Vf}0a"еol|FA">4)K( 6Xkqrk'&Uj%ak]̥ I@5澘ka14SozΦ: Mu,(� �ѻA|d>{y~o?~k\ϹS:3wՏoM+Wstlۓִ5iMkѠ2!Tb)+> g-�E 01Q#ojg} (DeX J_UY2]$X p$,Ki3F0+0Aeo[+ N"0ֶjA2@z ,J=\ ]a4̢J=[H$[΂[@g2H+j-TJwާVP0xfH%]mӱo%֚ !S{&10Q`1Am SPAKcz+Nj]m(tf6sֶkmj,e?l>m� 0>V`b@YW;0}}w* S@-}TEಷ{ޝg7 J5⍫SqS0U]h\^(˒b8 eiP*i+'Wr5,Ճ7x "&  3t2eV 7b,GYuGVmb1-D_do cǞ@kEY'lo :C|3sx C`{ce]lppIa&5v #s#̃썒 >?ۙk"Υ6ΥsFBu3q9鲈iks R©/O f Jʺ{G z5sJ<0 .}JU KIb(ӦTO-w+`ȱʃMp  kWnE2вk [t V'bΞӷIAIN pnI(;*$gg>i,bdkJK#C %b"\VK˭ol ӟ"?S9 ߵ:y9{:y>4泝fUո3v�[/VXGuh�Uevޮ >U3= @ǎyWws@ؔ{@;^Y+Ӌ7f;㑳Nd)ģr?woM<@_b4^ִ5iMkiuLAk[8:8){LLI"#o4euƻV bQ.x /4VY$u%i8x&~֢as"hKt+.BP' Z:U!R>-ڮ>u ¬P8f}>t ^( !ܛVdg;`7_~Op3?WZ)oL6W|ι)(�SGa{ʩ19 FV [rڻ| ^ 2s30B6<Mtu:7E3[izӗ`t@{&O%(o:ik×m`6밯 {6 aILb3Pgeɷ6*4TN.˸M9oGC8wkϦPlw5/&<txeYEejTm)lX@)AUp8; @g/ 3+|D}f|l:! 9ػ͛;m̈́P׻r+QpDō]-;s1>?L?8$Y(KˠܘNr؝�/o~闹|αw8+SΝaleC7�;nb:SEP¸:$JThIgBu%]P,TWah]О2m08A9S07⑱r."ל"OC>(pb|W,\65 �+:(s Yr^h/6İC;HtH+լpV= f_D(X{ini|_](ZV>If\B#yT9 9V/^l٪Yܤ@~΢|"4}* N)t 2TSh+QF=69{F-_ӭ_n's uUS J; dmNkg]* W2 ˻@ m,x/G +�$zwLֽޑ􋔗V+pzt9bQ$ͳXrmbX> 5 7.?\=H՝7(e֗3X1n(~^Wrk.3wmKi}ou)>rv;WV᯿5>qy7.M+wySӦ{kZӚִ5pa^Pͥ:C F1̦%Zu 2sDGaP +Ź.B|;^ZQ - xQLPHw/-P1j*+؆!ŘIg/>bFaIZ(qv BT"Dy6jw%;cHhEiBvJ9)I[}b'ei1Za&aL<sjexi-oɟW\cyFgȸ|ɷW9ȶacB==bTB'2{G - @m30{hmILB(`g=XΡ�*P{7}:?Yۙ.v) ~jBCV*poY:Z_ jb̚muc9Xp/jn9`\~Qvg v.SpPޘ!bo7nh7:s  d倚(2DY W4�`KfF[6HSԘЅru%<dʲFփS* > K_շQL];rk .?Vp8"S]Fi!lvZ:A`pE<BWe JE[SyP;.uMI<E)P-(Qs�*K׿�F$SfA%aA/9ԜrDЗwN=GXУ[չ .lG#XD3\x5OЍRsG@T5ظ@[<A;2SHz+*UnAZWnC�/+:8j˝񐅊ŽЗio+\hI<�I8-k,Gx `lS,%# w}{f(-UhU8u1БU@ϮQ[`wJ^VE ]`)9t㲗za'iT|^OilI1cT9O[k) 8{".C:ǭ1sua(C3G)sw6i`tty'^d5|_xnm7?NwMB??t{Q^Ӛִ5iMk:MӴ_nŠ8}W?řmGwoD8~H ֔6pCL]hYUY4 FEQ (j0*M`@_% LD@U~mg%8%yLS`-r X FG^&Aʷ+ ],& rw>kxa*V?m% > Nt5(Yi?rSJKgیR`%&`PY.f<§֚_/},(YC_]lda薚.@j m5쀺 ĻIԳm>䁮=+Eʻ3px CA~zFa6m=llŹDn9\=ev;>Q� ؄₟p7a|䆋:x Njq?-꿴81@  {E/\<ϸkp>{{{ﳿӪt2 ɘ4(` ?p1Є.ay}/L%Xios˛rKgθo`l{3Nv4ĹgܸKo^e2gB>eY3Ag k,y~lA CpIE/9ٳ<x)6vP_HAd\Ҏt&�{kQʁKQ�� �IDAT;>L~Hp9#Dp 8Ws.5_&ل^A+�$+%U/y( kJUO({vH *q �Br?wprbMhUψLg.g~k_/uVJ1B?$?ݳeX4c k>pl؍Sؤ%=Y{]f-B:}x\`2ф%$Ҕ=_#V8Սcȳ Ņ{kBnNKʸwޣu[d(->pztUhϴ$W.-27rId}v<7iW:t?"n'VpH+pEX5A{#sWjw$"` ߧZi~yJ,C <޵u>b(rsΘO_mB#-?}ֱ�{!{v˨Ƨ.ǬkZӚִ5iM Ivg)ߥc`ivn6eΟ; "[N3;p?IH; 6W56zS!T˨=gx~Xuj) Ռ`mUB=P_زZUs9̑cLuAf$ ̷ӷ gC^^H<Dߏ~lmc[cm0?3N\ڟ�|BTT([tVVmYs=ŀw+^./鋈RmB*fֹʼc4Лy¦$*AP3tX {>Kbqm&z�`}ң ;&Cb,ӑ6m)"XW[?|˧rm cv?Ro+=tL9- Nx]"U+Wۿ iYR+gN4&4cΫv%J�\ CFŀ(Flm<#ddYFlmmeuƙ3h\Z2Ye: y}A`FgCjݾūo]7w2vEv˒\eD|T!#;[fQ˯2,䙳DW3^~5�}Fhj2%(LۛNg9Ƞw|}8r1H2H@y LoQߨ{h*= 1|3QMq1X}{aEBUȥ}Isz_S3:'J5tɷI-" `w־x:!^ݜIJyPy$2Ws=4xmRPc@C 30XN^1{gSdiKV}siSA|K#3 6wn^'$wr4̗TՎp쬯ЗGZ];)+s,:bw�Z�L]/~ <YK;~QBgd`k nHܸo qp}g|?ؤȽ�d:Mdk$nQǘW>H?Vv. dKr'bc{F38)XNtݥgDG=eHI^16c_Ecy "řUjJ߆vyо5.J^gZꯕƈ=ɇS׼Kә9-С,J*zv}fGh ۇ z~3sOm7�KOl5nݠoxƌg/'S/kZӚִ5%NU\" MQJ5Lܺ_eW�#!^q%!* 7pA&xWǍ%ˈz�1K,w஼,zbni~л_VyF6Մ] %4ƺo u 22Cnr@ &mM@ٴM>u9פC,FP н σ6BM,Byd"(Fi޹zouy^{{?hc60<oPenr% Bz7^#p< ֡ojo Lp\[f[*l^:RIp87(r!@\Uȇh+ |c [rnWx \X2'sԽ;KvAq(G87~*޺~{NԤނ!ZUe `{{hMΝ;[B1(6:3dYAkTrW28gU`uj J=Є&Sv^�" ky0=E￈n?g9;*`|2D4F4,EQ&L ?<61.ʊl8{7Y5ڀpL(yp$kzи,`؟G|ۂ3P21{*מ"L|'x^=i$ܰ%VZ&GZ!x7wx>0/(WmD :iM ;Czw O;2@9/xtFsCw7ƅ6@Fk*ܽ}kڵXDVl) CiF{yi顶]tєߗIȴKc="x]4tJcV2M)B{qE.1S7 k> CAY%bnjƝցݙpo!T潧i$2eגΒ@ hQIͽkSW"PLyXYZ'}g_]%4֐o6I犝&fthQn*T}c(yhEmz0ZK=!;Lܣ]P=uu4d>\B Nduϛxݼ@sX[={n#hg1w:vUO49C^1;rk 5iMkZӚԦ6TB 7{{ ST.9j1, @\�7r}m:vn RF8n1){ TNPu9NiH�]C%v*y�(wKֶ4˾]fi&$XGRmq#uv["/cLK( Sn1(*k[Wv~as} ^~<׈>ddp27w 5q S/ϟ$ Sf?AW?[.3vjIi^UPJN'o=Z=6,q! C|S+|wWmƀ´.D 2&�rxiƀmF=[;lnnQd<xv<'3F#`μEe@ S)|Q`l1Sj)&1'm�MUoj~xTUE]k%m7̊2d9TF?J9;pw/˽"PJХ|*.?W^}7eځzxʋ(fqTu]/&k`6ut'-FR:+0|  P0Hq^ۚ�ݚa�n~T\-P;ݷL8 ts JP'I7lljԣFEw}OZZEkB(& o&">oY ]tYYF�r<{"h/ܷqw�")#�[ˊi(8+ڿJ^o#O|AA)ӱX}T)2-NE�Y'XۂF n-- DogCZO9-sȣ./W_q)}mMLYw/u}{⾐}|o~vg=y$Ca`ay6vvcuTi`5Kppx/Jm ٬Q0lz蕨 KȳRO@vݍG_}Jm|V[}ruX\%?]> ;IP_Ӛִ5iMǡ+0Z(_L@x1`lBQUHge,41|/Kyfm^XHm[)Mdhu"-otjB;v`[Զxw 'ǘߥ̻(_=b6O2a BE:CkE]-aM~ikY(iT#�2l_N'dZ0JQ[7|ȳ<> ;I^^D൓!$m9;?ZwB�0s֖ٜޥ ?x3r�01L߅!pX-r U~-5S nó-.@,:]Vo h,)5|m^8)g!=0=EF=v3K+p8Dk-2A<-JjV2u0ÔdRu9c1b  VZ3w%%e5O>5Kݳ= hny>s<o۷hLkkxq.?$VjSo°A1ܺ~":gRsM}ǝ# o|_Y #9  K4Pyz*i^ u8n}.EZS@XP{))סqQIg\%OP#`2߆�`Zjt9Ÿ6ؾIpy  "}σRw sZiciw~%`PݪkDKe&+׺c#S1hY5ƭM]p$|ov$NܹyOTkrQ)Z1_;t37uT7wNy+#iO`Yw㳻5?/kt~pumblԳsR3x^}ܼ>yc27q/ry/rֱJkZnF/a{qiZuܥ=d/lfzޗ^o -~\ٿ@qKۧ^\kZӚִ5MZx! hFZ2Dgƙ9Fࠃy~"Znsukrj0n"Pi{f% 2Ps̠ҞP`YٵL I>WչDȐ LO_ދ,Qg.F/[iץHE=@o̷P]6=*d,)ږɻ-�n6._-&2N�kx7{?XQjHvq-dљPva7R+"0oݒcuA�ʛH#ܯ�`0y- fp:loR"?V hu4 -dl-r2Cns\<ߝMxGy<胜Ʌs9yl kTYDCL]5eYR%u-L&D,ujuwQ1`"QJ0&:.4:D%PSts{&-UP.UޚR�?OgnL'SѦ3&~,Lx2A3%xȘL,ѧg~?HP6UG ,nJMg+]3K͉W}"| ?o}r e~-(%~Ľp14Cz�%+P-39+S!k7mmz7HZ).9w?~tm 1Hs%tUrtdD' Z,.>hKҫ>Ws }M.&yK .Zc#C<S`I\Th5L9EB6`}i1\?{%YZQ{&}N59ĎC}4`Po(5VA4_b5B}.J/EƭK0V}]e?˂ҋ[}^Kn߫~Y .\C} A:>_e;ws֫繐錝3x?pck"ٽy\ w3LiY\;bHs(yʉd-Pd/mpWZO<\Cg[~V^YzlaMkZӚִ5C:ێ@H* @*>Vy`0�Ҹ'LS`$(\ ϵ  {Y}iNheY#lꛤ[&g!-P3K"铔볚 \RlUm]>ڦZÂ=�)˧-Vr7kdb7;W/_@Hsy5$SƇ>djA8I~.%4 J[Jܽ|{-gg&0܆ 3xy #3^>*^j`tf7�^0`_1qQW}C Y YVW_K?<}͜_}U~4;K|~O< 66*R P%Z2Z)UU3fLʲd:-k(t^qUM4p�4Nr%{P=_TMF$*sۦ_#ZĭSLb/մ"jz0<(fVr]VTF,f:l5Lc.?(o}|?+F" jc` A�Yi5pi-aX psPxZ߰T;la�zy?DiȜѠ4K(<i,Ii!5Q}B4wF*͎ܾ޴/;ja[Om1> Hpn95.xh΂Ύq`w *Sv/]wi^i.O<j~0}s.5y5ƯTǗQľU 2;E|yhQk9aJ}ـ\M!Vmuš*7o2Xеa"}נRMZ#�NHݿV`z"[vWΜeiO)-T}`bc5"ZanI҇'M] iK I_ 2�[ʲx‰o}+L\0׹1y5̪<b1Bascѐ<k,J}kEEVKh+*E;5l]&ZLO2FepG)NtOߎl N+޹+͏`O]1pzMk_W[j5iMkQEW׺%qqF BڑeR Fxo~KٿeF"|P+[ 'u󀍊|}yr#|uhIo!fm� l �ig JyLw!v:7eк‚A�A?=sqB>LŠq3ցa| 㭺#^%!`F`修?yXQ2Be`w,?`]=iSVh@%Z[W-8 y &~kq:8m\}q٠[lإsRn](^œ$ƷUG;޽p0>$9�Д xswE`s WS_7)tMa+t9qkXTeN1ְ[1LJf34 ,'J9K%敭56'-)7a4rnkgy99 FԓCn|3Vg<RWӬ!R <gK8~~}RlVn O{?~8a5T[$90 _8wq wԑ} <[;(�� �IDATsGC7k2oAꄥ*u  @8ZS+ (obkg}hWVJM! tJ{҂{iW$i^8)#X,k)�L_Qnb#""SAv~x<8O ]k\Mu(h2$S`倝08/)^FW&iu"ZLѽ-"b%:FxT4w%4|~(N:I\)_dcOs,nE:nn {ܭg# 3W fCndz0[;X#b| DJ=(b<qǍ/<?Xfr?}>qvptD_}S/tqg}NO?3F|〽ru7ғ~x}5o�Z1mNWpli ״5GLJaX BjP ;w!]\<(Ky\ⓑyKv�]F0'sm }D!*tI\jYԣ[p~okm9W/z]�zYcviC>>ad7ŝu Z)h0AibJϗl׾Mn\o֯k;�J_l?#{ؿ]\ST*8+67ε,ga-vP$`( /d3x1p:P{y p2NYQCuǺ|gib(Z.7m?Kc ̾1)Ƈ0KE{N@}i�§9͍7Âhu$px54h 3@7۝Z/$"7^-iŲ="F?<y>dV^AlvΠϠxU -ςDīP芤wKoD!Xn~Sܟ"**g0O ge8rPtgѳVG}E{U%4w[2\8wO}S[ޥp`A&-& m^8OA},ލ(UЃj7�vp}*)jڰXeeڎ@;ݘac1k܏_u' }zb_5Օ(K@EK11f1WJ( h ;X"8(=F1DkȲɻlM(  |kF @9~3;r@Mw|Pa>-Uu3V2>t+s{uN&k[TgzhW6FmqI։R5h,x n@a߹s4)(vW/\髍 ^0޿Lgxxpk2gzI+נ#%rϿGZQNΘ=쫯u/o+owWskţgkZ8`å4#~eIiŤ1Ɛ]ӚcCǻ (=쪐kT&%0 MJtM !T)h*x(iLC&ץ.&O8u]͵r6Gk<ukYƠMouܪxT<?N!SL[* B&-.]%A"D  `]pL2^K\?g/|o'(%b:Ws_ی@=gp:\%Jt#{ ڳ] v7[Nwj%Ӳy t<?A=X=EhzsuM8 vY&õd2cְunN}50_d&7#2LLGra_sM< |JҪqew'4e9Eb0`0RZixhlHy6Iqi4C.?$>ob.+y%m:{Kl̰HDh(V`,w?xKjV kEZ㣬]wȋWFҌX)<WJa<ؒN]Vjߴ$ayb 61f`ze`i&z" ttt0,f:�7,cCX0H*Y$Tz]2ϙ?Β'o*U"޽y3Ϟ~osKpRiKC<{S}U(/C۫u)% Z7;Wqd70%d)?{1Q  4=T<nxeo}ȖBbm 'wj{~8b9dp �X2y VՕQKÆ7bx0z2kykYk^+ ]~ +1`8°J{?\bVEN`!_ oI鯝|4HkB"u8i.^#,/hł4ݬ#oQG{8m$pBnR }m7Q[^W;q.ҵ$yyl>9 FcaviۡZ+!LVN#eL*$`U&�5`^;C<j(<[T}"<teKZ+n{1hcUS^VTj0bke<(6b>>ϐQx.�%$'!ӆ?cfg_ُp-7U^fr?,{&Rʑ(L{`u#mlN 2s4*E:׏ߘE;�АaBm毻rn-ݭS&'x.}0K`fAM7cF:׃,Nԛ" /)>?E7CleYWfN&h$F[S=P (@(bN8EؽJgRz۠ŶgvFf&`ߺsVASLɢ!ͷ��eH_>ameFj #֣=_,y%i![*G0`k饪F�ʘ|6d� |Y^Y!18C1Itt[5Gw 2'[}_6BF4b@;D/D ,^ϪCj!qmd[k@m{積@3j{ig\{cK q 7A,֍0p1%;0Oa,絭l�bsEAArҴs 7 l`Ke䰺ͿɆ oG0 b~{C銡V [_\F_:@jF@_VWnɛbw}M4 ͥ|v�&uVбNй!\j1,;q)_kkx&\1]~_P,yR' ~UK^V(mߴK-Bb#I$)] fi[Ut:Df)KK=@(Ch(Aqr4  <핑>2UEس i4Aʐb 5�*t^Qr0U:Fտ%0Dum+NYTUJ;D p:b)c{PP2GW<yԗM'wp|ckF> <B++,1$zeDW~Ks{5ևP=L{L`ΫOxi|̫ i,(9ȦQDZ'G 9�cerg0RH gLj;d3N߅s`Vutךּ4c^h\a-1ԙI~x}<ssst8" Md a n ;ܞeb˅ 9Σ0X°‹/<MҜEfX;Ğ+WJgc~wGʰ|yր>g33R6J,+q]^]c;ۈKeKJ!$-.HwvʍF+\=OizWsBB ">Qo*<~mp'0Ó{('qƆ9W r|~Qxoݗr7n E>fc Vgȳ~K+*T17YJ}&=kf=Ѹa}*nu3|ivH&w*AȠޯi""nQJ[Iq]aBŸfbκx-rZ$6[n~MF#3ρ1u2o5:72][o.hzzO=2K6tj[ݥ]V$뱶@&I&Ys9ͲU>㌍$[إ]ڥ]?0<EHt4I9-Zc[Z[ITd)U GqPdJTc͒c OgU^R�bRhӘAŎ8ahrYG+>Ton4V]D|gB/nW<  de'c[!<X >ZPeZ+1RL:4E% y]hAF0I|(SO5Lֹ'WVVywI~~F^YϴiƧ3 SAӸ{W (!j_̐pcʠ=VȚрtWlĈg6KqXo޵98o^\h<7ǭPY' @OBʧl6)~Cefq,CDj{71j-P+�O벾#et7Y^dU>#Wb:/B. 6ILzJ ¶ڊ=p0$&=2EJ^y]:7{S9p&n=2qR;^J J)])B6e $FDmc F(! P~cRD<GUmXڏ@U [1]Z(oTx!kɓ_QTS~a,`\7,dsARV.b@ DJ=meUk.]n՘WDc`A]I嫕!cU淺zʷ Qi Baoap܂+MR2;R&Š=o%9u gQ|pK imq 7pTL� &c}E_T$ZXVtsd;Kcc=xYx>4d|1Ŗ `̐ =]]Wh)$JM(ɬ J\2ΰ\ $! fi7}K{AB;gO_R^O| r(Z+|@{9Gm!D)8^j_b|A)E+F+ѥ5o+-~RݵҾR @u<`x\ .�o;Ԣ*ձVVCoT+OO=Nb~~ۑ#GЇ>@_:=Nin6k]r[>Yv ;+++\tq޽{IӔ={h4 ;gyKKtTT  IITqބڃNlXwc }ʊT+&kZbN6D¯q j^JlwX%QQƣ*$G-uWV`}>C2HHJTP`_|^v)+x=/UV`ae3گWgWEunzJ хw_yKrRh0vr~}_~ }ϻ0zm&Q]1wn%_Yʠ_=�cfA!_syULBGtkFlg;40մwrlj Y=F 6W3@ǵ#!Ha-B.N!gIܲ䐬G=o^;JRF=5Ɨ _i~C&˼~yT3Ns\<;T?[ i sжm7])yz׆h8y H'&^G0kNe^h$`DaT�k(q %3 q$$ş`@ס5US%;=۠J!+X6ct_X#7C6X\k($CmD h FYoLD܅tuKyi3HD<r /=<pN1_HaVND <g|KJy:7B#=Gc v&^x0`(ņm"mQXDl]rޥX71q ʯe^~3w7\ADኍQol4ߞ{5FE1ֈ 1(e%*[.Q^tyǥQ%* Q;=NޅzM5g!uHF=37R~DHQt,Z/46bWqV$q6F9TcL1'{`2&Js]v! tUb׻3TH\쫐$i7hr8#9b7r3  ^L8*?΢,CT.\u`ϑd4MsM(LR>7ַFejuȦJxDZq B86Nҹs瘝ҥKbjjoy ovӥkN/�zgqqqGr-… | _pw^&mJĉ;NRLMM133O?j`VWWChb5LOOj󜩩)9|puKKt=ҰkT(<T-ZMyRn'VsAtNq#ds6jk,YUֆ O+uʖp9Z|MUzq 躲Krsʩ \ UЋrèUyXʌۇ^7Rͭ;8C)X <y>Kȱ#zM6/ice9 |NKa ecCC�4YI+%dԜ\Cz Ai: RNW|͂ɺ~)=x (0U}g`q ~ dykΜ%`f\QGo,ղ9g'םgr8)IH5dYf �mO"zRʣ:(gyYd;ډ"ڐ-j%s O{*�e{kʫfD(j5vh6tuNl 1%4}p~0_˄^K?H~~UB4^GdllvOo+Jb4v ?Ui[e-@YhDfS6,ڮR F @‘ܞXk˱60ɕWy%V<'s{ ݨGFbzy]_.4- lt V P]' aaYrak1FY�(~LPϗ<NR9S9cQYEj#?n"Se;u]b~;)Le Qnb e TKaST3x-�VqqC9&G2v<8YZ^tE_.^bu~JbF�� �IDAT@߽q@'SvVd7l1x:rJ>μ~HyCjԞpdX`ZFzg9FR jOeg@`r6(bcЦ&kkk~4+ˋX#Zg!,[ɏ? kmPl.Qi%͑Bծw�J.gK VEKn,B!LW3u]@Jѻn?S<JΝ z!nwnn>LMMDe=O>]v<xpGq177Gd̄q?]o>}i>F_OOxWHGrq19900OLkkK<}Nw11viviYQef.GNmh4M71ufCUH&ŸPK-0NHafvW?m-Ց©WIE XĉQTvnD֍y,28+ @d VBW�*`l/5 (I9;倔 W(V^Ʃ>ſ___#1-,0Ӟih(pCSK @ 2F#'Qȗ lBo^; m:W[ZuFLVB[2hܛ7VRJD]= m&V{]\5du6�Cv } l.k_6XNhR _~O5ce?7|2hL\uQiB"MSǣĚ~CS)5ukH m /<[ZccuvovE6բXX׊~6hmD }m<g1in?y+MEBW)SQ;@jB>Sa R8~݇\F& BSIe?D`7)~͜|6Hw nLo+E<.5OݹZ6J]+ڐ<+x?΍z(kc<%|U*ـ uiC/fH0}VR�Vx*ˣJc"P 3-1XT vQ}]~Gڐ$  uy GųqWG.`8BK#nKG'JCKe *I7/@1>(Y1;7FۜIj E]de4\ ,uhaϗvZ< (B0(dN(J.{R$ Ûe9IbO&mXv-(hM[g,}o Ub,k .6]cyQ18Ğ}tזYL$%V6 k2Pb,ca2"6zQ")ooukiԞe!M6piT/R9})sFJ?I҄'O2>1ܬ?727Fl_uȾS@hc.죏>ʓO>YCC8v؎ꫯ{o-yoޱ4>}Gyo9_s-iB�ydžy=ZZZⳟ,/ĉOp&i^gn"Bޟ@LLN6LOYZZw~wxͷ..]- *tiLLNvY^]!:x:gumriCa>.e-x0^ !?2zĈuHNuGm{C!`!vP6b<y!{ ek{nzMPfi-Da@gU,xKh0J/m(ɿA:*/8`ZXK2bXRLCd7vA`z G=iԂ<|I אAfh/J㑂jO ,Z2:lw@skrl8gl9Uhޱmup9lS3w횢|&]wUVfR*rvgM7웘`eZK=fP̯>U}=9JnQ]u{�]ms( j'\5i];f^NZ]yڛ7{7F?6(`!kw`T`CAK8VWFM 6gU/-k$цύR\ "+Q0<Gr,PDc Y;QBc5u,v!s4ߵPmQa ;+c[K{ K=S:o,Euy_{ذ{`<Gi$hy8 TZeZb^ 'uh:De qxcшmhuSxv*j[@ 7r $A{nĐՏҤ{I|-~LԬ4(RZ-ݐ?Vkt\}O8S 1|4ԈGzZb|*G v:=$ϾHeaTHI (JȴdJlqY<DW+=bkOh=W>MָznB,6~mGg.g `8Y#¸c=?FF7}d9ۥ).dS% 7=p5 WO#Gws�gejjff&|>~x=>t1p]P09p�^G^VmΟ?ϡC;J… |ӟ,?77i/N?q<#O(y^y\|yV:kd c{oO2y(o~yΜ9G>]ڥ]ڥB]u,NӔ,Ϭ02Hy%J@؀˕C7 Z$^"N88,[ҽ瘙 V( 5u)  D׫)@J}p%UEQWٻX^G�o N+zJ)kuA:O !Wg[(>ŗWVf>+؄,/?3nP`7_CHu/?65$6$Qȗ9̅FZ6s^6ФPh$@ra$ol"VZoRlneszGr6fi`}OESW3_̫=lhᕎ1f`!<V$rQ:<{f6?z?'HaMXR/ ) ="퍺Ug6=_ 0>>NlrQStX\\twdN{2Q;oW3*`a*- 0clsCF_}(D+̭z?^Gu9oX$! HyZZOMlybTWAmH+KAʈ?A] =XS&*6Z!`PAh|AxMp^ʏ\oYu,]* imyu|W[�OPbNKu k`T &.UP"28b=qӈ7&{'c o]2R<˟YԶo4L] a&6ȱ6Ŀ7FTae70uZJ0bz&mpd4/3Z.c000(䮢nP�(ʭmM *Fu<%/:5I[߷[n=ɡ7<,_܃xL]=Es !6,(}g @%6$~|1?I=9}. q1g~uE{6aD6OY2HN~_xF**9+b7ѐMDmbòD =|%٥mۦI.\pUڿߪez\N aqG^oةgczqf@K9~zfl6z=z{7+_fӟg/kg/"y?C?2-h#&(H4gj z{6ʱ9⋜;w9D14ޥ]ڥ74Uy0 @5kkdyPxh67Z0qbεer)Zf hK$V\k}Rb!sPRPF SRpP`[GͨzցYV=,ZUa ʣaq7ƐyP ;Vb <I^XQR6aa~/xGKͷ8HIRjh'VІ rvـ 9S5¸ )V�70eIsӂe=H՜ס`=R}v+;ZZnRyyhL{黦6%+jVҥ., ;t-=w^x~)f.{&t:T9J#ͷ޺H?&5M{z/*_70X#}?:gY׆,ccLMM16>ƤozqN8ƾ}9v(LNN233KKKt:ss/?S]ane}q>IT5FNyl1Y={HvMeVafP`RFSEӹ1 pG ֡і8Ч,m#JwN�"9k4eC9#RLv՘d5KVFU�9MӠOXX\ sq^(A{esK9ox}x&8^1-X2`E:*y AZ1$I,ktq+ i"pa 8-|kʔ1lXr ҄:ɖYa� }9oX Oyr-S7wa�5E)tW/B :q5%f`@9{ (-CKgs2+G{,8<<ZK_OpqW:gM͵kR(CT0Upƒ#ד[S cuN~Oס$ث?یoݿ0҆<AW1ֈj\[9ghf޽z}/:WWas3lc]Al@bLa<@Zc5 u#3t]:C-0W;18GRηtb)0c/\7A0Ρk̙34sϖY^^뮻J9g=P933S =+z<y^xT?q<,4wH<['xz*|?w\qثڧ}CΖ@tOo{۸{X^^3 6$w=;*8YZYYj[ᑇ <WYB�)'9A~s|O~>?G!ǎ駞ƛnBknO}S|}>/..}Q5Ԙ2\!R9Uf$&RDF`{v6FPH(*4*4ԃN\(ƶ& XRGQ`/B.I;.lS!{닕H+:㺇=u߂J! qTJcwVQSVݴk-'T#5VyI>LZ[JTNSu+/o))p&8G6?]x NNLP� ! `L0$rc\H fN؍h(U;"Y_eRPT*%W"7uFvX͉nL�^ׁtLv%] Ͽ*?HܞJ kkIE)?/%WEC0Jy;O;nƣGY[XD8wo'er~vҚ,`Xc?{Z[#clN=?$ n/uF˙^hQ8ysFD1>> YgϞ==yw>ǿW`/1xbLi\ufC`U!짨_Ǜ]c]`f*0!\ c -z$6U.n!س9bԃsŦL)qE)AW ]ޮ eHi˭ C/Hg9^)~M̅&9WܚRzz@ʃ = tɇ 58apZGQ2F"dllK.z H0@xqpt`xSSg4Ѻ/xg?eajAr()51pDz`ѹ.~KA[G+ޫ/ c)zU5 ,5 .ç!p ˁA5!lϣ 7@pýEXT24q,nV) nML,=7sib2hJ7ڀKʟa5ʖB9 +/鬆\TP_&!kFKWRKˬ2$u7uCS7[=]6�1CeFNiqV1*q>2p{0}Dv`,\g,G2/\!嵢8T]wݵ% mnn|3CXҗL𰝛GYŋK7Bupv|硇*;lՁۥC{'Y\\ŋC·:u*|ʚ:p~tUkyJo ih+hy<J% X^YokOs]w155ŋW'?I~'~bmޥ]ڥ]('Ze!ខ3n,hh\8T%359EayyłCvv)B5>#+"WLuL3uu؊t'dVj;z8*^Q{7k,mD GJN9ߟeZ{ʯ+{P:I ޫcD0Ơ1Rr?;J<+6Y]]_˿r cHzL͡bCBnW!MX"d =p>eB8龒V^Y;C|f9vDHHw I7ѐYo\nJ澬Ų}ܽzy|r{vc:Wy4P!WoϿqc_;y0pNX6&o&_"Ϟ>[oz}j"5UymZUz:?6l )IO5E@+1ǵdY$q˖$$* 9-Qd=,yc*o|:[@ d hi}�]km bÀv4{j~AWvPk*F-k[r'Q<~?v`v[yh^mfj=gVV"M dZΐɲ,Qe$yS[ěhfz f~~x�HFkFJfbbYޒRw�'A7((P66\ XRQC9W;eJeI/�=q{N9c-^ ^8%yɆȰ2OX {?"R>h6jmekĆ.ܻ5 z'ߞzqLSzݎ d~Wi`? >(HGk_f9�� �IDATgba4?SOr{, Ɛ6F\yN%}�P<xwZoĠyF URvLSJ:9~505H*ѵhOtfnq ]w qbKvt 0}mcƘz^ .//s�>LMMqw~>яx}jcYP SO@nswswx'o>(ǎ۱goo#G["m[aWl?8wyg><`7?`ii;'5e}2rM<VAi8@� <hvq^}UMwvivJGXVes!yPPnx 4S˖{=$nZkT>Y1;5Rxsb̸ڈ?W9~T y=Z蘆yLT4{EXe^ JQ(ϟ.S  4/?( ^Z\>o!˛{`{,M]5 CZ%Ix" ` x9h M\yϦ ,hc+57\zkhT 5A;O}ؐ[v\_Lt ZcB3*CUwtXweE-X+~`k||-xmG^~,34F xη`]%ǐasyy(wa#0y ?r냐[PϮH +a�rȯ+S^?FA匏7ztheW^yFƛs[\J7$6j /)iؐi ,=S5 B7[r^InAl&S{H ɺ�aFX@AEz0(".skfc%5B0ibtӧ+baùお&I yG/z 0�`3Tvd�ֆY]qz?mPׇٓ-5p>f<){F9�ްVDi<I6%^Ɖx-%/$Cbz/혤f~BU5]?jm]5Hrm6n IB2u цIh7*Q^`rIH~,=EdhcE0ɺji G_;[ _*;~q3lm4�v\цMA!)M=ymcԄ苋$2kЖFݐˏeP䙝sD+A_E\ǕpLݯ?f/jJyi7٦*7z(]w,m<j-viӔ*l&oLO`4799BzԩS 9Mv(:O<>?Of￟EΝ;Gٳyvlۛ:x~wrR|#% _\-;^>PG=yϖ@sb7?_~Wye HPi <Ǻ2r2{߽9~[yS4́<Wے..ҕ%<'q>4$xF*yeSWEA{#\Zc+N uLUUQ71Y�>b l;6zvXڦB ](6?0ky^O,KMTs{7DET/K)i=T !(YG}Ubg^?~S7Nn"=?Ǒ,]mt.6#:IaXZ݅W`OR/C9; z)'cPF6Ni&SۈcԺ I7! 4 MUG�rњ(pY"W? y4:r\3P牳]^Tm~W~G2/=Ǟ={/ɟ)~>1^}UC0,_~M9\KOV&Z+:+k}o)"x+]s+Z\ }3 $IA?7&PZNlnJ,h|#HvBV㓸B1 Uuhk@{4֋u+к e39n!l 4sX;co¢2,A],n!C:Iy^&뭹aQK$Qh׍9 mH":DlڝJֹ} _;=>CX=:V k:PۅV.L6:cG2gKܷ1oP%g\"{=VγQ: � Q8s;I>},ߛ*?u}sރra`"`WX?v!?�!&;7Uyw벐O-1^of5:.|V v@e"Ikn'm$N`2B1QX8�9:.u=k(<\lmF6K"ue6Ji*cs\ĠՍa|M>a1س>屠1iwV 9ynCۺL$C0bmY&s>ZW|xԿTz*QVW:>Y*yJH.>g(|N/{}yzdO6;o.\\;8<ymc]a!NN:<\paGɘ.\Pҭ7TγWk=O87+ɓ, sG<(I^{_?sxS;s5z}\nBhC΍7[qx}/,ҹ@?a}饗KKF#1i(_=Vhcsn1ޚ=<[D gFkVVVI*L<X|~RE3E܈ |+RU^ <W7ݵؒ=@*{QP1S>8eSh ' (|?>aUq�\-w@ ?f+ٴP1q'WO6?rxcP~hR0ShKʟV:<g_<=QzD΁Og)[ {({P.Zlb͵9-v2nĆc5� 2ri$ ;աݿ0gAn&~Q\fŵGy=S]w r޸-]g@nB"??/1�$wҖΝa0yVCZXY)Eg+LaL,*.@Ggz (ʠ.~->"dh=c7 v%Tq){ ڲ &רU.ƾ;vk678]@@tF;@ Z,D{P2 8>_YZŨ8 Gl)IHvhi m4^LP0 (qB}{PUt(Dxcrp~|]ņ872Ϫ\Y>643(Q;dQ %{TӔGì3oF)? P e(hem؜5+|BԖں+<lthh{ Tz Lca'Aߖ�\sዩ*SɵIy*D@F?6MX~9 7H6P6j4V6<^]G,B*N]pӍ7s۹|yg9CB+OkI=RWęgGAX`6<ẅ́Ve9,ҍ]EM1m*_ ƝϠA-jɊ=Cv[<׸@hn!^k>Jl›[ÿ(cAeP.hhP{[$UIeң/$v-Dm6o"79%{QpL}6oA8h8)+WNd,aY͐+u�|[K+̬f߼K|;v(e{m1S433>>|x 3腅ˆ`;^mڻw8qbSeD/Sz+4>>N,.u9x?~N_KgxW[CDC=~Ggj>WV… )kkk-o)չʙ3g8tЖ۽KKtHQ覲jm_j,q{ }D)$Z|YOǹd]AA!%Q(RTZohB }y 6$"8RkƯ.N՚u;ѭHȟK80שA%X=><r2-S) ˭B+\Tmuzg̀5żDy 3s|>DF: 3g:GMrzC0,b=c0 dyw^zX{)*yC�)$S]4h'h+^~3BcB2_Ps׽j~;|t)|"O0!->]j⏾$-?Ͽ6#&R(୐RnK*ro266F+Q~tDÖІUn Nս�K =.pk{ ߷{R!_kXOX:46jA^\Z;SU=)TC3:[>9g}r*!T$w<]XO9#&J%>vvW(=~e\.i%WZn(ޯAcl^zm#TtBZ� (HݬmcXxK;&:ϏxO t .7fC5~[>V5hBH稣,A?4GsUz{-~W-z=t- כv) 'Ť˪/ cJa\\} }2�SOֶ$˺2_WʪR\f N&&&9t0I`=ȼ `w$=mlqK1`Y<VشW5;L4t֎;yr_Rvk"U4t.#Ҁ>Ѩ$!6+gt$ڷҶ֐<aϫr?sm$A#Sl ca ÚQF뀼qKJcW;c@*?nuEҝ7OSSS;Z(�n;f)y:|ӝ|jЕ � p 7yHߣhl6Mo䐜,cue%^"k\r-,ޥ]ڥ7$F* U4U@Yi~AySEL?1 ى8q=_7`/QѵB1|ѺNvsaR;lOYBT( (ˍo5`Wl8DeWAQY}(>x>~& i@> &bq iZJ[yU%,]DD\zaW-kG.n�+!XWA6z@A86nL뱨UZW.?K28q0GO>@*MQZ&SϑVi$Bbrta@}9O){6kk166vMc4 3Ξ;gkeCc߫ I%oA+GQC:slN5ЁfaB_Q-H@޳eƆ^z cP`2.ͭdXKS�}o<Xk+٭xQNJ@7yA׵a9,`Aqf3Nc _12|Ћ_qr+WQorC#濌<Zק&Q <w{1[!ġ{@Xg�^1F"0P0ǔx8"L @bčHn"6nFa>~OVuP/ԕa",qC5Fm(R]JYkDGqmFR8Zrx FN )CF6Č}~Ȳ<…HU.ctN"ި4-:8 J-7d柍~ sꆋV ?V̽V|3m>F={Max돦:K) /<9ؿB'U6:8Gi(\k' Q*qUO힍3vB"뭰{V޾.uQ @xۍ5=&)a0ڪJu yt/,^\I<:vA cO]z7J;if~a>Le}k<JB(<ؒy}[j˲6˲%lْ-[6660,Ѓcz6 !``要c!c`KZjj{wssNɼݷ"*^wٷ~'INurBI)�%wghh{gЀ4 A Vu~|t^Y:k|CXz„KtOѩMaog]pU/+K_Wr*t3SLt۾L=)% *%^/b|Kv+Q9a|!a{r9҆v3 :bk Q*5j_SE<~oVnkT! nP.n/Y!]chAd@+7^O-z[|b-=9 & vGdԌWBQ n+?zعe'aP'db0pݾIu߽\ 1;=ZƛM:!WTLh8ftxǚp駹WwkIB)/Hԕ֚f ##(8!OJNYE8 2k]x!nCge+S* :P$Q IvYj$ܹh@86 +{.L+Q/`)eG@$*AIva¦^JBkuRIP *,#&w) SanjڼD*) i]҂ŊmKyR#CC$ Q&ZB($Jh"TµR e \J]?e�s(CZ@'q*^Ue5t~WwkOA3M6 >AA<f:n^[uw$F~R⿟M^8p m*ކՔ"$㕺6b! ѡ$:Ä[I:{dLI!*\IxN8ygϜE)MGҼTL ]?p2}'S3�1Aٰ^`ci8:q9^ RB]R;=L۠Wf tUk$H,]H ^#Y 椱A;Jap*</7g %ܽZiwVnr5,-i"Dhؾe9wn94 +b als(յ$Ҋ9Y:QQkm}.1lI<ܙ!vMvJ/i@7o/u.uU.k5^Ǭ�� �IDATn|uѣG{~߾;^ג@kmΝ;ץ۶mcffNaK>w؄tJԲy~~>رZƶm֥ Ѐ$e@Qk*应EXSu甄(e_RNVSu]Je*ڿJݽ`3R*SH^D%_'zA\kDqfTgdcoI| ~*oc@iš'O|&R~j_ƿxT˥d%l%m m%Qq6tS0 'b^byd� Z7+8u.I@Eh1>'hɤ빁fI3%c{;qvjNl@ Rx-.k?J$f!5 :^~f]jw*I$ظq#ccc,87|s h۴m5Xsvv)ezΝ;R7_xA;wKG_]ZM+{e#x 履:ql GbήI{(Hz m%?L58B_ũ]Wg d>mSgq$3t6{xfބNuauIJJǭEy:,@l7X!5ˮSwt|]UйݑF Q&ܳ5Jz8fcX2^DXbY> !b#x'OEP (5 -H`y],Jr.-+#c!}Ox!5ڂSbY?g*ٙ=g+)y>-V{6džԣx m?<_M7M�Z'(eڛov vkńJ! lXJo-f*"+X}Ix:rަ`\;PޛwI Wy,7wIbB[Z<yd}Y^3e!K[R "eAPg~8pzgaH= XXGőon|s"imձ-UKJun2DJY=RZzVtɎKe\E6v:P=[rh&;R.F^O=Ԋؾ}{t{=gz_օNrӦM]]z׺yJ)ٽ{7q*ZN#j߹:P6"^W裏O}0 򖷬K4 hY(bn̸8c_l n0!2J|0{=ToWg90ֽ_ϪEjL[1~~R/jݡ'{f1-["uEPy!q*%i)10`u8[o`rFޥ#i?HQJ^=*5,/*KmP [AU4|JT(ql(O KG 0DϹ�E`5j1cԕtڧAP.Kv?mCdj (*IxYPk={9!z"�O3q~8;C%ij d(PB"2TyoNJakI2೟,yքLQ{c?7=丹+1YXXEٻw/ZS'Ĕ9:"ٻw/RJ9u-s (�mV}1 rS d(Sƥ[H ja۷e˖4WK$�Z=#EJ \FqeFV8ldA]F|  ivLJ*VYftHh):0N|�x"%5d pdu#!70k!H{;s#l<Pg5*)?$Z$/�zg>:C;r vwD x гjI xœԅ>wrk|h:X/.)Y4̭_|<i{ҀNRɥvZ^ Af?wZ9cuy CF*.H#,P(LtoNWfxA/wd\]PZ*ȈAkTBHH%j/Kw�.]nH\\ p@da~y(ykna֭,!|ҳTbRZe,NPƉ 5pk9+ws<, 5~çsL2޶V>BpG_*Nm6N6?ӥi8tPy(}<tz+ccc<Jj{Akm |wO~ Ą)q0 B4\!냲[jqfffk o Ѐ4 u]�Y(݌/Zy>u#!QZ-gY.eR|f.!wWܻT80)&+<ޭʫ,ww\m„ m6]’t:U6f#8k}uV&ǩkZLS9rG333Gĕm9Se^�玜9~Cq4JXR/n)zz�cBs0쾳u~zzK0B܆EliBSrrm 0+`췯D+5$a<t$lÄ..1�x(']kU O<KjQN?<`t7Pșќ&jh2@ Hl@k|fψmc}%4ycj$QēO>8 ;׾5oGG%H)M0 ٽ{/Im9|ؼa-4jf7uО<IVSfΊzT4;C C - ^: ()*;mf|CdWy~Kf<Et˼[2BYEVȡ7d_vW^O;%X+s7(2^ˉh)IV~_S1rhH{~һo-Oi:$6)BI9e]SUhI<}]yzm4.ob~׎O?f_"d>RPVc:LNf?%OiT]g>0e==Y�K-qY;OdH箹,k?]ev=keFK͛gnng`摹9,(~{w=ږz](eWbsRd6z>MX~$5[ZQ5"nOB7z|"#$PBc!Pq̱q):5EZM ٝ{Fpz#UN`"749Ɏ;9u 'ϜEvz} Ho煡 pa~|s*-yץ7HW_}5GIA|;qM7cǎܻN'Otj#p |;Inٲ%Υy `OrwwtfӦM+ӧO/'Zk/ 7ܰ"@cΜ9Ö-[r Y+wq l Ð0 yG ÐkwK4 肑 03+LhNbN5 Wf<ܪDz@Yj1K_ȧ,_a۫mi�bi9 VV'rWt] _QQ֗K S�f3 ihrn~m۷2PcoQٌx _?,/Tva!gI X_&??D"BE' oxSO3&@xՅ$'g�|.n$qh΋PO|="-[Kԛf 0[(Ql5/?92bD:2�]EfC&skCܿ5HGcҵRP۷N<X/|x~6 Wot#sQZp8QUcT\kUbBp`b/O<s_\e$ ߿/33SοBk͟ɟl>"V /:I;i4a�4Dwl"T15V!tk.AQz἖gA>gƵlYu`Y`hsxZ!:R[fC !K>ݙlqI.b=Z :6yE"(xƀLmSQPEZۙU d`sG(R4ށ6dѦLnl.>pumtJnRR팗׎W)EdKq轌JkY.|ٴ`QO-;S;C+Uܳ\UnLa=VY?i@+e]3A`<JHee[*'g&{= J3BU9(*Ou~s^۫LPƜuN%:AYi@")Y oNTbE@Wdx< h^�_VEq=ZI ( .{I|deA _.7r"@!$2h Q ).|}09z.5*Z#&j5Y'C{'Oi噞 $V@ e`u.@GVsy:2`V+ [2n_f+xӕ8ߌ*wo"e@o~yROףGrQҐħON=<ߟcccz|ࡇ'+d~~Ç醆;.X;㎴O\s [lpt|׻ޕגv9ھ}{ڷiַu{`χ^7@஻8 X['O{]h@Х&c !{~'AT 2E$g[ʴ @RĂyW̪9WZb !ߜ*P_*wU, A) 5mz~Jip~.ѱ!^{ kA�eź@Ȅp;>yGfgr¹\t̩%׷Jk9,_ۇx;DI:6I2ĀdRN=w4 Y1 4$-h9*~콦NBc ؂+{Re^+V B1<ލ}\ fB̖- Nޤ б`lY>R{!׃XN5|k}scxod0_`vfҢBJѷa,݋uW9~,]Ͻj5tسw/{3ҵw.Pֵ!I S,.-$M6o?G>NF67]{ ['' Q6:j!w]=)L/H ԺYR0:J0lC6`:snBAOĵڜ23g[odJLR(ޤZ< vz.TK*ȌH=(k s&�/mk5�J:��!}�]eKWY`mUGƷ妭Tb@Zpk{T(нt =<MVwxi]:B>I];?uշwa˓;A#QRM:̽ir]S 1*SGmQzBn9{lx&ErO<hA@(5e'L]eۓd;i(E _,nB/YR'h$#cN$12;D8j-rsݺ嫞",^%1F Γ[HJiKH!ef<]2Q�Z�Z[sIe_уDB&0?7F@Jci1M"偠WFk` 7+>5wrI魯M//{<裩+Er zGy0 MNN򖷼x:K_333 }B]n6>Ox}9Z?99Xz w˿ꫯ^آgR*`}Yǐu]5Y:h@E!EbbEFp<GkTplf0J.U`zJ }??I&'`ً|N*j5QAy[,s/Wj gůŲӻp;rOv5@Y^9p;|o[Hkk_/p=&|BhceJ5H?·b6Ѕy=?7~wS&7�W24r3E_ܤX//w N8 hwޏvD`$Q7TƳu xˆ-ҽ5QY\p$9UOzb#6`.�ӘhדDo% Rµ|7+ZS-7[^u-= q=*5s'"Ϋom@^j9u_addfgfٴu Qk|?z:ċ'Q <}}C,Q&m۹++[@~iM[1F^e4 s5UF>?_|<$1Ff]^X<(~e%!L(^\U8YȇV}Q^\ (`7l]BpK) Giڨ�%BHػ*qХ;ʢ$IBS7gqg^JZêz8> 0+S\%2DիsW8d a_t;8=vgf';| E4>-f{_iYo&˓bd5Xc Ґ(f9ohQ&#VyѸnlAˋK=4fg8&T5�@V=߃2'Hk/+^EIwd7K(4^JY++ʗVK8 S\k~,£4. 蒊"c&x+VqL;[8~'fcyܙP;';u98FHzK)B0>[2زi+[ncvfSNd0^a c+^^]c[Vn9z�?r&B)g=encu^s 7ϧ}ڽ{7F믿 /ǻ޽jǦMQ>T F-[k׮ҰkIۋv>:ٳg`ڸe˖\^9ڲe˚Ymo6z)�nҲWKZkvŻn~odgzRiZ;v]v~%܀4]֤ ^Tn [k3 Ǟ2Yb:'(ukpEuxJ/YkEBn+}Qb~e-%1s3pvC)@m^iiFCoWG' Es`#<@֩�(6AG~>}cW'' O~G{ُQ]B(Xl$9L$U�� �IDAT)4Y}�H (i+n" \ھ D ʛZ2mR?+*d 8#́F 슴Uޏ)x?cEaxo7Ff2}zRz%(ِD1?{y{YbQqoƿГOя~ݻw_y[`w^'c^[o*gaa{idXc޽l8G?)BlW]KbT7ZF*;P!rd&t0XZ+3&Hs,HYcVM̝/yP R(b*][\\Zy.x{0t +h! :aR DeZ[ :_%}$ZS \>tЮCיWY,_p1mwk/YMya^j{hRK@[><}ZzX 5l'b_rLwE!$!qp)ޓUfaqOx^R_3'-a @j8[Z[5o}n OWF5欫{W=nw[ ܋K^JHJg෱o2rk-(up{PV0zSz֩j 1<<Lw^y# ˍ^Kds]nިg^yu;v,,u@ {-[vK.RA :{Vֈ*i9{-`Ze Ȃ _BYI ]W]ahll믿~MiWXh5yz1n喞W1ٱcDz!xyV_ 9+?3>ٰaZv͹sXXXn=y~9kM4\I;bHXOVTz? # > tavNa\s/rL&̜V:UN6 t.>"l?zwYPuzڄ\ ˯n[Y9EEAl<p3߄GKu Œ�cʆaȾ}�8vIj}U !j0?B _D&`D 0J'-{A#G _E+>%fgqwW]Klk>w~~%P1|3sSO0Kn[!0VaYR&pnvxW: J`(vu?3d><d]R6 ŲWHNLh:lhd[@x͵{x{Y<sK5hۗ�@2CFZW}$1￟~o'LJNsSF#Gù3B)ʼn'8qQ1<<8NCA_$fJ4`oeQCHᆵJ&T((^?k :9QFb 4U#0Ffن$[l)B%wUAZg*!@ro K;4BX'6nBٍ-'El(h5Ws~;da髎|{)I1 L"1ZHPiԇh-PILQKaGB{U\W=S)|]uS HF e"GEFGRgé揍<P=1X?v@Zx:<I�婬'4(rh)27 )ad6T84qiτ,DID\.�W�T5OͷkcH.PJ:bbrMqnjIQ2Z osK5]tpϕ:}\5:C(@$m1d[wLR'$2B5_>[RS9#\2ռVU:edB\jsaj3f LkdoNx(y/FȐQxdFId^!Jrv 3ttd<R)<G_C!l{9Pޱ/.p_ 0^ƀ,]Wm`hȟ|,KK'jh&M;/uU4< 0:://|ÇEF#nbtt+|ٳv]U; h@/Zs KU)K_R(ᰌ|2B$s׵)}ծuYI¨[(IYe5UN|zZwD& ͳϊ^N)Q9x BN:B*joND0곴^ ՎyWGm44;;C-ݠV_aaqa،zEģfؾm#qFD:j]^SiqHf^L 2h–!qa/%e;J[]q)F,I Nb&Xp>ŗ"'\l4po;  9yQE!An/,S!\BFxnEcO(3n&6o9)>W2U;wSN133C$,..z}3?gi WL]H{]@ !s^QZhB$5㩂ܺ`s_Pu ]y_E'+áR5Uye"�CzDkKRAf(FߵJl@ %jQqۈixuьBz͕jWn֚!BJc N{wwC@Z-ٴA5xg{Wl.S}=YpyUݫ%U֝7ry\] ]Jkl|_I ;Wsgyyik6 GY~}ֱmToR5ֽ~PkcР$}֯ou] ;Y{eE}T3\cG͙ iT*"(9uU8,-U*<Zk:O K%ڭA Z٩]ZH&W2"uQsx~e ܴs.葳<}sAxVs@(04X:7tx+8uɓq 6pWsA&''RE�Ѐ!?WdTc'j=N^z<5cUQ޸_в=t*^HɢmU!z])~ӕbZN@!$(Bi@E>| hZOW^M-#jT s_g~,ST 94OxyFt50#k,7y+di)Ճ$SZ6!x)s/Єb`(|>]2|@$0$3}/m& vڱ6w}PF8SN5a4rKCO{Z |Wq[=Q_dz,VQ +dQQַOCuN&yݫ_Æ<hV<c&y}?:q3??Ϲs8q?~'x~g!#ḧ́ꊭk Lt Z\ru{ܼV@:3 `j7|_8qڲ{VǮ rc]_ZZS`I IK6dzٺjV}+EξyteThG-Z �imݭXjW^<2)h6DQd@hBs]5dQ%v�Nh (<:Lhy)Te5Җ]NsR*lV@O[P2mHD"E4|+vZ Ӌ1PZ5$ZQ"!و۫|Ezn="/!rD] l!v K-ݎgFJK6X>nz I IqL(ȼzʲ yn}榎UZ(s79o ºp?o]];4,/DaOJNB 0@ty!3WzQ'Μʶ҄wV ޖѐ>sܾZ==7l$f@)zF) @J2}߿[p@Ѐ^ʴR_J=1'){+u{x๜BBnV)E+zCg-ʃE?퍋i9V)烘"uc >LvoG+hݚfl 9h?|O2) hldMЯ4fKǩ"oXYfַ֣߃ � 9r-x CrH@@X8IӀL]ܴ/Ve  l`RД]a{�G~̖?Q�ssdacjAmbwsofjbvv)33j5&7T%Tܡ//OϸӤVt-:7\uɱ4*O=Rr)&gΜɓ={$I:Ia($fײsFqH=1߇.>"mLӹ<F0e!Ps^~+&.1eQHݻ  :}}'-PB敗 Tiy7V[),ea=m3\ihbf&*?xe|Yq,@O\R$ʲBN]BZ6!9}eyF  3SnU<2wxmv�yťS')#)g뫬Mi[W`1F% vdn Wr`Z# 'DXAgp3-T+χZõ(}+7;%-9rD*K8`(3sŜAG!V%7F&f8IBFGPJ1??'{U'j2-igk`Lo=ljI*1& J+Y@l|U)34.v9ܥP8YYٮd~X+.ZŦ @ ޴<R|˘ĭa Dt!\kCCC]h�Ѐ^NTv{1MW1+S^-�=WwɜBνS((euvP*Գb8jSUxֹf8@6R+s%N4KȐQHR iԒ '  d _d{X#ܵ?'_< _D(Н2)qy#ϲif4 [a�`Hq2kQNמc0vAt^Sfy~-6#%PL80uYSqIe /^鼾W]n9�bRX90]WsW �xͼfrdɩӜ=`B&HbҌ a&=u_a!О% \ezSڷɡgi+cpyg? oTPfr 1ͨٵ+QMgF�)댌m@ˠz:fFQK!:! |R1UXSIf޻N'0cA.! ĸϻB$CQnOs~/`شYy @im~|ZZ@v)T,uhq`H"LR΀]]{?mTw%-Oo|~.!;nu)Urkm'́ZB{llѠ3P‡P>wʍixA| LΐP[jiҘY:1g.3|u*Lل 5)PHڭ#ٗ;tT4-Q\Ԥ4A q.WG򄫯 ?=y8'fl\fFkԊrz[ Avv͗2!W Dj$$apeK DgzAN,<Յg#6HaB'Jp72Ob3*g\ZvJ3v ߶|wM[/s 75 2z@Ѐ^r$aY*JpfRL%D^KOaQ�G{g$ه"Z=[iM(+PWދJ��4I Zh'uV֚E|^{ǫщ�1g)<=N둀ҏ<7I"I C &Cu(fiEԎz$e )NG8?7؄I#F v9�Ig"q6@V/e&×Sy2JA ޙ0 dw]>v�\E^΋Q(c@�'l}I>e8a}x=pëGL9ܹ/! N)H\ƲP $Z+x$[&UW_ȋiFVEGiR6s}Če|3gf6l@4TC'P4Fi u^LZVbB- EwL܉~o�K ؉VqwL|D˜Ch}犨-~-ٯ5UދB,(?d:+@u#JI3i)hXCҒ񒔶/|gB"݀^eʶ+J?̟ IA /in5Z53RPf߼( `_ދF+En*OME zC( X敺 P]Axrگ;)l*C<ݼ|^0&:m ):7 d~ITQ&)O=Ɵ~K/V#o9C3^1&K f3c镔?IgAj*$*!BRf +eOcH{㿚Ԏ~9腼:5`ȗR䒋;rBTN=RY}f.*B) 0=S ʖ]vV;r9HO Gs,@#Mxӕ3|m�='6vy3 h@Ѐ^$Iǥ@OU^y0XlW[ψ.;UsHEp @t:.eثb\dJJ.ST)CQs,ܭDž$mtj| 2a0^bCO=J@�0MѓQ@ڻty 2j@gOТ͈V lfnz={w3::"'O/ws,--b$yiu F!JMA#(SrQC "]\Eeie<aeb@:V? !A6r*6jAYq\1\i\ND)5 I�!N7ه=вl{:V&&6њ[̩3,G $:Z&Ο6ؿJ^ LO0==M(Js/ CZI&&&#@3V A$;jjƄj2<4JHԥts!anIӾi>[Q5J;.3Cy#%AOM</r`x-AߔJlRAa1PB-oA" (+Rf7앑,n 4 WRyU\V/!\~{{ݠIwȮd͞9ƩRџyΦOUkӏ~奐(/͔q@7�V!R}e){YaR>$IB.fXc」i;JeeK+*x2@6 >qF".h7q6%wRF+{&4{]G5œHL}z\�� �IDAToskCs]͙Z0罫)un.eU̼̮Aj:s356mKK mvkLwdXA& ]:[?EѼ|WΩ(nwbiN[+1=Gު'NW3Y:ѤF/�)DZ8ISHߓmc5>xVL'f 𴌤[vWMg~3 h@ЀHJ Qk*JAK账SlRz92bpGA?|O7g)pX?JkU_ؐ%Fi6YEPޓ!L>7g=q3OcL<0t*QqV'5 К4*+@4o#?NHZ �4gΜ?9s pJI H AQ${-#s"b r,5Ah=㌲j:Rc$p4Nv#/)hE7G/?#2@n^qΞ 'EE!Q^VAAI97Fn {vo5QJ'IzqL$qL-lDw"gW 0<2LQR-\\Ecȧa4K'ǶPPy:Z^?jO=VCUFY9% gR`"p~9fθݎ2^aqb :GM~g^PQM}vXbz$UQBJ$Q*"Oa;<�'@=Z+@ْ7 r09 @K_*cȮE:S+qdR*n$";o%<}Ҵn<`W1EWK)HB:̼e.}^;SxzgH۹U�}!qΖp+@~!` _e_w]"ZWh7m&sffe&FW$ a=`M_i:XP:[q$rO9M8԰V8D~"5޻P݄T d@E6#g9ޡJ&MјHa2<<֚E^]UuֵK\LZ7YoI<?|Qt<b4 h@ŧ$6ޒ)\K"Ow֙`dVU]NH~T1e} j+=Y+W8 L˅Z+:!Ϟ}Cw}_3{9FF77:$rk$qr?,j,5hԉIl2 �!Mm۶z?o  ͫI̱Lj'@8%i\7,?NE?W:m+t:H52 Ʈ+]%A+91b :];^Ȑ Ǚenn @Bs/r80AHdnzZ͗:J%hRI/eʗV!A-4YKI5A(_zsڒ3Zh.nX356FC@8ԛ waeLPKA_:8()#J> 腦u e #CRب4Pʮo=}ʚ;\)t-0k& N^ʨٛ49Wf:/*i-Bd L*P0Bpx_yYZn/ F" ^l1/G~ _lRER[)]]t]mi7=('+g )/ SYGJDHI$Jx:k#K8b~nVIwXZZ,ϑ$ j_3(/ʥ}a>SOQU6z➟a;R\ 9ڎG*C@T[Gx<n2Hy* mڵ(8qNu܅G[Gkȍxyd>'h'P(q7; 4 h@ЀTbu[X)KX0Z4*S<RƦW9eʔKPF/ g^-׽<֦^Zsi]&r+NofΟd$l (+{Mb+#DW32&49Ҫ0_ Q᪫>"Xo(!c2'o-\q�^/Y|]>d_ &C^pgu!.c E$pc30Zsmdbt-#3B%%�D ]xi̽.lЂz0ޛޒ\}S}ޛ8Z)Q6 r,r" ;aN!Ad?bY/dP$%d+bIQQ )"E9$g{wU]ݷ[p3xsowթZ9sN2Ʉ<3u.$"Ct:hgO@91!9|y-Pegl?oYN{?kAAMQ:pRYkR $Q 2r?3|{Ц\'-hc#MѧtgʓP7A%>~�PWÖ{ŏ+MX),lxV)E_TP"7.L�.za%7_, l{0-;M*Y"8jL5Et`X&tݵh1C�<5iQF�yɗ^qZMtŭIDb#rNRDa00OW$+-m߻}Df˾/'w^ C",,Tlb<-_;9>6Խ~1lC yUiUhXׯ#&s2mOճ})V·>·>B ;敽 95psûtza%-iIKZۆ(Y"BESEwM~T mN:IThySֈY#'W9'ox}rB #ۢD[yYLAYhoQ_B%`㡫 nܶfbSʀ>&\ZG&&פi1,PJNX__g?~c݈'?W!Ed ut9MB6F- > ^u9$N&y>R9_+c]~<|XɊo&?~ww߹ (;?L;FcT&B5}vG4CBI>a2IɲCR``س"#KE3{(�M10<}yeno+]�F/@EjxK6[*]xmh4-VTFu4Ƹcl KH1Sk=zP[Ia`6(8SGHʒ dkBe0xQ iک͜f3׊E=I_=b*kcZD~cBum<R"sJ)˻qx 4%v24bܜ.xƯDOTGXV纜ʾΰJIaeuxLyNe6z6�<s(z|F۴Ɖ<'TqRr%P9_N[45[fSQ~,“tDy}PO1AZk,Ν;.wnJ]24-9+WzJ47yބ;G)w3v9G㜣IN&.X'HXDu1b'<FBu%-iIKZII( +y> J):0 @jZvKCb QŗQcuv: 0` a_oάaHxJy+qmn۞qW]4^>3I?~럱y;adt3BC[^E?ycܺ]�quaM.^H 3ngS?3 1J ۋ{E'Vv>o> vaCԇH7)Q;jm1<92O+1dQA<L{+_ Qڨ:-rSy_?`A=n\/GƧyuuNk:qJ[ pJ Sގdp aӝ*YJ1ƦwO!^n{H Q)ƇTI_) 0<npR67s#�Ofe$xb P%I.G#z'dT1! ]2NK6#bc=sȝyꥼYzJ[摲^GA2}>X0 A >)^n3KwqG؞2& Fuع>-ʠ4auGe0uqhTfAq }JفvIwUΟb)E5Vh�**հme/TUWMrWPƠ]HQfk^;۴΁159zY4EHPˋW}y-kG } Xo\LHz_LyxG;+@չUZ#& Nkr�<!#Zc S_E0&/t,[D95};(0dj#藆ק܀ڿ;)=,SsjNKqڧs>p .-JzYyiV뷎&=skho LaB۲wWl<=rb:*ƒ *)OKK@D n^|YYҒ%-iIKzFI)EP[CE;]� +61UgbO>iT))GZ{K#+fFƆE֩Kߚۦk۶JMxPo`?w돸pq3 ^< w2$^N1:[e8e0bȽ7Z7qxx>[[[\xѦD|叵*D1p&Nzٱnxi A:up k#]`"e{=s=D4!¢29΂P>Ϙ8 s c*Qzޔv|axS1PTo4 Qm=(ǵ۔re$w|Ā^}Ͼ/a{boI3ǿ|w}kp6;H:+Augc GwT7,Rec<Qͬm5}iW܁azO9k+T@# +zא|(} D -xaԪШZD poh0;@C*o#'{iqtYc3+YνY$?£=+K&+L'_tSi@<gwt Nw3 ;ǔjFo m|e¶=hv[XM5jƚ<h!Sqz^#0Lљ$S}jAr鱥:f Q^| 0|9xdj˪D}ֱzF6y?8O62ecc}CܾuO| C%)(IoՁfze'=mIT^XQi\ZXyZkc QS>ڶ_E�H;LuNuJVֶ€ycs~*M|֬yAS+&%/{jIfei^]'1Ia< \'0 TJ`\4+wh%mrj{d%-iIKZҒNb[) Rxjbs[$dH8ϖqF2rװ{A3Lu9Y&#h:i ]Y?6Άm cZR]7>\qVދhV]2xا W%r㣌6Z`vnoU3I֣X)DDv` o:}d^K+)?#6AS[5?(n 7`- Cېwc z�+{VǹF w`( ]X l80ڸv|d"jΘT_& DCf]$έM~7¯|u�w}{|N6K_%MS<p*0@zbCj2T.0+w-z1��o!�v}%.6o'08 Y5 ) Γ P`(_[d% ^-}@}j]ۺqg)YK0m6,T<@�)f겸LVs% l]yi]:Jdva*nb\v > [hj2zmJ VF)DŽD6bR l*OlgAK_W8}* ]~xf:t:cF<eK1Uy[ZgV67kLPmRkدMkN8u  j$uN-` u!7Bl$2CUሴkƘ܎SxD$qe{[Cj4M ~7u[0;ʲ Dȣr˼kl߿G>ϲ(^:{Uv52쒖%-iIKZҒZ*Fyg^Pa y3-.*Op~ B3D�HmgQ9wf/h(kƶSY|K?9hzwW\~?hcAvIIz1U(gj#H?H~d끡r<޸uWփ<Gk&Ϡw>wH]:~ Ϗ"bـh tꀒ &u\~E;~:  vաƍuTKJhM"% } a2|c|IO ބG~?w "k_<5nݹdiqd<, "4#`Yz*" CP&R@Y m";\*�F'>Ni8+KL&Ɋiֶg g)*, -0b�u_l F,j\]bw�ykorx8>YG;1%~,Lb\aݤԸ z_r" 8ݸ ڲ*A> 5ÿKj{}Qԙ9Ks7`tne1Q+cnktDA7jAK[Bu7:$ `ᯚ*#SwxxO?ME4-ch׏Z_)cЕЩ?_ @59k[ޢ4 `{MfW"ł]!N""NRrt6{3kh4rK[t 4}J8 ?ڵWCNn1U4e`̝alhwL-ȃ=eh4͕©mNWN `V]Ғ%-iIKZR#YT<U<Ν 3L6+V[\l E%ǧT&<-9/NS^J~VP="&<t`~:;>6DQDuAMڟEHӔW^xON[PVAcd_ Ħ,|\#�� �IDAT?SSL&cdmn?)n1wd!d+d}Ouϗ"0c76aB #63Xw}M]0 <~5:l{�B> c�y]`l`6 :.m1us0?X3bYYaΧ__kXFÍ+[|?Oe; IbFfhml\7(Ŧ"Rn! T - �wp8_VGP<0 C%F4 |u<LBk'S"$hv JZ )/ {21e9Y|lL73u!t˦dMň<9 tD|0{(OɎ3VX73%+@eThABE tVK^sJ-bĝKD:^0rX1#9H](ҁzPP AE:σq#9G׫ߊsv9_M_wJZkfr ]Dיߦ 5&(jwaˬz^I𝵁X4އͥf1$D,й|zcD$>D$VVza9[熍Lw냎=CUEMu?0P*#TCs-cHiW}:䚤`Fp Ӵ6۔f;m -4g-U9d%-iIKZҒHVV^Q*)D9X)wS³܌/ИslM}R0rt%P4xuj2(ׁtNYIܗ^/#{>9hQ՟y{NNBCma~V եfZ P/gi"&{=~{ޞ6/K`\-l[ z 'u8lû~`c 6RG ΊF.4�Ny GRު|LvYbٱuL 0Ɖ0f C~7>FY{c;;z;<H6A&#FٳEe"8,8K הܡZ@DXJɵy SgE("ܦ9WXׂ(Taor]7g/>eSw=8:-X]q@8d $3Kr˩1MCp Z�"?mJUcTiNڴF$ĬL'9 8y\.V1ϼb*R(Ug9(]`m}6L p Uc,kwTv]/VA:vc0Aj�ֆE{๼^[ɉ`r]Oۋh{8G ƿ0X! ZS2tXjcSHƔuetIȏy&Y8Cl<�B\(lPkí-ƭ[M6dݔsme]ǂ4Qd|ragwc E^OSg7'Y<ۡ<4s*>ꚡseix2!"<A%~g0Ɉ:g WՏ '3J'P"-HK@vIKZҒ%-iIvH8naeUɼUvbdZ2e/6дۧXn);=c^^Djޡ>&yl$x<CsHU"6Ox`4?C * +{"3,bs6„.n+7z޽P4][:a=$_d]�5…i2-\` lB/WOCa| cʓ]DXG%MȊ릶{n Ȃٛcz۞/j~?o*?۹y!oquQVP E1[ -{ Jٳd`4Ll.PY]2P&KkPXqgj> 26J|ЯBsp4Oc` Cm'SP(+g(L;c`mU�t.E%͈8@a3?�2X�6ϬyCC)EȆX~y63/6Ƕ(Z4zuyK*03 a˕FPl()[1(0fdƮmO Omwc\M[ Ī x6"1N(Xg ٵ)x.{'%<W ޳$euW*7E׼l8F칟Q%;, waDi@ƙkmE4-X*bd.JlUQsQvlԽ8֞3L\J&<߰2S 1$!bك~73L/3{Zpje\MԼUg{u2ŁXצ܂j�gXMm)餶}Z쒖%-iIKZҒIS BDgʩ/jY5F9My,eQdO<~~)"e<"<#<Y9`z֥qJ`2H+ '?h2r:H~G8t/%b)R @N{st$+:Tw8+$©t'D =`) j,<5[.P6X &re 6 F};cD+V$Ȯe s(~v?zO~^x^?tI诮|knWKH("C?QO!G)Hx$I*8y%E/B  euIbl[>v $h]a}-ߍXUl=}x9kFbdtȍB"g|=f9ce#i,0`yTj(<#w͞%m ?צk"E%c&\g'2{ }qW;e1W(8.9ʝf1Ygl^'x*_Տ6hVZPDWU 2H!-XUPg 팯u9㋍@mmAܞr銫 N~m a$,j�.23|dj)ʉ?gpێG'N@iN0@/3A$+zַUyz~.?[oӪbusХBM$\+hTS[2<P>oJ0JFlH1~d٩c1s>gqStcZҗMgO7` d97U(իo~2qID\2kE-|Gx]Ғ%-iIKZR#Y14Xvk=TU@DꊽۤPcH&A*WZ/tm"k-: #<0f ~hF MV*ԣnP}Q^q)� l]\E:xK/ .FC&C}l3+O2qH�D a8X)&f8�'?4 tEMN:9X%L^f�9@﮻TqFb/e9d;6frMw|Q7sq %�LxDF) +۩nc羺|z6/X;Ŧ.9MK"p+]E7ֺ:ЉEBass(I(X[[$t:tn w*QB>pHro To ygGo [ܥNX#d(2$#6c<Z[kHM&Rc9"!0LuFy$ƹ7=H?$tȥ&rkN!JE))y%B.59 .GM$<6pg1cbbLtU|z| Ǒ:Bޠ]̈́AcJ oSTab�(c6R6P[@�[ f罕 v;G7eCLG%Caxf<toِ] s!. ;Nê[끮\/jvMl;DIKUP `A߽,.�e5Vr_SR0K!Zq`t=cWpB?EKR#M-zY[L1/l:YMU]|/oXߋ"<fAQԣPG,D9`WPJ}L(�]fdB(TƱaot~Ӣ}!u>~6N*:߰Ĕ;")KjaW@qwꙓL3]-{֣a?\Α쒖%-iIKZҒI;K=-MWld=( bF0x/}l]I{re+, lGuKh'TN3 f=Շ|_> .M QڝOV=]|yyo˿Q1)ND'^n�?N%w#?<3DE%:1lΝ[\pw)Vd1F`Q8{1kHpGJ֓OuA.sv"PH\3V�Sr+{fh-/"vm$!#h4{z D{ò-{4@S6v`UL8{)/m?d6!w|CZq e3CۮtEjw <\(][1n8IbeuM6=61+݈ ^n֚Na<ɀ;VIztIIf<xob9&+BhA;CAIk\pkk$ }KNdE܂Py2GGX.I̫12bx̮ive{`S)ƩȦ."ܸSE]9("aڞ#7 3&Ӛ,MIr6`uWd2! :+r$a}ph"V͈H:)>CJ^ 9marQ|j} # q2O?L0�,0*Jq3>ӳ":S3@8v[و9QHnț5V&|)Pb2Kv<sOGdYS} .coK}bSEI,Tc쾃[T4 i=·I\ymUqJP (k)W.F rʓ EB-ky!3%[#=E,{&L}X?Z OQoѺNKpٸpй=# 161\ 4Lҩ�D"-a..o ~]'U,]d?Sm}Tn] )L}e<kSy3tVwV8qA'% %-iIKZҒf2 0hxWE (n<$"Uܓw\@C݀(5 9cg҆׽7TT΃fzlרj(N0NN0u�ڟ|+eҐg9o~_r4~槱uO׿Y0?Kp<Byf)bvߧ]%gѬUlɄ A.-dr 8,z4ܦmTXGЇT`غMbիXڏag�Vt^D۲~@uXGO??ů{燼 |&׮]#I.i2I'diFwx`0`8),ȲdB<:�;mR&I8Ik\~W.SNT5Kh9_DJoU!R :C+]C|Gv!s"u#(J/B_qBGn1sǛ&A"Z3XY+B\ B{frpxp2f839>>&s&I29,e29nxL%GPzЍ߱}١37zW.lm7jr*evD`;P81O5eb@<۾i  ef3'3"cc"*'2z89t{= B:)\gtEsr]˶LYٯMy=5,V(j nCNsV4ϋ8_ 0rc /9vV]36^[CaJN7~-g]7glTQ.R_q00eVrד׊TJ%MZfBg>7s*+UY1AjOXMB1wH,ԩL +Dn㴎*#C.P"k-c|\.NC0 ȵFH`Lh8Wj_I1+γ3~i/:E6ߧ-Nu-kn΍No~VrՙpDxn;ԦI6k_]q=~mj7OqH1yA9]Ғ%-iIKZR#wG"l`nf3ٳ* ;XtPeTYȅB) yIVoe<X"f׸n=} [``lf6AAgN*/ Iv X v^!ֿD$ȵg;H]f|gh"e0%,;БڣߍPQD'#A+gd|ods>N~;Q7|]X_|F{?oc~[t, S\[.+lFЙ�[ݤz`aZH]lTjA k3/_g <;k>^>#AY]YU=2RIqnd2!l�۽{njF#1F#F C,#MJjp=# 01{<63|{>N.7F"Ի=m_iOg-U2$ɄX x5 +t 5Fb0z]q1ʲ(2DQBMr6/sg_ggoDh-6y?@jrLYY RF1I!{GꀌhR?-6گ1QH$q`L$(@ex}lc,[`_XU.D\Yqa-e5ztdp.mBK9>S~,ƞvdfz,j0ף 2r.3*FD%u2O8qFx@pwGb3e5xʖf'fO ;_gI݁|F' iˍ6sht LmD:tŹ>Ԅr@s0)Ebr4Ӕ8J#RNB^02KwxcJ'<7`2$T,D(w "xF2J\q/'z=)0[HNZa=Zy~a]j~O~V0vU QsDk⿟&:@J1nlh"$q[d4ݿEk3 @׃d'lZ/le sʭ)b2X+@{CT-u͟T3 B1 ҥvC0# eXWQĒGF;vMsUnjfMU2vS,gҺ~6KsJK@vIKZҒ%-iIڔ+(ِukqoc/ZF{JbKwdj%#Q*FA*J+;QmVtq�� �IDATb[~:[٪N3zͧ Wik[ޛ;b Q E%l#ˇv?~tr~;~/bٸ˻}їx2dm&y q'%x/#*l5dZ"H6Éb1v#ҍ"Qq �=ͥSk. 20w.a'0L@u`t ]Xs=`lS� 6*GvpWwxmd\[_տ7}7t{},0c209::b0isw2"65q135Yf4ӌ4s)mcg,!?]b"2(#f~ڳ3OS\iw6|=_ʅMjGRs)snCkD2TG0se"W?.>ܺ}ج`ck$" k˘˯qpp{[ĘL9&nvw(KX]Yv鯮Q]$&p@F =Fl?%xNN _z5g…>\ZѴ} .w8MHpj7-&`̭tZ@5.t;)\u5~s˓Rc_GNiJjP,DZUjΪ�cbL f;?$L{}ĥ EeT(@6G?g'鯬 ѐ,͑ؔ*`/|/Tx!n1p(7뼖=cR(,h"HsYTȢlXovhoc c[9s?]G6^5Mqn\~źS<)@Rۺwr]S I^8C'P5+kd4\k ,ҕ1.ӧůu^;{N+`s[Lq#Җ9D'eiܶN�zZKZҒ%-iIKj$ɳ6 -꣙aqwtG< =?46;+vgY7*44,:΄_&nR=եťrF*S�jSt[Հ˿'{W7FD$p.o~Myޘvp'C<Mrt/xşpW̍_ׇ_k1k$f^8#Y$zu;ׯCW 9Bw|䷡k>[@1O95&Q`|6, yuJ) "=H3菁ouЦqQ}0:(o@6�d;4{ŝj�i NyYLoóvo`eS_މXKW_ywrD`Pdm 3k֚neYpѵve}+^;5vЈ]?4 ͧX[0UEN`ӑgk%}|JLbDzwroD@@Yڭ; R{k]Ē p'_|یju%1*Rp|<`os|<d41RD"&cl ^zW._csC\p [\v[JDNCG$"BK 0S#&';:}Fܾ^~Mvrw611]Sx}Wr=xʄO^>ܸly ;X:fԦӥ #} 坞θwQ7"c0ab3_LKybtq7]VEQ2_R;VP|�0-դF,,}tIj:})67*vR8t`<ۡ" YȥGW<" G:=HeĠMSdQJȆmAa[k2pPͧ&]d@C?8Nz A 1rmD*"s;mјp6ͺ" ʞTH*uߦzy0]_+t6أ+Dd`b<Į&r6, ΌjUE9*1Şm.)E=i&}r79|̲쒖%-iIKZҒ)V1FtTLel3;2NƠ\ 荆 KX49+5ay!2^4ƍ%<6%$I6Q?nXol[nO;.r/~MLj @~79<8i7x߻7yh>{CG1Zl]]9da^_/ ˯ k}8F1(Ʃdk&ZV YvY)(8э`8=e,Goh} ְ@Q{@luێ!D+x#َ|&| %h@a23O}<҇՝| Ʀ]mԜޫn[d{>Db6 Y|\^[<xu^i) ")`LF1XK\XKܸeё9FƦIz W}5`:qˌMY{ܽ>"0XMb7o͛<\;~V}8!v @A6yJ62(F-#F9Ǡ&b k1<Ìq&dY;;]?r>;CcR~1cG̋FF)Mܩȃ#Yh^Hs\'+R;+П)ިZQ<0TfR?lVm cﵙ0.J΄Қ /J~)Ȳ/qiƶ& egJX:p<D<G4LVc�|TOɛ9M[^Z2d ΋lM 2թ95]Zế-9_/kQ'4m[|b?TH F#t=Dyer6TS j:]GӚS-ZNe?<:op>2 ʹ apN"sJPVxn97"_EA{4JX@dFr;n;V`,%-iIKZҒ%5e=(EB ΪRk@UMTpł2Gx f0 A=֒%nm?cAi|!0N׳RqZ#"F ofs(#_fHqaVwG})Yqc F>?_m:.-Pl<w>`MK;901_ేvYᅯ~G>K:} $qACyDOcO:%6iAǸ^r&0t{K 1 s |MZWAVTLզ=ڲ$ޓ va26 h10g?ӟ,H."B>B֏u(:_6G T:LJv&<W߆LJYu#�&)9@4!՜ ﹜pmjd1v|EQp>@_iM$%^z$ ͛;/=�1NQ шJ7<8yϻy'Y__ Y:B3rxHft2:s׳ :c,P$r"F(wbXՅ[ܼv#&0;mܳqw{qI;W?zۄ{N߱Kͪ :Wq*ѳU䒙 er&+d#ȡ]DdÀ- pH%Y�Xʝ(ef8MxmOPʯ&<,L_ A7Srn>W(Vw2uM(P)U2|OJiΗxTdvP=1w{"{Vlo114y$Myq:B / [&E\*PFkH9Xp pr` +Zp jO_BGnk)ZM ,į/#xʽMmgӖ3ϱPaۮH٨x`k?q׋'Yȫ: B<ž;p c$U3GVC"8_t}5tbesJۯ3[z[< Ǽ+㊝Svow e>([~IKZҒ%-iIB?ڞ2H'q="<l:m4\ NF(L4<[/<0SmSa WMgSk19@Is 7w;q[~[Ic5]7>U`ݻ 5F>?wOt"?wH10QrWEџԫD �ѓ1}O}#g3GwH0[axHG^yD6y rDAŕl$bP @s'4>iFBx�] elR W@ "h![{Vpux"$fZ lZf]80s<?FDV-f Yf;7cRź~`lwmҔ~˅-F7nsog vOWZ@4cer-Qƞ7|mk7VxƇ=ڌHuN]! ugg�Q!\D 9]^u~|C^ ݎ G|+7UV<I."n "g CF!{;!qJE6PIK( MqKcS!Q(EMm p&Kɲ :T^Q1ll\{p=^u}i^}Mvұa%׿ix!nK<KYh~Q֔'Ğk#h&(F@:(Z�N_@3o K֚($IF1Y㫿9�)@DP8g[jʿph0! �77y6_"l} ca =SR sIeR;J%ӮM RFE(eѵ<6 G] amm_/{ͪ.He3LW<ugYjs+Z4g4<-y!ԁ`&Yo:{N lwxl ^vjl3uZdJ>~EinI:[w>gi )+.=ķ~qU^ |\UslԽ~ u!i0ĔwAPXp -%-iIKZҒ%5RۃEb-)V=:Fv* -b^md4Pm:Yīcõzz*XMCqR& 9*Uz"S dM�- ]ZeW}&"o{HtQUHGAwo_%+9|_~밳kd D~7o܁;7$>SUwy{zfzV.r4(.(dʀÑ kb%q`808� e!#IPIIːr6Low:sNթuﻯ!q3U~N~;<Û8滰<dasq�PP4'iW^^vV]!C�p*S\�&@WkgT'$ՓplllwlW3wsFrz];GP*//$\W4*°ocvF` [\Թ 5:?~90V V2Ca\X剝rz-cdΞ+$!EQ Q߉FIFWedf/S`Mb5栺(`$LX ܟ?ĵ]K}ߙUx|?Ȼ뫬<zDNoĢ0GCvwnp0dp8D0U$IwLi<uޢEIȋ 6Xm=,CuI_F0EXoڕIy3<r ?p6~> ɸ :[E=[GdSc*iQ%X|wzy:}1[i,ߵWhx<K%rC:&Vա[(MNlss1 y;ag{jfb"= '`-4şG@QeNQxCbqs| ~% 1XkNӺںlg4{Nq> ;-չ\X / I*/0?hHmI[SE;jdZ5m."9姦u& hJVhAvz:P<٫3\F9>& Sm6vRo[#SsLjZyk8eSuB (kO:}OwsϹ8_eG�F ]_YU6=\F;}Hiöa:f>eּvd%-iIKZҒJNpAIdžغ¥L5\V8:mm%$NWSDqҚ,]Zc ag?KDZ퉅^r5�b $ 8Ccm `\'cY+Nv~_\rw6N 9 9d }NR)uW`_HPQw(yǸ6Vg[MUe;FPS3f,5#v1,=;>?˕F 3˶5Ca!I`eH<gqH*̆"U:y{~zNyXETAH91"G:J&@A &VQ+5"B"lBҤbI ?vs I;:tUsÀMX{uP- ^XNay/r� ΞNyoxo߆5n'C-`k{׷$Ys">"ExU ~6J1E:JRN {)Hm9\àS>*`<S1 GN3ۢB<kGuYN �׀ߩw|N\l#i H]v$IRkG-| -F �ڒ`Ad:R)n,ynmoS-2�c < kzO |X,C}2ŻM5y67%P^oSC"Λļ R([za#]GoΒCS2fXH6Xbv-kGs-blFh4}J-zΣ3^.6.0{֘JՌg>3mgR5 Z\.z"kW1 8Nwctur{. j+ʯX*Գ;|s<5hǡ% %-iIKZҒV2Տ*5٠lZv7G1~4U5օ;1pht-y ِn]s^6A9H̗WT! zޞmny7iB/n,`3;Y ?yǠ+7gx'4?Ezn;3&z;Mq$ <78,48tV;w3m.\<K�jSz\9CB88|l 0P`�� �IDATP4^uiA5ۖutӅ~&c:uuUӫ}|eo~looxS�eJ]q?0W!$ID|iA� T'Q1RseH1G(pFݛ<J=@}QN*BM]L6r[]Ra颕*ǎ>Ϳ|W0fj;?^EOhГ MQ>,kp0`{k ʝkE*Т Qׂ$g_S7QYaAbXQ`I:/=ϾȚGN;:MŠ q/ f~t NR>%pU`\t!ʫJ[)»ց3G;kU%;S38<dp8�k9<0 7%4:ʔϵ�bެZ82JE|ܔ +:V %VXS`DP}ba1lC[}S*0P̾R!Z1zzLHQj&Hno1i@Z{Ѩ̔y-]8K J8}}65^�{U(j=_O]f|1Į?]wOtbD%&peݿuA {~{)n=j-p/dlE,"u[\P6gv9/ϑ]Ғ%-iIKZR;*"06mf=hhN8 [U6K5Qyތ\IMu00hL=iMP=}`}H)U<q!nMYA hεd'?A�]niKǴM5~['I|z1(C/,/G? &ES HN){XȔ;0Vz(lvFMȺBO`�d5>rjjKU˵IC9zs^ap@l̪g}m]p Lro] 'N:d [Gb`WpiU#.]ă>0LNF2tF.&` 7PqXx<Đ�:Cݔs].6slY2;cG:`2` Jka:S*Ag0I/?;mc$|=|}IV`bQQBRn W_}=Cܒ(Hm5xI>3_o5No2ex@[ƃDRM)?d9�ą}ysy- svA;ׇ S;x'h 1̋>Q=GFǥ�`gB`H3EQ tg!Fsgs^ҼfR<SZX{W<XCc1yNa yQ`%Qx} Dmi)7گzfqO RX`}u+X]xo벢rFUNG 6 C ew@c}SyFWZͅXy7٠ØJvu,~?)_~[hoXBlϋhIUֆ+.֠D/4 {Ui邋؄s5]aC~PYybb#ǵ֗r|k�]Ғ%-iIKZ, !ghi*&ŞEufJW�A �emS9^ؽf}RU9(-c"nxE^+<A/FT& YBJuwqi?[!+,d%+,)L9HyJG5Zg?Vz7e{k?x}?s簀)).(}XO?e۰e䰖)d!u&q&NNoTǐ_نAU'$l_>rH:`'.<u !ΓJ7pFʓo&(%K<;98zSkJ=m T EG8Gl[*E9Ns7pVC&MrDN^ad4Ik#߱t3KbH>H2B. ]glUQC$azwoKrP1-O|K'X{Hq8иX =!ݽ`8 KWdYŚi6c7W.coE% ?aַӧNcpHV!㭵`B=W.GI,%N6-wƷ_ K<%]d ,@!= Yp&?-ZZEE9 Q^]M$xk(Z|t}?(@Զ;mT3 |Hmh;Slÿ#*8sER(hCP3WNu&<&}1Gk8S!1}d2Dn,yxk_{aւ @帴OvZGB [1�{ &v7t@xL ܢt ވD9V)T yaiCQV0+MM8b)\a8MlP EVM(4eE+dk3y`/sJW%/K)|#mu]m^p_7½cĿw.|HQ˺;#Y[ϐwݫn�D[:O~t=m)ú?jT͹aZ^%pi .iIKZҒ%-ĀE%nuB@P 5d<%[,j5NK\X6-SRY<j 1e5T �W_J$z@Eeۚ_TqW׫<a kRe7zQy:-[kyeN]P y`Nk4D)R"09< yHx. k,[7vsܸ~+}}E8;tNNBYK%~(�FV#*eHBGǧğ_ZYX8 ְ$_hal]ŋF;`EXY=P=H <Ä /d2IK(x7PLU(X =&U=2$(;A1D灬$C/4Udn9jNua'*G%%죘̅kWH%ɥ<m!,ZäD2k|~| /q='@rPhٷhNJ>);hHӄa>!Q}g7}D{m�JE>!Xȇ?d<\ %�q$=;yuNf#٧!kߓ. stxu܁G7bUk[*�J)wx͵{9/"/_P]%'/vk/[7@loHj(yhKfd`d✚.9ޘ?]㉦ ~FHblEsq`UTn@p/@k\QγTRa>ƸV/$ \~Iy)XR墔Uh>9) <mXhd`"}E1A3 eXqF(X-Ol92`0Q `0Q] "<cubZ4G[(*A&1F8KY|j[emYX;eR2c4 GF̤?d WC8v8L[r7ӕ7S?irkF[SÆ Hb1x,,( $b@!Yo\]7}{aT`£֔p|rejj7S qrmlv_[ yj .iIKZҒ%-iUJupψ<#gxk=T fi Pi3=urD}@ivO|mRQJYRAiS)o^pXtLlskmC 6ۦq\_SӼ'_$Y*9)r!_/?~p(sy&ee^SYwkR~ Wdn($.tƐ|VhF"<Wڂ(ϛe�Fm9uWׇaPN �CBo>^U^~e$EĢh_ !"d!I3lɋ[$MTQ`ŠƇ;Lw8JңMɲ4:,5�!F$~ 4Γ@ �"9|?+##P(]*[^sLdU/bb o~4?Y>Pv릈q�bHvwnp ncowHJ{Lȋ Vk:u>_b :_S|ŊF!h]ekF $ 4׮m18 ʐddBRDHP5\;GSO=E_W^pHb5UT1MF<Sd6.#@BA`Col؟&3!0tV#=֍UQ |?;}WK�qᘷ;N4ה|G5^b7穢1B锷`ⴳ�FR(ƶ L3"u�tCௌR΃͠UwM[hh;9c5Pa̝9y<vRݯ)muYk]Y�a8&ȳwh.1.hO WuvSo_oTwq㍭z>C.\|U{kռw)@`93Kc*s/&Q ۩tw,savh*x0բow>&ЫeԯU[=XN]Ғ%-iIKZjr^)0s(̳=Wq),0vZ N<DCAM*wmbeSUJPL[3j<MT@ڌ;V0-j_ͷ1,o4`XYCu ŤV~6!o3_&#ϡo E]<`6\{#xA `̡ MtF5`KS}T. ,WTޫT97]C}=PPcp:npV,ZѼ<|*G|[ϓB(F\D9uQyA>܍TP 8Ճ|mN[$vL?KMvL !29i6.$sg=2j4�$jR~[ 7?5">/ HE<2^ /_fo,v1N1s:Yl}&v) Cbʋ4 zxn7#+FnA41CO]h>I!S$(v|2xt&0ʭ-109"eacaFp=ZĶ&dh^⸆j7,9^tpɃ9;uP[;�hPRg6U 0f*^QmÑ4fi#PqG;W=J+kX De4d<:U ej9.sޔJ`A% c4EpbXo*l'!RMsV^XG" XW7 VOˬ4d#紷8FB7#ME8NyF ׬{*Glm.)P�3--No >y!kcdɸK )m2Yi}s̙RFd�XQB06|jksw_KZҒ%-iIKj4;]T`c-hzǹ^ Uv"ҩ.LTVv |#$.h]Z,Jz X5 |l@/j2=P?K|>FNB`{:;hh<Γ5 -7bQY?чI_Pʋ)6) g&P]SBԭiEed n_BCU [U lbn1TuROqKkAd'<\YPT@- ~} ןf$:vDUl3,o/\k ;o3 I lWsuز¹ xt_&a-1& 7 hWȪX#[a @czٗf_W+TP!t%sAx3}5Bu1;ۼrhkI5 I3WU|?O$\|bxjKoNﴓP A%(ą׆$ 4l5J;;+t:ixH볝9[_هF(L|b7 dztSp d6$2)CsJñFE`e 90}OS&3<N=gθZtQ*̨ʴoc,JAk;㾲yZy( Ƴt-kQ*aױ $iDj̲6k`�9vY�18bq Qd&b/z[:|NrxE6F5T.$A^*XiWl5ʷmI( ./֖dvf~ Hl0ҬgNN7`K/ I|Զɷmo6.&_ 0%`%2>Nse^™C[hL67aucw/WT�P!wggлa6PrZ% %-iIKZҒ AnTڗdCQx3JV/T[yOSY(lKrZ=QL]!z\pE7!T gJzǚ<? $a08vɒT$;Rc-<x(RV̓-4p8k R8$4_¸z18ׯK?*5Nbq=wtoGW6 R jНU[CW7H.� b-aR(Qu[w.4hȊkLb *&C8<k{{6 YKb+OUQ({((;Ϊ_,j+ 76*`̽r3wwHi [7q5v$A!MVI>" !07O؛l_y.դ/bs P"M2VVVT^0k{eF A8: 3kS8Ox+UH ûm^ 9C9�<U\JLQim~9jc=ym\� jػ7QFy6=igQ|!yV^v"ev]q-ET‚;TI|ȝq7t灊d(:@QTi>:bhoeRf"0՛YSV67sg4t 6r<B7|]fu#\[5e:P{[ZpG[Q$C͛GsL <%=J[[[Ykz0X(ܭR ׎o=xeUfI[wLxX k5ikzQZ<P;ܿUtvh5ͲM<ڵkU#x:JcZ5~l8e;FaI�vP�� �IDATg_lb-%-iIKZҒ%Aq:.@mJ6u@^&mFBk\kԋ`Bvg"ѵXVb ȼP_ J_B?X$ 6 ũ](=jC~Tdm7�#qr>뤉}˻\uu6T}6AqMrkWDaP-Z  W {x ',缆= ŶAˣN<cʶh c_ !:+9(t|R&v^2#]aLmXP(c 5 dD9o۠ҦM&9w,0w&Lv'(3{F;5 {XEvvekkB$n ٹ/Lv( d4"֒K78s&BIc8O5ن!c $Mp xB4v<⅝|�V0 :�yOڬ `<-<ݔGQ* N:~oUwliB˳aj&k>W[f?1ā mo�<̣EAgrf�5OPSɖ4$uwTKH"I;h+bƺY:4m![ٌGHRSکU�`zt=$~a a@ GTB[R~;wحAE-|]͐10kMN]Pj v;y-G˔m_zo|ԖbZ9/..ϤYFfovڛ/N[X23_]<jm?z͐NYKpk9BdcM)7:O0]|K]KA::VO-%-iIKZҒ%Rr%I?J;^8y &J+ƌ{˓y )qrsM&Wcu2>[�\+o8S-k_|"D5Ҵr,V+� GPX- V'7Vy]C>qwcŰe䏟5^:-}Iٲ-]d.+,K=؃i6e! >l=AkwD:v fυU` iL8Kp^ƅ}ؙ7‡Pu:*+.Ng}$,S=z|YMr �51mj3U(FsWP::CXC=<iɐ&4j>=@=p4*a+t3.^Ƴz7s=K/@$ǢLr<\F> 4F_OyJ%Xʇ>V7#z ;'Wr^7;y|@> ûwτ^9)@z?H{f?siu Ym:h2*PL`Vy;ͩ(Pk5=ww3s{d_|Nvأ),՞o(Pzp#AKk$Mvr[D[X 64#N5/uC\6.*ɓ~)--,dHDB8Be(̙OM>{m+5Ϛǣ?w3M+\@XkmIGI$h;z$MS\hSwd}ASkpQZx/HY4xvS!|7`Z,6djVJÊ ;x'hٹ}G<hƊ$)$'K3VI>… \x]J{4 R&lk`U�6_@F(^8x><F2]p:̻fUlOT/ݺf쒖%-iIKZҒfW~={P0<r$?T4ø5=/o*B6םrɝ]2vϐctHW"emM/rjs~oOO` X4V Ϝ㣟<G?q.]ȯ  ,lbT<(rB W/<SHR:8ftufaY6~l!,pn<[px>ߌ<eC}bIct0>!ɏPm>=籗u(ʺ?w> {7?�Ä.c&gNH9ÈBĖ�.t^Dվ[Ќހ*W/]`KOg<|BqLkRY+@oi?|lB^LH.J%䓂o5vGwy$Ww;<$QL&.\@X{L Mqppp4*<P�~ ( 2ghڐHD ~S]o9j;lTR /%TDgO~D*P藖m,*k<IkɭR$[$G[Pi>6=×ǡ2siWGa¬?G4DљP%mR:,oHy6[XoXv5yT> *.LbUFli9ms[2[1Ĝa=dE J~z(75qI29(CQ肢{c&g`ԓN3'Mu }ba{{6\MZ�7qp|5em8l"svCDMua~2D|-θ* `lX YPxosclmC3EI8Zsy$Qfxzx]#9-iIKZҒ%-i4"HR1c{t:2K16}ݍ?a*DH)\!!jX$"siz^k}[܋YcrT}+"Mվ߭qH-#!յ>ćF颕ARKt!wnr=w7_>>VL�o\?2I5X2s� C ʭR'OBfae׾ ~[?:rjkksq*ڱFhV{B??74neV GwI cv}pslFv3VX``x@y~I}ٱ ?q(peFN]') U…Ky^`(!xL5WT'ÜkI%a=ܷ^:FTt|+EeuǽqְkjJ96Mfcx=~,ꈲ[c3jK{Mh-ZzsFrBC;UuYxԳuL+=*֠ubFhb78XcY^ρfZhz0{VZ|rnXU=]^^V2 -jjHѸßjRe7Uʈ7I&޶~[U�ꯑ ҈|6I4,VyNQYMnK%Mf͛oqr{�6Ϭ8̬gy,ά[K|槽5ja_[f}뵵/㮻4jt%.BqR%Pg /O+T+SYTs0u JVjW[Y..P$i0& Q f]Ғ%-iIKZl<^ XT0V-Ryf["YRStmaOVWW 0k 5j@kMn~X1Z6Ɲ e3)Z6Rixf.;WajmCwhv+R ?sQd2SAqEI՘KS'O~N?he<W �kwwm8 � ;w즌k@0G>ӊYnׁ != :t'6y M~ {t:]V+Xk)tZ!|E4Bሽ ~'ױ19VW2zT(�N(+h-$dX%P$|([qI)J_<ŧ]"C(}o'D\.&<%-T_k\h9w"ne\yVuu=]|Y,^Yy<mߛ.ټ]qPNyWΗX7O0V|h̋DfW:dYV^_ZMt Eg,_�ƵMvP&@ў6׀]kkxx g�zxh8ZcL&#<z kIӌ5z~YŦ<پԣGcrp7zn*my%~j>Fw*,FVWWy{ç>$>d< MڱC`]T9#nc Kr{><jYJx?K >NwZk&sa*u'2d񒖴%-iIKZҒZIk]e<O}+垬3>[ 4H[P~+5usm2]>!,- C"B]` NFe4PhT�ւ[* ;QUB{!j e@l_KY*#Sj /'( UK|@$Ǹ`J@].sߦϑt<& vX[??'E>kڵ-u- Ͽ¯Pt<GC?'iS(TIʷpa'H-qbmPzݱ63N7F t#ꡬvH;ف?uOa\gm$bbc9 3&cD𗻮8<{0b@jcU6Ig;&HEoR$Jt颲!YjXphp I, {iӟ-r ?GސAFA']X37*Lwզ |)f]׈w;"H ?-R$:Un9voojw+Jְ1 eScO煈5T4sl.֩Gͫ}ĘDamxH m!A+~(qLAH`(fϥJFHT^ƨSoXH<]|cHR9  yCED)GvPFhF%'<~ e<rmN&T덶]&Rykm^=J$cIPRնO5FC?>8#:lZlPL!3. U1/*AlR%@c sη޴ΡݮSgSO>\t XIBHzI Z7򟺬,0-RY{,Ro2x"FW^4U5׵eNz|DM#O|ю9M1n#-c7|{;l]`nj'cIu:t=z.nյUNm&gΰqj׻IKZҒ%-iIulbF^Rq!EZ^mBRiVZiFKǚ,yUo*]Ӕ8�;"( aX�]ST7{ϱyn^x׮SA%UP3ڒHRWqTBƊJ`6SiD P*N1`i(BNc-Z̦5fV0;qb=VH(P{`G&>{~z贃s?3lܵ:t+*_9׭u}-zݜ[9sL> &9<?2{@O$@꒗ mns6~6 W)sZ.l]M> nډOT^k݀EPtR[Chhڅ@__U>.zmi~oHc>5~ѯA\枻α؜p$Xz@ *+`<"!k L,\9a<"x C<*RCCe۳5,nJ;cɘ; edHQ01C6O"@ v8K[k<ϳ /@w1EA8EG[v@۹ԲOC[3dSU>;W.>>�:,ÖrqT)EHn)^EXӚoKcumQ >�{6 ~^<s)a_4<j @4gm -'ܘ[c@T/;,jiCunkݝ`p8prd~Oad Pgp**Kh S\+6zJB{J*E6TR>b3Ҵ4.fM8"i_sb:2֌Z?? mⶭ) .v?Tm3 ![lxis->>7o2)/ BΐаC@l z\] l1V46*(t/ǚ1ƌ)P*sD(P;ĽƢD#+QQ 43|n {c@ E{p=j)$xx(q޸n~TԴZ;gНK@ \|U^xy^~;\t y^P9I6~Wц4Knjc> V,YAkM™3wss^%-iIKZҒI]X%v.]]3T6/2T*ԜUJX) ՙߗzJQS!?V>8R`5+Ĭ3shm}wCNrlpa*g$)RN[S:eHP* 6z]sVSne_4fJ41(,+|ş7dŒu`䩓<7 _ @AW2~ H̟QsBm]wm_oL>˺VF ylo2)= ?霢�gQÜ88 LOq`ճ.sb討kՁ (nWǾ+TG_7muHߝ;ڴ7z'`){U4ỵ%N_} ^M(ln1a8aJ:- Jvрp8fkob<mX }g8'ClQ"g}�H~O hr2ĺtY `0bko맱Fwt #Μr콬seT{\^#KW.3(8 gyˁHSy9;)�POw hx2)CL6C3_z[9 m :{lAߤExһJ st:MyſCJ=z㚏`e)~V@xwPeaq ۶|+zov!OK~L VEeQT 6\TVQ[xǸ-ؐEX_`8`tTukd 7+"ss>T@ِF|1ΨT(,T SaS�1: .RFOJߵ|CE|Q.r' "y^ ռv'y5vنlF{6ϳw.="F$oBSOBp2gUU Wx`N;[9U"E+uAm% 6^C4xz^[{YʹiF<o|]ԝQwP۰K > %$Iɲ(:WW/r #677<أtק%-iIKZҒ&1%1Bŕj�� �IDAT)(ӧWw6x>_ >u #8c uѐ6lYTP F$X5k *a0ʫ8X˺<Mj!X<]oJ`j iiR4f-cvzqk(ʴry`^(s" UԖCĉ5\U!Z퐙*a I}8IY$?CK/7^`2΃|Y:C>_⡇#*ȃ`7f:?|| <#5n䝢[* Ƹ}'Otƣi&ϝUFQ[Νsy6'|=9 arIґ0 3D$Alʭ'B{`<f|8Bixy?ɻtOyd}90�$AA$E$E(ZIֵ [ڻ %UAmk;lYò(JD<@ $q =GUWzd{^ A&j<^CC1b,u6AI<)զt5N7^#tȲ?SWb}}M2[ҵgb 7+ 9ҘlJvm+@-WyU3Q6~q= bi{8�ѹ= !B^ypxmxJg.y[8FqsfϜ.f\ mqv] @)/'EH cəxsZΡ]t X F4%uz C<o&Yk-(90sq<ڨyMXsIf7�ET~;&D\m0|Pke(!fKB)$Ve'W VB]6^f1+y�b֘7uvJc29G!Z;9.Gh~\߲bf̈}!�qzz<}F!J0:<[D4P�52lQl 7CùPvam(-E"d*~ 2<26ʆ\R g/و;~&|>ά#J $"Eh%)h1(R҅r9yp q�QR0iJч᱇eu*7*[ٷ sӜ49ME8B1SZ K! �~, u…*iCl)-' 7Ҷ"C4`?OU(>qmxv]-+.Wv0e>,=Ff L8h jXD2{Ĉ~TEhmB?"O0Tr4)]˵@nQ X,gk( VHX"N. cP[Ҡo;Nga{b{0cY XB?~wO~CWsρ]@,A̕ u[}'6=KS~ HP ڋ:L A-P{@}N۰J;ϥ׽Lu,p^00eݿQJ"o]߶Dgxg8%Z.6x|IX҇ӖdvaF5w ?뗹~5Ci]C�2ɀ.$U C}6Ξ#IF.?`\.,ؿ? z!JJZi8zF1N XGOa;)\1t Ʌv0BR jk I!4W@hKɵFd1^|V/"�M, [F0{𷲧Wʴ^v]v�#62Xol*`βjD0&4PK. AM}&G-C((΁b9^m :˼R>6,t`\�wgn*~D 觟a[4M@_|(RڭJe&2-JDkS.5XC&Iz[1kP$gxd&_J0|VTO 9 !Wd躉4)˧ށB@hTVB)E61o$aC?m|~W@ �)˔( g\8m!|4H.Ѻ;_+C-MIUm/R Nm;^FtƳ۹z^]&E8l~C"%i+%ٱ\0\ : 'Ť9 9 _<x}*O\!E-IR{y.PZYlIEXm,REd%,I9 h-[}ux䶷UW_М49iNsz9Rʷ�lR-vDcx}q`R FxYb~fDYu *zpsd Ƃ R:ͮŇ2fuz,sD �h-E(s0UC9!YJ"lll^-[Az k al<9*&#R+bU X?Ήg7Os5YC+,I6N,_30ZHro#mwr刺N='Yh([SOqwンǿ .Npk0fҕKo!_n"r}AUC_V n}(ᅢ0C>,~&-0Μ<dh8G S:Ԯ7>Ds-s6W?~?뎟z۫8�t "R>PkXCnt |xfoJl6x)DN2Rhd# +gN`ecc('ѺXvE[<豣E2KhcH}2'Nr6pm95ayTQZ{r`b5RPx7_2msRH9BsmB6žbW/s:S pq$f5Zy&x}*w[rv~TPb ƫtEUqZYp_LAI~lvUR b`oWu@3w1$QC4Ei { :wQuU{H:t=K<ԓlm'u#x,< �r B9vt;+kS `NTA-:y}<?y, r3Ea`LQ_,e.iΤk.)Rxc/|Nx/'cSQkG,0JQ\oTg ۤ՘F>gDQ<޽,v<wy/G%\#i#渐su e >w_<j.$B*xάi-M1;ǺTcbjT)`\XcY;FC>2Z-ZFK@-:7䣜&Hm“?SO<k^w o~s9iNsӜtI3)TH Txmnlm nNT1G�-!DBi U=*`s8j�$G O u~IcrN0ܔJ2R-P zUV>'hb C<PJ0/*a:ԕ1ccKTdL8z|!Z' 3 apt:`0!>zۭ|G~78[)V㾹o>“?>soϛ֛Y 1+H:+x+ZWbFI`�IYu4YX%Y-(X;0\|/�IQ8[8m4{'@[]c(!Fχ6>[~�% W,mZjJ[e`By-ş/yNE'mwX%IRbFsvE򪯗Cȏ<?z(ԩ3lnk{4ʺWg m>O06G>; ơ% 7.keqi^`Ppš޲XSV̚u'@r%u c*PTN|wqݍ�]YGRk^�J&)ԋZ&o .o|&wսlϭ΋\8 a<c7uo=ׯϥjw`1g5竈FWĈ (dYV�O3Q*wj2^\Xj) b|2+@>GOqAVe ?f4z)k;f!o[ ^\^^A)ٳk-Vy2N|,>wAhƮw] |8lF5[M!W5. ]ZIP_ﳶp¶P}_"Tla-9ِ|ҵXtRtbr/9QiE0tyY^;w C'! iy!,l`Rra.r3BJ`8d0g4r/rq펳7vKbьm b4"EJ;cIhZX4I`o99iNsӜ.k4a#kNr5pʆpPdp Ii .~eO�]e{PX0-$(%`HB/H f\dvʎ5!bmE֕ k#f,8+1]� _)0E x-Y6♧ -R"t[A( '%ʁa֠c`_,!W^?5PFԭKLK?ӏ=]׾F>;Đ x5,^[Iv(`&V Ѹ/*،ݙRQn^; J2/6ͤ'Ӂlۅ.$pmN{ݼT 1pa~;9_?�,]?XW\:oPv8r<�~{oUWkp^CcKb3M4inw)ֺ|6Itnp \n0[HYig_;k-=B Maoۢ&嚾4R7Pe<g;q8A^/qyeW;v>DC,#IWVX]Y }iWxɔ.1?> (ņkmk{|Q~ j}Kxm N1`v.-\nZrL=BxQw|Th j?񹷍7�>5R)N<>m`d$<~‡P0 Tq΁_ ;7c~5QVN38yj:?I ܾDU�FklQ7 D:[e�!Oc!ms6lC(PlxķTVسMX+)f0 r~ d+XX'Ja8K}0.fā }$;鏸NO[iQ#4EpR)9ir3<{'d0HXZI060ưJf[U75W_ICtj!r 4IbQJzOۜo7xx˷-nz/LGiNsӜ4˞dʡϻ*-E0"+쏛'*dmzurh_ȁ&JJB[W]c ePua+uT 26VhK1@*"?Xe^ �mTr�1q[K<qC_P+!›GȑvUMM/ 5a0]suk8{ٻJ'GٛCp5W+{S)"p09r8'[>?~7d܂^=*{Jj5k2i mq^R%H8"/ &tu6υ։Օ9 iY8/\2<=;{0v3mΕg-Ϝ<q…3 ;oJz-pվ2) ),?đ}PnMDW TNBZkgoƇDz!Ƚ;iqYZ){D5{!_\ch*a?eֽcwWm핢Zca=`+I~ cwwdqd G.mp!aSA8||A^ e) „ ĕ7 8S NN54d`RDdvE*ר< >La`J)Rb`>~t|Wi,2NT4LN-J٪ Y(%׻0'С,(jkZ&h u+D6k[:ʙf˖`P,Ȳ D%8:2֎x7Q[,{4[oxOZY7ej1̝ WNgc<pj hM8e (򺗗\%ihl|u4l)XQ<n -ϩaAE~b#/@v=qn5*QX,RZOv'])t>l!f@9|0hKV3wŬF ):6gϮss|Mo,MB hZcX6׹㓟3GwVt49iNslZKg={zByaqukok֧n<A-QE8`_-ݙf>kK<g0ZvI8$N*]:C"%ZJ!hyIy%S@ON۩O>)(xT=lfc,Ѩ“U+%w8y%O,`r]�}(g_@:$˷?/荠 A"a!ޯuAp5W?|λ&р4[!`4<)| _m| bV)x!}u%1IL|.3_WqM6´6 t=_2`G M'(0oKQSwh_S& >Dzv ,vY�wppq?<@#GY޿ ^"%s\3! E>1J@&7lol1XߤetG_*+裼+\wrM0NY.}kqˏB#^#nh5:VPʰ~h" ە*z{!HK<<݃ ?Au 59ϔkN}޴+xsUQ}yHy6_蔸u[[*W\(;�vhn75/. ]lc/G )8d)eTUcmCk"o<߭}w4)j/=aևK]faaӧOk P+,iKp:DFQ'&>3!wK&H\_;,cD(qj'ʳ[�ʵz["n]|R5چ$ c4F1(:jNC K@k7,&ٟ2gސd^ZjbDNX!BsR}[?c¨Mqt�(e g H!1V<feE cPv=Bz_#'G8z`B6X Rrm]>4vq ]x`ZEs(YCB7N~g-�mII69šysk#wY-hmVj)LA&d٨u eϝ;^<Bu499P z�� �IDATiNsLiii׼<67p)bp ndMf8饱}P$y9cmk�$1a@WXnu(g W"M/x&/')mDq S䐍kKp>[)em[  eł![Q��m{!Z^VksޗqG%.m7snڥן3AH^ʯo;fKqXx?VBR?n Ԝu2bӘd=D{ qf˝ Ssmd dJ1wA_X#+q:Q]rhOc@vion\T_s@<_.01`dE]y%<WO=ٻw/2iёQfF] c&k5݌F.o亃85m|Z++\{؜vZ89*iS\#`]/TL^ G96VS X^=8 nr6 |>J|˪@?'{mO59djMqR#"eD؇W�ƫ0c D:xsz)mڋ�o(h%Ć s!@sW>vsO^phl~E,BcW֙ pTI`lK 3g(rR rHi{NȚR^r M Hwכ@[<ms`{ ?alYxj EbscӤ/) 1AA)Wdo:[.{o)/Ɨ]$C0\&G*F=$ rOjnt貗r70|\l]b_?hi"CxM 4Lu_||wK{|zYF6Q2&(^X1B 3G}OH$II]r+~bh, $8VR Ϟ/>1?9iNsӜ26'9Bb(1r#Y'*J`/ !O^^Ȱ0 ͳ,Kcl;vs'Q#A@,)F$y[o Wd0/(,?]8'@nҎWMVhJ~A<fs{uK! L\RR{/a0҂RT->֖llǏpАL+G!/+"9:�!?09d#??~W+˫g1"MžEơ6t$yq(ڳ `~\4EgR JO.^1?.ny)zvYBփ _`n8/~~-gy?$1`N]>ǯk=ɱgX3TYMNzǏsXJaw\O|M\z]u`$]|=_r$k-N)Ev<e0jIp8eHL߇`sԄ^$waPnO;>BsZ`rn%J --.¾1Oq+pVo_l`kI2x/;QZkxò^TV@�0 *`{CYcw濫R1#Xi~7q&\:!~N2y1ç7PZRM[ wLogMl z6K֙{k۪KKJ  !PI¾ګXXxSJu5a±YךKISmL1t kLb#i(שtTx#ς(ND꼗\PpeJ{c㏭e|0b_0r=b4t Av;,..j^ƎPh?@Ya)Ҡ)Z ?p "�#'>s)ŢC w|䣀SDZkQ63R&#HtZ`/<ì,V!hmNnrFy&,Y`m-+¼eS%Ir="MVSO>ʟɟ@H69y!(Ç 6{|x+m/PiNsӜ4ˉlno8= ڇ~7oePAVJ'CϢF{'mB?PJVU]V@}#'PlEẨ(X+0y+P>`.AyՎ=d o p8b{8�Hz vT}g1f clWƃHpYX…ehk{wduEPMb8[VN<︋^F:s ʢ_Y aVCwYXy,U~d+]Y'OB0A-[paHx{} :p ֝I$I#8vV8Vxr5%[8o[ͮMe6 n (nl{>)Nܢ\z#C[\E-'[F/&�{{0y,kxiZ d{B9rcw:n Y06;[nCK{8u$m|3/$pWjw\$,֖FVR!7#`xe$zޱF)5*rΜ9҅+W-F[�N2Vtک/Bų:{ܸ7t(,.*NJ w(!b:x0Ɲ�ީ xĸ{5eqxs􆕅gR5%Z؝hwPrڢz{̍Uu}z''PFAuX헩쀙).]XCj(ݲ)GSgeVD,Q`3Foo@Rxn&{�rS;4CTϻiR P5bԕO8LHQ(%KK\u &a% 2I\Lɠ5|bM-o*k?*&Vg-/ /zl�JĪGC7u[#>Ŕ3߆'q Z[E6rBx3v;GGE[x)C_ jL3ֿk]c|^Ti~}ѺXcc\d]r1?K>W*J;�t^JO݀lDH}Gzŕe6"e$El;DJʁUP.W0ڐE{ ,-.kO?S~8Z&iT`vu:{r/}iNsӜ4ˊhmH)|MBc܋� ;ѤoBmb{ܵ!Ţ(s`՗gUaZDʩ&A x'2J/b` AHWNApჍa I, kx6g B1'.\/+x^5{*V)l{(amMI_ ޥ"g4w0׾z 3X�aƅ]?/XGK? k|U/p Wg<cl #QQp5WS?ct:.D:_fEZ.udq쁷|I sr�F I "R($Aa<Y5Ugµ? ]Vu NB Y8�~#5˵$G<+==.8ig,:lV_o}+xkduty K s,XF&r dIh,^XV]ZBVXR677X__ IF*}g?YZpp\"ȇ.P5@sEwԔ3Ѵ=P>Z%g/Cb`%&=�cRpUcM)N~A@)V3ocDuano͛j3뫴8C_]l0CQbR¸+/N rtƬNT ݄>ƥe95OlS)BϣrqO:MVo+lQ. DLtnBv:Qoˎ])&e9@X; Z'G=V4&^k⇭@?#ϱ6ˏAk$,,.q[n7_SOaFc$zNG0$Mw�RάK3{`yy,hn*vK% |/}25$zZkޝY(7SA)ZQF'瞻v`{[kF6# P@"jvYìZ\X nӰIӡZ@*H;|q}N +6BXu^$i!QJ2<rri:</~+x9Ӝ49ĉ>}g][[رcv7|y|y=yCZж%[j.M, :{zĞTݚ`+ީ ah#ocb:8;U(RXAѸH2s;6ʇlmShf!bJ| Z(<B7 EZ:r *JФ&%e1jkm=|??$PW}Dg#k`1v0!ZX̺WH۽@L~>[~o=_=lnG٨]~{yyaO3ܼ!}(Rpkq5ǠDg=hCk!{>)F5; '`8SuWdpIvbᢋDigҏޗFT9qJ�15z"x"I}g�v�W/ W׿]͂8My0ǵWn .E?Q_<du!8@7)}RF9W)HHIy^ut gxEۮ^W؀@7>�Lvavi|'/ \J6/$ڮS2KF{Ia vm{I[ϕ}㹡މ#ff*ƓNb0T-GeGjcy'�B\#&Tx"v K滍~i^E. lz6QJ*ƢҶK8*bZwc<.gnúDPՐdSMeɞh0}l}GF.8 f}H(TъlDt<<)yJ5z}78|YFyBE.P cyb'Ӥ1ht9i}r8JMGfrd?O~ $VX9;V>I!)^yմU$i`+cPmX# FuN6&ks -Jm?ﺛCW݀T<ΒgaasDa%sghJu?i78?|C*~csӜ4KFO殻*~w:~gv{y_C֒= vEGROWJS9?.ú0TL֎+Jdx!АvBp^9~on�-p<!D*^1jӽMiIcBo-xZ[ja :B"l~B&E{]0$D/~?47t= [(y� yiZ4I?{?�7#u k_?yǎdʂ<.n~=FyN*yN{ vyTJvl^j8�6y5λ(S{@ܔwAFC Z8/:aa$%~SU]-q�6OžpuߋA),.¹m,:o{1}e xYة>Ox8l(x8V!.-"L!=H1fzf9T (7P![/Ӵ@zn)ʼÜ'Rl^8gxj_wÎ#DL篙$ 3>/.׬%MRRd>R <Vw;C\I�-[#9fT Ė2]1TǾ�z'՞DnQM![lJ;FcϓAXdOP_A8W Yx֟g;ݶDӳny͡I ɔ/]jKV7XڅBzOLg_.]HݔU)SC,6mrp}!vL|U⿕wba0Nb)6g-"|�4Za-1A)]Vd$m&'AJAىb9|@:Wc\[)1f�Q^Lҿ(gmmXq1fq<zy?/县 !ZHgV]ťta1f ŅE~Q;Aqa *M‡b$FDY&g*@cȭa0=1Ia =Kg&ifJHڝ1rmI6<wyfTivP49iNsz `0;^eYI)NC>4YθԕA Rر㳐W`ul\$Wk.̞ M e!l\֩) <62{>ʨ4D"2SF2F$TJk΍DqD}6*mi}kq"L5`u`~VX0%>}?%@g :Uԡw2ד[@*H0hQ8g0cIVc1/ z;Xٻ\atee?T!DN(htE/ 0-NPP̦ @n` rJC'Z#Hn^)pQ7N[d^M{&(~x�HqC}aC"G݀o8?~/~+7!}Y/}�L5LL6 4VhЍĸex'!4!DZR,X*,0d9dæĺK!'O XjVP@D c4bҕSWwÞ'QF(UB"MJJ䒆yg8 Dtwƛ>9$j5<JG#C=WFU7Mف籲 b3*]|c@A3B eah!f' ᪍{)M4?-~ ӱ=y|Lׄl1/f4Vv~<NGhTY!Bׅ,R,2֯/25jkڀAFwiQ_[(ٻNxQj Mm\I[ Vl`@,8P ?l,$s@ W #671e\WkMƪȎ=v&͗ Ѷ ]UBN1^De!}VMeIJHҔ|VDE1!VQ< F{HeFƐI[%)HDpHvaXSA!{QiMbrVNN|b0v $F$IPRv"IáNv|ku7\˜49iN/zꩧv [裏^"n^M 2NxW(j$|8ܭpƭMK kjSۍ53zicVB9[£0'.�EHwL%vK6Ȇ9‡zIV2WW!f-R( SԔE_ υ*�� �IDATckaDŽPa^qh+�SOEXdpٶW-aMv�ʅd{|ٿ"|ۑB@C?##q":憠</ǿL +tލNZX+Imo6c>cO".~~!3d l1fc(6PcXO &s;l-#�NnZdGanĽC‡{),s'`A@< 8ja%sv4ac+Y/, xZ�1Ih) ^F!<2ENMWZw,˥hY}7ÍgNnd߉ n{Mܘ,| B62p2 eF\^ ,T> X9�ۀe9YM�a5{tAnUBG$J!l__FhR]rbm3CdW5Q B9.y-#'Ƙ CYZ Q30*f"#RUڝ1 |+5neC|4iZk)D^6>\+@YT%S cFg8!#X0X|-s|(\.7"p{,  ܮ`yl._ Y飳ܰێYTcz+d;1>tg<2L<RN8U»[(Q:{ /.[RvM`P!tL/SJ]>&W^@R-U ^q`|\O~L5S]wZ{ի 3-.T] ]%y:UpHn+/*#|Ȑc!;=8 }#^hàZk$ϧƭ_rk.-g6[[[lK͈Paq;[7-ӸS]Lp:';q>tY�ǎsJNBBHem l %%V9(/L-j|{9~8ˋb@ʁRa#Sr2l#Ξ=CIk slnXYnDi4ܸ gi{[Yݷg,vA @ёJޯ~eV4r SO<O?4^׿79iNszУ>[&^OW[[[lmm*ĉ^ #,6 p6s^t눏B慕0]),KŊ("(&x�A9&@k\֎+֠D.ã5|^ToٳՆ >(TX $ch�[&U"Zfh*(T6(\J pUeBJ|?;;\| &kc ghХ#s`y=?~l/_ 8Zѧ:f/ojǻEOcgzE0 97p^`:sÙlpqXH|֭%hn{  _'Ձv͙u:a}'Cd+n:W]CgYX?3>;:^{?mrsD*T!+) V!OlD0!`u¸24M4IȲ:Y<ȩ8uF 8Xho֊w/v ޔXO_#r�J&ؑm"ACI^}F]ma4E])bJ]"8XVWAr,_w+ZިqyI:˜wN9eЁ(;p]l9b"`Kn-!/‚)?bPLW*<ѣY\Y;/pj\t;^뎹JFQp<Gcn2%a=*o+gKQwu8u_.TUMuT@]UYQ냗YMX-F?7u[Z4y[u,@H�<#I+6yã<:BBaE '"1 .YsC8~8[}1` qF6o2gE{qڐB(VxO/y@v0DQX[kQ*QQaUoF&Ba悇|yjC'UNfR`V 9V-ynA%Xޢ3,v"t6*M$i$IfCmEIx@Jկ?A !o %I[#+tVS* :H Z6_뫼{K&׋~i~w~??;__ ٟѣG+ǯ~eaaasӜ4ˉVWW9w/ox[[[\`c?>`Q~Lz+zk#z >==9sU|b=֊H- D+dulr; c^APՄܱL)bG A٠kZSd-_S k 5š(v4"5aF"J)h>R:!q-ׯSNh^z*=Qa6/u+ksD2Dkc B {~�+{�֏?BQ'{RA4AXg�y7IuX: mm +8EC7HcT4�}/(p ޷׿ˠn: ;c gJC=(leZ'52m|ˈCҁ"cN;3Q�dV uwn~<v} ੧'絯^7^n>u~Ը1F:m꥟<n6~!*U7g1Rcqd` t:aFR ]Dd?huWE�EɋK3NV)-i:{|<z dϻ=K!,]Y5�GMYq-= Ǯ/9:,FD4nAlg $G‚e;4cQ%9^Gu_vߧg.^$HPH!QD+q\GIRJr?c&YMuJdyKtٲdJlJbD" @⍋{q_1gz9^�P{{Tf+7rIvFCR,bǶU3CMB6%mm$ۈ6ZvڦloHqJcm[1qw9k"afz]B}/Cȏu-c\,˘c< kj++<uծ/lX8r@+cKc:]rDrfc\+M= '_#N8T#�.0 t6(GZ$D%$"Q('_>%3]B4M.:JJ2 $ py$UX+PV0s,Cn֥(EQpP)s]'`fn#/emu@e |9DK$?JJLMM0Q,Is{]bSO|dÇsa>'>Ç[=q=O>$뿾 n6m65{�<sc^x}o{b7&ƛ}g8rЇؽ{򕯌W>l *vP/\{Tq]zv0X[jÏ0 0Ba+on6+,.Ayl1ÁsvgWF^__G t./|F 6}{fG�7b"pCXZDO4Asgmpqys~PAV_d&Y:FG\i >OgxߏL܂;td$@s s1?QظP; _sPl.k_CRXV-=KW--e>n4Ľ35 Yw( '6;fg!;oxyyW.iy]r((NJ![PS&A sֲ-RVi%eȽRo! q/\jKW])\-0vk$%.7m+p7=fi➦&?p/@kHIk>ct_ylX�<.=2BcLB2+P >T0zBx\iٖr(X7n3ZI,uR� "E <ZbDW?4mԶ[1 R*7Y zD\dr ҂CHan{y1ynJh_�{ b+%%gm ^{7mZ+J+K-l7eM-KK{ }J"WB,KY[Ye}uLZ!c-ާm<[.w:]!Ph*N5?ÿ'K'RI6%0܀H%RfX =p|~R^(PJbM,S=Лv:(*N TtҔ<) vF3v3I"(L.r|Ϡsں۶&BS^^^42Es!_?uzйs_ڵ~õ'8w\裏cqÇ+_mڦmڦmz޽{49rcoi>50#'>78Op8ܰq4�~+z7ҬZcLQ.vY%eRB�|M5*wvz|<ǎ7~&X⸚|1Vx�BzDVtoqYV8% HxrEV#BLV[Dm2[xcŘ78ɺ`<\2_dvW-bkW`mϵ(g)oO271z=(Դ*N:#PJ !T@z)|)oD4B*;pLH@-Йu0ٴ9]$T o8!=8i:VJ7p굨>+{k vaBH*�5q4i>Ю]@BYn wV¿s7 Ÿ=u>kGx/!n: Zi "Aj?$@( 2!deai5r[5Y"wta|G%DE,="pShie7g;4eaaRM eQ;߈d_ŢL۷>Sϴs߸`u0bi{6N^p>$C*P ^Dۆ /[x53o-NK'%|G :Go6Pлem"mʇme-JR]Z @KKі<7ۚ廡6sl!EFca6aԶkB~ޅёI<"H,5tRof[Q}hRP]\aD& ݩ.SS>tO~dN9׆+r N c"<<`k]lp61p?o.j_՚ecߦu Ȯ< (ZX64e0~iƩӧH;hL gJ992Ay#4 %?@"P^^sarV!ezI*я~r4ei$lRbtbzvq�],3=C7p]?/|fG3 <ԧxGt9{|s㓟$G_?=n6m6m[w<бcJw,hcw[~衇{K` >vi޽~JJ$ )�EX[Z:e*P* 1?iAWotpL�eZS7i5ppg a h\Xe%) QU sQ(Erue\5֘'pj Wd�ʭ?q`p`ƖrQ^LwLPH6zrD33jRe@[$ƿo6ٻa'n'tS)$~ޙcV){ ^^Ŝ{a$9A{9�Dm!Ӑv"[k vП^q,!p~/s$pq&6380=BQj]E"iݡ ( i ?8 vCΜOuάt1~2]7.(-[b'O.pY^]A$̤ dyq9++=vE3Mӹ$k #%ɵA ?bfj>chyգ}Dy7Ҳd$%[ZZ& w]+Xl׻R4J^{7EѴi !gR*Ps[FDզVX:v{(�#F:P){@lCFkFӭ,mdPNfF>_.Y�ZY$jquBemnܖ %cGoE+FTců \ZxCJ@ɲ|B:(i+:ͶE51oQu6ADs*U2E5^y~pOL?nmcYXgDX�욼"o7.LOfwDώon42\im :@l9]t,?bZpI)(>W(u1}T,려diquS3Om,RNDXTd - bDu&=kmAn �Bq9Ξ?ΝނMIPYεE^8`9MSla"lZveQ>#<RUԞ_:TOOOO~Lmڦmڦmzk]wU~gF,V_yg%x;?? fYƏXF NInwl .L,וʥlJo\m`(IXD_Fk6mdZᚱCps J},c,JNvx 2z(|":/-|4bKAжƠ"I5H0?ٲܤYd%~}Lh"wXS(%څo-A7ltCz%�Hyg=ڿRIr_W9swA{;rXlkx4Eg.$).CS? /Y],:_vㄖRz(LPo3ٜ )bnDvž_|˲ygP,'aҗs [ +K>/qqKlM~JKES)S�=6Ls|}>66H {~KG5x8 d!jkf�Kv+6o. Q\s"g-G(Lґ$}/\sEbB,,--XY]MʃXz-(NQKaEZy/Lի$D}v?"?w"DBx/+Z:ab5=2ےoYƹ-ֽFm}vDW׼^ B Q^eu^Z{kG9sA*[g#Pb4h!^CWփiFf^G~KY$*hfj@}hrre8 {0~5t:eW�� �IDATvV.fX7('{RW D9No6c0-uNY![MXQG 2[wl#iUnS}O@,7xD%$A dH B(,:Rc -[ʵ3jO['m=8Ua ֚sϳ|sūO4F5ExkE8,*a6?-Bu֕Ǝz J<45iSc5|ޤt]Ɛ=}_/]Kb%I3fhU(oݚi9؝`Zùs NK8\\L4a-Vkm`jzh v2Ď.RN/Pg TwbXh4E"Q2u^r n�X]Ԁ$M(&LڹOV%hC[[#G_xoanܵKK￟~߉?yd O<DZl} zݦmڦmڦ눲,㮻_Ew oA`=x ϟ/qcIkIǗJm&7AV큉Tm d/ߍjQb}*.yFYԥG͸jME٬’ PQ BAЀV*uayqR38ݍ6kƘ2FPleNR ](`Kڪ;Wq~[i([&W"\k,ʤx{+Kv}Ο|~C8? ïwL ,yLhRkmq$To0kO.:@p̐Z{G(qncI0C�͐BC2 w>7‘oCk47þ}7]SHtu)uJյHfFm|= g8=uv/|9 ?#qx߻ঽs|<a8%*kvpsNc,y2(,fDFL>w,р"ΰ6(8}v3ga"Ġ4JҠMF>Oem ￁eVWV0FtRuFxtA4SZMC~bvkmG]etϵb{s ZJ78pCR a[K_+Wukp-mFM|9r3-"=].Z=3l7=kR-CTgGfOXKX9ꊁMYr1lH)\t,M<Cp >oӒ͵oׯ] JI+w!z/Zz[ƒtL(L}$I U.c RH7)tm/?PEP-ƹ .۩BfAPezDIm a,RWu9&SF<kQޘYg4!nk#E6z^;Eon{t]_/Q-F bcI2fJ%Q*A)U$F(tQ;}X:*R-"aN(+V/mNf)#bH`Ht; = ZThm-G_yt: ) $¦̈ @H0=;2=<V]j|駷3gӟ"ڦmڦmڦvm% sϕlpo),.^LRמy}nKjL}xCDi5m {ӥW}Z(Fv*]p+FyE2Z;WI"^4_E0��k&3%@(*9tG_( %qkG$ MnBكbd@.3JT@T)*4!2o/?{|Ӈ$Nq[ ʥUFAY~s@f!א$)n"J\K)xR$,A}2>ΏpSZ<oQ h]̮ @v@``n ݁˳*̎#?,Z;.#jFL\'e`nϰG b fgRE.Vq%ਪCwM]w@KWa5g_bN{9xƚu$,C,IDVx4BKHjMYRiU͓MHW& bH2 PN)yƫYXX0�V�IMe"%oo`Z%m;y7Com5i`.]UKR,+,-."͐ 9Cg澭1&nTV GP_ұ7*,qW mKϚ$b]bUq}Dh:RwCZ{X:4cHؔ@T @P<~t;KbҸɬzr?:z drݞfT0钦)k`%`cv:87̋�hUL�q)o&ӶֵWhr] mfr"cGG]B@0FsPM]%V$ Uiae%["n.%-J0x�zjʓ:V7+֏.]LRL%H؛Cf$gr "F;y!P|=R)@J[ۖh5amyML EK}q~0Ql,+TՀRz0]S*q.Τ ZƻTSIՊ0,\XD& m5RaQZJ)( ŝ93tayf^oYT1m NDefS!Y"aAܸXNbm}!c+!5 Ns1sÙSg]W۶iiitAYZZ̙3\pݻw =nfzhEMVI@2 hjoLuF'~&f [ShiE\QcFцΪ qpiA ,Hk5Xהu *bfY6y\'e,8p�?G:^J`6і[zH2ě ueZShc'<]]_x˫}5r-@(hk0,,\O~y}wq S ]?Gf ֿ)cPtIiIxkJ 2`dVD&�)  Si}O'�\c0ȩ03յ *Y(8wz\~Ei|= L q#z]N-3}N_'`yz,B;WBHVs~Y'cPz"Q"" u UYAW+AReVK#K3~4e[f;uo?u[3k7(|Zf$-H%h<>X'~pz:͉d}7`0(Hl9ؑ(T ȷ 2z%ePޣ*]k˧MŌ1ҋnĆN鍦�&c@Pg[7y<BFό<0s+*ƸZ-Y-q[qř[u4sW*{[BJ&)z8+.*Uƅ-*5k*˱/ͽ}v~T]y}FcA:7&|gָaͫ!#Ϥrm: e )"@"iffwI:,{EA$PwoXEkRIyy~c44'aNZn7v{Q�AK�:T.}@6/XtM`-'OB9BnmZ[w3qLZ]�_w9E\B:X5m B()EȠDcR`l6(&ZtHi3vPЕ;o"՚kIP*: k BX4a)R/ kk,{[s`\},. t]hkM) #8z(xz~__$n?6m6m6U;6�*+++e<;󚃣o*]xM-YX+L L/ p\8 +59DV}S^e j%oUnZյ6 Ji(TEe{f,G[R̫L  5ᐣGW_-Z= 6\|" o=%m)bgy1)i¢Κyx=ΙEoXH!Pަ-Pݚ2 ̀QQWr ɅqKځ,snYZ g qN7.`=Wh׺W/:?2P]Wޫn < e\ߎk5d/|_yBH!z_};o<p+=;@_|B::Ɖ bHg:B#_j'/|�P ($Bi~xd~_:2;kfQD)'S7!1c EQ]) MTLy3!+kױƲgwFg6A(6ʴђ3Q)o,إNod?6 F?^璮[ڲ=D7WXF<~MwG#]`MdUĀCR8�.Lzu\(_[[cbƏ=("�&c-*k7<#)%Erw>p0(e. Qz4Ǹ;у#,("q^A5ڗ<RCxX,,JoHyن ES!4ʱaBq>~^m&Ŗ!j,`-F&ˡɞ=h4Ok'rЍa%["G<{v;5Ņ c)Uձ S1g%([1e 8} ƵM)bL D!ϝa^N41OEr8x]-vdϜ:ͳO?ѠPŊJbU0 $(rT!M6XmVs{}T9B#0+Ӯsdi ` D"fR,ajv ,3Snط4jv'.&wC ]7V3vH>=z{c uTd*$M&duOLeS%`$)Bs237{-swv0rm=CAy>;;iiOwuW >zoDssU8l.\Pzhj)7&3;Bb 6P\օPW[25X5@z4xHFB)~Q1TemK 0oCdVIYW^yW^yY*U"_#mee3Ke#G!"3hvй*2Lb )L2 6,'?4 cwLE:\Xh׀fdqqd ˂M ~L2:@z.YAX z 2�<7\<>toZlw5Jt:e8+'$,0R3]dhw^Wx}%Yc_=7uGsw1*G<2#󀫮ڽ|FY::EEaHN\e4Ϝ套^G/-Kk0M7fǎ }T"$-E kIL*eqrul_,KI}L7U=.f@Ԇ5AqUr@`BFث %þNJnaTk2[ۚv̶ﶁcabo24w@|-,TvLg`KS+DZ-EQ0 +VZm6)c,b4Mʥk&tLpQ.+YfW rK[Q<iOzswZ-$دAXQMP^8 -Q{iKīxd IwfwH]s@*ɍ )3Hݹ۝b}옛jC^ةzmF<15ec^+Pm=A[{u={;&Xzޘ6Ŷ1Reo=x=nIp#YȐ`0AH77|NCQԙR8Pr) ?sKtq %s c, c JXff3)IȺT e:H ;fgagg43YAɄn5Rɟ>}< <X7hB$uEiXc~`'F]1zO?4O<Ħݿ77Y__beۦmڦmڦfggK~{7{MgϞ/js/Bmo{ۥS  .5Ng:&y{H9,`&cJWcm*.`K  u-uu΍R[K2X�4Вh9L$oֺr1OLZa`}y#O^ͅr)L2 X8Da0KZ he(%գB[{9M{;* Uqet8M4 $`، o0Um-.5T4(gA|x'<.(TrzOTam (=;AJWkMH*TaW0j `@>7Ab\ǖ෿x/<Wtn ^&9źvn`hw25{#^tB!YAU"S2q1ь{tӎ{N$AH_3d,k2okk? 3͟~3 cbĚC-.n2HX9% ;굤J-[ƻޯ]�]Xsb~.#Lǵѥfr10ZuaKEc�G '*VJ=էy.1ϑZo{^aS:nܼc{5Fr?9nX_8gG߯>p䥗x饗0F&r[)d)& A<NFʈi~.# "~\Wy8e<mb:h&ؼMQ;`QqYB-ΒܶxpL5X3Ul\c^-ZxD,K,ZX.1m3z[oS>(X\sX[]¾b ru$I*UE]u]~Ocasߍ[jV )dFv!7Y7)m%fB;� s.@%H?:P6P�9hcb\jcP~ÂA@`q̍wcg!ISiif);f_@I4 `$ط{7znYK$A*.1NeE ¹ xaj3%R`bZ"]ݮ{,9{jʩ|$};Irm__K/#�Çp֯c}}gy|;|~J+|~|駟~?΃>8?Svŧ>7۴M۴Mocǎծow}<3�>|~5wG)vmYK=>b^h4wu՛\(F!#K|'`ɴ<5 R2R#W"U}`R:^h4z]'#].ݳ@$\zc8!~O%@p�� �IDATm‰2 2Tqc6{2JVqD-0:;-V*HRX)+%X#I.Luş=Iw*ÿOI ogx Yp[5MՈCN)-F)Nàeր,~xO:L8d5XXZk0va2&QXzCXYY /*HRźEq4ӖzKW5r̓sMtd臙-~1z8̌!ΣBfvc'9IɎsLtI),!Kh,PXKmGa$fZ0%Cѳ;+ƫ8{T&PSRv֛cVNGy0Z[.޸) R#ev."jDV#m s3DΑ_K~ZGS8ھNVo^^Nx7[[Y8=*Z!0^9M 0ƷNxoUe(-5'Ҹ|?o(A#ujb`fBmchlt=ȣe'(2|+P(q(cV$�x.4\bLT8\(51\ ި{ӡAkH9 \APl7/޻kVma)Ӷ=f Ǹf ܦkØtY_ʸe`&ϋHi? 2 T0h\-IBm[ &mBbtN11RdAII>0�knKcX19>rԍ(8{,XTͤP< _iw-\Zm]}`Spޤw5p-י0E - l8ޡggfgmm cR !hP\z3(^Kn�{c.+ 14i,.HzQ o4܂NdRH!x+.?IaA!I%( sɾ槻Lw3ݔ勫|LA(4ev,$33Å V(`8DJU~!Y btT\$E\LR1ihI ]Y)2?`7|UQ?=~�d|A} *?<'N�<xc?tiiC햱c2 w}9r~ϙ3g?ϝw/2gΜ)}F8\J q ~JAc`4EpӾ՘X[>ZI,Sedi: d+N`E�1Wx2T.ќ+z6GI{: @eVKؽ_%6Ƿy$\VV6(J[#`ٟ5 1!MB i8gJ(X@%9J(AФJMF֙wEu9v( |oK @Bv[~壬Dj^&ϐS0VjަtEHxJqFLJ fw~nRLPjPN֑&X[qqn[uK%�N$t߫b^#pT ([Y.Op�ϭq*CVOx=OoC V"%]sWę,=Kwۡa]kJ1?;Ů]}: !`hv)z˹k9'8u~ Y�+)H;ӂ{f=i' X!/dtPXR-6)Qc,jR:Q</XV>RI1Ь8eY[7@~M{^&|7!,#nElr9 7/ᛦC kXA E'|c˝ IO$;J;�eK@MtiZFWƂ emSqkT!Orlޭ".]m LnnhR+KJH[jkev^^OqC^TP8/ JOVJ%~7"6nSy�-遴0 RXFQز^b)*1֢diVSv,?T'5>'X+&4OԖΫAXaogNw~v-Ϟbq<4,BY m8 LgYo_ʹ1mrϻ{V: ,\;4"e8]"~ںkY/aM5|h /([r;kΏ~kky| zӁqG\%n�{(o@)^k"E͝\kDڐ& G Ip%,Z JK(r2ianfҌn0h ר4aznt aJ%,.sn'Yv}(MQj}Xc1F3j<3Bk4%IS)%i2 Ȳ kt.yatٹs'Sg9 '?=ؖ=txGy駯|ii麧,˸;K fݺCO>$gΜ,b="T cY ڒ)I`! D8 a-&?nByJ & vEHC*F^RU#< ;ˣvʃu-؛KV/'Qe<YDi[Z9a)1m.j֭>?!pl\;8ehS7?i5fu J@7 ;�a`sR2Phw/ηCu H ?zH#́؅5P溋`Cq zapX@c$] k#[}Œmqrqd!9P14H�[. s9([M-Akڀ)`zƕ9Ka`<(& Nt-)»G^ۿC C !s\J/33,zק.~ PH8pn/gOPˤfm$}NFc|-,.D&f͸y_oC $}lqrU_5pk?tw2au Këz=krs.T~%.VsssLMuܷDzK6 V 4iʒf] ꀖƼ.׈i]P<QzJKG^ C7X˂l ݋P+-U�L͞6QZ }6(h1 8֡c9RͶ}a}>۫fZCjnI)K$'gR IQʭiuO6HY/7L`X^F۽Y} p nadEG2rvm{9F), ~xKgܸLeNڼ"|E<i!MSw볲E:Pt7BaL Wb%2^yaӧY8u ="slqKijZL ` Ȥt|a[y̲<R\p5&XdF7|H' x-?,u#kmQdB@br( i"j+\J 6zI]uȮ/R!B(DWmPuS9ϝQek!QMf܆DB$\\Y0V%!I$w2732N ]uťE߰sd � 3\\[<1:FYp }Ξw( `,8AhEVHr;@>I1*o�q<G?Q~g~<s,-U 9p@ƸI_"O=/">lC=K7۴M۴M֣{C<m}-fo?;;[Ii<#9rϳ\;p�jͻ޽{ocqIT@h } BQ TLC83w^d Zi Y__+ŠW)b&reh.".:á)5MPy"�ƴ0%P3 J/RխYZ $e+3 "MFQAҏ!&?aU% (t^u$$IJy@?~4II$ʍۥ K _{wyכ ڀ] n=pksX:?- -:�۴^N@+P>ȡ/2l7۾pN .5-AihTg&E:oz[&A٨Bv- U{M,=?~ p| t1n .3??;:|@QS .^?S0~o=ă.-</EX_Ye34? N6e|ӑHR# T,Lu ~<5NZPXhtv4EAomqύF R<G*Nrȵ!2)ez}{=BTmH:dؿ]0c]Z I2L.Nz*[C57G�-g upքonvîuz}.tojɼG^km>tȇCy-V8TǘumQٸ~An/Rk{B6*T&k/Jz-(GjL$*{ :%ЧrNS⻶èc'NŨ HX]wp]\>3r]jze|TN\i]RT~QƒD_E%Pkv=-,~oC ,Vl7?mmَnW{{|5ފw* ;Zʦ Vݨp5틴թ9?V$ELJ)YhKxUkk L2v/3fWqe k \+N&Gb_cpJێ:ʎ3~n. ˠj,0ޭsGt3JBQҵV\d3v^}/(8z(˫} )Up-ӦUxKZ<d]쫯!\ Uk,ؒefU宽�$h$=f'cc;/p̋pQ[ZidIŔhQI {^n߾٫*3UYgE7HC9urϬbJ%}u!*oZc-i`AC&h6Z&`-qN1oJD)Yi9] g;k1}ʼ26:].یDd-}r)Kd:!FP(AL@5j$ eY #hqJv0ư3޲9}Wv=.9s.}c[KZҒwΝ;w`U|cӣ>ʣ>z[;j[ó Wd| SQ U &mܣ˒7 -vL£"uks xJ/ƽ}k8_?dJ [ƻG-䈱^+? NlpZPӡ+6{6ga ü@S 21v&ԅ++KS'LF$/Kf&jeskX-:cVWu._9.\8]Vֈ$7@ 4t@/ _K3 ifI09ٕZN!|=8y?\AHڬFt ^o\S`V 7J \E" >Oql~$U<}ܼu]L!(P3ƌcz2P\zDYZ턕NFNh} )4k8E@z@]&[[i(Cr RI}taYڀL*M5ݵ:( Ͻp߸AA96xp>t9^]z?`x9Uu}q-wVyGU3LO¼6#x�Wk)+<Zq|cPVWlKG@k1h= MV(󜥚tzj�T:ic0zz.[o]oVL5ၻ1iR )JFf6gq:jD{޲¤xJ 6ŇxEzG;kC51]_z‚h7YjƲF=X&Ʀoy^J*N7c/7AGŸf;D~ 0R.q$u8Ѽ(ؾy2N~D=\"D)=DTEձ>)MoiF >6=T0C1/CV{>:2[Gлa7Ǝ^IL#چ9~V"h=c: d֞@ܴظُ֎BBQڔ`8fR2Uflnf"S\UzG�{{U|uKQ$ rc`3" QIum`&݋+<S%TH6I5?FkYFL1%RdgϞMt<غ7n0ݡߧtWHAngޛ))ǚ$M]$C7kj%!ktQh3Z F$흷]Ғ%-iIljl2.\+˲@7}@gv[pCYܸ~R 㰹Wk]LA\-7Bj H$ ax XΔ7=NfXJ 4F<sH3'>TJ0򆝻y}o/׍i[6_8/�c*$A)1R$neӼK~:_7|''>3r3 e{$!r-0@㎱ق㒆<d-w DzgQ8y ۄ'MTBC.+ 83H1heo[{%^#V$evg?Aۡsi2rk<sl:g= +~m$_~ mJDL`̈́vG@(O /$E@Mm ei"mr"%%Z&{=EzC\2H4DadlC`cwwޠx3ϿXl7F\|cēe{.Gs-cp`µGԴ0||0,_w, 8DcRz>Ç|axa\G M^찶N=ּ$cսnQVU>`3`qW !:/OtR]Pxkw�𻋪XZrKEX`UqKGo#X& [—gAYE&rϋ-`qZҞ|NNzVW|?ϝ^`{{�Y >ֈ5'Kܗ5p8\!;#?T=Ԁ5T[6 cR,kU{'Eaen :kfHԐE>+ԑ�6�K:?`Yp8 2<3}μC##*>sXkR!(]DIbTy㈌9n o7}̝w {񕗫Tҿpyhj>9I"R[JX@ڊzR]ޒ j,m%ܸRrbssS^0) 7d©S$ ']In@ghHRХt-vkgY`2)X[[uTXVJ74\٤1(f2Tۭ(.iIKZҒ%•4M)˲ j|Уa%=U[|L*1�� �IDATi�f8Ut M ; aA|j㼮+L@fc<] 5 jQSq ΡyA_+H:>Hh@^k k0ZcA�^<$R`b)k-oK&1|ezo|=|>{}<yG8sqK J2I巏LΦ$zɇ2 w42vk 4$#ܸg 'xV:_ӯs:l/iYx_'ZFd9kJI :Ӡ]$.nqȋ1/]b0\bCA*Lb6s.Yk1ZPFfmYFyĔ�w7f2)h:h@]4Csih4lݸŕׯ2 cFkH[n/OJM)ګ|{e>~o ;aKHAZ[:KޙӦroNm0N屛7^q3$x)t'63x^܀e[66`lwBeO>{ǻҴv�~ܲ,+%o3RS M`տrx*lm fӑ'RA&EU^vs;@;HR$/#%6v_mBGnj$e@O|3 9S5I=x2 7QCl*KW!y8�= %X<kHhx`{�{> BnVbp؊wԖq1`͹7^ᰖسћ=wBh9WiP4V(qhfzҹd{=]Lx;yJZ&`eek]gI aΜt%B ZZ6R ISZIJ1)11^>JC=Ν:='Osږ_Fg}y[Xရ;$6S ֢)R0XJ Aקt~XkY[[u|bvMb]</|NX-Q4IХf<y,iIKZҒ%:s"!t:t:=%z\5S֘ӈX)}ξ`d'C^(! +*V 9۵H" ;:S6H8Hi޳*Tm X_wz bGVs@ʺ>j�Q=KDBjk҂pV)^z jKd`K =4)dΣE"J}+xe| cVϞ?/9gCi@Q2e4VFJ7|^';: k 0}zD)=[]H0׷L[9kܮ~߶ ?|KXbu[3=͗k<^oַ$i^eMz?_3BaX]c0F=B,hPIwEyk~]I6XYin8.Id272PjI}?~H<Dᣤ64M\PB2BcQ2.+KQɓv'…s9uN퐥-, ynx{ @w_@yNJڝIZqz8mLM{oSQ!bTuUwS)/1?BRSBP<~pG۬Vu|lⶊ $Bύ{9^Wmk~$;D)ҍ9%=?,8yJI] ꋵ+ +++(z.Og1ZZԦn;΁a>:,*TN{�~iιLDEXdAeg;kUVW;SzU*r&-:_yBp\z~O 2i>zuV,T>�W`Oi+}p2f04ueyf-tUWc*lX'aCcBYpsΦFzOӇy\$VkrF{6^q3 g} ѵ1XFmzs{yigy0 Yx]J$Ĝ p??46~)qw D9 /.Qp�iU[4 (KD%Lą#/ytV[ NQ&M2Ԇ4}̙"g.o\d2f5g/c֭mrBX ؿt| X\< {7fi-(ISY 7D$, f9!IRMώ 1c-F[]Ғ%-iI 3cs6w]: [:adu�pq}o@m m^bƪvUT5wŸ4(bO�c}A잍G3jut=_NER9>QHS XO)!6XSEaLl-,SR !R@D t x?t/0DeG3$\v㪀 CuPcSAY@YVU.P� Th=~ 2 xV_|ikgַwz],/i?gPJ>o q3k+i2awoBk4g}(tWluz?(={޾I`sM0eqGYHOT+o|~Mn1tWi2 $$h%J 6VdȗE#&?=y{9w677QЖV7R"/m>{oؙ } ^y~؃g(8ц�M1@Ҵy)\iYK+!5˯x/Ww$jPwt$1x( NA[[sbĪ ? {ySZk>O<T}E-ԍ6| `bp7n0vmeUӃ͡FouO5e+ޕP꒛762n ZvB8P:vic(He`,Yl4> mj<wή;A"t=yWgZO#<~t|SlJ @Xtȱ+e~{g_9ፗD2ҭQys!\ut>M{ѼWL2sMҟFzY;r^б6�!%ushy64Z>Ya >5[kOS.^ը.!o@uTQ**&(:॒ITp$IxdڠJra5^i r HhM6:L#1Ii9wy :\|N:Z.J&}iaK2oJ bLx<XHS#D (ʒNC$2%ISg*e\j^Ij%Mx8풖%-iIKzgRj1X1eY2Lj+VQ`3-h\庴Ge(-RU84_~b&orz]{*K5J%ZPF] Q>ؙOMg],a}wȚH 7O6OTM50mu߼& º 8u`+lpiMXcd J R{O!fccg N VVW| t(zO %a 3t8\t!8$$i^[%=z /475<�ᐔKf|?v 1^8>7_3D!W]` �TJee2Q0H[3ٹyWo'mNYa&hWn�XZ1a9F &SkaT H>RVV;lllNA=fmuv=Jڒdɸ1XR| 7o?͖5|j~}/ HS<#:? 0/>O9b~>.6yQl8J 4f÷jh"2c7^;W<b]U0uA b8c߽{h1.RO�ʲd80m%зIY5-ipzoDU8Odh!/r^|Ewhm4H@P W\`02(2ݡt 9X h 8<Ad1>sOkֳ7(5TkPr'RHI RHJcu +tG܁įjq"YHkJE#y]ӸL}>uc>ߗ]\ul] (x\�l}^+<V4MiD <5@ kGj㌜J P:p]GRKdojѺhцv;E*G",tJ]80RYkL ҬShZZIQPem Rӥvt<ҫISOnbg9+x /O *`k[\vDym7QxDKMc}}QZC$XkQ*9rrP*w;ec./Q <_p[Ғ%-iIKiÅ-K]y~b꧀V[[.<Gq޵ \`g1n 'n1ZQŌ(P;h@C6N-VĵjBX&rq}\@;Hv; 3sh^3)6�S`~Tpuo ,IT>XѦh5 aZ2,RR/ym<G Ͽ@=X-[ʦNˮ(6o' 򀞉Z`SֹC kAl yqp"DOsjL? I!n>Y[Y S?.\a^O<n:O�oOe4W^_ԙ'2PZ X#M{uCD ! <'IS݌^OwO<ʳϼ@ozYz+ BJjN=aX]zÒ�(5# FL2t[L*e4GYV4:I5Ξ"YtȲXbJ,S;s-ЅA >^Ѓ(PR$?OS/kyn\ߥH[lm=S0zpǹ5.H8ݴ^Z4L{u `n-�vuƗysL4Q9xp2/}M>i`+`kΐ+ 1=Bzp=p: 2K1mPJ5  @ocбiZP.}D0Z >E]ǫ 染 ZSͫφyNgn h R$XY!͉[a7ϙgCϜ+cj<b %$d-vE;fg;3dVfG+C9Fbthߛgj{5kpqnx-nvww>Smߝ!u!_7=۔;hBb Jw#DվTR%*XKIJ5ԤYUR]l�m5K$RI%i2L9yГ\y7n첷|Q~Ad}}&gNo]]O==ȭVM7*7nPX6½c`�<\'Q,JM%HB4,R!D{ V*O4I\Ib8}&yIKZҒ%ciZ"%coTbʑX\yF�6Do 9r߅{Lr^JQU D."g& BrP+>ո4sɺ2 UĘ9<yؽmω9BݴCV:~umJDWIPoF0ҧqB( Ɩ`š 8hL['o~ v3qJ1i@L4u WP=ԳPC6#@ ڄ Hm+a^c*;re YR{w*`3+@ Fr =u?;cZIm 6,]h+0#h),<q=:BQ1% }BGBj=Ǭ 7=lWWnOCyqn+%/fiu!,#T;6 -lll`JC?}}y>NL.'&DQ}T}Rn ;E Zjw2EIn^sC瓜7wO\&�#ec\*4M}.AmҴU+ &: R R%HUk.ٳgVh(!IeJ+K) BHPJ`{{<x~/k/~j,cSnƐ(0 {}�YsODV!CAn 벛Lm7ߤxB7�L9Eןŷ{Ah0^xnjB:ҋQR?VE9@//V#cޔj<ã nZRO4x&k8;ϭ9>! TC.0"ETPa>O+)o16#, RPz I Xm SYUV",ޯ2 w<y.0n vzgκ2. h)A0>T�KbHJJw_'|zfcsxC͹=d�6,_s./hDGD4z6זóu~byXxk)M#�#;&$U/gq{ݦ鱘c6M6M w쥋yoYH"IdJja"QI4y0&A.JRQafdsf=tIX<Eْ҄>5R3cVWڬO [{ d)P_|YzCvx;W%RId02~h]zF%:Jb\(kFJIS8IҌI>Y.iIKZҒ%t"J{@�`bQ=Y$;-EIӔ,+3r`)xi[mZvww)˲Nʇ)ê5b;m?wp2{PhFJqozЀY�TRdV8%@% h6#uPl{N* RE$VO 6NJA;Q-@dԖ>QUUYP[+9u !vl(d`e{F`l{<J47K;ou^߹t"wqڃMm38NC7D~ ԊS(u([x>H0|q}H7óϼo?VʐPdtgp4r,T`ly3L9Vy;-rSp=X@c@`u8;}89$A$KP2(5E=$ڃ /\*?yxs( UJ$ Ij|p ސn 5 `O"S)ije$iJ%MR,#QDf{b'#y�� �IDAT?_==n/~]lʋ)k $GH*Q}Ihq`هq>()DCm3_:iSc[gnqy8{yQ3o9ܘMؼ~Wށc9skε]CB4"F{Wfpt& 0BY<FCecZxZ%fIc\ Apq%σ֓|yMFy.!+T`򈺂wGe1~~g32SC?u w^6x Z5~/HEY2a�D#iajO%kȊ{2HT [^֭݅!K<40>E= aLdU8i:L[t?U볆#.#zEm ۣCo{@v43/1? ]X,) )ʊR*)F. ]`@aHtU ҙNJ)VR.k;KIg]ҙ-R9iB&),a<0V'װyXVI.; WwE =qT(R֢hC&$i,M!Qʅ5i0 16!M7FRPQZ85<\`[Ғ%-iIKM[K)[ ht J8qB 0Yx s1*%B2x5֔et`XNZVIW>N+dY.K^,"2Pr <@+oكӿ-R(V@1 9RJ/-SMُ:rOw%GFJ<h!@0Xk0yx[1H$ -"@l 5|�H 9I@xZr�}`wAdH*-2SJ zEZx0𖎍!UE^7^ kR]LT0a+o7B'#lp 2HP;~/Ge s#k]~0`b_ kb2FJA$ ++iSV]I<裼Lpk{ eJ'm$PH ?6h e mᘭ[% Q-|-@sljyVT<M$RI677Q"MRDj\dޏi8c >2FhhT?@% Y& vt-ZJ:~C4Y7c{_HV3~9ݺ/}ZU* i9X%яsݥ&( o1!K*(*q^G]_^{^;')m Fov(mm>j2g&%3T{l :ό |uϦ:jW~o; bԆw1z+�-l.R7:Ucm#VxB9̠p-y\&Ѡ HFؠiMsL\Mf!n%ћ=&șxP3X8v8FSߌl-Jds.6k`g!aee�%f^~H9ȋ*GTlGn:#S 멍6k[\cug~Ѽ~5'9Xޭm.7nOKO[Ȭ $:BBj;W$kR*g5$4Q$ׅ2E[VzD8H 8I5bRzDQX\sVZQ)Zpፔh ֖PHD MTHdbM;]m-i 8a4c@Mauu/F]f,]%-iIKZҒ$OrGPm~k^vc\cr{ΝC<ϙL&@ :o;aV  `i]0�( D 3F#Ru+ SL&l+i:aY)ID$k Xгr:eKՋJy zyIXq6<kg"yMVR[]`D^/OU+]Vak<je `@`PNQ$s`Ņ ڸ\֯خBz!A]-BaW.qR<z !!Z'}}'hx,n5D</)^�](Gͱ}+(:3Wo~a$|쑳\8n:n!%JJ& }k{zkw>ܳ*ܱaE)v#oۃ>?ᅲ[M>91џ~OkJTaTt� G* A`0,4*'ŗg+&\|:^'\ol m[lZ4sJ] Ɣ9LiBBiY<AZ-4Ai�?�R(!(K}[Z8#g(4EIYqyN z}1+XZY*F)]T;cc4{xW|K/C+�|C %^.JxR'5z ++KCZyN~A?_1<w^L3:п|yb*g<a,k%|R4;W#F i"vGϽD5;XfKj"®:V7Z[gٰ;TskַyYoj̋b} }Q\ Z2pK1?uޢM]Ӂ@YR cxvf*"DP?#g|h ߼z\&O"nԃ~c*G/q:,j6tY9R1whWX/eL#-\P, ox8-dMkĥ7&9NPzWu%5\Xj_H-_o_4\ rbY7Yt2\j|6-,N>_< t;``Tm 2{?.^DbgGp5%:_ȀD. <gYw%PUcDC%QH9-(V#Ea+.o"ּcv\z|g~<KKd>DU\xejF V`LoDQw(&9W/_%AK VM;Vc>k6IF]nam"Q$!$|DaJ<!M0%҂s ]bG%R*EF)X0eIX0I YYpD$3(JvT)$a2cFdx}%-iIKZҒNy^CRJ, ߥtޱ!4֚7ZS\0�l-iU::_`-G8U bBѢKͨq)Ο?-x ʲ@*WօS>ڒ${9W(<uVJs鹘U [E _ 9D}_#Oަw뺛@OUvP{ή QVR \EQt5)U BJ.ZJSFEAySkll"H1E[F1۷ }V׻@?E1B)hƶGM.)Pl\فbr*V̷Fl5нc7u-]q OkkP~ \ނ{_'xXh=,'ŵR#z\{}_|Yu+'ƠV ^w+kۆb(kd?غy?Lʔ?9x{ I5 ''+tZm]H{ՈVE`GJIwm^m4j#=3Ip+'nƉ6LH< O&\L?01*R 6hu1J͠p}2dҥ(6yhs_'׸ lXK]j͞f4ީ?mB%ջE:%c(j/Ԩ70’ݸ:k'?=k7?/=O㏬R/8.Qm/켿UL[=L{J(]fAB¿pG[\Z(QF-o!WhEVQ3պϛ3M%[,F3b=oѸkNU1gֹL/۳V==x-е󞜶^S{m}Lak< H9Ōk17" ?-,ܗ湧["Z>LxaY|vR}{IhSS59k~z(̿|a\SCxc]?R_xp? 7A5> YٍoX@b RZ8cH !\F`5VYG#'jzἑ- v�SEeL&trTT>5A qFJbEm0o _aJZȸ}wz!@}O~S貶t g^+\\{gŦ W\g<6(٢X_KWCiPR(KT0296*6EhK2PZim+iF&4([$)JL ax#&nqi5XTamAYJJǴZ(&.m+\RZ{k_M% 1$Ml2Ҵ}%-iIKZҒΣPh8<AZڮü=h- Ɗwg})l?W�c,kP*iXHvހiUJʅoWu/ћ {'kvU.1x ̍SJ@khzmiJR[r*7u[k׸]xw#c!΋ 6r+r VVW1D{QГ@@|E@5Ƨ|ׅ_n 9XW*+PY:Aod�Ÿ? lGyˤBЗ)-1 +q{xmυWxx:_K8#6u$tww,!ׯ;C?m):?ٱ`0@Y^_c|W!Gi*sJ<ygd$MrB7dH=dX[Y'RTp4b83Os(-J kMIi,h$m)ʂdխ].zW^ֵz|O<Ih;U0khdf6ɨH+Ƶkl]ٓ{g? m.׿rWH(n݇A}[d| 7NW9;y*{C}ƈE_j`߶gq&1%>[[4"»ȸb?ZW3I3KVz˝:v,|i�3B4PB @3K6F`qrkѽ32nc6u[/PKPzp#1N=Gr:ރ@#D;<{yOmTԎ}[%Aq QeY`t3.rlY`+ D`QCF"ƾYingg ]Ζ,˰V#BJcY!7Z)RjZYW._\D`J^./Զ\iV--IIo7RFZcw(clv)fRh)@~^;P-E &[�2xlX$ tBQZ$!$n:βJ[֘RXlN(0m,+.%-iIKZҒik۬0:+,k[!ֺ<V"ݞ1tH0^&ALtŔ`$Za,�W -y^pu$c21ڢA0Ƌ u*mH ݩg)jG�R.85* Em@P4[G+1'G"" ySkMq'*�ERƀ.A%ͰϜS|c𝁢0T8'&A_Z|ԎZ1R#( KlmjM;@Ӱ6B@\/хyx? iQu^ڔ20nzer$р>_6>…yU+7W N  oσM]OnKŸ|"�S$yJX3VAY`VI( e ,Qd q^xLeoweBHU;vTebv!*RJHkiHS͞m^zenʕܸqٓ_?N-f#)n k\Ya3R\y51?{/or-.m~PC= 0#H9cvgC{EG8KFE1~âm,"7geu{snxs:7 � H ْQ,i$**KU|чry<.eY(YES �Dэ9n<g=n 뮪wmwJ JX$xrgJ wE& G؁U}Q5 ^Jpjoǰy.H%Q_4䌦9e0n /a)]t2VYχ̙RrYw<fn%[4"y^0�u搳+?( Ze3o t.o.qmߺ{Nq% !Xm }|m_ECj{iJ8"(fcS")98?X/uֱ $`1k+3 $O\\3"!HKF2q>B[AZYj Q A(~'.-i8o &~ S|Q5w;,/OAY&ǎ,]LcbzF`b8^dqny.FBbn-vW6;KEx,A5Iq:}wj1=uvW/ O:B ~=d0ticauZAzog{LcӘ41Td]K 0uk$~Ee/9T)sxL+ӓEtuC,qWVA5kkac X쩓� s1gـ1݉ZY?'$k|?\;Ty6 ؜[Ð6n1c@>GMG)vŃO˭w&\#C =C�gk_}}4TcR X k}`z= \�ƃ؊hӀo SF#9dUЮu\%Ȇf*O"<_&t|G @"fX[¥6oC <nHV;71OLnNܨqf $!IyCKAs<Xxo^ணwqNCNJ%Q�e~emiVDZCmM=,ws'_s ňFHm bB'.uEʀϳt 󬭵|J z|Cd9av-BX>S6$#2y8V8mZ}ѯO=^z }}U[x=\=_FL/rѨTP X:w�� �IDAT]ݧ�7+ˬT�r_KOtDKh)zPҝq<|'Rjֿ`}IeMJˍfKgMVy\֟_Q%?U+A!Y98?)y˯r7_(At^,e+{nQJ {AyX^筢5MmC];/C,anmEƢ_ (b&-C _֍}rKybr F5S.T3N2Jrpv�\3m$VGaҥ~!v0K'!b&Fgm�:r^khO"<mGo% P!X':Dk5Iϲ0Di4顤B[&}k,Jj4*K+&ghЊzϑ;"�޽{wJ`azNCk1Ǹ<OVGiIԵ5!s3.v/ SR u()0t]XJ|utr1iLcӘ4ᔁqՖt˟|RgV]07_<dmM`ΊE‘Xe KNEH )%rB!akBƼ1 JXbvDpg¶|[ryt pdn !s ]jKN`ZA"a&X.]c Jxfw{'ßz?ǁRX>!Zڸ5h�6(\V((;&ֈEU)oBnI[(2-Vq24X uCd\ d2lG:O]~N$y M@,\\sZ]ޭ*=Nć ^Av{1@Dtq~2? mkBZN155͖{|7-psBNҽҺrAY{Ih`&sO%gRտzo k.anHS7d X\\5V tRFӣדza5u-0\pJAZjd} R(_" GgզYdq 3G!Ƅ[gk wq {e YA(nX#"XXg}J)pjF2ٴQ& n J'4G֤ruݻ?g%6ޱ{;<ӟ xްX,w4jͶ֨T vIQ1pAr?~4P=&WPyIGD= KdO6T4"϶wvQ1m7؛hn/u 'r Ybs ǭ; in?Z7Coy:GE[9\6>g֮ϧ|ݒfUaIDnM U<7*1kEף;ǯ;>^&J]ʒ($a{ܼ2ְgn^Ǐ}HatlBc_V}i]6.>_V<Ҥ$Z0W\!uϟ_-s6ewAlPXJ3[&}`SalCXt3N6UScZwɔDA&S7h=7ZC`>jRDYG(-;yOYEBӥi# DJT[ HNK \Y]bCАٳ& &50b1hњd-<:>+]DZy>Zimh KKLOٽ{gN$&|Cբٶ\*IDL-=6 O:!bӘ41iLcڈ Zؖئi̸%VI fxHvϜc0I+DgJ1&Yt̜Yqi-VB8�Wz̸S  X$ sfDc8+%7F全9XSsOc˜B`H""/ٺu!~&g" h�߷6˴@@0050 0S4s(5 &z j(&\g!/Ҿ/ xIVrG80a $..h߀ߍ D; CRL5+8+m:](/\>U'?I6AAjMнF*`v ?/;j>YՁ4xs6~mm]EǸpClt&AW5bu"~ & Ы07i/Km�{?gObK<K<DnNer9=90NT |Ed>ƚxaCٶu3iv/CzZ-"2HGaYZ>} n6 P BwY[POR+gMpdM;14}rA a~PSa\Q<E.Zs=ǯO=r~/_#ztG~nR"vT2D./݅nEWl+*gkm8XhP1y^y?6O)*cus',ڲq^WgT5f�"h s,9d[E2Pzj/7q`%|m^緡JCҕyQg/}R<GkmvG+6qvlZᕀol"#)Cq?M8;c5] G[5^#r{YW6ǹ]r_I RvY^^i* 6M^Op ߩlOsH gAF[+.exfl4R!{YӥR(r:,H"Q/˓bِr+Rcnk\9-N~EVuv y٩)@[ڹQǟ@~pAVV\o*&Ts޳՚=pFrtzRHBMH$j.D룅DZرN}v!ZZlISl7۹ќGR 5&von5P 'x6X1}W9DVÕ+0/Lj>^i1hk<ul,}vVC5tQj9ύ~iLcӘ4 /6\@*P6{6H01I&#'CJ,@/'uwV`oHJ 3!%ge{svahN2)Z9Y1sj IzKHZ"ٵk'H̃}˰$DKҮ8$9N,A6>s{@;4gf1抓ʦM8Z2l ℊ:�@IL@2*KZ m)ChG8"�MXJ_I;%^ހ;l];a#Q; )}Lq*/>{g_v~lq;k- +^p`4-( 6N%XNN4}& ט5G�_augd>xI&m ~o ]^CPXh� kL̰]oz3\rV~>Q0D!D@c£Cfع[2{7{v3g;ԤOJؿS_oDR-G\ti~gs<I?|m%ԍ۴=mἵ\�n"xV6Ȭ#iQz r?Klخbp#7 9~G'0Ok-*Q�E_U9p1_Hb^dc".�QVrRj^v-<J<U%N(Kfucss("*b�p^%["<R%!sJW(&婌wuU!'拷ه2E.eib.6.t|*JAAjbs- X VS5x n\p+/cXXc);JK֚䤋GFp-]E'g`F#o—LHL=__?*MN"v5ֺؾɩIY^^MR9oώ;3i;ٶm+^�k5ξ sNxJ B+-p{hwOcNdrΞ2sϟ$h EƪQZ:TX^^M{y趻x8@sbϿDm-ۘl0e$Ijo+0ݘE>/:BTXaxEh,iT_<QO-0PjiyG#dlFkyxA@q<GjӘ41iLcS<Bҍ}t bz {ץ6dffepœA"ɻ4*0we(cr/X,4d"[, 9ڕ֯cŲ1j(`Ɗ\g~_GD$B@,>h/?NLxFLHeo @ >PiL7x[anXkW|#vu`=LYpkIۉН>x}:$&NN8\1Yh4}%ĥu֜ǯE>x;B)tD \ `�!Ӽ< -Gc4R%Ο_{ϼ k|piy7ܻT D檸q\{?Y]S]Bs;q^cuƉa[IaS&? @NLb=2+YgڂF`>:}VXmmi" u4f9rd?9mZh4;"ur$$՛MŽ޾'$6(XYYAz5~7^Ye)^ŷ`3]p# Udr!J(4ֺyn~ 8f^Yc \je5O$w & �[~>`Qnd(]ץ Tp}@bbڤb z!TFQh)2_?mRMcĺυ$[{)Ð0jvG!B_`)dDΞi8>Aux/:gVU+HBٗ7_W0'iqugetJ`>1�}ZgZf{D_΅- سǛ>RAJ,T5)>֑_?(F[@z*^BJgP?iTlmnXO&89o^>G=<s<7reO/UܗV :tt֢$n qv/=Ͼ嚦7<}]hon[ƣWӟ3[tejf3;hLrޛ0L$Ӭ-\Fidz=jtk-~~3%<ϽGn烇ofx?<fwk,.o$6C7Ncqa_oҫ3594,Ä ߗxBc,xOHE.\ع{/k- MXcjFEz=feB@I "R4ccӘ41nMR^0y2svUp@̏VE(% TD9חBL� +:&VJJ" R*`$5%6pir|ppJ>r,B~/Gk,H+ضc+?gӉcv �9_` v':qCr.s~ ~~/xFmwA,癊+tqєar?zՁ\hIQrPD X.,),tlN[>JQIW"e-+uO342ag? FH79RؿV)}LNy4u:U4}33l߫x}}t+O~9xj׮ɯ608NDdl,x.[`>@B׀c#u7ݴS^XO}Q^=2_@c[= `p^{�g9A/ u%-hꬭ\L+QOr%XmBTXw4B{!:rf-;xez4kk+\tnw% Qw /C|\1Q8bs`T76MDLM64?wB۫l{dҜVgLbaӠoW1K,[�/w|F. l\}?,604 CgiNƤdaw2 -qZ!r]$ŗ1[LA*!Ob cVUg|#e(kx6O*Q1p](=z.1ͭJThK2%n16Sc¯X )yqRaX!_FPP* k%h,pz- )Eadc4ȯK@R &8T8ߘ\ dj &|.Kq.=*lVnR*̺Kffgs3k+;MZہ[k:ԇEO4ڝà>|iP'N2e"L(z$(ܼ@hB.^oI7^ܙ3t;-fm^ kd@Ě-0ƠtO/|G(oV$q:(iEN_Dx&~w?Ǿ7AJ/rog׮,\k_90xQr<k܂bE ۼR(U# MRMvByS|wxQD^+R)nao=%/cӘ41EWksYr3|'/<a4668@ q%oZ'_ x0Sh6qbb(t;gY5ehWy x$ 8 ySuW;ܥi>W~p2!pˬdԼ  .Y܈�ρ_ǡe4, h vc6A"hΙ"S^wN1>E TC8oNBκ*2"vY iwvͣYIlwk� E]Wλs; t C'uDj}s<_78{I͗my4}"R`65WB;̞mxD4w" 2 >a/? w<�yT/%%A0;;Q7j #=^_~F^t{pp~Á{ٷo/;vncQV1tY^%Ο}n'FґH!ILB9o- k(\cޝ| =loό8͞h1[2EJ{عY*r<l(׽&* )EQ䀁oƘtǻԅ5'}c%ж]}^ԡ}nN%lܰ"ynJ4 4\XQ%hND(]UY >t$WG5L:]3gՅv<\ǔ\<O˶myWX^^.)<7[FZ#qʸL%xوϛ,T|l|1L>,dSl�� �IDATd[a#JQ.3&iPnO0�Agݺ)KLNOo>.^ȥK}$l/KtDrztẑ(x`7"]t){'Y[[B?}m])RC+:{)%KW.2"h+%綵o";:0fu>r`+TU3=O*|%`۶offf[n=s/>dΖYfS\n G!u,o}︛}Zcvz÷bfV.+|>֚DQHM4x?=&uPԬWuag5&Q/7|)=cQʢA uw.{t=</v\(jp41iLcۑFc3r< ZiqSU1}"l,ֱjh:M$;g#1LCѻ$A01邠1 5z;EK.:/c&O,\L4I瘛MrԂ6@4%SSM>ۯ`ħ N.�S-H4'м l`㠵P؋p t@!XYj�Dpu(7 o|5Ҋ8$.gr"LV5|p[8zh稉iVx7ab N i*:|%5dNP,jrDDu!f~쯟⥗Wx[;]x皫 0i @P&=sѹ_^Y:;n7Ó ¾zs|[/pz{5% IOݽW>*h5iQ>*ѽ>~=܉S\Xl5>wNjD^nepYv MqFQ7Z$MC�)I q#~14;w^ah�%ʔyetw]*ڂ[Ċ3 T%qf,p6Ė^//pI|Lp5B$VN]P>oϙL/f H{O3pÚ%rmΦCN^T7!֝I?ދ w(+US|W<vƊm&tS1#}�&!$k]Kns}1qFۆYu(5g$S.ͷ3SAVbk.8 SRI+k ;[! drrm۶P5/M 3uҐz* y>ZkTqx!*vl]mB"7߳ !a,B8U˜4ȏ-^MW)ccElFac~1tE7&B7__6>.ݸKT&1|J'SB^#Ak ˋ+{8VH;~Յ9ӳ!N<lrT˅:$@rwh=ZIU!^{7ضu^9ƹsW*TtύwB6d{%p W.(*!T%k%TόN7< {AgY\\ S"HDZn~r[:[g"疽,..pkt21=ŋX!P4`G対z�7M_&{]LnS=}7V3>{N۩noC?Ԃ+I`:(|HjЏ kxlk053i z*0ԂT+pݢV!41iLcӛBW blm 8F ~+] jƔũSXˑqR-sa(1NpuPԩP2H"u嬂n1wKndq:,PHSȫBW.p> 0�K+}=o\` ݯY<)oL#҉?2RT0#.FZP@;D!ܻ](is}fd1Y pc]l",1B.‰KྻRV,#BUNveI.ӨHX'rYhD(#Aw9rV'E< g]\Ŝ 2HV-v VFp;f\>2h\ ʳ&| z-|Es h '\X xϻ'h4->6ZA#0 ˗Y\X>J)Ȣ7p# Vh [ѵnEJ)! {!k؇S\qx sm8au"*?R;R�{WgP!@U HEo< qTapom6%c g;p(<w:W*\L <{:'[ 0g&9lRJmv)ZV6G Iҿ"-JEzzBE#m{&! ٤+[nvns"o o:Νcee9nZm"_2D=*I%C6TsH@W:Suv<?,d˶aUбuW HE$[rHUiS {l >%瘉;[ -HἜTCA|8}}ngR2&2f(%Zpeǿ8(}b@)/U" YV\ 7Yҕ/LL�(< ȁ+C옲7< }vn9z O[hOy$7kTt:]Kef4kLLHPطfxWQ7_'2X)CvLZkqiy>{=s{K/puN8KZɓ[nx jk|@{xrx+F{}Pҋ~"4PYV6(4I*S!Jd>1d41iLcS%\_ T3gI[  住]oe^vlezN>B@izݐvE<K|ˏs]4+PR:FZ3!ތ<(xbj@ae+9}{ MeTӃ,;~ǘ2,@iV.+L9X|y,ˇ5 (b@?+T4;A-C8Ev&@3sY3T&ѶX?Te ?g* I7(5( -x<`N8zhMzVF`m&j0$ʅr>ƌ(Q$f<ր%6#F_嵓~-2 &bˀ\7qi,btB K1>I17u̳|F\ۯ5}t1Q)Fk1Áó9O}谏{!ҪN4X U1heha]XY_Go;&>.^i^-�Wv�*7X;P= -]>o ֊<t /|?u۽liF!tUAεD\?9,t3!6IYU@0 G΁>ɧÓ \h-QN?Yk Tloy*+eNcЮ7Wrw輵0Wɻ#7fPa빍w)/Ҝ:mRO =�hqGGT!%@5WPJϓ2YH=Eۆp1Y1҄ٴa�Fbd#Ie? [lm9kPBbh)C&gﰣdqdJRn&&'' ;#dxwںTr<OAi w !)YF8SeaYk6UjS~L\Ӥ[R=jcL!H|gGX!e  "VfE \Bo^r�AӉu5 4ads?P|I9E$j BVPA 0a{p%??W?̎9^?{cn{W֡DVיazv'fZm 9=peiS2;'NH}rQ LG�!$J z:wd3X qB =jA͹)6+4j6()+Ә41iL4<JEMOO#?>x<^Ξ>#g{@ռK\_/V7 4 r\HmdB/Z}dܲQJ3*YZEаSM>Gx#1h<Vz Խ VjRJ嗔p "@_Vlp'נA/Qƾw\g/�߇(z^,&k '/04-Bk .즶Lz4ƨE+}' =l{/q2<9hmN yt s\@wŁں^%(; I8?s+Q5Ŏ#V]Z)0VI~pm78NC-БI5Cd-F;eFZkk5L4j\Yjvnm!a%ԆzK9wK)GWbeK"`Qӟ0/ E>>t 6viJaU$7=UcUd;F~28eEV7'LzHo@gER H;` qK,Un_ʦYezZ ^J5Uu.bEv0\֒èʽpKurc2^N^Kժ޽*(A 0 wd zqi:kHm:`U\!!i)Xʫq=asnJ,vg7:",U0EHuk]\p(E: a HHAt=riiqXK8BơrJ(4t'k4~N㬂m-�?#mMڴkyoU.rIHQд TAclDzLn4+FOlB_ckҥW;Ï<̿g%AGĂ'@5D[yx&fZ'If4&fuݶǾ}N:+vC0%u 55'/}ć_9g2f?g31x=l:CsIj5ķxkTn;"nDŽ9ˇ~_+x)1E ;mo'Pa_\h4iZLPRҜjFwLcӘ41ݨ[Nqy??s?ɣ~7$ #N<O?'yWY]]CJI?L4&x8t z=v͇}7~|</ FG9Gc|߫=adj#oSKJ3:w[<:x@$;(D?r A րDX&J6{:W&^CYJ Upu-*j6e舔YH ?j0$ a1)ؽc{ܟ?x͕d X%Dtة`!`�B`kV!6}#}$@[x[1;<73T]WR$2nZ}*Q_i ZUxhWO/tQMssر#|ߣ8},.^byiy׬྽|>\/D59 a4;]DƂM෬T@aV@dAKWC`@Yl�ԫ~sUUf![Ѝ>@?#T̖_ތuF^h yѾK5 P)HԐJ9<^H2v#ͧ|zo %s}# [ϫj |U^E+Dq]: QD! z\Xv\rW{gN\fXp-_/^M> LVrtlXcN@Hg)lBe(' ʹ.nĽr/59|6=RŇ\5l,NZ+&\nnۥ=1"!D\&׌k&TJVƋ~ cB<m<HX +K!b<:a?a5;*-�۶qypqTnvۭ8vR44 o4"a]R G{3Nߧ(kũc(C}vo><vn};9},o<Ł[wW^<o';xy8s,/"->on'3z -&UO>[?�%Nᴅ4E: PCtTs"DfS $kZ"<Isb Ȏ魡~\|}c#6&G <S�:t[n\1iLF^, pA~~SN6S |ԷũSx+3GfZ<𝧟ӟ =ߧiK/rq>?g~|3>O<Mڒp$$nEPLUJq%QϜfEJU-|( ǒߤ:$pX<ŌUV�M .ɷZ_pe_ Z2|Qd@]&W]%!$t@vPB!&m uJ >tz0; {vPS #k]ZڭUڝ5tuj1{rli^O;f A"+v0Ն压iS#R}6 8ߡg\w;WckR`!E4'z=3wh6A@ � % hMjZk&StmzmN@۷uťE~y{R7NXҼxyyc,c!eٺ/T)'P!C.ZEqyT\5AKj3!<9I`Yu} )ޗP'v=@,-'_/5Z!` GwH|đgzKKz ^ ˍ]I p-Y;dP9U4bN?{;B"NG- k!y eq5 \yI^՝(I7F< ks%(i7"[k)(D ;5>Yp$V d xy>~?]1[.+9,�/ MS؋u2ؕ^i\H lݩI*Iby7]M0;vW]'[Q̲J{:2˧~R*>Nc ^]x^!V:AP9a v-<'~G (SZ*` ~טģliK^>+|N.]yn43ѨcL/,~{|[?ٿG5H@jZg9r6o|+!!~ivM 2 ( j5S3(&G+i�� �IDATz=�o{ 9^uݍn\&AqXܑ43,ˣYʖ&$C#ֻj}j][c uc4EZ# 818`@uY]U]}Ĉ?D2_ww 3662]\ҥK#GtbqnܸԆu^|EjǏ_ڵk0==KOO{appp[ˌh޽ۖwT*wo&5uP&2/oڠ*0:v)a8>?cXLJ7n\_*N~q-ݻXY-mC_Vu*ئ?c<ʧ}˗par*Ο1MyN/_յo/n[Y p<'5n ccEyJOu?.Xr4QOo`|L9굯UZl'Ah%mT_n*0 dƁL#JRbgk/Kr]ב'EltZ80lX";@IEyO6,.qۙ~|4憺Gyv]#\/p+u7Ѹbj70f�t_x s@YXGC6Ř TXϰeqߑ�0qZGQqt}&s۴�E9 J%a۔WW}۶C]Jy)_o10mTӼQ*rת @g?͕W6U8qG00 5ꓖ*BvU'5:M/]1(ڭ1U:Ե= @vgsZo &@fj[ע h{zd}[7~/LeB_ "]5 kJJf5uV~(ɰPZ {0:J^Ƙ j^Vlezk{>"kJXmI(&>iU4.㮪KUD=L"EFh(ٰ-fll>]aaq?Y|$_ =*DElJ)ɄUҘ5IS zU6vkJOj\v8Ğ�i %|C}ƶmv߈N-�Yg󗿉addrXZr~FGGYZZրR,Lq> aaOt$kS %? WQa0)  kY_{U˳]dQ?  9._9#<%c(R<Mzu?2&a#~x?E~_n$5V7Uj,!3UӲ b ²3AJyZGְ�mfiɓ'8zh[@ܹsq: ,--Gsssj5�[L 8 mo<q8wNDOԖJSSSѣGyꩧR׶w2'N3[dϟ?,=�G̭-7K?@>%JC6sYN_+G96AP+KܬS{2sYFw rw,/S願+X AcO?~۶ rq՜& RV� -K *IZӔkhG'5)% d$^?j%`I>c0(R [yBJ[ɛxy5Pk( 9k]@/x>IrHB�2-=j�KLky«׷CF́nA)ė>HCS¢()\XrCd YH-lQ7m�4} �yU\߷}n*"n# G.#Q;q}�d2- JP"7ӭ`,a T(O}ï3_?}s{,u5-ծ ;c`}ARh=ljX+p[pooS P]6vu WT:H(Zkm5rs.k X%j8(|0sRA~� c'+_r/w+"%;gyWl<o=ٖXo M2m <MbVyc^ǡVgP76OFf35.-t[R# :Tc4G] `nw>;ֈь�!PbH__? 󋚏$77 )C qQ}:+bfVj§6=(EØ'7X3oRP.H6�\_74zGut<KK LuNSmoc#?: OR0x4 LBha|`@z3o|, / ۤT oɳOϕkER'ox*"~xQ^[\ge(|�Cf}|_E\i43XF@)&'/3}PA& [:¶mgq7\4(nZ;ٳ'~ʕ5�J^]0(1 x?ld2ޖiQ>~̊(2:: l @LuO333 kdYScUoWh;3<ꫯ2>>?[OIk,�丙ZRP?_SS }�m}2;,!qXQAP*X\X۰XcddUQ7C_FK կX*,N>I?~3k(my',/,lswtДR"Cs%phҸ<"5x[}"骸+cHrǍ*{z<kd(t LObZG[uzE ί&.BP4 桺q~ eV9 PxBe[cl!ՀR5E*YTVW,BH;Ɛg!k’UȂ% w4#Rlmbř-9 M(UL^Zb> R/XLIn[S^mp1LN}\cR!P ۶ٿ*Y  Gm V7l?6ubuB/ <p/'O͙2|SC#IA ^3[B*y/厶 ȵ *Ow/աAXRVOqr7@)@8Jh& x[P.N5I7Po埊 Gî [ݩe5) *veסU.4#P h@Z>I%fK>n B F C&# V BZN:.׮ͰҒcD1X⊔DIQ^BM&R֏t7])mk+`/;uq\zYmUr%I.*oTLpY$Mt�b- kgBdQ:K2R60p<ꢋmd6zM]|;9-P |O L#$&JZd2& <vcX",Se`x`41P;KXYYbpG9d,pTi Kq ޟ䭳g1rL~ Tj8c L&CΐX,..266F^'PE(k,Zkzz% {ڵ6s.tAp%𶰰Е%z8'=Ç׸̍\NOOs=l쿈{GGGԧ>/"WLLLݮ6iK_Һ^xmuU=88ѣGywjmd>:u7BǿE_v}] ۿ)D02pj5j ¯Q46="7@︃Rշp+TezP?ԕ)~se;'>CW/]μf?2ݦ [ #ڹ:B04Y&kB8N͖=ٿ܊e2wqVMV8bTI\#Ps@aP[ 2 nox@NqGl;[-T<b%v d*iMz?]Q32 S/1.z@z |m( |=бc# [e\eX]bB@JvR[/U9u ׇB!˱`>n;a&BJj 0)r8Bj5CQy~̳ls}y AH@�CA6yߣv d|<L]Yjn<"F[$KZjϡWP!O7 k}q0yRG$qiCsIpԞM)Wy_Ѽ[J�2뵢l.75[z~Bi<X_@;Lm"6UoeIr?ql*- n;[ۏGwʩ]k,|Vt+)v*FLv)aK&fgYBS)Œ *"W53ɓDE@&;s|(ݡ!BU}-}QNiַeV5UI'E$/)EHS[ RƇj*uo3AXDJ/#D>cfB-h6,_W_~jmq"bZ&QǴ$R(L <% q:c;'_eanPhLcYr^}Jg{,K^^ΓgxC>)t!DžJ ܺG|r9 /q0 …lQ0)-/Q]uj\6KV0MLCM@ K}޽uЮwq||<ZYx9?Ƃknn. \ӭq\\.ǿq:33m{/N[>AhvoO~m]E9u|Νʹ凉._g֌[۶9x`6loܮv n9dyy۪|D7C|܍ug "~ }1?WںIDc?.y},7_g.VVVp8y!S"$z) X8v9ޓW3> y><>I{I =}xJ07-eNٷSڿ턎\i%As\RʂHW[߶iG7}n'8il$OdrAL*·6@:-tS,BcH!:kJ nJ<2S<kFBȻ (@zD}p  -o6Ew-!W0lX 0'aaEcP,Vo$ܫ~P/"UGCr}7n(Je~cv;C4,jJNRl6i{`W=(Z!,]¥>zJ_cvh pYPu@qT&)HK[_ RqBxv?Wgo\Vq^-dRY%oP9N5j-@bXUL[.a@"r&׶ ;|HPQՇT`Z"}~^[\]gs`@Xd2:;Ӳtm\.T K]+8L$Mcz8MxeJ 2ج$L+F𱰼9Y{fSx،'Ўw 7ډ7__Ь>:kkΖ٪|7UmG;ZkSy~76Za_:ֺ{io:yL*kۤ~VQ�>;_]mf@kJW[{$D"k:G_!YV4y �eBO+h ZӺ*k}H|`TYcPC2BgV)ϲ_k6?[gȣmV1<4h^ڪ9h<+}{dÏgzv dgm*5ףִM9+Rz`g30{JH?=ۧ~@X+TX8xea ko+W(/&o=ås)x3 a+%2�3OL_Bɚ)-{l7}Yŗx6!+gT{5laae{a("j0P0SoOlRݻwo .ͭ k ]vmd=9ӧOO<%T?OIΜ9cǎ"˸q|A^{y=ztKz-h=~x\ oV߀8]Dݺ-1xո;Q--DyRmu8_1.''Oc:tO|KnʹvO~Kry]x1oI^z%@x(:tsZfُ6")6Xk"+W0L hڂ^pEߧ^]an:n]16K์fX?ɲ{l}}x"q,aQқː30J jg;yz yd4߿L_?úЕד?6[?r bOSel9acqfQAH Q!J "R@BkQ!nA k#fقfCt;/|D�J_Vs&:v^ZRtPQbu;Aa=ضvzP˺9,ms x&E.^^tU'#==%ˢq$*\= 0h߁iX C<`蛆) |x +-qaa΅R=zC2rM [SaxJQ8]=Xf�xRUZ<fLLa/zBlM;9pM*LDVZuBqOwϲTqxG10.dtt4<cUٔv=E تqN}@(!P*VAEcwH`+0L!x.MlE�>b*4z"mDAe�ry&]k7]S7쒵iq"H_8PY sPy!kTvnyy6}StYѐe&ֻdf>PT3kN!?,O--a*yIxY,!!|v�p"](]MJMP@iS!eC%nZ_ՔZwSi\Ʀ:zinDgw35iJSmv?BsJ];-u*>,^LO2Vc-(QڋW\h܄fKC fcMD|Ȯ!zz{)--Q_-ol>Ott mm&Z6:,on+@NXD@Y!0 xTUW[nY@ש!!ҀaRu 2ٜ0 �2<@\~W/^g�� �IDAT~.>|'y{[T)STujHasWufp%Юafj~왧xﰪ�dd7YZc3 K=˲,5 B6_>| ca88fyc0ի)�54ͮl^}%^xᅎSQI`taa!~VTb 255{w|_5t+f\:%-.^;U'OP+bvv6 裏rI�^y~<'N񘧃}ٳmޞ?>ޭ$=}tW6ߎvj<-ݭRR nX^k )0z޽q֒J )w$4 8J%LO]z5 dm)o. )),z yr)|LJRܶ+ܵw|eTcirH+7 ><Z¶-_ԫˉk]V/4@W!-EC8{>A#X+d$ u)\B98{Z)'$-E̟%cҎ6"n%5j%L]n7y` k`mS&Th׸uTڢ3xe|iAހ *� I <]ɮ==xqpz =:B޾FqҺhaAV'A]^pkPF-cUpe( j�\�ǀ7xâЮQ,­P:ض&]@\&C9q;&0\泴R %cXVJ_f~ ` BS{pٽgj ҝF8 aN"P,H]ව"[ա29J{ݜ|k˙w/BA�%u ه7,/%c $7 #-F ՎhX#Wpߧr =h*h …ީeAB&iVAZA]a?!0м qbP>4l#忖Y5ku}ͽ~ȴy(IƲ3NڋkriA][&m=cp }Qٌ HNNlzZK��j$q Yz-?{ن(Бo~r%z`$TVh̴G! 7Rs5 P,œӈ}_4@PܭnLRcBаl!-YlKg:EuwR>׮]+Xluk{; vdDJh=x ?8&/",�Z|jL\auLJ6s\Sb~�8k.|beB>'[Ps*X,cgLܺJa%N݋Sf9{m@)v316Lﰼʕk3;kV;ʽ![^*Y>رZ ǩ++S #} 0||,ۦ^k+ ;C] el!}= [҆dCKKK,,,`jH;Rd zjT=}t 8vqyޫݻ787hK " 1_<^s&&&\.�3gά C1>>8;w.fggc}v&&&ŋ;?~8Mo˼Ty睜;wCw^DZmI^}UjZԸ9vW\avv%N>ͱc�=.O?tÇm%Bgٮ ?-86ߎvHGeϞ=8-cرc[γݻw߷Ky#"%< uN{G?4bglA|p|e=tS(r"cJ"}ZnjkR)$a73d9i9,K21@_/r/0>1.J%~7~;&084P4ô2word{ETl$@a&뢔d0BP<-|¦EAbwI}d]v͵ug@ى_1"h!jynUq P򬞔U6r$>=*W{$)eLLǗ4* 3PlqSAFTȚpc urEI.i8kp0m %)O>1· ^]i0k=xAY)kPwLAOd8U6E @YphzVo-EPM&gq^< @ T"ohh�w 9._wN'Mޞ('�N~}]y(|  qd(9o{g"2{{bSئߛx7':g~Xʫ\kJ'=Ydm*tz.PJ5<`URn k nf#lMeE+{f�A|Hn/؅2O'5 )\_nAB@M f0% r*>-Tym}! 'JnzT5MMo[푰e~"wP?7Bzbb#5D˒.;TwHQJ?SIZD"[LnvHiͨc+ԖxSD4WG KEپ2sZ<_ʛ<.KuAbpMt�|_1 )M+2;xi6= * gQ,(}bϘYL2=VkBC fwA0_\Zdyyr|玉;1plp +/+x ls籬 Bk- _6f}_ٳV5zj $Aֽ{bvsV133[w%]'SN?�m<SJ8K.mJt i\-|1@݉g> lȑ#8[7t.}vx}e31wnšfi'p||/}Kk<xzIɈyuɓ'ٳgqz%8?;;KVcrrr ݈E#G8}t\'N̙3w}@3mͷ6kqߎ"&&&6sssuݻf<ŔkG%hhnt3m:z?A_O!^~Bc8yoFw "<N8@>'P1U\>0L0LPvLD*E6ECCqۖ\ѽk5}2R|u/8v_OPZZgQ5fG[La!*eYA5| 6AXFCt,he4έfк#wmQv nYV-4yrj]6 kFJȅ47O{@u, ]O]IyfP%Bc�vs6vHYss0yw ^y[XJMȓR+*”!Е棂cH#w'? 7!SgVq^RaY]oBn$30p@]'P_\C 䮻](<ϣVbDItG$.k+t:uBׯ"B*ػ ;gST(-YYq9*|;Vٷ==Q [op<wq^~;L_)p}6@;- 7@J\2}vŷ:4x=y퍳ï?Mufd3Y!;>rHLn-rۺx M4B  *@B%ȉ8ōM*`A>Y]][7ϦV1$\bWM{n� iG%hN!ZVϝ6Im2-;E[C|"BCuҤpK3Ai`y Wgiߵ>M(Up]"zoF,&7u= 4z89ё(|F tT&MmhQ 8{Y pWO5-1?@iejujuuZ˴)ׇ A*b2Ômcg-E&kRXIJ zuzs [7-T7q'?˓? 4Y>|㕷ڋ[y3.ؽl/V ?OQΊ [8•0c8B `qq)e{l(N]w$prʕ{&&˗/ߓV^33313::Z{'-ףd6TkݝtVDݸ=u/B˿ u[A?~W8qDG:pLZ ߏ>|k~m'~'Rcxii'N[[َٟnѣ B>z,#{닿;qF?xn#zF-cܗV F(N~ 1u<ˋTKe !߷B RE)Wa %md4M.0euFGٻg7.\2My_R|O5ӺE=ۑւQd6}}}X[JQ-*5B4 L!CFuZDHU{3taz([{ jDxDs`%VmV{)dWØ嶅%VLtm/TiR >ڊ\Z!kTg_gس *|<T(6u# z\[9pT^c \R u3vPBhepO[E|J&k̵ksd W0{:냒p _o TSfV+l'=!9uJqmΞ@P$(}H<:W:U21wBZO;H4}*T#"lȲQl#.Th V *e[4S~y7:Ձtw7!R[s1EE{ʄo's+؎uVhM΁H/lRgߵ 34oۦks=qt Z!'"`%V _g1/뱫M:;mkfF#aה[v_V T@!Ct @ Rl*HnW 6} [4UP/euUZDybH2@l\ PRiJL 17&qnP=~߿ȍzozݡSUY^eVk,,.2{VLz{ L7dVZs̚uzשkmftA9X<}<V&@)2+5O("_,>̹w8A!cB`C5wޝ.Ⱥs=:[' òPad�0 l6C&}L˼ecP#`uaa!ljترc)" Hڵ%"]^^d$-,,8N*ny2c;F7nhlNԡ8,,,n94SSS,--c2ͦ$nϝ;ǣ>߰eXcrr .ܹsqy{V!8Nz}#@Fi޽qmʂ#ANUzwp5">:4*~}g={m>+0:=sg(s"Sp]SR ǩcgp}9L|O/>Zeq+8ˮ>z*Vラ){q|0͍9".wu=)q{?wk^) "(2А2D'Z)TFdv<RIBq{ԦQ*d(<G+ 8T6JWD%c#wũzAc-es{?L@M«MXfY6o& `\WCE[{ !A#(ɈlF•yVs62Pqx ?,?a p@x@,Ԝ ,NO?ۯqe|�&6G+#|8N]K56 p.]ЖWk~Onp*`A>/x /%啕8oW$ ZN\B)E_!<]VWk+'[{YuJ|p zf2:6J&AZ^SpoO< 4YlTqYZ^!;ΓO> Pd�a$6RId`01gW* w Cr GI�u4#K>geejoiAA$&Ҭ yOPHPş MƆMT& $+ZicuɤKܮDj艴 =\[o{M˟,ZNgw͉"7"6[Y}FrH`:Jvı[&v5ߪu5 c&@3 *@$K0"[15;)Ō".ѡ" =QµKcZ x]b}7$I$iV5B%rբ&Vd"ZujWXoQkf"Va09u4r,uuneO/]Sc()#X?AZ{'(_ YD)dX "3UJ�m 9>:eA 0L\WUJE.\8'cH2cCWOjmLaopÔg_VyeԽP6hz%rS 064!+^,sル ㎁Qn�pM<i)Z92.× ò8r(Afof-Q/?X&B!b6e\.OH!{?y%djTRᄏeڥ%r*m' 1/m֭FE^O:r}QIW}n'vkpffzkK@~qD??ܶض㱖t~ҥ8Vb<xR.tR"wݛeJ&AΝȍGt)bI]CFȩ||0oh8~E[7 ȪV),K(p}vK *9|C&smaefYl o QiUY\Z\Frb> /^`b. ))JΜ9Ci/5?'�ЀLbĠB,cXY).K0LC[R0֊">L(4ҩ]5C6jK'Z\@V8e{/2�Y0wvܸ9Re_nGeXH$87*U�3Tf߫Sf)cۊx7Хkú^6D f 49:}%"^V|Ab"�VDcZ8 H6g0@8 g)\P.0q.pUޙ1 % 0+$O&Ip C P9U83 "F`*&�� �IDATglwGU+8*^|U<OR#YJKxq㙊&;~`g24L ӤVu2Koz<a öN+pNdxd!Esf1E+h4JXwۗfu_ _H>G)˰x{ s�^aY95i|7+ﳺZ%2@�E!Fn["4+H hM. X(&*EłmL@P6ף=Q\u{t! ୑6+k gYJhI4;�S-.E VS 8>&+ fSW6- )GT)w*hoAk\SFQ{7j WK&nϰm6(+q0{XQ{Gi<EJ=~Ob(E{9[Q)>DŽ8N"~QP|�Ɣ|}Mc]'U SV ]ho۽xRs_a.lsE-{fӿ3,###˔K+}7bֲxֿJ.AC)' 5=1/7m^'b"K}@V>?hޕR#Djd=zO8|OcppuZƠRo{vgQڣ uWw4ӂсaAFe 2=xTerxV2Rh+ v gb!veiu<J9xA�QFU:xz!J PO`10Xs?9.sy ~zz{zHi�4$^3V)Ů#W7UW^fkĠ˥KRlCCC7͓8<)0>FFFm3gt o4)(a3 xغ;I;UKkd2YYYYWAt?,^:ÇϜ9OLLLt*mX,r:ĉ'b'Oȑ#V,3g;YWDͦ\A c:>LgrlΗI|h܍d3v36:-`Ji!v_1Ӈmd{3) JZ8uORDV0FV\&`߾}X2P5B4_agW_Na lLĶmux1m`<ia& @a&<ġJ( )%IlkڲAUcGm0Mlti4<2܋) Q_|;yFLb͟B $ ~4xoaj+@g`%E94hQ1gPUPVKϚ˷:4BCV-5 O.U=oErI+Yx8I,v8g?rJIJJe�˥e,8uER02dmiC_  0 sy~νg"<0 }('˨ZPɰC\!R[SkPaߜ᥷<(kUg"p?{luS۷4 � z(r!GR~ШJ岥*U~]~/.=xFq4cYc$0M$M@ܾo<igt&_{k}+*PQy ei܍TJC4h80=cKVxܻx^Het\g?wgu{6㓱Qt7딆JLNNr_pE}�x{ 龜V( "# Bi_/&92:T <,"RI_657}#W-lN9כ\c0/=տٮueISX-&~+^QizM6qIn`M!Ѻҷdn궗 Akڻ Yvnw*7֭wCe( ~M:I&s P4@ g R)hqڦ<Z7s pkfsص {v+@@*K6t!qoՕhYɢh wϜ/kb'/c С$S>6eYN"; E.읬թ%:]Sո~:A2*f؋Ly|`vnX9Ƶ\_G]E%^V3Z,Pt)W;iS_i@૿e\{O= sqps9v68 #hY9o7t#G/3q.7֌_bjfJãc CYv[-T*r`x{5\+$Gg ׊y"b4J7~pc͟oo1::uG*AzSRڶ).5o<8kkk)(˭i]XXhPvL{o[\ԛϧy^x.0oaaWgPLN/]tKNԡ'?I9s{ (\ZZ={lW/_cccf.9ky31P<s۽[ <s!|>Nm&_T~?-+pU~ogffA\j6q9ʮ(Q2;{²l<|)qJE [,7=dlBc}kK%``g{re1r^\R02<LPblb0 pmj$K~?/իV\7WJ!D"qͻJ|0²%㠔%4z2K�"m5�E殔M@+hy{mn4bA1 B h|Psw&€qLH`K̯ODȁ zbA-w%Sj;7KI8&&%{;`rHfyym=Jo 8l.;n]k5M.Jj *,-XA`zfScXCJQ&B)<L9r\7 h鷝^i-;�PŠ-PGIGKvT8kDGΐ˻xxYQR{vpCV BѱQ n(E֤P(!۵*_|+|xՍuO}>33(s5arE}Ah05ԿqA+% ZD%տO?:\EHS 5N@R+ % ,J)ωG٬U ҚbȃgNpw"Ejשԋ~¸l=棅n5i؄d%o{}=^aj)oKnx:6\ޏ6Z֡{=.wo:0цjz`D*rh qk;V,𗂆)�@v~0ɸNۮ;e7;]fm;:mX1;}ϟges̈́47uNeL۽kP!,aF){FxҺ-\iMF4~$]EIg 岳M uLyWVVPJa;Ҋ yd=@ۥ[Q{7P6e&,>z̈́v7ǭrwɩivvTM0)\ENw^Oʐ$1rPA`d2σ`cN/<%Oeb&f113C2Dah9#)prv &c kT!B(*8ҡyx"/C4w6y8u O! \Aś5Db6B ?e]͝R+70433Z(f]fc&YKA7B |\n?i幓\^^%w;*?S]cc.\Hc>gzz0eZK˗/?v`' ߼ڗx#<rCRݣ_-ꥉZDw.1sMc* !CEب2^Ü:qSFejť%^]^mw򌍌3;{RqCukWpB{M `\ʇbI Kڸ˥>7t=Y%OuMGJIE4Mzj5YZ 0=|vд8m! f)%R(�I,IYB\[Y8;\"Щ NtOVȲNC`6Vo\Fn$.N 8�((۸1.oDv4AmA ^VqE˽1f{雥N駏)DMQڭ ;nF$i#3#Xd{Cם#٩YXY[ۚg F]rG(v~/ʳ%@PLNqڂ뫰Ij4g t*-W2vӝ|mn?bsvq/EXoQ,Q*d|<#cCEXJ!$am)́Je|/Vk4X"#͍eV.lVyGGLNNq><qRmwN{fTLLNM/FwQBE 4n;5%;>]>pW_}-@a9QȧO1{%벰_ٵN&6kN Tc&ZBjm�`2vNRy⵫-Jk`tVl&B$6ܾ|퉫'iDz'+H ڦ=i2ZqkԽ_-j>*moyFgOp)?Ex5*h4:) =^IacLvzߓڸP(#9d% 9hVKl'0*;ǥ<{[);'XVJwfgӣWn67^{4V%_Eӫdxj-'JC(wv;2|JZ"=en%<9޽o `aC9Xh!||r"]BnfN7osjU(P(]aks>09߽L9CA @`4c,-0(=~ljN3_*~q99faar|iHgAPCrCPɑ+M8fYo{}h֘q377ӟ q(Jd v,Ba)C`O:'t  ƚMVmKرgl;E BhllCo;Q@{oj>˗T_gii/^… vuwzg;H-}yii)w'ikk+UҸG0 ^B{},$n,.u%$!If!NZҦ~10"_,&^_+xE>XX`Q*Fǩ6\~w/\ܻիAZutؒ0J?{-%mΞgǏ*"́JDx mSDshnRǦ5KZXY؎Xrq[{gi5ݝ970^AMp$.TQ@AVR��[CNv�bK}@go]B߀`Pf�!`X[1,7ܘXaqexSZv&ZY "R%4.(i.y\csc#bauj3a@Y[j:vVX͈e6'+ϑ @X+'!A m//SQ:_3k_bkk ;<͑cA4wv7$V%2?0h4Q�B1<ZAaHV%@(.׮ĕ+Ww~)^s8挋ڛu״X&�w b gΜ2w.?xbrŴ ]h`xyLL153X|G=cdL "Ph:އia\[GJ^!, i1c YHxUֱ`^!|马yvg4q[9Ėb mtىeyht}oc!׵<tT]w[]kAkEd_7g:=%i&2l%ʹ+ޥsP#k@ۯ@E rԃ<u VVN&{2l}zTT<[uit>4xO6δ{|v3-RRʬIHmt~vZdL't{ZѝyvtDǍ5.ulя{U\X-\@OjMR- Q&>qf^li2 gr<q07B&Dwn L?}_W:a(W㭟MN?9Ny Qp,(afvO?!:?#?Ƀe!FG(ɍM`rs69)6-X<ع NCh M<t@803M#hp)&#HǾEel|j=5l?Ľډkr @,--JBV+VA?w{9w\_ {u'pyy+si8Ux8ydf8?AriZ'NwusܹwZ_l tu m~z+fܹs|.{u6#ߣNhT )�/tg>Juh~[r#<[u fiF"mb^!\+o]⇯/^~׸u||pysG-%^~u$ı:%jlmnQo!"&ϳD5}0bk*Q\?kGQnkɞ\kA 7>ojZ(*e4\""BFZ(Bֱ684υYh#,R(L` &bTL2fht)p|-GX'#JAhA ۢ!7b EйF&lIA){‹1n|qeIc�X?�\gԹ4.ȊeE3"2`'K7(B?\X6g8¾,IiGqccHDi6΍rc ^E^E~.u~\`aif[�\ &m$^5lȹ*K\n;\۔1g-a[Xx |× aY 9~d|;7` JE>2MoPV`޴BfwEpl!9Q VWW)K8mY0`-rhbOƆGnoy:tloo>0x4}Q5ne177ǃ"?~O@8x%pܛuˤd5 0tWǶ%o&"{x7�� �IDATDYojxG$ K:d3Б,aƱ$2b%/RH>'{|N*l!{}Kv+Jc>]_hFY`oϛk6b핇xg>"o~Re4J!MkZ+<!\bI%ڵvkǟ$MSTڞX";6P-&EZo B cbX PDdu nV;IJ̶a۬q㤻�^u'4jm |[Oc> Gt~ ep,9[pBt`!bg/o16ڽcv7ٞ5(1-焤5Vv6A{ޮis¸D9%oOYC=u~$AARD1xH,٦8c+o\tE ! n8 s2ɌWtە{л(Nn I%-ZP~�0 EAi:W\wk4t{ 9"ǐҳ_FP*r{yb%ϕkWx4;K:& C4\D12<9 Ef-_omsT]DZA<^"GZF'$$jZD*g#D~_J�i4FRG98^ <y/p <3ke'O2117}?x[8qJ5^JLӝM:{, βOZyzQx, ;vl`+ı1y^|ٳg9{l{ߤ.ѝĭw٤l}/s{=f|>O?;}4 \rfٳgyzswũ=z>W\i偭<mi//--ɓfy pzy,Gh_x#NFV8~ <iҠu.Ɋ)esy2>.^2s'BRX]`IW  3\*P)4׶*o{>(} X^d{cYժ8 P)W5($?&' X5 !S*]M QaY! uiVEQ2ﳵV aIDP:v!hh(Xa@Q|XLx-f3P,n"DKQr){v@*\7VŠ:#q~Dqp5TVe'Ӹ#t2}-2TiM/ᣥ56w(ZL,&+k[=P;("!(UB "l*8n(T,oUX\]hEUDA !ssR׵hPg\_Y8s$xqT"L:yc!ةi>kĵm yj>T/0u`E3R8ϜrLU/wa; څפ+ r.cãh�fq<4=JnѬqI67iXڵk<he7ZaY{p/%ԿR*kRV pbuJ*7GOqp }m:w bZ6 }TSk+2:>nn$?pbHu[nZ;Fb)xj; m\e9+|fsd g7lIzB$|^R'?(J׎l}�,΅ig:#Uk,r"OM;+;N;U۱e*L6͔-s!+:Vj-u"u}&>ÜiI2RZDė'n#l,~:F9 a!QAbeΆqB; :sQt ~-"R;mKZNsc~Hkk399m0F˲4{A4S,Ⱥn=-n[O{$9PWɎ3^K)T|~B{KX^^Ѩk˯ /94ByO _J٤X,Rq]7Sfgb(0�l)csX[VkǰePZJEJcJ9;ȰDe8KDXJ9%IBL |Fu1"E@EO䄸.W+345l̯/lh6p 98nN kBr( (|_Sd_:_�T`2yN>믿~˼ }Ӌ癟nvщ'^k+w5hpg?aڎ9sk׮u={6 /t)FdΟ?lO {ll)^7SOuDﶾ=s̾{JW_ƏݣA6�vO?TPkO%VxCayH ت1=1H1G%#C֪A)rr&xi[G ' šHA2-/Cukdaqyb׮/cgs˗319aS}r\wſeBy4!uf*ΜqZuǸ2!Nci9< *c 6Jsm;FZ[(B)LXcl-h�uHr%; L hH>: `(CpCP�c#}zJ&-@TX  n� K[f=vbU;}7% {7±٦ٴɡ@ e[TjD6{p#':@>_ammz%6vVix9j d|cHK6Q !oU9w^�ff<+pz|HPiS&QҦF4|nl7\޻mBSF䬀<o_=EN(Bn,!R<RFA[n:ZjR)fFFQSٟӨ֩ p|k>bك\zNa~0Ae�_<ffGߧQ2<>J-Xg?}/o^BNƊƲ4ZLό359իWp<ON|.~z6eT~JLYk8�dʏI7_L@%Lx4T,ⵏMQu[+x^:ɔzh˿=00tKD{Ƥ9<Vq=i7'{&/ �w2UzɖrD;=_FQZVNTf"Nz4ǵ#X*"Rʴi{5" >MβvCj;BrbK)ӏBY:u{�-~%Mi݂ԀcbߖAn7e9 >/}%zD|ݠ_'K6X1R~:+w~peod߲EƲr9!6S]y,QHꭘ Pj6iљH& ;7鷤~o3uw;gztUH ZzF;-%3r|~!x}&)aD-Blcc mr8pPxR!4}(`v+O4MBK3=:ZV٣8V umzF]@""RuB! $V27}+>ˑ)(JȱCC=#G<`o\p5{\ <^iW\J{rrJB}_… ,,,eMx8y$K\nرc{r9hwKy)k/ʖ_Оկ~u_Z+!* uk7[u~~o}[?$dff c'^�j\gmGsss) {ԩ=Eccc|rj՞ v'h]V۔TnWE2>Ovݳ?Td,'LNM2:: |CANNyG)* ?’HEyHs}iG͟r <:c\[^c}}E^gCTzmК < Os7ƿO6ɲ,lF v嶶6-uXLl_BES6NHUZ8adՅq뤭ؚQZ~B]`_T1#S<!Pۿ:Y:'V 1-f%bgik>H3`-3 r36-oT  tg-1pUP7 H;Q+n6i0\تf97%M\]IY�hYKZQj�cd|R xǘ6.G v_)6l7 PѨ)67")Ξ=cg)».3UBJAʫ0g>TPj&Ա�_Ð<?pogxED>rmÿ>%JaGfgkuS$b!*ylꍈzG E!g1:\"x yqͿ-U"O *aҒLMM02<L 7 4 QeF } ҕ2`maQbk{{C\_Z">|#F |,+on-v.MtRΕH{˓Oވ '?^2=mFm,lho NBԉZ|GBҴGE˴�<[b׵k# H2vصȂO7+݊z.Hq;3={ߤE&]ʵ˯.kZfޮ~ZTdvc0ybe&"Z<OK’˲ͺ &7VG\PfP1 h2{#(kuV XJc r@(6i" ik( bYBi"u)-`aKQphO7i}2>KV u3y$sw{,\w_k[̞&BVWmA@J+c\:o<Vv2/㙤Y|&]i/T{ֽPtLtԾmk:^w:BEdҒ%hBH<ݟ>,7~?a||7_##\իDo~y,|Sv_[&84X T`)(zK\<Sr8(FKё@llmԊrQF)įxEQK/q\<7~@x (E);1# eРbXĉcPJenϺ^T.?( v/??pv|g' ::~t|_wrlnwzKKK^YK3gtei/kpm'?ro,M<$0 9<w'|ڪrE_15\12>OxF @D&^L@0R?}8W251Eh)GKe6o04<&R*4خyԽI vY|q H<R&vABV5Y{eEF( EF_8"K'AQJT66aC4qhH ,$6Q:~4m>|E0&G}eQz J bK6l&}[`!lZr 2H'k|.Z`ImpXdٲ  Xj #ɦچOKg=f˸(901 [OGVIeڏX8В " >|WY&+]L*++ e^fM# .Ma\b`4ۛ#R/} 'o{US(AFY� C'GN—?˱s +#m:7Dc886 Qu/4-3E$Q)(/y R3>RȅuDzm|g>9q!pz26>||:<hKqbw}c !BR$T$lnlD#80Vanzk˛,ոr:qB2(Ōj$D*8v` @Y?FHs4J2=3 /.sK\zF17Z'*[K&I7]4Y=hڞ1}# *7 teOמPt @ʁQO@[:%eݕ}~.[>m'5T0пx, jm;u{-/#BbNhO3}7'q+61\Q|$1chV.](vDXB@ C! ">Bh"K!R.BTHql�(Zujpi? ;H,-y^ُND*H'vByMf&M<}#>UAL!/Jwwn|+Vn�]d3]dRm=*jә]-tWEuf'o7}bYg$ ,^_8G>ePe#>gc>Mγu$J:xeIoPKϩ.1<Qdrd)$Ѹ68PaAȹJ\t`"d$( p\9=Gvх R@Gm|3gRyG:dArol浧m|I+s8z(sssYDQD!__"/#ƶm*E'8rlo7kT\aT衃9u˅K.ÔJCfFH#Dftt*`<̍\\B\�"�.>0aFiuunGE\g*1l 70V P"" LPFJ uD bo%{[Zm/.p;!9Jt6c$(+w4Zb Q*4~E0PQ1>-`G"AФ;%B�t ('(?mkT7ɇtFFx7^ g�Le3_ryi8vrjfx&Ml�A8 6|x23 U|?DҨRD`5snF 1)YXjr<WVCLNo_gdxb1$S01D>R__4+oc .8p`G>?Jdc*\FGe677QaD m�k<x" Yo E_}n#PH%ӥED{C\V!%Rkjlǯ+I&reT<>Z5x -^bn\&SO�Z2OWzNfH6*dxז7BŹwģg!m8ER"X%[&'F*U|6\79Qp\+ g5VQO^:i^^W�� �IDATī&vǘ\o[%�tlkWf%Vvg_bbdyKLgY BzrmNj۵cq> Y)`"{O&Ob4o)ky.5@?w=GRm|uc�(h;` l6n؍d:F#Er#+m-P%$->j42 'Bk5EgA:-B Z֠hrQT\U|kޏRI:@`Dͻrxw vJ%R(k-dz Rmt{=�]cai|F3]mw0~&ͦw滼7?SH%Jj">D2\c~]O4 p195Ώ~*:O~ ;qE"/0}H4,.6lIa#!5G"M'$BǡFT#Ϩ7)L3yx P rEΒ() FJjuQ'Q89jcZhq] ?`$:ũ"µyG|}=1o>v=}y,_z>z8G})cX<H~8yZOZku%伣@46a >C<P۩#*rQ42OB%,4^X<qd;Nmq<46*# :C9P+c{cs?{ŏrx(a>&?4/5d4\ns[X:N(v;l6"`#Z5d\-bXӾUU.q?d %WjŖxzH! cC( �1({+IP&7)S� &_V{`~؆ N TXhG <B[ze k8_-Dž-z§Oxrsg[QSc)ǠK4<傶b0BkzH8!}|!mO9G׮#sS(yW/"h&&◟Z=F.?=GQ'C, ,BJ8 B%v hllQk4P*U:AP.Wخns5֌9}hRS3@p/{v*LSj�v+e+ʯ=ñQaO}'zNΣBj5BnUEk`?IWj#fII4uZcm e%i4}x^qJ s2LtpZ* UJn,ȅT �>j{(>q(_wY^Zk7ur)L,hƞթ[N@r rg$d]L%!<n:-a<Cxt&.UrT!*:Lxcmab ڳh5tֵɘyjczO�j=q>ڣ=C82ճgXC)DP^ym+*BiHKM$*T|%t[SReв/o -e^`sI[&kn�)M.~u&!dϢ3^ +RQguy"v1kwN=ÇuGNNizJd"(U,0xk]*,Yk_"c˯7xW }E`+~<c8=RƦT86P F@ê  7G+?%we$٤RH@C 5BBJ@ |>| }?&&yaX{#_|NDZwݣ{ .ys9&`c{48?~BߣO6u EįJuC`{b"}6=n Ts..Nb1>1H^,x7088^fzb BD^@._P ĶmMz(~#!ET¶- 18bEeev~6,j1d-XjAgD,2g, A(զRR(c"MB$~BF!HTrwRHR\.Q(pN'G~uCsSqe:Σ(Zei9Rp UʸtǔKmc~-;Z;xsF" R RI"EƒX*yjdUMTU.?y^l͌˥Ȇ(J 3 tn{tk-? nsvX9~_<nB{ ,AXBP1 ֕rEW%.˰9Y�d۬M%p肰$ #Jt_\R*AK7_sBt-b͓ 㽛҉}ppP03ДJn& j 8{<iIxkm<ǰ|SgOO9{4[8)VVV0&^gxPa#3kUa4qNF4M;4[-}^D\\.{xpX3'9rdƟSvr==J iAH-hv͔( (9?,/`6'9~Z@QN a2PƑ!1 59y�cB89!c D׺Ti&:-׹u>|8�3MGOm;6 ǎx;<K|\A ɿDQĿ󯳾kW kTZ >\l}/{1H+ֺxi绻8d {${9xJ^6|sGCh7 e6ǖ^�Z> H zpagU2z,źvAmSYʊlEOsPart4!F`ƪ|TKKj  ukb0-JXZAK�zObA|,h ;svnnBQ ڋ^wS˨(\p96++Id _KqI˖{t)&ۃS5� ֍jʗ$ԗh,e3CYurdGSKnΞ+yNN,{|_<sLMN[?]VSAV`c+BŊN9ړY\k~.)Jf3CYPFx4!MxgĝQtLl�Ws>:A;`4n߾ͽ{ צԧ>hL{1;L@7!eN`LbîHFsica3~҅et\l!p6As%NpQůQmE!fJ >~emiTL*8Ct'ƕUmܻN-+kk8u=<a F+6;RjJkڭ?೿&UĈ঵:B8=)S3aKVXZ⤮`&5Icl]!Ί#ZB$FXku-kK6o;w=:q%Ξș'9qNennD #@eVƢ�T@<,ȬH{u4d 1>({S%Z+6P 9YywPyJ_Qɳ)kC_ Tʰ=~qXhxt;]fj.NTm(jtZ{iw ҭ^"Qx^fKi*) B "N,Ar*|V1?NݤR)W%<c}kJc$;k뼿I*UOX=:Z:t:>(Tt:m0b{AQdǙиRZ ^< `"qdϕ3st4Xlxz}$rdiy s`brݤjP.QVOp,-)( ָ4v ,%a=csh RD+Lk1Z+V@HGc&᠅hU9#x(8}߿޻u~y�]nu)$a+ՙa7_) 'qLiU4 I�,/6fE^[Ԑ}vŸ9% R(njy(7ĈF=cGx@HCh3l.=۳z^f �wCi|O6 O3 [�eŶZwug/D~hʇԿxshePBI{{]dl\WecMy1Y <ha];H02qs,N�Yf TQ4Sha&6|T|80tޱFq\ u}_%:Ca[9VnZ( (*$Spɓ!XZZ" NcC8/,ZH c)C a<(&[̪-s<5JːɰKȐ0 ._@6O]5uu_y'+WOX7]aj`u1 i;P+L7o=.xJN(Gbdi!hb‒*#aV8+ehrS?YPCӘ4N>ʕ+u1iL9B A瘐52d'[5-6 B!_bޣ"Aɓs>M8V)WjD; wx9z$Sӳ& ˜<~ϱ,MLp `6,,8JՉp T1+*3aLa \+ _AUuz<Hh3@-#|u*qRIs zD&*DKT ,@,DJ{8Ak+My P>Gpx*>u><Ӊ;ftk4ȓK;燤- . MQ`9A~(^ RPQjZ6JCAif" UTj+0[Leo_^y<~#GP-i/,Mbp0R[kZG 8 0N2q$FɉcADcC"KfgOs r1-VWQҥ8}$CeyRa5AE1Q1NDrRHac޹s)A8k/PصN}IPCab/yNj5qveƙEh0KEQ!>#pAH\8>‘WM櫵}J~aT\_HcN ׺LI'qGZAb:8!ڭ� Z؁ZJUcsSKAYZ^g{A )Rll#fEKXX޽{sq^~e}YJZ[ť?˝o9,TG=?T灮b?vϿf.Y�~vLnl}-<k*j{�ָgdή`ӂES#ڥ("Mvjw"-Fq\g[^SvҎ\AW(qOc adHakFHc$FYF(2 i N r3Zk&yEe8y^0qsOt_#AP7vO31q x>R( �H^OS s=2纬$ t6)?vNɭvZ8U7dV+ LMam{@tOw|Gޒ |8b%u_8@\@s%ʵ4vןF۷9vlc=/ k [;-8z˿e: CL)׉JuBr"m4A;PF mp.P: v1 /lLcӘѓO>9ӘS48^MAv8UJ{Cdq`-n߿K͙'9{tb<!F}&g┖νq]&NGfq'QE۬tf6D:D%5;nQL;€._<\yN<50@^ӒOYoi)8Iż2` e8w }j6[FZƂF[+@)=ϳbZ+q)ԧ_3ij!qQFТ_Yc'ʥ~dc0>c{6 (Ԝ<0{H "�5 T6']{-؎g2s%HM&8P#�2�#Rۺ:+|湷8VW!AI+pGFI-b]xC*676)zvj[o*j/O?85t:n#LD%B_$F ;~%n޼Kc{aBDv-#BXi6m<,O<q Fij�Ͷ尖0 ih); 1h5,i,Hj' a :!VFqg!-gVž>U>:9Xۄ9@p銆׍q' iAbGxvFXR(:D6+k037."J䨴b2;3E^emkmhV q1 [w<쳙ҹgZ5ZL/0HPg^1gD4v1, // eM!0 uϣia ف�m8] Sp$ã`[pzZ*^ f OEF`u2P^yDA~|#…5}IJrHS7+I/#2OqZ(z:�)bmPS~R11af6>|a`ʏ=ְg<+6bI<il7Pbkrb9k;O#tұHR4BOl*kUqC! 1waP{Q t׾d`X}YB/t׾s?Lr_7AN7CZH$]uEr dSr\=RΜ)ev #X-tM bغ;ZA41L|k+8A8EGBiC!nwģD.%!"2鳏1w)ʳG?Ә41iLx 7"P4Jv H̻m6[ܹ{*2l7[j=• ?uNE'ZJDZlS.V0 l0d}T(КV H&uv# ;-ÑG$.PqOeVTVQ`3Fi ! k>o;:MmP1 n(h-p;ƺ&Fk1Uu oOVh!H!V/s^x/uzWqD"#1V3MGF�U�̳!1]rqs؉aۺ.9O9g|mevۍ~Tu/a r +h#Vf+TT*\<{^>+;mV_[f̵9^;)0BhX{Hf �� �IDATOh\SKZw?7XXє%|>wkb#Ɠ.Rz|FrT낏={ۍ&;;Mk ycgݎ+tA&$q˽Ňl7 *Gsx֚Ѱ.JhLhH4QL'}G:&Qzд{4NO!)׎Ē kܹjJPRV&ւiEX7Fu>EJHGz8#%z=WZ1QTJx)bѤ`U BE `b( "-,sOu:$FhyeBjeJFwoB^n lZw !˗/S*֚o~7_7,' M $[ƈHD~fsBX BF{%m;Ce\1?4hoY Co#xk a%1Sx<U4ɨK�ܷ]hOq!=)oDK`_oryT9kO\tAټݲ`zcbAd]`%+On-H"$)g�cƄh<i=3T'pkc &]y2Ӥ::g稷lؚ]d--&L)c'xi5YTngM׋y CLS$u" {LC{kB%Mr繨8fya)[zBcMr\'&FY!F=О`G _ÁRW@޳n6}P]sMGw3З: k}8 ȦT*K(S*%c5 g`ZqתDD UBjVר{.>;.#m<5ƯfEmL<\GLy/#UӌiLcӘ4_pVj9Zń%w(di/ wֻ6u54[!/of~SǏrjg|ajK4a'A͕WW*LQH/^dݻ07QӦn B ˜^$f8DQV:9_sm$T�eMz9 @zP�t!PJ'k]$}^^זj[&UUfffZ#9uf|.\ΰAAG GЛ8f2AoZpD`& X8`JP> # a=rn@B`w+OPY`$ Z c!Em8^AA3w4@Խa L.$] υ({ >&Z{9:]LݷVw\<t} ¸BaYX\gcIybV[Zmt9\~n _/sMV)DWM$h<E޼}f Nֹr*h.yiCzݟ&nvQ.~m7Ֆ'np,&; c2Y,VF ѽƎB6ZH &''YXKx$kV&",$<ic#<ܒ{gw޹ER.0BSn#]iK p])4wG 1ˈ DYcppHcVo yGZ㗺mǨ1T "8ra$ڟǦsaF7ZԽYJBerQna! Cʸ^DZmi:/^D+å%ݾ7^yW!gΞ䩧۷P[{chv9)~qnx}Ul1k דTnId{{ȧ#nCd2;QB4փ`,tD*_% V{LJ,^4i;?a/nM0gI:cͺ7tw!QZbC }mH 4lji@Uhb\(h "}sc%"q_n Z$&eݒHD}邏ul8u0w?]c y~t$RH9k2&s WA岹Jir* +,g,?\{DQ쇶J"oڕ =#W"GϺ_PPyDJ'.4%ȧћ5PH֚CW*Y gsG@u2_7%T 2+ܠLP_KlEC<G J9?u>ϓO]\~XUӘ41iLc꣌N]%V_ }!Zr%ΧZ31=ǭwޣz[k+ӧOw"׏avn#s3/㔫xO"1Q\$ZG*Zpne_)+|N:٩Im1O9z 1rG\y ?kl]EjSKDQD&|JY(+(KR G Ċ5SZ1gӮi,//jOx5>O{Rl7@yoeD/g4 ٿj@ QAxiA,s<ԇC$0 "@̓NR[vcj a2I-{ឡZڴ^Σ[G13])a]⎢ \}Iݻ;M^Ǒi)gLN $_bejH#al7h:/qc݌~U<oQI�_k+t2nߧي+\~,K7 6dV=aOA% :*Z(yp[[FHcE:z̒N@ N(p؂R8QDɛdax{{q: 8z(GcrrF: 4 T)]^ן`cc5&BZQ$.;JQSMٽBk69EUg;$uD%ץ\R)>qV 86 NHmc#g~/ڗ %<̈́V{?sLcƓO,uBϽ/1Yr:Ђr> X7sW4+/?HZ0^u/O4P*l~8-`tnZҠy+5g7.|OW1w ߠÀGdfU:}vŹ 1,kaiBA*uJ)MԌ*>c iFt_W س5s!�͞ycW#mTaW'mϘ89UmΝGYvks`4{'kg~P/\+$l�4Y9em4뺄Q7ߵ!Xh=z1�(/gx5*6Rή@x|2 p4dK ]Cԁٹl44Xq4w|ʞxDm$Bi²K_'_S׹tӘ41iL?[FN|B$jFUhj){="ޒӈsVk7(K[FЖ%~/*oar']RʷcW|WW <\')tIJ*ۭ6v8H|&滼ƛ~Re0/3llm.\@iG1KcBu>$I.CYc zSQF`J1ah+9n "E>̯" A@bvv__jjKQocZixQNR,SOik*Z9i,ԟc}^DxVmW(+A\aPNY &z̻_vy(DJiZytط04b  N#Ձ)j7baqworxsoujuI<ףTp[4m@MXBTe>)>(%Oo~ZgJ˟8Οfss!#6HE <v~N@ ,GQTGu>:ml0?1GpM�ēè̀N {L_4v-2¸mG]_-qcycQV%X!=0 y0?ϛoF;//vM'dq(liJQeNfAӡʬl'tֶRJ *^#^@!YxƆ:Y=Ԏ8><Z+DR~PMp'h heAkv 'O`uu[n[o/pZ_}Ͽf{]뒷K,VO˚w`}r!eeECy7{ ^ܚUwr'e<hm#4o%Ϳi9o`!^,V,B/"-wP=l^4 Jpʕ f]Ɩ۠Z$(@ |ƒՄQTh-@b:l(ʟ}S0$]z\6ɼBJ|rtPB^jh4J٥ˬ%uRʧƨm &Q`B&Ɂ{pBaG4Ica%k#1Bj EϮ35wWB�4m[6�+YO, rq8|!=Iրc.P.W(9-f&̸T)OOMNnST|}ԵӘ41iLcIyk<#Hlv'brw3&)M 1<S=~ N>'Cz箿HEZ-<ܳcw>}:K+alӎTK;mAD ,,bzǏ;|_sk?/WWe։km;hYl juWEQY-]==rK R ĺ"v,o)(r.˕q^8o$slA@/@tzN|_RKMk,�/ezeO>8ʁϵj?ʬb/9 jt]`lj͗YYJ\++ *: MƋ >XaaQ*-'_ i*鄬o5)W=Oq&''YZZbscXp8EJe&'/ ׯ_Vo|4Zg/_E6 .-Uxջ<_Ap/>,lmR*J8.Zmj�emm-9~rAIF(y@J|Pân'ғDQ32p1Yx%޾s,R|+WR.۶lq<6={r]$>i}b#sTڝ6RHJ~o19GdXeI!Dh[!lxUdт8jo5ߦl0?υRz7]f] ˾oNJ4vh#BJ� "k+e^}<O`Az/ګoY2)-P*c<{.t}'ga/~,kfD:�ܞ|-|g {o?]^_?<,}5h<ec..rc/uZe)*%ΐ1{zeUX'c;=? &x[칞6c Vp:?w;ebY F@ BeQdт[còI6?ܺ�$!`Hb7Q[nk{}hۃqoĜ9- !"[|cH+eקS E'g~b{ i{tvdX)`?u?=: hM%]0R8BJKW)t%a-}xVoF~}LaЖy?G/g/\GR1iLcӘ4Cdդ�JXX.Jt4ܭQEP7Ƹn49~l^aQo*GQsI_ҽiVj[M;+Z%jU\ Q7yU&Rx~K+t˜v6(U`sI"1ӓSܸz_Z:Uڍk|糟,_6jtz:"I eQu&4jWȮ&H,܌Na,d96 M|W[Y�]h6QaZWM ^/RVGAHRV� Jm@mSWZMc(nG &@v�D HwDWCm>^no̐뽔5T*ذޓS�SM݅۷hQ'0h^gy8W/C[09315ecJ+iqy(mժufS>yO<"E||{Z9޸v_Vu0&,tZiб?)Ć_k>{3(|00 w[[ .\<l Y^{mZ$Ç3KKKH)p$LNZ!!:EeqF܉K\«?۷(M>Ǹ|2�*urǺOƵc*2/[lmmͿ~w p�(E5Wrӵ:Y0 qn c&$ CJc'0z.;;%կ4YD b<_251ET6rXciPp5oIxMڭ&rņ�]H\0*ЖA wJGE)io#|⢸iRWQ5cӒd{Z#'*520/oGnw#ʤ鵼;TuwD/?*3vSۯvGayHJ'i@JM"v%#i]YJ1(!U#`cǣ(,l=Q3h;KC^:EK_%+uulb ;braҞ@)#%j(к1҆б5vXUg PN  _ \1L%ؼҊ_{#wMT.DW^'JŔJ%R6+BGP>zktZm.` 1iLcӘ41B]y`WME3Mk<~we>H`UDV ^[ǯj4V>z\@pkjIH;P<xR:ss ޺yeM'jc!$ +Lx:=y8qE8K/DT&S,m"p12iծDd +Q"Fmr(?t6u{Z T|n<}?WOBnb{/)outeYe t<@WHKl&T=h,B%un`%}%Kp H" |oC=S0fh7c6! CVE.]>ϹNP)8H*eIN8!Vb =zN\FlwK_R O>q?g5k넔N I6<oO}{wniw,<\0'p#IȞAwuc-W]ץT` t@6Wq08<\xRDcg\/IoA?ᙧg% ̝A- R/) kA5yΞ?VM<PbJ*҆`]G#9@tffgϲ<7oBT�� �IDATƓOf^z1fL` + .g63He[1.`Txa*{(eHɢ8,r@{ixڨgGeL̽�gA~gwq"Ǟ#C⡲r8^7N5 2Z>(u/`u{c? *^]M "wDQ%qcya,#3 KVZ"&PoApoK6GJ#Oit$/m0vffn'Opq+N 8%jgR c6{q<HmZJ[/"NeRRu$鞳_9]褜iZ@t~7!ԃRHL6l S(hw:XYlHFؠ|!Yڮw4?$8d41iLcӘ4FTa_-r҃ 2 ~^^$1۝&ALWjkk;l՛Ib J'@&p] ND;-|F'@}Smĺ7.Ǟc9Є>ԩ3lori2vsG냻1&u46dp�r@lJ@uNvN1?S2gkx:^|h!Ux';|l}:AHiP _7aﵟ7Jv8 %XqIg2`hb$L\[[ϵ<ep=k5-|%?>H-U_o+БOi֗WX[n6ͷ;v󘝫LRE81 Ch:q_˻own#=RS}#8 8%[.7v§_hlm#cbnb{�Msea5"J,s?mZDQLVV1ZJ $D6}ŕڝ\z2j(V(L>kuY_Λo6ope<}dqZ*,R 22*p;:y鸄loo+#44R$nzZ0{A~Q麜>}_8Vܾmٴ=|'OcR C}U'.){]~VHo)^&cW4콼dF}1Ǯ4@2I8,wwijXUH., gI=Mg Lt zQ.s"� N9uzZ0(xV}p)bp|]R%=a0&Bha:9:0*Q`H|ΒwO!|#%r\ٺgϟi#>u o,ػ.@)I-;Nɬ(uy� #&)O%DzP,nb*@'J/:7w;i=�va HAVHiI'qP2d41iLcӘ4xEjzB+LWIY7J:+e‘cdys;k,/a^% CԧMN4cMR@ah!R ffh}*O^X*q5wvxctRƨ񽑌ݛwA5e:umj !S�De<2I[ch7 ^O`,$�q vZf7c~{ ez>{$B$&ybTV݄.w<>H7~VGؘN Ɩ�biYoS eDŽ cu?~zj]/ɯWx1Jqm3=" pI^>?}]vv:&dmsvw035T+W.05]6Q°p2ﰸXYݢҥ\)Ͽ"& v0j Qo媓=@~뇼nṆXa&+{mc.k[ll41 N;ʑs(`ggĂ_6–ݎP9j+7 ͨqhdMTyIxcelcT*vT!4+qoߦlq}~/ Dxz%:A@RR|%w|JQP*k_YP0{t0( FkH'"#8RzW^䉓ܻwt~sǶLMMajBgz=䑺z͟1z5Q( MΡT &l,U{^A@h0 !vO }W;ZJDR& vH`>ۓ˲]W3ulnn&L||h6Jƍ.irSQ͞\*΍a]'�d{LKʺMD-t@k `(ҵ9BS r84 <H ٶtAr ed}u46 KS,}Eo>P #IҀvNhz8-r(qPbz>,`ifUʧ!tRzud d.d׍etN')Ie&g~> y]Wx[V]CӘ41iLcӇHN䵁{5S(P8^ˁAp98fdrbs[#;zjw,/iΞ"Қ H* +kĕ jPTh-)t(oAT'=XDQȽ;wx!Łm�{{htAZYHCJT ;"sCԋ{BؤfMSSX^ԙ/E+ě o_5&mp VCa|�^Ƀ<hzY]01} 5 2ߐaZty'}gȊlkp4 8r j `V qJ>#LQvO^ok=(loy!˼rr %NM"y%Y^9d]߾U=0fa@Xd 92lda8,r�[f600BaU=3յuos̛y^uU 7꽗ɳ/oI=֞|8{/qkhǂVʗBW()16s(D!UV²ҳ˳<N) ,QoR<wzO?NP " $Q`R!4~]=K`O=$^L%᥍\y5#ߧQX� :1#QTJ:a-qww5.]z*4 M0vjUJ[Z /}V+|kh$Ic< PRrpx@#Bilbaii ^ogg{[ă>!_E ^7dTm{\d8"ګW ⸴O<cd'U$Y\Xsq o\utJޣW|Az ?3̬lnl`0W^hb S(˺_Rxrn!M /UǓ ) ONzN%PLcbax}֨9vҏL*i',D=;]W)RՕ<< FR*<n[L5cgn uM~KY SR9Bq,CeMQ -@ HmR .O74.S c &SӞs?M S2PRbHZRe-vv Baõ>M=LG3cJ{ di֟LP>_T%KQ&c##YT}uTCHZ:qK}; 052@!,1cFPd,dU\ӭ9!;s1s1L=p>jaQzr^ÖUn^Ǟ|(rp�bmmZF-צuǝ!K g}sŕEݺMW^LBDTAxn$q[cGE!Ȱ,9w"gΞߏI}͛8!0asȜBX ֠D'&ps$SG"A#ɄD2:~0]nu#<ZtޟG_=J~֡ػAůq<$4�Ur9ϣ;�YCɑ~ ,<d:wPe7FÂWI_|ڜ[ RZ$_xoo}VCʑCb.RAPPWx<@L4`RN BT$QwV\8{^z7 j3j!xH]Ѕ1;GH|SO Fa"xC˜n?DAP* š�Wt1^H kMZ]CBѼKno$L@lmmqucc Z %ev?8�`r2 3 Nm{n.V4jUVW *vN1A~6X H#$ &=1Bry*A)!̙u tbAB0ឩZr9:Co"U*/.,jȩv%%ºMȬ>'|pL^T 'EZc'YY2t8iLc%/njl%r!zBd9{iM$mtܝ+2bz Q~lXb#Z2o3QdZKD7&q4z4 k3X0ȑ}n lNq�L! qjE|:NЈ5C2)|`XYؚxLXOj>]Sϔb4kN4MH 3Q ܞhn2vb;fΧtS=OLi-O*_ `lzu:iGIHK5%^io#eEFfe5> uϽZ*C>epk;Q/Bwҩ-ii0FN?֖&z:ͮ%ID{Y> 9c9c9&B.s1;we;@DG|Z"dd,8wצv(X\\ }zoaF֖xWu6i6cU!,ժ=7W!5!~{aeyv<"Z'IL'e/D L[* B`A'1/C"Ґg?iN-@$!<DTa4` X)Rw|w?}De|2ŵyL o|o ÆmZ@jEA_1qLfӅ=8܂ǹ4ރAj=]KؽzР ~ER6@ЉLAFP}yh }i0*n4_K<z1eag{9m$!"*AKKKj}sfst A'1R *ȕrDg0PUO>k(%*B 6 &8o1OV\y aQ]/<㔃v”¤xj~1(ߧknGZHmQ,Љ9urw!4A=jmg} XP# # "T"^kMx+o>gy/l6$I}T)yJ9)8a9sv_œШHk8RJ܉Iq_�4c0Z$U!\fcm^Csms ~m,;K tR:%=~?1<Vqle>/ȅկ1B F͟B2w^$){ˬފhEd#f[1DLMQ𐘢ޤ9n1wH*|Te3f|e1^mKC,$I4yAyX\xȕ_!yl^CI4-$X,41]W !ҽt\N YX>DjN:RWSNw: iAm2,)<g}'?sYȵ$&1VdnvyVYY$Li'ѵSTCnhMp(:@[ſ'M֌yq*,=~Ldh#^B*7{5SP bDycTe^v"O.焼gf‡ldޜ!=<GY1"=N*МQ0c$=1(LG2g+R$+۬GJ#Ͷ<YOq_\')iLy#]W2]ѝ0[à II^x> I")w<QyPy8(yR:6bN1s1sq*̊>q}oYGRXe1Wh6]%,V<9ߣVU|%%l*RxaKl,-b#h4$ƀ7tnpǯ}C|& ҃KtQO3)nZyB:af* % $VL&$A9aGyPX:mδG nA珱&PC"B&! .3Af̽E14_)mc&ΚWGƏMpt�.C؆Y8ڇɮ ` 678)(.*[-e89}aL%IbZ:>:Py=ΓOn E06&Dg6RTU*H)ز.JvKXcF%Eg?~j}8nw=$ͪWbQDF^{$ưƙUuDXe2-|d }T*H@%1ALY>y틗ڧVS |@CrւG.=ٳg>B ^QVޡLd<|Vҗ^[<ӹG0 KiA }Q`ԳŵK-kxJcK)Y[_V:aey 0&Ήb߾8Heڸz{$`}L;^9:<,Ys>ܓ,-nX6c Xg|e.6.Q Nnml[b $k31eDjLrBOf&rA1is4䗎;=Jʄ6%VSnhe?\3N؟,0}1JL{2_BiF9,N'ǝ86#t;{[c{9Icrd Kv.ƂԮޮ9|R)Nxftk;-ӔH9=C~Z^䌌izPe.m[ׇ0iWBZTYB:ĭRJ,,.>nc4*JMfN 1(+F󥼚n ;�n>UZc,ͫR2߿* IbGhϟYإbz>FOsBv9c9c( s"MkҚOɧwo3g6'Rj5Ɛ$՛4 6,6*YlY]](,,.=VWfݡ5(!40RX(dcc D`qċ/~*|3lo?։#GDÃ}o1u\V/;X@(['k�� �IDATBl YZ4L Ԓ =AWͻ8 {M/QQ]B٩C֘ųq;/ڑQYA]SiϽUsχ,]`hݵJ�d~ kоK ;zֹnJ\ڍ:Rӭ8::bd}m _ը6x`Ju"B fGVC*74bØW`u,/@[zcW00?ܓD͗(ayjmv,'ēU2Aң8wf ĀNC$!w6A\ kuO0+ gY_;ã>SO?#+׹CJ{9Qb#,X{>O?ڢ aiy v3 =QGk EAH\ Tt}Raf0SՉ~?&Bj2'�ZRD!Pwkd8zx@0 bbU+Mb R)љ#LJJpoq=y/I$u&έZ " ÜB*)x [XJF:P5DkPذv2"^HLM4T {G 4򷏯YdXI>׏2 =+ȬAޓ%# fg/CAdH3wTUǑ'J^opڝ6{ǭg"�)Ӷy̦W'ikEYX~HKX' VM'Gk{B,V^4zrVi|v:X ӆx ;1v,D4u677{Q䔹$F PCu\qj+\2�)=667X]]a{k}&U+?'d>*gR߰6w=mbx"9=bsGŜc9c9c6 L@4 Uֺ3Ź~&XưlpbVBxN+UW|ͳgEJE%hՈd#A ! P_[3e^/||ŝ,\|v;BȂ;аK*U[<)w_x(,B\+RYUn.Y^YBbA^v=q*ſo'FVQN# @< M\Y~2~bDCR$aE|V9]A<΅LO9;wymZZU^zEVVk&1JGqLצht}H8c&V`Q4k\~.oJ8M/XEPUdJ[)ю׉4I N{@y]RHALCqs߉$ JrUyzNВ?g=41Qj16@z6|f7O 0 i.,pR}Ԭ{(.];w}Q,#RT<c4Ik5R<5<g EJ2v!#-o)Y><L& OcѭBSe0`VyfSh+";VV 5DKN:5,ߨR*>[k`Hs+f!;L>)R&"VVA`=KY/~P2(Ɋ'lԎ;Btrdž&c؝4ug:`T='pDm)vďic,Jޢ%.m9RQY[ifLCY ;.ghwn0cM:Ȇ-.MRܔD' tO/hθ7{}3OfTrkrdliWS|t/C:u~V;2P_L8K3oJMl3{Ӗe}h.yFJ GDqHcyeK\;Wƞ:? )_-4YYYkٳMQS&Lx$C"ĎƊiԬF`N1s1s1iy\Ja>.9Cmӷ6}$|њfگ$�AN?ayynX0F?9 ۻE^/QDX~Bh8{fF@RoT1Ƴ_XZ\b}m=sX\Z%<_GDZudɉ̺ع('$yXA|)o"ᘂ`-vHQw| NXCHqRw\iQ3!Ȃ}_)ga7KhF8Aց'Ҁmt!tTۀQ"gXm@ Q\)KCDYw_=À<D4Ej_n%$1+˫my Oy=/hV] J㖖]c "&J&7#֨o4_ܹU U>Hc\z RQBd@;h+XjyσH0&q >`dևB+{q9jw~&'إg%wt9TP6VUhD1C2/k$N-X=E$ZVNʎVǥ̧?CbggWޕrY:Nta½{v%IVm!Kek-JI0*|"S YŚš# OS [1ɿcln `Gh"v;s|9$,I$Xke0Po4gG*U{�c܄"I H d]*W2,D:V-ns*'2p?i1n#Qj"ߏr2'�8m!1v|OJxIIi>9p:ڳ=eF9Q+ %o8 ,8 HY!(28)[o9hK'BF콉R P''u9R$z"EJ9y1o=&3ſZ9 ܖb3X[YOlST,|hRц]gcvINd<Rv"j#ܙ\ou?2RK3_'!-a9k?:DqXJ[l|�Z Y"Ʋs%b*K%Q 'i}!R.Yc,jN7{/'X/s1s1cc]hjӷ;_I-+H~@0>Ql4CzRz& vbzxQⴓƫ8 3g6<}<gVFVh6!> jG/>`bpV*d$bX yg52wU5ҹ K&=Wjln;40n`i3=Yl.$KQlT=vǔ ʶ!FB $5d|�G3 @vjH;* ]y"7̭+nGä29~FO1Yqpa`Nb5ΟHpm]Ey,z zaPy \,KT"V!j5VF)obŪ[3}= ՆϷ76X8ҩ<XE&_X2(z#|鳶̹3kXljsIgwga,=QxO`L֖W>yWXW0&w{mכ^hgiu[f.֠Y)$z#r-EDQDZ#0x2[[e}(җ"T*qZ6ژ}/ /NR\v_/q…ǭDL `s$ro4=4#aG / aH5^XFߕJ!d' YkYXlQ3]>\f ,ά}gЖwj\)X\n;$i7]%$(h/)bދ. ʼny*1㞦[{v:8>S@לcx:EzUdDF\Hb7"*gF;kB\q!XؔTɮZ,ŦhQ쯳pS&sjSR/ùDZ1T)si491^{'SZ}hoLR7΋SLNKޟ&]^y?w 4z Zr2(Ғ<&f*%H[,V ln_kX*iwʄBΖ7Y SAݡ1%Ŝa9JOwcp5ϢZpy0޽\c�(U{CbII9!;s1s1L7%fQn|~,^^Ũ{wo tz;}R.i&9dŽ;[liE$Zyq-P,jT*kˋ\\_boJP cЉsŵ) T?3E7!$.X !JH#९z7o^>Ig%8R8tq\R >#gO@$Hw`#0gdO҂,z*I$� vL ˹O~)tJ\U >T%w!At,Fd3B'�VM<'#09 XJ0-BZH$D-4~f_i>3Uq?&WXYY*J!,o\UDĆ` :kʣ{X;lRMc"Vt}S4j>u;$PR@ `%hdj׉Н~j 낁6ڣg6VU9Ҵ:}WAO('VMH$NΝ[#]uy7$un *n"|G%P6xu8Y\X$0 +R67y_c]VyI_ ϳAʕ+\rKGGV(Oq-j*J)XyAϐĊݽ=z.ﳰH$:= N5݈_fLzL8K~_"m(jCdqq_8'J 3Y~Jvv{B0D)RA>)lsZBfm E!a4`<VRw|B`d3O>jᙲ]X-[9NN\>L,sԂt+;,cD'DR/e@d}xBVEF"H)|7nNYօR>6nQQ9&];Ku4zʘ·?juJJ9V'E:ӕ]`1lk¦~{}y„k;%A#u<)&qU6/g9#\<M+]ݞ*t(rّ3gܴ$LKiH¹MtdPl=0/w`))a30R"h\#"ٵ- PhS:)NFXz*9Y)ݔҶrm `}}vIToLRU*KF55Oa* 9c9c9"xࡐ2oGA`( 0VFVj'?ܼuGڢ<Hպ:tcia(bS@UHr͵O<v3K4IWaTwݛwy'ILW%m믾'?~t^^~kR61Oա}Y>`XYlZ'v4WVv.=~)%q@ItPf|NOtI4l6D-_5ha1KcTc9B~<a*!,kXUV/1Sfg ! ֢i[aQm:>G]n,~/iSaDE@(H%cP~O9Qll.WwT%vqHsJxhAHMN[h�~~p`VRz^vMT+ތ};EbZ:Kes:!<>.eae|fmOghh4 ͿH(DJəg_]"6rc}uڭ[auuJJ'DaHDŽW_eii/<E^ciiNo\͛oҋ/'1O>$E*A%MXYC$ JJ<&<p{3i.Gj|ڭamP6SqA8BX ]<$ɬ X%"#Wg! ]O2g1p=>YbP&FE(O~RL0t<>;{?\:)DD1ߧFJMc22ve- /enpB8bTI6&wc2gϕii%v1c, EJdisIK9)"rSLDnM$gr{6'`fobLR7NMN&:kd&IpswF˟+(L8yADLik!03cfrBpʤyr_`h-&y\juhc',w#cw8\�*jH<Xk:9}9ČTR&'b:eKy^d7zPI/BT/ƍ aJ Ѩæȕw rͰo7& TsBv9c9cI6/>mf~+c-R(|=˴[mqqZ8n]<} 2 `u"9ËO=RNw(aщF+.sW ƹ'KYH1:T'B<Mע%A dyi0vdx@:"s#|NPH:~??bU'ޘN1DZʦqMg%@A n@/q>i]=e8.mXh6?$oCa64(C*}㗠BtC ˋUEUڇ-n߽#։pRcŤc8NBTl4@%Bi"շA—_o;A)c.=roce:$F**Jca[!H%I:ڵc^|W}MA'Jnp.H:6DQDah6k4`tI<hms6R@Y]Ͼ@}aO|K}VW<I8XJh?`:;;pmukllnJfg{wH#Ib0$Mh6=w7sy'46_~}" nqyl>IF=`M7wj)֦GڦJqpp@i.,8'%Y6NS 5q0 ҘA{u$n.kui9UPVRw=&/ul&X269qO8|Eq$( c<=LD)h 4hYLU$kۇԘI3[Y|bvƀ(4Φ}b~*hr>vD ]ZkQ N |L?Y=M`l!vwfe-Swd(TnԆf5(=dA$L!z2ZKﻛڭ* 5 5%7ӿ֖ƕvp qT):+ӳI PV4`,5Tz)qnFI[N sdHCfySsp/j[ 'Ÿd]m|M=!{.JyZ �� �IDAT>F'40|$p1h)GWs1s1s5;=KRx$> O)qRt zL F'Ӵ&Bޤ(jU'|hV}XGΰ֬ҬS Ia㓿;S,-0#$ c~ 7o. H3\Jj2@iZ 0Gi!d--DAh^[ďJ%Xo[MFj;,gniF@ z4ˎKޒ"O;WPioA ?hW&ċMc]ik(FСEcq -(7S(ڭB)%zrV^0kCB->_~>D}uﶨU$FkA@:apPң uk U oveL;3x&!NP֨VG^Ǘ OJ:W^;7+ˬ-n{_ƯVh6J$.vqaq+A Hgϻa% @8B5[gnߺ[\ATEvv? v{.j DQX*'bhPG}eo}LΜf6OK;-8ZUt˯ `0賰�YkZMٕ_1ڧ<9Bv"a1biz"ca7 YV{'I#{dY;f5Y`2:GdQP`gi^db:iiMTNs$)2O3 1ca8*m1'ƠMV2'Go#y-Z=5s)SS1'f织nOzz;mq<rS(,MA`�V~`H0S8?q̥hEJ0'C )[ft$NL KK\x-vvQʌ")r뎯񇮢H #6_n?<<<̓yBJzx;U2s 锔fǓJvN1s1s1M{ZXM�KW{s3!$җ"$ܾ~ ILXs{ńJ'+.=ϣV 0"c6Phf1|߹Tݣ.ȵ_ ( /˵W CwEbELܨgL _JNPB:W֢ebE˗7&Ȉ^)A7~yggY;G=H:**v$>7,<"4uW.6X@< {P)_mdSe.~Xa7a l_O0iiXi^CXy #$fiA7$6ZmQJB2@u0 BJ_ip6WO~΀XE^zTAM'T&uFlHc ~<7o:x'y(Orb0 -2nhMRgkMF) qc8 4 x HtHgk٧ڔcIr nr$l6EZ<* {ܼyk-v:~K6u~ܼykׯ>Ql6|z mtj _JI8{8)YZ^Z)z*\CK'܄c(dwwXz9gb$((G' n jF^sm{t:8R,/6z)lZ!2Lto|kǝgk1^,ҽn2RLvǝM"-&iϐbOs^>S<ѽgwEMK|8[{߄5oE&~[DfpΦ[RV4YBibf5x`!TX1$kU*}81^OqFNȹӝe`żAsR֒F.I #uűNhk,NA ֭SXiR$f$kAh+Ό||%!wȑ4d<G Hc^|jXKuJ`tDõm529kl_,% J9BJȑn$FGLZDqm1nS]/YL [A kִ=.Ƶ@5#6d+bRoT)n{rs1s1suYRI"E^|Y.GG}Ps/RՑLJ~Wy5QxvY3A*c0&BŒ0քa!Ouvut@y$qB/iV+ íI Oʓ? 9K}~ogkgj͍xXv{9N&HDl2mc!#QX<B`ZG~ tAGTH ƈ0` %OGFTҿ;]\d?Ò-xDQQxp? LG N<͕d‘B`"tW^;"Tv!`\_IT3_0?PA'QGVcZ5 zݘO}>G}"@Tzq^~=ll4(<RH#h֫*O5IP 5([],\<r(;+qDߩ2"8 tbť&�nM~AKi,.Q4Zb:RH$"Q">mRIj ^?H NQrutέ^qJ9Jyloosppf@B<K~g/NCj.\w$F =Kx'^` ̂\8ZA_`n*%$K@LZ:IfZ, '%"%VNPEN%H)>')iMX?!GQ,kIubc>k˘p:''\V1Ho1F,Ie*j G0Y[|Ϥ~_RrlZkpVn,fKE؇#$pxmqeK #PutHJ4HLC~8Je5SzC pf (qj H_:Gsy]w!NdoQRiB2|m 266{i/Kʭ${4޺#< : )R\b ÈHGIܠ9%1J]-n)A 98:_!,RJa\,V)T:vm~*91\CsJfqq2xk`D#osj6gm0{M1Bc�<K@IR()1#'S-e73c< 9c9c9mFD!(v:?wk?\wm%HA[0:F'1Q ]ffi4saX\V8l\khTk(ہF<a-T >ggG?YYYd8ZigjZw.^/[90kB,K` 5 pҞťۿ½#1qam&(ʊ1&=S5oE0.3zg1B+=1+&<0PNBY3YȈw\Ζ#G&$F@�diز< C՟P1E6x'inEns#]x 0 |_O(UCIw~3~7F1"_X᫿^|qjТ{ WsVaܾW<S>Q`ɤ Jt0cT\h!Q?Ylnrylm(1}ǚ<$zXV7ɇ>!nߺ{s|?pqR"\Y׆(NC676K2%a'q .sO $I&UңrmVMԛw4vYT;89*Sl"1T?{o,I}="2KK/t�3 g aa&hh3,3tAfO4d "aEr� f_gz[\cqw#2"22_WUMS~ͪ^f/oY[[뢋wnwg7޺#RׇYA&YU<t[1K:2X2k"K]Co>1NQ.-7/&:'xv=wLP:d c*0>nk;ȕʫ^kSEO 'kPy_fuvypn{DS)i?)w\gK šXS'wN[JzOƲ/k)qZxRxD"'B|_|oA}"a JSiefrl"K1cu5/bYFw,+,/tid0]E9Lh6<N:o<3`]h%E& Ǧg >)OO mCA d  OzJg) lOYl:&d41 ۷%<oy.=,K,K,g6'ޚ߽?߿ -FIBBh4q2 mPJ`m&`.8DaCbdl~@;< V;mxqG5a(㌻1Y)YֆNÿ?b<}GS"$I2[5Dq wpH' ]9Y9Ok}5�wHc*$Le@%CcgjA uYtq%'syW0܃ 1EɆ|-a ! DO~h[Om\i1"rB:WWXg|51>P-i'i* pe *Y鴹Ͽ<綶HF} ge*bM06lnb`+?{{1L1N6=}{Vt Y2D0BhdqL3H c(j9MG 28 .HE$%מ&&TGc83YF �!-ܺǽ}7v8?Fp "tZ[?UJE!\z=>̧?kzH'q3 xͷxw7x{BȦIBBMmð#g9 z ZGVH8s1)6WXHuT]{rӌМ$Z;Wr2,w?$Ii9"R)B!"P.nEݸ4+[n7X o*Q#m ^nJt`՗ Rѧ|E=W[6o4 d4g8#͜NWxiw&Ֆ)ZIGr*9fމi/Mb皂T#j!t:+y9C3qB\e뉚61mx{$"1j^P7tڢ\k1m`g)B)uePa S WSn 9Y<뺟щ`Qߛ)iDR/kRZY3e'2'-ܧ(U.E ȂXo\9u"&SBa } 4c4>c/+(d`+)]ܹs{FcCxqe iD] ?$dXb%Xb%80G9}qbJ0\`0b?% MW!lJZ++  УH#H;n{TDh%%Lo@Hڭa<&'' k_I/*urggsyYV3"[?ݢYQҰ^o Lb ivBSeAwo[> 1.$CHcGl e\UdʧӕUh5?*ͺ:'Ul9}9?5 b7 @{ F+ rR �A窔{V Ʒ9 P߃lVs\Phߚ-a#H$ >4d,3%YPQ<kP'ASjJz/pg_e<g><}uq$En%!#atFhȽh|Ľuш8iBZ:׮\SOr.ۛѨ0P-N JxOZ# `{cӹJ'"kVUVZ;4!K34ZhĽ#r>MDžm{pQU%X%JXlh_DF(IFkaLvH4 #|+zZg\t}7oDJ+Cjܤ6λ%o!Q-zӏiK]k-*PN(-f\K"FIJ+1+-^8'�q[+Hs\ 85DZ!qB�Xg208O)H a4B:z�ǿwDŽaH8[OF.L|ۊju0f"4幝@:IazdڔI}'c2~rIdT>SYk-VOx|Щ^BB'L5>c9i>lmgOը@ۭn-Y&{=):neN 獇G $cHPy3�X6X@R&`?.<˛qeO PGJ'bIxL'&cZ `4o-s]zG]);r@ܔxJl`}X|S#b9hl:X4s{<OimB!]4y Zp*\# nǥPwP/^w#?�d^a(tsZC O8<<B 3r,SiqqaZIyC't@ySؒ]b%Xb%XuGMʘvy4}gEx{$/ qB8\YFF\8a.;[KN8׀.l3tӔDG.a`,\*ehPڡ7<�!/~}'a=VLA_26w^ 6+,<38H1$d߬P3.C2-.4O.wȉ5Pk@Z"$85/'+u%"oFYS֑Lkp6.' IrgSGO9~ Z vOǢ?K/ksF.aps50J@粷ZDžSϑ+h^s4`two#4)&Y[[EpaQ~pH ZVMg%\Í"e}c5Qn+|px@FZ"v}ʈIB9Lc h4-qpE_ :!G1`XPi5'_$:h6c:kNT%Bе0 x=~uF|^zx[/<`;y',+W;W:�� �IDATC!~]�pt-ZcP)"]* e. AJqzZ '."%ݩzc敳.HjᕷC{΍!"N<J'5 .uة$6&e˝ܵ$| [0{ۓ٦ /<0PN,s@J}2{Bͺ\< 8}$ӷLsbV[MIr!e*b6f0,cN7VOƞ>R9+?@( DV R"?j1M8mj_ކݜ6?͹t:YYzv(;XZ!YI3|w#MSDR:7/lم"ĪGߙQWi<׋cpD]n!Z7)LNuمbPգά!=)} VVuX9,sxޝ b ou?뉲8jz>)yZV=Ndo||q<t]ziU \ %حK,K,­mOI6k|\o*ZQ$x]I&FC2'PZjшkW2Mj%a;"h9-9k,!03DA #(@ւmh!`۹V_k1_󩷱&+B W,.iO_vV6CHMigׄHz ?͒W# W2P8a0>YC'JiXiwa-Xh/õpS)Hloe90A= ]W6Ю1hS?>BpC ހVB<7n'18"B\ GVhJu_qOH` ,&J[zHa @Htag{vf64z Y'5 #Kʂ>Ib a {H !n\G><ylpц(lnp1VVVH3/|<ij+}Pd²aъ׺(4/B8v6G�\v_x,Kݺ<S|?Wwy+Wg}c5^CΟPXa\X/N,]Xf)oycR:gy<;<F YU!AۻR0~DNiN0fccҔrxY=H&89ZLW)\L鹠ޢ}\˭cݗ;OTfXBl9˘*Q(4QzB7D1^?-ؔpQgi΋Ѷj&u]dy, �=^}U:,F[B)ȩ"gTG-Y3CSkXyLVi 0L psB`0/'g?/8?PeL3%.dS;0) J[;!9UdǧϮX16-�>J P)nc@n-fۉkmIpVTn^٧ht vu: J)X-le~XK,K,KDMiua 4kp?3]me4I T/4GG\z.r=f`}(EA D*q@ VR RI(bmm.p?XB.k'ҩJ{L܉Mxڪ*4䷧o<3�)Aza۴Гu7ϭ;D( 񂐂lf@`@`c [A{"/$E>4|ʖP29U.\[)D+ B q\xAְ[IFX}-|@q~ڗ+./G@aۼv+?dtoa ~o˰vFF3LjVC2~uM9@Kp5 YRiEˆ˗.s6;[)HXv7/*ܽSw*ZkM&>Uu;[,M._,ˈ,͐5F.Gc'L CZo0Ҕ(hqpk1ֆbi$@�c5V�9|M1A/~S="Bz?s|h8=˅@OgNCAx<(V*(988yHpV^c5* luh t̻IJV:!.]$K'g^0<|{\$!ڑBb@"P2=&cT&VBb.& QJ?9wvhZ[ԑ:+FwLƺ6 mvOX'9N`.e7t&Fg m TcL%&h6:!-xk3O_Ey{SV3Y yCWdZ=ΕmmȘɠ[K8/*>zL[Kˌ}aBFJhyW6yk46p.wrRؠ=dg:żQFqʹbN/ˢyo%Ӛv"j,ȴF*:ܿw~bҌS JGΕ礟9IJ^,s! uH^(Fy988}_+$o&/ magThr,Ei]^@_\IK,K,'@Lo+¢YO3!My֡WړYېvZ8>>Fg91;1( 9:<t)wpK& -@MDꦵ;m  Jb<q 7ٿ{\<C:(\B:87"r+w)[鬾r�5~\0��_EN<\@;CU19{`˱p4cpqho w\@ ᡀ1ph2L>@i<d#̔k ÏC.Cã#g8O~!hHND0"ɷAS\N|[7Vtgbf?֛*o-x7u'J%/ZW Ӹz(" .oݝwlȱ0Hb>yl6b|sѹE,IY鰾u2B1NvI9r.J|J)q֚Xq=!aǞF).J} Lg V( AJG:! cVç>2>o?e8m!!Vػo!4kk\|8䖦 ;K|;%cz:i"$.څQ۷'?s8K`o1޽{t]gcmᖸ"r1w>ǽ<‹\z;PlÓPR02p!~ ')Y&y<uYqAĒؐ~n!._v1#dW[dijE8M6UJzeX OBkBY˅` nYlY =ֶO '(YXZ&(gθ1V߷d}Z'gӤe)t[eZd*?F;BVTNFDLeWi: +\Eݭ ׽^HJQ.t% X8#Ff$Z!'k*3zPG5knL #IR(=ct!J8K FEx~H#?N3,K8ub$2ꊮ} f_<=�kdQ_+jA;um"h3.Ae%1dӨ45g+}XK,K,KEeQ2 "-a>&º=Z>v5@`.#R>JR?2( QK%KcJi%[Zw3+'Q�27GZj(?8P-uo)>(*9ۄt[O;Gp`�Ulw@j ZIKPALER:+JP }j`\{-{ลpk !YDmEԞ|ع1I?w`u)\_կO_?]o OAɣ8Cs)k2 ;>֑2pB)!@ĸbiŤրmGnqܷy�=gl4><~-m %@$68cO_&? Kِ~ zPX@hD5V:@) = r9q!iz<. L+Lxu I4)`uuFkk$T`f4 y~c2`eAVXktNy^BH<xh4$6v.q,P-UV[nկ~p)aDFt:ZV#AEԉ8:{Hd)Ͽ,r.iX>l3cgj\ :pq;zG}P2`g/qںq@Z\@-Ir4UVmE<^`4DZ#M /兛[U9NevRiv{|/y+f<Xltoz= JJ}ӂr}%f[!LlJYtb֘ b٠�no#`߰I`ξ4ݺ~pnYat$^X2zY^YϖX9 �r*(\,wc]�{|Lߟ]Ѷ[m|?1q@1qg 8Jc+ߋ,9 3GϫCy9<CN:"veޜQ9B?z, %Xb%Xb%1t- nYN4 lħ ΅I-n<{tpqB�2Y]6hKDk,A)ED!ވۯJgv_9)FXc S!cui>g"V"ä7^VPkzBV*0#Ui$`}C�]<}P#X 0ڮ+roI pp.ܗOKog|�Zl !(s�A幬p4I&=0L$ {�YN큳>0ЉyP9M!އx =X1q؃ X 8L%hʵA" ?c^{osy~3T[a*CN (L�TIG!9m׋6Y5Ȁ{ #Ds_aH4iGt]lnn!l8=riާۍҒ&c1h$-0 M:)%kk묯9�ྌ/!ߧsnVXV:H)QRr@ǴZ-5d:c0pttx<b{{{5VVVhZH֦)ˆ!+7oZC4f{ç> R=8 CH!|O҄A1k${}2扫WpZWaai]XTj/$I)BHO|8 byC8;tZG)1>$%!usGNtW'*WjFeyebyz֧ `/ko-y2p&\L*Tf}Xu_kȰ|2k?{Ks=xԄscbqAG ?dx8ceȹ],s^jMGY^9_h<ɚ+{F'Deb:Vrˤ EE?< |Ntms(fʹxv]eK3XlyQIc,>~8:yO[Kri>i^ kآ@4"Ou yH?w};^R1XuA18c7]b%Xb%Xb.fmFH|c]6mO:.*,{p:W|B7IӔ 4cP鴉G1tӒXaJrd xXvAO4&ĤKj**»3ލt$YQiee:1:|p4F6vՉZk|/#BK@t! ܙ-@%Sd k 0ڇ�v+1[wKl >sp?3TNB@i` h>U՚Zm0!$h?+O;` q4i,p<>7/d }I:;jeba!c aQ ` W>w᭿tck־ZTҾ7f͔cA̞>y݌pV-HY  /]:w;)} ko~a#1rKh@jWrxdl>@ )kP$�Dst:OӔLYGk-I0 B,yu+%A C[K;1֢X5K3t:my}wgg7|3!t6Q*jH%µqcZkQREa"dks@ "XrMI$AiҹM{*o[TG/B @g8 x QmRq>O>jgQT%gݟeN P.,uavZ6(&I  ќd/*bjZ0WXɛO y}˿(/Mi "Qީ.=:\)OʠDp>o`CrlM4ʇ }&1;|RNY̙:rbȷK)qy}[(+V*Z8#< ib̴œmiG5?n\TӏڿVc'${uyP#&}WHb]/{@ʿ0olVj!ð:|]DvVH1Qh6":E҆~Q&,^Px{jƈdsaeeo=le7R};8k; եy̝j,r|E7^)IK1ۚB#Q(6cI.K,K,1ã v'o}JΠMۙsx#_2$ɘ$ Y]YqJZEEC86Dk-e%++m0%36G~ŻtB!wM q++|oc:a[y,٪&YP#Z<ɗ~&ȎI\U U/A!CnBX�+CBTM!bf  brzl8Vu۾MA0R8 B ׿5/NoaVM \6/籲}=>pN+v3vor{n|X@!م[-,t&?\ee%[\Qj3=ˆw _^Fj_; >ٟc/.mp izfy[% 1d̶35d"F2>s/ !WHd!k-:X#hBrMX 6@j ")ܿ!8F@s5g[vΝT0oW [D EGG]8w"X֖@E({L70b�� �IDAT5(gM$n2:#Mn*|<6[EG)"'b$S2€Hi$ѪE9�0$զ[,KB*'s!iܬ۴ZR`4&әo|p4 X~>E{RK\`ZG)UȰ$͌wϋ3j}UV d:2n= ye:YUUۧflTӚc TYBBv֥~eKly K 3Tۯw0ٹ&FG+T/W3EC+5vvB4L -R8摰=zi&׋9yL&f1k'§_W["reQ?B +|P8#C4\מz>ʭ+[6R!$GdR5w~yjQz6ߛIq: x⑻{NIVs(oJI̠IY%uGN"'4D-gŸ[tqZ/ %Xb%Xb%bv#4lh9ꮋ7Ӓ+p-_ioF*HͭMvv!8Eh+k 0a]!z)kT)%VO,R%(e@.tG悽4ɛƹEhϋYmL{'_CK,p;| ꧼ QNjq Q欐iPX2'T=AphLVqG*tCUJVѠpV=$&8צ4dV6.\P$� 2Gp:D{/yZ :ĸH`tg~Cr�\ظ/à {`%v] ٜ?iA?2!=K[_߼$?_!U%~&wQAhΐj@{E8"a^ FxkkhHe46<wE X_8V$Ňؓ!a,(`%i @t}6VAf,+֊_aHN `#2("I}*a)~pJIi1o-%Щӟ8҄@:f068[<Pۼ=%vΝdƒiTHTg!XX' D{8w텨uh}[J)Kx_Ҟ7ZcLȭG&9u"(=&V-\8l.6uvQIMPDQr'K$߉RcO1rտhiڡ$H| ORvvJy*%RJ)-VUR} :%|&.V9W'[O<6h`z-ͭg(BJp4hTmY:yZ>YN_\1S凅M礹]-󛥰UWJaccsιGGdYH%!K,K,rC"ְeRyC^8 YjrA5;$qVw}{3lPhZcce3EU^Z%k P83/Ϸ\i_WGq٧J2LB[\ a'Fq*�)q<ͣ"4HC07=V[�5ື2uUWtM48bv؝8BH9Ϯ'a5T9xƻxq1c7: zR9%GUЎ µ?? ?I/Ps"A ~Lf5 aPgii tj{}2!;\|k+h@Vx 8C!ZE+_eޟ& B~p@cw{,K!Q1( 7GFcnߺE$[�t,#\iRkGo[H!x|s65(ȬH뭵q6zB!$i?{8Nh6xXd8H!H8~ȗr;<<,fzWmd-!.]fmmw}~OEv@HbդܶRvy5R,'<ߌ=\}]-,jWm"{$ k]\e{d[(U3WQ&k c @i*([aX6שPPS[92c+m@"H݊6v|+x\�aw?fǻ%- 83VЬ?B$>pic ,ӌw8дMl5_~[He$Nة\4.:f3-mmhgMJlzd`|I^ze~;>~sؒ]b%Xb%XD9.XdVP|9\Z{N΅P.֊,eT�t~ݐ9 R,?g5|n1+0<nlz7ZeMY?(&s̗!낰z>,oܣGQ1ы(,%E.D“Spgae5di:΢v H{ p l_C#޷:r. ~߽7 |=/~ޣ?!08#)FCBݡ)[4Lم<5'AH<JG v~)BSXgckBFhZV ,"b4EnxPm B6MbPJ}1~) qsxxh4BQćG `0�p3/ZrW_}5>hg7?q}]'ֱcAZ BJCo:/<"Y',#M3X[['rQ<8D3x=Yާ;;}~ M ' \(J/4xISSN(fѺf=_j]ݢ*,Zs"ʂR8'ɽ92\NZ`7MWYƓJZ {wO Bj)Cz׆0NPO*`MGƸj S: l]a*yV)Xcz~BwP*@_3B ̻x RQ{YK="mZ"cKƔӢYY\1滇F0`SDu{^ҔHKBmϨ{LҏCJdrLvEF{m͝ l\dUwγa?K,K,\y̧ '-xXh0֏STEB$K!j�$, " A;S""wi7y^P%RTZO]*^ g�P`! `B~3QAj+&kߋJվsg钖ɳU>&r>ʰտf<MHyGp-[Z8t߇aKZƮIF#G&~ֹ#ۆQ@ [.JCk6/B,AKЊ¥ E?  t;[٘W#7ӿ:|9qjK?b<JY[7DQ@FI6kB3 3 |KJ'JJ2Z!8& 4w+ {Uz8yZjÃ$e*u-09qH0tcm4o~']% chth.{7n<SG{{{IB$=vwwRJ1k-c 5AVYysWyѐo|߿./=t],p@Aǣ0I҄ 8suPJX+R.\׿1ác/_ڵ$.'c4ZkqVQ+G3Vm4E4M9?peJGJ&k~NwѝKe[ܹs^笷wS/\@ m4r2T؊]t}g~M$dVN7*I{֭f 1C5iOiqd[Nwg҅+Le Q lIs*YudV)ejSO8p+tC}uZB]1w1(4KE#8nJCUY{ne/d{/ !*56)L/XCz΍{i.opR^y{-vGxZOV8ϧ<!6cuM\&2YJ@ նt&)9 Z{`A{m>}TX'`?w^ΛrݛܻMB!m*jB1G#%H:l]kppյ+K,K,L7[49=u4'-s_jBF*P;w0b.ZH-[ΒTLizB\66,ֹ;hU+oVƹH`}(7i̭^u%R#d,�B\+X0]ECt:#f *'<Ã-T wyC#cG8,ɜs#"Y;gua]e~m3udC~ADd10z|M w{S>xg' T6 GaH 6*AyeXm:J#iba Fg-V0WH3ɽ|G?F<uZ4c&&DGYf1ƹU˜)Z!őM7ndoVR=qsXWNa9~~V}^y'o),a>BIc ZkV-?'GtJ ]=R1Y3M0,еgyU4A 3FT^*Peeu!;A6OxW~q1�3a*nqGקqVNLk:Ib #%vw]~~w,jcE ^q kR)Bq+^IғFkSJޔl )wsO,D òr\^Yډ r/Sߺ')NZ1 k '?nsg v2ݤng鳙 duc:[}:U\?[zD)#Wa$ a-4qUkLRʥvw:1;Ibq}@W{yz<(un+(Zo)Xӥ:}=B\{\Vژi`2De|7ګLC6<\Z%<.D ԤrBMf:}>|-K,U*Y7p.W._f`|ƪy4db8,U~wdiFfq\\X wƭ7~̸%BD ƂhFoA;!SI:N"Xڥ=G @i6Ȕ[}5~Ws\1|S\QsreXb%Xb%>h6M ]\ȅSyv%absl :Zc̓,9;Of3�f7@EI$H*BZkj׫yöBÎp6ݵvmKJ+ѤE7E@ �ϾwՕ?^zG!F̼*++a!DH# M66=-KU'8t:IB #I6R"mТLaAG0DJ!o!6#NPDXzq xٳCR:Y)E;<YVq+  ~GɎs783 l�qCZ|Mﵔ!Oz+N_歰ͿɻB"a,dy'b .��,R.pBiK {4'α^C U5~HD~5) d;S A ;Rҹ|ju ÌaK#o\8ǩi4(F@+#G,nyfmƏB8eu~SSQFH,IT`~~eƭe111imYP`u0 Y[[3,+9k;NGBz%ږ iҥK|"BlFhpY^yw{Xf)#nyt{ofebU+\2ǥ*"m9 ( E),KJu۲5|E*:M\b֊ .fڞ]?eG@Ji(u\k.]dEQL;߿߻~s]'!Bl!Sjiާt)"f<OK*DGv{ks yvmZ>Uv^SigeIYMhsi(̺\ٺG`�Zz]䉝, E *gD:B 7vt7?^Hhsy8$HPɾ3%cׁ%tLZe\rnvŐ%@=*OfLuU 1vOi3ɚ 7#kC$9H}eLTz]agnwK/s4y7nðJmLNqJhtf5"aIceE>sV``9^Wyﲺ8geXHĚMay 7D ZeLD{-ϣ؄\AV=XDDh@}}3Ǿϙc1c#o}nwlc6m u0o<AWAF"1֣;]h ILRXL7-:a^2\-ک0mJT??nY5u*v+Kz:| cLyy }ۛY.au+C`^v.6k#۠=;Pǯ1*OX M+c5 v4yZX0B( 7i݅l7:qNme ';kvI`E7`2Q([[X9*(:.Bwz}aRKq(:qq( K8NDDQ@KPV%mΝ+טCMT`YY,5֙w==Nz Vk6nė: R۲kRqq906ADm}E&vN!T*�4MH, ]M-eIL.: B\FѬy-/-bGVygYXX? }vl++=acEԩDɑCwk"jCjmADaHEuG0R5J^#vԊIi痩ךe,Fib)eK"(VH˸Gn1m|>bbcdY+D>= Ϛ~JiotL jw*!sl`GͶTNI~uY^oY;.i5TJelסlqsssExiL%Ca!8G4{P)$ ֠279΍m/KxhI-ߗSNXQ.oX:a2_ø2VPJm#؜n\]VГu"l\XɷUA6@Sǧ' ?7fA0tU;W٩z)ӆk 3aDz"iiY*ۄ,pq9Ө0mL!̃k.E`% !IJ2NBZA:.fZ$@�� �IDAT**bcACjj>b ƈY?-N>->pmmlc6V Eqn$}9AȆU3 0vy'3V"`2&豸o�둅Mt IHE]U\TV.vtbXJ$v}c;Fs>APAKP#?,g'|cf͛K{2Zd~+>eⶆg �{Eb2az͐Ya,\r-P*()˵bZm,clѨf(,+.C+^503:#D?Kq"6'Å+"!T2=>D>{+-KKBRkKS.LLM051΁ݻQ*TC-#GKAB2P0a~XX\]<3 .4 ]h4[e~HD](*wۉ-BXhey !l&CbDYL)90 }aYIDQHRl" ?hXl[k}\GކTM@k8>;v`׮iΜ> @QO$Rc!$T$2yر$5VWWYXgyy Ҵu*%.qG:og:xOUx|5]isTIeqSzZnwR /jR,.sI'G>ʡÇi!SB&ٖuY-W,0:6̵k,/-!1>ven~ 7%L]!$zKEn[ G` 11}ۃ5M\<J'kL,:V]H^rۏ\-גi# %+Gl} g춈2n5a1YAFu9fE \-4[Lv;4jEdu'iC@sn(Vl+ЅL8v%!w{Jwh`FE*;Ӵ1r&tf^zSw^tFO7N;|8+{{tHQϲD(/Q$ss#-#SE{\<Ћ%ӥflt(,=t,C:3se!WآkV'v4DEFHċdB]_C d-hC2 KH4FFAT22JP_j Jܩq|>=Ocn{4"hՈuXKEɹWrwO{okmc6m�#O&<YkM1�QqA+|دWf!MzX6"].uuq"2,᭵62:#e3ZIyBG9[Ț^kAjL~1;qo'msSJ-p&]^/1ݠ@k]f_<P)C*ٿPwBjvoHȆOJ39!B}ow ;F^ r�JV-[ۂ/UBֹOv((7#Ɯ&qސ!c*Z3V3W9ivNrX gr$ŒF ]FyZ [`e k/_5< b4ԌK)>E I<76Z 4Qi5[h%[t RDqLےǩ&m`Q.Y\X0?"uIal ;D+x^+mPTUgvv3;Ƒ$ C^)^B5:ꝅB6}0 ӸgΜammy;Q#>8\x5D䣃:{y\?Uk@o>{wlpsXz( {S)7ON\:r:({u͸N!rrĕ+W}˶cvTqʾЫ_n:\e)vMPBVκKDp9$׋tK~7 ߲kT ҧV;@0dHfz.B+K8Q>:9[؍q1dg=B$= &l-Bj6qCT=ԩ +a,Oo1ّ}X -忙7&<r},|tN7v| "vH& d~}ޏ.aUD҇qY]mLF3Υ۠l}SW{( N-RQn|#b8:R:d؅@ZyNZ2^F;IC#37h_КHBQ['3/p,,XvuZT;HvfϮ4u]\C WZkksWJS4yhl2vh/Aku5hy4XFD {˯;�rv6mlcfl>~X|ێ`W? EkH)dկІ7(8NPumdlRGiDY8O '96|]=Qǔ}7hc;F"M ` Cm)9/b17<%9%yoKg ({мJ$nǐ h! 8 c�,m@KTb͐VCFJ)~L4eckq ȉXhCfa8^rV0!pIis\RBUdIGJ( R)WCZKZH R:RZ-k5ŕ ?j{Mt˧)Z[ v""_fJ&G ʒS( ("jż]C JSHH-y9K8brbe*6k .P /r}8ŵ+WY^^b42??<ݷbDl6X__Ƕm\ǥT*߾^Ą[(DlRHuuX_q �STGZaϞT׾e8|.EW 0O><%җb]޵FN%□lFQRL ySٌx9Z!8E(j:-J{HRB10(9zի3;s1%PDQ 4OtZĄH2tE4$9i:FCƲͶCumyua0F^uם`x_"!Yykq$&Ȥ% d蓸ˎ߭XYA ,|dU }/(n��;'lUTܑ=Tv5Xg-u7Uz%ۅD{]S9%!2im( *"R[txfW4$-y߯cg,t$[C#̎*vÛHP�p9+o&3گHm>xCd{6g<8DQ*UIJdoOh:HԝN#REFt^=S~iSZaYz Iu3nž'=o?^j댙BQGq>*4M,%)Ufp].hh&:DelGp;aua; ]qPڭ|ٌpEv`Ta}*QGi`.e\Da|kϽ#x?r{s6m'T{ ;j#(޻rw{YDy1q5ެIAZvGCmz'aB @%dl\hό iI7 9*sĜK\|RI l 1._o=!VfKr.&!<wsĚD0͆v2֟5֩BqC1m VÐQ,h&HP>̜S?;;bm<!*!ZcF_9С!~Ԃ#' ǡT7BdhĤo3}6\<.!^Je%P!2ԄJάRm$Q"j?+Q*BZ8(G>caȄI)rh$brgrC-.t5 hn"%d0$S'1v(!E}\p㻩VF=>EXح�٤46N}ueڍ֒###\r󘝝{ؽ{R0 j,,,P(h68e[8CN]' cEe!<c,..s$;vP,0y6e!׮\[ *la>AqVgxO'8}4 /,>+2cBvXFgm!RuO؎|﹗|m-v?ᣇYXYBH ;cuY!g9{+˫KRСA{k{\:ߗ昖'G׺*TN~B-l7F'x ܮKhd?I{%J.!b7ÖFl nF>9b%aˡ{T6My={\7*闄U"IBv:%E^ L~o..tUDpmȍM+ؕ07̚[t]-%ʦcJUd4gB)ҙoTz33,|z^}lTP(h40%|~j7=y~=CA"y绖;uױWϾlXܽyjYNgԉtp 9Wo32RHDcݙ5 2 zo+C6cB`Y&UEW:j~י&[\SSt[d~ ˵l\ܚ]L=tCogiB/‰3y+hv wpY~4> _xrgݬ7>h5Hi3]d?9 ?qbiplc6mlDf΂3.v<.^h+%h#M1P3JH>e -LL6O$ktC:i5mA"&GTgծn,iӮDP(: ZDPƐMnȧ0 ۏ1`ǻ`CE c .hA4e  7 s0O0$bs `{[P71[k5ZIW(p|{"UKH_abG� :c0ZA<6p'!f- %\SP I*�gm@PX h9K-Vf^iy%[6�_h<KDH-fPI 28%t)(ը1.`ZlưiCdZภ4SShq]WgcJ�d> !f-mx; .P 2?ʋD*6AE )Rt PgO޻q,s(gϜ<?}i~!|IƋ/fVɉI|OZkZW]X,PMݗ R(,CCwv7|l!ʲP~ȷUg"ÐC<%",[{<pϝ? ?WDZQ~b\!B0"J'b)e۴ZMV �GyBs([h< Z@ G{^k/jlb;vBɩo07?}a "N�Jc]QktN:jIAj9۱H K78rVǔ'9 W ;cѥ7)nS)rG.ߜykOy[PP5NyG\JQFz"4#ܑ9xx/9%_&ц!o 7y͑-C`Ifjƒ\!|Q*Aɿ]xѺed;'wrE1片Ywxkl?$ϢCU9s֏ρeڊ_$?qQ{:eOY3F|zl~"^ tDE7Jj\Uq(xxqםYLix^0Q~KsW(U8̫1OVw>?_8Ϳ/G8rdq&&'_x48J͡](J@ފbymlcƛZkR]~kX[[VAǺ]Dn7$/nLo"%3q:vXkb'5DIq@:C'[ mn[d3B/ө’>'urN-`f% %-(U1 U͏Ƴ,bǟ+'e@[r4LLY!lyA!�&G@`o;vAyqiuU] aЊUfXD!4$kV4$sl7Za�#(bPlòZy50|a j OEZc)pVWHZRbL)ƕDiP8qD@a9eلQ)$AP(5-L �+v!Ų3y8." Kk\rq&v0֐:"z !e\p9%0d=e133瞧V[5 RF! ,,>J)I-Zr߰m !eQ)ދ/+SE~2S*(t(Zp|k|&r |wOG:7O cao0 p|\ ~~15/)}c-2$*u&c2cXk3gqEoT)i[h W.)+M9T7-|bfPu\BDvze]=۹$u'ćvǥwe&y9"3Rb=eAn\?8Ok`v|2&so$jub&͆5v!6EN7݇$sQ>O]A~aXQ8M(*7As[XD_ξe@$."omٔ+UfďQFDZȄԌa[<NWk�zJv&a)ۋXAL B.‡#YTgZF^F(O?2,gzg1uܚ+&^h'.{(1Ιoz~kś}o{4B.uU<p6Es+! ,Uk| ﺗfsn<Ų;~<sVO#.sIw~.hU-~vgVsiNUo(FlB 3"P[^g|tex"_?*w6mlM pysF,/-.~Z…~-4W&'+uw'W['&jIlE%J =Sm6mGx!5HeHͳ\ֹ#Y *rrB(D N20\1P�Sk_aHR V/BU}�eC65~UW`K0c&t3PaaX9 rS0v7lۥc^BNx'h[B[~%?Hwf[\ `'+( i #+PLk[H !k"s^S۴-ue7Œ@ | TخeC臄O%ogvJ) qDZ/۶׎Z01>Ϡ"~g|2.]Z@B\#Dע\(cْ+/qes{w^Ν='O.0?8_i4؎{(T.cYJV.'BP,7leY(K<7hWZ?ޟGض|zF+O~1oo5iq+zF(myı Zg<VJL3~? qH12e}2j7Nf~�� �IDAT~f߾}ڂsgsy~j1hfY˲~ Z2SqE7U\[M5gu/(sh^)5% Y*o:VCGmXuBJVկuSUi9z3N%&Ը7}7Mo՞!رG'$F"PN=+.|)bTZHX[]u D:;\M4]{ĵr?@S1v:5Ìs= nө8r: ;%YM,H:*+ m/- ٿK2'ĤUYbl.‚\ar.1g 9uvݷgCQo= .^ar4.{b4->ǖ6wLsQvuȲYxի\4xL5Ο|]'Ё~A$!Tg)*!4^ηo_oc67 ^D`]hН\ݛD>wYozֹ7:VԥM'{\NTHR1G(jG]`\gǖM<B2%cr21N{>8V(J| 7ac`uLւ¾Lݪ9ƺƘu!Cjӄ+{q'|'@9*213VMPb<7 9y=a`+Pt {qGtҍ!/U/isU"Gwȴ-w28A^�xZ^]%$V̈́k y2K0l-J,r2;077KV K+˜tZFI )`7FaD59yURJ\e}9sqSţccfllcǎSSTG,8"|(R5(w^fffvcSԩS扏#|I4Jh4WKӻi.!/^ň?_ѧ)@i:AʯY2]!4Ҷ/8~Oc]Hd+xvor뺬E!RHT+U#p,t~g9󄐱^YenzuVK%St02#TgNSJW8 Ăz_Au`{62Ċ6{cz]ȮQtfپ/\iqc[큌y&f.JBȌNٞ\ ޤT~UZM:iBS1fZro:w'{.mJY "!Q`;̾cvRX1^ќBMH\ "c_Vs3KJ0D R-Bs>{8Ց+odg:mc~di>JeƶH~ߴv)TͰy|\ɶ5_$M׵Ds=ߤ򟸭*~RoǢH'[S6{2l+<޴/߱5 `ӿYB[H,aÊEYbԘE/Bp,ac<�BX\x*u*V}#̞<lzϰ;{=$y?Guzg=X{i޳rȃ<+_6B,ϱT 4*#xlIy?r{-g?Y^}U�|A~6 6<EQq$):c&i6ާб6 =]m{N3θ@Iq>pssֻO)͒.&IJyJƒ/[l r? Y.0i @@r~Z’13{Z`Zl_(Ж!Ip%7: @yA PFMZFtW50;Ӹ^LAUciNcg0dbtǘh�Qll:X֒\N"#8wxבvMJ΄./r .9g)H="WHgXy&mٸN ;bau :h_F n@*7к۶RJEHK#mVE %֖ػwr<s}x}|7zSسg/^eIuG/xk<NE:BkqyN<{^|硇⡇baae(" C\5aHr 0²,}Q*RJjy>FXXXZM<jfn~@*?ku`?Y󘙽e,UawϿ2qL?>ȮUZ^Pi4 Ȅ}:ayjaM#&oPMR?x!E,SVR"BkTC9D)ҲR򲙟#$cc<Ll F4Efטn=r䧰x11X7mཿel{;.e>`Yg̟,boXn߳~Pggx4t[U^8n#E~wX-m z-oV3y׶ ʹA,Uݑk:e?=2o_)%R 2aslFc\+B㌊T[ա?5OXH+݀*V$+ULzfzBt/MC=rDNІ8!W=BF!>x8 JE,ˢlmmB6=[yf:H>5Eͻ%ɞk O:=4Vv.8Qv%Q\V6;x/oJBx!<i! "EˍYVVfV JM?`N~Հ>~=SM}DiXŮ{È=,\###خͅb<Kɿ qǣ{82w"Ej8JvٯU~c~ǹajjj}*_nfq軿=j766,aaa!=CݻիWB#G?0hI~Ie`:1f7DIh@{;۠S.[]S".* jcn:dFdIf&]L*M# P ߍ< w#t )8^,4چp"3&x�,(LQWcPA`  *e-GP2u_O"kN,X^/[$tj`[tK n 4m]+)[\Y{Ė2/M5PTP]`K8ť%yt8, Ǚ>?]^m319Ezxu[`anS#!{>B^~e**cYcc;9D]__\.3?kW, ۶_s\ayih6<|?@Z( �B"ڵ+x~./U3&4,/.{FCɐPKgm&?w4[WKRF8Mb 0&[F\ed~_?Kӳ9pN>)ʄJT C$Z&aQbY.QHF8M\BkMTO<;_cMg^L]!Ct "lxzs`kTy,StKˤuVqK;do=P&tCe>i'q9\'v)& نhe-Uѹ�zVZ=֞,YqWFޛ%ɷtײ$B5*(I P)ՋW}S%i-b$\1=MkQI'deIEE7 *l P@:f˃(IeEEv+ofCwawk0d$ݕ2vwl7Ag3rÖ"c$:݇ǽƲq>qCn "b#dogAc!@ 8_,HK`[.fÎ G>̕Y*nEP[Z*eZaz4kFpx=\9yS5sףtfCPfVmNqtE1k(-w]V )"@$qE8o"[~÷!\y.rc!qsۿۛ瓟6!mlc xgbȧ>}9vXz|8BvM ̱!Dܠc_K 6׼\-7]B$T6ދo uE`eyvFq >>HoC[Jz"=\(ƱRuD^*<ev|E�F O]‰h?S p2v[LFL]>uл HII7kŮ=Jh莁Iڏ%Ke-eTMf )-mjr]:HY[e%92bR BK"ᖊk3XR/ܳpϽ'>K<g9r};~qi4+N'^}{E(p莃)0<w>N3&KccM( R8qZe5[H!|TRZTR DHoR ">PBӇ@ˆVV ’hAk'Nw^ϳ^?7x!<,+|RblurFu<KvY ZM0XϾDDXV~~ q](2N"tG1QQΜ=>9(`ǎ -h*j-SϱVkŒC0? j} u`nsZ"mG\kQr~-Z ~[гw$٘M}2T9^!rmٺ1D]rlֽRHrSW6eL/}r{2D<NX6'aWR`i,Pg,es_aݝC~Ê6Oew7"u6JWGP(ա`m1>9NRafv5./\A#VTe(Pz*wkkR#_7*`xٝ PZt|eG`! U #+m$! F_Ko\67!/iMq"Vx=+b!'D"i;0f&?-oU{\:~#|d#JDwOقQ#4=jsQ;ʢ< *qjk afa-k5Dï,h;~wuBZFjK_)y?g> mlcƭ@… ZAnYWכۿ�z>)5sBs #8q:ވf!{7ӐAB^Bۤ*\0"PuP䶥l_%gc5QxϿfth !2Q8hKQs$^JVJ #&bB:$bkXSABa,j80v� KA2cR0ֿpuԜ8뭪{ҾV"Q4υ5,}O>P/D# t7Z1ܯ .5DNUv>[P*P[ZȆQD\)Vxq 7>s~3U'ǒl%'N3?.�yx饗m;[VVVV,RĞ={i<8ͫǏSضEV۶ plj .J#X][^`KΝ=z ZJI2cԽfMkLm�zo=v{?<G?;^vG6E7 ZG>`2&vhe'?tq) ٳ3.-N^.w`;.jq)::a|;8|0RZoqBPɿljW0c*7.GFV݊m,#,)ˇvq` rpdVDEc?$^]m{ܤym5f' ?]Q cI):"yD;۱e;Yե @Hal꘼1k\[PՍ"%h/tPkh]"޴kb#3o ie( ^CSP Zo8lw+?!~PUo>~c;v_[\O]%#g` ?7u:#["e4@eH!#JMP 2\hN!PsjyvoVMCȾ260qm&֍(KP(V#^a}mhm4 ΡJF(Vm xk,gmŽ((`|f/P\ C2Шh7iuVXR,"_SS|d4ej+NBXʌQodX?^zK/ynC??xʕ+AǹO|7J6ޤ8w܆SnQm~4 Jik5hQih/&0 ,=p&Z6.RVw$T6]' ɷKjۙhmHkʈP=]O/|}.u}?>-F^ 05Ăע/ds `ETw2Q>@pɐk쌰A(@hK.X26Xja�,x4gdOo$&]kA}g%K{@l_FW-L޽U݇\*uҠ pq@TPIfLw<ʙ?e`I/)r8'V!`ls�IХ[-us۷<>ݭZHH:}Yk[Z-bShRj]U7PSA4I^e";}ux`wqɕP9GgډM2~g}}+OP@m,R"bV=omdf;CvszYGFOObpv)}}}:!3G{}}򅟣6:ipzR <ȱ'яoI#G\.sa=vwѨoG>¾}0ld!}jOj ՞Z[ Ty<oQ*q#ǨM-]2Xf@s/~}~O:_8Dr?@oeh\pqF Y (b&s1L7$i ڂ cL XkE(R ۷݈ӧ':ـlqrׇ4r@?W/s=6^6oBa�c_jə V1sva`lLNGܩ'{ȋ}Y:o:9s|n&Kq p,D%cmr&3lMy}_lHKeu-ZFPyW3ߗKthyWy3N bFďsۡ/y}s.kP٧s=ق"'S,tn"_=g?+sxҴf:[JilZ:̖eVbf>Lu\l{wsB/yʜ@yk95mb焚e_ϙϠ҈Iɱt�� �IDAT6f8i$_kǽTݹ3'2QbPk)1eVzOk7 YegM1=KVm8z+۫DzJ<|wXш<G۬x_0X.037?jgǺb[킫kD@?N}Oβ|6j#J =} R/G!j{6հDdHy(iFcS VX1kWf`wk2܃hHmFFTFmzGdtrU~1u)?s]vhm:/b`x%?<벧zd>L~}!;>>ɓ';7nH\>ǶZ-:m]1̒Wunrہ?LZ=:oh4h4۫WvFGG;׫}t#朎9پ/yAs9Y?޻X<sOl_=s`n٥;=`g~0 ,8f(A"T:s=.� 3CQh#QșJZQ%hzOFLƛ JQBX(Κ04FzqGeTEp}W]-�y�D-�wC =/!"Q:C0{;JѫSFۆ} ƣwzBFӠ ^�As)>gLKs0u1g*oy>n?VZ*')iM0ՎHl^5mƍ;WRdkrˇ?DZFG>֚1uGƁشyC+HSOqA=FA@޾^*L<tc)>'w7xϳoKH1;ʁ-uM,C 3uXoMm> j8t?yZ@m`A3,|�FI3]JP){_x ƍ;‹2Yu*.kg?7g<AUҔ<4T.qn2>BO~Emrљ`^2f~o vx\}Z .'w栯̼sۙ]egu{kA{Cgmn3ΕS2r,x{[ ymG?:CNExU8l9'z.x y.9yvt99} sfj?;U~p-vX},;3g̉0=x8kWȹ{AB30/Kps/20g|sbKofoj$J E@Zx/h3R^sE7Yr&h0r/G�4Z(^fNvٵoB|iXdw-'f3d3}*6PX(""C(YoPdƀ6ș< `M3{类1-Cy$s3΄|ԙKlذ?I}^BU/@HO)�< }}#L=*h Tɲ<^DO(#ϗHI5č6)8m3՘drrj/rzB պ8?4:㌍'McJȔVE~)B,K()o L)2;UWs_=?7~78v@g={�022O~>_jgݻwo|cq~ _w_WpsnPdi >]}VœO>O<|<�Ug>_ַ`ݥژ5>>C=###<]cٟxښ N1_`ddh;wj5|>fbb~Eq\b_{SV[lǎرcѠ(?�{8}4{<~nsf^�rN6]b}=G{K] ._~pp F.|ls.RG.v[liY^<w: ˏ8uE|mm,Es",~tq,]jhgRhZ}k~y#_ƚg})/ FzWYfHmq.7yy(:RBJٹ&OxgkN9yKSȢ/< JZYuԧ49B% " ){("GCdyXO5XϧTUX4 N1~+V144Tky#} `VT(vfr`.NƐ cV'f#9d~m01v_p=wv"$ԧ>չr{j/ų;ƃ>O?__vy纂_W馛:'>|+8YZ|~_|y۳gY>o s}-BC1sobrrrA_FFd]_1o`8v 7tY\߾};/’>K7+pA>O388x~uYVG?s% rϾ8se __}>688kO-Lx7T/~s{+l% IsK;hKlǍ׳ixaq#a4& bjJO2H⌸7fF4$TWfd=#kspMlݼk7mg՛^֬/hehmӠcr"C5lZU+VSaU6IF:C9q-A rfz3?⳼speWgw޵kvZr lf>Lܳg۹m۶u|$ƍ߽{7<<@ÓO>y^ca]ζXZݹs{;sRy)J;sV\I?P8)صk.޽{v=êU�Ǥiz~mcݝ6Ebwqqqq͐m7ykM17X#@Xm֒@9daE6o`Zar|ZBRxXt1*#B0$T�yNƜ:ui7Wo *d7@Ir9v+BG><!4,Y"b.# ioJ=͊ |*zBTtCCk~_gV]} AtyG;N:g>(grڟTdw}Q ?u~g={x;zr7MFGG뮻6GFF2I_}sΓ:o꯺^PxnqOt.9m/wodd/ܞ8,C- >[\/;A^~=k֬>ZZo͖-[6'?9hll܃tqqqqxf>GGJܒ&9Ig43CDV7VH<Ni53Z͘Sh4kL'iY$I8M5ILkW!["x}`Zwp�/ K> <A cS&&sb0y"-2dK%4�/9$ rQLz,cg=SZKKhNb9f|Ϛ-W ~vu/u _Br/}Kg}L\_z~xAYޯ}kɽ۸{,\l^vɗN;r??ZK/u޽{w ]P�댩\./3by)vynڹ+,X}뭷]w1m m(2Kqqqqq}!;vO?9L,H9kJ` 5VpS. di 2QLd{٬YaL1 GJ_G%j IRV* r<_'1ST4ZcV.Bc'#r5)NdF>q, LDCV ^ vf`!gbW`-kne`hف3{챮sK-;r3*?u^w2\g/h4no۶Gy�#< (9߾}h4<;s=~umȢs.8zc^b]bqޭ غu+�Xm4_֯_OZ]y1שS:7o|SX3K9ǎ8888ޗ26~-Q1\[2jZJt'Hui&jSG)AI6C#eLWW&mS e#O m2z{JФFP ,n!l؉7"<0TFۄ,JGP裗Jd5Jfnw\q\sC4O|yow^0C=u{n_b~ O<wy 8w/9]cدVww,(m|6箝blsq.ƍ;W_}kYVA\в[qqqqq+. koO|sO! CH ࡵAkKyr _6QJrbl)~|O3g^  8YXK,ɰ! ʤ !yڒgFZ<ؼf1VZ; %9^iP 3gJREmNC&B<Ȓ3W,HYdJ)1Ў&jӜOQǼt0;n-m{!+0pA6nܸw}yz.KeW]).XT8V166|(j}oqqqqq+. أSu**I<O̟*HͱVr۹m(cSMi[&g }uA=iUQaPJd BeMFYn[9VYR$y" |4!%ғ4M<!i5BkJa$hT>I h|=!4I1^@$*V?O_Is$`Bb; ̂CD78qyx|;w}G^p?hG#wygw###<l޼7R.ٴi^yR۶] ڼy3/PR;nٲGqqqqYU@Л X@ e&(DU$6rM}aդiv* €<i@jZ&d@oB PM^Й&nHR""Ll"yB1J6XQxHHZv AK@#}=0_EXx>}a$'Mq&+vT1(W$͘?5JI/ٴ=ݏ=\Wn|s[t{ =v=yk׮@jZ]v�o|||΍7UNr~o}[]jՇ 1oƂZ.W|.z̮T8[v{eݺue^{yӹ>w>&&&:Uqqqqq'rG?@ X0`m*am,}e;P!Hk@g<7m-By-V˄%<c`r$z gxC EdEV$VAkA{N6UGۈ`TZ|Au7s6񶍬ݲe+ cd W1xU+FzL I|*ΑB#=B!4BN0P$ ۢqedy*QHOr y߻n}l۶mw߽cz7C=<~y;;`B|eB̟3[0Kُ9fWqRZ%3U<6VXѹ~Ƣ߿sꫯ.;8888s{d,emY?p#;~8I: b$OsJR7o<@ &k'ĭt280D9*aZ6 }tkuRP!:Ni%MdV@Z)<\1) VWXޕ <0ILh Y1bh73zNVLrA O`@͔, jmɴ!)B)PO2ru~Z-}L䑑㎳?٣>ʗ vmy睝_|??;Y楗^'```o|/U<7+>/"P}k.E77|l3,.^ wq###`?RV/~du1<TΝ;y7x7|8ι\s5 2SvZc^y�'?ɮr,K88888& ;=vjpˎܶz(ǎ(SBHz*lr5o\3ʕ `2s &O(!3e}-I(0`z' z2:3y|-h<$M4S%T%wOuR_Z%M+D)O3tmdY$qNgeԦqުAxaHN\n �6Ys)9'GGy~)KW2W'<`׾5^N`rϞ=]oڽ{w2|+_ 7t6lXP.Ŕex1,5wRy߾} /vKqYq.뉢3w,cǎ<x8;[l7|κTupqqqq}Sv0RX`U@Jr]p]ŧ|[oΊj}N &3H)F1%*U0V5bNb Bh C|O)Em$F15=NjnJ!cmFr``c=k{+X@b)/)WKB*>=4#4D2갇! cjȲ4AC5iYfxzϿg'}蜏G>dKٰa=;w<¬| mۺJ}]wŝwyֶw<\{w(uqb-[tnر+|TU>Oj*(+3,r-8888\y<5R0,`ӋX 6aF Y<IE)H%+%+ȵ!0O~DwB1M9IJFҜxô& mx"TxW !~_ Rkڢ4TE "`{r:ORIZ�YQ5b3CETHJm MDclJZk-,F[giLOQSԧ:gK/;GYtwͮ]ڰax8p�{Zk.V^՗뮻{}ۻr̷m?OVnfȢ㚻/}<S]7w 7p^ؼy_/�� �IDATq1|68s\rϏ;vڵk,VLJas rrAN:tg6l8W\yq\s5~ouqqqq%1vvf"hhҷ35<GkݹyΆ .z'GϞk1fXBIE9,zPdqS>q$:BLpbqF?tZxo?TCf6OCR?݋m{K\;@�q FSʧmTA"J,AQl<ctNc$F,͈79_@_3xUU_]R]ACRAH1)yA,,ijCPk4n4iRX$Z~_YuEw8ʎ8888ίÇyJ<(Rv.B�{_dO-jZb&XlAY5X6 H@@EZa V>jK^kP 髖<363}'6(J<S))+HC= d`J:h0k v j `KDbbjAĪDA!@ Y*i*' <YVZ R!Bs^Aڼ {qqqqqqq><Gj\q1AZA049`V),Q */j䚬"7 a-L=Zu*,Q`s QBx(/�"DUBdD+Iyu`P #X\B t3}RB` fjREь'X5Q@Y%Bns*0@'(v/"tv9w888888| ],nӘ/{`A@>SF9ϋ)3X1d:#JAB%h\R"biP)G2QR2IzƩ&vN%)bWE%"<}/?/F FJ&֠<KEX\M^~MuS&Obۏ HO E(eVĨ0qqqqqqq>Pl\+$|BxB,yik`m"TPƳ aD=t1Qi#%3DT*m5&8r4 ,C!EIyR/7֑kjp Agy'JրٺXDX2 F Drm"*}(a}TQ9"GZUl<a /+1VT+.ZCQZY888888s])l!ĚcƂR0 Z"r-2@ʼdY#4aC CV%*غ} )UJ ]['TVeNyշ嫯{f|xVnZV P)!DeE+|1X!e3tn16Ex>62kwWTV@y),a{ؠh_XDbƠl-RR!HD+.׮vqqqqqq+> OFkEy]-N0c-S@HUd|=ӂmX40(a$R*hLAT`tUR,#4BjE=$S*=ЍװwA9˯ oЎXNJܲR<H2MmA3I78~bS㴛MBOpæY_%S2k{)EQՆ<O *2q@H+!1ffج Qdc@frbqqqqqqYZ[U䦘UJ<B6O1B -Oa!ܲ~1a <rm#8iQEeaR&ON2>==}C'K}Ճo^uWpjE6y5,(?,J*cf߫36I39]Rشn-[^Ț =)-&KI\F"VGF"m k4RJeU+=eӎ888888sd !SR3AG(E $R ?Q(ul XC-XkK<DT@S?: "O0݂[n`8~kؼv@!%&7haS8G>L<PUp mgxxJh4ȱI 0`,~#h͑Z 7(U�k:Zd2MD c4dŎ888888udS$q)B%B59Xs%%Ҋ"x+,ZJ~ )6q,iԧ8u9̊ I'"T*AQRXpM[6OcoWCk:(Rdy͛44N14P V$E0_XWf@ ʶZ#U� |/A�9Z3X ƈ"OV%ȬZ)5s�2$-Wqq5ccc311A$ˊJR~V\ɪUT*888KZcǎ1994IE <0FFF=`hZ8pQ"TVYz5[n\._^lkz4ny )Z[V%y%+B +$SX@) F$I)m͘Fb=<!H(ktaLM{󺭬7FW"V' ^MYWckH"+WȵAz 09Uȑwx^x[fM㧿x#ϲn 7n?R,ϙ<y'T*DJHrA46g< `k  żZD\;qq]2pa8Ԣq$""j:qqqqg&''9rG)iR9v{ZrUWn:^p߿_~:SSS<xok2qd뵓di$c-ɒf !%VyH 1,%M4d+V ivʽet[GАZK5̔yP~|>7@(Q0^<E:1κ5W9Qô0"ЮLjC@;or;|[vG4g Wj0jy;^+)"Ke ;"` 5W I88I^痿%89x GeΝ[)Йqqqq~]5M^~e9rm4 ^u^u֭[Ǎ7h-þ}ػwcxm۶G=2]h5Zu%TEcY,X遵XE Rę*E oU " CzUPcaRmLugV GPauMSQER~9ghaODܚ~CXU@*UWoR٣,z!_-~SY^g }WUHĊiL1:Q#rDOo/J!+$RPc B*[ɐڠLں{qqMNN3,z6lET*RcHz2Hghp 7Wvqqqg,c߾}<xpAV1eemۆZƾ}{}ڗYe%ܣ4,~�R v87DQH_o/=er/Am5ҳhɲ ehCjRYr/ 'O"@5X<ၒHaz=R<M F[?%R %Q*PZbE:2DPYɩiL>:gv}5kWWe1M&,F‡O\k0y& kS1"(>F!a{>*@I;a3Y ;qqLLLOqVkappjڹ?MSxwx뭷Zw+�.(8888WzO?$YRk2<<LT""JP1v'Orq>FOO%UZkwq {ue^/NEdd"U\B+D$y 6YP)-J@ ms(!C{>aJL€ՠ5hTbZ9cX|>&OJb0sfQPU$ ͩ1'h6hm#Y1SJx&-X� $ ALP$ު7LPʾOoDaP\jfxw288{brr]Xعs'6mZ1A0446l^`rrW^R~KqqqqmllgdYuM6q5,} NWTx뭷x7m:?ٵkVy[tQ/AO?l\&~{xJ`,Z5ܒefܲX#`Ƙb%R!F"Bbhc&):OB%T(! B�kZ Eh9:nc i DP3 @y ! cr V*LM5 �)QJDJ &:9QD9>=LJrQ *.^vqYSN+~;k֬96'>>ˉ':WbŊ]888:6l`<k?r ^{-{ÝeiSOq뭷qwU.c>HlT*RRJy$I59Z{ ||Fi>A#"s<J!ȵ&s(UW,iX/}6L#E</@  \a4ydY_`4d9j6JaU =hm2S,mJRa XL?3E�k4(tj3ZB�k$TbZ|PV*w_88<O^4я~e>~;O=�$IkƭzQ8888[}]vzJn3tp?\.Lً`9%?xAc-Vk<adƐ+aXZ0Se�k驄AT c-Xr Z[t k,!jcL2t1"L)x{83 ^"Y,U$J%niz4ҨzmmzaemOkckޗއ6Xff=-Y,RUb"LD&�C3`f߿'\Q: (Pȥe1Z#u a@0aO9($tp<\W >H-"BSEL1&4#UJ�B 0 !] c,:xg|-bX6 c+<xYZx-2bX,bX,IӼUB^*~hbl%]]]Uc1?~|ռzyDB aEAJEshJ%hJєbD,Y 됰a& CtI(HC.X!D1>"0@֑Tt&̝I ,D] A1utH1((E12[a%B"̑H+0Ɣ0Z|6:j(C}kg-bX6$^xj/U6Db;w<x-bX,blyS_ӇMkmX6/&ژ> "oa(G\<WḂd] q$.PJȞxQD(Th 5BC ZܳA0 s9槦 E\ύY@pԥR!u]Hm0Fym@JІ04hXtz !ƄaH!��%P+I=>gkX,ŲsX,ѣGioo*;66Ɖ'xw5l%&'' X,bX,bYΝJu]_=_ut:-.O jne!W  ~HjHRHT88Jb18qW\Jl@X!ja11+ p=ϋDb1OP,I ) R 2i)XpCr1R>|ߧP̓dEX6aKVHI9,P bQ,)$Zk q3}bX,ebbcǎQ(Vcxqin޼ɱc8uT $ÿbX,bX,d|rվ#G<R)9RҥK,,,|fm<[<lO&00A$ZM )B"#aX:npOX>q"(/9#?rxc\|^c̒m֘bA�DBm^@f~|>C:[V@8!(BH| ڠcҵŚX,+^ CjPctVQ1+Ŝ4>!kX,cbbJ+3gp2\|3gάz-]�UO*/P((ֶN~\@!W^&Kx(c8qf½[,bX,RPb͛7?֚O>3oy =$S)swEr"r$&]rSnd: sQAIE/D9SKy_#^֑5BBapNj\D" A 07D1effh9 YSx m|) hl>͔E"!%]5ϕR|֠ :ZbX� ( %$A"FZ,by5m[[[~|URB._LOOϺ^Vv s($%o~|8}s[?1 `{ h91ܮ^v=z;)bX,b<ܽ{7nٳg:Ӝ;w)%{qt?5?J͛77صk+)M56G!_$_2y\'}֔4FrzJJ!8־DlZ0V0QJ."\szFH : :پo{^~2sd3ԧ4%H)JuFh)eJ`ƔV6h]HEA&Ct)E"%J(J;@g&R?bX,O+l:mmbӧxbվ~>[ou֪cׯ_y;bXޮdcb9tqr3\[Z]v*%x$*VE-Zѕu6{kX,bX,gJ1`ppU/,,1FGG~zAӟQ~ q}}=kbYO 0}O(l')>ZHH5!\'cljY8R:Q.OQDVya4M1:,m4 h#$^Sƞ.6˦{i<H DCd+lt6yYfiapz1OЋvjFVm|Q> )+K)"*Dm7Q410x-bXVpN8/& ^{+WO>ͥK+ňb߿n~~f.مly{'%ѻ5軷SÿL^*ٺs/ M}뭎-N\,%�� �IDAT'bX,bXVp֭ s媅ՅBUW' pŜrlٲf-[ܻ.%/AH1F8)$#PR5AhL ʷ ZDQAQ地Z(j,F("T*#%.\tA@1 0F#B*$qO*%!ڈH Cb<L<P, E9dC& P\F d y (]M(l{h-bXV0>>WGÙ3gV8mڴC@ Z+WJ)*G^˶I!Md(:-+3s=EݚepWӲ>㷧g<;RnOuylxbX,b|fΒ,N$45-offffž%W?vn͋477H$"L&=fTT̃lmED P21d=E mL9Uњ DLh4ZGѱJ  Ld)9"XR8h|:Ԅ:QT2D Ⴁ0_qRר"$DjB6Mf. q7�R"LiЄѵA|\>G.@>/[So\b+mټcy�#Sy_F/nG ˽xZ?C__{֓tzߞNMN~soBVoR؊d2k"?˩SrJվ^~*1v~~ǏWYd w3؉uҝ4\ N2Y*E�"!0}6&L]FgK:_:#u`']\rs96 wo0r; ]$�tKѭs$u./y/^<Coa&et6SwC0DytlcT3ɮmLfohg M(cc.]K0s'~0zmgȕV+/Acs]]tWG?o|T\&J-i_Gs7 f抑͵JLPѺuƆbX,byVmܳ-_ٖWGGG kO [jOKAd? EChDb!F mCY  0(""I(v(rX ̆ac$Fd昸9o"0!wq$5&EtHHnup1  4ӹ"O"Qt+%$-G* Y0Pȳ%-+QEₑec@ ,;D]ƣ!]&xvt$[{i2]Wxf|c[#)oղL!/xmgӏO#SyNLsl$]-)z<S[}{Z\~Y_Vʅ0Yb<jS\)c8}tM1СCbtcǎN/}[ٳϗ[ZZYەpgt*O_o|p67ȚǮcN7S]%wYcIé3C-~жoķ1t=�<[+p7+,,m4}x dnGw9} g\O\;u~_yūPO5ULt7?fqYT,cÜ;ȗo)O_V`bϟ8Wy5LOOp;qpn_YobWWC?K bX,İ<Ruڷ|Ac7o\q{^%+bYS!~MN{B8m(#`cLEI1H A@%ȖXCP̓/າ4'u@:(!wΓp]13ShXObCZ@$_NohCB2sB]K MLs=#aEP!b@!瓛ϑ˳#5m@J)EN#=۳�>ض OCr�wZ}N9g.>L%DчB!ɭ"ބ~~WCO"<F-˓۷9qDp]]G]!~GU/A�7o_cWRJ:DCCCUK.>޽{wdZ6up$PO@p0vEMlosm`c m^ʓ=^mR{?ddqJ䑞NSL@pJnf3yf'fBZSG7!6ѓW+5uКP9&f9g~qw8y9~ehZhi U SA>;\,'RM2Vj=?ݹMt'Ȍ_c$nh7VȌ RZkobifhn%Khm^17~s_aIO*MW &G.p҂Lպ)ږ?aX,bX*ICH^l۶aޯ\epvA.\P^ .d۶mN2\˽xj@W_/GoP,()a}?)%(D�GIKyU](l E"//Rg$1!%14D A<KIu(,d 4v9D]Spj6c, q%P|? |- CM!0 E2ـtHנeٮXLF vid>@ JBG5t$S}fdXcU^|?_a( >_zYt%YjP.#@˳̩Kc;u!oX FGG9qaVWWkVJub{W{F)ūJ___U˗/s}[n]5ғKWrS6Ռ][\@oIr,D6/ >GK$X{Tֱٌ.e$v^:Ç7Ŗ%7o�07a9jใ fRO2#'Izckɦ敭uU[#y<׾د/1u'|Y{VŹ;ee|~Y`]gt,M[$Xn"p۲r>'g_8=\V|koQz]]l>?,;ʶ<aX,bX*S illѣ !_OW<x N>֚{ظjڲ˽xjx">&_EcP2@j@v@ ԑ/Phm(`\E=Ph C!".>A!ʍJ 5~9 RAVTís(3S8R= B|.r5ZZZR!D( '=@" cZhB(>L|?8 B�@&)gzR{gd"u2]lIO\KdO\dֶ'Ied*>sW{JXv/.E;:rvlwm&P(VGc֜:uje<X32v=bKV߿gLoavV?d6�=tɦNBZ'1*}t)1Gֽ~7^&RDe A62wJoJpHm~89惸ͅ"%ѻCoa9޿Z;T!.u?εMqޣblDGAMcKٶ~¨WT n�kT-C/}/<OdXfb`gY]n.:?Q bX,dP닐(FUoom۲˽xjY#o}_g/1Zk">u�BJ0BQXm@10F*k_%$CP@*ADpd)*bdk/Iƙ e\bRE=[yE9M;dGl0@FjBYnO2912.q@$Ir}fI֙(.|!Oz'J`oo}}[ReVkeE,VvƗrkt2]d|"i+QshFYCQf㖶8[ں}ΡѥoCԋSQ?Am=TFl,X,O.[nɓ'_Z;<<K/T%r16"ƶq\isnpi el"Ϧ8h9'l˖� g)nLiwwƁ "k&&%7o}m}y~QjecRݷIIo(N}@V}}!+^zvO{?ׅtkKIP4 Y bX,ȹsOعs'?#իW ^IdSm/p]~5t\Q !B�ZJ&ʱ*@!2*20R`!AE !1FC2i@: F"r I)ЮPdc$Dtf)H$J :K@wO_dxrzmL$2!F(BVk.)牉:H(‹&S8C&.a3BC"Ʈ6uT"Iں>ږ%Ax/>^VlIɭ snIP<пRӓթ*[E}r{MO0q%ͷ߾2z[R|[q3|_UQKm /(2Buh4w>r<L =+-K:U^K͞㢒==ɿ?vgE$zꞸ:ǿfoB^\m`edp% Vk V76 zrjEomK?ѳ0PW[kB!g8;]u\nb<} 8Tj{ɚbK"Vꫯ[駟r̙}mmm=zXl'N(ߎݚ>&n*{~B`/v17:Y>޵Րݛ?9^LNfI}N|]f+Ό߅?;739G}A@X\=l$j-3^z~mþa>@~G[mVWuo|:ܸuL(=*Y\Ng;knG{ŢG?6,bX,qg\TprUێTk弼L(/ $QnSscHx.RJT$4Q4) (F01A� D9 %%\A)sADѲ81$&B] "/P Q_$( EܚDQD #, %%NQ"D9 bqL%ʡXċɎVvB܋y|3~zi<~6؛PN)^d+v& q/~:ZSZOONsw߶4 73By,ck=6&rUףaibNM-eK\K]"5ZX_iU2 ˂Am=k_J^9. kŕtͱ7<{}jX?UZkN<Hu[-1vvvfdr1ŋ|U:::8rS+:@+7n^z*]gQs3ܭ[0>?cGGjw"Biy| I&O Ãη~_%"c>j̑^+!|Ծѡ_r$m/i ;IIfb-ZmƆbX,byrbUl>)^vq8 k]t: 333ċ/\>_g)eYyTYd{h߶!\mR(*R1mJG F"1C\C 8"5E)I,�n%!H "s" >>k%6EhcH-q%QSya>E j^Ў-7s㸱2"C˖k_UdK[ P."#Ss]Sl"Q;򭷖&T+-q(==Y%V}H{9W|w4P.w +Eol@=BHjb-1R{Xm ~\ {K[Ͽ݁ӭ쟑<_Md+w1˻[C_/?͋UL?Y7ɟ)Gw roed(2 wnGU7{/W?]3oֶ �csTSeX�5vZA^z饪/wرcUqꫯ!SKmoo؈&z:$w&4gtD&}/RlKpz;ܚc?4д_#45TٺU:pյ#?mo I&]\W2zRƒKG.|z %1:{x=o.ϵ:U~j]_\ޞlcp&E7Ohe>*c?4O.(U0*}=aX,bX(HGaȇ~1息8Ί\޽{Ab[e*чP[ح<d@Jvrnى ܽK 2",D`z'QYG\% @I"QDA!R.SMPɧASx8Ck)  D>&4�\9'_)j0aM58D"F6�Wl(I0](-̳/Mu(!sQt{1>Hl* Zn˂חMޜ.9gr!Cns]?vrÃg򻗢H"_[3p#S*AVƍО^X@ёxykbSzh兲;-_˭|/ΖT;zk啹\Y4R_˭e1|K[?(qroָߏ|\wGs/BIkTy&<qiX6".FV-/c/\'|RY]B篳+V oW"~+okO׃ov$z+43OCx[y,[WmmItfyC w>z#7=‡5ْ{kb]W׾ln"cċ/݊HWZٵE6/wy4cbX,b<Y4773::Zޞ`ӦMUeRxGP L200@[[l\.G6%S,)e<K,#LH$H&LOOs5rՌvjn^=ϳ@"(G+O(4oWD ߋx]Okx*xc+;1):ڻB08 !T)U#$` B*th}_K0)K1b1@Ho#e bq7l6CfJ#".ċB&CtX/ϢB8 9m()"cDY`(m0F0C( | Mρ=h5BQ8JJTp(ZvH{)(YgDz|�Ñr%2Geq^]yITiĨX.ή*v6�� �IDAT~`ۆE ;uT9ӓSpr/zie7VE?Mrc#kY[zfCE}{[/8|s9 ;rr[ٞalXl|\~jyؙ;VbvZblgg'G/K'Tw8!!׶۱~ZىERT͜Ktj]pM-Er|b,hg/=ţaEu Jq5+@jB]Y.znzԟ,ƶ-|~}ذX,bX,O=svg?l6+�6|yna۶m5#9eyzg&\.BUTpWW׊z8T<"SeNj7woT=8;x1뒈ň.#yu1D#\p8Ч%̓t^p%%J9G.NKk;)Lqq'G͐Kϑˤ',gYK2 Yt6 C6?dY ƈuqdh,Bf<2k T!_ΫۻinhB&R8uSU$[i޹R@]VG3dY{cے_.̖V ?Q'jVYkS=4[wUbGGVZC?6>|v|VD>3]?Ϳ%FV_IVor]%*d$b.=Bչ**3Fx{[/k{KYӽgjX~39uTM15mGY"v1z#-/ұ@]͏.7Lb6)صodC02pl+GO=avzIXC@`yVh3LJV$[O[|t'>b<SYgI;w kX,bX,&s+;vE"f~'ܾ}{CֺFGGywsd{nEٻwV?l/mk[z_+T߹sr !VLUk><�ʋѾc'L&H)]rܪhYؘ(qN5\<WQLO&PJ DI<OL)yy'!RNd=1̧g uHܕo)E B0F hM( !(t6 lod6z;R$ux^ ׍!Tr`)C([<t\T*'?T;E&Ūk ?C|X+sދʜ�{G&>6>鴧<H7tW?.LG"f,bDZ,.ŽwkCx{[>~qָX,ո~:W^ڷ[IXq8|0]]]Ueϟ?Pվ.9>cGKn|0-膉Q ~zTW'Ɓ~bڧ4ǐ;y<''40w?Fw[|68ׯ]b"~@jZz4VFې�a)>{7غzhkq!sG+ICGyb*Ȇ@*W>fK ?~TҸCBi->g8-1nkQ LJF :kX,bX,>.^XUe\W^yH$twwACCuuu8N4O 311XռÇW222:MMM%}-\@Ymjjb޽+޽{Dlc W\)o !y1OvѼe7W/!BM1&Oa A#€@'0%(2E@9$8u"G`!RD9j'xC,'d"DKDt<W10G@)ɶV>{ oޡ 0$ (*V5@>GGc*@8JF(߬uRҴm/V- 3InK+]xF=(?u?<l`#wu?(7p.䟆kF?[IɎ-˳I?jbdl%SSSUyJ<ȑ#tvvV=w\<blD[_7ܺA$NZ6޽$dt5 fՈǵ*tٱw+G/[yW2 $[>f~.óxDmH9z~u�]>;|6YLj @y&( xĀCqL=~n$ Rlzqzvϰ`MبU=7M^teL/ubX,b<26mT%^z;wRWWWUGKox\a7R5#43̊>477\]̳e˖r{)%|IY)%cΝU]ty{z2OұNvA>e)Rvn6_c%hPBhy|? x%xҍ='phŶqH$uA9hj" F). HBѾF!JI<rz[ncfHq&5Tb0(#ךzן#"r5.%A8BEQʉhiiKqUqhwv,wG/z+`_HAI=6槣K6`,̏,o)4~TZ>il?|>˥ַs_9TC8|=OX,Irɷ /T}>`]bٳg9w\վn>L�n܈DD=ku2d0+XSbrYh{̍j7*xyAS|44EbF{_ЮhjuKIYF-vlU,pZhtdfOos_RI}4r/i3G|uGJ-i~p|itDɍRP1=1]>ȯN ,9k؈QP&]cnŢ(cdcbX,̦Mq�ZkΞ=+m(+6yٳUbMy@)Ŏ;կ~U'ZKvΝs%d2@dŎ;H&WDرYdriwW? 2;?<&I1I$+F}" X뢔‘ 1#d)(CVI�)`D0Zqp81p1Rb@ >ѡF_+|n4t BPRHpqp<T,A˾#Hɚ|RL"gll&2"= KgDz|cyKp}H77B`ggH?6s =FI�*ZV#Sb o2F*Mjۓ>.-Ν;U۶m[EqÇ~zb#GjZ=s6ow˞=,ض|s[3?%D.do2s}@#%u:x魯/!r]et|sZ% [_esgR$ Ėl"[Ϸ@f>F/nb{Ģ:yߠvcpr,Po|llyԏMQTI:*&Nw/535W$Ak彮ubX,b<Rǭ[BkX`qN:EnlU)/@\~B?W^err|>ω'x79|!ʽ/er'NZlNLƓjua</#^@}c---(?ğ[ɉӞjXIP(ea~C}<NC]=nK$pI7 #.ҭCyu(7p< !# Bz u^D:%)%IaWXd=mn9^Ϟͽ¡P :D*z(:Nqh{0^Ób.ޕvÇjG=TXfVaggLΡ,E{/n 70X)ЮV\X|52>j״jY.EU+s֯Moݭ}ŃB!dh4hfrͮz:yiVZ׫i Fƥbaq%D/8+!W^;44BY51!c447@}}UIT R bB$ʶO U�|CCÒ[)Gvxi16:g,51{{jf-掠~ͮubX,b<:ؾ:Iknݺ/~ttt]]]|K_ZUM?~jߎ;VX'?H)y9Y~355FLMM٥ax뵫zyf0OӮx&%IRuF` Ţ_(Ϧ.̓X*I}<Fc}uuuQT0(Ez^ljgk4HR.B8H"'Eqqύ ȱ{wyy<e^ȡDJlQohkz[+A(͢ PUћ݋EЛ obQEbfm InH(FdYRlK߆9yp(Q0fs3gp~ :9s(q҉sRRU.տytobB *:e((ɚPy{Hŧ~/_jA*m5rC)~' ޺pG/vуz|?$̓Xg9}s_ ea[?ؐ^ r^`swm{Bm(zz'-Z8ټƵO?Q,eUOyg~m/|V4??u|]}gOO} +}.hlc7<ݺu^~alZZ|Sw'=?X���,޽{U*MZVuiU*[7M<yRvQYc}[7oTTtiUcoJٳgYn><#0֩StYOW.uY:ujƸ*k>CѪ*ܶ[J*8' WE&el"&ժBcDra|1FAX#/'k0#%202#Z#J]"眼(IRiUY;)u2SbSKhr|\O(_&&&T,ѥ|(*c( YQ{Au=ڿ gJ5o\9o?ۭݱP}^k?Kё oљ\_G<٥4C[YrsqЫu44ž޽2Z}!l[3zH}N﯋\w/$ӟtVٍKqQ?طV)jLOҙsP;ohJ-wm;/sk޽22 34l7d^�Lt{xFFFZTL)crѣT�[(*Tta|TlWպjYww]?P7$߳[ ���`H'No]aGFFoĉs===?ɩLEwȈN>=7c8qwoo>_I}6I]xQڸqz{{U(Z\.ƍ} = C:tHUTK RȈjr|\*c877UY6T`Lvߚ,`PꔦL+H2&$,ls( yQDNY+UjU2X6g{6Vq XBNsEEqkeyDZۿ_]ٽC}na\ӑ }kanW\I~upjlciMmr[{ySyK][6?1>#loϚc;R={H/-b_VZqк-R+z~b_6}m}wgipoO4Ʒzmkus80[|m8uڡ-zm_o~Yћ m;u ӣ+WH~zۭΜ9>hc#&ڬCϮ;$U4;@SSMz2S_ux?���T*:uꔼϒ?[Δm aknݺ_3*c1:~b۶mկ~R~={?VI YXÇgu>[sηF{/\sJDioIhssr^+I%ժ\2!TT**wOcʥP\^\^B) DQ& f!MR/2rBi0eXބONHiUAC#CUL˻$MSU+UMLLhrrR>2a0*( CA }(P拊 e*sM{Wf~ օ;j,˹*Uօ;xC^+{5WA(e[߹|o55m3Yc͹5}m[I{;Ǟ|u~yuDCSsdk\9{l7yRR9us:k=Ss sm[;{z/u26 wf<vo\s{/iܤ;+ӡq5k8׶] �`yݺuK΃0::7xCT*cՉ'{Va͛uѩ:K:صWvlű����$ʕ+]ϭjmۦ}sicOf<n /%5+Wԫe{bիW9] @Ԋ;f6twCI)w$UTT)RdYLZUr9 syŹ0/Xyy)uY 2A$* :*(˺TQSc*r6OJDɊ|+ CX6I6R`XH l,ı0T`s2q$?چau_eE;Ss {#�Xf +I|._\DZ;{}ᇺtҌ7oެ#GP <dYd/T,���0۷|F}{ΝSwwy]]rE*:8ǟ*v իyf.5QC<o߮{Ё~s.\g%Trl*š0PUVV'T&Ҋ\%&'&QqNaȆQDA TժWYYT*2i*KIEާriEVFYL*IK&T;ckUȚ X& $@a(sZ)*XMٮ׾cV�X٣۷oT*;֭[!ݻݼys6T>++7h����hqF<yr|W400BM6iÆ **;&&&4>>?\}rcJyBҁt ihhHތ9r֮]k?$YՁ$ڬ/_|疌fzkAˇlw�� IDAT8O$^i%VTS P P&r6V\emZ$(+(^H&j%X(U y#/)kCl L`l8/Y|zyAqd-Z��_PБ#GjbbBRV ;00˗/X,9-[ȑ#�����RRI'Oԅ tҥYUrY\ڵk٣(Ēе9}:> V} +IQg?M edF&\Z ;8z N4uJӊsި* 1V2;/(*:/o$gmJr?"ZUV덗*d J+Zum{Vkw oS䦕^� n:8qB{566r>:t9�����:EQ_gϞյk~nݪ<Mxz"YIa5)^I#=z^2VJPUi֞84H{Ei"'/ldmd}IykdIy٩M+$/yYuh*52rO%oRϞ#*nܼBgO߻��,TOO^z%]xQW^d˟Ԯ]?|�����tttرczt5}]>:;;yfmݺugzbٚMʭYk5jE s$"&򾶕VX(Tʪ]Z{'ﭜ󒜔lQ(Szc$/.^9kd9z^=&uBҟE(h `er9}_Ν;umݽ{WrYuvvjݺuzJ/�����nuwwҍ7t] krrRIH0T.SWWlS<TO\ +I6յ k䓏4z듬׸uYRjYcS4M땬^ Zy#kMY'/c5* cU BWͲM\V덗Kw>r^AjZk7iXe� RIRi�����qY dk5{D JVX+#/,PѬ+罌dǥ,G5N7j즃ج6z'R9'o H۟ӺGU|wN���������^#dkrkz;v5z*OScM*׬Tj$2O͊Vo n^~*9Wrr> $bwSs|S+}����������,lV#Z5]P5ULJ%$$+t5NaMvjϪbdmry/',{y$JDi(RquܧҖg>%����������@Wgoɲ_k&LnbL^Nf ?XkqI>kw켓U:'WG ԧ- +���������<x ݡ<]UVu*#CJ˓rK䝓QXQ$D28sR*,v���������d(,t*,tqJ/���������#ή����������`"���������eB ����������˄@����������I871s���������Uh)����������Ld���������`���������2!���������e@q0m ���������GYs9W6ڮV!K ���������`5xV����������gϥ d佟U ����������6YkWہlslq=���������Q*.bv-k;o%���������@kC4CU2 ����������)xQ jsbk2]����������xԲ<tYq~V~뜓^{9S}眒$Q$3X����������Xc툭 Paփ dQE jv[`ιPA dWY����������K5WH*vōl\ȶZ`-mNkճk_X*d���������,U־6lc ۼB,(m\lsZ`[ g���������,BVҌ�9m]vpqm;a����������/<mUܪ:v[;U!ۼǂ Ѻx0����������|+]lJہlqƙb@���������ò@\1\snA)h1to뜫/97+mo����������X,׻JX fkhB,eqm JqFT���������XmܚMq*v-:2tmnW\f����������C;lcϷm.|���������X:] fmks �����������uōZhsB d���������,bBf.Œٚ5ϖ���������eYrd,mD ���������`<Jy]����������j,(%����������TT���������2!���������eB ����������WZ[����IENDB`���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/docs/building-cmake.md��������������������������������������������������������������0000664�0000000�0000000�00000016702�13262451251�0017573�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������## Build instructions for GYP/CMake under Ubuntu 14.04 ### Prepare folder Choose an empty folder for the future build, for example **/home/user/TBuild**. It will be named ***BuildPath*** in the rest of this document. ### Install software and required packages You will need GCC 7.2 and CMake 3.2 installed. To install them and all the required dependencies run sudo apt-get install software-properties-common sudo add-apt-repository ppa:ubuntu-toolchain-r/test sudo add-apt-repository ppa:george-edison55/cmake-3.x sudo apt-get update sudo apt-get install gcc-7 g++-7 cmake sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-7 60 sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-7 60 sudo add-apt-repository --remove ppa:ubuntu-toolchain-r/test sudo add-apt-repository --remove ppa:george-edison55/cmake-3.x sudo apt-get install git libexif-dev liblzma-dev libz-dev libssl-dev libappindicator-dev libunity-dev libicu-dev libdee-dev libdrm-dev dh-autoreconf autoconf automake build-essential libass-dev libfreetype6-dev libgpac-dev libsdl1.2-dev libtheora-dev libtool libva-dev libvdpau-dev libvorbis-dev libxcb1-dev libxcb-image0-dev libxcb-shm0-dev libxcb-xfixes0-dev libxcb-keysyms1-dev libxcb-icccm4-dev libxcb-render-util0-dev libxcb-util0-dev libxrender-dev libasound-dev libpulse-dev libxcb-sync0-dev libxcb-randr0-dev libx11-xcb-dev libffi-dev libncurses5-dev pkg-config texi2html zlib1g-dev yasm cmake xutils-dev bison python-xcbgen You can set the multithreaded make parameter by running MAKE_THREADS_CNT=-j8 ### Clone source code and prepare libraries Go to ***BuildPath*** and run git clone --recursive https://github.com/telegramdesktop/tdesktop.git mkdir Libraries cd Libraries git clone https://github.com/ericniebler/range-v3 git clone https://github.com/telegramdesktop/zlib.git cd zlib ./configure make $MAKE_THREADS_CNT sudo make install cd .. git clone https://github.com/xiph/opus cd opus git checkout v1.2.1 ./autogen.sh ./configure make $MAKE_THREADS_CNT sudo make install cd .. git clone https://github.com/01org/libva.git cd libva ./autogen.sh --enable-static make $MAKE_THREADS_CNT sudo make install cd .. git clone git://anongit.freedesktop.org/vdpau/libvdpau cd libvdpau ./autogen.sh --enable-static make $MAKE_THREADS_CNT sudo make install cd .. git clone https://github.com/FFmpeg/FFmpeg.git ffmpeg cd ffmpeg git checkout release/3.4 ./configure --prefix=/usr/local --disable-programs --disable-doc --disable-everything --enable-protocol=file --enable-libopus --enable-decoder=aac --enable-decoder=aac_latm --enable-decoder=aasc --enable-decoder=flac --enable-decoder=gif --enable-decoder=h264 --enable-decoder=h264_vdpau --enable-decoder=mp1 --enable-decoder=mp1float --enable-decoder=mp2 --enable-decoder=mp2float --enable-decoder=mp3 --enable-decoder=mp3adu --enable-decoder=mp3adufloat --enable-decoder=mp3float --enable-decoder=mp3on4 --enable-decoder=mp3on4float --enable-decoder=mpeg4 --enable-decoder=mpeg4_vdpau --enable-decoder=msmpeg4v2 --enable-decoder=msmpeg4v3 --enable-decoder=opus --enable-decoder=pcm_alaw --enable-decoder=pcm_alaw_at --enable-decoder=pcm_f32be --enable-decoder=pcm_f32le --enable-decoder=pcm_f64be --enable-decoder=pcm_f64le --enable-decoder=pcm_lxf --enable-decoder=pcm_mulaw --enable-decoder=pcm_mulaw_at --enable-decoder=pcm_s16be --enable-decoder=pcm_s16be_planar --enable-decoder=pcm_s16le --enable-decoder=pcm_s16le_planar --enable-decoder=pcm_s24be --enable-decoder=pcm_s24daud --enable-decoder=pcm_s24le --enable-decoder=pcm_s24le_planar --enable-decoder=pcm_s32be --enable-decoder=pcm_s32le --enable-decoder=pcm_s32le_planar --enable-decoder=pcm_s64be --enable-decoder=pcm_s64le --enable-decoder=pcm_s8 --enable-decoder=pcm_s8_planar --enable-decoder=pcm_u16be --enable-decoder=pcm_u16le --enable-decoder=pcm_u24be --enable-decoder=pcm_u24le --enable-decoder=pcm_u32be --enable-decoder=pcm_u32le --enable-decoder=pcm_u8 --enable-decoder=pcm_zork --enable-decoder=vorbis --enable-decoder=wavpack --enable-decoder=wmalossless --enable-decoder=wmapro --enable-decoder=wmav1 --enable-decoder=wmav2 --enable-decoder=wmavoice --enable-encoder=libopus --enable-hwaccel=h264_vaapi --enable-hwaccel=h264_vdpau --enable-hwaccel=mpeg4_vaapi --enable-hwaccel=mpeg4_vdpau --enable-parser=aac --enable-parser=aac_latm --enable-parser=flac --enable-parser=h264 --enable-parser=mpeg4video --enable-parser=mpegaudio --enable-parser=opus --enable-parser=vorbis --enable-demuxer=aac --enable-demuxer=flac --enable-demuxer=gif --enable-demuxer=h264 --enable-demuxer=mov --enable-demuxer=mp3 --enable-demuxer=ogg --enable-demuxer=wav --enable-muxer=ogg --enable-muxer=opus make $MAKE_THREADS_CNT sudo make install cd .. git clone https://git.assembla.com/portaudio.git cd portaudio git checkout 396fe4b669 ./configure make $MAKE_THREADS_CNT sudo make install cd .. git clone git://repo.or.cz/openal-soft.git cd openal-soft/build cmake -D LIBTYPE:STRING=STATIC .. make $MAKE_THREADS_CNT sudo make install cd ../.. git clone https://github.com/openssl/openssl cd openssl git checkout OpenSSL_1_0_1-stable ./config make $MAKE_THREADS_CNT sudo make install cd .. git clone https://github.com/xkbcommon/libxkbcommon.git cd libxkbcommon ./autogen.sh --disable-x11 make $MAKE_THREADS_CNT sudo make install cd .. git clone git://code.qt.io/qt/qt5.git qt5_6_2 cd qt5_6_2 perl init-repository --module-subset=qtbase,qtimageformats git checkout v5.6.2 cd qtimageformats && git checkout v5.6.2 && cd .. cd qtbase && git checkout v5.6.2 && cd .. cd qtbase && git apply ../../../tdesktop/Telegram/Patches/qtbase_5_6_2.diff && cd .. cd qtbase/src/plugins/platforminputcontexts git clone https://github.com/telegramdesktop/fcitx.git git clone https://github.com/telegramdesktop/hime.git cd ../../../.. ./configure -prefix "/usr/local/tdesktop/Qt-5.6.2" -release -force-debug-info -opensource -confirm-license -qt-zlib -qt-libpng -qt-libjpeg -qt-freetype -qt-harfbuzz -qt-pcre -qt-xcb -qt-xkbcommon-x11 -no-opengl -no-gtkstyle -static -openssl-linked -nomake examples -nomake tests make $MAKE_THREADS_CNT sudo make install cd .. git clone https://chromium.googlesource.com/external/gyp cd gyp git checkout 702ac58e47 git apply ../../tdesktop/Telegram/Patches/gyp.diff cd .. git clone https://chromium.googlesource.com/breakpad/breakpad cd breakpad git checkout bc8fb886 git clone https://chromium.googlesource.com/linux-syscall-support src/third_party/lss cd src/third_party/lss git checkout a91633d1 cd ../../.. ./configure make $MAKE_THREADS_CNT sudo make install cd src/tools ../../../gyp/gyp --depth=. --generator-output=.. -Goutput_dir=../out tools.gyp --format=cmake cd ../../out/Default cmake . make $MAKE_THREADS_CNT dump_syms cd ../../.. ### Building the project Go to ***BuildPath*/tdesktop/Telegram** and run gyp/refresh.sh To make Debug version go to ***BuildPath*/tdesktop/out/Debug** and run make $MAKE_THREADS_CNT To make Release version go to ***BuildPath*/tdesktop/out/Release** and run make $MAKE_THREADS_CNT You can debug your builds from Qt Creator, just open **CMakeLists.txt** from ***BuildPath*/tdesktop/out/Debug** and launch with debug. ��������������������������������������������������������������tdesktop-1.2.17/docs/building-msvc.md���������������������������������������������������������������0000664�0000000�0000000�00000016271�13262451251�0017464�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Build instructions for Visual Studio 2017 - [Prepare folder](#prepare-folder) - [Install third party software](#install-third-party-software) - [Clone source code and prepare libraries](#clone-source-code-and-prepare-libraries) - [Build the project](#build-the-project) - [Qt Visual Studio Tools](#qt-visual-studio-tools) ## Prepare folder Choose an empty folder for the future build, for example **D:\\TBuild**. It will be named ***BuildPath*** in the rest of this document. Create two folders there, ***BuildPath*\\ThirdParty** and ***BuildPath*\\Libraries**. All commands (if not stated otherwise) will be launched from **x86 Native Tools Command Prompt for VS 2017.bat** (should be in **Start Menu > Visual Studio 2017** menu folder). Pay attention not to use any other Command Prompt. ## Install third party software * Download **ActivePerl** installer from [https://www.activestate.com/activeperl/downloads](https://www.activestate.com/activeperl/downloads) and install to ***BuildPath*\\ThirdParty\\Perl** * Download **NASM** installer from [http://www.nasm.us](http://www.nasm.us) and install to ***BuildPath*\\ThirdParty\\NASM** * Download **Yasm** executable from [http://yasm.tortall.net/Download.html](http://yasm.tortall.net/Download.html), rename to *yasm.exe* and put to ***BuildPath*\\ThirdParty\\yasm** * Download **MSYS2** installer from [http://www.msys2.org/](http://www.msys2.org/) and install to ***BuildPath*\\ThirdParty\\msys64** * Download **jom** archive from [http://download.qt.io/official_releases/jom/jom.zip](http://download.qt.io/official_releases/jom/jom.zip) and unpack to ***BuildPath*\\ThirdParty\\jom** * Download **Python 2.7** installer from [https://www.python.org/downloads/](https://www.python.org/downloads/) and install to ***BuildPath*\\ThirdParty\\Python27** * Download **CMake** installer from [https://cmake.org/download/](https://cmake.org/download/) and install to ***BuildPath*\\ThirdParty\\cmake** * Download **Ninja** executable from [https://github.com/ninja-build/ninja/releases/download/v1.7.2/ninja-win.zip](https://github.com/ninja-build/ninja/releases/download/v1.7.2/ninja-win.zip) and unpack to ***BuildPath*\\ThirdParty\\Ninja** Open **x86 Native Tools Command Prompt for VS 2017.bat**, go to ***BuildPath*** and run cd ThirdParty git clone https://chromium.googlesource.com/external/gyp cd gyp git checkout a478c1ab51 cd ..\.. Add **GYP** and **Ninja** to your PATH: * Open **Control Panel** -> **System** -> **Advanced system settings** * Press **Environment Variables...** * Select **Path** * Press **Edit** * Add ***BuildPath*\\ThirdParty\\gyp** value * Add ***BuildPath*\\ThirdParty\\Ninja** value ## Clone source code and prepare libraries Open **x86 Native Tools Command Prompt for VS 2017.bat**, go to ***BuildPath*** and run SET PATH=%cd%\ThirdParty\Perl\bin;%cd%\ThirdParty\Python27;%cd%\ThirdParty\NASM;%cd%\ThirdParty\jom;%cd%\ThirdParty\cmake\bin;%cd%\ThirdParty\yasm;%PATH% git clone --recursive https://github.com/telegramdesktop/tdesktop.git mkdir Libraries cd Libraries git clone https://github.com/Microsoft/Range-V3-VS2015 range-v3 git clone https://github.com/telegramdesktop/lzma.git cd lzma\C\Util\LzmaLib msbuild LzmaLib.sln /property:Configuration=Debug msbuild LzmaLib.sln /property:Configuration=Release cd ..\..\..\.. git clone https://github.com/openssl/openssl.git cd openssl git checkout OpenSSL_1_0_1-stable perl Configure no-shared --prefix=%cd%\Release --openssldir=%cd%\Release VC-WIN32 ms\do_ms nmake -f ms\nt.mak nmake -f ms\nt.mak install xcopy tmp32\lib.pdb Release\lib\ nmake -f ms\nt.mak clean perl Configure no-shared --prefix=%cd%\Debug --openssldir=%cd%\Debug debug-VC-WIN32 ms\do_ms nmake -f ms\nt.mak nmake -f ms\nt.mak install xcopy tmp32.dbg\lib.pdb Debug\lib\ cd .. git clone https://github.com/telegramdesktop/zlib.git cd zlib git checkout tdesktop cd contrib\vstudio\vc14 msbuild zlibstat.vcxproj /property:Configuration=Debug msbuild zlibstat.vcxproj /property:Configuration=ReleaseWithoutAsm cd ..\..\..\.. git clone git://repo.or.cz/openal-soft.git cd openal-soft git checkout 18bb46163af cd build cmake -G "Visual Studio 15 2017" -D LIBTYPE:STRING=STATIC -D FORCE_STATIC_VCRT:STRING=ON .. msbuild OpenAL32.vcxproj /property:Configuration=Debug msbuild OpenAL32.vcxproj /property:Configuration=Release cd ..\.. git clone https://github.com/google/breakpad cd breakpad git checkout a1dbcdcb43 git apply ../../tdesktop/Telegram/Patches/breakpad.diff cd src git clone https://github.com/google/googletest testing cd client\windows set GYP_MSVS_VERSION=2017 gyp --no-circular-check breakpad_client.gyp --format=ninja cd ..\.. ninja -C out/Debug common crash_generation_client exception_handler ninja -C out/Release common crash_generation_client exception_handler cd ..\.. git clone https://github.com/telegramdesktop/opus.git cd opus git checkout tdesktop cd win32\VS2015 msbuild opus.sln /property:Configuration=Debug /property:Platform="Win32" msbuild opus.sln /property:Configuration=Release /property:Platform="Win32" cd ..\..\..\.. SET PATH_BACKUP_=%PATH% SET PATH=%cd%\ThirdParty\msys64\usr\bin;%PATH% cd Libraries git clone https://github.com/FFmpeg/FFmpeg.git ffmpeg cd ffmpeg git checkout release/3.4 set CHERE_INVOKING=enabled_from_arguments set MSYS2_PATH_TYPE=inherit bash --login ../../tdesktop/Telegram/Patches/build_ffmpeg_win.sh SET PATH=%PATH_BACKUP_% cd .. git clone git://code.qt.io/qt/qt5.git qt5_6_2 cd qt5_6_2 perl init-repository --module-subset=qtbase,qtimageformats git checkout v5.6.2 cd qtimageformats git checkout v5.6.2 cd ..\qtbase git checkout v5.6.2 git apply ../../../tdesktop/Telegram/Patches/qtbase_5_6_2.diff cd .. configure -debug-and-release -force-debug-info -opensource -confirm-license -static -I "%cd%\..\openssl\Release\include" -no-opengl -openssl-linked OPENSSL_LIBS_DEBUG="%cd%\..\openssl\Debug\lib\ssleay32.lib %cd%\..\openssl\Debug\lib\libeay32.lib" OPENSSL_LIBS_RELEASE="%cd%\..\openssl\Release\lib\ssleay32.lib %cd%\..\openssl\Release\lib\libeay32.lib" -mp -nomake examples -nomake tests -platform win32-msvc2015 jom -j4 jom -j4 install cd .. cd ../tdesktop/Telegram gyp\refresh.bat ## Build the project If you want to pass a build define (like `TDESKTOP_DISABLE_AUTOUPDATE` or `TDESKTOP_DISABLE_NETWORK_PROXY`), call `set TDESKTOP_BUILD_DEFINES=TDESKTOP_DISABLE_AUTOUPDATE,TDESKTOP_DISABLE_NETWORK_PROXY,...` (comma seperated string) After, call **gyp\refresh.bat** once again. * Open ***BuildPath*\\tdesktop\\Telegram\\Telegram.sln** in Visual Studio 2017 * Select Telegram project and press Build > Build Telegram (Debug and Release configurations) * The result Telegram.exe will be located in **D:\TBuild\tdesktop\out\Debug** (and **Release**) ### Qt Visual Studio Tools For better debugging you may want to install Qt Visual Studio Tools: * Open **Tools** -> **Extensions and Updates...** * Go to **Online** tab * Search for **Qt** * Install **Qt Visual Studio Tools** extension ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/docs/building-qmake.md��������������������������������������������������������������0000664�0000000�0000000�00000011373�13262451251�0017610�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Building via qmake ================== **NB** These are outdated, please refer to [Building using GYP/CMake][cmake] instructions. The following commands assume the following environment variables are set: * `$srcdir`: The directory into which the source has been downloaded and unpacked. * `_qtver`: The Qt version being used (eg: `5.6.2`). * `$pkgdir`: The directory into which installable files are places. This is `/` for local installations, or can be different directory when preparing a redistributable package. Either set them accordingly, or replace them in the below commands as desired. The following sources should be downloaded and unpacked into `$srcdir`: * This repository (either `master` or a specific tag). * `git clone git://code.qt.io/qt/qt5.git` * `git clone git+https://chromium.googlesource.com/breakpad/breakpad breakpad` * `git clone git+https://chromium.googlesource.com/linux-syscall-support breakpad-lss` * telegramdesktop.desktop (The intention is to include this file inside the source package at some point): `https://aur.archlinux.org/cgit/aur.git/plain/telegramdesktop.desktop?h=telegram-desktop` * tg.protocol: `https://aur.archlinux.org/cgit/aur.git/plain/tg.protocol?h=telegram-desktop` Preparation ----------- cd "$srcdir/tdesktop" mkdir -p "$srcdir/Libraries" local qt_patch_file="$srcdir/tdesktop/Telegram/Patches/qtbase_${_qtver//./_}.diff" local qt_dir="$srcdir/Libraries/qt${_qtver//./_}" if [ "$qt_patch_file" -nt "$qt_dir" ]; then rm -rf "$qt_dir" git clone git://code.qt.io/qt/qt5.git cd "$qt_dir" perl init-repository --module-subset=qtbase,qtimageformats git checkout v$_qtver cd qtimageformats git checkout v$_qtver cd ../qtbase git checkout v$_qtver git apply "$qt_patch_file" fi if [ ! -h "$srcdir/Libraries/breakpad" ]; then ln -s "$srcdir/breakpad" "$srcdir/Libraries/breakpad" ln -s "$srcdir/breakpad-lss" "$srcdir/Libraries/breakpad/src/third_party/lss" fi sed -i 's/CUSTOM_API_ID//g' "$srcdir/tdesktop/Telegram/Telegram.pro" ( echo "DEFINES += TDESKTOP_DISABLE_AUTOUPDATE" echo "DEFINES += TDESKTOP_DISABLE_REGISTER_CUSTOM_SCHEME" ) >> "$srcdir/tdesktop/Telegram/Telegram.pro" Building -------- # Build patched Qt cd "$qtdir" ./configure -prefix "$srcdir/qt" -release -opensource -confirm-license -qt-zlib \ -qt-libpng -qt-libjpeg -qt-freetype -qt-harfbuzz -qt-pcre -qt-xcb \ -qt-xkbcommon-x11 -no-opengl -no-gtkstyle -static -nomake examples -nomake tests make module-qtbase module-qtimageformats make module-qtbase-install_subtargets module-qtimageformats-install_subtargets export PATH="$srcdir/qt/bin:$PATH" # Build breakpad cd "$srcdir/Libraries/breakpad" ./configure make # Build codegen_style mkdir -p "$srcdir/tdesktop/Linux/obj/codegen_style/Debug" cd "$srcdir/tdesktop/Linux/obj/codegen_style/Debug" qmake CONFIG+=debug ../../../../Telegram/build/qmake/codegen_style/codegen_style.pro make # Build codegen_numbers mkdir -p "$srcdir/tdesktop/Linux/obj/codegen_numbers/Debug" cd "$srcdir/tdesktop/Linux/obj/codegen_numbers/Debug" qmake CONFIG+=debug ../../../../Telegram/build/qmake/codegen_numbers/codegen_numbers.pro make # Build MetaLang mkdir -p "$srcdir/tdesktop/Linux/DebugIntermediateLang" cd "$srcdir/tdesktop/Linux/DebugIntermediateLang" qmake CONFIG+=debug "../../Telegram/MetaLang.pro" make # Build Telegram Desktop mkdir -p "$srcdir/tdesktop/Linux/ReleaseIntermediate" cd "$srcdir/tdesktop/Linux/ReleaseIntermediate" qmake CONFIG+=release "../../Telegram/Telegram.pro" make Installation ------------ install -dm755 "$pkgdir/usr/bin" install -m755 "$srcdir/tdesktop/Linux/Release/Telegram" "$pkgdir/usr/bin/telegram-desktop" install -d "$pkgdir/usr/share/applications" install -m644 "$srcdir/telegramdesktop.desktop" "$pkgdir/usr/share/applications/telegramdesktop.desktop" install -d "$pkgdir/usr/share/kde4/services" install -m644 "$srcdir/tg.protocol" "$pkgdir/usr/share/kde4/services/tg.protocol" local icon_size icon_dir for icon_size in 16 32 48 64 128 256 512; do icon_dir="$pkgdir/usr/share/icons/hicolor/${icon_size}x${icon_size}/apps" install -d "$icon_dir" install -m644 "$srcdir/tdesktop/Telegram/SourceFiles/art/icon${icon_size}.png" "$icon_dir/telegram-desktop.png" done Notes ----- These instructions are based on the [ArchLinux package][arch-package] for telegram-desktop. In case these instructions are at some point out of date, the above may serve as an update reference. [arch-package]: https://aur.archlinux.org/packages/telegram-desktop/ [cmake]: building-cmake.md ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/docs/building-qtcreator.md����������������������������������������������������������0000664�0000000�0000000�00000022347�13262451251�0020521�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������## Build instructions for Qt Creator 3.5.1 under Ubuntu 12.04 **NB** These are outdated, please refer to [Building using GYP/CMake][cmake] instructions. ### Prepare * Install git by command **sudo apt-get install git** in Terminal * Install g++ by command **sudo apt-get install g++** in Terminal * Install Qt Creator from [**Downloads page**](https://www.qt.io/download/) You need to install g++ version 4.9 manually by such commands * sudo add-apt-repository ppa:ubuntu-toolchain-r/test * sudo apt-get update * sudo apt-get install gcc-4.9 g++-4.9 * sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.9 21 * sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.9 21 ### Prepare folder Choose a folder for the future build, for example **/home/user/TBuild** There you will have two folders, **Libraries** for third-party libs and **tdesktop** (or **tdesktop-master**) for the app. ### Clone source code By git – in Terminal go to **/home/user/TBuild** and run git clone --recursive https://github.com/telegramdesktop/tdesktop.git ### Prepare libraries Install dev libraries sudo apt-get install libexif-dev liblzma-dev libz-dev libssl-dev libappindicator-dev libunity-dev #### zlib 1.2.8 http://www.zlib.net/ > Download [**zlib source code, version 1.2.8, zipfile format**](http://zlib.net/zlib128.zip) Extract to **/home/user/TBuild/Libraries** ##### Building library In Terminal go to **/home/user/TBuild/Libraries/zlib-1.2.8** and run: ./configure make sudo make install Install audio libraries #### Opus codec In Terminal go to **/home/user/TBuild/Libraries** and run git clone https://github.com/xiph/opus cd opus git checkout v1.2-alpha2 ./autogen.sh ./configure make sudo make install #### FFmpeg In Terminal go to **/home/user/TBuild/Libraries** and run git clone git://anongit.freedesktop.org/git/libva cd libva ./autogen.sh --enable-static make sudo make install cd .. git clone https://github.com/FFmpeg/FFmpeg.git ffmpeg cd ffmpeg git checkout release/3.2 sudo apt-get update sudo apt-get -y --force-yes install autoconf automake build-essential libass-dev libfreetype6-dev libgpac-dev libsdl1.2-dev libtheora-dev libtool libva-dev libvdpau-dev libvorbis-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev pkg-config texi2html zlib1g-dev sudo apt-get install yasm ./configure --prefix=/usr/local --disable-programs --disable-doc --disable-everything --enable-protocol=file --enable-libopus --enable-decoder=aac --enable-decoder=aac_latm --enable-decoder=aasc --enable-decoder=flac --enable-decoder=gif --enable-decoder=h264 --enable-decoder=h264_vdpau --enable-decoder=mp1 --enable-decoder=mp1float --enable-decoder=mp2 --enable-decoder=mp2float --enable-decoder=mp3 --enable-decoder=mp3adu --enable-decoder=mp3adufloat --enable-decoder=mp3float --enable-decoder=mp3on4 --enable-decoder=mp3on4float --enable-decoder=mpeg4 --enable-decoder=mpeg4_vdpau --enable-decoder=msmpeg4v2 --enable-decoder=msmpeg4v3 --enable-decoder=opus --enable-decoder=pcm_alaw --enable-decoder=pcm_alaw_at --enable-decoder=pcm_f32be --enable-decoder=pcm_f32le --enable-decoder=pcm_f64be --enable-decoder=pcm_f64le --enable-decoder=pcm_lxf --enable-decoder=pcm_mulaw --enable-decoder=pcm_mulaw_at --enable-decoder=pcm_s16be --enable-decoder=pcm_s16be_planar --enable-decoder=pcm_s16le --enable-decoder=pcm_s16le_planar --enable-decoder=pcm_s24be --enable-decoder=pcm_s24daud --enable-decoder=pcm_s24le --enable-decoder=pcm_s24le_planar --enable-decoder=pcm_s32be --enable-decoder=pcm_s32le --enable-decoder=pcm_s32le_planar --enable-decoder=pcm_s64be --enable-decoder=pcm_s64le --enable-decoder=pcm_s8 --enable-decoder=pcm_s8_planar --enable-decoder=pcm_u16be --enable-decoder=pcm_u16le --enable-decoder=pcm_u24be --enable-decoder=pcm_u24le --enable-decoder=pcm_u32be --enable-decoder=pcm_u32le --enable-decoder=pcm_u8 --enable-decoder=pcm_zork --enable-decoder=vorbis --enable-decoder=wavpack --enable-decoder=wmalossless --enable-decoder=wmapro --enable-decoder=wmav1 --enable-decoder=wmav2 --enable-decoder=wmavoice --enable-encoder=libopus --enable-hwaccel=h264_vaapi --enable-hwaccel=h264_vdpau --enable-hwaccel=mpeg4_vaapi --enable-hwaccel=mpeg4_vdpau --enable-parser=aac --enable-parser=aac_latm --enable-parser=flac --enable-parser=h264 --enable-parser=mpeg4video --enable-parser=mpegaudio --enable-parser=opus --enable-parser=vorbis --enable-demuxer=aac --enable-demuxer=flac --enable-demuxer=gif --enable-demuxer=h264 --enable-demuxer=mov --enable-demuxer=mp3 --enable-demuxer=ogg --enable-demuxer=wav --enable-muxer=ogg --enable-muxer=opus make sudo make install #### PortAudio 19 [Download portaudio sources](http://www.portaudio.com/archives/pa_stable_v19_20140130.tgz) from **http://www.portaudio.com/download.html**, extract to **/home/user/TBuild/Libraries**, go to **/home/user/TBuild/Libraries/portaudio** and run ./configure make sudo make install #### OpenAL Soft In Terminal go to **/home/user/TBuild/Libraries** and run git clone git://repo.or.cz/openal-soft.git then go to **/home/user/TBuild/Libraries/openal-soft/build** and run sudo apt-get install cmake cmake -D LIBTYPE:STRING=STATIC .. make sudo make install #### OpenSSL In Terminal go to **/home/user/TBuild/Libraries** and run git clone https://github.com/openssl/openssl cd openssl git checkout OpenSSL_1_0_1-stable ./config make sudo make install #### libxkbcommon (required for Fcitx Qt plugin) In Terminal go to **/home/user/TBuild/Libraries** and run sudo apt-get install xutils-dev bison python-xcbgen git clone https://github.com/xkbcommon/libxkbcommon.git cd libxkbcommon ./autogen.sh --disable-x11 make sudo make install #### Qt 5.6.2, slightly patched In Terminal go to **/home/user/TBuild/Libraries** and run git clone git://code.qt.io/qt/qt5.git qt5_6_2 cd qt5_6_2 perl init-repository --module-subset=qtbase,qtimageformats git checkout v5.6.2 cd qtimageformats && git checkout v5.6.2 && cd .. cd qtbase && git checkout v5.6.2 && cd .. ##### Apply the patch cd qtbase && git apply ../../../tdesktop/Telegram/Patches/qtbase_5_6_2.diff && cd .. ##### Building library Install some packages for Qt (see **/home/user/TBuild/Libraries/qt5_6_2/qtbase/src/plugins/platforms/xcb/README**) sudo apt-get install libxcb1-dev libxcb-image0-dev libxcb-keysyms1-dev libxcb-icccm4-dev libxcb-render-util0-dev libxcb-util0-dev libxrender-dev libasound-dev libpulse-dev libxcb-sync0-dev libxcb-xfixes0-dev libxcb-randr0-dev libx11-xcb-dev libffi-dev In Terminal go to **/home/user/TBuild/Libraries/qt5_6_2** and there run ./configure -prefix "/usr/local/tdesktop/Qt-5.6.2" -release -force-debug-info -opensource -confirm-license -qt-zlib -qt-libpng -qt-libjpeg -qt-freetype -qt-harfbuzz -qt-pcre -qt-xcb -qt-xkbcommon-x11 -no-opengl -no-gtkstyle -static -openssl-linked -nomake examples -nomake tests make -j4 sudo make install building (**make** command) will take really long time. #### Google Breakpad In Terminal go to **/home/user/TBuild/Libraries** and run git clone https://chromium.googlesource.com/breakpad/breakpad git clone https://chromium.googlesource.com/linux-syscall-support breakpad/src/third_party/lss cd breakpad ./configure make sudo make install ### Building Telegram codegen utilities In Terminal go to **/home/user/TBuild/tdesktop** and run mkdir -p Linux/obj/codegen_style/Debug cd Linux/obj/codegen_style/Debug /usr/local/tdesktop/Qt-5.6.2/bin/qmake CONFIG+=debug ../../../../Telegram/build/qmake/codegen_style/codegen_style.pro make mkdir -p ../../codegen_numbers/Debug cd ../../codegen_numbers/Debug /usr/local/tdesktop/Qt-5.6.2/bin/qmake CONFIG+=debug ../../../../Telegram/build/qmake/codegen_numbers/codegen_numbers.pro make ### Building Telegram Desktop * Launch Qt Creator, all projects will be taken from **/home/user/TBuild/tdesktop/Telegram** * Tools > Options > Build & Run > Qt Versions tab > Add > File System /usr/local/tdesktop/Qt-5.6.2/bin/qmake > **Qt 5.6.2 (Qt-5.6.2)** > Apply * Tools > Options > Build & Run > Kits tab > Desktop (default) > change **Qt version** to **Qt 5.6.2 (Qt-5.6.2)** > Apply * Open MetaLang.pro, configure project with paths **/home/user/TBuild/tdesktop/Linux/DebugIntermediateLang** and **/home/user/TBuild/tdesktop/Linux/ReleaseIntermediateLang** and build for Debug * Open Telegram.pro, configure project with paths **/home/user/TBuild/tdesktop/Linux/DebugIntermediate** and **/home/user/TBuild/tdesktop/Linux/ReleaseIntermediate** and build for Debug, if GeneratedFiles are not found click **Run qmake** from **Build** menu and try again * Open Updater.pro, configure project with paths **/home/user/TBuild/tdesktop/Linux/DebugIntermediateUpdater** and **/home/user/TBuild/tdesktop/Linux/ReleaseIntermediateUpdater** and build for Debug * Release Telegram build will require removing **CUSTOM_API_ID** definition in Telegram.pro project and may require changing paths in **/home/user/TBuild/tdesktop/Telegram/FixMake.sh** or **/home/user/TBuild/tdesktop/Telegram/FixMake32.sh** for static library linking fix, static linking applies only on second Release build (first uses old Makefile) [cmake]: building-cmake.md �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/docs/building-xcode-old.md����������������������������������������������������������0000664�0000000�0000000�00000017426�13262451251�0020375�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������## Build instructions for Xcode 9.0 **NB** These are outdated, please refer to [Building using Xcode][xcode] instructions. ### Prepare folder Choose a folder for the future build, for example **/Users/user/TBuild**. It will be named ***BuildPath*** in the rest of this document. All commands will be launched from Terminal. ### Download libraries Download [**xz-5.0.5**](http://tukaani.org/xz/xz-5.0.5.tar.gz) and unpack to ***BuildPath*/Libraries/xz-5.0.5** Download [**libiconv-1.15**](http://www.gnu.org/software/libiconv/#downloading) and unpack to ***BuildPath*/Libraries/macold/libiconv-1.15** ### Clone source code and prepare libraries Go to ***BuildPath*** and run MACOSX_DEPLOYMENT_TARGET=10.6 git clone --recursive https://github.com/telegramdesktop/tdesktop.git cd Libraries cd xz-5.0.5 ./configure make sudo make install cd .. git clone https://github.com/Kitware/CMake cd CMake ./bootstrap make -j4 sudo make install cd .. cd macold svn co http://llvm.org/svn/llvm-project/llvm/trunk llvm cd llvm/projects svn co http://llvm.org/svn/llvm-project/libcxx/trunk libcxx svn co http://llvm.org/svn/llvm-project/libcxxabi/trunk libcxxabi cd ../../ mkdir libcxxabi cd libcxxabi cmake -G "Unix Makefiles" -DCMAKE_OSX_DEPLOYMENT_TARGET:STRING=10.6 -DCMAKE_BUILD_TYPE:STRING=Release -DLIBCXX_ENABLE_SHARED:BOOL=NO -DCMAKE_INSTALL_PREFIX:PATH=/usr/local/macold -DLLVM_PATH=../llvm -DLIBCXXABI_LIBCXX_PATH=../llvm/projects/libcxx ../llvm/projects/libcxxabi/ make -j4 sudo make install cd ../ mkdir libcxx cd libcxx cmake -G "Unix Makefiles" -DCMAKE_OSX_DEPLOYMENT_TARGET:STRING=10.6 -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr/local/macold -DLIBCXX_ENABLE_SHARED:BOOL=NO -DLIBCXX_CXX_ABI:STRING=libstdc++ -DLIBCXX_CXX_ABI_INCLUDE_PATHS="/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/4.2.1/" -DLLVM_PATH=../llvm/ ../llvm/projects/libcxx/ make -j4 sudo make install cd ../ git clone https://github.com/telegramdesktop/zlib.git cd zlib prefix=/usr/local/macold CFLAGS="-mmacosx-version-min=10.6" LDFLAGS="-mmacosx-version-min=10.6" ./configure make -j4 sudo make install cd .. git clone https://github.com/openssl/openssl cd openssl git checkout OpenSSL_1_0_1-stable ./Configure --install_prefix=/usr/local/macold darwin64-x86_64-cc -static -mmacosx-version-min=10.6 make build_libs -j4 cd .. git clone https://github.com/xiph/opus cd opus git checkout v1.2.1 ./autogen.sh CFLAGS="-mmacosx-version-min=10.6" CPPFLAGS="-mmacosx-version-min=10.6" LDFLAGS="-mmacosx-version-min=10.6" ./configure --prefix=/usr/local/macold make -j4 sudo make install cd .. cd libiconv-1.15 CFLAGS="-mmacosx-version-min=10.6" CPPFLAGS="-mmacosx-version-min=10.6 -nostdinc++" LDFLAGS="-mmacosx-version-min=10.6" ./configure --enable-static --prefix=/usr/local/macold make -j4 sudo make install cd .. git clone https://github.com/FFmpeg/FFmpeg.git ffmpeg cd ffmpeg git checkout release/3.4 CFLAGS=`freetype-config --cflags` LDFLAGS=`freetype-config --libs` PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig:/usr/lib/pkgconfig:/usr/X11/lib/pkgconfig ./configure --prefix=/usr/local/macold --disable-programs --disable-doc --disable-everything --enable-protocol=file --enable-libopus --enable-decoder=aac --enable-decoder=aac_latm --enable-decoder=aasc --enable-decoder=flac --enable-decoder=gif --enable-decoder=h264 --enable-decoder=mp1 --enable-decoder=mp1float --enable-decoder=mp2 --enable-decoder=mp2float --enable-decoder=mp3 --enable-decoder=mp3adu --enable-decoder=mp3adufloat --enable-decoder=mp3float --enable-decoder=mp3on4 --enable-decoder=mp3on4float --enable-decoder=mpeg4 --enable-decoder=msmpeg4v2 --enable-decoder=msmpeg4v3 --enable-decoder=opus --enable-decoder=pcm_alaw --enable-decoder=pcm_alaw_at --enable-decoder=pcm_f32be --enable-decoder=pcm_f32le --enable-decoder=pcm_f64be --enable-decoder=pcm_f64le --enable-decoder=pcm_lxf --enable-decoder=pcm_mulaw --enable-decoder=pcm_mulaw_at --enable-decoder=pcm_s16be --enable-decoder=pcm_s16be_planar --enable-decoder=pcm_s16le --enable-decoder=pcm_s16le_planar --enable-decoder=pcm_s24be --enable-decoder=pcm_s24daud --enable-decoder=pcm_s24le --enable-decoder=pcm_s24le_planar --enable-decoder=pcm_s32be --enable-decoder=pcm_s32le --enable-decoder=pcm_s32le_planar --enable-decoder=pcm_s64be --enable-decoder=pcm_s64le --enable-decoder=pcm_s8 --enable-decoder=pcm_s8_planar --enable-decoder=pcm_u16be --enable-decoder=pcm_u16le --enable-decoder=pcm_u24be --enable-decoder=pcm_u24le --enable-decoder=pcm_u32be --enable-decoder=pcm_u32le --enable-decoder=pcm_u8 --enable-decoder=pcm_zork --enable-decoder=vorbis --enable-decoder=wavpack --enable-decoder=wmalossless --enable-decoder=wmapro --enable-decoder=wmav1 --enable-decoder=wmav2 --enable-decoder=wmavoice --enable-encoder=libopus --enable-parser=aac --enable-parser=aac_latm --enable-parser=flac --enable-parser=h264 --enable-parser=mpeg4video --enable-parser=mpegaudio --enable-parser=opus --enable-parser=vorbis --enable-demuxer=aac --enable-demuxer=flac --enable-demuxer=gif --enable-demuxer=h264 --enable-demuxer=mov --enable-demuxer=mp3 --enable-demuxer=ogg --enable-demuxer=wav --enable-muxer=ogg --enable-muxer=opus --extra-cflags="-mmacosx-version-min=10.6" --extra-cxxflags="-mmacosx-version-min=10.6 -nostdinc++" --extra-ldflags="-mmacosx-version-min=10.6" make -j4 sudo make install cd .. git clone git://repo.or.cz/openal-soft.git cd openal-soft/build cmake -DLIBTYPE:STRING=STATIC -DCMAKE_OSX_DEPLOYMENT_TARGET:STRING=10.6 -DCMAKE_INSTALL_PREFIX:STRING=/usr/local/macold .. make -j4 sudo make install cd ../.. git clone https://github.com/telegramdesktop/libexif-0.6.20.git cd libexif-0.6.20 CFLAGS="-mmacosx-version-min=10.6" CPPFLAGS="-mmacosx-version-min=10.6 -nostdinc++" LDFLAGS="-mmacosx-version-min=10.6" ./configure --prefix=/usr/local/macold make -j4 sudo make install git clone https://chromium.googlesource.com/crashpad/crashpad.git cd crashpad git checkout feb3aa3923 git apply ../../../tdesktop/Telegram/Patches/macold/crashpad.diff cd third_party/mini_chromium git clone https://chromium.googlesource.com/chromium/mini_chromium cd mini_chromium git checkout 7c5b0c1ab4 git apply ../../../../../../tdesktop/Telegram/Patches/macold/mini_chromium.diff cd ../../gtest git clone https://chromium.googlesource.com/external/github.com/google/googletest gtest cd gtest git checkout d62d6c6556 cd ../../../ build/gyp_crashpad.py -Dmac_deployment_target=10.6 ninja -C out/Debug ninja -C out/Release cd .. CurDir=`pwd` git clone git://code.qt.io/qt/qt5.git qt5_3_2 cd qt5_3_2 perl init-repository --module-subset=qtbase,qtimageformats git checkout v5.3.2 cd qtimageformats && git checkout v5.3.2 && git apply ../../../../tdesktop/Telegram/Patches/macold/qtimageformats_5_3_2.diff && cd .. cd qtbase && git checkout v5.3.2 && git apply ../../../../tdesktop/Telegram/Patches/macold/qtbase_5_3_2.diff && cd .. OPENSSL_LIBS="$CurDir/openssl/libssl.a $CurDir/openssl/libcrypto.a" ./configure -prefix "/usr/local/macold/Qt-5.3.2" -debug-and-release -force-debug-info -opensource -confirm-license -static -opengl desktop -openssl-linked -I "$CurDir/openssl/include" -no-glib -nomake examples -nomake tests -platform macx-g++ make -j4 sudo make install cd .. ### Building the project Go to ***BuildPath*/tdesktop/Telegram** and run gyp/refresh.sh Then launch Xcode, open ***BuildPath*/tdesktop/Telegram/Telegram.xcodeproj** and build for Debug / Release. [xcode]: building-xcode.md ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/docs/building-xcode.md��������������������������������������������������������������0000664�0000000�0000000�00000015413�13262451251�0017613�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������## Build instructions for Xcode 9.0 ### Prepare folder Choose a folder for the future build, for example **/Users/user/TBuild**. It will be named ***BuildPath*** in the rest of this document. All commands will be launched from Terminal. ### Download libraries Download [**xz-5.0.5**](http://tukaani.org/xz/xz-5.0.5.tar.gz) and unpack to ***BuildPath*/Libraries/xz-5.0.5** Download [**libiconv-1.15**](http://www.gnu.org/software/libiconv/#downloading) and unpack to ***BuildPath*/Libraries/libiconv-1.15** ### Clone source code and prepare libraries Go to ***BuildPath*** and run MACOSX_DEPLOYMENT_TARGET=10.8 ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" brew install automake fdk-aac git lame libass libtool libvorbis libvpx opus sdl shtool texi2html theora wget x264 xvid yasm automake libtool sudo xcode-select -s /Applications/Xcode.app/Contents/Developer git clone --recursive https://github.com/telegramdesktop/tdesktop.git cd Libraries git clone https://github.com/ericniebler/range-v3 cd xz-5.0.5 CFLAGS="-mmacosx-version-min=10.8" LDFLAGS="-mmacosx-version-min=10.8" ./configure make sudo make install cd .. git clone https://github.com/Kitware/CMake cd CMake ./bootstrap make -j4 sudo make install cd .. git clone https://github.com/telegramdesktop/zlib.git cd zlib CFLAGS="-mmacosx-version-min=10.8" LDFLAGS="-mmacosx-version-min=10.8" ./configure make -j4 sudo make install cd .. git clone https://github.com/openssl/openssl cd openssl git checkout OpenSSL_1_0_1-stable ./Configure darwin64-x86_64-cc -static -mmacosx-version-min=10.8 make build_libs -j4 cd .. git clone https://github.com/xiph/opus cd opus git checkout v1.2.1 ./autogen.sh CFLAGS="-mmacosx-version-min=10.8" CPPFLAGS="-mmacosx-version-min=10.8" LDFLAGS="-mmacosx-version-min=10.8" ./configure make -j4 sudo make install cd .. cd libiconv-1.15 CFLAGS="-mmacosx-version-min=10.8" CPPFLAGS="-mmacosx-version-min=10.8" LDFLAGS="-mmacosx-version-min=10.8" ./configure --enable-static make -j4 sudo make install cd .. git clone https://github.com/FFmpeg/FFmpeg.git ffmpeg cd ffmpeg git checkout release/3.4 CFLAGS=`freetype-config --cflags` LDFLAGS=`freetype-config --libs` PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig:/usr/lib/pkgconfig:/usr/X11/lib/pkgconfig ./configure --prefix=/usr/local --disable-programs --disable-doc --disable-everything --enable-protocol=file --enable-libopus --enable-decoder=aac --enable-decoder=aac_latm --enable-decoder=aasc --enable-decoder=flac --enable-decoder=gif --enable-decoder=h264 --enable-decoder=h264_vda --enable-decoder=mp1 --enable-decoder=mp1float --enable-decoder=mp2 --enable-decoder=mp2float --enable-decoder=mp3 --enable-decoder=mp3adu --enable-decoder=mp3adufloat --enable-decoder=mp3float --enable-decoder=mp3on4 --enable-decoder=mp3on4float --enable-decoder=mpeg4 --enable-decoder=msmpeg4v2 --enable-decoder=msmpeg4v3 --enable-decoder=opus --enable-decoder=pcm_alaw --enable-decoder=pcm_alaw_at --enable-decoder=pcm_f32be --enable-decoder=pcm_f32le --enable-decoder=pcm_f64be --enable-decoder=pcm_f64le --enable-decoder=pcm_lxf --enable-decoder=pcm_mulaw --enable-decoder=pcm_mulaw_at --enable-decoder=pcm_s16be --enable-decoder=pcm_s16be_planar --enable-decoder=pcm_s16le --enable-decoder=pcm_s16le_planar --enable-decoder=pcm_s24be --enable-decoder=pcm_s24daud --enable-decoder=pcm_s24le --enable-decoder=pcm_s24le_planar --enable-decoder=pcm_s32be --enable-decoder=pcm_s32le --enable-decoder=pcm_s32le_planar --enable-decoder=pcm_s64be --enable-decoder=pcm_s64le --enable-decoder=pcm_s8 --enable-decoder=pcm_s8_planar --enable-decoder=pcm_u16be --enable-decoder=pcm_u16le --enable-decoder=pcm_u24be --enable-decoder=pcm_u24le --enable-decoder=pcm_u32be --enable-decoder=pcm_u32le --enable-decoder=pcm_u8 --enable-decoder=pcm_zork --enable-decoder=vorbis --enable-decoder=wavpack --enable-decoder=wmalossless --enable-decoder=wmapro --enable-decoder=wmav1 --enable-decoder=wmav2 --enable-decoder=wmavoice --enable-encoder=libopus --enable-hwaccel=mpeg4_videotoolbox --enable-hwaccel=h264_vda --enable-hwaccel=h264_videotoolbox --enable-parser=aac --enable-parser=aac_latm --enable-parser=flac --enable-parser=h264 --enable-parser=mpeg4video --enable-parser=mpegaudio --enable-parser=opus --enable-parser=vorbis --enable-demuxer=aac --enable-demuxer=flac --enable-demuxer=gif --enable-demuxer=h264 --enable-demuxer=mov --enable-demuxer=mp3 --enable-demuxer=ogg --enable-demuxer=wav --enable-muxer=ogg --enable-muxer=opus --extra-cflags="-mmacosx-version-min=10.8" --extra-cxxflags="-mmacosx-version-min=10.8" --extra-ldflags="-mmacosx-version-min=10.8" make -j4 sudo make install cd .. git clone git://repo.or.cz/openal-soft.git cd openal-soft/build cmake -D LIBTYPE:STRING=STATIC -D CMAKE_OSX_DEPLOYMENT_TARGET:STRING=10.8 .. make -j4 sudo make install cd ../.. git clone https://chromium.googlesource.com/external/gyp git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git export PATH="$PWD/depot_tools:$PATH" cd gyp git checkout 702ac58e47 git apply ../../tdesktop/Telegram/Patches/gyp.diff ./setup.py build sudo ./setup.py install cd .. git clone https://chromium.googlesource.com/crashpad/crashpad.git cd crashpad git checkout feb3aa3923 git apply ../../tdesktop/Telegram/Patches/crashpad.diff cd third_party/mini_chromium git clone https://chromium.googlesource.com/chromium/mini_chromium cd mini_chromium git checkout 7c5b0c1ab4 git apply ../../../../../tdesktop/Telegram/Patches/mini_chromium.diff cd ../../gtest git clone https://chromium.googlesource.com/external/github.com/google/googletest gtest cd gtest git checkout d62d6c6556 cd ../../.. build/gyp_crashpad.py -Dmac_deployment_target=10.8 ninja -C out/Debug ninja -C out/Release cd .. git clone git://code.qt.io/qt/qt5.git qt5_6_2 cd qt5_6_2 perl init-repository --module-subset=qtbase,qtimageformats git checkout v5.6.2 cd qtimageformats && git checkout v5.6.2 && cd .. cd qtbase && git checkout v5.6.2 && git apply ../../../tdesktop/Telegram/Patches/qtbase_5_6_2.diff && cd .. ./configure -prefix "/usr/local/tdesktop/Qt-5.6.2" -debug-and-release -force-debug-info -opensource -confirm-license -static -opengl desktop -no-openssl -securetransport -nomake examples -nomake tests -platform macx-clang make -j4 sudo make install cd .. ### Building the project Go to ***BuildPath*/tdesktop/Telegram** and run gyp/refresh.sh Then launch Xcode, open ***BuildPath*/tdesktop/Telegram/Telegram.xcodeproj** and build for Debug / Release. �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/docs/misc.txt�����������������������������������������������������������������������0000664�0000000�0000000�00000001141�13262451251�0016061�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������macOS: breakpad - clone breakpad like for msvc, including the patch, open breakpad/src/client/mac/Breakpad.xcodeproj and build Release dump_syms - open breakpad/src/tools/mac/dump_syms/dump_syms.xcodeproj and build Release, copy dump_syms from ~/Library/Developer/Xcode/DerivedData/dump_syms-[a-z0-9]+/Build/Products/Release/ to BuildPath/Libraries/breakpad/src/tools/mac/dump_syms/build/Release/ minidump_stackwalk - go to Libraries/breakpad, run "configure", run "make" Windows: install InnoSetup open brekpad/src/tools/windows/dump_syms/dump_syms.vcproj, upgrade, change SDK and build Debug and Release �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/lib/��������������������������������������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0014206�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/lib/xdg/����������������������������������������������������������������������������0000775�0000000�0000000�00000000000�13262451251�0014770�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/lib/xdg/telegramdesktop.appdata.xml�������������������������������������������������0000664�0000000�0000000�00000001667�13262451251�0022327�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?xml version="1.0" encoding="utf-8"?> <component type="desktop"> <id>org.telegram.desktop</id> <metadata_license>CC0-1.0</metadata_license> <project_license>GPL-3.0</project_license> <name>Telegram Desktop</name> <summary>Telegram Desktop messenger</summary> <description> <p>Fast and secure desktop app, perfectly synced with your mobile phone.</p> </description> <categories> <category>Network</category> <category>InstantMessaging</category> </categories> <url type="homepage">https://desktop.telegram.org/</url> <url type="bugtracker">https://github.com/telegramdesktop/tdesktop/issues</url> <screenshots> <screenshot type="default"> <image>https://raw.githubusercontent.com/telegramdesktop/tdesktop/dev/docs/assets/preview.png</image> </screenshot> </screenshots> <provides> <binary>telegram-desktop</binary> </provides> </component> �������������������������������������������������������������������������tdesktop-1.2.17/lib/xdg/telegramdesktop.desktop�����������������������������������������������������0000664�0000000�0000000�00000000434�13262451251�0021556�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[Desktop Entry] Version=1.0 Name=Telegram Desktop Comment=Official desktop version of Telegram messaging app Exec=telegram-desktop -- %u Icon=telegram Terminal=false StartupWMClass=TelegramDesktop Type=Application Categories=Network;InstantMessaging;Qt; MimeType=x-scheme-handler/tg; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tdesktop-1.2.17/lib/xdg/tg.protocol�����������������������������������������������������������������0000664�0000000�0000000�00000000246�13262451251�0017167�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[Protocol] exec=/usr/bin/telegram-desktop -- %u protocol=tg input=none output=none helper=true listing=false reading=false writing=false makedir=false deleting=false ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������